@djangocfg/ui-tools 2.1.314 → 2.1.315

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 (46) hide show
  1. package/dist/TreeRoot-DO33TIS5.mjs +4 -0
  2. package/dist/TreeRoot-DO33TIS5.mjs.map +1 -0
  3. package/dist/TreeRoot-NJOZ2DMV.cjs +19 -0
  4. package/dist/TreeRoot-NJOZ2DMV.cjs.map +1 -0
  5. package/dist/chunk-E5BP4IXF.mjs +1231 -0
  6. package/dist/chunk-E5BP4IXF.mjs.map +1 -0
  7. package/dist/chunk-MA552EWC.cjs +1282 -0
  8. package/dist/chunk-MA552EWC.cjs.map +1 -0
  9. package/dist/index.cjs +186 -0
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +435 -2
  12. package/dist/index.d.ts +435 -2
  13. package/dist/index.mjs +60 -2
  14. package/dist/index.mjs.map +1 -1
  15. package/package.json +11 -6
  16. package/src/index.ts +4 -0
  17. package/src/tools/Tree/README.md +220 -0
  18. package/src/tools/Tree/Tree.story.tsx +536 -0
  19. package/src/tools/Tree/TreeRoot.tsx +157 -0
  20. package/src/tools/Tree/components/TreeChevron.tsx +39 -0
  21. package/src/tools/Tree/components/TreeContent.tsx +48 -0
  22. package/src/tools/Tree/components/TreeEmpty.tsx +21 -0
  23. package/src/tools/Tree/components/TreeError.tsx +24 -0
  24. package/src/tools/Tree/components/TreeIcon.tsx +29 -0
  25. package/src/tools/Tree/components/TreeIndentGuides.tsx +33 -0
  26. package/src/tools/Tree/components/TreeLabel.tsx +24 -0
  27. package/src/tools/Tree/components/TreeRow.tsx +173 -0
  28. package/src/tools/Tree/components/TreeSearchInput.tsx +50 -0
  29. package/src/tools/Tree/components/TreeSkeleton.tsx +22 -0
  30. package/src/tools/Tree/components/index.ts +22 -0
  31. package/src/tools/Tree/context/TreeContext.tsx +538 -0
  32. package/src/tools/Tree/context/hooks.ts +110 -0
  33. package/src/tools/Tree/context/index.ts +13 -0
  34. package/src/tools/Tree/data/appearance.ts +175 -0
  35. package/src/tools/Tree/data/childCache.ts +43 -0
  36. package/src/tools/Tree/data/createDemoTree.ts +42 -0
  37. package/src/tools/Tree/data/flatten.ts +51 -0
  38. package/src/tools/Tree/data/index.ts +24 -0
  39. package/src/tools/Tree/data/persist.ts +62 -0
  40. package/src/tools/Tree/hooks/index.ts +6 -0
  41. package/src/tools/Tree/hooks/useTreeKeyboard.ts +137 -0
  42. package/src/tools/Tree/hooks/useTreeTypeAhead.ts +100 -0
  43. package/src/tools/Tree/index.tsx +99 -0
  44. package/src/tools/Tree/lazy.tsx +14 -0
  45. package/src/tools/Tree/types.ts +136 -0
  46. package/src/tools/index.ts +75 -0
