@simplex-viz/react 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/ghackenberg/simplex-viz/main/docs/assets/logo-react.png" width="640" alt="SimplexViz / react — @simplex-viz/react" />
2
+ <img src="https://raw.githubusercontent.com/ghackenberg/simplex-viz/main/docs/assets/logo-react.png" width="525" alt="SimplexViz / react — @simplex-viz/react" />
3
3
  </p>
4
4
 
5
5
  # @simplex-viz/react
@@ -31,13 +31,105 @@ In React, `<SimplexTopicGraph />` lets you drop custom JSX directly into cluster
31
31
 
32
32
  ## Installation
33
33
 
34
- Install the React wrapper together with its core engine and SVG scene-graph peer packages (`react >= 18.0.0` and `react-dom >= 18.0.0`):
34
+ Install `@simplex-viz/react` (`@simplex-viz/core` and `@simplex-viz/svg` are bundled as dependencies, and their views, helpers, and TypeScript types are re-exported directly from `@simplex-viz/react`; requires `react >= 18.0.0` and `react-dom >= 18.0.0`):
35
35
 
36
36
  ```bash
37
- npm install @simplex-viz/core @simplex-viz/svg @simplex-viz/react
37
+ npm install @simplex-viz/react
38
38
  ```
39
39
 
40
- ## Quickstart: Declarative JSX Node Renderers
40
+ ## Quickstart (Zero-Config)
41
+
42
+ Render `<SimplexTopicGraph />` with any array of `{ id, tags }` objects. Drag-to-pan, cursor wheel zoom (`onWheel="zoom-to-cursor"`), cluster click-focus (`focusOnClusterClick`), and optional hover tooltips (`tooltip`) work immediately without a `VisualResolver` or view plugins:
43
+
44
+ ```tsx
45
+ import { SimplexTopicGraph } from '@simplex-viz/react';
46
+
47
+ const items = [
48
+ { id: '1', label: 'Simplex Mesh', tags: ['geometry', 'triangulation', 'math'] },
49
+ { id: '2', label: 'OKLab Mixing', tags: ['color', 'geometry', 'perception'] },
50
+ { id: '3', label: 'SAT Solver', tags: ['collision', 'geometry'] },
51
+ ];
52
+
53
+ export function QuickTopicMap() {
54
+ return (
55
+ <div style={{ width: '100%', height: 560 }}>
56
+ <SimplexTopicGraph items={items} preset="geometric" tooltip />
57
+ </div>
58
+ );
59
+ }
60
+ ```
61
+
62
+ ### Switch Visual Presets & Customize Tooltips
63
+
64
+ Swap between the three built-in presets (`'geometric'`, `'glyphs'`, or `'badges'`) with live animated morphing/crossfading, pass `getLabel` for custom records, or supply a React JSX `tooltip` renderer:
65
+
66
+ ```tsx
67
+ <SimplexTopicGraph
68
+ items={articles}
69
+ getId={(a) => a.slug}
70
+ getTags={(a) => a.hashtags}
71
+ getLabel={(a) => a.title}
72
+ preset="glyphs"
73
+ tooltip={(target) =>
74
+ target.kind === 'content' ? (
75
+ <div>
76
+ <strong>{target.item.title}</strong>
77
+ <div>{`${target.item.readingMinutes} min read`}</div>
78
+ </div>
79
+ ) : (
80
+ <strong>{`${target.cluster.defaultLabel} (${target.cluster.members.length})`}</strong>
81
+ )
82
+ }
83
+ />
84
+ ```
85
+
86
+ ## Level 2 — Keyless Declarative JSX Renderers
87
+
88
+ Want bespoke React SVG cards without writing a `VisualResolver`? Pass `renderContentNode` and/or `renderClusterNode` directly—`@simplex-viz/react` manages cache keys automatically (customizable via optional `getContentCacheKey` / `getClusterCacheKey`) and measures your JSX via offscreen `getBBox()`:
89
+
90
+ ```tsx
91
+ import { SimplexTopicGraph } from '@simplex-viz/react';
92
+
93
+ interface Article {
94
+ readonly slug: string;
95
+ readonly title: string;
96
+ readonly hashtags: readonly string[];
97
+ }
98
+
99
+ export function CardMap({ articles }: { articles: readonly Article[] }) {
100
+ return (
101
+ <SimplexTopicGraph
102
+ items={articles}
103
+ getId={(a) => a.slug}
104
+ getTags={(a) => a.hashtags}
105
+ tooltip
106
+ renderContentNode={(article, ctx, state) => (
107
+ <g>
108
+ <rect
109
+ x={-54}
110
+ y={-15}
111
+ width={108}
112
+ height={30}
113
+ rx={6}
114
+ fill="#0f172a"
115
+ stroke={state.interaction.isHovered ? '#f8fafc' : ctx.mixedColorHex}
116
+ strokeWidth={state.interaction.isHovered ? 2 : 1.2}
117
+ />
118
+ <text textAnchor="middle" y={4} fill="#f8fafc" fontSize={9}>
119
+ {article.title.slice(0, 16)}
120
+ </text>
121
+ </g>
122
+ )}
123
+ />
124
+ );
125
+ }
126
+ ```
127
+
128
+ Notice that you can even customize only `renderContentNode` while letting `preset="badges"` handle cluster cards automatically.
129
+
130
+ ## Level 3 — Full Custom `VisualResolver`, Controlled State & Imperative Hook
131
+
132
+ For full control over cluster aggregation (`deriveClusterMeta`), explicit visual cache keys, pinned items, controlled cluster focus, and external camera toolbars, combine `<SimplexTopicGraph />` with `useSimplexGraphController`.
41
133
 
42
134
  ### Step 1 — Define Domain, Visual, and Cluster Metadata Types
43
135
 
@@ -71,7 +163,7 @@ interface ClusterStats {
71
163
  }
72
164
  ```
73
165
 
74
- ### Step 2 — Define Declarative JSX Node Renderers
166
+ ### Step 2 — Define Typed Declarative JSX Node Renderers
75
167
 
76
168
  Write standard SVG JSX centered around `(0, 0)` for individual items and topic clusters. `@simplex-viz/react` measures the resulting `<g>` subtree automatically via offscreen `getBBox()` whenever `getVisualCacheKey` changes, guaranteeing collision-free card placement:
77
169
 
@@ -80,7 +172,7 @@ import type {
80
172
  ClusterDescriptor,
81
173
  ContentLayoutContext,
82
174
  NodeRenderState,
83
- } from '@simplex-viz/core';
175
+ } from '@simplex-viz/react';
84
176
 
85
177
  const renderArticleNode = (
86
178
  article: Article,
@@ -138,8 +230,8 @@ import { useMemo, useState } from 'react';
138
230
  import {
139
231
  SimplexTopicGraph,
140
232
  useSimplexGraphController,
233
+ type VisualResolver,
141
234
  } from '@simplex-viz/react';
142
- import type { VisualResolver } from '@simplex-viz/core';
143
235
 
144
236
  export function KnowledgeAtlas({ articles }: { articles: readonly Article[] }) {
145
237
  const [focusedClusterUid, setFocusedClusterUid] = useState<string | null>(null);
@@ -226,25 +318,33 @@ Finally, wire the camera toolbar (`handle.zoomBy`, `handle.resetCamera`), pass t
226
318
  }
227
319
  ```
228
320
 
229
- ## Using Portable `SvgNodeView` Plugins (60 fps Without JSX Re-Renders)
321
+ ## Level 4 — Portable `SvgNodeView` Plugins (60 fps Without JSX Re-Renders)
230
322
 
231
- For larger graphs or built-in shape morphing without per-frame JSX overhead, instantiate `GeometricShapeSvgView` and `ClusterAnchorSvgView` once outside the component and pass them via `contentView` and `clusterView` instead of `renderContentNode` / `renderClusterNode`:
323
+ For larger graphs or custom 64-point shape morphing without per-frame JSX overhead, instantiate `GeometricShapeSvgView` and `ClusterAnchorSvgView` (both re-exported directly from `@simplex-viz/react`) once outside the component and pass them via `contentView` and `clusterView`:
232
324
 
233
325
  ```tsx
234
- import { SimplexTopicGraph } from '@simplex-viz/react';
235
326
  import {
327
+ SimplexTopicGraph,
236
328
  GeometricShapeSvgView,
237
329
  ClusterAnchorSvgView,
238
330
  type GeometricShapeVisual,
239
331
  type ClusterAnchorVisual,
240
- } from '@simplex-viz/svg';
332
+ type VisualResolver,
333
+ type DefaultTaggedItem,
334
+ } from '@simplex-viz/react';
241
335
 
242
336
  const contentView = new GeometricShapeSvgView();
243
337
  const clusterView = new ClusterAnchorSvgView();
244
338
 
245
- export function FastGraph({ items, visualResolver }) {
339
+ export function FastGraph({
340
+ items,
341
+ visualResolver,
342
+ }: {
343
+ items: readonly DefaultTaggedItem[];
344
+ visualResolver: VisualResolver<DefaultTaggedItem, GeometricShapeVisual, ClusterAnchorVisual>;
345
+ }) {
246
346
  return (
247
- <SimplexTopicGraph<MyItem, GeometricShapeVisual, ClusterAnchorVisual>
347
+ <SimplexTopicGraph<DefaultTaggedItem, GeometricShapeVisual, ClusterAnchorVisual>
248
348
  items={items}
249
349
  visualResolver={visualResolver}
250
350
  contentView={contentView}
@@ -254,7 +354,7 @@ export function FastGraph({ items, visualResolver }) {
254
354
  }
255
355
  ```
256
356
 
257
- > **Tip:** Memoize `visualResolver` (e.g., via `useMemo`) and keep `contentView` / `clusterView` instances stable outside render to avoid unnecessary crossfades. Changing `visualResolver` or switching view props intentionally triggers a smooth crossfade/morph transition.
357
+ > **Tip:** Memoize `visualResolver` (e.g., via `useMemo`) and keep `contentView` / `clusterView` instances stable outside render to avoid unnecessary crossfades. Changing `preset`, `visualResolver`, or switching view props intentionally triggers a smooth crossfade/morph transition.
258
358
 
259
359
  ## `<SimplexTopicGraph />` Props Reference
260
360
 
