@x-plat/design-system 0.5.12 → 0.5.14

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.
@@ -2151,29 +2151,50 @@ var toSmoothPath = (points) => {
2151
2151
  }
2152
2152
  return d;
2153
2153
  };
2154
+ var RESIZE_SETTLE_MS = 150;
2154
2155
  var useChartSize = (ref) => {
2155
2156
  const [size, setSize] = import_react5.default.useState({ width: 0, height: 0 });
2157
+ const isResizing = import_react5.default.useRef(false);
2158
+ const settleTimer = import_react5.default.useRef(0);
2159
+ const lastSize = import_react5.default.useRef({ width: 0, height: 0 });
2160
+ const initialRef = import_react5.default.useRef(true);
2156
2161
  import_react5.default.useEffect(() => {
2157
2162
  const el = ref.current;
2158
2163
  if (!el) return;
2159
- let rafId = 0;
2160
2164
  const observer = new ResizeObserver((entries) => {
2161
- cancelAnimationFrame(rafId);
2162
- rafId = requestAnimationFrame(() => {
2163
- const entry = entries[0];
2164
- if (!entry) return;
2165
- const { width, height } = entry.contentRect;
2166
- const w = Math.floor(width);
2167
- const h = Math.floor(height);
2168
- setSize((prev) => prev.width === w && prev.height === h ? prev : { width: w, height: h });
2169
- });
2165
+ const entry = entries[0];
2166
+ if (!entry) return;
2167
+ const { width, height } = entry.contentRect;
2168
+ const w = Math.floor(width);
2169
+ const h = Math.floor(height);
2170
+ if (w === lastSize.current.width && h === lastSize.current.height) return;
2171
+ lastSize.current = { width: w, height: h };
2172
+ if (initialRef.current) {
2173
+ initialRef.current = false;
2174
+ setSize({ width: w, height: h });
2175
+ return;
2176
+ }
2177
+ isResizing.current = true;
2178
+ if (el.firstElementChild) {
2179
+ const svg = el.firstElementChild;
2180
+ svg.style.transformOrigin = "0 0";
2181
+ svg.style.transform = `scale(${w / (size.width || w)}, ${h / (size.height || h)})`;
2182
+ }
2183
+ window.clearTimeout(settleTimer.current);
2184
+ settleTimer.current = window.setTimeout(() => {
2185
+ isResizing.current = false;
2186
+ if (el.firstElementChild) {
2187
+ el.firstElementChild.style.transform = "";
2188
+ }
2189
+ setSize({ width: w, height: h });
2190
+ }, RESIZE_SETTLE_MS);
2170
2191
  });
2171
2192
  observer.observe(el);
2172
2193
  return () => {
2173
- cancelAnimationFrame(rafId);
2194
+ window.clearTimeout(settleTimer.current);
2174
2195
  observer.disconnect();
2175
2196
  };
2176
- }, [ref]);
2197
+ }, [ref, size.width, size.height]);
2177
2198
  return size;
2178
2199
  };
