@visualli/react 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +335 -0
  2. package/dist/index.cjs +3262 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +375 -0
  5. package/dist/index.d.ts +375 -0
  6. package/dist/index.js +3289 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +48 -0
  9. package/src/VisualliCanvas.tsx +1087 -0
  10. package/src/VisualliRenderer.tsx +427 -0
  11. package/src/animations/index.ts +2 -0
  12. package/src/animations/konvaLayerTransition.ts +223 -0
  13. package/src/components/KonvaContainer.tsx +255 -0
  14. package/src/components/KonvaContainerLayer.tsx +63 -0
  15. package/src/components/KonvaEdge.tsx +254 -0
  16. package/src/components/KonvaEdgeLayer.tsx +72 -0
  17. package/src/components/KonvaNode.tsx +177 -0
  18. package/src/components/KonvaNodeLayer.tsx +61 -0
  19. package/src/components/KonvaStage.tsx +80 -0
  20. package/src/components/NavigationStack.tsx +184 -0
  21. package/src/components/SketchyBoxKonva.tsx +121 -0
  22. package/src/components/ZoomControls.tsx +110 -0
  23. package/src/config/blobShapes.ts +204 -0
  24. package/src/config/index.ts +21 -0
  25. package/src/config/textScaling.ts +69 -0
  26. package/src/context/VisualliContext.tsx +50 -0
  27. package/src/hooks/index.ts +7 -0
  28. package/src/hooks/useKonvaLayerTransition.ts +221 -0
  29. package/src/hooks/useKonvaRenderer.ts +185 -0
  30. package/src/hooks/useViewportNodes.ts +59 -0
  31. package/src/index.ts +81 -0
  32. package/src/konvaCompat.ts +29 -0
  33. package/src/stores/index.ts +3 -0
  34. package/src/stores/stores.ts +17 -0
  35. package/src/stores/useNodeStore.ts +101 -0
  36. package/src/stores/useViewportStore.ts +95 -0
  37. package/src/utils/index.ts +1 -0
  38. package/src/utils/layerNavigation.ts +15 -0
