Skip to content

Commit

Permalink
fix: add compareFn to Selectable、remove useSelectable compareFn
Browse files Browse the repository at this point in the history
  • Loading branch information
linxianxi committed Jan 31, 2024
1 parent 1c7011b commit d75b4b1
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 50 deletions.
13 changes: 7 additions & 6 deletions docs/guides/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ nav: Get Started
| 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 | - |
| compareFn | Because value supports any type, you may need to define a custom function for comparison. The default is `===` | (item: any, value: any) => boolean |
| onStart | Called when selection starts | () => void | - |
| onEnd | Called when selection ends | (selectingValue: any[], { added: any[], removed: any[] }) => void | - |

Expand All @@ -34,12 +35,12 @@ const { setNodeRef, isSelected, isAdding, isRemoving, isSelecting, isDragging }
});
```

| 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 | 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` |
| |

| Property | 说明 | 类型 |
| ----------- | -------------------------------------- | -------------------------------------- |
Expand Down
12 changes: 6 additions & 6 deletions docs/guides/api.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ nav: 快速上手
| dragContainer | 指定可以开始拖拽的容器, 如果设置了 `scrollContainer` 请不要设置,因为在可滚动容器中这两个应该相等 | () => HTMLElement | scrollContainer |
| boxStyle | 框选框的样式 | React.CSSProperties | - |
| boxClassName | 框选框的类名 | string | - |
| compareFn | 因为 value 支持任意类型,所以你可能需要自定义函数进行比较,默认使用 `===` | (item: any, value: any) => boolean | === |
| onStart | 框选开始时触发的事件 | () => void | - |
| onEnd | 框选结束时触发的事件 | (selectingValue: any[], { added: any[], removed: any[] }) => void | - |

Expand All @@ -34,12 +35,11 @@ const { setNodeRef, isSelected, isAdding, isRemoving, isSelecting, isDragging }
});
```

| 参数 | 说明 | 类型 | 默认值 |
| --------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| 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 | === |
| 参数 | 说明 | 类型 | 默认值 |
| -------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
| value | 当前可框选元素的值 | any | - |
| disabled | 是否禁用 | boolean | false |
| rule | 选中规则,碰撞、包含或自定义,自定义时的 `boxPosition` 是相对于 `scrollContainer` 的位置 | `collision` \| `inclusion` \| ( boxElement: HTMLDivElement, boxPosition: { left: number; top: number; width: number; height: number }) => boolean | `collision` |

| 参数 | 说明 | 类型 |
| ----------- | ------------------ | -------------------------------------- |
Expand Down
50 changes: 28 additions & 22 deletions src/Selectable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface SelectableProps<T> {
dragContainer?: () => HTMLElement;
boxStyle?: React.CSSProperties;
boxClassName?: string;
compareFn?: (a: T, b: T) => boolean;
onStart?: () => void;
onEnd?: (selectingValue: T[], changed: { added: T[]; removed: T[] }) => void;
/**
Expand All @@ -33,6 +34,8 @@ export interface SelectableRef {
cancel: () => void;
}

const defaultCompareFn = (a: any, b: any) => a === b;

function Selectable<T>(
{
defaultValue,
Expand All @@ -47,6 +50,7 @@ function Selectable<T>(
dragContainer: propsDragContainer,
boxStyle,
boxClassName,
compareFn = defaultCompareFn,
onStart,
onEnd,
}: SelectableProps<T>,
Expand Down Expand Up @@ -96,27 +100,6 @@ function Selectable<T>(
},
}));

const onScroll = (e: Event) => {
if (isDraggingRef.current && scrollContainer) {
const target = e.target as HTMLElement;
scrollInfo.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft };

const containerRect = scrollContainer.getBoundingClientRect();
const x = Math.min(
moveClient.current.x - containerRect.left + scrollContainer.scrollLeft,
scrollContainer.scrollWidth,
);
const y = Math.min(
moveClient.current.y - containerRect.top + scrollContainer.scrollTop,
scrollContainer.scrollHeight,
);
setMoveCoords({
x,
y,
});
}
};

const handleStart = useEvent(() => {
onStart?.();
});
Expand Down Expand Up @@ -151,7 +134,7 @@ function Selectable<T>(
const removed: T[] = [];

selectingValue.current.forEach((i) => {
if (value?.includes(i)) {
if (value?.some((val) => compareFn(val, i))) {
if (mode === 'remove' || mode === 'reverse') {
removed.push(i);
}
Expand Down Expand Up @@ -222,6 +205,27 @@ function Selectable<T>(

const scrollListenerElement = scrollContainer === document.body ? document : scrollContainer;

const onScroll = (e: Event) => {
if (isDraggingRef.current && scrollContainer) {
const target = e.target as HTMLElement;
scrollInfo.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft };

const containerRect = scrollContainer.getBoundingClientRect();
const x = Math.min(
moveClient.current.x - containerRect.left + scrollContainer.scrollLeft,
scrollContainer.scrollWidth,
);
const y = Math.min(
moveClient.current.y - containerRect.top + scrollContainer.scrollTop,
scrollContainer.scrollHeight,
);
setMoveCoords({
x,
y,
});
}
};

const onMouseUp = () => {
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
Expand Down Expand Up @@ -287,6 +291,7 @@ function Selectable<T>(
scrollInfo,
virtual,
boxRef,
compareFn,
}),
[
value,
Expand All @@ -299,6 +304,7 @@ function Selectable<T>(
scrollInfo,
virtual,
boxRef,
compareFn,
],
);

Expand Down
1 change: 1 addition & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface ISelectableContext<T> {
}>;
virtual: boolean;
boxRef: React.MutableRefObject<HTMLDivElement | null>;
compareFn: (a: T, b: T) => boolean;
}

export const SelectableContext = React.createContext<ISelectableContext<any>>(
Expand Down
19 changes: 3 additions & 16 deletions src/hooks/useSelectable.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { Rule, useSelectableContext } from '../context';
import { isInRange } from '../utils';
import useEvent from './useEvent';
import useUpdateEffect from './useUpdateEffect';

interface UseSelectableProps<T> {
value: T;
disabled?: boolean;
rule?: Rule;
compareFn?: (item: T, value: T) => boolean;
}

export default function useSelectable<T>({
value,
disabled,
rule = 'collision',
compareFn,
}: UseSelectableProps<T>) {
const {
mode,
Expand All @@ -30,6 +27,7 @@ export default function useSelectable<T>({
scrollInfo,
virtual,
boxRef,
compareFn,
} = useSelectableContext();
const node = useRef<HTMLElement | null>(null);
const rectRef = useRef<DOMRect>();
Expand All @@ -44,16 +42,7 @@ export default function useSelectable<T>({
}
}, [rectRef.current, rule, scrollContainer, boxPosition, isDragging]);

const compare = useEvent((item: T, value: T) => {
if (compareFn) {
return compareFn(item, value);
}
return false;
});

const isSelected = compare
? contextValue.some((i) => compare(i, value))
: contextValue.includes(value);
const isSelected = contextValue.some((i) => compareFn(i, value));

const isSelecting = isDragging && !disabled && inRange;

Expand All @@ -79,9 +68,7 @@ export default function useSelectable<T>({
if (isSelecting) {
selectingValue.current.push(value);
} else {
selectingValue.current = selectingValue.current.filter((i) =>
compare ? !compare(i, value) : i !== value,
);
selectingValue.current = selectingValue.current.filter((i) => !compareFn(i, value));
}
}
}, [isSelecting]);
Expand Down

0 comments on commit d75b4b1

Please sign in to comment.