@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 +140 -34
- package/dist/SimplexTopicGraph.d.ts +38 -4
- package/dist/SimplexTopicGraph.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +185 -57
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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
|
|
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/
|
|
37
|
+
npm install @simplex-viz/react
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
## Quickstart
|
|
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/
|
|
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
|
-
##
|
|
321
|
+
## Level 4 — Portable `SvgNodeView` Plugins (60 fps Without JSX Re-Renders)
|
|
230
322
|
|
|
231
|
-
For larger graphs or
|
|
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
|
-
|
|
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({
|
|
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<
|
|
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
|
|
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
|
-
| `
|
|
275
|
-
| `
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
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` |
|
|
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
|
-
| `
|
|
296
|
-
| `
|
|
297
|
-
| `
|
|
298
|
-
| `
|
|
299
|
-
| `
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
302
|
-
| `
|
|
303
|
-
| `
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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":"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,
|
|
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 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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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
|
-
|
|
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
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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) =>
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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) =>
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
|
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
|
-
|
|
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) =>
|
|
290
|
-
|
|
291
|
-
|
|
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
|
|
406
|
+
const effectiveResolver = resolveEffectiveResolver();
|
|
407
|
+
const { contentView, clusterView } = resolveActiveViews(effectiveResolver);
|
|
335
408
|
ctrl.setViews({ contentView, clusterView });
|
|
336
|
-
ctrl.setVisualResolver(
|
|
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.
|
|
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.
|
|
24
|
-
"@simplex-viz/svg": "^0.
|
|
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",
|