@misoto22/design 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +12 -0
  2. package/dist/agent/Accordion.md +1 -1
  3. package/dist/agent/Alert.md +1 -1
  4. package/dist/agent/AppShell.md +1 -1
  5. package/dist/agent/ArchitectureFigure.md +48 -0
  6. package/dist/agent/AreaChart.md +2 -2
  7. package/dist/agent/Article.md +1 -1
  8. package/dist/agent/Avatar.md +1 -1
  9. package/dist/agent/Badge.md +1 -1
  10. package/dist/agent/BarChart.md +2 -2
  11. package/dist/agent/BarList.md +2 -2
  12. package/dist/agent/BigNumber.md +2 -2
  13. package/dist/agent/BoxPlot.md +2 -2
  14. package/dist/agent/Breadcrumb.md +1 -1
  15. package/dist/agent/BulletChart.md +2 -2
  16. package/dist/agent/Button.md +1 -1
  17. package/dist/agent/Calendar.md +1 -1
  18. package/dist/agent/Card.md +1 -1
  19. package/dist/agent/Checkbox.md +1 -1
  20. package/dist/agent/Collapsible.md +1 -1
  21. package/dist/agent/Combobox.md +1 -1
  22. package/dist/agent/Command.md +1 -1
  23. package/dist/agent/ComposedChart.md +2 -2
  24. package/dist/agent/ContextMenu.md +1 -1
  25. package/dist/agent/DataflowFigure.md +27 -0
  26. package/dist/agent/DatePicker.md +1 -1
  27. package/dist/agent/Diagram.md +1 -1
  28. package/dist/agent/DiagramCanvas.md +47 -0
  29. package/dist/agent/DiagramExportMenu.md +38 -0
  30. package/dist/agent/DiagramInspector.md +50 -0
  31. package/dist/agent/DiagramLegend.md +34 -0
  32. package/dist/agent/DiagramMinimap.md +48 -0
  33. package/dist/agent/DiagramToolbar.md +47 -0
  34. package/dist/agent/Dialog.md +1 -1
  35. package/dist/agent/DropdownMenu.md +1 -1
  36. package/dist/agent/EmptyState.md +1 -1
  37. package/dist/agent/ErrorState.md +1 -1
  38. package/dist/agent/Facet.md +2 -2
  39. package/dist/agent/Field.md +1 -1
  40. package/dist/agent/FigureBand.md +1 -1
  41. package/dist/agent/FloatingIconButton.md +1 -1
  42. package/dist/agent/FunnelChart.md +2 -2
  43. package/dist/agent/Heatmap.md +2 -2
  44. package/dist/agent/Histogram.md +2 -2
  45. package/dist/agent/Input.md +1 -1
  46. package/dist/agent/Kbd.md +1 -1
  47. package/dist/agent/LifecycleFigure.md +28 -0
  48. package/dist/agent/LineChart.md +2 -2
  49. package/dist/agent/LinkArrow.md +1 -1
  50. package/dist/agent/NativeSelect.md +1 -1
  51. package/dist/agent/NavItem.md +1 -1
  52. package/dist/agent/Pagination.md +1 -1
  53. package/dist/agent/PieChart.md +2 -2
  54. package/dist/agent/Popover.md +1 -1
  55. package/dist/agent/Progress.md +1 -1
  56. package/dist/agent/RadarChart.md +2 -2
  57. package/dist/agent/RadialChart.md +2 -2
  58. package/dist/agent/RadioGroup.md +1 -1
  59. package/dist/agent/SankeyChart.md +2 -2
  60. package/dist/agent/ScatterChart.md +2 -2
  61. package/dist/agent/ScrollArea.md +1 -1
  62. package/dist/agent/SearchableMenu.md +1 -1
  63. package/dist/agent/Select.md +1 -1
  64. package/dist/agent/Separator.md +1 -1
  65. package/dist/agent/SequenceFigure.md +28 -0
  66. package/dist/agent/Sheet.md +1 -1
  67. package/dist/agent/Skeleton.md +1 -1
  68. package/dist/agent/Slider.md +1 -1
  69. package/dist/agent/Sparkline.md +2 -2
  70. package/dist/agent/Spinner.md +1 -1
  71. package/dist/agent/StatusDot.md +1 -1
  72. package/dist/agent/StatusPill.md +1 -1
  73. package/dist/agent/Steps.md +1 -1
  74. package/dist/agent/Switch.md +1 -1
  75. package/dist/agent/Table.md +1 -1
  76. package/dist/agent/Tabs.md +1 -1
  77. package/dist/agent/Tag.md +1 -1
  78. package/dist/agent/Textarea.md +1 -1
  79. package/dist/agent/Toast.md +1 -1
  80. package/dist/agent/ToggleGroup.md +1 -1
  81. package/dist/agent/Tooltip.md +1 -1
  82. package/dist/agent/TreemapChart.md +2 -2
  83. package/dist/agent/WaterfallChart.md +2 -2
  84. package/dist/agent/WorkflowFigure.md +28 -0
  85. package/dist/agent/catalog.json +295 -3
  86. package/dist/agent/index.md +15 -1
  87. package/dist/charts/index.d.ts +2 -1
  88. package/dist/charts/lib/export.d.ts +33 -43
  89. package/dist/charts/lib/export.js +16 -158
  90. package/dist/charts/lib/export.js.map +1 -1
  91. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +49 -0
  92. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +135 -0
  93. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -0
  94. package/dist/diagrams/DataflowFigure/DataflowFigure.d.ts +28 -0
  95. package/dist/diagrams/DataflowFigure/DataflowFigure.js +129 -0
  96. package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -0
  97. package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +64 -0
  98. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +222 -0
  99. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -0
  100. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +72 -0
  101. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +125 -0
  102. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -0
  103. package/dist/diagrams/DiagramInspector/DiagramInspector.d.ts +70 -0
  104. package/dist/diagrams/DiagramInspector/DiagramInspector.js +94 -0
  105. package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -0
  106. package/dist/diagrams/DiagramLegend/DiagramLegend.d.ts +42 -0
  107. package/dist/diagrams/DiagramLegend/DiagramLegend.js +35 -0
  108. package/dist/diagrams/DiagramLegend/DiagramLegend.js.map +1 -0
  109. package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +61 -0
  110. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +79 -0
  111. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -0
  112. package/dist/diagrams/DiagramToolbar/DiagramToolbar.d.ts +48 -0
  113. package/dist/diagrams/DiagramToolbar/DiagramToolbar.js +53 -0
  114. package/dist/diagrams/DiagramToolbar/DiagramToolbar.js.map +1 -0
  115. package/dist/diagrams/LifecycleFigure/LifecycleFigure.d.ts +31 -0
  116. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +202 -0
  117. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -0
  118. package/dist/diagrams/SequenceFigure/SequenceFigure.d.ts +38 -0
  119. package/dist/diagrams/SequenceFigure/SequenceFigure.js +231 -0
  120. package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -0
  121. package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +34 -0
  122. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +211 -0
  123. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -0
  124. package/dist/diagrams/index.d.ts +16 -0
  125. package/dist/diagrams/index.js +19 -0
  126. package/dist/diagrams/index.js.map +1 -0
  127. package/dist/diagrams/lib/frame.d.ts +97 -0
  128. package/dist/diagrams/lib/frame.js +116 -0
  129. package/dist/diagrams/lib/frame.js.map +1 -0
  130. package/dist/diagrams/lib/geometry.d.ts +114 -0
  131. package/dist/diagrams/lib/geometry.js +113 -0
  132. package/dist/diagrams/lib/geometry.js.map +1 -0
  133. package/dist/diagrams/lib/legend.d.ts +32 -0
  134. package/dist/diagrams/lib/legend.js +84 -0
  135. package/dist/diagrams/lib/legend.js.map +1 -0
  136. package/dist/diagrams/lib/marks.d.ts +207 -0
  137. package/dist/diagrams/lib/marks.js +480 -0
  138. package/dist/diagrams/lib/marks.js.map +1 -0
  139. package/dist/diagrams/lib/route.d.ts +85 -0
  140. package/dist/diagrams/lib/route.js +178 -0
  141. package/dist/diagrams/lib/route.js.map +1 -0
  142. package/dist/diagrams/lib/wires.d.ts +59 -0
  143. package/dist/diagrams/lib/wires.js +90 -0
  144. package/dist/diagrams/lib/wires.js.map +1 -0
  145. package/dist/diagrams/spec.d.ts +353 -0
  146. package/dist/diagrams/spec.js +1 -0
  147. package/dist/diagrams/spec.js.map +1 -0
  148. package/dist/lib/svg-export.d.ts +138 -0
  149. package/dist/lib/svg-export.js +198 -0
  150. package/dist/lib/svg-export.js.map +1 -0
  151. package/dist/semantic.css +34 -0
  152. package/dist/styles.css +1 -1
  153. package/dist/tokens.d.ts +16 -0
  154. package/dist/tokens.js +82 -0
  155. package/dist/tokens.json +82 -0
  156. package/package.json +5 -1
  157. package/skills/misoto22-design/SKILL.md +58 -6
