@simplex-viz/react 0.1.3 → 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.
package/README.md CHANGED
@@ -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,37 @@
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 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
+ /** Optional label extractor for built-in presets and default hover tooltips. */
25
+ readonly getLabel?: (item: TItem) => string;
26
+ /**
27
+ * Optional `VisualResolver` mapping items and clusters to pure visual descriptors.
28
+ * Optional in both zero-config preset mode and declarative JSX renderer mode.
29
+ */
30
+ readonly visualResolver?: VisualResolver<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>;
31
+ /** Optional cache key override when using `renderContentNode` without `visualResolver`. */
32
+ readonly getContentCacheKey?: (item: TItem, ctx: ContentLayoutContext<TItem, TTagMeta, TClusterMeta>) => string;
33
+ /** Optional cache key override when using `renderClusterNode` without `visualResolver`. */
34
+ readonly getClusterCacheKey?: (cluster: ClusterDescriptor<TItem, TTagMeta, TClusterMeta>) => string;
11
35
  /** Portable `@simplex-viz/svg` view plugin for content items. */
12
36
  readonly contentView?: SvgNodeView<TContentVisual>;
13
37
  /** Portable `@simplex-viz/svg` view plugin for topic clusters. */
@@ -25,6 +49,16 @@ export type SimplexTopicGraphProps<TItem = DefaultTaggedItem, TContentVisual = u
25
49
  readonly pinnedIds?: ReadonlySet<string> | readonly string[];
26
50
  readonly highlightedIds?: ReadonlySet<string> | readonly string[];
27
51
  readonly focusedClusterUid?: string | null;
52
+ /**
53
+ * Enables built-in cluster focus toggle on click when `focusedClusterUid` and `onLeftClick`
54
+ * are not controlled externally. Default: `true`.
55
+ */
56
+ readonly focusOnClusterClick?: boolean;
57
+ /**
58
+ * Enables a built-in floating hover tooltip, or accepts a custom `(target) => ReactNode` formatter.
59
+ * Default: `false`.
60
+ */
61
+ readonly tooltip?: boolean | ((target: SimplexReactTooltipTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>) => ReactNode);
28
62
  readonly onContentHover?: (event: ContentHoverEvent<TItem, TContentVisual, TTagMeta, TClusterMeta> | null) => void;
29
63
  readonly onClusterHover?: (event: ClusterHoverEvent<TItem, TClusterVisual, TTagMeta, TClusterMeta> | null) => void;
30
64
  readonly onLeftClick?: (target: HitTarget<TItem, TContentVisual, TClusterVisual, TTagMeta, TClusterMeta>, rawEvent: NormalizedPointerEvent) => void;
