Skip to content

Commit

Permalink
fix: fixed some boundary issues with disabled (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
linxianxi authored Sep 3, 2023
1 parent 6db0352 commit 4a0b4b6
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 81 deletions.
35 changes: 31 additions & 4 deletions docs/examples/sort.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { DndContext } from '@dnd-kit/core';
import { DndContext, PointerSensor, useSensor, useSensors } 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++) {
for (let i = 0; i < 200; i++) {
list.push(String(i));
}

const Item = ({
value,
disabled,
onClick,
selectedLength,
}: {
value: React.Key;
disabled: boolean;
onClick: (isSelected: boolean) => void;
selectedLength: number;
}) => {
const {
Expand Down Expand Up @@ -54,16 +56,18 @@ const Item = ({
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
zIndex: isDragging ? 999 : undefined,
width: 50,
height: 50,
borderRadius: 4,
transform: CSS.Transform.toString(transform),
transition,
zIndex: isDragging ? 999 : undefined,
cursor: isSelected ? 'move' : undefined,
visibility: isSelected && !isDragging && isSorting ? 'hidden' : undefined,
border: isAdding ? '1px solid #1677ff' : undefined,
background: isRemoving ? 'red' : isSelected ? '#1677ff' : '#ccc',
}}
onClick={() => onClick(isSelected)}
>
{value}
{isDragging && (
Expand All @@ -79,8 +83,18 @@ export default () => {
const [beforeSortItems, setBeforeSortItems] = useState<React.Key[]>([]);
const [isSorting, setIsSorting] = useState(false);

const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
// make item click event available
distance: 1,
},
}),
);

return (
<DndContext
sensors={sensors}
onDragStart={(event) => {
setIsSorting(true);
const index = event.active.data.current?.sortable.index as number;
Expand Down Expand Up @@ -121,6 +135,7 @@ export default () => {
disabled={isSorting}
getContainer={() => document.querySelector('.container') as HTMLElement}
onEnd={(selectingValue, { added, removed }) => {
console.log('onEnd');
const result = value.concat(added).filter((i) => !removed.includes(i));
setValue(result);
}}
Expand All @@ -137,7 +152,19 @@ export default () => {
className="container"
>
{items.map((i) => (
<Item selectedLength={value.length} key={i} value={i} disabled={!value.includes(i)} />
<Item
selectedLength={value.length}
key={i}
value={i}
disabled={!value.includes(i)}
onClick={(isSelected) => {
if (isSelected) {
setValue(value.filter((val) => val !== i));
} else {
setValue(value.concat(i));
}
}}
/>
))}
</div>
</Selectable>
Expand Down
50 changes: 24 additions & 26 deletions docs/examples/virtual-list.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,43 @@
import React, { useState } from 'react';
import React, { HTMLAttributes, useState } from 'react';
import Selectable, { useSelectable } from 'react-selectable-box';
import { FixedSizeList } from 'react-window';
import { VirtuosoGrid } from 'react-virtuoso';

const list: string[] = [];
for (let i = 0; i < 2000; i++) {
for (let i = 0; i < 2002; i++) {
list.push(String(i));
}

const columnCount = 10;

const Cell = ({ value, style }: { value: string; style: React.CSSProperties }) => {
const { setNodeRef, isSelected, isAdding, isRemoving, isDragging, isSelecting } = useSelectable({
const Item = ({ value }: { value: string }) => {
const { setNodeRef, isSelected, isAdding, isRemoving } = useSelectable({
value,
});

return (
<div
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}
</div>
);
};

const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
const rowList = list.slice(index * columnCount, (index + 1) * columnCount);
const List: React.ForwardRefExoticComponent<
HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>
> = React.forwardRef(({ style, ...props }, ref) => {
return (
<div style={{ ...style, display: 'flex' }}>
{rowList.map((i, idx) => {
const marginRight = idx < 9 ? 20 : 0;
return <Cell key={i} value={i} style={{ marginRight }} />;
})}
</div>
<div ref={ref} {...props} style={{ ...style, display: 'flex', flexWrap: 'wrap', gap: 20 }} />
);
};
});

export default () => {
const [value, setValue] = useState<React.Key[]>([]);
Expand All @@ -54,15 +52,15 @@ export default () => {
setValue(result);
}}
>
<FixedSizeList
<VirtuosoGrid
totalCount={list.length}
style={{ height: 500 }}
components={{
List,
}}
className="container"
height={500}
itemSize={70}
width={columnCount * 50 + (columnCount - 1) * 20}
itemCount={Math.ceil(list.length / columnCount)}
>
{({ index, style }) => <Row index={index} style={style} />}
</FixedSizeList>
itemContent={(index) => <Item value={list[index]} />}
/>
</Selectable>
);
};
2 changes: 1 addition & 1 deletion docs/guides/sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ group:
order: 8
---

### sort
### Use `dnd-kit` to sort after selecting some options

<code src="../examples/sort.tsx"></code>
2 changes: 1 addition & 1 deletion docs/guides/sort.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ group:
order: 8
---

### Use `dnd-kit` to sort after selecting some options
### 配合 `dnd-kit` 选择一些选项后进行排序

<code src="../examples/sort.tsx"></code>
2 changes: 1 addition & 1 deletion docs/guides/virtual-list.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ group:
order: 7
---

### Box selection in a virtual list using `react-window`
### Box selection in a virtual list using `react-virtuoso`

<code src="../examples/virtual-list.tsx"></code>
2 changes: 1 addition & 1 deletion docs/guides/virtual-list.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ group:
order: 7
---

### 配合 `react-window` 在虚拟列表中进行框选
### 配合 `react-virtuoso` 在虚拟列表中进行框选

<code src="../examples/virtual-list.tsx"></code>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
"prettier-plugin-packagejson": "^2",
"react": "^18",
"react-dom": "^18",
"react-window": "^1.8.9",
"react-virtuoso": "^4.5.0",
"semantic-release": "^21",
"stylelint": "^15",
"typescript": "^5",
Expand Down
22 changes: 8 additions & 14 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 39 additions & 32 deletions src/Selectable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, {
useState,
} from 'react';
import { SelectableContext } from './context';
import useLatest from './hooks/useLatest';

interface SelectableProps {
defaultValue?: React.Key[];
Expand Down Expand Up @@ -61,31 +62,11 @@ const Selectable = forwardRef<SelectableRef, SelectableProps>(
const moveClient = useRef({ x: 0, y: 0 });
const [value, setValue] = useMergedState(defaultValue || [], {
value: propsValue,
onChange: (newValue) => {
if (onEnd) {
const added: React.Key[] = [];
const removed: React.Key[] = [];

newValue.forEach((i) => {
if (value.includes(i)) {
if (mode === 'remove' || mode === 'reverse') {
removed.push(i);
}
} else {
if (mode === 'add' || mode === 'reverse') {
added.push(i);
}
}
});
onEnd(newValue, { added, removed });
}
},
});

const startCoordsRef = useRef(startCoords);
startCoordsRef.current = startCoords;
const isDraggingRef = useRef(isDragging);
isDraggingRef.current = isDragging;
const startCoordsRef = useLatest(startCoords);
const isDraggingRef = useLatest(isDragging);
const selectFromInsideRef = useLatest(selectFromInside);

const top = Math.max(0, Math.min(startCoords.y, moveCoords.y));
const left = Math.max(0, Math.min(startCoords.x, moveCoords.x));
Expand All @@ -111,15 +92,44 @@ const Selectable = forwardRef<SelectableRef, SelectableProps>(
}));

const handleStart = useEvent(() => {
if (!isDragging) {
if (!isDraggingRef.current) {
onStart?.();
}
});

const handleEnd = useEvent((newValue: React.Key[]) => {
if (onEnd) {
const added: React.Key[] = [];
const removed: React.Key[] = [];

newValue.forEach((i) => {
if (value.includes(i)) {
if (mode === 'remove' || mode === 'reverse') {
removed.push(i);
}
} else {
if (mode === 'add' || mode === 'reverse') {
added.push(i);
}
}
});
onEnd(newValue, { added, removed });
}
});

const handleReset = () => {
setIsDragging(false);
setStartTarget(null);
isStart.current = false;
startInside.current = false;
selectingValue.current = [];
};

useEffect(() => {
const container = getContainer();

if (disabled || !container) {
handleReset();
return;
}

Expand All @@ -143,7 +153,7 @@ const Selectable = forwardRef<SelectableRef, SelectableProps>(
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 > 1 || height > 1) {
if (!isDraggingRef.current && (width > 1 || height > 1)) {
setIsDragging(true);
handleStart();
}
Expand All @@ -159,18 +169,15 @@ const Selectable = forwardRef<SelectableRef, SelectableProps>(

if (isDraggingRef.current) {
setValue(selectingValue.current);
selectingValue.current = [];
setIsDragging(false);
handleEnd(selectingValue.current);
}
setStartTarget(null);
isStart.current = false;
startInside.current = false;
handleReset();
};

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

const onMouseDown = (e: MouseEvent) => {
if (!selectFromInside) {
if (!selectFromInsideRef.current) {
setStartTarget(e.target as HTMLElement);
}

Expand All @@ -195,7 +202,7 @@ const Selectable = forwardRef<SelectableRef, SelectableProps>(
window.removeEventListener('mousemove', onMouseMove);
window.removeEventListener('mouseup', onMouseUp);
};
}, [disabled, selectFromInside]);
}, [disabled]);

const container = getContainer();

Expand Down
Loading

0 comments on commit 4a0b4b6

Please sign in to comment.