@flanksource/clicky-ui 0.3.45 → 0.3.46

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 (140) hide show
  1. package/README.md +22 -0
  2. package/dist/data/AccessMark.cjs +23 -0
  3. package/dist/data/AccessMark.cjs.map +1 -0
  4. package/dist/data/AccessMark.d.ts +11 -0
  5. package/dist/data/AccessMark.d.ts.map +1 -0
  6. package/dist/data/AccessMark.js +23 -0
  7. package/dist/data/AccessMark.js.map +1 -0
  8. package/dist/data/CodeDiff.cjs +116 -23
  9. package/dist/data/CodeDiff.cjs.map +1 -1
  10. package/dist/data/CodeDiff.d.ts +26 -0
  11. package/dist/data/CodeDiff.d.ts.map +1 -1
  12. package/dist/data/CodeDiff.js +116 -23
  13. package/dist/data/CodeDiff.js.map +1 -1
  14. package/dist/data/GraphDiagram.cjs +65 -13
  15. package/dist/data/GraphDiagram.cjs.map +1 -1
  16. package/dist/data/GraphDiagram.d.ts +9 -2
  17. package/dist/data/GraphDiagram.d.ts.map +1 -1
  18. package/dist/data/GraphDiagram.js +66 -14
  19. package/dist/data/GraphDiagram.js.map +1 -1
  20. package/dist/data/GraphDiagramGroups.cjs +100 -0
  21. package/dist/data/GraphDiagramGroups.cjs.map +1 -0
  22. package/dist/data/GraphDiagramGroups.d.ts +24 -0
  23. package/dist/data/GraphDiagramGroups.d.ts.map +1 -0
  24. package/dist/data/GraphDiagramGroups.js +100 -0
  25. package/dist/data/GraphDiagramGroups.js.map +1 -0
  26. package/dist/data/GraphDiagramNodes.cjs +17 -31
  27. package/dist/data/GraphDiagramNodes.cjs.map +1 -1
  28. package/dist/data/GraphDiagramNodes.d.ts +6 -13
  29. package/dist/data/GraphDiagramNodes.d.ts.map +1 -1
  30. package/dist/data/GraphDiagramNodes.js +17 -31
  31. package/dist/data/GraphDiagramNodes.js.map +1 -1
  32. package/dist/data/TreeNode.cjs +1 -1
  33. package/dist/data/TreeNode.cjs.map +1 -1
  34. package/dist/data/TreeNode.d.ts.map +1 -1
  35. package/dist/data/TreeNode.js +1 -1
  36. package/dist/data/TreeNode.js.map +1 -1
  37. package/dist/data/call-graph/CallGraph.cjs +50 -31
  38. package/dist/data/call-graph/CallGraph.cjs.map +1 -1
  39. package/dist/data/call-graph/CallGraph.d.ts +10 -1
  40. package/dist/data/call-graph/CallGraph.d.ts.map +1 -1
  41. package/dist/data/call-graph/CallGraph.js +52 -33
  42. package/dist/data/call-graph/CallGraph.js.map +1 -1
  43. package/dist/data/call-graph/CallGraphMembers.cjs +5 -4
  44. package/dist/data/call-graph/CallGraphMembers.cjs.map +1 -1
  45. package/dist/data/call-graph/CallGraphMembers.d.ts +1 -1
  46. package/dist/data/call-graph/CallGraphMembers.d.ts.map +1 -1
  47. package/dist/data/call-graph/CallGraphMembers.js +5 -4
  48. package/dist/data/call-graph/CallGraphMembers.js.map +1 -1
  49. package/dist/data/call-graph/CallGraphToolbar.cjs +17 -5
  50. package/dist/data/call-graph/CallGraphToolbar.cjs.map +1 -1
  51. package/dist/data/call-graph/CallGraphToolbar.d.ts +10 -0
  52. package/dist/data/call-graph/CallGraphToolbar.d.ts.map +1 -1
  53. package/dist/data/call-graph/CallGraphToolbar.js +18 -6
  54. package/dist/data/call-graph/CallGraphToolbar.js.map +1 -1
  55. package/dist/data/call-graph/call-graph-model.cjs +169 -18
  56. package/dist/data/call-graph/call-graph-model.cjs.map +1 -1
  57. package/dist/data/call-graph/call-graph-model.d.ts +40 -4
  58. package/dist/data/call-graph/call-graph-model.d.ts.map +1 -1
  59. package/dist/data/call-graph/call-graph-model.js +170 -19
  60. package/dist/data/call-graph/call-graph-model.js.map +1 -1
  61. package/dist/data/call-graph/call-graph-vocabulary.cjs +19 -1
  62. package/dist/data/call-graph/call-graph-vocabulary.cjs.map +1 -1
  63. package/dist/data/call-graph/call-graph-vocabulary.d.ts +4 -0
  64. package/dist/data/call-graph/call-graph-vocabulary.d.ts.map +1 -1
  65. package/dist/data/call-graph/call-graph-vocabulary.js +19 -1
  66. package/dist/data/call-graph/call-graph-vocabulary.js.map +1 -1
  67. package/dist/data/call-graph/call-graph.fixtures.d.ts.map +1 -1
  68. package/dist/data/call-graph/index.d.ts +2 -2
  69. package/dist/data/call-graph/index.d.ts.map +1 -1
  70. package/dist/data/call-graph/types.cjs.map +1 -1
  71. package/dist/data/call-graph/types.d.ts +7 -2
  72. package/dist/data/call-graph/types.d.ts.map +1 -1
  73. package/dist/data/call-graph/types.js.map +1 -1
  74. package/dist/data/data-access.cjs +8 -0
  75. package/dist/data/data-access.cjs.map +1 -0
  76. package/dist/data/data-access.d.ts +5 -0
  77. package/dist/data/data-access.d.ts.map +1 -0
  78. package/dist/data/data-access.js +8 -0
  79. package/dist/data/data-access.js.map +1 -0
  80. package/dist/data/git/GitChangeList.cjs +6 -2
  81. package/dist/data/git/GitChangeList.cjs.map +1 -1
  82. package/dist/data/git/GitChangeList.d.ts +6 -1
  83. package/dist/data/git/GitChangeList.d.ts.map +1 -1
  84. package/dist/data/git/GitChangeList.js +6 -2
  85. package/dist/data/git/GitChangeList.js.map +1 -1
  86. package/dist/data/graph-columns-layout.cjs +47 -16
  87. package/dist/data/graph-columns-layout.cjs.map +1 -1
  88. package/dist/data/graph-columns-layout.d.ts +34 -0
  89. package/dist/data/graph-columns-layout.d.ts.map +1 -1
  90. package/dist/data/graph-columns-layout.js +47 -16
  91. package/dist/data/graph-columns-layout.js.map +1 -1
  92. package/dist/data/graph-diagram-model.cjs +5 -0
  93. package/dist/data/graph-diagram-model.cjs.map +1 -1
  94. package/dist/data/graph-diagram-model.d.ts +19 -0
  95. package/dist/data/graph-diagram-model.d.ts.map +1 -1
  96. package/dist/data/graph-diagram-model.js +5 -0
  97. package/dist/data/graph-diagram-model.js.map +1 -1
  98. package/dist/data/graph-group-window.cjs +60 -0
  99. package/dist/data/graph-group-window.cjs.map +1 -0
  100. package/dist/data/graph-group-window.d.ts +33 -0
  101. package/dist/data/graph-group-window.d.ts.map +1 -0
  102. package/dist/data/graph-group-window.js +60 -0
  103. package/dist/data/graph-group-window.js.map +1 -0
  104. package/dist/data/use-group-windows.cjs +50 -0
  105. package/dist/data/use-group-windows.cjs.map +1 -0
  106. package/dist/data/use-group-windows.d.ts +27 -0
  107. package/dist/data/use-group-windows.d.ts.map +1 -0
  108. package/dist/data/use-group-windows.js +50 -0
  109. package/dist/data/use-group-windows.js.map +1 -0
  110. package/dist/data/version-info.cjs +3 -3
  111. package/dist/data/version-info.js +3 -3
  112. package/dist/data.cjs +10 -0
  113. package/dist/data.cjs.map +1 -1
  114. package/dist/data.d.ts +4 -2
  115. package/dist/data.d.ts.map +1 -1
  116. package/dist/data.js +13 -3
  117. package/dist/data.js.map +1 -1
  118. package/dist/hooks/event-hub.cjs +229 -0
  119. package/dist/hooks/event-hub.cjs.map +1 -0
  120. package/dist/hooks/event-hub.d.ts +26 -0
  121. package/dist/hooks/event-hub.d.ts.map +1 -0
  122. package/dist/hooks/event-hub.js +229 -0
  123. package/dist/hooks/event-hub.js.map +1 -0
  124. package/dist/hooks.cjs +2 -0
  125. package/dist/hooks.cjs.map +1 -1
  126. package/dist/hooks.d.ts +1 -0
  127. package/dist/hooks.d.ts.map +1 -1
  128. package/dist/hooks.js +2 -0
  129. package/dist/hooks.js.map +1 -1
  130. package/dist/index.cjs +12 -0
  131. package/dist/index.cjs.map +1 -1
  132. package/dist/index.js +15 -3
  133. package/dist/index.js.map +1 -1
  134. package/dist/overlay/modalStack.cjs +13 -6
  135. package/dist/overlay/modalStack.cjs.map +1 -1
  136. package/dist/overlay/modalStack.d.ts.map +1 -1
  137. package/dist/overlay/modalStack.js +13 -6
  138. package/dist/overlay/modalStack.js.map +1 -1
  139. package/dist/styles.css +1 -1
  140. package/package.json +1 -1