2179
2200
  var useChartTooltip = (enabled) => {
@@ -2365,17 +2386,19 @@ var BarChart = import_react5.default.memo(({ data, labels, width, height, onHove
2365
2386
  const chartW = width - PADDING.left - PADDING.right;
2366
2387
  const chartH = height - PADDING.top - PADDING.bottom;
2367
2388
  const groupW = chartW / count;
2368
- const barW = Math.max(1, Math.min(32, groupW * 0.7 / groupCount));
2389
+ const barGap = groupCount > 1 ? 2 : 0;
2390
+ const barW = Math.max(1, Math.min(32, (groupW * 0.7 - barGap * (groupCount - 1)) / groupCount));
2369
2391
  const bars = import_react5.default.useMemo(
2370
2392
  () => entries.map(
2371
2393
  ([, values], di) => values.map((v, i) => {
2394
+ const totalBarsW = barW * groupCount + barGap * (groupCount - 1);
2372
2395
  const h = Math.max(0, v / maxVal * chartH);
2373
- const x = PADDING.left + groupW * i + (groupW - barW * groupCount) / 2 + barW * di;
2396
+ const x = PADDING.left + groupW * i + (groupW - totalBarsW) / 2 + (barW + barGap) * di;
2374
2397
  const y = PADDING.top + chartH - h;
2375
2398
  return { x, y, w: barW, h, v };
2376
2399
  })
2377
2400
  ),
2378
- [entries, maxVal, chartH, groupW, barW, groupCount]
2401
+ [entries, maxVal, chartH, groupW, barW, barGap, groupCount]
2379
2402
  );
2380
2403
  const barLabelStep = getLabelStep(count, chartW);
2381
2404
  return /* @__PURE__ */ (0, import_jsx_runtime305.jsxs)("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
@@ -2387,22 +2410,22 @@ var BarChart = import_react5.default.memo(({ data, labels, width, height, onHove
2387
2410
  entries.map(([key], di) => {
2388
2411
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
2389
2412
  const color = palette[2];
2390
- return bars[di].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(
2391
- "rect",
2392
- {
2393
- x: b.x,
2394
- y: b.y,
2395
- width: b.w,
2396
- height: b.h,
2397
- rx: Math.min(4, b.w / 2),
2398
- fill: color,
2399
- className: "chart-bar",
2400
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
2401
- onMouseMove: onMove,
2402
- onMouseLeave: onLeave
2403
- },
2404
- `${di}-${i}`
2405
- ));
2413
+ return bars[di].map((b, i) => {
2414
+ const r2 = Math.min(4, b.w / 2);
2415
+ const d = b.h <= r2 ? `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 + r2} Q ${b.x} ${b.y} ${b.x + r2} ${b.y} H ${b.x + b.w - r2} Q ${b.x + b.w} ${b.y} ${b.x + b.w} ${b.y + r2} V ${b.y + b.h} Z`;
2416
+ return /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(
2417
+ "path",
2418
+ {
2419
+ d,
2420
+ fill: color,
2421
+ className: "chart-bar",
2422
+ onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
2423
+ onMouseMove: onMove,
2424
+ onMouseLeave: onLeave
2425
+ },
2426
+ `${di}-${i}`
2427
+ );
2428
+ });
2406
2429
  })
2407
2430
  ] });
2408
2431
  });
@@ -2489,20 +2512,22 @@ var TooltipBubble = ({ x, y, containerWidth, children }) => {
2489
2512
  }
2490
2513
  );
2491
2514
  };
