Skip to content

Commit 89ebb0d

Browse files
committed
Fix input focus loss in GroupCommandList #19
Replace useSortableList hook with direct DndContext implementation to prevent DOM recreation during renders.
1 parent 037aa60 commit 89ebb0d

File tree

1 file changed

+71
-19
lines changed

1 file changed

+71
-19
lines changed

src/web-view/src/components/group-command-list.tsx

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
import { Plus, FolderPlus } from "lucide-react";
2+
import { useCallback, useMemo } from "react";
3+
import {
4+
DndContext,
5+
closestCenter,
6+
KeyboardSensor,
7+
PointerSensor,
8+
useSensor,
9+
type DragEndEvent,
10+
} from "@dnd-kit/core";
11+
import {
12+
arrayMove,
13+
SortableContext,
14+
sortableKeyboardCoordinates,
15+
verticalListSortingStrategy,
16+
} from "@dnd-kit/sortable";
217
import { type ButtonConfig } from "../types";
318
import { Button } from "~/core";
419
import { GroupCommandItem } from "./group-command-item";
520
import { useCommandOperations } from "../hooks/use-command-operations";
6-
import { useSortableList } from "../hooks/use-sortable-list";
721

822
const MAX_NESTING_DEPTH = 2; // 0-indexed, so 3 levels total (0,1,2)
923

@@ -31,11 +45,40 @@ export const GroupCommandList = ({
3145
addGroup,
3246
} = useCommandOperations(commands, onChange);
3347

34-
const { SortableWrapper } = useSortableList({
35-
items: commands,
36-
onReorder: onChange,
48+
const pointerSensor = useSensor(PointerSensor, {
49+
activationConstraint: {
50+
distance: 8,
51+
},
3752
});
3853

54+
const keyboardSensor = useSensor(KeyboardSensor, {
55+
coordinateGetter: sortableKeyboardCoordinates,
56+
});
57+
58+
const sensors = useMemo(() => [pointerSensor, keyboardSensor], [pointerSensor, keyboardSensor]);
59+
60+
const sortableItemIds = useMemo(() =>
61+
commands.map((_, index) => `${index}`),
62+
[commands]
63+
);
64+
65+
const handleDragEnd = useCallback(
66+
(event: DragEndEvent) => {
67+
const { active, over } = event;
68+
69+
if (over && active.id !== over.id) {
70+
const oldIndex = Number(active.id);
71+
const newIndex = Number(over.id);
72+
73+
if (oldIndex !== -1 && newIndex !== -1) {
74+
const newItems = arrayMove(commands, oldIndex, newIndex);
75+
onChange(newItems);
76+
}
77+
}
78+
},
79+
[commands, onChange]
80+
);
81+
3982
return (
4083
<div className="space-y-4">
4184
{title && (
@@ -44,21 +87,30 @@ export const GroupCommandList = ({
4487
</div>
4588
)}
4689

47-
<SortableWrapper>
48-
<div className="space-y-3">
49-
{commands.map((command, index) => (
50-
<GroupCommandItem
51-
key={index}
52-
id={`${index}`}
53-
command={command}
54-
index={index}
55-
onUpdate={updateCommand}
56-
onDelete={deleteCommand}
57-
onEditGroup={onEditGroup ? () => onEditGroup(index) : undefined}
58-
/>
59-
))}
60-
</div>
61-
</SortableWrapper>
90+
<DndContext
91+
sensors={sensors}
92+
collisionDetection={closestCenter}
93+
onDragEnd={handleDragEnd}
94+
>
95+
<SortableContext
96+
items={sortableItemIds}
97+
strategy={verticalListSortingStrategy}
98+
>
99+
<div className="space-y-3">
100+
{commands.map((command, index) => (
101+
<GroupCommandItem
102+
key={index}
103+
id={`${index}`}
104+
command={command}
105+
index={index}
106+
onUpdate={updateCommand}
107+
onDelete={deleteCommand}
108+
onEditGroup={onEditGroup ? () => onEditGroup(index) : undefined}
109+
/>
110+
))}
111+
</div>
112+
</SortableContext>
113+
</DndContext>
62114

63115
<div className="flex gap-2">
64116
<Button

0 commit comments

Comments
 (0)