@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.
Files changed (49) hide show
  1. package/README.md +13 -5
  2. package/dist/index.cjs +1900 -2033
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +354 -97
  5. package/dist/index.d.ts +354 -97
  6. package/dist/index.js +2005 -2095
  7. package/dist/index.js.map +1 -1
  8. package/fonts/Caveat-Variable.ttf +0 -0
  9. package/licenses/visualli-design-system/LICENSE +9 -0
  10. package/licenses/visualli-design-system/NOTICE.md +6 -0
  11. package/package.json +9 -4
  12. package/src/VisualliCanvas.tsx +452 -409
  13. package/src/VisualliRenderer.tsx +90 -112
  14. package/src/components/A11yLayer.tsx +73 -0
  15. package/src/components/KonvaContainer.tsx +28 -237
  16. package/src/components/KonvaContainerLabelLayer.tsx +41 -0
  17. package/src/components/KonvaContainerLayer.tsx +34 -33
  18. package/src/components/KonvaEdge.tsx +25 -240
  19. package/src/components/KonvaEdgeLayer.tsx +69 -49
  20. package/src/components/KonvaNode.tsx +21 -156
  21. package/src/components/KonvaNodeLayer.tsx +102 -34
  22. package/src/components/KonvaStage.tsx +17 -3
  23. package/src/components/NavigationStack.tsx +16 -159
  24. package/src/components/Overlays.tsx +327 -0
  25. package/src/components/ZoomControls.tsx +3 -107
  26. package/src/components/useFrames.ts +30 -0
  27. package/src/context/VisualliContext.tsx +3 -3
  28. package/src/design/choreography.ts +157 -0
  29. package/src/design/containers.ts +150 -0
  30. package/src/design/design.ts +115 -0
  31. package/src/design/drawing.ts +303 -0
  32. package/src/design/measure.ts +34 -0
  33. package/src/design/runtime.ts +154 -0
  34. package/src/design/useDesign.ts +38 -0
  35. package/src/generated/specCss.ts +15 -0
  36. package/src/hooks/index.ts +0 -2
  37. package/src/hooks/useKonvaRenderer.ts +13 -18
  38. package/src/hooks/useLayerChoreography.ts +159 -0
  39. package/src/hooks/useViewportNodes.ts +0 -1
  40. package/src/index.ts +22 -22
  41. package/src/stores/stores.ts +2 -2
  42. package/src/stores/useViewportStore.ts +12 -2
  43. package/src/animations/index.ts +0 -2
  44. package/src/animations/konvaLayerTransition.ts +0 -223
  45. package/src/components/SketchyBoxKonva.tsx +0 -121
  46. package/src/config/blobShapes.ts +0 -204
  47. package/src/config/index.ts +0 -21
  48. package/src/config/textScaling.ts +0 -69
  49. package/src/hooks/useKonvaLayerTransition.ts +0 -221
package/dist/index.d.cts CHANGED
@@ -1,11 +1,38 @@
1
1
  import React$1 from 'react';
2
- import { VisualliDocument, FlatNode, VisualliLayer, NodeMap, ViewportState, IRenderConfigStore, ISelectionStore, MindMapConnection } from '@visualli/core';
3
- export { ACTIVE_BLOB_TYPES, ALL_BLOB_SHAPES, BLOB_LAYER_CONFIG, BLOB_TEXT_OFFSETS, DESCRIPTION_TEXT_BASE_FONT_PX, FitResult, NODE_LAYER_CONFIG, NODE_TEXT_BASE_FONT_PX, calculateFitCenter, calculateFitView, calculateFitZoom, computeEdgeLabelScale, computeNodeTextScreenScale, computeNodeTextWorldScale, computeOverlayScale, drawBlobPath, getBlobTypeForLayer, getChildLayerForNode, getConnectionsForLayer, getLayerForNavigation } from '@visualli/core';
2
+ import { ThemeInput, Comfort, VisualliDocument, FlatNode, VisualliLayer, NodeMap, ViewportState, IRenderConfigStore, ISelectionStore, ThemeName, ResolvedComfort, Metrics, MindMapConnection, Hull, LabelSide, SemanticAnchor, IdeaMeasure } from '@visualli/core';
3
+ export { BLOB_SHAPES, Comfort, DESIGN_SYSTEM_VERSION, FitResult, IDEA, LABEL_GROWTH, METRICS, RINGS, THEME_NAMES, TOKENS, TOPICS, ThemeInput, ThemeName, TopicName, arrowPath, blobPath, blobRadius, calculateFitCenter, calculateFitView, calculateFitZoom, edgePath, getChildLayerForNode, getConnectionsForLayer, getLayerForNavigation, getTokens, ideaColor, ideaSize, ideaStyle, labelGrowth, resolveComfort, resolveTheme, shapeForLevel, topicStyle } from '@visualli/core';
4
4
  import * as zustand from 'zustand';
5
5
  import Konva from 'konva';
6
6
  import * as konva_lib_Stage from 'konva/lib/Stage';
7
7
 
