@epilot/spark-ui 0.20.0 → 0.20.2

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 (314) hide show
  1. package/dist/components/accordion.cjs +7 -7
  2. package/dist/components/accordion.cjs.map +1 -1
  3. package/dist/components/accordion.d.ts +1 -1
  4. package/dist/components/accordion.js +1 -1
  5. package/dist/components/alert-dialog.cjs +10 -10
  6. package/dist/components/alert-dialog.cjs.map +1 -1
  7. package/dist/components/alert-dialog.js +1 -1
  8. package/dist/components/attachment.cjs +10 -10
  9. package/dist/components/attachment.cjs.map +1 -1
  10. package/dist/components/attachment.d.cts +2 -2
  11. package/dist/components/attachment.d.ts +2 -2
  12. package/dist/components/autocomplete.cjs +13 -13
  13. package/dist/components/autocomplete.cjs.map +1 -1
  14. package/dist/components/autocomplete.js +1 -1
  15. package/dist/components/avatar.cjs +2 -2
  16. package/dist/components/avatar.cjs.map +1 -1
  17. package/dist/components/avatar.js +1 -1
  18. package/dist/components/badge.cjs +2 -2
  19. package/dist/components/badge.cjs.map +1 -1
  20. package/dist/components/badge.js +2 -2
  21. package/dist/components/badge.js.map +1 -1
  22. package/dist/components/bar-chart-with-total.d.cts +1 -1
  23. package/dist/components/bar-chart-with-total.d.ts +1 -1
  24. package/dist/components/bar-chart.cjs +4 -4
  25. package/dist/components/bar-chart.cjs.map +1 -1
  26. package/dist/components/bar-chart.js +2 -2
  27. package/dist/components/bar-chart.js.map +1 -1
  28. package/dist/components/box.cjs +2 -2
  29. package/dist/components/box.cjs.map +1 -1
  30. package/dist/components/box.js +1 -1
  31. package/dist/components/breadcrumbs.cjs +7 -7
  32. package/dist/components/breadcrumbs.cjs.map +1 -1
  33. package/dist/components/bubble.cjs +5 -5
  34. package/dist/components/bubble.cjs.map +1 -1
  35. package/dist/components/bubble.d.cts +1 -1
  36. package/dist/components/bubble.d.ts +1 -1
  37. package/dist/components/button-group.cjs +5 -5
  38. package/dist/components/button-group.cjs.map +1 -1
  39. package/dist/components/button-group.js +1 -1
  40. package/dist/components/button.cjs +2 -2
  41. package/dist/components/button.cjs.map +1 -1
  42. package/dist/components/button.d.cts +3 -3
  43. package/dist/components/button.d.ts +3 -3
  44. package/dist/components/button.js +1 -1
  45. package/dist/components/button.js.map +1 -1
  46. package/dist/components/calendar.cjs +12 -11
  47. package/dist/components/calendar.cjs.map +1 -1
  48. package/dist/components/calendar.js +5 -4
  49. package/dist/components/calendar.js.map +1 -1
  50. package/dist/components/callout.cjs +8 -8
  51. package/dist/components/callout.cjs.map +1 -1
  52. package/dist/components/callout.d.ts +1 -1
  53. package/dist/components/callout.js +1 -1
  54. package/dist/components/card.cjs +16 -16
  55. package/dist/components/card.cjs.map +1 -1
  56. package/dist/components/card.js +2 -2
  57. package/dist/components/card.js.map +1 -1
  58. package/dist/components/chart.cjs +2 -2
  59. package/dist/components/chart.cjs.map +1 -1
  60. package/dist/components/checkbox.cjs +2 -2
  61. package/dist/components/checkbox.cjs.map +1 -1
  62. package/dist/components/checkbox.js +1 -1
  63. package/dist/components/checkbox.js.map +1 -1
  64. package/dist/components/circular-gauge.cjs +3 -3
  65. package/dist/components/circular-gauge.cjs.map +1 -1
  66. package/dist/components/circular-gauge.js +1 -1
  67. package/dist/components/circular-gauge.js.map +1 -1
  68. package/dist/components/day-of-week-picker.cjs +4 -4
  69. package/dist/components/day-of-week-picker.cjs.map +1 -1
  70. package/dist/components/dialog.cjs +9 -9
  71. package/dist/components/dialog.cjs.map +1 -1
  72. package/dist/components/dialog.js +1 -1
  73. package/dist/components/donut-chart.cjs.map +1 -1
  74. package/dist/components/donut-chart.js.map +1 -1
  75. package/dist/components/drawer.cjs +8 -8
  76. package/dist/components/drawer.cjs.map +1 -1
  77. package/dist/components/drawer.js +2 -2
  78. package/dist/components/drawer.js.map +1 -1
  79. package/dist/components/dropdown-menu.cjs +11 -11
  80. package/dist/components/dropdown-menu.cjs.map +1 -1
  81. package/dist/components/dropdown-menu.js +1 -1
  82. package/dist/components/empty-state.d.ts +1 -1
  83. package/dist/components/energy-breakdown.cjs +80 -0
  84. package/dist/components/energy-breakdown.cjs.map +1 -0
  85. package/dist/components/energy-breakdown.d.cts +24 -0
  86. package/dist/components/energy-breakdown.d.ts +24 -0
  87. package/dist/components/energy-breakdown.js +79 -0
  88. package/dist/components/energy-breakdown.js.map +1 -0
  89. package/dist/components/energy-history-chart.cjs +63 -0
  90. package/dist/components/energy-history-chart.cjs.map +1 -0
  91. package/dist/components/energy-history-chart.d.cts +64 -0
  92. package/dist/components/energy-history-chart.d.ts +64 -0
  93. package/dist/components/energy-history-chart.js +62 -0
  94. package/dist/components/energy-history-chart.js.map +1 -0
  95. package/dist/components/entity-card.cjs +11 -11
  96. package/dist/components/entity-card.cjs.map +1 -1
  97. package/dist/components/entity-card.d.ts +1 -1
  98. package/dist/components/field.cjs +16 -16
  99. package/dist/components/field.cjs.map +1 -1
  100. package/dist/components/field.js +1 -1
  101. package/dist/components/field.js.map +1 -1
  102. package/dist/components/hero-metric.cjs +4 -4
  103. package/dist/components/hero-metric.cjs.map +1 -1
  104. package/dist/components/icon.cjs +6 -6
  105. package/dist/components/icon.cjs.map +1 -1
  106. package/dist/components/icon.js +5 -5
  107. package/dist/components/icon.js.map +1 -1
  108. package/dist/components/info-block.cjs +9 -9
  109. package/dist/components/info-block.cjs.map +1 -1
  110. package/dist/components/info-block.d.cts +1 -1
  111. package/dist/components/info-block.d.ts +1 -1
  112. package/dist/components/input-date.cjs +5 -4
  113. package/dist/components/input-date.cjs.map +1 -1
  114. package/dist/components/input-date.d.cts +3 -1
  115. package/dist/components/input-date.d.ts +3 -1
  116. package/dist/components/input-date.js +3 -2
  117. package/dist/components/input-date.js.map +1 -1
  118. package/dist/components/input-group.cjs +5 -5
  119. package/dist/components/input-group.cjs.map +1 -1
  120. package/dist/components/input-otp.cjs +3 -3
  121. package/dist/components/input-otp.cjs.map +1 -1
  122. package/dist/components/input-otp.js +1 -1
  123. package/dist/components/input.cjs +3 -3
  124. package/dist/components/input.cjs.map +1 -1
  125. package/dist/components/input.js +1 -1
  126. package/dist/components/label.cjs +2 -2
  127. package/dist/components/label.cjs.map +1 -1
  128. package/dist/components/layout.cjs +2 -2
  129. package/dist/components/layout.cjs.map +1 -1
  130. package/dist/components/line-chart.cjs +7 -7
  131. package/dist/components/line-chart.cjs.map +1 -1
  132. package/dist/components/line-chart.js +5 -5
  133. package/dist/components/line-chart.js.map +1 -1
  134. package/dist/components/list.cjs +11 -11
  135. package/dist/components/list.cjs.map +1 -1
  136. package/dist/components/list.d.cts +7 -2
  137. package/dist/components/list.d.ts +7 -2
  138. package/dist/components/list.js +2 -2
  139. package/dist/components/list.js.map +1 -1
  140. package/dist/components/marker.cjs +4 -4
  141. package/dist/components/marker.cjs.map +1 -1
  142. package/dist/components/marker.d.cts +1 -1
  143. package/dist/components/marker.d.ts +1 -1
  144. package/dist/components/message-scroller.cjs +8 -8
  145. package/dist/components/message-scroller.cjs.map +1 -1
  146. package/dist/components/message-scroller.js +4 -4
  147. package/dist/components/message-scroller.js.map +1 -1
  148. package/dist/components/message.cjs +7 -7
  149. package/dist/components/message.cjs.map +1 -1
  150. package/dist/components/number-flow.d.cts +2 -12
  151. package/dist/components/number-flow.d.ts +2 -12
  152. package/dist/components/page-header.cjs +6 -6
  153. package/dist/components/page-header.cjs.map +1 -1
  154. package/dist/components/page-header.js +1 -1
  155. package/dist/components/period-tabs.cjs +6 -6
  156. package/dist/components/period-tabs.cjs.map +1 -1
  157. package/dist/components/pill.cjs +4 -4
  158. package/dist/components/pill.cjs.map +1 -1
  159. package/dist/components/pill.js +4 -4
  160. package/dist/components/pill.js.map +1 -1
  161. package/dist/components/popover.cjs +2 -2
  162. package/dist/components/popover.cjs.map +1 -1
  163. package/dist/components/power-history-chart.cjs +498 -0
  164. package/dist/components/power-history-chart.cjs.map +1 -0
  165. package/dist/components/power-history-chart.d.cts +71 -0
  166. package/dist/components/power-history-chart.d.ts +71 -0
  167. package/dist/components/power-history-chart.js +496 -0
  168. package/dist/components/power-history-chart.js.map +1 -0
  169. package/dist/components/power-network.cjs +559 -0
  170. package/dist/components/power-network.cjs.map +1 -0
  171. package/dist/components/power-network.d.cts +80 -0
  172. package/dist/components/power-network.d.ts +80 -0
  173. package/dist/components/power-network.js +553 -0
  174. package/dist/components/power-network.js.map +1 -0
  175. package/dist/components/progress.cjs +3 -3
  176. package/dist/components/progress.cjs.map +1 -1
  177. package/dist/components/progress.js +1 -1
  178. package/dist/components/progress.js.map +1 -1
  179. package/dist/components/radio-card-group.cjs +3 -3
  180. package/dist/components/radio-card-group.cjs.map +1 -1
  181. package/dist/components/radio.cjs +3 -3
  182. package/dist/components/radio.cjs.map +1 -1
  183. package/dist/components/radio.js +1 -1
  184. package/dist/components/sankey-chart.cjs +15 -5
  185. package/dist/components/sankey-chart.cjs.map +1 -1
  186. package/dist/components/sankey-chart.d.cts +3 -2
  187. package/dist/components/sankey-chart.d.ts +3 -2
  188. package/dist/components/sankey-chart.js +13 -4
  189. package/dist/components/sankey-chart.js.map +1 -1
  190. package/dist/components/schedule-editor.cjs +9 -9
  191. package/dist/components/schedule-editor.cjs.map +1 -1
  192. package/dist/components/schedule-list.cjs +11 -11
  193. package/dist/components/schedule-list.cjs.map +1 -1
  194. package/dist/components/schedule-list.js +1 -1
  195. package/dist/components/scroll-area.cjs +3 -3
  196. package/dist/components/scroll-area.cjs.map +1 -1
  197. package/dist/components/segmented-control.cjs +8 -8
  198. package/dist/components/segmented-control.cjs.map +1 -1
  199. package/dist/components/segmented-control.d.cts +2 -16
  200. package/dist/components/segmented-control.d.ts +2 -16
  201. package/dist/components/segmented-control.js +5 -5
  202. package/dist/components/segmented-control.js.map +1 -1
  203. package/dist/components/select.cjs +13 -13
  204. package/dist/components/select.cjs.map +1 -1
  205. package/dist/components/select.js +2 -2
  206. package/dist/components/separator.cjs +3 -3
  207. package/dist/components/separator.cjs.map +1 -1
  208. package/dist/components/series-legend.cjs +4 -4
  209. package/dist/components/series-legend.cjs.map +1 -1
  210. package/dist/components/sheet.cjs +9 -9
  211. package/dist/components/sheet.cjs.map +1 -1
  212. package/dist/components/sheet.js +1 -1
  213. package/dist/components/skeleton.cjs +3 -3
  214. package/dist/components/skeleton.cjs.map +1 -1
  215. package/dist/components/slider.cjs +11 -11
  216. package/dist/components/slider.cjs.map +1 -1
  217. package/dist/components/slider.js.map +1 -1
  218. package/dist/components/solar-forecast-chart.cjs +108 -0
  219. package/dist/components/solar-forecast-chart.cjs.map +1 -0
  220. package/dist/components/solar-forecast-chart.d.cts +77 -0
  221. package/dist/components/solar-forecast-chart.d.ts +77 -0
  222. package/dist/components/solar-forecast-chart.js +103 -0
  223. package/dist/components/solar-forecast-chart.js.map +1 -0
  224. package/dist/components/sortable-list.cjs +3 -3
  225. package/dist/components/sortable-list.cjs.map +1 -1
  226. package/dist/components/spinner.cjs +2 -2
  227. package/dist/components/spinner.cjs.map +1 -1
  228. package/dist/components/stat-card.cjs +8 -8
  229. package/dist/components/stat-card.cjs.map +1 -1
  230. package/dist/components/stepper.cjs +3 -3
  231. package/dist/components/stepper.cjs.map +1 -1
  232. package/dist/components/stepper.js +1 -1
  233. package/dist/components/switch.cjs +3 -3
  234. package/dist/components/switch.cjs.map +1 -1
  235. package/dist/components/switch.js +2 -2
  236. package/dist/components/switch.js.map +1 -1
  237. package/dist/components/table.cjs +9 -9
  238. package/dist/components/table.cjs.map +1 -1
  239. package/dist/components/table.js +1 -1
  240. package/dist/components/tabs.cjs +10 -10
  241. package/dist/components/tabs.cjs.map +1 -1
  242. package/dist/components/tabs.d.cts +1 -8
  243. package/dist/components/tabs.d.ts +1 -8
  244. package/dist/components/tabs.js +3 -3
  245. package/dist/components/tabs.js.map +1 -1
  246. package/dist/components/textarea.cjs +3 -3
  247. package/dist/components/textarea.cjs.map +1 -1
  248. package/dist/components/textarea.js +1 -1
  249. package/dist/components/theme.cjs +2 -2
  250. package/dist/components/theme.cjs.map +1 -1
  251. package/dist/components/theme.js +1 -1
  252. package/dist/components/thumbnail.cjs +3 -3
  253. package/dist/components/thumbnail.cjs.map +1 -1
  254. package/dist/components/time-picker.cjs +3 -3
  255. package/dist/components/time-picker.cjs.map +1 -1
  256. package/dist/components/toast.cjs +40 -15
  257. package/dist/components/toast.cjs.map +1 -1
  258. package/dist/components/toast.d.cts +15 -2
  259. package/dist/components/toast.d.ts +15 -2
  260. package/dist/components/toast.js +33 -8
  261. package/dist/components/toast.js.map +1 -1
  262. package/dist/components/toggle-group.cjs +40 -0
  263. package/dist/components/toggle-group.cjs.map +1 -0
  264. package/dist/components/toggle-group.d.cts +28 -0
  265. package/dist/components/toggle-group.d.ts +28 -0
  266. package/dist/components/toggle-group.js +37 -0
  267. package/dist/components/toggle-group.js.map +1 -0
  268. package/dist/components/toggle.cjs +69 -0
  269. package/dist/components/toggle.cjs.map +1 -0
  270. package/dist/components/toggle.d.cts +23 -0
  271. package/dist/components/toggle.d.ts +23 -0
  272. package/dist/components/toggle.js +66 -0
  273. package/dist/components/toggle.js.map +1 -0
  274. package/dist/components/tooltip.cjs +4 -4
  275. package/dist/components/tooltip.cjs.map +1 -1
  276. package/dist/components/tooltip.js +2 -2
  277. package/dist/components/tooltip.js.map +1 -1
  278. package/dist/components/typography.cjs +6 -6
  279. package/dist/components/typography.cjs.map +1 -1
  280. package/dist/components/wizard-layout.cjs +8 -8
  281. package/dist/components/wizard-layout.cjs.map +1 -1
  282. package/dist/lib/generate-radix-colors.cjs +1 -1
  283. package/dist/lib/generate-radix-colors.cjs.map +1 -1
  284. package/dist/lib/generate-radix-colors.js +1 -1
  285. package/dist/lib/generate-radix-colors.js.map +1 -1
  286. package/dist/lib/input-box-variants.cjs +9 -9
  287. package/dist/lib/input-box-variants.cjs.map +1 -1
  288. package/dist/lib/toggle-group-context.cjs +12 -0
  289. package/dist/lib/toggle-group-context.cjs.map +1 -0
  290. package/dist/lib/toggle-group-context.d.cts +17 -0
  291. package/dist/lib/toggle-group-context.d.ts +17 -0
  292. package/dist/lib/toggle-group-context.js +9 -0
  293. package/dist/lib/toggle-group-context.js.map +1 -0
  294. package/dist/number-flow-Bch81ixZ.d.cts +14 -0
  295. package/dist/number-flow-Bch81ixZ.d.ts +14 -0
  296. package/dist/styles/spark-default.css +11 -0
  297. package/dist/styles/theme.css +15 -4
  298. package/dist/utils/build-sankey-nodes.cjs +72 -0
  299. package/dist/utils/build-sankey-nodes.cjs.map +1 -0
  300. package/dist/utils/build-sankey-nodes.d.cts +27 -0
  301. package/dist/utils/build-sankey-nodes.d.ts +27 -0
  302. package/dist/utils/build-sankey-nodes.js +71 -0
  303. package/dist/utils/build-sankey-nodes.js.map +1 -0
  304. package/dist/utils/cn.cjs +2 -2
  305. package/dist/utils/cn.d.cts +1 -1
  306. package/dist/utils/cn.d.ts +1 -1
  307. package/dist/utils/cn.js +1 -1
  308. package/dist/utils/format.cjs +14 -0
  309. package/dist/utils/format.cjs.map +1 -1
  310. package/dist/utils/format.d.cts +8 -1
  311. package/dist/utils/format.d.ts +8 -1
  312. package/dist/utils/format.js +12 -1
  313. package/dist/utils/format.js.map +1 -1
  314. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"slider.js","names":["React","BaseSlider"],"sources":["../../src/components/slider.tsx"],"sourcesContent":["import { Slider as BaseSlider } from '@base-ui/react/slider'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\ntype SliderValue = number | number[]\n\ntype SliderVariant = 'default' | 'ticks'\n\n// Above this many steps the tick dots become a picket fence, so `variant=\"ticks\"`\n// silently stops drawing them (the continuous rail stays).\nconst MAX_TICKS = 24\n\n// Clamp to [min, max] then snap to the step grid (origin = min), trimming float\n// noise to the step's own decimal precision. `max` is always valid even if it\n// isn't a step multiple (matches Base UI).\nfunction snapToStep(\n value: number,\n min: number,\n max: number,\n step: number,\n): number {\n const clamped = Math.min(max, Math.max(min, value))\n\n if (!(step > 0)) {\n return clamped\n }\n\n const snapped = min + Math.round((clamped - min) / step) * step\n const bounded = Math.min(max, Math.max(min, snapped))\n const decimals = (String(step).split('.')[1] ?? '').length\n\n return decimals > 0 ? Number(bounded.toFixed(decimals)) : bounded\n}\n\nfunction matchValueShape(\n next: number | readonly number[],\n current: SliderValue,\n): SliderValue {\n const nextValues = typeof next === 'number' ? [next] : [...next]\n if (Array.isArray(current)) {\n return nextValues\n }\n return nextValues[0] ?? current\n}\n\n/**\n * Base UI's slider context is internal-only (not re-exported from\n * `@base-ui/react/slider`), so the editable `<SliderInput>` can't read the\n * current value from it. Instead `<Slider>` owns the value (controllable) and\n * shares it through this Spark context — keeping direct-input robust against\n * Base UI internal changes and letting inputs live anywhere in the subtree.\n */\ntype SliderContextValue = {\n values: readonly number[]\n min: number\n max: number\n step: number\n disabled: boolean\n /**\n * Write one thumb's value (from a `SliderInput`). Snaps to the step grid and\n * clamps; range values are kept sorted. Fires `onValueChange` only when the\n * value actually changes. When `commit` is set it also fires `onValueCommitted`\n * — but only if the committed value differs from `commitBaseline` (the value\n * when editing began), so a plain focus→blur never emits a spurious commit.\n */\n setThumbValue: (\n index: number,\n next: number,\n options?: { commit?: boolean; commitBaseline?: number },\n ) => void\n}\n\nconst SliderContext = React.createContext<SliderContextValue | null>(null)\n\nfunction useSliderContext(part: string): SliderContextValue {\n const ctx = React.useContext(SliderContext)\n\n if (!ctx) {\n throw new Error(`<${part}> must be rendered inside <Slider>`)\n }\n\n return ctx\n}\n\ntype SliderProps<Value extends SliderValue = SliderValue> = Omit<\n React.ComponentProps<typeof BaseSlider.Root>,\n 'value' | 'defaultValue' | 'onValueChange' | 'onValueCommitted' | 'render'\n> & {\n value?: Value\n defaultValue?: Value\n // Generic over the value shape: a single `number` keeps single-thumb handlers\n // number-typed, an array keeps range handlers array-typed — so callers don't\n // widen/narrow at every call site.\n onValueChange?: (\n value: Value,\n eventDetails: BaseSlider.Root.ChangeEventDetails,\n ) => void\n onValueCommitted?: (\n value: Value,\n eventDetails: BaseSlider.Root.CommitEventDetails,\n ) => void\n /**\n * `ticks` marks each step with a small dot on the rail — light on the accent\n * fill, neutral on the unfilled rail. Needs a sensible `step`; the dots are\n * hidden when the step count is < 2 or exceeds {@link MAX_TICKS}, and (in the\n * default render) for vertical sliders. Default `default`.\n */\n variant?: SliderVariant\n}\n\n/**\n * Root. Owns the value (controlled via `value` or uncontrolled via\n * `defaultValue`) and provides the Spark context. With no children it renders\n * the default single/range track (a childless-default, matching the library's\n * `*Close` idiom); pass children to compose `SliderInput` / `SliderValue` /\n * `SliderControl` yourself.\n */\nfunction Slider<Value extends SliderValue = SliderValue>({\n className,\n children,\n value,\n defaultValue,\n min = 0,\n max = 100,\n step = 1,\n disabled = false,\n orientation = 'horizontal',\n variant = 'default',\n onValueChange,\n onValueCommitted,\n // Radix positioned thumbs edge-aligned (inset within the track at min/max);\n // Base UI defaults to center-aligned (half the thumb overflows). `edge` keeps\n // the classic look and the thumb inside the rail.\n thumbAlignment = 'edge',\n // The accessible name belongs on the thumb's nested range input, not just the\n // Root — so keep it on the Root (names the group) AND forward it to the\n // childless-default thumb(s). When you compose your own thumbs, label them.\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...props\n}: SliderProps<Value>) {\n const isControlled = value !== undefined\n const [uncontrolled, setUncontrolled] = React.useState<SliderValue>(\n defaultValue ?? min,\n )\n const current = (isControlled ? value : uncontrolled) as SliderValue\n const values = React.useMemo(\n () => (Array.isArray(current) ? current : [current]),\n [current],\n )\n\n // Base UI hands back `number | readonly number[]`; we forward it as the\n // caller's `Value` (the runtime shape matches what they passed in).\n const emitChange = (\n next: number | readonly number[],\n eventDetails: BaseSlider.Root.ChangeEventDetails,\n ) => {\n const shaped = matchValueShape(next, current) as Value\n\n if (!isControlled) {\n setUncontrolled(shaped as SliderValue)\n }\n\n onValueChange?.(shaped, eventDetails)\n }\n\n // The direct-input write path. Snaps + clamps; for a range it swaps one thumb\n // and re-sorts (Base UI renders from a sorted copy, so an unsorted write would\n // desync the inputs from the thumbs). Fires onValueChange only on a real\n // change; fires onValueCommitted only when the committed value moved off the\n // pre-edit baseline.\n const setThumbValue = (\n index: number,\n next: number,\n options?: { commit?: boolean; commitBaseline?: number },\n ) => {\n const snapped = snapToStep(next, min, max, step)\n const wasArray = Array.isArray(current)\n let nextValue: SliderValue\n\n if (wasArray) {\n const copy = [...(current as number[])]\n copy[index] = snapped\n copy.sort((a, b) => a - b)\n nextValue = copy\n } else {\n nextValue = snapped\n }\n\n const changed = wasArray\n ? (nextValue as number[]).some((v, i) => v !== (current as number[])[i])\n : nextValue !== current\n\n // input-driven changes have no pointer/keyboard event; Base UI's own detail\n // object isn't constructible here, so we hand the consumer a minimal one.\n const details = {\n reason: 'input-change',\n } as unknown as BaseSlider.Root.ChangeEventDetails\n\n if (changed) {\n emitChange(nextValue, details)\n }\n\n if (options?.commit) {\n const committedChanged =\n options.commitBaseline === undefined\n ? changed\n : snapped !== options.commitBaseline\n\n if (committedChanged) {\n onValueCommitted?.(\n nextValue as Value,\n details as unknown as BaseSlider.Root.CommitEventDetails,\n )\n }\n }\n }\n\n // Rebuilt each render (cheap — <Slider> only re-renders on a value change) so\n // setThumbValue always closes over the current value + latest callbacks.\n const context: SliderContextValue = {\n values,\n min,\n max,\n step,\n disabled,\n setThumbValue,\n }\n\n // ticks are horizontal-only in the default render; SliderTicks self-hides when\n // the step count isn't countable, so no density check is needed here.\n const showTicks = variant === 'ticks' && orientation === 'horizontal'\n\n return (\n <SliderContext.Provider value={context}>\n <BaseSlider.Root\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n data-slot=\"slider\"\n value={current}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n orientation={orientation}\n thumbAlignment={thumbAlignment}\n onValueChange={emitChange}\n onValueCommitted={(next, eventDetails) =>\n onValueCommitted?.(\n matchValueShape(next, current) as Value,\n eventDetails,\n )\n }\n className={cn(\n 'relative flex w-full flex-col gap-2 data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-row',\n className,\n )}\n {...props}\n >\n {children ?? (\n <SliderControl>\n <SliderTrack>\n <SliderIndicator />\n {showTicks ? <SliderTicks /> : null}\n {values.map((_, index) => (\n <SliderThumb\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n index={values.length > 1 ? index : undefined}\n key={index}\n />\n ))}\n </SliderTrack>\n </SliderControl>\n )}\n </BaseSlider.Root>\n </SliderContext.Provider>\n )\n}\n\nSlider.displayName = 'Slider'\n\n/**\n * The interactive, draggable surface. A pointer-press anywhere on it moves the\n * nearest thumb (Base UI's track-press), so its full band is the target — kept\n * at ≥24px tall (WCAG 2.5.8) with the thin rail centered.\n */\nfunction SliderControl({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Control>) {\n return (\n <BaseSlider.Control\n data-slot=\"slider-control\"\n className={cn(\n 'group/slider touch-target relative flex min-h-6 w-full touch-none items-center select-none',\n 'data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-6 data-[orientation=vertical]:justify-center',\n 'data-disabled:pointer-events-none data-disabled:opacity-50',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderControl.displayName = 'SliderControl'\n\n/** The rail — the full extent, neutral at rest. Holds the Indicator + Thumb(s). */\nfunction SliderTrack({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Track>) {\n return (\n <BaseSlider.Track\n data-slot=\"slider-track\"\n className={cn(\n // 12px rail so the corner radius is legible; follows the [data-radius] knob\n // (square at none → pill at large/full). no overflow-hidden: the thumb is a\n // child and would be clipped.\n 'relative h-3 w-full rounded-(--spark-rounded-sm) bg-gray-a4',\n 'data-[orientation=vertical]:h-full data-[orientation=vertical]:w-3',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderTrack.displayName = 'SliderTrack'\n\n/** The filled portion (min → thumb). Accent, deepens in high-contrast. */\nfunction SliderIndicator({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Indicator>) {\n return (\n <BaseSlider.Indicator\n data-slot=\"slider-indicator\"\n className={cn(\n // matches the rail's radius so the fill's corners track the knob too\n 'rounded-(--spark-rounded-sm) bg-(--spark-slider-range,var(--spark-9))',\n // ease only while focused (keyboard/rail-click); programmatic and\n // initial value sets snap instantly instead of sweeping from 0.\n // transition-none base matters: bare duration-150 leaves property=all\n 'transition-none duration-150 group-focus-within/slider:transition-[width,height,inset-inline-start] data-dragging:transition-none motion-reduce:transition-none',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderIndicator.displayName = 'SliderIndicator'\n\n/**\n * Step tick dots overlaid on the rail (shown when `variant=\"ticks\"`). A dot at\n * each interior step — light (`--spark-slider-tick-on`, on the accent fill) for\n * steps up to the value, neutral gray beyond it. The endpoints are omitted (they\n * sit under the rounded rail ends / the thumb). Self-guards: draws nothing unless\n * the step count is in `[2, MAX_TICKS]`, so it's safe to compose directly.\n * Decorative + pointer-transparent so it never blocks a track-press. (Horizontal\n * orientation.)\n */\nfunction SliderTicks({ className, ...props }: React.ComponentProps<'div'>) {\n const { min, max, step, values } = useSliderContext('SliderTicks')\n\n if (!(step > 0) || max <= min) {\n return null\n }\n\n // count the reachable step intervals (floor, so a non-dividing step still gets\n // a dot on its last reachable step); drop the final one when it lands on max.\n const intervals = Math.floor((max - min) / step)\n\n if (intervals < 2 || intervals > MAX_TICKS) {\n return null\n }\n\n const lastIsEndpoint = min + intervals * step === max\n const dotCount = lastIsEndpoint ? intervals - 1 : intervals\n\n // Active band: [min, value] for a single thumb, [low, high] for a range.\n const bandLow = values.length > 1 ? Math.min(...values) : min\n const bandHigh = values.length > 1 ? Math.max(...values) : (values[0] ?? min)\n\n return (\n <div\n aria-hidden=\"true\"\n className={cn('pointer-events-none absolute inset-0', className)}\n data-slot=\"slider-ticks\"\n {...props}\n >\n {Array.from({ length: dotCount }, (_, k) => {\n const index = k + 1 // interior steps only (skip the two endpoints)\n const position = min + index * step\n const pct = ((position - min) / (max - min)) * 100\n const filled = position >= bandLow && position <= bandHigh\n\n return (\n <span\n className={cn(\n 'absolute top-1/2 size-1 -translate-x-1/2 -translate-y-1/2 rounded-full',\n // light dot on the accent fill (flips to step-1 in HC via the token);\n // neutral gray dot on the unfilled rail.\n filled\n ? 'bg-(--spark-slider-tick-on,var(--spark-on-solid,#fff))'\n : 'bg-gray-a8',\n )}\n key={index}\n style={{ left: `${pct}%` }}\n />\n )\n })}\n </div>\n )\n}\n\nSliderTicks.displayName = 'SliderTicks'\n\nfunction SliderThumb({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Thumb>) {\n return (\n <BaseSlider.Thumb\n data-slot=\"slider-thumb\"\n className={cn(\n 'block size-4 shrink-0 rounded-(--spark-rounded-lg) bg-(--spark-slider-range,var(--spark-9))',\n 'shadow-[0_0_0_2px_var(--spark-background,#fff),0_1px_2px_0_rgb(0_0_0/0.2)]',\n \"before:absolute before:top-1/2 before:left-1/2 before:size-6 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\",\n 'outline-hidden has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-offset-2',\n 'data-dragging:ring-2 data-dragging:ring-offset-2',\n 'transition-shadow duration-150 group-focus-within/slider:transition-[inset-inline-start,top,box-shadow] data-dragging:transition-none motion-reduce:transition-none',\n 'disabled:pointer-events-none',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderThumb.displayName = 'SliderThumb'\n\nfunction SliderValue({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Value>) {\n return (\n <BaseSlider.Value\n data-slot=\"slider-value\"\n className={cn('text-sm text-gray tabular-nums', className)}\n {...props}\n />\n )\n}\n\nSliderValue.displayName = 'SliderValue'\n\ntype SliderInputProps = Omit<\n React.ComponentProps<'input'>,\n 'value' | 'defaultValue' | 'type' | 'onChange' | 'min' | 'max' | 'step'\n> & {\n /** Which thumb this input drives on a range slider. Defaults to 0. */\n index?: number\n /** Unit rendered after the field (e.g. \"€/month\"). Consumer-supplied — no default. */\n unit?: React.ReactNode\n}\n\n/**\n * Accessible direct-input — a native number field synced to a thumb's value, so\n * a user can type an exact value instead of dragging (or in addition to it).\n * Needs an accessible name: pass `aria-label` (no built-in default — portals are\n * multi-language). While typing it live-updates the thumb only for values already\n * in range (no mid-keystroke jump to the min/max); it snaps + clamps on\n * blur/Enter. `onFocus`/`onBlur`/`onKeyDown` you pass are composed with the\n * internal handlers, not replaced.\n */\nfunction SliderInput({\n className,\n index = 0,\n unit,\n disabled,\n onFocus,\n onBlur,\n onKeyDown,\n ...props\n}: SliderInputProps) {\n const ctx = useSliderContext('SliderInput')\n const currentValue = ctx.values[index] ?? ctx.min\n const [draft, setDraft] = React.useState<string>(String(currentValue))\n const [editing, setEditing] = React.useState(false)\n // the thumb value when editing began — commit fires only if it moved off this\n const baselineRef = React.useRef(currentValue)\n\n // Keep the field in sync when the value changes elsewhere (drag/keyboard),\n // but never fight the user's in-progress typing.\n React.useEffect(() => {\n if (!editing) {\n setDraft(String(currentValue))\n }\n }, [currentValue, editing])\n\n const parseInput = (raw: string): number | null => {\n if (raw.trim() === '') {\n return null\n }\n\n const parsed = Number(raw)\n\n return Number.isNaN(parsed) ? null : parsed\n }\n\n const commit = () => {\n const parsed = parseInput(draft)\n\n if (parsed === null) {\n setDraft(String(currentValue)) // reset an empty/invalid entry\n return\n }\n\n ctx.setThumbValue(index, parsed, {\n commit: true,\n commitBaseline: baselineRef.current,\n })\n }\n\n return (\n <span\n data-slot=\"slider-input\"\n className={cn('inline-flex items-baseline gap-1', className)}\n >\n <input\n type=\"number\"\n inputMode=\"decimal\"\n min={ctx.min}\n max={ctx.max}\n step={ctx.step}\n disabled={disabled ?? ctx.disabled}\n value={editing ? draft : String(currentValue)}\n onChange={(event) => {\n const raw = event.target.value\n setDraft(raw)\n const parsed = parseInput(raw)\n\n // live-update the thumb only when the typed value is already in range,\n // so a partial entry (e.g. \"7\" en route to \"75\") doesn't snap the thumb\n // to the min mid-keystroke. Out-of-range values clamp on blur/Enter.\n if (parsed !== null && parsed >= ctx.min && parsed <= ctx.max) {\n ctx.setThumbValue(index, parsed)\n }\n }}\n onFocus={(event) => {\n baselineRef.current = ctx.values[index] ?? ctx.min\n setEditing(true)\n onFocus?.(event)\n }}\n onBlur={(event) => {\n setEditing(false)\n commit()\n onBlur?.(event)\n }}\n onKeyDown={(event) => {\n // Enter blurs, and the single onBlur commit runs — no double-commit.\n if (event.key === 'Enter') {\n event.currentTarget.blur()\n }\n\n onKeyDown?.(event)\n }}\n className={cn(\n 'w-16 bg-transparent tabular-nums outline-hidden',\n 'rounded-(--spark-rounded-sm) focus-visible:ring-2 focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n // hide the native spinners — the slider is the primary affordance\n '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',\n )}\n {...props}\n />\n {unit ? (\n <span className=\"text-sm text-gray\" data-slot=\"slider-unit\">\n {unit}\n </span>\n ) : null}\n </span>\n )\n}\n\nSliderInput.displayName = 'SliderInput'\n\nexport {\n Slider,\n SliderControl,\n SliderIndicator,\n SliderInput,\n SliderThumb,\n SliderTicks,\n SliderTrack,\n SliderValue,\n}\nexport type { SliderInputProps, SliderProps, SliderVariant }\n"],"mappings":"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"}
