@gunkin/uiify 0.0.3-alpha → 0.0.4-alpha

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 (44) hide show
  1. package/dist/components/candle-chart/CandleChart.d.ts +10 -1
  2. package/dist/components/candle-chart/CandleChart.d.ts.map +1 -1
  3. package/dist/components/candle-chart/CandleChart.js +82 -42
  4. package/dist/components/candle-chart/CandleChart.js.map +1 -1
  5. package/dist/components/candle-chart/client/CandleChartClient.d.ts +2 -2
  6. package/dist/components/candle-chart/client/CandleChartClient.d.ts.map +1 -1
  7. package/dist/components/candle-chart/client/CandleChartClient.js +103 -14
  8. package/dist/components/candle-chart/client/CandleChartClient.js.map +1 -1
  9. package/dist/components/candle-chart/client/index.d.ts +1 -0
  10. package/dist/components/candle-chart/client/index.d.ts.map +1 -1
  11. package/dist/components/candle-chart/client/index.js.map +1 -1
  12. package/dist/components/candle-chart/types.d.ts +39 -0
  13. package/dist/components/candle-chart/types.d.ts.map +1 -1
  14. package/dist/components/internal/chart-scrollbar/use-scrollbar-reserve.d.ts +11 -0
  15. package/dist/components/internal/chart-scrollbar/use-scrollbar-reserve.d.ts.map +1 -0
  16. package/dist/components/internal/chart-scrollbar/use-scrollbar-reserve.js +21 -0
  17. package/dist/components/internal/chart-scrollbar/use-scrollbar-reserve.js.map +1 -0
  18. package/dist/components/internal/live-chart/types.d.ts +36 -0
  19. package/dist/components/internal/live-chart/types.d.ts.map +1 -0
  20. package/dist/components/internal/live-chart/types.js +1 -0
  21. package/dist/components/internal/live-chart/types.js.map +1 -0
  22. package/dist/components/internal/live-chart/use-live-chart-window.d.ts +18 -0
  23. package/dist/components/internal/live-chart/use-live-chart-window.d.ts.map +1 -0
  24. package/dist/components/internal/live-chart/use-live-chart-window.js +75 -0
  25. package/dist/components/internal/live-chart/use-live-chart-window.js.map +1 -0
  26. package/dist/components/line-chart/LineChart.d.ts +6 -1
  27. package/dist/components/line-chart/LineChart.d.ts.map +1 -1
  28. package/dist/components/line-chart/LineChart.js +82 -48
  29. package/dist/components/line-chart/LineChart.js.map +1 -1
  30. package/dist/components/line-chart/client/LineChartClient.d.ts +2 -2
  31. package/dist/components/line-chart/client/LineChartClient.d.ts.map +1 -1
  32. package/dist/components/line-chart/client/LineChartClient.js +106 -14
  33. package/dist/components/line-chart/client/LineChartClient.js.map +1 -1
  34. package/dist/components/line-chart/client/index.d.ts +1 -1
  35. package/dist/components/line-chart/client/index.d.ts.map +1 -1
  36. package/dist/components/line-chart/client/index.js.map +1 -1
  37. package/dist/components/line-chart/line-chart-geometry.d.ts +7 -0
  38. package/dist/components/line-chart/line-chart-geometry.d.ts.map +1 -1
  39. package/dist/components/line-chart/line-chart-geometry.js +14 -2
  40. package/dist/components/line-chart/line-chart-geometry.js.map +1 -1
  41. package/dist/components/line-chart/types.d.ts +42 -0
  42. package/dist/components/line-chart/types.d.ts.map +1 -1
  43. package/dist/styles/index.css +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/internal/chart-scrollbar/use-scrollbar-reserve.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, type RefObject } from \"react\";\n\n/**\n * Publishes the horizontal scrollbar thickness of a chart's\n * `[data-part=\"scroll-viewport\"]` as `variable` on the chart element, so the\n * plot grid can reserve that height below the time axis. A classic scrollbar\n * otherwise takes its height from the scroll viewport, and the flexible\n * candle row absorbs it, misaligning the value axis. Overlay scrollbars\n * measure 0. Read from ResizeObserver box sizes: no forced layout.\n */\nexport function useScrollbarReserve(\n chartRef: RefObject<HTMLElement | null>,\n variable: `--${string}`,\n): void {\n useEffect(() => {\n const chart = chartRef.current;\n const scroller = chart?.querySelector<HTMLElement>('[data-part=\"scroll-viewport\"]');\n if (!chart || !scroller || typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n const border = entry?.borderBoxSize[0]?.blockSize;\n const content = entry?.contentBoxSize[0]?.blockSize;\n if (border === undefined || content === undefined) return;\n // The scroll viewport has no padding or border, so the difference is the scrollbar.\n chart.style.setProperty(variable, `${Math.max(0, border - content)}px`);\n });\n observer.observe(scroller);\n return () => observer.disconnect();\n }, [chartRef, variable]);\n}\n"],"mappings":";AAEA,SAAS,iBAAiC;AAUnC,SAAS,oBACd,UACA,UACM;AACN,YAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,UAAM,WAAW,OAAO,cAA2B,+BAA+B;AAClF,QAAI,CAAC,SAAS,CAAC,YAAY,OAAO,mBAAmB,YAAa;AAClE,UAAM,WAAW,IAAI,eAAe,CAAC,CAAC,KAAK,MAAM;AAC/C,YAAM,SAAS,OAAO,cAAc,CAAC,GAAG;AACxC,YAAM,UAAU,OAAO,eAAe,CAAC,GAAG;AAC1C,UAAI,WAAW,UAAa,YAAY,OAAW;AAEnD,YAAM,MAAM,YAAY,UAAU,GAAG,KAAK,IAAI,GAAG,SAAS,OAAO,CAAC,IAAI;AAAA,IACxE,CAAC;AACD,aAAS,QAAQ,QAAQ;AACzB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,UAAU,QAAQ,CAAC;AACzB;","names":[]}
