@misoto22/design 0.7.0 → 0.9.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 (398) hide show
  1. package/AGENTS.md +87 -0
  2. package/CLAUDE.md +1 -0
  3. package/README.md +50 -5
  4. package/bin/misoto22-design.mjs +59 -13
  5. package/dist/agent/Accordion.md +24 -1
  6. package/dist/agent/Alert.md +23 -1
  7. package/dist/agent/AppShell.md +30 -2
  8. package/dist/agent/ArchitectureFigure.md +25 -2
  9. package/dist/agent/AreaChart.md +27 -2
  10. package/dist/agent/Article.md +26 -3
  11. package/dist/agent/AspectRatio.md +57 -0
  12. package/dist/agent/Avatar.md +25 -5
  13. package/dist/agent/Badge.md +22 -1
  14. package/dist/agent/BarChart.md +29 -2
  15. package/dist/agent/BarList.md +27 -1
  16. package/dist/agent/BigNumber.md +26 -2
  17. package/dist/agent/BoxPlot.md +24 -1
  18. package/dist/agent/Breadcrumb.md +24 -2
  19. package/dist/agent/BulletChart.md +23 -1
  20. package/dist/agent/Button.md +25 -1
  21. package/dist/agent/Calendar.md +37 -3
  22. package/dist/agent/Card.md +24 -2
  23. package/dist/agent/Checkbox.md +23 -2
  24. package/dist/agent/Code.md +50 -0
  25. package/dist/agent/CodeBlock.md +74 -0
  26. package/dist/agent/Collapsible.md +22 -1
  27. package/dist/agent/ColorPicker.md +75 -0
  28. package/dist/agent/Combobox.md +29 -3
  29. package/dist/agent/Command.md +25 -2
  30. package/dist/agent/ComposedChart.md +26 -2
  31. package/dist/agent/ContextMenu.md +37 -4
  32. package/dist/agent/DataflowFigure.md +22 -1
  33. package/dist/agent/DatePicker.md +34 -6
  34. package/dist/agent/DescriptionList.md +68 -0
  35. package/dist/agent/Diagram.md +27 -3
  36. package/dist/agent/DiagramCanvas.md +24 -1
  37. package/dist/agent/DiagramExportMenu.md +29 -3
  38. package/dist/agent/DiagramInspector.md +23 -1
  39. package/dist/agent/DiagramLegend.md +22 -1
  40. package/dist/agent/DiagramMinimap.md +24 -4
  41. package/dist/agent/DiagramToolbar.md +23 -1
  42. package/dist/agent/Dialog.md +26 -3
  43. package/dist/agent/DropdownMenu.md +45 -6
  44. package/dist/agent/EmptyState.md +26 -1
  45. package/dist/agent/ErrorState.md +27 -2
  46. package/dist/agent/Facet.md +25 -1
  47. package/dist/agent/Field.md +45 -4
  48. package/dist/agent/FigureBand.md +24 -1
  49. package/dist/agent/FloatingIconButton.md +23 -1
  50. package/dist/agent/FunnelChart.md +23 -1
  51. package/dist/agent/Heading.md +64 -0
  52. package/dist/agent/Heatmap.md +25 -1
  53. package/dist/agent/Histogram.md +26 -2
  54. package/dist/agent/Input.md +22 -1
  55. package/dist/agent/Kbd.md +19 -1
  56. package/dist/agent/LifecycleFigure.md +22 -1
  57. package/dist/agent/LineChart.md +26 -2
  58. package/dist/agent/LinkArrow.md +20 -1
  59. package/dist/agent/Markdown.md +68 -0
  60. package/dist/agent/NativeSelect.md +23 -2
  61. package/dist/agent/NavItem.md +21 -1
  62. package/dist/agent/NumberField.md +72 -0
  63. package/dist/agent/Pagination.md +28 -2
  64. package/dist/agent/PieChart.md +26 -2
  65. package/dist/agent/Popover.md +23 -1
  66. package/dist/agent/Progress.md +27 -3
  67. package/dist/agent/RadarChart.md +26 -2
  68. package/dist/agent/RadialChart.md +26 -3
  69. package/dist/agent/RadioGroup.md +23 -2
  70. package/dist/agent/SankeyChart.md +25 -2
  71. package/dist/agent/ScatterChart.md +27 -2
  72. package/dist/agent/ScrollArea.md +24 -3
  73. package/dist/agent/SearchableMenu.md +23 -1
  74. package/dist/agent/Select.md +29 -3
  75. package/dist/agent/Separator.md +28 -4
  76. package/dist/agent/SequenceFigure.md +23 -1
  77. package/dist/agent/Sheet.md +25 -2
  78. package/dist/agent/Sidebar.md +193 -0
  79. package/dist/agent/Skeleton.md +24 -2
  80. package/dist/agent/Slider.md +31 -4
  81. package/dist/agent/Sparkline.md +23 -1
  82. package/dist/agent/Spinner.md +24 -2
  83. package/dist/agent/StatusDot.md +20 -1
  84. package/dist/agent/StatusPill.md +28 -2
  85. package/dist/agent/Steps.md +24 -1
  86. package/dist/agent/Switch.md +21 -1
  87. package/dist/agent/Table.md +28 -3
  88. package/dist/agent/Tabs.md +24 -2
  89. package/dist/agent/Tag.md +40 -9
  90. package/dist/agent/Text.md +65 -0
  91. package/dist/agent/Textarea.md +20 -1
  92. package/dist/agent/Timestamp.md +64 -0
  93. package/dist/agent/Toast.md +26 -2
  94. package/dist/agent/ToggleGroup.md +24 -2
  95. package/dist/agent/Toolbar.md +71 -0
  96. package/dist/agent/Tooltip.md +23 -1
  97. package/dist/agent/TreemapChart.md +24 -1
  98. package/dist/agent/WaterfallChart.md +24 -1
  99. package/dist/agent/WorkflowFigure.md +24 -2
  100. package/dist/agent/catalog.json +5915 -699
  101. package/dist/agent/index.md +20 -8
  102. package/dist/article.css +16 -4
  103. package/dist/charts/AreaChart/AreaChart.d.ts +9 -2
  104. package/dist/charts/AreaChart/AreaChart.js +12 -13
  105. package/dist/charts/AreaChart/AreaChart.js.map +1 -1
  106. package/dist/charts/BarChart/BarChart.d.ts +14 -3
  107. package/dist/charts/BarChart/BarChart.js +20 -12
  108. package/dist/charts/BarChart/BarChart.js.map +1 -1
  109. package/dist/charts/BarList/BarList.d.ts +9 -1
  110. package/dist/charts/BarList/BarList.js +9 -1
  111. package/dist/charts/BarList/BarList.js.map +1 -1
  112. package/dist/charts/BigNumber/BigNumber.d.ts +19 -3
  113. package/dist/charts/BigNumber/BigNumber.js +25 -5
  114. package/dist/charts/BigNumber/BigNumber.js.map +1 -1
  115. package/dist/charts/BoxPlot/BoxPlot.js +4 -2
  116. package/dist/charts/BoxPlot/BoxPlot.js.map +1 -1
  117. package/dist/charts/BulletChart/BulletChart.js +44 -9
  118. package/dist/charts/BulletChart/BulletChart.js.map +1 -1
  119. package/dist/charts/ComposedChart/ComposedChart.d.ts +9 -2
  120. package/dist/charts/ComposedChart/ComposedChart.js +10 -10
  121. package/dist/charts/ComposedChart/ComposedChart.js.map +1 -1
  122. package/dist/charts/FunnelChart/FunnelChart.d.ts +7 -2
  123. package/dist/charts/FunnelChart/FunnelChart.js +6 -16
  124. package/dist/charts/FunnelChart/FunnelChart.js.map +1 -1
  125. package/dist/charts/Heatmap/Heatmap.d.ts +17 -3
  126. package/dist/charts/Heatmap/Heatmap.js +20 -11
  127. package/dist/charts/Heatmap/Heatmap.js.map +1 -1
  128. package/dist/charts/Histogram/Histogram.d.ts +24 -1
  129. package/dist/charts/Histogram/Histogram.js +43 -11
  130. package/dist/charts/Histogram/Histogram.js.map +1 -1
  131. package/dist/charts/LineChart/LineChart.d.ts +9 -2
  132. package/dist/charts/LineChart/LineChart.js +11 -12
  133. package/dist/charts/LineChart/LineChart.js.map +1 -1
  134. package/dist/charts/PieChart/PieChart.d.ts +15 -3
  135. package/dist/charts/PieChart/PieChart.js +14 -6
  136. package/dist/charts/PieChart/PieChart.js.map +1 -1
  137. package/dist/charts/RadarChart/RadarChart.d.ts +15 -3
  138. package/dist/charts/RadarChart/RadarChart.js +15 -11
  139. package/dist/charts/RadarChart/RadarChart.js.map +1 -1
  140. package/dist/charts/RadialChart/RadialChart.d.ts +22 -4
  141. package/dist/charts/RadialChart/RadialChart.js +28 -6
  142. package/dist/charts/RadialChart/RadialChart.js.map +1 -1
  143. package/dist/charts/SankeyChart/SankeyChart.d.ts +16 -4
  144. package/dist/charts/SankeyChart/SankeyChart.js +14 -6
  145. package/dist/charts/SankeyChart/SankeyChart.js.map +1 -1
  146. package/dist/charts/ScatterChart/ScatterChart.d.ts +24 -3
  147. package/dist/charts/ScatterChart/ScatterChart.js +21 -15
  148. package/dist/charts/ScatterChart/ScatterChart.js.map +1 -1
  149. package/dist/charts/Sparkline/Sparkline.js +5 -3
  150. package/dist/charts/Sparkline/Sparkline.js.map +1 -1
  151. package/dist/charts/TreemapChart/TreemapChart.d.ts +7 -2
  152. package/dist/charts/TreemapChart/TreemapChart.js +20 -11
  153. package/dist/charts/TreemapChart/TreemapChart.js.map +1 -1
  154. package/dist/charts/WaterfallChart/WaterfallChart.js +4 -2
  155. package/dist/charts/WaterfallChart/WaterfallChart.js.map +1 -1
  156. package/dist/charts/lib/export.d.ts +1 -0
  157. package/dist/charts/lib/figure.d.ts +21 -1
  158. package/dist/charts/lib/figure.js +5 -2
  159. package/dist/charts/lib/figure.js.map +1 -1
  160. package/dist/charts/lib/scale.d.ts +23 -0
  161. package/dist/charts/lib/scale.js +13 -0
  162. package/dist/charts/lib/scale.js.map +1 -0
  163. package/dist/charts/lib/selection.d.ts +20 -0
  164. package/dist/charts/lib/selection.js +18 -0
  165. package/dist/charts/lib/selection.js.map +1 -0
  166. package/dist/charts/lib/toolbar.d.ts +1 -0
  167. package/dist/components/Accordion/Accordion.js +8 -1
  168. package/dist/components/Accordion/Accordion.js.map +1 -1
  169. package/dist/components/AppShell/AppShell.d.ts +6 -0
  170. package/dist/components/AppShell/AppShell.js +34 -4
  171. package/dist/components/AppShell/AppShell.js.map +1 -1
  172. package/dist/components/Article/Article.d.ts +11 -4
  173. package/dist/components/Article/Article.js +13 -0
  174. package/dist/components/Article/Article.js.map +1 -1
  175. package/dist/components/AspectRatio/AspectRatio.d.ts +46 -0
  176. package/dist/components/AspectRatio/AspectRatio.js +33 -0
  177. package/dist/components/AspectRatio/AspectRatio.js.map +1 -0
  178. package/dist/components/Avatar/Avatar.d.ts +12 -3
  179. package/dist/components/Avatar/Avatar.js +6 -1
  180. package/dist/components/Avatar/Avatar.js.map +1 -1
  181. package/dist/components/Breadcrumb/Breadcrumb.d.ts +6 -0
  182. package/dist/components/Breadcrumb/Breadcrumb.js +15 -0
  183. package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -1
  184. package/dist/components/Button/Button.js +14 -0
  185. package/dist/components/Button/Button.js.map +1 -1
  186. package/dist/components/Calendar/Calendar.d.ts +29 -3
  187. package/dist/components/Calendar/Calendar.js +60 -12
  188. package/dist/components/Calendar/Calendar.js.map +1 -1
  189. package/dist/components/Card/Card.d.ts +6 -0
  190. package/dist/components/Card/Card.js +1 -1
  191. package/dist/components/Card/Card.js.map +1 -1
  192. package/dist/components/Checkbox/Checkbox.d.ts +5 -2
  193. package/dist/components/Checkbox/Checkbox.js +12 -2
  194. package/dist/components/Checkbox/Checkbox.js.map +1 -1
  195. package/dist/components/Code/Code.d.ts +30 -0
  196. package/dist/components/Code/Code.js +21 -0
  197. package/dist/components/Code/Code.js.map +1 -0
  198. package/dist/components/CodeBlock/CodeBlock.d.ts +141 -0
  199. package/dist/components/CodeBlock/CodeBlock.js +123 -0
  200. package/dist/components/CodeBlock/CodeBlock.js.map +1 -0
  201. package/dist/components/Collapsible/Collapsible.js +10 -2
  202. package/dist/components/Collapsible/Collapsible.js.map +1 -1
  203. package/dist/components/ColorPicker/ColorPicker.d.ts +67 -0
  204. package/dist/components/ColorPicker/ColorPicker.js +334 -0
  205. package/dist/components/ColorPicker/ColorPicker.js.map +1 -0
  206. package/dist/components/Combobox/Combobox.d.ts +15 -1
  207. package/dist/components/Combobox/Combobox.js +25 -5
  208. package/dist/components/Combobox/Combobox.js.map +1 -1
  209. package/dist/components/Command/Command.d.ts +4 -2
  210. package/dist/components/Command/Command.js +14 -2
  211. package/dist/components/Command/Command.js.map +1 -1
  212. package/dist/components/ContextMenu/ContextMenu.d.ts +30 -5
  213. package/dist/components/ContextMenu/ContextMenu.js +33 -3
  214. package/dist/components/ContextMenu/ContextMenu.js.map +1 -1
  215. package/dist/components/DatePicker/DatePicker.d.ts +36 -4
  216. package/dist/components/DatePicker/DatePicker.js +55 -12
  217. package/dist/components/DatePicker/DatePicker.js.map +1 -1
  218. package/dist/components/DescriptionList/DescriptionList.d.ts +73 -0
  219. package/dist/components/DescriptionList/DescriptionList.js +35 -0
  220. package/dist/components/DescriptionList/DescriptionList.js.map +1 -0
  221. package/dist/components/Diagram/Diagram.d.ts +8 -3
  222. package/dist/components/Diagram/Diagram.js +82 -10
  223. package/dist/components/Diagram/Diagram.js.map +1 -1
  224. package/dist/components/Dialog/Dialog.d.ts +18 -3
  225. package/dist/components/Dialog/Dialog.js +21 -3
  226. package/dist/components/Dialog/Dialog.js.map +1 -1
  227. package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -11
  228. package/dist/components/DropdownMenu/DropdownMenu.js +33 -3
  229. package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -1
  230. package/dist/components/EmptyState/EmptyState.d.ts +25 -1
  231. package/dist/components/EmptyState/EmptyState.js +3 -1
  232. package/dist/components/EmptyState/EmptyState.js.map +1 -1
  233. package/dist/components/ErrorState/ErrorState.d.ts +22 -1
  234. package/dist/components/ErrorState/ErrorState.js +12 -3
  235. package/dist/components/ErrorState/ErrorState.js.map +1 -1
  236. package/dist/components/Field/Field.d.ts +70 -3
  237. package/dist/components/Field/Field.js +53 -12
  238. package/dist/components/Field/Field.js.map +1 -1
  239. package/dist/components/Field/field-control.d.ts +31 -0
  240. package/dist/components/Field/field-control.js +12 -0
  241. package/dist/components/Field/field-control.js.map +1 -0
  242. package/dist/components/FloatingIconButton/FloatingIconButton.js +2 -0
  243. package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -1
  244. package/dist/components/Heading/Heading.d.ts +62 -0
  245. package/dist/components/Heading/Heading.js +44 -0
  246. package/dist/components/Heading/Heading.js.map +1 -0
  247. package/dist/components/Markdown/Markdown.d.ts +139 -0
  248. package/dist/components/Markdown/Markdown.js +135 -0
  249. package/dist/components/Markdown/Markdown.js.map +1 -0
  250. package/dist/components/Markdown/inline.d.ts +29 -0
  251. package/dist/components/Markdown/inline.js +74 -0
  252. package/dist/components/Markdown/inline.js.map +1 -0
  253. package/dist/components/Markdown/nodes.d.ts +65 -0
  254. package/dist/components/Markdown/nodes.js +1 -0
  255. package/dist/components/Markdown/nodes.js.map +1 -0
  256. package/dist/components/Markdown/parse.d.ts +24 -0
  257. package/dist/components/Markdown/parse.js +110 -0
  258. package/dist/components/Markdown/parse.js.map +1 -0
  259. package/dist/components/Markdown/slug.d.ts +15 -0
  260. package/dist/components/Markdown/slug.js +7 -0
  261. package/dist/components/Markdown/slug.js.map +1 -0
  262. package/dist/components/NativeSelect/NativeSelect.d.ts +6 -0
  263. package/dist/components/NativeSelect/NativeSelect.js +2 -3
  264. package/dist/components/NativeSelect/NativeSelect.js.map +1 -1
  265. package/dist/components/NavItem/NavItem.js +1 -1
  266. package/dist/components/NavItem/NavItem.js.map +1 -1
  267. package/dist/components/NumberField/NumberField.d.ts +69 -0
  268. package/dist/components/NumberField/NumberField.js +162 -0
  269. package/dist/components/NumberField/NumberField.js.map +1 -0
  270. package/dist/components/Pagination/Pagination.d.ts +24 -1
  271. package/dist/components/Pagination/Pagination.js +6 -3
  272. package/dist/components/Pagination/Pagination.js.map +1 -1
  273. package/dist/components/Popover/Popover.js +1 -1
  274. package/dist/components/Popover/Popover.js.map +1 -1
  275. package/dist/components/Progress/Progress.d.ts +17 -5
  276. package/dist/components/Progress/Progress.js +18 -6
  277. package/dist/components/Progress/Progress.js.map +1 -1
  278. package/dist/components/RadioGroup/RadioGroup.d.ts +6 -0
  279. package/dist/components/RadioGroup/RadioGroup.js +11 -1
  280. package/dist/components/RadioGroup/RadioGroup.js.map +1 -1
  281. package/dist/components/ScrollArea/ScrollArea.d.ts +15 -0
  282. package/dist/components/ScrollArea/ScrollArea.js +2 -2
  283. package/dist/components/ScrollArea/ScrollArea.js.map +1 -1
  284. package/dist/components/SearchableMenu/SearchableMenu.d.ts +11 -2
  285. package/dist/components/SearchableMenu/SearchableMenu.js +24 -2
  286. package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -1
  287. package/dist/components/Select/Select.d.ts +18 -2
  288. package/dist/components/Select/Select.js +21 -4
  289. package/dist/components/Select/Select.js.map +1 -1
  290. package/dist/components/Separator/Separator.d.ts +21 -3
  291. package/dist/components/Separator/Separator.js +9 -1
  292. package/dist/components/Separator/Separator.js.map +1 -1
  293. package/dist/components/Sheet/Sheet.d.ts +3 -0
  294. package/dist/components/Sheet/Sheet.js +12 -3
  295. package/dist/components/Sheet/Sheet.js.map +1 -1
  296. package/dist/components/Sidebar/Sidebar.d.ts +349 -0
  297. package/dist/components/Sidebar/Sidebar.js +455 -0
  298. package/dist/components/Sidebar/Sidebar.js.map +1 -0
  299. package/dist/components/Skeleton/Skeleton.d.ts +8 -1
  300. package/dist/components/Skeleton/Skeleton.js +1 -1
  301. package/dist/components/Skeleton/Skeleton.js.map +1 -1
  302. package/dist/components/Slider/Slider.d.ts +42 -3
  303. package/dist/components/Slider/Slider.js +121 -30
  304. package/dist/components/Slider/Slider.js.map +1 -1
  305. package/dist/components/Spinner/Spinner.d.ts +8 -0
  306. package/dist/components/Spinner/Spinner.js +3 -2
  307. package/dist/components/Spinner/Spinner.js.map +1 -1
  308. package/dist/components/StatusPill/StatusPill.d.ts +5 -0
  309. package/dist/components/StatusPill/StatusPill.js +6 -0
  310. package/dist/components/StatusPill/StatusPill.js.map +1 -1
  311. package/dist/components/Table/Table.d.ts +20 -0
  312. package/dist/components/Table/Table.js +3 -1
  313. package/dist/components/Table/Table.js.map +1 -1
  314. package/dist/components/Tabs/Tabs.js +7 -1
  315. package/dist/components/Tabs/Tabs.js.map +1 -1
  316. package/dist/components/Tag/Tag.d.ts +68 -9
  317. package/dist/components/Tag/Tag.js +50 -10
  318. package/dist/components/Tag/Tag.js.map +1 -1
  319. package/dist/components/Text/Text.d.ts +62 -0
  320. package/dist/components/Text/Text.js +30 -0
  321. package/dist/components/Text/Text.js.map +1 -0
  322. package/dist/components/Timestamp/Timestamp.d.ts +59 -0
  323. package/dist/components/Timestamp/Timestamp.js +56 -0
  324. package/dist/components/Timestamp/Timestamp.js.map +1 -0
  325. package/dist/components/Toast/Toast.d.ts +9 -1
  326. package/dist/components/Toast/Toast.js +27 -4
  327. package/dist/components/Toast/Toast.js.map +1 -1
  328. package/dist/components/ToggleGroup/ToggleGroup.d.ts +6 -1
  329. package/dist/components/ToggleGroup/ToggleGroup.js +11 -1
  330. package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -1
  331. package/dist/components/Toolbar/Toolbar.d.ts +55 -0
  332. package/dist/components/Toolbar/Toolbar.js +43 -0
  333. package/dist/components/Toolbar/Toolbar.js.map +1 -0
  334. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +6 -0
  335. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +48 -8
  336. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -1
  337. package/dist/diagrams/DataflowFigure/DataflowFigure.js +21 -2
  338. package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -1
  339. package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +14 -0
  340. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +14 -7
  341. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -1
  342. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +38 -4
  343. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +14 -16
  344. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -1
  345. package/dist/diagrams/DiagramInspector/DiagramInspector.js +2 -2
  346. package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -1
  347. package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +23 -7
  348. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +45 -18
  349. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -1
  350. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +44 -12
  351. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -1
  352. package/dist/diagrams/SequenceFigure/SequenceFigure.js +35 -5
  353. package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -1
  354. package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +3 -1
  355. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +76 -28
  356. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -1
  357. package/dist/diagrams/index.d.ts +1 -0
  358. package/dist/diagrams/index.js +10 -0
  359. package/dist/diagrams/index.js.map +1 -1
  360. package/dist/diagrams/lib/dev.d.ts +61 -0
  361. package/dist/diagrams/lib/dev.js +96 -0
  362. package/dist/diagrams/lib/dev.js.map +1 -0
  363. package/dist/diagrams/lib/frame.d.ts +48 -7
  364. package/dist/diagrams/lib/frame.js +17 -2
  365. package/dist/diagrams/lib/frame.js.map +1 -1
  366. package/dist/diagrams/lib/route.d.ts +22 -1
  367. package/dist/diagrams/lib/route.js +24 -1
  368. package/dist/diagrams/lib/route.js.map +1 -1
  369. package/dist/diagrams/lib/wires.js +9 -5
  370. package/dist/diagrams/lib/wires.js.map +1 -1
  371. package/dist/diagrams/spec.d.ts +64 -12
  372. package/dist/index.d.ts +19 -4
  373. package/dist/index.js +12 -0
  374. package/dist/index.js.map +1 -1
  375. package/dist/keyframes.css +57 -3
  376. package/dist/lib/color.d.ts +80 -0
  377. package/dist/lib/color.js +189 -0
  378. package/dist/lib/color.js.map +1 -0
  379. package/dist/lib/numeric.d.ts +35 -0
  380. package/dist/lib/numeric.js +29 -0
  381. package/dist/lib/numeric.js.map +1 -0
  382. package/dist/lib/overlay-container.d.ts +14 -4
  383. package/dist/lib/overlay-container.js.map +1 -1
  384. package/dist/lib/warn.d.ts +73 -0
  385. package/dist/lib/warn.js +38 -0
  386. package/dist/lib/warn.js.map +1 -0
  387. package/dist/semantic.css +17 -6
  388. package/dist/styles.css +1 -1
  389. package/dist/themes.css +8 -0
  390. package/dist/tokens.css +86 -5
  391. package/dist/tokens.d.ts +4 -1
  392. package/dist/tokens.js +56 -36
  393. package/dist/tokens.json +56 -36
  394. package/llms.txt +35 -0
  395. package/package.json +5 -2
  396. package/skills/misoto22-design/evals/evals.json +84 -0
  397. package/skills/misoto22-design/rules/a11y.md +55 -3
  398. package/skills/misoto22-design/rules/forms.md +8 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ToggleGroup/ToggleGroup.tsx"],"sourcesContent":["'use client'\n\nimport * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'\nimport { createContext, useContext, useState, type ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useSelectionIndicator } from '../../lib/useSelectionIndicator'\n\nexport type ToggleGroupProps = ComponentProps<typeof ToggleGroupPrimitive.Root>\n\n/** Lets an item know whether the group tracks one value or several. */\nconst SingleValueContext = createContext<string | null>(null)\n\n/**\n * A segmented control: several options, one strip.\n *\n * `type=\"single\"` is a choice — Radix gives it radio semantics, and it is the\n * right shape for a view switcher or a density setting. `type=\"multiple\"` is a\n * set of independent toggles, which is a different thing announced differently;\n * choosing the wrong one is how a \"filter by tag\" control ends up telling a\n * screen reader that picking one tag unpicks the others.\n *\n * The two look different on purpose. A single-value strip moves ONE filled pill\n * between its options, so the eye follows a thing travelling; a multiple-value\n * strip fills each pressed option separately, because there is no single\n * selection to travel. Two people looking at a screenshot should be able to\n * tell which kind they are looking at, and before this they could not.\n *\n * Distinct from `Tabs`, which switches PANELS and owns a tabpanel relationship.\n * A toggle group changes a value.\n *\n * @example\n * <ToggleGroup type=\"single\" defaultValue=\"grid\" aria-label=\"Layout\">\n * <ToggleGroupItem value=\"grid\">Grid</ToggleGroupItem>\n * <ToggleGroupItem value=\"list\">List</ToggleGroupItem>\n * </ToggleGroup>\n */\nexport function ToggleGroup({ className, children, ...props }: ToggleGroupProps) {\n // Tracked here rather than left to Radix alone, because the sliding pill has\n // to know which item to measure — and an uncontrolled group never tells\n // anyone what it picked.\n const single = props.type === 'single'\n const [uncontrolled, setUncontrolled] = useState<string>(\n (single ? (props.defaultValue as string | undefined) : undefined) ?? '',\n )\n const value = single ? ((props.value as string | undefined) ?? uncontrolled) : null\n const [ref, indicator] = useSelectionIndicator<HTMLDivElement>(value)\n\n const root = (\n <ToggleGroupPrimitive.Root\n ref={ref}\n className={cn(\n // `w-fit` as well as `inline-flex`: a flex or grid parent stretches its\n // children to the track by default, and `inline-flex` does not opt out\n // of that. Without it the strip grew to whatever the widest sibling was\n // — a label above it — and left dead space after the last segment.\n 'relative inline-flex w-fit items-center gap-1 rounded-(--radius-pill) border border-(--rule-2) p-1',\n className,\n )}\n {...props}\n onValueChange={(next: never) => {\n if (single) setUncontrolled((next as string) ?? '')\n ;(props.onValueChange as ((v: never) => void) | undefined)?.(next)\n }}\n >\n {single && indicator.ready && (\n <span\n aria-hidden\n data-m22-animated\n className=\"absolute rounded-(--radius-pill) bg-(--accent) transition-[transform,width] duration-(--duration-base) ease-(--ease-out-expo) motion-reduce:transition-none\"\n style={{\n transform: `translate(${indicator.offset}px, ${indicator.top}px)`,\n width: indicator.width,\n height: indicator.height,\n insetInlineStart: 0,\n top: 0,\n }}\n />\n )}\n {children}\n </ToggleGroupPrimitive.Root>\n )\n\n return single ? (\n <SingleValueContext.Provider value={value}>{root}</SingleValueContext.Provider>\n ) : (\n root\n )\n}\n\nexport type ToggleGroupItemProps = ComponentProps<typeof ToggleGroupPrimitive.Item>\n\n/**\n * One segment.\n *\n * In a single-value group the item draws no background of its own — the\n * travelling pill behind it does — so it only changes ink. In a multiple-value\n * group it fills, because there is nothing travelling.\n */\nexport function ToggleGroupItem({ className, value, ...props }: ToggleGroupItemProps) {\n const selected = useContext(SingleValueContext)\n const isSingle = selected !== null\n const active = isSingle && selected === value\n\n return (\n <ToggleGroupPrimitive.Item\n value={value}\n data-indicator-active={active ? 'true' : undefined}\n className={cn(\n 'relative z-1 inline-flex min-h-(--control-h-sm) items-center justify-center gap-2 rounded-(--radius-pill) px-3.5 text-sm transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n isSingle\n ? active\n ? 'text-(--accent-foreground)'\n : 'text-(--ink-3-aa) hover:text-(--ink)'\n : 'text-(--ink-3-aa) hover:text-(--ink) data-[state=on]:bg-(--accent) data-[state=on]:text-(--accent-foreground)',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport default ToggleGroup\n"],"mappings":";AAgDI,SAiBI,KAjBJ;AA9CJ,YAAY,0BAA0B;AACtC,SAAS,eAAe,YAAY,gBAAqC;AACzE,SAAS,UAAU;AACnB,SAAS,6BAA6B;AAKtC,MAAM,qBAAqB,cAA6B,IAAI;AA0BrD,SAAS,YAAY,EAAE,WAAW,UAAU,GAAG,MAAM,GAAqB;AAI/E,QAAM,SAAS,MAAM,SAAS;AAC9B,QAAM,CAAC,cAAc,eAAe,IAAI;AAAA,KACrC,SAAU,MAAM,eAAsC,WAAc;AAAA,EACvE;AACA,QAAM,QAAQ,SAAW,MAAM,SAAgC,eAAgB;AAC/E,QAAM,CAAC,KAAK,SAAS,IAAI,sBAAsC,KAAK;AAEpE,QAAM,OACJ;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC;AAAA,MACA,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,QAKT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MACJ,eAAe,CAAC,SAAgB;AAC9B,YAAI,OAAQ,iBAAiB,QAAmB,EAAE;AACjD,QAAC,MAAM,gBAAqD,IAAI;AAAA,MACnE;AAAA,MAEC;AAAA,kBAAU,UAAU,SACnB;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YACX,qBAAiB;AAAA,YACjB,WAAU;AAAA,YACV,OAAO;AAAA,cACL,WAAW,aAAa,UAAU,MAAM,OAAO,UAAU,GAAG;AAAA,cAC5D,OAAO,UAAU;AAAA,cACjB,QAAQ,UAAU;AAAA,cAClB,kBAAkB;AAAA,cAClB,KAAK;AAAA,YACP;AAAA;AAAA,QACF;AAAA,QAED;AAAA;AAAA;AAAA,EACH;AAGF,SAAO,SACL,oBAAC,mBAAmB,UAAnB,EAA4B,OAAe,gBAAK,IAEjD;AAEJ;AAWO,SAAS,gBAAgB,EAAE,WAAW,OAAO,GAAG,MAAM,GAAyB;AACpF,QAAM,WAAW,WAAW,kBAAkB;AAC9C,QAAM,WAAW,aAAa;AAC9B,QAAM,SAAS,YAAY,aAAa;AAExC,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC;AAAA,MACA,yBAAuB,SAAS,SAAS;AAAA,MACzC,WAAW;AAAA,QACT;AAAA,QACA,WACI,SACE,+BACA,yCACF;AAAA,QACJ;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ToggleGroup/ToggleGroup.tsx"],"sourcesContent":["'use client'\n\nimport * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group'\nimport { createContext, useContext, useState, type ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useFieldControl } from '../Field/field-control'\nimport { useSelectionIndicator } from '../../lib/useSelectionIndicator'\n\nexport type ToggleGroupProps = ComponentProps<typeof ToggleGroupPrimitive.Root>\n\n/** Lets an item know whether the group tracks one value or several. */\nconst SingleValueContext = createContext<string | null>(null)\n\n/**\n * A segmented control: several options, one strip.\n *\n * `type=\"single\"` is a choice — Radix gives it radio semantics, and it is the\n * right shape for a view switcher or a density setting. `type=\"multiple\"` is a\n * set of independent toggles, which is a different thing announced differently;\n * choosing the wrong one is how a \"filter by tag\" control ends up telling a\n * screen reader that picking one tag unpicks the others.\n *\n * The two look different on purpose. A single-value strip moves ONE filled pill\n * between its options, so the eye follows a thing travelling; a multiple-value\n * strip fills each pressed option separately, because there is no single\n * selection to travel. Two people looking at a screenshot should be able to\n * tell which kind they are looking at, and before this they could not.\n *\n * Distinct from `Tabs`, which switches PANELS and owns a tabpanel relationship.\n * A toggle group changes a value.\n *\n * Inside a `Field` the strip takes its name from that label, by pointing back at\n * it — the root is a div, and `<label for>` does not bind to one, so the words\n * above it do not click through. Standing alone it still needs its own\n * `aria-label`.\n *\n * @example\n * <ToggleGroup type=\"single\" defaultValue=\"grid\" aria-label=\"Layout\">\n * <ToggleGroupItem value=\"grid\">Grid</ToggleGroupItem>\n * <ToggleGroupItem value=\"list\">List</ToggleGroupItem>\n * </ToggleGroup>\n */\nexport function ToggleGroup({\n className,\n children,\n 'aria-required': ariaRequired,\n ...props\n}: ToggleGroupProps) {\n const field = useFieldControl()\n const named = props['aria-label'] != null || props['aria-labelledby'] != null\n\n // Tracked here rather than left to Radix alone, because the sliding pill has\n // to know which item to measure — and an uncontrolled group never tells\n // anyone what it picked.\n const single = props.type === 'single'\n const [uncontrolled, setUncontrolled] = useState<string>(\n (single ? (props.defaultValue as string | undefined) : undefined) ?? '',\n )\n const value = single ? ((props.value as string | undefined) ?? uncontrolled) : null\n const [ref, indicator] = useSelectionIndicator<HTMLDivElement>(value)\n\n const root = (\n <ToggleGroupPrimitive.Root\n ref={ref}\n aria-labelledby={named ? undefined : field?.labelId}\n // Only on the single-value strip. Radix gives that one role=\"radiogroup\",\n // which takes aria-required; a multiple-value strip is a role=\"toolbar\",\n // where the attribute is not allowed at all — so a Field's `required`\n // reaches one shape of this control and not the other.\n aria-required={props.type === 'single' ? ariaRequired : undefined}\n className={cn(\n // `w-fit` as well as `inline-flex`: a flex or grid parent stretches its\n // children to the track by default, and `inline-flex` does not opt out\n // of that. Without it the strip grew to whatever the widest sibling was\n // — a label above it — and left dead space after the last segment.\n 'relative inline-flex w-fit items-center gap-1 rounded-(--radius-pill) border border-(--rule-2) p-1',\n className,\n )}\n {...props}\n onValueChange={(next: never) => {\n if (single) setUncontrolled((next as string) ?? '')\n ;(props.onValueChange as ((v: never) => void) | undefined)?.(next)\n }}\n >\n {single && indicator.ready && (\n <span\n aria-hidden\n data-m22-animated\n className=\"absolute rounded-(--radius-pill) bg-(--accent) transition-[transform,width] duration-(--duration-base) ease-(--ease-out-expo) motion-reduce:transition-none\"\n style={{\n transform: `translate(${indicator.offset}px, ${indicator.top}px)`,\n width: indicator.width,\n height: indicator.height,\n insetInlineStart: 0,\n top: 0,\n }}\n />\n )}\n {children}\n </ToggleGroupPrimitive.Root>\n )\n\n return single ? (\n <SingleValueContext.Provider value={value}>{root}</SingleValueContext.Provider>\n ) : (\n root\n )\n}\n\nexport type ToggleGroupItemProps = ComponentProps<typeof ToggleGroupPrimitive.Item>\n\n/**\n * One segment.\n *\n * In a single-value group the item draws no background of its own — the\n * travelling pill behind it does — so it only changes ink. In a multiple-value\n * group it fills, because there is nothing travelling.\n */\nexport function ToggleGroupItem({ className, value, ...props }: ToggleGroupItemProps) {\n const selected = useContext(SingleValueContext)\n const isSingle = selected !== null\n const active = isSingle && selected === value\n\n return (\n <ToggleGroupPrimitive.Item\n value={value}\n data-indicator-active={active ? 'true' : undefined}\n className={cn(\n 'relative z-1 inline-flex min-h-(--control-h-sm) items-center justify-center gap-2 rounded-(--radius-pill) px-3.5 text-sm transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none',\n isSingle\n ? active\n ? 'text-(--accent-foreground)'\n : 'text-(--ink-3-aa) hover:text-(--ink)'\n : 'text-(--ink-3-aa) hover:text-(--ink) data-[state=on]:bg-(--accent) data-[state=on]:text-(--accent-foreground)',\n className,\n )}\n {...props}\n />\n )\n}\n\nexport default ToggleGroup\n"],"mappings":";AA8DI,SAuBI,KAvBJ;AA5DJ,YAAY,0BAA0B;AACtC,SAAS,eAAe,YAAY,gBAAqC;AACzE,SAAS,UAAU;AACnB,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AAKtC,MAAM,qBAAqB,cAA6B,IAAI;AA+BrD,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,GAAG;AACL,GAAqB;AACnB,QAAM,QAAQ,gBAAgB;AAC9B,QAAM,QAAQ,MAAM,YAAY,KAAK,QAAQ,MAAM,iBAAiB,KAAK;AAKzE,QAAM,SAAS,MAAM,SAAS;AAC9B,QAAM,CAAC,cAAc,eAAe,IAAI;AAAA,KACrC,SAAU,MAAM,eAAsC,WAAc;AAAA,EACvE;AACA,QAAM,QAAQ,SAAW,MAAM,SAAgC,eAAgB;AAC/E,QAAM,CAAC,KAAK,SAAS,IAAI,sBAAsC,KAAK;AAEpE,QAAM,OACJ;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC;AAAA,MACA,mBAAiB,QAAQ,SAAY,OAAO;AAAA,MAK5C,iBAAe,MAAM,SAAS,WAAW,eAAe;AAAA,MACxD,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,QAKT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MACJ,eAAe,CAAC,SAAgB;AAC9B,YAAI,OAAQ,iBAAiB,QAAmB,EAAE;AACjD,QAAC,MAAM,gBAAqD,IAAI;AAAA,MACnE;AAAA,MAEC;AAAA,kBAAU,UAAU,SACnB;AAAA,UAAC;AAAA;AAAA,YACC,eAAW;AAAA,YACX,qBAAiB;AAAA,YACjB,WAAU;AAAA,YACV,OAAO;AAAA,cACL,WAAW,aAAa,UAAU,MAAM,OAAO,UAAU,GAAG;AAAA,cAC5D,OAAO,UAAU;AAAA,cACjB,QAAQ,UAAU;AAAA,cAClB,kBAAkB;AAAA,cAClB,KAAK;AAAA,YACP;AAAA;AAAA,QACF;AAAA,QAED;AAAA;AAAA;AAAA,EACH;AAGF,SAAO,SACL,oBAAC,mBAAmB,UAAnB,EAA4B,OAAe,gBAAK,IAEjD;AAEJ;AAWO,SAAS,gBAAgB,EAAE,WAAW,OAAO,GAAG,MAAM,GAAyB;AACpF,QAAM,WAAW,WAAW,kBAAkB;AAC9C,QAAM,WAAW,aAAa;AAC9B,QAAM,SAAS,YAAY,aAAa;AAExC,SACE;AAAA,IAAC,qBAAqB;AAAA,IAArB;AAAA,MACC;AAAA,MACA,yBAAuB,SAAS,SAAS;AAAA,MACzC,WAAW;AAAA,QACT;AAAA,QACA,WACI,SACE,+BACA,yCACF;AAAA,QACJ;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAEA,IAAO,sBAAQ;","names":[]}
