@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,353 @@
1
+ /**
2
+ * The five diagram specifications, and the vocabulary they share.
3
+ *
4
+ * These types mirror the JSON schemas published by
5
+ * [archify](https://github.com/tt-a1i/archify) — `architecture`, `workflow`,
6
+ * `sequence`, `dataflow` and `lifecycle` — so a specification authored for that
7
+ * tool renders here without a translation step. That compatibility is the whole
8
+ * point of copying somebody else's field names: an agent that already knows how
9
+ * to write `{ "diagram_type": "architecture", "components": [...] }` can hand
10
+ * the same object to a React component and get the system's own look back.
11
+ *
12
+ * WHAT IS DELIBERATELY NOT HERE: archify's routing micro-controls. `via`,
13
+ * `channelX`, `channelY`, `labelAt`, `labelSegment` and `bias` exist there to
14
+ * let an author nudge a line after a validator complains about a collision.
15
+ * They are accepted below and honoured where they are cheap, because dropping a
16
+ * field silently turns a valid specification into a different diagram — but
17
+ * they are hints, not a contract, and a figure that needs many of them is a
18
+ * figure that wants fewer edges.
19
+ *
20
+ * Every position in every type is EXPLICIT — a row and a column, a lane and a
21
+ * step, a participant and a y. Nothing below is solved for. That is what makes
22
+ * these figures server-renderable: layout is arithmetic on numbers the
23
+ * specification already carries, not a simulation that has to settle.
24
+ */
25
+ /** A point in the figure's own user-unit space. */
26
+ type Point = [x: number, y: number];
27
+ /** Which face of a box a line leaves from or arrives at. */
28
+ type Side = 'left' | 'right' | 'top' | 'bottom';
29
+ /**
30
+ * What a node IS, across every diagram type that has nodes.
31
+ *
32
+ * Seven kinds, and in a monochrome system none of them is a colour. Each is
33
+ * drawn as a distinct sigil in the node's leading corner — a cylinder for a
34
+ * database, a shield for a security boundary, rails and ticks for a queue — so
35
+ * the kind survives a greyscale print, a colour-blind reader, and the system's
36
+ * own rule that the only chroma left is bound to state.
37
+ */
38
+ type NodeKind = 'frontend' | 'backend' | 'database' | 'cloud' | 'security' | 'messagebus' | 'external';
39
+ /**
40
+ * How much a relationship or a node is being insisted upon.
41
+ *
42
+ * `emphasis` reverses the plate; `security` dashes the frame and doubles the
43
+ * rule; `dashed` is the quiet one — an optional path, an asynchronous hop.
44
+ */
45
+ type Variant = 'default' | 'emphasis' | 'security' | 'dashed';
46
+ /** How a line gets from one box to another. */
47
+ type RouteMode = 'auto' | 'straight' | 'orthogonal-h' | 'orthogonal-v';
48
+ /** Whether the key beside the figure names every kind, only the used ones, or none. */
49
+ type LegendMode = 'auto' | 'all' | 'hidden';
50
+ /**
51
+ * One curated stop in a guided reading of the figure.
52
+ *
53
+ * At most five, which is archify's cap and a good one: a sixth chapter is a
54
+ * second diagram wearing the first one's clothes.
55
+ */
56
+ interface DiagramView {
57
+ id: string;
58
+ label: string;
59
+ /** Node ids this chapter lights. */
60
+ focus: string[];
61
+ note?: string;
62
+ }
63
+ /**
64
+ * A conclusion card printed under the figure.
65
+ *
66
+ * `dot` names a hue in archify's palette. Here it selects a MARK — a filled
67
+ * square, a ring, a bar — rather than a colour, so seven cards in a row are
68
+ * still seven distinguishable cards on paper-white.
69
+ */
70
+ interface DiagramCard {
71
+ dot: 'cyan' | 'emerald' | 'violet' | 'amber' | 'rose' | 'orange' | 'slate';
72
+ title: string;
73
+ items: string[];
74
+ }
75
+ /** What every specification says about itself. */
76
+ interface DiagramMeta {
77
+ title: string;
78
+ subtitle?: string;
79
+ /**
80
+ * The extent the author's own renderer came out at.
81
+ *
82
+ * Accepted so a specification carrying it still typechecks, and deliberately
83
+ * NOT applied. It describes archify's layout — a different grid, different
84
+ * column widths, a different plate height — so on any figure placed from
85
+ * lanes and columns it is simply the wrong rectangle: too small and it crops
86
+ * the last participants off the edge, too large and it hangs a band of dead
87
+ * paper under the diagram. The extent here is computed from the marks
88
+ * actually drawn, which is the only number that can be right.
89
+ */
90
+ viewBox?: [number, number] | [number, number, number, number];
91
+ legend?: {
92
+ mode?: LegendMode;
93
+ entries?: Record<string, {
94
+ label?: string;
95
+ visible?: boolean;
96
+ }>;
97
+ };
98
+ views?: DiagramView[];
99
+ }
100
+ /** Where a node's facts came from, when the figure was built against real code. */
101
+ interface SourceRef {
102
+ path: string;
103
+ line?: number;
104
+ end_line?: number;
105
+ label?: string;
106
+ }
107
+ /** The fields every drawn box carries, whatever the diagram type calls it. */
108
+ interface NodeBase {
109
+ id: string;
110
+ label: string;
111
+ /** One short line under the label — what it is, a step back. */
112
+ sublabel?: string;
113
+ /** A mono chip on the box: a version, a region, a protocol. */
114
+ tag?: string;
115
+ /** How wide and tall this box is, when the type's default does not suit it. */
116
+ width?: number;
117
+ height?: number;
118
+ }
119
+ /** The fields every drawn line carries. */
120
+ interface EdgeBase {
121
+ id?: string;
122
+ from: string;
123
+ to: string;
124
+ label?: string;
125
+ variant?: Variant;
126
+ fromSide?: Side;
127
+ toSide?: Side;
128
+ route?: RouteMode | string;
129
+ /** Waypoints the line is dragged through, in order. */
130
+ via?: Point[];
131
+ /** Pins the label rather than letting the router place it. */
132
+ labelAt?: Point;
133
+ labelDx?: number;
134
+ labelDy?: number;
135
+ /** Forces an orthogonal run through a fixed coordinate. */
136
+ channelX?: number;
137
+ channelY?: number;
138
+ /** Stroke weight, in user units. */
139
+ width?: number;
140
+ }
141
+ interface ArchitectureComponent extends NodeBase {
142
+ type: NodeKind;
143
+ /** Grid placement. Ignored when `pos` is given. */
144
+ row?: number;
145
+ col?: number;
146
+ /** Absolute placement, in user units, overriding the grid. */
147
+ pos?: Point;
148
+ /**
149
+ * `[width, height]`, which is how archify's architecture schema spells it.
150
+ *
151
+ * Accepted alongside the `width` / `height` pair every other diagram type
152
+ * uses, and it wins when both are given. Two spellings for one fact is not a
153
+ * design — it is the cost of reading somebody else's file format without
154
+ * making its authors edit their files.
155
+ */
156
+ size?: [number, number];
157
+ sources?: SourceRef[];
158
+ }
159
+ /**
160
+ * A frame drawn around several components.
161
+ *
162
+ * `region` is a deployment boundary — an availability zone, a VPC, a cluster.
163
+ * `security-group` is a trust boundary, and is drawn dashed for that reason: a
164
+ * reader should be able to see which line is about where a thing runs and which
165
+ * is about what may reach it, without reading either label.
166
+ */
167
+ interface ArchitectureBoundary {
168
+ kind: 'region' | 'security-group';
169
+ label: string;
170
+ /** Component ids this frame encloses. */
171
+ wraps: string[];
172
+ /** Clear space between the frame and the boxes it holds. */
173
+ pad?: number;
174
+ }
175
+ interface ArchitectureSpec {
176
+ diagram_type?: 'architecture';
177
+ meta: DiagramMeta;
178
+ layout?: {
179
+ origin?: Point;
180
+ cols?: number;
181
+ gapX?: number;
182
+ gapY?: number;
183
+ cellW?: number;
184
+ cellH?: number;
185
+ };
186
+ components: ArchitectureComponent[];
187
+ boundaries?: ArchitectureBoundary[];
188
+ connections?: EdgeBase[];
189
+ cards?: DiagramCard[];
190
+ }
191
+ /** A horizontal band: who or what performs the steps in it. */
192
+ interface WorkflowLane {
193
+ id: string;
194
+ label: string;
195
+ /** `exception` bands hold the failure path and are drawn on a washed ground. */
196
+ variant?: 'normal' | 'exception';
197
+ }
198
+ /** A vertical band across every lane: a stage of the process. */
199
+ interface WorkflowPhase {
200
+ id: string;
201
+ label: string;
202
+ fromCol: number;
203
+ toCol: number;
204
+ variant?: Variant;
205
+ }
206
+ /** A frame around a run of columns inside ONE lane. */
207
+ interface WorkflowGroup extends WorkflowPhase {
208
+ lane: string;
209
+ }
210
+ interface WorkflowNode extends NodeBase {
211
+ lane: string;
212
+ col: number;
213
+ type?: NodeKind;
214
+ /** Nudges this box off its row's centre line, for a branch that needs room. */
215
+ yOffset?: number;
216
+ }
217
+ /**
218
+ * What a line MEANS in a process, which is not the same question as how it is
219
+ * drawn.
220
+ *
221
+ * `main` is the path the reader should be able to follow without thinking;
222
+ * `branch` leaves it and comes back; `async` does not block; `return` goes
223
+ * backwards; `error` leaves for the exception lane.
224
+ */
225
+ type WorkflowEdgeRole = 'main' | 'branch' | 'async' | 'return' | 'error';
226
+ interface WorkflowEdge extends EdgeBase {
227
+ role?: WorkflowEdgeRole;
228
+ }
229
+ interface WorkflowSpec {
230
+ diagram_type?: 'workflow';
231
+ schema_version?: 1 | 2;
232
+ meta: DiagramMeta;
233
+ lanes: WorkflowLane[];
234
+ phases?: WorkflowPhase[];
235
+ groups?: WorkflowGroup[];
236
+ nodes: WorkflowNode[];
237
+ edges?: WorkflowEdge[];
238
+ /** Node ids on the path a reader should follow first. Drawn heavier. */
239
+ mainPath?: string[];
240
+ cards?: DiagramCard[];
241
+ }
242
+ interface SequenceParticipant extends NodeBase {
243
+ type?: NodeKind;
244
+ }
245
+ /** A labelled horizontal band across the whole figure: a phase of the exchange. */
246
+ interface SequenceSegment {
247
+ from: number;
248
+ to: number;
249
+ label: string;
250
+ }
251
+ interface SequenceMessage {
252
+ id?: string;
253
+ from: string;
254
+ to: string;
255
+ /** Where this message sits on the vertical time axis, in user units. */
256
+ y: number;
257
+ label?: string;
258
+ /** `return` is drawn dashed with an open head — a reply, not a call. */
259
+ variant?: Variant | 'return';
260
+ note?: string;
261
+ }
262
+ /** A bar on a lifeline: this participant is busy for this span of the axis. */
263
+ interface SequenceActivation {
264
+ participant: string;
265
+ from: number;
266
+ to: number;
267
+ type?: NodeKind;
268
+ }
269
+ interface SequenceSpec {
270
+ diagram_type?: 'sequence';
271
+ meta: DiagramMeta & {
272
+ /**
273
+ * `fixed` gives every participant the same column width; `spread` divides
274
+ * the figure evenly instead.
275
+ *
276
+ * Reach for `spread` when a fixed layout would leave a wide empty margin,
277
+ * or when a meaningful participant label does not fit. Shortening the label
278
+ * to make it fit is the wrong repair — the label is the data.
279
+ */
280
+ column_fit?: 'fixed' | 'spread';
281
+ };
282
+ participants: SequenceParticipant[];
283
+ segments?: SequenceSegment[];
284
+ messages: SequenceMessage[];
285
+ activations?: SequenceActivation[];
286
+ cards?: DiagramCard[];
287
+ }
288
+ /** A column header: a stage of the pipeline. */
289
+ interface DataflowStage {
290
+ label: string;
291
+ }
292
+ interface DataflowNode extends NodeBase {
293
+ type: NodeKind;
294
+ /** Index into `stages`. */
295
+ stage: number;
296
+ row: number;
297
+ yOffset?: number;
298
+ }
299
+ interface DataflowEdge extends EdgeBase {
300
+ /**
301
+ * What is travelling, in governance terms — `PII`, `aggregated`, `hashed`.
302
+ *
303
+ * Printed as a mono chip on the line rather than folded into the label,
304
+ * because a data-flow diagram is very often read for exactly this and for
305
+ * nothing else.
306
+ */
307
+ classification?: string;
308
+ }
309
+ interface DataflowSpec {
310
+ diagram_type?: 'dataflow';
311
+ meta: DiagramMeta;
312
+ stages: DataflowStage[];
313
+ nodes: DataflowNode[];
314
+ flows?: DataflowEdge[];
315
+ cards?: DiagramCard[];
316
+ }
317
+ /**
318
+ * What a state IS, and here the system's one licence for colour applies.
319
+ *
320
+ * `success` and `failure` are bound to `--ok` and `--danger` — the two tokens
321
+ * this design system reserves for state and refuses to spend on brand. Every
322
+ * other kind is carried by shape: a start is a filled cap, a decision is a
323
+ * diamond, a wait is dashed, an external is a plate with a cut corner.
324
+ */
325
+ type LifecycleStateKind = 'start' | 'active' | 'waiting' | 'decision' | 'success' | 'failure' | 'neutral' | 'external';
326
+ interface LifecycleLane {
327
+ id: string;
328
+ label: string;
329
+ }
330
+ interface LifecycleState extends NodeBase {
331
+ type: LifecycleStateKind;
332
+ /** A step number printed in the corner — `01`, `02a`. */
333
+ step?: string;
334
+ lane?: string;
335
+ col: number;
336
+ yOffset?: number;
337
+ }
338
+ interface LifecycleTransition extends EdgeBase {
339
+ /** A second line under the label: the condition, the timeout, the retry count. */
340
+ note?: string;
341
+ }
342
+ interface LifecycleSpec {
343
+ diagram_type?: 'lifecycle';
344
+ meta: DiagramMeta;
345
+ lanes?: LifecycleLane[];
346
+ states: LifecycleState[];
347
+ transitions?: LifecycleTransition[];
348
+ cards?: DiagramCard[];
349
+ }
350
+ /** Any of the five, for a call site that dispatches on `diagram_type`. */
351
+ type DiagramSpecAny = ArchitectureSpec | WorkflowSpec | SequenceSpec | DataflowSpec | LifecycleSpec;
352
+
353
+ export type { ArchitectureBoundary, ArchitectureComponent, ArchitectureSpec, DataflowEdge, DataflowNode, DataflowSpec, DataflowStage, DiagramCard, DiagramMeta, DiagramSpecAny, DiagramView, EdgeBase, LegendMode, LifecycleLane, LifecycleSpec, LifecycleState, LifecycleStateKind, LifecycleTransition, NodeBase, NodeKind, Point, RouteMode, SequenceActivation, SequenceMessage, SequenceParticipant, SequenceSegment, SequenceSpec, Side, SourceRef, Variant, WorkflowEdge, WorkflowEdgeRole, WorkflowGroup, WorkflowLane, WorkflowNode, WorkflowPhase, WorkflowSpec };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Taking an `<svg>` off the page as a file.
3
+ *
4
+ * Shared by two entries that both draw in SVG and both need to hand a reader a
5
+ * picture of it: `@misoto22/design/charts` and `@misoto22/design/diagrams`.
6
+ * Everything here is engine-agnostic — nothing knows what a series or a node
7
+ * is — so the parts that DO know (which `<svg>` inside a wrapper is the plot,
8
+ * what the file should be called, what sits behind it) stay with the caller.
9
+ *
10
+ * Nothing touches the DOM at module scope. Every browser API — `document`,
11
+ * `canvas`, `URL.createObjectURL` — is reached for inside a function body, so
12
+ * this module imports cleanly into a server component and only fails when a
13
+ * consumer actually asks for a file on a server, which is a call that cannot
14
+ * succeed anywhere.
15
+ */
16
+ /**
17
+ * Copies the resolved paint of every node in `live` onto the matching node in
18
+ * `clone`.
19
+ *
20
+ * ## The problem this function exists to solve
21
+ *
22
+ * Serialising an `<svg>` and handing it to an `<img>` puts it in a document of
23
+ * its own. That document has none of the page's stylesheets, so every
24
+ * `var(--ink)`, `var(--rule-2)` and `var(--diagram-node)` in the markup
25
+ * resolves to nothing — and a paint that resolves to nothing is not a fallback
26
+ * colour, it is an invisible mark. Serialise naively and you get an empty
27
+ * rectangle.
28
+ *
29
+ * ## The solution
30
+ *
31
+ * Walk the live tree and the clone in step, and on every node write
32
+ * `getComputedStyle`'s answer into an inline `style`. Computed values have
33
+ * already had `var()` substituted by the engine, so `fill: var(--ink)` arrives
34
+ * as `fill: rgb(16, 16, 16)` — and, because the walk reads the LIVE element, it
35
+ * reads it under whichever theme is currently applied. Light and dark come out
36
+ * right for free, with no theme argument anywhere in this file.
37
+ *
38
+ * `<stop>` and `<marker>` children are walked too, which is the half that is
39
+ * easy to miss: a gradient's stops and an arrowhead's fill carry custom
40
+ * properties of their own, and a `<defs>` that lost its paint takes every line
41
+ * ending with it.
42
+ */
43
+ declare function inlinePaint(live: Element, clone: Element): void;
44
+ /**
45
+ * A custom property, resolved against the live element.
46
+ *
47
+ * `var()` inside a custom property is substituted at computed-value time, so
48
+ * `--diagram-surface: var(--paper)` reads back as a colour. The guard is for
49
+ * the case where it did not — an unregistered name, or a value the engine left
50
+ * as a token stream — because writing `var(--paper)` into a serialised SVG
51
+ * paints nothing at all.
52
+ */
53
+ declare function readToken(element: Element, name: string, fallback: string): string;
54
+ interface SerializeOptions {
55
+ /** Space around the artwork, in user units. Defaults to 16. */
56
+ padding?: number;
57
+ /** Painted behind the artwork. Omit for a transparent ground. */
58
+ background?: string;
59
+ /** A band above the artwork carrying this text. */
60
+ title?: string;
61
+ /** Paint for the title. Defaults to `#101010`. */
62
+ titleColor?: string;
63
+ /** Family for the title. Defaults to `sans-serif`. */
64
+ titleFont?: string;
65
+ /**
66
+ * Forces the output box, letterboxing the artwork inside it.
67
+ *
68
+ * This is what a share card is: a fixed 1200×630 frame that the diagram is
69
+ * fitted into, rather than a frame the diagram sizes. Omit it and the output
70
+ * is the artwork plus its padding.
71
+ */
72
+ frame?: {
73
+ width: number;
74
+ height: number;
75
+ };
76
+ }
77
+ /**
78
+ * One standalone `<svg>` document: the element's own artwork, repainted with
79
+ * resolved colours, on an optional plate, under an optional title.
80
+ *
81
+ * Returns markup rather than a blob because the SVG export wants the string
82
+ * and every raster export wants it as a data source — one serialisation, four
83
+ * file formats.
84
+ *
85
+ * ## What this cannot capture, honestly
86
+ *
87
+ * - **Anything drawn in HTML.** A toolbar, a tooltip, an inspector panel are
88
+ * DOM, not SVG. The file is the artwork.
89
+ * - **Web fonts.** The isolated document cannot fetch the page's `@font-face`
90
+ * sources, so text is rasterised in whatever the resolved family stack finds
91
+ * locally. Type metrics will differ from the screen.
92
+ * - **Cross-origin images.** An `<image>` from another origin taints the canvas
93
+ * and `toBlob` throws; there is no way around it from script.
94
+ */
95
+ declare function serializeSvg(svg: SVGSVGElement, options?: SerializeOptions): string;
96
+ /** The raster formats a browser canvas can encode. */
97
+ type RasterFormat = 'png' | 'jpeg' | 'webp';
98
+ interface RasterOptions {
99
+ /** Multiplier on the on-screen size. 2 is a retina-sharp export. */
100
+ scale?: number;
101
+ /** Encoder quality for `jpeg` and `webp`, 0–1. Ignored by `png`. */
102
+ quality?: number;
103
+ /**
104
+ * Painted behind the image, for a format with no alpha channel.
105
+ *
106
+ * JPEG has no transparency, so a diagram serialised without a plate lands on
107
+ * black. Passing the page's own surface here is what makes the file look
108
+ * like the screen rather than like a negative.
109
+ */
110
+ background?: string;
111
+ }
112
+ /**
113
+ * SVG markup, rasterised through a canvas.
114
+ *
115
+ * This is the browser's own SVG rasteriser re-drawing the markup, not a
116
+ * screenshot: antialiasing, `backdrop-filter` and any effect a page stylesheet
117
+ * applied from outside the `<svg>` are not in it.
118
+ */
119
+ declare function rasterize(markup: string, format: RasterFormat, options?: RasterOptions): Promise<Blob>;
120
+ /**
121
+ * Hands a blob to the browser as a download.
122
+ *
123
+ * Throws rather than no-oping without a `document`: a download is something a
124
+ * person just asked for, and a silent return would look to them like a click
125
+ * that did nothing.
126
+ */
127
+ declare function downloadBlob(blob: Blob, filename: string): void;
128
+ /**
129
+ * A title as a filename.
130
+ *
131
+ * Lowercase, ASCII-safe and hyphenated, because a downloaded file crosses into
132
+ * shells, zip archives and Windows paths where a title's spaces and slashes are
133
+ * someone else's problem. A title with no ASCII in it at all — a Chinese one,
134
+ * say — slugs to nothing, so the caller's fallback is what names the file.
135
+ */
136
+ declare function exportFilename(title: string, extension: string, fallback?: string): string;
137
+
138
+ export { type RasterFormat, type RasterOptions, type SerializeOptions, downloadBlob, exportFilename, inlinePaint, rasterize, readToken, serializeSvg };
@@ -0,0 +1,198 @@
1
+ const SVG_NS = "http://www.w3.org/2000/svg";
2
+ const PAINTED_PROPERTIES = [
3
+ "display",
4
+ "visibility",
5
+ "opacity",
6
+ "color",
7
+ "fill",
8
+ "fill-opacity",
9
+ "fill-rule",
10
+ "stroke",
11
+ "stroke-opacity",
12
+ "stroke-width",
13
+ "stroke-linecap",
14
+ "stroke-linejoin",
15
+ "stroke-dasharray",
16
+ "stroke-dashoffset",
17
+ "stop-color",
18
+ "stop-opacity",
19
+ "flood-color",
20
+ "flood-opacity",
21
+ "marker-start",
22
+ "marker-mid",
23
+ "marker-end",
24
+ "mask",
25
+ "filter",
26
+ "clip-path",
27
+ "clip-rule",
28
+ "paint-order",
29
+ "mix-blend-mode",
30
+ "shape-rendering",
31
+ "text-anchor",
32
+ "dominant-baseline",
33
+ "font-family",
34
+ "font-size",
35
+ "font-style",
36
+ "font-weight",
37
+ "letter-spacing",
38
+ "text-transform"
39
+ ];
40
+ function inlinePaint(live, clone) {
41
+ const computed = getComputedStyle(live);
42
+ let css = "";
43
+ for (const property of PAINTED_PROPERTIES) {
44
+ const value = computed.getPropertyValue(property);
45
+ if (value) css += `${property}:${value};`;
46
+ }
47
+ if (css) clone.setAttribute("style", css);
48
+ const children = live.children;
49
+ const clones = clone.children;
50
+ for (let index = 0; index < children.length; index += 1) {
51
+ const liveChild = children[index];
52
+ const cloneChild = clones[index];
53
+ if (liveChild && cloneChild) inlinePaint(liveChild, cloneChild);
54
+ }
55
+ }
56
+ function readToken(element, name, fallback) {
57
+ const value = getComputedStyle(element).getPropertyValue(name).trim();
58
+ return value && !value.includes("var(") ? value : fallback;
59
+ }
60
+ function serializeSvg(svg, options = {}) {
61
+ const { padding = 16, background, title, titleColor = "#101010", titleFont = "sans-serif" } = options;
62
+ const box = svg.getBoundingClientRect();
63
+ const width = Math.ceil(box.width) || Number(svg.getAttribute("width")) || 0;
64
+ const height = Math.ceil(box.height) || Number(svg.getAttribute("height")) || 0;
65
+ if (width === 0 || height === 0) {
66
+ throw new Error("serializeSvg: the artwork has no measured size yet \u2014 export it after it renders");
67
+ }
68
+ const clone = svg.cloneNode(true);
69
+ inlinePaint(svg, clone);
70
+ clone.setAttribute("width", String(width));
71
+ clone.setAttribute("height", String(height));
72
+ const titleBand = title ? 34 : 0;
73
+ const contentWidth = width + padding * 2;
74
+ const contentHeight = height + padding * 2 + titleBand;
75
+ const totalWidth = options.frame?.width ?? contentWidth;
76
+ const totalHeight = options.frame?.height ?? contentHeight;
77
+ const scale = options.frame ? Math.min(totalWidth / contentWidth, totalHeight / contentHeight, 1) : 1;
78
+ const offsetX = (totalWidth - contentWidth * scale) / 2;
79
+ const offsetY = (totalHeight - contentHeight * scale) / 2;
80
+ const page = document.createElementNS(SVG_NS, "svg");
81
+ page.setAttribute("xmlns", SVG_NS);
82
+ page.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
83
+ page.setAttribute("width", String(totalWidth));
84
+ page.setAttribute("height", String(totalHeight));
85
+ page.setAttribute("viewBox", `0 0 ${totalWidth} ${totalHeight}`);
86
+ if (background) {
87
+ const plate = document.createElementNS(SVG_NS, "rect");
88
+ plate.setAttribute("width", String(totalWidth));
89
+ plate.setAttribute("height", String(totalHeight));
90
+ plate.setAttribute("fill", background);
91
+ page.append(plate);
92
+ }
93
+ const stage = document.createElementNS(SVG_NS, "g");
94
+ stage.setAttribute("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
95
+ page.append(stage);
96
+ if (title) {
97
+ const caption = document.createElementNS(SVG_NS, "text");
98
+ caption.setAttribute("x", String(padding));
99
+ caption.setAttribute("y", String(padding + 15));
100
+ caption.setAttribute("fill", titleColor);
101
+ caption.setAttribute("font-size", "15");
102
+ caption.setAttribute("font-weight", "500");
103
+ caption.setAttribute("font-family", titleFont);
104
+ caption.textContent = title;
105
+ stage.append(caption);
106
+ }
107
+ const group = document.createElementNS(SVG_NS, "g");
108
+ group.setAttribute("transform", `translate(${padding}, ${padding + titleBand})`);
109
+ group.append(clone);
110
+ stage.append(group);
111
+ return `<?xml version="1.0" encoding="UTF-8"?>
112
+ ${new XMLSerializer().serializeToString(page)}`;
113
+ }
114
+ const MIME = {
115
+ png: "image/png",
116
+ jpeg: "image/jpeg",
117
+ webp: "image/webp"
118
+ };
119
+ async function rasterize(markup, format, options = {}) {
120
+ const { scale = 2, quality = 0.92, background } = options;
121
+ const url = URL.createObjectURL(new Blob([markup], { type: "image/svg+xml;charset=utf-8" }));
122
+ try {
123
+ const image = await loadImage(url);
124
+ const canvas = document.createElement("canvas");
125
+ canvas.width = Math.max(1, Math.ceil(image.width * scale));
126
+ canvas.height = Math.max(1, Math.ceil(image.height * scale));
127
+ const context = canvas.getContext("2d");
128
+ if (!context) throw new Error("rasterize: this browser gave back no 2D canvas context");
129
+ if (background) {
130
+ context.fillStyle = background;
131
+ context.fillRect(0, 0, canvas.width, canvas.height);
132
+ }
133
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
134
+ return await canvasToBlob(canvas, MIME[format], quality);
135
+ } finally {
136
+ URL.revokeObjectURL(url);
137
+ }
138
+ }
139
+ function loadImage(url) {
140
+ return new Promise((resolve, reject) => {
141
+ const image = new Image();
142
+ image.addEventListener("load", () => resolve(image));
143
+ image.addEventListener(
144
+ "error",
145
+ () => reject(new Error("rasterize: the browser refused to decode the serialised artwork"))
146
+ );
147
+ image.src = url;
148
+ });
149
+ }
150
+ function canvasToBlob(canvas, mime, quality) {
151
+ return new Promise((resolve, reject) => {
152
+ try {
153
+ canvas.toBlob(
154
+ (blob) => {
155
+ if (blob) resolve(blob);
156
+ else reject(new Error(`rasterize: the canvas produced no ${mime} data`));
157
+ },
158
+ mime,
159
+ quality
160
+ );
161
+ } catch (cause) {
162
+ reject(
163
+ new Error(
164
+ "rasterize: the canvas is tainted, which means the artwork contains a cross-origin image",
165
+ { cause }
166
+ )
167
+ );
168
+ }
169
+ });
170
+ }
171
+ function downloadBlob(blob, filename) {
172
+ if (typeof document === "undefined") {
173
+ throw new Error("downloadBlob: needs a browser document \u2014 call it from an event handler");
174
+ }
175
+ const url = URL.createObjectURL(blob);
176
+ const anchor = document.createElement("a");
177
+ anchor.href = url;
178
+ anchor.download = filename;
179
+ anchor.rel = "noopener";
180
+ anchor.style.display = "none";
181
+ document.body.append(anchor);
182
+ anchor.click();
183
+ anchor.remove();
184
+ setTimeout(() => URL.revokeObjectURL(url), 0);
185
+ }
186
+ function exportFilename(title, extension, fallback = "figure") {
187
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
188
+ return `${slug || fallback}.${extension}`;
189
+ }
190
+ export {
191
+ downloadBlob,
192
+ exportFilename,
193
+ inlinePaint,
194
+ rasterize,
195
+ readToken,
196
+ serializeSvg
197
+ };
198
+ //# sourceMappingURL=svg-export.js.map