@@ -0,0 +1,36 @@
1
+ import type { ReactNode, RefObject } from "react";
2
+ import type { VirtualScrollHandle, VirtualScrollRangeChange } from "../../virtual-scroll/types.js";
3
+ export interface LiveChartAxisLabel {
4
+ readonly label: ReactNode;
5
+ /** Position across the whole scrolled content, as a percentage from 0 to 100. */
6
+ readonly position: number;
7
+ }
8
+ export interface UseLiveChartWindowOptions {
9
+ /** Total number of items (candles or points) in the chart. */
10
+ readonly count: number;
11
+ /** Items that fill the viewport; `undefined` or `0` turns live mode off. */
12
+ readonly visibleCount: number | undefined;
13
+ /** Measured width of one item slot, in CSS pixels. */
14
+ readonly itemWidth: number;
15
+ /** Measured content height of the chart `<figure>`, once known. */
16
+ readonly figureHeight: number | undefined;
17
+ /** Custom property that receives the scroll offset, e.g. `--candle-scroll-x`. */
18
+ readonly scrollVariable: `--${string}`;
19
+ }
20
+ export interface UseLiveChartWindowResult {
21
+ readonly live: boolean;
22
+ /** Pass to the chart's VirtualScroll `scrollRef`. */
23
+ readonly scrollRef: RefObject<VirtualScrollHandle | null>;
24
+ /** Merge into the chart `<figure>` ref; the scroll offset variable is written there. */
25
+ readonly chartRef: RefObject<HTMLElement | null>;
26
+ /** Pass to the chart's VirtualScroll `onRangeChange`. */
27
+ readonly onRangeChange: (range: VirtualScrollRangeChange) => void;
28
+ /** Visible slice `[sliceStart, sliceEnd)` that drives the scale, readout, and table. */
29
+ readonly sliceStart: number;
30
+ readonly sliceEnd: number;
31
+ /** Width of all item slots, in CSS pixels. */
32
+ readonly contentSize: number;
33
+ /** Block size of the item viewport, in CSS pixels. */
34
+ readonly plotHeight: number;
35
+ }
36
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/components/internal/live-chart/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEnG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,yBAAyB;IACxC,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,iFAAiF;IACjF,QAAQ,CAAC,cAAc,EAAE,KAAK,MAAM,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,qDAAqD;IACrD,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IAC1D,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACjD,yDAAyD;IACzD,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,wBAAwB,KAAK,IAAI,CAAC;IAClE,wFAAwF;IACxF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,18 @@
1
+ import type { LiveChartAxisLabel, UseLiveChartWindowOptions, UseLiveChartWindowResult } from "./types.js";
2
+ /**
3
+ * Time ticks pinned to absolute item indices and positioned at slot centers
4
+ * across the whole scrolled content, so labels scroll with their items. Only
5
+ * ticks near the visible slice are returned.
6
+ */
7
+ export declare function liveTimeAxis(data: readonly {
8
+ readonly time: string | number;
9
+ }[], start: number, end: number, visibleCount: number): LiveChartAxisLabel[];
10
+ /**
11
+ * Live chart window over a horizontal VirtualScroll: follows appends while the
12
+ * reader is at the newest item, leaves a scrolled-back reader in place, and
13
+ * reports the visible slice. The chart pins the scroll position itself rather
14
+ * than using VirtualScroll's `anchor`, and publishes the offset as a CSS
15
+ * variable so axes and crosshairs scroll without re-rendering.
16
+ */
17
+ export declare function useLiveChartWindow(options: UseLiveChartWindowOptions): UseLiveChartWindowResult;
18
+ //# sourceMappingURL=use-live-chart-window.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-live-chart-window.d.ts","sourceRoot":"","sources":["../../../../src/components/internal/live-chart/use-live-chart-window.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAClB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAQpB;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,EAAE,EACnD,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,GACnB,kBAAkB,EAAE,CAYtB;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAqE/F"}
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { useIsomorphicLayoutEffect } from "../../../hooks/use-isomorphic-layout-effect.js";
4
+ const TIME_AXIS_ROW_HEIGHT = 24;
5
+ const MIN_PLOT_HEIGHT = 288;
6
+ const LIVE_TIME_TICKS = 4;
7
+ function liveTimeAxis(data, start, end, visibleCount) {
8
+ const step = Math.max(1, Math.ceil(visibleCount / LIVE_TIME_TICKS));
9
+ const labels = [];
10
+ const first = Math.max(0, Math.floor(start / step) - 1) * step;
11
+ const last = Math.min(data.length, end + step);
12
+ for (let index = first; index < last; index += step) {
13
+ labels.push({
14
+ label: String(data[index]?.time),
15
+ position: (index + 0.5) / data.length * 100
16
+ });
17
+ }
18
+ return labels;
19
+ }
20
+ function useLiveChartWindow(options) {
21
+ const { count, visibleCount = 0, itemWidth, figureHeight, scrollVariable } = options;
22
+ const live = visibleCount > 0;
23
+ const scrollRef = useRef(null);
24
+ const chartRef = useRef(null);
25
+ const [visibleRange, setVisibleRange] = useState(null);
26
+ const [following, setFollowing] = useState(true);
27
+ const contentSize = count * itemWidth;
28
+ const viewportWidth = itemWidth * visibleCount;
29
+ const layoutRef = useRef({ contentSize, itemWidth, viewportWidth });
30
+ layoutRef.current = { contentSize, itemWidth, viewportWidth };
31
+ useEffect(() => {
32
+ if (!live) return;
33
+ const element = scrollRef.current?.element;
34
+ const chart = chartRef.current;
35
+ if (!element || !chart) return;
36
+ const onScroll = () => {
37
+ const offset = element.scrollLeft;
38
+ chart.style.setProperty(scrollVariable, `${offset}px`);
39
+ const layout = layoutRef.current;
40
+ setFollowing(
41
+ offset + layout.viewportWidth >= layout.contentSize - Math.max(1, layout.itemWidth)
42
+ );
43
+ };
44
+ element.addEventListener("scroll", onScroll, { passive: true });
45
+ return () => element.removeEventListener("scroll", onScroll);
46
+ }, [live, scrollVariable]);
47
+ useIsomorphicLayoutEffect(() => {
48
+ if (!live || !following) return;
49
+ const element = scrollRef.current?.element;
50
+ const chart = chartRef.current;
51
+ if (!element || !chart) return;
52
+ const end = Math.max(0, contentSize - viewportWidth);
53
+ element.scrollLeft = end;
54
+ chart.style.setProperty(scrollVariable, `${end}px`);
55
+ }, [live, following, contentSize, viewportWidth, scrollVariable]);
56
+ const tail = following || !visibleRange;
57
+ const sliceStart = tail ? Math.max(0, count - visibleCount) : Math.min(visibleRange.startIndex, Math.max(0, count - 1));
58
+ const sliceEnd = tail ? count : Math.min(count, Math.max(sliceStart + 1, visibleRange.endIndex + 1));
59
+ const plotHeight = figureHeight === void 0 ? MIN_PLOT_HEIGHT : Math.max(MIN_PLOT_HEIGHT, figureHeight - TIME_AXIS_ROW_HEIGHT);
60
+ return {
61
+ live,
62
+ scrollRef,
63
+ chartRef,
64
+ onRangeChange: setVisibleRange,
65
+ sliceStart,
66
+ sliceEnd,
67
+ contentSize,
68
+ plotHeight
69
+ };
70
+ }
71
+ export {
72
+ liveTimeAxis,
73
+ useLiveChartWindow
74
+ };
75
+ //# sourceMappingURL=use-live-chart-window.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/components/internal/live-chart/use-live-chart-window.ts"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../../../hooks/use-isomorphic-layout-effect.js\";\nimport type { VirtualScrollHandle, VirtualScrollRangeChange } from \"../../virtual-scroll/types.js\";\nimport type {\n LiveChartAxisLabel,\n UseLiveChartWindowOptions,\n UseLiveChartWindowResult,\n} from \"./types.js\";\n\n/** The plot's 1.5rem time-axis row below the item viewport (candle-chart.css, line-chart.css). */\nconst TIME_AXIS_ROW_HEIGHT = 24;\n/** The item viewport's 18rem minimum block size, used until the figure is measured. */\nconst MIN_PLOT_HEIGHT = 288;\nconst LIVE_TIME_TICKS = 4;\n\n/**\n * Time ticks pinned to absolute item indices and positioned at slot centers\n * across the whole scrolled content, so labels scroll with their items. Only\n * ticks near the visible slice are returned.\n */\nexport function liveTimeAxis(\n data: readonly { readonly time: string | number }[],\n start: number,\n end: number,\n visibleCount: number,\n): LiveChartAxisLabel[] {\n const step = Math.max(1, Math.ceil(visibleCount / LIVE_TIME_TICKS));\n const labels: LiveChartAxisLabel[] = [];\n const first = Math.max(0, Math.floor(start / step) - 1) * step;\n const last = Math.min(data.length, end + step);\n for (let index = first; index < last; index += step) {\n labels.push({\n label: String(data[index]?.time),\n position: ((index + 0.5) / data.length) * 100,\n });\n }\n return labels;\n}\n\n/**\n * Live chart window over a horizontal VirtualScroll: follows appends while the\n * reader is at the newest item, leaves a scrolled-back reader in place, and\n * reports the visible slice. The chart pins the scroll position itself rather\n * than using VirtualScroll's `anchor`, and publishes the offset as a CSS\n * variable so axes and crosshairs scroll without re-rendering.\n */\nexport function useLiveChartWindow(options: UseLiveChartWindowOptions): UseLiveChartWindowResult {\n const { count, visibleCount = 0, itemWidth, figureHeight, scrollVariable } = options;\n const live = visibleCount > 0;\n const scrollRef = useRef<VirtualScrollHandle>(null);\n const chartRef = useRef<HTMLElement | null>(null);\n const [visibleRange, setVisibleRange] = useState<VirtualScrollRangeChange | null>(null);\n // True while the reader sits at the newest item; appends then keep it in view.\n const [following, setFollowing] = useState(true);\n const contentSize = count * itemWidth;\n const viewportWidth = itemWidth * visibleCount;\n const layoutRef = useRef({ contentSize, itemWidth, viewportWidth });\n layoutRef.current = { contentSize, itemWidth, viewportWidth };\n\n // Publish the scroll offset (a CSS variable, so scrolling re-renders nothing)\n // and track whether the reader is at the end.\n useEffect(() => {\n if (!live) return;\n const element = scrollRef.current?.element;\n const chart = chartRef.current;\n if (!element || !chart) return;\n const onScroll = (): void => {\n const offset = element.scrollLeft; // banned-read-ok: scroll position drives the axis offset.\n chart.style.setProperty(scrollVariable, `${offset}px`);\n const layout = layoutRef.current;\n // Within one item of the end counts: an item appended while the reader\n // scrolls there moves the end by exactly one item width.\n setFollowing(\n offset + layout.viewportWidth >= layout.contentSize - Math.max(1, layout.itemWidth),\n );\n };\n element.addEventListener(\"scroll\", onScroll, { passive: true });\n return () => element.removeEventListener(\"scroll\", onScroll);\n }, [live, scrollVariable]);\n\n // Keep the newest item in view while following: on appends, resizes, and once measured.\n useIsomorphicLayoutEffect(() => {\n if (!live || !following) return;\n const element = scrollRef.current?.element;\n const chart = chartRef.current;\n if (!element || !chart) return;\n const end = Math.max(0, contentSize - viewportWidth);\n element.scrollLeft = end; // banned-read-ok: pin the newest item before paint.\n chart.style.setProperty(scrollVariable, `${end}px`);\n }, [live, following, contentSize, viewportWidth, scrollVariable]);\n\n // While following, the slice is the tail, so an append rescales in the same\n // commit that adds the item.\n const tail = following || !visibleRange;\n const sliceStart = tail\n ? Math.max(0, count - visibleCount)\n : Math.min(visibleRange.startIndex, Math.max(0, count - 1));\n const sliceEnd = tail\n ? count\n : Math.min(count, Math.max(sliceStart + 1, visibleRange.endIndex + 1));\n const plotHeight =\n figureHeight === undefined\n ? MIN_PLOT_HEIGHT\n : Math.max(MIN_PLOT_HEIGHT, figureHeight - TIME_AXIS_ROW_HEIGHT);\n\n return {\n live,\n scrollRef,\n chartRef,\n onRangeChange: setVisibleRange,\n sliceStart,\n sliceEnd,\n contentSize,\n plotHeight,\n };\n}\n"],"mappings":";AAEA,SAAS,WAAW,QAAQ,gBAAgB;AAC5C,SAAS,iCAAiC;AAS1C,MAAM,uBAAuB;AAE7B,MAAM,kBAAkB;AACxB,MAAM,kBAAkB;AAOjB,SAAS,aACd,MACA,OACA,KACA,cACsB;AACtB,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,KAAK,eAAe,eAAe,CAAC;AAClE,QAAM,SAA+B,CAAC;AACtC,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,IAAI;AAC1D,QAAM,OAAO,KAAK,IAAI,KAAK,QAAQ,MAAM,IAAI;AAC7C,WAAS,QAAQ,OAAO,QAAQ,MAAM,SAAS,MAAM;AACnD,WAAO,KAAK;AAAA,MACV,OAAO,OAAO,KAAK,KAAK,GAAG,IAAI;AAAA,MAC/B,WAAY,QAAQ,OAAO,KAAK,SAAU;AAAA,IAC5C,CAAC;AAAA,EACH;AACA,SAAO;AACT;AASO,SAAS,mBAAmB,SAA8D;AAC/F,QAAM,EAAE,OAAO,eAAe,GAAG,WAAW,cAAc,eAAe,IAAI;AAC7E,QAAM,OAAO,eAAe;AAC5B,QAAM,YAAY,OAA4B,IAAI;AAClD,QAAM,WAAW,OAA2B,IAAI;AAChD,QAAM,CAAC,cAAc,eAAe,IAAI,SAA0C,IAAI;AAEtF,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,cAAc,QAAQ;AAC5B,QAAM,gBAAgB,YAAY;AAClC,QAAM,YAAY,OAAO,EAAE,aAAa,WAAW,cAAc,CAAC;AAClE,YAAU,UAAU,EAAE,aAAa,WAAW,cAAc;AAI5D,YAAU,MAAM;AACd,QAAI,CAAC,KAAM;AACX,UAAM,UAAU,UAAU,SAAS;AACnC,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,WAAW,CAAC,MAAO;AACxB,UAAM,WAAW,MAAY;AAC3B,YAAM,SAAS,QAAQ;AACvB,YAAM,MAAM,YAAY,gBAAgB,GAAG,MAAM,IAAI;AACrD,YAAM,SAAS,UAAU;AAGzB;AAAA,QACE,SAAS,OAAO,iBAAiB,OAAO,cAAc,KAAK,IAAI,GAAG,OAAO,SAAS;AAAA,MACpF;AAAA,IACF;AACA,YAAQ,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;AAC9D,WAAO,MAAM,QAAQ,oBAAoB,UAAU,QAAQ;AAAA,EAC7D,GAAG,CAAC,MAAM,cAAc,CAAC;AAGzB,4BAA0B,MAAM;AAC9B,QAAI,CAAC,QAAQ,CAAC,UAAW;AACzB,UAAM,UAAU,UAAU,SAAS;AACnC,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,WAAW,CAAC,MAAO;AACxB,UAAM,MAAM,KAAK,IAAI,GAAG,cAAc,aAAa;AACnD,YAAQ,aAAa;AACrB,UAAM,MAAM,YAAY,gBAAgB,GAAG,GAAG,IAAI;AAAA,EACpD,GAAG,CAAC,MAAM,WAAW,aAAa,eAAe,cAAc,CAAC;AAIhE,QAAM,OAAO,aAAa,CAAC;AAC3B,QAAM,aAAa,OACf,KAAK,IAAI,GAAG,QAAQ,YAAY,IAChC,KAAK,IAAI,aAAa,YAAY,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC;AAC5D,QAAM,WAAW,OACb,QACA,KAAK,IAAI,OAAO,KAAK,IAAI,aAAa,GAAG,aAAa,WAAW,CAAC,CAAC;AACvE,QAAM,aACJ,iBAAiB,SACb,kBACA,KAAK,IAAI,iBAAiB,eAAe,oBAAoB;AAEnE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -1,5 +1,10 @@
1
1
  import type { ElementType, ReactElement } from "react";
