@djangocfg/ui-tools 2.1.413 → 2.1.416

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/file-icon/index.d.cts +1 -1
  2. package/dist/file-icon/index.d.ts +1 -1
  3. package/dist/slots-ClRpIzoh.d.cts +88 -0
  4. package/dist/slots-ClRpIzoh.d.ts +88 -0
  5. package/dist/tree/index.cjs +1994 -276
  6. package/dist/tree/index.cjs.map +1 -1
  7. package/dist/tree/index.d.cts +717 -72
  8. package/dist/tree/index.d.ts +717 -72
  9. package/dist/tree/index.mjs +1984 -279
  10. package/dist/tree/index.mjs.map +1 -1
  11. package/package.json +10 -6
  12. package/src/tools/chat/README.md +111 -1
  13. package/src/tools/chat/composer/Composer.tsx +138 -17
  14. package/src/tools/chat/composer/ComposerRichTextarea.tsx +25 -0
  15. package/src/tools/chat/composer/index.ts +22 -0
  16. package/src/tools/chat/composer/slash/README.md +187 -0
  17. package/src/tools/chat/composer/slash/SlashHighlightTextarea.tsx +144 -0
  18. package/src/tools/chat/composer/slash/SlashMenu.tsx +142 -0
  19. package/src/tools/chat/composer/slash/SlashToken.tsx +57 -0
  20. package/src/tools/chat/composer/slash/index.ts +44 -0
  21. package/src/tools/chat/composer/slash/labels.ts +19 -0
  22. package/src/tools/chat/composer/slash/state.ts +168 -0
  23. package/src/tools/chat/composer/slash/types.ts +64 -0
  24. package/src/tools/chat/composer/slash/useSlashCommands.ts +204 -0
  25. package/src/tools/chat/composer/types.ts +8 -0
  26. package/src/tools/chat/shell/SuggestedPrompts.tsx +194 -0
  27. package/src/tools/chat/shell/index.ts +6 -0
  28. package/src/tools/data/Listbox/lazy.tsx +1 -1
  29. package/src/tools/data/Masonry/lazy.tsx +1 -1
  30. package/src/tools/data/Timeline/lazy.tsx +1 -1
  31. package/src/tools/data/Tree/FinderTree.tsx +42 -0
  32. package/src/tools/data/Tree/README.md +337 -208
  33. package/src/tools/data/Tree/TreeDndProvider.tsx +137 -0
  34. package/src/tools/data/Tree/TreeRoot.tsx +170 -55
  35. package/src/tools/data/Tree/__tests__/dnd.test.ts +160 -0
  36. package/src/tools/data/Tree/__tests__/keyboard.test.ts +137 -0
  37. package/src/tools/data/Tree/__tests__/renameUtils.test.ts +52 -0
  38. package/src/tools/data/Tree/__tests__/selection.test.ts +227 -0
  39. package/src/tools/data/Tree/components/TreeDropIndicator.tsx +65 -0
  40. package/src/tools/data/Tree/components/TreeEmptyArea.tsx +160 -0
  41. package/src/tools/data/Tree/components/TreeRenameInput.tsx +114 -0
  42. package/src/tools/data/Tree/components/TreeRow.tsx +92 -8
  43. package/src/tools/data/Tree/components/index.ts +6 -0
  44. package/src/tools/data/Tree/context/TreeContext.tsx +204 -363
  45. package/src/tools/data/Tree/context/TreeContextValue.ts +139 -0
  46. package/src/tools/data/Tree/context/async-children/collect-ids.ts +27 -0
  47. package/src/tools/data/Tree/context/async-children/index.ts +8 -0
  48. package/src/tools/data/Tree/context/async-children/use-async-children.ts +157 -0
  49. package/src/tools/data/Tree/context/clipboard/index.ts +4 -0
  50. package/src/tools/data/Tree/context/clipboard/use-clipboard.ts +115 -0
  51. package/src/tools/data/Tree/context/dnd/index.ts +8 -0
  52. package/src/tools/data/Tree/context/dnd/use-dnd.ts +194 -0
  53. package/src/tools/data/Tree/context/expansion/index.ts +4 -0
  54. package/src/tools/data/Tree/context/expansion/use-expansion.ts +55 -0
  55. package/src/tools/data/Tree/context/hooks.ts +68 -1
  56. package/src/tools/data/Tree/context/index.ts +3 -0
  57. package/src/tools/data/Tree/context/menu/builtin-actions.ts +357 -0
  58. package/src/tools/data/Tree/context/menu/index.ts +10 -0
  59. package/src/tools/data/Tree/context/menu/use-resolved-menu.ts +127 -0
  60. package/src/tools/data/Tree/context/persist/index.ts +4 -0
  61. package/src/tools/data/Tree/context/persist/use-persist-sync.ts +74 -0
  62. package/src/tools/data/Tree/context/rename/index.ts +4 -0
  63. package/src/tools/data/Tree/context/rename/use-rename.ts +113 -0
  64. package/src/tools/data/Tree/context/selection/index.ts +4 -0
  65. package/src/tools/data/Tree/context/selection/use-selection.ts +146 -0
  66. package/src/tools/data/Tree/context/state/index.ts +6 -0
  67. package/src/tools/data/Tree/context/state/initial.ts +41 -0
  68. package/src/tools/data/Tree/context/state/reducer.ts +76 -0
  69. package/src/tools/data/Tree/context/state/types.ts +46 -0
  70. package/src/tools/data/Tree/data/clipboard.ts +33 -0
  71. package/src/tools/data/Tree/data/dnd.ts +123 -0
  72. package/src/tools/data/Tree/data/finderShortcuts.ts +67 -0
  73. package/src/tools/data/Tree/data/index.ts +19 -0
  74. package/src/tools/data/Tree/data/renameUtils.ts +51 -0
  75. package/src/tools/data/Tree/data/selection.ts +157 -0
  76. package/src/tools/data/Tree/hooks/finder-hotkeys/build-ctx.ts +48 -0
  77. package/src/tools/data/Tree/hooks/finder-hotkeys/index.ts +8 -0
  78. package/src/tools/data/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts +166 -0
  79. package/src/tools/data/Tree/hooks/index.ts +23 -4
  80. package/src/tools/data/Tree/hooks/keyboard/activation.ts +27 -0
  81. package/src/tools/data/Tree/hooks/keyboard/arrow-nav.ts +26 -0
  82. package/src/tools/data/Tree/hooks/keyboard/expand-collapse.ts +54 -0
  83. package/src/tools/data/Tree/hooks/keyboard/index.ts +10 -0
  84. package/src/tools/data/Tree/hooks/keyboard/types.ts +39 -0
  85. package/src/tools/data/Tree/hooks/keyboard/use-tree-keyboard.ts +196 -0
  86. package/src/tools/data/Tree/hooks/type-ahead/index.ts +5 -0
  87. package/src/tools/data/Tree/hooks/type-ahead/match-prefix.ts +42 -0
  88. package/src/tools/data/Tree/hooks/{useTreeTypeAhead.ts → type-ahead/use-tree-type-ahead.ts} +8 -19
  89. package/src/tools/data/Tree/index.tsx +25 -2
  90. package/src/tools/data/Tree/types/activation.ts +30 -0
  91. package/src/tools/data/Tree/types/adapter.ts +70 -0
  92. package/src/tools/data/Tree/types/index.ts +27 -0
  93. package/src/tools/data/Tree/types/labels.ts +97 -0
  94. package/src/tools/data/Tree/types/loader.ts +9 -0
  95. package/src/tools/data/Tree/types/node.ts +38 -0
  96. package/src/tools/data/Tree/types/root-props.ts +142 -0
  97. package/src/tools/data/Tree/types/selection.ts +3 -0
  98. package/src/tools/data/Tree/types/slots.ts +64 -0
  99. package/src/tools/dev/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +1 -1
  100. package/src/tools/dev/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +1 -1
  101. package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +85 -0
  102. package/src/tools/forms/MarkdownEditor/index.ts +1 -0
  103. package/src/tools/forms/MarkdownEditor/lazy.tsx +6 -0
  104. package/src/tools/forms/MarkdownEditor/slash/SlashCommandNode.ts +162 -0
  105. package/src/tools/forms/MarkdownEditor/slash/index.ts +4 -0
  106. package/src/tools/forms/MarkdownEditor/slash/syncSlashNode.ts +97 -0
  107. package/src/tools/forms/MarkdownEditor/slash/types.ts +13 -0
  108. package/src/tools/forms/MarkdownEditor/styles.css +18 -0
  109. package/src/tools/index.ts +2 -2
  110. package/dist/types-j2vhn4Kv.d.cts +0 -241
  111. package/dist/types-j2vhn4Kv.d.ts +0 -241
  112. package/src/tools/data/Tree/hooks/useTreeKeyboard.ts +0 -171
  113. package/src/tools/data/Tree/types.ts +0 -217
@@ -1,22 +1,14 @@
1
1
  import { __name } from '../chunk-PAWJFY3S.mjs';
2
2
  import * as React from 'react';
3
- import { createContext, memo, useMemo, useReducer, useRef, useCallback, useEffect, Fragment as Fragment$1 } from 'react';
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
5
  import { ContextMenu, ContextMenuTrigger, ContextMenuContent, ContextMenuSeparator, ContextMenuItem, ContextMenuShortcut } from '@djangocfg/ui-core/components';
