@djangocfg/ui-tools 2.1.415 → 2.1.417
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/audio-player/index.cjs +2099 -0
- package/dist/audio-player/index.cjs.map +1 -0
- package/dist/audio-player/index.css +65 -0
- package/dist/audio-player/index.css.map +1 -0
- package/dist/audio-player/index.d.cts +174 -0
- package/dist/audio-player/index.d.ts +174 -0
- package/dist/audio-player/index.mjs +2076 -0
- package/dist/audio-player/index.mjs.map +1 -0
- package/dist/composer-registry/index.cjs +45 -0
- package/dist/composer-registry/index.cjs.map +1 -0
- package/dist/composer-registry/index.d.cts +73 -0
- package/dist/composer-registry/index.d.ts +73 -0
- package/dist/composer-registry/index.mjs +39 -0
- package/dist/composer-registry/index.mjs.map +1 -0
- 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 +2019 -279
- package/dist/tree/index.cjs.map +1 -1
- package/dist/tree/index.d.cts +731 -72
- package/dist/tree/index.d.ts +731 -72
- package/dist/tree/index.mjs +2009 -282
- package/dist/tree/index.mjs.map +1 -1
- package/package.json +18 -9
- package/src/tools/chat/README.md +111 -1
- package/src/tools/chat/composer/Composer.tsx +146 -25
- 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/context/ChatProvider.tsx +13 -78
- package/src/tools/chat/hooks/useAutoFocusOnStreamEnd.ts +12 -15
- package/src/tools/chat/hooks/useFocusOnEmptyClick.ts +4 -5
- package/src/tools/chat/launcher/header/ChatHeader.tsx +14 -19
- package/src/tools/chat/launcher/header/ChatHeaderActionButton.tsx +8 -12
- 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 +111 -72
- 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 +103 -8
- package/src/tools/data/Tree/components/index.ts +6 -0
- package/src/tools/data/Tree/context/TreeContext.tsx +223 -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 +11 -0
- package/src/tools/data/Tree/context/menu/render.tsx +75 -0
- package/src/tools/data/Tree/context/menu/use-resolved-menu.ts +141 -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 +26 -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 +158 -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/Header/MetaActions.tsx +6 -9
- package/src/tools/dev/OpenapiViewer/components/DocsLayout/index.tsx +2 -4
- 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/input/SpeechRecognition/widgets/VoiceComposerSlot.tsx +11 -12
- package/src/tools/integration/ComposerRegistry/index.ts +105 -0
- package/src/tools/media/AudioPlayer/Player.tsx +2 -0
- package/src/tools/media/AudioPlayer/PlayerShell.tsx +37 -22
- package/src/tools/media/AudioPlayer/lazy.tsx +30 -42
- package/src/tools/media/AudioPlayer/parts/Controls/IconButton.tsx +10 -11
- package/src/tools/media/AudioPlayer/parts/Controls/VolumeControl.tsx +52 -115
- package/src/tools/media/AudioPlayer/types.ts +15 -0
- 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
package/dist/tree/index.mjs
CHANGED
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
import { __name } from '../chunk-PAWJFY3S.mjs';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { createContext, memo,
|
|
3
|
+
import { createContext, memo, useRef, useCallback, useMemo, useEffect, useState, useReducer, Fragment as Fragment$1 } from 'react';
|
|
4
4
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
5
|
+
import { getDialog } from '@djangocfg/ui-core/lib/dialog-service';
|
|
6
|
+
import { CornerUpLeft, Pencil, Copy, Scissors, Trash2, FilePlus, FolderPlus, ChevronDown, ChevronRight, FolderOpen, Folder, File, Loader2, Search, X, AlertCircle } from 'lucide-react';
|
|
5
7
|
import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuSeparator, ContextMenuItem, ContextMenuShortcut } from '@djangocfg/ui-core/components';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, useDraggable, useDroppable } from '@dnd-kit/core';
|
|
8
10
|
import { useHotkey } from '@djangocfg/ui-core/hooks';
|
|
9
11
|
|
|
10
|
-
// src/tools/data/Tree/types.ts
|
|
11
|
-
var DEFAULT_TREE_LABELS = {
|
|
12
|
-
loading: "Loading\u2026",
|
|
13
|
-
empty: "Nothing to show",
|
|
14
|
-
error: "Failed to load",
|
|
15
|
-
searchPlaceholder: "Search\u2026",
|
|
16
|
-
searchMatches: /* @__PURE__ */ __name((n) => `${n} match${n === 1 ? "" : "es"}`, "searchMatches"),
|
|
17
|
-
ariaLabel: "Tree"
|
|
18
|
-
};
|
|
19
|
-
|
|
20
12
|
// src/tools/data/Tree/data/childCache.ts
|
|
21
13
|
var createChildCache = /* @__PURE__ */ __name(() => /* @__PURE__ */ new Map(), "createChildCache");
|
|
22
14
|
var resolveChildren = /* @__PURE__ */ __name((cache, node) => {
|
|
@@ -205,7 +197,44 @@ function rowStateClasses(a) {
|
|
|
205
197
|
].join(" ");
|
|
206
198
|
}
|
|
207
199
|
__name(rowStateClasses, "rowStateClasses");
|
|
208
|
-
|
|
200
|
+
|
|
201
|
+
// src/tools/data/Tree/types/labels.ts
|
|
202
|
+
var DEFAULT_TREE_LABELS = {
|
|
203
|
+
loading: "Loading\u2026",
|
|
204
|
+
empty: "Nothing to show",
|
|
205
|
+
error: "Failed to load",
|
|
206
|
+
searchPlaceholder: "Search\u2026",
|
|
207
|
+
searchMatches: /* @__PURE__ */ __name((n) => `${n} match${n === 1 ? "" : "es"}`, "searchMatches"),
|
|
208
|
+
ariaLabel: "Tree",
|
|
209
|
+
actionOpen: "Open",
|
|
210
|
+
actionRename: "Rename",
|
|
211
|
+
actionDuplicate: "Duplicate",
|
|
212
|
+
actionCut: "Cut",
|
|
213
|
+
actionCopy: "Copy",
|
|
214
|
+
actionPaste: "Paste",
|
|
215
|
+
actionDelete: "Delete",
|
|
216
|
+
actionNewFile: "New file",
|
|
217
|
+
actionNewFolder: "New folder",
|
|
218
|
+
confirmDeleteTitle: /* @__PURE__ */ __name((n) => n === 1 ? "Delete item?" : `Delete ${n} items?`, "confirmDeleteTitle"),
|
|
219
|
+
confirmDeleteMessage: /* @__PURE__ */ __name((names) => names.length === 1 ? `"${names[0]}" will be removed. This action cannot be undone.` : `${names.length} items will be removed. This action cannot be undone.`, "confirmDeleteMessage"),
|
|
220
|
+
confirmDeleteOk: "Delete",
|
|
221
|
+
confirmDeleteCancel: "Cancel",
|
|
222
|
+
newFileTitle: "New file",
|
|
223
|
+
newFileMessage: "File name",
|
|
224
|
+
newFilePlaceholder: "untitled.txt",
|
|
225
|
+
newFileDefault: "untitled.txt",
|
|
226
|
+
newFolderTitle: "New folder",
|
|
227
|
+
newFolderMessage: "Folder name",
|
|
228
|
+
newFolderPlaceholder: "untitled folder",
|
|
229
|
+
newFolderDefault: "untitled folder",
|
|
230
|
+
renameTitle: "Rename",
|
|
231
|
+
renameMessage: "New name",
|
|
232
|
+
invalidNameEmpty: "Name cannot be empty",
|
|
233
|
+
duplicateSuffix: /* @__PURE__ */ __name((name) => `${name} copy`, "duplicateSuffix")
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/tools/data/Tree/context/state/reducer.ts
|
|
237
|
+
function reducer(state, action) {
|
|
209
238
|
switch (action.type) {
|
|
210
239
|
case "expand": {
|
|
211
240
|
if (state.expanded.has(action.id)) return state;
|
|
@@ -230,100 +259,92 @@ var reducer = /* @__PURE__ */ __name((state, action) => {
|
|
|
230
259
|
case "select": {
|
|
231
260
|
if (action.mode === "none") return state;
|
|
232
261
|
if (action.mode === "single") {
|
|
233
|
-
return {
|
|
262
|
+
return {
|
|
263
|
+
...state,
|
|
264
|
+
selected: /* @__PURE__ */ new Set([action.id]),
|
|
265
|
+
anchor: action.id,
|
|
266
|
+
focused: action.id
|
|
267
|
+
};
|
|
234
268
|
}
|
|
235
269
|
const next = new Set(state.selected);
|
|
236
270
|
if (next.has(action.id)) next.delete(action.id);
|
|
237
271
|
else next.add(action.id);
|
|
238
|
-
return { ...state, selected: next, focused: action.id };
|
|
272
|
+
return { ...state, selected: next, anchor: action.id, focused: action.id };
|
|
239
273
|
}
|
|
240
274
|
case "select-many":
|
|
241
275
|
return { ...state, selected: new Set(action.ids) };
|
|
242
276
|
case "clear-selection":
|
|
243
|
-
return { ...state, selected: /* @__PURE__ */ new Set() };
|
|
277
|
+
return { ...state, selected: /* @__PURE__ */ new Set(), anchor: null };
|
|
278
|
+
case "selection-replace":
|
|
279
|
+
return {
|
|
280
|
+
...state,
|
|
281
|
+
selected: new Set(action.selected),
|
|
282
|
+
anchor: action.anchor,
|
|
283
|
+
focused: action.focused
|
|
284
|
+
};
|
|
285
|
+
case "set-anchor":
|
|
286
|
+
return { ...state, anchor: action.id };
|
|
244
287
|
case "focus":
|
|
245
288
|
return { ...state, focused: action.id };
|
|
246
289
|
case "set-query":
|
|
247
290
|
return { ...state, query: action.q };
|
|
291
|
+
case "start-rename":
|
|
292
|
+
return { ...state, renaming: action.id };
|
|
293
|
+
case "stop-rename":
|
|
294
|
+
return state.renaming === null ? state : { ...state, renaming: null };
|
|
295
|
+
case "clipboard-set":
|
|
296
|
+
return { ...state, clipboard: action.payload };
|
|
248
297
|
case "cache-tick":
|
|
249
298
|
return { ...state, cacheTick: state.cacheTick + 1 };
|
|
250
299
|
default:
|
|
251
300
|
return state;
|
|
252
301
|
}
|
|
253
|
-
}, "reducer");
|
|
254
|
-
var TreeContext = createContext(null);
|
|
255
|
-
function useTreeContext() {
|
|
256
|
-
const ctx = React.useContext(TreeContext);
|
|
257
|
-
if (!ctx) {
|
|
258
|
-
throw new Error("useTreeContext must be used inside <TreeProvider>");
|
|
259
|
-
}
|
|
260
|
-
return ctx;
|
|
261
302
|
}
|
|
262
|
-
__name(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
303
|
+
__name(reducer, "reducer");
|
|
304
|
+
|
|
305
|
+
// src/tools/data/Tree/context/state/initial.ts
|
|
306
|
+
function createInitialState(input) {
|
|
307
|
+
const { persisted, initialExpandedIds, initialSelectedIds, persistSelection } = input;
|
|
308
|
+
const initialSelected = new Set(
|
|
309
|
+
(persistSelection ? persisted?.selectedItems : void 0) ?? initialSelectedIds ?? []
|
|
310
|
+
);
|
|
311
|
+
const initialAnchor = initialSelected.size > 0 ? initialSelected.values().next().value : null;
|
|
312
|
+
return {
|
|
313
|
+
expanded: new Set(persisted?.expandedItems ?? initialExpandedIds ?? []),
|
|
314
|
+
selected: initialSelected,
|
|
315
|
+
anchor: initialAnchor,
|
|
316
|
+
focused: null,
|
|
317
|
+
query: "",
|
|
318
|
+
renaming: null,
|
|
319
|
+
clipboard: null,
|
|
320
|
+
cacheTick: 0
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
__name(createInitialState, "createInitialState");
|
|
324
|
+
|
|
325
|
+
// src/tools/data/Tree/context/async-children/collect-ids.ts
|
|
326
|
+
function collectAllFolderIds(roots, cache, out) {
|
|
269
327
|
for (const node of roots) {
|
|
270
328
|
if (Array.isArray(node.children)) {
|
|
271
329
|
out.push(node.id);
|
|
272
|
-
|
|
330
|
+
collectAllFolderIds(node.children, cache, out);
|
|
273
331
|
} else if (node.isFolder) {
|
|
274
332
|
out.push(node.id);
|
|
275
333
|
const entry = cache.get(node.id);
|
|
276
|
-
if (entry?.children)
|
|
334
|
+
if (entry?.children) collectAllFolderIds(entry.children, cache, out);
|
|
277
335
|
}
|
|
278
336
|
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
appearance,
|
|
291
|
-
onSelectionChange,
|
|
292
|
-
onExpansionChange,
|
|
293
|
-
onActivate,
|
|
294
|
-
filterNode,
|
|
295
|
-
enableSearch = false,
|
|
296
|
-
showIndentGuides = false,
|
|
297
|
-
renderIcon,
|
|
298
|
-
renderLabel,
|
|
299
|
-
renderActions,
|
|
300
|
-
renderContextMenu,
|
|
301
|
-
labels: labelsOverride,
|
|
302
|
-
persistKey,
|
|
303
|
-
persistSelection = false,
|
|
304
|
-
children
|
|
305
|
-
} = props;
|
|
306
|
-
const labels = useMemo(
|
|
307
|
-
() => ({ ...DEFAULT_TREE_LABELS, ...labelsOverride }),
|
|
308
|
-
[labelsOverride]
|
|
309
|
-
);
|
|
310
|
-
const resolvedAppearance = useMemo(
|
|
311
|
-
() => resolveAppearance(appearance, indent),
|
|
312
|
-
[appearance, indent]
|
|
313
|
-
);
|
|
314
|
-
const persisted = useMemo(
|
|
315
|
-
() => persistKey ? loadTreeState(persistKey) : null,
|
|
316
|
-
[persistKey]
|
|
317
|
-
);
|
|
318
|
-
const [state, dispatch] = useReducer(reducer, void 0, () => ({
|
|
319
|
-
expanded: new Set(persisted?.expandedItems ?? initialExpandedIds ?? []),
|
|
320
|
-
selected: new Set(
|
|
321
|
-
(persistSelection ? persisted?.selectedItems : void 0) ?? initialSelectedIds ?? []
|
|
322
|
-
),
|
|
323
|
-
focused: null,
|
|
324
|
-
query: "",
|
|
325
|
-
cacheTick: 0
|
|
326
|
-
}));
|
|
337
|
+
}
|
|
338
|
+
__name(collectAllFolderIds, "collectAllFolderIds");
|
|
339
|
+
|
|
340
|
+
// src/tools/data/Tree/context/async-children/use-async-children.ts
|
|
341
|
+
function useAsyncChildren({
|
|
342
|
+
data,
|
|
343
|
+
loadChildren,
|
|
344
|
+
expanded,
|
|
345
|
+
cacheTick,
|
|
346
|
+
bumpCacheTick
|
|
347
|
+
}) {
|
|
327
348
|
const cacheRef = useRef(createChildCache());
|
|
328
349
|
const inflightRef = useRef(/* @__PURE__ */ new Map());
|
|
329
350
|
const fetchChildren = useCallback(
|
|
@@ -335,11 +356,11 @@ function TreeProvider(props) {
|
|
|
335
356
|
const inflight = inflightRef.current.get(node.id);
|
|
336
357
|
if (inflight) return inflight;
|
|
337
358
|
cacheRef.current.set(node.id, { status: "loading", children: [] });
|
|
338
|
-
|
|
359
|
+
bumpCacheTick();
|
|
339
360
|
const promise = (async () => {
|
|
340
361
|
try {
|
|
341
|
-
const
|
|
342
|
-
cacheRef.current.set(node.id, { status: "loaded", children
|
|
362
|
+
const children = await loadChildren(node);
|
|
363
|
+
cacheRef.current.set(node.id, { status: "loaded", children });
|
|
343
364
|
} catch (err) {
|
|
344
365
|
cacheRef.current.set(node.id, {
|
|
345
366
|
status: "error",
|
|
@@ -348,13 +369,13 @@ function TreeProvider(props) {
|
|
|
348
369
|
});
|
|
349
370
|
} finally {
|
|
350
371
|
inflightRef.current.delete(node.id);
|
|
351
|
-
|
|
372
|
+
bumpCacheTick();
|
|
352
373
|
}
|
|
353
374
|
})();
|
|
354
375
|
inflightRef.current.set(node.id, promise);
|
|
355
376
|
return promise;
|
|
356
377
|
},
|
|
357
|
-
[loadChildren]
|
|
378
|
+
[loadChildren, bumpCacheTick]
|
|
358
379
|
);
|
|
359
380
|
const nodeById = useMemo(() => {
|
|
360
381
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -370,141 +391,1111 @@ function TreeProvider(props) {
|
|
|
370
391
|
}, "walk");
|
|
371
392
|
walk(data);
|
|
372
393
|
return map;
|
|
373
|
-
}, [data,
|
|
394
|
+
}, [data, cacheTick]);
|
|
374
395
|
useEffect(() => {
|
|
375
396
|
if (!loadChildren) return;
|
|
376
|
-
for (const id of
|
|
397
|
+
for (const id of expanded) {
|
|
377
398
|
const node = nodeById.get(id);
|
|
378
399
|
if (!node) continue;
|
|
379
400
|
void fetchChildren(node);
|
|
380
401
|
}
|
|
381
|
-
}, [loadChildren,
|
|
382
|
-
const
|
|
383
|
-
() =>
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
402
|
+
}, [loadChildren, expanded, cacheTick, nodeById, fetchChildren]);
|
|
403
|
+
const refresh = useCallback(
|
|
404
|
+
async (id) => {
|
|
405
|
+
const node = nodeById.get(id);
|
|
406
|
+
if (!node || !loadChildren) return;
|
|
407
|
+
cacheRef.current.delete(id);
|
|
408
|
+
bumpCacheTick();
|
|
409
|
+
await fetchChildren(node);
|
|
410
|
+
},
|
|
411
|
+
[nodeById, loadChildren, fetchChildren, bumpCacheTick]
|
|
390
412
|
);
|
|
391
|
-
const
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
413
|
+
const refreshAll = useCallback(async () => {
|
|
414
|
+
cacheRef.current.clear();
|
|
415
|
+
bumpCacheTick();
|
|
416
|
+
if (!loadChildren) return;
|
|
417
|
+
await Promise.all(
|
|
418
|
+
[...expanded].map((id) => {
|
|
419
|
+
const node = nodeById.get(id);
|
|
420
|
+
return node ? fetchChildren(node) : void 0;
|
|
421
|
+
})
|
|
422
|
+
);
|
|
423
|
+
}, [loadChildren, expanded, nodeById, fetchChildren, bumpCacheTick]);
|
|
424
|
+
const collectFolderIds = useCallback(() => {
|
|
425
|
+
const ids = [];
|
|
426
|
+
collectAllFolderIds(data, cacheRef.current, ids);
|
|
427
|
+
return ids;
|
|
428
|
+
}, [data]);
|
|
429
|
+
return {
|
|
430
|
+
cache: cacheRef.current,
|
|
431
|
+
nodeById,
|
|
432
|
+
fetchChildren,
|
|
433
|
+
refresh,
|
|
434
|
+
refreshAll,
|
|
435
|
+
collectFolderIds
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
__name(useAsyncChildren, "useAsyncChildren");
|
|
439
|
+
function useExpansion({
|
|
440
|
+
dispatch,
|
|
441
|
+
collectFolderIds
|
|
442
|
+
}) {
|
|
443
|
+
const expand = useCallback(
|
|
444
|
+
(id) => dispatch({ type: "expand", id }),
|
|
445
|
+
[dispatch]
|
|
446
|
+
);
|
|
447
|
+
const collapse = useCallback(
|
|
448
|
+
(id) => dispatch({ type: "collapse", id }),
|
|
449
|
+
[dispatch]
|
|
450
|
+
);
|
|
451
|
+
const toggle = useCallback(
|
|
452
|
+
(id) => dispatch({ type: "toggle", id }),
|
|
453
|
+
[dispatch]
|
|
454
|
+
);
|
|
455
|
+
const expandAll = useCallback(() => {
|
|
456
|
+
dispatch({ type: "set-expanded", ids: collectFolderIds() });
|
|
457
|
+
}, [dispatch, collectFolderIds]);
|
|
458
|
+
const collapseAll = useCallback(
|
|
459
|
+
() => dispatch({ type: "set-expanded", ids: [] }),
|
|
460
|
+
[dispatch]
|
|
461
|
+
);
|
|
462
|
+
return { expand, collapse, toggle, expandAll, collapseAll };
|
|
463
|
+
}
|
|
464
|
+
__name(useExpansion, "useExpansion");
|
|
465
|
+
|
|
466
|
+
// src/tools/data/Tree/data/selection.ts
|
|
467
|
+
function indexOf(rows, id) {
|
|
468
|
+
if (id == null) return -1;
|
|
469
|
+
for (let i = 0; i < rows.length; i++) {
|
|
470
|
+
if (rows[i].node.id === id) return i;
|
|
471
|
+
}
|
|
472
|
+
return -1;
|
|
473
|
+
}
|
|
474
|
+
__name(indexOf, "indexOf");
|
|
475
|
+
function computeRange(rows, fromId, toId) {
|
|
476
|
+
if (fromId == null || toId == null) return [];
|
|
477
|
+
const a = indexOf(rows, fromId);
|
|
478
|
+
const b = indexOf(rows, toId);
|
|
479
|
+
if (a < 0 || b < 0) return [];
|
|
480
|
+
const [lo, hi] = a <= b ? [a, b] : [b, a];
|
|
481
|
+
const out = [];
|
|
482
|
+
for (let i = lo; i <= hi; i++) out.push(rows[i].node.id);
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
485
|
+
__name(computeRange, "computeRange");
|
|
486
|
+
function selectionFromClick(state, rows, id, mods, multi) {
|
|
487
|
+
if (!multi) {
|
|
488
|
+
return { selected: /* @__PURE__ */ new Set([id]), anchor: id, focused: id };
|
|
489
|
+
}
|
|
490
|
+
if (mods.shift && mods.meta) {
|
|
491
|
+
const anchor = state.anchor ?? state.focused ?? id;
|
|
492
|
+
const range = computeRange(rows, anchor, id);
|
|
493
|
+
const next = new Set(state.selected);
|
|
494
|
+
for (const r of range) next.add(r);
|
|
495
|
+
return { selected: next, anchor: state.anchor ?? id, focused: id };
|
|
496
|
+
}
|
|
497
|
+
if (mods.shift) {
|
|
498
|
+
const anchor = state.anchor ?? state.focused ?? id;
|
|
499
|
+
const range = computeRange(rows, anchor, id);
|
|
500
|
+
return {
|
|
501
|
+
selected: new Set(range.length > 0 ? range : [id]),
|
|
502
|
+
anchor: state.anchor ?? anchor,
|
|
503
|
+
focused: id
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
if (mods.meta) {
|
|
507
|
+
const next = new Set(state.selected);
|
|
508
|
+
if (next.has(id)) next.delete(id);
|
|
509
|
+
else next.add(id);
|
|
510
|
+
return { selected: next, anchor: id, focused: id };
|
|
511
|
+
}
|
|
512
|
+
return { selected: /* @__PURE__ */ new Set([id]), anchor: id, focused: id };
|
|
513
|
+
}
|
|
514
|
+
__name(selectionFromClick, "selectionFromClick");
|
|
515
|
+
function selectionFromMove(state, rows, nextFocusedId, extend, multi) {
|
|
516
|
+
if (!multi || !extend) {
|
|
517
|
+
return { selected: /* @__PURE__ */ new Set([nextFocusedId]), anchor: nextFocusedId, focused: nextFocusedId };
|
|
518
|
+
}
|
|
519
|
+
const anchor = state.anchor ?? state.focused ?? nextFocusedId;
|
|
520
|
+
const range = computeRange(rows, anchor, nextFocusedId);
|
|
521
|
+
return {
|
|
522
|
+
selected: new Set(range.length > 0 ? range : [nextFocusedId]),
|
|
523
|
+
anchor,
|
|
524
|
+
focused: nextFocusedId
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
__name(selectionFromMove, "selectionFromMove");
|
|
528
|
+
function selectionSelectAll(rows, focused) {
|
|
529
|
+
if (rows.length === 0) {
|
|
530
|
+
return { selected: /* @__PURE__ */ new Set(), anchor: null, focused };
|
|
531
|
+
}
|
|
532
|
+
const ids = rows.map((r) => r.node.id);
|
|
533
|
+
return {
|
|
534
|
+
selected: new Set(ids),
|
|
535
|
+
anchor: ids[0],
|
|
536
|
+
focused: focused ?? ids[0]
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
__name(selectionSelectAll, "selectionSelectAll");
|
|
540
|
+
|
|
541
|
+
// src/tools/data/Tree/context/selection/use-selection.ts
|
|
542
|
+
function useSelection({
|
|
543
|
+
dispatch,
|
|
544
|
+
selectionMode,
|
|
545
|
+
flatRows,
|
|
546
|
+
selected,
|
|
547
|
+
anchor,
|
|
548
|
+
focused
|
|
549
|
+
}) {
|
|
550
|
+
const flatRowsRef = useRef(flatRows);
|
|
551
|
+
flatRowsRef.current = flatRows;
|
|
552
|
+
const selectionRef = useRef({ selected, anchor, focused });
|
|
553
|
+
selectionRef.current = { selected, anchor, focused };
|
|
554
|
+
const select = useCallback(
|
|
555
|
+
(id) => dispatch({ type: "select", id, mode: selectionMode }),
|
|
556
|
+
[dispatch, selectionMode]
|
|
557
|
+
);
|
|
558
|
+
const setSelectedIds = useCallback(
|
|
559
|
+
(ids) => dispatch({ type: "select-many", ids }),
|
|
560
|
+
[dispatch]
|
|
561
|
+
);
|
|
562
|
+
const clearSelection = useCallback(
|
|
563
|
+
() => dispatch({ type: "clear-selection" }),
|
|
564
|
+
[dispatch]
|
|
565
|
+
);
|
|
566
|
+
const setFocus = useCallback(
|
|
567
|
+
(id) => dispatch({ type: "focus", id }),
|
|
568
|
+
[dispatch]
|
|
569
|
+
);
|
|
570
|
+
const clickSelect = useCallback(
|
|
571
|
+
(id, mods) => {
|
|
572
|
+
if (selectionMode === "none") return;
|
|
573
|
+
if (selectionMode === "single") {
|
|
574
|
+
dispatch({ type: "select", id, mode: "single" });
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const next = selectionFromClick(
|
|
578
|
+
selectionRef.current,
|
|
579
|
+
flatRowsRef.current,
|
|
580
|
+
id,
|
|
581
|
+
mods,
|
|
582
|
+
true
|
|
583
|
+
);
|
|
584
|
+
dispatch({
|
|
585
|
+
type: "selection-replace",
|
|
586
|
+
selected: [...next.selected],
|
|
587
|
+
anchor: next.anchor,
|
|
588
|
+
focused: next.focused
|
|
589
|
+
});
|
|
590
|
+
},
|
|
591
|
+
[dispatch, selectionMode]
|
|
592
|
+
);
|
|
593
|
+
const moveSelect = useCallback(
|
|
594
|
+
(id, opts) => {
|
|
595
|
+
if (selectionMode !== "multiple" || !opts.extend) {
|
|
596
|
+
dispatch({ type: "focus", id });
|
|
597
|
+
return;
|
|
398
598
|
}
|
|
599
|
+
const next = selectionFromMove(
|
|
600
|
+
selectionRef.current,
|
|
601
|
+
flatRowsRef.current,
|
|
602
|
+
id,
|
|
603
|
+
true,
|
|
604
|
+
true
|
|
605
|
+
);
|
|
606
|
+
dispatch({
|
|
607
|
+
type: "selection-replace",
|
|
608
|
+
selected: [...next.selected],
|
|
609
|
+
anchor: next.anchor,
|
|
610
|
+
focused: next.focused
|
|
611
|
+
});
|
|
612
|
+
},
|
|
613
|
+
[dispatch, selectionMode]
|
|
614
|
+
);
|
|
615
|
+
const selectAll = useCallback(() => {
|
|
616
|
+
if (selectionMode !== "multiple") return;
|
|
617
|
+
const next = selectionSelectAll(
|
|
618
|
+
flatRowsRef.current,
|
|
619
|
+
selectionRef.current.focused
|
|
620
|
+
);
|
|
621
|
+
dispatch({
|
|
622
|
+
type: "selection-replace",
|
|
623
|
+
selected: [...next.selected],
|
|
624
|
+
anchor: next.anchor,
|
|
625
|
+
focused: next.focused
|
|
626
|
+
});
|
|
627
|
+
}, [dispatch, selectionMode]);
|
|
628
|
+
return {
|
|
629
|
+
select,
|
|
630
|
+
setSelectedIds,
|
|
631
|
+
clearSelection,
|
|
632
|
+
clickSelect,
|
|
633
|
+
moveSelect,
|
|
634
|
+
selectAll,
|
|
635
|
+
setFocus
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
__name(useSelection, "useSelection");
|
|
639
|
+
function useRename({
|
|
640
|
+
dispatch,
|
|
641
|
+
adapter,
|
|
642
|
+
enableInlineRename,
|
|
643
|
+
nodeById,
|
|
644
|
+
getItemName,
|
|
645
|
+
labels
|
|
646
|
+
}) {
|
|
647
|
+
const enabled = enableInlineRename && !!adapter?.rename;
|
|
648
|
+
const startRename = useCallback(
|
|
649
|
+
(id) => {
|
|
650
|
+
if (!enableInlineRename) return;
|
|
651
|
+
if (!adapter?.rename) {
|
|
652
|
+
if (process.env.NODE_ENV !== "production") {
|
|
653
|
+
console.warn(
|
|
654
|
+
"[Tree] startRename called but adapter.rename is not defined."
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
dispatch({ type: "start-rename", id });
|
|
660
|
+
},
|
|
661
|
+
[dispatch, enableInlineRename, adapter]
|
|
662
|
+
);
|
|
663
|
+
const cancelRename = useCallback(
|
|
664
|
+
() => dispatch({ type: "stop-rename" }),
|
|
665
|
+
[dispatch]
|
|
666
|
+
);
|
|
667
|
+
const commitRename = useCallback(
|
|
668
|
+
async (id, nextName) => {
|
|
669
|
+
if (!adapter?.rename) {
|
|
670
|
+
dispatch({ type: "stop-rename" });
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
const node = nodeById.get(id);
|
|
674
|
+
if (!node) {
|
|
675
|
+
dispatch({ type: "stop-rename" });
|
|
676
|
+
return false;
|
|
677
|
+
}
|
|
678
|
+
const trimmed = nextName.trim();
|
|
679
|
+
if (trimmed === getItemName(node)) {
|
|
680
|
+
dispatch({ type: "stop-rename" });
|
|
681
|
+
return true;
|
|
682
|
+
}
|
|
683
|
+
let err = null;
|
|
684
|
+
if (trimmed === "") err = labels.invalidNameEmpty;
|
|
685
|
+
else err = adapter.validateName?.(trimmed, { node }) ?? null;
|
|
686
|
+
if (err) {
|
|
687
|
+
const dialog = getDialog();
|
|
688
|
+
await dialog?.alert({ title: labels.error, message: err });
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
try {
|
|
692
|
+
await adapter.rename(node, trimmed);
|
|
693
|
+
} catch (e) {
|
|
694
|
+
const dialog = getDialog();
|
|
695
|
+
await dialog?.alert({
|
|
696
|
+
title: labels.error,
|
|
697
|
+
message: e instanceof Error ? e.message : String(e)
|
|
698
|
+
});
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
dispatch({ type: "stop-rename" });
|
|
702
|
+
return true;
|
|
703
|
+
},
|
|
704
|
+
[dispatch, adapter, nodeById, getItemName, labels]
|
|
705
|
+
);
|
|
706
|
+
return { enabled, startRename, cancelRename, commitRename };
|
|
707
|
+
}
|
|
708
|
+
__name(useRename, "useRename");
|
|
709
|
+
function useClipboard({
|
|
710
|
+
dispatch,
|
|
711
|
+
clipboard,
|
|
712
|
+
adapter,
|
|
713
|
+
nodeById,
|
|
714
|
+
labels
|
|
715
|
+
}) {
|
|
716
|
+
const clipboardRef = useRef(clipboard);
|
|
717
|
+
clipboardRef.current = clipboard;
|
|
718
|
+
const cutToClipboard = useCallback(
|
|
719
|
+
(ids) => {
|
|
720
|
+
if (ids.length === 0) return;
|
|
721
|
+
dispatch({ type: "clipboard-set", payload: { kind: "cut", ids } });
|
|
722
|
+
},
|
|
723
|
+
[dispatch]
|
|
724
|
+
);
|
|
725
|
+
const copyToClipboard = useCallback(
|
|
726
|
+
(ids) => {
|
|
727
|
+
if (ids.length === 0) return;
|
|
728
|
+
dispatch({ type: "clipboard-set", payload: { kind: "copy", ids } });
|
|
729
|
+
},
|
|
730
|
+
[dispatch]
|
|
731
|
+
);
|
|
732
|
+
const clearClipboard = useCallback(
|
|
733
|
+
() => dispatch({ type: "clipboard-set", payload: null }),
|
|
734
|
+
[dispatch]
|
|
735
|
+
);
|
|
736
|
+
const pasteFromClipboard = useCallback(
|
|
737
|
+
async (target, position = "inside") => {
|
|
738
|
+
const cb = clipboardRef.current;
|
|
739
|
+
if (!cb || cb.ids.length === 0) return;
|
|
740
|
+
if (!adapter) return;
|
|
741
|
+
const nodes = cb.ids.map((id) => nodeById.get(id)).filter((n) => !!n);
|
|
742
|
+
if (nodes.length === 0) {
|
|
743
|
+
dispatch({ type: "clipboard-set", payload: null });
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
try {
|
|
747
|
+
if (cb.kind === "cut") {
|
|
748
|
+
if (!adapter.move) return;
|
|
749
|
+
await adapter.move(nodes, target, position);
|
|
750
|
+
dispatch({ type: "clipboard-set", payload: null });
|
|
751
|
+
} else {
|
|
752
|
+
if (!adapter.copy) return;
|
|
753
|
+
await adapter.copy(nodes, target, position);
|
|
754
|
+
}
|
|
755
|
+
} catch (e) {
|
|
756
|
+
const dialog = getDialog();
|
|
757
|
+
await dialog?.alert({
|
|
758
|
+
title: labels.error,
|
|
759
|
+
message: e instanceof Error ? e.message : String(e)
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
[dispatch, adapter, nodeById, labels]
|
|
764
|
+
);
|
|
765
|
+
return {
|
|
766
|
+
cutToClipboard,
|
|
767
|
+
copyToClipboard,
|
|
768
|
+
pasteFromClipboard,
|
|
769
|
+
clearClipboard
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
__name(useClipboard, "useClipboard");
|
|
773
|
+
async function confirmDelete(ctx) {
|
|
774
|
+
const dialog = getDialog();
|
|
775
|
+
if (!dialog) return false;
|
|
776
|
+
const { selectedNodes, labels, getName } = ctx;
|
|
777
|
+
return dialog.confirm({
|
|
778
|
+
title: labels.confirmDeleteTitle(selectedNodes.length),
|
|
779
|
+
message: labels.confirmDeleteMessage(selectedNodes.map(getName)),
|
|
780
|
+
confirmText: labels.confirmDeleteOk,
|
|
781
|
+
cancelText: labels.confirmDeleteCancel,
|
|
782
|
+
variant: "destructive"
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
__name(confirmDelete, "confirmDelete");
|
|
786
|
+
async function promptName(ctx, spec) {
|
|
787
|
+
const dialog = getDialog();
|
|
788
|
+
if (!dialog) return null;
|
|
789
|
+
return dialog.prompt({
|
|
790
|
+
title: spec.title,
|
|
791
|
+
message: spec.message,
|
|
792
|
+
placeholder: spec.placeholder,
|
|
793
|
+
defaultValue: spec.defaultValue
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
__name(promptName, "promptName");
|
|
797
|
+
async function alertError(ctx, message) {
|
|
798
|
+
const dialog = getDialog();
|
|
799
|
+
if (!dialog) return;
|
|
800
|
+
await dialog.alert({ message, title: ctx.labels.error });
|
|
801
|
+
}
|
|
802
|
+
__name(alertError, "alertError");
|
|
803
|
+
function validateName(ctx, name, validateCtx) {
|
|
804
|
+
if (name.trim() === "") return ctx.labels.invalidNameEmpty;
|
|
805
|
+
return ctx.adapter.validateName?.(name, validateCtx) ?? null;
|
|
806
|
+
}
|
|
807
|
+
__name(validateName, "validateName");
|
|
808
|
+
var BUILTIN_ACTIONS = [
|
|
809
|
+
{
|
|
810
|
+
id: "open",
|
|
811
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionOpen, "label"),
|
|
812
|
+
icon: CornerUpLeft,
|
|
813
|
+
available: /* @__PURE__ */ __name(() => false, "available"),
|
|
814
|
+
// wired by Tree on activate; not in menu by default
|
|
815
|
+
run: /* @__PURE__ */ __name(() => {
|
|
816
|
+
}, "run")
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
id: "rename",
|
|
820
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionRename, "label"),
|
|
821
|
+
icon: Pencil,
|
|
822
|
+
shortcut: "F2",
|
|
823
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.adapter.rename && ctx.selectedNodes.length === 1 && !ctx.selectedNodes[0].disabled, "available"),
|
|
824
|
+
run: /* @__PURE__ */ __name(async (ctx) => {
|
|
825
|
+
const node = ctx.selectedNodes[0];
|
|
826
|
+
if (ctx.startInlineRename) {
|
|
827
|
+
ctx.startInlineRename(node.id);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
const name = await promptName(ctx, {
|
|
831
|
+
title: ctx.labels.renameTitle,
|
|
832
|
+
message: ctx.labels.renameMessage,
|
|
833
|
+
placeholder: ctx.getName(node),
|
|
834
|
+
defaultValue: ctx.getName(node)
|
|
835
|
+
});
|
|
836
|
+
if (name === null) return;
|
|
837
|
+
const err = validateName(ctx, name, { node });
|
|
838
|
+
if (err) return alertError(ctx, err);
|
|
839
|
+
await ctx.adapter.rename(node, name);
|
|
840
|
+
}, "run")
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
id: "duplicate",
|
|
844
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionDuplicate, "label"),
|
|
845
|
+
icon: Copy,
|
|
846
|
+
shortcut: "\u2318D",
|
|
847
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.adapter.duplicate && ctx.selectedNodes.length > 0, "available"),
|
|
848
|
+
run: /* @__PURE__ */ __name((ctx) => ctx.adapter.duplicate(ctx.selectedNodes), "run")
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
id: "cut",
|
|
852
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionCut, "label"),
|
|
853
|
+
icon: Scissors,
|
|
854
|
+
shortcut: "\u2318X",
|
|
855
|
+
// Only meaningful when the adapter supports `move` (paste-after-cut)
|
|
856
|
+
// AND Tree provided a clipboard binding.
|
|
857
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.adapter.move && !!ctx.clipboard && ctx.selectedNodes.length > 0, "available"),
|
|
858
|
+
run: /* @__PURE__ */ __name((ctx) => {
|
|
859
|
+
ctx.clipboard?.cut(ctx.selectedNodes.map((n) => n.id));
|
|
860
|
+
}, "run")
|
|
861
|
+
},
|
|
862
|
+
{
|
|
863
|
+
id: "copy",
|
|
864
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionCopy, "label"),
|
|
865
|
+
icon: Copy,
|
|
866
|
+
shortcut: "\u2318C",
|
|
867
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.adapter.copy && !!ctx.clipboard && ctx.selectedNodes.length > 0, "available"),
|
|
868
|
+
run: /* @__PURE__ */ __name((ctx) => {
|
|
869
|
+
ctx.clipboard?.copy(ctx.selectedNodes.map((n) => n.id));
|
|
870
|
+
}, "run")
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
id: "paste",
|
|
874
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionPaste, "label"),
|
|
875
|
+
icon: CornerUpLeft,
|
|
876
|
+
shortcut: "\u2318V",
|
|
877
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.clipboard?.hasItems, "available"),
|
|
878
|
+
run: /* @__PURE__ */ __name(async (ctx) => {
|
|
879
|
+
await ctx.clipboard?.paste();
|
|
880
|
+
}, "run")
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
id: "delete",
|
|
884
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionDelete, "label"),
|
|
885
|
+
icon: Trash2,
|
|
886
|
+
shortcut: "\u2318\u232B",
|
|
887
|
+
destructive: true,
|
|
888
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.adapter.remove && ctx.selectedNodes.length > 0, "available"),
|
|
889
|
+
run: /* @__PURE__ */ __name(async (ctx) => {
|
|
890
|
+
const ok = await confirmDelete(ctx);
|
|
891
|
+
if (!ok) return;
|
|
892
|
+
await ctx.adapter.remove(ctx.selectedNodes);
|
|
893
|
+
}, "run")
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
id: "new-file",
|
|
897
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionNewFile, "label"),
|
|
898
|
+
icon: FilePlus,
|
|
899
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.adapter.createFile, "available"),
|
|
900
|
+
run: /* @__PURE__ */ __name(async (ctx) => {
|
|
901
|
+
const parent = resolveParentForCreate(ctx);
|
|
902
|
+
const name = await promptName(ctx, {
|
|
903
|
+
title: ctx.labels.newFileTitle,
|
|
904
|
+
message: ctx.labels.newFileMessage,
|
|
905
|
+
placeholder: ctx.labels.newFilePlaceholder,
|
|
906
|
+
defaultValue: ctx.labels.newFileDefault
|
|
907
|
+
});
|
|
908
|
+
if (name === null) return;
|
|
909
|
+
const err = validateName(ctx, name, { parent });
|
|
910
|
+
if (err) return alertError(ctx, err);
|
|
911
|
+
await ctx.adapter.createFile(parent, name);
|
|
912
|
+
}, "run")
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
id: "new-folder",
|
|
916
|
+
label: /* @__PURE__ */ __name((ctx) => ctx.labels.actionNewFolder, "label"),
|
|
917
|
+
icon: FolderPlus,
|
|
918
|
+
shortcut: "\u2318\u21E7N",
|
|
919
|
+
available: /* @__PURE__ */ __name((ctx) => !!ctx.adapter.createFolder, "available"),
|
|
920
|
+
run: /* @__PURE__ */ __name(async (ctx) => {
|
|
921
|
+
const parent = resolveParentForCreate(ctx);
|
|
922
|
+
const name = await promptName(ctx, {
|
|
923
|
+
title: ctx.labels.newFolderTitle,
|
|
924
|
+
message: ctx.labels.newFolderMessage,
|
|
925
|
+
placeholder: ctx.labels.newFolderPlaceholder,
|
|
926
|
+
defaultValue: ctx.labels.newFolderDefault
|
|
927
|
+
});
|
|
928
|
+
if (name === null) return;
|
|
929
|
+
const err = validateName(ctx, name, { parent });
|
|
930
|
+
if (err) return alertError(ctx, err);
|
|
931
|
+
await ctx.adapter.createFolder(parent, name);
|
|
932
|
+
}, "run")
|
|
933
|
+
}
|
|
934
|
+
];
|
|
935
|
+
var DEFAULT_BUILTIN_MENU_ORDER = [
|
|
936
|
+
"rename",
|
|
937
|
+
"duplicate",
|
|
938
|
+
"separator",
|
|
939
|
+
"cut",
|
|
940
|
+
"copy",
|
|
941
|
+
"paste",
|
|
942
|
+
"separator",
|
|
943
|
+
"new-file",
|
|
944
|
+
"new-folder",
|
|
945
|
+
"separator",
|
|
946
|
+
"delete"
|
|
947
|
+
];
|
|
948
|
+
function resolveParentForCreate(ctx) {
|
|
949
|
+
const { targetNode } = ctx;
|
|
950
|
+
if (!targetNode) return null;
|
|
951
|
+
const isFolder = Array.isArray(targetNode.children) || !!targetNode.isFolder;
|
|
952
|
+
return isFolder ? targetNode : null;
|
|
953
|
+
}
|
|
954
|
+
__name(resolveParentForCreate, "resolveParentForCreate");
|
|
955
|
+
function buildDefaultMenuItems(ctx, order = DEFAULT_BUILTIN_MENU_ORDER) {
|
|
956
|
+
const items = [];
|
|
957
|
+
let pendingSeparator = false;
|
|
958
|
+
for (const entry of order) {
|
|
959
|
+
if (entry === "separator") {
|
|
960
|
+
pendingSeparator = items.length > 0;
|
|
961
|
+
continue;
|
|
399
962
|
}
|
|
400
|
-
|
|
401
|
-
|
|
963
|
+
const desc = BUILTIN_ACTIONS.find((a) => a.id === entry);
|
|
964
|
+
if (!desc) continue;
|
|
965
|
+
if (!desc.available(ctx)) continue;
|
|
966
|
+
if (pendingSeparator) {
|
|
967
|
+
items.push("separator");
|
|
968
|
+
pendingSeparator = false;
|
|
969
|
+
}
|
|
970
|
+
items.push({
|
|
971
|
+
id: desc.id,
|
|
972
|
+
label: desc.label(ctx),
|
|
973
|
+
icon: desc.icon,
|
|
974
|
+
shortcut: desc.shortcut,
|
|
975
|
+
destructive: desc.destructive,
|
|
976
|
+
onSelect: /* @__PURE__ */ __name(() => void desc.run(ctx), "onSelect")
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
return items.length > 0 ? items : null;
|
|
980
|
+
}
|
|
981
|
+
__name(buildDefaultMenuItems, "buildDefaultMenuItems");
|
|
982
|
+
async function runBuiltinAction(id, ctx) {
|
|
983
|
+
const desc = BUILTIN_ACTIONS.find((a) => a.id === id);
|
|
984
|
+
if (!desc) return false;
|
|
985
|
+
if (!desc.available(ctx)) return false;
|
|
986
|
+
await desc.run(ctx);
|
|
987
|
+
return true;
|
|
988
|
+
}
|
|
989
|
+
__name(runBuiltinAction, "runBuiltinAction");
|
|
990
|
+
function useResolvedMenu(opts) {
|
|
991
|
+
const {
|
|
992
|
+
adapter,
|
|
993
|
+
contextMenuActions,
|
|
994
|
+
defaultMenuItems,
|
|
995
|
+
labels,
|
|
996
|
+
selected,
|
|
997
|
+
clipboard,
|
|
998
|
+
nodeById,
|
|
999
|
+
getItemName,
|
|
1000
|
+
enableInlineRename,
|
|
1001
|
+
startRename,
|
|
1002
|
+
cutToClipboard,
|
|
1003
|
+
copyToClipboard,
|
|
1004
|
+
pasteFromClipboard
|
|
1005
|
+
} = opts;
|
|
1006
|
+
return useMemo(() => {
|
|
1007
|
+
if (!adapter && !contextMenuActions) return void 0;
|
|
1008
|
+
return (rowProps) => {
|
|
1009
|
+
const selectedIds = selected.has(rowProps.node.id) ? [...selected] : [rowProps.node.id];
|
|
1010
|
+
const selectedNodes = selectedIds.map((id) => nodeById.get(id)).filter((n) => !!n);
|
|
1011
|
+
const builtin = adapter ? buildDefaultMenuItems(
|
|
1012
|
+
{
|
|
1013
|
+
adapter,
|
|
1014
|
+
labels,
|
|
1015
|
+
selectedNodes,
|
|
1016
|
+
targetNode: rowProps.node,
|
|
1017
|
+
getName: getItemName,
|
|
1018
|
+
startInlineRename: enableInlineRename && adapter.rename ? startRename : void 0,
|
|
1019
|
+
clipboard: {
|
|
1020
|
+
hasItems: !!clipboard && clipboard.ids.length > 0,
|
|
1021
|
+
cut: cutToClipboard,
|
|
1022
|
+
copy: copyToClipboard,
|
|
1023
|
+
paste: /* @__PURE__ */ __name(() => pasteFromClipboard(rowProps.node, "inside"), "paste")
|
|
1024
|
+
}
|
|
1025
|
+
},
|
|
1026
|
+
defaultMenuItems ? defaultMenuItems : void 0
|
|
1027
|
+
) : null;
|
|
1028
|
+
const user = contextMenuActions?.({ ...rowProps, selectedNodes }) ?? null;
|
|
1029
|
+
if (!builtin && !user) return null;
|
|
1030
|
+
if (!user) return builtin;
|
|
1031
|
+
if (!builtin) return user;
|
|
1032
|
+
return [...builtin, "separator", ...user];
|
|
1033
|
+
};
|
|
1034
|
+
}, [
|
|
1035
|
+
adapter,
|
|
1036
|
+
contextMenuActions,
|
|
1037
|
+
defaultMenuItems,
|
|
1038
|
+
labels,
|
|
1039
|
+
selected,
|
|
1040
|
+
clipboard,
|
|
1041
|
+
nodeById,
|
|
1042
|
+
getItemName,
|
|
1043
|
+
enableInlineRename,
|
|
1044
|
+
startRename,
|
|
1045
|
+
cutToClipboard,
|
|
1046
|
+
copyToClipboard,
|
|
1047
|
+
pasteFromClipboard
|
|
1048
|
+
]);
|
|
1049
|
+
}
|
|
1050
|
+
__name(useResolvedMenu, "useResolvedMenu");
|
|
1051
|
+
function renderItemsAsContextMenu(rowProps, items, trigger) {
|
|
1052
|
+
return /* @__PURE__ */ jsxs(ContextMenu, { children: [
|
|
1053
|
+
/* @__PURE__ */ jsx(ContextMenuTrigger, { asChild: true, children: trigger }),
|
|
1054
|
+
/* @__PURE__ */ jsx(ContextMenuContent, { children: items.map((item, idx) => {
|
|
1055
|
+
if (item === "separator") {
|
|
1056
|
+
return /* @__PURE__ */ jsx(ContextMenuSeparator, {}, `sep-${idx}`);
|
|
1057
|
+
}
|
|
1058
|
+
const Icon = item.icon;
|
|
1059
|
+
return /* @__PURE__ */ jsxs(
|
|
1060
|
+
ContextMenuItem,
|
|
1061
|
+
{
|
|
1062
|
+
disabled: item.disabled,
|
|
1063
|
+
variant: item.destructive ? "destructive" : void 0,
|
|
1064
|
+
onSelect: () => item.onSelect(rowProps),
|
|
1065
|
+
children: [
|
|
1066
|
+
Icon ? /* @__PURE__ */ jsx(Icon, {}) : null,
|
|
1067
|
+
item.label,
|
|
1068
|
+
item.shortcut ? /* @__PURE__ */ jsx(ContextMenuShortcut, { children: item.shortcut }) : null
|
|
1069
|
+
]
|
|
1070
|
+
},
|
|
1071
|
+
item.id
|
|
1072
|
+
);
|
|
1073
|
+
}) })
|
|
1074
|
+
] });
|
|
1075
|
+
}
|
|
1076
|
+
__name(renderItemsAsContextMenu, "renderItemsAsContextMenu");
|
|
1077
|
+
function tidyMenuItems(items) {
|
|
1078
|
+
const out = [];
|
|
1079
|
+
for (const it of items) {
|
|
1080
|
+
if (it === "separator") {
|
|
1081
|
+
if (out.length === 0) continue;
|
|
1082
|
+
if (out[out.length - 1] === "separator") continue;
|
|
1083
|
+
out.push(it);
|
|
1084
|
+
} else {
|
|
1085
|
+
out.push(it);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
while (out.length > 0 && out[out.length - 1] === "separator") out.pop();
|
|
1089
|
+
return out;
|
|
1090
|
+
}
|
|
1091
|
+
__name(tidyMenuItems, "tidyMenuItems");
|
|
1092
|
+
|
|
1093
|
+
// src/tools/data/Tree/data/dnd.ts
|
|
1094
|
+
function resolveDropZone(input) {
|
|
1095
|
+
const { pointerY, rowRect, isFolder } = input;
|
|
1096
|
+
const offset = pointerY - rowRect.top;
|
|
1097
|
+
const ratio = rowRect.height > 0 ? offset / rowRect.height : 0.5;
|
|
1098
|
+
if (isFolder) {
|
|
1099
|
+
if (ratio < 0.33) return "before";
|
|
1100
|
+
if (ratio > 0.66) return "after";
|
|
1101
|
+
return "inside";
|
|
1102
|
+
}
|
|
1103
|
+
return ratio < 0.5 ? "before" : "after";
|
|
1104
|
+
}
|
|
1105
|
+
__name(resolveDropZone, "resolveDropZone");
|
|
1106
|
+
function defaultCanDrop(input) {
|
|
1107
|
+
const { source, target, position } = input;
|
|
1108
|
+
if (source.length === 0) return false;
|
|
1109
|
+
if (!target) return true;
|
|
1110
|
+
if (position === "inside") {
|
|
1111
|
+
const isFolder = Array.isArray(target.children) || !!target.isFolder;
|
|
1112
|
+
if (!isFolder) return false;
|
|
1113
|
+
}
|
|
1114
|
+
for (const node of source) {
|
|
1115
|
+
if (node.id === target.id) return false;
|
|
1116
|
+
if (isDescendant(node, target.id)) return false;
|
|
1117
|
+
}
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1120
|
+
__name(defaultCanDrop, "defaultCanDrop");
|
|
1121
|
+
function isDescendant(root, id) {
|
|
1122
|
+
if (!Array.isArray(root.children)) return false;
|
|
1123
|
+
for (const child of root.children) {
|
|
1124
|
+
if (child.id === id) return true;
|
|
1125
|
+
if (isDescendant(child, id)) return true;
|
|
1126
|
+
}
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
__name(isDescendant, "isDescendant");
|
|
1130
|
+
var TREE_DND_MIME = "application/x-djangocfg-tree";
|
|
1131
|
+
var TREE_ROOT_DROP_ID = "__tree_root_drop__";
|
|
1132
|
+
|
|
1133
|
+
// src/tools/data/Tree/context/dnd/use-dnd.ts
|
|
1134
|
+
function useDnd({
|
|
1135
|
+
enabled,
|
|
1136
|
+
adapter,
|
|
1137
|
+
nodeById,
|
|
1138
|
+
selected,
|
|
1139
|
+
labels,
|
|
1140
|
+
canDrop
|
|
1141
|
+
}) {
|
|
1142
|
+
const active = enabled && !!adapter?.move;
|
|
1143
|
+
const [draggingIds, setDraggingIds] = useState(
|
|
1144
|
+
() => /* @__PURE__ */ new Set()
|
|
1145
|
+
);
|
|
1146
|
+
const [dropTarget, setDropTarget] = useState(null);
|
|
1147
|
+
const beginDrag = useCallback(
|
|
1148
|
+
(rowId) => {
|
|
1149
|
+
if (!active) return;
|
|
1150
|
+
const ids = selected.has(rowId) ? new Set(selected) : /* @__PURE__ */ new Set([rowId]);
|
|
1151
|
+
setDraggingIds(ids);
|
|
1152
|
+
},
|
|
1153
|
+
[active, selected]
|
|
1154
|
+
);
|
|
1155
|
+
const cancelDrag = useCallback(() => {
|
|
1156
|
+
setDraggingIds(/* @__PURE__ */ new Set());
|
|
1157
|
+
setDropTarget(null);
|
|
1158
|
+
}, []);
|
|
1159
|
+
const resolveSourceNodes = useCallback(() => {
|
|
1160
|
+
const out = [];
|
|
1161
|
+
for (const id of draggingIds) {
|
|
1162
|
+
const node = nodeById.get(id);
|
|
1163
|
+
if (node) out.push(node);
|
|
1164
|
+
}
|
|
1165
|
+
return out;
|
|
1166
|
+
}, [draggingIds, nodeById]);
|
|
1167
|
+
const isAllowedDrop = useCallback(
|
|
1168
|
+
(target, position) => {
|
|
1169
|
+
if (!active) return false;
|
|
1170
|
+
if (draggingIds.size === 0) return false;
|
|
1171
|
+
const source = resolveSourceNodes();
|
|
1172
|
+
if (!defaultCanDrop({
|
|
1173
|
+
source,
|
|
1174
|
+
target,
|
|
1175
|
+
position})) {
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
return canDrop?.({ source, target, position }) ?? true;
|
|
1179
|
+
},
|
|
1180
|
+
[active, draggingIds, resolveSourceNodes, nodeById, canDrop]
|
|
1181
|
+
);
|
|
1182
|
+
const commitDrop = useCallback(async () => {
|
|
1183
|
+
if (!active || !adapter?.move) {
|
|
1184
|
+
cancelDrag();
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
const t = dropTarget;
|
|
1188
|
+
if (!t) {
|
|
1189
|
+
cancelDrag();
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const targetNode = t.id ? nodeById.get(t.id) ?? null : null;
|
|
1193
|
+
const source = resolveSourceNodes();
|
|
1194
|
+
if (source.length === 0) {
|
|
1195
|
+
cancelDrag();
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (!isAllowedDrop(targetNode, t.position)) {
|
|
1199
|
+
cancelDrag();
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
try {
|
|
1203
|
+
await adapter.move(source, targetNode, t.position);
|
|
1204
|
+
} catch (e) {
|
|
1205
|
+
const dialog = getDialog();
|
|
1206
|
+
await dialog?.alert({
|
|
1207
|
+
title: labels.error,
|
|
1208
|
+
message: e instanceof Error ? e.message : String(e)
|
|
1209
|
+
});
|
|
1210
|
+
} finally {
|
|
1211
|
+
cancelDrag();
|
|
1212
|
+
}
|
|
1213
|
+
}, [
|
|
1214
|
+
active,
|
|
1215
|
+
adapter,
|
|
1216
|
+
cancelDrag,
|
|
1217
|
+
dropTarget,
|
|
1218
|
+
isAllowedDrop,
|
|
1219
|
+
labels,
|
|
1220
|
+
nodeById,
|
|
1221
|
+
resolveSourceNodes
|
|
1222
|
+
]);
|
|
1223
|
+
return useMemo(
|
|
1224
|
+
() => ({
|
|
1225
|
+
active,
|
|
1226
|
+
draggingIds,
|
|
1227
|
+
dropTarget,
|
|
1228
|
+
beginDrag,
|
|
1229
|
+
setDropTarget,
|
|
1230
|
+
commitDrop,
|
|
1231
|
+
cancelDrag,
|
|
1232
|
+
isAllowedDrop
|
|
1233
|
+
}),
|
|
1234
|
+
[
|
|
1235
|
+
active,
|
|
1236
|
+
draggingIds,
|
|
1237
|
+
dropTarget,
|
|
1238
|
+
beginDrag,
|
|
1239
|
+
commitDrop,
|
|
1240
|
+
cancelDrag,
|
|
1241
|
+
isAllowedDrop
|
|
1242
|
+
]
|
|
1243
|
+
);
|
|
1244
|
+
}
|
|
1245
|
+
__name(useDnd, "useDnd");
|
|
1246
|
+
function usePersistSync({
|
|
1247
|
+
expanded,
|
|
1248
|
+
selected,
|
|
1249
|
+
persistKey,
|
|
1250
|
+
persistSelection,
|
|
1251
|
+
onSelectionChange,
|
|
1252
|
+
onExpansionChange
|
|
1253
|
+
}) {
|
|
402
1254
|
const onSelectionChangeRef = useRef(onSelectionChange);
|
|
403
1255
|
const onExpansionChangeRef = useRef(onExpansionChange);
|
|
404
|
-
const onActivateRef = useRef(onActivate);
|
|
405
1256
|
onSelectionChangeRef.current = onSelectionChange;
|
|
406
1257
|
onExpansionChangeRef.current = onExpansionChange;
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
const lastExpandedArrRef = useRef([...state.expanded]);
|
|
1258
|
+
const lastSelectedArrRef = useRef([...selected]);
|
|
1259
|
+
const lastExpandedArrRef = useRef([...expanded]);
|
|
410
1260
|
useEffect(() => {
|
|
411
|
-
const arr = [...
|
|
412
|
-
if (!setEqualsArr(
|
|
1261
|
+
const arr = [...expanded];
|
|
1262
|
+
if (!setEqualsArr(expanded, lastExpandedArrRef.current)) {
|
|
413
1263
|
lastExpandedArrRef.current = arr;
|
|
414
1264
|
onExpansionChangeRef.current?.(arr);
|
|
415
1265
|
if (persistKey) {
|
|
416
1266
|
saveTreeState(persistKey, {
|
|
417
1267
|
expandedItems: arr,
|
|
418
|
-
selectedItems: persistSelection ? [...
|
|
1268
|
+
selectedItems: persistSelection ? [...selected] : []
|
|
419
1269
|
});
|
|
420
1270
|
}
|
|
421
1271
|
}
|
|
422
|
-
}, [
|
|
1272
|
+
}, [expanded, persistKey, persistSelection, selected]);
|
|
423
1273
|
useEffect(() => {
|
|
424
|
-
const arr = [...
|
|
425
|
-
if (!setEqualsArr(
|
|
1274
|
+
const arr = [...selected];
|
|
1275
|
+
if (!setEqualsArr(selected, lastSelectedArrRef.current)) {
|
|
426
1276
|
lastSelectedArrRef.current = arr;
|
|
427
1277
|
onSelectionChangeRef.current?.(arr);
|
|
428
1278
|
if (persistKey && persistSelection) {
|
|
429
1279
|
saveTreeState(persistKey, {
|
|
430
|
-
expandedItems: [...
|
|
1280
|
+
expandedItems: [...expanded],
|
|
431
1281
|
selectedItems: arr
|
|
432
1282
|
});
|
|
433
1283
|
}
|
|
434
1284
|
}
|
|
435
|
-
}, [
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1285
|
+
}, [selected, persistKey, persistSelection, expanded]);
|
|
1286
|
+
}
|
|
1287
|
+
__name(usePersistSync, "usePersistSync");
|
|
1288
|
+
function setEqualsArr(set, arr) {
|
|
1289
|
+
if (set.size !== arr.length) return false;
|
|
1290
|
+
for (const id of arr) if (!set.has(id)) return false;
|
|
1291
|
+
return true;
|
|
1292
|
+
}
|
|
1293
|
+
__name(setEqualsArr, "setEqualsArr");
|
|
1294
|
+
var TreeContext = createContext(null);
|
|
1295
|
+
function useTreeContext() {
|
|
1296
|
+
const ctx = React.useContext(TreeContext);
|
|
1297
|
+
if (!ctx) {
|
|
1298
|
+
throw new Error("useTreeContext must be used inside <TreeProvider>");
|
|
1299
|
+
}
|
|
1300
|
+
return ctx;
|
|
1301
|
+
}
|
|
1302
|
+
__name(useTreeContext, "useTreeContext");
|
|
1303
|
+
function TreeProvider(props) {
|
|
1304
|
+
const {
|
|
1305
|
+
data,
|
|
1306
|
+
getItemName,
|
|
1307
|
+
loadChildren,
|
|
1308
|
+
selectionMode = "single",
|
|
1309
|
+
activationMode = "single-click",
|
|
1310
|
+
initialExpandedIds,
|
|
1311
|
+
initialSelectedIds,
|
|
1312
|
+
indent,
|
|
1313
|
+
appearance,
|
|
1314
|
+
onSelectionChange,
|
|
1315
|
+
onExpansionChange,
|
|
1316
|
+
onActivate,
|
|
1317
|
+
filterNode,
|
|
1318
|
+
enableSearch = false,
|
|
1319
|
+
showIndentGuides = false,
|
|
1320
|
+
renderIcon,
|
|
1321
|
+
renderLabel,
|
|
1322
|
+
renderActions,
|
|
1323
|
+
renderContextMenu,
|
|
1324
|
+
contextMenuActions,
|
|
1325
|
+
labels: labelsOverride,
|
|
1326
|
+
persistKey,
|
|
1327
|
+
persistSelection = false,
|
|
1328
|
+
adapter,
|
|
1329
|
+
defaultMenuItems,
|
|
1330
|
+
enableInlineRename = false,
|
|
1331
|
+
enableDnD = false,
|
|
1332
|
+
canDrop,
|
|
1333
|
+
children
|
|
1334
|
+
} = props;
|
|
1335
|
+
const labels = useMemo(
|
|
1336
|
+
() => ({ ...DEFAULT_TREE_LABELS, ...labelsOverride }),
|
|
1337
|
+
[labelsOverride]
|
|
1338
|
+
);
|
|
1339
|
+
const resolvedAppearance = useMemo(
|
|
1340
|
+
() => resolveAppearance(appearance, indent),
|
|
1341
|
+
[appearance, indent]
|
|
1342
|
+
);
|
|
1343
|
+
const persisted = useMemo(
|
|
1344
|
+
() => persistKey ? loadTreeState(persistKey) : null,
|
|
1345
|
+
[persistKey]
|
|
1346
|
+
);
|
|
1347
|
+
const [state, dispatch] = useReducer(
|
|
1348
|
+
reducer,
|
|
1349
|
+
void 0,
|
|
1350
|
+
() => createInitialState({
|
|
1351
|
+
persisted,
|
|
1352
|
+
initialExpandedIds,
|
|
1353
|
+
initialSelectedIds,
|
|
1354
|
+
persistSelection
|
|
1355
|
+
})
|
|
1356
|
+
);
|
|
1357
|
+
const bumpCacheTick = useCallback(() => dispatch({ type: "cache-tick" }), []);
|
|
1358
|
+
const {
|
|
1359
|
+
nodeById,
|
|
1360
|
+
refresh,
|
|
1361
|
+
refreshAll,
|
|
1362
|
+
collectFolderIds,
|
|
1363
|
+
cache
|
|
1364
|
+
} = useAsyncChildren({
|
|
1365
|
+
data,
|
|
1366
|
+
loadChildren,
|
|
1367
|
+
expanded: state.expanded,
|
|
1368
|
+
cacheTick: state.cacheTick,
|
|
1369
|
+
bumpCacheTick
|
|
1370
|
+
});
|
|
1371
|
+
const flatRows = useMemo(
|
|
1372
|
+
() => flattenTree({
|
|
1373
|
+
roots: data,
|
|
1374
|
+
expandedIds: state.expanded,
|
|
1375
|
+
cache,
|
|
1376
|
+
filterNode
|
|
1377
|
+
}),
|
|
1378
|
+
[data, state.expanded, state.cacheTick, cache, filterNode]
|
|
1379
|
+
);
|
|
1380
|
+
const matchingIds = useMemo(() => {
|
|
1381
|
+
const set = /* @__PURE__ */ new Set();
|
|
1382
|
+
if (!enableSearch || state.query.trim() === "") return set;
|
|
1383
|
+
const q = state.query.trim().toLowerCase();
|
|
1384
|
+
for (const row of flatRows) {
|
|
1385
|
+
if (getItemName(row.node).toLowerCase().includes(q)) {
|
|
1386
|
+
set.add(row.node.id);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return set;
|
|
1390
|
+
}, [enableSearch, state.query, flatRows, getItemName]);
|
|
1391
|
+
const expansion = useExpansion({ dispatch, collectFolderIds });
|
|
1392
|
+
const selection = useSelection({
|
|
1393
|
+
dispatch,
|
|
1394
|
+
selectionMode,
|
|
1395
|
+
flatRows,
|
|
1396
|
+
selected: state.selected,
|
|
1397
|
+
anchor: state.anchor,
|
|
1398
|
+
focused: state.focused
|
|
1399
|
+
});
|
|
1400
|
+
const rename = useRename({
|
|
1401
|
+
dispatch,
|
|
1402
|
+
adapter,
|
|
1403
|
+
enableInlineRename,
|
|
1404
|
+
nodeById,
|
|
1405
|
+
getItemName,
|
|
1406
|
+
labels
|
|
1407
|
+
});
|
|
1408
|
+
const clipboard = useClipboard({
|
|
1409
|
+
dispatch,
|
|
1410
|
+
clipboard: state.clipboard,
|
|
1411
|
+
adapter,
|
|
1412
|
+
nodeById,
|
|
1413
|
+
labels
|
|
1414
|
+
});
|
|
1415
|
+
const dnd = useDnd({
|
|
1416
|
+
enabled: enableDnD,
|
|
1417
|
+
adapter,
|
|
1418
|
+
nodeById,
|
|
1419
|
+
selected: state.selected,
|
|
1420
|
+
labels,
|
|
1421
|
+
canDrop
|
|
1422
|
+
});
|
|
1423
|
+
const onActivateRef = useRef(onActivate);
|
|
1424
|
+
onActivateRef.current = onActivate;
|
|
1425
|
+
const activate = useCallback(
|
|
1426
|
+
(node, opts = { preview: false }) => onActivateRef.current?.(node, opts),
|
|
454
1427
|
[]
|
|
455
1428
|
);
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
(id) => dispatch({ type: "focus", id }),
|
|
1429
|
+
const setQuery = useCallback(
|
|
1430
|
+
(q) => dispatch({ type: "set-query", q }),
|
|
459
1431
|
[]
|
|
460
1432
|
);
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
1433
|
+
usePersistSync({
|
|
1434
|
+
expanded: state.expanded,
|
|
1435
|
+
selected: state.selected,
|
|
1436
|
+
persistKey,
|
|
1437
|
+
persistSelection,
|
|
1438
|
+
onSelectionChange,
|
|
1439
|
+
onExpansionChange
|
|
1440
|
+
});
|
|
1441
|
+
const resolvedContextMenuActions = useResolvedMenu({
|
|
1442
|
+
adapter,
|
|
1443
|
+
contextMenuActions,
|
|
1444
|
+
defaultMenuItems,
|
|
1445
|
+
labels,
|
|
1446
|
+
selected: state.selected,
|
|
1447
|
+
clipboard: state.clipboard,
|
|
1448
|
+
nodeById,
|
|
1449
|
+
getItemName,
|
|
1450
|
+
enableInlineRename,
|
|
1451
|
+
startRename: rename.startRename,
|
|
1452
|
+
cutToClipboard: clipboard.cutToClipboard,
|
|
1453
|
+
copyToClipboard: clipboard.copyToClipboard,
|
|
1454
|
+
pasteFromClipboard: clipboard.pasteFromClipboard
|
|
1455
|
+
});
|
|
1456
|
+
const finalRenderContextMenu = useMemo(
|
|
1457
|
+
() => {
|
|
1458
|
+
if (renderContextMenu) return renderContextMenu;
|
|
1459
|
+
const resolve = resolvedContextMenuActions;
|
|
1460
|
+
if (!resolve) return void 0;
|
|
1461
|
+
return (rowProps, trigger) => {
|
|
1462
|
+
const items = resolve(rowProps);
|
|
1463
|
+
const cleaned = items ? tidyMenuItems(items) : null;
|
|
1464
|
+
if (!cleaned || cleaned.length === 0) return trigger;
|
|
1465
|
+
return renderItemsAsContextMenu(rowProps, cleaned, trigger);
|
|
1466
|
+
};
|
|
469
1467
|
},
|
|
470
|
-
[
|
|
471
|
-
);
|
|
472
|
-
const refreshAll = useCallback(async () => {
|
|
473
|
-
cacheRef.current.clear();
|
|
474
|
-
dispatch({ type: "cache-tick" });
|
|
475
|
-
if (!loadChildren) return;
|
|
476
|
-
await Promise.all(
|
|
477
|
-
[...state.expanded].map((id) => {
|
|
478
|
-
const node = nodeById.get(id);
|
|
479
|
-
return node ? fetchChildren(node) : void 0;
|
|
480
|
-
})
|
|
481
|
-
);
|
|
482
|
-
}, [loadChildren, state.expanded, nodeById, fetchChildren]);
|
|
483
|
-
const activate = useCallback(
|
|
484
|
-
(node, opts = { preview: false }) => onActivateRef.current?.(node, opts),
|
|
485
|
-
[]
|
|
1468
|
+
[renderContextMenu, resolvedContextMenuActions]
|
|
486
1469
|
);
|
|
487
1470
|
const value = useMemo(
|
|
488
1471
|
() => ({
|
|
1472
|
+
// state
|
|
489
1473
|
expanded: state.expanded,
|
|
490
1474
|
selected: state.selected,
|
|
1475
|
+
anchor: state.anchor,
|
|
491
1476
|
focused: state.focused,
|
|
492
1477
|
query: state.query,
|
|
1478
|
+
renamingId: state.renaming,
|
|
1479
|
+
inlineRenameEnabled: rename.enabled,
|
|
1480
|
+
clipboard: state.clipboard,
|
|
493
1481
|
flatRows,
|
|
494
1482
|
matchingIds,
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
collapseAll,
|
|
500
|
-
select,
|
|
501
|
-
setSelectedIds,
|
|
502
|
-
clearSelection,
|
|
503
|
-
setFocus,
|
|
1483
|
+
// expansion
|
|
1484
|
+
...expansion,
|
|
1485
|
+
// selection (note: `select` is from selection hook; expansion exports no `select`)
|
|
1486
|
+
...selection,
|
|
504
1487
|
setQuery,
|
|
1488
|
+
// clipboard
|
|
1489
|
+
...clipboard,
|
|
1490
|
+
// rename
|
|
1491
|
+
startRename: rename.startRename,
|
|
1492
|
+
cancelRename: rename.cancelRename,
|
|
1493
|
+
commitRename: rename.commitRename,
|
|
1494
|
+
// async
|
|
505
1495
|
refresh,
|
|
506
1496
|
refreshAll,
|
|
507
1497
|
activate,
|
|
1498
|
+
// config
|
|
508
1499
|
labels,
|
|
509
1500
|
appearance: resolvedAppearance,
|
|
510
1501
|
indent: resolvedAppearance.indent,
|
|
@@ -513,28 +1504,34 @@ function TreeProvider(props) {
|
|
|
513
1504
|
enableSearch,
|
|
514
1505
|
showIndentGuides,
|
|
515
1506
|
getItemName,
|
|
1507
|
+
// slots
|
|
516
1508
|
renderIcon,
|
|
517
1509
|
renderLabel,
|
|
518
1510
|
renderActions,
|
|
519
|
-
renderContextMenu
|
|
1511
|
+
renderContextMenu: finalRenderContextMenu,
|
|
1512
|
+
adapter,
|
|
1513
|
+
resolvedContextMenuActions,
|
|
1514
|
+
getNodeById: /* @__PURE__ */ __name((id) => nodeById.get(id), "getNodeById"),
|
|
1515
|
+
dnd
|
|
520
1516
|
}),
|
|
521
1517
|
[
|
|
522
1518
|
state.expanded,
|
|
523
1519
|
state.selected,
|
|
1520
|
+
state.anchor,
|
|
524
1521
|
state.focused,
|
|
525
1522
|
state.query,
|
|
1523
|
+
state.renaming,
|
|
1524
|
+
state.clipboard,
|
|
1525
|
+
rename.enabled,
|
|
1526
|
+
rename.startRename,
|
|
1527
|
+
rename.cancelRename,
|
|
1528
|
+
rename.commitRename,
|
|
526
1529
|
flatRows,
|
|
527
1530
|
matchingIds,
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
toggle,
|
|
531
|
-
expandAll,
|
|
532
|
-
collapseAll,
|
|
533
|
-
select,
|
|
534
|
-
setSelectedIds,
|
|
535
|
-
clearSelection,
|
|
536
|
-
setFocus,
|
|
1531
|
+
expansion,
|
|
1532
|
+
selection,
|
|
537
1533
|
setQuery,
|
|
1534
|
+
clipboard,
|
|
538
1535
|
refresh,
|
|
539
1536
|
refreshAll,
|
|
540
1537
|
activate,
|
|
@@ -548,12 +1545,98 @@ function TreeProvider(props) {
|
|
|
548
1545
|
renderIcon,
|
|
549
1546
|
renderLabel,
|
|
550
1547
|
renderActions,
|
|
551
|
-
|
|
1548
|
+
finalRenderContextMenu,
|
|
1549
|
+
adapter,
|
|
1550
|
+
resolvedContextMenuActions,
|
|
1551
|
+
nodeById,
|
|
1552
|
+
dnd
|
|
552
1553
|
]
|
|
553
1554
|
);
|
|
554
1555
|
return /* @__PURE__ */ jsx(TreeContext.Provider, { value, children });
|
|
555
1556
|
}
|
|
556
1557
|
__name(TreeProvider, "TreeProvider");
|
|
1558
|
+
function TreeDndProvider({ children }) {
|
|
1559
|
+
const ctx = useTreeContext();
|
|
1560
|
+
const { dnd } = ctx;
|
|
1561
|
+
const sensors = useSensors(
|
|
1562
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
1563
|
+
useSensor(KeyboardSensor)
|
|
1564
|
+
);
|
|
1565
|
+
const cursorYRef = useRef(0);
|
|
1566
|
+
const handleDragStart = useCallback(
|
|
1567
|
+
(e) => {
|
|
1568
|
+
dnd.beginDrag(e.active.id);
|
|
1569
|
+
},
|
|
1570
|
+
[dnd]
|
|
1571
|
+
);
|
|
1572
|
+
const handleDragMove = useCallback(
|
|
1573
|
+
(e) => {
|
|
1574
|
+
const overId = e.over?.id;
|
|
1575
|
+
if (overId === TREE_ROOT_DROP_ID) {
|
|
1576
|
+
const current2 = dnd.dropTarget;
|
|
1577
|
+
if (current2?.id !== null || current2?.position !== "inside") {
|
|
1578
|
+
dnd.setDropTarget({ id: null, position: "inside" });
|
|
1579
|
+
}
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
if (typeof overId !== "string") {
|
|
1583
|
+
if (dnd.dropTarget !== null) dnd.setDropTarget(null);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
const rect = e.over?.rect;
|
|
1587
|
+
if (!rect) return;
|
|
1588
|
+
const el = document.querySelector(
|
|
1589
|
+
`[data-tree-row][data-id="${CSS.escape(overId)}"]`
|
|
1590
|
+
);
|
|
1591
|
+
const isFolder = el?.dataset.folder === "true";
|
|
1592
|
+
const position = resolveDropZone({
|
|
1593
|
+
pointerY: cursorYRef.current,
|
|
1594
|
+
rowRect: { top: rect.top, bottom: rect.top + rect.height, height: rect.height },
|
|
1595
|
+
isFolder
|
|
1596
|
+
});
|
|
1597
|
+
const current = dnd.dropTarget;
|
|
1598
|
+
if (current?.id !== overId || current.position !== position) {
|
|
1599
|
+
dnd.setDropTarget({ id: overId, position });
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
[dnd]
|
|
1603
|
+
);
|
|
1604
|
+
const handleDragEnd = useCallback(
|
|
1605
|
+
async (_e) => {
|
|
1606
|
+
await dnd.commitDrop();
|
|
1607
|
+
},
|
|
1608
|
+
[dnd]
|
|
1609
|
+
);
|
|
1610
|
+
const handleDragCancel = useCallback(() => {
|
|
1611
|
+
dnd.cancelDrag();
|
|
1612
|
+
}, [dnd]);
|
|
1613
|
+
const handlePointerMove = useCallback((e) => {
|
|
1614
|
+
cursorYRef.current = e.clientY;
|
|
1615
|
+
}, []);
|
|
1616
|
+
if (!dnd.active) {
|
|
1617
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
1618
|
+
}
|
|
1619
|
+
return /* @__PURE__ */ jsx(
|
|
1620
|
+
DndContext,
|
|
1621
|
+
{
|
|
1622
|
+
sensors,
|
|
1623
|
+
onDragStart: handleDragStart,
|
|
1624
|
+
onDragMove: handleDragMove,
|
|
1625
|
+
onDragEnd: handleDragEnd,
|
|
1626
|
+
onDragCancel: handleDragCancel,
|
|
1627
|
+
children: /* @__PURE__ */ jsx(
|
|
1628
|
+
"div",
|
|
1629
|
+
{
|
|
1630
|
+
onPointerMove: handlePointerMove,
|
|
1631
|
+
className: "contents",
|
|
1632
|
+
"data-tree-dnd-surface": "",
|
|
1633
|
+
children
|
|
1634
|
+
}
|
|
1635
|
+
)
|
|
1636
|
+
}
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
__name(TreeDndProvider, "TreeDndProvider");
|
|
557
1640
|
function TreeChevronRaw({ isExpanded, isFolder, className }) {
|
|
558
1641
|
const { appearance } = useTreeContext();
|
|
559
1642
|
const size = { width: "var(--tree-icon-size)", height: "var(--tree-icon-size)" };
|
|
@@ -583,6 +1666,48 @@ function TreeChevronRaw({ isExpanded, isFolder, className }) {
|
|
|
583
1666
|
}
|
|
584
1667
|
__name(TreeChevronRaw, "TreeChevronRaw");
|
|
585
1668
|
var TreeChevron = memo(TreeChevronRaw);
|
|
1669
|
+
function TreeDropIndicator({
|
|
1670
|
+
position,
|
|
1671
|
+
indent,
|
|
1672
|
+
invalid = false
|
|
1673
|
+
}) {
|
|
1674
|
+
if (position === "inside") {
|
|
1675
|
+
return /* @__PURE__ */ jsx(
|
|
1676
|
+
"span",
|
|
1677
|
+
{
|
|
1678
|
+
"aria-hidden": true,
|
|
1679
|
+
"data-tree-drop": "inside",
|
|
1680
|
+
className: cn(
|
|
1681
|
+
"pointer-events-none absolute inset-0 rounded-sm ring-1",
|
|
1682
|
+
invalid ? "bg-destructive/10 ring-destructive/40" : "bg-primary/10 ring-primary/40"
|
|
1683
|
+
)
|
|
1684
|
+
}
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
const isBefore = position === "before";
|
|
1688
|
+
return /* @__PURE__ */ jsx(
|
|
1689
|
+
"span",
|
|
1690
|
+
{
|
|
1691
|
+
"aria-hidden": true,
|
|
1692
|
+
"data-tree-drop": position,
|
|
1693
|
+
style: { paddingLeft: indent },
|
|
1694
|
+
className: cn(
|
|
1695
|
+
"pointer-events-none absolute right-0 left-0 h-px",
|
|
1696
|
+
isBefore ? "top-0" : "bottom-0"
|
|
1697
|
+
),
|
|
1698
|
+
children: /* @__PURE__ */ jsx(
|
|
1699
|
+
"span",
|
|
1700
|
+
{
|
|
1701
|
+
className: cn(
|
|
1702
|
+
"block h-0.5 rounded-full",
|
|
1703
|
+
invalid ? "bg-destructive/70" : "bg-primary"
|
|
1704
|
+
)
|
|
1705
|
+
}
|
|
1706
|
+
)
|
|
1707
|
+
}
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
__name(TreeDropIndicator, "TreeDropIndicator");
|
|
586
1711
|
function TreeIconRaw({ isFolder, isExpanded, className }) {
|
|
587
1712
|
const { appearance } = useTreeContext();
|
|
588
1713
|
const Icon = isFolder ? isExpanded ? FolderOpen : Folder : File;
|
|
@@ -639,6 +1764,96 @@ function TreeLabelRaw({ children, isMatchingSearch, className }) {
|
|
|
639
1764
|
}
|
|
640
1765
|
__name(TreeLabelRaw, "TreeLabelRaw");
|
|
641
1766
|
var TreeLabel = memo(TreeLabelRaw);
|
|
1767
|
+
|
|
1768
|
+
// src/tools/data/Tree/data/renameUtils.ts
|
|
1769
|
+
function splitFileName(name) {
|
|
1770
|
+
if (name.length === 0) return { base: "", ext: "" };
|
|
1771
|
+
if (name.startsWith(".")) {
|
|
1772
|
+
const rest = name.slice(1);
|
|
1773
|
+
const dot2 = rest.lastIndexOf(".");
|
|
1774
|
+
if (dot2 < 0) return { base: name, ext: "" };
|
|
1775
|
+
return { base: "." + rest.slice(0, dot2), ext: rest.slice(dot2) };
|
|
1776
|
+
}
|
|
1777
|
+
const dot = name.lastIndexOf(".");
|
|
1778
|
+
if (dot <= 0) return { base: name, ext: "" };
|
|
1779
|
+
return { base: name.slice(0, dot), ext: name.slice(dot) };
|
|
1780
|
+
}
|
|
1781
|
+
__name(splitFileName, "splitFileName");
|
|
1782
|
+
function autoSelectRange(name, isFolder) {
|
|
1783
|
+
if (isFolder) return [0, name.length];
|
|
1784
|
+
const { base } = splitFileName(name);
|
|
1785
|
+
return [0, base.length];
|
|
1786
|
+
}
|
|
1787
|
+
__name(autoSelectRange, "autoSelectRange");
|
|
1788
|
+
function TreeRenameInput({
|
|
1789
|
+
initialValue,
|
|
1790
|
+
isFolder,
|
|
1791
|
+
onCommit,
|
|
1792
|
+
onCancel,
|
|
1793
|
+
className
|
|
1794
|
+
}) {
|
|
1795
|
+
const [value, setValue] = useState(initialValue);
|
|
1796
|
+
const inputRef = useRef(null);
|
|
1797
|
+
const settledRef = useRef(false);
|
|
1798
|
+
useEffect(() => {
|
|
1799
|
+
const el = inputRef.current;
|
|
1800
|
+
if (!el) return;
|
|
1801
|
+
el.focus();
|
|
1802
|
+
const [start, end] = autoSelectRange(initialValue, isFolder);
|
|
1803
|
+
requestAnimationFrame(() => {
|
|
1804
|
+
try {
|
|
1805
|
+
el.setSelectionRange(start, end);
|
|
1806
|
+
} catch {
|
|
1807
|
+
}
|
|
1808
|
+
});
|
|
1809
|
+
}, []);
|
|
1810
|
+
const commit = /* @__PURE__ */ __name(() => {
|
|
1811
|
+
if (settledRef.current) return;
|
|
1812
|
+
settledRef.current = true;
|
|
1813
|
+
void onCommit(value);
|
|
1814
|
+
}, "commit");
|
|
1815
|
+
const cancel = /* @__PURE__ */ __name(() => {
|
|
1816
|
+
if (settledRef.current) return;
|
|
1817
|
+
settledRef.current = true;
|
|
1818
|
+
onCancel();
|
|
1819
|
+
}, "cancel");
|
|
1820
|
+
return /* @__PURE__ */ jsx(
|
|
1821
|
+
"input",
|
|
1822
|
+
{
|
|
1823
|
+
ref: inputRef,
|
|
1824
|
+
type: "text",
|
|
1825
|
+
value,
|
|
1826
|
+
onKeyDown: (e) => {
|
|
1827
|
+
e.stopPropagation();
|
|
1828
|
+
if (e.key === "Enter") {
|
|
1829
|
+
e.preventDefault();
|
|
1830
|
+
commit();
|
|
1831
|
+
} else if (e.key === "Escape") {
|
|
1832
|
+
e.preventDefault();
|
|
1833
|
+
cancel();
|
|
1834
|
+
}
|
|
1835
|
+
},
|
|
1836
|
+
onChange: (e) => setValue(e.target.value),
|
|
1837
|
+
onBlur: commit,
|
|
1838
|
+
onClick: (e) => e.stopPropagation(),
|
|
1839
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
1840
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1841
|
+
onContextMenu: (e) => e.stopPropagation(),
|
|
1842
|
+
className: cn(
|
|
1843
|
+
"min-w-0 flex-1 rounded-sm border border-primary/50 bg-background",
|
|
1844
|
+
"px-1 py-0 text-foreground outline-none",
|
|
1845
|
+
"focus:ring-1 focus:ring-primary/40",
|
|
1846
|
+
className
|
|
1847
|
+
),
|
|
1848
|
+
style: {
|
|
1849
|
+
// Match the row's font metrics so the input doesn't visibly jolt.
|
|
1850
|
+
fontSize: "var(--tree-font-size)",
|
|
1851
|
+
height: "calc(var(--tree-row-height) - 4px)"
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
__name(TreeRenameInput, "TreeRenameInput");
|
|
642
1857
|
function TreeRowRaw({ row, className }) {
|
|
643
1858
|
const ctx = useTreeContext();
|
|
644
1859
|
const {
|
|
@@ -650,6 +1865,7 @@ function TreeRowRaw({ row, className }) {
|
|
|
650
1865
|
matchingIds,
|
|
651
1866
|
select,
|
|
652
1867
|
setSelectedIds,
|
|
1868
|
+
clickSelect,
|
|
653
1869
|
toggle,
|
|
654
1870
|
setFocus,
|
|
655
1871
|
activate,
|
|
@@ -657,13 +1873,19 @@ function TreeRowRaw({ row, className }) {
|
|
|
657
1873
|
renderIcon,
|
|
658
1874
|
renderLabel,
|
|
659
1875
|
renderActions,
|
|
660
|
-
renderContextMenu
|
|
1876
|
+
renderContextMenu,
|
|
1877
|
+
renamingId,
|
|
1878
|
+
commitRename,
|
|
1879
|
+
cancelRename,
|
|
1880
|
+
clipboard,
|
|
1881
|
+
dnd
|
|
661
1882
|
} = ctx;
|
|
662
1883
|
const { node, level, isFolder, isExpanded, isLoading, posInSet, setSize } = row;
|
|
663
1884
|
const isSelected = selected.has(node.id);
|
|
664
1885
|
const isFocused = focused === node.id;
|
|
665
1886
|
const isMatchingSearch = matchingIds.has(node.id);
|
|
666
1887
|
const isMultiSelect = ctx.selectionMode === "multiple";
|
|
1888
|
+
const isCut = clipboard?.kind === "cut" && clipboard.ids.includes(node.id);
|
|
667
1889
|
const slot = {
|
|
668
1890
|
node,
|
|
669
1891
|
level,
|
|
@@ -674,30 +1896,57 @@ function TreeRowRaw({ row, className }) {
|
|
|
674
1896
|
isLoading,
|
|
675
1897
|
isMatchingSearch
|
|
676
1898
|
};
|
|
1899
|
+
const isRenaming = renamingId === node.id;
|
|
1900
|
+
const isDragging = dnd.draggingIds.has(node.id);
|
|
1901
|
+
const dropTarget = dnd.dropTarget;
|
|
1902
|
+
const isDropTarget = dropTarget?.id === node.id;
|
|
1903
|
+
const dropPosition = isDropTarget ? dropTarget.position : null;
|
|
1904
|
+
const dndDisabled = !dnd.active || isRenaming || node.disabled;
|
|
1905
|
+
const draggable = useDraggable({ id: node.id, disabled: dndDisabled });
|
|
1906
|
+
const droppable = useDroppable({ id: node.id, disabled: dndDisabled });
|
|
1907
|
+
const setRowEl = useCallback(
|
|
1908
|
+
(el) => {
|
|
1909
|
+
draggable.setNodeRef(el);
|
|
1910
|
+
droppable.setNodeRef(el);
|
|
1911
|
+
},
|
|
1912
|
+
[draggable, droppable]
|
|
1913
|
+
);
|
|
1914
|
+
const isAllowedDrop = dropPosition && !isDragging ? dnd.isAllowedDrop(node, dropPosition) : true;
|
|
677
1915
|
const handleClick = /* @__PURE__ */ __name((e) => {
|
|
678
|
-
if (node.disabled) return;
|
|
1916
|
+
if (node.disabled || isRenaming) return;
|
|
679
1917
|
setFocus(node.id);
|
|
680
|
-
if (isMultiSelect
|
|
681
|
-
|
|
1918
|
+
if (isMultiSelect) {
|
|
1919
|
+
clickSelect(node.id, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
|
|
682
1920
|
} else {
|
|
683
1921
|
select(node.id);
|
|
684
1922
|
}
|
|
685
1923
|
if (isFolder) {
|
|
1924
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) return;
|
|
686
1925
|
toggle(node.id);
|
|
687
1926
|
} else if (activationMode === "single-click") {
|
|
1927
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) return;
|
|
688
1928
|
activate(node, { preview: false });
|
|
689
1929
|
} else if (activationMode === "single-click-preview") {
|
|
1930
|
+
if (e.shiftKey || e.metaKey || e.ctrlKey) return;
|
|
690
1931
|
activate(node, { preview: true });
|
|
691
1932
|
}
|
|
692
1933
|
}, "handleClick");
|
|
693
1934
|
const handleDoubleClick = /* @__PURE__ */ __name(() => {
|
|
694
|
-
if (node.disabled) return;
|
|
1935
|
+
if (node.disabled || isRenaming) return;
|
|
695
1936
|
if (isFolder) return;
|
|
696
1937
|
activate(node, { preview: false });
|
|
697
1938
|
}, "handleDoubleClick");
|
|
1939
|
+
const handleContextMenu = /* @__PURE__ */ __name(() => {
|
|
1940
|
+
if (node.disabled || isRenaming) return;
|
|
1941
|
+
setFocus(node.id);
|
|
1942
|
+
if (!isSelected) {
|
|
1943
|
+
setSelectedIds([node.id]);
|
|
1944
|
+
}
|
|
1945
|
+
}, "handleContextMenu");
|
|
698
1946
|
const trigger = /* @__PURE__ */ jsxs(
|
|
699
1947
|
"div",
|
|
700
1948
|
{
|
|
1949
|
+
ref: dnd.active ? setRowEl : void 0,
|
|
701
1950
|
id: treeRowDomId(node.id),
|
|
702
1951
|
role: "treeitem",
|
|
703
1952
|
"aria-level": level + 1,
|
|
@@ -710,6 +1959,8 @@ function TreeRowRaw({ row, className }) {
|
|
|
710
1959
|
"data-id": node.id,
|
|
711
1960
|
"data-activation-mode": activationMode,
|
|
712
1961
|
"data-selected": isSelected ? "true" : void 0,
|
|
1962
|
+
"data-clipboard": isCut ? "cut" : void 0,
|
|
1963
|
+
"data-dragging": isDragging ? "true" : void 0,
|
|
713
1964
|
"data-focused": isFocused && !isSelected ? "true" : void 0,
|
|
714
1965
|
"data-folder": isFolder || void 0,
|
|
715
1966
|
"data-expanded": isExpanded || void 0,
|
|
@@ -719,8 +1970,14 @@ function TreeRowRaw({ row, className }) {
|
|
|
719
1970
|
height: "var(--tree-row-height)",
|
|
720
1971
|
gap: "var(--tree-gap)"
|
|
721
1972
|
},
|
|
1973
|
+
...dnd.active ? draggable.listeners : {},
|
|
1974
|
+
...dnd.active ? draggable.attributes : {},
|
|
1975
|
+
onMouseDown: (e) => {
|
|
1976
|
+
if (e.button === 0) e.preventDefault();
|
|
1977
|
+
},
|
|
722
1978
|
onClick: handleClick,
|
|
723
1979
|
onDoubleClick: handleDoubleClick,
|
|
1980
|
+
onContextMenu: handleContextMenu,
|
|
724
1981
|
onFocus: () => setFocus(node.id),
|
|
725
1982
|
className: cn(
|
|
726
1983
|
"group/row relative flex w-full select-none items-center pr-2 text-left",
|
|
@@ -730,6 +1987,8 @@ function TreeRowRaw({ row, className }) {
|
|
|
730
1987
|
rowStateClasses(appearance),
|
|
731
1988
|
"focus-visible:ring-1 focus-visible:ring-ring/50",
|
|
732
1989
|
isMatchingSearch && "ring-1 ring-primary/30",
|
|
1990
|
+
isCut && "opacity-60",
|
|
1991
|
+
isDragging && "opacity-40",
|
|
733
1992
|
node.disabled && "opacity-50",
|
|
734
1993
|
className
|
|
735
1994
|
),
|
|
@@ -744,6 +2003,14 @@ function TreeRowRaw({ row, className }) {
|
|
|
744
2003
|
)
|
|
745
2004
|
}
|
|
746
2005
|
) : null,
|
|
2006
|
+
dropPosition && !isDragging ? /* @__PURE__ */ jsx(
|
|
2007
|
+
TreeDropIndicator,
|
|
2008
|
+
{
|
|
2009
|
+
position: dropPosition,
|
|
2010
|
+
indent: 6 + level * appearance.indent,
|
|
2011
|
+
invalid: !isAllowedDrop
|
|
2012
|
+
}
|
|
2013
|
+
) : null,
|
|
747
2014
|
showIndentGuides && level > 0 ? /* @__PURE__ */ jsx(TreeIndentGuides, { level, indent: appearance.indent }) : null,
|
|
748
2015
|
/* @__PURE__ */ jsx(TreeChevron, { isExpanded, isFolder }),
|
|
749
2016
|
isLoading ? /* @__PURE__ */ jsx(
|
|
@@ -760,7 +2027,15 @@ function TreeRowRaw({ row, className }) {
|
|
|
760
2027
|
{
|
|
761
2028
|
className: "flex min-w-0 flex-1 items-center",
|
|
762
2029
|
style: { gap: "var(--tree-gap)" },
|
|
763
|
-
children:
|
|
2030
|
+
children: renamingId === node.id ? /* @__PURE__ */ jsx(
|
|
2031
|
+
TreeRenameInput,
|
|
2032
|
+
{
|
|
2033
|
+
initialValue: getItemName(node),
|
|
2034
|
+
isFolder,
|
|
2035
|
+
onCommit: (next) => commitRename(node.id, next),
|
|
2036
|
+
onCancel: cancelRename
|
|
2037
|
+
}
|
|
2038
|
+
) : renderLabel ? renderLabel(slot) : /* @__PURE__ */ jsx(TreeLabel, { isMatchingSearch, children: getItemName(node) })
|
|
764
2039
|
}
|
|
765
2040
|
),
|
|
766
2041
|
renderActions ? /* @__PURE__ */ jsx(
|
|
@@ -832,6 +2107,105 @@ function TreeContent({
|
|
|
832
2107
|
);
|
|
833
2108
|
}
|
|
834
2109
|
__name(TreeContent, "TreeContent");
|
|
2110
|
+
function TreeEmptyArea({ className }) {
|
|
2111
|
+
const ctx = useTreeContext();
|
|
2112
|
+
const {
|
|
2113
|
+
adapter,
|
|
2114
|
+
labels,
|
|
2115
|
+
getItemName,
|
|
2116
|
+
clipboard,
|
|
2117
|
+
cutToClipboard,
|
|
2118
|
+
copyToClipboard,
|
|
2119
|
+
pasteFromClipboard,
|
|
2120
|
+
startRename,
|
|
2121
|
+
inlineRenameEnabled,
|
|
2122
|
+
dnd
|
|
2123
|
+
} = ctx;
|
|
2124
|
+
const { setNodeRef: setDroppableRef, isOver } = useDroppable({
|
|
2125
|
+
id: TREE_ROOT_DROP_ID,
|
|
2126
|
+
disabled: !dnd.active
|
|
2127
|
+
});
|
|
2128
|
+
const items = useMemo(() => {
|
|
2129
|
+
if (!adapter) return null;
|
|
2130
|
+
const builtinCtx = {
|
|
2131
|
+
adapter,
|
|
2132
|
+
labels,
|
|
2133
|
+
selectedNodes: [],
|
|
2134
|
+
targetNode: null,
|
|
2135
|
+
getName: getItemName,
|
|
2136
|
+
startInlineRename: inlineRenameEnabled ? startRename : void 0,
|
|
2137
|
+
clipboard: {
|
|
2138
|
+
hasItems: !!clipboard && clipboard.ids.length > 0,
|
|
2139
|
+
cut: cutToClipboard,
|
|
2140
|
+
copy: copyToClipboard,
|
|
2141
|
+
paste: /* @__PURE__ */ __name(() => pasteFromClipboard(null, "inside"), "paste")
|
|
2142
|
+
}
|
|
2143
|
+
};
|
|
2144
|
+
return buildDefaultMenuItems(builtinCtx);
|
|
2145
|
+
}, [
|
|
2146
|
+
adapter,
|
|
2147
|
+
labels,
|
|
2148
|
+
getItemName,
|
|
2149
|
+
inlineRenameEnabled,
|
|
2150
|
+
startRename,
|
|
2151
|
+
clipboard,
|
|
2152
|
+
cutToClipboard,
|
|
2153
|
+
copyToClipboard,
|
|
2154
|
+
pasteFromClipboard
|
|
2155
|
+
]);
|
|
2156
|
+
const surface = /* @__PURE__ */ jsx(
|
|
2157
|
+
"div",
|
|
2158
|
+
{
|
|
2159
|
+
ref: dnd.active ? setDroppableRef : void 0,
|
|
2160
|
+
"data-tree-empty-area": "",
|
|
2161
|
+
"data-drop-active": dnd.active && isOver ? "true" : void 0,
|
|
2162
|
+
className: cn(
|
|
2163
|
+
// Soaks up the remaining vertical space inside the scroll
|
|
2164
|
+
// container so right-click on whitespace lands here, not on
|
|
2165
|
+
// the scroll viewport.
|
|
2166
|
+
"min-h-[2.5rem] flex-1",
|
|
2167
|
+
dnd.active && isOver && "bg-primary/5 rounded-sm ring-1 ring-primary/30",
|
|
2168
|
+
className
|
|
2169
|
+
)
|
|
2170
|
+
}
|
|
2171
|
+
);
|
|
2172
|
+
if (!items || items.length === 0) {
|
|
2173
|
+
return surface;
|
|
2174
|
+
}
|
|
2175
|
+
return /* @__PURE__ */ jsxs(ContextMenu, { children: [
|
|
2176
|
+
/* @__PURE__ */ jsx(ContextMenuTrigger, { asChild: true, children: surface }),
|
|
2177
|
+
/* @__PURE__ */ jsx(ContextMenuContent, { children: items.map((item, idx) => {
|
|
2178
|
+
if (item === "separator") {
|
|
2179
|
+
return /* @__PURE__ */ jsx(ContextMenuSeparator, {}, `sep-${idx}`);
|
|
2180
|
+
}
|
|
2181
|
+
const Icon = item.icon;
|
|
2182
|
+
return /* @__PURE__ */ jsxs(
|
|
2183
|
+
ContextMenuItem,
|
|
2184
|
+
{
|
|
2185
|
+
disabled: item.disabled,
|
|
2186
|
+
variant: item.destructive ? "destructive" : void 0,
|
|
2187
|
+
onSelect: () => item.onSelect({
|
|
2188
|
+
node: void 0,
|
|
2189
|
+
level: 0,
|
|
2190
|
+
isSelected: false,
|
|
2191
|
+
isExpanded: false,
|
|
2192
|
+
isFocused: false,
|
|
2193
|
+
isFolder: false,
|
|
2194
|
+
isLoading: false,
|
|
2195
|
+
isMatchingSearch: false
|
|
2196
|
+
}),
|
|
2197
|
+
children: [
|
|
2198
|
+
Icon ? /* @__PURE__ */ jsx(Icon, {}) : null,
|
|
2199
|
+
item.label,
|
|
2200
|
+
item.shortcut ? /* @__PURE__ */ jsx(ContextMenuShortcut, { children: item.shortcut }) : null
|
|
2201
|
+
]
|
|
2202
|
+
},
|
|
2203
|
+
item.id
|
|
2204
|
+
);
|
|
2205
|
+
}) })
|
|
2206
|
+
] });
|
|
2207
|
+
}
|
|
2208
|
+
__name(TreeEmptyArea, "TreeEmptyArea");
|
|
835
2209
|
function useTreeLabels() {
|
|
836
2210
|
return useTreeContext().labels;
|
|
837
2211
|
}
|
|
@@ -850,12 +2224,26 @@ function useTreeSelection() {
|
|
|
850
2224
|
return useMemo(
|
|
851
2225
|
() => ({
|
|
852
2226
|
selectedIds,
|
|
2227
|
+
anchor: ctx.anchor,
|
|
853
2228
|
select: ctx.select,
|
|
854
2229
|
setSelectedIds: ctx.setSelectedIds,
|
|
855
2230
|
clear: ctx.clearSelection,
|
|
2231
|
+
clickSelect: ctx.clickSelect,
|
|
2232
|
+
moveSelect: ctx.moveSelect,
|
|
2233
|
+
selectAll: ctx.selectAll,
|
|
856
2234
|
isSelected
|
|
857
2235
|
}),
|
|
858
|
-
[
|
|
2236
|
+
[
|
|
2237
|
+
selectedIds,
|
|
2238
|
+
ctx.anchor,
|
|
2239
|
+
ctx.select,
|
|
2240
|
+
ctx.setSelectedIds,
|
|
2241
|
+
ctx.clearSelection,
|
|
2242
|
+
ctx.clickSelect,
|
|
2243
|
+
ctx.moveSelect,
|
|
2244
|
+
ctx.selectAll,
|
|
2245
|
+
isSelected
|
|
2246
|
+
]
|
|
859
2247
|
);
|
|
860
2248
|
}
|
|
861
2249
|
__name(useTreeSelection, "useTreeSelection");
|
|
@@ -910,6 +2298,59 @@ function useTreeSearch() {
|
|
|
910
2298
|
);
|
|
911
2299
|
}
|
|
912
2300
|
__name(useTreeSearch, "useTreeSearch");
|
|
2301
|
+
function useTreeDnd() {
|
|
2302
|
+
const ctx = useTreeContext();
|
|
2303
|
+
return ctx.dnd;
|
|
2304
|
+
}
|
|
2305
|
+
__name(useTreeDnd, "useTreeDnd");
|
|
2306
|
+
function useTreeClipboard() {
|
|
2307
|
+
const ctx = useTreeContext();
|
|
2308
|
+
const isCut = useCallback(
|
|
2309
|
+
(id) => ctx.clipboard?.kind === "cut" && ctx.clipboard.ids.includes(id),
|
|
2310
|
+
[ctx.clipboard]
|
|
2311
|
+
);
|
|
2312
|
+
return useMemo(
|
|
2313
|
+
() => ({
|
|
2314
|
+
clipboard: ctx.clipboard,
|
|
2315
|
+
isCut,
|
|
2316
|
+
cut: ctx.cutToClipboard,
|
|
2317
|
+
copy: ctx.copyToClipboard,
|
|
2318
|
+
paste: ctx.pasteFromClipboard,
|
|
2319
|
+
clear: ctx.clearClipboard
|
|
2320
|
+
}),
|
|
2321
|
+
[
|
|
2322
|
+
ctx.clipboard,
|
|
2323
|
+
isCut,
|
|
2324
|
+
ctx.cutToClipboard,
|
|
2325
|
+
ctx.copyToClipboard,
|
|
2326
|
+
ctx.pasteFromClipboard,
|
|
2327
|
+
ctx.clearClipboard
|
|
2328
|
+
]
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
__name(useTreeClipboard, "useTreeClipboard");
|
|
2332
|
+
function useTreeRename() {
|
|
2333
|
+
const ctx = useTreeContext();
|
|
2334
|
+
return useMemo(
|
|
2335
|
+
() => ({
|
|
2336
|
+
/** True when the host allowed inline rename AND the adapter exposes `rename`. */
|
|
2337
|
+
enabled: ctx.inlineRenameEnabled,
|
|
2338
|
+
/** Currently renaming id, or `null`. */
|
|
2339
|
+
renamingId: ctx.renamingId,
|
|
2340
|
+
startRename: ctx.startRename,
|
|
2341
|
+
cancelRename: ctx.cancelRename,
|
|
2342
|
+
commitRename: ctx.commitRename
|
|
2343
|
+
}),
|
|
2344
|
+
[
|
|
2345
|
+
ctx.inlineRenameEnabled,
|
|
2346
|
+
ctx.renamingId,
|
|
2347
|
+
ctx.startRename,
|
|
2348
|
+
ctx.cancelRename,
|
|
2349
|
+
ctx.commitRename
|
|
2350
|
+
]
|
|
2351
|
+
);
|
|
2352
|
+
}
|
|
2353
|
+
__name(useTreeRename, "useTreeRename");
|
|
913
2354
|
function useTreeActions() {
|
|
914
2355
|
const ctx = useTreeContext();
|
|
915
2356
|
return useMemo(
|
|
@@ -974,16 +2415,78 @@ function TreeSearchInput({ className, showMatches = true }) {
|
|
|
974
2415
|
);
|
|
975
2416
|
}
|
|
976
2417
|
__name(TreeSearchInput, "TreeSearchInput");
|
|
2418
|
+
|
|
2419
|
+
// src/tools/data/Tree/hooks/keyboard/arrow-nav.ts
|
|
2420
|
+
function nextRowId(rows, idx) {
|
|
2421
|
+
if (rows.length === 0) return null;
|
|
2422
|
+
const next = rows[Math.min(idx + 1, rows.length - 1)] ?? rows[0];
|
|
2423
|
+
return next.node.id;
|
|
2424
|
+
}
|
|
2425
|
+
__name(nextRowId, "nextRowId");
|
|
2426
|
+
function prevRowId(rows, idx) {
|
|
2427
|
+
if (rows.length === 0) return null;
|
|
2428
|
+
const prev = rows[Math.max(idx - 1, 0)] ?? rows[0];
|
|
2429
|
+
return prev.node.id;
|
|
2430
|
+
}
|
|
2431
|
+
__name(prevRowId, "prevRowId");
|
|
2432
|
+
function edgeRowId(rows, edge) {
|
|
2433
|
+
if (rows.length === 0) return null;
|
|
2434
|
+
return edge === "first" ? rows[0].node.id : rows[rows.length - 1].node.id;
|
|
2435
|
+
}
|
|
2436
|
+
__name(edgeRowId, "edgeRowId");
|
|
2437
|
+
|
|
2438
|
+
// src/tools/data/Tree/hooks/keyboard/expand-collapse.ts
|
|
2439
|
+
function resolveRightArrow(current, rows, idx) {
|
|
2440
|
+
if (!current) return { kind: "noop" };
|
|
2441
|
+
if (current.isFolder && !current.isExpanded) {
|
|
2442
|
+
return { kind: "expand", id: current.node.id };
|
|
2443
|
+
}
|
|
2444
|
+
if (current.isFolder && current.isExpanded) {
|
|
2445
|
+
const next = rows[idx + 1];
|
|
2446
|
+
return next ? { kind: "focus", id: next.node.id } : { kind: "noop" };
|
|
2447
|
+
}
|
|
2448
|
+
return { kind: "noop" };
|
|
2449
|
+
}
|
|
2450
|
+
__name(resolveRightArrow, "resolveRightArrow");
|
|
2451
|
+
function resolveLeftArrow(current) {
|
|
2452
|
+
if (!current) return { kind: "noop" };
|
|
2453
|
+
if (current.isFolder && current.isExpanded) {
|
|
2454
|
+
return { kind: "collapse", id: current.node.id };
|
|
2455
|
+
}
|
|
2456
|
+
if (current.parentId) {
|
|
2457
|
+
return { kind: "focus", id: current.parentId };
|
|
2458
|
+
}
|
|
2459
|
+
return { kind: "noop" };
|
|
2460
|
+
}
|
|
2461
|
+
__name(resolveLeftArrow, "resolveLeftArrow");
|
|
2462
|
+
|
|
2463
|
+
// src/tools/data/Tree/hooks/keyboard/activation.ts
|
|
2464
|
+
function resolveActivate(current) {
|
|
2465
|
+
if (!current) return { kind: "noop" };
|
|
2466
|
+
if (current.isFolder) {
|
|
2467
|
+
return {
|
|
2468
|
+
kind: "toggle-folder",
|
|
2469
|
+
id: current.node.id,
|
|
2470
|
+
willExpand: !current.isExpanded
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
return { kind: "activate-leaf", id: current.node.id };
|
|
2474
|
+
}
|
|
2475
|
+
__name(resolveActivate, "resolveActivate");
|
|
2476
|
+
|
|
2477
|
+
// src/tools/data/Tree/hooks/keyboard/use-tree-keyboard.ts
|
|
977
2478
|
function useTreeKeyboard({
|
|
978
2479
|
rows,
|
|
979
2480
|
focusedId,
|
|
980
2481
|
enabled = true,
|
|
2482
|
+
multiSelect = false,
|
|
981
2483
|
onFocus,
|
|
982
2484
|
onSelect,
|
|
983
2485
|
onActivate,
|
|
984
2486
|
onExpand,
|
|
985
2487
|
onCollapse,
|
|
986
|
-
onClearSelection
|
|
2488
|
+
onClearSelection,
|
|
2489
|
+
onSelectAll
|
|
987
2490
|
}) {
|
|
988
2491
|
const rowsRef = useRef(rows);
|
|
989
2492
|
const focusedIdRef = useRef(focusedId);
|
|
@@ -996,53 +2499,65 @@ function useTreeKeyboard({
|
|
|
996
2499
|
return { rows: r, idx, current: idx >= 0 ? r[idx] : null };
|
|
997
2500
|
}, "getCurrent");
|
|
998
2501
|
const refDown = useHotkey(
|
|
999
|
-
"down",
|
|
1000
|
-
() => {
|
|
2502
|
+
["down", "shift+down"],
|
|
2503
|
+
(e) => {
|
|
1001
2504
|
const { rows: r, idx } = getCurrent();
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
onFocus(next.node.id);
|
|
2505
|
+
const id = nextRowId(r, idx);
|
|
2506
|
+
if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
|
|
1005
2507
|
},
|
|
1006
|
-
{ enabled, preventDefault: true, description: "Next row" }
|
|
2508
|
+
{ enabled, preventDefault: true, description: "Next row (Shift extends)" }
|
|
1007
2509
|
);
|
|
1008
2510
|
const refUp = useHotkey(
|
|
1009
|
-
"up",
|
|
1010
|
-
() => {
|
|
2511
|
+
["up", "shift+up"],
|
|
2512
|
+
(e) => {
|
|
1011
2513
|
const { rows: r, idx } = getCurrent();
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
onFocus(prev.node.id);
|
|
2514
|
+
const id = prevRowId(r, idx);
|
|
2515
|
+
if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
|
|
1015
2516
|
},
|
|
1016
|
-
{ enabled, preventDefault: true, description: "Previous row" }
|
|
2517
|
+
{ enabled, preventDefault: true, description: "Previous row (Shift extends)" }
|
|
1017
2518
|
);
|
|
1018
2519
|
const refHome = useHotkey(
|
|
1019
|
-
"home",
|
|
1020
|
-
() => {
|
|
1021
|
-
const
|
|
1022
|
-
if (
|
|
1023
|
-
onFocus(r[0].node.id);
|
|
2520
|
+
["home", "shift+home"],
|
|
2521
|
+
(e) => {
|
|
2522
|
+
const id = edgeRowId(rowsRef.current, "first");
|
|
2523
|
+
if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
|
|
1024
2524
|
},
|
|
1025
|
-
{ enabled, preventDefault: true, description: "First row" }
|
|
2525
|
+
{ enabled, preventDefault: true, description: "First row (Shift extends)" }
|
|
1026
2526
|
);
|
|
1027
2527
|
const refEnd = useHotkey(
|
|
1028
|
-
"end",
|
|
2528
|
+
["end", "shift+end"],
|
|
2529
|
+
(e) => {
|
|
2530
|
+
const id = edgeRowId(rowsRef.current, "last");
|
|
2531
|
+
if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
|
|
2532
|
+
},
|
|
2533
|
+
{ enabled, preventDefault: true, description: "Last row (Shift extends)" }
|
|
2534
|
+
);
|
|
2535
|
+
const refSelectAll = useHotkey(
|
|
2536
|
+
"mod+a",
|
|
1029
2537
|
() => {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
onFocus(r[r.length - 1].node.id);
|
|
2538
|
+
if (!multiSelect) return;
|
|
2539
|
+
onSelectAll?.();
|
|
1033
2540
|
},
|
|
1034
|
-
{
|
|
2541
|
+
{
|
|
2542
|
+
enabled: enabled && multiSelect,
|
|
2543
|
+
preventDefault: true,
|
|
2544
|
+
description: "Select all visible rows"
|
|
2545
|
+
}
|
|
1035
2546
|
);
|
|
1036
2547
|
const refRight = useHotkey(
|
|
1037
2548
|
"right",
|
|
1038
2549
|
() => {
|
|
1039
2550
|
const { rows: r, idx, current } = getCurrent();
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
2551
|
+
const out = resolveRightArrow(current, r, idx);
|
|
2552
|
+
switch (out.kind) {
|
|
2553
|
+
case "expand":
|
|
2554
|
+
onExpand(out.id);
|
|
2555
|
+
return;
|
|
2556
|
+
case "focus":
|
|
2557
|
+
onFocus(out.id, { extend: false });
|
|
2558
|
+
return;
|
|
2559
|
+
case "noop":
|
|
2560
|
+
return;
|
|
1046
2561
|
}
|
|
1047
2562
|
},
|
|
1048
2563
|
{ enabled, preventDefault: true, description: "Expand / first child" }
|
|
@@ -1051,11 +2566,16 @@ function useTreeKeyboard({
|
|
|
1051
2566
|
"left",
|
|
1052
2567
|
() => {
|
|
1053
2568
|
const { current } = getCurrent();
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
2569
|
+
const out = resolveLeftArrow(current);
|
|
2570
|
+
switch (out.kind) {
|
|
2571
|
+
case "collapse":
|
|
2572
|
+
onCollapse(out.id);
|
|
2573
|
+
return;
|
|
2574
|
+
case "focus":
|
|
2575
|
+
onFocus(out.id, { extend: false });
|
|
2576
|
+
return;
|
|
2577
|
+
case "noop":
|
|
2578
|
+
return;
|
|
1059
2579
|
}
|
|
1060
2580
|
},
|
|
1061
2581
|
{ enabled, preventDefault: true, description: "Collapse / parent" }
|
|
@@ -1064,13 +2584,14 @@ function useTreeKeyboard({
|
|
|
1064
2584
|
["enter", "space"],
|
|
1065
2585
|
() => {
|
|
1066
2586
|
const { current } = getCurrent();
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
2587
|
+
const out = resolveActivate(current);
|
|
2588
|
+
if (out.kind === "noop") return;
|
|
2589
|
+
onSelect(out.kind === "activate-leaf" ? out.id : out.id);
|
|
2590
|
+
if (out.kind === "toggle-folder") {
|
|
2591
|
+
if (out.willExpand) onExpand(out.id);
|
|
2592
|
+
else onCollapse(out.id);
|
|
1072
2593
|
} else {
|
|
1073
|
-
onActivate(
|
|
2594
|
+
onActivate(out.id);
|
|
1074
2595
|
}
|
|
1075
2596
|
},
|
|
1076
2597
|
{ enabled, preventDefault: true, description: "Activate / toggle" }
|
|
@@ -1090,12 +2611,44 @@ function useTreeKeyboard({
|
|
|
1090
2611
|
refLeft(instance);
|
|
1091
2612
|
refActivate(instance);
|
|
1092
2613
|
refEscape(instance);
|
|
2614
|
+
refSelectAll(instance);
|
|
1093
2615
|
},
|
|
1094
|
-
[
|
|
2616
|
+
[
|
|
2617
|
+
refDown,
|
|
2618
|
+
refUp,
|
|
2619
|
+
refHome,
|
|
2620
|
+
refEnd,
|
|
2621
|
+
refRight,
|
|
2622
|
+
refLeft,
|
|
2623
|
+
refActivate,
|
|
2624
|
+
refEscape,
|
|
2625
|
+
refSelectAll
|
|
2626
|
+
]
|
|
1095
2627
|
);
|
|
1096
2628
|
return { ref };
|
|
1097
2629
|
}
|
|
1098
2630
|
__name(useTreeKeyboard, "useTreeKeyboard");
|
|
2631
|
+
|
|
2632
|
+
// src/tools/data/Tree/hooks/type-ahead/match-prefix.ts
|
|
2633
|
+
function findRowByPrefix(rows, getName, prefix) {
|
|
2634
|
+
if (prefix.length === 0) return void 0;
|
|
2635
|
+
return rows.find((row) => getName(row.node).toLowerCase().startsWith(prefix));
|
|
2636
|
+
}
|
|
2637
|
+
__name(findRowByPrefix, "findRowByPrefix");
|
|
2638
|
+
function isResetKey(key) {
|
|
2639
|
+
return key === "Escape" || key === "Enter" || key === "Tab" || key.startsWith("Arrow") || key === "Home" || key === "End" || key === "PageUp" || key === "PageDown";
|
|
2640
|
+
}
|
|
2641
|
+
__name(isResetKey, "isResetKey");
|
|
2642
|
+
function isTypingTarget(target) {
|
|
2643
|
+
const el = target;
|
|
2644
|
+
if (!el) return false;
|
|
2645
|
+
const tag = el.tagName;
|
|
2646
|
+
if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
|
|
2647
|
+
return el.isContentEditable === true;
|
|
2648
|
+
}
|
|
2649
|
+
__name(isTypingTarget, "isTypingTarget");
|
|
2650
|
+
|
|
2651
|
+
// src/tools/data/Tree/hooks/type-ahead/use-tree-type-ahead.ts
|
|
1099
2652
|
var FLUSH_MS = 600;
|
|
1100
2653
|
function useTreeTypeAhead({
|
|
1101
2654
|
rows,
|
|
@@ -1124,11 +2677,9 @@ function useTreeTypeAhead({
|
|
|
1124
2677
|
}
|
|
1125
2678
|
}, "reset");
|
|
1126
2679
|
const handler = /* @__PURE__ */ __name((e) => {
|
|
1127
|
-
|
|
1128
|
-
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
1129
|
-
if (e.target?.isContentEditable) return;
|
|
2680
|
+
if (isTypingTarget(e.target)) return;
|
|
1130
2681
|
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
1131
|
-
if (
|
|
2682
|
+
if (isResetKey(e.key)) {
|
|
1132
2683
|
reset();
|
|
1133
2684
|
return;
|
|
1134
2685
|
}
|
|
@@ -1136,9 +2687,10 @@ function useTreeTypeAhead({
|
|
|
1136
2687
|
bufferRef.current += e.key.toLowerCase();
|
|
1137
2688
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1138
2689
|
timerRef.current = setTimeout(reset, FLUSH_MS);
|
|
1139
|
-
const
|
|
1140
|
-
|
|
1141
|
-
|
|
2690
|
+
const hit = findRowByPrefix(
|
|
2691
|
+
rowsRef.current,
|
|
2692
|
+
getNameRef.current,
|
|
2693
|
+
bufferRef.current
|
|
1142
2694
|
);
|
|
1143
2695
|
if (hit) {
|
|
1144
2696
|
e.preventDefault();
|
|
@@ -1153,36 +2705,125 @@ function useTreeTypeAhead({
|
|
|
1153
2705
|
}, [containerRef, enabled]);
|
|
1154
2706
|
}
|
|
1155
2707
|
__name(useTreeTypeAhead, "useTreeTypeAhead");
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
Icon ? /* @__PURE__ */ jsx(Icon, {}) : null,
|
|
1175
|
-
item.label,
|
|
1176
|
-
item.shortcut ? /* @__PURE__ */ jsx(ContextMenuShortcut, { children: item.shortcut }) : null
|
|
1177
|
-
]
|
|
1178
|
-
},
|
|
1179
|
-
item.id
|
|
1180
|
-
);
|
|
1181
|
-
}) })
|
|
1182
|
-
] });
|
|
2708
|
+
|
|
2709
|
+
// src/tools/data/Tree/hooks/finder-hotkeys/build-ctx.ts
|
|
2710
|
+
function buildBuiltinCtx(input) {
|
|
2711
|
+
if (!input.adapter) return null;
|
|
2712
|
+
const selectedNodes = [];
|
|
2713
|
+
for (const id of input.selected) {
|
|
2714
|
+
const n = input.getNodeById(id);
|
|
2715
|
+
if (n) selectedNodes.push(n);
|
|
2716
|
+
}
|
|
2717
|
+
const targetNode = input.focused ? input.getNodeById(input.focused) ?? null : null;
|
|
2718
|
+
return {
|
|
2719
|
+
adapter: input.adapter,
|
|
2720
|
+
labels: input.labels,
|
|
2721
|
+
selectedNodes,
|
|
2722
|
+
targetNode,
|
|
2723
|
+
getName: input.getItemName,
|
|
2724
|
+
startInlineRename: input.startInlineRename,
|
|
2725
|
+
clipboard: input.clipboard
|
|
1183
2726
|
};
|
|
1184
2727
|
}
|
|
1185
|
-
__name(
|
|
2728
|
+
__name(buildBuiltinCtx, "buildBuiltinCtx");
|
|
2729
|
+
|
|
2730
|
+
// src/tools/data/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts
|
|
2731
|
+
function useTreeFinderHotkeys(opts) {
|
|
2732
|
+
const optsRef = useRef(opts);
|
|
2733
|
+
optsRef.current = opts;
|
|
2734
|
+
const run = useCallback(async (action) => {
|
|
2735
|
+
const o = optsRef.current;
|
|
2736
|
+
if (o.paused) return;
|
|
2737
|
+
const ctx = buildBuiltinCtx({
|
|
2738
|
+
adapter: o.adapter,
|
|
2739
|
+
labels: o.labels,
|
|
2740
|
+
selected: o.selected,
|
|
2741
|
+
focused: o.focused,
|
|
2742
|
+
getNodeById: o.getNodeById,
|
|
2743
|
+
getItemName: o.getItemName,
|
|
2744
|
+
startInlineRename: o.startInlineRename,
|
|
2745
|
+
clipboard: o.clipboard
|
|
2746
|
+
});
|
|
2747
|
+
if (!ctx) return;
|
|
2748
|
+
await runBuiltinAction(action, ctx);
|
|
2749
|
+
}, []);
|
|
2750
|
+
const refDelete = useHotkey(
|
|
2751
|
+
["mod+backspace", "delete"],
|
|
2752
|
+
() => void run("delete"),
|
|
2753
|
+
{
|
|
2754
|
+
enabled: opts.enabled,
|
|
2755
|
+
preventDefault: true,
|
|
2756
|
+
description: "Delete selected items",
|
|
2757
|
+
scope: "tree"
|
|
2758
|
+
}
|
|
2759
|
+
);
|
|
2760
|
+
const refRename = useHotkey("f2", () => void run("rename"), {
|
|
2761
|
+
enabled: opts.enabled,
|
|
2762
|
+
preventDefault: true,
|
|
2763
|
+
description: "Rename selected item",
|
|
2764
|
+
scope: "tree"
|
|
2765
|
+
});
|
|
2766
|
+
const refDuplicate = useHotkey("mod+d", () => void run("duplicate"), {
|
|
2767
|
+
enabled: opts.enabled,
|
|
2768
|
+
preventDefault: true,
|
|
2769
|
+
description: "Duplicate selected items",
|
|
2770
|
+
scope: "tree"
|
|
2771
|
+
});
|
|
2772
|
+
const refNewFolder = useHotkey("mod+shift+n", () => void run("new-folder"), {
|
|
2773
|
+
enabled: opts.enabled,
|
|
2774
|
+
preventDefault: true,
|
|
2775
|
+
description: "New folder",
|
|
2776
|
+
scope: "tree"
|
|
2777
|
+
});
|
|
2778
|
+
const refNewFile = useHotkey("mod+n", () => void run("new-file"), {
|
|
2779
|
+
enabled: opts.enabled,
|
|
2780
|
+
preventDefault: true,
|
|
2781
|
+
description: "New file",
|
|
2782
|
+
scope: "tree"
|
|
2783
|
+
});
|
|
2784
|
+
const refCut = useHotkey("mod+x", () => void run("cut"), {
|
|
2785
|
+
enabled: opts.enabled,
|
|
2786
|
+
preventDefault: true,
|
|
2787
|
+
description: "Cut",
|
|
2788
|
+
scope: "tree"
|
|
2789
|
+
});
|
|
2790
|
+
const refCopy = useHotkey("mod+c", () => void run("copy"), {
|
|
2791
|
+
enabled: opts.enabled,
|
|
2792
|
+
preventDefault: true,
|
|
2793
|
+
description: "Copy",
|
|
2794
|
+
scope: "tree"
|
|
2795
|
+
});
|
|
2796
|
+
const refPaste = useHotkey("mod+v", () => void run("paste"), {
|
|
2797
|
+
enabled: opts.enabled,
|
|
2798
|
+
preventDefault: true,
|
|
2799
|
+
description: "Paste",
|
|
2800
|
+
scope: "tree"
|
|
2801
|
+
});
|
|
2802
|
+
const ref = useCallback(
|
|
2803
|
+
(instance) => {
|
|
2804
|
+
refDelete(instance);
|
|
2805
|
+
refRename(instance);
|
|
2806
|
+
refDuplicate(instance);
|
|
2807
|
+
refNewFolder(instance);
|
|
2808
|
+
refNewFile(instance);
|
|
2809
|
+
refCut(instance);
|
|
2810
|
+
refCopy(instance);
|
|
2811
|
+
refPaste(instance);
|
|
2812
|
+
},
|
|
2813
|
+
[
|
|
2814
|
+
refDelete,
|
|
2815
|
+
refRename,
|
|
2816
|
+
refDuplicate,
|
|
2817
|
+
refNewFolder,
|
|
2818
|
+
refNewFile,
|
|
2819
|
+
refCut,
|
|
2820
|
+
refCopy,
|
|
2821
|
+
refPaste
|
|
2822
|
+
]
|
|
2823
|
+
);
|
|
2824
|
+
return { ref };
|
|
2825
|
+
}
|
|
2826
|
+
__name(useTreeFinderHotkeys, "useTreeFinderHotkeys");
|
|
1186
2827
|
function TreeRoot(props) {
|
|
1187
2828
|
const {
|
|
1188
2829
|
data,
|
|
@@ -1201,6 +2842,10 @@ function TreeRoot(props) {
|
|
|
1201
2842
|
enableSearch = false,
|
|
1202
2843
|
enableTypeAhead = true,
|
|
1203
2844
|
showIndentGuides = false,
|
|
2845
|
+
enableInlineRename = false,
|
|
2846
|
+
enableFinderHotkeys = false,
|
|
2847
|
+
enableDnD = false,
|
|
2848
|
+
canDrop,
|
|
1204
2849
|
renderRow,
|
|
1205
2850
|
renderIcon,
|
|
1206
2851
|
renderLabel,
|
|
@@ -1210,14 +2855,12 @@ function TreeRoot(props) {
|
|
|
1210
2855
|
labels,
|
|
1211
2856
|
persistKey,
|
|
1212
2857
|
persistSelection = false,
|
|
2858
|
+
adapter,
|
|
2859
|
+
defaultMenuItems,
|
|
2860
|
+
actionsRef,
|
|
1213
2861
|
className,
|
|
1214
2862
|
style
|
|
1215
2863
|
} = props;
|
|
1216
|
-
const resolvedRenderContextMenu = useMemo(() => {
|
|
1217
|
-
if (renderContextMenu) return renderContextMenu;
|
|
1218
|
-
if (contextMenuActions) return actionsToRenderContextMenu(contextMenuActions);
|
|
1219
|
-
return void 0;
|
|
1220
|
-
}, [renderContextMenu, contextMenuActions]);
|
|
1221
2864
|
return /* @__PURE__ */ jsx(
|
|
1222
2865
|
TreeProvider,
|
|
1223
2866
|
{
|
|
@@ -1239,7 +2882,13 @@ function TreeRoot(props) {
|
|
|
1239
2882
|
renderIcon,
|
|
1240
2883
|
renderLabel,
|
|
1241
2884
|
renderActions,
|
|
1242
|
-
renderContextMenu
|
|
2885
|
+
renderContextMenu,
|
|
2886
|
+
contextMenuActions,
|
|
2887
|
+
adapter,
|
|
2888
|
+
defaultMenuItems,
|
|
2889
|
+
enableInlineRename,
|
|
2890
|
+
enableDnD,
|
|
2891
|
+
canDrop,
|
|
1243
2892
|
labels,
|
|
1244
2893
|
persistKey,
|
|
1245
2894
|
persistSelection,
|
|
@@ -1250,7 +2899,9 @@ function TreeRoot(props) {
|
|
|
1250
2899
|
style,
|
|
1251
2900
|
enableSearch,
|
|
1252
2901
|
enableTypeAhead,
|
|
1253
|
-
|
|
2902
|
+
enableFinderHotkeys,
|
|
2903
|
+
renderRow,
|
|
2904
|
+
actionsRef
|
|
1254
2905
|
}
|
|
1255
2906
|
)
|
|
1256
2907
|
}
|
|
@@ -1262,14 +2913,46 @@ function TreeRootShell({
|
|
|
1262
2913
|
style,
|
|
1263
2914
|
enableSearch,
|
|
1264
2915
|
enableTypeAhead,
|
|
1265
|
-
|
|
2916
|
+
enableFinderHotkeys,
|
|
2917
|
+
renderRow,
|
|
2918
|
+
actionsRef
|
|
1266
2919
|
}) {
|
|
1267
2920
|
const containerRef = useRef(null);
|
|
1268
2921
|
const ctx = useTreeContext();
|
|
2922
|
+
useEffect(() => {
|
|
2923
|
+
if (!actionsRef) return;
|
|
2924
|
+
actionsRef.current = {
|
|
2925
|
+
refresh: ctx.refresh,
|
|
2926
|
+
refreshAll: ctx.refreshAll,
|
|
2927
|
+
expandAll: ctx.expandAll,
|
|
2928
|
+
collapseAll: ctx.collapseAll
|
|
2929
|
+
};
|
|
2930
|
+
return () => {
|
|
2931
|
+
if (actionsRef.current) actionsRef.current = null;
|
|
2932
|
+
};
|
|
2933
|
+
}, [
|
|
2934
|
+
actionsRef,
|
|
2935
|
+
ctx.refresh,
|
|
2936
|
+
ctx.refreshAll,
|
|
2937
|
+
ctx.expandAll,
|
|
2938
|
+
ctx.collapseAll
|
|
2939
|
+
]);
|
|
2940
|
+
const isMulti = ctx.selectionMode === "multiple";
|
|
1269
2941
|
const { ref: keyboardRef } = useTreeKeyboard({
|
|
1270
2942
|
rows: ctx.flatRows,
|
|
1271
2943
|
focusedId: ctx.focused,
|
|
1272
|
-
|
|
2944
|
+
multiSelect: isMulti,
|
|
2945
|
+
// Pause container hotkeys while inline rename is active so the
|
|
2946
|
+
// user can type freely (TreeRenameInput stops bubbling already, but
|
|
2947
|
+
// gating here is the cleaner second line of defence).
|
|
2948
|
+
enabled: ctx.renamingId === null,
|
|
2949
|
+
onFocus: /* @__PURE__ */ __name((id, { extend }) => {
|
|
2950
|
+
if (extend && isMulti) {
|
|
2951
|
+
ctx.moveSelect(id, { extend: true });
|
|
2952
|
+
} else {
|
|
2953
|
+
ctx.setFocus(id);
|
|
2954
|
+
}
|
|
2955
|
+
}, "onFocus"),
|
|
1273
2956
|
onSelect: ctx.select,
|
|
1274
2957
|
onActivate: /* @__PURE__ */ __name((id) => {
|
|
1275
2958
|
const row = ctx.flatRows.find((r) => r.node.id === id);
|
|
@@ -1277,14 +2960,37 @@ function TreeRootShell({
|
|
|
1277
2960
|
}, "onActivate"),
|
|
1278
2961
|
onExpand: ctx.expand,
|
|
1279
2962
|
onCollapse: ctx.collapse,
|
|
1280
|
-
onClearSelection: ctx.clearSelection
|
|
2963
|
+
onClearSelection: ctx.clearSelection,
|
|
2964
|
+
onSelectAll: ctx.selectAll
|
|
2965
|
+
});
|
|
2966
|
+
const { ref: finderHotkeysRef } = useTreeFinderHotkeys({
|
|
2967
|
+
enabled: enableFinderHotkeys,
|
|
2968
|
+
paused: ctx.renamingId !== null,
|
|
2969
|
+
adapter: ctx.adapter,
|
|
2970
|
+
labels: ctx.labels,
|
|
2971
|
+
selected: ctx.selected,
|
|
2972
|
+
focused: ctx.focused,
|
|
2973
|
+
getNodeById: ctx.getNodeById,
|
|
2974
|
+
getItemName: ctx.getItemName,
|
|
2975
|
+
startInlineRename: ctx.inlineRenameEnabled ? ctx.startRename : void 0,
|
|
2976
|
+
clipboard: {
|
|
2977
|
+
hasItems: !!ctx.clipboard && ctx.clipboard.ids.length > 0,
|
|
2978
|
+
cut: ctx.cutToClipboard,
|
|
2979
|
+
copy: ctx.copyToClipboard,
|
|
2980
|
+
// Hotkey paste targets the currently focused row (or null = root).
|
|
2981
|
+
paste: /* @__PURE__ */ __name(() => {
|
|
2982
|
+
const target = ctx.focused ? ctx.getNodeById(ctx.focused) ?? null : null;
|
|
2983
|
+
return ctx.pasteFromClipboard(target, "inside");
|
|
2984
|
+
}, "paste")
|
|
2985
|
+
}
|
|
1281
2986
|
});
|
|
1282
2987
|
const setContainerRef = useCallback(
|
|
1283
2988
|
(instance) => {
|
|
1284
2989
|
containerRef.current = instance;
|
|
1285
2990
|
keyboardRef(instance);
|
|
2991
|
+
finderHotkeysRef(instance);
|
|
1286
2992
|
},
|
|
1287
|
-
[keyboardRef]
|
|
2993
|
+
[keyboardRef, finderHotkeysRef]
|
|
1288
2994
|
);
|
|
1289
2995
|
const focusedId = ctx.focused;
|
|
1290
2996
|
useEffect(() => {
|
|
@@ -1307,7 +3013,7 @@ function TreeRootShell({
|
|
|
1307
3013
|
onMatch: onTypeAheadMatch,
|
|
1308
3014
|
enabled: enableTypeAhead
|
|
1309
3015
|
});
|
|
1310
|
-
|
|
3016
|
+
const treeBody = /* @__PURE__ */ jsxs(
|
|
1311
3017
|
"div",
|
|
1312
3018
|
{
|
|
1313
3019
|
ref: setContainerRef,
|
|
@@ -1325,13 +3031,34 @@ function TreeRootShell({
|
|
|
1325
3031
|
"data-tree-root": "",
|
|
1326
3032
|
children: [
|
|
1327
3033
|
enableSearch ? /* @__PURE__ */ jsx(TreeSearchInput, { className: "mx-2 mt-2" }) : null,
|
|
1328
|
-
/* @__PURE__ */
|
|
3034
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-auto px-1", children: [
|
|
3035
|
+
/* @__PURE__ */ jsx(TreeContent, { role: "group", children: renderRow }),
|
|
3036
|
+
/* @__PURE__ */ jsx(TreeEmptyArea, {})
|
|
3037
|
+
] })
|
|
1329
3038
|
]
|
|
1330
3039
|
}
|
|
1331
3040
|
);
|
|
3041
|
+
return /* @__PURE__ */ jsx(TreeDndProvider, { children: treeBody });
|
|
1332
3042
|
}
|
|
1333
3043
|
__name(TreeRootShell, "TreeRootShell");
|
|
1334
3044
|
var TreeRoot_default = TreeRoot;
|
|
3045
|
+
function FinderTree(props) {
|
|
3046
|
+
return /* @__PURE__ */ jsx(
|
|
3047
|
+
TreeRoot,
|
|
3048
|
+
{
|
|
3049
|
+
selectionMode: "multiple",
|
|
3050
|
+
activationMode: "double-click",
|
|
3051
|
+
enableInlineRename: true,
|
|
3052
|
+
enableFinderHotkeys: true,
|
|
3053
|
+
enableDnD: true,
|
|
3054
|
+
enableTypeAhead: true,
|
|
3055
|
+
showIndentGuides: true,
|
|
3056
|
+
appearance: { density: "cozy" },
|
|
3057
|
+
...props
|
|
3058
|
+
}
|
|
3059
|
+
);
|
|
3060
|
+
}
|
|
3061
|
+
__name(FinderTree, "FinderTree");
|
|
1335
3062
|
function TreeSkeleton({ rows = 6, className }) {
|
|
1336
3063
|
return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col gap-1 p-2", className), "aria-hidden": true, children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", style: { paddingLeft: i % 3 * 16 }, children: [
|
|
1337
3064
|
/* @__PURE__ */ jsx("span", { className: "size-4 shrink-0 animate-pulse rounded bg-muted" }),
|
|
@@ -1383,6 +3110,6 @@ function createDemoTree({
|
|
|
1383
3110
|
}
|
|
1384
3111
|
__name(createDemoTree, "createDemoTree");
|
|
1385
3112
|
|
|
1386
|
-
export { DEFAULT_TREE_APPEARANCE, DEFAULT_TREE_LABELS, TreeRoot as Tree, TreeChevron, TreeContent, TreeEmpty, TreeError, TreeIcon, TreeIndentGuides, TreeLabel, TreeProvider, TreeRoot, TreeRow, TreeSearchInput, TreeSkeleton, appearanceToStyle, clearTreeState, createChildCache, createDemoTree, TreeRoot_default as default, flattenTree, loadTreeState, resolveAppearance, resolveChildren, saveTreeState, useTreeActions, useTreeContext, useTreeExpansion, useTreeFocus, useTreeKeyboard, useTreeLabels, useTreeRows, useTreeSearch, useTreeSelection, useTreeTypeAhead };
|
|
3113
|
+
export { DEFAULT_TREE_APPEARANCE, DEFAULT_TREE_LABELS, FinderTree, TREE_DND_MIME, TreeRoot as Tree, TreeChevron, TreeContent, TreeDropIndicator, TreeEmpty, TreeEmptyArea, TreeError, TreeIcon, TreeIndentGuides, TreeLabel, TreeProvider, TreeRenameInput, TreeRoot, TreeRow, TreeSearchInput, TreeSkeleton, appearanceToStyle, autoSelectRange, clearTreeState, createChildCache, createDemoTree, TreeRoot_default as default, defaultCanDrop, flattenTree, loadTreeState, resolveAppearance, resolveChildren, resolveDropZone, saveTreeState, splitFileName, useTreeActions, useTreeClipboard, useTreeContext, useTreeDnd, useTreeExpansion, useTreeFinderHotkeys, useTreeFocus, useTreeKeyboard, useTreeLabels, useTreeRename, useTreeRows, useTreeSearch, useTreeSelection, useTreeTypeAhead };
|
|
1387
3114
|
//# sourceMappingURL=index.mjs.map
|
|
1388
3115
|
//# sourceMappingURL=index.mjs.map
|