@djangocfg/ui-tools 2.1.415 → 2.1.417

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