@heyramzi/ui 1.54.2 → 2.0.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 (363) hide show
  1. package/COMPONENTS.md +3 -2
  2. package/README.md +34 -9
  3. package/package.json +205 -13
  4. package/scripts/sync-shadcn.mjs +2 -0
  5. package/src/ai/generation-progress/GenerationProgress.svelte +12 -12
  6. package/src/ambient.d.ts +8 -0
  7. package/src/brand/brand.test.ts +67 -0
  8. package/src/brand/index.ts +137 -0
  9. package/src/canvas/README.md +236 -0
  10. package/src/canvas/card/card-frame.svelte +61 -0
  11. package/src/canvas/card/card.css +39 -0
  12. package/src/canvas/card/resize-handles.svelte +119 -0
  13. package/src/canvas/card/resize.ts +33 -0
  14. package/src/canvas/chrome/chrome-zoom.svelte +120 -0
  15. package/src/canvas/chrome/chrome.css +231 -0
  16. package/src/canvas/chrome/lock-badge.svelte +18 -0
  17. package/src/canvas/chrome/side-handles.svelte +30 -0
  18. package/src/canvas/chrome/size-chip.svelte +45 -0
  19. package/src/canvas/chrome/snap-guides.svelte +69 -0
  20. package/src/canvas/components/icons/ToolIcon.svelte +30 -0
  21. package/src/canvas/components/shared/svg/PreviewSvg.svelte +67 -0
  22. package/src/canvas/components/shared/svg/ShapeOutline.svelte +76 -0
  23. package/src/canvas/components/shared/svg/StrokeNode.svelte +69 -0
  24. package/src/canvas/components/shared/svg/TextRule.svelte +37 -0
  25. package/src/canvas/edges/bend-handle.svelte +76 -0
  26. package/src/canvas/edges/edge-gradient.svelte +30 -0
  27. package/src/canvas/edges/edge-path.test.ts +58 -0
  28. package/src/canvas/edges/edge-path.ts +293 -0
  29. package/src/canvas/edges/gradient-edge.svelte +43 -0
  30. package/src/canvas/editor/context-menu.test.ts +65 -0
  31. package/src/canvas/editor/context-menu.ts +48 -0
  32. package/src/canvas/editor/dispatch.svelte.test.ts +243 -0
  33. package/src/canvas/editor/dispatch.svelte.ts +205 -0
  34. package/src/canvas/editor/editor-context.ts +62 -0
  35. package/src/canvas/editor/editor-mode.ts +67 -0
  36. package/src/canvas/editor/frame.ts +98 -0
  37. package/src/canvas/editor/hand-context.ts +15 -0
  38. package/src/canvas/editor/history-core.test.ts +239 -0
  39. package/src/canvas/editor/history-core.ts +247 -0
  40. package/src/canvas/editor/keymap.test.ts +161 -0
  41. package/src/canvas/editor/keymap.ts +151 -0
  42. package/src/canvas/editor/layout-actions.test.ts +156 -0
  43. package/src/canvas/editor/layout-actions.ts +219 -0
  44. package/src/canvas/editor/lock.test.ts +68 -0
  45. package/src/canvas/editor/lock.ts +87 -0
  46. package/src/canvas/editor/rebase.test.ts +208 -0
  47. package/src/canvas/editor/rebase.ts +272 -0
  48. package/src/canvas/editor/style.ts +184 -0
  49. package/src/canvas/editor/text-field-context.ts +41 -0
  50. package/src/canvas/editor/z-order.test.ts +75 -0
  51. package/src/canvas/editor/z-order.ts +87 -0
  52. package/src/canvas/headless/alignment.test.ts +59 -0
  53. package/src/canvas/headless/alignment.ts +23 -0
  54. package/src/canvas/headless/break-apart-find.ts +418 -0
  55. package/src/canvas/headless/break-apart-pieces.ts +104 -0
  56. package/src/canvas/headless/break-apart-plan.ts +439 -0
  57. package/src/canvas/headless/break-apart-style.ts +106 -0
  58. package/src/canvas/headless/break-apart.ts +244 -0
  59. package/src/canvas/headless/canvas-cleanup.test.ts +181 -0
  60. package/src/canvas/headless/canvas-cleanup.ts +102 -0
  61. package/src/canvas/headless/edge-anchors.test.ts +70 -0
  62. package/src/canvas/headless/edge-anchors.ts +143 -0
  63. package/src/canvas/headless/edge-memo.test.ts +58 -0
  64. package/src/canvas/headless/edge-memo.ts +32 -0
  65. package/src/canvas/headless/edge-serialization.test.ts +55 -0
  66. package/src/canvas/headless/edge-serialization.ts +32 -0
  67. package/src/canvas/headless/figure-geometry.ts +293 -0
  68. package/src/canvas/headless/fit-viewport.test.ts +85 -0
  69. package/src/canvas/headless/fit-viewport.ts +137 -0
  70. package/src/canvas/headless/grid.test.ts +34 -0
  71. package/src/canvas/headless/grid.ts +94 -0
  72. package/src/canvas/headless/icon-node.test.ts +23 -0
  73. package/src/canvas/headless/icon-node.ts +66 -0
  74. package/src/canvas/headless/index.ts +9 -0
  75. package/src/canvas/headless/linkify.test.ts +23 -0
  76. package/src/canvas/headless/linkify.ts +25 -0
  77. package/src/canvas/headless/pen-marks.ts +133 -0
  78. package/src/canvas/headless/sketch.test.ts +99 -0
  79. package/src/canvas/headless/sketch.ts +302 -0
  80. package/src/canvas/headless/snap-guides.test.ts +61 -0
  81. package/src/canvas/headless/snap-guides.ts +146 -0
  82. package/src/canvas/headless/svg-figure.test.ts +49 -0
  83. package/src/canvas/headless/svg-figure.ts +176 -0
  84. package/src/canvas/headless/table-layout.test.ts +158 -0
  85. package/src/canvas/headless/table-layout.ts +309 -0
  86. package/src/canvas/index.ts +35 -0
  87. package/src/canvas/lod/lod-mode.svelte.ts +30 -0
  88. package/src/canvas/lod/lod-switch.svelte +140 -0
  89. package/src/canvas/lod/render-all.svelte.ts +52 -0
  90. package/src/canvas/ops/arrange.ts +108 -0
  91. package/src/canvas/ops/ops.test.ts +98 -0
  92. package/src/canvas/ops/ops.ts +347 -0
  93. package/src/canvas/pen/PenField.svelte +128 -0
  94. package/src/canvas/pen/PenField.test.ts +87 -0
  95. package/src/canvas/pen/PenOverlay.svelte +172 -0
  96. package/src/canvas/pen/ShapeNode.svelte +64 -0
  97. package/src/canvas/pen/TextNode.svelte +97 -0
  98. package/src/canvas/pen/colors.ts +28 -0
  99. package/src/canvas/pen/eraser.ts +46 -0
  100. package/src/canvas/pen/geometry.ts +148 -0
  101. package/src/canvas/pen/host-field.test.svelte +13 -0
  102. package/src/canvas/pen/index.ts +40 -0
  103. package/src/canvas/pen/ops.ts +16 -0
  104. package/src/canvas/pen/pen-session.ts +177 -0
  105. package/src/canvas/pen/pen.css +149 -0
  106. package/src/canvas/pen/plugin.ts +139 -0
  107. package/src/canvas/pen/shapes.ts +104 -0
  108. package/src/canvas/pen/stroke-path.ts +54 -0
  109. package/src/canvas/pen/types.ts +96 -0
  110. package/src/canvas/pen/ui.ts +26 -0
  111. package/src/canvas/plugin.test.ts +45 -0
  112. package/src/canvas/plugin.ts +159 -0
  113. package/src/canvas/screen/GlyphIcon.svelte +25 -0
  114. package/src/canvas/screen/PartView.svelte +176 -0
  115. package/src/canvas/screen/ScreenBody.svelte +142 -0
  116. package/src/canvas/screen/ScreenCard.svelte +32 -0
  117. package/src/canvas/screen/glyphs/button.svg +1 -0
  118. package/src/canvas/screen/glyphs/close.svg +1 -0
  119. package/src/canvas/screen/glyphs/down.svg +1 -0
  120. package/src/canvas/screen/glyphs/header.svg +1 -0
  121. package/src/canvas/screen/glyphs/hills.svg +1 -0
  122. package/src/canvas/screen/glyphs/image.svg +1 -0
  123. package/src/canvas/screen/glyphs/input.svg +1 -0
  124. package/src/canvas/screen/glyphs/list.svg +1 -0
  125. package/src/canvas/screen/glyphs/modal.svg +1 -0
  126. package/src/canvas/screen/glyphs/plus.svg +1 -0
  127. package/src/canvas/screen/glyphs/up.svg +1 -0
  128. package/src/canvas/screen/glyphs.ts +40 -0
  129. package/src/canvas/screen/labels.ts +41 -0
  130. package/src/canvas/screen/parts.ts +164 -0
  131. package/src/canvas/screen/plugin.ts +67 -0
  132. package/src/canvas/screen/screen.css +593 -0
  133. package/src/canvas/screen/types.ts +47 -0
  134. package/src/canvas/screen/ui.ts +13 -0
  135. package/src/canvas/test-utils.ts +18 -0
  136. package/src/canvas/types.ts +53 -0
  137. package/src/canvas/ui/CanvasToolbar.svelte +169 -0
  138. package/src/canvas/ui/FrameLayer.svelte +116 -0
  139. package/src/canvas/ui/PenLayer.svelte +39 -0
  140. package/src/canvas/ui/StylePanel.svelte +268 -0
  141. package/src/canvas/ui/canvas-grid.svelte +156 -0
  142. package/src/canvas/ui/canvas-ui.css +350 -0
  143. package/src/canvas/ui/forward-wheel.ts +29 -0
  144. package/src/canvas/ui/icons.ts +124 -0
  145. package/src/canvas/ui/place-panel.ts +80 -0
  146. package/src/canvas/ui/shortcut-tip.test.ts +16 -0
  147. package/src/canvas/ui/shortcut-tip.ts +17 -0
  148. package/src/canvas/ui/tools.ts +146 -0
  149. package/src/charts/README.md +104 -0
  150. package/src/charts/ambient.d.ts +4 -0
  151. package/src/charts/animated-value.css +7 -0
  152. package/src/charts/animated-value.svelte +110 -0
  153. package/src/charts/area-chart.css +29 -0
  154. package/src/charts/area-chart.svelte +204 -0
  155. package/src/charts/bar-chart.css +119 -0
  156. package/src/charts/bar-chart.svelte +275 -0
  157. package/src/charts/charts.css +201 -0
  158. package/src/charts/crosshair.svelte +35 -0
  159. package/src/charts/dashboard/chart.ts +5 -0
  160. package/src/charts/dashboard/dashboard-empty.svelte +55 -0
  161. package/src/charts/dashboard/dashboard-section.svelte +58 -0
  162. package/src/charts/dashboard/data-panel.svelte +81 -0
  163. package/src/charts/dashboard/data-sort-head.svelte +102 -0
  164. package/src/charts/dashboard/data-table.svelte +278 -0
  165. package/src/charts/dashboard/data-table.ts +26 -0
  166. package/src/charts/dashboard/index.ts +25 -0
  167. package/src/charts/dashboard/panel-grid.svelte +43 -0
  168. package/src/charts/dashboard/period-filter.svelte +53 -0
  169. package/src/charts/dashboard/rating-chip.svelte +35 -0
  170. package/src/charts/dashboard/stat-band.svelte +47 -0
  171. package/src/charts/dashboard/stat-tile.svelte +176 -0
  172. package/src/charts/dashboard/windows.ts +8 -0
  173. package/src/charts/donut-chart.css +56 -0
  174. package/src/charts/donut-chart.svelte +158 -0
  175. package/src/charts/edge-fade.svelte +43 -0
  176. package/src/charts/edge-fade.ts +16 -0
  177. package/src/charts/figure.css +100 -0
  178. package/src/charts/figure.svelte +122 -0
  179. package/src/charts/gauge-chart.css +94 -0
  180. package/src/charts/gauge-chart.svelte +302 -0
  181. package/src/charts/hover-band.test.ts +30 -0
  182. package/src/charts/hover-band.ts +21 -0
  183. package/src/charts/hover-focus.svelte +85 -0
  184. package/src/charts/index.ts +31 -0
  185. package/src/charts/legend.css +136 -0
  186. package/src/charts/legend.svelte +112 -0
  187. package/src/charts/line-chart.css +26 -0
  188. package/src/charts/line-chart.svelte +256 -0
  189. package/src/charts/motion.ts +19 -0
  190. package/src/charts/number-chart.css +179 -0
  191. package/src/charts/number-chart.svelte +187 -0
  192. package/src/charts/primitives/chart-container.svelte +57 -0
  193. package/src/charts/primitives/chart-style.svelte +36 -0
  194. package/src/charts/primitives/chart-tooltip.svelte +161 -0
  195. package/src/charts/primitives/chart-utils.ts +54 -0
  196. package/src/charts/primitives/index.ts +11 -0
  197. package/src/charts/reference-line.css +18 -0
  198. package/src/charts/reference-line.svelte +31 -0
  199. package/src/charts/reveal.svelte +61 -0
  200. package/src/charts/ring-chart.css +104 -0
  201. package/src/charts/ring-chart.svelte +166 -0
  202. package/src/charts/sparkline.css +45 -0
  203. package/src/charts/sparkline.svelte +141 -0
  204. package/src/charts/stat-card.css +105 -0
  205. package/src/charts/stat-card.svelte +267 -0
  206. package/src/charts/time-series-chart.css +28 -0
  207. package/src/charts/time-series-chart.svelte +179 -0
  208. package/src/charts/trend-pill.css +38 -0
  209. package/src/charts/trend-pill.svelte +48 -0
  210. package/src/components/copy/copy-button.svelte +1 -1
  211. package/src/components/docs/doc-page.svelte +1 -1
  212. package/src/components/docs/on-this-page.svelte +2 -2
  213. package/src/components/list-page/empty-state.svelte +1 -1
  214. package/src/components/list-page/filter-popover.svelte +1 -1
  215. package/src/components/list-page/section-label.svelte +1 -1
  216. package/src/components/segmented/segment.svelte +4 -4
  217. package/src/components/theme/theme-provider.svelte +6 -4
  218. package/src/components/toolbar/index.ts +5 -0
  219. package/src/components/toolbar/toolbar.svelte +168 -0
  220. package/src/components/toolbar/toolbar.test.ts +66 -0
  221. package/src/editor/README.md +58 -0
  222. package/src/editor/ambient.d.ts +1 -0
  223. package/src/editor/block-id.ts +77 -0
  224. package/src/editor/block-ops.test.ts +237 -0
  225. package/src/editor/block-ops.ts +157 -0
  226. package/src/editor/content.css +418 -0
  227. package/src/editor/editor-toolbar.svelte +303 -0
  228. package/src/editor/editor.svelte +469 -0
  229. package/src/editor/extensions.ts +145 -0
  230. package/src/editor/image-toolbar.svelte +123 -0
  231. package/src/editor/index.ts +17 -0
  232. package/src/editor/inline-editor.svelte +261 -0
  233. package/src/editor/markdown-escaping.test.ts +158 -0
  234. package/src/editor/markdown-marks.test.ts +89 -0
  235. package/src/editor/markdown-test-helpers.ts +13 -0
  236. package/src/editor/markdown-youtube.test.ts +93 -0
  237. package/src/editor/markdown.test.ts +250 -0
  238. package/src/editor/markdown.ts +511 -0
  239. package/src/editor/renderer.svelte +92 -0
  240. package/src/editor/serializer.test.ts +133 -0
  241. package/src/editor/serializer.ts +370 -0
  242. package/src/editor/slash-items.ts +115 -0
  243. package/src/editor/slash-menu.svelte +159 -0
  244. package/src/editor/youtube-url.ts +35 -0
  245. package/src/effects/interactive-hover-button/InteractiveHoverButton.svelte +13 -13
  246. package/src/gallery.test.ts +20 -1
  247. package/src/lib/result.ts +19 -0
  248. package/src/onboarding/README.md +120 -0
  249. package/src/onboarding/define-tours.test.ts +50 -0
  250. package/src/onboarding/define-tours.ts +31 -0
  251. package/src/onboarding/demo.svelte +86 -0
  252. package/src/onboarding/index.ts +12 -0
  253. package/src/onboarding/onboarding-provider.svelte +120 -0
  254. package/src/onboarding/onboarding.svelte.ts +24 -0
  255. package/src/onboarding/pick.test.ts +79 -0
  256. package/src/onboarding/pick.ts +36 -0
  257. package/src/onboarding/stores.test.ts +103 -0
  258. package/src/onboarding/stores.ts +58 -0
  259. package/src/onboarding/tour-card.svelte +70 -0
  260. package/src/onboarding/tour-runner.svelte +197 -0
  261. package/src/onboarding/types.ts +34 -0
  262. package/src/onboarding/wait.ts +29 -0
  263. package/src/shadcn/command/command-input.svelte +1 -1
  264. package/src/slides/Controls.svelte +138 -0
  265. package/src/slides/Deck.svelte +271 -0
  266. package/src/slides/Fragment.svelte +71 -0
  267. package/src/slides/Overview.svelte +113 -0
  268. package/src/slides/Presenter.svelte +292 -0
  269. package/src/slides/README.md +89 -0
  270. package/src/slides/Slide.svelte +179 -0
  271. package/src/slides/SlideList.svelte +19 -0
  272. package/src/slides/ambient.d.ts +1 -0
  273. package/src/slides/bridges.ts +69 -0
  274. package/src/slides/deck-controller.svelte.ts +292 -0
  275. package/src/slides/demo-slides.svelte +358 -0
  276. package/src/slides/demo.svelte +176 -0
  277. package/src/slides/export/capture.ts +113 -0
  278. package/src/slides/export/ffmpeg.ts +120 -0
  279. package/src/slides/export/index.ts +9 -0
  280. package/src/slides/export/launch.ts +10 -0
  281. package/src/slides/export/pdf.ts +152 -0
  282. package/src/slides/export/types.ts +48 -0
  283. package/src/slides/index.ts +19 -0
  284. package/src/slides/labels.ts +48 -0
  285. package/src/slides/layout.ts +51 -0
  286. package/src/slides/model.ts +47 -0
  287. package/src/slides/navigation.ts +100 -0
  288. package/src/slides/theme.css +205 -0
  289. package/src/slides/types.ts +86 -0
  290. package/src/tasks/README.md +312 -0
  291. package/src/tasks/ambient.d.ts +3 -0
  292. package/src/tasks/board-order.ts +137 -0
  293. package/src/tasks/dates.ts +31 -0
  294. package/src/tasks/define-statuses.ts +32 -0
  295. package/src/tasks/demo.svelte +389 -0
  296. package/src/tasks/detail/detail-demo.svelte +186 -0
  297. package/src/tasks/detail/detail-types.ts +54 -0
  298. package/src/tasks/detail/task-chip.svelte +28 -0
  299. package/src/tasks/detail/task-detail.svelte +315 -0
  300. package/src/tasks/detail/task-property.svelte +17 -0
  301. package/src/tasks/detail/task-thread.svelte +258 -0
  302. package/src/tasks/gantt/gantt-bar.svelte +243 -0
  303. package/src/tasks/gantt/gantt-grid.svelte +94 -0
  304. package/src/tasks/gantt/gantt-group-row.svelte +55 -0
  305. package/src/tasks/gantt/gantt-header.svelte +71 -0
  306. package/src/tasks/gantt/gantt-task-row.svelte +186 -0
  307. package/src/tasks/gantt/gantt.css +22 -0
  308. package/src/tasks/gantt/gantt.test.ts +77 -0
  309. package/src/tasks/gantt/logic.test.ts +163 -0
  310. package/src/tasks/gantt/logic.ts +248 -0
  311. package/src/tasks/inbox/inbox-card.svelte +283 -0
  312. package/src/tasks/inbox/inbox-draft.svelte +84 -0
  313. package/src/tasks/inbox/inbox-logic.ts +55 -0
  314. package/src/tasks/inbox/inbox-statuses.ts +48 -0
  315. package/src/tasks/inbox/inbox-trace.svelte +45 -0
  316. package/src/tasks/inbox/inbox-types.ts +108 -0
  317. package/src/tasks/inbox/inbox.svelte +203 -0
  318. package/src/tasks/inbox/index.ts +23 -0
  319. package/src/tasks/index.ts +85 -0
  320. package/src/tasks/kanban-board.svelte +561 -0
  321. package/src/tasks/kanban-column.svelte +117 -0
  322. package/src/tasks/kanban-drag.ts +38 -0
  323. package/src/tasks/kanban-types.ts +65 -0
  324. package/src/tasks/presets/sales.ts +16 -0
  325. package/src/tasks/record/demo-data.ts +286 -0
  326. package/src/tasks/record/field-section.svelte +53 -0
  327. package/src/tasks/record/fields.ts +142 -0
  328. package/src/tasks/record/format.ts +87 -0
  329. package/src/tasks/record/gutter.ts +2 -0
  330. package/src/tasks/record/index.ts +54 -0
  331. package/src/tasks/record/info-list.svelte +316 -0
  332. package/src/tasks/record/kinds.ts +42 -0
  333. package/src/tasks/record/logic.ts +324 -0
  334. package/src/tasks/record/next-up.svelte +70 -0
  335. package/src/tasks/record/project-list.svelte +133 -0
  336. package/src/tasks/record/record-demo.svelte +324 -0
  337. package/src/tasks/record/record-header.svelte +70 -0
  338. package/src/tasks/record/record-shell.svelte +138 -0
  339. package/src/tasks/record/record-stats.svelte +48 -0
  340. package/src/tasks/record/record-timeline.svelte +231 -0
  341. package/src/tasks/record/record.test.ts +350 -0
  342. package/src/tasks/record/steps-rail.svelte +474 -0
  343. package/src/tasks/record/types.ts +280 -0
  344. package/src/tasks/record/where-we-are.svelte +121 -0
  345. package/src/tasks/reorder.ts +19 -0
  346. package/src/tasks/side-panel-types.ts +16 -0
  347. package/src/tasks/side-panel.svelte +193 -0
  348. package/src/tasks/status-badge.svelte +63 -0
  349. package/src/tasks/status-dot.svelte +228 -0
  350. package/src/tasks/status-icon.svelte +30 -0
  351. package/src/tasks/status-types.ts +40 -0
  352. package/src/tasks/task-gantt.svelte +230 -0
  353. package/src/tasks/task-list.svelte +470 -0
  354. package/src/tasks/task-types.ts +33 -0
  355. package/src/tasks/time/duration.ts +69 -0
  356. package/src/tasks/time/time-chip.svelte +62 -0
  357. package/src/tasks/time/time-dot.svelte +58 -0
  358. package/src/tasks/time/time-ring.svelte +74 -0
  359. package/src/tasks/time/time-tracker.svelte +565 -0
  360. package/src/tasks/time/time-types.ts +74 -0
  361. package/src/tasks/use-kanban-dnd.svelte.ts +173 -0
  362. package/src/tokens/base.css +5 -5
  363. package/src/tokens/palette.css +115 -120