1
+ {"version":3,"file":"slider.js","names":["React","BaseSlider"],"sources":["../../src/components/slider.tsx"],"sourcesContent":["import { Slider as BaseSlider } from '@base-ui/react/slider'\nimport * as React from 'react'\nimport { cn } from '../utils/cn'\n\ntype SliderValue = number | number[]\n\ntype SliderVariant = 'default' | 'ticks'\n\n// Above this many steps the tick dots become a picket fence, so `variant=\"ticks\"`\n// silently stops drawing them (the continuous rail stays).\nconst MAX_TICKS = 24\n\n// Clamp to [min, max] then snap to the step grid (origin = min), trimming float\n// noise to the step's own decimal precision. `max` is always valid even if it\n// isn't a step multiple (matches Base UI).\nfunction snapToStep(\n value: number,\n min: number,\n max: number,\n step: number,\n): number {\n const clamped = Math.min(max, Math.max(min, value))\n\n if (!(step > 0)) {\n return clamped\n }\n\n const snapped = min + Math.round((clamped - min) / step) * step\n const bounded = Math.min(max, Math.max(min, snapped))\n const decimals = (String(step).split('.')[1] ?? '').length\n\n return decimals > 0 ? Number(bounded.toFixed(decimals)) : bounded\n}\n\nfunction matchValueShape(\n next: number | readonly number[],\n current: SliderValue,\n): SliderValue {\n const nextValues = typeof next === 'number' ? [next] : [...next]\n if (Array.isArray(current)) {\n return nextValues\n }\n return nextValues[0] ?? current\n}\n\n/**\n * Base UI's slider context is internal-only (not re-exported from\n * `@base-ui/react/slider`), so the editable `<SliderInput>` can't read the\n * current value from it. Instead `<Slider>` owns the value (controllable) and\n * shares it through this Spark context — keeping direct-input robust against\n * Base UI internal changes and letting inputs live anywhere in the subtree.\n */\ntype SliderContextValue = {\n values: readonly number[]\n min: number\n max: number\n step: number\n disabled: boolean\n /**\n * Write one thumb's value (from a `SliderInput`). Snaps to the step grid and\n * clamps; range values are kept sorted. Fires `onValueChange` only when the\n * value actually changes. When `commit` is set it also fires `onValueCommitted`\n * — but only if the committed value differs from `commitBaseline` (the value\n * when editing began), so a plain focus→blur never emits a spurious commit.\n */\n setThumbValue: (\n index: number,\n next: number,\n options?: { commit?: boolean; commitBaseline?: number },\n ) => void\n}\n\nconst SliderContext = React.createContext<SliderContextValue | null>(null)\n\nfunction useSliderContext(part: string): SliderContextValue {\n const ctx = React.useContext(SliderContext)\n\n if (!ctx) {\n throw new Error(`<${part}> must be rendered inside <Slider>`)\n }\n\n return ctx\n}\n\ntype SliderProps<Value extends SliderValue = SliderValue> = Omit<\n React.ComponentProps<typeof BaseSlider.Root>,\n 'value' | 'defaultValue' | 'onValueChange' | 'onValueCommitted' | 'render'\n> & {\n value?: Value\n defaultValue?: Value\n // Generic over the value shape: a single `number` keeps single-thumb handlers\n // number-typed, an array keeps range handlers array-typed — so callers don't\n // widen/narrow at every call site.\n onValueChange?: (\n value: Value,\n eventDetails: BaseSlider.Root.ChangeEventDetails,\n ) => void\n onValueCommitted?: (\n value: Value,\n eventDetails: BaseSlider.Root.CommitEventDetails,\n ) => void\n /**\n * `ticks` marks each step with a small dot on the rail — light on the accent\n * fill, neutral on the unfilled rail. Needs a sensible `step`; the dots are\n * hidden when the step count is < 2 or exceeds {@link MAX_TICKS}, and (in the\n * default render) for vertical sliders. Default `default`.\n */\n variant?: SliderVariant\n}\n\n/**\n * Root. Owns the value (controlled via `value` or uncontrolled via\n * `defaultValue`) and provides the Spark context. With no children it renders\n * the default single/range track (a childless-default, matching the library's\n * `*Close` idiom); pass children to compose `SliderInput` / `SliderValue` /\n * `SliderControl` yourself.\n */\nfunction Slider<Value extends SliderValue = SliderValue>({\n className,\n children,\n value,\n defaultValue,\n min = 0,\n max = 100,\n step = 1,\n disabled = false,\n orientation = 'horizontal',\n variant = 'default',\n onValueChange,\n onValueCommitted,\n // Radix positioned thumbs edge-aligned (inset within the track at min/max);\n // Base UI defaults to center-aligned (half the thumb overflows). `edge` keeps\n // the classic look and the thumb inside the rail.\n thumbAlignment = 'edge',\n // The accessible name belongs on the thumb's nested range input, not just the\n // Root — so keep it on the Root (names the group) AND forward it to the\n // childless-default thumb(s). When you compose your own thumbs, label them.\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...props\n}: SliderProps<Value>) {\n const isControlled = value !== undefined\n const [uncontrolled, setUncontrolled] = React.useState<SliderValue>(\n defaultValue ?? min,\n )\n const current = (isControlled ? value : uncontrolled) as SliderValue\n const values = React.useMemo(\n () => (Array.isArray(current) ? current : [current]),\n [current],\n )\n\n // Base UI hands back `number | readonly number[]`; we forward it as the\n // caller's `Value` (the runtime shape matches what they passed in).\n const emitChange = (\n next: number | readonly number[],\n eventDetails: BaseSlider.Root.ChangeEventDetails,\n ) => {\n const shaped = matchValueShape(next, current) as Value\n\n if (!isControlled) {\n setUncontrolled(shaped as SliderValue)\n }\n\n onValueChange?.(shaped, eventDetails)\n }\n\n // The direct-input write path. Snaps + clamps; for a range it swaps one thumb\n // and re-sorts (Base UI renders from a sorted copy, so an unsorted write would\n // desync the inputs from the thumbs). Fires onValueChange only on a real\n // change; fires onValueCommitted only when the committed value moved off the\n // pre-edit baseline.\n const setThumbValue = (\n index: number,\n next: number,\n options?: { commit?: boolean; commitBaseline?: number },\n ) => {\n const snapped = snapToStep(next, min, max, step)\n const wasArray = Array.isArray(current)\n let nextValue: SliderValue\n\n if (wasArray) {\n const copy = [...(current as number[])]\n copy[index] = snapped\n copy.sort((a, b) => a - b)\n nextValue = copy\n } else {\n nextValue = snapped\n }\n\n const changed = wasArray\n ? (nextValue as number[]).some((v, i) => v !== (current as number[])[i])\n : nextValue !== current\n\n // input-driven changes have no pointer/keyboard event; Base UI's own detail\n // object isn't constructible here, so we hand the consumer a minimal one.\n const details = {\n reason: 'input-change',\n } as unknown as BaseSlider.Root.ChangeEventDetails\n\n if (changed) {\n emitChange(nextValue, details)\n }\n\n if (options?.commit) {\n const committedChanged =\n options.commitBaseline === undefined\n ? changed\n : snapped !== options.commitBaseline\n\n if (committedChanged) {\n onValueCommitted?.(\n nextValue as Value,\n details as unknown as BaseSlider.Root.CommitEventDetails,\n )\n }\n }\n }\n\n // Rebuilt each render (cheap — <Slider> only re-renders on a value change) so\n // setThumbValue always closes over the current value + latest callbacks.\n const context: SliderContextValue = {\n values,\n min,\n max,\n step,\n disabled,\n setThumbValue,\n }\n\n // ticks are horizontal-only in the default render; SliderTicks self-hides when\n // the step count isn't countable, so no density check is needed here.\n const showTicks = variant === 'ticks' && orientation === 'horizontal'\n\n return (\n <SliderContext.Provider value={context}>\n <BaseSlider.Root\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n data-slot=\"slider\"\n value={current}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n orientation={orientation}\n thumbAlignment={thumbAlignment}\n onValueChange={emitChange}\n onValueCommitted={(next, eventDetails) =>\n onValueCommitted?.(\n matchValueShape(next, current) as Value,\n eventDetails,\n )\n }\n className={cn(\n 'relative flex w-full flex-col gap-2 data-[orientation=vertical]:h-full data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-row',\n className,\n )}\n {...props}\n >\n {children ?? (\n <SliderControl>\n <SliderTrack>\n <SliderIndicator />\n {showTicks ? <SliderTicks /> : null}\n {values.map((_, index) => (\n <SliderThumb\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledby}\n index={values.length > 1 ? index : undefined}\n // oxlint-disable-next-line react/no-array-index-key -- Base UI identifies thumbs by their index\n key={index}\n />\n ))}\n </SliderTrack>\n </SliderControl>\n )}\n </BaseSlider.Root>\n </SliderContext.Provider>\n )\n}\n\nSlider.displayName = 'Slider'\n\n/**\n * The interactive, draggable surface. A pointer-press anywhere on it moves the\n * nearest thumb (Base UI's track-press), so its full band is the target — kept\n * at ≥24px tall (WCAG 2.5.8) with the thin rail centered.\n */\nfunction SliderControl({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Control>) {\n return (\n <BaseSlider.Control\n data-slot=\"slider-control\"\n className={cn(\n 'group/slider touch-target relative flex min-h-6 w-full touch-none items-center select-none',\n 'data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-40 data-[orientation=vertical]:w-6 data-[orientation=vertical]:justify-center',\n 'data-disabled:pointer-events-none data-disabled:opacity-50',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderControl.displayName = 'SliderControl'\n\n/** The rail — the full extent, neutral at rest. Holds the Indicator + Thumb(s). */\nfunction SliderTrack({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Track>) {\n return (\n <BaseSlider.Track\n data-slot=\"slider-track\"\n className={cn(\n // 12px rail so the corner radius is legible; follows the [data-radius] knob\n // (square at none → pill at large/full). no overflow-hidden: the thumb is a\n // child and would be clipped.\n 'relative h-3 w-full rounded-(--spark-rounded-sm) bg-gray-a4',\n 'data-[orientation=vertical]:h-full data-[orientation=vertical]:w-3',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderTrack.displayName = 'SliderTrack'\n\n/** The filled portion (min → thumb). Accent, deepens in high-contrast. */\nfunction SliderIndicator({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Indicator>) {\n return (\n <BaseSlider.Indicator\n data-slot=\"slider-indicator\"\n className={cn(\n // matches the rail's radius so the fill's corners track the knob too\n 'rounded-(--spark-rounded-sm) bg-(--spark-slider-range,var(--spark-9))',\n // ease only while focused (keyboard/rail-click); programmatic and\n // initial value sets snap instantly instead of sweeping from 0.\n // transition-none base matters: bare duration-150 leaves property=all\n 'transition-none duration-150 group-focus-within/slider:transition-[width,height,inset-inline-start] data-dragging:transition-none motion-reduce:transition-none',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderIndicator.displayName = 'SliderIndicator'\n\n/**\n * Step tick dots overlaid on the rail (shown when `variant=\"ticks\"`). A dot at\n * each interior step — light (`--spark-slider-tick-on`, on the accent fill) for\n * steps up to the value, neutral gray beyond it. The endpoints are omitted (they\n * sit under the rounded rail ends / the thumb). Self-guards: draws nothing unless\n * the step count is in `[2, MAX_TICKS]`, so it's safe to compose directly.\n * Decorative + pointer-transparent so it never blocks a track-press. (Horizontal\n * orientation.)\n */\nfunction SliderTicks({ className, ...props }: React.ComponentProps<'div'>) {\n const { min, max, step, values } = useSliderContext('SliderTicks')\n\n if (!(step > 0) || max <= min) {\n return null\n }\n\n // count the reachable step intervals (floor, so a non-dividing step still gets\n // a dot on its last reachable step); drop the final one when it lands on max.\n const intervals = Math.floor((max - min) / step)\n\n if (intervals < 2 || intervals > MAX_TICKS) {\n return null\n }\n\n const lastIsEndpoint = min + intervals * step === max\n const dotCount = lastIsEndpoint ? intervals - 1 : intervals\n\n // Active band: [min, value] for a single thumb, [low, high] for a range.\n const bandLow = values.length > 1 ? Math.min(...values) : min\n const bandHigh = values.length > 1 ? Math.max(...values) : (values[0] ?? min)\n\n return (\n <div\n aria-hidden=\"true\"\n className={cn('pointer-events-none absolute inset-0', className)}\n data-slot=\"slider-ticks\"\n {...props}\n >\n {Array.from({ length: dotCount }, (_, k) => {\n const index = k + 1 // interior steps only (skip the two endpoints)\n const position = min + index * step\n const pct = ((position - min) / (max - min)) * 100\n const filled = position >= bandLow && position <= bandHigh\n\n return (\n <span\n className={cn(\n 'absolute top-1/2 size-1 -translate-x-1/2 -translate-y-1/2 rounded-full',\n // light dot on the accent fill (flips to step-1 in HC via the token);\n // neutral gray dot on the unfilled rail.\n filled\n ? 'bg-(--spark-slider-tick-on,var(--spark-on-solid,#fff))'\n : 'bg-gray-a8',\n )}\n key={index}\n style={{ left: `${pct}%` }}\n />\n )\n })}\n </div>\n )\n}\n\nSliderTicks.displayName = 'SliderTicks'\n\nfunction SliderThumb({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Thumb>) {\n return (\n <BaseSlider.Thumb\n data-slot=\"slider-thumb\"\n className={cn(\n 'block size-4 shrink-0 rounded-(--spark-rounded-lg) bg-(--spark-slider-range,var(--spark-9))',\n 'shadow-[0_0_0_2px_var(--spark-background,#fff),0_1px_2px_0_rgb(0_0_0/0.2)]',\n \"before:absolute before:top-1/2 before:left-1/2 before:size-6 before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']\",\n 'outline-hidden has-[input:focus-visible]:ring-2 has-[input:focus-visible]:ring-offset-2',\n 'data-dragging:ring-2 data-dragging:ring-offset-2',\n 'transition-shadow duration-150 group-focus-within/slider:transition-[inset-inline-start,top,box-shadow] data-dragging:transition-none motion-reduce:transition-none',\n 'disabled:pointer-events-none',\n className,\n )}\n {...props}\n />\n )\n}\n\nSliderThumb.displayName = 'SliderThumb'\n\nfunction SliderValue({\n className,\n ...props\n}: React.ComponentProps<typeof BaseSlider.Value>) {\n return (\n <BaseSlider.Value\n data-slot=\"slider-value\"\n className={cn('text-sm text-gray tabular-nums', className)}\n {...props}\n />\n )\n}\n\nSliderValue.displayName = 'SliderValue'\n\ntype SliderInputProps = Omit<\n React.ComponentProps<'input'>,\n 'value' | 'defaultValue' | 'type' | 'onChange' | 'min' | 'max' | 'step'\n> & {\n /** Which thumb this input drives on a range slider. Defaults to 0. */\n index?: number\n /** Unit rendered after the field (e.g. \"€/month\"). Consumer-supplied — no default. */\n unit?: React.ReactNode\n}\n\n/**\n * Accessible direct-input — a native number field synced to a thumb's value, so\n * a user can type an exact value instead of dragging (or in addition to it).\n * Needs an accessible name: pass `aria-label` (no built-in default — portals are\n * multi-language). While typing it live-updates the thumb only for values already\n * in range (no mid-keystroke jump to the min/max); it snaps + clamps on\n * blur/Enter. `onFocus`/`onBlur`/`onKeyDown` you pass are composed with the\n * internal handlers, not replaced.\n */\nfunction SliderInput({\n className,\n index = 0,\n unit,\n disabled,\n onFocus,\n onBlur,\n onKeyDown,\n ...props\n}: SliderInputProps) {\n const ctx = useSliderContext('SliderInput')\n const currentValue = ctx.values[index] ?? ctx.min\n const [draft, setDraft] = React.useState<string>(String(currentValue))\n const [editing, setEditing] = React.useState(false)\n // the thumb value when editing began — commit fires only if it moved off this\n const baselineRef = React.useRef(currentValue)\n\n // Keep the field in sync when the value changes elsewhere (drag/keyboard),\n // but never fight the user's in-progress typing.\n React.useEffect(() => {\n if (!editing) {\n setDraft(String(currentValue))\n }\n }, [currentValue, editing])\n\n const parseInput = (raw: string): number | null => {\n if (raw.trim() === '') {\n return null\n }\n\n const parsed = Number(raw)\n\n return Number.isNaN(parsed) ? null : parsed\n }\n\n const commit = () => {\n const parsed = parseInput(draft)\n\n if (parsed === null) {\n setDraft(String(currentValue)) // reset an empty/invalid entry\n return\n }\n\n ctx.setThumbValue(index, parsed, {\n commit: true,\n commitBaseline: baselineRef.current,\n })\n }\n\n return (\n <span\n data-slot=\"slider-input\"\n className={cn('inline-flex items-baseline gap-1', className)}\n >\n <input\n type=\"number\"\n inputMode=\"decimal\"\n min={ctx.min}\n max={ctx.max}\n step={ctx.step}\n disabled={disabled ?? ctx.disabled}\n value={editing ? draft : String(currentValue)}\n onChange={(event) => {\n const raw = event.target.value\n setDraft(raw)\n const parsed = parseInput(raw)\n\n // live-update the thumb only when the typed value is already in range,\n // so a partial entry (e.g. \"7\" en route to \"75\") doesn't snap the thumb\n // to the min mid-keystroke. Out-of-range values clamp on blur/Enter.\n if (parsed !== null && parsed >= ctx.min && parsed <= ctx.max) {\n ctx.setThumbValue(index, parsed)\n }\n }}\n onFocus={(event) => {\n baselineRef.current = ctx.values[index] ?? ctx.min\n setEditing(true)\n onFocus?.(event)\n }}\n onBlur={(event) => {\n setEditing(false)\n commit()\n onBlur?.(event)\n }}\n onKeyDown={(event) => {\n // Enter blurs, and the single onBlur commit runs — no double-commit.\n if (event.key === 'Enter') {\n event.currentTarget.blur()\n }\n\n onKeyDown?.(event)\n }}\n className={cn(\n 'w-16 bg-transparent tabular-nums outline-hidden',\n 'rounded-(--spark-rounded-sm) focus-visible:ring-2 focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n // hide the native spinners — the slider is the primary affordance\n '[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',\n )}\n {...props}\n />\n {unit ? (\n <span className=\"text-sm text-gray\" data-slot=\"slider-unit\">\n {unit}\n </span>\n ) : null}\n </span>\n )\n}\n\nSliderInput.displayName = 'SliderInput'\n\nexport {\n Slider,\n SliderControl,\n SliderIndicator,\n SliderInput,\n SliderThumb,\n SliderTicks,\n SliderTrack,\n SliderValue,\n}\nexport type { SliderInputProps, SliderProps, SliderVariant }\n"],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ Object.defineProperties(exports, {
3
+ __esModule: { value: true },
4
+ [Symbol.toStringTag]: { value: "Module" }
5
+ });
6
+ require("../utils/chars.cjs");
7
+ const require_utils_format = require("../utils/format.cjs");
8
+ const require_components_line_chart = require("./line-chart.cjs");
9
+ let react = require("react");
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ let cn = require("cn");
12
+ //#region src/components/solar-forecast-chart.tsx
13
+ const DEFAULT_HEIGHT = 220;
14
+ const BASE_MARGIN = {
15
+ top: 8,
16
+ right: 8,
17
+ bottom: 28,
18
+ left: 34
19
+ };
20
+ /** Extra headroom for the weather icon row pinned above the plot. */
21
+ const WEATHER_ROW_HEIGHT = 24;
22
+ function defaultFormatValue(value) {
23
+ return `${require_utils_format.oneDecimalFormat.format(value)} kW`;
24
+ }
25
+ function defaultFormatTime(date) {
26
+ return date.getHours().toString().padStart(2, "0");
27
+ }
28
+ /**
29
+ * Responsive line plot of measured production (solid) against a forecast
30
+ * (dashed) over a day. Optionally overlays hourly weather icons and
31
+ * sunrise/sunset markers, all pinned to the same time axis. Sizes itself to its
32
+ * container; compose a title and any summary around it.
33
+ */
34
+ function SolarForecastChart({ produced, forecast, producedLabel = "Produced", forecastLabel = "Forecast", formatValue = defaultFormatValue, yAxisUnit = "kW", formatTime = defaultFormatTime, formatTooltipTime, weather, sunrise, sunset, formatMarkerTime, curve = "monotone", height = DEFAULT_HEIGHT, className, zoomable, zoomResetLabel, zoomDomain, onZoomDomainChange }) {
35
+ const containerRef = (0, react.useRef)(null);
36
+ const [width, setWidth] = (0, react.useState)(0);
37
+ (0, react.useEffect)(() => {
38
+ const el = containerRef.current;
39
+ if (!el) return;
40
+ const ro = new ResizeObserver((entries) => {
41
+ const entry = entries[0];
42
+ if (entry) setWidth(entry.contentRect.width);
43
+ });
44
+ ro.observe(el);
45
+ return () => ro.disconnect();
46
+ }, []);
47
+ const hasWeather = weather != null && weather.length > 0;
48
+ const margin = {
49
+ ...BASE_MARGIN,
50
+ top: hasWeather ? BASE_MARGIN.top + WEATHER_ROW_HEIGHT : BASE_MARGIN.top
51
+ };
52
+ const formatMarker = formatMarkerTime ?? formatTooltipTime ?? formatTime;
53
+ const topContent = hasWeather ? weather.map((point, i) => {
54
+ const WeatherGlyph = point.icon;
55
+ return {
56
+ key: `weather-${point.date.valueOf()}-${i}`,
57
+ date: point.date,
58
+ content: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WeatherGlyph, { className: "text-gray-light [--spark-icon-size:18px]" })
59
+ };
60
+ }) : void 0;
61
+ const referenceLines = [sunrise && {
62
+ key: "sunrise",
63
+ date: sunrise,
64
+ label: formatMarker(sunrise)
65
+ }, sunset && {
66
+ key: "sunset",
67
+ date: sunset,
68
+ label: formatMarker(sunset)
69
+ }].filter((x) => Boolean(x));
70
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
71
+ ref: containerRef,
72
+ className: (0, cn.cn)("w-full", className),
73
+ children: width > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_line_chart.LineChart, {
74
+ width,
75
+ height,
76
+ margin,
77
+ curve,
78
+ series: [{
79
+ id: "produced",
80
+ data: produced,
81
+ label: producedLabel
82
+ }, {
83
+ id: "forecast",
84
+ data: forecast,
85
+ label: forecastLabel,
86
+ dashed: true
87
+ }],
88
+ showGrid: true,
89
+ xTickCount: 6,
90
+ yTickCount: 4,
91
+ yAxisUnit,
92
+ formatDate: formatTime,
93
+ formatTooltipDate: formatTooltipTime ?? formatTime,
94
+ formatValue,
95
+ referenceLines: referenceLines.length > 0 ? referenceLines : void 0,
96
+ topContent,
97
+ zoomable,
98
+ zoomResetLabel,
99
+ zoomDomain,
100
+ onZoomDomainChange
101
+ })
102
+ });
103
+ }
104
+ //#endregion
105
+ exports.SolarForecastChart = SolarForecastChart;
106
+ exports.default = SolarForecastChart;
107
+
108
+ //# sourceMappingURL=solar-forecast-chart.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solar-forecast-chart.cjs","names":["oneDecimalFormat","LineChart"],"sources":["../../src/components/solar-forecast-chart.tsx"],"sourcesContent":["import type { SparkIcon } from '@epilot/spark-icons'\nimport { type JSX, type ReactNode, useEffect, useRef, useState } from 'react'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { cn } from '../utils/cn'\nimport { oneDecimalFormat } from '../utils/format'\nimport {\n LineChart,\n type LineChartCurve,\n type LineChartDatum,\n} from './line-chart'\n\nconst DEFAULT_HEIGHT = 220\n// `left` leaves room for the y-axis tick labels + unit (matches the 34px gutter\n// used by BarChart / PowerHistoryChart).\nconst BASE_MARGIN = { top: 8, right: 8, bottom: 28, left: 34 }\n/** Extra headroom for the weather icon row pinned above the plot. */\nconst WEATHER_ROW_HEIGHT = 24\n\nfunction defaultFormatValue(value: number): string {\n return `${oneDecimalFormat.format(value)}${NARROW_NBSP}kW`\n}\n\nfunction defaultFormatTime(date: Date): string {\n return date.getHours().toString().padStart(2, '0')\n}\n\nexport interface SolarForecastWeatherPoint {\n date: Date\n icon: SparkIcon\n}\n\nexport interface SolarForecastChartProps {\n /** Measured production, drawn as the solid line. */\n produced: LineChartDatum[]\n /** Predicted production, drawn as the dashed line. */\n forecast: LineChartDatum[]\n /** Tooltip label for the produced series. */\n producedLabel?: ReactNode\n /** Tooltip label for the forecast series. */\n forecastLabel?: ReactNode\n /** Tooltip value formatter. Defaults to one-decimal kW (power). */\n formatValue?: (value: number) => string\n /** Unit shown once atop the y-axis; ticks render as plain numbers. Defaults to \"kW\". */\n yAxisUnit?: string\n /** X-axis tick formatter. Defaults to the zero-padded hour. */\n formatTime?: (date: Date) => string\n /** Tooltip time formatter. Defaults to `formatTime`. */\n formatTooltipTime?: (date: Date) => string\n /**\n * Hourly weather icons drawn along the top of the plot, aligned to the time\n * axis. Their dates must fall within the plotted day.\n */\n weather?: SolarForecastWeatherPoint[]\n /** Sunrise time, drawn as a labelled vertical marker on the plot. */\n sunrise?: Date\n /** Sunset time, drawn as a labelled vertical marker on the plot. */\n sunset?: Date\n /** Label formatter for the sunrise/sunset markers. Defaults to `formatTime`. */\n formatMarkerTime?: (date: Date) => string\n curve?: LineChartCurve\n height?: number\n className?: string\n zoomable?: boolean\n zoomResetLabel?: string\n zoomDomain?: [Date, Date] | null\n onZoomDomainChange?: (domain: [Date, Date] | null) => void\n}\n\n/**\n * Responsive line plot of measured production (solid) against a forecast\n * (dashed) over a day. Optionally overlays hourly weather icons and\n * sunrise/sunset markers, all pinned to the same time axis. Sizes itself to its\n * container; compose a title and any summary around it.\n */\nexport function SolarForecastChart({\n produced,\n forecast,\n producedLabel = 'Produced',\n forecastLabel = 'Forecast',\n formatValue = defaultFormatValue,\n yAxisUnit = 'kW',\n formatTime = defaultFormatTime,\n formatTooltipTime,\n weather,\n sunrise,\n sunset,\n formatMarkerTime,\n curve = 'monotone',\n height = DEFAULT_HEIGHT,\n className,\n zoomable,\n zoomResetLabel,\n zoomDomain,\n onZoomDomainChange,\n}: SolarForecastChartProps): JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n\n useEffect(() => {\n const el = containerRef.current\n if (!el) return\n const ro = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (entry) setWidth(entry.contentRect.width)\n })\n ro.observe(el)\n return () => ro.disconnect()\n }, [])\n\n const hasWeather = weather != null && weather.length > 0\n const margin = {\n ...BASE_MARGIN,\n top: hasWeather ? BASE_MARGIN.top + WEATHER_ROW_HEIGHT : BASE_MARGIN.top,\n }\n const formatMarker = formatMarkerTime ?? formatTooltipTime ?? formatTime\n\n const topContent = hasWeather\n ? weather.map((point, i) => {\n const WeatherGlyph = point.icon\n return {\n key: `weather-${point.date.valueOf()}-${i}`,\n date: point.date,\n content: (\n <WeatherGlyph className=\"text-gray-light [--spark-icon-size:18px]\" />\n ),\n }\n })\n : undefined\n\n const referenceLines = [\n sunrise && { key: 'sunrise', date: sunrise, label: formatMarker(sunrise) },\n sunset && { key: 'sunset', date: sunset, label: formatMarker(sunset) },\n ].filter((x): x is { key: string; date: Date; label: string } => Boolean(x))\n\n return (\n <div ref={containerRef} className={cn('w-full', className)}>\n {width > 0 && (\n <LineChart\n width={width}\n height={height}\n margin={margin}\n curve={curve}\n series={[\n { id: 'produced', data: produced, label: producedLabel },\n {\n id: 'forecast',\n data: forecast,\n label: forecastLabel,\n dashed: true,\n },\n ]}\n showGrid\n xTickCount={6}\n yTickCount={4}\n yAxisUnit={yAxisUnit}\n formatDate={formatTime}\n formatTooltipDate={formatTooltipTime ?? formatTime}\n formatValue={formatValue}\n referenceLines={\n referenceLines.length > 0 ? referenceLines : undefined\n }\n topContent={topContent}\n zoomable={zoomable}\n zoomResetLabel={zoomResetLabel}\n zoomDomain={zoomDomain}\n onZoomDomainChange={onZoomDomainChange}\n />\n )}\n </div>\n )\n}\n\nexport default SolarForecastChart\n"],"mappings":";;;;;;;;;;;;AAWA,MAAM,iBAAiB;AAGvB,MAAM,cAAc;CAAE,KAAK;CAAG,OAAO;CAAG,QAAQ;CAAI,MAAM;AAAG;;AAE7D,MAAM,qBAAqB;AAE3B,SAAS,mBAAmB,OAAuB;CACjD,OAAO,GAAGA,qBAAAA,iBAAiB,OAAO,KAAK;AACzC;AAEA,SAAS,kBAAkB,MAAoB;CAC7C,OAAO,KAAK,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACnD;;;;;;;AAkDA,SAAgB,mBAAmB,EACjC,UACA,UACA,gBAAgB,YAChB,gBAAgB,YAChB,cAAc,oBACd,YAAY,MACZ,aAAa,mBACb,mBACA,SACA,SACA,QACA,kBACA,QAAQ,YACR,SAAS,gBACT,WACA,UACA,gBACA,YACA,sBACuC;CACvC,MAAM,gBAAA,GAAA,MAAA,QAAsC,IAAI;CAChD,MAAM,CAAC,OAAO,aAAA,GAAA,MAAA,UAAqB,CAAC;CAEpC,CAAA,GAAA,MAAA,iBAAgB;EACd,MAAM,KAAK,aAAa;EACxB,IAAI,CAAC,IAAI;EACT,MAAM,KAAK,IAAI,gBAAgB,YAAY;GACzC,MAAM,QAAQ,QAAQ;GACtB,IAAI,OAAO,SAAS,MAAM,YAAY,KAAK;EAC7C,CAAC;EACD,GAAG,QAAQ,EAAE;EACb,aAAa,GAAG,WAAW;CAC7B,GAAG,CAAC,CAAC;CAEL,MAAM,aAAa,WAAW,QAAQ,QAAQ,SAAS;CACvD,MAAM,SAAS;EACb,GAAG;EACH,KAAK,aAAa,YAAY,MAAM,qBAAqB,YAAY;CACvE;CACA,MAAM,eAAe,oBAAoB,qBAAqB;CAE9D,MAAM,aAAa,aACf,QAAQ,KAAK,OAAO,MAAM;EACxB,MAAM,eAAe,MAAM;EAC3B,OAAO;GACL,KAAK,WAAW,MAAM,KAAK,QAAQ,EAAE,GAAG;GACxC,MAAM,MAAM;GACZ,SACE,iBAAA,GAAA,kBAAA,KAAC,cAAD,EAAc,WAAU,2CAA4C,CAAA;EAExE;CACF,CAAC,IACD,KAAA;CAEJ,MAAM,iBAAiB,CACrB,WAAW;EAAE,KAAK;EAAW,MAAM;EAAS,OAAO,aAAa,OAAO;CAAE,GACzE,UAAU;EAAE,KAAK;EAAU,MAAM;EAAQ,OAAO,aAAa,MAAM;CAAE,CACvE,EAAE,QAAQ,MAAuD,QAAQ,CAAC,CAAC;CAE3E,OACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EAAK,KAAK;EAAc,YAAA,GAAA,GAAA,IAAc,UAAU,SAAS;YACtD,QAAQ,KACP,iBAAA,GAAA,kBAAA,KAACC,8BAAAA,WAAD;GACS;GACC;GACA;GACD;GACP,QAAQ,CACN;IAAE,IAAI;IAAY,MAAM;IAAU,OAAO;GAAc,GACvD;IACE,IAAI;IACJ,MAAM;IACN,OAAO;IACP,QAAQ;GACV,CACF;GACA,UAAA;GACA,YAAY;GACZ,YAAY;GACD;GACX,YAAY;GACZ,mBAAmB,qBAAqB;GAC3B;GACb,gBACE,eAAe,SAAS,IAAI,iBAAiB,KAAA;GAEnC;GACF;GACM;GACJ;GACQ;EACrB,CAAA;CAEA,CAAA;AAET"}
@@ -0,0 +1,77 @@
1
+ "use client";
2
+
3
+ import { LineChartCurve, LineChartDatum } from "./line-chart.cjs";
4
+ import { JSX, ReactNode } from "react";
5
+ import { SparkIcon } from "@epilot/spark-icons";
6
+
7
+ //#region src/components/solar-forecast-chart.d.ts
8
+ interface SolarForecastWeatherPoint {
9
+ date: Date;
10
+ icon: SparkIcon;
11
+ }
12
+ interface SolarForecastChartProps {
13
+ /** Measured production, drawn as the solid line. */
14
+ produced: LineChartDatum[];
15
+ /** Predicted production, drawn as the dashed line. */
16
+ forecast: LineChartDatum[];
17
+ /** Tooltip label for the produced series. */
18
+ producedLabel?: ReactNode;
19
+ /** Tooltip label for the forecast series. */
20
+ forecastLabel?: ReactNode;
21
+ /** Tooltip value formatter. Defaults to one-decimal kW (power). */
22
+ formatValue?: (value: number) => string;
23
+ /** Unit shown once atop the y-axis; ticks render as plain numbers. Defaults to "kW". */
24
+ yAxisUnit?: string;
25
+ /** X-axis tick formatter. Defaults to the zero-padded hour. */
26
+ formatTime?: (date: Date) => string;
27
+ /** Tooltip time formatter. Defaults to `formatTime`. */
28
+ formatTooltipTime?: (date: Date) => string;
29
+ /**
30
+ * Hourly weather icons drawn along the top of the plot, aligned to the time
31
+ * axis. Their dates must fall within the plotted day.
32
+ */
33
+ weather?: SolarForecastWeatherPoint[];
34
+ /** Sunrise time, drawn as a labelled vertical marker on the plot. */
35
+ sunrise?: Date;
36
+ /** Sunset time, drawn as a labelled vertical marker on the plot. */
37
+ sunset?: Date;
38
+ /** Label formatter for the sunrise/sunset markers. Defaults to `formatTime`. */
39
+ formatMarkerTime?: (date: Date) => string;
40
+ curve?: LineChartCurve;
41
+ height?: number;
42
+ className?: string;
43
+ zoomable?: boolean;
44
+ zoomResetLabel?: string;
45
+ zoomDomain?: [Date, Date] | null;
46
+ onZoomDomainChange?: (domain: [Date, Date] | null) => void;
47
+ }
48
+ /**
49
+ * Responsive line plot of measured production (solid) against a forecast
50
+ * (dashed) over a day. Optionally overlays hourly weather icons and
51
+ * sunrise/sunset markers, all pinned to the same time axis. Sizes itself to its
52
+ * container; compose a title and any summary around it.
53
+ */
54
+ declare function SolarForecastChart({
55
+ produced,
56
+ forecast,
57
+ producedLabel,
58
+ forecastLabel,
59
+ formatValue,
60
+ yAxisUnit,
61
+ formatTime,
62
+ formatTooltipTime,
63
+ weather,
64
+ sunrise,
65
+ sunset,
66
+ formatMarkerTime,
67
+ curve,
68
+ height,
69
+ className,
70
+ zoomable,
71
+ zoomResetLabel,
72
+ zoomDomain,
73
+ onZoomDomainChange
74
+ }: SolarForecastChartProps): JSX.Element;
75
+ //#endregion
76
+ export { SolarForecastChart, SolarForecastChart as default, SolarForecastChartProps, SolarForecastWeatherPoint };
77
+ //# sourceMappingURL=solar-forecast-chart.d.cts.map
@@ -0,0 +1,77 @@
1
+ "use client";
2
+
3
+ import { LineChartCurve, LineChartDatum } from "./line-chart.js";
4
+ import { JSX, ReactNode } from "react";
5
+ import { SparkIcon } from "@epilot/spark-icons";
6
+
7
+ //#region src/components/solar-forecast-chart.d.ts
8
+ interface SolarForecastWeatherPoint {
9
+ date: Date;
10
+ icon: SparkIcon;
11
+ }
12
+ interface SolarForecastChartProps {
13
+ /** Measured production, drawn as the solid line. */
14
+ produced: LineChartDatum[];
15
+ /** Predicted production, drawn as the dashed line. */
16
+ forecast: LineChartDatum[];
17
+ /** Tooltip label for the produced series. */
18
+ producedLabel?: ReactNode;
19
+ /** Tooltip label for the forecast series. */
20
+ forecastLabel?: ReactNode;
21
+ /** Tooltip value formatter. Defaults to one-decimal kW (power). */
22
+ formatValue?: (value: number) => string;
23
+ /** Unit shown once atop the y-axis; ticks render as plain numbers. Defaults to "kW". */
24
+ yAxisUnit?: string;
25
+ /** X-axis tick formatter. Defaults to the zero-padded hour. */
26
+ formatTime?: (date: Date) => string;
27
+ /** Tooltip time formatter. Defaults to `formatTime`. */
28
+ formatTooltipTime?: (date: Date) => string;
29
+ /**
30
+ * Hourly weather icons drawn along the top of the plot, aligned to the time
31
+ * axis. Their dates must fall within the plotted day.
32
+ */
33
+ weather?: SolarForecastWeatherPoint[];
34
+ /** Sunrise time, drawn as a labelled vertical marker on the plot. */
35
+ sunrise?: Date;
36
+ /** Sunset time, drawn as a labelled vertical marker on the plot. */
37
+ sunset?: Date;
38
+ /** Label formatter for the sunrise/sunset markers. Defaults to `formatTime`. */
39
+ formatMarkerTime?: (date: Date) => string;
40
+ curve?: LineChartCurve;
41
+ height?: number;
42
+ className?: string;
43
+ zoomable?: boolean;
44
+ zoomResetLabel?: string;
45
+ zoomDomain?: [Date, Date] | null;
46
+ onZoomDomainChange?: (domain: [Date, Date] | null) => void;
47
+ }
48
+ /**
49
+ * Responsive line plot of measured production (solid) against a forecast
50
+ * (dashed) over a day. Optionally overlays hourly weather icons and
51
+ * sunrise/sunset markers, all pinned to the same time axis. Sizes itself to its
52
+ * container; compose a title and any summary around it.
53
+ */
54
+ declare function SolarForecastChart({
55
+ produced,
56
+ forecast,
57
+ producedLabel,
58
+ forecastLabel,
59
+ formatValue,
60
+ yAxisUnit,
61
+ formatTime,
62
+ formatTooltipTime,
63
+ weather,
64
+ sunrise,
65
+ sunset,
66
+ formatMarkerTime,
67
+ curve,
68
+ height,
69
+ className,
70
+ zoomable,
71
+ zoomResetLabel,
72
+ zoomDomain,
73
+ onZoomDomainChange
74
+ }: SolarForecastChartProps): JSX.Element;
75
+ //#endregion
76
+ export { SolarForecastChart, SolarForecastChart as default, SolarForecastChartProps, SolarForecastWeatherPoint };
77
+ //# sourceMappingURL=solar-forecast-chart.d.ts.map
@@ -0,0 +1,103 @@
1
+ "use client";
2
+ import { cn } from "../utils/cn.js";
3
+ import "../utils/chars.js";
4
+ import { oneDecimalFormat } from "../utils/format.js";
5
+ import { LineChart } from "./line-chart.js";
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ //#region src/components/solar-forecast-chart.tsx
9
+ const DEFAULT_HEIGHT = 220;
10
+ const BASE_MARGIN = {
11
+ top: 8,
12
+ right: 8,
13
+ bottom: 28,
14
+ left: 34
15
+ };
16
+ /** Extra headroom for the weather icon row pinned above the plot. */
17
+ const WEATHER_ROW_HEIGHT = 24;
18
+ function defaultFormatValue(value) {
19
+ return `${oneDecimalFormat.format(value)} kW`;
20
+ }
21
+ function defaultFormatTime(date) {
22
+ return date.getHours().toString().padStart(2, "0");
23
+ }
24
+ /**
25
+ * Responsive line plot of measured production (solid) against a forecast
26
+ * (dashed) over a day. Optionally overlays hourly weather icons and
27
+ * sunrise/sunset markers, all pinned to the same time axis. Sizes itself to its
28
+ * container; compose a title and any summary around it.
29
+ */
30
+ function SolarForecastChart({ produced, forecast, producedLabel = "Produced", forecastLabel = "Forecast", formatValue = defaultFormatValue, yAxisUnit = "kW", formatTime = defaultFormatTime, formatTooltipTime, weather, sunrise, sunset, formatMarkerTime, curve = "monotone", height = DEFAULT_HEIGHT, className, zoomable, zoomResetLabel, zoomDomain, onZoomDomainChange }) {
31
+ const containerRef = useRef(null);
32
+ const [width, setWidth] = useState(0);
33
+ useEffect(() => {
34
+ const el = containerRef.current;
35
+ if (!el) return;
36
+ const ro = new ResizeObserver((entries) => {
37
+ const entry = entries[0];
38
+ if (entry) setWidth(entry.contentRect.width);
39
+ });
40
+ ro.observe(el);
41
+ return () => ro.disconnect();
42
+ }, []);
43
+ const hasWeather = weather != null && weather.length > 0;
44
+ const margin = {
45
+ ...BASE_MARGIN,
46
+ top: hasWeather ? BASE_MARGIN.top + WEATHER_ROW_HEIGHT : BASE_MARGIN.top
47
+ };
48
+ const formatMarker = formatMarkerTime ?? formatTooltipTime ?? formatTime;
49
+ const topContent = hasWeather ? weather.map((point, i) => {
50
+ const WeatherGlyph = point.icon;
51
+ return {
52
+ key: `weather-${point.date.valueOf()}-${i}`,
53
+ date: point.date,
54
+ content: /* @__PURE__ */ jsx(WeatherGlyph, { className: "text-gray-light [--spark-icon-size:18px]" })
55
+ };
56
+ }) : void 0;
57
+ const referenceLines = [sunrise && {
58
+ key: "sunrise",
59
+ date: sunrise,
60
+ label: formatMarker(sunrise)
61
+ }, sunset && {
62
+ key: "sunset",
63
+ date: sunset,
64
+ label: formatMarker(sunset)
65
+ }].filter((x) => Boolean(x));
66
+ return /* @__PURE__ */ jsx("div", {
67
+ ref: containerRef,
68
+ className: cn("w-full", className),
69
+ children: width > 0 && /* @__PURE__ */ jsx(LineChart, {
70
+ width,
71
+ height,
72
+ margin,
73
+ curve,
74
+ series: [{
75
+ id: "produced",
76
+ data: produced,
77
+ label: producedLabel
78
+ }, {
79
+ id: "forecast",
80
+ data: forecast,
81
+ label: forecastLabel,
82
+ dashed: true
83
+ }],
84
+ showGrid: true,
85
+ xTickCount: 6,
86
+ yTickCount: 4,
87
+ yAxisUnit,
88
+ formatDate: formatTime,
89
+ formatTooltipDate: formatTooltipTime ?? formatTime,
90
+ formatValue,
91
+ referenceLines: referenceLines.length > 0 ? referenceLines : void 0,
92
+ topContent,
93
+ zoomable,
94
+ zoomResetLabel,
95
+ zoomDomain,
96
+ onZoomDomainChange
97
+ })
98
+ });
99
+ }
100
+ //#endregion
101
+ export { SolarForecastChart, SolarForecastChart as default };
102
+
103
+ //# sourceMappingURL=solar-forecast-chart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"solar-forecast-chart.js","names":[],"sources":["../../src/components/solar-forecast-chart.tsx"],"sourcesContent":["import type { SparkIcon } from '@epilot/spark-icons'\nimport { type JSX, type ReactNode, useEffect, useRef, useState } from 'react'\nimport { NARROW_NBSP } from '../utils/chars'\nimport { cn } from '../utils/cn'\nimport { oneDecimalFormat } from '../utils/format'\nimport {\n LineChart,\n type LineChartCurve,\n type LineChartDatum,\n} from './line-chart'\n\nconst DEFAULT_HEIGHT = 220\n// `left` leaves room for the y-axis tick labels + unit (matches the 34px gutter\n// used by BarChart / PowerHistoryChart).\nconst BASE_MARGIN = { top: 8, right: 8, bottom: 28, left: 34 }\n/** Extra headroom for the weather icon row pinned above the plot. */\nconst WEATHER_ROW_HEIGHT = 24\n\nfunction defaultFormatValue(value: number): string {\n return `${oneDecimalFormat.format(value)}${NARROW_NBSP}kW`\n}\n\nfunction defaultFormatTime(date: Date): string {\n return date.getHours().toString().padStart(2, '0')\n}\n\nexport interface SolarForecastWeatherPoint {\n date: Date\n icon: SparkIcon\n}\n\nexport interface SolarForecastChartProps {\n /** Measured production, drawn as the solid line. */\n produced: LineChartDatum[]\n /** Predicted production, drawn as the dashed line. */\n forecast: LineChartDatum[]\n /** Tooltip label for the produced series. */\n producedLabel?: ReactNode\n /** Tooltip label for the forecast series. */\n forecastLabel?: ReactNode\n /** Tooltip value formatter. Defaults to one-decimal kW (power). */\n formatValue?: (value: number) => string\n /** Unit shown once atop the y-axis; ticks render as plain numbers. Defaults to \"kW\". */\n yAxisUnit?: string\n /** X-axis tick formatter. Defaults to the zero-padded hour. */\n formatTime?: (date: Date) => string\n /** Tooltip time formatter. Defaults to `formatTime`. */\n formatTooltipTime?: (date: Date) => string\n /**\n * Hourly weather icons drawn along the top of the plot, aligned to the time\n * axis. Their dates must fall within the plotted day.\n */\n weather?: SolarForecastWeatherPoint[]\n /** Sunrise time, drawn as a labelled vertical marker on the plot. */\n sunrise?: Date\n /** Sunset time, drawn as a labelled vertical marker on the plot. */\n sunset?: Date\n /** Label formatter for the sunrise/sunset markers. Defaults to `formatTime`. */\n formatMarkerTime?: (date: Date) => string\n curve?: LineChartCurve\n height?: number\n className?: string\n zoomable?: boolean\n zoomResetLabel?: string\n zoomDomain?: [Date, Date] | null\n onZoomDomainChange?: (domain: [Date, Date] | null) => void\n}\n\n/**\n * Responsive line plot of measured production (solid) against a forecast\n * (dashed) over a day. Optionally overlays hourly weather icons and\n * sunrise/sunset markers, all pinned to the same time axis. Sizes itself to its\n * container; compose a title and any summary around it.\n */\nexport function SolarForecastChart({\n produced,\n forecast,\n producedLabel = 'Produced',\n forecastLabel = 'Forecast',\n formatValue = defaultFormatValue,\n yAxisUnit = 'kW',\n formatTime = defaultFormatTime,\n formatTooltipTime,\n weather,\n sunrise,\n sunset,\n formatMarkerTime,\n curve = 'monotone',\n height = DEFAULT_HEIGHT,\n className,\n zoomable,\n zoomResetLabel,\n zoomDomain,\n onZoomDomainChange,\n}: SolarForecastChartProps): JSX.Element {\n const containerRef = useRef<HTMLDivElement>(null)\n const [width, setWidth] = useState(0)\n\n useEffect(() => {\n const el = containerRef.current\n if (!el) return\n const ro = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (entry) setWidth(entry.contentRect.width)\n })\n ro.observe(el)\n return () => ro.disconnect()\n }, [])\n\n const hasWeather = weather != null && weather.length > 0\n const margin = {\n ...BASE_MARGIN,\n top: hasWeather ? BASE_MARGIN.top + WEATHER_ROW_HEIGHT : BASE_MARGIN.top,\n }\n const formatMarker = formatMarkerTime ?? formatTooltipTime ?? formatTime\n\n const topContent = hasWeather\n ? weather.map((point, i) => {\n const WeatherGlyph = point.icon\n return {\n key: `weather-${point.date.valueOf()}-${i}`,\n date: point.date,\n content: (\n <WeatherGlyph className=\"text-gray-light [--spark-icon-size:18px]\" />\n ),\n }\n })\n : undefined\n\n const referenceLines = [\n sunrise && { key: 'sunrise', date: sunrise, label: formatMarker(sunrise) },\n sunset && { key: 'sunset', date: sunset, label: formatMarker(sunset) },\n ].filter((x): x is { key: string; date: Date; label: string } => Boolean(x))\n\n return (\n <div ref={containerRef} className={cn('w-full', className)}>\n {width > 0 && (\n <LineChart\n width={width}\n height={height}\n margin={margin}\n curve={curve}\n series={[\n { id: 'produced', data: produced, label: producedLabel },\n {\n id: 'forecast',\n data: forecast,\n label: forecastLabel,\n dashed: true,\n },\n ]}\n showGrid\n xTickCount={6}\n yTickCount={4}\n yAxisUnit={yAxisUnit}\n formatDate={formatTime}\n formatTooltipDate={formatTooltipTime ?? formatTime}\n formatValue={formatValue}\n referenceLines={\n referenceLines.length > 0 ? referenceLines : undefined\n }\n topContent={topContent}\n zoomable={zoomable}\n zoomResetLabel={zoomResetLabel}\n zoomDomain={zoomDomain}\n onZoomDomainChange={onZoomDomainChange}\n />\n )}\n </div>\n )\n}\n\nexport default SolarForecastChart\n"],"mappings":";;;;;;;;AAWA,MAAM,iBAAiB;AAGvB,MAAM,cAAc;CAAE,KAAK;CAAG,OAAO;CAAG,QAAQ;CAAI,MAAM;AAAG;;AAE7D,MAAM,qBAAqB;AAE3B,SAAS,mBAAmB,OAAuB;CACjD,OAAO,GAAG,iBAAiB,OAAO,KAAK;AACzC;AAEA,SAAS,kBAAkB,MAAoB;CAC7C,OAAO,KAAK,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACnD;;;;;;;AAkDA,SAAgB,mBAAmB,EACjC,UACA,UACA,gBAAgB,YAChB,gBAAgB,YAChB,cAAc,oBACd,YAAY,MACZ,aAAa,mBACb,mBACA,SACA,SACA,QACA,kBACA,QAAQ,YACR,SAAS,gBACT,WACA,UACA,gBACA,YACA,sBACuC;CACvC,MAAM,eAAe,OAAuB,IAAI;CAChD,MAAM,CAAC,OAAO,YAAY,SAAS,CAAC;CAEpC,gBAAgB;EACd,MAAM,KAAK,aAAa;EACxB,IAAI,CAAC,IAAI;EACT,MAAM,KAAK,IAAI,gBAAgB,YAAY;GACzC,MAAM,QAAQ,QAAQ;GACtB,IAAI,OAAO,SAAS,MAAM,YAAY,KAAK;EAC7C,CAAC;EACD,GAAG,QAAQ,EAAE;EACb,aAAa,GAAG,WAAW;CAC7B,GAAG,CAAC,CAAC;CAEL,MAAM,aAAa,WAAW,QAAQ,QAAQ,SAAS;CACvD,MAAM,SAAS;EACb,GAAG;EACH,KAAK,aAAa,YAAY,MAAM,qBAAqB,YAAY;CACvE;CACA,MAAM,eAAe,oBAAoB,qBAAqB;CAE9D,MAAM,aAAa,aACf,QAAQ,KAAK,OAAO,MAAM;EACxB,MAAM,eAAe,MAAM;EAC3B,OAAO;GACL,KAAK,WAAW,MAAM,KAAK,QAAQ,EAAE,GAAG;GACxC,MAAM,MAAM;GACZ,SACE,oBAAC,cAAD,EAAc,WAAU,2CAA4C,CAAA;EAExE;CACF,CAAC,IACD,KAAA;CAEJ,MAAM,iBAAiB,CACrB,WAAW;EAAE,KAAK;EAAW,MAAM;EAAS,OAAO,aAAa,OAAO;CAAE,GACzE,UAAU;EAAE,KAAK;EAAU,MAAM;EAAQ,OAAO,aAAa,MAAM;CAAE,CACvE,EAAE,QAAQ,MAAuD,QAAQ,CAAC,CAAC;CAE3E,OACE,oBAAC,OAAD;EAAK,KAAK;EAAc,WAAW,GAAG,UAAU,SAAS;YACtD,QAAQ,KACP,oBAAC,WAAD;GACS;GACC;GACA;GACD;GACP,QAAQ,CACN;IAAE,IAAI;IAAY,MAAM;IAAU,OAAO;GAAc,GACvD;IACE,IAAI;IACJ,MAAM;IACN,OAAO;IACP,QAAQ;GACV,CACF;GACA,UAAA;GACA,YAAY;GACZ,YAAY;GACD;GACX,YAAY;GACZ,mBAAmB,qBAAqB;GAC3B;GACb,gBACE,eAAe,SAAS,IAAI,iBAAiB,KAAA;GAEnC;GACF;GACM;GACJ;GACQ;EACrB,CAAA;CAEA,CAAA;AAET"}
@@ -5,7 +5,7 @@ let _dnd_kit_core = require("@dnd-kit/core");
5
5
  let _dnd_kit_modifiers = require("@dnd-kit/modifiers");
