@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
@@ -17,6 +17,7 @@ import AntdTextArea from "antd/es/input/TextArea";
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
  import { jsxs as _jsxs } from "react/jsx-runtime";
22
23
  var TextArea = function TextArea(props) {
@@ -40,56 +41,74 @@ var TextArea = function TextArea(props) {
40
41
  autoSize = props.autoSize,
41
42
  style = props.style,
42
43
  inputConfig = _objectWithoutProperties(props, _excluded);
44
+ var ref = useRef(null);
43
45
  var _useState = useState(false),
44
46
  _useState2 = _slicedToArray(_useState, 2),
45
- isComposing = _useState2[0],
46
- setComposing = _useState2[1];
47
- var ref = useRef(null);
48
- var _useState3 = useState(false),
49
- _useState4 = _slicedToArray(_useState3, 2),
50
- focused = _useState4[0],
51
- setFocused = _useState4[1];
47
+ focused = _useState2[0],
48
+ setFocused = _useState2[1];
52
49
 
53
50
  // On initialization, prefer value, then fall back to defaultValue
54
51
  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 : "" : "";
55
- var _useState5 = useState(initialValue),
52
+ var _useState3 = useState(initialValue),
53
+ _useState4 = _slicedToArray(_useState3, 2),
54
+ _value = _useState4[0],
55
+ setValue = _useState4[1];
56
+ var _useState5 = useState(false),
56
57
  _useState6 = _slicedToArray(_useState5, 2),
57
- _value = _useState6[0],
58
- setValue = _useState6[1];
59
- var _useState7 = useState(initialValue),
60
- _useState8 = _slicedToArray(_useState7, 2),
61
- lastValue = _useState8[0],
62
- setLastValue = _useState8[1];
63
- var _useState9 = useState(false),
64
- _useState10 = _slicedToArray(_useState9, 2),
65
- hasError = _useState10[0],
66
- setHasError = _useState10[1];
58
+ hasError = _useState6[0],
59
+ setHasError = _useState6[1];
67
60
  var prevValueRef = useRef(undefined);
68
- useEffect(function () {
69
- if (!isComposing && _value !== lastValue) {
70
- var trimmedValue = _value.trim();
71
- var finalValue = trimmedValue === "" ? "" : _value;
72
- setLastValue(finalValue);
73
- onChange === null || onChange === void 0 || onChange(finalValue);
74
- }
75
- }, [isComposing, onChange, _value, lastValue]);
61
+ var getRefRef = useLatestRef(getRef);
62
+
63
+ // 输入法组字期间不往外通知。放 ref 不放 state:它只影响「要不要发通知」,
64
+ // 不参与渲染,而且必须在同一次事件里立刻生效
65
+ var composingRef = useRef(false);
66
+ // 最近一次通知出去的原文。只用来去掉 compositionend input 两个事件的重复通知
67
+ var notifiedRef = useRef(initialValue);
68
+
69
+ /**
70
+ * `onChange` 是「用户改了输入」这个**事件**的通知,不是从 state 推导出来的结果,
71
+ * 所以它在事件处理里发,不在 effect 里发。
72
+ *
73
+ * 早先是一个 effect 在发:依赖数组里放着 `onChange`,effect 体内又调它,
74
+ * 还用一个 `lastValue` state 记「这个值通知过没有」。两处都是错的——
75
+ * 1. 消费方传内联箭头(`onChange={(v) => setX(v)}`,React 里最常见的写法)时
76
+ * 每次渲染都是新引用,effect 每次渲染都重跑;
77
+ * 2. 「通知过没有」的记账放在 React state 里就**不可靠**:父级用 MobX /
78
+ * `useSyncExternalStore` 这类外部 store 回写 `value` 时,React 会以更高的
79
+ * 同步优先级重渲一次,effect 里排的那次 `setLastValue` 会被这一趟**跳过**,
80
+ * 于是 effect 读到过期的记账、判定「还没通知」,再发一次 → 父级再回写 →
81
+ * 死循环,React 抛 `Maximum update depth exceeded`,输入框被上层
82
+ * ErrorBoundary 整块摘掉。
83
+ *
84
+ * 现在 `onChange` 的引用稳不稳定完全不影响正确性:永远调到最新的那个回调,
85
+ * 且只在真的有新文本时调一次。
86
+ */
87
+ var notify = function notify(next) {
88
+ if (next === notifiedRef.current) return;
89
+ notifiedRef.current = next;
90
+ // 全是空白等同于空:外部拿到的是 "",但输入框里用户敲的空格照旧留着
91
+ onChange === null || onChange === void 0 || onChange(next.trim() === "" ? "" : next);
92
+ };
76
93
  useEffect(function () {
77
94
  // Update internal state only when the external value prop actually changes
78
95
  if (prevValueRef.current !== value) {
96
+ var _value$toString2;
79
97
  prevValueRef.current = value;
80
- if (value !== undefined) {
81
- setValue(value === null || value === void 0 ? void 0 : value.toString());
82
- setLastValue(value === null || value === void 0 ? void 0 : value.toString());
83
- } else {
84
- // Clear only on initialization or when explicitly set to undefined externally
85
- setValue("");
86
- setLastValue("");
87
- }
98
+
99
+ // 外部把值改了,之前通知过什么就不作数了,重新以外部值为准
100
+ var next = value !== undefined ? (_value$toString2 = value === null || value === void 0 ? void 0 : value.toString()) !== null && _value$toString2 !== void 0 ? _value$toString2 : "" : "";
101
+ notifiedRef.current = next;
102
+ setValue(next);
88
103
  }
89
104
  }, [value]);
105
+
106
+ // 只在挂载时把 ref 交出去。`getRef` 进依赖数组同样会被内联箭头带着每次渲染重跑,
107
+ // 消费方在里面 setState 就是一个死循环
90
108
  useEffect(function () {
91
- getRef === null || getRef === void 0 || getRef(ref.current);
92
- }, [getRef]);
109
+ var _getRefRef$current;
110
+ (_getRefRef$current = getRefRef.current) === null || _getRefRef$current === void 0 || _getRefRef$current.call(getRefRef, ref.current);
111
+ }, [getRefRef]);
93
112
  useEffect(function () {
94
113
  var _ref$current2;
95
114
  // Use a MutationObserver to watch for error state changes
@@ -124,19 +143,22 @@ var TextArea = function TextArea(props) {
124
143
  children: [prefix, /*#__PURE__*/_jsx(AntdTextArea, _objectSpread({
125
144
  ref: ref,
126
145
  onCompositionStart: function onCompositionStart() {
127
- return setComposing(true);
146
+ composingRef.current = true;
128
147
  },
129
- onCompositionEnd: function onCompositionEnd() {
130
- setTimeout(function () {
131
- setComposing(false);
132
- }, 1);
148
+ onCompositionEnd: function onCompositionEnd(e) {
149
+ composingRef.current = false;
150
+ // 组完字的那一下自己发通知。浏览器之间 compositionend 与 input 的先后
151
+ // 不一致,`notify` 里按原文去重,两种顺序都只会发出一次
152
+ notify(e.currentTarget.value);
133
153
  },
134
154
  value: _value,
135
155
  autoSize: disabled && text ? autoSize || {
136
156
  minRows: 1
137
157
  } : autoSize,
138
158
  onChange: function onChange(e) {
139
- setValue(e.target.value);
159
+ var next = e.target.value;
160
+ setValue(next);
161
+ if (!composingRef.current) notify(next);
140
162
  },
141
163
  onFocus: function onFocus(e) {
142
164
  setFocused(true);
@@ -22,7 +22,7 @@ import TextArea from "./TextArea";
22
22
  import classNames from "classnames";
23
23
  import styles from "./index.module.scss";
24
24
  import RangeInput from "./Range";
25
- import { useDebounceEffect } from "ahooks";
25
+ import { useLatestRef } from "../../hooks/useLatestRef";
26
26
  import { jsx as _jsx } from "react/jsx-runtime";
27
27
  var Input = function Input(props) {
28
28
  var onChange = props.onChange,
@@ -39,11 +39,11 @@ var Input = function Input(props) {
39
39
  tooltip = props.tooltip,
40
40
  escapeCharacters = props.escapeCharacters,
41
41
  inputConfig = _objectWithoutProperties(props, _excluded);
42
- var _useState = useState(false),
43
- _useState2 = _slicedToArray(_useState, 2),
44
- isComposing = _useState2[0],
45
- setComposing = _useState2[1];
46
42
  var ref = useRef(null);
43
+ var getRefRef = useLatestRef(getRef);
44
+ // 输入法组字期间不往外通知。放 ref 不放 state:它只决定「要不要发通知」,
45
+ // 不参与渲染,而且必须在同一次事件里立刻生效
46
+ var composingRef = useRef(false);
47
47
 
48
48
  // Handle escaping: if the value contains characters listed in escapeCharacters, prefix them with an escape
49
49
  var escapeValue = useCallback(function (val) {
@@ -112,27 +112,27 @@ var Input = function Input(props) {
112
112
  return unescapeValue(rawValue);
113
113
  };
114
114
  var initialValue = getInitialValue();
115
- var _useState3 = useState(initialValue),
116
- _useState4 = _slicedToArray(_useState3, 2),
117
- _value = _useState4[0],
118
- setValue = _useState4[1];
119
- var _useState5 = useState(initialValue),
120
- _useState6 = _slicedToArray(_useState5, 2),
121
- lastValue = _useState6[0],
122
- setLastValue = _useState6[1];
115
+ var _useState = useState(initialValue),
116
+ _useState2 = _slicedToArray(_useState, 2),
117
+ _value = _useState2[0],
118
+ setValue = _useState2[1];
123
119
  var prevValueRef = useRef(undefined);
124
- useDebounceEffect(function () {
125
- if (!isComposing && _value !== lastValue) {
126
- var trimmedValue = _value.trim();
127
- var finalValue = trimmedValue === "" ? "" : _value;
128
- setLastValue(finalValue);
129
- // If escapeCharacters is set and the value matches, return the escaped value
130
- var escapedValue = escapeValue(finalValue);
131
- onChange === null || onChange === void 0 || onChange(escapedValue);
132
- }
133
- }, [_value, isComposing, lastValue, onChange, escapeValue], {
134
- wait: 10
135
- });
120
+ // 最近一次通知出去的原文(未转义)。只用来去掉 compositionend 与 input
121
+ // 两个事件的重复通知
122
+ var notifiedRef = useRef(initialValue);
123
+
124
+ /**
125
+ * `onChange` 是「用户改了输入」这个**事件**的通知,不是从 state 推导出来的结果,
126
+ * 所以在事件处理里发,不在 effect 里发。详见 `TextArea/index.tsx` 里那段说明——
127
+ * 旧写法(依赖数组里放 `onChange`、体内又调它、用 state 记「通知过没有」)
128
+ * 会让传内联箭头的消费方死循环。
129
+ */
130
+ var notify = function notify(next) {
131
+ if (next === notifiedRef.current) return;
132
+ notifiedRef.current = next;
133
+ // 全是空白等同于空;escapeCharacters 命中时对外给转义后的值
134
+ onChange === null || onChange === void 0 || onChange(escapeValue(next.trim() === "" ? "" : next));
135
+ };
136
136
  useEffect(function () {
137
137
  // Update internal state only when the external value prop actually changes
138
138
  if (prevValueRef.current !== value) {
@@ -141,18 +141,22 @@ var Input = function Input(props) {
141
141
  var rawValue = typeof value === "number" ? "".concat(value) : value === null || value === void 0 ? void 0 : value.toString();
142
142
  // Unescape before displaying
143
143
  var newValue = unescapeValue(rawValue);
144
+ // 外部把值改了,之前通知过什么就不作数了,重新以外部值为准
145
+ notifiedRef.current = newValue;
144
146
  setValue(newValue);
145
- setLastValue(newValue);
146
147
  } else {
147
148
  // Clear only on initialization or when explicitly set to undefined externally
149
+ notifiedRef.current = "";
148
150
  setValue("");
149
- setLastValue("");
150
151
  }
151
152
  }
152
153
  }, [value, unescapeValue]);
154
+
155
+ // 只在挂载时把 ref 交出去。`getRef` 进依赖数组同样会被内联箭头带着每次渲染重跑
153
156
  useEffect(function () {
154
- getRef === null || getRef === void 0 || getRef(ref.current);
155
- }, [getRef]);
157
+ var _getRefRef$current;
158
+ (_getRefRef$current = getRefRef.current) === null || _getRefRef$current === void 0 || _getRefRef$current.call(getRefRef, ref.current);
159
+ }, [getRefRef]);
156
160
  return /*#__PURE__*/_jsx(Tooltip, _objectSpread(_objectSpread({
157
161
  placement: "topLeft"
158
162
  }, tooltip), {}, {
@@ -161,12 +165,13 @@ var Input = function Input(props) {
161
165
  disabled: disabled,
162
166
  placeholder: disabled ? "" : placeholder,
163
167
  onCompositionStart: function onCompositionStart() {
164
- return setComposing(true);
168
+ composingRef.current = true;
165
169
  },
166
- onCompositionEnd: function onCompositionEnd() {
167
- setTimeout(function () {
168
- setComposing(false);
169
- }, 1);
170
+ onCompositionEnd: function onCompositionEnd(e) {
171
+ composingRef.current = false;
172
+ // 组完字的那一下自己发通知。浏览器之间 compositionend 与 input 的先后
173
+ // 不一致,`notify` 里按原文去重,两种顺序都只会发出一次
174
+ notify(unescapeValue(e.currentTarget.value));
170
175
  },
171
176
  value: _value,
172
177
  onChange: function onChange(e) {
@@ -174,6 +179,7 @@ var Input = function Input(props) {
174
179
  var inputValue = e.target.value;
175
180
  var unescapedInput = unescapeValue(inputValue);
176
181
  setValue(unescapedInput);
182
+ if (!composingRef.current) notify(unescapedInput);
177
183
  },
178
184
  className: classNames(styles.antdInput, className),
179
185
  type: type,
@@ -14,6 +14,7 @@ export function useSelectComposition(_ref) {
14
14
  _useState2 = _slicedToArray(_useState, 2),
15
15
  isComposing = _useState2[0],
16
16
  setComposing = _useState2[1];
17
+ var hasOnSearch = !!onSearch;
17
18
  useEffect(function () {
18
19
  var compositionend = function compositionend() {
19
20
  setComposing(false);
@@ -21,7 +22,7 @@ export function useSelectComposition(_ref) {
21
22
  var compositionstart = function compositionstart() {
22
23
  setComposing(true);
23
24
  };
24
- if (onSearch) {
25
+ if (hasOnSearch) {
25
26
  window.addEventListener("compositionstart", compositionstart);
26
27
  window.addEventListener("compositionend", compositionend);
27
28
  }
@@ -29,7 +30,10 @@ export function useSelectComposition(_ref) {
29
30
  window.removeEventListener("compositionstart", compositionstart);
30
31
  window.removeEventListener("compositionend", compositionend);
31
32
  };
32
- }, [onSearch]);
33
+ // onSearch 在这里只被当作「要不要监听输入法事件」的真值判断,effect 体从不调它。
34
+ // 直接进依赖数组的话,消费方传内联箭头就会每渲染一次拆一次、装一次 window 监听;
35
+ // 只取「有没有传」这个布尔量,从无到有时照样会重新注册。
36
+ }, [hasOnSearch]);
33
37
  return {
34
38
  isComposing: isComposing
35
39
  };
@@ -5,6 +5,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
5
5
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
6
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
7
  import { useEffect, useState } from "react";
8
+ import { useLatestRef } from "../../../hooks/useLatestRef";
8
9
  /**
9
10
  * Manage input options (when there are no matching options)
10
11
  */
@@ -18,11 +19,16 @@ export function useSelectInputOptions(_ref) {
18
19
  _useState2 = _slicedToArray(_useState, 2),
19
20
  inputOptions = _useState2[0],
20
21
  setInputOptions = _useState2[1];
22
+ var onSearchRef = useLatestRef(onSearch);
23
+
24
+ // 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
25
+ // 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
21
26
  useEffect(function () {
22
27
  if (!isComposing) {
23
- onSearch === null || onSearch === void 0 || onSearch(searchValue);
28
+ var _onSearchRef$current;
29
+ (_onSearchRef$current = onSearchRef.current) === null || _onSearchRef$current === void 0 || _onSearchRef$current.call(onSearchRef, searchValue);
24
30
  }
25
- }, [inputOptions, isComposing, onChange, onSearch, optionsLength, searchValue]);
31
+ }, [inputOptions, isComposing, onSearchRef, optionsLength, searchValue]);
26
32
  return {
27
33
  inputOptions: inputOptions,
28
34
  setInputOptions: setInputOptions
@@ -14,7 +14,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
14
14
  function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
15
15
  function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
16
16
  import { Slider as AntdSlider } from "antd";
17
- import React, { useEffect, useState } from "react";
17
+ import React, { useEffect, useRef, useState } from "react";
18
18
  import classNames from "classnames";
19
19
  import styles from "./index.module.scss";
20
20
  import { jsx as _jsx } from "react/jsx-runtime";
@@ -29,28 +29,37 @@ var Slider = function Slider(props) {
29
29
  _useState2 = _slicedToArray(_useState, 2),
30
30
  _value = _useState2[0],
31
31
  setValue = _useState2[1];
32
- var _useState3 = useState(value),
33
- _useState4 = _slicedToArray(_useState3, 2),
34
- lastValue = _useState4[0],
35
- setLastValue = _useState4[1];
32
+ // 最近一次通知出去的值,代替原来那个用 state 记账的写法
33
+ var notifiedRef = useRef(value);
34
+
35
+ /**
36
+ * `onChange` 是「用户拖了滑块」这个**事件**的通知,不是从 state 推导出来的结果,
37
+ * 所以在事件处理里发,不在 effect 里发。详见 `Input/TextArea/index.tsx` 里那段
38
+ * 说明——旧写法(依赖数组里放 `onChange`、体内又调它、用 state 记「通知过没有」)
39
+ * 会让传内联箭头的消费方死循环。
40
+ */
41
+ var handleChange = function handleChange(next) {
42
+ setValue(next);
43
+ if (next === notifiedRef.current) return;
44
+ notifiedRef.current = next;
45
+ onChange === null || onChange === void 0 || onChange(next);
46
+ };
47
+
48
+ // 外部值(`value` / `topValue`)变了才回写,不跟着通知走
36
49
  useEffect(function () {
37
- if (_value !== lastValue) {
38
- setLastValue(_value);
39
- onChange === null || onChange === void 0 || onChange(_value);
50
+ var next = topValue || value;
51
+ if (next !== notifiedRef.current) {
52
+ notifiedRef.current = next;
53
+ setValue(next);
40
54
  }
41
- }, [lastValue, onChange, _value]);
42
- useEffect(function () {
43
- if ((topValue || value) !== lastValue) {
44
- setValue(topValue || value);
45
- }
46
- }, [lastValue, topValue, value]);
55
+ }, [topValue, value]);
47
56
  return /*#__PURE__*/_jsx(AntdSlider, _objectSpread({
48
57
  className: classNames([styles.slider, className]),
49
58
  tooltip: {
50
59
  open: false
51
60
  },
52
61
  value: _value,
53
- onChange: setValue
62
+ onChange: handleChange
54
63
  }, sliderConfig));
55
64
  };
56
65
  export default Slider;
@@ -7,6 +7,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
7
7
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
8
8
  import { useMutationObserver } from "ahooks";
9
9
  import { useCallback, useEffect, useRef, useState } from "react";
10
+ import { useLatestRef } from "../../../hooks/useLatestRef";
10
11
  var DEFAULT_INTERVAL = 2000;
11
12
  var DEFAULT_SPEED = 25;
12
13
  var SMOOTH_SCROLL_TICK = 10;
@@ -29,6 +30,13 @@ var useAutoScrolling = function useAutoScrolling(props) {
29
30
  autoScrollLoopMode = _props$autoScrollLoop2 === void 0 ? "reset" : _props$autoScrollLoop2,
30
31
  _props$autoScrollingO = props.autoScrollingOffset,
31
32
  autoScrollingOffset = _props$autoScrollingO === void 0 ? 0 : _props$autoScrollingO;
33
+
34
+ // onAutoScrollEndAdd 在这里身兼两职:既是「有没有加载更多」的开关,又是真正去加载的动作。
35
+ // 整个 rAF 循环(loop → 依赖它 → 启停 effect 依赖 loop)都挂在它的引用上,
36
+ // 消费方传内联箭头就会每渲染一次重启一次滚动循环 —— 表格滚到一半被打回起点。
37
+ // 拆成两半:开关看布尔量(从无到有传入时仍会正确接上加载更多),动作取 ref 里的最新引用。
38
+ var onAutoScrollEndAddRef = useLatestRef(onAutoScrollEndAdd);
39
+ var hasOnAutoScrollEndAdd = !!onAutoScrollEndAdd;
32
40
  var validSpeed = autoScrollingSpeed > 0 ? autoScrollingSpeed : DEFAULT_SPEED;
33
41
  var smoothStep = validSpeed * SMOOTH_SCROLL_TICK / 1000;
34
42
  var _useState = useState(false),
@@ -260,11 +268,12 @@ var useAutoScrolling = function useAutoScrolling(props) {
260
268
  }, SMOOTH_SCROLL_TICK);
261
269
  }, [bindHover, checkSeamlessReset, clearSmoothTimer, getBody, interval, isAtBottom, isBodyScrollable, isSeamless, smoothStep]);
262
270
  var runLoadMore = useCallback(function () {
263
- if (!onAutoScrollEndAdd || pendingLoadMoreRef.current) return;
271
+ var loadMore = onAutoScrollEndAddRef.current;
272
+ if (!loadMore || pendingLoadMoreRef.current) return;
264
273
  pendingLoadMoreRef.current = true;
265
274
  phaseRef.current = "loading";
266
275
  var token = ++loadTokenRef.current;
267
- onAutoScrollEndAdd().then(function (hasMore) {
276
+ loadMore().then(function (hasMore) {
268
277
  if (token !== loadTokenRef.current) return;
269
278
  var currentBody = getBody();
270
279
  if (hasMore) {
@@ -284,7 +293,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
284
293
  if (token !== loadTokenRef.current) return;
285
294
  pendingLoadMoreRef.current = false;
286
295
  });
287
- }, [autoScrollLoop, getBody, onAutoScrollEndAdd]);
296
+ }, [autoScrollLoop, getBody, onAutoScrollEndAddRef]);
288
297
  var loop = useCallback(function () {
289
298
  rafRef.current = requestAnimationFrame(loop);
290
299
  if (!autoScrolling || !ready || !(dataSource !== null && dataSource !== void 0 && dataSource.length) || !(ref !== null && ref !== void 0 && ref.current)) {
@@ -316,7 +325,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
316
325
  // In seamless mode the scroll never actually reaches the bottom; just keep scrolling
317
326
  phaseRef.current = "idle";
318
327
  } else if (isAtBottom(body)) {
319
- if (onAutoScrollEndAdd) {
328
+ if (hasOnAutoScrollEndAdd) {
320
329
  runLoadMore();
321
330
  } else {
322
331
  if (autoScrollLoop) {
@@ -362,7 +371,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
362
371
  return;
363
372
  }
364
373
  startSmoothStep(body, targetTop);
365
- }, [autoScrollMode, autoScrollLoop, autoScrolling, bindHover, checkSeamlessReset, dataSource, getBody, getRowHeight, interval, isAtBottom, isBodyScrollable, isSeamless, onAutoScrollEndAdd, ready, ref, runLoadMore, setupSeamlessClones, startSmoothStep]);
374
+ }, [autoScrollMode, autoScrollLoop, autoScrolling, bindHover, checkSeamlessReset, dataSource, getBody, getRowHeight, interval, isAtBottom, isBodyScrollable, isSeamless, hasOnAutoScrollEndAdd, ready, ref, runLoadMore, setupSeamlessClones, startSmoothStep]);
366
375
  useEffect(function () {
367
376
  if (!autoScrolling || !ready || !(dataSource !== null && dataSource !== void 0 && dataSource.length) || !(ref !== null && ref !== void 0 && ref.current)) {
368
377
  cleanup();
@@ -387,7 +396,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
387
396
  cleanupClones();
388
397
  return;
389
398
  }
390
- if (onAutoScrollEndAdd) {
399
+ if (hasOnAutoScrollEndAdd) {
391
400
  // In load-more mode, if scrolling had stopped, resume auto-scrolling when new data arrives
392
401
  if (phaseRef.current === "stopped") {
393
402
  phaseRef.current = autoScrollMode === "row" ? "wait" : "idle";
@@ -400,7 +409,7 @@ var useAutoScrolling = function useAutoScrolling(props) {
400
409
  phaseRef.current = autoScrollMode === "row" ? "wait" : "idle";
401
410
  waitUntilRef.current = autoScrollMode === "row" ? performance.now() + interval : 0;
402
411
  }
403
- }, [autoScrollMode, cleanupClones, dataSource, getBody, interval, isBodyScrollable, isSeamless, onAutoScrollEndAdd]);
412
+ }, [autoScrollMode, cleanupClones, dataSource, getBody, interval, isBodyScrollable, isSeamless, hasOnAutoScrollEndAdd]);
404
413
  useEffect(function () {
405
414
  if (!autoScrolling || !(ref !== null && ref !== void 0 && ref.current)) return;
406
415
  var resizeRaf = null;
@@ -7,6 +7,7 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
7
  import { useEffect, useState } from "react";
8
8
  import { deepCopy, Equal } from "hsu-utils";
9
9
  import { extractFileUrl } from "../_utils";
10
+ import { useLatestRef } from "../../../hooks/useLatestRef";
10
11
  /**
11
12
  * Manage the upload file list state
12
13
  */
@@ -22,6 +23,10 @@ export function useUploadFileList(_ref) {
22
23
  _useState4 = _slicedToArray(_useState3, 2),
23
24
  lastFileList = _useState4[0],
24
25
  setLastFileList = _useState4[1];
26
+ var onChangeRef = useLatestRef(onChange);
27
+
28
+ // 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
29
+ // 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
25
30
  useEffect(function () {
26
31
  if (rmFile) {
27
32
  var file = _fileList.find(function (item) {
@@ -33,14 +38,15 @@ export function useUploadFileList(_ref) {
33
38
  if (file) {
34
39
  setFilelist(filteredList);
35
40
  setTimeout(function () {
36
- onChange === null || onChange === void 0 || onChange({
41
+ var _onChangeRef$current;
42
+ (_onChangeRef$current = onChangeRef.current) === null || _onChangeRef$current === void 0 || _onChangeRef$current.call(onChangeRef, {
37
43
  file: file,
38
44
  fileList: filteredList
39
45
  });
40
46
  }, 100);
41
47
  }
42
48
  }
43
- }, [_fileList, onChange, rmFile]);
49
+ }, [_fileList, onChangeRef, rmFile]);
44
50
  useEffect(function () {
45
51
  if (!Equal.ObjEqual(fileList !== null && fileList !== void 0 ? fileList : [], lastFileList)) {
46
52
  if (fileList !== null && fileList !== void 0 && fileList.length) {
@@ -6,6 +6,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
6
6
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
7
7
  import { useEffect, useRef, useState } from "react";
8
8
  import { deepCopy, Equal } from "hsu-utils";
9
+ import { useLatestRef } from "../../../hooks/useLatestRef";
9
10
  /**
10
11
  * Manage upload and download operations
11
12
  */
@@ -27,6 +28,7 @@ export function useUploadOperations(_ref) {
27
28
  downloading: {},
28
29
  uploadingList: {}
29
30
  });
31
+ var onUploadingListRef = useLatestRef(onUploadingList);
30
32
 
31
33
  // Sync to the ref on update
32
34
  useEffect(function () {
@@ -35,12 +37,16 @@ export function useUploadOperations(_ref) {
35
37
  useEffect(function () {
36
38
  operationsRef.current.uploadingList = uploadingList;
37
39
  }, [uploadingList]);
40
+
41
+ // 回调 prop 不进依赖数组:消费方传内联箭头时每次渲染都是新引用,effect 会跟着
42
+ // 重跑并再调一次回调 —— 回调里 setState 就是死循环(详见 Input/TextArea 的说明)
38
43
  useEffect(function () {
39
44
  if (!Equal.ObjEqual(uploadingList, lastUploadList)) {
40
- onUploadingList === null || onUploadingList === void 0 || onUploadingList(deepCopy(uploadingList));
45
+ var _onUploadingListRef$c;
46
+ (_onUploadingListRef$c = onUploadingListRef.current) === null || _onUploadingListRef$c === void 0 || _onUploadingListRef$c.call(onUploadingListRef, deepCopy(uploadingList));
41
47
  setLastUploadList(deepCopy(uploadingList));
42
48
  }
43
- }, [uploadingList, lastUploadList, onUploadingList]);
49
+ }, [uploadingList, lastUploadList, onUploadingListRef]);
44
50
 
45
51
  // Cleanup function
46
52
  useEffect(function () {
@@ -0,0 +1,24 @@
1
+ /// <reference types="react" />
2
+ /**
3
+ * 把一个每次渲染都可能换引用的值(通常是回调 prop)装进一个**引用恒定**的 ref。
4
+ *
5
+ * 用途只有一个:让 effect 不必把回调放进依赖数组。
6
+ * 回调放进依赖数组、effect 体内又调它,是一条会让消费方死循环的写法——
7
+ * 消费方传内联箭头(`onChange={(v) => setX(v)}`,React 里最常见的写法)时
8
+ * 每次渲染都是新引用 → effect 重跑 → 调回调 → 父级 setState → 再渲染 →
9
+ * 又是新引用 …… 直到 React 抛 `Maximum update depth exceeded`。
10
+ *
11
+ * ```ts
12
+ * const onDoneRef = useLatestRef(onDone);
13
+ * useEffect(() => {
14
+ * const t = setTimeout(() => onDoneRef.current?.(), 1000);
15
+ * return () => clearTimeout(t);
16
+ * }, [onDoneRef]); // 只在挂载时跑一次,且永远调到最新的 onDone
17
+ * ```
18
+ *
19
+ * 注意:渲染期间就地赋值。React 官方对 ref 的约束是「不要在渲染中**读**可变值」,
20
+ * 写入最新的 props 是 useEffectEvent 落地前的通行做法(ahooks 的 `useLatest`、
21
+ * React 文档里的 `useEvent` polyfill 都是这么写的)。
22
+ */
23
+ export declare function useLatestRef<T>(value: T): import("react").MutableRefObject<T>;
24
+ export default useLatestRef;
@@ -0,0 +1,29 @@
1
+ import { useRef } from "react";
2
+
3
+ /**
4
+ * 把一个每次渲染都可能换引用的值(通常是回调 prop)装进一个**引用恒定**的 ref。
5
+ *
6
+ * 用途只有一个:让 effect 不必把回调放进依赖数组。
7
+ * 回调放进依赖数组、effect 体内又调它,是一条会让消费方死循环的写法——
8
+ * 消费方传内联箭头(`onChange={(v) => setX(v)}`,React 里最常见的写法)时
9
+ * 每次渲染都是新引用 → effect 重跑 → 调回调 → 父级 setState → 再渲染 →
10
+ * 又是新引用 …… 直到 React 抛 `Maximum update depth exceeded`。
11
+ *
12
+ * ```ts
13
+ * const onDoneRef = useLatestRef(onDone);
14
+ * useEffect(() => {
15
+ * const t = setTimeout(() => onDoneRef.current?.(), 1000);
16
+ * return () => clearTimeout(t);
17
+ * }, [onDoneRef]); // 只在挂载时跑一次,且永远调到最新的 onDone
18
+ * ```
19
+ *
20
+ * 注意:渲染期间就地赋值。React 官方对 ref 的约束是「不要在渲染中**读**可变值」,
21
+ * 写入最新的 props 是 useEffectEvent 落地前的通行做法(ahooks 的 `useLatest`、
22
+ * React 文档里的 `useEvent` polyfill 都是这么写的)。
23
+ */
24
+ export function useLatestRef(value) {
25
+ var ref = useRef(value);
26
+ ref.current = value;
27
+ return ref;
28
+ }
29
+ export default useLatestRef;
@@ -1,4 +1,5 @@
1
1
  import { useEffect } from "react";
2
+ import { useLatestRef } from "../../../hooks/useLatestRef";
2
3
  import { useLocation } from "react-router";
3
4
  /**
4
5
  * Handle the top-level-only menu logic
@@ -10,16 +11,22 @@ export var useOnlyLvOneMenu = function useOnlyLvOneMenu(_ref) {
10
11
  setOpenkeys = _ref.setOpenkeys,
11
12
  setMenuKey = _ref.setMenuKey;
12
13
  var location = useLocation();
14
+ // getCurrChildItems 是 effect 体里发出去的通知,本身进依赖数组是这一类死循环的标准配方:
15
+ // 消费方传内联箭头 → 每次渲染新引用 → effect 重跑 → setOpenkeys([item.key]) 每次都是
16
+ // 新数组必定触发重渲染 → 又是新引用 …… 直到 React 抛 Maximum update depth exceeded。
17
+ // 通知取 ref 里的最新引用,依赖数组只留真正的输入。
18
+ var getCurrChildItemsRef = useLatestRef(getCurrChildItems);
13
19
  useEffect(function () {
14
20
  if (onlyLvOneMenu) {
15
21
  var item = items.find(function (i) {
16
22
  return i.key === "/" + location.pathname.split("/").filter(Boolean)[0];
17
23
  });
18
24
  if (item) {
19
- getCurrChildItems === null || getCurrChildItems === void 0 || getCurrChildItems(item.children || []);
25
+ var _getCurrChildItemsRef;
26
+ (_getCurrChildItemsRef = getCurrChildItemsRef.current) === null || _getCurrChildItemsRef === void 0 || _getCurrChildItemsRef.call(getCurrChildItemsRef, item.children || []);
20
27
  setOpenkeys([item.key]);
21
28
  setMenuKey(item.key);
22
29
  }
23
30
  }
24
- }, [items, getCurrChildItems, location, onlyLvOneMenu, setOpenkeys, setMenuKey]);
31
+ }, [items, getCurrChildItemsRef, location, onlyLvOneMenu, setOpenkeys, setMenuKey]);
25
32
  };