@hsu-react/ui 2.5.3 → 2.5.5

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 (90) hide show
  1. package/es/components/ChainGraph/_hooks/useChainGraphData.js +29 -8
  2. package/es/components/ChainGraph/_hooks/useChainGraphLayout.js +14 -3
  3. package/es/components/Chart/Bar/index.js +49 -19
  4. package/es/components/Chart/Bubble/index.js +21 -6
  5. package/es/components/Chart/Common/index.js +7 -2
  6. package/es/components/Chart/Heatmap/index.js +21 -6
  7. package/es/components/Chart/Line/index.js +49 -19
  8. package/es/components/Chart/Pie/Pie3D/index.js +22 -6
  9. package/es/components/Chart/Pie/index.js +21 -6
  10. package/es/components/Chart/Sankey/index.js +11 -2
  11. package/es/components/Chart/Tree/index.js +11 -2
  12. package/es/components/Chat/ChatInput/index.js +7 -2
  13. package/es/components/Icon/index.js +8 -2
  14. package/es/components/Input/Number/index.js +28 -21
  15. package/es/components/Input/Password/index.js +44 -36
  16. package/es/components/Input/Search/index.js +44 -36
  17. package/es/components/Input/TextArea/index.js +64 -42
  18. package/es/components/Input/index.js +40 -34
  19. package/es/components/Select/_hooks/useSelectComposition.js +6 -2
  20. package/es/components/Select/_hooks/useSelectInputOptions.js +8 -2
  21. package/es/components/Slider/index.js +24 -15
  22. package/es/components/Table/_hooks/useAutoScrolling.js +16 -7
  23. package/es/components/Upload/_hooks/useUploadFileList.js +8 -2
  24. package/es/components/Upload/_hooks/useUploadOperations.js +8 -2
  25. package/es/hooks/useLatestRef.d.ts +24 -0
  26. package/es/hooks/useLatestRef.js +29 -0
  27. package/es/layout/Menu/_hooks/useOnlyLvOneMenu.js +9 -2
  28. package/lib/components/ChainGraph/_hooks/useChainGraphData.js +24 -7
  29. package/lib/components/ChainGraph/_hooks/useChainGraphLayout.js +12 -2
  30. package/lib/components/Chart/Bar/index.js +45 -18
  31. package/lib/components/Chart/Bubble/index.js +19 -6
  32. package/lib/components/Chart/Common/index.js +6 -2
  33. package/lib/components/Chart/Heatmap/index.js +19 -6
  34. package/lib/components/Chart/Line/index.js +45 -18
  35. package/lib/components/Chart/Pie/Pie3D/index.js +20 -6
  36. package/lib/components/Chart/Pie/index.js +19 -6
  37. package/lib/components/Chart/Sankey/index.js +10 -2
  38. package/lib/components/Chart/Tree/index.js +10 -2
  39. package/lib/components/Chat/ChatInput/index.js +6 -2
  40. package/lib/components/Icon/index.js +7 -2
  41. package/lib/components/Input/Number/index.js +27 -18
  42. package/lib/components/Input/Password/index.js +40 -26
  43. package/lib/components/Input/Search/index.js +42 -26
  44. package/lib/components/Input/TextArea/index.js +54 -26
  45. package/lib/components/Input/index.js +37 -24
  46. package/lib/components/Select/_hooks/useSelectComposition.js +6 -2
  47. package/lib/components/Select/_hooks/useSelectInputOptions.js +7 -2
  48. package/lib/components/Slider/index.js +23 -11
  49. package/lib/components/Table/_hooks/useAutoScrolling.js +16 -7
  50. package/lib/components/Upload/_hooks/useUploadFileList.js +7 -2
  51. package/lib/components/Upload/_hooks/useUploadOperations.js +7 -2
  52. package/lib/hooks/useLatestRef.d.ts +24 -0
  53. package/lib/hooks/useLatestRef.js +35 -0
  54. package/lib/layout/Menu/_hooks/useOnlyLvOneMenu.js +8 -2
  55. package/package.json +9 -3
  56. package/src/__tests__/callbackDepsGuard.test.ts +119 -0
  57. package/src/__tests__/setup.ts +26 -0
  58. package/src/components/ChainGraph/_hooks/callbacks.test.tsx +155 -0
  59. package/src/components/ChainGraph/_hooks/useChainGraphData.ts +38 -10
  60. package/src/components/ChainGraph/_hooks/useChainGraphLayout.ts +16 -3
  61. package/src/components/Chart/Bar/index.tsx +59 -21
  62. package/src/components/Chart/Bubble/index.tsx +22 -7
  63. package/src/components/Chart/Common/index.tsx +7 -2
  64. package/src/components/Chart/Heatmap/index.tsx +25 -6
  65. package/src/components/Chart/Line/index.tsx +59 -21
  66. package/src/components/Chart/Pie/Pie3D/index.tsx +22 -7
  67. package/src/components/Chart/Pie/index.tsx +22 -7
  68. package/src/components/Chart/Sankey/index.tsx +10 -4
  69. package/src/components/Chart/Tree/index.tsx +10 -4
  70. package/src/components/Chart/callbacks.test.tsx +315 -0
  71. package/src/components/Chat/ChatInput/index.tsx +7 -2
  72. package/src/components/Icon/index.tsx +7 -2
  73. package/src/components/Input/Number/index.tsx +25 -23
  74. package/src/components/Input/Password/index.tsx +37 -26
  75. package/src/components/Input/Search/index.tsx +39 -26
  76. package/src/components/Input/TextArea/index.test.tsx +208 -0
  77. package/src/components/Input/TextArea/index.tsx +56 -30
  78. package/src/components/Input/index.test.tsx +189 -0
  79. package/src/components/Input/index.tsx +36 -29
  80. package/src/components/Select/_hooks/useSelectComposition.test.ts +47 -0
  81. package/src/components/Select/_hooks/useSelectComposition.ts +6 -2
  82. package/src/components/Select/_hooks/useSelectInputOptions.ts +6 -9
  83. package/src/components/Slider/index.tsx +22 -12
  84. package/src/components/Table/_hooks/useAutoScrolling.test.tsx +95 -0
  85. package/src/components/Table/_hooks/useAutoScrolling.ts +16 -7
  86. package/src/components/Upload/_hooks/useUploadFileList.ts +6 -2
  87. package/src/components/Upload/_hooks/useUploadOperations.ts +7 -2
  88. package/src/hooks/useLatestRef.ts +31 -0
  89. package/src/layout/Menu/_hooks/useOnlyLvOneMenu.test.tsx +56 -0
  90. package/src/layout/Menu/_hooks/useOnlyLvOneMenu.ts +8 -2
