@visualli/react 0.1.1 → 0.1.2

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 CHANGED
@@ -1,20 +1,18 @@
1
1
  # @visualli/react
2
2
 
3
- React canvas rendering for Visualli — powered by Konva. Drop-in component that displays a `VisualliDocument` as an interactive, zoomable, navigable mind-map.
3
+ React canvas rendering for Visualli — powered by Konva. A high-performance component suite that displays `.visualli` documents as interactive, zoomable, and navigable mind-maps.
4
4
 
5
5
  ## Features
6
6
 
7
- - **GPU-accelerated canvas** via `react-konva` — handles thousands of nodes at 60 fps
8
- - **Organic blob nodes** — 6 quadratic-bezier blob shapes that cycle by level
9
- - **Layer navigation** — double-click any node to drill into its child layer, breadcrumb back
10
- - **Animated transitions** — rAF-driven zoom-into-layer / zoom-out-to-parent with color crossfade
11
- - **Zoom controls** — +/− buttons, %, fit-to-screen
12
- - **Viewport culling** — RBush spatial index keeps only visible nodes on the canvas
13
- - **Zustand stores** — fine-grained subscriptions for nodes, viewport, selection, render config
14
- - **Light / dark theme** — single `isDark` prop
15
- - **🆕 Extension system** — inject custom parser middlewares and UI components at runtime
16
- - **🆕 Stream fetching** — `useVisualliStream` hook for backend JSONL streams
17
- - **🆕 Context provider** — `VisualliProvider` for dependency injection
7
+ - **High-Performance Canvas** — GPU-accelerated rendering via `react-konva` capable of handling thousands of nodes at 60fps
8
+ - **Organic Visuals** — Dynamic blob-based node shapes that cycle by hierarchy level
9
+ - **Layer Navigation** — Drill down into child layers and navigate back via breadcrumb UI
10
+ - **Smooth Transitions** — Animated zoom effects with color crossfading during layer changes
11
+ - **Chromatic Immersion** — Optional background effects that adapt to the current layer's context
12
+ - **Viewport Culling** — Spatial indexing ensures only visible elements are rendered
13
+ - **Worker-based Parsing** — Parse large documents off the main thread to prevent UI blocking
14
+ - **Flexible Theming** — Light, Dark, and System-aware (auto) theme support
15
+ - **State Management** — Fine-grained control via Zustand stores for viewport, nodes, and selection
18
16
 
19
17
  ## Installation
20
18
 
@@ -22,293 +20,151 @@ React canvas rendering for Visualli — powered by Konva. Drop-in component that
22
20
  npm install @visualli/react @visualli/core konva react-konva zustand
23
21
  ```
24
22
 
25
- Peer dependencies: `react@^18`, `react-dom@^18`
23
+ **Peer dependencies:** `react@^18`, `react-dom@^18`
26
24
 
27
25
  ## Quick Start
28
26
 
29
- ```tsx
30
- import { VisualliCanvas } from '@visualli/react';
31
-
32
- // Option A — pass a pre-parsed VisualliDocument
33
- import { parseVisualliFile } from '@visualli/core';
34
-
35
- const doc = parseVisualliFile(rawJsonlString);
27
+ Use `VisualliRenderer` for the simplest integration — it handles loading, parsing, error states, and responsive sizing automatically.
36
28
 
37
- export default function App() {
38
- return (
39
- <div style={{ width: '100vw', height: '100vh' }}>
40
- <VisualliCanvas document={doc} isDark={false} />
41
- </div>
42
- );
43
- }
29
+ ```tsx
30
+ import { VisualliRenderer } from '@visualli/react';
44
31
 
45
- // Option B — pass the raw JSONL string directly
46
32
  export default function App() {
47
33
  return (
48
- <div style={{ width: '100vw', height: '100vh' }}>
49
- <VisualliCanvas visualliString={rawJsonlString} isDark={true} />
50
- </div>
34
+ <VisualliRenderer
35
+ visualliFile="/data/mindmap.visualli"
36
+ theme="auto"
37
+ width="100%"
38
+ height="100vh"
39
+ />
51
40
  );
52
41
  }
53
42
  ```
