@modernrelay/orbit-react 0.2.0 → 0.13.6
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/components/Navigator.js +16 -1
- package/dist/components/Navigator.js.map +1 -1
- package/dist/components/SimControls.d.ts +3 -3
- package/dist/components/SimControls.js +3 -1
- package/dist/components/SimControls.js.map +1 -1
- package/dist/components/Table.js +1 -1
- package/dist/components/Table.js.map +1 -1
- package/dist/index.d.ts +23 -2
- package/dist/index.js +64 -27
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -80,6 +80,10 @@ function GraphNavigator(props) {
|
|
|
80
80
|
useEffect(() => {
|
|
81
81
|
if (root !== null && root.model !== slice.model) setRoot(null);
|
|
82
82
|
}, [root, slice.model]);
|
|
83
|
+
const releaseEmphasis = useCallback(() => {
|
|
84
|
+
if (instance.store.getState().hover.nodeId === null) instance.emphasizeNode(null);
|
|
85
|
+
}, [instance]);
|
|
86
|
+
useEffect(() => () => releaseEmphasis(), [releaseEmphasis]);
|
|
83
87
|
if (degreesRef.current.model !== slice.model) {
|
|
84
88
|
degreesRef.current = { model: slice.model, counts: /* @__PURE__ */ new Map() };
|
|
85
89
|
}
|
|
@@ -226,6 +230,7 @@ function GraphNavigator(props) {
|
|
|
226
230
|
const row = rows[Math.min(Math.max(index, 0), rows.length - 1)];
|
|
227
231
|
if (row === void 0) return;
|
|
228
232
|
setActiveKey(row.key);
|
|
233
|
+
instance.emphasizeNode(row.nodeId);
|
|
229
234
|
focusItemSoon();
|
|
230
235
|
};
|
|
231
236
|
const pageBy = (delta) => {
|
|
@@ -238,7 +243,10 @@ function GraphNavigator(props) {
|
|
|
238
243
|
if (next === current) return;
|
|
239
244
|
setPages((p) => ({ ...p, [listKey]: next }));
|
|
240
245
|
const first = items[next * windowSize];
|
|
241
|
-
if (first !== void 0)
|
|
246
|
+
if (first !== void 0) {
|
|
247
|
+
setActiveKey(`${listKey}:${first}`);
|
|
248
|
+
instance.emphasizeNode(first);
|
|
249
|
+
}
|
|
242
250
|
focusItemSoon();
|
|
243
251
|
};
|
|
244
252
|
const activate = (row) => {
|
|
@@ -263,6 +271,7 @@ function GraphNavigator(props) {
|
|
|
263
271
|
const clearTransientFocus = () => {
|
|
264
272
|
setRoot(null);
|
|
265
273
|
setActiveKey(null);
|
|
274
|
+
releaseEmphasis();
|
|
266
275
|
focusItemSoon();
|
|
267
276
|
};
|
|
268
277
|
const onKeyDown = (e) => {
|
|
@@ -310,6 +319,11 @@ function GraphNavigator(props) {
|
|
|
310
319
|
"aria-label": panelLabel,
|
|
311
320
|
className: props.className,
|
|
312
321
|
style: { ...PANEL_STYLE, ...props.style },
|
|
322
|
+
onBlur: (e) => {
|
|
323
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
324
|
+
releaseEmphasis();
|
|
325
|
+
}
|
|
326
|
+
},
|
|
313
327
|
children: [
|
|
314
328
|
/* @__PURE__ */ jsx(
|
|
315
329
|
"div",
|
|
@@ -337,6 +351,7 @@ function GraphNavigator(props) {
|
|
|
337
351
|
},
|
|
338
352
|
onClick: () => {
|
|
339
353
|
setActiveKey(row.key);
|
|
354
|
+
instance.emphasizeNode(row.nodeId);
|
|
340
355
|
focusItemSoon();
|
|
341
356
|
},
|
|
342
357
|
style: isActive ? ACTIVE_ITEM_STYLE : ITEM_STYLE,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/Navigator/index.tsx"],"names":[],"mappings":"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","file":"Navigator.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Navigator` — `<GraphNavigator>`, the\n * bounded semantic navigator (spec §15.1; S7-T11/T19).\n *\n * The navigator is the keyboard/screen-reader equivalent surface for the\n * canvas: a visually compact panel with `role=\"listbox\"` semantics driven by\n * a roving tabindex — exactly one item is tabbable at any time, and\n * `aria-activedescendant` on the listbox container mirrors the active item's\n * stable DOM id. It is bounded by design: every list is paged by\n * `accessibility.navigatorWindow` (default 50), so the DOM never grows one\n * row per entity.\n *\n * Sections:\n * - **Search results** (S11-T15) — the last completed §16.5 search\n * (`store.search`), shown whenever the slice is non-null (it clears on\n * `clearSearch()` / dataset swap). Paged like every other list; Enter\n * routes through `instance.activateSearchResult` (§16.5 result contract:\n * an in-scene, mask-visible id focuses; anything else is classification\n * only — the navigator never mutates scope/filters). Row text prefers the\n * service-supplied `label`.\n * - **Focused node** — the transiently focused root plus its paged 1-hop\n * neighborhood.\n * - **Selection** — the current node selection, paged.\n * - **All nodes** — the entry list (first `navigatorWindow` nodes in\n * accepted-base order, paged over the full set), shown when nothing is\n * focused.\n *\n * ## Keyboard model — `keyboardMode: 'topology'` (the v0.4 default and only mode)\n *\n * | Key | Action |\n * |---------------------|-------------------------------------------------------------------|\n * | ArrowDown / ArrowUp | move the active item within the current list |\n * | Home / End | move to the bounds of the current list |\n * | PageDown / PageUp | next / previous page of the active item's list |\n * | Enter | focus the node (`instance.focusNode` — camera fly) and re-root the neighborhood at it |\n * | Space | toggle the node's selection membership |\n * | Escape | clear the transient focus (returns to the entry list) |\n *\n * `keyboardMode: 'spatial'` (arrows move by screen direction) is a\n * documented post-v0.4 mode and intentionally NOT part of the accepted prop\n * union yet — only `'topology'` type-checks in v0.4.\n *\n * Text names resolve via `accessibility.getAccessibleLabel`, falling back to\n * `attrs.label` (when a string) and then the node id — always rendered as\n * TEXT NODES (§14 untrusted-content rule; markup in labels stays literal).\n * Each item exposes selection state (`aria-selected`), its neighbor count\n * when known, and pinned/hidden state in text (e.g. `hub · 5 neighbors ·\n * pinned`).\n *\n * Neighborhood semantics (v0.4): direction is UNDIRECTED — `focusNode`\n * resolves the 1-hop neighbor ids through the engine's adjacency when the\n * adapter provides one, else through the core CSR adjacency\n * (`buildAdjacency`/`neighborsOf`) built lazily per accepted model revision\n * and invalidated on model change; self-loops are excluded and parallel\n * edges deduplicate. The navigator caches the resolved neighborhood and the\n * per-node neighbor counts it has learned in refs keyed by\n * `revisions.model`; a model change drops the (now stale) transient root and\n * returns to the entry list. Neighbor counts therefore appear on items whose\n * neighborhood has been resolved (the current and previously visited roots);\n * a public O(1) degree read is a post-v0.4 core surface.\n *\n * Focus never lives only in WebGL pixels (§15.1): the active item carries a\n * visible DOM focus outline and receives real DOM focus on keyboard moves.\n * Sync is navigator → graph (Enter → `focusNode` camera fly); graph →\n * navigator focus sync is deferred past v0.4. Space routes through\n * `instance.selectNodes`, so a controlled selection surfaces intent only\n * (§6.4) — `aria-selected` always reflects the store.\n */\n\nimport { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, KeyboardEvent, ReactElement } from 'react';\nimport type {\n AccessibilityConfig,\n GraphNode,\n NodeId,\n SearchResult,\n SelectionState,\n} from '@modernrelay/orbit-core';\nimport { useResolvedInstance } from '../shared';\nimport type { AnyGraphInstance } from '../../GraphProvider';\n\n/** §15.1 default page size when `accessibility.navigatorWindow` is unset. */\nconst DEFAULT_NAVIGATOR_WINDOW = 50;\n\nexport interface GraphNavigatorProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /**\n * Keyboard model. v0.4 ships `'topology'` (the default; documented above);\n * `'spatial'` (arrows-by-screen-direction) is post-v0.4 and not accepted\n * by this union yet.\n */\n keyboardMode?: 'topology';\n /** Accessible name of the navigator panel. Default 'Graph navigator'. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\n/** Which paged id list a row belongs to (drives PageUp/PageDown targeting). */\ntype PagedListKey = 'search' | 'neighbors' | 'selection' | 'entry';\n\ninterface NavRow {\n /** Stable identity across renders: `${list}:${nodeId}` (`root:` for the root row). */\n key: string;\n /** Stable DOM id (aria-activedescendant target). */\n domId: string;\n nodeId: NodeId;\n listKey: PagedListKey;\n text: string;\n selected: boolean;\n}\n\ninterface NavSection {\n key: 'search' | 'focused' | 'selection' | 'entry';\n listKey: PagedListKey;\n label: string;\n /** Group accessible name; carries the 'page x of y' state. */\n groupLabel: string;\n rows: NavRow[];\n page: number;\n pageCount: number;\n /** Total pageable items (excludes the focused section's root chrome row). */\n total: number;\n}\n\n/** Transient navigator focus: the neighborhood root (§15.1). */\ninterface NavRoot {\n id: NodeId;\n neighbors: readonly NodeId[];\n /** Model revision the neighborhood was resolved against. */\n model: number;\n}\n\ninterface NavStoreSlice {\n selection: SelectionState;\n pins: ReadonlyMap<NodeId, readonly [number, number]>;\n hidden: ReadonlySet<NodeId>;\n model: number;\n nodeCount: number;\n /** Last completed §16.5 search (S11-T15); null hides the section. */\n search: { query: string; results: readonly SearchResult[] } | null;\n}\n\n/** Narrow store subscription: re-renders only when a navigator-relevant slice\n * changes (never on per-frame viewport writes). */\nfunction useNavStoreSlice(instance: AnyGraphInstance): NavStoreSlice {\n const cacheRef = useRef<NavStoreSlice | null>(null);\n const subscribe = useCallback(\n (cb: () => void) => instance.store.subscribe(cb),\n [instance],\n );\n const getSnapshot = useCallback((): NavStoreSlice => {\n const s = instance.store.getState();\n const prev = cacheRef.current;\n if (\n prev !== null &&\n prev.selection === s.selection &&\n prev.pins === s.pins &&\n prev.hidden === s.hiddenNodeIds &&\n prev.model === s.revisions.model &&\n prev.nodeCount === s.nodeCount &&\n prev.search === s.search\n ) {\n return prev;\n }\n const next: NavStoreSlice = {\n selection: s.selection,\n pins: s.pins,\n hidden: s.hiddenNodeIds,\n model: s.revisions.model,\n nodeCount: s.nodeCount,\n search: s.search,\n };\n cacheRef.current = next;\n return next;\n }, [instance]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\ninterface PageWindow {\n page: number;\n pageCount: number;\n items: readonly NodeId[];\n}\n\n/** Clamp a raw page into `[0, pageCount)` and slice that page's ids. */\nfunction pageWindow(all: readonly NodeId[], rawPage: number, size: number): PageWindow {\n const pageCount = Math.max(1, Math.ceil(all.length / size));\n const page = Math.min(Math.max(rawPage, 0), pageCount - 1);\n return { page, pageCount, items: all.slice(page * size, page * size + size) };\n}\n\n// --- styling defaults (headless-styleable: className/style override) ---\nconst PANEL_STYLE: CSSProperties = {\n pointerEvents: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n maxWidth: 320,\n fontSize: 12,\n};\nconst LIST_STYLE: CSSProperties = { overflowY: 'auto', maxHeight: 320 };\nconst HEADER_STYLE: CSSProperties = { fontWeight: 600, padding: '4px 6px' };\nconst ITEM_STYLE: CSSProperties = {\n padding: '2px 6px',\n cursor: 'default',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n/** §15.1 visible DOM focus ring on the active item. */\nconst ACTIVE_ITEM_STYLE: CSSProperties = {\n ...ITEM_STYLE,\n outline: '2px solid currentColor',\n outlineOffset: -2,\n};\nconst PAGING_STYLE: CSSProperties = { display: 'flex', alignItems: 'center', gap: 8 };\nconst EMPTY_STYLE: CSSProperties = { padding: '4px 6px', fontStyle: 'italic' };\n\nexport function GraphNavigator(props: GraphNavigatorProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphNavigator>');\n const slice = useNavStoreSlice(instance);\n\n // 'topology' is the only v0.4 mode; the prop exists so the shape is stable\n // when 'spatial' lands post-v0.4 (no behavioral switch yet).\n void props.keyboardMode;\n\n const baseId = useId();\n const [root, setRoot] = useState<NavRoot | null>(null);\n const [pages, setPages] = useState<Record<PagedListKey, number>>({\n search: 0,\n neighbors: 0,\n selection: 0,\n entry: 0,\n });\n /** Active row identity (roving target); null → first row. */\n const [activeKey, setActiveKey] = useState<string | null>(null);\n\n /** Stable per-node DOM-id tokens (node ids may contain arbitrary text). */\n const tokensRef = useRef<Map<NodeId, number>>(new Map());\n /** Learned neighbor counts, cached per model revision (see module JSDoc). */\n const degreesRef = useRef<{ model: number; counts: Map<NodeId, number> }>({\n model: -1,\n counts: new Map(),\n });\n const itemsRef = useRef<Map<string, HTMLElement>>(new Map());\n /** Set by keyboard handlers so the focus effect moves DOM focus once. */\n const pendingFocusRef = useRef(false);\n\n // A model change invalidates the resolved neighborhood: drop the transient\n // root (back to the entry list) rather than rendering stale rows.\n useEffect(() => {\n if (root !== null && root.model !== slice.model) setRoot(null);\n }, [root, slice.model]);\n\n // Idempotent render-time cache reset (ref only — safe under StrictMode).\n if (degreesRef.current.model !== slice.model) {\n degreesRef.current = { model: slice.model, counts: new Map() };\n }\n\n const accessibility = instance.getAccessibility() as\n | AccessibilityConfig<Record<string, unknown>>\n | undefined;\n const rawWindow = accessibility?.navigatorWindow;\n const windowSize =\n typeof rawWindow === 'number' && Number.isFinite(rawWindow) && rawWindow >= 1\n ? Math.floor(rawWindow)\n : DEFAULT_NAVIGATOR_WINDOW;\n\n const getAccessibleLabel = accessibility?.getAccessibleLabel;\n const nameOf = (id: NodeId): string => {\n const node = instance.getNode(id) as GraphNode<Record<string, unknown>> | undefined;\n if (node === undefined) return id;\n if (getAccessibleLabel !== undefined) {\n try {\n return getAccessibleLabel(node);\n } catch {\n // fall through to the built-in resolution\n }\n }\n const label = node.attrs?.['label'];\n return typeof label === 'string' ? label : node.id;\n };\n\n const selectedSet = new Set<NodeId>(slice.selection.nodeIds);\n\n /** '{name} · {n} neighbors · pinned · hidden' — always a single text node. */\n const rowText = (id: NodeId): string => {\n const parts = [nameOf(id)];\n const count = degreesRef.current.counts.get(id);\n if (count !== undefined) parts.push(count === 1 ? '1 neighbor' : `${count} neighbors`);\n if (slice.pins.has(id)) parts.push('pinned');\n if (slice.hidden.has(id)) parts.push('hidden');\n return parts.join(' · ');\n };\n\n const domIdFor = (listKey: string, id: NodeId): string => {\n const tokens = tokensRef.current;\n let token = tokens.get(id);\n if (token === undefined) {\n token = tokens.size;\n tokens.set(id, token);\n }\n return `${baseId}nav-${listKey}-${token}`;\n };\n\n const makeRow = (listKey: PagedListKey, id: NodeId): NavRow => ({\n key: `${listKey}:${id}`,\n domId: domIdFor(listKey, id),\n nodeId: id,\n listKey,\n text: rowText(id),\n selected: selectedSet.has(id),\n });\n\n // --- section/row model (all O(navigatorWindow) work) ---\n const searchResults: readonly SearchResult[] = slice.search === null ? [] : slice.search.results;\n const listItems: Record<PagedListKey, readonly NodeId[]> = {\n search: searchResults.map((r) => r.id),\n neighbors: root !== null ? root.neighbors : [],\n selection: slice.selection.nodeIds,\n // §15.1: the entry list is the SCENE roster (scope applied, mask not) —\n // hidden/filtered nodes stay listed with their state exposed in text.\n entry: root === null ? instance.getSceneNodeIds() : [],\n };\n\n const sections: NavSection[] = [];\n if (slice.search !== null) {\n const view = pageWindow(listItems.search, pages.search, windowSize);\n const count = searchResults.length;\n const start = view.page * windowSize;\n sections.push({\n key: 'search',\n listKey: 'search',\n label: 'Search results',\n groupLabel: `Search results for ${slice.search.query}: ${count} ${\n count === 1 ? 'result' : 'results'\n }, page ${view.page + 1} of ${view.pageCount}`,\n // §16.5: row text prefers the service-supplied label (still a TEXT\n // NODE); results may name ids outside the model — nameOf falls back to\n // the id for those.\n rows: view.items.map((id, i) => {\n const row = makeRow('search', id);\n const label = searchResults[start + i]?.label;\n return label === undefined ? row : { ...row, text: label };\n }),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root !== null) {\n const view = pageWindow(listItems.neighbors, pages.neighbors, windowSize);\n const rootRow: NavRow = {\n key: `root:${root.id}`,\n domId: domIdFor('root', root.id),\n nodeId: root.id,\n listKey: 'neighbors',\n text: rowText(root.id),\n selected: selectedSet.has(root.id),\n };\n const count = root.neighbors.length;\n sections.push({\n key: 'focused',\n listKey: 'neighbors',\n label: 'Focused node',\n groupLabel: `Focused node ${nameOf(root.id)}: ${count} ${\n count === 1 ? 'neighbor' : 'neighbors'\n }, page ${view.page + 1} of ${view.pageCount}`,\n rows: [rootRow, ...view.items.map((id) => makeRow('neighbors', id))],\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (slice.selection.nodeIds.length > 0) {\n const view = pageWindow(listItems.selection, pages.selection, windowSize);\n const count = slice.selection.nodeIds.length;\n sections.push({\n key: 'selection',\n listKey: 'selection',\n label: 'Selection',\n groupLabel: `Selection: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('selection', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root === null) {\n const view = pageWindow(listItems.entry, pages.entry, windowSize);\n const count = listItems.entry.length;\n sections.push({\n key: 'entry',\n listKey: 'entry',\n label: 'All nodes',\n groupLabel: `All nodes: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('entry', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n\n const rows: NavRow[] = sections.flatMap((s) => s.rows);\n let activeIndex = activeKey === null ? -1 : rows.findIndex((r) => r.key === activeKey);\n if (activeIndex === -1 && rows.length > 0) activeIndex = 0;\n const activeRow = activeIndex >= 0 ? rows[activeIndex] : undefined;\n const activeSection =\n activeRow !== undefined ? sections.find((s) => s.listKey === activeRow.listKey) : undefined;\n\n // Move real DOM focus to the active item after keyboard-driven changes so\n // the focus ring is a genuine DOM focus, never only a WebGL highlight.\n useEffect(() => {\n if (!pendingFocusRef.current) return;\n pendingFocusRef.current = false;\n if (activeRow === undefined) return;\n itemsRef.current.get(activeRow.domId)?.focus();\n });\n\n // --- keyboard model ('topology'; see module JSDoc) ---\n const focusItemSoon = (): void => {\n pendingFocusRef.current = true;\n };\n\n const moveTo = (index: number): void => {\n if (rows.length === 0) return;\n const row = rows[Math.min(Math.max(index, 0), rows.length - 1)];\n if (row === undefined) return;\n setActiveKey(row.key);\n focusItemSoon();\n };\n\n const pageBy = (delta: number): void => {\n if (activeRow === undefined) return;\n const listKey = activeRow.listKey;\n const items = listItems[listKey];\n const pageCount = Math.max(1, Math.ceil(items.length / windowSize));\n const current = Math.min(Math.max(pages[listKey], 0), pageCount - 1);\n const next = Math.min(Math.max(current + delta, 0), pageCount - 1);\n if (next === current) return;\n setPages((p) => ({ ...p, [listKey]: next }));\n const first = items[next * windowSize];\n if (first !== undefined) setActiveKey(`${listKey}:${first}`);\n focusItemSoon();\n };\n\n /** Enter: focus the node in the graph (camera fly) and re-root the\n * neighborhood. The returned ids come from the core adjacency resolution\n * (undirected; see module JSDoc). Search rows route through the §16.5\n * result contract instead: `activateSearchResult` focuses an in-scene,\n * mask-visible id and otherwise only CLASSIFIES — the navigator never\n * mutates scope/filters (the host reacts through its own channels). */\n const activate = (row: NavRow): void => {\n if (row.listKey === 'search') {\n const result = searchResults.find((r) => r.id === row.nodeId);\n if (result !== undefined) instance.activateSearchResult(result);\n return;\n }\n const neighbors = instance.focusNode(row.nodeId);\n degreesRef.current.counts.set(row.nodeId, neighbors.length);\n setRoot({ id: row.nodeId, neighbors, model: slice.model });\n setPages((p) => ({ ...p, neighbors: 0 }));\n setActiveKey(`root:${row.nodeId}`);\n focusItemSoon();\n };\n\n /** Space: toggle membership through the §6.4 selection ownership path.\n * Search rows may name ids outside the accepted model — those no-op. */\n const toggleSelection = (row: NavRow): void => {\n if (row.listKey === 'search' && instance.getNode(row.nodeId) === undefined) return;\n const current = slice.selection.nodeIds;\n const next = current.includes(row.nodeId)\n ? current.filter((id) => id !== row.nodeId)\n : [...current, row.nodeId];\n instance.selectNodes(next);\n };\n\n const clearTransientFocus = (): void => {\n setRoot(null);\n setActiveKey(null);\n focusItemSoon();\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (activeRow === undefined) return;\n switch (e.key) {\n case 'ArrowDown':\n moveTo(activeIndex + 1);\n break;\n case 'ArrowUp':\n moveTo(activeIndex - 1);\n break;\n case 'Home':\n moveTo(0);\n break;\n case 'End':\n moveTo(rows.length - 1);\n break;\n case 'PageDown':\n pageBy(1);\n break;\n case 'PageUp':\n pageBy(-1);\n break;\n case 'Enter':\n activate(activeRow);\n break;\n case ' ':\n toggleSelection(activeRow);\n break;\n case 'Escape':\n if (root === null) return; // nothing transient — let it bubble\n clearTransientFocus();\n break;\n default:\n return;\n }\n e.preventDefault();\n };\n\n const panelLabel = props.label ?? 'Graph navigator';\n\n return (\n <div\n data-orbit-navigator=\"\"\n role=\"group\"\n aria-label={panelLabel}\n className={props.className}\n style={{ ...PANEL_STYLE, ...props.style }}\n >\n <div\n role=\"listbox\"\n aria-label={`${panelLabel} items`}\n aria-activedescendant={activeRow?.domId}\n aria-keyshortcuts=\"ArrowUp ArrowDown Home End PageUp PageDown Enter Space Escape\"\n onKeyDown={onKeyDown}\n style={LIST_STYLE}\n >\n {rows.length === 0 ? (\n <div role=\"presentation\" style={EMPTY_STYLE}>\n No nodes\n </div>\n ) : (\n sections.map((section) => (\n <div key={section.key} role=\"group\" aria-label={section.groupLabel}>\n <div role=\"presentation\" style={HEADER_STYLE}>\n {section.label}\n </div>\n {section.rows.map((row) => {\n const isActive = row.key === activeRow?.key;\n return (\n <div\n key={row.key}\n id={row.domId}\n role=\"option\"\n aria-selected={row.selected}\n tabIndex={isActive ? 0 : -1}\n ref={(el) => {\n if (el === null) itemsRef.current.delete(row.domId);\n else itemsRef.current.set(row.domId, el);\n }}\n onClick={() => {\n setActiveKey(row.key);\n focusItemSoon();\n }}\n style={isActive ? ACTIVE_ITEM_STYLE : ITEM_STYLE}\n >\n {row.text}\n </div>\n );\n })}\n </div>\n ))\n )}\n </div>\n {activeSection !== undefined ? (\n <div data-orbit-navigator-paging=\"\" style={PAGING_STYLE}>\n <button\n type=\"button\"\n onClick={() => pageBy(-1)}\n disabled={activeSection.page === 0}\n aria-keyshortcuts=\"PageUp\"\n >\n Previous page\n </button>\n <span role=\"status\" data-orbit-navigator-page-status=\"\">\n {`${activeSection.label}: page ${activeSection.page + 1} of ${\n activeSection.pageCount\n } (${activeSection.total} ${activeSection.total === 1 ? 'item' : 'items'})`}\n </span>\n <button\n type=\"button\"\n onClick={() => pageBy(1)}\n disabled={activeSection.page >= activeSection.pageCount - 1}\n aria-keyshortcuts=\"PageDown\"\n >\n Next page\n </button>\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/Navigator/index.tsx"],"names":[],"mappings":";;;;AAkFA,IAAM,wBAAA,GAA2B,EAAA;AAgEjC,SAAS,iBAAiB,QAAA,EAA2C;AACnE,EAAA,MAAM,QAAA,GAAW,OAA6B,IAAI,CAAA;AAClD,EAAA,MAAM,SAAA,GAAY,WAAA;AAAA,IAChB,CAAC,EAAA,KAAmB,QAAA,CAAS,KAAA,CAAM,UAAU,EAAE,CAAA;AAAA,IAC/C,CAAC,QAAQ;AAAA,GACX;AACA,EAAA,MAAM,WAAA,GAAc,YAAY,MAAqB;AACnD,IAAA,MAAM,CAAA,GAAI,QAAA,CAAS,KAAA,CAAM,QAAA,EAAS;AAClC,IAAA,MAAM,OAAO,QAAA,CAAS,OAAA;AACtB,IAAA,IACE,IAAA,KAAS,IAAA,IACT,IAAA,CAAK,SAAA,KAAc,CAAA,CAAE,SAAA,IACrB,IAAA,CAAK,IAAA,KAAS,CAAA,CAAE,IAAA,IAChB,IAAA,CAAK,MAAA,KAAW,CAAA,CAAE,iBAClB,IAAA,CAAK,KAAA,KAAU,CAAA,CAAE,SAAA,CAAU,KAAA,IAC3B,IAAA,CAAK,SAAA,KAAc,CAAA,CAAE,SAAA,IACrB,IAAA,CAAK,MAAA,KAAW,CAAA,CAAE,MAAA,EAClB;AACA,MAAA,OAAO,IAAA;AAAA,IACT;AACA,IAAA,MAAM,IAAA,GAAsB;AAAA,MAC1B,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,MAAM,CAAA,CAAE,IAAA;AAAA,MACR,QAAQ,CAAA,CAAE,aAAA;AAAA,MACV,KAAA,EAAO,EAAE,SAAA,CAAU,KAAA;AAAA,MACnB,WAAW,CAAA,CAAE,SAAA;AAAA,MACb,QAAQ,CAAA,CAAE;AAAA,KACZ;AACA,IAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,IAAA,OAAO,IAAA;AAAA,EACT,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AACb,EAAA,OAAO,oBAAA,CAAqB,SAAA,EAAW,WAAA,EAAa,WAAW,CAAA;AACjE;AASA,SAAS,UAAA,CAAW,GAAA,EAAwB,OAAA,EAAiB,IAAA,EAA0B;AACrF,EAAA,MAAM,SAAA,GAAY,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,IAAA,CAAK,GAAA,CAAI,MAAA,GAAS,IAAI,CAAC,CAAA;AAC1D,EAAA,MAAM,IAAA,GAAO,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,OAAA,EAAS,CAAC,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA;AACzD,EAAA,OAAO,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,GAAA,CAAI,KAAA,CAAM,IAAA,GAAO,IAAA,EAAM,IAAA,GAAO,IAAA,GAAO,IAAI,CAAA,EAAE;AAC9E;AAGA,IAAM,WAAA,GAA6B;AAAA,EACjC,aAAA,EAAe,MAAA;AAAA,EACf,OAAA,EAAS,MAAA;AAAA,EACT,aAAA,EAAe,QAAA;AAAA,EACf,GAAA,EAAK,CAAA;AAAA,EACL,QAAA,EAAU,GAAA;AAAA,EACV,QAAA,EAAU;AACZ,CAAA;AACA,IAAM,UAAA,GAA4B,EAAE,SAAA,EAAW,MAAA,EAAQ,WAAW,GAAA,EAAI;AACtE,IAAM,YAAA,GAA8B,EAAE,UAAA,EAAY,GAAA,EAAK,SAAS,SAAA,EAAU;AAC1E,IAAM,UAAA,GAA4B;AAAA,EAChC,OAAA,EAAS,SAAA;AAAA,EACT,MAAA,EAAQ,SAAA;AAAA,EACR,UAAA,EAAY,QAAA;AAAA,EACZ,QAAA,EAAU,QAAA;AAAA,EACV,YAAA,EAAc;AAChB,CAAA;AAEA,IAAM,iBAAA,GAAmC;AAAA,EACvC,GAAG,UAAA;AAAA,EACH,OAAA,EAAS,wBAAA;AAAA,EACT,aAAA,EAAe;AACjB,CAAA;AACA,IAAM,eAA8B,EAAE,OAAA,EAAS,QAAQ,UAAA,EAAY,QAAA,EAAU,KAAK,CAAA,EAAE;AACpF,IAAM,WAAA,GAA6B,EAAE,OAAA,EAAS,SAAA,EAAW,WAAW,QAAA,EAAS;AAEtE,SAAS,eAAe,KAAA,EAA0C;AACvE,EAAA,MAAM,QAAA,GAAW,mBAAA,CAAoB,KAAA,CAAM,QAAA,EAAU,kBAAkB,CAAA;AACvE,EAAA,MAAM,KAAA,GAAQ,iBAAiB,QAAQ,CAAA;AAIvC,EAAA,KAAK,KAAA,CAAM,YAAA;AAEX,EAAA,MAAM,SAAS,KAAA,EAAM;AACrB,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAyB,IAAI,CAAA;AACrD,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,QAAA,CAAuC;AAAA,IAC/D,MAAA,EAAQ,CAAA;AAAA,IACR,SAAA,EAAW,CAAA;AAAA,IACX,SAAA,EAAW,CAAA;AAAA,IACX,KAAA,EAAO;AAAA,GACR,CAAA;AAED,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAwB,IAAI,CAAA;AAG9D,EAAA,MAAM,SAAA,GAAY,MAAA,iBAA4B,IAAI,GAAA,EAAK,CAAA;AAEvD,EAAA,MAAM,aAAa,MAAA,CAAuD;AAAA,IACxE,KAAA,EAAO,EAAA;AAAA,IACP,MAAA,sBAAY,GAAA;AAAI,GACjB,CAAA;AACD,EAAA,MAAM,QAAA,GAAW,MAAA,iBAAiC,IAAI,GAAA,EAAK,CAAA;AAE3D,EAAA,MAAM,eAAA,GAAkB,OAAO,KAAK,CAAA;AAIpC,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAS,IAAA,IAAQ,IAAA,CAAK,UAAU,KAAA,CAAM,KAAA,UAAe,IAAI,CAAA;AAAA,EAC/D,CAAA,EAAG,CAAC,IAAA,EAAM,KAAA,CAAM,KAAK,CAAC,CAAA;AAMtB,EAAA,MAAM,eAAA,GAAkB,YAAY,MAAY;AAC9C,IAAA,IAAI,QAAA,CAAS,MAAM,QAAA,EAAS,CAAE,MAAM,MAAA,KAAW,IAAA,EAAM,QAAA,CAAS,aAAA,CAAc,IAAI,CAAA;AAAA,EAClF,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAGb,EAAA,SAAA,CAAU,MAAM,MAAM,eAAA,EAAgB,EAAG,CAAC,eAAe,CAAC,CAAA;AAG1D,EAAA,IAAI,UAAA,CAAW,OAAA,CAAQ,KAAA,KAAU,KAAA,CAAM,KAAA,EAAO;AAC5C,IAAA,UAAA,CAAW,OAAA,GAAU,EAAE,KAAA,EAAO,KAAA,CAAM,OAAO,MAAA,kBAAQ,IAAI,KAAI,EAAE;AAAA,EAC/D;AAEA,EAAA,MAAM,aAAA,GAAgB,SAAS,gBAAA,EAAiB;AAGhD,EAAA,MAAM,YAAY,aAAA,EAAe,eAAA;AACjC,EAAA,MAAM,UAAA,GACJ,OAAO,SAAA,KAAc,QAAA,IAAY,MAAA,CAAO,QAAA,CAAS,SAAS,CAAA,IAAK,SAAA,IAAa,CAAA,GACxE,IAAA,CAAK,KAAA,CAAM,SAAS,CAAA,GACpB,wBAAA;AAEN,EAAA,MAAM,qBAAqB,aAAA,EAAe,kBAAA;AAC1C,EAAA,MAAM,MAAA,GAAS,CAAC,EAAA,KAAuB;AACrC,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,OAAA,CAAQ,EAAE,CAAA;AAChC,IAAA,IAAI,IAAA,KAAS,QAAW,OAAO,EAAA;AAC/B,IAAA,IAAI,uBAAuB,MAAA,EAAW;AACpC,MAAA,IAAI;AACF,QAAA,OAAO,mBAAmB,IAAI,CAAA;AAAA,MAChC,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,GAAQ,OAAO,CAAA;AAClC,IAAA,OAAO,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,IAAA,CAAK,EAAA;AAAA,EAClD,CAAA;AAEA,EAAA,MAAM,WAAA,GAAc,IAAI,GAAA,CAAY,KAAA,CAAM,UAAU,OAAO,CAAA;AAG3D,EAAA,MAAM,OAAA,GAAU,CAAC,EAAA,KAAuB;AACtC,IAAA,MAAM,KAAA,GAAQ,CAAC,MAAA,CAAO,EAAE,CAAC,CAAA;AACzB,IAAA,MAAM,KAAA,GAAQ,UAAA,CAAW,OAAA,CAAQ,MAAA,CAAO,IAAI,EAAE,CAAA;AAC9C,IAAA,IAAI,KAAA,KAAU,QAAW,KAAA,CAAM,IAAA,CAAK,UAAU,CAAA,GAAI,YAAA,GAAe,CAAA,EAAG,KAAK,CAAA,UAAA,CAAY,CAAA;AACrF,IAAA,IAAI,MAAM,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA,EAAG,KAAA,CAAM,KAAK,QAAQ,CAAA;AAC3C,IAAA,IAAI,MAAM,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA,EAAG,KAAA,CAAM,KAAK,QAAQ,CAAA;AAC7C,IAAA,OAAO,KAAA,CAAM,KAAK,QAAK,CAAA;AAAA,EACzB,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,OAAA,EAAiB,EAAA,KAAuB;AACxD,IAAA,MAAM,SAAS,SAAA,CAAU,OAAA;AACzB,IAAA,IAAI,KAAA,GAAQ,MAAA,CAAO,GAAA,CAAI,EAAE,CAAA;AACzB,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,KAAA,GAAQ,MAAA,CAAO,IAAA;AACf,MAAA,MAAA,CAAO,GAAA,CAAI,IAAI,KAAK,CAAA;AAAA,IACtB;AACA,IAAA,OAAO,CAAA,EAAG,MAAM,CAAA,IAAA,EAAO,OAAO,IAAI,KAAK,CAAA,CAAA;AAAA,EACzC,CAAA;AAEA,EAAA,MAAM,OAAA,GAAU,CAAC,OAAA,EAAuB,EAAA,MAAwB;AAAA,IAC9D,GAAA,EAAK,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA;AAAA,IACrB,KAAA,EAAO,QAAA,CAAS,OAAA,EAAS,EAAE,CAAA;AAAA,IAC3B,MAAA,EAAQ,EAAA;AAAA,IACR,OAAA;AAAA,IACA,IAAA,EAAM,QAAQ,EAAE,CAAA;AAAA,IAChB,QAAA,EAAU,WAAA,CAAY,GAAA,CAAI,EAAE;AAAA,GAC9B,CAAA;AAGA,EAAA,MAAM,gBAAyC,KAAA,CAAM,MAAA,KAAW,OAAO,EAAC,GAAI,MAAM,MAAA,CAAO,OAAA;AACzF,EAAA,MAAM,SAAA,GAAqD;AAAA,IACzD,QAAQ,aAAA,CAAc,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,EAAE,CAAA;AAAA,IACrC,SAAA,EAAW,IAAA,KAAS,IAAA,GAAO,IAAA,CAAK,YAAY,EAAC;AAAA,IAC7C,SAAA,EAAW,MAAM,SAAA,CAAU,OAAA;AAAA;AAAA;AAAA,IAG3B,OAAO,IAAA,KAAS,IAAA,GAAO,QAAA,CAAS,eAAA,KAAoB;AAAC,GACvD;AAEA,EAAA,MAAM,WAAyB,EAAC;AAChC,EAAA,IAAI,KAAA,CAAM,WAAW,IAAA,EAAM;AACzB,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,MAAA,EAAQ,KAAA,CAAM,QAAQ,UAAU,CAAA;AAClE,IAAA,MAAM,QAAQ,aAAA,CAAc,MAAA;AAC5B,IAAA,MAAM,KAAA,GAAQ,KAAK,IAAA,GAAO,UAAA;AAC1B,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,QAAA;AAAA,MACL,OAAA,EAAS,QAAA;AAAA,MACT,KAAA,EAAO,gBAAA;AAAA,MACP,YAAY,CAAA,mBAAA,EAAsB,KAAA,CAAM,OAAO,KAAK,CAAA,EAAA,EAAK,KAAK,CAAA,CAAA,EAC5D,KAAA,KAAU,CAAA,GAAI,QAAA,GAAW,SAC3B,CAAA,OAAA,EAAU,IAAA,CAAK,OAAO,CAAC,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA;AAAA;AAAA;AAAA,MAI5C,MAAM,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAC,IAAI,CAAA,KAAM;AAC9B,QAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AAChC,QAAA,MAAM,KAAA,GAAQ,aAAA,CAAc,KAAA,GAAQ,CAAC,CAAA,EAAG,KAAA;AACxC,QAAA,OAAO,UAAU,MAAA,GAAY,GAAA,GAAM,EAAE,GAAG,GAAA,EAAK,MAAM,KAAA,EAAM;AAAA,MAC3D,CAAC,CAAA;AAAA,MACD,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,IAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,SAAA,EAAW,KAAA,CAAM,WAAW,UAAU,CAAA;AACxE,IAAA,MAAM,OAAA,GAAkB;AAAA,MACtB,GAAA,EAAK,CAAA,KAAA,EAAQ,IAAA,CAAK,EAAE,CAAA,CAAA;AAAA,MACpB,KAAA,EAAO,QAAA,CAAS,MAAA,EAAQ,IAAA,CAAK,EAAE,CAAA;AAAA,MAC/B,QAAQ,IAAA,CAAK,EAAA;AAAA,MACb,OAAA,EAAS,WAAA;AAAA,MACT,IAAA,EAAM,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAA;AAAA,MACrB,QAAA,EAAU,WAAA,CAAY,GAAA,CAAI,IAAA,CAAK,EAAE;AAAA,KACnC;AACA,IAAA,MAAM,KAAA,GAAQ,KAAK,SAAA,CAAU,MAAA;AAC7B,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,SAAA;AAAA,MACL,OAAA,EAAS,WAAA;AAAA,MACT,KAAA,EAAO,cAAA;AAAA,MACP,YAAY,CAAA,aAAA,EAAgB,MAAA,CAAO,KAAK,EAAE,CAAC,KAAK,KAAK,CAAA,CAAA,EACnD,UAAU,CAAA,GAAI,UAAA,GAAa,WAC7B,CAAA,OAAA,EAAU,IAAA,CAAK,OAAO,CAAC,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA,MAC5C,IAAA,EAAM,CAAC,OAAA,EAAS,GAAG,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAC,EAAA,KAAO,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAC,CAAC,CAAA;AAAA,MACnE,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,IAAI,KAAA,CAAM,SAAA,CAAU,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACtC,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,SAAA,EAAW,KAAA,CAAM,WAAW,UAAU,CAAA;AACxE,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,SAAA,CAAU,OAAA,CAAQ,MAAA;AACtC,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,WAAA;AAAA,MACL,OAAA,EAAS,WAAA;AAAA,MACT,KAAA,EAAO,WAAA;AAAA,MACP,UAAA,EAAY,CAAA,WAAA,EAAc,KAAK,CAAA,CAAA,EAAI,UAAU,CAAA,GAAI,MAAA,GAAS,OAAO,CAAA,OAAA,EAC/D,IAAA,CAAK,IAAA,GAAO,CACd,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA,MACrB,IAAA,EAAM,KAAK,KAAA,CAAM,GAAA,CAAI,CAAC,EAAA,KAAO,OAAA,CAAQ,WAAA,EAAa,EAAE,CAAC,CAAA;AAAA,MACrD,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AACA,EAAA,IAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,OAAO,UAAA,CAAW,SAAA,CAAU,KAAA,EAAO,KAAA,CAAM,OAAO,UAAU,CAAA;AAChE,IAAA,MAAM,KAAA,GAAQ,UAAU,KAAA,CAAM,MAAA;AAC9B,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,GAAA,EAAK,OAAA;AAAA,MACL,OAAA,EAAS,OAAA;AAAA,MACT,KAAA,EAAO,WAAA;AAAA,MACP,UAAA,EAAY,CAAA,WAAA,EAAc,KAAK,CAAA,CAAA,EAAI,UAAU,CAAA,GAAI,MAAA,GAAS,OAAO,CAAA,OAAA,EAC/D,IAAA,CAAK,IAAA,GAAO,CACd,CAAA,IAAA,EAAO,KAAK,SAAS,CAAA,CAAA;AAAA,MACrB,IAAA,EAAM,KAAK,KAAA,CAAM,GAAA,CAAI,CAAC,EAAA,KAAO,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAC,CAAA;AAAA,MACjD,MAAM,IAAA,CAAK,IAAA;AAAA,MACX,WAAW,IAAA,CAAK,SAAA;AAAA,MAChB,KAAA,EAAO;AAAA,KACR,CAAA;AAAA,EACH;AAEA,EAAA,MAAM,OAAiB,QAAA,CAAS,OAAA,CAAQ,CAAC,CAAA,KAAM,EAAE,IAAI,CAAA;AACrD,EAAA,IAAI,WAAA,GAAc,SAAA,KAAc,IAAA,GAAO,EAAA,GAAK,IAAA,CAAK,UAAU,CAAC,CAAA,KAAM,CAAA,CAAE,GAAA,KAAQ,SAAS,CAAA;AACrF,EAAA,IAAI,WAAA,KAAgB,EAAA,IAAM,IAAA,CAAK,MAAA,GAAS,GAAG,WAAA,GAAc,CAAA;AACzD,EAAA,MAAM,SAAA,GAAY,WAAA,IAAe,CAAA,GAAI,IAAA,CAAK,WAAW,CAAA,GAAI,MAAA;AACzD,EAAA,MAAM,aAAA,GACJ,SAAA,KAAc,MAAA,GAAY,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,KAAY,SAAA,CAAU,OAAO,CAAA,GAAI,MAAA;AAIpF,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,gBAAgB,OAAA,EAAS;AAC9B,IAAA,eAAA,CAAgB,OAAA,GAAU,KAAA;AAC1B,IAAA,IAAI,cAAc,MAAA,EAAW;AAC7B,IAAA,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,SAAA,CAAU,KAAK,GAAG,KAAA,EAAM;AAAA,EAC/C,CAAC,CAAA;AAGD,EAAA,MAAM,gBAAgB,MAAY;AAChC,IAAA,eAAA,CAAgB,OAAA,GAAU,IAAA;AAAA,EAC5B,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,KAAA,KAAwB;AACtC,IAAA,IAAI,IAAA,CAAK,WAAW,CAAA,EAAG;AACvB,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,CAAC,CAAA,EAAG,IAAA,CAAK,MAAA,GAAS,CAAC,CAAC,CAAA;AAC9D,IAAA,IAAI,QAAQ,MAAA,EAAW;AACvB,IAAA,YAAA,CAAa,IAAI,GAAG,CAAA;AAGpB,IAAA,QAAA,CAAS,aAAA,CAAc,IAAI,MAAM,CAAA;AACjC,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,KAAA,KAAwB;AACtC,IAAA,IAAI,cAAc,MAAA,EAAW;AAC7B,IAAA,MAAM,UAAU,SAAA,CAAU,OAAA;AAC1B,IAAA,MAAM,KAAA,GAAQ,UAAU,OAAO,CAAA;AAC/B,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,CAAI,CAAA,EAAG,KAAK,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,UAAU,CAAC,CAAA;AAClE,IAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,EAAG,CAAC,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA;AACnE,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,UAAU,KAAA,EAAO,CAAC,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA;AACjE,IAAA,IAAI,SAAS,OAAA,EAAS;AACtB,IAAA,QAAA,CAAS,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,OAAO,GAAG,IAAA,EAAK,CAAE,CAAA;AAC3C,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,GAAO,UAAU,CAAA;AACrC,IAAA,IAAI,UAAU,MAAA,EAAW;AACvB,MAAA,YAAA,CAAa,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,KAAK,CAAA,CAAE,CAAA;AAClC,MAAA,QAAA,CAAS,cAAc,KAAK,CAAA;AAAA,IAC9B;AACA,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAQA,EAAA,MAAM,QAAA,GAAW,CAAC,GAAA,KAAsB;AACtC,IAAA,IAAI,GAAA,CAAI,YAAY,QAAA,EAAU;AAC5B,MAAA,MAAM,MAAA,GAAS,cAAc,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,IAAI,MAAM,CAAA;AAC5D,MAAA,IAAI,MAAA,KAAW,MAAA,EAAW,QAAA,CAAS,oBAAA,CAAqB,MAAM,CAAA;AAC9D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,SAAA,GAAY,QAAA,CAAS,SAAA,CAAU,GAAA,CAAI,MAAM,CAAA;AAC/C,IAAA,UAAA,CAAW,QAAQ,MAAA,CAAO,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,UAAU,MAAM,CAAA;AAC1D,IAAA,OAAA,CAAQ,EAAE,IAAI,GAAA,CAAI,MAAA,EAAQ,WAAW,KAAA,EAAO,KAAA,CAAM,OAAO,CAAA;AACzD,IAAA,QAAA,CAAS,CAAC,CAAA,MAAO,EAAE,GAAG,CAAA,EAAG,SAAA,EAAW,GAAE,CAAE,CAAA;AACxC,IAAA,YAAA,CAAa,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACjC,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAIA,EAAA,MAAM,eAAA,GAAkB,CAAC,GAAA,KAAsB;AAC7C,IAAA,IAAI,GAAA,CAAI,YAAY,QAAA,IAAY,QAAA,CAAS,QAAQ,GAAA,CAAI,MAAM,MAAM,MAAA,EAAW;AAC5E,IAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,OAAA;AAChC,IAAA,MAAM,OAAO,OAAA,CAAQ,QAAA,CAAS,IAAI,MAAM,CAAA,GACpC,QAAQ,MAAA,CAAO,CAAC,EAAA,KAAO,EAAA,KAAO,IAAI,MAAM,CAAA,GACxC,CAAC,GAAG,OAAA,EAAS,IAAI,MAAM,CAAA;AAC3B,IAAA,QAAA,CAAS,YAAY,IAAI,CAAA;AAAA,EAC3B,CAAA;AAEA,EAAA,MAAM,sBAAsB,MAAY;AACtC,IAAA,OAAA,CAAQ,IAAI,CAAA;AACZ,IAAA,YAAA,CAAa,IAAI,CAAA;AACjB,IAAA,eAAA,EAAgB;AAChB,IAAA,aAAA,EAAc;AAAA,EAChB,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,CAAA,KAA2C;AAC5D,IAAA,IAAI,cAAc,MAAA,EAAW;AAC7B,IAAA,QAAQ,EAAE,GAAA;AAAK,MACb,KAAK,WAAA;AACH,QAAA,MAAA,CAAO,cAAc,CAAC,CAAA;AACtB,QAAA;AAAA,MACF,KAAK,SAAA;AACH,QAAA,MAAA,CAAO,cAAc,CAAC,CAAA;AACtB,QAAA;AAAA,MACF,KAAK,MAAA;AACH,QAAA,MAAA,CAAO,CAAC,CAAA;AACR,QAAA;AAAA,MACF,KAAK,KAAA;AACH,QAAA,MAAA,CAAO,IAAA,CAAK,SAAS,CAAC,CAAA;AACtB,QAAA;AAAA,MACF,KAAK,UAAA;AACH,QAAA,MAAA,CAAO,CAAC,CAAA;AACR,QAAA;AAAA,MACF,KAAK,QAAA;AACH,QAAA,MAAA,CAAO,EAAE,CAAA;AACT,QAAA;AAAA,MACF,KAAK,OAAA;AACH,QAAA,QAAA,CAAS,SAAS,CAAA;AAClB,QAAA;AAAA,MACF,KAAK,GAAA;AACH,QAAA,eAAA,CAAgB,SAAS,CAAA;AACzB,QAAA;AAAA,MACF,KAAK,QAAA;AACH,QAAA,IAAI,SAAS,IAAA,EAAM;AACnB,QAAA,mBAAA,EAAoB;AACpB,QAAA;AAAA,MACF;AACE,QAAA;AAAA;AAEJ,IAAA,CAAA,CAAE,cAAA,EAAe;AAAA,EACnB,CAAA;AAEA,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,IAAS,iBAAA;AAElC,EAAA,uBACE,IAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,sBAAA,EAAqB,EAAA;AAAA,MACrB,IAAA,EAAK,OAAA;AAAA,MACL,YAAA,EAAY,UAAA;AAAA,MACZ,WAAW,KAAA,CAAM,SAAA;AAAA,MACjB,OAAO,EAAE,GAAG,WAAA,EAAa,GAAG,MAAM,KAAA,EAAM;AAAA,MACxC,MAAA,EAAQ,CAAC,CAAA,KAAM;AAGb,QAAA,IAAI,CAAC,CAAA,CAAE,aAAA,CAAc,QAAA,CAAS,CAAA,CAAE,aAA4B,CAAA,EAAG;AAC7D,UAAA,eAAA,EAAgB;AAAA,QAClB;AAAA,MACF,CAAA;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,KAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,SAAA;AAAA,YACL,YAAA,EAAY,GAAG,UAAU,CAAA,MAAA,CAAA;AAAA,YACzB,yBAAuB,SAAA,EAAW,KAAA;AAAA,YAClC,mBAAA,EAAkB,+DAAA;AAAA,YAClB,SAAA;AAAA,YACA,KAAA,EAAO,UAAA;AAAA,YAEN,QAAA,EAAA,IAAA,CAAK,WAAW,CAAA,mBACf,GAAA,CAAC,SAAI,IAAA,EAAK,cAAA,EAAe,OAAO,WAAA,EAAa,QAAA,EAAA,UAAA,EAE7C,IAEA,QAAA,CAAS,GAAA,CAAI,CAAC,OAAA,qBACZ,IAAA,CAAC,SAAsB,IAAA,EAAK,OAAA,EAAQ,YAAA,EAAY,OAAA,CAAQ,UAAA,EACtD,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,SAAI,IAAA,EAAK,cAAA,EAAe,KAAA,EAAO,YAAA,EAC7B,kBAAQ,KAAA,EACX,CAAA;AAAA,cACC,OAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,CAAC,GAAA,KAAQ;AACzB,gBAAA,MAAM,QAAA,GAAW,GAAA,CAAI,GAAA,KAAQ,SAAA,EAAW,GAAA;AACxC,gBAAA,uBACE,GAAA;AAAA,kBAAC,KAAA;AAAA,kBAAA;AAAA,oBAEC,IAAI,GAAA,CAAI,KAAA;AAAA,oBACR,IAAA,EAAK,QAAA;AAAA,oBACL,iBAAe,GAAA,CAAI,QAAA;AAAA,oBACnB,QAAA,EAAU,WAAW,CAAA,GAAI,EAAA;AAAA,oBACzB,GAAA,EAAK,CAAC,EAAA,KAAO;AACX,sBAAA,IAAI,OAAO,IAAA,EAAM,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,IAAI,KAAK,CAAA;AAAA,2BAC7C,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,GAAA,CAAI,OAAO,EAAE,CAAA;AAAA,oBACzC,CAAA;AAAA,oBACA,SAAS,MAAM;AACb,sBAAA,YAAA,CAAa,IAAI,GAAG,CAAA;AACpB,sBAAA,QAAA,CAAS,aAAA,CAAc,IAAI,MAAM,CAAA;AACjC,sBAAA,aAAA,EAAc;AAAA,oBAChB,CAAA;AAAA,oBACA,KAAA,EAAO,WAAW,iBAAA,GAAoB,UAAA;AAAA,oBAErC,QAAA,EAAA,GAAA,CAAI;AAAA,mBAAA;AAAA,kBAhBA,GAAA,CAAI;AAAA,iBAiBX;AAAA,cAEJ,CAAC;AAAA,aAAA,EAAA,EA3BO,OAAA,CAAQ,GA4BlB,CACD;AAAA;AAAA,SAEL;AAAA,QACC,kBAAkB,MAAA,mBACjB,IAAA,CAAC,SAAI,6BAAA,EAA4B,EAAA,EAAG,OAAO,YAAA,EACzC,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,MAAM,MAAA,CAAO,EAAE,CAAA;AAAA,cACxB,QAAA,EAAU,cAAc,IAAA,KAAS,CAAA;AAAA,cACjC,mBAAA,EAAkB,QAAA;AAAA,cACnB,QAAA,EAAA;AAAA;AAAA,WAED;AAAA,0BACA,GAAA,CAAC,MAAA,EAAA,EAAK,IAAA,EAAK,QAAA,EAAS,kCAAA,EAAiC,IAClD,QAAA,EAAA,CAAA,EAAG,aAAA,CAAc,KAAK,CAAA,OAAA,EAAU,aAAA,CAAc,IAAA,GAAO,CAAC,CAAA,IAAA,EACrD,aAAA,CAAc,SAChB,CAAA,EAAA,EAAK,aAAA,CAAc,KAAK,CAAA,CAAA,EAAI,aAAA,CAAc,KAAA,KAAU,CAAA,GAAI,MAAA,GAAS,OAAO,CAAA,CAAA,CAAA,EAC1E,CAAA;AAAA,0BACA,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,OAAA,EAAS,MAAM,MAAA,CAAO,CAAC,CAAA;AAAA,cACvB,QAAA,EAAU,aAAA,CAAc,IAAA,IAAQ,aAAA,CAAc,SAAA,GAAY,CAAA;AAAA,cAC1D,mBAAA,EAAkB,UAAA;AAAA,cACnB,QAAA,EAAA;AAAA;AAAA;AAED,SAAA,EACF,CAAA,GACE;AAAA;AAAA;AAAA,GACN;AAEJ","file":"Navigator.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/Navigator` — `<GraphNavigator>`, the\n * bounded semantic navigator (spec §15.1; S7-T11/T19).\n *\n * The navigator is the keyboard/screen-reader equivalent surface for the\n * canvas: a visually compact panel with `role=\"listbox\"` semantics driven by\n * a roving tabindex — exactly one item is tabbable at any time, and\n * `aria-activedescendant` on the listbox container mirrors the active item's\n * stable DOM id. It is bounded by design: every list is paged by\n * `accessibility.navigatorWindow` (default 50), so the DOM never grows one\n * row per entity.\n *\n * Sections:\n * - **Search results** (S11-T15) — the last completed §16.5 search\n * (`store.search`), shown whenever the slice is non-null (it clears on\n * `clearSearch()` / dataset swap). Paged like every other list; Enter\n * routes through `instance.activateSearchResult` (§16.5 result contract:\n * an in-scene, mask-visible id focuses; anything else is classification\n * only — the navigator never mutates scope/filters). Row text prefers the\n * service-supplied `label`.\n * - **Focused node** — the transiently focused root plus its paged 1-hop\n * neighborhood.\n * - **Selection** — the current node selection, paged.\n * - **All nodes** — the entry list (first `navigatorWindow` nodes in\n * accepted-base order, paged over the full set), shown when nothing is\n * focused.\n *\n * ## Keyboard model — `keyboardMode: 'topology'` (the v0.4 default and only mode)\n *\n * | Key | Action |\n * |---------------------|-------------------------------------------------------------------|\n * | ArrowDown / ArrowUp | move the active item within the current list |\n * | Home / End | move to the bounds of the current list |\n * | PageDown / PageUp | next / previous page of the active item's list |\n * | Enter | focus the node (`instance.focusNode` — camera fly) and re-root the neighborhood at it |\n * | Space | toggle the node's selection membership |\n * | Escape | clear the transient focus (returns to the entry list) |\n *\n * `keyboardMode: 'spatial'` (arrows move by screen direction) is a\n * documented post-v0.4 mode and intentionally NOT part of the accepted prop\n * union yet — only `'topology'` type-checks in v0.4.\n *\n * Text names resolve via `accessibility.getAccessibleLabel`, falling back to\n * `attrs.label` (when a string) and then the node id — always rendered as\n * TEXT NODES (§14 untrusted-content rule; markup in labels stays literal).\n * Each item exposes selection state (`aria-selected`), its neighbor count\n * when known, and pinned/hidden state in text (e.g. `hub · 5 neighbors ·\n * pinned`).\n *\n * Neighborhood semantics (v0.4): direction is UNDIRECTED — `focusNode`\n * resolves the 1-hop neighbor ids through the engine's adjacency when the\n * adapter provides one, else through the core CSR adjacency\n * (`buildAdjacency`/`neighborsOf`) built lazily per accepted model revision\n * and invalidated on model change; self-loops are excluded and parallel\n * edges deduplicate. The navigator caches the resolved neighborhood and the\n * per-node neighbor counts it has learned in refs keyed by\n * `revisions.model`; a model change drops the (now stale) transient root and\n * returns to the entry list. Neighbor counts therefore appear on items whose\n * neighborhood has been resolved (the current and previously visited roots);\n * a public O(1) degree read is a post-v0.4 core surface.\n *\n * Focus never lives only in WebGL pixels (§15.1): the active item carries a\n * visible DOM focus outline and receives real DOM focus on keyboard moves.\n * Sync is navigator → graph (Enter → `focusNode` camera fly); graph →\n * navigator focus sync is deferred past v0.4. Space routes through\n * `instance.selectNodes`, so a controlled selection surfaces intent only\n * (§6.4) — `aria-selected` always reflects the store.\n */\n\nimport { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, KeyboardEvent, ReactElement } from 'react';\nimport type {\n AccessibilityConfig,\n GraphNode,\n NodeId,\n SearchResult,\n SelectionState,\n} from '@modernrelay/orbit-core';\nimport { useResolvedInstance } from '../shared';\nimport type { AnyGraphInstance } from '../../GraphProvider';\n\n/** §15.1 default page size when `accessibility.navigatorWindow` is unset. */\nconst DEFAULT_NAVIGATOR_WINDOW = 50;\n\nexport interface GraphNavigatorProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /**\n * Keyboard model. v0.4 ships `'topology'` (the default; documented above);\n * `'spatial'` (arrows-by-screen-direction) is post-v0.4 and not accepted\n * by this union yet.\n */\n keyboardMode?: 'topology';\n /** Accessible name of the navigator panel. Default 'Graph navigator'. */\n label?: string;\n className?: string;\n style?: CSSProperties;\n}\n\n/** Which paged id list a row belongs to (drives PageUp/PageDown targeting). */\ntype PagedListKey = 'search' | 'neighbors' | 'selection' | 'entry';\n\ninterface NavRow {\n /** Stable identity across renders: `${list}:${nodeId}` (`root:` for the root row). */\n key: string;\n /** Stable DOM id (aria-activedescendant target). */\n domId: string;\n nodeId: NodeId;\n listKey: PagedListKey;\n text: string;\n selected: boolean;\n}\n\ninterface NavSection {\n key: 'search' | 'focused' | 'selection' | 'entry';\n listKey: PagedListKey;\n label: string;\n /** Group accessible name; carries the 'page x of y' state. */\n groupLabel: string;\n rows: NavRow[];\n page: number;\n pageCount: number;\n /** Total pageable items (excludes the focused section's root chrome row). */\n total: number;\n}\n\n/** Transient navigator focus: the neighborhood root (§15.1). */\ninterface NavRoot {\n id: NodeId;\n neighbors: readonly NodeId[];\n /** Model revision the neighborhood was resolved against. */\n model: number;\n}\n\ninterface NavStoreSlice {\n selection: SelectionState;\n pins: ReadonlyMap<NodeId, readonly [number, number]>;\n hidden: ReadonlySet<NodeId>;\n model: number;\n nodeCount: number;\n /** Last completed §16.5 search (S11-T15); null hides the section. */\n search: { query: string; results: readonly SearchResult[] } | null;\n}\n\n/** Narrow store subscription: re-renders only when a navigator-relevant slice\n * changes (never on per-frame viewport writes). */\nfunction useNavStoreSlice(instance: AnyGraphInstance): NavStoreSlice {\n const cacheRef = useRef<NavStoreSlice | null>(null);\n const subscribe = useCallback(\n (cb: () => void) => instance.store.subscribe(cb),\n [instance],\n );\n const getSnapshot = useCallback((): NavStoreSlice => {\n const s = instance.store.getState();\n const prev = cacheRef.current;\n if (\n prev !== null &&\n prev.selection === s.selection &&\n prev.pins === s.pins &&\n prev.hidden === s.hiddenNodeIds &&\n prev.model === s.revisions.model &&\n prev.nodeCount === s.nodeCount &&\n prev.search === s.search\n ) {\n return prev;\n }\n const next: NavStoreSlice = {\n selection: s.selection,\n pins: s.pins,\n hidden: s.hiddenNodeIds,\n model: s.revisions.model,\n nodeCount: s.nodeCount,\n search: s.search,\n };\n cacheRef.current = next;\n return next;\n }, [instance]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\ninterface PageWindow {\n page: number;\n pageCount: number;\n items: readonly NodeId[];\n}\n\n/** Clamp a raw page into `[0, pageCount)` and slice that page's ids. */\nfunction pageWindow(all: readonly NodeId[], rawPage: number, size: number): PageWindow {\n const pageCount = Math.max(1, Math.ceil(all.length / size));\n const page = Math.min(Math.max(rawPage, 0), pageCount - 1);\n return { page, pageCount, items: all.slice(page * size, page * size + size) };\n}\n\n// --- styling defaults (headless-styleable: className/style override) ---\nconst PANEL_STYLE: CSSProperties = {\n pointerEvents: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 4,\n maxWidth: 320,\n fontSize: 12,\n};\nconst LIST_STYLE: CSSProperties = { overflowY: 'auto', maxHeight: 320 };\nconst HEADER_STYLE: CSSProperties = { fontWeight: 600, padding: '4px 6px' };\nconst ITEM_STYLE: CSSProperties = {\n padding: '2px 6px',\n cursor: 'default',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n};\n/** §15.1 visible DOM focus ring on the active item. */\nconst ACTIVE_ITEM_STYLE: CSSProperties = {\n ...ITEM_STYLE,\n outline: '2px solid currentColor',\n outlineOffset: -2,\n};\nconst PAGING_STYLE: CSSProperties = { display: 'flex', alignItems: 'center', gap: 8 };\nconst EMPTY_STYLE: CSSProperties = { padding: '4px 6px', fontStyle: 'italic' };\n\nexport function GraphNavigator(props: GraphNavigatorProps): ReactElement {\n const instance = useResolvedInstance(props.instance, '<GraphNavigator>');\n const slice = useNavStoreSlice(instance);\n\n // 'topology' is the only v0.4 mode; the prop exists so the shape is stable\n // when 'spatial' lands post-v0.4 (no behavioral switch yet).\n void props.keyboardMode;\n\n const baseId = useId();\n const [root, setRoot] = useState<NavRoot | null>(null);\n const [pages, setPages] = useState<Record<PagedListKey, number>>({\n search: 0,\n neighbors: 0,\n selection: 0,\n entry: 0,\n });\n /** Active row identity (roving target); null → first row. */\n const [activeKey, setActiveKey] = useState<string | null>(null);\n\n /** Stable per-node DOM-id tokens (node ids may contain arbitrary text). */\n const tokensRef = useRef<Map<NodeId, number>>(new Map());\n /** Learned neighbor counts, cached per model revision (see module JSDoc). */\n const degreesRef = useRef<{ model: number; counts: Map<NodeId, number> }>({\n model: -1,\n counts: new Map(),\n });\n const itemsRef = useRef<Map<string, HTMLElement>>(new Map());\n /** Set by keyboard handlers so the focus effect moves DOM focus once. */\n const pendingFocusRef = useRef(false);\n\n // A model change invalidates the resolved neighborhood: drop the transient\n // root (back to the entry list) rather than rendering stale rows.\n useEffect(() => {\n if (root !== null && root.model !== slice.model) setRoot(null);\n }, [root, slice.model]);\n\n // §13: release the keyboard ring — but ONLY when no pointer hover owns the\n // shared channel (blur while the pointer rests on a canvas node must not\n // erase that node's hover ring; hover already superseded the sticky\n // keyboard target inside core, so there is nothing else to clear).\n const releaseEmphasis = useCallback((): void => {\n if (instance.store.getState().hover.nodeId === null) instance.emphasizeNode(null);\n }, [instance]);\n\n // A ring left by keyboard navigation must not outlive the navigator.\n useEffect(() => () => releaseEmphasis(), [releaseEmphasis]);\n\n // Idempotent render-time cache reset (ref only — safe under StrictMode).\n if (degreesRef.current.model !== slice.model) {\n degreesRef.current = { model: slice.model, counts: new Map() };\n }\n\n const accessibility = instance.getAccessibility() as\n | AccessibilityConfig<Record<string, unknown>>\n | undefined;\n const rawWindow = accessibility?.navigatorWindow;\n const windowSize =\n typeof rawWindow === 'number' && Number.isFinite(rawWindow) && rawWindow >= 1\n ? Math.floor(rawWindow)\n : DEFAULT_NAVIGATOR_WINDOW;\n\n const getAccessibleLabel = accessibility?.getAccessibleLabel;\n const nameOf = (id: NodeId): string => {\n const node = instance.getNode(id) as GraphNode<Record<string, unknown>> | undefined;\n if (node === undefined) return id;\n if (getAccessibleLabel !== undefined) {\n try {\n return getAccessibleLabel(node);\n } catch {\n // fall through to the built-in resolution\n }\n }\n const label = node.attrs?.['label'];\n return typeof label === 'string' ? label : node.id;\n };\n\n const selectedSet = new Set<NodeId>(slice.selection.nodeIds);\n\n /** '{name} · {n} neighbors · pinned · hidden' — always a single text node. */\n const rowText = (id: NodeId): string => {\n const parts = [nameOf(id)];\n const count = degreesRef.current.counts.get(id);\n if (count !== undefined) parts.push(count === 1 ? '1 neighbor' : `${count} neighbors`);\n if (slice.pins.has(id)) parts.push('pinned');\n if (slice.hidden.has(id)) parts.push('hidden');\n return parts.join(' · ');\n };\n\n const domIdFor = (listKey: string, id: NodeId): string => {\n const tokens = tokensRef.current;\n let token = tokens.get(id);\n if (token === undefined) {\n token = tokens.size;\n tokens.set(id, token);\n }\n return `${baseId}nav-${listKey}-${token}`;\n };\n\n const makeRow = (listKey: PagedListKey, id: NodeId): NavRow => ({\n key: `${listKey}:${id}`,\n domId: domIdFor(listKey, id),\n nodeId: id,\n listKey,\n text: rowText(id),\n selected: selectedSet.has(id),\n });\n\n // --- section/row model (all O(navigatorWindow) work) ---\n const searchResults: readonly SearchResult[] = slice.search === null ? [] : slice.search.results;\n const listItems: Record<PagedListKey, readonly NodeId[]> = {\n search: searchResults.map((r) => r.id),\n neighbors: root !== null ? root.neighbors : [],\n selection: slice.selection.nodeIds,\n // §15.1: the entry list is the SCENE roster (scope applied, mask not) —\n // hidden/filtered nodes stay listed with their state exposed in text.\n entry: root === null ? instance.getSceneNodeIds() : [],\n };\n\n const sections: NavSection[] = [];\n if (slice.search !== null) {\n const view = pageWindow(listItems.search, pages.search, windowSize);\n const count = searchResults.length;\n const start = view.page * windowSize;\n sections.push({\n key: 'search',\n listKey: 'search',\n label: 'Search results',\n groupLabel: `Search results for ${slice.search.query}: ${count} ${\n count === 1 ? 'result' : 'results'\n }, page ${view.page + 1} of ${view.pageCount}`,\n // §16.5: row text prefers the service-supplied label (still a TEXT\n // NODE); results may name ids outside the model — nameOf falls back to\n // the id for those.\n rows: view.items.map((id, i) => {\n const row = makeRow('search', id);\n const label = searchResults[start + i]?.label;\n return label === undefined ? row : { ...row, text: label };\n }),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root !== null) {\n const view = pageWindow(listItems.neighbors, pages.neighbors, windowSize);\n const rootRow: NavRow = {\n key: `root:${root.id}`,\n domId: domIdFor('root', root.id),\n nodeId: root.id,\n listKey: 'neighbors',\n text: rowText(root.id),\n selected: selectedSet.has(root.id),\n };\n const count = root.neighbors.length;\n sections.push({\n key: 'focused',\n listKey: 'neighbors',\n label: 'Focused node',\n groupLabel: `Focused node ${nameOf(root.id)}: ${count} ${\n count === 1 ? 'neighbor' : 'neighbors'\n }, page ${view.page + 1} of ${view.pageCount}`,\n rows: [rootRow, ...view.items.map((id) => makeRow('neighbors', id))],\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (slice.selection.nodeIds.length > 0) {\n const view = pageWindow(listItems.selection, pages.selection, windowSize);\n const count = slice.selection.nodeIds.length;\n sections.push({\n key: 'selection',\n listKey: 'selection',\n label: 'Selection',\n groupLabel: `Selection: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('selection', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n if (root === null) {\n const view = pageWindow(listItems.entry, pages.entry, windowSize);\n const count = listItems.entry.length;\n sections.push({\n key: 'entry',\n listKey: 'entry',\n label: 'All nodes',\n groupLabel: `All nodes: ${count} ${count === 1 ? 'node' : 'nodes'}, page ${\n view.page + 1\n } of ${view.pageCount}`,\n rows: view.items.map((id) => makeRow('entry', id)),\n page: view.page,\n pageCount: view.pageCount,\n total: count,\n });\n }\n\n const rows: NavRow[] = sections.flatMap((s) => s.rows);\n let activeIndex = activeKey === null ? -1 : rows.findIndex((r) => r.key === activeKey);\n if (activeIndex === -1 && rows.length > 0) activeIndex = 0;\n const activeRow = activeIndex >= 0 ? rows[activeIndex] : undefined;\n const activeSection =\n activeRow !== undefined ? sections.find((s) => s.listKey === activeRow.listKey) : undefined;\n\n // Move real DOM focus to the active item after keyboard-driven changes so\n // the focus ring is a genuine DOM focus, never only a WebGL highlight.\n useEffect(() => {\n if (!pendingFocusRef.current) return;\n pendingFocusRef.current = false;\n if (activeRow === undefined) return;\n itemsRef.current.get(activeRow.domId)?.focus();\n });\n\n // --- keyboard model ('topology'; see module JSDoc) ---\n const focusItemSoon = (): void => {\n pendingFocusRef.current = true;\n };\n\n const moveTo = (index: number): void => {\n if (rows.length === 0) return;\n const row = rows[Math.min(Math.max(index, 0), rows.length - 1)];\n if (row === undefined) return;\n setActiveKey(row.key);\n // §13/§15.1 keyboard parity with pointer hover: ring the active row\n // WITHOUT flying the camera (that stays on Enter via focusNode).\n instance.emphasizeNode(row.nodeId);\n focusItemSoon();\n };\n\n const pageBy = (delta: number): void => {\n if (activeRow === undefined) return;\n const listKey = activeRow.listKey;\n const items = listItems[listKey];\n const pageCount = Math.max(1, Math.ceil(items.length / windowSize));\n const current = Math.min(Math.max(pages[listKey], 0), pageCount - 1);\n const next = Math.min(Math.max(current + delta, 0), pageCount - 1);\n if (next === current) return;\n setPages((p) => ({ ...p, [listKey]: next }));\n const first = items[next * windowSize];\n if (first !== undefined) {\n setActiveKey(`${listKey}:${first}`);\n instance.emphasizeNode(first);\n }\n focusItemSoon();\n };\n\n /** Enter: focus the node in the graph (camera fly) and re-root the\n * neighborhood. The returned ids come from the core adjacency resolution\n * (undirected; see module JSDoc). Search rows route through the §16.5\n * result contract instead: `activateSearchResult` focuses an in-scene,\n * mask-visible id and otherwise only CLASSIFIES — the navigator never\n * mutates scope/filters (the host reacts through its own channels). */\n const activate = (row: NavRow): void => {\n if (row.listKey === 'search') {\n const result = searchResults.find((r) => r.id === row.nodeId);\n if (result !== undefined) instance.activateSearchResult(result);\n return;\n }\n const neighbors = instance.focusNode(row.nodeId);\n degreesRef.current.counts.set(row.nodeId, neighbors.length);\n setRoot({ id: row.nodeId, neighbors, model: slice.model });\n setPages((p) => ({ ...p, neighbors: 0 }));\n setActiveKey(`root:${row.nodeId}`);\n focusItemSoon();\n };\n\n /** Space: toggle membership through the §6.4 selection ownership path.\n * Search rows may name ids outside the accepted model — those no-op. */\n const toggleSelection = (row: NavRow): void => {\n if (row.listKey === 'search' && instance.getNode(row.nodeId) === undefined) return;\n const current = slice.selection.nodeIds;\n const next = current.includes(row.nodeId)\n ? current.filter((id) => id !== row.nodeId)\n : [...current, row.nodeId];\n instance.selectNodes(next);\n };\n\n const clearTransientFocus = (): void => {\n setRoot(null);\n setActiveKey(null);\n releaseEmphasis();\n focusItemSoon();\n };\n\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (activeRow === undefined) return;\n switch (e.key) {\n case 'ArrowDown':\n moveTo(activeIndex + 1);\n break;\n case 'ArrowUp':\n moveTo(activeIndex - 1);\n break;\n case 'Home':\n moveTo(0);\n break;\n case 'End':\n moveTo(rows.length - 1);\n break;\n case 'PageDown':\n pageBy(1);\n break;\n case 'PageUp':\n pageBy(-1);\n break;\n case 'Enter':\n activate(activeRow);\n break;\n case ' ':\n toggleSelection(activeRow);\n break;\n case 'Escape':\n if (root === null) return; // nothing transient — let it bubble\n clearTransientFocus();\n break;\n default:\n return;\n }\n e.preventDefault();\n };\n\n const panelLabel = props.label ?? 'Graph navigator';\n\n return (\n <div\n data-orbit-navigator=\"\"\n role=\"group\"\n aria-label={panelLabel}\n className={props.className}\n style={{ ...PANEL_STYLE, ...props.style }}\n onBlur={(e) => {\n // Focus left the panel entirely (paging buttons stay inside): the\n // keyboard ring clears, mirroring pointer hover leaving the canvas.\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) {\n releaseEmphasis();\n }\n }}\n >\n <div\n role=\"listbox\"\n aria-label={`${panelLabel} items`}\n aria-activedescendant={activeRow?.domId}\n aria-keyshortcuts=\"ArrowUp ArrowDown Home End PageUp PageDown Enter Space Escape\"\n onKeyDown={onKeyDown}\n style={LIST_STYLE}\n >\n {rows.length === 0 ? (\n <div role=\"presentation\" style={EMPTY_STYLE}>\n No nodes\n </div>\n ) : (\n sections.map((section) => (\n <div key={section.key} role=\"group\" aria-label={section.groupLabel}>\n <div role=\"presentation\" style={HEADER_STYLE}>\n {section.label}\n </div>\n {section.rows.map((row) => {\n const isActive = row.key === activeRow?.key;\n return (\n <div\n key={row.key}\n id={row.domId}\n role=\"option\"\n aria-selected={row.selected}\n tabIndex={isActive ? 0 : -1}\n ref={(el) => {\n if (el === null) itemsRef.current.delete(row.domId);\n else itemsRef.current.set(row.domId, el);\n }}\n onClick={() => {\n setActiveKey(row.key);\n instance.emphasizeNode(row.nodeId); // active row = ringed row\n focusItemSoon();\n }}\n style={isActive ? ACTIVE_ITEM_STYLE : ITEM_STYLE}\n >\n {row.text}\n </div>\n );\n })}\n </div>\n ))\n )}\n </div>\n {activeSection !== undefined ? (\n <div data-orbit-navigator-paging=\"\" style={PAGING_STYLE}>\n <button\n type=\"button\"\n onClick={() => pageBy(-1)}\n disabled={activeSection.page === 0}\n aria-keyshortcuts=\"PageUp\"\n >\n Previous page\n </button>\n <span role=\"status\" data-orbit-navigator-page-status=\"\">\n {`${activeSection.label}: page ${activeSection.page + 1} of ${\n activeSection.pageCount\n } (${activeSection.total} ${activeSection.total === 1 ? 'item' : 'items'})`}\n </span>\n <button\n type=\"button\"\n onClick={() => pageBy(1)}\n disabled={activeSection.page >= activeSection.pageCount - 1}\n aria-keyshortcuts=\"PageDown\"\n >\n Next page\n </button>\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
@@ -7,9 +7,9 @@ import { G as GraphCorner } from '../shared-Bj3Gk219.js';
|
|
|
7
7
|
* `@modernrelay/orbit-react/components/SimControls` — `<GraphSimControls>`,
|
|
8
8
|
* the §16.11 simulation control panel (S12-T12).
|
|
9
9
|
*
|
|
10
|
-
* Live sliders over
|
|
11
|
-
* friction, linkDistance, linkSpring, collision, decay,
|
|
12
|
-
*
|
|
10
|
+
* Live sliders over the effective `SimulationConfig` fields (gravity,
|
|
11
|
+
* repulsion, friction, linkDistance, linkSpring, collision, decay, center —
|
|
12
|
+
* repulsionTheta is omitted: inert on cosmos >= 3.4), a speed
|
|
13
13
|
* preset toggle, and a reheat button. Every control write is ONE atomic
|
|
14
14
|
* host update — `applyHostUpdate({ simulation: merged })`, the same lane the
|
|
15
15
|
* `<Graph>` `simulation` prop uses — which sinks as a single config-only
|
|
@@ -13,7 +13,9 @@ var SIM_FIELDS = [
|
|
|
13
13
|
// what turns the force on.
|
|
14
14
|
{ key: "collision", label: "Collision", min: 0, max: 2, step: 0.01, fallback: 0 },
|
|
15
15
|
{ key: "decay", label: "Cool-down", min: 100, max: 2e4, step: 100, fallback: 5e3 },
|
|
16
|
-
|
|
16
|
+
// repulsionTheta is deliberately absent: cosmos >= 3.4 ignores it
|
|
17
|
+
// (grid-based repulsion) — a slider that visibly does nothing is worse
|
|
18
|
+
// than no slider. The SimulationConfig field survives for other engines.
|
|
17
19
|
{ key: "center", label: "Centering", min: 0, max: 1, step: 0.01, fallback: 0 }
|
|
18
20
|
];
|
|
19
21
|
var SPEED_NORMAL_DECAY = 5e3;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/SimControls/index.tsx"],"names":[],"mappings":"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","file":"SimControls.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/SimControls` — `<GraphSimControls>`,\n * the §16.11 simulation control panel (S12-T12).\n *\n * Live sliders over every `SimulationConfig` field (gravity, repulsion,\n * friction, linkDistance, linkSpring, collision, decay, repulsionTheta,\n * center — the full §10 tunable set), a speed\n * preset toggle, and a reheat button. Every control write is ONE atomic\n * host update — `applyHostUpdate({ simulation: merged })`, the same lane the\n * `<Graph>` `simulation` prop uses — which sinks as a single config-only\n * `engine.commit`: no structure, no buffers, no restart, positions untouched\n * (R-16.11-02, E1).\n *\n * ## Current-value resolution (v0.10, investigated)\n * The store publishes NO simulation/layout lane and the instance exposes no\n * config getter — `layout`/`simulation` are instance-local in\n * `createGraphInstance`; `GraphStoreState` carries only `simulationRunning`.\n * The panel therefore mirrors the caller's `simulation` prop (the same\n * object handed to `<Graph>`) and merges control writes over that mirror; a\n * prop identity change re-seeds the mirror (the external writer wins). Like\n * `subgraph` (§9.2), the lane is last-writer-wins: the panel and the\n * `<Graph>` prop write the same state. `onSimulationChange` fires with the\n * full merged config after every write so controlled callers can loop it\n * back through their `simulation` prop (the spec's `onLayoutChange` seam\n * traffics in the LayoutSpec object form and is post-v0.10).\n *\n * ## Applicability (R-16.11-04)\n * Rendered ONLY under layout `'force'` — the `layout` prop mirrors the\n * caller's `<Graph>` prop for the same no-observable-lane reason, defaulting\n * to `'force'` (the instance construction default). Under `'fixed'` the\n * panel renders nothing. The spec's third case — `static` while its\n * convergence run is active — is N/A in v0.10: `LayoutKind` is\n * `'force' | 'fixed'` (core types.ts) and the store exposes no\n * convergence-run state; wire it when the `static` variant lands.\n *\n * ## Reheat\n * The spec names `reheat(alpha)`; the shipped instance surface is\n * `resumeSimulation()` (§16.1 simulation controls — sinks to\n * `engine.start()` and publishes `simulationRunning`). The button calls\n * that; the alpha parameter follows when core grows the reheat API.\n *\n * ## Speed toggle\n * A preset over the `decay` lane — the §10 cool-down tunable (cosmos\n * `simulationDecay`), which core exposes as of v0.10.2. ON writes a smaller\n * coefficient so the run cools SLOWER and stays lively; OFF restores the\n * pre-toggle value, or the engine default when the field was unset. The\n * toggle and the Cool-down slider are two views over one lane, so moving\n * either is reflected by the other. Same config-only commit discipline as\n * every slider. (Before v0.10.2 this proxied through `friction` because\n * `decay` had no core lane — that workaround is now retired.)\n *\n * ## Keyboard & ARIA (§15.1)\n * Native `<input type=\"range\">`/checkbox controls with `<label htmlFor>`\n * wiring. Arrow/Home/End/Page keys are handled in `onKeyDown` with\n * `preventDefault()` so stepping is deterministic both in browsers (never\n * double-steps against the native range keyboard model) and under jsdom\n * (which has no native range keyboard model at all).\n */\n\nimport { Fragment, useCallback, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport type { LayoutKind, SimulationConfig } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { cornerStyle, mergeStyle, useResolvedInstance } from '../shared';\nimport type { GraphCorner } from '../shared';\n\nexport type SimFieldKey = keyof SimulationConfig;\n\nexport interface SimFieldDescriptor {\n key: SimFieldKey;\n /** Visible label text (rendered as a text node). */\n label: string;\n min: number;\n max: number;\n step: number;\n /** Displayed when the field is unset — the engine default the simulation\n * is actually running with (cosmos 3.3.0 `defaultConfigValues`). Never\n * WRITTEN unless the user moves the control. */\n fallback: number;\n}\n\n/** §16.11 slider roster = the full v0.10 `SimulationConfig` field list. */\nexport const SIM_FIELDS: readonly SimFieldDescriptor[] = [\n { key: 'gravity', label: 'Gravity', min: 0, max: 1, step: 0.01, fallback: 0.25 },\n { key: 'repulsion', label: 'Repulsion', min: 0, max: 2, step: 0.01, fallback: 1 },\n { key: 'friction', label: 'Friction', min: 0, max: 1, step: 0.01, fallback: 0.85 },\n { key: 'linkDistance', label: 'Link distance', min: 1, max: 100, step: 1, fallback: 10 },\n { key: 'linkSpring', label: 'Link spring', min: 0, max: 2, step: 0.01, fallback: 1 },\n // v0.10.2: the rest of the §10 tunable set. `collision` and `center` ship\n // OFF at the engine default, so their sliders start at 0 — moving one is\n // what turns the force on.\n { key: 'collision', label: 'Collision', min: 0, max: 2, step: 0.01, fallback: 0 },\n { key: 'decay', label: 'Cool-down', min: 100, max: 20000, step: 100, fallback: 5000 },\n { key: 'repulsionTheta', label: 'Repulsion θ', min: 0.3, max: 2, step: 0.05, fallback: 1.15 },\n { key: 'center', label: 'Centering', min: 0, max: 1, step: 0.01, fallback: 0 },\n];\n\n/** Speed-preset friction values (module JSDoc: the toggle's documented\n * simulation field is `friction` until core ships `decay`). */\nexport const SPEED_NORMAL_DECAY = 5000;\n/** A smaller decay coefficient cools SLOWER, so the run keeps moving —\n * \"fast\" here means \"stays lively\", matching the §10 semantics. */\nexport const SPEED_FAST_DECAY = 1000;\n\nexport interface GraphSimControlsProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Active layout kind — mirror of the caller's <Graph> `layout` prop\n * (no observable lane exists; module JSDoc). Default 'force'. */\n layout?: LayoutKind;\n /** Current simulation config seed — the same object passed to <Graph>.\n * An identity change re-seeds the panel's mirror. */\n simulation?: SimulationConfig;\n /** Fires with the FULL merged SimulationConfig after every write. */\n onSimulationChange?: (next: SimulationConfig) => void;\n /** Render-prop REPLACEMENT for a field row; the default renders\n * label + native range input + value readout. `set` clamps/quantizes to\n * the descriptor's bounds and step before writing. */\n renderField?: (ctx: {\n field: SimFieldDescriptor;\n value: number;\n /** Stable id for `htmlFor` wiring in custom rows. */\n inputId: string;\n set: (value: number) => void;\n }) => ReactNode;\n /** Accessible name of the panel. Default 'Simulation controls'. */\n label?: string;\n /** Corner this panel anchors to. Default 'bottom-left'. */\n position?: GraphCorner;\n /** Distance from the two anchored edges in CSS px. Default 12. */\n offset?: number;\n /** Class hook for the 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\n * wins over the one `position` implies (resolved per axis). */\n style?: CSSProperties;\n}\n\n/** Decimal places of `step` — the quantization grid for keyboard stepping. */\nfunction stepDecimals(step: number): number {\n const s = String(step);\n const dot = s.indexOf('.');\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\n/** Clamp to [min, max] and quantize to the step's decimal grid so repeated\n * keyboard steps never accumulate float noise (0.25 + 0.01 stays 0.26). */\nfunction quantize(field: SimFieldDescriptor, raw: number): number {\n const clamped = Math.min(field.max, Math.max(field.min, raw));\n return Number(clamped.toFixed(stepDecimals(field.step)));\n}\n\n/** §15.1 keyboard model for a slider; null = key not handled (let it pass). */\nfunction keyboardTarget(field: SimFieldDescriptor, value: number, key: string): number | null {\n switch (key) {\n case 'ArrowRight':\n case 'ArrowUp':\n return quantize(field, value + field.step);\n case 'ArrowLeft':\n case 'ArrowDown':\n return quantize(field, value - field.step);\n case 'PageUp':\n return quantize(field, value + field.step * 10);\n case 'PageDown':\n return quantize(field, value - field.step * 10);\n case 'Home':\n return field.min;\n case 'End':\n return field.max;\n default:\n return null;\n }\n}\n\n// --- styling defaults (headless-styleable: className/style override) ---\nconst PANEL_STYLE: CSSProperties = {\n width: 250,\n pointerEvents: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n padding: 10,\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.4 system-ui, sans-serif',\n};\nconst TITLE_STYLE: CSSProperties = { fontWeight: 600, fontSize: 13 };\nconst ROW_STYLE: CSSProperties = { display: 'flex', alignItems: 'center', gap: 6 };\nconst LABEL_STYLE: CSSProperties = { flex: 'none', width: 86, opacity: 0.8 };\nconst SLIDER_STYLE: CSSProperties = { flex: 1, minWidth: 0 };\nconst VALUE_STYLE: CSSProperties = {\n flex: 'none',\n width: 38,\n textAlign: 'right',\n fontVariantNumeric: 'tabular-nums',\n};\nconst BUTTON_STYLE: CSSProperties = {\n appearance: 'none',\n background: 'transparent',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n borderRadius: 6,\n color: 'inherit',\n cursor: 'pointer',\n font: 'inherit',\n padding: '4px 10px',\n};\nconst STATUS_STYLE: CSSProperties = { opacity: 0.65 };\n\nexport function GraphSimControls(props: GraphSimControlsProps): ReactElement | null {\n const instance = useResolvedInstance(props.instance, '<GraphSimControls>');\n const baseId = useId();\n\n // Mirror of the last-known SimulationConfig (module JSDoc: no store lane\n // exists to subscribe to). Prop identity change re-seeds — external\n // writer wins (the React-documented derive-during-render pattern).\n const [mirror, setMirror] = useState<SimulationConfig>(() => props.simulation ?? {});\n const [seededFrom, setSeededFrom] = useState(props.simulation);\n if (seededFrom !== props.simulation) {\n setSeededFrom(props.simulation);\n setMirror(props.simulation ?? {});\n }\n\n /** Decay the speed preset restores on release; undefined = the field was\n * unset pre-toggle (restore the engine-default constant). */\n const preSpeedDecayRef = useRef<number | undefined>(undefined);\n\n // The one simulation lane the store DOES publish (§16.1).\n const subscribe = useCallback(\n (onStoreChange: () => void) => instance.store.subscribe(onStoreChange),\n [instance],\n );\n const getRunning = useCallback(\n () => instance.store.getState().simulationRunning,\n [instance],\n );\n const simulationRunning = useSyncExternalStore(subscribe, getRunning, getRunning);\n\n // R-16.11-04 applicability gate — after hooks, per the rules of hooks.\n if ((props.layout ?? 'force') !== 'force') return null;\n\n /** ONE atomic config-only host update per control write (R-16.11-02). */\n const write = (next: SimulationConfig): void => {\n setMirror(next);\n instance.applyHostUpdate({ simulation: next });\n props.onSimulationChange?.(next);\n };\n\n const setField = (key: SimFieldKey, value: number): void => {\n const next: SimulationConfig = { ...mirror };\n next[key] = value;\n write(next);\n };\n\n // The preset holds exactly while decay sits at the fast value — the toggle\n // and the cool-down slider are two views over the same lane.\n const speedOn = mirror.decay === SPEED_FAST_DECAY;\n const toggleSpeed = (): void => {\n if (speedOn) {\n const restored = preSpeedDecayRef.current ?? SPEED_NORMAL_DECAY;\n preSpeedDecayRef.current = undefined;\n setField('decay', restored);\n } else {\n preSpeedDecayRef.current = mirror.decay;\n setField('decay', SPEED_FAST_DECAY);\n }\n };\n\n const panelLabel = props.label ?? 'Simulation controls';\n const speedId = `${baseId}-speed`;\n\n return (\n <div\n data-orbit-simcontrols=\"\"\n role=\"group\"\n aria-label={panelLabel}\n className={props.className}\n style={\n props.className !== undefined\n ? props.style\n : mergeStyle(\n { ...cornerStyle(props.position ?? 'bottom-left', props.offset), ...PANEL_STYLE },\n props.style,\n )\n }\n >\n <div data-orbit-simcontrols-title=\"\" style={TITLE_STYLE}>\n {panelLabel}\n </div>\n\n {SIM_FIELDS.map((field) => {\n const value = mirror[field.key] ?? field.fallback;\n const inputId = `${baseId}-${field.key}`;\n const set = (v: number): void => {\n setField(field.key, quantize(field, v));\n };\n if (props.renderField !== undefined) {\n return (\n <Fragment key={field.key}>\n {props.renderField({ field, value, inputId, set })}\n </Fragment>\n );\n }\n return (\n <div key={field.key} data-orbit-simcontrols-field={field.key} style={ROW_STYLE}>\n <label htmlFor={inputId} style={LABEL_STYLE}>\n {field.label}\n </label>\n <input\n id={inputId}\n type=\"range\"\n data-orbit-simcontrols-input={field.key}\n min={field.min}\n max={field.max}\n step={field.step}\n value={value}\n onChange={(e) => {\n const v = Number(e.target.value);\n if (Number.isFinite(v)) set(v);\n }}\n onKeyDown={(e) => {\n const target = keyboardTarget(field, value, e.key);\n if (target === null) return;\n // Suppress the browser's native range step so the write goes\n // through this handler exactly once (module JSDoc).\n e.preventDefault();\n if (target !== value) setField(field.key, target);\n }}\n style={SLIDER_STYLE}\n />\n <output\n htmlFor={inputId}\n data-orbit-simcontrols-value={field.key}\n style={VALUE_STYLE}\n >\n {String(value)}\n </output>\n </div>\n );\n })}\n\n <div data-orbit-simcontrols-speed-row=\"\" style={ROW_STYLE}>\n <input\n id={speedId}\n type=\"checkbox\"\n data-orbit-simcontrols-speed=\"\"\n checked={speedOn}\n onChange={toggleSpeed}\n />\n <label htmlFor={speedId}>Speed boost</label>\n </div>\n\n <div style={ROW_STYLE}>\n <button\n type=\"button\"\n data-orbit-simcontrols-reheat=\"\"\n style={BUTTON_STYLE}\n onClick={() => {\n instance.resumeSimulation();\n }}\n >\n Reheat\n </button>\n <span data-orbit-simcontrols-status=\"\" style={STATUS_STYLE}>\n {simulationRunning ? 'simulation running' : 'settled'}\n </span>\n </div>\n </div>\n );\n}\n\n/** S0-T02 tree-shake probe — must never reach a root-only consumer bundle. */\nexport const __ORBIT_SIMCONTROLS_SENTINEL__ = 'orbit-react/components/SimControls';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/SimControls/index.tsx"],"names":[],"mappings":"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","file":"SimControls.js","sourcesContent":["/**\n * `@modernrelay/orbit-react/components/SimControls` — `<GraphSimControls>`,\n * the §16.11 simulation control panel (S12-T12).\n *\n * Live sliders over the effective `SimulationConfig` fields (gravity,\n * repulsion, friction, linkDistance, linkSpring, collision, decay, center —\n * repulsionTheta is omitted: inert on cosmos >= 3.4), a speed\n * preset toggle, and a reheat button. Every control write is ONE atomic\n * host update — `applyHostUpdate({ simulation: merged })`, the same lane the\n * `<Graph>` `simulation` prop uses — which sinks as a single config-only\n * `engine.commit`: no structure, no buffers, no restart, positions untouched\n * (R-16.11-02, E1).\n *\n * ## Current-value resolution (v0.10, investigated)\n * The store publishes NO simulation/layout lane and the instance exposes no\n * config getter — `layout`/`simulation` are instance-local in\n * `createGraphInstance`; `GraphStoreState` carries only `simulationRunning`.\n * The panel therefore mirrors the caller's `simulation` prop (the same\n * object handed to `<Graph>`) and merges control writes over that mirror; a\n * prop identity change re-seeds the mirror (the external writer wins). Like\n * `subgraph` (§9.2), the lane is last-writer-wins: the panel and the\n * `<Graph>` prop write the same state. `onSimulationChange` fires with the\n * full merged config after every write so controlled callers can loop it\n * back through their `simulation` prop (the spec's `onLayoutChange` seam\n * traffics in the LayoutSpec object form and is post-v0.10).\n *\n * ## Applicability (R-16.11-04)\n * Rendered ONLY under layout `'force'` — the `layout` prop mirrors the\n * caller's `<Graph>` prop for the same no-observable-lane reason, defaulting\n * to `'force'` (the instance construction default). Under `'fixed'` the\n * panel renders nothing. The spec's third case — `static` while its\n * convergence run is active — is N/A in v0.10: `LayoutKind` is\n * `'force' | 'fixed'` (core types.ts) and the store exposes no\n * convergence-run state; wire it when the `static` variant lands.\n *\n * ## Reheat\n * The spec names `reheat(alpha)`; the shipped instance surface is\n * `resumeSimulation()` (§16.1 simulation controls — sinks to\n * `engine.start()` and publishes `simulationRunning`). The button calls\n * that; the alpha parameter follows when core grows the reheat API.\n *\n * ## Speed toggle\n * A preset over the `decay` lane — the §10 cool-down tunable (cosmos\n * `simulationDecay`), which core exposes as of v0.10.2. ON writes a smaller\n * coefficient so the run cools SLOWER and stays lively; OFF restores the\n * pre-toggle value, or the engine default when the field was unset. The\n * toggle and the Cool-down slider are two views over one lane, so moving\n * either is reflected by the other. Same config-only commit discipline as\n * every slider. (Before v0.10.2 this proxied through `friction` because\n * `decay` had no core lane — that workaround is now retired.)\n *\n * ## Keyboard & ARIA (§15.1)\n * Native `<input type=\"range\">`/checkbox controls with `<label htmlFor>`\n * wiring. Arrow/Home/End/Page keys are handled in `onKeyDown` with\n * `preventDefault()` so stepping is deterministic both in browsers (never\n * double-steps against the native range keyboard model) and under jsdom\n * (which has no native range keyboard model at all).\n */\n\nimport { Fragment, useCallback, useId, useRef, useState, useSyncExternalStore } from 'react';\nimport type { CSSProperties, ReactElement, ReactNode } from 'react';\nimport type { LayoutKind, SimulationConfig } from '@modernrelay/orbit-core';\nimport type { AnyGraphInstance } from '../../GraphProvider';\nimport { cornerStyle, mergeStyle, useResolvedInstance } from '../shared';\nimport type { GraphCorner } from '../shared';\n\nexport type SimFieldKey = keyof SimulationConfig;\n\nexport interface SimFieldDescriptor {\n key: SimFieldKey;\n /** Visible label text (rendered as a text node). */\n label: string;\n min: number;\n max: number;\n step: number;\n /** Displayed when the field is unset — the engine default the simulation\n * is actually running with (cosmos 3.3.0 `defaultConfigValues`). Never\n * WRITTEN unless the user moves the control. */\n fallback: number;\n}\n\n/** §16.11 slider roster = the full v0.10 `SimulationConfig` field list. */\nexport const SIM_FIELDS: readonly SimFieldDescriptor[] = [\n { key: 'gravity', label: 'Gravity', min: 0, max: 1, step: 0.01, fallback: 0.25 },\n { key: 'repulsion', label: 'Repulsion', min: 0, max: 2, step: 0.01, fallback: 1 },\n { key: 'friction', label: 'Friction', min: 0, max: 1, step: 0.01, fallback: 0.85 },\n { key: 'linkDistance', label: 'Link distance', min: 1, max: 100, step: 1, fallback: 10 },\n { key: 'linkSpring', label: 'Link spring', min: 0, max: 2, step: 0.01, fallback: 1 },\n // v0.10.2: the rest of the §10 tunable set. `collision` and `center` ship\n // OFF at the engine default, so their sliders start at 0 — moving one is\n // what turns the force on.\n { key: 'collision', label: 'Collision', min: 0, max: 2, step: 0.01, fallback: 0 },\n { key: 'decay', label: 'Cool-down', min: 100, max: 20000, step: 100, fallback: 5000 },\n // repulsionTheta is deliberately absent: cosmos >= 3.4 ignores it\n // (grid-based repulsion) — a slider that visibly does nothing is worse\n // than no slider. The SimulationConfig field survives for other engines.\n { key: 'center', label: 'Centering', min: 0, max: 1, step: 0.01, fallback: 0 },\n];\n\n/** Speed-preset friction values (module JSDoc: the toggle's documented\n * simulation field is `friction` until core ships `decay`). */\nexport const SPEED_NORMAL_DECAY = 5000;\n/** A smaller decay coefficient cools SLOWER, so the run keeps moving —\n * \"fast\" here means \"stays lively\", matching the §10 semantics. */\nexport const SPEED_FAST_DECAY = 1000;\n\nexport interface GraphSimControlsProps {\n /** Explicit instance (multi-instance pages); ambient context otherwise. */\n instance?: AnyGraphInstance;\n /** Active layout kind — mirror of the caller's <Graph> `layout` prop\n * (no observable lane exists; module JSDoc). Default 'force'. */\n layout?: LayoutKind;\n /** Current simulation config seed — the same object passed to <Graph>.\n * An identity change re-seeds the panel's mirror. */\n simulation?: SimulationConfig;\n /** Fires with the FULL merged SimulationConfig after every write. */\n onSimulationChange?: (next: SimulationConfig) => void;\n /** Render-prop REPLACEMENT for a field row; the default renders\n * label + native range input + value readout. `set` clamps/quantizes to\n * the descriptor's bounds and step before writing. */\n renderField?: (ctx: {\n field: SimFieldDescriptor;\n value: number;\n /** Stable id for `htmlFor` wiring in custom rows. */\n inputId: string;\n set: (value: number) => void;\n }) => ReactNode;\n /** Accessible name of the panel. Default 'Simulation controls'. */\n label?: string;\n /** Corner this panel anchors to. Default 'bottom-left'. */\n position?: GraphCorner;\n /** Distance from the two anchored edges in CSS px. Default 12. */\n offset?: number;\n /** Class hook for the 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\n * wins over the one `position` implies (resolved per axis). */\n style?: CSSProperties;\n}\n\n/** Decimal places of `step` — the quantization grid for keyboard stepping. */\nfunction stepDecimals(step: number): number {\n const s = String(step);\n const dot = s.indexOf('.');\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\n/** Clamp to [min, max] and quantize to the step's decimal grid so repeated\n * keyboard steps never accumulate float noise (0.25 + 0.01 stays 0.26). */\nfunction quantize(field: SimFieldDescriptor, raw: number): number {\n const clamped = Math.min(field.max, Math.max(field.min, raw));\n return Number(clamped.toFixed(stepDecimals(field.step)));\n}\n\n/** §15.1 keyboard model for a slider; null = key not handled (let it pass). */\nfunction keyboardTarget(field: SimFieldDescriptor, value: number, key: string): number | null {\n switch (key) {\n case 'ArrowRight':\n case 'ArrowUp':\n return quantize(field, value + field.step);\n case 'ArrowLeft':\n case 'ArrowDown':\n return quantize(field, value - field.step);\n case 'PageUp':\n return quantize(field, value + field.step * 10);\n case 'PageDown':\n return quantize(field, value - field.step * 10);\n case 'Home':\n return field.min;\n case 'End':\n return field.max;\n default:\n return null;\n }\n}\n\n// --- styling defaults (headless-styleable: className/style override) ---\nconst PANEL_STYLE: CSSProperties = {\n width: 250,\n pointerEvents: 'auto',\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n padding: 10,\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.4 system-ui, sans-serif',\n};\nconst TITLE_STYLE: CSSProperties = { fontWeight: 600, fontSize: 13 };\nconst ROW_STYLE: CSSProperties = { display: 'flex', alignItems: 'center', gap: 6 };\nconst LABEL_STYLE: CSSProperties = { flex: 'none', width: 86, opacity: 0.8 };\nconst SLIDER_STYLE: CSSProperties = { flex: 1, minWidth: 0 };\nconst VALUE_STYLE: CSSProperties = {\n flex: 'none',\n width: 38,\n textAlign: 'right',\n fontVariantNumeric: 'tabular-nums',\n};\nconst BUTTON_STYLE: CSSProperties = {\n appearance: 'none',\n background: 'transparent',\n border: '1px solid rgba(255, 255, 255, 0.2)',\n borderRadius: 6,\n color: 'inherit',\n cursor: 'pointer',\n font: 'inherit',\n padding: '4px 10px',\n};\nconst STATUS_STYLE: CSSProperties = { opacity: 0.65 };\n\nexport function GraphSimControls(props: GraphSimControlsProps): ReactElement | null {\n const instance = useResolvedInstance(props.instance, '<GraphSimControls>');\n const baseId = useId();\n\n // Mirror of the last-known SimulationConfig (module JSDoc: no store lane\n // exists to subscribe to). Prop identity change re-seeds — external\n // writer wins (the React-documented derive-during-render pattern).\n const [mirror, setMirror] = useState<SimulationConfig>(() => props.simulation ?? {});\n const [seededFrom, setSeededFrom] = useState(props.simulation);\n if (seededFrom !== props.simulation) {\n setSeededFrom(props.simulation);\n setMirror(props.simulation ?? {});\n }\n\n /** Decay the speed preset restores on release; undefined = the field was\n * unset pre-toggle (restore the engine-default constant). */\n const preSpeedDecayRef = useRef<number | undefined>(undefined);\n\n // The one simulation lane the store DOES publish (§16.1).\n const subscribe = useCallback(\n (onStoreChange: () => void) => instance.store.subscribe(onStoreChange),\n [instance],\n );\n const getRunning = useCallback(\n () => instance.store.getState().simulationRunning,\n [instance],\n );\n const simulationRunning = useSyncExternalStore(subscribe, getRunning, getRunning);\n\n // R-16.11-04 applicability gate — after hooks, per the rules of hooks.\n if ((props.layout ?? 'force') !== 'force') return null;\n\n /** ONE atomic config-only host update per control write (R-16.11-02). */\n const write = (next: SimulationConfig): void => {\n setMirror(next);\n instance.applyHostUpdate({ simulation: next });\n props.onSimulationChange?.(next);\n };\n\n const setField = (key: SimFieldKey, value: number): void => {\n const next: SimulationConfig = { ...mirror };\n next[key] = value;\n write(next);\n };\n\n // The preset holds exactly while decay sits at the fast value — the toggle\n // and the cool-down slider are two views over the same lane.\n const speedOn = mirror.decay === SPEED_FAST_DECAY;\n const toggleSpeed = (): void => {\n if (speedOn) {\n const restored = preSpeedDecayRef.current ?? SPEED_NORMAL_DECAY;\n preSpeedDecayRef.current = undefined;\n setField('decay', restored);\n } else {\n preSpeedDecayRef.current = mirror.decay;\n setField('decay', SPEED_FAST_DECAY);\n }\n };\n\n const panelLabel = props.label ?? 'Simulation controls';\n const speedId = `${baseId}-speed`;\n\n return (\n <div\n data-orbit-simcontrols=\"\"\n role=\"group\"\n aria-label={panelLabel}\n className={props.className}\n style={\n props.className !== undefined\n ? props.style\n : mergeStyle(\n { ...cornerStyle(props.position ?? 'bottom-left', props.offset), ...PANEL_STYLE },\n props.style,\n )\n }\n >\n <div data-orbit-simcontrols-title=\"\" style={TITLE_STYLE}>\n {panelLabel}\n </div>\n\n {SIM_FIELDS.map((field) => {\n const value = mirror[field.key] ?? field.fallback;\n const inputId = `${baseId}-${field.key}`;\n const set = (v: number): void => {\n setField(field.key, quantize(field, v));\n };\n if (props.renderField !== undefined) {\n return (\n <Fragment key={field.key}>\n {props.renderField({ field, value, inputId, set })}\n </Fragment>\n );\n }\n return (\n <div key={field.key} data-orbit-simcontrols-field={field.key} style={ROW_STYLE}>\n <label htmlFor={inputId} style={LABEL_STYLE}>\n {field.label}\n </label>\n <input\n id={inputId}\n type=\"range\"\n data-orbit-simcontrols-input={field.key}\n min={field.min}\n max={field.max}\n step={field.step}\n value={value}\n onChange={(e) => {\n const v = Number(e.target.value);\n if (Number.isFinite(v)) set(v);\n }}\n onKeyDown={(e) => {\n const target = keyboardTarget(field, value, e.key);\n if (target === null) return;\n // Suppress the browser's native range step so the write goes\n // through this handler exactly once (module JSDoc).\n e.preventDefault();\n if (target !== value) setField(field.key, target);\n }}\n style={SLIDER_STYLE}\n />\n <output\n htmlFor={inputId}\n data-orbit-simcontrols-value={field.key}\n style={VALUE_STYLE}\n >\n {String(value)}\n </output>\n </div>\n );\n })}\n\n <div data-orbit-simcontrols-speed-row=\"\" style={ROW_STYLE}>\n <input\n id={speedId}\n type=\"checkbox\"\n data-orbit-simcontrols-speed=\"\"\n checked={speedOn}\n onChange={toggleSpeed}\n />\n <label htmlFor={speedId}>Speed boost</label>\n </div>\n\n <div style={ROW_STYLE}>\n <button\n type=\"button\"\n data-orbit-simcontrols-reheat=\"\"\n style={BUTTON_STYLE}\n onClick={() => {\n instance.resumeSimulation();\n }}\n >\n Reheat\n </button>\n <span data-orbit-simcontrols-status=\"\" style={STATUS_STYLE}>\n {simulationRunning ? 'simulation running' : 'settled'}\n </span>\n </div>\n </div>\n );\n}\n\n/** S0-T02 tree-shake probe — must never reach a root-only consumer bundle. */\nexport const __ORBIT_SIMCONTROLS_SENTINEL__ = 'orbit-react/components/SimControls';\n"]}
|
package/dist/components/Table.js
CHANGED
|
@@ -296,7 +296,7 @@ function GraphTableInner(props, ref) {
|
|
|
296
296
|
const session = wrote.instance.getCrossfilterSession();
|
|
297
297
|
if (session !== null) void session.setBrush(wrote.key, null).catch(() => void 0);
|
|
298
298
|
},
|
|
299
|
-
[]
|
|
299
|
+
[instance, mode, dimensionKey]
|
|
300
300
|
);
|
|
301
301
|
const selectedNodes = useMemo(() => new Set(selection.nodeIds), [selection]);
|
|
302
302
|
const selectedEdges = useMemo(() => new Set(selection.edgeIds), [selection]);
|