@@ -19,6 +19,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from "react";
19
19
  import { autoScrollLegend } from "../chartUtils";
20
20
  import styles from "../index.module.scss";
21
21
  import ChartPie3D from "./Pie3D";
22
+ import { useLatestRef } from "../../../hooks/useLatestRef";
22
23
  import { jsx as _jsx } from "react/jsx-runtime";
23
24
  var ChartPie = function ChartPie(props) {
24
25
  var className = props.className,
@@ -207,7 +208,16 @@ var ChartPie = function ChartPie(props) {
207
208
  if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
208
209
  (_chartInstanceRef$cur = chartInstanceRef.current) === null || _chartInstanceRef$cur === void 0 || _chartInstanceRef$cur.resize();
209
210
  }, []);
211
+ var onChartRef = useLatestRef(onChart);
210
212
 
213
+ // onClick 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 跟着重跑就会
214
+ // 再走一遍 setOption(notMerge),动画重播、悬浮/高亮态被清掉。注册与否看布尔量,
215
+ // 这样「从无到有传入 onClick」仍会注册;实际调用取 ref 里的最新引用。
216
+ var onClickRef = useLatestRef(onClick);
217
+ var hasOnClick = !!onClick;
218
+
219
+ // 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
220
+ // 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
211
221
  // Initialize the chart
