@hsu-react/ui 2.4.1 → 2.4.2

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 (39) hide show
  1. package/es/components/Chart/Bar/index.js +9 -2
  2. package/es/components/Chart/Common/index.js +9 -2
  3. package/es/components/Chart/Gauge/index.js +9 -2
  4. package/es/components/Chart/Heatmap/index.js +9 -2
  5. package/es/components/Chart/Line/index.js +9 -2
  6. package/es/components/Chart/Pie/Pie3D/index.js +9 -2
  7. package/es/components/Chart/Pie/index.js +9 -2
  8. package/es/components/Chart/Polar/index.js +9 -2
  9. package/es/components/Chart/Radar/index.js +9 -2
  10. package/es/components/Chart/Sankey/index.js +9 -2
  11. package/es/components/Chart/Tree/index.js +9 -2
  12. package/es/components/Chart/_hooks/useContainerReady.d.ts +18 -0
  13. package/es/components/Chart/_hooks/useContainerReady.js +56 -0
  14. package/lib/components/Chart/Bar/index.js +9 -2
  15. package/lib/components/Chart/Common/index.js +9 -2
  16. package/lib/components/Chart/Gauge/index.js +9 -2
  17. package/lib/components/Chart/Heatmap/index.js +9 -2
  18. package/lib/components/Chart/Line/index.js +9 -2
  19. package/lib/components/Chart/Pie/Pie3D/index.js +9 -2
  20. package/lib/components/Chart/Pie/index.js +9 -2
  21. package/lib/components/Chart/Polar/index.js +9 -2
  22. package/lib/components/Chart/Radar/index.js +9 -2
  23. package/lib/components/Chart/Sankey/index.js +9 -2
  24. package/lib/components/Chart/Tree/index.js +9 -2
  25. package/lib/components/Chart/_hooks/useContainerReady.d.ts +18 -0
  26. package/lib/components/Chart/_hooks/useContainerReady.js +51 -0
  27. package/package.json +1 -1
  28. package/src/components/Chart/Bar/index.tsx +10 -1
  29. package/src/components/Chart/Common/index.tsx +12 -2
  30. package/src/components/Chart/Gauge/index.tsx +12 -2
  31. package/src/components/Chart/Heatmap/index.tsx +12 -2
  32. package/src/components/Chart/Line/index.tsx +10 -1
  33. package/src/components/Chart/Pie/Pie3D/index.tsx +10 -1
  34. package/src/components/Chart/Pie/index.tsx +10 -1
  35. package/src/components/Chart/Polar/index.tsx +12 -2
  36. package/src/components/Chart/Radar/index.tsx +10 -1
  37. package/src/components/Chart/Sankey/index.tsx +12 -2
  38. package/src/components/Chart/Tree/index.tsx +12 -2
  39. package/src/components/Chart/_hooks/useContainerReady.ts +50 -0
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
2
+ import useContainerReady from "../_hooks/useContainerReady";
2
3
  import styles from "../index.module.scss";
3
4
  import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
4
5
  import * as echarts from "echarts";
@@ -74,6 +75,8 @@ const Heatmap: React.FC<ChartHeatmapProps> = (props) => {
74
75
  } = props;
75
76
  const chartRef = useRef<HTMLDivElement>(null);
76
77
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
78
+ // Defers `echarts.init` until the container has a box — see the hook for why
79
+ const containerReady = useContainerReady(chartRef);
77
80
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
78
81
  // echarts options are plain JS and cannot read the `--vita-*` variables, so the axis and legend
79
82
  // colours have to be resolved against the active appearance here
@@ -255,12 +258,17 @@ const Heatmap: React.FC<ChartHeatmapProps> = (props) => {
255
258
 
256
259
  // Callback for handling chart resize
257
260
  const handleResize = useCallback(() => {
261
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
262
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
263
+ const el = chartRef.current;
264
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
265
+
258
266
  chartInstanceRef.current?.resize();
259
267
  }, []);
260
268
 
261
269
  // Initialize the chart
262
270
  useEffect(() => {
263
- if (!chartRef.current) return;
271
+ if (!chartRef.current || !containerReady) return;
264
272
 
265
273
  // Initialize or reuse the existing instance
266
274
  let chart = chartInstanceRef.current;
@@ -296,7 +304,9 @@ const Heatmap: React.FC<ChartHeatmapProps> = (props) => {
296
304
  chartInstanceRef.current?.off("click", onClick);
297
305
  }
298
306
  };
299
- }, [chartOption, handleResize, onChart, onClick]);
307
+ }, [chartOption, handleResize, onChart, onClick,
308
+ containerReady,
309
+ ]);
300
310
 