54
43
 
55
- ## `VisualliCanvas` Props
44
+ ### Alternative: Pass Raw JSONL String
56
45
 
57
- | Prop | Type | Default | Description |
58
- |------|------|---------|-------------|
59
- | `document` | `VisualliDocument` | — | Pre-parsed document |
60
- | `visualliString` | `string` | — | Raw JSONL — parsed internally |
61
- | `isDark` | `boolean` | `false` | Light / dark canvas theme |
62
- | `onNodeClick` | `(node: FlatNode) => void` | — | Single-click callback |
63
- | `onLayerChange` | `(id: string, layer: VisualliLayer) => void` | — | Fired after navigation |
64
- | `className` | `string` | `''` | Extra CSS classes on the wrapper div |
65
- | `style` | `React.CSSProperties` | — | Inline styles on the wrapper div |
66
-
67
- > The component fills its parent container — set an explicit `width` / `height` on the wrapper.
68
-
69
- ## Architecture
70
-
71
- ```
72
- VisualliCanvas
73
- ├── KonvaStage react-konva <Stage>, position/scale from viewport store
74
- │ ├── KonvaContainerLayer <Layer> — convex-hull outlines (non-interactive)
75
- │ ├── KonvaEdgeLayer <Layer> — bezier edges between visible nodes
76
- │ └── KonvaNodeLayer <Layer> — blob nodes, handles click/dblclick
77
- ├── NavigationStack DOM overlay — breadcrumb trail, click to go back
78
- └── ZoomControls DOM overlay — +/−/% buttons, fit-to-screen
46
+ ```tsx
47
+ <VisualliRenderer
48
+ visualliString={rawJsonlString}
49
+ theme="dark"
50
+ chromaticImmersion={true}
51
+ />
79
52
  ```
80
53
 
81
- ### Stores (Zustand)
82
-
83
- Access any store directly for advanced use cases:
84
-
85
- ```ts
86
- import { useViewportStore, useNodeStore, useSelectionStore } from '@visualli/react';
87
-
88
- // Read viewport
89
- const { centerX, centerY, zoomLevel } = useViewportStore();
54
+ ### Alternative: Load from File Input
90
55
 
91
- // Programmatic zoom
92
- useViewportStore.getState().setZoom(1.5);
93
- useViewportStore.getState().setCenter(0, 0);
56
+ ```tsx
57
+ function FileUploader() {
58
+ const [file, setFile] = useState<File | null>(null);
94
59
 
95
- // Read selected node
96
- const selectedId = useSelectionStore(s => s.selectedId);
60
+ return (
61
+ <>
62
+ <input
63
+ type="file"
64
+ accept=".visualli"
65
+ onChange={(e) => setFile(e.target.files?.[0] || null)}
66
+ />
67
+ {file && <VisualliRenderer visualliFile={file} theme="light" />}
68
+ </>
69
+ );
70
+ }
97
71
  ```
98
72
 
99
- ### Hooks
73
+ ## API Reference
100
74
 
101
- ```ts
102
- import { useViewportNodes } from '@visualli/react';
75
+ ### `VisualliRenderer` (Recommended)
103
76
 
104
- // Get nodes currently visible in the viewport (culled)
105
- const visible = useViewportNodes(allNodes, /* optional level filter */ 0);
106
- ```
107
-
108
- ### Navigation Stack
77
+ The primary component for rendering `.visualli` documents. Manages the full document lifecycle including loading, parsing (with optional Web Worker), error handling, and responsive layout.
109
78
 
110
- Layer navigation is fully internal but observable via the `onLayerChange` callback. The breadcrumb UI renders automatically — no props required.
79
+ #### Props
111
80
 