@@ -0,0 +1,1282 @@
1
+ 'use strict';
2
+
3
+ var chunkWGEGR3DF_cjs = require('./chunk-WGEGR3DF.cjs');
4
+ var React = require('react');
5
+ var lib = require('@djangocfg/ui-core/lib');
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var lucideReact = require('lucide-react');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
28
+
29
+ // src/tools/Tree/types.ts
30
+ var DEFAULT_TREE_LABELS = {
31
+ loading: "Loading\u2026",
32
+ empty: "Nothing to show",
33
+ error: "Failed to load",
34
+ searchPlaceholder: "Search\u2026",
35
+ searchMatches: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((n) => `${n} match${n === 1 ? "" : "es"}`, "searchMatches"),
36
+ ariaLabel: "Tree"
37
+ };
38
+
39
+ // src/tools/Tree/data/childCache.ts
40
+ var createChildCache = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => /* @__PURE__ */ new Map(), "createChildCache");
41
+ var resolveChildren = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((cache, node) => {
42
+ if (Array.isArray(node.children)) {
43
+ return { children: node.children, status: "loaded" };
44
+ }
45
+ const entry = cache.get(node.id);
46
+ if (!entry) return { children: null, status: "idle" };
47
+ if (entry.status === "loaded") {
48
+ return { children: entry.children, status: "loaded" };
49
+ }
50
+ return { children: null, status: entry.status, error: entry.error };
51
+ }, "resolveChildren");
52
+
53
+ // src/tools/Tree/data/flatten.ts
54
+ var isNodeFolder = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((node) => {
55
+ if (typeof node.isFolder === "boolean") return node.isFolder;
56
+ return Array.isArray(node.children);
57
+ }, "isNodeFolder");
58
+ function flattenTree({ roots, expandedIds, cache }) {
59
+ const out = [];
60
+ const walk = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((nodes, level, parentId) => {
61
+ for (const node of nodes) {
62
+ const isFolder = isNodeFolder(node);
63
+ const isExpanded = expandedIds.has(node.id);
64
+ const resolved = isFolder ? resolveChildren(cache, node) : { children: [], status: "loaded" };
65
+ out.push({
66
+ node,
67
+ level,
68
+ parentId,
69
+ isFolder,
70
+ isExpanded,
71
+ isLoading: resolved.status === "loading",
72
+ hasError: resolved.status === "error"
73
+ });
74
+ if (isFolder && isExpanded && resolved.children) {
75
+ walk(resolved.children, level + 1, node.id);
76
+ }
77
+ }
78
+ }, "walk");
79
+ walk(roots, 0, null);
80
+ return out;
81
+ }
82
+ chunkWGEGR3DF_cjs.__name(flattenTree, "flattenTree");
83
+
84
+ // src/tools/Tree/data/persist.ts
85
+ var KEY_PREFIX = "@djangocfg/tree:";
86
+ var VERSION = 1;
87
+ function safeStorage() {
88
+ if (typeof window === "undefined") return null;
89
+ try {
90
+ return window.localStorage;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+ chunkWGEGR3DF_cjs.__name(safeStorage, "safeStorage");
96
+ function loadTreeState(key) {
97
+ const storage = safeStorage();
98
+ if (!storage) return null;
99
+ try {
100
+ const raw = storage.getItem(KEY_PREFIX + key);
101
+ if (!raw) return null;
102
+ const parsed = JSON.parse(raw);
103
+ if (parsed.version !== VERSION) return null;
104
+ return {
105
+ expandedItems: Array.isArray(parsed.expandedItems) ? parsed.expandedItems : [],
106
+ selectedItems: Array.isArray(parsed.selectedItems) ? parsed.selectedItems : []
107
+ };
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ chunkWGEGR3DF_cjs.__name(loadTreeState, "loadTreeState");
113
+ function saveTreeState(key, state) {
114
+ const storage = safeStorage();
115
+ if (!storage) return;
116
+ try {
117
+ const payload = { ...state, version: VERSION };
118
+ storage.setItem(KEY_PREFIX + key, JSON.stringify(payload));
119
+ } catch {
120
+ }
121
+ }
122
+ chunkWGEGR3DF_cjs.__name(saveTreeState, "saveTreeState");
123
+ function clearTreeState(key) {
124
+ const storage = safeStorage();
125
+ if (!storage) return;
126
+ try {
127
+ storage.removeItem(KEY_PREFIX + key);
128
+ } catch {
129
+ }
130
+ }
131
+ chunkWGEGR3DF_cjs.__name(clearTreeState, "clearTreeState");
132
+
133
+ // src/tools/Tree/data/appearance.ts
134
+ var DENSITY_PRESETS = {
135
+ compact: { rowHeight: 24, iconSize: 14, fontSize: 13, gap: 6 },
136
+ cozy: { rowHeight: 28, iconSize: 16, fontSize: 13, gap: 8 },
137
+ comfortable: { rowHeight: 32, iconSize: 16, fontSize: 14, gap: 8 }
138
+ };
139
+ var DEFAULT_TREE_APPEARANCE = {
140
+ density: "cozy",
141
+ ...DENSITY_PRESETS.cozy,
142
+ iconStrokeWidth: 1.5,
143
+ indent: 16,
144
+ accent: "default",
145
+ radius: "sm",
146
+ indentGuideOpacity: 0.4,
147
+ showActiveIndicator: true
148
+ };
149
+ function resolveAppearance(input, outerIndent) {
150
+ if (!input && outerIndent === void 0) return DEFAULT_TREE_APPEARANCE;
151
+ const density = input?.density ?? "cozy";
152
+ const preset = DENSITY_PRESETS[density];
153
+ return {
154
+ density,
155
+ rowHeight: input?.rowHeight ?? preset.rowHeight,
156
+ iconSize: input?.iconSize ?? preset.iconSize,
157
+ iconStrokeWidth: input?.iconStrokeWidth ?? DEFAULT_TREE_APPEARANCE.iconStrokeWidth,
158
+ fontSize: input?.fontSize ?? preset.fontSize,
159
+ gap: input?.gap ?? preset.gap,
160
+ indent: input?.indent ?? outerIndent ?? DEFAULT_TREE_APPEARANCE.indent,
161
+ accent: input?.accent ?? DEFAULT_TREE_APPEARANCE.accent,
162
+ radius: input?.radius ?? DEFAULT_TREE_APPEARANCE.radius,
163
+ indentGuideOpacity: input?.indentGuideOpacity ?? DEFAULT_TREE_APPEARANCE.indentGuideOpacity,
164
+ showActiveIndicator: input?.showActiveIndicator ?? DEFAULT_TREE_APPEARANCE.showActiveIndicator
165
+ };
166
+ }
167
+ chunkWGEGR3DF_cjs.__name(resolveAppearance, "resolveAppearance");
168
+ function appearanceToStyle(a) {
169
+ return {
170
+ ["--tree-row-height"]: `${a.rowHeight}px`,
171
+ ["--tree-icon-size"]: `${a.iconSize}px`,
172
+ ["--tree-icon-stroke"]: a.iconStrokeWidth,
173
+ ["--tree-font-size"]: `${a.fontSize}px`,
174
+ ["--tree-gap"]: `${a.gap}px`,
175
+ ["--tree-indent"]: `${a.indent}px`,
176
+ ["--tree-guide-opacity"]: a.indentGuideOpacity
177
+ };
178
+ }
179
+ chunkWGEGR3DF_cjs.__name(appearanceToStyle, "appearanceToStyle");
180
+ var RADIUS_CLASS = {
181
+ none: "rounded-none",
182
+ sm: "rounded-sm",
183
+ md: "rounded-md"
184
+ };
185
+ function radiusClass(a) {
186
+ return RADIUS_CLASS[a.radius];
187
+ }
188
+ chunkWGEGR3DF_cjs.__name(radiusClass, "radiusClass");
189
+ var HOVER = {
190
+ subtle: "hover:bg-foreground/[.03]",
191
+ default: "hover:bg-foreground/[.06]",
192
+ strong: "hover:bg-foreground/[.09]"
193
+ };
194
+ var FOCUSED_NOT_SELECTED = {
195
+ subtle: "data-[focused=true]:bg-foreground/[.05]",
196
+ default: "data-[focused=true]:bg-foreground/[.08]",
197
+ strong: "data-[focused=true]:bg-foreground/[.12]"
198
+ };
199
+ var SELECTED_INACTIVE = {
200
+ subtle: "data-[selected=true]:bg-foreground/[.06]",
201
+ default: "data-[selected=true]:bg-foreground/[.10]",
202
+ strong: "data-[selected=true]:bg-foreground/[.14]"
203
+ };
204
+ var SELECTED_ACTIVE = {
205
+ subtle: "data-[selected=true]:group-focus-within/tree:bg-primary/10 data-[selected=true]:group-focus-within/tree:text-primary",
206
+ default: "data-[selected=true]:group-focus-within/tree:bg-primary/15 data-[selected=true]:group-focus-within/tree:text-primary",
207
+ strong: "data-[selected=true]:group-focus-within/tree:bg-primary/25 data-[selected=true]:group-focus-within/tree:text-primary"
208
+ };
209
+ function rowStateClasses(a) {
210
+ return [
211
+ HOVER[a.accent],
212
+ FOCUSED_NOT_SELECTED[a.accent],
213
+ SELECTED_INACTIVE[a.accent],
214
+ SELECTED_ACTIVE[a.accent]
215
+ ].join(" ");
216
+ }
217
+ chunkWGEGR3DF_cjs.__name(rowStateClasses, "rowStateClasses");
218
+ var reducer = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((state, action) => {
219
+ switch (action.type) {
220
+ case "expand": {
221
+ if (state.expanded.has(action.id)) return state;
222
+ const next = new Set(state.expanded);
223
+ next.add(action.id);
224
+ return { ...state, expanded: next };
225
+ }
226
+ case "collapse": {
227
+ if (!state.expanded.has(action.id)) return state;
228
+ const next = new Set(state.expanded);
229
+ next.delete(action.id);
230
+ return { ...state, expanded: next };
231
+ }
232
+ case "toggle": {
233
+ const next = new Set(state.expanded);
234
+ if (next.has(action.id)) next.delete(action.id);
235
+ else next.add(action.id);
236
+ return { ...state, expanded: next };
237
+ }
238
+ case "set-expanded":
239
+ return { ...state, expanded: new Set(action.ids) };
240
+ case "select": {
241
+ if (action.mode === "none") return state;
242
+ if (action.mode === "single") {
243
+ return { ...state, selected: /* @__PURE__ */ new Set([action.id]), focused: action.id };
244
+ }
245
+ const next = new Set(state.selected);
246
+ if (next.has(action.id)) next.delete(action.id);
247
+ else next.add(action.id);
248
+ return { ...state, selected: next, focused: action.id };
249
+ }
250
+ case "select-many":
251
+ return { ...state, selected: new Set(action.ids) };
252
+ case "clear-selection":
253
+ return { ...state, selected: /* @__PURE__ */ new Set() };
254
+ case "focus":
255
+ return { ...state, focused: action.id };
256
+ case "set-query":
257
+ return { ...state, query: action.q };
258
+ case "cache-tick":
259
+ return { ...state, cacheTick: state.cacheTick + 1 };
260
+ default:
261
+ return state;
262
+ }
263
+ }, "reducer");
264
+ var TreeContext = React.createContext(null);
265
+ function useTreeContext() {
266
+ const ctx = React__namespace.useContext(TreeContext);
267
+ if (!ctx) {
268
+ throw new Error("useTreeContext must be used inside <TreeProvider>");
269
+ }
270
+ return ctx;
271
+ }
272
+ chunkWGEGR3DF_cjs.__name(useTreeContext, "useTreeContext");
273
+ var setEqualsArr = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((set, arr) => {
274
+ if (set.size !== arr.length) return false;
275
+ for (const id of arr) if (!set.has(id)) return false;
276
+ return true;
277
+ }, "setEqualsArr");
278
+ var collectAllIds = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((roots, cache, out) => {
279
+ for (const node of roots) {
280
+ if (Array.isArray(node.children)) {
281
+ out.push(node.id);
282
+ collectAllIds(node.children, cache, out);
283
+ } else if (node.isFolder) {
284
+ out.push(node.id);
285
+ const entry = cache.get(node.id);
286
+ if (entry?.children) collectAllIds(entry.children, cache, out);
287
+ }
288
+ }
289
+ }, "collectAllIds");
290
+ function TreeProvider(props) {
291
+ const {
292
+ data,
293
+ getItemName,
294
+ loadChildren,
295
+ selectionMode = "single",
296
+ initialExpandedIds,
297
+ initialSelectedIds,
298
+ indent,
299
+ appearance,
300
+ onSelectionChange,
301
+ onExpansionChange,
302
+ onActivate,
303
+ enableSearch = false,
304
+ showIndentGuides = false,
305
+ renderIcon,
306
+ renderLabel,
307
+ renderActions,
308
+ renderContextMenu,
309
+ labels: labelsOverride,
310
+ persistKey,
311
+ persistSelection = false,
312
+ children
313
+ } = props;
314
+ const labels = React.useMemo(
315
+ () => ({ ...DEFAULT_TREE_LABELS, ...labelsOverride }),
316
+ [labelsOverride]
317
+ );
318
+ const resolvedAppearance = React.useMemo(
319
+ () => resolveAppearance(appearance, indent),
320
+ [appearance, indent]
321
+ );
322
+ const persisted = React.useMemo(
323
+ () => persistKey ? loadTreeState(persistKey) : null,
324
+ [persistKey]
325
+ );
326
+ const [state, dispatch] = React.useReducer(reducer, void 0, () => ({
327
+ expanded: new Set(persisted?.expandedItems ?? initialExpandedIds ?? []),
328
+ selected: new Set(
329
+ (persistSelection ? persisted?.selectedItems : void 0) ?? initialSelectedIds ?? []
330
+ ),
331
+ focused: null,
332
+ query: "",
333
+ cacheTick: 0
334
+ }));
335
+ const cacheRef = React.useRef(createChildCache());
336
+ const inflightRef = React.useRef(/* @__PURE__ */ new Map());
337
+ const fetchChildren = React.useCallback(
338
+ async (node) => {
339
+ if (!loadChildren) return;
340
+ if (Array.isArray(node.children)) return;
341
+ const existing = cacheRef.current.get(node.id);
342
+ if (existing?.status === "loaded" || existing?.status === "loading") return;
343
+ const inflight = inflightRef.current.get(node.id);
344
+ if (inflight) return inflight;
345
+ cacheRef.current.set(node.id, { status: "loading", children: [] });
346
+ dispatch({ type: "cache-tick" });
347
+ const promise = (async () => {
348
+ try {
349
+ const children2 = await loadChildren(node);
350
+ cacheRef.current.set(node.id, { status: "loaded", children: children2 });
351
+ } catch (err) {
352
+ cacheRef.current.set(node.id, {
353
+ status: "error",
354
+ children: [],
355
+ error: err instanceof Error ? err.message : String(err)
356
+ });
357
+ } finally {
358
+ inflightRef.current.delete(node.id);
359
+ dispatch({ type: "cache-tick" });
360
+ }
361
+ })();
362
+ inflightRef.current.set(node.id, promise);
363
+ return promise;
364
+ },
365
+ [loadChildren]
366
+ );
367
+ const nodeById = React.useMemo(() => {
368
+ const map = /* @__PURE__ */ new Map();
369
+ const walk = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((nodes) => {
370
+ for (const n of nodes) {
371
+ map.set(n.id, n);
372
+ if (Array.isArray(n.children)) walk(n.children);
373
+ else {
374
+ const entry = cacheRef.current.get(n.id);
375
+ if (entry?.children) walk(entry.children);
376
+ }
377
+ }
378
+ }, "walk");
379
+ walk(data);
380
+ return map;
381
+ }, [data, state.cacheTick]);
382
+ React.useEffect(() => {
383
+ if (!loadChildren) return;
384
+ for (const id of state.expanded) {
385
+ const node = nodeById.get(id);
386
+ if (!node) continue;
387
+ void fetchChildren(node);
388
+ }
389
+ }, [loadChildren, state.expanded, state.cacheTick, nodeById, fetchChildren]);
390
+ const flatRows = React.useMemo(
391
+ () => flattenTree({
392
+ roots: data,
393
+ expandedIds: state.expanded,
394
+ cache: cacheRef.current
395
+ }),
396
+ [data, state.expanded, state.cacheTick]
397
+ );
398
+ const matchingIds = React.useMemo(() => {
399
+ const set = /* @__PURE__ */ new Set();
400
+ if (!enableSearch || state.query.trim() === "") return set;
401
+ const q = state.query.trim().toLowerCase();
402
+ for (const row of flatRows) {
403
+ if (getItemName(row.node).toLowerCase().includes(q)) {
404
+ set.add(row.node.id);
405
+ }
406
+ }
407
+ return set;
408
+ }, [enableSearch, state.query, flatRows, getItemName]);
409
+ const onSelectionChangeRef = React.useRef(onSelectionChange);
410
+ const onExpansionChangeRef = React.useRef(onExpansionChange);
411
+ const onActivateRef = React.useRef(onActivate);
412
+ onSelectionChangeRef.current = onSelectionChange;
413
+ onExpansionChangeRef.current = onExpansionChange;
414
+ onActivateRef.current = onActivate;
415
+ const lastSelectedArrRef = React.useRef([...state.selected]);
416
+ const lastExpandedArrRef = React.useRef([...state.expanded]);
417
+ React.useEffect(() => {
418
+ const arr = [...state.expanded];
419
+ if (!setEqualsArr(state.expanded, lastExpandedArrRef.current)) {
420
+ lastExpandedArrRef.current = arr;
421
+ onExpansionChangeRef.current?.(arr);
422
+ if (persistKey) {
423
+ saveTreeState(persistKey, {
424
+ expandedItems: arr,
425
+ selectedItems: persistSelection ? [...state.selected] : []
426
+ });
427
+ }
428
+ }
429
+ }, [state.expanded, persistKey, persistSelection, state.selected]);
430
+ React.useEffect(() => {
431
+ const arr = [...state.selected];
432
+ if (!setEqualsArr(state.selected, lastSelectedArrRef.current)) {
433
+ lastSelectedArrRef.current = arr;
434
+ onSelectionChangeRef.current?.(arr);
435
+ if (persistKey && persistSelection) {
436
+ saveTreeState(persistKey, {
437
+ expandedItems: [...state.expanded],
438
+ selectedItems: arr
439
+ });
440
+ }
441
+ }
442
+ }, [state.selected, persistKey, persistSelection, state.expanded]);
443
+ const expand = React.useCallback((id) => dispatch({ type: "expand", id }), []);
444
+ const collapse = React.useCallback((id) => dispatch({ type: "collapse", id }), []);
445
+ const toggle = React.useCallback((id) => dispatch({ type: "toggle", id }), []);
446
+ const expandAll = React.useCallback(() => {
447
+ const ids = [];
448
+ collectAllIds(data, cacheRef.current, ids);
449
+ dispatch({ type: "set-expanded", ids });
450
+ }, [data]);
451
+ const collapseAll = React.useCallback(
452
+ () => dispatch({ type: "set-expanded", ids: [] }),
453
+ []
454
+ );
455
+ const select = React.useCallback(
456
+ (id) => dispatch({ type: "select", id, mode: selectionMode }),
457
+ [selectionMode]
458
+ );
459
+ const setSelectedIds = React.useCallback(
460
+ (ids) => dispatch({ type: "select-many", ids }),
461
+ []
462
+ );
463
+ const clearSelection = React.useCallback(() => dispatch({ type: "clear-selection" }), []);
464
+ const setFocus = React.useCallback(
465
+ (id) => dispatch({ type: "focus", id }),
466
+ []
467
+ );
468
+ const setQuery = React.useCallback((q) => dispatch({ type: "set-query", q }), []);
469
+ const refresh = React.useCallback(
470
+ async (id) => {
471
+ const node = nodeById.get(id);
472
+ if (!node || !loadChildren) return;
473
+ cacheRef.current.delete(id);
474
+ dispatch({ type: "cache-tick" });
475
+ await fetchChildren(node);
476
+ },
477
+ [nodeById, loadChildren, fetchChildren]
478
+ );
479
+ const refreshAll = React.useCallback(async () => {
480
+ cacheRef.current.clear();
481
+ dispatch({ type: "cache-tick" });
482
+ if (!loadChildren) return;
483
+ await Promise.all(
484
+ [...state.expanded].map((id) => {
485
+ const node = nodeById.get(id);
486
+ return node ? fetchChildren(node) : void 0;
487
+ })
488
+ );
489
+ }, [loadChildren, state.expanded, nodeById, fetchChildren]);
490
+ const activate = React.useCallback(
491
+ (node) => onActivateRef.current?.(node),
492
+ []
493
+ );
494
+ const value = React.useMemo(
495
+ () => ({
496
+ expanded: state.expanded,
497
+ selected: state.selected,
498
+ focused: state.focused,
499
+ query: state.query,
500
+ flatRows,
501
+ matchingIds,
502
+ expand,
503
+ collapse,
504
+ toggle,
505
+ expandAll,
506
+ collapseAll,
507
+ select,
508
+ setSelectedIds,
509
+ clearSelection,
510
+ setFocus,
511
+ setQuery,
512
+ refresh,
513
+ refreshAll,
514
+ activate,
515
+ labels,
516
+ appearance: resolvedAppearance,
517
+ indent: resolvedAppearance.indent,
518
+ selectionMode,
519
+ enableSearch,
520
+ showIndentGuides,
521
+ getItemName,
522
+ renderIcon,
523
+ renderLabel,
524
+ renderActions,
525
+ renderContextMenu
526
+ }),
527
+ [
528
+ state.expanded,
529
+ state.selected,
530
+ state.focused,
531
+ state.query,
532
+ flatRows,
533
+ matchingIds,
534
+ expand,
535
+ collapse,
536
+ toggle,
537
+ expandAll,
538
+ collapseAll,
539
+ select,
540
+ setSelectedIds,
541
+ clearSelection,
542
+ setFocus,
543
+ setQuery,
544
+ refresh,
545
+ refreshAll,
546
+ activate,
547
+ labels,
548
+ resolvedAppearance,
549
+ selectionMode,
550
+ enableSearch,
551
+ showIndentGuides,
552
+ getItemName,
553
+ renderIcon,
554
+ renderLabel,
555
+ renderActions,
556
+ renderContextMenu
557
+ ]
558
+ );
559
+ return /* @__PURE__ */ jsxRuntime.jsx(TreeContext.Provider, { value, children });
560
+ }
561
+ chunkWGEGR3DF_cjs.__name(TreeProvider, "TreeProvider");
562
+ function TreeChevron({ isExpanded, isFolder, className }) {
563
+ const { appearance } = useTreeContext();
564
+ const size = { width: "var(--tree-icon-size)", height: "var(--tree-icon-size)" };
565
+ if (!isFolder) {
566
+ return /* @__PURE__ */ jsxRuntime.jsx(
567
+ "span",
568
+ {
569
+ "aria-hidden": true,
570
+ style: size,
571
+ className: lib.cn("inline-block shrink-0", className)
572
+ }
573
+ );
574
+ }
575
+ const Icon = isExpanded ? lucideReact.ChevronDown : lucideReact.ChevronRight;
576
+ return /* @__PURE__ */ jsxRuntime.jsx(
577
+ Icon,
578
+ {
579
+ "aria-hidden": true,
580
+ strokeWidth: appearance.iconStrokeWidth,
581
+ style: size,
582
+ className: lib.cn(
583
+ "shrink-0 text-muted-foreground/70 transition-transform",
584
+ className
585
+ )
586
+ }
587
+ );
588
+ }
589
+ chunkWGEGR3DF_cjs.__name(TreeChevron, "TreeChevron");
590
+ function TreeIcon({ isFolder, isExpanded, className }) {
591
+ const { appearance } = useTreeContext();
592
+ const Icon = isFolder ? isExpanded ? lucideReact.FolderOpen : lucideReact.Folder : lucideReact.File;
593
+ return /* @__PURE__ */ jsxRuntime.jsx(
594
+ Icon,
595
+ {
596
+ "aria-hidden": true,
597
+ strokeWidth: appearance.iconStrokeWidth,
598
+ style: { width: "var(--tree-icon-size)", height: "var(--tree-icon-size)" },
599
+ className: lib.cn(
600
+ "shrink-0",
601
+ isFolder ? "text-foreground/70" : "text-muted-foreground/80",
602
+ className
603
+ )
604
+ }
605
+ );
606
+ }
607
+ chunkWGEGR3DF_cjs.__name(TreeIcon, "TreeIcon");
608
+ function TreeIndentGuides({ level, indent }) {
609
+ const { appearance } = useTreeContext();
610
+ if (level <= 0) return null;
611
+ return /* @__PURE__ */ jsxRuntime.jsx(
612
+ "span",
613
+ {
614
+ "aria-hidden": true,
615
+ className: "pointer-events-none absolute inset-y-0 left-0",
616
+ style: { width: 8 + level * indent, opacity: appearance.indentGuideOpacity },
617
+ children: Array.from({ length: level }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
618
+ "span",
619
+ {
620
+ className: "absolute inset-y-0 w-px bg-border",
621
+ style: { left: 8 + i * indent + indent / 2 - 0.5 }
622
+ },
623
+ i
624
+ ))
625
+ }
626
+ );
627
+ }
628
+ chunkWGEGR3DF_cjs.__name(TreeIndentGuides, "TreeIndentGuides");
629
+ function TreeLabel({ children, isMatchingSearch, className }) {
630
+ return /* @__PURE__ */ jsxRuntime.jsx(
631
+ "span",
632
+ {
633
+ style: { fontSize: "var(--tree-font-size)" },
634
+ className: lib.cn(
635
+ "truncate leading-tight tracking-[-0.005em]",
636
+ isMatchingSearch && "font-medium text-foreground",
637
+ className
638
+ ),
639
+ children
640
+ }
641
+ );
642
+ }
643
+ chunkWGEGR3DF_cjs.__name(TreeLabel, "TreeLabel");
644
+ function TreeRow({ row, className }) {
645
+ const ctx = useTreeContext();
646
+ const {
647
+ appearance,
648
+ showIndentGuides,
649
+ selected,
650
+ focused,
651
+ matchingIds,
652
+ select,
653
+ toggle,
654
+ setFocus,
655
+ activate,
656
+ getItemName,
657
+ renderIcon,
658
+ renderLabel,
659
+ renderActions,
660
+ renderContextMenu
661
+ } = ctx;
662
+ const { node, level, isFolder, isExpanded, isLoading } = row;
663
+ const isSelected = selected.has(node.id);
664
+ const isFocused = focused === node.id;
665
+ const isMatchingSearch = matchingIds.has(node.id);
666
+ const slot = {
667
+ node,
668
+ level,
669
+ isSelected,
670
+ isExpanded,
671
+ isFocused,
672
+ isFolder,
673
+ isLoading,
674
+ isMatchingSearch
675
+ };
676
+ const handleActivate = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => {
677
+ if (node.disabled) return;
678
+ setFocus(node.id);
679
+ select(node.id);
680
+ if (isFolder) toggle(node.id);
681
+ else activate(node);
682
+ }, "handleActivate");
683
+ const handleClick = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
684
+ handleActivate();
685
+ e.currentTarget.scrollIntoView?.({ block: "nearest" });
686
+ }, "handleClick");
687
+ const handleDoubleClick = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => {
688
+ if (node.disabled) return;
689
+ if (!isFolder) activate(node);
690
+ }, "handleDoubleClick");
691
+ const handleKeyDown = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
692
+ if (node.disabled) return;
693
+ if (e.key === "Enter" || e.key === " ") {
694
+ e.preventDefault();
695
+ handleActivate();
696
+ }
697
+ }, "handleKeyDown");
698
+ const trigger = /* @__PURE__ */ jsxRuntime.jsxs(
699
+ "div",
700
+ {
701
+ role: "treeitem",
702
+ "aria-level": level + 1,
703
+ "aria-expanded": isFolder ? isExpanded : void 0,
704
+ "aria-selected": isSelected || void 0,
705
+ "aria-current": isSelected ? "true" : void 0,
706
+ "aria-disabled": node.disabled || void 0,
707
+ "data-tree-row": "",
708
+ "data-id": node.id,
709
+ "data-selected": isSelected ? "true" : void 0,
710
+ "data-focused": isFocused && !isSelected ? "true" : void 0,
711
+ "data-folder": isFolder || void 0,
712
+ "data-expanded": isExpanded || void 0,
713
+ tabIndex: isFocused ? 0 : -1,
714
+ style: {
715
+ paddingLeft: 6 + level * appearance.indent,
716
+ height: "var(--tree-row-height)",
717
+ gap: "var(--tree-gap)"
718
+ },
719
+ onClick: handleClick,
720
+ onDoubleClick: handleDoubleClick,
721
+ onKeyDown: handleKeyDown,
722
+ onFocus: () => setFocus(node.id),
723
+ className: lib.cn(
724
+ "group/row relative flex w-full select-none items-center pr-2 text-left",
725
+ "transition-colors outline-none",
726
+ node.disabled ? "cursor-not-allowed" : "cursor-pointer",
727
+ radiusClass(appearance),
728
+ rowStateClasses(appearance),
729
+ "focus-visible:ring-1 focus-visible:ring-ring/50",
730
+ isMatchingSearch && "ring-1 ring-primary/30",
731
+ node.disabled && "opacity-50",
732
+ className
733
+ ),
734
+ children: [
735
+ appearance.showActiveIndicator && isSelected ? /* @__PURE__ */ jsxRuntime.jsx(
736
+ "span",
737
+ {
738
+ "aria-hidden": true,
739
+ className: lib.cn(
740
+ "absolute left-0 top-1 bottom-1 w-0.5 rounded-r-full",
741
+ "bg-foreground/30 group-focus-within/tree:bg-primary"
742
+ )
743
+ }
744
+ ) : null,
745
+ showIndentGuides && level > 0 ? /* @__PURE__ */ jsxRuntime.jsx(TreeIndentGuides, { level, indent: appearance.indent }) : null,
746
+ /* @__PURE__ */ jsxRuntime.jsx(TreeChevron, { isExpanded, isFolder }),
747
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx(
748
+ lucideReact.Loader2,
749
+ {
750
+ "aria-hidden": true,
751
+ strokeWidth: appearance.iconStrokeWidth,
752
+ style: { width: "var(--tree-icon-size)", height: "var(--tree-icon-size)" },
753
+ className: "shrink-0 animate-spin text-muted-foreground/70"
754
+ }
755
+ ) : renderIcon ? renderIcon(slot) : /* @__PURE__ */ jsxRuntime.jsx(TreeIcon, { isFolder, isExpanded }),
756
+ /* @__PURE__ */ jsxRuntime.jsx(
757
+ "span",
758
+ {
759
+ className: "flex min-w-0 flex-1 items-center",
760
+ style: { gap: "var(--tree-gap)" },
761
+ children: renderLabel ? renderLabel(slot) : /* @__PURE__ */ jsxRuntime.jsx(TreeLabel, { isMatchingSearch, children: getItemName(node) })
762
+ }
763
+ ),
764
+ renderActions ? /* @__PURE__ */ jsxRuntime.jsx(
765
+ "span",
766
+ {
767
+ className: "ml-auto flex shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover/row:opacity-100 group-focus-within/row:opacity-100",
768
+ onClick: (e) => e.stopPropagation(),
769
+ children: renderActions(slot)
770
+ }
771
+ ) : null
772
+ ]
773
+ }
774
+ );
775
+ if (renderContextMenu) {
776
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderContextMenu(slot, trigger) });
777
+ }
778
+ return trigger;
779
+ }
780
+ chunkWGEGR3DF_cjs.__name(TreeRow, "TreeRow");
781
+ function TreeEmpty({ children, className }) {
782
+ return /* @__PURE__ */ jsxRuntime.jsx(
783
+ "div",
784
+ {
785
+ className: lib.cn(
786
+ "flex h-full min-h-32 items-center justify-center px-4 py-6 text-sm text-muted-foreground",
787
+ className
788
+ ),
789
+ children
790
+ }
791
+ );
792
+ }
793
+ chunkWGEGR3DF_cjs.__name(TreeEmpty, "TreeEmpty");
794
+ function TreeContent({ children, className, ariaLabel }) {
795
+ const { flatRows, labels, selected, focused, matchingIds } = useTreeContext();
796
+ if (flatRows.length === 0) {
797
+ return /* @__PURE__ */ jsxRuntime.jsx(TreeEmpty, { children: labels.empty });
798
+ }
799
+ return /* @__PURE__ */ jsxRuntime.jsx(
800
+ "div",
801
+ {
802
+ role: "tree",
803
+ "aria-label": ariaLabel ?? labels.ariaLabel,
804
+ className: lib.cn("relative flex flex-col py-1", className),
805
+ children: flatRows.map((row) => {
806
+ const slot = {
807
+ node: row.node,
808
+ level: row.level,
809
+ isSelected: selected.has(row.node.id),
810
+ isExpanded: row.isExpanded,
811
+ isFocused: focused === row.node.id,
812
+ isFolder: row.isFolder,
813
+ isLoading: row.isLoading,
814
+ isMatchingSearch: matchingIds.has(row.node.id)
815
+ };
816
+ const node = children ? children(slot) : /* @__PURE__ */ jsxRuntime.jsx(TreeRow, { row });
817
+ return /* @__PURE__ */ jsxRuntime.jsx(React.Fragment, { children: node }, row.node.id);
818
+ })
819
+ }
820
+ );
821
+ }
822
+ chunkWGEGR3DF_cjs.__name(TreeContent, "TreeContent");
823
+ function useTreeLabels() {
824
+ return useTreeContext().labels;
825
+ }
826
+ chunkWGEGR3DF_cjs.__name(useTreeLabels, "useTreeLabels");
827
+ function useTreeRows() {
828
+ return useTreeContext().flatRows;
829
+ }
830
+ chunkWGEGR3DF_cjs.__name(useTreeRows, "useTreeRows");
831
+ function useTreeSelection() {
832
+ const ctx = useTreeContext();
833
+ const selectedIds = React.useMemo(() => [...ctx.selected], [ctx.selected]);
834
+ const isSelected = React.useCallback(
835
+ (id) => ctx.selected.has(id),
836
+ [ctx.selected]
837
+ );
838
+ return React.useMemo(
839
+ () => ({
840
+ selectedIds,
841
+ select: ctx.select,
842
+ setSelectedIds: ctx.setSelectedIds,
843
+ clear: ctx.clearSelection,
844
+ isSelected
845
+ }),
846
+ [selectedIds, ctx.select, ctx.setSelectedIds, ctx.clearSelection, isSelected]
847
+ );
848
+ }
849
+ chunkWGEGR3DF_cjs.__name(useTreeSelection, "useTreeSelection");
850
+ function useTreeExpansion() {
851
+ const ctx = useTreeContext();
852
+ const expandedIds = React.useMemo(() => [...ctx.expanded], [ctx.expanded]);
853
+ const isExpanded = React.useCallback(
854
+ (id) => ctx.expanded.has(id),
855
+ [ctx.expanded]
856
+ );
857
+ return React.useMemo(
858
+ () => ({
859
+ expandedIds,
860
+ expand: ctx.expand,
861
+ collapse: ctx.collapse,
862
+ toggle: ctx.toggle,
863
+ expandAll: ctx.expandAll,
864
+ collapseAll: ctx.collapseAll,
865
+ isExpanded
866
+ }),
867
+ [
868
+ expandedIds,
869
+ ctx.expand,
870
+ ctx.collapse,
871
+ ctx.toggle,
872
+ ctx.expandAll,
873
+ ctx.collapseAll,
874
+ isExpanded
875
+ ]
876
+ );
877
+ }
878
+ chunkWGEGR3DF_cjs.__name(useTreeExpansion, "useTreeExpansion");
879
+ function useTreeFocus() {
880
+ const ctx = useTreeContext();
881
+ return React.useMemo(
882
+ () => ({ focusedId: ctx.focused, setFocus: ctx.setFocus }),
883
+ [ctx.focused, ctx.setFocus]
884
+ );
885
+ }
886
+ chunkWGEGR3DF_cjs.__name(useTreeFocus, "useTreeFocus");
887
+ function useTreeSearch() {
888
+ const ctx = useTreeContext();
889
+ return React.useMemo(
890
+ () => ({
891
+ isOpen: ctx.enableSearch,
892
+ query: ctx.query,
893
+ setQuery: ctx.setQuery,
894
+ matchingIds: ctx.matchingIds,
895
+ matchCount: ctx.matchingIds.size
896
+ }),
897
+ [ctx.enableSearch, ctx.query, ctx.setQuery, ctx.matchingIds]
898
+ );
899
+ }
900
+ chunkWGEGR3DF_cjs.__name(useTreeSearch, "useTreeSearch");
901
+ function useTreeActions() {
902
+ const ctx = useTreeContext();
903
+ return React.useMemo(
904
+ () => ({
905
+ expand: ctx.expand,
906
+ collapse: ctx.collapse,
907
+ toggle: ctx.toggle,
908
+ expandAll: ctx.expandAll,
909
+ collapseAll: ctx.collapseAll,
910
+ refresh: ctx.refresh,
911
+ refreshAll: ctx.refreshAll,
912
+ activate: ctx.activate
913
+ }),
914
+ [
915
+ ctx.expand,
916
+ ctx.collapse,
917
+ ctx.toggle,
918
+ ctx.expandAll,
919
+ ctx.collapseAll,
920
+ ctx.refresh,
921
+ ctx.refreshAll,
922
+ ctx.activate
923
+ ]
924
+ );
925
+ }
926
+ chunkWGEGR3DF_cjs.__name(useTreeActions, "useTreeActions");
927
+ function TreeSearchInput({ className, showMatches = true }) {
928
+ const { labels } = useTreeContext();
929
+ const { query, setQuery, matchCount } = useTreeSearch();
930
+ return /* @__PURE__ */ jsxRuntime.jsxs(
931
+ "div",
932
+ {
933
+ className: lib.cn(
934
+ "flex items-center gap-2 rounded-md border border-border bg-background px-2",
935
+ className
936
+ ),
937
+ children: [
938
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { "aria-hidden": true, className: "size-3.5 shrink-0 text-muted-foreground" }),
939
+ /* @__PURE__ */ jsxRuntime.jsx(
940
+ "input",
941
+ {
942
+ type: "search",
943
+ value: query,
944
+ onChange: (e) => setQuery(e.target.value),
945
+ placeholder: labels.searchPlaceholder,
946
+ className: "h-7 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
947
+ }
948
+ ),
949
+ showMatches && query.trim().length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: labels.searchMatches(matchCount) }) : null,
950
+ query.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
951
+ "button",
952
+ {
953
+ type: "button",
954
+ onClick: () => setQuery(""),
955
+ "aria-label": "Clear search",
956
+ className: "shrink-0 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground",
957
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { "aria-hidden": true, className: "size-3.5" })
958
+ }
959
+ ) : null
960
+ ]
961
+ }
962
+ );
963
+ }
964
+ chunkWGEGR3DF_cjs.__name(TreeSearchInput, "TreeSearchInput");
965
+ function useTreeKeyboard({
966
+ containerRef,
967
+ rows,
968
+ focusedId,
969
+ enabled = true,
970
+ onFocus,
971
+ onSelect,
972
+ onActivate,
973
+ onExpand,
974
+ onCollapse,
975
+ onClearSelection
976
+ }) {
977
+ React.useEffect(() => {
978
+ if (!enabled) return;
979
+ const target = containerRef.current;
980
+ if (!target) return;
981
+ const handler = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
982
+ const tag = e.target?.tagName;
983
+ if (tag === "INPUT" || tag === "TEXTAREA") return;
984
+ if (e.target?.isContentEditable) return;
985
+ if (rows.length === 0) return;
986
+ const currentIndex = focusedId ? rows.findIndex((r) => r.node.id === focusedId) : -1;
987
+ const current = currentIndex >= 0 ? rows[currentIndex] : null;
988
+ switch (e.key) {
989
+ case "ArrowDown": {
990
+ e.preventDefault();
991
+ const next = rows[Math.min(currentIndex + 1, rows.length - 1)] ?? rows[0];
992
+ onFocus(next.node.id);
993
+ return;
994
+ }
995
+ case "ArrowUp": {
996
+ e.preventDefault();
997
+ const prev = rows[Math.max(currentIndex - 1, 0)] ?? rows[0];
998
+ onFocus(prev.node.id);
999
+ return;
1000
+ }
1001
+ case "Home": {
1002
+ e.preventDefault();
1003
+ onFocus(rows[0].node.id);
1004
+ return;
1005
+ }
1006
+ case "End": {
1007
+ e.preventDefault();
1008
+ onFocus(rows[rows.length - 1].node.id);
1009
+ return;
1010
+ }
1011
+ case "ArrowRight": {
1012
+ if (!current) return;
1013
+ e.preventDefault();
1014
+ if (current.isFolder && !current.isExpanded) {
1015
+ onExpand(current.node.id);
1016
+ } else if (current.isFolder && current.isExpanded) {
1017
+ const next = rows[currentIndex + 1];
1018
+ if (next) onFocus(next.node.id);
1019
+ }
1020
+ return;
1021
+ }
1022
+ case "ArrowLeft": {
1023
+ if (!current) return;
1024
+ e.preventDefault();
1025
+ if (current.isFolder && current.isExpanded) {
1026
+ onCollapse(current.node.id);
1027
+ } else if (current.parentId) {
1028
+ onFocus(current.parentId);
1029
+ }
1030
+ return;
1031
+ }
1032
+ case "Enter":
1033
+ case " ": {
1034
+ if (!current) return;
1035
+ e.preventDefault();
1036
+ onSelect(current.node.id);
1037
+ if (current.isFolder) {
1038
+ if (current.isExpanded) onCollapse(current.node.id);
1039
+ else onExpand(current.node.id);
1040
+ } else {
1041
+ onActivate(current.node.id);
1042
+ }
1043
+ return;
1044
+ }
1045
+ case "Escape": {
1046
+ e.preventDefault();
1047
+ onClearSelection();
1048
+ return;
1049
+ }
1050
+ }
1051
+ }, "handler");
1052
+ target.addEventListener("keydown", handler);
1053
+ return () => target.removeEventListener("keydown", handler);
1054
+ }, [
1055
+ containerRef,
1056
+ rows,
1057
+ focusedId,
1058
+ enabled,
1059
+ onFocus,
1060
+ onSelect,
1061
+ onActivate,
1062
+ onExpand,
1063
+ onCollapse,
1064
+ onClearSelection
1065
+ ]);
1066
+ }
1067
+ chunkWGEGR3DF_cjs.__name(useTreeKeyboard, "useTreeKeyboard");
1068
+ var FLUSH_MS = 600;
1069
+ function useTreeTypeAhead({
1070
+ rows,
1071
+ getItemName,
1072
+ containerRef,
1073
+ onMatch,
1074
+ enabled = true
1075
+ }) {
1076
+ const bufferRef = React.useRef("");
1077
+ const timerRef = React.useRef(null);
1078
+ const rowsRef = React.useRef(rows);
1079
+ const getNameRef = React.useRef(getItemName);
1080
+ const onMatchRef = React.useRef(onMatch);
1081
+ rowsRef.current = rows;
1082
+ getNameRef.current = getItemName;
1083
+ onMatchRef.current = onMatch;
1084
+ React.useEffect(() => {
1085
+ if (!enabled) return;
1086
+ const target = containerRef.current;
1087
+ if (!target) return;
1088
+ const reset = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name(() => {
1089
+ bufferRef.current = "";
1090
+ if (timerRef.current) {
1091
+ clearTimeout(timerRef.current);
1092
+ timerRef.current = null;
1093
+ }
1094
+ }, "reset");
1095
+ const handler = /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((e) => {
1096
+ const tag = e.target?.tagName;
1097
+ if (tag === "INPUT" || tag === "TEXTAREA") return;
1098
+ if (e.target?.isContentEditable) return;
1099
+ if (e.metaKey || e.ctrlKey || e.altKey) return;
1100
+ 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") {
1101
+ reset();
1102
+ return;
1103
+ }
1104
+ if (e.key.length !== 1) return;
1105
+ bufferRef.current += e.key.toLowerCase();
1106
+ if (timerRef.current) clearTimeout(timerRef.current);
1107
+ timerRef.current = setTimeout(reset, FLUSH_MS);
1108
+ const prefix = bufferRef.current;
1109
+ const hit = rowsRef.current.find(
1110
+ (row) => getNameRef.current(row.node).toLowerCase().startsWith(prefix)
1111
+ );
1112
+ if (hit) {
1113
+ e.preventDefault();
1114
+ onMatchRef.current(hit.node.id);
1115
+ }
1116
+ }, "handler");
1117
+ target.addEventListener("keydown", handler);
1118
+ return () => {
1119
+ target.removeEventListener("keydown", handler);
1120
+ reset();
1121
+ };
1122
+ }, [containerRef, enabled]);
1123
+ }
1124
+ chunkWGEGR3DF_cjs.__name(useTreeTypeAhead, "useTreeTypeAhead");
1125
+ function TreeRoot(props) {
1126
+ const {
1127
+ data,
1128
+ getItemName,
1129
+ loadChildren,
1130
+ selectionMode,
1131
+ initialExpandedIds,
1132
+ initialSelectedIds,
1133
+ indent,
1134
+ appearance,
1135
+ onSelectionChange,
1136
+ onExpansionChange,
1137
+ onActivate,
1138
+ enableSearch = false,
1139
+ enableTypeAhead = true,
1140
+ showIndentGuides = false,
1141
+ renderRow,
1142
+ renderIcon,
1143
+ renderLabel,
1144
+ renderActions,
1145
+ renderContextMenu,
1146
+ labels,
1147
+ persistKey,
1148
+ persistSelection = false,
1149
+ className,
1150
+ style
1151
+ } = props;
1152
+ return /* @__PURE__ */ jsxRuntime.jsx(
1153
+ TreeProvider,
1154
+ {
1155
+ data,
1156
+ getItemName,
1157
+ loadChildren,
1158
+ selectionMode,
1159
+ initialExpandedIds,
1160
+ initialSelectedIds,
1161
+ indent,
1162
+ appearance,
1163
+ onSelectionChange,
1164
+ onExpansionChange,
1165
+ onActivate,
1166
+ enableSearch,
1167
+ showIndentGuides,
1168
+ renderIcon,
1169
+ renderLabel,
1170
+ renderActions,
1171
+ renderContextMenu,
1172
+ labels,
1173
+ persistKey,
1174
+ persistSelection,
1175
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1176
+ TreeRootShell,
1177
+ {
1178
+ className,
1179
+ style,
1180
+ enableSearch,
1181
+ enableTypeAhead,
1182
+ renderRow
1183
+ }
1184
+ )
1185
+ }
1186
+ );
1187
+ }
1188
+ chunkWGEGR3DF_cjs.__name(TreeRoot, "TreeRoot");
1189
+ function TreeRootShell({
1190
+ className,
1191
+ style,
1192
+ enableSearch,
1193
+ enableTypeAhead,
1194
+ renderRow
1195
+ }) {
1196
+ const containerRef = React.useRef(null);
1197
+ const ctx = useTreeContext();
1198
+ useTreeKeyboard({
1199
+ containerRef,
1200
+ rows: ctx.flatRows,
1201
+ focusedId: ctx.focused,
1202
+ onFocus: ctx.setFocus,
1203
+ onSelect: ctx.select,
1204
+ onActivate: /* @__PURE__ */ chunkWGEGR3DF_cjs.__name((id) => {
1205
+ const row = ctx.flatRows.find((r) => r.node.id === id);
1206
+ if (row) ctx.activate(row.node);
1207
+ }, "onActivate"),
1208
+ onExpand: ctx.expand,
1209
+ onCollapse: ctx.collapse,
1210
+ onClearSelection: ctx.clearSelection
1211
+ });
1212
+ const onTypeAheadMatch = React.useCallback(
1213
+ (id) => {
1214
+ ctx.setFocus(id);
1215
+ const el = containerRef.current?.querySelector(
1216
+ `[data-tree-row][data-id="${CSS.escape(id)}"]`
1217
+ );
1218
+ el?.scrollIntoView({ block: "nearest" });
1219
+ },
1220
+ [ctx]
1221
+ );
1222
+ useTreeTypeAhead({
1223
+ rows: ctx.flatRows,
1224
+ getItemName: ctx.getItemName,
1225
+ containerRef,
1226
+ onMatch: onTypeAheadMatch,
1227
+ enabled: enableTypeAhead
1228
+ });
1229
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1230
+ "div",
1231
+ {
1232
+ ref: containerRef,
1233
+ tabIndex: 0,
1234
+ className: lib.cn(
1235
+ "group/tree flex h-full w-full flex-col gap-2 outline-none",
1236
+ className
1237
+ ),
1238
+ style: { ...appearanceToStyle(ctx.appearance), ...style },
1239
+ "data-tree-root": "",
1240
+ children: [
1241
+ enableSearch ? /* @__PURE__ */ jsxRuntime.jsx(TreeSearchInput, { className: "mx-2 mt-2" }) : null,
1242
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-auto px-1", children: /* @__PURE__ */ jsxRuntime.jsx(TreeContent, { children: renderRow }) })
1243
+ ]
1244
+ }
1245
+ );
1246
+ }
1247
+ chunkWGEGR3DF_cjs.__name(TreeRootShell, "TreeRootShell");
1248
+ var TreeRoot_default = TreeRoot;
1249
+
1250
+ exports.DEFAULT_TREE_APPEARANCE = DEFAULT_TREE_APPEARANCE;
1251
+ exports.DEFAULT_TREE_LABELS = DEFAULT_TREE_LABELS;
1252
+ exports.TreeChevron = TreeChevron;
1253
+ exports.TreeContent = TreeContent;
1254
+ exports.TreeEmpty = TreeEmpty;
1255
+ exports.TreeIcon = TreeIcon;
1256
+ exports.TreeIndentGuides = TreeIndentGuides;
1257
+ exports.TreeLabel = TreeLabel;
1258
+ exports.TreeProvider = TreeProvider;
1259
+ exports.TreeRoot = TreeRoot;
1260
+ exports.TreeRoot_default = TreeRoot_default;
1261
+ exports.TreeRow = TreeRow;
1262
+ exports.TreeSearchInput = TreeSearchInput;
1263
+ exports.appearanceToStyle = appearanceToStyle;
1264
+ exports.clearTreeState = clearTreeState;
1265
+ exports.createChildCache = createChildCache;
1266
+ exports.flattenTree = flattenTree;
1267
+ exports.loadTreeState = loadTreeState;
1268
+ exports.resolveAppearance = resolveAppearance;
1269
+ exports.resolveChildren = resolveChildren;
1270
+ exports.saveTreeState = saveTreeState;
1271
+ exports.useTreeActions = useTreeActions;
1272
+ exports.useTreeContext = useTreeContext;
1273
+ exports.useTreeExpansion = useTreeExpansion;
1274
+ exports.useTreeFocus = useTreeFocus;
1275
+ exports.useTreeKeyboard = useTreeKeyboard;
1276
+ exports.useTreeLabels = useTreeLabels;
1277
+ exports.useTreeRows = useTreeRows;
1278
+ exports.useTreeSearch = useTreeSearch;
1279
+ exports.useTreeSelection = useTreeSelection;
1280
+ exports.useTreeTypeAhead = useTreeTypeAhead;
1281
+ //# sourceMappingURL=chunk-MA552EWC.cjs.map
1282
+ //# sourceMappingURL=chunk-MA552EWC.cjs.map