@seekalfred/primitives 0.6.0 → 0.7.1

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 (322) hide show
  1. package/README.md +19 -11
  2. package/dist/activity-heatmap/activity-heatmap.d.ts +92 -0
  3. package/dist/activity-heatmap/activity-heatmap.js +699 -0
  4. package/dist/activity-heatmap/activity-heatmap.js.map +1 -0
  5. package/dist/activity-heatmap/index.d.ts +2 -0
  6. package/dist/activity-heatmap/index.js +2 -0
  7. package/dist/alert/alert.d.ts +5 -1
  8. package/dist/alert/alert.js +68 -7
  9. package/dist/alert/alert.js.map +1 -1
  10. package/dist/alert-dialog/alert-dialog.d.ts +45 -18
  11. package/dist/alert-dialog/alert-dialog.js +128 -9
  12. package/dist/alert-dialog/alert-dialog.js.map +1 -1
  13. package/dist/alert-dialog/index.d.ts +2 -2
  14. package/dist/alert-dialog/index.js +2 -2
  15. package/dist/animated-counter/animated-counter.d.ts +38 -0
  16. package/dist/animated-counter/animated-counter.js +230 -0
  17. package/dist/animated-counter/animated-counter.js.map +1 -0
  18. package/dist/animated-counter/index.d.ts +2 -0
  19. package/dist/animated-counter/index.js +2 -0
  20. package/dist/announcement-bar/announcement-bar.d.ts +67 -0
  21. package/dist/announcement-bar/announcement-bar.js +364 -0
  22. package/dist/announcement-bar/announcement-bar.js.map +1 -0
  23. package/dist/announcement-bar/index.d.ts +2 -0
  24. package/dist/announcement-bar/index.js +2 -0
  25. package/dist/badge/badge.d.ts +1 -1
  26. package/dist/bar-chart/bar-chart.d.ts +51 -0
  27. package/dist/bar-chart/bar-chart.js +565 -0
  28. package/dist/bar-chart/bar-chart.js.map +1 -0
  29. package/dist/bar-chart/index.d.ts +2 -0
  30. package/dist/bar-chart/index.js +2 -0
  31. package/dist/breadcrumb/breadcrumb.d.ts +2 -2
  32. package/dist/breadcrumb/breadcrumb.js +12 -3
  33. package/dist/breadcrumb/breadcrumb.js.map +1 -1
  34. package/dist/brush-chart/brush-chart.d.ts +64 -0
  35. package/dist/brush-chart/brush-chart.js +1056 -0
  36. package/dist/brush-chart/brush-chart.js.map +1 -0
  37. package/dist/brush-chart/index.d.ts +2 -0
  38. package/dist/brush-chart/index.js +2 -0
  39. package/dist/button/button.d.ts +2 -2
  40. package/dist/button/button.js +31 -21
  41. package/dist/button/button.js.map +1 -1
  42. package/dist/button-group/button-group.js +3 -4
  43. package/dist/button-group/button-group.js.map +1 -1
  44. package/dist/card/card.js +1 -1
  45. package/dist/card/card.js.map +1 -1
  46. package/dist/card-stack/card-stack.d.ts +54 -0
  47. package/dist/card-stack/card-stack.js +492 -0
  48. package/dist/card-stack/card-stack.js.map +1 -0
  49. package/dist/card-stack/index.d.ts +2 -0
  50. package/dist/card-stack/index.js +2 -0
  51. package/dist/carousel/carousel.d.ts +56 -0
  52. package/dist/carousel/carousel.js +389 -0
  53. package/dist/carousel/carousel.js.map +1 -0
  54. package/dist/carousel/index.d.ts +2 -0
  55. package/dist/carousel/index.js +2 -0
  56. package/dist/checkbox/checkbox.js +4 -4
  57. package/dist/checkbox/checkbox.js.map +1 -1
  58. package/dist/chip-group/chip-group.d.ts +37 -0
  59. package/dist/chip-group/chip-group.js +148 -0
  60. package/dist/chip-group/chip-group.js.map +1 -0
  61. package/dist/chip-group/index.d.ts +2 -0
  62. package/dist/chip-group/index.js +2 -0
  63. package/dist/countdown/countdown.d.ts +45 -0
  64. package/dist/countdown/countdown.js +298 -0
  65. package/dist/countdown/countdown.js.map +1 -0
  66. package/dist/countdown/index.d.ts +2 -0
  67. package/dist/countdown/index.js +2 -0
  68. package/dist/date-picker/picker-parts.js +1 -1
  69. package/dist/date-picker/picker-parts.js.map +1 -1
  70. package/dist/dialog/dialog.js +1 -1
  71. package/dist/dialog/dialog.js.map +1 -1
  72. package/dist/donut-chart/donut-chart.d.ts +64 -0
  73. package/dist/donut-chart/donut-chart.js +576 -0
  74. package/dist/donut-chart/donut-chart.js.map +1 -0
  75. package/dist/donut-chart/index.d.ts +2 -0
  76. package/dist/donut-chart/index.js +2 -0
  77. package/dist/empty-state/empty-state.d.ts +21 -10
  78. package/dist/empty-state/empty-state.js +32 -26
  79. package/dist/empty-state/empty-state.js.map +1 -1
  80. package/dist/empty-state/index.d.ts +2 -2
  81. package/dist/empty-state/index.js +2 -2
  82. package/dist/file-drop-zone/file-drop-zone.js +1 -1
  83. package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
  84. package/dist/filter-bar/filter-bar.d.ts +47 -26
  85. package/dist/filter-bar/filter-bar.js +272 -156
  86. package/dist/filter-bar/filter-bar.js.map +1 -1
  87. package/dist/gauge/gauge.d.ts +45 -0
  88. package/dist/gauge/gauge.js +219 -0
  89. package/dist/gauge/gauge.js.map +1 -0
  90. package/dist/gauge/index.d.ts +2 -0
  91. package/dist/gauge/index.js +2 -0
  92. package/dist/in-view-title/in-view-title.d.ts +33 -0
  93. package/dist/in-view-title/in-view-title.js +119 -0
  94. package/dist/in-view-title/in-view-title.js.map +1 -0
  95. package/dist/in-view-title/index.d.ts +2 -0
  96. package/dist/in-view-title/index.js +2 -0
  97. package/dist/index.d.ts +49 -6
  98. package/dist/index.js +48 -8
  99. package/dist/lib/cn.js +11 -1
  100. package/dist/lib/cn.js.map +1 -1
  101. package/dist/lib/field-message.js +28 -10
  102. package/dist/lib/field-message.js.map +1 -1
  103. package/dist/lib/field-surface.js +9 -8
  104. package/dist/lib/field-surface.js.map +1 -1
  105. package/dist/lib/flag-emoji.js +50 -0
  106. package/dist/lib/flag-emoji.js.map +1 -0
  107. package/dist/lib/flip.js +11 -3
  108. package/dist/lib/flip.js.map +1 -1
  109. package/dist/lib/menu-highlight.js +64 -0
  110. package/dist/lib/menu-highlight.js.map +1 -0
  111. package/dist/lib/motion.js +9 -2
  112. package/dist/lib/motion.js.map +1 -1
  113. package/dist/lib/rolling-value.js +1 -1
  114. package/dist/lib/rolling-value.js.map +1 -1
  115. package/dist/lib/spring-tween.js +500 -0
  116. package/dist/lib/spring-tween.js.map +1 -0
  117. package/dist/lib/swap-text.js +3 -1
  118. package/dist/lib/swap-text.js.map +1 -1
  119. package/dist/line-chart/index.d.ts +2 -0
  120. package/dist/line-chart/index.js +2 -0
  121. package/dist/line-chart/line-chart.d.ts +76 -0
  122. package/dist/line-chart/line-chart.js +611 -0
  123. package/dist/line-chart/line-chart.js.map +1 -0
  124. package/dist/mention-input/index.d.ts +2 -0
  125. package/dist/mention-input/index.js +2 -0
  126. package/dist/mention-input/mention-input.d.ts +98 -0
  127. package/dist/mention-input/mention-input.js +576 -0
  128. package/dist/mention-input/mention-input.js.map +1 -0
  129. package/dist/menu/menu.d.ts +2 -0
  130. package/dist/menu/menu.js +23 -12
  131. package/dist/menu/menu.js.map +1 -1
  132. package/dist/money-input/index.d.ts +2 -0
  133. package/dist/money-input/index.js +2 -0
  134. package/dist/money-input/money-input.d.ts +65 -0
  135. package/dist/money-input/money-input.js +607 -0
  136. package/dist/money-input/money-input.js.map +1 -0
  137. package/dist/morph-select/index.d.ts +2 -0
  138. package/dist/morph-select/index.js +2 -0
  139. package/dist/morph-select/morph-select.d.ts +71 -0
  140. package/dist/morph-select/morph-select.js +693 -0
  141. package/dist/morph-select/morph-select.js.map +1 -0
  142. package/dist/multi-select/index.d.ts +2 -0
  143. package/dist/multi-select/index.js +2 -0
  144. package/dist/multi-select/multi-select.d.ts +36 -0
  145. package/dist/multi-select/multi-select.js +152 -0
  146. package/dist/multi-select/multi-select.js.map +1 -0
  147. package/dist/number-field/index.d.ts +2 -0
  148. package/dist/number-field/index.js +2 -0
  149. package/dist/number-field/number-field.d.ts +42 -0
  150. package/dist/number-field/number-field.js +199 -0
  151. package/dist/number-field/number-field.js.map +1 -0
  152. package/dist/onboarding-checklist/index.d.ts +2 -0
  153. package/dist/onboarding-checklist/index.js +2 -0
  154. package/dist/onboarding-checklist/onboarding-checklist.d.ts +55 -0
  155. package/dist/onboarding-checklist/onboarding-checklist.js +577 -0
  156. package/dist/onboarding-checklist/onboarding-checklist.js.map +1 -0
  157. package/dist/password-field/password-field.js +2 -2
  158. package/dist/password-field/password-field.js.map +1 -1
  159. package/dist/phone-input/index.d.ts +3 -0
  160. package/dist/phone-input/index.js +3 -0
  161. package/dist/phone-input/phone-countries.d.ts +29 -0
  162. package/dist/phone-input/phone-countries.js +170 -0
  163. package/dist/phone-input/phone-countries.js.map +1 -0
  164. package/dist/phone-input/phone-input.d.ts +66 -0
  165. package/dist/phone-input/phone-input.js +440 -0
  166. package/dist/phone-input/phone-input.js.map +1 -0
  167. package/dist/popover/popover.d.ts +12 -0
  168. package/dist/popover/popover.js +31 -7
  169. package/dist/popover/popover.js.map +1 -1
  170. package/dist/progress/progress.js +1 -1
  171. package/dist/radio-group/radio-group.js +1 -1
  172. package/dist/radio-group/radio-group.js.map +1 -1
  173. package/dist/reactions/index.d.ts +2 -0
  174. package/dist/reactions/index.js +2 -0
  175. package/dist/reactions/reactions.d.ts +50 -0
  176. package/dist/reactions/reactions.js +315 -0
  177. package/dist/reactions/reactions.js.map +1 -0
  178. package/dist/rich-text-editor/index.d.ts +3 -0
  179. package/dist/rich-text-editor/index.js +2 -0
  180. package/dist/rich-text-editor/rich-text-editor.d.ts +70 -0
  181. package/dist/rich-text-editor/rich-text-editor.js +909 -0
  182. package/dist/rich-text-editor/rich-text-editor.js.map +1 -0
  183. package/dist/rich-text-editor/rich-text-engine.d.ts +16 -0
  184. package/dist/rich-text-editor/rich-text-engine.js +366 -0
  185. package/dist/rich-text-editor/rich-text-engine.js.map +1 -0
  186. package/dist/rich-text-editor/rich-text-html.js +174 -0
  187. package/dist/rich-text-editor/rich-text-html.js.map +1 -0
  188. package/dist/rich-text-editor/rich-text-icons.js +43 -0
  189. package/dist/rich-text-editor/rich-text-icons.js.map +1 -0
  190. package/dist/ridgeline/index.d.ts +2 -0
  191. package/dist/ridgeline/index.js +2 -0
  192. package/dist/ridgeline/ridgeline.d.ts +55 -0
  193. package/dist/ridgeline/ridgeline.js +701 -0
  194. package/dist/ridgeline/ridgeline.js.map +1 -0
  195. package/dist/scroll-area/index.d.ts +2 -2
  196. package/dist/scroll-area/index.js +2 -2
  197. package/dist/scroll-area/scroll-area.d.ts +68 -31
  198. package/dist/scroll-area/scroll-area.js +208 -59
  199. package/dist/scroll-area/scroll-area.js.map +1 -1
  200. package/dist/scrub-input/evaluate.d.ts +14 -0
  201. package/dist/scrub-input/evaluate.js +72 -0
  202. package/dist/scrub-input/evaluate.js.map +1 -0
  203. package/dist/scrub-input/index.d.ts +3 -0
  204. package/dist/scrub-input/index.js +3 -0
  205. package/dist/scrub-input/scrub-input.d.ts +74 -0
  206. package/dist/scrub-input/scrub-input.js +486 -0
  207. package/dist/scrub-input/scrub-input.js.map +1 -0
  208. package/dist/search-field/search-field.d.ts +2 -1
  209. package/dist/search-field/search-field.js +4 -4
  210. package/dist/search-field/search-field.js.map +1 -1
  211. package/dist/select/select.d.ts +3 -1
  212. package/dist/select/select.js +20 -8
  213. package/dist/select/select.js.map +1 -1
  214. package/dist/separator/separator.d.ts +1 -1
  215. package/dist/sheet/sheet.js +2 -1
  216. package/dist/sheet/sheet.js.map +1 -1
  217. package/dist/shortcut-recorder/index.d.ts +3 -0
  218. package/dist/shortcut-recorder/index.js +3 -0
  219. package/dist/shortcut-recorder/shortcut-model.d.ts +27 -0
  220. package/dist/shortcut-recorder/shortcut-model.js +206 -0
  221. package/dist/shortcut-recorder/shortcut-model.js.map +1 -0
  222. package/dist/shortcut-recorder/shortcut-recorder.d.ts +93 -0
  223. package/dist/shortcut-recorder/shortcut-recorder.js +489 -0
  224. package/dist/shortcut-recorder/shortcut-recorder.js.map +1 -0
  225. package/dist/slider/index.d.ts +2 -2
  226. package/dist/slider/slider.d.ts +46 -14
  227. package/dist/slider/slider.js +151 -67
  228. package/dist/slider/slider.js.map +1 -1
  229. package/dist/slope-chart/index.d.ts +2 -0
  230. package/dist/slope-chart/index.js +2 -0
  231. package/dist/slope-chart/slope-chart.d.ts +54 -0
  232. package/dist/slope-chart/slope-chart.js +568 -0
  233. package/dist/slope-chart/slope-chart.js.map +1 -0
  234. package/dist/sparkline/index.d.ts +2 -0
  235. package/dist/sparkline/index.js +2 -0
  236. package/dist/sparkline/sparkline.d.ts +46 -0
  237. package/dist/sparkline/sparkline.js +390 -0
  238. package/dist/sparkline/sparkline.js.map +1 -0
  239. package/dist/split-bar/index.d.ts +2 -0
  240. package/dist/split-bar/index.js +2 -0
  241. package/dist/split-bar/split-bar.d.ts +62 -0
  242. package/dist/split-bar/split-bar.js +513 -0
  243. package/dist/split-bar/split-bar.js.map +1 -0
  244. package/dist/split-button/split-button.d.ts +1 -1
  245. package/dist/split-button/split-button.js +12 -11
  246. package/dist/split-button/split-button.js.map +1 -1
  247. package/dist/stat-card/index.d.ts +2 -2
  248. package/dist/stat-card/index.js +2 -2
  249. package/dist/stat-card/stat-card.d.ts +24 -4
  250. package/dist/stat-card/stat-card.js +114 -17
  251. package/dist/stat-card/stat-card.js.map +1 -1
  252. package/dist/stepper/index.d.ts +2 -0
  253. package/dist/stepper/index.js +2 -0
  254. package/dist/stepper/stepper.d.ts +51 -0
  255. package/dist/stepper/stepper.js +215 -0
  256. package/dist/stepper/stepper.js.map +1 -0
  257. package/dist/streamgraph/index.d.ts +2 -0
  258. package/dist/streamgraph/index.js +2 -0
  259. package/dist/streamgraph/streamgraph.d.ts +70 -0
  260. package/dist/streamgraph/streamgraph.js +613 -0
  261. package/dist/streamgraph/streamgraph.js.map +1 -0
  262. package/dist/styles.css +1 -1
  263. package/dist/swipe-actions/index.d.ts +2 -0
  264. package/dist/swipe-actions/index.js +2 -0
  265. package/dist/swipe-actions/swipe-actions.d.ts +67 -0
  266. package/dist/swipe-actions/swipe-actions.js +552 -0
  267. package/dist/swipe-actions/swipe-actions.js.map +1 -0
  268. package/dist/switch/switch.js +2 -2
  269. package/dist/switch/switch.js.map +1 -1
  270. package/dist/table/index.d.ts +2 -2
  271. package/dist/table/index.js +2 -2
  272. package/dist/table/table.d.ts +56 -4
  273. package/dist/table/table.js +340 -165
  274. package/dist/table/table.js.map +1 -1
  275. package/dist/tabs/tabs.js +2 -2
  276. package/dist/tabs/tabs.js.map +1 -1
  277. package/dist/tag-input/index.d.ts +2 -0
  278. package/dist/tag-input/index.js +2 -0
  279. package/dist/tag-input/tag-input.d.ts +40 -0
  280. package/dist/tag-input/tag-input.js +269 -0
  281. package/dist/tag-input/tag-input.js.map +1 -0
  282. package/dist/text-morph/index.d.ts +2 -0
  283. package/dist/text-morph/index.js +2 -0
  284. package/dist/text-morph/text-morph.d.ts +23 -0
  285. package/dist/text-morph/text-morph.js +21 -0
  286. package/dist/text-morph/text-morph.js.map +1 -0
  287. package/dist/text-reveal/index.d.ts +2 -0
  288. package/dist/text-reveal/index.js +2 -0
  289. package/dist/text-reveal/text-reveal.d.ts +27 -0
  290. package/dist/text-reveal/text-reveal.js +51 -0
  291. package/dist/text-reveal/text-reveal.js.map +1 -0
  292. package/dist/text-shimmer/index.d.ts +2 -0
  293. package/dist/text-shimmer/index.js +2 -0
  294. package/dist/text-shimmer/text-shimmer.d.ts +29 -0
  295. package/dist/text-shimmer/text-shimmer.js +29 -0
  296. package/dist/text-shimmer/text-shimmer.js.map +1 -0
  297. package/dist/time-picker/time-picker.js +2 -2
  298. package/dist/time-picker/time-picker.js.map +1 -1
  299. package/dist/toast/index.d.ts +2 -2
  300. package/dist/toast/index.js +2 -2
  301. package/dist/toast/toast.d.ts +6 -0
  302. package/dist/toast/toast.js +82 -40
  303. package/dist/toast/toast.js.map +1 -1
  304. package/dist/tooltip/tooltip.d.ts +9 -2
  305. package/dist/tooltip/tooltip.js +7 -7
  306. package/dist/tooltip/tooltip.js.map +1 -1
  307. package/dist/treemap/index.d.ts +2 -0
  308. package/dist/treemap/index.js +2 -0
  309. package/dist/treemap/treemap.d.ts +53 -0
  310. package/dist/treemap/treemap.js +699 -0
  311. package/dist/treemap/treemap.js.map +1 -0
  312. package/dist/usage-meter/index.d.ts +2 -0
  313. package/dist/usage-meter/index.js +2 -0
  314. package/dist/usage-meter/usage-meter.d.ts +50 -0
  315. package/dist/usage-meter/usage-meter.js +440 -0
  316. package/dist/usage-meter/usage-meter.js.map +1 -0
  317. package/dist/waffle-chart/index.d.ts +2 -0
  318. package/dist/waffle-chart/index.js +2 -0
  319. package/dist/waffle-chart/waffle-chart.d.ts +57 -0
  320. package/dist/waffle-chart/waffle-chart.js +426 -0
  321. package/dist/waffle-chart/waffle-chart.js.map +1 -0
  322. package/package.json +165 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ridgeline.js","names":[],"sources":["../../src/ridgeline/ridgeline.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type PointerEvent,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { REVEAL, SWELL, tween } from \"../lib/spring-tween.js\";\n\n/**\n * Ridgeline: compare the shape of many distributions at once, such as daily\n * temperatures by month or response times by region. Each series is one ridge, a\n * smoothed density of its raw values, drawn top to bottom and overlapping the row\n * behind it; the ridges share one value axis.\n *\n * The ridges roll in from the top row down once the chart is in view. New data\n * morphs every ridge from the shape on screen, and the axis follows. Hovering,\n * touching or the arrow keys lift one ridge above its neighbours with its middle\n * half shaded and its median marked, step the others back, and show a tooltip with\n * the median, the middle half and the range. Moving along the values shows how\n * much of the series sits at or below that value. Each ridge is shaded by its\n * median, from a light tint to the full first series colour, with the scale\n * under the axis.\n *\n * Plain SVG written straight to the DOM by one small spring tween.\n */\nexport interface RidgelineSeries {\n /** Stable identity. A ridge that survives a data change morphs into its new shape. */\n id: string;\n label: string;\n /** Raw observations. The ridge is a smoothed density of these values. */\n values: number[];\n}\n\nexport interface RidgelineProps {\n /** One ridge per series, drawn top to bottom. */\n series: RidgelineSeries[];\n /** What is measured, such as \"Daily highs in Zurich\". Names the chart for assistive technology. */\n label: string;\n /** Unit after each value, such as \"°C\". */\n unit?: string;\n formatValue?: (value: number) => string;\n /** Value range of the axis. Defaults to the data with a little room on each side. */\n domain?: [number, number];\n /** How far a ridge may rise into the rows above, in row heights. @default 2.4 */\n overlap?: number;\n /** Height of one row in pixels. @default 30 */\n rowHeight?: number;\n /** Smoothing in value units. Defaults to Silverman's rule per series. */\n bandwidth?: number;\n /** Shades each ridge by its median, from a light tint to the full first series colour, and shows the scale under the axis. @default true */\n tint?: boolean;\n /** Controlled id of the lifted ridge. */\n active?: string | null;\n defaultActive?: string | null;\n onActiveChange?: (id: string | null) => void;\n /** @default \"No data yet\" */\n emptyLabel?: string;\n /** Extra classes for the root. Merged last. */\n className?: string;\n}\n\ntype Stats = { q1: number; median: number; q3: number; min: number; max: number; n: number; sorted: number[] };\ntype Shape = { xs: number[]; ys: number[]; row: number; q1: number; median: number; q3: number };\n\n/** Points along each ridge, the axis strip, the gap between the tooltip and what it describes, and how far the lifted ridge rises, in pixels. */\nconst SAMPLES = 96;\nconst AXIS = 28;\nconst TIP = 14;\nconst LIFT = 6;\nconst GROUPED = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 1 });\nconst PERCENT = new Intl.NumberFormat(\"en-US\", { style: \"percent\", maximumFractionDigits: 0 });\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\nconst mix = (a: number, b: number, t: number) => a + (b - a) * t;\n\nfunction quantile(sorted: number[], q: number) {\n if (!sorted.length) return 0;\n const at = (sorted.length - 1) * q;\n const low = Math.floor(at);\n const high = Math.ceil(at);\n return sorted[low]! + (sorted[high]! - sorted[low]!) * (at - low);\n}\nfunction statsOf(values: number[]): Stats {\n const sorted = values.filter(Number.isFinite).sort((a, b) => a - b);\n return {\n sorted,\n n: sorted.length,\n min: sorted[0] ?? 0,\n max: sorted[sorted.length - 1] ?? 0,\n q1: quantile(sorted, 0.25),\n median: quantile(sorted, 0.5),\n q3: quantile(sorted, 0.75),\n };\n}\n/** Silverman's rule of thumb for the smoothing of a Gaussian kernel. */\nfunction silverman(stats: Stats) {\n if (stats.n < 2) return 1;\n const mean = stats.sorted.reduce((sum, value) => sum + value, 0) / stats.n;\n const sd = Math.sqrt(stats.sorted.reduce((sum, value) => sum + (value - mean) ** 2, 0) / (stats.n - 1));\n const spread = Math.min(sd, (stats.q3 - stats.q1) / 1.34 || sd);\n return Math.max(1e-6, 0.9 * (spread || 1) * Math.pow(stats.n, -0.2));\n}\nfunction niceStep(span: number, count: number) {\n const raw = span / Math.max(1, count) || 1;\n const power = Math.pow(10, Math.floor(Math.log10(raw)));\n const unit = raw / power;\n return (unit >= 5 ? 5 : unit >= 2 ? 2 : 1) * power;\n}\n/** Samples a Gaussian kernel density across the domain. */\nfunction density(stats: Stats, lo: number, hi: number, bandwidth: number) {\n const xs: number[] = [];\n const ys: number[] = [];\n const norm = 1 / (Math.max(1, stats.n) * bandwidth * Math.sqrt(2 * Math.PI));\n for (let i = 0; i < SAMPLES; i++) {\n const x = lo + ((hi - lo) * i) / (SAMPLES - 1);\n let sum = 0;\n for (const value of stats.sorted) {\n const u = (x - value) / bandwidth;\n if (u > -5 && u < 5) sum += Math.exp(-0.5 * u * u);\n }\n xs.push(x);\n ys.push(sum * norm);\n }\n return { xs, ys };\n}\nconst heightAt = (shape: Shape, x: number) => {\n const { xs, ys } = shape;\n if (x <= xs[0]! || x >= xs[xs.length - 1]!) return 0;\n let i = 1;\n while (i < xs.length - 1 && xs[i]! < x) i++;\n const t = (x - xs[i - 1]!) / (xs[i]! - xs[i - 1]! || 1);\n return mix(ys[i - 1]!, ys[i]!, t);\n};\n/** Ridge tone: one hue, the first series colour, from a light tint (low median) to full strength (high median), so the scale never passes through a muddy middle. */\nconst toneOf = (t: number) => `color-mix(in oklab, var(--color-chart-1) ${Math.round(38 + clamp(t, 0, 1) * 62)}%, var(--color-surface))`;\n\nexport const Ridgeline = forwardRef<HTMLElement, RidgelineProps>(function Ridgeline(\n {\n series,\n label,\n unit = \"\",\n formatValue,\n domain,\n overlap = 2.4,\n rowHeight = 30,\n bandwidth,\n tint = true,\n active,\n defaultActive = null,\n onActiveChange,\n emptyLabel = \"No data yet\",\n className,\n },\n ref,\n) {\n const figure = useRef<HTMLElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, figure), [ref]);\n const plot = useRef<HTMLDivElement>(null);\n const tip = useRef<HTMLDivElement>(null);\n const cross = useRef<HTMLDivElement>(null);\n // A true minus sign keeps negative values tight and lined up with the tabular digits.\n const format = (value: number) => (formatValue ? formatValue(value) : `${GROUPED.format(value).replace(\"-\", \"−\")}${unit}`);\n\n const [width, setWidth] = useState(0);\n useIsomorphicLayoutEffect(() => {\n const node = plot.current;\n if (!node || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => setWidth(node.clientWidth));\n observer.observe(node);\n setWidth(node.clientWidth);\n return () => observer.disconnect();\n }, []);\n\n // Targets: one density per series on a shared domain, scaled so the tallest peak of all rises `overlap` rows.\n const signature = JSON.stringify([series, domain, bandwidth]);\n const model = useMemo(() => {\n const stats = series.map((entry) => statsOf(entry.values));\n const widths = stats.map((entry) => bandwidth ?? silverman(entry));\n const filled = stats.map((entry, index) => ({ entry, room: 2.5 * widths[index]! })).filter(({ entry }) => entry.n);\n let lo = domain?.[0] ?? Math.min(...filled.map(({ entry, room }) => entry.min - room));\n let hi = domain?.[1] ?? Math.max(...filled.map(({ entry, room }) => entry.max + room));\n if (!Number.isFinite(lo) || !Number.isFinite(hi) || lo === hi) {\n lo = 0;\n hi = 1;\n }\n if (!domain) {\n const step = niceStep(hi - lo, 5);\n lo = Math.floor(lo / step) * step;\n hi = Math.ceil(hi / step) * step;\n }\n const curves = stats.map((entry, index) =>\n entry.n\n ? density(entry, lo, hi, widths[index]!)\n : { xs: Array.from({ length: SAMPLES }, (_, i) => lo + ((hi - lo) * i) / (SAMPLES - 1)), ys: new Array<number>(SAMPLES).fill(0) },\n );\n const peak = Math.max(1e-9, ...curves.flatMap((curve) => curve.ys));\n const shapes = new Map<string, Shape>(\n series.map((entry, index) => {\n const curve = curves[index]!;\n const stat = stats[index]!;\n return [entry.id, { xs: curve.xs, ys: curve.ys.map((y) => y / peak), row: index, q1: stat.q1, median: stat.median, q3: stat.q3 }];\n }),\n );\n return { stats: new Map(series.map((entry, index) => [entry.id, stats[index]!])), shapes, lo, hi };\n // The signature stands for the series, the domain and the bandwidth.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [signature]);\n\n const [ownActive, setOwnActive] = useState<string | null>(defaultActive);\n const current = active !== undefined ? active : ownActive;\n const setCurrent = (id: string | null) => {\n if (active === undefined) setOwnActive(id);\n if (id !== current) onActiveChange?.(id);\n };\n const [cursor, setCursor] = useState<{ value: number; pointerY: number | null } | null>(null);\n\n const narrow = width > 0 && width < 420;\n const gutter = narrow ? 36 : 52;\n const top = Math.ceil((overlap - 1) * rowHeight) + LIFT + 8;\n const height = top + series.length * rowHeight + AXIS;\n const plotWidth = Math.max(40, width - gutter - 4);\n const baselineOf = (row: number) => top + (row + 1) * rowHeight;\n\n // Drawn geometry lives outside React; a spring interpolates it from what is on screen to the new targets.\n const drawn = useRef({ shapes: new Map<string, Shape>(), lo: 0, hi: 1 });\n const geometry = useRef({ gutter, plotWidth, rowHeight, overlap, top, current });\n const xOf = (value: number) => {\n const d = drawn.current;\n return gutter + ((value - d.lo) / (d.hi - d.lo || 1)) * plotWidth;\n };\n\n const paint = () => {\n const root = plot.current;\n if (!root) return;\n const d = drawn.current;\n const g = geometry.current;\n const sx = (value: number) => g.gutter + ((value - d.lo) / (d.hi - d.lo || 1)) * g.plotWidth;\n const lift = g.rowHeight * g.overlap;\n /** A ridge as a path; `from` and `to` cut it to a stretch of the axis (the middle half). */\n const curvePath = (shape: Shape, closed: boolean, from = -Infinity, to = Infinity) => {\n const base = g.top + (shape.row + 1) * g.rowHeight;\n const points: string[] = [];\n const push = (x: number, y: number) => points.push(`${sx(x).toFixed(2)},${(base - y * lift).toFixed(2)}`);\n if (from > -Infinity) push(from, heightAt(shape, from));\n shape.xs.forEach((x, i) => {\n if (x > from && x < to) push(x, shape.ys[i]!);\n });\n if (to < Infinity) push(to, heightAt(shape, to));\n if (!points.length) return \"\";\n const first = from > -Infinity ? from : shape.xs[0]!;\n const last = to < Infinity ? to : shape.xs[shape.xs.length - 1]!;\n return closed ? `M${sx(first).toFixed(2)},${base}L${points.join(\"L\")}L${sx(last).toFixed(2)},${base}Z` : `M${points.join(\"L\")}`;\n };\n const mark = (node: Element | null, shape: Shape) => {\n if (!node) return;\n const x = sx(shape.median).toFixed(2);\n const base = g.top + (shape.row + 1) * g.rowHeight;\n node.setAttribute(\"x1\", x);\n node.setAttribute(\"x2\", x);\n node.setAttribute(\"y1\", String(base));\n node.setAttribute(\"y2\", (base - heightAt(shape, shape.median) * lift).toFixed(2));\n };\n for (const [id, shape] of d.shapes) {\n const group = root.querySelector(`[data-ridge=\"${CSS.escape(id)}\"]`);\n group?.querySelector(\"[data-part=fill]\")?.setAttribute(\"d\", curvePath(shape, true));\n group?.querySelector(\"[data-part=line]\")?.setAttribute(\"d\", curvePath(shape, false));\n mark(group?.querySelector(\"[data-part=median]\") ?? null, shape);\n }\n const shape = g.current ? d.shapes.get(g.current) : undefined;\n if (shape) {\n const lens = root.querySelector(\"[data-part=lens]\");\n lens?.querySelector(\"[data-part=lens-fill]\")?.setAttribute(\"d\", curvePath(shape, true));\n lens?.querySelector(\"[data-part=lens-line]\")?.setAttribute(\"d\", curvePath(shape, false));\n lens?.querySelector(\"[data-part=lens-band]\")?.setAttribute(\"d\", curvePath(shape, true, shape.q1, shape.q3));\n mark(lens?.querySelector(\"[data-part=lens-median]\") ?? null, shape);\n }\n for (const node of root.querySelectorAll<HTMLElement>(\"[data-tick]\")) {\n node.style.transform = `translate3d(${sx(Number(node.dataset.tick)).toFixed(2)}px, 0, 0)`;\n }\n };\n const paintRef = useRef(paint);\n useIsomorphicLayoutEffect(() => {\n geometry.current = { gutter, plotWidth, rowHeight, overlap, top, current };\n paintRef.current = paint;\n paint();\n });\n\n // The ridges roll in once a third of the chart is in view.\n const [shown, setShown] = useState(false);\n useEffect(() => {\n const node = figure.current;\n if (!node || prefersReducedMotion() || typeof IntersectionObserver === \"undefined\") {\n setShown(true);\n return;\n }\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry?.isIntersecting) return;\n observer.disconnect();\n setShown(true);\n },\n { threshold: 0.3 },\n );\n observer.observe(node);\n return () => observer.disconnect();\n }, []);\n\n // New data morphs every ridge, and the axis, from what is on screen; the first time, ridges grow from flat, a beat apart from the top row down.\n useEffect(() => {\n if (!shown) return;\n const d = drawn.current;\n const first = d.shapes.size === 0;\n const from = new Map<string, Shape>();\n for (const [id, target] of model.shapes) {\n const now = d.shapes.get(id);\n from.set(id, now ? { ...now, xs: [...now.xs], ys: [...now.ys] } : { ...target, ys: target.ys.map(() => 0), q1: target.median, q3: target.median });\n }\n const fromLo = first ? model.lo : d.lo;\n const fromHi = first ? model.hi : d.hi;\n const settle = () => {\n d.shapes = new Map([...model.shapes].map(([id, shape]) => [id, { ...shape }]));\n d.lo = model.lo;\n d.hi = model.hi;\n paintRef.current();\n };\n d.shapes = new Map([...from].map(([id, shape]) => [id, { ...shape, xs: [...shape.xs], ys: [...shape.ys] }]));\n return tween(\n first ? SWELL : REVEAL,\n (t) => {\n d.lo = mix(fromLo, model.lo, t);\n d.hi = mix(fromHi, model.hi, t);\n for (const [id, to] of model.shapes) {\n const f = from.get(id)!;\n const out = d.shapes.get(id)!;\n const local = first ? clamp((t - to.row * 0.025) / (1 - Math.min(0.5, model.shapes.size * 0.025)), 0, 1) : t;\n for (let i = 0; i < SAMPLES; i++) {\n out.xs[i] = mix(f.xs[i]!, to.xs[i]!, local);\n out.ys[i] = mix(f.ys[i]!, to.ys[i]!, local);\n }\n out.row = mix(f.row, to.row, local);\n out.q1 = mix(f.q1, to.q1, local);\n out.median = mix(f.median, to.median, local);\n out.q3 = mix(f.q3, to.q3, local);\n }\n paintRef.current();\n },\n settle,\n );\n }, [shown, model]);\n\n // Axis ticks for the target domain; their positions follow the drawn domain every frame.\n const tickCount = Math.max(2, Math.floor(plotWidth / 72));\n const step = niceStep(model.hi - model.lo, tickCount);\n const ticks: number[] = [];\n for (let value = Math.ceil(model.lo / step) * step; value <= model.hi + 1e-9; value += step) ticks.push(Math.round(value * 1e6) / 1e6 || 0);\n\n const activeStats = current ? model.stats.get(current) : undefined;\n const activeSeries = current ? series.find((entry) => entry.id === current) : undefined;\n const reading =\n cursor && activeStats\n ? { value: cursor.value, below: activeStats.n ? activeStats.sorted.filter((value) => value <= cursor.value).length / activeStats.n : 0 }\n : null;\n // The tooltip keeps its last content while it fades out, so it never collapses to an empty box on leave.\n const [held, setHeld] = useState<{ id: string; value: number | null } | null>(null);\n if (current && (held?.id !== current || held.value !== (reading?.value ?? null))) setHeld({ id: current, value: reading?.value ?? null });\n const tipSeries = held ? series.find((entry) => entry.id === held.id) : undefined;\n const tipStats = held ? model.stats.get(held.id) : undefined;\n const tipBelow = held && held.value !== null && tipStats?.n ? tipStats.sorted.filter((value) => value <= held.value!).length / tipStats.n : 0;\n\n // The tooltip trails the pointer or the keyboard cursor and stays inside the chart; it glides between ridges once showing.\n const shownBefore = useRef(false);\n useIsomorphicLayoutEffect(() => {\n const bubble = tip.current;\n const shape = current ? model.shapes.get(current) : undefined;\n if (cross.current) cross.current.style.transform = `translate3d(${reading ? xOf(reading.value).toFixed(2) : 0}px, 0, 0)`;\n if (!bubble || !shape || !width) {\n shownBefore.current = false;\n return;\n }\n const tw = bubble.offsetWidth;\n const th = bubble.offsetHeight;\n const ax = reading ? xOf(reading.value) : xOf(shape.median);\n const ay = cursor?.pointerY ?? baselineOf(shape.row) - heightAt(shape, shape.median) * rowHeight * overlap - LIFT;\n let left = ax + TIP;\n if (left + tw > width) left = ax - TIP - tw;\n let y = ay - th - TIP;\n if (y < 0) y = ay + TIP;\n if (!shownBefore.current) bubble.style.transition = \"none\";\n bubble.style.translate = `${clamp(left, 0, Math.max(0, width - tw))}px ${clamp(y, 0, Math.max(0, height - th))}px`;\n if (!shownBefore.current) {\n void bubble.offsetWidth;\n bubble.style.transition = \"\";\n }\n shownBefore.current = true;\n });\n\n const valueAt = (x: number) => {\n const d = drawn.current;\n return d.lo + clamp((x - gutter) / plotWidth, 0, 1) * (d.hi - d.lo);\n };\n /** The ridge under the pointer: the front-most one whose outline holds it, else the row it is in. */\n const pick = (x: number, y: number) => {\n const value = valueAt(x);\n const lift = rowHeight * overlap;\n let best: Shape | null = null;\n let bestId: string | null = null;\n for (const [id, shape] of drawn.current.shapes) {\n const base = baselineOf(shape.row);\n const h = heightAt(shape, value) * lift;\n if (y <= base + 2 && y >= base - h - 4 && (!best || shape.row > best.row)) {\n best = shape;\n bestId = id;\n }\n }\n if (!bestId) {\n const row = clamp(Math.floor((y - top) / rowHeight), 0, series.length - 1);\n bestId = series[row]?.id ?? null;\n }\n return { id: bestId, value };\n };\n const onPointerMove = (event: PointerEvent<HTMLDivElement>) => {\n const rect = event.currentTarget.getBoundingClientRect();\n const x = event.clientX - rect.left;\n const y = event.clientY - rect.top;\n if (x < gutter - 8 || y > height - AXIS + 4) {\n if (event.pointerType === \"mouse\") {\n setCurrent(null);\n setCursor(null);\n }\n return;\n }\n const hit = pick(x, y);\n setCurrent(hit.id);\n setCursor({ value: hit.value, pointerY: y });\n };\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const at = current ? series.findIndex((entry) => entry.id === current) : -1;\n const choose = (index: number) => {\n event.preventDefault();\n const next = series[clamp(index, 0, series.length - 1)];\n if (!next) return;\n setCurrent(next.id);\n setCursor({ value: cursor?.value ?? model.stats.get(next.id)?.median ?? model.lo, pointerY: null });\n };\n const nudge = (direction: number) => {\n event.preventDefault();\n if (!current) {\n choose(0);\n return;\n }\n const base = cursor?.value ?? model.stats.get(current)?.median ?? model.lo;\n setCursor({ value: clamp((Math.round(base / step) + direction) * step, model.lo, model.hi), pointerY: null });\n };\n if (event.key === \"ArrowDown\") choose(at + 1);\n else if (event.key === \"ArrowUp\") choose(at < 0 ? 0 : at - 1);\n else if (event.key === \"Home\") choose(0);\n else if (event.key === \"End\") choose(series.length - 1);\n else if (event.key === \"ArrowRight\") nudge(1);\n else if (event.key === \"ArrowLeft\") nudge(-1);\n else if (event.key === \"Escape\" && current) {\n event.preventDefault();\n setCurrent(null);\n setCursor(null);\n }\n };\n\n const empty = !series.length || series.every((entry) => !entry.values.length);\n const ridgeColor = (value: number) => (tint ? toneOf((value - model.lo) / (model.hi - model.lo || 1)) : \"var(--color-chart-1)\");\n const describe = (entry: RidgelineSeries) => {\n const s = model.stats.get(entry.id)!;\n return `${entry.label}: median ${format(s.median)}, middle half ${format(s.q1)} to ${format(s.q3)}, range ${format(s.min)} to ${format(s.max)}, ${s.n} values`;\n };\n const spoken = activeSeries ? `${describe(activeSeries)}${reading ? `. ${PERCENT.format(reading.below)} at or below ${format(reading.value)}` : \"\"}` : \"\";\n const summary = empty ? `${label}. ${emptyLabel}.` : `${label}. ${series.map(describe).join(\". \")}.`;\n\n return (\n <figure ref={refs} data-slot=\"ridgeline\" aria-label={label} className={cn(\"ridgeline box-border font-sans text-foreground\", className)}>\n <div\n ref={plot}\n data-slot=\"ridgeline-plot\"\n data-active={current ? \"\" : undefined}\n data-scrubbing={current ? \"\" : undefined}\n data-cursor={reading ? \"\" : undefined}\n role={empty ? undefined : \"slider\"}\n tabIndex={empty ? undefined : 0}\n aria-label={empty ? undefined : `${label}, choose a ridge`}\n aria-orientation={empty ? undefined : \"vertical\"}\n aria-valuemin={empty ? undefined : 1}\n aria-valuemax={empty ? undefined : series.length}\n aria-valuenow={empty ? undefined : (activeSeries ? series.indexOf(activeSeries) : 0) + 1}\n aria-valuetext={empty ? undefined : spoken || summary}\n className=\"ridgeline-plot\"\n style={{ height }}\n onPointerMove={onPointerMove}\n onPointerDown={onPointerMove}\n onPointerLeave={(event) => {\n if (event.pointerType === \"mouse\") {\n setCurrent(null);\n setCursor(null);\n }\n }}\n onKeyDown={onKeyDown}\n onBlur={() => {\n setCurrent(null);\n setCursor(null);\n }}\n >\n <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-x-0 top-0\" style={{ height: height - AXIS + 22 }}>\n {ticks.map((value) => (\n <div key={value} data-tick={value} className=\"ridgeline-tick motion-safe:animate-fade-in\" style={{ top: top - rowHeight * 0.5 }}>\n <span className=\"ridgeline-tick-label\">{format(value)}</span>\n </div>\n ))}\n </div>\n <div ref={cross} aria-hidden=\"true\" className=\"ridgeline-crosshair\" style={{ top: top - rowHeight * 0.5, height: height - AXIS - top + rowHeight * 0.5 }} />\n <svg width={width || undefined} height={height} viewBox={`0 0 ${Math.max(1, width)} ${height}`} aria-hidden=\"true\" focusable=\"false\" className=\"absolute inset-0 block overflow-visible\">\n {series.map((entry) => (\n <g\n key={entry.id}\n data-ridge={entry.id}\n data-dim={(current !== null && current !== entry.id) || undefined}\n data-lifted={current === entry.id || undefined}\n className=\"ridgeline-ridge\"\n style={{ \"--ridge\": ridgeColor(model.stats.get(entry.id)?.median ?? model.lo) } as CSSProperties}\n >\n <path data-part=\"fill\" className=\"ridgeline-fill\" />\n <line data-part=\"median\" className=\"ridgeline-median\" />\n <path data-part=\"line\" className=\"ridgeline-line\" />\n </g>\n ))}\n <g\n data-part=\"lens\"\n data-on={current ? \"\" : undefined}\n className=\"ridgeline-lens\"\n style={tipStats ? ({ \"--ridge\": ridgeColor(tipStats.median) } as CSSProperties) : undefined}\n >\n <path data-part=\"lens-fill\" className=\"ridgeline-lens-fill\" />\n <path data-part=\"lens-band\" className=\"ridgeline-lens-band\" />\n <line data-part=\"lens-median\" className=\"ridgeline-lens-median\" />\n <path data-part=\"lens-line\" className=\"ridgeline-lens-line\" />\n </g>\n </svg>\n <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0\">\n {series.map((entry, index) => (\n <span key={entry.id} data-active={current === entry.id || undefined} className=\"ridgeline-row-label\" style={{ top: baselineOf(index) - 18, width: gutter - 10 }}>\n {entry.label}\n </span>\n ))}\n </div>\n {empty ? <p className=\"pointer-events-none absolute inset-0 m-0 grid place-items-center text-sm text-muted-foreground\">{emptyLabel}</p> : null}\n <div ref={tip} aria-hidden=\"true\" data-slot=\"ridgeline-tooltip\" className=\"line-chart-tooltip\">\n {tipSeries && tipStats ? (\n <>\n <p className=\"m-0 mb-0.5 text-xs text-text-secondary\">{tipSeries.label}</p>\n <p className=\"ridgeline-tip-row text-sm font-medium\">\n <span className=\"text-xs font-normal text-muted-foreground\">Median</span>\n <span>{format(tipStats.median)}</span>\n </p>\n <p className=\"ridgeline-tip-row text-xs\">\n <span className=\"text-muted-foreground\">Middle half</span>\n <span>\n {format(tipStats.q1)} to {format(tipStats.q3)}\n </span>\n </p>\n <p className=\"ridgeline-tip-row text-xs\">\n <span className=\"text-muted-foreground\">Range</span>\n <span>\n {format(tipStats.min)} to {format(tipStats.max)}\n </span>\n </p>\n {held?.value != null ? (\n <p className=\"m-0 mt-1 text-xs tabular-nums text-muted-foreground\">\n {PERCENT.format(tipBelow)} at or below {format(held.value)}\n </p>\n ) : null}\n </>\n ) : null}\n </div>\n </div>\n {tint && !empty ? (\n <div aria-hidden=\"true\" className=\"mt-3 flex flex-wrap items-center justify-end gap-2 text-xs tabular-nums text-muted-foreground\">\n <span className=\"me-1 text-text-secondary\">Median</span>\n <span>{format(model.lo)}</span>\n <span className=\"inline-flex gap-0.5\">\n {[0, 0.25, 0.5, 0.75, 1].map((at) => (\n <span key={at} className=\"ridgeline-step\" style={{ \"--ridge\": toneOf(at) } as CSSProperties} />\n ))}\n </span>\n <span>{format(model.hi)}</span>\n </div>\n ) : null}\n <p className=\"sr-only\">{summary}</p>\n <p className=\"sr-only\" aria-live=\"polite\" aria-atomic=\"true\">\n {spoken}\n </p>\n {empty ? null : (\n <div className=\"sr-only\">\n <table>\n <caption>{label}</caption>\n <thead>\n <tr>\n <th scope=\"col\">Series</th>\n <th scope=\"col\">Median</th>\n <th scope=\"col\">First quartile</th>\n <th scope=\"col\">Third quartile</th>\n <th scope=\"col\">Lowest</th>\n <th scope=\"col\">Highest</th>\n <th scope=\"col\">Count</th>\n </tr>\n </thead>\n <tbody>\n {series.map((entry) => {\n const s = model.stats.get(entry.id)!;\n return (\n <tr key={entry.id}>\n <th scope=\"row\">{entry.label}</th>\n <td>{format(s.median)}</td>\n <td>{format(s.q1)}</td>\n <td>{format(s.q3)}</td>\n <td>{format(s.min)}</td>\n <td>{format(s.max)}</td>\n <td>{s.n}</td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n )}\n </figure>\n );\n});\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { ScrollArea, ScrollAreaProps, scrollAreaVariants } from "./scroll-area.js";