112
- Drill-in: **double-click** a node that has a child layer.
113
- Back: click any crumb in the breadcrumb bar, or use `onNavigateBack` exposed by `NavigationStack` directly.
81
+ | Prop | Type | Default | Description |
82
+ |------|------|---------|-------------|
83
+ | `visualliFile` | `File \| string` | — | A `.visualli` file as a File object or URL path |
84
+ | `visualliString` | `string` | — | Raw JSONL content as a string |
85
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'light'` | Color theme. `'auto'` follows system preference |
86
+ | `chromaticImmersion` | `boolean` | `false` | Enable background color effects based on layer context |
87
+ | `useWorker` | `boolean` | `true` | Parse documents in a Web Worker (recommended for large files) |
88
+ | `width` | `string \| number` | `'100%'` | Width as CSS value or pixel number |
89
+ | `height` | `string \| number` | `'100%'` | Height as CSS value or pixel number |
90
+ | `className` | `string` | — | Additional CSS classes |
91
+ | `style` | `React.CSSProperties` | — | Inline styles |
114
92
 
115
- ## Extension System 🆕
93
+ > **Note:** Provide either `visualliFile` or `visualliString`, not both.
116
94
 
117
- The extension system allows you to inject custom parser middlewares and UI components at runtime without modifying the SDK.
95
+ ### `VisualliCanvas` (Advanced)
118
96
 
119
- ### Basic Setup with Provider
97
+ The low-level canvas component used internally by `VisualliRenderer`. Use this if you need direct control over the rendering pipeline or already have a parsed `VisualliDocument`.
120
98
 
121
- ```tsx
122
- import { VisualliProvider, VisualliCanvas } from '@visualli/react';
99
+ #### Props
123
100
 
124
- function App() {
125
- return (
126
- <VisualliProvider>
127
- <VisualliCanvas document={document} />
128
- </VisualliProvider>
129
- );
130
- }
101
+ | Prop | Type | Description |
102
+ |------|------|-------------|
103
+ | `preParsedVisualli` | `VisualliDocument` | Pre-parsed document from `@visualli/core` |
104
+ | `visualliString` | `string` | Raw JSONL string (parsed on mount) |
105
+ | `isDark` | `boolean` | Dark mode flag |
106
+ | `chromaticImmersion` | `boolean` | Enable chromatic background |
107
+ | `onNodeClick` | `(node: FlatNode) => void` | Callback fired on node click |
108
+ | `onLayerChange` | `(id: string, layer: VisualliLayer) => void` | Callback fired on layer navigation |
131
109
 
132
- ### With Custom Middleware
110
+ #### Example
133
111
 
134
112
  ```tsx
135
- import { VisualliProvider } from '@visualli/react';
136
- import type { ParserMiddleware } from '@visualli/core';
113
+ import { VisualliCanvas } from '@visualli/react';
114
+ import { parseVisualliFile } from '@visualli/core';
137
115
 
138
- const myMiddleware: ParserMiddleware = (data) => {
139
- if (data.type === 'extension') {
140
- return { ...data, enhanced: true };
141
- }
142
- return data;
143
- };
116
+ const document = parseVisualliFile(jsonlString);
144
117
 
145
- <VisualliProvider middlewares={[myMiddleware]}>
146
- <App />
147
- </VisualliProvider>
118
+ <VisualliCanvas
119
+ preParsedVisualli={document}
120
+ isDark={true}
121
+ onNodeClick={(node) => console.log('Clicked:', node.data.label)}
122
+ />
148
123
  ```
149
124
 
150
- ### With Extension Components
125
+ ## Customization & Extensions
151
126
 
152
- ```tsx
153
- import type { ExtensionComponentProps } from '@visualli/react';
127
+ The SDK is designed to be extensible. While the core renderer provides a complete visualization experience, you can extend it with custom UI overlays, data processing pipelines, or application-specific behaviors without modifying the core library. This allows developers to build specialized features on top of the base canvas while maintaining upgrade compatibility.
154
128
 
155
- function MyExtension({ extension, document }: ExtensionComponentProps) {
156
- return (
157
- <div style={{ position: 'absolute', top: 20, right: 20 }}>
158
- <p>{extension.data?.message}</p>
159
- </div>
160
- );
161
- }
162
-
163
- const extensions = {
164
- 'my-ext-id': MyExtension,
165
- };
129
+ ## State Management
166
130
 
167
- <VisualliProvider extensions={extensions}>
168
- <VisualliCanvas document={document} />
169
- </VisualliProvider>
170
- ```
171
-
172
- ### Stream Fetching
131
+ The renderer uses Zustand stores for reactive state management. You can access these stores for advanced use cases like programmatic navigation or custom UI controls.
173
132
 