2
- import type { LineChartProps } from "./types.js";
2
+ import type { LineChartPointProps, LineChartProps } from "./types.js";
3
+ /** One point and the segment from its predecessor; shared by the static list and virtual rows. */
4
+ export declare function LineChartPoint({ as: Tag, point, previousPoint, index, count, min, max, interactive, isActive, isSelected, cursorY, onPointClick, onPointPointerLeave, onPointPointerMove, }: LineChartPointProps): ReactElement;
5
+ export declare namespace LineChartPoint {
6
+ var displayName: string;
7
+ }
3
8
  export declare function LineChart<TAs extends ElementType = "figure">(props: LineChartProps<TAs>): ReactElement | null;
4
9
  export declare namespace LineChart {
5
10
  var displayName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"LineChart.d.ts","sourceRoot":"","sources":["../../../src/components/line-chart/LineChart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA+B,YAAY,EAAE,MAAM,OAAO,CAAC;AAcpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,wBAAgB,SAAS,CAAC,GAAG,SAAS,WAAW,GAAG,QAAQ,EAC1D,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,GACzB,YAAY,GAAG,IAAI,CAiKrB;yBAnKe,SAAS"}
1
+ {"version":3,"file":"LineChart.d.ts","sourceRoot":"","sources":["../../../src/components/line-chart/LineChart.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAA+B,YAAY,EAAE,MAAM,OAAO,CAAC;AAepF,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAsB,MAAM,YAAY,CAAC;AAE1F,kGAAkG;AAClG,wBAAgB,cAAc,CAAC,EAC7B,EAAE,EAAE,GAAG,EACP,KAAK,EACL,aAAa,EACb,KAAK,EACL,KAAK,EACL,GAAG,EACH,GAAG,EACH,WAAW,EACX,QAAQ,EACR,UAAU,EACV,OAAO,EACP,YAAY,EACZ,mBAAmB,EACnB,kBAAkB,GACnB,EAAE,mBAAmB,GAAG,YAAY,CAmCpC;yBAlDe,cAAc;;;AAqD9B,wBAAgB,SAAS,CAAC,GAAG,SAAS,WAAW,GAAG,QAAQ,EAC1D,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,GACzB,YAAY,GAAG,IAAI,CAqJrB;yBAvJe,SAAS"}
@@ -10,8 +10,59 @@ import {
10
10
  monthLabelsFor,
11
11
  pointStyle,
12
12
  pointsStyle,
13
+ slotCrosshairStyle,
13
14
  valueLabelsFor
14
15
  } from "./line-chart-geometry.js";
16
+ function LineChartPoint({
17
+ as: Tag,
18
+ point,
19
+ previousPoint,
20
+ index,
21
+ count,
22
+ min,
23
+ max,
24
+ interactive,
25
+ isActive,
26
+ isSelected,
27
+ cursorY,
28
+ onPointClick,
29
+ onPointPointerLeave,
30
+ onPointPointerMove
31
+ }) {
32
+ const geometry = geometryFor(point, previousPoint, index, count, min, max);
33
+ const handleKeyDown = (event) => {
34
+ if (event.key !== "Enter" && event.key !== " ") return;
35
+ event.preventDefault();
36
+ onPointClick?.(index);
37
+ };
38
+ return /* @__PURE__ */ jsx(
39
+ Tag,
40
+ {
41
+ "aria-hidden": interactive ? void 0 : true,
42
+ "data-part": "point",
43
+ "data-first": previousPoint === void 0 ? "" : void 0,
44
+ "data-crosshair-active": interactive && isActive ? "" : void 0,
45
+ "data-point-selected": interactive && isSelected ? "" : void 0,
46
+ "data-time": String(point.time),
47
+ "data-trend": geometry.trend,
48
+ style: pointStyle(geometry, isActive ? cursorY : void 0),
49
+ children: interactive ? /* @__PURE__ */ jsx(
50
+ "button",
51
+ {
52
+ "data-part": "point-hit",
53
+ type: "button",
54
+ "aria-label": `Point ${String(point.time)}`,
55
+ "aria-pressed": isSelected,
56
+ onClick: () => onPointClick?.(index),
57
+ onKeyDown: handleKeyDown,
58
+ onPointerLeave: onPointPointerLeave,
59
+ onPointerMove: (event) => onPointPointerMove?.({ index, offsetY: event.nativeEvent.offsetY })
60
+ }
61
+ ) : null
62
+ }
63
+ );
64
+ }
65
+ LineChartPoint.displayName = "LineChartPoint";
15
66
  function LineChart(props) {
16
67
  const {
17
68
  as,
@@ -27,6 +78,10 @@ function LineChart(props) {
27
78
  onPointPointerLeave,
28
79
  onPointPointerMove,
29
80
  selectedPointIndex,
81
+ pointLayer,
82
+ announceReadout = true,
83
+ slotCount,
84
+ slotOffset = 0,
30
85
  ...consumerProps
31
86
  } = props;
32
87
  const { min, max } = getValueBounds(data);
@@ -36,41 +91,27 @@ function LineChart(props) {
36
91
  const readoutIndex = activePointIndex ?? selectedPointIndex ?? (data.length > 0 ? data.length - 1 : void 0);
37
92
  const readoutPoint = readoutIndex === void 0 ? void 0 : data[readoutIndex];
38
93
  const crosshairPoint = activePointIndex === void 0 ? void 0 : data[activePointIndex];
39
- const points = data.map((point, index) => {
40
- const geometry = geometryFor(point, data[index - 1], index, data.length, min, max);
41
- const isActive = activePointIndex === index;
42
- const isSelected = selectedPointIndex === index;
43
- const handleKeyDown = (event) => {
44
- if (event.key !== "Enter" && event.key !== " ") return;
45
- event.preventDefault();
46
- onPointClick?.(index);
47
- };
48
- return /* @__PURE__ */ jsx(
49
- "li",
50
- {
51
- "aria-hidden": interactive ? void 0 : true,
52
- "data-crosshair-active": interactive && isActive ? "" : void 0,
53
- "data-point-selected": interactive && isSelected ? "" : void 0,
54
- "data-time": String(point.time),
55
- "data-trend": geometry.trend,
56
- style: pointStyle(geometry, isActive ? cursorY : void 0),
57
- children: interactive ? /* @__PURE__ */ jsx(
58
- "button",
59
- {
60
- "data-part": "point-hit",
61
- type: "button",
62
- "aria-label": `Point ${String(point.time)}`,
63
- "aria-pressed": isSelected,
64
- onClick: () => onPointClick?.(index),
65
- onKeyDown: handleKeyDown,
66
- onPointerLeave: onPointPointerLeave,
67
- onPointerMove: (event) => onPointPointerMove?.({ index, offsetY: event.nativeEvent.offsetY })
68
- }
69
- ) : null
70
- },
71
- `${point.time}-${index}`
72
- );
73
- });
94
+ const points = pointLayer ?? /* @__PURE__ */ jsx("ol", { "data-part": "points", "aria-label": `${label} points`, style: pointsStyle(data.length), children: data.map((point, index) => /* @__PURE__ */ jsx(
95
+ LineChartPoint,
96
+ {
97
+ as: "li",
98
+ point,
99
+ previousPoint: data[index - 1],
100
+ index,
101
+ count: data.length,
102
+ min,
103
+ max,
104
+ interactive,
105
+ isActive: activePointIndex === index,
106
+ isSelected: selectedPointIndex === index,
107
+ cursorY,
108
+ onPointClick,
109
+ onPointPointerLeave,
110
+ onPointPointerMove
111
+ },
112
+ `${point.time}-${index}`
113
+ )) });
114
+ const crosshairFor = (index, y) => slotCount === void 0 ? crosshairStyle(index, data.length, y) : slotCrosshairStyle(slotOffset + index, slotCount, y);
74
115
  return useRenderElement({
75
116
  as,
76
117
  defaultTag: "figure",
@@ -81,28 +122,20 @@ function LineChart(props) {
81
122
  "data-interactive": interactive ? "" : void 0,
82
123
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
83
124
  /* @__PURE__ */ jsx("figcaption", { children: label }),
84
- interactive && readoutPoint ? /* @__PURE__ */ jsxs("div", { "data-part": "readout", "aria-live": "polite", children: [
125
+ interactive && readoutPoint ? /* @__PURE__ */ jsxs("div", { "data-part": "readout", "aria-live": announceReadout ? "polite" : "off", children: [
85
126
  /* @__PURE__ */ jsx("span", { "data-part": "readout-time", children: String(readoutPoint.time) }),
86
127
  /* @__PURE__ */ jsx("span", { children: readoutPoint.value })
87
128
  ] }) : null,
88
129
  /* @__PURE__ */ jsxs("div", { "data-part": "plot", "aria-hidden": interactive ? void 0 : true, children: [
89
130
  /* @__PURE__ */ jsx("div", { "data-part": "scroll-viewport", children: /* @__PURE__ */ jsxs("div", { "data-part": "canvas", children: [
90
131
  /* @__PURE__ */ jsxs("div", { "data-part": "viewport", children: [
91
- /* @__PURE__ */ jsx(
92
- "ol",
93
- {
94
- "data-part": "points",
95
- "aria-label": `${label} points`,
96
- style: pointsStyle(data.length),
97
- children: points
98
- }
99
- ),
132
+ points,
100
133
  interactive && crosshairPoint && activePointIndex !== void 0 ? /* @__PURE__ */ jsx(
101
134
  "div",
102
135
  {
103
136
  "data-part": "crosshair",
104
137
  "aria-hidden": "true",
105
- style: crosshairStyle(activePointIndex, data.length, cursorY),
138
+ style: crosshairFor(activePointIndex, cursorY),
106
139
  children: /* @__PURE__ */ jsx("span", { "data-part": "crosshair-marker", children: "+" })
107
140
  }
108
141
  ) : null
@@ -120,7 +153,7 @@ function LineChart(props) {
120
153
  "li",
121
154
  {
122
155
  "data-part": "crosshair-time",
123
- style: crosshairStyle(activePointIndex, data.length, void 0),
156
+ style: crosshairFor(activePointIndex, void 0),
124
157
  children: String(crosshairPoint.time)
125
158
  }
126
159
  ) : null
@@ -156,6 +189,7 @@ function LineChart(props) {
156
189
  }
157
190
  LineChart.displayName = "LineChart";
158
191
  export {
159
- LineChart
192
+ LineChart,
193
+ LineChartPoint
160
194
  };
161
195
  //# sourceMappingURL=LineChart.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/line-chart/LineChart.tsx"],"sourcesContent":["import type { ElementType, KeyboardEvent, PointerEvent, ReactElement } from \"react\";\nimport { useRenderElement } from \"@gunkin/uiify/core/render\";\nimport {\n axisLabelsFor,\n axisStyle,\n crosshairStyle,\n cursorStyle,\n geometryFor,\n getValueBounds,\n monthLabelsFor,\n pointStyle,\n pointsStyle,\n valueLabelsFor,\n} from \"./line-chart-geometry.js\";\nimport type { LineChartProps } from \"./types.js\";\n\nexport function LineChart<TAs extends ElementType = \"figure\">(\n props: LineChartProps<TAs>,\n): ReactElement | null {\n const {\n as,\n render,\n data,\n xAxis,\n yAxis,\n \"aria-label\": ariaLabel,\n activePointIndex,\n cursorY,\n interactive = false,\n onPointClick,\n onPointPointerLeave,\n onPointPointerMove,\n selectedPointIndex,\n ...consumerProps\n } = props as LineChartProps<\"figure\">;\n const { min, max } = getValueBounds(data);\n const label = ariaLabel ?? \"Line chart\";\n const timeLabels = axisLabelsFor(xAxis, monthLabelsFor(data));\n const valueLabels = axisLabelsFor(yAxis, valueLabelsFor(min, max));\n const readoutIndex =\n activePointIndex ?? selectedPointIndex ?? (data.length > 0 ? data.length - 1 : undefined);\n const readoutPoint = readoutIndex === undefined ? undefined : data[readoutIndex];\n const crosshairPoint = activePointIndex === undefined ? undefined : data[activePointIndex];\n const points = data.map((point, index) => {\n const geometry = geometryFor(point, data[index - 1], index, data.length, min, max);\n const isActive = activePointIndex === index;\n const isSelected = selectedPointIndex === index;\n const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n event.preventDefault();\n onPointClick?.(index);\n };\n\n return (\n <li\n key={`${point.time}-${index}`}\n aria-hidden={interactive ? undefined : true}\n data-crosshair-active={interactive && isActive ? \"\" : undefined}\n data-point-selected={interactive && isSelected ? \"\" : undefined}\n data-time={String(point.time)}\n data-trend={geometry.trend}\n style={pointStyle(geometry, isActive ? cursorY : undefined)}\n >\n {interactive ? (\n <button\n data-part=\"point-hit\"\n type=\"button\"\n aria-label={`Point ${String(point.time)}`}\n aria-pressed={isSelected}\n onClick={() => onPointClick?.(index)}\n onKeyDown={handleKeyDown}\n onPointerLeave={onPointPointerLeave}\n onPointerMove={(event: PointerEvent<HTMLButtonElement>) =>\n onPointPointerMove?.({ index, offsetY: event.nativeEvent.offsetY })\n }\n />\n ) : null}\n </li>\n );\n });\n\n return useRenderElement({\n as,\n defaultTag: \"figure\",\n props: {\n ...consumerProps,\n \"aria-label\": label,\n \"data-uiify-line-chart\": \"\",\n \"data-interactive\": interactive ? \"\" : undefined,\n children: (\n <>\n <figcaption>{label}</figcaption>\n {interactive && readoutPoint ? (\n <div data-part=\"readout\" aria-live=\"polite\">\n <span data-part=\"readout-time\">{String(readoutPoint.time)}</span>\n <span>{readoutPoint.value}</span>\n </div>\n ) : null}\n <div data-part=\"plot\" aria-hidden={interactive ? undefined : true}>\n <div data-part=\"scroll-viewport\">\n <div data-part=\"canvas\">\n <div data-part=\"viewport\">\n <ol\n data-part=\"points\"\n aria-label={`${label} points`}\n style={pointsStyle(data.length)}\n >\n {points}\n </ol>\n {interactive && crosshairPoint && activePointIndex !== undefined ? (\n <div\n data-part=\"crosshair\"\n aria-hidden=\"true\"\n style={crosshairStyle(activePointIndex, data.length, cursorY)}\n >\n <span data-part=\"crosshair-marker\">+</span>\n </div>\n ) : null}\n </div>\n <div data-part=\"time-viewport\">\n <ol data-part=\"time-axis\" data-axis=\"time\" aria-label={`${label} dates`}>\n {timeLabels.map((axisLabel, index) => (\n <li\n key={`${axisLabel.position}-${index}`}\n style={axisStyle(\"x\", axisLabel.position)}\n >\n {axisLabel.label}\n </li>\n ))}\n {interactive && crosshairPoint && activePointIndex !== undefined ? (\n <li\n data-part=\"crosshair-time\"\n style={crosshairStyle(activePointIndex, data.length, undefined)}\n >\n {String(crosshairPoint.time)}\n </li>\n ) : null}\n </ol>\n </div>\n </div>\n </div>\n <ol data-part=\"value-axis\" data-axis=\"value\" aria-label={`${label} values`}>\n {valueLabels.map((axisLabel, index) => (\n <li\n key={`${axisLabel.position}-${index}`}\n style={axisStyle(\"y\", axisLabel.position)}\n >\n {axisLabel.label}\n </li>\n ))}\n {interactive && crosshairPoint && activePointIndex !== undefined ? (\n <li data-part=\"crosshair-value\" style={cursorStyle(cursorY)}>\n {crosshairPoint.value}\n </li>\n ) : null}\n </ol>\n </div>\n <table data-part=\"data\" aria-label={`${label} data`}>\n <thead>\n <tr>\n <th scope=\"col\">Time</th>\n <th scope=\"col\">Value</th>\n </tr>\n </thead>\n <tbody>\n {data.map((point, index) => (\n <tr key={`${point.time}-${index}`}>\n <th scope=\"row\">{String(point.time)}</th>\n <td>{point.value}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </>\n ),\n },\n render,\n state: {},\n });\n}\nLineChart.displayName = \"LineChart\";\n"],"mappings":"AAgEU,SA0BF,UA1BE,KA6BE,YA7BF;AA/DV,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGA,SAAS,UACd,OACqB;AACrB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AACJ,QAAM,EAAE,KAAK,IAAI,IAAI,eAAe,IAAI;AACxC,QAAM,QAAQ,aAAa;AAC3B,QAAM,aAAa,cAAc,OAAO,eAAe,IAAI,CAAC;AAC5D,QAAM,cAAc,cAAc,OAAO,eAAe,KAAK,GAAG,CAAC;AACjE,QAAM,eACJ,oBAAoB,uBAAuB,KAAK,SAAS,IAAI,KAAK,SAAS,IAAI;AACjF,QAAM,eAAe,iBAAiB,SAAY,SAAY,KAAK,YAAY;AAC/E,QAAM,iBAAiB,qBAAqB,SAAY,SAAY,KAAK,gBAAgB;AACzF,QAAM,SAAS,KAAK,IAAI,CAAC,OAAO,UAAU;AACxC,UAAM,WAAW,YAAY,OAAO,KAAK,QAAQ,CAAC,GAAG,OAAO,KAAK,QAAQ,KAAK,GAAG;AACjF,UAAM,WAAW,qBAAqB;AACtC,UAAM,aAAa,uBAAuB;AAC1C,UAAM,gBAAgB,CAAC,UAA4C;AACjE,UAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,IAAK;AAChD,YAAM,eAAe;AACrB,qBAAe,KAAK;AAAA,IACtB;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QAEC,eAAa,cAAc,SAAY;AAAA,QACvC,yBAAuB,eAAe,WAAW,KAAK;AAAA,QACtD,uBAAqB,eAAe,aAAa,KAAK;AAAA,QACtD,aAAW,OAAO,MAAM,IAAI;AAAA,QAC5B,cAAY,SAAS;AAAA,QACrB,OAAO,WAAW,UAAU,WAAW,UAAU,MAAS;AAAA,QAEzD,wBACC;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YACV,MAAK;AAAA,YACL,cAAY,SAAS,OAAO,MAAM,IAAI,CAAC;AAAA,YACvC,gBAAc;AAAA,YACd,SAAS,MAAM,eAAe,KAAK;AAAA,YACnC,WAAW;AAAA,YACX,gBAAgB;AAAA,YAChB,eAAe,CAAC,UACd,qBAAqB,EAAE,OAAO,SAAS,MAAM,YAAY,QAAQ,CAAC;AAAA;AAAA,QAEtE,IACE;AAAA;AAAA,MArBC,GAAG,MAAM,IAAI,IAAI,KAAK;AAAA,IAsB7B;AAAA,EAEJ,CAAC;AAED,SAAO,iBAAiB;AAAA,IACtB;AAAA,IACA,YAAY;AAAA,IACZ,OAAO;AAAA,MACL,GAAG;AAAA,MACH,cAAc;AAAA,MACd,yBAAyB;AAAA,MACzB,oBAAoB,cAAc,KAAK;AAAA,MACvC,UACE,iCACE;AAAA,4BAAC,gBAAY,iBAAM;AAAA,QAClB,eAAe,eACd,qBAAC,SAAI,aAAU,WAAU,aAAU,UACjC;AAAA,8BAAC,UAAK,aAAU,gBAAgB,iBAAO,aAAa,IAAI,GAAE;AAAA,UAC1D,oBAAC,UAAM,uBAAa,OAAM;AAAA,WAC5B,IACE;AAAA,QACJ,qBAAC,SAAI,aAAU,QAAO,eAAa,cAAc,SAAY,MAC3D;AAAA,8BAAC,SAAI,aAAU,mBACb,+BAAC,SAAI,aAAU,UACb;AAAA,iCAAC,SAAI,aAAU,YACb;AAAA;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,cAAY,GAAG,KAAK;AAAA,kBACpB,OAAO,YAAY,KAAK,MAAM;AAAA,kBAE7B;AAAA;AAAA,cACH;AAAA,cACC,eAAe,kBAAkB,qBAAqB,SACrD;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,eAAY;AAAA,kBACZ,OAAO,eAAe,kBAAkB,KAAK,QAAQ,OAAO;AAAA,kBAE5D,8BAAC,UAAK,aAAU,oBAAmB,eAAC;AAAA;AAAA,cACtC,IACE;AAAA,eACN;AAAA,YACA,oBAAC,SAAI,aAAU,iBACb,+BAAC,QAAG,aAAU,aAAY,aAAU,QAAO,cAAY,GAAG,KAAK,UAC5D;AAAA,yBAAW,IAAI,CAAC,WAAW,UAC1B;AAAA,gBAAC;AAAA;AAAA,kBAEC,OAAO,UAAU,KAAK,UAAU,QAAQ;AAAA,kBAEvC,oBAAU;AAAA;AAAA,gBAHN,GAAG,UAAU,QAAQ,IAAI,KAAK;AAAA,cAIrC,CACD;AAAA,cACA,eAAe,kBAAkB,qBAAqB,SACrD;AAAA,gBAAC;AAAA;AAAA,kBACC,aAAU;AAAA,kBACV,OAAO,eAAe,kBAAkB,KAAK,QAAQ,MAAS;AAAA,kBAE7D,iBAAO,eAAe,IAAI;AAAA;AAAA,cAC7B,IACE;AAAA,eACN,GACF;AAAA,aACF,GACF;AAAA,UACA,qBAAC,QAAG,aAAU,cAAa,aAAU,SAAQ,cAAY,GAAG,KAAK,WAC9D;AAAA,wBAAY,IAAI,CAAC,WAAW,UAC3B;AAAA,cAAC;AAAA;AAAA,gBAEC,OAAO,UAAU,KAAK,UAAU,QAAQ;AAAA,gBAEvC,oBAAU;AAAA;AAAA,cAHN,GAAG,UAAU,QAAQ,IAAI,KAAK;AAAA,YAIrC,CACD;AAAA,YACA,eAAe,kBAAkB,qBAAqB,SACrD,oBAAC,QAAG,aAAU,mBAAkB,OAAO,YAAY,OAAO,GACvD,yBAAe,OAClB,IACE;AAAA,aACN;AAAA,WACF;AAAA,QACA,qBAAC,WAAM,aAAU,QAAO,cAAY,GAAG,KAAK,SAC1C;AAAA,8BAAC,WACC,+BAAC,QACC;AAAA,gCAAC,QAAG,OAAM,OAAM,kBAAI;AAAA,YACpB,oBAAC,QAAG,OAAM,OAAM,mBAAK;AAAA,aACvB,GACF;AAAA,UACA,oBAAC,WACE,eAAK,IAAI,CAAC,OAAO,UAChB,qBAAC,QACC;AAAA,gCAAC,QAAG,OAAM,OAAO,iBAAO,MAAM,IAAI,GAAE;AAAA,YACpC,oBAAC,QAAI,gBAAM,OAAM;AAAA,eAFV,GAAG,MAAM,IAAI,IAAI,KAAK,EAG/B,CACD,GACH;AAAA,WACF;AAAA,SACF;AAAA,IAEJ;AAAA,IACA;AAAA,IACA,OAAO,CAAC;AAAA,EACV,CAAC;AACH;AACA,UAAU,cAAc;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/line-chart/LineChart.tsx"],"sourcesContent":["import type { ElementType, KeyboardEvent, PointerEvent, ReactElement } from \"react\";\nimport { useRenderElement } from \"@gunkin/uiify/core/render\";\nimport {\n axisLabelsFor,\n axisStyle,\n crosshairStyle,\n cursorStyle,\n geometryFor,\n getValueBounds,\n monthLabelsFor,\n pointStyle,\n pointsStyle,\n slotCrosshairStyle,\n valueLabelsFor,\n} from \"./line-chart-geometry.js\";\nimport type { LineChartPointProps, LineChartProps, LineChartViewProps } from \"./types.js\";\n\n/** One point and the segment from its predecessor; shared by the static list and virtual rows. */\nexport function LineChartPoint({\n as: Tag,\n point,\n previousPoint,\n index,\n count,\n min,\n max,\n interactive,\n isActive,\n isSelected,\n cursorY,\n onPointClick,\n onPointPointerLeave,\n onPointPointerMove,\n}: LineChartPointProps): ReactElement {\n const geometry = geometryFor(point, previousPoint, index, count, min, max);\n const handleKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== \"Enter\" && event.key !== \" \") return;\n event.preventDefault();\n onPointClick?.(index);\n };\n\n return (\n <Tag\n aria-hidden={interactive ? undefined : true}\n data-part=\"point\"\n data-first={previousPoint === undefined ? \"\" : undefined}\n data-crosshair-active={interactive && isActive ? \"\" : undefined}\n data-point-selected={interactive && isSelected ? \"\" : undefined}\n data-time={String(point.time)}\n data-trend={geometry.trend}\n style={pointStyle(geometry, isActive ? cursorY : undefined)}\n >\n {interactive ? (\n <button\n data-part=\"point-hit\"\n type=\"button\"\n aria-label={`Point ${String(point.time)}`}\n aria-pressed={isSelected}\n onClick={() => onPointClick?.(index)}\n onKeyDown={handleKeyDown}\n onPointerLeave={onPointPointerLeave}\n onPointerMove={(event: PointerEvent<HTMLButtonElement>) =>\n onPointPointerMove?.({ index, offsetY: event.nativeEvent.offsetY })\n }\n />\n ) : null}\n </Tag>\n );\n}\nLineChartPoint.displayName = \"LineChartPoint\";\n\nexport function LineChart<TAs extends ElementType = \"figure\">(\n props: LineChartProps<TAs>,\n): ReactElement | null {\n const {\n as,\n render,\n data,\n xAxis,\n yAxis,\n \"aria-label\": ariaLabel,\n activePointIndex,\n cursorY,\n interactive = false,\n onPointClick,\n onPointPointerLeave,\n onPointPointerMove,\n selectedPointIndex,\n pointLayer,\n announceReadout = true,\n slotCount,\n slotOffset = 0,\n ...consumerProps\n } = props as LineChartProps<\"figure\"> & LineChartViewProps;\n const { min, max } = getValueBounds(data);\n const label = ariaLabel ?? \"Line chart\";\n const timeLabels = axisLabelsFor(xAxis, monthLabelsFor(data));\n const valueLabels = axisLabelsFor(yAxis, valueLabelsFor(min, max));\n const readoutIndex =\n activePointIndex ?? selectedPointIndex ?? (data.length > 0 ? data.length - 1 : undefined);\n const readoutPoint = readoutIndex === undefined ? undefined : data[readoutIndex];\n const crosshairPoint = activePointIndex === undefined ? undefined : data[activePointIndex];\n const points = pointLayer ?? (\n <ol data-part=\"points\" aria-label={`${label} points`} style={pointsStyle(data.length)}>\n {data.map((point, index) => (\n <LineChartPoint\n key={`${point.time}-${index}`}\n as=\"li\"\n point={point}\n previousPoint={data[index - 1]}\n index={index}\n count={data.length}\n min={min}\n max={max}\n interactive={interactive}\n isActive={activePointIndex === index}\n isSelected={selectedPointIndex === index}\n cursorY={cursorY}\n onPointClick={onPointClick}\n onPointPointerLeave={onPointPointerLeave}\n onPointPointerMove={onPointPointerMove}\n />\n ))}\n </ol>\n );\n const crosshairFor = (index: number, y: number | undefined) =>\n slotCount === undefined\n ? crosshairStyle(index, data.length, y)\n : slotCrosshairStyle(slotOffset + index, slotCount, y);\n\n return useRenderElement({\n as,\n defaultTag: \"figure\",\n props: {\n ...consumerProps,\n \"aria-label\": label,\n \"data-uiify-line-chart\": \"\",\n \"data-interactive\": interactive ? \"\" : undefined,\n children: (\n <>\n <figcaption>{label}</figcaption>\n {interactive && readoutPoint ? (\n <div data-part=\"readout\" aria-live={announceReadout ? \"polite\" : \"off\"}>\n <span data-part=\"readout-time\">{String(readoutPoint.time)}</span>\n <span>{readoutPoint.value}</span>\n </div>\n ) : null}\n <div data-part=\"plot\" aria-hidden={interactive ? undefined : true}>\n <div data-part=\"scroll-viewport\">\n <div data-part=\"canvas\">\n <div data-part=\"viewport\">\n {points}\n {interactive && crosshairPoint && activePointIndex !== undefined ? (\n <div\n data-part=\"crosshair\"\n aria-hidden=\"true\"\n style={crosshairFor(activePointIndex, cursorY)}\n >\n <span data-part=\"crosshair-marker\">+</span>\n </div>\n ) : null}\n </div>\n <div data-part=\"time-viewport\">\n <ol data-part=\"time-axis\" data-axis=\"time\" aria-label={`${label} dates`}>\n {timeLabels.map((axisLabel, index) => (\n <li\n key={`${axisLabel.position}-${index}`}\n style={axisStyle(\"x\", axisLabel.position)}\n >\n {axisLabel.label}\n </li>\n ))}\n {interactive && crosshairPoint && activePointIndex !== undefined ? (\n <li\n data-part=\"crosshair-time\"\n style={crosshairFor(activePointIndex, undefined)}\n >\n {String(crosshairPoint.time)}\n </li>\n ) : null}\n </ol>\n </div>\n </div>\n </div>\n <ol data-part=\"value-axis\" data-axis=\"value\" aria-label={`${label} values`}>\n {valueLabels.map((axisLabel, index) => (\n <li\n key={`${axisLabel.position}-${index}`}\n style={axisStyle(\"y\", axisLabel.position)}\n >\n {axisLabel.label}\n </li>\n ))}\n {interactive && crosshairPoint && activePointIndex !== undefined ? (\n <li data-part=\"crosshair-value\" style={cursorStyle(cursorY)}>\n {crosshairPoint.value}\n </li>\n ) : null}\n </ol>\n </div>\n <table data-part=\"data\" aria-label={`${label} data`}>\n <thead>\n <tr>\n <th scope=\"col\">Time</th>\n <th scope=\"col\">Value</th>\n </tr>\n </thead>\n <tbody>\n {data.map((point, index) => (\n <tr key={`${point.time}-${index}`}>\n <th scope=\"row\">{String(point.time)}</th>\n <td>{point.value}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </>\n ),\n },\n render,\n state: {},\n });\n}\nLineChart.displayName = \"LineChart\";\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import type { ElementType, ReactElement } from "react";
2
- import type { LineChartProps } from "../types.js";
3
- export declare function LineChartClient<TAs extends ElementType = "figure">(props: LineChartProps<TAs>): ReactElement | null;
2
+ import type { LineChartClientProps } from "../types.js";
3
+ export declare function LineChartClient<TAs extends ElementType = "figure">(props: LineChartClientProps<TAs>): ReactElement | null;
4
4
  export declare namespace LineChartClient {
5
5
  var displayName: string;
6
6
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LineChartClient.d.ts","sourceRoot":"","sources":["../../../../src/components/line-chart/client/LineChartClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,WAAW,EAAE,YAAY,EAAO,MAAM,OAAO,CAAC;AAI3E,OAAO,KAAK,EAA4B,cAAc,EAAE,MAAM,aAAa,CAAC;AAgB5E,wBAAgB,eAAe,CAAC,GAAG,SAAS,WAAW,GAAG,QAAQ,EAChE,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,GACzB,YAAY,GAAG,IAAI,CA2DrB;yBA7De,eAAe"}
1
+ {"version":3,"file":"LineChartClient.d.ts","sourceRoot":"","sources":["../../../../src/components/line-chart/client/LineChartClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,WAAW,EAAE,YAAY,EAAO,MAAM,OAAO,CAAC;AAY3E,OAAO,KAAK,EACV,oBAAoB,EAKrB,MAAM,aAAa,CAAC;AAwBrB,wBAAgB,eAAe,CAAC,GAAG,SAAS,WAAW,GAAG,QAAQ,EAChE,KAAK,EAAE,oBAAoB,CAAC,GAAG,CAAC,GAC/B,YAAY,GAAG,IAAI,CAqJrB;yBAvJe,eAAe"}
@@ -1,13 +1,23 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { useCallback, useState } from "react";
3
+ import { useCallback, useMemo, useState } from "react";
4
4
  import { useChartViewport } from "../../../hooks/use-chart-viewport.js";
5
5
  import { useMergedRefs } from "../../../hooks/use-merged-refs.js";
6
- import { LineChart } from "../LineChart.js";
6
+ import { useResizeObserver } from "../../../hooks/use-resize-observer.js";
7
+ import {
8
+ liveTimeAxis,
9
+ useLiveChartWindow
10
+ } from "../../internal/live-chart/use-live-chart-window.js";
11
+ import { useScrollbarReserve } from "../../internal/chart-scrollbar/use-scrollbar-reserve.js";
12
+ import { VirtualScroll } from "../../virtual-scroll/VirtualScroll.js";
13
+ import { LineChart, LineChartPoint } from "../LineChart.js";
14
+ import { getValueBounds } from "../line-chart-geometry.js";
7
15
  const Y_AXIS_COLUMN_WIDTH = 72 + 12;
8
16
  function LineChartClient(props) {
9
17
  const {
10
18
  data,
19
+ visibleCount,
20
+ xAxis,
11
21
  ref: consumerRef,
12
22
  style: consumerStyle,
13
23
  onPointClick: consumerOnPointClick,
@@ -15,14 +25,27 @@ function LineChartClient(props) {
15
25
  onPointPointerMove: consumerOnPointPointerMove,
16
26
  ...consumerProps
17
27
  } = props;
18
- const viewport = useChartViewport({
19
- itemCount: data.length,
20
- measuredWidthOffset: Y_AXIS_COLUMN_WIDTH,
21
- minItemWidth: 24
28
+ const live = visibleCount !== void 0 && visibleCount > 0;
29
+ const viewport = useChartViewport(
30
+ live ? {
31
+ enabled: false,
32
+ initialScale: 1,
33
+ itemCount: visibleCount,
34
+ measuredWidthOffset: Y_AXIS_COLUMN_WIDTH
35
+ } : { itemCount: data.length, measuredWidthOffset: Y_AXIS_COLUMN_WIDTH, minItemWidth: 24 }
36
+ );
37
+ const figureSize = useResizeObserver(viewport.ref);
38
+ const liveWindow = useLiveChartWindow({
39
+ count: data.length,
40
+ visibleCount,
41
+ itemWidth: viewport.itemWidth,
42
+ figureHeight: figureSize?.height,
43
+ scrollVariable: "--line-scroll-x"
22
44
  });
45
+ useScrollbarReserve(liveWindow.chartRef, "--line-scrollbar-size");
23
46
  const [cursor, setCursor] = useState(null);
24
47
  const [selectedPointIndex, setSelectedPointIndex] = useState();
25
- const chartRef = useMergedRefs(viewport.ref, consumerRef);
48
+ const chartRef = useMergedRefs(viewport.ref, consumerRef, liveWindow.chartRef);
26
49
  const handlePointClick = useCallback(
27
50
  (index) => {
28
51
  setSelectedPointIndex(index);
@@ -41,22 +64,91 @@ function LineChartClient(props) {
41
64
  },
42
65
  [consumerOnPointPointerMove]
43
66
  );
67
+ const liveItems = useMemo(
68
+ () => live ? data.map((point, index) => ({ id: String(index), point })) : [],
69
+ [data, live]
70
+ );
44
71
  const style = {
45
72
  ...consumerStyle,
46
73
  "--line-scale": String(viewport.scale)
47
74
  };
75
+ if (!live) {
76
+ const chartProps2 = {
77
+ ...consumerProps,
78
+ ...xAxis ? { xAxis } : {},
79
+ data,
80
+ activePointIndex: cursor?.index,
81
+ cursorY: cursor?.offsetY,
82
+ interactive: true,
83
+ onPointClick: handlePointClick,
84
+ onPointPointerLeave: handlePointPointerLeave,
85
+ onPointPointerMove: handlePointPointerMove,
86
+ ref: chartRef,
87
+ selectedPointIndex,
88
+ style
89
+ };
90
+ return /* @__PURE__ */ jsx(LineChart, { ...chartProps2 });
91
+ }
92
+ const { sliceStart, sliceEnd, contentSize, plotHeight } = liveWindow;
93
+ const slice = data.slice(sliceStart, sliceEnd);
94
+ const { min, max } = getValueBounds(slice);
95
+ const toSliceIndex = (index) => index !== void 0 && index >= sliceStart && index < sliceEnd ? index - sliceStart : void 0;
96
+ const pointLayer = /* @__PURE__ */ jsx(
97
+ VirtualScroll,
98
+ {
99
+ height: plotHeight,
100
+ items: liveItems,
101
+ orientation: "horizontal",
102
+ overscan: 2,
103
+ rowHeight: viewport.itemWidth,
104
+ scrollRef: liveWindow.scrollRef,
105
+ onRangeChange: liveWindow.onRangeChange,
106
+ renderRow: ({ point }, index) => {
107
+ const inSlice = index >= sliceStart && index < sliceEnd;
108
+ const isActive = cursor?.index === index;
109
+ return /* @__PURE__ */ jsx(
110
+ LineChartPoint,
111
+ {
112
+ as: "div",
113
+ point,
114
+ previousPoint: data[index - 1],
115
+ index,
116
+ count: data.length,
117
+ min,
118
+ max,
119
+ interactive: inSlice,
120
+ isActive,
121
+ isSelected: selectedPointIndex === index,
122
+ cursorY: isActive ? cursor?.offsetY : void 0,
123
+ onPointClick: handlePointClick,
124
+ onPointPointerLeave: handlePointPointerLeave,
125
+ onPointPointerMove: handlePointPointerMove
126
+ }
127
+ );
128
+ }
129
+ }
130
+ );
131
+ const liveStyle = {
132
+ ...style,
133
+ "--line-content-size": `${contentSize}px`,
134
+ "--line-plot-height": `${plotHeight}px`,
135
+ // One virtual row: the viewport holds `visibleCount` of them.
136
+ "--line-run": String(100 / visibleCount)
137
+ };
48
138
  const chartProps = {
49
139
  ...consumerProps,
50
- data,
51
- activePointIndex: cursor?.index,
140
+ data: slice,
141
+ xAxis: xAxis ?? liveTimeAxis(data, sliceStart, sliceEnd, visibleCount),
142
+ activePointIndex: toSliceIndex(cursor?.index),
143
+ announceReadout: false,
144
+ pointLayer,
52
145
  cursorY: cursor?.offsetY,
53
146
  interactive: true,
54
- onPointClick: handlePointClick,
55
- onPointPointerLeave: handlePointPointerLeave,
56
- onPointPointerMove: handlePointPointerMove,
57
147
  ref: chartRef,
58
- selectedPointIndex,
59
- style
148
+ selectedPointIndex: toSliceIndex(selectedPointIndex),
149
+ slotCount: data.length,
150
+ slotOffset: sliceStart,
151
+ style: liveStyle
60
152
  };
61
153
  return /* @__PURE__ */ jsx(LineChart, { ...chartProps });
62
154
  }