2
- export { ScrollArea, type ScrollAreaProps, scrollAreaVariants };
1
+ import { ScrollArea, ScrollAreaProps, scrollAreaRootVariants, scrollAreaVariants } from "./scroll-area.js";
2
+ export { ScrollArea, type ScrollAreaProps, scrollAreaRootVariants, scrollAreaVariants };
@@ -1,2 +1,2 @@
1
- import { ScrollArea, scrollAreaVariants } from "./scroll-area.js";
2
- export { ScrollArea, scrollAreaVariants };
1
+ import { ScrollArea, scrollAreaRootVariants, scrollAreaVariants } from "./scroll-area.js";
2
+ export { ScrollArea, scrollAreaRootVariants, scrollAreaVariants };
@@ -1,38 +1,44 @@
1
1
  import { ComponentPropsWithoutRef, ReactNode } from "react";
2
2
  //#region src/scroll-area/scroll-area.d.ts
3
3
  /**
4
- * ScrollArea. React Aria has no equivalent, so this is a plain scrolling
5
- * div -- and that is the honest answer rather than a shortcut.
4
+ * ScrollArea: a native scroll container with thin overlay scrollbars and edge
5
+ * fades. React Aria has no equivalent, so the scrolling stays the browser's --
6
+ * keyboard, touch momentum and scroll snap all behave as the platform expects --
7
+ * and only the scrollbar is drawn here.
6
8
  *
7
- * Census: fourteen paths match, thirteen are counted, ELEVEN render
8
- * anything, and across them 17 uses. Every single one passes `className`
9
- * and nothing else, and 13 of the 17 are a height or flex constraint plus
10
- * padding. `ScrollBar` is exported by the legacy and imported by nobody.
11
- * `orientation="horizontal"` has zero call sites.
9
+ * Census: fourteen paths match, thirteen are counted, ELEVEN render anything,
10
+ * and across them 17 uses. Every one passes `className` and nothing else, and 13
11
+ * of the 17 are a height or flex constraint plus padding. The legacy Radix one
12
+ * cannot be scrolled by keyboard at all.
12
13
  *
13
- * So there is no styled-scrollbar requirement to port. The legacy pays for
14
- * one anyway: Radix injects a `<style>` tag that hides the native
15
- * scrollbar, then re-draws it in JavaScript with a ResizeObserver, a
16
- * portal-free overlay and a `display: table` content wrapper. That is a
17
- * lot of machinery to reproduce a scrollbar, and it is why the legacy
18
- * `ScrollArea` cannot be scrolled by keyboard at all.
14
+ * The native scrollbar is hidden and replaced by an overlay pill that appears
15
+ * while scrolling or hovering and fades away at rest (or stays, with
16
+ * `scrollbars="always"`). It is written straight to the DOM on each scroll
17
+ * frame, so scrolling never re-renders React. The pill can be dragged, and
18
+ * pressing its track pages toward the pointer.
19
19
  *
20
- * Tailwind 4.3.3 ships `scrollbar-width` and `scrollbar-color` utilities
21
- * (verified in the installed dist, not assumed), which gets the same look
22
- * in three classes and no JavaScript.
20
+ * WHERE THINGS LAND. `className`, `style` and the rest go on the ROOT, the box
21
+ * that carries the height and the optional card. `ref` and `viewportClassName`
22
+ * go on the VIEWPORT, the element that really scrolls -- so a caller can call
23
+ * `ref.current.scrollTo(...)` or `scrollBy(...)` for a "back to top" or an
24
+ * arrow button, and put padding on the content without moving the scrollbar
25
+ * inside it.
23
26
  *
24
- * WHAT CANNOT BE STYLED, stated rather than glossed: `scrollbar-color`
25
- * takes a thumb and a track colour and nothing else -- no width in pixels,
26
- * no radius, no hover state, no margins. `::-webkit-scrollbar` can do
27
- * those but is non-standard and Firefox has never supported it. This
28
- * component uses the standard properties only.
27
+ * The font is declared here: the reset in @seekalfred/styles is OPT-IN, so a
28
+ * consumer may load none, and a scroll area's content (a list of plain
29
+ * paragraphs) would render in the browser's serif.
29
30
  */