@@ -0,0 +1,55 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+
4
+ /**
5
+ * Which edge the bar sticks to, or neither.
6
+ *
7
+ * `bottom` is the form footer — the actions stay in reach while a long form
8
+ * scrolls under them. `top` is the filter bar over a list. `static` is the same
9
+ * bar with no stickiness, for a short page or a bar inside a dialog that is
10
+ * already the height of its content.
11
+ */
12
+ type ToolbarPosition = 'bottom' | 'top' | 'static';
13
+ type ToolbarAlign = 'start' | 'center' | 'end' | 'between';
14
+ interface ToolbarProps extends HTMLAttributes<HTMLDivElement> {
15
+ /**
16
+ * Names the bar for a screen reader — "Form actions", "List filters".
17
+ * Required, because a group with no name is announced as "group" and a page
18
+ * with two of them is a page with two identical announcements.
19
+ */
20
+ label: string;
21
+ /** Which edge the bar sticks to. See {@link ToolbarPosition}. */
22
+ position?: ToolbarPosition;
23
+ /** Where the contents sit along the inline axis. */
24
+ align?: ToolbarAlign;
25
+ children: ReactNode;
26
+ }
27
+ /**
28
+ * The bar of actions at the edge of a working surface.
29
+ *
30
+ * Two page templates built the same thing independently — a sticky strip on
31
+ * `--paper` with a rule along the edge it sticks to — which is the signal that
32
+ * it belongs here rather than in each of them.
33
+ *
34
+ * The ground is opaque `--paper` and not a blur. Content scrolls UNDER this
35
+ * bar, so anything translucent puts the last row of a table behind the submit
36
+ * button and makes both unreadable; `FloatingIconButton` blurs because it
37
+ * floats over a gap, which is a different problem.
38
+ *
39
+ * It is not `role="toolbar"`. That role's contract is a single tab stop with
40
+ * arrow keys moving between the controls inside it, and this implements no such
41
+ * thing — declaring the role without the behaviour tells a screen-reader user
42
+ * to press arrow keys that do nothing. It is a named `group`, so the controls
43
+ * keep their own places in the tab order and the bar is still announced.
44
+ *
45
+ * @example
46
+ * <Toolbar label="Form actions">
47
+ * <Button variant="secondary">Cancel</Button>
48
+ * <Button type="submit">Save changes</Button>
49
+ * </Toolbar>
50
+ * @example
51
+ * <Toolbar label="List filters" position="top" align="between">…</Toolbar>
52
+ */
53
+ declare function Toolbar({ label, position, align, className, children, ...rest }: ToolbarProps): react.JSX.Element;
54
+
55
+ export { Toolbar, type ToolbarAlign, type ToolbarPosition, type ToolbarProps, Toolbar as default };
@@ -0,0 +1,43 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/cn.js";
3
+ const POSITION = {
4
+ bottom: "sticky bottom-0 z-(--z-sticky) border-t border-(--rule-2)",
5
+ top: "sticky top-0 z-(--z-sticky) border-b border-(--rule-2)",
6
+ static: "border-t border-(--rule-2)"
7
+ };
8
+ const ALIGN = {
9
+ start: "justify-start",
10
+ center: "justify-center",
11
+ end: "justify-end",
12
+ between: "justify-between"
13
+ };
14
+ function Toolbar({
15
+ label,
16
+ position = "bottom",
17
+ align = "end",
18
+ className,
19
+ children,
20
+ ...rest
21
+ }) {
22
+ return /* @__PURE__ */ jsx(
23
+ "div",
24
+ {
25
+ role: "group",
26
+ "aria-label": label,
27
+ className: cn(
28
+ "flex flex-wrap items-center gap-3 bg-(--paper) px-4 py-3",
29
+ POSITION[position],
30
+ ALIGN[align],
31
+ className
32
+ ),
33
+ ...rest,
34
+ children
35
+ }
36
+ );
37
+ }
38
+ var Toolbar_default = Toolbar;
39
+ export {
40
+ Toolbar,
41
+ Toolbar_default as default
42
+ };
43
+ //# sourceMappingURL=Toolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/Toolbar/Toolbar.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * Which edge the bar sticks to, or neither.\n *\n * `bottom` is the form footer — the actions stay in reach while a long form\n * scrolls under them. `top` is the filter bar over a list. `static` is the same\n * bar with no stickiness, for a short page or a bar inside a dialog that is\n * already the height of its content.\n */\nexport type ToolbarPosition = 'bottom' | 'top' | 'static'\n\nexport type ToolbarAlign = 'start' | 'center' | 'end' | 'between'\n\nexport interface ToolbarProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Names the bar for a screen reader — \"Form actions\", \"List filters\".\n * Required, because a group with no name is announced as \"group\" and a page\n * with two of them is a page with two identical announcements.\n */\n label: string\n /** Which edge the bar sticks to. See {@link ToolbarPosition}. */\n position?: ToolbarPosition\n /** Where the contents sit along the inline axis. */\n align?: ToolbarAlign\n children: ReactNode\n}\n\nconst POSITION: Record<ToolbarPosition, string> = {\n bottom: 'sticky bottom-0 z-(--z-sticky) border-t border-(--rule-2)',\n top: 'sticky top-0 z-(--z-sticky) border-b border-(--rule-2)',\n static: 'border-t border-(--rule-2)',\n}\n\nconst ALIGN: Record<ToolbarAlign, string> = {\n start: 'justify-start',\n center: 'justify-center',\n end: 'justify-end',\n between: 'justify-between',\n}\n\n/**\n * The bar of actions at the edge of a working surface.\n *\n * Two page templates built the same thing independently — a sticky strip on\n * `--paper` with a rule along the edge it sticks to — which is the signal that\n * it belongs here rather than in each of them.\n *\n * The ground is opaque `--paper` and not a blur. Content scrolls UNDER this\n * bar, so anything translucent puts the last row of a table behind the submit\n * button and makes both unreadable; `FloatingIconButton` blurs because it\n * floats over a gap, which is a different problem.\n *\n * It is not `role=\"toolbar\"`. That role's contract is a single tab stop with\n * arrow keys moving between the controls inside it, and this implements no such\n * thing — declaring the role without the behaviour tells a screen-reader user\n * to press arrow keys that do nothing. It is a named `group`, so the controls\n * keep their own places in the tab order and the bar is still announced.\n *\n * @example\n * <Toolbar label=\"Form actions\">\n * <Button variant=\"secondary\">Cancel</Button>\n * <Button type=\"submit\">Save changes</Button>\n * </Toolbar>\n * @example\n * <Toolbar label=\"List filters\" position=\"top\" align=\"between\">…</Toolbar>\n */\nexport function Toolbar({\n label,\n position = 'bottom',\n align = 'end',\n className,\n children,\n ...rest\n}: ToolbarProps) {\n return (\n <div\n role=\"group\"\n aria-label={label}\n className={cn(\n 'flex flex-wrap items-center gap-3 bg-(--paper) px-4 py-3',\n POSITION[position],\n ALIGN[align],\n className,\n )}\n {...rest}\n >\n {children}\n </div>\n )\n}\n\nexport default Toolbar\n"],"mappings":"AA6EI;AA5EJ,SAAS,UAAU;AA4BnB,MAAM,WAA4C;AAAA,EAChD,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,QAAQ;AACV;AAEA,MAAM,QAAsC;AAAA,EAC1C,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,SAAS;AACX;AA4BO,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,WAAW;AAAA,EACX,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiB;AACf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAW;AAAA,QACT;AAAA,QACA,SAAS,QAAQ;AAAA,QACjB,MAAM,KAAK;AAAA,QACX;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
@@ -21,6 +21,12 @@ interface ArchitectureFigureProps extends FigureChrome {
21
21
  * the input, it is identical on the server and in the browser, and there is no
22
22
  * layout shift on hydration because there is no layout to do.
23
23
  *
24
+ * A component that declares NEITHER flows: it takes the next free cell in
25
+ * declaration order, wrapping at `layout.cols`. That is still arithmetic on
26
+ * numbers the specification carries rather than a solver — and the alternative
27
+ * was every unplaced component defaulting to row 0, column 0 and stacking into
28
+ * one plate.
29
+ *
24
30
  * BOUNDARIES ARE DRAWN FIRST, AND DRAWN DIFFERENTLY. A `region` is where
25
31
  * something RUNS — a VPC, a zone, a cluster — and is a solid frame. A
26
32
  * `security-group` is what may REACH it, and is dashed. That is not decoration:
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useId, useMemo } from "react";
4
+ import { liveEdges, useSpecIdentity, warnCollision } from "../lib/dev.js";
4
5
  import { DiagramFrame } from "../lib/frame.js";
