@ii_elif_ii/ui-node-tree 1.0.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/README.md +123 -0
- package/dist/index.cjs +880 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +87 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +120 -0
- package/dist/index.d.ts +120 -0
- package/dist/index.js +843 -0
- package/dist/index.js.map +1 -0
- package/dist/node-tree.css +87 -0
- package/dist/node-tree.css.map +1 -0
- package/dist/node-tree.d.mts +2 -0
- package/dist/node-tree.d.ts +2 -0
- package/package.json +60 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
NodeTree: () => NodeTree
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/components/node-tree.tsx
|
|
38
|
+
var React2 = __toESM(require("react"));
|
|
39
|
+
|
|
40
|
+
// src/utils/cn.ts
|
|
41
|
+
var import_clsx = require("clsx");
|
|
42
|
+
function cn(...inputs) {
|
|
43
|
+
return (0, import_clsx.clsx)(inputs);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/components/tree-connections.tsx
|
|
47
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
48
|
+
function TreeConnections({
|
|
49
|
+
layoutState,
|
|
50
|
+
debug,
|
|
51
|
+
strokeColor,
|
|
52
|
+
strokeWidth,
|
|
53
|
+
opacity,
|
|
54
|
+
className
|
|
55
|
+
}) {
|
|
56
|
+
if (!layoutState.svgBounds) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
60
|
+
"svg",
|
|
61
|
+
{
|
|
62
|
+
className: cn("unt-tree-connections", className),
|
|
63
|
+
width: layoutState.svgBounds.width,
|
|
64
|
+
height: layoutState.svgBounds.height,
|
|
65
|
+
viewBox: `0 0 ${layoutState.svgBounds.width} ${layoutState.svgBounds.height}`,
|
|
66
|
+
style: {
|
|
67
|
+
left: layoutState.svgBounds.offsetX,
|
|
68
|
+
top: layoutState.svgBounds.offsetY,
|
|
69
|
+
opacity
|
|
70
|
+
},
|
|
71
|
+
children: layoutState.segments.map((segment) => {
|
|
72
|
+
const debugPalette = [
|
|
73
|
+
"#22d3ee",
|
|
74
|
+
"#a855f7",
|
|
75
|
+
"#f59e0b",
|
|
76
|
+
"#10b981",
|
|
77
|
+
"#f97316",
|
|
78
|
+
"#38bdf8"
|
|
79
|
+
];
|
|
80
|
+
const lineColor = debug ? debugPalette[segment.colorIndex % debugPalette.length] : strokeColor;
|
|
81
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
82
|
+
"line",
|
|
83
|
+
{
|
|
84
|
+
x1: segment.x1 - layoutState.svgBounds.offsetX,
|
|
85
|
+
y1: segment.y1 - layoutState.svgBounds.offsetY,
|
|
86
|
+
x2: segment.x2 - layoutState.svgBounds.offsetX,
|
|
87
|
+
y2: segment.y2 - layoutState.svgBounds.offsetY,
|
|
88
|
+
className: "node-line",
|
|
89
|
+
style: {
|
|
90
|
+
strokeDasharray: segment.length,
|
|
91
|
+
strokeDashoffset: segment.length,
|
|
92
|
+
animationDelay: `${segment.delay}s`,
|
|
93
|
+
animationDuration: `${segment.duration}s`
|
|
94
|
+
},
|
|
95
|
+
stroke: lineColor,
|
|
96
|
+
strokeWidth,
|
|
97
|
+
strokeLinecap: "round"
|
|
98
|
+
},
|
|
99
|
+
`${segment.x1}-${segment.y1}-${segment.x2}-${segment.y2}-${segment.delay}`
|
|
100
|
+
);
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/components/tree-renderer.tsx
|
|
107
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
108
|
+
function axisToFlexAlign(axis) {
|
|
109
|
+
if (axis === "start") {
|
|
110
|
+
return "flex-start";
|
|
111
|
+
}
|
|
112
|
+
if (axis === "end") {
|
|
113
|
+
return "flex-end";
|
|
114
|
+
}
|
|
115
|
+
return "center";
|
|
116
|
+
}
|
|
117
|
+
function axisToFlexJustify(axis) {
|
|
118
|
+
if (axis === "start") {
|
|
119
|
+
return "flex-start";
|
|
120
|
+
}
|
|
121
|
+
if (axis === "end") {
|
|
122
|
+
return "flex-end";
|
|
123
|
+
}
|
|
124
|
+
return "center";
|
|
125
|
+
}
|
|
126
|
+
function NodeFrame({ node, className, onRef, children, ...props }) {
|
|
127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
128
|
+
"div",
|
|
129
|
+
{
|
|
130
|
+
ref: (element) => {
|
|
131
|
+
onRef(node.id, element);
|
|
132
|
+
},
|
|
133
|
+
className: cn("unt-tree-node-hit", className),
|
|
134
|
+
"data-nodeframe": true,
|
|
135
|
+
"data-viewport-no-pan": true,
|
|
136
|
+
...props,
|
|
137
|
+
children
|
|
138
|
+
}
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
function renderTreeNode({
|
|
142
|
+
node,
|
|
143
|
+
index,
|
|
144
|
+
parentId,
|
|
145
|
+
depth,
|
|
146
|
+
path,
|
|
147
|
+
flowDown,
|
|
148
|
+
alignX,
|
|
149
|
+
alignY,
|
|
150
|
+
gap,
|
|
151
|
+
debug,
|
|
152
|
+
layoutState,
|
|
153
|
+
doneNodes,
|
|
154
|
+
registerNode,
|
|
155
|
+
nodeFrameClassName,
|
|
156
|
+
nodeFrameStyle
|
|
157
|
+
}) {
|
|
158
|
+
const stackUnder = !flowDown && node.children?.layout === "stack";
|
|
159
|
+
if (path.has(node.id)) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
path.add(node.id);
|
|
163
|
+
const childrenLayoutIsStack = node.children?.layout === "stack" || !flowDown;
|
|
164
|
+
const childCount = node.children?.nodes.length ?? 0;
|
|
165
|
+
const isLeaf = childCount === 0;
|
|
166
|
+
const pathIds = [...path];
|
|
167
|
+
const childrenContent = node.children?.nodes && node.children.nodes.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
168
|
+
"div",
|
|
169
|
+
{
|
|
170
|
+
className: "unt-tree-children",
|
|
171
|
+
style: {
|
|
172
|
+
display: "flex",
|
|
173
|
+
flexShrink: 0,
|
|
174
|
+
flexDirection: childrenLayoutIsStack ? "column" : "row",
|
|
175
|
+
alignItems: axisToFlexAlign(childrenLayoutIsStack ? alignX : alignY),
|
|
176
|
+
justifyContent: axisToFlexJustify(childrenLayoutIsStack ? alignY : alignX),
|
|
177
|
+
gap,
|
|
178
|
+
marginTop: flowDown || stackUnder ? gap : 0,
|
|
179
|
+
marginLeft: flowDown ? node.children?.layout === "stack" ? gap : 0 : stackUnder ? gap / 2 : gap
|
|
180
|
+
},
|
|
181
|
+
children: node.children.nodes.map(
|
|
182
|
+
(child, childIndex) => renderTreeNode({
|
|
183
|
+
node: child,
|
|
184
|
+
index: childIndex,
|
|
185
|
+
parentId: node.id,
|
|
186
|
+
depth: depth + 1,
|
|
187
|
+
path,
|
|
188
|
+
flowDown,
|
|
189
|
+
alignX,
|
|
190
|
+
alignY,
|
|
191
|
+
gap,
|
|
192
|
+
debug,
|
|
193
|
+
layoutState,
|
|
194
|
+
doneNodes,
|
|
195
|
+
registerNode,
|
|
196
|
+
nodeFrameClassName,
|
|
197
|
+
nodeFrameStyle
|
|
198
|
+
})
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
) : null;
|
|
202
|
+
path.delete(node.id);
|
|
203
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
204
|
+
"div",
|
|
205
|
+
{
|
|
206
|
+
className: "unt-tree-node-wrap",
|
|
207
|
+
style: {
|
|
208
|
+
display: "flex",
|
|
209
|
+
position: "relative",
|
|
210
|
+
flexDirection: flowDown || stackUnder ? "column" : "row",
|
|
211
|
+
alignItems: axisToFlexAlign(flowDown ? alignX : alignY),
|
|
212
|
+
justifyContent: axisToFlexJustify(flowDown ? alignY : alignX)
|
|
213
|
+
},
|
|
214
|
+
children: [
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
216
|
+
NodeFrame,
|
|
217
|
+
{
|
|
218
|
+
node,
|
|
219
|
+
className: cn("node-enter unt-tree-node-frame", nodeFrameClassName),
|
|
220
|
+
style: {
|
|
221
|
+
justifyContent: axisToFlexJustify(alignX),
|
|
222
|
+
animationDuration: `${layoutState.nodeAnimDuration}s`,
|
|
223
|
+
animationDelay: `${layoutState.nodeDelays.get(node.id) ?? depth * 0.08 + index * 0.04}s`,
|
|
224
|
+
...nodeFrameStyle
|
|
225
|
+
},
|
|
226
|
+
onRef: registerNode,
|
|
227
|
+
children: [
|
|
228
|
+
debug ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
229
|
+
"div",
|
|
230
|
+
{
|
|
231
|
+
className: cn(
|
|
232
|
+
"unt-tree-debug-badge",
|
|
233
|
+
`unt-tree-debug-badge--${depth % 6}`
|
|
234
|
+
),
|
|
235
|
+
children: [
|
|
236
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: `DEPTH: ${depth}` }),
|
|
237
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: `PARENT-ID: ${parentId ?? "root"}` }),
|
|
238
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: `NODE-ID: ${node.id}` }),
|
|
239
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: `C-LAYOUT: ${node.children?.layout ?? "N/A"}` })
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
) : null,
|
|
243
|
+
node.render({
|
|
244
|
+
node,
|
|
245
|
+
index,
|
|
246
|
+
depth,
|
|
247
|
+
parentId,
|
|
248
|
+
path: pathIds,
|
|
249
|
+
isLeaf,
|
|
250
|
+
childCount,
|
|
251
|
+
isNodeAnimationDone: doneNodes.has(node.id)
|
|
252
|
+
})
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
),
|
|
256
|
+
childrenContent
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
`${node.id}-${index}`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
function TreeRenderer({
|
|
263
|
+
nodeTree,
|
|
264
|
+
rootLayout,
|
|
265
|
+
flowDown,
|
|
266
|
+
alignX,
|
|
267
|
+
alignY,
|
|
268
|
+
gap,
|
|
269
|
+
debug,
|
|
270
|
+
layoutState,
|
|
271
|
+
doneNodes,
|
|
272
|
+
registerNode,
|
|
273
|
+
rendererClassName,
|
|
274
|
+
nodeFrameClassName,
|
|
275
|
+
nodeFrameStyle
|
|
276
|
+
}) {
|
|
277
|
+
const rootLayoutRow = rootLayout === "row";
|
|
278
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
279
|
+
"section",
|
|
280
|
+
{
|
|
281
|
+
className: cn("unt-tree-renderer", rendererClassName),
|
|
282
|
+
style: {
|
|
283
|
+
gap,
|
|
284
|
+
display: "flex",
|
|
285
|
+
width: "100%",
|
|
286
|
+
overflow: "visible",
|
|
287
|
+
position: "relative",
|
|
288
|
+
zIndex: 10,
|
|
289
|
+
flexDirection: rootLayoutRow ? "row" : "column",
|
|
290
|
+
alignItems: axisToFlexAlign(rootLayoutRow ? alignY : alignX),
|
|
291
|
+
justifyContent: axisToFlexJustify(rootLayoutRow ? alignX : alignY)
|
|
292
|
+
},
|
|
293
|
+
children: nodeTree.map(
|
|
294
|
+
(node, index) => renderTreeNode({
|
|
295
|
+
node,
|
|
296
|
+
index,
|
|
297
|
+
depth: 0,
|
|
298
|
+
path: /* @__PURE__ */ new Set(),
|
|
299
|
+
flowDown,
|
|
300
|
+
alignX,
|
|
301
|
+
alignY,
|
|
302
|
+
gap,
|
|
303
|
+
debug,
|
|
304
|
+
layoutState,
|
|
305
|
+
doneNodes,
|
|
306
|
+
registerNode,
|
|
307
|
+
nodeFrameClassName,
|
|
308
|
+
nodeFrameStyle
|
|
309
|
+
})
|
|
310
|
+
)
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/hooks/use-node-tree-layout.ts
|
|
316
|
+
var React = __toESM(require("react"));
|
|
317
|
+
var EMPTY_LAYOUT = {
|
|
318
|
+
segments: [],
|
|
319
|
+
nodeDelays: /* @__PURE__ */ new Map(),
|
|
320
|
+
nodeAnimDuration: 0.42,
|
|
321
|
+
animationTotal: 0,
|
|
322
|
+
svgBounds: null
|
|
323
|
+
};
|
|
324
|
+
function collectEdges(nodes) {
|
|
325
|
+
const edges = [];
|
|
326
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
327
|
+
const visit = (node) => {
|
|
328
|
+
if (visiting.has(node.id)) {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
visiting.add(node.id);
|
|
332
|
+
if (node.children?.nodes && node.children.nodes.length > 0) {
|
|
333
|
+
node.children.nodes.forEach((child, index) => {
|
|
334
|
+
const key = `${node.id}=>${child.id}`;
|
|
335
|
+
edges.push({
|
|
336
|
+
key,
|
|
337
|
+
from: node.id,
|
|
338
|
+
to: child.id,
|
|
339
|
+
index,
|
|
340
|
+
count: node.children?.nodes.length ?? 1
|
|
341
|
+
});
|
|
342
|
+
visit(child);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
visiting.delete(node.id);
|
|
346
|
+
};
|
|
347
|
+
nodes.forEach(visit);
|
|
348
|
+
return edges;
|
|
349
|
+
}
|
|
350
|
+
function collectDescendants(nodes) {
|
|
351
|
+
const map = /* @__PURE__ */ new Map();
|
|
352
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
353
|
+
const visit = (node) => {
|
|
354
|
+
if (visiting.has(node.id)) {
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
visiting.add(node.id);
|
|
358
|
+
const descendants = [];
|
|
359
|
+
node.children?.nodes.forEach((child) => {
|
|
360
|
+
descendants.push(child.id);
|
|
361
|
+
descendants.push(...visit(child));
|
|
362
|
+
});
|
|
363
|
+
map.set(node.id, descendants);
|
|
364
|
+
visiting.delete(node.id);
|
|
365
|
+
return descendants;
|
|
366
|
+
};
|
|
367
|
+
nodes.forEach(visit);
|
|
368
|
+
return map;
|
|
369
|
+
}
|
|
370
|
+
function useNodeTreeLayout({
|
|
371
|
+
nodeTree,
|
|
372
|
+
direction,
|
|
373
|
+
gap,
|
|
374
|
+
padding,
|
|
375
|
+
animationSpeed,
|
|
376
|
+
debug,
|
|
377
|
+
containerRef,
|
|
378
|
+
nodeRefs
|
|
379
|
+
}) {
|
|
380
|
+
const [layoutState, setLayoutState] = React.useState(EMPTY_LAYOUT);
|
|
381
|
+
const [doneNodes, setDoneNodes] = React.useState(
|
|
382
|
+
() => /* @__PURE__ */ new Set()
|
|
383
|
+
);
|
|
384
|
+
const doneNodesRef = React.useRef(/* @__PURE__ */ new Set());
|
|
385
|
+
const edges = React.useMemo(() => collectEdges(nodeTree), [nodeTree]);
|
|
386
|
+
const descendantMap = React.useMemo(
|
|
387
|
+
() => collectDescendants(nodeTree),
|
|
388
|
+
[nodeTree]
|
|
389
|
+
);
|
|
390
|
+
const totalAnimationSec = Math.max(0.1, animationSpeed / 1e3);
|
|
391
|
+
const drawConnections = React.useCallback(() => {
|
|
392
|
+
const container = containerRef.current;
|
|
393
|
+
if (!container) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const flowDown = direction === "down";
|
|
397
|
+
const getRelativeRect = (element) => {
|
|
398
|
+
let left = 0;
|
|
399
|
+
let top = 0;
|
|
400
|
+
let current = element;
|
|
401
|
+
while (current && current !== container) {
|
|
402
|
+
left += current.offsetLeft;
|
|
403
|
+
top += current.offsetTop;
|
|
404
|
+
current = current.offsetParent;
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
left,
|
|
408
|
+
top,
|
|
409
|
+
right: left + element.offsetWidth,
|
|
410
|
+
bottom: top + element.offsetHeight,
|
|
411
|
+
width: element.offsetWidth,
|
|
412
|
+
height: element.offsetHeight
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
const rectMap = /* @__PURE__ */ new Map();
|
|
416
|
+
nodeRefs.current.forEach((el, id) => {
|
|
417
|
+
rectMap.set(id, getRelativeRect(el));
|
|
418
|
+
});
|
|
419
|
+
const nextSegments = [];
|
|
420
|
+
const nextNodeDelays = /* @__PURE__ */ new Map();
|
|
421
|
+
const baseSecondsPerPixel = 1 / 900;
|
|
422
|
+
const baseNodeAnimDuration = 0.42;
|
|
423
|
+
const edgeColorIndex = debug ? /* @__PURE__ */ new Map() : null;
|
|
424
|
+
const edgeData = /* @__PURE__ */ new Map();
|
|
425
|
+
edges.forEach((edge) => {
|
|
426
|
+
const fromRect = rectMap.get(edge.from);
|
|
427
|
+
const toRect = rectMap.get(edge.to);
|
|
428
|
+
if (!fromRect || !toRect) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const fromX = flowDown ? fromRect.left + fromRect.width / 2 : fromRect.right;
|
|
432
|
+
const fromY = flowDown ? fromRect.bottom : fromRect.top + fromRect.height / 2;
|
|
433
|
+
const toX = flowDown ? toRect.left + toRect.width / 2 : toRect.left;
|
|
434
|
+
const toY = flowDown ? toRect.top : toRect.top + toRect.height / 2;
|
|
435
|
+
edgeData.set(edge.key, {
|
|
436
|
+
edge,
|
|
437
|
+
fromX,
|
|
438
|
+
fromY,
|
|
439
|
+
fromBottom: fromRect.bottom,
|
|
440
|
+
fromCenterX: fromRect.left + fromRect.width / 2,
|
|
441
|
+
toX,
|
|
442
|
+
toY,
|
|
443
|
+
toLeft: toRect.left,
|
|
444
|
+
toCenterY: toRect.top + toRect.height / 2
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
const pushSegment = (x1, y1, x2, y2, depth, delay, colorIndex, order) => {
|
|
448
|
+
const length = Math.hypot(x2 - x1, y2 - y1);
|
|
449
|
+
const duration = Math.max(0.05, length * baseSecondsPerPixel);
|
|
450
|
+
nextSegments.push({
|
|
451
|
+
x1,
|
|
452
|
+
y1,
|
|
453
|
+
x2,
|
|
454
|
+
y2,
|
|
455
|
+
length,
|
|
456
|
+
depth,
|
|
457
|
+
delay,
|
|
458
|
+
duration,
|
|
459
|
+
colorIndex,
|
|
460
|
+
order
|
|
461
|
+
});
|
|
462
|
+
return duration;
|
|
463
|
+
};
|
|
464
|
+
const visit = (node, depth, nodeDelay) => {
|
|
465
|
+
const existing = nextNodeDelays.get(node.id) ?? 0;
|
|
466
|
+
const resolvedDelay = Math.max(existing, nodeDelay);
|
|
467
|
+
nextNodeDelays.set(node.id, resolvedDelay);
|
|
468
|
+
const childEdges = node.children?.nodes.map((child) => edgeData.get(`${node.id}=>${child.id}`)).filter((edge) => Boolean(edge)) ?? [];
|
|
469
|
+
const stackLayout = node.children?.layout === "stack";
|
|
470
|
+
const descendantIds = flowDown && stackLayout ? descendantMap.get(node.id) ?? [] : [];
|
|
471
|
+
const descendantLefts = flowDown && stackLayout ? descendantIds.map((id) => rectMap.get(id)).filter((rect) => Boolean(rect)).map((rect) => rect.left) : [];
|
|
472
|
+
const descendantMinLeft = descendantLefts.length > 0 ? Math.min(...descendantLefts) : void 0;
|
|
473
|
+
const gutterX = flowDown && stackLayout ? (descendantMinLeft ?? (childEdges.length > 0 ? Math.min(...childEdges.map((edge) => edge.toLeft)) : 0)) - gap / 2 : 0;
|
|
474
|
+
const gutterXRight = !flowDown && stackLayout ? (childEdges.length > 0 ? Math.min(...childEdges.map((edge) => edge.toLeft)) : 0) - gap / 2 : 0;
|
|
475
|
+
const orderedChildren = node.children?.nodes.map((child) => {
|
|
476
|
+
const edge = edgeData.get(`${node.id}=>${child.id}`);
|
|
477
|
+
if (!edge) {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
const dx = edge.toX - edge.fromX;
|
|
481
|
+
const dy = edge.toY - edge.fromY;
|
|
482
|
+
const length = Math.hypot(dx, dy);
|
|
483
|
+
return { child, edge, length, toX: edge.toX };
|
|
484
|
+
}).filter(
|
|
485
|
+
(entry) => Boolean(entry)
|
|
486
|
+
) ?? [];
|
|
487
|
+
if (debug) {
|
|
488
|
+
orderedChildren.sort((a, b) => {
|
|
489
|
+
if (a.length !== b.length) {
|
|
490
|
+
return a.length - b.length;
|
|
491
|
+
}
|
|
492
|
+
return a.toX - b.toX;
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
orderedChildren.forEach((entry, index) => {
|
|
496
|
+
const { child, edge } = entry;
|
|
497
|
+
const edgeKey = edge.edge.key;
|
|
498
|
+
let colorIndex = 0;
|
|
499
|
+
if (edgeColorIndex) {
|
|
500
|
+
if (!edgeColorIndex.has(edgeKey)) {
|
|
501
|
+
edgeColorIndex.set(edgeKey, edgeColorIndex.size);
|
|
502
|
+
}
|
|
503
|
+
colorIndex = edgeColorIndex.get(edgeKey) ?? 0;
|
|
504
|
+
}
|
|
505
|
+
const order = orderedChildren.length - 1 - index;
|
|
506
|
+
const edgeDelay = resolvedDelay + baseNodeAnimDuration + index * 0.04;
|
|
507
|
+
let totalDuration = 0;
|
|
508
|
+
if (flowDown && stackLayout) {
|
|
509
|
+
const baseDrop = Math.max(12, gap / 2);
|
|
510
|
+
const targetY = edge.toCenterY;
|
|
511
|
+
const midY = edge.fromY + Math.min(baseDrop, (targetY - edge.fromY) * 0.6);
|
|
512
|
+
totalDuration += pushSegment(
|
|
513
|
+
edge.fromX,
|
|
514
|
+
edge.fromY,
|
|
515
|
+
edge.fromX,
|
|
516
|
+
midY,
|
|
517
|
+
depth,
|
|
518
|
+
edgeDelay,
|
|
519
|
+
colorIndex,
|
|
520
|
+
order
|
|
521
|
+
);
|
|
522
|
+
totalDuration += pushSegment(
|
|
523
|
+
edge.fromX,
|
|
524
|
+
midY,
|
|
525
|
+
gutterX,
|
|
526
|
+
midY,
|
|
527
|
+
depth,
|
|
528
|
+
edgeDelay + totalDuration,
|
|
529
|
+
colorIndex,
|
|
530
|
+
order
|
|
531
|
+
);
|
|
532
|
+
totalDuration += pushSegment(
|
|
533
|
+
gutterX,
|
|
534
|
+
midY,
|
|
535
|
+
gutterX,
|
|
536
|
+
targetY,
|
|
537
|
+
depth,
|
|
538
|
+
edgeDelay + totalDuration,
|
|
539
|
+
colorIndex,
|
|
540
|
+
order
|
|
541
|
+
);
|
|
542
|
+
totalDuration += pushSegment(
|
|
543
|
+
gutterX,
|
|
544
|
+
targetY,
|
|
545
|
+
edge.toLeft,
|
|
546
|
+
targetY,
|
|
547
|
+
depth,
|
|
548
|
+
edgeDelay + totalDuration,
|
|
549
|
+
colorIndex,
|
|
550
|
+
order
|
|
551
|
+
);
|
|
552
|
+
} else if (!flowDown && stackLayout) {
|
|
553
|
+
const targetY = edge.toCenterY;
|
|
554
|
+
const baseDrop = Math.max(12, gap / 2);
|
|
555
|
+
const midY = edge.fromBottom + Math.min(baseDrop, (targetY - edge.fromBottom) * 0.6);
|
|
556
|
+
totalDuration += pushSegment(
|
|
557
|
+
edge.fromCenterX,
|
|
558
|
+
edge.fromBottom,
|
|
559
|
+
edge.fromCenterX,
|
|
560
|
+
midY,
|
|
561
|
+
depth,
|
|
562
|
+
edgeDelay,
|
|
563
|
+
colorIndex,
|
|
564
|
+
order
|
|
565
|
+
);
|
|
566
|
+
totalDuration += pushSegment(
|
|
567
|
+
edge.fromCenterX,
|
|
568
|
+
midY,
|
|
569
|
+
gutterXRight,
|
|
570
|
+
midY,
|
|
571
|
+
depth,
|
|
572
|
+
edgeDelay + totalDuration,
|
|
573
|
+
colorIndex,
|
|
574
|
+
order
|
|
575
|
+
);
|
|
576
|
+
totalDuration += pushSegment(
|
|
577
|
+
gutterXRight,
|
|
578
|
+
midY,
|
|
579
|
+
gutterXRight,
|
|
580
|
+
targetY,
|
|
581
|
+
depth,
|
|
582
|
+
edgeDelay + totalDuration,
|
|
583
|
+
colorIndex,
|
|
584
|
+
order
|
|
585
|
+
);
|
|
586
|
+
totalDuration += pushSegment(
|
|
587
|
+
gutterXRight,
|
|
588
|
+
targetY,
|
|
589
|
+
edge.toLeft,
|
|
590
|
+
targetY,
|
|
591
|
+
depth,
|
|
592
|
+
edgeDelay + totalDuration,
|
|
593
|
+
colorIndex,
|
|
594
|
+
order
|
|
595
|
+
);
|
|
596
|
+
} else if (flowDown) {
|
|
597
|
+
const midY = edge.fromY + (edge.toY - edge.fromY) * 0.5;
|
|
598
|
+
totalDuration += pushSegment(
|
|
599
|
+
edge.fromX,
|
|
600
|
+
edge.fromY,
|
|
601
|
+
edge.fromX,
|
|
602
|
+
midY,
|
|
603
|
+
depth,
|
|
604
|
+
edgeDelay,
|
|
605
|
+
colorIndex,
|
|
606
|
+
order
|
|
607
|
+
);
|
|
608
|
+
totalDuration += pushSegment(
|
|
609
|
+
edge.fromX,
|
|
610
|
+
midY,
|
|
611
|
+
edge.toX,
|
|
612
|
+
midY,
|
|
613
|
+
depth,
|
|
614
|
+
edgeDelay + totalDuration,
|
|
615
|
+
colorIndex,
|
|
616
|
+
order
|
|
617
|
+
);
|
|
618
|
+
totalDuration += pushSegment(
|
|
619
|
+
edge.toX,
|
|
620
|
+
midY,
|
|
621
|
+
edge.toX,
|
|
622
|
+
edge.toY,
|
|
623
|
+
depth,
|
|
624
|
+
edgeDelay + totalDuration,
|
|
625
|
+
colorIndex,
|
|
626
|
+
order
|
|
627
|
+
);
|
|
628
|
+
} else {
|
|
629
|
+
const midX = edge.fromX + (edge.toX - edge.fromX) * 0.5;
|
|
630
|
+
totalDuration += pushSegment(
|
|
631
|
+
edge.fromX,
|
|
632
|
+
edge.fromY,
|
|
633
|
+
midX,
|
|
634
|
+
edge.fromY,
|
|
635
|
+
depth,
|
|
636
|
+
edgeDelay,
|
|
637
|
+
colorIndex,
|
|
638
|
+
order
|
|
639
|
+
);
|
|
640
|
+
totalDuration += pushSegment(
|
|
641
|
+
midX,
|
|
642
|
+
edge.fromY,
|
|
643
|
+
midX,
|
|
644
|
+
edge.toY,
|
|
645
|
+
depth,
|
|
646
|
+
edgeDelay + totalDuration,
|
|
647
|
+
colorIndex,
|
|
648
|
+
order
|
|
649
|
+
);
|
|
650
|
+
totalDuration += pushSegment(
|
|
651
|
+
midX,
|
|
652
|
+
edge.toY,
|
|
653
|
+
edge.toX,
|
|
654
|
+
edge.toY,
|
|
655
|
+
depth,
|
|
656
|
+
edgeDelay + totalDuration,
|
|
657
|
+
colorIndex,
|
|
658
|
+
order
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
visit(child, depth + 1, edgeDelay + totalDuration);
|
|
662
|
+
});
|
|
663
|
+
};
|
|
664
|
+
nodeTree.forEach((node) => visit(node, 0, 0));
|
|
665
|
+
if (nextSegments.length === 0) {
|
|
666
|
+
setLayoutState((prev) => ({
|
|
667
|
+
...prev,
|
|
668
|
+
segments: [],
|
|
669
|
+
svgBounds: null,
|
|
670
|
+
animationTotal: 0
|
|
671
|
+
}));
|
|
672
|
+
setDoneNodes(/* @__PURE__ */ new Set());
|
|
673
|
+
doneNodesRef.current = /* @__PURE__ */ new Set();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
let minX = Infinity;
|
|
677
|
+
let minY = Infinity;
|
|
678
|
+
let maxX = -Infinity;
|
|
679
|
+
let maxY = -Infinity;
|
|
680
|
+
nextSegments.forEach((segment) => {
|
|
681
|
+
minX = Math.min(minX, segment.x1, segment.x2);
|
|
682
|
+
minY = Math.min(minY, segment.y1, segment.y2);
|
|
683
|
+
maxX = Math.max(maxX, segment.x1, segment.x2);
|
|
684
|
+
maxY = Math.max(maxY, segment.y1, segment.y2);
|
|
685
|
+
});
|
|
686
|
+
if (!Number.isFinite(minX) || !Number.isFinite(minY) || !Number.isFinite(maxX) || !Number.isFinite(maxY)) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const width = Math.max(1, maxX - minX + padding * 2);
|
|
690
|
+
const height = Math.max(1, maxY - minY + padding * 2);
|
|
691
|
+
const offsetX = minX - padding;
|
|
692
|
+
const offsetY = minY - padding;
|
|
693
|
+
const maxLineEnd = Math.max(
|
|
694
|
+
0,
|
|
695
|
+
...nextSegments.map((segment) => segment.delay + segment.duration)
|
|
696
|
+
);
|
|
697
|
+
const maxNodeEnd = Math.max(0, ...Array.from(nextNodeDelays.values())) + baseNodeAnimDuration;
|
|
698
|
+
const animationMax = Math.max(maxLineEnd, maxNodeEnd);
|
|
699
|
+
const scale = animationMax > 0 ? totalAnimationSec / animationMax : 1;
|
|
700
|
+
const scaledSegments = nextSegments.map((segment) => ({
|
|
701
|
+
...segment,
|
|
702
|
+
delay: segment.delay * scale,
|
|
703
|
+
duration: segment.duration * scale
|
|
704
|
+
})).sort((a, b) => a.order - b.order);
|
|
705
|
+
const scaledNodeDelays = /* @__PURE__ */ new Map();
|
|
706
|
+
nextNodeDelays.forEach((value, key) => {
|
|
707
|
+
scaledNodeDelays.set(key, value * scale);
|
|
708
|
+
});
|
|
709
|
+
setLayoutState({
|
|
710
|
+
segments: scaledSegments,
|
|
711
|
+
nodeDelays: scaledNodeDelays,
|
|
712
|
+
nodeAnimDuration: baseNodeAnimDuration * scale,
|
|
713
|
+
animationTotal: animationMax * scale,
|
|
714
|
+
svgBounds: { width, height, offsetX, offsetY }
|
|
715
|
+
});
|
|
716
|
+
setDoneNodes(/* @__PURE__ */ new Set());
|
|
717
|
+
doneNodesRef.current = /* @__PURE__ */ new Set();
|
|
718
|
+
}, [
|
|
719
|
+
animationSpeed,
|
|
720
|
+
containerRef,
|
|
721
|
+
debug,
|
|
722
|
+
descendantMap,
|
|
723
|
+
direction,
|
|
724
|
+
edges,
|
|
725
|
+
gap,
|
|
726
|
+
nodeRefs,
|
|
727
|
+
nodeTree,
|
|
728
|
+
padding,
|
|
729
|
+
totalAnimationSec
|
|
730
|
+
]);
|
|
731
|
+
React.useLayoutEffect(() => {
|
|
732
|
+
const rafId = requestAnimationFrame(drawConnections);
|
|
733
|
+
return () => cancelAnimationFrame(rafId);
|
|
734
|
+
}, [drawConnections, nodeTree]);
|
|
735
|
+
React.useEffect(() => {
|
|
736
|
+
if (layoutState.animationTotal <= 0 || layoutState.nodeDelays.size === 0) {
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const entries = Array.from(layoutState.nodeDelays.entries()).map(([id, delay]) => ({
|
|
740
|
+
id,
|
|
741
|
+
end: delay + layoutState.nodeAnimDuration
|
|
742
|
+
})).sort((a, b) => a.end - b.end);
|
|
743
|
+
doneNodesRef.current = /* @__PURE__ */ new Set();
|
|
744
|
+
setDoneNodes(/* @__PURE__ */ new Set());
|
|
745
|
+
const start = performance.now();
|
|
746
|
+
let rafId = 0;
|
|
747
|
+
let index = 0;
|
|
748
|
+
const tick = () => {
|
|
749
|
+
const elapsed = (performance.now() - start) / 1e3;
|
|
750
|
+
let updated = false;
|
|
751
|
+
while (index < entries.length && elapsed >= entries[index].end) {
|
|
752
|
+
doneNodesRef.current.add(entries[index].id);
|
|
753
|
+
index += 1;
|
|
754
|
+
updated = true;
|
|
755
|
+
}
|
|
756
|
+
if (updated) {
|
|
757
|
+
setDoneNodes(new Set(doneNodesRef.current));
|
|
758
|
+
}
|
|
759
|
+
if (index < entries.length) {
|
|
760
|
+
rafId = requestAnimationFrame(tick);
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
rafId = requestAnimationFrame(tick);
|
|
764
|
+
return () => cancelAnimationFrame(rafId);
|
|
765
|
+
}, [
|
|
766
|
+
layoutState.animationTotal,
|
|
767
|
+
layoutState.nodeAnimDuration,
|
|
768
|
+
layoutState.nodeDelays
|
|
769
|
+
]);
|
|
770
|
+
return { doneNodes, layoutState };
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/components/node-tree.tsx
|
|
774
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
775
|
+
var NodeTree = React2.forwardRef(
|
|
776
|
+
({
|
|
777
|
+
className,
|
|
778
|
+
nodeTree,
|
|
779
|
+
layout,
|
|
780
|
+
connection,
|
|
781
|
+
animation,
|
|
782
|
+
nodeFrame,
|
|
783
|
+
debug = false,
|
|
784
|
+
style,
|
|
785
|
+
...props
|
|
786
|
+
}, ref) => {
|
|
787
|
+
const containerRef = React2.useRef(null);
|
|
788
|
+
const nodeRefs = React2.useRef(/* @__PURE__ */ new Map());
|
|
789
|
+
const registerNode = React2.useCallback(
|
|
790
|
+
(id, element) => {
|
|
791
|
+
const registry = nodeRefs.current;
|
|
792
|
+
if (element) {
|
|
793
|
+
registry.set(id, element);
|
|
794
|
+
} else {
|
|
795
|
+
registry.delete(id);
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
[]
|
|
799
|
+
);
|
|
800
|
+
const resolvedAlign = layout?.align ?? "center";
|
|
801
|
+
const resolvedDirection = layout?.direction ?? "down";
|
|
802
|
+
const resolvedRootLayout = layout?.root ?? "stack";
|
|
803
|
+
const resolvedPaddingContainer = layout?.containerPadding ?? 128;
|
|
804
|
+
const resolvedPadding = layout?.padding ?? 64;
|
|
805
|
+
const resolvedGap = layout?.gap ?? 64;
|
|
806
|
+
const resolvedStrokeColor = connection?.color ?? "rgba(255,255,255)";
|
|
807
|
+
const resolvedStrokeWidth = connection?.width ?? 1;
|
|
808
|
+
const resolvedAnimationDurationMs = animation?.durationMs ?? 2e3;
|
|
809
|
+
const resolvedNodeFrameStyle = nodeFrame?.style;
|
|
810
|
+
const { doneNodes, layoutState } = useNodeTreeLayout({
|
|
811
|
+
nodeTree,
|
|
812
|
+
direction: resolvedDirection,
|
|
813
|
+
gap: resolvedGap,
|
|
814
|
+
padding: resolvedPadding,
|
|
815
|
+
animationSpeed: resolvedAnimationDurationMs,
|
|
816
|
+
debug,
|
|
817
|
+
containerRef,
|
|
818
|
+
nodeRefs
|
|
819
|
+
});
|
|
820
|
+
const flowDown = resolvedDirection === "down";
|
|
821
|
+
const alignValue = resolvedAlign;
|
|
822
|
+
const alignX = typeof alignValue === "string" ? alignValue : alignValue.x;
|
|
823
|
+
const alignY = typeof alignValue === "string" ? "start" : alignValue.y;
|
|
824
|
+
const resolvedConnectionOpacity = connection?.opacity ?? (debug ? 1 : 0.1);
|
|
825
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
826
|
+
"div",
|
|
827
|
+
{
|
|
828
|
+
ref,
|
|
829
|
+
className: cn("unt-tree-root-container", className?.root),
|
|
830
|
+
style,
|
|
831
|
+
...props,
|
|
832
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
833
|
+
"div",
|
|
834
|
+
{
|
|
835
|
+
ref: containerRef,
|
|
836
|
+
className: cn("unt-tree-canvas", className?.canvas),
|
|
837
|
+
style: { padding: resolvedPaddingContainer },
|
|
838
|
+
children: [
|
|
839
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
840
|
+
TreeConnections,
|
|
841
|
+
{
|
|
842
|
+
layoutState,
|
|
843
|
+
debug,
|
|
844
|
+
strokeColor: resolvedStrokeColor,
|
|
845
|
+
strokeWidth: resolvedStrokeWidth,
|
|
846
|
+
opacity: resolvedConnectionOpacity,
|
|
847
|
+
className: className?.connections
|
|
848
|
+
}
|
|
849
|
+
),
|
|
850
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
851
|
+
TreeRenderer,
|
|
852
|
+
{
|
|
853
|
+
nodeTree,
|
|
854
|
+
rootLayout: resolvedRootLayout,
|
|
855
|
+
flowDown,
|
|
856
|
+
alignX,
|
|
857
|
+
alignY,
|
|
858
|
+
gap: resolvedGap,
|
|
859
|
+
debug,
|
|
860
|
+
layoutState,
|
|
861
|
+
doneNodes,
|
|
862
|
+
registerNode,
|
|
863
|
+
rendererClassName: className?.renderer,
|
|
864
|
+
nodeFrameClassName: className?.frame,
|
|
865
|
+
nodeFrameStyle: resolvedNodeFrameStyle
|
|
866
|
+
}
|
|
867
|
+
)
|
|
868
|
+
]
|
|
869
|
+
}
|
|
870
|
+
)
|
|
871
|
+
}
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
);
|
|
875
|
+
NodeTree.displayName = "NodeTree";
|
|
876
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
877
|
+
0 && (module.exports = {
|
|
878
|
+
NodeTree
|
|
879
|
+
});
|
|
880
|
+
//# sourceMappingURL=index.cjs.map
|