@@ -264,18 +364,22 @@ export function FastGraph({ items, visualResolver }) {
264
364
  | `items` | Yes | `readonly TItem[]` — Domain items to cluster and lay out. |
265
365
  | `getId` | If `TItem` lacks `id` | `(item: TItem) => string` — Extracts a unique ID per item. |
266
366
  | `getTags` | If `TItem` lacks `tags` | `(item: TItem) => readonly string[]` — Extracts raw tag strings per item. |
367
+ | `getLabel` | No | `(item: TItem) => string` — Optional label extractor used by presets and default hover tooltips. |
267
368
  | `normalizeTag` | No | `(rawTag: string) => string` — Custom tag normalizer (defaults to lowercase trim + `#` strip). |
268
369
  | `resolveTagMeta` | No | `(normalizedTag: string) => TTagMeta \| undefined` — Attaches domain metadata to each tag. |
269
370
  | `deriveClusterMeta` | If `TClusterMeta` is defined | `(ctx) => TClusterMeta` — Derives aggregate metadata once per cluster update. |
270
371
 
271
- ### Visuals & Renderers
272
- | Prop | Description |
273
- | --- | --- |
274
- | `visualResolver` | Required `VisualResolver` mapping domain items and clusters to pure visual descriptors and cache keys. |
275
- | `renderContentNode` | Declarative React JSX renderer `(item, ctx, state) => ReactNode` (auto-measured via `getBBox()`). |
276
- | `renderClusterNode` | Declarative React JSX renderer `(cluster, state) => ReactNode` (auto-measured via `getBBox()`). |
277
- | `contentView` | Portable `SvgNodeView<TContentVisual>` plugin from `@simplex-viz/svg` (used when `renderContentNode` is omitted). |
278
- | `clusterView` | Portable `SvgNodeView<TClusterVisual>` plugin from `@simplex-viz/svg` (used when `renderClusterNode` is omitted). |
372
+ ### Visuals, Presets & Renderers
373
+ | Prop | Default | Description |
374
+ | --- | --- | --- |
375
+ | `preset` | `'geometric'` | Built-in visual preset (`'geometric' \| 'glyphs' \| 'badges'`) used when custom views or `visualResolver` are omitted. |
376
+ | `visualResolver` | Built-in preset resolver | Optional `VisualResolver` mapping items and clusters to custom descriptors and cache keys. |
377
+ | `renderContentNode` | — | Declarative React JSX renderer `(item, ctx, state) => ReactNode` (works with or without `visualResolver`, auto-measured via `getBBox()`). |
378
+ | `renderClusterNode` | — | Declarative React JSX renderer `(cluster, state) => ReactNode` (works with or without `visualResolver`, auto-measured via `getBBox()`). |
379
+ | `getContentCacheKey` | Auto-generated | Optional cache key callback `(item, ctx) => string` for keyless `renderContentNode`. |
380
+ | `getClusterCacheKey` | Auto-generated | Optional cache key callback `(cluster) => string` for keyless `renderClusterNode`. |
381
+ | `contentView` | Preset view | Portable `SvgNodeView<TContentVisual>` plugin (used when `renderContentNode` is omitted). |
382
+ | `clusterView` | Preset view | Portable `SvgNodeView<TClusterVisual>` plugin (used when `renderClusterNode` is omitted). |
279
383
 
280
384
  ### Mesh, Solver & Visual Emphasis
281
385
  | Prop | Default | Description |
@@ -286,21 +390,23 @@ export function FastGraph({ items, visualResolver }) {
286
390
  | `showHulls` | `false` | Renders convex bounding hull wireframes over nodes. |
287
391
  | `pinnedIds` | — | `ReadonlySet<string> \| readonly string[]` of pinned item IDs. |
288
392
  | `highlightedIds` | — | `ReadonlySet<string> \| readonly string[]` of highlighted item IDs (dims others). |
289
- | `focusedClusterUid` | `null` | Isolates a cluster and its members, dimming unrelated nodes. |
393
+ | `focusedClusterUid` | `null` | Controlled cluster focus UID. When omitted (`undefined`), clicking clusters toggles focus automatically. |
290
394
  | `className` / `style` | — | Styling forwarded to the outer container `<div>` (defaults to `width: 100%; height: 100%`). |
291
395
 
292
- ### Interactions, Telemetry & Controller
293
- | Prop | Description |
294
- | --- | --- |
295
- | `onContentHover` | `(event: ContentHoverEvent \| null) => void` |
296
- | `onClusterHover` | `(event: ClusterHoverEvent \| null) => void` |
297
- | `onLeftClick` | `(target: HitTarget, rawEvent: NormalizedPointerEvent) => void` |
298
- | `onRightClick` | `(target: HitTarget, rawEvent: NormalizedPointerEvent) => boolean \| void` (return `true` to suppress browser menu) |
299
- | `onWheel` | `'zoom-to-cursor' \| 'pan-vertical' \| 'none' \| ((event, camera) => void)` |
300
- | `onMetricsChange` | `(metrics: LayoutMetrics) => void` — Reports cluster count, scales `α`, `overlapCount`, `meshSideLength`, `areaEfficiency`. |
301
- | `onClustersChange` | `(clusters: readonly ClusterDescriptor[]) => void` |
302
- | `onFpsUpdate` | `(fps: number) => void` |
303
- | `controllerRef` | Ref populated with the underlying `SimplexSvgController` instance. |
396
+ ### Interactions, Tooltips, Telemetry & Controller
397
+ | Prop | Default / Signature | Description |
398
+ | --- | --- | --- |
399
+ | `tooltip` | `false` | `boolean \| ((target) => ReactNode)` — Renders a built-in floating React `role="tooltip"` overlay. |
400
+ | `focusOnClusterClick` | `true` | Automatically toggles cluster focus on click when `focusedClusterUid` and `onLeftClick` are omitted. |
401
+ | `onContentHover` | — | `(event: ContentHoverEvent \| null) => void` |
402
+ | `onClusterHover` | — | `(event: ClusterHoverEvent \| null) => void` |
403
+ | `onLeftClick` | — | `(target: HitTarget, rawEvent: NormalizedPointerEvent) => void` |
404
+ | `onRightClick` | — | `(target: HitTarget, rawEvent: NormalizedPointerEvent) => boolean \| void` (return `true` to suppress browser menu) |
405
+ | `onWheel` | `'zoom-to-cursor'` | `'zoom-to-cursor' \| 'pan-vertical' \| 'none' \| ((event, camera) => void)` |
406
+ | `onMetricsChange` | — | `(metrics: LayoutMetrics) => void` — Reports cluster count, scales `α`, `overlapCount`, `meshSideLength`, `areaEfficiency`. |
407
+ | `onClustersChange` | — | `(clusters: readonly ClusterDescriptor[]) => void` |
408
+ | `onFpsUpdate` | — | `(fps: number) => void` |
409
+ | `controllerRef` | — | Ref populated with the underlying `SimplexSvgController` instance. |
304
410
 
305
411
  ## `useSimplexGraphController()` Hook
306
412
 
@@ -1,13 +1,39 @@
1
1
  import type { ClusterDescriptor, ClusterHoverEvent, ClusterMetaConfig, ContentHoverEvent, ContentLayoutContext, DefaultTaggedItem, HitTarget, ItemIdentityConfig, LayoutMetrics, LayoutSolverOptions, MeshOptions, NodeRenderState, NormalizedPointerEvent, VisualResolver, WheelBehavior } from '@simplex-viz/core';
2
- import { SimplexSvgController, type SvgNodeView } from '@simplex-viz/svg';
2
+ import { SimplexSvgController, type DefaultClusterPresetVisual, type DefaultContentPresetVisual, type SimplexTheme, type SimplexVisualPreset, type SvgNodeView } from '@simplex-viz/svg';
3
3
  import { type CSSProperties, type MutableRefObject, type ReactNode } from 'react';
4
+ /**
5
+ * Union payload passed to a custom React `tooltip` formatter on `<SimplexTopicGraph />`.
6
+ */
7
+ export type SimplexReactTooltipTarget<TItem = DefaultTaggedItem, TContentVisual = DefaultContentPresetVisual, TClusterVisual = DefaultClusterPresetVisual, TTagMeta = undefined, TClusterMeta = undefined> = ({
8
+ readonly kind: 'content';
9
+ } & ContentHoverEvent<TItem, TContentVisual, TTagMeta, TClusterMeta>) | ({
10
+ readonly kind: 'cluster';
11
+ } & ClusterHoverEvent<TItem, TClusterVisual, TTagMeta, TClusterMeta>);
4
12
  /**
5
13
  * Props for `<SimplexTopicGraph />`, preserving end-to-end generic inference across
6
14
  * `TItem`, `TContentVisual`, `TClusterVisual`, `TTagMeta`, and `TClusterMeta`.
15
+ *
16
+ * Works zero-config with `<SimplexTopicGraph items={items} />` via built-in presets,
17
+ * or with keyless declarative JSX renderers (`renderContentNode` / `renderClusterNode`),
18
+ * or with full `VisualResolver` + `SvgNodeView` customization.
7
19
  */
8
- export type SimplexTopicGraphProps<TItem = DefaultTaggedItem, TContentVisual = unknown, TClusterVisual = unknown, TTagMeta = undefined, TClusterMeta = undefined> = ItemIdentityConfig<TItem> & ClusterMetaConfig<TItem, TTagMeta, TClusterMeta> & {
20
+ export type SimplexTopicGraphProps<TItem = DefaultTaggedItem, TContentVisual = DefaultContentPresetVisual, TClusterVisual = DefaultClusterPresetVisual, TTagMeta = undefined, TClusterMeta = undefined> = ItemIdentityConfig<TItem> & ClusterMetaConfig<TItem, TTagMeta, TClusterMeta> & {
9
21
  readonly items: readonly TItem[];
10
- readonly visualResolver: VisualResolver<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>;
22
+ /** Built-in visual preset used when custom views / `visualResolver` are omitted. Default: `'geometric'`. */
23
+ readonly preset?: SimplexVisualPreset;
24
+ /** Visual color theme (`'dark'` or `'light'`) for built-in presets and tooltips. Default: `'dark'`. */
25
+ readonly theme?: SimplexTheme;
26
+ /** Optional label extractor for built-in presets and default hover tooltips. */
27
+ readonly getLabel?: (item: TItem) => string;
28
+ /**
29
+ * Optional `VisualResolver` mapping items and clusters to pure visual descriptors.
30
+ * Optional in both zero-config preset mode and declarative JSX renderer mode.
31
+ */
32
+ readonly visualResolver?: VisualResolver<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>;
33
+ /** Optional cache key override when using `renderContentNode` without `visualResolver`. */
34
+ readonly getContentCacheKey?: (item: TItem, ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>) => string;
35
+ /** Optional cache key override when using `renderClusterNode` without `visualResolver`. */
36
+ readonly getClusterCacheKey?: (cluster: ClusterDescriptor<TItem, TTagMeta, TClusterMeta>) => string;
11
37
  /** Portable `@simplex-viz/svg` view plugin for content items. */
12
38
  readonly contentView?: SvgNodeView<TContentVisual>;
13
39
  /** Portable `@simplex-viz/svg` view plugin for topic clusters. */
@@ -25,6 +51,16 @@ export type SimplexTopicGraphProps<TItem = DefaultTaggedItem, TContentVisual = u
25
51
  readonly pinnedIds?: ReadonlySet<string> | readonly string[];
26
52
  readonly highlightedIds?: ReadonlySet<string> | readonly string[];
27
53
  readonly focusedClusterUid?: string | null;
54
+ /**
55
+ * Enables built-in cluster focus toggle on click when `focusedClusterUid` and `onLeftClick`
56
+ * are not controlled externally. Default: `true`.
57
+ */
58
+ readonly focusOnClusterClick?: boolean;
59
+ /**
60
+ * Enables a built-in floating hover tooltip, or accepts a custom `(target) => ReactNode` formatter.
61
+ * Default: `false`.
62
+ */
63
+ readonly tooltip?: boolean | ((target: SimplexReactTooltipTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>) => ReactNode);
28
64
  readonly onContentHover?: (event: ContentHoverEvent<TItem, TContentVisual, TTagMeta, TClusterMeta> | null) => void;
29
65
  readonly onClusterHover?: (event: ClusterHoverEvent<TItem, TClusterVisual, TTagMeta, TClusterMeta> | null) => void;
30
66
  readonly onLeftClick?: (target: HitTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>, rawEvent: NormalizedPointerEvent) => void;
@@ -40,5 +76,5 @@ export type SimplexTopicGraphProps<TItem = DefaultTaggedItem, TContentVisual = u
40
76
  /**
41
77
  * Type-safe React visualization component wrapping `@simplex-viz/core` and `@simplex-viz/svg`.
42
78
  */
43
- export declare function SimplexTopicGraph<TItem = DefaultTaggedItem, TContentVisual = unknown, TClusterVisual = unknown, TTagMeta = undefined, TClusterMeta = undefined>(props: SimplexTopicGraphProps<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>): import("react").JSX.Element;
79
+ export declare function SimplexTopicGraph<TItem = DefaultTaggedItem, TContentVisual = DefaultContentPresetVisual, TClusterVisual = DefaultClusterPresetVisual, TTagMeta = undefined, TClusterMeta = undefined>(props: SimplexTopicGraphProps<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>): import("react").JSX.Element;
44
80
  //# sourceMappingURL=SimplexTopicGraph.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SimplexTopicGraph.d.ts","sourceRoot":"","sources":["../src/SimplexTopicGraph.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SimplexTopicGraph.d.ts","sourceRoot":"","sources":["../src/SimplexTopicGraph.tsx"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export * from './ReactJsxSvgView';
2
2
  export * from './useSimplexGraphController';
3
3
  export * from './SimplexTopicGraph';
4
+ export { BadgeClusterSvgView, ClusterAnchorSvgView, GeometricShapeSvgView, MultiSectorGlyphSvgView, SimplexSvgController, createSimplexSvg, type BadgeClusterVisual, type ClusterAnchorVisual, type DefaultClusterPresetVisual, type DefaultContentPresetVisual, type GeometricShapeVisual, type MultiSectorGlyphVisual, type SimplexTheme, type SimplexVisualPreset, type SvgNodeView, } from '@simplex-viz/svg';
5
+ export { solveSimplexSnapshot, computeConvexHull, rectToConvexPolygon, regularPolygon, starConvexHull, type CameraController, type CameraState, type ClusterDescriptor, type ClusterHoverEvent, type ContentHoverEvent, type ContentLayoutContext, type ConvexGeometry, type DefaultTaggedItem, type HitTarget, type LayoutMetrics, type LayoutSolverOptions, type MeshOptions, type NodeRenderState, type SimplexLayoutSnapshot, type SimplexSnapshotOptions, type VisualResolver, } from '@simplex-viz/core';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AAGpC,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,gBAAgB,EAChB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,KAAK,WAAW,GACjB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,cAAc,GACpB,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { rectToConvexPolygon } from '@simplex-viz/core';
2
- import { SVG_NS, SimplexSvgController } from '@simplex-viz/svg';
2
+ export { computeConvexHull, rectToConvexPolygon, regularPolygon, solveSimplexSnapshot, starConvexHull } from '@simplex-viz/core';
3
+ import { SVG_NS, SimplexSvgController, createPresetVisualResolver, createPresetViews, extractDefaultItemLabel } from '@simplex-viz/svg';
4
+ export { BadgeClusterSvgView, ClusterAnchorSvgView, GeometricShapeSvgView, MultiSectorGlyphSvgView, SimplexSvgController, createSimplexSvg } from '@simplex-viz/svg';
3
5
  import { createRoot } from 'react-dom/client';
4
6
  import { renderToStaticMarkup } from 'react-dom/server';
5
- import { useRef, useCallback, useEffect } from 'react';
6
- import { jsx } from 'react/jsx-runtime';
7
+ import { useRef, useCallback, useState, useEffect } from 'react';
8
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
9
 
8
10
  // src/ReactJsxSvgView.ts
9
11
  var ReactJsxSvgView = class {
@@ -170,9 +172,14 @@ function useSimplexGraphController() {
170
172
  }
171
173
  };
172
174
  }
175
+ function isAutoKeyVisual(v) {
176
+ return typeof v === "object" && v !== null && "__svAutoKey" in v && typeof v.__svAutoKey === "string";
177
+ }
173
178
  function SimplexTopicGraph(props) {
174
179
  const containerRef = useRef(null);
175
180
  const ctrlRef = useRef(null);
181
+ const [hoveredTarget, setHoveredTarget] = useState(null);
182
+ const internalFocusedUidRef = useRef(null);
176
183
  const contentDomainByKeyRef = useRef(
177
184
  /* @__PURE__ */ new Map()
178
185
  );
@@ -183,30 +190,89 @@ function SimplexTopicGraph(props) {
183
190
  const jsxClusterViewRef = useRef(null);
184
191
  const latestPropsRef = useRef(props);
185
192
  latestPropsRef.current = props;
186
- const buildWrappedResolver = (base) => ({
187
- resolveContentVisual: (item, ctx) => {
188
- const visual = base.resolveContentVisual(item, ctx);
189
- const key = base.getVisualCacheKey(visual);
190
- contentDomainByKeyRef.current.set(key, { item, ctx });
191
- return visual;
192
- },
193
- resolveClusterVisual: (cluster) => {
194
- const visual = base.resolveClusterVisual(cluster);
195
- const key = base.getVisualCacheKey(visual);
196
- clusterDomainByKeyRef.current.set(key, cluster);
197
- return visual;
198
- },
199
- getVisualCacheKey: (visual) => base.getVisualCacheKey(visual)
200
- });
201
- const resolveActiveViews = () => {
193
+ const resolveEffectiveResolver = () => {
194
+ const p = latestPropsRef.current;
195
+ const preset = p.preset ?? "geometric";
196
+ const theme = p.theme ?? "dark";
197
+ const presetResolver = createPresetVisualResolver(
198
+ preset,
199
+ (item) => {
200
+ const curGetLabel = latestPropsRef.current.getLabel;
201
+ if (curGetLabel) return curGetLabel(item);
202
+ if (typeof item === "object" && item !== null) {
203
+ if ("label" in item && typeof item.label === "string" && item.label.length > 0) {
204
+ return item.label;
205
+ }
206
+ if ("title" in item && typeof item.title === "string" && item.title.length > 0) {
207
+ return item.title;
208
+ }
209
+ if ("headline" in item && typeof item.headline === "string" && item.headline.length > 0) {
210
+ return item.headline;
211
+ }
212
+ if ("name" in item && typeof item.name === "string" && item.name.length > 0) {
213
+ return item.name;
214
+ }
215
+ }
216
+ return "";
217
+ },
218
+ theme
219
+ );
220
+ const base = p.visualResolver;
221
+ const getKey = (visual) => {
222
+ if (isAutoKeyVisual(visual)) {
223
+ return visual.__svAutoKey;
224
+ }
225
+ if (base) {
226
+ return base.getVisualCacheKey(visual);
227
+ }
228
+ return presetResolver.getVisualCacheKey(
229
+ visual
230
+ );
231
+ };
232
+ return {
233
+ resolveContentVisual: (item, ctx) => {
234
+ const cur = latestPropsRef.current;
235
+ let visual;
236
+ if (base) {
237
+ visual = base.resolveContentVisual(item, ctx);
238
+ } else if (cur.renderContentNode) {
239
+ const autoKey = cur.getContentCacheKey ? cur.getContentCacheKey(item, ctx) : `${ctx.id}:${ctx.mixedColorHex}:${ctx.tags.map((t) => t.tag).join(",")}`;
240
+ visual = { __svAutoKey: autoKey };
241
+ } else {
242
+ visual = presetResolver.resolveContentVisual(item, ctx);
243
+ }
244
+ const key = getKey(visual);
245
+ contentDomainByKeyRef.current.set(key, { item, ctx });
246
+ return visual;
247
+ },
248
+ resolveClusterVisual: (cluster) => {
249
+ const cur = latestPropsRef.current;
250
+ let visual;
251
+ if (base) {
252
+ visual = base.resolveClusterVisual(cluster);
253
+ } else if (cur.renderClusterNode) {
254
+ const autoKey = cur.getClusterCacheKey ? cur.getClusterCacheKey(cluster) : `${cluster.clusterUid}:${cluster.defaultLabel}:${cluster.members.length}:${cluster.colorHex}`;
255
+ visual = { __svAutoKey: autoKey };
256
+ } else {
257
+ visual = presetResolver.resolveClusterVisual(cluster);
258
+ }
259
+ const key = getKey(visual);
260
+ clusterDomainByKeyRef.current.set(key, cluster);
261
+ return visual;
262
+ },
263
+ getVisualCacheKey: getKey
264
+ };
265
+ };
266
+ const resolveActiveViews = (activeResolver) => {
202
267
  const p = latestPropsRef.current;
268
+ const presetViews = createPresetViews(p.preset ?? "geometric");
203
269
  let contentView;
204
270
  if (p.renderContentNode) {
205
271
  const renderFn = p.renderContentNode;
206
272
  jsxContentViewRef.current?.dispose();
207
273
  const jsxView = new ReactJsxSvgView({
208
274
  renderFn: (domain, state) => renderFn(domain.item, domain.ctx, state),
209
- getKey: (v) => p.visualResolver.getVisualCacheKey(v),
275
+ getKey: (v) => activeResolver.getVisualCacheKey(v),
210
276
  domainByKey: contentDomainByKeyRef.current
211
277
  });
212
278
  jsxContentViewRef.current = jsxView;
@@ -216,19 +282,9 @@ function SimplexTopicGraph(props) {
216
282
  jsxContentViewRef.current = null;
217
283
  contentView = p.contentView;
218
284
  } else {
219
- contentView = {
220
- measureConvexHull: () => ({ kind: "circle", radius: 10 }),
221
- mountOrUpdate: (groupEl, _visual, state) => {
222
- const doc = groupEl.ownerDocument;
223
- let circle = groupEl.querySelector("circle");
224
- if (!circle) {
225
- circle = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
226
- circle.setAttribute("r", "9");
227
- groupEl.appendChild(circle);
228
- }
229
- circle.setAttribute("fill", state.interaction.isHovered ? "#22d3ee" : "#64748b");
230
- }
231
- };
285
+ jsxContentViewRef.current?.dispose();
286
+ jsxContentViewRef.current = null;
287
+ contentView = presetViews.contentView;
232
288
  }
233
289
  let clusterView;
234
290
  if (p.renderClusterNode) {
@@ -236,7 +292,7 @@ function SimplexTopicGraph(props) {
236
292
  jsxClusterViewRef.current?.dispose();
237
293
  const jsxView = new ReactJsxSvgView({
238
294
  renderFn: (cluster, state) => renderFn(cluster, state),
239
- getKey: (v) => p.visualResolver.getVisualCacheKey(v),
295
+ getKey: (v) => activeResolver.getVisualCacheKey(v),
240
296
  domainByKey: clusterDomainByKeyRef.current
241
297
  });
242
298
  jsxClusterViewRef.current = jsxView;
@@ -246,21 +302,9 @@ function SimplexTopicGraph(props) {
246
302
  jsxClusterViewRef.current = null;
247
303
  clusterView = p.clusterView;
248
304
  } else {
249
- clusterView = {
250
- measureConvexHull: () => ({ kind: "circle", radius: 22 }),
251
- mountOrUpdate: (groupEl, _visual, state) => {
252
- const doc = groupEl.ownerDocument;
253
- let circle = groupEl.querySelector("circle");
254
- if (!circle) {
255
- circle = doc.createElementNS("http://www.w3.org/2000/svg", "circle");
256
- circle.setAttribute("r", "20");
257
- circle.setAttribute("fill", "#0f172a");
258
- circle.setAttribute("stroke-width", "1.5");
259
- groupEl.appendChild(circle);
260
- }
261
- circle.setAttribute("stroke", state.interaction.isHovered ? "#f8fafc" : "#38bdf8");
262
- }
263
- };
305
+ jsxClusterViewRef.current?.dispose();
306
+ jsxClusterViewRef.current = null;
307
+ clusterView = presetViews.clusterView;
264
308
  }
265
309
  return { contentView, clusterView };
266
310
  };
@@ -268,13 +312,17 @@ function SimplexTopicGraph(props) {
268
312
  const el = containerRef.current;
269
313
  if (!el) return;
270
314
  const p = latestPropsRef.current;
271
- const { contentView, clusterView } = resolveActiveViews();
315
+ const effectiveResolver = resolveEffectiveResolver();
316
+ const { contentView, clusterView } = resolveActiveViews(effectiveResolver);
272
317
  const ctrl = SimplexSvgController.createFromResolved(el, {
273
318
  items: p.items,
274
319
  getId: p.getId,
275
320
  getTags: p.getTags,
321
+ getLabel: p.getLabel,
276
322
  deriveClusterMeta: p.deriveClusterMeta,
277
- visualResolver: buildWrappedResolver(p.visualResolver),
323
+ preset: p.preset,
324
+ theme: p.theme,
325
+ visualResolver: effectiveResolver,
278
326
  contentView,
279
327
  clusterView,
280
328
  normalizeTag: p.normalizeTag,
@@ -285,10 +333,37 @@ function SimplexTopicGraph(props) {
285
333
  sizeMultiplier: p.sizeMultiplier ?? p.layoutOptions?.sizeMultiplier ?? 1
286
334
  },
287
335
  showHulls: p.showHulls,
336
+ focusOnClusterClick: false,
288
337
  interactions: {
289
- onContentHover: (ev) => latestPropsRef.current.onContentHover?.(ev),
290
- onClusterHover: (ev) => latestPropsRef.current.onClusterHover?.(ev),
291
- onLeftClick: (t, raw) => latestPropsRef.current.onLeftClick?.(t, raw),
338
+ onContentHover: (ev) => {
339
+ latestPropsRef.current.onContentHover?.(ev);
340
+ if (latestPropsRef.current.tooltip) {
341
+ setHoveredTarget(ev ? { kind: "content", ...ev } : null);
342
+ }
343
+ },
344
+ onClusterHover: (ev) => {
345
+ latestPropsRef.current.onClusterHover?.(ev);
346
+ if (latestPropsRef.current.tooltip) {
347
+ setHoveredTarget(ev ? { kind: "cluster", ...ev } : null);
348
+ }
349
+ },
350
+ onLeftClick: (target, raw) => {
351
+ const cur = latestPropsRef.current;
352
+ if (cur.onLeftClick) {
353
+ cur.onLeftClick(target, raw);
354
+ return;
355
+ }
356
+ if (cur.focusedClusterUid === void 0 && (cur.focusOnClusterClick ?? true)) {
357
+ if (target.kind === "cluster") {
358
+ const next = internalFocusedUidRef.current === target.cluster.clusterUid ? null : target.cluster.clusterUid;
359
+ internalFocusedUidRef.current = next;
360
+ ctrlRef.current?.setFocusedClusterUid(next);
361
+ } else if (target.kind === "background" && internalFocusedUidRef.current !== null) {
362
+ internalFocusedUidRef.current = null;
363
+ ctrlRef.current?.setFocusedClusterUid(null);
364
+ }
365
+ }
366
+ },
292
367
  onRightClick: (t, raw) => latestPropsRef.current.onRightClick?.(t, raw),
293
368
  onWheel: (ev, cam) => {
294
369
  const policy = latestPropsRef.current.onWheel ?? "zoom-to-cursor";
@@ -331,10 +406,14 @@ function SimplexTopicGraph(props) {
331
406
  useEffect(() => {
332
407
  const ctrl = ctrlRef.current;
333
408
  if (!ctrl) return;
334
- const { contentView, clusterView } = resolveActiveViews();
409
+ ctrl.setTheme(props.theme ?? "dark");
410
+ const effectiveResolver = resolveEffectiveResolver();
411
+ const { contentView, clusterView } = resolveActiveViews(effectiveResolver);
335
412
  ctrl.setViews({ contentView, clusterView });
336
- ctrl.setVisualResolver(buildWrappedResolver(props.visualResolver));
413
+ ctrl.setVisualResolver(effectiveResolver);
337
414
  }, [
415
+ props.preset,
416
+ props.theme,
338
417
  props.visualResolver,
339
418
  props.contentView,
340
419
  props.clusterView,
@@ -363,9 +442,63 @@ function SimplexTopicGraph(props) {
363
442
  }, [props.highlightedIds]);
364
443
  useEffect(() => {
365
444
  if (props.focusedClusterUid !== void 0) {
445
+ internalFocusedUidRef.current = props.focusedClusterUid;
366
446
  ctrlRef.current?.setFocusedClusterUid(props.focusedClusterUid);
367
447
  }
368
448
  }, [props.focusedClusterUid]);
449
+ useEffect(() => {
450
+ if (!props.tooltip) {
451
+ setHoveredTarget(null);
452
+ }
453
+ }, [props.tooltip]);
454
+ const renderBuiltInTooltip = () => {
455
+ if (!props.tooltip || !hoveredTarget) return null;
456
+ const containerW = containerRef.current?.clientWidth || 960;
457
+ const containerH = containerRef.current?.clientHeight || 600;
458
+ const left = Math.min(
459
+ Math.max(8, Math.round(hoveredTarget.screenPosition.x + 14)),
460
+ Math.max(8, containerW - 256)
461
+ );
462
+ const top = Math.min(
463
+ Math.max(8, Math.round(hoveredTarget.screenPosition.y + 14)),
464
+ Math.max(8, containerH - 80)
465
+ );
466
+ const isLight = props.theme === "light";
467
+ const content = typeof props.tooltip === "function" ? props.tooltip(hoveredTarget) : hoveredTarget.kind === "content" ? /* @__PURE__ */ jsxs("div", { children: [
468
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: isLight ? "#0f172a" : "#f8fafc" }, children: extractDefaultItemLabel(
469
+ hoveredTarget.item,
470
+ hoveredTarget.layoutContext.id,
471
+ props.getLabel
472
+ ) }),
473
+ hoveredTarget.layoutContext.tags.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: 3, fontSize: 11, color: isLight ? "#475569" : "#94a3b8" }, children: hoveredTarget.layoutContext.tags.map((t) => `#${t.tag}`).slice(0, 5).join(" \xB7 ") })
474
+ ] }) : /* @__PURE__ */ jsxs("div", { children: [
475
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: hoveredTarget.cluster.colorHex }, children: `${hoveredTarget.cluster.code} \xB7 ${hoveredTarget.cluster.defaultLabel}` }),
476
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 3, fontSize: 11, color: isLight ? "#475569" : "#94a3b8" }, children: `${hoveredTarget.cluster.members.length} ${hoveredTarget.cluster.members.length === 1 ? "item" : "items"}` })
477
+ ] });
478
+ return /* @__PURE__ */ jsx(
479
+ "div",
480
+ {
481
+ role: "tooltip",
482
+ style: {
483
+ position: "absolute",
484
+ left,
485
+ top,
486
+ zIndex: 20,
487
+ maxWidth: 244,
488
+ padding: "7px 10px",
489
+ borderRadius: 8,
490
+ background: isLight ? "rgba(255, 255, 255, 0.96)" : "rgba(9, 13, 22, 0.94)",
491
+ border: isLight ? "1px solid rgba(203, 213, 225, 0.95)" : "1px solid rgba(51, 65, 85, 0.85)",
492
+ color: isLight ? "#0f172a" : "#f1f5f9",
493
+ fontSize: 12,
494
+ lineHeight: 1.4,
495
+ pointerEvents: "none",
496
+ boxShadow: isLight ? "0 10px 24px rgba(15, 23, 42, 0.12)" : "0 10px 24px rgba(0, 0, 0, 0.45)"
497
+ },
498
+ children: content
499
+ }
500
+ );
501
+ };
369
502
  return /* @__PURE__ */ jsx(
370
503
  "div",
371
504
  {
@@ -377,7 +510,8 @@ function SimplexTopicGraph(props) {
377
510
  height: "100%",
378
511
  overflow: "hidden",
379
512
  ...props.style
380
- }
513
+ },
514
+ children: renderBuiltInTooltip()
381
515
  }
382
516
  );
383
517
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ReactJsxSvgView.ts","../src/useSimplexGraphController.ts","../src/SimplexTopicGraph.tsx"],"names":["jsx","useRef"],"mappings":"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","file":"index.js","sourcesContent":["import {\n rectToConvexPolygon,\n type ClusterDescriptor,\n type ContentLayoutContext,\n type ConvexGeometry,\n type NodeRenderState,\n} from '@simplex-viz/core';\nimport { SVG_NS, type SvgNodeView } from '@simplex-viz/svg';\nimport type { ReactNode } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\nimport { renderToStaticMarkup } from 'react-dom/server';\n\nexport interface ContentDomainRecord<TItem, TTagMeta, TClusterMeta> {\n readonly item: TItem;\n readonly ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>;\n}\n\n/**\n * Bridges a declarative React JSX render function into an `@simplex-viz/svg` `SvgNodeView`\n * with automatic offscreen `getBBox()` convex hull measurement and cached DOM updates.\n */\nexport class ReactJsxSvgView<TVisual, TDomain> implements SvgNodeView<TVisual> {\n private readonly renderFn: (\n domain: TDomain,\n state: NodeRenderState<TVisual>,\n ) => ReactNode;\n private readonly getKey: (visual: TVisual) => string;\n private readonly domainByKey: ReadonlyMap<string, TDomain>;\n private readonly rootsByGroup = new WeakMap<SVGGElement, Root>();\n private readonly activeRoots = new Set<Root>();\n private measureGroup: SVGGElement | null = null;\n\n constructor(params: {\n readonly renderFn: (domain: TDomain, state: NodeRenderState<TVisual>) => ReactNode;\n readonly getKey: (visual: TVisual) => string;\n readonly domainByKey: ReadonlyMap<string, TDomain>;\n }) {\n this.renderFn = params.renderFn;\n this.getKey = params.getKey;\n this.domainByKey = params.domainByKey;\n }\n\n measureConvexHull(visual: TVisual, measureSvg: SVGSVGElement): ConvexGeometry {\n const key = this.getKey(visual);\n const domain = this.domainByKey.get(key);\n if (!domain) {\n return rectToConvexPolygon(48, 28, 6);\n }\n\n const doc = measureSvg.ownerDocument;\n let bin = measureSvg.querySelector<SVGGElement>('g.sv-measure-bin');\n if (!bin) {\n bin = doc.createElementNS(SVG_NS, 'g');\n bin.setAttribute('class', 'sv-measure-bin');\n bin.setAttribute('visibility', 'hidden');\n bin.setAttribute('pointer-events', 'none');\n bin.setAttribute('transform', 'translate(-99999, -99999)');\n measureSvg.appendChild(bin);\n }\n\n if (!this.measureGroup || this.measureGroup.parentNode !== bin) {\n this.measureGroup = doc.createElementNS(SVG_NS, 'g');\n bin.appendChild(this.measureGroup);\n }\n\n const dummyState: NodeRenderState<TVisual> = {\n id: '__measure__',\n x: 0,\n y: 0,\n scale: 1,\n opacity: 1,\n visual,\n hull: { kind: 'circle', radius: 16 },\n cacheKey: key,\n interaction: {\n isHovered: false,\n isPinned: false,\n isDimmed: false,\n isHighlighted: false,\n },\n transition: null,\n };\n\n const jsx = this.renderFn(domain, dummyState);\n const markup = renderToStaticMarkup(jsx);\n // Parse JSX markup into SVG namespace inside the offscreen measurement group\n const wrapperSvg = doc.createElementNS(SVG_NS, 'svg');\n wrapperSvg.innerHTML = markup;\n while (this.measureGroup.firstChild) {\n this.measureGroup.removeChild(this.measureGroup.firstChild);\n }\n while (wrapperSvg.firstChild) {\n this.measureGroup.appendChild(wrapperSvg.firstChild);\n }\n\n let width = 0;\n let height = 0;\n try {\n if (typeof this.measureGroup.getBBox === 'function') {\n const bbox = this.measureGroup.getBBox();\n width = bbox.width;\n height = bbox.height;\n }\n } catch {\n // Headless DOM without layout engine\n }\n\n if (width <= 1 || height <= 1) {\n let maxHalfW = 0;\n let maxHalfH = 0;\n this.measureGroup.querySelectorAll('rect, circle, text').forEach((el) => {\n const tag = el.tagName.toLowerCase();\n if (tag === 'rect') {\n const w = parseFloat(el.getAttribute('width') ?? '0');\n const h = parseFloat(el.getAttribute('height') ?? '0');\n maxHalfW = Math.max(maxHalfW, w / 2);\n maxHalfH = Math.max(maxHalfH, h / 2);\n } else if (tag === 'circle') {\n const r = parseFloat(el.getAttribute('r') ?? '0');\n maxHalfW = Math.max(maxHalfW, r);\n maxHalfH = Math.max(maxHalfH, r);\n } else if (tag === 'text') {\n const len = (el.textContent ?? '').length;\n const fs = parseFloat(el.getAttribute('font-size') ?? '11');\n maxHalfW = Math.max(maxHalfW, (len * fs * 0.56) / 2);\n maxHalfH = Math.max(maxHalfH, fs * 0.75);\n }\n });\n width = Math.max(24, maxHalfW * 2);\n height = Math.max(20, maxHalfH * 2);\n }\n\n return rectToConvexPolygon(width + 4, height + 4, 6);\n }\n\n mountOrUpdate(\n groupEl: SVGGElement,\n visual: TVisual,\n state: NodeRenderState<TVisual>,\n ): void {\n const key = this.getKey(visual);\n const domain = this.domainByKey.get(key);\n if (!domain) return;\n\n let root = this.rootsByGroup.get(groupEl);\n if (!root) {\n while (groupEl.firstChild) groupEl.removeChild(groupEl.firstChild);\n root = createRoot(groupEl);\n this.rootsByGroup.set(groupEl, root);\n this.activeRoots.add(root);\n }\n\n root.render(this.renderFn(domain, state));\n }\n\n dispose(): void {\n const roots = [...this.activeRoots];\n this.activeRoots.clear();\n this.measureGroup = null;\n queueMicrotask(() => {\n for (const r of roots) {\n r.unmount();\n }\n });\n }\n}\n\nexport type ContentJsxRenderFn<TItem, TContentVisual, TTagMeta, TClusterMeta> = (\n item: TItem,\n ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TContentVisual>,\n) => ReactNode;\n\nexport type ClusterJsxRenderFn<TItem, TClusterVisual, TTagMeta, TClusterMeta> = (\n cluster: ClusterDescriptor<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TClusterVisual>,\n) => ReactNode;\n","import type {\n CameraController,\n ClusterDescriptor,\n DefaultTaggedItem,\n LayoutMetrics,\n} from '@simplex-viz/core';\nimport type { SimplexSvgController } from '@simplex-viz/svg';\nimport { useCallback, useRef } from 'react';\n\n/**\n * Imperative handle bound to `<SimplexTopicGraph controllerRef={...} />`.\n */\nexport interface SimplexGraphHandle<\n TItem = DefaultTaggedItem,\n TContentVisual = unknown,\n TClusterVisual = unknown,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n> {\n addItems(items: TItem | readonly TItem[]): void;\n removeItems(ids: string | readonly string[]): void;\n setItems(items: readonly TItem[]): void;\n resetCamera(): void;\n zoomBy(factor: number): void;\n getMetrics(): LayoutMetrics | null;\n getClusters(): readonly ClusterDescriptor<TItem, TTagMeta, TClusterMeta>[];\n getRawController(): SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null;\n}\n\n/**\n * React hook returning a stable `controllerRef` and imperative transaction helpers\n * (`addItems`, `removeItems`, `setItems`, camera controls, and live telemetry getters).\n */\nexport function useSimplexGraphController<\n TItem = DefaultTaggedItem,\n TContentVisual = unknown,\n TClusterVisual = unknown,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n>(): {\n readonly controllerRef: React.MutableRefObject<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>;\n readonly handle: SimplexGraphHandle<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >;\n} {\n const controllerRef = useRef<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>(null);\n\n const addItems = useCallback((items: TItem | readonly TItem[]) => {\n controllerRef.current?.addItems(items);\n }, []);\n\n const removeItems = useCallback((ids: string | readonly string[]) => {\n controllerRef.current?.removeItems(ids);\n }, []);\n\n const setItems = useCallback((items: readonly TItem[]) => {\n controllerRef.current?.setItems(items);\n }, []);\n\n const resetCamera = useCallback(() => {\n controllerRef.current?.camera.reset();\n }, []);\n\n const zoomBy = useCallback((factor: number) => {\n const ctrl = controllerRef.current;\n if (!ctrl) return;\n const svg = ctrl.getSvgElement();\n const rect = svg.getBoundingClientRect();\n const cam: CameraController = ctrl.camera;\n cam.zoomToCursor(rect.width / 2, rect.height / 2, factor);\n }, []);\n\n const getMetrics = useCallback(() => {\n return controllerRef.current?.getMetrics() ?? null;\n }, []);\n\n const getClusters = useCallback(() => {\n return controllerRef.current?.getClusters() ?? [];\n }, []);\n\n const getRawController = useCallback(() => {\n return controllerRef.current;\n }, []);\n\n return {\n controllerRef,\n handle: {\n addItems,\n removeItems,\n setItems,\n resetCamera,\n zoomBy,\n getMetrics,\n getClusters,\n getRawController,\n },\n };\n}\n","import type {\n ClusterDescriptor,\n ClusterHoverEvent,\n ClusterMetaConfig,\n ContentHoverEvent,\n ContentLayoutContext,\n DefaultTaggedItem,\n HitTarget,\n ItemIdentityConfig,\n LayoutMetrics,\n LayoutSolverOptions,\n MeshOptions,\n NodeRenderState,\n NormalizedPointerEvent,\n VisualResolver,\n WheelBehavior,\n} from '@simplex-viz/core';\nimport {\n SimplexSvgController,\n type SvgNodeView,\n} from '@simplex-viz/svg';\nimport {\n useEffect,\n useRef,\n type CSSProperties,\n type MutableRefObject,\n type ReactNode,\n} from 'react';\nimport {\n ReactJsxSvgView,\n type ContentDomainRecord,\n} from './ReactJsxSvgView';\n\n/**\n * Props for `<SimplexTopicGraph />`, preserving end-to-end generic inference across\n * `TItem`, `TContentVisual`, `TClusterVisual`, `TTagMeta`, and `TClusterMeta`.\n */\nexport type SimplexTopicGraphProps<\n TItem = DefaultTaggedItem,\n TContentVisual = unknown,\n TClusterVisual = unknown,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n> = ItemIdentityConfig<TItem> &\n ClusterMetaConfig<TItem, TTagMeta, TClusterMeta> & {\n readonly items: readonly TItem[];\n readonly visualResolver: VisualResolver<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >;\n /** Portable `@simplex-viz/svg` view plugin for content items. */\n readonly contentView?: SvgNodeView<TContentVisual>;\n /** Portable `@simplex-viz/svg` view plugin for topic clusters. */\n readonly clusterView?: SvgNodeView<TClusterVisual>;\n /** Optional declarative React JSX renderer for content items (auto-measured via `getBBox()`). */\n readonly renderContentNode?: (\n item: TItem,\n ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TContentVisual>,\n ) => ReactNode;\n /** Optional declarative React JSX renderer for topic clusters (auto-measured via `getBBox()`). */\n readonly renderClusterNode?: (\n cluster: ClusterDescriptor<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TClusterVisual>,\n ) => ReactNode;\n readonly normalizeTag?: (rawTag: string) => string;\n readonly resolveTagMeta?: (normalizedTag: string) => TTagMeta | undefined;\n readonly meshOptions?: MeshOptions;\n readonly layoutOptions?: LayoutSolverOptions;\n readonly sizeMultiplier?: number;\n readonly showHulls?: boolean;\n readonly pinnedIds?: ReadonlySet<string> | readonly string[];\n readonly highlightedIds?: ReadonlySet<string> | readonly string[];\n readonly focusedClusterUid?: string | null;\n readonly onContentHover?: (\n event: ContentHoverEvent<TItem, TContentVisual, TTagMeta, TClusterMeta> | null,\n ) => void;\n readonly onClusterHover?: (\n event: ClusterHoverEvent<TItem, TClusterVisual, TTagMeta, TClusterMeta> | null,\n ) => void;\n readonly onLeftClick?: (\n target: HitTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>,\n rawEvent: NormalizedPointerEvent,\n ) => void;\n readonly onRightClick?: (\n target: HitTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>,\n rawEvent: NormalizedPointerEvent,\n ) => boolean | void;\n readonly onWheel?: WheelBehavior;\n readonly onMetricsChange?: (metrics: LayoutMetrics) => void;\n readonly onClustersChange?: (\n clusters: readonly ClusterDescriptor<TItem, TTagMeta, TClusterMeta>[],\n ) => void;\n readonly onFpsUpdate?: (fps: number) => void;\n readonly controllerRef?: MutableRefObject<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>;\n readonly className?: string;\n readonly style?: CSSProperties;\n };\n\n/**\n * Type-safe React visualization component wrapping `@simplex-viz/core` and `@simplex-viz/svg`.\n */\nexport function SimplexTopicGraph<\n TItem = DefaultTaggedItem,\n TContentVisual = unknown,\n TClusterVisual = unknown,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n>(\n props: SimplexTopicGraphProps<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >,\n) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const ctrlRef = useRef<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>(null);\n\n const contentDomainByKeyRef = useRef(\n new Map<string, ContentDomainRecord<TItem, TTagMeta, TClusterMeta>>(),\n );\n const clusterDomainByKeyRef = useRef(\n new Map<string, ClusterDescriptor<TItem, TTagMeta, TClusterMeta>>(),\n );\n const jsxContentViewRef = useRef<ReactJsxSvgView<\n TContentVisual,\n ContentDomainRecord<TItem, TTagMeta, TClusterMeta>\n > | null>(null);\n const jsxClusterViewRef = useRef<ReactJsxSvgView<\n TClusterVisual,\n ClusterDescriptor<TItem, TTagMeta, TClusterMeta>\n > | null>(null);\n\n const latestPropsRef = useRef(props);\n latestPropsRef.current = props;\n\n const buildWrappedResolver = (\n base: VisualResolver<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>,\n ): VisualResolver<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta> => ({\n resolveContentVisual: (item, ctx) => {\n const visual = base.resolveContentVisual(item, ctx);\n const key = base.getVisualCacheKey(visual);\n contentDomainByKeyRef.current.set(key, { item, ctx });\n return visual;\n },\n resolveClusterVisual: (cluster) => {\n const visual = base.resolveClusterVisual(cluster);\n const key = base.getVisualCacheKey(visual);\n clusterDomainByKeyRef.current.set(key, cluster);\n return visual;\n },\n getVisualCacheKey: (visual) => base.getVisualCacheKey(visual),\n });\n\n const resolveActiveViews = (): {\n contentView: SvgNodeView<TContentVisual>;\n clusterView: SvgNodeView<TClusterVisual>;\n } => {\n const p = latestPropsRef.current;\n\n let contentView: SvgNodeView<TContentVisual>;\n if (p.renderContentNode) {\n const renderFn = p.renderContentNode;\n jsxContentViewRef.current?.dispose();\n const jsxView = new ReactJsxSvgView<\n TContentVisual,\n ContentDomainRecord<TItem, TTagMeta, TClusterMeta>\n >({\n renderFn: (domain, state) => renderFn(domain.item, domain.ctx, state),\n getKey: (v) => p.visualResolver.getVisualCacheKey(v),\n domainByKey: contentDomainByKeyRef.current,\n });\n jsxContentViewRef.current = jsxView;\n contentView = jsxView;\n } else if (p.contentView) {\n jsxContentViewRef.current?.dispose();\n jsxContentViewRef.current = null;\n contentView = p.contentView;\n } else {\n contentView = {\n measureConvexHull: () => ({ kind: 'circle', radius: 10 }),\n mountOrUpdate: (groupEl, _visual, state) => {\n const doc = groupEl.ownerDocument;\n let circle = groupEl.querySelector('circle');\n if (!circle) {\n circle = doc.createElementNS('http://www.w3.org/2000/svg', 'circle');\n circle.setAttribute('r', '9');\n groupEl.appendChild(circle);\n }\n circle.setAttribute('fill', state.interaction.isHovered ? '#22d3ee' : '#64748b');\n },\n };\n }\n\n let clusterView: SvgNodeView<TClusterVisual>;\n if (p.renderClusterNode) {\n const renderFn = p.renderClusterNode;\n jsxClusterViewRef.current?.dispose();\n const jsxView = new ReactJsxSvgView<\n TClusterVisual,\n ClusterDescriptor<TItem, TTagMeta, TClusterMeta>\n >({\n renderFn: (cluster, state) => renderFn(cluster, state),\n getKey: (v) => p.visualResolver.getVisualCacheKey(v),\n domainByKey: clusterDomainByKeyRef.current,\n });\n jsxClusterViewRef.current = jsxView;\n clusterView = jsxView;\n } else if (p.clusterView) {\n jsxClusterViewRef.current?.dispose();\n jsxClusterViewRef.current = null;\n clusterView = p.clusterView;\n } else {\n clusterView = {\n measureConvexHull: () => ({ kind: 'circle', radius: 22 }),\n mountOrUpdate: (groupEl, _visual, state) => {\n const doc = groupEl.ownerDocument;\n let circle = groupEl.querySelector('circle');\n if (!circle) {\n circle = doc.createElementNS('http://www.w3.org/2000/svg', 'circle');\n circle.setAttribute('r', '20');\n circle.setAttribute('fill', '#0f172a');\n circle.setAttribute('stroke-width', '1.5');\n groupEl.appendChild(circle);\n }\n circle.setAttribute('stroke', state.interaction.isHovered ? '#f8fafc' : '#38bdf8');\n },\n };\n }\n\n return { contentView, clusterView };\n };\n\n // Mount controller once on container\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const p = latestPropsRef.current;\n const { contentView, clusterView } = resolveActiveViews();\n\n const ctrl = SimplexSvgController.createFromResolved(el, {\n items: p.items,\n getId: p.getId,\n getTags: p.getTags,\n deriveClusterMeta: p.deriveClusterMeta,\n visualResolver: buildWrappedResolver(p.visualResolver),\n contentView,\n clusterView,\n normalizeTag: p.normalizeTag,\n resolveTagMeta: p.resolveTagMeta,\n meshOptions: p.meshOptions,\n layoutOptions: {\n ...p.layoutOptions,\n sizeMultiplier: p.sizeMultiplier ?? p.layoutOptions?.sizeMultiplier ?? 1,\n },\n showHulls: p.showHulls,\n interactions: {\n onContentHover: (ev) => latestPropsRef.current.onContentHover?.(ev),\n onClusterHover: (ev) => latestPropsRef.current.onClusterHover?.(ev),\n onLeftClick: (t, raw) => latestPropsRef.current.onLeftClick?.(t, raw),\n onRightClick: (t, raw) => latestPropsRef.current.onRightClick?.(t, raw),\n onWheel: (ev, cam) => {\n const policy = latestPropsRef.current.onWheel ?? 'zoom-to-cursor';\n if (policy === 'none') return;\n if (typeof policy === 'function') {\n policy(ev, cam);\n return;\n }\n if (policy === 'pan-vertical') {\n cam.panBy(-ev.deltaX, -ev.deltaY);\n return;\n }\n const factor = Math.exp(-ev.deltaY * 0.0014);\n cam.zoomToCursor(ev.screenX, ev.screenY, factor);\n },\n },\n onMetricsChange: (m) => latestPropsRef.current.onMetricsChange?.(m),\n onClustersChange: (c) => latestPropsRef.current.onClustersChange?.(c),\n onFpsUpdate: (fps) => latestPropsRef.current.onFpsUpdate?.(fps),\n });\n ctrlRef.current = ctrl;\n if (p.controllerRef) {\n p.controllerRef.current = ctrl;\n }\n\n return () => {\n ctrl.destroy();\n ctrlRef.current = null;\n if (latestPropsRef.current.controllerRef) {\n latestPropsRef.current.controllerRef.current = null;\n }\n jsxContentViewRef.current?.dispose();\n jsxClusterViewRef.current?.dispose();\n jsxContentViewRef.current = null;\n jsxClusterViewRef.current = null;\n };\n }, []);\n\n // Sync items\n useEffect(() => {\n ctrlRef.current?.setItems(props.items);\n }, [props.items]);\n\n // Sync views & visual resolver (triggers smooth crossfade / morph!)\n useEffect(() => {\n const ctrl = ctrlRef.current;\n if (!ctrl) return;\n const { contentView, clusterView } = resolveActiveViews();\n ctrl.setViews({ contentView, clusterView });\n ctrl.setVisualResolver(buildWrappedResolver(props.visualResolver));\n }, [\n props.visualResolver,\n props.contentView,\n props.clusterView,\n props.renderContentNode,\n props.renderClusterNode,\n ]);\n\n // Sync sizeMultiplier\n useEffect(() => {\n if (props.sizeMultiplier !== undefined) {\n ctrlRef.current?.setSizeMultiplier(props.sizeMultiplier);\n }\n }, [props.sizeMultiplier]);\n\n // Sync showHulls\n useEffect(() => {\n if (props.showHulls !== undefined) {\n ctrlRef.current?.setShowHulls(props.showHulls);\n }\n }, [props.showHulls]);\n\n // Sync pinned / highlighted / focused cluster\n useEffect(() => {\n if (props.pinnedIds !== undefined) {\n ctrlRef.current?.setPinnedIds(props.pinnedIds);\n }\n }, [props.pinnedIds]);\n\n useEffect(() => {\n if (props.highlightedIds !== undefined) {\n ctrlRef.current?.setHighlightedIds(props.highlightedIds);\n }\n }, [props.highlightedIds]);\n\n useEffect(() => {\n if (props.focusedClusterUid !== undefined) {\n ctrlRef.current?.setFocusedClusterUid(props.focusedClusterUid);\n }\n }, [props.focusedClusterUid]);\n\n return (\n <div\n ref={containerRef}\n className={props.className}\n style={{\n position: 'relative',\n width: '100%',\n height: '100%',\n overflow: 'hidden',\n ...props.style,\n }}\n />\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/ReactJsxSvgView.ts","../src/useSimplexGraphController.ts","../src/SimplexTopicGraph.tsx"],"names":["jsx","useRef"],"mappings":"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","file":"index.js","sourcesContent":["import {\n rectToConvexPolygon,\n type ClusterDescriptor,\n type ContentLayoutContext,\n type ConvexGeometry,\n type NodeRenderState,\n} from '@simplex-viz/core';\nimport { SVG_NS, type SvgNodeView } from '@simplex-viz/svg';\nimport type { ReactNode } from 'react';\nimport { createRoot, type Root } from 'react-dom/client';\nimport { renderToStaticMarkup } from 'react-dom/server';\n\nexport interface ContentDomainRecord<TItem, TTagMeta, TClusterMeta> {\n readonly item: TItem;\n readonly ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>;\n}\n\n/**\n * Bridges a declarative React JSX render function into an `@simplex-viz/svg` `SvgNodeView`\n * with automatic offscreen `getBBox()` convex hull measurement and cached DOM updates.\n */\nexport class ReactJsxSvgView<TVisual, TDomain> implements SvgNodeView<TVisual> {\n private readonly renderFn: (\n domain: TDomain,\n state: NodeRenderState<TVisual>,\n ) => ReactNode;\n private readonly getKey: (visual: TVisual) => string;\n private readonly domainByKey: ReadonlyMap<string, TDomain>;\n private readonly rootsByGroup = new WeakMap<SVGGElement, Root>();\n private readonly activeRoots = new Set<Root>();\n private measureGroup: SVGGElement | null = null;\n\n constructor(params: {\n readonly renderFn: (domain: TDomain, state: NodeRenderState<TVisual>) => ReactNode;\n readonly getKey: (visual: TVisual) => string;\n readonly domainByKey: ReadonlyMap<string, TDomain>;\n }) {\n this.renderFn = params.renderFn;\n this.getKey = params.getKey;\n this.domainByKey = params.domainByKey;\n }\n\n measureConvexHull(visual: TVisual, measureSvg: SVGSVGElement): ConvexGeometry {\n const key = this.getKey(visual);\n const domain = this.domainByKey.get(key);\n if (!domain) {\n return rectToConvexPolygon(48, 28, 6);\n }\n\n const doc = measureSvg.ownerDocument;\n let bin = measureSvg.querySelector<SVGGElement>('g.sv-measure-bin');\n if (!bin) {\n bin = doc.createElementNS(SVG_NS, 'g');\n bin.setAttribute('class', 'sv-measure-bin');\n bin.setAttribute('visibility', 'hidden');\n bin.setAttribute('pointer-events', 'none');\n bin.setAttribute('transform', 'translate(-99999, -99999)');\n measureSvg.appendChild(bin);\n }\n\n if (!this.measureGroup || this.measureGroup.parentNode !== bin) {\n this.measureGroup = doc.createElementNS(SVG_NS, 'g');\n bin.appendChild(this.measureGroup);\n }\n\n const dummyState: NodeRenderState<TVisual> = {\n id: '__measure__',\n x: 0,\n y: 0,\n scale: 1,\n opacity: 1,\n visual,\n hull: { kind: 'circle', radius: 16 },\n cacheKey: key,\n interaction: {\n isHovered: false,\n isPinned: false,\n isDimmed: false,\n isHighlighted: false,\n },\n transition: null,\n };\n\n const jsx = this.renderFn(domain, dummyState);\n const markup = renderToStaticMarkup(jsx);\n // Parse JSX markup into SVG namespace inside the offscreen measurement group\n const wrapperSvg = doc.createElementNS(SVG_NS, 'svg');\n wrapperSvg.innerHTML = markup;\n while (this.measureGroup.firstChild) {\n this.measureGroup.removeChild(this.measureGroup.firstChild);\n }\n while (wrapperSvg.firstChild) {\n this.measureGroup.appendChild(wrapperSvg.firstChild);\n }\n\n let width = 0;\n let height = 0;\n try {\n if (typeof this.measureGroup.getBBox === 'function') {\n const bbox = this.measureGroup.getBBox();\n width = bbox.width;\n height = bbox.height;\n }\n } catch {\n // Headless DOM without layout engine\n }\n\n if (width <= 1 || height <= 1) {\n let maxHalfW = 0;\n let maxHalfH = 0;\n this.measureGroup.querySelectorAll('rect, circle, text').forEach((el) => {\n const tag = el.tagName.toLowerCase();\n if (tag === 'rect') {\n const w = parseFloat(el.getAttribute('width') ?? '0');\n const h = parseFloat(el.getAttribute('height') ?? '0');\n maxHalfW = Math.max(maxHalfW, w / 2);\n maxHalfH = Math.max(maxHalfH, h / 2);\n } else if (tag === 'circle') {\n const r = parseFloat(el.getAttribute('r') ?? '0');\n maxHalfW = Math.max(maxHalfW, r);\n maxHalfH = Math.max(maxHalfH, r);\n } else if (tag === 'text') {\n const len = (el.textContent ?? '').length;\n const fs = parseFloat(el.getAttribute('font-size') ?? '11');\n maxHalfW = Math.max(maxHalfW, (len * fs * 0.56) / 2);\n maxHalfH = Math.max(maxHalfH, fs * 0.75);\n }\n });\n width = Math.max(24, maxHalfW * 2);\n height = Math.max(20, maxHalfH * 2);\n }\n\n return rectToConvexPolygon(width + 4, height + 4, 6);\n }\n\n mountOrUpdate(\n groupEl: SVGGElement,\n visual: TVisual,\n state: NodeRenderState<TVisual>,\n ): void {\n const key = this.getKey(visual);\n const domain = this.domainByKey.get(key);\n if (!domain) return;\n\n let root = this.rootsByGroup.get(groupEl);\n if (!root) {\n while (groupEl.firstChild) groupEl.removeChild(groupEl.firstChild);\n root = createRoot(groupEl);\n this.rootsByGroup.set(groupEl, root);\n this.activeRoots.add(root);\n }\n\n root.render(this.renderFn(domain, state));\n }\n\n dispose(): void {\n const roots = [...this.activeRoots];\n this.activeRoots.clear();\n this.measureGroup = null;\n queueMicrotask(() => {\n for (const r of roots) {\n r.unmount();\n }\n });\n }\n}\n\nexport type ContentJsxRenderFn<TItem, TContentVisual, TTagMeta, TClusterMeta> = (\n item: TItem,\n ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TContentVisual>,\n) => ReactNode;\n\nexport type ClusterJsxRenderFn<TItem, TClusterVisual, TTagMeta, TClusterMeta> = (\n cluster: ClusterDescriptor<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TClusterVisual>,\n) => ReactNode;\n","import type {\n CameraController,\n ClusterDescriptor,\n DefaultTaggedItem,\n LayoutMetrics,\n} from '@simplex-viz/core';\nimport type { SimplexSvgController } from '@simplex-viz/svg';\nimport { useCallback, useRef } from 'react';\n\n/**\n * Imperative handle bound to `<SimplexTopicGraph controllerRef={...} />`.\n */\nexport interface SimplexGraphHandle<\n TItem = DefaultTaggedItem,\n TContentVisual = unknown,\n TClusterVisual = unknown,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n> {\n addItems(items: TItem | readonly TItem[]): void;\n removeItems(ids: string | readonly string[]): void;\n setItems(items: readonly TItem[]): void;\n resetCamera(): void;\n zoomBy(factor: number): void;\n getMetrics(): LayoutMetrics | null;\n getClusters(): readonly ClusterDescriptor<TItem, TTagMeta, TClusterMeta>[];\n getRawController(): SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null;\n}\n\n/**\n * React hook returning a stable `controllerRef` and imperative transaction helpers\n * (`addItems`, `removeItems`, `setItems`, camera controls, and live telemetry getters).\n */\nexport function useSimplexGraphController<\n TItem = DefaultTaggedItem,\n TContentVisual = unknown,\n TClusterVisual = unknown,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n>(): {\n readonly controllerRef: React.MutableRefObject<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>;\n readonly handle: SimplexGraphHandle<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >;\n} {\n const controllerRef = useRef<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>(null);\n\n const addItems = useCallback((items: TItem | readonly TItem[]) => {\n controllerRef.current?.addItems(items);\n }, []);\n\n const removeItems = useCallback((ids: string | readonly string[]) => {\n controllerRef.current?.removeItems(ids);\n }, []);\n\n const setItems = useCallback((items: readonly TItem[]) => {\n controllerRef.current?.setItems(items);\n }, []);\n\n const resetCamera = useCallback(() => {\n controllerRef.current?.camera.reset();\n }, []);\n\n const zoomBy = useCallback((factor: number) => {\n const ctrl = controllerRef.current;\n if (!ctrl) return;\n const svg = ctrl.getSvgElement();\n const rect = svg.getBoundingClientRect();\n const cam: CameraController = ctrl.camera;\n cam.zoomToCursor(rect.width / 2, rect.height / 2, factor);\n }, []);\n\n const getMetrics = useCallback(() => {\n return controllerRef.current?.getMetrics() ?? null;\n }, []);\n\n const getClusters = useCallback(() => {\n return controllerRef.current?.getClusters() ?? [];\n }, []);\n\n const getRawController = useCallback(() => {\n return controllerRef.current;\n }, []);\n\n return {\n controllerRef,\n handle: {\n addItems,\n removeItems,\n setItems,\n resetCamera,\n zoomBy,\n getMetrics,\n getClusters,\n getRawController,\n },\n };\n}\n","import type {\n ClusterDescriptor,\n ClusterHoverEvent,\n ClusterMetaConfig,\n ContentHoverEvent,\n ContentLayoutContext,\n DefaultTaggedItem,\n HitTarget,\n ItemIdentityConfig,\n LayoutMetrics,\n LayoutSolverOptions,\n MeshOptions,\n NodeRenderState,\n NormalizedPointerEvent,\n VisualResolver,\n WheelBehavior,\n} from '@simplex-viz/core';\nimport {\n SimplexSvgController,\n createPresetViews,\n createPresetVisualResolver,\n extractDefaultItemLabel,\n type DefaultClusterPresetVisual,\n type DefaultContentPresetVisual,\n type SimplexTheme,\n type SimplexVisualPreset,\n type SvgNodeView,\n} from '@simplex-viz/svg';\nimport {\n useEffect,\n useRef,\n useState,\n type CSSProperties,\n type MutableRefObject,\n type ReactNode,\n} from 'react';\nimport {\n ReactJsxSvgView,\n type ContentDomainRecord,\n} from './ReactJsxSvgView';\n\n/**\n * Union payload passed to a custom React `tooltip` formatter on `<SimplexTopicGraph />`.\n */\nexport type SimplexReactTooltipTarget<\n TItem = DefaultTaggedItem,\n TContentVisual = DefaultContentPresetVisual,\n TClusterVisual = DefaultClusterPresetVisual,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n> =\n | ({ readonly kind: 'content' } & ContentHoverEvent<\n TItem,\n TContentVisual,\n TTagMeta,\n TClusterMeta\n >)\n | ({ readonly kind: 'cluster' } & ClusterHoverEvent<\n TItem,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >);\n\n/**\n * Props for `<SimplexTopicGraph />`, preserving end-to-end generic inference across\n * `TItem`, `TContentVisual`, `TClusterVisual`, `TTagMeta`, and `TClusterMeta`.\n *\n * Works zero-config with `<SimplexTopicGraph items={items} />` via built-in presets,\n * or with keyless declarative JSX renderers (`renderContentNode` / `renderClusterNode`),\n * or with full `VisualResolver` + `SvgNodeView` customization.\n */\nexport type SimplexTopicGraphProps<\n TItem = DefaultTaggedItem,\n TContentVisual = DefaultContentPresetVisual,\n TClusterVisual = DefaultClusterPresetVisual,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n> = ItemIdentityConfig<TItem> &\n ClusterMetaConfig<TItem, TTagMeta, TClusterMeta> & {\n readonly items: readonly TItem[];\n /** Built-in visual preset used when custom views / `visualResolver` are omitted. Default: `'geometric'`. */\n readonly preset?: SimplexVisualPreset;\n /** Visual color theme (`'dark'` or `'light'`) for built-in presets and tooltips. Default: `'dark'`. */\n readonly theme?: SimplexTheme;\n /** Optional label extractor for built-in presets and default hover tooltips. */\n readonly getLabel?: (item: TItem) => string;\n /**\n * Optional `VisualResolver` mapping items and clusters to pure visual descriptors.\n * Optional in both zero-config preset mode and declarative JSX renderer mode.\n */\n readonly visualResolver?: VisualResolver<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >;\n /** Optional cache key override when using `renderContentNode` without `visualResolver`. */\n readonly getContentCacheKey?: (\n item: TItem,\n ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>,\n ) => string;\n /** Optional cache key override when using `renderClusterNode` without `visualResolver`. */\n readonly getClusterCacheKey?: (\n cluster: ClusterDescriptor<TItem, TTagMeta, TClusterMeta>,\n ) => string;\n /** Portable `@simplex-viz/svg` view plugin for content items. */\n readonly contentView?: SvgNodeView<TContentVisual>;\n /** Portable `@simplex-viz/svg` view plugin for topic clusters. */\n readonly clusterView?: SvgNodeView<TClusterVisual>;\n /** Optional declarative React JSX renderer for content items (auto-measured via `getBBox()`). */\n readonly renderContentNode?: (\n item: TItem,\n ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TContentVisual>,\n ) => ReactNode;\n /** Optional declarative React JSX renderer for topic clusters (auto-measured via `getBBox()`). */\n readonly renderClusterNode?: (\n cluster: ClusterDescriptor<TItem, TTagMeta, TClusterMeta>,\n state: NodeRenderState<TClusterVisual>,\n ) => ReactNode;\n readonly normalizeTag?: (rawTag: string) => string;\n readonly resolveTagMeta?: (normalizedTag: string) => TTagMeta | undefined;\n readonly meshOptions?: MeshOptions;\n readonly layoutOptions?: LayoutSolverOptions;\n readonly sizeMultiplier?: number;\n readonly showHulls?: boolean;\n readonly pinnedIds?: ReadonlySet<string> | readonly string[];\n readonly highlightedIds?: ReadonlySet<string> | readonly string[];\n readonly focusedClusterUid?: string | null;\n /**\n * Enables built-in cluster focus toggle on click when `focusedClusterUid` and `onLeftClick`\n * are not controlled externally. Default: `true`.\n */\n readonly focusOnClusterClick?: boolean;\n /**\n * Enables a built-in floating hover tooltip, or accepts a custom `(target) => ReactNode` formatter.\n * Default: `false`.\n */\n readonly tooltip?:\n | boolean\n | ((\n target: SimplexReactTooltipTarget<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >,\n ) => ReactNode);\n readonly onContentHover?: (\n event: ContentHoverEvent<TItem, TContentVisual, TTagMeta, TClusterMeta> | null,\n ) => void;\n readonly onClusterHover?: (\n event: ClusterHoverEvent<TItem, TClusterVisual, TTagMeta, TClusterMeta> | null,\n ) => void;\n readonly onLeftClick?: (\n target: HitTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>,\n rawEvent: NormalizedPointerEvent,\n ) => void;\n readonly onRightClick?: (\n target: HitTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>,\n rawEvent: NormalizedPointerEvent,\n ) => boolean | void;\n readonly onWheel?: WheelBehavior;\n readonly onMetricsChange?: (metrics: LayoutMetrics) => void;\n readonly onClustersChange?: (\n clusters: readonly ClusterDescriptor<TItem, TTagMeta, TClusterMeta>[],\n ) => void;\n readonly onFpsUpdate?: (fps: number) => void;\n readonly controllerRef?: MutableRefObject<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>;\n readonly className?: string;\n readonly style?: CSSProperties;\n };\n\ninterface AutoKeyVisual {\n readonly __svAutoKey: string;\n}\n\nfunction isAutoKeyVisual(v: unknown): v is AutoKeyVisual {\n return (\n typeof v === 'object' &&\n v !== null &&\n '__svAutoKey' in v &&\n typeof (v as AutoKeyVisual).__svAutoKey === 'string'\n );\n}\n\n/**\n * Type-safe React visualization component wrapping `@simplex-viz/core` and `@simplex-viz/svg`.\n */\nexport function SimplexTopicGraph<\n TItem = DefaultTaggedItem,\n TContentVisual = DefaultContentPresetVisual,\n TClusterVisual = DefaultClusterPresetVisual,\n TTagMeta = undefined,\n TClusterMeta = undefined,\n>(\n props: SimplexTopicGraphProps<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >,\n) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const ctrlRef = useRef<SimplexSvgController<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>(null);\n\n const [hoveredTarget, setHoveredTarget] = useState<SimplexReactTooltipTarget<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > | null>(null);\n\n const internalFocusedUidRef = useRef<string | null>(null);\n\n const contentDomainByKeyRef = useRef(\n new Map<string, ContentDomainRecord<TItem, TTagMeta, TClusterMeta>>(),\n );\n const clusterDomainByKeyRef = useRef(\n new Map<string, ClusterDescriptor<TItem, TTagMeta, TClusterMeta>>(),\n );\n const jsxContentViewRef = useRef<ReactJsxSvgView<\n TContentVisual,\n ContentDomainRecord<TItem, TTagMeta, TClusterMeta>\n > | null>(null);\n const jsxClusterViewRef = useRef<ReactJsxSvgView<\n TClusterVisual,\n ClusterDescriptor<TItem, TTagMeta, TClusterMeta>\n > | null>(null);\n\n const latestPropsRef = useRef(props);\n latestPropsRef.current = props;\n\n const resolveEffectiveResolver = (): VisualResolver<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n > => {\n const p = latestPropsRef.current;\n const preset: SimplexVisualPreset = p.preset ?? 'geometric';\n const theme: SimplexTheme = p.theme ?? 'dark';\n const presetResolver = createPresetVisualResolver<TItem, TTagMeta, TClusterMeta>(\n preset,\n (item) => {\n const curGetLabel = latestPropsRef.current.getLabel;\n if (curGetLabel) return curGetLabel(item);\n if (typeof item === 'object' && item !== null) {\n if ('label' in item && typeof item.label === 'string' && item.label.length > 0) {\n return item.label;\n }\n if ('title' in item && typeof item.title === 'string' && item.title.length > 0) {\n return item.title;\n }\n if ('headline' in item && typeof item.headline === 'string' && item.headline.length > 0) {\n return item.headline;\n }\n if ('name' in item && typeof item.name === 'string' && item.name.length > 0) {\n return item.name;\n }\n }\n return '';\n },\n theme,\n );\n const base = p.visualResolver;\n\n const getKey = (visual: TContentVisual | TClusterVisual): string => {\n if (isAutoKeyVisual(visual)) {\n return visual.__svAutoKey;\n }\n if (base) {\n return base.getVisualCacheKey(visual);\n }\n return presetResolver.getVisualCacheKey(\n visual as unknown as DefaultContentPresetVisual | DefaultClusterPresetVisual,\n );\n };\n\n return {\n resolveContentVisual: (item, ctx) => {\n const cur = latestPropsRef.current;\n let visual: TContentVisual;\n if (base) {\n visual = base.resolveContentVisual(item, ctx);\n } else if (cur.renderContentNode) {\n const autoKey = cur.getContentCacheKey\n ? cur.getContentCacheKey(item, ctx)\n : `${ctx.id}:${ctx.mixedColorHex}:${ctx.tags.map((t) => t.tag).join(',')}`;\n visual = { __svAutoKey: autoKey } as unknown as TContentVisual;\n } else {\n visual = presetResolver.resolveContentVisual(item, ctx) as unknown as TContentVisual;\n }\n const key = getKey(visual);\n contentDomainByKeyRef.current.set(key, { item, ctx });\n return visual;\n },\n resolveClusterVisual: (cluster) => {\n const cur = latestPropsRef.current;\n let visual: TClusterVisual;\n if (base) {\n visual = base.resolveClusterVisual(cluster);\n } else if (cur.renderClusterNode) {\n const autoKey = cur.getClusterCacheKey\n ? cur.getClusterCacheKey(cluster)\n : `${cluster.clusterUid}:${cluster.defaultLabel}:${cluster.members.length}:${cluster.colorHex}`;\n visual = { __svAutoKey: autoKey } as unknown as TClusterVisual;\n } else {\n visual = presetResolver.resolveClusterVisual(cluster) as unknown as TClusterVisual;\n }\n const key = getKey(visual);\n clusterDomainByKeyRef.current.set(key, cluster);\n return visual;\n },\n getVisualCacheKey: getKey,\n };\n };\n\n const resolveActiveViews = (\n activeResolver: VisualResolver<\n TItem,\n TContentVisual,\n TClusterVisual,\n TTagMeta,\n TClusterMeta\n >,\n ): {\n contentView: SvgNodeView<TContentVisual>;\n clusterView: SvgNodeView<TClusterVisual>;\n } => {\n const p = latestPropsRef.current;\n const presetViews = createPresetViews(p.preset ?? 'geometric');\n\n let contentView: SvgNodeView<TContentVisual>;\n if (p.renderContentNode) {\n const renderFn = p.renderContentNode;\n jsxContentViewRef.current?.dispose();\n const jsxView = new ReactJsxSvgView<\n TContentVisual,\n ContentDomainRecord<TItem, TTagMeta, TClusterMeta>\n >({\n renderFn: (domain, state) => renderFn(domain.item, domain.ctx, state),\n getKey: (v) => activeResolver.getVisualCacheKey(v),\n domainByKey: contentDomainByKeyRef.current,\n });\n jsxContentViewRef.current = jsxView;\n contentView = jsxView;\n } else if (p.contentView) {\n jsxContentViewRef.current?.dispose();\n jsxContentViewRef.current = null;\n contentView = p.contentView;\n } else {\n jsxContentViewRef.current?.dispose();\n jsxContentViewRef.current = null;\n contentView = presetViews.contentView as unknown as SvgNodeView<TContentVisual>;\n }\n\n let clusterView: SvgNodeView<TClusterVisual>;\n if (p.renderClusterNode) {\n const renderFn = p.renderClusterNode;\n jsxClusterViewRef.current?.dispose();\n const jsxView = new ReactJsxSvgView<\n TClusterVisual,\n ClusterDescriptor<TItem, TTagMeta, TClusterMeta>\n >({\n renderFn: (cluster, state) => renderFn(cluster, state),\n getKey: (v) => activeResolver.getVisualCacheKey(v),\n domainByKey: clusterDomainByKeyRef.current,\n });\n jsxClusterViewRef.current = jsxView;\n clusterView = jsxView;\n } else if (p.clusterView) {\n jsxClusterViewRef.current?.dispose();\n jsxClusterViewRef.current = null;\n clusterView = p.clusterView;\n } else {\n jsxClusterViewRef.current?.dispose();\n jsxClusterViewRef.current = null;\n clusterView = presetViews.clusterView as unknown as SvgNodeView<TClusterVisual>;\n }\n\n return { contentView, clusterView };\n };\n\n // Mount controller once on container\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n const p = latestPropsRef.current;\n const effectiveResolver = resolveEffectiveResolver();\n const { contentView, clusterView } = resolveActiveViews(effectiveResolver);\n\n const ctrl = SimplexSvgController.createFromResolved(el, {\n items: p.items,\n getId: p.getId,\n getTags: p.getTags,\n getLabel: p.getLabel,\n deriveClusterMeta: p.deriveClusterMeta,\n preset: p.preset,\n theme: p.theme,\n visualResolver: effectiveResolver,\n contentView,\n clusterView,\n normalizeTag: p.normalizeTag,\n resolveTagMeta: p.resolveTagMeta,\n meshOptions: p.meshOptions,\n layoutOptions: {\n ...p.layoutOptions,\n sizeMultiplier: p.sizeMultiplier ?? p.layoutOptions?.sizeMultiplier ?? 1,\n },\n showHulls: p.showHulls,\n focusOnClusterClick: false,\n interactions: {\n onContentHover: (ev) => {\n latestPropsRef.current.onContentHover?.(ev);\n if (latestPropsRef.current.tooltip) {\n setHoveredTarget(ev ? { kind: 'content', ...ev } : null);\n }\n },\n onClusterHover: (ev) => {\n latestPropsRef.current.onClusterHover?.(ev);\n if (latestPropsRef.current.tooltip) {\n setHoveredTarget(ev ? { kind: 'cluster', ...ev } : null);\n }\n },\n onLeftClick: (target, raw) => {\n const cur = latestPropsRef.current;\n if (cur.onLeftClick) {\n cur.onLeftClick(target, raw);\n return;\n }\n if (cur.focusedClusterUid === undefined && (cur.focusOnClusterClick ?? true)) {\n if (target.kind === 'cluster') {\n const next =\n internalFocusedUidRef.current === target.cluster.clusterUid\n ? null\n : target.cluster.clusterUid;\n internalFocusedUidRef.current = next;\n ctrlRef.current?.setFocusedClusterUid(next);\n } else if (\n target.kind === 'background' &&\n internalFocusedUidRef.current !== null\n ) {\n internalFocusedUidRef.current = null;\n ctrlRef.current?.setFocusedClusterUid(null);\n }\n }\n },\n onRightClick: (t, raw) => latestPropsRef.current.onRightClick?.(t, raw),\n onWheel: (ev, cam) => {\n const policy = latestPropsRef.current.onWheel ?? 'zoom-to-cursor';\n if (policy === 'none') return;\n if (typeof policy === 'function') {\n policy(ev, cam);\n return;\n }\n if (policy === 'pan-vertical') {\n cam.panBy(-ev.deltaX, -ev.deltaY);\n return;\n }\n const factor = Math.exp(-ev.deltaY * 0.0014);\n cam.zoomToCursor(ev.screenX, ev.screenY, factor);\n },\n },\n onMetricsChange: (m) => latestPropsRef.current.onMetricsChange?.(m),\n onClustersChange: (c) => latestPropsRef.current.onClustersChange?.(c),\n onFpsUpdate: (fps) => latestPropsRef.current.onFpsUpdate?.(fps),\n });\n ctrlRef.current = ctrl;\n if (p.controllerRef) {\n p.controllerRef.current = ctrl;\n }\n\n return () => {\n ctrl.destroy();\n ctrlRef.current = null;\n if (latestPropsRef.current.controllerRef) {\n latestPropsRef.current.controllerRef.current = null;\n }\n jsxContentViewRef.current?.dispose();\n jsxClusterViewRef.current?.dispose();\n jsxContentViewRef.current = null;\n jsxClusterViewRef.current = null;\n };\n }, []);\n\n // Sync items\n useEffect(() => {\n ctrlRef.current?.setItems(props.items);\n }, [props.items]);\n\n // Sync views, presets, theme & visual resolver (triggers smooth crossfade / morph!)\n useEffect(() => {\n const ctrl = ctrlRef.current;\n if (!ctrl) return;\n ctrl.setTheme(props.theme ?? 'dark');\n const effectiveResolver = resolveEffectiveResolver();\n const { contentView, clusterView } = resolveActiveViews(effectiveResolver);\n ctrl.setViews({ contentView, clusterView });\n ctrl.setVisualResolver(effectiveResolver);\n }, [\n props.preset,\n props.theme,\n props.visualResolver,\n props.contentView,\n props.clusterView,\n props.renderContentNode,\n props.renderClusterNode,\n ]);\n\n // Sync sizeMultiplier\n useEffect(() => {\n if (props.sizeMultiplier !== undefined) {\n ctrlRef.current?.setSizeMultiplier(props.sizeMultiplier);\n }\n }, [props.sizeMultiplier]);\n\n // Sync showHulls\n useEffect(() => {\n if (props.showHulls !== undefined) {\n ctrlRef.current?.setShowHulls(props.showHulls);\n }\n }, [props.showHulls]);\n\n // Sync pinned / highlighted / focused cluster\n useEffect(() => {\n if (props.pinnedIds !== undefined) {\n ctrlRef.current?.setPinnedIds(props.pinnedIds);\n }\n }, [props.pinnedIds]);\n\n useEffect(() => {\n if (props.highlightedIds !== undefined) {\n ctrlRef.current?.setHighlightedIds(props.highlightedIds);\n }\n }, [props.highlightedIds]);\n\n useEffect(() => {\n if (props.focusedClusterUid !== undefined) {\n internalFocusedUidRef.current = props.focusedClusterUid;\n ctrlRef.current?.setFocusedClusterUid(props.focusedClusterUid);\n }\n }, [props.focusedClusterUid]);\n\n // Clear built-in tooltip if prop toggled off\n useEffect(() => {\n if (!props.tooltip) {\n setHoveredTarget(null);\n }\n }, [props.tooltip]);\n\n const renderBuiltInTooltip = () => {\n if (!props.tooltip || !hoveredTarget) return null;\n\n const containerW = containerRef.current?.clientWidth || 960;\n const containerH = containerRef.current?.clientHeight || 600;\n const left = Math.min(\n Math.max(8, Math.round(hoveredTarget.screenPosition.x + 14)),\n Math.max(8, containerW - 256),\n );\n const top = Math.min(\n Math.max(8, Math.round(hoveredTarget.screenPosition.y + 14)),\n Math.max(8, containerH - 80),\n );\n\n const isLight = props.theme === 'light';\n const content =\n typeof props.tooltip === 'function' ? (\n props.tooltip(hoveredTarget)\n ) : hoveredTarget.kind === 'content' ? (\n <div>\n <div style={{ fontWeight: 600, color: isLight ? '#0f172a' : '#f8fafc' }}>\n {extractDefaultItemLabel(\n hoveredTarget.item,\n hoveredTarget.layoutContext.id,\n props.getLabel,\n )}\n </div>\n {hoveredTarget.layoutContext.tags.length > 0 && (\n <div style={{ marginTop: 3, fontSize: 11, color: isLight ? '#475569' : '#94a3b8' }}>\n {hoveredTarget.layoutContext.tags\n .map((t) => `#${t.tag}`)\n .slice(0, 5)\n .join(' · ')}\n </div>\n )}\n </div>\n ) : (\n <div>\n <div style={{ fontWeight: 600, color: hoveredTarget.cluster.colorHex }}>\n {`${hoveredTarget.cluster.code} · ${hoveredTarget.cluster.defaultLabel}`}\n </div>\n <div style={{ marginTop: 3, fontSize: 11, color: isLight ? '#475569' : '#94a3b8' }}>\n {`${hoveredTarget.cluster.members.length} ${\n hoveredTarget.cluster.members.length === 1 ? 'item' : 'items'\n }`}\n </div>\n </div>\n );\n\n return (\n <div\n role=\"tooltip\"\n style={{\n position: 'absolute',\n left,\n top,\n zIndex: 20,\n maxWidth: 244,\n padding: '7px 10px',\n borderRadius: 8,\n background: isLight ? 'rgba(255, 255, 255, 0.96)' : 'rgba(9, 13, 22, 0.94)',\n border: isLight\n ? '1px solid rgba(203, 213, 225, 0.95)'\n : '1px solid rgba(51, 65, 85, 0.85)',\n color: isLight ? '#0f172a' : '#f1f5f9',\n fontSize: 12,\n lineHeight: 1.4,\n pointerEvents: 'none',\n boxShadow: isLight\n ? '0 10px 24px rgba(15, 23, 42, 0.12)'\n : '0 10px 24px rgba(0, 0, 0, 0.45)',\n }}\n >\n {content}\n </div>\n );\n };\n\n return (\n <div\n ref={containerRef}\n className={props.className}\n style={{\n position: 'relative',\n width: '100%',\n height: '100%',\n overflow: 'hidden',\n ...props.style,\n }}\n >\n {renderBuiltInTooltip()}\n </div>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplex-viz/react",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "End-to-end type-safe React bindings, declarative JSX node renderers, and hooks for @simplex-viz/core and @simplex-viz/svg.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,8 +20,8 @@
20
20
  "LICENSE"
21
21
  ],
22
22
  "dependencies": {
23
- "@simplex-viz/core": "^0.1.3",
24
- "@simplex-viz/svg": "^0.1.3"
23
+ "@simplex-viz/core": "^0.2.1",
24
+ "@simplex-viz/svg": "^0.2.1"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": ">=18.0.0",