@misoto22/design 0.6.1 → 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 +1 -1
  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 +1 -1
  11. package/dist/agent/BarList.md +1 -1
  12. package/dist/agent/BigNumber.md +1 -1
  13. package/dist/agent/BoxPlot.md +1 -1
  14. package/dist/agent/Breadcrumb.md +1 -1
  15. package/dist/agent/BulletChart.md +1 -1
  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 +1 -1
  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 +1 -1
  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 +1 -1
  43. package/dist/agent/Heatmap.md +1 -1
  44. package/dist/agent/Histogram.md +1 -1
  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 +1 -1
  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 +1 -1
  54. package/dist/agent/Popover.md +1 -1
  55. package/dist/agent/Progress.md +1 -1
  56. package/dist/agent/RadarChart.md +1 -1
  57. package/dist/agent/RadialChart.md +1 -1
  58. package/dist/agent/RadioGroup.md +1 -1
  59. package/dist/agent/SankeyChart.md +1 -1
  60. package/dist/agent/ScatterChart.md +1 -1
  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 +1 -1
  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 +1 -1
  83. package/dist/agent/WaterfallChart.md +1 -1
  84. package/dist/agent/WorkflowFigure.md +28 -0
  85. package/dist/agent/catalog.json +223 -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 +21 -7
