@modernrelay/orbit-react 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/GraphProvider-B8ITDXS0.d.ts +11 -0
  3. package/dist/chunk-7XB47YST.js +69 -0
  4. package/dist/chunk-7XB47YST.js.map +1 -0
  5. package/dist/chunk-DGFLU2SN.js +108 -0
  6. package/dist/chunk-DGFLU2SN.js.map +1 -0
  7. package/dist/chunk-JN7QCMFW.js +93 -0
  8. package/dist/chunk-JN7QCMFW.js.map +1 -0
  9. package/dist/chunk-QJ7JXQVK.js +119 -0
  10. package/dist/chunk-QJ7JXQVK.js.map +1 -0
  11. package/dist/components/ContextMenu.d.ts +73 -0
  12. package/dist/components/ContextMenu.js +367 -0
  13. package/dist/components/ContextMenu.js.map +1 -0
  14. package/dist/components/Histogram.d.ts +33 -0
  15. package/dist/components/Histogram.js +216 -0
  16. package/dist/components/Histogram.js.map +1 -0
  17. package/dist/components/Inspector.d.ts +63 -0
  18. package/dist/components/Inspector.js +246 -0
  19. package/dist/components/Inspector.js.map +1 -0
  20. package/dist/components/Legend.d.ts +45 -0
  21. package/dist/components/Legend.js +185 -0
  22. package/dist/components/Legend.js.map +1 -0
  23. package/dist/components/Minimap.d.ts +63 -0
  24. package/dist/components/Minimap.js +222 -0
  25. package/dist/components/Minimap.js.map +1 -0
  26. package/dist/components/Navigator.d.ts +90 -0
  27. package/dist/components/Navigator.js +381 -0
  28. package/dist/components/Navigator.js.map +1 -0
  29. package/dist/components/Search.d.ts +70 -0
  30. package/dist/components/Search.js +203 -0
  31. package/dist/components/Search.js.map +1 -0
  32. package/dist/components/SelectionActions.d.ts +36 -0
  33. package/dist/components/SelectionActions.js +126 -0
  34. package/dist/components/SelectionActions.js.map +1 -0
  35. package/dist/components/SimControls.d.ts +124 -0
  36. package/dist/components/SimControls.js +221 -0
  37. package/dist/components/SimControls.js.map +1 -0
  38. package/dist/components/Table.d.ts +92 -0
  39. package/dist/components/Table.js +505 -0
  40. package/dist/components/Table.js.map +1 -0
  41. package/dist/components/Timeline.d.ts +34 -0
  42. package/dist/components/Timeline.js +182 -0
  43. package/dist/components/Timeline.js.map +1 -0
  44. package/dist/components/Toolbar.d.ts +60 -0
  45. package/dist/components/Toolbar.js +220 -0
  46. package/dist/components/Toolbar.js.map +1 -0
  47. package/dist/components/Tooltip.d.ts +65 -0
  48. package/dist/components/Tooltip.js +178 -0
  49. package/dist/components/Tooltip.js.map +1 -0
  50. package/dist/index.d.ts +488 -0
  51. package/dist/index.js +827 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/shared-Bj3Gk219.d.ts +43 -0
  54. package/package.json +99 -0
