@misoto22/design 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/README.md +12 -0
  2. package/dist/agent/Accordion.md +1 -1
  3. package/dist/agent/Alert.md +1 -1
  4. package/dist/agent/AppShell.md +1 -1
  5. package/dist/agent/ArchitectureFigure.md +48 -0
  6. package/dist/agent/AreaChart.md +1 -1
  7. package/dist/agent/Article.md +1 -1
  8. package/dist/agent/Avatar.md +1 -1
  9. package/dist/agent/Badge.md +1 -1
  10. package/dist/agent/BarChart.md +1 -1
  11. package/dist/agent/BarList.md +1 -1
  12. package/dist/agent/BigNumber.md +1 -1
  13. package/dist/agent/BoxPlot.md +1 -1
  14. package/dist/agent/Breadcrumb.md +1 -1
  15. package/dist/agent/BulletChart.md +1 -1
  16. package/dist/agent/Button.md +1 -1
  17. package/dist/agent/Calendar.md +1 -1
  18. package/dist/agent/Card.md +1 -1
  19. package/dist/agent/Checkbox.md +1 -1
  20. package/dist/agent/Collapsible.md +1 -1
  21. package/dist/agent/Combobox.md +1 -1
  22. package/dist/agent/Command.md +1 -1
  23. package/dist/agent/ComposedChart.md +1 -1
  24. package/dist/agent/ContextMenu.md +1 -1
  25. package/dist/agent/DataflowFigure.md +27 -0
  26. package/dist/agent/DatePicker.md +1 -1
  27. package/dist/agent/Diagram.md +1 -1
  28. package/dist/agent/DiagramCanvas.md +47 -0
  29. package/dist/agent/DiagramExportMenu.md +38 -0
  30. package/dist/agent/DiagramInspector.md +50 -0
  31. package/dist/agent/DiagramLegend.md +34 -0
  32. package/dist/agent/DiagramMinimap.md +48 -0
  33. package/dist/agent/DiagramToolbar.md +47 -0
  34. package/dist/agent/Dialog.md +1 -1
  35. package/dist/agent/DropdownMenu.md +1 -1
  36. package/dist/agent/EmptyState.md +1 -1
  37. package/dist/agent/ErrorState.md +1 -1
  38. package/dist/agent/Facet.md +1 -1
  39. package/dist/agent/Field.md +1 -1
  40. package/dist/agent/FigureBand.md +1 -1
  41. package/dist/agent/FloatingIconButton.md +1 -1
  42. package/dist/agent/FunnelChart.md +1 -1
  43. package/dist/agent/Heatmap.md +1 -1
  44. package/dist/agent/Histogram.md +1 -1
  45. package/dist/agent/Input.md +1 -1
  46. package/dist/agent/Kbd.md +1 -1
  47. package/dist/agent/LifecycleFigure.md +28 -0
  48. package/dist/agent/LineChart.md +1 -1
  49. package/dist/agent/LinkArrow.md +1 -1
  50. package/dist/agent/NativeSelect.md +1 -1
  51. package/dist/agent/NavItem.md +1 -1
  52. package/dist/agent/Pagination.md +1 -1
  53. package/dist/agent/PieChart.md +1 -1
  54. package/dist/agent/Popover.md +1 -1
  55. package/dist/agent/Progress.md +1 -1
  56. package/dist/agent/RadarChart.md +1 -1
  57. package/dist/agent/RadialChart.md +1 -1
  58. package/dist/agent/RadioGroup.md +1 -1
  59. package/dist/agent/SankeyChart.md +1 -1
  60. package/dist/agent/ScatterChart.md +1 -1
  61. package/dist/agent/ScrollArea.md +1 -1
  62. package/dist/agent/SearchableMenu.md +1 -1
  63. package/dist/agent/Select.md +1 -1
  64. package/dist/agent/Separator.md +1 -1
  65. package/dist/agent/SequenceFigure.md +28 -0
  66. package/dist/agent/Sheet.md +1 -1
  67. package/dist/agent/Skeleton.md +1 -1
  68. package/dist/agent/Slider.md +1 -1
  69. package/dist/agent/Sparkline.md +1 -1
  70. package/dist/agent/Spinner.md +1 -1
  71. package/dist/agent/StatusDot.md +1 -1
  72. package/dist/agent/StatusPill.md +1 -1
  73. package/dist/agent/Steps.md +1 -1
  74. package/dist/agent/Switch.md +1 -1
  75. package/dist/agent/Table.md +1 -1
  76. package/dist/agent/Tabs.md +1 -1
  77. package/dist/agent/Tag.md +1 -1
  78. package/dist/agent/Textarea.md +1 -1
  79. package/dist/agent/Toast.md +1 -1
  80. package/dist/agent/ToggleGroup.md +1 -1
  81. package/dist/agent/Tooltip.md +1 -1
  82. package/dist/agent/TreemapChart.md +1 -1
  83. package/dist/agent/WaterfallChart.md +1 -1
  84. package/dist/agent/WorkflowFigure.md +28 -0
  85. package/dist/agent/catalog.json +223 -3
  86. package/dist/agent/index.md +15 -1
  87. package/dist/charts/index.d.ts +2 -1
  88. package/dist/charts/lib/export.d.ts +33 -43
  89. package/dist/charts/lib/export.js +16 -158
  90. package/dist/charts/lib/export.js.map +1 -1
  91. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.d.ts +49 -0
  92. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js +135 -0
  93. package/dist/diagrams/ArchitectureFigure/ArchitectureFigure.js.map +1 -0
  94. package/dist/diagrams/DataflowFigure/DataflowFigure.d.ts +28 -0
  95. package/dist/diagrams/DataflowFigure/DataflowFigure.js +129 -0
  96. package/dist/diagrams/DataflowFigure/DataflowFigure.js.map +1 -0
  97. package/dist/diagrams/DiagramCanvas/DiagramCanvas.d.ts +64 -0
  98. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js +222 -0
  99. package/dist/diagrams/DiagramCanvas/DiagramCanvas.js.map +1 -0
  100. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.d.ts +72 -0
  101. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js +125 -0
  102. package/dist/diagrams/DiagramExportMenu/DiagramExportMenu.js.map +1 -0
  103. package/dist/diagrams/DiagramInspector/DiagramInspector.d.ts +70 -0
  104. package/dist/diagrams/DiagramInspector/DiagramInspector.js +94 -0
  105. package/dist/diagrams/DiagramInspector/DiagramInspector.js.map +1 -0
  106. package/dist/diagrams/DiagramLegend/DiagramLegend.d.ts +42 -0
  107. package/dist/diagrams/DiagramLegend/DiagramLegend.js +35 -0
  108. package/dist/diagrams/DiagramLegend/DiagramLegend.js.map +1 -0
  109. package/dist/diagrams/DiagramMinimap/DiagramMinimap.d.ts +61 -0
  110. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js +79 -0
  111. package/dist/diagrams/DiagramMinimap/DiagramMinimap.js.map +1 -0
  112. package/dist/diagrams/DiagramToolbar/DiagramToolbar.d.ts +48 -0
  113. package/dist/diagrams/DiagramToolbar/DiagramToolbar.js +53 -0
  114. package/dist/diagrams/DiagramToolbar/DiagramToolbar.js.map +1 -0
  115. package/dist/diagrams/LifecycleFigure/LifecycleFigure.d.ts +31 -0
  116. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js +202 -0
  117. package/dist/diagrams/LifecycleFigure/LifecycleFigure.js.map +1 -0
  118. package/dist/diagrams/SequenceFigure/SequenceFigure.d.ts +38 -0
  119. package/dist/diagrams/SequenceFigure/SequenceFigure.js +231 -0
  120. package/dist/diagrams/SequenceFigure/SequenceFigure.js.map +1 -0
  121. package/dist/diagrams/WorkflowFigure/WorkflowFigure.d.ts +34 -0
  122. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js +211 -0
  123. package/dist/diagrams/WorkflowFigure/WorkflowFigure.js.map +1 -0
  124. package/dist/diagrams/index.d.ts +16 -0
  125. package/dist/diagrams/index.js +19 -0
  126. package/dist/diagrams/index.js.map +1 -0
  127. package/dist/diagrams/lib/frame.d.ts +97 -0
  128. package/dist/diagrams/lib/frame.js +116 -0
  129. package/dist/diagrams/lib/frame.js.map +1 -0
  130. package/dist/diagrams/lib/geometry.d.ts +114 -0
  131. package/dist/diagrams/lib/geometry.js +113 -0
  132. package/dist/diagrams/lib/geometry.js.map +1 -0
  133. package/dist/diagrams/lib/legend.d.ts +32 -0
  134. package/dist/diagrams/lib/legend.js +84 -0
  135. package/dist/diagrams/lib/legend.js.map +1 -0
  136. package/dist/diagrams/lib/marks.d.ts +207 -0
  137. package/dist/diagrams/lib/marks.js +480 -0
  138. package/dist/diagrams/lib/marks.js.map +1 -0
  139. package/dist/diagrams/lib/route.d.ts +85 -0
  140. package/dist/diagrams/lib/route.js +178 -0
  141. package/dist/diagrams/lib/route.js.map +1 -0
  142. package/dist/diagrams/lib/wires.d.ts +59 -0
  143. package/dist/diagrams/lib/wires.js +90 -0
  144. package/dist/diagrams/lib/wires.js.map +1 -0
  145. package/dist/diagrams/spec.d.ts +353 -0
  146. package/dist/diagrams/spec.js +1 -0
  147. package/dist/diagrams/spec.js.map +1 -0
  148. package/dist/lib/svg-export.d.ts +138 -0
  149. package/dist/lib/svg-export.js +198 -0
  150. package/dist/lib/svg-export.js.map +1 -0
  151. package/dist/semantic.css +34 -0
  152. package/dist/styles.css +1 -1
  153. package/dist/tokens.d.ts +16 -0
  154. package/dist/tokens.js +82 -0
  155. package/dist/tokens.json +82 -0
  156. package/package.json +5 -1
  157. package/skills/misoto22-design/SKILL.md +21 -7
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.1",
2
+ "version": "0.7.0",
3
3
  "groups": [
4
4
  "Actions",
5
5
  "Forms",
@@ -9,7 +9,8 @@
9
9
  "Display",
10
10
  "Surfaces",
11
11
  "Data",
12
- "Charts"
12
+ "Charts",
13
+ "Diagrams"
13
14
  ],
