basalt-ui 1.30.2 → 1.32.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 (357) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +54 -0
  3. package/MIGRATING.md +256 -12
  4. package/agent/rules/basalt-controls.md +19 -19
  5. package/agent/rules/basalt-mantine.md +5 -5
  6. package/configs/oxlint-plugin.js +371 -4
  7. package/configs/oxlint-plugin.test.ts +327 -2
  8. package/configs/oxlint.json +2 -1
  9. package/dist/agent-chat/composer.d.ts.map +1 -1
  10. package/dist/agent-chat/composer.js +41 -43
  11. package/dist/agent-chat/thread-workspace.d.ts +2 -2
  12. package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
  13. package/dist/agent-chat/thread-workspace.js +3 -4
  14. package/dist/agent-chat/use-keyboard-inset.d.ts +2 -0
  15. package/dist/agent-chat/use-keyboard-inset.d.ts.map +1 -0
  16. package/dist/agent-chat/use-keyboard-inset.js +34 -0
  17. package/dist/charts/cursor/touch-pin.d.ts +45 -0
  18. package/dist/charts/cursor/touch-pin.d.ts.map +1 -0
  19. package/dist/charts/cursor/touch-pin.js +79 -0
  20. package/dist/charts/hooks/use-discrete-cursor.d.ts +66 -0
  21. package/dist/charts/hooks/use-discrete-cursor.d.ts.map +1 -0
  22. package/dist/charts/hooks/use-discrete-cursor.js +198 -0
  23. package/dist/charts/hooks/use-hysteresis.d.ts +18 -0
  24. package/dist/charts/hooks/use-hysteresis.d.ts.map +1 -0
  25. package/dist/charts/hooks/use-hysteresis.js +12 -0
  26. package/dist/charts/hooks/useBandPlot.d.ts +7 -0
  27. package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
  28. package/dist/charts/hooks/useBandPlot.js +62 -18
  29. package/dist/charts/hooks/useChartCursor.d.ts +40 -11
  30. package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
  31. package/dist/charts/hooks/useChartCursor.js +102 -11
  32. package/dist/charts/index.d.ts +0 -1
  33. package/dist/charts/index.d.ts.map +1 -1
  34. package/dist/charts/index.js +0 -10
  35. package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
  36. package/dist/charts/kinds/BandStrip.js +47 -82
  37. package/dist/charts/kinds/Donut.d.ts +9 -3
  38. package/dist/charts/kinds/Donut.d.ts.map +1 -1
  39. package/dist/charts/kinds/Donut.js +110 -48
  40. package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
  41. package/dist/charts/kinds/DualPanel.js +138 -145
  42. package/dist/charts/kinds/Heatmap.d.ts +7 -0
  43. package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
  44. package/dist/charts/kinds/Heatmap.js +37 -16
  45. package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
  46. package/dist/charts/kinds/MirroredBars.js +68 -111
  47. package/dist/charts/kinds/band-chrome.d.ts +31 -0
  48. package/dist/charts/kinds/band-chrome.d.ts.map +1 -0
  49. package/dist/charts/kinds/band-chrome.js +71 -0
  50. package/dist/charts/kinds/donut-layout.d.ts +47 -0
  51. package/dist/charts/kinds/donut-layout.d.ts.map +1 -0
  52. package/dist/charts/kinds/donut-layout.js +52 -0
  53. package/dist/charts/layout/auto-margin.d.ts +7 -2
  54. package/dist/charts/layout/auto-margin.d.ts.map +1 -1
  55. package/dist/charts/layout/auto-margin.js +12 -3
  56. package/dist/charts/layout/end-labels.d.ts +64 -0
  57. package/dist/charts/layout/end-labels.d.ts.map +1 -0
  58. package/dist/charts/layout/end-labels.js +76 -0
  59. package/dist/charts/primitives/Axes.d.ts +32 -4
  60. package/dist/charts/primitives/Axes.d.ts.map +1 -1
  61. package/dist/charts/primitives/Axes.js +62 -16
  62. package/dist/charts/primitives/CartesianChart.d.ts +7 -6
  63. package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
  64. package/dist/charts/primitives/CartesianChart.js +264 -145
  65. package/dist/charts/primitives/ChartCard.d.ts +0 -14
  66. package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
  67. package/dist/charts/primitives/ChartCard.js +56 -21
  68. package/dist/charts/primitives/ChartFrame.d.ts +28 -22
  69. package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
  70. package/dist/charts/primitives/ChartFrame.js +116 -34
  71. package/dist/charts/primitives/ChartLegend.d.ts +19 -15
  72. package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
  73. package/dist/charts/primitives/ChartLegend.js +86 -73
  74. package/dist/charts/primitives/ChartTooltip.d.ts +5 -0
  75. package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
  76. package/dist/charts/primitives/ChartTooltip.js +20 -4
  77. package/dist/charts/primitives/Crosshair.js +2 -2
  78. package/dist/charts/primitives/HoverOverlay.d.ts +29 -9
  79. package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
  80. package/dist/charts/primitives/HoverOverlay.js +54 -27
  81. package/dist/charts/primitives/HoverOverlay.module.css +13 -0
  82. package/dist/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
  83. package/dist/charts/primitives/chart-card-context.d.ts +33 -0
  84. package/dist/charts/primitives/chart-card-context.d.ts.map +1 -0
  85. package/dist/charts/primitives/chart-card-context.js +18 -0
  86. package/dist/charts/primitives/chart-frame-layout.d.ts +93 -113
  87. package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
  88. package/dist/charts/primitives/chart-frame-layout.js +75 -34
  89. package/dist/charts/primitives/chart-layout.d.ts +112 -0
  90. package/dist/charts/primitives/chart-layout.d.ts.map +1 -0
  91. package/dist/charts/primitives/chart-layout.js +175 -0
  92. package/dist/charts/primitives/chart-tier.d.ts +13 -11
  93. package/dist/charts/primitives/chart-tier.d.ts.map +1 -1
  94. package/dist/charts/primitives/chart-tier.js +42 -13
  95. package/dist/charts/primitives/legend-disclosure.d.ts +27 -0
  96. package/dist/charts/primitives/legend-disclosure.d.ts.map +1 -0
  97. package/dist/charts/primitives/legend-disclosure.js +153 -0
  98. package/dist/charts/series.d.ts +4 -1
  99. package/dist/charts/series.d.ts.map +1 -1
  100. package/dist/charts/utils/ticks.d.ts +27 -8
  101. package/dist/charts/utils/ticks.d.ts.map +1 -1
  102. package/dist/charts/utils/ticks.js +35 -11
  103. package/dist/common/errors.d.ts +11 -1
  104. package/dist/common/errors.d.ts.map +1 -1
  105. package/dist/common/errors.js +16 -2
  106. package/dist/common/hit-floor.d.ts +10 -0
  107. package/dist/common/hit-floor.d.ts.map +1 -0
  108. package/dist/common/hit-floor.js +6 -0
  109. package/dist/{shell → common}/isomorphic-layout-effect.d.ts +4 -3
  110. package/dist/common/isomorphic-layout-effect.d.ts.map +1 -0
  111. package/dist/common/use-measured-widths.d.ts +37 -0
  112. package/dist/common/use-measured-widths.d.ts.map +1 -0
  113. package/dist/common/use-measured-widths.js +51 -0
  114. package/dist/common/use-media-query.d.ts +6 -0
  115. package/dist/common/use-media-query.d.ts.map +1 -0
  116. package/dist/common/use-media-query.js +40 -0
  117. package/dist/common/validate.d.ts +9 -0
  118. package/dist/common/validate.d.ts.map +1 -1
  119. package/dist/common/validate.js +9 -4
  120. package/dist/content/article-layout.d.ts +4 -2
  121. package/dist/content/article-layout.d.ts.map +1 -1
  122. package/dist/content/article-layout.js +43 -3
  123. package/dist/content/article-layout.module.css +45 -4
  124. package/dist/content/article-layout.module.css.d.ts +3 -0
  125. package/dist/content/code-block.d.ts.map +1 -1
  126. package/dist/content/code-block.js +40 -2
  127. package/dist/content/code-block.module.css +32 -2
  128. package/dist/content/prose.module.css +13 -8
  129. package/dist/controls/actions.d.ts +29 -6
  130. package/dist/controls/actions.d.ts.map +1 -1
  131. package/dist/controls/actions.js +158 -27
  132. package/dist/controls/actions.module.css +14 -1
  133. package/dist/controls/actions.module.css.d.ts +1 -0
  134. package/dist/controls/controls.module.css +13 -31
  135. package/dist/controls/enum-filter.js +2 -2
  136. package/dist/controls/filter-context.d.ts +10 -0
  137. package/dist/controls/filter-context.d.ts.map +1 -1
  138. package/dist/controls/filter-context.js +2 -0
  139. package/dist/controls/filter-pill.d.ts.map +1 -1
  140. package/dist/controls/filter-pill.js +1 -0
  141. package/dist/controls/index.d.ts +1 -1
  142. package/dist/controls/index.d.ts.map +1 -1
  143. package/dist/controls/index.js +0 -2
  144. package/dist/controls/multi-select-filter.js +2 -2
  145. package/dist/controls/sync-button.module.css +2 -1
  146. package/dist/dashboard/section.d.ts.map +1 -1
  147. package/dist/dashboard/section.js +1 -0
  148. package/dist/dashboard/section.module.css +1 -26
  149. package/dist/dashboard/stat-card.d.ts +1 -1
  150. package/dist/dashboard/stat-card.d.ts.map +1 -1
  151. package/dist/dashboard/stat-card.js +4 -1
  152. package/dist/dashboard/stat-card.module.css +6 -9
  153. package/dist/dashboard/stat-group.module.css +33 -66
  154. package/dist/dashboard/widget-grid.d.ts.map +1 -1
  155. package/dist/dashboard/widget-grid.module.css +11 -38
  156. package/dist/dashboard/widget-header.d.ts +14 -4
  157. package/dist/dashboard/widget-header.d.ts.map +1 -1
  158. package/dist/dashboard/widget-header.js +23 -6
  159. package/dist/dashboard/widget-header.module.css +145 -133
  160. package/dist/dashboard/widget-header.module.css.d.ts +2 -0
  161. package/dist/data/data-table-state.d.ts +83 -0
  162. package/dist/data/data-table-state.d.ts.map +1 -0
  163. package/dist/data/data-table-state.js +139 -0
  164. package/dist/data/data-table.d.ts +56 -2
  165. package/dist/data/data-table.d.ts.map +1 -1
  166. package/dist/data/data-table.js +436 -336
  167. package/dist/data/data-table.module.css +94 -0
  168. package/dist/data/data-table.module.css.d.ts +11 -0
  169. package/dist/data/virtual-list.d.ts.map +1 -1
  170. package/dist/data/virtual-list.js +10 -0
  171. package/dist/forms/form-layout.d.ts.map +1 -1
  172. package/dist/forms/form-layout.js +3 -3
  173. package/dist/forms/form-layout.module.css +20 -5
  174. package/dist/forms/form-layout.module.css.d.ts +1 -0
  175. package/dist/guard/index.d.ts +8 -2
  176. package/dist/guard/index.d.ts.map +1 -1
  177. package/dist/guard/index.js +68 -2
  178. package/dist/guard/types.d.ts +3 -3
  179. package/dist/guard/types.d.ts.map +1 -1
  180. package/dist/index.d.ts +1 -1
  181. package/dist/index.d.ts.map +1 -1
  182. package/dist/index.js +3 -1
  183. package/dist/notifications/center.d.ts.map +1 -1
  184. package/dist/provider/index.d.ts +20 -1
  185. package/dist/provider/index.d.ts.map +1 -1
  186. package/dist/provider/index.js +30 -8
  187. package/dist/shell/app-brand.d.ts.map +1 -1
  188. package/dist/shell/app-brand.js +1 -0
  189. package/dist/shell/app-brand.module.css +2 -2
  190. package/dist/shell/app-header.module.css +18 -8
  191. package/dist/shell/app-main.module.css +54 -3
  192. package/dist/shell/app-main.module.css.d.ts +1 -0
  193. package/dist/shell/app-mobile-nav.module.css +16 -4
  194. package/dist/shell/app-sidebar.module.css +4 -6
  195. package/dist/shell/aside-docking.d.ts +31 -0
  196. package/dist/shell/aside-docking.d.ts.map +1 -0
  197. package/dist/shell/aside-docking.js +19 -0
  198. package/dist/shell/index.d.ts +1 -0
  199. package/dist/shell/index.d.ts.map +1 -1
  200. package/dist/shell/index.js +56 -16
  201. package/dist/shell/mobile-nav-model.d.ts +1 -1
  202. package/dist/shell/page-aside.d.ts +22 -3
  203. package/dist/shell/page-aside.d.ts.map +1 -1
  204. package/dist/shell/page-aside.js +27 -13
  205. package/dist/shell/page-bar.d.ts.map +1 -1
  206. package/dist/shell/page-bar.js +10 -5
  207. package/dist/shell/page-bar.module.css +10 -34
  208. package/dist/shell/use-breakpoint.d.ts +3 -4
  209. package/dist/shell/use-breakpoint.d.ts.map +1 -1
  210. package/dist/shell/use-breakpoint.js +3 -25
  211. package/dist/shell/use-size-class.d.ts +6 -0
  212. package/dist/shell/use-size-class.d.ts.map +1 -0
  213. package/dist/shell/use-size-class.js +19 -0
  214. package/dist/state/persisted-or-local.d.ts +4 -1
  215. package/dist/state/persisted-or-local.d.ts.map +1 -1
  216. package/dist/state/persisted-or-local.js +4 -3
  217. package/dist/state/persisted.d.ts +10 -0
  218. package/dist/state/persisted.d.ts.map +1 -1
  219. package/dist/state/persisted.js +44 -14
  220. package/dist/styles.css +41 -0
  221. package/dist/surfaces.d.ts +4 -4
  222. package/dist/surfaces.d.ts.map +1 -1
  223. package/dist/surfaces.js +11 -3
  224. package/dist/theme/controls.module.css +7 -0
  225. package/dist/theme/ctl-theme.d.ts +1 -0
  226. package/dist/theme/ctl-theme.d.ts.map +1 -1
  227. package/dist/theme/ctl-theme.js +12 -4
  228. package/dist/theme/floating.module.css +37 -0
  229. package/dist/theme/floating.module.css.d.ts +3 -0
  230. package/dist/theme/index.d.ts +2 -2
  231. package/dist/theme/index.d.ts.map +1 -1
  232. package/dist/theme/index.js +34 -5
  233. package/dist/theme/nav-link.module.css +7 -0
  234. package/dist/theme-lab/derive-controls.d.ts.map +1 -1
  235. package/dist/tokens/index.d.ts +7 -16
  236. package/dist/tokens/index.d.ts.map +1 -1
  237. package/dist/tokens/index.js +23 -28
  238. package/dist/tokens/palette.d.ts +5 -27
  239. package/dist/tokens/palette.d.ts.map +1 -1
  240. package/dist/tokens/palette.js +8 -30
  241. package/dist/tokens/size-classes.d.ts +40 -0
  242. package/dist/tokens/size-classes.d.ts.map +1 -0
  243. package/dist/tokens/size-classes.js +29 -0
  244. package/dist/tokens.css +8 -4
  245. package/llms.txt +3 -3
  246. package/package.json +18 -17
  247. package/src/agent-chat/composer.tsx +74 -47
  248. package/src/agent-chat/thread-workspace.tsx +8 -11
  249. package/src/agent-chat/use-keyboard-inset.ts +68 -0
  250. package/src/charts/cursor/touch-pin.ts +138 -0
  251. package/src/charts/hooks/use-discrete-cursor.ts +335 -0
  252. package/src/charts/hooks/use-hysteresis.ts +26 -0
  253. package/src/charts/hooks/useBandPlot.ts +90 -27
  254. package/src/charts/hooks/useChartCursor.ts +211 -26
  255. package/src/charts/index.ts +0 -8
  256. package/src/charts/kinds/BandStrip.tsx +25 -61
  257. package/src/charts/kinds/Donut.tsx +153 -62
  258. package/src/charts/kinds/DualPanel.tsx +21 -26
  259. package/src/charts/kinds/Heatmap.tsx +61 -22
  260. package/src/charts/kinds/MirroredBars.tsx +23 -65
  261. package/src/charts/kinds/band-chrome.tsx +91 -0
  262. package/src/charts/kinds/donut-layout.ts +82 -0
  263. package/src/charts/layout/auto-margin.ts +24 -5
  264. package/src/charts/layout/end-labels.ts +135 -0
  265. package/src/charts/primitives/Axes.tsx +112 -21
  266. package/src/charts/primitives/CartesianChart.tsx +282 -111
  267. package/src/charts/primitives/ChartCard.tsx +119 -48
  268. package/src/charts/primitives/ChartFrame.tsx +204 -60
  269. package/src/charts/primitives/ChartLegend.tsx +115 -78
  270. package/src/charts/primitives/ChartTooltip.tsx +24 -3
  271. package/src/charts/primitives/Crosshair.tsx +2 -2
  272. package/src/charts/primitives/HoverOverlay.module.css +13 -0
  273. package/src/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
  274. package/src/charts/primitives/HoverOverlay.tsx +62 -20
  275. package/src/charts/primitives/chart-card-context.ts +46 -0
  276. package/src/charts/primitives/chart-frame-layout.ts +207 -182
  277. package/src/charts/primitives/chart-layout.ts +318 -0
  278. package/src/charts/primitives/chart-tier.tsx +66 -21
  279. package/src/charts/primitives/legend-disclosure.tsx +177 -0
  280. package/src/charts/series.ts +4 -1
  281. package/src/charts/utils/ticks.ts +95 -19
  282. package/src/common/errors.ts +25 -1
  283. package/src/common/hit-floor.ts +9 -0
  284. package/src/{shell → common}/isomorphic-layout-effect.ts +4 -3
  285. package/src/common/use-measured-widths.ts +88 -0
  286. package/src/common/use-media-query.ts +56 -0
  287. package/src/common/validate.ts +17 -4
  288. package/src/content/article-layout.module.css +45 -4
  289. package/src/content/article-layout.module.css.d.ts +3 -0
  290. package/src/content/article-layout.tsx +60 -5
  291. package/src/content/code-block.module.css +32 -2
  292. package/src/content/code-block.tsx +47 -3
  293. package/src/content/prose.module.css +13 -8
  294. package/src/controls/actions.module.css +14 -1
  295. package/src/controls/actions.module.css.d.ts +1 -0
  296. package/src/controls/actions.tsx +223 -75
  297. package/src/controls/controls.module.css +13 -31
  298. package/src/controls/enum-filter.tsx +2 -2
  299. package/src/controls/filter-context.tsx +11 -0
  300. package/src/controls/filter-pill.tsx +1 -0
  301. package/src/controls/index.ts +0 -1
  302. package/src/controls/multi-select-filter.tsx +2 -2
  303. package/src/controls/sync-button.module.css +2 -1
  304. package/src/dashboard/section.module.css +1 -26
  305. package/src/dashboard/section.tsx +1 -0
  306. package/src/dashboard/stat-card.module.css +6 -9
  307. package/src/dashboard/stat-card.tsx +6 -3
  308. package/src/dashboard/stat-group.module.css +33 -66
  309. package/src/dashboard/widget-grid.module.css +11 -38
  310. package/src/dashboard/widget-grid.tsx +7 -6
  311. package/src/dashboard/widget-header.module.css +145 -133
  312. package/src/dashboard/widget-header.module.css.d.ts +2 -0
  313. package/src/dashboard/widget-header.tsx +45 -11
  314. package/src/data/data-table-state.ts +218 -0
  315. package/src/data/data-table.module.css +94 -0
  316. package/src/data/data-table.module.css.d.ts +11 -0
  317. package/src/data/data-table.tsx +547 -299
  318. package/src/data/virtual-list.tsx +31 -0
  319. package/src/forms/form-layout.module.css +20 -5
  320. package/src/forms/form-layout.module.css.d.ts +1 -0
  321. package/src/forms/form-layout.tsx +12 -10
  322. package/src/guard/index.ts +188 -3
  323. package/src/guard/types.ts +3 -2
  324. package/src/index.ts +2 -0
  325. package/src/notifications/center.tsx +3 -0
  326. package/src/provider/index.tsx +60 -7
  327. package/src/shell/app-brand.module.css +2 -2
  328. package/src/shell/app-brand.tsx +1 -0
  329. package/src/shell/app-header.module.css +18 -8
  330. package/src/shell/app-main.module.css +54 -3
  331. package/src/shell/app-main.module.css.d.ts +1 -0
  332. package/src/shell/app-mobile-nav.module.css +16 -4
  333. package/src/shell/app-sidebar.module.css +4 -6
  334. package/src/shell/aside-docking.ts +54 -0
  335. package/src/shell/index.tsx +178 -129
  336. package/src/shell/mobile-nav-model.ts +1 -1
  337. package/src/shell/page-aside.tsx +70 -21
  338. package/src/shell/page-bar.module.css +10 -34
  339. package/src/shell/page-bar.tsx +10 -5
  340. package/src/shell/use-breakpoint.ts +9 -45
  341. package/src/shell/use-size-class.ts +31 -0
  342. package/src/state/persisted-or-local.ts +10 -4
  343. package/src/state/persisted.ts +76 -17
  344. package/src/styles.css +41 -0
  345. package/src/surfaces.ts +9 -1
  346. package/src/theme/controls.module.css +7 -0
  347. package/src/theme/ctl-theme.tsx +15 -3
  348. package/src/theme/floating.module.css +37 -0
  349. package/src/theme/floating.module.css.d.ts +3 -0
  350. package/src/theme/index.ts +46 -8
  351. package/src/theme/nav-link.module.css +7 -0
  352. package/src/theme-lab/derive-controls.tsx +2 -0
  353. package/src/tokens/index.ts +20 -30
  354. package/src/tokens/palette.ts +6 -28
  355. package/src/tokens/size-classes.ts +46 -0
  356. package/dist/shell/isomorphic-layout-effect.d.ts.map +0 -1
  357. /package/dist/{shell → common}/isomorphic-layout-effect.js +0 -0
