@djangocfg/ui-tools 2.1.413 → 2.1.416

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/file-icon/index.d.cts +1 -1
  2. package/dist/file-icon/index.d.ts +1 -1
  3. package/dist/slots-ClRpIzoh.d.cts +88 -0
  4. package/dist/slots-ClRpIzoh.d.ts +88 -0
  5. package/dist/tree/index.cjs +1994 -276
  6. package/dist/tree/index.cjs.map +1 -1
  7. package/dist/tree/index.d.cts +717 -72
  8. package/dist/tree/index.d.ts +717 -72
  9. package/dist/tree/index.mjs +1984 -279
  10. package/dist/tree/index.mjs.map +1 -1
  11. package/package.json +10 -6
  12. package/src/tools/chat/README.md +111 -1
  13. package/src/tools/chat/composer/Composer.tsx +138 -17
  14. package/src/tools/chat/composer/ComposerRichTextarea.tsx +25 -0
  15. package/src/tools/chat/composer/index.ts +22 -0
  16. package/src/tools/chat/composer/slash/README.md +187 -0
  17. package/src/tools/chat/composer/slash/SlashHighlightTextarea.tsx +144 -0
  18. package/src/tools/chat/composer/slash/SlashMenu.tsx +142 -0
  19. package/src/tools/chat/composer/slash/SlashToken.tsx +57 -0
  20. package/src/tools/chat/composer/slash/index.ts +44 -0
  21. package/src/tools/chat/composer/slash/labels.ts +19 -0
  22. package/src/tools/chat/composer/slash/state.ts +168 -0
  23. package/src/tools/chat/composer/slash/types.ts +64 -0
  24. package/src/tools/chat/composer/slash/useSlashCommands.ts +204 -0
  25. package/src/tools/chat/composer/types.ts +8 -0
  26. package/src/tools/chat/shell/SuggestedPrompts.tsx +194 -0
  27. package/src/tools/chat/shell/index.ts +6 -0
  28. package/src/tools/data/Listbox/lazy.tsx +1 -1
  29. package/src/tools/data/Masonry/lazy.tsx +1 -1
  30. package/src/tools/data/Timeline/lazy.tsx +1 -1
  31. package/src/tools/data/Tree/FinderTree.tsx +42 -0
  32. package/src/tools/data/Tree/README.md +337 -208
  33. package/src/tools/data/Tree/TreeDndProvider.tsx +137 -0
  34. package/src/tools/data/Tree/TreeRoot.tsx +170 -55
  35. package/src/tools/data/Tree/__tests__/dnd.test.ts +160 -0
  36. package/src/tools/data/Tree/__tests__/keyboard.test.ts +137 -0
  37. package/src/tools/data/Tree/__tests__/renameUtils.test.ts +52 -0
  38. package/src/tools/data/Tree/__tests__/selection.test.ts +227 -0
  39. package/src/tools/data/Tree/components/TreeDropIndicator.tsx +65 -0
  40. package/src/tools/data/Tree/components/TreeEmptyArea.tsx +160 -0
  41. package/src/tools/data/Tree/components/TreeRenameInput.tsx +114 -0
  42. package/src/tools/data/Tree/components/TreeRow.tsx +92 -8
  43. package/src/tools/data/Tree/components/index.ts +6 -0
  44. package/src/tools/data/Tree/context/TreeContext.tsx +204 -363
  45. package/src/tools/data/Tree/context/TreeContextValue.ts +139 -0
  46. package/src/tools/data/Tree/context/async-children/collect-ids.ts +27 -0
  47. package/src/tools/data/Tree/context/async-children/index.ts +8 -0
  48. package/src/tools/data/Tree/context/async-children/use-async-children.ts +157 -0
  49. package/src/tools/data/Tree/context/clipboard/index.ts +4 -0
  50. package/src/tools/data/Tree/context/clipboard/use-clipboard.ts +115 -0
  51. package/src/tools/data/Tree/context/dnd/index.ts +8 -0
  52. package/src/tools/data/Tree/context/dnd/use-dnd.ts +194 -0
  53. package/src/tools/data/Tree/context/expansion/index.ts +4 -0
  54. package/src/tools/data/Tree/context/expansion/use-expansion.ts +55 -0
  55. package/src/tools/data/Tree/context/hooks.ts +68 -1
  56. package/src/tools/data/Tree/context/index.ts +3 -0
  57. package/src/tools/data/Tree/context/menu/builtin-actions.ts +357 -0
  58. package/src/tools/data/Tree/context/menu/index.ts +10 -0
  59. package/src/tools/data/Tree/context/menu/use-resolved-menu.ts +127 -0
  60. package/src/tools/data/Tree/context/persist/index.ts +4 -0
  61. package/src/tools/data/Tree/context/persist/use-persist-sync.ts +74 -0
  62. package/src/tools/data/Tree/context/rename/index.ts +4 -0
  63. package/src/tools/data/Tree/context/rename/use-rename.ts +113 -0
  64. package/src/tools/data/Tree/context/selection/index.ts +4 -0
  65. package/src/tools/data/Tree/context/selection/use-selection.ts +146 -0
  66. package/src/tools/data/Tree/context/state/index.ts +6 -0
  67. package/src/tools/data/Tree/context/state/initial.ts +41 -0
  68. package/src/tools/data/Tree/context/state/reducer.ts +76 -0
  69. package/src/tools/data/Tree/context/state/types.ts +46 -0
  70. package/src/tools/data/Tree/data/clipboard.ts +33 -0
  71. package/src/tools/data/Tree/data/dnd.ts +123 -0
  72. package/src/tools/data/Tree/data/finderShortcuts.ts +67 -0
  73. package/src/tools/data/Tree/data/index.ts +19 -0
  74. package/src/tools/data/Tree/data/renameUtils.ts +51 -0
  75. package/src/tools/data/Tree/data/selection.ts +157 -0
  76. package/src/tools/data/Tree/hooks/finder-hotkeys/build-ctx.ts +48 -0
  77. package/src/tools/data/Tree/hooks/finder-hotkeys/index.ts +8 -0
  78. package/src/tools/data/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts +166 -0
  79. package/src/tools/data/Tree/hooks/index.ts +23 -4
  80. package/src/tools/data/Tree/hooks/keyboard/activation.ts +27 -0
  81. package/src/tools/data/Tree/hooks/keyboard/arrow-nav.ts +26 -0
  82. package/src/tools/data/Tree/hooks/keyboard/expand-collapse.ts +54 -0
  83. package/src/tools/data/Tree/hooks/keyboard/index.ts +10 -0
  84. package/src/tools/data/Tree/hooks/keyboard/types.ts +39 -0
  85. package/src/tools/data/Tree/hooks/keyboard/use-tree-keyboard.ts +196 -0
  86. package/src/tools/data/Tree/hooks/type-ahead/index.ts +5 -0
  87. package/src/tools/data/Tree/hooks/type-ahead/match-prefix.ts +42 -0
  88. package/src/tools/data/Tree/hooks/{useTreeTypeAhead.ts → type-ahead/use-tree-type-ahead.ts} +8 -19
  89. package/src/tools/data/Tree/index.tsx +25 -2
  90. package/src/tools/data/Tree/types/activation.ts +30 -0
  91. package/src/tools/data/Tree/types/adapter.ts +70 -0
  92. package/src/tools/data/Tree/types/index.ts +27 -0
  93. package/src/tools/data/Tree/types/labels.ts +97 -0
  94. package/src/tools/data/Tree/types/loader.ts +9 -0
  95. package/src/tools/data/Tree/types/node.ts +38 -0
  96. package/src/tools/data/Tree/types/root-props.ts +142 -0
  97. package/src/tools/data/Tree/types/selection.ts +3 -0
  98. package/src/tools/data/Tree/types/slots.ts +64 -0
  99. package/src/tools/dev/OpenapiViewer/components/DocsLayout/EndpointDoc/Responses/ResponseBody.tsx +1 -1
  100. package/src/tools/dev/OpenapiViewer/components/shared/ResponsePanel/PrettyView.tsx +1 -1
  101. package/src/tools/forms/MarkdownEditor/MarkdownEditor.tsx +85 -0
  102. package/src/tools/forms/MarkdownEditor/index.ts +1 -0
  103. package/src/tools/forms/MarkdownEditor/lazy.tsx +6 -0
  104. package/src/tools/forms/MarkdownEditor/slash/SlashCommandNode.ts +162 -0
  105. package/src/tools/forms/MarkdownEditor/slash/index.ts +4 -0
  106. package/src/tools/forms/MarkdownEditor/slash/syncSlashNode.ts +97 -0
  107. package/src/tools/forms/MarkdownEditor/slash/types.ts +13 -0
  108. package/src/tools/forms/MarkdownEditor/styles.css +18 -0
  109. package/src/tools/index.ts +2 -2
  110. package/dist/types-j2vhn4Kv.d.cts +0 -241
  111. package/dist/types-j2vhn4Kv.d.ts +0 -241
  112. package/src/tools/data/Tree/hooks/useTreeKeyboard.ts +0 -171
  113. package/src/tools/data/Tree/types.ts +0 -217
@@ -6,8 +6,10 @@ var chunkPK6SKIKE_cjs = require('../chunk-PK6SKIKE.cjs');
6
6
  var React = require('react');
7
7
  var lib = require('@djangocfg/ui-core/lib');
8
8
  var components = require('@djangocfg/ui-core/components');
9
- var jsxRuntime = require('react/jsx-runtime');
9
+ var dialogService = require('@djangocfg/ui-core/lib/dialog-service');
10
10
  var lucideReact = require('lucide-react');