14
15
  "components": [
15
16
  {
@@ -1613,6 +1614,211 @@
1613
1614
  "line-chart",
1614
1615
  "sparkline"
1615
1616
  ]
1617
+ },
1618
+ {
1619
+ "slug": "architecture-figure",
1620
+ "entry": "@misoto22/design/diagrams",
1621
+ "name": "ArchitectureFigure",
1622
+ "group": "Diagrams",
1623
+ "summary": "A component map: services, datastores, trust boundaries, and what talks to what.",
1624
+ "when": "Reach for it when the question is \"what talks to what\". If the question is \"in what order\", that is a workflow or a sequence; if it is \"what is in this arrow\", that is a data flow.",
1625
+ "accessibility": [
1626
+ "The <svg> is role=\"img\" with a name, so a screen reader announces a picture instead of walking two hundred <text> nodes in drawing order.",
1627
+ "The diagram’s content is published beside it as an ordinary list — every node with its kind, every relationship as \"A → B: over HTTPS\". That list is where the meaning lives for anyone not looking at the picture.",
1628
+ "Passing onSelectNode turns that list into real buttons, which is the keyboard’s only route to a selection: the plates inside the picture are presentational by construction."
1629
+ ],
1630
+ "related": [
1631
+ "dataflow-figure",
1632
+ "diagram",
1633
+ "diagram-canvas"
1634
+ ]
1635
+ },
1636
+ {
1637
+ "slug": "workflow-figure",
1638
+ "entry": "@misoto22/design/diagrams",
1639
+ "name": "WorkflowFigure",
1640
+ "group": "Diagrams",
1641
+ "summary": "A process in lanes and phases, with the main path drawn heavier than its branches.",
1642
+ "when": "A runbook, an approval chain, a CI pipeline — anything with an owner per step. mainPath is what turns fourteen boxes into a diagram with a subject.",
1643
+ "accessibility": [
1644
+ "Same contract as every figure: a named picture, with its nodes and relationships published as text beside it.",
1645
+ "The exception lane is the one washed band in the system’s diagrams, and it is still labelled — the wash is not carrying the meaning on its own."
1646
+ ],
1647
+ "related": [
1648
+ "lifecycle-figure",
1649
+ "sequence-figure",
1650
+ "steps"
1651
+ ]
1652
+ },
1653
+ {
1654
+ "slug": "sequence-figure",
1655
+ "entry": "@misoto22/design/diagrams",
1656
+ "name": "SequenceFigure",
1657
+ "group": "Diagrams",
1658
+ "summary": "A call chain over time: who asks whom, in what order, and what comes back.",
1659
+ "when": "The only figure whose vertical axis means something. A message carries an explicit y, so two calls eight units apart happened together and two two hundred apart did not.",
1660
+ "accessibility": [
1661
+ "Return messages are dashed AND take an open arrowhead — two signals, because the reply is what a reader most often needs to pick out of a dense trace.",
1662
+ "The message list beside the picture reads in order, which is the same order the axis is drawn in."
1663
+ ],
1664
+ "related": [
1665
+ "workflow-figure",
1666
+ "architecture-figure"
1667
+ ]
1668
+ },
1669
+ {
1670
+ "slug": "dataflow-figure",
1671
+ "entry": "@misoto22/design/diagrams",
1672
+ "name": "DataflowFigure",
1673
+ "group": "Diagrams",
1674
+ "summary": "A pipeline: where data comes from, what happens to it, and who ends up with it.",
1675
+ "when": "Structurally close to an architecture map, read for a different question. classification gets its own chip because a governance reviewer is looking for exactly that.",
1676
+ "accessibility": [
1677
+ "A flow’s classification is folded into its summary line, so \"clickstream — PII touch\" reaches a reader who cannot see the chip."
1678
+ ],
1679
+ "related": [
1680
+ "architecture-figure",
1681
+ "lifecycle-figure"
1682
+ ]
1683
+ },
1684
+ {
1685
+ "slug": "lifecycle-figure",
1686
+ "entry": "@misoto22/design/diagrams",
1687
+ "name": "LifecycleFigure",
1688
+ "group": "Diagrams",
1689
+ "summary": "A state machine: what something can be, and what moves it between states.",
1690
+ "when": "The one figure that spends colour, and it spends exactly the two tokens the system reserves for state. Every other distinction is shape, so a greyscale print keeps six of the eight.",
1691
+ "accessibility": [
1692
+ "success and failure are the only coloured marks in the package’s diagrams, and each is also a distinct plate shape — colour is never the only carrier.",
1693
+ "The main rail is drawn between consecutive states in the first lane even when the specification does not list those transitions, because they are the diagram’s spine rather than its exceptions."
1694
+ ],
1695
+ "related": [
1696
+ "workflow-figure",
1697
+ "steps"
1698
+ ]
1699
+ },
1700
+ {
1701
+ "slug": "diagram-canvas",
1702
+ "entry": "@misoto22/design/diagrams",
1703
+ "name": "DiagramCanvas",
1704
+ "group": "Diagrams",
1705
+ "summary": "A frame that a picture larger than it can be panned and zoomed inside.",
1706
+ "when": "Any oversized figure — an SVG, an image, a table that will not fold. It knows nothing about nodes, which is what makes it reusable.",
1707
+ "accessibility": [
1708
+ "The frame is a real tab stop, so the keyboard controls can be pressed at all.",
1709
+ "A plain wheel scrolls the page. Zoom needs the platform modifier, so the canvas is never a scroll trap in the middle of an article."
1710
+ ],
1711
+ "keyboard": [
1712
+ {
1713
+ "keys": [
1714
+ "+",
1715
+ "="
1716
+ ],
1717
+ "does": "Zooms in about the centre of the frame."
1718
+ },
1719
+ {
1720
+ "keys": [
1721
+ "-"
1722
+ ],
1723
+ "does": "Zooms out."
1724
+ },
1725
+ {
1726
+ "keys": [
1727
+ "0"
1728
+ ],
1729
+ "does": "Resets the scale and the offset together."
1730
+ },
1731
+ {
1732
+ "keys": [
1733
+ "←",
1734
+ "→",
1735
+ "↑",
1736
+ "↓"
1737
+ ],
1738
+ "does": "Pans. Shift pans further per press."
1739
+ }
1740
+ ],
1741
+ "related": [
1742
+ "diagram-minimap",
1743
+ "architecture-figure",
1744
+ "scroll-area"
1745
+ ]
1746
+ },
1747
+ {
1748
+ "slug": "diagram-toolbar",
1749
+ "entry": "@misoto22/design/diagrams",
1750
+ "name": "DiagramToolbar",
1751
+ "group": "Diagrams",
1752
+ "summary": "A bar of actions belonging to the surface underneath them.",
1753
+ "when": "FloatingIconButton is one pinned action. This is the container for several, so they read as one object rather than as a scatter.",
1754
+ "accessibility": [
1755
+ "role=\"toolbar\" announces a toolbar rather than six unrelated buttons. It does not implement roving focus — Tab visits every control, which is honest for a bar of three."
1756
+ ],
1757
+ "related": [
1758
+ "floating-icon-button",
1759
+ "diagram-export-menu"
1760
+ ]
1761
+ },
1762
+ {
1763
+ "slug": "diagram-export-menu",
1764
+ "entry": "@misoto22/design/diagrams",
1765
+ "name": "DiagramExportMenu",
1766
+ "group": "Diagrams",
1767
+ "summary": "Taking the figure off the page: PNG, JPEG, WebP, SVG and a 1200×630 share card.",
1768
+ "when": "It does the export rather than emitting a format name, because the interesting half — baking custom properties into real colours before serialising — is the half a caller would not know to write.",
1769
+ "accessibility": [
1770
+ "A failed export is reported through onResult rather than swallowed: a click that quietly does nothing is indistinguishable from a broken button."
1771
+ ],
1772
+ "related": [
1773
+ "dropdown-menu",
1774
+ "diagram-toolbar"
1775
+ ]
1776
+ },
1777
+ {
1778
+ "slug": "diagram-inspector",
1779
+ "entry": "@misoto22/design/diagrams",
1780
+ "name": "DiagramInspector",
1781
+ "group": "Diagrams",
1782
+ "summary": "What the reader just picked, written out beside the picture.",
1783
+ "when": "A node holds about eight words before it stops being a node. Everything past those — the port, the owner, the six relationships — belongs here.",
1784
+ "accessibility": [
1785
+ "A labelled region with aria-live=\"polite\", not a dialog: the reader clicked a node, they did not open anything, so focus is never trapped or demanded.",
1786
+ "Relationships are real buttons when they carry onSelect, which is how the graph becomes walkable peer by peer from the keyboard."
1787
+ ],
1788
+ "related": [
1789
+ "card",
1790
+ "diagram-canvas"
1791
+ ]
1792
+ },
1793
+ {
1794
+ "slug": "diagram-minimap",
1795
+ "entry": "@misoto22/design/diagrams",
1796
+ "name": "DiagramMinimap",
1797
+ "group": "Diagrams",
1798
+ "summary": "Where you are in something bigger than the window.",
1799
+ "when": "Pairs with DiagramCanvas. The viewport rectangle is derived from the canvas’s own view, never stored — a map that disagrees with its territory is worse than none.",
1800
+ "accessibility": [
1801
+ "The miniature is aria-hidden. The figure it mirrors already publishes its own summary, and a second copy would read the whole diagram out twice."
1802
+ ],
1803
+ "related": [
1804
+ "diagram-canvas"
1805
+ ]
1806
+ },
1807
+ {
1808
+ "slug": "diagram-legend",
1809
+ "entry": "@misoto22/design/diagrams",
1810
+ "name": "DiagramLegend",
1811
+ "group": "Diagrams",
1812
+ "summary": "The key: which drawn form means which kind of thing.",
1813
+ "when": "Not optional furniture in a monochrome system. When a queue and a cache differ by a sigil rather than a colour, this is the only place a reader is told what the sigil means.",
1814
+ "accessibility": [
1815
+ "A list of pairs rather than a row of spans, so the count is part of what a screen reader says about it.",
1816
+ "kindLegend, variantLegend and stateLegend build the standard sets out of the renderers’ own drawing code, so the key can never drift from the figure."
1817
+ ],
1818
+ "related": [
1819
+ "architecture-figure",
1820
+ "lifecycle-figure"
1821
+ ]
1616
1822
  }
