@agions/taroviz 1.7.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +7 -3
  2. package/dist/cjs/index.js +1 -1
  3. package/dist/esm/index.js +960 -136
  4. package/package.json +1 -1
  5. package/src/adapters/__tests__/index.test.ts +4 -2
  6. package/src/adapters/h5/index.ts +16 -0
  7. package/src/adapters/types.ts +28 -120
  8. package/src/charts/boxplot/types.ts +5 -3
  9. package/src/charts/common/BaseChartWrapper.tsx +193 -32
  10. package/src/charts/liquid/index.tsx +6 -5
  11. package/src/charts/liquid/types.ts +4 -4
  12. package/src/charts/parallel/types.ts +6 -3
  13. package/src/charts/tree/types.ts +4 -4
  14. package/src/charts/types.ts +1 -1
  15. package/src/core/animation/AnimationManager.ts +69 -42
  16. package/src/core/components/Annotation.tsx +12 -10
  17. package/src/core/components/BaseChart.tsx +75 -12
  18. package/src/core/components/ErrorBoundary.tsx +30 -17
  19. package/src/core/components/LazyChart.tsx +14 -9
  20. package/src/core/themes/ThemeManager.ts +33 -0
  21. package/src/core/types/common.ts +21 -110
  22. package/src/core/types/index.ts +4 -135
  23. package/src/core/types/platform.ts +38 -230
  24. package/src/core/utils/chartUtils.ts +8 -3
  25. package/src/core/utils/export/ExportUtils.ts +10 -1
  26. package/src/core/utils/performance/PerformanceAnalyzer.ts +21 -1
  27. package/src/core/utils/performance/types.ts +5 -0
  28. package/src/hooks/__tests__/index.test.tsx +7 -5
  29. package/src/hooks/index.ts +23 -1
  30. package/src/hooks/useAnimation.ts +427 -0
  31. package/src/hooks/useChartHistory.ts +273 -0
  32. package/src/hooks/useChartSelection.ts +350 -0
  33. package/src/hooks/usePerformance.ts +291 -0
  34. package/src/themes/__tests__/index.test.ts +7 -13
package/dist/esm/index.js CHANGED
@@ -346,6 +346,21 @@ var H5Adapter = /** @class */function () {
346
346
  this.instance = null;
347
347
  }
348
348
  };
349
+ /**
350
+ * 触发图表行为
351
+ */
352
+ H5Adapter.prototype.dispatchAction = function (payload) {
353
+ if (this.instance) {
354
+ this.instance.dispatchAction(payload);
355
+ }
356
+ };
357
+ /**
358
+ * 获取DataURL
359
+ */
360
+ H5Adapter.prototype.getDataURL = function (opts) {
361
+ var _a;
362
+ return (_a = this.instance) === null || _a === void 0 ? void 0 : _a.getDataURL(opts);
363
+ };
349
364
  /**
350
365
  * 处理图表大小变化
351
366
  */
@@ -685,16 +700,12 @@ var ChartEventType;
685
700
  ChartEventType["BRUSHSELECTED"] = "brushselected";
686
701
  ChartEventType["RENDERED"] = "rendered";
687
702
  ChartEventType["FINISHED"] = "finished";
688
- // 自定义事件
689
703
  ChartEventType["CHART_READY"] = "chartReady";
690
704
  ChartEventType["CHART_RESIZE"] = "chartResize";
691
705
  ChartEventType["CHART_ERROR"] = "chartError";
692
706
  ChartEventType["CHART_DISPOSE"] = "chartDispose";
693
707
  })(ChartEventType || (ChartEventType = {}));
694
708
  ;// ./src/core/types/platform.ts
695
- /**
696
- * 支持的平台类型
697
- */
698
709
  var PlatformType;
