@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
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
5
  const button = require("../../components/button.cjs");
6
6
  const utils = require("../../lib/utils.cjs");
7
+ const AccessMark = require("../AccessMark.cjs");
7
8
  const GraphDiagram = require("../GraphDiagram.cjs");
8
9
  const callGraphExclude = require("./call-graph-exclude.cjs");
9
10
  const callGraphModel = require("./call-graph-model.cjs");
@@ -26,7 +27,7 @@ function Alert({ message }) {
26
27
  if (!message) return null;
27
28
  return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "alert", className: "whitespace-pre-wrap rounded-md border border-destructive p-3 text-sm text-destructive", children: message });
28
29
  }
29
- const ACCESS_WORDS = { call: "Calls", read: "Reads", write: "Writes" };
30
+ const ACCESS_WORDS = { call: "Calls", read: "Reads", write: "Writes", variable: "Variables" };
30
31
  function accessWords(access) {
31
32
  return access.map((type) => ACCESS_WORDS[type]).join(" and ");
32
33
  }
@@ -43,7 +44,11 @@ function CallGraph(props) {
43
44
  const [exclude, setExclude] = useCallGraph.useHeld(props.exclude, props.onExcludeChange);
44
45
  const [access, setAccess] = useCallGraph.useHeld(props.access ?? [...types.CALL_GRAPH_ACCESS], props.onAccessChange);
45
46
  const [columns, setColumns] = useCallGraph.useHeld(props.columns ?? false, props.onColumnsChange);
46
- if (access.length === 0) throw new Error("call graph: access must name at least one of call, read and write");
47
+ const [collapsedGroups, setCollapsedGroups] = useCallGraph.useHeld(props.collapsedGroups ?? [], props.onCollapsedGroupsChange);
48
+ if (callGraphModel.baseAccess(access).length === 0) throw new Error("call graph: access must name at least one of call, read and write");
49
+ if (access.includes("variable") && !(props.dataAccess && props.variableAccess)) {
50
+ throw new Error("call graph: access names variable, but the host does not offer variableAccess");
51
+ }
47
52
  const [ownSelection, setOwnSelection] = react.useState();
48
53
  const request = root === void 0 ? void 0 : { root, direction, depth, exclude, access, columns, purpose: "load" };
49
54
  const key = `call-graph:${JSON.stringify([request ?? null, props.fetchKey ?? null])}`;
@@ -76,36 +81,42 @@ function CallGraph(props) {
76
81
  const glyphs = react.useMemo(() => ({
77
82
  node: (glyph, words) => /* @__PURE__ */ jsxRuntime.jsx(CallGraphGlyphs.NodeGlyphIcon, { vocabulary, glyph, title: words }),
78
83
  edge: (type) => type === "call" ? void 0 : /* @__PURE__ */ jsxRuntime.jsx(CallGraphGlyphs.EdgeTypeGlyph, { type, title: labels[type] }),
79
- group: (caption) => /* @__PURE__ */ jsxRuntime.jsx(CallGraphGlyphs.GroupCaption, { icon: vocabulary.group.icon, caption })
84
+ group: (caption, glyph) => /* @__PURE__ */ jsxRuntime.jsx(CallGraphGlyphs.GroupCaption, { icon: glyph === void 0 ? vocabulary.group.icon : callGraphVocabulary.requireGlyph(vocabulary, glyph).icon, caption }),
85
+ access: (access2) => /* @__PURE__ */ jsxRuntime.jsx(AccessMark.AccessMark, { access: access2 })
80
86
  }), [vocabulary, labels.dispatch, labels.read, labels.write]);
81
87
  const guardLabel = props.guardLabel ?? "innermost";
82
88
  const diagram = react.useMemo(
83
- () => graph && visible && callGraphModel.toDiagram({ graph, visible, direction, options: { ...OPTIONS, guardLabel }, glyphs, vocabulary, edgeLabels: labels }),
84
- [graph, visible, direction, guardLabel, glyphs, vocabulary, labels.call, labels.dispatch, labels.read, labels.write]
89
+ () => graph && visible && callGraphModel.toDiagram({ graph, visible, direction, options: { ...OPTIONS, guardLabel }, glyphs, vocabulary, edgeLabels: labels, collapsed: collapsedGroups }),
90
+ [graph, visible, direction, guardLabel, glyphs, vocabulary, labels.call, labels.dispatch, labels.read, labels.write, collapsedGroups]
85
91
  );
92
+ const drawn = diagram == null ? void 0 : diagram.drawn;
93
+ const collapsible = react.useMemo(() => graph && visible ? callGraphModel.collapsibleGroups(graph, visible) : [], [graph, visible]);
94
+ const toggleGroup = (id) => setCollapsedGroups(collapsedGroups.includes(id) ? collapsedGroups.filter((entry) => entry !== id) : [...collapsedGroups, id]);
86
95
  const rootId = graph == null ? void 0 : graph.roots[0];
87
96
  const selection = props.onSelectedChange ? props.selected : (ownSelection == null ? void 0 : ownSelection.key) === (shown == null ? void 0 : shown.key) ? ownSelection == null ? void 0 : ownSelection.selected : void 0;
88
97
  const select = (next) => {
89
98
  if (props.onSelectedChange) props.onSelectedChange(next);
90
99
  else setOwnSelection(next && shown ? { key: shown.key, selected: next } : void 0);
91
100
  };
92
- const openAction = (node, drawn) => {
101
+ const openAction = (node, drawn2) => {
93
102
  var _a2;
94
103
  const { onOpenNode, canOpenNode } = props;
95
- const accepted = onOpenNode && ((canOpenNode == null ? void 0 : canOpenNode(node, drawn)) ?? true) ? () => onOpenNode(node, drawn) : void 0;
96
- return action((_a2 = props.nodeHref) == null ? void 0 : _a2.call(props, node, drawn), accepted);
104
+ const accepted = onOpenNode && ((canOpenNode == null ? void 0 : canOpenNode(node, drawn2)) ?? true) ? () => onOpenNode(node, drawn2) : void 0;
105
+ return action((_a2 = props.nodeHref) == null ? void 0 : _a2.call(props, node, drawn2), accepted);
97
106
  };
98
- const selectedNode = (selection == null ? void 0 : selection.kind) === "node" ? visible == null ? void 0 : visible.nodes.find((node) => node.id === selection.id) : void 0;
99
- const selectedEdge = (selection == null ? void 0 : selection.kind) === "edge" ? visible == null ? void 0 : visible.edges.find((edge) => edge.id === selection.id) : void 0;
100
- const guards = useCallGraph.useSiteGuards(props.loadSiteGuards, shown == null ? void 0 : shown.key, selectedEdge, graph);
107
+ const selectedNode = (selection == null ? void 0 : selection.kind) === "node" ? drawn == null ? void 0 : drawn.visible.nodes.find((node) => node.id === selection.id) : void 0;
108
+ const selectedEdge = (selection == null ? void 0 : selection.kind) === "edge" ? drawn == null ? void 0 : drawn.visible.edges.find((edge) => edge.id === selection.id) : void 0;
109
+ const mergedEdge = selectedEdge !== void 0 && (drawn == null ? void 0 : drawn.merged.has(selectedEdge.id)) === true;
110
+ const standIn = selectedNode !== void 0 && (drawn == null ? void 0 : drawn.folds.has(selectedNode.id)) === true;
111
+ const guards = useCallGraph.useSiteGuards(props.loadSiteGuards, shown == null ? void 0 : shown.key, mergedEdge ? void 0 : selectedEdge, graph);
101
112
  const expansion = (expanding == null ? void 0 : expanding.request.key) === key ? expanding : void 0;
102
- const expand = async (drawn, id) => {
113
+ const expand = async (drawn2, id) => {
103
114
  var _a2, _b2;
104
- if (drawn.key !== activeKey.current) return;
105
- if (((_a2 = pendingExpansion.current) == null ? void 0 : _a2.key) === drawn.key && pendingExpansion.current.nodeId === id) return;
115
+ if (drawn2.key !== activeKey.current) return;
116
+ if (((_a2 = pendingExpansion.current) == null ? void 0 : _a2.key) === drawn2.key && pendingExpansion.current.nodeId === id) return;
106
117
  (_b2 = pendingExpansion.current) == null ? void 0 : _b2.controller.abort();
107
- const node = callGraphModel.requireNode(drawn.graph, id);
108
- const request2 = { key: drawn.key, nodeId: id, label: node.label, controller: new AbortController() };
118
+ const node = callGraphModel.requireNode(drawn2.graph, id);
119
+ const request2 = { key: drawn2.key, nodeId: id, label: node.label, controller: new AbortController() };
109
120
  pendingExpansion.current = request2;
110
121
  setExpanding({ request: request2 });
111
122
  try {
@@ -119,15 +130,15 @@ function CallGraph(props) {
119
130
  purpose: "expand",
120
131
  signal: request2.controller.signal
121
132
  });
122
- if (request2.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request2) return;
133
+ if (request2.controller.signal.aborted || activeKey.current !== drawn2.key || pendingExpansion.current !== request2) return;
123
134
  setExpanded((held) => {
124
- if (request2.controller.signal.aborted || activeKey.current !== drawn.key) return held;
125
- const base = (held == null ? void 0 : held.key) === drawn.key ? held : drawn;
126
- return { key: drawn.key, graph: callGraphModel.mergeGraph(base.graph, data), revealed: [...base.revealed, ...data.nodes.map((entry) => entry.id)] };
135
+ if (request2.controller.signal.aborted || activeKey.current !== drawn2.key) return held;
136
+ const base = (held == null ? void 0 : held.key) === drawn2.key ? held : drawn2;
137
+ return { key: drawn2.key, graph: callGraphModel.mergeGraph(base.graph, data), revealed: [...base.revealed, ...data.nodes.map((entry) => entry.id)] };
127
138
  });
128
139
  setExpanding((current) => (current == null ? void 0 : current.request) === request2 ? void 0 : current);
129
140
  } catch (error) {
130
- if (request2.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request2) return;
141
+ if (request2.controller.signal.aborted || activeKey.current !== drawn2.key || pendingExpansion.current !== request2) return;
131
142
  setExpanding((current) => (current == null ? void 0 : current.request) === request2 ? { request: request2, error: `Cannot expand ${node.label}: ${useCallGraph.errorMessage(error)}` } : current);
132
143
  } finally {
133
144
  if (pendingExpansion.current === request2) pendingExpansion.current = void 0;
@@ -155,7 +166,13 @@ function CallGraph(props) {
155
166
  onExclude: (patterns) => setExclude(patterns),
156
167
  onDefaults: () => setExclude(void 0)
157
168
  } } : {},
158
- ...props.dataAccess ? { data: { access, onAccess: setAccess, columns, onColumns: setColumns } } : {},
169
+ ...props.dataAccess ? { data: { access, onAccess: setAccess, columns, onColumns: setColumns, variables: props.variableAccess ?? false } } : {},
170
+ ...collapsible.length > 0 ? { groupCollapse: {
171
+ anyCollapsed: collapsedGroups.some((id) => collapsible.includes(id)),
172
+ anyExpanded: collapsible.some((id) => !collapsedGroups.includes(id)),
173
+ onExpandAll: () => setCollapsedGroups([]),
174
+ onCollapseAll: () => setCollapsedGroups(collapsible)
175
+ } } : {},
159
176
  onDirection: setDirection,
160
177
  onDepth: setDepth,
161
178
  onFit: () => setOpening({ nonce: opening.nonce + 1, fit: true }),
@@ -176,7 +193,7 @@ function CallGraph(props) {
176
193
  graph && ((_b = props.renderHeader) == null ? void 0 : _b.call(props, graph)),
177
194
  !shown && load.loading && /* @__PURE__ */ jsxRuntime.jsx(Notice, { children: "Loading call graph…" }),
178
195
  graph && !rootId && /* @__PURE__ */ jsxRuntime.jsx(Notice, { children: "The root resolved to no node in this scope." }),
179
- graph && rootId && graph.edges.length === 0 && access.length < types.CALL_GRAPH_ACCESS.length && /* @__PURE__ */ jsxRuntime.jsx(Notice, { children: `Nothing is reachable with ${accessWords(access)} only.` }),
196
+ graph && rootId && graph.edges.length === 0 && callGraphModel.baseAccess(access).length < types.CALL_GRAPH_ACCESS.length && /* @__PURE__ */ jsxRuntime.jsx(Notice, { children: `Nothing is reachable with ${accessWords(callGraphModel.baseAccess(access))} only.` }),
180
197
  graph && rootId && graph.edges.length > 0 && !selectedEdge && !selectedNode && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Select an edge to list its sites, or a node to see its details." }),
181
198
  shown && graph && rootId && diagram && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-3", children: [
182
199
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils.cn("relative min-h-64 min-w-0 flex-1 rounded-lg border border-border bg-background", load.loading && "opacity-60"), children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -194,10 +211,12 @@ function CallGraph(props) {
194
211
  nodeWidth: NODE_WIDTH,
195
212
  nodeHeight: NODE_HEIGHT,
196
213
  compactNodeHeight: COMPACT_NODE_HEIGHT,
214
+ recordHeaderHeight: 24,
197
215
  columnGap: callGraphModel.columnGap(diagram),
198
216
  rowGap: ROW_GAP,
199
217
  className: "h-full",
200
218
  ariaLabel: `Call graph of ${callGraphModel.requireNode(graph, rootId).label}`,
219
+ onGroupToggle: toggleGroup,
201
220
  onNodeSelect: (id) => select({ kind: "node", id }),
202
221
  onEdgeSelect: (id) => select({ kind: "edge", id }),
203
222
  ...fresh ? { onNodeExpand: (id) => void expand(fresh, id) } : {},
@@ -209,15 +228,15 @@ function CallGraph(props) {
209
228
  (selectedEdge || selectedNode || expansion) && /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "w-80 max-w-[45%] shrink-0 overflow-y-auto rounded-lg border border-border bg-card p-3", "aria-label": "Selection", children: [
210
229
  expansion && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", className: "mb-2 text-xs text-muted-foreground", children: expansion.error ?? `Loading more around ${expansion.request.label}…` }),
211
230
  (selectedEdge || selectedNode) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-1 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(button.Button, { type: "button", size: "sm", variant: "ghost", "aria-label": "Close selection", onClick: () => select(void 0), children: "×" }) }),
212
- selectedEdge ? /* @__PURE__ */ jsxRuntime.jsx(
231
+ selectedEdge && drawn ? /* @__PURE__ */ jsxRuntime.jsx(
213
232
  CallGraphParts.EdgeSites,
214
233
  {
215
234
  edge: selectedEdge,
216
- graph,
235
+ graph: drawn.graph,
217
236
  labels,
218
237
  guards,
219
238
  lazyGuards: props.loadSiteGuards !== void 0,
220
- siteAction: (props.siteHref || props.onRevealSite) && vocabulary.hasSource(callGraphModel.requireNode(graph, selectedEdge.from)) ? (site) => {
239
+ siteAction: (props.siteHref || props.onRevealSite) && !drawn.folds.has(selectedEdge.from) && vocabulary.hasSource(callGraphModel.requireNode(graph, selectedEdge.from)) ? (site) => {
221
240
  var _a2;
222
241
  return action((_a2 = props.siteHref) == null ? void 0 : _a2.call(props, site, selectedEdge, graph), props.onRevealSite && (() => {
223
242
  var _a3;
@@ -225,17 +244,17 @@ function CallGraph(props) {
225
244
  })) ?? {};
226
245
  } : void 0
227
246
  }
228
- ) : selectedNode && /* @__PURE__ */ jsxRuntime.jsx(
247
+ ) : selectedNode && drawn && /* @__PURE__ */ jsxRuntime.jsx(
229
248
  CallGraphParts.NodeDetails,
230
249
  {
231
250
  node: selectedNode,
232
- graph,
251
+ graph: drawn.graph,
233
252
  vocabulary,
234
253
  isRoot: selectedNode.id === rootId,
235
254
  openLabel: props.openLabel ?? "Open",
236
- opens: props.nodeHref !== void 0 || props.onOpenNode !== void 0,
237
- open: openAction(selectedNode, graph),
238
- onFocus: props.onFocusNode && ((id) => {
255
+ opens: !standIn && (props.nodeHref !== void 0 || props.onOpenNode !== void 0),
256
+ open: standIn ? void 0 : openAction(selectedNode, graph),
257
+ onFocus: standIn ? void 0 : props.onFocusNode && ((id) => {
239
258
  var _a2;
240
259
  return (_a2 = props.onFocusNode) == null ? void 0 : _a2.call(props, id, graph);
241
260
  })
@@ -1 +1 @@
1
- {"version":3,"file":"CallGraph.cjs","sources":["../../../src/data/call-graph/CallGraph.tsx"],"sourcesContent":["// A call graph around one root: fetched through the host, with `+N` expansions merged in, the call\n// sites of a selected edge (guards fetched on selection when the host's graph leaves them out) and the\n// details of a selected node. It knows no language: the host's vocabulary names and draws node kinds,\n// and the host's callbacks open nodes and sites.\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { cn } from \"../../lib/utils\";\nimport { GraphDiagram } from \"../GraphDiagram\";\nimport { matchesExclude, type ExcludeMatcher } from \"./call-graph-exclude\";\nimport type { GuardLabel } from \"./call-graph-labels\";\nimport {\n CALL_GRAPH_DEFAULT_DEPTH,\n CALL_GRAPH_MAX_DEPTH,\n columnGap,\n DEFAULT_EDGE_LABELS,\n expansionDirection,\n mergeGraph,\n requireNode,\n toDiagram,\n visibleGraph,\n type DiagramGlyphs,\n type DiagramOptions,\n} from \"./call-graph-model\";\nimport { UIR_CALL_GRAPH_VOCABULARY, type CallGraphVocabulary } from \"./call-graph-vocabulary\";\nimport { EdgeTypeGlyph, GroupCaption, NodeGlyphIcon } from \"./CallGraphGlyphs\";\nimport { EdgeSites, Legend, NodeDetails, type CallGraphAction } from \"./CallGraphParts\";\nimport { CallGraphToolbar } from \"./CallGraphToolbar\";\nimport {\n CALL_GRAPH_ACCESS,\n type CallGraph as Graph,\n type CallGraphAccess,\n type CallGraphDirection,\n type CallGraphEdge,\n type CallGraphEdgeLabels,\n type CallGraphFetchParams,\n type CallGraphGroupFact,\n type CallGraphNode,\n type CallGraphSelection,\n type CallGraphSite,\n} from \"./types\";\nimport { errorMessage, useGraphLoad, useHeld, useSiteGuards, type GraphRequest } from \"./use-call-graph\";\n\nexport interface CallGraphProps<G extends Graph = Graph> {\n /** What the graph is drawn around, in the host's terms. Undefined shows `emptyMessage`. */\n root: string | undefined;\n /** Loads the graph around `params.root`, and one hop around a node for its `+N`. */\n fetchGraph: (params: CallGraphFetchParams) => Promise<G>;\n /** What else `fetchGraph` reads, such as the host's scope: part of the request's key, so a change refetches. */\n fetchKey?: string;\n /** Pass `onDirectionChange` to control it; without one the component holds it, starting here. */\n direction?: CallGraphDirection;\n onDirectionChange?: (direction: CallGraphDirection) => void;\n depth?: number;\n onDepthChange?: (depth: number) => void;\n /** The exclusion patterns; undefined takes the host's defaults. Controlled with `onExcludeChange`. */\n exclude?: string[] | undefined;\n onExcludeChange?: (exclude: string[] | undefined) => void;\n selected?: CallGraphSelection | undefined;\n onSelectedChange?: (selected: CallGraphSelection | undefined) => void;\n /** The source reports reads and writes of data: shows the Access toggles and the Columns switch. */\n dataAccess?: boolean;\n /** The edge types the host follows, never empty; all three by default. Controlled with `onAccessChange`. */\n access?: CallGraphAccess[];\n onAccessChange?: (access: CallGraphAccess[]) => void;\n /** Columns and fields as nodes of their own; collapsed into tables and entities by default. */\n columns?: boolean;\n onColumnsChange?: (columns: boolean) => void;\n /** The groups the graph reached, drawn or excluded. Without it the graph offers no exclusions. */\n groupFacts?: (graph: G) => CallGraphGroupFact[];\n /** The patterns in force when `exclude` is undefined: the host's defaults, as its response reports them. */\n defaultExclude?: (graph: G) => string[];\n /** How a pattern matches a group, for a host with keywords of its own. */\n excludeMatcher?: ExcludeMatcher;\n /**\n * Where a node opens. A node the vocabulary gives no source is never opened, and Open is disabled on\n * a node with no href that `onOpenNode` does not accept.\n */\n nodeHref?: (node: CallGraphNode, graph: G) => string | undefined;\n /** Opens a node; with `nodeHref` it handles a plain click on the link. */\n onOpenNode?: (node: CallGraphNode, graph: G) => void;\n /** Whether `onOpenNode` opens a node; every node by default. */\n canOpenNode?: (node: CallGraphNode, graph: G) => boolean;\n openLabel?: string;\n /** Re-roots the graph on a node, by id: a drawn node the vocabulary can root, or the column behind a field. */\n onFocusNode?: (nodeId: string, graph: G) => void;\n /** Where a call site opens; a site opens only when its caller has a source. */\n siteHref?: (site: CallGraphSite, edge: CallGraphEdge, graph: G) => string | undefined;\n onRevealSite?: (site: CallGraphSite, edge: CallGraphEdge, graph: G) => void;\n /** Fetches the guards of a selected edge's sites, for a host whose graph leaves them out. */\n loadSiteGuards?: (edge: CallGraphEdge, graph: G, signal: AbortSignal) => Promise<CallGraphSite[]>;\n edgeLabels?: Partial<CallGraphEdgeLabels>;\n /** What a conditional edge's pill says of its guards; \"innermost\" by default. The edge's tooltip always lists them. */\n guardLabel?: GuardLabel;\n vocabulary?: CallGraphVocabulary;\n /** Shows a Pin root toggle; the host keeps `root` while pinned. */\n pinned?: boolean;\n onPinnedChange?: (pinned: boolean, rootId: string | undefined) => void;\n /** Host facts above the diagram, such as how the root was resolved. */\n renderHeader?: (graph: G) => ReactNode;\n toolbarEnd?: ReactNode;\n emptyMessage?: ReactNode;\n maxDepth?: number;\n className?: string;\n}\n\ntype Held<G> = { key: string; graph: G; revealed: string[] };\ntype PendingExpansion = { key: string; nodeId: string; label: string; controller: AbortController };\n\nconst NODE_WIDTH = 188;\nconst NODE_HEIGHT = 32;\nconst COMPACT_NODE_HEIGHT = 22;\nconst ROW_GAP = 14;\n/** Node labels are 12px: at this scale they are 7.2px, the smallest that still reads. */\nconst READABLE_SCALE = 0.6;\nconst OPTIONS: Omit<DiagramOptions, \"guardLabel\"> = { truncateAt: 32, grouped: true };\n\nfunction Notice({ children }: { children: ReactNode }) {\n return (\n <div className=\"flex h-full items-center justify-center p-6 text-center\">\n <span className=\"text-sm text-muted-foreground\">{children}</span>\n </div>\n );\n}\n\nfunction Alert({ message }: { message: string | undefined }) {\n if (!message) return null;\n return <div role=\"alert\" className=\"whitespace-pre-wrap rounded-md border border-destructive p-3 text-sm text-destructive\">{message}</div>;\n}\n\nconst ACCESS_WORDS: Record<CallGraphAccess, string> = { call: \"Calls\", read: \"Reads\", write: \"Writes\" };\n\nfunction accessWords(access: readonly CallGraphAccess[]): string {\n return access.map((type) => ACCESS_WORDS[type]).join(\" and \");\n}\n\nfunction action(href: string | undefined, onSelect: (() => void) | undefined): CallGraphAction | undefined {\n if (href === undefined && onSelect === undefined) return undefined;\n return { ...(href !== undefined ? { href } : {}), ...(onSelect ? { onSelect } : {}) };\n}\n\nexport function CallGraph<G extends Graph = Graph>(props: CallGraphProps<G>) {\n const { root, fetchGraph, vocabulary = UIR_CALL_GRAPH_VOCABULARY, maxDepth = CALL_GRAPH_MAX_DEPTH } = props;\n const labels: CallGraphEdgeLabels = { ...DEFAULT_EDGE_LABELS, ...props.edgeLabels };\n const [direction, setDirection] = useHeld(props.direction ?? \"both\", props.onDirectionChange);\n const [depth, setDepth] = useHeld(props.depth ?? CALL_GRAPH_DEFAULT_DEPTH, props.onDepthChange);\n const [exclude, setExclude] = useHeld(props.exclude, props.onExcludeChange);\n const [access, setAccess] = useHeld(props.access ?? [...CALL_GRAPH_ACCESS], props.onAccessChange);\n const [columns, setColumns] = useHeld(props.columns ?? false, props.onColumnsChange);\n if (access.length === 0) throw new Error(\"call graph: access must name at least one of call, read and write\");\n const [ownSelection, setOwnSelection] = useState<{ key: string; selected: CallGraphSelection }>();\n\n const request: GraphRequest | undefined = root === undefined ? undefined : { root, direction, depth, exclude, access, columns, purpose: \"load\" };\n const key = `call-graph:${JSON.stringify([request ?? null, props.fetchKey ?? null])}`;\n const load = useGraphLoad(fetchGraph, request, key);\n\n const [expanded, setExpanded] = useState<Held<G>>();\n const [expanding, setExpanding] = useState<{ request: PendingExpansion; error?: string }>();\n const pendingExpansion = useRef<PendingExpansion | undefined>(undefined);\n const activeKey = useRef(key);\n activeKey.current = key;\n useEffect(() => {\n setExpanding((current) => current?.request.key === key && !current.request.controller.signal.aborted ? current : undefined);\n return () => {\n if (pendingExpansion.current?.key === key) {\n pendingExpansion.current.controller.abort();\n pendingExpansion.current = undefined;\n }\n };\n }, [key]);\n const [opening, setOpening] = useState({ nonce: 0, fit: false });\n const fresh = useMemo<Held<G> | undefined>(\n () => load.fresh && (expanded?.key === key ? expanded : { key, graph: load.fresh, revealed: [] }),\n [load.fresh, expanded, key],\n );\n const [last, setLast] = useState<Held<G>>();\n if (fresh && fresh !== last) setLast(fresh);\n const shown = root === undefined ? undefined : (fresh ?? last);\n const graph = shown?.graph;\n\n const visible = useMemo(() => shown && visibleGraph(shown.graph, { direction, depth, revealed: shown.revealed }), [shown, direction, depth]);\n const glyphs = useMemo<DiagramGlyphs>(() => ({\n node: (glyph, words) => <NodeGlyphIcon vocabulary={vocabulary} glyph={glyph} title={words} />,\n edge: (type) => (type === \"call\" ? undefined : <EdgeTypeGlyph type={type} title={labels[type]} />),\n group: (caption) => <GroupCaption icon={vocabulary.group.icon} caption={caption} />,\n }), [vocabulary, labels.dispatch, labels.read, labels.write]);\n const guardLabel = props.guardLabel ?? \"innermost\";\n const diagram = useMemo(\n () => graph && visible && toDiagram({ graph, visible, direction, options: { ...OPTIONS, guardLabel }, glyphs, vocabulary, edgeLabels: labels }),\n [graph, visible, direction, guardLabel, glyphs, vocabulary, labels.call, labels.dispatch, labels.read, labels.write],\n );\n\n const rootId = graph?.roots[0];\n const selection = props.onSelectedChange ? props.selected : ownSelection?.key === shown?.key ? ownSelection?.selected : undefined;\n const select = (next: CallGraphSelection | undefined) => {\n if (props.onSelectedChange) props.onSelectedChange(next);\n else setOwnSelection(next && shown ? { key: shown.key, selected: next } : undefined);\n };\n const openAction = (node: CallGraphNode, drawn: G) => {\n const { onOpenNode, canOpenNode } = props;\n const accepted = onOpenNode && (canOpenNode?.(node, drawn) ?? true) ? () => onOpenNode(node, drawn) : undefined;\n return action(props.nodeHref?.(node, drawn), accepted);\n };\n const selectedNode = selection?.kind === \"node\" ? visible?.nodes.find((node) => node.id === selection.id) : undefined;\n const selectedEdge = selection?.kind === \"edge\" ? visible?.edges.find((edge) => edge.id === selection.id) : undefined;\n const guards = useSiteGuards(props.loadSiteGuards, shown?.key, selectedEdge, graph);\n const expansion = expanding?.request.key === key ? expanding : undefined;\n\n // A `+N` loads one more hop on the node's outer side and merges it into the graph this request drew.\n const expand = async (drawn: Held<G>, id: string) => {\n if (drawn.key !== activeKey.current) return;\n if (pendingExpansion.current?.key === drawn.key && pendingExpansion.current.nodeId === id) return;\n pendingExpansion.current?.controller.abort();\n const node = requireNode(drawn.graph, id);\n const request: PendingExpansion = { key: drawn.key, nodeId: id, label: node.label, controller: new AbortController() };\n pendingExpansion.current = request;\n setExpanding({ request });\n try {\n const data = await fetchGraph({\n root: id, direction: expansionDirection(node), depth: 1, exclude, access, columns, purpose: \"expand\", signal: request.controller.signal,\n });\n if (request.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request) return;\n setExpanded((held) => {\n if (request.controller.signal.aborted || activeKey.current !== drawn.key) return held;\n const base = held?.key === drawn.key ? held : drawn;\n return { key: drawn.key, graph: mergeGraph(base.graph, data), revealed: [...base.revealed, ...data.nodes.map((entry) => entry.id)] };\n });\n setExpanding((current) => current?.request === request ? undefined : current);\n } catch (error) {\n if (request.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request) return;\n setExpanding((current) => current?.request === request\n ? { request, error: `Cannot expand ${node.label}: ${errorMessage(error)}` } : current);\n } finally {\n if (pendingExpansion.current === request) pendingExpansion.current = undefined;\n }\n };\n\n const groups = graph && props.groupFacts?.(graph);\n const effectiveExclude = exclude ?? (graph && props.defaultExclude ? props.defaultExclude(graph) : []);\n const { onPinnedChange } = props;\n const toolbar = (\n <CallGraphToolbar direction={direction} depth={depth} maxDepth={maxDepth} omitted={graph?.omitted} end={props.toolbarEnd} vocabulary={vocabulary}\n {...(onPinnedChange ? { pin: { pinned: props.pinned ?? false, canPin: rootId !== undefined, onPin: (pinned: boolean) => onPinnedChange(pinned, rootId) } } : {})}\n {...(groups ? { exclusions: { exclude: effectiveExclude, groups, matches: props.excludeMatcher ?? matchesExclude, busy: load.loading, vocabulary,\n onExclude: (patterns: string[]) => setExclude(patterns), onDefaults: () => setExclude(undefined) } } : {})}\n {...(props.dataAccess ? { data: { access, onAccess: setAccess, columns, onColumns: setColumns } } : {})}\n onDirection={setDirection} onDepth={setDepth}\n onFit={() => setOpening({ nonce: opening.nonce + 1, fit: true })} onReset={() => setOpening({ nonce: opening.nonce + 1, fit: false })} />\n );\n\n const frame = cn(\"flex h-full min-h-0 flex-col gap-2 p-2\", props.className);\n if (root === undefined) {\n return <div className={frame}>{toolbar}<Notice>{props.emptyMessage ?? \"Select a node to draw its call graph.\"}</Notice></div>;\n }\n const loadError = load.error && `Cannot draw the call graph: ${load.error}`;\n return (\n <div className={frame} aria-busy={load.loading}>\n {toolbar}\n <Alert message={loadError} />\n {graph && props.renderHeader?.(graph)}\n {!shown && load.loading && <Notice>Loading call graph…</Notice>}\n {graph && !rootId && <Notice>The root resolved to no node in this scope.</Notice>}\n {graph && rootId && graph.edges.length === 0 && access.length < CALL_GRAPH_ACCESS.length && (\n <Notice>{`Nothing is reachable with ${accessWords(access)} only.`}</Notice>\n )}\n {graph && rootId && graph.edges.length > 0 && !selectedEdge && !selectedNode && (\n <p className=\"text-xs text-muted-foreground\">Select an edge to list its sites, or a node to see its details.</p>\n )}\n {shown && graph && rootId && diagram && (\n <div className=\"flex min-h-0 flex-1 gap-3\">\n <div className={cn(\"relative min-h-64 min-w-0 flex-1 rounded-lg border border-border bg-background\", load.loading && \"opacity-60\")}>\n <GraphDiagram key={`${shown.key}:${opening.nonce}`} nodes={diagram.nodes} edges={diagram.edges} groups={diagram.groups} layout=\"columns\" zoomable wheelZoom=\"zoom\" edgeFocus=\"auto\"\n focusId={rootId} {...(opening.fit ? {} : { fitMinScale: READABLE_SCALE })} nodeWidth={NODE_WIDTH} nodeHeight={NODE_HEIGHT} compactNodeHeight={COMPACT_NODE_HEIGHT} columnGap={columnGap(diagram)} rowGap={ROW_GAP}\n className=\"h-full\" ariaLabel={`Call graph of ${requireNode(graph, rootId).label}`}\n onNodeSelect={(id: string) => select({ kind: \"node\", id })} onEdgeSelect={(id: string) => select({ kind: \"edge\", id })}\n {...(fresh ? { onNodeExpand: (id: string) => void expand(fresh, id) } : {})}\n {...(selectedNode ? { selectedId: selectedNode.id } : {})} {...(selectedEdge ? { selectedEdgeId: selectedEdge.id } : {})} />\n </div>\n {(selectedEdge || selectedNode || expansion) && (\n <aside className=\"w-80 max-w-[45%] shrink-0 overflow-y-auto rounded-lg border border-border bg-card p-3\" aria-label=\"Selection\">\n {expansion && <p role=\"status\" className=\"mb-2 text-xs text-muted-foreground\">{expansion.error ?? `Loading more around ${expansion.request.label}…`}</p>}\n {(selectedEdge || selectedNode) && (\n <div className=\"mb-1 flex justify-end\">\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" aria-label=\"Close selection\" onClick={() => select(undefined)}>×</Button>\n </div>\n )}\n {selectedEdge ? (\n <EdgeSites edge={selectedEdge} graph={graph} labels={labels} guards={guards} lazyGuards={props.loadSiteGuards !== undefined}\n siteAction={(props.siteHref || props.onRevealSite) && vocabulary.hasSource(requireNode(graph, selectedEdge.from))\n ? (site) => action(props.siteHref?.(site, selectedEdge, graph), props.onRevealSite && (() => props.onRevealSite?.(site, selectedEdge, graph))) ?? {}\n : undefined} />\n ) : selectedNode && (\n <NodeDetails node={selectedNode} graph={graph} vocabulary={vocabulary} isRoot={selectedNode.id === rootId} openLabel={props.openLabel ?? \"Open\"}\n opens={props.nodeHref !== undefined || props.onOpenNode !== undefined} open={openAction(selectedNode, graph)}\n onFocus={props.onFocusNode && ((id: string) => props.onFocusNode?.(id, graph))} />\n )}\n </aside>\n )}\n </div>\n )}\n {graph && rootId && <div className=\"shrink-0\"><Legend graph={graph} vocabulary={vocabulary} labels={labels} /></div>}\n </div>\n );\n}\n"],"names":["jsx","UIR_CALL_GRAPH_VOCABULARY","CALL_GRAPH_MAX_DEPTH","DEFAULT_EDGE_LABELS","useHeld","CALL_GRAPH_DEFAULT_DEPTH","CALL_GRAPH_ACCESS","useState","useGraphLoad","useRef","useEffect","_a","useMemo","visibleGraph","NodeGlyphIcon","EdgeTypeGlyph","GroupCaption","toDiagram","useSiteGuards","_b","requireNode","request","expansionDirection","mergeGraph","errorMessage","CallGraphToolbar","matchesExclude","cn","jsxs","GraphDiagram","columnGap","Button","EdgeSites","NodeDetails","Legend"],"mappings":"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"}
1
+ {"version":3,"file":"CallGraph.cjs","sources":["../../../src/data/call-graph/CallGraph.tsx"],"sourcesContent":["// A call graph around one root: fetched through the host, with `+N` expansions merged in, the call\n// sites of a selected edge (guards fetched on selection when the host's graph leaves them out) and the\n// details of a selected node. It knows no language: the host's vocabulary names and draws node kinds,\n// and the host's callbacks open nodes and sites.\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from \"react\";\nimport { Button } from \"../../components/button\";\nimport { cn } from \"../../lib/utils\";\nimport { AccessMark } from \"../AccessMark\";\nimport { GraphDiagram } from \"../GraphDiagram\";\nimport { matchesExclude, type ExcludeMatcher } from \"./call-graph-exclude\";\nimport type { GuardLabel } from \"./call-graph-labels\";\nimport {\n baseAccess,\n CALL_GRAPH_DEFAULT_DEPTH,\n CALL_GRAPH_MAX_DEPTH,\n collapsibleGroups,\n columnGap,\n DEFAULT_EDGE_LABELS,\n expansionDirection,\n mergeGraph,\n requireNode,\n toDiagram,\n visibleGraph,\n type DiagramGlyphs,\n type DiagramOptions,\n} from \"./call-graph-model\";\nimport { requireGlyph, UIR_CALL_GRAPH_VOCABULARY, type CallGraphVocabulary } from \"./call-graph-vocabulary\";\nimport { EdgeTypeGlyph, GroupCaption, NodeGlyphIcon } from \"./CallGraphGlyphs\";\nimport { EdgeSites, Legend, NodeDetails, type CallGraphAction } from \"./CallGraphParts\";\nimport { CallGraphToolbar } from \"./CallGraphToolbar\";\nimport {\n CALL_GRAPH_ACCESS,\n type CallGraph as Graph,\n type CallGraphAccess,\n type CallGraphDirection,\n type CallGraphEdge,\n type CallGraphEdgeLabels,\n type CallGraphFetchParams,\n type CallGraphGroupFact,\n type CallGraphNode,\n type CallGraphSelection,\n type CallGraphSite,\n} from \"./types\";\nimport { errorMessage, useGraphLoad, useHeld, useSiteGuards, type GraphRequest } from \"./use-call-graph\";\n\nexport interface CallGraphProps<G extends Graph = Graph> {\n /** What the graph is drawn around, in the host's terms. Undefined shows `emptyMessage`. */\n root: string | undefined;\n /** Loads the graph around `params.root`, and one hop around a node for its `+N`. */\n fetchGraph: (params: CallGraphFetchParams) => Promise<G>;\n /** What else `fetchGraph` reads, such as the host's scope: part of the request's key, so a change refetches. */\n fetchKey?: string;\n /** Pass `onDirectionChange` to control it; without one the component holds it, starting here. */\n direction?: CallGraphDirection;\n onDirectionChange?: (direction: CallGraphDirection) => void;\n depth?: number;\n onDepthChange?: (depth: number) => void;\n /** The exclusion patterns; undefined takes the host's defaults. Controlled with `onExcludeChange`. */\n exclude?: string[] | undefined;\n onExcludeChange?: (exclude: string[] | undefined) => void;\n selected?: CallGraphSelection | undefined;\n onSelectedChange?: (selected: CallGraphSelection | undefined) => void;\n /** The source reports reads and writes of data: shows the Access toggles and the Columns switch. */\n dataAccess?: boolean;\n /** The source can follow the variables a body reads and writes: shows a Variables toggle beside the Access ones. Needs `dataAccess`. */\n variableAccess?: boolean;\n /** The edge types the host follows, at least one of call, read and write; all three by default. Controlled with `onAccessChange`. */\n access?: CallGraphAccess[];\n onAccessChange?: (access: CallGraphAccess[]) => void;\n /** Columns and fields as nodes of their own; collapsed into tables and entities by default. */\n columns?: boolean;\n onColumnsChange?: (columns: boolean) => void;\n /**\n * The groups drawn as their header alone, by `graph.groups` id: each folds into one node, and its\n * members' edges merge onto it, counting the members. The root's group never folds. Controlled with\n * `onCollapsedGroupsChange`; without one the component holds it, starting here, none by default.\n */\n collapsedGroups?: string[];\n onCollapsedGroupsChange?: (ids: string[]) => void;\n /** The groups the graph reached, drawn or excluded. Without it the graph offers no exclusions. */\n groupFacts?: (graph: G) => CallGraphGroupFact[];\n /** The patterns in force when `exclude` is undefined: the host's defaults, as its response reports them. */\n defaultExclude?: (graph: G) => string[];\n /** How a pattern matches a group, for a host with keywords of its own. */\n excludeMatcher?: ExcludeMatcher;\n /**\n * Where a node opens. A node the vocabulary gives no source is never opened, and Open is disabled on\n * a node with no href that `onOpenNode` does not accept.\n */\n nodeHref?: (node: CallGraphNode, graph: G) => string | undefined;\n /** Opens a node; with `nodeHref` it handles a plain click on the link. */\n onOpenNode?: (node: CallGraphNode, graph: G) => void;\n /** Whether `onOpenNode` opens a node; every node by default. */\n canOpenNode?: (node: CallGraphNode, graph: G) => boolean;\n openLabel?: string;\n /** Re-roots the graph on a node, by id: a drawn node the vocabulary can root, or the column behind a field. */\n onFocusNode?: (nodeId: string, graph: G) => void;\n /** Where a call site opens; a site opens only when its caller has a source. */\n siteHref?: (site: CallGraphSite, edge: CallGraphEdge, graph: G) => string | undefined;\n onRevealSite?: (site: CallGraphSite, edge: CallGraphEdge, graph: G) => void;\n /** Fetches the guards of a selected edge's sites, for a host whose graph leaves them out. */\n loadSiteGuards?: (edge: CallGraphEdge, graph: G, signal: AbortSignal) => Promise<CallGraphSite[]>;\n edgeLabels?: Partial<CallGraphEdgeLabels>;\n /** What a conditional edge's pill says of its guards; \"innermost\" by default. The edge's tooltip always lists them. */\n guardLabel?: GuardLabel;\n vocabulary?: CallGraphVocabulary;\n /** Shows a Pin root toggle; the host keeps `root` while pinned. */\n pinned?: boolean;\n onPinnedChange?: (pinned: boolean, rootId: string | undefined) => void;\n /** Host facts above the diagram, such as how the root was resolved. */\n renderHeader?: (graph: G) => ReactNode;\n toolbarEnd?: ReactNode;\n emptyMessage?: ReactNode;\n maxDepth?: number;\n className?: string;\n}\n\ntype Held<G> = { key: string; graph: G; revealed: string[] };\ntype PendingExpansion = { key: string; nodeId: string; label: string; controller: AbortController };\n\nconst NODE_WIDTH = 188;\nconst NODE_HEIGHT = 32;\nconst COMPACT_NODE_HEIGHT = 22;\nconst ROW_GAP = 14;\n/** Node labels are 12px: at this scale they are 7.2px, the smallest that still reads. */\nconst READABLE_SCALE = 0.6;\nconst OPTIONS: Omit<DiagramOptions, \"guardLabel\"> = { truncateAt: 32, grouped: true };\n\nfunction Notice({ children }: { children: ReactNode }) {\n return (\n <div className=\"flex h-full items-center justify-center p-6 text-center\">\n <span className=\"text-sm text-muted-foreground\">{children}</span>\n </div>\n );\n}\n\nfunction Alert({ message }: { message: string | undefined }) {\n if (!message) return null;\n return <div role=\"alert\" className=\"whitespace-pre-wrap rounded-md border border-destructive p-3 text-sm text-destructive\">{message}</div>;\n}\n\nconst ACCESS_WORDS: Record<CallGraphAccess, string> = { call: \"Calls\", read: \"Reads\", write: \"Writes\", variable: \"Variables\" };\n\nfunction accessWords(access: readonly CallGraphAccess[]): string {\n return access.map((type) => ACCESS_WORDS[type]).join(\" and \");\n}\n\nfunction action(href: string | undefined, onSelect: (() => void) | undefined): CallGraphAction | undefined {\n if (href === undefined && onSelect === undefined) return undefined;\n return { ...(href !== undefined ? { href } : {}), ...(onSelect ? { onSelect } : {}) };\n}\n\nexport function CallGraph<G extends Graph = Graph>(props: CallGraphProps<G>) {\n const { root, fetchGraph, vocabulary = UIR_CALL_GRAPH_VOCABULARY, maxDepth = CALL_GRAPH_MAX_DEPTH } = props;\n const labels: CallGraphEdgeLabels = { ...DEFAULT_EDGE_LABELS, ...props.edgeLabels };\n const [direction, setDirection] = useHeld(props.direction ?? \"both\", props.onDirectionChange);\n const [depth, setDepth] = useHeld(props.depth ?? CALL_GRAPH_DEFAULT_DEPTH, props.onDepthChange);\n const [exclude, setExclude] = useHeld(props.exclude, props.onExcludeChange);\n const [access, setAccess] = useHeld(props.access ?? [...CALL_GRAPH_ACCESS], props.onAccessChange);\n const [columns, setColumns] = useHeld(props.columns ?? false, props.onColumnsChange);\n const [collapsedGroups, setCollapsedGroups] = useHeld(props.collapsedGroups ?? [], props.onCollapsedGroupsChange);\n if (baseAccess(access).length === 0) throw new Error(\"call graph: access must name at least one of call, read and write\");\n if (access.includes(\"variable\") && !(props.dataAccess && props.variableAccess)) {\n throw new Error(\"call graph: access names variable, but the host does not offer variableAccess\");\n }\n const [ownSelection, setOwnSelection] = useState<{ key: string; selected: CallGraphSelection }>();\n\n const request: GraphRequest | undefined = root === undefined ? undefined : { root, direction, depth, exclude, access, columns, purpose: \"load\" };\n const key = `call-graph:${JSON.stringify([request ?? null, props.fetchKey ?? null])}`;\n const load = useGraphLoad(fetchGraph, request, key);\n\n const [expanded, setExpanded] = useState<Held<G>>();\n const [expanding, setExpanding] = useState<{ request: PendingExpansion; error?: string }>();\n const pendingExpansion = useRef<PendingExpansion | undefined>(undefined);\n const activeKey = useRef(key);\n activeKey.current = key;\n useEffect(() => {\n setExpanding((current) => current?.request.key === key && !current.request.controller.signal.aborted ? current : undefined);\n return () => {\n if (pendingExpansion.current?.key === key) {\n pendingExpansion.current.controller.abort();\n pendingExpansion.current = undefined;\n }\n };\n }, [key]);\n const [opening, setOpening] = useState({ nonce: 0, fit: false });\n const fresh = useMemo<Held<G> | undefined>(\n () => load.fresh && (expanded?.key === key ? expanded : { key, graph: load.fresh, revealed: [] }),\n [load.fresh, expanded, key],\n );\n const [last, setLast] = useState<Held<G>>();\n if (fresh && fresh !== last) setLast(fresh);\n const shown = root === undefined ? undefined : (fresh ?? last);\n const graph = shown?.graph;\n\n const visible = useMemo(() => shown && visibleGraph(shown.graph, { direction, depth, revealed: shown.revealed }), [shown, direction, depth]);\n const glyphs = useMemo<DiagramGlyphs>(() => ({\n node: (glyph, words) => <NodeGlyphIcon vocabulary={vocabulary} glyph={glyph} title={words} />,\n edge: (type) => (type === \"call\" ? undefined : <EdgeTypeGlyph type={type} title={labels[type]} />),\n group: (caption, glyph) => <GroupCaption icon={glyph === undefined ? vocabulary.group.icon : requireGlyph(vocabulary, glyph).icon} caption={caption} />,\n access: (access) => <AccessMark access={access} />,\n }), [vocabulary, labels.dispatch, labels.read, labels.write]);\n const guardLabel = props.guardLabel ?? \"innermost\";\n const diagram = useMemo(\n () => graph && visible && toDiagram({ graph, visible, direction, options: { ...OPTIONS, guardLabel }, glyphs, vocabulary, edgeLabels: labels, collapsed: collapsedGroups }),\n [graph, visible, direction, guardLabel, glyphs, vocabulary, labels.call, labels.dispatch, labels.read, labels.write, collapsedGroups],\n );\n // What a selection is looked up in: the graph as drawn, collapsed groups folded into their stand-ins.\n const drawn = diagram?.drawn;\n const collapsible = useMemo(() => (graph && visible ? collapsibleGroups(graph, visible) : []), [graph, visible]);\n const toggleGroup = (id: string) => setCollapsedGroups(collapsedGroups.includes(id) ? collapsedGroups.filter((entry) => entry !== id) : [...collapsedGroups, id]);\n\n const rootId = graph?.roots[0];\n const selection = props.onSelectedChange ? props.selected : ownSelection?.key === shown?.key ? ownSelection?.selected : undefined;\n const select = (next: CallGraphSelection | undefined) => {\n if (props.onSelectedChange) props.onSelectedChange(next);\n else setOwnSelection(next && shown ? { key: shown.key, selected: next } : undefined);\n };\n const openAction = (node: CallGraphNode, drawn: G) => {\n const { onOpenNode, canOpenNode } = props;\n const accepted = onOpenNode && (canOpenNode?.(node, drawn) ?? true) ? () => onOpenNode(node, drawn) : undefined;\n return action(props.nodeHref?.(node, drawn), accepted);\n };\n const selectedNode = selection?.kind === \"node\" ? drawn?.visible.nodes.find((node) => node.id === selection.id) : undefined;\n const selectedEdge = selection?.kind === \"edge\" ? drawn?.visible.edges.find((edge) => edge.id === selection.id) : undefined;\n // A merged edge stands for several the host knows: it has no guards of its own to load.\n const mergedEdge = selectedEdge !== undefined && drawn?.merged.has(selectedEdge.id) === true;\n const standIn = selectedNode !== undefined && drawn?.folds.has(selectedNode.id) === true;\n const guards = useSiteGuards(props.loadSiteGuards, shown?.key, mergedEdge ? undefined : selectedEdge, graph);\n const expansion = expanding?.request.key === key ? expanding : undefined;\n\n // A `+N` loads one more hop on the node's outer side and merges it into the graph this request drew.\n const expand = async (drawn: Held<G>, id: string) => {\n if (drawn.key !== activeKey.current) return;\n if (pendingExpansion.current?.key === drawn.key && pendingExpansion.current.nodeId === id) return;\n pendingExpansion.current?.controller.abort();\n const node = requireNode(drawn.graph, id);\n const request: PendingExpansion = { key: drawn.key, nodeId: id, label: node.label, controller: new AbortController() };\n pendingExpansion.current = request;\n setExpanding({ request });\n try {\n const data = await fetchGraph({\n root: id, direction: expansionDirection(node), depth: 1, exclude, access, columns, purpose: \"expand\", signal: request.controller.signal,\n });\n if (request.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request) return;\n setExpanded((held) => {\n if (request.controller.signal.aborted || activeKey.current !== drawn.key) return held;\n const base = held?.key === drawn.key ? held : drawn;\n return { key: drawn.key, graph: mergeGraph(base.graph, data), revealed: [...base.revealed, ...data.nodes.map((entry) => entry.id)] };\n });\n setExpanding((current) => current?.request === request ? undefined : current);\n } catch (error) {\n if (request.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request) return;\n setExpanding((current) => current?.request === request\n ? { request, error: `Cannot expand ${node.label}: ${errorMessage(error)}` } : current);\n } finally {\n if (pendingExpansion.current === request) pendingExpansion.current = undefined;\n }\n };\n\n const groups = graph && props.groupFacts?.(graph);\n const effectiveExclude = exclude ?? (graph && props.defaultExclude ? props.defaultExclude(graph) : []);\n const { onPinnedChange } = props;\n const toolbar = (\n <CallGraphToolbar direction={direction} depth={depth} maxDepth={maxDepth} omitted={graph?.omitted} end={props.toolbarEnd} vocabulary={vocabulary}\n {...(onPinnedChange ? { pin: { pinned: props.pinned ?? false, canPin: rootId !== undefined, onPin: (pinned: boolean) => onPinnedChange(pinned, rootId) } } : {})}\n {...(groups ? { exclusions: { exclude: effectiveExclude, groups, matches: props.excludeMatcher ?? matchesExclude, busy: load.loading, vocabulary,\n onExclude: (patterns: string[]) => setExclude(patterns), onDefaults: () => setExclude(undefined) } } : {})}\n {...(props.dataAccess ? { data: { access, onAccess: setAccess, columns, onColumns: setColumns, variables: props.variableAccess ?? false } } : {})}\n {...(collapsible.length > 0 ? { groupCollapse: {\n anyCollapsed: collapsedGroups.some((id) => collapsible.includes(id)),\n anyExpanded: collapsible.some((id) => !collapsedGroups.includes(id)),\n onExpandAll: () => setCollapsedGroups([]),\n onCollapseAll: () => setCollapsedGroups(collapsible),\n } } : {})}\n onDirection={setDirection} onDepth={setDepth}\n onFit={() => setOpening({ nonce: opening.nonce + 1, fit: true })} onReset={() => setOpening({ nonce: opening.nonce + 1, fit: false })} />\n );\n\n const frame = cn(\"flex h-full min-h-0 flex-col gap-2 p-2\", props.className);\n if (root === undefined) {\n return <div className={frame}>{toolbar}<Notice>{props.emptyMessage ?? \"Select a node to draw its call graph.\"}</Notice></div>;\n }\n const loadError = load.error && `Cannot draw the call graph: ${load.error}`;\n return (\n <div className={frame} aria-busy={load.loading}>\n {toolbar}\n <Alert message={loadError} />\n {graph && props.renderHeader?.(graph)}\n {!shown && load.loading && <Notice>Loading call graph…</Notice>}\n {graph && !rootId && <Notice>The root resolved to no node in this scope.</Notice>}\n {graph && rootId && graph.edges.length === 0 && baseAccess(access).length < CALL_GRAPH_ACCESS.length && (\n <Notice>{`Nothing is reachable with ${accessWords(baseAccess(access))} only.`}</Notice>\n )}\n {graph && rootId && graph.edges.length > 0 && !selectedEdge && !selectedNode && (\n <p className=\"text-xs text-muted-foreground\">Select an edge to list its sites, or a node to see its details.</p>\n )}\n {shown && graph && rootId && diagram && (\n <div className=\"flex min-h-0 flex-1 gap-3\">\n <div className={cn(\"relative min-h-64 min-w-0 flex-1 rounded-lg border border-border bg-background\", load.loading && \"opacity-60\")}>\n <GraphDiagram key={`${shown.key}:${opening.nonce}`} nodes={diagram.nodes} edges={diagram.edges} groups={diagram.groups} layout=\"columns\" zoomable wheelZoom=\"zoom\" edgeFocus=\"auto\"\n focusId={rootId} {...(opening.fit ? {} : { fitMinScale: READABLE_SCALE })} nodeWidth={NODE_WIDTH} nodeHeight={NODE_HEIGHT} compactNodeHeight={COMPACT_NODE_HEIGHT} recordHeaderHeight={24} columnGap={columnGap(diagram)} rowGap={ROW_GAP}\n className=\"h-full\" ariaLabel={`Call graph of ${requireNode(graph, rootId).label}`} onGroupToggle={toggleGroup}\n onNodeSelect={(id: string) => select({ kind: \"node\", id })} onEdgeSelect={(id: string) => select({ kind: \"edge\", id })}\n {...(fresh ? { onNodeExpand: (id: string) => void expand(fresh, id) } : {})}\n {...(selectedNode ? { selectedId: selectedNode.id } : {})} {...(selectedEdge ? { selectedEdgeId: selectedEdge.id } : {})} />\n </div>\n {(selectedEdge || selectedNode || expansion) && (\n <aside className=\"w-80 max-w-[45%] shrink-0 overflow-y-auto rounded-lg border border-border bg-card p-3\" aria-label=\"Selection\">\n {expansion && <p role=\"status\" className=\"mb-2 text-xs text-muted-foreground\">{expansion.error ?? `Loading more around ${expansion.request.label}…`}</p>}\n {(selectedEdge || selectedNode) && (\n <div className=\"mb-1 flex justify-end\">\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" aria-label=\"Close selection\" onClick={() => select(undefined)}>×</Button>\n </div>\n )}\n {selectedEdge && drawn ? (\n <EdgeSites edge={selectedEdge} graph={drawn.graph} labels={labels} guards={guards} lazyGuards={props.loadSiteGuards !== undefined}\n siteAction={(props.siteHref || props.onRevealSite) && !drawn.folds.has(selectedEdge.from) && vocabulary.hasSource(requireNode(graph, selectedEdge.from))\n ? (site) => action(props.siteHref?.(site, selectedEdge, graph), props.onRevealSite && (() => props.onRevealSite?.(site, selectedEdge, graph))) ?? {}\n : undefined} />\n ) : selectedNode && drawn && (\n <NodeDetails node={selectedNode} graph={drawn.graph} vocabulary={vocabulary} isRoot={selectedNode.id === rootId} openLabel={props.openLabel ?? \"Open\"}\n opens={!standIn && (props.nodeHref !== undefined || props.onOpenNode !== undefined)} open={standIn ? undefined : openAction(selectedNode, graph)}\n onFocus={standIn ? undefined : props.onFocusNode && ((id: string) => props.onFocusNode?.(id, graph))} />\n )}\n </aside>\n )}\n </div>\n )}\n {graph && rootId && <div className=\"shrink-0\"><Legend graph={graph} vocabulary={vocabulary} labels={labels} /></div>}\n </div>\n );\n}\n"],"names":["jsx","UIR_CALL_GRAPH_VOCABULARY","CALL_GRAPH_MAX_DEPTH","DEFAULT_EDGE_LABELS","useHeld","CALL_GRAPH_DEFAULT_DEPTH","CALL_GRAPH_ACCESS","baseAccess","useState","useGraphLoad","useRef","useEffect","_a","useMemo","visibleGraph","NodeGlyphIcon","EdgeTypeGlyph","GroupCaption","requireGlyph","access","AccessMark","toDiagram","collapsibleGroups","drawn","useSiteGuards","_b","requireNode","request","expansionDirection","mergeGraph","errorMessage","CallGraphToolbar","matchesExclude","cn","jsxs","GraphDiagram","columnGap","Button","EdgeSites","NodeDetails","Legend"],"mappings":"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"}
@@ -22,12 +22,21 @@ export interface CallGraphProps<G extends Graph = Graph> {
22
22
  onSelectedChange?: (selected: CallGraphSelection | undefined) => void;
23
23
  /** The source reports reads and writes of data: shows the Access toggles and the Columns switch. */
24
24
  dataAccess?: boolean;
25
- /** The edge types the host follows, never empty; all three by default. Controlled with `onAccessChange`. */
25
+ /** The source can follow the variables a body reads and writes: shows a Variables toggle beside the Access ones. Needs `dataAccess`. */
26
+ variableAccess?: boolean;
27
+ /** The edge types the host follows, at least one of call, read and write; all three by default. Controlled with `onAccessChange`. */
26
28
  access?: CallGraphAccess[];
27
29
  onAccessChange?: (access: CallGraphAccess[]) => void;
28
30
  /** Columns and fields as nodes of their own; collapsed into tables and entities by default. */
29
31
  columns?: boolean;
30
32
  onColumnsChange?: (columns: boolean) => void;
33
+ /**
34
+ * The groups drawn as their header alone, by `graph.groups` id: each folds into one node, and its
35
+ * members' edges merge onto it, counting the members. The root's group never folds. Controlled with
36
+ * `onCollapsedGroupsChange`; without one the component holds it, starting here, none by default.
37
+ */
38
+ collapsedGroups?: string[];
39
+ onCollapsedGroupsChange?: (ids: string[]) => void;
31
40
  /** The groups the graph reached, drawn or excluded. Without it the graph offers no exclusions. */
32
41
  groupFacts?: (graph: G) => CallGraphGroupFact[];
33
42
  /** The patterns in force when `exclude` is undefined: the host's defaults, as its response reports them. */
@@ -1 +1 @@
1
- {"version":3,"file":"CallGraph.d.ts","sourceRoot":"","sources":["../../../src/data/call-graph/CallGraph.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CallGraph.d.ts","sourceRoot":"","sources":["../../../src/data/call-graph/CallGraph.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAwC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAK7E,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAgBtD,OAAO,EAA2C,KAAK,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAI5G,OAAO,EAEL,KAAK,SAAS,IAAI,KAAK,EACvB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,SAAS,CAAC;AAGjB,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK;IACrD,2FAA2F;IAC3F,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,oFAAoF;IACpF,UAAU,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;IACzD,gHAAgH;IAChH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,sGAAsG;IACtG,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC/B,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,SAAS,KAAK,IAAI,CAAC;IAC1D,QAAQ,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC1C,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,GAAG,SAAS,KAAK,IAAI,CAAC;IACtE,oGAAoG;IACpG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wIAAwI;IACxI,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qIAAqI;IACrI,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,IAAI,CAAC;IACrD,+FAA+F;IAC/F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7C;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,uBAAuB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAClD,kGAAkG;IAClG,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,kBAAkB,EAAE,CAAC;IAChD,4GAA4G;IAC5G,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,EAAE,CAAC;IACxC,0EAA0E;IAC1E,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;IACjE,0EAA0E;IAC1E,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACrD,gEAAgE;IAChE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+GAA+G;IAC/G,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,KAAK,MAAM,GAAG,SAAS,CAAC;IACtF,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAC5E,6FAA6F;IAC7F,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;IAClG,UAAU,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC1C,uHAAuH;IACvH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACvE,uEAAuE;IACvE,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC;IACvC,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqCD,wBAAgB,SAAS,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC,CAAC,+BAoL1E"}
@@ -2,10 +2,11 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect, useMemo } from "react";
3
3
  import { Button } from "../../components/button.js";
4
4
  import { cn } from "../../lib/utils.js";
5
+ import { AccessMark } from "../AccessMark.js";
5
6
  import { GraphDiagram } from "../GraphDiagram.js";
6
7
  import { matchesExclude } from "./call-graph-exclude.js";
7
- import { CALL_GRAPH_MAX_DEPTH, CALL_GRAPH_DEFAULT_DEPTH, visibleGraph, DEFAULT_EDGE_LABELS, toDiagram, requireNode, columnGap, expansionDirection, mergeGraph } from "./call-graph-model.js";
8
- import { UIR_CALL_GRAPH_VOCABULARY } from "./call-graph-vocabulary.js";
8
+ import { CALL_GRAPH_MAX_DEPTH, CALL_GRAPH_DEFAULT_DEPTH, baseAccess, visibleGraph, DEFAULT_EDGE_LABELS, toDiagram, collapsibleGroups, requireNode, columnGap, expansionDirection, mergeGraph } from "./call-graph-model.js";
9
+ import { UIR_CALL_GRAPH_VOCABULARY, requireGlyph } from "./call-graph-vocabulary.js";
9
10
  import { GroupCaption, EdgeTypeGlyph, NodeGlyphIcon } from "./CallGraphGlyphs.js";
10
11
  import { EdgeSites, NodeDetails, Legend } from "./CallGraphParts.js";
11
12
  import { CallGraphToolbar } from "./CallGraphToolbar.js";
@@ -24,7 +25,7 @@ function Alert({ message }) {
24
25
  if (!message) return null;
25
26
  return /* @__PURE__ */ jsx("div", { role: "alert", className: "whitespace-pre-wrap rounded-md border border-destructive p-3 text-sm text-destructive", children: message });
26
27
  }
27
- const ACCESS_WORDS = { call: "Calls", read: "Reads", write: "Writes" };
28
+ const ACCESS_WORDS = { call: "Calls", read: "Reads", write: "Writes", variable: "Variables" };
28
29
  function accessWords(access) {
29
30
  return access.map((type) => ACCESS_WORDS[type]).join(" and ");
30
31
  }
@@ -41,7 +42,11 @@ function CallGraph(props) {
41
42
  const [exclude, setExclude] = useHeld(props.exclude, props.onExcludeChange);
42
43
  const [access, setAccess] = useHeld(props.access ?? [...CALL_GRAPH_ACCESS], props.onAccessChange);
43
44
  const [columns, setColumns] = useHeld(props.columns ?? false, props.onColumnsChange);
44
- if (access.length === 0) throw new Error("call graph: access must name at least one of call, read and write");
45
+ const [collapsedGroups, setCollapsedGroups] = useHeld(props.collapsedGroups ?? [], props.onCollapsedGroupsChange);
46
+ if (baseAccess(access).length === 0) throw new Error("call graph: access must name at least one of call, read and write");
47
+ if (access.includes("variable") && !(props.dataAccess && props.variableAccess)) {
48
+ throw new Error("call graph: access names variable, but the host does not offer variableAccess");
49
+ }
45
50
  const [ownSelection, setOwnSelection] = useState();
46
51
  const request = root === void 0 ? void 0 : { root, direction, depth, exclude, access, columns, purpose: "load" };
47
52
  const key = `call-graph:${JSON.stringify([request ?? null, props.fetchKey ?? null])}`;
@@ -74,36 +79,42 @@ function CallGraph(props) {
74
79
  const glyphs = useMemo(() => ({
75
80
  node: (glyph, words) => /* @__PURE__ */ jsx(NodeGlyphIcon, { vocabulary, glyph, title: words }),
76
81
  edge: (type) => type === "call" ? void 0 : /* @__PURE__ */ jsx(EdgeTypeGlyph, { type, title: labels[type] }),
77
- group: (caption) => /* @__PURE__ */ jsx(GroupCaption, { icon: vocabulary.group.icon, caption })
82
+ group: (caption, glyph) => /* @__PURE__ */ jsx(GroupCaption, { icon: glyph === void 0 ? vocabulary.group.icon : requireGlyph(vocabulary, glyph).icon, caption }),
83
+ access: (access2) => /* @__PURE__ */ jsx(AccessMark, { access: access2 })
78
84
  }), [vocabulary, labels.dispatch, labels.read, labels.write]);
79
85
  const guardLabel = props.guardLabel ?? "innermost";
80
86
  const diagram = useMemo(
81
- () => graph && visible && toDiagram({ graph, visible, direction, options: { ...OPTIONS, guardLabel }, glyphs, vocabulary, edgeLabels: labels }),
82
- [graph, visible, direction, guardLabel, glyphs, vocabulary, labels.call, labels.dispatch, labels.read, labels.write]
87
+ () => graph && visible && toDiagram({ graph, visible, direction, options: { ...OPTIONS, guardLabel }, glyphs, vocabulary, edgeLabels: labels, collapsed: collapsedGroups }),
88
+ [graph, visible, direction, guardLabel, glyphs, vocabulary, labels.call, labels.dispatch, labels.read, labels.write, collapsedGroups]
83
89
  );
90
+ const drawn = diagram == null ? void 0 : diagram.drawn;
91
+ const collapsible = useMemo(() => graph && visible ? collapsibleGroups(graph, visible) : [], [graph, visible]);
92
+ const toggleGroup = (id) => setCollapsedGroups(collapsedGroups.includes(id) ? collapsedGroups.filter((entry) => entry !== id) : [...collapsedGroups, id]);
84
93
  const rootId = graph == null ? void 0 : graph.roots[0];
85
94
  const selection = props.onSelectedChange ? props.selected : (ownSelection == null ? void 0 : ownSelection.key) === (shown == null ? void 0 : shown.key) ? ownSelection == null ? void 0 : ownSelection.selected : void 0;
86
95
  const select = (next) => {
87
96
  if (props.onSelectedChange) props.onSelectedChange(next);
88
97
  else setOwnSelection(next && shown ? { key: shown.key, selected: next } : void 0);
89
98
  };
90
- const openAction = (node, drawn) => {
99
+ const openAction = (node, drawn2) => {
91
100
  var _a2;
92
101
  const { onOpenNode, canOpenNode } = props;
93
- const accepted = onOpenNode && ((canOpenNode == null ? void 0 : canOpenNode(node, drawn)) ?? true) ? () => onOpenNode(node, drawn) : void 0;
94
- return action((_a2 = props.nodeHref) == null ? void 0 : _a2.call(props, node, drawn), accepted);
102
+ const accepted = onOpenNode && ((canOpenNode == null ? void 0 : canOpenNode(node, drawn2)) ?? true) ? () => onOpenNode(node, drawn2) : void 0;
103
+ return action((_a2 = props.nodeHref) == null ? void 0 : _a2.call(props, node, drawn2), accepted);
95
104
  };
96
- const selectedNode = (selection == null ? void 0 : selection.kind) === "node" ? visible == null ? void 0 : visible.nodes.find((node) => node.id === selection.id) : void 0;
97
- const selectedEdge = (selection == null ? void 0 : selection.kind) === "edge" ? visible == null ? void 0 : visible.edges.find((edge) => edge.id === selection.id) : void 0;
98
- const guards = useSiteGuards(props.loadSiteGuards, shown == null ? void 0 : shown.key, selectedEdge, graph);
105
+ const selectedNode = (selection == null ? void 0 : selection.kind) === "node" ? drawn == null ? void 0 : drawn.visible.nodes.find((node) => node.id === selection.id) : void 0;
106
+ const selectedEdge = (selection == null ? void 0 : selection.kind) === "edge" ? drawn == null ? void 0 : drawn.visible.edges.find((edge) => edge.id === selection.id) : void 0;
107
+ const mergedEdge = selectedEdge !== void 0 && (drawn == null ? void 0 : drawn.merged.has(selectedEdge.id)) === true;
108
+ const standIn = selectedNode !== void 0 && (drawn == null ? void 0 : drawn.folds.has(selectedNode.id)) === true;
109
+ const guards = useSiteGuards(props.loadSiteGuards, shown == null ? void 0 : shown.key, mergedEdge ? void 0 : selectedEdge, graph);
99
110
  const expansion = (expanding == null ? void 0 : expanding.request.key) === key ? expanding : void 0;
100
- const expand = async (drawn, id) => {
111
+ const expand = async (drawn2, id) => {
101
112
  var _a2, _b2;
102
- if (drawn.key !== activeKey.current) return;
103
- if (((_a2 = pendingExpansion.current) == null ? void 0 : _a2.key) === drawn.key && pendingExpansion.current.nodeId === id) return;
113
+ if (drawn2.key !== activeKey.current) return;
114
+ if (((_a2 = pendingExpansion.current) == null ? void 0 : _a2.key) === drawn2.key && pendingExpansion.current.nodeId === id) return;
104
115
  (_b2 = pendingExpansion.current) == null ? void 0 : _b2.controller.abort();
105
- const node = requireNode(drawn.graph, id);
106
- const request2 = { key: drawn.key, nodeId: id, label: node.label, controller: new AbortController() };
116
+ const node = requireNode(drawn2.graph, id);
117
+ const request2 = { key: drawn2.key, nodeId: id, label: node.label, controller: new AbortController() };
107
118
  pendingExpansion.current = request2;
108
119
  setExpanding({ request: request2 });
109
120
  try {
@@ -117,15 +128,15 @@ function CallGraph(props) {
117
128
  purpose: "expand",
118
129
  signal: request2.controller.signal
119
130
  });
120
- if (request2.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request2) return;
131
+ if (request2.controller.signal.aborted || activeKey.current !== drawn2.key || pendingExpansion.current !== request2) return;
121
132
  setExpanded((held) => {
122
- if (request2.controller.signal.aborted || activeKey.current !== drawn.key) return held;
123
- const base = (held == null ? void 0 : held.key) === drawn.key ? held : drawn;
124
- return { key: drawn.key, graph: mergeGraph(base.graph, data), revealed: [...base.revealed, ...data.nodes.map((entry) => entry.id)] };
133
+ if (request2.controller.signal.aborted || activeKey.current !== drawn2.key) return held;
134
+ const base = (held == null ? void 0 : held.key) === drawn2.key ? held : drawn2;
135
+ return { key: drawn2.key, graph: mergeGraph(base.graph, data), revealed: [...base.revealed, ...data.nodes.map((entry) => entry.id)] };
125
136
  });
126
137
  setExpanding((current) => (current == null ? void 0 : current.request) === request2 ? void 0 : current);
127
138
  } catch (error) {
128
- if (request2.controller.signal.aborted || activeKey.current !== drawn.key || pendingExpansion.current !== request2) return;
139
+ if (request2.controller.signal.aborted || activeKey.current !== drawn2.key || pendingExpansion.current !== request2) return;
129
140
  setExpanding((current) => (current == null ? void 0 : current.request) === request2 ? { request: request2, error: `Cannot expand ${node.label}: ${errorMessage(error)}` } : current);
130
141
  } finally {
131
142
  if (pendingExpansion.current === request2) pendingExpansion.current = void 0;
@@ -153,7 +164,13 @@ function CallGraph(props) {
153
164
  onExclude: (patterns) => setExclude(patterns),
154
165
  onDefaults: () => setExclude(void 0)
155
166
  } } : {},
156
- ...props.dataAccess ? { data: { access, onAccess: setAccess, columns, onColumns: setColumns } } : {},
167
+ ...props.dataAccess ? { data: { access, onAccess: setAccess, columns, onColumns: setColumns, variables: props.variableAccess ?? false } } : {},
168
+ ...collapsible.length > 0 ? { groupCollapse: {
169
+ anyCollapsed: collapsedGroups.some((id) => collapsible.includes(id)),
170
+ anyExpanded: collapsible.some((id) => !collapsedGroups.includes(id)),
171
+ onExpandAll: () => setCollapsedGroups([]),
172
+ onCollapseAll: () => setCollapsedGroups(collapsible)
173
+ } } : {},
157
174
  onDirection: setDirection,
158
175
  onDepth: setDepth,
159
176
  onFit: () => setOpening({ nonce: opening.nonce + 1, fit: true }),
@@ -174,7 +191,7 @@ function CallGraph(props) {
174
191
  graph && ((_b = props.renderHeader) == null ? void 0 : _b.call(props, graph)),
175
192
  !shown && load.loading && /* @__PURE__ */ jsx(Notice, { children: "Loading call graph…" }),
176
193
  graph && !rootId && /* @__PURE__ */ jsx(Notice, { children: "The root resolved to no node in this scope." }),
177
- graph && rootId && graph.edges.length === 0 && access.length < CALL_GRAPH_ACCESS.length && /* @__PURE__ */ jsx(Notice, { children: `Nothing is reachable with ${accessWords(access)} only.` }),
194
+ graph && rootId && graph.edges.length === 0 && baseAccess(access).length < CALL_GRAPH_ACCESS.length && /* @__PURE__ */ jsx(Notice, { children: `Nothing is reachable with ${accessWords(baseAccess(access))} only.` }),
178
195
  graph && rootId && graph.edges.length > 0 && !selectedEdge && !selectedNode && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Select an edge to list its sites, or a node to see its details." }),
179
196
  shown && graph && rootId && diagram && /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-3", children: [
180
197
  /* @__PURE__ */ jsx("div", { className: cn("relative min-h-64 min-w-0 flex-1 rounded-lg border border-border bg-background", load.loading && "opacity-60"), children: /* @__PURE__ */ jsx(
@@ -192,10 +209,12 @@ function CallGraph(props) {
192
209
  nodeWidth: NODE_WIDTH,
193
210
  nodeHeight: NODE_HEIGHT,
194
211
  compactNodeHeight: COMPACT_NODE_HEIGHT,
212
+ recordHeaderHeight: 24,
195
213
  columnGap: columnGap(diagram),
196
214
  rowGap: ROW_GAP,
197
215
  className: "h-full",
198
216
  ariaLabel: `Call graph of ${requireNode(graph, rootId).label}`,
217
+ onGroupToggle: toggleGroup,
199
218
  onNodeSelect: (id) => select({ kind: "node", id }),
200
219
  onEdgeSelect: (id) => select({ kind: "edge", id }),
201
220
  ...fresh ? { onNodeExpand: (id) => void expand(fresh, id) } : {},
@@ -207,15 +226,15 @@ function CallGraph(props) {
207
226
  (selectedEdge || selectedNode || expansion) && /* @__PURE__ */ jsxs("aside", { className: "w-80 max-w-[45%] shrink-0 overflow-y-auto rounded-lg border border-border bg-card p-3", "aria-label": "Selection", children: [
208
227
  expansion && /* @__PURE__ */ jsx("p", { role: "status", className: "mb-2 text-xs text-muted-foreground", children: expansion.error ?? `Loading more around ${expansion.request.label}…` }),
209
228
  (selectedEdge || selectedNode) && /* @__PURE__ */ jsx("div", { className: "mb-1 flex justify-end", children: /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "ghost", "aria-label": "Close selection", onClick: () => select(void 0), children: "×" }) }),
210
- selectedEdge ? /* @__PURE__ */ jsx(
229
+ selectedEdge && drawn ? /* @__PURE__ */ jsx(
211
230
  EdgeSites,
212
231
  {
213
232
  edge: selectedEdge,
214
- graph,
233
+ graph: drawn.graph,
215
234
  labels,
216
235
  guards,
217
236
  lazyGuards: props.loadSiteGuards !== void 0,
218
- siteAction: (props.siteHref || props.onRevealSite) && vocabulary.hasSource(requireNode(graph, selectedEdge.from)) ? (site) => {
237
+ siteAction: (props.siteHref || props.onRevealSite) && !drawn.folds.has(selectedEdge.from) && vocabulary.hasSource(requireNode(graph, selectedEdge.from)) ? (site) => {
219
238
  var _a2;
220
239
  return action((_a2 = props.siteHref) == null ? void 0 : _a2.call(props, site, selectedEdge, graph), props.onRevealSite && (() => {
221
240
  var _a3;
@@ -223,17 +242,17 @@ function CallGraph(props) {
223
242
  })) ?? {};
224
243
  } : void 0
225
244
  }
226
- ) : selectedNode && /* @__PURE__ */ jsx(
245
+ ) : selectedNode && drawn && /* @__PURE__ */ jsx(
227
246
  NodeDetails,
228
247
  {
229
248
  node: selectedNode,
230
- graph,
249
+ graph: drawn.graph,
231
250
  vocabulary,
232
251
  isRoot: selectedNode.id === rootId,
233
252
  openLabel: props.openLabel ?? "Open",
234
- opens: props.nodeHref !== void 0 || props.onOpenNode !== void 0,
235
- open: openAction(selectedNode, graph),
236
- onFocus: props.onFocusNode && ((id) => {
253
+ opens: !standIn && (props.nodeHref !== void 0 || props.onOpenNode !== void 0),
254
+ open: standIn ? void 0 : openAction(selectedNode, graph),
255
+ onFocus: standIn ? void 0 : props.onFocusNode && ((id) => {
237
256
  var _a2;
238
257
  return (_a2 = props.onFocusNode) == null ? void 0 : _a2.call(props, id, graph);
239
258
  })