@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,196 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useRef } from 'react';
4
+ import { useHotkey } from '@djangocfg/ui-core/hooks';
5
+
6
+ import { edgeRowId, nextRowId, prevRowId } from './arrow-nav';
7
+ import { resolveLeftArrow, resolveRightArrow } from './expand-collapse';
8
+ import { resolveActivate } from './activation';
9
+ import type {
10
+ CurrentRow,
11
+ UseTreeKeyboardOptions,
12
+ UseTreeKeyboardReturn,
13
+ } from './types';
14
+
15
+ /**
16
+ * Standard tree keyboard navigation, scoped to the container ref.
17
+ *
18
+ * - ↑ / ↓ : prev / next visible row (Shift extends range)
19
+ * - Home / End : first / last visible row (Shift extends range)
20
+ * - → / ← : expand-or-jump-to-child / collapse-or-jump-to-parent
21
+ * - Enter / Space : activate (folder → toggle, leaf → onActivate)
22
+ * - Esc : clear selection
23
+ * - Cmd/Ctrl + A : select all (multi-select only)
24
+ *
25
+ * Pure decision-making lives in the sibling helpers (`arrow-nav.ts`,
26
+ * `expand-collapse.ts`, `activation.ts`) so it's unit-testable without
27
+ * a DOM. This file only wires up `useHotkey` bindings and dispatches
28
+ * the helper outcomes back to the consumer's callbacks.
29
+ */
30
+ export function useTreeKeyboard<T>({
31
+ rows,
32
+ focusedId,
33
+ enabled = true,
34
+ multiSelect = false,
35
+ onFocus,
36
+ onSelect,
37
+ onActivate,
38
+ onExpand,
39
+ onCollapse,
40
+ onClearSelection,
41
+ onSelectAll,
42
+ }: UseTreeKeyboardOptions<T>): UseTreeKeyboardReturn {
43
+ // Keep latest values in refs so the callbacks below stay stable across
44
+ // renders — react-hotkeys-hook re-binds on dep change otherwise.
45
+ const rowsRef = useRef(rows);
46
+ const focusedIdRef = useRef(focusedId);
47
+ rowsRef.current = rows;
48
+ focusedIdRef.current = focusedId;
49
+
50
+ const getCurrent = (): CurrentRow<T> => {
51
+ const r = rowsRef.current;
52
+ const id = focusedIdRef.current;
53
+ const idx = id ? r.findIndex((x) => x.node.id === id) : -1;
54
+ return { rows: r, idx, current: (idx >= 0 ? r[idx] : null) ?? null };
55
+ };
56
+
57
+ // Down / Shift+Down. Plain moves focus, shift extends selection range.
58
+ const refDown = useHotkey(
59
+ ['down', 'shift+down'],
60
+ (e) => {
61
+ const { rows: r, idx } = getCurrent();
62
+ const id = nextRowId(r, idx);
63
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
64
+ },
65
+ { enabled, preventDefault: true, description: 'Next row (Shift extends)' },
66
+ );
67
+
68
+ const refUp = useHotkey(
69
+ ['up', 'shift+up'],
70
+ (e) => {
71
+ const { rows: r, idx } = getCurrent();
72
+ const id = prevRowId(r, idx);
73
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
74
+ },
75
+ { enabled, preventDefault: true, description: 'Previous row (Shift extends)' },
76
+ );
77
+
78
+ const refHome = useHotkey(
79
+ ['home', 'shift+home'],
80
+ (e) => {
81
+ const id = edgeRowId(rowsRef.current, 'first');
82
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
83
+ },
84
+ { enabled, preventDefault: true, description: 'First row (Shift extends)' },
85
+ );
86
+
87
+ const refEnd = useHotkey(
88
+ ['end', 'shift+end'],
89
+ (e) => {
90
+ const id = edgeRowId(rowsRef.current, 'last');
91
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
92
+ },
93
+ { enabled, preventDefault: true, description: 'Last row (Shift extends)' },
94
+ );
95
+
96
+ const refSelectAll = useHotkey(
97
+ 'mod+a',
98
+ () => {
99
+ if (!multiSelect) return;
100
+ onSelectAll?.();
101
+ },
102
+ {
103
+ enabled: enabled && multiSelect,
104
+ preventDefault: true,
105
+ description: 'Select all visible rows',
106
+ },
107
+ );
108
+
109
+ const refRight = useHotkey(
110
+ 'right',
111
+ () => {
112
+ const { rows: r, idx, current } = getCurrent();
113
+ const out = resolveRightArrow(current, r, idx);
114
+ switch (out.kind) {
115
+ case 'expand':
116
+ onExpand(out.id);
117
+ return;
118
+ case 'focus':
119
+ onFocus(out.id, { extend: false });
120
+ return;
121
+ case 'noop':
122
+ return;
123
+ }
124
+ },
125
+ { enabled, preventDefault: true, description: 'Expand / first child' },
126
+ );
127
+
128
+ const refLeft = useHotkey(
129
+ 'left',
130
+ () => {
131
+ const { current } = getCurrent();
132
+ const out = resolveLeftArrow(current);
133
+ switch (out.kind) {
134
+ case 'collapse':
135
+ onCollapse(out.id);
136
+ return;
137
+ case 'focus':
138
+ onFocus(out.id, { extend: false });
139
+ return;
140
+ case 'noop':
141
+ return;
142
+ }
143
+ },
144
+ { enabled, preventDefault: true, description: 'Collapse / parent' },
145
+ );
146
+
147
+ const refActivate = useHotkey(
148
+ ['enter', 'space'],
149
+ () => {
150
+ const { current } = getCurrent();
151
+ const out = resolveActivate(current);
152
+ if (out.kind === 'noop') return;
153
+ onSelect(out.kind === 'activate-leaf' ? out.id : out.id);
154
+ if (out.kind === 'toggle-folder') {
155
+ if (out.willExpand) onExpand(out.id);
156
+ else onCollapse(out.id);
157
+ } else {
158
+ onActivate(out.id);
159
+ }
160
+ },
161
+ { enabled, preventDefault: true, description: 'Activate / toggle' },
162
+ );
163
+
164
+ const refEscape = useHotkey(
165
+ 'escape',
166
+ () => onClearSelection(),
167
+ { enabled, preventDefault: true, description: 'Clear selection' },
168
+ );
169
+
170
+ const ref = useCallback(
171
+ (instance: HTMLElement | null) => {
172
+ refDown(instance);
173
+ refUp(instance);
174
+ refHome(instance);
175
+ refEnd(instance);
176
+ refRight(instance);
177
+ refLeft(instance);
178
+ refActivate(instance);
179
+ refEscape(instance);
180
+ refSelectAll(instance);
181
+ },
182
+ [
183
+ refDown,
184
+ refUp,
185
+ refHome,
186
+ refEnd,
187
+ refRight,
188
+ refLeft,
189
+ refActivate,
190
+ refEscape,
191
+ refSelectAll,
192
+ ],
193
+ );
194
+
195
+ return { ref };
196
+ }
@@ -0,0 +1,5 @@
1
+ 'use client';
2
+
3
+ export { useTreeTypeAhead } from './use-tree-type-ahead';
4
+ export type { UseTreeTypeAheadOptions } from './use-tree-type-ahead';
5
+ export { findRowByPrefix, isResetKey, isTypingTarget } from './match-prefix';
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import type { FlatRow, TreeNode } from '../../types';
4
+
5
+ /** Find the first row whose name starts with `prefix` (case-insensitive). */
6
+ export function findRowByPrefix<T>(
7
+ rows: readonly FlatRow<T>[],
8
+ getName: (node: TreeNode<T>) => string,
9
+ prefix: string,
10
+ ): FlatRow<T> | undefined {
11
+ if (prefix.length === 0) return undefined;
12
+ return rows.find((row) => getName(row.node).toLowerCase().startsWith(prefix));
13
+ }
14
+
15
+ /**
16
+ * Should this key terminate type-ahead instead of extending the buffer?
17
+ * Navigation, Enter, Tab, Escape — all reset; everything else continues.
18
+ */
19
+ export function isResetKey(key: string): boolean {
20
+ return (
21
+ key === 'Escape' ||
22
+ key === 'Enter' ||
23
+ key === 'Tab' ||
24
+ key.startsWith('Arrow') ||
25
+ key === 'Home' ||
26
+ key === 'End' ||
27
+ key === 'PageUp' ||
28
+ key === 'PageDown'
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Skip keystrokes that originate inside a text field so type-ahead
34
+ * doesn't yank focus while the user is filling in a form.
35
+ */
36
+ export function isTypingTarget(target: EventTarget | null): boolean {
37
+ const el = target as HTMLElement | null;
38
+ if (!el) return false;
39
+ const tag = el.tagName;
40
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
41
+ return el.isContentEditable === true;
42
+ }
@@ -0,0 +1,89 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useRef } from 'react';
4
+
5
+ import type { FlatRow, TreeNode } from '../../types';
6
+ import { findRowByPrefix, isResetKey, isTypingTarget } from './match-prefix';
7
+
8
+ const FLUSH_MS = 600;
9
+
10
+ export interface UseTreeTypeAheadOptions<T> {
11
+ /** Visible flat rows in render order. */
12
+ rows: FlatRow<T>[];
13
+ /** How to read the displayed name of a node (matched case-insensitively). */
14
+ getItemName: (node: TreeNode<T>) => string;
15
+ /** Element receiving keydown events. */
16
+ containerRef: React.RefObject<HTMLElement | null>;
17
+ /** Called with the matched node id so the consumer can focus / scroll. */
18
+ onMatch: (id: string) => void;
19
+ /** Disable without removing the call site. */
20
+ enabled?: boolean;
21
+ }
22
+
23
+ /**
24
+ * Type-ahead jump (Finder / VSCode style).
25
+ *
26
+ * Builds a rolling buffer of printable keys (within ~600 ms of each other)
27
+ * and notifies the consumer of the first row whose name starts with that
28
+ * prefix. Resets on Esc / Enter / navigation keys / timeout.
29
+ */
30
+ export function useTreeTypeAhead<T>({
31
+ rows,
32
+ getItemName,
33
+ containerRef,
34
+ onMatch,
35
+ enabled = true,
36
+ }: UseTreeTypeAheadOptions<T>) {
37
+ const bufferRef = useRef('');
38
+ const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
39
+ const rowsRef = useRef(rows);
40
+ const getNameRef = useRef(getItemName);
41
+ const onMatchRef = useRef(onMatch);
42
+ rowsRef.current = rows;
43
+ getNameRef.current = getItemName;
44
+ onMatchRef.current = onMatch;
45
+
46
+ useEffect(() => {
47
+ if (!enabled) return;
48
+ const target = containerRef.current;
49
+ if (!target) return;
50
+
51
+ const reset = () => {
52
+ bufferRef.current = '';
53
+ if (timerRef.current) {
54
+ clearTimeout(timerRef.current);
55
+ timerRef.current = null;
56
+ }
57
+ };
58
+
59
+ const handler = (e: KeyboardEvent) => {
60
+ if (isTypingTarget(e.target)) return;
61
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
62
+ if (isResetKey(e.key)) {
63
+ reset();
64
+ return;
65
+ }
66
+ if (e.key.length !== 1) return;
67
+
68
+ bufferRef.current += e.key.toLowerCase();
69
+ if (timerRef.current) clearTimeout(timerRef.current);
70
+ timerRef.current = setTimeout(reset, FLUSH_MS);
71
+
72
+ const hit = findRowByPrefix(
73
+ rowsRef.current,
74
+ getNameRef.current,
75
+ bufferRef.current,
76
+ );
77
+ if (hit) {
78
+ e.preventDefault();
79
+ onMatchRef.current(hit.node.id);
80
+ }
81
+ };
82
+
83
+ target.addEventListener('keydown', handler);
84
+ return () => {
85
+ target.removeEventListener('keydown', handler);
86
+ reset();
87
+ };
88
+ }, [containerRef, enabled]);
89
+ }
@@ -0,0 +1,135 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * Decomposed shadcn-style tree component.
5
+ *
6
+ * Two usage patterns:
7
+ *
8
+ * 1) High-level (most cases):
9
+ * <TreeRoot data={...} getItemName={...} onSelectionChange={...} />
10
+ *
11
+ * 2) Composition (full control):
12
+ * <TreeProvider data={...} getItemName={...}>
13
+ * <TreeSearchInput />
14
+ * <TreeContent />
15
+ * </TreeProvider>
16
+ */
17
+
18
+ export { TreeRoot, TreeRoot as Tree, default as default } from './TreeRoot';
19
+ export { FinderTree } from './FinderTree';
20
+
21
+ export {
22
+ TreeProvider,
23
+ useTreeContext,
24
+ useTreeLabels,
25
+ useTreeRows,
26
+ useTreeSelection,
27
+ useTreeExpansion,
28
+ useTreeFocus,
29
+ useTreeSearch,
30
+ useTreeRename,
31
+ useTreeClipboard,
32
+ useTreeDnd,
33
+ useTreeActions,
34
+ } from './context';
35
+ export type { TreeProviderProps, TreeContextValue } from './context';
36
+
37
+ export { useTreeTypeAhead, useTreeKeyboard, useTreeFinderHotkeys } from './hooks';
38
+ export type {
39
+ UseTreeTypeAheadOptions,
40
+ UseTreeKeyboardOptions,
41
+ UseTreeFinderHotkeysOptions,
42
+ } from './hooks';
43
+
44
+ export {
45
+ TreeContent,
46
+ TreeRow,
47
+ TreeChevron,
48
+ TreeIcon,
49
+ TreeLabel,
50
+ TreeSearchInput,
51
+ TreeEmpty,
52
+ TreeSkeleton,
53
+ TreeError,
54
+ TreeIndentGuides,
55
+ TreeRenameInput,
56
+ TreeDropIndicator,
57
+ TreeEmptyArea,
58
+ } from './components';
59
+ export type {
60
+ TreeContentProps,
61
+ TreeRowProps,
62
+ TreeChevronProps,
63
+ TreeIconProps,
64
+ TreeLabelProps,
65
+ TreeSearchInputProps,
66
+ TreeEmptyProps,
67
+ TreeSkeletonProps,
68
+ TreeErrorProps,
69
+ TreeIndentGuidesProps,
70
+ TreeRenameInputProps,
71
+ TreeDropIndicatorProps,
72
+ TreeEmptyAreaProps,
73
+ } from './components';
74
+
75
+ export {
76
+ createChildCache,
77
+ resolveChildren,
78
+ flattenTree,
79
+ loadTreeState,
80
+ saveTreeState,
81
+ clearTreeState,
82
+ createDemoTree,
83
+ DEFAULT_TREE_APPEARANCE,
84
+ resolveAppearance,
85
+ appearanceToStyle,
86
+ splitFileName,
87
+ autoSelectRange,
88
+ resolveDropZone,
89
+ defaultCanDrop,
90
+ TREE_DND_MIME,
91
+ } from './data';
92
+ export type {
93
+ ChildCache,
94
+ ChildEntry,
95
+ ChildEntryStatus,
96
+ FlattenInput,
97
+ PersistedTreeState,
98
+ DemoNode,
99
+ TreeAppearance,
100
+ ResolvedAppearance,
101
+ TreeDensity,
102
+ TreeAccentIntensity,
103
+ TreeRadius,
104
+ TreeVariant,
105
+ TreeRowSizing,
106
+ } from './data';
107
+
108
+ export { DEFAULT_TREE_LABELS } from './types';
109
+ export type {
110
+ TreeActionsHandle,
111
+ TreeRootProps,
112
+ TreeNode,
113
+ TreeItemId,
114
+ TreeLabels,
115
+ TreeSelectionMode,
116
+ TreeActivationMode,
117
+ TreeActivateOptions,
118
+ TreeRowRenderProps,
119
+ TreeRowSlot,
120
+ TreeContextMenuSlot,
121
+ TreeContextMenuAction,
122
+ TreeContextMenuItem,
123
+ TreeContextMenuActionsContext,
124
+ TreeContextMenuActionsResolver,
125
+ TreeLoadChildren,
126
+ TreeAdapter,
127
+ TreeBuiltinAction,
128
+ TreeMovePosition,
129
+ FlatRow,
130
+ } from './types';
131
+
132
+ // The dynamic-import wrapper. Exported here because the subpath resolves to
133
+ // this barrel: in the retired ui-tools `lazy.tsx` was reachable from no declared export,
134
+ // so `LazyTree` was dead code for every tool but `JsonTree`.
135
+ export { LazyTree } from './lazy';
@@ -0,0 +1,14 @@
1
+ 'use client';
2
+
3
+ import { createLazyComponent, LoadingFallback } from '@djangocfg/widget-kit/lazy';
4
+ import type { TreeRootProps } from './types';
5
+
6
+ export const LazyTree = createLazyComponent<TreeRootProps<unknown>>(
7
+ () => import('./TreeRoot'),
8
+ {
9
+ displayName: 'LazyTree',
10
+ fallback: <LoadingFallback minHeight={200} text="Loading tree…" />,
11
+ },
12
+ );
13
+
14
+ export type { TreeRootProps };
@@ -0,0 +1,30 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * How a node becomes "activated" (i.e. opened) on pointer interaction.
5
+ *
6
+ * - `'single-click'` (default): single click activates a leaf immediately;
7
+ * double-click also activates. Folders always toggle on single click.
8
+ * - `'double-click'`: single click only selects + focuses; double-click is
9
+ * required to activate. Mirrors classic file-manager behaviour.
10
+ * - `'single-click-preview'`: VSCode Explorer / Cursor behaviour. Single
11
+ * click activates with `{ preview: true }` (consumer renders a preview
12
+ * tab); double-click activates with `{ preview: false }` (pinned tab).
13
+ *
14
+ * Folders ignore this setting — they always toggle on single click and
15
+ * never call `onActivate`.
16
+ */
17
+ export type TreeActivationMode =
18
+ | 'single-click'
19
+ | 'double-click'
20
+ | 'single-click-preview';
21
+
22
+ export interface TreeActivateOptions {
23
+ /**
24
+ * `true` when the activation came from a single click in
25
+ * `'single-click-preview'` mode. `false` for double-click and for
26
+ * non-preview modes. Consumers typically map this to a
27
+ * preview-tab vs pinned-tab distinction.
28
+ */
29
+ preview: boolean;
30
+ }
@@ -0,0 +1,70 @@
1
+ 'use client';
2
+
3
+ import type { TreeNode } from './node';
4
+
5
+ /**
6
+ * Position of a drop / move relative to the target row. `inside` means
7
+ * "drop into this folder"; `before`/`after` are sibling reorder hints.
8
+ */
9
+ export type TreeMovePosition = 'before' | 'inside' | 'after';
10
+
11
+ /**
12
+ * CRUD adapter. Every method is optional — Tree only surfaces built-in
13
+ * menu items / hotkeys whose adapter method is defined. So an
14
+ * inspection-only tree just passes `{}` (or no adapter) and gets no
15
+ * destructive menu actions.
16
+ *
17
+ * Dialogs (`alert` / `confirm` / `prompt`) are taken from `window.dialog`
18
+ * exposed by `<DialogProvider />` in `@djangocfg/ui-core`. The host app
19
+ * is expected to mount that provider once at the layout level — Tree
20
+ * never re-implements its own dialogs.
21
+ */
22
+ export interface TreeAdapter<T = unknown> {
23
+ /** Delete the given nodes. Tree calls `window.dialog.confirm` first. */
24
+ remove?: (nodes: TreeNode<T>[]) => Promise<void>;
25
+ /** Inline rename — node + new name (already non-empty, post-validate). */
26
+ rename?: (node: TreeNode<T>, nextName: string) => Promise<void>;
27
+ /** Create a file under `parent` (null → root). Tree prompts for name. */
28
+ createFile?: (parent: TreeNode<T> | null, name: string) => Promise<void>;
29
+ /** Create a folder under `parent` (null → root). Tree prompts for name. */
30
+ createFolder?: (parent: TreeNode<T> | null, name: string) => Promise<void>;
31
+ /** Duplicate the given nodes in-place. */
32
+ duplicate?: (nodes: TreeNode<T>[]) => Promise<void>;
33
+ /** Move nodes (drag-and-drop, cut+paste). */
34
+ move?: (
35
+ nodes: TreeNode<T>[],
36
+ target: TreeNode<T> | null,
37
+ position: TreeMovePosition,
38
+ ) => Promise<void>;
39
+ /** Copy nodes (copy+paste, drop with modifier). */
40
+ copy?: (
41
+ nodes: TreeNode<T>[],
42
+ target: TreeNode<T> | null,
43
+ position: TreeMovePosition,
44
+ ) => Promise<void>;
45
+
46
+ /**
47
+ * Optional name validator. Return a non-empty string to surface as an
48
+ * error via `window.dialog.alert`. Return `null` to accept.
49
+ */
50
+ validateName?: (
51
+ name: string,
52
+ ctx: { node?: TreeNode<T>; parent?: TreeNode<T> | null },
53
+ ) => string | null;
54
+ }
55
+
56
+ /**
57
+ * Built-in action ids. Used by `defaultMenuItems` and the internal
58
+ * built-in action registry. Each id maps 1:1 to an adapter method
59
+ * (plus a few selection helpers).
60
+ */
61
+ export type TreeBuiltinAction =
62
+ | 'open'
63
+ | 'rename'
64
+ | 'duplicate'
65
+ | 'cut'
66
+ | 'copy'
67
+ | 'paste'
68
+ | 'delete'
69
+ | 'new-file'
70
+ | 'new-folder';
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ // Public surface — re-exports each types module. External code imports
4
+ // from `'../types'` (or `'@djangocfg/widget-data/tree'`) and never touches
5
+ // these files directly.
6
+
7
+ export type { TreeItemId, TreeNode, FlatRow } from './node';
8
+ export type { TreeSelectionMode } from './selection';
9
+ export type { TreeActivationMode, TreeActivateOptions } from './activation';
10
+ export type { TreeLoadChildren } from './loader';
11
+ export type { TreeLabels } from './labels';
12
+ export { DEFAULT_TREE_LABELS } from './labels';
13
+ export type {
14
+ TreeRowRenderProps,
15
+ TreeRowSlot,
16
+ TreeContextMenuSlot,
17
+ TreeContextMenuAction,
18
+ TreeContextMenuItem,
19
+ TreeContextMenuActionsContext,
20
+ TreeContextMenuActionsResolver,
21
+ } from './slots';
22
+ export type {
23
+ TreeMovePosition,
24
+ TreeAdapter,
25
+ TreeBuiltinAction,
26
+ } from './adapter';
27
+ export type { TreeRootProps, TreeActionsHandle } from './root-props';