@@ -0,0 +1,229 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ const DEFAULT_URL = "/api/events";
5
+ const TEARDOWN_GRACE_MS = 1e3;
6
+ const RESUBSCRIBE_DELAY_MS = 1e3;
7
+ const READY_STATE_CONNECTING = 0;
8
+ const READY_STATE_OPEN = 1;
9
+ const READY_STATE_CLOSED = 2;
10
+ function invokeListener(listener, event) {
11
+ if (typeof listener === "function") listener(event);
12
+ else listener.handleEvent(event);
13
+ }
14
+ class Sub {
15
+ constructor(id, url, hooks) {
16
+ __publicField(this, "readyState", READY_STATE_CONNECTING);
17
+ __publicField(this, "onopen", null);
18
+ __publicField(this, "onmessage", null);
19
+ __publicField(this, "onerror", null);
20
+ __publicField(this, "listeners", /* @__PURE__ */ new Map());
21
+ // Real-connection listeners this sub has registered, keyed by the prefixed
22
+ // event name (`${id}/${name}`), so close() can remove exactly what it added.
23
+ __publicField(this, "routedHandlers", /* @__PURE__ */ new Map());
24
+ __publicField(this, "resubscribeTimer", null);
25
+ __publicField(this, "disposed", false);
26
+ this.id = id;
27
+ this.url = url;
28
+ this.hooks = hooks;
29
+ }
30
+ addEventListener(type, listener) {
31
+ let set = this.listeners.get(type);
32
+ if (!set) {
33
+ set = /* @__PURE__ */ new Set();
34
+ this.listeners.set(type, set);
35
+ }
36
+ set.add(listener);
37
+ this.hooks.route(this, type);
38
+ }
39
+ removeEventListener(type, listener) {
40
+ var _a;
41
+ (_a = this.listeners.get(type)) == null ? void 0 : _a.delete(listener);
42
+ }
43
+ close() {
44
+ this.hooks.close(this);
45
+ }
46
+ }
47
+ function dispatch(sub, name, event) {
48
+ var _a, _b, _c;
49
+ for (const listener of Array.from(sub.listeners.get(name) ?? [])) invokeListener(listener, event);
50
+ if (name === "message") (_a = sub.onmessage) == null ? void 0 : _a.call(sub, event);
51
+ if (name === "error") (_b = sub.onerror) == null ? void 0 : _b.call(sub, event);
52
+ if (name === "open") (_c = sub.onopen) == null ? void 0 : _c.call(sub, event);
53
+ }
54
+ function createEventHub(options = {}) {
55
+ const { url: hubUrl = DEFAULT_URL, buildId = null, onBuildMismatch = () => window.location.reload() } = options;
56
+ let real = null;
57
+ let connId = null;
58
+ let subCounter = 0;
59
+ const subs = /* @__PURE__ */ new Map();
60
+ let teardownTimer = null;
61
+ let reloadedForBuildMismatch = false;
62
+ function maybeReloadForBuildMismatch(helloBuildId) {
63
+ if (reloadedForBuildMismatch) return;
64
+ if (!helloBuildId || !buildId) return;
65
+ if (helloBuildId === buildId) return;
66
+ reloadedForBuildMismatch = true;
67
+ onBuildMismatch();
68
+ }
69
+ function failSub(sub, message, cause) {
70
+ sub.readyState = READY_STATE_CLOSED;
71
+ dispatch(sub, "error", new Event("error"));
72
+ if (cause !== void 0) console.error(`[event-hub] ${message} for ${sub.url}:`, cause);
73
+ else console.error(`[event-hub] ${message} for ${sub.url}`);
74
+ }
75
+ async function postSub(sub) {
76
+ const conn = connId;
77
+ if (!conn || sub.disposed || sub.readyState === READY_STATE_CLOSED) return;
78
+ const stale = () => sub.disposed || conn !== connId || sub.readyState === READY_STATE_CLOSED;
79
+ try {
80
+ const res = await fetch(`${hubUrl}/${conn}/subs`, {
81
+ method: "POST",
82
+ headers: { "Content-Type": "application/json" },
83
+ body: JSON.stringify({ id: sub.id, path: sub.url })
84
+ });
85
+ if (stale()) return;
86
+ if (res.status === 204) {
87
+ sub.readyState = READY_STATE_OPEN;
88
+ dispatch(sub, "open", new Event("open"));
89
+ return;
90
+ }
91
+ if (res.status === 404) return;
92
+ failSub(sub, `subscribe failed: ${res.status}`);
93
+ } catch (err) {
94
+ if (stale()) return;
95
+ failSub(sub, "subscribe request failed", err);
96
+ }
97
+ }
98
+ function onHello(event) {
99
+ const payload = JSON.parse(event.data);
100
+ connId = payload.conn;
101
+ maybeReloadForBuildMismatch(payload.build);
102
+ for (const sub of subs.values()) {
103
+ if (!sub.disposed) void postSub(sub);
104
+ }
105
+ }
106
+ function onRealError(es) {
107
+ connId = null;
108
+ for (const sub of subs.values()) {
109
+ if (sub.disposed || sub.readyState === READY_STATE_CLOSED) continue;
110
+ sub.readyState = READY_STATE_CONNECTING;
111
+ dispatch(sub, "error", new Event("error"));
112
+ }
113
+ if (es.readyState !== READY_STATE_CLOSED || real !== es) return;
114
+ for (const sub of subs.values()) {
115
+ for (const [eventName, handler] of sub.routedHandlers) es.removeEventListener(eventName, handler);
116
+ sub.routedHandlers.clear();
117
+ }
118
+ es.onerror = null;
119
+ real = null;
120
+ setTimeout(() => {
121
+ if (!real && subs.size > 0) ensureRealOpen();
122
+ }, RESUBSCRIBE_DELAY_MS);
123
+ }
124
+ function ensureRealOpen() {
125
+ if (real) {
126
+ if (teardownTimer) {
127
+ clearTimeout(teardownTimer);
128
+ teardownTimer = null;
129
+ }
130
+ return real;
131
+ }
132
+ connId = null;
133
+ const es = new EventSource(hubUrl);
134
+ es.addEventListener("__hello", (event) => onHello(event));
135
+ es.onerror = () => onRealError(es);
136
+ real = es;
137
+ routeActiveSubs();
138
+ return es;
139
+ }
140
+ function routeActiveSubs() {
141
+ for (const sub of subs.values()) {
142
+ if (sub.disposed) continue;
143
+ routeName(sub, "message");
144
+ for (const name of sub.listeners.keys()) routeName(sub, name);
145
+ routeClosed(sub);
146
+ }
147
+ }
148
+ const eventNameFor = (sub, name) => `${sub.id}/${name}`;
149
+ function routeName(sub, name) {
150
+ const eventName = eventNameFor(sub, name);
151
+ if (!real || name === "open" || sub.disposed || sub.routedHandlers.has(eventName)) return;
152
+ const handler = (event) => {
153
+ const message = event;
154
+ dispatch(sub, name, new MessageEvent(name, { data: message.data, lastEventId: message.lastEventId }));
155
+ };
156
+ real.addEventListener(eventName, handler);
157
+ sub.routedHandlers.set(eventName, handler);
158
+ }
159
+ function onSubClosed(sub, event) {
160
+ if (sub.disposed) return;
161
+ let payload;
162
+ try {
163
+ payload = JSON.parse(event.data);
164
+ } catch (err) {
165
+ failSub(sub, "closed frame was malformed", err);
166
+ return;
167
+ }
168
+ if (payload.status >= 200 && payload.status < 300) {
169
+ sub.readyState = READY_STATE_CONNECTING;
170
+ dispatch(sub, "error", new Event("error"));
171
+ sub.resubscribeTimer = setTimeout(() => {
172
+ sub.resubscribeTimer = null;
173
+ if (!sub.disposed) void postSub(sub);
174
+ }, RESUBSCRIBE_DELAY_MS);
175
+ return;
176
+ }
177
+ failSub(sub, `sub closed with status ${payload.status}`, payload.error);
178
+ }
179
+ function routeClosed(sub) {
180
+ const eventName = eventNameFor(sub, "__closed");
181
+ if (!real || sub.routedHandlers.has(eventName)) return;
182
+ const handler = (event) => onSubClosed(sub, event);
183
+ real.addEventListener(eventName, handler);
184
+ sub.routedHandlers.set(eventName, handler);
185
+ }
186
+ function scheduleTeardownIfIdle() {
187
+ if (subs.size > 0 || teardownTimer) return;
188
+ teardownTimer = setTimeout(() => {
189
+ teardownTimer = null;
190
+ if (subs.size > 0) return;
191
+ real == null ? void 0 : real.close();
192
+ real = null;
193
+ connId = null;
194
+ }, TEARDOWN_GRACE_MS);
195
+ }
196
+ function closeSub(sub) {
197
+ if (sub.disposed) return;
198
+ sub.disposed = true;
199
+ sub.readyState = READY_STATE_CLOSED;
200
+ if (sub.resubscribeTimer) {
201
+ clearTimeout(sub.resubscribeTimer);
202
+ sub.resubscribeTimer = null;
203
+ }
204
+ for (const [eventName, handler] of sub.routedHandlers) real == null ? void 0 : real.removeEventListener(eventName, handler);
205
+ sub.routedHandlers.clear();
206
+ subs.delete(sub.id);
207
+ const conn = connId;
208
+ if (conn) {
209
+ fetch(`${hubUrl}/${conn}/subs/${sub.id}`, { method: "DELETE" }).then((res) => {
210
+ if (!res.ok && res.status !== 404) console.error(`[event-hub] unsubscribe failed for ${sub.url}: ${res.status}`);
211
+ }).catch((err) => console.error(`[event-hub] unsubscribe request failed for ${sub.url}:`, err));
212
+ }
213
+ scheduleTeardownIfIdle();
214
+ }
215
+ const hooks = { route: routeName, close: closeSub };
216
+ return (url) => {
217
+ const sub = new Sub(`s${++subCounter}`, url, hooks);
218
+ subs.set(sub.id, sub);
219
+ ensureRealOpen();
220
+ routeName(sub, "message");
221
+ routeClosed(sub);
222
+ if (connId) void postSub(sub);
223
+ return sub;
224
+ };
225
+ }
226
+ export {
227
+ createEventHub
228
+ };
229
+ //# sourceMappingURL=event-hub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-hub.js","sources":["../../src/hooks/event-hub.ts"],"sourcesContent":["import type { EventSourceFactory, EventSourceLike } from \"./event-source\";\n\n// Browsers allow only six HTTP/1.1 connections per host, shared across every\n// tab. An app that opens one native EventSource per topic (list view, status\n// badge, detail pane, activity feed, ...) lets a second tab or a reload starve\n// ordinary fetches behind a queue that never drains.\n//\n// An event hub multiplexes every topic a tab opens onto a single real\n// connection to the server's events endpoint (`/api/events` by default): one\n// native EventSource, with each logical stream registered as a \"sub\"\n// (POST {url}/{conn}/subs, DELETE {url}/{conn}/subs/{id}) whose frames arrive\n// prefixed `<subId>/<name>`. Callers never see any of this — the factory\n// returned by createEventHub() hands back an EventSourceLike that behaves like\n// `new EventSource(url)` from their point of view, so it plugs straight into\n// `<EventSourceProvider value={...}>`.\n\nexport interface EventHubOptions {\n /** Server endpoint of the multiplexed stream. Defaults to `/api/events`. */\n url?: string;\n /**\n * The build id this page was served with. When the server's hello frame\n * reports a different build, `onBuildMismatch` fires once. `null`/absent\n * (tests, Storybook, a bare dev server with no backend in front of it)\n * means the hub never reloads.\n */\n buildId?: string | null;\n /** Called once on a build mismatch. Defaults to `window.location.reload()`. */\n onBuildMismatch?: () => void;\n}\n\nconst DEFAULT_URL = \"/api/events\";\n\n// How long an idle hub (no subs left) keeps the real connection open before\n// tearing it down — long enough to survive React StrictMode's mount → unmount\n// → mount churn and a component swap that closes one stream and immediately\n// opens its replacement.\nconst TEARDOWN_GRACE_MS = 1000;\n\n// Delay before re-subscribing after a sub's handler ends with a 2xx status\n// (server-side stream completed normally and can be re-opened), mirroring\n// native EventSource's own reconnect delay.\nconst RESUBSCRIBE_DELAY_MS = 1000;\n\nconst READY_STATE_CONNECTING = 0;\nconst READY_STATE_OPEN = 1;\nconst READY_STATE_CLOSED = 2;\n\ntype DomListener = EventListenerOrEventListenerObject;\n\nfunction invokeListener(listener: DomListener, event: Event): void {\n if (typeof listener === \"function\") listener(event);\n else listener.handleEvent(event);\n}\n\n// Sub is both the internal bookkeeping for one logical stream and the public\n// EventSourceLike handed back to the caller — there is no separate wrapper\n// object, so `readyState`/`url` always reflect live state.\nclass Sub implements EventSourceLike {\n readyState = READY_STATE_CONNECTING;\n onopen: ((ev: Event) => unknown) | null = null;\n onmessage: ((ev: MessageEvent) => unknown) | null = null;\n onerror: ((ev: Event) => unknown) | null = null;\n\n readonly listeners = new Map<string, Set<DomListener>>();\n // Real-connection listeners this sub has registered, keyed by the prefixed\n // event name (`${id}/${name}`), so close() can remove exactly what it added.\n readonly routedHandlers = new Map<string, (ev: Event) => void>();\n resubscribeTimer: ReturnType<typeof setTimeout> | null = null;\n disposed = false;\n\n constructor(\n readonly id: string,\n readonly url: string,\n private readonly hooks: { route(sub: Sub, name: string): void; close(sub: Sub): void },\n ) {}\n\n addEventListener(type: string, listener: DomListener): void {\n let set = this.listeners.get(type);\n if (!set) {\n set = new Set();\n this.listeners.set(type, set);\n }\n set.add(listener);\n this.hooks.route(this, type);\n }\n\n removeEventListener(type: string, listener: DomListener): void {\n this.listeners.get(type)?.delete(listener);\n }\n\n close(): void {\n this.hooks.close(this);\n }\n}\n\nfunction dispatch(sub: Sub, name: string, event: Event): void {\n // Snapshot the set so a listener that (un)registers listeners mid-dispatch\n // doesn't change who receives this frame.\n for (const listener of Array.from(sub.listeners.get(name) ?? [])) invokeListener(listener, event);\n // onmessage/onerror/onopen mirror native EventSource: each is just the\n // single-slot form of addEventListener('message'|'error'|'open', ...), so a\n // frame named \"error\" (server-sent) reaches both a registered\n // addEventListener('error', ...) and .onerror, exactly like the browser.\n if (name === \"message\") sub.onmessage?.(event as MessageEvent);\n if (name === \"error\") sub.onerror?.(event);\n if (name === \"open\") sub.onopen?.(event);\n}\n\n/**\n * Creates an `EventSourceFactory` that multiplexes every stream it opens over\n * one real connection to `options.url`. Each returned stream behaves like\n * `new EventSource(url)` to the caller: CONNECTING until the subscription is\n * live, dispatches 'open'/'message'/'error' (and any server-named event), and\n * auto-reconnects the way native EventSource does.\n *\n * All state (real connection, connection id, sub registry, teardown timer,\n * reload guard) is per hub, so two hubs never share a connection.\n */\nexport function createEventHub(options: EventHubOptions = {}): EventSourceFactory {\n const { url: hubUrl = DEFAULT_URL, buildId = null, onBuildMismatch = () => window.location.reload() } = options;\n\n let real: EventSourceLike | null = null;\n let connId: string | null = null;\n let subCounter = 0;\n const subs = new Map<string, Sub>();\n let teardownTimer: ReturnType<typeof setTimeout> | null = null;\n // Guards against a reload loop within one page load: once triggered, further\n // __hello frames (e.g. after the reload's own new connection reconnects\n // mid-flight) never trigger a second one.\n let reloadedForBuildMismatch = false;\n\n // A stale tab still serving an old bundle would otherwise keep working\n // against a server that has moved on — including racing the six-connection\n // cap this hub exists to close, since an old bundle may hold raw\n // EventSources of its own. When the server's hello reports a different build\n // than the one this page was served with, reload once to pick up the\n // current bundle.\n function maybeReloadForBuildMismatch(helloBuildId: string | undefined): void {\n if (reloadedForBuildMismatch) return;\n if (!helloBuildId || !buildId) return;\n if (helloBuildId === buildId) return;\n reloadedForBuildMismatch = true;\n onBuildMismatch();\n }\n\n function failSub(sub: Sub, message: string, cause?: unknown): void {\n sub.readyState = READY_STATE_CLOSED;\n dispatch(sub, \"error\", new Event(\"error\"));\n if (cause !== undefined) console.error(`[event-hub] ${message} for ${sub.url}:`, cause);\n else console.error(`[event-hub] ${message} for ${sub.url}`);\n }\n\n async function postSub(sub: Sub): Promise<void> {\n const conn = connId;\n if (!conn || sub.disposed || sub.readyState === READY_STATE_CLOSED) return;\n // A response for a connection that has since dropped (or a sub that has\n // since failed) is stale: the next __hello re-subscribes on the live one.\n const stale = () => sub.disposed || conn !== connId || sub.readyState === READY_STATE_CLOSED;\n try {\n const res = await fetch(`${hubUrl}/${conn}/subs`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id: sub.id, path: sub.url }),\n });\n if (stale()) return;\n if (res.status === 204) {\n sub.readyState = READY_STATE_OPEN;\n dispatch(sub, \"open\", new Event(\"open\"));\n return;\n }\n if (res.status === 404) return; // stale conn id; the next __hello retries\n failSub(sub, `subscribe failed: ${res.status}`);\n } catch (err) {\n if (stale()) return;\n failSub(sub, \"subscribe request failed\", err);\n }\n }\n\n function onHello(event: MessageEvent): void {\n const payload = JSON.parse(event.data) as { conn: string; build?: string };\n connId = payload.conn;\n maybeReloadForBuildMismatch(payload.build);\n // A hello means every currently-active sub needs a fresh subscription: the\n // first hello of a brand-new connection, or a reconnect hello after a\n // drop, both start from a server side with no subs registered yet.\n for (const sub of subs.values()) {\n if (!sub.disposed) void postSub(sub);\n }\n }\n\n function onRealError(es: EventSourceLike): void {\n connId = null;\n for (const sub of subs.values()) {\n if (sub.disposed || sub.readyState === READY_STATE_CLOSED) continue;\n sub.readyState = READY_STATE_CONNECTING;\n dispatch(sub, \"error\", new Event(\"error\"));\n }\n // Native EventSource reconnects on its own after a transient error; the\n // next __hello re-subscribes everything and dispatches 'open' again. A\n // CLOSED source (non-200 or wrong Content-Type) never reconnects, so drop\n // it and open a replacement after a delay.\n if (es.readyState !== READY_STATE_CLOSED || real !== es) return;\n for (const sub of subs.values()) {\n for (const [eventName, handler] of sub.routedHandlers) es.removeEventListener(eventName, handler);\n sub.routedHandlers.clear();\n }\n es.onerror = null;\n real = null;\n setTimeout(() => {\n if (!real && subs.size > 0) ensureRealOpen();\n }, RESUBSCRIBE_DELAY_MS);\n }\n\n function ensureRealOpen(): EventSourceLike {\n if (real) {\n if (teardownTimer) {\n clearTimeout(teardownTimer);\n teardownTimer = null;\n }\n return real;\n }\n connId = null;\n const es = new EventSource(hubUrl);\n es.addEventListener(\"__hello\", (event) => onHello(event as MessageEvent));\n es.onerror = () => onRealError(es);\n real = es;\n routeActiveSubs();\n return es;\n }\n\n // routeActiveSubs binds every live sub's routing onto a freshly created real\n // connection (a replacement after a terminal error has none yet).\n function routeActiveSubs(): void {\n for (const sub of subs.values()) {\n if (sub.disposed) continue;\n routeName(sub, \"message\");\n for (const name of sub.listeners.keys()) routeName(sub, name);\n routeClosed(sub);\n }\n }\n\n const eventNameFor = (sub: Sub, name: string) => `${sub.id}/${name}`;\n\n // routeName registers, at most once per sub+name, a real-connection listener\n // that re-dispatches `${sub.id}/${name}` frames to this sub's own listeners.\n // \"open\" is synthetic (dispatched locally on subscribe/reconnect) and never\n // arrives on the wire, so it is never routed. A closed sub has no routing to\n // register: its real connection may already be torn down, and no frame for\n // it can arrive. With no real connection (awaiting a replacement after a\n // terminal error) routing is deferred to routeActiveSubs.\n function routeName(sub: Sub, name: string): void {\n const eventName = eventNameFor(sub, name);\n if (!real || name === \"open\" || sub.disposed || sub.routedHandlers.has(eventName)) return;\n const handler = (event: Event) => {\n const message = event as MessageEvent;\n dispatch(sub, name, new MessageEvent(name, { data: message.data, lastEventId: message.lastEventId }));\n };\n real.addEventListener(eventName, handler);\n sub.routedHandlers.set(eventName, handler);\n }\n\n function onSubClosed(sub: Sub, event: MessageEvent): void {\n if (sub.disposed) return;\n let payload: { status: number; error?: string };\n try {\n payload = JSON.parse(event.data) as { status: number; error?: string };\n } catch (err) {\n failSub(sub, \"closed frame was malformed\", err);\n return;\n }\n if (payload.status >= 200 && payload.status < 300) {\n // The server-side handler ended on its own with a success status — the\n // same shape as a native stream simply reconnecting.\n sub.readyState = READY_STATE_CONNECTING;\n dispatch(sub, \"error\", new Event(\"error\"));\n sub.resubscribeTimer = setTimeout(() => {\n sub.resubscribeTimer = null;\n if (!sub.disposed) void postSub(sub);\n }, RESUBSCRIBE_DELAY_MS);\n return;\n }\n failSub(sub, `sub closed with status ${payload.status}`, payload.error);\n }\n\n // routeClosed wires the sub's terminal frame: unlike every other name, its\n // payload drives re-subscription rather than being handed to consumers.\n function routeClosed(sub: Sub): void {\n const eventName = eventNameFor(sub, \"__closed\");\n if (!real || sub.routedHandlers.has(eventName)) return;\n const handler = (event: Event) => onSubClosed(sub, event as MessageEvent);\n real.addEventListener(eventName, handler);\n sub.routedHandlers.set(eventName, handler);\n }\n\n function scheduleTeardownIfIdle(): void {\n if (subs.size > 0 || teardownTimer) return;\n teardownTimer = setTimeout(() => {\n teardownTimer = null;\n if (subs.size > 0) return; // something re-subscribed during the grace window\n real?.close();\n real = null;\n connId = null;\n }, TEARDOWN_GRACE_MS);\n }\n\n function closeSub(sub: Sub): void {\n if (sub.disposed) return;\n sub.disposed = true;\n sub.readyState = READY_STATE_CLOSED;\n if (sub.resubscribeTimer) {\n clearTimeout(sub.resubscribeTimer);\n sub.resubscribeTimer = null;\n }\n for (const [eventName, handler] of sub.routedHandlers) real?.removeEventListener(eventName, handler);\n sub.routedHandlers.clear();\n subs.delete(sub.id);\n\n const conn = connId;\n if (conn) {\n fetch(`${hubUrl}/${conn}/subs/${sub.id}`, { method: \"DELETE\" })\n .then((res) => {\n if (!res.ok && res.status !== 404) console.error(`[event-hub] unsubscribe failed for ${sub.url}: ${res.status}`);\n })\n .catch((err) => console.error(`[event-hub] unsubscribe request failed for ${sub.url}:`, err));\n }\n scheduleTeardownIfIdle();\n }\n\n const hooks = { route: routeName, close: closeSub };\n\n return (url) => {\n const sub = new Sub(`s${++subCounter}`, url, hooks);\n subs.set(sub.id, sub);\n ensureRealOpen();\n // Routing must be registered before the subscribe POST is sent: frames can\n // start arriving on the real connection as soon as the server accepts the\n // subscription, which can race the POST's own response.\n routeName(sub, \"message\");\n routeClosed(sub);\n if (connId) void postSub(sub);\n return sub;\n };\n}\n"],"names":[],"mappings":"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"}
package/dist/hooks.cjs CHANGED
@@ -10,6 +10,7 @@ const useSort = require("./hooks/use-sort.cjs");
10
10
  const useHistoryRoute = require("./hooks/use-history-route.cjs");
