@modernrelay/orbit-react 0.13.6 → 0.15.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/dist/{chunk-DGFLU2SN.js → chunk-DTXFQLNP.js} +2 -2
- package/dist/chunk-DTXFQLNP.js.map +1 -0
- package/dist/{chunk-7XB47YST.js → chunk-NPOV5GA3.js} +2 -2
- package/dist/chunk-NPOV5GA3.js.map +1 -0
- package/dist/{chunk-QJ7JXQVK.js → chunk-ODOPSUI7.js} +4 -4
- package/dist/chunk-ODOPSUI7.js.map +1 -0
- package/dist/{chunk-JN7QCMFW.js → chunk-S36LHSNX.js} +2 -2
- package/dist/chunk-S36LHSNX.js.map +1 -0
- package/dist/components/ContextMenu.d.ts +4 -4
- package/dist/components/ContextMenu.js +2 -2
- package/dist/components/ContextMenu.js.map +1 -1
- package/dist/components/Histogram.d.ts +1 -1
- package/dist/components/Histogram.js +3 -3
- package/dist/components/Histogram.js.map +1 -1
- package/dist/components/Inspector.d.ts +20 -20
- package/dist/components/Inspector.js +1 -1
- package/dist/components/Inspector.js.map +1 -1
- package/dist/components/Legend.d.ts +9 -10
- package/dist/components/Legend.js +3 -3
- package/dist/components/Legend.js.map +1 -1
- package/dist/components/Minimap.d.ts +29 -29
- package/dist/components/Minimap.js +11 -6
- package/dist/components/Minimap.js.map +1 -1
- package/dist/components/Navigator.d.ts +24 -24
- package/dist/components/Navigator.js +11 -4
- package/dist/components/Navigator.js.map +1 -1
- package/dist/components/Search.d.ts +28 -28
- package/dist/components/Search.js +3 -3
- package/dist/components/Search.js.map +1 -1
- package/dist/components/SelectionActions.d.ts +3 -3
- package/dist/components/SelectionActions.js +1 -1
- package/dist/components/SelectionActions.js.map +1 -1
- package/dist/components/SimControls.d.ts +15 -16
- package/dist/components/SimControls.js +2 -2
- package/dist/components/SimControls.js.map +1 -1
- package/dist/components/Table.d.ts +4 -4
- package/dist/components/Table.js +1 -1
- package/dist/components/Table.js.map +1 -1
- package/dist/components/Timeline.d.ts +2 -2
- package/dist/components/Timeline.js +3 -3
- package/dist/components/Timeline.js.map +1 -1
- package/dist/components/Toolbar.d.ts +2 -2
- package/dist/components/Toolbar.js +1 -1
- package/dist/components/Toolbar.js.map +1 -1
- package/dist/components/Tooltip.d.ts +16 -16
- package/dist/components/Tooltip.js +3 -2
- package/dist/components/Tooltip.js.map +1 -1
- package/dist/index.d.ts +109 -107
- package/dist/index.js +44 -12
- package/dist/index.js.map +1 -1
- package/dist/{shared-Bj3Gk219.d.ts → shared-n98tkXJe.d.ts} +3 -3
- package/package.json +2 -2
- package/dist/chunk-7XB47YST.js.map +0 -1
- package/dist/chunk-DGFLU2SN.js.map +0 -1
- package/dist/chunk-JN7QCMFW.js.map +0 -1
- package/dist/chunk-QJ7JXQVK.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Toolbar/index.tsx"],"names":[],"mappings":"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","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"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/Toolbar/index.tsx"],"names":[],"mappings":"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","file":"Toolbar.js","sourcesContent":["/**\n * <GraphToolbar> — camera & simulation controls.\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"]}
|
|
@@ -3,10 +3,10 @@ import { GraphNode, AcceptedEdge } from '@modernrelay/orbit-core';
|
|
|
3
3
|
import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* `@modernrelay/orbit-react/components/Tooltip` — `<GraphTooltip>`, the
|
|
7
|
-
* hover card
|
|
6
|
+
* `@modernrelay/orbit-react/components/Tooltip` — `<GraphTooltip>`, the
|
|
7
|
+
* hover card.
|
|
8
8
|
*
|
|
9
|
-
* Visibility rides the store hover slice
|
|
9
|
+
* Visibility rides the store hover slice: a
|
|
10
10
|
* non-null hover arms a `delayMs` timer (default 150); the card mounts when
|
|
11
11
|
* it fires and unmounts IMMEDIATELY when hover clears — no flicker-out delay.
|
|
12
12
|
*
|
|
@@ -18,22 +18,22 @@ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
|
|
|
18
18
|
*
|
|
19
19
|
* Positioning is imperative (transform writes on a ref — never a React
|
|
20
20
|
* re-render per movement), fed by two lanes:
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
21
|
+
* 1. the 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
33
|
*
|
|
34
34
|
* Default content: the target's label (`attrs.label` string, else id) plus up
|
|
35
35
|
* to {@link TOOLTIP_ATTR_ROWS} attr rows (label excluded — it is the title),
|
|
36
|
-
* every key/value rendered as TEXT NODES only (
|
|
36
|
+
* every key/value rendered as TEXT NODES only (untrusted-content rule;
|
|
37
37
|
* hostile attr strings appear literally). `render` / `renderEdge` replace the
|
|
38
38
|
* card content entirely, and `getEdgeText` retitles an edge card without
|
|
39
39
|
* taking over its body — useful where the meaningful name lives in an
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useResolvedInstance, overlaySurface } from '../chunk-
|
|
1
|
+
import { useResolvedInstance, overlaySurface } from '../chunk-NPOV5GA3.js';
|
|
2
2
|
import { useState, useRef, useCallback, useEffect, useLayoutEffect, useSyncExternalStore } from 'react';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -106,6 +106,7 @@ function GraphTooltip(props) {
|
|
|
106
106
|
setVisibleKey(null);
|
|
107
107
|
return void 0;
|
|
108
108
|
}
|
|
109
|
+
setVisibleKey((current) => current === hoverKey ? current : null);
|
|
109
110
|
const timer = setTimeout(() => {
|
|
110
111
|
setVisibleKey(hoverKey);
|
|
111
112
|
}, delayMs);
|
|
@@ -146,7 +147,7 @@ function GraphTooltip(props) {
|
|
|
146
147
|
useLayoutEffect(() => {
|
|
147
148
|
if (visibleKey !== null) position(decodeHover(visibleKey).id);
|
|
148
149
|
}, [visibleKey, position]);
|
|
149
|
-
const target = visibleKey === null ? null : decodeHover(visibleKey);
|
|
150
|
+
const target = visibleKey === null || visibleKey !== hoverKey ? null : decodeHover(visibleKey);
|
|
150
151
|
const node = target?.kind === "node" ? instance.getNode(target.id) : void 0;
|
|
151
152
|
const edge = target?.kind === "edge" ? instance.getEdge(target.id) : void 0;
|
|
152
153
|
const item = node ?? edge;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Tooltip/index.tsx"],"names":[],"mappings":"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","file":"Tooltip.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Tooltip` — `<GraphTooltip>`, the §16.9\n * hover card (S11-T12).\n *\n * Visibility rides the store hover slice (the §7.4 typed hover lane): a\n * non-null hover arms a `delayMs` timer (default 150); the card mounts when\n * it fires and unmounts IMMEDIATELY when hover clears — no flicker-out delay.\n *\n * BOTH hover namespaces are served: `hover.nodeId` and `hover.edgeId`. A node\n * WINS when both are set — it is the more specific target and sits on top of\n * its own links. Edges have no entry in the overlay position lane (that lane\n * tracks labeled NODE slots), so an edge card always rides the cursor\n * fallback, which is exactly right: the hovered edge is under the pointer.\n *\n * Positioning is imperative (transform writes on a ref — never a React\n * re-render per movement), fed by two lanes:\n * 1. the S7 overlay position lane (`instance.labels.subscribePositions` —\n * the same public seam <LabelLayer> renders from): when the hovered id is\n * in the tracked candidate set, each scheduler tick writes its fresh\n * container-relative screen x/y, so the card follows the node through\n * simulation drift and camera moves;\n * 2. the pointer fallback: when the hovered id is NOT label-tracked (the\n * common case — labels cover k ranked nodes), a window `pointermove`\n * listener (attached only while hovering) pins the card to the cursor\n * + a fixed offset. The engine's `spaceToScreen` is not reachable through\n * the public instance surface in v0.9, so cursor-anchoring is the\n * documented fallback — the hovered node is by definition under the\n * cursor.\n *\n * Default content: the target's label (`attrs.label` string, else id) plus up\n * to {@link TOOLTIP_ATTR_ROWS} attr rows (label excluded — it is the title),\n * every key/value rendered as TEXT NODES only (§14 untrusted-content rule;\n * hostile attr strings appear literally). `render` / `renderEdge` replace the\n * card content entirely, and `getEdgeText` retitles an edge card without\n * taking over its body — useful where the meaningful name lives in an\n * adapter-injected attr rather than `label` (an Omnigraph edge's relationship\n * type is at `attrs['orbit:type']`, for instance). The card is pointer-inert\n * (it must never steal canvas hover).\n */\n\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport type { AcceptedEdge, GraphNode, NodeId } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { overlaySurface, useResolvedInstance } from '../shared';\n\nconst DELAY_MS_DEFAULT = 150;\n/** Max attr rows in the default card content. */\nexport const TOOLTIP_ATTR_ROWS = 6;\n/** Cursor-anchored offset (px) so the card never sits under the pointer. */\nconst POINTER_OFFSET_PX = 12;\n\nexport interface GraphTooltipProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Replaces the default card content for a NODE (label + attr rows). */\n render?: (node: GraphNode<any>) => ReactNode;\n /** Replaces the default card content for an EDGE. */\n renderEdge?: (edge: AcceptedEdge<any>) => ReactNode;\n /** Title text for an edge card. Default `attrs.label ?? id`. */\n getEdgeText?: (edge: AcceptedEdge<any>) => string;\n /** Set false to ignore edge hovers entirely (nodes only). Default true. */\n edges?: boolean;\n /** Hover → show delay in ms. Default 150. */\n delayMs?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst LAYER_STYLE: CSSProperties = {\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n};\n\nconst CARD_STYLE: CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n maxWidth: 280,\n padding: '6px 10px',\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.96)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n color: '#e8eaf0',\n font: '12px/1.5 system-ui, sans-serif',\n pointerEvents: 'none',\n whiteSpace: 'nowrap',\n willChange: 'transform',\n};\n\nconst TITLE_STYLE: CSSProperties = {\n fontWeight: 600,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n\nconst ROW_STYLE: CSSProperties = {\n display: 'flex',\n justifyContent: 'space-between',\n gap: 12,\n overflow: 'hidden',\n};\n\nconst ROW_KEY_STYLE: CSSProperties = { opacity: 0.65 };\nconst ROW_VALUE_STYLE: CSSProperties = { overflow: 'hidden', textOverflow: 'ellipsis' };\n\n/** Same derivation as the core §14 label default: `attrs.label ?? id`. */\nfunction nodeLabelText(node: GraphNode<any>): string {\n const attrs = node.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(node.id) : String(label);\n}\n\n/** Attr value → display TEXT (never markup): primitives verbatim, structured\n * values JSON (guarded — cycles degrade to the String form). */\nfunction attrValueText(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && value !== null) {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n/** The hovered target, node-first. Encoded as a STRING (`'node\\u0000id'`)\n * rather than an object so `useSyncExternalStore` compares by value — a fresh\n * object every publication would loop. */\ntype HoverKey = string;\n\nfunction encodeHover(kind: 'node' | 'edge', id: string): HoverKey {\n return `${kind}\\u0000${id}`;\n}\n\nfunction decodeHover(key: HoverKey): { kind: 'node' | 'edge'; id: string } {\n const at = key.indexOf('\\u0000');\n return { kind: key.slice(0, at) as 'node' | 'edge', id: key.slice(at + 1) };\n}\n\nfunction useHoveredTarget(instance: AnyGraphInstance, edges: boolean): HoverKey | null {\n const subscribe = useCallback(\n (onChange: () => void) => instance.store.subscribe(onChange),\n [instance],\n );\n const getSnapshot = useCallback((): HoverKey | null => {\n const hover = instance.store.getState().hover;\n // Node wins: it is the more specific target and sits above its links.\n if (hover.nodeId !== null) return encodeHover('node', hover.nodeId);\n if (edges && hover.edgeId !== null) return encodeHover('edge', hover.edgeId);\n return null;\n }, [instance, edges]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Same derivation as the node default: `attrs.label ?? id`. */\nfunction edgeLabelText(edge: AcceptedEdge<any>): string {\n const attrs = edge.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(edge.id) : String(label);\n}\n\nexport function GraphTooltip(props: GraphTooltipProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphTooltip>');\n const labels = overlaySurface(instance).labels;\n const delayMs = props.delayMs ?? DELAY_MS_DEFAULT;\n\n const edgesEnabled = props.edges ?? true;\n const hoverKey = useHoveredTarget(instance, edgesEnabled);\n /** The target the mounted card shows — hoverKey promoted after delayMs. */\n const [visibleKey, setVisibleKey] = useState<HoverKey | null>(null);\n\n const layerRef = useRef<HTMLDivElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n /** Freshest label-lane screen position per tracked id (container-relative). */\n const trackedRef = useRef(new Map<NodeId, readonly [number, number]>());\n /** Freshest cursor position (container-relative, offset applied). */\n const pointerRef = useRef<readonly [number, number] | null>(null);\n\n /** One place decides the transform: label lane wins when it tracks the id. */\n const position = useCallback(\n (id: NodeId): void => {\n const el = cardRef.current;\n if (el === null) return;\n const at = trackedRef.current.get(id) ?? pointerRef.current;\n if (at === null || at === undefined) return;\n el.style.transform = `translate3d(${at[0]}px, ${at[1]}px, 0)`;\n },\n [],\n );\n\n // Delay lane: arm on hover, cancel/hide immediately on unhover.\n useEffect(() => {\n if (hoverKey === null) {\n setVisibleKey(null);\n return undefined;\n }\n const timer = setTimeout(() => {\n setVisibleKey(hoverKey);\n }, delayMs);\n return () => {\n clearTimeout(timer);\n };\n }, [hoverKey, delayMs]);\n\n // Position lane 1: the S7 overlay position lane (same seam as LabelLayer).\n useLayoutEffect(() => {\n if (labels === undefined) return undefined;\n return labels.subscribePositions((list) => {\n const tracked = trackedRef.current;\n tracked.clear();\n for (const p of list) tracked.set(p.id, [p.x, p.y]);\n const id = instance.store.getState().hover.nodeId;\n if (id !== null && tracked.has(id)) position(id);\n });\n }, [labels, instance, position]);\n\n // Position lane 2: cursor fallback. The listener lives for the component's\n // lifetime (storing two numbers per move) so a hover that lands WITHOUT a\n // subsequent move still knows where the cursor already is; it only writes\n // the card transform while a node is hovered and the id is untracked.\n useEffect(() => {\n const onMove = (e: PointerEvent): void => {\n // Container-relative like the label lane: subtract the positioned\n // ancestor's origin (zero-rect hosts — jsdom — degrade to client px).\n const host = layerRef.current?.offsetParent;\n const origin = host instanceof Element ? host.getBoundingClientRect() : null;\n pointerRef.current = [\n e.clientX - (origin?.left ?? 0) + POINTER_OFFSET_PX,\n e.clientY - (origin?.top ?? 0) + POINTER_OFFSET_PX,\n ];\n const hover = instance.store.getState().hover;\n if (hover.nodeId !== null) {\n if (!trackedRef.current.has(hover.nodeId)) position(hover.nodeId);\n } else if (hover.edgeId !== null) {\n // Edges never appear in the label lane — always cursor-anchored.\n position(hover.edgeId);\n }\n };\n window.addEventListener('pointermove', onMove);\n return () => {\n window.removeEventListener('pointermove', onMove);\n };\n }, [instance, position]);\n\n // Place the card the moment it mounts (before paint — no corner flash).\n useLayoutEffect(() => {\n if (visibleKey !== null) position(decodeHover(visibleKey).id);\n }, [visibleKey, position]);\n\n const target = visibleKey === null ? null : decodeHover(visibleKey);\n const node =\n target?.kind === 'node'\n ? (instance.getNode(target.id) as GraphNode<any> | undefined)\n : undefined;\n const edge =\n target?.kind === 'edge'\n ? (instance.getEdge(target.id) as AcceptedEdge<any> | undefined)\n : undefined;\n const item = node ?? edge;\n const isEdge = edge !== undefined;\n\n /** Title + custom body, shared by both kinds — only the text derivation and\n * the `data-orbit-tooltip-edge` marker differ. Computed ONLY when a target\n * actually resolved: `visibleKey` can name an id the model no longer holds\n * (a hover surviving a dataset swap), and `target` is null whenever nothing\n * is hovered at all. */\n const title =\n item === undefined\n ? ''\n : isEdge\n ? (props.getEdgeText ?? edgeLabelText)(edge)\n : nodeLabelText(node as GraphNode<any>);\n const custom =\n item === undefined\n ? undefined\n : isEdge\n ? props.renderEdge?.(edge)\n : props.render?.(node as GraphNode<any>);\n\n return (\n <div ref={layerRef} data-orbit-tooltip-layer=\"\" style={LAYER_STYLE}>\n {item !== undefined ? (\n <div\n ref={cardRef}\n data-orbit-tooltip=\"\"\n {...(isEdge ? { 'data-orbit-tooltip-edge': '' } : {})}\n role=\"tooltip\"\n className={props.className}\n style={{ ...CARD_STYLE, ...props.style }}\n >\n {custom !== undefined ? (\n custom\n ) : (\n <>\n {/* Untrusted text renders as TEXT NODES only (§14). */}\n <div data-orbit-tooltip-label=\"\" style={TITLE_STYLE}>\n {title}\n </div>\n {Object.entries((item.attrs ?? {}) as Record<string, unknown>)\n .filter(([key]) => key !== 'label')\n .slice(0, TOOLTIP_ATTR_ROWS)\n .map(([key, value]) => (\n <div key={key} data-orbit-tooltip-attr={key} style={ROW_STYLE}>\n <span style={ROW_KEY_STYLE}>{key}</span>\n <span style={ROW_VALUE_STYLE}>{attrValueText(value)}</span>\n </div>\n ))}\n </>\n )}\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/Tooltip/index.tsx"],"names":[],"mappings":"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","file":"Tooltip.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Tooltip` — `<GraphTooltip>`, the\n * hover card.\n *\n * Visibility rides the store hover slice: a\n * non-null hover arms a `delayMs` timer (default 150); the card mounts when\n * it fires and unmounts IMMEDIATELY when hover clears — no flicker-out delay.\n *\n * BOTH hover namespaces are served: `hover.nodeId` and `hover.edgeId`. A node\n * WINS when both are set — it is the more specific target and sits on top of\n * its own links. Edges have no entry in the overlay position lane (that lane\n * tracks labeled NODE slots), so an edge card always rides the cursor\n * fallback, which is exactly right: the hovered edge is under the pointer.\n *\n * Positioning is imperative (transform writes on a ref — never a React\n * re-render per movement), fed by two lanes:\n * 1. the overlay position lane (`instance.labels.subscribePositions`,\n * the same public seam <LabelLayer> renders from): when the hovered id is\n * in the tracked candidate set, each scheduler tick writes its fresh\n * container-relative screen x/y, so the card follows the node through\n * simulation drift and camera moves;\n * 2. the pointer fallback: when the hovered id is NOT label-tracked (the\n * common case — labels cover k ranked nodes), a window `pointermove`\n * listener (attached only while hovering) pins the card to the cursor\n * + a fixed offset. The engine's `spaceToScreen` is not reachable through\n * the public instance surface in v0.9, so cursor-anchoring is the\n * documented fallback — the hovered node is by definition under the\n * cursor.\n *\n * Default content: the target's label (`attrs.label` string, else id) plus up\n * to {@link TOOLTIP_ATTR_ROWS} attr rows (label excluded — it is the title),\n * every key/value rendered as TEXT NODES only (untrusted-content rule;\n * hostile attr strings appear literally). `render` / `renderEdge` replace the\n * card content entirely, and `getEdgeText` retitles an edge card without\n * taking over its body — useful where the meaningful name lives in an\n * adapter-injected attr rather than `label` (an Omnigraph edge's relationship\n * type is at `attrs['orbit:type']`, for instance). The card is pointer-inert\n * (it must never steal canvas hover).\n */\n\nimport { useCallback, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport type { AcceptedEdge, GraphNode, NodeId } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { overlaySurface, useResolvedInstance } from '../shared';\n\nconst DELAY_MS_DEFAULT = 150;\n/** Max attr rows in the default card content. */\nexport const TOOLTIP_ATTR_ROWS = 6;\n/** Cursor-anchored offset (px) so the card never sits under the pointer. */\nconst POINTER_OFFSET_PX = 12;\n\nexport interface GraphTooltipProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Replaces the default card content for a NODE (label + attr rows). */\n render?: (node: GraphNode<any>) => ReactNode;\n /** Replaces the default card content for an EDGE. */\n renderEdge?: (edge: AcceptedEdge<any>) => ReactNode;\n /** Title text for an edge card. Default `attrs.label ?? id`. */\n getEdgeText?: (edge: AcceptedEdge<any>) => string;\n /** Set false to ignore edge hovers entirely (nodes only). Default true. */\n edges?: boolean;\n /** Hover → show delay in ms. Default 150. */\n delayMs?: number;\n className?: string;\n style?: CSSProperties;\n}\n\nconst LAYER_STYLE: CSSProperties = {\n position: 'absolute',\n inset: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n};\n\nconst CARD_STYLE: CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n maxWidth: 280,\n padding: '6px 10px',\n borderRadius: 8,\n background: 'rgba(23, 25, 32, 0.96)',\n border: '1px solid rgba(255, 255, 255, 0.14)',\n color: '#e8eaf0',\n font: '12px/1.5 system-ui, sans-serif',\n pointerEvents: 'none',\n whiteSpace: 'nowrap',\n willChange: 'transform',\n};\n\nconst TITLE_STYLE: CSSProperties = {\n fontWeight: 600,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n\nconst ROW_STYLE: CSSProperties = {\n display: 'flex',\n justifyContent: 'space-between',\n gap: 12,\n overflow: 'hidden',\n};\n\nconst ROW_KEY_STYLE: CSSProperties = { opacity: 0.65 };\nconst ROW_VALUE_STYLE: CSSProperties = { overflow: 'hidden', textOverflow: 'ellipsis' };\n\n/** Same derivation as the core label default: `attrs.label ?? id`. */\nfunction nodeLabelText(node: GraphNode<any>): string {\n const attrs = node.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(node.id) : String(label);\n}\n\n/** Attr value → display TEXT (never markup): primitives verbatim, structured\n * values JSON (guarded — cycles degrade to the String form). */\nfunction attrValueText(value: unknown): string {\n if (typeof value === 'string') return value;\n if (typeof value === 'object' && value !== null) {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n/** The hovered target, node-first. Encoded as a STRING (`'node\\u0000id'`)\n * rather than an object so `useSyncExternalStore` compares by value — a fresh\n * object every publication would loop. */\ntype HoverKey = string;\n\nfunction encodeHover(kind: 'node' | 'edge', id: string): HoverKey {\n return `${kind}\\u0000${id}`;\n}\n\nfunction decodeHover(key: HoverKey): { kind: 'node' | 'edge'; id: string } {\n const at = key.indexOf('\\u0000');\n return { kind: key.slice(0, at) as 'node' | 'edge', id: key.slice(at + 1) };\n}\n\nfunction useHoveredTarget(instance: AnyGraphInstance, edges: boolean): HoverKey | null {\n const subscribe = useCallback(\n (onChange: () => void) => instance.store.subscribe(onChange),\n [instance],\n );\n const getSnapshot = useCallback((): HoverKey | null => {\n const hover = instance.store.getState().hover;\n // Node wins: it is the more specific target and sits above its links.\n if (hover.nodeId !== null) return encodeHover('node', hover.nodeId);\n if (edges && hover.edgeId !== null) return encodeHover('edge', hover.edgeId);\n return null;\n }, [instance, edges]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Same derivation as the node default: `attrs.label ?? id`. */\nfunction edgeLabelText(edge: AcceptedEdge<any>): string {\n const attrs = edge.attrs as Record<string, unknown> | undefined;\n const label = attrs?.['label'];\n return label === undefined || label === null ? String(edge.id) : String(label);\n}\n\nexport function GraphTooltip(props: GraphTooltipProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphTooltip>');\n const labels = overlaySurface(instance).labels;\n const delayMs = props.delayMs ?? DELAY_MS_DEFAULT;\n\n const edgesEnabled = props.edges ?? true;\n const hoverKey = useHoveredTarget(instance, edgesEnabled);\n /** The target the mounted card shows — hoverKey promoted after delayMs. */\n const [visibleKey, setVisibleKey] = useState<HoverKey | null>(null);\n\n const layerRef = useRef<HTMLDivElement | null>(null);\n const cardRef = useRef<HTMLDivElement | null>(null);\n /** Freshest label-lane screen position per tracked id (container-relative). */\n const trackedRef = useRef(new Map<NodeId, readonly [number, number]>());\n /** Freshest cursor position (container-relative, offset applied). */\n const pointerRef = useRef<readonly [number, number] | null>(null);\n\n /** One place decides the transform: label lane wins when it tracks the id. */\n const position = useCallback(\n (id: NodeId): void => {\n const el = cardRef.current;\n if (el === null) return;\n const at = trackedRef.current.get(id) ?? pointerRef.current;\n if (at === null || at === undefined) return;\n el.style.transform = `translate3d(${at[0]}px, ${at[1]}px, 0)`;\n },\n [],\n );\n\n // Delay lane: arm on hover, cancel/hide immediately on unhover OR target\n // replacement. Keeping the previous card mounted while a new target's\n // timer runs presents stale content for an item that is no longer hovered.\n useEffect(() => {\n if (hoverKey === null) {\n setVisibleKey(null);\n return undefined;\n }\n setVisibleKey((current) => (current === hoverKey ? current : null));\n const timer = setTimeout(() => {\n setVisibleKey(hoverKey);\n }, delayMs);\n return () => {\n clearTimeout(timer);\n };\n }, [hoverKey, delayMs]);\n\n // Position lane 1: the overlay position lane (same seam as LabelLayer).\n useLayoutEffect(() => {\n if (labels === undefined) return undefined;\n return labels.subscribePositions((list) => {\n const tracked = trackedRef.current;\n tracked.clear();\n for (const p of list) tracked.set(p.id, [p.x, p.y]);\n const id = instance.store.getState().hover.nodeId;\n if (id !== null && tracked.has(id)) position(id);\n });\n }, [labels, instance, position]);\n\n // Position lane 2: cursor fallback. The listener lives for the component's\n // lifetime (storing two numbers per move) so a hover that lands WITHOUT a\n // subsequent move still knows where the cursor already is; it only writes\n // the card transform while a node is hovered and the id is untracked.\n useEffect(() => {\n const onMove = (e: PointerEvent): void => {\n // Container-relative like the label lane: subtract the positioned\n // ancestor's origin (zero-rect hosts — jsdom — degrade to client px).\n const host = layerRef.current?.offsetParent;\n const origin = host instanceof Element ? host.getBoundingClientRect() : null;\n pointerRef.current = [\n e.clientX - (origin?.left ?? 0) + POINTER_OFFSET_PX,\n e.clientY - (origin?.top ?? 0) + POINTER_OFFSET_PX,\n ];\n const hover = instance.store.getState().hover;\n if (hover.nodeId !== null) {\n if (!trackedRef.current.has(hover.nodeId)) position(hover.nodeId);\n } else if (hover.edgeId !== null) {\n // Edges never appear in the label lane — always cursor-anchored.\n position(hover.edgeId);\n }\n };\n window.addEventListener('pointermove', onMove);\n return () => {\n window.removeEventListener('pointermove', onMove);\n };\n }, [instance, position]);\n\n // Place the card the moment it mounts (before paint — no corner flash).\n useLayoutEffect(() => {\n if (visibleKey !== null) position(decodeHover(visibleKey).id);\n }, [visibleKey, position]);\n\n // Render-gate synchronously as well as clearing state in the effect: on the\n // first commit for a replacement hover, effects have not run yet and\n // `visibleKey` still names the previous target.\n const target = visibleKey === null || visibleKey !== hoverKey ? null : decodeHover(visibleKey);\n const node =\n target?.kind === 'node'\n ? (instance.getNode(target.id) as GraphNode<any> | undefined)\n : undefined;\n const edge =\n target?.kind === 'edge'\n ? (instance.getEdge(target.id) as AcceptedEdge<any> | undefined)\n : undefined;\n const item = node ?? edge;\n const isEdge = edge !== undefined;\n\n /** Title + custom body, shared by both kinds — only the text derivation and\n * the `data-orbit-tooltip-edge` marker differ. Computed ONLY when a target\n * actually resolved: `visibleKey` can name an id the model no longer holds\n * (a hover surviving a dataset swap), and `target` is null whenever nothing\n * is hovered at all. */\n const title =\n item === undefined\n ? ''\n : isEdge\n ? (props.getEdgeText ?? edgeLabelText)(edge)\n : nodeLabelText(node as GraphNode<any>);\n const custom =\n item === undefined\n ? undefined\n : isEdge\n ? props.renderEdge?.(edge)\n : props.render?.(node as GraphNode<any>);\n\n return (\n <div ref={layerRef} data-orbit-tooltip-layer=\"\" style={LAYER_STYLE}>\n {item !== undefined ? (\n <div\n ref={cardRef}\n data-orbit-tooltip=\"\"\n {...(isEdge ? { 'data-orbit-tooltip-edge': '' } : {})}\n role=\"tooltip\"\n className={props.className}\n style={{ ...CARD_STYLE, ...props.style }}\n >\n {custom !== undefined ? (\n custom\n ) : (\n <>\n {/* Untrusted text renders as TEXT NODES only. */}\n <div data-orbit-tooltip-label=\"\" style={TITLE_STYLE}>\n {title}\n </div>\n {Object.entries((item.attrs ?? {}) as Record<string, unknown>)\n .filter(([key]) => key !== 'label')\n .slice(0, TOOLTIP_ATTR_ROWS)\n .map(([key, value]) => (\n <div key={key} data-orbit-tooltip-attr={key} style={ROW_STYLE}>\n <span style={ROW_KEY_STYLE}>{key}</span>\n <span style={ROW_VALUE_STYLE}>{attrValueText(value)}</span>\n </div>\n ))}\n </>\n )}\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|