174
133
  ```tsx
175
- import { useVisualliStream, VisualliCanvas } from '@visualli/react';
176
-
177
- function MindMapViewer({ apiUrl }: { apiUrl: string }) {
178
- const { document, isLoading, error, progress } = useVisualliStream(apiUrl);
179
-
180
- if (isLoading) return <div>Loading... {progress}%</div>;
181
- if (error) return <div>Error: {error.message}</div>;
182
- if (!document) return null;
183
-
184
- return <VisualliCanvas document={document} />;
185
- }
186
- ```
187
-
188
- ### Complete Example
134
+ import { useViewportStore, useNodeStore, useSelectionStore } from '@visualli/react';
189
135
 
190
- ```tsx
191
- import {
192
- VisualliProvider,
193
- useVisualliStream,
194
- VisualliCanvas
195
- } from '@visualli/react';
196
- import type {
197
- ParserMiddleware,
198
- ExtensionComponentProps
199
- } from '@visualli/react';
200
-
201
- // Middleware
202
- const middleware: ParserMiddleware = (data) => {
203
- if (data.type === 'extension') {
204
- return { ...data, processed: true };
205
- }
206
- return data;
207
- };
136
+ function CustomControls() {
137
+ const { zoomLevel, setZoom, setCenter } = useViewportStore();
138
+ const selectedNode = useSelectionStore(s => s.selectedId);
208
139
 
209
- // Extension Component
210
- function TooltipExtension({ extension }: ExtensionComponentProps) {
211
140
  return (
212
- <div style={{
213
- position: 'absolute',
214
- top: 20,
215
- right: 20,
216
- background: 'white',
217
- padding: '12px',
218
- borderRadius: '8px',
219
- pointerEvents: 'auto'
220
- }}>
221
- {extension.data?.message}
141
+ <div>
142
+ <button onClick={() => setZoom(zoomLevel * 1.2)}>Zoom In</button>
143
+ <button onClick={() => setCenter(0, 0)}>Reset View</button>
144
+ <p>Selected: {selectedNode}</p>
222
145
  </div>
223
146
  );
224
147
  }
225
-
226
- // App
227
- function App() {
228
- const { document, isLoading } = useVisualliStream('/api/mindmap');
229
-
230
- return (
231
- <VisualliProvider
232
- middlewares={[middleware]}
233
- extensions={{ 'tooltip': TooltipExtension }}
234
- >
235
- {isLoading ? <Loading /> : <VisualliCanvas document={document} />}
236
- </VisualliProvider>
237
- );
238
- }
239
- ```
240
-
241
- 📚 **See `EXTENSION_GUIDE.md` for comprehensive documentation and examples.**
242
-
243
- ## Exports
244
-
245
- ### Component
246
-
247
- ```ts
248
- import { VisualliCanvas } from '@visualli/react';
249
- ```
250
-
251
- ### Context & Provider 🆕
252
-
253
- ```ts
254
- import { VisualliProvider, useVisualli } from '@visualli/react';
255
- import type {
256
- VisualliProviderProps,
257
- VisualliContextValue,
258
- ExtensionComponentProps,
259
- ExtensionRegistry
260
- } from '@visualli/react';
261
- ```
262
-
263
- ### Stores
264
-
265
- ```ts
266
- import { useNodeStore, useViewportStore, useSelectionStore, useRenderConfigStore } from '@visualli/react';
267
- ```
268
-
269
- ### Hooks
270
-
271
- ```ts
272
- import { useKonvaRenderer, useKonvaLayerTransition, useViewportNodes } from '@visualli/react';
273
-
274
- // 🆕 Stream fetching hook
275
- import { useVisualliStream } from '@visualli/react';
276
- import type { UseVisualliStreamReturn } from '@visualli/react';
277
148
  ```
278
149
 
279
- ### Sub-components (composition)
150
+ ## Advanced Hooks
280
151
 
