@visualli/react 0.1.6 → 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 -2032
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +355 -97
  5. package/dist/index.d.ts +355 -97
  6. package/dist/index.js +2005 -2094
  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 +453 -408
  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.ts 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,12 +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;
169
+ onNodeHover?: (nodeId: string | null) => void;
84
170
  renderOverlay?: (params: {
85
171
  isDark: boolean;
172
+ theme: string;
86
173
  containerWidth: number;
87
174
  containerHeight: number;
88
175
  }) => React$1.ReactNode;
@@ -141,10 +228,15 @@ declare const useNodeStore: zustand.UseBoundStore<zustand.StoreApi<INodeStore>>;
141
228
  interface IViewportStore extends ViewportState {
142
229
  canvasWidth: number;
143
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;
144
235
  pan: (deltaX: number, deltaY: number) => void;
145
236
  zoom: (delta: number, pivotX?: number, pivotY?: number) => void;
146
237
  setCenter: (x: number, y: number) => void;
147
238
  setZoom: (level: number) => void;
239
+ setFit: (scale: number, centerX: number, centerY: number) => void;
148
240
  updateCanvasSize: (width: number, height: number) => void;
149
241
  getVisibleBounds: () => ViewportState['visibleBounds'];
150
242
  worldToScreen: (x: number, y: number) => {
@@ -171,98 +263,107 @@ interface UseKonvaRendererReturn {
171
263
  handleMouseDown: (e: Konva.KonvaEventObject<MouseEvent>) => void;
172
264
  handleMouseMove: (e: Konva.KonvaEventObject<MouseEvent>) => void;
173
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;
174
268
  canvasWidth: number;
175
269
  canvasHeight: number;
176
270
  }
177
- declare function useKonvaRenderer({ containerRef, stageRef, enabled, }: UseKonvaRendererOptions): UseKonvaRendererReturn;
271
+ declare function useKonvaRenderer({ containerRef, enabled, }: UseKonvaRendererOptions): UseKonvaRendererReturn;
178
272
 
273
+ /** A viewport to return to: centre and zoom. */
179
274
  interface AnimatorViewport {
180
275
  centerX: number;
181
276
  centerY: number;
182
277
  zoomLevel: number;
183
278
  }
184
- interface NodeBounds {
185
- x: number;
186
- y: number;
187
- width: number;
188
- height: number;
189
- }
190
- interface TransitionCallbacks {
191
- onUpdate: (state: {
192
- viewport: AnimatorViewport;
193
- opacity: number;
194
- backgroundColor: string;
195
- }) => void;
196
- onComplete: () => void;
197
- onPhaseChange?: (phase: string) => void;
198
- }
199
- declare class KonvaLayerTransitionAnimator {
200
- private rafId;
201
- private timers;
202
- private _active;
203
- get active(): boolean;
204
- cancel(): void;
205
- animateZoomIntoLayer(current: AnimatorViewport, targetNode: NodeBounds, childCenter: {
206
- x: number;
207
- y: number;
208
- }, fromColor: string, toColor: string, cb: TransitionCallbacks, targetZoom?: number, canvasWidth?: number, canvasHeight?: number): void;
209
- animateZoomOutToParent(current: AnimatorViewport, parent: AnimatorViewport, fromColor: string, toColor: string, cb: TransitionCallbacks): void;
210
- private _complete;
211
- }
212
- declare const konvaLayerTransitionAnimator: KonvaLayerTransitionAnimator;
213
-
214
- interface LayerTransitionOptions {
215
- /** Ref to the Konva Stage for imperative position/scale updates each frame. */
279
+ interface LayerChoreographyOptions {
216
280
  stageRef: React.RefObject<Konva.Stage | null>;
217
- /** 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. */
218
282
  canvasWrapperRef: React.RefObject<HTMLDivElement | null>;
219
- /** Ref to the background color div. */
220
- bgRef?: React.RefObject<HTMLDivElement | null>;
221
- /** Called at the content-swap point — use to switch the displayed layer. */
222
- onSwapLayer: (newLayerId: string) => void;
223
- /** 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). */
224
287
  onSwapBack: () => void;
225
- /** Called after the transition fully completes. */
288
+ /** Called once the new layer has settled. */
226
289
  onComplete: () => void;
227
- /**
228
- * GPU CSS-transform mode: called each rAF frame instead of applyViewport.
229
- * When provided, the caller is responsible for moving the canvas content.
230
- */
231
- onViewportFrame?: (vp: AnimatorViewport) => void;
232
- /**
233
- * Called once when the animation reaches its final viewport.
234
- * When provided, replaces the default applyViewport call at completion.
235
- */
236
- onViewportFinal?: (vp: AnimatorViewport) => void;
237
- /**
238
- * Called at phase-change points ('swap', 'zoom-back') in addition to the
239
- * internal content-swap logic. Use to reset CSS transforms before Konva redraws.
240
- */
241
- onPhaseChange?: (phase: string) => void;
242
- /**
243
- * Called with 'start' before the animator fires its first frame and with
244
- * 'end' after onComplete. Use to set up / tear down GPU hints (willChange, etc.).
245
- */
246
- 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;
247
292
  }
248
- interface LayerTransitionHandlers {
249
- zoomIntoLayer: (doc: VisualliDocument, targetNode: FlatNode, childLayerId: string, childNodes: FlatNode[], fromColor: string, toColor: string) => void;
250
- 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;
251
300
  isTransitioning: () => boolean;
252
301
  }
253
- declare function useKonvaLayerTransition(opts: LayerTransitionOptions): LayerTransitionHandlers;
302
+ declare function useLayerChoreography(o: LayerChoreographyOptions): LayerChoreography;
254
303
 
255
304
  declare function useViewportNodes(nodes: FlatNode[], levelFilter?: number, skipCulling?: boolean,
256
305
  /** When true, skip culling so dragged nodes are always visible and the
257
306
  * spatial index (keyed on layout positions) is not consulted. */
258
307
  isDragging?: boolean): FlatNode[];
259
308
 
260
- interface BlobPoint {
309
+ interface NodeShadow {
261
310
  x: number;
262
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;
263
352
  }
264
- type BlobShape = ReadonlyArray<BlobPoint>;
265
- 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;
266
367
 
267
368
  interface KonvaStageProps {
268
369
  children: React$1.ReactNode;
@@ -285,8 +386,10 @@ interface KonvaNodeProps {
285
386
  isExternallyHovered?: boolean;
286
387
  /** True while this node is being pressed/dragged. */
287
388
  isExternallyPressed?: boolean;
288
- /** 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. */
289
391
  isDragging?: boolean;
392
+ design?: Design;
290
393
  }
291
394
  declare const KonvaNode: React$1.NamedExoticComponent<KonvaNodeProps>;
292
395
 
@@ -294,42 +397,134 @@ interface KonvaEdgeProps {
294
397
  sourceNode: FlatNode;
295
398
  targetNode: FlatNode;
296
399
  connection?: MindMapConnection;
400
+ /** @deprecated pass `design`. */
297
401
  isDark?: boolean;
402
+ /** @deprecated connectors use the design system's edge colour; kept so existing callers compile. */
298
403
  colorOverride?: string;
299
404
  zoomLevel?: number;
405
+ design?: Design;
300
406
  }
301
407
  declare const KonvaEdge: React$1.NamedExoticComponent<KonvaEdgeProps>;
302
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
+
303
446
  interface KonvaNodeLayerProps {
304
447
  nodes?: FlatNode[];
305
448
  isTransitioning?: boolean;
306
449
  isDragging?: boolean;
307
450
  hoveredNodeId?: string | null;
308
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;
309
462
  }
310
- 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;
311
464
 
312
465
  interface KonvaEdgeLayerProps {
313
466
  nodes: FlatNode[];
314
467
  connections: MindMapConnection[];
315
468
  currentLevel?: number;
469
+ /** @deprecated pass `design`; kept so existing callers still compile. */
316
470
  isDark?: boolean;
317
- /** 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). */
318
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;
319
476
  }
320
- 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;
321
478
 
322
479
  interface KonvaContainerProps {
323
480
  /** All nodes in this container group (already resolved from nodeMap). */
324
481
  nodes: FlatNode[];
325
- /** Label text shown on the pill badge. */
482
+ /** The group's name, drawn as a pill on the hull. */
326
483
  label?: string;
327
- /** Unused – kept for API compat; stroke is always #747270. */
484
+ /** @deprecated unused: the hull and name use the design system's container style. */
328
485
  color?: string;
486
+ /** @deprecated pass `design`. */
329
487
  isDark?: boolean;
488
+ zoomLevel?: number;
489
+ design?: Design;
330
490
  }
331
491
  declare const KonvaContainer: React$1.NamedExoticComponent<KonvaContainerProps>;
332
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
+
333
528
  interface ContainerGroup {
334
529
  id: string;
335
530
  label?: string;
@@ -337,39 +532,102 @@ interface ContainerGroup {
337
532
  level: number;
338
533
  }
339
534
  interface KonvaContainerLayerProps {
340
- /** Layout-snapshot nodes — used only as a seed for initial IDs. */
535
+ /** Layout-snapshot nodes (kept for API compatibility). */
341
536
  nodes: FlatNode[];
342
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. */
343
559
  isDark?: boolean;
344
560
  }
345
- 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>;
346
598
 
347
599
  interface NavStackEntry {
348
600
  layerId: string;
349
601
  layer: VisualliLayer;
350
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;
351
606
  }
352
607
  interface NavigationStackProps {
353
608
  stack: NavStackEntry[];
354
609
  onNavigateBack: (index: number) => void;
610
+ /** @deprecated theme comes from the surrounding data-theme. */
355
611
  isDark?: boolean;
612
+ /** Override the trail's default offset from the top-left corner. */
356
613
  top?: string;
357
614
  left?: string;
358
615
  }
359
616
  declare const NavigationStack: React$1.NamedExoticComponent<NavigationStackProps>;
360
617
 
361
- interface ZoomControlsProps {
362
- 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;
363
627
  }
364
- declare const ZoomControls: React$1.NamedExoticComponent<ZoomControlsProps>;
628
+ declare const A11yLayer: React$1.NamedExoticComponent<A11yLayerProps>;
365
629
 
366
- interface SketchyBoxKonvaProps {
367
- children: React$1.ReactNode;
368
- fill: string;
369
- stroke: string;
370
- backStroke: string;
371
- padding?: string;
372
- }
373
- 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;
374
632
 
375
- 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 };