11
+ var jsxRuntime = require('react/jsx-runtime');
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,1056 @@ 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;
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;
985
+ }
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
+
1075
+ // src/tools/data/Tree/data/dnd.ts
1076
+ function resolveDropZone(input) {
1077
+ const { pointerY, rowRect, isFolder } = input;
1078
+ const offset = pointerY - rowRect.top;
1079
+ const ratio = rowRect.height > 0 ? offset / rowRect.height : 0.5;
1080
+ if (isFolder) {
1081
+ if (ratio < 0.33) return "before";
1082
+ if (ratio > 0.66) return "after";
1083
+ return "inside";
1084
+ }
1085
+ return ratio < 0.5 ? "before" : "after";
1086
+ }
1087
+ chunkPK6SKIKE_cjs.__name(resolveDropZone, "resolveDropZone");
1088
+ function defaultCanDrop(input) {
1089
+ const { source, target, position } = input;
1090
+ if (source.length === 0) return false;
1091
+ if (!target) return true;
1092
+ if (position === "inside") {
1093
+ const isFolder = Array.isArray(target.children) || !!target.isFolder;
1094
+ if (!isFolder) return false;
1095
+ }
1096
+ for (const node of source) {
1097
+ if (node.id === target.id) return false;
1098
+ if (isDescendant(node, target.id)) return false;
1099
+ }
1100
+ return true;
1101
+ }
1102
+ chunkPK6SKIKE_cjs.__name(defaultCanDrop, "defaultCanDrop");
1103
+ function isDescendant(root, id) {
1104
+ if (!Array.isArray(root.children)) return false;
1105
+ for (const child of root.children) {
1106
+ if (child.id === id) return true;
1107
+ if (isDescendant(child, id)) return true;
1108
+ }
1109
+ return false;
1110
+ }
1111
+ chunkPK6SKIKE_cjs.__name(isDescendant, "isDescendant");
1112
+ var TREE_DND_MIME = "application/x-djangocfg-tree";
1113
+ var TREE_ROOT_DROP_ID = "__tree_root_drop__";
1114
+
1115
+ // src/tools/data/Tree/context/dnd/use-dnd.ts
1116
+ function useDnd({
1117
+ enabled,
1118
+ adapter,
1119
+ nodeById,
1120
+ selected,
1121
+ labels,
1122
+ canDrop
1123
+ }) {
1124
+ const active = enabled && !!adapter?.move;
1125
+ const [draggingIds, setDraggingIds] = React.useState(
1126
+ () => /* @__PURE__ */ new Set()
1127
+ );
1128
+ const [dropTarget, setDropTarget] = React.useState(null);
1129
+ const beginDrag = React.useCallback(
1130
+ (rowId) => {
1131
+ if (!active) return;
1132
+ const ids = selected.has(rowId) ? new Set(selected) : /* @__PURE__ */ new Set([rowId]);
1133
+ setDraggingIds(ids);
1134
+ },
1135
+ [active, selected]
1136
+ );
1137
+ const cancelDrag = React.useCallback(() => {
1138
+ setDraggingIds(/* @__PURE__ */ new Set());
1139
+ setDropTarget(null);
1140
+ }, []);
1141
+ const resolveSourceNodes = React.useCallback(() => {
1142
+ const out = [];
1143
+ for (const id of draggingIds) {
1144
+ const node = nodeById.get(id);
1145
+ if (node) out.push(node);
1146
+ }
1147
+ return out;
1148
+ }, [draggingIds, nodeById]);
1149
+ const isAllowedDrop = React.useCallback(
1150
+ (target, position) => {
1151
+ if (!active) return false;
1152
+ if (draggingIds.size === 0) return false;
1153
+ const source = resolveSourceNodes();
1154
+ if (!defaultCanDrop({
1155
+ source,
1156
+ target,
1157
+ position})) {
1158
+ return false;
421
1159
  }
1160
+ return canDrop?.({ source, target, position }) ?? true;
1161
+ },
1162
+ [active, draggingIds, resolveSourceNodes, nodeById, canDrop]
1163
+ );
1164
+ const commitDrop = React.useCallback(async () => {
1165
+ if (!active || !adapter?.move) {
1166
+ cancelDrag();
1167
+ return;
422
1168
  }
423
- return set;
424
- }, [enableSearch, state.query, flatRows, getItemName]);
1169
+ const t = dropTarget;
1170
+ if (!t) {
1171
+ cancelDrag();
1172
+ return;
1173
+ }
1174
+ const targetNode = t.id ? nodeById.get(t.id) ?? null : null;
1175
+ const source = resolveSourceNodes();
1176
+ if (source.length === 0) {
1177
+ cancelDrag();
1178
+ return;
1179
+ }
1180
+ if (!isAllowedDrop(targetNode, t.position)) {
1181
+ cancelDrag();
1182
+ return;
1183
+ }
1184
+ try {
1185
+ await adapter.move(source, targetNode, t.position);
1186
+ } catch (e) {
1187
+ const dialog = dialogService.getDialog();
1188
+ await dialog?.alert({
1189
+ title: labels.error,
1190
+ message: e instanceof Error ? e.message : String(e)
1191
+ });
1192
+ } finally {
1193
+ cancelDrag();
1194
+ }
1195
+ }, [
1196
+ active,
1197
+ adapter,
1198
+ cancelDrag,
1199
+ dropTarget,
1200
+ isAllowedDrop,
1201
+ labels,
1202
+ nodeById,
1203
+ resolveSourceNodes
1204
+ ]);
1205
+ return React.useMemo(
1206
+ () => ({
1207
+ active,
1208
+ draggingIds,
1209
+ dropTarget,
1210
+ beginDrag,
1211
+ setDropTarget,
1212
+ commitDrop,
1213
+ cancelDrag,
1214
+ isAllowedDrop
1215
+ }),
1216
+ [
1217
+ active,
1218
+ draggingIds,
1219
+ dropTarget,
1220
+ beginDrag,
1221
+ commitDrop,
1222
+ cancelDrag,
1223
+ isAllowedDrop
1224
+ ]
1225
+ );
1226
+ }
1227
+ chunkPK6SKIKE_cjs.__name(useDnd, "useDnd");
1228
+ function usePersistSync({
1229
+ expanded,
1230
+ selected,
1231
+ persistKey,
1232
+ persistSelection,
1233
+ onSelectionChange,
1234
+ onExpansionChange
1235
+ }) {
425
1236
  const onSelectionChangeRef = React.useRef(onSelectionChange);
426
1237
  const onExpansionChangeRef = React.useRef(onExpansionChange);
427
- const onActivateRef = React.useRef(onActivate);
428
1238
  onSelectionChangeRef.current = onSelectionChange;
429
1239
  onExpansionChangeRef.current = onExpansionChange;
430
- onActivateRef.current = onActivate;
431
- const lastSelectedArrRef = React.useRef([...state.selected]);
432
- const lastExpandedArrRef = React.useRef([...state.expanded]);
1240
+ const lastSelectedArrRef = React.useRef([...selected]);
1241
+ const lastExpandedArrRef = React.useRef([...expanded]);
433
1242
  React.useEffect(() => {
434
- const arr = [...state.expanded];
435
- if (!setEqualsArr(state.expanded, lastExpandedArrRef.current)) {
1243
+ const arr = [...expanded];
1244
+ if (!setEqualsArr(expanded, lastExpandedArrRef.current)) {
436
1245
  lastExpandedArrRef.current = arr;
437
1246
  onExpansionChangeRef.current?.(arr);
438
1247
  if (persistKey) {
439
1248
  saveTreeState(persistKey, {
440
1249
  expandedItems: arr,
441
- selectedItems: persistSelection ? [...state.selected] : []
1250
+ selectedItems: persistSelection ? [...selected] : []
442
1251
  });
443
1252
  }
444
1253
  }
445
- }, [state.expanded, persistKey, persistSelection, state.selected]);
1254
+ }, [expanded, persistKey, persistSelection, selected]);
446
1255
  React.useEffect(() => {
447
- const arr = [...state.selected];
448
- if (!setEqualsArr(state.selected, lastSelectedArrRef.current)) {
1256
+ const arr = [...selected];
1257
+ if (!setEqualsArr(selected, lastSelectedArrRef.current)) {
449
1258
  lastSelectedArrRef.current = arr;
450
1259
  onSelectionChangeRef.current?.(arr);
451
1260
  if (persistKey && persistSelection) {
452
1261
  saveTreeState(persistKey, {
453
- expandedItems: [...state.expanded],
1262
+ expandedItems: [...expanded],
454
1263
  selectedItems: arr
455
1264
  });
456
1265
  }
457
1266
  }
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 }),
477
- []
1267
+ }, [selected, persistKey, persistSelection, expanded]);
1268
+ }
1269
+ chunkPK6SKIKE_cjs.__name(usePersistSync, "usePersistSync");
1270
+ function setEqualsArr(set, arr) {
1271
+ if (set.size !== arr.length) return false;
1272
+ for (const id of arr) if (!set.has(id)) return false;
1273
+ return true;
1274
+ }
1275
+ chunkPK6SKIKE_cjs.__name(setEqualsArr, "setEqualsArr");
1276
+ var TreeContext = React.createContext(null);
1277
+ function useTreeContext() {
1278
+ const ctx = React__namespace.useContext(TreeContext);
1279
+ if (!ctx) {
1280
+ throw new Error("useTreeContext must be used inside <TreeProvider>");
1281
+ }
1282
+ return ctx;
1283
+ }
1284
+ chunkPK6SKIKE_cjs.__name(useTreeContext, "useTreeContext");
1285
+ function TreeProvider(props) {
1286
+ const {
1287
+ data,
1288
+ getItemName,
1289
+ loadChildren,
1290
+ selectionMode = "single",
1291
+ activationMode = "single-click",
1292
+ initialExpandedIds,
1293
+ initialSelectedIds,
1294
+ indent,
1295
+ appearance,
1296
+ onSelectionChange,
1297
+ onExpansionChange,
1298
+ onActivate,
1299
+ filterNode,
1300
+ enableSearch = false,
1301
+ showIndentGuides = false,
1302
+ renderIcon,
1303
+ renderLabel,
1304
+ renderActions,
1305
+ renderContextMenu,
1306
+ contextMenuActions,
1307
+ labels: labelsOverride,
1308
+ persistKey,
1309
+ persistSelection = false,
1310
+ adapter,
1311
+ defaultMenuItems,
1312
+ enableInlineRename = false,
1313
+ enableDnD = false,
1314
+ canDrop,
1315
+ children
1316
+ } = props;
1317
+ const labels = React.useMemo(
1318
+ () => ({ ...DEFAULT_TREE_LABELS, ...labelsOverride }),
1319
+ [labelsOverride]
478
1320
  );
479
- const clearSelection = React.useCallback(() => dispatch({ type: "clear-selection" }), []);
480
- const setFocus = React.useCallback(
481
- (id) => dispatch({ type: "focus", id }),
482
- []
1321
+ const resolvedAppearance = React.useMemo(
1322
+ () => resolveAppearance(appearance, indent),
1323
+ [appearance, indent]
483
1324
  );
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);
492
- },
493
- [nodeById, loadChildren, fetchChildren]
1325
+ const persisted = React.useMemo(
1326
+ () => persistKey ? loadTreeState(persistKey) : null,
1327
+ [persistKey]
494
1328
  );
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]);
1329
+ const [state, dispatch] = React.useReducer(
1330
+ reducer,
1331
+ void 0,
1332
+ () => createInitialState({
1333
+ persisted,
1334
+ initialExpandedIds,
1335
+ initialSelectedIds,
1336
+ persistSelection
1337
+ })
1338
+ );
1339
+ const bumpCacheTick = React.useCallback(() => dispatch({ type: "cache-tick" }), []);
1340
+ const {
1341
+ nodeById,
1342
+ refresh,
1343
+ refreshAll,
1344
+ collectFolderIds,
1345
+ cache
1346
+ } = useAsyncChildren({
1347
+ data,
1348
+ loadChildren,
1349
+ expanded: state.expanded,
1350
+ cacheTick: state.cacheTick,
1351
+ bumpCacheTick
1352
+ });
1353
+ const flatRows = React.useMemo(
1354
+ () => flattenTree({
1355
+ roots: data,
1356
+ expandedIds: state.expanded,
1357
+ cache,
1358
+ filterNode
1359
+ }),
1360
+ [data, state.expanded, state.cacheTick, cache, filterNode]
1361
+ );
1362
+ const matchingIds = React.useMemo(() => {
1363
+ const set = /* @__PURE__ */ new Set();
1364
+ if (!enableSearch || state.query.trim() === "") return set;
1365
+ const q = state.query.trim().toLowerCase();
1366
+ for (const row of flatRows) {
1367
+ if (getItemName(row.node).toLowerCase().includes(q)) {
1368
+ set.add(row.node.id);
1369
+ }
1370
+ }
1371
+ return set;
1372
+ }, [enableSearch, state.query, flatRows, getItemName]);
1373
+ const expansion = useExpansion({ dispatch, collectFolderIds });
1374
+ const selection = useSelection({
1375
+ dispatch,
1376
+ selectionMode,
1377
+ flatRows,
1378
+ selected: state.selected,
1379
+ anchor: state.anchor,
1380
+ focused: state.focused
1381
+ });
1382
+ const rename = useRename({
1383
+ dispatch,
1384
+ adapter,
1385
+ enableInlineRename,
1386
+ nodeById,
1387
+ getItemName,
1388
+ labels
1389
+ });
1390
+ const clipboard = useClipboard({
1391
+ dispatch,
1392
+ clipboard: state.clipboard,
1393
+ adapter,
1394
+ nodeById,
1395
+ labels
1396
+ });
1397
+ const dnd = useDnd({
1398
+ enabled: enableDnD,
1399
+ adapter,
1400
+ nodeById,
1401
+ selected: state.selected,
1402
+ labels,
1403
+ canDrop
1404
+ });
1405
+ const onActivateRef = React.useRef(onActivate);
1406
+ onActivateRef.current = onActivate;
506
1407
  const activate = React.useCallback(
507
1408
  (node, opts = { preview: false }) => onActivateRef.current?.(node, opts),
508
1409
  []
509
1410
  );
