diff --git a/docs/examples/cancel.tsx b/docs/examples/cancel.tsx new file mode 100644 index 0000000..4335951 --- /dev/null +++ b/docs/examples/cancel.tsx @@ -0,0 +1,73 @@ +import { useEffect, useRef, useState } from 'react'; +import Selectable, { SelectableRef, useSelectable } from 'react-selectable-box'; + +const list: string[] = []; +for (let i = 0; i < 200; i++) { + list.push(String(i)); +} + +const Item = ({ value }: { value: string }) => { + const { setNodeRef, isSelected, isAdding, isRemoving } = useSelectable({ + value, + }); + + return ( +
+ ); +}; + +export default () => { + const [value, setValue] = useState([]); + const selectableRef = useRef(null); + + useEffect(() => { + const onKeyDown = (e: KeyboardEvent) => { + if (e.code === 'Escape') { + selectableRef.current?.cancel(); + } + }; + + document.addEventListener('keydown', onKeyDown); + + return () => { + document.removeEventListener('keydown', onKeyDown); + }; + }, []); + + return ( + document.getElementById('drag-container') as HTMLElement} + onEnd={(selectingValue, { added, removed }) => { + console.log('onEnd'); + const result = value.concat(added).filter((i) => !removed.includes(i)); + setValue(result); + }} + > +
+ {list.map((i) => ( + + ))} +
+
+ ); +}; diff --git a/docs/guides/api.md b/docs/guides/api.md index eedd5fd..7094b2b 100644 --- a/docs/guides/api.md +++ b/docs/guides/api.md @@ -33,13 +33,13 @@ const { setNodeRef, isSelected, isAdding, isRemoving, isSelecting, isDragging } }); ``` -| 参数 | 说明 | 类型 | 默认值 | +| Property | Description | Type | Default | | -------- | ------------------------------------------- | -------------------------- | ----------- | | value | The value of the current selectable element | string \| number | - | | disabled | Whether to disable | boolean | false | | rule | Selection rule | `collision` \| `inclusion` | `collision` | -| 参数 | 说明 | 类型 | +| Property | 说明 | 类型 | | ----------- | -------------------------------------- | -------------------------------------- | | setNodeRef | Set the selectable element | (element: HTMLElement \| null) => void | | isDragging | Whether it is currently dragging | boolean | @@ -47,3 +47,9 @@ const { setNodeRef, isSelected, isAdding, isRemoving, isSelecting, isDragging } | isAdding | Whether it is currently being added | boolean | | isRemoving | Whether it is currently being removed | boolean | | isSelecting | Whether it is currently being selected | boolean | + +### Methods + +| Name | Description | Type | +| ------ | ---------------- | ---------- | +| cancel | cancel selection | () => void | diff --git a/docs/guides/api.zh-CN.md b/docs/guides/api.zh-CN.md index 1083fba..4d2d726 100644 --- a/docs/guides/api.zh-CN.md +++ b/docs/guides/api.zh-CN.md @@ -47,3 +47,9 @@ const { setNodeRef, isSelected, isAdding, isRemoving, isSelecting, isDragging } | isAdding | 当前是否正在添加 | boolean | | isRemoving | 当前是否正在删除 | boolean | | isSelecting | 当前是否被框选 | boolean | + +### 方法 + +| 名称 | 说明 | 类型 | +| ------ | -------- | ---------- | +| cancel | 取消选择 | () => void | diff --git a/docs/guides/cancel.md b/docs/guides/cancel.md new file mode 100644 index 0000000..b06c50f --- /dev/null +++ b/docs/guides/cancel.md @@ -0,0 +1,11 @@ +--- +title: cancel +group: + title: examples + order: 1 +order: 9 +--- + +### Press Esc to cancel + + diff --git a/docs/guides/cancel.zh-CN.md b/docs/guides/cancel.zh-CN.md new file mode 100644 index 0000000..a973be9 --- /dev/null +++ b/docs/guides/cancel.zh-CN.md @@ -0,0 +1,11 @@ +--- +title: 取消 +group: + title: 示例 + order: 1 +order: 9 +--- + +### 按下 Esc 取消 + + diff --git a/src/Selectable.tsx b/src/Selectable.tsx index 3b75423..96b7bf9 100644 --- a/src/Selectable.tsx +++ b/src/Selectable.tsx @@ -27,21 +27,28 @@ export interface SelectableProps { getContainer?: () => HTMLElement; } -function Selectable({ - defaultValue, - value: propsValue, - disabled, - mode = 'add', - children, - selectStartRange = 'all', - getContainer, - scrollContainer: propsScrollContainer, - dragContainer: propsDragContainer, - boxStyle, - boxClassName, - onStart, - onEnd, -}: SelectableProps) { +export interface SelectableRef { + cancel: () => void; +} + +function Selectable( + { + defaultValue, + value: propsValue, + disabled, + mode = 'add', + children, + selectStartRange = 'all', + getContainer, + scrollContainer: propsScrollContainer, + dragContainer: propsDragContainer, + boxStyle, + boxClassName, + onStart, + onEnd, + }: SelectableProps, + ref: React.ForwardedRef, +) { const [isDragging, setIsDragging] = useState(false); const [startCoords, setStartCoords] = useState({ x: 0, y: 0 }); const [moveCoords, setMoveCoords] = useState({ x: 0, y: 0 }); @@ -52,6 +59,7 @@ function Selectable({ const [value, setValue] = useMergedState(defaultValue || [], { value: propsValue, }); + const [isCanceled, setIsCanceled] = useState(false); const scrollContainer = useContainer(propsScrollContainer || getContainer); const dragContainer = useContainer(propsDragContainer || propsScrollContainer || getContainer); @@ -73,6 +81,13 @@ function Selectable({ ); } + React.useImperativeHandle(ref, () => ({ + cancel: () => { + setIsCanceled(true); + setIsDragging(false); + }, + })); + const onScroll = () => { if (isDraggingRef.current && scrollContainer) { const containerRect = scrollContainer.getBoundingClientRect(); @@ -114,13 +129,14 @@ function Selectable({ const reset = () => { setIsDragging(false); + setIsCanceled(false); setStartTarget(null); isMouseDowning = false; startInside.current = false; selectingValue.current = []; }; - if (disabled || !scrollContainer || !dragContainer) { + if (disabled || !scrollContainer || !dragContainer || isCanceled) { reset(); return; } @@ -210,7 +226,7 @@ function Selectable({ window.removeEventListener('touchmove', onMouseMove); window.removeEventListener('touchend', onMouseUp); }; - }, [disabled, scrollContainer, dragContainer]); + }, [disabled, scrollContainer, dragContainer, isCanceled]); const contextValue = useMemo( () => ({ @@ -252,4 +268,6 @@ function Selectable({ ); } -export default Selectable; +export default React.forwardRef(Selectable) as ( + props: SelectableProps & React.RefAttributes, +) => React.ReactElement; diff --git a/src/index.ts b/src/index.ts index 33083b8..abc5799 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import Selectable from './Selectable'; import useSelectable from './hooks/useSelectable'; -export type { SelectableProps } from './Selectable'; +export type { SelectableProps, SelectableRef } from './Selectable'; export { useSelectable };