From 64baea7b2369d7a5922208459ff60d19b6167656 Mon Sep 17 00:00:00 2001 From: linxianxi <904492381@qq.com> Date: Tue, 30 Jan 2024 18:06:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20value=20support=20any=E3=80=81add=20com?= =?UTF-8?q?pareFn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/guides/api.md | 39 +++++++++++++++++++------------------- docs/guides/api.zh-CN.md | 39 +++++++++++++++++++------------------- src/Selectable.tsx | 2 +- src/context.ts | 6 ++---- src/hooks/useSelectable.ts | 32 ++++++++++++++++++++++--------- 5 files changed, 66 insertions(+), 52 deletions(-) diff --git a/docs/guides/api.md b/docs/guides/api.md index 65069c5..de0f113 100644 --- a/docs/guides/api.md +++ b/docs/guides/api.md @@ -9,20 +9,20 @@ nav: Get Started ### Selectable -| Property | Description | Type | Default | -| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------- | -| defaultValue | Initial selected option | (string \| number)[] | - | -| value | Current selected option | (string \| number)[] | - | -| disabled | Whether to disable | boolean | false | -| mode | Selection mode | `add` \| `remove` \| `reverse` | `add` | -| items | The collection value of all items, only the virtual list needs to be passed | (string \| number)[] | - | -| selectStartRange | Where to start with box selection | `all` \| `inside` \| `outside` | `all` | -| scrollContainer | Specify the scrolling container. After setting, the container needs to set `position` because the selection box is `absolute` and needs to be positioned relative to the container. | () => HTMLElement | -| dragContainer | Specify the container that can start dragging. If `scrollContainer` is set, please do not set it because the two should be equal in a scrollable container. | () => HTMLElement | scrollContainer | -| boxStyle | Selection box style | React.CSSProperties | - | -| boxClassName | Selection box className | string | - | -| onStart | Called when selection starts | () => void | - | -| onEnd | Called when selection ends | (selectingValue: (string \| number)[], { added: (string \| number)[], removed: (string \| number)[] }) => void | - | +| Property | Description | Type | Default | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------- | +| defaultValue | Initial selected option | any[] | - | +| value | Current selected option | any[] | - | +| disabled | Whether to disable | boolean | false | +| mode | Selection mode | `add` \| `remove` \| `reverse` | `add` | +| items | The collection value of all items, only the virtual list needs to be passed | any[] | - | +| selectStartRange | Where to start with box selection | `all` \| `inside` \| `outside` | `all` | +| scrollContainer | Specify the scrolling container. After setting, the container needs to set `position` because the selection box is `absolute` and needs to be positioned relative to the container. | () => HTMLElement | +| dragContainer | Specify the container that can start dragging. If `scrollContainer` is set, please do not set it because the two should be equal in a scrollable container. | () => HTMLElement | scrollContainer | +| boxStyle | Selection box style | React.CSSProperties | - | +| boxClassName | Selection box className | string | - | +| onStart | Called when selection starts | () => void | - | +| onEnd | Called when selection ends | (selectingValue: any[], { added: any[], removed: any[] }) => void | - | ### useSelectable @@ -34,11 +34,12 @@ 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 or customization. When customizing, the `boxPosition` is relative to the position of `scrollContainer` | `collision` \| `inclusion` \| ( boxElement: HTMLDivElement, boxPosition: { left: number; top: number; width: number; height: number }) => boolean | `collision` | +| Property | Description | Type | Default | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| value | The value of the current selectable element | any | - | +| disabled | Whether to disable | boolean | false | +| rule | Selection rule, collision, inclusion or customization. When customizing, the `boxPosition` is relative to the position of `scrollContainer` | `collision` \| `inclusion` \| ( boxElement: HTMLDivElement, boxPosition: { left: number; top: number; width: number; height: number }) => boolean | `collision` | +| compareFn | Because value supports any type, you may need to define a custom function for comparison. The default is `===` | (item: any, value: any) => boolean | | Property | 说明 | 类型 | | ----------- | -------------------------------------- | -------------------------------------- | diff --git a/docs/guides/api.zh-CN.md b/docs/guides/api.zh-CN.md index 2fd0dc1..f4a703c 100644 --- a/docs/guides/api.zh-CN.md +++ b/docs/guides/api.zh-CN.md @@ -9,20 +9,20 @@ nav: 快速上手 ### Selectable -| 参数 | 说明 | 类型 | 默认值 | -| ---------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------- | -| defaultValue | 默认已选择的值 | (string \| number)[] | - | -| value | 受控已选择的值 | (string \| number)[] | - | -| disabled | 是否禁用 | boolean | false | -| mode | 模式 | `add` \| `remove` \| `reverse` | `add` | -| items | 全部 item 的集合值,只有虚拟列表时要传 | (string \| number)[] | - | -| selectStartRange | 从哪里可以开始进行框选 | `all` \| `inside` \| `outside` | `all` | -| scrollContainer | 指定滚动的容器,设置后,容器需要设置 `position`,因为选择框是 `absolute` 要相对于容器定位 | () => HTMLElement | -| dragContainer | 指定可以开始拖拽的容器, 如果设置了 `scrollContainer` 请不要设置,因为在可滚动容器中这两个应该相等 | () => HTMLElement | scrollContainer | -| boxStyle | 框选框的样式 | React.CSSProperties | - | -| boxClassName | 框选框的类名 | string | - | -| onStart | 框选开始时触发的事件 | () => void | - | -| onEnd | 框选结束时触发的事件 | (selectingValue: (string \| number)[], { added: (string \| number)[], removed: (string \| number)[] }) => void | - | +| 参数 | 说明 | 类型 | 默认值 | +| ---------------- | -------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | --------------- | +| defaultValue | 默认已选择的值 | any[] | - | +| value | 受控已选择的值 | any[] | - | +| disabled | 是否禁用 | boolean | false | +| mode | 模式 | `add` \| `remove` \| `reverse` | `add` | +| items | 全部 item 的集合值,只有虚拟列表时要传 | any[] | - | +| selectStartRange | 从哪里可以开始进行框选 | `all` \| `inside` \| `outside` | `all` | +| scrollContainer | 指定滚动的容器,设置后,容器需要设置 `position`,因为选择框是 `absolute` 要相对于容器定位 | () => HTMLElement | +| dragContainer | 指定可以开始拖拽的容器, 如果设置了 `scrollContainer` 请不要设置,因为在可滚动容器中这两个应该相等 | () => HTMLElement | scrollContainer | +| boxStyle | 框选框的样式 | React.CSSProperties | - | +| boxClassName | 框选框的类名 | string | - | +| onStart | 框选开始时触发的事件 | () => void | - | +| onEnd | 框选结束时触发的事件 | (selectingValue: any[], { added: any[], removed: any[] }) => void | - | ### useSelectable @@ -34,11 +34,12 @@ const { setNodeRef, isSelected, isAdding, isRemoving, isSelecting, isDragging } }); ``` -| 参数 | 说明 | 类型 | 默认值 | -| -------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| value | 当前可框选元素的值 | string \| number | - | -| disabled | 是否禁用 | boolean | false | -| rule | 选中规则,碰撞、包含或自定义,自定义时的 `boxPosition` 是相对于 `scrollContainer` 的位置 | `collision` \| `inclusion` \| ( boxElement: HTMLDivElement, boxPosition: { left: number; top: number; width: number; height: number }) => boolean | `collision` | +| 参数 | 说明 | 类型 | 默认值 | +| --------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| value | 当前可框选元素的值 | any | - | +| disabled | 是否禁用 | boolean | false | +| rule | 选中规则,碰撞、包含或自定义,自定义时的 `boxPosition` 是相对于 `scrollContainer` 的位置 | `collision` \| `inclusion` \| ( boxElement: HTMLDivElement, boxPosition: { left: number; top: number; width: number; height: number }) => boolean | `collision` | +| compareFn | 因为 value 支持任意类型,所以你可能需要自定义函数进行比较,默认使用 `===` | (item: any, value: any) => boolean | === | | 参数 | 说明 | 类型 | | ----------- | ------------------ | -------------------------------------- | diff --git a/src/Selectable.tsx b/src/Selectable.tsx index 0655e8a..6ea5c45 100644 --- a/src/Selectable.tsx +++ b/src/Selectable.tsx @@ -33,7 +33,7 @@ export interface SelectableRef { cancel: () => void; } -function Selectable( +function Selectable( { defaultValue, value: propsValue, diff --git a/src/context.ts b/src/context.ts index a86aaf6..30e1c00 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,7 +1,5 @@ import React, { useContext } from 'react'; -type ValueType = string | number; - export type Rule = | 'collision' | 'inclusion' @@ -39,8 +37,8 @@ interface ISelectableContext { boxRef: React.MutableRefObject; } -export const SelectableContext = React.createContext>( - {} as ISelectableContext, +export const SelectableContext = React.createContext>( + {} as ISelectableContext, ); export const useSelectableContext = () => { diff --git a/src/hooks/useSelectable.ts b/src/hooks/useSelectable.ts index a49702a..4e1f507 100644 --- a/src/hooks/useSelectable.ts +++ b/src/hooks/useSelectable.ts @@ -1,17 +1,22 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { Rule, useSelectableContext } from '../context'; import { isInRange } from '../utils'; +import useEvent from './useEvent'; import useUpdateEffect from './useUpdateEffect'; -export default function useSelectable({ +interface UseSelectableProps { + value: T; + disabled?: boolean; + rule?: Rule; + compareFn?: (item: T, value: T) => boolean; +} + +export default function useSelectable({ value, disabled, rule = 'collision', -}: { - value: string | number; - disabled?: boolean; - rule?: Rule; -}) { + compareFn, +}: UseSelectableProps) { const { mode, scrollContainer, @@ -51,6 +56,13 @@ export default function useSelectable({ node.current = ref; }, []); + const compare = useEvent((a: any, b: any) => { + if (compareFn) { + return compareFn(a, b); + } + return false; + }); + useEffect(() => { if (startTarget && !startInside.current) { const contain = node.current?.contains(startTarget); @@ -65,7 +77,9 @@ export default function useSelectable({ if (isSelecting) { selectingValue.current.push(value); } else { - selectingValue.current = selectingValue.current.filter((i) => i !== value); + selectingValue.current = selectingValue.current.filter((i) => + compare ? !compare(i, value) : i !== value, + ); } } }, [isSelecting]); @@ -94,10 +108,10 @@ export default function useSelectable({ if (virtual) { const info = unmountItemsInfo.current.get(value); if (info) { - unmountItemsInfo.current.set(value, { ...info, disabled }); + unmountItemsInfo.current.set(value, { ...info, disabled, rule }); } } - }, [virtual, disabled]); + }, [virtual, disabled, rule]); return { setNodeRef,