@@ -40,5 +74,5 @@ export type SimplexTopicGraphProps<TItem = DefaultTaggedItem, TContentVisual = u
40
74
  /**
41
75
  * Type-safe React visualization component wrapping `@simplex-viz/core` and `@simplex-viz/svg`.
42
76
  */
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;
77
+ 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
78
  //# 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":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EACjB,SAAS,EACT,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,aAAa,EACd,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,oBAAoB,EAIpB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,WAAW,EACjB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAIL,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAMf;;GAEG;AACH,MAAM,MAAM,yBAAyB,CACnC,KAAK,GAAG,iBAAiB,EACzB,cAAc,GAAG,0BAA0B,EAC3C,cAAc,GAAG,0BAA0B,EAC3C,QAAQ,GAAG,SAAS,EACpB,YAAY,GAAG,SAAS,IAEtB,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,iBAAiB,CAC/C,KAAK,EACL,cAAc,EACd,QAAQ,EACR,YAAY,CACb,CAAC,GACF,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG,iBAAiB,CAC/C,KAAK,EACL,cAAc,EACd,QAAQ,EACR,YAAY,CACb,CAAC,CAAC;AAEP;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,CAChC,KAAK,GAAG,iBAAiB,EACzB,cAAc,GAAG,0BAA0B,EAC3C,cAAc,GAAG,0BAA0B,EAC3C,QAAQ,GAAG,SAAS,EACpB,YAAY,GAAG,SAAS,IACtB,kBAAkB,CAAC,KAAK,CAAC,GAC3B,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG;IACjD,QAAQ,CAAC,KAAK,EAAE,SAAS,KAAK,EAAE,CAAC;IACjC,4GAA4G;IAC5G,QAAQ,CAAC,MAAM,CAAC,EAAE,mBAAmB,CAAC;IACtC,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,MAAM,CAAC;IAC5C;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CACtC,KAAK,EACL,cAAc,EACd,cAAc,EACd,QAAQ,EACR,YAAY,CACb,CAAC;IACF,2FAA2F;IAC3F,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAC5B,IAAI,EAAE,KAAK,EACX,GAAG,EAAE,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,CAAC,KACrD,MAAM,CAAC;IACZ,2FAA2F;IAC3F,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAC5B,OAAO,EAAE,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,CAAC,KACtD,MAAM,CAAC;IACZ,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IACnD,kEAAkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IACnD,iGAAiG;IACjG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAC3B,IAAI,EAAE,KAAK,EACX,GAAG,EAAE,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,CAAC,EACxD,KAAK,EAAE,eAAe,CAAC,cAAc,CAAC,KACnC,SAAS,CAAC;IACf,kGAAkG;IAClG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAC3B,OAAO,EAAE,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,CAAC,EACzD,KAAK,EAAE,eAAe,CAAC,cAAc,CAAC,KACnC,SAAS,CAAC;IACf,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACnD,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,QAAQ,GAAG,SAAS,CAAC;IAC1E,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IAC7C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC;IAC7D,QAAQ,CAAC,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,SAAS,MAAM,EAAE,CAAC;IAClE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C;;;OAGG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EACb,OAAO,GACP,CAAC,CACC,MAAM,EAAE,yBAAyB,CAC/B,KAAK,EACL,cAAc,EACd,cAAc,EACd,QAAQ,EACR,YAAY,CACb,KACE,SAAS,CAAC,CAAC;IACpB,QAAQ,CAAC,cAAc,CAAC,EAAE,CACxB,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,IAAI,KAC3E,IAAI,CAAC;IACV,QAAQ,CAAC,cAAc,CAAC,EAAE,CACxB,KAAK,EAAE,iBAAiB,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,GAAG,IAAI,KAC3E,IAAI,CAAC;IACV,QAAQ,CAAC,WAAW,CAAC,EAAE,CACrB,MAAM,EAAE,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,EAChF,QAAQ,EAAE,sBAAsB,KAC7B,IAAI,CAAC;IACV,QAAQ,CAAC,YAAY,CAAC,EAAE,CACtB,MAAM,EAAE,SAAS,CAAC,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,YAAY,CAAC,EAChF,QAAQ,EAAE,sBAAsB,KAC7B,OAAO,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAC1B,QAAQ,EAAE,SAAS,iBAAiB,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,KAClE,IAAI,CAAC;IACV,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,gBAAgB,CAAC,oBAAoB,CAC5D,KAAK,EACL,cAAc,EACd,cAAc,EACd,QAAQ,EACR,YAAY,CACb,GAAG,IAAI,CAAC,CAAC;IACV,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC,CAAC;AAeJ;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,GAAG,iBAAiB,EACzB,cAAc,GAAG,0BAA0B,EAC3C,cAAc,GAAG,0BAA0B,EAC3C,QAAQ,GAAG,SAAS,EACpB,YAAY,GAAG,SAAS,EAExB,KAAK,EAAE,sBAAsB,CAC3B,KAAK,EACL,cAAc,EACd,cAAc,EACd,QAAQ,EACR,YAAY,CACb,+BA0bF"}
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 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,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,87 @@ 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 presetResolver = createPresetVisualResolver(
197
+ preset,
198
+ (item) => {
199
+ const curGetLabel = latestPropsRef.current.getLabel;
200
+ if (curGetLabel) return curGetLabel(item);
201
+ if (typeof item === "object" && item !== null) {
202
+ if ("label" in item && typeof item.label === "string" && item.label.length > 0) {
203
+ return item.label;
204
+ }
205
+ if ("title" in item && typeof item.title === "string" && item.title.length > 0) {
206
+ return item.title;
207
+ }
208
+ if ("headline" in item && typeof item.headline === "string" && item.headline.length > 0) {
209
+ return item.headline;
210
+ }
211
+ if ("name" in item && typeof item.name === "string" && item.name.length > 0) {
212
+ return item.name;
213
+ }
214
+ }
215
+ return "";
216
+ }
217
+ );
218
+ const base = p.visualResolver;
219
+ const getKey = (visual) => {
220
+ if (isAutoKeyVisual(visual)) {
221
+ return visual.__svAutoKey;
222
+ }
223
+ if (base) {
224
+ return base.getVisualCacheKey(visual);
225
+ }
226
+ return presetResolver.getVisualCacheKey(
227
+ visual
228
+ );
229
+ };
230
+ return {
231
+ resolveContentVisual: (item, ctx) => {
232
+ const cur = latestPropsRef.current;
233
+ let visual;
234
+ if (base) {
235
+ visual = base.resolveContentVisual(item, ctx);
236
+ } else if (cur.renderContentNode) {
237
+ const autoKey = cur.getContentCacheKey ? cur.getContentCacheKey(item, ctx) : `${ctx.id}:${ctx.mixedColorHex}:${ctx.tags.map((t) => t.tag).join(",")}`;
238
+ visual = { __svAutoKey: autoKey };
239
+ } else {
240
+ visual = presetResolver.resolveContentVisual(item, ctx);
241
+ }
242
+ const key = getKey(visual);
243
+ contentDomainByKeyRef.current.set(key, { item, ctx });
244
+ return visual;
245
+ },
246
+ resolveClusterVisual: (cluster) => {
247
+ const cur = latestPropsRef.current;
248
+ let visual;
249
+ if (base) {
250
+ visual = base.resolveClusterVisual(cluster);
251
+ } else if (cur.renderClusterNode) {
252
+ const autoKey = cur.getClusterCacheKey ? cur.getClusterCacheKey(cluster) : `${cluster.clusterUid}:${cluster.defaultLabel}:${cluster.members.length}:${cluster.colorHex}`;
253
+ visual = { __svAutoKey: autoKey };
254
+ } else {
255
+ visual = presetResolver.resolveClusterVisual(cluster);
256
+ }
257
+ const key = getKey(visual);
258
+ clusterDomainByKeyRef.current.set(key, cluster);
259
+ return visual;
260
+ },
261
+ getVisualCacheKey: getKey
262
+ };
263
+ };
264
+ const resolveActiveViews = (activeResolver) => {
202
265
  const p = latestPropsRef.current;
266
+ const presetViews = createPresetViews(p.preset ?? "geometric");
203
267
  let contentView;
204
268
  if (p.renderContentNode) {
205
269
  const renderFn = p.renderContentNode;
206
270
  jsxContentViewRef.current?.dispose();
207
271
  const jsxView = new ReactJsxSvgView({
208
272
  renderFn: (domain, state) => renderFn(domain.item, domain.ctx, state),
209
- getKey: (v) => p.visualResolver.getVisualCacheKey(v),
273
+ getKey: (v) => activeResolver.getVisualCacheKey(v),
210
274
  domainByKey: contentDomainByKeyRef.current
211
275
  });
212
276
  jsxContentViewRef.current = jsxView;
@@ -216,19 +280,9 @@ function SimplexTopicGraph(props) {
216
280
  jsxContentViewRef.current = null;
217
281
  contentView = p.contentView;
218
282
  } 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
- };
283
+ jsxContentViewRef.current?.dispose();
284
+ jsxContentViewRef.current = null;
285
+ contentView = presetViews.contentView;
232
286
  }
