@modernrelay/orbit-react 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/GraphProvider-B8ITDXS0.d.ts +11 -0
  3. package/dist/chunk-7XB47YST.js +69 -0
  4. package/dist/chunk-7XB47YST.js.map +1 -0
  5. package/dist/chunk-DGFLU2SN.js +108 -0
  6. package/dist/chunk-DGFLU2SN.js.map +1 -0
  7. package/dist/chunk-JN7QCMFW.js +93 -0
  8. package/dist/chunk-JN7QCMFW.js.map +1 -0
  9. package/dist/chunk-QJ7JXQVK.js +119 -0
  10. package/dist/chunk-QJ7JXQVK.js.map +1 -0
  11. package/dist/components/ContextMenu.d.ts +73 -0
  12. package/dist/components/ContextMenu.js +367 -0
  13. package/dist/components/ContextMenu.js.map +1 -0
  14. package/dist/components/Histogram.d.ts +33 -0
  15. package/dist/components/Histogram.js +216 -0
  16. package/dist/components/Histogram.js.map +1 -0
  17. package/dist/components/Inspector.d.ts +63 -0
  18. package/dist/components/Inspector.js +246 -0
  19. package/dist/components/Inspector.js.map +1 -0
  20. package/dist/components/Legend.d.ts +45 -0
  21. package/dist/components/Legend.js +185 -0
  22. package/dist/components/Legend.js.map +1 -0
  23. package/dist/components/Minimap.d.ts +63 -0
  24. package/dist/components/Minimap.js +222 -0
  25. package/dist/components/Minimap.js.map +1 -0
  26. package/dist/components/Navigator.d.ts +90 -0
  27. package/dist/components/Navigator.js +381 -0
  28. package/dist/components/Navigator.js.map +1 -0
  29. package/dist/components/Search.d.ts +70 -0
  30. package/dist/components/Search.js +203 -0
  31. package/dist/components/Search.js.map +1 -0
  32. package/dist/components/SelectionActions.d.ts +36 -0
  33. package/dist/components/SelectionActions.js +126 -0
  34. package/dist/components/SelectionActions.js.map +1 -0
  35. package/dist/components/SimControls.d.ts +124 -0
  36. package/dist/components/SimControls.js +221 -0
  37. package/dist/components/SimControls.js.map +1 -0
  38. package/dist/components/Table.d.ts +92 -0
  39. package/dist/components/Table.js +505 -0
  40. package/dist/components/Table.js.map +1 -0
  41. package/dist/components/Timeline.d.ts +34 -0
  42. package/dist/components/Timeline.js +182 -0
  43. package/dist/components/Timeline.js.map +1 -0
  44. package/dist/components/Toolbar.d.ts +60 -0
  45. package/dist/components/Toolbar.js +220 -0
  46. package/dist/components/Toolbar.js.map +1 -0
  47. package/dist/components/Tooltip.d.ts +65 -0
  48. package/dist/components/Tooltip.js +178 -0
  49. package/dist/components/Tooltip.js.map +1 -0
  50. package/dist/index.d.ts +488 -0
  51. package/dist/index.js +827 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/shared-Bj3Gk219.d.ts +43 -0
  54. package/package.json +99 -0
