@visualli/react 0.1.7 → 0.2.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 +13 -5
- package/dist/index.cjs +1900 -2033
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +354 -97
- package/dist/index.d.ts +354 -97
- package/dist/index.js +2005 -2095
- package/dist/index.js.map +1 -1
- package/fonts/Caveat-Variable.ttf +0 -0
- package/licenses/visualli-design-system/LICENSE +9 -0
- package/licenses/visualli-design-system/NOTICE.md +6 -0
- package/package.json +9 -4
- package/src/VisualliCanvas.tsx +452 -409
- package/src/VisualliRenderer.tsx +90 -112
- package/src/components/A11yLayer.tsx +73 -0
- package/src/components/KonvaContainer.tsx +28 -237
- package/src/components/KonvaContainerLabelLayer.tsx +41 -0
- package/src/components/KonvaContainerLayer.tsx +34 -33
- package/src/components/KonvaEdge.tsx +25 -240
- package/src/components/KonvaEdgeLayer.tsx +69 -49
- package/src/components/KonvaNode.tsx +21 -156
- package/src/components/KonvaNodeLayer.tsx +102 -34
- package/src/components/KonvaStage.tsx +17 -3
- package/src/components/NavigationStack.tsx +16 -159
- package/src/components/Overlays.tsx +327 -0
- package/src/components/ZoomControls.tsx +3 -107
- package/src/components/useFrames.ts +30 -0
- package/src/context/VisualliContext.tsx +3 -3
- package/src/design/choreography.ts +157 -0
- package/src/design/containers.ts +150 -0
- package/src/design/design.ts +115 -0
- package/src/design/drawing.ts +303 -0
- package/src/design/measure.ts +34 -0
- package/src/design/runtime.ts +154 -0
- package/src/design/useDesign.ts +38 -0
- package/src/generated/specCss.ts +15 -0
- package/src/hooks/index.ts +0 -2
- package/src/hooks/useKonvaRenderer.ts +13 -18
- package/src/hooks/useLayerChoreography.ts +159 -0
- package/src/hooks/useViewportNodes.ts +0 -1
- package/src/index.ts +22 -22
- package/src/stores/stores.ts +2 -2
- package/src/stores/useViewportStore.ts +12 -2
- package/src/animations/index.ts +0 -2
- package/src/animations/konvaLayerTransition.ts +0 -223
- package/src/components/SketchyBoxKonva.tsx +0 -121
- package/src/config/blobShapes.ts +0 -204
- package/src/config/index.ts +0 -21
- package/src/config/textScaling.ts +0 -69
- package/src/hooks/useKonvaLayerTransition.ts +0 -221
package/dist/index.cjs
CHANGED
|
@@ -12,9 +12,9 @@ var __export = (target, all) => {
|
|
|
12
12
|
};
|
|
13
13
|
var __copyProps = (to, from, except, desc) => {
|
|
14
14
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let
|
|
16
|
-
if (!__hasOwnProp.call(to,
|
|
17
|
-
__defProp(to,
|
|
15
|
+
for (let key2 of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
17
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
18
18
|
}
|
|
19
19
|
return to;
|
|
20
20
|
};
|
|
@@ -31,44 +31,68 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
// src/index.ts
|
|
32
32
|
var index_exports = {};
|
|
33
33
|
__export(index_exports, {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
A11yLayer: () => A11yLayer,
|
|
35
|
+
BLOB_SHAPES: () => import_core19.BLOB_SHAPES,
|
|
36
|
+
DEFAULT_DESIGN: () => DEFAULT_DESIGN,
|
|
37
|
+
DESIGN_SYSTEM_VERSION: () => import_core19.DESIGN_SYSTEM_VERSION,
|
|
38
|
+
DepthTrail: () => DepthTrail,
|
|
39
|
+
IDEA: () => import_core19.IDEA,
|
|
39
40
|
KonvaContainer: () => KonvaContainer_default,
|
|
41
|
+
KonvaContainerLabelLayer: () => KonvaContainerLabelLayer,
|
|
40
42
|
KonvaContainerLayer: () => KonvaContainerLayer,
|
|
41
43
|
KonvaEdge: () => KonvaEdge_default,
|
|
42
44
|
KonvaEdgeLayer: () => KonvaEdgeLayer,
|
|
43
|
-
KonvaLayerTransitionAnimator: () => KonvaLayerTransitionAnimator,
|
|
44
45
|
KonvaNode: () => KonvaNode_default,
|
|
45
46
|
KonvaNodeLayer: () => KonvaNodeLayer,
|
|
46
47
|
KonvaStage: () => KonvaStage_default,
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
LABEL_GROWTH: () => import_core19.LABEL_GROWTH,
|
|
49
|
+
METRICS: () => import_core19.METRICS,
|
|
49
50
|
NavigationStack: () => NavigationStack_default,
|
|
50
|
-
|
|
51
|
+
PeekCard: () => PeekCard,
|
|
52
|
+
RINGS: () => import_core19.RINGS,
|
|
53
|
+
THEME_NAMES: () => import_core19.THEME_NAMES,
|
|
54
|
+
TOKENS: () => import_core19.TOKENS,
|
|
55
|
+
TOPICS: () => import_core19.TOPICS,
|
|
56
|
+
TermCard: () => TermCard,
|
|
51
57
|
VisualliCanvas: () => VisualliCanvas,
|
|
52
58
|
VisualliProvider: () => VisualliProvider,
|
|
53
59
|
VisualliRenderer: () => VisualliRenderer,
|
|
54
|
-
ZoomControls: () =>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
getChildLayerForNode: () =>
|
|
66
|
-
getConnectionsForLayer: () =>
|
|
67
|
-
getLayerForNavigation: () =>
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
ZoomControls: () => ZoomControls,
|
|
61
|
+
arrowPath: () => import_core19.arrowPath,
|
|
62
|
+
blobPath: () => import_core19.blobPath,
|
|
63
|
+
blobRadius: () => import_core19.blobRadius,
|
|
64
|
+
calculateFitCenter: () => import_core3.calculateFitCenter,
|
|
65
|
+
calculateFitView: () => import_core3.calculateFitView,
|
|
66
|
+
calculateFitZoom: () => import_core3.calculateFitZoom,
|
|
67
|
+
drawHull: () => drawHull,
|
|
68
|
+
drawPill: () => drawPill,
|
|
69
|
+
edgePath: () => import_core19.edgePath,
|
|
70
|
+
ensureDesignSystemStyles: () => ensureDesignSystemStyles,
|
|
71
|
+
getChildLayerForNode: () => import_core3.getChildLayerForNode,
|
|
72
|
+
getConnectionsForLayer: () => import_core3.getConnectionsForLayer,
|
|
73
|
+
getLayerForNavigation: () => import_core3.getLayerForNavigation,
|
|
74
|
+
getTokens: () => import_core19.getTokens,
|
|
75
|
+
ideaColor: () => import_core19.ideaColor,
|
|
76
|
+
ideaMeasure: () => ideaMeasure,
|
|
77
|
+
ideaSize: () => import_core19.ideaSize,
|
|
78
|
+
ideaStyle: () => import_core19.ideaStyle,
|
|
79
|
+
labelGrowth: () => import_core19.labelGrowth,
|
|
80
|
+
layoutContainers: () => layoutContainers,
|
|
81
|
+
loadCanvasFonts: () => loadCanvasFonts,
|
|
82
|
+
makeDesign: () => makeDesign,
|
|
83
|
+
measurePill: () => measurePill,
|
|
84
|
+
paintVars: () => paintVars,
|
|
85
|
+
resolveComfort: () => import_core19.resolveComfort,
|
|
86
|
+
resolveDesign: () => resolveDesign,
|
|
87
|
+
resolveTheme: () => import_core19.resolveTheme,
|
|
88
|
+
shapeForLevel: () => import_core19.shapeForLevel,
|
|
89
|
+
splitTerms: () => splitTerms,
|
|
90
|
+
topicStyle: () => import_core19.topicStyle,
|
|
91
|
+
useDesign: () => useDesign,
|
|
70
92
|
useKonvaRenderer: () => useKonvaRenderer,
|
|
93
|
+
useLayerChoreography: () => useLayerChoreography,
|
|
71
94
|
useNodeStore: () => useNodeStore,
|
|
95
|
+
useReaderEnv: () => useReaderEnv,
|
|
72
96
|
useRenderConfigStore: () => useRenderConfigStore,
|
|
73
97
|
useSelectionStore: () => useSelectionStore,
|
|
74
98
|
useViewportNodes: () => useViewportNodes,
|
|
@@ -79,11 +103,11 @@ module.exports = __toCommonJS(index_exports);
|
|
|
79
103
|
|
|
80
104
|
// src/VisualliRenderer.tsx
|
|
81
105
|
var import_react15 = require("react");
|
|
82
|
-
var
|
|
106
|
+
var import_core16 = require("@visualli/core");
|
|
83
107
|
|
|
84
108
|
// src/VisualliCanvas.tsx
|
|
85
109
|
var import_react14 = require("react");
|
|
86
|
-
var
|
|
110
|
+
var import_core15 = require("@visualli/core");
|
|
87
111
|
|
|
88
112
|
// src/stores/useNodeStore.ts
|
|
89
113
|
var import_zustand = require("zustand");
|
|
@@ -180,13 +204,16 @@ var useViewportStore = (0, import_zustand2.create)((set, get) => {
|
|
|
180
204
|
...init,
|
|
181
205
|
canvasWidth: import_core.DEFAULT_CANVAS_WIDTH,
|
|
182
206
|
canvasHeight: import_core.DEFAULT_CANVAS_HEIGHT,
|
|
207
|
+
fitScale: 1,
|
|
208
|
+
fitCenterX: 0,
|
|
209
|
+
fitCenterY: 0,
|
|
183
210
|
pan: (dx, dy) => set((s) => {
|
|
184
211
|
const updated = (0, import_core.recalculateViewportBounds)((0, import_core.panViewport)(dx, dy, getVP(s)), s.canvasWidth, s.canvasHeight);
|
|
185
212
|
return { centerX: updated.centerX, centerY: updated.centerY, visibleBounds: updated.visibleBounds };
|
|
186
213
|
}),
|
|
187
214
|
zoom: (delta, pivotX, pivotY) => set((s) => {
|
|
188
215
|
const updated = (0, import_core.recalculateViewportBounds)(
|
|
189
|
-
(0, import_core.zoomViewport)(delta, getVP(s), pivotX, pivotY, s.canvasWidth, s.canvasHeight),
|
|
216
|
+
(0, import_core.zoomViewport)(delta, getVP(s), pivotX, pivotY, s.canvasWidth, s.canvasHeight, s.fitScale),
|
|
190
217
|
s.canvasWidth,
|
|
191
218
|
s.canvasHeight
|
|
192
219
|
);
|
|
@@ -197,10 +224,11 @@ var useViewportStore = (0, import_zustand2.create)((set, get) => {
|
|
|
197
224
|
return { centerX: updated.centerX, centerY: updated.centerY, visibleBounds: updated.visibleBounds };
|
|
198
225
|
}),
|
|
199
226
|
setZoom: (level) => set((s) => {
|
|
200
|
-
const clamped = (0, import_core.clampZoom)(level);
|
|
227
|
+
const clamped = (0, import_core.clampZoom)(level, s.fitScale);
|
|
201
228
|
const updated = (0, import_core.recalculateViewportBounds)({ ...getVP(s), zoomLevel: clamped }, s.canvasWidth, s.canvasHeight);
|
|
202
229
|
return { zoomLevel: updated.zoomLevel, visibleBounds: updated.visibleBounds };
|
|
203
230
|
}),
|
|
231
|
+
setFit: (scale, centerX, centerY) => set({ fitScale: scale > 0 ? scale : 1, fitCenterX: centerX, fitCenterY: centerY }),
|
|
204
232
|
updateCanvasSize: (w, h) => set((s) => {
|
|
205
233
|
const updated = (0, import_core.recalculateViewportBounds)(getVP(s), w, h);
|
|
206
234
|
return { canvasWidth: w, canvasHeight: h, visibleBounds: updated.visibleBounds };
|
|
@@ -220,11 +248,11 @@ var useViewportStore = (0, import_zustand2.create)((set, get) => {
|
|
|
220
248
|
// src/stores/stores.ts
|
|
221
249
|
var import_zustand3 = require("zustand");
|
|
222
250
|
var import_core2 = require("@visualli/core");
|
|
223
|
-
var useSelectionStore = (0, import_zustand3.create)((set
|
|
251
|
+
var useSelectionStore = (0, import_zustand3.create)((set) => {
|
|
224
252
|
import_core2.selectionStore.subscribe((state) => set(state));
|
|
225
253
|
return import_core2.selectionStore.getState();
|
|
226
254
|
});
|
|
227
|
-
var useRenderConfigStore = (0, import_zustand3.create)((set
|
|
255
|
+
var useRenderConfigStore = (0, import_zustand3.create)((set) => {
|
|
228
256
|
import_core2.renderConfigStore.subscribe((state) => set(state));
|
|
229
257
|
return import_core2.renderConfigStore.getState();
|
|
230
258
|
});
|
|
@@ -233,13 +261,10 @@ var useRenderConfigStore = (0, import_zustand3.create)((set, get, store) => {
|
|
|
233
261
|
var import_react = require("react");
|
|
234
262
|
function useKonvaRenderer({
|
|
235
263
|
containerRef,
|
|
236
|
-
stageRef,
|
|
237
264
|
enabled = true
|
|
238
265
|
}) {
|
|
239
266
|
const pan = useViewportStore((s) => s.pan);
|
|
240
267
|
const zoom = useViewportStore((s) => s.zoom);
|
|
241
|
-
const setZoom = useViewportStore((s) => s.setZoom);
|
|
242
|
-
const setCenter = useViewportStore((s) => s.setCenter);
|
|
243
268
|
const updateCanvasSize = useViewportStore((s) => s.updateCanvasSize);
|
|
244
269
|
const autoAdjust = useRenderConfigStore((s) => s.autoAdjustQuality);
|
|
245
270
|
const [canvasWidth, setCanvasWidth] = (0, import_react.useState)(0);
|
|
@@ -297,7 +322,8 @@ function useKonvaRenderer({
|
|
|
297
322
|
window.addEventListener("resize", updateSize);
|
|
298
323
|
}
|
|
299
324
|
return () => {
|
|
300
|
-
ro
|
|
325
|
+
if (ro) ro.disconnect();
|
|
326
|
+
else window.removeEventListener("resize", updateSize);
|
|
301
327
|
if (rafId) cancelAnimationFrame(rafId);
|
|
302
328
|
};
|
|
303
329
|
}, [enabled, containerRef, updateCanvasSize]);
|
|
@@ -317,17 +343,10 @@ function useKonvaRenderer({
|
|
|
317
343
|
isPanningRef.current = true;
|
|
318
344
|
lastPanPointRef.current = { x: e.evt.clientX, y: e.evt.clientY };
|
|
319
345
|
panAccumRef.current = { x: 0, y: 0 };
|
|
320
|
-
const stage = e.target.getStage();
|
|
321
|
-
if (stage) stage.container().style.cursor = "grabbing";
|
|
322
346
|
}
|
|
323
347
|
}, []);
|
|
324
348
|
const handleMouseMove = (0, import_react.useCallback)((e) => {
|
|
325
|
-
if (!isPanningRef.current)
|
|
326
|
-
const isOverEmpty = e.target === e.target.getStage();
|
|
327
|
-
const stage2 = e.target.getStage();
|
|
328
|
-
if (stage2) stage2.container().style.cursor = isOverEmpty ? "grab" : "default";
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
349
|
+
if (!isPanningRef.current) return;
|
|
331
350
|
e.evt.preventDefault();
|
|
332
351
|
const deltaX = e.evt.clientX - lastPanPointRef.current.x;
|
|
333
352
|
const deltaY = e.evt.clientY - lastPanPointRef.current.y;
|
|
@@ -341,7 +360,7 @@ function useKonvaRenderer({
|
|
|
341
360
|
panAccumRef.current.y += deltaY;
|
|
342
361
|
lastPanPointRef.current = { x: e.evt.clientX, y: e.evt.clientY };
|
|
343
362
|
}, []);
|
|
344
|
-
const handleMouseUp = (0, import_react.useCallback)((
|
|
363
|
+
const handleMouseUp = (0, import_react.useCallback)(() => {
|
|
345
364
|
if (isPanningRef.current) {
|
|
346
365
|
isPanningRef.current = false;
|
|
347
366
|
const { x, y } = panAccumRef.current;
|
|
@@ -349,348 +368,422 @@ function useKonvaRenderer({
|
|
|
349
368
|
pan(x, y);
|
|
350
369
|
panAccumRef.current = { x: 0, y: 0 };
|
|
351
370
|
}
|
|
352
|
-
const stage = e.target.getStage();
|
|
353
|
-
if (stage) stage.container().style.cursor = "grab";
|
|
354
371
|
}
|
|
355
372
|
}, [pan]);
|
|
356
|
-
|
|
373
|
+
const cancelPan = (0, import_react.useCallback)(() => {
|
|
374
|
+
if (!isPanningRef.current) return;
|
|
375
|
+
isPanningRef.current = false;
|
|
376
|
+
const { x, y } = panAccumRef.current;
|
|
377
|
+
if (x !== 0 || y !== 0) {
|
|
378
|
+
pan(x, y);
|
|
379
|
+
panAccumRef.current = { x: 0, y: 0 };
|
|
380
|
+
}
|
|
381
|
+
}, [pan]);
|
|
382
|
+
return { handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, cancelPan, canvasWidth, canvasHeight };
|
|
357
383
|
}
|
|
358
384
|
|
|
359
|
-
// src/hooks/
|
|
385
|
+
// src/hooks/useLayerChoreography.ts
|
|
360
386
|
var import_react2 = require("react");
|
|
387
|
+
var import_core4 = require("@visualli/core");
|
|
361
388
|
|
|
362
|
-
// src/
|
|
389
|
+
// src/utils/layerNavigation.ts
|
|
363
390
|
var import_core3 = require("@visualli/core");
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
391
|
+
|
|
392
|
+
// src/hooks/useLayerChoreography.ts
|
|
393
|
+
function useLayerChoreography(o) {
|
|
394
|
+
const { stageRef, canvasWrapperRef } = o;
|
|
395
|
+
const opts = (0, import_react2.useRef)(o);
|
|
396
|
+
opts.current = o;
|
|
397
|
+
const busy = (0, import_react2.useRef)(false);
|
|
398
|
+
const waiting = (0, import_react2.useRef)(false);
|
|
399
|
+
const anims = (0, import_react2.useRef)([]);
|
|
400
|
+
const timers = (0, import_react2.useRef)([]);
|
|
401
|
+
const after = (0, import_react2.useRef)([]);
|
|
402
|
+
const clear = (0, import_react2.useCallback)(() => {
|
|
403
|
+
timers.current.forEach(clearTimeout);
|
|
404
|
+
timers.current = [];
|
|
405
|
+
after.current.forEach(cancelAnimationFrame);
|
|
406
|
+
after.current = [];
|
|
407
|
+
anims.current.forEach((a) => a.cancel());
|
|
408
|
+
anims.current = [];
|
|
409
|
+
}, []);
|
|
410
|
+
const applyViewport = (0, import_react2.useCallback)((vp) => {
|
|
411
|
+
const stage = stageRef.current;
|
|
412
|
+
if (stage) {
|
|
413
|
+
stage.x(stage.width() / 2 - vp.centerX * vp.zoomLevel);
|
|
414
|
+
stage.y(stage.height() / 2 - vp.centerY * vp.zoomLevel);
|
|
415
|
+
stage.scaleX(vp.zoomLevel);
|
|
416
|
+
stage.scaleY(vp.zoomLevel);
|
|
417
|
+
stage.getLayers().forEach((l) => l.drawScene());
|
|
418
|
+
}
|
|
419
|
+
const s = useViewportStore.getState();
|
|
420
|
+
s.setCenter(vp.centerX, vp.centerY);
|
|
421
|
+
s.setZoom(vp.zoomLevel);
|
|
422
|
+
}, [stageRef]);
|
|
423
|
+
const lifecycle = (0, import_react2.useCallback)((phase) => {
|
|
424
|
+
const stage = stageRef.current;
|
|
425
|
+
stage?.getLayers().forEach((l) => {
|
|
426
|
+
l.listening(phase === "end");
|
|
427
|
+
});
|
|
428
|
+
if (phase === "end") stage?.batchDraw();
|
|
429
|
+
opts.current.onLifecycle?.(phase);
|
|
430
|
+
}, [stageRef]);
|
|
431
|
+
const leave = (0, import_react2.useCallback)((wrap, dir, origin, vp, swap) => {
|
|
432
|
+
busy.current = true;
|
|
433
|
+
waiting.current = false;
|
|
434
|
+
lifecycle("start");
|
|
435
|
+
const reduced = opts.current.reducedMotion;
|
|
436
|
+
if (wrap && !reduced && typeof wrap.animate === "function") {
|
|
437
|
+
const m = import_core4.MOTION;
|
|
438
|
+
wrap.style.transformOrigin = origin;
|
|
439
|
+
wrap.style.willChange = "transform, opacity";
|
|
440
|
+
if (dir === "in") {
|
|
441
|
+
anims.current.push(
|
|
442
|
+
wrap.animate([{ transform: "scale(1)" }, { transform: `scale(${m.dive.scale})` }], { duration: m.dive.duration, easing: (0, import_core4.cssEase)(m.dive.ease), fill: "forwards" }),
|
|
443
|
+
wrap.animate([{ opacity: 1 }, { opacity: 0 }], { delay: m.dive.fadeDelay, duration: m.dive.fade, easing: "ease", fill: "both" })
|
|
444
|
+
);
|
|
445
|
+
} else {
|
|
446
|
+
anims.current.push(
|
|
447
|
+
wrap.animate([{ transform: "scale(1)" }, { transform: `scale(${m.surface.scale})` }], { duration: m.surface.duration, easing: (0, import_core4.cssEase)(m.surface.ease), fill: "forwards" }),
|
|
448
|
+
wrap.animate([{ opacity: 1 }, { opacity: 0 }], { duration: m.surface.fade, easing: "ease", fill: "both" })
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
timers.current.push(setTimeout(() => {
|
|
453
|
+
waiting.current = true;
|
|
454
|
+
swap();
|
|
455
|
+
applyViewport(vp);
|
|
456
|
+
timers.current.push(setTimeout(() => arriveRef.current(), 500));
|
|
457
|
+
}, (0, import_core4.swapDelay)(dir, reduced)));
|
|
458
|
+
}, [applyViewport, lifecycle]);
|
|
459
|
+
const arriveRef = (0, import_react2.useRef)(() => {
|
|
460
|
+
});
|
|
461
|
+
const arrive = (0, import_react2.useCallback)(() => {
|
|
462
|
+
if (!waiting.current) return;
|
|
463
|
+
waiting.current = false;
|
|
464
|
+
const wrap = canvasWrapperRef.current;
|
|
465
|
+
const reduced = opts.current.reducedMotion;
|
|
466
|
+
anims.current.forEach((a) => a.cancel());
|
|
467
|
+
anims.current = [];
|
|
468
|
+
let settle = reduced ? import_core4.MOTION.reduced.fade : import_core4.MOTION.settle.duration;
|
|
469
|
+
if (wrap && !reduced && typeof wrap.animate === "function") {
|
|
470
|
+
const a = wrap.animate([{ transform: `scale(${import_core4.MOTION.settle.fromScale})` }, { transform: "none" }], { duration: import_core4.MOTION.settle.duration, easing: (0, import_core4.cssEase)(import_core4.MOTION.settle.ease) });
|
|
471
|
+
anims.current.push(a);
|
|
472
|
+
settle = import_core4.MOTION.settle.duration;
|
|
473
|
+
}
|
|
474
|
+
timers.current.push(setTimeout(() => {
|
|
475
|
+
anims.current.forEach((a) => a.cancel());
|
|
476
|
+
anims.current = [];
|
|
477
|
+
if (wrap) {
|
|
478
|
+
wrap.style.willChange = "auto";
|
|
479
|
+
wrap.style.transformOrigin = "";
|
|
480
|
+
}
|
|
481
|
+
busy.current = false;
|
|
482
|
+
opts.current.onComplete();
|
|
483
|
+
lifecycle("end");
|
|
484
|
+
}, settle));
|
|
485
|
+
}, [canvasWrapperRef, lifecycle]);
|
|
486
|
+
arriveRef.current = arrive;
|
|
487
|
+
const stepInside = (0, import_react2.useCallback)((targetNode, childLayerId, childNodes) => {
|
|
488
|
+
if (busy.current) return;
|
|
489
|
+
const stage = stageRef.current;
|
|
490
|
+
const zoom = stage?.scaleX() ?? 1;
|
|
491
|
+
const origin = stage ? `${targetNode.x * zoom + stage.x()}px ${targetNode.y * zoom + stage.y()}px` : "50% 50%";
|
|
492
|
+
const fit = (0, import_core3.calculateFitView)(childNodes, stage?.width() ?? 800, stage?.height() ?? 600);
|
|
493
|
+
leave(canvasWrapperRef.current, "in", origin, { centerX: fit.centerX, centerY: fit.centerY, zoomLevel: fit.zoomLevel }, () => opts.current.onSwapLayer(childLayerId));
|
|
494
|
+
}, [stageRef, canvasWrapperRef, leave]);
|
|
495
|
+
const backOut = (0, import_react2.useCallback)((parent) => {
|
|
496
|
+
if (busy.current) return;
|
|
497
|
+
leave(canvasWrapperRef.current, "out", "50% 50%", parent, () => opts.current.onSwapBack());
|
|
498
|
+
}, [canvasWrapperRef, leave]);
|
|
499
|
+
const isTransitioning = (0, import_react2.useCallback)(() => busy.current, []);
|
|
500
|
+
(0, import_react2.useEffect)(() => () => {
|
|
501
|
+
clear();
|
|
502
|
+
busy.current = false;
|
|
503
|
+
}, [clear]);
|
|
504
|
+
return { stepInside, backOut, arrive, isTransitioning };
|
|
379
505
|
}
|
|
380
|
-
|
|
506
|
+
|
|
507
|
+
// src/design/choreography.ts
|
|
508
|
+
var import_core5 = require("@visualli/core");
|
|
509
|
+
var clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
510
|
+
var RevealClock = class {
|
|
381
511
|
constructor() {
|
|
382
|
-
this.
|
|
383
|
-
this.
|
|
384
|
-
this.
|
|
512
|
+
this.t0 = -Infinity;
|
|
513
|
+
this.end = 0;
|
|
514
|
+
this.plan = (0, import_core5.layerReveal)(0, 0);
|
|
515
|
+
this.reduced = false;
|
|
516
|
+
this.center = { x: 0, y: 0 };
|
|
517
|
+
this.order = /* @__PURE__ */ new Map();
|
|
518
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
519
|
+
this.timer = null;
|
|
385
520
|
}
|
|
386
|
-
|
|
387
|
-
|
|
521
|
+
/** Begin a layer's arrival. `reduced` (reduced motion or instant reveal): everything fades in together. */
|
|
522
|
+
start(nodes, edgeCount, reduced, now = performance.now()) {
|
|
523
|
+
this.order.clear();
|
|
524
|
+
nodes.forEach((n, i) => this.order.set(n.id, i));
|
|
525
|
+
this.plan = (0, import_core5.layerReveal)(nodes.length, edgeCount);
|
|
526
|
+
this.reduced = reduced || this.plan.instant;
|
|
527
|
+
reduced = this.reduced;
|
|
528
|
+
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
|
529
|
+
for (const n of nodes) {
|
|
530
|
+
minX = Math.min(minX, n.x);
|
|
531
|
+
maxX = Math.max(maxX, n.x);
|
|
532
|
+
minY = Math.min(minY, n.y);
|
|
533
|
+
maxY = Math.max(maxY, n.y);
|
|
534
|
+
}
|
|
535
|
+
this.center = nodes.length ? { x: (minX + maxX) / 2, y: (minY + maxY) / 2 } : { x: 0, y: 0 };
|
|
536
|
+
this.t0 = now;
|
|
537
|
+
this.end = reduced ? import_core5.MOTION.reduced.fade : this.plan.total;
|
|
538
|
+
this.emit(true);
|
|
539
|
+
if (this.timer) clearTimeout(this.timer);
|
|
540
|
+
this.timer = setTimeout(() => {
|
|
541
|
+
this.timer = null;
|
|
542
|
+
this.emit(false);
|
|
543
|
+
}, this.end + 50);
|
|
388
544
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.
|
|
545
|
+
/** Jump to the finished state (e.g. on unmount). */
|
|
546
|
+
stop() {
|
|
547
|
+
this.t0 = -Infinity;
|
|
548
|
+
if (this.timer) {
|
|
549
|
+
clearTimeout(this.timer);
|
|
550
|
+
this.timer = null;
|
|
551
|
+
}
|
|
552
|
+
this.emit(false);
|
|
397
553
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
this._active = true;
|
|
402
|
-
const ZOOM_DURATION = import_core4.ANIMATION_PHASES.ZOOM_IN.ZOOM_TO_TARGET;
|
|
403
|
-
const FADE_OUT_DELAY = import_core4.ANIMATION_PHASES.ZOOM_IN.FADE_OUT_START;
|
|
404
|
-
const FADE_OUT_DURATION = import_core4.ANIMATION_PHASES.ZOOM_IN.FADE_OUT;
|
|
405
|
-
const SWAP_AT_MS = import_core4.ANIMATION_PHASES.ZOOM_IN.CONTENT_SWAP;
|
|
406
|
-
const FADE_IN_DURATION = import_core4.ANIMATION_PHASES.ZOOM_IN.FADE_IN;
|
|
407
|
-
const SETTLE_FACTOR = 0.94;
|
|
408
|
-
const z0 = current.zoomLevel;
|
|
409
|
-
const dx = targetNode.x - current.centerX;
|
|
410
|
-
const dy = targetNode.y - current.centerY;
|
|
411
|
-
const vmin = Math.min(canvasWidth, canvasHeight);
|
|
412
|
-
const fillZoom = Math.max(4, vmin * 1.5 / Math.max(targetNode.width, 100));
|
|
413
|
-
const fromRgb = parseColor(fromColor);
|
|
414
|
-
const toRgb = parseColor(toColor);
|
|
415
|
-
const state = { viewport: { centerX: 0, centerY: 0, zoomLevel: 0 }, opacity: 1, backgroundColor: fromColor };
|
|
416
|
-
const start = performance.now();
|
|
417
|
-
let swapped = false;
|
|
418
|
-
let fadeInStart = 0;
|
|
419
|
-
cb.onPhaseChange?.("zoom");
|
|
420
|
-
const tick = (now) => {
|
|
421
|
-
if (!this._active) return;
|
|
422
|
-
const elapsed = now - start;
|
|
423
|
-
if (!swapped) {
|
|
424
|
-
if (elapsed >= SWAP_AT_MS) {
|
|
425
|
-
swapped = true;
|
|
426
|
-
fadeInStart = now;
|
|
427
|
-
state.viewport = { centerX: childCenter.x, centerY: childCenter.y, zoomLevel: targetZoom * SETTLE_FACTOR };
|
|
428
|
-
state.opacity = 0;
|
|
429
|
-
state.backgroundColor = toColor;
|
|
430
|
-
cb.onUpdate(state);
|
|
431
|
-
cb.onPhaseChange?.("swap");
|
|
432
|
-
this.rafId = requestAnimationFrame(tick);
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
const rawT = Math.min(elapsed / ZOOM_DURATION, 1);
|
|
436
|
-
const zoomT = (0, import_core3.easeZoomTransition)(rawT);
|
|
437
|
-
const zl = lerp(z0, fillZoom, zoomT);
|
|
438
|
-
state.viewport = {
|
|
439
|
-
centerX: targetNode.x - dx * z0 * (1 - zoomT) / zl,
|
|
440
|
-
centerY: targetNode.y - dy * z0 * (1 - zoomT) / zl,
|
|
441
|
-
zoomLevel: zl
|
|
442
|
-
};
|
|
443
|
-
state.opacity = elapsed > FADE_OUT_DELAY ? Math.max(0, 1 - (0, import_core3.easeOutCubic)(Math.min((elapsed - FADE_OUT_DELAY) / FADE_OUT_DURATION, 1))) : 1;
|
|
444
|
-
state.backgroundColor = interpolateRgb(fromRgb, toRgb, rawT);
|
|
445
|
-
cb.onUpdate(state);
|
|
446
|
-
} else {
|
|
447
|
-
const fi = now - fadeInStart;
|
|
448
|
-
const ft = Math.min(fi / FADE_IN_DURATION, 1);
|
|
449
|
-
const eased = (0, import_core3.easeOutCubic)(ft);
|
|
450
|
-
state.viewport = { centerX: childCenter.x, centerY: childCenter.y, zoomLevel: lerp(targetZoom * SETTLE_FACTOR, targetZoom, eased) };
|
|
451
|
-
state.opacity = eased;
|
|
452
|
-
state.backgroundColor = toColor;
|
|
453
|
-
cb.onUpdate(state);
|
|
454
|
-
if (ft >= 1) {
|
|
455
|
-
cb.onPhaseChange?.("fade-in");
|
|
456
|
-
this.rafId = null;
|
|
457
|
-
this._complete(cb.onComplete);
|
|
458
|
-
return;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
this.rafId = requestAnimationFrame(tick);
|
|
462
|
-
};
|
|
463
|
-
this.rafId = requestAnimationFrame(tick);
|
|
554
|
+
/** True while the layer is still arriving. */
|
|
555
|
+
active(now = performance.now()) {
|
|
556
|
+
return now - this.t0 < this.end;
|
|
464
557
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const FADE_IN = import_core4.ANIMATION_PHASES.ZOOM_OUT.FADE_IN;
|
|
471
|
-
const OPACITY_SETTLE = 66;
|
|
472
|
-
const fromRgb = parseColor(fromColor);
|
|
473
|
-
const toRgb = parseColor(toColor);
|
|
474
|
-
const state = { viewport: { centerX: 0, centerY: 0, zoomLevel: 0 }, opacity: 1, backgroundColor: fromColor };
|
|
475
|
-
const start = performance.now();
|
|
476
|
-
let swapped = false;
|
|
477
|
-
let fadeInStart = 0;
|
|
478
|
-
cb.onPhaseChange?.("fade-out");
|
|
479
|
-
const tick = (now) => {
|
|
480
|
-
if (!this._active) return;
|
|
481
|
-
const elapsed = now - start;
|
|
482
|
-
if (!swapped) {
|
|
483
|
-
if (elapsed >= FADE_OUT) {
|
|
484
|
-
swapped = true;
|
|
485
|
-
fadeInStart = now;
|
|
486
|
-
state.viewport = { centerX: parent.centerX, centerY: parent.centerY, zoomLevel: parent.zoomLevel * 5 };
|
|
487
|
-
state.opacity = 0;
|
|
488
|
-
state.backgroundColor = toColor;
|
|
489
|
-
cb.onUpdate(state);
|
|
490
|
-
cb.onPhaseChange?.("zoom-back");
|
|
491
|
-
this.rafId = requestAnimationFrame(tick);
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
const rawT = Math.min(elapsed / FADE_OUT, 1);
|
|
495
|
-
const t = (0, import_core3.easeOutCubic)(rawT);
|
|
496
|
-
state.viewport = { centerX: current.centerX, centerY: current.centerY, zoomLevel: lerp(current.zoomLevel, current.zoomLevel * 0.6, t) };
|
|
497
|
-
state.opacity = 1 - t;
|
|
498
|
-
state.backgroundColor = interpolateRgb(fromRgb, toRgb, rawT);
|
|
499
|
-
cb.onUpdate(state);
|
|
500
|
-
} else {
|
|
501
|
-
const fi = now - fadeInStart;
|
|
502
|
-
const rawT = Math.min(fi / FADE_IN, 1);
|
|
503
|
-
const t = (0, import_core3.easeZoomTransition)(rawT);
|
|
504
|
-
state.viewport = { centerX: parent.centerX, centerY: parent.centerY, zoomLevel: lerp(parent.zoomLevel * 5, parent.zoomLevel, t) };
|
|
505
|
-
state.opacity = fi < OPACITY_SETTLE ? 0 : (0, import_core3.easeZoomTransition)(Math.min((fi - OPACITY_SETTLE) / (FADE_IN - OPACITY_SETTLE), 1));
|
|
506
|
-
state.backgroundColor = toColor;
|
|
507
|
-
cb.onUpdate(state);
|
|
508
|
-
if (rawT >= 1) {
|
|
509
|
-
cb.onPhaseChange?.("fade-in");
|
|
510
|
-
this.rafId = null;
|
|
511
|
-
this._complete(cb.onComplete);
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
this.rafId = requestAnimationFrame(tick);
|
|
558
|
+
/** Called with true when an arrival starts and false when it has finished. Returns the unsubscribe function. */
|
|
559
|
+
subscribe(fn) {
|
|
560
|
+
this.listeners.add(fn);
|
|
561
|
+
return () => {
|
|
562
|
+
this.listeners.delete(fn);
|
|
516
563
|
};
|
|
517
|
-
this.rafId = requestAnimationFrame(tick);
|
|
518
564
|
}
|
|
519
|
-
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
565
|
+
emit(running) {
|
|
566
|
+
this.listeners.forEach((fn) => fn(running));
|
|
567
|
+
}
|
|
568
|
+
/** Fills `out` with idea `id`'s arrival look. Returns false when it has arrived (nothing to apply). */
|
|
569
|
+
node(id, x, y, now, out) {
|
|
570
|
+
const el = now - this.t0;
|
|
571
|
+
if (el >= this.end) return false;
|
|
572
|
+
const i = this.order.get(id);
|
|
573
|
+
if (i === void 0) return false;
|
|
574
|
+
if (this.reduced) {
|
|
575
|
+
out.alpha = clamp01(el / import_core5.MOTION.reduced.fade);
|
|
576
|
+
out.scale = 1;
|
|
577
|
+
out.dx = 0;
|
|
578
|
+
out.dy = 0;
|
|
579
|
+
return true;
|
|
580
|
+
}
|
|
581
|
+
const r = import_core5.MOTION.reveal;
|
|
582
|
+
const t = el - this.plan.ideaDelay(i);
|
|
583
|
+
if (t >= r.duration) return false;
|
|
584
|
+
const e = t <= 0 ? 0 : (0, import_core5.easeBezier)(r.ease, t / r.duration);
|
|
585
|
+
const from = (0, import_core5.bloomOffset)({ x, y }, this.center);
|
|
586
|
+
out.alpha = clamp01(e);
|
|
587
|
+
out.scale = r.fromScale + (1 - r.fromScale) * e;
|
|
588
|
+
out.dx = from.x * (1 - e);
|
|
589
|
+
out.dy = from.y * (1 - e);
|
|
590
|
+
return true;
|
|
591
|
+
}
|
|
592
|
+
/** Fills `out` with connector `i`'s arrival look. Returns false when it has arrived. */
|
|
593
|
+
edge(i, dashed, now, out) {
|
|
594
|
+
const el = now - this.t0;
|
|
595
|
+
if (el >= this.end) return false;
|
|
596
|
+
if (this.reduced) {
|
|
597
|
+
out.alpha = clamp01(el / import_core5.MOTION.reduced.fade);
|
|
598
|
+
out.draw = 1;
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
const c = import_core5.MOTION.connector;
|
|
602
|
+
const t = el - this.plan.connectorDelay(i);
|
|
603
|
+
if (t >= c.draw && t >= c.fade) return false;
|
|
604
|
+
const group = t <= 0 ? 0 : (0, import_core5.easeBezier)(c.ease, clamp01(t / c.fade));
|
|
605
|
+
const line = t <= 0 ? 0 : (0, import_core5.easeBezier)(c.ease, clamp01(t / c.draw));
|
|
606
|
+
out.alpha = dashed ? group * line : group;
|
|
607
|
+
out.draw = dashed ? 1 : line;
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
var HoverTweens = class {
|
|
612
|
+
constructor() {
|
|
613
|
+
this.tweens = /* @__PURE__ */ new Map();
|
|
614
|
+
this.raised = /* @__PURE__ */ new Set();
|
|
615
|
+
this.cache = { lift: 0, ring: 0 };
|
|
616
|
+
}
|
|
617
|
+
/** Set which ideas are raised now. Returns true when anything started moving. */
|
|
618
|
+
set(next, reduced, now = performance.now()) {
|
|
619
|
+
let changed = false;
|
|
620
|
+
const ids = /* @__PURE__ */ new Set([...this.raised, ...next]);
|
|
621
|
+
for (const id of ids) {
|
|
622
|
+
const was = this.raised.has(id), is = next.has(id);
|
|
623
|
+
if (was === is) continue;
|
|
624
|
+
const cur = this.progress(id, now);
|
|
625
|
+
this.tweens.set(id, { fromLift: cur.lift, fromRing: cur.ring, to: is ? 1 : 0, t0: reduced ? -Infinity : now });
|
|
626
|
+
changed = true;
|
|
627
|
+
}
|
|
628
|
+
this.raised = new Set(next);
|
|
629
|
+
return changed;
|
|
630
|
+
}
|
|
631
|
+
/** Lift (bloom ease) and ring (standard ease) progress of an idea, 0 at rest and 1 fully raised. */
|
|
632
|
+
progress(id, now) {
|
|
633
|
+
const tw = this.tweens.get(id);
|
|
634
|
+
const out = this.cache;
|
|
635
|
+
if (!tw) {
|
|
636
|
+
out.lift = out.ring = this.raised.has(id) ? 1 : 0;
|
|
637
|
+
return out;
|
|
638
|
+
}
|
|
639
|
+
const u = clamp01((now - tw.t0) / import_core5.MOTION.hover.duration);
|
|
640
|
+
out.lift = tw.fromLift + (tw.to - tw.fromLift) * (0, import_core5.easeBezier)(import_core5.MOTION.hover.liftEase, u);
|
|
641
|
+
out.ring = tw.fromRing + (tw.to - tw.fromRing) * (0, import_core5.easeBezier)(import_core5.MOTION.hover.ringEase, u);
|
|
642
|
+
if (u >= 1) {
|
|
643
|
+
this.tweens.delete(id);
|
|
644
|
+
out.lift = out.ring = tw.to;
|
|
645
|
+
}
|
|
646
|
+
return out;
|
|
647
|
+
}
|
|
648
|
+
/** True while any idea is still moving. */
|
|
649
|
+
active(now = performance.now()) {
|
|
650
|
+
for (const tw of this.tweens.values()) if (now - tw.t0 < import_core5.MOTION.hover.duration) return true;
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
has(id) {
|
|
654
|
+
return this.tweens.has(id) || this.raised.has(id);
|
|
524
655
|
}
|
|
525
656
|
};
|
|
526
|
-
var konvaLayerTransitionAnimator = new KonvaLayerTransitionAnimator();
|
|
527
|
-
|
|
528
|
-
// src/utils/layerNavigation.ts
|
|
529
|
-
var import_core5 = require("@visualli/core");
|
|
530
|
-
|
|
531
|
-
// src/hooks/useKonvaLayerTransition.ts
|
|
532
|
-
function useKonvaLayerTransition(opts) {
|
|
533
|
-
const {
|
|
534
|
-
stageRef,
|
|
535
|
-
canvasWrapperRef,
|
|
536
|
-
bgRef,
|
|
537
|
-
onSwapLayer,
|
|
538
|
-
onSwapBack,
|
|
539
|
-
onComplete,
|
|
540
|
-
onViewportFrame,
|
|
541
|
-
onViewportFinal,
|
|
542
|
-
onPhaseChange: onPhaseChangeExt,
|
|
543
|
-
onTransitionLifecycle
|
|
544
|
-
} = opts;
|
|
545
|
-
const setZoom = useViewportStore((s) => s.setZoom);
|
|
546
|
-
const setCenter = useViewportStore((s) => s.setCenter);
|
|
547
|
-
const getState = useViewportStore.getState;
|
|
548
|
-
const swappedRef = (0, import_react2.useRef)(false);
|
|
549
|
-
const canvasSizeRef = (0, import_react2.useRef)({ width: 800, height: 600 });
|
|
550
|
-
(0, import_react2.useEffect)(() => {
|
|
551
|
-
const update = () => {
|
|
552
|
-
const vp = useViewportStore.getState();
|
|
553
|
-
canvasSizeRef.current = { width: vp.canvasWidth || 800, height: vp.canvasHeight || 600 };
|
|
554
|
-
};
|
|
555
|
-
update();
|
|
556
|
-
const unsubscribe = useViewportStore.subscribe(update);
|
|
557
|
-
return unsubscribe;
|
|
558
|
-
}, []);
|
|
559
|
-
const applyViewport = (0, import_react2.useCallback)((vp) => {
|
|
560
|
-
const stage = stageRef.current;
|
|
561
|
-
if (!stage) return;
|
|
562
|
-
const { width, height } = canvasSizeRef.current;
|
|
563
|
-
stage.x(width / 2 - vp.centerX * vp.zoomLevel);
|
|
564
|
-
stage.y(height / 2 - vp.centerY * vp.zoomLevel);
|
|
565
|
-
stage.scaleX(vp.zoomLevel);
|
|
566
|
-
stage.scaleY(vp.zoomLevel);
|
|
567
|
-
stage.getLayers().forEach((l) => l.drawScene());
|
|
568
|
-
setCenter(vp.centerX, vp.centerY);
|
|
569
|
-
setZoom(vp.zoomLevel);
|
|
570
|
-
}, [stageRef, setCenter, setZoom]);
|
|
571
|
-
const applyOpacity = (0, import_react2.useCallback)((opacity) => {
|
|
572
|
-
if (canvasWrapperRef.current) {
|
|
573
|
-
canvasWrapperRef.current.style.opacity = String(opacity);
|
|
574
|
-
}
|
|
575
|
-
}, [canvasWrapperRef]);
|
|
576
|
-
const applyBgColor = (0, import_react2.useCallback)((color) => {
|
|
577
|
-
if (bgRef?.current) {
|
|
578
|
-
bgRef.current.style.backgroundColor = color;
|
|
579
|
-
}
|
|
580
|
-
}, [bgRef]);
|
|
581
|
-
const zoomIntoLayer = (0, import_react2.useCallback)((_doc, targetNode, childLayerId, childNodes, fromColor, toColor) => {
|
|
582
|
-
if (konvaLayerTransitionAnimator.active) return;
|
|
583
|
-
const vp = getState();
|
|
584
|
-
const stage = stageRef.current;
|
|
585
|
-
const w = stage?.width() ?? 800;
|
|
586
|
-
const h = stage?.height() ?? 600;
|
|
587
|
-
const fit = (0, import_core5.calculateFitView)(childNodes, w, h);
|
|
588
|
-
swappedRef.current = false;
|
|
589
|
-
onTransitionLifecycle?.("start");
|
|
590
|
-
konvaLayerTransitionAnimator.animateZoomIntoLayer(
|
|
591
|
-
{ centerX: vp.centerX, centerY: vp.centerY, zoomLevel: vp.zoomLevel },
|
|
592
|
-
{ x: targetNode.x, y: targetNode.y, width: targetNode.width, height: targetNode.height },
|
|
593
|
-
{ x: fit.centerX, y: fit.centerY },
|
|
594
|
-
fromColor,
|
|
595
|
-
toColor,
|
|
596
|
-
{
|
|
597
|
-
onUpdate: ({ viewport, opacity, backgroundColor }) => {
|
|
598
|
-
if (onViewportFrame) {
|
|
599
|
-
onViewportFrame(viewport);
|
|
600
|
-
} else {
|
|
601
|
-
applyViewport(viewport);
|
|
602
|
-
}
|
|
603
|
-
applyOpacity(opacity);
|
|
604
|
-
applyBgColor(backgroundColor);
|
|
605
|
-
},
|
|
606
|
-
onComplete: () => {
|
|
607
|
-
const finalVp = { centerX: fit.centerX, centerY: fit.centerY, zoomLevel: fit.zoomLevel };
|
|
608
|
-
if (onViewportFinal) {
|
|
609
|
-
onViewportFinal(finalVp);
|
|
610
|
-
} else {
|
|
611
|
-
applyViewport(finalVp);
|
|
612
|
-
}
|
|
613
|
-
applyOpacity(1);
|
|
614
|
-
onComplete();
|
|
615
|
-
onTransitionLifecycle?.("end");
|
|
616
|
-
},
|
|
617
|
-
onPhaseChange: (phase) => {
|
|
618
|
-
onPhaseChangeExt?.(phase);
|
|
619
|
-
if (phase === "swap" && !swappedRef.current) {
|
|
620
|
-
swappedRef.current = true;
|
|
621
|
-
onSwapLayer(childLayerId);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
},
|
|
625
|
-
fit.zoomLevel,
|
|
626
|
-
w,
|
|
627
|
-
h
|
|
628
|
-
);
|
|
629
|
-
}, [stageRef, getState, applyViewport, applyOpacity, applyBgColor, onSwapLayer, onComplete, onViewportFrame, onViewportFinal, onPhaseChangeExt, onTransitionLifecycle]);
|
|
630
|
-
const zoomOutToParent = (0, import_react2.useCallback)((parentViewport, fromColor, toColor) => {
|
|
631
|
-
if (konvaLayerTransitionAnimator.active) return;
|
|
632
|
-
const vp = getState();
|
|
633
|
-
swappedRef.current = false;
|
|
634
|
-
onTransitionLifecycle?.("start");
|
|
635
|
-
konvaLayerTransitionAnimator.animateZoomOutToParent(
|
|
636
|
-
{ centerX: vp.centerX, centerY: vp.centerY, zoomLevel: vp.zoomLevel },
|
|
637
|
-
parentViewport,
|
|
638
|
-
fromColor,
|
|
639
|
-
toColor,
|
|
640
|
-
{
|
|
641
|
-
onUpdate: ({ viewport, opacity, backgroundColor }) => {
|
|
642
|
-
if (onViewportFrame) {
|
|
643
|
-
onViewportFrame(viewport);
|
|
644
|
-
} else {
|
|
645
|
-
applyViewport(viewport);
|
|
646
|
-
}
|
|
647
|
-
applyOpacity(opacity);
|
|
648
|
-
applyBgColor(backgroundColor);
|
|
649
|
-
},
|
|
650
|
-
onComplete: () => {
|
|
651
|
-
if (onViewportFinal) {
|
|
652
|
-
onViewportFinal(parentViewport);
|
|
653
|
-
} else {
|
|
654
|
-
applyViewport(parentViewport);
|
|
655
|
-
}
|
|
656
|
-
applyOpacity(1);
|
|
657
|
-
onComplete();
|
|
658
|
-
onTransitionLifecycle?.("end");
|
|
659
|
-
},
|
|
660
|
-
onPhaseChange: (phase) => {
|
|
661
|
-
onPhaseChangeExt?.(phase);
|
|
662
|
-
if (phase === "zoom-back" && !swappedRef.current) {
|
|
663
|
-
swappedRef.current = true;
|
|
664
|
-
onSwapBack();
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
);
|
|
669
|
-
}, [getState, applyViewport, applyOpacity, applyBgColor, onSwapBack, onComplete, onViewportFrame, onViewportFinal, onPhaseChangeExt, onTransitionLifecycle]);
|
|
670
|
-
const isTransitioning = (0, import_react2.useCallback)(() => konvaLayerTransitionAnimator.active, []);
|
|
671
|
-
(0, import_react2.useEffect)(() => () => {
|
|
672
|
-
konvaLayerTransitionAnimator.cancel();
|
|
673
|
-
}, []);
|
|
674
|
-
return { zoomIntoLayer, zoomOutToParent, isTransitioning };
|
|
675
|
-
}
|
|
676
657
|
|
|
677
658
|
// src/components/KonvaStage.tsx
|
|
678
|
-
var
|
|
659
|
+
var import_react4 = require("react");
|
|
679
660
|
|
|
680
661
|
// src/konvaCompat.ts
|
|
681
662
|
var import_react_konva = require("react-konva");
|
|
682
663
|
var KStage = import_react_konva.Stage;
|
|
683
664
|
var KLayer = import_react_konva.Layer;
|
|
684
|
-
var KGroup = import_react_konva.Group;
|
|
685
665
|
var KShape = import_react_konva.Shape;
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
var
|
|
689
|
-
|
|
666
|
+
|
|
667
|
+
// src/components/KonvaStage.tsx
|
|
668
|
+
var import_konva = __toESM(require("konva"), 1);
|
|
669
|
+
|
|
670
|
+
// src/design/runtime.ts
|
|
671
|
+
var import_react3 = require("react");
|
|
672
|
+
var import_core6 = require("@visualli/core");
|
|
673
|
+
|
|
674
|
+
// src/generated/specCss.ts
|
|
675
|
+
var SPEC_TOKENS_CSS = '/* Spec tokens for all 8 themes \u2014 generated from the Visualli design system (v0.2.0). Do not edit here: changes come in through PRs from the private design system. MIT license, see ../LICENSE. */\n:root, [data-theme="light"] {\n --font-display: "Atkinson Hyperlegible Next", system-ui, sans-serif;\n --font-ui: "Atkinson Hyperlegible Next", system-ui, -apple-system, "Segoe UI", sans-serif;\n --font-hand: Kalam, "Atkinson Hyperlegible Next", cursive;\n --font-note: Caveat, Kalam, cursive;\n --font-mono: "Atkinson Hyperlegible Mono", ui-monospace, "SF Mono", Menlo, monospace;\n --canvas: #f6f3ee;\n --surface: #ffffff;\n --surface-sunken: #f0ece5;\n --surface-overlay: #ffffff;\n --line: #e4dfd6;\n --line-strong: #7a7f94;\n --ink: #182140;\n --ink-muted: #4f5671;\n --ink-subtle: #5f647c;\n --action: #182140;\n --on-action: #ffffff;\n --focus: #325e8c;\n --link: #2f5a87;\n --selection: rgba(18, 199, 211, 0.28);\n --node-ink: #182140;\n --edge: #2a3048;\n --edge-label: #4f5671;\n --topic-teal: #87f2f8;\n --topic-teal-ring: #00cdd5;\n --topic-harbor: #bcdbff;\n --topic-harbor-ring: #76b2f5;\n --topic-iris: #e0c8fe;\n --topic-iris-ring: #bd96ea;\n --topic-berry: #ffbdc4;\n --topic-berry-ring: #f47e8f;\n --topic-coral: #ffc7b2;\n --topic-coral-ring: #f38e65;\n --topic-amber: #ffcf9d;\n --topic-amber-ring: #eb9c38;\n --topic-sun: #ffea94;\n --topic-sun-ring: #dfbd04;\n --topic-stone: #e0dacf;\n --topic-stone-ring: #bab09f;\n --space-1: 4px;\n --space-2: 8px;\n --space-3: 12px;\n --space-4: 16px;\n --space-7: 48px;\n --radius-sm: 10px;\n --radius-md: 16px;\n --radius-lg: 24px;\n --radius-pill: 999px;\n --shadow-float: 0 1px 2px rgba(24, 33, 64, 0.06), 0 8px 24px rgba(24, 33, 64, 0.08);\n --shadow-pop: 0 2px 4px rgba(24, 33, 64, 0.08), 0 16px 40px rgba(24, 33, 64, 0.14);\n --node-width: 200px;\n --node-height: 148px;\n --node-stroke: 3px;\n --edge-width: 2.5px;\n --edge-gap: 15px;\n --edge-arc-max: 45px;\n --immersion-alpha: 0.15;\n --zoom-min: 0.3;\n --zoom-max: 5;\n --duration-instant: 100ms;\n --duration-quick: 160ms;\n --duration-base: 240ms;\n --duration-zoom: 500ms;\n --duration-reveal: 420ms;\n --stagger-reveal: 70ms;\n --ease-zoom: cubic-bezier(0.2, 0.8, 0.2, 1);\n --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);\n --ease-bloom: cubic-bezier(0.34, 1.4, 0.64, 1);\n --z-chrome: 10;\n --z-card: 20;\n --z-menu: 40;\n }\n[data-theme="dark"] {\n --canvas: #0f1220;\n --surface: #171b2b;\n --surface-sunken: #0f1220;\n --surface-overlay: #212639;\n --line: #2b3148;\n --line-strong: #6c7392;\n --ink: #eef0f7;\n --ink-muted: #b3b8cc;\n --ink-subtle: #8f95ae;\n --action: #f6f3ee;\n --on-action: #182140;\n --focus: #12c7d3;\n --link: #5fd8e1;\n --selection: rgba(18, 199, 211, 0.32);\n --edge: #c9cddc;\n --edge-label: #b3b8cc;\n --topic-teal-ring: #0fc5cd;\n --topic-harbor-ring: #73aceb;\n --topic-iris-ring: #b691e1;\n --topic-berry-ring: #eb798a;\n --topic-coral-ring: #ea8962;\n --topic-amber-ring: #e29737;\n --topic-sun-ring: #d8b70a;\n --topic-stone-ring: #b4a999;\n --shadow-float: 0 1px 2px rgba(0, 0, 0, 0.40), 0 8px 24px rgba(0, 0, 0, 0.35);\n --shadow-pop: 0 2px 4px rgba(0, 0, 0, 0.50), 0 16px 40px rgba(0, 0, 0, 0.50);\n }\n[data-theme="focus-light"] {\n --canvas: #f4f2ee;\n --edge: #5a6079;\n --edge-label: #5a6079;\n --topic-teal: #bce8ea;\n --topic-teal-ring: #67c6cb;\n --topic-harbor: #c9d9ec;\n --topic-harbor-ring: #8bb2de;\n --topic-iris: #d9cfe8;\n --topic-iris-ring: #b79ed4;\n --topic-berry: #edc6c9;\n --topic-berry-ring: #da9099;\n --topic-coral: #eecec1;\n --topic-coral-ring: #db9b82;\n --topic-amber: #eed4ba;\n --topic-amber-ring: #d6a570;\n --topic-sun: #f4eac1;\n --topic-sun-ring: #d2bf73;\n --topic-stone: #dddad5;\n }\n[data-theme="focus-dark"] {\n --canvas: #12141c;\n --surface: #171b2b;\n --surface-sunken: #0f1220;\n --surface-overlay: #212639;\n --line: #2b3148;\n --line-strong: #6c7392;\n --ink: #eef0f7;\n --ink-muted: #b3b8cc;\n --ink-subtle: #8f95ae;\n --action: #f6f3ee;\n --on-action: #182140;\n --focus: #12c7d3;\n --link: #5fd8e1;\n --selection: rgba(18, 199, 211, 0.32);\n --edge: #a9aec0;\n --edge-label: #b3b8cc;\n --topic-teal: #bce8ea;\n --topic-teal-ring: #71bdc1;\n --topic-harbor: #c9d9ec;\n --topic-harbor-ring: #8cabce;\n --topic-iris: #d9cfe8;\n --topic-iris-ring: #ae9bc6;\n --topic-berry: #edc6c9;\n --topic-berry-ring: #cc8e95;\n --topic-coral: #eecec1;\n --topic-coral-ring: #cd9884;\n --topic-amber: #eed4ba;\n --topic-amber-ring: #c9a176;\n --topic-sun: #f4eac1;\n --topic-sun-ring: #c9b97a;\n --topic-stone: #dddad5;\n --topic-stone-ring: #b4a999;\n }\n[data-theme="colorsafe-light"] {\n --topic-teal: #b0ecbc;\n --topic-teal-ring: #6cc982;\n --topic-harbor: #abdbfe;\n --topic-harbor-ring: #60b4ec;\n --topic-iris: #f6bde2;\n --topic-iris-ring: #dc87c0;\n --topic-berry: #feb79e;\n --topic-berry-ring: #ec7f58;\n --topic-coral: #fece96;\n --topic-coral-ring: #e49d41;\n --topic-amber: #b2e8fa;\n --topic-amber-ring: #5cc4e2;\n --topic-sun: #f6f097;\n --topic-sun-ring: #d0c637;\n --topic-stone: #dbdbdb;\n --topic-stone-ring: #b7afb1;\n }\n[data-theme="colorsafe-dark"] {\n --canvas: #0f1220;\n --surface: #171b2b;\n --surface-sunken: #0f1220;\n --surface-overlay: #212639;\n --line: #2b3148;\n --line-strong: #6c7392;\n --ink: #eef0f7;\n --ink-muted: #b3b8cc;\n --ink-subtle: #8f95ae;\n --action: #f6f3ee;\n --on-action: #182140;\n --focus: #12c7d3;\n --link: #5fd8e1;\n --selection: rgba(18, 199, 211, 0.32);\n --edge: #c9cddc;\n --edge-label: #b3b8cc;\n --topic-teal: #b0ecbc;\n --topic-teal-ring: #69c27e;\n --topic-harbor: #abdbfe;\n --topic-harbor-ring: #5eade2;\n --topic-iris: #f6bde2;\n --topic-iris-ring: #d383b9;\n --topic-berry: #feb79e;\n --topic-berry-ring: #e37a55;\n --topic-coral: #fece96;\n --topic-coral-ring: #db9840;\n --topic-amber: #b2e8fa;\n --topic-amber-ring: #5bbdda;\n --topic-sun: #f6f097;\n --topic-sun-ring: #c9bf37;\n --topic-stone: #dbdbdb;\n --topic-stone-ring: #b1a8aa;\n }\n[data-theme="contrast-light"] {\n --canvas: #ffffff;\n --surface-sunken: #f2f2f2;\n --line: #5c6178;\n --line-strong: #000000;\n --ink: #000000;\n --ink-muted: #1f2433;\n --ink-subtle: #3a3f52;\n --action: #000000;\n --focus: #0b4a8f;\n --link: #0b4a8f;\n --node-ink: #000000;\n --edge: #000000;\n --edge-label: #1f2433;\n --topic-teal-ring: #006b70;\n --topic-harbor-ring: #0e599d;\n --topic-iris-ring: #683b92;\n --topic-berry-ring: #96173a;\n --topic-coral-ring: #923400;\n --topic-amber-ring: #7e4d00;\n --topic-sun-ring: #7a6600;\n --topic-stone-ring: #63594b;\n --shadow-float: none;\n --shadow-pop: none;\n }\n[data-theme="contrast-dark"] {\n --canvas: #000000;\n --surface: #000000;\n --surface-sunken: #0d0d0d;\n --surface-overlay: #0d0d0d;\n --line: #9aa0b8;\n --line-strong: #ffffff;\n --ink: #ffffff;\n --ink-muted: #e6e8f0;\n --ink-subtle: #c8ccda;\n --action: #ffffff;\n --on-action: #000000;\n --focus: #5fe8f0;\n --link: #7ff0f5;\n --selection: rgba(18, 199, 211, 0.32);\n --node-ink: #000000;\n --edge: #ffffff;\n --edge-label: #e6e8f0;\n --topic-teal-ring: #64eff7;\n --topic-harbor-ring: #b0d5fe;\n --topic-iris-ring: #dcbfff;\n --topic-berry-ring: #feb3bb;\n --topic-coral-ring: #ffbea5;\n --topic-amber-ring: #ffc78a;\n --topic-sun-ring: #ffe26d;\n --topic-stone-ring: #ddd3c2;\n --shadow-float: none;\n --shadow-pop: none;\n }\n[data-motion="reduced"] {\n --duration-instant: 120ms;\n --duration-quick: 0ms;\n --duration-base: 0ms;\n --duration-zoom: 0ms;\n --duration-reveal: 0ms;\n --stagger-reveal: 0ms;\n }\n@media (prefers-reduced-motion: reduce) {\n :root:not([data-motion="full"]) {\n --duration-instant: 120ms;\n --duration-quick: 0ms;\n --duration-base: 0ms;\n --duration-zoom: 0ms;\n --duration-reveal: 0ms;\n --stagger-reveal: 0ms;\n }\n}\n\n/* Type styles */\n.heading { font-family: var(--font-ui); font-size: 17px; line-height: 24px; font-weight: 700; }\n.body { font-family: var(--font-ui); font-size: 15px; line-height: 22px; font-weight: 400; }\n.body-sm { font-family: var(--font-ui); font-size: 13px; line-height: 18px; font-weight: 400; }\n.label { font-family: var(--font-ui); font-size: 13px; line-height: 16px; font-weight: 600; letter-spacing: 0.01em; }\n.caption { font-family: var(--font-ui); font-size: 12px; line-height: 16px; font-weight: 400; }\n.node-root { font-family: var(--font-hand); font-size: 30px; line-height: 34px; font-weight: 700; }\n.node-label { font-family: var(--font-hand); font-size: 22px; line-height: 26px; font-weight: 700; }\n.edge-label { font-family: var(--font-note); font-size: 18px; line-height: 20px; font-weight: 500; }\n.annotation { font-family: var(--font-note); font-size: 24px; line-height: 26px; font-weight: 600; }\n';
|
|
676
|
+
var SPEC_COMPONENT_CSS = '/* Styles for the spec components (map, nodes, connectors, peek, terms, depth trail, controls) \u2014 generated from the Visualli design system (v0.2.0). Do not edit here: changes come in through PRs from the private design system. MIT license, see ../LICENSE. */\n:root { --vi-fs-body: 15px; --vi-fs-sm: 13px; --vi-fs-cap: 12px; --vi-lh-body: 22px; --vi-label-scale: 1; color-scheme: light; }\n[data-theme$="dark"] { color-scheme: dark; }\n[data-scale="lg"] { --vi-fs-body: 17px; --vi-fs-sm: 15px; --vi-fs-cap: 14px; --vi-lh-body: 26px; --vi-label-scale: 1.15; }\n[data-type="readable"] { --font-hand: "Atkinson Hyperlegible Next", system-ui, sans-serif; --font-note: "Atkinson Hyperlegible Next", system-ui, sans-serif; --font-display: "Atkinson Hyperlegible Next", system-ui, sans-serif; letter-spacing: 0.01em; }\n[data-type="readable"] .vi-node__label, [data-type="readable"] .vi-edge__label, [data-type="readable"] .vi-map__group-label { font-family: var(--font-ui) !important; font-weight: 700; letter-spacing: 0.02em; font-style: normal; }\n\n.vi-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }\n.vi-icon { flex: none; display: block; }\n.vi-map :where(button, a, input, textarea, [tabindex]):focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }\n.vi-iconbtn { display: inline-grid; place-items: center; border-radius: var(--radius-sm); border: 1.5px solid transparent; background: transparent; color: var(--ink); cursor: pointer; transition: background var(--duration-instant); flex: none; }\n.vi-iconbtn--sm { width: 32px; height: 32px; }\n.vi-iconbtn--md { width: 40px; height: 40px; }\n.vi-iconbtn--lg { width: 48px; height: 48px; border-radius: var(--radius-md); }\n.vi-iconbtn:hover { background: var(--surface-sunken); }\n.vi-iconbtn--surface { background: var(--surface); border-color: var(--line); }\n.vi-iconbtn--surface:hover { border-color: var(--line-strong); }\n.vi-iconbtn--primary { background: var(--action); color: var(--on-action); border-radius: var(--radius-pill); }\n.vi-iconbtn.is-active { background: var(--action); color: var(--on-action); }\n[data-theme^="contrast"] .vi-ctrls { border: 2px solid var(--line-strong); }\n.vi-node { position: relative; display: inline-grid; place-items: center; border: 0; padding: 0; background: none; color: var(--node-ink); cursor: default; transition: transform var(--duration-base) var(--ease-bloom), opacity var(--duration-base); -webkit-tap-highlight-color: transparent; }\nbutton.vi-node { cursor: pointer; }\n.vi-node__svg { position: absolute; inset: 0; overflow: visible; }\n.vi-node__body { filter: drop-shadow(var(--vi-node-shadow, 0 6px 14px rgba(24, 33, 64, .10))); }\n[data-theme$="dark"] .vi-node__body { --vi-node-shadow: 0 0 22px rgba(255, 255, 255, .10); }\n[data-theme^="contrast"] .vi-node__body { filter: none; }\n.vi-node__rings { transform-origin: center; transition: transform var(--duration-base) var(--ease-standard); }\n@media (hover: hover) {\n button.vi-node:hover .vi-node__rings { transform: rotate(var(--vi-ring-turn, -2deg)) scale(var(--vi-ring-grow, 1.015)); }\n button.vi-node:hover { transform: translateY(calc(var(--vi-lift, 2px) * -1)); }\n}\n.vi-node { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; touch-action: manipulation; }\n.vi-node.is-selected { transform: translateY(-2px); }\n.vi-node.is-selected .vi-node__rings { transform: rotate(var(--vi-ring-turn, -2deg)) scale(var(--vi-ring-grow, 1.015)); }\n.vi-node.is-selected .vi-node__body { stroke-width: 4; }\n.vi-node:focus-visible { outline: none; }\n.vi-node:focus-visible .vi-node__body { stroke: var(--focus); stroke-width: 4; }\n.vi-node.is-selected .vi-node__body { stroke: var(--ink); }\n.vi-node__label { position: relative; text-align: center; color: var(--node-ink); display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: calc(var(--vi-nfs, 22px) * var(--vi-label-scale) * var(--vi-ninv, 1)); line-height: 1.15; padding-top: 2px; }\n.vi-node__label.node-root { --vi-nfs: 30px; }\n.vi-node--mini .vi-node__label { --vi-nfs: 15px; }\n.vi-node.is-dimmed { opacity: 1; }\n[data-theme^="focus"] .vi-node.is-dimmed { opacity: .4; }\n.vi-map.is-touch .vi-node.is-dimmed { opacity: .6; }\n[data-theme^="focus"] .vi-map.is-touch .vi-node.is-dimmed { opacity: .4; }\n.vi-edge__line { fill: none; stroke: var(--edge); stroke-width: 2.5; stroke-linecap: round; }\n.vi-edge.is-dashed .vi-edge__line { stroke-dasharray: 7 7; }\n.vi-edge__arrow { fill: none; stroke: var(--edge); stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; }\n.vi-edge__label { font: 500 calc(18px * var(--vi-inv, 1)) / 1 var(--font-note); fill: var(--edge-label); paint-order: stroke; stroke: var(--canvas); stroke-width: 5px; stroke-linejoin: round; letter-spacing: .01em; }\n[data-type="readable"] .vi-edge__label { font-size: calc(14px * var(--vi-inv, 1)); font-weight: 600; }\n.vi-fact { position: relative; width: 300px; box-sizing: border-box; padding: var(--space-4) var(--space-4) var(--space-3); background: var(--surface-overlay); color: var(--ink); border: 2px solid var(--vi-fact-ring); border-radius: 14px 18px 12px 16px / 16px 12px 18px 14px; font: 400 var(--vi-fs-sm)/20px var(--font-ui); z-index: var(--z-card); animation: vi-pop var(--duration-quick) var(--ease-standard); }\n.vi-fact { box-shadow: 5px 6px 0 0 var(--vi-fact-fill), 5px 6px 0 2px var(--vi-fact-ring); }\n[data-theme^="contrast"] .vi-fact { border-color: var(--ink); }\n[data-theme^="contrast"] .vi-fact { box-shadow: none; }\n.vi-fact__title { font: 700 22px/26px var(--font-hand); margin-bottom: var(--space-1); }\n.vi-fact__body { color: var(--ink); }\n.vi-fact__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-top: var(--space-3); padding-top: var(--space-2); border-top: 1px dashed var(--line); }\n.vi-fact__acts { display: inline-flex; gap: var(--space-3); align-items: center; flex: none; }\n.vi-fact__link { color: var(--link); font: 600 var(--vi-fs-cap)/16px var(--font-ui); }\n.vi-fact__step { display: inline-flex; align-items: center; gap: 2px; border: 0; background: var(--action); color: var(--on-action); font: 600 var(--vi-fs-cap)/16px var(--font-ui); padding: 5px 8px 5px 10px; border-radius: var(--radius-pill); cursor: pointer; }\n.vi-trail ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; position: relative; }\n.vi-trail li { position: relative; }\n.vi-trail li + li::before { content: ""; position: absolute; left: 13px; top: -8px; height: 10px; border-left: 1.5px dashed var(--line-strong); }\n.vi-trail button { display: inline-flex; align-items: center; gap: 6px; border: 0; background: none; padding: 4px 8px 4px 5px; border-radius: var(--radius-sm); color: var(--ink-muted); font: 400 var(--vi-fs-cap)/16px var(--font-ui); cursor: pointer; }\n.vi-trail button span { display: inline-flex; align-items: center; gap: 4px; }\n.vi-trail button:hover:not(:disabled) { background: var(--surface-sunken); color: var(--ink); }\n.vi-trail li.is-current button { color: var(--ink); font-weight: 700; font-size: var(--vi-fs-sm); cursor: default; background: var(--surface); box-shadow: var(--shadow-float); }\n.vi-trail li.is-current button:disabled { opacity: 1; }\n.vi-ctrls { display: inline-flex; align-items: center; gap: 2px; padding: 4px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-md); box-shadow: var(--shadow-float); }\n.vi-ctrls__pct { min-width: 44px; text-align: center; font: 600 var(--vi-fs-cap)/16px var(--font-ui); color: var(--ink-muted); font-variant-numeric: tabular-nums; }\n.vi-ctrls__div { width: 1px; height: 20px; background: var(--line); margin: 0 4px; }\n.vi-map { position: relative; overflow: hidden; background: var(--canvas); border-radius: inherit; isolation: isolate; }\n.vi-map__immersion { position: absolute; inset: 0; opacity: var(--immersion-alpha); transition: background var(--duration-base); z-index: -1; }\n.vi-map__world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }\n.vi-map__edges { position: absolute; overflow: visible; pointer-events: none; }\n.vi-map__group rect { fill: none; stroke: var(--line-strong); stroke-width: 1.5; stroke-dasharray: 10 6; }\n.vi-map__group-label { position: absolute; transform: translate(-50%, -50%); z-index: 2; max-width: 420px; box-sizing: border-box; padding: 6px 22px 8px; background: var(--topic-stone); border: 1.5px solid var(--topic-stone-ring); border-radius: var(--radius-pill); font: 700 22px/1.1 var(--font-note); color: var(--node-ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; pointer-events: none; font-size: calc(26px * var(--vi-label-scale) * var(--vi-ninv, 1)); }\n[data-theme^="contrast"] .vi-map__group-label { border: 2px solid var(--line-strong); }\n.vi-map__slot { position: absolute; transform: translate(-50%, -50%); }\n.vi-map.is-reveal .vi-map__slot { animation: vi-bloom var(--duration-reveal) var(--ease-bloom) backwards; }\n.vi-map.is-reveal .vi-edge { animation: vi-fade var(--duration-base) var(--ease-standard) backwards; }\n.vi-map.is-reveal .vi-edge__line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: vi-line var(--vi-line-draw, .5s) var(--ease-standard) forwards; animation-delay: inherit; }\n.vi-map.is-reveal .vi-edge.is-dashed .vi-edge__line { stroke-dasharray: 7 7; stroke-dashoffset: 0; animation: vi-fade var(--vi-line-draw, .5s) var(--ease-standard) backwards; animation-delay: inherit; }\n.vi-map:not(.is-reveal) .vi-edge__line { stroke-dasharray: none; }\n.vi-map:not(.is-reveal) .vi-edge.is-dashed .vi-edge__line { stroke-dasharray: 7 7; }\n.vi-map__lens { position: absolute; inset: 0; transform-origin: var(--vi-ox) var(--vi-oy); }\n.vi-map.is-diving .vi-map__lens { transform: scale(var(--vi-dive-scale, 3.2)); opacity: 0; transition: transform var(--duration-zoom) var(--ease-zoom), opacity var(--vi-dive-fade, 160ms) var(--vi-dive-fade-delay, 150ms); }\n.vi-map.is-surfacing .vi-map__lens { transform: scale(var(--vi-surface-scale, .6)); opacity: 0; transform-origin: 50% 50%; transition: transform var(--vi-surface-dur, 220ms) var(--ease-zoom), opacity var(--vi-surface-fade, 200ms); }\n.vi-map.is-reveal .vi-map__lens { animation: vi-settle var(--vi-settle-dur, 260ms) var(--ease-standard); }\n[data-motion="reduced"] .vi-map__lens { animation: none !important; transform: none !important; }\n.vi-map__fact { position: absolute; }\n.vi-map.is-idle .vi-map__world { transition: transform var(--duration-base) var(--ease-standard); }\n.vi-map__hint { position: absolute; left: var(--space-4); right: var(--space-4); margin: 0; text-align: center; color: var(--ink-muted); pointer-events: none; z-index: var(--z-chrome); animation: vi-fade var(--duration-base) var(--ease-standard); }\n.vi-fact__head { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-2); }\n.vi-fact__back { display: inline-flex; align-items: center; gap: 2px; border: 0; background: none; padding: 2px 0; margin-bottom: var(--space-1); color: var(--link); font: 600 var(--vi-fs-cap)/16px var(--font-ui); cursor: pointer; }\n.vi-term { border: 0; background: none; padding: 0; font: inherit; color: var(--link); text-decoration: underline; text-decoration-style: dotted; text-decoration-thickness: 1.5px; text-underline-offset: 3px; cursor: pointer; }\n.vi-fact--sheet { position: absolute; left: 0; right: 0; bottom: 0; width: auto; max-width: 560px; margin: 0 auto; max-height: var(--vi-sheet-h, 300px); overflow-y: auto; overscroll-behavior: contain; padding: 0 var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom, 0px)); border: 0; border-top: 2px solid var(--vi-fact-ring); border-radius: var(--radius-lg) var(--radius-lg) 0 0; box-shadow: 0 -6px 0 0 var(--vi-fact-fill), var(--shadow-pop); font-size: var(--vi-fs-body); line-height: var(--vi-lh-body); animation: vi-sheet-in var(--duration-base) var(--ease-standard); transition: transform var(--duration-quick) var(--ease-standard), max-height var(--duration-base) var(--ease-standard); }\n.vi-fact--sheet.is-expanded { max-height: calc(100% - var(--space-7)); }\n.vi-fact__grip { position: sticky; top: 0; display: flex; justify-content: center; margin: 0 calc(var(--space-4) * -1); background: var(--surface-overlay); touch-action: none; cursor: grab; z-index: 1; }\n.vi-fact__handle { position: relative; width: 64px; height: 24px; border: 0; padding: 0; background: none; cursor: pointer; }\n.vi-fact__handle::after { content: ""; position: absolute; left: 12px; right: 12px; top: 10px; height: 5px; border-radius: var(--radius-pill); background: var(--line-strong); }\n.vi-fact--sheet .vi-fact__title { font-size: calc(24px * var(--vi-label-scale)); line-height: 1.15; padding-top: var(--space-1); }\n.vi-fact--sheet .vi-fact__close { margin: 0 calc(var(--space-2) * -1) 0 0; }\n.vi-fact--sheet .vi-fact__step { min-height: 44px; padding: 0 var(--space-3) 0 var(--space-4); font-size: var(--vi-fs-sm); }\n.vi-fact--sheet .vi-fact__link { font-size: var(--vi-fs-sm); min-height: 44px; display: inline-flex; align-items: center; }\n[data-motion="reduced"] .vi-fact--sheet { animation: vi-fade 120ms; }\n@media (prefers-reduced-motion: reduce) {\n .vi-fact--sheet { animation: vi-fade 120ms; }\n .vi-map.is-idle .vi-map__world { transition: none; }\n}\n.vi-map__trail { position: absolute; top: var(--space-4); left: var(--space-4); z-index: var(--z-chrome); }\n.vi-map__ctrls { position: absolute; top: var(--space-4); right: var(--space-4); z-index: var(--z-chrome); }\n[data-reveal="instant"] .vi-map.is-reveal .vi-map__slot, [data-motion="reduced"] .vi-map.is-reveal .vi-map__slot { animation: vi-fade var(--vi-reduced-fade, 120ms) backwards; animation-delay: 0ms !important; }\n[data-reveal="instant"] .vi-map.is-reveal .vi-edge, [data-reveal="instant"] .vi-map.is-reveal .vi-edge__line, [data-motion="reduced"] .vi-map.is-reveal .vi-edge, [data-motion="reduced"] .vi-map.is-reveal .vi-edge__line { animation: none; stroke-dashoffset: 0; }\n@media (prefers-reduced-motion: reduce) {\n .vi-map.is-reveal .vi-map__slot { animation: vi-fade var(--vi-reduced-fade, 120ms) backwards; animation-delay: 0ms !important; }\n .vi-map.is-reveal .vi-edge, .vi-map.is-reveal .vi-edge__line { animation: none; stroke-dashoffset: 0; }\n}\n[data-motion="reduced"] *, [data-motion="reduced"] *::before, [data-motion="reduced"] *::after { transition-duration: 1ms !important; }\n.vi-fact__acts { flex-wrap: wrap; justify-content: flex-end; }\n.vi-fact__link { display: inline-flex; align-items: center; gap: 2px; }\n.vi-anchor { position: relative; display: inline; }\n.vi-anchor.is-open .vi-term { background: var(--selection); border-radius: 3px; }\n.vi-anchor__pop { position: absolute; left: -12px; top: calc(100% + 6px); z-index: var(--z-menu); display: block; width: max-content; }\n.vi-anchor-card { position: relative; display: block; width: min(300px, calc(100vw - 32px)); box-sizing: border-box; padding: var(--space-3) var(--space-4) var(--space-3); background: var(--surface-overlay); color: var(--ink); text-align: left; white-space: normal; font: 400 var(--vi-fs-sm)/20px var(--font-ui); border: 2px solid var(--vi-fact-ring); border-radius: 12px 16px 10px 14px / 14px 10px 16px 12px; box-shadow: 4px 5px 0 0 var(--vi-fact-fill), 4px 5px 0 2px var(--vi-fact-ring), var(--shadow-pop); animation: vi-pop var(--duration-quick) var(--ease-standard); }\n.vi-anchor-card.is-in-sheet { width: auto; border: 0; box-shadow: none; padding: 0; animation: none; background: transparent; }\n[data-theme^="contrast"] .vi-anchor-card { border-color: var(--ink); box-shadow: none; }\n.vi-anchor-card__eyebrow { font: 400 11px/14px var(--font-display); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-muted); }\n.vi-anchor-card__word { font: 700 20px/24px var(--font-hand); color: var(--ink); margin: 2px 0 var(--space-1); }\n.vi-fact--sheet .vi-anchor-card__word { font-size: calc(24px * var(--vi-label-scale)); line-height: 1.15; }\n.vi-anchor-card__desc { margin: 0; color: var(--ink); }\n.vi-anchor-card__acts { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-3); padding-top: var(--space-2); border-top: 1px dashed var(--line); }\n.vi-fact--sheet .vi-anchor-card__acts .vi-fact__link { min-height: 44px; }\n[data-type="readable"] .vi-anchor-card__word { font-family: var(--font-ui); }\n[data-motion="reduced"] .vi-anchor-card { animation: vi-fade 120ms; }\n@media (prefers-reduced-motion: reduce) {\n .vi-anchor-card { animation: vi-fade 120ms; }\n}\n.vi-peek .vi-fact__foot { flex-wrap: wrap; row-gap: var(--space-2); }\n.vi-peek .vi-fact__acts { margin-left: auto; }\n.vi-map.has-highlight .vi-node.is-dimmed { opacity: .35; transition: opacity var(--duration-quick) var(--ease-standard); }\n.vi-map.has-highlight .vi-node.is-selected .vi-node__body { stroke: var(--ink); }\n.vi-map { container-type: inline-size; }\n.vi-edge__hit { fill: none; stroke: transparent; stroke-width: 18; pointer-events: stroke; cursor: pointer; }\n.vi-edge__hit:hover + .vi-edge__line { stroke-width: 3.5; }\n.vi-edge.is-selected .vi-edge__line, .vi-edge.is-selected .vi-edge__arrow { stroke: var(--focus); stroke-width: 3.5; }\n.vi-map__group-hit { fill: none; stroke: transparent; stroke-width: 16; pointer-events: stroke; cursor: pointer; }\n.vi-map__group.is-selected rect:not(.vi-map__group-hit) { stroke: var(--focus); stroke-width: 2.5; stroke-dasharray: none; }\n.vi-map__group-label.is-selected { border-color: var(--focus); color: var(--focus); box-shadow: 0 0 0 1px var(--focus); }\n.vi-map { touch-action: pan-x pan-y; }\n.vi-map.is-app { touch-action: none; }\n.vi-map.is-pinching .vi-map__world { transition: none; }\n.vi-map.is-instant.is-reveal .vi-map__slot { animation: vi-fade var(--vi-reduced-fade, 120ms) backwards; animation-delay: 0ms !important; }\n.vi-map.is-instant.is-reveal .vi-edge, .vi-map.is-instant.is-reveal .vi-edge__line { animation: none; stroke-dashoffset: 0; }\n.vi-map.is-instant .vi-node, .vi-map.is-instant .vi-node__rings { transition: none; }\n.vi-map.is-detail-body .vi-node:not(.is-selected):not(:focus-visible):not(:hover) :is(.vi-node__rings, .vi-node__label), .vi-map.is-detail-speck .vi-node:not(.is-selected):not(:focus-visible):not(:hover) :is(.vi-node__rings, .vi-node__label) { display: none; }\n.vi-map.is-detail-body .vi-node:not(.is-selected):not(:focus-visible):not(:hover) .vi-node__body, .vi-map.is-detail-speck .vi-node:not(.is-selected):not(:focus-visible):not(:hover) .vi-node__body, .vi-map.no-shadows .vi-node__body { filter: none; }\n@keyframes vi-pop { from { opacity: 0; transform: translateY(-4px) scale(.98); } }\n@keyframes vi-fade { from { opacity: 0; } }\n@keyframes vi-bloom { from { opacity: 0; transform: translate(calc(-50% + var(--vi-dx)), calc(-50% + var(--vi-dy))) scale(var(--vi-bloom-from, .55)); } }\n@keyframes vi-line { to { stroke-dashoffset: 0; } }\n@keyframes vi-settle { from { transform: scale(var(--vi-settle-from, .94)); } }\n@keyframes vi-sheet-in { from { transform: translateY(100%); } }\n';
|
|
677
|
+
var SPEC_FONTS_URL = "https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800&family=Atkinson+Hyperlegible+Mono:wght@400;600&family=Kalam:wght@400;700&display=swap";
|
|
678
|
+
var SPEC_DESIGN_SYSTEM_VERSION = "0.2.0";
|
|
679
|
+
var SPEC_BUNDLED_FONTS = [
|
|
680
|
+
{
|
|
681
|
+
"family": "Caveat",
|
|
682
|
+
"file": "Caveat-Variable.ttf",
|
|
683
|
+
"weight": "400 700"
|
|
684
|
+
}
|
|
685
|
+
];
|
|
686
|
+
|
|
687
|
+
// src/design/runtime.ts
|
|
688
|
+
var STYLE_ID = "visualli-design-system";
|
|
689
|
+
var LAYOUT_CSS = `
|
|
690
|
+
.vi-map__ctrls { transition: bottom var(--duration-quick) var(--ease-standard); } /* controlsPosition 'bottom-right': rising above the sheet */
|
|
691
|
+
.vi-map.is-compact .vi-trail li:not(:first-child):not(.is-current):not(:nth-last-child(2)) { display: none; }
|
|
692
|
+
.vi-map.is-compact .vi-trail button span { max-width: 40vw; max-width: 40cqw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
693
|
+
.vi-map.is-compact .vi-fact:not(.vi-fact--sheet) { width: min(300px, calc(100cqw - 2 * var(--space-4))); }
|
|
694
|
+
.vi-map.is-touch .vi-trail button { min-height: 32px; }
|
|
695
|
+
`;
|
|
696
|
+
var FONTS_ID = "visualli-design-system-fonts";
|
|
697
|
+
var CAVEAT_ID = "visualli-design-system-caveat";
|
|
698
|
+
var SDK_VERSION = true ? "0.2.0" : "latest";
|
|
699
|
+
var webFontsSheet = Promise.resolve();
|
|
700
|
+
function ensureDesignSystemStyles(assets = {}) {
|
|
701
|
+
if (typeof document === "undefined") return Promise.resolve();
|
|
702
|
+
if (!document.getElementById(STYLE_ID)) {
|
|
703
|
+
const style = document.createElement("style");
|
|
704
|
+
style.id = STYLE_ID;
|
|
705
|
+
style.setAttribute("data-design-system", SPEC_DESIGN_SYSTEM_VERSION);
|
|
706
|
+
style.textContent = `${SPEC_TOKENS_CSS}
|
|
707
|
+
${SPEC_COMPONENT_CSS}
|
|
708
|
+
${LAYOUT_CSS}`;
|
|
709
|
+
document.head.appendChild(style);
|
|
710
|
+
}
|
|
711
|
+
if (assets.loadWebFonts !== false && SPEC_FONTS_URL && !document.getElementById(FONTS_ID)) {
|
|
712
|
+
const link = document.createElement("link");
|
|
713
|
+
link.id = FONTS_ID;
|
|
714
|
+
link.rel = "stylesheet";
|
|
715
|
+
link.href = SPEC_FONTS_URL;
|
|
716
|
+
webFontsSheet = new Promise((resolve) => {
|
|
717
|
+
link.addEventListener("load", () => resolve(), { once: true });
|
|
718
|
+
link.addEventListener("error", () => resolve(), { once: true });
|
|
719
|
+
});
|
|
720
|
+
document.head.appendChild(link);
|
|
721
|
+
}
|
|
722
|
+
if (!document.getElementById(CAVEAT_ID)) {
|
|
723
|
+
const base = (assets.fontBaseUrl ?? `https://cdn.jsdelivr.net/npm/@visualli/react@${SDK_VERSION}/fonts`).replace(/\/$/, "");
|
|
724
|
+
const style = document.createElement("style");
|
|
725
|
+
style.id = CAVEAT_ID;
|
|
726
|
+
style.textContent = SPEC_BUNDLED_FONTS.map((f) => `@font-face { font-family: "${f.family}"; src: url("${base}/${f.file}"); font-weight: ${f.weight}; font-display: swap; }`).join("\n");
|
|
727
|
+
document.head.appendChild(style);
|
|
728
|
+
}
|
|
729
|
+
return webFontsSheet;
|
|
730
|
+
}
|
|
731
|
+
var firstFamily = (stack) => stack.split(",")[0].trim();
|
|
732
|
+
var CANVAS_FONT_FACES = ["node-label", "edge-label", "body"].map((style) => {
|
|
733
|
+
const t = import_core6.TYPE_STYLES[style];
|
|
734
|
+
return `${t.weight} ${t.size}px ${firstFamily(import_core6.TOKENS.light[t.family])}`;
|
|
735
|
+
});
|
|
736
|
+
var fontsEpochValue = 0;
|
|
737
|
+
var fontListeners = /* @__PURE__ */ new Set();
|
|
738
|
+
var fontsEpoch = () => fontsEpochValue;
|
|
739
|
+
function useFontsEpoch() {
|
|
740
|
+
return (0, import_react3.useSyncExternalStore)(onFontsChange, fontsEpoch, () => 0);
|
|
741
|
+
}
|
|
742
|
+
var onFontsChange = (cb) => {
|
|
743
|
+
fontListeners.add(cb);
|
|
744
|
+
return () => fontListeners.delete(cb);
|
|
745
|
+
};
|
|
746
|
+
function loadCanvasFonts(sheet = Promise.resolve(), timeoutMs = 3e3) {
|
|
747
|
+
if (typeof document === "undefined" || !("fonts" in document)) return Promise.resolve();
|
|
748
|
+
const fonts = document.fonts;
|
|
749
|
+
const loads = sheet.then(() => Promise.all(CANVAS_FONT_FACES.map((f) => fonts.load(f).catch(() => [])))).then(() => fonts.ready).then(() => void 0);
|
|
750
|
+
const timeout = new Promise((r) => setTimeout(r, timeoutMs));
|
|
751
|
+
return Promise.race([loads, timeout]).then(() => {
|
|
752
|
+
fontsEpochValue++;
|
|
753
|
+
fontListeners.forEach((cb) => cb());
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
if (typeof document !== "undefined" && "fonts" in document) {
|
|
757
|
+
document.fonts.addEventListener?.("loadingdone", () => {
|
|
758
|
+
fontsEpochValue++;
|
|
759
|
+
fontListeners.forEach((cb) => cb());
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
var currentPixelRatio = () => typeof window === "undefined" ? 1 : Math.min(Math.max(window.devicePixelRatio || 1, 1), 3);
|
|
763
|
+
function watchPixelRatio(cb) {
|
|
764
|
+
if (typeof window === "undefined" || !window.matchMedia) return () => {
|
|
765
|
+
};
|
|
766
|
+
let mql;
|
|
767
|
+
let stopped = false;
|
|
768
|
+
const arm = () => {
|
|
769
|
+
mql = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
|
|
770
|
+
mql.addEventListener("change", onChange, { once: true });
|
|
771
|
+
};
|
|
772
|
+
const onChange = () => {
|
|
773
|
+
if (stopped) return;
|
|
774
|
+
cb(currentPixelRatio());
|
|
775
|
+
arm();
|
|
776
|
+
};
|
|
777
|
+
arm();
|
|
778
|
+
return () => {
|
|
779
|
+
stopped = true;
|
|
780
|
+
mql.removeEventListener("change", onChange);
|
|
781
|
+
};
|
|
782
|
+
}
|
|
690
783
|
|
|
691
784
|
// src/components/KonvaStage.tsx
|
|
692
785
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
693
|
-
var KonvaStage = (0,
|
|
786
|
+
var KonvaStage = (0, import_react4.forwardRef)(function KonvaStage2({
|
|
694
787
|
children,
|
|
695
788
|
onWheel,
|
|
696
789
|
onMouseDown,
|
|
@@ -702,20 +795,32 @@ var KonvaStage = (0, import_react3.forwardRef)(function KonvaStage2({
|
|
|
702
795
|
onTouchEnd,
|
|
703
796
|
onStageReady
|
|
704
797
|
}, ref) {
|
|
705
|
-
const internalRef = (0,
|
|
798
|
+
const internalRef = (0, import_react4.useRef)(null);
|
|
706
799
|
const { centerX, centerY, zoomLevel, canvasWidth, canvasHeight } = useViewportStore();
|
|
707
|
-
(0,
|
|
800
|
+
(0, import_react4.useEffect)(() => {
|
|
708
801
|
if (internalRef.current) {
|
|
709
802
|
if (typeof ref === "function") ref(internalRef.current);
|
|
710
803
|
else if (ref) ref.current = internalRef.current;
|
|
711
804
|
onStageReady?.(internalRef.current);
|
|
712
805
|
}
|
|
713
806
|
}, []);
|
|
714
|
-
(0,
|
|
807
|
+
(0, import_react4.useEffect)(() => {
|
|
808
|
+
const stage = internalRef.current;
|
|
715
809
|
return () => {
|
|
716
|
-
|
|
810
|
+
stage?.destroy();
|
|
717
811
|
};
|
|
718
812
|
}, []);
|
|
813
|
+
import_konva.default.pixelRatio = currentPixelRatio();
|
|
814
|
+
(0, import_react4.useEffect)(() => watchPixelRatio((ratio) => {
|
|
815
|
+
import_konva.default.pixelRatio = ratio;
|
|
816
|
+
const stage = internalRef.current;
|
|
817
|
+
if (!stage) return;
|
|
818
|
+
stage.getLayers().forEach((l) => {
|
|
819
|
+
l.getCanvas().setPixelRatio(ratio);
|
|
820
|
+
l.getHitCanvas().setPixelRatio(ratio);
|
|
821
|
+
});
|
|
822
|
+
stage.batchDraw();
|
|
823
|
+
}), []);
|
|
719
824
|
const w = canvasWidth || 800;
|
|
720
825
|
const h = canvasHeight || 600;
|
|
721
826
|
const stageX = w / 2 - centerX * zoomLevel;
|
|
@@ -739,7 +844,6 @@ var KonvaStage = (0, import_react3.forwardRef)(function KonvaStage2({
|
|
|
739
844
|
onTouchMove,
|
|
740
845
|
onTouchEnd,
|
|
741
846
|
draggable: false,
|
|
742
|
-
pixelRatio: 1,
|
|
743
847
|
children
|
|
744
848
|
}
|
|
745
849
|
);
|
|
@@ -748,1348 +852,999 @@ var KonvaStage_default = KonvaStage;
|
|
|
748
852
|
|
|
749
853
|
// src/components/KonvaNodeLayer.tsx
|
|
750
854
|
var import_react6 = require("react");
|
|
855
|
+
var import_core9 = require("@visualli/core");
|
|
751
856
|
|
|
752
|
-
// src/
|
|
753
|
-
var
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
857
|
+
// src/design/design.ts
|
|
858
|
+
var import_core7 = require("@visualli/core");
|
|
859
|
+
function parseShadow(css) {
|
|
860
|
+
const m = /^(-?[\d.]+)(?:px)?\s+(-?[\d.]+)(?:px)?\s+([\d.]+)(?:px)?\s+(.+)$/.exec(css.trim());
|
|
861
|
+
return m ? { x: +m[1], y: +m[2], blur: +m[3], color: m[4].trim() } : null;
|
|
862
|
+
}
|
|
863
|
+
var cache = /* @__PURE__ */ new Map();
|
|
864
|
+
function makeDesign(theme, comfortIn) {
|
|
865
|
+
const key2 = `${theme}|${+comfortIn.readableType}${+comfortIn.largerText}${+comfortIn.reducedMotion}`;
|
|
866
|
+
const hit = cache.get(key2);
|
|
867
|
+
if (hit) return hit;
|
|
868
|
+
const tokens = import_core7.TOKENS[theme];
|
|
869
|
+
const ui = tokens["font-ui"];
|
|
870
|
+
const d = {
|
|
871
|
+
theme,
|
|
872
|
+
comfort: comfortIn,
|
|
873
|
+
tokens,
|
|
874
|
+
metrics: (0, import_core7.metricsFor)(comfortIn),
|
|
875
|
+
isDark: (0, import_core7.isDarkTheme)(theme),
|
|
876
|
+
fontHand: comfortIn.readableType ? ui : tokens["font-hand"],
|
|
877
|
+
fontNote: comfortIn.readableType ? ui : tokens["font-note"],
|
|
878
|
+
fontUi: ui,
|
|
879
|
+
labelScale: (0, import_core7.labelScale)(comfortIn),
|
|
880
|
+
nodeShadow: (0, import_core7.isContrastTheme)(theme) ? null : parseShadow((0, import_core7.isDarkTheme)(theme) ? import_core7.CANVAS_STYLE.node.shadowDark : import_core7.CANVAS_STYLE.node.shadowLight),
|
|
881
|
+
attrs: {
|
|
882
|
+
"data-theme": theme,
|
|
883
|
+
...comfortIn.readableType ? { "data-type": "readable" } : {},
|
|
884
|
+
...comfortIn.largerText ? { "data-scale": "lg" } : {},
|
|
885
|
+
...comfortIn.reducedMotion ? { "data-motion": "reduced" } : {}
|
|
886
|
+
},
|
|
887
|
+
edgeLabel: comfortIn.readableType ? { weight: import_core7.CANVAS_STYLE.edge.readableLabelWeight, size: import_core7.CANVAS_STYLE.edge.readableLabelSize } : { weight: import_core7.CANVAS_STYLE.edge.labelWeight, size: import_core7.CANVAS_STYLE.edge.labelSize }
|
|
888
|
+
};
|
|
889
|
+
cache.set(key2, d);
|
|
890
|
+
return d;
|
|
891
|
+
}
|
|
892
|
+
var DEFAULT_DESIGN = makeDesign("light", { readableType: false, largerText: false, reducedMotion: false });
|
|
893
|
+
function resolveDesign(props, env) {
|
|
894
|
+
const input = props.theme ?? (props.isDark === void 0 ? void 0 : props.isDark ? "dark" : "light");
|
|
895
|
+
let theme = (0, import_core7.resolveTheme)(input, { prefersDark: env.prefersDark, forcedColors: env.forcedColors });
|
|
896
|
+
if (env.forcedColors && props.respectForcedColors !== false && !(0, import_core7.isContrastTheme)(theme)) {
|
|
897
|
+
theme = (0, import_core7.isDarkTheme)(theme) ? "contrast-dark" : "contrast-light";
|
|
764
898
|
}
|
|
765
|
-
return (0,
|
|
766
|
-
if (nodes.length === 0) return [];
|
|
767
|
-
if (!indexRef.current && nodes.length >= SPATIAL_INDEX_THRESHOLD && !skipCulling && !isDragging) {
|
|
768
|
-
indexRef.current = new import_core6.RBushSpatialIndex();
|
|
769
|
-
indexRef.current.bulkLoad(nodes.map((n) => ({
|
|
770
|
-
nodeId: n.id,
|
|
771
|
-
bounds: { minX: n.x, minY: n.y, maxX: n.x + n.width, maxY: n.y + n.height }
|
|
772
|
-
})));
|
|
773
|
-
}
|
|
774
|
-
return (0, import_core6.getViewportVisibleNodes)({
|
|
775
|
-
nodes,
|
|
776
|
-
viewport: { centerX, centerY, zoomLevel, rotation: 0, visibleBounds: { minX: 0, minY: 0, maxX: 0, maxY: 0 } },
|
|
777
|
-
canvasWidth,
|
|
778
|
-
canvasHeight,
|
|
779
|
-
levelFilter,
|
|
780
|
-
skipCulling,
|
|
781
|
-
isDragging,
|
|
782
|
-
spatialIndex: indexRef.current,
|
|
783
|
-
spatialIndexThreshold: SPATIAL_INDEX_THRESHOLD
|
|
784
|
-
});
|
|
785
|
-
}, [nodes, levelFilter, skipCulling, isDragging, centerX, centerY, zoomLevel, canvasWidth, canvasHeight]);
|
|
899
|
+
return makeDesign(theme, (0, import_core7.resolveComfort)(props.comfort, { prefersReducedMotion: env.prefersReducedMotion }));
|
|
786
900
|
}
|
|
787
901
|
|
|
788
|
-
// src/
|
|
789
|
-
var import_react5 = __toESM(require("react"), 1);
|
|
902
|
+
// src/design/drawing.ts
|
|
790
903
|
var import_core8 = require("@visualli/core");
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
var
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
{ x: -0.384, y: -0.716 },
|
|
814
|
-
{ x: -0.285, y: -0.792 },
|
|
815
|
-
{ x: -0.189, y: -0.855 },
|
|
816
|
-
{ x: -0.09, y: -0.903 },
|
|
817
|
-
{ x: 0.014, y: -0.932 },
|
|
818
|
-
{ x: 0.129, y: -0.94 },
|
|
819
|
-
{ x: 0.255, y: -0.924 },
|
|
820
|
-
{ x: 0.412, y: -0.876 },
|
|
821
|
-
{ x: 0.516, y: -0.832 },
|
|
822
|
-
{ x: 0.612, y: -0.78 },
|
|
823
|
-
{ x: 0.695, y: -0.725 },
|
|
824
|
-
{ x: 0.767, y: -0.666 },
|
|
825
|
-
{ x: 0.83, y: -0.606 },
|
|
826
|
-
{ x: 0.885, y: -0.546 },
|
|
827
|
-
{ x: 0.933, y: -0.486 },
|
|
828
|
-
{ x: 0.979, y: -0.425 },
|
|
829
|
-
{ x: 1.006, y: -0.385 },
|
|
830
|
-
{ x: 1.034, y: -0.334 },
|
|
831
|
-
{ x: 1.058, y: -0.276 },
|
|
832
|
-
{ x: 1.078, y: -0.209 },
|
|
833
|
-
{ x: 1.092, y: -0.133 },
|
|
834
|
-
{ x: 1.101, y: -0.046 },
|
|
835
|
-
{ x: 1.102, y: 0.052 },
|
|
836
|
-
{ x: 1.094, y: 0.173 },
|
|
837
|
-
{ x: 1.083, y: 0.244 },
|
|
838
|
-
{ x: 1.063, y: 0.324 },
|
|
839
|
-
{ x: 1.034, y: 0.401 },
|
|
840
|
-
{ x: 0.996, y: 0.477 },
|
|
841
|
-
{ x: 0.948, y: 0.549 },
|
|
842
|
-
{ x: 0.889, y: 0.617 },
|
|
843
|
-
{ x: 0.82, y: 0.681 },
|
|
844
|
-
{ x: 0.735, y: 0.741 },
|
|
845
|
-
{ x: 0.658, y: 0.783 },
|
|
846
|
-
{ x: 0.569, y: 0.822 },
|
|
847
|
-
{ x: 0.474, y: 0.855 },
|
|
848
|
-
{ x: 0.372, y: 0.882 },
|
|
849
|
-
{ x: 0.265, y: 0.904 },
|
|
850
|
-
{ x: 0.156, y: 0.92 },
|
|
851
|
-
{ x: 0.046, y: 0.931 },
|
|
852
|
-
{ x: -0.069, y: 0.938 },
|
|
853
|
-
{ x: -0.155, y: 0.935 },
|
|
854
|
-
{ x: -0.25, y: 0.919 },
|
|
855
|
-
{ x: -0.346, y: 0.89 },
|
|
856
|
-
{ x: -0.443, y: 0.851 },
|
|
857
|
-
{ x: -0.539, y: 0.8 },
|
|
858
|
-
{ x: -0.633, y: 0.738 },
|
|
859
|
-
{ x: -0.723, y: 0.667 },
|
|
860
|
-
{ x: -0.808, y: 0.585 },
|
|
861
|
-
{ x: -0.882, y: 0.501 },
|
|
862
|
-
{ x: -0.944, y: 0.421 },
|
|
863
|
-
{ x: -0.994, y: 0.343 },
|
|
864
|
-
{ x: -1.034, y: 0.267 },
|
|
865
|
-
{ x: -1.064, y: 0.192 },
|
|
866
|
-
{ x: -1.085, y: 0.12 },
|
|
867
|
-
{ x: -1.097, y: 0.042 },
|
|
868
|
-
{ x: -1.102, y: 1e-3 },
|
|
869
|
-
{ x: -1.09, y: -0.243 },
|
|
870
|
-
{ x: -1.05, y: -0.365 },
|
|
871
|
-
{ x: -0.99, y: -0.441 },
|
|
872
|
-
{ x: -0.911, y: -0.479 },
|
|
873
|
-
{ x: -0.819, y: -0.499 },
|
|
874
|
-
{ x: -0.715, y: -0.517 },
|
|
875
|
-
{ x: -0.605, y: -0.551 }
|
|
876
|
-
],
|
|
877
|
-
// Shape 2 · Blob 3
|
|
878
|
-
[
|
|
879
|
-
{ x: 3e-3, y: -0.97 },
|
|
880
|
-
{ x: 0.163, y: -0.984 },
|
|
881
|
-
{ x: 0.286, y: -1.028 },
|
|
882
|
-
{ x: 0.389, y: -1.096 },
|
|
883
|
-
{ x: 0.488, y: -1.174 },
|
|
884
|
-
{ x: 0.597, y: -1.21 },
|
|
885
|
-
{ x: 0.728, y: -1.165 },
|
|
886
|
-
{ x: 0.867, y: -1.036 },
|
|
887
|
-
{ x: 0.996, y: -0.839 },
|
|
888
|
-
{ x: 1.098, y: -0.59 },
|
|
889
|
-
{ x: 1.158, y: -0.304 },
|
|
890
|
-
{ x: 1.153, y: 0.012 },
|
|
891
|
-
{ x: 1.08, y: 0.336 },
|
|
892
|
-
{ x: 0.944, y: 0.639 },
|
|
893
|
-
{ x: 0.756, y: 0.889 },
|
|
894
|
-
{ x: 0.484, y: 1.072 },
|
|
895
|
-
{ x: 0.339, y: 1.079 },
|
|
896
|
-
{ x: 0.219, y: 1.051 },
|
|
897
|
-
{ x: 0.131, y: 0.994 },
|
|
898
|
-
{ x: 0.059, y: 0.939 },
|
|
899
|
-
{ x: 8e-3, y: 0.915 },
|
|
900
|
-
{ x: -0.105, y: 0.931 },
|
|
901
|
-
{ x: -0.176, y: 0.965 },
|
|
902
|
-
{ x: -0.249, y: 1.005 },
|
|
903
|
-
{ x: -0.345, y: 1.039 },
|
|
904
|
-
{ x: -0.497, y: 1.055 },
|
|
905
|
-
{ x: -0.672, y: 1.032 },
|
|
906
|
-
{ x: -0.817, y: 0.952 },
|
|
907
|
-
{ x: -0.945, y: 0.81 },
|
|
908
|
-
{ x: -1.054, y: 0.6 },
|
|
909
|
-
{ x: -1.16, y: 0.302 },
|
|
910
|
-
{ x: -1.187, y: -0.011 },
|
|
911
|
-
{ x: -1.165, y: -0.326 },
|
|
912
|
-
{ x: -1.097, y: -0.599 },
|
|
913
|
-
{ x: -0.991, y: -0.838 },
|
|
914
|
-
{ x: -0.852, y: -0.912 },
|
|
915
|
-
{ x: -0.695, y: -0.958 },
|
|
916
|
-
{ x: -0.53, y: -0.988 },
|
|
917
|
-
{ x: -0.357, y: -0.992 },
|
|
918
|
-
{ x: -0.18, y: -0.983 }
|
|
919
|
-
],
|
|
920
|
-
// Shape 3 · Blob 4
|
|
921
|
-
[
|
|
922
|
-
{ x: -0.734, y: -1.127 },
|
|
923
|
-
{ x: -0.656, y: -1.146 },
|
|
924
|
-
{ x: -0.567, y: -1.142 },
|
|
925
|
-
{ x: -0.468, y: -1.115 },
|
|
926
|
-
{ x: -0.358, y: -1.079 },
|
|
927
|
-
{ x: -0.238, y: -1.034 },
|
|
928
|
-
{ x: -0.107, y: -0.989 },
|
|
929
|
-
{ x: 0.035, y: -0.953 },
|
|
930
|
-
{ x: 0.188, y: -0.929 },
|
|
931
|
-
{ x: 0.325, y: -0.916 },
|
|
932
|
-
{ x: 0.484, y: -0.896 },
|
|
933
|
-
{ x: 0.65, y: -0.854 },
|
|
934
|
-
{ x: 0.811, y: -0.77 },
|
|
935
|
-
{ x: 0.955, y: -0.631 },
|
|
936
|
-
{ x: 1.068, y: -0.422 },
|
|
937
|
-
{ x: 1.138, y: -0.126 },
|
|
938
|
-
{ x: 1.152, y: 0.272 },
|
|
939
|
-
{ x: 1.121, y: 0.451 },
|
|
940
|
-
{ x: 1.046, y: 0.602 },
|
|
941
|
-
{ x: 0.928, y: 0.727 },
|
|
942
|
-
{ x: 0.767, y: 0.833 },
|
|
943
|
-
{ x: 0.565, y: 0.925 },
|
|
944
|
-
{ x: 0.323, y: 1.001 },
|
|
945
|
-
{ x: 0.042, y: 1.075 },
|
|
946
|
-
{ x: -0.277, y: 1.142 },
|
|
947
|
-
{ x: -0.368, y: 1.147 },
|
|
948
|
-
{ x: -0.475, y: 1.136 },
|
|
949
|
-
{ x: -0.589, y: 1.104 },
|
|
950
|
-
{ x: -0.704, y: 1.05 },
|
|
951
|
-
{ x: -0.815, y: 0.973 },
|
|
952
|
-
{ x: -0.913, y: 0.871 },
|
|
953
|
-
{ x: -0.993, y: 0.746 },
|
|
954
|
-
{ x: -1.048, y: 0.593 },
|
|
955
|
-
{ x: -1.109, y: 0.298 },
|
|
956
|
-
{ x: -1.145, y: 0.018 },
|
|
957
|
-
{ x: -1.154, y: -0.243 },
|
|
958
|
-
{ x: -1.135, y: -0.482 },
|
|
959
|
-
{ x: -1.085, y: -0.695 },
|
|
960
|
-
{ x: -1.003, y: -0.874 },
|
|
961
|
-
{ x: -0.886, y: -1.02 }
|
|
962
|
-
],
|
|
963
|
-
// Shape 4 · Blob 5 (inverted Blob 4)
|
|
964
|
-
[
|
|
965
|
-
{ x: -0.734, y: 1.127 },
|
|
966
|
-
{ x: -0.656, y: 1.146 },
|
|
967
|
-
{ x: -0.567, y: 1.142 },
|
|
968
|
-
{ x: -0.468, y: 1.115 },
|
|
969
|
-
{ x: -0.358, y: 1.079 },
|
|
970
|
-
{ x: -0.238, y: 1.034 },
|
|
971
|
-
{ x: -0.107, y: 0.989 },
|
|
972
|
-
{ x: 0.035, y: 0.953 },
|
|
973
|
-
{ x: 0.188, y: 0.929 },
|
|
974
|
-
{ x: 0.325, y: 0.916 },
|
|
975
|
-
{ x: 0.484, y: 0.896 },
|
|
976
|
-
{ x: 0.65, y: 0.854 },
|
|
977
|
-
{ x: 0.811, y: 0.77 },
|
|
978
|
-
{ x: 0.955, y: 0.631 },
|
|
979
|
-
{ x: 1.068, y: 0.422 },
|
|
980
|
-
{ x: 1.138, y: 0.126 },
|
|
981
|
-
{ x: 1.152, y: -0.272 },
|
|
982
|
-
{ x: 1.121, y: -0.451 },
|
|
983
|
-
{ x: 1.046, y: -0.602 },
|
|
984
|
-
{ x: 0.928, y: -0.727 },
|
|
985
|
-
{ x: 0.767, y: -0.833 },
|
|
986
|
-
{ x: 0.565, y: -0.925 },
|
|
987
|
-
{ x: 0.323, y: -1.001 },
|
|
988
|
-
{ x: 0.042, y: -1.075 },
|
|
989
|
-
{ x: -0.277, y: -1.142 },
|
|
990
|
-
{ x: -0.368, y: -1.147 },
|
|
991
|
-
{ x: -0.475, y: -1.136 },
|
|
992
|
-
{ x: -0.589, y: -1.104 },
|
|
993
|
-
{ x: -0.704, y: -1.05 },
|
|
994
|
-
{ x: -0.815, y: -0.973 },
|
|
995
|
-
{ x: -0.913, y: -0.871 },
|
|
996
|
-
{ x: -0.993, y: -0.746 },
|
|
997
|
-
{ x: -1.048, y: -0.593 },
|
|
998
|
-
{ x: -1.109, y: -0.298 },
|
|
999
|
-
{ x: -1.145, y: -0.018 },
|
|
1000
|
-
{ x: -1.154, y: 0.243 },
|
|
1001
|
-
{ x: -1.135, y: 0.482 },
|
|
1002
|
-
{ x: -1.085, y: 0.695 },
|
|
1003
|
-
{ x: -1.003, y: 0.874 },
|
|
1004
|
-
{ x: -0.886, y: 1.02 }
|
|
1005
|
-
],
|
|
1006
|
-
// Shape 5 · Blob 6
|
|
1007
|
-
[
|
|
1008
|
-
{ x: 1e-3, y: -1.006 },
|
|
1009
|
-
{ x: 0.134, y: -1.001 },
|
|
1010
|
-
{ x: 0.262, y: -1.024 },
|
|
1011
|
-
{ x: 0.382, y: -1.073 },
|
|
1012
|
-
{ x: 0.498, y: -1.107 },
|
|
1013
|
-
{ x: 0.614, y: -1.105 },
|
|
1014
|
-
{ x: 0.733, y: -1.049 },
|
|
1015
|
-
{ x: 0.85, y: -0.932 },
|
|
1016
|
-
{ x: 0.953, y: -0.755 },
|
|
1017
|
-
{ x: 1.03, y: -0.529 },
|
|
1018
|
-
{ x: 1.067, y: -0.265 },
|
|
1019
|
-
{ x: 1.054, y: 0.018 },
|
|
1020
|
-
{ x: 0.986, y: 0.3 },
|
|
1021
|
-
{ x: 0.868, y: 0.557 },
|
|
1022
|
-
{ x: 0.715, y: 0.766 },
|
|
1023
|
-
{ x: 0.55, y: 0.909 },
|
|
1024
|
-
{ x: 0.396, y: 0.984 },
|
|
1025
|
-
{ x: 0.262, y: 1.005 },
|
|
1026
|
-
{ x: 0.148, y: 1.003 },
|
|
1027
|
-
{ x: 0.037, y: 1.008 },
|
|
1028
|
-
{ x: -0.083, y: 1.04 },
|
|
1029
|
-
{ x: -0.22, y: 1.118 },
|
|
1030
|
-
{ x: -0.36, y: 1.192 },
|
|
1031
|
-
{ x: -0.5, y: 1.234 },
|
|
1032
|
-
{ x: -0.65, y: 1.175 },
|
|
1033
|
-
{ x: -0.81, y: 1.045 },
|
|
1034
|
-
{ x: -0.99, y: 0.855 },
|
|
1035
|
-
{ x: -1.14, y: 0.61 },
|
|
1036
|
-
{ x: -1.24, y: 0.33 },
|
|
1037
|
-
{ x: -1.27, y: 0.04 },
|
|
1038
|
-
{ x: -1.275, y: -0.25 },
|
|
1039
|
-
{ x: -1.225, y: -0.5 },
|
|
1040
|
-
{ x: -1.08, y: -0.72 },
|
|
1041
|
-
{ x: -0.89, y: -0.92 },
|
|
1042
|
-
{ x: -0.734, y: -1.053 },
|
|
1043
|
-
{ x: -0.614, y: -1.112 },
|
|
1044
|
-
{ x: -0.498, y: -1.127 },
|
|
1045
|
-
{ x: -0.381, y: -1.11 },
|
|
1046
|
-
{ x: -0.261, y: -1.095 },
|
|
1047
|
-
{ x: -0.133, y: -1.058 },
|
|
1048
|
-
{ x: 1e-3, y: -1.01 },
|
|
1049
|
-
{ x: 0.134, y: -1.001 }
|
|
1050
|
-
]
|
|
1051
|
-
];
|
|
1052
|
-
function buildBlobPathData(_shape, cx, cy, width, height) {
|
|
1053
|
-
const rx = width / 2;
|
|
1054
|
-
const ry = height / 2;
|
|
1055
|
-
const blobType = 0;
|
|
1056
|
-
const srcPts = ALL_BLOB_SHAPES[blobType];
|
|
1057
|
-
const pts = srcPts.map((p) => ({ x: cx + p.x * rx, y: cy + p.y * ry }));
|
|
1058
|
-
const mid = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
|
|
1059
|
-
const last = pts[pts.length - 1];
|
|
1060
|
-
const first = pts[0];
|
|
1061
|
-
const s = mid(last, first);
|
|
1062
|
-
let d = `M ${s.x} ${s.y}`;
|
|
1063
|
-
for (let i = 0; i < pts.length; i++) {
|
|
1064
|
-
const curr = pts[i];
|
|
1065
|
-
const next = pts[(i + 1) % pts.length];
|
|
1066
|
-
const m = mid(curr, next);
|
|
1067
|
-
d += ` Q ${curr.x} ${curr.y} ${m.x} ${m.y}`;
|
|
904
|
+
var DEG = Math.PI / 180;
|
|
905
|
+
var MAX_CACHED_PATHS = 4096;
|
|
906
|
+
var pathCache = /* @__PURE__ */ new Map();
|
|
907
|
+
function blobPath2D(shape, rx, ry) {
|
|
908
|
+
const key2 = `${shape}|${rx}|${ry}`;
|
|
909
|
+
let p = pathCache.get(key2);
|
|
910
|
+
if (!p) {
|
|
911
|
+
if (pathCache.size >= MAX_CACHED_PATHS) pathCache.delete(pathCache.keys().next().value);
|
|
912
|
+
p = new Path2D((0, import_core8.blobPath)(shape, rx, ry));
|
|
913
|
+
pathCache.set(key2, p);
|
|
914
|
+
}
|
|
915
|
+
return p;
|
|
916
|
+
}
|
|
917
|
+
var NO_DASH = [];
|
|
918
|
+
var NO_RINGS = [];
|
|
919
|
+
var dashCache = /* @__PURE__ */ new Map();
|
|
920
|
+
function dashOf(spec) {
|
|
921
|
+
if (!spec) return NO_DASH;
|
|
922
|
+
let d = dashCache.get(spec);
|
|
923
|
+
if (!d) {
|
|
924
|
+
d = spec.split(/\s+/).map(Number);
|
|
925
|
+
dashCache.set(spec, d);
|
|
1068
926
|
}
|
|
1069
|
-
d += " Z";
|
|
1070
927
|
return d;
|
|
1071
928
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
const
|
|
1083
|
-
const
|
|
1084
|
-
const
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
929
|
+
var lineCache = /* @__PURE__ */ new Map();
|
|
930
|
+
var MAX_CACHED_LAYOUTS = 8192;
|
|
931
|
+
function layoutLabel(c, text, font, maxWidth, maxLines = Infinity) {
|
|
932
|
+
const key2 = `${fontsEpoch()}|${font}|${maxWidth}|${text}`;
|
|
933
|
+
const hit = lineCache.get(key2);
|
|
934
|
+
if (hit) return hit;
|
|
935
|
+
c.font = font;
|
|
936
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
937
|
+
const lines = [];
|
|
938
|
+
let cur = "";
|
|
939
|
+
const fit = (s) => c.measureText(s).width <= maxWidth;
|
|
940
|
+
for (const w of words) {
|
|
941
|
+
const next = cur ? `${cur} ${w}` : w;
|
|
942
|
+
if (fit(next)) {
|
|
943
|
+
cur = next;
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (cur) lines.push(cur);
|
|
947
|
+
let rest = w;
|
|
948
|
+
while (!fit(rest) && rest.length > 1) {
|
|
949
|
+
let n = rest.length - 1;
|
|
950
|
+
while (n > 1 && !fit(rest.slice(0, n))) n--;
|
|
951
|
+
lines.push(rest.slice(0, n));
|
|
952
|
+
rest = rest.slice(n);
|
|
953
|
+
}
|
|
954
|
+
cur = rest;
|
|
955
|
+
}
|
|
956
|
+
if (cur) lines.push(cur);
|
|
957
|
+
if (lines.length > maxLines) {
|
|
958
|
+
lines.length = maxLines;
|
|
959
|
+
let last = lines[maxLines - 1] + "\u2026";
|
|
960
|
+
while (last.length > 1 && !fit(last)) last = last.slice(0, -2) + "\u2026";
|
|
961
|
+
lines[maxLines - 1] = last;
|
|
962
|
+
}
|
|
963
|
+
if (lineCache.size >= MAX_CACHED_LAYOUTS) lineCache.clear();
|
|
964
|
+
lineCache.set(key2, lines);
|
|
965
|
+
return lines;
|
|
966
|
+
}
|
|
967
|
+
var IDLE = { hovered: false, pressed: false, selected: false, focused: false, dimmed: false };
|
|
968
|
+
var ideaColors = (d, n) => (0, import_core8.ideaStyle)(d.theme, n);
|
|
969
|
+
var ideaLabelPx = (n, d) => import_core8.IDEA.labelSize[n.kind ?? "node"] * d.labelScale;
|
|
970
|
+
function drawIdea(c, node, d, zoom, st, opt) {
|
|
971
|
+
const { rx, ry } = (0, import_core8.nodeRadii)(node);
|
|
972
|
+
const { fill, ring } = ideaColors(d, node);
|
|
973
|
+
const screenW = node.width * zoom;
|
|
974
|
+
const base = opt.base;
|
|
975
|
+
const detail = (0, import_core8.ideaDetail)(screenW, st.selected || st.focused || st.hovered);
|
|
976
|
+
if (detail === "speck") {
|
|
977
|
+
const a = (st.dimmed ? opt.dimOpacity ?? import_core8.CANVAS_STYLE.node.dimmedOpacityFocus : 1) * (st.arrival ? st.arrival.alpha : 1);
|
|
978
|
+
if (a !== 1) c.globalAlpha = a;
|
|
979
|
+
c.fillStyle = fill;
|
|
980
|
+
c.fillRect(node.x - rx, node.y - ry, rx * 2, ry * 2);
|
|
981
|
+
if (a !== 1) c.globalAlpha = 1;
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const detailed = detail === "full";
|
|
985
|
+
const shape = (0, import_core8.shapeOfLevel)(node.level);
|
|
986
|
+
const rings = detailed ? (0, import_core8.ringsFor)(node.branchCount) : NO_RINGS;
|
|
987
|
+
const raised = st.hovered || st.pressed || st.selected;
|
|
988
|
+
const lift = st.lift ?? (raised ? 1 : 0);
|
|
989
|
+
const ringT = st.ring ?? (raised ? 1 : 0);
|
|
990
|
+
const arr = st.arrival;
|
|
991
|
+
const y = node.y + lift * import_core8.CANVAS_STYLE.node.hoverLift + (arr ? arr.dy : 0);
|
|
992
|
+
const x = node.x + (arr ? arr.dx : 0);
|
|
993
|
+
let k = opt.k;
|
|
994
|
+
if (k === void 0) {
|
|
995
|
+
const m = c.getTransform();
|
|
996
|
+
k = Math.hypot(m.a, m.b);
|
|
997
|
+
}
|
|
998
|
+
if (base) c.setTransform(base.a, base.b, base.c, base.d, base.e + base.a * x + base.c * y, base.f + base.b * x + base.d * y);
|
|
999
|
+
else {
|
|
1000
|
+
c.save();
|
|
1001
|
+
c.translate(x, y);
|
|
1002
|
+
}
|
|
1003
|
+
if (arr) c.scale(arr.scale, arr.scale);
|
|
1004
|
+
c.lineJoin = "round";
|
|
1005
|
+
const baseAlpha = (st.dimmed ? opt.dimOpacity ?? import_core8.CANVAS_STYLE.node.dimmedOpacityFocus : 1) * (arr ? arr.alpha : 1);
|
|
1006
|
+
c.globalAlpha = baseAlpha;
|
|
1007
|
+
if (rings.length) {
|
|
1008
|
+
const ringSpin = ringT * import_core8.CANVAS_STYLE.node.hoverRingsRotate;
|
|
1009
|
+
const ringGrow = 1 + ringT * (import_core8.CANVAS_STYLE.node.hoverRingsScale - 1);
|
|
1010
|
+
c.fillStyle = fill;
|
|
1011
|
+
c.strokeStyle = ring;
|
|
1012
|
+
let dashed = false;
|
|
1013
|
+
for (let i = rings.length - 1; i >= 0; i--) {
|
|
1014
|
+
const r = rings[i];
|
|
1015
|
+
const angle = (r.rotate + ringSpin) * DEG;
|
|
1016
|
+
c.rotate(angle);
|
|
1017
|
+
c.globalAlpha = baseAlpha * r.opacity;
|
|
1018
|
+
const path = blobPath2D(shape, rx * r.scale * ringGrow, ry * r.scale * ringGrow);
|
|
1019
|
+
c.fill(path);
|
|
1020
|
+
c.lineWidth = r.width;
|
|
1021
|
+
if (r.dash) {
|
|
1022
|
+
c.setLineDash(dashOf(r.dash));
|
|
1023
|
+
dashed = true;
|
|
1024
|
+
} else if (dashed) {
|
|
1025
|
+
c.setLineDash(NO_DASH);
|
|
1026
|
+
dashed = false;
|
|
1157
1027
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1028
|
+
c.stroke(path);
|
|
1029
|
+
c.rotate(-angle);
|
|
1030
|
+
}
|
|
1031
|
+
if (dashed) c.setLineDash(NO_DASH);
|
|
1032
|
+
c.globalAlpha = baseAlpha;
|
|
1033
|
+
}
|
|
1034
|
+
const body = blobPath2D(shape, rx, ry);
|
|
1035
|
+
c.fillStyle = fill;
|
|
1036
|
+
const shadow = detailed && opt.shadows ? d.nodeShadow : null;
|
|
1037
|
+
if (shadow) {
|
|
1038
|
+
c.shadowColor = shadow.color;
|
|
1039
|
+
c.shadowBlur = shadow.blur * k;
|
|
1040
|
+
c.shadowOffsetX = shadow.x * k;
|
|
1041
|
+
c.shadowOffsetY = shadow.y * k;
|
|
1042
|
+
}
|
|
1043
|
+
c.fill(body);
|
|
1044
|
+
if (shadow) {
|
|
1045
|
+
c.shadowColor = "transparent";
|
|
1046
|
+
c.shadowBlur = 0;
|
|
1047
|
+
c.shadowOffsetX = 0;
|
|
1048
|
+
c.shadowOffsetY = 0;
|
|
1049
|
+
}
|
|
1050
|
+
c.strokeStyle = st.focused ? d.tokens["focus"] : st.selected ? d.tokens["ink"] : ring;
|
|
1051
|
+
c.lineWidth = st.selected || st.focused ? import_core8.CANVAS_STYLE.node.selectedStrokeWidth : d.metrics.nodeStroke;
|
|
1052
|
+
c.stroke(body);
|
|
1053
|
+
if (detailed && node.title) {
|
|
1054
|
+
const px = ideaLabelPx(node, d);
|
|
1055
|
+
const font = `${import_core8.TYPE_STYLES["node-label"].weight} ${px}px ${d.fontHand}`;
|
|
1056
|
+
const lines = layoutLabel(c, node.title, font, node.width - import_core8.IDEA.labelInset);
|
|
1057
|
+
const s = (0, import_core8.labelGrowth)(zoom).idea;
|
|
1058
|
+
const lh = px * import_core8.IDEA.labelLineHeight;
|
|
1059
|
+
c.scale(s, s);
|
|
1060
|
+
c.font = font;
|
|
1061
|
+
c.fillStyle = d.tokens["node-ink"];
|
|
1062
|
+
c.textAlign = "center";
|
|
1063
|
+
c.textBaseline = "middle";
|
|
1064
|
+
if (d.comfort.readableType && "letterSpacing" in c) c.letterSpacing = `${(px * 0.02).toFixed(2)}px`;
|
|
1065
|
+
const y0 = -((lines.length - 1) * lh) / 2;
|
|
1066
|
+
for (let i = 0; i < lines.length; i++) c.fillText(lines[i], 0, y0 + i * lh);
|
|
1067
|
+
}
|
|
1068
|
+
if (base) c.globalAlpha = 1;
|
|
1069
|
+
else c.restore();
|
|
1070
|
+
}
|
|
1071
|
+
function cubicLength(d) {
|
|
1072
|
+
const v = d.match(/-?\d+(?:\.\d+)?/g)?.map(Number);
|
|
1073
|
+
if (!v || v.length < 8) return 0;
|
|
1074
|
+
const [x0, y0, x1, y1, x2, y2, x3, y3] = v;
|
|
1075
|
+
let len = 0, px = x0, py = y0;
|
|
1076
|
+
for (let i = 1; i <= 16; i++) {
|
|
1077
|
+
const t = i / 16, u = 1 - t;
|
|
1078
|
+
const x = u * u * u * x0 + 3 * u * u * t * x1 + 3 * u * t * t * x2 + t * t * t * x3;
|
|
1079
|
+
const y = u * u * u * y0 + 3 * u * u * t * y1 + 3 * u * t * t * y2 + t * t * t * y3;
|
|
1080
|
+
len += Math.hypot(x - px, y - py);
|
|
1081
|
+
px = x;
|
|
1082
|
+
py = y;
|
|
1083
|
+
}
|
|
1084
|
+
return len;
|
|
1085
|
+
}
|
|
1086
|
+
function prepareConnector(g) {
|
|
1087
|
+
return { ...g, path: new Path2D(g.d), arrowPath: new Path2D(g.arrow), len: cubicLength(g.d) };
|
|
1088
|
+
}
|
|
1089
|
+
var CONNECTOR_MIN_SCREEN_PX = 3;
|
|
1090
|
+
var LABEL_MIN_SCREEN_PX = 6;
|
|
1091
|
+
function drawConnector(c, g, d, zoom, dashed, label, arrival) {
|
|
1092
|
+
if (Math.hypot(g.b.x - g.a.x, g.b.y - g.a.y) * zoom < CONNECTOR_MIN_SCREEN_PX) return;
|
|
1093
|
+
c.save();
|
|
1094
|
+
if (arrival) c.globalAlpha = arrival.alpha;
|
|
1095
|
+
c.strokeStyle = d.tokens["edge"];
|
|
1096
|
+
c.lineWidth = d.metrics.edgeWidth;
|
|
1097
|
+
c.lineCap = "round";
|
|
1098
|
+
c.lineJoin = "round";
|
|
1099
|
+
if (dashed) c.setLineDash([...import_core8.CANVAS_STYLE.edge.dash]);
|
|
1100
|
+
else if (arrival && arrival.draw < 1) c.setLineDash([g.len * arrival.draw, g.len * 2]);
|
|
1101
|
+
if (!(arrival && arrival.draw <= 1e-3)) c.stroke(g.path);
|
|
1102
|
+
c.setLineDash([]);
|
|
1103
|
+
c.stroke(g.arrowPath);
|
|
1104
|
+
const labelPx = label ? d.edgeLabel.size * (0, import_core8.labelGrowth)(zoom).connector : 0;
|
|
1105
|
+
if (label && labelPx * zoom >= LABEL_MIN_SCREEN_PX) {
|
|
1106
|
+
const px = labelPx;
|
|
1107
|
+
c.font = `${d.edgeLabel.weight} ${px}px ${d.fontNote}`;
|
|
1108
|
+
c.textAlign = "center";
|
|
1109
|
+
c.textBaseline = "middle";
|
|
1110
|
+
c.lineWidth = import_core8.CANVAS_STYLE.edge.labelHaloWidth;
|
|
1111
|
+
c.strokeStyle = d.tokens["canvas"];
|
|
1112
|
+
c.strokeText(label, g.mid.x, g.mid.y);
|
|
1113
|
+
c.fillStyle = d.tokens["edge-label"];
|
|
1114
|
+
c.fillText(label, g.mid.x, g.mid.y);
|
|
1115
|
+
}
|
|
1116
|
+
c.restore();
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// src/components/useFrames.ts
|
|
1120
|
+
var import_react5 = require("react");
|
|
1121
|
+
function useFrames(layerRef, busy) {
|
|
1122
|
+
const raf = (0, import_react5.useRef)(null);
|
|
1123
|
+
const busyRef = (0, import_react5.useRef)(busy);
|
|
1124
|
+
busyRef.current = busy;
|
|
1125
|
+
(0, import_react5.useEffect)(() => () => {
|
|
1126
|
+
if (raf.current !== null) cancelAnimationFrame(raf.current);
|
|
1127
|
+
raf.current = null;
|
|
1128
|
+
}, []);
|
|
1129
|
+
return (0, import_react5.useMemo)(() => {
|
|
1130
|
+
const tick = () => {
|
|
1131
|
+
raf.current = null;
|
|
1132
|
+
if (!busyRef.current()) return;
|
|
1133
|
+
layerRef.current?.batchDraw();
|
|
1134
|
+
raf.current = requestAnimationFrame(tick);
|
|
1135
|
+
};
|
|
1136
|
+
return {
|
|
1137
|
+
run: () => {
|
|
1138
|
+
layerRef.current?.batchDraw();
|
|
1139
|
+
if (raf.current === null) raf.current = requestAnimationFrame(tick);
|
|
1191
1140
|
}
|
|
1192
|
-
|
|
1193
|
-
]
|
|
1194
|
-
}
|
|
1195
|
-
var KonvaNode_default = KonvaNode;
|
|
1141
|
+
};
|
|
1142
|
+
}, [layerRef]);
|
|
1143
|
+
}
|
|
1196
1144
|
|
|
1197
1145
|
// src/components/KonvaNodeLayer.tsx
|
|
1198
|
-
var
|
|
1146
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1147
|
+
var SPATIAL_INDEX_THRESHOLD = 200;
|
|
1148
|
+
function stageViewRect(stage) {
|
|
1149
|
+
const s = stage.scaleX() || 1;
|
|
1150
|
+
return { minX: -stage.x() / s, minY: -stage.y() / s, maxX: (stage.width() - stage.x()) / s, maxY: (stage.height() - stage.y()) / s };
|
|
1151
|
+
}
|
|
1199
1152
|
function KonvaNodeLayer({
|
|
1200
1153
|
nodes: propNodes,
|
|
1201
|
-
isTransitioning = false,
|
|
1202
1154
|
isDragging = false,
|
|
1203
1155
|
hoveredNodeId = null,
|
|
1204
|
-
pressedNodeId = null
|
|
1156
|
+
pressedNodeId = null,
|
|
1157
|
+
selectedNodeId = null,
|
|
1158
|
+
focusedNodeId = null,
|
|
1159
|
+
design = DEFAULT_DESIGN,
|
|
1160
|
+
clock,
|
|
1161
|
+
touch = false,
|
|
1162
|
+
peekedNodeId = null
|
|
1205
1163
|
}) {
|
|
1206
1164
|
const layerRef = (0, import_react6.useRef)(null);
|
|
1207
|
-
const
|
|
1165
|
+
const fonts = useFontsEpoch();
|
|
1208
1166
|
const storeNodes = useNodeStore((s) => s.nodes);
|
|
1209
|
-
const
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1167
|
+
const nodes = (0, import_react6.useMemo)(() => propNodes ?? Array.from(storeNodes.values()), [propNodes, storeNodes]);
|
|
1168
|
+
const index = (0, import_react6.useMemo)(() => {
|
|
1169
|
+
if (nodes.length < SPATIAL_INDEX_THRESHOLD || isDragging) return null;
|
|
1170
|
+
const idx = new import_core9.RBushSpatialIndex();
|
|
1171
|
+
idx.bulkLoad(nodes.map((n) => ({ nodeId: n.id, bounds: (0, import_core9.nodeBounds)(n) })));
|
|
1172
|
+
return idx;
|
|
1173
|
+
}, [nodes, isDragging]);
|
|
1174
|
+
const byId = (0, import_react6.useMemo)(() => index ? new Map(nodes.map((n) => [n.id, n])) : null, [index, nodes]);
|
|
1175
|
+
const highlighted = hoveredNodeId ?? peekedNodeId ?? selectedNodeId;
|
|
1176
|
+
const N = import_core9.CANVAS_STYLE.node;
|
|
1177
|
+
const focusTheme = design.theme.startsWith("focus");
|
|
1178
|
+
const dimOpacity = focusTheme ? touch ? N.dimmedOpacityFocusTouch : N.dimmedOpacityFocus : touch ? N.dimmedOpacityTouch : N.dimmedOpacity;
|
|
1179
|
+
const dimOthers = highlighted !== null && dimOpacity !== 1;
|
|
1180
|
+
const tweens = (0, import_react6.useRef)(new HoverTweens()).current;
|
|
1181
|
+
const frames = useFrames(layerRef, () => clock?.active() || tweens.active());
|
|
1182
|
+
(0, import_react6.useLayoutEffect)(() => {
|
|
1183
|
+
const raised = /* @__PURE__ */ new Set();
|
|
1184
|
+
if (hoveredNodeId) raised.add(hoveredNodeId);
|
|
1185
|
+
if (pressedNodeId) raised.add(pressedNodeId);
|
|
1186
|
+
if (selectedNodeId) raised.add(selectedNodeId);
|
|
1187
|
+
if (tweens.set(raised, design.comfort.reducedMotion || !(0, import_core9.layerBlooms)(nodes.length))) frames.run();
|
|
1188
|
+
}, [hoveredNodeId, pressedNodeId, selectedNodeId, design.comfort.reducedMotion, (0, import_core9.layerBlooms)(nodes.length), tweens, frames]);
|
|
1189
|
+
(0, import_react6.useLayoutEffect)(() => clock?.subscribe(() => frames.run()), [clock, frames]);
|
|
1214
1190
|
(0, import_react6.useLayoutEffect)(() => {
|
|
1215
1191
|
layerRef.current?.batchDraw();
|
|
1216
|
-
}, [
|
|
1217
|
-
(
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1192
|
+
}, [nodes, index, design, fonts, hoveredNodeId, pressedNodeId, selectedNodeId, focusedNodeId, isDragging, touch, peekedNodeId]);
|
|
1193
|
+
const sceneFunc = (ctx2, shape) => {
|
|
1194
|
+
const stage = shape.getStage();
|
|
1195
|
+
if (!stage) return;
|
|
1196
|
+
const zoom = stage.scaleX() || 1;
|
|
1197
|
+
const view = stageViewRect(stage);
|
|
1198
|
+
let visible;
|
|
1199
|
+
if (isDragging) visible = nodes;
|
|
1200
|
+
else if (index && byId) visible = index.query(view).map((id) => byId.get(id)).filter(Boolean);
|
|
1201
|
+
else visible = nodes.filter((n) => (0, import_core9.intersectsViewport)(n, view));
|
|
1202
|
+
const c = ctx2._context;
|
|
1203
|
+
const m = c.getTransform();
|
|
1204
|
+
const opt = { shadows: !isDragging && (0, import_core9.shadowsShown)(visible.length), k: Math.hypot(m.a, m.b), base: m, dimOpacity };
|
|
1205
|
+
const st = { ...IDLE };
|
|
1206
|
+
const now = performance.now();
|
|
1207
|
+
const arrival = { alpha: 1, scale: 1, dx: 0, dy: 0 };
|
|
1208
|
+
const arriving = clock?.active(now) ?? false;
|
|
1209
|
+
for (let i = 0; i < visible.length; i++) {
|
|
1210
|
+
const n = visible[i];
|
|
1211
|
+
st.hovered = n.id === hoveredNodeId;
|
|
1212
|
+
st.pressed = n.id === pressedNodeId;
|
|
1213
|
+
st.selected = n.id === selectedNodeId;
|
|
1214
|
+
st.focused = n.id === focusedNodeId;
|
|
1215
|
+
st.dimmed = dimOthers && n.id !== highlighted;
|
|
1216
|
+
if (tweens.has(n.id)) {
|
|
1217
|
+
const p = tweens.progress(n.id, now);
|
|
1218
|
+
st.lift = p.lift;
|
|
1219
|
+
st.ring = p.ring;
|
|
1220
|
+
} else {
|
|
1221
|
+
st.lift = void 0;
|
|
1222
|
+
st.ring = void 0;
|
|
1223
|
+
}
|
|
1224
|
+
st.arrival = arriving && clock.node(n.id, n.x, n.y, now, arrival) ? arrival : null;
|
|
1225
|
+
drawIdea(c, n, design, zoom, st, opt);
|
|
1226
|
+
}
|
|
1227
|
+
c.setTransform(m);
|
|
1228
|
+
c.globalAlpha = 1;
|
|
1250
1229
|
};
|
|
1230
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(KLayer, { ref: layerRef, name: "nodes", listening: false, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(KShape, { sceneFunc, listening: false, perfectDrawEnabled: false }) });
|
|
1251
1231
|
}
|
|
1252
|
-
var EDGE_SPACING = 25;
|
|
1253
|
-
var STROKE_COLOR = "#000000";
|
|
1254
|
-
var STROKE_COLOR_DARK = "#ffffff";
|
|
1255
|
-
var STROKE_WIDTH = 2.5;
|
|
1256
|
-
var KonvaEdge = (0, import_react7.memo)(function KonvaEdge2({
|
|
1257
|
-
sourceNode: from,
|
|
1258
|
-
targetNode: to,
|
|
1259
|
-
connection,
|
|
1260
|
-
isDark = false,
|
|
1261
|
-
zoomLevel = 1
|
|
1262
|
-
}) {
|
|
1263
|
-
const label = connection?.label;
|
|
1264
|
-
const points = (0, import_react7.useMemo)(() => {
|
|
1265
|
-
const fromBaseX = from.width / 2;
|
|
1266
|
-
const fromBaseY = Math.max(import_core9.NODE_HEIGHT * 1.6 / 2, fromBaseX * 0.74);
|
|
1267
|
-
const fromScale = getOutermostScaleUp(from.branchCount);
|
|
1268
|
-
const fromRadiusX = fromBaseX * fromScale;
|
|
1269
|
-
const fromRadiusY = fromBaseY * fromScale;
|
|
1270
|
-
const toBaseX = to.width / 2;
|
|
1271
|
-
const toBaseY = Math.max(import_core9.NODE_HEIGHT * 1.6 / 2, toBaseX * 0.74);
|
|
1272
|
-
const toScale = getOutermostScaleUp(to.branchCount);
|
|
1273
|
-
const toRadiusX = toBaseX * toScale;
|
|
1274
|
-
const toRadiusY = toBaseY * toScale;
|
|
1275
|
-
const angle = Math.atan2(to.y - from.y, to.x - from.x);
|
|
1276
|
-
const fromEdge = getEllipseIntersection(from.x, from.y, fromRadiusX, fromRadiusY, angle);
|
|
1277
|
-
const toEdge = getEllipseIntersection(to.x, to.y, toRadiusX, toRadiusY, angle + Math.PI);
|
|
1278
|
-
const fromX = fromEdge.x + EDGE_SPACING * Math.cos(angle);
|
|
1279
|
-
const fromY = fromEdge.y + EDGE_SPACING * Math.sin(angle);
|
|
1280
|
-
const toX = toEdge.x - EDGE_SPACING * Math.cos(angle);
|
|
1281
|
-
const toY = toEdge.y - EDGE_SPACING * Math.sin(angle);
|
|
1282
|
-
const midX = (fromX + toX) / 2;
|
|
1283
|
-
const midY = (fromY + toY) / 2;
|
|
1284
|
-
const distance = Math.sqrt((toX - fromX) ** 2 + (toY - fromY) ** 2);
|
|
1285
|
-
const arcHeight = Math.min(45, 45 / (1 + Math.max(0, (distance - 200) / 400)));
|
|
1286
|
-
const perpX = -(toY - fromY) / distance;
|
|
1287
|
-
const perpY = (toX - fromX) / distance;
|
|
1288
|
-
const offsetMidX = midX + perpX * arcHeight;
|
|
1289
|
-
const offsetMidY = midY + perpY * arcHeight;
|
|
1290
|
-
const ctrl1X = fromX + (offsetMidX - fromX) * 0.66;
|
|
1291
|
-
const ctrl1Y = fromY + (offsetMidY - fromY) * 0.66;
|
|
1292
|
-
const ctrl2X = toX + (offsetMidX - toX) * 0.66;
|
|
1293
|
-
const ctrl2Y = toY + (offsetMidY - toY) * 0.66;
|
|
1294
|
-
const NUM_SEGMENTS = 20;
|
|
1295
|
-
const pts = [];
|
|
1296
|
-
for (let i = 0; i <= NUM_SEGMENTS; i++) {
|
|
1297
|
-
const t = i / NUM_SEGMENTS;
|
|
1298
|
-
const mt = 1 - t;
|
|
1299
|
-
const mt2 = mt * mt;
|
|
1300
|
-
const mt3 = mt2 * mt;
|
|
1301
|
-
const t2 = t * t;
|
|
1302
|
-
const t3 = t2 * t;
|
|
1303
|
-
pts.push(
|
|
1304
|
-
mt3 * fromX + 3 * mt2 * t * ctrl1X + 3 * mt * t2 * ctrl2X + t3 * toX,
|
|
1305
|
-
mt3 * fromY + 3 * mt2 * t * ctrl1Y + 3 * mt * t2 * ctrl2Y + t3 * toY
|
|
1306
|
-
);
|
|
1307
|
-
}
|
|
1308
|
-
return pts;
|
|
1309
|
-
}, [from.x, from.y, from.width, from.branchCount, to.x, to.y, to.width, to.branchCount]);
|
|
1310
|
-
const arrowHeadPoints = (0, import_react7.useMemo)(() => {
|
|
1311
|
-
const len = points.length;
|
|
1312
|
-
const tipX = points[len - 2];
|
|
1313
|
-
const tipY = points[len - 1];
|
|
1314
|
-
const prevX = points[len - 4];
|
|
1315
|
-
const prevY = points[len - 3];
|
|
1316
|
-
const angle = Math.atan2(tipY - prevY, tipX - prevX);
|
|
1317
|
-
const armLength = 11;
|
|
1318
|
-
const armAngle = Math.PI / 5.5;
|
|
1319
|
-
return [
|
|
1320
|
-
tipX - armLength * Math.cos(angle - armAngle),
|
|
1321
|
-
tipY - armLength * Math.sin(angle - armAngle),
|
|
1322
|
-
tipX,
|
|
1323
|
-
tipY,
|
|
1324
|
-
tipX - armLength * Math.cos(angle + armAngle),
|
|
1325
|
-
tipY - armLength * Math.sin(angle + armAngle)
|
|
1326
|
-
];
|
|
1327
|
-
}, [points]);
|
|
1328
|
-
const { pathData, fontSize } = (0, import_react7.useMemo)(() => {
|
|
1329
|
-
if (!label) return { pathData: null, fontSize: 12 };
|
|
1330
|
-
const fromBaseX = from.width / 2;
|
|
1331
|
-
const fromBaseY = Math.max(import_core9.NODE_HEIGHT * 1.6 / 2, fromBaseX * 0.74);
|
|
1332
|
-
const fromScale = getOutermostScaleUp(from.branchCount);
|
|
1333
|
-
const fromRadiusX = fromBaseX * fromScale;
|
|
1334
|
-
const fromRadiusY = fromBaseY * fromScale;
|
|
1335
|
-
const toBaseX = to.width / 2;
|
|
1336
|
-
const toBaseY = Math.max(import_core9.NODE_HEIGHT * 1.6 / 2, toBaseX * 0.74);
|
|
1337
|
-
const toScale = getOutermostScaleUp(to.branchCount);
|
|
1338
|
-
const toRadiusX = toBaseX * toScale;
|
|
1339
|
-
const toRadiusY = toBaseY * toScale;
|
|
1340
|
-
const angle = Math.atan2(to.y - from.y, to.x - from.x);
|
|
1341
|
-
const fromEdge = getEllipseIntersection(from.x, from.y, fromRadiusX, fromRadiusY, angle);
|
|
1342
|
-
const toEdge = getEllipseIntersection(to.x, to.y, toRadiusX, toRadiusY, angle + Math.PI);
|
|
1343
|
-
const fromX = fromEdge.x + EDGE_SPACING * Math.cos(angle);
|
|
1344
|
-
const fromY = fromEdge.y + EDGE_SPACING * Math.sin(angle);
|
|
1345
|
-
const toX = toEdge.x - EDGE_SPACING * Math.cos(angle);
|
|
1346
|
-
const toY = toEdge.y - EDGE_SPACING * Math.sin(angle);
|
|
1347
|
-
const midX = (fromX + toX) / 2;
|
|
1348
|
-
const midY = (fromY + toY) / 2;
|
|
1349
|
-
const distance = Math.sqrt((toX - fromX) ** 2 + (toY - fromY) ** 2);
|
|
1350
|
-
const arcHeight = Math.min(45, 45 / (1 + Math.max(0, (distance - 200) / 400)));
|
|
1351
|
-
const perpX = -(toY - fromY) / distance;
|
|
1352
|
-
const perpY = (toX - fromX) / distance;
|
|
1353
|
-
const offsetMidX = midX + perpX * arcHeight;
|
|
1354
|
-
const offsetMidY = midY + perpY * arcHeight;
|
|
1355
|
-
const ctrl1X = fromX + (offsetMidX - fromX) * 0.66;
|
|
1356
|
-
const ctrl1Y = fromY + (offsetMidY - fromY) * 0.66;
|
|
1357
|
-
const ctrl2X = toX + (offsetMidX - toX) * 0.66;
|
|
1358
|
-
const ctrl2Y = toY + (offsetMidY - toY) * 0.66;
|
|
1359
|
-
const TEXT_OFFSET = 15;
|
|
1360
|
-
const fxo = fromX + perpX * TEXT_OFFSET;
|
|
1361
|
-
const fyo = fromY + perpY * TEXT_OFFSET;
|
|
1362
|
-
const txo = toX + perpX * TEXT_OFFSET;
|
|
1363
|
-
const tyo = toY + perpY * TEXT_OFFSET;
|
|
1364
|
-
const c1xo = ctrl1X + perpX * TEXT_OFFSET;
|
|
1365
|
-
const c1yo = ctrl1Y + perpY * TEXT_OFFSET;
|
|
1366
|
-
const c2xo = ctrl2X + perpX * TEXT_OFFSET;
|
|
1367
|
-
const c2yo = ctrl2Y + perpY * TEXT_OFFSET;
|
|
1368
|
-
const baseFontSize = 14;
|
|
1369
|
-
const zoomFactor = Math.max(1, 1 / zoomLevel);
|
|
1370
|
-
const scaledFontSize = Math.max(12, Math.min(24, baseFontSize * (from.width / 180) * zoomFactor));
|
|
1371
|
-
let d = "";
|
|
1372
|
-
if (fromX > toX) {
|
|
1373
|
-
d = `M ${txo},${tyo} C ${c2xo},${c2yo} ${c1xo},${c1yo} ${fxo},${fyo}`;
|
|
1374
|
-
} else {
|
|
1375
|
-
d = `M ${fxo},${fyo} C ${c1xo},${c1yo} ${c2xo},${c2yo} ${txo},${tyo}`;
|
|
1376
|
-
}
|
|
1377
|
-
return { pathData: d, fontSize: scaledFontSize };
|
|
1378
|
-
}, [from.x, from.y, from.width, from.branchCount, to.x, to.y, to.width, to.branchCount, label, zoomLevel]);
|
|
1379
|
-
const strokeColor = isDark ? STROKE_COLOR_DARK : STROKE_COLOR;
|
|
1380
|
-
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(KGroup, { children: [
|
|
1381
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1382
|
-
KLine,
|
|
1383
|
-
{
|
|
1384
|
-
points,
|
|
1385
|
-
stroke: strokeColor,
|
|
1386
|
-
strokeWidth: STROKE_WIDTH,
|
|
1387
|
-
tension: 0,
|
|
1388
|
-
opacity: 0.9,
|
|
1389
|
-
lineCap: "round",
|
|
1390
|
-
lineJoin: "round",
|
|
1391
|
-
listening: false,
|
|
1392
|
-
perfectDrawEnabled: false
|
|
1393
|
-
}
|
|
1394
|
-
),
|
|
1395
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1396
|
-
KLine,
|
|
1397
|
-
{
|
|
1398
|
-
points: arrowHeadPoints,
|
|
1399
|
-
stroke: strokeColor,
|
|
1400
|
-
strokeWidth: STROKE_WIDTH,
|
|
1401
|
-
lineCap: "round",
|
|
1402
|
-
lineJoin: "round",
|
|
1403
|
-
opacity: 0.9,
|
|
1404
|
-
listening: false,
|
|
1405
|
-
perfectDrawEnabled: false
|
|
1406
|
-
}
|
|
1407
|
-
),
|
|
1408
|
-
label && pathData && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1409
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1410
|
-
KTextPath,
|
|
1411
|
-
{
|
|
1412
|
-
data: pathData,
|
|
1413
|
-
text: label,
|
|
1414
|
-
fontSize,
|
|
1415
|
-
fontFamily: "'Playpen Sans', cursive",
|
|
1416
|
-
fontStyle: "normal",
|
|
1417
|
-
fill: "#ffffff",
|
|
1418
|
-
align: "center",
|
|
1419
|
-
letterSpacing: 0.5,
|
|
1420
|
-
listening: false,
|
|
1421
|
-
opacity: 0.9
|
|
1422
|
-
}
|
|
1423
|
-
),
|
|
1424
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1425
|
-
KTextPath,
|
|
1426
|
-
{
|
|
1427
|
-
data: pathData,
|
|
1428
|
-
text: label,
|
|
1429
|
-
fontSize,
|
|
1430
|
-
fontFamily: "'Playpen Sans', cursive",
|
|
1431
|
-
fontStyle: "normal",
|
|
1432
|
-
fill: "#6b6860",
|
|
1433
|
-
align: "center",
|
|
1434
|
-
letterSpacing: 0.5,
|
|
1435
|
-
listening: false,
|
|
1436
|
-
opacity: 0.9
|
|
1437
|
-
}
|
|
1438
|
-
)
|
|
1439
|
-
] })
|
|
1440
|
-
] });
|
|
1441
|
-
});
|
|
1442
|
-
var KonvaEdge_default = KonvaEdge;
|
|
1443
1232
|
|
|
1444
1233
|
// src/components/KonvaEdgeLayer.tsx
|
|
1445
|
-
var
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
const layerRef = (0, import_react8.useRef)(null);
|
|
1454
|
-
const zoomLevel = useViewportStore((s) => s.zoomLevel);
|
|
1234
|
+
var import_react7 = require("react");
|
|
1235
|
+
var import_core10 = require("@visualli/core");
|
|
1236
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1237
|
+
var key = (n) => `${n.x},${n.y},${n.width},${n.level},${n.branchCount ?? 0}`;
|
|
1238
|
+
var MARGIN = 80;
|
|
1239
|
+
function KonvaEdgeLayer({ connections, isDragging = false, design = DEFAULT_DESIGN, clock }) {
|
|
1240
|
+
const layerRef = (0, import_react7.useRef)(null);
|
|
1241
|
+
const fonts = useFontsEpoch();
|
|
1455
1242
|
const storeNodes = useNodeStore((s) => s.nodes);
|
|
1456
|
-
const
|
|
1457
|
-
const
|
|
1458
|
-
|
|
1243
|
+
const cache3 = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
|
|
1244
|
+
const edges = (0, import_react7.useMemo)(() => {
|
|
1245
|
+
const out = [];
|
|
1246
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1247
|
+
connections.forEach((conn, i) => {
|
|
1248
|
+
const src = storeNodes.get(conn.from);
|
|
1249
|
+
const tgt = storeNodes.get(conn.to);
|
|
1250
|
+
if (!src || !tgt) return;
|
|
1251
|
+
const id = `${conn.from}>${conn.to}#${i}`;
|
|
1252
|
+
seen.add(id);
|
|
1253
|
+
const k = `${key(src)}|${key(tgt)}`;
|
|
1254
|
+
let hit = cache3.current.get(id);
|
|
1255
|
+
if (!hit || hit.k !== k) {
|
|
1256
|
+
hit = { k, g: prepareConnector((0, import_core10.connectorGeometry)(src, tgt, design.metrics.edgeGap)) };
|
|
1257
|
+
cache3.current.set(id, hit);
|
|
1258
|
+
}
|
|
1259
|
+
out.push({ g: hit.g, dashed: conn.style === "dashed", label: conn.label });
|
|
1260
|
+
});
|
|
1261
|
+
for (const id of cache3.current.keys()) if (!seen.has(id)) cache3.current.delete(id);
|
|
1262
|
+
return out;
|
|
1263
|
+
}, [connections, storeNodes, design.metrics.edgeGap]);
|
|
1264
|
+
const frames = useFrames(layerRef, () => clock?.active() ?? false);
|
|
1265
|
+
(0, import_react7.useLayoutEffect)(() => clock?.subscribe(() => frames.run()), [clock, frames]);
|
|
1266
|
+
(0, import_react7.useLayoutEffect)(() => {
|
|
1459
1267
|
layerRef.current?.batchDraw();
|
|
1460
|
-
}, [
|
|
1461
|
-
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1268
|
+
}, [edges, design, fonts, isDragging]);
|
|
1269
|
+
const sceneFunc = (ctx2, shape) => {
|
|
1270
|
+
const stage = shape.getStage();
|
|
1271
|
+
if (!stage) return;
|
|
1272
|
+
const zoom = stage.scaleX() || 1;
|
|
1273
|
+
const v = stageViewRect(stage);
|
|
1274
|
+
const c = ctx2._context;
|
|
1275
|
+
const now = performance.now();
|
|
1276
|
+
const arriving = clock?.active(now) ?? false;
|
|
1277
|
+
const arrival = { alpha: 1, draw: 1 };
|
|
1278
|
+
for (let i = 0; i < edges.length; i++) {
|
|
1279
|
+
const e = edges[i];
|
|
1280
|
+
const { a, b, mid } = e.g;
|
|
1281
|
+
const minX = Math.min(a.x, b.x, mid.x) - MARGIN, maxX = Math.max(a.x, b.x, mid.x) + MARGIN;
|
|
1282
|
+
const minY = Math.min(a.y, b.y, mid.y) - MARGIN, maxY = Math.max(a.y, b.y, mid.y) + MARGIN;
|
|
1283
|
+
if (maxX < v.minX || minX > v.maxX || maxY < v.minY || minY > v.maxY) continue;
|
|
1284
|
+
drawConnector(c, e.g, design, zoom, e.dashed, e.label, arriving && clock.edge(i, e.dashed, now, arrival) ? arrival : null);
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(KLayer, { ref: layerRef, listening: false, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(KShape, { sceneFunc, listening: false, perfectDrawEnabled: false }) });
|
|
1478
1288
|
}
|
|
1479
1289
|
|
|
1480
1290
|
// src/components/KonvaContainerLayer.tsx
|
|
1481
|
-
var
|
|
1291
|
+
var import_react8 = require("react");
|
|
1482
1292
|
|
|
1483
|
-
// src/
|
|
1484
|
-
var import_react9 = require("react");
|
|
1293
|
+
// src/design/containers.ts
|
|
1485
1294
|
var import_core11 = require("@visualli/core");
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
var
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
function computeNodeTextWorldScale2(nodeWidth, zoomLevel) {
|
|
1493
|
-
const safeZoom = Math.max(zoomLevel, 1e-4);
|
|
1494
|
-
const baseWidth = 300;
|
|
1495
|
-
const baseRadiusX = baseWidth / 2;
|
|
1496
|
-
const baseRadiusY = Math.max(import_core10.NODE_HEIGHT * 1.6 / 2, baseRadiusX * 0.74);
|
|
1497
|
-
const textNaturalWidth = baseWidth - 40;
|
|
1498
|
-
const textNaturalHeight = import_core10.NODE_HEIGHT * 1.2;
|
|
1499
|
-
const maxScaleX = baseRadiusX * 2 * NODE_TEXT_SAFE_BLOB_FILL_RATIO / Math.max(textNaturalWidth, 1);
|
|
1500
|
-
const maxScaleY = baseRadiusY * 2 * NODE_TEXT_SAFE_BLOB_FILL_RATIO / Math.max(textNaturalHeight, 1);
|
|
1501
|
-
const fitScale = Math.max(0.01, Math.min(maxScaleX, maxScaleY));
|
|
1502
|
-
return Math.min(1 / safeZoom, fitScale);
|
|
1295
|
+
var PILL = import_core11.CANVAS_STYLE.group;
|
|
1296
|
+
var pillFont = (d, px) => `${PILL.labelWeight} ${px}px ${d.fontNote}`;
|
|
1297
|
+
var measureCtx = null;
|
|
1298
|
+
function measurer() {
|
|
1299
|
+
if (!measureCtx && typeof document !== "undefined") measureCtx = document.createElement("canvas").getContext("2d");
|
|
1300
|
+
return measureCtx;
|
|
1503
1301
|
}
|
|
1504
|
-
function
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
var _labelCtx = null;
|
|
1523
|
-
function measureLabelBoxWidth(text, fontSize) {
|
|
1524
|
-
if (!_labelCtx) {
|
|
1525
|
-
const c = document.createElement("canvas");
|
|
1526
|
-
_labelCtx = c.getContext("2d");
|
|
1302
|
+
function measurePill(text, d, k, c = measurer()) {
|
|
1303
|
+
const px = PILL.labelSize * d.labelScale * k;
|
|
1304
|
+
const padX = PILL.labelPadding.x, border = PILL.labelBorderWidth;
|
|
1305
|
+
const room = PILL.labelMaxWidth - padX * 2 - border * 2;
|
|
1306
|
+
let shown = text, tw = text.length * px * 0.5;
|
|
1307
|
+
if (c) {
|
|
1308
|
+
c.font = pillFont(d, px);
|
|
1309
|
+
tw = c.measureText(text).width;
|
|
1310
|
+
if (tw > room) {
|
|
1311
|
+
let lo = 0, hi = text.length;
|
|
1312
|
+
while (lo < hi) {
|
|
1313
|
+
const mid = lo + hi + 1 >> 1;
|
|
1314
|
+
if (c.measureText(text.slice(0, mid).trimEnd() + "\u2026").width <= room) lo = mid;
|
|
1315
|
+
else hi = mid - 1;
|
|
1316
|
+
}
|
|
1317
|
+
shown = text.slice(0, lo).trimEnd() + "\u2026";
|
|
1318
|
+
tw = c.measureText(shown).width;
|
|
1319
|
+
}
|
|
1527
1320
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1321
|
+
return {
|
|
1322
|
+
text: shown,
|
|
1323
|
+
x: 0,
|
|
1324
|
+
y: 0,
|
|
1325
|
+
side: "bottom",
|
|
1326
|
+
w: Math.min(PILL.labelMaxWidth, tw + padX * 2 + border * 2),
|
|
1327
|
+
h: px * PILL.labelLineHeight + PILL.labelPadding.top + PILL.labelPadding.bottom + border * 2
|
|
1328
|
+
};
|
|
1531
1329
|
}
|
|
1532
|
-
function
|
|
1533
|
-
|
|
1330
|
+
function ideaBox(n) {
|
|
1331
|
+
const { rx, ry } = (0, import_core11.nodeRadii)(n);
|
|
1332
|
+
const rings = (0, import_core11.ringsFor)(n.branchCount);
|
|
1333
|
+
const s = rings.length ? import_core11.RINGS[rings.length - 1].scale : 1;
|
|
1334
|
+
return { x0: n.x - rx * s, x1: n.x + rx * s, y0: n.y - ry * s, y1: n.y + ry * s };
|
|
1534
1335
|
}
|
|
1535
|
-
function
|
|
1536
|
-
if (
|
|
1537
|
-
|
|
1538
|
-
for (
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
const da = Math.atan2(a.y - sp.y, a.x - sp.x);
|
|
1545
|
-
const db = Math.atan2(b.y - sp.y, b.x - sp.x);
|
|
1546
|
-
if (da !== db) return da - db;
|
|
1547
|
-
return Math.hypot(a.x - sp.x, a.y - sp.y) - Math.hypot(b.x - sp.x, b.y - sp.y);
|
|
1336
|
+
function layoutContainers(containers, nodes, connections, d) {
|
|
1337
|
+
if (containers.length === 0) return [];
|
|
1338
|
+
const ideas = [];
|
|
1339
|
+
for (const n of nodes.values()) ideas.push(ideaBox(n));
|
|
1340
|
+
const lines = connections.flatMap((c) => {
|
|
1341
|
+
const a = nodes.get(c.from), b = nodes.get(c.to);
|
|
1342
|
+
if (!a || !b) return [];
|
|
1343
|
+
const g = (0, import_core11.connectorGeometry)(a, b, d.metrics.edgeGap);
|
|
1344
|
+
return [(0, import_core11.connectorSamples)(g.a, g.mid, g.b)];
|
|
1548
1345
|
});
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1346
|
+
const placed = [];
|
|
1347
|
+
const out = [];
|
|
1348
|
+
for (const c of containers) {
|
|
1349
|
+
const members = c.nodeIds.map((id) => nodes.get(id)).filter((n) => !!n);
|
|
1350
|
+
const hull = (0, import_core11.containerHull)(members.map((n) => ({ x: n.x, y: n.y, ...(0, import_core11.nodeRadii)(n) })));
|
|
1351
|
+
if (!hull) continue;
|
|
1352
|
+
let pill = null;
|
|
1353
|
+
if (c.label) {
|
|
1354
|
+
const size = measurePill(c.label, d, 1);
|
|
1355
|
+
const at = (0, import_core11.placeContainerLabel)(hull, size, { ideas, connectors: lines, labels: placed });
|
|
1356
|
+
pill = { ...size, x: at.x, y: at.y, side: at.side };
|
|
1357
|
+
placed.push({ x0: at.x - size.w / 2, x1: at.x + size.w / 2, y0: at.y - size.h / 2, y1: at.y + size.h / 2 });
|
|
1358
|
+
}
|
|
1359
|
+
out.push({ id: c.id, hull, pill });
|
|
1553
1360
|
}
|
|
1554
|
-
return
|
|
1361
|
+
return out;
|
|
1555
1362
|
}
|
|
1556
|
-
function
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
}
|
|
1566
|
-
cur = next;
|
|
1567
|
-
}
|
|
1568
|
-
return cur.flatMap((p) => [p.x, p.y]);
|
|
1363
|
+
function drawHull(c, h, d) {
|
|
1364
|
+
c.save();
|
|
1365
|
+
c.strokeStyle = d.tokens["line-strong"];
|
|
1366
|
+
c.lineWidth = PILL.strokeWidth;
|
|
1367
|
+
c.setLineDash([...PILL.dash]);
|
|
1368
|
+
c.beginPath();
|
|
1369
|
+
c.roundRect(h.x0, h.y0, h.x1 - h.x0, h.y1 - h.y0, h.radius);
|
|
1370
|
+
c.stroke();
|
|
1371
|
+
c.restore();
|
|
1569
1372
|
}
|
|
1570
|
-
function
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
return result;
|
|
1373
|
+
function drawPill(c, p, d, zoom) {
|
|
1374
|
+
const k = (0, import_core11.labelGrowth)(zoom).idea;
|
|
1375
|
+
const size = k === 1 ? p : measurePill(p.text, d, k, c);
|
|
1376
|
+
const contrast = d.theme.startsWith("contrast");
|
|
1377
|
+
const bw = contrast ? PILL.contrastLabelBorderWidth : PILL.labelBorderWidth;
|
|
1378
|
+
c.save();
|
|
1379
|
+
c.beginPath();
|
|
1380
|
+
c.roundRect(p.x - size.w / 2 + bw / 2, p.y - size.h / 2 + bw / 2, size.w - bw, size.h - bw, (size.h - bw) / 2);
|
|
1381
|
+
c.fillStyle = d.tokens[PILL.labelFill];
|
|
1382
|
+
c.fill();
|
|
1383
|
+
c.lineWidth = bw;
|
|
1384
|
+
c.strokeStyle = d.tokens[contrast ? PILL.contrastLabelBorder : PILL.labelBorder];
|
|
1385
|
+
c.stroke();
|
|
1386
|
+
c.font = pillFont(d, PILL.labelSize * d.labelScale * k);
|
|
1387
|
+
c.fillStyle = d.tokens[PILL.labelInk];
|
|
1388
|
+
c.textAlign = "center";
|
|
1389
|
+
c.textBaseline = "middle";
|
|
1390
|
+
c.fillText(size.text, p.x, p.y + (PILL.labelPadding.top - PILL.labelPadding.bottom) / 2);
|
|
1391
|
+
c.restore();
|
|
1590
1392
|
}
|
|
1591
|
-
function calculateHull(containerNodes) {
|
|
1592
|
-
if (containerNodes.length === 0) return null;
|
|
1593
|
-
const NUM_SAMPLES = 48;
|
|
1594
|
-
const samples = [];
|
|
1595
|
-
for (const node of containerNodes) {
|
|
1596
|
-
const baseRX = node.width / 2;
|
|
1597
|
-
const baseRY = Math.max(import_core11.NODE_HEIGHT * 1.6 / 2, baseRX * 0.74);
|
|
1598
|
-
const halfW = baseRX * MAX_BLOB_EXTENT_X + CONTAINER_PADDING;
|
|
1599
|
-
const halfH = baseRY * MAX_BLOB_EXTENT_Y + CONTAINER_PADDING;
|
|
1600
|
-
for (let j = 0; j < NUM_SAMPLES; j++) {
|
|
1601
|
-
const angle = j / NUM_SAMPLES * Math.PI * 2;
|
|
1602
|
-
samples.push({ x: node.x + halfW * Math.cos(angle), y: node.y + halfH * Math.sin(angle) });
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
const hull = convexHull(samples);
|
|
1606
|
-
const points = chaikinSmooth(hull, 6);
|
|
1607
|
-
const xs = containerNodes.map((n) => n.x);
|
|
1608
|
-
const ys = containerNodes.map((n) => n.y);
|
|
1609
|
-
const spanX = Math.max(...xs) - Math.min(...xs);
|
|
1610
|
-
const spanY = Math.max(...ys) - Math.min(...ys);
|
|
1611
|
-
const useTopEdge = spanY > spanX * 2;
|
|
1612
|
-
let hullMinX = Infinity, hullMaxX = -Infinity;
|
|
1613
|
-
for (let i = 0; i < points.length; i += 2) {
|
|
1614
|
-
if (points[i] < hullMinX) hullMinX = points[i];
|
|
1615
|
-
if (points[i] > hullMaxX) hullMaxX = points[i];
|
|
1616
|
-
}
|
|
1617
|
-
const anchorX = (hullMinX + hullMaxX) / 2;
|
|
1618
|
-
const anchorEdgeY = hullEdgeYAtX(points, anchorX, !useTopEdge);
|
|
1619
|
-
return { points, anchorX, anchorEdgeY };
|
|
1620
|
-
}
|
|
1621
|
-
var KonvaContainer = (0, import_react9.memo)(function KonvaContainer2({
|
|
1622
|
-
nodes,
|
|
1623
|
-
label = ""
|
|
1624
|
-
}) {
|
|
1625
|
-
const zoomLevel = useViewportStore((s) => s.zoomLevel);
|
|
1626
|
-
const hullData = (0, import_react9.useMemo)(() => calculateHull(nodes), [nodes]);
|
|
1627
|
-
if (!hullData) return null;
|
|
1628
|
-
const refNode = nodes[0];
|
|
1629
|
-
const screenScale = refNode ? computeNodeTextScreenScale2(refNode.width, zoomLevel) : 1;
|
|
1630
|
-
const labelFontSize = LABEL_FONT_SIZE * screenScale;
|
|
1631
|
-
const labelBoxHeight = Math.ceil(labelFontSize * LABEL_LINE_HEIGHT + 2 * LABEL_PAD_V);
|
|
1632
|
-
const boxWidth = label ? measureLabelBoxWidth(label, labelFontSize) : 0;
|
|
1633
|
-
const boxX = hullData.anchorX - boxWidth / 2;
|
|
1634
|
-
const boxY = hullData.anchorEdgeY - labelBoxHeight / 2;
|
|
1635
|
-
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(KGroup, { listening: false, children: [
|
|
1636
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1637
|
-
KLine,
|
|
1638
|
-
{
|
|
1639
|
-
points: hullData.points,
|
|
1640
|
-
stroke: "#747270",
|
|
1641
|
-
strokeWidth: 1,
|
|
1642
|
-
dash: CONTAINER_DASH,
|
|
1643
|
-
closed: true,
|
|
1644
|
-
tension: 0,
|
|
1645
|
-
listening: false,
|
|
1646
|
-
perfectDrawEnabled: false
|
|
1647
|
-
}
|
|
1648
|
-
),
|
|
1649
|
-
label.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1650
|
-
KGroup,
|
|
1651
|
-
{
|
|
1652
|
-
x: boxX + boxWidth / 2,
|
|
1653
|
-
y: boxY + labelBoxHeight / 2,
|
|
1654
|
-
offsetX: boxWidth / 2,
|
|
1655
|
-
offsetY: labelBoxHeight / 2,
|
|
1656
|
-
scaleX: 1 / zoomLevel,
|
|
1657
|
-
scaleY: 1 / zoomLevel,
|
|
1658
|
-
visible: zoomLevel >= import_core11.TEXT_LABEL_HIDE_BELOW_ZOOM,
|
|
1659
|
-
listening: false,
|
|
1660
|
-
children: [
|
|
1661
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1662
|
-
KRect,
|
|
1663
|
-
{
|
|
1664
|
-
width: boxWidth,
|
|
1665
|
-
height: labelBoxHeight,
|
|
1666
|
-
fill: LABEL_BG,
|
|
1667
|
-
cornerRadius: LABEL_BORDER_RADIUS,
|
|
1668
|
-
shadowEnabled: false,
|
|
1669
|
-
perfectDrawEnabled: false
|
|
1670
|
-
}
|
|
1671
|
-
),
|
|
1672
|
-
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1673
|
-
KText,
|
|
1674
|
-
{
|
|
1675
|
-
x: LABEL_PAD_H,
|
|
1676
|
-
y: LABEL_PAD_V,
|
|
1677
|
-
width: boxWidth - 2 * LABEL_PAD_H,
|
|
1678
|
-
height: labelBoxHeight - 2 * LABEL_PAD_V,
|
|
1679
|
-
text: label,
|
|
1680
|
-
fontSize: labelFontSize,
|
|
1681
|
-
fontFamily: LABEL_FONT_FAMILY,
|
|
1682
|
-
fontStyle: LABEL_FONT_WEIGHT,
|
|
1683
|
-
fill: "rgba(0,0,0,1)",
|
|
1684
|
-
align: "center",
|
|
1685
|
-
verticalAlign: "middle",
|
|
1686
|
-
lineHeight: LABEL_LINE_HEIGHT,
|
|
1687
|
-
perfectDrawEnabled: false,
|
|
1688
|
-
listening: false
|
|
1689
|
-
}
|
|
1690
|
-
)
|
|
1691
|
-
]
|
|
1692
|
-
}
|
|
1693
|
-
)
|
|
1694
|
-
] });
|
|
1695
|
-
});
|
|
1696
|
-
var KonvaContainer_default = KonvaContainer;
|
|
1697
1393
|
|
|
1698
1394
|
// src/components/KonvaContainerLayer.tsx
|
|
1699
|
-
var
|
|
1700
|
-
function
|
|
1701
|
-
const
|
|
1395
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1396
|
+
function useContainerLayout(containers, connections = [], design = DEFAULT_DESIGN) {
|
|
1397
|
+
const fonts = useFontsEpoch();
|
|
1702
1398
|
const storeNodes = useNodeStore((s) => s.nodes);
|
|
1703
|
-
(0,
|
|
1399
|
+
return (0, import_react8.useMemo)(
|
|
1400
|
+
() => layoutContainers(containers, storeNodes, connections, design),
|
|
1401
|
+
// fonts: the names are measured, so measure again once the web fonts are in.
|
|
1402
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1403
|
+
[containers, connections, storeNodes, design, fonts]
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
function KonvaContainerLayer({ containers, connections = [], design = DEFAULT_DESIGN }) {
|
|
1407
|
+
const layerRef = (0, import_react8.useRef)(null);
|
|
1408
|
+
const placed = useContainerLayout(containers, connections, design);
|
|
1409
|
+
(0, import_react8.useLayoutEffect)(() => {
|
|
1704
1410
|
layerRef.current?.batchDraw();
|
|
1705
|
-
}, [
|
|
1411
|
+
}, [placed, design]);
|
|
1706
1412
|
if (containers.length === 0) return null;
|
|
1707
|
-
return /* @__PURE__ */ (0,
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
{
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1413
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(KLayer, { ref: layerRef, listening: false, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1414
|
+
KShape,
|
|
1415
|
+
{
|
|
1416
|
+
listening: false,
|
|
1417
|
+
perfectDrawEnabled: false,
|
|
1418
|
+
sceneFunc: (ctx2) => {
|
|
1419
|
+
for (const p of placed) drawHull(ctx2._context, p.hull, design);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
) });
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// src/components/KonvaContainerLabelLayer.tsx
|
|
1426
|
+
var import_react9 = require("react");
|
|
1427
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1428
|
+
function KonvaContainerLabelLayer({ containers, connections = [], design = DEFAULT_DESIGN }) {
|
|
1429
|
+
const layerRef = (0, import_react9.useRef)(null);
|
|
1430
|
+
const placed = useContainerLayout(containers, connections, design);
|
|
1431
|
+
(0, import_react9.useLayoutEffect)(() => {
|
|
1432
|
+
layerRef.current?.batchDraw();
|
|
1433
|
+
}, [placed, design]);
|
|
1434
|
+
if (!placed.some((p) => p.pill)) return null;
|
|
1435
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(KLayer, { ref: layerRef, listening: false, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1436
|
+
KShape,
|
|
1437
|
+
{
|
|
1438
|
+
listening: false,
|
|
1439
|
+
perfectDrawEnabled: false,
|
|
1440
|
+
sceneFunc: (ctx2, shape) => {
|
|
1441
|
+
const zoom = shape.getStage()?.scaleX() || 1;
|
|
1442
|
+
for (const p of placed) if (p.pill) drawPill(ctx2._context, p.pill, design, zoom);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
) });
|
|
1720
1446
|
}
|
|
1721
1447
|
|
|
1722
1448
|
// src/components/NavigationStack.tsx
|
|
1723
1449
|
var import_react11 = require("react");
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
"
|
|
1733
|
-
"
|
|
1734
|
-
];
|
|
1735
|
-
var
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
var
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
})
|
|
1756
|
-
|
|
1757
|
-
const
|
|
1758
|
-
const
|
|
1759
|
-
const
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1450
|
+
|
|
1451
|
+
// src/components/Overlays.tsx
|
|
1452
|
+
var import_react10 = __toESM(require("react"), 1);
|
|
1453
|
+
var import_core12 = require("@visualli/core");
|
|
1454
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1455
|
+
var Icon = ({ children, size = 20 }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "vi-icon", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
|
|
1456
|
+
var MinusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Icon, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) });
|
|
1457
|
+
var PlusIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Icon, { children: [
|
|
1458
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
|
|
1459
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "5", y1: "12", x2: "19", y2: "12" })
|
|
1460
|
+
] });
|
|
1461
|
+
var FitIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Icon, { children: [
|
|
1462
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
1463
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
|
|
1464
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
|
|
1465
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
|
|
1466
|
+
] });
|
|
1467
|
+
var ChevronLeftIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Icon, { size: 14, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "15 6 9 12 15 18" }) });
|
|
1468
|
+
var ChevronIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Icon, { size: 14, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "9 6 15 12 9 18" }) });
|
|
1469
|
+
var ExternalIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Icon, { size: 14, children: [
|
|
1470
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }),
|
|
1471
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "15 3 21 3 21 9" }),
|
|
1472
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "10", y1: "14", x2: "21", y2: "3" })
|
|
1473
|
+
] });
|
|
1474
|
+
var HomeIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Icon, { size: import_core12.TRAIL.homeIconSize, children: [
|
|
1475
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" }),
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "9 22 9 12 15 12 15 22" })
|
|
1477
|
+
] });
|
|
1478
|
+
var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Icon, { size: 18, children: [
|
|
1479
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
|
|
1480
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
|
|
1481
|
+
] });
|
|
1482
|
+
var ZoomControls = (0, import_react10.memo)(function ZoomControls2({ onFit, touch = false }) {
|
|
1483
|
+
const size = touch ? "vi-iconbtn--md" : "vi-iconbtn--sm";
|
|
1484
|
+
const zoom = useViewportStore((s) => s.zoomLevel);
|
|
1485
|
+
const setZoom = useViewportStore((s) => s.setZoom);
|
|
1486
|
+
const fitted = useViewportStore((s) => Math.abs(s.zoomLevel - s.fitScale) < 1e-6 && Math.abs(s.centerX - s.fitCenterX) < 0.5 && Math.abs(s.centerY - s.fitCenterY) < 0.5);
|
|
1487
|
+
const fit = useViewportStore((s) => s.fitScale);
|
|
1488
|
+
const zoomIn = (0, import_react10.useCallback)(() => setZoom(zoom * import_core12.VIEW.zoomStep), [zoom, setZoom]);
|
|
1489
|
+
const zoomOut = (0, import_react10.useCallback)(() => setZoom(zoom / import_core12.VIEW.zoomStep), [zoom, setZoom]);
|
|
1490
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vi-ctrls", role: "toolbar", "aria-label": "Canvas", "data-help": "zoom-controls", children: [
|
|
1491
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: `vi-iconbtn ${size}`, "aria-label": "Zoom out", onClick: zoomOut, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MinusIcon, {}) }),
|
|
1492
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "vi-ctrls__pct", "aria-live": "polite", children: [
|
|
1493
|
+
Math.round(zoom / fit * 100),
|
|
1494
|
+
"%"
|
|
1495
|
+
] }),
|
|
1496
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: `vi-iconbtn ${size}`, "aria-label": "Zoom in", onClick: zoomIn, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(PlusIcon, {}) }),
|
|
1497
|
+
onFit && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1498
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vi-ctrls__div", "aria-hidden": "true" }),
|
|
1499
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: `vi-iconbtn ${size}`, "aria-label": "Fit map to view", disabled: fitted, onClick: onFit, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FitIcon, {}) })
|
|
1500
|
+
] })
|
|
1763
1501
|
] });
|
|
1764
1502
|
});
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
})
|
|
1772
|
-
|
|
1503
|
+
function paintVars(p) {
|
|
1504
|
+
if (p.custom) return { fill: p.custom.fill, ring: p.custom.ring ?? "var(--edge)" };
|
|
1505
|
+
const t = p.topic ?? import_core12.TRAIL.rootTopic;
|
|
1506
|
+
return { fill: `var(--topic-${t})`, ring: `var(--topic-${t}-ring)` };
|
|
1507
|
+
}
|
|
1508
|
+
var TrailDot = ({ level, paint, current }) => {
|
|
1509
|
+
const { fill, ring } = paintVars(paint);
|
|
1510
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: import_core12.TRAIL.dotBox, height: import_core12.TRAIL.dotBox, viewBox: `${-import_core12.TRAIL.dotBox / 2} ${-import_core12.TRAIL.dotBox / 2} ${import_core12.TRAIL.dotBox} ${import_core12.TRAIL.dotBox}`, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: (0, import_core12.blobPath)((0, import_core12.shapeOfLevel)(level), import_core12.TRAIL.dotRadius, import_core12.TRAIL.dotRadius), fill, stroke: ring, strokeWidth: current ? import_core12.TRAIL.currentStroke : import_core12.TRAIL.stroke }) });
|
|
1511
|
+
};
|
|
1512
|
+
var DepthTrail = (0, import_react10.memo)(function DepthTrail2({ stack, onNavigateBack }) {
|
|
1773
1513
|
if (stack.length === 0) return null;
|
|
1774
|
-
const
|
|
1775
|
-
const
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
const connectorColor = isDark ? "#908e8c" : "#747270";
|
|
1779
|
-
const connectorMarginLeft = 10.5;
|
|
1780
|
-
const currentIndex = stack.length - 1;
|
|
1781
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1782
|
-
"div",
|
|
1514
|
+
const current = stack.length - 1;
|
|
1515
|
+
const iconOnly = import_core12.TRAIL.rootLabel === "icon";
|
|
1516
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("nav", { className: "vi-trail", "aria-label": "Depth trail", "data-help": "navigation-stack", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ol", { children: stack.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { className: i === current ? "is-current" : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1517
|
+
"button",
|
|
1783
1518
|
{
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
const rowBg = isCurrent ? hexToRgba(pastelFill, 0.6) : isHovered && isClickable ? hexToRgba(pastelFill, 0.3) : "transparent";
|
|
1798
|
-
const items = [];
|
|
1799
|
-
if (index > 0) {
|
|
1800
|
-
items.push(
|
|
1801
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1802
|
-
"div",
|
|
1803
|
-
{
|
|
1804
|
-
style: {
|
|
1805
|
-
width: "1px",
|
|
1806
|
-
height: "20px",
|
|
1807
|
-
marginLeft: `${connectorMarginLeft}px`,
|
|
1808
|
-
marginTop: "-6px",
|
|
1809
|
-
marginBottom: "-6px",
|
|
1810
|
-
background: connectorColor,
|
|
1811
|
-
alignSelf: "flex-start",
|
|
1812
|
-
flexShrink: 0,
|
|
1813
|
-
position: "relative",
|
|
1814
|
-
zIndex: 1
|
|
1815
|
-
}
|
|
1816
|
-
},
|
|
1817
|
-
`conn-${index}`
|
|
1818
|
-
)
|
|
1819
|
-
);
|
|
1820
|
-
}
|
|
1821
|
-
items.push(
|
|
1822
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1823
|
-
"div",
|
|
1824
|
-
{
|
|
1825
|
-
style: {
|
|
1826
|
-
position: "relative",
|
|
1827
|
-
zIndex: 2,
|
|
1828
|
-
alignSelf: "flex-start",
|
|
1829
|
-
display: "flex",
|
|
1830
|
-
alignItems: "center",
|
|
1831
|
-
gap: "4px",
|
|
1832
|
-
height: "22px",
|
|
1833
|
-
padding: "4px",
|
|
1834
|
-
borderRadius: "8px",
|
|
1835
|
-
cursor: isClickable ? "pointer" : "default",
|
|
1836
|
-
background: rowBg,
|
|
1837
|
-
transition: "background 0.15s"
|
|
1838
|
-
},
|
|
1839
|
-
onMouseEnter: () => setHoveredIndex(index),
|
|
1840
|
-
onMouseLeave: () => setHoveredIndex(null),
|
|
1841
|
-
onClick: (e) => {
|
|
1842
|
-
e.stopPropagation();
|
|
1843
|
-
if (isClickable) onNavigateBack(index);
|
|
1844
|
-
},
|
|
1845
|
-
children: [
|
|
1846
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { style: { width: CIRCLE_SIZE, height: CIRCLE_SIZE, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, zIndex: 4 }, children: isCurrent ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(OrganicCircle, { size: CIRCLE_SIZE, fill: pastelFill, stroke: activeStroke, strokeWidth: 1.5, variant: index, isActive: true }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(OrganicCircle, { size: CIRCLE_SIZE, fill: pastelFill, stroke: visitedStroke, strokeWidth: 1, variant: index }) }),
|
|
1847
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1848
|
-
"span",
|
|
1849
|
-
{
|
|
1850
|
-
style: {
|
|
1851
|
-
fontFamily: "'Nunito', sans-serif",
|
|
1852
|
-
fontSize: isCurrent || isHovered && isClickable ? "11px" : "9px",
|
|
1853
|
-
fontWeight: isCurrent ? 600 : 400,
|
|
1854
|
-
color: isCurrent ? activeText : visitedText,
|
|
1855
|
-
lineHeight: 1,
|
|
1856
|
-
whiteSpace: "nowrap",
|
|
1857
|
-
transition: "color 0.15s, font-size 0.15s"
|
|
1858
|
-
},
|
|
1859
|
-
children: entry.label
|
|
1860
|
-
}
|
|
1861
|
-
)
|
|
1862
|
-
]
|
|
1863
|
-
},
|
|
1864
|
-
`${entry.layerId}-${index}`
|
|
1865
|
-
)
|
|
1866
|
-
);
|
|
1867
|
-
return items;
|
|
1868
|
-
}) })
|
|
1519
|
+
type: "button",
|
|
1520
|
+
disabled: i === current,
|
|
1521
|
+
"aria-current": i === current ? "location" : void 0,
|
|
1522
|
+
onClick: () => onNavigateBack(i),
|
|
1523
|
+
...i === 0 && iconOnly ? { "aria-label": e.label, title: e.label } : {},
|
|
1524
|
+
children: [
|
|
1525
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TrailDot, { level: e.level, paint: e.paint, current: i === current }),
|
|
1526
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: i === 0 ? iconOnly ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HomeIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1527
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(HomeIcon, {}),
|
|
1528
|
+
" ",
|
|
1529
|
+
e.label
|
|
1530
|
+
] }) : e.label })
|
|
1531
|
+
]
|
|
1869
1532
|
}
|
|
1870
|
-
);
|
|
1533
|
+
) }, `${e.layerId}-${i}`)) }) });
|
|
1871
1534
|
});
|
|
1872
|
-
var
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
]
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1535
|
+
var escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1536
|
+
function splitTerms(text, anchors) {
|
|
1537
|
+
const words = anchors.filter((a) => a.word).sort((a, b) => b.word.length - a.word.length);
|
|
1538
|
+
if (!words.length) return [text];
|
|
1539
|
+
const re = new RegExp(`(${words.map((a) => escapeRe(a.word)).join("|")})`, "giu");
|
|
1540
|
+
const byWord = new Map(words.map((a) => [a.word.toLowerCase(), a]));
|
|
1541
|
+
const isWordChar = (ch) => !!ch && /[\p{L}\p{N}]/u.test(ch);
|
|
1542
|
+
const out = [];
|
|
1543
|
+
let last = 0;
|
|
1544
|
+
for (const m of text.matchAll(re)) {
|
|
1545
|
+
const start = m.index, end = start + m[0].length;
|
|
1546
|
+
if (isWordChar(text[start - 1]) || isWordChar(text[end])) continue;
|
|
1547
|
+
if (start > last) out.push(text.slice(last, start));
|
|
1548
|
+
out.push({ ...byWord.get(m[0].toLowerCase()), word: m[0] });
|
|
1549
|
+
last = end;
|
|
1550
|
+
}
|
|
1551
|
+
if (last < text.length) out.push(text.slice(last));
|
|
1552
|
+
return out;
|
|
1553
|
+
}
|
|
1554
|
+
var TermCard = ({ anchor, inSheet = false }) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: `vi-anchor-card${inSheet ? " is-in-sheet" : ""}`, role: "note", children: [
|
|
1555
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vi-anchor-card__eyebrow", children: "Term" }),
|
|
1556
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vi-anchor-card__word", style: { display: "block" }, children: anchor.word }),
|
|
1557
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "vi-anchor-card__desc", children: anchor.description }),
|
|
1558
|
+
anchor.knowMoreUrl && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vi-anchor-card__acts", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("a", { className: "vi-fact__link", href: anchor.knowMoreUrl, target: "_blank", rel: "noopener noreferrer", children: [
|
|
1559
|
+
import_core12.TERM.learnMoreLabel,
|
|
1560
|
+
" ",
|
|
1561
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExternalIcon, {})
|
|
1562
|
+
] }) })
|
|
1888
1563
|
] });
|
|
1889
|
-
|
|
1890
|
-
const
|
|
1891
|
-
const
|
|
1892
|
-
const
|
|
1893
|
-
const
|
|
1894
|
-
const
|
|
1895
|
-
const
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1564
|
+
function TermAnchor({ anchor }) {
|
|
1565
|
+
const [open, setOpen] = (0, import_react10.useState)(false);
|
|
1566
|
+
const [shift, setShift] = (0, import_react10.useState)(0);
|
|
1567
|
+
const pinned = (0, import_react10.useRef)(false);
|
|
1568
|
+
const timer = (0, import_react10.useRef)(void 0);
|
|
1569
|
+
const wrap = (0, import_react10.useRef)(null), btn = (0, import_react10.useRef)(null), card = (0, import_react10.useRef)(null);
|
|
1570
|
+
const show = (pin) => {
|
|
1571
|
+
clearTimeout(timer.current);
|
|
1572
|
+
if (pin) pinned.current = true;
|
|
1573
|
+
setOpen(true);
|
|
1574
|
+
};
|
|
1575
|
+
const hide = () => {
|
|
1576
|
+
clearTimeout(timer.current);
|
|
1577
|
+
pinned.current = false;
|
|
1578
|
+
setOpen(false);
|
|
1579
|
+
};
|
|
1580
|
+
(0, import_react10.useEffect)(() => () => clearTimeout(timer.current), []);
|
|
1581
|
+
(0, import_react10.useLayoutEffect)(() => {
|
|
1582
|
+
if (!open || !card.current) {
|
|
1583
|
+
setShift(0);
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
const r = card.current.getBoundingClientRect(), vw = document.documentElement.clientWidth, m = import_core12.TERM.viewportMargin;
|
|
1587
|
+
setShift(r.right > vw - m ? vw - m - r.right : r.left < m ? m - r.left : 0);
|
|
1588
|
+
}, [open]);
|
|
1589
|
+
(0, import_react10.useEffect)(() => {
|
|
1590
|
+
if (!open) return;
|
|
1591
|
+
const onKey = (e) => {
|
|
1592
|
+
if (e.key === "Escape") {
|
|
1593
|
+
e.stopPropagation();
|
|
1594
|
+
hide();
|
|
1595
|
+
btn.current?.focus();
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
const onPress = (e) => {
|
|
1599
|
+
if (!wrap.current?.contains(e.target)) hide();
|
|
1600
|
+
};
|
|
1601
|
+
document.addEventListener("keydown", onKey, true);
|
|
1602
|
+
document.addEventListener("pointerdown", onPress, true);
|
|
1603
|
+
return () => {
|
|
1604
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1605
|
+
document.removeEventListener("pointerdown", onPress, true);
|
|
1606
|
+
};
|
|
1607
|
+
}, [open]);
|
|
1608
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1609
|
+
"span",
|
|
1900
1610
|
{
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1611
|
+
ref: wrap,
|
|
1612
|
+
className: `vi-anchor${open ? " is-open" : ""}`,
|
|
1613
|
+
"data-semantic-tooltip": "true",
|
|
1614
|
+
onPointerEnter: (e) => {
|
|
1615
|
+
if (!(0, import_core12.hoverOpensTerm)(e.pointerType)) return;
|
|
1616
|
+
clearTimeout(timer.current);
|
|
1617
|
+
timer.current = setTimeout(() => setOpen(true), import_core12.TERM.hoverOpen);
|
|
1618
|
+
},
|
|
1619
|
+
onPointerLeave: (e) => {
|
|
1620
|
+
if (!(0, import_core12.hoverOpensTerm)(e.pointerType) || pinned.current) return;
|
|
1621
|
+
clearTimeout(timer.current);
|
|
1622
|
+
timer.current = setTimeout(() => setOpen(false), import_core12.TERM.hoverClose);
|
|
1623
|
+
},
|
|
1624
|
+
onBlur: (e) => {
|
|
1625
|
+
if (!wrap.current?.contains(e.relatedTarget)) hide();
|
|
1910
1626
|
},
|
|
1911
|
-
role: "group",
|
|
1912
|
-
"aria-label": "Zoom controls",
|
|
1913
1627
|
children: [
|
|
1914
|
-
/* @__PURE__ */ (0,
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1915
1629
|
"button",
|
|
1916
1630
|
{
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
border: "none",
|
|
1929
|
-
background: "transparent",
|
|
1930
|
-
cursor: zoomLevel >= import_core12.ZOOM_MAX ? "not-allowed" : "pointer",
|
|
1931
|
-
color: zoomLevel >= import_core12.ZOOM_MAX ? muted : primary,
|
|
1932
|
-
transition: "background 0.15s"
|
|
1933
|
-
},
|
|
1934
|
-
onMouseEnter: (e) => {
|
|
1935
|
-
if (zoomLevel < import_core12.ZOOM_MAX) e.currentTarget.style.background = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.05)";
|
|
1936
|
-
},
|
|
1937
|
-
onMouseLeave: (e) => {
|
|
1938
|
-
e.currentTarget.style.background = "transparent";
|
|
1631
|
+
ref: btn,
|
|
1632
|
+
type: "button",
|
|
1633
|
+
className: "vi-term",
|
|
1634
|
+
"aria-expanded": open,
|
|
1635
|
+
onClick: () => {
|
|
1636
|
+
if (open && pinned.current) {
|
|
1637
|
+
hide();
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
show(true);
|
|
1641
|
+
setTimeout(() => card.current?.querySelector("a, button")?.focus(), 0);
|
|
1939
1642
|
},
|
|
1940
|
-
|
|
1941
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ZoomInIcon, {})
|
|
1643
|
+
children: anchor.word
|
|
1942
1644
|
}
|
|
1943
1645
|
),
|
|
1944
|
-
/* @__PURE__ */ (0,
|
|
1945
|
-
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1946
|
-
"button",
|
|
1947
|
-
{
|
|
1948
|
-
onClick: handleZoomOut,
|
|
1949
|
-
disabled: zoomLevel <= import_core12.ZOOM_MIN,
|
|
1950
|
-
style: {
|
|
1951
|
-
position: "absolute",
|
|
1952
|
-
top: 0,
|
|
1953
|
-
right: 0,
|
|
1954
|
-
width: "33px",
|
|
1955
|
-
height: "100%",
|
|
1956
|
-
display: "flex",
|
|
1957
|
-
alignItems: "center",
|
|
1958
|
-
justifyContent: "center",
|
|
1959
|
-
border: "none",
|
|
1960
|
-
background: "transparent",
|
|
1961
|
-
cursor: zoomLevel <= import_core12.ZOOM_MIN ? "not-allowed" : "pointer",
|
|
1962
|
-
color: zoomLevel <= import_core12.ZOOM_MIN ? muted : primary,
|
|
1963
|
-
transition: "background 0.15s"
|
|
1964
|
-
},
|
|
1965
|
-
onMouseEnter: (e) => {
|
|
1966
|
-
if (zoomLevel > import_core12.ZOOM_MIN) e.currentTarget.style.background = isDark ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.05)";
|
|
1967
|
-
},
|
|
1968
|
-
onMouseLeave: (e) => {
|
|
1969
|
-
e.currentTarget.style.background = "transparent";
|
|
1970
|
-
},
|
|
1971
|
-
"aria-label": "Zoom out",
|
|
1972
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ZoomOutIcon, {})
|
|
1973
|
-
}
|
|
1974
|
-
)
|
|
1646
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { ref: card, className: "vi-anchor__pop", style: shift ? { transform: `translateX(${shift}px)` } : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TermCard, { anchor }) })
|
|
1975
1647
|
]
|
|
1976
1648
|
}
|
|
1977
1649
|
);
|
|
1650
|
+
}
|
|
1651
|
+
var PeekCard = (0, import_react10.memo)(function PeekCard2({ node, anchors, onStepInside, onClose, renderContent }) {
|
|
1652
|
+
const parts = (0, import_react10.useMemo)(() => splitTerms(node.description ?? "", anchors), [node.description, anchors]);
|
|
1653
|
+
const { fill, ring } = paintVars(node);
|
|
1654
|
+
const style = { ["--vi-fact-fill"]: fill, ["--vi-fact-ring"]: ring };
|
|
1655
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vi-fact vi-peek", style, role: "dialog", "aria-label": node.title, "data-node-tooltip": "true", children: [
|
|
1656
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vi-fact__head", children: [
|
|
1657
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vi-fact__title", children: node.title }),
|
|
1658
|
+
onClose && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "vi-iconbtn vi-iconbtn--sm vi-fact__close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, {}) })
|
|
1659
|
+
] }),
|
|
1660
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vi-fact__body", children: renderContent ? renderContent(node.description ?? "") : parts.map((p, i) => typeof p === "string" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react10.default.Fragment, { children: p }, i) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TermAnchor, { anchor: p }, i)) }),
|
|
1661
|
+
onStepInside && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vi-fact__foot", children: [
|
|
1662
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", {}),
|
|
1663
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vi-fact__acts", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: "vi-fact__step", onClick: onStepInside, children: [
|
|
1664
|
+
"Step inside ",
|
|
1665
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronIcon, {})
|
|
1666
|
+
] }) })
|
|
1667
|
+
] })
|
|
1668
|
+
] });
|
|
1978
1669
|
});
|
|
1979
|
-
var
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
var
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
var SHADOW_PAD = 12;
|
|
1994
|
-
var SketchyBoxKonva = ({
|
|
1995
|
-
children,
|
|
1996
|
-
fill,
|
|
1997
|
-
stroke,
|
|
1998
|
-
backStroke,
|
|
1999
|
-
padding = "1rem 1.25rem"
|
|
2000
|
-
}) => {
|
|
2001
|
-
const wrapperRef = (0, import_react13.useRef)(null);
|
|
2002
|
-
const konvaContainerRef = (0, import_react13.useRef)(null);
|
|
2003
|
-
const stageRef = (0, import_react13.useRef)(null);
|
|
2004
|
-
(0, import_react13.useEffect)(() => {
|
|
2005
|
-
const wrapper = wrapperRef.current;
|
|
2006
|
-
const konvaEl = konvaContainerRef.current;
|
|
2007
|
-
if (!wrapper || !konvaEl) return;
|
|
2008
|
-
function draw(W, H) {
|
|
2009
|
-
if (!konvaEl || W <= 0 || H <= 0) return;
|
|
2010
|
-
if (stageRef.current) {
|
|
2011
|
-
stageRef.current.destroy();
|
|
2012
|
-
stageRef.current = null;
|
|
2013
|
-
}
|
|
2014
|
-
const stage = new import_konva.default.Stage({ container: konvaEl, width: W, height: H });
|
|
2015
|
-
const layer = new import_konva.default.Layer({ listening: false });
|
|
2016
|
-
const contentH = H - SHADOW_PAD;
|
|
2017
|
-
layer.add(new import_konva.default.Path({
|
|
2018
|
-
x: 0,
|
|
2019
|
-
y: BACK_OFFSET_Y,
|
|
2020
|
-
data: BACK_PATH,
|
|
2021
|
-
scaleX: W / BACK_W,
|
|
2022
|
-
scaleY: contentH / BACK_H,
|
|
2023
|
-
fill: "#ffffff",
|
|
2024
|
-
stroke: backStroke,
|
|
2025
|
-
strokeWidth: 2,
|
|
2026
|
-
strokeScaleEnabled: false,
|
|
2027
|
-
listening: false
|
|
2028
|
-
}));
|
|
2029
|
-
layer.add(new import_konva.default.Path({
|
|
2030
|
-
x: FRONT_OFFSET_X,
|
|
2031
|
-
y: 0,
|
|
2032
|
-
data: FRONT_PATH,
|
|
2033
|
-
scaleX: (W - FRONT_OFFSET_X) / FRONT_W,
|
|
2034
|
-
scaleY: contentH / FRONT_H,
|
|
2035
|
-
fill,
|
|
2036
|
-
stroke,
|
|
2037
|
-
strokeWidth: 3,
|
|
2038
|
-
strokeScaleEnabled: false,
|
|
2039
|
-
listening: false
|
|
2040
|
-
}));
|
|
2041
|
-
stage.add(layer);
|
|
2042
|
-
stageRef.current = stage;
|
|
2043
|
-
}
|
|
2044
|
-
draw(wrapper.offsetWidth, wrapper.offsetHeight);
|
|
2045
|
-
const ro = new ResizeObserver((entries) => {
|
|
2046
|
-
const el = entries[0].target;
|
|
2047
|
-
draw(el.offsetWidth, el.offsetHeight);
|
|
2048
|
-
});
|
|
2049
|
-
ro.observe(wrapper);
|
|
2050
|
-
return () => {
|
|
2051
|
-
ro.disconnect();
|
|
2052
|
-
stageRef.current?.destroy();
|
|
2053
|
-
stageRef.current = null;
|
|
2054
|
-
};
|
|
2055
|
-
}, [fill, stroke, backStroke]);
|
|
2056
|
-
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ref: wrapperRef, style: { position: "relative", paddingBottom: `${SHADOW_PAD}px` }, children: [
|
|
2057
|
-
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1670
|
+
var PeekSheet = (0, import_react10.memo)(function PeekSheet2({ node, anchors, onStepInside, onClose, renderContent, expanded, onToggleExpanded, height }) {
|
|
1671
|
+
const [term, setTerm] = (0, import_react10.useState)(null);
|
|
1672
|
+
const parts = (0, import_react10.useMemo)(() => splitTerms(node.description ?? "", anchors), [node.description, anchors]);
|
|
1673
|
+
const startY = (0, import_react10.useRef)(null);
|
|
1674
|
+
const swiped = (0, import_react10.useRef)(false);
|
|
1675
|
+
const { fill, ring } = paintVars(node);
|
|
1676
|
+
const style = {
|
|
1677
|
+
["--vi-fact-fill"]: fill,
|
|
1678
|
+
["--vi-fact-ring"]: ring,
|
|
1679
|
+
["--vi-sheet-h"]: `${height}px`,
|
|
1680
|
+
zIndex: "var(--z-card)"
|
|
1681
|
+
};
|
|
1682
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `vi-fact vi-fact--sheet vi-peek${expanded ? " is-expanded" : ""}`, style, role: "dialog", "aria-label": node.title, "data-node-tooltip": "true", children: [
|
|
1683
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
2058
1684
|
"div",
|
|
2059
1685
|
{
|
|
2060
|
-
|
|
2061
|
-
|
|
1686
|
+
className: "vi-fact__grip",
|
|
1687
|
+
onPointerDown: (e) => {
|
|
1688
|
+
startY.current = e.clientY;
|
|
1689
|
+
swiped.current = false;
|
|
1690
|
+
},
|
|
1691
|
+
onPointerUp: (e) => {
|
|
1692
|
+
if (startY.current === null) return;
|
|
1693
|
+
const dy = e.clientY - startY.current;
|
|
1694
|
+
startY.current = null;
|
|
1695
|
+
if (dy < -import_core12.SHEET.expandDrag) {
|
|
1696
|
+
swiped.current = true;
|
|
1697
|
+
if (!expanded) onToggleExpanded();
|
|
1698
|
+
} else if (dy > import_core12.SHEET.dismissDrag) {
|
|
1699
|
+
swiped.current = true;
|
|
1700
|
+
if (expanded) onToggleExpanded();
|
|
1701
|
+
else onClose();
|
|
1702
|
+
}
|
|
1703
|
+
},
|
|
1704
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "vi-fact__handle", "aria-label": expanded ? "Collapse" : "Expand", "aria-expanded": expanded, onClick: () => {
|
|
1705
|
+
if (!swiped.current) onToggleExpanded();
|
|
1706
|
+
swiped.current = false;
|
|
1707
|
+
} })
|
|
2062
1708
|
}
|
|
2063
1709
|
),
|
|
2064
|
-
/* @__PURE__ */ (0,
|
|
1710
|
+
term ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1711
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: "vi-fact__back", onClick: () => setTerm(null), children: [
|
|
1712
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronLeftIcon, {}),
|
|
1713
|
+
" Back"
|
|
1714
|
+
] }),
|
|
1715
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TermCard, { anchor: term, inSheet: true })
|
|
1716
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
1717
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vi-fact__head", children: [
|
|
1718
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vi-fact__title", children: node.title }),
|
|
1719
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "vi-iconbtn vi-iconbtn--md vi-fact__close", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CloseIcon, {}) })
|
|
1720
|
+
] }),
|
|
1721
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vi-fact__body", children: renderContent ? renderContent(node.description ?? "") : parts.map((p, i) => typeof p === "string" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react10.default.Fragment, { children: p }, i) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vi-anchor", "data-semantic-tooltip": "true", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: "vi-term", onClick: () => setTerm(p), children: p.word }) }, i)) }),
|
|
1722
|
+
onStepInside && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vi-fact__foot", children: [
|
|
1723
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", {}),
|
|
1724
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "vi-fact__acts", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("button", { type: "button", className: "vi-fact__step", onClick: onStepInside, children: [
|
|
1725
|
+
"Step inside ",
|
|
1726
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ChevronIcon, {})
|
|
1727
|
+
] }) })
|
|
1728
|
+
] })
|
|
1729
|
+
] })
|
|
2065
1730
|
] });
|
|
2066
|
-
};
|
|
2067
|
-
var SketchyBoxKonva_default = SketchyBoxKonva;
|
|
1731
|
+
});
|
|
2068
1732
|
|
|
2069
|
-
// src/
|
|
2070
|
-
var
|
|
2071
|
-
function
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
1733
|
+
// src/components/NavigationStack.tsx
|
|
1734
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1735
|
+
var NavigationStack = (0, import_react11.memo)(function NavigationStack2({ stack, onNavigateBack, top, left }) {
|
|
1736
|
+
const entries = (0, import_react11.useMemo)(
|
|
1737
|
+
() => stack.map((e) => ({ layerId: e.layerId, label: e.label, level: e.layer.level, paint: e.paint ?? {} })),
|
|
1738
|
+
[stack]
|
|
1739
|
+
);
|
|
1740
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "vi-map__trail", style: top || left ? { top, left } : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(DepthTrail, { stack: entries, onNavigateBack }) });
|
|
1741
|
+
});
|
|
1742
|
+
var NavigationStack_default = NavigationStack;
|
|
1743
|
+
|
|
1744
|
+
// src/components/A11yLayer.tsx
|
|
1745
|
+
var import_react12 = require("react");
|
|
1746
|
+
var import_core13 = require("@visualli/core");
|
|
1747
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1748
|
+
var MAX_MIRRORED = 150;
|
|
1749
|
+
var A11yLayer = (0, import_react12.memo)(function A11yLayer2({ nodes, label, hasChildLayer, onActivate, onFocusNode, announcement }) {
|
|
1750
|
+
const cx = useViewportStore((s) => s.centerX);
|
|
1751
|
+
const cy = useViewportStore((s) => s.centerY);
|
|
1752
|
+
const zoom = useViewportStore((s) => s.zoomLevel);
|
|
1753
|
+
const w = useViewportStore((s) => s.canvasWidth);
|
|
1754
|
+
const h = useViewportStore((s) => s.canvasHeight);
|
|
1755
|
+
const [view, setView] = (0, import_react12.useState)({ cx, cy, zoom, w, h });
|
|
1756
|
+
(0, import_react12.useEffect)(() => {
|
|
1757
|
+
const t = setTimeout(() => setView({ cx, cy, zoom, w, h }), 120);
|
|
1758
|
+
return () => clearTimeout(t);
|
|
1759
|
+
}, [cx, cy, zoom, w, h]);
|
|
1760
|
+
const items = (0, import_react12.useMemo)(() => {
|
|
1761
|
+
const halfW = view.w / 2 / view.zoom, halfH = view.h / 2 / view.zoom;
|
|
1762
|
+
const bounds = { minX: view.cx - halfW, maxX: view.cx + halfW, minY: view.cy - halfH, maxY: view.cy + halfH };
|
|
1763
|
+
const visible = nodes.filter((n) => (0, import_core13.intersectsViewport)(n, bounds));
|
|
1764
|
+
const picked = visible.length > MAX_MIRRORED ? visible.map((n) => ({ n, d: (n.x - view.cx) ** 2 + (n.y - view.cy) ** 2 })).sort((a, b) => a.d - b.d).slice(0, MAX_MIRRORED).map((e) => e.n) : visible;
|
|
1765
|
+
const row = (n) => Math.round((0, import_core13.nodeBounds)(n).minY / 150);
|
|
1766
|
+
return [...picked].sort((a, b) => row(a) - row(b) || a.x - b.x);
|
|
1767
|
+
}, [nodes, view]);
|
|
1768
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
|
|
1769
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "vi-sr", role: "list", "aria-label": label, children: items.map((n) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { role: "listitem", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1770
|
+
"button",
|
|
1771
|
+
{
|
|
1772
|
+
type: "button",
|
|
1773
|
+
"data-node-id": n.id,
|
|
1774
|
+
"aria-label": `${n.title}. Depth ${n.level + 1}.${hasChildLayer(n.id) ? " Has more inside; press Enter to step inside." : ""}`,
|
|
1775
|
+
onFocus: () => onFocusNode(n.id),
|
|
1776
|
+
onBlur: () => onFocusNode(null),
|
|
1777
|
+
onClick: () => onActivate(n)
|
|
1778
|
+
}
|
|
1779
|
+
) }, n.id)) }),
|
|
1780
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "vi-sr", role: "status", "aria-live": "polite", children: announcement })
|
|
1781
|
+
] });
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
// src/design/useDesign.ts
|
|
1785
|
+
var import_react13 = require("react");
|
|
1786
|
+
function useMedia(query) {
|
|
1787
|
+
const subscribe = (cb) => {
|
|
1788
|
+
if (typeof window === "undefined" || !window.matchMedia) return () => {
|
|
1789
|
+
};
|
|
1790
|
+
const mq = window.matchMedia(query);
|
|
1791
|
+
mq.addEventListener("change", cb);
|
|
1792
|
+
return () => mq.removeEventListener("change", cb);
|
|
1793
|
+
};
|
|
1794
|
+
const get = () => typeof window !== "undefined" && !!window.matchMedia && window.matchMedia(query).matches;
|
|
1795
|
+
return (0, import_react13.useSyncExternalStore)(subscribe, get, () => false);
|
|
1796
|
+
}
|
|
1797
|
+
function useReaderEnv() {
|
|
1798
|
+
return {
|
|
1799
|
+
prefersDark: useMedia("(prefers-color-scheme: dark)"),
|
|
1800
|
+
forcedColors: useMedia("(forced-colors: active)"),
|
|
1801
|
+
prefersReducedMotion: useMedia("(prefers-reduced-motion: reduce)")
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function useDesign(props) {
|
|
1805
|
+
const env = useReaderEnv();
|
|
1806
|
+
const { theme, isDark, respectForcedColors } = props;
|
|
1807
|
+
const { readableType, largerText, reducedMotion } = props.comfort ?? {};
|
|
1808
|
+
return (0, import_react13.useMemo)(
|
|
1809
|
+
() => resolveDesign({ theme, isDark, respectForcedColors, comfort: { readableType, largerText, reducedMotion } }, env),
|
|
1810
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1811
|
+
[theme, isDark, respectForcedColors, readableType, largerText, reducedMotion, env.prefersDark, env.forcedColors, env.prefersReducedMotion]
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/design/measure.ts
|
|
1816
|
+
var import_core14 = require("@visualli/core");
|
|
1817
|
+
var ctx;
|
|
1818
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
1819
|
+
var MAX_CACHED = 8192;
|
|
1820
|
+
function ideaMeasure(d) {
|
|
1821
|
+
if (ctx === void 0) ctx = typeof document !== "undefined" ? document.createElement("canvas").getContext("2d") : null;
|
|
1822
|
+
const c = ctx;
|
|
1823
|
+
if (!c) return import_core14.approximateMeasure;
|
|
1824
|
+
const weight = import_core14.TYPE_STYLES["node-label"].weight;
|
|
1825
|
+
const tracking = d.comfort.readableType ? 0.02 : 0;
|
|
1826
|
+
return (text, px) => {
|
|
1827
|
+
const font = `${weight} ${px}px ${d.fontHand}`;
|
|
1828
|
+
const key2 = `${fontsEpoch()}|${font}|${text}`;
|
|
1829
|
+
let w = cache2.get(key2);
|
|
1830
|
+
if (w === void 0) {
|
|
1831
|
+
c.font = font;
|
|
1832
|
+
w = c.measureText(text).width + text.length * px * tracking;
|
|
1833
|
+
if (cache2.size >= MAX_CACHED) cache2.clear();
|
|
1834
|
+
cache2.set(key2, w);
|
|
1835
|
+
}
|
|
1836
|
+
return w;
|
|
1837
|
+
};
|
|
2086
1838
|
}
|
|
1839
|
+
|
|
1840
|
+
// src/VisualliCanvas.tsx
|
|
1841
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
2087
1842
|
function resolveDocument(props, fileText) {
|
|
2088
1843
|
if (props.preParsedVisualli) return props.preParsedVisualli;
|
|
2089
1844
|
const rawContent = fileText ?? props.visualliString;
|
|
2090
1845
|
if (rawContent) {
|
|
2091
1846
|
try {
|
|
2092
|
-
return (0,
|
|
1847
|
+
return (0, import_core15.parseVisualliFile)(rawContent);
|
|
2093
1848
|
} catch {
|
|
2094
1849
|
return null;
|
|
2095
1850
|
}
|
|
@@ -2108,6 +1863,13 @@ function layerLabel(doc, layer, layerId) {
|
|
|
2108
1863
|
}
|
|
2109
1864
|
return layer.description ?? layerId;
|
|
2110
1865
|
}
|
|
1866
|
+
function layerPaint(doc, layer) {
|
|
1867
|
+
const parentLayer = layer.parentLayerId ? doc.layers.get(layer.parentLayerId) : null;
|
|
1868
|
+
const index = parentLayer ? parentLayer.nodes.findIndex((n) => n.id === layer.parentNodeId) : -1;
|
|
1869
|
+
if (!parentLayer || index < 0) return { topic: import_core15.TRAIL.rootTopic };
|
|
1870
|
+
const { topic, custom } = (0, import_core15.ideaColor)(parentLayer.nodes[index].data.color, index);
|
|
1871
|
+
return topic ? { topic } : { custom };
|
|
1872
|
+
}
|
|
2111
1873
|
function getRootLayerId(doc) {
|
|
2112
1874
|
for (const [id, layer] of doc.layers) {
|
|
2113
1875
|
if (!layer.parentLayerId) return id;
|
|
@@ -2115,7 +1877,22 @@ function getRootLayerId(doc) {
|
|
|
2115
1877
|
return doc.layers.keys().next().value ?? null;
|
|
2116
1878
|
}
|
|
2117
1879
|
function VisualliCanvas(props) {
|
|
2118
|
-
const {
|
|
1880
|
+
const { chromaticImmersion = false, onNodeClick, onLayerChange, onNodeHover, renderOverlay, renderNodeContent, navigationStackTop, navigationStackLeft, className = "", style } = props;
|
|
1881
|
+
const onNodeHoverRef = (0, import_react14.useRef)(onNodeHover);
|
|
1882
|
+
onNodeHoverRef.current = onNodeHover;
|
|
1883
|
+
const design = useDesign({ theme: props.theme ?? (props.isDark === void 0 ? "light" : void 0), isDark: props.isDark, comfort: props.comfort, respectForcedColors: props.respectForcedColors });
|
|
1884
|
+
const isDark = design.isDark;
|
|
1885
|
+
const [fontSheet] = (0, import_react14.useState)(() => ensureDesignSystemStyles({ fontBaseUrl: props.fontBaseUrl, loadWebFonts: props.loadWebFonts }));
|
|
1886
|
+
const [fontsReady, setFontsReady] = (0, import_react14.useState)(false);
|
|
1887
|
+
(0, import_react14.useEffect)(() => {
|
|
1888
|
+
let live = true;
|
|
1889
|
+
loadCanvasFonts(fontSheet).then(() => {
|
|
1890
|
+
if (live) setFontsReady(true);
|
|
1891
|
+
});
|
|
1892
|
+
return () => {
|
|
1893
|
+
live = false;
|
|
1894
|
+
};
|
|
1895
|
+
}, [fontSheet]);
|
|
2119
1896
|
const [fileText, setFileText] = (0, import_react14.useState)(void 0);
|
|
2120
1897
|
(0, import_react14.useEffect)(() => {
|
|
2121
1898
|
if (!props.visualliFile) {
|
|
@@ -2147,6 +1924,7 @@ function VisualliCanvas(props) {
|
|
|
2147
1924
|
const [navStack, setNavStack] = (0, import_react14.useState)([]);
|
|
2148
1925
|
const [currentLayerId, setCurrentLayerId] = (0, import_react14.useState)(null);
|
|
2149
1926
|
const parentViewports = (0, import_react14.useRef)([]);
|
|
1927
|
+
const stepTargetRef = (0, import_react14.useRef)(null);
|
|
2150
1928
|
const [isTransitioning, setIsTransitioning] = (0, import_react14.useState)(false);
|
|
2151
1929
|
const isTransitioningRef = (0, import_react14.useRef)(false);
|
|
2152
1930
|
(0, import_react14.useEffect)(() => {
|
|
@@ -2155,21 +1933,22 @@ function VisualliCanvas(props) {
|
|
|
2155
1933
|
if (!rootId) return;
|
|
2156
1934
|
const rootLayer = doc.layers.get(rootId);
|
|
2157
1935
|
setCurrentLayerId(rootId);
|
|
2158
|
-
setNavStack([{ layerId: rootId, layer: rootLayer, label: "Home" }]);
|
|
1936
|
+
setNavStack([{ layerId: rootId, layer: rootLayer, label: doc.meta?.title || "Home", paint: { topic: import_core15.TRAIL.rootTopic } }]);
|
|
2159
1937
|
parentViewports.current = [];
|
|
2160
1938
|
}, [doc]);
|
|
1939
|
+
const sizing = (0, import_react14.useMemo)(() => ({ measure: ideaMeasure(design), labelScale: design.labelScale }), [design.fontHand, design.labelScale, design.comfort.readableType, fontsReady]);
|
|
2161
1940
|
const { flatNodes, connections, containers } = (0, import_react14.useMemo)(() => {
|
|
2162
1941
|
if (!doc || !currentLayerId) return { flatNodes: [], connections: [], containers: [] };
|
|
2163
1942
|
try {
|
|
2164
1943
|
return {
|
|
2165
|
-
flatNodes: (0,
|
|
2166
|
-
connections: (0,
|
|
2167
|
-
containers: (0,
|
|
1944
|
+
flatNodes: (0, import_core15.getNodesForLayer)(doc, currentLayerId, sizing),
|
|
1945
|
+
connections: (0, import_core3.getConnectionsForLayer)(doc, currentLayerId),
|
|
1946
|
+
containers: (0, import_core3.getContainersForLayer)(doc, currentLayerId)
|
|
2168
1947
|
};
|
|
2169
1948
|
} catch {
|
|
2170
1949
|
return { flatNodes: [], connections: [], containers: [] };
|
|
2171
1950
|
}
|
|
2172
|
-
}, [doc, currentLayerId]);
|
|
1951
|
+
}, [doc, currentLayerId, sizing]);
|
|
2173
1952
|
const viewport = useViewportStore((s) => ({
|
|
2174
1953
|
centerX: s.centerX,
|
|
2175
1954
|
centerY: s.centerY,
|
|
@@ -2189,52 +1968,48 @@ function VisualliCanvas(props) {
|
|
|
2189
1968
|
setNodes(map);
|
|
2190
1969
|
clearSel();
|
|
2191
1970
|
}, [flatNodes, setNodes, clearSel]);
|
|
1971
|
+
const clock = (0, import_react14.useMemo)(() => new RevealClock(), []);
|
|
1972
|
+
const instantReveal = design.comfort.reducedMotion || props.reveal === "instant";
|
|
1973
|
+
(0, import_react14.useEffect)(() => {
|
|
1974
|
+
if (!fontsReady || flatNodes.length === 0) return;
|
|
1975
|
+
clock.start(flatNodes, connections.length, instantReveal);
|
|
1976
|
+
const r1 = requestAnimationFrame(() => {
|
|
1977
|
+
r2 = requestAnimationFrame(arrive);
|
|
1978
|
+
});
|
|
1979
|
+
let r2 = 0;
|
|
1980
|
+
return () => {
|
|
1981
|
+
cancelAnimationFrame(r1);
|
|
1982
|
+
cancelAnimationFrame(r2);
|
|
1983
|
+
};
|
|
1984
|
+
}, [flatNodes, fontsReady]);
|
|
1985
|
+
(0, import_react14.useEffect)(() => clock.subscribe((running) => {
|
|
1986
|
+
const el = containerRef.current;
|
|
1987
|
+
if (el) el.dataset.viReveal = running ? "running" : "idle";
|
|
1988
|
+
}), [clock]);
|
|
1989
|
+
(0, import_react14.useEffect)(() => () => clock.stop(), [clock]);
|
|
2192
1990
|
const containerRef = (0, import_react14.useRef)(null);
|
|
2193
1991
|
const stageRef = (0, import_react14.useRef)(null);
|
|
2194
1992
|
const canvasWrapperRef = (0, import_react14.useRef)(null);
|
|
2195
|
-
const baselineTransformRef = (0, import_react14.useRef)(null);
|
|
2196
|
-
const konvaContentDivRef = (0, import_react14.useRef)(null);
|
|
2197
|
-
const lastAnimViewportRef = (0, import_react14.useRef)(null);
|
|
2198
|
-
const zoomOutTargetRef = (0, import_react14.useRef)(null);
|
|
2199
1993
|
const canvasSizeRef = (0, import_react14.useRef)({ width: 0, height: 0 });
|
|
2200
1994
|
const dragMoveRafRef = (0, import_react14.useRef)(null);
|
|
2201
1995
|
const dragMoveLatestRef = (0, import_react14.useRef)(null);
|
|
2202
1996
|
const dragStartPosRef = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
|
|
2203
|
-
const
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
const parentLayer = currentLayer.parentLayerId ? doc.layers.get(currentLayer.parentLayerId) : null;
|
|
2210
|
-
if (parentLayer) {
|
|
2211
|
-
const parentNode = parentLayer.nodes.find((n) => n.id === currentLayer.parentNodeId);
|
|
2212
|
-
if (parentNode) {
|
|
2213
|
-
const nodeColor = parentNode.data.color || baseBgColor;
|
|
2214
|
-
return hexToRgba2(nodeColor, 0.15);
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
return baseBgColor;
|
|
2218
|
-
}, [chromaticImmersion, doc, currentLayerId, baseBgColor]);
|
|
2219
|
-
const bgColor = chromaticImmersion ? chromaticBgColor : baseBgColor;
|
|
1997
|
+
const immersionFill = (0, import_react14.useMemo)(() => {
|
|
1998
|
+
if (!chromaticImmersion || !doc || !currentLayerId) return null;
|
|
1999
|
+
const layer = doc.layers.get(currentLayerId);
|
|
2000
|
+
if (!layer || layer.level === 0 || !layer.parentNodeId) return null;
|
|
2001
|
+
return paintVars(layerPaint(doc, layer)).fill;
|
|
2002
|
+
}, [chromaticImmersion, doc, currentLayerId]);
|
|
2220
2003
|
const spatialIndexRef = (0, import_react14.useRef)(null);
|
|
2221
2004
|
(0, import_react14.useEffect)(() => {
|
|
2222
2005
|
if (flatNodes.length === 0) {
|
|
2223
2006
|
spatialIndexRef.current?.clear();
|
|
2224
2007
|
return;
|
|
2225
2008
|
}
|
|
2226
|
-
const idx = new
|
|
2009
|
+
const idx = new import_core15.RBushSpatialIndex();
|
|
2227
2010
|
idx.bulkLoad(flatNodes.map((n) => {
|
|
2228
|
-
const
|
|
2229
|
-
return {
|
|
2230
|
-
nodeId: n.id,
|
|
2231
|
-
bounds: {
|
|
2232
|
-
minX: n.x - n.width / 2,
|
|
2233
|
-
minY: n.y - blobHalfH,
|
|
2234
|
-
maxX: n.x + n.width / 2,
|
|
2235
|
-
maxY: n.y + blobHalfH
|
|
2236
|
-
}
|
|
2237
|
-
};
|
|
2011
|
+
const { rx, ry } = (0, import_core15.nodeRadii)(n);
|
|
2012
|
+
return { nodeId: n.id, bounds: { minX: n.x - rx, minY: n.y - ry, maxX: n.x + rx, maxY: n.y + ry } };
|
|
2238
2013
|
}));
|
|
2239
2014
|
spatialIndexRef.current = idx;
|
|
2240
2015
|
return () => {
|
|
@@ -2246,6 +2021,7 @@ function VisualliCanvas(props) {
|
|
|
2246
2021
|
handleMouseDown: rendererHandleMouseDown,
|
|
2247
2022
|
handleMouseMove: rendererHandleMouseMove,
|
|
2248
2023
|
handleMouseUp: rendererHandleMouseUp,
|
|
2024
|
+
cancelPan: rendererCancelPan,
|
|
2249
2025
|
canvasWidth,
|
|
2250
2026
|
canvasHeight
|
|
2251
2027
|
} = useKonvaRenderer({ containerRef, stageRef });
|
|
@@ -2285,20 +2061,20 @@ function VisualliCanvas(props) {
|
|
|
2285
2061
|
if (node) {
|
|
2286
2062
|
const oldPos = dragStartPosRef.current.get(nodeId);
|
|
2287
2063
|
if (oldPos) {
|
|
2288
|
-
const
|
|
2064
|
+
const { rx: rx2, ry: ry2 } = (0, import_core15.nodeRadii)(node);
|
|
2289
2065
|
spatialIndexRef.current.remove(nodeId, {
|
|
2290
|
-
minX: oldPos.x -
|
|
2291
|
-
minY: oldPos.y -
|
|
2292
|
-
maxX: oldPos.x +
|
|
2293
|
-
maxY: oldPos.y +
|
|
2066
|
+
minX: oldPos.x - rx2,
|
|
2067
|
+
minY: oldPos.y - ry2,
|
|
2068
|
+
maxX: oldPos.x + rx2,
|
|
2069
|
+
maxY: oldPos.y + ry2
|
|
2294
2070
|
});
|
|
2295
2071
|
}
|
|
2296
|
-
const
|
|
2072
|
+
const { rx, ry } = (0, import_core15.nodeRadii)(node);
|
|
2297
2073
|
spatialIndexRef.current.insert(nodeId, {
|
|
2298
|
-
minX: x -
|
|
2299
|
-
minY: y -
|
|
2300
|
-
maxX: x +
|
|
2301
|
-
maxY: y +
|
|
2074
|
+
minX: x - rx,
|
|
2075
|
+
minY: y - ry,
|
|
2076
|
+
maxX: x + rx,
|
|
2077
|
+
maxY: y + ry
|
|
2302
2078
|
});
|
|
2303
2079
|
}
|
|
2304
2080
|
}
|
|
@@ -2311,12 +2087,11 @@ function VisualliCanvas(props) {
|
|
|
2311
2087
|
if (isTransitioningRef.current) return;
|
|
2312
2088
|
const cw = canvasSizeRef.current.width || containerRef.current?.getBoundingClientRect().width || 800;
|
|
2313
2089
|
const ch = canvasSizeRef.current.height || containerRef.current?.getBoundingClientRect().height || 600;
|
|
2314
|
-
const { centerX, centerY } = (0,
|
|
2315
|
-
|
|
2316
|
-
const zoomLevel = isRootLayer ? 1 : (0, import_core5.calculateFitView)(flatNodes, cw, ch).zoomLevel;
|
|
2090
|
+
const { centerX, centerY, zoomLevel } = (0, import_core3.calculateFitView)(flatNodes, cw, ch, containers);
|
|
2091
|
+
useViewportStore.getState().setFit(zoomLevel, centerX, centerY);
|
|
2317
2092
|
setCenter(centerX, centerY);
|
|
2318
2093
|
setZoom(zoomLevel);
|
|
2319
|
-
}, [flatNodes,
|
|
2094
|
+
}, [flatNodes, containers, setCenter, setZoom]);
|
|
2320
2095
|
fitToScreenRef.current = fitToScreen;
|
|
2321
2096
|
(0, import_react14.useEffect)(() => {
|
|
2322
2097
|
fitToScreen();
|
|
@@ -2328,14 +2103,22 @@ function VisualliCanvas(props) {
|
|
|
2328
2103
|
fitToScreen();
|
|
2329
2104
|
}, [canvasWidth, fitToScreen]);
|
|
2330
2105
|
const navBackTargetRef = (0, import_react14.useRef)(-1);
|
|
2331
|
-
const {
|
|
2106
|
+
const { stepInside: diveInto, backOut, arrive, isTransitioning: isAnimating } = useLayerChoreography({
|
|
2332
2107
|
stageRef,
|
|
2333
2108
|
canvasWrapperRef,
|
|
2109
|
+
reducedMotion: design.comfort.reducedMotion || props.reveal === "instant",
|
|
2334
2110
|
onSwapLayer: (childLayerId) => {
|
|
2335
2111
|
const childLayer = doc?.layers.get(childLayerId);
|
|
2336
2112
|
if (!childLayer || !doc) return;
|
|
2337
2113
|
setCurrentLayerId(childLayerId);
|
|
2338
|
-
|
|
2114
|
+
const target = stepTargetRef.current;
|
|
2115
|
+
stepTargetRef.current = null;
|
|
2116
|
+
setNavStack((prev) => [...prev, {
|
|
2117
|
+
layerId: childLayerId,
|
|
2118
|
+
layer: childLayer,
|
|
2119
|
+
label: target?.label || layerLabel(doc, childLayer, childLayerId),
|
|
2120
|
+
paint: target?.paint ?? layerPaint(doc, childLayer)
|
|
2121
|
+
}]);
|
|
2339
2122
|
onLayerChange?.(childLayerId, childLayer);
|
|
2340
2123
|
},
|
|
2341
2124
|
onSwapBack: () => {
|
|
@@ -2355,96 +2138,6 @@ function VisualliCanvas(props) {
|
|
|
2355
2138
|
requestAnimationFrame(() => {
|
|
2356
2139
|
fitToScreenRef.current();
|
|
2357
2140
|
});
|
|
2358
|
-
},
|
|
2359
|
-
// GPU CSS-transform callbacks (matching visualli.ai exactly):
|
|
2360
|
-
onViewportFrame: (vp) => {
|
|
2361
|
-
lastAnimViewportRef.current = vp;
|
|
2362
|
-
const baseline = baselineTransformRef.current;
|
|
2363
|
-
const contentDiv = konvaContentDivRef.current;
|
|
2364
|
-
const { width, height } = canvasSizeRef.current;
|
|
2365
|
-
if (baseline && contentDiv) {
|
|
2366
|
-
const targetX = width / 2 - vp.centerX * vp.zoomLevel;
|
|
2367
|
-
const targetY = height / 2 - vp.centerY * vp.zoomLevel;
|
|
2368
|
-
const ratio = vp.zoomLevel / baseline.scaleX;
|
|
2369
|
-
const tx = targetX - baseline.x * ratio;
|
|
2370
|
-
const ty = targetY - baseline.y * ratio;
|
|
2371
|
-
contentDiv.style.transform = `translate(${tx}px, ${ty}px) scale(${ratio})`;
|
|
2372
|
-
} else {
|
|
2373
|
-
const stage = stageRef.current;
|
|
2374
|
-
if (stage) {
|
|
2375
|
-
stage.x(width / 2 - vp.centerX * vp.zoomLevel);
|
|
2376
|
-
stage.y(height / 2 - vp.centerY * vp.zoomLevel);
|
|
2377
|
-
stage.scaleX(vp.zoomLevel);
|
|
2378
|
-
stage.scaleY(vp.zoomLevel);
|
|
2379
|
-
stage.batchDraw();
|
|
2380
|
-
}
|
|
2381
|
-
}
|
|
2382
|
-
},
|
|
2383
|
-
onViewportFinal: (vp) => {
|
|
2384
|
-
if (konvaContentDivRef.current) konvaContentDivRef.current.style.transform = "";
|
|
2385
|
-
const stage = stageRef.current;
|
|
2386
|
-
if (stage) {
|
|
2387
|
-
const { width, height } = canvasSizeRef.current;
|
|
2388
|
-
stage.x(width / 2 - vp.centerX * vp.zoomLevel);
|
|
2389
|
-
stage.y(height / 2 - vp.centerY * vp.zoomLevel);
|
|
2390
|
-
stage.scaleX(vp.zoomLevel);
|
|
2391
|
-
stage.scaleY(vp.zoomLevel);
|
|
2392
|
-
stage.getLayers().forEach((l) => l.drawScene());
|
|
2393
|
-
}
|
|
2394
|
-
setCenter(vp.centerX, vp.centerY);
|
|
2395
|
-
setZoom(vp.zoomLevel);
|
|
2396
|
-
},
|
|
2397
|
-
onPhaseChange: (phase) => {
|
|
2398
|
-
const stage = stageRef.current;
|
|
2399
|
-
const contentDiv = konvaContentDivRef.current;
|
|
2400
|
-
if (stage && contentDiv && (phase === "swap" || phase === "zoom-back")) {
|
|
2401
|
-
contentDiv.style.transform = "";
|
|
2402
|
-
const vp = phase === "zoom-back" ? zoomOutTargetRef.current : lastAnimViewportRef.current;
|
|
2403
|
-
if (vp) {
|
|
2404
|
-
const { width, height } = canvasSizeRef.current;
|
|
2405
|
-
const nx = width / 2 - vp.centerX * vp.zoomLevel;
|
|
2406
|
-
const ny = height / 2 - vp.centerY * vp.zoomLevel;
|
|
2407
|
-
stage.x(nx);
|
|
2408
|
-
stage.y(ny);
|
|
2409
|
-
stage.scaleX(vp.zoomLevel);
|
|
2410
|
-
stage.scaleY(vp.zoomLevel);
|
|
2411
|
-
stage.getLayers().forEach((l) => l.drawScene());
|
|
2412
|
-
baselineTransformRef.current = { x: nx, y: ny, scaleX: vp.zoomLevel, scaleY: vp.zoomLevel };
|
|
2413
|
-
}
|
|
2414
|
-
}
|
|
2415
|
-
},
|
|
2416
|
-
onTransitionLifecycle: (phase) => {
|
|
2417
|
-
const stage = stageRef.current;
|
|
2418
|
-
if (phase === "start") {
|
|
2419
|
-
if (canvasWrapperRef.current) canvasWrapperRef.current.style.willChange = "opacity, transform";
|
|
2420
|
-
if (stage) {
|
|
2421
|
-
baselineTransformRef.current = { x: stage.x(), y: stage.y(), scaleX: stage.scaleX(), scaleY: stage.scaleY() };
|
|
2422
|
-
const container = stage.container();
|
|
2423
|
-
const cd = container?.querySelector(".konvajs-content");
|
|
2424
|
-
if (cd) {
|
|
2425
|
-
konvaContentDivRef.current = cd;
|
|
2426
|
-
cd.style.transformOrigin = "0 0";
|
|
2427
|
-
cd.style.willChange = "transform";
|
|
2428
|
-
}
|
|
2429
|
-
stage.getLayers().forEach((l) => {
|
|
2430
|
-
l.listening(false);
|
|
2431
|
-
});
|
|
2432
|
-
}
|
|
2433
|
-
} else {
|
|
2434
|
-
if (canvasWrapperRef.current) canvasWrapperRef.current.style.willChange = "auto";
|
|
2435
|
-
if (konvaContentDivRef.current) {
|
|
2436
|
-
konvaContentDivRef.current.style.willChange = "auto";
|
|
2437
|
-
konvaContentDivRef.current = null;
|
|
2438
|
-
}
|
|
2439
|
-
baselineTransformRef.current = null;
|
|
2440
|
-
zoomOutTargetRef.current = null;
|
|
2441
|
-
if (stage) {
|
|
2442
|
-
stage.getLayers().forEach((l) => {
|
|
2443
|
-
l.listening(true);
|
|
2444
|
-
});
|
|
2445
|
-
stage.batchDraw();
|
|
2446
|
-
}
|
|
2447
|
-
}
|
|
2448
2141
|
}
|
|
2449
2142
|
});
|
|
2450
2143
|
const handleNavigate = (0, import_react14.useCallback)((nodeId) => {
|
|
@@ -2454,7 +2147,7 @@ function VisualliCanvas(props) {
|
|
|
2454
2147
|
if (isTransitioningRef.current || isAnimating()) {
|
|
2455
2148
|
return;
|
|
2456
2149
|
}
|
|
2457
|
-
const childLayer = (0,
|
|
2150
|
+
const childLayer = (0, import_core3.getChildLayerForNode)(doc, nodeId, currentLayerId);
|
|
2458
2151
|
if (!childLayer) {
|
|
2459
2152
|
return;
|
|
2460
2153
|
}
|
|
@@ -2464,7 +2157,7 @@ function VisualliCanvas(props) {
|
|
|
2464
2157
|
}
|
|
2465
2158
|
const childNodes = (() => {
|
|
2466
2159
|
try {
|
|
2467
|
-
return (0,
|
|
2160
|
+
return (0, import_core15.getNodesForLayer)(doc, childLayerId, sizing);
|
|
2468
2161
|
} catch {
|
|
2469
2162
|
return [];
|
|
2470
2163
|
}
|
|
@@ -2476,20 +2169,13 @@ function VisualliCanvas(props) {
|
|
|
2476
2169
|
if (!node) return;
|
|
2477
2170
|
const cw = canvasSizeRef.current.width || 800;
|
|
2478
2171
|
const ch = canvasSizeRef.current.height || 600;
|
|
2479
|
-
const fitVp = (0,
|
|
2480
|
-
|
|
2481
|
-
|
|
2172
|
+
const fitVp = (0, import_core3.calculateFitView)(flatNodes, cw, ch, containers);
|
|
2173
|
+
parentViewports.current.push({ centerX: fitVp.centerX, centerY: fitVp.centerY, zoomLevel: fitVp.zoomLevel });
|
|
2174
|
+
stepTargetRef.current = { paint: { topic: node.topic, custom: node.custom }, label: node.title };
|
|
2482
2175
|
setIsTransitioning(true);
|
|
2483
2176
|
isTransitioningRef.current = true;
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
node,
|
|
2487
|
-
childLayerId,
|
|
2488
|
-
childNodes,
|
|
2489
|
-
node.color ?? (0, import_core13.getThemeBackground)(isDark, "secondary"),
|
|
2490
|
-
node.color ?? (0, import_core13.getThemeBackground)(isDark, "primary")
|
|
2491
|
-
);
|
|
2492
|
-
}, [doc, currentLayerId, nodes, isDark, isAnimating, zoomIntoLayer]);
|
|
2177
|
+
diveInto(node, childLayerId, childNodes);
|
|
2178
|
+
}, [doc, currentLayerId, nodes, isAnimating, diveInto, sizing, flatNodes, containers]);
|
|
2493
2179
|
const handleNavigateBack = (0, import_react14.useCallback)((targetIndex) => {
|
|
2494
2180
|
if (targetIndex >= navStack.length - 1) return;
|
|
2495
2181
|
if (isTransitioningRef.current || isAnimating()) return;
|
|
@@ -2498,45 +2184,8 @@ function VisualliCanvas(props) {
|
|
|
2498
2184
|
navBackTargetRef.current = targetIndex;
|
|
2499
2185
|
setIsTransitioning(true);
|
|
2500
2186
|
isTransitioningRef.current = true;
|
|
2501
|
-
|
|
2502
|
-
}, [navStack.length,
|
|
2503
|
-
const baseZoomRef = (0, import_react14.useRef)(1);
|
|
2504
|
-
const lastZoomTransitionRef = (0, import_react14.useRef)(0);
|
|
2505
|
-
const ZOOM_COOLDOWN = 1e3;
|
|
2506
|
-
(0, import_react14.useEffect)(() => {
|
|
2507
|
-
baseZoomRef.current = 1;
|
|
2508
|
-
}, [currentLayerId]);
|
|
2509
|
-
(0, import_react14.useEffect)(() => {
|
|
2510
|
-
if (isTransitioningRef.current || isAnimating()) return;
|
|
2511
|
-
const now = Date.now();
|
|
2512
|
-
if (now - lastZoomTransitionRef.current < ZOOM_COOLDOWN) return;
|
|
2513
|
-
const relative = viewport.zoomLevel / baseZoomRef.current;
|
|
2514
|
-
if (relative >= import_core13.ZOOM_NAV_IN_THRESHOLD) {
|
|
2515
|
-
let closest = null;
|
|
2516
|
-
let minDist = Infinity;
|
|
2517
|
-
for (const node of nodes.values()) {
|
|
2518
|
-
const dx = node.x - viewport.centerX;
|
|
2519
|
-
const dy = node.y - viewport.centerY;
|
|
2520
|
-
const d = dx * dx + dy * dy;
|
|
2521
|
-
if (d < minDist) {
|
|
2522
|
-
minDist = d;
|
|
2523
|
-
closest = node;
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
if (closest) {
|
|
2527
|
-
const childLayer = doc ? (0, import_core5.getChildLayerForNode)(doc, closest.id, currentLayerId ?? "") : null;
|
|
2528
|
-
if (childLayer) {
|
|
2529
|
-
lastZoomTransitionRef.current = now;
|
|
2530
|
-
handleNavigate(closest.id);
|
|
2531
|
-
baseZoomRef.current = 1;
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
} else if (relative < import_core13.ZOOM_NAV_OUT_THRESHOLD && navStack.length > 1) {
|
|
2535
|
-
lastZoomTransitionRef.current = now;
|
|
2536
|
-
handleNavigateBack(navStack.length - 2);
|
|
2537
|
-
baseZoomRef.current = 1;
|
|
2538
|
-
}
|
|
2539
|
-
}, [viewport.zoomLevel, viewport.centerX, viewport.centerY, nodes, doc, currentLayerId, navStack, isAnimating, handleNavigate, handleNavigateBack]);
|
|
2187
|
+
backOut(savedVp);
|
|
2188
|
+
}, [navStack.length, isAnimating, backOut]);
|
|
2540
2189
|
const toWorldCoords = (0, import_react14.useCallback)((stage, pointer) => {
|
|
2541
2190
|
const sp = stage.position();
|
|
2542
2191
|
const sc = stage.scaleX();
|
|
@@ -2599,8 +2248,45 @@ function VisualliCanvas(props) {
|
|
|
2599
2248
|
window.removeEventListener("keydown", onKey);
|
|
2600
2249
|
};
|
|
2601
2250
|
}, [canvasContextMenu]);
|
|
2251
|
+
const selectedNodeId = useSelectionStore((st) => st.selectedId);
|
|
2252
|
+
const [focusedNodeId, setFocusedNodeId] = (0, import_react14.useState)(null);
|
|
2253
|
+
const anchors = (0, import_react14.useMemo)(() => doc ? (0, import_core15.getSemanticAnchors)(doc) : [], [doc]);
|
|
2254
|
+
const [autoTouch, setAutoTouch] = (0, import_react14.useState)(() => typeof window !== "undefined" && !!window.matchMedia && window.matchMedia("(hover: none)").matches);
|
|
2255
|
+
const touchMode = props.layout === "touch" ? true : props.layout === "pointer" ? false : autoTouch;
|
|
2256
|
+
const touchModeRef = (0, import_react14.useRef)(touchMode);
|
|
2257
|
+
touchModeRef.current = touchMode;
|
|
2258
|
+
const lastTouchAtRef = (0, import_react14.useRef)(0);
|
|
2259
|
+
const emptyTapRef = (0, import_react14.useRef)(null);
|
|
2260
|
+
const [pinnedId, setPinnedId] = (0, import_react14.useState)(null);
|
|
2261
|
+
const [sheetExpanded, setSheetExpanded] = (0, import_react14.useState)(false);
|
|
2262
|
+
const isEmulatedMouse = (evt) => evt.type.startsWith("mouse") && performance.now() - lastTouchAtRef.current < 700;
|
|
2263
|
+
const clientOf = (evt) => {
|
|
2264
|
+
const t = "touches" in evt && (evt.touches[0] ?? evt.changedTouches[0]) || evt;
|
|
2265
|
+
return { x: t.clientX, y: t.clientY };
|
|
2266
|
+
};
|
|
2267
|
+
const pinnedIdRef = (0, import_react14.useRef)(null);
|
|
2268
|
+
pinnedIdRef.current = pinnedId;
|
|
2269
|
+
const sheetHeight = Math.min(import_core15.SHEET.maxHeight, Math.round((viewport.canvasHeight || canvasSizeRef.current.height || 600) * import_core15.SHEET.heightRatio));
|
|
2270
|
+
const openSheet = (node) => {
|
|
2271
|
+
setPinnedId(node.id);
|
|
2272
|
+
setSheetExpanded(false);
|
|
2273
|
+
setHoveredNode(null);
|
|
2274
|
+
const vp = useViewportStore.getState();
|
|
2275
|
+
const ch = canvasSizeRef.current.height || vp.canvasHeight;
|
|
2276
|
+
const sh = Math.min(import_core15.SHEET.maxHeight, Math.round(ch * import_core15.SHEET.heightRatio));
|
|
2277
|
+
const screenY = (node.y - vp.centerY) * vp.zoomLevel + ch / 2;
|
|
2278
|
+
const reach = (0, import_core15.nodeRadii)(node).ry * vp.zoomLevel;
|
|
2279
|
+
if (screenY + reach > ch - sh - 12 || screenY - reach < 0) vp.setCenter(vp.centerX, node.y + sh / (2 * vp.zoomLevel));
|
|
2280
|
+
};
|
|
2281
|
+
(0, import_react14.useEffect)(() => {
|
|
2282
|
+
setPinnedId(null);
|
|
2283
|
+
}, [currentLayerId]);
|
|
2602
2284
|
const handleStageMouseDown = (0, import_react14.useCallback)((e) => {
|
|
2603
|
-
if (e.evt
|
|
2285
|
+
if (isEmulatedMouse(e.evt)) return;
|
|
2286
|
+
const isTouch = e.evt.type.startsWith("touch");
|
|
2287
|
+
if (isTouch) lastTouchAtRef.current = performance.now();
|
|
2288
|
+
if (props.layout === void 0 || props.layout === "auto") setAutoTouch(isTouch);
|
|
2289
|
+
if (!isTouch && e.evt.button !== 0) {
|
|
2604
2290
|
rendererHandleMouseDown(e);
|
|
2605
2291
|
return;
|
|
2606
2292
|
}
|
|
@@ -2622,7 +2308,7 @@ function VisualliCanvas(props) {
|
|
|
2622
2308
|
select(nodeId);
|
|
2623
2309
|
stageActiveNodeIdRef.current = nodeId;
|
|
2624
2310
|
stageDragNodeOffsetRef.current = { x: worldX - node.x, y: worldY - node.y };
|
|
2625
|
-
stageMouseDownPosRef.current =
|
|
2311
|
+
stageMouseDownPosRef.current = clientOf(e.evt);
|
|
2626
2312
|
stageDragCommittedRef.current = false;
|
|
2627
2313
|
setPressedNodeId(nodeId);
|
|
2628
2314
|
setHoveredNode(null);
|
|
@@ -2630,10 +2316,12 @@ function VisualliCanvas(props) {
|
|
|
2630
2316
|
}
|
|
2631
2317
|
}
|
|
2632
2318
|
setHoveredNode(null);
|
|
2319
|
+
emptyTapRef.current = clientOf(e.evt);
|
|
2633
2320
|
isCanvasPanningRef.current = true;
|
|
2634
2321
|
rendererHandleMouseDown(e);
|
|
2635
|
-
}, [hitTestNode, toWorldCoords, nodes, select, rendererHandleMouseDown]);
|
|
2322
|
+
}, [hitTestNode, toWorldCoords, nodes, select, rendererHandleMouseDown, props.layout]);
|
|
2636
2323
|
const handleStageMouseMove = (0, import_react14.useCallback)((e) => {
|
|
2324
|
+
if (isEmulatedMouse(e.evt)) return;
|
|
2637
2325
|
const stage = e.target.getStage();
|
|
2638
2326
|
if (!stage) {
|
|
2639
2327
|
rendererHandleMouseMove(e);
|
|
@@ -2646,7 +2334,8 @@ function VisualliCanvas(props) {
|
|
|
2646
2334
|
}
|
|
2647
2335
|
const { worldX, worldY } = toWorldCoords(stage, pointer);
|
|
2648
2336
|
if (stageActiveNodeIdRef.current && stageMouseDownPosRef.current) {
|
|
2649
|
-
const
|
|
2337
|
+
const cur = clientOf(e.evt);
|
|
2338
|
+
const moved = Math.hypot(cur.x - stageMouseDownPosRef.current.x, cur.y - stageMouseDownPosRef.current.y);
|
|
2650
2339
|
if (!stageDragCommittedRef.current && moved >= 5) {
|
|
2651
2340
|
stageDragCommittedRef.current = true;
|
|
2652
2341
|
isDraggingRef.current = true;
|
|
@@ -2658,7 +2347,6 @@ function VisualliCanvas(props) {
|
|
|
2658
2347
|
if (stageDragCommittedRef.current) {
|
|
2659
2348
|
const offset = stageDragNodeOffsetRef.current;
|
|
2660
2349
|
handleDragMove(stageActiveNodeIdRef.current, worldX - offset.x, worldY - offset.y);
|
|
2661
|
-
stage.container().style.cursor = "grabbing";
|
|
2662
2350
|
return;
|
|
2663
2351
|
}
|
|
2664
2352
|
return;
|
|
@@ -2674,17 +2362,14 @@ function VisualliCanvas(props) {
|
|
|
2674
2362
|
if (hovered !== pointerNodeIdRef.current) {
|
|
2675
2363
|
pointerNodeIdRef.current = hovered;
|
|
2676
2364
|
setPointerNodeId(hovered);
|
|
2677
|
-
|
|
2678
|
-
}
|
|
2679
|
-
if (hovered) {
|
|
2680
|
-
stage.container().style.cursor = "pointer";
|
|
2681
|
-
} else {
|
|
2682
|
-
stage.container().style.cursor = "grab";
|
|
2365
|
+
onNodeHoverRef.current?.(hovered);
|
|
2683
2366
|
}
|
|
2367
|
+
stage.container().style.cursor = hovered ? "pointer" : "";
|
|
2684
2368
|
}
|
|
2685
2369
|
rendererHandleMouseMove(e);
|
|
2686
2370
|
}, [toWorldCoords, hitTestNode, handleDragStart, handleDragMove, rendererHandleMouseMove]);
|
|
2687
2371
|
const handleStageMouseUp = (0, import_react14.useCallback)((e) => {
|
|
2372
|
+
if (isEmulatedMouse(e.evt)) return;
|
|
2688
2373
|
setPressedNodeId(null);
|
|
2689
2374
|
if (stageActiveNodeIdRef.current) {
|
|
2690
2375
|
const nodeId = stageActiveNodeIdRef.current;
|
|
@@ -2701,8 +2386,13 @@ function VisualliCanvas(props) {
|
|
|
2701
2386
|
} else {
|
|
2702
2387
|
const node = nodes.get(nodeId);
|
|
2703
2388
|
if (node) {
|
|
2704
|
-
const childLayer = doc ? (0,
|
|
2705
|
-
if (childLayer) {
|
|
2389
|
+
const childLayer = doc ? (0, import_core3.getChildLayerForNode)(doc, nodeId, currentLayerId ?? "") : null;
|
|
2390
|
+
if (touchModeRef.current && childLayer && pinnedIdRef.current === nodeId) {
|
|
2391
|
+
setPinnedId(null);
|
|
2392
|
+
handleNavigate(nodeId);
|
|
2393
|
+
} else if (touchModeRef.current && (node.description || childLayer)) {
|
|
2394
|
+
openSheet(node);
|
|
2395
|
+
} else if (childLayer) {
|
|
2706
2396
|
handleNavigate(nodeId);
|
|
2707
2397
|
}
|
|
2708
2398
|
onNodeClick?.(node);
|
|
@@ -2715,6 +2405,12 @@ function VisualliCanvas(props) {
|
|
|
2715
2405
|
stageDragCommittedRef.current = false;
|
|
2716
2406
|
return;
|
|
2717
2407
|
}
|
|
2408
|
+
const tap = emptyTapRef.current;
|
|
2409
|
+
emptyTapRef.current = null;
|
|
2410
|
+
if (tap && pinnedIdRef.current) {
|
|
2411
|
+
const end = clientOf(e.evt);
|
|
2412
|
+
if (!Number.isFinite(end.x) || Math.hypot(end.x - tap.x, end.y - tap.y) < 6) setPinnedId(null);
|
|
2413
|
+
}
|
|
2718
2414
|
isCanvasPanningRef.current = false;
|
|
2719
2415
|
rendererHandleMouseUp(e);
|
|
2720
2416
|
}, [toWorldCoords, handleDragEnd, nodes, handleNavigate, onNodeClick, rendererHandleMouseUp]);
|
|
@@ -2729,31 +2425,75 @@ function VisualliCanvas(props) {
|
|
|
2729
2425
|
const localY = rect ? e.evt.clientY - rect.top : e.evt.clientY;
|
|
2730
2426
|
setCanvasContextMenu({ x: localX, y: localY });
|
|
2731
2427
|
}, [navStack.length]);
|
|
2732
|
-
const
|
|
2733
|
-
const
|
|
2734
|
-
const
|
|
2735
|
-
const
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2428
|
+
const touchPointsRef = (0, import_react14.useRef)(/* @__PURE__ */ new Map());
|
|
2429
|
+
const pinchRef = (0, import_react14.useRef)(null);
|
|
2430
|
+
const pinchEndedAtRef = (0, import_react14.useRef)(-Infinity);
|
|
2431
|
+
const [pinching, setPinching] = (0, import_react14.useState)(false);
|
|
2432
|
+
const localOf = (e) => {
|
|
2433
|
+
const r = containerRef.current.getBoundingClientRect();
|
|
2434
|
+
return { x: e.clientX - r.left, y: e.clientY - r.top };
|
|
2435
|
+
};
|
|
2436
|
+
const onPinchDown = (e) => {
|
|
2437
|
+
if (!import_core15.GESTURE.pinchZoom || !import_core15.GESTURE.pointers.includes(e.pointerType)) return;
|
|
2438
|
+
touchPointsRef.current.set(e.pointerId, localOf(e));
|
|
2439
|
+
if (touchPointsRef.current.size !== 2 || isTransitioningRef.current || isAnimating()) return;
|
|
2440
|
+
const [a, b] = [...touchPointsRef.current.values()];
|
|
2441
|
+
const vp = useViewportStore.getState();
|
|
2442
|
+
pinchRef.current = { d0: Math.hypot(a.x - b.x, a.y - b.y), m0: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }, z0: vp.zoomLevel, c0: { x: vp.centerX, y: vp.centerY } };
|
|
2443
|
+
rendererCancelPan();
|
|
2444
|
+
isCanvasPanningRef.current = false;
|
|
2445
|
+
emptyTapRef.current = null;
|
|
2446
|
+
stageActiveNodeIdRef.current = null;
|
|
2447
|
+
stageDragCommittedRef.current = false;
|
|
2448
|
+
isDraggingRef.current = false;
|
|
2449
|
+
setPressedNodeId(null);
|
|
2450
|
+
setPinching(true);
|
|
2451
|
+
};
|
|
2452
|
+
const onPinchMove = (e) => {
|
|
2453
|
+
if (!touchPointsRef.current.has(e.pointerId)) return;
|
|
2454
|
+
touchPointsRef.current.set(e.pointerId, localOf(e));
|
|
2455
|
+
const p = pinchRef.current;
|
|
2456
|
+
if (!p || touchPointsRef.current.size < 2) return;
|
|
2457
|
+
const [a, b] = [...touchPointsRef.current.values()];
|
|
2458
|
+
const fit = useViewportStore.getState().fitScale;
|
|
2459
|
+
const z = (0, import_core15.pinchZoom)(p.z0 / fit, p.d0, Math.hypot(a.x - b.x, a.y - b.y)) * fit;
|
|
2460
|
+
const m = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
2461
|
+
const cw = canvasSizeRef.current.width || containerRef.current?.clientWidth || 800;
|
|
2462
|
+
const ch = canvasSizeRef.current.height || containerRef.current?.clientHeight || 600;
|
|
2463
|
+
const t = (0, import_core15.zoomAround)({ x: cw / 2 - p.c0.x * p.z0, y: ch / 2 - p.c0.y * p.z0 }, p.z0, z, p.m0, import_core15.GESTURE.twoFingerPan ? m : p.m0);
|
|
2464
|
+
setZoom(z);
|
|
2465
|
+
setCenter((cw / 2 - t.x) / z, (ch / 2 - t.y) / z);
|
|
2466
|
+
};
|
|
2467
|
+
const onPinchUp = (e) => {
|
|
2468
|
+
if (!touchPointsRef.current.delete(e.pointerId)) return;
|
|
2469
|
+
if (pinchRef.current && touchPointsRef.current.size < 2) {
|
|
2470
|
+
pinchRef.current = null;
|
|
2471
|
+
pinchEndedAtRef.current = performance.now();
|
|
2472
|
+
setPinching(false);
|
|
2740
2473
|
}
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2474
|
+
};
|
|
2475
|
+
const inPinch = () => pinchRef.current !== null || touchPointsRef.current.size > 1 || performance.now() - pinchEndedAtRef.current < 300;
|
|
2476
|
+
const handleStageTouchStart = (0, import_react14.useCallback)((e) => {
|
|
2477
|
+
if (inPinch() || e.evt.touches.length > 1) return;
|
|
2478
|
+
handleStageMouseDown(e);
|
|
2479
|
+
}, [handleStageMouseDown]);
|
|
2480
|
+
const handleStageTouchMove = (0, import_react14.useCallback)((e) => {
|
|
2481
|
+
if (inPinch() || e.evt.touches.length > 1) return;
|
|
2482
|
+
handleStageMouseMove(e);
|
|
2483
|
+
}, [handleStageMouseMove]);
|
|
2484
|
+
const handleStageTouchEnd = (0, import_react14.useCallback)((e) => {
|
|
2485
|
+
if (inPinch()) {
|
|
2486
|
+
isCanvasPanningRef.current = false;
|
|
2751
2487
|
return;
|
|
2752
2488
|
}
|
|
2489
|
+
handleStageMouseUp(e);
|
|
2490
|
+
}, [handleStageMouseUp]);
|
|
2491
|
+
const handleWheel = (0, import_react14.useCallback)((e) => {
|
|
2753
2492
|
rendererHandleWheel(e);
|
|
2754
|
-
}, [rendererHandleWheel
|
|
2493
|
+
}, [rendererHandleWheel]);
|
|
2755
2494
|
const lastHoverCheckRef = (0, import_react14.useRef)(0);
|
|
2756
2495
|
const handleCanvasMouseMove = (0, import_react14.useCallback)((e) => {
|
|
2496
|
+
if (touchModeRef.current || isEmulatedMouse(e.nativeEvent)) return;
|
|
2757
2497
|
if (!containerRef.current || isDraggingRef.current || isCanvasPanningRef.current) {
|
|
2758
2498
|
setHoveredNode(null);
|
|
2759
2499
|
return;
|
|
@@ -2789,7 +2529,7 @@ function VisualliCanvas(props) {
|
|
|
2789
2529
|
if (clearTooltipTimerRef.current) clearTimeout(clearTooltipTimerRef.current);
|
|
2790
2530
|
clearTooltipTimerRef.current = setTimeout(() => {
|
|
2791
2531
|
if (!tooltipHoverRef.current && !keepTooltipOpenRef.current) setHoveredNode(null);
|
|
2792
|
-
},
|
|
2532
|
+
}, import_core15.PEEK.hoverClose);
|
|
2793
2533
|
} else {
|
|
2794
2534
|
if (clearTooltipTimerRef.current) {
|
|
2795
2535
|
clearTimeout(clearTooltipTimerRef.current);
|
|
@@ -2813,28 +2553,77 @@ function VisualliCanvas(props) {
|
|
|
2813
2553
|
if (dragMoveRafRef.current !== null) cancelAnimationFrame(dragMoveRafRef.current);
|
|
2814
2554
|
};
|
|
2815
2555
|
}, []);
|
|
2816
|
-
const
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2556
|
+
const activateNode = (0, import_react14.useCallback)((node) => {
|
|
2557
|
+
const childLayer = doc ? (0, import_core3.getChildLayerForNode)(doc, node.id, currentLayerId ?? "") : null;
|
|
2558
|
+
if (touchModeRef.current && childLayer && pinnedIdRef.current === node.id) {
|
|
2559
|
+
setPinnedId(null);
|
|
2560
|
+
handleNavigate(node.id);
|
|
2561
|
+
} else if (touchModeRef.current && (node.description || childLayer)) openSheet(node);
|
|
2562
|
+
else if (childLayer) handleNavigate(node.id);
|
|
2563
|
+
onNodeClick?.(node);
|
|
2564
|
+
}, [doc, currentLayerId, handleNavigate, onNodeClick]);
|
|
2565
|
+
const stepInside = (0, import_react14.useCallback)((nodeId) => {
|
|
2566
|
+
setHoveredNode(null);
|
|
2567
|
+
handleNavigate(nodeId);
|
|
2568
|
+
}, [handleNavigate]);
|
|
2569
|
+
(0, import_react14.useEffect)(() => {
|
|
2570
|
+
if (!hoveredNode) tooltipHoverRef.current = false;
|
|
2571
|
+
}, [hoveredNode]);
|
|
2572
|
+
const handleKeyDown = (0, import_react14.useCallback)((e) => {
|
|
2573
|
+
if (e.target.closest('input, textarea, [contenteditable="true"]')) return;
|
|
2574
|
+
const cmd = e.metaKey || e.ctrlKey;
|
|
2575
|
+
if (cmd && !e.altKey) {
|
|
2576
|
+
if (e.key === "+" || e.key === "=") {
|
|
2577
|
+
e.preventDefault();
|
|
2578
|
+
setZoom(viewport.zoomLevel * import_core15.VIEW.zoomStep);
|
|
2579
|
+
} else if (e.key === "-" || e.key === "_") {
|
|
2580
|
+
e.preventDefault();
|
|
2581
|
+
setZoom(viewport.zoomLevel / import_core15.VIEW.zoomStep);
|
|
2582
|
+
} else if (e.key === "0") {
|
|
2583
|
+
e.preventDefault();
|
|
2584
|
+
fitToScreen();
|
|
2585
|
+
}
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
if (e.altKey || e.shiftKey) return;
|
|
2589
|
+
if (e.key === "Escape" && pinnedIdRef.current) {
|
|
2590
|
+
e.preventDefault();
|
|
2591
|
+
setPinnedId(null);
|
|
2592
|
+
} else if ((e.key === "Escape" || e.key === "Backspace") && navStack.length > 1) {
|
|
2593
|
+
e.preventDefault();
|
|
2594
|
+
handleNavigateBack(navStack.length - 2);
|
|
2595
|
+
}
|
|
2596
|
+
}, [navStack.length, handleNavigateBack, setZoom, viewport.zoomLevel, fitToScreen]);
|
|
2820
2597
|
if (!doc) {
|
|
2821
|
-
return /* @__PURE__ */ (0,
|
|
2598
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `vi-map ${className}`, ...design.attrs, style: { display: "flex", alignItems: "center", justifyContent: "center", ...style }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "body-sm", style: { color: "var(--ink-muted)" }, children: "No document" }) });
|
|
2822
2599
|
}
|
|
2823
2600
|
const hasBackOption = navStack.length > 1;
|
|
2824
|
-
|
|
2601
|
+
const currentLayerTitle = navStack[navStack.length - 1]?.label ?? "";
|
|
2602
|
+
const peekNode = hoveredNode ? nodes.get(hoveredNode.nodeId) : void 0;
|
|
2603
|
+
const pinnedNode = pinnedId ? nodes.get(pinnedId) : void 0;
|
|
2604
|
+
const pinnedHasChild = !!(pinnedNode && (0, import_core3.getChildLayerForNode)(doc, pinnedNode.id, currentLayerId ?? ""));
|
|
2605
|
+
const compact = (viewport.canvasWidth || canvasSizeRef.current.width || 1024) < 560;
|
|
2606
|
+
const medium = !compact && (viewport.canvasWidth || canvasSizeRef.current.width || 1024) < 900;
|
|
2607
|
+
const peekHasChild = !!(peekNode && doc && (0, import_core3.getChildLayerForNode)(doc, peekNode.id, currentLayerId ?? ""));
|
|
2608
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2825
2609
|
"div",
|
|
2826
2610
|
{
|
|
2827
2611
|
ref: containerRef,
|
|
2828
|
-
className
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2612
|
+
className: `vi-map${touchMode ? " is-touch" : ""}${compact ? " is-compact" : ""}${medium ? " is-medium" : ""}${pinching ? " is-pinching" : ""}${props.app ? " is-app" : ""} ${className}`,
|
|
2613
|
+
...design.attrs,
|
|
2614
|
+
"data-reveal": instantReveal ? "instant" : void 0,
|
|
2615
|
+
role: "group",
|
|
2616
|
+
"aria-label": `${doc.meta?.title ?? "Map"}${currentLayerTitle ? `, ${currentLayerTitle}` : ""}`,
|
|
2617
|
+
onKeyDown: handleKeyDown,
|
|
2618
|
+
tabIndex: -1,
|
|
2619
|
+
onPointerDownCapture: (e) => {
|
|
2620
|
+
onPinchDown(e);
|
|
2621
|
+
if (!e.target.closest("button, a, input")) containerRef.current?.focus({ preventScroll: true });
|
|
2837
2622
|
},
|
|
2623
|
+
onPointerMoveCapture: onPinchMove,
|
|
2624
|
+
onPointerUpCapture: onPinchUp,
|
|
2625
|
+
onPointerCancelCapture: onPinchUp,
|
|
2626
|
+
style: { position: "relative", overflow: "hidden", userSelect: "none", width: "100%", height: "100%", outline: "none", ...(0, import_core15.motionVars)(), ...style },
|
|
2838
2627
|
onMouseMove: handleCanvasMouseMove,
|
|
2839
2628
|
onMouseLeave: () => {
|
|
2840
2629
|
if (!tooltipHoverRef.current && !keepTooltipOpenRef.current) {
|
|
@@ -2842,7 +2631,8 @@ function VisualliCanvas(props) {
|
|
|
2842
2631
|
}
|
|
2843
2632
|
},
|
|
2844
2633
|
children: [
|
|
2845
|
-
/* @__PURE__ */ (0,
|
|
2634
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "vi-map__immersion", style: { background: immersionFill ?? "transparent" }, "aria-hidden": "true" }),
|
|
2635
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { ref: canvasWrapperRef, style: { position: "relative", zIndex: 1, pointerEvents: "auto" }, children: fontsReady && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
2846
2636
|
KonvaStage_default,
|
|
2847
2637
|
{
|
|
2848
2638
|
ref: stageRef,
|
|
@@ -2855,56 +2645,59 @@ function VisualliCanvas(props) {
|
|
|
2855
2645
|
onTouchMove: handleStageTouchMove,
|
|
2856
2646
|
onTouchEnd: handleStageTouchEnd,
|
|
2857
2647
|
children: [
|
|
2858
|
-
/* @__PURE__ */ (0,
|
|
2859
|
-
/* @__PURE__ */ (0,
|
|
2860
|
-
|
|
2861
|
-
{
|
|
2862
|
-
nodes: flatNodes,
|
|
2863
|
-
connections,
|
|
2864
|
-
isDark,
|
|
2865
|
-
isDragging: isDraggingState
|
|
2866
|
-
}
|
|
2867
|
-
),
|
|
2868
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2648
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(KonvaContainerLayer, { nodes: flatNodes, containers, connections, design }),
|
|
2649
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(KonvaEdgeLayer, { nodes: flatNodes, connections, design, isDragging: isDraggingState, clock }),
|
|
2650
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2869
2651
|
KonvaNodeLayer,
|
|
2870
2652
|
{
|
|
2653
|
+
design,
|
|
2654
|
+
clock,
|
|
2871
2655
|
isTransitioning,
|
|
2872
2656
|
isDragging: isDraggingState,
|
|
2873
2657
|
hoveredNodeId: pointerNodeId,
|
|
2874
|
-
pressedNodeId
|
|
2658
|
+
pressedNodeId,
|
|
2659
|
+
selectedNodeId,
|
|
2660
|
+
focusedNodeId,
|
|
2661
|
+
touch: touchMode,
|
|
2662
|
+
peekedNodeId: touchMode ? pinnedId : null
|
|
2875
2663
|
}
|
|
2876
|
-
)
|
|
2664
|
+
),
|
|
2665
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(KonvaContainerLabelLayer, { containers, connections, design })
|
|
2877
2666
|
]
|
|
2878
2667
|
}
|
|
2879
2668
|
) }),
|
|
2880
|
-
|
|
2669
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2670
|
+
A11yLayer,
|
|
2671
|
+
{
|
|
2672
|
+
nodes: flatNodes,
|
|
2673
|
+
label: `Ideas in ${currentLayerTitle || "this map"}`,
|
|
2674
|
+
hasChildLayer: (id) => !!(0, import_core3.getChildLayerForNode)(doc, id, currentLayerId ?? ""),
|
|
2675
|
+
onActivate: activateNode,
|
|
2676
|
+
onFocusNode: setFocusedNodeId,
|
|
2677
|
+
announcement: hasBackOption ? `Now inside ${currentLayerTitle}` : void 0
|
|
2678
|
+
}
|
|
2679
|
+
),
|
|
2680
|
+
canvasContextMenu && hasBackOption && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2881
2681
|
"div",
|
|
2882
2682
|
{
|
|
2883
2683
|
role: "menu",
|
|
2884
2684
|
"aria-label": "Canvas options",
|
|
2885
2685
|
style: {
|
|
2886
2686
|
position: "absolute",
|
|
2887
|
-
zIndex:
|
|
2687
|
+
zIndex: "var(--z-menu)",
|
|
2888
2688
|
pointerEvents: "auto",
|
|
2889
2689
|
left: `${Math.min(canvasContextMenu.x, Math.max(8, (canvasSizeRef.current.width || 800) - 152))}px`,
|
|
2890
2690
|
top: `${Math.min(canvasContextMenu.y, Math.max(8, (canvasSizeRef.current.height || 600) - 72))}px`
|
|
2891
2691
|
},
|
|
2892
2692
|
onContextMenu: (e) => e.preventDefault(),
|
|
2893
2693
|
onPointerDown: (e) => e.stopPropagation(),
|
|
2894
|
-
children: /* @__PURE__ */ (0,
|
|
2694
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2895
2695
|
"button",
|
|
2896
2696
|
{
|
|
2897
2697
|
type: "button",
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
border: `1px solid ${isDark ? "#333330" : "#DDD9D0"}`,
|
|
2902
|
-
background: isDark ? "#1C1C1A" : "#FAF8F4",
|
|
2903
|
-
color: isDark ? "#F0EDE6" : "#1A1A18",
|
|
2904
|
-
cursor: "pointer",
|
|
2905
|
-
fontSize: 13,
|
|
2906
|
-
boxShadow: "0 4px 12px rgba(0,0,0,0.15)"
|
|
2907
|
-
},
|
|
2698
|
+
role: "menuitem",
|
|
2699
|
+
className: "vi-iconbtn vi-iconbtn--surface",
|
|
2700
|
+
style: { width: "auto", padding: "6px var(--space-4)", boxShadow: "var(--shadow-pop)" },
|
|
2908
2701
|
onClick: () => {
|
|
2909
2702
|
setCanvasContextMenu(null);
|
|
2910
2703
|
handleNavigateBack(navStack.length - 2);
|
|
@@ -2914,32 +2707,41 @@ function VisualliCanvas(props) {
|
|
|
2914
2707
|
)
|
|
2915
2708
|
}
|
|
2916
2709
|
),
|
|
2917
|
-
/* @__PURE__ */ (0,
|
|
2918
|
-
/* @__PURE__ */ (0,
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2710
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(NavigationStack_default, { stack: navStack, onNavigateBack: handleNavigateBack, top: navigationStackTop, left: navigationStackLeft }),
|
|
2711
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2712
|
+
"div",
|
|
2713
|
+
{
|
|
2714
|
+
className: "vi-map__ctrls",
|
|
2715
|
+
style: props.controlsPosition === "bottom-right" ? { top: "auto", bottom: pinnedNode && touchMode ? `calc(var(--space-4) + ${sheetHeight}px)` : "var(--space-4)" } : void 0,
|
|
2716
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ZoomControls, { onFit: fitToScreen, touch: touchMode })
|
|
2717
|
+
}
|
|
2718
|
+
),
|
|
2719
|
+
renderOverlay?.({ isDark, theme: design.theme, containerWidth: canvasSizeRef.current.width || 0, containerHeight: canvasSizeRef.current.height || 0 }),
|
|
2720
|
+
touchMode && pinnedNode && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2721
|
+
PeekSheet,
|
|
2722
|
+
{
|
|
2723
|
+
node: pinnedNode,
|
|
2724
|
+
anchors,
|
|
2725
|
+
height: sheetHeight,
|
|
2726
|
+
expanded: sheetExpanded,
|
|
2727
|
+
onToggleExpanded: () => setSheetExpanded((v) => !v),
|
|
2728
|
+
onClose: () => setPinnedId(null),
|
|
2729
|
+
onStepInside: pinnedHasChild ? () => {
|
|
2730
|
+
setPinnedId(null);
|
|
2731
|
+
handleNavigate(pinnedNode.id);
|
|
2732
|
+
} : void 0,
|
|
2733
|
+
renderContent: renderNodeContent ? (summary) => renderNodeContent({ summary, nodeId: pinnedNode.id, nodeColor: pinnedNode.color, zoom: viewport.zoomLevel }) : void 0
|
|
2734
|
+
}
|
|
2735
|
+
),
|
|
2736
|
+
!touchMode && hoveredNode && hoveredNodePosition && peekNode && (() => {
|
|
2737
|
+
const rings = (0, import_core15.ringsFor)(peekNode.branchCount);
|
|
2738
|
+
const half = (0, import_core15.nodeRadii)(peekNode).rx * (rings.length ? rings[rings.length - 1].scale : 1) * hoveredNodePosition.zoom;
|
|
2739
|
+
const at = (0, import_core15.peekPosition)({ x: hoveredNodePosition.screenX, y: hoveredNodePosition.screenY, half }, { w: canvasSizeRef.current.width || 800, h: canvasSizeRef.current.height || 600 });
|
|
2740
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2931
2741
|
"div",
|
|
2932
2742
|
{
|
|
2933
|
-
|
|
2934
|
-
style: {
|
|
2935
|
-
position: "absolute",
|
|
2936
|
-
zIndex: 50,
|
|
2937
|
-
left: safeLeft,
|
|
2938
|
-
top: safeTop,
|
|
2939
|
-
transform: "translate(-50%, -100%)",
|
|
2940
|
-
transformOrigin: "bottom center",
|
|
2941
|
-
pointerEvents: "auto"
|
|
2942
|
-
},
|
|
2743
|
+
className: "vi-map__fact",
|
|
2744
|
+
style: { left: at.left, top: at.top, pointerEvents: "auto", zIndex: "var(--z-card)" },
|
|
2943
2745
|
onMouseEnter: () => {
|
|
2944
2746
|
tooltipHoverRef.current = true;
|
|
2945
2747
|
if (clearTooltipTimerRef.current) {
|
|
@@ -2952,22 +2754,17 @@ function VisualliCanvas(props) {
|
|
|
2952
2754
|
if (clearTooltipTimerRef.current) clearTimeout(clearTooltipTimerRef.current);
|
|
2953
2755
|
clearTooltipTimerRef.current = setTimeout(() => {
|
|
2954
2756
|
if (!keepTooltipOpenRef.current) setHoveredNode(null);
|
|
2955
|
-
},
|
|
2757
|
+
}, import_core15.PEEK.hoverClose);
|
|
2956
2758
|
},
|
|
2957
|
-
children: /* @__PURE__ */ (0,
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
summary: hoveredNode.summary,
|
|
2967
|
-
nodeId: hoveredNode.nodeId,
|
|
2968
|
-
nodeColor: nodeData?.color,
|
|
2969
|
-
zoom: viewport.zoomLevel
|
|
2970
|
-
}) : hoveredNode.summary }) }) })
|
|
2759
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
2760
|
+
PeekCard,
|
|
2761
|
+
{
|
|
2762
|
+
node: peekNode,
|
|
2763
|
+
anchors,
|
|
2764
|
+
onStepInside: peekHasChild ? () => stepInside(peekNode.id) : void 0,
|
|
2765
|
+
renderContent: renderNodeContent ? (summary) => renderNodeContent({ summary, nodeId: peekNode.id, nodeColor: peekNode.color, zoom: viewport.zoomLevel }) : void 0
|
|
2766
|
+
}
|
|
2767
|
+
)
|
|
2971
2768
|
}
|
|
2972
2769
|
);
|
|
2973
2770
|
})()
|
|
@@ -2977,91 +2774,29 @@ function VisualliCanvas(props) {
|
|
|
2977
2774
|
}
|
|
2978
2775
|
|
|
2979
2776
|
// src/VisualliRenderer.tsx
|
|
2980
|
-
var
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
mq.addEventListener("change", handler);
|
|
2988
|
-
return () => mq.removeEventListener("change", handler);
|
|
2989
|
-
}, [mq]);
|
|
2990
|
-
return dark;
|
|
2991
|
-
}
|
|
2992
|
-
function useFontInjection() {
|
|
2993
|
-
(0, import_react15.useEffect)(() => {
|
|
2994
|
-
if (typeof document === "undefined") return;
|
|
2995
|
-
const links = [
|
|
2996
|
-
"https://fonts.googleapis.com",
|
|
2997
|
-
"https://fonts.gstatic.com",
|
|
2998
|
-
"https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700;800&family=Nunito+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300&display=swap",
|
|
2999
|
-
"https://fonts.googleapis.com/css2?family=Playpen+Sans:wght@300;400;600;800&family=Story+Script:wght@400&display=swap"
|
|
3000
|
-
];
|
|
3001
|
-
links.forEach((href, i) => {
|
|
3002
|
-
const id = `visualli-font-${i}`;
|
|
3003
|
-
if (!document.getElementById(id)) {
|
|
3004
|
-
const link = document.createElement("link");
|
|
3005
|
-
link.id = id;
|
|
3006
|
-
link.rel = i < 2 ? "preconnect" : "stylesheet";
|
|
3007
|
-
link.href = href;
|
|
3008
|
-
if (i === 1) link.crossOrigin = "anonymous";
|
|
3009
|
-
document.head.appendChild(link);
|
|
3010
|
-
}
|
|
3011
|
-
});
|
|
3012
|
-
}, []);
|
|
3013
|
-
}
|
|
3014
|
-
function EmptyState({ isDark }) {
|
|
3015
|
-
const bg = isDark ? "#131311" : "#F5F3EF";
|
|
3016
|
-
const fg = isDark ? "rgba(240,237,230,0.35)" : "rgba(26,26,24,0.35)";
|
|
3017
|
-
const bd = isDark ? "#2A2A28" : "#DDD9D0";
|
|
3018
|
-
const code = { fontFamily: "monospace", fontSize: 12, padding: "1px 4px", borderRadius: 4, background: isDark ? "#222220" : "#E8E4DC", border: `1px solid ${bd}` };
|
|
3019
|
-
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", gap: 16, background: bg, color: fg, fontFamily: "'Nunito', system-ui, sans-serif" }, children: [
|
|
3020
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "48", height: "48", viewBox: "0 0 48 48", fill: "none", stroke: fg, strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3021
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("rect", { x: "10", y: "6", width: "28", height: "36", rx: "3" }),
|
|
3022
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { x1: "16", y1: "16", x2: "32", y2: "16" }),
|
|
3023
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { x1: "16", y1: "22", x2: "32", y2: "22" }),
|
|
3024
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { x1: "16", y1: "28", x2: "26", y2: "28" })
|
|
3025
|
-
] }),
|
|
3026
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { textAlign: "center", lineHeight: 1.7 }, children: [
|
|
3027
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { margin: 0, fontSize: 15, fontWeight: 600, color: isDark ? "rgba(240,237,230,0.7)" : "rgba(26,26,24,0.7)" }, children: "No .visualli file provided" }),
|
|
3028
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { style: { margin: "4px 0 0", fontSize: 13 }, children: [
|
|
3029
|
-
"Pass a ",
|
|
3030
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { style: code, children: "visualliFile" }),
|
|
3031
|
-
" or ",
|
|
3032
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { style: code, children: "visualliString" }),
|
|
3033
|
-
" prop."
|
|
3034
|
-
] })
|
|
3035
|
-
] })
|
|
2777
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
2778
|
+
var stateBox = { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", gap: "var(--space-3)", padding: "var(--space-7)", boxSizing: "border-box", textAlign: "center", color: "var(--ink-muted)", background: "var(--canvas)" };
|
|
2779
|
+
function StateNote({ topic, title, children, role }) {
|
|
2780
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "vi-map", style: stateBox, role, "aria-live": role === "status" ? "polite" : void 0, children: [
|
|
2781
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { width: "64", height: "52", viewBox: "-32 -26 64 52", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: (0, import_core16.blobPath)((0, import_core16.shapeForLevel)(0), 26, 20), fill: `var(--topic-${topic})`, stroke: `var(--topic-${topic}-ring)`, strokeWidth: "2" }) }),
|
|
2782
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "label", style: { margin: 0, color: "var(--ink)" }, children: title }),
|
|
2783
|
+
children && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "body-sm", style: { margin: 0, maxWidth: 480, overflowWrap: "anywhere" }, children })
|
|
3036
2784
|
] });
|
|
3037
2785
|
}
|
|
3038
|
-
function
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
/* @__PURE__ */ (0,
|
|
3044
|
-
|
|
3045
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { margin: 0, fontSize: 14 }, children: "Loading .visualli file\u2026" })
|
|
2786
|
+
function EmptyState() {
|
|
2787
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(StateNote, { topic: "stone", title: "Nothing to show yet", children: [
|
|
2788
|
+
"Pass a ",
|
|
2789
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { style: { fontFamily: "var(--font-mono)" }, children: "visualliFile" }),
|
|
2790
|
+
" or ",
|
|
2791
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { style: { fontFamily: "var(--font-mono)" }, children: "visualliString" }),
|
|
2792
|
+
"."
|
|
3046
2793
|
] });
|
|
3047
2794
|
}
|
|
3048
|
-
function
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
const body = isDark ? "rgba(248,113,113,0.8)" : "rgba(180,40,40,0.8)";
|
|
3054
|
-
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", width: "100%", height: "100%", padding: "24px", background: bg, fontFamily: "'Nunito', system-ui, sans-serif" }, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { maxWidth: 480, width: "100%", padding: "20px 24px", borderRadius: 12, border: `1px solid ${bd}`, background: card }, children: [
|
|
3055
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10, marginBottom: 12 }, children: [
|
|
3056
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: head, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3057
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("circle", { cx: "12", cy: "12", r: "10" }),
|
|
3058
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
|
|
3059
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
|
|
3060
|
-
] }),
|
|
3061
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: { fontSize: 15, fontWeight: 700, color: head }, children: "Failed to parse .visualli file" })
|
|
3062
|
-
] }),
|
|
3063
|
-
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: { margin: 0, fontSize: 13, color: body, lineHeight: 1.6, wordBreak: "break-word", fontFamily: "monospace" }, children: message })
|
|
3064
|
-
] }) });
|
|
2795
|
+
function LoadingState() {
|
|
2796
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StateNote, { topic: "teal", title: "Opening the map\u2026", role: "status" });
|
|
2797
|
+
}
|
|
2798
|
+
function ErrorState({ message }) {
|
|
2799
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(StateNote, { topic: "berry", title: "This map couldn't be opened", role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { style: { fontFamily: "var(--font-mono)" }, children: message }) });
|
|
3065
2800
|
}
|
|
3066
2801
|
var WORKER_SCRIPT = `
|
|
3067
2802
|
self.onmessage = function(e) {
|
|
@@ -3073,6 +2808,7 @@ self.onmessage = function(e) {
|
|
|
3073
2808
|
layers: new Map(),
|
|
3074
2809
|
layersByLevel: new Map(),
|
|
3075
2810
|
rootLayer: null,
|
|
2811
|
+
extensions: {},
|
|
3076
2812
|
};
|
|
3077
2813
|
for (var i = 0; i < lines.length; i++) {
|
|
3078
2814
|
var line = lines[i].trim();
|
|
@@ -3088,6 +2824,8 @@ self.onmessage = function(e) {
|
|
|
3088
2824
|
if (!doc.layersByLevel.has(obj.level)) doc.layersByLevel.set(obj.level, []);
|
|
3089
2825
|
doc.layersByLevel.get(obj.level).push(obj);
|
|
3090
2826
|
if (obj.level === 0) doc.rootLayer = obj;
|
|
2827
|
+
} else if (obj.type === 'extension' && typeof obj.id === 'string') {
|
|
2828
|
+
doc.extensions[obj.id] = Array.isArray(obj.data) ? obj.data : [];
|
|
3091
2829
|
}
|
|
3092
2830
|
}
|
|
3093
2831
|
if (!doc.meta) throw new Error('Missing required meta section');
|
|
@@ -3140,7 +2878,7 @@ function useAsyncParse(content, useWorker) {
|
|
|
3140
2878
|
Promise.resolve().then(() => {
|
|
3141
2879
|
if (cancelled) return;
|
|
3142
2880
|
try {
|
|
3143
|
-
const doc = (0,
|
|
2881
|
+
const doc = (0, import_core16.parseVisualliFile)(content);
|
|
3144
2882
|
if (!cancelled) setState({ status: "ready", doc });
|
|
3145
2883
|
} catch (err) {
|
|
3146
2884
|
if (!cancelled) setState({ status: "error", message: err instanceof Error ? err.message : String(err) });
|
|
@@ -3208,16 +2946,25 @@ function VisualliRenderer({
|
|
|
3208
2946
|
visualliString,
|
|
3209
2947
|
visualliFile,
|
|
3210
2948
|
theme = "light",
|
|
2949
|
+
comfort,
|
|
2950
|
+
respectForcedColors,
|
|
2951
|
+
layout,
|
|
2952
|
+
controlsPosition,
|
|
2953
|
+
app,
|
|
3211
2954
|
width = "100%",
|
|
3212
2955
|
height = "100%",
|
|
3213
2956
|
useWorker = true,
|
|
3214
2957
|
chromaticImmersion = false,
|
|
2958
|
+
fontBaseUrl,
|
|
2959
|
+
loadWebFonts,
|
|
3215
2960
|
className,
|
|
3216
2961
|
style
|
|
3217
2962
|
}) {
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
2963
|
+
const design = useDesign({ theme, comfort, respectForcedColors });
|
|
2964
|
+
(0, import_react15.useState)(() => {
|
|
2965
|
+
ensureDesignSystemStyles({ fontBaseUrl, loadWebFonts });
|
|
2966
|
+
return true;
|
|
2967
|
+
});
|
|
3221
2968
|
const cssWidth = typeof width === "number" ? `${width}px` : width;
|
|
3222
2969
|
const cssHeight = typeof height === "number" ? `${height}px` : height;
|
|
3223
2970
|
const { text: fileText, error: fileReadError, loading: fileReading } = useFileText(visualliFile);
|
|
@@ -3228,21 +2975,29 @@ function VisualliRenderer({
|
|
|
3228
2975
|
return null;
|
|
3229
2976
|
}, [parseState]);
|
|
3230
2977
|
const wrapperStyle = { width: cssWidth, height: cssHeight, overflow: "hidden", ...style };
|
|
2978
|
+
const attrs = design.attrs;
|
|
3231
2979
|
if (!visualliString && !visualliFile) {
|
|
3232
|
-
return /* @__PURE__ */ (0,
|
|
2980
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className, ...attrs, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EmptyState, {}) });
|
|
3233
2981
|
}
|
|
3234
2982
|
if (fileReading || parseState.status === "loading") {
|
|
3235
|
-
return /* @__PURE__ */ (0,
|
|
2983
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className, ...attrs, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(LoadingState, {}) });
|
|
3236
2984
|
}
|
|
3237
2985
|
if (fileReadError || parseState.status === "error") {
|
|
3238
2986
|
const msg = fileReadError ?? (parseState.status === "error" ? parseState.message : "Unknown error");
|
|
3239
|
-
return /* @__PURE__ */ (0,
|
|
2987
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className, ...attrs, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(ErrorState, { message: msg }) });
|
|
3240
2988
|
}
|
|
3241
|
-
return /* @__PURE__ */ (0,
|
|
2989
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className, style: wrapperStyle, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
3242
2990
|
VisualliCanvas,
|
|
3243
2991
|
{
|
|
3244
2992
|
preParsedVisualli: resolvedDoc ?? void 0,
|
|
3245
|
-
|
|
2993
|
+
theme,
|
|
2994
|
+
comfort,
|
|
2995
|
+
respectForcedColors,
|
|
2996
|
+
layout,
|
|
2997
|
+
controlsPosition,
|
|
2998
|
+
app,
|
|
2999
|
+
fontBaseUrl,
|
|
3000
|
+
loadWebFonts,
|
|
3246
3001
|
chromaticImmersion,
|
|
3247
3002
|
style: { width: "100%", height: "100%" }
|
|
3248
3003
|
}
|
|
@@ -3251,19 +3006,131 @@ function VisualliRenderer({
|
|
|
3251
3006
|
|
|
3252
3007
|
// src/context/VisualliContext.tsx
|
|
3253
3008
|
var import_react16 = require("react");
|
|
3254
|
-
var
|
|
3009
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
3255
3010
|
var VisualliContext = (0, import_react16.createContext)(null);
|
|
3256
3011
|
function VisualliProvider({
|
|
3257
3012
|
children
|
|
3258
3013
|
}) {
|
|
3259
3014
|
const value = {};
|
|
3260
|
-
return /* @__PURE__ */ (0,
|
|
3015
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(VisualliContext.Provider, { value, children });
|
|
3261
3016
|
}
|
|
3262
3017
|
function useVisualli() {
|
|
3263
|
-
const
|
|
3264
|
-
if (!
|
|
3018
|
+
const ctx2 = (0, import_react16.useContext)(VisualliContext);
|
|
3019
|
+
if (!ctx2) {
|
|
3265
3020
|
return {};
|
|
3266
3021
|
}
|
|
3267
|
-
return
|
|
3022
|
+
return ctx2;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
// src/hooks/useViewportNodes.ts
|
|
3026
|
+
var import_react17 = require("react");
|
|
3027
|
+
var import_core17 = require("@visualli/core");
|
|
3028
|
+
var SPATIAL_INDEX_THRESHOLD2 = 200;
|
|
3029
|
+
function useViewportNodes(nodes, levelFilter, skipCulling = false, isDragging = false) {
|
|
3030
|
+
const { centerX, centerY, zoomLevel, canvasWidth, canvasHeight } = useViewportStore();
|
|
3031
|
+
const indexRef = (0, import_react17.useRef)(null);
|
|
3032
|
+
const nodesHashRef = (0, import_react17.useRef)(0);
|
|
3033
|
+
const nodesHash = nodes.length;
|
|
3034
|
+
if (nodesHash !== nodesHashRef.current) {
|
|
3035
|
+
nodesHashRef.current = nodesHash;
|
|
3036
|
+
indexRef.current = null;
|
|
3037
|
+
}
|
|
3038
|
+
return (0, import_react17.useMemo)(() => {
|
|
3039
|
+
if (nodes.length === 0) return [];
|
|
3040
|
+
if (!indexRef.current && nodes.length >= SPATIAL_INDEX_THRESHOLD2 && !skipCulling && !isDragging) {
|
|
3041
|
+
indexRef.current = new import_core17.RBushSpatialIndex();
|
|
3042
|
+
indexRef.current.bulkLoad(nodes.map((n) => ({
|
|
3043
|
+
nodeId: n.id,
|
|
3044
|
+
bounds: { minX: n.x, minY: n.y, maxX: n.x + n.width, maxY: n.y + n.height }
|
|
3045
|
+
})));
|
|
3046
|
+
}
|
|
3047
|
+
return (0, import_core17.getViewportVisibleNodes)({
|
|
3048
|
+
nodes,
|
|
3049
|
+
viewport: { centerX, centerY, zoomLevel, rotation: 0, visibleBounds: { minX: 0, minY: 0, maxX: 0, maxY: 0 } },
|
|
3050
|
+
canvasWidth,
|
|
3051
|
+
canvasHeight,
|
|
3052
|
+
levelFilter,
|
|
3053
|
+
skipCulling,
|
|
3054
|
+
isDragging,
|
|
3055
|
+
spatialIndex: indexRef.current,
|
|
3056
|
+
spatialIndexThreshold: SPATIAL_INDEX_THRESHOLD2
|
|
3057
|
+
});
|
|
3058
|
+
}, [nodes, levelFilter, skipCulling, isDragging, centerX, centerY, zoomLevel, canvasWidth, canvasHeight]);
|
|
3268
3059
|
}
|
|
3060
|
+
|
|
3061
|
+
// src/index.ts
|
|
3062
|
+
var import_core19 = require("@visualli/core");
|
|
3063
|
+
|
|
3064
|
+
// src/components/KonvaNode.tsx
|
|
3065
|
+
var import_react18 = __toESM(require("react"), 1);
|
|
3066
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
3067
|
+
var KonvaNode = import_react18.default.memo(function KonvaNode2({
|
|
3068
|
+
node,
|
|
3069
|
+
zoomLevel,
|
|
3070
|
+
isExternallyHovered = false,
|
|
3071
|
+
isExternallyPressed = false,
|
|
3072
|
+
isSelected = false,
|
|
3073
|
+
isDragging = false,
|
|
3074
|
+
design = DEFAULT_DESIGN
|
|
3075
|
+
}) {
|
|
3076
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
3077
|
+
KShape,
|
|
3078
|
+
{
|
|
3079
|
+
listening: false,
|
|
3080
|
+
perfectDrawEnabled: false,
|
|
3081
|
+
sceneFunc: (ctx2) => {
|
|
3082
|
+
drawIdea(ctx2._context, node, design, zoomLevel, { ...IDLE, hovered: isExternallyHovered, pressed: isExternallyPressed, selected: isSelected }, {
|
|
3083
|
+
shadows: !isDragging
|
|
3084
|
+
});
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
);
|
|
3088
|
+
});
|
|
3089
|
+
var KonvaNode_default = KonvaNode;
|
|
3090
|
+
|
|
3091
|
+
// src/components/KonvaEdge.tsx
|
|
3092
|
+
var import_react19 = require("react");
|
|
3093
|
+
var import_core18 = require("@visualli/core");
|
|
3094
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
3095
|
+
var KonvaEdge = (0, import_react19.memo)(function KonvaEdge2({ sourceNode: from, targetNode: to, connection, zoomLevel = 1, design = DEFAULT_DESIGN }) {
|
|
3096
|
+
const g = (0, import_react19.useMemo)(
|
|
3097
|
+
() => prepareConnector((0, import_core18.connectorGeometry)(from, to, design.metrics.edgeGap)),
|
|
3098
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3099
|
+
[from.x, from.y, from.width, from.level, from.branchCount, to.x, to.y, to.width, to.level, to.branchCount, design.metrics.edgeGap]
|
|
3100
|
+
);
|
|
3101
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
3102
|
+
KShape,
|
|
3103
|
+
{
|
|
3104
|
+
listening: false,
|
|
3105
|
+
perfectDrawEnabled: false,
|
|
3106
|
+
sceneFunc: (ctx2) => {
|
|
3107
|
+
drawConnector(ctx2._context, g, design, zoomLevel, connection?.style === "dashed", connection?.label);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
);
|
|
3111
|
+
});
|
|
3112
|
+
var KonvaEdge_default = KonvaEdge;
|
|
3113
|
+
|
|
3114
|
+
// src/components/KonvaContainer.tsx
|
|
3115
|
+
var import_react20 = require("react");
|
|
3116
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
3117
|
+
var KonvaContainer = (0, import_react20.memo)(function KonvaContainer2({ nodes, label = "", zoomLevel = 1, design = DEFAULT_DESIGN }) {
|
|
3118
|
+
const placed = (0, import_react20.useMemo)(() => {
|
|
3119
|
+
const map = new Map(nodes.map((n) => [n.id, n]));
|
|
3120
|
+
return layoutContainers([{ id: "c", label: label || void 0, nodeIds: nodes.map((n) => n.id) }], map, [], design)[0] ?? null;
|
|
3121
|
+
}, [nodes, label, design]);
|
|
3122
|
+
if (!placed) return null;
|
|
3123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
3124
|
+
KShape,
|
|
3125
|
+
{
|
|
3126
|
+
listening: false,
|
|
3127
|
+
perfectDrawEnabled: false,
|
|
3128
|
+
sceneFunc: (ctx2) => {
|
|
3129
|
+
drawHull(ctx2._context, placed.hull, design);
|
|
3130
|
+
if (placed.pill) drawPill(ctx2._context, placed.pill, design, zoomLevel);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
);
|
|
3134
|
+
});
|
|
3135
|
+
var KonvaContainer_default = KonvaContainer;
|
|
3269
3136
|
//# sourceMappingURL=index.cjs.map
|