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