6
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { ChevronDown, ChevronRight, FolderOpen, Folder, File, Loader2, Search, X, AlertCircle } from 'lucide-react';
6
+ import { getDialog } from '@djangocfg/ui-core/lib/dialog-service';
7
+ import { CornerUpLeft, Pencil, Copy, Scissors, Trash2, FilePlus, FolderPlus, ChevronDown, ChevronRight, FolderOpen, Folder, File, Loader2, Search, X, AlertCircle } from 'lucide-react';
8
+ import { jsx, Fragment, jsxs } 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
- var reducer = /* @__PURE__ */ __name((state, action) => {
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 { ...state, selected: /* @__PURE__ */ new Set([action.id]), focused: action.id };
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(useTreeContext, "useTreeContext");
263
- var setEqualsArr = /* @__PURE__ */ __name((set, arr) => {
264
- if (set.size !== arr.length) return false;
265
- for (const id of arr) if (!set.has(id)) return false;
266
- return true;
267
- }, "setEqualsArr");
268
- var collectAllIds = /* @__PURE__ */ __name((roots, cache, out) => {
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
- collectAllIds(node.children, cache, out);
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) collectAllIds(entry.children, cache, out);
334
+ if (entry?.children) collectAllFolderIds(entry.children, cache, out);
277
335
  }
278
336
  }
279
- }, "collectAllIds");
280
- function TreeProvider(props) {
281
- const {
282
- data,
283
- getItemName,
284
- loadChildren,
285
- selectionMode = "single",
286
- activationMode = "single-click",
287
- initialExpandedIds,
288
- initialSelectedIds,
289
- indent,
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
- dispatch({ type: "cache-tick" });
359
+ bumpCacheTick();
339
360
  const promise = (async () => {
340
361
  try {
341
- const children2 = await loadChildren(node);
342
- cacheRef.current.set(node.id, { status: "loaded", children: children2 });
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
- dispatch({ type: "cache-tick" });
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,1056 @@ function TreeProvider(props) {
370
391
  }, "walk");
371
392
  walk(data);
372
393
  return map;
373
- }, [data, state.cacheTick]);
394
+ }, [data, cacheTick]);
374
395
  useEffect(() => {
375
396
  if (!loadChildren) return;
376
- for (const id of state.expanded) {
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, state.expanded, state.cacheTick, nodeById, fetchChildren]);
382
- const flatRows = useMemo(
383
- () => flattenTree({
384
- roots: data,
385
- expandedIds: state.expanded,
386
- cache: cacheRef.current,
387
- filterNode
388
- }),
389
- [data, state.expanded, state.cacheTick, filterNode]
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 matchingIds = useMemo(() => {
392
- const set = /* @__PURE__ */ new Set();
393
- if (!enableSearch || state.query.trim() === "") return set;
394
- const q = state.query.trim().toLowerCase();
395
- for (const row of flatRows) {
396
- if (getItemName(row.node).toLowerCase().includes(q)) {
397
- set.add(row.node.id);
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;
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;
398
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
- return set;
401
- }, [enableSearch, state.query, flatRows, getItemName]);
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
+
1052
+ // src/tools/data/Tree/data/dnd.ts
1053
+ function resolveDropZone(input) {
1054
+ const { pointerY, rowRect, isFolder } = input;
1055
+ const offset = pointerY - rowRect.top;
1056
+ const ratio = rowRect.height > 0 ? offset / rowRect.height : 0.5;
1057
+ if (isFolder) {
1058
+ if (ratio < 0.33) return "before";
1059
+ if (ratio > 0.66) return "after";
1060
+ return "inside";
1061
+ }
1062
+ return ratio < 0.5 ? "before" : "after";
1063
+ }
1064
+ __name(resolveDropZone, "resolveDropZone");
1065
+ function defaultCanDrop(input) {
1066
+ const { source, target, position } = input;
1067
+ if (source.length === 0) return false;
1068
+ if (!target) return true;
1069
+ if (position === "inside") {
1070
+ const isFolder = Array.isArray(target.children) || !!target.isFolder;
1071
+ if (!isFolder) return false;
1072
+ }
1073
+ for (const node of source) {
1074
+ if (node.id === target.id) return false;
1075
+ if (isDescendant(node, target.id)) return false;
1076
+ }
1077
+ return true;
1078
+ }
1079
+ __name(defaultCanDrop, "defaultCanDrop");
1080
+ function isDescendant(root, id) {
1081
+ if (!Array.isArray(root.children)) return false;
1082
+ for (const child of root.children) {
1083
+ if (child.id === id) return true;
1084
+ if (isDescendant(child, id)) return true;
1085
+ }
1086
+ return false;
1087
+ }
1088
+ __name(isDescendant, "isDescendant");
1089
+ var TREE_DND_MIME = "application/x-djangocfg-tree";
1090
+ var TREE_ROOT_DROP_ID = "__tree_root_drop__";
1091
+
1092
+ // src/tools/data/Tree/context/dnd/use-dnd.ts
1093
+ function useDnd({
1094
+ enabled,
1095
+ adapter,
1096
+ nodeById,
1097
+ selected,
1098
+ labels,
1099
+ canDrop
1100
+ }) {
1101
+ const active = enabled && !!adapter?.move;
1102
+ const [draggingIds, setDraggingIds] = useState(
1103
+ () => /* @__PURE__ */ new Set()
1104
+ );
1105
+ const [dropTarget, setDropTarget] = useState(null);
1106
+ const beginDrag = useCallback(
1107
+ (rowId) => {
1108
+ if (!active) return;
1109
+ const ids = selected.has(rowId) ? new Set(selected) : /* @__PURE__ */ new Set([rowId]);
1110
+ setDraggingIds(ids);
1111
+ },
1112
+ [active, selected]
1113
+ );
1114
+ const cancelDrag = useCallback(() => {
1115
+ setDraggingIds(/* @__PURE__ */ new Set());
1116
+ setDropTarget(null);
1117
+ }, []);
1118
+ const resolveSourceNodes = useCallback(() => {
1119
+ const out = [];
1120
+ for (const id of draggingIds) {
1121
+ const node = nodeById.get(id);
1122
+ if (node) out.push(node);
1123
+ }
1124
+ return out;
1125
+ }, [draggingIds, nodeById]);
1126
+ const isAllowedDrop = useCallback(
1127
+ (target, position) => {
1128
+ if (!active) return false;
1129
+ if (draggingIds.size === 0) return false;
1130
+ const source = resolveSourceNodes();
1131
+ if (!defaultCanDrop({
1132
+ source,
1133
+ target,
1134
+ position})) {
1135
+ return false;
1136
+ }
1137
+ return canDrop?.({ source, target, position }) ?? true;
1138
+ },
1139
+ [active, draggingIds, resolveSourceNodes, nodeById, canDrop]
1140
+ );
1141
+ const commitDrop = useCallback(async () => {
1142
+ if (!active || !adapter?.move) {
1143
+ cancelDrag();
1144
+ return;
1145
+ }
1146
+ const t = dropTarget;
1147
+ if (!t) {
1148
+ cancelDrag();
1149
+ return;
1150
+ }
1151
+ const targetNode = t.id ? nodeById.get(t.id) ?? null : null;
1152
+ const source = resolveSourceNodes();
1153
+ if (source.length === 0) {
1154
+ cancelDrag();
1155
+ return;
1156
+ }
1157
+ if (!isAllowedDrop(targetNode, t.position)) {
1158
+ cancelDrag();
1159
+ return;
1160
+ }
1161
+ try {
1162
+ await adapter.move(source, targetNode, t.position);
1163
+ } catch (e) {
1164
+ const dialog = getDialog();
1165
+ await dialog?.alert({
1166
+ title: labels.error,
1167
+ message: e instanceof Error ? e.message : String(e)
1168
+ });
1169
+ } finally {
1170
+ cancelDrag();
1171
+ }
1172
+ }, [
1173
+ active,
1174
+ adapter,
1175
+ cancelDrag,
1176
+ dropTarget,
1177
+ isAllowedDrop,
1178
+ labels,
1179
+ nodeById,
1180
+ resolveSourceNodes
1181
+ ]);
1182
+ return useMemo(
1183
+ () => ({
1184
+ active,
1185
+ draggingIds,
1186
+ dropTarget,
1187
+ beginDrag,
1188
+ setDropTarget,
1189
+ commitDrop,
1190
+ cancelDrag,
1191
+ isAllowedDrop
1192
+ }),
1193
+ [
1194
+ active,
1195
+ draggingIds,
1196
+ dropTarget,
1197
+ beginDrag,
1198
+ commitDrop,
1199
+ cancelDrag,
1200
+ isAllowedDrop
1201
+ ]
1202
+ );
1203
+ }
1204
+ __name(useDnd, "useDnd");
1205
+ function usePersistSync({
1206
+ expanded,
1207
+ selected,
1208
+ persistKey,
1209
+ persistSelection,
1210
+ onSelectionChange,
1211
+ onExpansionChange
1212
+ }) {
402
1213
  const onSelectionChangeRef = useRef(onSelectionChange);
403
1214
  const onExpansionChangeRef = useRef(onExpansionChange);
404
- const onActivateRef = useRef(onActivate);
405
1215
  onSelectionChangeRef.current = onSelectionChange;
406
1216
  onExpansionChangeRef.current = onExpansionChange;
407
- onActivateRef.current = onActivate;
408
- const lastSelectedArrRef = useRef([...state.selected]);
409
- const lastExpandedArrRef = useRef([...state.expanded]);
1217
+ const lastSelectedArrRef = useRef([...selected]);
1218
+ const lastExpandedArrRef = useRef([...expanded]);
410
1219
  useEffect(() => {
411
- const arr = [...state.expanded];
412
- if (!setEqualsArr(state.expanded, lastExpandedArrRef.current)) {
1220
+ const arr = [...expanded];
1221
+ if (!setEqualsArr(expanded, lastExpandedArrRef.current)) {
413
1222
  lastExpandedArrRef.current = arr;
414
1223
  onExpansionChangeRef.current?.(arr);
415
1224
  if (persistKey) {
416
1225
  saveTreeState(persistKey, {
417
1226
  expandedItems: arr,
418
- selectedItems: persistSelection ? [...state.selected] : []
1227
+ selectedItems: persistSelection ? [...selected] : []
419
1228
  });
420
1229
  }
421
1230
  }
422
- }, [state.expanded, persistKey, persistSelection, state.selected]);
1231
+ }, [expanded, persistKey, persistSelection, selected]);
423
1232
  useEffect(() => {
424
- const arr = [...state.selected];
425
- if (!setEqualsArr(state.selected, lastSelectedArrRef.current)) {
1233
+ const arr = [...selected];
1234
+ if (!setEqualsArr(selected, lastSelectedArrRef.current)) {
426
1235
  lastSelectedArrRef.current = arr;
427
1236
  onSelectionChangeRef.current?.(arr);
428
1237
  if (persistKey && persistSelection) {
429
1238
  saveTreeState(persistKey, {
430
- expandedItems: [...state.expanded],
1239
+ expandedItems: [...expanded],
431
1240
  selectedItems: arr
432
1241
  });
433
1242
  }
434
1243
  }
435
- }, [state.selected, persistKey, persistSelection, state.expanded]);
436
- const expand = useCallback((id) => dispatch({ type: "expand", id }), []);
437
- const collapse = useCallback((id) => dispatch({ type: "collapse", id }), []);
438
- const toggle = useCallback((id) => dispatch({ type: "toggle", id }), []);
439
- const expandAll = useCallback(() => {
440
- const ids = [];
441
- collectAllIds(data, cacheRef.current, ids);
442
- dispatch({ type: "set-expanded", ids });
443
- }, [data]);
444
- const collapseAll = useCallback(
445
- () => dispatch({ type: "set-expanded", ids: [] }),
446
- []
447
- );
448
- const select = useCallback(
449
- (id) => dispatch({ type: "select", id, mode: selectionMode }),
450
- [selectionMode]
451
- );
452
- const setSelectedIds = useCallback(
453
- (ids) => dispatch({ type: "select-many", ids }),
454
- []
1244
+ }, [selected, persistKey, persistSelection, expanded]);
1245
+ }
1246
+ __name(usePersistSync, "usePersistSync");
1247
+ function setEqualsArr(set, arr) {
1248
+ if (set.size !== arr.length) return false;
1249
+ for (const id of arr) if (!set.has(id)) return false;
1250
+ return true;
1251
+ }
1252
+ __name(setEqualsArr, "setEqualsArr");
1253
+ var TreeContext = createContext(null);
1254
+ function useTreeContext() {
1255
+ const ctx = React.useContext(TreeContext);
1256
+ if (!ctx) {
1257
+ throw new Error("useTreeContext must be used inside <TreeProvider>");
1258
+ }
1259
+ return ctx;
1260
+ }
1261
+ __name(useTreeContext, "useTreeContext");
1262
+ function TreeProvider(props) {
1263
+ const {
1264
+ data,
1265
+ getItemName,
1266
+ loadChildren,
1267
+ selectionMode = "single",
1268
+ activationMode = "single-click",
1269
+ initialExpandedIds,
1270
+ initialSelectedIds,
1271
+ indent,
1272
+ appearance,
1273
+ onSelectionChange,
1274
+ onExpansionChange,
1275
+ onActivate,
1276
+ filterNode,
1277
+ enableSearch = false,
1278
+ showIndentGuides = false,
1279
+ renderIcon,
1280
+ renderLabel,
1281
+ renderActions,
1282
+ renderContextMenu,
1283
+ contextMenuActions,
1284
+ labels: labelsOverride,
1285
+ persistKey,
1286
+ persistSelection = false,
1287
+ adapter,
1288
+ defaultMenuItems,
1289
+ enableInlineRename = false,
1290
+ enableDnD = false,
1291
+ canDrop,
1292
+ children
1293
+ } = props;
1294
+ const labels = useMemo(
1295
+ () => ({ ...DEFAULT_TREE_LABELS, ...labelsOverride }),
1296
+ [labelsOverride]
455
1297
  );
456
- const clearSelection = useCallback(() => dispatch({ type: "clear-selection" }), []);
457
- const setFocus = useCallback(
458
- (id) => dispatch({ type: "focus", id }),
459
- []
1298
+ const resolvedAppearance = useMemo(
1299
+ () => resolveAppearance(appearance, indent),
1300
+ [appearance, indent]
460
1301
  );
461
- const setQuery = useCallback((q) => dispatch({ type: "set-query", q }), []);
462
- const refresh = useCallback(
463
- async (id) => {
464
- const node = nodeById.get(id);
465
- if (!node || !loadChildren) return;
466
- cacheRef.current.delete(id);
467
- dispatch({ type: "cache-tick" });
468
- await fetchChildren(node);
469
- },
470
- [nodeById, loadChildren, fetchChildren]
1302
+ const persisted = useMemo(
1303
+ () => persistKey ? loadTreeState(persistKey) : null,
1304
+ [persistKey]
471
1305
  );
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]);
1306
+ const [state, dispatch] = useReducer(
1307
+ reducer,
1308
+ void 0,
1309
+ () => createInitialState({
1310
+ persisted,
1311
+ initialExpandedIds,
1312
+ initialSelectedIds,
1313
+ persistSelection
1314
+ })
1315
+ );
1316
+ const bumpCacheTick = useCallback(() => dispatch({ type: "cache-tick" }), []);
1317
+ const {
1318
+ nodeById,
1319
+ refresh,
1320
+ refreshAll,
1321
+ collectFolderIds,
1322
+ cache
1323
+ } = useAsyncChildren({
1324
+ data,
1325
+ loadChildren,
1326
+ expanded: state.expanded,
1327
+ cacheTick: state.cacheTick,
1328
+ bumpCacheTick
1329
+ });
1330
+ const flatRows = useMemo(
1331
+ () => flattenTree({
1332
+ roots: data,
1333
+ expandedIds: state.expanded,
1334
+ cache,
1335
+ filterNode
1336
+ }),
1337
+ [data, state.expanded, state.cacheTick, cache, filterNode]
1338
+ );
1339
+ const matchingIds = useMemo(() => {
1340
+ const set = /* @__PURE__ */ new Set();
1341
+ if (!enableSearch || state.query.trim() === "") return set;
1342
+ const q = state.query.trim().toLowerCase();
1343
+ for (const row of flatRows) {
1344
+ if (getItemName(row.node).toLowerCase().includes(q)) {
1345
+ set.add(row.node.id);
1346
+ }
1347
+ }
1348
+ return set;
1349
+ }, [enableSearch, state.query, flatRows, getItemName]);
1350
+ const expansion = useExpansion({ dispatch, collectFolderIds });
1351
+ const selection = useSelection({
1352
+ dispatch,
1353
+ selectionMode,
1354
+ flatRows,
1355
+ selected: state.selected,
1356
+ anchor: state.anchor,
1357
+ focused: state.focused
1358
+ });
1359
+ const rename = useRename({
1360
+ dispatch,
1361
+ adapter,
1362
+ enableInlineRename,
1363
+ nodeById,
1364
+ getItemName,
1365
+ labels
1366
+ });
1367
+ const clipboard = useClipboard({
1368
+ dispatch,
1369
+ clipboard: state.clipboard,
1370
+ adapter,
1371
+ nodeById,
1372
+ labels
1373
+ });
1374
+ const dnd = useDnd({
1375
+ enabled: enableDnD,
1376
+ adapter,
1377
+ nodeById,
1378
+ selected: state.selected,
1379
+ labels,
1380
+ canDrop
1381
+ });
1382
+ const onActivateRef = useRef(onActivate);
1383
+ onActivateRef.current = onActivate;
483
1384
  const activate = useCallback(
484
1385
  (node, opts = { preview: false }) => onActivateRef.current?.(node, opts),
485
1386
  []
486
1387
  );
1388
+ const setQuery = useCallback(
1389
+ (q) => dispatch({ type: "set-query", q }),
1390
+ []
1391
+ );
1392
+ usePersistSync({
1393
+ expanded: state.expanded,
1394
+ selected: state.selected,
1395
+ persistKey,
1396
+ persistSelection,
1397
+ onSelectionChange,
1398
+ onExpansionChange
1399
+ });
1400
+ const resolvedContextMenuActions = useResolvedMenu({
1401
+ adapter,
1402
+ contextMenuActions,
1403
+ defaultMenuItems,
1404
+ labels,
1405
+ selected: state.selected,
1406
+ clipboard: state.clipboard,
1407
+ nodeById,
1408
+ getItemName,
1409
+ enableInlineRename,
1410
+ startRename: rename.startRename,
1411
+ cutToClipboard: clipboard.cutToClipboard,
1412
+ copyToClipboard: clipboard.copyToClipboard,
1413
+ pasteFromClipboard: clipboard.pasteFromClipboard
1414
+ });
487
1415
  const value = useMemo(
488
1416
  () => ({
1417
+ // state
489
1418
  expanded: state.expanded,
490
1419
  selected: state.selected,
1420
+ anchor: state.anchor,
491
1421
  focused: state.focused,
492
1422
  query: state.query,
1423
+ renamingId: state.renaming,
1424
+ inlineRenameEnabled: rename.enabled,
1425
+ clipboard: state.clipboard,
493
1426
  flatRows,
494
1427
  matchingIds,
495
- expand,
496
- collapse,
497
- toggle,
498
- expandAll,
499
- collapseAll,
500
- select,
501
- setSelectedIds,
502
- clearSelection,
503
- setFocus,
1428
+ // expansion
1429
+ ...expansion,
1430
+ // selection (note: `select` is from selection hook; expansion exports no `select`)
1431
+ ...selection,
504
1432
  setQuery,
1433
+ // clipboard
1434
+ ...clipboard,
1435
+ // rename
1436
+ startRename: rename.startRename,
1437
+ cancelRename: rename.cancelRename,
1438
+ commitRename: rename.commitRename,
1439
+ // async
505
1440
  refresh,
506
1441
  refreshAll,
507
1442
  activate,
1443
+ // config
508
1444
  labels,
509
1445
  appearance: resolvedAppearance,
510
1446
  indent: resolvedAppearance.indent,
@@ -513,28 +1449,34 @@ function TreeProvider(props) {
513
1449
  enableSearch,
514
1450
  showIndentGuides,
515
1451
  getItemName,
1452
+ // slots
516
1453
  renderIcon,
517
1454
  renderLabel,
518
1455
  renderActions,
519
- renderContextMenu
1456
+ renderContextMenu,
1457
+ adapter,
1458
+ resolvedContextMenuActions,
1459
+ getNodeById: /* @__PURE__ */ __name((id) => nodeById.get(id), "getNodeById"),
1460
+ dnd
520
1461
  }),
521
1462
  [
522
1463
  state.expanded,
523
1464
  state.selected,
1465
+ state.anchor,
524
1466
  state.focused,
525
1467
  state.query,
1468
+ state.renaming,
1469
+ state.clipboard,
1470
+ rename.enabled,
1471
+ rename.startRename,
1472
+ rename.cancelRename,
1473
+ rename.commitRename,
526
1474
  flatRows,
527
1475
  matchingIds,
528
- expand,
529
- collapse,
530
- toggle,
531
- expandAll,
532
- collapseAll,
533
- select,
534
- setSelectedIds,
535
- clearSelection,
536
- setFocus,
1476
+ expansion,
1477
+ selection,
537
1478
  setQuery,
1479
+ clipboard,
538
1480
  refresh,
539
1481
  refreshAll,
540
1482
  activate,
@@ -548,12 +1490,98 @@ function TreeProvider(props) {
548
1490
  renderIcon,
549
1491
  renderLabel,
550
1492
  renderActions,
551
- renderContextMenu
1493
+ renderContextMenu,
1494
+ adapter,
1495
+ resolvedContextMenuActions,
1496
+ nodeById,
1497
+ dnd
552
1498
  ]
553
1499
  );
554
1500
  return /* @__PURE__ */ jsx(TreeContext.Provider, { value, children });
555
1501
  }
556
1502
  __name(TreeProvider, "TreeProvider");
1503
+ function TreeDndProvider({ children }) {
1504
+ const ctx = useTreeContext();
1505
+ const { dnd } = ctx;
1506
+ const sensors = useSensors(
1507
+ useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
1508
+ useSensor(KeyboardSensor)
1509
+ );
1510
+ const cursorYRef = useRef(0);
1511
+ const handleDragStart = useCallback(
1512
+ (e) => {
1513
+ dnd.beginDrag(e.active.id);
1514
+ },
1515
+ [dnd]
1516
+ );
1517
+ const handleDragMove = useCallback(
1518
+ (e) => {
1519
+ const overId = e.over?.id;
1520
+ if (overId === TREE_ROOT_DROP_ID) {
1521
+ const current2 = dnd.dropTarget;
1522
+ if (current2?.id !== null || current2?.position !== "inside") {
1523
+ dnd.setDropTarget({ id: null, position: "inside" });
1524
+ }
1525
+ return;
1526
+ }
1527
+ if (typeof overId !== "string") {
1528
+ if (dnd.dropTarget !== null) dnd.setDropTarget(null);
1529
+ return;
1530
+ }
1531
+ const rect = e.over?.rect;
1532
+ if (!rect) return;
1533
+ const el = document.querySelector(
1534
+ `[data-tree-row][data-id="${CSS.escape(overId)}"]`
1535
+ );
1536
+ const isFolder = el?.dataset.folder === "true";
1537
+ const position = resolveDropZone({
1538
+ pointerY: cursorYRef.current,
1539
+ rowRect: { top: rect.top, bottom: rect.top + rect.height, height: rect.height },
1540
+ isFolder
1541
+ });
1542
+ const current = dnd.dropTarget;
1543
+ if (current?.id !== overId || current.position !== position) {
1544
+ dnd.setDropTarget({ id: overId, position });
1545
+ }
1546
+ },
1547
+ [dnd]
1548
+ );
1549
+ const handleDragEnd = useCallback(
1550
+ async (_e) => {
1551
+ await dnd.commitDrop();
1552
+ },
1553
+ [dnd]
1554
+ );
1555
+ const handleDragCancel = useCallback(() => {
1556
+ dnd.cancelDrag();
1557
+ }, [dnd]);
1558
+ const handlePointerMove = useCallback((e) => {
1559
+ cursorYRef.current = e.clientY;
1560
+ }, []);
1561
+ if (!dnd.active) {
1562
+ return /* @__PURE__ */ jsx(Fragment, { children });
1563
+ }
1564
+ return /* @__PURE__ */ jsx(
1565
+ DndContext,
1566
+ {
1567
+ sensors,
1568
+ onDragStart: handleDragStart,
1569
+ onDragMove: handleDragMove,
1570
+ onDragEnd: handleDragEnd,
1571
+ onDragCancel: handleDragCancel,
1572
+ children: /* @__PURE__ */ jsx(
1573
+ "div",
1574
+ {
1575
+ onPointerMove: handlePointerMove,
1576
+ className: "contents",
1577
+ "data-tree-dnd-surface": "",
1578
+ children
1579
+ }
1580
+ )
1581
+ }
1582
+ );
1583
+ }
1584
+ __name(TreeDndProvider, "TreeDndProvider");
557
1585
  function TreeChevronRaw({ isExpanded, isFolder, className }) {
558
1586
  const { appearance } = useTreeContext();
559
1587
  const size = { width: "var(--tree-icon-size)", height: "var(--tree-icon-size)" };
@@ -583,6 +1611,48 @@ function TreeChevronRaw({ isExpanded, isFolder, className }) {
583
1611
  }
584
1612
  __name(TreeChevronRaw, "TreeChevronRaw");
585
1613
  var TreeChevron = memo(TreeChevronRaw);
1614
+ function TreeDropIndicator({
1615
+ position,
1616
+ indent,
1617
+ invalid = false
1618
+ }) {
1619
+ if (position === "inside") {
1620
+ return /* @__PURE__ */ jsx(
1621
+ "span",
1622
+ {
1623
+ "aria-hidden": true,
1624
+ "data-tree-drop": "inside",
1625
+ className: cn(
1626
+ "pointer-events-none absolute inset-0 rounded-sm ring-1",
1627
+ invalid ? "bg-destructive/10 ring-destructive/40" : "bg-primary/10 ring-primary/40"
1628
+ )
1629
+ }
1630
+ );
1631
+ }
1632
+ const isBefore = position === "before";
1633
+ return /* @__PURE__ */ jsx(
1634
+ "span",
1635
+ {
1636
+ "aria-hidden": true,
1637
+ "data-tree-drop": position,
1638
+ style: { paddingLeft: indent },
1639
+ className: cn(
1640
+ "pointer-events-none absolute right-0 left-0 h-px",
1641
+ isBefore ? "top-0" : "bottom-0"
1642
+ ),
1643
+ children: /* @__PURE__ */ jsx(
1644
+ "span",
1645
+ {
1646
+ className: cn(
1647
+ "block h-0.5 rounded-full",
1648
+ invalid ? "bg-destructive/70" : "bg-primary"
1649
+ )
1650
+ }
1651
+ )
1652
+ }
1653
+ );
1654
+ }
1655
+ __name(TreeDropIndicator, "TreeDropIndicator");
586
1656
  function TreeIconRaw({ isFolder, isExpanded, className }) {
587
1657
  const { appearance } = useTreeContext();
588
1658
  const Icon = isFolder ? isExpanded ? FolderOpen : Folder : File;
@@ -639,6 +1709,96 @@ function TreeLabelRaw({ children, isMatchingSearch, className }) {
639
1709
  }
640
1710
  __name(TreeLabelRaw, "TreeLabelRaw");
641
1711
  var TreeLabel = memo(TreeLabelRaw);
1712
+
1713
+ // src/tools/data/Tree/data/renameUtils.ts
1714
+ function splitFileName(name) {
1715
+ if (name.length === 0) return { base: "", ext: "" };
1716
+ if (name.startsWith(".")) {
1717
+ const rest = name.slice(1);
1718
+ const dot2 = rest.lastIndexOf(".");
1719
+ if (dot2 < 0) return { base: name, ext: "" };
1720
+ return { base: "." + rest.slice(0, dot2), ext: rest.slice(dot2) };
1721
+ }
1722
+ const dot = name.lastIndexOf(".");
1723
+ if (dot <= 0) return { base: name, ext: "" };
1724
+ return { base: name.slice(0, dot), ext: name.slice(dot) };
1725
+ }
1726
+ __name(splitFileName, "splitFileName");
1727
+ function autoSelectRange(name, isFolder) {
1728
+ if (isFolder) return [0, name.length];
1729
+ const { base } = splitFileName(name);
1730
+ return [0, base.length];
1731
+ }
1732
+ __name(autoSelectRange, "autoSelectRange");
1733
+ function TreeRenameInput({
1734
+ initialValue,
1735
+ isFolder,
1736
+ onCommit,
1737
+ onCancel,
1738
+ className
1739
+ }) {
1740
+ const [value, setValue] = useState(initialValue);
1741
+ const inputRef = useRef(null);
1742
+ const settledRef = useRef(false);
1743
+ useEffect(() => {
1744
+ const el = inputRef.current;
1745
+ if (!el) return;
1746
+ el.focus();
1747
+ const [start, end] = autoSelectRange(initialValue, isFolder);
1748
+ requestAnimationFrame(() => {
1749
+ try {
1750
+ el.setSelectionRange(start, end);
1751
+ } catch {
1752
+ }
1753
+ });
1754
+ }, []);
1755
+ const commit = /* @__PURE__ */ __name(() => {
1756
+ if (settledRef.current) return;
1757
+ settledRef.current = true;
1758
+ void onCommit(value);
1759
+ }, "commit");
1760
+ const cancel = /* @__PURE__ */ __name(() => {
1761
+ if (settledRef.current) return;
1762
+ settledRef.current = true;
1763
+ onCancel();
1764
+ }, "cancel");
1765
+ return /* @__PURE__ */ jsx(
1766
+ "input",
1767
+ {
1768
+ ref: inputRef,
1769
+ type: "text",
1770
+ value,
1771
+ onKeyDown: (e) => {
1772
+ e.stopPropagation();
1773
+ if (e.key === "Enter") {
1774
+ e.preventDefault();
1775
+ commit();
1776
+ } else if (e.key === "Escape") {
1777
+ e.preventDefault();
1778
+ cancel();
1779
+ }
1780
+ },
1781
+ onChange: (e) => setValue(e.target.value),
1782
+ onBlur: commit,
1783
+ onClick: (e) => e.stopPropagation(),
1784
+ onDoubleClick: (e) => e.stopPropagation(),
1785
+ onMouseDown: (e) => e.stopPropagation(),
1786
+ onContextMenu: (e) => e.stopPropagation(),
1787
+ className: cn(
1788
+ "min-w-0 flex-1 rounded-sm border border-primary/50 bg-background",
1789
+ "px-1 py-0 text-foreground outline-none",
1790
+ "focus:ring-1 focus:ring-primary/40",
1791
+ className
1792
+ ),
1793
+ style: {
1794
+ // Match the row's font metrics so the input doesn't visibly jolt.
1795
+ fontSize: "var(--tree-font-size)",
1796
+ height: "calc(var(--tree-row-height) - 4px)"
1797
+ }
1798
+ }
1799
+ );
1800
+ }
1801
+ __name(TreeRenameInput, "TreeRenameInput");
642
1802
  function TreeRowRaw({ row, className }) {
643
1803
  const ctx = useTreeContext();
644
1804
  const {
@@ -650,6 +1810,7 @@ function TreeRowRaw({ row, className }) {
650
1810
  matchingIds,
651
1811
  select,
652
1812
  setSelectedIds,
1813
+ clickSelect,
653
1814
  toggle,
654
1815
  setFocus,
655
1816
  activate,
@@ -657,13 +1818,19 @@ function TreeRowRaw({ row, className }) {
657
1818
  renderIcon,
658
1819
  renderLabel,
659
1820
  renderActions,
660
- renderContextMenu
1821
+ renderContextMenu,
1822
+ renamingId,
1823
+ commitRename,
1824
+ cancelRename,
1825
+ clipboard,
1826
+ dnd
661
1827
  } = ctx;
662
1828
  const { node, level, isFolder, isExpanded, isLoading, posInSet, setSize } = row;
663
1829
  const isSelected = selected.has(node.id);
664
1830
  const isFocused = focused === node.id;
665
1831
  const isMatchingSearch = matchingIds.has(node.id);
666
1832
  const isMultiSelect = ctx.selectionMode === "multiple";
1833
+ const isCut = clipboard?.kind === "cut" && clipboard.ids.includes(node.id);
667
1834
  const slot = {
668
1835
  node,
669
1836
  level,
@@ -674,30 +1841,57 @@ function TreeRowRaw({ row, className }) {
674
1841
  isLoading,
675
1842
  isMatchingSearch
676
1843
  };
1844
+ const isRenaming = renamingId === node.id;
1845
+ const isDragging = dnd.draggingIds.has(node.id);
1846
+ const dropTarget = dnd.dropTarget;
1847
+ const isDropTarget = dropTarget?.id === node.id;
1848
+ const dropPosition = isDropTarget ? dropTarget.position : null;
1849
+ const dndDisabled = !dnd.active || isRenaming || node.disabled;
1850
+ const draggable = useDraggable({ id: node.id, disabled: dndDisabled });
1851
+ const droppable = useDroppable({ id: node.id, disabled: dndDisabled });
1852
+ const setRowEl = useCallback(
1853
+ (el) => {
1854
+ draggable.setNodeRef(el);
1855
+ droppable.setNodeRef(el);
1856
+ },
1857
+ [draggable, droppable]
1858
+ );
1859
+ const isAllowedDrop = dropPosition && !isDragging ? dnd.isAllowedDrop(node, dropPosition) : true;
677
1860
  const handleClick = /* @__PURE__ */ __name((e) => {
678
- if (node.disabled) return;
1861
+ if (node.disabled || isRenaming) return;
679
1862
  setFocus(node.id);
680
- if (isMultiSelect && !(e.metaKey || e.ctrlKey)) {
681
- setSelectedIds([node.id]);
1863
+ if (isMultiSelect) {
1864
+ clickSelect(node.id, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
682
1865
  } else {
683
1866
  select(node.id);
684
1867
  }
685
1868
  if (isFolder) {
1869
+ if (e.shiftKey || e.metaKey || e.ctrlKey) return;
686
1870
  toggle(node.id);
687
1871
  } else if (activationMode === "single-click") {
1872
+ if (e.shiftKey || e.metaKey || e.ctrlKey) return;
688
1873
  activate(node, { preview: false });
689
1874
  } else if (activationMode === "single-click-preview") {
1875
+ if (e.shiftKey || e.metaKey || e.ctrlKey) return;
690
1876
  activate(node, { preview: true });
691
1877
  }
692
1878
  }, "handleClick");
693
1879
  const handleDoubleClick = /* @__PURE__ */ __name(() => {
694
- if (node.disabled) return;
1880
+ if (node.disabled || isRenaming) return;
695
1881
  if (isFolder) return;
696
1882
  activate(node, { preview: false });
697
1883
  }, "handleDoubleClick");
1884
+ const handleContextMenu = /* @__PURE__ */ __name(() => {
1885
+ if (node.disabled || isRenaming) return;
1886
+ setFocus(node.id);
1887
+ if (!isSelected) {
1888
+ setSelectedIds([node.id]);
1889
+ }
1890
+ }, "handleContextMenu");
698
1891
  const trigger = /* @__PURE__ */ jsxs(
699
1892
  "div",
700
1893
  {
1894
+ ref: dnd.active ? setRowEl : void 0,
701
1895
  id: treeRowDomId(node.id),
702
1896
  role: "treeitem",
703
1897
  "aria-level": level + 1,
@@ -710,6 +1904,8 @@ function TreeRowRaw({ row, className }) {
710
1904
  "data-id": node.id,
711
1905
  "data-activation-mode": activationMode,
712
1906
  "data-selected": isSelected ? "true" : void 0,
1907
+ "data-clipboard": isCut ? "cut" : void 0,
1908
+ "data-dragging": isDragging ? "true" : void 0,
713
1909
  "data-focused": isFocused && !isSelected ? "true" : void 0,
714
1910
  "data-folder": isFolder || void 0,
715
1911
  "data-expanded": isExpanded || void 0,
@@ -719,8 +1915,11 @@ function TreeRowRaw({ row, className }) {
719
1915
  height: "var(--tree-row-height)",
720
1916
  gap: "var(--tree-gap)"
721
1917
  },
1918
+ ...dnd.active ? draggable.listeners : {},
1919
+ ...dnd.active ? draggable.attributes : {},
722
1920
  onClick: handleClick,
723
1921
  onDoubleClick: handleDoubleClick,
1922
+ onContextMenu: handleContextMenu,
724
1923
  onFocus: () => setFocus(node.id),
725
1924
  className: cn(
726
1925
  "group/row relative flex w-full select-none items-center pr-2 text-left",
@@ -730,6 +1929,8 @@ function TreeRowRaw({ row, className }) {
730
1929
  rowStateClasses(appearance),
731
1930
  "focus-visible:ring-1 focus-visible:ring-ring/50",
732
1931
  isMatchingSearch && "ring-1 ring-primary/30",
1932
+ isCut && "opacity-60",
1933
+ isDragging && "opacity-40",
733
1934
  node.disabled && "opacity-50",
734
1935
  className
735
1936
  ),
@@ -744,6 +1945,14 @@ function TreeRowRaw({ row, className }) {
744
1945
  )
745
1946
  }
746
1947
  ) : null,
1948
+ dropPosition && !isDragging ? /* @__PURE__ */ jsx(
1949
+ TreeDropIndicator,
1950
+ {
1951
+ position: dropPosition,
1952
+ indent: 6 + level * appearance.indent,
1953
+ invalid: !isAllowedDrop
1954
+ }
1955
+ ) : null,
747
1956
  showIndentGuides && level > 0 ? /* @__PURE__ */ jsx(TreeIndentGuides, { level, indent: appearance.indent }) : null,
748
1957
  /* @__PURE__ */ jsx(TreeChevron, { isExpanded, isFolder }),
749
1958
  isLoading ? /* @__PURE__ */ jsx(
@@ -760,7 +1969,15 @@ function TreeRowRaw({ row, className }) {
760
1969
  {
761
1970
  className: "flex min-w-0 flex-1 items-center",
762
1971
  style: { gap: "var(--tree-gap)" },
763
- children: renderLabel ? renderLabel(slot) : /* @__PURE__ */ jsx(TreeLabel, { isMatchingSearch, children: getItemName(node) })
1972
+ children: renamingId === node.id ? /* @__PURE__ */ jsx(
1973
+ TreeRenameInput,
1974
+ {
1975
+ initialValue: getItemName(node),
1976
+ isFolder,
1977
+ onCommit: (next) => commitRename(node.id, next),
1978
+ onCancel: cancelRename
1979
+ }
1980
+ ) : renderLabel ? renderLabel(slot) : /* @__PURE__ */ jsx(TreeLabel, { isMatchingSearch, children: getItemName(node) })
764
1981
  }
765
1982
  ),
766
1983
  renderActions ? /* @__PURE__ */ jsx(
@@ -832,6 +2049,105 @@ function TreeContent({
832
2049
  );
833
2050
  }
834
2051
  __name(TreeContent, "TreeContent");
2052
+ function TreeEmptyArea({ className }) {
2053
+ const ctx = useTreeContext();
2054
+ const {
2055
+ adapter,
2056
+ labels,
2057
+ getItemName,
2058
+ clipboard,
2059
+ cutToClipboard,
2060
+ copyToClipboard,
2061
+ pasteFromClipboard,
2062
+ startRename,
2063
+ inlineRenameEnabled,
2064
+ dnd
2065
+ } = ctx;
2066
+ const { setNodeRef: setDroppableRef, isOver } = useDroppable({
2067
+ id: TREE_ROOT_DROP_ID,
2068
+ disabled: !dnd.active
2069
+ });
2070
+ const items = useMemo(() => {
2071
+ if (!adapter) return null;
2072
+ const builtinCtx = {
2073
+ adapter,
2074
+ labels,
2075
+ selectedNodes: [],
2076
+ targetNode: null,
2077
+ getName: getItemName,
2078
+ startInlineRename: inlineRenameEnabled ? startRename : void 0,
2079
+ clipboard: {
2080
+ hasItems: !!clipboard && clipboard.ids.length > 0,
2081
+ cut: cutToClipboard,
2082
+ copy: copyToClipboard,
2083
+ paste: /* @__PURE__ */ __name(() => pasteFromClipboard(null, "inside"), "paste")
2084
+ }
2085
+ };
2086
+ return buildDefaultMenuItems(builtinCtx);
2087
+ }, [
2088
+ adapter,
2089
+ labels,
2090
+ getItemName,
2091
+ inlineRenameEnabled,
2092
+ startRename,
2093
+ clipboard,
2094
+ cutToClipboard,
2095
+ copyToClipboard,
2096
+ pasteFromClipboard
2097
+ ]);
2098
+ const surface = /* @__PURE__ */ jsx(
2099
+ "div",
2100
+ {
2101
+ ref: dnd.active ? setDroppableRef : void 0,
2102
+ "data-tree-empty-area": "",
2103
+ "data-drop-active": dnd.active && isOver ? "true" : void 0,
2104
+ className: cn(
2105
+ // Soaks up the remaining vertical space inside the scroll
2106
+ // container so right-click on whitespace lands here, not on
2107
+ // the scroll viewport.
2108
+ "min-h-[2.5rem] flex-1",
2109
+ dnd.active && isOver && "bg-primary/5 rounded-sm ring-1 ring-primary/30",
2110
+ className
2111
+ )
2112
+ }
2113
+ );
2114
+ if (!items || items.length === 0) {
2115
+ return surface;
2116
+ }
2117
+ return /* @__PURE__ */ jsxs(ContextMenu, { children: [
2118
+ /* @__PURE__ */ jsx(ContextMenuTrigger, { asChild: true, children: surface }),
2119
+ /* @__PURE__ */ jsx(ContextMenuContent, { children: items.map((item, idx) => {
2120
+ if (item === "separator") {
2121
+ return /* @__PURE__ */ jsx(ContextMenuSeparator, {}, `sep-${idx}`);
2122
+ }
2123
+ const Icon = item.icon;
2124
+ return /* @__PURE__ */ jsxs(
2125
+ ContextMenuItem,
2126
+ {
2127
+ disabled: item.disabled,
2128
+ variant: item.destructive ? "destructive" : void 0,
2129
+ onSelect: () => item.onSelect({
2130
+ node: void 0,
2131
+ level: 0,
2132
+ isSelected: false,
2133
+ isExpanded: false,
2134
+ isFocused: false,
2135
+ isFolder: false,
2136
+ isLoading: false,
2137
+ isMatchingSearch: false
2138
+ }),
2139
+ children: [
2140
+ Icon ? /* @__PURE__ */ jsx(Icon, {}) : null,
2141
+ item.label,
2142
+ item.shortcut ? /* @__PURE__ */ jsx(ContextMenuShortcut, { children: item.shortcut }) : null
2143
+ ]
2144
+ },
2145
+ item.id
2146
+ );
2147
+ }) })
2148
+ ] });
2149
+ }
2150
+ __name(TreeEmptyArea, "TreeEmptyArea");
835
2151
  function useTreeLabels() {
836
2152
  return useTreeContext().labels;
837
2153
  }
@@ -850,12 +2166,26 @@ function useTreeSelection() {
850
2166
  return useMemo(
851
2167
  () => ({
852
2168
  selectedIds,
2169
+ anchor: ctx.anchor,
853
2170
  select: ctx.select,
854
2171
  setSelectedIds: ctx.setSelectedIds,
855
2172
  clear: ctx.clearSelection,
2173
+ clickSelect: ctx.clickSelect,
2174
+ moveSelect: ctx.moveSelect,
2175
+ selectAll: ctx.selectAll,
856
2176
  isSelected
857
2177
  }),
858
- [selectedIds, ctx.select, ctx.setSelectedIds, ctx.clearSelection, isSelected]
2178
+ [
2179
+ selectedIds,
2180
+ ctx.anchor,
2181
+ ctx.select,
2182
+ ctx.setSelectedIds,
2183
+ ctx.clearSelection,
2184
+ ctx.clickSelect,
2185
+ ctx.moveSelect,
2186
+ ctx.selectAll,
2187
+ isSelected
2188
+ ]
859
2189
  );
860
2190
  }
861
2191
  __name(useTreeSelection, "useTreeSelection");
@@ -910,6 +2240,59 @@ function useTreeSearch() {
910
2240
  );
911
2241
  }
912
2242
  __name(useTreeSearch, "useTreeSearch");
2243
+ function useTreeDnd() {
2244
+ const ctx = useTreeContext();
2245
+ return ctx.dnd;
2246
+ }
2247
+ __name(useTreeDnd, "useTreeDnd");
2248
+ function useTreeClipboard() {
2249
+ const ctx = useTreeContext();
2250
+ const isCut = useCallback(
2251
+ (id) => ctx.clipboard?.kind === "cut" && ctx.clipboard.ids.includes(id),
2252
+ [ctx.clipboard]
2253
+ );
2254
+ return useMemo(
2255
+ () => ({
2256
+ clipboard: ctx.clipboard,
2257
+ isCut,
2258
+ cut: ctx.cutToClipboard,
2259
+ copy: ctx.copyToClipboard,
2260
+ paste: ctx.pasteFromClipboard,
2261
+ clear: ctx.clearClipboard
2262
+ }),
2263
+ [
2264
+ ctx.clipboard,
2265
+ isCut,
2266
+ ctx.cutToClipboard,
2267
+ ctx.copyToClipboard,
2268
+ ctx.pasteFromClipboard,
2269
+ ctx.clearClipboard
2270
+ ]
2271
+ );
2272
+ }
2273
+ __name(useTreeClipboard, "useTreeClipboard");
2274
+ function useTreeRename() {
2275
+ const ctx = useTreeContext();
2276
+ return useMemo(
2277
+ () => ({
2278
+ /** True when the host allowed inline rename AND the adapter exposes `rename`. */
2279
+ enabled: ctx.inlineRenameEnabled,
2280
+ /** Currently renaming id, or `null`. */
2281
+ renamingId: ctx.renamingId,
2282
+ startRename: ctx.startRename,
2283
+ cancelRename: ctx.cancelRename,
2284
+ commitRename: ctx.commitRename
2285
+ }),
2286
+ [
2287
+ ctx.inlineRenameEnabled,
2288
+ ctx.renamingId,
2289
+ ctx.startRename,
2290
+ ctx.cancelRename,
2291
+ ctx.commitRename
2292
+ ]
2293
+ );
2294
+ }
2295
+ __name(useTreeRename, "useTreeRename");
913
2296
  function useTreeActions() {
914
2297
  const ctx = useTreeContext();
915
2298
  return useMemo(
@@ -974,16 +2357,78 @@ function TreeSearchInput({ className, showMatches = true }) {
974
2357
  );
975
2358
  }
976
2359
  __name(TreeSearchInput, "TreeSearchInput");
2360
+
2361
+ // src/tools/data/Tree/hooks/keyboard/arrow-nav.ts
2362
+ function nextRowId(rows, idx) {
2363
+ if (rows.length === 0) return null;
2364
+ const next = rows[Math.min(idx + 1, rows.length - 1)] ?? rows[0];
2365
+ return next.node.id;
2366
+ }
2367
+ __name(nextRowId, "nextRowId");
2368
+ function prevRowId(rows, idx) {
2369
+ if (rows.length === 0) return null;
2370
+ const prev = rows[Math.max(idx - 1, 0)] ?? rows[0];
2371
+ return prev.node.id;
2372
+ }
2373
+ __name(prevRowId, "prevRowId");
2374
+ function edgeRowId(rows, edge) {
2375
+ if (rows.length === 0) return null;
2376
+ return edge === "first" ? rows[0].node.id : rows[rows.length - 1].node.id;
2377
+ }
2378
+ __name(edgeRowId, "edgeRowId");
2379
+
2380
+ // src/tools/data/Tree/hooks/keyboard/expand-collapse.ts
2381
+ function resolveRightArrow(current, rows, idx) {
2382
+ if (!current) return { kind: "noop" };
2383
+ if (current.isFolder && !current.isExpanded) {
2384
+ return { kind: "expand", id: current.node.id };
2385
+ }
2386
+ if (current.isFolder && current.isExpanded) {
2387
+ const next = rows[idx + 1];
2388
+ return next ? { kind: "focus", id: next.node.id } : { kind: "noop" };
2389
+ }
2390
+ return { kind: "noop" };
2391
+ }
2392
+ __name(resolveRightArrow, "resolveRightArrow");
2393
+ function resolveLeftArrow(current) {
2394
+ if (!current) return { kind: "noop" };
2395
+ if (current.isFolder && current.isExpanded) {
2396
+ return { kind: "collapse", id: current.node.id };
2397
+ }
2398
+ if (current.parentId) {
2399
+ return { kind: "focus", id: current.parentId };
2400
+ }
2401
+ return { kind: "noop" };
2402
+ }
2403
+ __name(resolveLeftArrow, "resolveLeftArrow");
2404
+
2405
+ // src/tools/data/Tree/hooks/keyboard/activation.ts
2406
+ function resolveActivate(current) {
2407
+ if (!current) return { kind: "noop" };
2408
+ if (current.isFolder) {
2409
+ return {
2410
+ kind: "toggle-folder",
2411
+ id: current.node.id,
2412
+ willExpand: !current.isExpanded
2413
+ };
2414
+ }
2415
+ return { kind: "activate-leaf", id: current.node.id };
2416
+ }
2417
+ __name(resolveActivate, "resolveActivate");
2418
+
2419
+ // src/tools/data/Tree/hooks/keyboard/use-tree-keyboard.ts
977
2420
  function useTreeKeyboard({
978
2421
  rows,
979
2422
  focusedId,
980
2423
  enabled = true,
2424
+ multiSelect = false,
981
2425
  onFocus,
982
2426
  onSelect,
983
2427
  onActivate,
984
2428
  onExpand,
985
2429
  onCollapse,
986
- onClearSelection
2430
+ onClearSelection,
2431
+ onSelectAll
987
2432
  }) {
988
2433
  const rowsRef = useRef(rows);
989
2434
  const focusedIdRef = useRef(focusedId);
@@ -996,53 +2441,65 @@ function useTreeKeyboard({
996
2441
  return { rows: r, idx, current: idx >= 0 ? r[idx] : null };
997
2442
  }, "getCurrent");
998
2443
  const refDown = useHotkey(
999
- "down",
1000
- () => {
2444
+ ["down", "shift+down"],
2445
+ (e) => {
1001
2446
  const { rows: r, idx } = getCurrent();
1002
- if (r.length === 0) return;
1003
- const next = r[Math.min(idx + 1, r.length - 1)] ?? r[0];
1004
- onFocus(next.node.id);
2447
+ const id = nextRowId(r, idx);
2448
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
1005
2449
  },
1006
- { enabled, preventDefault: true, description: "Next row" }
2450
+ { enabled, preventDefault: true, description: "Next row (Shift extends)" }
1007
2451
  );
1008
2452
  const refUp = useHotkey(
1009
- "up",
1010
- () => {
2453
+ ["up", "shift+up"],
2454
+ (e) => {
1011
2455
  const { rows: r, idx } = getCurrent();
1012
- if (r.length === 0) return;
1013
- const prev = r[Math.max(idx - 1, 0)] ?? r[0];
1014
- onFocus(prev.node.id);
2456
+ const id = prevRowId(r, idx);
2457
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
1015
2458
  },
1016
- { enabled, preventDefault: true, description: "Previous row" }
2459
+ { enabled, preventDefault: true, description: "Previous row (Shift extends)" }
1017
2460
  );
1018
2461
  const refHome = useHotkey(
1019
- "home",
1020
- () => {
1021
- const { rows: r } = getCurrent();
1022
- if (r.length === 0) return;
1023
- onFocus(r[0].node.id);
2462
+ ["home", "shift+home"],
2463
+ (e) => {
2464
+ const id = edgeRowId(rowsRef.current, "first");
2465
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
1024
2466
  },
1025
- { enabled, preventDefault: true, description: "First row" }
2467
+ { enabled, preventDefault: true, description: "First row (Shift extends)" }
1026
2468
  );
1027
2469
  const refEnd = useHotkey(
1028
- "end",
2470
+ ["end", "shift+end"],
2471
+ (e) => {
2472
+ const id = edgeRowId(rowsRef.current, "last");
2473
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
2474
+ },
2475
+ { enabled, preventDefault: true, description: "Last row (Shift extends)" }
2476
+ );
2477
+ const refSelectAll = useHotkey(
2478
+ "mod+a",
1029
2479
  () => {
1030
- const { rows: r } = getCurrent();
1031
- if (r.length === 0) return;
1032
- onFocus(r[r.length - 1].node.id);
2480
+ if (!multiSelect) return;
2481
+ onSelectAll?.();
1033
2482
  },
1034
- { enabled, preventDefault: true, description: "Last row" }
2483
+ {
2484
+ enabled: enabled && multiSelect,
2485
+ preventDefault: true,
2486
+ description: "Select all visible rows"
2487
+ }
1035
2488
  );
1036
2489
  const refRight = useHotkey(
1037
2490
  "right",
1038
2491
  () => {
1039
2492
  const { rows: r, idx, current } = getCurrent();
1040
- if (!current) return;
1041
- if (current.isFolder && !current.isExpanded) {
1042
- onExpand(current.node.id);
1043
- } else if (current.isFolder && current.isExpanded) {
1044
- const next = r[idx + 1];
1045
- if (next) onFocus(next.node.id);
2493
+ const out = resolveRightArrow(current, r, idx);
2494
+ switch (out.kind) {
2495
+ case "expand":
2496
+ onExpand(out.id);
2497
+ return;
2498
+ case "focus":
2499
+ onFocus(out.id, { extend: false });
2500
+ return;
2501
+ case "noop":
2502
+ return;
1046
2503
  }
1047
2504
  },
1048
2505
  { enabled, preventDefault: true, description: "Expand / first child" }
@@ -1051,11 +2508,16 @@ function useTreeKeyboard({
1051
2508
  "left",
1052
2509
  () => {
1053
2510
  const { current } = getCurrent();
1054
- if (!current) return;
1055
- if (current.isFolder && current.isExpanded) {
1056
- onCollapse(current.node.id);
1057
- } else if (current.parentId) {
1058
- onFocus(current.parentId);
2511
+ const out = resolveLeftArrow(current);
2512
+ switch (out.kind) {
2513
+ case "collapse":
2514
+ onCollapse(out.id);
2515
+ return;
2516
+ case "focus":
2517
+ onFocus(out.id, { extend: false });
2518
+ return;
2519
+ case "noop":
2520
+ return;
1059
2521
  }
1060
2522
  },
1061
2523
  { enabled, preventDefault: true, description: "Collapse / parent" }
@@ -1064,13 +2526,14 @@ function useTreeKeyboard({
1064
2526
  ["enter", "space"],
1065
2527
  () => {
1066
2528
  const { current } = getCurrent();
1067
- if (!current) return;
1068
- onSelect(current.node.id);
1069
- if (current.isFolder) {
1070
- if (current.isExpanded) onCollapse(current.node.id);
1071
- else onExpand(current.node.id);
2529
+ const out = resolveActivate(current);
2530
+ if (out.kind === "noop") return;
2531
+ onSelect(out.kind === "activate-leaf" ? out.id : out.id);
2532
+ if (out.kind === "toggle-folder") {
2533
+ if (out.willExpand) onExpand(out.id);
2534
+ else onCollapse(out.id);
1072
2535
  } else {
1073
- onActivate(current.node.id);
2536
+ onActivate(out.id);
1074
2537
  }
1075
2538
  },
1076
2539
  { enabled, preventDefault: true, description: "Activate / toggle" }
@@ -1090,12 +2553,44 @@ function useTreeKeyboard({
1090
2553
  refLeft(instance);
1091
2554
  refActivate(instance);
1092
2555
  refEscape(instance);
2556
+ refSelectAll(instance);
1093
2557
  },
1094
- [refDown, refUp, refHome, refEnd, refRight, refLeft, refActivate, refEscape]
2558
+ [
2559
+ refDown,
2560
+ refUp,
2561
+ refHome,
2562
+ refEnd,
2563
+ refRight,
2564
+ refLeft,
2565
+ refActivate,
2566
+ refEscape,
2567
+ refSelectAll
2568
+ ]
1095
2569
  );
1096
2570
  return { ref };
1097
2571
  }
1098
2572
  __name(useTreeKeyboard, "useTreeKeyboard");
2573
+
2574
+ // src/tools/data/Tree/hooks/type-ahead/match-prefix.ts
2575
+ function findRowByPrefix(rows, getName, prefix) {
2576
+ if (prefix.length === 0) return void 0;
2577
+ return rows.find((row) => getName(row.node).toLowerCase().startsWith(prefix));
2578
+ }
2579
+ __name(findRowByPrefix, "findRowByPrefix");
2580
+ function isResetKey(key) {
2581
+ return key === "Escape" || key === "Enter" || key === "Tab" || key.startsWith("Arrow") || key === "Home" || key === "End" || key === "PageUp" || key === "PageDown";
2582
+ }
2583
+ __name(isResetKey, "isResetKey");
2584
+ function isTypingTarget(target) {
2585
+ const el = target;
2586
+ if (!el) return false;
2587
+ const tag = el.tagName;
2588
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
2589
+ return el.isContentEditable === true;
2590
+ }
2591
+ __name(isTypingTarget, "isTypingTarget");
2592
+
2593
+ // src/tools/data/Tree/hooks/type-ahead/use-tree-type-ahead.ts
1099
2594
  var FLUSH_MS = 600;
1100
2595
  function useTreeTypeAhead({
1101
2596
  rows,
@@ -1124,11 +2619,9 @@ function useTreeTypeAhead({
1124
2619
  }
1125
2620
  }, "reset");
1126
2621
  const handler = /* @__PURE__ */ __name((e) => {
1127
- const tag = e.target?.tagName;
1128
- if (tag === "INPUT" || tag === "TEXTAREA") return;
1129
- if (e.target?.isContentEditable) return;
2622
+ if (isTypingTarget(e.target)) return;
1130
2623
  if (e.metaKey || e.ctrlKey || e.altKey) return;
1131
- if (e.key === "Escape" || e.key === "Enter" || e.key === "Tab" || e.key.startsWith("Arrow") || e.key === "Home" || e.key === "End" || e.key === "PageUp" || e.key === "PageDown") {
2624
+ if (isResetKey(e.key)) {
1132
2625
  reset();
1133
2626
  return;
1134
2627
  }
@@ -1136,9 +2629,10 @@ function useTreeTypeAhead({
1136
2629
  bufferRef.current += e.key.toLowerCase();
1137
2630
  if (timerRef.current) clearTimeout(timerRef.current);
1138
2631
  timerRef.current = setTimeout(reset, FLUSH_MS);
1139
- const prefix = bufferRef.current;
1140
- const hit = rowsRef.current.find(
1141
- (row) => getNameRef.current(row.node).toLowerCase().startsWith(prefix)
2632
+ const hit = findRowByPrefix(
2633
+ rowsRef.current,
2634
+ getNameRef.current,
2635
+ bufferRef.current
1142
2636
  );
1143
2637
  if (hit) {
1144
2638
  e.preventDefault();
@@ -1153,36 +2647,166 @@ function useTreeTypeAhead({
1153
2647
  }, [containerRef, enabled]);
1154
2648
  }
1155
2649
  __name(useTreeTypeAhead, "useTreeTypeAhead");
1156
- function actionsToRenderContextMenu(resolve) {
1157
- return (rowProps, trigger) => {
1158
- const items = resolve(rowProps);
1159
- if (!items || items.length === 0) return trigger;
1160
- return /* @__PURE__ */ jsxs(ContextMenu, { children: [
1161
- /* @__PURE__ */ jsx(ContextMenuTrigger, { asChild: true, children: trigger }),
1162
- /* @__PURE__ */ jsx(ContextMenuContent, { children: items.map((item, idx) => {
1163
- if (item === "separator") {
1164
- return /* @__PURE__ */ jsx(ContextMenuSeparator, {}, `sep-${idx}`);
1165
- }
1166
- const Icon = item.icon;
1167
- return /* @__PURE__ */ jsxs(
1168
- ContextMenuItem,
1169
- {
1170
- disabled: item.disabled,
1171
- variant: item.destructive ? "destructive" : void 0,
1172
- onSelect: () => item.onSelect(rowProps),
1173
- children: [
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
- ] });
2650
+
2651
+ // src/tools/data/Tree/hooks/finder-hotkeys/build-ctx.ts
2652
+ function buildBuiltinCtx(input) {
2653
+ if (!input.adapter) return null;
2654
+ const selectedNodes = [];
2655
+ for (const id of input.selected) {
2656
+ const n = input.getNodeById(id);
2657
+ if (n) selectedNodes.push(n);
2658
+ }
2659
+ const targetNode = input.focused ? input.getNodeById(input.focused) ?? null : null;
2660
+ return {
2661
+ adapter: input.adapter,
2662
+ labels: input.labels,
2663
+ selectedNodes,
2664
+ targetNode,
2665
+ getName: input.getItemName,
2666
+ startInlineRename: input.startInlineRename,
2667
+ clipboard: input.clipboard
1183
2668
  };
1184
2669
  }
1185
- __name(actionsToRenderContextMenu, "actionsToRenderContextMenu");
2670
+ __name(buildBuiltinCtx, "buildBuiltinCtx");
2671
+
2672
+ // src/tools/data/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts
2673
+ function useTreeFinderHotkeys(opts) {
2674
+ const optsRef = useRef(opts);
2675
+ optsRef.current = opts;
2676
+ const run = useCallback(async (action) => {
2677
+ const o = optsRef.current;
2678
+ if (o.paused) return;
2679
+ const ctx = buildBuiltinCtx({
2680
+ adapter: o.adapter,
2681
+ labels: o.labels,
2682
+ selected: o.selected,
2683
+ focused: o.focused,
2684
+ getNodeById: o.getNodeById,
2685
+ getItemName: o.getItemName,
2686
+ startInlineRename: o.startInlineRename,
2687
+ clipboard: o.clipboard
2688
+ });
2689
+ if (!ctx) return;
2690
+ await runBuiltinAction(action, ctx);
2691
+ }, []);
2692
+ const refDelete = useHotkey(
2693
+ ["mod+backspace", "delete"],
2694
+ () => void run("delete"),
2695
+ {
2696
+ enabled: opts.enabled,
2697
+ preventDefault: true,
2698
+ description: "Delete selected items",
2699
+ scope: "tree"
2700
+ }
2701
+ );
2702
+ const refRename = useHotkey("f2", () => void run("rename"), {
2703
+ enabled: opts.enabled,
2704
+ preventDefault: true,
2705
+ description: "Rename selected item",
2706
+ scope: "tree"
2707
+ });
2708
+ const refDuplicate = useHotkey("mod+d", () => void run("duplicate"), {
2709
+ enabled: opts.enabled,
2710
+ preventDefault: true,
2711
+ description: "Duplicate selected items",
2712
+ scope: "tree"
2713
+ });
2714
+ const refNewFolder = useHotkey("mod+shift+n", () => void run("new-folder"), {
2715
+ enabled: opts.enabled,
2716
+ preventDefault: true,
2717
+ description: "New folder",
2718
+ scope: "tree"
2719
+ });
2720
+ const refNewFile = useHotkey("mod+n", () => void run("new-file"), {
2721
+ enabled: opts.enabled,
2722
+ preventDefault: true,
2723
+ description: "New file",
2724
+ scope: "tree"
2725
+ });
2726
+ const refCut = useHotkey("mod+x", () => void run("cut"), {
2727
+ enabled: opts.enabled,
2728
+ preventDefault: true,
2729
+ description: "Cut",
2730
+ scope: "tree"
2731
+ });
2732
+ const refCopy = useHotkey("mod+c", () => void run("copy"), {
2733
+ enabled: opts.enabled,
2734
+ preventDefault: true,
2735
+ description: "Copy",
2736
+ scope: "tree"
2737
+ });
2738
+ const refPaste = useHotkey("mod+v", () => void run("paste"), {
2739
+ enabled: opts.enabled,
2740
+ preventDefault: true,
2741
+ description: "Paste",
2742
+ scope: "tree"
2743
+ });
2744
+ const ref = useCallback(
2745
+ (instance) => {
2746
+ refDelete(instance);
2747
+ refRename(instance);
2748
+ refDuplicate(instance);
2749
+ refNewFolder(instance);
2750
+ refNewFile(instance);
2751
+ refCut(instance);
2752
+ refCopy(instance);
2753
+ refPaste(instance);
2754
+ },
2755
+ [
2756
+ refDelete,
2757
+ refRename,
2758
+ refDuplicate,
2759
+ refNewFolder,
2760
+ refNewFile,
2761
+ refCut,
2762
+ refCopy,
2763
+ refPaste
2764
+ ]
2765
+ );
2766
+ return { ref };
2767
+ }
2768
+ __name(useTreeFinderHotkeys, "useTreeFinderHotkeys");
2769
+ function renderItemsAsContextMenu(rowProps, items, trigger) {
2770
+ return /* @__PURE__ */ jsxs(ContextMenu, { children: [
2771
+ /* @__PURE__ */ jsx(ContextMenuTrigger, { asChild: true, children: trigger }),
2772
+ /* @__PURE__ */ jsx(ContextMenuContent, { children: items.map((item, idx) => {
2773
+ if (item === "separator") {
2774
+ return /* @__PURE__ */ jsx(ContextMenuSeparator, {}, `sep-${idx}`);
2775
+ }
2776
+ const Icon = item.icon;
2777
+ return /* @__PURE__ */ jsxs(
2778
+ ContextMenuItem,
2779
+ {
2780
+ disabled: item.disabled,
2781
+ variant: item.destructive ? "destructive" : void 0,
2782
+ onSelect: () => item.onSelect(rowProps),
2783
+ children: [
2784
+ Icon ? /* @__PURE__ */ jsx(Icon, {}) : null,
2785
+ item.label,
2786
+ item.shortcut ? /* @__PURE__ */ jsx(ContextMenuShortcut, { children: item.shortcut }) : null
2787
+ ]
2788
+ },
2789
+ item.id
2790
+ );
2791
+ }) })
2792
+ ] });
2793
+ }
2794
+ __name(renderItemsAsContextMenu, "renderItemsAsContextMenu");
2795
+ function tidyMenuItems(items) {
2796
+ const out = [];
2797
+ for (const it of items) {
2798
+ if (it === "separator") {
2799
+ if (out.length === 0) continue;
2800
+ if (out[out.length - 1] === "separator") continue;
2801
+ out.push(it);
2802
+ } else {
2803
+ out.push(it);
2804
+ }
2805
+ }
2806
+ while (out.length > 0 && out[out.length - 1] === "separator") out.pop();
2807
+ return out;
2808
+ }
2809
+ __name(tidyMenuItems, "tidyMenuItems");
1186
2810
  function TreeRoot(props) {
1187
2811
  const {
1188
2812
  data,
@@ -1201,6 +2825,10 @@ function TreeRoot(props) {
1201
2825
  enableSearch = false,
1202
2826
  enableTypeAhead = true,
1203
2827
  showIndentGuides = false,
2828
+ enableInlineRename = false,
2829
+ enableFinderHotkeys = false,
2830
+ enableDnD = false,
2831
+ canDrop,
1204
2832
  renderRow,
1205
2833
  renderIcon,
1206
2834
  renderLabel,
@@ -1210,14 +2838,11 @@ function TreeRoot(props) {
1210
2838
  labels,
1211
2839
  persistKey,
1212
2840
  persistSelection = false,
2841
+ adapter,
2842
+ defaultMenuItems,
1213
2843
  className,
1214
2844
  style
1215
2845
  } = props;
1216
- const resolvedRenderContextMenu = useMemo(() => {
1217
- if (renderContextMenu) return renderContextMenu;
1218
- if (contextMenuActions) return actionsToRenderContextMenu(contextMenuActions);
1219
- return void 0;
1220
- }, [renderContextMenu, contextMenuActions]);
1221
2846
  return /* @__PURE__ */ jsx(
1222
2847
  TreeProvider,
1223
2848
  {
@@ -1239,7 +2864,13 @@ function TreeRoot(props) {
1239
2864
  renderIcon,
1240
2865
  renderLabel,
1241
2866
  renderActions,
1242
- renderContextMenu: resolvedRenderContextMenu,
2867
+ renderContextMenu,
2868
+ contextMenuActions,
2869
+ adapter,
2870
+ defaultMenuItems,
2871
+ enableInlineRename,
2872
+ enableDnD,
2873
+ canDrop,
1243
2874
  labels,
1244
2875
  persistKey,
1245
2876
  persistSelection,
@@ -1250,6 +2881,7 @@ function TreeRoot(props) {
1250
2881
  style,
1251
2882
  enableSearch,
1252
2883
  enableTypeAhead,
2884
+ enableFinderHotkeys,
1253
2885
  renderRow
1254
2886
  }
1255
2887
  )
@@ -1262,14 +2894,27 @@ function TreeRootShell({
1262
2894
  style,
1263
2895
  enableSearch,
1264
2896
  enableTypeAhead,
2897
+ enableFinderHotkeys,
1265
2898
  renderRow
1266
2899
  }) {
1267
2900
  const containerRef = useRef(null);
1268
2901
  const ctx = useTreeContext();
2902
+ const isMulti = ctx.selectionMode === "multiple";
1269
2903
  const { ref: keyboardRef } = useTreeKeyboard({
1270
2904
  rows: ctx.flatRows,
1271
2905
  focusedId: ctx.focused,
1272
- onFocus: ctx.setFocus,
2906
+ multiSelect: isMulti,
2907
+ // Pause container hotkeys while inline rename is active so the
2908
+ // user can type freely (TreeRenameInput stops bubbling already, but
2909
+ // gating here is the cleaner second line of defence).
2910
+ enabled: ctx.renamingId === null,
2911
+ onFocus: /* @__PURE__ */ __name((id, { extend }) => {
2912
+ if (extend && isMulti) {
2913
+ ctx.moveSelect(id, { extend: true });
2914
+ } else {
2915
+ ctx.setFocus(id);
2916
+ }
2917
+ }, "onFocus"),
1273
2918
  onSelect: ctx.select,
1274
2919
  onActivate: /* @__PURE__ */ __name((id) => {
1275
2920
  const row = ctx.flatRows.find((r) => r.node.id === id);
@@ -1277,14 +2922,37 @@ function TreeRootShell({
1277
2922
  }, "onActivate"),
1278
2923
  onExpand: ctx.expand,
1279
2924
  onCollapse: ctx.collapse,
1280
- onClearSelection: ctx.clearSelection
2925
+ onClearSelection: ctx.clearSelection,
2926
+ onSelectAll: ctx.selectAll
2927
+ });
2928
+ const { ref: finderHotkeysRef } = useTreeFinderHotkeys({
2929
+ enabled: enableFinderHotkeys,
2930
+ paused: ctx.renamingId !== null,
2931
+ adapter: ctx.adapter,
2932
+ labels: ctx.labels,
2933
+ selected: ctx.selected,
2934
+ focused: ctx.focused,
2935
+ getNodeById: ctx.getNodeById,
2936
+ getItemName: ctx.getItemName,
2937
+ startInlineRename: ctx.inlineRenameEnabled ? ctx.startRename : void 0,
2938
+ clipboard: {
2939
+ hasItems: !!ctx.clipboard && ctx.clipboard.ids.length > 0,
2940
+ cut: ctx.cutToClipboard,
2941
+ copy: ctx.copyToClipboard,
2942
+ // Hotkey paste targets the currently focused row (or null = root).
2943
+ paste: /* @__PURE__ */ __name(() => {
2944
+ const target = ctx.focused ? ctx.getNodeById(ctx.focused) ?? null : null;
2945
+ return ctx.pasteFromClipboard(target, "inside");
2946
+ }, "paste")
2947
+ }
1281
2948
  });
1282
2949
  const setContainerRef = useCallback(
1283
2950
  (instance) => {
1284
2951
  containerRef.current = instance;
1285
2952
  keyboardRef(instance);
2953
+ finderHotkeysRef(instance);
1286
2954
  },
1287
- [keyboardRef]
2955
+ [keyboardRef, finderHotkeysRef]
1288
2956
  );
1289
2957
  const focusedId = ctx.focused;
1290
2958
  useEffect(() => {
@@ -1307,7 +2975,22 @@ function TreeRootShell({
1307
2975
  onMatch: onTypeAheadMatch,
1308
2976
  enabled: enableTypeAhead
1309
2977
  });
1310
- return /* @__PURE__ */ jsxs(
2978
+ const finalRenderContextMenu = useMemo(() => {
2979
+ if (ctx.renderContextMenu) return ctx.renderContextMenu;
2980
+ const resolve = ctx.resolvedContextMenuActions;
2981
+ if (!resolve) return void 0;
2982
+ return (rowProps, trigger) => {
2983
+ const items = resolve(rowProps);
2984
+ const cleaned = items ? tidyMenuItems(items) : null;
2985
+ if (!cleaned || cleaned.length === 0) return trigger;
2986
+ return renderItemsAsContextMenu(rowProps, cleaned, trigger);
2987
+ };
2988
+ }, [ctx.renderContextMenu, ctx.resolvedContextMenuActions]);
2989
+ const childCtx = useMemo(
2990
+ () => ({ ...ctx, renderContextMenu: finalRenderContextMenu }),
2991
+ [ctx, finalRenderContextMenu]
2992
+ );
2993
+ const treeBody = /* @__PURE__ */ jsxs(
1311
2994
  "div",
1312
2995
  {
1313
2996
  ref: setContainerRef,
@@ -1325,13 +3008,35 @@ function TreeRootShell({
1325
3008
  "data-tree-root": "",
1326
3009
  children: [
1327
3010
  enableSearch ? /* @__PURE__ */ jsx(TreeSearchInput, { className: "mx-2 mt-2" }) : null,
1328
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto px-1", children: /* @__PURE__ */ jsx(TreeContent, { role: "group", children: renderRow }) })
3011
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-auto px-1", children: [
3012
+ /* @__PURE__ */ jsx(TreeContent, { role: "group", children: renderRow }),
3013
+ /* @__PURE__ */ jsx(TreeEmptyArea, {})
3014
+ ] })
1329
3015
  ]
1330
3016
  }
1331
3017
  );
3018
+ const body = finalRenderContextMenu === ctx.renderContextMenu ? treeBody : /* @__PURE__ */ jsx(TreeContext.Provider, { value: childCtx, children: treeBody });
3019
+ return /* @__PURE__ */ jsx(TreeDndProvider, { children: body });
1332
3020
  }
1333
3021
  __name(TreeRootShell, "TreeRootShell");
1334
3022
  var TreeRoot_default = TreeRoot;
3023
+ function FinderTree(props) {
3024
+ return /* @__PURE__ */ jsx(
3025
+ TreeRoot,
3026
+ {
3027
+ selectionMode: "multiple",
3028
+ activationMode: "double-click",
3029
+ enableInlineRename: true,
3030
+ enableFinderHotkeys: true,
3031
+ enableDnD: true,
3032
+ enableTypeAhead: true,
3033
+ showIndentGuides: true,
3034
+ appearance: { density: "cozy" },
3035
+ ...props
3036
+ }
3037
+ );
3038
+ }
3039
+ __name(FinderTree, "FinderTree");
1335
3040
  function TreeSkeleton({ rows = 6, className }) {
1336
3041
  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
3042
  /* @__PURE__ */ jsx("span", { className: "size-4 shrink-0 animate-pulse rounded bg-muted" }),
@@ -1383,6 +3088,6 @@ function createDemoTree({
1383
3088
  }
1384
3089
  __name(createDemoTree, "createDemoTree");
1385
3090
 
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 };
3091
+ 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
3092
  //# sourceMappingURL=index.mjs.map
1388
3093
  //# sourceMappingURL=index.mjs.map