@stealthscale/component-graphs 0.1.0 → 0.2.2

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.
@@ -0,0 +1,3808 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { t as recipe } from "./recipes-lWPA4IJC.js";
3
+ import { createContext, isValidElement, use, useEffect, useId, useRef, useState } from "react";
4
+ import { c } from "react/compiler-runtime";
5
+ import { Button, ButtonPropsProvider, IconButton } from "@stealthscale/component-actions";
6
+ import { Badge, Status } from "@stealthscale/component-data";
7
+ import { createLabelling, createRequiredContext, omitUndefined, useControllableState } from "@stealthscale/hooks";
8
+ import { Graph, layout } from "@dagrejs/dagre";
9
+ import { Background, BaseEdge, EdgeLabelRenderer, Handle, MiniMap, Position, ReactFlow, ReactFlowProvider, addEdge, applyNodeChanges, getBezierPath, getViewportForBounds, useNodeId, useReactFlow, useStore, useStoreApi } from "@xyflow/react";
10
+ import { createSlotRecipeContext } from "@stealthscale/theme";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ import { RovingFocus } from "@stealthscale/component-a11y";
13
+ import { LocaleContext } from "@stealthscale/provider-locale";
14
+ import { forceCenter, forceCollide, forceLink, forceManyBody, forceSimulation, forceX, forceY } from "d3-force";
15
+ //#region src/graph/words.ts
16
+ /**
17
+ * Instruction every node of an editable canvas is described by, unless stated.
18
+ */
19
+ const NODE = "Press Enter or Space to select the node, the arrow keys to move it, Delete to remove it and Escape to clear the selection.";
20
+ /**
21
+ * Instruction every edge of an editable canvas is described by, unless stated.
22
+ */
23
+ const EDGE = "Press Enter or Space to select the connection, Delete to remove it and Escape to clear the selection.";
24
+ /**
25
+ * Writes the announcement of a move, unless stated.
26
+ */
27
+ function moveAnnouncementOf({ direction }) {
28
+ return `Moved the selected node ${direction}.`;
29
+ }
30
+ /**
31
+ * Writes the name of an edge from the names of its ends, unless stated.
32
+ */
33
+ function edgeNameOf({ source, target }) {
34
+ return `${source} to ${target}`;
35
+ }
36
+ /**
37
+ * Writes the name of an edge's remove control from the names of its ends, unless stated.
38
+ */
39
+ function removeNameOf({ source, target }) {
40
+ return `Remove the connection from ${source} to ${target}`;
41
+ }
42
+ /**
43
+ * Returns React Flow's words for a canvas: the caller's or the English defaults while it takes
44
+ * edits, and no instructions while it is read-only.
45
+ *
46
+ * @param words - The caller's words.
47
+ * @param editable - Whether the canvas takes edits.
48
+ */
49
+ function ariaWordsOf(words, editable) {
50
+ const node = editable ? words.nodeDescription ?? NODE : "";
51
+ return {
52
+ "edge.a11yDescription.default": editable ? words.edgeDescription ?? EDGE : "",
53
+ "node.a11yDescription.ariaLiveMessage": words.moveAnnouncement ?? moveAnnouncementOf,
54
+ "node.a11yDescription.default": node,
55
+ "node.a11yDescription.keyboardDisabled": node
56
+ };
57
+ }
58
+ //#endregion
59
+ //#region src/diff/diff.ts
60
+ /**
61
+ * Compares two versions of a graph: the nodes and edges the later version adds, changes or removes.
62
+ *
63
+ * @remarks
64
+ * A node is the same node in both versions while its id is the same, and it is changed while a
65
+ * field of its `data` differs. Each field is compared by its JSON with object keys sorted, so a
66
+ * node that only moved, or whose data lists its keys in another order, is unchanged. A React
67
+ * element in the data, such as an icon, is compared by its component and its props. An edge is
68
+ * the same edge in both versions while it joins the same ports of the same nodes, so an edge is
69
+ * added or removed, never changed. The entries follow the later version's order, and the removed
70
+ * ones follow in the earlier version's order.
71
+ */
72
+ /**
73
+ * Returns a function that writes a value's JSON with object keys sorted and each React element as
74
+ * its component's number of first sight and its props.
75
+ */
76
+ function canonicalOf() {
77
+ const components = /* @__PURE__ */ new Map();
78
+ /**
79
+ * Replaces a React element and an object with the forms the comparison reads.
80
+ */
81
+ const replacer = (_key, value) => {
82
+ if (isValidElement(value)) {
83
+ if (!components.has(value.type)) components.set(value.type, components.size);
84
+ return {
85
+ component: components.get(value.type),
86
+ props: value.props
87
+ };
88
+ }
89
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
90
+ const fields = new Map(Object.entries(value));
91
+ return Object.fromEntries([...fields.keys()].toSorted().map((key) => [key, fields.get(key)]));
92
+ };
93
+ return (value) => JSON.stringify(value, replacer) ?? "";
94
+ }
95
+ /**
96
+ * Returns a node's name: its data's string `label`, else its id.
97
+ */
98
+ function labelOf(node) {
99
+ const label = node.data["label"];
100
+ return typeof label === "string" ? label : node.id;
101
+ }
102
+ /**
103
+ * Returns the fields of two versions of a node's data that differ, sorted.
104
+ */
105
+ function fieldsOf(before, after, canonical) {
106
+ return [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].filter((key) => canonical(before[key]) !== canonical(after[key])).toSorted();
107
+ }
108
+ /**
109
+ * Returns a node's entry with the fields of its data that differ.
110
+ */
111
+ function nodeEntryOf(node, change, fields = []) {
112
+ return {
113
+ change,
114
+ fields,
115
+ id: node.id,
116
+ label: labelOf(node)
117
+ };
118
+ }
119
+ /**
120
+ * Returns an entry for every node of either version.
121
+ */
122
+ function nodesOf(before, after) {
123
+ const canonical = canonicalOf();
124
+ const earlier = new Map(before.nodes.map((node) => [node.id, node]));
125
+ const later = new Set(after.nodes.map((node) => node.id));
126
+ const kept = after.nodes.map((node) => {
127
+ const previous = earlier.get(node.id);
128
+ if (previous === void 0) return nodeEntryOf(node, "added");
129
+ const fields = fieldsOf(previous.data, node.data, canonical);
130
+ return nodeEntryOf(node, fields.length > 0 ? "changed" : "unchanged", fields);
131
+ });
132
+ const gone = before.nodes.filter((node) => !later.has(node.id));
133
+ return [...kept, ...gone.map((node) => nodeEntryOf(node, "removed"))];
134
+ }
135
+ /**
136
+ * Returns the name of each node of a version by its id.
137
+ */
138
+ function namesOf(version) {
139
+ return new Map(version.nodes.map((node) => [node.id, labelOf(node)]));
140
+ }
141
+ /**
142
+ * Returns the key an edge is the same edge by: its ends and its ports.
143
+ */
144
+ function keyOf(edge) {
145
+ return JSON.stringify([
146
+ edge.source,
147
+ edge.sourceHandle ?? null,
148
+ edge.target,
149
+ edge.targetHandle ?? null
150
+ ]);
151
+ }
152
+ /**
153
+ * Returns an entry for every edge of either version, each named from the names of its ends in the
154
+ * version it is in.
155
+ */
156
+ function edgesOf$2(before, after, edgeName) {
157
+ const earlier = new Set(before.edges.map(keyOf));
158
+ const later = new Set(after.edges.map(keyOf));
159
+ const [earlierNames, laterNames] = [namesOf(before), namesOf(after)];
160
+ /**
161
+ * Returns an edge's entry, named from the names of its version's nodes.
162
+ */
163
+ const entry = (edge, change, names) => ({
164
+ change,
165
+ fields: [],
166
+ id: edge.id,
167
+ label: edgeName({
168
+ source: names.get(edge.source) ?? edge.source,
169
+ target: names.get(edge.target) ?? edge.target
170
+ })
171
+ });
172
+ return [...after.edges.map((edge) => entry(edge, earlier.has(keyOf(edge)) ? "unchanged" : "added", laterNames)), ...before.edges.filter((edge) => !later.has(keyOf(edge))).map((edge) => entry(edge, "removed", earlierNames))];
173
+ }
174
+ /**
175
+ * Returns what the later version of a graph adds, changes and removes, node by node and edge by
176
+ * edge.
177
+ *
178
+ * @param before - The earlier version.
179
+ * @param after - The later version.
180
+ * @param edgeName - Writes an edge's name from the names of its ends. `Orders to Revenue` unless
181
+ * stated.
182
+ */
183
+ function diffGraphs(before, after, edgeName = edgeNameOf) {
184
+ return {
185
+ edges: edgesOf$2(before, after, edgeName),
186
+ nodes: nodesOf(before, after)
187
+ };
188
+ }
189
+ //#endregion
190
+ //#region src/directed-graph/branches.ts
191
+ /**
192
+ * Returns the ids each node's links lead to, from the links that leave a known node, or from every
193
+ * link without a set of known nodes.
194
+ */
195
+ function childrenOf(links, known) {
196
+ const children = /* @__PURE__ */ new Map();
197
+ for (const { source, target } of links) if (known === void 0 || known.has(source)) children.set(source, [...children.get(source) ?? [], target]);
198
+ return children;
199
+ }
200
+ /**
201
+ * Returns the ids a walk visits from its starts, leaving the children of a node it stops at out of
202
+ * the walk.
203
+ */
204
+ function walkFrom(starts, children, stops) {
205
+ const visited = new Set(starts);
206
+ const queue = [...visited];
207
+ for (const id of queue) for (const child of stops.has(id) ? [] : children.get(id) ?? []) if (!visited.has(child)) {
208
+ visited.add(child);
209
+ queue.push(child);
210
+ }
211
+ return visited;
212
+ }
213
+ /**
214
+ * Returns the ids of every node a node leads to, not counting the node itself.
215
+ */
216
+ function descendantsOf(links, id) {
217
+ const visited = walkFrom([id], childrenOf(links), /* @__PURE__ */ new Set());
218
+ visited.delete(id);
219
+ return visited;
220
+ }
221
+ /**
222
+ * Returns the ids of the nodes the collapsed nodes hide.
223
+ *
224
+ * @param ids - Every node of the graph.
225
+ * @param links - The graph's links.
226
+ * @param collapsed - The nodes whose branches are closed.
227
+ */
228
+ function hiddenBy(ids, links, collapsed) {
229
+ const children = childrenOf(links, new Set(ids));
230
+ const entered = new Set([...children.values()].flat());
231
+ const roots = ids.filter((id) => !entered.has(id));
232
+ const rooted = walkFrom(roots, children, /* @__PURE__ */ new Set());
233
+ const shown = walkFrom([...roots, ...ids.filter((id) => !rooted.has(id))], children, collapsed);
234
+ return new Set(ids.filter((id) => !shown.has(id)));
235
+ }
236
+ //#endregion
237
+ //#region src/graph/walk.ts
238
+ /**
239
+ * Returns the nodes a walk along the links visits from a node within a number of hops.
240
+ *
241
+ * @param links - The graph's links.
242
+ * @param from - The node the walk starts from, which it never counts.
243
+ * @param depth - How many hops the walk takes.
244
+ * @param forward - Whether the walk follows a link from its source to its target, or back.
245
+ */
246
+ function walk(links, from, depth, forward) {
247
+ const visited = /* @__PURE__ */ new Set();
248
+ let frontier = /* @__PURE__ */ new Set([from]);
249
+ for (let hop = 0; hop < depth && frontier.size > 0; hop += 1) {
250
+ const next = /* @__PURE__ */ new Set();
251
+ for (const link of links) {
252
+ const [start, end] = forward ? [link.source, link.target] : [link.target, link.source];
253
+ if (frontier.has(start) && end !== from && !visited.has(end)) {
254
+ visited.add(end);
255
+ next.add(end);
256
+ }
257
+ }
258
+ frontier = next;
259
+ }
260
+ return visited;
261
+ }
262
+ //#endregion
263
+ //#region src/directed-graph/trace.ts
264
+ /**
265
+ * Traces a directed graph from one node: the nodes that feed it and the nodes it feeds, within a
266
+ * number of hops.
267
+ *
268
+ * @remarks
269
+ * A lineage question is "what feeds this" and "what breaks if this breaks". The focused node is
270
+ * in neither set, so a cycle that leads back to it does not make it its own ancestor. A node that
271
+ * is both upstream and downstream, which happens only in a cycle, counts as downstream, because
272
+ * what breaks is the more consequential of the two. The walks stop at a node they visited
273
+ * before, so a cycle never hangs them.
274
+ */
275
+ /**
276
+ * Returns the nodes that feed a node and the nodes it feeds, within `depth` hops.
277
+ *
278
+ * @param links - Every edge of the graph, by the ids of its ends.
279
+ * @param id - The node the trace starts from, which is in neither set.
280
+ * @param depth - How many hops each way count. Every hop unless stated.
281
+ */
282
+ function traceGraph(links, id, depth = Number.POSITIVE_INFINITY) {
283
+ return {
284
+ downstream: walk(links, id, depth, true),
285
+ upstream: walk(links, id, depth, false)
286
+ };
287
+ }
288
+ /**
289
+ * Returns where a node is against the focused node of a trace.
290
+ */
291
+ function relationOf(id, focus, trace) {
292
+ if (id === focus) return "focus";
293
+ if (trace.downstream.has(id)) return "downstream";
294
+ return trace.upstream.has(id) ? "upstream" : "unrelated";
295
+ }
296
+ //#endregion
297
+ //#region src/graph/marks.ts
298
+ /**
299
+ * Returns the attributes that mark an edge on or off a traced path.
300
+ *
301
+ * @param on - Whether the edge is on the traced path.
302
+ */
303
+ function traceMarkOf(on) {
304
+ return { "data-trace": on ? "on" : "off" };
305
+ }
306
+ //#endregion
307
+ //#region src/layout/size.ts
308
+ /**
309
+ * Size of a node that states none and has not been measured, in pixels.
310
+ */
311
+ const SIZE = {
312
+ height: 44,
313
+ width: 256
314
+ };
315
+ /**
316
+ * Returns the size a layout places a node by: the measured size, the stated size, the initial
317
+ * size, or the default.
318
+ */
319
+ function sizeOf(node) {
320
+ return {
321
+ height: node.measured?.height ?? node.height ?? node.initialHeight ?? SIZE.height,
322
+ width: node.measured?.width ?? node.width ?? node.initialWidth ?? SIZE.width
323
+ };
324
+ }
325
+ //#endregion
326
+ //#region src/layout/rank.ts
327
+ /**
328
+ * Places a node graph's nodes in ranks with dagre, so its edges run down the canvas or across it.
329
+ *
330
+ * @remarks
331
+ * Dagre centres a node on the point it returns, and React Flow places a node by its top-left
332
+ * corner, so each position is that point less half the node's size. A node's size is the size
333
+ * React Flow measured, else its stated `width` and `height`, else its `initialWidth` and
334
+ * `initialHeight`, else 256 by 44 pixels. A node comes back with that size as its initial size,
335
+ * which React Flow applies to the node's wrapper until it measures the card inside and which the
336
+ * overview map sizes the node by. After the measurement the wrapper, and the focus ring on it,
337
+ * fit the card. Ranks are 128px apart and the nodes of one
338
+ * rank 48px apart, which leaves room for the names of ports outside a node's box. An edge whose
339
+ * ends are not both nodes of the graph takes no part. Dagre knows no ports, and it places the
340
+ * targets of one node in the reverse of the order their edges are added, so the edges are added
341
+ * last first: the targets of a node, and the sources of a node, follow the order of their edges,
342
+ * and edges listed in their ports' order do not cross.
343
+ */
344
+ /**
345
+ * Space between two ranks and between two nodes of one rank, in pixels.
346
+ */
347
+ const SPACING = {
348
+ nodesep: 48,
349
+ ranksep: 128
350
+ };
351
+ /**
352
+ * Returns the nodes placed in ranks, each with the size it was placed by as its initial size and
353
+ * the sides its edges leave and enter.
354
+ *
355
+ * @typeParam N - One node of the graph.
356
+ * @param nodes - The graph's nodes, in any order.
357
+ * @param edges - The links between the nodes, of which one to a node outside `nodes` takes no part.
358
+ * @param options - The direction and the spacing.
359
+ */
360
+ function layoutGraph(nodes, edges, options = {}) {
361
+ const { direction = "down", nodesep = SPACING.nodesep, ranksep = SPACING.ranksep } = options;
362
+ const graph = new Graph();
363
+ const down = direction === "down";
364
+ graph.setDefaultEdgeLabel(() => ({}));
365
+ graph.setGraph({
366
+ nodesep,
367
+ rankdir: down ? "TB" : "LR",
368
+ ranksep
369
+ });
370
+ for (const node of nodes) graph.setNode(node.id, { ...sizeOf(node) });
371
+ for (const edge of edges.toReversed()) if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) graph.setEdge(edge.source, edge.target);
372
+ layout(graph);
373
+ return nodes.map((node) => {
374
+ const { height, width } = sizeOf(node);
375
+ const { x = 0, y = 0 } = graph.node(node.id);
376
+ return {
377
+ ...node,
378
+ initialHeight: height,
379
+ initialWidth: width,
380
+ position: {
381
+ x: x - width / 2,
382
+ y: y - height / 2
383
+ },
384
+ sourcePosition: down ? Position.Bottom : Position.Right,
385
+ targetPosition: down ? Position.Top : Position.Left
386
+ };
387
+ });
388
+ }
389
+ //#endregion
390
+ //#region src/directed-graph/view.ts
391
+ /**
392
+ * Returns the ids of the nodes the canvas shows.
393
+ */
394
+ function shownOf(input, hidden, traced) {
395
+ const kept = input.nodes.map((node) => node.id).filter((id) => !hidden.has(id));
396
+ if (input.trace !== "isolate" || traced === void 0) return new Set(kept);
397
+ return new Set(kept.filter((id) => id === input.focus || traced.upstream.has(id) || traced.downstream.has(id)));
398
+ }
399
+ /**
400
+ * Returns the relation of each shown node to the focused node, while a trace shows relations.
401
+ */
402
+ function relationsOf(input, shown, traced) {
403
+ const { focus, trace } = input;
404
+ if (focus === null || traced === void 0 || trace === "off") return /* @__PURE__ */ new Map();
405
+ return new Map([...shown].map((id) => [id, relationOf(id, focus, traced)]));
406
+ }
407
+ /**
408
+ * Returns the word for a relation, or undefined for an unrelated node.
409
+ */
410
+ function tagOf$1(relation, words) {
411
+ if (relation === "focus") return words.focusLabel;
412
+ if (relation === "upstream") return words.upstreamLabel;
413
+ return relation === "downstream" ? words.downstreamLabel : void 0;
414
+ }
415
+ /**
416
+ * Returns a node's branch control: the number of nodes a press hides or shows, or undefined while
417
+ * a press changes nothing.
418
+ */
419
+ function branchOf(id, input, scope) {
420
+ if (!input.collapsible) return void 0;
421
+ const expanded = !input.collapsed.has(id);
422
+ const toggled = new Set(input.collapsed);
423
+ if (expanded) toggled.add(id);
424
+ else toggled.delete(id);
425
+ const count = Math.abs(hiddenBy(scope.ids, input.edges, toggled).size - scope.hidden.size);
426
+ const label = expanded ? input.words.collapseLabel(count) : input.words.expandLabel(count);
427
+ return count === 0 ? void 0 : {
428
+ expanded,
429
+ label
430
+ };
431
+ }
432
+ /**
433
+ * Returns the card of one node, unplaced.
434
+ */
435
+ function cardOf(node, input, scope) {
436
+ const { words } = input;
437
+ const relation = scope.relations.get(node.id);
438
+ const tag = tagOf$1(relation, words);
439
+ const size = input.sizes.get(node.id);
440
+ const data = {
441
+ branch: branchOf(node.id, input, scope),
442
+ detail: node.detail,
443
+ dimmed: relation === "unrelated",
444
+ icon: node.icon,
445
+ inputs: [{
446
+ id: "in",
447
+ label: words.upstreamLabel,
448
+ unused: !scope.entered.has(node.id)
449
+ }],
450
+ kind: node.kind,
451
+ label: node.label,
452
+ outputs: [{
453
+ id: "out",
454
+ label: words.downstreamLabel,
455
+ unused: !scope.left.has(node.id)
456
+ }],
457
+ status: node.status,
458
+ tag
459
+ };
460
+ return {
461
+ ariaLabel: tag === void 0 ? node.label : `${node.label}, ${tag}`,
462
+ data,
463
+ id: node.id,
464
+ position: {
465
+ x: 0,
466
+ y: 0
467
+ },
468
+ selected: node.id === input.focus,
469
+ type: "directed",
470
+ ...size === void 0 ? {} : { measured: size }
471
+ };
472
+ }
473
+ /**
474
+ * Returns the edges whose ends are both among the visible nodes, each with the nodes at its ends.
475
+ */
476
+ function keptOf(edges, visible) {
477
+ return edges.flatMap((edge) => {
478
+ const from = visible.get(edge.source);
479
+ const to = visible.get(edge.target);
480
+ return from === void 0 || to === void 0 ? [] : [{
481
+ edge,
482
+ from,
483
+ to
484
+ }];
485
+ });
486
+ }
487
+ /**
488
+ * Returns the kept edges for React Flow, named by their ends and marked on or off a traced path
489
+ * while a trace shows relations.
490
+ */
491
+ function edgesOf$1(input, kept, scope) {
492
+ return kept.map(({ edge, from, to }) => {
493
+ const on = [from, to].every((end) => scope.relations.get(end.id) !== "unrelated");
494
+ return {
495
+ ariaLabel: input.words.edgeName({
496
+ source: from.label,
497
+ target: to.label
498
+ }),
499
+ id: edge.id ?? `${from.id}-${to.id}`,
500
+ selectable: false,
501
+ source: from.id,
502
+ target: to.id,
503
+ ...scope.relations.size === 0 ? {} : { domAttributes: traceMarkOf(on) }
504
+ };
505
+ });
506
+ }
507
+ /**
508
+ * Returns the trace of the focused node in numbers, named by the node's label, else by its id.
509
+ */
510
+ function countsOf(input, traced) {
511
+ const { focus, nodes } = input;
512
+ if (focus === null || traced === void 0) return void 0;
513
+ return {
514
+ downstream: traced.downstream.size,
515
+ name: nodes.find((node) => node.id === focus)?.label ?? focus,
516
+ upstream: traced.upstream.size
517
+ };
518
+ }
519
+ /**
520
+ * Returns the view of a directed graph: its cards and edges, placed once every node has been
521
+ * measured.
522
+ *
523
+ * @param input - The graph, its state, the measured sizes and the words.
524
+ */
525
+ function viewOf$1(input) {
526
+ const ids = input.nodes.map((node) => node.id);
527
+ const complete = ids.every((id) => input.sizes.has(id));
528
+ const traced = input.focus === null ? void 0 : traceGraph(input.edges, input.focus, input.depth);
529
+ const hidden = input.collapsible ? hiddenBy(ids, input.edges, input.collapsed) : /* @__PURE__ */ new Set();
530
+ const shown = complete ? shownOf(input, hidden, traced) : new Set(ids);
531
+ const visible = new Map(input.nodes.filter((node) => shown.has(node.id)).map((node) => [node.id, node]));
532
+ const kept = keptOf(input.edges, visible);
533
+ const scope = {
534
+ entered: new Set(kept.map(({ to }) => to.id)),
535
+ hidden,
536
+ ids,
537
+ left: new Set(kept.map(({ from }) => from.id)),
538
+ relations: relationsOf(input, shown, traced)
539
+ };
540
+ const cards = [...visible.values()].map((node) => cardOf(node, input, scope));
541
+ const edges = edgesOf$1(input, kept, scope);
542
+ return {
543
+ complete,
544
+ counts: countsOf(input, traced),
545
+ edges,
546
+ nodes: complete ? layoutGraph(cards, edges, { direction: input.direction }) : cards
547
+ };
548
+ }
549
+ //#endregion
550
+ //#region src/graph/changes.ts
551
+ /**
552
+ * Returns the id of the node the changes select, `null` when they only clear the selection, or
553
+ * undefined when they change no selection.
554
+ */
555
+ function selectedBy(changes) {
556
+ const selections = changes.filter((change) => change.type === "select");
557
+ if (selections.length === 0) return void 0;
558
+ return selections.find((change) => change.selected)?.id ?? null;
559
+ }
560
+ /**
561
+ * Returns the sizes with every size the changes measured, or the same sizes when they measured
562
+ * none that differs.
563
+ *
564
+ * @param sizes - The sizes measured before.
565
+ * @param changes - The changes React Flow reported.
566
+ */
567
+ function measuredBy(sizes, changes) {
568
+ const next = new Map(sizes);
569
+ let changed = false;
570
+ for (const change of changes) if (change.type === "dimensions" && change.dimensions !== void 0) {
571
+ const { height, width } = change.dimensions;
572
+ const known = sizes.get(change.id);
573
+ if (known?.height !== height || known.width !== width) {
574
+ next.set(change.id, {
575
+ height,
576
+ width
577
+ });
578
+ changed = true;
579
+ }
580
+ }
581
+ return changed ? next : sizes;
582
+ }
583
+ /**
584
+ * Returns the positions with every position the changes moved a node to, or the same positions
585
+ * when they moved none.
586
+ *
587
+ * @param positions - The positions people moved nodes to before.
588
+ * @param changes - The changes React Flow reported.
589
+ */
590
+ function movedBy(positions, changes) {
591
+ const moves = changes.flatMap((change) => change.type === "position" && change.position !== void 0 ? [[change.id, change.position]] : []);
592
+ return moves.length === 0 ? positions : new Map([...positions, ...moves]);
593
+ }
594
+ //#endregion
595
+ //#region src/directed-graph/state.ts
596
+ /**
597
+ * Keeps a directed graph's focus, closed branches and measured sizes, and shares the function that
598
+ * opens and closes a branch with the graph's cards.
599
+ *
600
+ * @remarks
601
+ * The focus and the closed branches are controlled where the caller passes them. A focus of
602
+ * `null` is a controlled graph with no focus, because `undefined` leaves the focus to the graph.
603
+ * React Flow renders each card inside the canvas, where the graph's state is out of reach of the
604
+ * card's props, so the graph provides the function around the canvas.
605
+ */
606
+ /**
607
+ * Provides the function that opens and closes the branch of the node whose id it receives, and
608
+ * reads it back.
609
+ */
610
+ const [BranchProvider, useToggle] = createRequiredContext("DirectedGraph");
611
+ /**
612
+ * Returns the ids of the closed branches after a press on a node's branch control.
613
+ */
614
+ function toggled(closed, id) {
615
+ return closed.includes(id) ? closed.filter((known) => known !== id) : [...closed, id];
616
+ }
617
+ /**
618
+ * Returns the graph's view in its current state and the functions that change the state.
619
+ *
620
+ * @param props - The graph, its focus and branches, its words and its direction.
621
+ */
622
+ function useTraced(props) {
623
+ const $ = c(36);
624
+ let t0;
625
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
626
+ t0 = /* @__PURE__ */ new Map();
627
+ $[0] = t0;
628
+ } else t0 = $[0];
629
+ const [sizes, setSizes] = useState(t0);
630
+ const t1 = props.defaultFocus ?? null;
631
+ let t2;
632
+ if ($[1] !== props.focus || $[2] !== props.onFocusChange || $[3] !== t1) {
633
+ t2 = {
634
+ defaultValue: t1,
635
+ onChange: props.onFocusChange,
636
+ value: props.focus
637
+ };
638
+ $[1] = props.focus;
639
+ $[2] = props.onFocusChange;
640
+ $[3] = t1;
641
+ $[4] = t2;
642
+ } else t2 = $[4];
643
+ const [focus, setFocus] = useControllableState(t2);
644
+ let t3;
645
+ if ($[5] !== props.defaultCollapsed) {
646
+ t3 = props.defaultCollapsed ?? [];
647
+ $[5] = props.defaultCollapsed;
648
+ $[6] = t3;
649
+ } else t3 = $[6];
650
+ let t4;
651
+ if ($[7] !== t3) {
652
+ t4 = [...t3];
653
+ $[7] = t3;
654
+ $[8] = t4;
655
+ } else t4 = $[8];
656
+ let t5;
657
+ if ($[9] !== props.collapsed) {
658
+ t5 = props.collapsed === void 0 ? void 0 : [...props.collapsed];
659
+ $[9] = props.collapsed;
660
+ $[10] = t5;
661
+ } else t5 = $[10];
662
+ let t6;
663
+ if ($[11] !== props.onCollapsedChange || $[12] !== t4 || $[13] !== t5) {
664
+ t6 = {
665
+ defaultValue: t4,
666
+ onChange: props.onCollapsedChange,
667
+ value: t5
668
+ };
669
+ $[11] = props.onCollapsedChange;
670
+ $[12] = t4;
671
+ $[13] = t5;
672
+ $[14] = t6;
673
+ } else t6 = $[14];
674
+ const [collapsed, setCollapsed] = useControllableState(t6);
675
+ let t7;
676
+ if ($[15] !== collapsed || $[16] !== focus || $[17] !== props.collapsible || $[18] !== props.depth || $[19] !== props.direction || $[20] !== props.edges || $[21] !== props.nodes || $[22] !== props.trace || $[23] !== props.words || $[24] !== sizes) {
677
+ t7 = viewOf$1({
678
+ collapsed: new Set(collapsed),
679
+ collapsible: props.collapsible === true,
680
+ depth: props.depth ?? Number.POSITIVE_INFINITY,
681
+ direction: props.direction,
682
+ edges: props.edges,
683
+ focus,
684
+ nodes: props.nodes,
685
+ sizes,
686
+ trace: props.trace ?? "highlight",
687
+ words: props.words
688
+ });
689
+ $[15] = collapsed;
690
+ $[16] = focus;
691
+ $[17] = props.collapsible;
692
+ $[18] = props.depth;
693
+ $[19] = props.direction;
694
+ $[20] = props.edges;
695
+ $[21] = props.nodes;
696
+ $[22] = props.trace;
697
+ $[23] = props.words;
698
+ $[24] = sizes;
699
+ $[25] = t7;
700
+ } else t7 = $[25];
701
+ const view = t7;
702
+ let t8;
703
+ let t9;
704
+ if ($[26] !== setFocus) {
705
+ t8 = () => {
706
+ setFocus(null);
707
+ };
708
+ t9 = (changes) => {
709
+ const selected = selectedBy(changes);
710
+ if (selected !== void 0) setFocus(selected);
711
+ setSizes((known) => measuredBy(known, changes));
712
+ };
713
+ $[26] = setFocus;
714
+ $[27] = t8;
715
+ $[28] = t9;
716
+ } else {
717
+ t8 = $[27];
718
+ t9 = $[28];
719
+ }
720
+ let t10;
721
+ if ($[29] !== setCollapsed) {
722
+ t10 = (id) => {
723
+ setCollapsed((closed) => toggled(closed, id));
724
+ };
725
+ $[29] = setCollapsed;
726
+ $[30] = t10;
727
+ } else t10 = $[30];
728
+ let t11;
729
+ if ($[31] !== t10 || $[32] !== t8 || $[33] !== t9 || $[34] !== view) {
730
+ t11 = {
731
+ clear: t8,
732
+ onNodesChange: t9,
733
+ toggle: t10,
734
+ view
735
+ };
736
+ $[31] = t10;
737
+ $[32] = t8;
738
+ $[33] = t9;
739
+ $[34] = view;
740
+ $[35] = t11;
741
+ } else t11 = $[35];
742
+ return t11;
743
+ }
744
+ //#endregion
745
+ //#region src/graph/context.ts
746
+ /**
747
+ * Binds the graph recipe to its parts.
748
+ *
749
+ * @remarks
750
+ * The binding is apart from the recipe, because a compiler reads the recipe at build time and the
751
+ * binding imports the runtime.
752
+ */
753
+ /**
754
+ * Binds the recipe once. The root provides the variants and every other part reads them.
755
+ */
756
+ const { withContext, withProvider } = createSlotRecipeContext(recipe);
757
+ //#endregion
758
+ //#region src/graph/diffed.ts
759
+ /**
760
+ * Shares a comparison's changes with a canvas's nodes, names the changes, and marks each edge with
761
+ * its change for the recipe.
762
+ *
763
+ * @remarks
764
+ * A canvas given `changes` provides each node's change by its id and the words of the changes. A
765
+ * node's card reads its own through React Flow's node id: a changed, added or removed card takes
766
+ * the change as its tag, and an unchanged card recedes. An edge takes `data-change` on React
767
+ * Flow's element, which the recipe reads. A node or an edge the comparison does not list, and
768
+ * every node and edge of a canvas without `changes`, is marked by nothing. Each marked copy of an
769
+ * edge is kept for the edge it was made from while its change is the same, so an unchanged edge
770
+ * yields the same copy on every render.
771
+ */
772
+ /**
773
+ * Provides the changes a canvas marks, or undefined while it marks none.
774
+ */
775
+ const DiffContext = createContext(void 0);
776
+ /**
777
+ * Marked copy of each edge object, kept while the edge's change is the same.
778
+ */
779
+ const MARKED = /* @__PURE__ */ new WeakMap();
780
+ /**
781
+ * Returns the word a change is marked by, or undefined for no change.
782
+ */
783
+ function wordOf(change, words) {
784
+ if (change === "added") return words.addedLabel ?? "Added";
785
+ if (change === "changed") return words.changedLabel ?? "Changed";
786
+ return change === "removed" ? words.removedLabel ?? "Removed" : void 0;
787
+ }
788
+ /**
789
+ * Returns the changes a canvas marks from its comparison and words, or undefined without a
790
+ * comparison.
791
+ */
792
+ function diffedOf(changes, words) {
793
+ if (changes === void 0) return void 0;
794
+ return {
795
+ edges: new Map(changes.edges.map(({ change, id }) => [id, change])),
796
+ nodes: new Map(changes.nodes.map(({ change, id }) => [id, change])),
797
+ words
798
+ };
799
+ }
800
+ /**
801
+ * Returns the copy of an edge marked with a change: the kept copy while its change matches, else a
802
+ * new copy, which replaces it.
803
+ */
804
+ function marked(kept, edge, change) {
805
+ const known = kept.get(edge);
806
+ if (known?.change === change) return known.copy;
807
+ const attributes = {
808
+ ...edge.domAttributes,
809
+ "data-change": change
810
+ };
811
+ const copy = {
812
+ ...edge,
813
+ domAttributes: attributes
814
+ };
815
+ kept.set(edge, {
816
+ change,
817
+ copy
818
+ });
819
+ return copy;
820
+ }
821
+ /**
822
+ * Returns the edges, each the comparison lists marked with its change.
823
+ *
824
+ * @typeParam E - One edge of the graph.
825
+ */
826
+ function markedEdges(edges, diffed) {
827
+ const kept = MARKED;
828
+ return edges.map((edge) => {
829
+ const change = diffed.edges.get(edge.id);
830
+ return change === void 0 ? edge : marked(kept, edge, change);
831
+ });
832
+ }
833
+ /**
834
+ * Returns the change of the node a card renders for, or undefined outside a canvas that marks
835
+ * changes and for a node the comparison does not list.
836
+ */
837
+ function useNodeChange() {
838
+ const $ = c(4);
839
+ const id = useNodeId();
840
+ const diffed = use(DiffContext);
841
+ if (id === null || diffed === void 0) return;
842
+ let t0;
843
+ if ($[0] !== diffed.nodes || $[1] !== diffed.words || $[2] !== id) {
844
+ const change = diffed.nodes.get(id);
845
+ t0 = change === void 0 ? void 0 : {
846
+ change,
847
+ word: wordOf(change, diffed.words)
848
+ };
849
+ $[0] = diffed.nodes;
850
+ $[1] = diffed.words;
851
+ $[2] = id;
852
+ $[3] = t0;
853
+ } else t0 = $[3];
854
+ return t0;
855
+ }
856
+ //#endregion
857
+ //#region src/graph/ports.tsx
858
+ /**
859
+ * Renders the port's name with the recipe's port name class.
860
+ */
861
+ const Name$1 = withContext("span", "portName");
862
+ /**
863
+ * Returns the side of the node the ports of a kind are on.
864
+ */
865
+ function sideOf(direction, kind) {
866
+ if (direction === "right") return kind === "target" ? Position.Left : Position.Right;
867
+ return kind === "target" ? Position.Top : Position.Bottom;
868
+ }
869
+ /**
870
+ * Renders a handle per port, spread along the side, each containing its name.
871
+ *
872
+ * @param props - The direction, the kind and the ports.
873
+ */
874
+ function Ports(t0) {
875
+ const $ = c(17);
876
+ const { direction, kind, ports } = t0;
877
+ const connectable = useStore(_temp$4);
878
+ let t1;
879
+ if ($[0] !== direction || $[1] !== kind) {
880
+ t1 = sideOf(direction, kind);
881
+ $[0] = direction;
882
+ $[1] = kind;
883
+ $[2] = t1;
884
+ } else t1 = $[2];
885
+ const side = t1;
886
+ const shown = ports.length > 1;
887
+ let t2;
888
+ if ($[3] !== connectable || $[4] !== kind || $[5] !== ports || $[6] !== shown || $[7] !== side) {
889
+ let t3;
890
+ if ($[9] !== connectable || $[10] !== kind || $[11] !== ports.length || $[12] !== shown || $[13] !== side) {
891
+ t3 = (port, index) => {
892
+ const open = connectable && port.disabled !== true;
893
+ const along = { "--graph-port-along": `${String((index + 1) / (ports.length + 1) * 100)}%` };
894
+ return /* @__PURE__ */ jsx(Handle, {
895
+ "data-unused": port.unused === true ? "" : void 0,
896
+ id: port.id,
897
+ isConnectable: open,
898
+ isConnectableStart: open,
899
+ position: side,
900
+ style: along,
901
+ type: kind,
902
+ children: /* @__PURE__ */ jsx(Name$1, {
903
+ "data-hidden": shown ? void 0 : "",
904
+ children: port.label
905
+ })
906
+ }, port.id);
907
+ };
908
+ $[9] = connectable;
909
+ $[10] = kind;
910
+ $[11] = ports.length;
911
+ $[12] = shown;
912
+ $[13] = side;
913
+ $[14] = t3;
914
+ } else t3 = $[14];
915
+ t2 = ports.map(t3);
916
+ $[3] = connectable;
917
+ $[4] = kind;
918
+ $[5] = ports;
919
+ $[6] = shown;
920
+ $[7] = side;
921
+ $[8] = t2;
922
+ } else t2 = $[8];
923
+ let t3;
924
+ if ($[15] !== t2) {
925
+ t3 = /* @__PURE__ */ jsx(Fragment, { children: t2 });
926
+ $[15] = t2;
927
+ $[16] = t3;
928
+ } else t3 = $[16];
929
+ return t3;
930
+ }
931
+ function _temp$4(state) {
932
+ return state.nodesConnectable;
933
+ }
934
+ //#endregion
935
+ //#region src/graph/state.ts
936
+ /**
937
+ * Shares the graph's direction, its caption's identity and the edges' remove controls with the
938
+ * parts.
939
+ *
940
+ * @remarks
941
+ * A node places its ports on the sides its edges leave and enter. It reads the direction from the
942
+ * root, so a caller states it once per graph. The caption reports itself to the root, which names
943
+ * the figure by it while it renders.
944
+ */
945
+ /**
946
+ * Provides the way the graph's edges run, down unless a root states otherwise.
947
+ */
948
+ const DirectionContext = createContext("down");
949
+ /**
950
+ * Returns the way the graph's edges run.
951
+ */
952
+ function useGraphDirection() {
953
+ return use(DirectionContext);
954
+ }
955
+ /**
956
+ * Provides the root's setter the caption reports itself through, and the hook that reports it.
957
+ */
958
+ const [LabellingProvider, useLabelled] = createLabelling("Graph");
959
+ /**
960
+ * Provides the ID the caption renders with, and reads it back.
961
+ */
962
+ const [CaptionProvider, useCaptionId] = createRequiredContext("GraphCaption");
963
+ /**
964
+ * Provides the edges' remove glyph, the words that name each remove control, and whether the
965
+ * canvas takes edits.
966
+ */
967
+ const RemovalContext = createContext({
968
+ editable: false,
969
+ glyph: void 0,
970
+ name: removeNameOf
971
+ });
972
+ //#endregion
973
+ //#region src/graph/node.tsx
974
+ /**
975
+ * Renders the card with the recipe's node class.
976
+ */
977
+ const Card$1 = withContext("div", "node");
978
+ /**
979
+ * Renders the header row with the recipe's node header class.
980
+ */
981
+ const Header = withContext("div", "nodeHeader");
982
+ /**
983
+ * Renders the icon's box with the recipe's node icon class.
984
+ */
985
+ const Icon = withContext("span", "nodeIcon");
986
+ /**
987
+ * Renders the title and the subtitle's column with the recipe's node text class.
988
+ */
989
+ const Text = withContext("span", "nodeText");
990
+ /**
991
+ * Renders the title with the recipe's node title class.
992
+ */
993
+ const Title = withContext("span", "nodeTitle");
994
+ /**
995
+ * Renders the subtitle with the recipe's node subtitle class.
996
+ */
997
+ const Subtitle = withContext("span", "nodeSubtitle");
998
+ /**
999
+ * Renders the actions' row with the recipe's node actions class.
1000
+ */
1001
+ const Actions = withContext("span", "nodeActions");
1002
+ /**
1003
+ * Renders the body with the recipe's node body class.
1004
+ */
1005
+ const Body = withContext("div", "nodeBody");
1006
+ /**
1007
+ * Renders the problem line with the recipe's node problem class.
1008
+ */
1009
+ const Problem = withContext("div", "nodeProblem");
1010
+ /**
1011
+ * Renders the tag's box with the recipe's node tag class.
1012
+ */
1013
+ const Tag = withContext("span", "nodeTag");
1014
+ /**
1015
+ * Renders the branch control's box with the recipe's node branch class.
1016
+ */
1017
+ const Branch = withContext("span", "nodeBranch");
1018
+ /**
1019
+ * Palette of each change's tag.
1020
+ */
1021
+ const PALETTES = {
1022
+ added: "success",
1023
+ changed: "warning",
1024
+ removed: "error",
1025
+ unchanged: "neutral"
1026
+ };
1027
+ /**
1028
+ * Mark of each change's tag, before its word, so a color is never the only signal.
1029
+ */
1030
+ const MARKS = {
1031
+ added: "+",
1032
+ changed: "~",
1033
+ removed: "−",
1034
+ unchanged: ""
1035
+ };
1036
+ /**
1037
+ * Returns the card's header: the icon, the title and the subtitle, the status and the actions.
1038
+ */
1039
+ function headerOf({ actions, icon, status, subtitle, title }) {
1040
+ return /* @__PURE__ */ jsxs(Header, { children: [
1041
+ icon === void 0 ? null : /* @__PURE__ */ jsx(Icon, {
1042
+ "aria-hidden": true,
1043
+ children: icon
1044
+ }),
1045
+ /* @__PURE__ */ jsxs(Text, { children: [/* @__PURE__ */ jsx(Title, { children: title }), subtitle === void 0 ? null : /* @__PURE__ */ jsx(Subtitle, { children: subtitle })] }),
1046
+ status === void 0 ? null : /* @__PURE__ */ jsxs(Status.Root, {
1047
+ palette: status.palette,
1048
+ size: "sm",
1049
+ children: [/* @__PURE__ */ jsx(Status.Indicator, {}), status.label]
1050
+ }),
1051
+ actions === void 0 ? null : /* @__PURE__ */ jsx(Actions, {
1052
+ className: "nodrag nopan",
1053
+ children: actions
1054
+ })
1055
+ ] });
1056
+ }
1057
+ /**
1058
+ * Returns the card's body, its problem line while the node is invalid, and its tag.
1059
+ */
1060
+ function contentOf({ children, invalid, problem, tag }) {
1061
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1062
+ children === void 0 ? null : /* @__PURE__ */ jsx(Body, { children }),
1063
+ invalid === true && problem !== void 0 ? /* @__PURE__ */ jsx(Problem, { children: problem }) : null,
1064
+ tag === void 0 ? null : /* @__PURE__ */ jsx(Tag, { children: tag })
1065
+ ] });
1066
+ }
1067
+ /**
1068
+ * Returns the tag of a card's change: a badge with the change's mark and word, or nothing for an
1069
+ * unchanged node and for a node without a change.
1070
+ */
1071
+ function changeTagOf(change) {
1072
+ if (change?.word === void 0) return void 0;
1073
+ return /* @__PURE__ */ jsx(Badge, {
1074
+ "aria-hidden": true,
1075
+ palette: PALETTES[change.change],
1076
+ size: "sm",
1077
+ variant: "surface",
1078
+ children: `${MARKS[change.change]} ${change.word}`
1079
+ });
1080
+ }
1081
+ /**
1082
+ * Renders the card with its ports, header, body, problem line, tag and branch control.
1083
+ *
1084
+ * @remarks
1085
+ * The branch control renders after the ports, so it covers the handle of the side it is on.
1086
+ * @param props - The title, the details, the ports and the states.
1087
+ */
1088
+ function Node$1(props) {
1089
+ const $ = c(28);
1090
+ const { branch, inputs: t0, outputs: t1 } = props;
1091
+ let t2;
1092
+ if ($[0] !== t0) {
1093
+ t2 = t0 === void 0 ? [] : t0;
1094
+ $[0] = t0;
1095
+ $[1] = t2;
1096
+ } else t2 = $[1];
1097
+ const inputs = t2;
1098
+ let t3;
1099
+ if ($[2] !== t1) {
1100
+ t3 = t1 === void 0 ? [] : t1;
1101
+ $[2] = t1;
1102
+ $[3] = t3;
1103
+ } else t3 = $[3];
1104
+ const outputs = t3;
1105
+ const flow = useGraphDirection();
1106
+ const change = useNodeChange();
1107
+ const direction = props.direction ?? flow;
1108
+ const t4 = props.dimmed ?? change?.change === "unchanged" ? "" : void 0;
1109
+ const t5 = props.invalid === true ? "" : void 0;
1110
+ let t6;
1111
+ if ($[4] !== direction || $[5] !== inputs) {
1112
+ t6 = /* @__PURE__ */ jsx(Ports, {
1113
+ direction,
1114
+ kind: "target",
1115
+ ports: inputs
1116
+ });
1117
+ $[4] = direction;
1118
+ $[5] = inputs;
1119
+ $[6] = t6;
1120
+ } else t6 = $[6];
1121
+ let t7;
1122
+ if ($[7] !== props) {
1123
+ t7 = headerOf(props);
1124
+ $[7] = props;
1125
+ $[8] = t7;
1126
+ } else t7 = $[8];
1127
+ let t8;
1128
+ if ($[9] !== change || $[10] !== props.tag) {
1129
+ t8 = props.tag ?? changeTagOf(change);
1130
+ $[9] = change;
1131
+ $[10] = props.tag;
1132
+ $[11] = t8;
1133
+ } else t8 = $[11];
1134
+ let t9;
1135
+ if ($[12] !== props || $[13] !== t8) {
1136
+ t9 = contentOf({
1137
+ ...props,
1138
+ tag: t8
1139
+ });
1140
+ $[12] = props;
1141
+ $[13] = t8;
1142
+ $[14] = t9;
1143
+ } else t9 = $[14];
1144
+ let t10;
1145
+ if ($[15] !== direction || $[16] !== outputs) {
1146
+ t10 = /* @__PURE__ */ jsx(Ports, {
1147
+ direction,
1148
+ kind: "source",
1149
+ ports: outputs
1150
+ });
1151
+ $[15] = direction;
1152
+ $[16] = outputs;
1153
+ $[17] = t10;
1154
+ } else t10 = $[17];
1155
+ let t11;
1156
+ if ($[18] !== branch) {
1157
+ t11 = branch === void 0 ? null : /* @__PURE__ */ jsx(Branch, { children: branch });
1158
+ $[18] = branch;
1159
+ $[19] = t11;
1160
+ } else t11 = $[19];
1161
+ let t12;
1162
+ if ($[20] !== t10 || $[21] !== t11 || $[22] !== t4 || $[23] !== t5 || $[24] !== t6 || $[25] !== t7 || $[26] !== t9) {
1163
+ t12 = /* @__PURE__ */ jsxs(Card$1, {
1164
+ "data-dimmed": t4,
1165
+ "data-invalid": t5,
1166
+ children: [
1167
+ t6,
1168
+ t7,
1169
+ t9,
1170
+ t10,
1171
+ t11
1172
+ ]
1173
+ });
1174
+ $[20] = t10;
1175
+ $[21] = t11;
1176
+ $[22] = t4;
1177
+ $[23] = t5;
1178
+ $[24] = t6;
1179
+ $[25] = t7;
1180
+ $[26] = t9;
1181
+ $[27] = t12;
1182
+ } else t12 = $[27];
1183
+ return t12;
1184
+ }
1185
+ //#endregion
1186
+ //#region src/directed-graph/card.tsx
1187
+ /**
1188
+ * Renders the node's card from its data.
1189
+ *
1190
+ * @param props - React Flow's node props: the node's id and its data among them.
1191
+ */
1192
+ function Card(t0) {
1193
+ const $ = c(17);
1194
+ const { data, id } = t0;
1195
+ const toggle = useToggle();
1196
+ const { branch, tag } = data;
1197
+ let t1;
1198
+ if ($[0] !== branch || $[1] !== id || $[2] !== toggle) {
1199
+ t1 = branch === void 0 ? void 0 : /* @__PURE__ */ jsx(Button, {
1200
+ "aria-expanded": branch.expanded,
1201
+ className: "nokey nodrag nopan",
1202
+ onClick: (event) => {
1203
+ event.stopPropagation();
1204
+ toggle(id);
1205
+ },
1206
+ palette: "neutral",
1207
+ size: "xs",
1208
+ variant: "surface",
1209
+ children: branch.label
1210
+ });
1211
+ $[0] = branch;
1212
+ $[1] = id;
1213
+ $[2] = toggle;
1214
+ $[3] = t1;
1215
+ } else t1 = $[3];
1216
+ let t2;
1217
+ if ($[4] !== tag) {
1218
+ t2 = tag === void 0 ? void 0 : /* @__PURE__ */ jsx(Badge, {
1219
+ "aria-hidden": true,
1220
+ palette: "neutral",
1221
+ size: "sm",
1222
+ variant: "surface",
1223
+ children: tag
1224
+ });
1225
+ $[4] = tag;
1226
+ $[5] = t2;
1227
+ } else t2 = $[5];
1228
+ let t3;
1229
+ if ($[6] !== data.detail || $[7] !== data.dimmed || $[8] !== data.icon || $[9] !== data.inputs || $[10] !== data.kind || $[11] !== data.label || $[12] !== data.outputs || $[13] !== data.status || $[14] !== t1 || $[15] !== t2) {
1230
+ t3 = /* @__PURE__ */ jsx(Node$1, {
1231
+ branch: t1,
1232
+ dimmed: data.dimmed,
1233
+ icon: data.icon,
1234
+ inputs: data.inputs,
1235
+ outputs: data.outputs,
1236
+ status: data.status,
1237
+ subtitle: data.kind,
1238
+ tag: t2,
1239
+ title: data.label,
1240
+ children: data.detail
1241
+ });
1242
+ $[6] = data.detail;
1243
+ $[7] = data.dimmed;
1244
+ $[8] = data.icon;
1245
+ $[9] = data.inputs;
1246
+ $[10] = data.kind;
1247
+ $[11] = data.label;
1248
+ $[12] = data.outputs;
1249
+ $[13] = data.status;
1250
+ $[14] = t1;
1251
+ $[15] = t2;
1252
+ $[16] = t3;
1253
+ } else t3 = $[16];
1254
+ return t3;
1255
+ }
1256
+ //#endregion
1257
+ //#region src/graph/drop.ts
1258
+ /**
1259
+ * Type of the drag data a palette item writes its id under.
1260
+ */
1261
+ const ITEM = "application/x-stealth-graph-item";
1262
+ /**
1263
+ * Returns the handlers that take a palette item's drop and report it with the point it was dropped
1264
+ * at, or no handler without a caller to report to.
1265
+ *
1266
+ * @param onDropItem - Called with the item's id and the point it was dropped at.
1267
+ */
1268
+ function useDrop(onDropItem) {
1269
+ const $ = c(4);
1270
+ const flow = useReactFlow();
1271
+ if (onDropItem === void 0) {
1272
+ let t0;
1273
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
1274
+ t0 = {};
1275
+ $[0] = t0;
1276
+ } else t0 = $[0];
1277
+ return t0;
1278
+ }
1279
+ let t0;
1280
+ if ($[1] !== flow || $[2] !== onDropItem) {
1281
+ t0 = {
1282
+ onDragOver: _temp$3,
1283
+ onDrop: (event_0) => {
1284
+ const item = event_0.dataTransfer.getData(ITEM);
1285
+ if (item === "") return;
1286
+ event_0.preventDefault();
1287
+ onDropItem(item, flow.screenToFlowPosition({
1288
+ x: event_0.clientX,
1289
+ y: event_0.clientY
1290
+ }));
1291
+ }
1292
+ };
1293
+ $[1] = flow;
1294
+ $[2] = onDropItem;
1295
+ $[3] = t0;
1296
+ } else t0 = $[3];
1297
+ return t0;
1298
+ }
1299
+ function _temp$3(event) {
1300
+ if (!event.dataTransfer.types.includes("application/x-stealth-graph-item")) return;
1301
+ event.preventDefault();
1302
+ event.dataTransfer.dropEffect = "copy";
1303
+ }
1304
+ /**
1305
+ * Returns a function that returns the middle of what the canvas shows, in the graph's coordinates,
1306
+ * or the graph's origin while no canvas renders.
1307
+ */
1308
+ function useViewCentre() {
1309
+ const $ = c(3);
1310
+ const flow = useReactFlow();
1311
+ const store = useStoreApi();
1312
+ let t0;
1313
+ if ($[0] !== flow || $[1] !== store) {
1314
+ t0 = () => {
1315
+ const { domNode, height, width } = store.getState();
1316
+ if (domNode === null) return {
1317
+ x: 0,
1318
+ y: 0
1319
+ };
1320
+ const box = domNode.getBoundingClientRect();
1321
+ return flow.screenToFlowPosition({
1322
+ x: box.left + width / 2,
1323
+ y: box.top + height / 2
1324
+ });
1325
+ };
1326
+ $[0] = flow;
1327
+ $[1] = store;
1328
+ $[2] = t0;
1329
+ } else t0 = $[2];
1330
+ return t0;
1331
+ }
1332
+ //#endregion
1333
+ //#region src/graph/editing.ts
1334
+ /**
1335
+ * Turns React Flow's changes into whole graphs for a caller's history: a connection, a move that
1336
+ * ends and a removal each arrive once, as the graph after the edit and a step that names it.
1337
+ *
1338
+ * @remarks
1339
+ * A drag reports a node's position on every frame and a last position with `dragging` false, and
1340
+ * an arrow key reports one position with `dragging` false, so a move arrives once, where it ends.
1341
+ * A removal by Delete, Backspace or an edge's remove control arrives once with every node and
1342
+ * edge it removed, and a removed node takes its edges with it. A connection that repeats an edge
1343
+ * the graph has already adds nothing and arrives as nothing. A selection and a measurement are no
1344
+ * edits. The caller's own `onConnect`, `onDelete` and `onNodesChange` run first, so a caller that
1345
+ * applies React Flow's changes keeps doing so.
1346
+ */
1347
+ /**
1348
+ * Returns the positions a move ended at, or no change while no move ended.
1349
+ */
1350
+ function endsOf(changes) {
1351
+ return changes.filter((change) => change.type === "position" && change.dragging === false);
1352
+ }
1353
+ /**
1354
+ * Returns the handlers that report each edit through `onGraphChange` after the caller's own.
1355
+ *
1356
+ * @typeParam N - One node of the graph.
1357
+ * @typeParam E - One edge of the graph.
1358
+ */
1359
+ function editingOf(input) {
1360
+ const { edges, nodes, onGraphChange } = input;
1361
+ return {
1362
+ onConnect: (connection) => {
1363
+ input.onConnect?.(connection);
1364
+ const next = addEdge(connection, edges);
1365
+ const added = next.filter((edge) => !edges.includes(edge)).map(({ id }) => id);
1366
+ if (added.length > 0) onGraphChange({
1367
+ edges: next,
1368
+ nodes
1369
+ }, {
1370
+ edges: added,
1371
+ nodes: [],
1372
+ type: "connect"
1373
+ });
1374
+ },
1375
+ onDelete: (removed) => {
1376
+ input.onDelete?.(removed);
1377
+ const [nodeIds, edgeIds] = [new Set(removed.nodes.map(({ id }) => id)), new Set(removed.edges.map(({ id }) => id))];
1378
+ const graph = {
1379
+ edges: edges.filter(({ id }) => !edgeIds.has(id)),
1380
+ nodes: nodes.filter(({ id }) => !nodeIds.has(id))
1381
+ };
1382
+ onGraphChange(graph, {
1383
+ edges: [...edgeIds],
1384
+ nodes: [...nodeIds],
1385
+ type: "remove"
1386
+ });
1387
+ },
1388
+ onNodesChange: (changes) => {
1389
+ input.onNodesChange?.(changes);
1390
+ const ends = endsOf(changes);
1391
+ if (ends.length > 0) onGraphChange({
1392
+ edges,
1393
+ nodes: applyNodeChanges(ends, nodes)
1394
+ }, {
1395
+ edges: [],
1396
+ nodes: ends.map(({ id }) => id),
1397
+ type: "move"
1398
+ });
1399
+ }
1400
+ };
1401
+ }
1402
+ /**
1403
+ * Returns the handlers a canvas gives React Flow for its edits and drops: the drop target, and the
1404
+ * edit handlers while the caller takes edits through `onGraphChange`.
1405
+ *
1406
+ * @typeParam N - One node of the graph.
1407
+ * @typeParam E - One edge of the graph.
1408
+ */
1409
+ function useEdits(input) {
1410
+ const $ = c(14);
1411
+ const drop = useDrop(input.onDropItem);
1412
+ const { onGraphChange } = input;
1413
+ if (onGraphChange === void 0) return drop;
1414
+ let t0;
1415
+ if ($[0] !== input.edges) {
1416
+ t0 = input.edges ?? [];
1417
+ $[0] = input.edges;
1418
+ $[1] = t0;
1419
+ } else t0 = $[1];
1420
+ let t1;
1421
+ if ($[2] !== input.nodes) {
1422
+ t1 = input.nodes ?? [];
1423
+ $[2] = input.nodes;
1424
+ $[3] = t1;
1425
+ } else t1 = $[3];
1426
+ let t2;
1427
+ let t3;
1428
+ if ($[4] !== drop || $[5] !== input || $[6] !== onGraphChange || $[7] !== t0 || $[8] !== t1) {
1429
+ const graph = {
1430
+ edges: t0,
1431
+ nodes: t1
1432
+ };
1433
+ t2 = drop;
1434
+ t3 = editingOf({
1435
+ ...input,
1436
+ ...graph,
1437
+ onGraphChange
1438
+ });
1439
+ $[4] = drop;
1440
+ $[5] = input;
1441
+ $[6] = onGraphChange;
1442
+ $[7] = t0;
1443
+ $[8] = t1;
1444
+ $[9] = t2;
1445
+ $[10] = t3;
1446
+ } else {
1447
+ t2 = $[9];
1448
+ t3 = $[10];
1449
+ }
1450
+ let t4;
1451
+ if ($[11] !== t2 || $[12] !== t3) {
1452
+ t4 = {
1453
+ ...t2,
1454
+ ...t3
1455
+ };
1456
+ $[11] = t2;
1457
+ $[12] = t3;
1458
+ $[13] = t4;
1459
+ } else t4 = $[13];
1460
+ return t4;
1461
+ }
1462
+ //#endregion
1463
+ //#region src/graph/names.ts
1464
+ /**
1465
+ * Named copy of each node object, kept while the node's name is unchanged.
1466
+ */
1467
+ const NODES = /* @__PURE__ */ new WeakMap();
1468
+ /**
1469
+ * Named copy of each edge object, kept while the edge's name is unchanged.
1470
+ */
1471
+ const EDGES = /* @__PURE__ */ new WeakMap();
1472
+ /**
1473
+ * Returns the name a node takes: its own `ariaLabel`, else its data's string `label`.
1474
+ */
1475
+ function nameOf(node) {
1476
+ const label = node.data["label"];
1477
+ return node.ariaLabel ?? (typeof label === "string" ? label : void 0);
1478
+ }
1479
+ /**
1480
+ * Returns the copy of an element named `name`: the kept copy while its name matches, else a new
1481
+ * copy, which replaces it.
1482
+ */
1483
+ function named(kept, element, name) {
1484
+ const copy = kept.get(element);
1485
+ if (copy?.ariaLabel === name) return copy;
1486
+ const made = {
1487
+ ...element,
1488
+ ariaLabel: name
1489
+ };
1490
+ kept.set(element, made);
1491
+ return made;
1492
+ }
1493
+ /**
1494
+ * Returns no word, for a node or an edge the canvas marks no change on.
1495
+ */
1496
+ function none() {}
1497
+ /**
1498
+ * Returns the nodes, each without a name of its own named by its data's `label`, and each with a
1499
+ * change named with the change's word after its name, else after its id.
1500
+ *
1501
+ * @typeParam N - One node of the graph.
1502
+ * @param nodes - The graph's nodes.
1503
+ * @param changeOf - Returns the word of a node's change by its id, or undefined for none.
1504
+ */
1505
+ function namedNodes(nodes, changeOf = none) {
1506
+ const kept = NODES;
1507
+ return nodes.map((node) => {
1508
+ const name = nameOf(node);
1509
+ const word = changeOf(node.id);
1510
+ if (word !== void 0) return named(kept, node, `${name ?? node.id}, ${word}`);
1511
+ return node.ariaLabel !== void 0 || name === void 0 ? node : named(kept, node, name);
1512
+ });
1513
+ }
1514
+ /**
1515
+ * Returns the edges, each without a name of its own named by the names of its ends, and each with a
1516
+ * change named with the change's word after its name.
1517
+ *
1518
+ * @typeParam E - One edge of the graph.
1519
+ * @param edges - Links between the nodes.
1520
+ * @param nodes - The graph's nodes, whose names name the ends. An end without a name is named by
1521
+ * its id.
1522
+ * @param nameEdge - The canvas's words for an edge between two named ends.
1523
+ * @param changeOf - Returns the word of an edge's change by its id, or undefined for none.
1524
+ */
1525
+ function namedEdges(edges, nodes, nameEdge, changeOf = none) {
1526
+ const kept = EDGES;
1527
+ const names = new Map(nodes.map((node) => [node.id, nameOf(node)]));
1528
+ return edges.map((edge) => {
1529
+ const word = changeOf(edge.id);
1530
+ if (edge.ariaLabel !== void 0 && word === void 0) return edge;
1531
+ const name = edge.ariaLabel ?? nameEdge({
1532
+ source: names.get(edge.source) ?? edge.source,
1533
+ target: names.get(edge.target) ?? edge.target
1534
+ });
1535
+ return named(kept, edge, word === void 0 ? name : `${name}, ${word}`);
1536
+ });
1537
+ }
1538
+ /**
1539
+ * Returns the function that reads the word of a node's or an edge's change, or no word while the
1540
+ * canvas marks no change.
1541
+ */
1542
+ function changesOf(diffed, kind) {
1543
+ return diffed === void 0 ? none : (id) => wordOf(diffed[kind].get(id), diffed.words);
1544
+ }
1545
+ /**
1546
+ * Returns the edges named, and each marked with its change while the canvas marks changes.
1547
+ */
1548
+ function edgesOf(edges, nodes, nameEdge, diffed) {
1549
+ const labelled = namedEdges(edges, nodes, nameEdge, changesOf(diffed, "edges"));
1550
+ return diffed === void 0 ? labelled : markedEdges(labelled, diffed);
1551
+ }
1552
+ /**
1553
+ * Returns the graph with its nodes and edges named, leaving out what the graph leaves out.
1554
+ *
1555
+ * @typeParam N - One node of the graph.
1556
+ * @typeParam E - One edge of the graph.
1557
+ * @param graph - The nodes and the edges, either of which may be absent.
1558
+ * @param nameEdge - The canvas's words for an edge between two named ends.
1559
+ * @param diffed - The changes the canvas marks, or undefined for none.
1560
+ */
1561
+ function namedGraph({ edges, nodes }, nameEdge, diffed) {
1562
+ return omitUndefined({
1563
+ edges: edges === void 0 ? void 0 : edgesOf(edges, nodes ?? [], nameEdge, diffed),
1564
+ nodes: nodes === void 0 ? void 0 : namedNodes(nodes, changesOf(diffed, "nodes"))
1565
+ });
1566
+ }
1567
+ /**
1568
+ * Room a fit keeps clear on each side of the canvas, in pixels.
1569
+ */
1570
+ const PADDING = {
1571
+ bottom: "32px",
1572
+ left: "24px",
1573
+ right: "24px",
1574
+ top: "32px"
1575
+ };
1576
+ /**
1577
+ * How the view fits the graph: at most at its own size, clear of the canvas's edges and the
1578
+ * attribution.
1579
+ */
1580
+ const FIT = {
1581
+ maxZoom: 1,
1582
+ padding: PADDING
1583
+ };
1584
+ /**
1585
+ * Smallest zoom the view takes.
1586
+ */
1587
+ const MIN_ZOOM = .1;
1588
+ //#endregion
1589
+ //#region src/graph/edge.tsx
1590
+ /**
1591
+ * Renders the kit's edge: React Flow's bezier between two ports, and a control that removes it.
1592
+ *
1593
+ * @remarks
1594
+ * React Flow's edge is a path 1px wide with a thin area the pointer can hit, which a mouse hits
1595
+ * with difficulty and a finger misses. While the canvas takes edits and has a `removeGlyph`, a
1596
+ * deletable edge renders the actions `IconButton` at its middle with the glyph, in React Flow's
1597
+ * label layer, a tab stop like any button. The control is named by the edge data's
1598
+ * `removeLabel`, else by the canvas's `removeName` over the names of the edge's ends, so an edge
1599
+ * a person connects has a control too. A press removes the edge through React Flow, which calls
1600
+ * `onEdgesDelete` as the Delete key does. The control leaves with its edge, so focus then moves
1601
+ * to the node the edge left, unless the removal was refused.
1602
+ */
1603
+ /**
1604
+ * Renders the box the remove control is placed in, with the recipe's remove class.
1605
+ */
1606
+ const Place = withContext("span", "remove");
1607
+ /**
1608
+ * Returns the name of an edge's end: the node's own name, else its id.
1609
+ */
1610
+ function endOf(state, id) {
1611
+ return nameOf(state.nodeLookup.get(id)) ?? id;
1612
+ }
1613
+ /**
1614
+ * Focuses the node of an id inside a canvas, where it renders.
1615
+ *
1616
+ * @param canvas - React Flow's element, which contains the canvas's nodes.
1617
+ */
1618
+ function focusNode(canvas, id) {
1619
+ [...canvas.querySelectorAll(".react-flow__node")].find((node) => node.dataset["id"] === id)?.focus();
1620
+ }
1621
+ /**
1622
+ * Removes an edge through React Flow, then focuses the node it left unless the removal was
1623
+ * refused.
1624
+ *
1625
+ * @param flow - React Flow's instance, which removes the edge and calls the canvas's handlers.
1626
+ * @param canvas - React Flow's element, which contains the canvas's nodes.
1627
+ * @param edge - The edge's id and the id of the node it leaves.
1628
+ */
1629
+ async function removed(flow, canvas, edge) {
1630
+ const { deletedEdges } = await flow.deleteElements({ edges: [{ id: edge.id }] });
1631
+ if (deletedEdges.length > 0) focusNode(canvas, edge.source);
1632
+ }
1633
+ /**
1634
+ * Renders the edge's path, and its remove control while the canvas takes edits.
1635
+ *
1636
+ * @param props - React Flow's edge props: its ends, its sides and its data among them.
1637
+ */
1638
+ function Edge$1(props) {
1639
+ const $ = c(29);
1640
+ let t0;
1641
+ if ($[0] !== props) {
1642
+ t0 = getBezierPath(props);
1643
+ $[0] = props;
1644
+ $[1] = t0;
1645
+ } else t0 = $[1];
1646
+ const [path, labelX, labelY] = t0;
1647
+ const flow = useReactFlow();
1648
+ const canvas = useStore(_temp$2);
1649
+ let t1;
1650
+ if ($[2] !== props.source) {
1651
+ t1 = (state_0) => endOf(state_0, props.source);
1652
+ $[2] = props.source;
1653
+ $[3] = t1;
1654
+ } else t1 = $[3];
1655
+ const source = useStore(t1);
1656
+ let t2;
1657
+ if ($[4] !== props.target) {
1658
+ t2 = (state_1) => endOf(state_1, props.target);
1659
+ $[4] = props.target;
1660
+ $[5] = t2;
1661
+ } else t2 = $[5];
1662
+ const target = useStore(t2);
1663
+ const { editable, glyph, name } = use(RemovalContext);
1664
+ const t3 = `${String(labelX)}px`;
1665
+ const t4 = `${String(labelY)}px`;
1666
+ let t5;
1667
+ if ($[6] !== t3 || $[7] !== t4) {
1668
+ t5 = {
1669
+ "--graph-edge-x": t3,
1670
+ "--graph-edge-y": t4
1671
+ };
1672
+ $[6] = t3;
1673
+ $[7] = t4;
1674
+ $[8] = t5;
1675
+ } else t5 = $[8];
1676
+ const at = t5;
1677
+ const t6 = props.id;
1678
+ let t7;
1679
+ if ($[9] !== props.markerEnd || $[10] !== props.markerStart) {
1680
+ t7 = omitUndefined({
1681
+ markerEnd: props.markerEnd,
1682
+ markerStart: props.markerStart
1683
+ });
1684
+ $[9] = props.markerEnd;
1685
+ $[10] = props.markerStart;
1686
+ $[11] = t7;
1687
+ } else t7 = $[11];
1688
+ let t8;
1689
+ if ($[12] !== path || $[13] !== props.id || $[14] !== t7) {
1690
+ t8 = /* @__PURE__ */ jsx(BaseEdge, {
1691
+ id: t6,
1692
+ path,
1693
+ ...t7
1694
+ });
1695
+ $[12] = path;
1696
+ $[13] = props.id;
1697
+ $[14] = t7;
1698
+ $[15] = t8;
1699
+ } else t8 = $[15];
1700
+ let t9;
1701
+ if ($[16] !== at || $[17] !== canvas || $[18] !== editable || $[19] !== flow || $[20] !== glyph || $[21] !== name || $[22] !== props || $[23] !== source || $[24] !== target) {
1702
+ t9 = editable && props.deletable !== false && glyph !== void 0 ? /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(Place, {
1703
+ className: "nodrag nopan",
1704
+ style: at,
1705
+ children: /* @__PURE__ */ jsx(IconButton, {
1706
+ "aria-label": props.data?.removeLabel ?? name({
1707
+ source,
1708
+ target
1709
+ }),
1710
+ onClick: () => {
1711
+ removed(flow, canvas, props);
1712
+ },
1713
+ size: "xs",
1714
+ variant: "outline",
1715
+ children: glyph
1716
+ })
1717
+ }) }) : null;
1718
+ $[16] = at;
1719
+ $[17] = canvas;
1720
+ $[18] = editable;
1721
+ $[19] = flow;
1722
+ $[20] = glyph;
1723
+ $[21] = name;
1724
+ $[22] = props;
1725
+ $[23] = source;
1726
+ $[24] = target;
1727
+ $[25] = t9;
1728
+ } else t9 = $[25];
1729
+ let t10;
1730
+ if ($[26] !== t8 || $[27] !== t9) {
1731
+ t10 = /* @__PURE__ */ jsxs(Fragment, { children: [t8, t9] });
1732
+ $[26] = t8;
1733
+ $[27] = t9;
1734
+ $[28] = t10;
1735
+ } else t10 = $[28];
1736
+ return t10;
1737
+ }
1738
+ function _temp$2(state) {
1739
+ return state.domNode;
1740
+ }
1741
+ //#endregion
1742
+ //#region src/graph/label-node.tsx
1743
+ /**
1744
+ * Port edges enter, unless the data states the node's inputs.
1745
+ */
1746
+ const INPUTS = [{
1747
+ id: "in",
1748
+ label: "In"
1749
+ }];
1750
+ /**
1751
+ * Port edges leave, unless the data states the node's outputs.
1752
+ */
1753
+ const OUTPUTS = [{
1754
+ id: "out",
1755
+ label: "Out"
1756
+ }];
1757
+ /**
1758
+ * Renders the card of a built-in node from its data, with the ports its type has.
1759
+ *
1760
+ * @param props - React Flow's node props: its data and its type among them.
1761
+ */
1762
+ function LabelNode(t0) {
1763
+ const $ = c(18);
1764
+ const { data, type } = t0;
1765
+ let t1;
1766
+ if ($[0] !== data.inputs || $[1] !== type) {
1767
+ t1 = data.inputs ?? (type === "input" ? [] : INPUTS);
1768
+ $[0] = data.inputs;
1769
+ $[1] = type;
1770
+ $[2] = t1;
1771
+ } else t1 = $[2];
1772
+ const inputs = t1;
1773
+ let t2;
1774
+ if ($[3] !== data.outputs || $[4] !== type) {
1775
+ t2 = data.outputs ?? (type === "output" ? [] : OUTPUTS);
1776
+ $[3] = data.outputs;
1777
+ $[4] = type;
1778
+ $[5] = t2;
1779
+ } else t2 = $[5];
1780
+ const outputs = t2;
1781
+ const t3 = data.label;
1782
+ let t4;
1783
+ if ($[6] !== data.icon || $[7] !== data.invalid || $[8] !== data.problem || $[9] !== data.status || $[10] !== data.subtitle) {
1784
+ t4 = omitUndefined({
1785
+ icon: data.icon,
1786
+ invalid: data.invalid,
1787
+ problem: data.problem,
1788
+ status: data.status,
1789
+ subtitle: data.subtitle
1790
+ });
1791
+ $[6] = data.icon;
1792
+ $[7] = data.invalid;
1793
+ $[8] = data.problem;
1794
+ $[9] = data.status;
1795
+ $[10] = data.subtitle;
1796
+ $[11] = t4;
1797
+ } else t4 = $[11];
1798
+ let t5;
1799
+ if ($[12] !== data.detail || $[13] !== data.label || $[14] !== inputs || $[15] !== outputs || $[16] !== t4) {
1800
+ t5 = /* @__PURE__ */ jsx(Node$1, {
1801
+ inputs,
1802
+ outputs,
1803
+ title: t3,
1804
+ ...t4,
1805
+ children: data.detail
1806
+ });
1807
+ $[12] = data.detail;
1808
+ $[13] = data.label;
1809
+ $[14] = inputs;
1810
+ $[15] = outputs;
1811
+ $[16] = t4;
1812
+ $[17] = t5;
1813
+ } else t5 = $[17];
1814
+ return t5;
1815
+ }
1816
+ //#endregion
1817
+ //#region src/graph/types.ts
1818
+ /**
1819
+ * Maps React Flow's `default`, `input` and `output` node types to the kit's node.
1820
+ */
1821
+ const NODE_TYPES$2 = {
1822
+ default: LabelNode,
1823
+ input: LabelNode,
1824
+ output: LabelNode
1825
+ };
1826
+ /**
1827
+ * Maps React Flow's `default` edge type to the kit's edge.
1828
+ */
1829
+ const EDGE_TYPES = { default: Edge$1 };
1830
+ //#endregion
1831
+ //#region src/graph/settings.ts
1832
+ /**
1833
+ * React Flow's switches for a canvas that is read and not edited.
1834
+ */
1835
+ const READ_ONLY = {
1836
+ deleteKeyCode: null,
1837
+ disableKeyboardA11y: true,
1838
+ edgesFocusable: false,
1839
+ elementsSelectable: false,
1840
+ nodesConnectable: false,
1841
+ nodesDraggable: false
1842
+ };
1843
+ /**
1844
+ * React Flow's switches for a canvas that takes edits: both keys that remove the selection.
1845
+ */
1846
+ const EDITABLE = { deleteKeyCode: ["Backspace", "Delete"] };
1847
+ /**
1848
+ * Returns the props a canvas gives React Flow before the caller's own, for its mode and its
1849
+ * words.
1850
+ *
1851
+ * @param readOnly - Whether the canvas is read and not edited.
1852
+ * @param words - The words a screen reader hears.
1853
+ */
1854
+ function settingsOf(readOnly, words) {
1855
+ return {
1856
+ ariaLabelConfig: ariaWordsOf(omitUndefined(words), !readOnly),
1857
+ attributionPosition: "bottom-left",
1858
+ edgeTypes: EDGE_TYPES,
1859
+ fitView: true,
1860
+ fitViewOptions: FIT,
1861
+ minZoom: MIN_ZOOM,
1862
+ nodeTypes: NODE_TYPES$2,
1863
+ ...readOnly ? READ_ONLY : EDITABLE
1864
+ };
1865
+ }
1866
+ //#endregion
1867
+ //#region src/graph/canvas.tsx
1868
+ /**
1869
+ * Renders the canvas's box with the recipe's canvas class.
1870
+ */
1871
+ const Box$2 = withContext("div", "canvas");
1872
+ /**
1873
+ * Renders React Flow in the canvas's box with the kit's types, words, names, switches and edits.
1874
+ *
1875
+ * @typeParam N - One node of the graph.
1876
+ * @typeParam E - One edge of the graph.
1877
+ * @param props - The graph, its name, its mode, its words, its edits, and React Flow's props.
1878
+ */
1879
+ function Canvas(props) {
1880
+ const $ = c(57);
1881
+ let addedLabel;
1882
+ let changedLabel;
1883
+ let changes;
1884
+ let children;
1885
+ let edgeDescription;
1886
+ let edges;
1887
+ let flow;
1888
+ let label;
1889
+ let moveAnnouncement;
1890
+ let nodeDescription;
1891
+ let nodes;
1892
+ let onDropItem;
1893
+ let onGraphChange;
1894
+ let removeGlyph;
1895
+ let removedLabel;
1896
+ let t0;
1897
+ let t1;
1898
+ let t2;
1899
+ let t3;
1900
+ if ($[0] !== props) {
1901
+ ({addedLabel, background: t0, changedLabel, changes, children, edgeDescription, edgeName: t1, edges, label, moveAnnouncement, nodeDescription, nodes, onDropItem, onGraphChange, readOnly: t2, removedLabel, removeGlyph, removeName: t3, ...flow} = props);
1902
+ $[0] = props;
1903
+ $[1] = addedLabel;
1904
+ $[2] = changedLabel;
1905
+ $[3] = changes;
1906
+ $[4] = children;
1907
+ $[5] = edgeDescription;
1908
+ $[6] = edges;
1909
+ $[7] = flow;
1910
+ $[8] = label;
1911
+ $[9] = moveAnnouncement;
1912
+ $[10] = nodeDescription;
1913
+ $[11] = nodes;
1914
+ $[12] = onDropItem;
1915
+ $[13] = onGraphChange;
1916
+ $[14] = removeGlyph;
1917
+ $[15] = removedLabel;
1918
+ $[16] = t0;
1919
+ $[17] = t1;
1920
+ $[18] = t2;
1921
+ $[19] = t3;
1922
+ } else {
1923
+ addedLabel = $[1];
1924
+ changedLabel = $[2];
1925
+ changes = $[3];
1926
+ children = $[4];
1927
+ edgeDescription = $[5];
1928
+ edges = $[6];
1929
+ flow = $[7];
1930
+ label = $[8];
1931
+ moveAnnouncement = $[9];
1932
+ nodeDescription = $[10];
1933
+ nodes = $[11];
1934
+ onDropItem = $[12];
1935
+ onGraphChange = $[13];
1936
+ removeGlyph = $[14];
1937
+ removedLabel = $[15];
1938
+ t0 = $[16];
1939
+ t1 = $[17];
1940
+ t2 = $[18];
1941
+ t3 = $[19];
1942
+ }
1943
+ const background = t0 === void 0 ? true : t0;
1944
+ const edgeName = t1 === void 0 ? edgeNameOf : t1;
1945
+ const readOnly = t2 === void 0 ? false : t2;
1946
+ const removeName = t3 === void 0 ? removeNameOf : t3;
1947
+ const diffed = diffedOf(changes, {
1948
+ addedLabel,
1949
+ changedLabel,
1950
+ removedLabel
1951
+ });
1952
+ let t4;
1953
+ if ($[20] !== edges || $[21] !== flow || $[22] !== nodes || $[23] !== onDropItem || $[24] !== onGraphChange) {
1954
+ t4 = {
1955
+ ...flow,
1956
+ edges,
1957
+ nodes,
1958
+ onDropItem,
1959
+ onGraphChange
1960
+ };
1961
+ $[20] = edges;
1962
+ $[21] = flow;
1963
+ $[22] = nodes;
1964
+ $[23] = onDropItem;
1965
+ $[24] = onGraphChange;
1966
+ $[25] = t4;
1967
+ } else t4 = $[25];
1968
+ const edits = useEdits(t4);
1969
+ const T0 = RemovalContext;
1970
+ const t5 = !readOnly;
1971
+ let t6;
1972
+ if ($[26] !== removeGlyph || $[27] !== removeName || $[28] !== t5) {
1973
+ t6 = {
1974
+ editable: t5,
1975
+ glyph: removeGlyph,
1976
+ name: removeName
1977
+ };
1978
+ $[26] = removeGlyph;
1979
+ $[27] = removeName;
1980
+ $[28] = t5;
1981
+ $[29] = t6;
1982
+ } else t6 = $[29];
1983
+ const T1 = DiffContext;
1984
+ const T2 = Box$2;
1985
+ const T3 = ReactFlow;
1986
+ let t7;
1987
+ if ($[30] !== edgeDescription || $[31] !== moveAnnouncement || $[32] !== nodeDescription || $[33] !== readOnly) {
1988
+ t7 = settingsOf(readOnly, {
1989
+ edgeDescription,
1990
+ moveAnnouncement,
1991
+ nodeDescription
1992
+ });
1993
+ $[30] = edgeDescription;
1994
+ $[31] = moveAnnouncement;
1995
+ $[32] = nodeDescription;
1996
+ $[33] = readOnly;
1997
+ $[34] = t7;
1998
+ } else t7 = $[34];
1999
+ const t8 = namedGraph({
2000
+ edges,
2001
+ nodes
2002
+ }, edgeName, diffed);
2003
+ let t9;
2004
+ if ($[35] !== background) {
2005
+ t9 = background ? /* @__PURE__ */ jsx(Background, {}) : null;
2006
+ $[35] = background;
2007
+ $[36] = t9;
2008
+ } else t9 = $[36];
2009
+ let t10;
2010
+ if ($[37] !== T3 || $[38] !== children || $[39] !== edits || $[40] !== flow || $[41] !== label || $[42] !== t7 || $[43] !== t8 || $[44] !== t9) {
2011
+ t10 = /* @__PURE__ */ jsxs(T3, {
2012
+ "aria-label": label,
2013
+ ...t7,
2014
+ ...flow,
2015
+ ...edits,
2016
+ ...t8,
2017
+ children: [t9, children]
2018
+ });
2019
+ $[37] = T3;
2020
+ $[38] = children;
2021
+ $[39] = edits;
2022
+ $[40] = flow;
2023
+ $[41] = label;
2024
+ $[42] = t7;
2025
+ $[43] = t8;
2026
+ $[44] = t9;
2027
+ $[45] = t10;
2028
+ } else t10 = $[45];
2029
+ let t11;
2030
+ if ($[46] !== T2 || $[47] !== t10) {
2031
+ t11 = /* @__PURE__ */ jsx(T2, { children: t10 });
2032
+ $[46] = T2;
2033
+ $[47] = t10;
2034
+ $[48] = t11;
2035
+ } else t11 = $[48];
2036
+ let t12;
2037
+ if ($[49] !== T1 || $[50] !== diffed || $[51] !== t11) {
2038
+ t12 = /* @__PURE__ */ jsx(T1, {
2039
+ value: diffed,
2040
+ children: t11
2041
+ });
2042
+ $[49] = T1;
2043
+ $[50] = diffed;
2044
+ $[51] = t11;
2045
+ $[52] = t12;
2046
+ } else t12 = $[52];
2047
+ let t13;
2048
+ if ($[53] !== T0 || $[54] !== t12 || $[55] !== t6) {
2049
+ t13 = /* @__PURE__ */ jsx(T0, {
2050
+ value: t6,
2051
+ children: t12
2052
+ });
2053
+ $[53] = T0;
2054
+ $[54] = t12;
2055
+ $[55] = t6;
2056
+ $[56] = t13;
2057
+ } else t13 = $[56];
2058
+ return t13;
2059
+ }
2060
+ //#endregion
2061
+ //#region src/graph/placed.ts
2062
+ /**
2063
+ * Fits a preset's view to its layout and reports whether the canvas is still placing its nodes.
2064
+ *
2065
+ * @remarks
2066
+ * React Flow takes a controlled graph's nodes into its store in an effect of its own, one step
2067
+ * after the render that laid them out, so a canvas shown in that step renders the old positions.
2068
+ * The canvas places until every node has been measured and until React Flow's store has every
2069
+ * node where the layout put it, and the recipe hides the viewport while it places. The view fits
2070
+ * the layout once every node has been measured and whenever the layout moves a node after that,
2071
+ * in an effect that runs after React Flow's, so the bounds are the measured nodes at their new
2072
+ * positions. The fit is at most at the graph's own size and clear of the
2073
+ * canvas's edges, as `Graph.Canvas` fits it. A change that moves no node, such as a new focus,
2074
+ * keeps the view where a person panned and zoomed it. A node a person moved counts for neither:
2075
+ * a drag moves no view, and the step the store trails a drag by does not hide the viewport.
2076
+ */
2077
+ /**
2078
+ * Ids of no node, for a graph whose nodes nobody moved.
2079
+ */
2080
+ const NONE = /* @__PURE__ */ new Set();
2081
+ /**
2082
+ * Returns a string that differs whenever a node is added, removed or moved.
2083
+ */
2084
+ function signatureOf(nodes) {
2085
+ return nodes.map(({ id, position }) => `${id}:${String(position.x)},${String(position.y)}`).join(";");
2086
+ }
2087
+ /**
2088
+ * Fits the view to the laid-out nodes whenever the layout moves them, and returns whether the
2089
+ * canvas is still placing them.
2090
+ *
2091
+ * @param nodes - The nodes where the layout put them.
2092
+ * @param complete - Whether every node of the graph has been measured and laid out.
2093
+ * @param moved - Ids of the nodes a person moved away from where the layout put them.
2094
+ */
2095
+ function usePlaced(nodes, complete, t0) {
2096
+ const $ = c(16);
2097
+ const moved = t0 === void 0 ? NONE : t0;
2098
+ const flow = useReactFlow();
2099
+ const store = useStoreApi();
2100
+ let t1;
2101
+ if ($[0] !== nodes) {
2102
+ t1 = signatureOf(nodes);
2103
+ $[0] = nodes;
2104
+ $[1] = t1;
2105
+ } else t1 = $[1];
2106
+ const signature = t1;
2107
+ let t2;
2108
+ if ($[2] !== moved || $[3] !== nodes) {
2109
+ let t3;
2110
+ if ($[5] !== moved) {
2111
+ t3 = (node) => !moved.has(node.id);
2112
+ $[5] = moved;
2113
+ $[6] = t3;
2114
+ } else t3 = $[6];
2115
+ t2 = signatureOf(nodes.filter(t3));
2116
+ $[2] = moved;
2117
+ $[3] = nodes;
2118
+ $[4] = t2;
2119
+ } else t2 = $[4];
2120
+ const settled = t2;
2121
+ let t3;
2122
+ if ($[7] !== moved) {
2123
+ t3 = (state) => signatureOf(state.nodes.filter((node_0) => !moved.has(node_0.id)));
2124
+ $[7] = moved;
2125
+ $[8] = t3;
2126
+ } else t3 = $[8];
2127
+ const stored = useStore(t3);
2128
+ const fitted = useRef("");
2129
+ let t4;
2130
+ let t5;
2131
+ if ($[9] !== complete || $[10] !== flow || $[11] !== nodes || $[12] !== signature || $[13] !== store) {
2132
+ t4 = () => {
2133
+ if (!complete || fitted.current === signature) return;
2134
+ const { height, width } = store.getState();
2135
+ const bounds = flow.getNodesBounds([...nodes]);
2136
+ fitted.current = signature;
2137
+ flow.setViewport(getViewportForBounds(bounds, width, height, MIN_ZOOM, 1, PADDING));
2138
+ };
2139
+ t5 = [
2140
+ complete,
2141
+ flow,
2142
+ nodes,
2143
+ signature,
2144
+ store
2145
+ ];
2146
+ $[9] = complete;
2147
+ $[10] = flow;
2148
+ $[11] = nodes;
2149
+ $[12] = signature;
2150
+ $[13] = store;
2151
+ $[14] = t4;
2152
+ $[15] = t5;
2153
+ } else {
2154
+ t4 = $[14];
2155
+ t5 = $[15];
2156
+ }
2157
+ useEffect(t4, t5);
2158
+ return !complete || stored !== settled;
2159
+ }
2160
+ //#endregion
2161
+ //#region src/graph/summary.ts
2162
+ /**
2163
+ * Renders the `div` with the recipe's summary class.
2164
+ */
2165
+ const Summary = withContext("div", "summary");
2166
+ //#endregion
2167
+ //#region src/graph/readout.tsx
2168
+ /**
2169
+ * Renders the words, the clear control and the elements at the row's end in the graph's summary
2170
+ * row.
2171
+ *
2172
+ * @param props - The words, the focus, the clear handler and the elements at the row's end.
2173
+ */
2174
+ function Readout(t0) {
2175
+ const $ = c(13);
2176
+ const { children, clearLabel, focused, onClear, text } = t0;
2177
+ let t1;
2178
+ if ($[0] !== text) {
2179
+ t1 = /* @__PURE__ */ jsx("output", { children: text });
2180
+ $[0] = text;
2181
+ $[1] = t1;
2182
+ } else t1 = $[1];
2183
+ const t2 = focused ? void 0 : true;
2184
+ let t3;
2185
+ if ($[2] !== focused || $[3] !== onClear) {
2186
+ t3 = () => {
2187
+ if (focused) onClear();
2188
+ };
2189
+ $[2] = focused;
2190
+ $[3] = onClear;
2191
+ $[4] = t3;
2192
+ } else t3 = $[4];
2193
+ let t4;
2194
+ if ($[5] !== clearLabel || $[6] !== t2 || $[7] !== t3) {
2195
+ t4 = /* @__PURE__ */ jsx(Button, {
2196
+ "aria-disabled": t2,
2197
+ onClick: t3,
2198
+ size: "sm",
2199
+ variant: "outline",
2200
+ children: clearLabel
2201
+ });
2202
+ $[5] = clearLabel;
2203
+ $[6] = t2;
2204
+ $[7] = t3;
2205
+ $[8] = t4;
2206
+ } else t4 = $[8];
2207
+ let t5;
2208
+ if ($[9] !== children || $[10] !== t1 || $[11] !== t4) {
2209
+ t5 = /* @__PURE__ */ jsxs(Summary, { children: [
2210
+ t1,
2211
+ t4,
2212
+ children
2213
+ ] });
2214
+ $[9] = children;
2215
+ $[10] = t1;
2216
+ $[11] = t4;
2217
+ $[12] = t5;
2218
+ } else t5 = $[12];
2219
+ return t5;
2220
+ }
2221
+ //#endregion
2222
+ //#region src/directed-graph/traced.tsx
2223
+ /**
2224
+ * Maps the directed graph's node type to its card.
2225
+ */
2226
+ const NODE_TYPES$1 = { directed: Card };
2227
+ /**
2228
+ * Renders the canvas over the graph's view and the readout below it.
2229
+ *
2230
+ * @param props - The graph, its focus and branches, its words and its direction.
2231
+ */
2232
+ function Traced(props) {
2233
+ const $ = c(20);
2234
+ const { clear, onNodesChange, toggle, view } = useTraced(props);
2235
+ const placing = usePlaced(view.nodes, view.complete);
2236
+ const { counts } = view;
2237
+ const { words } = props;
2238
+ const t0 = placing ? "" : void 0;
2239
+ let t1;
2240
+ if ($[0] !== onNodesChange || $[1] !== props.label || $[2] !== t0 || $[3] !== view.edges || $[4] !== view.nodes || $[5] !== words.nodeDescription) {
2241
+ t1 = /* @__PURE__ */ jsx(Canvas, {
2242
+ "data-placing": t0,
2243
+ edges: view.edges,
2244
+ edgesFocusable: false,
2245
+ label: props.label,
2246
+ multiSelectionKeyCode: null,
2247
+ nodeDescription: words.nodeDescription,
2248
+ nodes: view.nodes,
2249
+ nodesConnectable: false,
2250
+ nodesDraggable: false,
2251
+ nodeTypes: NODE_TYPES$1,
2252
+ onNodesChange,
2253
+ selectionKeyCode: null
2254
+ });
2255
+ $[0] = onNodesChange;
2256
+ $[1] = props.label;
2257
+ $[2] = t0;
2258
+ $[3] = view.edges;
2259
+ $[4] = view.nodes;
2260
+ $[5] = words.nodeDescription;
2261
+ $[6] = t1;
2262
+ } else t1 = $[6];
2263
+ const t2 = counts !== void 0;
2264
+ let t3;
2265
+ if ($[7] !== counts || $[8] !== words) {
2266
+ t3 = counts === void 0 ? words.promptLabel : words.summary(counts);
2267
+ $[7] = counts;
2268
+ $[8] = words;
2269
+ $[9] = t3;
2270
+ } else t3 = $[9];
2271
+ let t4;
2272
+ if ($[10] !== clear || $[11] !== props.overview || $[12] !== t2 || $[13] !== t3 || $[14] !== words.clearLabel) {
2273
+ t4 = /* @__PURE__ */ jsx(Readout, {
2274
+ clearLabel: words.clearLabel,
2275
+ focused: t2,
2276
+ onClear: clear,
2277
+ text: t3,
2278
+ children: props.overview
2279
+ });
2280
+ $[10] = clear;
2281
+ $[11] = props.overview;
2282
+ $[12] = t2;
2283
+ $[13] = t3;
2284
+ $[14] = words.clearLabel;
2285
+ $[15] = t4;
2286
+ } else t4 = $[15];
2287
+ let t5;
2288
+ if ($[16] !== t1 || $[17] !== t4 || $[18] !== toggle) {
2289
+ t5 = /* @__PURE__ */ jsxs(BranchProvider, {
2290
+ value: toggle,
2291
+ children: [t1, t4]
2292
+ });
2293
+ $[16] = t1;
2294
+ $[17] = t4;
2295
+ $[18] = toggle;
2296
+ $[19] = t5;
2297
+ } else t5 = $[19];
2298
+ return t5;
2299
+ }
2300
+ //#endregion
2301
+ //#region src/directed-graph/words.ts
2302
+ /**
2303
+ * Writes the words a directed graph renders, from the caller's words and the English defaults.
2304
+ *
2305
+ * @remarks
2306
+ * A caller that states a word replaces the default and keeps every other default. The counts of
2307
+ * a branch control are the nodes the press hides or shows, so the words name no kind of node and
2308
+ * suit a lineage and an org chart alike.
2309
+ */
2310
+ /**
2311
+ * Writes the words of a control that closes a branch, unless stated.
2312
+ */
2313
+ function hidden(count) {
2314
+ return `Hide ${String(count)}`;
2315
+ }
2316
+ /**
2317
+ * Writes the words of a control that opens a branch, unless stated.
2318
+ */
2319
+ function shown(count) {
2320
+ return `Show ${String(count)}`;
2321
+ }
2322
+ /**
2323
+ * Writes the summary of a trace, unless stated.
2324
+ */
2325
+ function summarised({ downstream, name, upstream }) {
2326
+ return `${name}: ${String(upstream)} upstream, ${String(downstream)} downstream`;
2327
+ }
2328
+ /**
2329
+ * English words of a directed graph.
2330
+ */
2331
+ const WORDS$1 = {
2332
+ clearLabel: "Clear focus",
2333
+ collapseLabel: hidden,
2334
+ downstreamLabel: "Downstream",
2335
+ edgeName: edgeNameOf,
2336
+ emptyLabel: "No nodes to show.",
2337
+ expandLabel: shown,
2338
+ focusLabel: "Focus",
2339
+ nodeDescription: "Press Enter or Space to trace the node, and Escape to clear the trace.",
2340
+ promptLabel: "Select a node to trace what feeds it and what it feeds.",
2341
+ summary: summarised,
2342
+ upstreamLabel: "Upstream"
2343
+ };
2344
+ /**
2345
+ * Returns the caller's words over the English defaults.
2346
+ *
2347
+ * @param words - The words the caller states.
2348
+ */
2349
+ function wordsOf$1(words) {
2350
+ return {
2351
+ ...WORDS$1,
2352
+ ...omitUndefined(words)
2353
+ };
2354
+ }
2355
+ //#endregion
2356
+ //#region src/graph/caption.tsx
2357
+ /**
2358
+ * Renders the `figcaption` with the recipe's caption class.
2359
+ */
2360
+ const Shown$1 = withContext("figcaption", "caption");
2361
+ /**
2362
+ * Renders the caption with the ID the root names the figure by.
2363
+ *
2364
+ * @param props - The props of the `figcaption`.
2365
+ */
2366
+ function Caption(props) {
2367
+ const $ = c(3);
2368
+ useLabelled();
2369
+ const t0 = useCaptionId();
2370
+ let t1;
2371
+ if ($[0] !== props || $[1] !== t0) {
2372
+ t1 = /* @__PURE__ */ jsx(Shown$1, {
2373
+ id: t0,
2374
+ ...props
2375
+ });
2376
+ $[0] = props;
2377
+ $[1] = t0;
2378
+ $[2] = t1;
2379
+ } else t1 = $[2];
2380
+ return t1;
2381
+ }
2382
+ //#endregion
2383
+ //#region src/graph/empty.ts
2384
+ /**
2385
+ * Renders the `div` with the recipe's empty class.
2386
+ */
2387
+ const Empty = withContext("div", "empty");
2388
+ //#endregion
2389
+ //#region src/graph/root.tsx
2390
+ /**
2391
+ * Renders a graph's figure and the React Flow provider every part reads.
2392
+ *
2393
+ * @remarks
2394
+ * The element is a `figure`, named by its `Graph.Caption` through `aria-labelledby` while a
2395
+ * caption renders. The root renders React Flow's provider, so the controls and the overview map
2396
+ * read the canvas's store from anywhere inside the figure. `direction` tells every node which
2397
+ * sides its ports are on. The root takes the recipe's `ratio`, which sets the canvas's height.
2398
+ */
2399
+ /**
2400
+ * Renders the `figure` that provides the recipe's variants.
2401
+ */
2402
+ const Figure = withProvider("figure", "root");
2403
+ /**
2404
+ * Renders the figure inside React Flow's provider, named by its caption while one renders.
2405
+ *
2406
+ * @param props - The direction, the recipe's variants and the props of a `figure`.
2407
+ * @returns The `figure` element inside the providers.
2408
+ */
2409
+ function Root(t0) {
2410
+ const $ = c(12);
2411
+ let props;
2412
+ let t1;
2413
+ if ($[0] !== t0) {
2414
+ ({direction: t1, ...props} = t0);
2415
+ $[0] = t0;
2416
+ $[1] = props;
2417
+ $[2] = t1;
2418
+ } else {
2419
+ props = $[1];
2420
+ t1 = $[2];
2421
+ }
2422
+ const direction = t1 === void 0 ? "down" : t1;
2423
+ const captionId = useId();
2424
+ const [captioned, setCaptioned] = useState(false);
2425
+ const t2 = captioned ? captionId : void 0;
2426
+ let t3;
2427
+ if ($[3] !== props || $[4] !== t2) {
2428
+ t3 = /* @__PURE__ */ jsx(Figure, {
2429
+ "aria-labelledby": t2,
2430
+ ...props
2431
+ });
2432
+ $[3] = props;
2433
+ $[4] = t2;
2434
+ $[5] = t3;
2435
+ } else t3 = $[5];
2436
+ let t4;
2437
+ if ($[6] !== captionId || $[7] !== t3) {
2438
+ t4 = /* @__PURE__ */ jsx(LabellingProvider, {
2439
+ value: setCaptioned,
2440
+ children: /* @__PURE__ */ jsx(CaptionProvider, {
2441
+ value: captionId,
2442
+ children: t3
2443
+ })
2444
+ });
2445
+ $[6] = captionId;
2446
+ $[7] = t3;
2447
+ $[8] = t4;
2448
+ } else t4 = $[8];
2449
+ let t5;
2450
+ if ($[9] !== direction || $[10] !== t4) {
2451
+ t5 = /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(DirectionContext, {
2452
+ value: direction,
2453
+ children: t4
2454
+ }) });
2455
+ $[9] = direction;
2456
+ $[10] = t4;
2457
+ $[11] = t5;
2458
+ } else t5 = $[11];
2459
+ return t5;
2460
+ }
2461
+ //#endregion
2462
+ //#region src/directed-graph/directed-graph.tsx
2463
+ /**
2464
+ * Splits a directed graph's props into its words, the canvas's props and the figure's props.
2465
+ */
2466
+ function split$1(props) {
2467
+ const { clearLabel, collapsed, collapseLabel, collapsible, defaultCollapsed, defaultFocus, depth, downstreamLabel, edgeName, edges, emptyLabel, expandLabel, focus, focusLabel, label, nodeDescription, nodes, onCollapsedChange, onFocusChange, overview, promptLabel, summary, trace, upstreamLabel, ...rest } = props;
2468
+ const words = {
2469
+ clearLabel,
2470
+ collapseLabel,
2471
+ downstreamLabel,
2472
+ edgeName,
2473
+ emptyLabel,
2474
+ expandLabel
2475
+ };
2476
+ const more = {
2477
+ focusLabel,
2478
+ nodeDescription,
2479
+ promptLabel,
2480
+ summary,
2481
+ upstreamLabel
2482
+ };
2483
+ const state = {
2484
+ collapsed,
2485
+ collapsible,
2486
+ defaultCollapsed,
2487
+ defaultFocus,
2488
+ depth,
2489
+ focus,
2490
+ trace
2491
+ };
2492
+ const graph = {
2493
+ edges,
2494
+ label,
2495
+ nodes,
2496
+ onCollapsedChange,
2497
+ onFocusChange,
2498
+ overview
2499
+ };
2500
+ return [
2501
+ {
2502
+ ...words,
2503
+ ...more
2504
+ },
2505
+ {
2506
+ ...state,
2507
+ ...graph
2508
+ },
2509
+ rest
2510
+ ];
2511
+ }
2512
+ /**
2513
+ * Renders the figure: the controls, the canvas, its readout with the overview, and the caption,
2514
+ * or the empty state while the graph has no node.
2515
+ *
2516
+ * @param props - The graph, its focus and branches, its parts, its words and the figure's props.
2517
+ */
2518
+ function DirectedGraph(props) {
2519
+ const $ = c(21);
2520
+ let caption;
2521
+ let controls;
2522
+ let direction;
2523
+ let own;
2524
+ let root;
2525
+ let t0;
2526
+ if ($[0] !== props) {
2527
+ const [stated, t1, rest] = split$1(props);
2528
+ own = t1;
2529
+ const { caption: t2, controls: t3, direction: t4, ...t5 } = rest;
2530
+ caption = t2;
2531
+ controls = t3;
2532
+ root = t5;
2533
+ direction = t4 === void 0 ? "down" : t4;
2534
+ t0 = wordsOf$1(stated);
2535
+ $[0] = props;
2536
+ $[1] = caption;
2537
+ $[2] = controls;
2538
+ $[3] = direction;
2539
+ $[4] = own;
2540
+ $[5] = root;
2541
+ $[6] = t0;
2542
+ } else {
2543
+ caption = $[1];
2544
+ controls = $[2];
2545
+ direction = $[3];
2546
+ own = $[4];
2547
+ root = $[5];
2548
+ t0 = $[6];
2549
+ }
2550
+ const words = t0;
2551
+ let t1;
2552
+ if ($[7] !== direction || $[8] !== root) {
2553
+ t1 = {
2554
+ direction,
2555
+ ...root
2556
+ };
2557
+ $[7] = direction;
2558
+ $[8] = root;
2559
+ $[9] = t1;
2560
+ } else t1 = $[9];
2561
+ const figure = t1;
2562
+ let t2;
2563
+ if ($[10] !== controls || $[11] !== direction || $[12] !== own || $[13] !== words) {
2564
+ t2 = own.nodes.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: words.emptyLabel }) : /* @__PURE__ */ jsxs(Fragment, { children: [controls, /* @__PURE__ */ jsx(Traced, {
2565
+ direction,
2566
+ words,
2567
+ ...own
2568
+ })] });
2569
+ $[10] = controls;
2570
+ $[11] = direction;
2571
+ $[12] = own;
2572
+ $[13] = words;
2573
+ $[14] = t2;
2574
+ } else t2 = $[14];
2575
+ let t3;
2576
+ if ($[15] !== caption) {
2577
+ t3 = caption === void 0 ? null : /* @__PURE__ */ jsx(Caption, { children: caption });
2578
+ $[15] = caption;
2579
+ $[16] = t3;
2580
+ } else t3 = $[16];
2581
+ let t4;
2582
+ if ($[17] !== figure || $[18] !== t2 || $[19] !== t3) {
2583
+ t4 = /* @__PURE__ */ jsxs(Root, {
2584
+ ...figure,
2585
+ children: [t2, t3]
2586
+ });
2587
+ $[17] = figure;
2588
+ $[18] = t2;
2589
+ $[19] = t3;
2590
+ $[20] = t4;
2591
+ } else t4 = $[20];
2592
+ return t4;
2593
+ }
2594
+ //#endregion
2595
+ //#region src/directed-graph/tree.ts
2596
+ /**
2597
+ * Returns an edge from each item's parent to the item, for every item whose parent is in the list.
2598
+ *
2599
+ * @typeParam T - One item of the list.
2600
+ * @param items - The items, in any order.
2601
+ * @param parentOf - Returns an item's parent's id, or undefined for an item without one.
2602
+ */
2603
+ function treeEdges(items, parentOf) {
2604
+ const ids = new Set(items.map((item) => item.id));
2605
+ return items.flatMap((item) => {
2606
+ const parent = parentOf(item);
2607
+ return parent !== void 0 && ids.has(parent) ? [{
2608
+ id: `${parent}-${item.id}`,
2609
+ source: parent,
2610
+ target: item.id
2611
+ }] : [];
2612
+ });
2613
+ }
2614
+ //#endregion
2615
+ //#region src/graph/control.tsx
2616
+ /**
2617
+ * Renders the control as a toolbar item that zooms or fits the view when pressed.
2618
+ *
2619
+ * @param props - The action, the name and the item's props.
2620
+ */
2621
+ function Control(t0) {
2622
+ const $ = c(11);
2623
+ let action;
2624
+ let label;
2625
+ let props;
2626
+ if ($[0] !== t0) {
2627
+ ({action, label, ...props} = t0);
2628
+ $[0] = t0;
2629
+ $[1] = action;
2630
+ $[2] = label;
2631
+ $[3] = props;
2632
+ } else {
2633
+ action = $[1];
2634
+ label = $[2];
2635
+ props = $[3];
2636
+ }
2637
+ const flow = useReactFlow();
2638
+ let t1;
2639
+ if ($[4] !== action || $[5] !== flow) {
2640
+ t1 = function act() {
2641
+ if (action === "fit") flow.fitView(FIT);
2642
+ else if (action === "zoomIn") flow.zoomIn();
2643
+ else flow.zoomOut();
2644
+ };
2645
+ $[4] = action;
2646
+ $[5] = flow;
2647
+ $[6] = t1;
2648
+ } else t1 = $[6];
2649
+ const act = t1;
2650
+ let t2;
2651
+ if ($[7] !== act || $[8] !== label || $[9] !== props) {
2652
+ t2 = /* @__PURE__ */ jsx(RovingFocus.Item, {
2653
+ "aria-label": label,
2654
+ as: IconButton,
2655
+ onClick: act,
2656
+ ...props
2657
+ });
2658
+ $[7] = act;
2659
+ $[8] = label;
2660
+ $[9] = props;
2661
+ $[10] = t2;
2662
+ } else t2 = $[10];
2663
+ return t2;
2664
+ }
2665
+ //#endregion
2666
+ //#region src/graph/controls.tsx
2667
+ /**
2668
+ * Renders the roving focus group with the recipe's controls class.
2669
+ */
2670
+ const Bar = withContext(RovingFocus.Root, "controls");
2671
+ /**
2672
+ * Renders the toolbar with small ghost buttons.
2673
+ *
2674
+ * @param props - The name and the group's props.
2675
+ */
2676
+ function Controls(t0) {
2677
+ const $ = c(7);
2678
+ let props;
2679
+ let t1;
2680
+ if ($[0] !== t0) {
2681
+ ({label: t1, ...props} = t0);
2682
+ $[0] = t0;
2683
+ $[1] = props;
2684
+ $[2] = t1;
2685
+ } else {
2686
+ props = $[1];
2687
+ t1 = $[2];
2688
+ }
2689
+ const label = t1 === void 0 ? "Canvas" : t1;
2690
+ let t2;
2691
+ if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
2692
+ t2 = {
2693
+ size: "sm",
2694
+ variant: "ghost"
2695
+ };
2696
+ $[3] = t2;
2697
+ } else t2 = $[3];
2698
+ let t3;
2699
+ if ($[4] !== label || $[5] !== props) {
2700
+ t3 = /* @__PURE__ */ jsx(ButtonPropsProvider, {
2701
+ value: t2,
2702
+ children: /* @__PURE__ */ jsx(Bar, {
2703
+ "aria-label": label,
2704
+ className: "nokey",
2705
+ role: "toolbar",
2706
+ ...props
2707
+ })
2708
+ });
2709
+ $[4] = label;
2710
+ $[5] = props;
2711
+ $[6] = t3;
2712
+ } else t3 = $[6];
2713
+ return t3;
2714
+ }
2715
+ //#endregion
2716
+ //#region src/graph/minimap.tsx
2717
+ /**
2718
+ * Renders the box the map is placed in, with the recipe's overview class.
2719
+ */
2720
+ const Box$1 = withContext("div", "overview");
2721
+ /**
2722
+ * Renders React Flow's minimap in the overview box, named, pannable and zoomable.
2723
+ *
2724
+ * @param props - The name and the props of React Flow's minimap.
2725
+ */
2726
+ function MiniMap$1(t0) {
2727
+ const $ = c(6);
2728
+ let props;
2729
+ let t1;
2730
+ if ($[0] !== t0) {
2731
+ ({label: t1, ...props} = t0);
2732
+ $[0] = t0;
2733
+ $[1] = props;
2734
+ $[2] = t1;
2735
+ } else {
2736
+ props = $[1];
2737
+ t1 = $[2];
2738
+ }
2739
+ const label = t1 === void 0 ? "Overview" : t1;
2740
+ let t2;
2741
+ if ($[3] !== label || $[4] !== props) {
2742
+ t2 = /* @__PURE__ */ jsx(Box$1, { children: /* @__PURE__ */ jsx(MiniMap, {
2743
+ ariaLabel: label,
2744
+ pannable: true,
2745
+ zoomable: true,
2746
+ ...props
2747
+ }) });
2748
+ $[3] = label;
2749
+ $[4] = props;
2750
+ $[5] = t2;
2751
+ } else t2 = $[5];
2752
+ return t2;
2753
+ }
2754
+ //#endregion
2755
+ //#region src/graph/palette-item.tsx
2756
+ /**
2757
+ * Renders the item as a button a person drags onto the canvas or presses.
2758
+ *
2759
+ * @param props - The kind of node, what a press does, and the button's props.
2760
+ */
2761
+ function PaletteItem(t0) {
2762
+ const $ = c(14);
2763
+ let item;
2764
+ let onAdd;
2765
+ let props;
2766
+ if ($[0] !== t0) {
2767
+ ({item, onAdd, ...props} = t0);
2768
+ $[0] = t0;
2769
+ $[1] = item;
2770
+ $[2] = onAdd;
2771
+ $[3] = props;
2772
+ } else {
2773
+ item = $[1];
2774
+ onAdd = $[2];
2775
+ props = $[3];
2776
+ }
2777
+ const centre = useViewCentre();
2778
+ let t1;
2779
+ if ($[4] !== centre || $[5] !== item || $[6] !== onAdd) {
2780
+ t1 = () => {
2781
+ onAdd(item, centre());
2782
+ };
2783
+ $[4] = centre;
2784
+ $[5] = item;
2785
+ $[6] = onAdd;
2786
+ $[7] = t1;
2787
+ } else t1 = $[7];
2788
+ let t2;
2789
+ if ($[8] !== item) {
2790
+ t2 = (event) => {
2791
+ event.dataTransfer.setData(ITEM, item);
2792
+ event.dataTransfer.effectAllowed = "copy";
2793
+ };
2794
+ $[8] = item;
2795
+ $[9] = t2;
2796
+ } else t2 = $[9];
2797
+ let t3;
2798
+ if ($[10] !== props || $[11] !== t1 || $[12] !== t2) {
2799
+ t3 = /* @__PURE__ */ jsx(Button, {
2800
+ draggable: true,
2801
+ onClick: t1,
2802
+ onDragStart: t2,
2803
+ variant: "outline",
2804
+ ...props
2805
+ });
2806
+ $[10] = props;
2807
+ $[11] = t1;
2808
+ $[12] = t2;
2809
+ $[13] = t3;
2810
+ } else t3 = $[13];
2811
+ return t3;
2812
+ }
2813
+ //#endregion
2814
+ //#region src/graph/zoom-level.tsx
2815
+ /**
2816
+ * Renders the canvas's zoom level as a percentage in the toolbar.
2817
+ *
2818
+ * @remarks
2819
+ * The element is an `output`, which reads its value to a screen reader when it changes. The
2820
+ * percentage is written in `locale`, else the nearest `LocaleProvider`'s, else the runtime's.
2821
+ */
2822
+ /**
2823
+ * Renders the `output` with the recipe's level class.
2824
+ */
2825
+ const Shown = withContext("output", "level");
2826
+ /**
2827
+ * Renders the canvas's zoom as a percentage.
2828
+ *
2829
+ * @param props - The locale and the props of the `output`.
2830
+ */
2831
+ function ZoomLevel(t0) {
2832
+ const $ = c(14);
2833
+ let locale;
2834
+ let props;
2835
+ if ($[0] !== t0) {
2836
+ ({locale, ...props} = t0);
2837
+ $[0] = t0;
2838
+ $[1] = locale;
2839
+ $[2] = props;
2840
+ } else {
2841
+ locale = $[1];
2842
+ props = $[2];
2843
+ }
2844
+ const zoom = useStore(_temp$1);
2845
+ const scoped = use(LocaleContext)?.locale;
2846
+ let T0;
2847
+ let t1;
2848
+ let t2;
2849
+ if ($[3] !== locale || $[4] !== props || $[5] !== scoped || $[6] !== zoom) {
2850
+ const percent = new Intl.NumberFormat(locale ?? scoped, { style: "percent" });
2851
+ T0 = Shown;
2852
+ t1 = props;
2853
+ t2 = percent.format(zoom);
2854
+ $[3] = locale;
2855
+ $[4] = props;
2856
+ $[5] = scoped;
2857
+ $[6] = zoom;
2858
+ $[7] = T0;
2859
+ $[8] = t1;
2860
+ $[9] = t2;
2861
+ } else {
2862
+ T0 = $[7];
2863
+ t1 = $[8];
2864
+ t2 = $[9];
2865
+ }
2866
+ let t3;
2867
+ if ($[10] !== T0 || $[11] !== t1 || $[12] !== t2) {
2868
+ t3 = /* @__PURE__ */ jsx(T0, {
2869
+ ...t1,
2870
+ children: t2
2871
+ });
2872
+ $[10] = T0;
2873
+ $[11] = t1;
2874
+ $[12] = t2;
2875
+ $[13] = t3;
2876
+ } else t3 = $[13];
2877
+ return t3;
2878
+ }
2879
+ function _temp$1(state) {
2880
+ return state.transform[2];
2881
+ }
2882
+ //#endregion
2883
+ //#region src/graph/index.ts
2884
+ var graph_exports = /* @__PURE__ */ __exportAll({
2885
+ Canvas: () => Canvas,
2886
+ Caption: () => Caption,
2887
+ Control: () => Control,
2888
+ Controls: () => Controls,
2889
+ Edge: () => Edge$1,
2890
+ Empty: () => Empty,
2891
+ LabelNode: () => LabelNode,
2892
+ MiniMap: () => MiniMap$1,
2893
+ Node: () => Node$1,
2894
+ PaletteItem: () => PaletteItem,
2895
+ Root: () => Root,
2896
+ Summary: () => Summary,
2897
+ ZoomLevel: () => ZoomLevel,
2898
+ edgeTypes: () => EDGE_TYPES,
2899
+ nodeTypes: () => NODE_TYPES$2,
2900
+ useGraphDirection: () => useGraphDirection
2901
+ });
2902
+ //#endregion
2903
+ //#region src/layout/start.ts
2904
+ /**
2905
+ * Returns a seat for each id, linked to the seats its links reach. A link with an end outside the
2906
+ * graph links nothing.
2907
+ */
2908
+ function seatsOf(ids, links) {
2909
+ const seats = ids.map((id) => ({
2910
+ children: [],
2911
+ depth: -1,
2912
+ id,
2913
+ leaves: 1,
2914
+ neighbours: /* @__PURE__ */ new Set()
2915
+ }));
2916
+ const byId = new Map(seats.map((seat) => [seat.id, seat]));
2917
+ for (const { source, target } of links) {
2918
+ const [from, to] = [byId.get(source), byId.get(target)];
2919
+ if (from !== void 0 && to !== void 0) {
2920
+ from.neighbours.add(to);
2921
+ to.neighbours.add(from);
2922
+ }
2923
+ }
2924
+ return seats;
2925
+ }
2926
+ /**
2927
+ * Returns the seats in an order the number source shuffles.
2928
+ */
2929
+ function shuffled(seats, random) {
2930
+ return seats.map((seat) => ({
2931
+ key: random(),
2932
+ seat
2933
+ })).toSorted((a, b) => a.key - b.key).map(({ seat }) => seat);
2934
+ }
2935
+ /**
2936
+ * Returns the seats in their order, except that each seat linked to the one before it comes next.
2937
+ */
2938
+ function chained(seats) {
2939
+ const chain = [];
2940
+ const left = [...seats];
2941
+ for (let at = 0; at < seats.length; at += 1) {
2942
+ const last = chain.at(-1);
2943
+ const next = last === void 0 ? 0 : left.findIndex((seat) => last.neighbours.has(seat));
2944
+ chain.push(...left.splice(Math.max(next, 0), 1));
2945
+ }
2946
+ return chain;
2947
+ }
2948
+ /**
2949
+ * Walks each part breadth first from its most-linked seat, gives every seat its depth and its
2950
+ * children, and returns the seat each part starts from.
2951
+ */
2952
+ function rootsOf(seats, random) {
2953
+ const roots = [];
2954
+ for (const hub of seats.toSorted((a, b) => b.neighbours.size - a.neighbours.size)) {
2955
+ if (hub.depth >= 0) continue;
2956
+ const part = [hub];
2957
+ hub.depth = 0;
2958
+ roots.push(hub);
2959
+ for (const seat of part) {
2960
+ const unvisited = [...seat.neighbours].filter((neighbour) => neighbour.depth < 0);
2961
+ for (const child of chained(shuffled(unvisited, random))) {
2962
+ child.depth = seat.depth + 1;
2963
+ seat.children.push(child);
2964
+ part.push(child);
2965
+ }
2966
+ }
2967
+ for (const seat of part.toReversed()) if (seat.children.length > 0) seat.leaves = seat.children.reduce((sum, child) => sum + child.leaves, 0);
2968
+ }
2969
+ return roots;
2970
+ }
2971
+ /**
2972
+ * Returns the start of every node of a force layout.
2973
+ *
2974
+ * @param ids - The graph's nodes.
2975
+ * @param links - The links between the nodes, read as undirected.
2976
+ * @param random - The number source the order of each node's neighbours and the first angle come
2977
+ * from.
2978
+ * @param step - The distance between two rings, in pixels.
2979
+ */
2980
+ function startsOf(ids, links, random, step) {
2981
+ const roots = rootsOf(seatsOf(ids, links), random);
2982
+ const total = roots.reduce((sum, root) => sum + root.leaves, 0);
2983
+ const offset = roots.length > 1 ? 1 : 0;
2984
+ const arcs = [];
2985
+ const starts = /* @__PURE__ */ new Map();
2986
+ let from = random() * Math.PI * 2;
2987
+ for (const seat of roots) {
2988
+ const to = from + Math.PI * 2 * seat.leaves / total;
2989
+ arcs.push({
2990
+ from,
2991
+ seat,
2992
+ to
2993
+ });
2994
+ from = to;
2995
+ }
2996
+ for (const arc of arcs) {
2997
+ const angle = (arc.from + arc.to) / 2;
2998
+ const radius = (arc.seat.depth + offset) * step;
2999
+ let start = arc.from;
3000
+ starts.set(arc.seat.id, {
3001
+ x: Math.cos(angle) * radius,
3002
+ y: Math.sin(angle) * radius
3003
+ });
3004
+ for (const seat of arc.seat.children) {
3005
+ const end = start + (arc.to - arc.from) * seat.leaves / arc.seat.leaves;
3006
+ arcs.push({
3007
+ from: start,
3008
+ seat,
3009
+ to: end
3010
+ });
3011
+ start = end;
3012
+ }
3013
+ }
3014
+ return starts;
3015
+ }
3016
+ //#endregion
3017
+ //#region src/layout/force.ts
3018
+ /**
3019
+ * Length of a link of strength 1, in pixels.
3020
+ */
3021
+ const DISTANCE = 110;
3022
+ /**
3023
+ * Push between every two nodes. A negative strength pushes.
3024
+ */
3025
+ const CHARGE = -500;
3026
+ /**
3027
+ * Strength of the pull to the middle across the canvas.
3028
+ */
3029
+ const PULL = .05;
3030
+ /**
3031
+ * How much stronger the pull to the middle is up and down than across.
3032
+ */
3033
+ const FLATTEN = 3;
3034
+ /**
3035
+ * Returns how strongly a link ties its ends, from 1 to 2: its strength, 1 unless stated.
3036
+ */
3037
+ function strengthOf(link) {
3038
+ return Math.min(Math.max(link.strength ?? 1, 1), 2);
3039
+ }
3040
+ /**
3041
+ * Returns a number source that yields the same numbers for the same seed.
3042
+ */
3043
+ function seeded(seed) {
3044
+ let state = seed;
3045
+ return () => {
3046
+ state = (state * 1664525 + 1013904223) % 4294967296;
3047
+ return state / 4294967296;
3048
+ };
3049
+ }
3050
+ /**
3051
+ * Returns the simulation's nodes at their starts.
3052
+ */
3053
+ function bodiesOf(nodes, starts) {
3054
+ return nodes.map((node) => {
3055
+ const size = sizeOf(node);
3056
+ const { x, y } = starts.get(node.id);
3057
+ return {
3058
+ node,
3059
+ radius: Math.hypot(size.width, size.height) / 2,
3060
+ size,
3061
+ x,
3062
+ y
3063
+ };
3064
+ });
3065
+ }
3066
+ /**
3067
+ * Returns the simulation's links: each link between two different nodes of the graph.
3068
+ */
3069
+ function tiesOf(links, ids) {
3070
+ return links.filter(({ source, target }) => source !== target && ids.has(source) && ids.has(target)).map((link) => ({
3071
+ source: link.source,
3072
+ strength: strengthOf(link),
3073
+ target: link.target
3074
+ }));
3075
+ }
3076
+ /**
3077
+ * Returns the nodes placed by the force simulation, each with the size it was placed by as its
3078
+ * initial size.
3079
+ *
3080
+ * @typeParam N - One node of the graph.
3081
+ * @param nodes - The graph's nodes, in any order.
3082
+ * @param links - The links between the nodes, read as undirected.
3083
+ * @param options - The number of steps and the seed.
3084
+ */
3085
+ function layoutForce(nodes, links, options = {}) {
3086
+ const { iterations = 300, seed = 1 } = options;
3087
+ const random = seeded(seed);
3088
+ const ids = nodes.map((node) => node.id);
3089
+ const bodies = bodiesOf(nodes, startsOf(ids, links, random, DISTANCE));
3090
+ const ties = tiesOf(links, new Set(ids));
3091
+ forceSimulation(bodies).randomSource(random).force("link", forceLink(ties).id((body) => body.node.id).distance((tie) => DISTANCE / tie.strength)).force("charge", forceManyBody().strength(CHARGE)).force("collide", forceCollide((body) => body.radius).iterations(2)).force("x", forceX().strength(PULL)).force("y", forceY().strength(PULL * FLATTEN)).force("center", forceCenter()).stop().tick(iterations);
3092
+ const placed = [];
3093
+ for (const { node, size, x = 0, y = 0 } of bodies) placed.push({
3094
+ ...node,
3095
+ initialHeight: size.height,
3096
+ initialWidth: size.width,
3097
+ position: {
3098
+ x: x - size.width / 2,
3099
+ y: y - size.height / 2
3100
+ }
3101
+ });
3102
+ return placed;
3103
+ }
3104
+ //#endregion
3105
+ //#region src/network-graph/disc.tsx
3106
+ /**
3107
+ * Renders the node's box with the recipe's disc node class.
3108
+ */
3109
+ const Box = withContext("div", "discNode");
3110
+ /**
3111
+ * Renders the disc with the recipe's disc class.
3112
+ */
3113
+ const Round = withContext("div", "disc");
3114
+ /**
3115
+ * Renders the hub the handles are in with the recipe's disc hub class.
3116
+ */
3117
+ const Hub = withContext("div", "discHub");
3118
+ /**
3119
+ * Renders the name with the recipe's disc label class.
3120
+ */
3121
+ const Name = withContext("span", "discLabel");
3122
+ /**
3123
+ * Renders the node's disc, its handles, its icon and its name from its data.
3124
+ *
3125
+ * @param props - React Flow's node props: the node's data among them.
3126
+ */
3127
+ function Disc(t0) {
3128
+ const $ = c(12);
3129
+ const { data } = t0;
3130
+ const t1 = `${String(data.disc)}px`;
3131
+ let t2;
3132
+ if ($[0] !== t1) {
3133
+ t2 = { "--graph-disc": t1 };
3134
+ $[0] = t1;
3135
+ $[1] = t2;
3136
+ } else t2 = $[1];
3137
+ const size = t2;
3138
+ const t3 = data.dimmed ? "" : void 0;
3139
+ let t4;
3140
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
3141
+ t4 = /* @__PURE__ */ jsxs(Hub, { children: [/* @__PURE__ */ jsx(Handle, {
3142
+ isConnectable: false,
3143
+ position: Position.Top,
3144
+ type: "target"
3145
+ }), /* @__PURE__ */ jsx(Handle, {
3146
+ isConnectable: false,
3147
+ position: Position.Top,
3148
+ type: "source"
3149
+ })] });
3150
+ $[2] = t4;
3151
+ } else t4 = $[2];
3152
+ let t5;
3153
+ if ($[3] !== data.icon || $[4] !== size) {
3154
+ t5 = /* @__PURE__ */ jsxs(Round, {
3155
+ "aria-hidden": true,
3156
+ style: size,
3157
+ children: [t4, data.icon]
3158
+ });
3159
+ $[3] = data.icon;
3160
+ $[4] = size;
3161
+ $[5] = t5;
3162
+ } else t5 = $[5];
3163
+ let t6;
3164
+ if ($[6] !== data.label) {
3165
+ t6 = /* @__PURE__ */ jsx(Name, { children: data.label });
3166
+ $[6] = data.label;
3167
+ $[7] = t6;
3168
+ } else t6 = $[7];
3169
+ let t7;
3170
+ if ($[8] !== t3 || $[9] !== t5 || $[10] !== t6) {
3171
+ t7 = /* @__PURE__ */ jsxs(Box, {
3172
+ "data-dimmed": t3,
3173
+ children: [t5, t6]
3174
+ });
3175
+ $[8] = t3;
3176
+ $[9] = t5;
3177
+ $[10] = t6;
3178
+ $[11] = t7;
3179
+ } else t7 = $[11];
3180
+ return t7;
3181
+ }
3182
+ //#endregion
3183
+ //#region src/network-graph/network.ts
3184
+ /**
3185
+ * Reads a network's links: the links the canvas renders with the nodes at their ends, and the
3186
+ * nodes one node connects to.
3187
+ *
3188
+ * @remarks
3189
+ * A link whose end is not a node of the graph is ordinary in a filtered graph, and a link from a
3190
+ * node to itself renders nothing, so neither is rendered, counted or weighed. A network's links
3191
+ * read the same either way round, so the walk from a node follows each link both ways. The walk
3192
+ * never counts the node it starts from, and stops at a node it visited before.
3193
+ */
3194
+ /**
3195
+ * Returns the links between two different nodes of the graph, each with the nodes at its ends.
3196
+ */
3197
+ function drawnOf(nodes, links) {
3198
+ const known = new Map(nodes.map((node) => [node.id, node]));
3199
+ return links.flatMap((link) => {
3200
+ const from = known.get(link.source);
3201
+ const to = known.get(link.target);
3202
+ return from === void 0 || to === void 0 || from === to ? [] : [{
3203
+ from,
3204
+ link,
3205
+ to
3206
+ }];
3207
+ });
3208
+ }
3209
+ /**
3210
+ * Returns the nodes within `depth` hops of a node, following each link either way round.
3211
+ *
3212
+ * @param links - The graph's links.
3213
+ * @param id - The node the walk starts from, which is never among the nodes it returns.
3214
+ * @param depth - How many hops count. 1 unless stated.
3215
+ */
3216
+ function neighborsOf(links, id, depth = 1) {
3217
+ return walk(links.flatMap(({ source, target }) => [{
3218
+ source,
3219
+ target
3220
+ }, {
3221
+ source: target,
3222
+ target: source
3223
+ }]), id, depth, true);
3224
+ }
3225
+ //#endregion
3226
+ //#region src/network-graph/discs.ts
3227
+ /**
3228
+ * Smallest and largest diameter of a disc, in pixels.
3229
+ */
3230
+ const DIAMETER = {
3231
+ max: 68,
3232
+ min: 36
3233
+ };
3234
+ /**
3235
+ * Returns each node, in order, with the diameter of its disc.
3236
+ *
3237
+ * @param nodes - The graph's nodes.
3238
+ * @param links - The links the canvas renders, which a node without a weight is weighed by.
3239
+ */
3240
+ function discsOf(nodes, links) {
3241
+ const degree = /* @__PURE__ */ new Map();
3242
+ for (const { source, target } of links) {
3243
+ degree.set(source, (degree.get(source) ?? 0) + 1);
3244
+ degree.set(target, (degree.get(target) ?? 0) + 1);
3245
+ }
3246
+ const weighed = nodes.map((node) => ({
3247
+ node,
3248
+ weight: Math.max(node.weight ?? degree.get(node.id) ?? 0, 0)
3249
+ }));
3250
+ const heaviest = Math.max(0, ...weighed.map(({ weight }) => weight));
3251
+ const span = DIAMETER.max - DIAMETER.min;
3252
+ return weighed.map(({ node, weight }) => ({
3253
+ disc: DIAMETER.min + span * (heaviest === 0 ? 0 : Math.sqrt(weight / heaviest)),
3254
+ node
3255
+ }));
3256
+ }
3257
+ //#endregion
3258
+ //#region src/network-graph/places.ts
3259
+ /**
3260
+ * Width a name takes for each of its characters, in pixels.
3261
+ */
3262
+ const CHARACTER = 8.5;
3263
+ /**
3264
+ * Inline padding of a name's pill, both sides together, in pixels.
3265
+ */
3266
+ const PILL = 8;
3267
+ /**
3268
+ * Height of a name's line under the disc and the gap above it, in pixels.
3269
+ */
3270
+ const NAME = 26;
3271
+ /**
3272
+ * Returns the box the layout places a node by: as wide as its disc or its name, whichever is
3273
+ * wider, and as tall as the disc and the name's line.
3274
+ */
3275
+ function boxOf({ disc, node }) {
3276
+ return {
3277
+ height: disc + NAME,
3278
+ width: Math.max(disc, node.label.length * CHARACTER + PILL)
3279
+ };
3280
+ }
3281
+ /**
3282
+ * Returns each node, in order, with its disc and the middle of its top edge where the layout put
3283
+ * it.
3284
+ *
3285
+ * @param input - The nodes, the links, the seed and the steps.
3286
+ */
3287
+ function placesOf(input) {
3288
+ const boxes = discsOf(input.nodes, input.links).map((sized) => ({
3289
+ data: { sized },
3290
+ id: sized.node.id,
3291
+ measured: boxOf(sized),
3292
+ position: {
3293
+ x: 0,
3294
+ y: 0
3295
+ }
3296
+ }));
3297
+ const options = {
3298
+ iterations: input.iterations,
3299
+ seed: input.seed
3300
+ };
3301
+ return layoutForce(boxes, input.links, options).map(({ data, measured, position }) => ({
3302
+ disc: data.sized.disc,
3303
+ node: data.sized.node,
3304
+ position: {
3305
+ x: position.x + measured.width / 2,
3306
+ y: position.y
3307
+ }
3308
+ }));
3309
+ }
3310
+ //#endregion
3311
+ //#region src/network-graph/view.ts
3312
+ /**
3313
+ * Point of a disc node its position is: the middle of its top edge.
3314
+ */
3315
+ const TOP_MIDDLE = [.5, 0];
3316
+ /**
3317
+ * Returns the word a node's accessible name ends with: the focus's, a lit node's, or none.
3318
+ */
3319
+ function tagOf(id, input, lit) {
3320
+ if (id === input.focus) return input.words.focusLabel;
3321
+ return lit?.has(id) === true ? input.words.neighborLabel : void 0;
3322
+ }
3323
+ /**
3324
+ * Returns the disc of one node, where the layout put it.
3325
+ */
3326
+ function discOf({ disc, node, position }, input, lit) {
3327
+ const tag = tagOf(node.id, input, lit);
3328
+ const size = input.sizes.get(node.id);
3329
+ return {
3330
+ ariaLabel: tag === void 0 ? node.label : `${node.label}, ${tag}`,
3331
+ data: {
3332
+ dimmed: lit !== void 0 && tag === void 0,
3333
+ disc,
3334
+ icon: node.icon,
3335
+ label: node.label
3336
+ },
3337
+ id: node.id,
3338
+ origin: TOP_MIDDLE,
3339
+ position,
3340
+ selected: node.id === input.focus,
3341
+ type: "disc",
3342
+ ...size === void 0 ? {} : { measured: size }
3343
+ };
3344
+ }
3345
+ /**
3346
+ * Returns a link for React Flow: a straight line named by its ends, as wide as its tie, and marked
3347
+ * on or off the lit set while a node is focused.
3348
+ */
3349
+ function edgeOf({ from, link, to }, input, lit) {
3350
+ const strength = { "--graph-strength": String(strengthOf(link)) };
3351
+ const on = [from.id, to.id].every((id) => id === input.focus || lit?.has(id) === true);
3352
+ return {
3353
+ ariaLabel: input.words.edgeName({
3354
+ source: from.label,
3355
+ target: to.label
3356
+ }),
3357
+ id: link.id ?? `${from.id}-${to.id}`,
3358
+ selectable: false,
3359
+ source: from.id,
3360
+ style: strength,
3361
+ target: to.id,
3362
+ type: "straight",
3363
+ ...lit === void 0 ? {} : { domAttributes: traceMarkOf(on) }
3364
+ };
3365
+ }
3366
+ /**
3367
+ * Returns a focus's connections: the focused node's name, else its id, and the number of nodes lit.
3368
+ */
3369
+ function connectionsOf(input, lit) {
3370
+ const { focus, placed } = input;
3371
+ if (focus === null || lit === void 0) return void 0;
3372
+ return {
3373
+ count: lit.size,
3374
+ name: placed.find(({ node }) => node.id === focus)?.node.label ?? focus
3375
+ };
3376
+ }
3377
+ /**
3378
+ * Returns the view of a network graph: its discs where the layout put them and its links.
3379
+ *
3380
+ * @param input - The placed nodes, the links, the focus, the sizes and the words.
3381
+ */
3382
+ function viewOf(input) {
3383
+ const links = input.drawn.map(({ link }) => link);
3384
+ const lit = input.focus === null ? void 0 : neighborsOf(links, input.focus, input.depth);
3385
+ return {
3386
+ complete: input.placed.every(({ node }) => input.sizes.has(node.id)),
3387
+ connections: connectionsOf(input, lit),
3388
+ edges: input.drawn.map((drawn) => edgeOf(drawn, input, lit)),
3389
+ nodes: input.placed.map((placed) => discOf(placed, input, lit))
3390
+ };
3391
+ }
3392
+ //#endregion
3393
+ //#region src/network-graph/state.ts
3394
+ /**
3395
+ * Keeps a network graph's focus, measured sizes and the positions people moved nodes to, and
3396
+ * works out its view.
3397
+ *
3398
+ * @remarks
3399
+ * The focus is controlled where the caller passes it. A focus of `null` is a controlled graph
3400
+ * with no focus, because `undefined` leaves the focus to the graph. The measured sizes go back to
3401
+ * React Flow with each node, which keeps its handles' measurements, and play no part in the
3402
+ * layout. A moved node keeps its position while the layout is the one it was moved on, and goes
3403
+ * back to the layout's position when the layout changes, such as for new data or a new seed. A
3404
+ * change that changes no state renders nothing again, so a report React Flow repeats for nodes
3405
+ * it measures again cannot start a loop of renders.
3406
+ */
3407
+ /**
3408
+ * No position, for a layout nobody moved a node on.
3409
+ */
3410
+ const UNMOVED = /* @__PURE__ */ new Map();
3411
+ /**
3412
+ * Returns the nodes with each moved node at the position a person moved it to.
3413
+ */
3414
+ function movedTo(nodes, positions) {
3415
+ return nodes.map((node) => {
3416
+ const position = positions.get(node.id);
3417
+ return position === void 0 ? node : {
3418
+ ...node,
3419
+ position
3420
+ };
3421
+ });
3422
+ }
3423
+ /**
3424
+ * Returns the network's view in its current state and the functions that change the state.
3425
+ *
3426
+ * @param props - The graph, its focus, its layout and its words.
3427
+ */
3428
+ function useLinked(props) {
3429
+ const $ = c(35);
3430
+ let t0;
3431
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
3432
+ t0 = /* @__PURE__ */ new Map();
3433
+ $[0] = t0;
3434
+ } else t0 = $[0];
3435
+ const [sizes, setSizes] = useState(t0);
3436
+ let t1;
3437
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
3438
+ t1 = {
3439
+ layout: "",
3440
+ positions: UNMOVED
3441
+ };
3442
+ $[1] = t1;
3443
+ } else t1 = $[1];
3444
+ const [moves, setMoves] = useState(t1);
3445
+ const t2 = props.defaultFocus ?? null;
3446
+ let t3;
3447
+ if ($[2] !== props.focus || $[3] !== props.onFocusChange || $[4] !== t2) {
3448
+ t3 = {
3449
+ defaultValue: t2,
3450
+ onChange: props.onFocusChange,
3451
+ value: props.focus
3452
+ };
3453
+ $[2] = props.focus;
3454
+ $[3] = props.onFocusChange;
3455
+ $[4] = t2;
3456
+ $[5] = t3;
3457
+ } else t3 = $[5];
3458
+ const [focus, setFocus] = useControllableState(t3);
3459
+ let layout;
3460
+ let t4;
3461
+ let t5;
3462
+ let t6;
3463
+ let view;
3464
+ if ($[6] !== focus || $[7] !== moves.layout || $[8] !== moves.positions || $[9] !== props.depth || $[10] !== props.iterations || $[11] !== props.links || $[12] !== props.nodes || $[13] !== props.seed || $[14] !== props.words || $[15] !== setFocus || $[16] !== sizes) {
3465
+ const drawn = drawnOf(props.nodes, props.links);
3466
+ const placed = placesOf({
3467
+ iterations: props.iterations ?? 300,
3468
+ links: drawn.map(_temp),
3469
+ nodes: props.nodes,
3470
+ seed: props.seed ?? 1
3471
+ });
3472
+ view = viewOf({
3473
+ depth: props.depth ?? 1,
3474
+ drawn,
3475
+ focus,
3476
+ placed,
3477
+ sizes,
3478
+ words: props.words
3479
+ });
3480
+ layout = signatureOf(view.nodes);
3481
+ const positions = moves.layout === layout ? moves.positions : UNMOVED;
3482
+ if ($[22] !== setFocus) {
3483
+ t4 = () => {
3484
+ setFocus(null);
3485
+ };
3486
+ $[22] = setFocus;
3487
+ $[23] = t4;
3488
+ } else t4 = $[23];
3489
+ if ($[24] !== positions) {
3490
+ t5 = new Set(positions.keys());
3491
+ $[24] = positions;
3492
+ $[25] = t5;
3493
+ } else t5 = $[25];
3494
+ t6 = movedTo(view.nodes, positions);
3495
+ $[6] = focus;
3496
+ $[7] = moves.layout;
3497
+ $[8] = moves.positions;
3498
+ $[9] = props.depth;
3499
+ $[10] = props.iterations;
3500
+ $[11] = props.links;
3501
+ $[12] = props.nodes;
3502
+ $[13] = props.seed;
3503
+ $[14] = props.words;
3504
+ $[15] = setFocus;
3505
+ $[16] = sizes;
3506
+ $[17] = layout;
3507
+ $[18] = t4;
3508
+ $[19] = t5;
3509
+ $[20] = t6;
3510
+ $[21] = view;
3511
+ } else {
3512
+ layout = $[17];
3513
+ t4 = $[18];
3514
+ t5 = $[19];
3515
+ t6 = $[20];
3516
+ view = $[21];
3517
+ }
3518
+ let t7;
3519
+ if ($[26] !== layout || $[27] !== setFocus) {
3520
+ t7 = (changes) => {
3521
+ const selected = selectedBy(changes);
3522
+ if (selected !== void 0) setFocus(selected);
3523
+ setSizes((known) => measuredBy(known, changes));
3524
+ setMoves((known_0) => {
3525
+ const kept = known_0.layout === layout ? known_0.positions : UNMOVED;
3526
+ const next = movedBy(kept, changes);
3527
+ return next === kept ? known_0 : {
3528
+ layout,
3529
+ positions: next
3530
+ };
3531
+ });
3532
+ };
3533
+ $[26] = layout;
3534
+ $[27] = setFocus;
3535
+ $[28] = t7;
3536
+ } else t7 = $[28];
3537
+ let t8;
3538
+ if ($[29] !== t4 || $[30] !== t5 || $[31] !== t6 || $[32] !== t7 || $[33] !== view) {
3539
+ t8 = {
3540
+ clear: t4,
3541
+ moved: t5,
3542
+ nodes: t6,
3543
+ onNodesChange: t7,
3544
+ view
3545
+ };
3546
+ $[29] = t4;
3547
+ $[30] = t5;
3548
+ $[31] = t6;
3549
+ $[32] = t7;
3550
+ $[33] = view;
3551
+ $[34] = t8;
3552
+ } else t8 = $[34];
3553
+ return t8;
3554
+ }
3555
+ function _temp(t0) {
3556
+ const { link } = t0;
3557
+ return link;
3558
+ }
3559
+ //#endregion
3560
+ //#region src/network-graph/linked.tsx
3561
+ /**
3562
+ * Maps the network's node type to its disc.
3563
+ */
3564
+ const NODE_TYPES = { disc: Disc };
3565
+ /**
3566
+ * Renders the canvas over the network's view and the readout below it.
3567
+ *
3568
+ * @param props - The graph, its focus, its layout and its words.
3569
+ */
3570
+ function Linked(props) {
3571
+ const $ = c(21);
3572
+ const { clear, moved, nodes, onNodesChange, view } = useLinked(props);
3573
+ const placing = usePlaced(view.nodes, view.complete, moved);
3574
+ const { connections } = view;
3575
+ const { words } = props;
3576
+ const t0 = placing ? "" : void 0;
3577
+ let t1;
3578
+ if ($[0] !== nodes || $[1] !== onNodesChange || $[2] !== props.draggable || $[3] !== props.label || $[4] !== t0 || $[5] !== view.edges || $[6] !== words.moveAnnouncement || $[7] !== words.nodeDescription) {
3579
+ t1 = /* @__PURE__ */ jsx(Canvas, {
3580
+ "data-placing": t0,
3581
+ edges: view.edges,
3582
+ edgesFocusable: false,
3583
+ label: props.label,
3584
+ moveAnnouncement: words.moveAnnouncement,
3585
+ multiSelectionKeyCode: null,
3586
+ nodeDescription: words.nodeDescription,
3587
+ nodes,
3588
+ nodesConnectable: false,
3589
+ nodesDraggable: props.draggable,
3590
+ nodeTypes: NODE_TYPES,
3591
+ onNodesChange,
3592
+ selectionKeyCode: null
3593
+ });
3594
+ $[0] = nodes;
3595
+ $[1] = onNodesChange;
3596
+ $[2] = props.draggable;
3597
+ $[3] = props.label;
3598
+ $[4] = t0;
3599
+ $[5] = view.edges;
3600
+ $[6] = words.moveAnnouncement;
3601
+ $[7] = words.nodeDescription;
3602
+ $[8] = t1;
3603
+ } else t1 = $[8];
3604
+ const t2 = connections !== void 0;
3605
+ let t3;
3606
+ if ($[9] !== connections || $[10] !== words) {
3607
+ t3 = connections === void 0 ? words.promptLabel : words.summary(connections);
3608
+ $[9] = connections;
3609
+ $[10] = words;
3610
+ $[11] = t3;
3611
+ } else t3 = $[11];
3612
+ let t4;
3613
+ if ($[12] !== clear || $[13] !== props.overview || $[14] !== t2 || $[15] !== t3 || $[16] !== words.clearLabel) {
3614
+ t4 = /* @__PURE__ */ jsx(Readout, {
3615
+ clearLabel: words.clearLabel,
3616
+ focused: t2,
3617
+ onClear: clear,
3618
+ text: t3,
3619
+ children: props.overview
3620
+ });
3621
+ $[12] = clear;
3622
+ $[13] = props.overview;
3623
+ $[14] = t2;
3624
+ $[15] = t3;
3625
+ $[16] = words.clearLabel;
3626
+ $[17] = t4;
3627
+ } else t4 = $[17];
3628
+ let t5;
3629
+ if ($[18] !== t1 || $[19] !== t4) {
3630
+ t5 = /* @__PURE__ */ jsxs(Fragment, { children: [t1, t4] });
3631
+ $[18] = t1;
3632
+ $[19] = t4;
3633
+ $[20] = t5;
3634
+ } else t5 = $[20];
3635
+ return t5;
3636
+ }
3637
+ //#endregion
3638
+ //#region src/network-graph/words.ts
3639
+ /**
3640
+ * Writes the words a network graph renders, from the caller's words and the English defaults.
3641
+ *
3642
+ * @remarks
3643
+ * A caller that states a word replaces the default and keeps every other default. A link names
3644
+ * its ends with "and", because a link reads the same either way round. The description offers
3645
+ * the arrow keys only while nodes can be moved.
3646
+ */
3647
+ /**
3648
+ * Instruction every node is described by while nodes can be moved, unless stated.
3649
+ */
3650
+ const MOVABLE = "Press Enter or Space to focus the node, the arrow keys to move it, and Escape to clear the focus.";
3651
+ /**
3652
+ * Instruction every node is described by while nodes cannot be moved, unless stated.
3653
+ */
3654
+ const FIXED = "Press Enter or Space to focus the node, and Escape to clear the focus.";
3655
+ /**
3656
+ * Writes the name of a link from the names of its ends, unless stated.
3657
+ */
3658
+ function linked({ source, target }) {
3659
+ return `${source} and ${target}`;
3660
+ }
3661
+ /**
3662
+ * Writes the summary of a focus, unless stated.
3663
+ */
3664
+ function counted({ count, name }) {
3665
+ return `${name}: ${String(count)} ${count === 1 ? "connection" : "connections"}`;
3666
+ }
3667
+ /**
3668
+ * English words of a network graph whose nodes can be moved.
3669
+ */
3670
+ const WORDS = {
3671
+ clearLabel: "Clear focus",
3672
+ edgeName: linked,
3673
+ emptyLabel: "No nodes to show.",
3674
+ focusLabel: "Focus",
3675
+ moveAnnouncement: moveAnnouncementOf,
3676
+ neighborLabel: "Connected",
3677
+ nodeDescription: MOVABLE,
3678
+ promptLabel: "Select a node to show what it connects to.",
3679
+ summary: counted
3680
+ };
3681
+ /**
3682
+ * Returns the caller's words over the English defaults.
3683
+ *
3684
+ * @param words - The words the caller states.
3685
+ * @param draggable - Whether a person can move the nodes, which the default description offers.
3686
+ */
3687
+ function wordsOf(words, draggable) {
3688
+ return {
3689
+ ...WORDS,
3690
+ nodeDescription: draggable ? MOVABLE : FIXED,
3691
+ ...omitUndefined(words)
3692
+ };
3693
+ }
3694
+ //#endregion
3695
+ //#region src/network-graph/network-graph.tsx
3696
+ /**
3697
+ * Splits a network graph's props into its words, the canvas's props, the dragging and the
3698
+ * figure's props.
3699
+ */
3700
+ function split(props) {
3701
+ const { clearLabel, defaultFocus, depth, draggable = true, edgeName, emptyLabel, focus, focusLabel, iterations, label, links, moveAnnouncement, neighborLabel, nodeDescription, nodes, onFocusChange, overview, promptLabel, seed, summary, ...rest } = props;
3702
+ const words = {
3703
+ clearLabel,
3704
+ edgeName,
3705
+ emptyLabel,
3706
+ focusLabel,
3707
+ moveAnnouncement
3708
+ };
3709
+ const more = {
3710
+ neighborLabel,
3711
+ nodeDescription,
3712
+ promptLabel,
3713
+ summary
3714
+ };
3715
+ const own = {
3716
+ defaultFocus,
3717
+ depth,
3718
+ focus,
3719
+ iterations,
3720
+ label,
3721
+ links,
3722
+ nodes,
3723
+ onFocusChange
3724
+ };
3725
+ return [
3726
+ {
3727
+ ...words,
3728
+ ...more
3729
+ },
3730
+ {
3731
+ ...own,
3732
+ overview,
3733
+ seed
3734
+ },
3735
+ draggable,
3736
+ rest
3737
+ ];
3738
+ }
3739
+ /**
3740
+ * Renders the figure: the controls, the canvas, its readout with the overview, and the caption,
3741
+ * or the empty state while the graph has no node.
3742
+ *
3743
+ * @param props - The graph, its focus, its layout, its parts, its words and the figure's props.
3744
+ */
3745
+ function NetworkGraph(props) {
3746
+ const $ = c(18);
3747
+ let caption;
3748
+ let controls;
3749
+ let draggable;
3750
+ let own;
3751
+ let root;
3752
+ let t0;
3753
+ if ($[0] !== props) {
3754
+ const [stated, t1, t2, rest] = split(props);
3755
+ own = t1;
3756
+ draggable = t2;
3757
+ ({caption, controls, ...root} = rest);
3758
+ t0 = wordsOf(stated, draggable);
3759
+ $[0] = props;
3760
+ $[1] = caption;
3761
+ $[2] = controls;
3762
+ $[3] = draggable;
3763
+ $[4] = own;
3764
+ $[5] = root;
3765
+ $[6] = t0;
3766
+ } else {
3767
+ caption = $[1];
3768
+ controls = $[2];
3769
+ draggable = $[3];
3770
+ own = $[4];
3771
+ root = $[5];
3772
+ t0 = $[6];
3773
+ }
3774
+ const words = t0;
3775
+ let t1;
3776
+ if ($[7] !== controls || $[8] !== draggable || $[9] !== own || $[10] !== words) {
3777
+ t1 = own.nodes.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: words.emptyLabel }) : /* @__PURE__ */ jsxs(Fragment, { children: [controls, /* @__PURE__ */ jsx(Linked, {
3778
+ draggable,
3779
+ words,
3780
+ ...own
3781
+ })] });
3782
+ $[7] = controls;
3783
+ $[8] = draggable;
3784
+ $[9] = own;
3785
+ $[10] = words;
3786
+ $[11] = t1;
3787
+ } else t1 = $[11];
3788
+ let t2;
3789
+ if ($[12] !== caption) {
3790
+ t2 = caption === void 0 ? null : /* @__PURE__ */ jsx(Caption, { children: caption });
3791
+ $[12] = caption;
3792
+ $[13] = t2;
3793
+ } else t2 = $[13];
3794
+ let t3;
3795
+ if ($[14] !== root || $[15] !== t1 || $[16] !== t2) {
3796
+ t3 = /* @__PURE__ */ jsxs(Root, {
3797
+ ...root,
3798
+ children: [t1, t2]
3799
+ });
3800
+ $[14] = root;
3801
+ $[15] = t1;
3802
+ $[16] = t2;
3803
+ $[17] = t3;
3804
+ } else t3 = $[17];
3805
+ return t3;
3806
+ }
3807
+ //#endregion
3808
+ export { relationOf as _, ZoomLevel as a, hiddenBy as b, Controls as c, DirectedGraph as d, Root as f, layoutGraph as g, Canvas as h, graph_exports as i, Control as l, Summary as m, neighborsOf as n, PaletteItem as o, Caption as p, layoutForce as r, MiniMap$1 as s, NetworkGraph as t, treeEdges as u, traceGraph as v, diffGraphs as x, descendantsOf as y };