212
222
  useEffect(function () {
213
223
  if (!chartRef.current || !containerReady) return;
@@ -215,10 +225,11 @@ var ChartPie = function ChartPie(props) {
215
225
  // Initialize or reuse the existing instance
216
226
  var chart = chartInstanceRef.current;
217
227
  if (!chart) {
228
+ var _onChartRef$current;
218
229
  chart = echarts.init(chartRef.current);
219
230
  chartInstanceRef.current = chart;
220
231
  // Call the onChart callback on first initialization
221
- onChart === null || onChart === void 0 || onChart(chart);
232
+ (_onChartRef$current = onChartRef.current) === null || _onChartRef$current === void 0 || _onChartRef$current.call(onChartRef, chart);
222
233
  }
223
234
 
224
235
  // Apply the option
@@ -234,9 +245,13 @@ var ChartPie = function ChartPie(props) {
234
245
  }
235
246
 
236
247
  // Add click event
237
- if (onClick) {
248
+ var handleClick = function handleClick(event) {
249
+ var _onClickRef$current;
250
+ (_onClickRef$current = onClickRef.current) === null || _onClickRef$current === void 0 || _onClickRef$current.call(onClickRef, event);
251
+ };
252
+ if (hasOnClick) {
238
253
  var _chartInstanceRef$cur2;
239
- (_chartInstanceRef$cur2 = chartInstanceRef.current) === null || _chartInstanceRef$cur2 === void 0 || _chartInstanceRef$cur2.on("click", onClick);
254
+ (_chartInstanceRef$cur2 = chartInstanceRef.current) === null || _chartInstanceRef$cur2 === void 0 || _chartInstanceRef$cur2.on("click", handleClick);
240
255
  }
241
256
 
242
257
  // Legend auto-scroll
@@ -257,9 +272,9 @@ var ChartPie = function ChartPie(props) {
257
272
  // Cleanup function
258
273
  return function () {
259
274
  window.removeEventListener("resize", handleResize);
260
- if (onClick) {
275
+ if (hasOnClick) {
261
276
  var _chartInstanceRef$cur3;
262
- (_chartInstanceRef$cur3 = chartInstanceRef.current) === null || _chartInstanceRef$cur3 === void 0 || _chartInstanceRef$cur3.off("click", onClick);
277
+ (_chartInstanceRef$cur3 = chartInstanceRef.current) === null || _chartInstanceRef$cur3 === void 0 || _chartInstanceRef$cur3.off("click", handleClick);
263
278
  }
264
279
 
265
280
  // Clean up legend scrolling
@@ -268,7 +283,7 @@ var ChartPie = function ChartPie(props) {
268
283
  legendScrollRef.current = null;
269
284
  }
270
285
  };
271
- }, [chartOption, handleResize, onChart, onClick, enableLegendAutoScroll, seriesData, legendVisibleCount, legendScrollInterval, containerReady]);
286
+ }, [chartOption, handleResize, onChartRef, onClickRef, hasOnClick, enableLegendAutoScroll, seriesData, legendVisibleCount, legendScrollInterval, containerReady]);
272
287
 
273
288
  // Clean up resources when the component unmounts
274
289
  useEffect(function () {
@@ -14,6 +14,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from "react";
14
14
  import useContainerReady from "../_hooks/useContainerReady";
15
15
  import styles from "../index.module.scss";
16
16
  import * as echarts from "echarts";
17
+ import useLatestRef from "../../../hooks/useLatestRef";
17
18
  import { jsx as _jsx } from "react/jsx-runtime";
18
19
  var Sankey = function Sankey(props) {
19
20
  var className = props.className,
@@ -103,6 +104,13 @@ var Sankey = function Sankey(props) {
103
104
  return option;
104
105
  }, [seriesData, seriesLinks, series]);
105
106
 
107
+ // getImage 只是「出图后把 dataURL 交出去」的出口,既不决定图怎么画,
108
+ // 也不该决定要不要重画。它进依赖数组会让 effect 跟着重跑 → setOption(notMerge)
109
+ // → 图重绘 → 再触发一次 finished → 再回调;消费方把图存进 state 就是死循环。
110
+ // 而且 finished 监听只在首次 init 时注册,闭包里锁死的是首帧的 getImage,
111
+ // 后续换了引用永远调不到 —— 依赖数组写了它也没用,是条死依赖。走 ref 两个问题一起解决。
112
+ var getImageRef = useLatestRef(getImage);
113
+
106
114
  // Callback handling chart resize
107
115
  var handleResize = useCallback(function () {
108
116
  var _chartInstanceRef$cur;
@@ -125,7 +133,8 @@ var Sankey = function Sankey(props) {
125
133
 
126
134
  // Attach the finished event listener on first initialization
127
135
  chart.on("finished", function () {
128
- getImage === null || getImage === void 0 || getImage(chart.getDataURL({
136
+ var _getImageRef$current;
137
+ (_getImageRef$current = getImageRef.current) === null || _getImageRef$current === void 0 || _getImageRef$current.call(getImageRef, chart.getDataURL({
129
138
  type: "png",
130
139
  pixelRatio: 1,
131
140
  backgroundColor: "#fff"
@@ -149,7 +158,7 @@ var Sankey = function Sankey(props) {
149
158
  return function () {
150
159
  window.removeEventListener("resize", handleResize);
151
160
  };
152
- }, [chartOption, handleResize, getImage, containerReady]);
161
+ }, [chartOption, handleResize, getImageRef, containerReady]);
153
162
 
154
163
  // Clean up resources on unmount
155
164
  useEffect(function () {
@@ -8,6 +8,7 @@ import React, { useCallback, useEffect, useMemo, useRef } from "react";
8
8
  import useContainerReady from "../_hooks/useContainerReady";
9
9
  import styles from "../index.module.scss";
10
10
  import * as echarts from "echarts";
11
+ import useLatestRef from "../../../hooks/useLatestRef";
11
12
  import { deepCopy } from "hsu-utils";
12
13
  import { handleTreeData } from "../_utils/tree";
13
14
  import { jsx as _jsx } from "react/jsx-runtime";
@@ -38,6 +39,13 @@ var Tree = function Tree(props) {
38
39
  return option;
39
40
  }, [seriesData, series]);
40
41
 
42
+ // getImage 只是「出图后把 dataURL 交出去」的出口,既不决定图怎么画,
43
+ // 也不该决定要不要重画。它进依赖数组会让 effect 跟着重跑 → setOption(notMerge)
44
+ // → 图重绘 → 再触发一次 finished → 再回调;消费方把图存进 state 就是死循环。
45
+ // 而且 finished 监听只在首次 init 时注册,闭包里锁死的是首帧的 getImage,
46
+ // 后续换了引用永远调不到 —— 依赖数组写了它也没用,是条死依赖。走 ref 两个问题一起解决。
47
+ var getImageRef = useLatestRef(getImage);
48
+
41
49
  // Callback handling chart resize
42
50
  var handleResize = useCallback(function () {
43
51
  var _chartInstanceRef$cur;
@@ -60,7 +68,8 @@ var Tree = function Tree(props) {
60
68
 
61
69
  // Attach the finished event listener on first initialization
62
70
  chart.on("finished", function () {
63
- getImage === null || getImage === void 0 || getImage(chart.getDataURL({
71
+ var _getImageRef$current;
72
+ (_getImageRef$current = getImageRef.current) === null || _getImageRef$current === void 0 || _getImageRef$current.call(getImageRef, chart.getDataURL({
64
73
  type: "png",
65
74
  pixelRatio: 1,
66
75
  backgroundColor: "#fff"
@@ -84,7 +93,7 @@ var Tree = function Tree(props) {
84
93
  return function () {
85
94
  window.removeEventListener("resize", handleResize);
86
95
  };
87
- }, [chartOption, handleResize, getImage, containerReady]);
96
+ }, [chartOption, handleResize, getImageRef, containerReady]);
88
97
 
89
98
  // Clean up resources on unmount
90
99
  useEffect(function () {
@@ -18,6 +18,7 @@ import { useFileList } from "./_hooks";
18
18
  import { FileList, AgentToggleButton, SendButton, StopButton, UploadButton } from "./_components";
19
19
  import classNames from "classnames";
20
20
  import Button from "../../Button";
21
+ import { useLatestRef } from "../../../hooks/useLatestRef";
21
22
  import { jsx as _jsx } from "react/jsx-runtime";
22
23
  import { jsxs as _jsxs } from "react/jsx-runtime";
23
24
  import { Fragment as _Fragment } from "react/jsx-runtime";
@@ -82,7 +83,10 @@ var ChatInput = function ChatInput(props) {
82
83
  var isControlled = externalAgentsState !== undefined;
83
84
  // Use external state or internal state
84
85
  var agentsState = isControlled ? externalAgentsState : internalAgentsState;
86
+ var onAgentsChangeRef = useLatestRef(onAgentsChange);
85
87
 
88
+ // 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
89
+ // 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
86
90
  // When the model switches, sync the agents state (only in uncontrolled mode)
87
91
  useEffect(function () {
88
92
  if (isControlled) return; // In controlled mode, state is managed externally
@@ -91,10 +95,11 @@ var ChatInput = function ChatInput(props) {
91
95
  return m.value === (modelConfig === null || modelConfig === void 0 ? void 0 : modelConfig.modelType);
92
96
  });
93
97
  if (currentModel !== null && currentModel !== void 0 && currentModel.agents && currentModel.agents.length > 0) {
98
+ var _onAgentsChangeRef$cu;
94
99
  setInternalAgentsState(currentModel.agents);
95
- onAgentsChange === null || onAgentsChange === void 0 || onAgentsChange(currentModel.agents);
100
+ (_onAgentsChangeRef$cu = onAgentsChangeRef.current) === null || _onAgentsChangeRef$cu === void 0 || _onAgentsChangeRef$cu.call(onAgentsChangeRef, currentModel.agents);
96
101
  }
97
- }, [modelConfig === null || modelConfig === void 0 ? void 0 : modelConfig.modelType, modelConfig === null || modelConfig === void 0 ? void 0 : modelConfig.modelList, onAgentsChange, isControlled]);
102
+ }, [modelConfig === null || modelConfig === void 0 ? void 0 : modelConfig.modelType, modelConfig === null || modelConfig === void 0 ? void 0 : modelConfig.modelList, onAgentsChangeRef, isControlled]);
98
103
 
99
104
  // Based on the currently selected model, filter the feature configs supported by that model
100
105
  var currentModel = modelConfig === null || modelConfig === void 0 ? void 0 : modelConfig.modelList.find(function (m) {
@@ -12,6 +12,7 @@ import * as AntdIcons from "@ant-design/icons";
12
12
  import React, { useCallback, useEffect, useRef } from "react";
13
13
  import classNames from "classnames";
14
14
  import styles from "./index.module.scss";
15
+ import { useLatestRef } from "../../hooks/useLatestRef";
15
16
  import { jsx as _jsx } from "react/jsx-runtime";
16
17
  /** Check whether it is an AntD icon component name (e.g. "SettingOutlined") and return the corresponding component */
17
18
  var getAntdIcon = function getAntdIcon(icon) {
@@ -46,9 +47,14 @@ var Icon = /*#__PURE__*/React.forwardRef(function (props, forwardedRef) {
46
47
  forwardedRef.current = node;
47
48
  }
48
49
  }, [forwardedRef]);
50
+ var onRefRef = useLatestRef(onRef);
51
+
52
+ // 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
53
+ // 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
49
54
  useEffect(function () {
50
- onRef === null || onRef === void 0 || onRef(ref);
51
- }, [onRef, ref]);
55
+ var _onRefRef$current;
56
+ (_onRefRef$current = onRefRef.current) === null || _onRefRef$current === void 0 || _onRefRef$current.call(onRefRef, ref);
57
+ }, [onRefRef, ref]);
52
58
  var mergedStyle = _objectSpread({
53
59
  color: color,
54
60
  fontSize: typeof fontSize === "number" ? "".concat(fontSize, "px") : fontSize
@@ -18,7 +18,7 @@ import React, { useEffect, useRef, useState } from "react";
18
18
  import { CloseCircleFilled } from "@ant-design/icons";
19
19
  import classNames from "classnames";
20
20
  import styles from "./index.module.scss";
21
- import { useDebounceEffect } from "ahooks";
21
+ import { useLatestRef } from "../../../hooks/useLatestRef";
22
22
  import { jsx as _jsx } from "react/jsx-runtime";
23
23
  import { Fragment as _Fragment } from "react/jsx-runtime";
24
24
  import { jsxs as _jsxs } from "react/jsx-runtime";
@@ -40,6 +40,7 @@ var InputNumber = function InputNumber(props) {
40
40
  // `getRef` keeps handing consumers an `HTMLInputElement` exactly as before.
41
41
  var ref = useRef(null);
42
42
  var wrapperRef = useRef(null);
43
+ var getRefRef = useLatestRef(getRef);
43
44
 
44
45
  // On initialization, prefer value, then fall back to defaultValue
45
46
  var initialValue = value !== undefined ? typeof value === "number" ? "".concat(value) : (_value$toString = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString !== void 0 ? _value$toString : "" : defaultValue !== undefined ? typeof defaultValue === "number" ? "".concat(defaultValue) : (_defaultValue$toStrin = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.toString()) !== null && _defaultValue$toStrin !== void 0 ? _defaultValue$toStrin : "" : "";
@@ -47,19 +48,21 @@ var InputNumber = function InputNumber(props) {
47
48
  _useState2 = _slicedToArray(_useState, 2),
48
49
  _value = _useState2[0],
49
50
  setValue = _useState2[1];
50
- var _useState3 = useState(initialValue),
51
- _useState4 = _slicedToArray(_useState3, 2),
52
- lastValue = _useState4[0],
53
- setLastValue = _useState4[1];
54
51
  var prevValueRef = useRef(undefined);
55
- useDebounceEffect(function () {
56
- if (_value !== lastValue) {
57
- setLastValue(_value);
58
- onChange === null || onChange === void 0 || onChange(_value);
59
- }
60
- }, [_value, lastValue, onChange], {
61
- wait: 10
62
- });
52
+ // 最近一次通知出去的文本,代替原来那个用 state 记账的写法
53
+ var notifiedRef = useRef(initialValue);
54
+
55
+ /**
56
+ * `onChange` 是「用户改了输入」这个**事件**的通知,不是从 state 推导出来的结果,
57
+ * 所以在事件处理里发,不在 effect 里发。详见 `TextArea/index.tsx` 里那段说明——
58
+ * 旧写法(依赖数组里放 `onChange`、体内又调它、用 state 记「通知过没有」)
59
+ * 会让传内联箭头的消费方死循环。
60
+ */
61
+ var notify = function notify(next) {
62
+ if (next === notifiedRef.current) return;
63
+ notifiedRef.current = next;
64
+ onChange === null || onChange === void 0 || onChange(next);
65
+ };
63
66
  useEffect(function () {
64
67
  // Update internal state only when the external value prop actually changes
65
68
  if (prevValueRef.current !== value) {
@@ -76,30 +79,33 @@ var InputNumber = function InputNumber(props) {
76
79
  // 表示的是同一个数,此时以用户正在敲的那份文本为准。
77
80
  var sameNumber = value !== undefined && value !== null && _value !== "" && Number(value) === Number(_value);
78
81
  if (sameNumber) {
79
- // 只同步「已提交值」,不动正在编辑的文本
80
- setLastValue(_value);
82
+ // 只同步「已通知过的值」,不动正在编辑的文本
83
+ notifiedRef.current = _value;
81
84
  } else if (value !== undefined && value !== null) {
82
85
  var newValue = typeof value === "number" ? "".concat(value) : value === null || value === void 0 ? void 0 : value.toString();
86
+ // 外部把值改了,之前通知过什么就不作数了,重新以外部值为准
87
+ notifiedRef.current = newValue;
83
88
  setValue(newValue);
84
- setLastValue(newValue);
85
89
  } else {
86
90
  // Clear only on initialization or when explicitly set to undefined/null externally
91
+ notifiedRef.current = "";
87
92
  setValue("");
88
- setLastValue("");
89
93
  }
90
94
  }
91
95
  // `_value` 是刻意不进依赖的:这个 effect 只该在**外部** value 变化时跑,
92
96
  // 把它加进来会让每次键入都重跑一遍,等于绕过上面那个 prevValueRef 判断
93
97
  // eslint-disable-next-line react-hooks/exhaustive-deps
94
98
  }, [value]);
99
+
100
+ // 只在挂载时把 ref 交出去。`getRef` 进依赖数组同样会被内联箭头带着每次渲染重跑
95
101
  useEffect(function () {
96
- getRef === null || getRef === void 0 || getRef(ref.current);
97
- }, [getRef]);
102
+ var _getRefRef$current;
103
+ (_getRefRef$current = getRefRef.current) === null || _getRefRef$current === void 0 || _getRefRef$current.call(getRefRef, ref.current);
104
+ }, [getRefRef]);
98
105
  var handleClear = function handleClear(e) {
99
106
  e.stopPropagation();
100
107
  setValue("");
101
- setLastValue("");
102
- onChange === null || onChange === void 0 || onChange("");
108
+ notify("");
103
109
  };
104
110
  var showClear = allowClear && _value !== "" && _value !== undefined && !disabled;
105
111
  var input = /*#__PURE__*/_jsx(AntdInputNumber, _objectSpread(_objectSpread({
@@ -108,6 +114,7 @@ var InputNumber = function InputNumber(props) {
108
114
  onChange: function onChange(e) {
109
115
  var newValue = e === null || e === undefined ? "" : (typeof e === "number" ? e : e || "").toString();
110
116
  setValue(newValue);
117
+ notify(newValue);
111
118
  },
112
119
  className: classNames(styles.antdInput, className),
113
120
  controls: false,
@@ -17,6 +17,7 @@ import AntdPassword from "antd/es/input/Password";
17
17
  import React, { useEffect, useRef, useState } from "react";
18
18
  import classNames from "classnames";
19
19
  import styles from "./index.module.scss";
20
+ import { useLatestRef } from "../../../hooks/useLatestRef";
20
21
  import { jsx as _jsx } from "react/jsx-runtime";
21
22
  var Password = function Password(props) {
22
23
  var _value$toString, _defaultValue$toStrin;
@@ -26,62 +27,69 @@ var Password = function Password(props) {
26
27
  defaultValue = props.defaultValue,
27
28
  className = props.className,
28
29
  inputConfig = _objectWithoutProperties(props, _excluded);
29
- var _useState = useState(false),
30
- _useState2 = _slicedToArray(_useState, 2),
31
- isComposing = _useState2[0],
32
- setComposing = _useState2[1];
33
30
  var ref = useRef(null);
31
+ var getRefRef = useLatestRef(getRef);
32
+ // 输入法组字期间不往外通知。放 ref 不放 state:它只决定「要不要发通知」,
33
+ // 不参与渲染,而且必须在同一次事件里立刻生效
34
+ var composingRef = useRef(false);
34
35
 
35
36
  // On initialization, prefer value, then fall back to defaultValue
36
37
  var initialValue = value !== undefined ? (_value$toString = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString !== void 0 ? _value$toString : "" : defaultValue !== undefined ? (_defaultValue$toStrin = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.toString()) !== null && _defaultValue$toStrin !== void 0 ? _defaultValue$toStrin : "" : "";
37
- var _useState3 = useState(initialValue),
38
- _useState4 = _slicedToArray(_useState3, 2),
39
- _value = _useState4[0],
40
- setValue = _useState4[1];
41
- var _useState5 = useState(initialValue),
42
- _useState6 = _slicedToArray(_useState5, 2),
43
- lastValue = _useState6[0],
44
- setLastValue = _useState6[1];
38
+ var _useState = useState(initialValue),
39
+ _useState2 = _slicedToArray(_useState, 2),
40
+ _value = _useState2[0],
41
+ setValue = _useState2[1];
45
42
  var prevValueRef = useRef(undefined);
46
- useEffect(function () {
47
- if (!isComposing && _value !== lastValue) {
48
- var trimmedValue = _value.trim();
49
- var finalValue = trimmedValue === "" ? "" : _value;
50
- setLastValue(finalValue);
51
- onChange === null || onChange === void 0 || onChange(finalValue);
52
- }
53
- }, [_value, isComposing, lastValue, onChange]);
43
+ // 最近一次通知出去的原文。只用来去掉 compositionend 与 input 两个事件的重复通知
44
+ var notifiedRef = useRef(initialValue);
45
+
46
+ /**
47
+ * `onChange` 是「用户改了输入」这个**事件**的通知,不是从 state 推导出来的结果,
48
+ * 所以在事件处理里发,不在 effect 里发。详见 `TextArea/index.tsx` 里那段说明——
49
+ * 旧写法(依赖数组里放 `onChange`、体内又调它、用 state 记「通知过没有」)
50
+ * 会让传内联箭头的消费方死循环。
51
+ */
52
+ var notify = function notify(next) {
53
+ if (next === notifiedRef.current) return;
54
+ notifiedRef.current = next;
55
+ // 全是空白等同于空
56
+ onChange === null || onChange === void 0 || onChange(next.trim() === "" ? "" : next);
57
+ };
54
58
  useEffect(function () {
55
59
  // Update internal state only when the external value prop actually changes
56
60
  if (prevValueRef.current !== value) {
61
+ var _value$toString2;
57
62
  prevValueRef.current = value;
58
- if (value !== undefined) {
59
- setValue(value === null || value === void 0 ? void 0 : value.toString());
60
- setLastValue(value === null || value === void 0 ? void 0 : value.toString());
61
- } else {
62
- // Clear only on initialization or when explicitly set to undefined externally
63
- setValue("");
64
- setLastValue("");
65
- }
63
+
64
+ // 外部把值改了,之前通知过什么就不作数了,重新以外部值为准
65
+ var next = value !== undefined ? (_value$toString2 = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString2 !== void 0 ? _value$toString2 : "" : "";
66
+ notifiedRef.current = next;
67
+ setValue(next);
66
68
  }
67
69
  }, [value]);
70
+
71
+ // 只在挂载时把 ref 交出去。`getRef` 进依赖数组同样会被内联箭头带着每次渲染重跑
68
72
  useEffect(function () {
69
- getRef === null || getRef === void 0 || getRef(ref.current);
70
- }, [getRef]);
73
+ var _getRefRef$current;
74
+ (_getRefRef$current = getRefRef.current) === null || _getRefRef$current === void 0 || _getRefRef$current.call(getRefRef, ref.current);
75
+ }, [getRefRef]);
71
76
  return /*#__PURE__*/_jsx(AntdPassword, _objectSpread({
72
77
  className: classNames(styles.antdInput, className),
73
78
  ref: ref,
74
79
  onCompositionStart: function onCompositionStart() {
75
- return setComposing(true);
80
+ composingRef.current = true;
76
81
  },
77
- onCompositionEnd: function onCompositionEnd() {
78
- setTimeout(function () {
79
- setComposing(false);
80
- }, 1);
82
+ onCompositionEnd: function onCompositionEnd(e) {
83
+ composingRef.current = false;
84
+ // 组完字的那一下自己发通知。浏览器之间 compositionend 与 input 的先后
85
+ // 不一致,`notify` 里按原文去重,两种顺序都只会发出一次
86
+ notify(e.currentTarget.value);
81
87
  },
82
88
  value: _value,
83
89
  onChange: function onChange(e) {
84
- setValue(e.target.value);
90
+ var next = e.target.value;
91
+ setValue(next);
92
+ if (!composingRef.current) notify(next);
85
93
  }
86
94
  }, inputConfig));
87
95
  };
@@ -17,6 +17,7 @@ import React, { useEffect, useRef, useState } from "react";
17
17
  import AntdSearch from "antd/es/input/Search";
18
18
  import classNames from "classnames";
19
19
  import styles from "./index.module.scss";
20
+ import { useLatestRef } from "../../../hooks/useLatestRef";
20
21
  import { jsx as _jsx } from "react/jsx-runtime";
21
22
  var Search = function Search(props) {
22
23
  var _value$toString, _defaultValue$toStrin;
@@ -26,62 +27,69 @@ var Search = function Search(props) {
26
27
  defaultValue = props.defaultValue,
27
28
  className = props.className,
28
29
  inputConfig = _objectWithoutProperties(props, _excluded);
29
- var _useState = useState(false),
30
- _useState2 = _slicedToArray(_useState, 2),
31
- isComposing = _useState2[0],
32
- setComposing = _useState2[1];
33
30
  var ref = useRef(null);
31
+ var getRefRef = useLatestRef(getRef);
32
+ // 输入法组字期间不往外通知。放 ref 不放 state:它只决定「要不要发通知」,
33
+ // 不参与渲染,而且必须在同一次事件里立刻生效
34
+ var composingRef = useRef(false);
34
35
 
35
36
  // On initialization, prefer value, then fall back to defaultValue
36
37
  var initialValue = value !== undefined ? (_value$toString = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString !== void 0 ? _value$toString : "" : defaultValue !== undefined ? (_defaultValue$toStrin = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.toString()) !== null && _defaultValue$toStrin !== void 0 ? _defaultValue$toStrin : "" : "";
37
- var _useState3 = useState(initialValue),
38
- _useState4 = _slicedToArray(_useState3, 2),
39
- _value = _useState4[0],
40
- setValue = _useState4[1];
41
- var _useState5 = useState(initialValue),
42
- _useState6 = _slicedToArray(_useState5, 2),
43
- lastValue = _useState6[0],
44
- setLastValue = _useState6[1];
38
+ var _useState = useState(initialValue),
39
+ _useState2 = _slicedToArray(_useState, 2),
40
+ _value = _useState2[0],
41
+ setValue = _useState2[1];
45
42
  var prevValueRef = useRef(undefined);
46
- useEffect(function () {
47
- if (!isComposing && _value !== lastValue) {
48
- var trimmedValue = _value.trim();
49
- var finalValue = trimmedValue === "" ? "" : _value;
50
- setLastValue(finalValue);
51
- onChange === null || onChange === void 0 || onChange(finalValue);
52
- }
53
- }, [_value, isComposing, lastValue, onChange]);
43
+ // 最近一次通知出去的原文。只用来去掉 compositionend 与 input 两个事件的重复通知
44
+ var notifiedRef = useRef(initialValue);
45
+
46
+ /**
47
+ * `onChange` 是「用户改了输入」这个**事件**的通知,不是从 state 推导出来的结果,
48
+ * 所以在事件处理里发,不在 effect 里发。详见 `TextArea/index.tsx` 里那段说明——
49
+ * 旧写法(依赖数组里放 `onChange`、体内又调它、用 state 记「通知过没有」)
50
+ * 会让传内联箭头的消费方死循环。
51
+ */
52
+ var notify = function notify(next) {
53
+ if (next === notifiedRef.current) return;
54
+ notifiedRef.current = next;
55
+ // 全是空白等同于空
56
+ onChange === null || onChange === void 0 || onChange(next.trim() === "" ? "" : next);
57
+ };
54
58
  useEffect(function () {
55
59
  // Update internal state only when the external value prop actually changes
56
60
  if (prevValueRef.current !== value) {
61
+ var _value$toString2;
57
62
  prevValueRef.current = value;
58
- if (value !== undefined) {
59
- setValue(value === null || value === void 0 ? void 0 : value.toString());
60
- setLastValue(value === null || value === void 0 ? void 0 : value.toString());
61
- } else {
62
- // Clear only on initialization or when explicitly set to undefined externally
63
- setValue("");
64
- setLastValue("");
65
- }
63
+
64
+ // 外部把值改了,之前通知过什么就不作数了,重新以外部值为准
65
+ var next = value !== undefined ? (_value$toString2 = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString2 !== void 0 ? _value$toString2 : "" : "";
66
+ notifiedRef.current = next;
67
+ setValue(next);
66
68
  }
67
69
  }, [value]);
70
+
71
+ // 只在挂载时把 ref 交出去。`getRef` 进依赖数组同样会被内联箭头带着每次渲染重跑
68
72
  useEffect(function () {
69
- getRef === null || getRef === void 0 || getRef(ref.current);
70
- }, [getRef]);
73
+ var _getRefRef$current;
74
+ (_getRefRef$current = getRefRef.current) === null || _getRefRef$current === void 0 || _getRefRef$current.call(getRefRef, ref.current);
75
+ }, [getRefRef]);
71
76
  return /*#__PURE__*/_jsx(AntdSearch, _objectSpread({
72
77
  ref: ref,
73
78
  className: classNames(styles.antdInput, className),
74
79
  onCompositionStart: function onCompositionStart() {
75
- return setComposing(true);
80
+ composingRef.current = true;
76
81
  },
77
- onCompositionEnd: function onCompositionEnd() {
78
- setTimeout(function () {
79
- setComposing(false);
80
- }, 1);
82
+ onCompositionEnd: function onCompositionEnd(e) {
83
+ composingRef.current = false;
84
+ // 组完字的那一下自己发通知。浏览器之间 compositionend 与 input 的先后
85
+ // 不一致,`notify` 里按原文去重,两种顺序都只会发出一次
86
+ notify(e.currentTarget.value);
81
87
  },
82
88
  value: _value,
83
89
  onChange: function onChange(e) {
84
- return setValue(e.target.value);
90
+ var next = e.target.value;
91
+ setValue(next);
92
+ if (!composingRef.current) notify(next);
85
93
  }
86
94
  }, inputConfig));
87
95
  };