8
- type VisualliTheme = 'dark' | 'light' | 'auto';
8
+ interface DesignSystemAssets {
9
+ /**
10
+ * Where the bundled Caveat font file is served from (URL of the directory that
11
+ * contains `Caveat-Variable.ttf`). Defaults to the copy shipped inside the npm
12
+ * package, via jsDelivr.
13
+ */
14
+ fontBaseUrl?: string;
15
+ /** Set false to skip the Google Fonts stylesheet (Kalam + Atkinson Hyperlegible), e.g. when self-hosting them. */
16
+ loadWebFonts?: boolean;
17
+ }
18
+ /**
19
+ * Inject the spec CSS and font declarations once. Safe to call repeatedly and on the server (no-op).
20
+ * The returned promise settles when the web-font stylesheet has loaded (its @font-face rules must exist
21
+ * before document.fonts.load() can fetch anything).
22
+ */
23
+ declare function ensureDesignSystemStyles(assets?: DesignSystemAssets): Promise<void>;
24
+ /**
25
+ * Resolves once the canvas fonts are usable (or after `timeoutMs`, so an
26
+ * offline reader still gets a map in the fallback faces).
27
+ */
28
+ declare function loadCanvasFonts(sheet?: Promise<void>, timeoutMs?: number): Promise<void>;
29
+
30
+ /**
31
+ * Any of the 8 design-system themes ('light', 'dark', 'focus-light', 'focus-dark', 'colorsafe-light',
32
+ * 'colorsafe-dark', 'contrast-light', 'contrast-dark'), a family ('focus' | 'colorsafe' | 'contrast'),
33
+ * or 'auto' (follow the reader's colour scheme). 'light' | 'dark' | 'auto' are the pre-0.2 values and keep working.
34
+ */
35
+ type VisualliTheme = ThemeInput;
9
36
  interface VisualliRendererProps {
10
37
  /**
11
38
  * Raw JSONL string — the content of a .visualli file.
@@ -29,18 +56,35 @@ interface VisualliRendererProps {
29
56
  */
30
57
  useWorker?: boolean;
31
58
  /**
32
- * Colour theme.
33
- * - 'dark' → always dark background
34
- * - 'light' → always light background
35
- * - 'auto' → follows the OS/browser prefers-color-scheme
59
+ * Colour theme (see VisualliTheme).
60
+ * - 'auto' follows prefers-color-scheme (and switches to high contrast under forced-colors)
36
61
  * @default 'light'
37
62
  */
38
63
  theme?: VisualliTheme;
64
+ /** Comfort settings: readable type, larger text, reduced motion ('system' follows the OS). */
65
+ comfort?: Comfort;
66
+ /** Switch to the high-contrast theme under forced-colors. @default true */
67
+ respectForcedColors?: boolean;
68
+ /**
69
+ * Interaction layout: 'auto' follows the reader's input (touch -> bottom-sheet peek and larger
70
+ * controls, pointer -> floating peek on hover); force one with 'touch' or 'pointer'.
71
+ * @default 'auto'
72
+ */
73
+ layout?: 'auto' | 'touch' | 'pointer';
74
+ /** Where the zoom / fit controls sit: the design system's 'bottom-right' (default) or the pre-0.2 'top-right'. */
75
+ /**
76
+ * The map is the page (an app such as Visualli's web app): it takes every touch gesture, so the browser never
77
+ * zooms or scrolls the page — the design system's `.vi-map.is-app` (`touch-action: none`). Leave it off when the
78
+ * map is embedded in a scrolling page: one finger then still scrolls the page, two fingers pinch the map.
79
+ */
80
+ app?: boolean;
81
+ /** Where the zoom / fit controls sit: 'top-right' (default, the design system's placement) or 'bottom-right'. */
82
+ controlsPosition?: 'top-right' | 'bottom-right';
39
83
  /**
40
84
  * Enable chromatic immersion background effect.
41
85
  * When true:
42
- * - Root layer (level 0) shows base background color
43
- * - Child layers show parent node's color as semi-transparent background
86
+ * - Root layer (level 0) shows the plain canvas
87
+ * - Child layers are tinted with the parent idea's topic colour
44
88
  * @default false
45
89
  */
46
90
  chromaticImmersion?: boolean;
@@ -56,12 +100,16 @@ interface VisualliRendererProps {
56
100
  * @default '100%'
57
101
  */
58
102
  height?: string | number;
103
+ /** Directory URL the bundled Caveat font is served from (defaults to the copy in the npm package, via jsDelivr). */
104
+ fontBaseUrl?: DesignSystemAssets['fontBaseUrl'];
105
+ /** Set false when you load Kalam and Atkinson Hyperlegible yourself. */
106
+ loadWebFonts?: DesignSystemAssets['loadWebFonts'];
59
107
  /** Additional CSS class on the root element. */
60
108
  className?: string;
61
109
  /** Additional inline styles merged onto the root element. */
62
110
  style?: React$1.CSSProperties;
63
111
  }
64
- declare function VisualliRenderer({ visualliString, visualliFile, theme, width, height, useWorker, chromaticImmersion, className, style, }: VisualliRendererProps): React$1.JSX.Element;
112
+ declare function VisualliRenderer({ visualliString, visualliFile, theme, comfort, respectForcedColors, layout, controlsPosition, app, width, height, useWorker, chromaticImmersion, fontBaseUrl, loadWebFonts, className, style, }: VisualliRendererProps): React$1.JSX.Element;
65
113
 
66
114
  interface VisualliCanvasProps {
67
115
  preParsedVisualli?: VisualliDocument;
@@ -77,13 +125,51 @@ interface VisualliCanvasProps {
77
125
  * Component handles all fetching and parsing automatically.
78
126
  */
79
127
  visualliFile?: File | string;
128
+ /**
129
+ * Theme: one of the 8 design-system themes ('light', 'dark', 'focus-light', …), a family
130
+ * ('focus' | 'colorsafe' | 'contrast') or 'auto' (follow the reader). Default 'light'.
131
+ */
132
+ theme?: ThemeInput;
133
+ /** @deprecated use `theme`. true -> 'dark', false -> 'light'. Kept for one release. */
80
134
  isDark?: boolean;
135
+ /** Comfort settings: readable type, larger text, reduced motion. */
136
+ comfort?: Comfort;
137
+ /**
138
+ * How a layer arrives. 'gradual' (default): ideas bloom in one by one, then connectors draw
139
+ * (design-system motion). 'instant': everything fades in together, as with reduced motion.
140
+ */
141
+ reveal?: 'gradual' | 'instant';
142
+ /** Switch to the high-contrast theme under forced-colors. Default true. */
143
+ respectForcedColors?: boolean;
144
+ /**
145
+ * Interaction layout. 'auto' (default) follows the reader's input: touch devices get the design
146
+ * system's bottom-sheet peek and larger controls, pointer devices the floating peek on hover.
147
+ * Force one with 'touch' or 'pointer'.
148
+ */
149
+ layout?: 'auto' | 'touch' | 'pointer';
150
+ /**
151
+ * The map is the page (an app such as Visualli's web app): it takes every touch gesture, so the browser never
152
+ * zooms or scrolls the page — the design system's `.vi-map.is-app` (`touch-action: none`). Leave it off when the
153
+ * map is embedded in a scrolling page: one finger then still scrolls the page, two fingers pinch the map.
154
+ */
155
+ app?: boolean;
156
+ /**
157
+ * Where the zoom / fit controls sit. Default 'top-right', the design system's placement (`.vi-map__ctrls`,
158
+ * opposite the depth trail). 'bottom-right' moves them to the bottom corner for hosts that need the top edge;
159
+ * there they rise above the touch peek sheet while it's open.
160
+ */
161
+ controlsPosition?: 'top-right' | 'bottom-right';
162
+ /** Where the bundled Caveat font is served from (directory URL). Defaults to the copy in the npm package, via jsDelivr. */
163
+ fontBaseUrl?: DesignSystemAssets['fontBaseUrl'];
164
+ /** Set false when you load Kalam and Atkinson Hyperlegible yourself. */
165
+ loadWebFonts?: DesignSystemAssets['loadWebFonts'];
81
166
  chromaticImmersion?: boolean;
82
167
  onNodeClick?: (node: FlatNode) => void;
83
168
  onLayerChange?: (layerId: string, layer: VisualliLayer) => void;
84
169
  onNodeHover?: (nodeId: string | null) => void;
85
170
  renderOverlay?: (params: {
86
171
  isDark: boolean;
172
+ theme: string;
87
173
  containerWidth: number;
88
174
  containerHeight: number;
89
175
  }) => React$1.ReactNode;
@@ -142,10 +228,15 @@ declare const useNodeStore: zustand.UseBoundStore<zustand.StoreApi<INodeStore>>;
142
228
  interface IViewportStore extends ViewportState {
143
229
  canvasWidth: number;
144
230
  canvasHeight: number;
231
+ /** The layer's fit to view (scale and centre): zoom is shown and limited (VIEW) relative to it, as in the design system. */
232
+ fitScale: number;
233
+ fitCenterX: number;
234
+ fitCenterY: number;
145
235
  pan: (deltaX: number, deltaY: number) => void;
146
236
  zoom: (delta: number, pivotX?: number, pivotY?: number) => void;
147
237
  setCenter: (x: number, y: number) => void;
148
238
  setZoom: (level: number) => void;
239
+ setFit: (scale: number, centerX: number, centerY: number) => void;
149
240
  updateCanvasSize: (width: number, height: number) => void;
150
241
  getVisibleBounds: () => ViewportState['visibleBounds'];
151
242
  worldToScreen: (x: number, y: number) => {
@@ -172,98 +263,107 @@ interface UseKonvaRendererReturn {
172
263
  handleMouseDown: (e: Konva.KonvaEventObject<MouseEvent>) => void;
173
264
  handleMouseMove: (e: Konva.KonvaEventObject<MouseEvent>) => void;
174
265
  handleMouseUp: (e: Konva.KonvaEventObject<MouseEvent>) => void;
266
+ /** End a one-finger / mouse pan now (e.g. a second finger turns it into a pinch), keeping how far it moved. */
267
+ cancelPan: () => void;
175
268
  canvasWidth: number;
176
269
  canvasHeight: number;
177
270
  }
178
- declare function useKonvaRenderer({ containerRef, stageRef, enabled, }: UseKonvaRendererOptions): UseKonvaRendererReturn;
271
+ declare function useKonvaRenderer({ containerRef, enabled, }: UseKonvaRendererOptions): UseKonvaRendererReturn;
179
272
 
273
+ /** A viewport to return to: centre and zoom. */
180
274
  interface AnimatorViewport {
181
275
  centerX: number;
182
276
  centerY: number;
183
277
  zoomLevel: number;
184
278
  }
185
- interface NodeBounds {
186
- x: number;
187
- y: number;
188
- width: number;
189
- height: number;
190
- }
191
- interface TransitionCallbacks {
192
- onUpdate: (state: {
193
- viewport: AnimatorViewport;
194
- opacity: number;
195
- backgroundColor: string;
196
- }) => void;
197
- onComplete: () => void;
198
- onPhaseChange?: (phase: string) => void;
199
- }
200
- declare class KonvaLayerTransitionAnimator {
201
- private rafId;
202
- private timers;
203
- private _active;
204
- get active(): boolean;
205
- cancel(): void;
206
- animateZoomIntoLayer(current: AnimatorViewport, targetNode: NodeBounds, childCenter: {
207
- x: number;
208
- y: number;
209
- }, fromColor: string, toColor: string, cb: TransitionCallbacks, targetZoom?: number, canvasWidth?: number, canvasHeight?: number): void;
210
- animateZoomOutToParent(current: AnimatorViewport, parent: AnimatorViewport, fromColor: string, toColor: string, cb: TransitionCallbacks): void;
211
- private _complete;
212
- }
213
- declare const konvaLayerTransitionAnimator: KonvaLayerTransitionAnimator;
214
-
215
- interface LayerTransitionOptions {
216
- /** Ref to the Konva Stage for imperative position/scale updates each frame. */
279
+ interface LayerChoreographyOptions {
217
280
  stageRef: React.RefObject<Konva.Stage | null>;
218
- /** Ref to the wrapping div whose CSS `opacity` is driven during transitions. */
281
+ /** The element around the Konva stage; the zoom, fade and settle play on it. */
219
282
  canvasWrapperRef: React.RefObject<HTMLDivElement | null>;
220
- /** Ref to the background color div. */
221
- bgRef?: React.RefObject<HTMLDivElement | null>;
222
- /** Called at the content-swap point — use to switch the displayed layer. */
223
- onSwapLayer: (newLayerId: string) => void;
224
- /** Called when a zoom-out swap happens — use to update the nav stack. */
283
+ reducedMotion: boolean;
284
+ /** Switch the displayed layer to a child layer (the layers swap here). */
285
+ onSwapLayer: (childLayerId: string) => void;
286
+ /** Switch back to the parent layer (the layers swap here). */
225
287
  onSwapBack: () => void;
226
- /** Called after the transition fully completes. */
288
+ /** Called once the new layer has settled. */
227
289
  onComplete: () => void;
228
- /**
229
- * GPU CSS-transform mode: called each rAF frame instead of applyViewport.
230
- * When provided, the caller is responsible for moving the canvas content.
231
- */
232
- onViewportFrame?: (vp: AnimatorViewport) => void;
233
- /**
234
- * Called once when the animation reaches its final viewport.
235
- * When provided, replaces the default applyViewport call at completion.
236
- */
237
- onViewportFinal?: (vp: AnimatorViewport) => void;
238
- /**
239
- * Called at phase-change points ('swap', 'zoom-back') in addition to the
240
- * internal content-swap logic. Use to reset CSS transforms before Konva redraws.
241
- */
242
- onPhaseChange?: (phase: string) => void;
243
- /**
244
- * Called with 'start' before the animator fires its first frame and with
245
- * 'end' after onComplete. Use to set up / tear down GPU hints (willChange, etc.).
246
- */
247
- onTransitionLifecycle?: (phase: 'start' | 'end') => void;
290
+ /** 'start' before anything moves, 'end' after the new layer has settled (pause hit-testing, GPU hints). */
291
+ onLifecycle?: (phase: 'start' | 'end') => void;
248
292
  }
249
- interface LayerTransitionHandlers {
250
- zoomIntoLayer: (doc: VisualliDocument, targetNode: FlatNode, childLayerId: string, childNodes: FlatNode[], fromColor: string, toColor: string) => void;
251
- zoomOutToParent: (parentViewport: AnimatorViewport, fromColor: string, toColor: string) => void;
293
+ interface LayerChoreography {
294
+ /** Step inside `targetNode` into the child layer. */
295
+ stepInside: (targetNode: FlatNode, childLayerId: string, childNodes: ReadonlyArray<FlatNode>) => void;
296
+ /** Back out to a layer whose fit viewport is `parent`. */
297
+ backOut: (parent: AnimatorViewport) => void;
298
+ /** The new layer is committed and drawing at its first frame: lift the veil and settle. Call after the swap's React commit. */
299
+ arrive: () => void;
252
300
  isTransitioning: () => boolean;
253
301
  }
254
- declare function useKonvaLayerTransition(opts: LayerTransitionOptions): LayerTransitionHandlers;
302
+ declare function useLayerChoreography(o: LayerChoreographyOptions): LayerChoreography;
255
303
 
256
304
  declare function useViewportNodes(nodes: FlatNode[], levelFilter?: number, skipCulling?: boolean,
257
305
  /** When true, skip culling so dragged nodes are always visible and the
258
306
  * spatial index (keyed on layout positions) is not consulted. */
259
307
  isDragging?: boolean): FlatNode[];
260
308
 
261
- interface BlobPoint {
309
+ interface NodeShadow {
262
310
  x: number;
263
311
  y: number;
312
+ blur: number;
313
+ color: string;
314
+ }
315
+ interface Design {
316
+ theme: ThemeName;
317
+ comfort: ResolvedComfort;
318
+ tokens: Readonly<Record<string, string>>;
319
+ metrics: Metrics;
320
+ isDark: boolean;
321
+ /** CSS font stacks, already switched to the readable face when that comfort setting is on. */
322
+ fontHand: string;
323
+ fontNote: string;
324
+ fontUi: string;
325
+ /** Multiplier for label sizes (`--vi-label-scale`). */
326
+ labelScale: number;
327
+ /** Idea drop shadow for this theme; null when the theme turns it off (high contrast). */
328
+ nodeShadow: NodeShadow | null;
329
+ /** Attributes the spec CSS keys off; put them on the renderer's root element. */
330
+ attrs: Record<string, string>;
331
+ /** Canvas weight/size for connector labels (the readable face is smaller and bolder). */
332
+ edgeLabel: {
333
+ weight: number;
334
+ size: number;
335
+ };
336
+ }
337
+ declare function makeDesign(theme: ThemeName, comfortIn: ResolvedComfort): Design;
338
+ declare const DEFAULT_DESIGN: Design;
339
+ interface DesignProps {
340
+ /**
341
+ * One of the 8 design-system themes ('light', 'dark', 'focus-light', 'focus-dark',
342
+ * 'colorsafe-light', 'colorsafe-dark', 'contrast-light', 'contrast-dark'), a family
343
+ * ('focus' | 'colorsafe' | 'contrast') or 'auto' (follow the reader's colour scheme).
344
+ */
345
+ theme?: ThemeInput;
346
+ /** @deprecated use `theme`. Kept for one release: true -> 'dark', false -> 'light'. */
347
+ isDark?: boolean;
348
+ /** Readable type, larger text, reduced motion. */
349
+ comfort?: Comfort;
350
+ /** Switch to the matching high-contrast theme under forced-colors (Windows high contrast etc.). Default true. */
351
+ respectForcedColors?: boolean;
264
352
  }
265
- type BlobShape = ReadonlyArray<BlobPoint>;
266
- declare function buildBlobPathData(_shape: BlobShape, cx: number, cy: number, width: number, height: number): string;
353
+ /** Resolve props + reader environment (colour scheme, forced colours, reduced motion) to a Design. */
354
+ declare function resolveDesign(props: DesignProps, env: {
355
+ prefersDark: boolean;
356
+ forcedColors: boolean;
357
+ prefersReducedMotion: boolean;
358
+ }): Design;
359
+
360
+ /** The reader's current preferences. */
361
+ declare function useReaderEnv(): {
362
+ prefersDark: boolean;
363
+ forcedColors: boolean;
364
+ prefersReducedMotion: boolean;
365
+ };
366
+ declare function useDesign(props: DesignProps): Design;
267
367
 
268
368
  interface KonvaStageProps {
269
369
  children: React$1.ReactNode;
@@ -286,8 +386,10 @@ interface KonvaNodeProps {
286
386
  isExternallyHovered?: boolean;
287
387
  /** True while this node is being pressed/dragged. */
288
388
  isExternallyPressed?: boolean;
289
- /** True while any node is being dragged — disables expensive shadows. */
389
+ isSelected?: boolean;
390
+ /** True while any node is being dragged — disables the drop shadow. */
290
391
  isDragging?: boolean;
392
+ design?: Design;
291
393
  }
292
394
  declare const KonvaNode: React$1.NamedExoticComponent<KonvaNodeProps>;
293
395
 
@@ -295,42 +397,134 @@ interface KonvaEdgeProps {
295
397
  sourceNode: FlatNode;
296
398
  targetNode: FlatNode;
297
399
  connection?: MindMapConnection;
400
+ /** @deprecated pass `design`. */
298
401
  isDark?: boolean;
402
+ /** @deprecated connectors use the design system's edge colour; kept so existing callers compile. */
299
403
  colorOverride?: string;
300
404
  zoomLevel?: number;
405
+ design?: Design;
301
406
  }
302
407
  declare const KonvaEdge: React$1.NamedExoticComponent<KonvaEdgeProps>;
303
408
 
409
+ /** How an idea looks mid-arrival (multiplies its resting look). */
410
+ interface Arrival {
411
+ alpha: number;
412
+ scale: number;
413
+ dx: number;
414
+ dy: number;
415
+ }
416
+ /** How a connector looks mid-arrival: `draw` is the fraction of the line drawn, `alpha` its group's fade. */
417
+ interface EdgeArrival {
418
+ alpha: number;
419
+ draw: number;
420
+ }
421
+ /** The timeline of one layer's arrival. Shared by the idea layer and the connector layer. */
422
+ declare class RevealClock {
423
+ private t0;
424
+ private end;
425
+ private plan;
426
+ private reduced;
427
+ private center;
428
+ private order;
429
+ private listeners;
430
+ private timer;
431
+ /** Begin a layer's arrival. `reduced` (reduced motion or instant reveal): everything fades in together. */
432
+ start(nodes: ReadonlyArray<FlatNode>, edgeCount: number, reduced: boolean, now?: number): void;
433
+ /** Jump to the finished state (e.g. on unmount). */
434
+ stop(): void;
435
+ /** True while the layer is still arriving. */
436
+ active(now?: number): boolean;
437
+ /** Called with true when an arrival starts and false when it has finished. Returns the unsubscribe function. */
438
+ subscribe(fn: (running: boolean) => void): () => void;
439
+ private emit;
440
+ /** Fills `out` with idea `id`'s arrival look. Returns false when it has arrived (nothing to apply). */
441
+ node(id: string, x: number, y: number, now: number, out: Arrival): boolean;
442
+ /** Fills `out` with connector `i`'s arrival look. Returns false when it has arrived. */
443
+ edge(i: number, dashed: boolean, now: number, out: EdgeArrival): boolean;
444
+ }
445
+
304
446
  interface KonvaNodeLayerProps {
305
447
  nodes?: FlatNode[];
306
448
  isTransitioning?: boolean;
307
449
  isDragging?: boolean;
308
450
  hoveredNodeId?: string | null;
309
451
  pressedNodeId?: string | null;
452
+ selectedNodeId?: string | null;
453
+ /** Idea with keyboard focus (accessible layer). */
454
+ focusedNodeId?: string | null;
455
+ design?: Design;
456
+ /** The layer's arrival timeline (bloom). Omit for no arrival animation. */
457
+ clock?: RevealClock;
458
+ /** Touch layout (`.vi-map.is-touch`): the other ideas fade while one is peeked. */
459
+ touch?: boolean;
460
+ /** Idea whose peek (sheet) is open on touch. */
461
+ peekedNodeId?: string | null;
310
462
  }
311
- declare function KonvaNodeLayer({ nodes: propNodes, isTransitioning, isDragging, hoveredNodeId, pressedNodeId, }: KonvaNodeLayerProps): React$1.JSX.Element;
463
+ declare function KonvaNodeLayer({ nodes: propNodes, isDragging, hoveredNodeId, pressedNodeId, selectedNodeId, focusedNodeId, design, clock, touch, peekedNodeId, }: KonvaNodeLayerProps): React$1.JSX.Element;
312
464
 
313
465
  interface KonvaEdgeLayerProps {
314
466
  nodes: FlatNode[];
315
467
  connections: MindMapConnection[];
316
468
  currentLevel?: number;
469
+ /** @deprecated pass `design`; kept so existing callers still compile. */
317
470
  isDark?: boolean;
318
- /** Pass true while a node is being dragged to skip viewport culling. */
471
+ /** Pass true while an idea is being dragged (connectors are always drawn then). */
319
472
  isDragging?: boolean;
473
+ design?: Design;
474
+ /** The layer's arrival timeline (connectors draw in after the ideas). Omit for no arrival animation. */
475
+ clock?: RevealClock;
320
476
  }
321
- declare function KonvaEdgeLayer({ nodes, connections, currentLevel, isDark, isDragging, }: KonvaEdgeLayerProps): React$1.JSX.Element;
477
+ declare function KonvaEdgeLayer({ connections, isDragging, design, clock }: KonvaEdgeLayerProps): React$1.JSX.Element;
322
478
 
323
479
  interface KonvaContainerProps {
324
480
  /** All nodes in this container group (already resolved from nodeMap). */
325
481
  nodes: FlatNode[];
326
- /** Label text shown on the pill badge. */
482
+ /** The group's name, drawn as a pill on the hull. */
327
483
  label?: string;
328
- /** Unused – kept for API compat; stroke is always #747270. */
484
+ /** @deprecated unused: the hull and name use the design system's container style. */
329
485
  color?: string;
486
+ /** @deprecated pass `design`. */
330
487
  isDark?: boolean;
488
+ zoomLevel?: number;
489
+ design?: Design;
331
490
  }
332
491
  declare const KonvaContainer: React$1.NamedExoticComponent<KonvaContainerProps>;
333
492
 
493
+ interface ContainerInput {
494
+ id: string;
495
+ label?: string;
496
+ nodeIds: string[];
497
+ }
498
+ interface ConnectionInput {
499
+ from: string;
500
+ to: string;
501
+ }
502
+ interface PlacedPill {
503
+ text: string;
504
+ x: number;
505
+ y: number;
506
+ side: LabelSide;
507
+ w: number;
508
+ h: number;
509
+ }
510
+ interface PlacedContainer {
511
+ id: string;
512
+ hull: Hull;
513
+ pill: PlacedPill | null;
514
+ }
515
+ /** The pill's box in world units for `text` (ellipsised past labelMaxWidth), with the text grown by `k`. */
516
+ declare function measurePill(text: string, d: Design, k: number, c?: CanvasRenderingContext2D | null): PlacedPill;
517
+ /**
518
+ * Hulls and name positions for a layer's containers, in file order (each name
519
+ * avoids the ones placed before it). Placement uses the names' size at 100%
520
+ * (k = 1), so they don't hop while zooming; they follow ideas that are dragged.
521
+ */
522
+ declare function layoutContainers(containers: ReadonlyArray<ContainerInput>, nodes: ReadonlyMap<string, FlatNode>, connections: ReadonlyArray<ConnectionInput>, d: Design): PlacedContainer[];
523
+ /** The dashed hull (beneath connectors and ideas). */
524
+ declare function drawHull(c: CanvasRenderingContext2D, h: Hull, d: Design): void;
525
+ /** The name pill (above connectors and ideas), its text growing like idea labels when zoomed out (labelGrowth). */
526
+ declare function drawPill(c: CanvasRenderingContext2D, p: PlacedPill, d: Design, zoom: number): void;
527
+
334
528
  interface ContainerGroup {
335
529
  id: string;
336
530
  label?: string;
@@ -338,39 +532,102 @@ interface ContainerGroup {
338
532
  level: number;
339
533
  }
340
534
  interface KonvaContainerLayerProps {
341
- /** Layout-snapshot nodes — used only as a seed for initial IDs. */
535
+ /** Layout-snapshot nodes (kept for API compatibility). */
342
536
  nodes: FlatNode[];
343
537
  containers: ContainerGroup[];
538
+ /** The layer's connections: container names are placed clear of them. */
539
+ connections?: ConnectionInput[];
540
+ /** @deprecated pass `design`. */
541
+ isDark?: boolean;
542
+ design?: Design;
543
+ }
544
+ declare function KonvaContainerLayer({ containers, connections, design }: KonvaContainerLayerProps): React$1.JSX.Element | null;
545
+
546
+ interface KonvaContainerLabelLayerProps {
547
+ containers: ContainerGroup[];
548
+ connections?: ConnectionInput[];
549
+ design?: Design;
550
+ }
551
+ declare function KonvaContainerLabelLayer({ containers, connections, design }: KonvaContainerLabelLayerProps): React$1.JSX.Element | null;
552
+
553
+ interface ZoomControlsProps {
554
+ /** Re-fit the current layer into view. */
555
+ onFit?: () => void;
556
+ /** Touch layout: larger targets (the design system's `md` icon buttons). */
557
+ touch?: boolean;
558
+ /** @deprecated theme comes from the surrounding data-theme; kept so existing callers compile. */
344
559
  isDark?: boolean;
345
560
  }
346
- declare function KonvaContainerLayer({ nodes, containers, isDark }: KonvaContainerLayerProps): React$1.JSX.Element | null;
561
+ declare const ZoomControls: React$1.NamedExoticComponent<ZoomControlsProps>;
562
+ /** An idea's colour: its topic, or a custom colour drawn as given (FlatNode.topic / FlatNode.custom). */
563
+ type IdeaPaint = Pick<FlatNode, 'topic' | 'custom'>;
564
+ /** CSS fill + ring for an idea's colour, as the design system's topicColors: the active theme's topic properties, or
565
+ * the custom colour with its darker ring (the `edge` token when it has none). */
566
+ declare function paintVars(p: IdeaPaint): {
567
+ fill: string;
568
+ ring: string;
569
+ };
570
+ /** A depth trail entry: the idea stepped into (its title and colour); the first is the map (home glyph, its title as the accessible name). */
571
+ interface TrailEntry {
572
+ layerId: string;
573
+ label: string;
574
+ level: number;
575
+ paint: IdeaPaint;
576
+ }
577
+ interface DepthTrailProps {
578
+ stack: TrailEntry[];
579
+ onNavigateBack: (index: number) => void;
580
+ }
581
+ declare const DepthTrail: React$1.NamedExoticComponent<DepthTrailProps>;
582
+ /** Split `text` into plain runs and runs that match an anchor word (longest word wins, whole words only). */
583
+ declare function splitTerms(text: string, anchors: ReadonlyArray<SemanticAnchor>): Array<string | SemanticAnchor>;
584
+ declare const TermCard: ({ anchor, inSheet }: {
585
+ anchor: SemanticAnchor;
586
+ inSheet?: boolean;
587
+ }) => React$1.JSX.Element;
588
+ interface PeekCardProps {
589
+ node: FlatNode;
590
+ anchors: ReadonlyArray<SemanticAnchor>;
591
+ /** Present when the idea has a layer inside it. */
592
+ onStepInside?: () => void;
593
+ onClose?: () => void;
594
+ /** Replaces the plain summary text (extension point kept from earlier versions). */
595
+ renderContent?: (summary: string) => React$1.ReactNode;
596
+ }
597
+ declare const PeekCard: React$1.NamedExoticComponent<PeekCardProps>;
347
598
 
348
599
  interface NavStackEntry {
349
600
  layerId: string;
350
601
  layer: VisualliLayer;
351
602
  label: string;
603
+ /** Colour of the idea this layer opens from (the design system's TRAIL: the clicked idea's own colour); the first
604
+ * entry, the map's title, is TRAIL.rootTopic. */
605
+ paint?: IdeaPaint;
352
606
  }
353
607
  interface NavigationStackProps {
354
608
  stack: NavStackEntry[];
355
609
  onNavigateBack: (index: number) => void;
610
+ /** @deprecated theme comes from the surrounding data-theme. */
356
611
  isDark?: boolean;
612
+ /** Override the trail's default offset from the top-left corner. */
357
613
  top?: string;
358
614
  left?: string;
359
615
  }
360
616
  declare const NavigationStack: React$1.NamedExoticComponent<NavigationStackProps>;
361
617
 
362
- interface ZoomControlsProps {
363
- isDark?: boolean;
618
+ interface A11yLayerProps {
619
+ nodes: FlatNode[];
620
+ /** Accessible name of the list, e.g. the layer title. */
621
+ label: string;
622
+ hasChildLayer: (nodeId: string) => boolean;
623
+ onActivate: (node: FlatNode) => void;
624
+ onFocusNode: (nodeId: string | null) => void;
625
+ /** Spoken when the layer changes. */
626
+ announcement?: string;
364
627
  }
365
- declare const ZoomControls: React$1.NamedExoticComponent<ZoomControlsProps>;
628
+ declare const A11yLayer: React$1.NamedExoticComponent<A11yLayerProps>;
366
629
 
367
- interface SketchyBoxKonvaProps {
368
- children: React$1.ReactNode;
369
- fill: string;
370
- stroke: string;
371
- backStroke: string;
372
- padding?: string;
373
- }
374
- declare const SketchyBoxKonva: React$1.FC<SketchyBoxKonvaProps>;
630
+ /** Width of a label in `d`'s idea face (cached per face, size, text and font load). */
631
+ declare function ideaMeasure(d: Pick<Design, 'fontHand' | 'comfort'>): IdeaMeasure;
375
632
 
376
- export { type AnimatorViewport, type ContainerGroup, type INodeStore, type IViewportStore, KonvaContainer, KonvaContainerLayer, KonvaEdge, KonvaEdgeLayer, type KonvaEdgeProps, KonvaLayerTransitionAnimator, KonvaNode, KonvaNodeLayer, type KonvaNodeProps, KonvaStage, type KonvaStageProps, type LayerTransitionHandlers, type NavStackEntry, NavigationStack, SketchyBoxKonva, type TransitionCallbacks, type UseKonvaRendererOptions, type UseKonvaRendererReturn, VisualliCanvas, type VisualliCanvasProps, type VisualliContextValue, VisualliProvider, type VisualliProviderProps, VisualliRenderer, type VisualliRendererProps, type VisualliTheme, ZoomControls, buildBlobPathData, konvaLayerTransitionAnimator, useKonvaLayerTransition, useKonvaRenderer, useNodeStore, useRenderConfigStore, useSelectionStore, useViewportNodes, useViewportStore, useVisualli };
633
+ export { A11yLayer, type AnimatorViewport, type ContainerGroup, DEFAULT_DESIGN, DepthTrail, type DepthTrailProps, type Design, type DesignProps, type DesignSystemAssets, type INodeStore, type IViewportStore, type IdeaPaint, KonvaContainer, KonvaContainerLabelLayer, KonvaContainerLayer, KonvaEdge, KonvaEdgeLayer, type KonvaEdgeProps, KonvaNode, KonvaNodeLayer, type KonvaNodeProps, KonvaStage, type KonvaStageProps, type LayerChoreography, type LayerChoreographyOptions, type NavStackEntry, NavigationStack, PeekCard, type PeekCardProps, type PlacedContainer, type PlacedPill, TermCard, type TrailEntry, type UseKonvaRendererOptions, type UseKonvaRendererReturn, VisualliCanvas, type VisualliCanvasProps, type VisualliContextValue, VisualliProvider, type VisualliProviderProps, VisualliRenderer, type VisualliRendererProps, type VisualliTheme, ZoomControls, drawHull, drawPill, ensureDesignSystemStyles, ideaMeasure, layoutContainers, loadCanvasFonts, makeDesign, measurePill, paintVars, resolveDesign, splitTerms, useDesign, useKonvaRenderer, useLayerChoreography, useNodeStore, useReaderEnv, useRenderConfigStore, useSelectionStore, useViewportNodes, useViewportStore, useVisualli };