1617
1823
  ],
1618
1824
  "exports": {
@@ -1786,7 +1992,21 @@
1786
1992
  "TreemapExtras": "TreemapChart",
1787
1993
  "WaterfallChart": "WaterfallChart",
1788
1994
  "WaterfallStepType": "WaterfallChart",
1789
- "WaterfallDirection": "WaterfallChart"
1995
+ "WaterfallDirection": "WaterfallChart",
1996
+ "ArchitectureFigure": "ArchitectureFigure",
1997
+ "DataflowFigure": "DataflowFigure",
1998
+ "DiagramCanvas": "DiagramCanvas",
1999
+ "DiagramExportMenu": "DiagramExportMenu",
2000
+ "exportFigure": "DiagramExportMenu",
2001
+ "ExportFormat": "DiagramExportMenu",
2002
+ "DiagramInspector": "DiagramInspector",
2003
+ "DiagramLegend": "DiagramLegend",
2004
+ "DiagramMinimap": "DiagramMinimap",
2005
+ "DiagramToolbar": "DiagramToolbar",
2006
+ "DiagramToolbarGroup": "DiagramToolbar",
2007
+ "LifecycleFigure": "LifecycleFigure",
2008
+ "SequenceFigure": "SequenceFigure",
2009
+ "WorkflowFigure": "WorkflowFigure"
1790
2010
  },
