@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,480 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/cn.js";
3
+ import { round, TYPE, textWidth, wrapText } from "./geometry.js";
4
+ const PLATE_RADIUS = "[rx:var(--radius-sm)] [ry:var(--radius-sm)]";
5
+ const KIND_WORD = {
6
+ frontend: "CLIENT",
7
+ backend: "SERVICE",
8
+ database: "DATA",
9
+ cloud: "CLOUD",
10
+ security: "POLICY",
11
+ messagebus: "QUEUE",
12
+ external: "EXTERNAL"
13
+ };
14
+ function DiagramDefs({ uid }) {
15
+ return /* @__PURE__ */ jsxs("defs", { children: [
16
+ /* @__PURE__ */ jsx(
17
+ "marker",
18
+ {
19
+ id: `${uid}-arrow`,
20
+ markerWidth: "11",
21
+ markerHeight: "11",
22
+ refX: "9.5",
23
+ refY: "5.5",
24
+ orient: "auto-start-reverse",
25
+ markerUnits: "userSpaceOnUse",
26
+ children: /* @__PURE__ */ jsx("path", { d: "M 0.6 1.4 L 10 5.5 L 0.6 9.6 Z", className: "fill-(--diagram-line)" })
27
+ }
28
+ ),
29
+ /* @__PURE__ */ jsx(
30
+ "marker",
31
+ {
32
+ id: `${uid}-arrow-strong`,
33
+ markerWidth: "12",
34
+ markerHeight: "12",
35
+ refX: "10.5",
36
+ refY: "6",
37
+ orient: "auto-start-reverse",
38
+ markerUnits: "userSpaceOnUse",
39
+ children: /* @__PURE__ */ jsx("path", { d: "M 0.6 1.6 L 11 6 L 0.6 10.4 Z", className: "fill-(--diagram-line-strong)" })
40
+ }
41
+ ),
42
+ /* @__PURE__ */ jsx(
43
+ "marker",
44
+ {
45
+ id: `${uid}-arrow-open`,
46
+ markerWidth: "11",
47
+ markerHeight: "11",
48
+ refX: "9.5",
49
+ refY: "5.5",
50
+ orient: "auto-start-reverse",
51
+ markerUnits: "userSpaceOnUse",
52
+ children: /* @__PURE__ */ jsx(
53
+ "path",
54
+ {
55
+ d: "M 1 1.6 L 9.6 5.5 L 1 9.4",
56
+ fill: "none",
57
+ strokeWidth: "1.4",
58
+ strokeLinecap: "round",
59
+ strokeLinejoin: "round",
60
+ className: "stroke-(--diagram-line-soft)"
61
+ }
62
+ )
63
+ }
64
+ )
65
+ ] });
66
+ }
67
+ function Sigil({
68
+ kind,
69
+ x,
70
+ y,
71
+ reversed
72
+ }) {
73
+ const stroke = cn(
74
+ "fill-none [stroke-width:1.35] [stroke-linejoin:round] [stroke-linecap:round]",
75
+ reversed ? "stroke-(--diagram-plate-ink)" : "stroke-(--diagram-sigil)"
76
+ );
77
+ return /* @__PURE__ */ jsxs("g", { transform: `translate(${round(x)}, ${round(y)})`, "aria-hidden": "true", children: [
78
+ kind === "frontend" && /* @__PURE__ */ jsxs("g", { className: stroke, children: [
79
+ /* @__PURE__ */ jsx("rect", { x: "0.8", y: "2", width: "12.4", height: "10", rx: "1.6" }),
80
+ /* @__PURE__ */ jsx("path", { d: "M 0.8 5.2 H 13.2" }),
81
+ /* @__PURE__ */ jsx("path", { d: "M 3 3.6 H 4" })
82
+ ] }),
83
+ kind === "backend" && /* @__PURE__ */ jsxs("g", { className: stroke, children: [
84
+ /* @__PURE__ */ jsx("rect", { x: "0.8", y: "1.8", width: "12.4", height: "4.4", rx: "1.2" }),
85
+ /* @__PURE__ */ jsx("rect", { x: "0.8", y: "7.8", width: "12.4", height: "4.4", rx: "1.2" }),
86
+ /* @__PURE__ */ jsx("path", { d: "M 3 4 H 4.2" }),
87
+ /* @__PURE__ */ jsx("path", { d: "M 3 10 H 4.2" })
88
+ ] }),
89
+ kind === "database" && /* @__PURE__ */ jsxs("g", { className: stroke, children: [
90
+ /* @__PURE__ */ jsx("ellipse", { cx: "7", cy: "3.4", rx: "6", ry: "2.4" }),
91
+ /* @__PURE__ */ jsx("path", { d: "M 1 3.4 V 10.6 A 6 2.4 0 0 0 13 10.6 V 3.4" }),
92
+ /* @__PURE__ */ jsx("path", { d: "M 1 7 A 6 2.4 0 0 0 13 7" })
93
+ ] }),
94
+ kind === "cloud" && /* @__PURE__ */ jsx("g", { className: stroke, children: /* @__PURE__ */ jsx("path", { d: "M 3.4 10.8 A 2.9 2.9 0 0 1 3.5 5 A 3.8 3.8 0 0 1 10.5 4.2 A 3.2 3.2 0 0 1 11 10.8 Z" }) }),
95
+ kind === "security" && /* @__PURE__ */ jsxs("g", { className: stroke, children: [
96
+ /* @__PURE__ */ jsx("path", { d: "M 7 1 L 12.5 3.1 V 7 C 12.5 10 10 12.1 7 13 C 4 12.1 1.5 10 1.5 7 V 3.1 Z" }),
97
+ /* @__PURE__ */ jsx("path", { d: "M 4.7 7 L 6.4 8.7 L 9.4 5.3" })
98
+ ] }),
99
+ kind === "messagebus" && /* @__PURE__ */ jsxs("g", { className: stroke, children: [
100
+ /* @__PURE__ */ jsx("path", { d: "M 0.9 2.8 H 13.1" }),
101
+ /* @__PURE__ */ jsx("path", { d: "M 0.9 11.2 H 13.1" }),
102
+ /* @__PURE__ */ jsx("path", { d: "M 3.6 5.2 V 8.8" }),
103
+ /* @__PURE__ */ jsx("path", { d: "M 7 5.2 V 8.8" }),
104
+ /* @__PURE__ */ jsx("path", { d: "M 10.4 5.2 V 8.8" })
105
+ ] }),
106
+ kind === "external" && /* @__PURE__ */ jsxs("g", { className: stroke, children: [
107
+ /* @__PURE__ */ jsx("path", { d: "M 7.4 1.8 H 1.6 V 12.2 H 12.4 V 6.4" }),
108
+ /* @__PURE__ */ jsx("path", { d: "M 8.4 5.4 L 13 1" }),
109
+ /* @__PURE__ */ jsx("path", { d: "M 9.6 0.9 H 13.1 V 4.4" })
110
+ ] })
111
+ ] });
112
+ }
113
+ function plateClasses(variant, muted) {
114
+ switch (variant) {
115
+ case "emphasis":
116
+ return "fill-(--diagram-plate) stroke-(--diagram-plate) [stroke-width:1]";
117
+ case "security":
118
+ return "fill-(--diagram-node) stroke-(--diagram-rule-hard) [stroke-width:1.3] [stroke-dasharray:5_3]";
119
+ case "dashed":
120
+ return "fill-(--diagram-node) stroke-(--diagram-rule) [stroke-width:1] [stroke-dasharray:4_3]";
121
+ default:
122
+ return muted ? "fill-(--diagram-node-2) stroke-(--diagram-rule) [stroke-width:1]" : "fill-(--diagram-node) stroke-(--diagram-rule) [stroke-width:1]";
123
+ }
124
+ }
125
+ const PLATE = {
126
+ padX: 15,
127
+ eyebrow: 19,
128
+ label: 39,
129
+ sub: 53,
130
+ lineStep: 17,
131
+ /** Where the eyebrow lands when there is none — the label moves up into it. */
132
+ riseWithoutEyebrow: 16
133
+ };
134
+ function plateHeight(labelLines, hasSub, hasEyebrow, floor = 0) {
135
+ const last = hasSub ? PLATE.sub : PLATE.label;
136
+ const content = last + (labelLines - 1) * PLATE.lineStep + 16 - (hasEyebrow ? 0 : PLATE.riseWithoutEyebrow);
137
+ return Math.max(floor, Math.ceil(content));
138
+ }
139
+ function NodePlate({
140
+ box,
141
+ label,
142
+ sublabel,
143
+ tag,
144
+ kind,
145
+ variant,
146
+ step,
147
+ muted,
148
+ active,
149
+ dimmed,
150
+ onSelect,
151
+ nodeId
152
+ }) {
153
+ const reversed = variant === "emphasis";
154
+ const lines = wrapText(label, TYPE.label, box.w - PLATE.padX * 2, 2);
155
+ const eyebrow = tag ?? (kind ? KIND_WORD[kind] : void 0);
156
+ const hasEyebrow = Boolean(eyebrow || step);
157
+ const rise = hasEyebrow ? 0 : PLATE.riseWithoutEyebrow;
158
+ const labelY = box.y + PLATE.label - rise;
159
+ const subY = box.y + PLATE.sub - rise + (lines.length - 1) * PLATE.lineStep;
160
+ return /* @__PURE__ */ jsxs(
161
+ "g",
162
+ {
163
+ "data-node": nodeId,
164
+ "data-active": active ? "" : void 0,
165
+ className: cn(
166
+ "transition-opacity duration-(--duration-fast)",
167
+ dimmed && "opacity-25",
168
+ onSelect && "cursor-pointer"
169
+ ),
170
+ onClick: onSelect,
171
+ children: [
172
+ /* @__PURE__ */ jsx(
173
+ "rect",
174
+ {
175
+ x: round(box.x),
176
+ y: round(box.y),
177
+ width: round(box.w),
178
+ height: round(box.h),
179
+ rx: 6,
180
+ ry: 6,
181
+ className: cn(PLATE_RADIUS, plateClasses(variant, Boolean(muted)))
182
+ }
183
+ ),
184
+ active && /* @__PURE__ */ jsx(
185
+ "rect",
186
+ {
187
+ x: round(box.x) - 3.5,
188
+ y: round(box.y) - 3.5,
189
+ width: round(box.w) + 7,
190
+ height: round(box.h) + 7,
191
+ rx: 9,
192
+ ry: 9,
193
+ fill: "none",
194
+ className: "stroke-(--accent) [stroke-width:1.5]"
195
+ }
196
+ ),
197
+ kind && /* @__PURE__ */ jsx(
198
+ Sigil,
199
+ {
200
+ kind,
201
+ x: box.x + PLATE.padX,
202
+ y: box.y + PLATE.eyebrow - 11,
203
+ reversed
204
+ }
205
+ ),
206
+ eyebrow && /* @__PURE__ */ jsx(
207
+ "text",
208
+ {
209
+ x: round(box.x + PLATE.padX + (kind ? 20 : 0)),
210
+ y: round(box.y + PLATE.eyebrow),
211
+ className: cn(
212
+ "font-mono [font-size:8.5px] [letter-spacing:0.13em] uppercase",
213
+ reversed ? "fill-(--diagram-plate-ink) opacity-70" : "fill-(--diagram-ink-3)"
214
+ ),
215
+ children: eyebrow
216
+ }
217
+ ),
218
+ step && /* @__PURE__ */ jsx(
219
+ "text",
220
+ {
221
+ x: round(box.x + box.w - PLATE.padX),
222
+ y: round(box.y + PLATE.eyebrow),
223
+ textAnchor: "end",
224
+ className: cn(
225
+ "font-mono [font-size:9px] [letter-spacing:0.1em]",
226
+ reversed ? "fill-(--diagram-plate-ink) opacity-65" : "fill-(--diagram-ink-3)"
227
+ ),
228
+ children: step
229
+ }
230
+ ),
231
+ lines.map((line, index) => /* @__PURE__ */ jsx(
232
+ "text",
233
+ {
234
+ x: round(box.x + PLATE.padX),
235
+ y: round(labelY + index * PLATE.lineStep),
236
+ className: cn(
237
+ "font-sans [font-size:14px] [letter-spacing:-0.005em]",
238
+ reversed ? "fill-(--diagram-plate-ink)" : "fill-(--diagram-ink)"
239
+ ),
240
+ children: line
241
+ },
242
+ index
243
+ )),
244
+ sublabel && /* @__PURE__ */ jsx(
245
+ "text",
246
+ {
247
+ x: round(box.x + PLATE.padX),
248
+ y: round(subY),
249
+ className: cn(
250
+ "font-mono [font-size:10px]",
251
+ reversed ? "fill-(--diagram-plate-ink) opacity-65" : "fill-(--diagram-ink-3)"
252
+ ),
253
+ children: sublabel
254
+ }
255
+ )
256
+ ]
257
+ }
258
+ );
259
+ }
260
+ function Chip({
261
+ x,
262
+ y,
263
+ text,
264
+ align = "start",
265
+ reversed
266
+ }) {
267
+ const width = textWidth(text, TYPE.chip) + 12;
268
+ const left = align === "end" ? x - width : align === "middle" ? x - width / 2 : x;
269
+ return /* @__PURE__ */ jsxs("g", { "aria-hidden": "true", children: [
270
+ /* @__PURE__ */ jsx(
271
+ "rect",
272
+ {
273
+ x: round(left),
274
+ y: round(y - 7.5),
275
+ width: round(width),
276
+ height: 15,
277
+ rx: 3,
278
+ ry: 3,
279
+ className: cn(
280
+ "[stroke-width:0.9] fill-(--diagram-surface)",
281
+ reversed ? "stroke-(--diagram-plate-ink) opacity-40" : "stroke-(--diagram-rule)"
282
+ )
283
+ }
284
+ ),
285
+ /* @__PURE__ */ jsx(
286
+ "text",
287
+ {
288
+ x: round(left + width / 2),
289
+ y: round(y + 2.8),
290
+ textAnchor: "middle",
291
+ className: cn(
292
+ "font-mono [font-size:9px] [letter-spacing:0.05em]",
293
+ reversed ? "fill-(--diagram-plate-ink) opacity-70" : "fill-(--diagram-ink-3)"
294
+ ),
295
+ children: text
296
+ }
297
+ )
298
+ ] });
299
+ }
300
+ function edgeClasses(variant) {
301
+ switch (variant) {
302
+ case "emphasis":
303
+ return "stroke-(--diagram-line-strong)";
304
+ case "dashed":
305
+ case "return":
306
+ return "stroke-(--diagram-line-soft)";
307
+ default:
308
+ return "stroke-(--diagram-line)";
309
+ }
310
+ }
311
+ function edgeStroke(variant, weight) {
312
+ switch (variant) {
313
+ case "emphasis":
314
+ return { strokeWidth: weight ?? 2.1 };
315
+ case "security":
316
+ return { strokeWidth: weight ?? 1.4, strokeDasharray: "7 4" };
317
+ case "dashed":
318
+ case "return":
319
+ return { strokeWidth: weight ?? 1.3, strokeDasharray: "4.5 4" };
320
+ default:
321
+ return { strokeWidth: weight ?? 1.4 };
322
+ }
323
+ }
324
+ function edgeMarker(variant) {
325
+ if (variant === "emphasis") return "arrow-strong";
326
+ if (variant === "return") return "arrow-open";
327
+ return "arrow";
328
+ }
329
+ function EdgeLabel({ x, y, axis, text, chip, note }) {
330
+ const width = textWidth(text, TYPE.edge) + 10;
331
+ const noteWidth = note ? textWidth(note, TYPE.chip) + 10 : 0;
332
+ const cy = y + (axis === "x" ? -10 : 0);
333
+ return /* @__PURE__ */ jsxs("g", { "aria-hidden": "true", children: [
334
+ /* @__PURE__ */ jsx(
335
+ "rect",
336
+ {
337
+ x: round(x - width / 2),
338
+ y: round(cy - 7),
339
+ width: round(width),
340
+ height: 14,
341
+ className: "fill-(--diagram-surface)"
342
+ }
343
+ ),
344
+ /* @__PURE__ */ jsx(
345
+ "text",
346
+ {
347
+ x: round(x),
348
+ y: round(cy + 3.5),
349
+ textAnchor: "middle",
350
+ className: "fill-(--diagram-ink-2) font-mono [font-size:10px]",
351
+ children: text
352
+ }
353
+ ),
354
+ note && /* @__PURE__ */ jsxs(Fragment, { children: [
355
+ /* @__PURE__ */ jsx(
356
+ "rect",
357
+ {
358
+ x: round(x - noteWidth / 2),
359
+ y: round(cy + 6),
360
+ width: round(noteWidth),
361
+ height: 13,
362
+ className: "fill-(--diagram-surface)"
363
+ }
364
+ ),
365
+ /* @__PURE__ */ jsx(
366
+ "text",
367
+ {
368
+ x: round(x),
369
+ y: round(cy + 15.5),
370
+ textAnchor: "middle",
371
+ className: "fill-(--diagram-ink-3) font-mono [font-size:9px]",
372
+ children: note
373
+ }
374
+ )
375
+ ] }),
376
+ chip && /* @__PURE__ */ jsx(Chip, { x, y: cy + (note ? 33 : 20), text: chip, align: "middle" })
377
+ ] });
378
+ }
379
+ function BandFrame({
380
+ box,
381
+ label,
382
+ dashed,
383
+ muted
384
+ }) {
385
+ return /* @__PURE__ */ jsxs("g", { "aria-hidden": "true", children: [
386
+ /* @__PURE__ */ jsx(
387
+ "rect",
388
+ {
389
+ x: round(box.x),
390
+ y: round(box.y),
391
+ width: round(box.w),
392
+ height: round(box.h),
393
+ rx: 10,
394
+ ry: 10,
395
+ className: cn(
396
+ "[rx:var(--radius-lg)] [ry:var(--radius-lg)]",
397
+ muted ? "fill-(--diagram-band)" : "fill-none",
398
+ dashed ? "stroke-(--diagram-rule-hard) [stroke-width:1.2] [stroke-dasharray:6_4]" : "stroke-(--diagram-rule) [stroke-width:1]"
399
+ )
400
+ }
401
+ ),
402
+ label && /* @__PURE__ */ jsx(
403
+ "text",
404
+ {
405
+ x: round(box.x + 2),
406
+ y: round(box.y - 7),
407
+ className: "fill-(--diagram-ink-3) font-mono [font-size:9px] [letter-spacing:0.14em] uppercase",
408
+ children: label
409
+ }
410
+ )
411
+ ] });
412
+ }
413
+ function StatePlate({
414
+ box,
415
+ kind,
416
+ children
417
+ }) {
418
+ const { x, y, w, h } = box;
419
+ const common = "[stroke-width:1.3]";
420
+ if (kind === "decision") {
421
+ const cx = x + w / 2;
422
+ const cy = y + h / 2;
423
+ return /* @__PURE__ */ jsxs("g", { children: [
424
+ /* @__PURE__ */ jsx(
425
+ "path",
426
+ {
427
+ d: `M ${round(cx)} ${round(y)} L ${round(x + w)} ${round(cy)} L ${round(cx)} ${round(y + h)} L ${round(x)} ${round(cy)} Z`,
428
+ className: cn("fill-(--diagram-node) stroke-(--diagram-rule-hard)", common)
429
+ }
430
+ ),
431
+ children
432
+ ] });
433
+ }
434
+ if (kind === "external") {
435
+ const cut = 13;
436
+ return /* @__PURE__ */ jsxs("g", { children: [
437
+ /* @__PURE__ */ jsx(
438
+ "path",
439
+ {
440
+ d: `M ${round(x)} ${round(y)} H ${round(x + w - cut)} L ${round(x + w)} ${round(y + cut)} V ${round(y + h)} H ${round(x)} Z`,
441
+ className: cn("fill-(--diagram-node-2) stroke-(--diagram-rule)", common)
442
+ }
443
+ ),
444
+ children
445
+ ] });
446
+ }
447
+ const shape = kind === "start" ? "fill-(--diagram-plate) stroke-(--diagram-plate)" : kind === "success" ? "fill-(--success-wash) stroke-(--success)" : kind === "failure" ? "fill-(--danger-wash) stroke-(--danger)" : kind === "waiting" ? "fill-(--diagram-node) stroke-(--diagram-rule) [stroke-dasharray:5_3]" : kind === "neutral" ? "fill-(--diagram-node-2) stroke-(--diagram-rule)" : "fill-(--diagram-node) stroke-(--diagram-rule-hard)";
448
+ const radius = kind === "start" ? h / 2 : 6;
449
+ return /* @__PURE__ */ jsxs("g", { children: [
450
+ /* @__PURE__ */ jsx(
451
+ "rect",
452
+ {
453
+ x: round(x),
454
+ y: round(y),
455
+ width: round(w),
456
+ height: round(h),
457
+ rx: radius,
458
+ ry: radius,
459
+ className: cn(kind === "start" ? "" : PLATE_RADIUS, shape, common)
460
+ }
461
+ ),
462
+ children
463
+ ] });
464
+ }
465
+ export {
466
+ BandFrame,
467
+ Chip,
468
+ DiagramDefs,
469
+ EdgeLabel,
470
+ KIND_WORD,
471
+ NodePlate,
472
+ PLATE,
473
+ Sigil,
474
+ StatePlate,
475
+ edgeClasses,
476
+ edgeMarker,
477
+ edgeStroke,
478
+ plateHeight
479
+ };
480
+ //# sourceMappingURL=marks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/diagrams/lib/marks.tsx"],"sourcesContent":["/**\n * The marks every figure is drawn out of: a plate, a sigil, a line, a band.\n *\n * THE PROBLEM THIS FILE SOLVES. Archify separates seven kinds of node by hue —\n * a cyan frontend, an emerald service, a rose security boundary. This system\n * has no hue to spend: the only chroma left in it is bound to state, and the\n * rest is paper and ink. So the kind is carried TWICE, by a drawn sigil and by\n * a word, on one eyebrow line above the name.\n *\n * Two carriers rather than one, and the redundancy is the design. A 12-unit\n * glyph alone is a guess — a cylinder and a queue are four grey pixels apart at\n * reading size, and a reader who has not learnt the key is looking at smudges\n * that cost 30 units of the plate's width. A word alone scans slowly: seven\n * boxes each reading SERVICE in the same weight is a column of text, not a\n * diagram. Together the sigil does the scanning and the word does the\n * disambiguating, and the key that teaches the sigil shows the pair.\n *\n * THE HIERARCHY IS WEIGHT, RULE AND REVERSAL — the system's own law 7, and the\n * only one available once colour is spent. A plate is paper on a hairline; the\n * one node a figure is ABOUT is ink-filled; a boundary is a rule and never a\n * ground; a main path is a heavier line rather than a different one.\n *\n * Everything paints through CSS rather than through a presentation attribute,\n * because `fill=\"var(--ink)\"` is not valid markup — a custom property has to\n * reach an SVG through a style rule. That indirection is also what makes the\n * export work: `getComputedStyle` has already substituted the variable, so a\n * serialised clone carries a real colour rather than a name nothing resolves.\n */\n\nimport type { ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport type { LifecycleStateKind, NodeKind, Variant } from '../spec'\nimport { round, TYPE, textWidth, wrapText, type Box } from './geometry'\n\n/** The corner radius on a plate, tracking the system's ladder. */\nconst PLATE_RADIUS = '[rx:var(--radius-sm)] [ry:var(--radius-sm)]' as const\n\n/** What each kind is called on a plate's eyebrow. Short, because it is a kicker. */\nexport const KIND_WORD: Record<NodeKind, string> = {\n frontend: 'CLIENT',\n backend: 'SERVICE',\n database: 'DATA',\n cloud: 'CLOUD',\n security: 'POLICY',\n messagebus: 'QUEUE',\n external: 'EXTERNAL',\n}\n\n/**\n * The marker definitions every figure needs.\n *\n * Rendered once per figure. The ids are namespaced by the figure's own id\n * because two diagrams on one page would otherwise share one `<defs>`, and the\n * second figure's arrowheads would silently take the first figure's paint —\n * SVG ids are document-global, and a duplicate is resolved to whichever came\n * first rather than to the nearest one.\n */\nexport function DiagramDefs({ uid }: { uid: string }) {\n return (\n <defs>\n <marker\n id={`${uid}-arrow`}\n markerWidth=\"11\"\n markerHeight=\"11\"\n refX=\"9.5\"\n refY=\"5.5\"\n orient=\"auto-start-reverse\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path d=\"M 0.6 1.4 L 10 5.5 L 0.6 9.6 Z\" className=\"fill-(--diagram-line)\" />\n </marker>\n <marker\n id={`${uid}-arrow-strong`}\n markerWidth=\"12\"\n markerHeight=\"12\"\n refX=\"10.5\"\n refY=\"6\"\n orient=\"auto-start-reverse\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path d=\"M 0.6 1.6 L 11 6 L 0.6 10.4 Z\" className=\"fill-(--diagram-line-strong)\" />\n </marker>\n {/* An open head, for a reply. A reader distinguishes a return from a call\n faster by the head than by the dash pattern, which is why the two are\n different shapes rather than the same shape in two weights. */}\n <marker\n id={`${uid}-arrow-open`}\n markerWidth=\"11\"\n markerHeight=\"11\"\n refX=\"9.5\"\n refY=\"5.5\"\n orient=\"auto-start-reverse\"\n markerUnits=\"userSpaceOnUse\"\n >\n <path\n d=\"M 1 1.6 L 9.6 5.5 L 1 9.4\"\n fill=\"none\"\n strokeWidth=\"1.4\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"stroke-(--diagram-line-soft)\"\n />\n </marker>\n </defs>\n )\n}\n\n/**\n * The 14×14 mark that says what kind of thing a node is.\n *\n * Drawn on a 14-unit grid at a 1.35 stroke. Both numbers went up from the first\n * pass and both had to: at 12 units and a 1.2 stroke the seven forms were a few\n * grey pixels apart at reading size, which is not a distinction — it is a\n * smudge. Every path is closed geometry rather than an icon font, because a\n * figure has to serialise into a standalone file and a font the exported\n * document cannot fetch renders as a box.\n */\nexport function Sigil({\n kind,\n x,\n y,\n reversed,\n}: {\n kind: NodeKind\n x: number\n y: number\n reversed?: boolean\n}) {\n const stroke = cn(\n 'fill-none [stroke-width:1.35] [stroke-linejoin:round] [stroke-linecap:round]',\n reversed ? 'stroke-(--diagram-plate-ink)' : 'stroke-(--diagram-sigil)',\n )\n\n return (\n <g transform={`translate(${round(x)}, ${round(y)})`} aria-hidden=\"true\">\n {kind === 'frontend' && (\n <g className={stroke}>\n <rect x=\"0.8\" y=\"2\" width=\"12.4\" height=\"10\" rx=\"1.6\" />\n <path d=\"M 0.8 5.2 H 13.2\" />\n <path d=\"M 3 3.6 H 4\" />\n </g>\n )}\n {kind === 'backend' && (\n <g className={stroke}>\n <rect x=\"0.8\" y=\"1.8\" width=\"12.4\" height=\"4.4\" rx=\"1.2\" />\n <rect x=\"0.8\" y=\"7.8\" width=\"12.4\" height=\"4.4\" rx=\"1.2\" />\n <path d=\"M 3 4 H 4.2\" />\n <path d=\"M 3 10 H 4.2\" />\n </g>\n )}\n {kind === 'database' && (\n <g className={stroke}>\n <ellipse cx=\"7\" cy=\"3.4\" rx=\"6\" ry=\"2.4\" />\n <path d=\"M 1 3.4 V 10.6 A 6 2.4 0 0 0 13 10.6 V 3.4\" />\n <path d=\"M 1 7 A 6 2.4 0 0 0 13 7\" />\n </g>\n )}\n {kind === 'cloud' && (\n <g className={stroke}>\n <path d=\"M 3.4 10.8 A 2.9 2.9 0 0 1 3.5 5 A 3.8 3.8 0 0 1 10.5 4.2 A 3.2 3.2 0 0 1 11 10.8 Z\" />\n </g>\n )}\n {kind === 'security' && (\n <g className={stroke}>\n <path d=\"M 7 1 L 12.5 3.1 V 7 C 12.5 10 10 12.1 7 13 C 4 12.1 1.5 10 1.5 7 V 3.1 Z\" />\n <path d=\"M 4.7 7 L 6.4 8.7 L 9.4 5.3\" />\n </g>\n )}\n {kind === 'messagebus' && (\n <g className={stroke}>\n <path d=\"M 0.9 2.8 H 13.1\" />\n <path d=\"M 0.9 11.2 H 13.1\" />\n <path d=\"M 3.6 5.2 V 8.8\" />\n <path d=\"M 7 5.2 V 8.8\" />\n <path d=\"M 10.4 5.2 V 8.8\" />\n </g>\n )}\n {kind === 'external' && (\n <g className={stroke}>\n <path d=\"M 7.4 1.8 H 1.6 V 12.2 H 12.4 V 6.4\" />\n <path d=\"M 8.4 5.4 L 13 1\" />\n <path d=\"M 9.6 0.9 H 13.1 V 4.4\" />\n </g>\n )}\n </g>\n )\n}\n\n/** How a plate is painted, given what the specification insisted on. */\nfunction plateClasses(variant: Variant | undefined, muted: boolean): string {\n switch (variant) {\n case 'emphasis':\n return 'fill-(--diagram-plate) stroke-(--diagram-plate) [stroke-width:1]'\n case 'security':\n return 'fill-(--diagram-node) stroke-(--diagram-rule-hard) [stroke-width:1.3] [stroke-dasharray:5_3]'\n case 'dashed':\n return 'fill-(--diagram-node) stroke-(--diagram-rule) [stroke-width:1] [stroke-dasharray:4_3]'\n default:\n return muted\n ? 'fill-(--diagram-node-2) stroke-(--diagram-rule) [stroke-width:1]'\n : 'fill-(--diagram-node) stroke-(--diagram-rule) [stroke-width:1]'\n }\n}\n\n/**\n * A plate's internal vertical rhythm, in user units from its top edge.\n *\n * Written out as one object rather than computed inline in three renderers,\n * because these numbers ARE the plate: change one and the card stops looking\n * like a card. The eyebrow sits high and tight, the name is the only thing set\n * at reading size, and the sublabel hangs under it in the meta voice.\n */\nexport const PLATE = {\n padX: 15,\n eyebrow: 19,\n label: 39,\n sub: 53,\n lineStep: 17,\n /** Where the eyebrow lands when there is none — the label moves up into it. */\n riseWithoutEyebrow: 16,\n} as const\n\n/**\n * How tall a plate 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 * 60-unit box and then puts two lines of name and a qualifier in it, honouring\n * the 60 prints the qualifier through the bottom rule — which reads as a\n * rendering fault rather than as a box asked to hold too much. Growing is\n * visible and correct; clipping is invisible and wrong.\n */\nexport function plateHeight(\n labelLines: number,\n hasSub: boolean,\n hasEyebrow: boolean,\n floor = 0,\n): number {\n const last = hasSub ? PLATE.sub : PLATE.label\n const content =\n last + (labelLines - 1) * PLATE.lineStep + 16 - (hasEyebrow ? 0 : PLATE.riseWithoutEyebrow)\n return Math.max(floor, Math.ceil(content))\n}\n\nexport interface NodePlateProps {\n box: Box\n label: string\n sublabel?: string\n tag?: string\n kind?: NodeKind\n variant?: Variant\n /** Printed in the trailing corner in mono — a step number. */\n step?: string\n /** Paints the plate on the recessed ground rather than on paper. */\n muted?: boolean\n /** Lit by a guided chapter or a reader's focus. */\n active?: boolean\n /** Dimmed because something else is lit. */\n dimmed?: boolean\n onSelect?: () => void\n /** A stable hook the viewer chrome and the tests both address nodes by. */\n nodeId?: string\n}\n\n/**\n * One box, with everything that can be printed inside it.\n *\n * Three registers, top to bottom, and each is a different voice on purpose: a\n * mono kicker naming the KIND, the name at reading size in the interface face,\n * and the qualifier back in mono. That is the same ladder a record on a page\n * uses — the diagram is not inventing a second typography for itself.\n *\n * WHAT IT IS NOT is a button, and that is deliberate. The `<svg>` around it\n * carries `role=\"img\"`, which makes everything inside it presentational to\n * assistive technology — so a `role=\"button\"` here would announce a control a\n * screen reader can never reach. The keyboard route to the same selection is\n * the figure's own summary list, which is a real `<ul>` of real `<button>`s\n * sitting beside the picture. `onSelect` here is the POINTER path to that same\n * action, and nothing else.\n */\nexport function NodePlate({\n box,\n label,\n sublabel,\n tag,\n kind,\n variant,\n step,\n muted,\n active,\n dimmed,\n onSelect,\n nodeId,\n}: NodePlateProps) {\n const reversed = variant === 'emphasis'\n const lines = wrapText(label, TYPE.label, box.w - PLATE.padX * 2, 2)\n\n // The eyebrow slot carries the most specific thing known about this node: the\n // author's own tag when there is one, and the kind word otherwise. Giving the\n // tag a row of its own instead cost every tagged plate 22 units of height for\n // one short string, and left the plate a portrait rectangle in a figure of\n // landscape ones. The sigil never moves, so the kind is still on the plate\n // either way — which is what makes the substitution safe.\n const eyebrow = tag ?? (kind ? KIND_WORD[kind] : undefined)\n const hasEyebrow = Boolean(eyebrow || step)\n const rise = hasEyebrow ? 0 : PLATE.riseWithoutEyebrow\n const labelY = box.y + PLATE.label - rise\n const subY = box.y + PLATE.sub - rise + (lines.length - 1) * PLATE.lineStep\n\n return (\n <g\n data-node={nodeId}\n data-active={active ? '' : undefined}\n className={cn(\n 'transition-opacity duration-(--duration-fast)',\n dimmed && 'opacity-25',\n onSelect && 'cursor-pointer',\n )}\n onClick={onSelect}\n >\n <rect\n x={round(box.x)}\n y={round(box.y)}\n width={round(box.w)}\n height={round(box.h)}\n rx={6}\n ry={6}\n className={cn(PLATE_RADIUS, plateClasses(variant, Boolean(muted)))}\n />\n\n {active && (\n <rect\n x={round(box.x) - 3.5}\n y={round(box.y) - 3.5}\n width={round(box.w) + 7}\n height={round(box.h) + 7}\n rx={9}\n ry={9}\n fill=\"none\"\n className=\"stroke-(--accent) [stroke-width:1.5]\"\n />\n )}\n\n {kind && (\n <Sigil\n kind={kind}\n x={box.x + PLATE.padX}\n y={box.y + PLATE.eyebrow - 11}\n reversed={reversed}\n />\n )}\n\n {eyebrow && (\n <text\n x={round(box.x + PLATE.padX + (kind ? 20 : 0))}\n y={round(box.y + PLATE.eyebrow)}\n className={cn(\n 'font-mono [font-size:8.5px] [letter-spacing:0.13em] uppercase',\n reversed ? 'fill-(--diagram-plate-ink) opacity-70' : 'fill-(--diagram-ink-3)',\n )}\n >\n {eyebrow}\n </text>\n )}\n\n {step && (\n <text\n x={round(box.x + box.w - PLATE.padX)}\n y={round(box.y + PLATE.eyebrow)}\n textAnchor=\"end\"\n className={cn(\n 'font-mono [font-size:9px] [letter-spacing:0.1em]',\n reversed ? 'fill-(--diagram-plate-ink) opacity-65' : 'fill-(--diagram-ink-3)',\n )}\n >\n {step}\n </text>\n )}\n\n {lines.map((line, index) => (\n <text\n key={index}\n x={round(box.x + PLATE.padX)}\n y={round(labelY + index * PLATE.lineStep)}\n className={cn(\n 'font-sans [font-size:14px] [letter-spacing:-0.005em]',\n reversed ? 'fill-(--diagram-plate-ink)' : 'fill-(--diagram-ink)',\n )}\n >\n {line}\n </text>\n ))}\n\n {sublabel && (\n <text\n x={round(box.x + PLATE.padX)}\n y={round(subY)}\n className={cn(\n 'font-mono [font-size:10px]',\n reversed ? 'fill-(--diagram-plate-ink) opacity-65' : 'fill-(--diagram-ink-3)',\n )}\n >\n {sublabel}\n </text>\n )}\n\n </g>\n )\n}\n\n/**\n * A mono chip: a version, a region, a protocol, a data classification.\n *\n * A rule and a word, with no fill under it. The filled chip this replaced put a\n * third ground inside a plate that already sat on a figure that sat on the\n * page, and three grounds deep is where a diagram stops reading as structure\n * and starts reading as packaging.\n */\nexport function Chip({\n x,\n y,\n text,\n align = 'start',\n reversed,\n}: {\n x: number\n y: number\n text: string\n align?: 'start' | 'middle' | 'end'\n reversed?: boolean\n}) {\n const width = textWidth(text, TYPE.chip) + 12\n const left = align === 'end' ? x - width : align === 'middle' ? x - width / 2 : x\n\n return (\n <g aria-hidden=\"true\">\n <rect\n x={round(left)}\n y={round(y - 7.5)}\n width={round(width)}\n height={15}\n rx={3}\n ry={3}\n className={cn(\n '[stroke-width:0.9] fill-(--diagram-surface)',\n reversed ? 'stroke-(--diagram-plate-ink) opacity-40' : 'stroke-(--diagram-rule)',\n )}\n />\n <text\n x={round(left + width / 2)}\n y={round(y + 2.8)}\n textAnchor=\"middle\"\n className={cn(\n 'font-mono [font-size:9px] [letter-spacing:0.05em]',\n reversed ? 'fill-(--diagram-plate-ink) opacity-70' : 'fill-(--diagram-ink-3)',\n )}\n >\n {text}\n </text>\n </g>\n )\n}\n\n/**\n * How a line is painted: the colour as a class, the geometry as attributes.\n *\n * THE SPLIT IS NOT ARBITRARY, and getting it wrong cost an afternoon. Tailwind\n * generates a rule only for a class name it can find as a LITERAL in the\n * source. A width built at runtime — `` `[stroke-width:${weight}]` `` — is a\n * class that never gets a rule, so the browser falls back to SVG's default\n * 1px, silently, on exactly the lines a figure most needs to be heavier. The\n * main path came out the same weight as a side branch and nothing said so.\n *\n * Colour still goes through a class, because a paint has to resolve a custom\n * property and `stroke=\"var(--x)\"` is not valid as an attribute. Width and dash\n * pattern are plain numbers, so they go through presentation attributes where\n * no build step has to have predicted them — and `getComputedStyle` reports an\n * attribute exactly as it reports a rule, so the export still picks them up.\n */\nexport function edgeClasses(variant?: Variant | 'return'): string {\n switch (variant) {\n case 'emphasis':\n return 'stroke-(--diagram-line-strong)'\n case 'dashed':\n case 'return':\n return 'stroke-(--diagram-line-soft)'\n default:\n return 'stroke-(--diagram-line)'\n }\n}\n\n/** A line's width and dash pattern, as presentation attributes. */\nexport function edgeStroke(\n variant?: Variant | 'return',\n weight?: number,\n): { strokeWidth: number; strokeDasharray?: string } {\n switch (variant) {\n case 'emphasis':\n return { strokeWidth: weight ?? 2.1 }\n case 'security':\n return { strokeWidth: weight ?? 1.4, strokeDasharray: '7 4' }\n case 'dashed':\n case 'return':\n return { strokeWidth: weight ?? 1.3, strokeDasharray: '4.5 4' }\n default:\n return { strokeWidth: weight ?? 1.4 }\n }\n}\n\n/** Which arrowhead a line ends in. */\nexport function edgeMarker(variant?: Variant | 'return'): 'arrow' | 'arrow-strong' | 'arrow-open' {\n if (variant === 'emphasis') return 'arrow-strong'\n if (variant === 'return') return 'arrow-open'\n return 'arrow'\n}\n\nexport interface EdgeLabelProps {\n x: number\n y: number\n axis: 'x' | 'y'\n text: string\n /** A mono chip printed under the wording — a data classification. */\n chip?: string\n /** A second, quieter line — a condition, a timeout, a retry count. */\n note?: string\n}\n\n/**\n * A relationship's wording, on a mask that punches the line out from under it.\n *\n * The mask is not decoration. A label drawn straight onto a line is unreadable\n * at every size, and the two repairs a renderer can make are moving the label\n * or hiding the line behind it. Moving it changes which segment the wording\n * belongs to, which is a semantic change; masking is the one that leaves the\n * meaning alone.\n *\n * Set in mono rather than in the interface face. The wording on a line is an\n * ANNOTATION on the picture, not a second rank of content — and at the sans\n * face it competed with the names on the plates, which is the one comparison it\n * must lose.\n */\nexport function EdgeLabel({ x, y, axis, text, chip, note }: EdgeLabelProps) {\n const width = textWidth(text, TYPE.edge) + 10\n const noteWidth = note ? textWidth(note, TYPE.chip) + 10 : 0\n const cy = y + (axis === 'x' ? -10 : 0)\n\n return (\n <g aria-hidden=\"true\">\n <rect\n x={round(x - width / 2)}\n y={round(cy - 7)}\n width={round(width)}\n height={14}\n className=\"fill-(--diagram-surface)\"\n />\n <text\n x={round(x)}\n y={round(cy + 3.5)}\n textAnchor=\"middle\"\n className=\"fill-(--diagram-ink-2) font-mono [font-size:10px]\"\n >\n {text}\n </text>\n {note && (\n <>\n <rect\n x={round(x - noteWidth / 2)}\n y={round(cy + 6)}\n width={round(noteWidth)}\n height={13}\n className=\"fill-(--diagram-surface)\"\n />\n <text\n x={round(x)}\n y={round(cy + 15.5)}\n textAnchor=\"middle\"\n className=\"fill-(--diagram-ink-3) font-mono [font-size:9px]\"\n >\n {note}\n </text>\n </>\n )}\n {chip && <Chip x={x} y={cy + (note ? 33 : 20)} text={chip} align=\"middle\" />}\n </g>\n )\n}\n\n/**\n * A labelled frame around a set of boxes: a region, a security group, a phase,\n * a process group.\n *\n * A FRAME IS A RULE AND NOT A GROUND. Archify washes its regions with a tint,\n * which this system cannot copy for two reasons: a second ground inside a paper\n * figure reads as packaging rather than as structure — the same argument the\n * containment `Diagram` primitive makes about nested boxes — and every\n * relationship label crossing the wash would then need to know which of two\n * grounds its mask is punching through, which is a question a label cannot\n * answer. `muted` therefore washes only where a band IS the ground it labels: a\n * workflow's exception lane, which nothing routes across.\n *\n * The label sits ABOVE the top rule rather than inside the frame or on it.\n * Inside, it costs a line of vertical space in every frame whether or not the\n * frame had one to spare. ON the rule — which is where it started — it has to\n * punch a mask through the frame, and that mask then competes for the same few\n * units of clear ground as any relationship label routed along the frame's own\n * edge: two captions, two white plates, one on top of the other. Above the\n * rule it needs no mask, leaves the frame unbroken, and reads as a caption for\n * the frame rather than as its first child.\n */\nexport function BandFrame({\n box,\n label,\n dashed,\n muted,\n}: {\n box: Box\n label?: string\n dashed?: boolean\n muted?: boolean\n}) {\n return (\n <g aria-hidden=\"true\">\n <rect\n x={round(box.x)}\n y={round(box.y)}\n width={round(box.w)}\n height={round(box.h)}\n rx={10}\n ry={10}\n className={cn(\n '[rx:var(--radius-lg)] [ry:var(--radius-lg)]',\n muted ? 'fill-(--diagram-band)' : 'fill-none',\n dashed\n ? 'stroke-(--diagram-rule-hard) [stroke-width:1.2] [stroke-dasharray:6_4]'\n : 'stroke-(--diagram-rule) [stroke-width:1]',\n )}\n />\n {label && (\n <text\n x={round(box.x + 2)}\n y={round(box.y - 7)}\n className=\"fill-(--diagram-ink-3) font-mono [font-size:9px] [letter-spacing:0.14em] uppercase\"\n >\n {label}\n </text>\n )}\n </g>\n )\n}\n\n/**\n * The plate shape for a lifecycle state.\n *\n * This is the one place the system spends colour, and it spends exactly the two\n * tokens it reserves for state: `--success` on a terminal success, `--danger`\n * on a terminal failure. Every other kind is carried by shape — a filled cap\n * for a start, a diamond for a decision, a dashed frame for a wait, a cut\n * corner for something outside the system — so a greyscale print loses the two\n * outcomes' hue and keeps all six other distinctions.\n */\nexport function StatePlate({\n box,\n kind,\n children,\n}: {\n box: Box\n kind: LifecycleStateKind\n children?: ReactNode\n}) {\n const { x, y, w, h } = box\n const common = '[stroke-width:1.3]'\n\n if (kind === 'decision') {\n const cx = x + w / 2\n const cy = y + h / 2\n return (\n <g>\n <path\n d={`M ${round(cx)} ${round(y)} L ${round(x + w)} ${round(cy)} L ${round(cx)} ${round(y + h)} L ${round(x)} ${round(cy)} Z`}\n className={cn('fill-(--diagram-node) stroke-(--diagram-rule-hard)', common)}\n />\n {children}\n </g>\n )\n }\n\n if (kind === 'external') {\n const cut = 13\n return (\n <g>\n <path\n d={`M ${round(x)} ${round(y)} H ${round(x + w - cut)} L ${round(x + w)} ${round(y + cut)} V ${round(y + h)} H ${round(x)} Z`}\n className={cn('fill-(--diagram-node-2) stroke-(--diagram-rule)', common)}\n />\n {children}\n </g>\n )\n }\n\n const shape =\n kind === 'start'\n ? 'fill-(--diagram-plate) stroke-(--diagram-plate)'\n : kind === 'success'\n ? 'fill-(--success-wash) stroke-(--success)'\n : kind === 'failure'\n ? 'fill-(--danger-wash) stroke-(--danger)'\n : kind === 'waiting'\n ? 'fill-(--diagram-node) stroke-(--diagram-rule) [stroke-dasharray:5_3]'\n : kind === 'neutral'\n ? 'fill-(--diagram-node-2) stroke-(--diagram-rule)'\n : 'fill-(--diagram-node) stroke-(--diagram-rule-hard)'\n\n const radius = kind === 'start' ? h / 2 : 6\n\n return (\n <g>\n <rect\n x={round(x)}\n y={round(y)}\n width={round(w)}\n height={round(h)}\n rx={radius}\n ry={radius}\n className={cn(kind === 'start' ? '' : PLATE_RADIUS, shape, common)}\n />\n {children}\n </g>\n )\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,85 @@
1
+ import { Side, RouteMode, Point } from '../spec.js';
2
+ import { Box } from './geometry.js';
3
+
4
+ /**
5
+ * Getting a line from one box to another.
6
+ *
7
+ * The routing rule this module keeps, and the reason it is worth having at all:
8
+ * A SIDE IS A DIRECTION CONTRACT. A line that says it leaves a box's right face
9
+ * must leave it going right, and a line that says it arrives at a top face must
10
+ * arrive going down. A router that takes the shortest path between two anchor
11
+ * points breaks that contract constantly — it produces a segment that grazes
12
+ * the box it just left, and a reader cannot tell which of two adjacent boxes an
13
+ * arrow belongs to. Every path below therefore begins and ends with a stub
14
+ * perpendicular to its face, and the interesting geometry happens between the
15
+ * two stubs.
16
+ *
17
+ * The second rule is about crowding. When several lines share one face they are
18
+ * spread across it rather than stacked on its midpoint, because two arrowheads
19
+ * landing on the same coordinate is one arrowhead as far as a reader is
20
+ * concerned. Explicit routing controls opt out of the spread: an author who
21
+ * pinned a channel has already decided where the line goes.
22
+ */
23
+
24
+ interface RouteRequest {
25
+ from: Box;
26
+ to: Box;
27
+ fromSide?: Side;
28
+ toSide?: Side;
29
+ mode?: RouteMode | string;
30
+ via?: Point[];
31
+ channelX?: number;
32
+ channelY?: number;
33
+ /** 0–1 along the face, where this line's port sits. 0.5 is the midpoint. */
34
+ fromOffset?: number;
35
+ toOffset?: number;
36
+ }
37
+ interface RoutedEdge {
38
+ /** The `d` attribute, corners already rounded. */
39
+ d: string;
40
+ /** The polyline before rounding — what the label placer reads. */
41
+ points: [number, number][];
42
+ /** Where a label wants to sit, and how the segment under it runs. */
43
+ label: {
44
+ x: number;
45
+ y: number;
46
+ axis: 'x' | 'y';
47
+ };
48
+ /** The last segment's direction, for the arrowhead. */
49
+ angle: number;
50
+ }
51
+ /**
52
+ * The polyline as a path, with the corners turned rather than mitred.
53
+ *
54
+ * A quadratic through the corner point, pulled back along both legs by the
55
+ * radius. The pull-back is clamped to a third of each leg so a short segment
56
+ * between two turns cannot produce a curve that overshoots its own endpoint —
57
+ * which renders as a hook and is the classic artefact of rounding a polyline
58
+ * without checking the leg lengths.
59
+ */
60
+ declare function roundedPath(points: [number, number][], radius?: number): string;
61
+ /** Routes one relationship. */
62
+ declare function routeEdge(request: RouteRequest): RoutedEdge;
63
+ /** One line's use of one face: which face, and which end of which line it is. */
64
+ interface PortUse {
65
+ /** The face, as `nodeId:side`. Two lines sharing this string share a face. */
66
+ face: string;
67
+ /** This endpoint, as `edgeIndex:from` or `edgeIndex:to`. */
68
+ ref: string;
69
+ }
70
+ /**
71
+ * Which share of its face each line gets, when several use the same one.
72
+ *
73
+ * A single line keeps the midpoint — spreading one line only moves it off
74
+ * centre for no reason. Lines that carry an explicit routing control are
75
+ * excluded by the caller before they get here, so a pinned channel is never
76
+ * nudged out from under its pin.
77
+ *
78
+ * Endpoints are keyed separately rather than by line, because a line's two ends
79
+ * land on two different faces with two different amounts of company. Keying by
80
+ * line gave both ends the same offset, which on a figure with one crowded face
81
+ * and one empty one visibly skewed every arrow that touched the empty side.
82
+ */
83
+ declare function spreadPorts(uses: PortUse[]): Map<string, number>;
84
+
85
+ export { type PortUse, type RouteRequest, type RoutedEdge, roundedPath, routeEdge, spreadPorts };