699
710
  (function (PlatformType) {
700
711
  PlatformType["H5"] = "h5";
@@ -1579,43 +1590,171 @@ var __generator = undefined && undefined.__generator || function (thisArg, body)
1579
1590
  /**
1580
1591
  * 基础图表包装组件
1581
1592
  * 提供统一的图表初始化、渲染和生命周期管理
1593
+ *
1594
+ * 无障碍支持 (WCAG):
1595
+ * - role="application" + keyboard navigation for zoom/pan
1596
+ * - Hidden data table with aria-live for screen readers
1597
+ * - Respects prefers-reduced-motion
1582
1598
  */
1583
1599
 
1584
1600
 
1585
1601
 
1586
1602
 
1587
- /**
1588
- * 基础图表包装组件
1589
- * 提供统一的图表初始化、渲染和生命周期管理
1590
- */
1603
+ /** Extract series data from an ECharts option for screen reader exposure */
1604
+ function extractSeriesData(option) {
1605
+ var opt = option;
1606
+ if (!(opt === null || opt === void 0 ? void 0 : opt.series) || !Array.isArray(opt.series)) return [];
1607
+ return opt.series.filter(function (s) {
1608
+ return (s === null || s === void 0 ? void 0 : s.data) && Array.isArray(s.data);
1609
+ }).map(function (s) {
1610
+ return {
1611
+ name: s.name || '系列',
1612
+ data: s.data
1613
+ };
1614
+ });
1615
+ }
1616
+ /** Build a human-readable aria-label from chart option */
1617
+ function buildAriaLabel(chartType, option) {
1618
+ var seriesData = extractSeriesData(option);
1619
+ if (!seriesData.length) {
1620
+ return chartType === 'chart' ? '空图表' : "".concat(chartType, " \u7A7A\u56FE\u8868");
1621
+ }
1622
+ var totalPoints = seriesData.reduce(function (sum, s) {
1623
+ return sum + s.data.length;
1624
+ }, 0);
1625
+ var seriesNames = seriesData.map(function (s) {
1626
+ return s.name;
1627
+ }).join('、');
1628
+ return "".concat(chartType, "\u56FE\u8868\uFF0C\u5305\u542B").concat(seriesData.length, "\u4E2A\u7CFB\u5217\uFF08").concat(seriesNames, "\uFF09\uFF0C\u5171").concat(totalPoints, "\u4E2A\u6570\u636E\u70B9");
1629
+ }
1630
+ // ─── Keyboard navigation step sizes ───────────────────────────────────────
1631
+ var ZOOM_STEP = 5; // % per key press
1632
+ var PAN_STEP = 10; // % pan per arrow key
1591
1633
  var BaseChartWrapper = function (_a) {
1634
+ var _b, _c;
1592
1635
  var option = _a.option,
1593
- _b = _a.width,
1594
- width = _b === void 0 ? '100%' : _b,
1595
- _c = _a.height,
1596
- height = _c === void 0 ? '300px' : _c,
1636
+ _d = _a.width,
1637
+ width = _d === void 0 ? '100%' : _d,
1638
+ _e = _a.height,
1639
+ height = _e === void 0 ? '300px' : _e,
1597
1640
  theme = _a.theme,
1598
- _d = _a.style,
1599
- style = _d === void 0 ? {} : _d,
1600
- _e = _a.className,
1601
- className = _e === void 0 ? '' : _e,
1602
- _f = _a.autoResize,
1603
- autoResize = _f === void 0 ? true : _f,
1604
- _g = _a.loading,
1605
- loading = _g === void 0 ? false : _g,
1641
+ _f = _a.style,
1642
+ style = _f === void 0 ? {} : _f,
1643
+ _g = _a.className,
1644
+ className = _g === void 0 ? '' : _g,
1645
+ _h = _a.autoResize,
1646
+ autoResize = _h === void 0 ? true : _h,
1647
+ _j = _a.loading,
1648
+ loading = _j === void 0 ? false : _j,
1606
1649
  loadingOption = _a.loadingOption,
1607
1650
  onChartInit = _a.onChartInit,
1608
1651
  onChartReady = _a.onChartReady,
1609
- _h = _a.renderer,
1610
- renderer = _h === void 0 ? 'canvas' : _h,
1611
- _j = _a.onEvents,
1612
- onEvents = _j === void 0 ? {} : _j,
1613
- _k = _a.chartType,
1614
- chartType = _k === void 0 ? 'chart' : _k;
1652
+ _k = _a.renderer,
1653
+ renderer = _k === void 0 ? 'canvas' : _k,
1654
+ _l = _a.onEvents,
1655
+ onEvents = _l === void 0 ? {} : _l,
1656
+ _m = _a.chartType,
1657
+ chartType = _m === void 0 ? 'chart' : _m;
1615
1658
  var chartId = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)("".concat(chartType, "-").concat((0,_core_utils__WEBPACK_IMPORTED_MODULE_2__/* .uuid */ .uR)()));
1616
1659
  var chartInstance = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
1617
1660
  var containerRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
1618
- // 使用 useMemo 缓存适配器配置,并处理类型问题
1661
+ var isMountedRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(true);
1662
+ var cleanupRef = (0,react__WEBPACK_IMPORTED_MODULE_0__.useRef)(null);
1663
+ var tableId = (0,react__WEBPACK_IMPORTED_MODULE_0__.useId)(); // unique id for aria-describedby
1664
+ var seriesData = (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(function () {
1665
+ return extractSeriesData(option);
1666
+ }, [option]);
1667
+ var ariaLabel = (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(function () {
1668
+ return buildAriaLabel(chartType, option);
1669
+ }, [chartType, option]);
1670
+ // Keyboard handler for zoom/pan — attached to the chart container
1671
+ var handleKeyDown = (0,react__WEBPACK_IMPORTED_MODULE_0__.useCallback)(function (e) {
1672
+ var _a, _b, _c, _d, _e, _f;
1673
+ var instance = chartInstance.current;
1674
+ if (!instance) return;
1675
+ // ECharts dataZoom dispatch — works for any chart with dataZoom axis
1676
+ var dispatchZoom = function (startDelta, endDelta) {
1677
+ instance.dispatchAction({
1678
+ type: 'dataZoom',
1679
+ startDelta: startDelta,
1680
+ endDelta: endDelta
1681
+ });
1682
+ };
1683
+ // Home = reset zoom to full range
1684
+ if (e.key === 'Home') {
1685
+ e.preventDefault();
1686
+ instance.dispatchAction({
1687
+ type: 'dataZoom',
1688
+ start: 0,
1689
+ end: 100
1690
+ });
1691
+ return;
1692
+ }
1693
+ switch (e.key) {
1694
+ case '+':
1695
+ case '=':
1696
+ {
1697
+ e.preventDefault();
1698
+ // Zoom in (narrow range) — decrease end by ZOOM_STEP
1699
+ var end = instance.getOption();
1700
+ var dz = (_a = end === null || end === void 0 ? void 0 : end.dataZoom) === null || _a === void 0 ? void 0 : _a[0];
1701
+ if (dz) {
1702
+ var newEnd = Math.max(0, ((_b = dz.end) !== null && _b !== void 0 ? _b : 100) - ZOOM_STEP);
1703
+ var newStart = Math.max(0, ((_c = dz.start) !== null && _c !== void 0 ? _c : 0) - ZOOM_STEP);
1704
+ instance.dispatchAction({
1705
+ type: 'dataZoom',
1706
+ start: newStart,
1707
+ end: newEnd
1708
+ });
1709
+ }
1710
+ break;
1711
+ }
1712
+ case '-':
1713
+ case '_':
1714
+ {
1715
+ e.preventDefault();
1716
+ // Zoom out (expand range) — increase end by ZOOM_STEP
1717
+ var end = instance.getOption();
1718
+ var dz = (_d = end === null || end === void 0 ? void 0 : end.dataZoom) === null || _d === void 0 ? void 0 : _d[0];
1719
+ if (dz) {
1720
+ var newEnd = Math.min(100, ((_e = dz.end) !== null && _e !== void 0 ? _e : 100) + ZOOM_STEP);
1721
+ var newStart = Math.min(((_f = dz.start) !== null && _f !== void 0 ? _f : 0) + ZOOM_STEP, newEnd);
1722
+ instance.dispatchAction({
1723
+ type: 'dataZoom',
1724
+ start: newStart,
1725
+ end: newEnd
1726
+ });
1727
+ }
1728
+ break;
1729
+ }
1730
+ case 'ArrowLeft':
1731
+ {
1732
+ e.preventDefault();
1733
+ dispatchZoom(-PAN_STEP, 0);
1734
+ break;
1735
+ }
1736
+ case 'ArrowRight':
1737
+ {
1738
+ e.preventDefault();
1739
+ dispatchZoom(PAN_STEP, 0);
1740
+ break;
1741
+ }
1742
+ case 'ArrowUp':
1743
+ {
1744
+ e.preventDefault();
1745
+ dispatchZoom(0, -PAN_STEP);
1746
+ break;
1747
+ }
1748
+ case 'ArrowDown':
1749
+ {
1750
+ e.preventDefault();
1751
+ dispatchZoom(0, PAN_STEP);
1752
+ break;
1753
+ }
1754
+ // No default — let other keys pass through for accessibility tools
1755
+ }
1756
+ }, []);
1757
+ // Use memo to cache adapter config
1619
1758
  var adapterConfig = (0,react__WEBPACK_IMPORTED_MODULE_0__.useMemo)(function () {
1620
1759
  return (0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .processAdapterConfig */ .X)({
1621
1760
  canvasId: chartId.current,
@@ -1628,8 +1767,9 @@ var BaseChartWrapper = function (_a) {
1628
1767
  option: option
1629
1768
  });
1630
1769
  }, [width, height, theme, autoResize, renderer, option]);
1631
- // 处理图表初始化
1770
+ // Handle chart initialization
1632
1771
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {
1772
+ isMountedRef.current = true;
1633
1773
  var initChart = function () {
1634
1774
  return __awaiter(void 0, void 0, void 0, function () {
1635
1775
  var initConfig, adapter;
@@ -1638,18 +1778,21 @@ var BaseChartWrapper = function (_a) {
1638
1778
  case 0:
1639
1779
  initConfig = (0,_utils__WEBPACK_IMPORTED_MODULE_3__/* .processAdapterConfig */ .X)(__assign(__assign({}, adapterConfig), {
1640
1780
  onInit: function (instance) {
1781
+ if (!isMountedRef.current) {
1782
+ instance.dispose();
1783
+ return;
1784
+ }
1641
1785
  chartInstance.current = instance;
1642
- // 绑定事件
1643
1786
  if (onEvents) {
1644
- Object.keys(onEvents).forEach(function (eventName) {
1645
- instance.on(eventName, onEvents[eventName]);
1787
+ Object.entries(onEvents).forEach(function (_a) {
1788
+ var eventName = _a[0],
1789
+ handler = _a[1];
1790
+ instance.on(eventName, handler);
1646
1791
  });
1647
1792
  }
1648
- // 初始化回调
1649
1793
  if (onChartInit) {
1650
1794
  onChartInit(instance);
1651
1795
  }
1652
- // 准备好回调
1653
1796
  if (onChartReady) {
1654
1797
  onChartReady(instance);
1655
1798
  }
@@ -1658,41 +1801,43 @@ var BaseChartWrapper = function (_a) {
1658
1801
  return [4 /*yield*/, (0,_adapters__WEBPACK_IMPORTED_MODULE_1__/* .getAdapter */ .cK)(initConfig)];
1659
1802
  case 1:
1660
1803
  adapter = _a.sent();
1804
+ if (!isMountedRef.current) {
1805
+ return [2 /*return*/];
1806
+ }
1661
1807
  adapter.init();
1662
- // 返回清理函数
1663
- return [2 /*return*/, function () {
1808
+ cleanupRef.current = function () {
1664
1809
  if (chartInstance.current) {
1665
- // 解绑事件
1666
1810
  if (onEvents) {
1667
- Object.keys(onEvents).forEach(function (eventName) {
1668
- var _a;
1669
- (_a = chartInstance.current) === null || _a === void 0 ? void 0 : _a.off(eventName);
1811
+ Object.entries(onEvents).forEach(function (_a) {
1812
+ var eventName = _a[0];
1813
+ chartInstance.current.off(eventName);
1670
1814
  });
1671
1815
  }
1672
1816
  chartInstance.current.dispose();
1673
1817
  chartInstance.current = null;
1674
1818
  }
1675
- }];
1819
+ };
1820
+ return [2 /*return*/];
1676
1821
  }
1677
1822
  });
1678
1823
  });
1679
1824
  };
1680
- // 执行异步初始化并获取清理函数
1681
- var cleanupPromise = initChart();
1682
- // 返回清理函数
1825
+ initChart();
1683
1826
  return function () {
1684
- cleanupPromise.then(function (cleanup) {
1685
- return cleanup === null || cleanup === void 0 ? void 0 : cleanup();
1686
- });
1827
+ isMountedRef.current = false;
1828
+ if (cleanupRef.current) {
1829
+ cleanupRef.current();
1830
+ cleanupRef.current = null;
1831
+ }
1687
1832
  };
1688
1833
  }, [adapterConfig, onChartInit, onChartReady, onEvents]);
1689
- // 更新配置
1834
+ // Update config
1690
1835
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {
1691
1836
  if (chartInstance.current && option) {
1692
1837
  chartInstance.current.setOption(option, true);
1693
1838
  }
1694
1839
  }, [option]);
1695
- // 控制加载状态
1840
+ // Loading state
1696
1841
  (0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(function () {
1697
1842
  if (chartInstance.current) {
1698
1843
  if (loading) {
@@ -1702,16 +1847,51 @@ var BaseChartWrapper = function (_a) {
1702
1847
  }
1703
1848
  }
1704
1849
  }, [loading, loadingOption]);
1705
- // 自定义样式
1850
+ // Merged style
1706
1851
  var mergedStyle = __assign({
1707
1852
  width: typeof width === 'number' ? "".concat(width, "px") : width,
1708
1853
  height: typeof height === 'number' ? "".concat(height, "px") : height
1709
1854
  }, style);
1710
- return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
1855
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("table", {
1856
+ id: tableId,
1857
+ "aria-label": "".concat(chartType, " \u56FE\u8868\u6570\u636E"),
1858
+ style: {
1859
+ position: 'absolute',
1860
+ width: 1,
1861
+ height: 1,
1862
+ overflow: 'hidden',
1863
+ clip: 'rect(0,0,0,0)',
1864
+ clipPath: 'inset(50%)',
1865
+ whiteSpace: 'nowrap'
1866
+ },
1867
+ "aria-live": "polite",
1868
+ "aria-atomic": "false"
1869
+ }, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("caption", null, ariaLabel), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("thead", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", null, seriesData.map(function (s, i) {
1870
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("th", {
1871
+ key: i,
1872
+ scope: "col"
1873
+ }, s.name);
1874
+ }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tbody", null, Array.from({
1875
+ length: Math.min(20, (_c = (_b = seriesData[0]) === null || _b === void 0 ? void 0 : _b.data.length) !== null && _c !== void 0 ? _c : 0)
1876
+ }).map(function (_, rowIdx) {
1877
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("tr", {
1878
+ key: rowIdx
1879
+ }, seriesData.map(function (s, colIdx) {
1880
+ var _a;
1881
+ return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("td", {
1882
+ key: colIdx
1883
+ }, String((_a = s.data[rowIdx]) !== null && _a !== void 0 ? _a : ''));
1884
+ }));
1885
+ }))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
1711
1886
  className: "taroviz-".concat(chartType, " ").concat(className),
1712
1887
  style: mergedStyle,
1713
- ref: containerRef
1714
- });
1888
+ ref: containerRef,
1889
+ role: "application",
1890
+ "aria-label": ariaLabel,
1891
+ "aria-describedby": tableId,
1892
+ tabIndex: 0,
1893
+ onKeyDown: handleKeyDown
1894
+ }));
1715
1895
  };
1716
1896
  /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (BaseChartWrapper);
1717
1897
 
@@ -2212,20 +2392,31 @@ var __assign = undefined && undefined.__assign || function () {
2212
2392
  return __assign.apply(this, arguments);
2213
2393
  };
2214
2394
  /**
2215
- * 动画预设集合
2395
+ * Checks the OS/browser prefers-reduced-motion setting.
2396
+ * Returns true if the user has requested reduced motion.
2397
+ */
2398
+ function prefersReducedMotion() {
2399
+ if (typeof window === 'undefined') return false;
2400
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
2401
+ }
2402
+ /**
2403
+ * Professional animation presets following frontend-design-pro skill guidelines:
2404
+ * - Easing: cubic-bezier(0.16, 1, 0.3, 1) ("cubicOut") for natural deceleration
2405
+ * - Durations: 100-200ms micro, 300-500ms transitions, never >600ms
2406
+ * - NO bounce/elastic — anti-patterns that feel廉价 (cheap)
2216
2407
  */
2217
2408
  var DEFAULT_ANIMATION_PRESETS = [{
2218
2409
  name: 'default',
2219
- description: '默认动画配置',
2410
+ description: '默认动画配置 — 专业级 (300-500ms, cubicOut)',
2220
2411
  config: {
2221
2412
  enabled: true,
2222
- duration: 1000,
2413
+ duration: 400,
2223
2414
  easing: 'cubicOut',
2224
- appearDuration: 1200,
2415
+ appearDuration: 450,
2225
2416
  appearEasing: 'cubicOut',
2226
- updateDuration: 800,
2417
+ updateDuration: 300,
2227
2418
  updateEasing: 'cubicOut',
2228
- disappearDuration: 600,
2419
+ disappearDuration: 250,
2229
2420
  disappearEasing: 'cubicIn',
2230
2421
  threshold: 1000,
2231
2422
  progressive: true,
@@ -2233,70 +2424,76 @@ var DEFAULT_ANIMATION_PRESETS = [{
2233
2424
  }
2234
2425
  }, {
2235
2426
  name: 'fast',
2236
- description: '快速动画配置',
2427
+ description: '快速动画配置 — 微交互 (150-200ms)',
2237
2428
  config: {
2238
2429
  enabled: true,
2239
- duration: 500,
2240
- easing: 'linear',
2241
- appearDuration: 600,
2242
- appearEasing: 'linear',
2243
- updateDuration: 400,
2244
- updateEasing: 'linear',
2245
- disappearDuration: 300,
2246
- disappearEasing: 'linear',
2430
+ duration: 150,
2431
+ easing: 'cubicOut',
2432
+ appearDuration: 200,
2433
+ appearEasing: 'cubicOut',
2434
+ updateDuration: 150,
2435
+ updateEasing: 'cubicOut',
2436
+ disappearDuration: 100,
2437
+ disappearEasing: 'cubicIn',
2247
2438
  threshold: 2000,
2248
2439
  progressive: true,
2249
2440
  progressiveStep: 1000
2250
2441
  }
2251
2442
  }, {
2252
2443
  name: 'slow',
2253
- description: '慢速动画配置',
2444
+ description: '慢速动画配置 — 页面过渡 (500-600ms, capped)',
2254
2445
  config: {
2255
2446
  enabled: true,
2256
- duration: 2000,
2447
+ duration: 500,
2257
2448
  easing: 'cubicInOut',
2258
- appearDuration: 2400,
2449
+ appearDuration: 600,
2259
2450
  appearEasing: 'cubicInOut',
2260
- updateDuration: 1600,
2451
+ updateDuration: 400,
2261
2452
  updateEasing: 'cubicInOut',
2262
- disappearDuration: 1200,
2453
+ disappearDuration: 300,
2263
2454
  disappearEasing: 'cubicInOut',
2264
2455
  threshold: 500,
2265
2456
  progressive: true,
2266
2457
  progressiveStep: 250
2267
2458
  }
2268
- }, {
2459
+ },
2460
+ // DEPRECATED — bounce is an anti-pattern per frontend-design-pro skill
2461
+ {
2269
2462
  name: 'bounce',
2270
- description: '弹跳动画配置',
2463
+ description: '[已废弃] 弹跳动画 — 请使用 default 或 fast',
2271
2464
  config: {
2272
- enabled: true,
2273
- duration: 1500,
2274
- easing: 'bounceOut',
2275
- appearDuration: 1800,
2276
- appearEasing: 'bounceOut',
2277
- updateDuration: 1200,
2278
- updateEasing: 'bounceOut',
2279
- disappearDuration: 900,
2280
- disappearEasing: 'bounceIn',
2465
+ enabled: false,
2466
+ // disabled by default — anti-pattern
2467
+ duration: 0,
2468
+ easing: 'cubicOut',
2469
+ appearDuration: 0,
2470
+ appearEasing: 'cubicOut',
2471
+ updateDuration: 0,
2472
+ updateEasing: 'cubicOut',
2473
+ disappearDuration: 0,
2474
+ disappearEasing: 'cubicIn',
2281
2475
  threshold: 500,
2282
- progressive: true,
2476
+ progressive: false,
2283
2477
  progressiveStep: 250
2284
2478
  }
2285
- }, {
2479
+ },
2480
+ // DEPRECATED — elastic is an anti-pattern per frontend-design-pro skill
2481
+ {
2286
2482
  name: 'elastic',
2287
- description: '弹性动画配置',
2483
+ description: '[已废弃] 弹性动画 — 请使用 default 或 fast',
2288
2484
  config: {
2289
- enabled: true,
2290
- duration: 1500,
2291
- easing: 'elasticOut',
2292
- appearDuration: 1800,
2293
- appearEasing: 'elasticOut',
2294
- updateDuration: 1200,
2295
- updateEasing: 'elasticOut',
2296
- disappearDuration: 900,
2297
- disappearEasing: 'elasticIn',
2485
+ enabled: false,
2486
+ // disabled by default — anti-pattern
2487
+ duration: 0,
2488
+ easing: 'cubicOut',
2489
+ appearDuration: 0,
2490
+ appearEasing: 'cubicOut',
2491
+ updateDuration: 0,
2492
+ updateEasing: 'cubicOut',
2493
+ disappearDuration: 0,
2494
+ disappearEasing: 'cubicIn',
2298
2495
  threshold: 500,
2299
- progressive: true,
2496
+ progressive: false,
2300
2497
  progressiveStep: 250
2301
2498
  }
2302
2499
  }];
@@ -2392,6 +2589,7 @@ var AnimationManager = /** @class */function () {
2392
2589
  };
2393
2590
  /**
2394
2591
  * 根据数据量和动画类型获取优化后的动画配置
2592
+ * 尊重 prefers-reduced-motion 无障碍设置
2395
2593
  */
2396
2594
  AnimationManager.prototype.getOptimizedConfig = function (config, dataLength) {
2397
2595
  if (config === void 0) {
@@ -2402,6 +2600,16 @@ var AnimationManager = /** @class */function () {
2402
2600
  }
2403
2601
  // 合并配置:用户配置 > 默认配置
2404
2602
  var mergedConfig = __assign(__assign({}, this.defaultConfig), config);
2603
+ // Respect OS/browser prefers-reduced-motion setting (WCAG)
2604
+ if (prefersReducedMotion()) {
2605
+ return __assign(__assign({}, mergedConfig), {
2606
+ enabled: false,
2607
+ duration: 0,
2608
+ appearDuration: 0,
2609
+ updateDuration: 0,
2610
+ disappearDuration: 0
2611
+ });
2612
+ }
2405
2613
  // 根据数据量优化动画
2406
2614
  if (mergedConfig.threshold && dataLength > mergedConfig.threshold) {
2407
2615
  mergedConfig.enabled = false;
@@ -48788,9 +48996,18 @@ var PerformanceAnalyzer = /** @class */function () {
48788
48996
  }
48789
48997
  }
48790
48998
  /**
48791
- * 获取单例实例
48999
+ * 获取实例
49000
+ * - 传入 chartId 时:每个 chartId 获得独立实例(指标隔离)
49001
+ * - 不传 chartId 时:全局单例(向后兼容)
48792
49002
  */
48793
49003
  PerformanceAnalyzer.getInstance = function (config) {
49004
+ if (config === null || config === void 0 ? void 0 : config.chartId) {
49005
+ if (!PerformanceAnalyzer.instances.has(config.chartId)) {
49006
+ PerformanceAnalyzer.instances.set(config.chartId, new PerformanceAnalyzer(config));
49007
+ }
49008
+ return PerformanceAnalyzer.instances.get(config.chartId);
49009
+ }
49010
+ // Legacy: global singleton
48794
49011
  if (!PerformanceAnalyzer.instance) {
48795
49012
  PerformanceAnalyzer.instance = new PerformanceAnalyzer(config);
48796
49013
  }
@@ -48805,6 +49022,16 @@ var PerformanceAnalyzer = /** @class */function () {
48805
49022
  PerformanceAnalyzer.instance = null;
48806
49023
  }
48807
49024
  };
49025
+ /**
49026
+ * 重置所有图表实例(用于测试/清理)
49027
+ */
49028
+ PerformanceAnalyzer.resetAllInstances = function () {
49029
+ PerformanceAnalyzer.resetInstance();
49030
+ PerformanceAnalyzer.instances.forEach(function (analyzer) {
49031
+ return analyzer.stop();
49032
+ });
49033
+ PerformanceAnalyzer.instances.clear();
49034
+ };
48808
49035
  /**
48809
49036
  * 注册事件处理器
48810
49037
  */
@@ -49215,6 +49442,8 @@ var PerformanceAnalyzer = /** @class */function () {
49215
49442
  return this.analyze().score;
49216
49443
  };
49217
49444
  PerformanceAnalyzer.instance = null;
49445
+ // Per-chart isolated instances
49446
+ PerformanceAnalyzer.instances = new Map();
49218
49447
  return PerformanceAnalyzer;
49219
49448
  }();
49220
49449
 
@@ -49264,12 +49493,12 @@ function calculateDataLength(option) {
49264
49493
  function filterDataByKeys(data, filters) {
49265
49494
  if (!filters || Object.keys(filters).length === 0) return data;
49266
49495
  return data.filter(function (item) {
49267
- var _a, _b;
49268
- for (var _i = 0, _c = Object.entries(filters); _i < _c.length; _i++) {
49269
- var _d = _c[_i],
49270
- key = _d[0],
49271
- value = _d[1];
49272
- if (item[key] !== value && !((_b = (_a = item[key]) === null || _a === void 0 ? void 0 : _a.includes) === null || _b === void 0 ? void 0 : _b.call(_a, value))) return false;
49496
+ for (var _i = 0, _a = Object.entries(filters); _i < _a.length; _i++) {
49497
+ var _b = _a[_i],
49498
+ key = _b[0],
49499
+ value = _b[1];
49500
+ var itemVal = item[key];
49501
+ if (itemVal !== value && !(Array.isArray(itemVal) && itemVal.includes(value))) return false;
49273
49502
  }
49274
49503
  return true;
49275
49504
  });
@@ -49287,6 +49516,15 @@ var BaseChart_assign = undefined && undefined.__assign || function () {
49287
49516
  };
49288
49517
  return BaseChart_assign.apply(this, arguments);
49289
49518
  };
49519
+ var BaseChart_spreadArray = undefined && undefined.__spreadArray || function (to, from, pack) {
49520
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
49521
+ if (ar || !(i in from)) {
49522
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
49523
+ ar[i] = from[i];
49524
+ }
49525
+ }
49526
+ return to.concat(ar || Array.prototype.slice.call(from));
49527
+ };
49290
49528
  /**
49291
49529
  * TaroViz 基础图表组件
49292
49530
  * 所有图表组件的基类
@@ -49420,11 +49658,30 @@ var BaseChart = function (props) {
49420
49658
  });
49421
49659
  }
49422
49660
  }
49661
+ // Inject dataZoom when enableZoom is true (keyboard-accessible zoom)
49662
+ if (_enableZoom) {
49663
+ processed = JSON.parse(JSON.stringify(processed));
49664
+ // Avoid duplicate dataZoom entries
49665
+ var existingDzArr = Array.isArray(processed.dataZoom) ? processed.dataZoom : processed.dataZoom ? [processed.dataZoom] : [];
49666
+ if (!existingDzArr.some(function (dz) {
49667
+ return (dz === null || dz === void 0 ? void 0 : dz.type) === 'inside';
49668
+ })) {
49669
+ processed.dataZoom = BaseChart_spreadArray(BaseChart_spreadArray([], existingDzArr || [], true), [
49670
+ // Inside (mouse wheel + keyboard) — wired to keyboard nav in BaseChartWrapper
49671
+ {
49672
+ type: 'inside',
49673
+ start: 0,
49674
+ end: 100,
49675
+ zoomOnMouseWheel: true,
49676
+ moveOnMouseMove: false
49677
+ }], false);
49678
+ }
49679
+ }
49423
49680
  // Apply animation config
49424
49681
  var dataLength = calculateDataLength(processed);
49425
49682
  var animConfig = (0,core_animation/* generateEChartsAnimationConfig */.ek)(animation, dataLength);
49426
49683
  return BaseChart_assign(BaseChart_assign({}, processed), animConfig);
49427
- }, [option, animation, enableDataFiltering, filters, virtualScroll, virtualScrollPageSize, virtualScrollPreloadSize, onDataFiltered]);
49684
+ }, [option, animation, _enableZoom, enableDataFiltering, filters, virtualScroll, virtualScrollPageSize, virtualScrollPreloadSize, onDataFiltered]);
49428
49685
  // Internal chartInit that wraps the user's callback
49429
49686
  var handleChartInit = (0,external_react_.useCallback)(function (instance) {
49430
49687
  var _a;
@@ -49433,6 +49690,7 @@ var BaseChart = function (props) {
49433
49690
  // Performance monitoring init
49434
49691
  if (enablePerformanceMonitoring) {
49435
49692
  performanceAnalyzerRef.current = PerformanceAnalyzer.getInstance({
49693
+ chartId: chartId,
49436
49694
  enabled: true,
49437
49695
  metrics: ['initTime', 'renderTime', 'updateTime', 'dataSize', 'frameRate'],
49438
49696
  sampleInterval: 1000,
@@ -49588,15 +49846,36 @@ var BaseChart = function (props) {
49588
49846
  });
49589
49847
  }
49590
49848
  }, [option, onPerformance]);
49591
- // Data update callback
49849
+ // Data update callback — supports debounce
49850
+ var debounceTimerRef = (0,external_react_.useRef)(null);
49592
49851
  (0,external_react_.useEffect)(function () {
49593
- if (onDataUpdate && (dataUpdateOptions === null || dataUpdateOptions === void 0 ? void 0 : dataUpdateOptions.enabled) !== false) {
49852
+ var _a;
49853
+ if (!onDataUpdate || (dataUpdateOptions === null || dataUpdateOptions === void 0 ? void 0 : dataUpdateOptions.enabled) === false) return;
49854
+ var delay = (_a = dataUpdateOptions === null || dataUpdateOptions === void 0 ? void 0 : dataUpdateOptions.debounceDelay) !== null && _a !== void 0 ? _a : 0;
49855
+ if (debounceTimerRef.current) {
49856
+ clearTimeout(debounceTimerRef.current);
49857
+ }
49858
+ if (delay > 0) {
49859
+ debounceTimerRef.current = setTimeout(function () {
49860
+ var oldOpt = oldOptionRef.current;
49861
+ if (oldOpt !== option) {
49862
+ onDataUpdate(oldOpt, option);
49863
+ oldOptionRef.current = option;
49864
+ }
49865
+ }, delay);
49866
+ } else {
49594
49867
  var oldOpt = oldOptionRef.current;
49595
49868
  if (oldOpt !== option) {
49596
49869
  onDataUpdate(oldOpt, option);
49597
49870
  oldOptionRef.current = option;
49598
49871
  }
49599
49872
  }
49873
+ return function () {
49874
+ if (debounceTimerRef.current) {
49875
+ clearTimeout(debounceTimerRef.current);
49876
+ debounceTimerRef.current = null;
49877
+ }
49878
+ };
49600
49879
  }, [option, onDataUpdate, dataUpdateOptions]);
49601
49880
  // Cleanup on unmount
49602
49881
  (0,external_react_.useEffect)(function () {
@@ -50957,8 +51236,10 @@ var LiquidChart = /*#__PURE__*/(0,external_react_.memo)(function (props) {
50957
51236
  chartInstance.current = instance;
50958
51237
  // 绑定事件
50959
51238
  if (onEvents) {
50960
- Object.keys(onEvents).forEach(function (eventName) {
50961
- instance.on(eventName, onEvents[eventName]);
51239
+ Object.entries(onEvents).forEach(function (_a) {
51240
+ var eventName = _a[0],
51241
+ handler = _a[1];
51242
+ instance.on(eventName, handler);
50962
51243
  });
50963
51244
  }
50964
51245
  if (onChartInit) {
@@ -52205,6 +52486,38 @@ var ThemeManager = /** @class */function () {
52205
52486
  ThemeManager.prototype.isDarkMode = function () {
52206
52487
  return this.currentTheme.isDark || this.currentTheme.type === 'dark';
52207
52488
  };
52489
+ /**
52490
+ * 检测系统 prefers-color-scheme 是否为暗色
52491
+ */
52492
+ ThemeManager.prototype.getSystemPrefersDark = function () {
52493
+ if (typeof window === 'undefined') return false;
52494
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
52495
+ };
52496
+ /**
52497
+ * 订阅系统主题变化,自动切换匹配的主题
52498
+ * @returns 取消订阅的函数
52499
+ */
52500
+ ThemeManager.prototype.watchSystemTheme = function () {
52501
+ var _this = this;
52502
+ if (typeof window === 'undefined') return function () {};
52503
+ var mq = window.matchMedia('(prefers-color-scheme: dark)');
52504
+ var handler = function (e) {
52505
+ // 当系统主题切换时,自动应用对应主题
52506
+ _this.setTheme(e.matches ? 'dark' : 'default');
52507
+ };
52508
+ mq.addEventListener('change', handler);
52509
+ return function () {
52510
+ return mq.removeEventListener('change', handler);
52511
+ };
52512
+ };
52513
+ /**
52514
+ * 应用初始主题(根据系统偏好自动选择 dark/default)
52515
+ * 必须在 DOM 加载后调用
52516
+ */
52517
+ ThemeManager.prototype.applyInitialTheme = function () {
52518
+ var prefersDark = this.getSystemPrefersDark();
52519
+ this.setTheme(prefersDark ? 'dark' : 'default');
52520
+ };
52208
52521
  /**
52209
52522
  * 注册主题变更监听器
52210
52523
  */
@@ -53561,51 +53874,58 @@ var ErrorBoundary = /** @class */function (_super) {
53561
53874
  if (fallback) {
53562
53875
  return fallback(error, this.handleReset);
53563
53876
  }
53564
- // 默认错误展示
53877
+ // 默认错误展示(使用 CSS 变量,与 ThemeManager 对齐)
53565
53878
  return /*#__PURE__*/external_react_default().createElement("div", {
53879
+ role: "alert",
53880
+ "aria-live": "assertive",
53566
53881
  style: {
53567
53882
  display: 'flex',
53568
53883
  flexDirection: 'column',
53569
53884
  alignItems: 'center',
53570
53885
  justifyContent: 'center',
53571
- padding: '20px',
53572
- backgroundColor: '#fff',
53573
- border: '1px solid #ff4d4f',
53574
- borderRadius: '8px',
53575
- color: '#333',
53576
- minHeight: '200px'
53886
+ padding: 'var(--tv-border-radius, 16px)',
53887
+ backgroundColor: 'var(--tv-bg-color, #fff)',
53888
+ border: '1px solid var(--tv-error-color, #ff4d4f)',
53889
+ borderRadius: 'var(--tv-border-radius, 8px)',
53890
+ color: 'var(--tv-text-color, #333)',
53891
+ minHeight: '200px',
53892
+ fontFamily: 'var(--tv-font-family, sans-serif)'
53577
53893
  }
53578
53894
  }, /*#__PURE__*/external_react_default().createElement("div", {
53579
53895
  style: {
53580
53896
  fontSize: '48px',
53581
53897
  marginBottom: '16px'
53582
- }
53898
+ },
53899
+ "aria-hidden": "true"
53583
53900
  }, "\u26A0\uFE0F"), /*#__PURE__*/external_react_default().createElement("h3", {
53584
53901
  style: {
53585
53902
  margin: '0 0 12px',
53586
- color: '#ff4d4f'
53903
+ color: 'var(--tv-error-color, #ff4d4f)',
53904
+ fontWeight: 700
53587
53905
  }
53588
53906
  }, "\u56FE\u8868\u6E32\u67D3\u5931\u8D25"), /*#__PURE__*/external_react_default().createElement("p", {
53589
53907
  style: {
53590
53908
  margin: '0 0 16px',
53591
- color: '#666',
53592
- textAlign: 'center'
53909
+ color: 'var(--tv-text-color-secondary, #666)',
53910
+ textAlign: 'center',
53911
+ maxWidth: '320px'
53593
53912
  }
53594
53913
  }, "\u56FE\u8868\u5728\u6E32\u67D3\u8FC7\u7A0B\u4E2D\u9047\u5230\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u6570\u636E\u914D\u7F6E\u662F\u5426\u6B63\u786E"), showDetails && (/*#__PURE__*/external_react_default().createElement("details", {
53595
53914
  style: {
53596
53915
  width: '100%',
53597
53916
  padding: '12px',
53598
- backgroundColor: '#f5f5f5',
53599
- borderRadius: '4px',
53600
- fontSize: '12px',
53601
- fontFamily: 'monospace',
53917
+ backgroundColor: 'var(--tv-bg-color-secondary, #f5f5f5)',
53918
+ borderRadius: 'var(--tv-border-radius-small, 4px)',
53919
+ fontSize: 'var(--tv-font-size-small, 12px)',
53920
+ fontFamily: 'var(--tv-font-family, monospace)',
53602
53921
  overflow: 'auto',
53603
53922
  maxHeight: '150px'
53604
53923
  }
53605
53924
  }, /*#__PURE__*/external_react_default().createElement("summary", {
53606
53925
  style: {
53607
53926
  cursor: 'pointer',
53608
- marginBottom: '8px'
53927
+ marginBottom: '8px',
53928
+ fontWeight: 600
53609
53929
  }
53610
53930
  }, "\u9519\u8BEF\u8BE6\u60C5"), /*#__PURE__*/external_react_default().createElement("pre", {
53611
53931
  style: {
@@ -53618,12 +53938,20 @@ var ErrorBoundary = /** @class */function (_super) {
53618
53938
  style: {
53619
53939
  marginTop: '16px',
53620
53940
  padding: '8px 24px',
53621
- backgroundColor: '#1890ff',
53941
+ backgroundColor: 'var(--tv-primary-color, #1890ff)',
53622
53942
  color: '#fff',
53623
53943
  border: 'none',
53624
- borderRadius: '4px',
53944
+ borderRadius: 'var(--tv-border-radius-small, 4px)',
53625
53945
  cursor: 'pointer',
53626
- fontSize: '14px'
53946
+ fontSize: 'var(--tv-font-size, 14px)',
53947
+ fontWeight: 600,
53948
+ transition: 'background-color var(--tv-transition-duration, 0.3s)'
53949
+ },
53950
+ onMouseEnter: function (e) {
53951
+ e.currentTarget.style.backgroundColor = 'var(--tv-primary-color-hover, #40a9ff)';
53952
+ },
53953
+ onMouseLeave: function (e) {
53954
+ e.currentTarget.style.backgroundColor = 'var(--tv-primary-color, #1890ff)';
53627
53955
  }
53628
53956
  }, "\u91CD\u8BD5"));
53629
53957
  }
@@ -53742,12 +54070,14 @@ var LAZY_CHART_MODULES = {
53742
54070
  };
53743
54071
  var LAZY_CHART_TYPES = Object.keys(LAZY_CHART_MODULES);
53744
54072
  /**
53745
- * 默认加载状态组件
54073
+ * 默认加载状态组件(使用 CSS 变量,与 ThemeManager 对齐)
53746
54074
  */
53747
54075
  var DefaultLoadingFallback = function (_a) {
53748
54076
  var _b = _a.text,
53749
54077
  text = _b === void 0 ? '加载中...' : _b;
53750
54078
  return /*#__PURE__*/external_react_default().createElement("div", {
54079
+ role: "status",
54080
+ "aria-label": text,
53751
54081
  style: {
53752
54082
  display: 'flex',
53753
54083
  alignItems: 'center',
@@ -53755,8 +54085,8 @@ var DefaultLoadingFallback = function (_a) {
53755
54085
  width: '100%',
53756
54086
  height: '100%',
53757
54087
  minHeight: '200px',
53758
- backgroundColor: '#f5f5f5',
53759
- borderRadius: '8px'
54088
+ backgroundColor: 'var(--tv-bg-color-secondary, #f5f5f5)',
54089
+ borderRadius: 'var(--tv-border-radius, 8px)'
53760
54090
  }
53761
54091
  }, /*#__PURE__*/external_react_default().createElement("div", {
53762
54092
  style: {
@@ -53766,16 +54096,17 @@ var DefaultLoadingFallback = function (_a) {
53766
54096
  style: {
53767
54097
  width: '40px',
53768
54098
  height: '40px',
53769
- border: '3px solid #1890ff',
54099
+ border: '3px solid var(--tv-primary-color, #1890ff)',
53770
54100
  borderTopColor: 'transparent',
53771
54101
  borderRadius: '50%',
53772
54102
  animation: 'taroviz-spin 1s linear infinite',
53773
54103
  margin: '0 auto 12px'
53774
- }
54104
+ },
54105
+ "aria-hidden": "true"
53775
54106
  }), /*#__PURE__*/external_react_default().createElement("style", null, "\n @keyframes taroviz-spin {\n to { transform: rotate(360deg); }\n }\n "), /*#__PURE__*/external_react_default().createElement("span", {
53776
54107
  style: {
53777
- color: '#666',
53778
- fontSize: '14px'
54108
+ color: 'var(--tv-text-color-secondary, #666)',
54109
+ fontSize: 'var(--tv-font-size, 14px)'
53779
54110
  }
53780
54111
  }, text)));
53781
54112
  };
@@ -55868,6 +56199,491 @@ function useChartDownload(instance, options) {
55868
56199
  // 导出
55869
56200
  // ============================================================================
55870
56201
  /* harmony default export */ const hooks_useChartDownload = ((/* unused pure expression or super */ null && (useChartDownload)));
56202
+ ;// ./src/hooks/useChartHistory.ts
56203
+ var useChartHistory_spreadArray = undefined && undefined.__spreadArray || function (to, from, pack) {
56204
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
56205
+ if (ar || !(i in from)) {
56206
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
56207
+ ar[i] = from[i];
56208
+ }
56209
+ }
56210
+ return to.concat(ar || Array.prototype.slice.call(from));
56211
+ };
56212
+ /**
56213
+ * useChartHistory - 图表 Undo/Redo Hook
56214
+ * 追踪图表配置历史,支持撤销/重做操作和键盘快捷键
56215
+ *
56216
+ * 特性:
56217
+ * - 最多保存 50 条历史记录(可配置)
56218
+ * - 自动绑定 Ctrl+Z / Ctrl+Y 键盘快捷键
56219
+ * - 支持 ignoreKeys 忽略特定配置字段(如动画、时间戳)
56220
+ * - 暴露 canUndo / canRedo 状态
56221
+ */
56222
+
56223
+ // ============================================================================
56224
+ // 工具函数
56225
+ // ============================================================================
56226
+ /** 深度省略指定键后比较两个配置是否相等 */
56227
+ function omitAndCompare(a, b, ignoreKeys) {
56228
+ if (a === b) return true;
56229
+ if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
56230
+ return a === b;
56231
+ }
56232
+ var aObj = a;
56233
+ var bObj = b;
56234
+ var aKeys = Object.keys(aObj).filter(function (k) {
56235
+ return !ignoreKeys.has(k);
56236
+ });
56237
+ var bKeys = Object.keys(bObj).filter(function (k) {
56238
+ return !ignoreKeys.has(k);
56239
+ });
56240
+ if (aKeys.length !== bKeys.length) return false;
56241
+ for (var _i = 0, aKeys_1 = aKeys; _i < aKeys_1.length; _i++) {
56242
+ var key = aKeys_1[_i];
56243
+ if (!bObj.hasOwnProperty(key)) return false;
56244
+ if (!omitAndCompare(aObj[key], bObj[key], ignoreKeys)) return false;
56245
+ }
56246
+ return true;
56247
+ }
56248
+ // ============================================================================
56249
+ // Hook 实现
56250
+ // ============================================================================
56251
+ /**
56252
+ * 使用图表历史记录(Undo/Redo)
56253
+ * @param chartInstance 图表实例
56254
+ * @param options 配置选项
56255
+ * @returns 历史记录控制接口
56256
+ */
56257
+ function useChartHistory(chartInstance, options) {
56258
+ if (options === void 0) {
56259
+ options = {};
56260
+ }
56261
+ var _a = options.maxHistorySize,
56262
+ maxHistorySize = _a === void 0 ? 50 : _a,
56263
+ _b = options.ignoreKeys,
56264
+ ignoreKeys = _b === void 0 ? ['animation', 'animationDuration', 'animationEasing', 'animationFrame'] : _b,
56265
+ _c = options.enableKeyboard,
56266
+ enableKeyboard = _c === void 0 ? true : _c,
56267
+ _d = options.clearOnUnmount,
56268
+ clearOnUnmount = _d === void 0 ? false : _d;
56269
+ // 忽略键集合
56270
+ var ignoreKeySet = (0,external_react_.useRef)(new Set(ignoreKeys));
56271
+ // 历史栈(每次 setOption 快照)
56272
+ var historyStack = (0,external_react_.useRef)([]);
56273
+ // 当前索引
56274
+ var _e = (0,external_react_.useState)(-1),
56275
+ currentIndex = _e[0],
56276
+ setCurrentIndex = _e[1];
56277
+ // Chart instance ref
56278
+ var chartRef = (0,external_react_.useRef)(null);
56279
+ chartRef.current = chartInstance;
56280
+ // 是否正在执行 undo/redo(避免 push 时重复记录)
56281
+ var isApplyingRef = (0,external_react_.useRef)(false);
56282
+ // 拦截 chart.setOption,记录历史
56283
+ (0,external_react_.useEffect)(function () {
56284
+ var chart = chartRef.current;
56285
+ if (!chart) return;
56286
+ var originalSetOption = chart.setOption.bind(chart);
56287
+ chart.setOption = function (option, notMerge, lazyUpdate) {
56288
+ // 如果正在执行 undo/redo,跳过历史记录
56289
+ if (isApplyingRef.current) {
56290
+ return originalSetOption(option, notMerge, lazyUpdate);
56291
+ }
56292
+ var stack = historyStack.current;
56293
+ var idx = currentIndex;
56294
+ // 如果当前索引不在栈顶,丢弃redo历史(类似 Git 行为)
56295
+ var newStack = idx < stack.length - 1 ? stack.slice(0, idx + 1) : useChartHistory_spreadArray([], stack, true);
56296
+ // 检查是否与上一次配置相同(忽略动画字段)
56297
+ var lastOption = newStack[newStack.length - 1];
56298
+ if (lastOption && omitAndCompare(lastOption, option, ignoreKeySet.current)) {
56299
+ // 配置没变,直接应用
56300
+ return originalSetOption(option, notMerge, lazyUpdate);
56301
+ }
56302
+ // 入栈
56303
+ newStack.push(option);
56304
+ // 裁剪超出 maxHistorySize
56305
+ if (newStack.length > maxHistorySize) {
56306
+ newStack.shift();
56307
+ } else {
56308
+ // 更新索引
56309
+ setCurrentIndex(newStack.length - 1);
56310
+ }
56311
+ historyStack.current = newStack;
56312
+ return originalSetOption(option, notMerge, lazyUpdate);
56313
+ };
56314
+ return function () {
56315
+ // 恢复原始 setOption
56316
+ chart.setOption = originalSetOption;
56317
+ };
56318
+ }, [chartInstance, currentIndex, maxHistorySize]);
56319
+ // 键盘快捷键:Ctrl+Z 撤销,Ctrl+Y / Ctrl+Shift+Z 重做
56320
+ (0,external_react_.useEffect)(function () {
56321
+ if (!enableKeyboard) return;
56322
+ var handleKeyDown = function (e) {
56323
+ var isMod = e.ctrlKey || e.metaKey;
56324
+ if (!isMod) return;
56325
+ if (e.key === 'z' && !e.shiftKey) {
56326
+ e.preventDefault();
56327
+ undo();
56328
+ } else if (e.key === 'y' || e.key === 'z' && e.shiftKey) {
56329
+ e.preventDefault();
56330
+ redo();
56331
+ }
56332
+ };
56333
+ window.addEventListener('keydown', handleKeyDown);
56334
+ return function () {
56335
+ return window.removeEventListener('keydown', handleKeyDown);
56336
+ };
56337
+ }, [enableKeyboard]); // eslint-disable-line react-hooks/exhaustive-deps
56338
+ // 组件卸载时清空
56339
+ (0,external_react_.useEffect)(function () {
56340
+ return function () {
56341
+ if (clearOnUnmount) {
56342
+ historyStack.current = [];
56343
+ setCurrentIndex(-1);
56344
+ }
56345
+ };
56346
+ }, [clearOnUnmount]);
56347
+ // ─── Public API ───────────────────────────────────────────────────────────
56348
+ var undo = (0,external_react_.useCallback)(function () {
56349
+ var chart = chartRef.current;
56350
+ if (!chart || currentIndex <= 0) return;
56351
+ var idx = currentIndex - 1;
56352
+ isApplyingRef.current = true;
56353
+ chart.setOption(historyStack.current[idx], true, true);
56354
+ isApplyingRef.current = false;
56355
+ setCurrentIndex(idx);
56356
+ }, [currentIndex]);
56357
+ var redo = (0,external_react_.useCallback)(function () {
56358
+ var chart = chartRef.current;
56359
+ if (!chart || currentIndex >= historyStack.current.length - 1) return;
56360
+ var idx = currentIndex + 1;
56361
+ isApplyingRef.current = true;
56362
+ chart.setOption(historyStack.current[idx], true, true);
56363
+ isApplyingRef.current = false;
56364
+ setCurrentIndex(idx);
56365
+ }, [currentIndex]);
56366
+ var goTo = (0,external_react_.useCallback)(function (index) {
56367
+ var chart = chartRef.current;
56368
+ if (!chart) return;
56369
+ if (index < 0 || index >= historyStack.current.length) return;
56370
+ if (index === currentIndex) return;
56371
+ isApplyingRef.current = true;
56372
+ chart.setOption(historyStack.current[index], true, true);
56373
+ isApplyingRef.current = false;
56374
+ setCurrentIndex(index);
56375
+ }, [currentIndex]);
56376
+ var push = (0,external_react_.useCallback)(function (option) {
56377
+ var stack = historyStack.current;
56378
+ var idx = currentIndex;
56379
+ var newStack = idx < stack.length - 1 ? stack.slice(0, idx + 1) : useChartHistory_spreadArray([], stack, true);
56380
+ newStack.push(option);
56381
+ if (newStack.length > maxHistorySize) newStack.shift();
56382
+ historyStack.current = newStack;
56383
+ setCurrentIndex(newStack.length - 1);
56384
+ }, [currentIndex, maxHistorySize]);
56385
+ var clear = (0,external_react_.useCallback)(function () {
56386
+ historyStack.current = [];
56387
+ setCurrentIndex(-1);
56388
+ }, []);
56389
+ return {
56390
+ canUndo: currentIndex > 0,
56391
+ canRedo: currentIndex < historyStack.current.length - 1,
56392
+ currentIndex: currentIndex,
56393
+ historyCount: historyStack.current.length,
56394
+ undo: undo,
56395
+ redo: redo,
56396
+ goTo: goTo,
56397
+ push: push,
56398
+ clear: clear
56399
+ };
56400
+ }
56401
+ /* harmony default export */ const hooks_useChartHistory = ((/* unused pure expression or super */ null && (useChartHistory)));
56402
+ ;// ./src/hooks/useChartSelection.ts
56403
+ var useChartSelection_spreadArray = undefined && undefined.__spreadArray || function (to, from, pack) {
56404
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
56405
+ if (ar || !(i in from)) {
56406
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
56407
+ ar[i] = from[i];
56408
+ }
56409
+ }
56410
+ return to.concat(ar || Array.prototype.slice.call(from));
56411
+ };
56412
+ /**
56413
+ * useChartSelection - 图表数据点选择/高亮 Hook
56414
+ * 支持单个/批量选择、反选、清除选择,配合 ECharts select 事件
56415
+ *
56416
+ * 特性:
56417
+ * - 支持按 seriesIndex + dataIndex 选择
56418
+ * - 支持按 dataIndex 跨系列批量选择
56419
+ * - 支持反选(invertSelection)
56420
+ * - 支持多选模式(multi)
56421
+ * - 自动绑定图表 select/unselect 事件
56422
+ */
56423
+
56424
+ // ============================================================================
56425
+ // 工具函数
56426
+ // ============================================================================
56427
+ /** 生成唯一键字符串 */
56428
+ function keyToString(key) {
56429
+ return "".concat(key.seriesIndex, ":").concat(key.dataIndex);
56430
+ }
56431
+ function stringToKey(str) {
56432
+ var _a = str.split(':').map(Number),
56433
+ seriesIndex = _a[0],
56434
+ dataIndex = _a[1];
56435
+ return {
56436
+ seriesIndex: seriesIndex,
56437
+ dataIndex: dataIndex
56438
+ };
56439
+ }
56440
+ // ============================================================================
56441
+ // Hook 实现
56442
+ // ============================================================================
56443
+ /**
56444
+ * 使用图表数据点选择功能
56445
+ * @param chartInstance 图表实例
56446
+ * @param options 配置选项
56447
+ * @returns 选择控制接口
56448
+ */
56449
+ function useChartSelection(chartInstance, options) {
56450
+ if (options === void 0) {
56451
+ options = {};
56452
+ }
56453
+ var _a = options.mode,
56454
+ mode = _a === void 0 ? 'multiple' : _a,
56455
+ _b = options.clearOnUnmount,
56456
+ clearOnUnmount = _b === void 0 ? true : _b,
56457
+ _c = options.enableCtrlMultiSelect,
56458
+ enableCtrlMultiSelect = _c === void 0 ? true : _c,
56459
+ _d = options.enableShiftRangeSelect,
56460
+ enableShiftRangeSelect = _d === void 0 ? true : _d,
56461
+ onSelectionChange = options.onSelectionChange;
56462
+ // 选中点集合(字符串键)
56463
+ var _e = (0,external_react_.useState)([]),
56464
+ selectedPoints = _e[0],
56465
+ setSelectedPoints = _e[1];
56466
+ // Chart instance ref
56467
+ var chartRef = (0,external_react_.useRef)(null);
56468
+ chartRef.current = chartInstance;
56469
+ // 上一次 shift+click 的数据索引(用于范围选择)
56470
+ var lastShiftIndexRef = (0,external_react_.useRef)(null);
56471
+ // 当前模式 ref(用于事件处理)
56472
+ var modeRef = (0,external_react_.useRef)(mode);
56473
+ modeRef.current = mode;
56474
+ // 绑定图表 select/unselect 事件
56475
+ (0,external_react_.useEffect)(function () {
56476
+ var chart = chartRef.current;
56477
+ if (!chart || !chart.on) return;
56478
+ var handleSelect = function (params) {
56479
+ // ECharts 内置 select 会同步更新 legend
56480
+ // 这里我们用 dispatchAction 来实现纯数据点选择
56481
+ };
56482
+ // 单击 legend 时清除数据点选择(保持一致性)
56483
+ var handleLegendSelectChanged = function (params) {
56484
+ // 清除选择时的视觉反馈
56485
+ };
56486
+ chart.on('selectchanged', function (params) {
56487
+ // 当图表内部选择变化时同步状态
56488
+ var p = params;
56489
+ if (p.isFromClick) {
56490
+ // 用户点击了图例,清除所有数据点选择
56491
+ setSelectedPoints([]);
56492
+ onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange({
56493
+ selected: [],
56494
+ unselected: []
56495
+ });
56496
+ }
56497
+ });
56498
+ return function () {
56499
+ if (chart.off) {
56500
+ chart.off('selectchanged');
56501
+ }
56502
+ };
56503
+ }, [onSelectionChange]);
56504
+ // 组件卸载时清除选择
56505
+ (0,external_react_.useEffect)(function () {
56506
+ return function () {
56507
+ if (clearOnUnmount) {
56508
+ var chart = chartRef.current;
56509
+ if (chart === null || chart === void 0 ? void 0 : chart.dispatchAction) {
56510
+ // 清除所有系列的选择状态
56511
+ chart.dispatchAction({
56512
+ type: 'unselect'
56513
+ });
56514
+ }
56515
+ }
56516
+ };
56517
+ }, [clearOnUnmount]);
56518
+ // ─── 私有方法 ───────────────────────────────────────────────────────────────
56519
+ /** 触发选择变化回调 */
56520
+ var notifyChange = (0,external_react_.useCallback)(function (selected, unselected) {
56521
+ onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange({
56522
+ selected: selected,
56523
+ unselected: unselected
56524
+ });
56525
+ }, [onSelectionChange]);
56526
+ /** 执行 ECharts dispatchAction */
56527
+ var dispatchSelect = (0,external_react_.useCallback)(function (key, select) {
56528
+ var chart = chartRef.current;
56529
+ if (!(chart === null || chart === void 0 ? void 0 : chart.dispatchAction)) return;
56530
+ chart.dispatchAction({
56531
+ type: select ? 'select' : 'unselect',
56532
+ seriesIndex: key.seriesIndex,
56533
+ dataIndex: key.dataIndex
56534
+ });
56535
+ }, []);
56536
+ // ─── Public API ───────────────────────────────────────────────────────────
56537
+ var select = (0,external_react_.useCallback)(function (key) {
56538
+ setSelectedPoints(function (prev) {
56539
+ var str = keyToString(key);
56540
+ if (prev.some(function (p) {
56541
+ return keyToString(p) === str;
56542
+ })) return prev;
56543
+ var next = mode === 'single' ? [key] : useChartSelection_spreadArray(useChartSelection_spreadArray([], prev, true), [key], false);
56544
+ notifyChange(next, []);
56545
+ return next;
56546
+ });
56547
+ dispatchSelect(key, true);
56548
+ }, [mode, notifyChange, dispatchSelect]);
56549
+ var deselect = (0,external_react_.useCallback)(function (key) {
56550
+ setSelectedPoints(function (prev) {
56551
+ var str = keyToString(key);
56552
+ var removed = prev.filter(function (p) {
56553
+ return keyToString(p) !== str;
56554
+ });
56555
+ notifyChange([], removed);
56556
+ return removed;
56557
+ });
56558
+ dispatchSelect(key, false);
56559
+ }, [notifyChange, dispatchSelect]);
56560
+ var toggle = (0,external_react_.useCallback)(function (key) {
56561
+ var str = keyToString(key);
56562
+ if (selectedPoints.some(function (p) {
56563
+ return keyToString(p) === str;
56564
+ })) {
56565
+ deselect(key);
56566
+ } else {
56567
+ select(key);
56568
+ }
56569
+ }, [selectedPoints, select, deselect]);
56570
+ var selectMultiple = (0,external_react_.useCallback)(function (keys) {
56571
+ setSelectedPoints(function (prev) {
56572
+ var newPoints = mode === 'single' ? keys : useChartSelection_spreadArray(useChartSelection_spreadArray([], prev, true), keys.filter(function (k) {
56573
+ return !prev.some(function (p) {
56574
+ return keyToString(p) === keyToString(k);
56575
+ });
56576
+ }), true);
56577
+ notifyChange(newPoints, []);
56578
+ return newPoints;
56579
+ });
56580
+ keys.forEach(function (key) {
56581
+ return dispatchSelect(key, true);
56582
+ });
56583
+ }, [mode, notifyChange, dispatchSelect]);
56584
+ var deselectMultiple = (0,external_react_.useCallback)(function (keys) {
56585
+ setSelectedPoints(function (prev) {
56586
+ var keySet = new Set(keys.map(keyToString));
56587
+ var removed = prev.filter(function (p) {
56588
+ return keySet.has(keyToString(p));
56589
+ });
56590
+ var remaining = prev.filter(function (p) {
56591
+ return !keySet.has(keyToString(p));
56592
+ });
56593
+ notifyChange([], removed);
56594
+ return remaining;
56595
+ });
56596
+ keys.forEach(function (key) {
56597
+ return dispatchSelect(key, false);
56598
+ });
56599
+ }, [notifyChange, dispatchSelect]);
56600
+ var invertSelection = (0,external_react_.useCallback)(function (seriesIndex, dataIndices) {
56601
+ setSelectedPoints(function (prev) {
56602
+ var selectedSet = new Set(prev.filter(function (p) {
56603
+ return p.seriesIndex === seriesIndex;
56604
+ }).map(function (p) {
56605
+ return p.dataIndex;
56606
+ }));
56607
+ var toSelect = [];
56608
+ var toDeselect = [];
56609
+ dataIndices.forEach(function (dataIndex) {
56610
+ var key = {
56611
+ seriesIndex: seriesIndex,
56612
+ dataIndex: dataIndex
56613
+ };
56614
+ var str = keyToString(key);
56615
+ if (selectedSet.has(dataIndex)) {
56616
+ toDeselect.push(key);
56617
+ } else {
56618
+ toSelect.push(key);
56619
+ }
56620
+ });
56621
+ toDeselect.forEach(function (k) {
56622
+ return dispatchSelect(k, false);
56623
+ });
56624
+ toSelect.forEach(function (k) {
56625
+ return dispatchSelect(k, true);
56626
+ });
56627
+ var newSelected = prev.filter(function (p) {
56628
+ return !(p.seriesIndex === seriesIndex && selectedSet.has(p.dataIndex));
56629
+ }).concat(toSelect);
56630
+ notifyChange(toSelect, toDeselect);
56631
+ return newSelected;
56632
+ });
56633
+ }, [notifyChange, dispatchSelect]);
56634
+ var selectAll = (0,external_react_.useCallback)(function (seriesIndex, dataIndices) {
56635
+ var keys = dataIndices.map(function (dataIndex) {
56636
+ return {
56637
+ seriesIndex: seriesIndex,
56638
+ dataIndex: dataIndex
56639
+ };
56640
+ });
56641
+ setSelectedPoints(function (prev) {
56642
+ var newPoints = mode === 'single' ? keys : useChartSelection_spreadArray(useChartSelection_spreadArray([], prev, true), keys.filter(function (k) {
56643
+ return !prev.some(function (p) {
56644
+ return keyToString(p) === keyToString(k);
56645
+ });
56646
+ }), true);
56647
+ notifyChange(newPoints, []);
56648
+ return newPoints;
56649
+ });
56650
+ keys.forEach(function (key) {
56651
+ return dispatchSelect(key, true);
56652
+ });
56653
+ }, [mode, notifyChange, dispatchSelect]);
56654
+ var clearSelection = (0,external_react_.useCallback)(function () {
56655
+ var chart = chartRef.current;
56656
+ if (chart === null || chart === void 0 ? void 0 : chart.dispatchAction) {
56657
+ chart.dispatchAction({
56658
+ type: 'unselect'
56659
+ });
56660
+ }
56661
+ var prev = selectedPoints;
56662
+ setSelectedPoints([]);
56663
+ notifyChange([], prev);
56664
+ }, [selectedPoints, notifyChange]);
56665
+ var isSelected = (0,external_react_.useCallback)(function (key) {
56666
+ var str = keyToString(key);
56667
+ return selectedPoints.some(function (p) {
56668
+ return keyToString(p) === str;
56669
+ });
56670
+ }, [selectedPoints]);
56671
+ return {
56672
+ selectedPoints: selectedPoints,
56673
+ hasSelection: selectedPoints.length > 0,
56674
+ selectionCount: selectedPoints.length,
56675
+ select: select,
56676
+ deselect: deselect,
56677
+ selectMultiple: selectMultiple,
56678
+ deselectMultiple: deselectMultiple,
56679
+ toggle: toggle,
56680
+ invertSelection: invertSelection,
56681
+ selectAll: selectAll,
56682
+ clearSelection: clearSelection,
56683
+ isSelected: isSelected
56684
+ };
56685
+ }
56686
+ /* harmony default export */ const hooks_useChartSelection = ((/* unused pure expression or super */ null && (useChartSelection)));
55871
56687
  ;// ./src/hooks/useDataTransform.ts
55872
56688
  var useDataTransform_assign = undefined && undefined.__assign || function () {
55873
56689
  useDataTransform_assign = Object.assign || function (t) {
@@ -56459,6 +57275,8 @@ var hooks_spreadArray = undefined && undefined.__spreadArray || function (to, fr
56459
57275
 
56460
57276
 
56461
57277
 
57278
+
57279
+
56462
57280
  // ============================================================================
56463
57281
  // Hooks
56464
57282
  // ============================================================================
@@ -56695,7 +57513,7 @@ function useChartTheme(theme, darkMode) {
56695
57513
  */
56696
57514
  function useChartData(data, transformer) {
56697
57515
  return (0,external_react_.useMemo)(function () {
56698
- if (!data) {
57516
+ if (!data || Array.isArray(data) && data.length === 0) {
56699
57517
  return {};
56700
57518
  }
56701
57519
  return transformer(data);
@@ -57061,6 +57879,10 @@ function useChartTools(instance) {
57061
57879
 
57062
57880
  // 图表下载 Hook
57063
57881
 
57882
+ // 图表历史记录 Hook (Undo/Redo)
57883
+
57884
+ // 图表选择 Hook
57885
+
57064
57886
  // ============================================================================
57065
57887
  // 导出
57066
57888
  // ============================================================================
@@ -57084,7 +57906,9 @@ var hooks_version = '1.7.0';
57084
57906
  // v1.7.0 新增
57085
57907
  useDataZoom: useDataZoom,
57086
57908
  useChartConnect: useChartConnect,
57087
- useChartDownload: useChartDownload
57909
+ useChartDownload: useChartDownload,
57910
+ useChartHistory: useChartHistory,
57911
+ useChartSelection: useChartSelection
57088
57912
  });
57089
57913
  ;// ./src/index.ts
57090
57914
  /**