1411
+ const setQuery = React.useCallback(
1412
+ (q) => dispatch({ type: "set-query", q }),
1413
+ []
1414
+ );
1415
+ usePersistSync({
1416
+ expanded: state.expanded,
1417
+ selected: state.selected,
1418
+ persistKey,
1419
+ persistSelection,
1420
+ onSelectionChange,
1421
+ onExpansionChange
1422
+ });
1423
+ const resolvedContextMenuActions = useResolvedMenu({
1424
+ adapter,
1425
+ contextMenuActions,
1426
+ defaultMenuItems,
1427
+ labels,
1428
+ selected: state.selected,
1429
+ clipboard: state.clipboard,
1430
+ nodeById,
1431
+ getItemName,
1432
+ enableInlineRename,
1433
+ startRename: rename.startRename,
1434
+ cutToClipboard: clipboard.cutToClipboard,
1435
+ copyToClipboard: clipboard.copyToClipboard,
1436
+ pasteFromClipboard: clipboard.pasteFromClipboard
1437
+ });
510
1438
  const value = React.useMemo(
511
1439
  () => ({
1440
+ // state
512
1441
  expanded: state.expanded,
513
1442
  selected: state.selected,
1443
+ anchor: state.anchor,
514
1444
  focused: state.focused,
515
1445
  query: state.query,
1446
+ renamingId: state.renaming,
1447
+ inlineRenameEnabled: rename.enabled,
1448
+ clipboard: state.clipboard,
516
1449
  flatRows,
517
1450
  matchingIds,
518
- expand,
519
- collapse,
520
- toggle,
521
- expandAll,
522
- collapseAll,
523
- select,
524
- setSelectedIds,
525
- clearSelection,
526
- setFocus,
1451
+ // expansion
1452
+ ...expansion,
1453
+ // selection (note: `select` is from selection hook; expansion exports no `select`)
1454
+ ...selection,
527
1455
  setQuery,
1456
+ // clipboard
1457
+ ...clipboard,
1458
+ // rename
1459
+ startRename: rename.startRename,
1460
+ cancelRename: rename.cancelRename,
1461
+ commitRename: rename.commitRename,
1462
+ // async
528
1463
  refresh,
529
1464
  refreshAll,
530
1465
  activate,
1466
+ // config
531
1467
  labels,
532
1468
  appearance: resolvedAppearance,
533
1469
  indent: resolvedAppearance.indent,
@@ -536,28 +1472,34 @@ function TreeProvider(props) {
536
1472
  enableSearch,
537
1473
  showIndentGuides,
538
1474
  getItemName,
1475
+ // slots
539
1476
  renderIcon,
540
1477
  renderLabel,
541
1478
  renderActions,
542
- renderContextMenu
1479
+ renderContextMenu,
1480
+ adapter,
1481
+ resolvedContextMenuActions,
1482
+ getNodeById: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((id) => nodeById.get(id), "getNodeById"),
1483
+ dnd
543
1484
  }),
544
1485
  [
545
1486
  state.expanded,
546
1487
  state.selected,
1488
+ state.anchor,
547
1489
  state.focused,
548
1490
  state.query,
1491
+ state.renaming,
1492
+ state.clipboard,
1493
+ rename.enabled,
1494
+ rename.startRename,
1495
+ rename.cancelRename,
1496
+ rename.commitRename,
549
1497
  flatRows,
550
1498
  matchingIds,
551
- expand,
552
- collapse,
553
- toggle,
554
- expandAll,
555
- collapseAll,
556
- select,
557
- setSelectedIds,
558
- clearSelection,
559
- setFocus,
1499
+ expansion,
1500
+ selection,
560
1501
  setQuery,
1502
+ clipboard,
561
1503
  refresh,
562
1504
  refreshAll,
563
1505
  activate,
@@ -571,12 +1513,98 @@ function TreeProvider(props) {
571
1513
  renderIcon,
572
1514
  renderLabel,
573
1515
  renderActions,
574
- renderContextMenu
1516
+ renderContextMenu,
1517
+ adapter,
1518
+ resolvedContextMenuActions,
1519
+ nodeById,
1520
+ dnd
575
1521
  ]
576
1522
  );
577
1523
  return /* @__PURE__ */ jsxRuntime.jsx(TreeContext.Provider, { value, children });
578
1524
  }
579
1525
  chunkPK6SKIKE_cjs.__name(TreeProvider, "TreeProvider");
1526
+ function TreeDndProvider({ children }) {
1527
+ const ctx = useTreeContext();
1528
+ const { dnd } = ctx;
1529
+ const sensors = core.useSensors(
1530
+ core.useSensor(core.PointerSensor, { activationConstraint: { distance: 4 } }),
1531
+ core.useSensor(core.KeyboardSensor)
1532
+ );
1533
+ const cursorYRef = React.useRef(0);
1534
+ const handleDragStart = React.useCallback(
1535
+ (e) => {
1536
+ dnd.beginDrag(e.active.id);
1537
+ },
1538
+ [dnd]
1539
+ );
1540
+ const handleDragMove = React.useCallback(
1541
+ (e) => {
1542
+ const overId = e.over?.id;
1543
+ if (overId === TREE_ROOT_DROP_ID) {
1544
+ const current2 = dnd.dropTarget;
1545
+ if (current2?.id !== null || current2?.position !== "inside") {
1546
+ dnd.setDropTarget({ id: null, position: "inside" });
1547
+ }
1548
+ return;
1549
+ }
1550
+ if (typeof overId !== "string") {
1551
+ if (dnd.dropTarget !== null) dnd.setDropTarget(null);
1552
+ return;
1553
+ }
1554
+ const rect = e.over?.rect;
1555
+ if (!rect) return;
1556
+ const el = document.querySelector(
1557
+ `[data-tree-row][data-id="${CSS.escape(overId)}"]`
1558
+ );
1559
+ const isFolder = el?.dataset.folder === "true";
1560
+ const position = resolveDropZone({
1561
+ pointerY: cursorYRef.current,
1562
+ rowRect: { top: rect.top, bottom: rect.top + rect.height, height: rect.height },
1563
+ isFolder
1564
+ });
1565
+ const current = dnd.dropTarget;
1566
+ if (current?.id !== overId || current.position !== position) {
1567
+ dnd.setDropTarget({ id: overId, position });
1568
+ }
1569
+ },
1570
+ [dnd]
1571
+ );
1572
+ const handleDragEnd = React.useCallback(
1573
+ async (_e) => {
1574
+ await dnd.commitDrop();
1575
+ },
1576
+ [dnd]
1577
+ );
1578
+ const handleDragCancel = React.useCallback(() => {
1579
+ dnd.cancelDrag();
1580
+ }, [dnd]);
1581
+ const handlePointerMove = React.useCallback((e) => {
1582
+ cursorYRef.current = e.clientY;
1583
+ }, []);
1584
+ if (!dnd.active) {
1585
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
1586
+ }
1587
+ return /* @__PURE__ */ jsxRuntime.jsx(
1588
+ core.DndContext,
1589
+ {
1590
+ sensors,
1591
+ onDragStart: handleDragStart,
1592
+ onDragMove: handleDragMove,
1593
+ onDragEnd: handleDragEnd,
1594
+ onDragCancel: handleDragCancel,
1595
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1596
+ "div",
1597
+ {
1598
+ onPointerMove: handlePointerMove,
1599
+ className: "contents",
1600
+ "data-tree-dnd-surface": "",
1601
+ children
1602
+ }
1603
+ )
1604
+ }
1605
+ );
1606
+ }
1607
+ chunkPK6SKIKE_cjs.__name(TreeDndProvider, "TreeDndProvider");
580
1608
  function TreeChevronRaw({ isExpanded, isFolder, className }) {
581
1609
  const { appearance } = useTreeContext();
582
1610
  const size = { width: "var(--tree-icon-size)", height: "var(--tree-icon-size)" };
@@ -606,6 +1634,48 @@ function TreeChevronRaw({ isExpanded, isFolder, className }) {
606
1634
  }
607
1635
  chunkPK6SKIKE_cjs.__name(TreeChevronRaw, "TreeChevronRaw");
608
1636
  var TreeChevron = React.memo(TreeChevronRaw);
1637
+ function TreeDropIndicator({
1638
+ position,
1639
+ indent,
1640
+ invalid = false
1641
+ }) {
1642
+ if (position === "inside") {
1643
+ return /* @__PURE__ */ jsxRuntime.jsx(
1644
+ "span",
1645
+ {
1646
+ "aria-hidden": true,
1647
+ "data-tree-drop": "inside",
1648
+ className: lib.cn(
1649
+ "pointer-events-none absolute inset-0 rounded-sm ring-1",
1650
+ invalid ? "bg-destructive/10 ring-destructive/40" : "bg-primary/10 ring-primary/40"
1651
+ )
1652
+ }
1653
+ );
1654
+ }
1655
+ const isBefore = position === "before";
1656
+ return /* @__PURE__ */ jsxRuntime.jsx(
1657
+ "span",
1658
+ {
1659
+ "aria-hidden": true,
1660
+ "data-tree-drop": position,
1661
+ style: { paddingLeft: indent },
1662
+ className: lib.cn(
1663
+ "pointer-events-none absolute right-0 left-0 h-px",
1664
+ isBefore ? "top-0" : "bottom-0"
1665
+ ),
1666
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1667
+ "span",
1668
+ {
1669
+ className: lib.cn(
1670
+ "block h-0.5 rounded-full",
1671
+ invalid ? "bg-destructive/70" : "bg-primary"
1672
+ )
1673
+ }
1674
+ )
1675
+ }
1676
+ );
1677
+ }
1678
+ chunkPK6SKIKE_cjs.__name(TreeDropIndicator, "TreeDropIndicator");
609
1679
  function TreeIconRaw({ isFolder, isExpanded, className }) {
610
1680
  const { appearance } = useTreeContext();
611
1681
  const Icon = isFolder ? isExpanded ? lucideReact.FolderOpen : lucideReact.Folder : lucideReact.File;
@@ -662,6 +1732,96 @@ function TreeLabelRaw({ children, isMatchingSearch, className }) {
662
1732
  }
663
1733
  chunkPK6SKIKE_cjs.__name(TreeLabelRaw, "TreeLabelRaw");
664
1734
  var TreeLabel = React.memo(TreeLabelRaw);
1735
+
1736
+ // src/tools/data/Tree/data/renameUtils.ts
1737
+ function splitFileName(name) {
1738
+ if (name.length === 0) return { base: "", ext: "" };
1739
+ if (name.startsWith(".")) {
1740
+ const rest = name.slice(1);
1741
+ const dot2 = rest.lastIndexOf(".");
1742
+ if (dot2 < 0) return { base: name, ext: "" };
1743
+ return { base: "." + rest.slice(0, dot2), ext: rest.slice(dot2) };
1744
+ }
1745
+ const dot = name.lastIndexOf(".");
1746
+ if (dot <= 0) return { base: name, ext: "" };
1747
+ return { base: name.slice(0, dot), ext: name.slice(dot) };
1748
+ }
1749
+ chunkPK6SKIKE_cjs.__name(splitFileName, "splitFileName");
1750
+ function autoSelectRange(name, isFolder) {
1751
+ if (isFolder) return [0, name.length];
1752
+ const { base } = splitFileName(name);
1753
+ return [0, base.length];
1754
+ }
1755
+ chunkPK6SKIKE_cjs.__name(autoSelectRange, "autoSelectRange");
1756
+ function TreeRenameInput({
1757
+ initialValue,
1758
+ isFolder,
1759
+ onCommit,
1760
+ onCancel,
1761
+ className
1762
+ }) {
1763
+ const [value, setValue] = React.useState(initialValue);
1764
+ const inputRef = React.useRef(null);
1765
+ const settledRef = React.useRef(false);
1766
+ React.useEffect(() => {
1767
+ const el = inputRef.current;
1768
+ if (!el) return;
1769
+ el.focus();
1770
+ const [start, end] = autoSelectRange(initialValue, isFolder);
1771
+ requestAnimationFrame(() => {
1772
+ try {
1773
+ el.setSelectionRange(start, end);
1774
+ } catch {
1775
+ }
1776
+ });
1777
+ }, []);
1778
+ const commit = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
1779
+ if (settledRef.current) return;
1780
+ settledRef.current = true;
1781
+ void onCommit(value);
1782
+ }, "commit");
1783
+ const cancel = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
1784
+ if (settledRef.current) return;
1785
+ settledRef.current = true;
1786
+ onCancel();
1787
+ }, "cancel");
1788
+ return /* @__PURE__ */ jsxRuntime.jsx(
1789
+ "input",
1790
+ {
1791
+ ref: inputRef,
1792
+ type: "text",
1793
+ value,
1794
+ onKeyDown: (e) => {
1795
+ e.stopPropagation();
1796
+ if (e.key === "Enter") {
1797
+ e.preventDefault();
1798
+ commit();
1799
+ } else if (e.key === "Escape") {
1800
+ e.preventDefault();
1801
+ cancel();
1802
+ }
1803
+ },
1804
+ onChange: (e) => setValue(e.target.value),
1805
+ onBlur: commit,
1806
+ onClick: (e) => e.stopPropagation(),
1807
+ onDoubleClick: (e) => e.stopPropagation(),
1808
+ onMouseDown: (e) => e.stopPropagation(),
1809
+ onContextMenu: (e) => e.stopPropagation(),
1810
+ className: lib.cn(
1811
+ "min-w-0 flex-1 rounded-sm border border-primary/50 bg-background",
1812
+ "px-1 py-0 text-foreground outline-none",
1813
+ "focus:ring-1 focus:ring-primary/40",
1814
+ className
1815
+ ),
1816
+ style: {
1817
+ // Match the row's font metrics so the input doesn't visibly jolt.
1818
+ fontSize: "var(--tree-font-size)",
1819
+ height: "calc(var(--tree-row-height) - 4px)"
1820
+ }
1821
+ }
1822
+ );
1823
+ }
1824
+ chunkPK6SKIKE_cjs.__name(TreeRenameInput, "TreeRenameInput");
665
1825
  function TreeRowRaw({ row, className }) {
666
1826
  const ctx = useTreeContext();
667
1827
  const {
@@ -673,6 +1833,7 @@ function TreeRowRaw({ row, className }) {
673
1833
  matchingIds,
674
1834
  select,
675
1835
  setSelectedIds,
1836
+ clickSelect,
676
1837
  toggle,
677
1838
  setFocus,
678
1839
  activate,
@@ -680,13 +1841,19 @@ function TreeRowRaw({ row, className }) {
680
1841
  renderIcon,
681
1842
  renderLabel,
682
1843
  renderActions,
683
- renderContextMenu
1844
+ renderContextMenu,
1845
+ renamingId,
1846
+ commitRename,
1847
+ cancelRename,
1848
+ clipboard,
1849
+ dnd
684
1850
  } = ctx;
685
1851
  const { node, level, isFolder, isExpanded, isLoading, posInSet, setSize } = row;
686
1852
  const isSelected = selected.has(node.id);
687
1853
  const isFocused = focused === node.id;
688
1854
  const isMatchingSearch = matchingIds.has(node.id);
689
1855
  const isMultiSelect = ctx.selectionMode === "multiple";
1856
+ const isCut = clipboard?.kind === "cut" && clipboard.ids.includes(node.id);
690
1857
  const slot = {
691
1858
  node,
692
1859
  level,
@@ -697,30 +1864,57 @@ function TreeRowRaw({ row, className }) {
697
1864
  isLoading,
698
1865
  isMatchingSearch
699
1866
  };
1867
+ const isRenaming = renamingId === node.id;
1868
+ const isDragging = dnd.draggingIds.has(node.id);
1869
+ const dropTarget = dnd.dropTarget;
1870
+ const isDropTarget = dropTarget?.id === node.id;
1871
+ const dropPosition = isDropTarget ? dropTarget.position : null;
1872
+ const dndDisabled = !dnd.active || isRenaming || node.disabled;
1873
+ const draggable = core.useDraggable({ id: node.id, disabled: dndDisabled });
1874
+ const droppable = core.useDroppable({ id: node.id, disabled: dndDisabled });
1875
+ const setRowEl = React.useCallback(
1876
+ (el) => {
1877
+ draggable.setNodeRef(el);
1878
+ droppable.setNodeRef(el);
1879
+ },
1880
+ [draggable, droppable]
1881
+ );
1882
+ const isAllowedDrop = dropPosition && !isDragging ? dnd.isAllowedDrop(node, dropPosition) : true;
700
1883
  const handleClick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((e) => {
701
- if (node.disabled) return;
1884
+ if (node.disabled || isRenaming) return;
702
1885
  setFocus(node.id);
703
- if (isMultiSelect && !(e.metaKey || e.ctrlKey)) {
704
- setSelectedIds([node.id]);
1886
+ if (isMultiSelect) {
1887
+ clickSelect(node.id, { shift: e.shiftKey, meta: e.metaKey || e.ctrlKey });
705
1888
  } else {
706
1889
  select(node.id);
707
1890
  }
708
1891
  if (isFolder) {
1892
+ if (e.shiftKey || e.metaKey || e.ctrlKey) return;
709
1893
  toggle(node.id);
710
1894
  } else if (activationMode === "single-click") {
1895
+ if (e.shiftKey || e.metaKey || e.ctrlKey) return;
711
1896
  activate(node, { preview: false });
712
1897
  } else if (activationMode === "single-click-preview") {
1898
+ if (e.shiftKey || e.metaKey || e.ctrlKey) return;
713
1899
  activate(node, { preview: true });
714
1900
  }
715
1901
  }, "handleClick");
716
1902
  const handleDoubleClick = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
717
- if (node.disabled) return;
1903
+ if (node.disabled || isRenaming) return;
718
1904
  if (isFolder) return;
719
1905
  activate(node, { preview: false });
720
1906
  }, "handleDoubleClick");
1907
+ const handleContextMenu = /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
1908
+ if (node.disabled || isRenaming) return;
1909
+ setFocus(node.id);
1910
+ if (!isSelected) {
1911
+ setSelectedIds([node.id]);
1912
+ }
1913
+ }, "handleContextMenu");
721
1914
  const trigger = /* @__PURE__ */ jsxRuntime.jsxs(
722
1915
  "div",
723
1916
  {
1917
+ ref: dnd.active ? setRowEl : void 0,
724
1918
  id: treeRowDomId(node.id),
725
1919
  role: "treeitem",
726
1920
  "aria-level": level + 1,
@@ -733,6 +1927,8 @@ function TreeRowRaw({ row, className }) {
733
1927
  "data-id": node.id,
734
1928
  "data-activation-mode": activationMode,
735
1929
  "data-selected": isSelected ? "true" : void 0,
1930
+ "data-clipboard": isCut ? "cut" : void 0,
1931
+ "data-dragging": isDragging ? "true" : void 0,
736
1932
  "data-focused": isFocused && !isSelected ? "true" : void 0,
737
1933
  "data-folder": isFolder || void 0,
738
1934
  "data-expanded": isExpanded || void 0,
@@ -742,8 +1938,11 @@ function TreeRowRaw({ row, className }) {
742
1938
  height: "var(--tree-row-height)",
743
1939
  gap: "var(--tree-gap)"
744
1940
  },
1941
+ ...dnd.active ? draggable.listeners : {},
1942
+ ...dnd.active ? draggable.attributes : {},
745
1943
  onClick: handleClick,
746
1944
  onDoubleClick: handleDoubleClick,
1945
+ onContextMenu: handleContextMenu,
747
1946
  onFocus: () => setFocus(node.id),
748
1947
  className: lib.cn(
749
1948
  "group/row relative flex w-full select-none items-center pr-2 text-left",
@@ -753,6 +1952,8 @@ function TreeRowRaw({ row, className }) {
753
1952
  rowStateClasses(appearance),
754
1953
  "focus-visible:ring-1 focus-visible:ring-ring/50",
755
1954
  isMatchingSearch && "ring-1 ring-primary/30",
1955
+ isCut && "opacity-60",
1956
+ isDragging && "opacity-40",
756
1957
  node.disabled && "opacity-50",
757
1958
  className
758
1959
  ),
@@ -767,6 +1968,14 @@ function TreeRowRaw({ row, className }) {
767
1968
  )
768
1969
  }
769
1970
  ) : null,
1971
+ dropPosition && !isDragging ? /* @__PURE__ */ jsxRuntime.jsx(
1972
+ TreeDropIndicator,
1973
+ {
1974
+ position: dropPosition,
1975
+ indent: 6 + level * appearance.indent,
1976
+ invalid: !isAllowedDrop
1977
+ }
1978
+ ) : null,
770
1979
  showIndentGuides && level > 0 ? /* @__PURE__ */ jsxRuntime.jsx(TreeIndentGuides, { level, indent: appearance.indent }) : null,
771
1980
  /* @__PURE__ */ jsxRuntime.jsx(TreeChevron, { isExpanded, isFolder }),
772
1981
  isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -783,7 +1992,15 @@ function TreeRowRaw({ row, className }) {
783
1992
  {
784
1993
  className: "flex min-w-0 flex-1 items-center",
785
1994
  style: { gap: "var(--tree-gap)" },
786
- children: renderLabel ? renderLabel(slot) : /* @__PURE__ */ jsxRuntime.jsx(TreeLabel, { isMatchingSearch, children: getItemName(node) })
1995
+ children: renamingId === node.id ? /* @__PURE__ */ jsxRuntime.jsx(
1996
+ TreeRenameInput,
1997
+ {
1998
+ initialValue: getItemName(node),
1999
+ isFolder,
2000
+ onCommit: (next) => commitRename(node.id, next),
2001
+ onCancel: cancelRename
2002
+ }
2003
+ ) : renderLabel ? renderLabel(slot) : /* @__PURE__ */ jsxRuntime.jsx(TreeLabel, { isMatchingSearch, children: getItemName(node) })
787
2004
  }
788
2005
  ),
789
2006
  renderActions ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -855,6 +2072,105 @@ function TreeContent({
855
2072
  );
856
2073
  }
857
2074
  chunkPK6SKIKE_cjs.__name(TreeContent, "TreeContent");
2075
+ function TreeEmptyArea({ className }) {
2076
+ const ctx = useTreeContext();
2077
+ const {
2078
+ adapter,
2079
+ labels,
2080
+ getItemName,
2081
+ clipboard,
2082
+ cutToClipboard,
2083
+ copyToClipboard,
2084
+ pasteFromClipboard,
2085
+ startRename,
2086
+ inlineRenameEnabled,
2087
+ dnd
2088
+ } = ctx;
2089
+ const { setNodeRef: setDroppableRef, isOver } = core.useDroppable({
2090
+ id: TREE_ROOT_DROP_ID,
2091
+ disabled: !dnd.active
2092
+ });
2093
+ const items = React.useMemo(() => {
2094
+ if (!adapter) return null;
2095
+ const builtinCtx = {
2096
+ adapter,
2097
+ labels,
2098
+ selectedNodes: [],
2099
+ targetNode: null,
2100
+ getName: getItemName,
2101
+ startInlineRename: inlineRenameEnabled ? startRename : void 0,
2102
+ clipboard: {
2103
+ hasItems: !!clipboard && clipboard.ids.length > 0,
2104
+ cut: cutToClipboard,
2105
+ copy: copyToClipboard,
2106
+ paste: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => pasteFromClipboard(null, "inside"), "paste")
2107
+ }
2108
+ };
2109
+ return buildDefaultMenuItems(builtinCtx);
2110
+ }, [
2111
+ adapter,
2112
+ labels,
2113
+ getItemName,
2114
+ inlineRenameEnabled,
2115
+ startRename,
2116
+ clipboard,
2117
+ cutToClipboard,
2118
+ copyToClipboard,
2119
+ pasteFromClipboard
2120
+ ]);
2121
+ const surface = /* @__PURE__ */ jsxRuntime.jsx(
2122
+ "div",
2123
+ {
2124
+ ref: dnd.active ? setDroppableRef : void 0,
2125
+ "data-tree-empty-area": "",
2126
+ "data-drop-active": dnd.active && isOver ? "true" : void 0,
2127
+ className: lib.cn(
2128
+ // Soaks up the remaining vertical space inside the scroll
2129
+ // container so right-click on whitespace lands here, not on
2130
+ // the scroll viewport.
2131
+ "min-h-[2.5rem] flex-1",
2132
+ dnd.active && isOver && "bg-primary/5 rounded-sm ring-1 ring-primary/30",
2133
+ className
2134
+ )
2135
+ }
2136
+ );
2137
+ if (!items || items.length === 0) {
2138
+ return surface;
2139
+ }
2140
+ return /* @__PURE__ */ jsxRuntime.jsxs(components.ContextMenu, { children: [
2141
+ /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuTrigger, { asChild: true, children: surface }),
2142
+ /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuContent, { children: items.map((item, idx) => {
2143
+ if (item === "separator") {
2144
+ return /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuSeparator, {}, `sep-${idx}`);
2145
+ }
2146
+ const Icon = item.icon;
2147
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2148
+ components.ContextMenuItem,
2149
+ {
2150
+ disabled: item.disabled,
2151
+ variant: item.destructive ? "destructive" : void 0,
2152
+ onSelect: () => item.onSelect({
2153
+ node: void 0,
2154
+ level: 0,
2155
+ isSelected: false,
2156
+ isExpanded: false,
2157
+ isFocused: false,
2158
+ isFolder: false,
2159
+ isLoading: false,
2160
+ isMatchingSearch: false
2161
+ }),
2162
+ children: [
2163
+ Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, {}) : null,
2164
+ item.label,
2165
+ item.shortcut ? /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuShortcut, { children: item.shortcut }) : null
2166
+ ]
2167
+ },
2168
+ item.id
2169
+ );
2170
+ }) })
2171
+ ] });
2172
+ }
2173
+ chunkPK6SKIKE_cjs.__name(TreeEmptyArea, "TreeEmptyArea");
858
2174
  function useTreeLabels() {
859
2175
  return useTreeContext().labels;
860
2176
  }
