@exegia/corpora-ui 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/blocks/auth/auth-flow-atom.d.ts +77 -0
- package/dist-lib/components/blocks/auth/auth-session-atom.d.ts +28 -0
- package/dist-lib/components/blocks/auth/auth-state-type.d.ts +71 -0
- package/dist-lib/components/blocks/auth/auth-state.d.ts +9 -0
- package/dist-lib/components/blocks/auth/use-auth-state.d.ts +52 -0
- package/dist-lib/components/blocks/layout.d.ts +4 -1
- package/dist-lib/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar/ai-sidebar-atom.d.ts +217 -0
- package/dist-lib/components/blocks/nav/sidebar/index.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/sidebar-context.d.ts +3 -0
- package/dist-lib/components/blocks/nav/sidebar/sidebar-row.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/type.d.ts +109 -29
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar-state.d.ts +29 -0
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +6 -1
- package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +1 -1
- package/dist-lib/components/blocks/profile/index.d.ts +7 -0
- package/dist-lib/components/blocks/profile/profile-card-atom.d.ts +68 -0
- package/dist-lib/components/blocks/profile/profile-card-block.d.ts +28 -39
- package/dist-lib/components/blocks/profile/type.d.ts +80 -0
- package/dist-lib/components/blocks/profile/use-profile-card-state.d.ts +24 -0
- package/dist-lib/components/blocks/profile/use-profile-card.d.ts +34 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +1 -1
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +1 -1
- package/dist-lib/components/blocks/shell/index.d.ts +4 -12
- package/dist-lib/components/blocks/shell/shell-fit-atom.d.ts +68 -0
- package/dist-lib/components/blocks/shell/shell-metrics.d.ts +54 -0
- package/dist-lib/components/blocks/shell/type.d.ts +115 -3
- package/dist-lib/components/blocks/shell/use-shell-fit-state.d.ts +25 -0
- package/dist-lib/components/blocks/shell/use-shell-fit.d.ts +14 -0
- package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +12 -1
- package/dist-lib/components/blocks/shell/utils.d.ts +32 -0
- package/dist-lib/components/composed/__tests__/logo.test.d.ts +1 -0
- package/dist-lib/components/composed/logo.d.ts +34 -0
- package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/constants.d.ts +4 -4
- package/dist-lib/components/composed/tree/index.d.ts +3 -1
- package/dist-lib/components/composed/tree/tree-atom.d.ts +160 -0
- package/dist-lib/components/composed/tree/tree-node.d.ts +6 -1
- package/dist-lib/components/composed/tree/type.d.ts +116 -18
- package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +18 -7
- package/dist-lib/components/composed/tree/use-tree-state.d.ts +25 -0
- package/dist-lib/components/composed/tree/use-tree.d.ts +4 -0
- package/dist-lib/components/composed/user-avatar.d.ts +5 -28
- package/dist-lib/components/user-avatar/__tests__/user-avatar.test.d.ts +1 -0
- package/dist-lib/components/user-avatar/component.d.ts +1 -8
- package/dist-lib/components/user-avatar/index.d.ts +5 -0
- package/dist-lib/components/user-avatar/presence-badge.d.ts +16 -0
- package/dist-lib/components/user-avatar/type.d.ts +53 -0
- package/dist-lib/components/user-avatar/use-user-avatar-state.d.ts +26 -0
- package/dist-lib/components/user-avatar/use-user-avatar.d.ts +37 -0
- package/dist-lib/components/user-avatar/user-avatar-atom.d.ts +64 -0
- package/dist-lib/components/user-avatar/utils.d.ts +22 -0
- package/dist-lib/index.d.ts +6 -3
- package/dist-lib/index.js +3732 -2670
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/state/exegia-provider.d.ts +51 -0
- package/dist-lib/state/index.d.ts +4 -0
- package/dist-lib/state/store.d.ts +17 -0
- package/package.json +15 -12
- package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +247 -0
- package/src/components/blocks/auth/auth-flow-atom.ts +238 -0
- package/src/components/blocks/auth/auth-session-atom.ts +82 -0
- package/src/components/blocks/auth/auth-state-type.ts +97 -0
- package/src/components/blocks/auth/auth-state.ts +52 -0
- package/src/components/blocks/auth/use-auth-state.ts +127 -0
- package/src/components/blocks/layout.ts +30 -2
- package/src/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.tsx +325 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar-atom.ts +698 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +48 -42
- package/src/components/blocks/nav/sidebar/index.ts +53 -1
- package/src/components/blocks/nav/sidebar/sidebar-context.ts +15 -0
- package/src/components/blocks/nav/sidebar/sidebar-row.tsx +119 -50
- package/src/components/blocks/nav/sidebar/type.ts +125 -30
- package/src/components/blocks/nav/sidebar/use-ai-sidebar-state.ts +123 -0
- package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +301 -248
- package/src/components/blocks/nav/sidebar/utils.ts +1 -1
- package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +194 -0
- package/src/components/blocks/profile/index.ts +28 -0
- package/src/components/blocks/profile/profile-card-atom.ts +247 -0
- package/src/components/blocks/profile/profile-card-block.tsx +153 -74
- package/src/components/blocks/profile/type.ts +95 -0
- package/src/components/blocks/profile/use-profile-card-state.ts +67 -0
- package/src/components/blocks/profile/use-profile-card.ts +126 -0
- package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +360 -0
- package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +192 -3
- package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +108 -0
- package/src/components/blocks/shell/animated-panel-inset.tsx +5 -1
- package/src/components/blocks/shell/animated-panel-provider.tsx +77 -11
- package/src/components/blocks/shell/animated-panel-trigger.tsx +4 -0
- package/src/components/blocks/shell/animated-panel.tsx +126 -29
- package/src/components/blocks/shell/index.ts +18 -13
- package/src/components/blocks/shell/shell-fit-atom.ts +243 -0
- package/src/components/blocks/shell/shell-layout.tsx +55 -53
- package/src/components/blocks/shell/shell-metrics.ts +79 -0
- package/src/components/blocks/shell/type.ts +130 -3
- package/src/components/blocks/shell/use-shell-fit-state.ts +49 -0
- package/src/components/blocks/shell/use-shell-fit.ts +135 -0
- package/src/components/blocks/shell/use-shell-panels.ts +57 -4
- package/src/components/blocks/shell/utils.ts +44 -4
- package/src/components/composed/__tests__/logo.test.tsx +60 -0
- package/src/components/composed/logo.tsx +159 -0
- package/src/components/composed/tree/CLAUDE.md +132 -0
- package/src/components/composed/tree/__tests__/tree-atom.test.tsx +217 -0
- package/src/components/composed/tree/__tests__/tree.test.tsx +162 -37
- package/src/components/composed/tree/__tests__/use-tree.test.tsx +2 -1
- package/src/components/composed/tree/constants.ts +4 -4
- package/src/components/composed/tree/index.ts +38 -0
- package/src/components/composed/tree/tree-atom.ts +590 -0
- package/src/components/composed/tree/tree-node.tsx +117 -78
- package/src/components/composed/tree/tree.tsx +24 -16
- package/src/components/composed/tree/type.ts +125 -15
- package/src/components/composed/tree/use-tree-dnd.ts +82 -51
- package/src/components/composed/tree/use-tree-state.ts +105 -0
- package/src/components/composed/tree/use-tree.ts +185 -184
- package/src/components/composed/user-avatar.tsx +11 -99
- package/src/components/user-avatar/__tests__/user-avatar.test.tsx +286 -0
- package/src/components/user-avatar/component.tsx +151 -22
- package/src/components/user-avatar/fallback.tsx +3 -1
- package/src/components/user-avatar/index.ts +19 -0
- package/src/components/user-avatar/presence-badge.tsx +56 -0
- package/src/components/user-avatar/type.ts +58 -0
- package/src/components/user-avatar/use-user-avatar-state.ts +60 -0
- package/src/components/user-avatar/use-user-avatar.ts +180 -0
- package/src/components/user-avatar/user-avatar-atom.ts +218 -0
- package/src/components/user-avatar/utils.ts +94 -0
- package/src/index.ts +35 -3
- package/src/state/exegia-provider.tsx +79 -0
- package/src/state/index.ts +4 -0
- package/src/state/store.ts +19 -0
- package/dist-lib/components/blocks/nav/sidebar-block.d.ts +0 -14
- package/dist-lib/components/blocks/nav/sidebar-nav-row.d.ts +0 -3
- package/dist-lib/components/blocks/nav/types.d.ts +0 -60
- package/dist-lib/components/blocks/nav/utils.d.ts +0 -1
- package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +0 -3
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +0 -3
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +0 -2
- package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +0 -2
- package/src/components/blocks/nav/__tests__/sidebar-block.test.tsx +0 -125
- package/src/components/blocks/nav/sidebar-block.tsx +0 -142
- package/src/components/blocks/nav/sidebar-nav-row.tsx +0 -78
- package/src/components/blocks/nav/types.ts +0 -65
- package/src/components/blocks/nav/utils.ts +0 -4
- package/src/components/blocks/shell/animated-sidebar-content.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-footer.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-group-content.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-group-label.tsx +0 -29
- package/src/components/blocks/shell/animated-sidebar-group.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-header.tsx +0 -16
- package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +0 -151
- package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +0 -20
- package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +0 -85
- package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +0 -19
- package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +0 -44
- package/src/components/blocks/shell/animated-sidebar-menu.tsx +0 -26
- /package/dist-lib/components/blocks/{nav/__tests__/sidebar-block.test.d.ts → auth/__tests__/auth-state-atom.test.d.ts} +0 -0
|
@@ -6,30 +6,70 @@ import {
|
|
|
6
6
|
useCallback,
|
|
7
7
|
useEffect,
|
|
8
8
|
useId,
|
|
9
|
+
useLayoutEffect,
|
|
9
10
|
useMemo,
|
|
10
11
|
useRef,
|
|
11
12
|
useState,
|
|
12
13
|
} from "react";
|
|
14
|
+
import { useAtomValue, useSetAtom, useStore } from "jotai";
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
aiSidebarAnnouncementAtom,
|
|
18
|
+
aiSidebarDraggingIdAtom,
|
|
19
|
+
aiSidebarDropTargetAtom,
|
|
20
|
+
aiSidebarExpandedIdsAtom,
|
|
21
|
+
aiSidebarFlatAtom,
|
|
22
|
+
aiSidebarFocusedIdAtom,
|
|
23
|
+
aiSidebarHoveredIdAtom,
|
|
24
|
+
aiSidebarItemsAtom,
|
|
25
|
+
aiSidebarMenuOpenIdAtom,
|
|
26
|
+
aiSidebarMovePendingAtom,
|
|
27
|
+
aiSidebarOwnActiveIdAtom,
|
|
28
|
+
aiSidebarOwnedExpandedIdsAtom,
|
|
29
|
+
aiSidebarOwnedItemsAtom,
|
|
30
|
+
aiSidebarRenamingIdAtom,
|
|
31
|
+
cancelAISidebarRenameAtom,
|
|
32
|
+
clearAISidebarHoverAtom,
|
|
33
|
+
closeAISidebarMenuAtom,
|
|
34
|
+
collapseAISidebarRowAtom,
|
|
35
|
+
collapseAllAISidebarRowsAtom,
|
|
36
|
+
endAISidebarDragAtom,
|
|
37
|
+
expandAISidebarRowAtom,
|
|
38
|
+
expandAllAISidebarRowsAtom,
|
|
39
|
+
focusAISidebarRowAtom,
|
|
40
|
+
mountAISidebarAtom,
|
|
41
|
+
moveAISidebarRowAtom,
|
|
42
|
+
normalizeAISidebarFocusAtom,
|
|
43
|
+
openAISidebarMenuAtom,
|
|
44
|
+
projectAISidebarActiveIdAtom,
|
|
45
|
+
projectAISidebarExpandedIdsAtom,
|
|
46
|
+
projectAISidebarItemsAtom,
|
|
47
|
+
removeAISidebarInstance,
|
|
48
|
+
renameAISidebarRowAtom,
|
|
49
|
+
resetAISidebarAtom,
|
|
50
|
+
revealAISidebarRowAtom,
|
|
51
|
+
selectAISidebarRowAtom,
|
|
52
|
+
setAISidebarHandlersAtom,
|
|
53
|
+
setAISidebarHoveredAtom,
|
|
54
|
+
startAISidebarRenameAtom,
|
|
55
|
+
toggleAISidebarRowAtom,
|
|
56
|
+
} from "./ai-sidebar-atom.ts";
|
|
13
57
|
import type {
|
|
58
|
+
AISidebarConfig,
|
|
14
59
|
AISidebarController,
|
|
15
|
-
|
|
60
|
+
AISidebarHandlers,
|
|
61
|
+
AISidebarSeed,
|
|
16
62
|
FlatResource,
|
|
17
|
-
SidebarResource,
|
|
18
|
-
SidebarResourceMove,
|
|
19
63
|
UseAISidebarOptions,
|
|
20
|
-
} from "./type.ts"
|
|
64
|
+
} from "./type.ts";
|
|
21
65
|
import {
|
|
22
|
-
ancestorIdsOf,
|
|
23
66
|
canContain,
|
|
24
67
|
containsResource,
|
|
25
|
-
expandableIdsOf,
|
|
26
68
|
findResource,
|
|
27
69
|
flattenResources,
|
|
28
|
-
|
|
29
|
-
renameResource,
|
|
30
|
-
} from "./utils.ts"
|
|
70
|
+
} from "./utils.ts";
|
|
31
71
|
|
|
32
|
-
export type { UseAISidebarOptions }
|
|
72
|
+
export type { UseAISidebarOptions };
|
|
33
73
|
|
|
34
74
|
/**
|
|
35
75
|
* The AI sidebar's headless controller — every behaviour the block
|
|
@@ -39,7 +79,7 @@ export type { UseAISidebarOptions }
|
|
|
39
79
|
*
|
|
40
80
|
* `items`, `activeId` and `expandedIds` are each controlled when passed
|
|
41
81
|
* and hook-owned via their `default*` twin otherwise. Reorder and rename
|
|
42
|
-
* are applied to the data optimistically
|
|
82
|
+
* are applied to the data optimistically and rolled back if
|
|
43
83
|
* `onMove`/`onRename` rejects, so a consumer never reduces the tree
|
|
44
84
|
* itself — `moveResource` and `renameResource` are exported for the ones
|
|
45
85
|
* who keep their data elsewhere.
|
|
@@ -49,8 +89,15 @@ export type { UseAISidebarOptions }
|
|
|
49
89
|
* <AISidebar controller={sidebar} />
|
|
50
90
|
* <Button onClick={sidebar.collapseAll}>Collapse all</Button>
|
|
51
91
|
* ```
|
|
92
|
+
*
|
|
93
|
+
* State lives in Jotai atoms keyed by `sidebarId`, so anything under
|
|
94
|
+
* `ExegiaProvider` can drive this sidebar without the controller — name it
|
|
95
|
+
* and reach for `useAISidebarState(sidebarId)` /
|
|
96
|
+
* `useAISidebarActions(sidebarId)` elsewhere.
|
|
52
97
|
*/
|
|
53
|
-
export function useAISidebar(
|
|
98
|
+
export function useAISidebar(
|
|
99
|
+
options: UseAISidebarOptions
|
|
100
|
+
): AISidebarController {
|
|
54
101
|
const {
|
|
55
102
|
items,
|
|
56
103
|
defaultItems = [],
|
|
@@ -62,60 +109,138 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
62
109
|
defaultActiveId = null,
|
|
63
110
|
onActiveChange,
|
|
64
111
|
onExpandedChange,
|
|
65
|
-
} = options
|
|
112
|
+
} = options;
|
|
113
|
+
|
|
114
|
+
// A generated key isolates unnamed sidebars from each other; an explicit
|
|
115
|
+
// `sidebarId` is the app's handle on this one.
|
|
116
|
+
const generatedId = useId();
|
|
117
|
+
const sidebarId = options.sidebarId ?? generatedId;
|
|
118
|
+
const store = useStore();
|
|
119
|
+
const hoverLayoutId = useId();
|
|
120
|
+
|
|
121
|
+
const controlledExpandedIds = options.expandedIds;
|
|
122
|
+
const controlsItems = items !== undefined;
|
|
123
|
+
const controlsActiveId = activeId !== undefined;
|
|
124
|
+
const controlsExpandedIds = controlledExpandedIds !== undefined;
|
|
66
125
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
() =>
|
|
126
|
+
// Primitives only, so this object is stable and the store write below runs
|
|
127
|
+
// once per real change rather than once per render.
|
|
128
|
+
const config = useMemo<AISidebarConfig>(
|
|
129
|
+
() => ({ controlsItems, controlsActiveId, controlsExpandedIds }),
|
|
130
|
+
[controlsActiveId, controlsExpandedIds, controlsItems]
|
|
71
131
|
);
|
|
72
|
-
|
|
73
|
-
|
|
132
|
+
|
|
133
|
+
const handlers = useMemo<AISidebarHandlers>(
|
|
134
|
+
() => ({
|
|
135
|
+
onItemsChange,
|
|
136
|
+
onMove,
|
|
137
|
+
onMoveError,
|
|
138
|
+
onRename,
|
|
139
|
+
onActiveChange,
|
|
140
|
+
onExpandedChange,
|
|
141
|
+
}),
|
|
142
|
+
[
|
|
143
|
+
onActiveChange,
|
|
144
|
+
onExpandedChange,
|
|
145
|
+
onItemsChange,
|
|
146
|
+
onMove,
|
|
147
|
+
onMoveError,
|
|
148
|
+
onRename,
|
|
149
|
+
]
|
|
74
150
|
);
|
|
75
|
-
const [draggingId, setDraggingId] = useState<string | null>(null);
|
|
76
|
-
const [dropTarget, setDropTarget] = useState<DropTarget | null>(null);
|
|
77
|
-
const [menuOpenId, setMenuOpenId] = useState<string | null>(null);
|
|
78
|
-
const [renamingId, setRenamingId] = useState<string | null>(null);
|
|
79
|
-
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
80
|
-
const hoverLayoutId = useId();
|
|
81
|
-
const [announcement, setAnnouncement] = useState("");
|
|
82
|
-
// `movePending` is mirrored in a ref because the guard has to read it
|
|
83
|
-
// synchronously, before React has committed the state change.
|
|
84
|
-
const [movePending, setMovePending] = useState(false);
|
|
85
|
-
const rowRefs = useRef(new Map<string, HTMLDivElement>());
|
|
86
|
-
const movePendingRef = useRef(false);
|
|
87
|
-
const renderedItems = internalItems;
|
|
88
|
-
const selectedId = activeId ?? internalActiveId;
|
|
89
151
|
|
|
90
|
-
|
|
152
|
+
// Read once: the `default*` options describe the mount, not every render.
|
|
153
|
+
const [seed] = useState<AISidebarSeed>(() => ({
|
|
154
|
+
items: items ?? defaultItems,
|
|
155
|
+
activeId: defaultActiveId,
|
|
156
|
+
focusedId: activeId ?? defaultActiveId,
|
|
157
|
+
expandedIds: [
|
|
158
|
+
...(controlledExpandedIds ?? options.defaultExpandedIds ?? []),
|
|
159
|
+
],
|
|
160
|
+
}));
|
|
161
|
+
|
|
162
|
+
const mount = useSetAtom(mountAISidebarAtom(sidebarId));
|
|
163
|
+
const publishHandlers = useSetAtom(setAISidebarHandlersAtom(sidebarId));
|
|
164
|
+
const projectItems = useSetAtom(projectAISidebarItemsAtom(sidebarId));
|
|
165
|
+
const projectActiveId = useSetAtom(projectAISidebarActiveIdAtom(sidebarId));
|
|
166
|
+
const projectExpandedIds = useSetAtom(
|
|
167
|
+
projectAISidebarExpandedIdsAtom(sidebarId)
|
|
168
|
+
);
|
|
169
|
+
const normalizeFocus = useSetAtom(normalizeAISidebarFocusAtom(sidebarId));
|
|
170
|
+
|
|
171
|
+
// Handlers first — they must be in the store before any action can fire.
|
|
172
|
+
// Rewritten every commit; no read atom depends on them, so nobody
|
|
173
|
+
// re-renders for it.
|
|
174
|
+
useLayoutEffect(() => {
|
|
175
|
+
publishHandlers(handlers);
|
|
176
|
+
}, [handlers, publishHandlers]);
|
|
177
|
+
|
|
178
|
+
// Before paint, so a seeded row never paints closed for a frame.
|
|
179
|
+
useLayoutEffect(() => {
|
|
180
|
+
mount(config, seed);
|
|
181
|
+
}, [config, mount, seed]);
|
|
182
|
+
|
|
183
|
+
// Controlled props stay the source of truth; the store carries a projection
|
|
184
|
+
// so the action atoms and remote readers see current data.
|
|
185
|
+
useLayoutEffect(() => {
|
|
186
|
+
if (items !== undefined) projectItems(items);
|
|
187
|
+
}, [items, projectItems]);
|
|
188
|
+
|
|
189
|
+
useLayoutEffect(() => {
|
|
190
|
+
if (activeId !== undefined) projectActiveId(activeId);
|
|
191
|
+
}, [activeId, projectActiveId]);
|
|
192
|
+
|
|
193
|
+
useLayoutEffect(() => {
|
|
194
|
+
if (controlledExpandedIds !== undefined)
|
|
195
|
+
projectExpandedIds(controlledExpandedIds);
|
|
196
|
+
}, [controlledExpandedIds, projectExpandedIds]);
|
|
197
|
+
|
|
198
|
+
// A sidebar the hook keyed is scrap once its component goes. An explicit
|
|
199
|
+
// `sidebarId` is the app's key and outlives the mount.
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (options.sidebarId !== undefined) return;
|
|
202
|
+
return () => removeAISidebarInstance(sidebarId);
|
|
203
|
+
}, [options.sidebarId, sidebarId]);
|
|
204
|
+
|
|
205
|
+
// Never `aiSidebarItemsAtom` — see the note on `aiSidebarOwnedItemsAtom`.
|
|
206
|
+
const ownedItems = useAtomValue(aiSidebarOwnedItemsAtom(sidebarId));
|
|
207
|
+
const renderedItems = items ?? ownedItems;
|
|
208
|
+
// `activeId ?? own` rather than the projected atom: a controlled `activeId`
|
|
209
|
+
// of `null` has always fallen through to the hook-owned selection.
|
|
210
|
+
const ownActiveId = useAtomValue(aiSidebarOwnActiveIdAtom(sidebarId));
|
|
211
|
+
const selectedId = activeId ?? ownActiveId;
|
|
212
|
+
const ownExpandedIds = useAtomValue(aiSidebarOwnedExpandedIdsAtom(sidebarId));
|
|
91
213
|
const expandedIds = useMemo(
|
|
92
|
-
() =>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
214
|
+
() =>
|
|
215
|
+
controlledExpandedIds ? new Set(controlledExpandedIds) : ownExpandedIds,
|
|
216
|
+
[controlledExpandedIds, ownExpandedIds]
|
|
217
|
+
);
|
|
218
|
+
const focusedId = useAtomValue(aiSidebarFocusedIdAtom(sidebarId));
|
|
219
|
+
const renamingId = useAtomValue(aiSidebarRenamingIdAtom(sidebarId));
|
|
220
|
+
const menuOpenId = useAtomValue(aiSidebarMenuOpenIdAtom(sidebarId));
|
|
221
|
+
const draggingId = useAtomValue(aiSidebarDraggingIdAtom(sidebarId));
|
|
222
|
+
const dropTarget = useAtomValue(aiSidebarDropTargetAtom(sidebarId));
|
|
223
|
+
const hoveredId = useAtomValue(aiSidebarHoveredIdAtom(sidebarId));
|
|
224
|
+
const announcement = useAtomValue(aiSidebarAnnouncementAtom(sidebarId));
|
|
225
|
+
const movePending = useAtomValue(aiSidebarMovePendingAtom(sidebarId));
|
|
101
226
|
|
|
102
227
|
const flat = useMemo(
|
|
103
228
|
() => flattenResources(renderedItems, expandedIds),
|
|
104
|
-
[expandedIds, renderedItems]
|
|
229
|
+
[expandedIds, renderedItems]
|
|
105
230
|
);
|
|
106
231
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
232
|
+
// The roving target must name a row that still renders: one that folds away
|
|
233
|
+
// hands focus to the first visible row instead of pointing at nothing. Last
|
|
234
|
+
// of the layout effects, so it reads the projections this commit wrote.
|
|
235
|
+
useLayoutEffect(() => {
|
|
236
|
+
normalizeFocus();
|
|
237
|
+
}, [flat, focusedId, normalizeFocus]);
|
|
113
238
|
|
|
114
239
|
useEffect(() => {
|
|
115
240
|
if (!menuOpenId) return;
|
|
116
241
|
const frame = requestAnimationFrame(() => {
|
|
117
242
|
const menus = Array.from(
|
|
118
|
-
document.querySelectorAll<HTMLElement>("[data-sidebar-resource-menu]")
|
|
243
|
+
document.querySelectorAll<HTMLElement>("[data-sidebar-resource-menu]")
|
|
119
244
|
);
|
|
120
245
|
menus
|
|
121
246
|
.find((menu) => menu.dataset.sidebarResourceMenu === menuOpenId)
|
|
@@ -125,177 +250,81 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
125
250
|
return () => cancelAnimationFrame(frame);
|
|
126
251
|
}, [menuOpenId]);
|
|
127
252
|
|
|
128
|
-
const updateItems = useCallback(
|
|
129
|
-
(next: SidebarResource[]) => {
|
|
130
|
-
setInternalItems(next);
|
|
131
|
-
onItemsChange?.(next);
|
|
132
|
-
},
|
|
133
|
-
[onItemsChange],
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
const commitExpanded = useCallback(
|
|
137
|
-
(next: Set<string>) => {
|
|
138
|
-
if (controlledExpandedIds === undefined) setOwnExpandedIds(next);
|
|
139
|
-
onExpandedChange?.([...next]);
|
|
140
|
-
},
|
|
141
|
-
[controlledExpandedIds, onExpandedChange],
|
|
142
|
-
);
|
|
143
|
-
|
|
144
253
|
const getItem = useCallback(
|
|
145
254
|
(id: string) => findResource(renderedItems, id),
|
|
146
|
-
[renderedItems]
|
|
255
|
+
[renderedItems]
|
|
147
256
|
);
|
|
148
257
|
|
|
149
258
|
const isExpanded = useCallback(
|
|
150
259
|
(id: string) => expandedIds.has(id),
|
|
151
|
-
[expandedIds]
|
|
260
|
+
[expandedIds]
|
|
152
261
|
);
|
|
153
262
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
);
|
|
161
|
-
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
[commitExpanded, expandedIds],
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
const expandAll = useCallback(() => {
|
|
183
|
-
commitExpanded(new Set(expandableIdsOf(renderedItems)));
|
|
184
|
-
}, [commitExpanded, renderedItems]);
|
|
185
|
-
|
|
186
|
-
const collapseAll = useCallback(() => {
|
|
187
|
-
commitExpanded(new Set());
|
|
188
|
-
}, [commitExpanded]);
|
|
189
|
-
|
|
190
|
-
const reveal = useCallback(
|
|
191
|
-
(id: string) => {
|
|
192
|
-
const ancestors = ancestorIdsOf(renderedItems, id);
|
|
193
|
-
if (!ancestors.some((ancestor) => !expandedIds.has(ancestor))) return;
|
|
194
|
-
commitExpanded(new Set([...expandedIds, ...ancestors]));
|
|
195
|
-
},
|
|
196
|
-
[commitExpanded, expandedIds, renderedItems],
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
const move = useCallback(
|
|
200
|
-
async (move: SidebarResourceMove) => {
|
|
201
|
-
if (movePendingRef.current) {
|
|
202
|
-
setAnnouncement("Wait for the current move to finish.");
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const before = renderedItems;
|
|
206
|
-
const next = moveResource(before, move);
|
|
207
|
-
if (!next || next === before) return;
|
|
208
|
-
|
|
209
|
-
movePendingRef.current = true;
|
|
210
|
-
setMovePending(true);
|
|
211
|
-
updateItems(next);
|
|
212
|
-
setDropTarget(null);
|
|
213
|
-
setDraggingId(null);
|
|
214
|
-
const moved = findResource(before, move.itemId);
|
|
215
|
-
const target = move.targetId ? findResource(before, move.targetId) : null;
|
|
216
|
-
setAnnouncement(
|
|
217
|
-
target
|
|
218
|
-
? `Moved ${moved?.label ?? "item"} ${move.position} ${target.label}.`
|
|
219
|
-
: `Moved ${moved?.label ?? "item"} to the top level.`,
|
|
220
|
-
);
|
|
263
|
+
// `useSetAtom` setters are already stable — no useCallback needed.
|
|
264
|
+
const select = useSetAtom(selectAISidebarRowAtom(sidebarId));
|
|
265
|
+
const expand = useSetAtom(expandAISidebarRowAtom(sidebarId));
|
|
266
|
+
const collapse = useSetAtom(collapseAISidebarRowAtom(sidebarId));
|
|
267
|
+
const toggleExpanded = useSetAtom(toggleAISidebarRowAtom(sidebarId));
|
|
268
|
+
const expandAll = useSetAtom(expandAllAISidebarRowsAtom(sidebarId));
|
|
269
|
+
const collapseAll = useSetAtom(collapseAllAISidebarRowsAtom(sidebarId));
|
|
270
|
+
const reveal = useSetAtom(revealAISidebarRowAtom(sidebarId));
|
|
271
|
+
const startRename = useSetAtom(startAISidebarRenameAtom(sidebarId));
|
|
272
|
+
const cancelRename = useSetAtom(cancelAISidebarRenameAtom(sidebarId));
|
|
273
|
+
const rename = useSetAtom(renameAISidebarRowAtom(sidebarId));
|
|
274
|
+
const openMenu = useSetAtom(openAISidebarMenuAtom(sidebarId));
|
|
275
|
+
const closeMenuInStore = useSetAtom(closeAISidebarMenuAtom(sidebarId));
|
|
276
|
+
const move = useSetAtom(moveAISidebarRowAtom(sidebarId));
|
|
277
|
+
const reset = useSetAtom(resetAISidebarAtom(sidebarId));
|
|
278
|
+
const setFocusedRow = useSetAtom(focusAISidebarRowAtom(sidebarId));
|
|
279
|
+
const onRowHover = useSetAtom(setAISidebarHoveredAtom(sidebarId));
|
|
280
|
+
const clearHover = useSetAtom(clearAISidebarHoverAtom(sidebarId));
|
|
281
|
+
const onRowDragEnd = useSetAtom(endAISidebarDragAtom(sidebarId));
|
|
282
|
+
const setDraggingId = useSetAtom(aiSidebarDraggingIdAtom(sidebarId));
|
|
283
|
+
const setDropTarget = useSetAtom(aiSidebarDropTargetAtom(sidebarId));
|
|
284
|
+
|
|
285
|
+
// The one piece of row state that cannot live in the store: these are DOM
|
|
286
|
+
// nodes. The block owns them, and every DOM focus move goes through here.
|
|
287
|
+
const rowRefs = useRef(new Map<string, HTMLDivElement>());
|
|
221
288
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
setAnnouncement(`Move failed. ${moved?.label ?? "Item"} was restored.`);
|
|
227
|
-
onMoveError?.(error, move);
|
|
228
|
-
} finally {
|
|
229
|
-
movePendingRef.current = false;
|
|
230
|
-
setMovePending(false);
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
[onMove, onMoveError, renderedItems, updateItems],
|
|
234
|
-
);
|
|
289
|
+
const setRowRef = useCallback((id: string, node: HTMLDivElement | null) => {
|
|
290
|
+
if (node) rowRefs.current.set(id, node);
|
|
291
|
+
else rowRefs.current.delete(id);
|
|
292
|
+
}, []);
|
|
235
293
|
|
|
236
|
-
const
|
|
237
|
-
setFocusedId(id);
|
|
294
|
+
const focusRow = useCallback((id: string) => {
|
|
238
295
|
requestAnimationFrame(() => rowRefs.current.get(id)?.focus());
|
|
239
296
|
}, []);
|
|
240
297
|
|
|
241
|
-
const
|
|
242
|
-
(id: string) => {
|
|
243
|
-
if (activeId === undefined) setInternalActiveId(id);
|
|
244
|
-
onActiveChange?.(id);
|
|
245
|
-
},
|
|
246
|
-
[activeId, onActiveChange],
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
const startRename = useCallback(
|
|
298
|
+
const focus = useCallback(
|
|
250
299
|
(id: string) => {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (!findResource(renderedItems, id)) return;
|
|
254
|
-
setRenamingId(id);
|
|
300
|
+
setFocusedRow(id);
|
|
301
|
+
focusRow(id);
|
|
255
302
|
},
|
|
256
|
-
[
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
const cancelRename = useCallback(() => setRenamingId(null), []);
|
|
260
|
-
|
|
261
|
-
const rename = useCallback(
|
|
262
|
-
(id: string, label: string) => {
|
|
263
|
-
const trimmed = label.trim();
|
|
264
|
-
setRenamingId(null);
|
|
265
|
-
const item = findResource(renderedItems, id);
|
|
266
|
-
if (!item || !trimmed || trimmed === item.label) return;
|
|
267
|
-
const before = renderedItems;
|
|
268
|
-
updateItems(renameResource(before, id, trimmed));
|
|
269
|
-
void Promise.resolve(onRename?.(item, trimmed)).catch(() => {
|
|
270
|
-
updateItems(before);
|
|
271
|
-
setAnnouncement(`Rename failed. ${item.label} was restored.`);
|
|
272
|
-
});
|
|
273
|
-
},
|
|
274
|
-
[onRename, renderedItems, updateItems],
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
const openMenu = useCallback(
|
|
278
|
-
(id: string) => {
|
|
279
|
-
if (!findResource(renderedItems, id)) return;
|
|
280
|
-
setMenuOpenId(id);
|
|
281
|
-
},
|
|
282
|
-
[renderedItems],
|
|
303
|
+
[focusRow, setFocusedRow]
|
|
283
304
|
);
|
|
284
305
|
|
|
285
306
|
// Closing returns focus to the row that owned the menu — otherwise focus
|
|
286
|
-
// falls back to the body and the keyboard user loses their place.
|
|
307
|
+
// falls back to the body and the keyboard user loses their place. The atom
|
|
308
|
+
// hands back the id it closed so only the DOM half happens here.
|
|
287
309
|
const closeMenu = useCallback(() => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
310
|
+
const closed = closeMenuInStore();
|
|
311
|
+
if (closed) focusRow(closed);
|
|
312
|
+
}, [closeMenuInStore, focusRow]);
|
|
313
|
+
|
|
314
|
+
// Roving nav reads the visible rows out of the store when a key lands
|
|
315
|
+
// rather than closing over them, so one stable handler serves every row.
|
|
316
|
+
const readFlat = useCallback(
|
|
317
|
+
() => store.get(aiSidebarFlatAtom(sidebarId)),
|
|
318
|
+
[sidebarId, store]
|
|
319
|
+
);
|
|
293
320
|
|
|
294
321
|
const onRowKeyDown = useCallback(
|
|
295
322
|
(event: KeyboardEvent<HTMLDivElement>, row: FlatResource) => {
|
|
296
|
-
const
|
|
297
|
-
const
|
|
298
|
-
const
|
|
323
|
+
const flatRows = readFlat();
|
|
324
|
+
const expanded = store.get(aiSidebarExpandedIdsAtom(sidebarId));
|
|
325
|
+
const index = flatRows.findIndex(({ item }) => item.id === row.item.id);
|
|
326
|
+
const previous = flatRows[index - 1];
|
|
327
|
+
const next = flatRows[index + 1];
|
|
299
328
|
const moveModifier = event.altKey && event.shiftKey;
|
|
300
329
|
|
|
301
330
|
if (event.key === "ArrowDown" && !moveModifier && next) {
|
|
@@ -308,14 +337,14 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
308
337
|
focus(previous.item.id);
|
|
309
338
|
return;
|
|
310
339
|
}
|
|
311
|
-
if (event.key === "Home" &&
|
|
340
|
+
if (event.key === "Home" && flatRows[0]) {
|
|
312
341
|
event.preventDefault();
|
|
313
|
-
focus(
|
|
342
|
+
focus(flatRows[0].item.id);
|
|
314
343
|
return;
|
|
315
344
|
}
|
|
316
|
-
if (event.key === "End" &&
|
|
345
|
+
if (event.key === "End" && flatRows.at(-1)) {
|
|
317
346
|
event.preventDefault();
|
|
318
|
-
focus(
|
|
347
|
+
focus(flatRows.at(-1)?.item.id ?? row.item.id);
|
|
319
348
|
return;
|
|
320
349
|
}
|
|
321
350
|
|
|
@@ -326,7 +355,7 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
326
355
|
} else if (
|
|
327
356
|
moveModifier ||
|
|
328
357
|
["ArrowRight", "Enter", " ", "F2", "ContextMenu"].includes(
|
|
329
|
-
event.key
|
|
358
|
+
event.key
|
|
330
359
|
) ||
|
|
331
360
|
(event.shiftKey && event.key === "F10")
|
|
332
361
|
) {
|
|
@@ -337,33 +366,54 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
337
366
|
|
|
338
367
|
if (moveModifier && event.key === "ArrowUp" && previous) {
|
|
339
368
|
event.preventDefault();
|
|
340
|
-
void move({
|
|
369
|
+
void move({
|
|
370
|
+
itemId: row.item.id,
|
|
371
|
+
targetId: previous.item.id,
|
|
372
|
+
position: "before",
|
|
373
|
+
});
|
|
341
374
|
return;
|
|
342
375
|
}
|
|
343
376
|
if (moveModifier && event.key === "ArrowDown" && next) {
|
|
344
377
|
event.preventDefault();
|
|
345
|
-
void move({
|
|
378
|
+
void move({
|
|
379
|
+
itemId: row.item.id,
|
|
380
|
+
targetId: next.item.id,
|
|
381
|
+
position: "after",
|
|
382
|
+
});
|
|
346
383
|
return;
|
|
347
384
|
}
|
|
348
|
-
if (
|
|
385
|
+
if (
|
|
386
|
+
moveModifier &&
|
|
387
|
+
event.key === "ArrowRight" &&
|
|
388
|
+
previous &&
|
|
389
|
+
canContain(previous.item)
|
|
390
|
+
) {
|
|
349
391
|
event.preventDefault();
|
|
350
392
|
expand(previous.item.id);
|
|
351
|
-
void move({
|
|
393
|
+
void move({
|
|
394
|
+
itemId: row.item.id,
|
|
395
|
+
targetId: previous.item.id,
|
|
396
|
+
position: "inside",
|
|
397
|
+
});
|
|
352
398
|
return;
|
|
353
399
|
}
|
|
354
400
|
if (moveModifier && event.key === "ArrowLeft" && row.parentId) {
|
|
355
401
|
event.preventDefault();
|
|
356
|
-
void move({
|
|
402
|
+
void move({
|
|
403
|
+
itemId: row.item.id,
|
|
404
|
+
targetId: row.parentId,
|
|
405
|
+
position: "after",
|
|
406
|
+
});
|
|
357
407
|
return;
|
|
358
408
|
}
|
|
359
409
|
|
|
360
410
|
if (event.key === "ArrowRight" && canContain(row.item)) {
|
|
361
411
|
event.preventDefault();
|
|
362
|
-
if (!
|
|
412
|
+
if (!expanded.has(row.item.id)) expand(row.item.id);
|
|
363
413
|
else if (next?.parentId === row.item.id) focus(next.item.id);
|
|
364
414
|
} else if (event.key === "ArrowLeft") {
|
|
365
415
|
event.preventDefault();
|
|
366
|
-
if (
|
|
416
|
+
if (expanded.has(row.item.id)) collapse(row.item.id);
|
|
367
417
|
else if (row.parentId) focus(row.parentId);
|
|
368
418
|
} else if (event.key === "Enter" || event.key === " ") {
|
|
369
419
|
event.preventDefault();
|
|
@@ -372,7 +422,10 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
372
422
|
} else if (event.key === "F2") {
|
|
373
423
|
event.preventDefault();
|
|
374
424
|
startRename(row.item.id);
|
|
375
|
-
} else if (
|
|
425
|
+
} else if (
|
|
426
|
+
event.key === "ContextMenu" ||
|
|
427
|
+
(event.shiftKey && event.key === "F10")
|
|
428
|
+
) {
|
|
376
429
|
event.preventDefault();
|
|
377
430
|
openMenu(row.item.id);
|
|
378
431
|
}
|
|
@@ -380,39 +433,49 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
380
433
|
[
|
|
381
434
|
collapse,
|
|
382
435
|
expand,
|
|
383
|
-
expandedIds,
|
|
384
|
-
flat,
|
|
385
436
|
focus,
|
|
386
437
|
move,
|
|
387
438
|
openMenu,
|
|
439
|
+
readFlat,
|
|
388
440
|
select,
|
|
441
|
+
sidebarId,
|
|
389
442
|
startRename,
|
|
443
|
+
store,
|
|
390
444
|
toggleExpanded,
|
|
391
|
-
]
|
|
445
|
+
]
|
|
392
446
|
);
|
|
393
447
|
|
|
448
|
+
// The drag handlers read drag state out of the store when they run instead
|
|
449
|
+
// of closing over it. That is what keeps them stable for the life of the
|
|
450
|
+
// sidebar, so every row can share one set without re-rendering on a drag.
|
|
394
451
|
const onRootDragOver = useCallback(
|
|
395
452
|
(event: DragEvent<HTMLDivElement>) => {
|
|
396
|
-
if (
|
|
453
|
+
if (
|
|
454
|
+
!store.get(aiSidebarDraggingIdAtom(sidebarId)) ||
|
|
455
|
+
event.target !== event.currentTarget
|
|
456
|
+
)
|
|
457
|
+
return;
|
|
397
458
|
event.preventDefault();
|
|
398
459
|
setDropTarget({ id: null, position: "after" });
|
|
399
460
|
},
|
|
400
|
-
[
|
|
461
|
+
[setDropTarget, sidebarId, store]
|
|
401
462
|
);
|
|
402
463
|
|
|
403
464
|
const onDrop = useCallback(
|
|
404
465
|
(event: DragEvent<HTMLDivElement>) => {
|
|
405
466
|
event.preventDefault();
|
|
406
467
|
event.stopPropagation();
|
|
407
|
-
|
|
468
|
+
const dragging = store.get(aiSidebarDraggingIdAtom(sidebarId));
|
|
469
|
+
const target = store.get(aiSidebarDropTargetAtom(sidebarId));
|
|
470
|
+
if (dragging && target) {
|
|
408
471
|
void move({
|
|
409
|
-
itemId:
|
|
410
|
-
targetId:
|
|
411
|
-
position:
|
|
472
|
+
itemId: dragging,
|
|
473
|
+
targetId: target.id,
|
|
474
|
+
position: target.position,
|
|
412
475
|
});
|
|
413
476
|
}
|
|
414
477
|
},
|
|
415
|
-
[
|
|
478
|
+
[move, sidebarId, store]
|
|
416
479
|
);
|
|
417
480
|
|
|
418
481
|
const onRowDragStart = useCallback(
|
|
@@ -421,18 +484,17 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
421
484
|
event.dataTransfer.effectAllowed = "move";
|
|
422
485
|
event.dataTransfer.setData("text/plain", id);
|
|
423
486
|
},
|
|
424
|
-
[]
|
|
487
|
+
[setDraggingId]
|
|
425
488
|
);
|
|
426
489
|
|
|
427
|
-
const onRowDragEnd = useCallback(() => {
|
|
428
|
-
setDraggingId(null);
|
|
429
|
-
setDropTarget(null);
|
|
430
|
-
}, []);
|
|
431
|
-
|
|
432
490
|
const onRowDragOver = useCallback(
|
|
433
491
|
(event: DragEvent<HTMLDivElement>, targetRow: FlatResource) => {
|
|
434
|
-
|
|
435
|
-
|
|
492
|
+
const dragging = store.get(aiSidebarDraggingIdAtom(sidebarId));
|
|
493
|
+
if (!dragging || dragging === targetRow.item.id) return;
|
|
494
|
+
const source = findResource(
|
|
495
|
+
store.get(aiSidebarItemsAtom(sidebarId)),
|
|
496
|
+
dragging
|
|
497
|
+
);
|
|
436
498
|
if (source && containsResource(source, targetRow.item.id)) return;
|
|
437
499
|
event.preventDefault();
|
|
438
500
|
event.stopPropagation();
|
|
@@ -449,22 +511,9 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
449
511
|
: "after";
|
|
450
512
|
setDropTarget({ id: targetRow.item.id, position });
|
|
451
513
|
},
|
|
452
|
-
[
|
|
514
|
+
[setDropTarget, sidebarId, store]
|
|
453
515
|
);
|
|
454
516
|
|
|
455
|
-
const onRowHover = useCallback((id: string, hovered: boolean) => {
|
|
456
|
-
setHoveredId((current) =>
|
|
457
|
-
hovered ? id : current === id ? null : current,
|
|
458
|
-
);
|
|
459
|
-
}, []);
|
|
460
|
-
|
|
461
|
-
const clearHover = useCallback(() => setHoveredId(null), []);
|
|
462
|
-
|
|
463
|
-
const setRowRef = useCallback((id: string, node: HTMLDivElement | null) => {
|
|
464
|
-
if (node) rowRefs.current.set(id, node);
|
|
465
|
-
else rowRefs.current.delete(id);
|
|
466
|
-
}, []);
|
|
467
|
-
|
|
468
517
|
const dnd = useMemo(
|
|
469
518
|
() => ({
|
|
470
519
|
draggingId,
|
|
@@ -483,7 +532,7 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
483
532
|
onRowDragStart,
|
|
484
533
|
onRowDragEnd,
|
|
485
534
|
onRowDragOver,
|
|
486
|
-
]
|
|
535
|
+
]
|
|
487
536
|
);
|
|
488
537
|
|
|
489
538
|
const hover = useMemo(
|
|
@@ -493,11 +542,12 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
493
542
|
onRowHover,
|
|
494
543
|
clear: clearHover,
|
|
495
544
|
}),
|
|
496
|
-
[clearHover, hoverLayoutId, hoveredId, onRowHover]
|
|
545
|
+
[clearHover, hoverLayoutId, hoveredId, onRowHover]
|
|
497
546
|
);
|
|
498
547
|
|
|
499
548
|
return useMemo<AISidebarController>(
|
|
500
549
|
() => ({
|
|
550
|
+
sidebarId,
|
|
501
551
|
items: renderedItems,
|
|
502
552
|
flat,
|
|
503
553
|
getItem,
|
|
@@ -523,12 +573,14 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
523
573
|
move,
|
|
524
574
|
movePending,
|
|
525
575
|
announcement,
|
|
576
|
+
reset,
|
|
526
577
|
dnd,
|
|
527
578
|
hover,
|
|
528
579
|
onRowKeyDown,
|
|
529
580
|
setRowRef,
|
|
530
581
|
}),
|
|
531
582
|
[
|
|
583
|
+
sidebarId,
|
|
532
584
|
renderedItems,
|
|
533
585
|
flat,
|
|
534
586
|
getItem,
|
|
@@ -554,10 +606,11 @@ export function useAISidebar(options: UseAISidebarOptions): AISidebarController
|
|
|
554
606
|
move,
|
|
555
607
|
movePending,
|
|
556
608
|
announcement,
|
|
609
|
+
reset,
|
|
557
610
|
dnd,
|
|
558
611
|
hover,
|
|
559
612
|
onRowKeyDown,
|
|
560
613
|
setRowRef,
|
|
561
|
-
]
|
|
614
|
+
]
|
|
562
615
|
);
|
|
563
616
|
}
|