@x-plat/design-system 0.5.35 → 0.5.37

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.
@@ -1,22 +1,6 @@
1
1
  // src/components/Chart/Chart.tsx
2
- import React2 from "react";
3
-
4
- // src/tokens/hooks/Portal.tsx
5
2
  import React from "react";
6
- import ReactDOM from "react-dom";
7
- import { jsx } from "react/jsx-runtime";
8
- var PortalContainerContext = React.createContext(null);
9
- var Portal = ({ children }) => {
10
- const contextContainer = React.useContext(PortalContainerContext);
11
- if (typeof document === "undefined") return null;
12
- const container = contextContainer ?? document.body;
13
- return ReactDOM.createPortal(children, container);
14
- };
15
- Portal.displayName = "Portal";
16
- var Portal_default = Portal;
17
-
18
- // src/components/Chart/Chart.tsx
19
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
20
4
  var CATEGORICAL_COUNT = 8;
21
5
  var LINE_BAR_PALETTES = Array.from({ length: CATEGORICAL_COUNT }, (_, i) => {
22
6
  const n = i + 1;
@@ -62,11 +46,11 @@ var toSmoothPath = (points) => {
62
46
  };
63
47
  var RESIZE_SETTLE_MS = 150;
64
48
  var useChartSize = (ref) => {
65
- const [size, setSize] = React2.useState({ width: 0, height: 0 });
66
- const settleTimer = React2.useRef(0);
67
- const committedSize = React2.useRef({ width: 0, height: 0 });
68
- const initialRef = React2.useRef(true);
69
- React2.useEffect(() => {
49
+ const [size, setSize] = React.useState({ width: 0, height: 0 });
50
+ const settleTimer = React.useRef(0);
51
+ const committedSize = React.useRef({ width: 0, height: 0 });
52
+ const initialRef = React.useRef(true);
53
+ React.useEffect(() => {
70
54
  const el = ref.current;
71
55
  if (!el) return;
72
56
  const observer = new ResizeObserver((entries) => {
@@ -108,10 +92,10 @@ var useChartSize = (ref) => {
108
92
  };
109
93
  var prefersReducedMotion = () => typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
110
94
  var useChartAnimation = (containerRef, dataKey) => {
111
- const [animate, setAnimate] = React2.useState(false);
112
- const prevDataKey = React2.useRef(dataKey);
113
- const hasAnimated = React2.useRef(false);
114
- React2.useEffect(() => {
95
+ const [animate, setAnimate] = React.useState(false);
96
+ const prevDataKey = React.useRef(dataKey);
97
+ const hasAnimated = React.useRef(false);
98
+ React.useEffect(() => {
115
99
  if (prefersReducedMotion()) return;
116
100
  const el = containerRef.current;
117
101
  if (!el) return;
@@ -127,7 +111,7 @@ var useChartAnimation = (containerRef, dataKey) => {
127
111
  observer.observe(el);
128
112
  return () => observer.disconnect();
129
113
  }, [containerRef]);
130
- React2.useEffect(() => {
114
+ React.useEffect(() => {
131
115
  if (dataKey !== prevDataKey.current) {
132
116
  prevDataKey.current = dataKey;
133
117
  if (prefersReducedMotion()) return;
@@ -141,39 +125,60 @@ var useChartAnimation = (containerRef, dataKey) => {
141
125
  };
142
126
  var TOOLTIP_OFFSET = 12;
143
127
  var useChartTooltip = (enabled) => {
144
- const [tooltip, setTooltip] = React2.useState({
128
+ const [tooltip, setTooltip] = React.useState({
145
129
  visible: false,
146
- clientX: 0,
147
- clientY: 0,
130
+ x: 0,
131
+ y: 0,
148
132
  content: ""
149
133
  });
150
- const containerRef = React2.useRef(null);
151
- const rafRef = React2.useRef(0);
152
- const move = React2.useCallback((e) => {
134
+ const containerRef = React.useRef(null);
135
+ const rafRef = React.useRef(0);
136
+ const move = React.useCallback((e) => {
153
137
  if (!enabled) return;
154
138
  const cx = e.clientX;
155
139
  const cy = e.clientY;
156
140
  cancelAnimationFrame(rafRef.current);
157
141
  rafRef.current = requestAnimationFrame(() => {
158
- setTooltip((prev) => ({ ...prev, clientX: cx, clientY: cy }));
142
+ const rect = containerRef.current?.getBoundingClientRect();
143
+ if (!rect) return;
144
+ setTooltip((prev) => ({ ...prev, x: cx - rect.left, y: cy - rect.top }));
159
145
  });
160
146
  }, [enabled]);
161
- const show = React2.useCallback((e, content) => {
147
+ const show = React.useCallback((e, content) => {
162
148
  if (!enabled) return;
163
- setTooltip({ visible: true, clientX: e.clientX, clientY: e.clientY, content });
149
+ const rect = containerRef.current?.getBoundingClientRect();
150
+ if (!rect) return;
151
+ setTooltip({ visible: true, x: e.clientX - rect.left, y: e.clientY - rect.top, content });
152
+ }, [enabled]);
153
+ const showAt = React.useCallback((svgX, svgY, content, svgEl) => {
154
+ if (!enabled) return;
155
+ const container = containerRef.current;
156
+ if (!container) return;
157
+ let x = svgX;
158
+ let y = svgY;
159
+ if (svgEl) {
160
+ const svgRect = svgEl.getBoundingClientRect();
161
+ const containerRect = container.getBoundingClientRect();
162
+ const vb = svgEl.viewBox.baseVal;
163
+ const scaleX = svgRect.width / (vb.width || 1);
164
+ const scaleY = svgRect.height / (vb.height || 1);
165
+ x = svgX * scaleX + (svgRect.left - containerRect.left);
166
+ y = svgY * scaleY + (svgRect.top - containerRect.top);
167
+ }
168
+ setTooltip({ visible: true, x, y, content });
164
169
  }, [enabled]);
165
- const hide = React2.useCallback(() => {
170
+ const hide = React.useCallback(() => {
166
171
  cancelAnimationFrame(rafRef.current);
167
172
  setTooltip((prev) => ({ ...prev, visible: false }));
168
173
  }, []);
169
- return { tooltip, show, hide, move, containerRef };
174
+ return { tooltip, show, showAt, hide, move, containerRef };
170
175
  };
171
- var GridLines = React2.memo(({ width, height, chartH, maxVal }) => /* @__PURE__ */ jsx2(Fragment, { children: [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
176
+ var GridLines = React.memo(({ width, height, chartH, maxVal }) => /* @__PURE__ */ jsx(Fragment, { children: [0, 0.25, 0.5, 0.75, 1].map((ratio) => {
172
177
  const y = PADDING.top + (1 - ratio) * chartH;
173
178
  const val = Math.round(maxVal * ratio);
174
179
  return /* @__PURE__ */ jsxs("g", { children: [
175
- /* @__PURE__ */ jsx2("line", { x1: PADDING.left, y1: y, x2: width - PADDING.right, y2: y, className: "chart-grid" }),
176
- /* @__PURE__ */ jsx2("text", { x: PADDING.left - 8, y: y + 4, className: "chart-axis-label", textAnchor: "end", children: val })
180
+ /* @__PURE__ */ jsx("line", { x1: PADDING.left, y1: y, x2: width - PADDING.right, y2: y, className: "chart-grid" }),
181
+ /* @__PURE__ */ jsx("text", { x: PADDING.left - 8, y: y + 4, className: "chart-axis-label", textAnchor: "end", children: val })
177
182
  ] }, ratio);
178
183
  }) }));
179
184
  GridLines.displayName = "GridLines";
@@ -183,18 +188,18 @@ var getLabelStep = (count, chartW) => {
183
188
  if (count <= maxLabels) return 1;
184
189
  return Math.ceil(count / maxLabels);
185
190
  };
186
- var AxisLabels = React2.memo(({ labels, count, chartW, height }) => {
191
+ var AxisLabels = React.memo(({ labels, count, chartW, height }) => {
187
192
  const step = getLabelStep(count, chartW);
188
- return /* @__PURE__ */ jsx2(Fragment, { children: labels.map((label, i) => {
193
+ return /* @__PURE__ */ jsx(Fragment, { children: labels.map((label, i) => {
189
194
  if (i % step !== 0) return null;
190
195
  const x = PADDING.left + i / (count - 1 || 1) * chartW;
191
- return /* @__PURE__ */ jsx2("text", { x, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
196
+ return /* @__PURE__ */ jsx("text", { x, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
192
197
  }) });
193
198
  });
194
199
  AxisLabels.displayName = "AxisLabels";
195
200
  var useCrosshair = (seriesPoints, entries, labels, chartH) => {
196
- const [activeIndex, setActiveIndex] = React2.useState(null);
197
- const handleMouseMove = React2.useCallback((e) => {
201
+ const [activeIndex, setActiveIndex] = React.useState(null);
202
+ const handleMouseMove = React.useCallback((e) => {
198
203
  const svg = e.currentTarget;
199
204
  const rect = svg.getBoundingClientRect();
200
205
  const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
@@ -213,17 +218,17 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
213
218
  }
214
219
  setActiveIndex(minDist <= threshold ? closest : null);
215
220
  }, [seriesPoints]);
216
- const handleMouseLeave = React2.useCallback(() => {
221
+ const handleMouseLeave = React.useCallback(() => {
217
222
  setActiveIndex(null);
218
223
  }, []);
219
- const tooltipContent = React2.useMemo(() => {
224
+ const tooltipContent = React.useMemo(() => {
220
225
  if (activeIndex === null) return "";
221
226
  return entries.map(([key], di) => {
222
227
  const p = seriesPoints[di]?.[activeIndex];
223
228
  return p ? `${key}: ${p.v}` : "";
224
229
  }).filter(Boolean).join(" / ");
225
230
  }, [activeIndex, entries, seriesPoints]);
226
- const getTooltipAt = React2.useCallback((idx) => {
231
+ const getTooltipAt = React.useCallback((idx) => {
227
232
  return entries.map(([key], di) => {
228
233
  const p = seriesPoints[di]?.[idx];
229
234
  return p ? `${key}: ${p.v}` : "";
@@ -231,16 +236,16 @@ var useCrosshair = (seriesPoints, entries, labels, chartH) => {
231
236
  }, [entries, seriesPoints]);
232
237
  return { activeIndex, handleMouseMove, handleMouseLeave, tooltipContent, getTooltipAt };
233
238
  };
234
- var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
235
- const entries = React2.useMemo(() => Object.entries(data), [data]);
236
- const maxVal = React2.useMemo(() => {
239
+ var LineChart = React.memo(({ data, labels, width, height, animate, onHover, onShowAt, onMove, onLeave }) => {
240
+ const entries = React.useMemo(() => Object.entries(data), [data]);
241
+ const maxVal = React.useMemo(() => {
237
242
  const allValues = entries.flatMap(([, v]) => v);
238
243
  return Math.max(...allValues) * 1.2 || 1;
239
244
  }, [entries]);
240
245
  const count = labels.length;
241
246
  const chartW = width - PADDING.left - PADDING.right;
242
247
  const chartH = height - PADDING.top - PADDING.bottom;
243
- const seriesPoints = React2.useMemo(
248
+ const seriesPoints = React.useMemo(
244
249
  () => entries.map(
245
250
  ([, values]) => values.map((v, i) => ({
246
251
  x: PADDING.left + i / (count - 1 || 1) * chartW,
@@ -250,9 +255,10 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
250
255
  ),
251
256
  [entries, count, chartW, chartH, maxVal]
252
257
  );
253
- const clipRef = React2.useRef(null);
258
+ const clipRef = React.useRef(null);
259
+ const svgRef = React.useRef(null);
254
260
  const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
255
- React2.useEffect(() => {
261
+ React.useEffect(() => {
256
262
  if (!animate || !clipRef.current) return;
257
263
  clipRef.current.setAttribute("width", "0");
258
264
  requestAnimationFrame(() => {
@@ -267,27 +273,14 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
267
273
  return /* @__PURE__ */ jsxs(
268
274
  "svg",
269
275
  {
276
+ ref: svgRef,
270
277
  viewBox: `0 0 ${width} ${height}`,
271
278
  className: "chart-svg",
272
279
  onMouseMove: (e) => {
273
280
  handleMouseMove(e);
274
- const svg = e.currentTarget;
275
- const rect = svg.getBoundingClientRect();
276
- const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
277
- const points = seriesPoints[0];
278
- if (!points || points.length === 0) return;
279
- const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
280
- let closest = 0;
281
- let minDist = Math.abs(points[0].x - mx);
282
- for (let i = 1; i < points.length; i++) {
283
- const dist = Math.abs(points[i].x - mx);
284
- if (dist < minDist) {
285
- minDist = dist;
286
- closest = i;
287
- }
288
- }
289
- if (minDist <= step / 2) {
290
- onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
281
+ if (activeIndex !== null && seriesPoints[0]?.[activeIndex]) {
282
+ const p = seriesPoints[0][activeIndex];
283
+ onShowAt(p.x, p.y, `${labels[activeIndex]} \u2014 ${getTooltipAt(activeIndex)}`, svgRef.current);
291
284
  } else {
292
285
  onLeave();
293
286
  }
@@ -297,9 +290,9 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
297
290
  onLeave();
298
291
  },
299
292
  children: [
300
- animate && /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsx2("clipPath", { id: lineClipId, children: /* @__PURE__ */ jsx2("rect", { ref: clipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
301
- /* @__PURE__ */ jsx2(GridLines, { width, height, chartH, maxVal }),
302
- /* @__PURE__ */ jsx2(AxisLabels, { labels, count, chartW, height }),
293
+ animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: lineClipId, children: /* @__PURE__ */ jsx("rect", { ref: clipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
294
+ /* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
295
+ /* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
303
296
  entries.map(([key], di) => {
304
297
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
305
298
  const color = palette[2];
@@ -309,15 +302,15 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
309
302
  const polyPoints = points.map((p) => `${p.x},${p.y}`).join(" ");
310
303
  const areaD = `M ${points[0].x},${points[0].y} ${points.map((p) => `L ${p.x},${p.y}`).join(" ")} L ${points[points.length - 1].x},${PADDING.top + chartH} L ${points[0].x},${PADDING.top + chartH} Z`;
311
304
  return /* @__PURE__ */ jsxs("g", { children: [
312
- /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
313
- /* @__PURE__ */ jsx2("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
314
- /* @__PURE__ */ jsx2("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
305
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
306
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.2" }),
307
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0" })
315
308
  ] }) }),
316
309
  /* @__PURE__ */ jsxs("g", { clipPath: animate ? `url(#${lineClipId})` : void 0, children: [
317
- /* @__PURE__ */ jsx2("path", { d: areaD, fill: `url(#${gradientId})` }),
318
- /* @__PURE__ */ jsx2("polyline", { points: polyPoints, fill: "none", stroke: color, strokeWidth: "2" })
310
+ /* @__PURE__ */ jsx("path", { d: areaD, fill: `url(#${gradientId})` }),
311
+ /* @__PURE__ */ jsx("polyline", { points: polyPoints, fill: "none", stroke: color, strokeWidth: "2" })
319
312
  ] }),
320
- activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx2(
313
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx(
321
314
  "circle",
322
315
  {
323
316
  cx: points[activeIndex].x,
@@ -329,7 +322,7 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
329
322
  )
330
323
  ] }, di);
331
324
  }),
332
- activeX !== null && /* @__PURE__ */ jsx2(
325
+ activeX !== null && /* @__PURE__ */ jsx(
333
326
  "line",
334
327
  {
335
328
  x1: activeX,
@@ -339,7 +332,7 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
339
332
  className: "chart-crosshair"
340
333
  }
341
334
  ),
342
- /* @__PURE__ */ jsx2(
335
+ /* @__PURE__ */ jsx(
343
336
  "rect",
344
337
  {
345
338
  x: PADDING.left,
@@ -355,16 +348,16 @@ var LineChart = React2.memo(({ data, labels, width, height, animate, onHover, on
355
348
  );
356
349
  });
357
350
  LineChart.displayName = "LineChart";
358
- var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
359
- const entries = React2.useMemo(() => Object.entries(data), [data]);
360
- const maxVal = React2.useMemo(() => {
351
+ var CurveChart = React.memo(({ data, labels, width, height, animate, onHover, onShowAt, onMove, onLeave }) => {
352
+ const entries = React.useMemo(() => Object.entries(data), [data]);
353
+ const maxVal = React.useMemo(() => {
361
354
  const allValues = entries.flatMap(([, v]) => v);
362
355
  return Math.max(...allValues) * 1.2 || 1;
363
356
  }, [entries]);
364
357
  const count = labels.length;
365
358
  const chartW = width - PADDING.left - PADDING.right;
366
359
  const chartH = height - PADDING.top - PADDING.bottom;
367
- const seriesPoints = React2.useMemo(
360
+ const seriesPoints = React.useMemo(
368
361
  () => entries.map(
369
362
  ([, values]) => values.map((v, i) => ({
370
363
  x: PADDING.left + i / (count - 1 || 1) * chartW,
@@ -374,9 +367,10 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
374
367
  ),
375
368
  [entries, count, chartW, chartH, maxVal]
376
369
  );
377
- const curveClipRef = React2.useRef(null);
370
+ const curveClipRef = React.useRef(null);
371
+ const curveSvgRef = React.useRef(null);
378
372
  const { activeIndex, handleMouseMove, handleMouseLeave, getTooltipAt } = useCrosshair(seriesPoints, entries, labels, chartH);
379
- React2.useEffect(() => {
373
+ React.useEffect(() => {
380
374
  if (!animate || !curveClipRef.current) return;
381
375
  curveClipRef.current.setAttribute("width", "0");
382
376
  requestAnimationFrame(() => {
@@ -391,27 +385,14 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
391
385
  return /* @__PURE__ */ jsxs(
392
386
  "svg",
393
387
  {
388
+ ref: curveSvgRef,
394
389
  viewBox: `0 0 ${width} ${height}`,
395
390
  className: "chart-svg",
396
391
  onMouseMove: (e) => {
397
392
  handleMouseMove(e);
398
- const svg = e.currentTarget;
399
- const rect = svg.getBoundingClientRect();
400
- const mx = (e.clientX - rect.left) / rect.width * svg.viewBox.baseVal.width;
401
- const points = seriesPoints[0];
402
- if (!points || points.length === 0) return;
403
- const step = points.length > 1 ? Math.abs(points[1].x - points[0].x) : 20;
404
- let closest = 0;
405
- let minDist = Math.abs(points[0].x - mx);
406
- for (let i = 1; i < points.length; i++) {
407
- const dist = Math.abs(points[i].x - mx);
408
- if (dist < minDist) {
409
- minDist = dist;
410
- closest = i;
411
- }
412
- }
413
- if (minDist <= step / 2) {
414
- onHover(e, `${labels[closest]} \u2014 ${getTooltipAt(closest)}`);
393
+ if (activeIndex !== null && seriesPoints[0]?.[activeIndex]) {
394
+ const p = seriesPoints[0][activeIndex];
395
+ onShowAt(p.x, p.y, `${labels[activeIndex]} \u2014 ${getTooltipAt(activeIndex)}`, curveSvgRef.current);
415
396
  } else {
416
397
  onLeave();
417
398
  }
@@ -421,9 +402,9 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
421
402
  onLeave();
422
403
  },
423
404
  children: [
424
- animate && /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsx2("clipPath", { id: curveClipId, children: /* @__PURE__ */ jsx2("rect", { ref: curveClipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
425
- /* @__PURE__ */ jsx2(GridLines, { width, height, chartH, maxVal }),
426
- /* @__PURE__ */ jsx2(AxisLabels, { labels, count, chartW, height }),
405
+ animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: curveClipId, children: /* @__PURE__ */ jsx("rect", { ref: curveClipRef, x: "0", y: "0", width: animate ? 0 : width, height }) }) }),
406
+ /* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
407
+ /* @__PURE__ */ jsx(AxisLabels, { labels, count, chartW, height }),
427
408
  entries.map(([key], di) => {
428
409
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
429
410
  const color = palette[2];
@@ -433,15 +414,15 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
433
414
  const linePath = toSmoothPath(points);
434
415
  const areaPath = linePath + ` L ${points[points.length - 1].x} ${PADDING.top + chartH} L ${points[0].x} ${PADDING.top + chartH} Z`;
435
416
  return /* @__PURE__ */ jsxs("g", { children: [
436
- /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
437
- /* @__PURE__ */ jsx2("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
438
- /* @__PURE__ */ jsx2("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
417
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
418
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: areaColor, stopOpacity: "0.4" }),
419
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: areaColor, stopOpacity: "0.02" })
439
420
  ] }) }),
440
421
  /* @__PURE__ */ jsxs("g", { clipPath: animate ? `url(#${curveClipId})` : void 0, children: [
441
- /* @__PURE__ */ jsx2("path", { d: areaPath, fill: `url(#${gradientId})` }),
442
- /* @__PURE__ */ jsx2("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" })
422
+ /* @__PURE__ */ jsx("path", { d: areaPath, fill: `url(#${gradientId})` }),
423
+ /* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2" })
443
424
  ] }),
444
- activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx2(
425
+ activeIndex !== null && points[activeIndex] && /* @__PURE__ */ jsx(
445
426
  "circle",
446
427
  {
447
428
  cx: points[activeIndex].x,
@@ -453,7 +434,7 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
453
434
  )
454
435
  ] }, di);
455
436
  }),
456
- activeX !== null && /* @__PURE__ */ jsx2(
437
+ activeX !== null && /* @__PURE__ */ jsx(
457
438
  "line",
458
439
  {
459
440
  x1: activeX,
@@ -463,7 +444,7 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
463
444
  className: "chart-crosshair"
464
445
  }
465
446
  ),
466
- /* @__PURE__ */ jsx2(
447
+ /* @__PURE__ */ jsx(
467
448
  "rect",
468
449
  {
469
450
  x: PADDING.left,
@@ -479,9 +460,10 @@ var CurveChart = React2.memo(({ data, labels, width, height, animate, onHover, o
479
460
  );
480
461
  });
481
462
  CurveChart.displayName = "CurveChart";
482
- var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onMove, onLeave }) => {
483
- const entries = React2.useMemo(() => Object.entries(data), [data]);
484
- const maxVal = React2.useMemo(() => {
463
+ var BarChart = React.memo(({ data, labels, width, height, animate, onHover, onShowAt, onMove, onLeave }) => {
464
+ const barSvgRef = React.useRef(null);
465
+ const entries = React.useMemo(() => Object.entries(data), [data]);
466
+ const maxVal = React.useMemo(() => {
485
467
  const allValues = entries.flatMap(([, v]) => v);
486
468
  return Math.max(...allValues) * 1.2 || 1;
487
469
  }, [entries]);
@@ -493,7 +475,7 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
493
475
  const barGap = groupCount > 1 ? 2 : 0;
494
476
  const barW = Math.max(1, Math.min(32, (groupW * 0.7 - barGap * (groupCount - 1)) / groupCount));
495
477
  const baseline = PADDING.top + chartH;
496
- const bars = React2.useMemo(
478
+ const bars = React.useMemo(
497
479
  () => entries.map(
498
480
  ([, values], di) => values.map((v, i) => {
499
481
  const totalBarsW = barW * groupCount + barGap * (groupCount - 1);
@@ -506,11 +488,11 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
506
488
  [entries, maxVal, chartH, groupW, barW, barGap, groupCount]
507
489
  );
508
490
  const barLabelStep = getLabelStep(count, chartW);
509
- return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
510
- /* @__PURE__ */ jsx2(GridLines, { width, height, chartH, maxVal }),
491
+ return /* @__PURE__ */ jsxs("svg", { ref: barSvgRef, viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
492
+ /* @__PURE__ */ jsx(GridLines, { width, height, chartH, maxVal }),
511
493
  labels.map((label, i) => {
512
494
  if (i % barLabelStep !== 0) return null;
513
- return /* @__PURE__ */ jsx2("text", { x: PADDING.left + groupW * i + groupW / 2, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
495
+ return /* @__PURE__ */ jsx("text", { x: PADDING.left + groupW * i + groupW / 2, y: height - 8, className: "chart-axis-label", textAnchor: "middle", children: label }, i);
514
496
  }),
515
497
  entries.map(([key], di) => {
516
498
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
@@ -519,7 +501,7 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
519
501
  const r = Math.min(4, b.w / 2);
520
502
  const d = b.h <= r ? `M ${b.x} ${b.y + b.h} V ${b.y} H ${b.x + b.w} V ${b.y + b.h} Z` : `M ${b.x} ${b.y + b.h} V ${b.y + r} Q ${b.x} ${b.y} ${b.x + r} ${b.y} H ${b.x + b.w - r} Q ${b.x + b.w} ${b.y} ${b.x + b.w} ${b.y + r} V ${b.y + b.h} Z`;
521
503
  const delay = 100 + i * 80;
522
- return /* @__PURE__ */ jsx2(
504
+ return /* @__PURE__ */ jsx(
523
505
  "path",
524
506
  {
525
507
  d,
@@ -529,8 +511,7 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
529
511
  transformOrigin: `${b.x + b.w / 2}px ${baseline}px`,
530
512
  animationDelay: `${delay}ms`
531
513
  } : void 0,
532
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
533
- onMouseMove: onMove,
514
+ onMouseEnter: () => onShowAt(b.x + b.w / 2, b.y, `${key}: ${labels[i]} \u2014 ${b.v}`, barSvgRef.current),
534
515
  onMouseLeave: onLeave
535
516
  },
536
517
  `${di}-${i}`
@@ -540,11 +521,11 @@ var BarChart = React2.memo(({ data, labels, width, height, animate, onHover, onM
540
521
  ] });
541
522
  });
542
523
  BarChart.displayName = "BarChart";
543
- var PieDonutChart = React2.memo(
524
+ var PieDonutChart = React.memo(
544
525
  ({ data, labels, width, height, animate, isDoughnut, onHover, onMove, onLeave }) => {
545
- const entries = React2.useMemo(() => Object.entries(data), [data]);
546
- const values = React2.useMemo(() => entries.flatMap(([, v]) => v), [entries]);
547
- const total = React2.useMemo(() => values.reduce((a, b) => a + b, 0) || 1, [values]);
526
+ const entries = React.useMemo(() => Object.entries(data), [data]);
527
+ const values = React.useMemo(() => entries.flatMap(([, v]) => v), [entries]);
528
+ const total = React.useMemo(() => values.reduce((a, b) => a + b, 0) || 1, [values]);
548
529
  const size = Math.min(width, height);
549
530
  const cx = size / 2;
550
531
  const cy = size / 2;
@@ -552,10 +533,10 @@ var PieDonutChart = React2.memo(
552
533
  const innerR = isDoughnut ? r * 0.5 : 0;
553
534
  const firstKey = entries[0]?.[0] ?? "";
554
535
  const colorOffset = hashString(firstKey);
555
- const maskRef = React2.useRef(null);
536
+ const maskRef = React.useRef(null);
556
537
  const maskR = r + 10;
557
538
  const maskCircumference = 2 * Math.PI * maskR;
558
- React2.useEffect(() => {
539
+ React.useEffect(() => {
559
540
  if (!animate || !maskRef.current) return;
560
541
  const el = maskRef.current;
561
542
  el.style.strokeDasharray = `${maskCircumference}`;
@@ -565,7 +546,7 @@ var PieDonutChart = React2.memo(
565
546
  el.style.strokeDashoffset = "0";
566
547
  });
567
548
  }, [animate, maskCircumference]);
568
- const sliceData = React2.useMemo(() => {
549
+ const sliceData = React.useMemo(() => {
569
550
  let angle0 = -Math.PI / 2;
570
551
  let cumulativeAngle = 0;
571
552
  return values.map((v, i) => {
@@ -600,7 +581,7 @@ var PieDonutChart = React2.memo(
600
581
  }, [values, total, cx, cy, r, innerR, labels]);
601
582
  const maskId = `pie-mask-${isDoughnut ? "d" : "p"}`;
602
583
  return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${size} ${size}`, className: "chart-svg chart-pie", children: [
603
- animate && /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsx2("mask", { id: maskId, children: /* @__PURE__ */ jsx2(
584
+ animate && /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("mask", { id: maskId, children: /* @__PURE__ */ jsx(
604
585
  "circle",
605
586
  {
606
587
  ref: maskRef,
@@ -613,56 +594,39 @@ var PieDonutChart = React2.memo(
613
594
  transform: `rotate(-90 ${cx} ${cy})`
614
595
  }
615
596
  ) }) }),
616
- /* @__PURE__ */ jsx2("g", { mask: animate ? `url(#${maskId})` : void 0, children: sliceData.map((s, i) => /* @__PURE__ */ jsx2("g", { children: /* @__PURE__ */ jsx2(
597
+ /* @__PURE__ */ jsx("g", { mask: animate ? `url(#${maskId})` : void 0, children: sliceData.map((s, i) => /* @__PURE__ */ jsx("g", { children: /* @__PURE__ */ jsx(
617
598
  "path",
618
599
  {
619
600
  d: s.d,
620
601
  fill: PIE_COLORS[(i + colorOffset) % PIE_COLORS.length],
621
- className: "chart-slice",
622
- onMouseEnter: (e) => onHover(e, `${s.label}: ${s.v} (${s.pct}%)`),
623
- onMouseMove: onMove,
624
- onMouseLeave: onLeave
602
+ className: "chart-slice"
625
603
  }
626
- ) }, i)) }),
627
- sliceData.map((s, i) => s.angle > 0.2 && /* @__PURE__ */ jsx2(
628
- "text",
629
- {
630
- x: s.lx,
631
- y: s.ly,
632
- className: `chart-pie-label ${animate ? "chart-pie-label-animate" : ""}`,
633
- style: animate ? { animationDelay: `${s.labelDelay}ms` } : void 0,
634
- textAnchor: "middle",
635
- dominantBaseline: "central",
636
- children: s.v
637
- },
638
- `label-${i}`
639
- ))
604
+ ) }, i)) })
640
605
  ] });
641
606
  }
642
607
  );
643
608
  PieDonutChart.displayName = "PieDonutChart";
644
- var ChartTooltipPortal = ({ clientX, clientY, visible, children }) => {
645
- const ref = React2.useRef(null);
646
- const [pos, setPos] = React2.useState({ left: 0, top: 0 });
647
- React2.useLayoutEffect(() => {
609
+ var ChartTooltip = ({ x, y, containerWidth, containerHeight, children }) => {
610
+ const ref = React.useRef(null);
611
+ const [pos, setPos] = React.useState({ left: 0, top: 0 });
612
+ React.useLayoutEffect(() => {
648
613
  const el = ref.current;
649
614
  if (!el) return;
650
615
  const w = el.offsetWidth;
651
616
  const h = el.offsetHeight;
652
- const vw = window.innerWidth;
653
- let left = clientX + TOOLTIP_OFFSET;
654
- let top = clientY - h - TOOLTIP_OFFSET;
655
- if (left + w > vw - 8) left = clientX - w - TOOLTIP_OFFSET;
656
- if (top < 8) top = clientY + TOOLTIP_OFFSET;
657
- if (left < 8) left = 8;
617
+ let left = x + TOOLTIP_OFFSET;
618
+ let top = y - h - TOOLTIP_OFFSET;
619
+ if (left + w > containerWidth) left = x - w - TOOLTIP_OFFSET;
620
+ if (top < 0) top = y + TOOLTIP_OFFSET;
621
+ if (left < 0) left = 0;
658
622
  setPos({ left, top });
659
- }, [clientX, clientY]);
660
- return /* @__PURE__ */ jsx2(
623
+ }, [x, y, containerWidth, containerHeight]);
624
+ return /* @__PURE__ */ jsx(
661
625
  "div",
662
626
  {
663
627
  ref,
664
- className: `chart-tooltip ${visible ? "chart-tooltip-show" : "chart-tooltip-hide"}`,
665
- style: { position: "fixed", left: pos.left, top: pos.top, zIndex: 1100 },
628
+ className: "chart-tooltip chart-tooltip-show",
629
+ style: { left: pos.left, top: pos.top },
666
630
  children
667
631
  }
668
632
  );
@@ -674,13 +638,13 @@ var ChartLegend = ({ data, labels, type }) => {
674
638
  const total = values.reduce((a, b) => a + b, 0) || 1;
675
639
  const firstKey = entries[0]?.[0] ?? "";
676
640
  const colorOffset = hashString(firstKey);
677
- return /* @__PURE__ */ jsx2("div", { className: "chart-legend", children: values.map((v, i) => {
641
+ return /* @__PURE__ */ jsx("div", { className: "chart-legend", children: values.map((v, i) => {
678
642
  const pct = Math.round(v / total * 100);
679
643
  const color = PIE_COLORS[(i + colorOffset) % PIE_COLORS.length];
680
644
  return /* @__PURE__ */ jsxs("div", { className: "chart-legend-item", children: [
681
- /* @__PURE__ */ jsx2("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
645
+ /* @__PURE__ */ jsx("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
682
646
  /* @__PURE__ */ jsxs("div", { className: "chart-legend-text", children: [
683
- /* @__PURE__ */ jsx2("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
647
+ /* @__PURE__ */ jsx("span", { className: "chart-legend-label", children: labels[i] || `${i + 1}` }),
684
648
  /* @__PURE__ */ jsxs("span", { className: "chart-legend-value", children: [
685
649
  v.toLocaleString(),
686
650
  "(",
@@ -691,37 +655,37 @@ var ChartLegend = ({ data, labels, type }) => {
691
655
  ] }, i);
692
656
  }) });
693
657
  }
694
- return /* @__PURE__ */ jsx2("div", { className: "chart-legend", children: entries.map(([key], di) => {
658
+ return /* @__PURE__ */ jsx("div", { className: "chart-legend", children: entries.map(([key], di) => {
695
659
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
696
660
  const color = palette[2];
697
661
  const values = entries[di][1];
698
662
  const sum = values.reduce((a, b) => a + b, 0);
699
663
  return /* @__PURE__ */ jsxs("div", { className: "chart-legend-item", children: [
700
- /* @__PURE__ */ jsx2("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
664
+ /* @__PURE__ */ jsx("span", { className: "chart-legend-dot", style: { backgroundColor: color } }),
701
665
  /* @__PURE__ */ jsxs("div", { className: "chart-legend-text", children: [
702
- /* @__PURE__ */ jsx2("span", { className: "chart-legend-label", children: key }),
703
- /* @__PURE__ */ jsx2("span", { className: "chart-legend-value", children: sum.toLocaleString() })
666
+ /* @__PURE__ */ jsx("span", { className: "chart-legend-label", children: key }),
667
+ /* @__PURE__ */ jsx("span", { className: "chart-legend-value", children: sum.toLocaleString() })
704
668
  ] })
705
669
  ] }, di);
706
670
  }) });
707
671
  };
708
- var Chart = React2.memo((props) => {
672
+ var Chart = React.memo((props) => {
709
673
  const { type, data, labels, tooltip: showTooltip = true } = props;
710
- const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
674
+ const { tooltip, show, showAt, hide, move, containerRef } = useChartTooltip(showTooltip);
711
675
  const { width, height } = useChartSize(containerRef);
712
- const stableData = React2.useMemo(() => data, [JSON.stringify(data)]);
713
- const stableLabels = React2.useMemo(() => labels, [JSON.stringify(labels)]);
714
- const dataKey = React2.useMemo(() => JSON.stringify(labels), [labels]);
676
+ const stableData = React.useMemo(() => data, [JSON.stringify(data)]);
677
+ const stableLabels = React.useMemo(() => labels, [JSON.stringify(labels)]);
678
+ const dataKey = React.useMemo(() => JSON.stringify(labels), [labels]);
715
679
  const animate = useChartAnimation(containerRef, dataKey);
716
680
  const ready = width > 0 && height > 0;
717
681
  return /* @__PURE__ */ jsxs("div", { className: "lib-xplat-chart", ref: containerRef, children: [
718
- ready && type === "line" && /* @__PURE__ */ jsx2(LineChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
719
- ready && type === "curve" && /* @__PURE__ */ jsx2(CurveChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
720
- ready && type === "bar" && /* @__PURE__ */ jsx2(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
721
- ready && type === "pie" && /* @__PURE__ */ jsx2(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onMove: move, onLeave: hide }),
722
- ready && type === "doughnut" && /* @__PURE__ */ jsx2(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
723
- ready && (type === "bar" || type === "pie" || type === "doughnut") && /* @__PURE__ */ jsx2(ChartLegend, { data: stableData, labels: stableLabels, type }),
724
- tooltip.content && /* @__PURE__ */ jsx2(Portal_default, { children: /* @__PURE__ */ jsx2(ChartTooltipPortal, { clientX: tooltip.clientX, clientY: tooltip.clientY, visible: tooltip.visible, children: tooltip.content }) })
682
+ ready && type === "line" && /* @__PURE__ */ jsx(LineChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
683
+ ready && type === "curve" && /* @__PURE__ */ jsx(CurveChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
684
+ ready && type === "bar" && /* @__PURE__ */ jsx(BarChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
685
+ ready && type === "pie" && /* @__PURE__ */ jsx(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
686
+ ready && type === "doughnut" && /* @__PURE__ */ jsx(PieDonutChart, { data: stableData, labels: stableLabels, width, height, animate, isDoughnut: true, onHover: show, onShowAt: showAt, onMove: move, onLeave: hide }),
687
+ ready && (type === "pie" || type === "doughnut") && /* @__PURE__ */ jsx(ChartLegend, { data: stableData, labels: stableLabels, type }),
688
+ tooltip.visible && tooltip.content && /* @__PURE__ */ jsx(ChartTooltip, { x: tooltip.x, y: tooltip.y, containerWidth: width, containerHeight: height, children: tooltip.content })
725
689
  ] });
726
690
  });
727
691
  Chart.displayName = "Chart";