@@ -873,12 +2189,26 @@ function useTreeSelection() {
873
2189
  return React.useMemo(
874
2190
  () => ({
875
2191
  selectedIds,
2192
+ anchor: ctx.anchor,
876
2193
  select: ctx.select,
877
2194
  setSelectedIds: ctx.setSelectedIds,
878
2195
  clear: ctx.clearSelection,
2196
+ clickSelect: ctx.clickSelect,
2197
+ moveSelect: ctx.moveSelect,
2198
+ selectAll: ctx.selectAll,
879
2199
  isSelected
880
2200
  }),
881
- [selectedIds, ctx.select, ctx.setSelectedIds, ctx.clearSelection, isSelected]
2201
+ [
2202
+ selectedIds,
2203
+ ctx.anchor,
2204
+ ctx.select,
2205
+ ctx.setSelectedIds,
2206
+ ctx.clearSelection,
2207
+ ctx.clickSelect,
2208
+ ctx.moveSelect,
2209
+ ctx.selectAll,
2210
+ isSelected
2211
+ ]
882
2212
  );
883
2213
  }
884
2214
  chunkPK6SKIKE_cjs.__name(useTreeSelection, "useTreeSelection");
@@ -933,6 +2263,59 @@ function useTreeSearch() {
933
2263
  );