6
6
  let _dnd_kit_sortable = require("@dnd-kit/sortable");
7
7
  let _dnd_kit_utilities = require("@dnd-kit/utilities");
8
- let cnfast = require("cnfast");
8
+ let cn = require("cn");
9
9
  //#region src/components/sortable-list.tsx
10
10
  /**
11
11
  * Vertical drag-and-drop sortable list built on @dnd-kit. Pointer, touch and
@@ -36,7 +36,7 @@ function SortableList({ items, getItemId, onReorder, renderItem, className }) {
36
36
  items: ids,
37
37
  strategy: _dnd_kit_sortable.verticalListSortingStrategy,
38
38
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
39
- className: (0, cnfast.cn)("flex flex-col gap-[calc(var(--spark-layout-gap)*2)]", className),
39
+ className: (0, cn.cn)("flex flex-col gap-[calc(var(--spark-layout-gap)*2)]", className),
40
40
  children: items.map((item, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SortableRow, {
41
41
  id: getItemId(item),
42
42
  children: (handle) => renderItem(item, handle, index)
@@ -53,7 +53,7 @@ function SortableRow({ id, children }) {
53
53
  transform: _dnd_kit_utilities.CSS.Transform.toString(transform),
54
54
  transition
55
55
  },
56
- className: (0, cnfast.cn)(isDragging && "relative z-10"),
56
+ className: (0, cn.cn)(isDragging && "relative z-10"),
57
57
  children: children({
58
58
  ref: setActivatorNodeRef,
59
59
  attributes,
@@ -1 +1 @@
1
- {"version":3,"file":"sortable-list.cjs","names":["PointerSensor","TouchSensor","KeyboardSensor","sortableKeyboardCoordinates","DndContext","closestCenter","restrictToVerticalAxis","restrictToParentElement","SortableContext","verticalListSortingStrategy","CSS"],"sources":["../../src/components/sortable-list.tsx"],"sourcesContent":["import {\n DndContext,\n type DragEndEvent,\n KeyboardSensor,\n PointerSensor,\n TouchSensor,\n closestCenter,\n useSensor,\n useSensors,\n} from '@dnd-kit/core'\nimport {\n restrictToParentElement,\n restrictToVerticalAxis,\n} from '@dnd-kit/modifiers'\nimport {\n SortableContext,\n arrayMove,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport type { ReactNode } from 'react'\nimport { cn } from '../utils/cn'\n\ntype SortableHookResult = ReturnType<typeof useSortable>\n\n/**\n * Props to spread onto the element that should start a drag (the handle).\n * Attach `ref`, `attributes`, and `listeners` to a focusable element such as a\n * button to make it the drag handle; `isDragging` reflects the row's state.\n */\nexport interface SortableDragHandle {\n ref: SortableHookResult['setActivatorNodeRef']\n attributes: SortableHookResult['attributes']\n listeners: SortableHookResult['listeners']\n isDragging: boolean\n}\n\nexport interface SortableListProps<T> {\n items: T[]\n /** Stable unique id for an item. */\n getItemId: (item: T) => string\n /** Called with the reordered items after a drag completes. */\n onReorder: (items: T[]) => void\n /** Render a single row. Spread `handle` onto the drag-handle element. */\n renderItem: (item: T, handle: SortableDragHandle, index: number) => ReactNode\n /** Classes for the list container (defaults to a vertical flex with gap). */\n className?: string\n}\n\n/**\n * Vertical drag-and-drop sortable list built on @dnd-kit. Pointer, touch and\n * keyboard sensors are wired up; dragging is constrained to the vertical axis\n * within the list. The consumer owns row markup and decides which element is\n * the handle via the `handle` argument of `renderItem`.\n */\nexport function SortableList<T>({\n items,\n getItemId,\n onReorder,\n renderItem,\n className,\n}: SortableListProps<T>) {\n const ids = items.map(getItemId)\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n useSensor(TouchSensor, {\n activationConstraint: { delay: 150, tolerance: 5 },\n }),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n }),\n )\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event\n if (!over || active.id === over.id) return\n const oldIndex = ids.indexOf(String(active.id))\n const newIndex = ids.indexOf(String(over.id))\n if (oldIndex < 0 || newIndex < 0) return\n onReorder(arrayMove(items, oldIndex, newIndex))\n }\n\n return (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n modifiers={[restrictToVerticalAxis, restrictToParentElement]}\n onDragEnd={handleDragEnd}\n >\n <SortableContext items={ids} strategy={verticalListSortingStrategy}>\n <div\n className={cn(\n 'flex flex-col gap-[calc(var(--spark-layout-gap)*2)]',\n className,\n )}\n >\n {items.map((item, index) => (\n <SortableRow key={getItemId(item)} id={getItemId(item)}>\n {(handle) => renderItem(item, handle, index)}\n </SortableRow>\n ))}\n </div>\n </SortableContext>\n </DndContext>\n )\n}\n\ninterface SortableRowProps {\n id: string\n children: (handle: SortableDragHandle) => ReactNode\n}\n\nfunction SortableRow({ id, children }: SortableRowProps) {\n const {\n attributes,\n listeners,\n setNodeRef,\n setActivatorNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id })\n\n const style = {\n transform: CSS.Transform.toString(transform),\n transition,\n }\n\n return (\n <div\n ref={setNodeRef}\n style={style}\n className={cn(isDragging && 'relative z-10')}\n >\n {children({\n ref: setActivatorNodeRef,\n attributes,\n listeners,\n isDragging,\n })}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyDA,SAAgB,aAAgB,EAC9B,OACA,WACA,WACA,YACA,aACuB;CACvB,MAAM,MAAM,MAAM,IAAI,SAAS;CAE/B,MAAM,WAAA,GAAA,cAAA,aAAA,GAAA,cAAA,WACMA,cAAAA,eAAe,EAAE,sBAAsB,EAAE,UAAU,EAAE,EAAE,CAAC,IAAA,GAAA,cAAA,WACxDC,cAAAA,aAAa,EACrB,sBAAsB;EAAE,OAAO;EAAK,WAAW;CAAE,EACnD,CAAC,IAAA,GAAA,cAAA,WACSC,cAAAA,gBAAgB,EACxB,kBAAkBC,kBAAAA,4BACpB,CAAC,CACH;CAEA,MAAM,iBAAiB,UAAwB;EAC7C,MAAM,EAAE,QAAQ,SAAS;EACzB,IAAI,CAAC,QAAQ,OAAO,OAAO,KAAK,IAAI;EACpC,MAAM,WAAW,IAAI,QAAQ,OAAO,OAAO,EAAE,CAAC;EAC9C,MAAM,WAAW,IAAI,QAAQ,OAAO,KAAK,EAAE,CAAC;EAC5C,IAAI,WAAW,KAAK,WAAW,GAAG;EAClC,WAAA,GAAA,kBAAA,WAAoB,OAAO,UAAU,QAAQ,CAAC;CAChD;CAEA,OACE,iBAAA,GAAA,kBAAA,KAACC,cAAAA,YAAD;EACW;EACT,oBAAoBC,cAAAA;EACpB,WAAW,CAACC,mBAAAA,wBAAwBC,mBAAAA,uBAAuB;EAC3D,WAAW;YAEX,iBAAA,GAAA,kBAAA,KAACC,kBAAAA,iBAAD;GAAiB,OAAO;GAAK,UAAUC,kBAAAA;aACrC,iBAAA,GAAA,kBAAA,KAAC,OAAD;IACE,YAAA,GAAA,OAAA,IACE,uDACA,SACF;cAEC,MAAM,KAAK,MAAM,UAChB,iBAAA,GAAA,kBAAA,KAAC,aAAD;KAAmC,IAAI,UAAU,IAAI;gBACjD,WAAW,WAAW,MAAM,QAAQ,KAAK;IAChC,GAFK,UAAU,IAAI,CAEnB,CACd;GACE,CAAA;EACU,CAAA;CACP,CAAA;AAEhB;AAOA,SAAS,YAAY,EAAE,IAAI,YAA8B;CACvD,MAAM,EACJ,YACA,WACA,YACA,qBACA,WACA,YACA,gBAAA,GAAA,kBAAA,aACc,EAAE,GAAG,CAAC;CAOtB,OACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EACE,KAAK;EACL,OAAO;GAPT,WAAWC,mBAAAA,IAAI,UAAU,SAAS,SAAS;GAC3C;EAMa;EACX,YAAA,GAAA,OAAA,IAAc,cAAc,eAAe;YAE1C,SAAS;GACR,KAAK;GACL;GACA;GACA;EACF,CAAC;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"sortable-list.cjs","names":["PointerSensor","TouchSensor","KeyboardSensor","sortableKeyboardCoordinates","DndContext","closestCenter","restrictToVerticalAxis","restrictToParentElement","SortableContext","verticalListSortingStrategy","CSS"],"sources":["../../src/components/sortable-list.tsx"],"sourcesContent":["import {\n DndContext,\n type DragEndEvent,\n KeyboardSensor,\n PointerSensor,\n TouchSensor,\n closestCenter,\n useSensor,\n useSensors,\n} from '@dnd-kit/core'\nimport {\n restrictToParentElement,\n restrictToVerticalAxis,\n} from '@dnd-kit/modifiers'\nimport {\n SortableContext,\n arrayMove,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from '@dnd-kit/sortable'\nimport { CSS } from '@dnd-kit/utilities'\nimport type { ReactNode } from 'react'\nimport { cn } from '../utils/cn'\n\ntype SortableHookResult = ReturnType<typeof useSortable>\n\n/**\n * Props to spread onto the element that should start a drag (the handle).\n * Attach `ref`, `attributes`, and `listeners` to a focusable element such as a\n * button to make it the drag handle; `isDragging` reflects the row's state.\n */\nexport interface SortableDragHandle {\n ref: SortableHookResult['setActivatorNodeRef']\n attributes: SortableHookResult['attributes']\n listeners: SortableHookResult['listeners']\n isDragging: boolean\n}\n\nexport interface SortableListProps<T> {\n items: T[]\n /** Stable unique id for an item. */\n getItemId: (item: T) => string\n /** Called with the reordered items after a drag completes. */\n onReorder: (items: T[]) => void\n /** Render a single row. Spread `handle` onto the drag-handle element. */\n renderItem: (item: T, handle: SortableDragHandle, index: number) => ReactNode\n /** Classes for the list container (defaults to a vertical flex with gap). */\n className?: string\n}\n\n/**\n * Vertical drag-and-drop sortable list built on @dnd-kit. Pointer, touch and\n * keyboard sensors are wired up; dragging is constrained to the vertical axis\n * within the list. The consumer owns row markup and decides which element is\n * the handle via the `handle` argument of `renderItem`.\n */\nexport function SortableList<T>({\n items,\n getItemId,\n onReorder,\n renderItem,\n className,\n}: SortableListProps<T>) {\n const ids = items.map(getItemId)\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),\n useSensor(TouchSensor, {\n activationConstraint: { delay: 150, tolerance: 5 },\n }),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n }),\n )\n\n const handleDragEnd = (event: DragEndEvent) => {\n const { active, over } = event\n if (!over || active.id === over.id) return\n const oldIndex = ids.indexOf(String(active.id))\n const newIndex = ids.indexOf(String(over.id))\n if (oldIndex < 0 || newIndex < 0) return\n onReorder(arrayMove(items, oldIndex, newIndex))\n }\n\n return (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n modifiers={[restrictToVerticalAxis, restrictToParentElement]}\n onDragEnd={handleDragEnd}\n >\n <SortableContext items={ids} strategy={verticalListSortingStrategy}>\n <div\n className={cn(\n 'flex flex-col gap-[calc(var(--spark-layout-gap)*2)]',\n className,\n )}\n >\n {items.map((item, index) => (\n <SortableRow key={getItemId(item)} id={getItemId(item)}>\n {(handle) => renderItem(item, handle, index)}\n </SortableRow>\n ))}\n </div>\n </SortableContext>\n </DndContext>\n )\n}\n\ninterface SortableRowProps {\n id: string\n children: (handle: SortableDragHandle) => ReactNode\n}\n\nfunction SortableRow({ id, children }: SortableRowProps) {\n const {\n attributes,\n listeners,\n setNodeRef,\n setActivatorNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id })\n\n const style = {\n transform: CSS.Transform.toString(transform),\n transition,\n }\n\n return (\n <div\n ref={setNodeRef}\n style={style}\n className={cn(isDragging && 'relative z-10')}\n >\n {children({\n ref: setActivatorNodeRef,\n attributes,\n listeners,\n isDragging,\n })}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;AAyDA,SAAgB,aAAgB,EAC9B,OACA,WACA,WACA,YACA,aACuB;CACvB,MAAM,MAAM,MAAM,IAAI,SAAS;CAE/B,MAAM,WAAA,GAAA,cAAA,aAAA,GAAA,cAAA,WACMA,cAAAA,eAAe,EAAE,sBAAsB,EAAE,UAAU,EAAE,EAAE,CAAC,IAAA,GAAA,cAAA,WACxDC,cAAAA,aAAa,EACrB,sBAAsB;EAAE,OAAO;EAAK,WAAW;CAAE,EACnD,CAAC,IAAA,GAAA,cAAA,WACSC,cAAAA,gBAAgB,EACxB,kBAAkBC,kBAAAA,4BACpB,CAAC,CACH;CAEA,MAAM,iBAAiB,UAAwB;EAC7C,MAAM,EAAE,QAAQ,SAAS;EACzB,IAAI,CAAC,QAAQ,OAAO,OAAO,KAAK,IAAI;EACpC,MAAM,WAAW,IAAI,QAAQ,OAAO,OAAO,EAAE,CAAC;EAC9C,MAAM,WAAW,IAAI,QAAQ,OAAO,KAAK,EAAE,CAAC;EAC5C,IAAI,WAAW,KAAK,WAAW,GAAG;EAClC,WAAA,GAAA,kBAAA,WAAoB,OAAO,UAAU,QAAQ,CAAC;CAChD;CAEA,OACE,iBAAA,GAAA,kBAAA,KAACC,cAAAA,YAAD;EACW;EACT,oBAAoBC,cAAAA;EACpB,WAAW,CAACC,mBAAAA,wBAAwBC,mBAAAA,uBAAuB;EAC3D,WAAW;YAEX,iBAAA,GAAA,kBAAA,KAACC,kBAAAA,iBAAD;GAAiB,OAAO;GAAK,UAAUC,kBAAAA;aACrC,iBAAA,GAAA,kBAAA,KAAC,OAAD;IACE,YAAA,GAAA,GAAA,IACE,uDACA,SACF;cAEC,MAAM,KAAK,MAAM,UAChB,iBAAA,GAAA,kBAAA,KAAC,aAAD;KAAmC,IAAI,UAAU,IAAI;gBACjD,WAAW,WAAW,MAAM,QAAQ,KAAK;IAChC,GAFK,UAAU,IAAI,CAEnB,CACd;GACE,CAAA;EACU,CAAA;CACP,CAAA;AAEhB;AAOA,SAAS,YAAY,EAAE,IAAI,YAA8B;CACvD,MAAM,EACJ,YACA,WACA,YACA,qBACA,WACA,YACA,gBAAA,GAAA,kBAAA,aACc,EAAE,GAAG,CAAC;CAOtB,OACE,iBAAA,GAAA,kBAAA,KAAC,OAAD;EACE,KAAK;EACL,OAAO;GAPT,WAAWC,mBAAAA,IAAI,UAAU,SAAS,SAAS;GAC3C;EAMa;EACX,YAAA,GAAA,GAAA,IAAc,cAAc,eAAe;YAE1C,SAAS;GACR,KAAK;GACL;GACA;GACA;EACF,CAAC;CACE,CAAA;AAET"}
@@ -4,7 +4,7 @@ const require_chunk = require("../chunk-DY84GLCh.cjs");
4
4
  let react = require("react");
5
5
  react = require_chunk.__toESM(react);
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
- let cnfast = require("cnfast");
7
+ let cn = require("cn");
8
8
  //#region src/components/spinner.tsx
9
9
  const sizeClasses = {
10
10
  sm: "size-4",
@@ -12,7 +12,7 @@ const sizeClasses = {
12
12
  lg: "size-8"
13
13
  };
14
14
  const Spinner = react.forwardRef(({ className, size = "default", label = "Loading", ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
15
- className: (0, cnfast.cn)("inline-flex text-spark-9", sizeClasses[size], className),
15
+ className: (0, cn.cn)("inline-flex text-spark-9", sizeClasses[size], className),
16
16
  "data-slot": "spinner",
17
17
  ref,
18
18
  role: "status",
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.cjs","names":["React"],"sources":["../../src/components/spinner.tsx"],"sourcesContent":["import * as React from 'react'\nimport { cn } from '../utils/cn'\n\ninterface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Size variant */\n size?: 'sm' | 'default' | 'lg'\n /** Screen-reader announcement */\n label?: string\n}\n\nconst sizeClasses = {\n sm: 'size-4',\n default: 'size-6',\n lg: 'size-8',\n} as const\n\nconst Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>(\n ({ className, size = 'default', label = 'Loading', ...props }, ref) => (\n // color + size live on the wrapper so className can override them (svg inherits via currentColor/size-full)\n <div\n className={cn('inline-flex text-spark-9', sizeClasses[size], className)}\n data-slot=\"spinner\"\n ref={ref}\n role=\"status\"\n {...props}\n >\n <span className=\"sr-only\">{label}</span>\n {/* CSS animation (not the ECP loader's SMIL): respects prefers-reduced-motion\n and stays off the main thread; the 0.65s speed matches ECP */}\n <svg\n className=\"size-full animate-spin [animation-duration:.65s] motion-reduce:animate-none\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z\"\n fill=\"currentColor\"\n opacity=\".25\"\n />\n <path\n d=\"M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n ),\n)\n\nSpinner.displayName = 'Spinner'\n\nexport { Spinner }\nexport type { SpinnerProps }\n"],"mappings":";;;;;;;;AAUA,MAAM,cAAc;CAClB,IAAI;CACJ,SAAS;CACT,IAAI;AACN;AAEA,MAAM,UAAUA,MAAM,YACnB,EAAE,WAAW,OAAO,WAAW,QAAQ,WAAW,GAAG,SAAS,QAE7D,iBAAA,GAAA,kBAAA,MAAC,OAAD;CACE,YAAA,GAAA,OAAA,IAAc,4BAA4B,YAAY,OAAO,SAAS;CACtE,aAAU;CACL;CACL,MAAK;CACL,GAAI;WALN,CAOE,iBAAA,GAAA,kBAAA,KAAC,QAAD;EAAM,WAAU;YAAW;CAAY,CAAA,GAGvC,iBAAA,GAAA,kBAAA,MAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,SAAQ;EACR,OAAM;YAJR,CAME,iBAAA,GAAA,kBAAA,KAAC,QAAD;GACE,GAAE;GACF,MAAK;GACL,SAAQ;EACT,CAAA,GACD,iBAAA,GAAA,kBAAA,KAAC,QAAD;GACE,GAAE;GACF,MAAK;EACN,CAAA,CACE;GACF;EAET;AAEA,QAAQ,cAAc"}
1
+ {"version":3,"file":"spinner.cjs","names":["React"],"sources":["../../src/components/spinner.tsx"],"sourcesContent":["import * as React from 'react'\nimport { cn } from '../utils/cn'\n\ninterface SpinnerProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Size variant */\n size?: 'sm' | 'default' | 'lg'\n /** Screen-reader announcement */\n label?: string\n}\n\nconst sizeClasses = {\n sm: 'size-4',\n default: 'size-6',\n lg: 'size-8',\n} as const\n\nconst Spinner = React.forwardRef<HTMLDivElement, SpinnerProps>(\n ({ className, size = 'default', label = 'Loading', ...props }, ref) => (\n // color + size live on the wrapper so className can override them (svg inherits via currentColor/size-full)\n <div\n className={cn('inline-flex text-spark-9', sizeClasses[size], className)}\n data-slot=\"spinner\"\n ref={ref}\n role=\"status\"\n {...props}\n >\n <span className=\"sr-only\">{label}</span>\n {/* CSS animation (not the ECP loader's SMIL): respects prefers-reduced-motion\n and stays off the main thread; the 0.65s speed matches ECP */}\n <svg\n className=\"size-full animate-spin [animation-duration:.65s] motion-reduce:animate-none\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z\"\n fill=\"currentColor\"\n opacity=\".25\"\n />\n <path\n d=\"M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z\"\n fill=\"currentColor\"\n />\n </svg>\n </div>\n ),\n)\n\nSpinner.displayName = 'Spinner'\n\nexport { Spinner }\nexport type { SpinnerProps }\n"],"mappings":";;;;;;;;AAUA,MAAM,cAAc;CAClB,IAAI;CACJ,SAAS;CACT,IAAI;AACN;AAEA,MAAM,UAAUA,MAAM,YACnB,EAAE,WAAW,OAAO,WAAW,QAAQ,WAAW,GAAG,SAAS,QAE7D,iBAAA,GAAA,kBAAA,MAAC,OAAD;CACE,YAAA,GAAA,GAAA,IAAc,4BAA4B,YAAY,OAAO,SAAS;CACtE,aAAU;CACL;CACL,MAAK;CACL,GAAI;WALN,CAOE,iBAAA,GAAA,kBAAA,KAAC,QAAD;EAAM,WAAU;YAAW;CAAY,CAAA,GAGvC,iBAAA,GAAA,kBAAA,MAAC,OAAD;EACE,WAAU;EACV,MAAK;EACL,SAAQ;EACR,OAAM;YAJR,CAME,iBAAA,GAAA,kBAAA,KAAC,QAAD;GACE,GAAE;GACF,MAAK;GACL,SAAQ;EACT,CAAA,GACD,iBAAA,GAAA,kBAAA,KAAC,QAAD;GACE,GAAE;GACF,MAAK;EACN,CAAA,CACE;GACF;EAET;AAEA,QAAQ,cAAc"}