@@ -0,0 +1,19 @@
1
+ export * from "./ArchitectureFigure/ArchitectureFigure.js";
2
+ export * from "./WorkflowFigure/WorkflowFigure.js";
3
+ export * from "./SequenceFigure/SequenceFigure.js";
4
+ export * from "./DataflowFigure/DataflowFigure.js";
5
+ export * from "./LifecycleFigure/LifecycleFigure.js";
6
+ export * from "./DiagramCanvas/DiagramCanvas.js";
7
+ export * from "./DiagramToolbar/DiagramToolbar.js";
8
+ export * from "./DiagramExportMenu/DiagramExportMenu.js";
9
+ export * from "./DiagramInspector/DiagramInspector.js";
10
+ export * from "./DiagramMinimap/DiagramMinimap.js";
11
+ export * from "./DiagramLegend/DiagramLegend.js";
12
+ import { kindLegend, resolveLegend, stateLegend, variantLegend } from "./lib/legend.js";
13
+ export {
14
+ kindLegend,
15
+ resolveLegend,
16
+ stateLegend,
17
+ variantLegend
18
+ };
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/diagrams/index.ts"],"sourcesContent":["/**\n * @misoto22/design/diagrams — the public entry.\n *\n * Five renderers for the five diagram shapes software actually needs, plus the\n * chrome a reader explores one with. They ship from their own entry point\n * rather than from the package root for one reason: a page that renders a Badge\n * should not be paying for a routing engine. Nothing under `src/components`\n * imports anything here, so the main barrel stays the size it was.\n *\n * The specification types mirror\n * [archify](https://github.com/tt-a1i/archify)'s published JSON schemas, so a\n * document authored for that tool renders here with no translation step — in\n * this system's own monochrome terms rather than in archify's palette.\n *\n * The look ships with the rest of the package:\n *\n * import '@misoto22/design/styles.css'\n *\n * Everything below is a consumer contract. Adding an export is cheap; changing\n * or removing one is a breaking change (DESIGN-API-001).\n */\n\n// ─── Specifications ───\nexport type {\n ArchitectureBoundary,\n ArchitectureComponent,\n ArchitectureSpec,\n DataflowEdge,\n DataflowNode,\n DataflowSpec,\n DataflowStage,\n DiagramCard,\n DiagramMeta,\n DiagramSpecAny,\n DiagramView,\n EdgeBase,\n LegendMode,\n LifecycleLane,\n LifecycleSpec,\n LifecycleState,\n LifecycleStateKind,\n LifecycleTransition,\n NodeBase,\n NodeKind,\n Point,\n RouteMode,\n SequenceActivation,\n SequenceMessage,\n SequenceParticipant,\n SequenceSegment,\n SequenceSpec,\n Side,\n SourceRef,\n Variant,\n WorkflowEdge,\n WorkflowEdgeRole,\n WorkflowGroup,\n WorkflowLane,\n WorkflowNode,\n WorkflowPhase,\n WorkflowSpec,\n} from './spec'\n\n// ─── Figures ───\nexport * from './ArchitectureFigure/ArchitectureFigure'\nexport * from './WorkflowFigure/WorkflowFigure'\nexport * from './SequenceFigure/SequenceFigure'\nexport * from './DataflowFigure/DataflowFigure'\nexport * from './LifecycleFigure/LifecycleFigure'\n\n// ─── Chrome ───\nexport * from './DiagramCanvas/DiagramCanvas'\nexport * from './DiagramToolbar/DiagramToolbar'\nexport * from './DiagramExportMenu/DiagramExportMenu'\nexport * from './DiagramInspector/DiagramInspector'\nexport * from './DiagramMinimap/DiagramMinimap'\nexport * from './DiagramLegend/DiagramLegend'\n\n// ─── Building a key ───\nexport { kindLegend, resolveLegend, stateLegend, variantLegend } from './lib/legend'\nexport type { LegendEntry } from './lib/legend'\n\n// ─── Shared chrome types ───\nexport type { FigureChrome } from './lib/frame'\n"],"mappings":"AAgEA,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAGd,SAAS,YAAY,eAAe,aAAa,qBAAqB;","names":[]}
@@ -0,0 +1,97 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { LegendMode, DiagramCard, DiagramMeta } from '../spec.js';
4
+ import { Box } from './geometry.js';
5
+
6
+ /**
7
+ * What a figure hands the frame around it.
8
+ *
9
+ * Five renderers produce this one shape, which is what lets the title, the
10
+ * accessible summary, the key and the conclusion cards be written once. A
11
+ * renderer's only job is placing marks; everything a reader needs AROUND the
12
+ * picture is the same question in all five cases.
13
+ */
14
+ interface FigureModel {
15
+ /** The extent the artwork was drawn into. */
16
+ extent: Box;
17
+ artwork: ReactNode;
18
+ /** Every drawn box, for the summary list. */
19
+ nodes: {
20
+ id: string;
21
+ label: string;
22
+ sublabel?: string;
23
+ kind?: string;
24
+ }[];
25
+ /** Every drawn line, for the summary list. */
26
+ edges: {
27
+ from: string;
28
+ to: string;
29
+ label?: string;
30
+ }[];
31
+ /** The kinds actually used, and how each is drawn. */
32
+ legend: {
33
+ key: string;
34
+ label: string;
35
+ sample: ReactNode;
36
+ }[];
37
+ }
38
+ interface FigureChrome {
39
+ className?: string;
40
+ /** Prints the key under the figure. `auto` shows only the kinds in use. */
41
+ legend?: LegendMode;
42
+ /** Prints the conclusion cards under the figure. Defaults to true. */
43
+ cards?: boolean;
44
+ /** Prints the title and subtitle over the figure. Defaults to true. */
45
+ heading?: boolean;
46
+ /** Node ids currently lit. Everything else is dimmed. */
47
+ activeIds?: string[];
48
+ /**
49
+ * Called when a reader picks a node.
50
+ *
51
+ * Giving this turns the summary list from a description into a control: the
52
+ * same list a screen reader reads becomes the keyboard's route to selection,
53
+ * and the plates in the picture become the pointer's route to the same thing.
54
+ */
55
+ onSelectNode?: (id: string) => void;
56
+ }
57
+ /**
58
+ * The shell every figure sits in: a heading, the picture, a key, the cards, and
59
+ * the part that makes the picture mean anything to a reader who cannot see it.
60
+ *
61
+ * ## The accessibility decision, stated rather than implied
62
+ *
63
+ * The `<svg>` carries `role="img"` and a label, which makes its whole subtree
64
+ * presentational. That is the honest description of what it is — a picture —
65
+ * and it is what stops a screen reader from walking two hundred `<text>`
66
+ * elements in drawing order, which is neither reading order nor useful.
67
+ *
68
+ * The diagram's CONTENT is then published beside it as an ordinary list: every
69
+ * node with what kind of thing it is, and every relationship as "A → B: over
70
+ * HTTPS". That list is where the meaning lives for anyone not looking at the
71
+ * picture, and it is why this component takes a model rather than markup — a
72
+ * frame handed a finished `<svg>` could not have written it.
73
+ *
74
+ * The list is visually hidden by default and NOT hidden from assistive
75
+ * technology, which is the opposite of what `aria-hidden` would do and the
76
+ * whole point.
77
+ */
78
+ declare function DiagramFrame({ meta, model, cards, className, legend, showCards, heading, activeIds, onSelectNode, }: {
79
+ meta: DiagramMeta;
80
+ model: FigureModel;
81
+ cards?: DiagramCard[];
82
+ showCards?: boolean;
83
+ } & Omit<FigureChrome, 'cards'>): react.JSX.Element;
84
+ /**
85
+ * The conclusion cards under a figure.
86
+ *
87
+ * Archify keys these by hue — cyan, emerald, rose. Here the key selects a MARK:
88
+ * a filled square, a ring, a bar, a diagonal. Seven cards in a row stay seven
89
+ * distinguishable cards on paper-white, and the same row survives being
90
+ * printed.
91
+ */
92
+ declare function CardGrid({ cards, className }: {
93
+ cards: DiagramCard[];
94
+ className?: string;
95
+ }): react.JSX.Element;
96
+
97
+ export { CardGrid, DiagramFrame, type FigureChrome, type FigureModel };
@@ -0,0 +1,116 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useId } from "react";
4
+ import { cn } from "../../lib/cn.js";
5
+ import { viewBoxOf } from "./geometry.js";
6
+ import { DiagramLegend } from "../DiagramLegend/DiagramLegend.js";
7
+ import { DiagramDefs } from "./marks.js";
8
+ function DiagramFrame({
9
+ meta,
10
+ model,
11
+ cards,
12
+ className,
13
+ legend = "auto",
14
+ showCards = true,
15
+ heading = true,
16
+ activeIds,
17
+ onSelectNode
18
+ }) {
19
+ const uid = useId().replace(/:/g, "");
20
+ const titleId = `${uid}-title`;
21
+ const descId = `${uid}-desc`;
22
+ const byId = new Map(model.nodes.map((node) => [node.id, node]));
23
+ const description = `${model.nodes.length} elements and ${model.edges.length} relationships. ${meta.subtitle ?? ""}`.trim();
24
+ const showLegend = legend !== "hidden" && model.legend.length > 0;
25
+ return /* @__PURE__ */ jsxs("figure", { className: cn("m-0 flex flex-col gap-5", className), children: [
26
+ heading && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
27
+ /* @__PURE__ */ jsx("h3", { className: "m-0 font-heading text-[length:var(--fs-sub)] font-normal leading-[1.15] tracking-[-0.015em] text-(--ink)", children: meta.title }),
28
+ meta.subtitle && /* @__PURE__ */ jsx("span", { className: "mono-meta text-(--ink-3-aa)", children: meta.subtitle })
29
+ ] }),
30
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--diagram-surface) p-[clamp(0.75rem,calc(1.6*var(--fluid)),1.25rem)] scroll-slim", children: /* @__PURE__ */ jsxs(
31
+ "svg",
32
+ {
33
+ viewBox: viewBoxOf(model.extent),
34
+ role: "img",
35
+ "aria-labelledby": `${titleId} ${descId}`,
36
+ className: "block h-auto w-full max-w-full font-sans",
37
+ "data-diagram-artwork": "",
38
+ children: [
39
+ /* @__PURE__ */ jsx("title", { id: titleId, children: meta.title }),
40
+ /* @__PURE__ */ jsx("desc", { id: descId, children: description }),
41
+ /* @__PURE__ */ jsx(DiagramDefs, { uid }),
42
+ model.artwork
43
+ ]
44
+ }
45
+ ) }),
46
+ /* @__PURE__ */ jsxs("div", { className: "sr-only", children: [
47
+ /* @__PURE__ */ jsx("p", { children: description }),
48
+ /* @__PURE__ */ jsx("ul", { children: model.nodes.map((node) => /* @__PURE__ */ jsx("li", { children: onSelectNode ? /* @__PURE__ */ jsx("button", { type: "button", onClick: () => onSelectNode(node.id), children: summaryLine(node) }) : summaryLine(node) }, node.id)) }),
49
+ model.edges.length > 0 && /* @__PURE__ */ jsx("ul", { children: model.edges.map((edge, index) => /* @__PURE__ */ jsxs("li", { children: [
50
+ byId.get(edge.from)?.label ?? edge.from,
51
+ " \u2192 ",
52
+ byId.get(edge.to)?.label ?? edge.to,
53
+ edge.label ? `: ${edge.label}` : ""
54
+ ] }, index)) })
55
+ ] }),
56
+ showLegend && /* @__PURE__ */ jsx(DiagramLegend, { entries: model.legend }),
57
+ showCards && cards && cards.length > 0 && /* @__PURE__ */ jsx(CardGrid, { cards }),
58
+ activeIds && activeIds.length > 0 && /* @__PURE__ */ jsx("span", { className: "sr-only", role: "status", children: `${activeIds.length} highlighted` })
59
+ ] });
60
+ }
61
+ function summaryLine(node) {
62
+ const parts = [node.label];
63
+ if (node.kind) parts.push(`(${node.kind})`);
64
+ if (node.sublabel) parts.push(`\u2014 ${node.sublabel}`);
65
+ return parts.join(" ");
66
+ }
67
+ function CardGrid({ cards, className }) {
68
+ return /* @__PURE__ */ jsx(
69
+ "div",
70
+ {
71
+ className: cn(
72
+ "grid gap-3 sm:grid-cols-2 lg:grid-cols-3",
73
+ className
74
+ ),
75
+ children: cards.map((card) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2.5 border-t border-(--ink) pt-3", children: [
76
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
77
+ /* @__PURE__ */ jsx(CardMark, { shape: card.dot }),
78
+ /* @__PURE__ */ jsx("span", { className: "font-sans text-[13.5px] leading-[1.35] text-(--ink)", children: card.title })
79
+ ] }),
80
+ /* @__PURE__ */ jsx("ul", { className: "m-0 flex list-none flex-col gap-1.5 p-0", children: card.items.map((item, index) => /* @__PURE__ */ jsx(
81
+ "li",
82
+ {
83
+ className: "text-[12.5px] leading-[1.55] text-(--ink-2) ps-3 -indent-3 before:content-['\u2014'] before:pe-1.5 before:text-(--ink-3-aa)",
84
+ children: item
85
+ },
86
+ index
87
+ )) })
88
+ ] }, card.title))
89
+ }
90
+ );
91
+ }
92
+ function CardMark({ shape }) {
93
+ const common = "size-2.5 shrink-0";
94
+ switch (shape) {
95
+ case "cyan":
96
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(common, "bg-(--ink)") });
97
+ case "emerald":
98
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(common, "rounded-full bg-(--ink)") });
99
+ case "violet":
100
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(common, "rounded-full border border-(--ink)") });
101
+ case "amber":
102
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(common, "border border-(--ink)") });
103
+ case "rose":
104
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(common, "rotate-45 bg-(--ink)") });
105
+ case "orange":
106
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(common, "rotate-45 border border-(--ink)") });
107
+ case "slate":
108
+ default:
109
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn("h-1 w-2.5 shrink-0 bg-(--ink-3-aa)") });
110
+ }
111
+ }
112
+ export {
113
+ CardGrid,
114
+ DiagramFrame
115
+ };
116
+ //# sourceMappingURL=frame.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/diagrams/lib/frame.tsx"],"sourcesContent":["'use client'\n\nimport { useId, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\nimport type { DiagramCard, DiagramMeta, LegendMode } from '../spec'\nimport { viewBoxOf, type Box } from './geometry'\nimport { DiagramLegend } from '../DiagramLegend/DiagramLegend'\nimport { DiagramDefs } from './marks'\n\n/**\n * What a figure hands the frame around it.\n *\n * Five renderers produce this one shape, which is what lets the title, the\n * accessible summary, the key and the conclusion cards be written once. A\n * renderer's only job is placing marks; everything a reader needs AROUND the\n * picture is the same question in all five cases.\n */\nexport interface FigureModel {\n /** The extent the artwork was drawn into. */\n extent: Box\n artwork: ReactNode\n /** Every drawn box, for the summary list. */\n nodes: { id: string; label: string; sublabel?: string; kind?: string }[]\n /** Every drawn line, for the summary list. */\n edges: { from: string; to: string; label?: string }[]\n /** The kinds actually used, and how each is drawn. */\n legend: { key: string; label: string; sample: ReactNode }[]\n}\n\nexport interface FigureChrome {\n className?: string\n /** Prints the key under the figure. `auto` shows only the kinds in use. */\n legend?: LegendMode\n /** Prints the conclusion cards under the figure. Defaults to true. */\n cards?: boolean\n /** Prints the title and subtitle over the figure. Defaults to true. */\n heading?: boolean\n /** Node ids currently lit. Everything else is dimmed. */\n activeIds?: string[]\n /**\n * Called when a reader picks a node.\n *\n * Giving this turns the summary list from a description into a control: the\n * same list a screen reader reads becomes the keyboard's route to selection,\n * and the plates in the picture become the pointer's route to the same thing.\n */\n onSelectNode?: (id: string) => void\n}\n\n/**\n * The shell every figure sits in: a heading, the picture, a key, the cards, and\n * the part that makes the picture mean anything to a reader who cannot see it.\n *\n * ## The accessibility decision, stated rather than implied\n *\n * The `<svg>` carries `role=\"img\"` and a label, which makes its whole subtree\n * presentational. That is the honest description of what it is — a picture —\n * and it is what stops a screen reader from walking two hundred `<text>`\n * elements in drawing order, which is neither reading order nor useful.\n *\n * The diagram's CONTENT is then published beside it as an ordinary list: every\n * node with what kind of thing it is, and every relationship as \"A → B: over\n * HTTPS\". That list is where the meaning lives for anyone not looking at the\n * picture, and it is why this component takes a model rather than markup — a\n * frame handed a finished `<svg>` could not have written it.\n *\n * The list is visually hidden by default and NOT hidden from assistive\n * technology, which is the opposite of what `aria-hidden` would do and the\n * whole point.\n */\nexport function DiagramFrame({\n meta,\n model,\n cards,\n className,\n legend = 'auto',\n showCards = true,\n heading = true,\n activeIds,\n onSelectNode,\n}: {\n meta: DiagramMeta\n model: FigureModel\n cards?: DiagramCard[]\n showCards?: boolean\n} & Omit<FigureChrome, 'cards'>) {\n const uid = useId().replace(/:/g, '')\n const titleId = `${uid}-title`\n const descId = `${uid}-desc`\n\n const byId = new Map(model.nodes.map((node) => [node.id, node]))\n const description = `${model.nodes.length} elements and ${model.edges.length} relationships. ${\n meta.subtitle ?? ''\n }`.trim()\n\n const showLegend = legend !== 'hidden' && model.legend.length > 0\n\n return (\n <figure className={cn('m-0 flex flex-col gap-5', className)}>\n {/* The title is set in the editorial serif, which is the system's own\n answer to \"this is a heading\" — and it is what stops a figure from\n reading as a screenshot dropped into the page. The subtitle stays in\n the meta voice underneath it. */}\n {heading && (\n <div className=\"flex flex-col gap-1.5\">\n <h3 className=\"m-0 font-heading text-[length:var(--fs-sub)] font-normal leading-[1.15] tracking-[-0.015em] text-(--ink)\">\n {meta.title}\n </h3>\n {meta.subtitle && <span className=\"mono-meta text-(--ink-3-aa)\">{meta.subtitle}</span>}\n </div>\n )}\n\n <div className=\"overflow-x-auto rounded-(--radius-lg) border border-(--rule) bg-(--diagram-surface) p-[clamp(0.75rem,calc(1.6*var(--fluid)),1.25rem)] scroll-slim\">\n <svg\n viewBox={viewBoxOf(model.extent)}\n role=\"img\"\n aria-labelledby={`${titleId} ${descId}`}\n className=\"block h-auto w-full max-w-full font-sans\"\n data-diagram-artwork=\"\"\n >\n <title id={titleId}>{meta.title}</title>\n <desc id={descId}>{description}</desc>\n <DiagramDefs uid={uid} />\n {model.artwork}\n </svg>\n </div>\n\n {/* The picture's content, for anyone not looking at the picture. */}\n <div className=\"sr-only\">\n <p>{description}</p>\n <ul>\n {model.nodes.map((node) => (\n <li key={node.id}>\n {onSelectNode ? (\n <button type=\"button\" onClick={() => onSelectNode(node.id)}>\n {summaryLine(node)}\n </button>\n ) : (\n summaryLine(node)\n )}\n </li>\n ))}\n </ul>\n {model.edges.length > 0 && (\n <ul>\n {model.edges.map((edge, index) => (\n <li key={index}>\n {(byId.get(edge.from)?.label ?? edge.from)} → {(byId.get(edge.to)?.label ?? edge.to)}\n {edge.label ? `: ${edge.label}` : ''}\n </li>\n ))}\n </ul>\n )}\n </div>\n\n {showLegend && <DiagramLegend entries={model.legend} />}\n\n {showCards && cards && cards.length > 0 && <CardGrid cards={cards} />}\n\n {activeIds && activeIds.length > 0 && <span className=\"sr-only\" role=\"status\">{`${activeIds.length} highlighted`}</span>}\n </figure>\n )\n}\n\nfunction summaryLine(node: { label: string; sublabel?: string; kind?: string }): string {\n const parts = [node.label]\n if (node.kind) parts.push(`(${node.kind})`)\n if (node.sublabel) parts.push(`— ${node.sublabel}`)\n return parts.join(' ')\n}\n\n/**\n * The conclusion cards under a figure.\n *\n * Archify keys these by hue — cyan, emerald, rose. Here the key selects a MARK:\n * a filled square, a ring, a bar, a diagonal. Seven cards in a row stay seven\n * distinguishable cards on paper-white, and the same row survives being\n * printed.\n */\nexport function CardGrid({ cards, className }: { cards: DiagramCard[]; className?: string }) {\n return (\n <div\n className={cn(\n 'grid gap-3 sm:grid-cols-2 lg:grid-cols-3',\n className,\n )}\n >\n {cards.map((card) => (\n <div key={card.title} className=\"flex flex-col gap-2.5 border-t border-(--ink) pt-3\">\n <div className=\"flex items-center gap-2\">\n <CardMark shape={card.dot} />\n <span className=\"font-sans text-[13.5px] leading-[1.35] text-(--ink)\">{card.title}</span>\n </div>\n <ul className=\"m-0 flex list-none flex-col gap-1.5 p-0\">\n {card.items.map((item, index) => (\n <li\n key={index}\n className=\"text-[12.5px] leading-[1.55] text-(--ink-2) ps-3 -indent-3 before:content-['—'] before:pe-1.5 before:text-(--ink-3-aa)\"\n >\n {item}\n </li>\n ))}\n </ul>\n </div>\n ))}\n </div>\n )\n}\n\n/** Seven marks, one per archify dot name, and not one of them a hue. */\nfunction CardMark({ shape }: { shape: DiagramCard['dot'] }) {\n const common = 'size-2.5 shrink-0'\n switch (shape) {\n case 'cyan':\n return <span aria-hidden className={cn(common, 'bg-(--ink)')} />\n case 'emerald':\n return <span aria-hidden className={cn(common, 'rounded-full bg-(--ink)')} />\n case 'violet':\n return <span aria-hidden className={cn(common, 'rounded-full border border-(--ink)')} />\n case 'amber':\n return <span aria-hidden className={cn(common, 'border border-(--ink)')} />\n case 'rose':\n return <span aria-hidden className={cn(common, 'rotate-45 bg-(--ink)')} />\n case 'orange':\n return <span aria-hidden className={cn(common, 'rotate-45 border border-(--ink)')} />\n case 'slate':\n default:\n return <span aria-hidden className={cn('h-1 w-2.5 shrink-0 bg-(--ink-3-aa)')} />\n }\n}\n"],"mappings":";AAwGQ,SACE,KADF;AAtGR,SAAS,aAA6B;AACtC,SAAS,UAAU;AAEnB,SAAS,iBAA2B;AACpC,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AA+DrB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,UAAU;AAAA,EACV;AAAA,EACA;AACF,GAKiC;AAC/B,QAAM,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE;AACpC,QAAM,UAAU,GAAG,GAAG;AACtB,QAAM,SAAS,GAAG,GAAG;AAErB,QAAM,OAAO,IAAI,IAAI,MAAM,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;AAC/D,QAAM,cAAc,GAAG,MAAM,MAAM,MAAM,iBAAiB,MAAM,MAAM,MAAM,mBAC1E,KAAK,YAAY,EACnB,GAAG,KAAK;AAER,QAAM,aAAa,WAAW,YAAY,MAAM,OAAO,SAAS;AAEhE,SACE,qBAAC,YAAO,WAAW,GAAG,2BAA2B,SAAS,GAKvD;AAAA,eACC,qBAAC,SAAI,WAAU,yBACb;AAAA,0BAAC,QAAG,WAAU,4GACX,eAAK,OACR;AAAA,MACC,KAAK,YAAY,oBAAC,UAAK,WAAU,+BAA+B,eAAK,UAAS;AAAA,OACjF;AAAA,IAGF,oBAAC,SAAI,WAAU,qJACb;AAAA,MAAC;AAAA;AAAA,QACC,SAAS,UAAU,MAAM,MAAM;AAAA,QAC/B,MAAK;AAAA,QACL,mBAAiB,GAAG,OAAO,IAAI,MAAM;AAAA,QACrC,WAAU;AAAA,QACV,wBAAqB;AAAA,QAErB;AAAA,8BAAC,WAAM,IAAI,SAAU,eAAK,OAAM;AAAA,UAChC,oBAAC,UAAK,IAAI,QAAS,uBAAY;AAAA,UAC/B,oBAAC,eAAY,KAAU;AAAA,UACtB,MAAM;AAAA;AAAA;AAAA,IACT,GACF;AAAA,IAGA,qBAAC,SAAI,WAAU,WACb;AAAA,0BAAC,OAAG,uBAAY;AAAA,MAChB,oBAAC,QACE,gBAAM,MAAM,IAAI,CAAC,SAChB,oBAAC,QACE,yBACC,oBAAC,YAAO,MAAK,UAAS,SAAS,MAAM,aAAa,KAAK,EAAE,GACtD,sBAAY,IAAI,GACnB,IAEA,YAAY,IAAI,KANX,KAAK,EAQd,CACD,GACH;AAAA,MACC,MAAM,MAAM,SAAS,KACpB,oBAAC,QACE,gBAAM,MAAM,IAAI,CAAC,MAAM,UACtB,qBAAC,QACG;AAAA,aAAK,IAAI,KAAK,IAAI,GAAG,SAAS,KAAK;AAAA,QAAM;AAAA,QAAK,KAAK,IAAI,KAAK,EAAE,GAAG,SAAS,KAAK;AAAA,QAChF,KAAK,QAAQ,KAAK,KAAK,KAAK,KAAK;AAAA,WAF3B,KAGT,CACD,GACH;AAAA,OAEJ;AAAA,IAEC,cAAc,oBAAC,iBAAc,SAAS,MAAM,QAAQ;AAAA,IAEpD,aAAa,SAAS,MAAM,SAAS,KAAK,oBAAC,YAAS,OAAc;AAAA,IAElE,aAAa,UAAU,SAAS,KAAK,oBAAC,UAAK,WAAU,WAAU,MAAK,UAAU,aAAG,UAAU,MAAM,gBAAe;AAAA,KACnH;AAEJ;AAEA,SAAS,YAAY,MAAmE;AACtF,QAAM,QAAQ,CAAC,KAAK,KAAK;AACzB,MAAI,KAAK,KAAM,OAAM,KAAK,IAAI,KAAK,IAAI,GAAG;AAC1C,MAAI,KAAK,SAAU,OAAM,KAAK,UAAK,KAAK,QAAQ,EAAE;AAClD,SAAO,MAAM,KAAK,GAAG;AACvB;AAUO,SAAS,SAAS,EAAE,OAAO,UAAU,GAAiD;AAC3F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEC,gBAAM,IAAI,CAAC,SACV,qBAAC,SAAqB,WAAU,sDAC9B;AAAA,6BAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,YAAS,OAAO,KAAK,KAAK;AAAA,UAC3B,oBAAC,UAAK,WAAU,uDAAuD,eAAK,OAAM;AAAA,WACpF;AAAA,QACA,oBAAC,QAAG,WAAU,2CACX,eAAK,MAAM,IAAI,CAAC,MAAM,UACrB;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YAET;AAAA;AAAA,UAHI;AAAA,QAIP,CACD,GACH;AAAA,WAdQ,KAAK,KAef,CACD;AAAA;AAAA,EACH;AAEJ;AAGA,SAAS,SAAS,EAAE,MAAM,GAAkC;AAC1D,QAAM,SAAS;AACf,UAAQ,OAAO;AAAA,IACb,KAAK;AACH,aAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,QAAQ,YAAY,GAAG;AAAA,IAChE,KAAK;AACH,aAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,QAAQ,yBAAyB,GAAG;AAAA,IAC7E,KAAK;AACH,aAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,QAAQ,oCAAoC,GAAG;AAAA,IACxF,KAAK;AACH,aAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,QAAQ,uBAAuB,GAAG;AAAA,IAC3E,KAAK;AACH,aAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,QAAQ,sBAAsB,GAAG;AAAA,IAC1E,KAAK;AACH,aAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,QAAQ,iCAAiC,GAAG;AAAA,IACrF,KAAK;AAAA,IACL;AACE,aAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,oCAAoC,GAAG;AAAA,EAClF;AACF;","names":[]}
@@ -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":[]}