934
2264
  }
935
2265
  chunkPK6SKIKE_cjs.__name(useTreeSearch, "useTreeSearch");
2266
+ function useTreeDnd() {
2267
+ const ctx = useTreeContext();
2268
+ return ctx.dnd;
2269
+ }
2270
+ chunkPK6SKIKE_cjs.__name(useTreeDnd, "useTreeDnd");
2271
+ function useTreeClipboard() {
2272
+ const ctx = useTreeContext();
2273
+ const isCut = React.useCallback(
2274
+ (id) => ctx.clipboard?.kind === "cut" && ctx.clipboard.ids.includes(id),
2275
+ [ctx.clipboard]
2276
+ );
2277
+ return React.useMemo(
2278
+ () => ({
2279
+ clipboard: ctx.clipboard,
2280
+ isCut,
2281
+ cut: ctx.cutToClipboard,
2282
+ copy: ctx.copyToClipboard,
2283
+ paste: ctx.pasteFromClipboard,
2284
+ clear: ctx.clearClipboard
2285
+ }),
2286
+ [
2287
+ ctx.clipboard,
2288
+ isCut,
2289
+ ctx.cutToClipboard,
2290
+ ctx.copyToClipboard,
2291
+ ctx.pasteFromClipboard,
2292
+ ctx.clearClipboard
2293
+ ]
2294
+ );
2295
+ }
2296
+ chunkPK6SKIKE_cjs.__name(useTreeClipboard, "useTreeClipboard");
2297
+ function useTreeRename() {
2298
+ const ctx = useTreeContext();
2299
+ return React.useMemo(
2300
+ () => ({
2301
+ /** True when the host allowed inline rename AND the adapter exposes `rename`. */
2302
+ enabled: ctx.inlineRenameEnabled,
2303
+ /** Currently renaming id, or `null`. */
2304
+ renamingId: ctx.renamingId,
2305
+ startRename: ctx.startRename,
2306
+ cancelRename: ctx.cancelRename,
2307
+ commitRename: ctx.commitRename
2308
+ }),
2309
+ [
2310
+ ctx.inlineRenameEnabled,
2311
+ ctx.renamingId,
2312
+ ctx.startRename,
2313
+ ctx.cancelRename,
2314
+ ctx.commitRename
2315
+ ]
2316
+ );
2317
+ }
2318
+ chunkPK6SKIKE_cjs.__name(useTreeRename, "useTreeRename");
936
2319
  function useTreeActions() {
937
2320
  const ctx = useTreeContext();
938
2321
  return React.useMemo(
@@ -997,16 +2380,78 @@ function TreeSearchInput({ className, showMatches = true }) {
997
2380
  );
998
2381
  }
999
2382
  chunkPK6SKIKE_cjs.__name(TreeSearchInput, "TreeSearchInput");
2383
+
2384
+ // src/tools/data/Tree/hooks/keyboard/arrow-nav.ts
2385
+ function nextRowId(rows, idx) {
2386
+ if (rows.length === 0) return null;
2387
+ const next = rows[Math.min(idx + 1, rows.length - 1)] ?? rows[0];
2388
+ return next.node.id;
2389
+ }
2390
+ chunkPK6SKIKE_cjs.__name(nextRowId, "nextRowId");
2391
+ function prevRowId(rows, idx) {
2392
+ if (rows.length === 0) return null;
2393
+ const prev = rows[Math.max(idx - 1, 0)] ?? rows[0];
2394
+ return prev.node.id;
2395
+ }
2396
+ chunkPK6SKIKE_cjs.__name(prevRowId, "prevRowId");
2397
+ function edgeRowId(rows, edge) {
2398
+ if (rows.length === 0) return null;
2399
+ return edge === "first" ? rows[0].node.id : rows[rows.length - 1].node.id;
2400
+ }
2401
+ chunkPK6SKIKE_cjs.__name(edgeRowId, "edgeRowId");
2402
+
2403
+ // src/tools/data/Tree/hooks/keyboard/expand-collapse.ts
2404
+ function resolveRightArrow(current, rows, idx) {
2405
+ if (!current) return { kind: "noop" };
2406
+ if (current.isFolder && !current.isExpanded) {
2407
+ return { kind: "expand", id: current.node.id };
2408
+ }
2409
+ if (current.isFolder && current.isExpanded) {
2410
+ const next = rows[idx + 1];
2411
+ return next ? { kind: "focus", id: next.node.id } : { kind: "noop" };
2412
+ }
2413
+ return { kind: "noop" };
2414
+ }
2415
+ chunkPK6SKIKE_cjs.__name(resolveRightArrow, "resolveRightArrow");
2416
+ function resolveLeftArrow(current) {
2417
+ if (!current) return { kind: "noop" };
2418
+ if (current.isFolder && current.isExpanded) {
2419
+ return { kind: "collapse", id: current.node.id };
2420
+ }
2421
+ if (current.parentId) {
2422
+ return { kind: "focus", id: current.parentId };
2423
+ }
2424
+ return { kind: "noop" };
2425
+ }
2426
+ chunkPK6SKIKE_cjs.__name(resolveLeftArrow, "resolveLeftArrow");
2427
+
2428
+ // src/tools/data/Tree/hooks/keyboard/activation.ts
2429
+ function resolveActivate(current) {
2430
+ if (!current) return { kind: "noop" };
2431
+ if (current.isFolder) {
2432
+ return {
2433
+ kind: "toggle-folder",
2434
+ id: current.node.id,
2435
+ willExpand: !current.isExpanded
2436
+ };
2437
+ }
2438
+ return { kind: "activate-leaf", id: current.node.id };
2439
+ }
2440
+ chunkPK6SKIKE_cjs.__name(resolveActivate, "resolveActivate");
2441
+
2442
+ // src/tools/data/Tree/hooks/keyboard/use-tree-keyboard.ts
1000
2443
  function useTreeKeyboard({
1001
2444
  rows,
1002
2445
  focusedId,
1003
2446
  enabled = true,
2447
+ multiSelect = false,
1004
2448
  onFocus,
1005
2449
  onSelect,
1006
2450
  onActivate,
1007
2451
  onExpand,
1008
2452
  onCollapse,
1009
- onClearSelection
2453
+ onClearSelection,
2454
+ onSelectAll
1010
2455
  }) {
1011
2456
  const rowsRef = React.useRef(rows);
1012
2457
  const focusedIdRef = React.useRef(focusedId);
@@ -1019,53 +2464,65 @@ function useTreeKeyboard({
1019
2464
  return { rows: r, idx, current: idx >= 0 ? r[idx] : null };
1020
2465
  }, "getCurrent");
1021
2466
  const refDown = hooks.useHotkey(
1022
- "down",
1023
- () => {
2467
+ ["down", "shift+down"],
2468
+ (e) => {
1024
2469
  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);
2470
+ const id = nextRowId(r, idx);
2471
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
1028
2472
  },
1029
- { enabled, preventDefault: true, description: "Next row" }
2473
+ { enabled, preventDefault: true, description: "Next row (Shift extends)" }
1030
2474
  );
1031
2475
  const refUp = hooks.useHotkey(
1032
- "up",
1033
- () => {
2476
+ ["up", "shift+up"],
2477
+ (e) => {
1034
2478
  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);
2479
+ const id = prevRowId(r, idx);
2480
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
1038
2481
  },
1039
- { enabled, preventDefault: true, description: "Previous row" }
2482
+ { enabled, preventDefault: true, description: "Previous row (Shift extends)" }
1040
2483
  );
1041
2484
  const refHome = hooks.useHotkey(
1042
- "home",
1043
- () => {
1044
- const { rows: r } = getCurrent();
1045
- if (r.length === 0) return;
1046
- onFocus(r[0].node.id);
2485
+ ["home", "shift+home"],
2486
+ (e) => {
2487
+ const id = edgeRowId(rowsRef.current, "first");
2488
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
1047
2489
  },
1048
- { enabled, preventDefault: true, description: "First row" }
2490
+ { enabled, preventDefault: true, description: "First row (Shift extends)" }
1049
2491
  );
1050
2492
  const refEnd = hooks.useHotkey(
1051
- "end",
2493
+ ["end", "shift+end"],
2494
+ (e) => {
2495
+ const id = edgeRowId(rowsRef.current, "last");
2496
+ if (id) onFocus(id, { extend: multiSelect && e.shiftKey });
2497
+ },
2498
+ { enabled, preventDefault: true, description: "Last row (Shift extends)" }
2499
+ );
2500
+ const refSelectAll = hooks.useHotkey(
2501
+ "mod+a",
1052
2502
  () => {
1053
- const { rows: r } = getCurrent();
1054
- if (r.length === 0) return;
1055
- onFocus(r[r.length - 1].node.id);
2503
+ if (!multiSelect) return;
2504
+ onSelectAll?.();
1056
2505
  },
1057
- { enabled, preventDefault: true, description: "Last row" }
2506
+ {
2507
+ enabled: enabled && multiSelect,
2508
+ preventDefault: true,
2509
+ description: "Select all visible rows"
2510
+ }
1058
2511
  );
1059
2512
  const refRight = hooks.useHotkey(
1060
2513
  "right",
1061
2514
  () => {
1062
2515
  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);
2516
+ const out = resolveRightArrow(current, r, idx);
2517
+ switch (out.kind) {
2518
+ case "expand":
2519
+ onExpand(out.id);
2520
+ return;
2521
+ case "focus":
2522
+ onFocus(out.id, { extend: false });
2523
+ return;
2524
+ case "noop":
2525
+ return;
1069
2526
  }
1070
2527
  },
1071
2528
  { enabled, preventDefault: true, description: "Expand / first child" }
@@ -1074,11 +2531,16 @@ function useTreeKeyboard({
1074
2531
  "left",
1075
2532
  () => {
1076
2533
  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);
2534
+ const out = resolveLeftArrow(current);
2535
+ switch (out.kind) {
2536
+ case "collapse":
2537
+ onCollapse(out.id);
2538
+ return;
2539
+ case "focus":
2540
+ onFocus(out.id, { extend: false });
2541
+ return;
2542
+ case "noop":
2543
+ return;
1082
2544
  }
1083
2545
  },
1084
2546
  { enabled, preventDefault: true, description: "Collapse / parent" }
@@ -1087,13 +2549,14 @@ function useTreeKeyboard({
1087
2549
  ["enter", "space"],
1088
2550
  () => {
1089
2551
  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);
2552
+ const out = resolveActivate(current);
2553
+ if (out.kind === "noop") return;
2554
+ onSelect(out.kind === "activate-leaf" ? out.id : out.id);
2555
+ if (out.kind === "toggle-folder") {
2556
+ if (out.willExpand) onExpand(out.id);
2557
+ else onCollapse(out.id);
1095
2558
  } else {
1096
- onActivate(current.node.id);
2559
+ onActivate(out.id);
1097
2560
  }
1098
2561
  },
1099
2562
  { enabled, preventDefault: true, description: "Activate / toggle" }
@@ -1113,12 +2576,44 @@ function useTreeKeyboard({
1113
2576
  refLeft(instance);
1114
2577
  refActivate(instance);
1115
2578
  refEscape(instance);
2579
+ refSelectAll(instance);
1116
2580
  },
1117
- [refDown, refUp, refHome, refEnd, refRight, refLeft, refActivate, refEscape]
2581
+ [
2582
+ refDown,
2583
+ refUp,
2584
+ refHome,
2585
+ refEnd,
2586
+ refRight,
2587
+ refLeft,
2588
+ refActivate,
2589
+ refEscape,
2590
+ refSelectAll
2591
+ ]
1118
2592
  );
1119
2593
  return { ref };
1120
2594
  }
1121
2595
  chunkPK6SKIKE_cjs.__name(useTreeKeyboard, "useTreeKeyboard");
2596
+
2597
+ // src/tools/data/Tree/hooks/type-ahead/match-prefix.ts
2598
+ function findRowByPrefix(rows, getName, prefix) {
2599
+ if (prefix.length === 0) return void 0;
2600
+ return rows.find((row) => getName(row.node).toLowerCase().startsWith(prefix));
2601
+ }
2602
+ chunkPK6SKIKE_cjs.__name(findRowByPrefix, "findRowByPrefix");
2603
+ function isResetKey(key) {
2604
+ return key === "Escape" || key === "Enter" || key === "Tab" || key.startsWith("Arrow") || key === "Home" || key === "End" || key === "PageUp" || key === "PageDown";
2605
+ }
2606
+ chunkPK6SKIKE_cjs.__name(isResetKey, "isResetKey");
2607
+ function isTypingTarget(target) {
2608
+ const el = target;
2609
+ if (!el) return false;
2610
+ const tag = el.tagName;
2611
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
2612
+ return el.isContentEditable === true;
2613
+ }
2614
+ chunkPK6SKIKE_cjs.__name(isTypingTarget, "isTypingTarget");
2615
+
2616
+ // src/tools/data/Tree/hooks/type-ahead/use-tree-type-ahead.ts
1122
2617
  var FLUSH_MS = 600;
1123
2618
  function useTreeTypeAhead({
1124
2619
  rows,
@@ -1147,11 +2642,9 @@ function useTreeTypeAhead({
1147
2642
  }
1148
2643
  }, "reset");
1149
2644
  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;
2645
+ if (isTypingTarget(e.target)) return;
1153
2646
  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") {
2647
+ if (isResetKey(e.key)) {
1155
2648
  reset();
1156
2649
  return;
1157
2650
  }
@@ -1159,9 +2652,10 @@ function useTreeTypeAhead({
1159
2652
  bufferRef.current += e.key.toLowerCase();
1160
2653
  if (timerRef.current) clearTimeout(timerRef.current);
1161
2654
  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)
2655
+ const hit = findRowByPrefix(
2656
+ rowsRef.current,
2657
+ getNameRef.current,
2658
+ bufferRef.current
1165
2659
  );
1166
2660
  if (hit) {
1167
2661
  e.preventDefault();
@@ -1176,36 +2670,166 @@ function useTreeTypeAhead({
1176
2670
  }, [containerRef, enabled]);
1177
2671
  }
1178
2672
  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
- ] });
2673
+
2674
+ // src/tools/data/Tree/hooks/finder-hotkeys/build-ctx.ts
2675
+ function buildBuiltinCtx(input) {
2676
+ if (!input.adapter) return null;
2677
+ const selectedNodes = [];
2678
+ for (const id of input.selected) {
2679
+ const n = input.getNodeById(id);
2680
+ if (n) selectedNodes.push(n);
2681
+ }
2682
+ const targetNode = input.focused ? input.getNodeById(input.focused) ?? null : null;
2683
+ return {
2684
+ adapter: input.adapter,
2685
+ labels: input.labels,
2686
+ selectedNodes,
2687
+ targetNode,
2688
+ getName: input.getItemName,
2689
+ startInlineRename: input.startInlineRename,
2690
+ clipboard: input.clipboard
1206
2691
  };
1207
2692
  }
1208
- chunkPK6SKIKE_cjs.__name(actionsToRenderContextMenu, "actionsToRenderContextMenu");
2693
+ chunkPK6SKIKE_cjs.__name(buildBuiltinCtx, "buildBuiltinCtx");
2694
+
2695
+ // src/tools/data/Tree/hooks/finder-hotkeys/use-tree-finder-hotkeys.ts
2696
+ function useTreeFinderHotkeys(opts) {
2697
+ const optsRef = React.useRef(opts);
2698
+ optsRef.current = opts;
2699
+ const run = React.useCallback(async (action) => {
2700
+ const o = optsRef.current;
2701
+ if (o.paused) return;
2702
+ const ctx = buildBuiltinCtx({
2703
+ adapter: o.adapter,
2704
+ labels: o.labels,
2705
+ selected: o.selected,
2706
+ focused: o.focused,
2707
+ getNodeById: o.getNodeById,
2708
+ getItemName: o.getItemName,
2709
+ startInlineRename: o.startInlineRename,
2710
+ clipboard: o.clipboard
2711
+ });
2712
+ if (!ctx) return;
2713
+ await runBuiltinAction(action, ctx);
2714
+ }, []);
2715
+ const refDelete = hooks.useHotkey(
2716
+ ["mod+backspace", "delete"],
2717
+ () => void run("delete"),
2718
+ {
2719
+ enabled: opts.enabled,
2720
+ preventDefault: true,
2721
+ description: "Delete selected items",
2722
+ scope: "tree"
2723
+ }
2724
+ );
2725
+ const refRename = hooks.useHotkey("f2", () => void run("rename"), {
2726
+ enabled: opts.enabled,
2727
+ preventDefault: true,
2728
+ description: "Rename selected item",
2729
+ scope: "tree"
2730
+ });
2731
+ const refDuplicate = hooks.useHotkey("mod+d", () => void run("duplicate"), {
2732
+ enabled: opts.enabled,
2733
+ preventDefault: true,
2734
+ description: "Duplicate selected items",
2735
+ scope: "tree"
2736
+ });
2737
+ const refNewFolder = hooks.useHotkey("mod+shift+n", () => void run("new-folder"), {
2738
+ enabled: opts.enabled,
2739
+ preventDefault: true,
2740
+ description: "New folder",
2741
+ scope: "tree"
2742
+ });
2743
+ const refNewFile = hooks.useHotkey("mod+n", () => void run("new-file"), {
2744
+ enabled: opts.enabled,
2745
+ preventDefault: true,
2746
+ description: "New file",
2747
+ scope: "tree"
2748
+ });
2749
+ const refCut = hooks.useHotkey("mod+x", () => void run("cut"), {
2750
+ enabled: opts.enabled,
2751
+ preventDefault: true,
2752
+ description: "Cut",
2753
+ scope: "tree"
2754
+ });
2755
+ const refCopy = hooks.useHotkey("mod+c", () => void run("copy"), {
2756
+ enabled: opts.enabled,
2757
+ preventDefault: true,
2758
+ description: "Copy",
2759
+ scope: "tree"
2760
+ });
2761
+ const refPaste = hooks.useHotkey("mod+v", () => void run("paste"), {
2762
+ enabled: opts.enabled,
2763
+ preventDefault: true,
2764
+ description: "Paste",
2765
+ scope: "tree"
2766
+ });
2767
+ const ref = React.useCallback(
2768
+ (instance) => {
2769
+ refDelete(instance);
2770
+ refRename(instance);
2771
+ refDuplicate(instance);
2772
+ refNewFolder(instance);
2773
+ refNewFile(instance);
2774
+ refCut(instance);
2775
+ refCopy(instance);
2776
+ refPaste(instance);
2777
+ },
2778
+ [
2779
+ refDelete,
2780
+ refRename,
2781
+ refDuplicate,
2782
+ refNewFolder,
2783
+ refNewFile,
2784
+ refCut,
2785
+ refCopy,
2786
+ refPaste
2787
+ ]
2788
+ );
2789
+ return { ref };
2790
+ }
2791
+ chunkPK6SKIKE_cjs.__name(useTreeFinderHotkeys, "useTreeFinderHotkeys");
2792
+ function renderItemsAsContextMenu(rowProps, items, trigger) {
2793
+ return /* @__PURE__ */ jsxRuntime.jsxs(components.ContextMenu, { children: [
2794
+ /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuTrigger, { asChild: true, children: trigger }),
2795
+ /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuContent, { children: items.map((item, idx) => {
2796
+ if (item === "separator") {
2797
+ return /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuSeparator, {}, `sep-${idx}`);
2798
+ }
2799
+ const Icon = item.icon;
2800
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2801
+ components.ContextMenuItem,
2802
+ {
2803
+ disabled: item.disabled,
2804
+ variant: item.destructive ? "destructive" : void 0,
2805
+ onSelect: () => item.onSelect(rowProps),
2806
+ children: [
2807
+ Icon ? /* @__PURE__ */ jsxRuntime.jsx(Icon, {}) : null,
2808
+ item.label,
2809
+ item.shortcut ? /* @__PURE__ */ jsxRuntime.jsx(components.ContextMenuShortcut, { children: item.shortcut }) : null
2810
+ ]
2811
+ },
2812
+ item.id
2813
+ );
2814
+ }) })
2815
+ ] });
2816
+ }
2817
+ chunkPK6SKIKE_cjs.__name(renderItemsAsContextMenu, "renderItemsAsContextMenu");
2818
+ function tidyMenuItems(items) {
2819
+ const out = [];
2820
+ for (const it of items) {
2821
+ if (it === "separator") {
2822
+ if (out.length === 0) continue;
2823
+ if (out[out.length - 1] === "separator") continue;
2824
+ out.push(it);
2825
+ } else {
2826
+ out.push(it);
2827
+ }
2828
+ }
2829
+ while (out.length > 0 && out[out.length - 1] === "separator") out.pop();
2830
+ return out;
2831
+ }
2832
+ chunkPK6SKIKE_cjs.__name(tidyMenuItems, "tidyMenuItems");
1209
2833
  function TreeRoot(props) {
1210
2834
  const {
1211
2835
  data,
@@ -1224,6 +2848,10 @@ function TreeRoot(props) {
1224
2848
  enableSearch = false,
1225
2849
  enableTypeAhead = true,
1226
2850
  showIndentGuides = false,
2851
+ enableInlineRename = false,
2852
+ enableFinderHotkeys = false,
2853
+ enableDnD = false,
2854
+ canDrop,
1227
2855
  renderRow,
1228
2856
  renderIcon,
1229
2857
  renderLabel,
@@ -1233,14 +2861,11 @@ function TreeRoot(props) {
1233
2861
  labels,
1234
2862
  persistKey,
1235
2863
  persistSelection = false,
2864
+ adapter,
2865
+ defaultMenuItems,
1236
2866
  className,
1237
2867
  style
1238
2868
  } = 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
2869
  return /* @__PURE__ */ jsxRuntime.jsx(
1245
2870
  TreeProvider,
1246
2871
  {
@@ -1262,7 +2887,13 @@ function TreeRoot(props) {
1262
2887
  renderIcon,
1263
2888
  renderLabel,
1264
2889
  renderActions,
1265
- renderContextMenu: resolvedRenderContextMenu,
2890
+ renderContextMenu,
2891
+ contextMenuActions,
2892
+ adapter,
2893
+ defaultMenuItems,
2894
+ enableInlineRename,
2895
+ enableDnD,
2896
+ canDrop,
1266
2897
  labels,
1267
2898
  persistKey,
1268
2899
  persistSelection,
@@ -1273,6 +2904,7 @@ function TreeRoot(props) {
1273
2904
  style,
1274
2905
  enableSearch,
1275
2906
  enableTypeAhead,
2907
+ enableFinderHotkeys,
1276
2908
  renderRow
1277
2909
  }
1278
2910
  )
@@ -1285,14 +2917,27 @@ function TreeRootShell({
1285
2917
  style,
1286
2918
  enableSearch,
1287
2919
  enableTypeAhead,
2920
+ enableFinderHotkeys,
1288
2921
  renderRow
1289
2922
  }) {
1290
2923
  const containerRef = React.useRef(null);
1291
2924
  const ctx = useTreeContext();
2925
+ const isMulti = ctx.selectionMode === "multiple";
1292
2926
  const { ref: keyboardRef } = useTreeKeyboard({
1293
2927
  rows: ctx.flatRows,
1294
2928
  focusedId: ctx.focused,
1295
- onFocus: ctx.setFocus,
2929
+ multiSelect: isMulti,
2930
+ // Pause container hotkeys while inline rename is active so the
2931
+ // user can type freely (TreeRenameInput stops bubbling already, but
2932
+ // gating here is the cleaner second line of defence).
2933
+ enabled: ctx.renamingId === null,
2934
+ onFocus: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((id, { extend }) => {
2935
+ if (extend && isMulti) {
2936
+ ctx.moveSelect(id, { extend: true });
2937
+ } else {
2938
+ ctx.setFocus(id);
2939
+ }
2940
+ }, "onFocus"),
1296
2941
  onSelect: ctx.select,
1297
2942
  onActivate: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name((id) => {
1298
2943
  const row = ctx.flatRows.find((r) => r.node.id === id);
@@ -1300,14 +2945,37 @@ function TreeRootShell({
1300
2945
  }, "onActivate"),
1301
2946
  onExpand: ctx.expand,
1302
2947
  onCollapse: ctx.collapse,
1303
- onClearSelection: ctx.clearSelection
2948
+ onClearSelection: ctx.clearSelection,
2949
+ onSelectAll: ctx.selectAll
2950
+ });
2951
+ const { ref: finderHotkeysRef } = useTreeFinderHotkeys({
2952
+ enabled: enableFinderHotkeys,
2953
+ paused: ctx.renamingId !== null,
2954
+ adapter: ctx.adapter,
2955
+ labels: ctx.labels,
2956
+ selected: ctx.selected,
2957
+ focused: ctx.focused,
2958
+ getNodeById: ctx.getNodeById,
2959
+ getItemName: ctx.getItemName,
2960
+ startInlineRename: ctx.inlineRenameEnabled ? ctx.startRename : void 0,
2961
+ clipboard: {
2962
+ hasItems: !!ctx.clipboard && ctx.clipboard.ids.length > 0,
2963
+ cut: ctx.cutToClipboard,
2964
+ copy: ctx.copyToClipboard,
2965
+ // Hotkey paste targets the currently focused row (or null = root).
2966
+ paste: /* @__PURE__ */ chunkPK6SKIKE_cjs.__name(() => {
2967
+ const target = ctx.focused ? ctx.getNodeById(ctx.focused) ?? null : null;
2968
+ return ctx.pasteFromClipboard(target, "inside");
2969
+ }, "paste")
2970
+ }
1304
2971
  });
1305
2972
  const setContainerRef = React.useCallback(
1306
2973
  (instance) => {
1307
2974
  containerRef.current = instance;
1308
2975
  keyboardRef(instance);
2976
+ finderHotkeysRef(instance);
1309
2977
  },
1310
- [keyboardRef]
2978
+ [keyboardRef, finderHotkeysRef]
1311
2979
  );
1312
2980
  const focusedId = ctx.focused;
1313
2981
  React.useEffect(() => {
@@ -1330,7 +2998,22 @@ function TreeRootShell({
1330
2998
  onMatch: onTypeAheadMatch,
1331
2999
  enabled: enableTypeAhead
1332
3000
  });
1333
- return /* @__PURE__ */ jsxRuntime.jsxs(
3001
+ const finalRenderContextMenu = React.useMemo(() => {
3002
+ if (ctx.renderContextMenu) return ctx.renderContextMenu;
3003
+ const resolve = ctx.resolvedContextMenuActions;
3004
+ if (!resolve) return void 0;
3005
+ return (rowProps, trigger) => {
3006
+ const items = resolve(rowProps);
3007
+ const cleaned = items ? tidyMenuItems(items) : null;
3008
+ if (!cleaned || cleaned.length === 0) return trigger;
3009
+ return renderItemsAsContextMenu(rowProps, cleaned, trigger);
3010
+ };
3011
+ }, [ctx.renderContextMenu, ctx.resolvedContextMenuActions]);
3012
+ const childCtx = React.useMemo(
3013
+ () => ({ ...ctx, renderContextMenu: finalRenderContextMenu }),
3014
+ [ctx, finalRenderContextMenu]
3015
+ );
3016
+ const treeBody = /* @__PURE__ */ jsxRuntime.jsxs(
1334
3017
  "div",
1335
3018
  {
1336
3019
  ref: setContainerRef,
@@ -1348,13 +3031,35 @@ function TreeRootShell({
1348
3031
  "data-tree-root": "",
1349
3032
  children: [
1350
3033
  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 }) })
3034
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-auto px-1", children: [
3035
+ /* @__PURE__ */ jsxRuntime.jsx(TreeContent, { role: "group", children: renderRow }),
3036
+ /* @__PURE__ */ jsxRuntime.jsx(TreeEmptyArea, {})
3037
+ ] })
1352
3038
  ]
1353
3039
  }
1354
3040
  );
3041
+ const body = finalRenderContextMenu === ctx.renderContextMenu ? treeBody : /* @__PURE__ */ jsxRuntime.jsx(TreeContext.Provider, { value: childCtx, children: treeBody });
3042
+ return /* @__PURE__ */ jsxRuntime.jsx(TreeDndProvider, { children: body });
1355
3043
  }
1356
3044
  chunkPK6SKIKE_cjs.__name(TreeRootShell, "TreeRootShell");
1357
3045
  var TreeRoot_default = TreeRoot;
3046
+ function FinderTree(props) {
3047
+ return /* @__PURE__ */ jsxRuntime.jsx(
3048
+ TreeRoot,
3049
+ {
3050
+ selectionMode: "multiple",
3051
+ activationMode: "double-click",
3052
+ enableInlineRename: true,
3053
+ enableFinderHotkeys: true,
3054
+ enableDnD: true,
3055
+ enableTypeAhead: true,
3056
+ showIndentGuides: true,
3057
+ appearance: { density: "cozy" },
3058
+ ...props
3059
+ }
3060
+ );
3061
+ }
3062
+ chunkPK6SKIKE_cjs.__name(FinderTree, "FinderTree");
1358
3063
  function TreeSkeleton({ rows = 6, className }) {
1359
3064
  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
3065
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-4 shrink-0 animate-pulse rounded bg-muted" }),
@@ -1408,35 +3113,48 @@ chunkPK6SKIKE_cjs.__name(createDemoTree, "createDemoTree");
1408
3113
 
1409
3114
  exports.DEFAULT_TREE_APPEARANCE = DEFAULT_TREE_APPEARANCE;
1410
3115
  exports.DEFAULT_TREE_LABELS = DEFAULT_TREE_LABELS;
3116
+ exports.FinderTree = FinderTree;
3117
+ exports.TREE_DND_MIME = TREE_DND_MIME;
1411
3118
  exports.Tree = TreeRoot;
1412
3119
  exports.TreeChevron = TreeChevron;
1413
3120
  exports.TreeContent = TreeContent;
3121
+ exports.TreeDropIndicator = TreeDropIndicator;
1414
3122
  exports.TreeEmpty = TreeEmpty;
3123
+ exports.TreeEmptyArea = TreeEmptyArea;
1415
3124
  exports.TreeError = TreeError;
1416
3125
  exports.TreeIcon = TreeIcon;
1417
3126
  exports.TreeIndentGuides = TreeIndentGuides;
1418
3127
  exports.TreeLabel = TreeLabel;
1419
3128
  exports.TreeProvider = TreeProvider;
3129
+ exports.TreeRenameInput = TreeRenameInput;
1420
3130
  exports.TreeRoot = TreeRoot;
1421
3131
  exports.TreeRow = TreeRow;
1422
3132
  exports.TreeSearchInput = TreeSearchInput;
1423
3133
  exports.TreeSkeleton = TreeSkeleton;
1424
3134
  exports.appearanceToStyle = appearanceToStyle;
3135
+ exports.autoSelectRange = autoSelectRange;
1425
3136
  exports.clearTreeState = clearTreeState;
1426
3137
  exports.createChildCache = createChildCache;
1427
3138
  exports.createDemoTree = createDemoTree;
1428
3139
  exports.default = TreeRoot_default;
3140
+ exports.defaultCanDrop = defaultCanDrop;
1429
3141
  exports.flattenTree = flattenTree;
1430
3142
  exports.loadTreeState = loadTreeState;
1431
3143
  exports.resolveAppearance = resolveAppearance;
1432
3144
  exports.resolveChildren = resolveChildren;
3145
+ exports.resolveDropZone = resolveDropZone;
1433
3146
  exports.saveTreeState = saveTreeState;
3147
+ exports.splitFileName = splitFileName;
1434
3148
  exports.useTreeActions = useTreeActions;
3149
+ exports.useTreeClipboard = useTreeClipboard;
1435
3150
  exports.useTreeContext = useTreeContext;
3151
+ exports.useTreeDnd = useTreeDnd;
1436
3152
  exports.useTreeExpansion = useTreeExpansion;
3153
+ exports.useTreeFinderHotkeys = useTreeFinderHotkeys;
1437
3154
  exports.useTreeFocus = useTreeFocus;
1438
3155
  exports.useTreeKeyboard = useTreeKeyboard;
1439
3156
  exports.useTreeLabels = useTreeLabels;
3157
+ exports.useTreeRename = useTreeRename;
1440
3158
  exports.useTreeRows = useTreeRows;
1441
3159
  exports.useTreeSearch = useTreeSearch;
1442
3160
  exports.useTreeSelection = useTreeSelection;