@@ -1,9 +1,20 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useReducer, useState } from "react";
2
3
  import { cx } from "../../common/props.js";
3
4
  import { VX } from "../../tokens/index.js";
4
5
  import { WidgetHeader } from "../../dashboard/widget-header.js";
6
+ import { useChartSize } from "../hooks/useChartSize.js";
7
+ import { useHysteresis } from "../hooks/use-hysteresis.js";
8
+ import { ChartCardContext, legendSlotReducer } from "./chart-card-context.js";
5
9
  import { ChartEmpty, ChartError, ChartPending, resolveChartState } from "./ChartPending.js";
10
+ const CARD_SHORT_HEIGHT = 280;
11
+ const SHORT_EXIT_SLACK = 24;
6
12
  const cardStyle = {
13
+ display: "flex",
14
+ flexDirection: "column",
15
+ minHeight: 0,
16
+ containerType: "inline-size",
17
+ containerName: "basalt-card",
7
18
  borderRadius: VX.radiusCard,
8
19
  boxShadow: VX.shadowCard,
9
20
  backgroundColor: VX.surface.panel
@@ -17,11 +28,19 @@ const bodyClipStyle = {
17
28
  borderBottomLeftRadius: VX.radiusCard,
18
29
  borderBottomRightRadius: VX.radiusCard,
19
30
  overflow: "hidden",
31
+ flex: "1 1 auto",
32
+ minHeight: 0,
33
+ // A column, so a `ChartFrame` inside can grow into the body a stretched grid row hands the card.
34
+ display: "flex",
35
+ flexDirection: "column",
20
36
  padding: "2px var(--vx-space-card-inset-x, 0.8125rem) var(--vx-space-card-inset-y, 0.6875rem)"
21
37
  };
22
38
  const headerWrapStyle = {
23
39
  padding: "var(--vx-space-card-inset-y, 0.6875rem) var(--vx-space-card-inset-x, 0.8125rem) 4px"
24
40
  };
41
+ const headerWrapShortStyle = {
42
+ padding: "4px var(--vx-space-card-inset-x, 0.8125rem) 4px"
43
+ };
25
44
  function ChartCard({
26
45
  title,
27
46
  icon,
@@ -41,38 +60,54 @@ function ChartCard({
41
60
  style,
42
61
  children
43
62
  }) {
63
+ const [legendSlot, setLegendSlot] = useState(null);
64
+ const { ref: sizeRef, height: measuredHeight } = useChartSize();
44
65
  const resolvedState = resolveChartState({ ...state !== void 0 && { state } });
45
66
  const hasHeader = title !== void 0 || info !== void 0 || value !== void 0 || actions !== void 0 || icon !== void 0 || count !== void 0;
46
- return /* @__PURE__ */ jsxs(
67
+ const short = useHysteresis(
68
+ (wasShort) => measuredHeight > 0 && measuredHeight < (wasShort ? CARD_SHORT_HEIGHT + SHORT_EXIT_SLACK : CARD_SHORT_HEIGHT),
69
+ false
70
+ );
71
+ const [legendClaimants, dispatchLegendSlot] = useReducer(legendSlotReducer, []);
72
+ const context = useMemo(
73
+ () => ({ legendSlot, inCard: true, short, legendClaimants, dispatchLegendSlot }),
74
+ [legendSlot, short, legendClaimants]
75
+ );
76
+ return /* @__PURE__ */ jsx(ChartCardContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(
47
77
  "div",
48
78
  {
79
+ ref: sizeRef,
49
80
  className: cx(classNames?.root, className),
50
81
  style: { ...cardStyle, ...style },
51
82
  ...resolvedState === "pending" && { "aria-busy": "true" },
83
+ ...short && { "data-basalt-card-short": "" },
52
84
  children: [
53
- hasHeader && /* @__PURE__ */ jsx(
85
+ hasHeader && /* @__PURE__ */ jsxs(
54
86
  "div",
55
87
  {
56
88
  ...classNames?.header !== void 0 && { className: classNames.header },
57
- style: headerWrapStyle,
58
- children: /* @__PURE__ */ jsx(
59
- WidgetHeader,
60
- {
61
- tier: "widget",
62
- title: title ?? "",
63
- ...icon !== void 0 && { icon },
64
- ...subtitle !== void 0 && { subtitle },
65
- ...info !== void 0 && { info },
66
- ...value !== void 0 && { value },
67
- ...delta !== void 0 && { delta },
68
- ...deltaPeriod !== void 0 && { deltaPeriod },
69
- ...deltaPolarity !== void 0 && { deltaPolarity },
70
- ...count !== void 0 && { count },
71
- ...actions !== void 0 && {
72
- actions: /* @__PURE__ */ jsx("span", { "data-basalt-tier": "widget", children: actions })
89
+ style: short ? headerWrapShortStyle : headerWrapStyle,
90
+ children: [
91
+ /* @__PURE__ */ jsx(
92
+ WidgetHeader,
93
+ {
94
+ tier: "widget",
95
+ title: title ?? "",
96
+ ...icon !== void 0 && { icon },
97
+ ...subtitle !== void 0 && { subtitle },
98
+ ...info !== void 0 && { info },
99
+ ...value !== void 0 && { value },
100
+ ...delta !== void 0 && { delta },
101
+ ...deltaPeriod !== void 0 && { deltaPeriod },
102
+ ...deltaPolarity !== void 0 && { deltaPolarity },
103
+ ...count !== void 0 && { count },
104
+ ...actions !== void 0 && {
105
+ actions: /* @__PURE__ */ jsx("span", { "data-basalt-tier": "widget", children: actions })
106
+ }
73
107
  }
74
- }
75
- )
108
+ ),
109
+ /* @__PURE__ */ jsx("div", { ref: setLegendSlot, "data-basalt-legend-slot": "" })
110
+ ]
76
111
  }
77
112
  ),
78
113
  /* @__PURE__ */ jsx(
@@ -101,7 +136,7 @@ function ChartCard({
101
136
  )
102
137
  ]
103
138
  }
104
- );
139
+ ) });
105
140
  }
106
141
  export {
107
142
  ChartCard
@@ -5,7 +5,7 @@ import type { ResponsiveChartHeight } from './chart-frame-layout.js';
5
5
  import type { ChartState } from './ChartPending.js';
6
6
  /** Re-exported for `ChartFrame.test.tsx` and any other consumer that previously reached these
7
7
  * through `ChartFrame` — the layout math itself lives in `./chart-frame-layout` (pure, DOM-free). */
8
- export { legendEntryCap, resolveLegendRollup, resolvePlotRect, resolveChartTier, } from './chart-frame-layout.js';
8
+ export { legendEntryCap, resolveLegendRollup, resolvePlotRect } from './chart-frame-layout.js';
9
9
  export type { ResponsiveChartHeight } from './chart-frame-layout.js';
10
10
  /** Legend configuration for {@link ChartFrame}. Omit entirely (or pass `{}`) for the default
11
11
  * bottom-placed legend; pass `false` only for the sparkline exemption. */
@@ -13,22 +13,12 @@ export type ChartFrameLegend = {
13
13
  /** Default 'bottom'. */
14
14
  placement?: LegendPlacement;
15
15
  /**
16
- * Rollup cap at high cardinality: the legend renders this many ENTRIES and folds the remainder
17
- * into a `+N more` disclosure chip.
16
+ * @deprecated Removed in 1.32.0 — the legend now fits by measured width every time and folds
17
+ * overflow into an `All N` disclosure (`docs/CHARTS-SPEC.md` §8); drop the prop.
18
18
  *
19
- * **It counts entries, not rows** — the name is inherited and now load-bearing across
20
- * `ChartTierMetrics.legendMaxRows`, so it is not renamed in a patch. `maxRows: 3` on a 7-entry
21
- * legend renders three entries, which is ONE row at any width they fit on. `ChartLegend` slices
22
- * (`entries.slice(0, cap)`); nothing here measures rows.
23
- *
24
- * An explicit value WINS OUTRIGHT — over the phone tier's two-row default (`chartTierMetrics`)
25
- * AND over a `fill` frame's measured fit (`legendEntryCap`). 1.30.0 fixed only the first half:
26
- * the fit still ran `Math.min(fitted, caller)` on top, so meteo's 7-entry meteogram legend in a
27
- * 150px docked row rendered 2 entries at `maxRows` 3, 6 AND 99 — five series drawn in colours
28
- * nothing named. Both losers are DEFAULTS (the tier's, and `VX.minPlotHeight`); this is the one
29
- * number a caller stated, so under `fill` the PLOT yields the height instead
30
- * ({@link resolvePlotRect}'s `legendWins`). State a number too large for the box and the plot
31
- * gets what is left — visible, and yours.
19
+ * Until then an explicit value still WINS OUTRIGHT over the measured fit, and it counts ENTRIES,
20
+ * not rows: `maxRows: 3` on a 7-entry legend renders three entries. Under `fill` the PLOT yields
21
+ * the height ({@link resolvePlotRect}'s `legendWins`).
32
22
  */
33
23
  maxRows?: number;
34
24
  /** Visually separate role: series | overlay | reference. */
@@ -43,19 +33,31 @@ export type ChartFrameLegend = {
43
33
  * plot, the tooltip, and the auto domain together (`docs/CHARTS-SPEC.md` §5).
44
34
  */
45
35
  toggle?: boolean;
36
+ /**
37
+ * Internal escape hatch for a kind that never wants its legend portalled into a `ChartCard`
38
+ * header, whatever the placement — `Donut` sets this (`docs/waves/PLAN.md` wave 3, P1-4): its
39
+ * side/bottom legend is the ring's only key, so it must stay under or beside the ring rather than
40
+ * follow every other frame's default of claiming an available header slot. `false` keeps the
41
+ * legend in its own band regardless of `placement`; omitted (default) behaves as today. Not
42
+ * something a consumer composing a kind's own `legend` prop can reach — kinds compose `ChartFrame`
43
+ * directly to set it.
44
+ */
45
+ headerSlot?: boolean;
46
46
  };
47
47
  export type ChartFrameProps = BasaltProps & {
48
48
  /** Series identity — drives the derived legend. Pass the SAME array the kind draws + tooltips from. */
49
49
  series: readonly SeriesStyle[];
50
50
  /**
51
- * Height in pixels, or one per size step. Used when neither `aspectRatio` nor `fill` is set.
52
- * Default 240. Passing it WITH `fill` is a wiring mistake, not a fallback — `fill` wins and
51
+ * Height in pixels, or one per container step. Used when neither `aspectRatio` nor `fill` is set.
52
+ * Default: derived from the measured width and container class (`resolveChartLayout`). Inside a
53
+ * `ChartCard` the resulting height is a MINIMUM — the frame grows into a taller card body. Passing it WITH `fill` is a wiring mistake, not a fallback — `fill` wins and
53
54
  * this is never read, which now warns once in dev (`common/errors.ts`' `ignoredProp`).
54
55
  *
55
- * `{ base: 180, md: 260 }` is the declarative escape from a page of fixed-height charts: the
56
- * steps are compared against the frame's own MEASURED width, not the viewport, so a chart squeezed
57
- * by a `PageAside` gets the short height on a desktop too. A plain number is unchanged and stays
58
- * supported ({@link resolveFrameHeight}).
56
+ * `{ base: 180, wide: 260 }` is the declarative escape from a page of fixed-height charts: the
57
+ * `regular`/`wide` steps are compared against the frame's own MEASURED width, not the viewport,
58
+ * so a chart squeezed by a `PageAside` gets the short height on a desktop too. A plain number is
59
+ * an override and stays supported ({@link resolveFrameHeight}); one that leaves the plot under
60
+ * `VX.minPlotHeight` warns once in dev.
59
61
  */
60
62
  height?: number | ResponsiveChartHeight;
61
63
  /** height = Math.round(containerWidth / aspectRatio). Ignored when `fill` is set — which warns
@@ -109,6 +111,10 @@ export type PlotRect = {
109
111
  width: number;
110
112
  height: number;
111
113
  hidden: ReadonlySet<string>;
114
+ /** Whether THIS frame's legend is currently portalled into a `ChartCard` header slot — a chart
115
+ * composing `ChartFrame` (e.g. `CartesianChart`'s end labels, R2C-10) reads this to skip a
116
+ * band-legend-only affordance once the header already names every series for free. */
117
+ legendInHeader: boolean;
112
118
  };
113
119
  /** A kind's own hover-dim wiring, merged into the resolved `ChartFrameLegend`. Kinds without
114
120
  * per-series highlight state (single-series `ZonedLine`, `DualPanel`) omit this. */
@@ -1 +1 @@
1
- {"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChartFrame.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartFrame.tsx"],"names":[],"mappings":"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"}
@@ -1,26 +1,24 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useState } from "react";
3
- import { ignoredProp } from "../../common/errors.js";
4
- import { useValidateProps } from "../../common/validate.js";
2
+ import { useCallback, useContext, useId, useLayoutEffect, useMemo, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import {
5
+ deprecatedProp,
6
+ ignoredProp,
7
+ legendSlotContention,
8
+ plotBelowFloor
9
+ } from "../../common/errors.js";
10
+ import { reportOnce, useValidateProps } from "../../common/validate.js";
11
+ import { SizeClassHintContext, useSizeClass } from "../../shell/use-size-class.js";
5
12
  import { VX } from "../../tokens/index.js";
6
13
  import { useChartSize } from "../hooks/useChartSize.js";
7
14
  import { deriveLegend } from "../series.js";
8
- import {
9
- resolveChartTier,
10
- resolveFrameHeight,
11
- resolveLegendRollup,
12
- resolvePlotRect
13
- } from "./chart-frame-layout.js";
14
- import { ChartTierProvider } from "./chart-tier.js";
15
+ import { ChartCardContext } from "./chart-card-context.js";
16
+ import { resolveChartLayout, resolveFrameClass } from "./chart-layout.js";
17
+ import { resolveFrameHeight, resolveLegendRollup, resolvePlotRect } from "./chart-frame-layout.js";
18
+ import { ChartTierProvider, useCoarsePointer, useViewportHeight } from "./chart-tier.js";
15
19
  import { ChartLegend } from "./ChartLegend.js";
16
20
  import { ChartEmpty, ChartError, ChartPending, resolveChartState } from "./ChartPending.js";
17
- import {
18
- legendEntryCap,
19
- resolveLegendRollup as resolveLegendRollup2,
20
- resolvePlotRect as resolvePlotRect2,
21
- resolveChartTier as resolveChartTier2
22
- } from "./chart-frame-layout.js";
23
- const DEFAULT_HEIGHT = 240;
21
+ import { legendEntryCap, resolveLegendRollup as resolveLegendRollup2, resolvePlotRect as resolvePlotRect2 } from "./chart-frame-layout.js";
24
22
  const DEFAULT_MIN_WIDTH = 200;
25
23
  const outerStyle = (fill, vertical) => ({
26
24
  width: "100%",
@@ -60,6 +58,44 @@ function ChartFrame({
60
58
  }) {
61
59
  const { ref: containerRef, width: containerW, height: containerH } = useChartSize();
62
60
  const { ref: legendRef, width: legendW, height: legendH } = useChartSize();
61
+ const { ref: slotRef, width: slotMeasuredW } = useChartSize();
62
+ const {
63
+ inCard,
64
+ legendSlot,
65
+ short: cardShort,
66
+ legendClaimants,
67
+ dispatchLegendSlot
68
+ } = useContext(ChartCardContext);
69
+ const frameId = useId();
70
+ const [slotSeedW, setSlotSeedW] = useState(0);
71
+ const placement = legend === false ? "bottom" : legend.placement ?? "bottom";
72
+ const vertical = placement === "left" || placement === "right";
73
+ const wantsHeaderSlot = legendSlot !== null && legend !== false && legend.headerSlot !== false && !vertical;
74
+ const resolvedState = resolveChartState({ ...state !== void 0 && { state }, isPending });
75
+ const legendVisible = legend !== false && resolvedState === null;
76
+ const ownsSlot = wantsHeaderSlot && legendClaimants[0] === frameId;
77
+ const slotDenied = wantsHeaderSlot && !ownsSlot && legendClaimants.includes(frameId);
78
+ useLayoutEffect(() => {
79
+ if (!wantsHeaderSlot) return void 0;
80
+ dispatchLegendSlot({ type: "claim", id: frameId });
81
+ return () => dispatchLegendSlot({ type: "release", id: frameId });
82
+ }, [wantsHeaderSlot, frameId, dispatchLegendSlot]);
83
+ useLayoutEffect(() => {
84
+ if (slotDenied && legendVisible) reportOnce("ChartFrame", legendSlotContention("ChartFrame"));
85
+ }, [slotDenied, legendVisible]);
86
+ useLayoutEffect(() => {
87
+ if (!ownsSlot || legendSlot === null) return void 0;
88
+ setSlotSeedW(legendSlot.getBoundingClientRect().width);
89
+ slotRef(legendSlot);
90
+ return () => {
91
+ slotRef(null);
92
+ setSlotSeedW(0);
93
+ };
94
+ }, [ownsSlot, legendSlot, slotRef]);
95
+ const viewportClass = useSizeClass();
96
+ const sizeClass = useContext(SizeClassHintContext) === void 0 ? "medium" : viewportClass;
97
+ const coarse = useCoarsePointer();
98
+ const viewportH = useViewportHeight();
63
99
  const [hidden, setHidden] = useState(() => /* @__PURE__ */ new Set());
64
100
  const toggleKey = useCallback((key) => {
65
101
  setHidden((prev) => {
@@ -85,20 +121,61 @@ function ChartFrame({
85
121
  ],
86
122
  [fill, height, aspectRatio]
87
123
  );
88
- const placement = legend === false ? "bottom" : legend.placement ?? "bottom";
89
- const vertical = placement === "left" || placement === "right";
90
- const resolvedState = resolveChartState({ ...state !== void 0 && { state }, isPending });
91
- const legendVisible = legend !== false && resolvedState === null;
92
- const tier = resolveChartTier(containerW);
93
- const resolvedHeight = fill ? containerH : aspectRatio !== void 0 ? Math.round(containerW / aspectRatio) : resolveFrameHeight(height ?? DEFAULT_HEIGHT, containerW);
94
- const sideLegendWidth = legendVisible && vertical ? legendW : 0;
95
- const topBottomLegendHeight = legendVisible && !vertical ? legendH : 0;
96
124
  const legendItems = legend === false ? [] : deriveLegend(series);
125
+ useValidateProps(
126
+ "ChartFrame",
127
+ () => (
128
+ // A cap covering every entry rolls nothing up — it is how `Donut` states "never roll up".
129
+ legend !== false && legend.maxRows !== void 0 && legend.maxRows < legendItems.length ? deprecatedProp(
130
+ "ChartFrame",
131
+ "legend.maxRows",
132
+ "nothing (the legend fits by measured width)",
133
+ "1.32.0",
134
+ `Its UNIT changed from rows to entries: \`maxRows: ${legend.maxRows}\` is now read as a cap of ${legend.maxRows} legend ENTRIES (not rows), with the rest folded into an \`All N\` disclosure. An explicit value still wins this release.`
135
+ ) : null
136
+ ),
137
+ [legend, legendItems.length]
138
+ );
139
+ const statedHeight = height === void 0 ? void 0 : resolveFrameHeight(height, resolveFrameClass({ frameW: containerW, sizeClass }));
140
+ const slotW = ownsSlot ? Math.max(slotMeasuredW, slotSeedW) : 0;
141
+ const groups = legend !== false && legend.groups === true;
142
+ const legendKey = legendItems.map((item) => `${item.key}\0${item.label}\0${item.role ?? ""}\0${item.note ?? ""}`).join("");
143
+ const layout = useMemo(
144
+ () => resolveChartLayout({
145
+ frameW: containerW,
146
+ slotW,
147
+ viewportH,
148
+ legendItems,
149
+ sizeClass,
150
+ coarse,
151
+ cardShort,
152
+ groups,
153
+ ...statedHeight !== void 0 && { override: { height: statedHeight } }
154
+ }),
155
+ // `legendItems` is a fresh array per render; `legendKey` is its identity.
156
+ // oxlint-disable-next-line react-hooks/exhaustive-deps
157
+ [containerW, slotW, viewportH, legendKey, sizeClass, coarse, cardShort, groups, statedHeight]
158
+ );
159
+ const fit = layout.legend.mode === "dots" || layout.legend.mode === "chips" ? layout.legend : null;
160
+ const legendMode = fit?.mode;
161
+ const showLegend = legendVisible && fit !== null;
162
+ const inHeader = showLegend && ownsSlot && fit?.where === "header";
163
+ const sideLegendWidth = showLegend && vertical ? legendW : 0;
164
+ const topBottomLegendHeight = showLegend && !vertical && !inHeader ? legendH : 0;
165
+ const computedHeight = aspectRatio !== void 0 ? Math.round(containerW / aspectRatio) : layout.height;
166
+ const grows = inCard && !fill;
167
+ const resolvedHeight = fill ? containerH : grows ? Math.max(computedHeight, containerH) : computedHeight;
168
+ const cardMinHeight = Math.max(computedHeight, VX.minPlotHeight + topBottomLegendHeight);
169
+ useValidateProps(
170
+ "ChartFrame",
171
+ () => typeof height === "number" && !fill && aspectRatio === void 0 && height - topBottomLegendHeight < VX.minPlotHeight ? plotBelowFloor("ChartFrame", height, VX.minPlotHeight) : null,
172
+ [height, fill, aspectRatio, topBottomLegendHeight]
173
+ );
97
174
  const togglable = legend !== false && (legend.toggle ?? legendItems.length > 1);
98
175
  const { maxRows, legendWins } = resolveLegendRollup({
99
176
  ...legend !== false && legend.maxRows !== void 0 && { statedMaxRows: legend.maxRows },
100
- tier,
101
- fillBand: legendVisible && fill && !vertical,
177
+ ...!vertical && fit !== null && fit.overflow > 0 && { fittedMaxRows: fit.visible },
178
+ fillBand: showLegend && fill && !inHeader && !vertical,
102
179
  items: legendItems,
103
180
  containerW,
104
181
  available: resolvedHeight - VX.minPlotHeight
@@ -111,7 +188,7 @@ function ChartFrame({
111
188
  topBottomLegendHeight,
112
189
  ...legendWins && { legendWins }
113
190
  });
114
- const legendNode = legend === false || resolvedState !== null ? null : /* @__PURE__ */ jsx("div", { ref: legendRef, style: legendWrapperStyle(vertical), children: /* @__PURE__ */ jsx(
191
+ const legendNode = legend === false || resolvedState !== null || !showLegend ? null : /* @__PURE__ */ jsx("div", { ref: legendRef, style: legendWrapperStyle(vertical), children: /* @__PURE__ */ jsx(
115
192
  ChartLegend,
116
193
  {
117
194
  items: legendItems,
@@ -121,20 +198,26 @@ function ChartFrame({
121
198
  ...chartId !== void 0 && { chartId },
122
199
  ...legend.groups !== void 0 && { groups: legend.groups },
123
200
  ...maxRows !== void 0 && { maxRows },
201
+ ...legendMode !== void 0 && { mode: legendMode },
124
202
  ...legend.highlighted !== void 0 && { highlighted: legend.highlighted },
125
203
  ...legend.onHighlight !== void 0 && { onHighlight: legend.onHighlight }
126
204
  }
127
205
  ) });
128
- return /* @__PURE__ */ jsx(ChartTierProvider, { tier, children: /* @__PURE__ */ jsxs(
206
+ return /* @__PURE__ */ jsx(ChartTierProvider, { containerClass: layout.containerClass, children: /* @__PURE__ */ jsxs(
129
207
  "div",
130
208
  {
131
209
  ref: containerRef,
132
210
  ...className !== void 0 && { className },
133
- style: { ...outerStyle(fill, vertical), ...style },
211
+ style: {
212
+ ...outerStyle(fill, vertical),
213
+ ...grows && { flex: "1 1 0", minHeight: cardMinHeight },
214
+ ...style
215
+ },
134
216
  ...ariaLabel !== void 0 && { role: "group", "aria-label": ariaLabel },
135
217
  ...resolvedState === "pending" && { "aria-busy": "true" },
136
218
  children: [
137
- legendNode !== null && (placement === "top" || placement === "left") && legendNode,
219
+ inHeader && legendNode !== null && legendSlot !== null && createPortal(legendNode, legendSlot),
220
+ !inHeader && legendNode !== null && (placement === "top" || placement === "left") && legendNode,
138
221
  plot.width > 0 && plot.height > 0 && (resolvedState === "pending" ? /* @__PURE__ */ jsx(ChartPending, { width: plot.width, height: plot.height }) : resolvedState === "error" ? /* @__PURE__ */ jsx(ChartError, { width: plot.width, height: plot.height, error: state?.error }) : resolvedState === "empty" ? /* @__PURE__ */ jsx(
139
222
  ChartEmpty,
140
223
  {
@@ -142,8 +225,8 @@ function ChartFrame({
142
225
  height: plot.height,
143
226
  ...typeof state?.empty === "string" && { label: state.empty }
144
227
  }
145
- ) : children({ ...plot, hidden })),
146
- legendNode !== null && (placement === "bottom" || placement === "right") && legendNode
228
+ ) : children({ ...plot, hidden, legendInHeader: inHeader })),
229
+ !inHeader && legendNode !== null && (placement === "bottom" || placement === "right") && legendNode
147
230
  ]
148
231
  }
149
232
  ) });
@@ -151,7 +234,6 @@ function ChartFrame({
151
234
  export {
152
235
  ChartFrame,
153
236
  legendEntryCap,
154
- resolveChartTier2 as resolveChartTier,
155
237
  resolveLegend,
156
238
  resolveLegendRollup2 as resolveLegendRollup,
157
239
  resolvePlotRect2 as resolvePlotRect
@@ -28,26 +28,21 @@ export type LegendEntry = {
28
28
  * static legends without hover interactivity. `chartId` namespaces `split` clipPath ids so two
29
29
  * legends on the same page never collide. `groups` renders series → hairline divider → overlay →
30
30
  * reference bands instead of one flat smear. `maxRows` caps the number of rendered ENTRIES and
31
- * rolls the remainder into a `+N more` chip — it is an entry count, not a row count, in spite of
32
- * the name (`entries.slice(0, cap)` below is the whole of it, and `ChartFrame` measures rows for a
33
- * different purpose in `legendEntryCap`). The name is inherited and reaches the public
34
- * `ChartTierMetrics.legendMaxRows`, so it stays until a minor can rename both.
31
+ * rolls the remainder into an `All N` disclosure chip — it is an entry count, not a row count, in
32
+ * spite of the name (`entries.slice(0, cap)` below is the whole of it). `ChartFrame` derives the
33
+ * cap from the measured fit (`resolveChartLayout().legend`); a caller-stated value wins.
35
34
  *
36
35
  * `onToggle` makes an entry a real toggle: clicking it hides the series in the plot, the tooltip,
37
36
  * and the auto y-domain together (`ChartFrame` owns the `hidden` set — see `docs/CHARTS-SPEC.md`
38
- * §5).
37
+ * §5). A hidden entry is announced via `aria-pressed`.
39
38
  *
40
- * The label font and the DEFAULT entry cap both track the ambient chart tier (§8): inside a
41
- * phone-width `ChartFrame` the legend drops one type step and rolls up past two entries. An
42
- * explicit `maxRows` always wins. Omit it for a static legend; a hidden entry is announced via `aria-pressed`.
43
- *
44
- * **The rollup is a disclosure, not a label.** A cap of two leaves six of eight plotted colours
45
- * unnamed, and a `+6 more` that is a `<span>` gives a phone reader no way to learn them — a
46
- * categorical encoding the chart draws and refuses to decode. The chip is a `<button>` carrying
47
- * `aria-expanded`: expanded, every entry renders, the legend band grows, and `ChartFrame`'s
48
- * observer re-flows the frame around it (the plot keeps `VX.minPlotHeight`).
39
+ * **The rollup is a disclosure, not a label.** `All N` is a `<button>` carrying `aria-expanded`
40
+ * that opens EVERY entry, toggles included, in a popover under the chip on a fine pointer and a
41
+ * bottom sheet on a coarse one. Both are fixed-position and portalled, so opening one never grows
42
+ * the legend band or moves the plot. Escape, an outside press, a scroll or a resize closes it, and
43
+ * focus returns to the chip on every close.
49
44
  */
50
- export declare function ChartLegend({ items, highlighted, onHighlight, hidden, onToggle, chartId, placement, groups, maxRows, className, style, }: BasaltProps & {
45
+ export declare function ChartLegend({ items, highlighted, onHighlight, hidden, onToggle, chartId, placement, groups, maxRows, mode, className, style, }: BasaltProps & {
51
46
  items: LegendEntry[];
52
47
  highlighted?: string | null;
53
48
  onHighlight?: (key: string | null) => void;
@@ -59,5 +54,14 @@ export declare function ChartLegend({ items, highlighted, onHighlight, hidden, o
59
54
  placement?: LegendPlacement;
60
55
  groups?: boolean;
61
56
  maxRows?: number;
57
+ /**
58
+ * `'dots'` collapses every VISIBLE entry to a plain color dot with no label (the name still
59
+ * reaches assistive tech via `aria-label`) — `resolveChartLayout`'s header-slot legend forces
60
+ * this for a short `ChartCard` (wave 11, `docs/CHARTS-SPEC.md` §5) so the legend band's height and
61
+ * width both shrink before it eats plot space. The `All N` disclosure panel is unaffected: it
62
+ * always shows the full swatch + label, `mode` only governs the inline row. Default `'chips'`
63
+ * (today's swatch + label rendering) for every other caller.
64
+ */
65
+ mode?: 'dots' | 'chips';
62
66
  }): import("react").JSX.Element;
63
67
  //# sourceMappingURL=ChartLegend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAG5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAgOD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,EACP,SAAS,EACT,KAAK,GACN,EAAE,WAAW,GAAG;IACf,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,+BAkFA"}
1
+ {"version":3,"file":"ChartLegend.d.ts","sourceRoot":"","sources":["../../../src/charts/primitives/ChartLegend.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAErD,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAK5D,MAAM,MAAM,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,WAAW,CAAA;IAC9C,mFAAmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,kGAAkG;IAClG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;0EACsE;IACtE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mFAAmF;IACnF,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB;iFAC6E;IAC7E,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAA;IACxB;gCAC4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAsOD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,WAAkB,EAClB,WAAW,EACX,MAAkB,EAClB,QAAQ,EACR,OAAY,EACZ,SAAoB,EACpB,MAAc,EACd,OAAO,EACP,IAAc,EACd,SAAS,EACT,KAAK,GACN,EAAE,WAAW,GAAG;IACf,KAAK,EAAE,WAAW,EAAE,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IAC1C,kCAAkC;IAClC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,eAAe,CAAA;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CACxB,+BA2GA"}