301
311
  // Clean up resources when the component unmounts
302
312
  useEffect(() => {
@@ -1,4 +1,5 @@
1
1
  import * as echarts from "echarts";
2
+ import useContainerReady from "../_hooks/useContainerReady";
2
3
 
3
4
  import {
4
5
  ChartCommonProps,
@@ -73,6 +74,8 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
73
74
  } = props;
74
75
  const chartRef = useRef<HTMLDivElement>(null);
75
76
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
77
+ // Defers `echarts.init` until the container has a box — see the hook for why
78
+ const containerReady = useContainerReady(chartRef);
76
79
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
77
80
  const prevXAxisKeyRef = useRef<string | null>(null);
78
81
  const legendScrollRef = useRef<ReturnType<typeof autoScrollLegend> | null>(
@@ -267,12 +270,17 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
267
270
 
268
271
  // Callback for handling chart resize
269
272
  const handleResize = useCallback(() => {
273
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
274
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
275
+ const el = chartRef.current;
276
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
277
+
270
278
  chartInstanceRef.current?.resize();
271
279
  }, []);
272
280
 
273
281
  // Initialize the chart
274
282
  useEffect(() => {
275
- if (!chartRef.current) return;
283
+ if (!chartRef.current || !containerReady) return;
276
284
 
277
285
  // Initialize or reuse the existing instance
278
286
  let chart = chartInstanceRef.current;
@@ -467,6 +475,7 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
467
475
  legendScrollInterval,
468
476
  // 图例是否接管滚轮由它推导,漏了会拿到过期的滚动配置
469
477
  normalizedScroll,
478
+ containerReady,
470
479
  ]);
471
480
 
472
481
  // Handle auto play
@@ -1,4 +1,5 @@
1
1
  import * as echarts from "echarts";
2
+ import useContainerReady from "../../_hooks/useContainerReady";
2
3
  import "echarts-gl";
3
4
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
4
5
 
@@ -108,6 +109,8 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
108
109
 
109
110
  const chartRef = useRef<HTMLDivElement>(null);
110
111
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
112
+ // Defers `echarts.init` until the container has a box — see the hook for why
113
+ const containerReady = useContainerReady(chartRef);
111
114
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
112
115
  const hoveredIndexRef = useRef<number | "">("");
113
116
  const optionRef = useRef<ChartsOption | null>(null);
@@ -145,6 +148,11 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
145
148
  ]);
146
149
 
147
150
  const handleResize = useCallback(() => {
151
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
152
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
153
+ const el = chartRef.current;
154
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
155
+
148
156
  chartInstanceRef.current?.resize();
149
157
  }, []);
150
158
 
@@ -163,7 +171,7 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
163
171
  );
164
172
 
