@djangocfg/widget-data 0.1.1

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.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -0
  3. package/package.json +122 -0
  4. package/src/DataGrid/README.md +48 -0
  5. package/src/DataGrid/context/DataGridContext.tsx +442 -0
  6. package/src/DataGrid/context/index.ts +4 -0
  7. package/src/DataGrid/index.ts +34 -0
  8. package/src/DataGrid/lazy.tsx +23 -0
  9. package/src/DataGrid/parts/DataGrid.tsx +125 -0
  10. package/src/DataGrid/parts/DataGridCell.tsx +30 -0
  11. package/src/DataGrid/parts/DataGridHeader.tsx +111 -0
  12. package/src/DataGrid/parts/DataGridPagination.tsx +105 -0
  13. package/src/DataGrid/parts/DataGridRow.tsx +84 -0
  14. package/src/DataGrid/parts/index.ts +7 -0
  15. package/src/DataGrid/types.ts +95 -0
  16. package/src/DataTable/README.md +42 -0
  17. package/src/DataTable/context/DataTableContext.tsx +434 -0
  18. package/src/DataTable/context/index.ts +4 -0
  19. package/src/DataTable/index.ts +33 -0
  20. package/src/DataTable/lazy.tsx +23 -0
  21. package/src/DataTable/parts/DataTable.tsx +142 -0
  22. package/src/DataTable/parts/DataTableHeader.tsx +103 -0
  23. package/src/DataTable/parts/DataTablePagination.tsx +115 -0
  24. package/src/DataTable/parts/DataTableRow.tsx +96 -0
  25. package/src/DataTable/parts/index.ts +6 -0
  26. package/src/DataTable/types.ts +88 -0
  27. package/src/FileIcon/FileIcon.tsx +87 -0
  28. package/src/FileIcon/get-file-icon.ts +73 -0
  29. package/src/FileIcon/icons/icon-data.ts +399 -0
  30. package/src/FileIcon/index.ts +19 -0
  31. package/src/FileIcon/loader.ts +29 -0
  32. package/src/FileIcon/specialFolders.ts +110 -0
  33. package/src/FileIcon/treeAdapter.tsx +49 -0
  34. package/src/JsonTree/JsonViewer.tsx +720 -0
  35. package/src/JsonTree/README.md +142 -0
  36. package/src/JsonTree/index.tsx +11 -0
  37. package/src/JsonTree/lazy.tsx +22 -0
  38. package/src/JsonTree/types.ts +91 -0
  39. package/src/Kanban/context/KanbanContext.tsx +165 -0
  40. package/src/Kanban/context/index.ts +4 -0
  41. package/src/Kanban/index.ts +31 -0
  42. package/src/Kanban/lazy.tsx +23 -0
  43. package/src/Kanban/parts/Kanban.tsx +77 -0
  44. package/src/Kanban/parts/KanbanCard.tsx +76 -0
  45. package/src/Kanban/parts/KanbanColumn.tsx +79 -0
  46. package/src/Kanban/parts/KanbanDragOverlay.tsx +46 -0
  47. package/src/Kanban/parts/index.ts +6 -0
  48. package/src/Kanban/types.ts +45 -0
  49. package/src/Listbox/context/ListboxProvider.tsx +616 -0
  50. package/src/Listbox/context/index.ts +8 -0
  51. package/src/Listbox/index.ts +28 -0
  52. package/src/Listbox/lazy.tsx +31 -0
  53. package/src/Listbox/parts/ListboxGroup.tsx +71 -0
  54. package/src/Listbox/parts/ListboxItem.tsx +151 -0
  55. package/src/Listbox/parts/index.ts +12 -0
  56. package/src/Listbox/types.ts +98 -0
  57. package/src/Masonry/README.md +79 -0
  58. package/src/Masonry/context/MasonryProvider.tsx +1205 -0
  59. package/src/Masonry/context/index.ts +8 -0
  60. package/src/Masonry/index.ts +18 -0
  61. package/src/Masonry/lazy.tsx +20 -0
  62. package/src/Masonry/parts/index.ts +2 -0
  63. package/src/Masonry/types.ts +44 -0
  64. package/src/Timeline/context/TimelineProvider.tsx +474 -0
  65. package/src/Timeline/context/index.ts +16 -0
  66. package/src/Timeline/index.ts +29 -0
  67. package/src/Timeline/lazy.tsx +27 -0
  68. package/src/Timeline/parts/index.ts +2 -0
  69. package/src/Timeline/types.ts +25 -0
  70. package/src/Tree/FinderTree.tsx +42 -0
  71. package/src/Tree/README.md +542 -0
  72. package/src/Tree/TreeDndProvider.tsx +137 -0
  73. package/src/Tree/TreeRoot.tsx +290 -0
  74. package/src/Tree/components/TreeChevron.tsx +48 -0
  75. package/src/Tree/components/TreeContent.tsx +70 -0
  76. package/src/Tree/components/TreeDropIndicator.tsx +65 -0
  77. package/src/Tree/components/TreeEmpty.tsx +22 -0
  78. package/src/Tree/components/TreeEmptyArea.tsx +160 -0
  79. package/src/Tree/components/TreeError.tsx +24 -0
  80. package/src/Tree/components/TreeIcon.tsx +39 -0
  81. package/src/Tree/components/TreeIndentGuides.tsx +33 -0
  82. package/src/Tree/components/TreeLabel.tsx +39 -0
  83. package/src/Tree/components/TreeRenameInput.tsx +114 -0
  84. package/src/Tree/components/TreeRow.tsx +300 -0
  85. package/src/Tree/components/TreeSearchInput.tsx +50 -0
  86. package/src/Tree/components/TreeSkeleton.tsx +22 -0
  87. package/src/Tree/components/index.ts +28 -0
  88. package/src/Tree/context/TreeContext.tsx +409 -0
  89. package/src/Tree/context/TreeContextValue.ts +139 -0
  90. package/src/Tree/context/async-children/collect-ids.ts +27 -0
  91. package/src/Tree/context/async-children/index.ts +8 -0
  92. package/src/Tree/context/async-children/use-async-children.ts +157 -0
  93. package/src/Tree/context/clipboard/index.ts +4 -0
  94. package/src/Tree/context/clipboard/use-clipboard.ts +115 -0
  95. package/src/Tree/context/dnd/index.ts +8 -0
  96. package/src/Tree/context/dnd/use-dnd.ts +194 -0
  97. package/src/Tree/context/expansion/index.ts +4 -0
  98. package/src/Tree/context/expansion/use-expansion.ts +55 -0
  99. package/src/Tree/context/hooks.ts +177 -0
  100. package/src/Tree/context/index.ts +16 -0
  101. package/src/Tree/context/menu/builtin-actions.ts +358 -0
  102. package/src/Tree/context/menu/index.ts +11 -0
  103. package/src/Tree/context/menu/render.tsx +104 -0
  104. package/src/Tree/context/menu/use-resolved-menu.ts +141 -0
  105. package/src/Tree/context/persist/index.ts +4 -0
  106. package/src/Tree/context/persist/use-persist-sync.ts +74 -0
  107. package/src/Tree/context/rename/index.ts +4 -0
  108. package/src/Tree/context/rename/use-rename.ts +113 -0
  109. package/src/Tree/context/selection/index.ts +4 -0
  110. package/src/Tree/context/selection/use-selection.ts +146 -0
  111. package/src/Tree/context/state/index.ts +6 -0
  112. package/src/Tree/context/state/initial.ts +41 -0
  113. package/src/Tree/context/state/reducer.ts +76 -0
  114. package/src/Tree/context/state/types.ts +46 -0
  115. package/src/Tree/data/appearance.ts +237 -0
  116. package/src/Tree/data/childCache.ts +43 -0
  117. package/src/Tree/data/clipboard.ts +33 -0
  118. package/src/Tree/data/createDemoTree.ts +42 -0
  119. package/src/Tree/data/dnd.ts +123 -0
  120. package/src/Tree/data/finderShortcuts.ts +67 -0
  121. package/src/Tree/data/flatten.ts +66 -0
  122. package/src/Tree/data/index.ts +45 -0
  123. package/src/Tree/data/persist.ts +62 -0
  124. package/src/Tree/data/renameUtils.ts +51 -0
  125. package/src/Tree/data/selection.ts +161 -0
  126. package/src/Tree/hooks/finder-hotkeys/build-ctx.ts +48 -0
  127. package/src/Tree/hooks/finder-hotkeys/index.ts +8 -0
  128. package/src/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts +166 -0
  129. package/src/Tree/hooks/index.ts +25 -0
  130. package/src/Tree/hooks/keyboard/activation.ts +27 -0
  131. package/src/Tree/hooks/keyboard/arrow-nav.ts +26 -0
  132. package/src/Tree/hooks/keyboard/expand-collapse.ts +54 -0
  133. package/src/Tree/hooks/keyboard/index.ts +10 -0
  134. package/src/Tree/hooks/keyboard/types.ts +39 -0
  135. package/src/Tree/hooks/keyboard/use-tree-keyboard.ts +196 -0
  136. package/src/Tree/hooks/type-ahead/index.ts +5 -0
  137. package/src/Tree/hooks/type-ahead/match-prefix.ts +42 -0
  138. package/src/Tree/hooks/type-ahead/use-tree-type-ahead.ts +89 -0
  139. package/src/Tree/index.tsx +135 -0
  140. package/src/Tree/lazy.tsx +14 -0
  141. package/src/Tree/types/activation.ts +30 -0
  142. package/src/Tree/types/adapter.ts +70 -0
  143. package/src/Tree/types/index.ts +27 -0
  144. package/src/Tree/types/labels.ts +97 -0
  145. package/src/Tree/types/loader.ts +9 -0
  146. package/src/Tree/types/node.ts +38 -0
  147. package/src/Tree/types/root-props.ts +158 -0
  148. package/src/Tree/types/selection.ts +3 -0
  149. package/src/Tree/types/slots.ts +64 -0
  150. package/src/index.ts +21 -0
  151. package/src/stories/_fixtures/categories.ts +51 -0
  152. package/src/stories/_fixtures/finder-store.ts +196 -0
  153. package/src/stories/_fixtures/fs.ts +83 -0
  154. package/src/stories/_fixtures/index.ts +5 -0
  155. package/src/stories/_fixtures/org.ts +46 -0
  156. package/src/stories/_fixtures/status.ts +21 -0
  157. package/src/styles/index.css +10 -0