11
11
  const eventSource = require("./hooks/event-source.cjs");
12
12
  const eventSourceProvider = require("./hooks/event-source-provider.cjs");
13
+ const eventHub = require("./hooks/event-hub.cjs");
13
14
  const useTaskRun = require("./hooks/use-task-run.cjs");
14
15
  const usePrompts = require("./hooks/use-prompts.cjs");
15
16
  const useHotkey = require("./hooks/use-hotkey.cjs");
@@ -31,6 +32,7 @@ exports.useSort = useSort.useSort;
31
32
  exports.useHistoryRoute = useHistoryRoute.useHistoryRoute;
32
33
  exports.useEventSourceFactory = eventSource.useEventSourceFactory;
33
34
  exports.EventSourceProvider = eventSourceProvider.EventSourceProvider;
35
+ exports.createEventHub = eventHub.createEventHub;
34
36
  exports.useTaskRun = useTaskRun.useTaskRun;
35
37
  exports.useTaskRuns = useTaskRun.useTaskRuns;
36
38
  exports.answerPrompt = usePrompts.answerPrompt;
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"hooks.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/hooks.d.ts CHANGED
@@ -8,6 +8,7 @@ export { useSort, type SortDir, type SortState, type UseSortOptions, type UseSor
8
8
  export { useHistoryRoute, type UseHistoryRouteOptions } from './hooks/use-history-route.js';
9
9
  export { useEventSourceFactory, type EventSourceFactory, type EventSourceLike, } from './hooks/event-source.js';
10
10
  export { EventSourceProvider, type EventSourceProviderProps, } from './hooks/event-source-provider.js';
11
+ export { createEventHub, type EventHubOptions } from './hooks/event-hub.js';
11
12
  export { useTaskRun, useTaskRuns, type UseTaskRunOptions, type UseTaskRunResult, type UseTaskRunsOptions, type UseTaskRunsResult, type TaskTransportOptions, } from './hooks/use-task-run.js';
12
13
  export { usePrompts, answerPrompt, type PromptSnapshot, type PromptFilter, type UsePromptsOptions, type UsePromptsResult, } from './hooks/use-prompts.js';
13
14
  export { useHotkey, parseHotkey, formatHotkey, type UseHotkeyOptions, type ParsedHotkey, } from './hooks/use-hotkey.js';
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,KAAK,EACV,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,GACd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,OAAO,EACP,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EACL,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,UAAU,EACV,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,UAAU,EACV,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,SAAS,EACT,WAAW,EACX,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC"}
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,KAAK,KAAK,EACV,KAAK,aAAa,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EACL,YAAY,EACZ,aAAa,EACb,aAAa,GACd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,KAAK,oBAAoB,GAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,OAAO,EACP,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,2BAA2B,CAAC;AACzF,OAAO,EACL,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,eAAe,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EACL,UAAU,EACV,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,UAAU,EACV,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,SAAS,EACT,WAAW,EACX,YAAY,EACZ,KAAK,gBAAgB,EACrB,KAAK,YAAY,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC"}
package/dist/hooks.js CHANGED
@@ -8,6 +8,7 @@ import { useSort } from "./hooks/use-sort.js";
8
8
  import { useHistoryRoute } from "./hooks/use-history-route.js";
9
9
  import { useEventSourceFactory } from "./hooks/event-source.js";
10
10
  import { EventSourceProvider } from "./hooks/event-source-provider.js";
11
+ import { createEventHub } from "./hooks/event-hub.js";
11
12
  import { useTaskRun, useTaskRuns } from "./hooks/use-task-run.js";
12
13
  import { answerPrompt, usePrompts } from "./hooks/use-prompts.js";
13
14
  import { formatHotkey, parseHotkey, useHotkey } from "./hooks/use-hotkey.js";
@@ -20,6 +21,7 @@ export {
20
21
  ThemeProvider,
21
22
  answerPrompt,
22
23
  appendTailEvent,
24
+ createEventHub,
23
25
  emptyLogTailBuffer,
24
26
  encodeTailParams,
25
27
  formatHotkey,
package/dist/hooks.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hooks.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"hooks.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;"}
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ const useSort = require("./hooks/use-sort.cjs");
23
23
  const useHistoryRoute = require("./hooks/use-history-route.cjs");
24
24
  const eventSource = require("./hooks/event-source.cjs");
25
25
  const eventSourceProvider = require("./hooks/event-source-provider.cjs");
26
+ const eventHub = require("./hooks/event-hub.cjs");
26
27
  const useTaskRun = require("./hooks/use-task-run.cjs");
27
28
  const usePrompts = require("./hooks/use-prompts.cjs");
28
29
  const useHotkey = require("./hooks/use-hotkey.cjs");
@@ -199,6 +200,8 @@ const ExecutionTree = require("./data/ExecutionTree.cjs");
199
200
  const GraphDiagram = require("./data/GraphDiagram.cjs");
200
201
  const graphLayout = require("./data/graph-layout.cjs");
201
202
  const graphColumnsLayout = require("./data/graph-columns-layout.cjs");
203
+ const AccessMark = require("./data/AccessMark.cjs");
204
+ const dataAccess = require("./data/data-access.cjs");
202
205
  const TreeNode = require("./data/TreeNode.cjs");
203
206
  const TreeGroupHeader = require("./data/TreeGroupHeader.cjs");
204
207
  const Timeline = require("./data/Timeline.cjs");
@@ -992,6 +995,7 @@ exports.useSort = useSort.useSort;
992
995
  exports.useHistoryRoute = useHistoryRoute.useHistoryRoute;
993
996
  exports.useEventSourceFactory = eventSource.useEventSourceFactory;
994
997
  exports.EventSourceProvider = eventSourceProvider.EventSourceProvider;
998
+ exports.createEventHub = eventHub.createEventHub;
995
999
  exports.useTaskRun = useTaskRun.useTaskRun;
996
1000
  exports.useTaskRuns = useTaskRun.useTaskRuns;
997
1001
  exports.answerPrompt = usePrompts.answerPrompt;
@@ -1270,7 +1274,10 @@ exports.pruneNullNodes = objectGraphNodes.pruneNullNodes;
1270
1274
  exports.ExecutionTree = ExecutionTree.ExecutionTree;
1271
1275
  exports.GraphDiagram = GraphDiagram.GraphDiagram;
1272
1276
  exports.ringLayout = graphLayout.ringLayout;
1277
+ exports.GROUP_MEMBER_WINDOW = graphColumnsLayout.GROUP_MEMBER_WINDOW;
1273
1278
  exports.columnsLayout = graphColumnsLayout.columnsLayout;
1279
+ exports.AccessMark = AccessMark.AccessMark;
1280
+ exports.mergeAccess = dataAccess.mergeAccess;
1274
1281
  exports.TreeNode = TreeNode.TreeNode;
1275
1282
  exports.TreeGroupHeader = TreeGroupHeader.TreeGroupHeader;
1276
1283
  exports.Timeline = Timeline.Timeline;
@@ -1471,14 +1478,19 @@ exports.isPositionalParam = types$2.isPositionalParam;
1471
1478
  exports.CALL_GRAPH_ACCESS = types.CALL_GRAPH_ACCESS;
1472
1479
  exports.CALL_GRAPH_DATA_GLYPHS = callGraphVocabulary.CALL_GRAPH_DATA_GLYPHS;
1473
1480
  exports.CALL_GRAPH_DATA_KIND_WORDS = callGraphVocabulary.DATA_KIND_WORDS;
1481
+ exports.DATA_MEMBER_OWNER = callGraphVocabulary.DATA_MEMBER_OWNER;
1474
1482
  exports.UIR_CALL_GRAPH_VOCABULARY = callGraphVocabulary.UIR_CALL_GRAPH_VOCABULARY;
1475
1483
  exports.callsItself = callGraphVocabulary.callsItself;
1484
+ exports.dataMemberAside = callGraphVocabulary.dataMemberAside;
1476
1485
  exports.dataNodeBacking = callGraphVocabulary.dataNodeBacking;
1486
+ exports.dataNodeGlyph = callGraphVocabulary.dataNodeGlyph;
1477
1487
  exports.dataNodeStatus = callGraphVocabulary.dataNodeStatus;
1478
1488
  exports.interfaceMethods = callGraphVocabulary.interfaceMethods;
1489
+ exports.isDataMember = callGraphVocabulary.isDataMember;
1479
1490
  exports.isDataNode = callGraphVocabulary.isDataNode;
1480
1491
  exports.CALL_GRAPH_DEFAULT_DEPTH = callGraphModel.CALL_GRAPH_DEFAULT_DEPTH;
1481
1492
  exports.CALL_GRAPH_MAX_DEPTH = callGraphModel.CALL_GRAPH_MAX_DEPTH;
1493
+ exports.baseCallGraphAccess = callGraphModel.baseAccess;
1482
1494
  exports.mergeCallGraph = callGraphModel.mergeGraph;
1483
1495
  exports.toggleCallGraphAccess = callGraphModel.toggleAccess;
1484
1496
  exports.CALL_GRAPH_EXCLUDE_EXTERNAL = callGraphExclude.EXCLUDE_EXTERNAL;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ import { useSort } from "./hooks/use-sort.js";
21
21
  import { useHistoryRoute } from "./hooks/use-history-route.js";
22
22
  import { useEventSourceFactory } from "./hooks/event-source.js";
23
23
  import { EventSourceProvider } from "./hooks/event-source-provider.js";
24
+ import { createEventHub } from "./hooks/event-hub.js";
24
25
  import { useTaskRun, useTaskRuns } from "./hooks/use-task-run.js";
25
26
  import { answerPrompt, usePrompts } from "./hooks/use-prompts.js";
26
27
  import { formatHotkey, parseHotkey, useHotkey } from "./hooks/use-hotkey.js";
@@ -196,7 +197,9 @@ import { countObjectGraphNodes, pruneNullNodes } from "./data/object-graph-nodes
196
197
  import { ExecutionTree } from "./data/ExecutionTree.js";
197
198
  import { GraphDiagram } from "./data/GraphDiagram.js";
198
199
  import { ringLayout } from "./data/graph-layout.js";
199
- import { columnsLayout } from "./data/graph-columns-layout.js";
200
+ import { GROUP_MEMBER_WINDOW, columnsLayout } from "./data/graph-columns-layout.js";
201
+ import { AccessMark } from "./data/AccessMark.js";
202
+ import { mergeAccess } from "./data/data-access.js";
200
203
  import { TreeNode } from "./data/TreeNode.js";
201
204
  import { TreeGroupHeader } from "./data/TreeGroupHeader.js";
202
205
  import { Timeline } from "./data/Timeline.js";
@@ -286,8 +289,8 @@ import { useBrowserRouter, useMemoryRouter, useRouter } from "./rpc/router.js";
286
289
  import { RouterProvider } from "./rpc/RouterProvider.js";
287
290
  import { isPositionalParam } from "./rpc/types.js";
288
291
  import { CALL_GRAPH_ACCESS } from "./data/call-graph/types.js";
289
- import { CALL_GRAPH_DATA_GLYPHS, DATA_KIND_WORDS, UIR_CALL_GRAPH_VOCABULARY, callsItself, dataNodeBacking, dataNodeStatus, interfaceMethods, isDataNode } from "./data/call-graph/call-graph-vocabulary.js";
290
- import { CALL_GRAPH_DEFAULT_DEPTH, CALL_GRAPH_MAX_DEPTH, mergeGraph, toggleAccess } from "./data/call-graph/call-graph-model.js";
292
+ import { CALL_GRAPH_DATA_GLYPHS, DATA_KIND_WORDS, DATA_MEMBER_OWNER, UIR_CALL_GRAPH_VOCABULARY, callsItself, dataMemberAside, dataNodeBacking, dataNodeGlyph, dataNodeStatus, interfaceMethods, isDataMember, isDataNode } from "./data/call-graph/call-graph-vocabulary.js";
293
+ import { CALL_GRAPH_DEFAULT_DEPTH, CALL_GRAPH_MAX_DEPTH, baseAccess, mergeGraph, toggleAccess } from "./data/call-graph/call-graph-model.js";
291
294
  import { EXCLUDE_EXTERNAL, callGraphGroupFacts, formatExclude, matchesExclude, parseExclude } from "./data/call-graph/call-graph-exclude.js";
292
295
  import { CacheAdapterRegistry, createCacheRegistry, prefixAdapter } from "./data/cache-browser/adapter.js";
293
296
  import { CacheBrowser } from "./data/cache-browser/CacheBrowser.js";
@@ -902,6 +905,7 @@ import { verifyReportCounts, verifyReportTests } from "./data/verification/verif
902
905
  export {
903
906
  ACCEPT_OPTIONS,
904
907
  AVATAR_PALETTE,
908
+ AccessMark,
905
909
  AccordionList,
906
910
  AnsiHtml,
907
911
  AppLayout,
@@ -971,6 +975,7 @@ export {
971
975
  CopyButton,
972
976
  CountryFlag,
973
977
  CountryPicker,
978
+ DATA_MEMBER_OWNER,
974
979
  DATA_TABLE_FILTER_LOOKUP_LIMIT,
975
980
  DEBUG_CAPTURE_HEADER,
976
981
  DEBUG_REFRESH_INSPECTION_HEADER,
@@ -1015,6 +1020,7 @@ export {
1015
1020
  FormLookupProvider,
1016
1021
  FormatOptionsDropdown,
1017
1022
  FrameSourceWindow,
1023
+ GROUP_MEMBER_WINDOW,
1018
1024
  Gauge,
1019
1025
  GaugeHoverCard,
1020
1026
  GitChangedFilesSummary,
@@ -2256,6 +2262,7 @@ export {
2256
2262
  authorDisplayName,
2257
2263
  authoredRuntimeSpec,
2258
2264
  badgeVariants,
2265
+ baseAccess as baseCallGraphAccess,
2259
2266
  bucketTasks,
2260
2267
  buildCommandHref,
2261
2268
  buildJSONPathNode,
@@ -2298,6 +2305,7 @@ export {
2298
2305
  createCacheRegistry,
2299
2306
  createChecklistMarkdown,
2300
2307
  createComboboxCustomEntry,
2308
+ createEventHub,
2301
2309
  createFenceMarkdown,
2302
2310
  createLazyJSONPathTree,
2303
2311
  createOperationsApiClient,
@@ -2305,7 +2313,9 @@ export {
2305
2313
  createTestRunnerRegistry,
2306
2314
  createUnitFormExtensions,
2307
2315
  cycleFilterState,
2316
+ dataMemberAside,
2308
2317
  dataNodeBacking,
2318
+ dataNodeGlyph,
2309
2319
  dataNodeStatus,
2310
2320
  debugCaptureHeaders,
2311
2321
  debugConsoleAvailable,
@@ -2386,6 +2396,7 @@ export {
2386
2396
  isApplicationStackFrame,
2387
2397
  isConnectionLoggingCapability,
2388
2398
  isCountryCode,
2399
+ isDataMember,
2389
2400
  isDataNode,
2390
2401
  isFailedOrWarn,
2391
2402
  isFlatMetadata,
@@ -2421,6 +2432,7 @@ export {
2421
2432
  matchingTools,
2422
2433
  mediaMatches,
2423
2434
  memoryTourStorage,
2435
+ mergeAccess,
2424
2436
  mergeGraph as mergeCallGraph,
2425
2437
  mergeRuntimeSpec,
2426
2438
  mergeSeries,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -23,11 +23,12 @@ function onEscapeKeyDown(event) {
23
23
  if (event.defaultPrevented || event.key !== "Escape" || event.isComposing || event.keyCode === 229) {
24
24
  return;
25
25
  }
26
- const top = escapeStack[escapeStack.length - 1];
26
+ const top = escapeStack.findLast((entry) => !entry.dismissed);
27
27
  if (!top) return;
28
28
  event.preventDefault();
29
29
  event.stopPropagation();
30
30
  event.stopImmediatePropagation();
31
+ top.dismissed = true;
31
32
  top.onEscape();
32
33
  }
33
34
  function ensureEscapeListener() {
@@ -66,14 +67,20 @@ function useEscapeLayer(open, onEscape, enabled = true) {
66
67
  const id = react.useId();
67
68
  const onEscapeRef = react.useRef(onEscape);
68
69
  onEscapeRef.current = onEscape;
70
+ const entryRef = react.useRef(null);
71
+ react.useEffect(() => {
72
+ if (entryRef.current) entryRef.current.dismissed = false;
73
+ });
69
74
  react.useEffect(() => {
70
75
  if (!open || !enabled) return;
71
76
  ensureEscapeListener();
72
- escapeStack.push({
73
- id,
74
- onEscape: () => onEscapeRef.current()
75
- });
76
- return () => removeEscapeLayer(id);
77
+ const entry = { id, dismissed: false, onEscape: () => onEscapeRef.current() };
78
+ entryRef.current = entry;
79
+ escapeStack.push(entry);
80
+ return () => {
81
+ entryRef.current = null;
82
+ removeEscapeLayer(id);
83
+ };
77
84
  }, [enabled, id, open]);
78
85
  }
79
86
  function useTourLayer(active) {
@@ -1 +1 @@
1
- {"version":3,"file":"modalStack.cjs","sources":["../../src/overlay/modalStack.ts"],"sourcesContent":["import { useEffect, useId, useRef, useSyncExternalStore } from \"react\";\nimport { zIndex } from \"./zIndex\";\n\n// A process-wide stack of the currently open modals, topmost last. Modals mount\n// independently (no shared provider), so the stack lives at module scope and is\n// shared through useSyncExternalStore. It lets nested modals cooperate: only the\n// topmost reacts to Escape, and each renders above the one it opened over.\nconst stack: string[] = [];\nconst listeners = new Set<() => void>();\nconst escapeStack: Array<{ id: string; onEscape: () => void }> = [];\nlet escapeDocument: Document | null = null;\n// Running guided tours. A tour dims the page from `zIndex.tour`, far above the\n// modal band, so floating content opened *during* a tour (the menu a step tells\n// the user to open) has to clear that dim or it renders invisibly underneath.\nlet tourLayers = 0;\n\nfunction emit() {\n for (const listener of listeners) listener();\n}\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n// The snapshot is the stack identity plus the tour-layer count; it only changes\n// when an entry is pushed or popped, so subscribers re-render exactly when their\n// position could have moved.\nfunction getSnapshot() {\n return `${tourLayers}:` + stack.join(\"\\u0000\");\n}\n\nfunction onEscapeKeyDown(event: KeyboardEvent) {\n if (\n event.defaultPrevented ||\n event.key !== \"Escape\" ||\n event.isComposing ||\n (event as KeyboardEvent & { keyCode?: number }).keyCode === 229\n ) {\n return;\n }\n\n const top = escapeStack[escapeStack.length - 1];\n if (!top) return;\n\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n top.onEscape();\n}\n\nfunction ensureEscapeListener() {\n if (escapeDocument || typeof document === \"undefined\") return;\n escapeDocument = document;\n escapeDocument.addEventListener(\"keydown\", onEscapeKeyDown);\n}\n\nfunction removeEscapeLayer(id: string) {\n const index = escapeStack.findIndex((entry) => entry.id === id);\n if (index !== -1) escapeStack.splice(index, 1);\n\n if (escapeStack.length === 0 && escapeDocument) {\n escapeDocument.removeEventListener(\"keydown\", onEscapeKeyDown);\n escapeDocument = null;\n }\n}\n\nexport type ModalStackPosition = {\n /** Whether this modal is the topmost open one and should own global affordances. */\n isTop: boolean;\n /** Zero-based depth in the stack; drives the stacking order of nested modals. */\n depth: number;\n};\n\n/**\n * Registers a modal in the global stack while `open`, returning its live\n * position. Used so a nested modal sits above its opener and only the topmost\n * modal closes on Escape.\n */\nexport function useModalStack(open: boolean): ModalStackPosition {\n const id = useId();\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n useEffect(() => {\n if (!open) return;\n stack.push(id);\n emit();\n return () => {\n const index = stack.indexOf(id);\n if (index !== -1) stack.splice(index, 1);\n emit();\n };\n }, [open, id]);\n\n const depth = stack.indexOf(id);\n return {\n isTop: depth !== -1 && depth === stack.length - 1,\n depth: depth === -1 ? 0 : depth,\n };\n}\n\n/**\n * Registers an Escape-closeable overlay while `open`. The topmost registered\n * layer owns Escape, so a keypress dismisses one dialog/menu layer at a time.\n */\nexport function useEscapeLayer(open: boolean, onEscape: () => void, enabled = true) {\n const id = useId();\n const onEscapeRef = useRef(onEscape);\n onEscapeRef.current = onEscape;\n\n useEffect(() => {\n if (!open || !enabled) return;\n\n ensureEscapeListener();\n escapeStack.push({\n id,\n onEscape: () => onEscapeRef.current(),\n });\n\n return () => removeEscapeLayer(id);\n }, [enabled, id, open]);\n}\n\n/**\n * Registers a running guided tour while `active`, so floating layers lift above\n * its dim. Symmetric with `useModalStack`, but a count rather than a stack: the\n * tour overlay does not nest.\n */\nexport function useTourLayer(active: boolean): void {\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n useEffect(() => {\n if (!active) return;\n tourLayers += 1;\n emit();\n return () => {\n tourLayers -= 1;\n emit();\n };\n }, [active]);\n}\n\n/**\n * z-index a floating layer (dropdown / combobox / tooltip / popover) should use\n * so it renders above any open modal — and below the next nested modal — or at\n * the popover floor when no modal is open. While a guided tour runs, the value\n * also clears the tour's dim and step card, so a step that says \"open this menu\"\n * shows the menu rather than hiding it under the overlay. Subscribes to the\n * modal stack so the value tracks modals and tours opening and closing.\n */\nexport function useFloatingZIndex(): number {\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n const base =\n stack.length === 0\n ? zIndex.popover\n : zIndex.modal + (stack.length - 1) * zIndex.modalStep + zIndex.popoverOverModalOffset;\n if (tourLayers === 0) return base;\n return Math.max(base, zIndex.tour + zIndex.tourCardOffset + zIndex.popoverOverModalOffset);\n}\n"],"names":["useId","useSyncExternalStore","useEffect","useRef","zIndex"],"mappings":"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"}
1
+ {"version":3,"file":"modalStack.cjs","sources":["../../src/overlay/modalStack.ts"],"sourcesContent":["import { useEffect, useId, useRef, useSyncExternalStore } from \"react\";\nimport { zIndex } from \"./zIndex\";\n\n// A process-wide stack of the currently open modals, topmost last. Modals mount\n// independently (no shared provider), so the stack lives at module scope and is\n// shared through useSyncExternalStore. It lets nested modals cooperate: only the\n// topmost reacts to Escape, and each renders above the one it opened over.\nconst stack: string[] = [];\nconst listeners = new Set<() => void>();\n// `dismissed` marks a layer whose onEscape already ran but whose close has not\n// committed yet, so a quick second Escape reaches the layer beneath it.\ntype EscapeLayer = { id: string; dismissed: boolean; onEscape: () => void };\nconst escapeStack: EscapeLayer[] = [];\nlet escapeDocument: Document | null = null;\n// Running guided tours. A tour dims the page from `zIndex.tour`, far above the\n// modal band, so floating content opened *during* a tour (the menu a step tells\n// the user to open) has to clear that dim or it renders invisibly underneath.\nlet tourLayers = 0;\n\nfunction emit() {\n for (const listener of listeners) listener();\n}\n\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n// The snapshot is the stack identity plus the tour-layer count; it only changes\n// when an entry is pushed or popped, so subscribers re-render exactly when their\n// position could have moved.\nfunction getSnapshot() {\n return `${tourLayers}:` + stack.join(\"\\u0000\");\n}\n\nfunction onEscapeKeyDown(event: KeyboardEvent) {\n if (\n event.defaultPrevented ||\n event.key !== \"Escape\" ||\n event.isComposing ||\n (event as KeyboardEvent & { keyCode?: number }).keyCode === 229\n ) {\n return;\n }\n\n const top = escapeStack.findLast((entry) => !entry.dismissed);\n if (!top) return;\n\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n top.dismissed = true;\n top.onEscape();\n}\n\nfunction ensureEscapeListener() {\n if (escapeDocument || typeof document === \"undefined\") return;\n escapeDocument = document;\n escapeDocument.addEventListener(\"keydown\", onEscapeKeyDown);\n}\n\nfunction removeEscapeLayer(id: string) {\n const index = escapeStack.findIndex((entry) => entry.id === id);\n if (index !== -1) escapeStack.splice(index, 1);\n\n if (escapeStack.length === 0 && escapeDocument) {\n escapeDocument.removeEventListener(\"keydown\", onEscapeKeyDown);\n escapeDocument = null;\n }\n}\n\nexport type ModalStackPosition = {\n /** Whether this modal is the topmost open one and should own global affordances. */\n isTop: boolean;\n /** Zero-based depth in the stack; drives the stacking order of nested modals. */\n depth: number;\n};\n\n/**\n * Registers a modal in the global stack while `open`, returning its live\n * position. Used so a nested modal sits above its opener and only the topmost\n * modal closes on Escape.\n */\nexport function useModalStack(open: boolean): ModalStackPosition {\n const id = useId();\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n useEffect(() => {\n if (!open) return;\n stack.push(id);\n emit();\n return () => {\n const index = stack.indexOf(id);\n if (index !== -1) stack.splice(index, 1);\n emit();\n };\n }, [open, id]);\n\n const depth = stack.indexOf(id);\n return {\n isTop: depth !== -1 && depth === stack.length - 1,\n depth: depth === -1 ? 0 : depth,\n };\n}\n\n/**\n * Registers an Escape-closeable overlay while `open`. The topmost registered\n * layer owns Escape, so a keypress dismisses one dialog/menu layer at a time.\n */\nexport function useEscapeLayer(open: boolean, onEscape: () => void, enabled = true) {\n const id = useId();\n const onEscapeRef = useRef(onEscape);\n onEscapeRef.current = onEscape;\n const entryRef = useRef<EscapeLayer | null>(null);\n\n // A dismissed layer that re-rendered without closing owns Escape again.\n useEffect(() => {\n if (entryRef.current) entryRef.current.dismissed = false;\n });\n\n useEffect(() => {\n if (!open || !enabled) return;\n\n ensureEscapeListener();\n const entry: EscapeLayer = { id, dismissed: false, onEscape: () => onEscapeRef.current() };\n entryRef.current = entry;\n escapeStack.push(entry);\n\n return () => {\n entryRef.current = null;\n removeEscapeLayer(id);\n };\n }, [enabled, id, open]);\n}\n\n/**\n * Registers a running guided tour while `active`, so floating layers lift above\n * its dim. Symmetric with `useModalStack`, but a count rather than a stack: the\n * tour overlay does not nest.\n */\nexport function useTourLayer(active: boolean): void {\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n useEffect(() => {\n if (!active) return;\n tourLayers += 1;\n emit();\n return () => {\n tourLayers -= 1;\n emit();\n };\n }, [active]);\n}\n\n/**\n * z-index a floating layer (dropdown / combobox / tooltip / popover) should use\n * so it renders above any open modal — and below the next nested modal — or at\n * the popover floor when no modal is open. While a guided tour runs, the value\n * also clears the tour's dim and step card, so a step that says \"open this menu\"\n * shows the menu rather than hiding it under the overlay. Subscribes to the\n * modal stack so the value tracks modals and tours opening and closing.\n */\nexport function useFloatingZIndex(): number {\n useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n const base =\n stack.length === 0\n ? zIndex.popover\n : zIndex.modal + (stack.length - 1) * zIndex.modalStep + zIndex.popoverOverModalOffset;\n if (tourLayers === 0) return base;\n return Math.max(base, zIndex.tour + zIndex.tourCardOffset + zIndex.popoverOverModalOffset);\n}\n"],"names":["useId","useSyncExternalStore","useEffect","useRef","zIndex"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"modalStack.d.ts","sourceRoot":"","sources":["../../src/overlay/modalStack.ts"],"names":[],"mappings":"AAqEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,oFAAoF;IACpF,KAAK,EAAE,OAAO,CAAC;IACf,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CAoB/D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,UAAO,QAgBjF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAYlD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C"}
1
+ {"version":3,"file":"modalStack.d.ts","sourceRoot":"","sources":["../../src/overlay/modalStack.ts"],"names":[],"mappings":"AAyEA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,oFAAoF;IACpF,KAAK,EAAE,OAAO,CAAC;IACf,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,kBAAkB,CAoB/D;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,UAAO,QAwBjF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAYlD;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C"}
@@ -21,11 +21,12 @@ function onEscapeKeyDown(event) {
21
21
  if (event.defaultPrevented || event.key !== "Escape" || event.isComposing || event.keyCode === 229) {
22
22
  return;
23
23
  }
24
- const top = escapeStack[escapeStack.length - 1];
24
+ const top = escapeStack.findLast((entry) => !entry.dismissed);
25
25
  if (!top) return;
26
26
  event.preventDefault();
27
27
  event.stopPropagation();
28
28
  event.stopImmediatePropagation();
29
+ top.dismissed = true;
29
30
  top.onEscape();
30
31
  }
31
32
  function ensureEscapeListener() {
@@ -64,14 +65,20 @@ function useEscapeLayer(open, onEscape, enabled = true) {
64
65
  const id = useId();
65
66
  const onEscapeRef = useRef(onEscape);
66
67
  onEscapeRef.current = onEscape;
68
+ const entryRef = useRef(null);
69
+ useEffect(() => {
70
+ if (entryRef.current) entryRef.current.dismissed = false;
71
+ });
67
72
  useEffect(() => {
68
73
  if (!open || !enabled) return;
69
74
  ensureEscapeListener();
70
- escapeStack.push({
71
- id,
72
- onEscape: () => onEscapeRef.current()
73
- });
74
- return () => removeEscapeLayer(id);
75
+ const entry = { id, dismissed: false, onEscape: () => onEscapeRef.current() };
76
+ entryRef.current = entry;
77
+ escapeStack.push(entry);
78
+ return () => {
79
+ entryRef.current = null;
80
+ removeEscapeLayer(id);
81
+ };
75
82
  }, [enabled, id, open]);
76
83
  }
77
84
  function useTourLayer(active) {