233
287
  let clusterView;
234
288
  if (p.renderClusterNode) {
@@ -236,7 +290,7 @@ function SimplexTopicGraph(props) {
236
290
  jsxClusterViewRef.current?.dispose();
237
291
  const jsxView = new ReactJsxSvgView({
238
292
  renderFn: (cluster, state) => renderFn(cluster, state),
239
- getKey: (v) => p.visualResolver.getVisualCacheKey(v),
293
+ getKey: (v) => activeResolver.getVisualCacheKey(v),
240
294
  domainByKey: clusterDomainByKeyRef.current
241
295
  });
242
296
  jsxClusterViewRef.current = jsxView;
@@ -246,21 +300,9 @@ function SimplexTopicGraph(props) {
246
300
  jsxClusterViewRef.current = null;
247
301
  clusterView = p.clusterView;
248
302
  } 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
- };
303
+ jsxClusterViewRef.current?.dispose();
304
+ jsxClusterViewRef.current = null;
305
+ clusterView = presetViews.clusterView;
264
306
  }
265
307
  return { contentView, clusterView };
266
308
  };
@@ -268,13 +310,16 @@ function SimplexTopicGraph(props) {
268
310
  const el = containerRef.current;
269
311
  if (!el) return;
270
312
  const p = latestPropsRef.current;
271
- const { contentView, clusterView } = resolveActiveViews();
313
+ const effectiveResolver = resolveEffectiveResolver();
314
+ const { contentView, clusterView } = resolveActiveViews(effectiveResolver);
272
315
  const ctrl = SimplexSvgController.createFromResolved(el, {
273
316
  items: p.items,
274
317
  getId: p.getId,
275
318
  getTags: p.getTags,
319
+ getLabel: p.getLabel,
276
320
  deriveClusterMeta: p.deriveClusterMeta,
277
- visualResolver: buildWrappedResolver(p.visualResolver),
321
+ preset: p.preset,
322
+ visualResolver: effectiveResolver,
278
323
  contentView,
279
324
  clusterView,
280
325
  normalizeTag: p.normalizeTag,
@@ -285,10 +330,37 @@ function SimplexTopicGraph(props) {
285
330
  sizeMultiplier: p.sizeMultiplier ?? p.layoutOptions?.sizeMultiplier ?? 1
286
331
  },
287
332
  showHulls: p.showHulls,
333
+ focusOnClusterClick: false,
288
334
  interactions: {
289
- onContentHover: (ev) => latestPropsRef.current.onContentHover?.(ev),
290
- onClusterHover: (ev) => latestPropsRef.current.onClusterHover?.(ev),
291
- onLeftClick: (t, raw) => latestPropsRef.current.onLeftClick?.(t, raw),
335
+ onContentHover: (ev) => {
336
+ latestPropsRef.current.onContentHover?.(ev);
337
+ if (latestPropsRef.current.tooltip) {
338
+ setHoveredTarget(ev ? { kind: "content", ...ev } : null);
339
+ }
340
+ },
341
+ onClusterHover: (ev) => {
342
+ latestPropsRef.current.onClusterHover?.(ev);
343
+ if (latestPropsRef.current.tooltip) {
344
+ setHoveredTarget(ev ? { kind: "cluster", ...ev } : null);
345
+ }
346
+ },
347
+ onLeftClick: (target, raw) => {
348
+ const cur = latestPropsRef.current;
349
+ if (cur.onLeftClick) {
350
+ cur.onLeftClick(target, raw);
351
+ return;
352
+ }
353
+ if (cur.focusedClusterUid === void 0 && (cur.focusOnClusterClick ?? true)) {
354
+ if (target.kind === "cluster") {
355
+ const next = internalFocusedUidRef.current === target.cluster.clusterUid ? null : target.cluster.clusterUid;
356
+ internalFocusedUidRef.current = next;
357
+ ctrlRef.current?.setFocusedClusterUid(next);
358
+ } else if (target.kind === "background" && internalFocusedUidRef.current !== null) {
359
+ internalFocusedUidRef.current = null;
360
+ ctrlRef.current?.setFocusedClusterUid(null);
361
+ }
362
+ }
363
+ },
292
364
  onRightClick: (t, raw) => latestPropsRef.current.onRightClick?.(t, raw),
293
365
  onWheel: (ev, cam) => {
294
366
  const policy = latestPropsRef.current.onWheel ?? "zoom-to-cursor";
@@ -331,10 +403,12 @@ function SimplexTopicGraph(props) {
331
403
  useEffect(() => {
332
404
  const ctrl = ctrlRef.current;
333
405
  if (!ctrl) return;
334
- const { contentView, clusterView } = resolveActiveViews();
406
+ const effectiveResolver = resolveEffectiveResolver();
407
+ const { contentView, clusterView } = resolveActiveViews(effectiveResolver);
335
408
  ctrl.setViews({ contentView, clusterView });
336
- ctrl.setVisualResolver(buildWrappedResolver(props.visualResolver));
409
+ ctrl.setVisualResolver(effectiveResolver);
337
410
  }, [
411
+ props.preset,
338
412
  props.visualResolver,
339
413
  props.contentView,
340
414
  props.clusterView,
@@ -363,9 +437,62 @@ function SimplexTopicGraph(props) {
363
437
  }, [props.highlightedIds]);
364
438
  useEffect(() => {
365
439
  if (props.focusedClusterUid !== void 0) {
440
+ internalFocusedUidRef.current = props.focusedClusterUid;
366
441
  ctrlRef.current?.setFocusedClusterUid(props.focusedClusterUid);
367
442
  }
368
443
  }, [props.focusedClusterUid]);
444
+ useEffect(() => {
445
+ if (!props.tooltip) {
446
+ setHoveredTarget(null);
447
+ }
448
+ }, [props.tooltip]);
449
+ const renderBuiltInTooltip = () => {
450
+ if (!props.tooltip || !hoveredTarget) return null;
451
+ const containerW = containerRef.current?.clientWidth || 960;
452
+ const containerH = containerRef.current?.clientHeight || 600;
453
+ const left = Math.min(
454
+ Math.max(8, Math.round(hoveredTarget.screenPosition.x + 14)),
455
+ Math.max(8, containerW - 256)
456
+ );
457
+ const top = Math.min(
458
+ Math.max(8, Math.round(hoveredTarget.screenPosition.y + 14)),
459
+ Math.max(8, containerH - 80)
460
+ );
461
+ const content = typeof props.tooltip === "function" ? props.tooltip(hoveredTarget) : hoveredTarget.kind === "content" ? /* @__PURE__ */ jsxs("div", { children: [
462
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: "#f8fafc" }, children: extractDefaultItemLabel(
463
+ hoveredTarget.item,
464
+ hoveredTarget.layoutContext.id,
465
+ props.getLabel
466
+ ) }),
467
+ hoveredTarget.layoutContext.tags.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginTop: 3, fontSize: 11, color: "#94a3b8" }, children: hoveredTarget.layoutContext.tags.map((t) => `#${t.tag}`).slice(0, 5).join(" \xB7 ") })
468
+ ] }) : /* @__PURE__ */ jsxs("div", { children: [
469
+ /* @__PURE__ */ jsx("div", { style: { fontWeight: 600, color: hoveredTarget.cluster.colorHex }, children: `${hoveredTarget.cluster.code} \xB7 ${hoveredTarget.cluster.defaultLabel}` }),
470
+ /* @__PURE__ */ jsx("div", { style: { marginTop: 3, fontSize: 11, color: "#94a3b8" }, children: `${hoveredTarget.cluster.members.length} ${hoveredTarget.cluster.members.length === 1 ? "item" : "items"}` })
471
+ ] });
472
+ return /* @__PURE__ */ jsx(
473
+ "div",
474
+ {
475
+ role: "tooltip",
476
+ style: {
477
+ position: "absolute",
478
+ left,
479
+ top,
480
+ zIndex: 20,
481
+ maxWidth: 244,
482
+ padding: "7px 10px",
483
+ borderRadius: 8,
484
+ background: "rgba(9, 13, 22, 0.94)",
485
+ border: "1px solid rgba(51, 65, 85, 0.85)",
486
+ color: "#f1f5f9",
487
+ fontSize: 12,
488
+ lineHeight: 1.4,
489
+ pointerEvents: "none",
490
+ boxShadow: "0 10px 24px rgba(0, 0, 0, 0.45)"
491
+ },
492
+ children: content
493
+ }
494
+ );
495
+ };
369
496
  return /* @__PURE__ */ jsx(
370
497
  "div",
371
498
  {
@@ -377,7 +504,8 @@ function SimplexTopicGraph(props) {
377
504
  height: "100%",
378
505
  overflow: "hidden",
379
506
  ...props.style
380
- }
507
+ },
508
+ children: renderBuiltInTooltip()
381
509
  }
382
510
  );
383
511
  }
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 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 /** 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 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 );\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 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 & visual resolver (triggers smooth crossfade / morph!)\n useEffect(() => {\n const ctrl = ctrlRef.current;\n if (!ctrl) return;\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.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 content =\n typeof props.tooltip === 'function' ? (\n props.tooltip(hoveredTarget)\n ) : hoveredTarget.kind === 'content' ? (\n <div>\n <div style={{ fontWeight: 600, color: '#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: '#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: '#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: 'rgba(9, 13, 22, 0.94)',\n border: '1px solid rgba(51, 65, 85, 0.85)',\n color: '#f1f5f9',\n fontSize: 12,\n lineHeight: 1.4,\n pointerEvents: 'none',\n boxShadow: '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.0",
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.0",
24
+ "@simplex-viz/svg": "^0.2.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": ">=18.0.0",