@epilot/spark-ui 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) 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 +1 -1
  21. package/dist/components/bar-chart-with-total.d.cts +1 -1
  22. package/dist/components/bar-chart-with-total.d.ts +1 -1
  23. package/dist/components/bar-chart.cjs +4 -4
  24. package/dist/components/bar-chart.cjs.map +1 -1
  25. package/dist/components/bar-chart.js +2 -2
  26. package/dist/components/box.cjs +2 -2
  27. package/dist/components/box.cjs.map +1 -1
  28. package/dist/components/box.js +1 -1
  29. package/dist/components/breadcrumbs.cjs +7 -7
  30. package/dist/components/breadcrumbs.cjs.map +1 -1
  31. package/dist/components/bubble.cjs +5 -5
  32. package/dist/components/bubble.cjs.map +1 -1
  33. package/dist/components/bubble.d.cts +1 -1
  34. package/dist/components/bubble.d.ts +1 -1
  35. package/dist/components/button-group.cjs +5 -5
  36. package/dist/components/button-group.cjs.map +1 -1
  37. package/dist/components/button-group.js +1 -1
  38. package/dist/components/button.cjs +2 -2
  39. package/dist/components/button.cjs.map +1 -1
  40. package/dist/components/button.d.cts +2 -2
  41. package/dist/components/button.d.ts +2 -2
  42. package/dist/components/button.js +1 -1
  43. package/dist/components/calendar.cjs +9 -9
  44. package/dist/components/calendar.cjs.map +1 -1
  45. package/dist/components/calendar.js +1 -1
  46. package/dist/components/callout.cjs +8 -8
  47. package/dist/components/callout.cjs.map +1 -1
  48. package/dist/components/callout.d.cts +1 -1
  49. package/dist/components/callout.d.ts +2 -2
  50. package/dist/components/callout.js +1 -1
  51. package/dist/components/card.cjs +16 -16
  52. package/dist/components/card.cjs.map +1 -1
  53. package/dist/components/card.js +2 -2
  54. package/dist/components/card.js.map +1 -1
  55. package/dist/components/chart.cjs +2 -2
  56. package/dist/components/chart.cjs.map +1 -1
  57. package/dist/components/checkbox.cjs +2 -2
  58. package/dist/components/checkbox.cjs.map +1 -1
  59. package/dist/components/checkbox.js +1 -1
  60. package/dist/components/circular-gauge.cjs +3 -3
  61. package/dist/components/circular-gauge.cjs.map +1 -1
  62. package/dist/components/day-of-week-picker.cjs +4 -4
  63. package/dist/components/day-of-week-picker.cjs.map +1 -1
  64. package/dist/components/dialog.cjs +9 -9
  65. package/dist/components/dialog.cjs.map +1 -1
  66. package/dist/components/dialog.js +1 -1
  67. package/dist/components/drawer.cjs +8 -8
  68. package/dist/components/drawer.cjs.map +1 -1
  69. package/dist/components/dropdown-menu.cjs +11 -11
  70. package/dist/components/dropdown-menu.cjs.map +1 -1
  71. package/dist/components/dropdown-menu.js +1 -1
  72. package/dist/components/empty-state.d.ts +1 -1
  73. package/dist/components/energy-breakdown.cjs +80 -0
  74. package/dist/components/energy-breakdown.cjs.map +1 -0
  75. package/dist/components/energy-breakdown.d.cts +24 -0
  76. package/dist/components/energy-breakdown.d.ts +24 -0
  77. package/dist/components/energy-breakdown.js +79 -0
  78. package/dist/components/energy-breakdown.js.map +1 -0
  79. package/dist/components/energy-history-chart.cjs +63 -0
  80. package/dist/components/energy-history-chart.cjs.map +1 -0
  81. package/dist/components/energy-history-chart.d.cts +64 -0
  82. package/dist/components/energy-history-chart.d.ts +64 -0
  83. package/dist/components/energy-history-chart.js +62 -0
  84. package/dist/components/energy-history-chart.js.map +1 -0
  85. package/dist/components/entity-card.cjs +11 -11
  86. package/dist/components/entity-card.cjs.map +1 -1
  87. package/dist/components/entity-card.d.ts +1 -1
  88. package/dist/components/field.cjs +15 -15
  89. package/dist/components/field.cjs.map +1 -1
  90. package/dist/components/hero-metric.cjs +4 -4
  91. package/dist/components/hero-metric.cjs.map +1 -1
  92. package/dist/components/icon.cjs +2 -2
  93. package/dist/components/icon.cjs.map +1 -1
  94. package/dist/components/icon.js +1 -1
  95. package/dist/components/info-block.cjs +9 -9
  96. package/dist/components/info-block.cjs.map +1 -1
  97. package/dist/components/info-block.d.cts +1 -1
  98. package/dist/components/info-block.d.ts +1 -1
  99. package/dist/components/input-date.cjs +3 -3
  100. package/dist/components/input-date.cjs.map +1 -1
  101. package/dist/components/input-date.js +1 -1
  102. package/dist/components/input-group.cjs +5 -5
  103. package/dist/components/input-group.cjs.map +1 -1
  104. package/dist/components/input-otp.cjs +3 -3
  105. package/dist/components/input-otp.cjs.map +1 -1
  106. package/dist/components/input-otp.js +1 -1
  107. package/dist/components/input.cjs +3 -3
  108. package/dist/components/input.cjs.map +1 -1
  109. package/dist/components/input.js +1 -1
  110. package/dist/components/label.cjs +2 -2
  111. package/dist/components/label.cjs.map +1 -1
  112. package/dist/components/layout.cjs +2 -2
  113. package/dist/components/layout.cjs.map +1 -1
  114. package/dist/components/line-chart.cjs +7 -7
  115. package/dist/components/line-chart.cjs.map +1 -1
  116. package/dist/components/line-chart.js +4 -4
  117. package/dist/components/list.cjs +10 -10
  118. package/dist/components/list.cjs.map +1 -1
  119. package/dist/components/list.js +1 -1
  120. package/dist/components/list.js.map +1 -1
  121. package/dist/components/marker.cjs +4 -4
  122. package/dist/components/marker.cjs.map +1 -1
  123. package/dist/components/marker.d.cts +1 -1
  124. package/dist/components/marker.d.ts +1 -1
  125. package/dist/components/message-scroller.cjs +8 -8
  126. package/dist/components/message-scroller.cjs.map +1 -1
  127. package/dist/components/message-scroller.js +4 -4
  128. package/dist/components/message-scroller.js.map +1 -1
  129. package/dist/components/message.cjs +7 -7
  130. package/dist/components/message.cjs.map +1 -1
  131. package/dist/components/number-flow.d.cts +2 -12
  132. package/dist/components/number-flow.d.ts +2 -12
  133. package/dist/components/page-header.cjs +6 -6
  134. package/dist/components/page-header.cjs.map +1 -1
  135. package/dist/components/page-header.d.cts +1 -1
  136. package/dist/components/page-header.d.ts +1 -1
  137. package/dist/components/page-header.js +1 -1
  138. package/dist/components/period-tabs.cjs +6 -6
  139. package/dist/components/period-tabs.cjs.map +1 -1
  140. package/dist/components/pill.cjs +3 -3
  141. package/dist/components/pill.cjs.map +1 -1
  142. package/dist/components/pill.js +2 -2
  143. package/dist/components/popover.cjs +2 -2
  144. package/dist/components/popover.cjs.map +1 -1
  145. package/dist/components/power-history-chart.cjs +498 -0
  146. package/dist/components/power-history-chart.cjs.map +1 -0
  147. package/dist/components/power-history-chart.d.cts +71 -0
  148. package/dist/components/power-history-chart.d.ts +71 -0
  149. package/dist/components/power-history-chart.js +496 -0
  150. package/dist/components/power-history-chart.js.map +1 -0
  151. package/dist/components/power-network.cjs +559 -0
  152. package/dist/components/power-network.cjs.map +1 -0
  153. package/dist/components/power-network.d.cts +80 -0
  154. package/dist/components/power-network.d.ts +80 -0
  155. package/dist/components/power-network.js +553 -0
  156. package/dist/components/power-network.js.map +1 -0
  157. package/dist/components/progress.cjs +2 -2
  158. package/dist/components/progress.cjs.map +1 -1
  159. package/dist/components/radio-card-group.cjs +3 -3
  160. package/dist/components/radio-card-group.cjs.map +1 -1
  161. package/dist/components/radio.cjs +3 -3
  162. package/dist/components/radio.cjs.map +1 -1
  163. package/dist/components/radio.js +1 -1
  164. package/dist/components/sankey-chart.cjs +13 -3
  165. package/dist/components/sankey-chart.cjs.map +1 -1
  166. package/dist/components/sankey-chart.d.cts +3 -2
  167. package/dist/components/sankey-chart.d.ts +3 -2
  168. package/dist/components/sankey-chart.js +11 -2
  169. package/dist/components/sankey-chart.js.map +1 -1
  170. package/dist/components/schedule-editor.cjs +9 -9
  171. package/dist/components/schedule-editor.cjs.map +1 -1
  172. package/dist/components/schedule-list.cjs +11 -11
  173. package/dist/components/schedule-list.cjs.map +1 -1
  174. package/dist/components/schedule-list.js +1 -1
  175. package/dist/components/scroll-area.cjs +3 -3
  176. package/dist/components/scroll-area.cjs.map +1 -1
  177. package/dist/components/segmented-control.cjs +4 -4
  178. package/dist/components/segmented-control.cjs.map +1 -1
  179. package/dist/components/select.cjs +13 -13
  180. package/dist/components/select.cjs.map +1 -1
  181. package/dist/components/select.js +2 -2
  182. package/dist/components/separator.cjs +3 -3
  183. package/dist/components/separator.cjs.map +1 -1
  184. package/dist/components/series-legend.cjs +4 -4
  185. package/dist/components/series-legend.cjs.map +1 -1
  186. package/dist/components/sheet.cjs +9 -9
  187. package/dist/components/sheet.cjs.map +1 -1
  188. package/dist/components/sheet.js +1 -1
  189. package/dist/components/skeleton.cjs +3 -3
  190. package/dist/components/skeleton.cjs.map +1 -1
  191. package/dist/components/slider.cjs +11 -11
  192. package/dist/components/slider.cjs.map +1 -1
  193. package/dist/components/solar-forecast-chart.cjs +108 -0
  194. package/dist/components/solar-forecast-chart.cjs.map +1 -0
  195. package/dist/components/solar-forecast-chart.d.cts +77 -0
  196. package/dist/components/solar-forecast-chart.d.ts +77 -0
  197. package/dist/components/solar-forecast-chart.js +103 -0
  198. package/dist/components/solar-forecast-chart.js.map +1 -0
  199. package/dist/components/sortable-list.cjs +3 -3
  200. package/dist/components/sortable-list.cjs.map +1 -1
  201. package/dist/components/spinner.cjs +2 -2
  202. package/dist/components/spinner.cjs.map +1 -1
  203. package/dist/components/stat-card.cjs +8 -8
  204. package/dist/components/stat-card.cjs.map +1 -1
  205. package/dist/components/stepper.cjs +3 -3
  206. package/dist/components/stepper.cjs.map +1 -1
  207. package/dist/components/stepper.js +1 -1
  208. package/dist/components/switch.cjs +3 -3
  209. package/dist/components/switch.cjs.map +1 -1
  210. package/dist/components/switch.js +1 -1
  211. package/dist/components/table.cjs +9 -9
  212. package/dist/components/table.cjs.map +1 -1
  213. package/dist/components/table.js +1 -1
  214. package/dist/components/tabs.cjs +8 -8
  215. package/dist/components/tabs.cjs.map +1 -1
  216. package/dist/components/tabs.js +1 -1
  217. package/dist/components/textarea.cjs +3 -3
  218. package/dist/components/textarea.cjs.map +1 -1
  219. package/dist/components/textarea.js +1 -1
  220. package/dist/components/theme.cjs +2 -2
  221. package/dist/components/theme.cjs.map +1 -1
  222. package/dist/components/theme.js +1 -1
  223. package/dist/components/thumbnail.cjs +3 -3
  224. package/dist/components/thumbnail.cjs.map +1 -1
  225. package/dist/components/time-picker.cjs +3 -3
  226. package/dist/components/time-picker.cjs.map +1 -1
  227. package/dist/components/toast.cjs +8 -8
  228. package/dist/components/toast.cjs.map +1 -1
  229. package/dist/components/toast.js +1 -1
  230. package/dist/components/tooltip.cjs +4 -4
  231. package/dist/components/tooltip.cjs.map +1 -1
  232. package/dist/components/tooltip.js +1 -1
  233. package/dist/components/typography.cjs +6 -6
  234. package/dist/components/typography.cjs.map +1 -1
  235. package/dist/components/wizard-layout.cjs +8 -8
  236. package/dist/components/wizard-layout.cjs.map +1 -1
  237. package/dist/lib/input-box-variants.cjs +9 -9
  238. package/dist/lib/input-box-variants.cjs.map +1 -1
  239. package/dist/number-flow-Bch81ixZ.d.cts +14 -0
  240. package/dist/number-flow-Bch81ixZ.d.ts +14 -0
  241. package/dist/styles/spark-default.css +11 -0
  242. package/dist/utils/build-sankey-nodes.cjs +72 -0
  243. package/dist/utils/build-sankey-nodes.cjs.map +1 -0
  244. package/dist/utils/build-sankey-nodes.d.cts +27 -0
  245. package/dist/utils/build-sankey-nodes.d.ts +27 -0
  246. package/dist/utils/build-sankey-nodes.js +71 -0
  247. package/dist/utils/build-sankey-nodes.js.map +1 -0
  248. package/dist/utils/cn.cjs +2 -2
  249. package/dist/utils/cn.d.cts +1 -1
  250. package/dist/utils/cn.d.ts +1 -1
  251. package/dist/utils/cn.js +1 -1
  252. package/dist/utils/format.cjs +13 -0
  253. package/dist/utils/format.cjs.map +1 -1
  254. package/dist/utils/format.d.cts +7 -1
  255. package/dist/utils/format.d.ts +7 -1
  256. package/dist/utils/format.js +12 -1
  257. package/dist/utils/format.js.map +1 -1
  258. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"power-history-chart.js","names":["React","valueFormat"],"sources":["../../src/components/power-history-chart.tsx"],"sourcesContent":["import { curveMonotoneX } from '@visx/curve'\nimport { localPoint } from '@visx/event'\nimport { scaleLinear } from '@visx/scale'\nimport { Area, LinePath } from '@visx/shape'\nimport { useTooltip } from '@visx/tooltip'\nimport { bisector } from '@visx/vendor/d3-array'\nimport * as React from 'react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useAnimatedChartDomain, useChartZoom } from '../hooks/use-chart-zoom'\nimport { useReducedMotion } from '../hooks/use-reduced-motion'\nimport { NARROW_NBSP } from '../utils/chars'\nimport {\n buildYAxisScale,\n formatYAxisTick,\n measureYAxisGutter,\n} from '../utils/chart-scale'\nimport { buildTimeTicks } from '../utils/chart-time-ticks'\nimport type { TimeDomain } from '../utils/chart-zoom'\nimport { cn } from '../utils/cn'\nimport { twoDecimalFormat as valueFormat } from '../utils/format'\nimport {\n ChartTooltipCard,\n type ChartTooltipRow,\n ChartZoomResetButton,\n clampTooltipPosition,\n useMeasuredSize,\n} from './chart'\nimport { SeriesLegend, type SeriesLegendItem } from './series-legend'\nimport { SectionTitle } from './typography'\n\nexport interface PowerHistoryPoint {\n /** Epoch milliseconds. */\n t: number\n solar: number\n /** Signed: positive = import, negative = feed-in. */\n grid: number\n /** Signed: positive = discharge, negative = charge. */\n battery: number\n consumption: number\n}\n\nexport interface PowerHistoryChartProps {\n data: PowerHistoryPoint[]\n /** Axis/tooltip unit, e.g. \"kW\". */\n unit: string\n formatX: (t: number) => string\n /** Tooltip time formatter. Defaults to `formatX`; use to keep the tooltip's\n * full clock time when the axis ticks are compact (hour-only). */\n formatTooltipX?: (t: number) => string\n formatValue?: (value: number) => string\n labels: {\n solar: string\n grid: string\n battery: string\n consumption: string\n }\n /** Series to drop entirely (no legend entry, area, or tooltip row). */\n omitSeries?: readonly PowerHistorySeriesKey[]\n /** Epoch ms to mark with the current-time dashed line (omit to hide). */\n highlightAt?: number\n title?: string\n className?: string\n height?: number\n zoomable?: boolean\n zoomResetLabel?: string\n zoomDomain?: TimeDomain | null\n onZoomDomainChange?: (domain: TimeDomain | null) => void\n}\n\nconst AREA_KEYS = ['solar', 'grid', 'battery'] as const\n\n// All toggleable series, in legend order. The areas (solar/grid/battery) plus\n// the consumption overlay line.\nconst SERIES_KEYS = ['solar', 'grid', 'battery', 'consumption'] as const\nexport type PowerHistorySeriesKey = (typeof SERIES_KEYS)[number]\ntype SeriesKey = PowerHistorySeriesKey\n\nconst SERIES_COLORS = {\n solar: 'var(--orange-a9)',\n grid: 'var(--blue-9)',\n battery: 'var(--green-a9)',\n consumption: 'var(--neutral-12)',\n} as const\n\nconst PADDING_TOP = 20\nconst PADDING_BOTTOM = 28\nconst PADDING_RIGHT = 8\nconst X_TICK_COUNT = 6\nconst DEFAULT_HEIGHT = 260\nconst DETAILED_X_AXIS_THRESHOLD = 6 * 60 * 60 * 1000\n\nconst tBisector = bisector<PowerHistoryPoint, number>((d) => d.t)\n\nfunction touchDistance(a: React.Touch, b: React.Touch): number {\n return Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY)\n}\n\nfunction touchMidpoint(\n a: React.Touch,\n b: React.Touch,\n): {\n clientX: number\n clientY: number\n} {\n return {\n clientX: (a.clientX + b.clientX) / 2,\n clientY: (a.clientY + b.clientY) / 2,\n }\n}\n\n/**\n * Diverging \"power sources\" area chart over a continuous time axis: solar\n * (orange), grid (blue) and battery (green) areas around a zero baseline, with\n * consumption overlaid as a line. Negative values (grid feed-in, battery\n * charging) dip below zero. For instantaneous power (day view). A legend at the\n * top toggles each series on/off; the y-axis rescales to the visible ones. visx.\n */\nexport function PowerHistoryChart({\n data,\n unit,\n formatX,\n formatTooltipX,\n formatValue,\n labels,\n omitSeries,\n highlightAt,\n title,\n className,\n height = DEFAULT_HEIGHT,\n zoomable = false,\n zoomResetLabel,\n zoomDomain,\n onZoomDomainChange,\n}: PowerHistoryChartProps) {\n const plotClipId = React.useId()\n const fmtTooltipX = formatTooltipX ?? formatX\n const containerRef = useRef<HTMLDivElement>(null)\n const reducedMotion = useReducedMotion()\n const [width, setWidth] = useState(0)\n const [hiddenSeries, setHiddenSeries] = useState<ReadonlySet<string>>(\n () => new Set(),\n )\n const omitted = useMemo(() => new Set<SeriesKey>(omitSeries), [omitSeries])\n\n const toggleSeries = useCallback((id: string) => {\n setHiddenSeries((prev) => {\n const next = new Set(prev)\n if (next.has(id)) {\n next.delete(id)\n } else {\n next.add(id)\n }\n return next\n })\n }, [])\n\n const isHidden = useCallback(\n (key: SeriesKey) => omitted.has(key) || hiddenSeries.has(key),\n [hiddenSeries, omitted],\n )\n\n const {\n showTooltip,\n hideTooltip,\n tooltipData,\n tooltipLeft,\n tooltipTop,\n tooltipOpen,\n } = useTooltip<PowerHistoryPoint>()\n\n useEffect(() => {\n const element = containerRef.current\n if (!element) return\n\n const resizeObserver = new ResizeObserver((entries) => {\n const entry = entries[0]\n if (!entry) return\n setWidth(entry.contentRect.width)\n })\n\n resizeObserver.observe(element)\n return () => resizeObserver.disconnect()\n }, [])\n\n const fmtValue = (value: number) =>\n formatValue\n ? formatValue(value)\n : `${valueFormat.format(value)}${NARROW_NBSP}${unit}`\n\n const tMin = data.length ? (data[0]?.t ?? 0) : 0\n const tMax = data.length ? (data[data.length - 1]?.t ?? 0) : 0\n const fullDomain = useMemo((): TimeDomain | null => {\n if (data.length === 0) return null\n return [tMin, tMax]\n }, [data.length, tMin, tMax])\n\n const zoom = useChartZoom({\n enabled: zoomable,\n fullDomain,\n zoomDomain,\n onZoomDomainChange,\n })\n\n const viewMin = zoom.zoomWindow?.[0] ?? tMin\n const viewMax = zoom.zoomWindow?.[1] ?? tMax\n\n const visibleData = useMemo(\n () => data.filter((d) => d.t >= viewMin && d.t <= viewMax),\n [data, viewMin, viewMax],\n )\n\n const { yMin, yMax } = useMemo(() => {\n let min = 0\n let max = 0\n for (const d of visibleData) {\n if (!isHidden('solar')) max = Math.max(max, d.solar)\n if (!isHidden('consumption')) max = Math.max(max, d.consumption)\n if (!isHidden('grid')) {\n max = Math.max(max, d.grid)\n min = Math.min(min, d.grid)\n }\n if (!isHidden('battery')) {\n max = Math.max(max, d.battery)\n min = Math.min(min, d.battery)\n }\n }\n return { yMin: min, yMax: max }\n }, [visibleData, isHidden])\n\n const {\n ticks: yTicks,\n min: lo,\n max: hi,\n } = useMemo(() => buildYAxisScale(yMin, yMax), [yMin, yMax])\n const displayedYDomain = useAnimatedChartDomain([lo, hi], {\n animateChanges: zoom.isPinching,\n reducedMotion,\n })\n\n const gutter = useMemo(\n () => measureYAxisGutter(yTicks, formatYAxisTick),\n [yTicks],\n )\n\n const plotRight = Math.max(gutter, width - PADDING_RIGHT)\n\n const xScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [viewMin, viewMax === viewMin ? viewMin + 1 : viewMax],\n range: [gutter, plotRight],\n }),\n [viewMin, viewMax, plotRight, gutter],\n )\n\n const plotHeight = Math.max(0, height - PADDING_BOTTOM - PADDING_TOP)\n const plotWidth = Math.max(0, plotRight - gutter)\n\n const yScale = useMemo(\n () =>\n scaleLinear<number>({\n domain: [\n displayedYDomain[0],\n displayedYDomain[1] === displayedYDomain[0]\n ? displayedYDomain[0] + 1\n : displayedYDomain[1],\n ],\n range: [height - PADDING_BOTTOM, PADDING_TOP],\n }),\n [displayedYDomain, height],\n )\n\n const xTicks = useMemo(\n () => buildTimeTicks(viewMin, viewMax, X_TICK_COUNT),\n [viewMin, viewMax],\n )\n const formatAxisX =\n zoom.isZoomed && viewMax - viewMin <= DETAILED_X_AXIS_THRESHOLD\n ? fmtTooltipX\n : formatX\n\n const domainXFromEvent = useCallback(\n (\n event:\n | React.MouseEvent<SVGRectElement>\n | React.PointerEvent<SVGRectElement>\n | React.TouchEvent<SVGRectElement>,\n ) => {\n const point = localPoint(event)\n if (!point) return null\n return xScale.invert(point.x)\n },\n [xScale],\n )\n\n const handleTooltip = useCallback(\n (\n event:\n | React.TouchEvent<SVGRectElement>\n | React.MouseEvent<SVGRectElement>,\n ) => {\n if (zoom.isBrushing || zoom.isPanning) return\n const point = localPoint(event)\n if (!point || data.length === 0) return\n\n const xMs = xScale.invert(point.x)\n const index = tBisector.left(data, xMs, 1)\n const d0 = data[index - 1]\n const d1 = data[index]\n if (!d0) return\n\n const d = d1 && xMs - d0.t > d1.t - xMs ? d1 : d0\n showTooltip({\n tooltipData: d,\n tooltipLeft: xScale(d.t),\n tooltipTop: PADDING_TOP,\n })\n },\n [data, showTooltip, xScale, zoom.isBrushing, zoom.isPanning],\n )\n\n const legendItems: SeriesLegendItem[] = SERIES_KEYS.filter(\n (key) => !omitted.has(key),\n ).map((key) => ({\n id: key,\n label: labels[key],\n color: SERIES_COLORS[key],\n }))\n\n const tooltipRows: ChartTooltipRow[] = tooltipData\n ? SERIES_KEYS.filter((key) => !isHidden(key)).map((key) => ({\n key,\n label: labels[key],\n color: SERIES_COLORS[key],\n value: fmtValue(tooltipData[key]),\n }))\n : []\n\n const showHighlight =\n highlightAt !== undefined &&\n highlightAt >= viewMin &&\n highlightAt <= viewMax\n\n const [tooltipRef, tooltipSize] = useMeasuredSize<HTMLDivElement>()\n const tooltipPos = clampTooltipPosition({\n anchorX: tooltipLeft ?? 0,\n anchorY: tooltipTop ?? PADDING_TOP,\n width,\n height,\n tipWidth: tooltipSize.width,\n tipHeight: tooltipSize.height,\n placement: 'beside',\n })\n\n return (\n <div className={cn('flex w-full flex-col gap-2', className)}>\n {title && <SectionTitle>{title}</SectionTitle>}\n <SeriesLegend\n items={legendItems}\n hiddenIds={hiddenSeries}\n onToggle={toggleSeries}\n />\n <span className=\"px-1 text-xs text-gray-light\">{unit}</span>\n <div ref={containerRef} className=\"relative w-full\" style={{ height }}>\n {width > 0 && (\n <svg width={width} height={height}>\n <defs>\n {AREA_KEYS.map((key) => (\n <linearGradient\n key={key}\n id={`power-history-fill-${key}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop\n offset=\"0%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.35}\n />\n <stop\n offset=\"100%\"\n stopColor={SERIES_COLORS[key]}\n stopOpacity={0.04}\n />\n </linearGradient>\n ))}\n <clipPath id={plotClipId}>\n <rect\n height={plotHeight}\n width={plotWidth}\n x={gutter}\n y={PADDING_TOP}\n />\n </clipPath>\n </defs>\n\n {yTicks.map((tick) => {\n const y = yScale(tick)\n return (\n <g key={`y-tick-${tick}`}>\n <line\n x1={gutter}\n x2={plotRight}\n y1={y}\n y2={y}\n stroke=\"var(--neutral-a4)\"\n strokeWidth={1}\n />\n <text\n x={gutter - 7}\n y={y + 3}\n textAnchor=\"end\"\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n >\n {formatYAxisTick(tick)}\n </text>\n </g>\n )\n })}\n\n <g clipPath={`url(#${plotClipId})`} data-testid=\"chart-plot-clip\">\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <Area<PowerHistoryPoint>\n key={`area-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y0={yScale(0)}\n y1={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n fill={`url(#power-history-fill-${key})`}\n stroke=\"transparent\"\n />\n ))}\n\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <LinePath<PowerHistoryPoint>\n key={`line-${key}`}\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d[key])}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS[key]}\n strokeWidth={1.5}\n fill=\"none\"\n />\n ))}\n\n {!isHidden('consumption') && (\n <LinePath<PowerHistoryPoint>\n data={data}\n x={(d) => xScale(d.t)}\n y={(d) => yScale(d.consumption)}\n curve={curveMonotoneX}\n stroke={SERIES_COLORS.consumption}\n strokeWidth={1.5}\n fill=\"none\"\n />\n )}\n </g>\n\n <line\n x1={gutter}\n x2={plotRight}\n y1={yScale(0)}\n y2={yScale(0)}\n stroke=\"var(--neutral-a6)\"\n strokeWidth={1}\n />\n\n {showHighlight && (\n <line\n x1={xScale(highlightAt)}\n x2={xScale(highlightAt)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--spark-a9)\"\n strokeWidth={1.5}\n strokeDasharray=\"5 4\"\n pointerEvents=\"none\"\n />\n )}\n\n {xTicks.map((tick, index) => (\n <text\n key={`x-tick-${tick}`}\n x={xScale(tick)}\n y={height - PADDING_BOTTOM + 16}\n textAnchor={\n index === 0\n ? 'start'\n : index === xTicks.length - 1\n ? 'end'\n : 'middle'\n }\n fill=\"var(--neutral-a11)\"\n fontSize={10}\n >\n {formatAxisX(tick)}\n </text>\n ))}\n\n {tooltipOpen &&\n tooltipData &&\n !zoom.isBrushing &&\n !zoom.isPanning && (\n <>\n <line\n x1={xScale(tooltipData.t)}\n x2={xScale(tooltipData.t)}\n y1={PADDING_TOP}\n y2={height - PADDING_BOTTOM}\n stroke=\"var(--neutral-a6)\"\n strokeDasharray=\"4 4\"\n pointerEvents=\"none\"\n />\n {AREA_KEYS.filter((key) => !isHidden(key)).map((key) => (\n <circle\n key={`dot-${key}`}\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData[key])}\n r={3.5}\n fill={SERIES_COLORS[key]}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n ))}\n {!isHidden('consumption') && (\n <circle\n cx={xScale(tooltipData.t)}\n cy={yScale(tooltipData.consumption)}\n r={3.5}\n fill={SERIES_COLORS.consumption}\n stroke=\"white\"\n strokeWidth={1.5}\n pointerEvents=\"none\"\n />\n )}\n </>\n )}\n\n {zoom.brushBand && (\n <rect\n data-testid=\"chart-brush-band\"\n x={Math.min(\n xScale(zoom.brushBand[0]),\n xScale(zoom.brushBand[1]),\n )}\n y={PADDING_TOP}\n width={Math.abs(\n xScale(zoom.brushBand[1]) - xScale(zoom.brushBand[0]),\n )}\n height={plotHeight}\n fill=\"var(--spark-a4)\"\n pointerEvents=\"none\"\n />\n )}\n\n <rect\n x={gutter}\n y={PADDING_TOP}\n width={plotWidth}\n height={plotHeight}\n fill=\"transparent\"\n style={zoomable ? { touchAction: 'pan-y' } : undefined}\n onMouseLeave={() => {\n if (!zoom.isBrushing) hideTooltip()\n }}\n onMouseMove={(event) => {\n if (zoom.isBrushing) {\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerMove(domainX)\n return\n }\n handleTooltip(event)\n }}\n onPointerDown={(event) => {\n if (!zoomable || event.pointerType === 'touch') return\n if (event.button !== 0) return\n const domainX = domainXFromEvent(event)\n if (domainX == null) return\n event.currentTarget.setPointerCapture?.(event.pointerId)\n hideTooltip()\n zoom.onPointerDown(domainX)\n }}\n onPointerMove={(event) => {\n if (!zoomable) return\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerMove(domainX)\n }}\n onPointerUp={(event) => {\n if (!zoomable) return\n const domainX = domainXFromEvent(event)\n if (domainX != null) zoom.onPointerUp(domainX)\n else zoom.onPointerCancel()\n }}\n onPointerCancel={() => {\n zoom.onPointerCancel()\n }}\n onDoubleClick={() => {\n if (zoomable) zoom.onDoubleClick()\n }}\n onTouchStart={(event) => {\n if (!zoomable) return\n if (event.touches.length === 2) {\n const [a, b] = [event.touches[0], event.touches[1]]\n if (!a || !b) return\n const point = localPoint(\n event.currentTarget,\n touchMidpoint(a, b) as MouseEvent,\n )\n if (!point) return\n const midpoint = Math.min(\n 1,\n Math.max(0, (point.x - gutter) / plotWidth),\n )\n zoom.onPinchStart(touchDistance(a, b), midpoint)\n hideTooltip()\n return\n }\n if (event.touches.length === 1 && zoom.isZoomed) {\n const point = localPoint(event)\n if (!point) return\n zoom.onPanStart({\n x: (point.x - gutter) / plotWidth,\n y: (point.y - PADDING_TOP) / plotHeight,\n })\n hideTooltip()\n }\n }}\n onTouchMove={(event) => {\n if (zoomable && event.touches.length === 2) {\n const [a, b] = [event.touches[0], event.touches[1]]\n if (!a || !b) return\n const point = localPoint(\n event.currentTarget,\n touchMidpoint(a, b) as MouseEvent,\n )\n if (!point) return\n const midpoint = Math.min(\n 1,\n Math.max(0, (point.x - gutter) / plotWidth),\n )\n zoom.onPinchMove(touchDistance(a, b), midpoint)\n return\n }\n if (event.touches.length === 1 && zoom.isZoomed) {\n const point = localPoint(event)\n if (!point) return\n zoom.onPanMove({\n x: (point.x - gutter) / plotWidth,\n y: (point.y - PADDING_TOP) / plotHeight,\n })\n return\n }\n if (event.touches.length === 1) handleTooltip(event)\n }}\n onTouchEnd={(event) => {\n if (zoomable && event.touches.length < 2) zoom.onPinchEnd()\n if (event.touches.length === 0) {\n if (zoomable) zoom.onPanEnd()\n if (zoomable) zoom.onTap()\n hideTooltip()\n }\n }}\n onTouchCancel={() => {\n zoom.onPinchEnd()\n zoom.onPanEnd()\n }}\n />\n </svg>\n )}\n\n {zoom.isZoomed && zoomResetLabel && (\n <ChartZoomResetButton\n label={zoomResetLabel}\n onReset={zoom.resetZoom}\n />\n )}\n\n <div\n ref={tooltipRef}\n className=\"pointer-events-none absolute z-10\"\n style={{\n left: tooltipPos.left,\n top: tooltipPos.top,\n opacity:\n tooltipOpen && tooltipData && !zoom.isPanning && !zoom.isBrushing\n ? 1\n : 0,\n transition: reducedMotion\n ? 'opacity 140ms ease-out'\n : 'left 140ms ease-out, top 140ms ease-out, opacity 140ms ease-out',\n }}\n >\n {tooltipData && (\n <ChartTooltipCard\n label={fmtTooltipX(tooltipData.t)}\n rows={tooltipRows}\n />\n )}\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
@@ -0,0 +1,559 @@
1
+ "use client";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_hooks_use_reduced_motion = require("../hooks/use-reduced-motion.cjs");
4
+ const require_components_skeleton = require("./skeleton.cjs");
5
+ const require_components_typography = require("./typography.cjs");
6
+ const require_components_number_flow = require("./number-flow.cjs");
7
+ let react = require("react");
8
+ let _epilot_spark_icons = require("@epilot/spark-icons");
9
+ let react_jsx_runtime = require("react/jsx-runtime");
10
+ let cn = require("cn");
11
+ //#region src/components/power-network.tsx
12
+ const SOURCE_VARIANTS = {
13
+ grid: {
14
+ activeClassName: "border-blue-a9 bg-blue-a1 shadow-blue-a4",
15
+ flowStroke: "var(--blue-11)",
16
+ initialDashGap: 10
17
+ },
18
+ solar: {
19
+ activeClassName: "border-orange-a9 bg-orange-a1 shadow-orange-a3",
20
+ flowStroke: "var(--orange-a9)",
21
+ initialDashGap: 5
22
+ },
23
+ battery: {
24
+ activeClassName: "border-green-a9 bg-green-a1 shadow-green-a3",
25
+ flowStroke: "var(--green-a9)",
26
+ initialDashGap: 7
27
+ },
28
+ neutral: {
29
+ activeClassName: "border-gray-a9 bg-gray-a1 shadow-gray-a3",
30
+ flowStroke: "var(--neutral-a9)",
31
+ initialDashGap: 9
32
+ }
33
+ };
34
+ const DEVICE_FLOW_STROKE = "var(--neutral-a9)";
35
+ const DEVICE_INITIAL_DASH_GAP = 9;
36
+ const IDLE_STROKE = "var(--neutral-a6)";
37
+ const SKELETON_STROKE = "var(--neutral-a6)";
38
+ const STROKE_WIDTH = 2;
39
+ const STRAIGHT_TOLERANCE_PX = 3;
40
+ const SOURCE_HOME_OFFSET_STEP_PX = 10;
41
+ const DEVICE_HOME_OFFSET_STEP_PX = 10;
42
+ const FLOW_DASH_SOLID = 1;
43
+ const FLOW_GAP_MAX = 14;
44
+ const FLOW_GAP_MIN = 5;
45
+ const FLOW_SPEED_MIN = 10;
46
+ const FLOW_SPEED_MAX = 56;
47
+ const PowerNetworkContext = (0, react.createContext)(null);
48
+ function usePowerNetwork() {
49
+ const ctx = (0, react.useContext)(PowerNetworkContext);
50
+ if (!ctx) throw new Error("Power network sub-components must be used inside <PowerNetworkDiagram>.");
51
+ return ctx;
52
+ }
53
+ function anchorTop(rect, containerRect) {
54
+ return {
55
+ x: rect.left + rect.width / 2 - containerRect.left,
56
+ y: rect.top - containerRect.top
57
+ };
58
+ }
59
+ function anchorBottom(rect, containerRect) {
60
+ return {
61
+ x: rect.left + rect.width / 2 - containerRect.left,
62
+ y: rect.bottom - containerRect.top
63
+ };
64
+ }
65
+ function buildAdaptivePath(from, to) {
66
+ if (Math.abs(from.x - to.x) < STRAIGHT_TOLERANCE_PX) return `M ${from.x} ${from.y} L ${to.x} ${to.y}`;
67
+ const dy = to.y - from.y;
68
+ const c1y = from.y + dy * .56;
69
+ const c2y = to.y - dy * .58;
70
+ return `M ${from.x} ${from.y} C ${from.x} ${c1y} ${to.x} ${c2y} ${to.x} ${to.y}`;
71
+ }
72
+ function connectorPathsEqual(a, b) {
73
+ if (a.length !== b.length) return false;
74
+ for (let i = 0; i < a.length; i++) {
75
+ const ap = a[i];
76
+ const bp = b[i];
77
+ if (!ap || !bp) return false;
78
+ if (ap.id !== bp.id || ap.d !== bp.d || ap.variant !== bp.variant || ap.kind !== bp.kind || ap.reversed !== bp.reversed) return false;
79
+ }
80
+ return true;
81
+ }
82
+ function PowerNetworkDiagram({ title, loading = false, className, children }) {
83
+ const containerRef = (0, react.useRef)(null);
84
+ const reducedMotion = require_hooks_use_reduced_motion.useReducedMotion();
85
+ const [nodes, setNodes] = (0, react.useState)(() => /* @__PURE__ */ new Map());
86
+ const [connectorPaths, setConnectorPaths] = (0, react.useState)([]);
87
+ const connectorPathsRef = (0, react.useRef)([]);
88
+ const flowPathRefs = (0, react.useRef)({});
89
+ const idlePathRefs = (0, react.useRef)({});
90
+ const currentFlowVisualsRef = (0, react.useRef)({});
91
+ const targetFlowVisualsRef = (0, react.useRef)({});
92
+ const register = (0, react.useCallback)((node) => {
93
+ setNodes((prev) => {
94
+ const existing = prev.get(node.id);
95
+ if (existing && existing.kind === node.kind && existing.variant === node.variant && existing.watts === node.watts && existing.element === node.element) return prev;
96
+ const next = new Map(prev);
97
+ next.set(node.id, node);
98
+ return next;
99
+ });
100
+ }, []);
101
+ const unregister = (0, react.useCallback)((id) => {
102
+ setNodes((prev) => {
103
+ if (!prev.has(id)) return prev;
104
+ const next = new Map(prev);
105
+ next.delete(id);
106
+ return next;
107
+ });
108
+ }, []);
109
+ const totalSourceWatts = (0, react.useMemo)(() => {
110
+ let total = 0;
111
+ for (const node of nodes.values()) if (node.kind === "source") total += node.watts;
112
+ return total;
113
+ }, [nodes]);
114
+ (0, react.useEffect)(() => {
115
+ const container = containerRef.current;
116
+ if (!container) return;
117
+ let homeNode;
118
+ const sources = [];
119
+ const devices = [];
120
+ for (const node of nodes.values()) if (node.kind === "home") homeNode = node;
121
+ else if (node.element) if (node.kind === "source") sources.push(node);
122
+ else devices.push(node);
123
+ const updatePaths = () => {
124
+ if (!homeNode || !homeNode.element) {
125
+ setConnectorPaths((prev) => prev.length === 0 ? prev : []);
126
+ return;
127
+ }
128
+ const containerRect = container.getBoundingClientRect();
129
+ const homeRect = homeNode.element.getBoundingClientRect();
130
+ const homeTop = anchorTop(homeRect, containerRect);
131
+ const homeBottom = anchorBottom(homeRect, containerRect);
132
+ const next = [];
133
+ const sourceCount = sources.length;
134
+ const deviceCount = devices.length;
135
+ sources.forEach((source, index) => {
136
+ if (!source.element) return;
137
+ const from = anchorBottom(source.element.getBoundingClientRect(), containerRect);
138
+ const offsetX = SOURCE_HOME_OFFSET_STEP_PX * (index - (sourceCount - 1) / 2);
139
+ const to = {
140
+ x: homeTop.x + offsetX,
141
+ y: homeTop.y
142
+ };
143
+ next.push({
144
+ id: source.id,
145
+ d: buildAdaptivePath(from, to),
146
+ kind: "source",
147
+ variant: source.variant,
148
+ reversed: source.watts < 0
149
+ });
150
+ });
151
+ devices.forEach((device, index) => {
152
+ if (!device.element) return;
153
+ const to = anchorTop(device.element.getBoundingClientRect(), containerRect);
154
+ const offsetX = DEVICE_HOME_OFFSET_STEP_PX * (index - (deviceCount - 1) / 2);
155
+ const from = {
156
+ x: homeBottom.x + offsetX,
157
+ y: homeBottom.y
158
+ };
159
+ next.push({
160
+ id: device.id,
161
+ d: buildAdaptivePath(from, to),
162
+ kind: "device",
163
+ reversed: false
164
+ });
165
+ });
166
+ setConnectorPaths((prev) => connectorPathsEqual(prev, next) ? prev : next);
167
+ };
168
+ updatePaths();
169
+ const resizeObserver = new ResizeObserver(updatePaths);
170
+ resizeObserver.observe(container);
171
+ for (const node of nodes.values()) if (node.element) resizeObserver.observe(node.element);
172
+ window.addEventListener("resize", updatePaths);
173
+ return () => {
174
+ resizeObserver.disconnect();
175
+ window.removeEventListener("resize", updatePaths);
176
+ };
177
+ }, [nodes]);
178
+ (0, react.useEffect)(() => {
179
+ connectorPathsRef.current = connectorPaths;
180
+ }, [connectorPaths]);
181
+ (0, react.useEffect)(() => {
182
+ const targets = {};
183
+ const homeWatts = totalSourceWatts;
184
+ for (const node of nodes.values()) {
185
+ if (node.kind === "home") continue;
186
+ const watts = node.watts;
187
+ const relative = homeWatts > 0 ? Math.max(0, Math.min(1, Math.abs(watts) / homeWatts)) : 0;
188
+ targets[node.id] = {
189
+ gap: FLOW_GAP_MAX - (FLOW_GAP_MAX - FLOW_GAP_MIN) * relative,
190
+ speed: FLOW_SPEED_MIN + (FLOW_SPEED_MAX - FLOW_SPEED_MIN) * relative,
191
+ opacity: Math.abs(watts) > 0 ? 1 : 0
192
+ };
193
+ }
194
+ targetFlowVisualsRef.current = targets;
195
+ }, [nodes, totalSourceWatts]);
196
+ (0, react.useEffect)(() => {
197
+ if (loading || reducedMotion) return;
198
+ let frameId = 0;
199
+ let lastFrameTime = performance.now();
200
+ const animate = (now) => {
201
+ const deltaSeconds = Math.max(.001, (now - lastFrameTime) / 1e3);
202
+ lastFrameTime = now;
203
+ const smoothing = 1 - Math.exp(-5.5 * deltaSeconds);
204
+ for (const connectorPath of connectorPathsRef.current) {
205
+ const id = connectorPath.id;
206
+ const target = targetFlowVisualsRef.current[id];
207
+ const pathElement = flowPathRefs.current[id];
208
+ if (!target || !pathElement) continue;
209
+ const current = currentFlowVisualsRef.current[id] ?? {
210
+ gap: target.gap,
211
+ offset: 0,
212
+ speed: target.speed,
213
+ opacity: target.opacity
214
+ };
215
+ current.gap += (target.gap - current.gap) * smoothing;
216
+ current.speed += (target.speed - current.speed) * smoothing;
217
+ current.opacity += (target.opacity - current.opacity) * smoothing;
218
+ const dashCycle = FLOW_DASH_SOLID + Math.max(.001, current.gap);
219
+ const direction = connectorPath.reversed ? 1 : -1;
220
+ current.offset = ((current.offset + direction * current.speed * deltaSeconds) % dashCycle + dashCycle) % dashCycle - dashCycle;
221
+ pathElement.setAttribute("stroke-dasharray", `${FLOW_DASH_SOLID} ${current.gap.toFixed(3)}`);
222
+ pathElement.setAttribute("stroke-dashoffset", current.offset.toFixed(3));
223
+ pathElement.setAttribute("opacity", current.opacity.toFixed(3));
224
+ idlePathRefs.current[id]?.setAttribute("opacity", (1 - current.opacity).toFixed(3));
225
+ currentFlowVisualsRef.current[id] = current;
226
+ }
227
+ frameId = window.requestAnimationFrame(animate);
228
+ };
229
+ frameId = window.requestAnimationFrame(animate);
230
+ return () => window.cancelAnimationFrame(frameId);
231
+ }, [loading, reducedMotion]);
232
+ (0, react.useEffect)(() => {
233
+ if (loading || !reducedMotion) return;
234
+ for (const connectorPath of connectorPathsRef.current) {
235
+ const target = targetFlowVisualsRef.current[connectorPath.id];
236
+ const pathElement = flowPathRefs.current[connectorPath.id];
237
+ if (!target || !pathElement) continue;
238
+ pathElement.setAttribute("stroke-dasharray", `${FLOW_DASH_SOLID} ${target.gap.toFixed(3)}`);
239
+ pathElement.setAttribute("stroke-dashoffset", "0");
240
+ pathElement.setAttribute("opacity", target.opacity.toFixed(3));
241
+ idlePathRefs.current[connectorPath.id]?.setAttribute("opacity", (1 - target.opacity).toFixed(3));
242
+ }
243
+ }, [
244
+ loading,
245
+ reducedMotion,
246
+ nodes,
247
+ totalSourceWatts
248
+ ]);
249
+ const contextValue = (0, react.useMemo)(() => ({
250
+ loading,
251
+ register,
252
+ unregister,
253
+ totalSourceWatts
254
+ }), [
255
+ loading,
256
+ register,
257
+ unregister,
258
+ totalSourceWatts
259
+ ]);
260
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PowerNetworkContext.Provider, {
261
+ value: contextValue,
262
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
263
+ ref: containerRef,
264
+ className: (0, cn.cn)("@container relative flex flex-col", className),
265
+ children: [
266
+ title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_typography.SectionTitle, { children: title }),
267
+ connectorPaths.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
268
+ className: "pointer-events-none absolute inset-0 size-full",
269
+ children: connectorPaths.map((path) => {
270
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
271
+ d: path.d,
272
+ fill: "none",
273
+ stroke: SKELETON_STROKE,
274
+ strokeWidth: STROKE_WIDTH,
275
+ strokeLinecap: "round"
276
+ }, path.id);
277
+ const flowStroke = path.kind === "source" && path.variant ? SOURCE_VARIANTS[path.variant].flowStroke : DEVICE_FLOW_STROKE;
278
+ const initialDashGap = path.kind === "source" && path.variant ? SOURCE_VARIANTS[path.variant].initialDashGap : DEVICE_INITIAL_DASH_GAP;
279
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
280
+ d: path.d,
281
+ fill: "none",
282
+ stroke: IDLE_STROKE,
283
+ strokeWidth: STROKE_WIDTH,
284
+ strokeLinecap: "round",
285
+ ref: (el) => {
286
+ idlePathRefs.current[path.id] = el;
287
+ },
288
+ opacity: 1,
289
+ style: reducedMotion ? { transition: "opacity 300ms ease" } : void 0
290
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
291
+ d: path.d,
292
+ fill: "none",
293
+ stroke: flowStroke,
294
+ strokeWidth: STROKE_WIDTH,
295
+ strokeLinecap: "round",
296
+ ref: (el) => {
297
+ flowPathRefs.current[path.id] = el;
298
+ },
299
+ strokeDasharray: `${FLOW_DASH_SOLID} ${initialDashGap}`,
300
+ strokeDashoffset: 0,
301
+ opacity: 0,
302
+ style: reducedMotion ? { transition: "opacity 300ms ease" } : void 0
303
+ })] }, path.id);
304
+ })
305
+ }),
306
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
307
+ className: "flex flex-col gap-y-14",
308
+ children
309
+ })
310
+ ]
311
+ })
312
+ });
313
+ }
314
+ function PowerNetworkSources({ className, children }) {
315
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
316
+ className: (0, cn.cn)("flex items-center justify-center gap-[clamp(1.5rem,10cqw,4rem)]", className),
317
+ children
318
+ });
319
+ }
320
+ function PowerNetworkDevices({ className, children }) {
321
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
322
+ className: (0, cn.cn)("flex items-center justify-center gap-[clamp(1.5rem,10cqw,4rem)]", className),
323
+ children
324
+ });
325
+ }
326
+ function PowerSource({ id: idProp, watts, icon, variant = "neutral", className, progress, standby = false }) {
327
+ const autoId = (0, react.useId)();
328
+ const id = idProp ?? autoId;
329
+ const { register, unregister, loading } = usePowerNetwork();
330
+ const [element, setElement] = (0, react.useState)(null);
331
+ (0, react.useEffect)(() => {
332
+ if (!element) return;
333
+ register({
334
+ id,
335
+ kind: "source",
336
+ variant,
337
+ watts,
338
+ element
339
+ });
340
+ }, [
341
+ element,
342
+ register,
343
+ id,
344
+ variant,
345
+ watts
346
+ ]);
347
+ (0, react.useEffect)(() => {
348
+ return () => unregister(id);
349
+ }, [unregister, id]);
350
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SourceSkeleton, { anchorRef: setElement });
351
+ const isColored = watts !== 0 || standby;
352
+ const variantStyles = SOURCE_VARIANTS[variant];
353
+ const clampedProgress = progress == null ? null : Math.max(0, Math.min(100, progress));
354
+ const showRing = clampedProgress != null;
355
+ const RING_RADIUS = 48;
356
+ const RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
357
+ const ringDash = clampedProgress != null ? clampedProgress / 100 * RING_CIRCUMFERENCE : 0;
358
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
359
+ className: (0, cn.cn)("flex flex-col items-center justify-center gap-1 transition-opacity duration-500", !isColored && "opacity-45", className),
360
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_number_flow, {
361
+ value: Math.abs(watts),
362
+ suffix: "W",
363
+ className: "mt-1 text-sm leading-none font-medium"
364
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
365
+ ref: setElement,
366
+ className: (0, cn.cn)("relative flex aspect-square items-center justify-center rounded-full border p-4 shadow-sm", isColored ? variantStyles.activeClassName : "border-gray bg-gray-a3 shadow-gray-a4 saturate-0"),
367
+ children: [showRing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
368
+ className: "pointer-events-none absolute inset-0 size-full -rotate-90",
369
+ viewBox: "0 0 100 100",
370
+ "aria-hidden": "true",
371
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
372
+ cx: "50",
373
+ cy: "50",
374
+ r: RING_RADIUS,
375
+ fill: "none",
376
+ stroke: variantStyles.flowStroke,
377
+ strokeWidth: "4",
378
+ strokeLinecap: "round",
379
+ strokeDasharray: `${ringDash} ${RING_CIRCUMFERENCE}`,
380
+ opacity: isColored ? 1 : .5
381
+ })
382
+ }), icon]
383
+ })]
384
+ });
385
+ }
386
+ function SourceSkeleton({ anchorRef }) {
387
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
388
+ className: "flex flex-col items-center justify-center gap-1",
389
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_skeleton.Skeleton, { className: "h-3.5 w-10" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
390
+ ref: anchorRef,
391
+ className: "flex aspect-square animate-pulse items-center justify-center rounded-full border border-gray bg-base-gray-soft p-4 motion-reduce:animate-none",
392
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "size-7" })
393
+ })]
394
+ });
395
+ }
396
+ function PowerDevice({ id: idProp, watts, icon, className }) {
397
+ const autoId = (0, react.useId)();
398
+ const id = idProp ?? autoId;
399
+ const { register, unregister, loading } = usePowerNetwork();
400
+ const [element, setElement] = (0, react.useState)(null);
401
+ (0, react.useEffect)(() => {
402
+ if (!element) return;
403
+ register({
404
+ id,
405
+ kind: "device",
406
+ watts,
407
+ element
408
+ });
409
+ }, [
410
+ element,
411
+ register,
412
+ id,
413
+ watts
414
+ ]);
415
+ (0, react.useEffect)(() => {
416
+ return () => unregister(id);
417
+ }, [unregister, id]);
418
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DeviceSkeleton, { anchorRef: setElement });
419
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
420
+ className: (0, cn.cn)("flex flex-col items-center justify-center gap-1", className),
421
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
422
+ ref: setElement,
423
+ className: "flex aspect-square items-center justify-center rounded-full border border-gray bg-gray-a1 p-4",
424
+ children: icon
425
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_number_flow, {
426
+ value: watts,
427
+ suffix: "W",
428
+ className: "text-sm font-medium tabular-nums"
429
+ })]
430
+ });
431
+ }
432
+ function DeviceSkeleton({ anchorRef }) {
433
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
434
+ className: "flex flex-col items-center justify-center gap-1",
435
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
436
+ ref: anchorRef,
437
+ className: "flex aspect-square animate-pulse items-center justify-center rounded-full border border-gray bg-base-gray-soft p-4 motion-reduce:animate-none",
438
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "size-5" })
439
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_skeleton.Skeleton, { className: "h-3.5 w-10" })]
440
+ });
441
+ }
442
+ function PowerNetworkHome({ watts, autarky, className }) {
443
+ const autoId = (0, react.useId)();
444
+ const { register, unregister, loading, totalSourceWatts } = usePowerNetwork();
445
+ const [element, setElement] = (0, react.useState)(null);
446
+ const effectiveWatts = watts ?? totalSourceWatts;
447
+ (0, react.useEffect)(() => {
448
+ if (!element) return;
449
+ register({
450
+ id: autoId,
451
+ kind: "home",
452
+ watts: effectiveWatts,
453
+ element
454
+ });
455
+ }, [
456
+ element,
457
+ register,
458
+ autoId,
459
+ effectiveWatts
460
+ ]);
461
+ (0, react.useEffect)(() => {
462
+ return () => unregister(autoId);
463
+ }, [unregister, autoId]);
464
+ if (loading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HomeSkeleton, {
465
+ anchorRef: setElement,
466
+ className
467
+ });
468
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HomeSection, {
469
+ anchorRef: setElement,
470
+ autarky: autarky ?? null,
471
+ watts: effectiveWatts,
472
+ className
473
+ });
474
+ }
475
+ function HomeSection({ anchorRef, autarky, className, watts }) {
476
+ const reducedMotion = require_hooks_use_reduced_motion.useReducedMotion();
477
+ const targetAutarky = autarky == null ? 0 : Math.max(0, Math.min(100, autarky * 100));
478
+ const [animatedAutarky, setAnimatedAutarky] = (0, react.useState)(targetAutarky);
479
+ const animatedAutarkyRef = (0, react.useRef)(animatedAutarky);
480
+ const nodeSize = 78;
481
+ const ringStrokeWidth = 2;
482
+ const radius = (nodeSize - ringStrokeWidth) / 2;
483
+ const circumference = 2 * Math.PI * radius;
484
+ const progressLength = animatedAutarky / 100 * circumference;
485
+ (0, react.useEffect)(() => {
486
+ animatedAutarkyRef.current = animatedAutarky;
487
+ }, [animatedAutarky]);
488
+ (0, react.useEffect)(() => {
489
+ if (reducedMotion) {
490
+ animatedAutarkyRef.current = targetAutarky;
491
+ setAnimatedAutarky(targetAutarky);
492
+ return;
493
+ }
494
+ let frameId = 0;
495
+ let lastTime = performance.now();
496
+ const animate = (now) => {
497
+ const deltaSeconds = Math.max(.001, (now - lastTime) / 1e3);
498
+ lastTime = now;
499
+ const smoothing = 1 - Math.exp(-7 * deltaSeconds);
500
+ const next = animatedAutarkyRef.current + (targetAutarky - animatedAutarkyRef.current) * smoothing;
501
+ animatedAutarkyRef.current = next;
502
+ setAnimatedAutarky(next);
503
+ if (Math.abs(targetAutarky - next) > .05) frameId = window.requestAnimationFrame(animate);
504
+ else {
505
+ animatedAutarkyRef.current = targetAutarky;
506
+ setAnimatedAutarky(targetAutarky);
507
+ }
508
+ };
509
+ frameId = window.requestAnimationFrame(animate);
510
+ return () => window.cancelAnimationFrame(frameId);
511
+ }, [targetAutarky, reducedMotion]);
512
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
513
+ ref: anchorRef,
514
+ className: (0, cn.cn)("relative mx-auto size-[78px]", className),
515
+ children: [autarky != null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
516
+ className: "absolute inset-0 size-full -rotate-90",
517
+ viewBox: `0 0 ${nodeSize} ${nodeSize}`,
518
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
519
+ cx: nodeSize / 2,
520
+ cy: nodeSize / 2,
521
+ r: radius,
522
+ fill: "none",
523
+ stroke: "var(--neutral-a5)",
524
+ strokeWidth: ringStrokeWidth
525
+ }), progressLength > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
526
+ cx: nodeSize / 2,
527
+ cy: nodeSize / 2,
528
+ r: radius,
529
+ fill: "none",
530
+ stroke: "var(--green-a9)",
531
+ strokeLinecap: "round",
532
+ strokeWidth: ringStrokeWidth,
533
+ strokeDasharray: `${progressLength} ${circumference}`
534
+ })]
535
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
536
+ className: "absolute inset-0 flex flex-col items-center justify-center rounded-full border border-gray-a5 shadow-sm shadow-gray-a1",
537
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_epilot_spark_icons.Home, { className: "[--spark-icon-size:14px]" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_components_number_flow, {
538
+ value: watts,
539
+ suffix: "W",
540
+ className: "mt-1 text-sm leading-none font-medium"
541
+ })]
542
+ })]
543
+ });
544
+ }
545
+ function HomeSkeleton({ anchorRef, className }) {
546
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
547
+ ref: anchorRef,
548
+ className: (0, cn.cn)("mx-auto size-[78px] animate-pulse rounded-full border border-gray motion-reduce:animate-none", className)
549
+ });
550
+ }
551
+ //#endregion
552
+ exports.PowerDevice = PowerDevice;
553
+ exports.PowerNetworkDevices = PowerNetworkDevices;
554
+ exports.PowerNetworkDiagram = PowerNetworkDiagram;
555
+ exports.PowerNetworkHome = PowerNetworkHome;
556
+ exports.PowerNetworkSources = PowerNetworkSources;
557
+ exports.PowerSource = PowerSource;
558
+
559
+ //# sourceMappingURL=power-network.cjs.map