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 };