@@ -0,0 +1,381 @@
1
+ import { useResolvedInstance } from '../chunk-7XB47YST.js';
2
+ import { useId, useState, useRef, useEffect, useCallback, useSyncExternalStore } from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var DEFAULT_NAVIGATOR_WINDOW = 50;
6
+ function useNavStoreSlice(instance) {
7
+ const cacheRef = useRef(null);
8
+ const subscribe = useCallback(
9
+ (cb) => instance.store.subscribe(cb),
10
+ [instance]
11
+ );
12
+ const getSnapshot = useCallback(() => {
13
+ const s = instance.store.getState();
14
+ const prev = cacheRef.current;
15
+ if (prev !== null && prev.selection === s.selection && prev.pins === s.pins && prev.hidden === s.hiddenNodeIds && prev.model === s.revisions.model && prev.nodeCount === s.nodeCount && prev.search === s.search) {
16
+ return prev;
17
+ }
18
+ const next = {
19
+ selection: s.selection,
20
+ pins: s.pins,
21
+ hidden: s.hiddenNodeIds,
22
+ model: s.revisions.model,
23
+ nodeCount: s.nodeCount,
24
+ search: s.search
25
+ };
26
+ cacheRef.current = next;
27
+ return next;
28
+ }, [instance]);
29
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
30
+ }
31
+ function pageWindow(all, rawPage, size) {
32
+ const pageCount = Math.max(1, Math.ceil(all.length / size));
33
+ const page = Math.min(Math.max(rawPage, 0), pageCount - 1);
34
+ return { page, pageCount, items: all.slice(page * size, page * size + size) };
35
+ }
36
+ var PANEL_STYLE = {
37
+ pointerEvents: "auto",
38
+ display: "flex",
39
+ flexDirection: "column",
40
+ gap: 4,
41
+ maxWidth: 320,
42
+ fontSize: 12
43
+ };
44
+ var LIST_STYLE = { overflowY: "auto", maxHeight: 320 };
45
+ var HEADER_STYLE = { fontWeight: 600, padding: "4px 6px" };
46
+ var ITEM_STYLE = {
47
+ padding: "2px 6px",
48
+ cursor: "default",
49
+ whiteSpace: "nowrap",
50
+ overflow: "hidden",
51
+ textOverflow: "ellipsis"
52
+ };
53
+ var ACTIVE_ITEM_STYLE = {
54
+ ...ITEM_STYLE,
55
+ outline: "2px solid currentColor",
56
+ outlineOffset: -2
57
+ };
58
+ var PAGING_STYLE = { display: "flex", alignItems: "center", gap: 8 };
59
+ var EMPTY_STYLE = { padding: "4px 6px", fontStyle: "italic" };
60
+ function GraphNavigator(props) {
61
+ const instance = useResolvedInstance(props.instance, "<GraphNavigator>");
62
+ const slice = useNavStoreSlice(instance);
63
+ void props.keyboardMode;
64
+ const baseId = useId();
65
+ const [root, setRoot] = useState(null);
66
+ const [pages, setPages] = useState({
67
+ search: 0,
68
+ neighbors: 0,
69
+ selection: 0,
70
+ entry: 0
71
+ });
72
+ const [activeKey, setActiveKey] = useState(null);
73
+ const tokensRef = useRef(/* @__PURE__ */ new Map());
74
+ const degreesRef = useRef({
75
+ model: -1,
76
+ counts: /* @__PURE__ */ new Map()
77
+ });
78
+ const itemsRef = useRef(/* @__PURE__ */ new Map());
79
+ const pendingFocusRef = useRef(false);
80
+ useEffect(() => {
81
+ if (root !== null && root.model !== slice.model) setRoot(null);
82
+ }, [root, slice.model]);
83
+ if (degreesRef.current.model !== slice.model) {
84
+ degreesRef.current = { model: slice.model, counts: /* @__PURE__ */ new Map() };
85
+ }
86
+ const accessibility = instance.getAccessibility();
87
+ const rawWindow = accessibility?.navigatorWindow;
88
+ const windowSize = typeof rawWindow === "number" && Number.isFinite(rawWindow) && rawWindow >= 1 ? Math.floor(rawWindow) : DEFAULT_NAVIGATOR_WINDOW;
89
+ const getAccessibleLabel = accessibility?.getAccessibleLabel;
90
+ const nameOf = (id) => {
91
+ const node = instance.getNode(id);
92
+ if (node === void 0) return id;
93
+ if (getAccessibleLabel !== void 0) {
94
+ try {
95
+ return getAccessibleLabel(node);
96
+ } catch {
97
+ }
98
+ }
99
+ const label = node.attrs?.["label"];
100
+ return typeof label === "string" ? label : node.id;
101
+ };
102
+ const selectedSet = new Set(slice.selection.nodeIds);
103
+ const rowText = (id) => {
104
+ const parts = [nameOf(id)];
105
+ const count = degreesRef.current.counts.get(id);
106
+ if (count !== void 0) parts.push(count === 1 ? "1 neighbor" : `${count} neighbors`);
107
+ if (slice.pins.has(id)) parts.push("pinned");
108
+ if (slice.hidden.has(id)) parts.push("hidden");
109
+ return parts.join(" \xB7 ");
110
+ };
111
+ const domIdFor = (listKey, id) => {
112
+ const tokens = tokensRef.current;
113
+ let token = tokens.get(id);
114
+ if (token === void 0) {
115
+ token = tokens.size;
116
+ tokens.set(id, token);
117
+ }
118
+ return `${baseId}nav-${listKey}-${token}`;
119
+ };
120
+ const makeRow = (listKey, id) => ({
121
+ key: `${listKey}:${id}`,
122
+ domId: domIdFor(listKey, id),
123
+ nodeId: id,
124
+ listKey,
125
+ text: rowText(id),
126
+ selected: selectedSet.has(id)
127
+ });
128
+ const searchResults = slice.search === null ? [] : slice.search.results;
129
+ const listItems = {
130
+ search: searchResults.map((r) => r.id),
131
+ neighbors: root !== null ? root.neighbors : [],
132
+ selection: slice.selection.nodeIds,
133
+ // §15.1: the entry list is the SCENE roster (scope applied, mask not) —
134
+ // hidden/filtered nodes stay listed with their state exposed in text.
135
+ entry: root === null ? instance.getSceneNodeIds() : []
136
+ };
137
+ const sections = [];
138
+ if (slice.search !== null) {
139
+ const view = pageWindow(listItems.search, pages.search, windowSize);
140
+ const count = searchResults.length;
141
+ const start = view.page * windowSize;
142
+ sections.push({
143
+ key: "search",
144
+ listKey: "search",
145
+ label: "Search results",
146
+ groupLabel: `Search results for ${slice.search.query}: ${count} ${count === 1 ? "result" : "results"}, page ${view.page + 1} of ${view.pageCount}`,
147
+ // §16.5: row text prefers the service-supplied label (still a TEXT
148
+ // NODE); results may name ids outside the model — nameOf falls back to
149
+ // the id for those.
150
+ rows: view.items.map((id, i) => {
151
+ const row = makeRow("search", id);
152
+ const label = searchResults[start + i]?.label;
153
+ return label === void 0 ? row : { ...row, text: label };
154
+ }),
155
+ page: view.page,
156
+ pageCount: view.pageCount,
157
+ total: count
158
+ });
159
+ }
160
+ if (root !== null) {
161
+ const view = pageWindow(listItems.neighbors, pages.neighbors, windowSize);
162
+ const rootRow = {
163
+ key: `root:${root.id}`,
164
+ domId: domIdFor("root", root.id),
165
+ nodeId: root.id,
166
+ listKey: "neighbors",
167
+ text: rowText(root.id),
168
+ selected: selectedSet.has(root.id)
169
+ };
170
+ const count = root.neighbors.length;
171
+ sections.push({
172
+ key: "focused",
173
+ listKey: "neighbors",
174
+ label: "Focused node",
175
+ groupLabel: `Focused node ${nameOf(root.id)}: ${count} ${count === 1 ? "neighbor" : "neighbors"}, page ${view.page + 1} of ${view.pageCount}`,
176
+ rows: [rootRow, ...view.items.map((id) => makeRow("neighbors", id))],
177
+ page: view.page,
178
+ pageCount: view.pageCount,
179
+ total: count
180
+ });
181
+ }
182
+ if (slice.selection.nodeIds.length > 0) {
183
+ const view = pageWindow(listItems.selection, pages.selection, windowSize);
184
+ const count = slice.selection.nodeIds.length;
185
+ sections.push({
186
+ key: "selection",
187
+ listKey: "selection",
188
+ label: "Selection",
189
+ groupLabel: `Selection: ${count} ${count === 1 ? "node" : "nodes"}, page ${view.page + 1} of ${view.pageCount}`,
190
+ rows: view.items.map((id) => makeRow("selection", id)),
191
+ page: view.page,
192
+ pageCount: view.pageCount,
193
+ total: count
194
+ });
195
+ }
196
+ if (root === null) {
197
+ const view = pageWindow(listItems.entry, pages.entry, windowSize);
198
+ const count = listItems.entry.length;
199
+ sections.push({
200
+ key: "entry",
201
+ listKey: "entry",
202
+ label: "All nodes",
203
+ groupLabel: `All nodes: ${count} ${count === 1 ? "node" : "nodes"}, page ${view.page + 1} of ${view.pageCount}`,
204
+ rows: view.items.map((id) => makeRow("entry", id)),
205
+ page: view.page,
206
+ pageCount: view.pageCount,
207
+ total: count
208
+ });
209
+ }
210
+ const rows = sections.flatMap((s) => s.rows);
211
+ let activeIndex = activeKey === null ? -1 : rows.findIndex((r) => r.key === activeKey);
212
+ if (activeIndex === -1 && rows.length > 0) activeIndex = 0;
213
+ const activeRow = activeIndex >= 0 ? rows[activeIndex] : void 0;
214
+ const activeSection = activeRow !== void 0 ? sections.find((s) => s.listKey === activeRow.listKey) : void 0;
215
+ useEffect(() => {
216
+ if (!pendingFocusRef.current) return;
217
+ pendingFocusRef.current = false;
218
+ if (activeRow === void 0) return;
219
+ itemsRef.current.get(activeRow.domId)?.focus();
220
+ });
221
+ const focusItemSoon = () => {
222
+ pendingFocusRef.current = true;
223
+ };
224
+ const moveTo = (index) => {
225
+ if (rows.length === 0) return;
226
+ const row = rows[Math.min(Math.max(index, 0), rows.length - 1)];
227
+ if (row === void 0) return;
228
+ setActiveKey(row.key);
229
+ focusItemSoon();
230
+ };
231
+ const pageBy = (delta) => {
232
+ if (activeRow === void 0) return;
233
+ const listKey = activeRow.listKey;
234
+ const items = listItems[listKey];
235
+ const pageCount = Math.max(1, Math.ceil(items.length / windowSize));
236
+ const current = Math.min(Math.max(pages[listKey], 0), pageCount - 1);
237
+ const next = Math.min(Math.max(current + delta, 0), pageCount - 1);
238
+ if (next === current) return;
239
+ setPages((p) => ({ ...p, [listKey]: next }));
240
+ const first = items[next * windowSize];
241
+ if (first !== void 0) setActiveKey(`${listKey}:${first}`);
242
+ focusItemSoon();
243
+ };
244
+ const activate = (row) => {
245
+ if (row.listKey === "search") {
246
+ const result = searchResults.find((r) => r.id === row.nodeId);
247
+ if (result !== void 0) instance.activateSearchResult(result);
248
+ return;
249
+ }
250
+ const neighbors = instance.focusNode(row.nodeId);
251
+ degreesRef.current.counts.set(row.nodeId, neighbors.length);
252
+ setRoot({ id: row.nodeId, neighbors, model: slice.model });
253
+ setPages((p) => ({ ...p, neighbors: 0 }));
254
+ setActiveKey(`root:${row.nodeId}`);
255
+ focusItemSoon();
256
+ };
257
+ const toggleSelection = (row) => {
258
+ if (row.listKey === "search" && instance.getNode(row.nodeId) === void 0) return;
259
+ const current = slice.selection.nodeIds;
260
+ const next = current.includes(row.nodeId) ? current.filter((id) => id !== row.nodeId) : [...current, row.nodeId];
261
+ instance.selectNodes(next);
262
+ };
263
+ const clearTransientFocus = () => {
264
+ setRoot(null);
265
+ setActiveKey(null);
266
+ focusItemSoon();
267
+ };
268
+ const onKeyDown = (e) => {
269
+ if (activeRow === void 0) return;
270
+ switch (e.key) {
271
+ case "ArrowDown":
272
+ moveTo(activeIndex + 1);
273
+ break;
274
+ case "ArrowUp":
275
+ moveTo(activeIndex - 1);
276
+ break;
277
+ case "Home":
278
+ moveTo(0);
279
+ break;
280
+ case "End":
281
+ moveTo(rows.length - 1);
282
+ break;
283
+ case "PageDown":
284
+ pageBy(1);
285
+ break;
286
+ case "PageUp":
287
+ pageBy(-1);
288
+ break;
289
+ case "Enter":
290
+ activate(activeRow);
291
+ break;
292
+ case " ":
293
+ toggleSelection(activeRow);
294
+ break;
295
+ case "Escape":
296
+ if (root === null) return;
297
+ clearTransientFocus();
298
+ break;
299
+ default:
300
+ return;
301
+ }
302
+ e.preventDefault();
303
+ };
304
+ const panelLabel = props.label ?? "Graph navigator";
305
+ return /* @__PURE__ */ jsxs(
306
+ "div",
307
+ {
308
+ "data-orbit-navigator": "",
309
+ role: "group",
310
+ "aria-label": panelLabel,
311
+ className: props.className,
312
+ style: { ...PANEL_STYLE, ...props.style },
313
+ children: [
314
+ /* @__PURE__ */ jsx(
315
+ "div",
316
+ {
317
+ role: "listbox",
318
+ "aria-label": `${panelLabel} items`,
319
+ "aria-activedescendant": activeRow?.domId,
320
+ "aria-keyshortcuts": "ArrowUp ArrowDown Home End PageUp PageDown Enter Space Escape",
321
+ onKeyDown,
322
+ style: LIST_STYLE,
323
+ children: rows.length === 0 ? /* @__PURE__ */ jsx("div", { role: "presentation", style: EMPTY_STYLE, children: "No nodes" }) : sections.map((section) => /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": section.groupLabel, children: [
324
+ /* @__PURE__ */ jsx("div", { role: "presentation", style: HEADER_STYLE, children: section.label }),
325
+ section.rows.map((row) => {
326
+ const isActive = row.key === activeRow?.key;
327
+ return /* @__PURE__ */ jsx(
328
+ "div",
329
+ {
330
+ id: row.domId,
331
+ role: "option",
332
+ "aria-selected": row.selected,
333
+ tabIndex: isActive ? 0 : -1,
334
+ ref: (el) => {
335
+ if (el === null) itemsRef.current.delete(row.domId);
336
+ else itemsRef.current.set(row.domId, el);
337
+ },
338
+ onClick: () => {
339
+ setActiveKey(row.key);
340
+ focusItemSoon();
341
+ },
342
+ style: isActive ? ACTIVE_ITEM_STYLE : ITEM_STYLE,
343
+ children: row.text
344
+ },
345
+ row.key
346
+ );
347
+ })
348
+ ] }, section.key))
349
+ }
350
+ ),
351
+ activeSection !== void 0 ? /* @__PURE__ */ jsxs("div", { "data-orbit-navigator-paging": "", style: PAGING_STYLE, children: [
352
+ /* @__PURE__ */ jsx(
353
+ "button",
354
+ {
355
+ type: "button",
356
+ onClick: () => pageBy(-1),
357
+ disabled: activeSection.page === 0,
358
+ "aria-keyshortcuts": "PageUp",
359
+ children: "Previous page"
360
+ }
361
+ ),
362
+ /* @__PURE__ */ jsx("span", { role: "status", "data-orbit-navigator-page-status": "", children: `${activeSection.label}: page ${activeSection.page + 1} of ${activeSection.pageCount} (${activeSection.total} ${activeSection.total === 1 ? "item" : "items"})` }),
363
+ /* @__PURE__ */ jsx(
364
+ "button",
365
+ {
366
+ type: "button",
367
+ onClick: () => pageBy(1),
368
+ disabled: activeSection.page >= activeSection.pageCount - 1,
369
+ "aria-keyshortcuts": "PageDown",
370
+ children: "Next page"
371
+ }
372
+ )
373
+ ] }) : null
374
+ ]
375
+ }
376
+ );
377
+ }
378
+
379
+ export { GraphNavigator };
380
+ //# sourceMappingURL=Navigator.js.map
381
+ //# sourceMappingURL=Navigator.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,70 @@
1
+ import { ReactNode, CSSProperties, ReactElement } from 'react';
2
+ import { SearchResult, SearchUnavailableReason } from '@modernrelay/orbit-core';
3
+ import { A as AnyGraphInstance } from '../GraphProvider-B8ITDXS0.js';
4
+
5
+ /**
6
+ * `@modernrelay/orbit-react/components/Search` — `<GraphSearch>`, the §16.5
7
+ * search box (S11-T08).
8
+ *
9
+ * Owns exactly the UI half of the §16.5 split: the text input, the debounce
10
+ * window, and the result list. Everything correctness-critical lives in the
11
+ * instance (`instance.search` creates the RequestContext, caches by declared
12
+ * revisions, cancels superseded work, and rejects stale results at admission
13
+ * — §9.2); this component just issues ONE `instance.search` per settled
14
+ * debounce window and renders `store.search`.
15
+ *
16
+ * Behavior:
17
+ * - typing schedules `instance.search(query, {limit})` after `debounceMs`
18
+ * (default 200); a keystroke inside the window replaces the pending call —
19
+ * one service call per settled window, never one per keystroke;
20
+ * - emptying the input cancels the pending call and clears the shared slice
21
+ * (`instance.clearSearch()`), so the navigator section disappears too;
22
+ * - the result list reads `store.search` (the §16.5 single source of truth —
23
+ * bespoke UIs and the navigator observe the same slice); `score` renders
24
+ * when the service supplied one;
25
+ * - Enter / click activates via `instance.activateSearchResult`: 'focused'
26
+ * closes the listbox and clears the active-option highlight; otherwise
27
+ * `onResultUnavailable(result, reason)` fires — falling back to the
28
+ * `<Graph onSearchResultUnavailable>` default when no local prop is given
29
+ * (spec §16.5 result contract: the HOST reacts; search never mutates
30
+ * scope/filters behind the user's back);
31
+ * - I6 (query coherence): Enter only activates when `store.search.query`
32
+ * matches the current input — after an edit, the previous query's rows may
33
+ * keep showing through the debounce window, but Enter is a no-op until the
34
+ * edited query's publication lands (clicks on visible rows stay live);
35
+ * - Escape clears: input text, pending debounce, active option, and the
36
+ * store slice (`instance.clearSearch`).
37
+ *
38
+ * ARIA: the combobox pattern — `role="combobox"` input with
39
+ * `aria-expanded`/`aria-controls`/`aria-activedescendant` over a
40
+ * `role="listbox"` of `role="option"` rows; ArrowDown/ArrowUp move the active
41
+ * option (no wrap), Home/End jump. Option DOM ids are index-derived (node ids
42
+ * are untrusted text). All result-derived text (label, id, score) renders as
43
+ * TEXT NODES only (§14) — `renderResult` is the customization escape hatch.
44
+ */
45
+
46
+ interface GraphSearchProps {
47
+ /** Explicit instance (multi-instance pages); ambient context otherwise. */
48
+ instance?: AnyGraphInstance;
49
+ placeholder?: string;
50
+ /** Debounce window in ms — one `instance.search` per settled window. Default 200. */
51
+ debounceMs?: number;
52
+ /** Result cap forwarded to the service (§16.5). Default 20. */
53
+ limit?: number;
54
+ /**
55
+ * §16.5 result contract: fired when an activated result cannot be focused
56
+ * ('not-loaded' | 'out-of-scope' | 'filtered'). Defaults to the
57
+ * `<Graph onSearchResultUnavailable>` prop when omitted.
58
+ */
59
+ onResultUnavailable?: (result: SearchResult, reason: SearchUnavailableReason) => void;
60
+ /** Custom option content (rendered INSIDE the option row). Default: label
61
+ * text node + score (when present). */
62
+ renderResult?: (r: SearchResult) => ReactNode;
63
+ /** Accessible name of the search box. Default 'Search graph'. */
64
+ label?: string;
65
+ className?: string;
66
+ style?: CSSProperties;
67
+ }
68
+ declare function GraphSearch(props: GraphSearchProps): ReactElement;
69
+
70
+ export { GraphSearch, type GraphSearchProps };