diff --git a/README.md b/README.md index 26f02a7..9b86e8a 100644 --- a/README.md +++ b/README.md @@ -46,27 +46,25 @@ A React component that allows you to select elements in the drag area using the -## 简介 +## Introduction A React component that allows you to select elements in the drag area using the mouse -## 快速上手 +## Usage -### 安装 - -推荐使用 `pnpm` 安装 +### Install ```bash pnpm i react-selectable-box ``` -### 使用 +### Docs [docs](https://linxianxi.github.io/react-selectable-box/) -## 迭代记录 +## Changelog -详情:[CHANGELOG](https://linxianxi.github.io/react-selectable-box/changelog) +[CHANGELOG](https://linxianxi.github.io/react-selectable-box/changelog)
diff --git a/docs/examples/basic.tsx b/docs/examples/basic.tsx index b8efd8b..6ff5243 100644 --- a/docs/examples/basic.tsx +++ b/docs/examples/basic.tsx @@ -1,4 +1,4 @@ -import { Radio, Space, Switch } from 'antd'; +import { Descriptions, Radio, Switch } from 'antd'; import React, { useState } from 'react'; import Selectable, { useSelectable } from 'react-selectable-box'; @@ -17,17 +17,13 @@ const Item = ({ value, rule }: { value: string; rule: 'collision' | 'inclusion'
- {value} -
+ /> ); }; @@ -40,41 +36,48 @@ export default () => { return (
- -
- enable: -
-
- selectFromInside: -
-
- rule: - setRule(e.target.value)} - buttonStyle="solid" - optionType="button" - options={[ - { label: 'collision', value: 'collision' }, - { label: 'inclusion', value: 'inclusion' }, - ]} - /> -
-
- mode: - setMode(e.target.value)} - buttonStyle="solid" - optionType="button" - options={[ - { label: 'add', value: 'add' }, - { label: 'remove', value: 'remove' }, - { label: 'reverse', value: 'reverse' }, - ]} - /> -
-
+ }, + { + label: 'selectFromInside', + children: , + }, + { + label: 'rule', + children: ( + setRule(e.target.value)} + buttonStyle="solid" + optionType="button" + options={[ + { label: 'collision', value: 'collision' }, + { label: 'inclusion', value: 'inclusion' }, + ]} + /> + ), + }, + { + label: 'mode', + children: ( + setMode(e.target.value)} + buttonStyle="solid" + optionType="button" + options={[ + { label: 'add', value: 'add' }, + { label: 'remove', value: 'remove' }, + { label: 'reverse', value: 'reverse' }, + ]} + /> + ), + }, + ]} + /> + onClick(isSelected)} - > - {value} -
+ /> ); }; diff --git a/docs/examples/item-disabled.tsx b/docs/examples/item-disabled.tsx new file mode 100644 index 0000000..f73df33 --- /dev/null +++ b/docs/examples/item-disabled.tsx @@ -0,0 +1,56 @@ +import React, { useState } from 'react'; +import Selectable, { 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 disabled = ['46', '47', '48'].includes(value); + const { setNodeRef, isSelected, isAdding, isRemoving } = useSelectable({ + value, + disabled, + }); + + return ( +
+ ); +}; + +export default () => { + const [value, setValue] = useState([]); + + return ( + { + const result = value.concat(added).filter((i) => !removed.includes(i)); + setValue(result); + }} + > +
+ {list.map((i) => ( + + ))} +
+
+ ); +}; diff --git a/docs/examples/reset-at-end.tsx b/docs/examples/reset-at-end.tsx index f9a6e9a..a8a2635 100644 --- a/docs/examples/reset-at-end.tsx +++ b/docs/examples/reset-at-end.tsx @@ -15,17 +15,13 @@ const Item = ({ value }: { value: string }) => {
- {value} -
+ /> ); }; diff --git a/docs/examples/reset-at-start.tsx b/docs/examples/reset-at-start.tsx index d3fc289..f0e76d7 100644 --- a/docs/examples/reset-at-start.tsx +++ b/docs/examples/reset-at-start.tsx @@ -15,17 +15,13 @@ const Item = ({ value }: { value: string }) => {
- {value} -
+ /> ); }; diff --git a/docs/examples/scroll-container.tsx b/docs/examples/scroll-container.tsx index ab928ee..2ecf421 100644 --- a/docs/examples/scroll-container.tsx +++ b/docs/examples/scroll-container.tsx @@ -15,17 +15,13 @@ const Item = ({ value }: { value: string }) => {
- {value} -
+ /> ); }; diff --git a/docs/examples/shift-remove.tsx b/docs/examples/shift-remove.tsx index 0a5cd2e..53a61c3 100644 --- a/docs/examples/shift-remove.tsx +++ b/docs/examples/shift-remove.tsx @@ -15,17 +15,13 @@ const Item = ({ value }: { value: string }) => {
- {value} -
+ /> ); }; diff --git a/docs/examples/sort.tsx b/docs/examples/sort.tsx new file mode 100644 index 0000000..ca1287d --- /dev/null +++ b/docs/examples/sort.tsx @@ -0,0 +1,147 @@ +import { DndContext } from '@dnd-kit/core'; +import { SortableContext, useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import React, { useState } from 'react'; +import Selectable, { useSelectable } from 'react-selectable-box'; + +const list: string[] = []; +for (let i = 0; i < 25; i++) { + list.push(String(i)); +} + +const Item = ({ + value, + disabled, + selectedLength, +}: { + value: React.Key; + disabled: boolean; + selectedLength: number; +}) => { + const { + setNodeRef: setSelectNodeRef, + isSelected, + isAdding, + isRemoving, + } = useSelectable({ + value, + }); + + const { + attributes, + listeners, + setNodeRef: setSortNodeRef, + transform, + transition, + isDragging, + isSorting, + } = useSortable({ + id: value, + disabled, + }); + + return ( +
{ + setSelectNodeRef(ref); + setSortNodeRef(ref); + }} + className="text" + style={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + position: 'relative', + zIndex: isDragging ? 999 : undefined, + width: 50, + height: 50, + borderRadius: 4, + transform: CSS.Transform.toString(transform), + transition, + visibility: isSelected && !isDragging && isSorting ? 'hidden' : undefined, + border: isAdding ? '1px solid #1677ff' : undefined, + background: isRemoving ? 'red' : isSelected ? '#1677ff' : '#ccc', + }} + > + {value} + {isDragging && ( +
{selectedLength}
+ )} +
+ ); +}; + +export default () => { + const [value, setValue] = useState([]); + const [items, setItems] = useState(list); + const [beforeSortItems, setBeforeSortItems] = useState([]); + const [isSorting, setIsSorting] = useState(false); + + return ( + { + setIsSorting(true); + const index = event.active.data.current?.sortable.index as number; + let unSelectItems: React.Key[] = []; + let sortValue: React.Key[] = []; + items.forEach((item) => { + if (value.includes(item)) { + sortValue.push(item); + } else { + unSelectItems.push(item); + } + }); + if (index <= unSelectItems.length) { + unSelectItems.splice(index, 0, items[index]); + } else { + unSelectItems.push(...sortValue); + } + setBeforeSortItems(items); + setItems(unSelectItems); + }} + onDragEnd={(event) => { + setIsSorting(false); + if (event.over) { + const index = event.over?.data.current?.sortable.index; + const newItems = items.filter((item) => !value.includes(item)); + const sortValue = beforeSortItems.filter((item) => value.includes(item)); + newItems.splice(index, 0, ...sortValue); + setItems(newItems); + } else { + setItems(beforeSortItems); + } + setValue([]); + }} + > + + document.querySelector('.container')} + onEnd={(selectingValue, { added, removed }) => { + const result = value.concat(added).filter((i) => !removed.includes(i)); + setValue(result); + }} + > +
+ {items.map((i) => ( + + ))} +
+
+
+
+ ); +}; diff --git a/docs/examples/virtual-list.tsx b/docs/examples/virtual-list.tsx index 5b346ff..bf9e56e 100644 --- a/docs/examples/virtual-list.tsx +++ b/docs/examples/virtual-list.tsx @@ -19,17 +19,13 @@ const Cell = ({ value, style }: { value: string; style: React.CSSProperties }) = ref={setNodeRef} style={{ ...style, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', width: 50, height: 50, + borderRadius: 4, border: isAdding ? '1px solid #1677ff' : undefined, background: isRemoving ? 'red' : isSelected ? '#1677ff' : '#ccc', }} - > - {value} -
+ /> ); }; diff --git a/docs/guides/basic.md b/docs/guides/basic.md index 22699c6..8ab343c 100644 --- a/docs/guides/basic.md +++ b/docs/guides/basic.md @@ -6,6 +6,6 @@ group: order: 0 --- -### basic +### Basic diff --git a/docs/guides/click-to-select.md b/docs/guides/click-to-select.md index d06f81b..a00aa96 100644 --- a/docs/guides/click-to-select.md +++ b/docs/guides/click-to-select.md @@ -6,6 +6,6 @@ group: order: 5 --- -### click to select +### You can add a click event to the item to achieve click selection diff --git a/docs/guides/item-disabled.md b/docs/guides/item-disabled.md new file mode 100644 index 0000000..ec04863 --- /dev/null +++ b/docs/guides/item-disabled.md @@ -0,0 +1,11 @@ +--- +title: item disabled +group: + title: examples + order: 1 +order: 1 +--- + +### Set some items to be unselectable + + diff --git a/docs/guides/item-disabled.zh-CN.md b/docs/guides/item-disabled.zh-CN.md new file mode 100644 index 0000000..5fe6207 --- /dev/null +++ b/docs/guides/item-disabled.zh-CN.md @@ -0,0 +1,11 @@ +--- +title: item disabled +group: + title: examples + order: 1 +order: 1 +--- + +### 设置某些项不可选择 + + diff --git a/docs/guides/reset-at-end.md b/docs/guides/reset-at-end.md index 30a2290..9988740 100644 --- a/docs/guides/reset-at-end.md +++ b/docs/guides/reset-at-end.md @@ -3,9 +3,9 @@ title: reset at end group: title: examples order: 1 -order: 2 +order: 3 --- -### reset at end +### Reset previous selection when box selection ends diff --git a/docs/guides/reset-at-end.zh-CN.md b/docs/guides/reset-at-end.zh-CN.md index f2483c4..4851fd5 100644 --- a/docs/guides/reset-at-end.zh-CN.md +++ b/docs/guides/reset-at-end.zh-CN.md @@ -3,7 +3,7 @@ title: 框选结束时重置 group: title: 示例 order: 1 -order: 2 +order: 3 --- ### 框选结束时重置之前的选择 diff --git a/docs/guides/reset-at-start.md b/docs/guides/reset-at-start.md index 0cdf54c..f48e540 100644 --- a/docs/guides/reset-at-start.md +++ b/docs/guides/reset-at-start.md @@ -3,9 +3,9 @@ title: reset at start group: title: examples order: 1 -order: 1 +order: 2 --- -### reset at start +### Reset previous selection when box selection starts diff --git a/docs/guides/reset-at-start.zh-CN.md b/docs/guides/reset-at-start.zh-CN.md index dfab8c0..83a1cde 100644 --- a/docs/guides/reset-at-start.zh-CN.md +++ b/docs/guides/reset-at-start.zh-CN.md @@ -3,7 +3,7 @@ title: 框选开始时重置 group: title: 示例 order: 1 -order: 1 +order: 2 --- ### 框选开始时重置之前的选择 diff --git a/docs/guides/scroll-container.md b/docs/guides/scroll-container.md index 3c87c86..28a8ca3 100644 --- a/docs/guides/scroll-container.md +++ b/docs/guides/scroll-container.md @@ -3,9 +3,9 @@ title: scroll-container group: title: examples order: 1 -order: 3 +order: 4 --- -### scrollContainer +### You can set the box selection container diff --git a/docs/guides/scroll-container.zh-CN.md b/docs/guides/scroll-container.zh-CN.md index f3ddca2..43b8d1f 100644 --- a/docs/guides/scroll-container.zh-CN.md +++ b/docs/guides/scroll-container.zh-CN.md @@ -3,7 +3,7 @@ title: 设置框选滚动的容器 group: title: 示例 order: 1 -order: 3 +order: 4 --- ### 可以设置框选滚动的容器 diff --git a/docs/guides/shift-remove.md b/docs/guides/shift-remove.md index 39ec7f7..f8269a5 100644 --- a/docs/guides/shift-remove.md +++ b/docs/guides/shift-remove.md @@ -3,7 +3,7 @@ title: hold shift to remove group: title: examples order: 1 -order: 4 +order: 6 --- ### hold shift to remove diff --git a/docs/guides/shift-remove.zh-CN.md b/docs/guides/shift-remove.zh-CN.md index 9104538..aea6153 100644 --- a/docs/guides/shift-remove.zh-CN.md +++ b/docs/guides/shift-remove.zh-CN.md @@ -3,7 +3,7 @@ title: 按住 shift 框选删除 group: title: 示例 order: 1 -order: 4 +order: 6 --- ### 按住 shift 框选进行删除 diff --git a/docs/guides/sort.md b/docs/guides/sort.md new file mode 100644 index 0000000..5271ec3 --- /dev/null +++ b/docs/guides/sort.md @@ -0,0 +1,11 @@ +--- +title: sort +group: + title: examples + order: 1 +order: 8 +--- + +### sort + + diff --git a/docs/guides/sort.zh-CN.md b/docs/guides/sort.zh-CN.md new file mode 100644 index 0000000..83b632a --- /dev/null +++ b/docs/guides/sort.zh-CN.md @@ -0,0 +1,11 @@ +--- +title: sort +group: + title: examples + order: 1 +order: 8 +--- + +### Use `dnd-kit` to sort after selecting some options + + diff --git a/docs/guides/virtual-list.md b/docs/guides/virtual-list.md index 76cb2f0..575fd85 100644 --- a/docs/guides/virtual-list.md +++ b/docs/guides/virtual-list.md @@ -3,9 +3,9 @@ title: virtual-list group: title: examples order: 1 -order: 6 +order: 7 --- -### virtual list +### Box selection in a virtual list using `react-window` diff --git a/docs/guides/virtual-list.zh-CN.md b/docs/guides/virtual-list.zh-CN.md index 5e80eb0..dc1b647 100644 --- a/docs/guides/virtual-list.zh-CN.md +++ b/docs/guides/virtual-list.zh-CN.md @@ -3,9 +3,9 @@ title: 虚拟列表 group: title: 示例 order: 1 -order: 6 +order: 7 --- -### 配合 `react-window` 实现在虚拟列表中进行框选 +### 配合 `react-window` 在虚拟列表中进行框选 diff --git a/package.json b/package.json index c46227c..9854ddd 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,9 @@ "devDependencies": { "@commitlint/cli": "^17", "@commitlint/config-conventional": "^17", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "@testing-library/jest-dom": "^5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 28715d9..e54e772 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,15 @@ devDependencies: '@commitlint/config-conventional': specifier: ^17 version: 17.7.0 + '@dnd-kit/core': + specifier: ^6.0.8 + version: 6.0.8(react-dom@18.2.0)(react@18.2.0) + '@dnd-kit/sortable': + specifier: ^7.0.2 + version: 7.0.2(@dnd-kit/core@6.0.8)(react@18.2.0) + '@dnd-kit/utilities': + specifier: ^3.2.1 + version: 3.2.1(react@18.2.0) '@semantic-release/changelog': specifier: ^6.0.3 version: 6.0.3(semantic-release@21.0.7) @@ -44,7 +53,7 @@ devDependencies: version: 4.0.75(eslint@8.47.0)(styled-components@6.0.7)(stylelint@15.10.2)(typescript@5.1.6) '@vitest/coverage-v8': specifier: latest - version: 0.34.1(vitest@0.34.1) + version: 0.34.3(vitest@0.34.3) antd: specifier: ^5.8.3 version: 5.8.3(react-dom@18.2.0)(react@18.2.0) @@ -107,7 +116,7 @@ devDependencies: version: 5.1.6 vitest: specifier: latest - version: 0.34.1(jsdom@22.1.0) + version: 0.34.3(jsdom@22.1.0) packages: @@ -575,6 +584,7 @@ packages: /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.22.10): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -586,6 +596,7 @@ packages: /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.22.10): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1951,6 +1962,49 @@ packages: engines: {node: '>=10'} dev: true + /@dnd-kit/accessibility@3.0.1(react@18.2.0): + resolution: {integrity: sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + tslib: 2.6.1 + dev: true + + /@dnd-kit/core@6.0.8(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@dnd-kit/accessibility': 3.0.1(react@18.2.0) + '@dnd-kit/utilities': 3.2.1(react@18.2.0) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tslib: 2.6.1 + dev: true + + /@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.0.8)(react@18.2.0): + resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==} + peerDependencies: + '@dnd-kit/core': ^6.0.7 + react: '>=16.8.0' + dependencies: + '@dnd-kit/core': 6.0.8(react-dom@18.2.0)(react@18.2.0) + '@dnd-kit/utilities': 3.2.1(react@18.2.0) + react: 18.2.0 + tslib: 2.6.1 + dev: true + + /@dnd-kit/utilities@3.2.1(react@18.2.0): + resolution: {integrity: sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + tslib: 2.6.1 + dev: true + /@emotion/babel-plugin@11.11.0: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: @@ -3961,7 +4015,7 @@ packages: /@types/sax@1.2.4: resolution: {integrity: sha512-pSAff4IAxJjfAXUG6tFkO7dsSbTmf8CtUpfhhZ5VhkRpC4628tJhh3+V6H1E+/Gs9piSzYKT5yzHO5M4GG9jkw==} dependencies: - '@types/node': 17.0.45 + '@types/node': 20.5.0 dev: true /@types/scheduler@0.16.3: @@ -4661,8 +4715,8 @@ packages: - supports-color dev: true - /@vitest/coverage-v8@0.34.1(vitest@0.34.1): - resolution: {integrity: sha512-lRgUwjTMr8idXEbUPSNH4jjRZJXJCVY3BqUa+LDXyJVe3pldxYMn/r0HMqatKUGTp0Kyf1j5LfFoY6kRqRp7jw==} + /@vitest/coverage-v8@0.34.3(vitest@0.34.3): + resolution: {integrity: sha512-bNjP0RHe8UxdklCigZlk6FVCNbOiqVjWnpZJ1zKixpvb7YHSaZiN/w+mzpvXIoqyxyePzKC+L+G1oj7SB20PJw==} peerDependencies: vitest: '>=0.32.0 <1' dependencies: @@ -4677,43 +4731,43 @@ packages: std-env: 3.3.3 test-exclude: 6.0.0 v8-to-istanbul: 9.1.0 - vitest: 0.34.1(jsdom@22.1.0) + vitest: 0.34.3(jsdom@22.1.0) transitivePeerDependencies: - supports-color dev: true - /@vitest/expect@0.34.1: - resolution: {integrity: sha512-q2CD8+XIsQ+tHwypnoCk8Mnv5e6afLFvinVGCq3/BOT4kQdVQmY6rRfyKkwcg635lbliLPqbunXZr+L1ssUWiQ==} + /@vitest/expect@0.34.3: + resolution: {integrity: sha512-F8MTXZUYRBVsYL1uoIft1HHWhwDbSzwAU9Zgh8S6WFC3YgVb4AnFV2GXO3P5Em8FjEYaZtTnQYoNwwBrlOMXgg==} dependencies: - '@vitest/spy': 0.34.1 - '@vitest/utils': 0.34.1 + '@vitest/spy': 0.34.3 + '@vitest/utils': 0.34.3 chai: 4.3.7 dev: true - /@vitest/runner@0.34.1: - resolution: {integrity: sha512-YfQMpYzDsYB7yqgmlxZ06NI4LurHWfrH7Wy3Pvf/z/vwUSgq1zLAb1lWcItCzQG+NVox+VvzlKQrYEXb47645g==} + /@vitest/runner@0.34.3: + resolution: {integrity: sha512-lYNq7N3vR57VMKMPLVvmJoiN4bqwzZ1euTW+XXYH5kzr3W/+xQG3b41xJn9ChJ3AhYOSoweu974S1V3qDcFESA==} dependencies: - '@vitest/utils': 0.34.1 + '@vitest/utils': 0.34.3 p-limit: 4.0.0 pathe: 1.1.1 dev: true - /@vitest/snapshot@0.34.1: - resolution: {integrity: sha512-0O9LfLU0114OqdF8lENlrLsnn024Tb1CsS9UwG0YMWY2oGTQfPtkW+B/7ieyv0X9R2Oijhi3caB1xgGgEgclSQ==} + /@vitest/snapshot@0.34.3: + resolution: {integrity: sha512-QyPaE15DQwbnIBp/yNJ8lbvXTZxS00kRly0kfFgAD5EYmCbYcA+1EEyRalc93M0gosL/xHeg3lKAClIXYpmUiQ==} dependencies: magic-string: 0.30.2 pathe: 1.1.1 pretty-format: 29.6.2 dev: true - /@vitest/spy@0.34.1: - resolution: {integrity: sha512-UT4WcI3EAPUNO8n6y9QoEqynGGEPmmRxC+cLzneFFXpmacivjHZsNbiKD88KUScv5DCHVDgdBsLD7O7s1enFcQ==} + /@vitest/spy@0.34.3: + resolution: {integrity: sha512-N1V0RFQ6AI7CPgzBq9kzjRdPIgThC340DGjdKdPSE8r86aUSmeliTUgkTqLSgtEwWWsGfBQ+UetZWhK0BgJmkQ==} dependencies: tinyspy: 2.1.1 dev: true - /@vitest/utils@0.34.1: - resolution: {integrity: sha512-/ql9dsFi4iuEbiNcjNHQWXBum7aL8pyhxvfnD9gNtbjR9fUKAjxhj4AA3yfLXg6gJpMGGecvtF8Au2G9y3q47Q==} + /@vitest/utils@0.34.3: + resolution: {integrity: sha512-kiSnzLG6m/tiT0XEl4U2H8JDBjFtwVlaE8I3QfGiMFR0QvnRDfYfdP3YvTBWM/6iJDAyaPY6yVQiCTUc7ZzTHA==} dependencies: diff-sequences: 29.4.3 loupe: 2.3.6 @@ -16311,8 +16365,8 @@ packages: vfile-message: 3.1.4 dev: true - /vite-node@0.34.1(@types/node@20.5.0): - resolution: {integrity: sha512-odAZAL9xFMuAg8aWd7nSPT+hU8u2r9gU3LRm9QKjxBEF2rRdWpMuqkrkjvyVQEdNFiBctqr2Gg4uJYizm5Le6w==} + /vite-node@0.34.3(@types/node@20.5.0): + resolution: {integrity: sha512-+0TzJf1g0tYXj6tR2vEyiA42OPq68QkRZCu/ERSo2PtsDJfBpDyEfuKbRvLmZqi/CgC7SCBtyC+WjTGNMRIaig==} engines: {node: '>=v14.18.0'} hasBin: true dependencies: @@ -16404,8 +16458,8 @@ packages: fsevents: 2.3.2 dev: true - /vitest@0.34.1(jsdom@22.1.0): - resolution: {integrity: sha512-G1PzuBEq9A75XSU88yO5G4vPT20UovbC/2osB2KEuV/FisSIIsw7m5y2xMdB7RsAGHAfg2lPmp2qKr3KWliVlQ==} + /vitest@0.34.3(jsdom@22.1.0): + resolution: {integrity: sha512-7+VA5Iw4S3USYk+qwPxHl8plCMhA5rtfwMjgoQXMT7rO5ldWcdsdo3U1QD289JgglGK4WeOzgoLTsGFu6VISyQ==} engines: {node: '>=v14.18.0'} hasBin: true peerDependencies: @@ -16438,11 +16492,11 @@ packages: '@types/chai': 4.3.5 '@types/chai-subset': 1.3.3 '@types/node': 20.5.0 - '@vitest/expect': 0.34.1 - '@vitest/runner': 0.34.1 - '@vitest/snapshot': 0.34.1 - '@vitest/spy': 0.34.1 - '@vitest/utils': 0.34.1 + '@vitest/expect': 0.34.3 + '@vitest/runner': 0.34.3 + '@vitest/snapshot': 0.34.3 + '@vitest/spy': 0.34.3 + '@vitest/utils': 0.34.3 acorn: 8.10.0 acorn-walk: 8.2.0 cac: 6.7.14 @@ -16458,7 +16512,7 @@ packages: tinybench: 2.5.0 tinypool: 0.7.0 vite: 4.4.9(@types/node@20.5.0) - vite-node: 0.34.1(@types/node@20.5.0) + vite-node: 0.34.3(@types/node@20.5.0) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/src/Selectable.tsx b/src/Selectable.tsx index 0f3ae3c..5c2020f 100644 --- a/src/Selectable.tsx +++ b/src/Selectable.tsx @@ -87,10 +87,10 @@ const Selectable = forwardRef( const isDraggingRef = useRef(isDragging); isDraggingRef.current = isDragging; - const top = Math.min(startCoords.y, moveCoords.y); - const left = Math.min(startCoords.x, moveCoords.x); - const width = isDragging ? Math.abs(startCoords.x - moveCoords.x) : 0; - const height = isDragging ? Math.abs(startCoords.y - moveCoords.y) : 0; + const top = Math.max(0, Math.min(startCoords.y, moveCoords.y)); + const left = Math.max(0, Math.min(startCoords.x, moveCoords.x)); + const width = isDragging ? Math.abs(startCoords.x - Math.max(0, moveCoords.x)) : 0; + const height = isDragging ? Math.abs(startCoords.y - Math.max(0, moveCoords.y)) : 0; const boxRect = { top, left, width, height }; const checkScroll = () => { @@ -143,7 +143,7 @@ const Selectable = forwardRef( const height = Math.abs(startCoordsRef.current.y - y); // prevent trigger when click too fast // https://github.com/linxianxi/react-selectable-box/issues/5 - if (width * height > 1) { + if (width > 1 || height > 1) { setIsDragging(true); handleStart(); }