281
- ```ts
282
- import {
283
- KonvaStage, KonvaNode, KonvaEdge,
284
- KonvaNodeLayer, KonvaEdgeLayer,
285
- KonvaContainerLayer,
286
- NavigationStack, ZoomControls,
287
- } from '@visualli/react';
288
- ```
289
-
290
- ### Utilities
152
+ The SDK exports low-level hooks for building custom rendering pipelines:
291
153
 
292
- ```ts
293
- import {
294
- getChildLayerForNode, getConnectionsForLayer,
295
- calculateFitView, calculateFitZoom, calculateFitCenter,
296
- } from '@visualli/react';
297
- ```
154
+ - **`useKonvaRenderer`** — Access the Konva stage and rendering loop
155
+ - **`useViewportNodes`** — Get nodes currently visible in the viewport (post-culling)
156
+ - **`useKonvaLayerTransition`** — Control layer transition animations
157
+ - **`useVisualli`** — Access the Visualli context (when wrapped in `VisualliProvider`)
298
158
 
299
- ### Config helpers
159
+ ## Requirements
300
160
 
301
- ```ts
302
- import {
303
- getBlobTypeForLayer, buildBlobPathData, ALL_BLOB_SHAPES,
304
- computeNodeTextWorldScale, computeNodeTextScreenScale,
305
- } from '@visualli/react';
306
- ```
161
+ - Node.js ≥ 22
162
+ - React 18
163
+ - TypeScript 5+ (recommended)
307
164
 
308
- ## TypeScript
165
+ ## TypeScript Configuration
309
166
 
310
- ```jsonc
311
- // tsconfig.json
167
+ ```json
312
168
  {
313
169
  "compilerOptions": {
314
170
  "moduleResolution": "bundler",
@@ -317,19 +173,3 @@ import {
317
173
  }
318
174
  }
319
175
  ```
320
-
321
- ## Requirements
322
-
323
- - Node.js ≥ 22
324
- - React 18
325
- - `@visualli/core` must be built (`npm run build` in `sdk-core/`) before typechecking
326
-
327
- ## Typecheck
328
-
329
- ```bash
330
- # Build core first
331
- cd ../sdk-core && npm run build
332
-
333
- # Typecheck react package
334
- cd ../sdk-react && npx tsc --noEmit
335
- ```
package/dist/index.cjs CHANGED
@@ -2720,7 +2720,10 @@ function VisualliCanvas(props) {
2720
2720
  setCanvasContextMenu(null);
2721
2721
  return;
2722
2722
  }
2723
- setCanvasContextMenu({ x: e.evt.clientX, y: e.evt.clientY });
2723
+ const rect = containerRef.current?.getBoundingClientRect();
2724
+ const localX = rect ? e.evt.clientX - rect.left : e.evt.clientX;
2725
+ const localY = rect ? e.evt.clientY - rect.top : e.evt.clientY;
2726
+ setCanvasContextMenu({ x: localX, y: localY });
2724
2727
  }, [navStack.length]);
2725
2728
  const handleStageTouchStart = (0, import_react14.useCallback)((e) => handleStageMouseDown(e), [handleStageMouseDown]);
2726
2729
  const handleStageTouchMove = (0, import_react14.useCallback)((e) => handleStageMouseMove(e), [handleStageMouseMove]);
@@ -2876,7 +2879,7 @@ function VisualliCanvas(props) {
2876
2879
  role: "menu",
2877
2880
  "aria-label": "Canvas options",
2878
2881
  style: {
2879
- position: "fixed",
2882
+ position: "absolute",
2880
2883
  zIndex: 60,
2881
2884
  pointerEvents: "auto",
2882
2885
  left: `${Math.min(canvasContextMenu.x, Math.max(8, (canvasSizeRef.current.width || 800) - 152))}px`,
@@ -2925,7 +2928,7 @@ function VisualliCanvas(props) {
2925
2928
  {
2926
2929
  "data-node-tooltip": "true",
2927
2930
  style: {
2928
- position: "fixed",
2931
+ position: "absolute",
2929
2932
  zIndex: 50,
2930
2933
  left: safeLeft,
2931
2934
  top: safeTop,