165
173
  useEffect(() => {
166
- if (!chartRef.current) return;
174
+ if (!chartRef.current || !containerReady) return;
167
175
 
168
176
  let chart = chartInstanceRef.current;
169
177
  if (!chart) {
@@ -205,6 +213,7 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
205
213
  handleGlobalOut,
206
214
  onChart,
207
215
  onClick,
216
+ containerReady,
208
217
  ]);
209
218
 
210
219
  useEffect(() => {
@@ -1,4 +1,5 @@
1
1
  import * as echarts from "echarts";
2
+ import useContainerReady from "../_hooks/useContainerReady";
2
3
 
3
4
  import {
4
5
  ChartCommonProps,
@@ -66,6 +67,8 @@ const ChartPie: ChartPieFC = (props) => {
66
67
  } = props;
67
68
  const chartRef = useRef<HTMLDivElement>(null);
68
69
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
70
+ // Defers `echarts.init` until the container has a box — see the hook for why
71
+ const containerReady = useContainerReady(chartRef);
69
72
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
70
73
  const legendScrollRef = useRef<ReturnType<typeof autoScrollLegend> | null>(
71
74
  null
@@ -241,12 +244,17 @@ const ChartPie: ChartPieFC = (props) => {
241
244
 
242
245
  // Callback for handling chart resize
243
246
  const handleResize = useCallback(() => {
247
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
248
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
249
+ const el = chartRef.current;
250
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
251
+
244
252
  chartInstanceRef.current?.resize();
245
253
  }, []);
246
254
 
247
255
  // Initialize the chart
248
256
  useEffect(() => {
249
- if (!chartRef.current) return;
257
+ if (!chartRef.current || !containerReady) return;
250
258
 
251
259
  // Initialize or reuse the existing instance
252
260
  let chart = chartInstanceRef.current;
@@ -313,6 +321,7 @@ const ChartPie: ChartPieFC = (props) => {
313
321
  seriesData,
314
322
  legendVisibleCount,
315
323
  legendScrollInterval,
324
+ containerReady,
316
325
  ]);
317
326
 
318
327
  // Clean up resources when the component unmounts
@@ -1,4 +1,5 @@
1
1
  import * as echarts from "echarts";
2
+ import useContainerReady from "../_hooks/useContainerReady";
2
3
  import {
3
4
  ChartCommonProps,
4
5
  ChartOptionType,
@@ -36,6 +37,8 @@ const Polar: React.FC<ChartPolarProps> = (props) => {
36
37
  } = props;
37
38
  const chartRef = useRef<HTMLDivElement>(null);
38
39
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
40
+ // Defers `echarts.init` until the container has a box — see the hook for why
41
+ const containerReady = useContainerReady(chartRef);
39
42
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
40
43
 
41
44
  // Cache the chart option with useMemo
@@ -170,12 +173,17 @@ const Polar: React.FC<ChartPolarProps> = (props) => {
170
173
 
171
174
  // Callback handling chart resize
172
175
  const handleResize = useCallback(() => {
176
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
177
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
178
+ const el = chartRef.current;
179
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
180
+
173
181
  chartInstanceRef.current?.resize();
174
182
  }, []);
175
183
 
176
184
  // Initialize the chart
177
185
  useEffect(() => {
178
- if (!chartRef.current) return;
186
+ if (!chartRef.current || !containerReady) return;
179
187
 
180
188
  // Initialize a new instance or reuse the existing one
181
189
  let chart = chartInstanceRef.current;
@@ -200,7 +208,9 @@ const Polar: React.FC<ChartPolarProps> = (props) => {
200
208
  return () => {
201
209
  window.removeEventListener("resize", handleResize);
202
210
  };
203
- }, [chartOption, handleResize]);
211
+ }, [chartOption, handleResize,
212
+ containerReady,
213
+ ]);
204
214
 
205
215
  // Clean up resources on unmount
206
216
  useEffect(() => {
@@ -1,4 +1,5 @@
1
1
  import * as echarts from "echarts";
2
+ import useContainerReady from "../_hooks/useContainerReady";
2
3
  import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
3
4
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
4
5
  import styles from "../index.module.scss";
@@ -55,6 +56,8 @@ const Radar: React.FC<ChartRadarProps> = (props) => {
55
56
 
56
57
  const chartRef = useRef<HTMLDivElement>(null);
57
58
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
59
+ // Defers `echarts.init` until the container has a box — see the hook for why
60
+ const containerReady = useContainerReady(chartRef);
58
61
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
59
62
  const hoveredIndicatorRef = useRef<number>(-1);
60
63
 
@@ -188,11 +191,16 @@ const Radar: React.FC<ChartRadarProps> = (props) => {
188
191
  ]);
189
192
 
190
193
  const handleResize = useCallback(() => {
194
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
195
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
196
+ const el = chartRef.current;
197
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
198
+
191
199
  chartInstanceRef.current?.resize();
192
200
  }, []);
193
201
 
194
202
  useEffect(() => {
195
- if (!chartRef.current) return;
203
+ if (!chartRef.current || !containerReady) return;
196
204
 
197
205
  let chart = chartInstanceRef.current;
198
206
  if (!chart) {
@@ -343,6 +351,7 @@ const Radar: React.FC<ChartRadarProps> = (props) => {
343
351
  singleIndicatorHighlight,
344
352
  singleIndicatorHoverFill,
345
353
  color,
354
+ containerReady,
346
355
  ]);
347
356
 
348
357
  useEffect(() => {
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
2
+ import useContainerReady from "../_hooks/useContainerReady";
2
3
  import styles from "../index.module.scss";
3
4
  import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
4
5
  import * as echarts from "echarts";
@@ -16,6 +17,8 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
16
17
  const { className, style, seriesData, seriesLinks, series, getImage } = props;
17
18
  const chartRef = useRef<HTMLDivElement>(null);
18
19
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
20
+ // Defers `echarts.init` until the container has a box — see the hook for why
21
+ const containerReady = useContainerReady(chartRef);
19
22
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
20
23
 
21
24
  // Cache the chart option with useMemo
@@ -100,12 +103,17 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
100
103
 
101
104
  // Callback handling chart resize
102
105
  const handleResize = useCallback(() => {
106
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
107
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
108
+ const el = chartRef.current;
109
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
110
+
103
111
  chartInstanceRef.current?.resize();
104
112
  }, []);
105
113
 
106
114
  // Initialize the chart
107
115
  useEffect(() => {
108
- if (!chartRef.current) return;
116
+ if (!chartRef.current || !containerReady) return;
109
117
 
110
118
  // Initialize a new instance or reuse the existing one
111
119
  let chart = chartInstanceRef.current;
@@ -141,7 +149,9 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
141
149
  return () => {
142
150
  window.removeEventListener("resize", handleResize);
143
151
  };
144
- }, [chartOption, handleResize, getImage]);
152
+ }, [chartOption, handleResize, getImage,
153
+ containerReady,
154
+ ]);
145
155
 
146
156
  // Clean up resources on unmount
147
157
  useEffect(() => {
@@ -1,4 +1,5 @@
1
1
  import React, { useCallback, useEffect, useMemo, useRef } from "react";
2
+ import useContainerReady from "../_hooks/useContainerReady";
2
3
  import styles from "../index.module.scss";
3
4
  import {
4
5
  ChartCommonProps,
@@ -19,6 +20,8 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
19
20
  const { className, style, seriesData, series, getImage } = props;
20
21
  const chartRef = useRef<HTMLDivElement>(null);
21
22
  const chartInstanceRef = useRef<echarts.ECharts | null>(null);
23
+ // Defers `echarts.init` until the container has a box — see the hook for why
24
+ const containerReady = useContainerReady(chartRef);
22
25
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
23
26
 
24
27
  // Cache the chart option with useMemo
@@ -40,12 +43,17 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
40
43
 
41
44
  // Callback handling chart resize
42
45
  const handleResize = useCallback(() => {
46
+ // A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
47
+ // the laid-out canvas away and it has to be rebuilt when the tab comes back
48
+ const el = chartRef.current;
49
+ if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
50
+
43
51
  chartInstanceRef.current?.resize();
44
52
  }, []);
45
53
 
46
54
  // Initialize the chart
47
55
  useEffect(() => {
48
- if (!chartRef.current) return;
56
+ if (!chartRef.current || !containerReady) return;
49
57
 
50
58
  // Initialize a new instance or reuse the existing one
51
59
  let chart = chartInstanceRef.current;
@@ -81,7 +89,9 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
81
89
  return () => {
82
90
  window.removeEventListener("resize", handleResize);
83
91
  };
84
- }, [chartOption, handleResize, getImage]);
92
+ }, [chartOption, handleResize, getImage,
93
+ containerReady,
94
+ ]);
85
95
 
86
96
  // Clean up resources on unmount
87
97
  useEffect(() => {
@@ -0,0 +1,50 @@
1
+ import { RefObject, useEffect, useState } from "react";
2
+
3
+ /**
4
+ * Whether the referenced element has a non-zero box yet.
5
+ *
6
+ * `echarts.init` on a 0×0 element logs `Can't get DOM width or height` and builds a canvas with no
7
+ * size, recovering only on the next `resize()`. A chart hits that path whenever it mounts before
8
+ * layout has given it space — inside a keep-alive tab that is not the active one, a collapsed
9
+ * panel, a modal that has not opened yet — so consuming apps see the warning for charts that end
10
+ * up rendering perfectly well.
11
+ *
12
+ * Gating `init` on this flag defers it to the first frame the element actually occupies space.
13
+ *
14
+ * It **latches**: once measured, the element going back to 0×0 (a keep-alive tab losing focus)
15
+ * must not read as "not ready" again, or the init effect would tear the chart down and re-create
16
+ * it on every tab switch.
17
+ */
18
+ const useContainerReady = (ref: RefObject<HTMLElement | null>): boolean => {
19
+ const [ready, setReady] = useState(false);
20
+
21
+ useEffect(() => {
22
+ if (ready) return;
23
+
24
+ const el = ref.current;
25
+ if (!el) return;
26
+
27
+ const measure = (): boolean => {
28
+ const { width, height } = el.getBoundingClientRect();
29
+ if (width > 0 && height > 0) {
30
+ setReady(true);
31
+ return true;
32
+ }
33
+ return false;
34
+ };
35
+
36
+ // Already laid out on mount — the common case, and it must not cost an extra frame
37
+ if (measure()) return;
38
+
39
+ const observer = new ResizeObserver(() => {
40
+ if (measure()) observer.disconnect();
41
+ });
42
+ observer.observe(el);
43
+
44
+ return () => observer.disconnect();
45
+ }, [ready, ref]);
46
+
47
+ return ready;
48
+ };
49
+
50
+ export default useContainerReady;