1791
2011
  "themeAxes": [
1792
2012
  {
@@ -1,4 +1,4 @@
1
- # @misoto22/design 0.6.1
1
+ # @misoto22/design 0.7.0
2
2
 
3
3
  > Monochrome design system: portable CSS tokens and accessible React 19
4
4
  > primitives. Components ship compiled — import them, do not copy them.
@@ -129,3 +129,17 @@ re-pointed in a stylesheet.
129
129
  - **Histogram** — The shape of one distribution.
130
130
  - **WaterfallChart** — How a total got from one figure to another.
131
131
  - **Facet** — The same chart once per group, on one shared scale.
132
+
133
+ ### Diagrams
134
+
135
+ - **ArchitectureFigure** — A component map: services, datastores, trust boundaries, and what talks to what.
136
+ - **WorkflowFigure** — A process in lanes and phases, with the main path drawn heavier than its branches.
137
+ - **SequenceFigure** — A call chain over time: who asks whom, in what order, and what comes back.
138
+ - **DataflowFigure** — A pipeline: where data comes from, what happens to it, and who ends up with it.
139
+ - **LifecycleFigure** — A state machine: what something can be, and what moves it between states.
140
+ - **DiagramCanvas** — A frame that a picture larger than it can be panned and zoomed inside.
141
+ - **DiagramToolbar** — A bar of actions belonging to the surface underneath them.
142
+ - **DiagramExportMenu** — Taking the figure off the page: PNG, JPEG, WebP, SVG and a 1200×630 share card.
143
+ - **DiagramInspector** — What the reader just picked, written out beside the picture.
144
+ - **DiagramMinimap** — Where you are in something bigger than the window.
145
+ - **DiagramLegend** — The key: which drawn form means which kind of thing.
@@ -31,11 +31,12 @@ export { SONIFY_DEFAULTS, SonifyController, SonifyOptions, SonifyPoint, SonifySe
31
31
  export { ChartSonifyButton, ChartSonifyButtonProps, Sonify, SonifyAlign, SonifyProps, UseSonifyOptions, UseSonifyResult, chartSonifySeries, useChartSonifySeries, useSonify } from './lib/sonify-control.js';
32
32
  export { ChartControls, ChartControlsProps, ChartExportFormat, ChartToolbar, ChartToolbarProps, Toolbar, ToolbarProps } from './lib/toolbar.js';
33
33
  export { ChartZoom, ChartZoomOptions, ChartZoomSurface, ChartZoomSurfaceProps, clampWindow, panWindow, useChartZoom, zoomWindow } from './lib/zoom.js';
34
- export { ChartPngOptions, chartToCsv, chartToPng, downloadBlob, exportFilename } from './lib/export.js';
34
+ export { ChartPngOptions, chartToCsv, chartToPng, exportFilename } from './lib/export.js';
35
35
  export { ANNOTATION_LAYER, Annotation, AnnotationProps, AnnotationWeight, ReferenceBand, ReferenceBandProps, ReferenceLine, ReferenceLineProps } from './lib/annotations.js';
36
36
  export { ValueLabelMode, Values, ValuesProps } from './lib/values.js';
37
37
  export { ChartEmpty, ChartEmptyProps } from './lib/empty.js';
38
38
  export { NumberFormatOptions, NumberStyle, defaultTick, formatNumber } from './lib/format.js';
39
+ export { downloadBlob } from '../lib/svg-export.js';
39
40
  import 'react';
40
41
  import 'recharts';
41
42
  import 'recharts/types/component/DefaultTooltipContent';
@@ -1,6 +1,22 @@
1
1
  import { ChartColumn } from './figure.js';
2
+ export { downloadBlob } from '../../lib/svg-export.js';
2
3
  import 'react';
3
4
 
5
+ /**
6
+ * Taking a chart off the page: a PNG of the plot, or a CSV of its rows.
7
+ *
8
+ * The general half — walking a live tree to inline its resolved paint,
9
+ * serialising a standalone SVG document, rasterising one through a canvas — is
10
+ * not chart-shaped at all, and lives in `src/lib/svg-export.ts` shared with the
11
+ * diagrams entry. What is left here is what only a Recharts chart knows: which
12
+ * `<svg>` in the subtree is the plot, what a row of chart data looks like as a
13
+ * CSV record, and that the ground behind an exported plot is `--chart-surface`.
14
+ *
15
+ * Nothing here touches the DOM at module scope. Every browser API is reached
16
+ * for inside a function body, so this module is safe to import from a server
17
+ * component and only fails when a consumer actually asks for a file on a
18
+ * server, which is a call that cannot succeed anywhere.
19
+ */
4
20
  interface ChartPngOptions {
5
21
  /**
6
22
  * Printed above the plot in the exported image. Pass the figure's title: a
@@ -21,46 +37,27 @@ interface ChartPngOptions {
21
37
  /**
22
38
  * The chart's `<svg>`, rasterised to a PNG blob.
23
39
  *
24
- * ## The problem this function exists to solve
25
- *
26
- * Serialising the `<svg>` and handing it to an `<img>` puts it in a document
27
- * of its own. That document has none of the page's stylesheets, so every
28
- * `var(--color-desktop-0)`, `var(--chart-fill)` and `var(--ink)` in the markup
29
- * resolves to nothing — and a paint that resolves to nothing is not a fallback
30
- * colour, it is an invisible mark. Serialise a chart naively and you get an
31
- * empty rectangle with axes on it.
32
- *
33
- * ## The solution
40
+ * Three chart-specific decisions and nothing else. `serializeSvg` does the
41
+ * work, and its comment carries the reason a naive `XMLSerializer` export of a
42
+ * themed chart comes out as an empty rectangle with axes on it.
34
43
  *
35
- * Walk the live tree and the clone in step, and on every node write
36
- * `getComputedStyle`'s answer for the painted properties into an inline
37
- * `style`. Computed values have already had `var()` substituted by the engine,
38
- * so `fill: var(--color-desktop-0)` arrives as `fill: rgb(16, 16, 16)` — and,
39
- * because the walk reads the LIVE element, it reads it under whichever theme
40
- * is currently applied. Light and dark come out right for free, with no theme
41
- * argument anywhere in this file.
42
- *
43
- * `<stop>` elements are walked too, which is the half that is easy to miss: a
44
- * series is painted `fill="url(#…-colors-desktop)"`, the url survives
45
- * serialisation because the gradient is inside the same `<svg>`, and it is the
46
- * gradient's STOPS that carry the custom properties.
44
+ * 1. WHICH `<svg>` the plot, not the first one in the subtree. See
45
+ * `findPlotSvg`.
46
+ * 2. WHAT GROUND `--chart-surface`, resolved against the live element, which
47
+ * is what makes a dark-mode export come out dark rather than transparent.
48
+ * 3. WHAT INK AND FACE for the caption `--ink` and the element's own
49
+ * resolved family, so the exported title matches the page it came from.
47
50
  *
48
51
  * ## What this cannot capture, honestly
49
52
  *
50
53
  * - **Anything drawn in HTML.** The legend, the tooltip, the brush strip and
51
54
  * the hidden data table are DOM, not SVG. The PNG is the plot.
52
- * - **Web fonts.** The isolated document cannot fetch the page's `@font-face`
53
- * sources, so text is rasterised in whatever the resolved family stack finds
54
- * locally. Type metrics will differ from the screen.
55
- * - **Cross-origin images.** A chart with an external `<image>` in it taints
56
- * the canvas and `toBlob` throws; there is no way around it from script.
57
- * - **The exact rendering.** This is the browser's SVG rasteriser re-drawing
58
- * the markup, not a screenshot. Antialiasing, `backdrop-filter` and any
59
- * effect a page stylesheet applied from outside the `<svg>` are not in it.
55
+ * - **Web fonts, cross-origin images, and the exact rendering.** See
56
+ * `serializeSvg` and `rasterize`.
60
57
  *
61
58
  * @example
62
59
  * const blob = await chartToPng(plotRef.current, { title: 'Visitors per month' })
63
- * downloadBlob(blob, 'visitors-per-month.png')
60
+ * downloadBlob(blob, exportFilename('Visitors per month', 'png'))
64
61
  */
65
62
  declare function chartToPng(element: HTMLElement | SVGSVGElement, options?: ChartPngOptions): Promise<Blob>;
66
63
  /**
@@ -83,21 +80,14 @@ declare function chartToPng(element: HTMLElement | SVGSVGElement, options?: Char
83
80
  * downloadBlob(new Blob([csv], { type: 'text/csv;charset=utf-8' }), 'visitors.csv')
84
81
  */
85
82
  declare function chartToCsv(rows: Record<string, unknown>[], columns: ChartColumn[]): string;
86
- /**
87
- * Hands a blob to the browser as a download.
88
- *
89
- * Throws rather than no-oping without a `document`: a download is something a
90
- * person just asked for, and a silent return would look to them like a click
91
- * that did nothing.
92
- */
93
- declare function downloadBlob(blob: Blob, filename: string): void;
83
+
94
84
  /**
95
85
  * A figure's title as a filename.
96
86
  *
97
- * Lowercase, ASCII-safe and hyphenated, because a downloaded file crosses into
98
- * shells, zip archives and Windows paths where a title's spaces and slashes
99
- * are someone else's problem.
87
+ * The shared slugger, with `chart` rather than `figure` as the fallback for a
88
+ * title that slugs to nothing an export from this entry is a chart, and the
89
+ * name a reader finds in their downloads folder should say so.
100
90
  */
101
91
  declare function exportFilename(title: string, extension: string): string;
102
92
 
103
- export { type ChartPngOptions, chartToCsv, chartToPng, downloadBlob, exportFilename };
93
+ export { type ChartPngOptions, chartToCsv, chartToPng, exportFilename };
@@ -1,154 +1,28 @@
1
- const SVG_NS = "http://www.w3.org/2000/svg";
2
- const PAINTED_PROPERTIES = [
3
- "display",
4
- "visibility",
5
- "opacity",
6
- "color",
7
- "fill",
8
- "fill-opacity",
9
- "fill-rule",
10
- "stroke",
11
- "stroke-opacity",
12
- "stroke-width",
13
- "stroke-linecap",
14
- "stroke-linejoin",
15
- "stroke-dasharray",
16
- "stroke-dashoffset",
17
- "stop-color",
18
- "stop-opacity",
19
- "flood-color",
20
- "flood-opacity",
21
- "mask",
22
- "filter",
23
- "clip-path",
24
- "clip-rule",
25
- "paint-order",
26
- "mix-blend-mode",
27
- "shape-rendering",
28
- "text-anchor",
29
- "dominant-baseline",
30
- "font-family",
31
- "font-size",
32
- "font-style",
33
- "font-weight",
34
- "letter-spacing",
35
- "text-transform"
36
- ];
1
+ import {
2
+ downloadBlob,
3
+ exportFilename as figureFilename,
4
+ rasterize,
5
+ readToken,
6
+ serializeSvg
7
+ } from "../../lib/svg-export.js";
37
8
  async function chartToPng(element, options = {}) {
38
9
  const { title, scale = 2, background, padding = 16 } = options;
39
10
  const svg = findPlotSvg(element);
40
11
  if (!svg) throw new Error("chartToPng: found no <svg> to export inside the element it was given");
41
- const box = svg.getBoundingClientRect();
42
- const width = Math.ceil(box.width);
43
- const height = Math.ceil(box.height);
44
- if (width === 0 || height === 0) {
45
- throw new Error("chartToPng: the chart has no measured size yet \u2014 export it after it renders");
46
- }
47
- const clone = svg.cloneNode(true);
48
- inlinePaint(svg, clone);
49
- clone.setAttribute("width", String(width));
50
- clone.setAttribute("height", String(height));
51
- const titleBand = title ? 30 : 0;
52
- const totalWidth = width + padding * 2;
53
- const totalHeight = height + padding * 2 + titleBand;
54
- const page = document.createElementNS(SVG_NS, "svg");
55
- page.setAttribute("xmlns", SVG_NS);
56
- page.setAttribute("width", String(totalWidth));
57
- page.setAttribute("height", String(totalHeight));
58
- page.setAttribute("viewBox", `0 0 ${totalWidth} ${totalHeight}`);
59
- const plate = document.createElementNS(SVG_NS, "rect");
60
- plate.setAttribute("width", String(totalWidth));
61
- plate.setAttribute("height", String(totalHeight));
62
- plate.setAttribute("fill", background ?? readToken(element, "--chart-surface", "#ffffff"));
63
- page.append(plate);
64
- if (title) {
65
- const caption = document.createElementNS(SVG_NS, "text");
66
- caption.setAttribute("x", String(padding));
67
- caption.setAttribute("y", String(padding + 14));
68
- caption.setAttribute("fill", readToken(element, "--ink", "#101010"));
69
- caption.setAttribute("font-size", "13");
70
- caption.setAttribute("font-weight", "500");
71
- caption.setAttribute("font-family", getComputedStyle(element).fontFamily || "sans-serif");
72
- caption.textContent = title;
73
- page.append(caption);
74
- }
75
- const group = document.createElementNS(SVG_NS, "g");
76
- group.setAttribute("transform", `translate(${padding}, ${padding + titleBand})`);
77
- group.append(clone);
78
- page.append(group);
79
- const markup = new XMLSerializer().serializeToString(page);
80
- const url = URL.createObjectURL(
81
- new Blob([`<?xml version="1.0" encoding="UTF-8"?>
82
- ${markup}`], {
83
- type: "image/svg+xml;charset=utf-8"
84
- })
85
- );
86
- try {
87
- const image = await loadImage(url);
88
- const canvas = document.createElement("canvas");
89
- canvas.width = Math.ceil(totalWidth * scale);
90
- canvas.height = Math.ceil(totalHeight * scale);
91
- const context = canvas.getContext("2d");
92
- if (!context) throw new Error("chartToPng: this browser gave back no 2D canvas context");
93
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
94
- return await canvasToBlob(canvas);
95
- } finally {
96
- URL.revokeObjectURL(url);
97
- }
12
+ const markup = serializeSvg(svg, {
13
+ padding,
14
+ background: background ?? readToken(element, "--chart-surface", "#ffffff"),
15
+ title,
16
+ titleColor: readToken(element, "--ink", "#101010"),
17
+ titleFont: getComputedStyle(element).fontFamily || "sans-serif"
18
+ });
19
+ return rasterize(markup, "png", { scale });
98
20
  }
99
21
  function findPlotSvg(element) {
100
22
  if (element.tagName.toLowerCase() === "svg") return element;
101
23
  const scope = element.querySelector('[data-slot="chart"]') ?? element;
102
24
  return scope.querySelector("svg.recharts-surface") ?? scope.querySelector("svg");
103
25
  }
104
- function inlinePaint(live, clone) {
105
- const computed = getComputedStyle(live);
106
- let css = "";
107
- for (const property of PAINTED_PROPERTIES) {
108
- const value = computed.getPropertyValue(property);
109
- if (value) css += `${property}:${value};`;
110
- }
111
- if (css) clone.setAttribute("style", css);
112
- const children = live.children;
113
- const clones = clone.children;
114
- for (let index = 0; index < children.length; index += 1) {
115
- const liveChild = children[index];
116
- const cloneChild = clones[index];
117
- if (liveChild && cloneChild) inlinePaint(liveChild, cloneChild);
118
- }
119
- }
120
- function readToken(element, name, fallback) {
121
- const value = getComputedStyle(element).getPropertyValue(name).trim();
122
- return value && !value.includes("var(") ? value : fallback;
123
- }
124
- function loadImage(url) {
125
- return new Promise((resolve, reject) => {
126
- const image = new Image();
127
- image.addEventListener("load", () => resolve(image));
128
- image.addEventListener(
129
- "error",
130
- () => reject(new Error("chartToPng: the browser refused to decode the serialised chart"))
131
- );
132
- image.src = url;
133
- });
134
- }
135
- function canvasToBlob(canvas) {
136
- return new Promise((resolve, reject) => {
137
- try {
138
- canvas.toBlob((blob) => {
139
- if (blob) resolve(blob);
140
- else reject(new Error("chartToPng: the canvas produced no PNG data"));
141
- }, "image/png");
142
- } catch (cause) {
143
- reject(
144
- new Error(
145
- "chartToPng: the canvas is tainted, which means the chart contains a cross-origin image",
146
- { cause }
147
- )
148
- );
149
- }
150
- });
151
- }
152
26
  const UTF8_BOM = "\uFEFF";
153
27
  function chartToCsv(rows, columns) {
154
28
  if (columns.length === 0) return UTF8_BOM;
@@ -176,24 +50,8 @@ function cellText(value) {
176
50
  if (value instanceof Date) return value.toISOString();
177
51
  return "";
178
52
  }
179
- function downloadBlob(blob, filename) {
180
- if (typeof document === "undefined") {
181
- throw new Error("downloadBlob: needs a browser document \u2014 call it from an event handler");
182
- }
183
- const url = URL.createObjectURL(blob);
184
- const anchor = document.createElement("a");
185
- anchor.href = url;
186
- anchor.download = filename;
187
- anchor.rel = "noopener";
188
- anchor.style.display = "none";
189
- document.body.append(anchor);
190
- anchor.click();
191
- anchor.remove();
192
- setTimeout(() => URL.revokeObjectURL(url), 0);
193
- }
194
53
  function exportFilename(title, extension) {
195
- const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
196
- return `${slug || "chart"}.${extension}`;
54
+ return figureFilename(title, extension, "chart");
197
55
  }
198
56
  export {
199
57
  chartToCsv,