@@ -0,0 +1,114 @@
1
+ /**
2
+ * The arithmetic every figure shares: boxes, text extents, and the frame that
3
+ * ends up around them.
4
+ *
5
+ * Everything here is a pure function of numbers already in the specification.
6
+ * That is not a stylistic preference — it is what lets these figures render on
7
+ * a server. A layout that measured real text would need a DOM, a layout that
8
+ * relaxed a force graph would need to settle, and neither can produce the same
9
+ * markup twice. Both would hydrate into a different picture than the one that
10
+ * was sent, which for a diagram is worse than not rendering at all.
11
+ */
12
+ /** A rectangle in the figure's user-unit space. */
13
+ interface Box {
14
+ x: number;
15
+ y: number;
16
+ w: number;
17
+ h: number;
18
+ }
19
+ declare const centerOf: (box: Box) => [number, number];
20
+ /** Grows a rectangle by `pad` on every side. */
21
+ declare function inflate(box: Box, pad: number): Box;
22
+ /** The smallest rectangle containing all of them. Returns null for an empty list. */
23
+ declare function union(boxes: Box[]): Box | null;
24
+ /**
25
+ * How wide a run of text is, without measuring it.
26
+ *
27
+ * A real measurement needs a DOM and the loaded face, and this module has
28
+ * neither by design — so this is an estimate, and the honest thing is to say
29
+ * what kind. Latin glyphs in the system's sans average close to 0.52em of
30
+ * advance across ordinary label text; digits and capitals run wider; a CJK
31
+ * ideograph is a full em by definition, as is a fullwidth punctuation mark.
32
+ *
33
+ * The estimate is used for two things and neither needs to be exact: choosing a
34
+ * box wide enough that a label is not clipped, and deciding where a line's
35
+ * label mask sits. Both fail gracefully — a box a few units too wide is
36
+ * invisible, and a mask a few units too wide hides a little more of a line than
37
+ * it had to.
38
+ *
39
+ * It is deliberately NOT used to justify, wrap mid-word, or letter-space
40
+ * anything. Those need the real face.
41
+ */
42
+ declare function textWidth(text: string, fontSize: number): number;
43
+ /**
44
+ * A label broken to fit a width, with a hard cap on how many lines it may take.
45
+ *
46
+ * Breaks on spaces for Latin and between characters for CJK, which has no
47
+ * spaces to break on. A word longer than the whole line is left to overflow
48
+ * rather than hyphenated: an identifier — `TenantMainMiddleware` — split across
49
+ * two lines is harder to read than one that runs slightly wide, and the caller
50
+ * sized the box from `textWidth` anyway.
51
+ *
52
+ * The last line is ellipsised when the text does not fit in `maxLines`, because
53
+ * silently dropping the tail is how a diagram comes to say something it does
54
+ * not mean.
55
+ */
56
+ declare function wrapText(text: string, fontSize: number, maxWidth: number, maxLines?: number): string[];
57
+ /**
58
+ * The type scale inside a figure, in user units.
59
+ *
60
+ * Fixed numbers rather than tokens, because these are SVG geometry: a `<text>`
61
+ * sized in `rem` inside a scaled `viewBox` changes size when the figure is
62
+ * zoomed, which means a diagram at 200% is a different diagram. The figure
63
+ * scales as one picture, so its type scales with it.
64
+ */
65
+ declare const TYPE: {
66
+ /** A node's name. */
67
+ readonly label: 13;
68
+ /** The line under it. */
69
+ readonly sub: 10.5;
70
+ /** A mono chip: a tag, a classification, a step number. */
71
+ readonly chip: 9.5;
72
+ /** A relationship's own wording. */
73
+ readonly edge: 10.5;
74
+ /** A band, lane or stage heading. */
75
+ readonly band: 10.5;
76
+ };
77
+ /** Default box sizes per diagram type, in user units. */
78
+ declare const BOX: {
79
+ readonly node: {
80
+ readonly w: 168;
81
+ readonly h: 62;
82
+ };
83
+ readonly wide: {
84
+ readonly w: 200;
85
+ readonly h: 62;
86
+ };
87
+ readonly participant: {
88
+ readonly w: 128;
89
+ readonly h: 46;
90
+ };
91
+ };
92
+ /**
93
+ * How tall a box has to be to hold what it was given.
94
+ *
95
+ * A declared height is a FLOOR, not a ceiling. When a specification asks for a
96
+ * 64-unit box and then puts two lines of label, a sublabel and a tag chip in
97
+ * it, honouring the 64 prints the tag over the sublabel — which reads as a
98
+ * rendering fault rather than as a box that was asked to hold too much. Growing
99
+ * the box is visible and correct; clipping is invisible and wrong.
100
+ */
101
+ declare function nodeHeight(labelLines: number, hasSub: boolean, hasTag?: boolean, base?: number): number;
102
+ /** `viewBox` as the attribute wants it. */
103
+ declare function viewBoxOf(box: Box): string;
104
+ /**
105
+ * Coordinates rounded to a tenth.
106
+ *
107
+ * SVG happily accepts eighteen decimal places and puts every one of them in the
108
+ * markup, which on a figure with two hundred line segments is a measurable
109
+ * share of the document. A tenth of a user unit is well under a device pixel at
110
+ * any zoom a reader will use.
111
+ */
112
+ declare function round(value: number): number;
113
+
114
+ export { BOX, type Box, TYPE, centerOf, inflate, nodeHeight, round, textWidth, union, viewBoxOf, wrapText };
@@ -0,0 +1,113 @@
1
+ const centerOf = (box) => [box.x + box.w / 2, box.y + box.h / 2];
2
+ function inflate(box, pad) {
3
+ return { x: box.x - pad, y: box.y - pad, w: box.w + pad * 2, h: box.h + pad * 2 };
4
+ }
5
+ function union(boxes) {
6
+ if (boxes.length === 0) return null;
7
+ let minX = Infinity;
8
+ let minY = Infinity;
9
+ let maxX = -Infinity;
10
+ let maxY = -Infinity;
11
+ for (const box of boxes) {
12
+ if (box.x < minX) minX = box.x;
13
+ if (box.y < minY) minY = box.y;
14
+ if (box.x + box.w > maxX) maxX = box.x + box.w;
15
+ if (box.y + box.h > maxY) maxY = box.y + box.h;
16
+ }
17
+ return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
18
+ }
19
+ function textWidth(text, fontSize) {
20
+ let em = 0;
21
+ for (const character of text) {
22
+ const code = character.codePointAt(0) ?? 0;
23
+ if (code > 11903) {
24
+ em += 1;
25
+ } else if (character === " ") {
26
+ em += 0.26;
27
+ } else if (/[.,:;'`!|iljtIf()[\]{}]/.test(character)) {
28
+ em += 0.31;
29
+ } else if (/[A-Z0-9@#%&WM]/.test(character)) {
30
+ em += 0.62;
31
+ } else {
32
+ em += 0.52;
33
+ }
34
+ }
35
+ return em * fontSize;
36
+ }
37
+ function wrapText(text, fontSize, maxWidth, maxLines = 2) {
38
+ if (textWidth(text, fontSize) <= maxWidth) return [text];
39
+ const lines = [];
40
+ let current = "";
41
+ const tokens = text.match(/[⺀-鿿豈-﫿＀-￯]|[^\s⺀-鿿]+|\s+/g) ?? [text];
42
+ for (const token of tokens) {
43
+ const candidate = current + token;
44
+ if (current !== "" && textWidth(candidate.trimEnd(), fontSize) > maxWidth) {
45
+ lines.push(current.trimEnd());
46
+ current = token.trimStart();
47
+ if (lines.length === maxLines) break;
48
+ } else {
49
+ current = candidate;
50
+ }
51
+ }
52
+ if (lines.length < maxLines && current.trim() !== "") lines.push(current.trimEnd());
53
+ if (lines.length === maxLines) {
54
+ const placed = lines.join("").replace(/\s+/g, "");
55
+ const whole = text.replace(/\s+/g, "");
56
+ if (placed.length < whole.length) {
57
+ const last = lines[maxLines - 1] ?? "";
58
+ lines[maxLines - 1] = `${clampToWidth(last, fontSize, maxWidth - textWidth("\u2026", fontSize))}\u2026`;
59
+ }
60
+ }
61
+ return lines;
62
+ }
63
+ function clampToWidth(text, fontSize, maxWidth) {
64
+ let width = 0;
65
+ let out = "";
66
+ for (const character of text) {
67
+ const next = textWidth(character, fontSize);
68
+ if (width + next > maxWidth) break;
69
+ width += next;
70
+ out += character;
71
+ }
72
+ return out.trimEnd();
73
+ }
74
+ const TYPE = {
75
+ /** A node's name. */
76
+ label: 13,
77
+ /** The line under it. */
78
+ sub: 10.5,
79
+ /** A mono chip: a tag, a classification, a step number. */
80
+ chip: 9.5,
81
+ /** A relationship's own wording. */
82
+ edge: 10.5,
83
+ /** A band, lane or stage heading. */
84
+ band: 10.5
85
+ };
86
+ const BOX = {
87
+ node: { w: 168, h: 62 },
88
+ wide: { w: 200, h: 62 },
89
+ participant: { w: 128, h: 46 }
90
+ };
91
+ function nodeHeight(labelLines, hasSub, hasTag = false, base = BOX.node.h) {
92
+ const content = 20 + labelLines * (TYPE.label * 1.25) + (hasSub ? TYPE.sub * 1.5 : 0) + (hasTag ? 20 : 0);
93
+ return Math.max(base, Math.ceil(content));
94
+ }
95
+ function viewBoxOf(box) {
96
+ return `${round(box.x)} ${round(box.y)} ${round(box.w)} ${round(box.h)}`;
97
+ }
98
+ function round(value) {
99
+ return Math.round(value * 10) / 10;
100
+ }
101
+ export {
102
+ BOX,
103
+ TYPE,
104
+ centerOf,
105
+ inflate,
106
+ nodeHeight,
107
+ round,
108
+ textWidth,
109
+ union,
110
+ viewBoxOf,
111
+ wrapText
112
+ };
113
+ //# sourceMappingURL=geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/diagrams/lib/geometry.ts"],"sourcesContent":["/**\n * The arithmetic every figure shares: boxes, text extents, and the frame that\n * ends up around them.\n *\n * Everything here is a pure function of numbers already in the specification.\n * That is not a stylistic preference — it is what lets these figures render on\n * a server. A layout that measured real text would need a DOM, a layout that\n * relaxed a force graph would need to settle, and neither can produce the same\n * markup twice. Both would hydrate into a different picture than the one that\n * was sent, which for a diagram is worse than not rendering at all.\n */\n\n/** A rectangle in the figure's user-unit space. */\nexport interface Box {\n x: number\n y: number\n w: number\n h: number\n}\n\nexport const centerOf = (box: Box): [number, number] => [box.x + box.w / 2, box.y + box.h / 2]\n\n/** Grows a rectangle by `pad` on every side. */\nexport function inflate(box: Box, pad: number): Box {\n return { x: box.x - pad, y: box.y - pad, w: box.w + pad * 2, h: box.h + pad * 2 }\n}\n\n/** The smallest rectangle containing all of them. Returns null for an empty list. */\nexport function union(boxes: Box[]): Box | null {\n if (boxes.length === 0) return null\n let minX = Infinity\n let minY = Infinity\n let maxX = -Infinity\n let maxY = -Infinity\n for (const box of boxes) {\n if (box.x < minX) minX = box.x\n if (box.y < minY) minY = box.y\n if (box.x + box.w > maxX) maxX = box.x + box.w\n if (box.y + box.h > maxY) maxY = box.y + box.h\n }\n return { x: minX, y: minY, w: maxX - minX, h: maxY - minY }\n}\n\n/**\n * How wide a run of text is, without measuring it.\n *\n * A real measurement needs a DOM and the loaded face, and this module has\n * neither by design — so this is an estimate, and the honest thing is to say\n * what kind. Latin glyphs in the system's sans average close to 0.52em of\n * advance across ordinary label text; digits and capitals run wider; a CJK\n * ideograph is a full em by definition, as is a fullwidth punctuation mark.\n *\n * The estimate is used for two things and neither needs to be exact: choosing a\n * box wide enough that a label is not clipped, and deciding where a line's\n * label mask sits. Both fail gracefully — a box a few units too wide is\n * invisible, and a mask a few units too wide hides a little more of a line than\n * it had to.\n *\n * It is deliberately NOT used to justify, wrap mid-word, or letter-space\n * anything. Those need the real face.\n */\nexport function textWidth(text: string, fontSize: number): number {\n let em = 0\n for (const character of text) {\n const code = character.codePointAt(0) ?? 0\n if (code > 0x2e7f) {\n // CJK, kana, hangul, fullwidth forms — one em each.\n em += 1\n } else if (character === ' ') {\n em += 0.26\n } else if (/[.,:;'`!|iljtIf()[\\]{}]/.test(character)) {\n em += 0.31\n } else if (/[A-Z0-9@#%&WM]/.test(character)) {\n em += 0.62\n } else {\n em += 0.52\n }\n }\n return em * fontSize\n}\n\n/**\n * A label broken to fit a width, with a hard cap on how many lines it may take.\n *\n * Breaks on spaces for Latin and between characters for CJK, which has no\n * spaces to break on. A word longer than the whole line is left to overflow\n * rather than hyphenated: an identifier — `TenantMainMiddleware` — split across\n * two lines is harder to read than one that runs slightly wide, and the caller\n * sized the box from `textWidth` anyway.\n *\n * The last line is ellipsised when the text does not fit in `maxLines`, because\n * silently dropping the tail is how a diagram comes to say something it does\n * not mean.\n */\nexport function wrapText(\n text: string,\n fontSize: number,\n maxWidth: number,\n maxLines = 2,\n): string[] {\n if (textWidth(text, fontSize) <= maxWidth) return [text]\n\n const lines: string[] = []\n let current = ''\n\n // Latin words stay whole; a CJK run is a sequence of individual break\n // opportunities, so each ideograph becomes its own token.\n const tokens = text.match(/[⺀-鿿豈-﫿＀-￯]|[^\\s⺀-鿿]+|\\s+/g) ?? [text]\n\n for (const token of tokens) {\n const candidate = current + token\n if (current !== '' && textWidth(candidate.trimEnd(), fontSize) > maxWidth) {\n lines.push(current.trimEnd())\n current = token.trimStart()\n if (lines.length === maxLines) break\n } else {\n current = candidate\n }\n }\n\n if (lines.length < maxLines && current.trim() !== '') lines.push(current.trimEnd())\n\n if (lines.length === maxLines) {\n // Anything still unplaced belongs to the last line, which must therefore\n // admit that it is truncated.\n const placed = lines.join('').replace(/\\s+/g, '')\n const whole = text.replace(/\\s+/g, '')\n if (placed.length < whole.length) {\n const last = lines[maxLines - 1] ?? ''\n lines[maxLines - 1] = `${clampToWidth(last, fontSize, maxWidth - textWidth('…', fontSize))}…`\n }\n }\n\n return lines\n}\n\n/** The longest prefix of `text` that fits, character by character. */\nfunction clampToWidth(text: string, fontSize: number, maxWidth: number): string {\n let width = 0\n let out = ''\n for (const character of text) {\n const next = textWidth(character, fontSize)\n if (width + next > maxWidth) break\n width += next\n out += character\n }\n return out.trimEnd()\n}\n\n/**\n * The type scale inside a figure, in user units.\n *\n * Fixed numbers rather than tokens, because these are SVG geometry: a `<text>`\n * sized in `rem` inside a scaled `viewBox` changes size when the figure is\n * zoomed, which means a diagram at 200% is a different diagram. The figure\n * scales as one picture, so its type scales with it.\n */\nexport const TYPE = {\n /** A node's name. */\n label: 13,\n /** The line under it. */\n sub: 10.5,\n /** A mono chip: a tag, a classification, a step number. */\n chip: 9.5,\n /** A relationship's own wording. */\n edge: 10.5,\n /** A band, lane or stage heading. */\n band: 10.5,\n} as const\n\n/** Default box sizes per diagram type, in user units. */\nexport const BOX = {\n node: { w: 168, h: 62 },\n wide: { w: 200, h: 62 },\n participant: { w: 128, h: 46 },\n} as const\n\n/**\n * How tall a box has to be to hold what it was given.\n *\n * A declared height is a FLOOR, not a ceiling. When a specification asks for a\n * 64-unit box and then puts two lines of label, a sublabel and a tag chip in\n * it, honouring the 64 prints the tag over the sublabel — which reads as a\n * rendering fault rather than as a box that was asked to hold too much. Growing\n * the box is visible and correct; clipping is invisible and wrong.\n */\nexport function nodeHeight(\n labelLines: number,\n hasSub: boolean,\n hasTag = false,\n base: number = BOX.node.h,\n): number {\n const content =\n 20 + labelLines * (TYPE.label * 1.25) + (hasSub ? TYPE.sub * 1.5 : 0) + (hasTag ? 20 : 0)\n return Math.max(base, Math.ceil(content))\n}\n\n/** `viewBox` as the attribute wants it. */\nexport function viewBoxOf(box: Box): string {\n return `${round(box.x)} ${round(box.y)} ${round(box.w)} ${round(box.h)}`\n}\n\n/**\n * Coordinates rounded to a tenth.\n *\n * SVG happily accepts eighteen decimal places and puts every one of them in the\n * markup, which on a figure with two hundred line segments is a measurable\n * share of the document. A tenth of a user unit is well under a device pixel at\n * any zoom a reader will use.\n */\nexport function round(value: number): number {\n return Math.round(value * 10) / 10\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,32 @@
1
+ import { NodeKind, LegendMode, LifecycleStateKind, Variant } from '../spec.js';
2
+
3
+ type LegendEntry = {
4
+ key: string;
5
+ label: string;
6
+ sample: React.ReactNode;
7
+ };
8
+ /**
9
+ * Which entries the key shows.
10
+ *
11
+ * `auto` — the default and the truthful one — lists only the kinds the figure
12
+ * actually drew. A key that names seven kinds beside a figure using three is
13
+ * not more informative; it is four claims the picture does not support.
14
+ */
15
+ declare function resolveLegend<T extends string>(used: T[], mode?: LegendMode, all?: T[], overrides?: Record<string, {
16
+ label?: string;
17
+ visible?: boolean;
18
+ }>): T[];
19
+ /** A key entry for each node kind, drawn with the same sigil the figure uses. */
20
+ declare function kindLegend(kinds: NodeKind[], overrides?: Record<string, {
21
+ label?: string;
22
+ }>): LegendEntry[];
23
+ /** A key entry for each relationship variant, drawn as the line itself. */
24
+ declare function variantLegend(variants: (Variant | 'return')[], overrides?: Record<string, {
25
+ label?: string;
26
+ }>): LegendEntry[];
27
+ /** A key entry for each lifecycle state kind, drawn as its plate. */
28
+ declare function stateLegend(kinds: LifecycleStateKind[], overrides?: Record<string, {
29
+ label?: string;
30
+ }>): LegendEntry[];
31
+
32
+ export { type LegendEntry, kindLegend, resolveLegend, stateLegend, variantLegend };
@@ -0,0 +1,84 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { KIND_WORD, Sigil } from "./marks.js";
3
+ const VARIANT_LABEL = {
4
+ default: "Call",
5
+ emphasis: "Primary path",
6
+ security: "Crosses a trust boundary",
7
+ dashed: "Asynchronous",
8
+ return: "Return"
9
+ };
10
+ const STATE_LABEL = {
11
+ start: "Entry",
12
+ active: "Working",
13
+ waiting: "Waiting",
14
+ decision: "Decision",
15
+ success: "Succeeded",
16
+ failure: "Failed",
17
+ neutral: "Idle",
18
+ external: "External"
19
+ };
20
+ function resolveLegend(used, mode = "auto", all = [], overrides) {
21
+ if (mode === "hidden") return [];
22
+ const base = mode === "all" ? all : [...new Set(used)];
23
+ return base.filter((key) => overrides?.[key]?.visible !== false);
24
+ }
25
+ function kindLegend(kinds, overrides) {
26
+ return kinds.map((kind) => ({
27
+ key: kind,
28
+ // The key names a kind with the SAME word the plate's eyebrow prints. They
29
+ // were two vocabularies for one fact — a plate reading SERVICE beside a key
30
+ // reading "Datastore" — which is the one thing a key must never do.
31
+ label: overrides?.[kind]?.label ?? KIND_WORD[kind],
32
+ sample: /* @__PURE__ */ jsx("g", { transform: "translate(1, 1)", children: /* @__PURE__ */ jsx(Sigil, { kind, x: 0, y: 0 }) })
33
+ }));
34
+ }
35
+ function variantLegend(variants, overrides) {
36
+ return variants.map((variant) => ({
37
+ key: variant,
38
+ label: overrides?.[variant]?.label ?? VARIANT_LABEL[variant],
39
+ sample: /* @__PURE__ */ jsx(
40
+ "path",
41
+ {
42
+ d: "M 0.5 7 H 13.5",
43
+ fill: "none",
44
+ className: variant === "emphasis" ? "stroke-(--diagram-line-strong) [stroke-width:2.4]" : variant === "security" ? "stroke-(--diagram-line) [stroke-width:1.6] [stroke-dasharray:4_2]" : variant === "dashed" || variant === "return" ? "stroke-(--diagram-line-soft) [stroke-width:1.6] [stroke-dasharray:3_2.5]" : "stroke-(--diagram-line) [stroke-width:1.6]"
45
+ }
46
+ )
47
+ }));
48
+ }
49
+ function stateLegend(kinds, overrides) {
50
+ return kinds.map((kind) => ({
51
+ key: kind,
52
+ label: overrides?.[kind]?.label ?? STATE_LABEL[kind],
53
+ sample: kind === "decision" ? /* @__PURE__ */ jsx(
54
+ "path",
55
+ {
56
+ d: "M 7 1 L 13 7 L 7 13 L 1 7 Z",
57
+ className: "fill-(--diagram-node) stroke-(--diagram-rule-hard) [stroke-width:1.2]"
58
+ }
59
+ ) : kind === "external" ? /* @__PURE__ */ jsx(
60
+ "path",
61
+ {
62
+ d: "M 1 2 H 9.5 L 13 5.5 V 12 H 1 Z",
63
+ className: "fill-(--diagram-node-2) stroke-(--diagram-rule) [stroke-width:1.2]"
64
+ }
65
+ ) : /* @__PURE__ */ jsx(
66
+ "rect",
67
+ {
68
+ x: "1",
69
+ y: "3",
70
+ width: "12",
71
+ height: "8",
72
+ rx: kind === "start" ? 4 : 2,
73
+ className: kind === "start" ? "fill-(--diagram-plate) stroke-(--diagram-plate) [stroke-width:1.2]" : kind === "success" ? "fill-(--success-wash) stroke-(--success) [stroke-width:1.2]" : kind === "failure" ? "fill-(--danger-wash) stroke-(--danger) [stroke-width:1.2]" : kind === "waiting" ? "fill-(--diagram-node) stroke-(--diagram-rule) [stroke-width:1.2] [stroke-dasharray:3_2]" : kind === "neutral" ? "fill-(--diagram-node-2) stroke-(--diagram-rule) [stroke-width:1.2]" : "fill-(--diagram-node) stroke-(--diagram-rule-hard) [stroke-width:1.2]"
74
+ }
75
+ )
76
+ }));
77
+ }
78
+ export {
79
+ kindLegend,
80
+ resolveLegend,
81
+ stateLegend,
82
+ variantLegend
83
+ };
84
+ //# sourceMappingURL=legend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/diagrams/lib/legend.tsx"],"sourcesContent":["import type { LegendMode, LifecycleStateKind, NodeKind, Variant } from '../spec'\nimport { KIND_WORD, Sigil } from './marks'\n\nconst VARIANT_LABEL: Record<Variant | 'return', string> = {\n default: 'Call',\n emphasis: 'Primary path',\n security: 'Crosses a trust boundary',\n dashed: 'Asynchronous',\n return: 'Return',\n}\n\nconst STATE_LABEL: Record<LifecycleStateKind, string> = {\n start: 'Entry',\n active: 'Working',\n waiting: 'Waiting',\n decision: 'Decision',\n success: 'Succeeded',\n failure: 'Failed',\n neutral: 'Idle',\n external: 'External',\n}\n\nexport type LegendEntry = { key: string; label: string; sample: React.ReactNode }\n\n/**\n * Which entries the key shows.\n *\n * `auto` — the default and the truthful one — lists only the kinds the figure\n * actually drew. A key that names seven kinds beside a figure using three is\n * not more informative; it is four claims the picture does not support.\n */\nexport function resolveLegend<T extends string>(\n used: T[],\n mode: LegendMode = 'auto',\n all: T[] = [],\n overrides?: Record<string, { label?: string; visible?: boolean }>,\n): T[] {\n if (mode === 'hidden') return []\n const base = mode === 'all' ? all : [...new Set(used)]\n return base.filter((key) => overrides?.[key]?.visible !== false)\n}\n\n/** A key entry for each node kind, drawn with the same sigil the figure uses. */\nexport function kindLegend(\n kinds: NodeKind[],\n overrides?: Record<string, { label?: string }>,\n): LegendEntry[] {\n return kinds.map((kind) => ({\n key: kind,\n // The key names a kind with the SAME word the plate's eyebrow prints. They\n // were two vocabularies for one fact — a plate reading SERVICE beside a key\n // reading \"Datastore\" — which is the one thing a key must never do.\n label: overrides?.[kind]?.label ?? KIND_WORD[kind],\n sample: (\n <g transform=\"translate(1, 1)\">\n <Sigil kind={kind} x={0} y={0} />\n </g>\n ),\n }))\n}\n\n/** A key entry for each relationship variant, drawn as the line itself. */\nexport function variantLegend(\n variants: (Variant | 'return')[],\n overrides?: Record<string, { label?: string }>,\n): LegendEntry[] {\n return variants.map((variant) => ({\n key: variant,\n label: overrides?.[variant]?.label ?? VARIANT_LABEL[variant],\n sample: (\n <path\n d=\"M 0.5 7 H 13.5\"\n fill=\"none\"\n className={\n variant === 'emphasis'\n ? 'stroke-(--diagram-line-strong) [stroke-width:2.4]'\n : variant === 'security'\n ? 'stroke-(--diagram-line) [stroke-width:1.6] [stroke-dasharray:4_2]'\n : variant === 'dashed' || variant === 'return'\n ? 'stroke-(--diagram-line-soft) [stroke-width:1.6] [stroke-dasharray:3_2.5]'\n : 'stroke-(--diagram-line) [stroke-width:1.6]'\n }\n />\n ),\n }))\n}\n\n/** A key entry for each lifecycle state kind, drawn as its plate. */\nexport function stateLegend(\n kinds: LifecycleStateKind[],\n overrides?: Record<string, { label?: string }>,\n): LegendEntry[] {\n return kinds.map((kind) => ({\n key: kind,\n label: overrides?.[kind]?.label ?? STATE_LABEL[kind],\n sample:\n kind === 'decision' ? (\n <path\n d=\"M 7 1 L 13 7 L 7 13 L 1 7 Z\"\n className=\"fill-(--diagram-node) stroke-(--diagram-rule-hard) [stroke-width:1.2]\"\n />\n ) : kind === 'external' ? (\n <path\n d=\"M 1 2 H 9.5 L 13 5.5 V 12 H 1 Z\"\n className=\"fill-(--diagram-node-2) stroke-(--diagram-rule) [stroke-width:1.2]\"\n />\n ) : (\n <rect\n x=\"1\"\n y=\"3\"\n width=\"12\"\n height=\"8\"\n rx={kind === 'start' ? 4 : 2}\n className={\n kind === 'start'\n ? 'fill-(--diagram-plate) stroke-(--diagram-plate) [stroke-width:1.2]'\n : kind === 'success'\n ? 'fill-(--success-wash) stroke-(--success) [stroke-width:1.2]'\n : kind === 'failure'\n ? 'fill-(--danger-wash) stroke-(--danger) [stroke-width:1.2]'\n : kind === 'waiting'\n ? 'fill-(--diagram-node) stroke-(--diagram-rule) [stroke-width:1.2] [stroke-dasharray:3_2]'\n : kind === 'neutral'\n ? 'fill-(--diagram-node-2) stroke-(--diagram-rule) [stroke-width:1.2]'\n : 'fill-(--diagram-node) stroke-(--diagram-rule-hard) [stroke-width:1.2]'\n }\n />\n ),\n }))\n}\n"],"mappings":"AAuDQ;AAtDR,SAAS,WAAW,aAAa;AAEjC,MAAM,gBAAoD;AAAA,EACxD,SAAS;AAAA,EACT,UAAU;AAAA,EACV,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,MAAM,cAAkD;AAAA,EACtD,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,UAAU;AAAA,EACV,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,UAAU;AACZ;AAWO,SAAS,cACd,MACA,OAAmB,QACnB,MAAW,CAAC,GACZ,WACK;AACL,MAAI,SAAS,SAAU,QAAO,CAAC;AAC/B,QAAM,OAAO,SAAS,QAAQ,MAAM,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC;AACrD,SAAO,KAAK,OAAO,CAAC,QAAQ,YAAY,GAAG,GAAG,YAAY,KAAK;AACjE;AAGO,SAAS,WACd,OACA,WACe;AACf,SAAO,MAAM,IAAI,CAAC,UAAU;AAAA,IAC1B,KAAK;AAAA;AAAA;AAAA;AAAA,IAIL,OAAO,YAAY,IAAI,GAAG,SAAS,UAAU,IAAI;AAAA,IACjD,QACE,oBAAC,OAAE,WAAU,mBACX,8BAAC,SAAM,MAAY,GAAG,GAAG,GAAG,GAAG,GACjC;AAAA,EAEJ,EAAE;AACJ;AAGO,SAAS,cACd,UACA,WACe;AACf,SAAO,SAAS,IAAI,CAAC,aAAa;AAAA,IAChC,KAAK;AAAA,IACL,OAAO,YAAY,OAAO,GAAG,SAAS,cAAc,OAAO;AAAA,IAC3D,QACE;AAAA,MAAC;AAAA;AAAA,QACC,GAAE;AAAA,QACF,MAAK;AAAA,QACL,WACE,YAAY,aACR,sDACA,YAAY,aACV,sEACA,YAAY,YAAY,YAAY,WAClC,6EACA;AAAA;AAAA,IAEZ;AAAA,EAEJ,EAAE;AACJ;AAGO,SAAS,YACd,OACA,WACe;AACf,SAAO,MAAM,IAAI,CAAC,UAAU;AAAA,IAC1B,KAAK;AAAA,IACL,OAAO,YAAY,IAAI,GAAG,SAAS,YAAY,IAAI;AAAA,IACnD,QACE,SAAS,aACP;AAAA,MAAC;AAAA;AAAA,QACC,GAAE;AAAA,QACF,WAAU;AAAA;AAAA,IACZ,IACE,SAAS,aACX;AAAA,MAAC;AAAA;AAAA,QACC,GAAE;AAAA,QACF,WAAU;AAAA;AAAA,IACZ,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,GAAE;AAAA,QACF,GAAE;AAAA,QACF,OAAM;AAAA,QACN,QAAO;AAAA,QACP,IAAI,SAAS,UAAU,IAAI;AAAA,QAC3B,WACE,SAAS,UACL,uEACA,SAAS,YACP,gEACA,SAAS,YACP,8DACA,SAAS,YACP,4FACA,SAAS,YACP,uEACA;AAAA;AAAA,IAEhB;AAAA,EAEN,EAAE;AACJ;","names":[]}
@@ -0,0 +1,207 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { NodeKind, Variant, LifecycleStateKind } from '../spec.js';
4
+ import { Box } from './geometry.js';
5
+
6
+ /** What each kind is called on a plate's eyebrow. Short, because it is a kicker. */
7
+ declare const KIND_WORD: Record<NodeKind, string>;
8
+ /**
9
+ * The marker definitions every figure needs.
10
+ *
11
+ * Rendered once per figure. The ids are namespaced by the figure's own id
12
+ * because two diagrams on one page would otherwise share one `<defs>`, and the
13
+ * second figure's arrowheads would silently take the first figure's paint —
14
+ * SVG ids are document-global, and a duplicate is resolved to whichever came
15
+ * first rather than to the nearest one.
16
+ */
17
+ declare function DiagramDefs({ uid }: {
18
+ uid: string;
19
+ }): react.JSX.Element;
20
+ /**
21
+ * The 14×14 mark that says what kind of thing a node is.
22
+ *
23
+ * Drawn on a 14-unit grid at a 1.35 stroke. Both numbers went up from the first
24
+ * pass and both had to: at 12 units and a 1.2 stroke the seven forms were a few
25
+ * grey pixels apart at reading size, which is not a distinction — it is a
26
+ * smudge. Every path is closed geometry rather than an icon font, because a
27
+ * figure has to serialise into a standalone file and a font the exported
28
+ * document cannot fetch renders as a box.
29
+ */
30
+ declare function Sigil({ kind, x, y, reversed, }: {
31
+ kind: NodeKind;
32
+ x: number;
33
+ y: number;
34
+ reversed?: boolean;
35
+ }): react.JSX.Element;
36
+ /**
37
+ * A plate's internal vertical rhythm, in user units from its top edge.
38
+ *
39
+ * Written out as one object rather than computed inline in three renderers,
40
+ * because these numbers ARE the plate: change one and the card stops looking
41
+ * like a card. The eyebrow sits high and tight, the name is the only thing set
42
+ * at reading size, and the sublabel hangs under it in the meta voice.
43
+ */
44
+ declare const PLATE: {
45
+ readonly padX: 15;
46
+ readonly eyebrow: 19;
47
+ readonly label: 39;
48
+ readonly sub: 53;
49
+ readonly lineStep: 17;
50
+ /** Where the eyebrow lands when there is none — the label moves up into it. */
51
+ readonly riseWithoutEyebrow: 16;
52
+ };
53
+ /**
54
+ * How tall a plate has to be to hold what it was given.
55
+ *
56
+ * A declared height is a FLOOR, not a ceiling. When a specification asks for a
57
+ * 60-unit box and then puts two lines of name and a qualifier in it, honouring
58
+ * the 60 prints the qualifier through the bottom rule — which reads as a
59
+ * rendering fault rather than as a box asked to hold too much. Growing is
60
+ * visible and correct; clipping is invisible and wrong.
61
+ */
62
+ declare function plateHeight(labelLines: number, hasSub: boolean, hasEyebrow: boolean, floor?: number): number;
63
+ interface NodePlateProps {
64
+ box: Box;
65
+ label: string;
66
+ sublabel?: string;
67
+ tag?: string;
68
+ kind?: NodeKind;
69
+ variant?: Variant;
70
+ /** Printed in the trailing corner in mono — a step number. */
71
+ step?: string;
72
+ /** Paints the plate on the recessed ground rather than on paper. */
73
+ muted?: boolean;
74
+ /** Lit by a guided chapter or a reader's focus. */
75
+ active?: boolean;
76
+ /** Dimmed because something else is lit. */
77
+ dimmed?: boolean;
78
+ onSelect?: () => void;
79
+ /** A stable hook the viewer chrome and the tests both address nodes by. */
80
+ nodeId?: string;
81
+ }
82
+ /**
83
+ * One box, with everything that can be printed inside it.
84
+ *
85
+ * Three registers, top to bottom, and each is a different voice on purpose: a
86
+ * mono kicker naming the KIND, the name at reading size in the interface face,
87
+ * and the qualifier back in mono. That is the same ladder a record on a page
88
+ * uses — the diagram is not inventing a second typography for itself.
89
+ *
90
+ * WHAT IT IS NOT is a button, and that is deliberate. The `<svg>` around it
91
+ * carries `role="img"`, which makes everything inside it presentational to
92
+ * assistive technology — so a `role="button"` here would announce a control a
93
+ * screen reader can never reach. The keyboard route to the same selection is
94
+ * the figure's own summary list, which is a real `<ul>` of real `<button>`s
95
+ * sitting beside the picture. `onSelect` here is the POINTER path to that same
96
+ * action, and nothing else.
97
+ */
98
+ declare function NodePlate({ box, label, sublabel, tag, kind, variant, step, muted, active, dimmed, onSelect, nodeId, }: NodePlateProps): react.JSX.Element;
99
+ /**
100
+ * A mono chip: a version, a region, a protocol, a data classification.
101
+ *
102
+ * A rule and a word, with no fill under it. The filled chip this replaced put a
103
+ * third ground inside a plate that already sat on a figure that sat on the
104
+ * page, and three grounds deep is where a diagram stops reading as structure
105
+ * and starts reading as packaging.
106
+ */
107
+ declare function Chip({ x, y, text, align, reversed, }: {
108
+ x: number;
109
+ y: number;
110
+ text: string;
111
+ align?: 'start' | 'middle' | 'end';
112
+ reversed?: boolean;
113
+ }): react.JSX.Element;
114
+ /**
115
+ * How a line is painted: the colour as a class, the geometry as attributes.
116
+ *
117
+ * THE SPLIT IS NOT ARBITRARY, and getting it wrong cost an afternoon. Tailwind
118
+ * generates a rule only for a class name it can find as a LITERAL in the
119
+ * source. A width built at runtime — `` `[stroke-width:${weight}]` `` — is a
120
+ * class that never gets a rule, so the browser falls back to SVG's default
121
+ * 1px, silently, on exactly the lines a figure most needs to be heavier. The
122
+ * main path came out the same weight as a side branch and nothing said so.
123
+ *
124
+ * Colour still goes through a class, because a paint has to resolve a custom
125
+ * property and `stroke="var(--x)"` is not valid as an attribute. Width and dash
126
+ * pattern are plain numbers, so they go through presentation attributes where
127
+ * no build step has to have predicted them — and `getComputedStyle` reports an
128
+ * attribute exactly as it reports a rule, so the export still picks them up.
129
+ */
130
+ declare function edgeClasses(variant?: Variant | 'return'): string;
131
+ /** A line's width and dash pattern, as presentation attributes. */
132
+ declare function edgeStroke(variant?: Variant | 'return', weight?: number): {
133
+ strokeWidth: number;
134
+ strokeDasharray?: string;
135
+ };
136
+ /** Which arrowhead a line ends in. */
137
+ declare function edgeMarker(variant?: Variant | 'return'): 'arrow' | 'arrow-strong' | 'arrow-open';
138
+ interface EdgeLabelProps {
139
+ x: number;
140
+ y: number;
141
+ axis: 'x' | 'y';
142
+ text: string;
143
+ /** A mono chip printed under the wording — a data classification. */
144
+ chip?: string;
145
+ /** A second, quieter line — a condition, a timeout, a retry count. */
146
+ note?: string;
147
+ }
148
+ /**
149
+ * A relationship's wording, on a mask that punches the line out from under it.
150
+ *
151
+ * The mask is not decoration. A label drawn straight onto a line is unreadable
152
+ * at every size, and the two repairs a renderer can make are moving the label
153
+ * or hiding the line behind it. Moving it changes which segment the wording
154
+ * belongs to, which is a semantic change; masking is the one that leaves the
155
+ * meaning alone.
156
+ *
157
+ * Set in mono rather than in the interface face. The wording on a line is an
158
+ * ANNOTATION on the picture, not a second rank of content — and at the sans
159
+ * face it competed with the names on the plates, which is the one comparison it
160
+ * must lose.
161
+ */
162
+ declare function EdgeLabel({ x, y, axis, text, chip, note }: EdgeLabelProps): react.JSX.Element;
163
+ /**
164
+ * A labelled frame around a set of boxes: a region, a security group, a phase,
165
+ * a process group.
166
+ *
167
+ * A FRAME IS A RULE AND NOT A GROUND. Archify washes its regions with a tint,
168
+ * which this system cannot copy for two reasons: a second ground inside a paper
169
+ * figure reads as packaging rather than as structure — the same argument the
170
+ * containment `Diagram` primitive makes about nested boxes — and every
171
+ * relationship label crossing the wash would then need to know which of two
172
+ * grounds its mask is punching through, which is a question a label cannot
173
+ * answer. `muted` therefore washes only where a band IS the ground it labels: a
174
+ * workflow's exception lane, which nothing routes across.
175
+ *
176
+ * The label sits ABOVE the top rule rather than inside the frame or on it.
177
+ * Inside, it costs a line of vertical space in every frame whether or not the
178
+ * frame had one to spare. ON the rule — which is where it started — it has to
179
+ * punch a mask through the frame, and that mask then competes for the same few
180
+ * units of clear ground as any relationship label routed along the frame's own
181
+ * edge: two captions, two white plates, one on top of the other. Above the
182
+ * rule it needs no mask, leaves the frame unbroken, and reads as a caption for
183
+ * the frame rather than as its first child.
184
+ */
185
+ declare function BandFrame({ box, label, dashed, muted, }: {
186
+ box: Box;
187
+ label?: string;
188
+ dashed?: boolean;
189
+ muted?: boolean;
190
+ }): react.JSX.Element;
191
+ /**
192
+ * The plate shape for a lifecycle state.
193
+ *
194
+ * This is the one place the system spends colour, and it spends exactly the two
195
+ * tokens it reserves for state: `--success` on a terminal success, `--danger`
196
+ * on a terminal failure. Every other kind is carried by shape — a filled cap
197
+ * for a start, a diamond for a decision, a dashed frame for a wait, a cut
198
+ * corner for something outside the system — so a greyscale print loses the two
199
+ * outcomes' hue and keeps all six other distinctions.
200
+ */
201
+ declare function StatePlate({ box, kind, children, }: {
202
+ box: Box;
203
+ kind: LifecycleStateKind;
204
+ children?: ReactNode;
205
+ }): react.JSX.Element;
206
+
207
+ export { BandFrame, Chip, DiagramDefs, EdgeLabel, type EdgeLabelProps, KIND_WORD, NodePlate, type NodePlateProps, PLATE, Sigil, StatePlate, edgeClasses, edgeMarker, edgeStroke, plateHeight };