@particle-academy/react-fancy 5.42.1 → 5.43.0
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.
- package/CHANGELOG.md +34 -0
- package/dist/{chunk-7VLRHXLQ.cjs → chunk-WN45ZE37.cjs} +171 -50
- package/dist/chunk-WN45ZE37.cjs.map +1 -0
- package/dist/{chunk-NUPQJ5SJ.js → chunk-ZVXOL5H3.js} +173 -52
- package/dist/chunk-ZVXOL5H3.js.map +1 -0
- package/dist/index.cjs +8 -8
- package/dist/index.js +2 -2
- package/dist/tree-nav.cjs +4 -3
- package/dist/tree-nav.d.cts +14 -7
- package/dist/tree-nav.d.ts +14 -7
- package/dist/tree-nav.js +2 -1
- package/package.json +1 -1
- package/dist/chunk-7VLRHXLQ.cjs.map +0 -1
- package/dist/chunk-NUPQJ5SJ.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.43.0] - 2026-10-17
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **A click in a big `TreeNav` no longer re-renders the whole tree.** Selection,
|
|
19
|
+
expansion and drag state lived in one context that every row read, so one click
|
|
20
|
+
re-rendered every row. Measured in a real browser on Genie 2's file browser
|
|
21
|
+
shape (5,000 nodes, 6 levels, all expanded): **175–267 ms per click before,
|
|
22
|
+
17–20 ms after**, with no change to your code. Rows now subscribe to their own
|
|
23
|
+
slice (am I selected / expanded / the drop target?), handlers are stable, and
|
|
24
|
+
expansion is a Set lookup instead of an array scan per row.
|
|
25
|
+
|
|
26
|
+
`useTreeNav()` is unchanged and still live: a component that reads it gets
|
|
27
|
+
current `selectedId` / `expandedIds` exactly as before.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`<TreeNav virtualize height={600}>` renders only the rows in view.** The
|
|
32
|
+
visible rows (expanded folders only) are flattened and windowed by
|
|
33
|
+
`VirtualList`; selection, expansion, context menus and drag-and-drop behave as
|
|
34
|
+
in the nested tree. Same tree, all expanded: **33 rows in the DOM instead of
|
|
35
|
+
5,000, and 30 ms to mount instead of 614 ms.** `rowHeight` seeds the row
|
|
36
|
+
estimate (default 26). The nested DOM wrapper
|
|
37
|
+
`data-react-fancy-tree-node-children` does not exist in this mode — rows are
|
|
38
|
+
flat, indented by `paddingLeft`.
|
|
39
|
+
|
|
40
|
+
- **`TreeNav` has tests.** It had none, which is why a refactor this size starts
|
|
41
|
+
with them: selection, toggling, lazy mounting, `useTreeNav`, virtual rows and
|
|
42
|
+
drag-and-drop in both modes.
|
|
43
|
+
|
|
44
|
+
**What you must DO: nothing.** `virtualize` is off by default and the nested tree
|
|
45
|
+
renders the same DOM as before. Collapsed folders were already cheap — their
|
|
46
|
+
children are not mounted until expanded — and still are.
|
|
47
|
+
|
|
14
48
|
## [5.42.1] - 2026-10-17
|
|
15
49
|
|
|
16
50
|
### Fixed
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var chunkIR6JSQ4O_cjs = require('./chunk-IR6JSQ4O.cjs');
|
|
3
4
|
var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
4
5
|
var react = require('react');
|
|
5
6
|
var jsxRuntime = require('react/jsx-runtime');
|
|
@@ -12,6 +13,35 @@ function useTreeNav() {
|
|
|
12
13
|
}
|
|
13
14
|
return ctx;
|
|
14
15
|
}
|
|
16
|
+
function createTreeStore(initial) {
|
|
17
|
+
let state = initial;
|
|
18
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
19
|
+
return {
|
|
20
|
+
get: () => state,
|
|
21
|
+
set(next) {
|
|
22
|
+
state = next;
|
|
23
|
+
for (const listener of listeners) listener();
|
|
24
|
+
},
|
|
25
|
+
subscribe(listener) {
|
|
26
|
+
listeners.add(listener);
|
|
27
|
+
return () => listeners.delete(listener);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
var TreeInternalsContext = react.createContext(null);
|
|
32
|
+
function useTreeInternals() {
|
|
33
|
+
const internals = react.useContext(TreeInternalsContext);
|
|
34
|
+
if (!internals) throw new Error("TreeNav.Node must be used within a <TreeNav> component");
|
|
35
|
+
return internals;
|
|
36
|
+
}
|
|
37
|
+
function useTreeSlice(select) {
|
|
38
|
+
const { store } = useTreeInternals();
|
|
39
|
+
return react.useSyncExternalStore(
|
|
40
|
+
store.subscribe,
|
|
41
|
+
() => select(store.get()),
|
|
42
|
+
() => select(store.get())
|
|
43
|
+
);
|
|
44
|
+
}
|
|
15
45
|
var EXT_COLORS = {
|
|
16
46
|
ts: "#3178c6",
|
|
17
47
|
tsx: "#3178c6",
|
|
@@ -89,32 +119,33 @@ function computeDropPosition(e, isFolder) {
|
|
|
89
119
|
if (offsetY > third * 2) return "after";
|
|
90
120
|
return isFolder ? "inside" : "after";
|
|
91
121
|
}
|
|
92
|
-
function
|
|
122
|
+
function TreeNodeImpl({ node, depth }) {
|
|
93
123
|
const {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
expandedIds,
|
|
124
|
+
store,
|
|
125
|
+
nodes,
|
|
126
|
+
select,
|
|
98
127
|
toggle,
|
|
128
|
+
expandNode,
|
|
129
|
+
setDragState,
|
|
130
|
+
contextMenu,
|
|
131
|
+
moveNode,
|
|
132
|
+
externalDrop,
|
|
99
133
|
indentSize,
|
|
100
134
|
showIcons,
|
|
101
135
|
draggable,
|
|
102
136
|
dragCursor,
|
|
103
|
-
dragState,
|
|
104
|
-
setDragState,
|
|
105
|
-
onNodeMove,
|
|
106
137
|
acceptExternalDrops,
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
expandNode
|
|
110
|
-
} = useTreeNav();
|
|
138
|
+
flat
|
|
139
|
+
} = useTreeInternals();
|
|
111
140
|
const isFolder = node.type === "folder" || node.children && node.children.length > 0;
|
|
112
|
-
const isExpanded =
|
|
113
|
-
const isSelected = selectedId === node.id;
|
|
141
|
+
const isExpanded = useTreeSlice((s) => s.expanded.has(node.id));
|
|
142
|
+
const isSelected = useTreeSlice((s) => s.selectedId === node.id);
|
|
143
|
+
const isDragging = useTreeSlice((s) => s.dragState.draggedNodeId === node.id);
|
|
144
|
+
const dropPosition = useTreeSlice(
|
|
145
|
+
(s) => s.dragState.dropTargetId === node.id ? s.dragState.dropPosition : null
|
|
146
|
+
);
|
|
147
|
+
const isDropTarget = dropPosition !== null;
|
|
114
148
|
const paddingLeft = depth * indentSize + 4;
|
|
115
|
-
const isDragging = dragState.draggedNodeId === node.id;
|
|
116
|
-
const isDropTarget = dragState.dropTargetId === node.id;
|
|
117
|
-
const dropPosition = isDropTarget ? dragState.dropPosition : null;
|
|
118
149
|
const autoExpandTimer = react.useRef(null);
|
|
119
150
|
const clearAutoExpand = react.useCallback(() => {
|
|
120
151
|
if (autoExpandTimer.current) {
|
|
@@ -128,12 +159,12 @@ function TreeNode({ node, depth }) {
|
|
|
128
159
|
if (isFolder) {
|
|
129
160
|
toggle(node.id);
|
|
130
161
|
}
|
|
131
|
-
|
|
162
|
+
select(node);
|
|
132
163
|
};
|
|
133
164
|
const handleContextMenu = (e) => {
|
|
134
|
-
if (
|
|
165
|
+
if (contextMenu) {
|
|
135
166
|
e.preventDefault();
|
|
136
|
-
|
|
167
|
+
contextMenu(e, node);
|
|
137
168
|
}
|
|
138
169
|
};
|
|
139
170
|
const handleDragStart = react.useCallback((e) => {
|
|
@@ -147,8 +178,9 @@ function TreeNode({ node, depth }) {
|
|
|
147
178
|
clearAutoExpand();
|
|
148
179
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
149
180
|
}, [clearAutoExpand, setDragState]);
|
|
150
|
-
const isExternalDrag = !dragState.draggedNodeId;
|
|
151
181
|
const handleDragOver = react.useCallback((e) => {
|
|
182
|
+
const { dragState } = store.get();
|
|
183
|
+
const isExternalDrag = !dragState.draggedNodeId;
|
|
152
184
|
if (isExternalDrag) {
|
|
153
185
|
if (!acceptExternalDrops) return;
|
|
154
186
|
} else {
|
|
@@ -160,7 +192,7 @@ function TreeNode({ node, depth }) {
|
|
|
160
192
|
e.stopPropagation();
|
|
161
193
|
e.dataTransfer.dropEffect = isExternalDrag ? "copy" : "move";
|
|
162
194
|
const position = computeDropPosition(e, !!isFolder);
|
|
163
|
-
if (isFolder && !
|
|
195
|
+
if (isFolder && !store.get().expanded.has(node.id) && position === "inside") {
|
|
164
196
|
if (!autoExpandTimer.current) {
|
|
165
197
|
autoExpandTimer.current = setTimeout(() => {
|
|
166
198
|
expandNode(node.id);
|
|
@@ -173,30 +205,32 @@ function TreeNode({ node, depth }) {
|
|
|
173
205
|
if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {
|
|
174
206
|
setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });
|
|
175
207
|
}
|
|
176
|
-
}, [
|
|
208
|
+
}, [store, acceptExternalDrops, node.id, isFolder, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
177
209
|
const handleDragLeave = react.useCallback((e) => {
|
|
178
210
|
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
179
211
|
clearAutoExpand();
|
|
212
|
+
const { dragState } = store.get();
|
|
180
213
|
if (dragState.dropTargetId === node.id) {
|
|
181
214
|
setDragState({ ...dragState, dropTargetId: null, dropPosition: null });
|
|
182
215
|
}
|
|
183
216
|
}
|
|
184
|
-
}, [
|
|
217
|
+
}, [store, node.id, setDragState, clearAutoExpand]);
|
|
185
218
|
const handleDrop = react.useCallback((e) => {
|
|
186
219
|
e.preventDefault();
|
|
187
220
|
e.stopPropagation();
|
|
188
221
|
clearAutoExpand();
|
|
222
|
+
const { dragState } = store.get();
|
|
189
223
|
const sourceId = dragState.draggedNodeId;
|
|
190
224
|
const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);
|
|
191
225
|
if (sourceId) {
|
|
192
226
|
if (sourceId === node.id) return;
|
|
193
227
|
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
194
|
-
|
|
228
|
+
moveNode?.(sourceId, node.id, position);
|
|
195
229
|
} else if (acceptExternalDrops) {
|
|
196
|
-
|
|
230
|
+
externalDrop?.(e, node, position);
|
|
197
231
|
}
|
|
198
232
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
199
|
-
}, [
|
|
233
|
+
}, [store, node, isFolder, nodes, moveNode, acceptExternalDrops, externalDrop, setDragState, clearAutoExpand]);
|
|
200
234
|
const canDrag = draggable && !node.disabled;
|
|
201
235
|
const dropEnabled = draggable || acceptExternalDrops;
|
|
202
236
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -255,11 +289,12 @@ function TreeNode({ node, depth }) {
|
|
|
255
289
|
style: { marginLeft: paddingLeft }
|
|
256
290
|
}
|
|
257
291
|
),
|
|
258
|
-
isFolder && isExpanded && node.children && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-react-fancy-tree-node-children": "", children: node.children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node: child, depth: depth + 1 }, child.id)) })
|
|
292
|
+
!flat && isFolder && isExpanded && node.children && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-react-fancy-tree-node-children": "", children: node.children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node: child, depth: depth + 1 }, child.id)) })
|
|
259
293
|
]
|
|
260
294
|
}
|
|
261
295
|
);
|
|
262
296
|
}
|
|
297
|
+
var TreeNode = react.memo(TreeNodeImpl);
|
|
263
298
|
TreeNode.displayName = "TreeNode";
|
|
264
299
|
function collectFolderIds(nodes) {
|
|
265
300
|
const ids = [];
|
|
@@ -276,6 +311,18 @@ var EMPTY_DRAG_STATE = {
|
|
|
276
311
|
dropTargetId: null,
|
|
277
312
|
dropPosition: null
|
|
278
313
|
};
|
|
314
|
+
function flattenVisible(nodes, expanded) {
|
|
315
|
+
const rows = [];
|
|
316
|
+
const walk = (list, depth) => {
|
|
317
|
+
for (const node of list) {
|
|
318
|
+
rows.push({ id: node.id, node, depth });
|
|
319
|
+
const isFolder = node.type === "folder" || node.children && node.children.length > 0;
|
|
320
|
+
if (isFolder && node.children && expanded.has(node.id)) walk(node.children, depth + 1);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
walk(nodes, 0);
|
|
324
|
+
return rows;
|
|
325
|
+
}
|
|
279
326
|
function TreeNavRoot({
|
|
280
327
|
nodes,
|
|
281
328
|
selectedId,
|
|
@@ -292,6 +339,9 @@ function TreeNavRoot({
|
|
|
292
339
|
indentSize = 16,
|
|
293
340
|
showIcons = true,
|
|
294
341
|
dragCursor = "none",
|
|
342
|
+
virtualize = false,
|
|
343
|
+
height = 400,
|
|
344
|
+
rowHeight = 26,
|
|
295
345
|
className
|
|
296
346
|
}) {
|
|
297
347
|
const [internalExpanded, setInternalExpanded] = react.useState(() => {
|
|
@@ -301,31 +351,86 @@ function TreeNavRoot({
|
|
|
301
351
|
});
|
|
302
352
|
const isControlled = controlledExpanded !== void 0;
|
|
303
353
|
const expandedIds = isControlled ? controlledExpanded : internalExpanded;
|
|
354
|
+
const expanded = react.useMemo(() => new Set(expandedIds), [expandedIds]);
|
|
355
|
+
const [dragState, setDragState] = react.useState(EMPTY_DRAG_STATE);
|
|
356
|
+
const latest = react.useRef({ expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop });
|
|
357
|
+
latest.current = { expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop };
|
|
358
|
+
const setExpanded = react.useCallback((next) => {
|
|
359
|
+
if (!latest.current.isControlled) setInternalExpanded(next);
|
|
360
|
+
latest.current.onExpandedChange?.(next);
|
|
361
|
+
}, []);
|
|
304
362
|
const toggle = react.useCallback(
|
|
305
363
|
(id) => {
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
setInternalExpanded(next);
|
|
309
|
-
}
|
|
310
|
-
onExpandedChange?.(next);
|
|
364
|
+
const current = latest.current.expandedIds;
|
|
365
|
+
setExpanded(current.includes(id) ? current.filter((v) => v !== id) : [...current, id]);
|
|
311
366
|
},
|
|
312
|
-
[
|
|
367
|
+
[setExpanded]
|
|
313
368
|
);
|
|
314
369
|
const expandNode = react.useCallback(
|
|
315
370
|
(id) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if (!isControlled) {
|
|
319
|
-
setInternalExpanded(next);
|
|
320
|
-
}
|
|
321
|
-
onExpandedChange?.(next);
|
|
371
|
+
const current = latest.current.expandedIds;
|
|
372
|
+
if (!current.includes(id)) setExpanded([...current, id]);
|
|
322
373
|
},
|
|
323
|
-
[
|
|
374
|
+
[setExpanded]
|
|
375
|
+
);
|
|
376
|
+
const select = react.useCallback((node) => latest.current.onSelect?.(node.id, node), []);
|
|
377
|
+
const contextMenu = react.useCallback(
|
|
378
|
+
(e, node) => latest.current.onNodeContextMenu?.(e, node),
|
|
379
|
+
[]
|
|
380
|
+
);
|
|
381
|
+
const moveNode = react.useCallback(
|
|
382
|
+
(sourceId, targetId, position) => latest.current.onNodeMove?.(sourceId, targetId, position),
|
|
383
|
+
[]
|
|
384
|
+
);
|
|
385
|
+
const externalDrop = react.useCallback(
|
|
386
|
+
(event, target, position) => latest.current.onExternalDrop?.(event, target, position),
|
|
387
|
+
[]
|
|
388
|
+
);
|
|
389
|
+
const [store] = react.useState(() => createTreeStore({ selectedId, expanded, dragState }));
|
|
390
|
+
react.useLayoutEffect(() => {
|
|
391
|
+
const current = store.get();
|
|
392
|
+
if (current.selectedId !== selectedId || current.expanded !== expanded || current.dragState !== dragState) {
|
|
393
|
+
store.set({ selectedId, expanded, dragState });
|
|
394
|
+
}
|
|
395
|
+
}, [store, selectedId, expanded, dragState]);
|
|
396
|
+
const hasContextMenu = onNodeContextMenu !== void 0;
|
|
397
|
+
const internals = react.useMemo(
|
|
398
|
+
() => ({
|
|
399
|
+
store,
|
|
400
|
+
nodes,
|
|
401
|
+
select,
|
|
402
|
+
toggle,
|
|
403
|
+
expandNode,
|
|
404
|
+
setDragState,
|
|
405
|
+
// Present only when the host gave one: a row then claims the right-click.
|
|
406
|
+
contextMenu: hasContextMenu ? contextMenu : void 0,
|
|
407
|
+
moveNode,
|
|
408
|
+
externalDrop,
|
|
409
|
+
indentSize,
|
|
410
|
+
showIcons,
|
|
411
|
+
draggable,
|
|
412
|
+
dragCursor,
|
|
413
|
+
acceptExternalDrops,
|
|
414
|
+
flat: virtualize
|
|
415
|
+
}),
|
|
416
|
+
[
|
|
417
|
+
store,
|
|
418
|
+
nodes,
|
|
419
|
+
select,
|
|
420
|
+
toggle,
|
|
421
|
+
expandNode,
|
|
422
|
+
hasContextMenu,
|
|
423
|
+
contextMenu,
|
|
424
|
+
moveNode,
|
|
425
|
+
externalDrop,
|
|
426
|
+
indentSize,
|
|
427
|
+
showIcons,
|
|
428
|
+
draggable,
|
|
429
|
+
dragCursor,
|
|
430
|
+
acceptExternalDrops,
|
|
431
|
+
virtualize
|
|
432
|
+
]
|
|
324
433
|
);
|
|
325
|
-
const [dragState, setDragState] = react.useState(EMPTY_DRAG_STATE);
|
|
326
|
-
const handleDragEnd = react.useCallback(() => {
|
|
327
|
-
setDragState(EMPTY_DRAG_STATE);
|
|
328
|
-
}, []);
|
|
329
434
|
const ctx = react.useMemo(
|
|
330
435
|
() => ({
|
|
331
436
|
selectedId,
|
|
@@ -363,16 +468,32 @@ function TreeNavRoot({
|
|
|
363
468
|
expandNode
|
|
364
469
|
]
|
|
365
470
|
);
|
|
471
|
+
const rows = react.useMemo(
|
|
472
|
+
() => virtualize ? flattenVisible(nodes, expanded) : [],
|
|
473
|
+
[virtualize, nodes, expanded]
|
|
474
|
+
);
|
|
475
|
+
const handleDragEnd = react.useCallback(() => {
|
|
476
|
+
setDragState(EMPTY_DRAG_STATE);
|
|
477
|
+
}, []);
|
|
366
478
|
const dropEnabled = draggable || acceptExternalDrops;
|
|
367
|
-
return /* @__PURE__ */ jsxRuntime.jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
479
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(TreeInternalsContext.Provider, { value: internals, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
368
480
|
"nav",
|
|
369
481
|
{
|
|
370
482
|
"data-react-fancy-tree-nav": "",
|
|
483
|
+
"data-react-fancy-tree-nav-virtual": virtualize ? "" : void 0,
|
|
371
484
|
className: chunkZU5Q77OT_cjs.cn("flex flex-col gap-0.5 py-1 text-sm", className),
|
|
372
485
|
onDragEnd: dropEnabled ? handleDragEnd : void 0,
|
|
373
|
-
children:
|
|
486
|
+
children: virtualize ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
487
|
+
chunkIR6JSQ4O_cjs.VirtualList,
|
|
488
|
+
{
|
|
489
|
+
items: rows,
|
|
490
|
+
height,
|
|
491
|
+
estimateRowHeight: rowHeight,
|
|
492
|
+
renderRow: (row) => /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node: row.node, depth: row.depth })
|
|
493
|
+
}
|
|
494
|
+
) : nodes.map((node) => /* @__PURE__ */ jsxRuntime.jsx(TreeNode, { node, depth: 0 }, node.id))
|
|
374
495
|
}
|
|
375
|
-
) });
|
|
496
|
+
) }) });
|
|
376
497
|
}
|
|
377
498
|
TreeNavRoot.displayName = "TreeNav";
|
|
378
499
|
var TreeNav = Object.assign(TreeNavRoot, {
|
|
@@ -381,5 +502,5 @@ var TreeNav = Object.assign(TreeNavRoot, {
|
|
|
381
502
|
|
|
382
503
|
exports.TreeNav = TreeNav;
|
|
383
504
|
exports.useTreeNav = useTreeNav;
|
|
384
|
-
//# sourceMappingURL=chunk-
|
|
385
|
-
//# sourceMappingURL=chunk-
|
|
505
|
+
//# sourceMappingURL=chunk-WN45ZE37.cjs.map
|
|
506
|
+
//# sourceMappingURL=chunk-WN45ZE37.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/TreeNav/TreeNav.context.ts","../src/components/TreeNav/tree-store.ts","../src/components/TreeNav/TreeNode.tsx","../src/components/TreeNav/TreeNav.tsx"],"names":["createContext","useContext","useSyncExternalStore","jsxs","jsx","cn","useRef","useCallback","useEffect","memo","useState","useMemo","useLayoutEffect","VirtualList"],"mappings":"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","file":"chunk-WN45ZE37.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { TreeNavContextValue } from \"./TreeNav.types\";\n\nexport const TreeNavContext = createContext<TreeNavContextValue | null>(null);\n\nexport function useTreeNav(): TreeNavContextValue {\n const ctx = useContext(TreeNavContext);\n if (!ctx) {\n throw new Error(\"useTreeNav must be used within a <TreeNav> component\");\n }\n return ctx;\n}\n","import { createContext, useContext, useSyncExternalStore } from \"react\";\nimport type { DragState, DropPosition, TreeNodeData } from \"./TreeNav.types\";\n\n/** What changes as someone clicks, toggles and drags — the state rows subscribe to. */\nexport interface TreeState {\n selectedId?: string;\n expanded: ReadonlySet<string>;\n dragState: DragState;\n}\n\nexport interface TreeStore {\n get(): TreeState;\n set(next: TreeState): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport function createTreeStore(initial: TreeState): TreeStore {\n let state = initial;\n const listeners = new Set<() => void>();\n return {\n get: () => state,\n set(next) {\n state = next;\n for (const listener of listeners) listener();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\n/**\n * Everything a row needs that does NOT change on a click. Handlers are stable\n * (they read the latest props and state when called), so this value only changes\n * when the tree's data or configuration does.\n *\n * Kept apart from the public `TreeNavContext` on purpose: that one still carries\n * live `selectedId` / `expandedIds` for `useTreeNav()` callers, and any component\n * reading it re-renders on every click. Rows no longer read it — which is the\n * whole fix: a click used to re-render every row in the tree.\n */\nexport interface TreeInternals {\n store: TreeStore;\n nodes: TreeNodeData[];\n select: (node: TreeNodeData) => void;\n toggle: (id: string) => void;\n expandNode: (id: string) => void;\n setDragState: (state: DragState) => void;\n contextMenu?: (e: React.MouseEvent, node: TreeNodeData) => void;\n moveNode?: (sourceId: string, targetId: string, position: DropPosition) => void;\n externalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;\n indentSize: number;\n showIcons: boolean;\n draggable: boolean;\n dragCursor: \"grab\" | \"none\";\n acceptExternalDrops: boolean;\n /** Rows are rendered flat by a virtual list, so a row never renders its children. */\n flat: boolean;\n}\n\nexport const TreeInternalsContext = createContext<TreeInternals | null>(null);\n\nexport function useTreeInternals(): TreeInternals {\n const internals = useContext(TreeInternalsContext);\n if (!internals) throw new Error(\"TreeNav.Node must be used within a <TreeNav> component\");\n return internals;\n}\n\n/** Subscribe to ONE derived value; the row re-renders only when that value changes. */\nexport function useTreeSlice<T>(select: (state: TreeState) => T): T {\n const { store } = useTreeInternals();\n return useSyncExternalStore(\n store.subscribe,\n () => select(store.get()),\n () => select(store.get()),\n );\n}\n","import { memo, useRef, useEffect, useCallback } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useTreeInternals, useTreeSlice } from \"./tree-store\";\nimport type { TreeNodeProps, TreeNodeData, DropPosition } from \"./TreeNav.types\";\n\nconst EXT_COLORS: Record<string, string> = {\n ts: \"#3178c6\",\n tsx: \"#3178c6\",\n js: \"#f7df1e\",\n jsx: \"#f7df1e\",\n php: \"#777bb4\",\n html: \"#e34c26\",\n htm: \"#e34c26\",\n css: \"#264de4\",\n json: \"#a1a1aa\",\n md: \"#71717a\",\n yaml: \"#cb171e\",\n yml: \"#cb171e\",\n};\n\nfunction FileIcon({ ext }: { ext?: string }) {\n const color = (ext && EXT_COLORS[ext.toLowerCase()]) || \"#71717a\";\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z\" stroke={color} strokeWidth=\"1.2\" />\n <path d=\"M9 1v4h4\" stroke={color} strokeWidth=\"1.2\" />\n </svg>\n );\n}\n\nfunction FolderIcon({ open }: { open: boolean }) {\n if (open) {\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z\" fill=\"#fbbf24\" />\n <path d=\"M1 6h13l-1.5 7.5H2.5L1 6z\" fill=\"#fbbf24\" opacity=\"0.7\" />\n </svg>\n );\n }\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z\" fill=\"#fbbf24\" />\n </svg>\n );\n}\n\nfunction ChevronIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"shrink-0 transition-transform duration-150\", open && \"rotate-90\")}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction isDescendantOf(\n nodes: TreeNodeData[],\n ancestorId: string,\n targetId: string,\n): boolean {\n function findNode(haystack: TreeNodeData[], id: string): TreeNodeData | undefined {\n for (const n of haystack) {\n if (n.id === id) return n;\n if (n.children) {\n const found = findNode(n.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n function hasDescendant(node: TreeNodeData, id: string): boolean {\n if (!node.children) return false;\n for (const child of node.children) {\n if (child.id === id) return true;\n if (hasDescendant(child, id)) return true;\n }\n return false;\n }\n\n const ancestor = findNode(nodes, ancestorId);\n return ancestor ? hasDescendant(ancestor, targetId) : false;\n}\n\nfunction computeDropPosition(\n e: React.DragEvent,\n isFolder: boolean,\n): DropPosition {\n const rect = e.currentTarget.getBoundingClientRect();\n const offsetY = e.clientY - rect.top;\n const third = rect.height / 3;\n\n if (offsetY < third) return \"before\";\n if (offsetY > third * 2) return \"after\";\n return isFolder ? \"inside\" : \"after\";\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n\n/**\n * One row (and, when nested, its expanded children).\n *\n * Reads what changes on a click — selection, expansion, drag — as per-row slices\n * of a store, so a click re-renders only the rows whose slice changed. It used to\n * read one context holding all of it, so every click re-rendered every row:\n * 175-267 ms per click on a 5,000-node tree. `memo` keeps a parent's re-render\n * from reaching rows whose `node` and `depth` did not change.\n */\nfunction TreeNodeImpl({ node, depth }: TreeNodeProps) {\n const {\n store, nodes, select, toggle, expandNode, setDragState, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, flat,\n } = useTreeInternals();\n\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n const isExpanded = useTreeSlice((s) => s.expanded.has(node.id));\n const isSelected = useTreeSlice((s) => s.selectedId === node.id);\n const isDragging = useTreeSlice((s) => s.dragState.draggedNodeId === node.id);\n const dropPosition = useTreeSlice((s) =>\n s.dragState.dropTargetId === node.id ? s.dragState.dropPosition : null,\n );\n const isDropTarget = dropPosition !== null;\n const paddingLeft = depth * indentSize + 4;\n\n // Auto-expand timer for folders during drag\n const autoExpandTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearAutoExpand = useCallback(() => {\n if (autoExpandTimer.current) {\n clearTimeout(autoExpandTimer.current);\n autoExpandTimer.current = null;\n }\n }, []);\n\n useEffect(() => clearAutoExpand, [clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Click handlers\n // ---------------------------------------------------------------------------\n\n const handleClick = () => {\n if (node.disabled) return;\n if (isFolder) {\n toggle(node.id);\n }\n select(node);\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n if (contextMenu) {\n e.preventDefault();\n contextMenu(e, node);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Drag source handlers\n // ---------------------------------------------------------------------------\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n e.dataTransfer.setData(\"text/plain\", node.id);\n e.dataTransfer.effectAllowed = \"move\";\n // Set state async so the browser captures the element for the drag ghost first\n requestAnimationFrame(() => {\n setDragState({ draggedNodeId: node.id, dropTargetId: null, dropPosition: null });\n });\n }, [node.id, setDragState]);\n\n const handleDragEnd = useCallback(() => {\n clearAutoExpand();\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [clearAutoExpand, setDragState]);\n\n // ---------------------------------------------------------------------------\n // Drop target handlers (on outer div). Drag state is read from the store when\n // the event fires, so a row does not re-render as a drag moves elsewhere.\n // ---------------------------------------------------------------------------\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n const { dragState } = store.get();\n const isExternalDrag = !dragState.draggedNodeId;\n if (isExternalDrag) {\n // External drop accepted only when host opted in.\n if (!acceptExternalDrops) return;\n } else {\n const sourceId = dragState.draggedNodeId!;\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n e.dataTransfer.dropEffect = isExternalDrag ? \"copy\" : \"move\";\n\n const position = computeDropPosition(e, !!isFolder);\n\n // Auto-expand collapsed folders when hovering \"inside\"\n if (isFolder && !store.get().expanded.has(node.id) && position === \"inside\") {\n if (!autoExpandTimer.current) {\n autoExpandTimer.current = setTimeout(() => {\n expandNode(node.id);\n autoExpandTimer.current = null;\n }, 500);\n }\n } else {\n clearAutoExpand();\n }\n\n if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {\n setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });\n }\n }, [store, acceptExternalDrops, node.id, isFolder, nodes, setDragState, expandNode, clearAutoExpand]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n clearAutoExpand();\n const { dragState } = store.get();\n if (dragState.dropTargetId === node.id) {\n setDragState({ ...dragState, dropTargetId: null, dropPosition: null });\n }\n }\n }, [store, node.id, setDragState, clearAutoExpand]);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n clearAutoExpand();\n\n const { dragState } = store.get();\n const sourceId = dragState.draggedNodeId;\n const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);\n\n if (sourceId) {\n // Internal node move.\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n moveNode?.(sourceId, node.id, position);\n } else if (acceptExternalDrops) {\n // External drop — files, items from other components, etc.\n externalDrop?.(e, node, position);\n }\n\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [store, node, isFolder, nodes, moveNode, acceptExternalDrops, externalDrop, setDragState, clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n const canDrag = draggable && !node.disabled;\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <div\n data-react-fancy-tree-node=\"\"\n onDragOver={dropEnabled ? handleDragOver : undefined}\n onDragLeave={dropEnabled ? handleDragLeave : undefined}\n onDrop={dropEnabled ? handleDrop : undefined}\n >\n {/* Before drop indicator */}\n {isDropTarget && dropPosition === \"before\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"before\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n <button\n type=\"button\"\n draggable={canDrag}\n onDragStart={canDrag ? handleDragStart : undefined}\n onDragEnd={canDrag ? handleDragEnd : undefined}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n disabled={node.disabled}\n className={cn(\n \"flex w-full items-center gap-1 rounded-md py-0.5 text-left text-[13px] transition-colors\",\n isSelected\n ? \"bg-blue-500/15 text-blue-600 dark:text-blue-400\"\n : \"text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800\",\n node.disabled && \"pointer-events-none opacity-40\",\n isDragging && \"opacity-50\",\n // Grab affordance. By default (`dragCursor=\"none\"`) the row keeps its\n // normal click cursor on hover — no open-hand \"move me\" cursor before\n // any drag — and only shows the closed-hand `grabbing` cursor while a\n // drag is actually underway (mousedown/drag). `dragCursor=\"grab\"` opts\n // back into the always-on grab cursor for whole-row drag handles (#14).\n canDrag &&\n (dragCursor === \"grab\" ? \"cursor-grab active:cursor-grabbing\" : \"active:cursor-grabbing\"),\n isDropTarget && dropPosition === \"inside\" && \"bg-blue-500/10 ring-1 ring-blue-500/30 ring-inset\",\n )}\n style={{ paddingLeft }}\n >\n {isFolder && <ChevronIcon open={isExpanded} />}\n {!isFolder && <span className=\"w-3.5 shrink-0\" />}\n {showIcons && (\n node.icon ?? (isFolder\n ? <FolderIcon open={isExpanded} />\n : <FileIcon ext={node.ext ?? node.label.split(\".\").pop()} />)\n )}\n <span className=\"truncate\">{node.label}</span>\n </button>\n\n {/* After drop indicator */}\n {isDropTarget && dropPosition === \"after\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"after\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n {/* A virtual list renders rows flat, so a row never renders its own children there. */}\n {!flat && isFolder && isExpanded && node.children && (\n <div data-react-fancy-tree-node-children=\"\">\n {node.children.map((child) => (\n <TreeNode key={child.id} node={child} depth={depth + 1} />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport const TreeNode = memo(TreeNodeImpl);\n\nTreeNode.displayName = \"TreeNode\";\n","import { useMemo, useCallback, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { VirtualList } from \"../VirtualList\";\nimport { TreeNavContext } from \"./TreeNav.context\";\nimport { TreeNode } from \"./TreeNode\";\nimport { createTreeStore, TreeInternalsContext, type TreeInternals } from \"./tree-store\";\nimport type { TreeNavProps, TreeNodeData, DragState } from \"./TreeNav.types\";\n\nfunction collectFolderIds(nodes: TreeNodeData[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (node.children && node.children.length > 0) {\n ids.push(node.id);\n ids.push(...collectFolderIds(node.children));\n }\n }\n return ids;\n}\n\nconst EMPTY_DRAG_STATE: DragState = {\n draggedNodeId: null,\n dropTargetId: null,\n dropPosition: null,\n};\n\ninterface FlatRow {\n id: string;\n node: TreeNodeData;\n depth: number;\n}\n\n/** The rows currently visible — every node, descending only into expanded folders. */\nfunction flattenVisible(nodes: TreeNodeData[], expanded: ReadonlySet<string>): FlatRow[] {\n const rows: FlatRow[] = [];\n const walk = (list: TreeNodeData[], depth: number) => {\n for (const node of list) {\n rows.push({ id: node.id, node, depth });\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n if (isFolder && node.children && expanded.has(node.id)) walk(node.children, depth + 1);\n }\n };\n walk(nodes, 0);\n return rows;\n}\n\nfunction TreeNavRoot({\n nodes,\n selectedId,\n onSelect,\n onNodeContextMenu,\n draggable = false,\n onNodeMove,\n acceptExternalDrops = false,\n onExternalDrop,\n expandedIds: controlledExpanded,\n defaultExpandedIds,\n onExpandedChange,\n defaultExpandAll = false,\n indentSize = 16,\n showIcons = true,\n dragCursor = \"none\",\n virtualize = false,\n height = 400,\n rowHeight = 26,\n className,\n}: TreeNavProps) {\n const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {\n if (defaultExpandedIds) return defaultExpandedIds;\n if (defaultExpandAll) return collectFolderIds(nodes);\n return [];\n });\n\n const isControlled = controlledExpanded !== undefined;\n const expandedIds = isControlled ? controlledExpanded : internalExpanded;\n // One Set per change, so a row's \"am I expanded?\" is a lookup, not a scan.\n const expanded = useMemo(() => new Set(expandedIds), [expandedIds]);\n\n const [dragState, setDragState] = useState<DragState>(EMPTY_DRAG_STATE);\n\n /*\n * The latest props and state, for handlers that must stay STABLE. A handler\n * whose identity changes on every click would change the context rows read,\n * and re-render every row — the exact cost this component is avoiding.\n */\n const latest = useRef({ expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop });\n latest.current = { expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop };\n\n const setExpanded = useCallback((next: string[]) => {\n if (!latest.current.isControlled) setInternalExpanded(next);\n latest.current.onExpandedChange?.(next);\n }, []);\n\n const toggle = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n setExpanded(current.includes(id) ? current.filter((v) => v !== id) : [...current, id]);\n },\n [setExpanded],\n );\n\n const expandNode = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n if (!current.includes(id)) setExpanded([...current, id]);\n },\n [setExpanded],\n );\n\n const select = useCallback((node: TreeNodeData) => latest.current.onSelect?.(node.id, node), []);\n const contextMenu = useCallback(\n (e: React.MouseEvent, node: TreeNodeData) => latest.current.onNodeContextMenu?.(e, node),\n [],\n );\n const moveNode = useCallback(\n (sourceId: string, targetId: string, position: Parameters<NonNullable<TreeNavProps[\"onNodeMove\"]>>[2]) =>\n latest.current.onNodeMove?.(sourceId, targetId, position),\n [],\n );\n const externalDrop = useCallback(\n (event: React.DragEvent, target: TreeNodeData, position: Parameters<NonNullable<TreeNavProps[\"onExternalDrop\"]>>[2]) =>\n latest.current.onExternalDrop?.(event, target, position),\n [],\n );\n\n // What rows subscribe to. Seeded with this render's values so the first paint\n // is right; kept current after each render that changes them.\n const [store] = useState(() => createTreeStore({ selectedId, expanded, dragState }));\n useLayoutEffect(() => {\n const current = store.get();\n if (current.selectedId !== selectedId || current.expanded !== expanded || current.dragState !== dragState) {\n store.set({ selectedId, expanded, dragState });\n }\n }, [store, selectedId, expanded, dragState]);\n\n const hasContextMenu = onNodeContextMenu !== undefined;\n const internals = useMemo<TreeInternals>(\n () => ({\n store,\n nodes,\n select,\n toggle,\n expandNode,\n setDragState,\n // Present only when the host gave one: a row then claims the right-click.\n contextMenu: hasContextMenu ? contextMenu : undefined,\n moveNode,\n externalDrop,\n indentSize,\n showIcons,\n draggable,\n dragCursor,\n acceptExternalDrops,\n flat: virtualize,\n }),\n [store, nodes, select, toggle, expandNode, hasContextMenu, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, virtualize],\n );\n\n // Unchanged public context, still live for `useTreeNav()` callers. Rows no\n // longer read it, so its changing on a click re-renders only those callers.\n const ctx = useMemo(\n () => ({\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n }),\n [selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode],\n );\n\n const rows = useMemo(\n () => (virtualize ? flattenVisible(nodes, expanded) : []),\n [virtualize, nodes, expanded],\n );\n\n const handleDragEnd = useCallback(() => {\n setDragState(EMPTY_DRAG_STATE);\n }, []);\n\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <TreeNavContext.Provider value={ctx}>\n <TreeInternalsContext.Provider value={internals}>\n <nav\n data-react-fancy-tree-nav=\"\"\n data-react-fancy-tree-nav-virtual={virtualize ? \"\" : undefined}\n className={cn(\"flex flex-col gap-0.5 py-1 text-sm\", className)}\n onDragEnd={dropEnabled ? handleDragEnd : undefined}\n >\n {virtualize ? (\n <VirtualList\n items={rows}\n height={height}\n estimateRowHeight={rowHeight}\n renderRow={(row) => <TreeNode node={row.node} depth={row.depth} />}\n />\n ) : (\n nodes.map((node) => <TreeNode key={node.id} node={node} depth={0} />)\n )}\n </nav>\n </TreeInternalsContext.Provider>\n </TreeNavContext.Provider>\n );\n}\n\nTreeNavRoot.displayName = \"TreeNav\";\n\nexport const TreeNav = Object.assign(TreeNavRoot, {\n Node: TreeNode,\n});\n"]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { VirtualList } from './chunk-Z4CW3U6C.js';
|
|
1
2
|
import { cn } from './chunk-MMW2JGKZ.js';
|
|
2
|
-
import { createContext,
|
|
3
|
-
import {
|
|
3
|
+
import { createContext, memo, useRef, useCallback, useEffect, useState, useMemo, useLayoutEffect, useContext, useSyncExternalStore } from 'react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
5
|
|
|
5
6
|
var TreeNavContext = createContext(null);
|
|
6
7
|
function useTreeNav() {
|
|
@@ -10,6 +11,35 @@ function useTreeNav() {
|
|
|
10
11
|
}
|
|
11
12
|
return ctx;
|
|
12
13
|
}
|
|
14
|
+
function createTreeStore(initial) {
|
|
15
|
+
let state = initial;
|
|
16
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
17
|
+
return {
|
|
18
|
+
get: () => state,
|
|
19
|
+
set(next) {
|
|
20
|
+
state = next;
|
|
21
|
+
for (const listener of listeners) listener();
|
|
22
|
+
},
|
|
23
|
+
subscribe(listener) {
|
|
24
|
+
listeners.add(listener);
|
|
25
|
+
return () => listeners.delete(listener);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
var TreeInternalsContext = createContext(null);
|
|
30
|
+
function useTreeInternals() {
|
|
31
|
+
const internals = useContext(TreeInternalsContext);
|
|
32
|
+
if (!internals) throw new Error("TreeNav.Node must be used within a <TreeNav> component");
|
|
33
|
+
return internals;
|
|
34
|
+
}
|
|
35
|
+
function useTreeSlice(select) {
|
|
36
|
+
const { store } = useTreeInternals();
|
|
37
|
+
return useSyncExternalStore(
|
|
38
|
+
store.subscribe,
|
|
39
|
+
() => select(store.get()),
|
|
40
|
+
() => select(store.get())
|
|
41
|
+
);
|
|
42
|
+
}
|
|
13
43
|
var EXT_COLORS = {
|
|
14
44
|
ts: "#3178c6",
|
|
15
45
|
tsx: "#3178c6",
|
|
@@ -87,32 +117,33 @@ function computeDropPosition(e, isFolder) {
|
|
|
87
117
|
if (offsetY > third * 2) return "after";
|
|
88
118
|
return isFolder ? "inside" : "after";
|
|
89
119
|
}
|
|
90
|
-
function
|
|
120
|
+
function TreeNodeImpl({ node, depth }) {
|
|
91
121
|
const {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
expandedIds,
|
|
122
|
+
store,
|
|
123
|
+
nodes,
|
|
124
|
+
select,
|
|
96
125
|
toggle,
|
|
126
|
+
expandNode,
|
|
127
|
+
setDragState,
|
|
128
|
+
contextMenu,
|
|
129
|
+
moveNode,
|
|
130
|
+
externalDrop,
|
|
97
131
|
indentSize,
|
|
98
132
|
showIcons,
|
|
99
133
|
draggable,
|
|
100
134
|
dragCursor,
|
|
101
|
-
dragState,
|
|
102
|
-
setDragState,
|
|
103
|
-
onNodeMove,
|
|
104
135
|
acceptExternalDrops,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
expandNode
|
|
108
|
-
} = useTreeNav();
|
|
136
|
+
flat
|
|
137
|
+
} = useTreeInternals();
|
|
109
138
|
const isFolder = node.type === "folder" || node.children && node.children.length > 0;
|
|
110
|
-
const isExpanded =
|
|
111
|
-
const isSelected = selectedId === node.id;
|
|
139
|
+
const isExpanded = useTreeSlice((s) => s.expanded.has(node.id));
|
|
140
|
+
const isSelected = useTreeSlice((s) => s.selectedId === node.id);
|
|
141
|
+
const isDragging = useTreeSlice((s) => s.dragState.draggedNodeId === node.id);
|
|
142
|
+
const dropPosition = useTreeSlice(
|
|
143
|
+
(s) => s.dragState.dropTargetId === node.id ? s.dragState.dropPosition : null
|
|
144
|
+
);
|
|
145
|
+
const isDropTarget = dropPosition !== null;
|
|
112
146
|
const paddingLeft = depth * indentSize + 4;
|
|
113
|
-
const isDragging = dragState.draggedNodeId === node.id;
|
|
114
|
-
const isDropTarget = dragState.dropTargetId === node.id;
|
|
115
|
-
const dropPosition = isDropTarget ? dragState.dropPosition : null;
|
|
116
147
|
const autoExpandTimer = useRef(null);
|
|
117
148
|
const clearAutoExpand = useCallback(() => {
|
|
118
149
|
if (autoExpandTimer.current) {
|
|
@@ -126,12 +157,12 @@ function TreeNode({ node, depth }) {
|
|
|
126
157
|
if (isFolder) {
|
|
127
158
|
toggle(node.id);
|
|
128
159
|
}
|
|
129
|
-
|
|
160
|
+
select(node);
|
|
130
161
|
};
|
|
131
162
|
const handleContextMenu = (e) => {
|
|
132
|
-
if (
|
|
163
|
+
if (contextMenu) {
|
|
133
164
|
e.preventDefault();
|
|
134
|
-
|
|
165
|
+
contextMenu(e, node);
|
|
135
166
|
}
|
|
136
167
|
};
|
|
137
168
|
const handleDragStart = useCallback((e) => {
|
|
@@ -145,8 +176,9 @@ function TreeNode({ node, depth }) {
|
|
|
145
176
|
clearAutoExpand();
|
|
146
177
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
147
178
|
}, [clearAutoExpand, setDragState]);
|
|
148
|
-
const isExternalDrag = !dragState.draggedNodeId;
|
|
149
179
|
const handleDragOver = useCallback((e) => {
|
|
180
|
+
const { dragState } = store.get();
|
|
181
|
+
const isExternalDrag = !dragState.draggedNodeId;
|
|
150
182
|
if (isExternalDrag) {
|
|
151
183
|
if (!acceptExternalDrops) return;
|
|
152
184
|
} else {
|
|
@@ -158,7 +190,7 @@ function TreeNode({ node, depth }) {
|
|
|
158
190
|
e.stopPropagation();
|
|
159
191
|
e.dataTransfer.dropEffect = isExternalDrag ? "copy" : "move";
|
|
160
192
|
const position = computeDropPosition(e, !!isFolder);
|
|
161
|
-
if (isFolder && !
|
|
193
|
+
if (isFolder && !store.get().expanded.has(node.id) && position === "inside") {
|
|
162
194
|
if (!autoExpandTimer.current) {
|
|
163
195
|
autoExpandTimer.current = setTimeout(() => {
|
|
164
196
|
expandNode(node.id);
|
|
@@ -171,30 +203,32 @@ function TreeNode({ node, depth }) {
|
|
|
171
203
|
if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {
|
|
172
204
|
setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });
|
|
173
205
|
}
|
|
174
|
-
}, [
|
|
206
|
+
}, [store, acceptExternalDrops, node.id, isFolder, nodes, setDragState, expandNode, clearAutoExpand]);
|
|
175
207
|
const handleDragLeave = useCallback((e) => {
|
|
176
208
|
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
177
209
|
clearAutoExpand();
|
|
210
|
+
const { dragState } = store.get();
|
|
178
211
|
if (dragState.dropTargetId === node.id) {
|
|
179
212
|
setDragState({ ...dragState, dropTargetId: null, dropPosition: null });
|
|
180
213
|
}
|
|
181
214
|
}
|
|
182
|
-
}, [
|
|
215
|
+
}, [store, node.id, setDragState, clearAutoExpand]);
|
|
183
216
|
const handleDrop = useCallback((e) => {
|
|
184
217
|
e.preventDefault();
|
|
185
218
|
e.stopPropagation();
|
|
186
219
|
clearAutoExpand();
|
|
220
|
+
const { dragState } = store.get();
|
|
187
221
|
const sourceId = dragState.draggedNodeId;
|
|
188
222
|
const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);
|
|
189
223
|
if (sourceId) {
|
|
190
224
|
if (sourceId === node.id) return;
|
|
191
225
|
if (isDescendantOf(nodes, sourceId, node.id)) return;
|
|
192
|
-
|
|
226
|
+
moveNode?.(sourceId, node.id, position);
|
|
193
227
|
} else if (acceptExternalDrops) {
|
|
194
|
-
|
|
228
|
+
externalDrop?.(e, node, position);
|
|
195
229
|
}
|
|
196
230
|
setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });
|
|
197
|
-
}, [
|
|
231
|
+
}, [store, node, isFolder, nodes, moveNode, acceptExternalDrops, externalDrop, setDragState, clearAutoExpand]);
|
|
198
232
|
const canDrag = draggable && !node.disabled;
|
|
199
233
|
const dropEnabled = draggable || acceptExternalDrops;
|
|
200
234
|
return /* @__PURE__ */ jsxs(
|
|
@@ -253,11 +287,12 @@ function TreeNode({ node, depth }) {
|
|
|
253
287
|
style: { marginLeft: paddingLeft }
|
|
254
288
|
}
|
|
255
289
|
),
|
|
256
|
-
isFolder && isExpanded && node.children && /* @__PURE__ */ jsx("div", { "data-react-fancy-tree-node-children": "", children: node.children.map((child) => /* @__PURE__ */ jsx(TreeNode, { node: child, depth: depth + 1 }, child.id)) })
|
|
290
|
+
!flat && isFolder && isExpanded && node.children && /* @__PURE__ */ jsx("div", { "data-react-fancy-tree-node-children": "", children: node.children.map((child) => /* @__PURE__ */ jsx(TreeNode, { node: child, depth: depth + 1 }, child.id)) })
|
|
257
291
|
]
|
|
258
292
|
}
|
|
259
293
|
);
|
|
260
294
|
}
|
|
295
|
+
var TreeNode = memo(TreeNodeImpl);
|
|
261
296
|
TreeNode.displayName = "TreeNode";
|
|
262
297
|
function collectFolderIds(nodes) {
|
|
263
298
|
const ids = [];
|
|
@@ -274,6 +309,18 @@ var EMPTY_DRAG_STATE = {
|
|
|
274
309
|
dropTargetId: null,
|
|
275
310
|
dropPosition: null
|
|
276
311
|
};
|
|
312
|
+
function flattenVisible(nodes, expanded) {
|
|
313
|
+
const rows = [];
|
|
314
|
+
const walk = (list, depth) => {
|
|
315
|
+
for (const node of list) {
|
|
316
|
+
rows.push({ id: node.id, node, depth });
|
|
317
|
+
const isFolder = node.type === "folder" || node.children && node.children.length > 0;
|
|
318
|
+
if (isFolder && node.children && expanded.has(node.id)) walk(node.children, depth + 1);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
walk(nodes, 0);
|
|
322
|
+
return rows;
|
|
323
|
+
}
|
|
277
324
|
function TreeNavRoot({
|
|
278
325
|
nodes,
|
|
279
326
|
selectedId,
|
|
@@ -290,6 +337,9 @@ function TreeNavRoot({
|
|
|
290
337
|
indentSize = 16,
|
|
291
338
|
showIcons = true,
|
|
292
339
|
dragCursor = "none",
|
|
340
|
+
virtualize = false,
|
|
341
|
+
height = 400,
|
|
342
|
+
rowHeight = 26,
|
|
293
343
|
className
|
|
294
344
|
}) {
|
|
295
345
|
const [internalExpanded, setInternalExpanded] = useState(() => {
|
|
@@ -299,31 +349,86 @@ function TreeNavRoot({
|
|
|
299
349
|
});
|
|
300
350
|
const isControlled = controlledExpanded !== void 0;
|
|
301
351
|
const expandedIds = isControlled ? controlledExpanded : internalExpanded;
|
|
352
|
+
const expanded = useMemo(() => new Set(expandedIds), [expandedIds]);
|
|
353
|
+
const [dragState, setDragState] = useState(EMPTY_DRAG_STATE);
|
|
354
|
+
const latest = useRef({ expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop });
|
|
355
|
+
latest.current = { expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop };
|
|
356
|
+
const setExpanded = useCallback((next) => {
|
|
357
|
+
if (!latest.current.isControlled) setInternalExpanded(next);
|
|
358
|
+
latest.current.onExpandedChange?.(next);
|
|
359
|
+
}, []);
|
|
302
360
|
const toggle = useCallback(
|
|
303
361
|
(id) => {
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
setInternalExpanded(next);
|
|
307
|
-
}
|
|
308
|
-
onExpandedChange?.(next);
|
|
362
|
+
const current = latest.current.expandedIds;
|
|
363
|
+
setExpanded(current.includes(id) ? current.filter((v) => v !== id) : [...current, id]);
|
|
309
364
|
},
|
|
310
|
-
[
|
|
365
|
+
[setExpanded]
|
|
311
366
|
);
|
|
312
367
|
const expandNode = useCallback(
|
|
313
368
|
(id) => {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
if (!isControlled) {
|
|
317
|
-
setInternalExpanded(next);
|
|
318
|
-
}
|
|
319
|
-
onExpandedChange?.(next);
|
|
369
|
+
const current = latest.current.expandedIds;
|
|
370
|
+
if (!current.includes(id)) setExpanded([...current, id]);
|
|
320
371
|
},
|
|
321
|
-
[
|
|
372
|
+
[setExpanded]
|
|
373
|
+
);
|
|
374
|
+
const select = useCallback((node) => latest.current.onSelect?.(node.id, node), []);
|
|
375
|
+
const contextMenu = useCallback(
|
|
376
|
+
(e, node) => latest.current.onNodeContextMenu?.(e, node),
|
|
377
|
+
[]
|
|
378
|
+
);
|
|
379
|
+
const moveNode = useCallback(
|
|
380
|
+
(sourceId, targetId, position) => latest.current.onNodeMove?.(sourceId, targetId, position),
|
|
381
|
+
[]
|
|
382
|
+
);
|
|
383
|
+
const externalDrop = useCallback(
|
|
384
|
+
(event, target, position) => latest.current.onExternalDrop?.(event, target, position),
|
|
385
|
+
[]
|
|
386
|
+
);
|
|
387
|
+
const [store] = useState(() => createTreeStore({ selectedId, expanded, dragState }));
|
|
388
|
+
useLayoutEffect(() => {
|
|
389
|
+
const current = store.get();
|
|
390
|
+
if (current.selectedId !== selectedId || current.expanded !== expanded || current.dragState !== dragState) {
|
|
391
|
+
store.set({ selectedId, expanded, dragState });
|
|
392
|
+
}
|
|
393
|
+
}, [store, selectedId, expanded, dragState]);
|
|
394
|
+
const hasContextMenu = onNodeContextMenu !== void 0;
|
|
395
|
+
const internals = useMemo(
|
|
396
|
+
() => ({
|
|
397
|
+
store,
|
|
398
|
+
nodes,
|
|
399
|
+
select,
|
|
400
|
+
toggle,
|
|
401
|
+
expandNode,
|
|
402
|
+
setDragState,
|
|
403
|
+
// Present only when the host gave one: a row then claims the right-click.
|
|
404
|
+
contextMenu: hasContextMenu ? contextMenu : void 0,
|
|
405
|
+
moveNode,
|
|
406
|
+
externalDrop,
|
|
407
|
+
indentSize,
|
|
408
|
+
showIcons,
|
|
409
|
+
draggable,
|
|
410
|
+
dragCursor,
|
|
411
|
+
acceptExternalDrops,
|
|
412
|
+
flat: virtualize
|
|
413
|
+
}),
|
|
414
|
+
[
|
|
415
|
+
store,
|
|
416
|
+
nodes,
|
|
417
|
+
select,
|
|
418
|
+
toggle,
|
|
419
|
+
expandNode,
|
|
420
|
+
hasContextMenu,
|
|
421
|
+
contextMenu,
|
|
422
|
+
moveNode,
|
|
423
|
+
externalDrop,
|
|
424
|
+
indentSize,
|
|
425
|
+
showIcons,
|
|
426
|
+
draggable,
|
|
427
|
+
dragCursor,
|
|
428
|
+
acceptExternalDrops,
|
|
429
|
+
virtualize
|
|
430
|
+
]
|
|
322
431
|
);
|
|
323
|
-
const [dragState, setDragState] = useState(EMPTY_DRAG_STATE);
|
|
324
|
-
const handleDragEnd = useCallback(() => {
|
|
325
|
-
setDragState(EMPTY_DRAG_STATE);
|
|
326
|
-
}, []);
|
|
327
432
|
const ctx = useMemo(
|
|
328
433
|
() => ({
|
|
329
434
|
selectedId,
|
|
@@ -361,16 +466,32 @@ function TreeNavRoot({
|
|
|
361
466
|
expandNode
|
|
362
467
|
]
|
|
363
468
|
);
|
|
469
|
+
const rows = useMemo(
|
|
470
|
+
() => virtualize ? flattenVisible(nodes, expanded) : [],
|
|
471
|
+
[virtualize, nodes, expanded]
|
|
472
|
+
);
|
|
473
|
+
const handleDragEnd = useCallback(() => {
|
|
474
|
+
setDragState(EMPTY_DRAG_STATE);
|
|
475
|
+
}, []);
|
|
364
476
|
const dropEnabled = draggable || acceptExternalDrops;
|
|
365
|
-
return /* @__PURE__ */ jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
477
|
+
return /* @__PURE__ */ jsx(TreeNavContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(TreeInternalsContext.Provider, { value: internals, children: /* @__PURE__ */ jsx(
|
|
366
478
|
"nav",
|
|
367
479
|
{
|
|
368
480
|
"data-react-fancy-tree-nav": "",
|
|
481
|
+
"data-react-fancy-tree-nav-virtual": virtualize ? "" : void 0,
|
|
369
482
|
className: cn("flex flex-col gap-0.5 py-1 text-sm", className),
|
|
370
483
|
onDragEnd: dropEnabled ? handleDragEnd : void 0,
|
|
371
|
-
children:
|
|
484
|
+
children: virtualize ? /* @__PURE__ */ jsx(
|
|
485
|
+
VirtualList,
|
|
486
|
+
{
|
|
487
|
+
items: rows,
|
|
488
|
+
height,
|
|
489
|
+
estimateRowHeight: rowHeight,
|
|
490
|
+
renderRow: (row) => /* @__PURE__ */ jsx(TreeNode, { node: row.node, depth: row.depth })
|
|
491
|
+
}
|
|
492
|
+
) : nodes.map((node) => /* @__PURE__ */ jsx(TreeNode, { node, depth: 0 }, node.id))
|
|
372
493
|
}
|
|
373
|
-
) });
|
|
494
|
+
) }) });
|
|
374
495
|
}
|
|
375
496
|
TreeNavRoot.displayName = "TreeNav";
|
|
376
497
|
var TreeNav = Object.assign(TreeNavRoot, {
|
|
@@ -378,5 +499,5 @@ var TreeNav = Object.assign(TreeNavRoot, {
|
|
|
378
499
|
});
|
|
379
500
|
|
|
380
501
|
export { TreeNav, useTreeNav };
|
|
381
|
-
//# sourceMappingURL=chunk-
|
|
382
|
-
//# sourceMappingURL=chunk-
|
|
502
|
+
//# sourceMappingURL=chunk-ZVXOL5H3.js.map
|
|
503
|
+
//# sourceMappingURL=chunk-ZVXOL5H3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/TreeNav/TreeNav.context.ts","../src/components/TreeNav/tree-store.ts","../src/components/TreeNav/TreeNode.tsx","../src/components/TreeNav/TreeNav.tsx"],"names":["createContext","useContext","useRef","useCallback","jsx"],"mappings":"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","file":"chunk-ZVXOL5H3.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { TreeNavContextValue } from \"./TreeNav.types\";\n\nexport const TreeNavContext = createContext<TreeNavContextValue | null>(null);\n\nexport function useTreeNav(): TreeNavContextValue {\n const ctx = useContext(TreeNavContext);\n if (!ctx) {\n throw new Error(\"useTreeNav must be used within a <TreeNav> component\");\n }\n return ctx;\n}\n","import { createContext, useContext, useSyncExternalStore } from \"react\";\nimport type { DragState, DropPosition, TreeNodeData } from \"./TreeNav.types\";\n\n/** What changes as someone clicks, toggles and drags — the state rows subscribe to. */\nexport interface TreeState {\n selectedId?: string;\n expanded: ReadonlySet<string>;\n dragState: DragState;\n}\n\nexport interface TreeStore {\n get(): TreeState;\n set(next: TreeState): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport function createTreeStore(initial: TreeState): TreeStore {\n let state = initial;\n const listeners = new Set<() => void>();\n return {\n get: () => state,\n set(next) {\n state = next;\n for (const listener of listeners) listener();\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n };\n}\n\n/**\n * Everything a row needs that does NOT change on a click. Handlers are stable\n * (they read the latest props and state when called), so this value only changes\n * when the tree's data or configuration does.\n *\n * Kept apart from the public `TreeNavContext` on purpose: that one still carries\n * live `selectedId` / `expandedIds` for `useTreeNav()` callers, and any component\n * reading it re-renders on every click. Rows no longer read it — which is the\n * whole fix: a click used to re-render every row in the tree.\n */\nexport interface TreeInternals {\n store: TreeStore;\n nodes: TreeNodeData[];\n select: (node: TreeNodeData) => void;\n toggle: (id: string) => void;\n expandNode: (id: string) => void;\n setDragState: (state: DragState) => void;\n contextMenu?: (e: React.MouseEvent, node: TreeNodeData) => void;\n moveNode?: (sourceId: string, targetId: string, position: DropPosition) => void;\n externalDrop?: (event: React.DragEvent, target: TreeNodeData, position: DropPosition) => void;\n indentSize: number;\n showIcons: boolean;\n draggable: boolean;\n dragCursor: \"grab\" | \"none\";\n acceptExternalDrops: boolean;\n /** Rows are rendered flat by a virtual list, so a row never renders its children. */\n flat: boolean;\n}\n\nexport const TreeInternalsContext = createContext<TreeInternals | null>(null);\n\nexport function useTreeInternals(): TreeInternals {\n const internals = useContext(TreeInternalsContext);\n if (!internals) throw new Error(\"TreeNav.Node must be used within a <TreeNav> component\");\n return internals;\n}\n\n/** Subscribe to ONE derived value; the row re-renders only when that value changes. */\nexport function useTreeSlice<T>(select: (state: TreeState) => T): T {\n const { store } = useTreeInternals();\n return useSyncExternalStore(\n store.subscribe,\n () => select(store.get()),\n () => select(store.get()),\n );\n}\n","import { memo, useRef, useEffect, useCallback } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useTreeInternals, useTreeSlice } from \"./tree-store\";\nimport type { TreeNodeProps, TreeNodeData, DropPosition } from \"./TreeNav.types\";\n\nconst EXT_COLORS: Record<string, string> = {\n ts: \"#3178c6\",\n tsx: \"#3178c6\",\n js: \"#f7df1e\",\n jsx: \"#f7df1e\",\n php: \"#777bb4\",\n html: \"#e34c26\",\n htm: \"#e34c26\",\n css: \"#264de4\",\n json: \"#a1a1aa\",\n md: \"#71717a\",\n yaml: \"#cb171e\",\n yml: \"#cb171e\",\n};\n\nfunction FileIcon({ ext }: { ext?: string }) {\n const color = (ext && EXT_COLORS[ext.toLowerCase()]) || \"#71717a\";\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z\" stroke={color} strokeWidth=\"1.2\" />\n <path d=\"M9 1v4h4\" stroke={color} strokeWidth=\"1.2\" />\n </svg>\n );\n}\n\nfunction FolderIcon({ open }: { open: boolean }) {\n if (open) {\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z\" fill=\"#fbbf24\" />\n <path d=\"M1 6h13l-1.5 7.5H2.5L1 6z\" fill=\"#fbbf24\" opacity=\"0.7\" />\n </svg>\n );\n }\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z\" fill=\"#fbbf24\" />\n </svg>\n );\n}\n\nfunction ChevronIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"shrink-0 transition-transform duration-150\", open && \"rotate-90\")}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction isDescendantOf(\n nodes: TreeNodeData[],\n ancestorId: string,\n targetId: string,\n): boolean {\n function findNode(haystack: TreeNodeData[], id: string): TreeNodeData | undefined {\n for (const n of haystack) {\n if (n.id === id) return n;\n if (n.children) {\n const found = findNode(n.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n function hasDescendant(node: TreeNodeData, id: string): boolean {\n if (!node.children) return false;\n for (const child of node.children) {\n if (child.id === id) return true;\n if (hasDescendant(child, id)) return true;\n }\n return false;\n }\n\n const ancestor = findNode(nodes, ancestorId);\n return ancestor ? hasDescendant(ancestor, targetId) : false;\n}\n\nfunction computeDropPosition(\n e: React.DragEvent,\n isFolder: boolean,\n): DropPosition {\n const rect = e.currentTarget.getBoundingClientRect();\n const offsetY = e.clientY - rect.top;\n const third = rect.height / 3;\n\n if (offsetY < third) return \"before\";\n if (offsetY > third * 2) return \"after\";\n return isFolder ? \"inside\" : \"after\";\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n\n/**\n * One row (and, when nested, its expanded children).\n *\n * Reads what changes on a click — selection, expansion, drag — as per-row slices\n * of a store, so a click re-renders only the rows whose slice changed. It used to\n * read one context holding all of it, so every click re-rendered every row:\n * 175-267 ms per click on a 5,000-node tree. `memo` keeps a parent's re-render\n * from reaching rows whose `node` and `depth` did not change.\n */\nfunction TreeNodeImpl({ node, depth }: TreeNodeProps) {\n const {\n store, nodes, select, toggle, expandNode, setDragState, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, flat,\n } = useTreeInternals();\n\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n const isExpanded = useTreeSlice((s) => s.expanded.has(node.id));\n const isSelected = useTreeSlice((s) => s.selectedId === node.id);\n const isDragging = useTreeSlice((s) => s.dragState.draggedNodeId === node.id);\n const dropPosition = useTreeSlice((s) =>\n s.dragState.dropTargetId === node.id ? s.dragState.dropPosition : null,\n );\n const isDropTarget = dropPosition !== null;\n const paddingLeft = depth * indentSize + 4;\n\n // Auto-expand timer for folders during drag\n const autoExpandTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearAutoExpand = useCallback(() => {\n if (autoExpandTimer.current) {\n clearTimeout(autoExpandTimer.current);\n autoExpandTimer.current = null;\n }\n }, []);\n\n useEffect(() => clearAutoExpand, [clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Click handlers\n // ---------------------------------------------------------------------------\n\n const handleClick = () => {\n if (node.disabled) return;\n if (isFolder) {\n toggle(node.id);\n }\n select(node);\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n if (contextMenu) {\n e.preventDefault();\n contextMenu(e, node);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Drag source handlers\n // ---------------------------------------------------------------------------\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n e.dataTransfer.setData(\"text/plain\", node.id);\n e.dataTransfer.effectAllowed = \"move\";\n // Set state async so the browser captures the element for the drag ghost first\n requestAnimationFrame(() => {\n setDragState({ draggedNodeId: node.id, dropTargetId: null, dropPosition: null });\n });\n }, [node.id, setDragState]);\n\n const handleDragEnd = useCallback(() => {\n clearAutoExpand();\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [clearAutoExpand, setDragState]);\n\n // ---------------------------------------------------------------------------\n // Drop target handlers (on outer div). Drag state is read from the store when\n // the event fires, so a row does not re-render as a drag moves elsewhere.\n // ---------------------------------------------------------------------------\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n const { dragState } = store.get();\n const isExternalDrag = !dragState.draggedNodeId;\n if (isExternalDrag) {\n // External drop accepted only when host opted in.\n if (!acceptExternalDrops) return;\n } else {\n const sourceId = dragState.draggedNodeId!;\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n e.dataTransfer.dropEffect = isExternalDrag ? \"copy\" : \"move\";\n\n const position = computeDropPosition(e, !!isFolder);\n\n // Auto-expand collapsed folders when hovering \"inside\"\n if (isFolder && !store.get().expanded.has(node.id) && position === \"inside\") {\n if (!autoExpandTimer.current) {\n autoExpandTimer.current = setTimeout(() => {\n expandNode(node.id);\n autoExpandTimer.current = null;\n }, 500);\n }\n } else {\n clearAutoExpand();\n }\n\n if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {\n setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });\n }\n }, [store, acceptExternalDrops, node.id, isFolder, nodes, setDragState, expandNode, clearAutoExpand]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n clearAutoExpand();\n const { dragState } = store.get();\n if (dragState.dropTargetId === node.id) {\n setDragState({ ...dragState, dropTargetId: null, dropPosition: null });\n }\n }\n }, [store, node.id, setDragState, clearAutoExpand]);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n clearAutoExpand();\n\n const { dragState } = store.get();\n const sourceId = dragState.draggedNodeId;\n const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);\n\n if (sourceId) {\n // Internal node move.\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n moveNode?.(sourceId, node.id, position);\n } else if (acceptExternalDrops) {\n // External drop — files, items from other components, etc.\n externalDrop?.(e, node, position);\n }\n\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [store, node, isFolder, nodes, moveNode, acceptExternalDrops, externalDrop, setDragState, clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n const canDrag = draggable && !node.disabled;\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <div\n data-react-fancy-tree-node=\"\"\n onDragOver={dropEnabled ? handleDragOver : undefined}\n onDragLeave={dropEnabled ? handleDragLeave : undefined}\n onDrop={dropEnabled ? handleDrop : undefined}\n >\n {/* Before drop indicator */}\n {isDropTarget && dropPosition === \"before\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"before\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n <button\n type=\"button\"\n draggable={canDrag}\n onDragStart={canDrag ? handleDragStart : undefined}\n onDragEnd={canDrag ? handleDragEnd : undefined}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n disabled={node.disabled}\n className={cn(\n \"flex w-full items-center gap-1 rounded-md py-0.5 text-left text-[13px] transition-colors\",\n isSelected\n ? \"bg-blue-500/15 text-blue-600 dark:text-blue-400\"\n : \"text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800\",\n node.disabled && \"pointer-events-none opacity-40\",\n isDragging && \"opacity-50\",\n // Grab affordance. By default (`dragCursor=\"none\"`) the row keeps its\n // normal click cursor on hover — no open-hand \"move me\" cursor before\n // any drag — and only shows the closed-hand `grabbing` cursor while a\n // drag is actually underway (mousedown/drag). `dragCursor=\"grab\"` opts\n // back into the always-on grab cursor for whole-row drag handles (#14).\n canDrag &&\n (dragCursor === \"grab\" ? \"cursor-grab active:cursor-grabbing\" : \"active:cursor-grabbing\"),\n isDropTarget && dropPosition === \"inside\" && \"bg-blue-500/10 ring-1 ring-blue-500/30 ring-inset\",\n )}\n style={{ paddingLeft }}\n >\n {isFolder && <ChevronIcon open={isExpanded} />}\n {!isFolder && <span className=\"w-3.5 shrink-0\" />}\n {showIcons && (\n node.icon ?? (isFolder\n ? <FolderIcon open={isExpanded} />\n : <FileIcon ext={node.ext ?? node.label.split(\".\").pop()} />)\n )}\n <span className=\"truncate\">{node.label}</span>\n </button>\n\n {/* After drop indicator */}\n {isDropTarget && dropPosition === \"after\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"after\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n {/* A virtual list renders rows flat, so a row never renders its own children there. */}\n {!flat && isFolder && isExpanded && node.children && (\n <div data-react-fancy-tree-node-children=\"\">\n {node.children.map((child) => (\n <TreeNode key={child.id} node={child} depth={depth + 1} />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport const TreeNode = memo(TreeNodeImpl);\n\nTreeNode.displayName = \"TreeNode\";\n","import { useMemo, useCallback, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { VirtualList } from \"../VirtualList\";\nimport { TreeNavContext } from \"./TreeNav.context\";\nimport { TreeNode } from \"./TreeNode\";\nimport { createTreeStore, TreeInternalsContext, type TreeInternals } from \"./tree-store\";\nimport type { TreeNavProps, TreeNodeData, DragState } from \"./TreeNav.types\";\n\nfunction collectFolderIds(nodes: TreeNodeData[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (node.children && node.children.length > 0) {\n ids.push(node.id);\n ids.push(...collectFolderIds(node.children));\n }\n }\n return ids;\n}\n\nconst EMPTY_DRAG_STATE: DragState = {\n draggedNodeId: null,\n dropTargetId: null,\n dropPosition: null,\n};\n\ninterface FlatRow {\n id: string;\n node: TreeNodeData;\n depth: number;\n}\n\n/** The rows currently visible — every node, descending only into expanded folders. */\nfunction flattenVisible(nodes: TreeNodeData[], expanded: ReadonlySet<string>): FlatRow[] {\n const rows: FlatRow[] = [];\n const walk = (list: TreeNodeData[], depth: number) => {\n for (const node of list) {\n rows.push({ id: node.id, node, depth });\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n if (isFolder && node.children && expanded.has(node.id)) walk(node.children, depth + 1);\n }\n };\n walk(nodes, 0);\n return rows;\n}\n\nfunction TreeNavRoot({\n nodes,\n selectedId,\n onSelect,\n onNodeContextMenu,\n draggable = false,\n onNodeMove,\n acceptExternalDrops = false,\n onExternalDrop,\n expandedIds: controlledExpanded,\n defaultExpandedIds,\n onExpandedChange,\n defaultExpandAll = false,\n indentSize = 16,\n showIcons = true,\n dragCursor = \"none\",\n virtualize = false,\n height = 400,\n rowHeight = 26,\n className,\n}: TreeNavProps) {\n const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {\n if (defaultExpandedIds) return defaultExpandedIds;\n if (defaultExpandAll) return collectFolderIds(nodes);\n return [];\n });\n\n const isControlled = controlledExpanded !== undefined;\n const expandedIds = isControlled ? controlledExpanded : internalExpanded;\n // One Set per change, so a row's \"am I expanded?\" is a lookup, not a scan.\n const expanded = useMemo(() => new Set(expandedIds), [expandedIds]);\n\n const [dragState, setDragState] = useState<DragState>(EMPTY_DRAG_STATE);\n\n /*\n * The latest props and state, for handlers that must stay STABLE. A handler\n * whose identity changes on every click would change the context rows read,\n * and re-render every row — the exact cost this component is avoiding.\n */\n const latest = useRef({ expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop });\n latest.current = { expandedIds, isControlled, onExpandedChange, onSelect, onNodeContextMenu, onNodeMove, onExternalDrop };\n\n const setExpanded = useCallback((next: string[]) => {\n if (!latest.current.isControlled) setInternalExpanded(next);\n latest.current.onExpandedChange?.(next);\n }, []);\n\n const toggle = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n setExpanded(current.includes(id) ? current.filter((v) => v !== id) : [...current, id]);\n },\n [setExpanded],\n );\n\n const expandNode = useCallback(\n (id: string) => {\n const current = latest.current.expandedIds;\n if (!current.includes(id)) setExpanded([...current, id]);\n },\n [setExpanded],\n );\n\n const select = useCallback((node: TreeNodeData) => latest.current.onSelect?.(node.id, node), []);\n const contextMenu = useCallback(\n (e: React.MouseEvent, node: TreeNodeData) => latest.current.onNodeContextMenu?.(e, node),\n [],\n );\n const moveNode = useCallback(\n (sourceId: string, targetId: string, position: Parameters<NonNullable<TreeNavProps[\"onNodeMove\"]>>[2]) =>\n latest.current.onNodeMove?.(sourceId, targetId, position),\n [],\n );\n const externalDrop = useCallback(\n (event: React.DragEvent, target: TreeNodeData, position: Parameters<NonNullable<TreeNavProps[\"onExternalDrop\"]>>[2]) =>\n latest.current.onExternalDrop?.(event, target, position),\n [],\n );\n\n // What rows subscribe to. Seeded with this render's values so the first paint\n // is right; kept current after each render that changes them.\n const [store] = useState(() => createTreeStore({ selectedId, expanded, dragState }));\n useLayoutEffect(() => {\n const current = store.get();\n if (current.selectedId !== selectedId || current.expanded !== expanded || current.dragState !== dragState) {\n store.set({ selectedId, expanded, dragState });\n }\n }, [store, selectedId, expanded, dragState]);\n\n const hasContextMenu = onNodeContextMenu !== undefined;\n const internals = useMemo<TreeInternals>(\n () => ({\n store,\n nodes,\n select,\n toggle,\n expandNode,\n setDragState,\n // Present only when the host gave one: a row then claims the right-click.\n contextMenu: hasContextMenu ? contextMenu : undefined,\n moveNode,\n externalDrop,\n indentSize,\n showIcons,\n draggable,\n dragCursor,\n acceptExternalDrops,\n flat: virtualize,\n }),\n [store, nodes, select, toggle, expandNode, hasContextMenu, contextMenu, moveNode, externalDrop,\n indentSize, showIcons, draggable, dragCursor, acceptExternalDrops, virtualize],\n );\n\n // Unchanged public context, still live for `useTreeNav()` callers. Rows no\n // longer read it, so its changing on a click re-renders only those callers.\n const ctx = useMemo(\n () => ({\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n }),\n [selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode],\n );\n\n const rows = useMemo(\n () => (virtualize ? flattenVisible(nodes, expanded) : []),\n [virtualize, nodes, expanded],\n );\n\n const handleDragEnd = useCallback(() => {\n setDragState(EMPTY_DRAG_STATE);\n }, []);\n\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <TreeNavContext.Provider value={ctx}>\n <TreeInternalsContext.Provider value={internals}>\n <nav\n data-react-fancy-tree-nav=\"\"\n data-react-fancy-tree-nav-virtual={virtualize ? \"\" : undefined}\n className={cn(\"flex flex-col gap-0.5 py-1 text-sm\", className)}\n onDragEnd={dropEnabled ? handleDragEnd : undefined}\n >\n {virtualize ? (\n <VirtualList\n items={rows}\n height={height}\n estimateRowHeight={rowHeight}\n renderRow={(row) => <TreeNode node={row.node} depth={row.depth} />}\n />\n ) : (\n nodes.map((node) => <TreeNode key={node.id} node={node} depth={0} />)\n )}\n </nav>\n </TreeInternalsContext.Provider>\n </TreeNavContext.Provider>\n );\n}\n\nTreeNavRoot.displayName = \"TreeNav\";\n\nexport const TreeNav = Object.assign(TreeNavRoot, {\n Node: TreeNode,\n});\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var chunkIR6JSQ4O_cjs = require('./chunk-IR6JSQ4O.cjs');
|
|
4
3
|
require('./chunk-O5IO7TCO.cjs');
|
|
5
4
|
var chunkZUQUL53P_cjs = require('./chunk-ZUQUL53P.cjs');
|
|
6
5
|
var chunkFPZYPD3N_cjs = require('./chunk-FPZYPD3N.cjs');
|
|
@@ -19,7 +18,8 @@ var chunkCOH45MGW_cjs = require('./chunk-COH45MGW.cjs');
|
|
|
19
18
|
var chunkYMDYDUBH_cjs = require('./chunk-YMDYDUBH.cjs');
|
|
20
19
|
var chunkRLMU3NHQ_cjs = require('./chunk-RLMU3NHQ.cjs');
|
|
21
20
|
var chunkUBZFOTZN_cjs = require('./chunk-UBZFOTZN.cjs');
|
|
22
|
-
var
|
|
21
|
+
var chunkWN45ZE37_cjs = require('./chunk-WN45ZE37.cjs');
|
|
22
|
+
var chunkIR6JSQ4O_cjs = require('./chunk-IR6JSQ4O.cjs');
|
|
23
23
|
var chunk5P3PISAX_cjs = require('./chunk-5P3PISAX.cjs');
|
|
24
24
|
var chunkOS2QC3NE_cjs = require('./chunk-OS2QC3NE.cjs');
|
|
25
25
|
var chunk6IBBAOQC_cjs = require('./chunk-6IBBAOQC.cjs');
|
|
@@ -125,10 +125,6 @@ var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
|
125
125
|
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
Object.defineProperty(exports, "VirtualList", {
|
|
129
|
-
enumerable: true,
|
|
130
|
-
get: function () { return chunkIR6JSQ4O_cjs.VirtualList; }
|
|
131
|
-
});
|
|
132
128
|
Object.defineProperty(exports, "MultiSwitch", {
|
|
133
129
|
enumerable: true,
|
|
134
130
|
get: function () { return chunkZUQUL53P_cjs.MultiSwitch; }
|
|
@@ -187,11 +183,15 @@ Object.defineProperty(exports, "useToast", {
|
|
|
187
183
|
});
|
|
188
184
|
Object.defineProperty(exports, "TreeNav", {
|
|
189
185
|
enumerable: true,
|
|
190
|
-
get: function () { return
|
|
186
|
+
get: function () { return chunkWN45ZE37_cjs.TreeNav; }
|
|
191
187
|
});
|
|
192
188
|
Object.defineProperty(exports, "useTreeNav", {
|
|
193
189
|
enumerable: true,
|
|
194
|
-
get: function () { return
|
|
190
|
+
get: function () { return chunkWN45ZE37_cjs.useTreeNav; }
|
|
191
|
+
});
|
|
192
|
+
Object.defineProperty(exports, "VirtualList", {
|
|
193
|
+
enumerable: true,
|
|
194
|
+
get: function () { return chunkIR6JSQ4O_cjs.VirtualList; }
|
|
195
195
|
});
|
|
196
196
|
Object.defineProperty(exports, "Section", {
|
|
197
197
|
enumerable: true,
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { VirtualList } from './chunk-Z4CW3U6C.js';
|
|
2
1
|
import './chunk-VXHFR5MV.js';
|
|
3
2
|
export { MultiSwitch } from './chunk-NE3Z4AWP.js';
|
|
4
3
|
export { RadioGroup } from './chunk-LBU3T2LF.js';
|
|
@@ -17,7 +16,8 @@ export { TimeGrid } from './chunk-7CN7IHES.js';
|
|
|
17
16
|
export { TimePicker } from './chunk-OTCLBINT.js';
|
|
18
17
|
export { Timeline } from './chunk-7JDVCQXD.js';
|
|
19
18
|
export { Toast, useToast } from './chunk-3AQLEMH7.js';
|
|
20
|
-
export { TreeNav, useTreeNav } from './chunk-
|
|
19
|
+
export { TreeNav, useTreeNav } from './chunk-ZVXOL5H3.js';
|
|
20
|
+
export { VirtualList } from './chunk-Z4CW3U6C.js';
|
|
21
21
|
export { Section } from './chunk-SLJD25VV.js';
|
|
22
22
|
export { Separator } from './chunk-ZLWCZV7R.js';
|
|
23
23
|
export { Sidebar, useSidebar } from './chunk-YD4SXA6K.js';
|
package/dist/tree-nav.cjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkWN45ZE37_cjs = require('./chunk-WN45ZE37.cjs');
|
|
4
|
+
require('./chunk-IR6JSQ4O.cjs');
|
|
4
5
|
require('./chunk-ZU5Q77OT.cjs');
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
Object.defineProperty(exports, "TreeNav", {
|
|
9
10
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunkWN45ZE37_cjs.TreeNav; }
|
|
11
12
|
});
|
|
12
13
|
Object.defineProperty(exports, "useTreeNav", {
|
|
13
14
|
enumerable: true,
|
|
14
|
-
get: function () { return
|
|
15
|
+
get: function () { return chunkWN45ZE37_cjs.useTreeNav; }
|
|
15
16
|
});
|
|
16
17
|
//# sourceMappingURL=tree-nav.cjs.map
|
|
17
18
|
//# sourceMappingURL=tree-nav.cjs.map
|
package/dist/tree-nav.d.cts
CHANGED
|
@@ -69,6 +69,18 @@ interface TreeNavProps {
|
|
|
69
69
|
* @default "none"
|
|
70
70
|
*/
|
|
71
71
|
dragCursor?: "grab" | "none";
|
|
72
|
+
/**
|
|
73
|
+
* Render only the rows in view. For big trees — thousands of expanded nodes —
|
|
74
|
+
* where mounting every visible row is itself the cost. The visible rows are
|
|
75
|
+
* flattened (expanded folders only) and windowed by `VirtualList`; selection,
|
|
76
|
+
* expansion, context menus and drag-and-drop behave as in the nested tree.
|
|
77
|
+
* Needs a `height`. Default `false`.
|
|
78
|
+
*/
|
|
79
|
+
virtualize?: boolean;
|
|
80
|
+
/** Height of the scroll viewport when `virtualize` is on. Default 400. */
|
|
81
|
+
height?: number | string;
|
|
82
|
+
/** Estimated row height in px when `virtualize` is on; real heights replace it as rows render. Default 26. */
|
|
83
|
+
rowHeight?: number;
|
|
72
84
|
/** Custom className */
|
|
73
85
|
className?: string;
|
|
74
86
|
}
|
|
@@ -96,17 +108,12 @@ interface TreeNodeProps {
|
|
|
96
108
|
className?: string;
|
|
97
109
|
}
|
|
98
110
|
|
|
99
|
-
declare function
|
|
100
|
-
declare namespace TreeNode {
|
|
101
|
-
var displayName: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, acceptExternalDrops, onExternalDrop, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, dragCursor, className, }: TreeNavProps): react.JSX.Element;
|
|
111
|
+
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, acceptExternalDrops, onExternalDrop, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, dragCursor, virtualize, height, rowHeight, className, }: TreeNavProps): react.JSX.Element;
|
|
105
112
|
declare namespace TreeNavRoot {
|
|
106
113
|
var displayName: string;
|
|
107
114
|
}
|
|
108
115
|
declare const TreeNav: typeof TreeNavRoot & {
|
|
109
|
-
Node:
|
|
116
|
+
Node: react.MemoExoticComponent<({ node, depth }: TreeNodeProps) => react.JSX.Element>;
|
|
110
117
|
};
|
|
111
118
|
|
|
112
119
|
declare function useTreeNav(): TreeNavContextValue;
|
package/dist/tree-nav.d.ts
CHANGED
|
@@ -69,6 +69,18 @@ interface TreeNavProps {
|
|
|
69
69
|
* @default "none"
|
|
70
70
|
*/
|
|
71
71
|
dragCursor?: "grab" | "none";
|
|
72
|
+
/**
|
|
73
|
+
* Render only the rows in view. For big trees — thousands of expanded nodes —
|
|
74
|
+
* where mounting every visible row is itself the cost. The visible rows are
|
|
75
|
+
* flattened (expanded folders only) and windowed by `VirtualList`; selection,
|
|
76
|
+
* expansion, context menus and drag-and-drop behave as in the nested tree.
|
|
77
|
+
* Needs a `height`. Default `false`.
|
|
78
|
+
*/
|
|
79
|
+
virtualize?: boolean;
|
|
80
|
+
/** Height of the scroll viewport when `virtualize` is on. Default 400. */
|
|
81
|
+
height?: number | string;
|
|
82
|
+
/** Estimated row height in px when `virtualize` is on; real heights replace it as rows render. Default 26. */
|
|
83
|
+
rowHeight?: number;
|
|
72
84
|
/** Custom className */
|
|
73
85
|
className?: string;
|
|
74
86
|
}
|
|
@@ -96,17 +108,12 @@ interface TreeNodeProps {
|
|
|
96
108
|
className?: string;
|
|
97
109
|
}
|
|
98
110
|
|
|
99
|
-
declare function
|
|
100
|
-
declare namespace TreeNode {
|
|
101
|
-
var displayName: string;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, acceptExternalDrops, onExternalDrop, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, dragCursor, className, }: TreeNavProps): react.JSX.Element;
|
|
111
|
+
declare function TreeNavRoot({ nodes, selectedId, onSelect, onNodeContextMenu, draggable, onNodeMove, acceptExternalDrops, onExternalDrop, expandedIds: controlledExpanded, defaultExpandedIds, onExpandedChange, defaultExpandAll, indentSize, showIcons, dragCursor, virtualize, height, rowHeight, className, }: TreeNavProps): react.JSX.Element;
|
|
105
112
|
declare namespace TreeNavRoot {
|
|
106
113
|
var displayName: string;
|
|
107
114
|
}
|
|
108
115
|
declare const TreeNav: typeof TreeNavRoot & {
|
|
109
|
-
Node:
|
|
116
|
+
Node: react.MemoExoticComponent<({ node, depth }: TreeNodeProps) => react.JSX.Element>;
|
|
110
117
|
};
|
|
111
118
|
|
|
112
119
|
declare function useTreeNav(): TreeNavContextValue;
|
package/dist/tree-nav.js
CHANGED
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/TreeNav/TreeNav.context.ts","../src/components/TreeNav/TreeNode.tsx","../src/components/TreeNav/TreeNav.tsx"],"names":["createContext","useContext","jsxs","jsx","cn","useRef","useCallback","useEffect","useState","useMemo"],"mappings":"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","file":"chunk-7VLRHXLQ.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { TreeNavContextValue } from \"./TreeNav.types\";\n\nexport const TreeNavContext = createContext<TreeNavContextValue | null>(null);\n\nexport function useTreeNav(): TreeNavContextValue {\n const ctx = useContext(TreeNavContext);\n if (!ctx) {\n throw new Error(\"useTreeNav must be used within a <TreeNav> component\");\n }\n return ctx;\n}\n","import { useRef, useEffect, useCallback } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useTreeNav } from \"./TreeNav.context\";\nimport type { TreeNodeProps, TreeNodeData, DropPosition } from \"./TreeNav.types\";\n\nconst EXT_COLORS: Record<string, string> = {\n ts: \"#3178c6\",\n tsx: \"#3178c6\",\n js: \"#f7df1e\",\n jsx: \"#f7df1e\",\n php: \"#777bb4\",\n html: \"#e34c26\",\n htm: \"#e34c26\",\n css: \"#264de4\",\n json: \"#a1a1aa\",\n md: \"#71717a\",\n yaml: \"#cb171e\",\n yml: \"#cb171e\",\n};\n\nfunction FileIcon({ ext }: { ext?: string }) {\n const color = (ext && EXT_COLORS[ext.toLowerCase()]) || \"#71717a\";\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z\" stroke={color} strokeWidth=\"1.2\" />\n <path d=\"M9 1v4h4\" stroke={color} strokeWidth=\"1.2\" />\n </svg>\n );\n}\n\nfunction FolderIcon({ open }: { open: boolean }) {\n if (open) {\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z\" fill=\"#fbbf24\" />\n <path d=\"M1 6h13l-1.5 7.5H2.5L1 6z\" fill=\"#fbbf24\" opacity=\"0.7\" />\n </svg>\n );\n }\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z\" fill=\"#fbbf24\" />\n </svg>\n );\n}\n\nfunction ChevronIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"shrink-0 transition-transform duration-150\", open && \"rotate-90\")}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction isDescendantOf(\n nodes: TreeNodeData[],\n ancestorId: string,\n targetId: string,\n): boolean {\n function findNode(haystack: TreeNodeData[], id: string): TreeNodeData | undefined {\n for (const n of haystack) {\n if (n.id === id) return n;\n if (n.children) {\n const found = findNode(n.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n function hasDescendant(node: TreeNodeData, id: string): boolean {\n if (!node.children) return false;\n for (const child of node.children) {\n if (child.id === id) return true;\n if (hasDescendant(child, id)) return true;\n }\n return false;\n }\n\n const ancestor = findNode(nodes, ancestorId);\n return ancestor ? hasDescendant(ancestor, targetId) : false;\n}\n\nfunction computeDropPosition(\n e: React.DragEvent,\n isFolder: boolean,\n): DropPosition {\n const rect = e.currentTarget.getBoundingClientRect();\n const offsetY = e.clientY - rect.top;\n const third = rect.height / 3;\n\n if (offsetY < third) return \"before\";\n if (offsetY > third * 2) return \"after\";\n return isFolder ? \"inside\" : \"after\";\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function TreeNode({ node, depth }: TreeNodeProps) {\n const {\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n } = useTreeNav();\n\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n const isExpanded = expandedIds.includes(node.id);\n const isSelected = selectedId === node.id;\n const paddingLeft = depth * indentSize + 4;\n\n const isDragging = dragState.draggedNodeId === node.id;\n const isDropTarget = dragState.dropTargetId === node.id;\n const dropPosition = isDropTarget ? dragState.dropPosition : null;\n\n // Auto-expand timer for folders during drag\n const autoExpandTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearAutoExpand = useCallback(() => {\n if (autoExpandTimer.current) {\n clearTimeout(autoExpandTimer.current);\n autoExpandTimer.current = null;\n }\n }, []);\n\n useEffect(() => clearAutoExpand, [clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Click handlers\n // ---------------------------------------------------------------------------\n\n const handleClick = () => {\n if (node.disabled) return;\n if (isFolder) {\n toggle(node.id);\n }\n onSelect?.(node.id, node);\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n if (onNodeContextMenu) {\n e.preventDefault();\n onNodeContextMenu(e, node);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Drag source handlers\n // ---------------------------------------------------------------------------\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n e.dataTransfer.setData(\"text/plain\", node.id);\n e.dataTransfer.effectAllowed = \"move\";\n // Set state async so the browser captures the element for the drag ghost first\n requestAnimationFrame(() => {\n setDragState({ draggedNodeId: node.id, dropTargetId: null, dropPosition: null });\n });\n }, [node.id, setDragState]);\n\n const handleDragEnd = useCallback(() => {\n clearAutoExpand();\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [clearAutoExpand, setDragState]);\n\n // ---------------------------------------------------------------------------\n // Drop target handlers (on outer div)\n // ---------------------------------------------------------------------------\n\n const isExternalDrag = !dragState.draggedNodeId;\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n if (isExternalDrag) {\n // External drop accepted only when host opted in.\n if (!acceptExternalDrops) return;\n } else {\n const sourceId = dragState.draggedNodeId!;\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n e.dataTransfer.dropEffect = isExternalDrag ? \"copy\" : \"move\";\n\n const position = computeDropPosition(e, !!isFolder);\n\n // Auto-expand collapsed folders when hovering \"inside\"\n if (isFolder && !isExpanded && position === \"inside\") {\n if (!autoExpandTimer.current) {\n autoExpandTimer.current = setTimeout(() => {\n expandNode(node.id);\n autoExpandTimer.current = null;\n }, 500);\n }\n } else {\n clearAutoExpand();\n }\n\n if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {\n setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });\n }\n }, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n clearAutoExpand();\n if (dragState.dropTargetId === node.id) {\n setDragState({ ...dragState, dropTargetId: null, dropPosition: null });\n }\n }\n }, [dragState, node.id, setDragState, clearAutoExpand]);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n clearAutoExpand();\n\n const sourceId = dragState.draggedNodeId;\n const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);\n\n if (sourceId) {\n // Internal node move.\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n onNodeMove?.(sourceId, node.id, position);\n } else if (acceptExternalDrops) {\n // External drop — files, items from other components, etc.\n onExternalDrop?.(e, node, position);\n }\n\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n const canDrag = draggable && !node.disabled;\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <div\n data-react-fancy-tree-node=\"\"\n onDragOver={dropEnabled ? handleDragOver : undefined}\n onDragLeave={dropEnabled ? handleDragLeave : undefined}\n onDrop={dropEnabled ? handleDrop : undefined}\n >\n {/* Before drop indicator */}\n {isDropTarget && dropPosition === \"before\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"before\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n <button\n type=\"button\"\n draggable={canDrag}\n onDragStart={canDrag ? handleDragStart : undefined}\n onDragEnd={canDrag ? handleDragEnd : undefined}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n disabled={node.disabled}\n className={cn(\n \"flex w-full items-center gap-1 rounded-md py-0.5 text-left text-[13px] transition-colors\",\n isSelected\n ? \"bg-blue-500/15 text-blue-600 dark:text-blue-400\"\n : \"text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800\",\n node.disabled && \"pointer-events-none opacity-40\",\n isDragging && \"opacity-50\",\n // Grab affordance. By default (`dragCursor=\"none\"`) the row keeps its\n // normal click cursor on hover — no open-hand \"move me\" cursor before\n // any drag — and only shows the closed-hand `grabbing` cursor while a\n // drag is actually underway (mousedown/drag). `dragCursor=\"grab\"` opts\n // back into the always-on grab cursor for whole-row drag handles (#14).\n canDrag &&\n (dragCursor === \"grab\" ? \"cursor-grab active:cursor-grabbing\" : \"active:cursor-grabbing\"),\n isDropTarget && dropPosition === \"inside\" && \"bg-blue-500/10 ring-1 ring-blue-500/30 ring-inset\",\n )}\n style={{ paddingLeft }}\n >\n {isFolder && <ChevronIcon open={isExpanded} />}\n {!isFolder && <span className=\"w-3.5 shrink-0\" />}\n {showIcons && (\n node.icon ?? (isFolder\n ? <FolderIcon open={isExpanded} />\n : <FileIcon ext={node.ext ?? node.label.split(\".\").pop()} />)\n )}\n <span className=\"truncate\">{node.label}</span>\n </button>\n\n {/* After drop indicator */}\n {isDropTarget && dropPosition === \"after\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"after\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n {isFolder && isExpanded && node.children && (\n <div data-react-fancy-tree-node-children=\"\">\n {node.children.map((child) => (\n <TreeNode key={child.id} node={child} depth={depth + 1} />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nTreeNode.displayName = \"TreeNode\";\n","import { useMemo, useCallback, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { TreeNavContext } from \"./TreeNav.context\";\nimport { TreeNode } from \"./TreeNode\";\nimport type { TreeNavProps, TreeNodeData, DragState } from \"./TreeNav.types\";\n\nfunction collectFolderIds(nodes: TreeNodeData[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (node.children && node.children.length > 0) {\n ids.push(node.id);\n ids.push(...collectFolderIds(node.children));\n }\n }\n return ids;\n}\n\nconst EMPTY_DRAG_STATE: DragState = {\n draggedNodeId: null,\n dropTargetId: null,\n dropPosition: null,\n};\n\nfunction TreeNavRoot({\n nodes,\n selectedId,\n onSelect,\n onNodeContextMenu,\n draggable = false,\n onNodeMove,\n acceptExternalDrops = false,\n onExternalDrop,\n expandedIds: controlledExpanded,\n defaultExpandedIds,\n onExpandedChange,\n defaultExpandAll = false,\n indentSize = 16,\n showIcons = true,\n dragCursor = \"none\",\n className,\n}: TreeNavProps) {\n const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {\n if (defaultExpandedIds) return defaultExpandedIds;\n if (defaultExpandAll) return collectFolderIds(nodes);\n return [];\n });\n\n const isControlled = controlledExpanded !== undefined;\n const expandedIds = isControlled ? controlledExpanded : internalExpanded;\n\n const toggle = useCallback(\n (id: string) => {\n const next = expandedIds.includes(id)\n ? expandedIds.filter((v) => v !== id)\n : [...expandedIds, id];\n\n if (!isControlled) {\n setInternalExpanded(next);\n }\n onExpandedChange?.(next);\n },\n [expandedIds, isControlled, onExpandedChange],\n );\n\n const expandNode = useCallback(\n (id: string) => {\n if (expandedIds.includes(id)) return;\n const next = [...expandedIds, id];\n if (!isControlled) {\n setInternalExpanded(next);\n }\n onExpandedChange?.(next);\n },\n [expandedIds, isControlled, onExpandedChange],\n );\n\n const [dragState, setDragState] = useState<DragState>(EMPTY_DRAG_STATE);\n\n const handleDragEnd = useCallback(() => {\n setDragState(EMPTY_DRAG_STATE);\n }, []);\n\n const ctx = useMemo(\n () => ({\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n }),\n [selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode],\n );\n\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <TreeNavContext.Provider value={ctx}>\n <nav\n data-react-fancy-tree-nav=\"\"\n className={cn(\"flex flex-col gap-0.5 py-1 text-sm\", className)}\n onDragEnd={dropEnabled ? handleDragEnd : undefined}\n >\n {nodes.map((node) => (\n <TreeNode key={node.id} node={node} depth={0} />\n ))}\n </nav>\n </TreeNavContext.Provider>\n );\n}\n\nTreeNavRoot.displayName = \"TreeNav\";\n\nexport const TreeNav = Object.assign(TreeNavRoot, {\n Node: TreeNode,\n});\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/TreeNav/TreeNav.context.ts","../src/components/TreeNav/TreeNode.tsx","../src/components/TreeNav/TreeNav.tsx"],"names":["useCallback","jsx"],"mappings":"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","file":"chunk-NUPQJ5SJ.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { TreeNavContextValue } from \"./TreeNav.types\";\n\nexport const TreeNavContext = createContext<TreeNavContextValue | null>(null);\n\nexport function useTreeNav(): TreeNavContextValue {\n const ctx = useContext(TreeNavContext);\n if (!ctx) {\n throw new Error(\"useTreeNav must be used within a <TreeNav> component\");\n }\n return ctx;\n}\n","import { useRef, useEffect, useCallback } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useTreeNav } from \"./TreeNav.context\";\nimport type { TreeNodeProps, TreeNodeData, DropPosition } from \"./TreeNav.types\";\n\nconst EXT_COLORS: Record<string, string> = {\n ts: \"#3178c6\",\n tsx: \"#3178c6\",\n js: \"#f7df1e\",\n jsx: \"#f7df1e\",\n php: \"#777bb4\",\n html: \"#e34c26\",\n htm: \"#e34c26\",\n css: \"#264de4\",\n json: \"#a1a1aa\",\n md: \"#71717a\",\n yaml: \"#cb171e\",\n yml: \"#cb171e\",\n};\n\nfunction FileIcon({ ext }: { ext?: string }) {\n const color = (ext && EXT_COLORS[ext.toLowerCase()]) || \"#71717a\";\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z\" stroke={color} strokeWidth=\"1.2\" />\n <path d=\"M9 1v4h4\" stroke={color} strokeWidth=\"1.2\" />\n </svg>\n );\n}\n\nfunction FolderIcon({ open }: { open: boolean }) {\n if (open) {\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z\" fill=\"#fbbf24\" />\n <path d=\"M1 6h13l-1.5 7.5H2.5L1 6z\" fill=\"#fbbf24\" opacity=\"0.7\" />\n </svg>\n );\n }\n return (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" className=\"shrink-0\">\n <path d=\"M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z\" fill=\"#fbbf24\" />\n </svg>\n );\n}\n\nfunction ChevronIcon({ open }: { open: boolean }) {\n return (\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className={cn(\"shrink-0 transition-transform duration-150\", open && \"rotate-90\")}\n >\n <polyline points=\"9 18 15 12 9 6\" />\n </svg>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\nfunction isDescendantOf(\n nodes: TreeNodeData[],\n ancestorId: string,\n targetId: string,\n): boolean {\n function findNode(haystack: TreeNodeData[], id: string): TreeNodeData | undefined {\n for (const n of haystack) {\n if (n.id === id) return n;\n if (n.children) {\n const found = findNode(n.children, id);\n if (found) return found;\n }\n }\n return undefined;\n }\n\n function hasDescendant(node: TreeNodeData, id: string): boolean {\n if (!node.children) return false;\n for (const child of node.children) {\n if (child.id === id) return true;\n if (hasDescendant(child, id)) return true;\n }\n return false;\n }\n\n const ancestor = findNode(nodes, ancestorId);\n return ancestor ? hasDescendant(ancestor, targetId) : false;\n}\n\nfunction computeDropPosition(\n e: React.DragEvent,\n isFolder: boolean,\n): DropPosition {\n const rect = e.currentTarget.getBoundingClientRect();\n const offsetY = e.clientY - rect.top;\n const third = rect.height / 3;\n\n if (offsetY < third) return \"before\";\n if (offsetY > third * 2) return \"after\";\n return isFolder ? \"inside\" : \"after\";\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function TreeNode({ node, depth }: TreeNodeProps) {\n const {\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n } = useTreeNav();\n\n const isFolder = node.type === \"folder\" || (node.children && node.children.length > 0);\n const isExpanded = expandedIds.includes(node.id);\n const isSelected = selectedId === node.id;\n const paddingLeft = depth * indentSize + 4;\n\n const isDragging = dragState.draggedNodeId === node.id;\n const isDropTarget = dragState.dropTargetId === node.id;\n const dropPosition = isDropTarget ? dragState.dropPosition : null;\n\n // Auto-expand timer for folders during drag\n const autoExpandTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearAutoExpand = useCallback(() => {\n if (autoExpandTimer.current) {\n clearTimeout(autoExpandTimer.current);\n autoExpandTimer.current = null;\n }\n }, []);\n\n useEffect(() => clearAutoExpand, [clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Click handlers\n // ---------------------------------------------------------------------------\n\n const handleClick = () => {\n if (node.disabled) return;\n if (isFolder) {\n toggle(node.id);\n }\n onSelect?.(node.id, node);\n };\n\n const handleContextMenu = (e: React.MouseEvent) => {\n if (onNodeContextMenu) {\n e.preventDefault();\n onNodeContextMenu(e, node);\n }\n };\n\n // ---------------------------------------------------------------------------\n // Drag source handlers\n // ---------------------------------------------------------------------------\n\n const handleDragStart = useCallback((e: React.DragEvent) => {\n e.dataTransfer.setData(\"text/plain\", node.id);\n e.dataTransfer.effectAllowed = \"move\";\n // Set state async so the browser captures the element for the drag ghost first\n requestAnimationFrame(() => {\n setDragState({ draggedNodeId: node.id, dropTargetId: null, dropPosition: null });\n });\n }, [node.id, setDragState]);\n\n const handleDragEnd = useCallback(() => {\n clearAutoExpand();\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [clearAutoExpand, setDragState]);\n\n // ---------------------------------------------------------------------------\n // Drop target handlers (on outer div)\n // ---------------------------------------------------------------------------\n\n const isExternalDrag = !dragState.draggedNodeId;\n\n const handleDragOver = useCallback((e: React.DragEvent) => {\n if (isExternalDrag) {\n // External drop accepted only when host opted in.\n if (!acceptExternalDrops) return;\n } else {\n const sourceId = dragState.draggedNodeId!;\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n }\n\n e.preventDefault();\n e.stopPropagation();\n e.dataTransfer.dropEffect = isExternalDrag ? \"copy\" : \"move\";\n\n const position = computeDropPosition(e, !!isFolder);\n\n // Auto-expand collapsed folders when hovering \"inside\"\n if (isFolder && !isExpanded && position === \"inside\") {\n if (!autoExpandTimer.current) {\n autoExpandTimer.current = setTimeout(() => {\n expandNode(node.id);\n autoExpandTimer.current = null;\n }, 500);\n }\n } else {\n clearAutoExpand();\n }\n\n if (dragState.dropTargetId !== node.id || dragState.dropPosition !== position) {\n setDragState({ draggedNodeId: dragState.draggedNodeId, dropTargetId: node.id, dropPosition: position });\n }\n }, [dragState, isExternalDrag, acceptExternalDrops, node.id, isFolder, isExpanded, nodes, setDragState, expandNode, clearAutoExpand]);\n\n const handleDragLeave = useCallback((e: React.DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n clearAutoExpand();\n if (dragState.dropTargetId === node.id) {\n setDragState({ ...dragState, dropTargetId: null, dropPosition: null });\n }\n }\n }, [dragState, node.id, setDragState, clearAutoExpand]);\n\n const handleDrop = useCallback((e: React.DragEvent) => {\n e.preventDefault();\n e.stopPropagation();\n clearAutoExpand();\n\n const sourceId = dragState.draggedNodeId;\n const position = dragState.dropPosition ?? computeDropPosition(e, !!isFolder);\n\n if (sourceId) {\n // Internal node move.\n if (sourceId === node.id) return;\n if (isDescendantOf(nodes, sourceId, node.id)) return;\n onNodeMove?.(sourceId, node.id, position);\n } else if (acceptExternalDrops) {\n // External drop — files, items from other components, etc.\n onExternalDrop?.(e, node, position);\n }\n\n setDragState({ draggedNodeId: null, dropTargetId: null, dropPosition: null });\n }, [dragState, node, isFolder, nodes, onNodeMove, acceptExternalDrops, onExternalDrop, setDragState, clearAutoExpand]);\n\n // ---------------------------------------------------------------------------\n // Render\n // ---------------------------------------------------------------------------\n\n const canDrag = draggable && !node.disabled;\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <div\n data-react-fancy-tree-node=\"\"\n onDragOver={dropEnabled ? handleDragOver : undefined}\n onDragLeave={dropEnabled ? handleDragLeave : undefined}\n onDrop={dropEnabled ? handleDrop : undefined}\n >\n {/* Before drop indicator */}\n {isDropTarget && dropPosition === \"before\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"before\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n <button\n type=\"button\"\n draggable={canDrag}\n onDragStart={canDrag ? handleDragStart : undefined}\n onDragEnd={canDrag ? handleDragEnd : undefined}\n onClick={handleClick}\n onContextMenu={handleContextMenu}\n disabled={node.disabled}\n className={cn(\n \"flex w-full items-center gap-1 rounded-md py-0.5 text-left text-[13px] transition-colors\",\n isSelected\n ? \"bg-blue-500/15 text-blue-600 dark:text-blue-400\"\n : \"text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800\",\n node.disabled && \"pointer-events-none opacity-40\",\n isDragging && \"opacity-50\",\n // Grab affordance. By default (`dragCursor=\"none\"`) the row keeps its\n // normal click cursor on hover — no open-hand \"move me\" cursor before\n // any drag — and only shows the closed-hand `grabbing` cursor while a\n // drag is actually underway (mousedown/drag). `dragCursor=\"grab\"` opts\n // back into the always-on grab cursor for whole-row drag handles (#14).\n canDrag &&\n (dragCursor === \"grab\" ? \"cursor-grab active:cursor-grabbing\" : \"active:cursor-grabbing\"),\n isDropTarget && dropPosition === \"inside\" && \"bg-blue-500/10 ring-1 ring-blue-500/30 ring-inset\",\n )}\n style={{ paddingLeft }}\n >\n {isFolder && <ChevronIcon open={isExpanded} />}\n {!isFolder && <span className=\"w-3.5 shrink-0\" />}\n {showIcons && (\n node.icon ?? (isFolder\n ? <FolderIcon open={isExpanded} />\n : <FileIcon ext={node.ext ?? node.label.split(\".\").pop()} />)\n )}\n <span className=\"truncate\">{node.label}</span>\n </button>\n\n {/* After drop indicator */}\n {isDropTarget && dropPosition === \"after\" && (\n <div\n data-react-fancy-tree-drop-indicator=\"after\"\n className=\"pointer-events-none h-0.5 rounded-full bg-blue-500\"\n style={{ marginLeft: paddingLeft }}\n />\n )}\n\n {isFolder && isExpanded && node.children && (\n <div data-react-fancy-tree-node-children=\"\">\n {node.children.map((child) => (\n <TreeNode key={child.id} node={child} depth={depth + 1} />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nTreeNode.displayName = \"TreeNode\";\n","import { useMemo, useCallback, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { TreeNavContext } from \"./TreeNav.context\";\nimport { TreeNode } from \"./TreeNode\";\nimport type { TreeNavProps, TreeNodeData, DragState } from \"./TreeNav.types\";\n\nfunction collectFolderIds(nodes: TreeNodeData[]): string[] {\n const ids: string[] = [];\n for (const node of nodes) {\n if (node.children && node.children.length > 0) {\n ids.push(node.id);\n ids.push(...collectFolderIds(node.children));\n }\n }\n return ids;\n}\n\nconst EMPTY_DRAG_STATE: DragState = {\n draggedNodeId: null,\n dropTargetId: null,\n dropPosition: null,\n};\n\nfunction TreeNavRoot({\n nodes,\n selectedId,\n onSelect,\n onNodeContextMenu,\n draggable = false,\n onNodeMove,\n acceptExternalDrops = false,\n onExternalDrop,\n expandedIds: controlledExpanded,\n defaultExpandedIds,\n onExpandedChange,\n defaultExpandAll = false,\n indentSize = 16,\n showIcons = true,\n dragCursor = \"none\",\n className,\n}: TreeNavProps) {\n const [internalExpanded, setInternalExpanded] = useState<string[]>(() => {\n if (defaultExpandedIds) return defaultExpandedIds;\n if (defaultExpandAll) return collectFolderIds(nodes);\n return [];\n });\n\n const isControlled = controlledExpanded !== undefined;\n const expandedIds = isControlled ? controlledExpanded : internalExpanded;\n\n const toggle = useCallback(\n (id: string) => {\n const next = expandedIds.includes(id)\n ? expandedIds.filter((v) => v !== id)\n : [...expandedIds, id];\n\n if (!isControlled) {\n setInternalExpanded(next);\n }\n onExpandedChange?.(next);\n },\n [expandedIds, isControlled, onExpandedChange],\n );\n\n const expandNode = useCallback(\n (id: string) => {\n if (expandedIds.includes(id)) return;\n const next = [...expandedIds, id];\n if (!isControlled) {\n setInternalExpanded(next);\n }\n onExpandedChange?.(next);\n },\n [expandedIds, isControlled, onExpandedChange],\n );\n\n const [dragState, setDragState] = useState<DragState>(EMPTY_DRAG_STATE);\n\n const handleDragEnd = useCallback(() => {\n setDragState(EMPTY_DRAG_STATE);\n }, []);\n\n const ctx = useMemo(\n () => ({\n selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, setDragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode,\n }),\n [selectedId, onSelect, onNodeContextMenu, expandedIds, toggle, indentSize, showIcons,\n draggable, dragCursor, dragState, onNodeMove, acceptExternalDrops, onExternalDrop, nodes, expandNode],\n );\n\n const dropEnabled = draggable || acceptExternalDrops;\n\n return (\n <TreeNavContext.Provider value={ctx}>\n <nav\n data-react-fancy-tree-nav=\"\"\n className={cn(\"flex flex-col gap-0.5 py-1 text-sm\", className)}\n onDragEnd={dropEnabled ? handleDragEnd : undefined}\n >\n {nodes.map((node) => (\n <TreeNode key={node.id} node={node} depth={0} />\n ))}\n </nav>\n </TreeNavContext.Provider>\n );\n}\n\nTreeNavRoot.displayName = \"TreeNav\";\n\nexport const TreeNav = Object.assign(TreeNavRoot, {\n Node: TreeNode,\n});\n"]}
|