@djangocfg/ui-tools 2.1.413 → 2.1.416
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/file-icon/index.d.cts +1 -1
- package/dist/file-icon/index.d.ts +1 -1
- package/dist/slots-ClRpIzoh.d.cts +88 -0
- package/dist/slots-ClRpIzoh.d.ts +88 -0
- package/dist/tree/index.cjs +1994 -276
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +717 -72
- package/dist/tree/index.d.ts +717 -72
- package/dist/tree/index.mjs +1984 -279
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +10 -6
- package/src/tools/chat/README.md +111 -1
- package/src/tools/chat/composer/Composer.tsx +138 -17
- package/src/tools/chat/composer/ComposerRichTextarea.tsx +25 -0
- package/src/tools/chat/composer/index.ts +22 -0
- package/src/tools/chat/composer/slash/README.md +187 -0
- package/src/tools/chat/composer/slash/SlashHighlightTextarea.tsx +144 -0
- package/src/tools/chat/composer/slash/SlashMenu.tsx +142 -0
- package/src/tools/chat/composer/slash/SlashToken.tsx +57 -0
- package/src/tools/chat/composer/slash/index.ts +44 -0
- package/src/tools/chat/composer/slash/labels.ts +19 -0
- package/src/tools/chat/composer/slash/state.ts +168 -0
- package/src/tools/chat/composer/slash/types.ts +64 -0
- package/src/tools/chat/composer/slash/useSlashCommands.ts +204 -0
- package/src/tools/chat/composer/types.ts +8 -0
- package/src/tools/chat/shell/SuggestedPrompts.tsx +194 -0
- package/src/tools/chat/shell/index.ts +6 -0
- package/src/tools/data/Listbox/lazy.tsx +1 -1
- package/src/tools/data/Masonry/lazy.tsx +1 -1
- package/src/tools/data/Timeline/lazy.tsx +1 -1
- package/src/tools/data/Tree/FinderTree.tsx +42 -0
- package/src/tools/data/Tree/README.md +337 -208
- package/src/tools/data/Tree/TreeDndProvider.tsx +137 -0
- package/src/tools/data/Tree/TreeRoot.tsx +170 -55
- package/src/tools/data/Tree/__tests__/dnd.test.ts +160 -0
- package/src/tools/data/Tree/__tests__/keyboard.test.ts +137 -0
- package/src/tools/data/Tree/__tests__/renameUtils.test.ts +52 -0
- package/src/tools/data/Tree/__tests__/selection.test.ts +227 -0
- package/src/tools/data/Tree/components/TreeDropIndicator.tsx +65 -0
- package/src/tools/data/Tree/components/TreeEmptyArea.tsx +160 -0
- package/src/tools/data/Tree/components/TreeRenameInput.tsx +114 -0
- package/src/tools/data/Tree/components/TreeRow.tsx +92 -8
- package/src/tools/data/Tree/components/index.ts +6 -0
- package/src/tools/data/Tree/context/TreeContext.tsx +204 -363
- package/src/tools/data/Tree/context/TreeContextValue.ts +139 -0
- package/src/tools/data/Tree/context/async-children/collect-ids.ts +27 -0
- package/src/tools/data/Tree/context/async-children/index.ts +8 -0
- package/src/tools/data/Tree/context/async-children/use-async-children.ts +157 -0
- package/src/tools/data/Tree/context/clipboard/index.ts +4 -0
- package/src/tools/data/Tree/context/clipboard/use-clipboard.ts +115 -0
- package/src/tools/data/Tree/context/dnd/index.ts +8 -0
- package/src/tools/data/Tree/context/dnd/use-dnd.ts +194 -0
- package/src/tools/data/Tree/context/expansion/index.ts +4 -0
- package/src/tools/data/Tree/context/expansion/use-expansion.ts +55 -0
- package/src/tools/data/Tree/context/hooks.ts +68 -1
- package/src/tools/data/Tree/context/index.ts +3 -0
- package/src/tools/data/Tree/context/menu/builtin-actions.ts +357 -0
- package/src/tools/data/Tree/context/menu/index.ts +10 -0
- package/src/tools/data/Tree/context/menu/use-resolved-menu.ts +127 -0
- package/src/tools/data/Tree/context/persist/index.ts +4 -0
- package/src/tools/data/Tree/context/persist/use-persist-sync.ts +74 -0
- package/src/tools/data/Tree/context/rename/index.ts +4 -0
- package/src/tools/data/Tree/context/rename/use-rename.ts +113 -0
- package/src/tools/data/Tree/context/selection/index.ts +4 -0
- package/src/tools/data/Tree/context/selection/use-selection.ts +146 -0
- package/src/tools/data/Tree/context/state/index.ts +6 -0
- package/src/tools/data/Tree/context/state/initial.ts +41 -0
- package/src/tools/data/Tree/context/state/reducer.ts +76 -0
- package/src/tools/data/Tree/context/state/types.ts +46 -0
- package/src/tools/data/Tree/data/clipboard.ts +33 -0
- package/src/tools/data/Tree/data/dnd.ts +123 -0
- package/src/tools/data/Tree/data/finderShortcuts.ts +67 -0
- package/src/tools/data/Tree/data/index.ts +19 -0
- package/src/tools/data/Tree/data/renameUtils.ts +51 -0
- package/src/tools/data/Tree/data/selection.ts +157 -0
- package/src/tools/data/Tree/hooks/finder-hotkeys/build-ctx.ts +48 -0
- package/src/tools/data/Tree/hooks/finder-hotkeys/index.ts +8 -0
- package/src/tools/data/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts +166 -0
- package/src/tools/data/Tree/hooks/index.ts +23 -4
- package/src/tools/data/Tree/hooks/keyboard/activation.ts +27 -0
- package/src/tools/data/Tree/hooks/keyboard/arrow-nav.ts +26 -0
- package/src/tools/data/Tree/hooks/keyboard/expand-collapse.ts +54 -0
- package/src/tools/data/Tree/hooks/keyboard/index.ts +10 -0
- package/src/tools/data/Tree/hooks/keyboard/types.ts +39 -0
- package/src/tools/data/Tree/hooks/keyboard/use-tree-keyboard.ts +196 -0
- package/src/tools/data/Tree/hooks/type-ahead/index.ts +5 -0
- package/src/tools/data/Tree/hooks/type-ahead/match-prefix.ts +42 -0
- package/src/tools/data/Tree/hooks/{useTreeTypeAhead.ts → type-ahead/use-tree-type-ahead.ts} +8 -19
- package/src/tools/data/Tree/index.tsx +25 -2
- package/src/tools/data/Tree/types/activation.ts +30 -0
- package/src/tools/data/Tree/types/adapter.ts +70 -0
- package/src/tools/data/Tree/types/index.ts +27 -0
- package/src/tools/data/Tree/types/labels.ts +97 -0
- package/src/tools/data/Tree/types/loader.ts +9 -0
- package/src/tools/data/Tree/types/node.ts +38 -0
- package/src/tools/data/Tree/types/root-props.ts +142 -0
- package/src/tools/data/Tree/types/selection.ts +3 -0
- package/src/tools/data/Tree/types/slots.ts +64 -0
- package/src/tools/dev/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +1 -1
- package/src/tools/dev/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +1 -1
- package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +85 -0
- package/src/tools/forms/MarkdownEditor/index.ts +1 -0
- package/src/tools/forms/MarkdownEditor/lazy.tsx +6 -0
- package/src/tools/forms/MarkdownEditor/slash/SlashCommandNode.ts +162 -0
- package/src/tools/forms/MarkdownEditor/slash/index.ts +4 -0
- package/src/tools/forms/MarkdownEditor/slash/syncSlashNode.ts +97 -0
- package/src/tools/forms/MarkdownEditor/slash/types.ts +13 -0
- package/src/tools/forms/MarkdownEditor/styles.css +18 -0
- package/src/tools/index.ts +2 -2
- package/dist/types-j2vhn4Kv.d.cts +0 -241
- package/dist/types-j2vhn4Kv.d.ts +0 -241
- package/src/tools/data/Tree/hooks/useTreeKeyboard.ts +0 -171
- package/src/tools/data/Tree/types.ts +0 -217
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# Tree
|
|
2
2
|
|
|
3
|
-
A decomposed, shadcn-styled tree for `@djangocfg/ui-tools`. Pure React engine, zero external tree libraries. Generic over `T`, slot-driven, async-friendly
|
|
3
|
+
A decomposed, shadcn-styled tree for `@djangocfg/ui-tools`. Pure React engine, zero external tree libraries. Generic over `T`, slot-driven, async-friendly, **with built-in Finder/Explorer CRUD UX** when you provide an `adapter`.
|
|
4
|
+
|
|
5
|
+
Ships two entry points:
|
|
6
|
+
|
|
7
|
+
- **`<TreeRoot>`** — every prop is opt-in. Use for read-only / display trees.
|
|
8
|
+
- **`<FinderTree>`** — opinionated Finder/Explorer preset: multi-select, double-click activation, inline rename, indent guides, Finder hotkeys, cozy density. Override anything by passing the same prop.
|
|
4
9
|
|
|
5
10
|
## Why this exists
|
|
6
11
|
|
|
@@ -9,39 +14,74 @@ We tried popular headless tree engines first. They all leak React-integration bu
|
|
|
9
14
|
## Philosophy
|
|
10
15
|
|
|
11
16
|
1. **No engine.** State lives in plain React. Every interaction goes through React's commit cycle.
|
|
12
|
-
2. **Generic over `T`.** Tree nodes carry your domain payload (`File`, `Project`, `JsonNode`, …). The component never assumes filesystem semantics.
|
|
17
|
+
2. **Generic over `T`.** Tree nodes carry your domain payload (`File`, `Project`, `JsonNode`, …). The component never assumes filesystem semantics — but it provides a Finder-shaped affordance layer (`TreeAdapter`) for those that want one.
|
|
13
18
|
3. **Sync or async.** Pass inline `children: TreeNode<T>[]` for sync data, or omit them and provide `loadChildren` for lazy loading. The async cache de-duplicates concurrent fetches.
|
|
14
19
|
4. **Slots over props.** New visual needs add a slot, not a flag: `renderRow` / `renderIcon` / `renderLabel` / `renderActions` / `renderContextMenu`.
|
|
15
|
-
5. **
|
|
16
|
-
6. **
|
|
20
|
+
5. **CRUD = adapter, not props.** The host app describes how to delete / rename / move / etc. through a single `TreeAdapter<T>` object. Tree owns the UX (dialogs, hotkeys, menus) and calls back into the adapter. No `onDelete`/`onRename`/... prop sprawl.
|
|
21
|
+
6. **Dialogs come from `<DialogProvider>`.** Tree never re-implements its own dialogs. Built-in CRUD flows resolve `window.dialog` (installed by `@djangocfg/ui-core/lib/dialog-service`). If the host app hasn't mounted it, CRUD flows silently no-op with a dev-mode warning — Tree itself still renders.
|
|
22
|
+
7. **CSS-variable theming.** Density, sizes, gaps, indent — all exposed as `--tree-*` variables on the root. Override in any consumer without re-implementing components.
|
|
17
23
|
|
|
18
24
|
## Layered architecture
|
|
19
25
|
|
|
20
26
|
```
|
|
21
|
-
types
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
types/ public types — generic over T, no `any`, folders-per-concept
|
|
28
|
+
node.ts TreeItemId / TreeNode / FlatRow
|
|
29
|
+
selection.ts TreeSelectionMode
|
|
30
|
+
activation.ts TreeActivationMode / TreeActivateOptions
|
|
31
|
+
loader.ts TreeLoadChildren
|
|
32
|
+
labels.ts TreeLabels + DEFAULT_TREE_LABELS (CRUD copy too)
|
|
33
|
+
slots.ts TreeRowSlot / TreeContextMenuSlot / TreeContextMenuItem / …
|
|
34
|
+
adapter.ts TreeAdapter / TreeBuiltinAction / TreeMovePosition
|
|
35
|
+
root-props.ts TreeRootProps
|
|
36
|
+
|
|
37
|
+
TreeRoot.tsx high-level entry — Provider + shell + content
|
|
38
|
+
TreeDndProvider.tsx thin <DndContext> wrapper (no-op when DnD off)
|
|
39
|
+
FinderTree.tsx Finder/Explorer preset over TreeRoot
|
|
40
|
+
lazy.tsx LazyTree via createLazyComponent
|
|
41
|
+
|
|
42
|
+
data/ pure helpers, zero React
|
|
43
|
+
appearance.ts density / accent / radius / sizes → CSS vars + classes
|
|
44
|
+
childCache.ts id → { status, children, error }
|
|
45
|
+
flatten.ts roots + expanded + cache → FlatRow<T>[]
|
|
46
|
+
persist.ts versioned localStorage helper
|
|
47
|
+
createDemoTree.ts deterministic synthetic tree for stories/tests
|
|
48
|
+
selection.ts Finder selection: anchor + shift-range + ⌘+A
|
|
49
|
+
clipboard.ts tree-local cut/copy state
|
|
50
|
+
renameUtils.ts splitFileName / autoSelectRange (base without ext)
|
|
51
|
+
finderShortcuts.ts Finder/Explorer keymap (mod+⌫, F2, ⌘D, …)
|
|
52
|
+
dnd.ts resolveDropZone + defaultCanDrop (cycle / self-drop)
|
|
53
|
+
|
|
54
|
+
context/ Provider + per-feature hooks (folders-per-feature)
|
|
55
|
+
TreeContext.tsx thin assembly: stitches the hooks below into one value
|
|
56
|
+
TreeContextValue.ts interface TreeContextValue<T>
|
|
57
|
+
hooks.ts public hooks: useTreeSelection / Expansion / Rename / Clipboard / Dnd / …
|
|
58
|
+
state/ reducer + initial state + action types
|
|
59
|
+
async-children/ cache + nodeById + fetchChildren + refresh / refreshAll
|
|
60
|
+
expansion/ expand / collapse / toggle / expandAll / collapseAll
|
|
61
|
+
selection/ clickSelect / moveSelect / selectAll + plain select / clear
|
|
62
|
+
rename/ startRename / cancelRename / commitRename (window.dialog.alert on error)
|
|
63
|
+
clipboard/ cutToClipboard / copyToClipboard / pasteFromClipboard
|
|
64
|
+
menu/ built-in actions registry + merged declarative resolver
|
|
65
|
+
dnd/ draggingIds / dropTarget / commitDrop / canDrop layering
|
|
66
|
+
persist/ localStorage + onSelectionChange / onExpansionChange notify
|
|
67
|
+
|
|
68
|
+
hooks/ container-level keyboard hooks (folders-per-scope)
|
|
69
|
+
keyboard/ ↑↓ ←→ Home/End Enter/Space Esc ⌘+A (Shift extends)
|
|
70
|
+
type-ahead/ Finder-style 600 ms prefix buffer
|
|
71
|
+
finder-hotkeys/ ⌘⌫ F2 ⌘D ⌘N ⌘⇧N ⌘C ⌘X ⌘V → adapter actions
|
|
72
|
+
|
|
34
73
|
components/
|
|
35
|
-
TreeRow.tsx
|
|
74
|
+
TreeRow.tsx default row: chevron + icon + label + actions + ctx-menu
|
|
75
|
+
TreeRenameInput.tsx inline rename input (auto-selects base name without extension)
|
|
76
|
+
TreeDropIndicator.tsx drop indicator (before / after line, inside fill)
|
|
77
|
+
TreeEmptyArea.tsx fills space below last row — empty context menu + root drop target
|
|
36
78
|
TreeChevron / TreeIcon / TreeLabel / TreeIndentGuides
|
|
37
|
-
TreeSearchInput.tsx
|
|
38
|
-
TreeContent.tsx
|
|
79
|
+
TreeSearchInput.tsx controlled search input
|
|
80
|
+
TreeContent.tsx iterates flatRows, default-renders TreeRow
|
|
39
81
|
TreeEmpty / TreeSkeleton / TreeError
|
|
40
|
-
TreeRoot.tsx high-level entry — Provider + shell + content
|
|
41
|
-
lazy.tsx LazyTree via createLazyComponent
|
|
42
82
|
```
|
|
43
83
|
|
|
44
|
-
Dependency direction: `components → context → data → types`. `hooks/` consume `context/`.
|
|
84
|
+
Dependency direction: `components → context → data → types`. `hooks/` consume `context/` and `data/`. Pure helpers (`data/`, `hooks/*/match-prefix.ts`, `hooks/keyboard/arrow-nav.ts`, …) are unit-testable without a DOM.
|
|
45
85
|
|
|
46
86
|
## Quick start
|
|
47
87
|
|
|
@@ -54,9 +94,7 @@ const data: TreeNode<FsNode>[] = [
|
|
|
54
94
|
{
|
|
55
95
|
id: 'src',
|
|
56
96
|
data: { name: 'src' },
|
|
57
|
-
children: [
|
|
58
|
-
{ id: 'index.ts', data: { name: 'index.ts' } },
|
|
59
|
-
],
|
|
97
|
+
children: [{ id: 'index.ts', data: { name: 'index.ts' } }],
|
|
60
98
|
},
|
|
61
99
|
];
|
|
62
100
|
|
|
@@ -71,6 +109,55 @@ const data: TreeNode<FsNode>[] = [
|
|
|
71
109
|
/>
|
|
72
110
|
```
|
|
73
111
|
|
|
112
|
+
## Finder/Explorer preset
|
|
113
|
+
|
|
114
|
+
`<FinderTree>` is `<TreeRoot>` with sensible Finder defaults pre-set:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { FinderTree, type TreeAdapter } from '@djangocfg/ui-tools/tree';
|
|
118
|
+
|
|
119
|
+
const fsAdapter: TreeAdapter<FsNode> = {
|
|
120
|
+
remove: async (nodes) => api.delete(nodes.map((n) => n.id)),
|
|
121
|
+
rename: async (node, name) => api.rename(node.id, name),
|
|
122
|
+
createFolder: async (parent, name) => api.mkdir(parent?.id ?? null, name),
|
|
123
|
+
move: async (nodes, target) => api.move(nodes.map((n) => n.id), target?.id ?? null),
|
|
124
|
+
// …
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
<FinderTree<FsNode>
|
|
128
|
+
data={data}
|
|
129
|
+
getItemName={(n) => n.data.name}
|
|
130
|
+
adapter={fsAdapter}
|
|
131
|
+
/>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Equivalent to:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
<TreeRoot<FsNode>
|
|
138
|
+
data={data}
|
|
139
|
+
getItemName={(n) => n.data.name}
|
|
140
|
+
adapter={fsAdapter}
|
|
141
|
+
selectionMode="multiple"
|
|
142
|
+
activationMode="double-click"
|
|
143
|
+
enableInlineRename
|
|
144
|
+
enableFinderHotkeys
|
|
145
|
+
enableTypeAhead
|
|
146
|
+
showIndentGuides
|
|
147
|
+
appearance={{ density: 'cozy' }}
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Override any default by passing the prop:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
<FinderTree
|
|
155
|
+
/* …data, getItemName, adapter… */
|
|
156
|
+
activationMode="single-click-preview" // VSCode/Cursor preview tabs
|
|
157
|
+
selectionMode="single" // disable multi-select
|
|
158
|
+
/>
|
|
159
|
+
```
|
|
160
|
+
|
|
74
161
|
## Async children
|
|
75
162
|
|
|
76
163
|
```tsx
|
|
@@ -110,14 +197,184 @@ function Toolbar() {
|
|
|
110
197
|
}
|
|
111
198
|
```
|
|
112
199
|
|
|
113
|
-
##
|
|
200
|
+
## Multi-selection (Finder / Explorer semantics)
|
|
201
|
+
|
|
202
|
+
When `selectionMode="multiple"`, Tree implements full file-manager selection:
|
|
203
|
+
|
|
204
|
+
| Gesture | Behaviour |
|
|
205
|
+
| --- | --- |
|
|
206
|
+
| **plain click** | replace selection, set anchor |
|
|
207
|
+
| **⌘ / Ctrl + click** | toggle row, set anchor |
|
|
208
|
+
| **shift + click** | range from anchor to clicked row |
|
|
209
|
+
| **shift + ⌘ + click** | union range with existing selection |
|
|
210
|
+
| **shift + ↑/↓** | extend range one row from anchor |
|
|
211
|
+
| **shift + Home / End** | extend range to top / bottom of visible rows |
|
|
212
|
+
| **⌘ / Ctrl + A** | select every visible row |
|
|
213
|
+
| **Esc** | clear selection (focused row stays) |
|
|
214
|
+
|
|
215
|
+
`anchor` is the pivot for shift-extend. Plain click and ⌘-click reset it to the clicked row; shift-click leaves it untouched. Access it from `useTreeSelection()`:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
const {
|
|
219
|
+
selectedIds, anchor,
|
|
220
|
+
clickSelect, moveSelect, selectAll,
|
|
221
|
+
setSelectedIds, clear, isSelected,
|
|
222
|
+
} = useTreeSelection();
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`clickSelect(id, { shift, meta })` and `moveSelect(id, { extend })` are also exposed for consumers building custom row components.
|
|
226
|
+
|
|
227
|
+
## CRUD adapter (delete / rename / new / cut+copy+paste)
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import type { TreeAdapter } from '@djangocfg/ui-tools/tree';
|
|
231
|
+
|
|
232
|
+
const adapter: TreeAdapter<FsNode> = {
|
|
233
|
+
remove?: (nodes) => Promise<void>;
|
|
234
|
+
rename?: (node, nextName) => Promise<void>;
|
|
235
|
+
createFile?: (parent, name) => Promise<void>; // parent === null → root
|
|
236
|
+
createFolder?: (parent, name) => Promise<void>;
|
|
237
|
+
duplicate?: (nodes) => Promise<void>;
|
|
238
|
+
move?: (nodes, target, position) => Promise<void>; // DnD + cut+paste
|
|
239
|
+
copy?: (nodes, target, position) => Promise<void>; // copy+paste
|
|
240
|
+
validateName?: (name, ctx) => string | null; // null = ok; string = error to show
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Every method is **optional**. Tree only exposes the corresponding context-menu item / hotkey when the matching method is defined. So an inspection-only tree (`adapter={{}}` or no adapter) gets no destructive actions — no greyed-out items, just nothing.
|
|
114
245
|
|
|
115
|
-
|
|
246
|
+
`window.dialog.confirm / prompt / alert` (from `@djangocfg/ui-core/lib/dialog-service`) drives every flow:
|
|
247
|
+
|
|
248
|
+
- **Delete** — confirms via `dialog.confirm({ variant: 'destructive' })`, then calls `adapter.remove`.
|
|
249
|
+
- **New file / folder** — prompts for a name via `dialog.prompt`, validates via `adapter.validateName`, then calls `adapter.createFile/createFolder`.
|
|
250
|
+
- **Rename (no inline)** — prompts via `dialog.prompt` and calls `adapter.rename`. When `enableInlineRename` is on, the menu item opens the in-row input instead.
|
|
251
|
+
- **Errors** — surface as `dialog.alert` and re-open the rename input on validation failure.
|
|
252
|
+
|
|
253
|
+
## Inline rename
|
|
254
|
+
|
|
255
|
+
Set `enableInlineRename` (requires `adapter.rename`):
|
|
256
|
+
|
|
257
|
+
- F2 opens the inline `<input>` for the focused row.
|
|
258
|
+
- Enter / blur commits → `adapter.rename(node, nextName)`.
|
|
259
|
+
- Escape cancels.
|
|
260
|
+
- The base name is pre-selected (Finder style — `foo.txt` highlights `foo`).
|
|
261
|
+
- Container hotkeys (delete / arrows / F2) are paused while the input is mounted.
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
const { renamingId, startRename, cancelRename, commitRename } = useTreeRename();
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Finder hotkeys
|
|
268
|
+
|
|
269
|
+
Set `enableFinderHotkeys` (only fires when the tree container has focus):
|
|
270
|
+
|
|
271
|
+
| Combo | Action |
|
|
272
|
+
| --- | --- |
|
|
273
|
+
| `F2` | rename |
|
|
274
|
+
| `⌘⌫` / `Delete` | delete |
|
|
275
|
+
| `⌘D` | duplicate |
|
|
276
|
+
| `⌘N` | new file |
|
|
277
|
+
| `⌘⇧N` | new folder |
|
|
278
|
+
| `⌘C` / `⌘X` / `⌘V` | copy / cut / paste |
|
|
279
|
+
|
|
280
|
+
Each binding is further gated by the adapter — `⌘⌫` does nothing when `adapter.remove` is undefined. Descriptions register with `useHotkeyHelp` for the global cheat sheet.
|
|
281
|
+
|
|
282
|
+
## Clipboard (cut / copy / paste)
|
|
283
|
+
|
|
284
|
+
Tree's clipboard is in-memory and tree-local (not the system clipboard) — that lets us dim cut rows the way Finder/Explorer do and gives a single source of truth across menu / hotkey / DnD entry points.
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
const { clipboard, isCut, cut, copy, paste, clear } = useTreeClipboard();
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Paste calls `adapter.move` for cut, `adapter.copy` for copy. Cut + paste clears the clipboard; copy + paste retains it. Errors → `dialog.alert`.
|
|
291
|
+
|
|
292
|
+
CSS hook for custom row styling: `[data-tree-row][data-clipboard="cut"]`.
|
|
293
|
+
|
|
294
|
+
## Drag and drop
|
|
295
|
+
|
|
296
|
+
Set `enableDnD` (requires `adapter.move`). Powered by `@dnd-kit/core` with pointer + keyboard sensors.
|
|
297
|
+
|
|
298
|
+
| Gesture | Behaviour |
|
|
299
|
+
| --- | --- |
|
|
300
|
+
| Drag a row | If it's part of the current selection, the whole selection drags together; otherwise just that row |
|
|
301
|
+
| Hover over top third of a row | Drop indicator above (reorder `before` sibling) |
|
|
302
|
+
| Hover over middle of a folder | Folder lights up (drop `inside`) |
|
|
303
|
+
| Hover over bottom third of a row | Drop indicator below (reorder `after` sibling) |
|
|
304
|
+
| Hover over empty area below the last row | Root drop target (drop into project root) |
|
|
305
|
+
| Drop onto self / descendant | Rejected by `defaultCanDrop` — indicator turns red |
|
|
306
|
+
|
|
307
|
+
```tsx
|
|
308
|
+
<TreeRoot
|
|
309
|
+
adapter={adapter}
|
|
310
|
+
enableDnD
|
|
311
|
+
canDrop={({ source, target, position }) => {
|
|
312
|
+
// Layer your domain rules on top of the built-in cycle check.
|
|
313
|
+
if (target?.data.isReadonly) return false;
|
|
314
|
+
return true;
|
|
315
|
+
}}
|
|
316
|
+
/>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
`useTreeDnd()` exposes `draggingIds`, `dropTarget`, `commitDrop`, `cancelDrag`, and `isAllowedDrop` for custom row renderers.
|
|
320
|
+
|
|
321
|
+
CSS hooks: `[data-tree-row][data-dragging="true"]` on the source row, `[data-tree-drop="before|after|inside"]` on `<TreeDropIndicator>`.
|
|
322
|
+
|
|
323
|
+
## Empty-area context menu
|
|
324
|
+
|
|
325
|
+
Tree's scroll container always ends with a `<TreeEmptyArea>` that fills the remaining vertical space. Right-clicking on whitespace below the last row opens a menu with the built-in actions that apply to "no row" — `New file`, `New folder`, and `Paste` (if clipboard has items). Items hide automatically when the matching adapter method is undefined; with no relevant items, the area renders as plain whitespace and right-click falls through to the browser default.
|
|
326
|
+
|
|
327
|
+
The empty area is also the **root drop target** during DnD — drop here to move/copy into the project root.
|
|
328
|
+
|
|
329
|
+
## Context menu
|
|
330
|
+
|
|
331
|
+
Two APIs, pick the lighter one when it fits.
|
|
332
|
+
|
|
333
|
+
**Short-form — `contextMenuActions`.** Pass a resolver that returns a flat list of actions per row. Tree builds a themed `<ContextMenu>` for you and **merges your items with built-in adapter actions** automatically. Use the string `'separator'` for dividers; mark dangerous rows with `destructive: true`.
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
import { Star } from 'lucide-react';
|
|
337
|
+
|
|
338
|
+
<TreeRoot<FsNode>
|
|
339
|
+
data={data}
|
|
340
|
+
getItemName={(n) => n.data.name}
|
|
341
|
+
adapter={fsAdapter}
|
|
342
|
+
contextMenuActions={({ row, selectedNodes }) => [
|
|
343
|
+
{ id: 'star', label: 'Star', icon: Star,
|
|
344
|
+
onSelect: () => star(selectedNodes) },
|
|
345
|
+
]}
|
|
346
|
+
/>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Built-in delete / rename / duplicate / cut / copy / paste / new-file / new-folder appear automatically based on adapter methods.
|
|
350
|
+
|
|
351
|
+
- Right-clicking a row outside the current selection switches selection to that single row first (Finder/Explorer convention), so destructive actions on multi-selection stay predictable.
|
|
352
|
+
- Limit / reorder the built-in items via `defaultMenuItems={['rename', 'delete']}`. Pass `[]` to suppress them entirely while still using the adapter for hotkeys / DnD.
|
|
353
|
+
|
|
354
|
+
**Full control — `renderContextMenu`.** Drop down when you need submenus, checkbox / radio items, custom JSX, or want to compose with non-default ContextMenu primitives. `renderContextMenu` wins if both props are set.
|
|
355
|
+
|
|
356
|
+
## Localisation
|
|
357
|
+
|
|
358
|
+
Every user-facing string lives on `TreeLabels` and is overridable via the `labels` prop:
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
<TreeRoot
|
|
362
|
+
labels={{
|
|
363
|
+
confirmDeleteTitle: (n) => n === 1 ? 'Удалить элемент?' : `Удалить ${n} элементов?`,
|
|
364
|
+
actionRename: 'Переименовать',
|
|
365
|
+
actionNewFolder: 'Новая папка',
|
|
366
|
+
invalidNameEmpty: 'Имя не может быть пустым',
|
|
367
|
+
// …all of TreeLabels is partial
|
|
368
|
+
}}
|
|
369
|
+
/>
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Defaults are English. Function-shaped entries (`confirmDeleteTitle(count)`, `confirmDeleteMessage(names)`, `searchMatches(n)`, `duplicateSuffix(name)`) receive runtime context for proper plurals / interpolation.
|
|
373
|
+
|
|
374
|
+
## Appearance
|
|
116
375
|
|
|
117
376
|
```tsx
|
|
118
377
|
<TreeRoot
|
|
119
|
-
data={…}
|
|
120
|
-
getItemName={…}
|
|
121
378
|
appearance={{
|
|
122
379
|
density: 'cozy', // 'compact' | 'cozy' | 'comfortable'
|
|
123
380
|
accent: 'default', // 'subtle' | 'default' | 'strong'
|
|
@@ -125,18 +382,12 @@ Cosmetic configuration is a single optional prop. Defaults to a comfortable VSCo
|
|
|
125
382
|
iconStrokeWidth: 1.5,
|
|
126
383
|
indentGuideOpacity: 0.4,
|
|
127
384
|
showActiveIndicator: true,
|
|
128
|
-
|
|
129
|
-
// fine-grained overrides (win over density):
|
|
130
|
-
rowHeight: 30,
|
|
131
|
-
iconSize: 18,
|
|
132
|
-
fontSize: 14,
|
|
133
|
-
gap: 10,
|
|
134
|
-
indent: 20,
|
|
385
|
+
rowHeight: 30, iconSize: 18, fontSize: 14, gap: 10, indent: 20,
|
|
135
386
|
}}
|
|
136
387
|
/>
|
|
137
388
|
```
|
|
138
389
|
|
|
139
|
-
The resolved appearance is exposed on the root container as CSS variables
|
|
390
|
+
The resolved appearance is exposed on the root container as CSS variables:
|
|
140
391
|
|
|
141
392
|
```
|
|
142
393
|
--tree-row-height
|
|
@@ -148,35 +399,43 @@ The resolved appearance is exposed on the root container as CSS variables, so an
|
|
|
148
399
|
--tree-guide-opacity
|
|
149
400
|
```
|
|
150
401
|
|
|
151
|
-
### VSCode-style
|
|
152
|
-
|
|
153
|
-
Row state visuals follow VSCode's Explorer:
|
|
402
|
+
### VSCode-style row state
|
|
154
403
|
|
|
155
404
|
| State | Look |
|
|
156
405
|
| --- | --- |
|
|
157
|
-
| Hover | subtle neutral wash
|
|
406
|
+
| Hover | subtle neutral wash |
|
|
158
407
|
| Focused (keyboard nav, not selected) | slightly stronger neutral |
|
|
159
408
|
| Selected, tree NOT focused | muted neutral block |
|
|
160
409
|
| Selected + tree focused-within | primary tint + colored text + left active bar |
|
|
161
410
|
| Search match | thin primary ring |
|
|
411
|
+
| Cut (clipboard) | `opacity-60`, dimmed |
|
|
162
412
|
| Disabled | dimmed + cursor-not-allowed |
|
|
163
413
|
|
|
164
|
-
|
|
414
|
+
## Activation modes
|
|
165
415
|
|
|
166
|
-
|
|
416
|
+
| Mode | Single click | Double click |
|
|
417
|
+
| --- | --- | --- |
|
|
418
|
+
| `'single-click'` *(default)* | activate `{ preview: false }` | activate `{ preview: false }` |
|
|
419
|
+
| `'double-click'` | select + focus only | activate `{ preview: false }` |
|
|
420
|
+
| `'single-click-preview'` *(VSCode / Cursor)* | activate `{ preview: true }` | activate `{ preview: false }` |
|
|
167
421
|
|
|
168
|
-
|
|
422
|
+
Folders ignore this — they always toggle on single click and never call `onActivate`. Keyboard `Enter` / `Space` always activates with `{ preview: false }`.
|
|
423
|
+
|
|
424
|
+
## Hooks
|
|
425
|
+
|
|
426
|
+
| Hook | What it exposes |
|
|
169
427
|
| --- | --- |
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
|
|
|
173
|
-
|
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
428
|
+
| `useTreeContext()` | full `TreeContextValue<T>` — use as a last resort |
|
|
429
|
+
| `useTreeRows()` | flat row list (visible only) |
|
|
430
|
+
| `useTreeSelection()` | `selectedIds`, `anchor`, `clickSelect`, `moveSelect`, `selectAll`, `setSelectedIds`, `clear`, `isSelected` |
|
|
431
|
+
| `useTreeExpansion()` | `expandedIds`, `expand`, `collapse`, `toggle`, `expandAll`, `collapseAll`, `isExpanded` |
|
|
432
|
+
| `useTreeFocus()` | `focusedId`, `setFocus` |
|
|
433
|
+
| `useTreeSearch()` | `query`, `setQuery`, `matchingIds`, `matchCount` |
|
|
434
|
+
| `useTreeRename()` | `renamingId`, `enabled`, `startRename`, `cancelRename`, `commitRename` |
|
|
435
|
+
| `useTreeClipboard()` | `clipboard`, `isCut`, `cut`, `copy`, `paste`, `clear` |
|
|
436
|
+
| `useTreeDnd()` | `active`, `draggingIds`, `dropTarget`, `beginDrag`, `setDropTarget`, `commitDrop`, `cancelDrag`, `isAllowedDrop` |
|
|
437
|
+
| `useTreeActions()` | imperative bag: `expand*` / `collapse*` / `refresh*` / `activate` |
|
|
438
|
+
| `useTreeLabels()` | resolved `TreeLabels` (with overrides applied) |
|
|
180
439
|
|
|
181
440
|
## Defaults
|
|
182
441
|
|
|
@@ -186,6 +445,9 @@ Toggle the bar with `appearance.showActiveIndicator`. Intensity scales with `app
|
|
|
186
445
|
| `activationMode` | `'single-click'` |
|
|
187
446
|
| `enableSearch` | `false` |
|
|
188
447
|
| `enableTypeAhead` | `true` |
|
|
448
|
+
| `enableInlineRename` | `false` |
|
|
449
|
+
| `enableFinderHotkeys` | `false` |
|
|
450
|
+
| `enableDnD` | `false` |
|
|
189
451
|
| `showIndentGuides` | `false` |
|
|
190
452
|
| `persistSelection` | `false` |
|
|
191
453
|
| `appearance.density` | `'cozy'` |
|
|
@@ -195,31 +457,9 @@ Toggle the bar with `appearance.showActiveIndicator`. Intensity scales with `app
|
|
|
195
457
|
| `appearance.iconStrokeWidth` | `1.5` |
|
|
196
458
|
| `appearance.indent` | `16` |
|
|
197
459
|
|
|
198
|
-
## Stories
|
|
199
|
-
|
|
200
|
-
| Story | Demonstrates |
|
|
201
|
-
| --- | --- |
|
|
202
|
-
| Default | sensible cozy defaults |
|
|
203
|
-
| WithActivationModes | single-click / double-click / preview semantics |
|
|
204
|
-
| WithHiddenFilter | `filterNode` toggle hides dot-files |
|
|
205
|
-
| Densities | three density presets side-by-side |
|
|
206
|
-
| WithIcons | file-type icons through `renderIcon` |
|
|
207
|
-
| WithStatus | modified / error / disabled rows through `renderLabel` |
|
|
208
|
-
| WithActions | rename / delete on hover through `renderActions` |
|
|
209
|
-
| WithSearch | built-in search bar + match highlight |
|
|
210
|
-
| WithIndentGuides | opt-in vertical guides |
|
|
211
|
-
| WithContextMenu | declarative right-click via `contextMenuActions={(row) => […]}` |
|
|
212
|
-
| AsyncLazyChildren | `loadChildren` + cache + dedup |
|
|
213
|
-
| ExpandCollapseAll | composition mode with `useTreeActions` |
|
|
214
|
-
| Persisted | localStorage round-trip |
|
|
215
|
-
| LargeTree | ~500 nodes scalability check |
|
|
216
|
-
| Playground | every knob exposed as a control |
|
|
217
|
-
|
|
218
460
|
## Filtering nodes
|
|
219
461
|
|
|
220
|
-
`filterNode` is a single predicate that decides which nodes appear at all.
|
|
221
|
-
Nodes returning `false` (and their descendants) are excluded from rendering,
|
|
222
|
-
keyboard navigation, and search.
|
|
462
|
+
`filterNode` is a single predicate that decides which nodes appear at all. Nodes returning `false` (and their descendants) are excluded from rendering, keyboard navigation, and search.
|
|
223
463
|
|
|
224
464
|
```tsx
|
|
225
465
|
const [showHidden, setShowHidden] = useState(false);
|
|
@@ -231,115 +471,13 @@ const [showHidden, setShowHidden] = useState(false);
|
|
|
231
471
|
/>
|
|
232
472
|
```
|
|
233
473
|
|
|
234
|
-
This is intentionally minimal — Tree is generic over `T` and has no opinion
|
|
235
|
-
on what "hidden" means in your domain. If your backend already provides
|
|
236
|
-
flags like `entry.isHidden` / `entry.isSystem`, use them directly:
|
|
474
|
+
This is intentionally minimal — Tree is generic over `T` and has no opinion on what "hidden" means in your domain. If your backend already provides flags like `entry.isHidden`, use them directly.
|
|
237
475
|
|
|
238
|
-
|
|
239
|
-
filterNode={(n) => showHidden || (!n.data.isHidden && !n.data.isSystem)}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
> **Frontend note.** From the browser you cannot read OS-level hidden
|
|
243
|
-
> attributes (Windows `FILE_ATTRIBUTE_HIDDEN`, macOS `kIsInvisible`).
|
|
244
|
-
> Either filter by name (Unix dot-prefix is the de-facto convention), or
|
|
245
|
-
> let your backend determine those flags and forward them in `node.data`.
|
|
246
|
-
|
|
247
|
-
## Activation modes
|
|
476
|
+
> **Frontend note.** From the browser you cannot read OS-level hidden attributes (`FILE_ATTRIBUTE_HIDDEN`, `kIsInvisible`). Either filter by name (Unix dot-prefix is the de-facto convention), or let your backend determine those flags and forward them in `node.data`.
|
|
248
477
|
|
|
249
|
-
|
|
250
|
-
by `activationMode`. Folders ignore this setting — they always toggle on
|
|
251
|
-
single click and never call `onActivate`.
|
|
478
|
+
## File icons
|
|
252
479
|
|
|
253
|
-
|
|
254
|
-
| --- | --- | --- |
|
|
255
|
-
| `'single-click'` *(default)* | activate `{ preview: false }` | activate `{ preview: false }` |
|
|
256
|
-
| `'double-click'` | select + focus only | activate `{ preview: false }` |
|
|
257
|
-
| `'single-click-preview'` *(VSCode / Cursor)* | activate `{ preview: true }` | activate `{ preview: false }` |
|
|
258
|
-
|
|
259
|
-
Keyboard `Enter` / `Space` always activates with `{ preview: false }` —
|
|
260
|
-
keyboard input is treated as an explicit user action.
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
<TreeRoot<FsNode>
|
|
264
|
-
data={data}
|
|
265
|
-
getItemName={(n) => n.data.name}
|
|
266
|
-
activationMode="single-click-preview"
|
|
267
|
-
onActivate={(node, { preview }) =>
|
|
268
|
-
preview ? openPreviewTab(node) : openPinnedTab(node)
|
|
269
|
-
}
|
|
270
|
-
/>
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
The active mode is also exposed on each row as
|
|
274
|
-
`data-activation-mode="<mode>"` for CSS-level targeting.
|
|
275
|
-
|
|
276
|
-
## Right-click menus
|
|
277
|
-
|
|
278
|
-
Two APIs, pick the lighter one when it fits.
|
|
279
|
-
|
|
280
|
-
**Short-form — `contextMenuActions`.** Pass a resolver that returns a flat
|
|
281
|
-
list of actions per row. Tree builds a themed `<ContextMenu>` for you. Use
|
|
282
|
-
the string `'separator'` to insert dividers between groups; mark dangerous
|
|
283
|
-
rows with `destructive: true`.
|
|
284
|
-
|
|
285
|
-
```tsx
|
|
286
|
-
import { Pencil, Copy, Trash2 } from 'lucide-react';
|
|
287
|
-
|
|
288
|
-
<TreeRoot<FsNode>
|
|
289
|
-
data={data}
|
|
290
|
-
getItemName={(n) => n.data.name}
|
|
291
|
-
contextMenuActions={({ node, isFolder }) => [
|
|
292
|
-
{ id: 'rename', label: 'Rename', icon: Pencil, shortcut: '↵',
|
|
293
|
-
onSelect: () => rename(node) },
|
|
294
|
-
{ id: 'copy', label: 'Copy', icon: Copy, shortcut: '⌘C',
|
|
295
|
-
onSelect: () => copy(node) },
|
|
296
|
-
'separator',
|
|
297
|
-
{ id: 'delete', label: 'Delete', icon: Trash2, shortcut: '⌫',
|
|
298
|
-
destructive: true,
|
|
299
|
-
onSelect: () => del(node, { folder: isFolder }) },
|
|
300
|
-
]}
|
|
301
|
-
/>
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
Return `null` / `undefined` / `[]` from the resolver to skip the menu for a
|
|
305
|
-
specific row (e.g. read-only system entries).
|
|
306
|
-
|
|
307
|
-
**Full control — `renderContextMenu`.** Drop down when you need submenus,
|
|
308
|
-
checkbox / radio items, custom JSX, or want to compose with non-default
|
|
309
|
-
ContextMenu primitives. `renderContextMenu` wins if both props are set.
|
|
310
|
-
|
|
311
|
-
```tsx
|
|
312
|
-
import {
|
|
313
|
-
ContextMenu, ContextMenuTrigger, ContextMenuContent,
|
|
314
|
-
ContextMenuSub, ContextMenuSubTrigger, ContextMenuSubContent,
|
|
315
|
-
ContextMenuCheckboxItem,
|
|
316
|
-
} from '@djangocfg/ui-core/components';
|
|
317
|
-
|
|
318
|
-
<TreeRoot<FsNode>
|
|
319
|
-
data={data}
|
|
320
|
-
getItemName={(n) => n.data.name}
|
|
321
|
-
renderContextMenu={({ node }, trigger) => (
|
|
322
|
-
<ContextMenu>
|
|
323
|
-
<ContextMenuTrigger asChild>{trigger}</ContextMenuTrigger>
|
|
324
|
-
<ContextMenuContent>
|
|
325
|
-
<ContextMenuCheckboxItem checked={isPinned(node.id)}
|
|
326
|
-
onCheckedChange={(v) => togglePin(node.id, v)}>
|
|
327
|
-
Pin to top
|
|
328
|
-
</ContextMenuCheckboxItem>
|
|
329
|
-
<ContextMenuSub>
|
|
330
|
-
<ContextMenuSubTrigger>Open with…</ContextMenuSubTrigger>
|
|
331
|
-
<ContextMenuSubContent>{/* editors */}</ContextMenuSubContent>
|
|
332
|
-
</ContextMenuSub>
|
|
333
|
-
</ContextMenuContent>
|
|
334
|
-
</ContextMenu>
|
|
335
|
-
)}
|
|
336
|
-
/>
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
## VSCode-style file icons
|
|
340
|
-
|
|
341
|
-
Tree is generic over `T` — it has no opinion on whether nodes are files. For a
|
|
342
|
-
ready-made VSCode-style icon set, use the `file-icon` companion subpath:
|
|
480
|
+
Tree is generic over `T` — it has no opinion on whether nodes are files. For a ready-made VSCode-style icon set, use the `file-icon` companion subpath:
|
|
343
481
|
|
|
344
482
|
```tsx
|
|
345
483
|
import { TreeRoot } from '@djangocfg/ui-tools/tree';
|
|
@@ -352,29 +490,20 @@ import { createFileIconSlot } from '@djangocfg/ui-tools/file-icon';
|
|
|
352
490
|
/>
|
|
353
491
|
```
|
|
354
492
|
|
|
355
|
-
The icon SVGs are vendored statically from `material-file-icons` (MIT) — no
|
|
356
|
-
runtime dependency, no install step, and resolution is synchronous in any
|
|
357
|
-
bundler.
|
|
358
|
-
|
|
359
|
-
Folders use a small built-in mapping (`src` → `FolderCode`,
|
|
360
|
-
`node_modules` → `Package`, `.git` → `FolderGit2`, `dist`/`build`/`.next`
|
|
361
|
-
→ `FolderOutput`, `tests`/`__tests__` → `FlaskConical`, …). Anything not
|
|
362
|
-
in the table renders the generic `Folder` / `FolderOpen` pair. Override
|
|
363
|
-
or extend the table per-call:
|
|
493
|
+
The icon SVGs are vendored statically from `material-file-icons` (MIT) — no runtime dependency, no install step, synchronous resolution.
|
|
364
494
|
|
|
365
|
-
|
|
366
|
-
import { FolderHeart } from 'lucide-react';
|
|
367
|
-
|
|
368
|
-
renderIcon={createFileIconSlot({
|
|
369
|
-
getName: (n) => n.data.name,
|
|
370
|
-
folderOverrides: { favorites: FolderHeart },
|
|
371
|
-
})}
|
|
372
|
-
```
|
|
495
|
+
## Status
|
|
373
496
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
-
|
|
377
|
-
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
497
|
+
| Feature | Status |
|
|
498
|
+
| --- | --- |
|
|
499
|
+
| Multi-selection (anchor / shift / ⌘+A) | ✅ |
|
|
500
|
+
| `TreeAdapter` + built-in CRUD via `window.dialog` | ✅ |
|
|
501
|
+
| `<FinderTree>` preset | ✅ |
|
|
502
|
+
| Inline rename (F2, auto-select base) | ✅ |
|
|
503
|
+
| Finder hotkeys (⌘⌫ F2 ⌘D ⌘N ⌘⇧N ⌘C ⌘X ⌘V) | ✅ |
|
|
504
|
+
| Clipboard (cut / copy / paste, dimmed cut state) | ✅ |
|
|
505
|
+
| Drag-and-drop (`@dnd-kit/core`) | ✅ |
|
|
506
|
+
| Empty-area context menu (`new file / new folder / paste` on background) | ✅ |
|
|
507
|
+
| Undo / Redo | n/a (host's job — implement in your adapter / Wails-Go layer) |
|
|
508
|
+
| Virtualization | out of scope (wrap with `@tanstack/react-virtual`) |
|
|
509
|
+
| Multi-tree cross-DnD | out of scope |
|