@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 +102 -262
- package/dist/index.cjs +6 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/VisualliCanvas.tsx +6 -3
package/README.md
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
# @visualli/react
|
|
2
2
|
|
|
3
|
-
React canvas rendering for Visualli — powered by Konva.
|
|
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
|
|
8
|
-
- **Organic
|
|
9
|
-
- **Layer
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **Viewport
|
|
13
|
-
- **
|
|
14
|
-
- **Light
|
|
15
|
-
-
|
|
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
|
|
23
|
+
**Peer dependencies:** `react@^18`, `react-dom@^18`
|
|
26
24
|
|
|
27
25
|
## Quick Start
|
|
28
26
|
|
|
29
|
-
|
|
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
|
-
|
|
38
|
-
|
|
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
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
44
|
+
### Alternative: Pass Raw JSONL String
|
|
56
45
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
56
|
+
```tsx
|
|
57
|
+
function FileUploader() {
|
|
58
|
+
const [file, setFile] = useState<File | null>(null);
|
|
94
59
|
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
73
|
+
## API Reference
|
|
100
74
|
|
|
101
|
-
|
|
102
|
-
import { useViewportNodes } from '@visualli/react';
|
|
75
|
+
### `VisualliRenderer` (Recommended)
|
|
103
76
|
|
|
104
|
-
|
|
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
|
-
|
|
79
|
+
#### Props
|
|
111
80
|
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
93
|
+
> **Note:** Provide either `visualliFile` or `visualliString`, not both.
|
|
116
94
|
|
|
117
|
-
|
|
95
|
+
### `VisualliCanvas` (Advanced)
|
|
118
96
|
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
import { VisualliProvider, VisualliCanvas } from '@visualli/react';
|
|
99
|
+
#### Props
|
|
123
100
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
110
|
+
#### Example
|
|
133
111
|
|
|
134
112
|
```tsx
|
|
135
|
-
import {
|
|
136
|
-
import
|
|
113
|
+
import { VisualliCanvas } from '@visualli/react';
|
|
114
|
+
import { parseVisualliFile } from '@visualli/core';
|
|
137
115
|
|
|
138
|
-
const
|
|
139
|
-
if (data.type === 'extension') {
|
|
140
|
-
return { ...data, enhanced: true };
|
|
141
|
-
}
|
|
142
|
-
return data;
|
|
143
|
-
};
|
|
116
|
+
const document = parseVisualliFile(jsonlString);
|
|
144
117
|
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
118
|
+
<VisualliCanvas
|
|
119
|
+
preParsedVisualli={document}
|
|
120
|
+
isDark={true}
|
|
121
|
+
onNodeClick={(node) => console.log('Clicked:', node.data.label)}
|
|
122
|
+
/>
|
|
148
123
|
```
|
|
149
124
|
|
|
150
|
-
|
|
125
|
+
## Customization & Extensions
|
|
151
126
|
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
|
|
150
|
+
## Advanced Hooks
|
|
280
151
|
|
|
281
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
|
|
159
|
+
## Requirements
|
|
300
160
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
```
|
|
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
|
-
|
|
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: "
|
|
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: "
|
|
2931
|
+
position: "absolute",
|
|
2929
2932
|
zIndex: 50,
|
|
2930
2933
|
left: safeLeft,
|
|
2931
2934
|
top: safeTop,
|