@@ -0,0 +1,182 @@
1
+ import { useRangeBrush, brushRangePct, sliderValueText, sliderValueNow } from '../chunk-DGFLU2SN.js';
2
+ import { useCrossfilterDimension, useSessionSetBrush } from '../chunk-JN7QCMFW.js';
3
+ import { useResolvedInstance } from '../chunk-7XB47YST.js';
4
+ import { useCallback, useSyncExternalStore } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ var ROOT_STYLE = {
8
+ display: "flex",
9
+ alignItems: "center",
10
+ gap: 8,
11
+ padding: 6,
12
+ borderRadius: 8,
13
+ background: "rgba(23, 25, 32, 0.92)",
14
+ border: "1px solid rgba(255, 255, 255, 0.14)",
15
+ color: "#e8eaf0",
16
+ font: "12px/1.4 system-ui, sans-serif"
17
+ };
18
+ var BUTTON_STYLE = {
19
+ appearance: "none",
20
+ background: "transparent",
21
+ border: "none",
22
+ borderRadius: 6,
23
+ color: "inherit",
24
+ cursor: "pointer",
25
+ font: "inherit",
26
+ minWidth: 28,
27
+ minHeight: 28,
28
+ padding: "2px 6px"
29
+ };
30
+ var DISABLED_BUTTON_STYLE = {
31
+ ...BUTTON_STYLE,
32
+ cursor: "default",
33
+ opacity: 0.4
34
+ };
35
+ var PLOT_STYLE = {
36
+ position: "relative",
37
+ flex: 1,
38
+ cursor: "crosshair",
39
+ touchAction: "none",
40
+ userSelect: "none"
41
+ };
42
+ var SVG_STYLE = { display: "block", width: "100%" };
43
+ var TRACK_FILL = "rgba(148, 163, 184, 0.18)";
44
+ var DENSITY_FILL = "rgba(148, 163, 184, 0.4)";
45
+ var WINDOW_FILL = "rgba(122, 162, 255, 0.28)";
46
+ var WINDOW_STROKE = "rgba(122, 162, 255, 0.9)";
47
+ function GraphTimeline(props) {
48
+ const instance = useResolvedInstance(props.instance, "<GraphTimeline>");
49
+ const { summary, brush } = useCrossfilterDimension(instance, props.dimension);
50
+ const setBrush = useSessionSetBrush(instance, props.dimension);
51
+ const subscribe = useCallback(
52
+ (onStoreChange) => instance.store.subscribe(onStoreChange),
53
+ [instance]
54
+ );
55
+ const getPlayingKey = useCallback(
56
+ () => instance.store.getState().timeline.playingKey,
57
+ [instance]
58
+ );
59
+ const playingKey = useSyncExternalStore(subscribe, getPlayingKey, getPlayingKey);
60
+ const playing = playingKey === props.dimension;
61
+ const domain = summary?.domain ?? null;
62
+ const handlers = useRangeBrush({
63
+ domain,
64
+ binCount: summary?.bins.length ?? 0,
65
+ brush,
66
+ setBrush
67
+ });
68
+ const playable = summary !== null && summary.kind !== "categorical" && domain !== null;
69
+ const toggle = useCallback(() => {
70
+ if (instance.store.getState().timeline.playingKey === props.dimension) {
71
+ instance.pauseTimeline();
72
+ return;
73
+ }
74
+ try {
75
+ instance.playTimeline(props.dimension, props.playback);
76
+ } catch {
77
+ }
78
+ }, [instance, props.dimension, props.playback]);
79
+ const styled = props.className === void 0;
80
+ const height = props.height ?? 24;
81
+ const bins = summary?.bins ?? [];
82
+ const maxTotal = bins.reduce((m, b) => b.total > m ? b.total : m, 0);
83
+ const barW = bins.length > 0 ? 100 / bins.length : 100;
84
+ const range = brushRangePct(brush, domain);
85
+ const label = playing ? "Pause timeline" : "Play timeline";
86
+ return /* @__PURE__ */ jsxs(
87
+ "div",
88
+ {
89
+ "data-orbit-timeline": props.dimension,
90
+ role: "group",
91
+ "aria-label": `${props.dimension} timeline`,
92
+ className: props.className,
93
+ style: styled ? { ...ROOT_STYLE, ...props.style } : props.style,
94
+ children: [
95
+ /* @__PURE__ */ jsx(
96
+ "button",
97
+ {
98
+ type: "button",
99
+ "data-orbit-timeline-toggle": "",
100
+ "aria-label": label,
101
+ title: label,
102
+ "aria-pressed": playing,
103
+ disabled: !playable,
104
+ className: props.buttonClassName,
105
+ style: props.buttonClassName !== void 0 ? void 0 : playable ? BUTTON_STYLE : DISABLED_BUTTON_STYLE,
106
+ onClick: toggle,
107
+ children: playing ? "\u23F8" : "\u25B6"
108
+ }
109
+ ),
110
+ /* @__PURE__ */ jsx(
111
+ "div",
112
+ {
113
+ "data-orbit-timeline-plot": "",
114
+ tabIndex: 0,
115
+ "aria-label": `${props.dimension} timeline brush: drag to filter, arrow keys nudge, Escape clears`,
116
+ role: "slider",
117
+ "aria-orientation": "horizontal",
118
+ "aria-valuemin": summary?.domain?.min ?? 0,
119
+ "aria-valuemax": summary?.domain?.max ?? 0,
120
+ "aria-valuenow": sliderValueNow(brush, summary?.domain),
121
+ "aria-valuetext": sliderValueText(brush, summary?.domain),
122
+ style: styled ? PLOT_STYLE : void 0,
123
+ onPointerDown: handlers.onPointerDown,
124
+ onPointerMove: handlers.onPointerMove,
125
+ onPointerUp: handlers.onPointerUp,
126
+ onPointerCancel: handlers.onPointerCancel,
127
+ onDoubleClick: handlers.onDoubleClick,
128
+ onKeyDown: handlers.onKeyDown,
129
+ children: /* @__PURE__ */ jsxs(
130
+ "svg",
131
+ {
132
+ width: "100%",
133
+ height,
134
+ viewBox: "0 0 100 100",
135
+ preserveAspectRatio: "none",
136
+ "aria-hidden": "true",
137
+ focusable: "false",
138
+ style: styled ? SVG_STYLE : void 0,
139
+ children: [
140
+ /* @__PURE__ */ jsx("rect", { x: 0, y: 0, width: 100, height: 100, fill: TRACK_FILL, rx: 2 }),
141
+ bins.map((bin, i) => {
142
+ const h = maxTotal > 0 ? bin.total / maxTotal * 100 : 0;
143
+ return /* @__PURE__ */ jsx(
144
+ "rect",
145
+ {
146
+ "data-orbit-timeline-density": "",
147
+ x: i * barW + barW * 0.1,
148
+ y: 100 - h,
149
+ width: barW * 0.8,
150
+ height: h,
151
+ fill: DENSITY_FILL
152
+ },
153
+ i
154
+ );
155
+ }),
156
+ range !== null ? /* @__PURE__ */ jsx(
157
+ "rect",
158
+ {
159
+ "data-orbit-timeline-window": "",
160
+ x: range.leftPct,
161
+ y: 0,
162
+ width: range.widthPct,
163
+ height: 100,
164
+ fill: WINDOW_FILL,
165
+ stroke: WINDOW_STROKE,
166
+ strokeWidth: 0.75,
167
+ pointerEvents: "none"
168
+ }
169
+ ) : null
170
+ ]
171
+ }
172
+ )
173
+ }
174
+ )
175
+ ]
176
+ }
177
+ );
178
+ }
179
+
180
+ export { GraphTimeline };
181
+ //# sourceMappingURL=Timeline.js.map
182
+ //# sourceMappingURL=Timeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Timeline/index.tsx"],"names":[],"mappings":"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","file":"Timeline.js","sourcesContent":["/**\n * <GraphTimeline> — §16.6 timeline playback control (S9).\n *\n * A slim horizontal band over one numeric/temporal crossfilter dimension:\n * the same pointer-drag brushing as the histogram (shared module), a\n * play/pause button wired to instance.playTimeline/pauseTimeline (the button\n * mirrors store `timeline.playingKey === dimension`), and a translucent\n * current-window indicator. While PLAYING, a user drag pauses playback (the\n * core folds the pause into the same brush publication). Headless-styleable:\n * providing `className` drops the built-in dark-theme inline styles.\n */\n\nimport { useCallback, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement } from 'react';\nimport type { TimelinePlayback } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { useResolvedInstance } from '../shared';\nimport { brushRangePct, sliderValueNow, sliderValueText, useRangeBrush } from '../_shared/brush';\nimport { useCrossfilterDimension, useSessionSetBrush } from '../_shared/crossfilter';\n\nexport interface GraphTimelineProps {\n /** Crossfilter dimension key — numeric/temporal to be playable (§16.6). */\n dimension: string;\n /** Playback options forwarded to instance.playTimeline. */\n playback?: Partial<TimelinePlayback>;\n /** Band height in CSS px. Default 24. */\n height?: number;\n /** Explicit instance; defaults to the ambient <Graph>/<GraphProvider> one. */\n instance?: AnyGraphInstance;\n /** Class hook for the root; providing it drops the default styles. */\n className?: string;\n style?: CSSProperties;\n /** Class hook for the play/pause button; providing it drops its defaults. */\n buttonClassName?: string;\n}\n\n// --- default dark-theme styling (dropped when `className` is provided) ---\n\nconst ROOT_STYLE: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: 6,\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.92)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n color: '#e8eaf0',\n font: '12px/1.4 system-ui, sans-serif',\n};\n\nconst BUTTON_STYLE: CSSProperties = {\n appearance: 'none',\n background: 'transparent',\n border: 'none',\n borderRadius: 6,\n color: 'inherit',\n cursor: 'pointer',\n font: 'inherit',\n minWidth: 28,\n minHeight: 28,\n padding: '2px 6px',\n};\n\nconst DISABLED_BUTTON_STYLE: CSSProperties = {\n ...BUTTON_STYLE,\n cursor: 'default',\n opacity: 0.4,\n};\n\nconst PLOT_STYLE: CSSProperties = {\n position: 'relative',\n flex: 1,\n cursor: 'crosshair',\n touchAction: 'none',\n userSelect: 'none',\n};\n\nconst SVG_STYLE: CSSProperties = { display: 'block', width: '100%' };\n\nconst TRACK_FILL = 'rgba(148, 163, 184, 0.18)';\nconst DENSITY_FILL = 'rgba(148, 163, 184, 0.4)';\nconst WINDOW_FILL = 'rgba(122, 162, 255, 0.28)';\nconst WINDOW_STROKE = 'rgba(122, 162, 255, 0.9)';\n\nexport function GraphTimeline(props: GraphTimelineProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphTimeline>');\n const { summary, brush } = useCrossfilterDimension(instance, props.dimension);\n const setBrush = useSessionSetBrush(instance, props.dimension);\n\n // --- store mirror: timeline.playingKey drives the play/pause toggle ---\n const subscribe = useCallback(\n (onStoreChange: () => void) => instance.store.subscribe(onStoreChange),\n [instance],\n );\n const getPlayingKey = useCallback(\n (): string | null => instance.store.getState().timeline.playingKey,\n [instance],\n );\n const playingKey = useSyncExternalStore(subscribe, getPlayingKey, getPlayingKey);\n const playing = playingKey === props.dimension;\n\n const domain = summary?.domain ?? null;\n const handlers = useRangeBrush({\n domain,\n binCount: summary?.bins.length ?? 0,\n brush,\n setBrush,\n });\n\n const playable = summary !== null && summary.kind !== 'categorical' && domain !== null;\n const toggle = useCallback((): void => {\n if (instance.store.getState().timeline.playingKey === props.dimension) {\n instance.pauseTimeline();\n return;\n }\n try {\n instance.playTimeline(props.dimension, props.playback);\n } catch {\n // Not playable (categorical / unknown / no dimensions yet): inert.\n }\n }, [instance, props.dimension, props.playback]);\n\n const styled = props.className === undefined;\n const height = props.height ?? 24;\n const bins = summary?.bins ?? [];\n const maxTotal = bins.reduce((m, b) => (b.total > m ? b.total : m), 0);\n const barW = bins.length > 0 ? 100 / bins.length : 100;\n const range = brushRangePct(brush, domain);\n const label = playing ? 'Pause timeline' : 'Play timeline';\n\n return (\n <div\n data-orbit-timeline={props.dimension}\n role=\"group\"\n aria-label={`${props.dimension} timeline`}\n className={props.className}\n style={styled ? { ...ROOT_STYLE, ...props.style } : props.style}\n >\n <button\n type=\"button\"\n data-orbit-timeline-toggle=\"\"\n aria-label={label}\n title={label}\n aria-pressed={playing}\n disabled={!playable}\n className={props.buttonClassName}\n style={\n props.buttonClassName !== undefined\n ? undefined\n : playable\n ? BUTTON_STYLE\n : DISABLED_BUTTON_STYLE\n }\n onClick={toggle}\n >\n {playing ? '⏸' : '▶'}\n </button>\n <div\n data-orbit-timeline-plot=\"\"\n tabIndex={0}\n aria-label={`${props.dimension} timeline brush: drag to filter, arrow keys nudge, Escape clears`}\n role=\"slider\"\n aria-orientation=\"horizontal\"\n aria-valuemin={summary?.domain?.min ?? 0}\n aria-valuemax={summary?.domain?.max ?? 0}\n aria-valuenow={sliderValueNow(brush, summary?.domain)}\n aria-valuetext={sliderValueText(brush, summary?.domain)}\n style={styled ? PLOT_STYLE : undefined}\n onPointerDown={handlers.onPointerDown}\n onPointerMove={handlers.onPointerMove}\n onPointerUp={handlers.onPointerUp}\n onPointerCancel={handlers.onPointerCancel}\n onDoubleClick={handlers.onDoubleClick}\n onKeyDown={handlers.onKeyDown}\n >\n <svg\n width=\"100%\"\n height={height}\n viewBox=\"0 0 100 100\"\n preserveAspectRatio=\"none\"\n aria-hidden=\"true\"\n focusable=\"false\"\n style={styled ? SVG_STYLE : undefined}\n >\n <rect x={0} y={0} width={100} height={100} fill={TRACK_FILL} rx={2} />\n {bins.map((bin, i) => {\n const h = maxTotal > 0 ? (bin.total / maxTotal) * 100 : 0;\n return (\n <rect\n key={i}\n data-orbit-timeline-density=\"\"\n x={i * barW + barW * 0.1}\n y={100 - h}\n width={barW * 0.8}\n height={h}\n fill={DENSITY_FILL}\n />\n );\n })}\n {range !== null ? (\n <rect\n data-orbit-timeline-window=\"\"\n x={range.leftPct}\n y={0}\n width={range.widthPct}\n height={100}\n fill={WINDOW_FILL}\n stroke={WINDOW_STROKE}\n strokeWidth={0.75}\n pointerEvents=\"none\"\n />\n ) : null}\n </svg>\n </div>\n </div>\n );\n}\n"]}
@@ -0,0 +1,60 @@
1
+ import { CSSProperties, RefObject, ReactNode, ReactElement } from 'react';
2
+ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
3
+ import { G as GraphCorner } from '../shared-Bj3Gk219.js';
4
+ import '@modernrelay/orbit-core';
5
+
6
+ /**
7
+ * <GraphToolbar> — §16.1 camera & simulation controls (S7-T06).
8
+ *
9
+ * Thin view over the GraphInstance ref API: zoom ±, fit view, reset view,
10
+ * fullscreen toggle, screenshot download, and a simulation play/pause toggle
11
+ * that mirrors the store's `simulationRunning` field. Headless-styleable:
12
+ * providing `className`/`buttonClassName` drops the built-in dark-theme
13
+ * inline styles, and `renderButton` replaces the default <button> rendering
14
+ * entirely while keeping the wiring.
15
+ *
16
+ * Fullscreen note: the instance exposes no container getter by design — the
17
+ * element sent fullscreen is `containerRef.current`, defaulting to
18
+ * `document.documentElement`.
19
+ */
20
+
21
+ type GraphToolbarAction = 'zoom-in' | 'zoom-out' | 'fit-view' | 'reset-view' | 'fullscreen' | 'screenshot' | 'simulation';
22
+ /** One toolbar affordance, as handed to the `renderButton` render prop. */
23
+ interface GraphToolbarButton {
24
+ action: GraphToolbarAction;
25
+ /** Accessible label (also the default tooltip). */
26
+ label: string;
27
+ /** Default glyph rendered inside the button. */
28
+ glyph: string;
29
+ disabled: boolean;
30
+ /** Present on the toggles (fullscreen, simulation). */
31
+ pressed?: boolean;
32
+ /** Perform the action. */
33
+ run(): void;
34
+ }
35
+ interface GraphToolbarProps {
36
+ /** Explicit instance; defaults to the ambient <Graph>/<GraphProvider> one. */
37
+ instance?: AnyGraphInstance;
38
+ /** Corner the toolbar anchors to. Default 'top-left'. */
39
+ position?: GraphCorner;
40
+ /** Distance from the two anchored edges in CSS px. Default 12. */
41
+ offset?: number;
42
+ /** Class hook for the toolbar root; providing it drops the default styles
43
+ * INCLUDING placement — position it yourself in CSS. */
44
+ className?: string;
45
+ /** Style overrides merged over the defaults; an inset you set here wins
46
+ * over the one `position` implies (they are resolved per axis, never
47
+ * layered). */
48
+ style?: CSSProperties;
49
+ /** Class hook for each button; providing it drops the default styles. */
50
+ buttonClassName?: string;
51
+ /** Element sent fullscreen; default `document.documentElement`. */
52
+ containerRef?: RefObject<HTMLElement | null>;
53
+ /** Download filename for screenshots. Default 'orbit-graph.png'. */
54
+ screenshotFileName?: string;
55
+ /** Render-prop replacement for a single button (wiring stays intact). */
56
+ renderButton?: (button: GraphToolbarButton) => ReactNode;
57
+ }
58
+ declare function GraphToolbar(props: GraphToolbarProps): ReactElement;
59
+
60
+ export { GraphToolbar, type GraphToolbarAction, type GraphToolbarButton, type GraphToolbarProps };
@@ -0,0 +1,220 @@
1
+ import { useResolvedInstance, mergeStyle, cornerStyle } from '../chunk-7XB47YST.js';
2
+ import { useCallback, useSyncExternalStore, useState, useEffect, Fragment } from 'react';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var TOOLBAR_STYLE = {
6
+ display: "flex",
7
+ alignItems: "center",
8
+ gap: 2,
9
+ padding: 4,
10
+ borderRadius: 8,
11
+ background: "rgba(23, 25, 32, 0.92)",
12
+ border: "1px solid rgba(255, 255, 255, 0.14)",
13
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.4)",
14
+ color: "#e8eaf0",
15
+ font: "13px/1.4 system-ui, sans-serif"
16
+ };
17
+ var BUTTON_STYLE = {
18
+ appearance: "none",
19
+ background: "transparent",
20
+ border: "none",
21
+ borderRadius: 6,
22
+ color: "inherit",
23
+ cursor: "pointer",
24
+ font: "inherit",
25
+ minWidth: 28,
26
+ minHeight: 28,
27
+ padding: "2px 6px"
28
+ };
29
+ var DISABLED_BUTTON_STYLE = {
30
+ ...BUTTON_STYLE,
31
+ cursor: "default",
32
+ opacity: 0.4
33
+ };
34
+ function GraphToolbar(props) {
35
+ const instance = useResolvedInstance(props.instance, "<GraphToolbar>");
36
+ const subscribe = useCallback(
37
+ (onStoreChange) => instance.store.subscribe(onStoreChange),
38
+ [instance]
39
+ );
40
+ const getSimulationRunning = useCallback(
41
+ () => instance.store.getState().simulationRunning,
42
+ [instance]
43
+ );
44
+ const simulationRunning = useSyncExternalStore(
45
+ subscribe,
46
+ getSimulationRunning,
47
+ getSimulationRunning
48
+ );
49
+ const [fullscreenActive, setFullscreenActive] = useState(false);
50
+ useEffect(() => {
51
+ if (typeof document === "undefined") return void 0;
52
+ const onChange = () => {
53
+ setFullscreenActive((document.fullscreenElement ?? null) !== null);
54
+ };
55
+ document.addEventListener("fullscreenchange", onChange);
56
+ return () => {
57
+ document.removeEventListener("fullscreenchange", onChange);
58
+ };
59
+ }, []);
60
+ const [screenshotSupported, setScreenshotSupported] = useState(true);
61
+ useEffect(() => {
62
+ let cancelled = false;
63
+ let unsubscribe = null;
64
+ const probe = () => {
65
+ void instance.captureScreenshot().then((blob) => {
66
+ if (!cancelled && blob === null) setScreenshotSupported(false);
67
+ });
68
+ };
69
+ if (instance.store.getState().status === "ready") {
70
+ probe();
71
+ } else {
72
+ unsubscribe = instance.store.subscribe(() => {
73
+ if (instance.store.getState().status !== "ready") return;
74
+ unsubscribe?.();
75
+ unsubscribe = null;
76
+ probe();
77
+ });
78
+ }
79
+ return () => {
80
+ cancelled = true;
81
+ unsubscribe?.();
82
+ };
83
+ }, [instance]);
84
+ const screenshotFileName = props.screenshotFileName;
85
+ const takeScreenshot = useCallback(() => {
86
+ void instance.captureScreenshot().then((blob) => {
87
+ if (blob === null) {
88
+ setScreenshotSupported(false);
89
+ return;
90
+ }
91
+ if (typeof document === "undefined" || typeof URL.createObjectURL !== "function") return;
92
+ const url = URL.createObjectURL(blob);
93
+ const anchor = document.createElement("a");
94
+ anchor.href = url;
95
+ anchor.download = screenshotFileName ?? "orbit-graph.png";
96
+ anchor.rel = "noopener";
97
+ anchor.style.display = "none";
98
+ document.body.appendChild(anchor);
99
+ anchor.click();
100
+ anchor.remove();
101
+ if (typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(url);
102
+ });
103
+ }, [instance, screenshotFileName]);
104
+ const containerRef = props.containerRef;
105
+ const toggleFullscreen = useCallback(() => {
106
+ if (typeof document === "undefined") return;
107
+ if ((document.fullscreenElement ?? null) !== null) {
108
+ if (typeof document.exitFullscreen === "function") {
109
+ void document.exitFullscreen().catch(() => void 0);
110
+ }
111
+ return;
112
+ }
113
+ const el = containerRef?.current ?? document.documentElement;
114
+ if (typeof el.requestFullscreen === "function") {
115
+ void el.requestFullscreen().catch(() => void 0);
116
+ }
117
+ }, [containerRef]);
118
+ const buttons = [
119
+ {
120
+ action: "zoom-in",
121
+ label: "Zoom in",
122
+ glyph: "+",
123
+ disabled: false,
124
+ run: () => {
125
+ instance.zoomIn();
126
+ }
127
+ },
128
+ {
129
+ action: "zoom-out",
130
+ label: "Zoom out",
131
+ glyph: "\u2212",
132
+ disabled: false,
133
+ run: () => {
134
+ instance.zoomOut();
135
+ }
136
+ },
137
+ {
138
+ action: "fit-view",
139
+ label: "Fit view",
140
+ glyph: "\u2922",
141
+ disabled: false,
142
+ run: () => {
143
+ instance.fitView();
144
+ }
145
+ },
146
+ {
147
+ action: "reset-view",
148
+ label: "Reset view",
149
+ glyph: "\u21BA",
150
+ disabled: false,
151
+ run: () => {
152
+ instance.setViewport({ x: 0, y: 0, zoom: 1 });
153
+ }
154
+ },
155
+ {
156
+ action: "fullscreen",
157
+ label: fullscreenActive ? "Exit fullscreen" : "Enter fullscreen",
158
+ glyph: "\u26F6",
159
+ disabled: false,
160
+ pressed: fullscreenActive,
161
+ run: toggleFullscreen
162
+ },
163
+ {
164
+ action: "screenshot",
165
+ label: "Save screenshot",
166
+ glyph: "\u25EB",
167
+ disabled: !screenshotSupported,
168
+ run: takeScreenshot
169
+ },
170
+ {
171
+ action: "simulation",
172
+ label: simulationRunning ? "Pause simulation" : "Resume simulation",
173
+ glyph: simulationRunning ? "\u23F8" : "\u25B6",
174
+ disabled: false,
175
+ pressed: simulationRunning,
176
+ run: () => {
177
+ if (instance.isSimulationRunning()) instance.pauseSimulation();
178
+ else instance.resumeSimulation();
179
+ }
180
+ }
181
+ ];
182
+ const renderButton = props.renderButton;
183
+ return /* @__PURE__ */ jsx(
184
+ "div",
185
+ {
186
+ "data-orbit-toolbar": "",
187
+ role: "toolbar",
188
+ "aria-label": "Graph controls",
189
+ className: props.className,
190
+ style: props.className !== void 0 ? props.style : mergeStyle(
191
+ { ...cornerStyle(props.position ?? "top-left", props.offset), ...TOOLBAR_STYLE },
192
+ props.style
193
+ ),
194
+ children: buttons.map(
195
+ (button) => renderButton !== void 0 ? /* @__PURE__ */ jsx(Fragment, { children: renderButton(button) }, button.action) : /* @__PURE__ */ jsx(
196
+ "button",
197
+ {
198
+ type: "button",
199
+ "data-orbit-toolbar-button": button.action,
200
+ className: props.buttonClassName,
201
+ style: props.buttonClassName !== void 0 ? void 0 : button.disabled ? DISABLED_BUTTON_STYLE : BUTTON_STYLE,
202
+ "aria-label": button.label,
203
+ title: button.label,
204
+ "aria-pressed": button.pressed,
205
+ disabled: button.disabled,
206
+ onClick: () => {
207
+ button.run();
208
+ },
209
+ children: button.glyph
210
+ },
211
+ button.action
212
+ )
213
+ )
214
+ }
215
+ );
216
+ }
217
+
218
+ export { GraphToolbar };
219
+ //# sourceMappingURL=Toolbar.js.map
220
+ //# sourceMappingURL=Toolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/Toolbar/index.tsx"],"names":[],"mappings":";;;;AAsEA,IAAM,aAAA,GAA+B;AAAA,EACnC,OAAA,EAAS,MAAA;AAAA,EACT,UAAA,EAAY,QAAA;AAAA,EACZ,GAAA,EAAK,CAAA;AAAA,EACL,OAAA,EAAS,CAAA;AAAA,EACT,YAAA,EAAc,CAAA;AAAA,EACd,UAAA,EAAY,wBAAA;AAAA,EACZ,MAAA,EAAQ,qCAAA;AAAA,EACR,SAAA,EAAW,+BAAA;AAAA,EACX,KAAA,EAAO,SAAA;AAAA,EACP,IAAA,EAAM;AACR,CAAA;AAEA,IAAM,YAAA,GAA8B;AAAA,EAClC,UAAA,EAAY,MAAA;AAAA,EACZ,UAAA,EAAY,aAAA;AAAA,EACZ,MAAA,EAAQ,MAAA;AAAA,EACR,YAAA,EAAc,CAAA;AAAA,EACd,KAAA,EAAO,SAAA;AAAA,EACP,MAAA,EAAQ,SAAA;AAAA,EACR,IAAA,EAAM,SAAA;AAAA,EACN,QAAA,EAAU,EAAA;AAAA,EACV,SAAA,EAAW,EAAA;AAAA,EACX,OAAA,EAAS;AACX,CAAA;AAEA,IAAM,qBAAA,GAAuC;AAAA,EAC3C,GAAG,YAAA;AAAA,EACH,MAAA,EAAQ,SAAA;AAAA,EACR,OAAA,EAAS;AACX,CAAA;AAEO,SAAS,aAAa,KAAA,EAAwC;AACnE,EAAA,MAAM,QAAA,GAAW,mBAAA,CAAoB,KAAA,CAAM,QAAA,EAAU,gBAAgB,CAAA;AAGrE,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,CAAC,aAAA,KAA8B,QAAA,CAAS,KAAA,CAAM,UAAU,aAAa,CAAA;AAAA,IACrE,CAAC,QAAQ;AAAA,GACX;AACA,EAAA,MAAM,oBAAA,GAAuB,WAAA;AAAA,IAC3B,MAAM,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS,CAAE,iBAAA;AAAA,IAChC,CAAC,QAAQ;AAAA,GACX;AACA,EAAA,MAAM,iBAAA,GAAoB,oBAAA;AAAA,IACxB,SAAA;AAAA,IACA,oBAAA;AAAA,IACA;AAAA,GACF;AAGA,EAAA,MAAM,CAAC,gBAAA,EAAkB,mBAAmB,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,MAAA;AAC5C,IAAA,MAAM,WAAW,MAAY;AAC3B,MAAA,mBAAA,CAAA,CAAqB,QAAA,CAAS,iBAAA,IAAqB,IAAA,MAAU,IAAI,CAAA;AAAA,IACnE,CAAA;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,oBAAoB,QAAQ,CAAA;AACtD,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,oBAAoB,QAAQ,CAAA;AAAA,IAC3D,CAAA;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAKL,EAAA,MAAM,CAAC,mBAAA,EAAqB,sBAAsB,CAAA,GAAI,SAAS,IAAI,CAAA;AACnE,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,IAAI,WAAA,GAAmC,IAAA;AACvC,IAAA,MAAM,QAAQ,MAAY;AACxB,MAAA,KAAK,QAAA,CAAS,iBAAA,EAAkB,CAAE,IAAA,CAAK,CAAC,IAAA,KAAS;AAC/C,QAAA,IAAI,CAAC,SAAA,IAAa,IAAA,KAAS,IAAA,yBAA6B,KAAK,CAAA;AAAA,MAC/D,CAAC,CAAA;AAAA,IACH,CAAA;AACA,IAAA,IAAI,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS,CAAE,WAAW,OAAA,EAAS;AAChD,MAAA,KAAA,EAAM;AAAA,IACR,CAAA,MAAO;AACL,MAAA,WAAA,GAAc,QAAA,CAAS,KAAA,CAAM,SAAA,CAAU,MAAM;AAC3C,QAAA,IAAI,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS,CAAE,WAAW,OAAA,EAAS;AAClD,QAAA,WAAA,IAAc;AACd,QAAA,WAAA,GAAc,IAAA;AACd,QAAA,KAAA,EAAM;AAAA,MACR,CAAC,CAAA;AAAA,IACH;AACA,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,WAAA,IAAc;AAAA,IAChB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,EAAA,MAAM,qBAAqB,KAAA,CAAM,kBAAA;AACjC,EAAA,MAAM,cAAA,GAAiB,YAAY,MAAY;AAC7C,IAAA,KAAK,QAAA,CAAS,iBAAA,EAAkB,CAAE,IAAA,CAAK,CAAC,IAAA,KAAS;AAC/C,MAAA,IAAI,SAAS,IAAA,EAAM;AAEjB,QAAA,sBAAA,CAAuB,KAAK,CAAA;AAC5B,QAAA;AAAA,MACF;AACA,MAAA,IAAI,OAAO,QAAA,KAAa,WAAA,IAAe,OAAO,GAAA,CAAI,oBAAoB,UAAA,EAAY;AAClF,MAAA,MAAM,GAAA,GAAM,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA;AACpC,MAAA,MAAM,MAAA,GAAS,QAAA,CAAS,aAAA,CAAc,GAAG,CAAA;AACzC,MAAA,MAAA,CAAO,IAAA,GAAO,GAAA;AACd,MAAA,MAAA,CAAO,WAAW,kBAAA,IAAsB,iBAAA;AACxC,MAAA,MAAA,CAAO,GAAA,GAAM,UAAA;AACb,MAAA,MAAA,CAAO,MAAM,OAAA,GAAU,MAAA;AACvB,MAAA,QAAA,CAAS,IAAA,CAAK,YAAY,MAAM,CAAA;AAChC,MAAA,MAAA,CAAO,KAAA,EAAM;AACb,MAAA,MAAA,CAAO,MAAA,EAAO;AACd,MAAA,IAAI,OAAO,GAAA,CAAI,eAAA,KAAoB,UAAA,EAAY,GAAA,CAAI,gBAAgB,GAAG,CAAA;AAAA,IACxE,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,CAAC,QAAA,EAAU,kBAAkB,CAAC,CAAA;AAEjC,EAAA,MAAM,eAAe,KAAA,CAAM,YAAA;AAC3B,EAAA,MAAM,gBAAA,GAAmB,YAAY,MAAY;AAC/C,IAAA,IAAI,OAAO,aAAa,WAAA,EAAa;AACrC,IAAA,IAAA,CAAK,QAAA,CAAS,iBAAA,IAAqB,IAAA,MAAU,IAAA,EAAM;AACjD,MAAA,IAAI,OAAO,QAAA,CAAS,cAAA,KAAmB,UAAA,EAAY;AACjD,QAAA,KAAK,QAAA,CAAS,cAAA,EAAe,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAAA,MACtD;AACA,MAAA;AAAA,IACF;AACA,IAAA,MAAM,EAAA,GAAK,YAAA,EAAc,OAAA,IAAW,QAAA,CAAS,eAAA;AAC7C,IAAA,IAAI,OAAO,EAAA,CAAG,iBAAA,KAAsB,UAAA,EAAY;AAC9C,MAAA,KAAK,EAAA,CAAG,iBAAA,EAAkB,CAAE,KAAA,CAAM,MAAM,MAAS,CAAA;AAAA,IACnD;AAAA,EACF,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AAEjB,EAAA,MAAM,OAAA,GAAgC;AAAA,IACpC;AAAA,MACE,MAAA,EAAQ,SAAA;AAAA,MACR,KAAA,EAAO,SAAA;AAAA,MACP,KAAA,EAAO,GAAA;AAAA,MACP,QAAA,EAAU,KAAA;AAAA,MACV,KAAK,MAAM;AACT,QAAA,QAAA,CAAS,MAAA,EAAO;AAAA,MAClB;AAAA,KACF;AAAA,IACA;AAAA,MACE,MAAA,EAAQ,UAAA;AAAA,MACR,KAAA,EAAO,UAAA;AAAA,MACP,KAAA,EAAO,QAAA;AAAA,MACP,QAAA,EAAU,KAAA;AAAA,MACV,KAAK,MAAM;AACT,QAAA,QAAA,CAAS,OAAA,EAAQ;AAAA,MACnB;AAAA,KACF;AAAA,IACA;AAAA,MACE,MAAA,EAAQ,UAAA;AAAA,MACR,KAAA,EAAO,UAAA;AAAA,MACP,KAAA,EAAO,QAAA;AAAA,MACP,QAAA,EAAU,KAAA;AAAA,MACV,KAAK,MAAM;AACT,QAAA,QAAA,CAAS,OAAA,EAAQ;AAAA,MACnB;AAAA,KACF;AAAA,IACA;AAAA,MACE,MAAA,EAAQ,YAAA;AAAA,MACR,KAAA,EAAO,YAAA;AAAA,MACP,KAAA,EAAO,QAAA;AAAA,MACP,QAAA,EAAU,KAAA;AAAA,MACV,KAAK,MAAM;AACT,QAAA,QAAA,CAAS,WAAA,CAAY,EAAE,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EAAG,IAAA,EAAM,GAAG,CAAA;AAAA,MAC9C;AAAA,KACF;AAAA,IACA;AAAA,MACE,MAAA,EAAQ,YAAA;AAAA,MACR,KAAA,EAAO,mBAAmB,iBAAA,GAAoB,kBAAA;AAAA,MAC9C,KAAA,EAAO,QAAA;AAAA,MACP,QAAA,EAAU,KAAA;AAAA,MACV,OAAA,EAAS,gBAAA;AAAA,MACT,GAAA,EAAK;AAAA,KACP;AAAA,IACA;AAAA,MACE,MAAA,EAAQ,YAAA;AAAA,MACR,KAAA,EAAO,iBAAA;AAAA,MACP,KAAA,EAAO,QAAA;AAAA,MACP,UAAU,CAAC,mBAAA;AAAA,MACX,GAAA,EAAK;AAAA,KACP;AAAA,IACA;AAAA,MACE,MAAA,EAAQ,YAAA;AAAA,MACR,KAAA,EAAO,oBAAoB,kBAAA,GAAqB,mBAAA;AAAA,MAChD,KAAA,EAAO,oBAAoB,QAAA,GAAM,QAAA;AAAA,MACjC,QAAA,EAAU,KAAA;AAAA,MACV,OAAA,EAAS,iBAAA;AAAA,MACT,KAAK,MAAM;AACT,QAAA,IAAI,QAAA,CAAS,mBAAA,EAAoB,EAAG,QAAA,CAAS,eAAA,EAAgB;AAAA,sBAC/C,gBAAA,EAAiB;AAAA,MACjC;AAAA;AACF,GACF;AAEA,EAAA,MAAM,eAAe,KAAA,CAAM,YAAA;AAC3B,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,oBAAA,EAAmB,EAAA;AAAA,MACnB,IAAA,EAAK,SAAA;AAAA,MACL,YAAA,EAAW,gBAAA;AAAA,MACX,WAAW,KAAA,CAAM,SAAA;AAAA,MACjB,KAAA,EACE,KAAA,CAAM,SAAA,KAAc,MAAA,GAChB,MAAM,KAAA,GACN,UAAA;AAAA,QACE,EAAE,GAAG,WAAA,CAAY,KAAA,CAAM,QAAA,IAAY,YAAY,KAAA,CAAM,MAAM,CAAA,EAAG,GAAG,aAAA,EAAc;AAAA,QAC/E,KAAA,CAAM;AAAA,OACR;AAAA,MAGL,QAAA,EAAA,OAAA,CAAQ,GAAA;AAAA,QAAI,CAAC,MAAA,KACZ,YAAA,KAAiB,MAAA,mBACf,GAAA,CAAC,QAAA,EAAA,EAA8B,QAAA,EAAA,YAAA,CAAa,MAAM,CAAA,EAAA,EAAnC,MAAA,CAAO,MAA8B,CAAA,mBAEpD,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YAEC,IAAA,EAAK,QAAA;AAAA,YACL,6BAA2B,MAAA,CAAO,MAAA;AAAA,YAClC,WAAW,KAAA,CAAM,eAAA;AAAA,YACjB,OACE,KAAA,CAAM,eAAA,KAAoB,SACtB,MAAA,GACA,MAAA,CAAO,WACL,qBAAA,GACA,YAAA;AAAA,YAER,cAAY,MAAA,CAAO,KAAA;AAAA,YACnB,OAAO,MAAA,CAAO,KAAA;AAAA,YACd,gBAAc,MAAA,CAAO,OAAA;AAAA,YACrB,UAAU,MAAA,CAAO,QAAA;AAAA,YACjB,SAAS,MAAM;AACb,cAAA,MAAA,CAAO,GAAA,EAAI;AAAA,YACb,CAAA;AAAA,YAEC,QAAA,EAAA,MAAA,CAAO;AAAA,WAAA;AAAA,UAnBH,MAAA,CAAO;AAAA;AAoBd;AAEJ;AAAA,GACF;AAEJ","file":"Toolbar.js","sourcesContent":["/**\n * <GraphToolbar> — §16.1 camera & simulation controls (S7-T06).\n *\n * Thin view over the GraphInstance ref API: zoom ±, fit view, reset view,\n * fullscreen toggle, screenshot download, and a simulation play/pause toggle\n * that mirrors the store's `simulationRunning` field. Headless-styleable:\n * providing `className`/`buttonClassName` drops the built-in dark-theme\n * inline styles, and `renderButton` replaces the default <button> rendering\n * entirely while keeping the wiring.\n *\n * Fullscreen note: the instance exposes no container getter by design — the\n * element sent fullscreen is `containerRef.current`, defaulting to\n * `document.documentElement`.\n */\n\nimport { Fragment, useCallback, useEffect, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode, RefObject } from 'react';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { cornerStyle, mergeStyle, useResolvedInstance } from '../shared';\nimport type { GraphCorner } from '../shared';\n\nexport type GraphToolbarAction =\n | 'zoom-in'\n | 'zoom-out'\n | 'fit-view'\n | 'reset-view'\n | 'fullscreen'\n | 'screenshot'\n | 'simulation';\n\n/** One toolbar affordance, as handed to the `renderButton` render prop. */\nexport interface GraphToolbarButton {\n action: GraphToolbarAction;\n /** Accessible label (also the default tooltip). */\n label: string;\n /** Default glyph rendered inside the button. */\n glyph: string;\n disabled: boolean;\n /** Present on the toggles (fullscreen, simulation). */\n pressed?: boolean;\n /** Perform the action. */\n run(): void;\n}\n\nexport interface GraphToolbarProps {\n /** Explicit instance; defaults to the ambient <Graph>/<GraphProvider> one. */\n instance?: AnyGraphInstance;\n /** Corner the toolbar anchors to. Default 'top-left'. */\n position?: GraphCorner;\n /** Distance from the two anchored edges in CSS px. Default 12. */\n offset?: number;\n /** Class hook for the toolbar root; providing it drops the default styles\n * INCLUDING placement — position it yourself in CSS. */\n className?: string;\n /** Style overrides merged over the defaults; an inset you set here wins\n * over the one `position` implies (they are resolved per axis, never\n * layered). */\n style?: CSSProperties;\n /** Class hook for each button; providing it drops the default styles. */\n buttonClassName?: string;\n /** Element sent fullscreen; default `document.documentElement`. */\n containerRef?: RefObject<HTMLElement | null>;\n /** Download filename for screenshots. Default 'orbit-graph.png'. */\n screenshotFileName?: string;\n /** Render-prop replacement for a single button (wiring stays intact). */\n renderButton?: (button: GraphToolbarButton) => ReactNode;\n}\n\n// --- default dark-theme styling (dropped when class hooks are provided) ---\n\nconst TOOLBAR_STYLE: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 2,\n padding: 4,\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.92)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n boxShadow: '0 4px 16px rgba(0, 0, 0, 0.4)',\n color: '#e8eaf0',\n font: '13px/1.4 system-ui, sans-serif',\n};\n\nconst BUTTON_STYLE: CSSProperties = {\n appearance: 'none',\n background: 'transparent',\n border: 'none',\n borderRadius: 6,\n color: 'inherit',\n cursor: 'pointer',\n font: 'inherit',\n minWidth: 28,\n minHeight: 28,\n padding: '2px 6px',\n};\n\nconst DISABLED_BUTTON_STYLE: CSSProperties = {\n ...BUTTON_STYLE,\n cursor: 'default',\n opacity: 0.4,\n};\n\nexport function GraphToolbar(props: GraphToolbarProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphToolbar>');\n\n // --- store mirror: simulationRunning drives the play/pause toggle ---\n const subscribe = useCallback(\n (onStoreChange: () => void) => instance.store.subscribe(onStoreChange),\n [instance],\n );\n const getSimulationRunning = useCallback(\n () => instance.store.getState().simulationRunning,\n [instance],\n );\n const simulationRunning = useSyncExternalStore(\n subscribe,\n getSimulationRunning,\n getSimulationRunning,\n );\n\n // --- fullscreen state mirror (for aria-pressed / label) ---\n const [fullscreenActive, setFullscreenActive] = useState(false);\n useEffect(() => {\n if (typeof document === 'undefined') return undefined;\n const onChange = (): void => {\n setFullscreenActive((document.fullscreenElement ?? null) !== null);\n };\n document.addEventListener('fullscreenchange', onChange);\n return () => {\n document.removeEventListener('fullscreenchange', onChange);\n };\n }, []);\n\n // --- screenshot capability sniff: probe once when the instance is ready\n // (captureScreenshot resolves null pre-ready and when the engine lacks\n // support); a null resolution no-op-disables the button permanently. ---\n const [screenshotSupported, setScreenshotSupported] = useState(true);\n useEffect(() => {\n let cancelled = false;\n let unsubscribe: (() => void) | null = null;\n const probe = (): void => {\n void instance.captureScreenshot().then((blob) => {\n if (!cancelled && blob === null) setScreenshotSupported(false);\n });\n };\n if (instance.store.getState().status === 'ready') {\n probe();\n } else {\n unsubscribe = instance.store.subscribe(() => {\n if (instance.store.getState().status !== 'ready') return;\n unsubscribe?.();\n unsubscribe = null;\n probe();\n });\n }\n return () => {\n cancelled = true;\n unsubscribe?.();\n };\n }, [instance]);\n\n const screenshotFileName = props.screenshotFileName;\n const takeScreenshot = useCallback((): void => {\n void instance.captureScreenshot().then((blob) => {\n if (blob === null) {\n // Unsupported / not ready — no-op-disable from here on.\n setScreenshotSupported(false);\n return;\n }\n if (typeof document === 'undefined' || typeof URL.createObjectURL !== 'function') return;\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = screenshotFileName ?? 'orbit-graph.png';\n anchor.rel = 'noopener';\n anchor.style.display = 'none';\n document.body.appendChild(anchor);\n anchor.click();\n anchor.remove();\n if (typeof URL.revokeObjectURL === 'function') URL.revokeObjectURL(url);\n });\n }, [instance, screenshotFileName]);\n\n const containerRef = props.containerRef;\n const toggleFullscreen = useCallback((): void => {\n if (typeof document === 'undefined') return;\n if ((document.fullscreenElement ?? null) !== null) {\n if (typeof document.exitFullscreen === 'function') {\n void document.exitFullscreen().catch(() => undefined);\n }\n return;\n }\n const el = containerRef?.current ?? document.documentElement;\n if (typeof el.requestFullscreen === 'function') {\n void el.requestFullscreen().catch(() => undefined);\n }\n }, [containerRef]);\n\n const buttons: GraphToolbarButton[] = [\n {\n action: 'zoom-in',\n label: 'Zoom in',\n glyph: '+',\n disabled: false,\n run: () => {\n instance.zoomIn();\n },\n },\n {\n action: 'zoom-out',\n label: 'Zoom out',\n glyph: '−',\n disabled: false,\n run: () => {\n instance.zoomOut();\n },\n },\n {\n action: 'fit-view',\n label: 'Fit view',\n glyph: '⤢',\n disabled: false,\n run: () => {\n instance.fitView();\n },\n },\n {\n action: 'reset-view',\n label: 'Reset view',\n glyph: '↺',\n disabled: false,\n run: () => {\n instance.setViewport({ x: 0, y: 0, zoom: 1 });\n },\n },\n {\n action: 'fullscreen',\n label: fullscreenActive ? 'Exit fullscreen' : 'Enter fullscreen',\n glyph: '⛶',\n disabled: false,\n pressed: fullscreenActive,\n run: toggleFullscreen,\n },\n {\n action: 'screenshot',\n label: 'Save screenshot',\n glyph: '◫',\n disabled: !screenshotSupported,\n run: takeScreenshot,\n },\n {\n action: 'simulation',\n label: simulationRunning ? 'Pause simulation' : 'Resume simulation',\n glyph: simulationRunning ? '⏸' : '▶',\n disabled: false,\n pressed: simulationRunning,\n run: () => {\n if (instance.isSimulationRunning()) instance.pauseSimulation();\n else instance.resumeSimulation();\n },\n },\n ];\n\n const renderButton = props.renderButton;\n return (\n <div\n data-orbit-toolbar=\"\"\n role=\"toolbar\"\n aria-label=\"Graph controls\"\n className={props.className}\n style={\n props.className !== undefined\n ? props.style\n : mergeStyle(\n { ...cornerStyle(props.position ?? 'top-left', props.offset), ...TOOLBAR_STYLE },\n props.style,\n )\n }\n >\n {buttons.map((button) =>\n renderButton !== undefined ? (\n <Fragment key={button.action}>{renderButton(button)}</Fragment>\n ) : (\n <button\n key={button.action}\n type=\"button\"\n data-orbit-toolbar-button={button.action}\n className={props.buttonClassName}\n style={\n props.buttonClassName !== undefined\n ? undefined\n : button.disabled\n ? DISABLED_BUTTON_STYLE\n : BUTTON_STYLE\n }\n aria-label={button.label}\n title={button.label}\n aria-pressed={button.pressed}\n disabled={button.disabled}\n onClick={() => {\n button.run();\n }}\n >\n {button.glyph}\n </button>\n ),\n )}\n </div>\n );\n}\n"]}
@@ -0,0 +1,65 @@
1
+ import { ReactNode, CSSProperties, ReactElement } from 'react';
2
+ import { GraphNode, AcceptedEdge } from '@modernrelay/orbit-core';
3
+ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
4
+
5
+ /**
6
+ * `@modernrelay/orbit-react/components/Tooltip` — `<GraphTooltip>`, the §16.9
7
+ * hover card (S11-T12).
8
+ *
9
+ * Visibility rides the store hover slice (the §7.4 typed hover lane): a
10
+ * non-null hover arms a `delayMs` timer (default 150); the card mounts when
11
+ * it fires and unmounts IMMEDIATELY when hover clears — no flicker-out delay.
12
+ *
13
+ * BOTH hover namespaces are served: `hover.nodeId` and `hover.edgeId`. A node
14
+ * WINS when both are set — it is the more specific target and sits on top of
15
+ * its own links. Edges have no entry in the overlay position lane (that lane
16
+ * tracks labeled NODE slots), so an edge card always rides the cursor
17
+ * fallback, which is exactly right: the hovered edge is under the pointer.
18
+ *
19
+ * Positioning is imperative (transform writes on a ref — never a React
20
+ * re-render per movement), fed by two lanes:
21
+ * 1. the S7 overlay position lane (`instance.labels.subscribePositions` —
22
+ * the same public seam <LabelLayer> renders from): when the hovered id is
23
+ * in the tracked candidate set, each scheduler tick writes its fresh
24
+ * container-relative screen x/y, so the card follows the node through
25
+ * simulation drift and camera moves;
26
+ * 2. the pointer fallback: when the hovered id is NOT label-tracked (the
27
+ * common case — labels cover k ranked nodes), a window `pointermove`
28
+ * listener (attached only while hovering) pins the card to the cursor
29
+ * + a fixed offset. The engine's `spaceToScreen` is not reachable through
30
+ * the public instance surface in v0.9, so cursor-anchoring is the
31
+ * documented fallback — the hovered node is by definition under the
32
+ * cursor.
33
+ *
34
+ * Default content: the target's label (`attrs.label` string, else id) plus up
35
+ * to {@link TOOLTIP_ATTR_ROWS} attr rows (label excluded — it is the title),
36
+ * every key/value rendered as TEXT NODES only (§14 untrusted-content rule;
37
+ * hostile attr strings appear literally). `render` / `renderEdge` replace the
38
+ * card content entirely, and `getEdgeText` retitles an edge card without
39
+ * taking over its body — useful where the meaningful name lives in an
40
+ * adapter-injected attr rather than `label` (an Omnigraph edge's relationship
41
+ * type is at `attrs['orbit:type']`, for instance). The card is pointer-inert
42
+ * (it must never steal canvas hover).
43
+ */
44
+
45
+ /** Max attr rows in the default card content. */
46
+ declare const TOOLTIP_ATTR_ROWS = 6;
47
+ interface GraphTooltipProps {
48
+ /** Explicit instance (multi-instance pages); ambient context otherwise. */
49
+ instance?: AnyGraphInstance;
50
+ /** Replaces the default card content for a NODE (label + attr rows). */
51
+ render?: (node: GraphNode<any>) => ReactNode;
52
+ /** Replaces the default card content for an EDGE. */
53
+ renderEdge?: (edge: AcceptedEdge<any>) => ReactNode;
54
+ /** Title text for an edge card. Default `attrs.label ?? id`. */
55
+ getEdgeText?: (edge: AcceptedEdge<any>) => string;
56
+ /** Set false to ignore edge hovers entirely (nodes only). Default true. */
57
+ edges?: boolean;
58
+ /** Hover → show delay in ms. Default 150. */
59
+ delayMs?: number;
60
+ className?: string;
61
+ style?: CSSProperties;
62
+ }
63
+ declare function GraphTooltip(props: GraphTooltipProps): ReactElement;
64
+
65
+ export { GraphTooltip, type GraphTooltipProps, TOOLTIP_ATTR_ROWS };