30
- export declare const scrollAreaVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
31
+ export declare const scrollAreaRootVariants: (props?: ({
32
+ framed?: boolean | null | undefined;
33
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
34
+ export declare const scrollAreaVariants: (props?: ({
35
+ orientation?: "vertical" | "horizontal" | "both" | null | undefined;
36
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
31
37
  /**
32
38
  * A scrolling region.
33
39
  *
34
40
  * @example
35
- * <ScrollArea label="Chat messages" className="h-64">...</ScrollArea>
41
+ * <ScrollArea label="Activity" className="h-64" framed fade={28}>...</ScrollArea>
36
42
  */
37
43
  export interface ScrollAreaProps extends ComponentPropsWithoutRef<"div"> {
38
44
  children?: ReactNode;
@@ -41,7 +47,7 @@ export interface ScrollAreaProps extends ComponentPropsWithoutRef<"div"> {
41
47
  *
42
48
  * **Required**, and it took a failing test to get here. A focusable
43
49
  * element with no role fails axe `focus-order-semantics`, so the
44
- * keyboard fix below cannot ship without a role -- and a role with no
50
+ * keyboard fix cannot ship without a role -- and a role with no
45
51
  * accessible name is a violation of its own. Naming it is the only
46
52
  * combination that is actually correct.
47
53
  *
@@ -53,26 +59,56 @@ export interface ScrollAreaProps extends ComponentPropsWithoutRef<"div"> {
53
59
  * jump to it is the point.
54
60
  */
55
61
  label: string;
62
+ /**
63
+ * Which axes scroll.
64
+ *
65
+ * @default "vertical"
66
+ */
67
+ orientation?: "vertical" | "horizontal" | "both";
56
68
  /**
57
69
  * Fade the content out at an edge by exactly as much as there is content hidden
58
70
  * past it, up to this many pixels: the fade grows in with the first pixels of
59
- * scroll and is gone at the ends. `0` turns it off, and the default leaves the
60
- * area as it was. Written on each scroll frame straight to the element, so
61
- * scrolling never re-renders.
71
+ * scroll and is gone at the ends, so an edge fades only while content
72
+ * overflows past it. `0` turns it off, and the default leaves the area as it
73
+ * was. Written on each scroll frame straight to the element, so scrolling
74
+ * never re-renders.
62
75
  *
63
76
  * @default 0
64
77
  */
65
78
  fade?: number;
79
+ /**
80
+ * `auto` shows the scrollbar while scrolling or hovering and fades it away at
81
+ * rest; `always` keeps it visible whenever the content overflows.
82
+ *
83
+ * @default "auto"
84
+ */
85
+ scrollbars?: "auto" | "always";
86
+ /**
87
+ * How long the scrollbar lingers after scrolling stops, in ms.
88
+ *
89
+ * @default 900
90
+ */
91
+ hideDelay?: number;
92
+ /**
93
+ * Draws the area as a white card: the panel radius and a light border. The
94
+ * content is clipped to it, so an edge fade and the scrollbar sit inside.
95
+ *
96
+ * @default false
97
+ */
98
+ framed?: boolean;
99
+ /**
100
+ * Classes for the viewport, the element that scrolls. Padding for the
101
+ * content goes here.
102
+ */
103
+ viewportClassName?: string;
66
104
  /**
67
105
  * Change this when the contents change, and the region scrolls back to the
68
106
  * top.
69
107
  *
70
- * This stays a declarative prop even though a `ref` now exists: reaching
108
+ * This stays a declarative prop even though a `ref` exists: reaching
71
109
  * for it would mean the caller writes `ref.current.scrollTop = 0` in their
72
110
  * own effect on every content change, which is worse ergonomics than a
73
- * prop that says "reset when this value changes." That is a design choice
74
- * independent of whether a ref is available, not a consequence of one
75
- * being withheld.
111
+ * prop that says "reset when this value changes."
76
112
  *
77
113
  * Sheet forwards its own `scrollResetKey` straight through: going a level
78
114
  * deeper in a panel and landing halfway down the new view is disorienting,
@@ -80,6 +116,7 @@ export interface ScrollAreaProps extends ComponentPropsWithoutRef<"div"> {
80
116
  */
81
117
  scrollResetKey?: string | number;
82
118
  }
119
+ /** The `ref` reaches the VIEWPORT, the element that scrolls. `onScroll` runs on its scroll events. */
83
120
  export declare const ScrollArea: import("react").ForwardRefExoticComponent<ScrollAreaProps & import("react").RefAttributes<HTMLDivElement>>;
84
121
  //#endregion
85
122
  //# sourceMappingURL=scroll-area.d.ts.map
@@ -1,87 +1,236 @@
1
1
  "use client";
2
- import { cnAll } from "../lib/cn.js";
2
+ import { cn, cnAll } from "../lib/cn.js";
3
3
  import { mergeRefs } from "../lib/merge-refs.js";
4
+ import { prefersReducedMotion, useIsomorphicLayoutEffect } from "../lib/motion.js";
4
5
  import { cva } from "class-variance-authority";
5
- import { forwardRef, useEffect, useMemo, useRef } from "react";
6
- import { jsx } from "react/jsx-runtime";
6
+ import { forwardRef, useCallback, useEffect, useMemo, useRef } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
7
8
  //#region src/scroll-area/scroll-area.tsx
8
9
  /**
9
- * ScrollArea. React Aria has no equivalent, so this is a plain scrolling
10
- * div -- and that is the honest answer rather than a shortcut.
10
+ * ScrollArea: a native scroll container with thin overlay scrollbars and edge
11
+ * fades. React Aria has no equivalent, so the scrolling stays the browser's --
12
+ * keyboard, touch momentum and scroll snap all behave as the platform expects --
13
+ * and only the scrollbar is drawn here.
11
14
  *
12
- * Census: fourteen paths match, thirteen are counted, ELEVEN render
13
- * anything, and across them 17 uses. Every single one passes `className`
14
- * and nothing else, and 13 of the 17 are a height or flex constraint plus
15
- * padding. `ScrollBar` is exported by the legacy and imported by nobody.
16
- * `orientation="horizontal"` has zero call sites.
15
+ * Census: fourteen paths match, thirteen are counted, ELEVEN render anything,
16
+ * and across them 17 uses. Every one passes `className` and nothing else, and 13
17
+ * of the 17 are a height or flex constraint plus padding. The legacy Radix one
18
+ * cannot be scrolled by keyboard at all.
17
19
  *
18
- * So there is no styled-scrollbar requirement to port. The legacy pays for
19
- * one anyway: Radix injects a `<style>` tag that hides the native
20
- * scrollbar, then re-draws it in JavaScript with a ResizeObserver, a
21
- * portal-free overlay and a `display: table` content wrapper. That is a
22
- * lot of machinery to reproduce a scrollbar, and it is why the legacy
23
- * `ScrollArea` cannot be scrolled by keyboard at all.
20
+ * The native scrollbar is hidden and replaced by an overlay pill that appears
21
+ * while scrolling or hovering and fades away at rest (or stays, with
22
+ * `scrollbars="always"`). It is written straight to the DOM on each scroll
23
+ * frame, so scrolling never re-renders React. The pill can be dragged, and
24
+ * pressing its track pages toward the pointer.
24
25
  *
25
- * Tailwind 4.3.3 ships `scrollbar-width` and `scrollbar-color` utilities
26
- * (verified in the installed dist, not assumed), which gets the same look
27
- * in three classes and no JavaScript.
26
+ * WHERE THINGS LAND. `className`, `style` and the rest go on the ROOT, the box
27
+ * that carries the height and the optional card. `ref` and `viewportClassName`
28
+ * go on the VIEWPORT, the element that really scrolls -- so a caller can call
29
+ * `ref.current.scrollTo(...)` or `scrollBy(...)` for a "back to top" or an
30
+ * arrow button, and put padding on the content without moving the scrollbar
31
+ * inside it.
28
32
  *
29
- * WHAT CANNOT BE STYLED, stated rather than glossed: `scrollbar-color`
30
- * takes a thumb and a track colour and nothing else -- no width in pixels,
31
- * no radius, no hover state, no margins. `::-webkit-scrollbar` can do
32
- * those but is non-standard and Firefox has never supported it. This
33
- * component uses the standard properties only.
33
+ * The font is declared here: the reset in @seekalfred/styles is OPT-IN, so a
34
+ * consumer may load none, and a scroll area's content (a list of plain
35
+ * paragraphs) would render in the browser's serif.
34
36
  */
37
+ const scrollAreaRootVariants = cva(["scroll-area relative flex min-h-0 min-w-0 flex-col overflow-hidden box-border font-sans"], {
38
+ variants: {
39
+ /** The area is a white card: the panel radius and a hairline border. */
40
+ framed: {
41
+ true: "rounded-panel border-(length:--border-width) border-border bg-surface",
42
+ false: ""
43
+ } },
44
+ defaultVariants: { framed: false }
45
+ });
35
46
  const scrollAreaVariants = cva([
36
- "overflow-y-auto overscroll-contain",
37
- "box-border",
38
- "scrollbar-thin scrollbar-thumb-border scrollbar-track-transparent",
47
+ "scroll-area-viewport min-h-0 min-w-0 flex-1 overscroll-contain box-border",
39
48
  "outline-none",
40
49
  "focus-visible:outline-solid",
41
50
  "focus-visible:outline-(length:--focus-ring-width)",
42
51
  "focus-visible:outline-offset-(--focus-ring-offset)",
43
52
  "focus-visible:outline-(--color-ring)"
44
- ]);
45
- const ScrollArea = forwardRef(function ScrollArea({ className, children, label, scrollResetKey, fade = 0, onScroll, ...props }, ref) {
46
- const region = useRef(null);
53
+ ], {
54
+ variants: { orientation: {
55
+ vertical: "overflow-x-hidden overflow-y-auto",
56
+ horizontal: "overflow-x-auto overflow-y-hidden",
57
+ both: "overflow-auto"
58
+ } },
59
+ defaultVariants: { orientation: "vertical" }
60
+ });
61
+ /** The shortest a scrollbar thumb gets, so a very long list still leaves something to grab. */
62
+ const MIN_THUMB = 28;
63
+ /** The `ref` reaches the VIEWPORT, the element that scrolls. `onScroll` runs on its scroll events. */
64
+ const ScrollArea = forwardRef(function ScrollArea({ className, children, label, scrollResetKey, orientation = "vertical", fade = 0, scrollbars = "auto", hideDelay = 900, framed = false, viewportClassName, onScroll, ...props }, ref) {
65
+ const root = useRef(null);
66
+ const viewport = useRef(null);
67
+ const tracks = useRef({
68
+ x: null,
69
+ y: null
70
+ });
71
+ const thumbs = useRef({
72
+ x: null,
73
+ y: null
74
+ });
75
+ const hideTimer = useRef(void 0);
76
+ const drag = useRef(null);
77
+ const vertical = orientation !== "horizontal";
78
+ const horizontal = orientation !== "vertical";
47
79
  useEffect(() => {
48
- if (region.current) region.current.scrollTop = 0;
80
+ if (viewport.current) viewport.current.scrollTop = 0;
49
81
  }, [scrollResetKey]);
50
- const rootRef = useMemo(() => mergeRefs(ref, region), [ref, region]);
51
- const syncFade = () => {
52
- const node = region.current;
53
- if (!node || fade <= 0) return;
54
- const max = Math.max(0, node.scrollHeight - node.clientHeight);
55
- node.style.setProperty("--fade-top", `${Math.min(fade, node.scrollTop)}px`);
56
- node.style.setProperty("--fade-bottom", `${Math.min(fade, max - node.scrollTop)}px`);
57
- };
58
- useEffect(() => {
59
- const node = region.current;
60
- if (!node || fade <= 0) return;
61
- syncFade();
82
+ const viewportRef = useMemo(() => mergeRefs(ref, viewport), [ref]);
83
+ const sync = useCallback(() => {
84
+ const node = viewport.current;
85
+ if (!node) return;
86
+ const { scrollTop, scrollHeight, clientHeight, scrollWidth, clientWidth } = node;
87
+ const maxY = Math.max(0, scrollHeight - clientHeight);
88
+ const maxX = Math.max(0, scrollWidth - clientWidth);
89
+ const left = Math.abs(node.scrollLeft);
90
+ if (fade > 0) {
91
+ node.style.setProperty("--fade-top", `${vertical ? Math.min(fade, scrollTop) : 0}px`);
92
+ node.style.setProperty("--fade-bottom", `${vertical ? Math.min(fade, Math.max(0, maxY - scrollTop)) : 0}px`);
93
+ node.style.setProperty("--fade-left", `${horizontal ? Math.min(fade, left) : 0}px`);
94
+ node.style.setProperty("--fade-right", `${horizontal ? Math.min(fade, Math.max(0, maxX - left)) : 0}px`);
95
+ }
96
+ const place = (axis, scroll, max, client, total) => {
97
+ const track = tracks.current[axis];
98
+ const thumb = thumbs.current[axis];
99
+ if (!track || !thumb) return;
100
+ const overflowing = max > .5;
101
+ track.toggleAttribute("data-hidden", !overflowing);
102
+ if (!overflowing) return;
103
+ const length = axis === "y" ? track.clientHeight : track.clientWidth;
104
+ const size = Math.max(MIN_THUMB, length * client / total);
105
+ const offset = (length - size) * (scroll / max);
106
+ const sign = axis === "x" && getComputedStyle(node).direction === "rtl" ? -1 : 1;
107
+ thumb.style[axis === "y" ? "height" : "width"] = `${size}px`;
108
+ thumb.style.transform = axis === "y" ? `translate3d(0, ${offset}px, 0)` : `translate3d(${offset * sign}px, 0, 0)`;
109
+ };
110
+ if (vertical) place("y", scrollTop, maxY, clientHeight, scrollHeight);
111
+ if (horizontal) place("x", left, maxX, clientWidth, scrollWidth);
112
+ root.current?.toggleAttribute("data-overflow", maxY > .5 || maxX > .5);
113
+ }, [
114
+ fade,
115
+ horizontal,
116
+ vertical
117
+ ]);
118
+ useIsomorphicLayoutEffect(() => {
119
+ const node = viewport.current;
120
+ if (!node) return;
121
+ sync();
62
122
  if (typeof ResizeObserver === "undefined") return;
63
- const observer = new ResizeObserver(syncFade);
123
+ const observer = new ResizeObserver(() => sync());
64
124
  observer.observe(node);
65
125
  for (const child of Array.from(node.children)) observer.observe(child);
66
126
  return () => observer.disconnect();
67
- }, [fade, children]);
68
- return /* @__PURE__ */ jsx("div", {
69
- ...props,
70
- ref: rootRef,
71
- tabIndex: 0,
72
- role: "region",
73
- "aria-label": label,
74
- "data-slot": "scroll-area",
75
- "data-fade": fade > 0 ? "" : void 0,
76
- onScroll: (event) => {
77
- syncFade();
78
- onScroll?.(event);
127
+ }, [sync, children]);
128
+ useEffect(() => () => window.clearTimeout(hideTimer.current), []);
129
+ const wake = () => {
130
+ const el = root.current;
131
+ if (!el) return;
132
+ el.setAttribute("data-scrolling", "");
133
+ window.clearTimeout(hideTimer.current);
134
+ hideTimer.current = window.setTimeout(() => el.removeAttribute("data-scrolling"), hideDelay);
135
+ };
136
+ const onThumbDown = (event, axis) => {
137
+ const node = viewport.current;
138
+ const track = tracks.current[axis];
139
+ const thumb = thumbs.current[axis];
140
+ if (!node || !track || !thumb || event.button !== 0) return;
141
+ event.preventDefault();
142
+ event.stopPropagation();
143
+ event.currentTarget.setPointerCapture(event.pointerId);
144
+ const length = axis === "y" ? track.clientHeight : track.clientWidth;
145
+ const size = axis === "y" ? thumb.offsetHeight : thumb.offsetWidth;
146
+ const max = axis === "y" ? node.scrollHeight - node.clientHeight : node.scrollWidth - node.clientWidth;
147
+ drag.current = {
148
+ axis,
149
+ start: axis === "y" ? event.clientY : event.clientX,
150
+ scroll: axis === "y" ? node.scrollTop : node.scrollLeft,
151
+ ratio: max / Math.max(1, length - size),
152
+ sign: axis === "x" && getComputedStyle(node).direction === "rtl" ? -1 : 1
153
+ };
154
+ root.current?.setAttribute("data-dragging", axis);
155
+ };
156
+ const onThumbMove = (event) => {
157
+ const state = drag.current;
158
+ const node = viewport.current;
159
+ if (!state || !node) return;
160
+ const travel = (state.axis === "y" ? event.clientY : event.clientX) - state.start;
161
+ const to = state.scroll + travel * state.ratio * state.sign;
162
+ if (state.axis === "y") node.scrollTop = to;
163
+ else node.scrollLeft = to;
164
+ };
165
+ const onThumbUp = (event) => {
166
+ if (!drag.current) return;
167
+ drag.current = null;
168
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
169
+ root.current?.removeAttribute("data-dragging");
170
+ wake();
171
+ };
172
+ const onTrackDown = (event, axis) => {
173
+ const node = viewport.current;
174
+ const thumb = thumbs.current[axis];
175
+ if (!node || !thumb || event.button !== 0 || event.target !== event.currentTarget) return;
176
+ const box = thumb.getBoundingClientRect();
177
+ const before = axis === "y" ? event.clientY < box.top : event.clientX < box.left;
178
+ const page = (axis === "y" ? node.clientHeight : node.clientWidth) * .9 * (before ? -1 : 1);
179
+ node.scrollBy({
180
+ [axis === "y" ? "top" : "left"]: page,
181
+ behavior: prefersReducedMotion() ? "auto" : "smooth"
182
+ });
183
+ };
184
+ const bar = (axis) => /* @__PURE__ */ jsx("div", {
185
+ ref: (node) => {
186
+ tracks.current[axis] = node;
79
187
  },
80
- className: cnAll(scrollAreaVariants(), fade > 0 && "scroll-fade-y", className),
81
- children
188
+ "data-slot": "scroll-area-track",
189
+ "data-axis": axis,
190
+ "data-hidden": "",
191
+ "aria-hidden": "true",
192
+ onPointerDown: (event) => onTrackDown(event, axis),
193
+ children: /* @__PURE__ */ jsx("div", {
194
+ ref: (node) => {
195
+ thumbs.current[axis] = node;
196
+ },
197
+ "data-slot": "scroll-area-thumb",
198
+ onPointerDown: (event) => onThumbDown(event, axis),
199
+ onPointerMove: onThumbMove,
200
+ onPointerUp: onThumbUp,
201
+ onPointerCancel: onThumbUp
202
+ })
203
+ });
204
+ const fadeClass = orientation === "vertical" ? "scroll-fade-y" : orientation === "horizontal" ? "scroll-fade-x" : "scroll-fade-both";
205
+ return /* @__PURE__ */ jsxs("div", {
206
+ ...props,
207
+ ref: root,
208
+ "data-slot": "scroll-area-root",
209
+ "data-orientation": orientation,
210
+ "data-scrollbars": scrollbars,
211
+ className: cn(scrollAreaRootVariants({ framed }), className),
212
+ children: [
213
+ /* @__PURE__ */ jsx("div", {
214
+ ref: viewportRef,
215
+ tabIndex: 0,
216
+ role: "region",
217
+ "aria-label": label,
218
+ "data-slot": "scroll-area",
219
+ "data-fade": fade > 0 ? "" : void 0,
220
+ onScroll: (event) => {
221
+ sync();
222
+ wake();
223
+ onScroll?.(event);
224
+ },
225
+ className: cn(cnAll(scrollAreaVariants({ orientation }), fade > 0 && fadeClass), viewportClassName),
226
+ children
227
+ }),
228
+ vertical ? bar("y") : null,
229
+ horizontal ? bar("x") : null
230
+ ]
82
231
  });
83
232
  });
84
233
  //#endregion
85
- export { ScrollArea, scrollAreaVariants };
234
+ export { ScrollArea, scrollAreaRootVariants, scrollAreaVariants };
86
235
 
87
236
  //# sourceMappingURL=scroll-area.js.map