@@ -0,0 +1,204 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import "./area-chart.css";
6
+ import { Area, Axis, Chart, Highlight, LinearGradient, Spline, Svg, Tooltip } from "layerchart";
7
+ import { curveMonotoneX } from "d3-shape";
8
+ import Crosshair from "./crosshair.svelte";
9
+ import EdgeFade from "./edge-fade.svelte";
10
+ import { resolveEdgeFade } from "./edge-fade.js";
11
+ import Figure from "./figure.svelte";
12
+ import HoverFocus from "./hover-focus.svelte";
13
+ import { DOMAIN_MOTION, TOOLTIP_GAP, TOOLTIP_TOP } from "./motion.js";
14
+ import Reveal from "./reveal.svelte";
15
+ import ReferenceLine, { type ChartReference } from "./reference-line.svelte";
16
+
17
+ // =========================
18
+ // TYPES
19
+ // =========================
20
+ type Series = {
21
+ key: keyof TData & string;
22
+ label?: string;
23
+ color?: string;
24
+ };
25
+
26
+ interface Props {
27
+ data: TData[];
28
+ xKey: keyof TData & string;
29
+ yKey?: keyof TData & string;
30
+ series?: Series[];
31
+ color?: string;
32
+ height?: number;
33
+ stacked?: boolean;
34
+ smooth?: boolean;
35
+ /**
36
+ * Softens the left and right ends of the filled area instead of cutting them off at the plot edge.
37
+ */
38
+
39
+ /*
40
+ * `true` fades 8% of the width per side, a number sets the fraction (0-0.45), `false` keeps the hard edges.
41
+ */
42
+ edgeFade?: boolean | number;
43
+ formatValue?: (v: number) => string;
44
+ formatX?: (v: unknown) => string;
45
+ xTicks?: number;
46
+ /** A target drawn across the plot, like the deal a cost runs up to. */
47
+ reference?: ChartReference;
48
+ class?: string;
49
+ };
50
+
51
+ // =========================
52
+ // PROPS
53
+ // =========================
54
+ let {
55
+ data,
56
+ xKey,
57
+ yKey,
58
+ series: seriesInput,
59
+ color = "var(--chart-1)",
60
+ height = 240,
61
+ stacked = false,
62
+ smooth = true,
63
+ edgeFade = true,
64
+ formatValue = (v) => v.toLocaleString(),
65
+ formatX = (v) => String(v),
66
+ xTicks = 6,
67
+ reference,
68
+ class: className,
69
+ }: Props = $props();
70
+
71
+ // =========================
72
+ // STATE
73
+ // =========================
74
+ let active = $state<TData | null>(null);
75
+
76
+ // ========================= FUNCTIONS ========================= WHY: called from the tooltip header's value expression so it re-runs on every active-point change; an unread {@const} side effect only evaluates once.
77
+
78
+ // The microtask defers the state write out of template evaluation.
79
+ function trackActive(d: TData): string {
80
+ queueMicrotask(() => {
81
+ active = d;
82
+ });
83
+ return formatX(d[xKey]);
84
+ }
85
+
86
+ // =========================
87
+ // DERIVED
88
+ // =========================
89
+ const uid = `hr-area-${Math.random().toString(36).slice(2, 8)}`;
90
+ const series = $derived(
91
+ seriesInput && seriesInput.length > 0
92
+ ? seriesInput.map((s, i) => ({
93
+ key: s.key,
94
+ label: s.label ?? s.key,
95
+ color: s.color ?? `var(--chart-${(i % 5) + 1})`,
96
+ }))
97
+ : yKey
98
+ ? [{ key: yKey, label: yKey, color }]
99
+ : [],
100
+ );
101
+ const dataMax = $derived.by(() => {
102
+ if (stacked) {
103
+ return Math.max(0, ...data.map((d) => series.reduce((s, k) => s + Number(d[k.key] ?? 0), 0)));
104
+ }
105
+ return Math.max(0, ...data.flatMap((d) => series.map((s) => Number(d[s.key] ?? 0))));
106
+ });
107
+ // A target above every point would sit off the plot.
108
+ const yMax = $derived(Math.max(dataMax, reference?.value ?? 0));
109
+ const curve = $derived(smooth ? curveMonotoneX : undefined);
110
+ const fade = $derived(resolveEdgeFade(edgeFade));
111
+ const fadeMask = $derived(fade > 0 ? `url(#${uid}-fade)` : undefined);
112
+ const fallback = $derived(data.length > 0 ? data[data.length - 1] : null);
113
+ const display = $derived(active ?? fallback);
114
+ const figureRows = $derived(
115
+ display
116
+ ? series.map((s) => ({ value: Number(display[s.key]), label: s.label, color: s.color }))
117
+ : null,
118
+ );
119
+ </script>
120
+
121
+ <!-- =========================== -->
122
+ <!-- AREA CHART -->
123
+ <!-- =========================== -->
124
+ <div
125
+ class="hr-area-chart {className ?? ''}"
126
+ role="img"
127
+ aria-label="Area chart"
128
+ onpointerleave={() => (active = null)}
129
+ >
130
+ {#if figureRows && display}
131
+ <Figure series={figureRows} eyebrow={formatX(display[xKey])} format={formatValue} size="md" />
132
+ {:else}
133
+ <Figure value="—" eyebrow="Latest" size="md" />
134
+ {/if}
135
+
136
+ <div class="hr-chart-body" style:height="{height}px">
137
+ {#if data.length === 0}
138
+ <div class="empty"><p>No data</p></div>
139
+ {:else}
140
+ <Chart
141
+ {data}
142
+ x={xKey}
143
+ y={series[0]?.key}
144
+ yDomain={[0, yMax || 1]}
145
+ yNice
146
+ padding={{ left: 48, bottom: 28, top: 8, right: 12 }}
147
+ tooltipContext={{ mode: "quadtree-x" }}
148
+ motion={DOMAIN_MOTION}
149
+ >
150
+ <Svg>
151
+ <Axis placement="left" format={(v) => formatValue(Number(v))} ticks={4} />
152
+ <Axis placement="bottom" format={(v) => formatX(v)} ticks={xTicks} />
153
+ <g class="hr-area-grid">
154
+ <Axis placement="left" grid rule={false} ticks={4} format={() => ""} />
155
+ </g>
156
+ {#if fade > 0}
157
+ <EdgeFade id="{uid}-fade" amount={fade} />
158
+ {/if}
159
+ <Crosshair id="{uid}-cross" />
160
+ {#if reference}
161
+ <ReferenceLine {reference} />
162
+ {/if}
163
+ <HoverFocus>
164
+ <Reveal>
165
+ {#each series as s, i (s.key)}
166
+ <LinearGradient id="{uid}-{i}" vertical>
167
+ {#snippet stopsContent()}
168
+ <stop offset="0%" stop-color={s.color} stop-opacity="0.45" />
169
+ <stop offset="100%" stop-color={s.color} stop-opacity="0.04" />
170
+ {/snippet}
171
+ </LinearGradient>
172
+ <!-- WHY `y1` and not `y`: layerchart's Area has no `y` prop, so every
173
+ series drew the first series' shape and the gradients stacked on
174
+ one path. See `Area.shared.svelte.js`. -->
175
+ <Area y1={s.key} {curve} fill="url(#{uid}-{i})" mask={fadeMask} />
176
+ <Spline y={s.key} {curve} style="stroke: {s.color}; fill: none" stroke-width="2" />
177
+ {/each}
178
+ </Reveal>
179
+ {#snippet segment()}
180
+ {#each series as s (s.key)}
181
+ <Spline y={s.key} {curve} style="stroke: {s.color}; fill: none" stroke-width="2" />
182
+ {/each}
183
+ {/snippet}
184
+ </HoverFocus>
185
+ <Highlight points lines={{ style: `stroke: url(#${uid}-cross)` }} />
186
+ </Svg>
187
+ <Tooltip.Root x="data" y={TOOLTIP_TOP} xOffset={TOOLTIP_GAP}>
188
+ {#snippet children({ data: d })}
189
+ <Tooltip.Header value={trackActive(d as TData)} />
190
+ <Tooltip.List>
191
+ {#each series as s (s.key)}
192
+ <Tooltip.Item
193
+ label={s.label}
194
+ value={formatValue(Number(d[s.key]))}
195
+ color={s.color}
196
+ />
197
+ {/each}
198
+ </Tooltip.List>
199
+ {/snippet}
200
+ </Tooltip.Root>
201
+ </Chart>
202
+ {/if}
203
+ </div>
204
+ </div>
@@ -0,0 +1,119 @@
1
+ /* Styles for BarChart. Plain stylesheet (see area-chart.css for why). Selectors
2
+ are namespaced under `.hr-bar-chart`. */
3
+
4
+ /* WHY: self-contained visually-hidden styling so the data table reaches
5
+ screen readers without depending on the consumer's CSS framework. */
6
+ .hr-sr-only {
7
+ position: absolute;
8
+ width: 1px;
9
+ height: 1px;
10
+ padding: 0;
11
+ margin: -1px;
12
+ overflow: hidden;
13
+ clip: rect(0, 0, 0, 0);
14
+ white-space: nowrap;
15
+ border: 0;
16
+ }
17
+ .hr-bar-chart {
18
+ width: 100%;
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 0.5rem;
22
+ }
23
+ .hr-bar-chart .hr-chart-body {
24
+ width: 100%;
25
+ }
26
+
27
+ /* WHY: height="fill" grows the chart to its parent's height (e.g. a card that
28
+ stretches to match a taller sibling) instead of the fixed body height. The
29
+ parent must be a flex column that gives this a determinate height. */
30
+ .hr-bar-chart.hr-bar-fill {
31
+ flex: 1 1 auto;
32
+ min-height: 0;
33
+ }
34
+ .hr-bar-chart.hr-bar-fill .hr-chart-body {
35
+ flex: 1 1 auto;
36
+ min-height: 0;
37
+ }
38
+
39
+ .hr-bar-chart .empty {
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ height: 100%;
44
+ border: 1px dashed var(--chart-grid, oklch(0.5 0.01 286 / 0.2));
45
+ border-radius: 0.5rem;
46
+ }
47
+ .hr-bar-chart .empty p {
48
+ font-size: 0.8125rem;
49
+ color: var(--chart-label, oklch(0.5 0.01 286));
50
+ margin: 0;
51
+ }
52
+
53
+ /* WHY: Bars render as <path> elements, not <rect>. Selectors target the path's class directly. */
54
+ .hr-bar-chart .hr-bar-bars {
55
+ transform-box: fill-box;
56
+ transform-origin: bottom;
57
+ animation: hr-bar-grow var(--chart-motion-reveal, 1100ms)
58
+ var(--chart-ease-reveal, cubic-bezier(0.85, 0, 0.15, 1)) both;
59
+ transition: opacity var(--chart-motion-fast, 150ms) ease-in-out;
60
+ }
61
+ .hr-bar-chart .hr-bar-bars:nth-of-type(2) {
62
+ animation-delay: var(--chart-stagger, 30ms);
63
+ }
64
+ .hr-bar-chart .hr-bar-bars:nth-of-type(3) {
65
+ animation-delay: calc(var(--chart-stagger, 30ms) * 2);
66
+ }
67
+ .hr-bar-chart .hr-bar-bars:nth-of-type(4) {
68
+ animation-delay: calc(var(--chart-stagger, 30ms) * 3);
69
+ }
70
+ .hr-bar-chart .hr-bar-bars:nth-of-type(5) {
71
+ animation-delay: calc(var(--chart-stagger, 30ms) * 4);
72
+ }
73
+ .hr-bar-chart .hr-bar-bars:nth-of-type(6) {
74
+ animation-delay: calc(var(--chart-stagger, 30ms) * 5);
75
+ }
76
+ .hr-bar-chart .hr-bar-bars:nth-of-type(7) {
77
+ animation-delay: calc(var(--chart-stagger, 30ms) * 6);
78
+ }
79
+ .hr-bar-chart .hr-bar-bars:nth-of-type(8) {
80
+ animation-delay: calc(var(--chart-stagger, 30ms) * 7);
81
+ }
82
+ .hr-bar-chart .hr-bar-bars:nth-of-type(9) {
83
+ animation-delay: calc(var(--chart-stagger, 30ms) * 8);
84
+ }
85
+ .hr-bar-chart .hr-bar-bars:nth-of-type(10) {
86
+ animation-delay: calc(var(--chart-stagger, 30ms) * 9);
87
+ }
88
+ .hr-bar-chart .hr-bar-bars:nth-of-type(11) {
89
+ animation-delay: calc(var(--chart-stagger, 30ms) * 10);
90
+ }
91
+ .hr-bar-chart .hr-bar-bars:nth-of-type(n + 12) {
92
+ animation-delay: calc(var(--chart-stagger, 30ms) * 11);
93
+ }
94
+ @keyframes hr-bar-grow {
95
+ from {
96
+ transform: scaleY(0);
97
+ }
98
+ }
99
+ .hr-bar-horizontal .hr-bar-bars {
100
+ transform-origin: left;
101
+ animation-name: hr-bar-grow-x;
102
+ }
103
+ @keyframes hr-bar-grow-x {
104
+ from {
105
+ transform: scaleX(0);
106
+ }
107
+ }
108
+ .hr-bar-chart.hr-bar-has-active .hr-bar-bars {
109
+ opacity: 0.3;
110
+ }
111
+ .hr-bar-chart .hr-bar-active {
112
+ pointer-events: none;
113
+ }
114
+
115
+ @media (prefers-reduced-motion: reduce) {
116
+ .hr-bar-chart .hr-bar-bars {
117
+ animation: none;
118
+ }
119
+ }
@@ -0,0 +1,275 @@
1
+ <script lang="ts" generics="TData extends Record<string, unknown>">
2
+ // =========================
3
+ // IMPORTS
4
+ // =========================
5
+ import "./bar-chart.css";
6
+ import { Axis, Bars, Chart, Highlight, Svg, Tooltip } from "layerchart";
7
+ import { scaleBand } from "d3-scale";
8
+ import Figure from "./figure.svelte";
9
+ import { TOOLTIP_GAP, TOOLTIP_TOP } from "./motion.js";
10
+
11
+ // =========================
12
+ // TYPES
13
+ // =========================
14
+ type Series = { key: keyof TData & string; label: string; color: string };
15
+
16
+ interface Props {
17
+ data: TData[];
18
+ xKey: keyof TData & string;
19
+ /** The bar's value. Required unless `series` is set. */
20
+ yKey?: keyof TData & string;
21
+ /** Stacks these keys into one bar per row; the bar's value is their sum. */
22
+ series?: Series[];
23
+ color?: string;
24
+ /** A fill per bar, for bands whose meaning changes along the axis. */
25
+ colorOf?: (d: TData) => string;
26
+ /** Body height in px, or "fill" to grow to the parent's height (parent must
27
+ * be a flex column that gives the chart a determinate height). Default 240. */
28
+ height?: number | "fill";
29
+ orientation?: "vertical" | "horizontal";
30
+ /** Left padding for horizontal charts (label space). Default 80. */
31
+ leftPadding?: number;
32
+ formatValue?: (v: number) => string;
33
+ formatX?: (v: unknown) => string;
34
+ /** Axis tick label. Return "" to skip a tick. Defaults to formatX. */
35
+ formatTick?: (v: unknown) => string;
36
+ /** Screen-reader summary; also renders an sr-only data table when set. */
37
+ label?: string;
38
+ /** Column header for the x values in the sr-only table. */
39
+ xLabel?: string;
40
+ /** Column header for the y values in the sr-only table, and the tooltip's label. */
41
+ yLabel?: string;
42
+ /** Extra tooltip rows under the value, for a second measure of the same row. */
43
+ details?: (d: TData) => { label: string; value: string }[];
44
+ /** Show the value/label figure above the chart. */
45
+ showFigure?: boolean;
46
+ class?: string;
47
+ };
48
+
49
+ // =========================
50
+ // PROPS
51
+ // =========================
52
+ let {
53
+ data,
54
+ xKey,
55
+ yKey,
56
+ series,
57
+ color = "var(--chart-1)",
58
+ colorOf,
59
+ height = 240,
60
+ orientation = "vertical",
61
+ leftPadding = 80,
62
+ formatValue = (v) => v.toLocaleString(),
63
+ formatX = (v) => String(v),
64
+ formatTick,
65
+ label,
66
+ xLabel,
67
+ yLabel,
68
+ details,
69
+ showFigure = true,
70
+ class: className,
71
+ }: Props = $props();
72
+
73
+ // =========================
74
+ // STATE
75
+ // =========================
76
+ let active = $state<TData | null>(null);
77
+
78
+ // ========================= FUNCTIONS ========================= WHY: called from the tooltip header's value expression so it re-runs on every active-band change; an unread {@const} side effect only evaluates once.
79
+
80
+ // The microtask defers the state write out of template evaluation.
81
+ function trackActive(d: TData): string {
82
+ queueMicrotask(() => {
83
+ active = d;
84
+ });
85
+ return formatX(d[xKey]);
86
+ }
87
+
88
+ function valueOf(d: TData): number {
89
+ if (series) return series.reduce((sum, s) => sum + Number(d[s.key] ?? 0), 0);
90
+ return Number(yKey ? (d[yKey] ?? 0) : 0);
91
+ }
92
+
93
+ /** Where one series' segment starts and ends inside its stacked bar. */
94
+ function span(d: TData, key: string): [number, number] {
95
+ let at = 0;
96
+ for (const s of series ?? []) {
97
+ const next = at + Number(d[s.key] ?? 0);
98
+ if (s.key === key) return [at, next];
99
+ at = next;
100
+ }
101
+ return [0, 0];
102
+ }
103
+
104
+ // =========================
105
+ // DERIVED
106
+ // =========================
107
+ const yMax = $derived(Math.max(0, ...data.map(valueOf)));
108
+ const total = $derived(data.reduce((sum, d) => sum + valueOf(d), 0));
109
+ const valueLabel = $derived(yLabel ?? String(yKey ?? "value"));
110
+ const fill = $derived(height === "fill");
111
+ const horizontal = $derived(orientation === "horizontal");
112
+ const radius = $derived(horizontal ? "right" : "top");
113
+ // One Bars layer per fill: a stacked series, a colorOf group, or the single color.
114
+ const layers = $derived.by(() => {
115
+ if (series) {
116
+ return series.map((s) => ({
117
+ key: s.key,
118
+ color: s.color,
119
+ rows: data,
120
+ at: (d: TData) => span(d, s.key),
121
+ }));
122
+ }
123
+ const groups = new Map<string, TData[]>();
124
+ for (const d of data) {
125
+ const c = colorOf?.(d) ?? color;
126
+ groups.set(c, [...(groups.get(c) ?? []), d]);
127
+ }
128
+ return [...groups].map(([c, rows]) => ({ key: c, color: c, rows, at: undefined }));
129
+ });
130
+ const activeColor = $derived(active && colorOf ? colorOf(active) : color);
131
+ </script>
132
+
133
+ <!-- =========================== -->
134
+ <!-- BAR CHART -->
135
+ <!-- =========================== -->
136
+ <div
137
+ class="hr-bar-chart {className ?? ''}"
138
+ class:hr-bar-horizontal={horizontal}
139
+ class:hr-bar-fill={fill}
140
+ class:hr-bar-has-active={active !== null && !series}
141
+ role="img"
142
+ aria-label={label ?? "Bar chart"}
143
+ onpointerleave={() => (active = null)}
144
+ >
145
+ {#if showFigure}
146
+ <Figure
147
+ value={active ? valueOf(active) : total}
148
+ color="var(--chart-1)"
149
+ eyebrow={active ? formatX(active[xKey]) : "Total"}
150
+ format={formatValue}
151
+ size="md"
152
+ />
153
+ {/if}
154
+
155
+ <div class="hr-chart-body" style:height={fill ? null : `${height}px`}>
156
+ {#if data.length === 0}
157
+ <div class="empty"><p>No data</p></div>
158
+ {:else}
159
+ <Chart
160
+ {data}
161
+ x={horizontal ? valueOf : xKey}
162
+ y={horizontal ? xKey : valueOf}
163
+ xScale={horizontal ? undefined : scaleBand().padding(0.3)}
164
+ yScale={horizontal ? scaleBand().padding(0.3) : undefined}
165
+ xDomain={horizontal ? [0, yMax || 1] : undefined}
166
+ yDomain={horizontal ? undefined : [0, yMax || 1]}
167
+ xNice={horizontal}
168
+ yNice={!horizontal}
169
+ padding={{ left: horizontal ? leftPadding : 48, bottom: 28, top: 8, right: 12 }}
170
+ tooltipContext={{ mode: "band" }}
171
+ >
172
+ <Svg>
173
+ {#if horizontal}
174
+ <Axis placement="left" format={(v) => (formatTick ?? formatX)(v)} />
175
+ <Axis placement="bottom" format={(v) => formatValue(Number(v))} ticks={4} />
176
+ <g class="hr-bar-grid">
177
+ <Axis placement="bottom" grid rule={false} ticks={4} format={() => ""} />
178
+ </g>
179
+ {:else}
180
+ <Axis placement="left" format={(v) => formatValue(Number(v))} ticks={4} />
181
+ <Axis placement="bottom" format={(v) => (formatTick ?? formatX)(v)} />
182
+ <g class="hr-bar-grid">
183
+ <Axis placement="left" grid rule={false} ticks={4} format={() => ""} />
184
+ </g>
185
+ {/if}
186
+ {#each layers as layer (layer.key)}
187
+ {#if layer.at && horizontal}
188
+ <Bars x={layer.at} style="fill: {layer.color}" class="hr-bar-bars" />
189
+ {:else if layer.at}
190
+ <Bars y={layer.at} style="fill: {layer.color}" class="hr-bar-bars" />
191
+ {:else}
192
+ <Bars
193
+ data={layer.rows}
194
+ rounded={radius}
195
+ radius={4}
196
+ style="fill: {layer.color}"
197
+ class="hr-bar-bars"
198
+ />
199
+ {/if}
200
+ {/each}
201
+ <!-- WHY: the band tooltip layer eats pointer events, so bar :hover never
202
+ fires. The active bar is a Highlight copy; motion "none" keeps it from
203
+ sliding between bands. -->
204
+ {#if !series}
205
+ <Highlight
206
+ motion="none"
207
+ bar={{ rounded: radius, radius: 4, fill: activeColor, class: "hr-bar-active" }}
208
+ />
209
+ {/if}
210
+ </Svg>
211
+ <Tooltip.Root
212
+ x={horizontal ? "pointer" : "data"}
213
+ y={horizontal ? "data" : TOOLTIP_TOP}
214
+ xOffset={TOOLTIP_GAP}
215
+ >
216
+ {#snippet children({ data: d })}
217
+ <Tooltip.Header value={trackActive(d as TData)} />
218
+ <Tooltip.List>
219
+ {#if series}
220
+ {#each series as s (s.key)}
221
+ <Tooltip.Item
222
+ label={s.label}
223
+ value={formatValue(Number(d[s.key] ?? 0))}
224
+ color={s.color}
225
+ />
226
+ {/each}
227
+ {:else}
228
+ <Tooltip.Item
229
+ label={valueLabel}
230
+ value={formatValue(valueOf(d as TData))}
231
+ color={colorOf ? colorOf(d as TData) : color}
232
+ />
233
+ {#each details?.(d as TData) ?? [] as row (row.label)}
234
+ <Tooltip.Item label={row.label} value={row.value} />
235
+ {/each}
236
+ {/if}
237
+ </Tooltip.List>
238
+ {/snippet}
239
+ </Tooltip.Root>
240
+ </Chart>
241
+ {/if}
242
+ </div>
243
+
244
+ {#if label && data.length > 0}
245
+ <table class="hr-sr-only">
246
+ <caption>{label}</caption>
247
+ <thead>
248
+ <tr>
249
+ <th scope="col">{xLabel}</th>
250
+ {#if series}
251
+ {#each series as s (s.key)}
252
+ <th scope="col">{s.label}</th>
253
+ {/each}
254
+ {:else}
255
+ <th scope="col">{yLabel}</th>
256
+ {/if}
257
+ </tr>
258
+ </thead>
259
+ <tbody>
260
+ {#each data as d, i (i)}
261
+ <tr>
262
+ <th scope="row">{formatX(d[xKey])}</th>
263
+ {#if series}
264
+ {#each series as s (s.key)}
265
+ <td>{formatValue(Number(d[s.key] ?? 0))}</td>
266
+ {/each}
267
+ {:else}
268
+ <td>{formatValue(valueOf(d))}</td>
269
+ {/if}
270
+ </tr>
271
+ {/each}
272
+ </tbody>
273
+ </table>
274
+ {/if}
275
+ </div>