5
- import { inflate, TYPE, union, wrapText } from "../lib/geometry.js";
6
+ import { inflate, round, TYPE, union, wrapText } from "../lib/geometry.js";
6
7
  import { kindLegend, resolveLegend } from "../lib/legend.js";
7
8
  import { BandFrame, NodePlate, PLATE, plateHeight } from "../lib/marks.js";
8
9
  import { renderWires, wiresFor } from "../lib/wires.js";
@@ -26,6 +27,7 @@ function ArchitectureFigure({
26
27
  onSelectNode
27
28
  }) {
28
29
  const uid = useId().replace(/:/g, "");
30
+ useSpecIdentity(spec, "ArchitectureFigure");
29
31
  const model = useMemo(() => buildModel(spec, uid, activeIds, onSelectNode), [spec, uid, activeIds, onSelectNode]);
30
32
  return /* @__PURE__ */ jsx(
31
33
  DiagramFrame,
@@ -42,12 +44,34 @@ function ArchitectureFigure({
42
44
  }
43
45
  );
44
46
  }
47
+ function flow(components, cols) {
48
+ const claimed = /* @__PURE__ */ new Set();
49
+ for (const component of components) {
50
+ if (!component.pos && (component.row !== void 0 || component.col !== void 0)) {
51
+ claimed.add(`${component.row ?? 0}:${component.col ?? 0}`);
52
+ }
53
+ }
54
+ let cursor = 0;
55
+ return (component) => {
56
+ if (component.row !== void 0 || component.col !== void 0) {
57
+ return [component.row ?? 0, component.col ?? 0];
58
+ }
59
+ while (claimed.has(`${Math.floor(cursor / cols)}:${cursor % cols}`)) cursor += 1;
60
+ const cell = [Math.floor(cursor / cols), cursor % cols];
61
+ claimed.add(`${cell[0]}:${cell[1]}`);
62
+ cursor += 1;
63
+ return cell;
64
+ };
65
+ }
45
66
  function buildModel(spec, uid, activeIds, onSelectNode) {
46
67
  const layout = { ...GRID, ...spec.layout };
47
68
  const [originX, originY] = layout.origin;
69
+ const nextCell = flow(spec.components, Math.max(1, Math.round(layout.cols)));
48
70
  const boxes = /* @__PURE__ */ new Map();
71
+ const cells = /* @__PURE__ */ new Map();
72
+ const named = new Map(spec.components.map((component) => [component.id, component.label]));
49
73
  const placed = spec.components.map((component) => {
50
- const labelWidth = component.size?.[0] ?? component.width ?? GRID.cellW;
74
+ const labelWidth = component.size?.[0] ?? component.width ?? layout.cellW;
51
75
  const lines = wrapText(component.label, TYPE.label, labelWidth - PLATE.padX * 2, 2);
52
76
  const height = plateHeight(
53
77
  lines.length,
@@ -55,21 +79,28 @@ function buildModel(spec, uid, activeIds, onSelectNode) {
55
79
  Boolean(component.type || component.tag),
56
80
  component.size?.[1] ?? component.height ?? 0
57
81
  );
58
- const x = component.pos ? component.pos[0] : originX + (component.col ?? 0) * (layout.cellW + layout.gapX);
59
- const y = component.pos ? component.pos[1] : originY + (component.row ?? 0) * (layout.cellH + layout.gapY);
82
+ const [row, col] = nextCell(component);
83
+ const x = component.pos ? component.pos[0] : originX + col * (layout.cellW + layout.gapX);
84
+ const y = component.pos ? component.pos[1] : originY + row * (layout.cellH + layout.gapY);
60
85
  const box = { x, y, w: labelWidth, h: height };
61
86
  boxes.set(component.id, box);
87
+ cells.set(`${round(x)}:${round(y)}`, [...cells.get(`${round(x)}:${round(y)}`) ?? [], component.id]);
62
88
  return { component, box };
63
89
  });
90
+ for (const ids of cells.values()) {
91
+ if (ids.length > 1) warnCollision("ArchitectureFigure", ids);
92
+ }
64
93
  const boundaries = (spec.boundaries ?? []).map((boundary) => {
65
- const wrapped = boundary.wraps.map((id) => boxes.get(id)).filter((box) => Boolean(box));
94
+ const inside = boundary.wraps.filter((id) => boxes.has(id));
95
+ const wrapped = inside.map((id) => boxes.get(id)).filter((box) => Boolean(box));
66
96
  const extent2 = union(wrapped);
67
- return { boundary, box: extent2 ? inflate(extent2, boundary.pad ?? 28) : null };
97
+ return { boundary, inside, box: extent2 ? inflate(extent2, boundary.pad ?? 28) : null };
68
98
  });
69
99
  const absolute = new Set(
70
100
  spec.components.filter((component) => component.pos).map((component) => component.id)
71
101
  );
72
- const wires = wiresFor(spec.connections, boxes, (connection) => ({
102
+ const connections = liveEdges("ArchitectureFigure", spec.connections, (id) => boxes.has(id));
103
+ const wires = wiresFor(connections, boxes, (connection) => ({
73
104
  keepWaypoints: absolute.has(connection.from) && absolute.has(connection.to)
74
105
  }));
75
106
  const { paths, labels } = renderWires(wires, uid);
@@ -116,11 +147,20 @@ function buildModel(spec, uid, activeIds, onSelectNode) {
116
147
  sublabel: component.sublabel,
117
148
  kind: component.type
118
149
  })),
119
- edges: (spec.connections ?? []).map((connection) => ({
150
+ edges: connections.map((connection) => ({
120
151
  from: connection.from,
121
152
  to: connection.to,
122
153
  label: connection.label
123
154
  })),
155
+ // A boundary is a statement — this runs there, that may reach it — drawn as
156
+ // a frame inside artwork a screen reader is told to skip. It groups nothing
157
+ // in the list, because two boundaries can hold the same component and a
158
+ // node published twice is a node the keyboard reaches twice.
159
+ notes: boundaries.map(({ boundary, inside }) => {
160
+ const kind = boundary.kind === "region" ? "Region" : "Security group";
161
+ const members = inside.map((id) => named.get(id) ?? id);
162
+ return members.length > 0 ? `${kind} ${boundary.label} encloses ${members.join(", ")}.` : `${kind} ${boundary.label} encloses nothing this figure draws.`;
163
+ }),
124
164
  legend: kindLegend(
125
165
  resolveLegend(used, spec.meta.legend?.mode, KINDS, spec.meta.legend?.entries),
126
166
  spec.meta.legend?.entries
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/diagrams/ArchitectureFigure/ArchitectureFigure.tsx"],"sourcesContent":["'use client'\n\nimport { useId, useMemo } from 'react'\nimport { DiagramFrame, type FigureChrome, type FigureModel } from '../lib/frame'\nimport { inflate, TYPE, union, wrapText, type Box } from '../lib/geometry'\nimport { kindLegend, resolveLegend } from '../lib/legend'\nimport { BandFrame, NodePlate, PLATE, plateHeight } from '../lib/marks'\nimport { renderWires, wiresFor } from '../lib/wires'\nimport type { ArchitectureSpec, NodeKind } from '../spec'\n\nconst KINDS: NodeKind[] = [\n 'frontend',\n 'backend',\n 'database',\n 'cloud',\n 'security',\n 'messagebus',\n 'external',\n]\n\n/** What a cell is, when the specification does not say. */\nconst GRID = { origin: [40, 48] as [number, number], cols: 4, cellW: 184, cellH: 72, gapX: 64, gapY: 76 }\n\nexport interface ArchitectureFigureProps extends FigureChrome {\n spec: ArchitectureSpec\n}\n\n/**\n * A component map: services, datastores, boundaries, and what talks to what.\n *\n * Takes the same JSON an archify `architecture` specification carries —\n * `components`, `boundaries`, `connections` — and draws it in this system's own\n * terms: paper plates on a hairline, one reversed plate for the component the\n * diagram is about, and seven drawn sigils where archify uses seven hues.\n *\n * IT RENDERS ON A SERVER because every position is already in the\n * specification. A component gives a `row` and a `col` into a grid whose cell\n * size is either declared or defaulted, or it gives an absolute `pos` — nothing\n * here is solved for, relaxed, or measured. So the markup is a pure function of\n * the input, it is identical on the server and in the browser, and there is no\n * layout shift on hydration because there is no layout to do.\n *\n * BOUNDARIES ARE DRAWN FIRST, AND DRAWN DIFFERENTLY. A `region` is where\n * something RUNS — a VPC, a zone, a cluster — and is a solid frame. A\n * `security-group` is what may REACH it, and is dashed. That is not decoration:\n * an infrastructure diagram is very often read for exactly one of those two\n * questions, and a reader should be able to tell which line answers which\n * without reading either label.\n *\n * @example\n * <ArchitectureFigure\n * spec={{\n * meta: { title: 'Request path' },\n * components: [\n * { id: 'cdn', type: 'cloud', label: 'CloudFront', row: 0, col: 0 },\n * { id: 'api', type: 'backend', label: 'FastAPI', sublabel: 'ECS', row: 0, col: 1 },\n * { id: 'db', type: 'database', label: 'Postgres', row: 0, col: 2 },\n * ],\n * connections: [\n * { from: 'cdn', to: 'api', label: 'HTTPS' },\n * { from: 'api', to: 'db', label: 'SQL' },\n * ],\n * }}\n * />\n */\nexport function ArchitectureFigure({\n spec,\n className,\n legend = 'auto',\n cards = true,\n heading = true,\n activeIds,\n onSelectNode,\n}: ArchitectureFigureProps) {\n const uid = useId().replace(/:/g, '')\n const model = useMemo(() => buildModel(spec, uid, activeIds, onSelectNode), [spec, uid, activeIds, onSelectNode])\n\n return (\n <DiagramFrame\n meta={spec.meta}\n model={model}\n cards={spec.cards}\n showCards={cards}\n heading={heading}\n legend={legend}\n className={className}\n activeIds={activeIds}\n onSelectNode={onSelectNode}\n />\n )\n}\n\nfunction buildModel(\n spec: ArchitectureSpec,\n uid: string,\n activeIds: string[] | undefined,\n onSelectNode: ((id: string) => void) | undefined,\n): FigureModel {\n const layout = { ...GRID, ...spec.layout }\n const [originX, originY] = layout.origin\n\n const boxes = new Map<string, Box>()\n const placed = spec.components.map((component) => {\n const labelWidth = component.size?.[0] ?? component.width ?? GRID.cellW\n const lines = wrapText(component.label, TYPE.label, labelWidth - PLATE.padX * 2, 2)\n const height = plateHeight(\n lines.length,\n Boolean(component.sublabel),\n Boolean(component.type || component.tag),\n component.size?.[1] ?? component.height ?? 0,\n )\n\n const x = component.pos ? component.pos[0] : originX + (component.col ?? 0) * (layout.cellW + layout.gapX)\n const y = component.pos ? component.pos[1] : originY + (component.row ?? 0) * (layout.cellH + layout.gapY)\n\n const box: Box = { x, y, w: labelWidth, h: height }\n boxes.set(component.id, box)\n return { component, box }\n })\n\n const boundaries = (spec.boundaries ?? []).map((boundary) => {\n const wrapped = boundary.wraps.map((id) => boxes.get(id)).filter((box): box is Box => Boolean(box))\n const extent = union(wrapped)\n return { boundary, box: extent ? inflate(extent, boundary.pad ?? 28) : null }\n })\n\n // A waypoint is only meaningful when both ends were placed absolutely — see\n // `Wire.keepWaypoints`. In this diagram type that is common, which is why an\n // archify architecture specification's hand-tuned routes survive the move.\n const absolute = new Set(\n spec.components.filter((component) => component.pos).map((component) => component.id),\n )\n const wires = wiresFor(spec.connections, boxes, (connection) => ({\n keepWaypoints: absolute.has(connection.from) && absolute.has(connection.to),\n }))\n const { paths, labels } = renderWires(wires, uid)\n\n const drawn = [...boxes.values(), ...boundaries.map((entry) => entry.box).filter((box): box is Box => Boolean(box))]\n const extent = inflate(union(drawn) ?? { x: 0, y: 0, w: 400, h: 200 }, 22)\n\n const active = activeIds && activeIds.length > 0 ? new Set(activeIds) : null\n\n const artwork = (\n <>\n {boundaries.map(({ boundary, box }, index) =>\n box ? (\n <BandFrame\n key={`${boundary.label}-${index}`}\n box={box}\n label={boundary.label}\n dashed={boundary.kind === 'security-group'}\n />\n ) : null,\n )}\n {paths}\n {placed.map(({ component, box }) => (\n <NodePlate\n key={component.id}\n nodeId={component.id}\n box={box}\n label={component.label}\n sublabel={component.sublabel}\n tag={component.tag}\n kind={component.type}\n active={active?.has(component.id)}\n dimmed={active ? !active.has(component.id) : false}\n onSelect={onSelectNode ? () => onSelectNode(component.id) : undefined}\n />\n ))}\n {labels}\n </>\n )\n\n const used = [...new Set(spec.components.map((component) => component.type))]\n\n return {\n extent,\n artwork,\n nodes: spec.components.map((component) => ({\n id: component.id,\n label: component.label,\n sublabel: component.sublabel,\n kind: component.type,\n })),\n edges: (spec.connections ?? []).map((connection) => ({\n from: connection.from,\n to: connection.to,\n label: connection.label,\n })),\n legend: kindLegend(\n resolveLegend(used, spec.meta.legend?.mode, KINDS, spec.meta.legend?.entries),\n spec.meta.legend?.entries,\n ),\n }\n}\n\nexport default ArchitectureFigure\n"],"mappings":"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","names":["extent"]}
1
+ {"version":3,"sources":["../../../src/diagrams/ArchitectureFigure/ArchitectureFigure.tsx"],"sourcesContent":["'use client'\n\nimport { useId, useMemo } from 'react'\nimport { liveEdges, useSpecIdentity, warnCollision } from '../lib/dev'\nimport { DiagramFrame, type FigureChrome, type FigureModel } from '../lib/frame'\nimport { inflate, round, TYPE, union, wrapText, type Box } from '../lib/geometry'\nimport { kindLegend, resolveLegend } from '../lib/legend'\nimport { BandFrame, NodePlate, PLATE, plateHeight } from '../lib/marks'\nimport { renderWires, wiresFor } from '../lib/wires'\nimport type { ArchitectureComponent, ArchitectureSpec, NodeKind } from '../spec'\n\nconst KINDS: NodeKind[] = [\n 'frontend',\n 'backend',\n 'database',\n 'cloud',\n 'security',\n 'messagebus',\n 'external',\n]\n\n/** What a cell is, when the specification does not say. */\nconst GRID = { origin: [40, 48] as [number, number], cols: 4, cellW: 184, cellH: 72, gapX: 64, gapY: 76 }\n\nexport interface ArchitectureFigureProps extends FigureChrome {\n spec: ArchitectureSpec\n}\n\n/**\n * A component map: services, datastores, boundaries, and what talks to what.\n *\n * Takes the same JSON an archify `architecture` specification carries —\n * `components`, `boundaries`, `connections` — and draws it in this system's own\n * terms: paper plates on a hairline, one reversed plate for the component the\n * diagram is about, and seven drawn sigils where archify uses seven hues.\n *\n * IT RENDERS ON A SERVER because every position is already in the\n * specification. A component gives a `row` and a `col` into a grid whose cell\n * size is either declared or defaulted, or it gives an absolute `pos` — nothing\n * here is solved for, relaxed, or measured. So the markup is a pure function of\n * the input, it is identical on the server and in the browser, and there is no\n * layout shift on hydration because there is no layout to do.\n *\n * A component that declares NEITHER flows: it takes the next free cell in\n * declaration order, wrapping at `layout.cols`. That is still arithmetic on\n * numbers the specification carries rather than a solver — and the alternative\n * was every unplaced component defaulting to row 0, column 0 and stacking into\n * one plate.\n *\n * BOUNDARIES ARE DRAWN FIRST, AND DRAWN DIFFERENTLY. A `region` is where\n * something RUNS — a VPC, a zone, a cluster — and is a solid frame. A\n * `security-group` is what may REACH it, and is dashed. That is not decoration:\n * an infrastructure diagram is very often read for exactly one of those two\n * questions, and a reader should be able to tell which line answers which\n * without reading either label.\n *\n * @example\n * <ArchitectureFigure\n * spec={{\n * meta: { title: 'Request path' },\n * components: [\n * { id: 'cdn', type: 'cloud', label: 'CloudFront', row: 0, col: 0 },\n * { id: 'api', type: 'backend', label: 'FastAPI', sublabel: 'ECS', row: 0, col: 1 },\n * { id: 'db', type: 'database', label: 'Postgres', row: 0, col: 2 },\n * ],\n * connections: [\n * { from: 'cdn', to: 'api', label: 'HTTPS' },\n * { from: 'api', to: 'db', label: 'SQL' },\n * ],\n * }}\n * />\n */\nexport function ArchitectureFigure({\n spec,\n className,\n legend = 'auto',\n cards = true,\n heading = true,\n activeIds,\n onSelectNode,\n}: ArchitectureFigureProps) {\n const uid = useId().replace(/:/g, '')\n useSpecIdentity(spec, 'ArchitectureFigure')\n const model = useMemo(() => buildModel(spec, uid, activeIds, onSelectNode), [spec, uid, activeIds, onSelectNode])\n\n return (\n <DiagramFrame\n meta={spec.meta}\n model={model}\n cards={spec.cards}\n showCards={cards}\n heading={heading}\n legend={legend}\n className={className}\n activeIds={activeIds}\n onSelectNode={onSelectNode}\n />\n )\n}\n\n/**\n * Where a component sits, for a specification that did not say.\n *\n * `row` and `col` are both optional and both defaulted to 0, which meant every\n * component that declared no placement was drawn in the same cell — a figure of\n * seven services rendering as one plate with six underneath it. Reading them as\n * \"put it in the next free cell\" is what `layout.cols` was always for.\n *\n * Cells an explicit `row`/`col` claims are reserved before the flow starts, so\n * an author who placed three of ten components keeps those three exactly where\n * they put them.\n */\nfunction flow(\n components: ArchitectureComponent[],\n cols: number,\n): (component: ArchitectureComponent) => [row: number, col: number] {\n const claimed = new Set<string>()\n for (const component of components) {\n if (!component.pos && (component.row !== undefined || component.col !== undefined)) {\n claimed.add(`${component.row ?? 0}:${component.col ?? 0}`)\n }\n }\n\n let cursor = 0\n return (component) => {\n if (component.row !== undefined || component.col !== undefined) {\n return [component.row ?? 0, component.col ?? 0]\n }\n while (claimed.has(`${Math.floor(cursor / cols)}:${cursor % cols}`)) cursor += 1\n const cell: [number, number] = [Math.floor(cursor / cols), cursor % cols]\n claimed.add(`${cell[0]}:${cell[1]}`)\n cursor += 1\n return cell\n }\n}\n\nfunction buildModel(\n spec: ArchitectureSpec,\n uid: string,\n activeIds: string[] | undefined,\n onSelectNode: ((id: string) => void) | undefined,\n): FigureModel {\n const layout = { ...GRID, ...spec.layout }\n const [originX, originY] = layout.origin\n const nextCell = flow(spec.components, Math.max(1, Math.round(layout.cols)))\n\n const boxes = new Map<string, Box>()\n const cells = new Map<string, string[]>()\n const named = new Map(spec.components.map((component) => [component.id, component.label]))\n const placed = spec.components.map((component) => {\n // `layout.cellW` sets the PITCH and the plate's own width. Reading the\n // module default here instead spaced the plates further apart at the width\n // they already had, which is a grid with gaps rather than wider boxes.\n const labelWidth = component.size?.[0] ?? component.width ?? layout.cellW\n const lines = wrapText(component.label, TYPE.label, labelWidth - PLATE.padX * 2, 2)\n const height = plateHeight(\n lines.length,\n Boolean(component.sublabel),\n Boolean(component.type || component.tag),\n component.size?.[1] ?? component.height ?? 0,\n )\n\n const [row, col] = nextCell(component)\n const x = component.pos ? component.pos[0] : originX + col * (layout.cellW + layout.gapX)\n const y = component.pos ? component.pos[1] : originY + row * (layout.cellH + layout.gapY)\n\n const box: Box = { x, y, w: labelWidth, h: height }\n boxes.set(component.id, box)\n cells.set(`${round(x)}:${round(y)}`, [...(cells.get(`${round(x)}:${round(y)}`) ?? []), component.id])\n return { component, box }\n })\n\n // Nothing else notices: two plates at one coordinate draw one on top of the\n // other, and both are still in the summary — so the mistake exists in the\n // picture and nowhere the author is looking. `warnCollision` is dev-only.\n for (const ids of cells.values()) {\n if (ids.length > 1) warnCollision('ArchitectureFigure', ids)\n }\n\n const boundaries = (spec.boundaries ?? []).map((boundary) => {\n const inside = boundary.wraps.filter((id) => boxes.has(id))\n const wrapped = inside.map((id) => boxes.get(id)).filter((box): box is Box => Boolean(box))\n const extent = union(wrapped)\n return { boundary, inside, box: extent ? inflate(extent, boundary.pad ?? 28) : null }\n })\n\n // A waypoint is only meaningful when both ends were placed absolutely — see\n // `Wire.keepWaypoints`. In this diagram type that is common, which is why an\n // archify architecture specification's hand-tuned routes survive the move.\n const absolute = new Set(\n spec.components.filter((component) => component.pos).map((component) => component.id),\n )\n const connections = liveEdges('ArchitectureFigure', spec.connections, (id) => boxes.has(id))\n const wires = wiresFor(connections, boxes, (connection) => ({\n keepWaypoints: absolute.has(connection.from) && absolute.has(connection.to),\n }))\n const { paths, labels } = renderWires(wires, uid)\n\n const drawn = [...boxes.values(), ...boundaries.map((entry) => entry.box).filter((box): box is Box => Boolean(box))]\n const extent = inflate(union(drawn) ?? { x: 0, y: 0, w: 400, h: 200 }, 22)\n\n const active = activeIds && activeIds.length > 0 ? new Set(activeIds) : null\n\n const artwork = (\n <>\n {boundaries.map(({ boundary, box }, index) =>\n box ? (\n <BandFrame\n key={`${boundary.label}-${index}`}\n box={box}\n label={boundary.label}\n dashed={boundary.kind === 'security-group'}\n />\n ) : null,\n )}\n {paths}\n {placed.map(({ component, box }) => (\n <NodePlate\n key={component.id}\n nodeId={component.id}\n box={box}\n label={component.label}\n sublabel={component.sublabel}\n tag={component.tag}\n kind={component.type}\n active={active?.has(component.id)}\n dimmed={active ? !active.has(component.id) : false}\n onSelect={onSelectNode ? () => onSelectNode(component.id) : undefined}\n />\n ))}\n {labels}\n </>\n )\n\n const used = [...new Set(spec.components.map((component) => component.type))]\n\n return {\n extent,\n artwork,\n nodes: spec.components.map((component) => ({\n id: component.id,\n label: component.label,\n sublabel: component.sublabel,\n kind: component.type,\n })),\n edges: connections.map((connection) => ({\n from: connection.from,\n to: connection.to,\n label: connection.label,\n })),\n // A boundary is a statement — this runs there, that may reach it — drawn as\n // a frame inside artwork a screen reader is told to skip. It groups nothing\n // in the list, because two boundaries can hold the same component and a\n // node published twice is a node the keyboard reaches twice.\n notes: boundaries.map(({ boundary, inside }) => {\n const kind = boundary.kind === 'region' ? 'Region' : 'Security group'\n const members = inside.map((id) => named.get(id) ?? id)\n return members.length > 0\n ? `${kind} ${boundary.label} encloses ${members.join(', ')}.`\n : `${kind} ${boundary.label} encloses nothing this figure draws.`\n }),\n legend: kindLegend(\n resolveLegend(used, spec.meta.legend?.mode, KINDS, spec.meta.legend?.entries),\n spec.meta.legend?.entries,\n ),\n }\n}\n\nexport default ArchitectureFigure\n"],"mappings":"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","names":["extent"]}
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useId, useMemo } from "react";
4
+ import { liveEdges, useSpecIdentity, warnStageOutOfRange } from "../lib/dev.js";
4
5
  import { DiagramFrame } from "../lib/frame.js";
5
6
  import { inflate, round, TYPE, union, wrapText } from "../lib/geometry.js";
6
7
  import { kindLegend, resolveLegend } from "../lib/legend.js";
@@ -26,6 +27,7 @@ function DataflowFigure({
26
27
  onSelectNode
27
28
  }) {
28
29
  const uid = useId().replace(/:/g, "");
30
+ useSpecIdentity(spec, "DataflowFigure");
29
31
  const model = useMemo(
30
32
  () => buildModel(spec, uid, activeIds, onSelectNode),
31
33
  [spec, uid, activeIds, onSelectNode]
@@ -51,13 +53,17 @@ function buildModel(spec, uid, activeIds, onSelectNode) {
51
53
  const w = node.width ?? GRID.colW;
52
54
  const lines = wrapText(node.label, TYPE.label, w - PLATE.padX * 2, 2);
53
55
  const h = plateHeight(lines.length, Boolean(node.sublabel), true, node.height ?? 0);
56
+ if (node.stage < 0 || node.stage >= spec.stages.length) {
57
+ warnStageOutOfRange("DataflowFigure", node.id, node.stage, spec.stages.length);
58
+ }
54
59
  const x = GRID.originX + node.stage * (GRID.colW + GRID.gapX);
55
60
  const y = GRID.originY + node.row * (GRID.rowH + GRID.nodeH) + (node.yOffset ?? 0);
56
61
  const box = { x, y, w, h };
57
62
  boxes.set(node.id, box);
58
63
  return { node, box };
59
64
  });
60
- const wires = wiresFor(spec.flows, boxes);
65
+ const flows = liveEdges("DataflowFigure", spec.flows, (id) => boxes.has(id));
66
+ const wires = wiresFor(flows, boxes);
61
67
  const { paths, labels } = renderWires(wires, uid);
62
68
  const extent = inflate(union([...boxes.values()]) ?? { x: 0, y: 0, w: 500, h: 240 }, 30);
63
69
  const headerY = Math.min(GRID.originY - 30, extent.y + 28);
@@ -101,6 +107,18 @@ function buildModel(spec, uid, activeIds, onSelectNode) {
101
107
  labels
102
108
  ] });
103
109
  const used = [...new Set(spec.nodes.map((node) => node.type))];
110
+ const inStage = (index) => spec.nodes.filter((node) => node.stage === index).sort((a, b) => a.row - b.row).map((node) => node.id);
111
+ const stray = spec.nodes.filter((node) => node.stage < 0 || node.stage >= spec.stages.length).map((node) => node.id);
112
+ const bands = [
113
+ ...spec.stages.map((stage, index) => ({
114
+ kind: "Stage",
115
+ label: stage.label,
116
+ ids: inStage(index)
117
+ })),
118
+ // Drawn past the last heading, so said that way rather than folded in
119
+ // beside nodes the axis does label.
120
+ ...stray.length > 0 ? [{ kind: "Stage", label: "outside the declared stages", ids: stray }] : []
121
+ ];
104
122
  return {
105
123
  extent: { ...extent, y: headerY - 22, h: extent.h + (extent.y - (headerY - 22)) },
106
124
  artwork,
@@ -110,11 +128,12 @@ function buildModel(spec, uid, activeIds, onSelectNode) {
110
128
  sublabel: node.sublabel,
111
129
  kind: node.type
112
130
  })),
113
- edges: (spec.flows ?? []).map((flow) => ({
131
+ edges: flows.map((flow) => ({
114
132
  from: flow.from,
115
133
  to: flow.to,
116
134
  label: [flow.label, flow.classification].filter(Boolean).join(" \u2014 ") || void 0
117
135
  })),
136
+ bands,
118
137
  legend: kindLegend(
119
138
  resolveLegend(used, spec.meta.legend?.mode, KINDS, spec.meta.legend?.entries),
120
139
  spec.meta.legend?.entries
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/diagrams/DataflowFigure/DataflowFigure.tsx"],"sourcesContent":["'use client'\n\nimport { useId, useMemo } from 'react'\nimport { DiagramFrame, type FigureChrome, type FigureModel } from '../lib/frame'\nimport { inflate, round, TYPE, union, wrapText, type Box } from '../lib/geometry'\nimport { kindLegend, resolveLegend } from '../lib/legend'\nimport { NodePlate, PLATE, plateHeight } from '../lib/marks'\nimport { renderWires, wiresFor } from '../lib/wires'\nimport type { DataflowSpec, NodeKind } from '../spec'\n\nconst KINDS: NodeKind[] = [\n 'frontend',\n 'backend',\n 'database',\n 'cloud',\n 'security',\n 'messagebus',\n 'external',\n]\n\nconst GRID = { originX: 40, originY: 92, colW: 184, gapX: 78, rowH: 52, nodeH: 74 }\n\nexport interface DataflowFigureProps extends FigureChrome {\n spec: DataflowSpec\n}\n\n/**\n * A pipeline: where data comes from, what happens to it, and who ends up with\n * it.\n *\n * Structurally close to an architecture map and read for a completely different\n * question, which is why it is a separate renderer rather than a preset. An\n * architecture diagram is read for \"what talks to what\". A data-flow diagram is\n * read for \"what is IN this arrow\" — and that question is why `classification`\n * gets its own mono chip under the label rather than being folded into the\n * wording. `clickstream / PII touch` and `clickstream` are two different facts,\n * and a governance reviewer is looking for the second one.\n *\n * Stages are printed as column headings on a rule across the top. They are the\n * axis of the figure: a node's `stage` is its position along the pipeline, and\n * a reader should be able to answer \"how far has this got\" by looking up rather\n * than by tracing arrows.\n */\nexport function DataflowFigure({\n spec,\n className,\n legend = 'auto',\n cards = true,\n heading = true,\n activeIds,\n onSelectNode,\n}: DataflowFigureProps) {\n const uid = useId().replace(/:/g, '')\n const model = useMemo(\n () => buildModel(spec, uid, activeIds, onSelectNode),\n [spec, uid, activeIds, onSelectNode],\n )\n\n return (\n <DiagramFrame\n meta={spec.meta}\n model={model}\n cards={spec.cards}\n showCards={cards}\n heading={heading}\n legend={legend}\n className={className}\n activeIds={activeIds}\n onSelectNode={onSelectNode}\n />\n )\n}\n\nfunction buildModel(\n spec: DataflowSpec,\n uid: string,\n activeIds: string[] | undefined,\n onSelectNode: ((id: string) => void) | undefined,\n): FigureModel {\n const boxes = new Map<string, Box>()\n const placed = spec.nodes.map((node) => {\n const w = node.width ?? GRID.colW\n const lines = wrapText(node.label, TYPE.label, w - PLATE.padX * 2, 2)\n const h = plateHeight(lines.length, Boolean(node.sublabel), true, node.height ?? 0)\n\n const x = GRID.originX + node.stage * (GRID.colW + GRID.gapX)\n const y = GRID.originY + node.row * (GRID.rowH + GRID.nodeH) + (node.yOffset ?? 0)\n\n const box: Box = { x, y, w, h }\n boxes.set(node.id, box)\n return { node, box }\n })\n\n // Waypoints refer to the author's own grid, not this one — see\n // `Wire.keepWaypoints`. A data-flow specification always places by stage and\n // row, so there is never a case where honouring them would be correct.\n const wires = wiresFor(spec.flows, boxes)\n const { paths, labels } = renderWires(wires, uid)\n\n const extent = inflate(union([...boxes.values()]) ?? { x: 0, y: 0, w: 500, h: 240 }, 30)\n const headerY = Math.min(GRID.originY - 30, extent.y + 28)\n const active = activeIds && activeIds.length > 0 ? new Set(activeIds) : null\n\n const artwork = (\n <>\n {/* Stage headings, on one rule across the whole figure. */}\n <path\n d={`M ${round(extent.x + 14)} ${round(headerY + 8)} H ${round(extent.x + extent.w - 14)}`}\n className=\"stroke-(--diagram-rule) [stroke-width:1]\"\n aria-hidden=\"true\"\n />\n {spec.stages.map((stage, index) => (\n <text\n key={`${stage.label}-${index}`}\n x={round(GRID.originX + index * (GRID.colW + GRID.gapX))}\n y={round(headerY)}\n className=\"fill-(--diagram-ink-3) font-mono [font-size:10.5px] [letter-spacing:0.08em] uppercase\"\n aria-hidden=\"true\"\n >\n {stage.label}\n </text>\n ))}\n\n {paths}\n\n {placed.map(({ node, box }) => (\n <NodePlate\n key={node.id}\n nodeId={node.id}\n box={box}\n label={node.label}\n sublabel={node.sublabel}\n tag={node.tag}\n kind={node.type}\n active={active?.has(node.id)}\n dimmed={active ? !active.has(node.id) : false}\n onSelect={onSelectNode ? () => onSelectNode(node.id) : undefined}\n />\n ))}\n\n {labels}\n </>\n )\n\n const used = [...new Set(spec.nodes.map((node) => node.type))]\n\n return {\n extent: { ...extent, y: headerY - 22, h: extent.h + (extent.y - (headerY - 22)) },\n artwork,\n nodes: spec.nodes.map((node) => ({\n id: node.id,\n label: node.label,\n sublabel: node.sublabel,\n kind: node.type,\n })),\n edges: (spec.flows ?? []).map((flow) => ({\n from: flow.from,\n to: flow.to,\n label: [flow.label, flow.classification].filter(Boolean).join(' — ') || undefined,\n })),\n legend: kindLegend(\n resolveLegend(used, spec.meta.legend?.mode, KINDS, spec.meta.legend?.entries),\n spec.meta.legend?.entries,\n ),\n }\n}\n\nexport default DataflowFigure\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/diagrams/DataflowFigure/DataflowFigure.tsx"],"sourcesContent":["'use client'\n\nimport { useId, useMemo } from 'react'\nimport { liveEdges, useSpecIdentity, warnStageOutOfRange } from '../lib/dev'\nimport { DiagramFrame, type FigureBand, type FigureChrome, type FigureModel } from '../lib/frame'\nimport { inflate, round, TYPE, union, wrapText, type Box } from '../lib/geometry'\nimport { kindLegend, resolveLegend } from '../lib/legend'\nimport { NodePlate, PLATE, plateHeight } from '../lib/marks'\nimport { renderWires, wiresFor } from '../lib/wires'\nimport type { DataflowSpec, NodeKind } from '../spec'\n\nconst KINDS: NodeKind[] = [\n 'frontend',\n 'backend',\n 'database',\n 'cloud',\n 'security',\n 'messagebus',\n 'external',\n]\n\nconst GRID = { originX: 40, originY: 92, colW: 184, gapX: 78, rowH: 52, nodeH: 74 }\n\nexport interface DataflowFigureProps extends FigureChrome {\n spec: DataflowSpec\n}\n\n/**\n * A pipeline: where data comes from, what happens to it, and who ends up with\n * it.\n *\n * Structurally close to an architecture map and read for a completely different\n * question, which is why it is a separate renderer rather than a preset. An\n * architecture diagram is read for \"what talks to what\". A data-flow diagram is\n * read for \"what is IN this arrow\" — and that question is why `classification`\n * gets its own mono chip under the label rather than being folded into the\n * wording. `clickstream / PII touch` and `clickstream` are two different facts,\n * and a governance reviewer is looking for the second one.\n *\n * Stages are printed as column headings on a rule across the top. They are the\n * axis of the figure: a node's `stage` is its position along the pipeline, and\n * a reader should be able to answer \"how far has this got\" by looking up rather\n * than by tracing arrows.\n */\nexport function DataflowFigure({\n spec,\n className,\n legend = 'auto',\n cards = true,\n heading = true,\n activeIds,\n onSelectNode,\n}: DataflowFigureProps) {\n const uid = useId().replace(/:/g, '')\n useSpecIdentity(spec, 'DataflowFigure')\n const model = useMemo(\n () => buildModel(spec, uid, activeIds, onSelectNode),\n [spec, uid, activeIds, onSelectNode],\n )\n\n return (\n <DiagramFrame\n meta={spec.meta}\n model={model}\n cards={spec.cards}\n showCards={cards}\n heading={heading}\n legend={legend}\n className={className}\n activeIds={activeIds}\n onSelectNode={onSelectNode}\n />\n )\n}\n\nfunction buildModel(\n spec: DataflowSpec,\n uid: string,\n activeIds: string[] | undefined,\n onSelectNode: ((id: string) => void) | undefined,\n): FigureModel {\n const boxes = new Map<string, Box>()\n const placed = spec.nodes.map((node) => {\n const w = node.width ?? GRID.colW\n const lines = wrapText(node.label, TYPE.label, w - PLATE.padX * 2, 2)\n const h = plateHeight(lines.length, Boolean(node.sublabel), true, node.height ?? 0)\n\n // The stage is an INDEX into the headings, and the x is arithmetic on it\n // rather than a lookup — so a stage nothing declares is drawn a full column\n // past the last heading, under no heading at all.\n if (node.stage < 0 || node.stage >= spec.stages.length) {\n warnStageOutOfRange('DataflowFigure', node.id, node.stage, spec.stages.length)\n }\n\n const x = GRID.originX + node.stage * (GRID.colW + GRID.gapX)\n const y = GRID.originY + node.row * (GRID.rowH + GRID.nodeH) + (node.yOffset ?? 0)\n\n const box: Box = { x, y, w, h }\n boxes.set(node.id, box)\n return { node, box }\n })\n\n // Waypoints refer to the author's own grid, not this one — see\n // `Wire.keepWaypoints`. A data-flow specification always places by stage and\n // row, so there is never a case where honouring them would be correct.\n const flows = liveEdges('DataflowFigure', spec.flows, (id) => boxes.has(id))\n const wires = wiresFor(flows, boxes)\n const { paths, labels } = renderWires(wires, uid)\n\n const extent = inflate(union([...boxes.values()]) ?? { x: 0, y: 0, w: 500, h: 240 }, 30)\n const headerY = Math.min(GRID.originY - 30, extent.y + 28)\n const active = activeIds && activeIds.length > 0 ? new Set(activeIds) : null\n\n const artwork = (\n <>\n {/* Stage headings, on one rule across the whole figure. */}\n <path\n d={`M ${round(extent.x + 14)} ${round(headerY + 8)} H ${round(extent.x + extent.w - 14)}`}\n className=\"stroke-(--diagram-rule) [stroke-width:1]\"\n aria-hidden=\"true\"\n />\n {spec.stages.map((stage, index) => (\n <text\n key={`${stage.label}-${index}`}\n x={round(GRID.originX + index * (GRID.colW + GRID.gapX))}\n y={round(headerY)}\n className=\"fill-(--diagram-ink-3) font-mono [font-size:10.5px] [letter-spacing:0.08em] uppercase\"\n aria-hidden=\"true\"\n >\n {stage.label}\n </text>\n ))}\n\n {paths}\n\n {placed.map(({ node, box }) => (\n <NodePlate\n key={node.id}\n nodeId={node.id}\n box={box}\n label={node.label}\n sublabel={node.sublabel}\n tag={node.tag}\n kind={node.type}\n active={active?.has(node.id)}\n dimmed={active ? !active.has(node.id) : false}\n onSelect={onSelectNode ? () => onSelectNode(node.id) : undefined}\n />\n ))}\n\n {labels}\n </>\n )\n\n const used = [...new Set(spec.nodes.map((node) => node.type))]\n\n // The stages ARE the list's structure. \"How far has this got\" is the question\n // the axis exists to answer, and it was printed only as headings inside\n // artwork a screen reader is told to skip.\n const inStage = (index: number) =>\n spec.nodes\n .filter((node) => node.stage === index)\n .sort((a, b) => a.row - b.row)\n .map((node) => node.id)\n\n const stray = spec.nodes\n .filter((node) => node.stage < 0 || node.stage >= spec.stages.length)\n .map((node) => node.id)\n\n const bands: FigureBand[] = [\n ...spec.stages.map((stage, index) => ({\n kind: 'Stage',\n label: stage.label,\n ids: inStage(index),\n })),\n // Drawn past the last heading, so said that way rather than folded in\n // beside nodes the axis does label.\n ...(stray.length > 0\n ? [{ kind: 'Stage', label: 'outside the declared stages', ids: stray }]\n : []),\n ]\n\n return {\n extent: { ...extent, y: headerY - 22, h: extent.h + (extent.y - (headerY - 22)) },\n artwork,\n nodes: spec.nodes.map((node) => ({\n id: node.id,\n label: node.label,\n sublabel: node.sublabel,\n kind: node.type,\n })),\n edges: flows.map((flow) => ({\n from: flow.from,\n to: flow.to,\n label: [flow.label, flow.classification].filter(Boolean).join(' — ') || undefined,\n })),\n bands,\n legend: kindLegend(\n resolveLegend(used, spec.meta.legend?.mode, KINDS, spec.meta.legend?.entries),\n spec.meta.legend?.entries,\n ),\n }\n}\n\nexport default DataflowFigure\n"],"mappings":"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","names":[]}
@@ -5,6 +5,20 @@ interface CanvasView {
5
5
  scale: number;
6
6
  x: number;
7
7
  y: number;
8
+ /**
9
+ * How big the frame was, in CSS pixels, when this view was reported.
10
+ *
11
+ * Carried on the view because everything that consumes a view needs it and
12
+ * nothing outside this component can measure it: a `DiagramMinimap`'s
13
+ * viewport rectangle is the frame's size over the zoom, and a caller who had
14
+ * to hand-declare that number was hand-declaring the one number that makes
15
+ * the map lie. Absent until the frame has been measured — the first render
16
+ * has no box yet — which is exactly when a map should draw nothing.
17
+ */
18
+ frame?: {
19
+ width: number;
20
+ height: number;
21
+ };
8
22
  }
9
23
  /** What a caller can do to the canvas from outside it. */
10
24
  interface DiagramCanvasHandle {
@@ -25,12 +25,17 @@ function DiagramCanvas({
25
25
  const frame = useRef(null);
26
26
  const [view, setView] = useState({ scale: 1, x: 0, y: 0 });
27
27
  const drag = useRef(null);
28
+ const measure = useCallback(() => {
29
+ const box = frame.current?.getBoundingClientRect();
30
+ return box && box.width > 0 ? { width: box.width, height: box.height } : void 0;
31
+ }, []);
28
32
  const apply = useCallback(
29
33
  (next) => {
30
- setView(next);
31
- onViewChange?.(next);
34
+ const measured = { ...next, frame: measure() };
35
+ setView(measured);
36
+ onViewChange?.(measured);
32
37
  },
33
- [onViewChange]
38
+ [measure, onViewChange]
34
39
  );
35
40
  const zoomAbout = useCallback(
36
41
  (factor, originX, originY) => {
@@ -43,13 +48,14 @@ function DiagramCanvas({
43
48
  const next = {
44
49
  scale,
45
50
  x: cx - (cx - current.x) * ratio,
46
- y: cy - (cy - current.y) * ratio
51
+ y: cy - (cy - current.y) * ratio,
52
+ frame: measure()
47
53
  };
48
54
  onViewChange?.(next);
49
55
  return next;
50
56
  });
51
57
  },
52
- [onViewChange]
58
+ [measure, onViewChange]
53
59
  );
54
60
  useImperativeHandle(
55
61
  ref,
@@ -64,7 +70,8 @@ function DiagramCanvas({
64
70
  const next = {
65
71
  ...current,
66
72
  x: box.width / 2 - x * current.scale,
67
- y: box.height / 2 - y * current.scale
73
+ y: box.height / 2 - y * current.scale,
74
+ frame: measure()
68
75
  };
69
76
  onViewChange?.(next);
70
77
  return next;
@@ -72,7 +79,7 @@ function DiagramCanvas({
72
79
  },
73
80
  view
74
81
  }),
75
- [apply, onViewChange, view, zoomAbout]
82
+ [apply, measure, onViewChange, view, zoomAbout]
76
83
  );
77
84
  useEffect(() => {
78
85
  const node = frame.current;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/diagrams/DiagramCanvas/DiagramCanvas.tsx"],"sourcesContent":["'use client'\n\nimport {\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n type Ref,\n} from 'react'\nimport { cn } from '../../lib/cn'\n\nconst MIN_SCALE = 0.35\nconst MAX_SCALE = 4\nconst STEP = 1.25\n\nexport interface CanvasView {\n scale: number\n x: number\n y: number\n}\n\n/** What a caller can do to the canvas from outside it. */\nexport interface DiagramCanvasHandle {\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n /** Pans so a point in CONTENT coordinates sits in the middle of the frame. */\n centerOn: (x: number, y: number) => void\n view: CanvasView\n}\n\nexport interface DiagramCanvasProps {\n children: ReactNode\n className?: string\n /** How tall the frame is. Anything CSS accepts. */\n height?: string\n /** Told about every view change, for a minimap or a percentage readout. */\n onViewChange?: (view: CanvasView) => void\n /** Hides the built-in zoom controls, for a caller supplying its own. */\n controls?: boolean\n /** Names the region for assistive tech. */\n label?: string\n ref?: Ref<DiagramCanvasHandle>\n}\n\n/**\n * A frame that a picture larger than it can be moved around inside.\n *\n * Pan with a drag, zoom with the controls or with ⌘/Ctrl and the wheel, and\n * reset with a key. That is the whole of it — this is deliberately a VIEWPORT\n * and not a diagram editor: nothing here knows what a node is, so it works for\n * any oversized figure, an SVG, an image, a table that will not fold.\n *\n * Three decisions here are not obvious.\n *\n * **A plain wheel scrolls the page, not the diagram.** A canvas that swallows\n * the wheel is a scroll trap: a reader flicking down an article hits the figure\n * and the page stops moving for no reason they can see. Zooming needs the\n * modifier — which is also the platform gesture for zoom everywhere else, and\n * is what a trackpad pinch already sends.\n *\n * **The transform is on a wrapper, not on the content.** The child keeps its\n * own coordinate space, so a figure inside can still be measured, exported and\n * read by anything that walks it. Scaling the child directly would make every\n * `getBoundingClientRect` inside it a lie.\n *\n * **Keyboard first, and the frame is a real tab stop.** `+` / `-` / `0` and the\n * arrow keys move the view; the frame takes focus so they can be pressed at\n * all. A canvas that only answers a drag is a canvas half the readers cannot\n * operate.\n *\n * @example\n * <DiagramCanvas height=\"26rem\" label=\"Request path\">\n * <ArchitectureFigure spec={spec} heading={false} legend=\"hidden\" cards={false} />\n * </DiagramCanvas>\n */\nexport function DiagramCanvas({\n children,\n className,\n height = '24rem',\n onViewChange,\n controls = true,\n label = 'Diagram canvas',\n ref,\n}: DiagramCanvasProps) {\n const uid = useId().replace(/:/g, '')\n const frame = useRef<HTMLDivElement>(null)\n const [view, setView] = useState<CanvasView>({ scale: 1, x: 0, y: 0 })\n const drag = useRef<{ pointer: number; x: number; y: number } | null>(null)\n\n const apply = useCallback(\n (next: CanvasView) => {\n setView(next)\n onViewChange?.(next)\n },\n [onViewChange],\n )\n\n /**\n * Zooms about a fixed point in FRAME coordinates.\n *\n * The arithmetic is what makes the pointer stay on the thing it is over:\n * the content point under the cursor must land back under the cursor, so the\n * offset moves by the difference the scale change opened up. Without it, a\n * zoom drifts toward the origin and a reader chases the node they were\n * looking at.\n */\n const zoomAbout = useCallback(\n (factor: number, originX?: number, originY?: number) => {\n const box = frame.current?.getBoundingClientRect()\n const cx = originX ?? (box ? box.width / 2 : 0)\n const cy = originY ?? (box ? box.height / 2 : 0)\n\n setView((current) => {\n const scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, current.scale * factor))\n const ratio = scale / current.scale\n const next = {\n scale,\n x: cx - (cx - current.x) * ratio,\n y: cy - (cy - current.y) * ratio,\n }\n onViewChange?.(next)\n return next\n })\n },\n [onViewChange],\n )\n\n useImperativeHandle(\n ref,\n () => ({\n zoomIn: () => zoomAbout(STEP),\n zoomOut: () => zoomAbout(1 / STEP),\n reset: () => apply({ scale: 1, x: 0, y: 0 }),\n centerOn: (x: number, y: number) => {\n const box = frame.current?.getBoundingClientRect()\n if (!box) return\n setView((current) => {\n const next = {\n ...current,\n x: box.width / 2 - x * current.scale,\n y: box.height / 2 - y * current.scale,\n }\n onViewChange?.(next)\n return next\n })\n },\n view,\n }),\n [apply, onViewChange, view, zoomAbout],\n )\n\n // Registered imperatively because React marks `wheel` passive, and a passive\n // listener cannot call preventDefault — so the modifier-wheel zoom would also\n // scroll the page underneath it.\n useEffect(() => {\n const node = frame.current\n if (!node) return\n\n const onWheel = (event: WheelEvent) => {\n if (!event.ctrlKey && !event.metaKey) return\n event.preventDefault()\n const box = node.getBoundingClientRect()\n zoomAbout(\n event.deltaY < 0 ? STEP : 1 / STEP,\n event.clientX - box.left,\n event.clientY - box.top,\n )\n }\n\n node.addEventListener('wheel', onWheel, { passive: false })\n return () => node.removeEventListener('wheel', onWheel)\n }, [zoomAbout])\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n drag.current = { pointer: event.pointerId, x: event.clientX - view.x, y: event.clientY - view.y }\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const state = drag.current\n if (!state || state.pointer !== event.pointerId) return\n apply({ ...view, x: event.clientX - state.x, y: event.clientY - state.y })\n }\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (drag.current?.pointer !== event.pointerId) return\n drag.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n }\n\n return (\n <div\n className={cn(\n 'relative overflow-hidden rounded-(--radius-lg) border border-(--rule) bg-(--diagram-surface)',\n className,\n )}\n style={{ height }}\n >\n <div\n ref={frame}\n role=\"group\"\n aria-label={label}\n aria-describedby={`${uid}-hint`}\n tabIndex={0}\n className=\"size-full cursor-grab touch-none select-none outline-none focus-visible:ring-2 focus-visible:ring-(--ring) focus-visible:ring-inset active:cursor-grabbing\"\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={(event) => {\n const nudge = event.shiftKey ? 80 : 24\n switch (event.key) {\n case '+':\n case '=':\n event.preventDefault()\n zoomAbout(STEP)\n break\n case '-':\n event.preventDefault()\n zoomAbout(1 / STEP)\n break\n case '0':\n event.preventDefault()\n apply({ scale: 1, x: 0, y: 0 })\n break\n case 'ArrowLeft':\n event.preventDefault()\n apply({ ...view, x: view.x + nudge })\n break\n case 'ArrowRight':\n event.preventDefault()\n apply({ ...view, x: view.x - nudge })\n break\n case 'ArrowUp':\n event.preventDefault()\n apply({ ...view, y: view.y + nudge })\n break\n case 'ArrowDown':\n event.preventDefault()\n apply({ ...view, y: view.y - nudge })\n break\n default:\n break\n }\n }}\n >\n <div\n className=\"origin-top-left will-change-transform\"\n style={{ transform: `translate(${view.x}px, ${view.y}px) scale(${view.scale})` }}\n data-diagram-stage=\"\"\n >\n {children}\n </div>\n </div>\n\n <p id={`${uid}-hint`} className=\"sr-only\">\n Drag to pan. Plus and minus zoom, zero resets, the arrow keys pan.\n </p>\n\n {controls && (\n <div className=\"absolute bottom-3 end-3 flex items-center gap-px rounded-(--radius) border border-(--rule-2) bg-(--panel-bg) p-0.5\">\n <CanvasButton label=\"Zoom out\" onClick={() => zoomAbout(1 / STEP)}>\n <span aria-hidden>−</span>\n </CanvasButton>\n <button\n type=\"button\"\n onClick={() => apply({ scale: 1, x: 0, y: 0 })}\n aria-label=\"Reset the view\"\n className=\"min-w-12 rounded-(--radius-xs) px-1.5 py-1 mono-meta text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--ring)\"\n >\n {Math.round(view.scale * 100)}%\n </button>\n <CanvasButton label=\"Zoom in\" onClick={() => zoomAbout(STEP)}>\n <span aria-hidden>+</span>\n </CanvasButton>\n </div>\n )}\n </div>\n )\n}\n\nfunction CanvasButton({\n label,\n onClick,\n children,\n}: {\n label: string\n onClick: () => void\n children: ReactNode\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n className=\"flex size-7 items-center justify-center rounded-(--radius-xs) font-mono text-[13px] leading-none text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--ring)\"\n >\n {children}\n </button>\n )\n}\n\nexport default DiagramCanvas\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/diagrams/DiagramCanvas/DiagramCanvas.tsx"],"sourcesContent":["'use client'\n\nimport {\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n type Ref,\n} from 'react'\nimport { cn } from '../../lib/cn'\n\nconst MIN_SCALE = 0.35\nconst MAX_SCALE = 4\nconst STEP = 1.25\n\nexport interface CanvasView {\n scale: number\n x: number\n y: number\n /**\n * How big the frame was, in CSS pixels, when this view was reported.\n *\n * Carried on the view because everything that consumes a view needs it and\n * nothing outside this component can measure it: a `DiagramMinimap`'s\n * viewport rectangle is the frame's size over the zoom, and a caller who had\n * to hand-declare that number was hand-declaring the one number that makes\n * the map lie. Absent until the frame has been measured — the first render\n * has no box yet — which is exactly when a map should draw nothing.\n */\n frame?: { width: number; height: number }\n}\n\n/** What a caller can do to the canvas from outside it. */\nexport interface DiagramCanvasHandle {\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n /** Pans so a point in CONTENT coordinates sits in the middle of the frame. */\n centerOn: (x: number, y: number) => void\n view: CanvasView\n}\n\nexport interface DiagramCanvasProps {\n children: ReactNode\n className?: string\n /** How tall the frame is. Anything CSS accepts. */\n height?: string\n /** Told about every view change, for a minimap or a percentage readout. */\n onViewChange?: (view: CanvasView) => void\n /** Hides the built-in zoom controls, for a caller supplying its own. */\n controls?: boolean\n /** Names the region for assistive tech. */\n label?: string\n ref?: Ref<DiagramCanvasHandle>\n}\n\n/**\n * A frame that a picture larger than it can be moved around inside.\n *\n * Pan with a drag, zoom with the controls or with ⌘/Ctrl and the wheel, and\n * reset with a key. That is the whole of it — this is deliberately a VIEWPORT\n * and not a diagram editor: nothing here knows what a node is, so it works for\n * any oversized figure, an SVG, an image, a table that will not fold.\n *\n * Three decisions here are not obvious.\n *\n * **A plain wheel scrolls the page, not the diagram.** A canvas that swallows\n * the wheel is a scroll trap: a reader flicking down an article hits the figure\n * and the page stops moving for no reason they can see. Zooming needs the\n * modifier — which is also the platform gesture for zoom everywhere else, and\n * is what a trackpad pinch already sends.\n *\n * **The transform is on a wrapper, not on the content.** The child keeps its\n * own coordinate space, so a figure inside can still be measured, exported and\n * read by anything that walks it. Scaling the child directly would make every\n * `getBoundingClientRect` inside it a lie.\n *\n * **Keyboard first, and the frame is a real tab stop.** `+` / `-` / `0` and the\n * arrow keys move the view; the frame takes focus so they can be pressed at\n * all. A canvas that only answers a drag is a canvas half the readers cannot\n * operate.\n *\n * @example\n * <DiagramCanvas height=\"26rem\" label=\"Request path\">\n * <ArchitectureFigure spec={spec} heading={false} legend=\"hidden\" cards={false} />\n * </DiagramCanvas>\n */\nexport function DiagramCanvas({\n children,\n className,\n height = '24rem',\n onViewChange,\n controls = true,\n label = 'Diagram canvas',\n ref,\n}: DiagramCanvasProps) {\n const uid = useId().replace(/:/g, '')\n const frame = useRef<HTMLDivElement>(null)\n const [view, setView] = useState<CanvasView>({ scale: 1, x: 0, y: 0 })\n const drag = useRef<{ pointer: number; x: number; y: number } | null>(null)\n\n /** The frame's own size, or nothing when it has not been laid out yet. */\n const measure = useCallback(() => {\n const box = frame.current?.getBoundingClientRect()\n return box && box.width > 0 ? { width: box.width, height: box.height } : undefined\n }, [])\n\n const apply = useCallback(\n (next: CanvasView) => {\n const measured = { ...next, frame: measure() }\n setView(measured)\n onViewChange?.(measured)\n },\n [measure, onViewChange],\n )\n\n /**\n * Zooms about a fixed point in FRAME coordinates.\n *\n * The arithmetic is what makes the pointer stay on the thing it is over:\n * the content point under the cursor must land back under the cursor, so the\n * offset moves by the difference the scale change opened up. Without it, a\n * zoom drifts toward the origin and a reader chases the node they were\n * looking at.\n */\n const zoomAbout = useCallback(\n (factor: number, originX?: number, originY?: number) => {\n const box = frame.current?.getBoundingClientRect()\n const cx = originX ?? (box ? box.width / 2 : 0)\n const cy = originY ?? (box ? box.height / 2 : 0)\n\n setView((current) => {\n const scale = Math.min(MAX_SCALE, Math.max(MIN_SCALE, current.scale * factor))\n const ratio = scale / current.scale\n const next = {\n scale,\n x: cx - (cx - current.x) * ratio,\n y: cy - (cy - current.y) * ratio,\n frame: measure(),\n }\n onViewChange?.(next)\n return next\n })\n },\n [measure, onViewChange],\n )\n\n useImperativeHandle(\n ref,\n () => ({\n zoomIn: () => zoomAbout(STEP),\n zoomOut: () => zoomAbout(1 / STEP),\n reset: () => apply({ scale: 1, x: 0, y: 0 }),\n centerOn: (x: number, y: number) => {\n const box = frame.current?.getBoundingClientRect()\n if (!box) return\n setView((current) => {\n const next = {\n ...current,\n x: box.width / 2 - x * current.scale,\n y: box.height / 2 - y * current.scale,\n frame: measure(),\n }\n onViewChange?.(next)\n return next\n })\n },\n view,\n }),\n [apply, measure, onViewChange, view, zoomAbout],\n )\n\n // Registered imperatively because React marks `wheel` passive, and a passive\n // listener cannot call preventDefault — so the modifier-wheel zoom would also\n // scroll the page underneath it.\n useEffect(() => {\n const node = frame.current\n if (!node) return\n\n const onWheel = (event: WheelEvent) => {\n if (!event.ctrlKey && !event.metaKey) return\n event.preventDefault()\n const box = node.getBoundingClientRect()\n zoomAbout(\n event.deltaY < 0 ? STEP : 1 / STEP,\n event.clientX - box.left,\n event.clientY - box.top,\n )\n }\n\n node.addEventListener('wheel', onWheel, { passive: false })\n return () => node.removeEventListener('wheel', onWheel)\n }, [zoomAbout])\n\n const onPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (event.button !== 0) return\n drag.current = { pointer: event.pointerId, x: event.clientX - view.x, y: event.clientY - view.y }\n event.currentTarget.setPointerCapture(event.pointerId)\n }\n\n const onPointerMove = (event: ReactPointerEvent<HTMLDivElement>) => {\n const state = drag.current\n if (!state || state.pointer !== event.pointerId) return\n apply({ ...view, x: event.clientX - state.x, y: event.clientY - state.y })\n }\n\n const endDrag = (event: ReactPointerEvent<HTMLDivElement>) => {\n if (drag.current?.pointer !== event.pointerId) return\n drag.current = null\n if (event.currentTarget.hasPointerCapture(event.pointerId)) {\n event.currentTarget.releasePointerCapture(event.pointerId)\n }\n }\n\n return (\n <div\n className={cn(\n 'relative overflow-hidden rounded-(--radius-lg) border border-(--rule) bg-(--diagram-surface)',\n className,\n )}\n style={{ height }}\n >\n <div\n ref={frame}\n role=\"group\"\n aria-label={label}\n aria-describedby={`${uid}-hint`}\n tabIndex={0}\n className=\"size-full cursor-grab touch-none select-none outline-none focus-visible:ring-2 focus-visible:ring-(--ring) focus-visible:ring-inset active:cursor-grabbing\"\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={endDrag}\n onPointerCancel={endDrag}\n onKeyDown={(event) => {\n const nudge = event.shiftKey ? 80 : 24\n switch (event.key) {\n case '+':\n case '=':\n event.preventDefault()\n zoomAbout(STEP)\n break\n case '-':\n event.preventDefault()\n zoomAbout(1 / STEP)\n break\n case '0':\n event.preventDefault()\n apply({ scale: 1, x: 0, y: 0 })\n break\n case 'ArrowLeft':\n event.preventDefault()\n apply({ ...view, x: view.x + nudge })\n break\n case 'ArrowRight':\n event.preventDefault()\n apply({ ...view, x: view.x - nudge })\n break\n case 'ArrowUp':\n event.preventDefault()\n apply({ ...view, y: view.y + nudge })\n break\n case 'ArrowDown':\n event.preventDefault()\n apply({ ...view, y: view.y - nudge })\n break\n default:\n break\n }\n }}\n >\n <div\n className=\"origin-top-left will-change-transform\"\n style={{ transform: `translate(${view.x}px, ${view.y}px) scale(${view.scale})` }}\n data-diagram-stage=\"\"\n >\n {children}\n </div>\n </div>\n\n <p id={`${uid}-hint`} className=\"sr-only\">\n Drag to pan. Plus and minus zoom, zero resets, the arrow keys pan.\n </p>\n\n {controls && (\n <div className=\"absolute bottom-3 end-3 flex items-center gap-px rounded-(--radius) border border-(--rule-2) bg-(--panel-bg) p-0.5\">\n <CanvasButton label=\"Zoom out\" onClick={() => zoomAbout(1 / STEP)}>\n <span aria-hidden>−</span>\n </CanvasButton>\n <button\n type=\"button\"\n onClick={() => apply({ scale: 1, x: 0, y: 0 })}\n aria-label=\"Reset the view\"\n className=\"min-w-12 rounded-(--radius-xs) px-1.5 py-1 mono-meta text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--ring)\"\n >\n {Math.round(view.scale * 100)}%\n </button>\n <CanvasButton label=\"Zoom in\" onClick={() => zoomAbout(STEP)}>\n <span aria-hidden>+</span>\n </CanvasButton>\n </div>\n )}\n </div>\n )\n}\n\nfunction CanvasButton({\n label,\n onClick,\n children,\n}: {\n label: string\n onClick: () => void\n children: ReactNode\n}) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={label}\n className=\"flex size-7 items-center justify-center rounded-(--radius-xs) font-mono text-[13px] leading-none text-(--ink-2) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--ring)\"\n >\n {children}\n </button>\n )\n}\n\nexport default DiagramCanvas\n"],"mappings":";AAkRQ,cAkBE,YAlBF;AAhRR;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,UAAU;AAEnB,MAAM,YAAY;AAClB,MAAM,YAAY;AAClB,MAAM,OAAO;AA0EN,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA,WAAW;AAAA,EACX,QAAQ;AAAA,EACR;AACF,GAAuB;AACrB,QAAM,MAAM,MAAM,EAAE,QAAQ,MAAM,EAAE;AACpC,QAAM,QAAQ,OAAuB,IAAI;AACzC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAqB,EAAE,OAAO,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AACrE,QAAM,OAAO,OAAyD,IAAI;AAG1E,QAAM,UAAU,YAAY,MAAM;AAChC,UAAM,MAAM,MAAM,SAAS,sBAAsB;AACjD,WAAO,OAAO,IAAI,QAAQ,IAAI,EAAE,OAAO,IAAI,OAAO,QAAQ,IAAI,OAAO,IAAI;AAAA,EAC3E,GAAG,CAAC,CAAC;AAEL,QAAM,QAAQ;AAAA,IACZ,CAAC,SAAqB;AACpB,YAAM,WAAW,EAAE,GAAG,MAAM,OAAO,QAAQ,EAAE;AAC7C,cAAQ,QAAQ;AAChB,qBAAe,QAAQ;AAAA,IACzB;AAAA,IACA,CAAC,SAAS,YAAY;AAAA,EACxB;AAWA,QAAM,YAAY;AAAA,IAChB,CAAC,QAAgB,SAAkB,YAAqB;AACtD,YAAM,MAAM,MAAM,SAAS,sBAAsB;AACjD,YAAM,KAAK,YAAY,MAAM,IAAI,QAAQ,IAAI;AAC7C,YAAM,KAAK,YAAY,MAAM,IAAI,SAAS,IAAI;AAE9C,cAAQ,CAAC,YAAY;AACnB,cAAM,QAAQ,KAAK,IAAI,WAAW,KAAK,IAAI,WAAW,QAAQ,QAAQ,MAAM,CAAC;AAC7E,cAAM,QAAQ,QAAQ,QAAQ;AAC9B,cAAM,OAAO;AAAA,UACX;AAAA,UACA,GAAG,MAAM,KAAK,QAAQ,KAAK;AAAA,UAC3B,GAAG,MAAM,KAAK,QAAQ,KAAK;AAAA,UAC3B,OAAO,QAAQ;AAAA,QACjB;AACA,uBAAe,IAAI;AACnB,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,IACA,CAAC,SAAS,YAAY;AAAA,EACxB;AAEA;AAAA,IACE;AAAA,IACA,OAAO;AAAA,MACL,QAAQ,MAAM,UAAU,IAAI;AAAA,MAC5B,SAAS,MAAM,UAAU,IAAI,IAAI;AAAA,MACjC,OAAO,MAAM,MAAM,EAAE,OAAO,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AAAA,MAC3C,UAAU,CAAC,GAAW,MAAc;AAClC,cAAM,MAAM,MAAM,SAAS,sBAAsB;AACjD,YAAI,CAAC,IAAK;AACV,gBAAQ,CAAC,YAAY;AACnB,gBAAM,OAAO;AAAA,YACX,GAAG;AAAA,YACH,GAAG,IAAI,QAAQ,IAAI,IAAI,QAAQ;AAAA,YAC/B,GAAG,IAAI,SAAS,IAAI,IAAI,QAAQ;AAAA,YAChC,OAAO,QAAQ;AAAA,UACjB;AACA,yBAAe,IAAI;AACnB,iBAAO;AAAA,QACT,CAAC;AAAA,MACH;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,OAAO,SAAS,cAAc,MAAM,SAAS;AAAA,EAChD;AAKA,YAAU,MAAM;AACd,UAAM,OAAO,MAAM;AACnB,QAAI,CAAC,KAAM;AAEX,UAAM,UAAU,CAAC,UAAsB;AACrC,UAAI,CAAC,MAAM,WAAW,CAAC,MAAM,QAAS;AACtC,YAAM,eAAe;AACrB,YAAM,MAAM,KAAK,sBAAsB;AACvC;AAAA,QACE,MAAM,SAAS,IAAI,OAAO,IAAI;AAAA,QAC9B,MAAM,UAAU,IAAI;AAAA,QACpB,MAAM,UAAU,IAAI;AAAA,MACtB;AAAA,IACF;AAEA,SAAK,iBAAiB,SAAS,SAAS,EAAE,SAAS,MAAM,CAAC;AAC1D,WAAO,MAAM,KAAK,oBAAoB,SAAS,OAAO;AAAA,EACxD,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,gBAAgB,CAAC,UAA6C;AAClE,QAAI,MAAM,WAAW,EAAG;AACxB,SAAK,UAAU,EAAE,SAAS,MAAM,WAAW,GAAG,MAAM,UAAU,KAAK,GAAG,GAAG,MAAM,UAAU,KAAK,EAAE;AAChG,UAAM,cAAc,kBAAkB,MAAM,SAAS;AAAA,EACvD;AAEA,QAAM,gBAAgB,CAAC,UAA6C;AAClE,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,SAAS,MAAM,YAAY,MAAM,UAAW;AACjD,UAAM,EAAE,GAAG,MAAM,GAAG,MAAM,UAAU,MAAM,GAAG,GAAG,MAAM,UAAU,MAAM,EAAE,CAAC;AAAA,EAC3E;AAEA,QAAM,UAAU,CAAC,UAA6C;AAC5D,QAAI,KAAK,SAAS,YAAY,MAAM,UAAW;AAC/C,SAAK,UAAU;AACf,QAAI,MAAM,cAAc,kBAAkB,MAAM,SAAS,GAAG;AAC1D,YAAM,cAAc,sBAAsB,MAAM,SAAS;AAAA,IAC3D;AAAA,EACF;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA,OAAO,EAAE,OAAO;AAAA,MAEhB;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,MAAK;AAAA,YACL,cAAY;AAAA,YACZ,oBAAkB,GAAG,GAAG;AAAA,YACxB,UAAU;AAAA,YACV,WAAU;AAAA,YACV;AAAA,YACA;AAAA,YACA,aAAa;AAAA,YACb,iBAAiB;AAAA,YACjB,WAAW,CAAC,UAAU;AACpB,oBAAM,QAAQ,MAAM,WAAW,KAAK;AACpC,sBAAQ,MAAM,KAAK;AAAA,gBACjB,KAAK;AAAA,gBACL,KAAK;AACH,wBAAM,eAAe;AACrB,4BAAU,IAAI;AACd;AAAA,gBACF,KAAK;AACH,wBAAM,eAAe;AACrB,4BAAU,IAAI,IAAI;AAClB;AAAA,gBACF,KAAK;AACH,wBAAM,eAAe;AACrB,wBAAM,EAAE,OAAO,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AAC9B;AAAA,gBACF,KAAK;AACH,wBAAM,eAAe;AACrB,wBAAM,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC;AACpC;AAAA,gBACF,KAAK;AACH,wBAAM,eAAe;AACrB,wBAAM,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC;AACpC;AAAA,gBACF,KAAK;AACH,wBAAM,eAAe;AACrB,wBAAM,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC;AACpC;AAAA,gBACF,KAAK;AACH,wBAAM,eAAe;AACrB,wBAAM,EAAE,GAAG,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC;AACpC;AAAA,gBACF;AACE;AAAA,cACJ;AAAA,YACF;AAAA,YAEA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO,EAAE,WAAW,aAAa,KAAK,CAAC,OAAO,KAAK,CAAC,aAAa,KAAK,KAAK,IAAI;AAAA,gBAC/E,sBAAmB;AAAA,gBAElB;AAAA;AAAA,YACH;AAAA;AAAA,QACF;AAAA,QAEA,oBAAC,OAAE,IAAI,GAAG,GAAG,SAAS,WAAU,WAAU,gFAE1C;AAAA,QAEC,YACC,qBAAC,SAAI,WAAU,sHACb;AAAA,8BAAC,gBAAa,OAAM,YAAW,SAAS,MAAM,UAAU,IAAI,IAAI,GAC9D,8BAAC,UAAK,eAAW,MAAC,oBAAC,GACrB;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,MAAM,EAAE,OAAO,GAAG,GAAG,GAAG,GAAG,EAAE,CAAC;AAAA,cAC7C,cAAW;AAAA,cACX,WAAU;AAAA,cAET;AAAA,qBAAK,MAAM,KAAK,QAAQ,GAAG;AAAA,gBAAE;AAAA;AAAA;AAAA,UAChC;AAAA,UACA,oBAAC,gBAAa,OAAM,WAAU,SAAS,MAAM,UAAU,IAAI,GACzD,8BAAC,UAAK,eAAW,MAAC,eAAC,GACrB;AAAA,WACF;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,cAAY;AAAA,MACZ,WAAU;AAAA,MAET;AAAA;AAAA,EACH;AAEJ;AAEA,IAAO,wBAAQ;","names":[]}