@@ -0,0 +1,409 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import { createContext, useCallback, useMemo, useReducer, useRef } from 'react';
5
+
6
+ import { flattenTree } from '../data/flatten';
7
+ import { loadTreeState } from '../data/persist';
8
+ import { resolveAppearance } from '../data/appearance';
9
+ import {
10
+ DEFAULT_TREE_LABELS,
11
+ type FlatRow,
12
+ type TreeActivateOptions,
13
+ type TreeContextMenuSlot,
14
+ type TreeItemId,
15
+ type TreeLabels,
16
+ type TreeNode,
17
+ type TreeRootProps,
18
+ } from '../types';
19
+
20
+ import { reducer, createInitialState } from './state';
21
+ import { useAsyncChildren } from './async-children';
22
+ import { useExpansion } from './expansion';
23
+ import { useSelection } from './selection';
24
+ import { useRename } from './rename';
25
+ import { useClipboard } from './clipboard';
26
+ import { useResolvedMenu, renderItemsAsContextMenu, tidyMenuItems } from './menu';
27
+ import { useDnd, type UseDndReturn } from './dnd';
28
+ import { usePersistSync } from './persist';
29
+ import type { TreeContextValue } from './TreeContextValue';
30
+
31
+ // Re-exported from this module: the value interface (so consumers
32
+ // continue to `import type { TreeContextValue }` from `./context`).
33
+ export type { TreeContextValue } from './TreeContextValue';
34
+
35
+ /**
36
+ * Internal context object. Exported so `TreeRoot` can wrap it with an
37
+ * override-provider that injects a slot-form `renderContextMenu`
38
+ * derived from the declarative resolver. Consumers should use
39
+ * `useTreeContext()` instead of touching this directly.
40
+ */
41
+ export const TreeContext = createContext<TreeContextValue<unknown> | null>(null);
42
+
43
+ export function useTreeContext<T>(): TreeContextValue<T> {
44
+ const ctx = React.useContext(TreeContext);
45
+ if (!ctx) {
46
+ throw new Error('useTreeContext must be used inside <TreeProvider>');
47
+ }
48
+ return ctx as TreeContextValue<T>;
49
+ }
50
+
51
+ // =====================================================================
52
+ // Provider — thin assembly. The real work lives in:
53
+ //
54
+ // state/ reducer + initial state
55
+ // async-children/ child cache, nodeById, fetchChildren, refresh, refreshAll
56
+ // expansion/ expand / collapse / toggle / expandAll / collapseAll
57
+ // selection/ click / move / select-all + plain select / clear
58
+ // rename/ start / cancel / commit
59
+ // clipboard/ cut / copy / paste / clear
60
+ // menu/ built-in actions registry + merged declarative resolver
61
+ // persist/ localStorage + onSelectionChange/onExpansionChange
62
+ //
63
+ // This file only stitches them together and shapes the final
64
+ // `TreeContextValue`. If a feature grows, add a folder above — don't
65
+ // extend this file.
66
+ // =====================================================================
67
+
68
+ export interface TreeProviderProps<T>
69
+ extends Pick<
70
+ TreeRootProps<T>,
71
+ | 'data'
72
+ | 'getItemName'
73
+ | 'loadChildren'
74
+ | 'selectionMode'
75
+ | 'activationMode'
76
+ | 'initialExpandedIds'
77
+ | 'initialSelectedIds'
78
+ | 'indent'
79
+ | 'appearance'
80
+ | 'onSelectionChange'
81
+ | 'onExpansionChange'
82
+ | 'onActivate'
83
+ | 'filterNode'
84
+ | 'enableSearch'
85
+ | 'showIndentGuides'
86
+ | 'renderIcon'
87
+ | 'renderLabel'
88
+ | 'renderActions'
89
+ | 'renderContextMenu'
90
+ | 'contextMenuActions'
91
+ | 'labels'
92
+ | 'persistKey'
93
+ | 'persistSelection'
94
+ | 'adapter'
95
+ | 'defaultMenuItems'
96
+ | 'enableInlineRename'
97
+ | 'enableDnD'
98
+ | 'canDrop'
99
+ > {
100
+ children: React.ReactNode;
101
+ }
102
+
103
+ export function TreeProvider<T>(props: TreeProviderProps<T>) {
104
+ const {
105
+ data,
106
+ getItemName,
107
+ loadChildren,
108
+ selectionMode = 'single',
109
+ activationMode = 'single-click',
110
+ initialExpandedIds,
111
+ initialSelectedIds,
112
+ indent,
113
+ appearance,
114
+ onSelectionChange,
115
+ onExpansionChange,
116
+ onActivate,
117
+ filterNode,
118
+ enableSearch = false,
119
+ showIndentGuides = false,
120
+ renderIcon,
121
+ renderLabel,
122
+ renderActions,
123
+ renderContextMenu,
124
+ contextMenuActions,
125
+ labels: labelsOverride,
126
+ persistKey,
127
+ persistSelection = false,
128
+ adapter,
129
+ defaultMenuItems,
130
+ enableInlineRename = false,
131
+ enableDnD = false,
132
+ canDrop,
133
+ children,
134
+ } = props;
135
+
136
+ // ---- Stable config ------------------------------------------------
137
+
138
+ const labels = useMemo<TreeLabels>(
139
+ () => ({ ...DEFAULT_TREE_LABELS, ...labelsOverride }),
140
+ [labelsOverride],
141
+ );
142
+
143
+ const resolvedAppearance = useMemo(
144
+ () => resolveAppearance(appearance, indent),
145
+ [appearance, indent],
146
+ );
147
+
148
+ // ---- Reducer ------------------------------------------------------
149
+
150
+ const persisted = useMemo(
151
+ () => (persistKey ? loadTreeState(persistKey) : null),
152
+ [persistKey],
153
+ );
154
+
155
+ const [state, dispatch] = useReducer(reducer, undefined, () =>
156
+ createInitialState({
157
+ persisted,
158
+ initialExpandedIds,
159
+ initialSelectedIds,
160
+ persistSelection,
161
+ }),
162
+ );
163
+
164
+ const bumpCacheTick = useCallback(() => dispatch({ type: 'cache-tick' }), []);
165
+
166
+ // ---- Async children (cache + nodeById + refresh) ------------------
167
+
168
+ const {
169
+ nodeById,
170
+ refresh,
171
+ refreshAll,
172
+ collectFolderIds,
173
+ cache,
174
+ } = useAsyncChildren<T>({
175
+ data,
176
+ loadChildren,
177
+ expanded: state.expanded,
178
+ cacheTick: state.cacheTick,
179
+ bumpCacheTick,
180
+ });
181
+
182
+ // ---- Flat rows (depend on cache via cacheTick) --------------------
183
+
184
+ const flatRows = useMemo<FlatRow<T>[]>(
185
+ () =>
186
+ flattenTree<T>({
187
+ roots: data,
188
+ expandedIds: state.expanded,
189
+ cache,
190
+ filterNode,
191
+ }),
192
+ [data, state.expanded, state.cacheTick, cache, filterNode],
193
+ );
194
+
195
+ const matchingIds = useMemo(() => {
196
+ const set = new Set<TreeItemId>();
197
+ if (!enableSearch || state.query.trim() === '') return set;
198
+ const q = state.query.trim().toLowerCase();
199
+ for (const row of flatRows) {
200
+ if (getItemName(row.node).toLowerCase().includes(q)) {
201
+ set.add(row.node.id);
202
+ }
203
+ }
204
+ return set;
205
+ }, [enableSearch, state.query, flatRows, getItemName]);
206
+
207
+ // ---- Feature hooks ------------------------------------------------
208
+
209
+ const expansion = useExpansion({ dispatch, collectFolderIds });
210
+ const selection = useSelection<T>({
211
+ dispatch,
212
+ selectionMode,
213
+ flatRows,
214
+ selected: state.selected,
215
+ anchor: state.anchor,
216
+ focused: state.focused,
217
+ });
218
+ const rename = useRename<T>({
219
+ dispatch,
220
+ adapter,
221
+ enableInlineRename,
222
+ nodeById,
223
+ getItemName,
224
+ labels,
225
+ });
226
+ const clipboard = useClipboard<T>({
227
+ dispatch,
228
+ clipboard: state.clipboard,
229
+ adapter,
230
+ nodeById,
231
+ labels,
232
+ });
233
+ const dnd = useDnd<T>({
234
+ enabled: enableDnD,
235
+ adapter,
236
+ nodeById,
237
+ selected: state.selected,
238
+ labels,
239
+ canDrop,
240
+ });
241
+
242
+ // ---- Activation ---------------------------------------------------
243
+
244
+ const onActivateRef = useRef(onActivate);
245
+ onActivateRef.current = onActivate;
246
+ const activate = useCallback(
247
+ (node: TreeNode<T>, opts: TreeActivateOptions = { preview: false }) =>
248
+ onActivateRef.current?.(node, opts),
249
+ [],
250
+ );
251
+
252
+ const setQuery = useCallback(
253
+ (q: string) => dispatch({ type: 'set-query', q }),
254
+ [],
255
+ );
256
+
257
+ // ---- Persist + notify callbacks -----------------------------------
258
+
259
+ usePersistSync({
260
+ expanded: state.expanded,
261
+ selected: state.selected,
262
+ persistKey,
263
+ persistSelection,
264
+ onSelectionChange,
265
+ onExpansionChange,
266
+ });
267
+
268
+ // ---- Resolved context-menu resolver -------------------------------
269
+
270
+ const resolvedContextMenuActions = useResolvedMenu<T>({
271
+ adapter,
272
+ contextMenuActions,
273
+ defaultMenuItems,
274
+ labels,
275
+ selected: state.selected,
276
+ clipboard: state.clipboard,
277
+ nodeById,
278
+ getItemName,
279
+ enableInlineRename,
280
+ startRename: rename.startRename,
281
+ cutToClipboard: clipboard.cutToClipboard,
282
+ copyToClipboard: clipboard.copyToClipboard,
283
+ pasteFromClipboard: clipboard.pasteFromClipboard,
284
+ });
285
+
286
+ // Translate the declarative resolver into a slot-form
287
+ // `renderContextMenu` so <TreeRow> doesn't need to know about it.
288
+ // Explicit slot prop wins (escape-hatch for full custom menus).
289
+ const finalRenderContextMenu = useMemo<TreeContextMenuSlot<T> | undefined>(
290
+ () => {
291
+ if (renderContextMenu) return renderContextMenu;
292
+ const resolve = resolvedContextMenuActions;
293
+ if (!resolve) return undefined;
294
+ return (rowProps, trigger) => {
295
+ const items = resolve(rowProps);
296
+ const cleaned = items ? tidyMenuItems(items) : null;
297
+ if (!cleaned || cleaned.length === 0) return trigger;
298
+ return renderItemsAsContextMenu(rowProps, cleaned, trigger);
299
+ };
300
+ },
301
+ [renderContextMenu, resolvedContextMenuActions],
302
+ );
303
+
304
+ // ---- Final value --------------------------------------------------
305
+
306
+ const value = useMemo<TreeContextValue<T>>(
307
+ () => ({
308
+ // state
309
+ expanded: state.expanded,
310
+ selected: state.selected,
311
+ anchor: state.anchor,
312
+ focused: state.focused,
313
+ query: state.query,
314
+ renamingId: state.renaming,
315
+ inlineRenameEnabled: rename.enabled,
316
+ clipboard: state.clipboard,
317
+ flatRows,
318
+ matchingIds,
319
+
320
+ // expansion
321
+ ...expansion,
322
+
323
+ // selection (note: `select` is from selection hook; expansion exports no `select`)
324
+ ...selection,
325
+
326
+ setQuery,
327
+
328
+ // clipboard
329
+ ...clipboard,
330
+
331
+ // rename
332
+ startRename: rename.startRename,
333
+ cancelRename: rename.cancelRename,
334
+ commitRename: rename.commitRename,
335
+
336
+ // async
337
+ refresh,
338
+ refreshAll,
339
+ activate,
340
+
341
+ // config
342
+ labels,
343
+ appearance: resolvedAppearance,
344
+ indent: resolvedAppearance.indent,
345
+ selectionMode,
346
+ activationMode,
347
+ enableSearch,
348
+ showIndentGuides,
349
+ getItemName,
350
+
351
+ // slots
352
+ renderIcon,
353
+ renderLabel,
354
+ renderActions,
355
+ renderContextMenu: finalRenderContextMenu,
356
+
357
+ adapter,
358
+ resolvedContextMenuActions,
359
+ getNodeById: (id: TreeItemId) => nodeById.get(id),
360
+ dnd,
361
+ }),
362
+ [
363
+ state.expanded,
364
+ state.selected,
365
+ state.anchor,
366
+ state.focused,
367
+ state.query,
368
+ state.renaming,
369
+ state.clipboard,
370
+ rename.enabled,
371
+ rename.startRename,
372
+ rename.cancelRename,
373
+ rename.commitRename,
374
+ flatRows,
375
+ matchingIds,
376
+ expansion,
377
+ selection,
378
+ setQuery,
379
+ clipboard,
380
+ refresh,
381
+ refreshAll,
382
+ activate,
383
+ labels,
384
+ resolvedAppearance,
385
+ selectionMode,
386
+ activationMode,
387
+ enableSearch,
388
+ showIndentGuides,
389
+ getItemName,
390
+ renderIcon,
391
+ renderLabel,
392
+ renderActions,
393
+ finalRenderContextMenu,
394
+ adapter,
395
+ resolvedContextMenuActions,
396
+ nodeById,
397
+ dnd,
398
+ ],
399
+ );
400
+
401
+ return (
402
+ <TreeContext.Provider value={value as TreeContextValue<unknown>}>
403
+ {children}
404
+ </TreeContext.Provider>
405
+ );
406
+ }
407
+
408
+ // Re-export internal types referenced by hook consumers.
409
+ export type { ChildCache, ChildEntry } from '../data/childCache';
@@ -0,0 +1,139 @@
1
+ 'use client';
2
+
3
+ import type { ClipboardState } from '../data/clipboard';
4
+ import type { UseDndReturn } from './dnd';
5
+ import type { ResolvedAppearance } from '../data/appearance';
6
+ import type { ClickModifiers } from '../data/selection';
7
+ import type {
8
+ FlatRow,
9
+ TreeActivateOptions,
10
+ TreeActivationMode,
11
+ TreeAdapter,
12
+ TreeContextMenuItem,
13
+ TreeContextMenuSlot,
14
+ TreeItemId,
15
+ TreeLabels,
16
+ TreeMovePosition,
17
+ TreeNode,
18
+ TreeRowRenderProps,
19
+ TreeRowSlot,
20
+ TreeSelectionMode,
21
+ } from '../types';
22
+
23
+ export interface TreeContextValue<T> {
24
+ // State
25
+ expanded: ReadonlySet<TreeItemId>;
26
+ selected: ReadonlySet<TreeItemId>;
27
+ /** Anchor for shift-range. `null` when nothing has been clicked yet. */
28
+ anchor: TreeItemId | null;
29
+ focused: TreeItemId | null;
30
+ query: string;
31
+ /** Id of the row currently in inline-rename mode (or `null`). */
32
+ renamingId: TreeItemId | null;
33
+ /** Is inline rename allowed by the host? */
34
+ inlineRenameEnabled: boolean;
35
+
36
+ /** Tree-local clipboard (cut / copy) — P5. `null` when empty. */
37
+ clipboard: ClipboardState;
38
+
39
+ // Flattened render rows (visible items only)
40
+ flatRows: FlatRow<T>[];
41
+ /** Search-matching node ids (subset of all flatRows). */
42
+ matchingIds: ReadonlySet<TreeItemId>;
43
+
44
+ // Imperative actions
45
+ expand: (id: TreeItemId) => void;
46
+ collapse: (id: TreeItemId) => void;
47
+ toggle: (id: TreeItemId) => void;
48
+ expandAll: () => void;
49
+ collapseAll: () => void;
50
+ select: (id: TreeItemId) => void;
51
+ setSelectedIds: (ids: TreeItemId[]) => void;
52
+ clearSelection: () => void;
53
+ /**
54
+ * Finder/Explorer-style click selection. Reads modifier keys from the
55
+ * passed event and decides between replace / toggle / range / union.
56
+ */
57
+ clickSelect: (id: TreeItemId, mods: ClickModifiers) => void;
58
+ /**
59
+ * Move-focus with optional range-extend (shift+arrows).
60
+ */
61
+ moveSelect: (id: TreeItemId, opts: { extend: boolean }) => void;
62
+ /** Select every currently visible row (mod+a). */
63
+ selectAll: () => void;
64
+ setFocus: (id: TreeItemId | null) => void;
65
+ setQuery: (q: string) => void;
66
+
67
+ /** Put the given ids on Tree's clipboard as `cut`. */
68
+ cutToClipboard: (ids: TreeItemId[]) => void;
69
+ /** Put the given ids on Tree's clipboard as `copy`. */
70
+ copyToClipboard: (ids: TreeItemId[]) => void;
71
+ /**
72
+ * Apply clipboard onto `target` (a row, or `null` for the root). Cut →
73
+ * `adapter.move`; copy → `adapter.copy`. After a successful cut+paste
74
+ * the clipboard is cleared. No-op when clipboard is empty or the
75
+ * matching adapter method is undefined.
76
+ */
77
+ pasteFromClipboard: (
78
+ target: TreeNode<T> | null,
79
+ position?: TreeMovePosition,
80
+ ) => Promise<void>;
81
+ /** Clear the clipboard without pasting. */
82
+ clearClipboard: () => void;
83
+
84
+ /** Begin inline rename on the given row. */
85
+ startRename: (id: TreeItemId) => void;
86
+ /** Cancel inline rename without committing. */
87
+ cancelRename: () => void;
88
+ /**
89
+ * Commit inline rename. Tree calls `adapter.rename` and then clears
90
+ * the renaming state. On validation failure surfaces an error via
91
+ * `window.dialog.alert` and keeps the input open.
92
+ */
93
+ commitRename: (id: TreeItemId, nextName: string) => Promise<boolean>;
94
+
95
+ refresh: (id: TreeItemId) => Promise<void>;
96
+ refreshAll: () => Promise<void>;
97
+ activate: (node: TreeNode<T>, opts?: TreeActivateOptions) => void;
98
+
99
+ // Config / slots
100
+ labels: TreeLabels;
101
+ /** Resolved cosmetic config — never null. */
102
+ appearance: ResolvedAppearance;
103
+ /** Convenience alias for `appearance.indent`. */
104
+ indent: number;
105
+ selectionMode: TreeSelectionMode;
106
+ activationMode: TreeActivationMode;
107
+ enableSearch: boolean;
108
+ showIndentGuides: boolean;
109
+ getItemName: (node: TreeNode<T>) => string;
110
+
111
+ renderIcon?: TreeRowSlot<T>;
112
+ renderLabel?: TreeRowSlot<T>;
113
+ renderActions?: TreeRowSlot<T>;
114
+ renderContextMenu?: TreeContextMenuSlot<T>;
115
+
116
+ /** CRUD adapter (P2). May be undefined — Tree still renders normally. */
117
+ adapter?: TreeAdapter<T>;
118
+ /**
119
+ * Final, merged declarative menu resolver. Combines built-in adapter
120
+ * actions (filtered by `defaultMenuItems`) with the consumer's
121
+ * `contextMenuActions` resolver, and injects the current
122
+ * `selectedNodes` before delegating.
123
+ */
124
+ resolvedContextMenuActions?: (
125
+ row: TreeRowRenderProps<T>,
126
+ ) => TreeContextMenuItem<T>[] | null | undefined;
127
+ /**
128
+ * Imperative lookup for any node currently known to Tree (root +
129
+ * cached async children).
130
+ */
131
+ getNodeById: (id: TreeItemId) => TreeNode<T> | undefined;
132
+
133
+ /**
134
+ * Drag-and-drop state and handlers (P6). `dnd.active` is `false`
135
+ * when the host didn't enable DnD or `adapter.move` is missing — in
136
+ * that case `TreeRow` skips all drag setup.
137
+ */
138
+ dnd: UseDndReturn<T>;
139
+ }
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import type { ChildCache } from '../../data/childCache';
4
+ import type { TreeItemId, TreeNode } from '../../types';
5
+
6
+ /**
7
+ * Walk `roots` + cached async children and push every *folder* id into
8
+ * `out`. Used by `expandAll`.
9
+ *
10
+ * Leaves are intentionally not collected — expanding a leaf is a no-op.
11
+ */
12
+ export function collectAllFolderIds<T>(
13
+ roots: TreeNode<T>[],
14
+ cache: ChildCache<T>,
15
+ out: TreeItemId[],
16
+ ): void {
17
+ for (const node of roots) {
18
+ if (Array.isArray(node.children)) {
19
+ out.push(node.id);
20
+ collectAllFolderIds(node.children, cache, out);
21
+ } else if (node.isFolder) {
22
+ out.push(node.id);
23
+ const entry = cache.get(node.id);
24
+ if (entry?.children) collectAllFolderIds(entry.children, cache, out);
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,8 @@
1
+ 'use client';
2
+
3
+ export { useAsyncChildren } from './use-async-children';
4
+ export type {
5
+ UseAsyncChildrenOptions,
6
+ UseAsyncChildrenReturn,
7
+ } from './use-async-children';
8
+ export { collectAllFolderIds } from './collect-ids';