@@ -0,0 +1,221 @@
1
+ // ─── useKonvaLayerTransition ──────────────────────────────────────────────────
2
+ //
3
+ // Manages layer navigation animations (zoom into child layer, zoom back to
4
+ // parent). Uses the same callback-driven pattern as the visualli.ai reference:
5
+ // - onViewportChange: imperatively updates the Konva stage each rAF frame
6
+ // - onOpacityChange: writes directly to canvasWrapperRef DOM (no React state)
7
+ // - onBackgroundColorChange: writes directly to bgRef DOM
8
+ // - onPhaseChange('swap'|'zoom-back'): triggers React layer content swap
9
+ // - onComplete: syncs final viewport to Zustand and ends transition
10
+
11
+ import { useCallback, useRef, useEffect } from 'react';
12
+ import type Konva from 'konva';
13
+ import type { VisualliDocument, FlatNode } from '@visualli/core';
14
+ import { useViewportStore } from '../stores/useViewportStore';
15
+ import { konvaLayerTransitionAnimator } from '../animations/konvaLayerTransition';
16
+ import type { AnimatorViewport } from '../animations/konvaLayerTransition';
17
+ import { calculateFitView } from '../utils/layerNavigation';
18
+
19
+ export interface LayerTransitionOptions {
20
+ /** Ref to the Konva Stage for imperative position/scale updates each frame. */
21
+ stageRef: React.RefObject<Konva.Stage | null>;
22
+ /** Ref to the wrapping div whose CSS `opacity` is driven during transitions. */
23
+ canvasWrapperRef: React.RefObject<HTMLDivElement | null>;
24
+ /** Ref to the background color div. */
25
+ bgRef?: React.RefObject<HTMLDivElement | null>;
26
+ /** Called at the content-swap point — use to switch the displayed layer. */
27
+ onSwapLayer: (newLayerId: string) => void;
28
+ /** Called when a zoom-out swap happens — use to update the nav stack. */
29
+ onSwapBack: () => void;
30
+ /** Called after the transition fully completes. */
31
+ onComplete: () => void;
32
+ /**
33
+ * GPU CSS-transform mode: called each rAF frame instead of applyViewport.
34
+ * When provided, the caller is responsible for moving the canvas content.
35
+ */
36
+ onViewportFrame?: (vp: AnimatorViewport) => void;
37
+ /**
38
+ * Called once when the animation reaches its final viewport.
39
+ * When provided, replaces the default applyViewport call at completion.
40
+ */
41
+ onViewportFinal?: (vp: AnimatorViewport) => void;
42
+ /**
43
+ * Called at phase-change points ('swap', 'zoom-back') in addition to the
44
+ * internal content-swap logic. Use to reset CSS transforms before Konva redraws.
45
+ */
46
+ onPhaseChange?: (phase: string) => void;
47
+ /**
48
+ * Called with 'start' before the animator fires its first frame and with
49
+ * 'end' after onComplete. Use to set up / tear down GPU hints (willChange, etc.).
50
+ */
51
+ onTransitionLifecycle?: (phase: 'start' | 'end') => void;
52
+ }
53
+
54
+ export interface LayerTransitionHandlers {
55
+ zoomIntoLayer: (
56
+ doc: VisualliDocument,
57
+ targetNode: FlatNode,
58
+ childLayerId: string,
59
+ childNodes: FlatNode[],
60
+ fromColor: string,
61
+ toColor: string,
62
+ ) => void;
63
+ zoomOutToParent: (
64
+ parentViewport: AnimatorViewport,
65
+ fromColor: string,
66
+ toColor: string,
67
+ ) => void;
68
+ isTransitioning: () => boolean;
69
+ }
70
+
71
+ export function useKonvaLayerTransition(opts: LayerTransitionOptions): LayerTransitionHandlers {
72
+ const {
73
+ stageRef, canvasWrapperRef, bgRef,
74
+ onSwapLayer, onSwapBack, onComplete,
75
+ onViewportFrame, onViewportFinal, onPhaseChange: onPhaseChangeExt,
76
+ onTransitionLifecycle,
77
+ } = opts;
78
+ const setZoom = useViewportStore(s => s.setZoom);
79
+ const setCenter = useViewportStore(s => s.setCenter);
80
+ const getState = useViewportStore.getState;
81
+
82
+ const swappedRef = useRef(false);
83
+ const canvasSizeRef = useRef({ width: 800, height: 600 });
84
+
85
+ // Keep canvas size ref fresh from viewport store (updated by ResizeObserver in useKonvaRenderer)
86
+ useEffect(() => {
87
+ const update = () => {
88
+ const vp = useViewportStore.getState();
89
+ canvasSizeRef.current = { width: vp.canvasWidth || 800, height: vp.canvasHeight || 600 };
90
+ };
91
+ update();
92
+ // Subscribe to viewport store changes instead of window resize
93
+ const unsubscribe = useViewportStore.subscribe(update);
94
+ return unsubscribe;
95
+ }, []);
96
+
97
+ // Apply viewport to Konva stage imperatively (no React re-render per frame)
98
+ const applyViewport = useCallback((vp: AnimatorViewport) => {
99
+ const stage = stageRef.current;
100
+ if (!stage) return;
101
+ const { width, height } = canvasSizeRef.current;
102
+ stage.x(width / 2 - vp.centerX * vp.zoomLevel);
103
+ stage.y(height / 2 - vp.centerY * vp.zoomLevel);
104
+ stage.scaleX(vp.zoomLevel);
105
+ stage.scaleY(vp.zoomLevel);
106
+ stage.getLayers().forEach(l => l.drawScene());
107
+ // Sync to Zustand so viewport store reflects reality
108
+ setCenter(vp.centerX, vp.centerY);
109
+ setZoom(vp.zoomLevel);
110
+ }, [stageRef, setCenter, setZoom]);
111
+
112
+ const applyOpacity = useCallback((opacity: number) => {
113
+ if (canvasWrapperRef.current) {
114
+ canvasWrapperRef.current.style.opacity = String(opacity);
115
+ }
116
+ }, [canvasWrapperRef]);
117
+
118
+ const applyBgColor = useCallback((color: string) => {
119
+ if (bgRef?.current) {
120
+ bgRef.current.style.backgroundColor = color;
121
+ }
122
+ }, [bgRef]);
123
+
124
+ const zoomIntoLayer = useCallback((
125
+ _doc: VisualliDocument,
126
+ targetNode: FlatNode,
127
+ childLayerId: string,
128
+ childNodes: FlatNode[],
129
+ fromColor: string,
130
+ toColor: string,
131
+ ): void => {
132
+ if (konvaLayerTransitionAnimator.active) return;
133
+
134
+ const vp = getState();
135
+ const stage = stageRef.current;
136
+ const w = stage?.width() ?? 800;
137
+ const h = stage?.height() ?? 600;
138
+ const fit = calculateFitView(childNodes, w, h);
139
+
140
+ swappedRef.current = false;
141
+
142
+ onTransitionLifecycle?.('start');
143
+ konvaLayerTransitionAnimator.animateZoomIntoLayer(
144
+ { centerX: vp.centerX, centerY: vp.centerY, zoomLevel: vp.zoomLevel },
145
+ { x: targetNode.x, y: targetNode.y, width: targetNode.width, height: targetNode.height },
146
+ { x: fit.centerX, y: fit.centerY },
147
+ fromColor,
148
+ toColor,
149
+ {
150
+ onUpdate: ({ viewport, opacity, backgroundColor }) => {
151
+ if (onViewportFrame) { onViewportFrame(viewport); } else { applyViewport(viewport); }
152
+ applyOpacity(opacity);
153
+ applyBgColor(backgroundColor);
154
+ },
155
+ onComplete: () => {
156
+ const finalVp = { centerX: fit.centerX, centerY: fit.centerY, zoomLevel: fit.zoomLevel };
157
+ if (onViewportFinal) { onViewportFinal(finalVp); } else { applyViewport(finalVp); }
158
+ applyOpacity(1);
159
+ onComplete();
160
+ onTransitionLifecycle?.('end');
161
+ },
162
+ onPhaseChange: (phase) => {
163
+ onPhaseChangeExt?.(phase);
164
+ if (phase === 'swap' && !swappedRef.current) {
165
+ swappedRef.current = true;
166
+ onSwapLayer(childLayerId);
167
+ }
168
+ },
169
+ },
170
+ fit.zoomLevel,
171
+ w,
172
+ h,
173
+ );
174
+ }, [stageRef, getState, applyViewport, applyOpacity, applyBgColor, onSwapLayer, onComplete, onViewportFrame, onViewportFinal, onPhaseChangeExt, onTransitionLifecycle]);
175
+
176
+ const zoomOutToParent = useCallback((
177
+ parentViewport: AnimatorViewport,
178
+ fromColor: string,
179
+ toColor: string,
180
+ ): void => {
181
+ if (konvaLayerTransitionAnimator.active) return;
182
+
183
+ const vp = getState();
184
+ swappedRef.current = false;
185
+
186
+ onTransitionLifecycle?.('start');
187
+ konvaLayerTransitionAnimator.animateZoomOutToParent(
188
+ { centerX: vp.centerX, centerY: vp.centerY, zoomLevel: vp.zoomLevel },
189
+ parentViewport,
190
+ fromColor,
191
+ toColor,
192
+ {
193
+ onUpdate: ({ viewport, opacity, backgroundColor }) => {
194
+ if (onViewportFrame) { onViewportFrame(viewport); } else { applyViewport(viewport); }
195
+ applyOpacity(opacity);
196
+ applyBgColor(backgroundColor);
197
+ },
198
+ onComplete: () => {
199
+ if (onViewportFinal) { onViewportFinal(parentViewport); } else { applyViewport(parentViewport); }
200
+ applyOpacity(1);
201
+ onComplete();
202
+ onTransitionLifecycle?.('end');
203
+ },
204
+ onPhaseChange: (phase) => {
205
+ onPhaseChangeExt?.(phase);
206
+ if (phase === 'zoom-back' && !swappedRef.current) {
207
+ swappedRef.current = true;
208
+ onSwapBack();
209
+ }
210
+ },
211
+ },
212
+ );
213
+ }, [getState, applyViewport, applyOpacity, applyBgColor, onSwapBack, onComplete, onViewportFrame, onViewportFinal, onPhaseChangeExt, onTransitionLifecycle]);
214
+
215
+ const isTransitioning = useCallback(() => konvaLayerTransitionAnimator.active, []);
216
+
217
+ // Cancel on unmount
218
+ useEffect(() => () => { konvaLayerTransitionAnimator.cancel(); }, []);
219
+
220
+ return { zoomIntoLayer, zoomOutToParent, isTransitioning };
221
+ }
@@ -0,0 +1,185 @@
1
+ // ─── useKonvaRenderer ─────────────────────────────────────────────────────────
2
+ //
3
+ // Manages Konva rendering lifecycle: resize, pan, zoom, and FPS monitoring.
4
+ // Returns Konva-level event handlers to be wired onto KonvaStage.
5
+ //
6
+ // Design matches visualli.ai's useKonvaRenderer:
7
+ // - handleWheel / handleMouseDown / handleMouseMove / handleMouseUp are
8
+ // Konva.KonvaEventObject handlers (not DOM-level)
9
+ // - Pan is applied directly to the stage during drag for 60 fps, flushed to
10
+ // Zustand store on mouseUp (no per-frame state updates)
11
+ // - Zoom targets the cursor position (same math as visualli.ai)
12
+
13
+ import { useEffect, useCallback, useRef, useState } from 'react';
14
+ import type Konva from 'konva';
15
+ import { ZOOM_MIN, ZOOM_MAX } from '@visualli/core';
16
+ import { useViewportStore } from '../stores/useViewportStore';
17
+ import { useRenderConfigStore } from '../stores/stores';
18
+
19
+ export interface UseKonvaRendererOptions {
20
+ containerRef: React.RefObject<HTMLDivElement | null>;
21
+ stageRef?: React.RefObject<Konva.Stage | null>;
22
+ enabled?: boolean;
23
+ }
24
+
25
+ export interface UseKonvaRendererReturn {
26
+ handleWheel: (e: Konva.KonvaEventObject<WheelEvent>) => void;
27
+ handleMouseDown:(e: Konva.KonvaEventObject<MouseEvent>) => void;
28
+ handleMouseMove:(e: Konva.KonvaEventObject<MouseEvent>) => void;
29
+ handleMouseUp: (e: Konva.KonvaEventObject<MouseEvent>) => void;
30
+ canvasWidth: number;
31
+ canvasHeight: number;
32
+ }
33
+
34
+ export function useKonvaRenderer({
35
+ containerRef,
36
+ stageRef,
37
+ enabled = true,
38
+ }: UseKonvaRendererOptions): UseKonvaRendererReturn {
39
+ const pan = useViewportStore(s => s.pan);
40
+ const zoom = useViewportStore(s => s.zoom);
41
+ const setZoom = useViewportStore(s => s.setZoom);
42
+ const setCenter = useViewportStore(s => s.setCenter);
43
+ const updateCanvasSize = useViewportStore(s => s.updateCanvasSize);
44
+ const autoAdjust = useRenderConfigStore(s => s.autoAdjustQuality);
45
+
46
+ const [canvasWidth, setCanvasWidth] = useState(0);
47
+ const [canvasHeight, setCanvasHeight] = useState(0);
48
+ const cachedSizeRef = useRef({ width: 0, height: 0 });
49
+
50
+ const isPanningRef = useRef(false);
51
+ const lastPanPointRef = useRef({ x: 0, y: 0 });
52
+ const panAccumRef = useRef({ x: 0, y: 0 });
53
+
54
+ // ── FPS monitor ──────────────────────────────────────────────────────────────
55
+ const fpsRef = useRef({ frames: 0, last: performance.now() });
56
+ const fpsRafId = useRef<number | null>(null);
57
+
58
+ useEffect(() => {
59
+ if (!enabled) return;
60
+ const tick = (now: number): void => {
61
+ fpsRef.current.frames++;
62
+ if (now - fpsRef.current.last >= 2000) {
63
+ const fps = (fpsRef.current.frames / ((now - fpsRef.current.last) / 1000)) | 0;
64
+ autoAdjust(fps);
65
+ fpsRef.current = { frames: 0, last: now };
66
+ }
67
+ fpsRafId.current = requestAnimationFrame(tick);
68
+ };
69
+ fpsRafId.current = requestAnimationFrame(tick);
70
+ return () => { if (fpsRafId.current !== null) cancelAnimationFrame(fpsRafId.current); };
71
+ }, [enabled, autoAdjust]);
72
+
73
+ // ── Resize ────────────────────────────────────────────────────────────────────
74
+ // Observes the container element (not window) so the canvas always fills
75
+ // its parent, regardless of how the renderer is sized by the consuming app.
76
+ useEffect(() => {
77
+ if (!enabled) return;
78
+
79
+ let rafId: number | null = null;
80
+
81
+ const measure = () => {
82
+ const el = containerRef.current;
83
+ const w = el ? Math.round(el.getBoundingClientRect().width) : 800;
84
+ const h = el ? Math.round(el.getBoundingClientRect().height) : 600;
85
+ return { w, h };
86
+ };
87
+
88
+ const updateSize = () => {
89
+ if (rafId) cancelAnimationFrame(rafId);
90
+ rafId = requestAnimationFrame(() => {
91
+ rafId = null;
92
+ const { w, h } = measure();
93
+ if (w > 0 && h > 0 &&
94
+ (cachedSizeRef.current.width !== w || cachedSizeRef.current.height !== h)) {
95
+ cachedSizeRef.current = { width: w, height: h };
96
+ setCanvasWidth(w);
97
+ setCanvasHeight(h);
98
+ updateCanvasSize(w, h);
99
+ }
100
+ });
101
+ };
102
+
103
+ // Initial measurement
104
+ updateSize();
105
+
106
+ // ResizeObserver tracks the container itself (handles % / flex / grid sizing)
107
+ let ro: ResizeObserver | null = null;
108
+ if (typeof ResizeObserver !== 'undefined' && containerRef.current) {
109
+ ro = new ResizeObserver(updateSize);
110
+ ro.observe(containerRef.current);
111
+ } else {
112
+ // Fallback: window resize only
113
+ window.addEventListener('resize', updateSize);
114
+ }
115
+
116
+ return () => {
117
+ ro ? ro.disconnect() : window.removeEventListener('resize', updateSize);
118
+ if (rafId) cancelAnimationFrame(rafId);
119
+ };
120
+ }, [enabled, containerRef, updateCanvasSize]);
121
+
122
+ // ── Wheel zoom ────────────────────────────────────────────────────────────────
123
+ const handleWheel = useCallback((e: Konva.KonvaEventObject<WheelEvent>): void => {
124
+ e.evt.preventDefault();
125
+ const stage = e.target.getStage();
126
+ if (!stage) return;
127
+ const pointer = stage.getPointerPosition();
128
+ if (!pointer) return;
129
+ const delta = -e.evt.deltaY / 1000;
130
+ zoom(delta, pointer.x, pointer.y);
131
+ }, [zoom]);
132
+
133
+ // ── Mouse pan ─────────────────────────────────────────────────────────────────
134
+ const handleMouseDown = useCallback((e: Konva.KonvaEventObject<MouseEvent>): void => {
135
+ const clickedOnEmpty = e.target === e.target.getStage();
136
+ if (clickedOnEmpty && e.evt.button === 0) {
137
+ e.evt.preventDefault();
138
+ isPanningRef.current = true;
139
+ lastPanPointRef.current = { x: e.evt.clientX, y: e.evt.clientY };
140
+ panAccumRef.current = { x: 0, y: 0 };
141
+ const stage = e.target.getStage();
142
+ if (stage) stage.container().style.cursor = 'grabbing';
143
+ }
144
+ }, []);
145
+
146
+ const handleMouseMove = useCallback((e: Konva.KonvaEventObject<MouseEvent>): void => {
147
+ if (!isPanningRef.current) {
148
+ // Update cursor on empty canvas
149
+ const isOverEmpty = e.target === e.target.getStage();
150
+ const stage = e.target.getStage();
151
+ if (stage) stage.container().style.cursor = isOverEmpty ? 'grab' : 'default';
152
+ return;
153
+ }
154
+ e.evt.preventDefault();
155
+ const deltaX = e.evt.clientX - lastPanPointRef.current.x;
156
+ const deltaY = e.evt.clientY - lastPanPointRef.current.y;
157
+
158
+ // Apply pan directly to stage (no Zustand overhead per-frame)
159
+ const stage = e.target.getStage();
160
+ if (stage) {
161
+ stage.x(stage.x() + deltaX);
162
+ stage.y(stage.y() + deltaY);
163
+ stage.batchDraw();
164
+ }
165
+
166
+ panAccumRef.current.x += deltaX;
167
+ panAccumRef.current.y += deltaY;
168
+ lastPanPointRef.current = { x: e.evt.clientX, y: e.evt.clientY };
169
+ }, []); // No Zustand dep — pan flushed on mouseUp
170
+
171
+ const handleMouseUp = useCallback((e: Konva.KonvaEventObject<MouseEvent>): void => {
172
+ if (isPanningRef.current) {
173
+ isPanningRef.current = false;
174
+ const { x, y } = panAccumRef.current;
175
+ if (x !== 0 || y !== 0) {
176
+ pan(x, y);
177
+ panAccumRef.current = { x: 0, y: 0 };
178
+ }
179
+ const stage = e.target.getStage();
180
+ if (stage) stage.container().style.cursor = 'grab';
181
+ }
182
+ }, [pan]);
183
+
184
+ return { handleWheel, handleMouseDown, handleMouseMove, handleMouseUp, canvasWidth, canvasHeight };
185
+ }
@@ -0,0 +1,59 @@
1
+ // ─── useViewportNodes ─────────────────────────────────────────────────────────
2
+ //
3
+ // Returns the subset of nodes currently visible in the viewport.
4
+ // Signature: useViewportNodes(nodes, levelFilter?, skipCulling?)
5
+ //
6
+ // When skipCulling=true (e.g. during layer transitions) all nodes are returned
7
+ // so the full layer is rendered when opacity ramps in.
8
+
9
+ import { useMemo, useRef } from 'react';
10
+ import type { FlatNode } from '@visualli/core';
11
+ import { RBushSpatialIndex, getViewportVisibleNodes } from '@visualli/core';
12
+ import { useViewportStore } from '../stores/useViewportStore';
13
+
14
+ const SPATIAL_INDEX_THRESHOLD = 200;
15
+
16
+ export function useViewportNodes(
17
+ nodes: FlatNode[],
18
+ levelFilter?: number,
19
+ skipCulling = false,
20
+ /** When true, skip culling so dragged nodes are always visible and the
21
+ * spatial index (keyed on layout positions) is not consulted. */
22
+ isDragging = false,
23
+ ): FlatNode[] {
24
+ const { centerX, centerY, zoomLevel, canvasWidth, canvasHeight } = useViewportStore();
25
+
26
+ const indexRef = useRef<RBushSpatialIndex | null>(null);
27
+ const nodesHashRef = useRef<number>(0);
28
+ const nodesHash = nodes.length;
29
+
30
+ if (nodesHash !== nodesHashRef.current) {
31
+ nodesHashRef.current = nodesHash;
32
+ indexRef.current = null; // rebuild below
33
+ }
34
+
35
+ return useMemo(() => {
36
+ if (nodes.length === 0) return [];
37
+
38
+ if (!indexRef.current && nodes.length >= SPATIAL_INDEX_THRESHOLD && !skipCulling && !isDragging) {
39
+ indexRef.current = new RBushSpatialIndex();
40
+ indexRef.current.bulkLoad(nodes.map(n => ({
41
+ nodeId: n.id,
42
+ bounds: { minX: n.x, minY: n.y, maxX: n.x + n.width, maxY: n.y + n.height },
43
+ })));
44
+ }
45
+
46
+ return getViewportVisibleNodes({
47
+ nodes,
48
+ viewport: { centerX, centerY, zoomLevel, rotation: 0, visibleBounds: { minX: 0, minY: 0, maxX: 0, maxY: 0 } },
49
+ canvasWidth,
50
+ canvasHeight,
51
+ levelFilter,
52
+ skipCulling,
53
+ isDragging,
54
+ spatialIndex: indexRef.current,
55
+ spatialIndexThreshold: SPATIAL_INDEX_THRESHOLD,
56
+ });
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ }, [nodes, levelFilter, skipCulling, isDragging, centerX, centerY, zoomLevel, canvasWidth, canvasHeight]);
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1,81 @@
1
+ // ─── @visualli/react ─────────────────────────────────────────────────────────
2
+
3
+ // ── Primary component (recommended for public use) ────────────────────────────────────────
4
+ export { default as VisualliRenderer } from './VisualliRenderer';
5
+ export type { VisualliRendererProps, VisualliTheme } from './VisualliRenderer';
6
+
7
+ // ── Canvas component (for advanced integrations) ───────────────────────────────────────────
8
+ export { default as VisualliCanvas } from './VisualliCanvas';
9
+ export type { VisualliCanvasProps } from './VisualliCanvas';
10
+
11
+ // ── Context and Provider ──────────────────────────────────────────────────────
12
+ export { VisualliProvider, useVisualli } from './context/VisualliContext';
13
+ export type {
14
+ VisualliProviderProps,
15
+ VisualliContextValue
16
+ } from './context/VisualliContext';
17
+
18
+ // Stores
19
+ export { useNodeStore } from './stores/useNodeStore';
20
+ export { useViewportStore } from './stores/useViewportStore';
21
+ export { useSelectionStore, useRenderConfigStore } from './stores/stores';
22
+ export type { INodeStore } from './stores/useNodeStore';
23
+ export type { IViewportStore } from './stores/useViewportStore';
24
+
25
+ // Hooks
26
+ export { useKonvaRenderer } from './hooks/useKonvaRenderer';
27
+ export { useKonvaLayerTransition } from './hooks/useKonvaLayerTransition';
28
+ export { useViewportNodes } from './hooks/useViewportNodes';
29
+ export type { UseKonvaRendererOptions, UseKonvaRendererReturn } from './hooks/useKonvaRenderer';
30
+ export type { LayerTransitionHandlers } from './hooks/useKonvaLayerTransition';
31
+
32
+ // Animations
33
+ export { KonvaLayerTransitionAnimator, konvaLayerTransitionAnimator } from './animations/konvaLayerTransition';
34
+ export type { AnimatorViewport, TransitionCallbacks } from './animations/konvaLayerTransition';
35
+
36
+ // Config helpers
37
+ export {
38
+ ALL_BLOB_SHAPES,
39
+ ACTIVE_BLOB_TYPES,
40
+ BLOB_LAYER_CONFIG,
41
+ NODE_LAYER_CONFIG,
42
+ getBlobTypeForLayer,
43
+ BLOB_TEXT_OFFSETS,
44
+ drawBlobPath,
45
+ buildBlobPathData,
46
+ NODE_TEXT_BASE_FONT_PX,
47
+ DESCRIPTION_TEXT_BASE_FONT_PX,
48
+ computeNodeTextWorldScale,
49
+ computeNodeTextScreenScale,
50
+ computeOverlayScale,
51
+ computeEdgeLabelScale,
52
+ } from './config';
53
+
54
+ // Utils
55
+ export {
56
+ getConnectionsForLayer,
57
+ getChildLayerForNode,
58
+ getLayerForNavigation,
59
+ calculateFitZoom,
60
+ calculateFitCenter,
61
+ calculateFitView,
62
+ } from './utils/layerNavigation';
63
+ export type { FitResult } from './utils/layerNavigation';
64
+
65
+ // Sub-components (for advanced usage / composition)
66
+ export { default as KonvaStage } from './components/KonvaStage';
67
+ export { default as KonvaNode } from './components/KonvaNode';
68
+ export { default as KonvaEdge } from './components/KonvaEdge';
69
+ export { default as KonvaNodeLayer } from './components/KonvaNodeLayer';
70
+ export { default as KonvaEdgeLayer } from './components/KonvaEdgeLayer';
71
+ export { default as KonvaContainer } from './components/KonvaContainer';
72
+ export { default as KonvaContainerLayer } from './components/KonvaContainerLayer';
73
+ export { default as NavigationStack } from './components/NavigationStack';
74
+ export { default as ZoomControls } from './components/ZoomControls';
75
+ export { default as SketchyBoxKonva } from './components/SketchyBoxKonva';
76
+ export type { NavStackEntry } from './components/NavigationStack';
77
+ export type { KonvaNodeProps } from './components/KonvaNode';
78
+ export type { KonvaEdgeProps } from './components/KonvaEdge';
79
+ export type { KonvaStageProps } from './components/KonvaStage';
80
+ export type { ContainerGroup } from './components/KonvaContainerLayer';
81
+
@@ -0,0 +1,29 @@
1
+ // ─── konvaCompat ──────────────────────────────────────────────────────────────
2
+ //
3
+ // react-konva@18.x defines KonvaNodeComponent with return type
4
+ // `ReactNode | Promise<ReactNode>`. @types/react v19 tightened the JSX checker
5
+ // so that signature no longer satisfies `(props: any) => ReactNode`, causing
6
+ // TS2786 errors everywhere a Konva primitive is used as JSX.
7
+ //
8
+ // The fix: re-export every Konva primitive cast to React.ComponentType<any>.
9
+ // This is purely a type-level shim — no runtime behaviour changes.
10
+
11
+ import {
12
+ Stage, Layer, Group, Shape, Path, Rect, Text, Arrow, Line, Circle, TextPath,
13
+ } from 'react-konva';
14
+ import type React from 'react';
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ type C = React.ComponentType<any>;
18
+
19
+ export const KStage = Stage as unknown as C;
20
+ export const KLayer = Layer as unknown as C;
21
+ export const KGroup = Group as unknown as C;
22
+ export const KShape = Shape as unknown as C;
23
+ export const KPath = Path as unknown as C;
24
+ export const KRect = Rect as unknown as C;
25
+ export const KText = Text as unknown as C;
26
+ export const KArrow = Arrow as unknown as C;
27
+ export const KLine = Line as unknown as C;
28
+ export const KCircle = Circle as unknown as C;
29
+ export const KTextPath = TextPath as unknown as C;
@@ -0,0 +1,3 @@
1
+ export * from './useNodeStore.js';
2
+ export * from './useViewportStore.js';
3
+ export * from './stores.js';
@@ -0,0 +1,17 @@
1
+ // ─── Selection Store ──────────────────────────────────────────────────────────
2
+
3
+ import { create } from 'zustand';
4
+ import { selectionStore, renderConfigStore } from '@visualli/core';
5
+ import type { ISelectionStore, IRenderConfigStore } from '@visualli/core';
6
+
7
+ export const useSelectionStore = create<ISelectionStore>((set, get, store) => {
8
+ selectionStore.subscribe((state) => set(state));
9
+ return selectionStore.getState();
10
+ });
11
+
12
+ // ─── Render Config Store ──────────────────────────────────────────────────────
13
+
14
+ export const useRenderConfigStore = create<IRenderConfigStore>((set, get, store) => {
15
+ renderConfigStore.subscribe((state) => set(state));
16
+ return renderConfigStore.getState();
17
+ });