2492
- var Chart = (props) => {
2515
+ var Chart = import_react5.default.memo((props) => {
2493
2516
  const { type, data, labels, tooltip: showTooltip = true } = props;
2494
2517
  const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
2495
2518
  const { width, height } = useChartSize(containerRef);
2519
+ const stableData = import_react5.default.useMemo(() => data, [JSON.stringify(data)]);
2520
+ const stableLabels = import_react5.default.useMemo(() => labels, [JSON.stringify(labels)]);
2496
2521
  const ready = width > 0 && height > 0;
2497
2522
  return /* @__PURE__ */ (0, import_jsx_runtime305.jsxs)("div", { className: "lib-xplat-chart", ref: containerRef, children: [
2498
- ready && type === "line" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(LineChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2499
- ready && type === "curve" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(CurveChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2500
- ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(BarChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2501
- ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2502
- ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data, labels, width, height, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
2523
+ ready && type === "line" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(LineChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2524
+ ready && type === "curve" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(CurveChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2525
+ ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(BarChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2526
+ ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2527
+ ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
2503
2528
  tooltip.visible && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(TooltipBubble, { x: tooltip.x, y: tooltip.y, containerWidth: width, children: tooltip.content })
2504
2529
  ] });
2505
- };
2530
+ });
2506
2531
  Chart.displayName = "Chart";
2507
2532
  var Chart_default = Chart;
2508
2533
 
@@ -2764,12 +2789,8 @@ var PortalContainerContext = import_react9.default.createContext(null);
2764
2789
  var PortalProvider = ({ container, children }) => /* @__PURE__ */ (0, import_jsx_runtime311.jsx)(PortalContainerContext.Provider, { value: container, children });
2765
2790
  var Portal = ({ children }) => {
2766
2791
  const contextContainer = import_react9.default.useContext(PortalContainerContext);
2767
- const [fallback, setFallback] = import_react9.default.useState(null);
2768
- import_react9.default.useEffect(() => {
2769
- if (!contextContainer) setFallback(document.body);
2770
- }, [contextContainer]);
2771
- const container = contextContainer ?? fallback;
2772
- if (!container) return null;
2792
+ if (typeof document === "undefined") return null;
2793
+ const container = contextContainer ?? document.body;
2773
2794
  return import_react_dom.default.createPortal(children, container);
2774
2795
  };
2775
2796
  Portal.displayName = "Portal";
@@ -3397,15 +3418,15 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3397
3418
  direction
3398
3419
  });
3399
3420
  }, [triggerRef, popRef]);
3421
+ import_react18.default.useLayoutEffect(() => {
3422
+ if (!enabled) return;
3423
+ calculatePosition();
3424
+ }, [calculatePosition, enabled]);
3400
3425
  import_react18.default.useEffect(() => {
3401
3426
  if (!enabled) return;
3402
- const raf = requestAnimationFrame(() => {
3403
- calculatePosition();
3404
- });
3405
3427
  window.addEventListener("resize", calculatePosition);
3406
3428
  window.addEventListener("scroll", calculatePosition, true);
3407
3429
  return () => {
3408
- cancelAnimationFrame(raf);
3409
3430
  window.removeEventListener("resize", calculatePosition);
3410
3431
  window.removeEventListener("scroll", calculatePosition, true);
3411
3432
  };
@@ -1810,6 +1810,8 @@
1810
1810
  display: block;
1811
1811
  width: 100%;
1812
1812
  height: 100%;
1813
+ will-change: transform;
1814
+ contain: layout style paint;
1813
1815
  }
1814
1816
  .lib-xplat-chart .chart-grid {
1815
1817
  stroke: var(--semantic-border-subtle);
@@ -2063,29 +2063,50 @@ var toSmoothPath = (points) => {
2063
2063
  }
2064
2064
  return d;
2065
2065
  };
2066
+ var RESIZE_SETTLE_MS = 150;
2066
2067
  var useChartSize = (ref) => {
2067
2068
  const [size, setSize] = React5.useState({ width: 0, height: 0 });
2069
+ const isResizing = React5.useRef(false);
2070
+ const settleTimer = React5.useRef(0);
2071
+ const lastSize = React5.useRef({ width: 0, height: 0 });
2072
+ const initialRef = React5.useRef(true);
2068
2073
  React5.useEffect(() => {
2069
2074
  const el = ref.current;
2070
2075
  if (!el) return;
2071
- let rafId = 0;
2072
2076
  const observer = new ResizeObserver((entries) => {
2073
- cancelAnimationFrame(rafId);
2074
- rafId = requestAnimationFrame(() => {
2075
- const entry = entries[0];
2076
- if (!entry) return;
2077
- const { width, height } = entry.contentRect;
2078
- const w = Math.floor(width);
2079
- const h = Math.floor(height);
2080
- setSize((prev) => prev.width === w && prev.height === h ? prev : { width: w, height: h });
2081
- });
2077
+ const entry = entries[0];
2078
+ if (!entry) return;
2079
+ const { width, height } = entry.contentRect;
2080
+ const w = Math.floor(width);
2081
+ const h = Math.floor(height);
2082
+ if (w === lastSize.current.width && h === lastSize.current.height) return;
2083
+ lastSize.current = { width: w, height: h };
2084
+ if (initialRef.current) {
2085
+ initialRef.current = false;
2086
+ setSize({ width: w, height: h });
2087
+ return;
2088
+ }
2089
+ isResizing.current = true;
2090
+ if (el.firstElementChild) {
2091
+ const svg = el.firstElementChild;
2092
+ svg.style.transformOrigin = "0 0";
2093
+ svg.style.transform = `scale(${w / (size.width || w)}, ${h / (size.height || h)})`;
2094
+ }
2095
+ window.clearTimeout(settleTimer.current);
2096
+ settleTimer.current = window.setTimeout(() => {
2097
+ isResizing.current = false;
2098
+ if (el.firstElementChild) {
2099
+ el.firstElementChild.style.transform = "";
2100
+ }
2101
+ setSize({ width: w, height: h });
2102
+ }, RESIZE_SETTLE_MS);
2082
2103
  });
2083
2104
  observer.observe(el);
2084
2105
  return () => {
2085
- cancelAnimationFrame(rafId);
2106
+ window.clearTimeout(settleTimer.current);
2086
2107
  observer.disconnect();
2087
2108
  };
2088
- }, [ref]);
2109
+ }, [ref, size.width, size.height]);
2089
2110
  return size;
2090
2111
  };
2091
2112
  var useChartTooltip = (enabled) => {
@@ -2277,17 +2298,19 @@ var BarChart = React5.memo(({ data, labels, width, height, onHover, onMove, onLe
2277
2298
  const chartW = width - PADDING.left - PADDING.right;
2278
2299
  const chartH = height - PADDING.top - PADDING.bottom;
2279
2300
  const groupW = chartW / count;
2280
- const barW = Math.max(1, Math.min(32, groupW * 0.7 / groupCount));
2301
+ const barGap = groupCount > 1 ? 2 : 0;
2302
+ const barW = Math.max(1, Math.min(32, (groupW * 0.7 - barGap * (groupCount - 1)) / groupCount));
2281
2303
  const bars = React5.useMemo(
2282
2304
  () => entries.map(
2283
2305
  ([, values], di) => values.map((v, i) => {
2306
+ const totalBarsW = barW * groupCount + barGap * (groupCount - 1);
2284
2307
  const h = Math.max(0, v / maxVal * chartH);
2285
- const x = PADDING.left + groupW * i + (groupW - barW * groupCount) / 2 + barW * di;
2308
+ const x = PADDING.left + groupW * i + (groupW - totalBarsW) / 2 + (barW + barGap) * di;
2286
2309
  const y = PADDING.top + chartH - h;
2287
2310
  return { x, y, w: barW, h, v };
2288
2311
  })
2289
2312
  ),
2290
- [entries, maxVal, chartH, groupW, barW, groupCount]
2313
+ [entries, maxVal, chartH, groupW, barW, barGap, groupCount]
2291
2314
  );
2292
2315
  const barLabelStep = getLabelStep(count, chartW);
2293
2316
  return /* @__PURE__ */ jsxs196("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
@@ -2299,22 +2322,22 @@ var BarChart = React5.memo(({ data, labels, width, height, onHover, onMove, onLe
2299
2322
  entries.map(([key], di) => {
2300
2323
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
2301
2324
  const color = palette[2];
2302
- return bars[di].map((b, i) => /* @__PURE__ */ jsx305(
2303
- "rect",
2304
- {
2305
- x: b.x,
2306
- y: b.y,
2307
- width: b.w,
2308
- height: b.h,
2309
- rx: Math.min(4, b.w / 2),
2310
- fill: color,
2311
- className: "chart-bar",
2312
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
2313
- onMouseMove: onMove,
2314
- onMouseLeave: onLeave
2315
- },
2316
- `${di}-${i}`
2317
- ));
2325
+ return bars[di].map((b, i) => {
2326
+ const r2 = Math.min(4, b.w / 2);
2327
+ const d = b.h <= r2 ? `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 + r2} Q ${b.x} ${b.y} ${b.x + r2} ${b.y} H ${b.x + b.w - r2} Q ${b.x + b.w} ${b.y} ${b.x + b.w} ${b.y + r2} V ${b.y + b.h} Z`;
2328
+ return /* @__PURE__ */ jsx305(
2329
+ "path",
2330
+ {
2331
+ d,
2332
+ fill: color,
2333
+ className: "chart-bar",
2334
+ onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
2335
+ onMouseMove: onMove,
2336
+ onMouseLeave: onLeave
2337
+ },
2338
+ `${di}-${i}`
2339
+ );
2340
+ });
2318
2341
  })
2319
2342
  ] });
2320
2343
  });
@@ -2401,20 +2424,22 @@ var TooltipBubble = ({ x, y, containerWidth, children }) => {
2401
2424
  }
2402
2425
  );
2403
2426
  };
2404
- var Chart = (props) => {
2427
+ var Chart = React5.memo((props) => {
2405
2428
  const { type, data, labels, tooltip: showTooltip = true } = props;
2406
2429
  const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
2407
2430
  const { width, height } = useChartSize(containerRef);
2431
+ const stableData = React5.useMemo(() => data, [JSON.stringify(data)]);
2432
+ const stableLabels = React5.useMemo(() => labels, [JSON.stringify(labels)]);
2408
2433
  const ready = width > 0 && height > 0;
2409
2434
  return /* @__PURE__ */ jsxs196("div", { className: "lib-xplat-chart", ref: containerRef, children: [
2410
- ready && type === "line" && /* @__PURE__ */ jsx305(LineChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2411
- ready && type === "curve" && /* @__PURE__ */ jsx305(CurveChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2412
- ready && type === "bar" && /* @__PURE__ */ jsx305(BarChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2413
- ready && type === "pie" && /* @__PURE__ */ jsx305(PieDonutChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
2414
- ready && type === "doughnut" && /* @__PURE__ */ jsx305(PieDonutChart, { data, labels, width, height, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
2435
+ ready && type === "line" && /* @__PURE__ */ jsx305(LineChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2436
+ ready && type === "curve" && /* @__PURE__ */ jsx305(CurveChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2437
+ ready && type === "bar" && /* @__PURE__ */ jsx305(BarChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2438
+ ready && type === "pie" && /* @__PURE__ */ jsx305(PieDonutChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
2439
+ ready && type === "doughnut" && /* @__PURE__ */ jsx305(PieDonutChart, { data: stableData, labels: stableLabels, width, height, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
2415
2440
  tooltip.visible && /* @__PURE__ */ jsx305(TooltipBubble, { x: tooltip.x, y: tooltip.y, containerWidth: width, children: tooltip.content })
2416
2441
  ] });
2417
- };
2442
+ });
2418
2443
  Chart.displayName = "Chart";
2419
2444
  var Chart_default = Chart;
2420
2445
 
@@ -2676,12 +2701,8 @@ var PortalContainerContext = React8.createContext(null);
2676
2701
  var PortalProvider = ({ container, children }) => /* @__PURE__ */ jsx311(PortalContainerContext.Provider, { value: container, children });
2677
2702
  var Portal = ({ children }) => {
2678
2703
  const contextContainer = React8.useContext(PortalContainerContext);
2679
- const [fallback, setFallback] = React8.useState(null);
2680
- React8.useEffect(() => {
2681
- if (!contextContainer) setFallback(document.body);
2682
- }, [contextContainer]);
2683
- const container = contextContainer ?? fallback;
2684
- if (!container) return null;
2704
+ if (typeof document === "undefined") return null;
2705
+ const container = contextContainer ?? document.body;
2685
2706
  return ReactDOM.createPortal(children, container);
2686
2707
  };
2687
2708
  Portal.displayName = "Portal";
@@ -3309,15 +3330,15 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
3309
3330
  direction
3310
3331
  });
3311
3332
  }, [triggerRef, popRef]);
3333
+ React17.useLayoutEffect(() => {
3334
+ if (!enabled) return;
3335
+ calculatePosition();
3336
+ }, [calculatePosition, enabled]);
3312
3337
  React17.useEffect(() => {
3313
3338
  if (!enabled) return;
3314
- const raf = requestAnimationFrame(() => {
3315
- calculatePosition();
3316
- });
3317
3339
  window.addEventListener("resize", calculatePosition);
3318
3340
  window.addEventListener("scroll", calculatePosition, true);
3319
3341
  return () => {
3320
- cancelAnimationFrame(raf);
3321
3342
  window.removeEventListener("resize", calculatePosition);
3322
3343
  window.removeEventListener("scroll", calculatePosition, true);
3323
3344
  };
package/dist/index.cjs CHANGED
@@ -6562,29 +6562,50 @@ var toSmoothPath = (points) => {
6562
6562
  }
6563
6563
  return d;
6564
6564
  };
6565
+ var RESIZE_SETTLE_MS = 150;
6565
6566
  var useChartSize = (ref) => {
6566
6567
  const [size, setSize] = import_react5.default.useState({ width: 0, height: 0 });
6568
+ const isResizing = import_react5.default.useRef(false);
6569
+ const settleTimer = import_react5.default.useRef(0);
6570
+ const lastSize = import_react5.default.useRef({ width: 0, height: 0 });
6571
+ const initialRef = import_react5.default.useRef(true);
6567
6572
  import_react5.default.useEffect(() => {
6568
6573
  const el = ref.current;
6569
6574
  if (!el) return;
6570
- let rafId = 0;
6571
6575
  const observer = new ResizeObserver((entries) => {
6572
- cancelAnimationFrame(rafId);
6573
- rafId = requestAnimationFrame(() => {
6574
- const entry = entries[0];
6575
- if (!entry) return;
6576
- const { width, height } = entry.contentRect;
6577
- const w = Math.floor(width);
6578
- const h = Math.floor(height);
6579
- setSize((prev) => prev.width === w && prev.height === h ? prev : { width: w, height: h });
6580
- });
6576
+ const entry = entries[0];
6577
+ if (!entry) return;
6578
+ const { width, height } = entry.contentRect;
6579
+ const w = Math.floor(width);
6580
+ const h = Math.floor(height);
6581
+ if (w === lastSize.current.width && h === lastSize.current.height) return;
6582
+ lastSize.current = { width: w, height: h };
6583
+ if (initialRef.current) {
6584
+ initialRef.current = false;
6585
+ setSize({ width: w, height: h });
6586
+ return;
6587
+ }
6588
+ isResizing.current = true;
6589
+ if (el.firstElementChild) {
6590
+ const svg = el.firstElementChild;
6591
+ svg.style.transformOrigin = "0 0";
6592
+ svg.style.transform = `scale(${w / (size.width || w)}, ${h / (size.height || h)})`;
6593
+ }
6594
+ window.clearTimeout(settleTimer.current);
6595
+ settleTimer.current = window.setTimeout(() => {
6596
+ isResizing.current = false;
6597
+ if (el.firstElementChild) {
6598
+ el.firstElementChild.style.transform = "";
6599
+ }
6600
+ setSize({ width: w, height: h });
6601
+ }, RESIZE_SETTLE_MS);
6581
6602
  });
6582
6603
  observer.observe(el);
6583
6604
  return () => {
6584
- cancelAnimationFrame(rafId);
6605
+ window.clearTimeout(settleTimer.current);
6585
6606
  observer.disconnect();
6586
6607
  };
6587
- }, [ref]);
6608
+ }, [ref, size.width, size.height]);
6588
6609
  return size;
6589
6610
  };
6590
6611
  var useChartTooltip = (enabled) => {
@@ -6776,17 +6797,19 @@ var BarChart = import_react5.default.memo(({ data, labels, width, height, onHove
6776
6797
  const chartW = width - PADDING.left - PADDING.right;
6777
6798
  const chartH = height - PADDING.top - PADDING.bottom;
6778
6799
  const groupW = chartW / count;
6779
- const barW = Math.max(1, Math.min(32, groupW * 0.7 / groupCount));
6800
+ const barGap = groupCount > 1 ? 2 : 0;
6801
+ const barW = Math.max(1, Math.min(32, (groupW * 0.7 - barGap * (groupCount - 1)) / groupCount));
6780
6802
  const bars = import_react5.default.useMemo(
6781
6803
  () => entries.map(
6782
6804
  ([, values], di) => values.map((v, i) => {
6805
+ const totalBarsW = barW * groupCount + barGap * (groupCount - 1);
6783
6806
  const h = Math.max(0, v / maxVal * chartH);
6784
- const x = PADDING.left + groupW * i + (groupW - barW * groupCount) / 2 + barW * di;
6807
+ const x = PADDING.left + groupW * i + (groupW - totalBarsW) / 2 + (barW + barGap) * di;
6785
6808
  const y = PADDING.top + chartH - h;
6786
6809
  return { x, y, w: barW, h, v };
6787
6810
  })
6788
6811
  ),
6789
- [entries, maxVal, chartH, groupW, barW, groupCount]
6812
+ [entries, maxVal, chartH, groupW, barW, barGap, groupCount]
6790
6813
  );
6791
6814
  const barLabelStep = getLabelStep(count, chartW);
6792
6815
  return /* @__PURE__ */ (0, import_jsx_runtime305.jsxs)("svg", { viewBox: `0 0 ${width} ${height}`, className: "chart-svg", children: [
@@ -6798,22 +6821,22 @@ var BarChart = import_react5.default.memo(({ data, labels, width, height, onHove
6798
6821
  entries.map(([key], di) => {
6799
6822
  const palette = getPalette(LINE_BAR_PALETTES, di, key);
6800
6823
  const color = palette[2];
6801
- return bars[di].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(
6802
- "rect",
6803
- {
6804
- x: b.x,
6805
- y: b.y,
6806
- width: b.w,
6807
- height: b.h,
6808
- rx: Math.min(4, b.w / 2),
6809
- fill: color,
6810
- className: "chart-bar",
6811
- onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
6812
- onMouseMove: onMove,
6813
- onMouseLeave: onLeave
6814
- },
6815
- `${di}-${i}`
6816
- ));
6824
+ return bars[di].map((b, i) => {
6825
+ const r2 = Math.min(4, b.w / 2);
6826
+ const d = b.h <= r2 ? `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 + r2} Q ${b.x} ${b.y} ${b.x + r2} ${b.y} H ${b.x + b.w - r2} Q ${b.x + b.w} ${b.y} ${b.x + b.w} ${b.y + r2} V ${b.y + b.h} Z`;
6827
+ return /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(
6828
+ "path",
6829
+ {
6830
+ d,
6831
+ fill: color,
6832
+ className: "chart-bar",
6833
+ onMouseEnter: (e) => onHover(e, `${key}: ${labels[i]} \u2014 ${b.v}`),
6834
+ onMouseMove: onMove,
6835
+ onMouseLeave: onLeave
6836
+ },
6837
+ `${di}-${i}`
6838
+ );
6839
+ });
6817
6840
  })
6818
6841
  ] });
6819
6842
  });
@@ -6900,20 +6923,22 @@ var TooltipBubble = ({ x, y, containerWidth, children }) => {
6900
6923
  }
6901
6924
  );
6902
6925
  };
6903
- var Chart = (props) => {
6926
+ var Chart = import_react5.default.memo((props) => {
6904
6927
  const { type, data, labels, tooltip: showTooltip = true } = props;
6905
6928
  const { tooltip, show, hide, move, containerRef } = useChartTooltip(showTooltip);
6906
6929
  const { width, height } = useChartSize(containerRef);
6930
+ const stableData = import_react5.default.useMemo(() => data, [JSON.stringify(data)]);
6931
+ const stableLabels = import_react5.default.useMemo(() => labels, [JSON.stringify(labels)]);
6907
6932
  const ready = width > 0 && height > 0;
6908
6933
  return /* @__PURE__ */ (0, import_jsx_runtime305.jsxs)("div", { className: "lib-xplat-chart", ref: containerRef, children: [
6909
- ready && type === "line" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(LineChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
6910
- ready && type === "curve" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(CurveChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
6911
- ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(BarChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
6912
- ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data, labels, width, height, onHover: show, onMove: move, onLeave: hide }),
6913
- ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data, labels, width, height, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
6934
+ ready && type === "line" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(LineChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
6935
+ ready && type === "curve" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(CurveChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
6936
+ ready && type === "bar" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(BarChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
6937
+ ready && type === "pie" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, onHover: show, onMove: move, onLeave: hide }),
6938
+ ready && type === "doughnut" && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(PieDonutChart, { data: stableData, labels: stableLabels, width, height, isDoughnut: true, onHover: show, onMove: move, onLeave: hide }),
6914
6939
  tooltip.visible && /* @__PURE__ */ (0, import_jsx_runtime305.jsx)(TooltipBubble, { x: tooltip.x, y: tooltip.y, containerWidth: width, children: tooltip.content })
6915
6940
  ] });
6916
- };
6941
+ });
6917
6942
  Chart.displayName = "Chart";
6918
6943
  var Chart_default = Chart;
6919
6944
 
@@ -7188,12 +7213,8 @@ var PortalContainerContext = import_react9.default.createContext(null);
7188
7213
  var PortalProvider = ({ container, children }) => /* @__PURE__ */ (0, import_jsx_runtime311.jsx)(PortalContainerContext.Provider, { value: container, children });
7189
7214
  var Portal = ({ children }) => {
7190
7215
  const contextContainer = import_react9.default.useContext(PortalContainerContext);
7191
- const [fallback, setFallback] = import_react9.default.useState(null);
7192
- import_react9.default.useEffect(() => {
7193
- if (!contextContainer) setFallback(document.body);
7194
- }, [contextContainer]);
7195
- const container = contextContainer ?? fallback;
7196
- if (!container) return null;
7216
+ if (typeof document === "undefined") return null;
7217
+ const container = contextContainer ?? document.body;
7197
7218
  return import_react_dom.default.createPortal(children, container);
7198
7219
  };
7199
7220
  Portal.displayName = "Portal";
@@ -7821,15 +7842,15 @@ var useAutoPosition = (triggerRef, popRef, enabled = true) => {
7821
7842
  direction
7822
7843
  });
7823
7844
  }, [triggerRef, popRef]);
7845
+ import_react18.default.useLayoutEffect(() => {
7846
+ if (!enabled) return;
7847
+ calculatePosition();
7848
+ }, [calculatePosition, enabled]);
7824
7849
  import_react18.default.useEffect(() => {
7825
7850
  if (!enabled) return;
7826
- const raf = requestAnimationFrame(() => {
7827
- calculatePosition();
7828
- });
7829
7851
  window.addEventListener("resize", calculatePosition);
7830
7852
  window.addEventListener("scroll", calculatePosition, true);
7831
7853
  return () => {
7832
- cancelAnimationFrame(raf);
7833
7854
  window.removeEventListener("resize", calculatePosition);
7834
7855
  window.removeEventListener("scroll", calculatePosition, true);
7835
7856
  };
package/dist/index.css CHANGED
@@ -1810,6 +1810,8 @@
1810
1810
  display: block;
1811
1811
  width: 100%;
1812
1812
  height: 100%;
1813
+ will-change: transform;
1814
+ contain: layout style paint;
1813
1815
  }
1814
1816
  .lib-xplat-chart .chart-grid {
1815
1817
  stroke: var(--semantic-border-subtle);