@hsu-react/ui 2.5.6 → 2.5.8

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 (61) hide show
  1. package/es/components/Checkbox/CheckboxGroup/index.js +4 -1
  2. package/es/components/FormItem/FormCodeMirror/index.js +3 -1
  3. package/es/components/FormItem/ItemContainer/index.js +15 -5
  4. package/es/components/Modal/index.d.ts +12 -0
  5. package/es/components/Modal/index.js +22 -1
  6. package/es/components/Modal/index.module.scss +7 -0
  7. package/es/components/Operate/index.js +4 -1
  8. package/es/components/Pagination/index.js +3 -1
  9. package/es/components/Search/_hooks/useSearchCommon.js +4 -1
  10. package/es/components/Select/AutoCompleteSelect/index.js +3 -1
  11. package/es/components/Spreadsheet/index.js +8 -2
  12. package/es/components/Table/_hooks/useTableColumns.js +4 -1
  13. package/es/components/Tree/_hooks/useTreeState.js +7 -4
  14. package/es/components/Tree/index.js +8 -2
  15. package/es/layout/Menu/index.module.scss +32 -0
  16. package/es/styles/tokens.d.ts +10 -0
  17. package/es/styles/tokens.js +11 -0
  18. package/es/styles/tokens.json +1 -0
  19. package/lib/components/Checkbox/CheckboxGroup/index.js +4 -1
  20. package/lib/components/FormItem/FormCodeMirror/index.js +3 -1
  21. package/lib/components/FormItem/ItemContainer/index.js +16 -6
  22. package/lib/components/Modal/index.d.ts +12 -0
  23. package/lib/components/Modal/index.js +22 -0
  24. package/lib/components/Modal/index.module.scss +7 -0
  25. package/lib/components/Operate/index.js +4 -1
  26. package/lib/components/Pagination/index.js +3 -1
  27. package/lib/components/Search/_hooks/useSearchCommon.js +4 -1
  28. package/lib/components/Select/AutoCompleteSelect/index.js +3 -1
  29. package/lib/components/Spreadsheet/index.js +8 -2
  30. package/lib/components/Table/_hooks/useTableColumns.js +4 -1
  31. package/lib/components/Tree/_hooks/useTreeState.js +6 -3
  32. package/lib/components/Tree/index.js +8 -2
  33. package/lib/layout/Menu/index.module.scss +32 -0
  34. package/lib/styles/tokens.d.ts +10 -0
  35. package/lib/styles/tokens.js +12 -1
  36. package/lib/styles/tokens.json +1 -0
  37. package/package.json +1 -1
  38. package/src/__tests__/depsScan.ts +62 -15
  39. package/src/__tests__/freshObjectDeps.test.tsx +380 -0
  40. package/src/__tests__/freshObjectDepsGuard.test.ts +54 -23
  41. package/src/components/Checkbox/CheckboxGroup/index.tsx +4 -1
  42. package/src/components/FormItem/FormCodeMirror/index.tsx +4 -1
  43. package/src/components/FormItem/ItemContainer/index.tsx +21 -6
  44. package/src/components/Modal/index.md +1 -0
  45. package/src/components/Modal/index.module.scss +7 -0
  46. package/src/components/Modal/index.tsx +40 -0
  47. package/src/components/Modal/minHeight.test.tsx +105 -0
  48. package/src/components/Operate/index.tsx +4 -1
  49. package/src/components/Pagination/index.tsx +4 -1
  50. package/src/components/Search/_hooks/useSearchCommon.ts +4 -1
  51. package/src/components/Select/AutoCompleteSelect/index.tsx +4 -1
  52. package/src/components/Spreadsheet/depsMemo.test.tsx +95 -0
  53. package/src/components/Spreadsheet/index.tsx +9 -2
  54. package/src/components/Table/_hooks/useTableColumns.tsx +4 -1
  55. package/src/components/Tree/_hooks/useTreeState.ts +14 -8
  56. package/src/components/Tree/depsMemo.test.tsx +64 -0
  57. package/src/components/Tree/index.tsx +9 -2
  58. package/src/layout/Menu/index.module.scss +32 -0
  59. package/src/layout/Menu/scrollGutter.test.ts +50 -0
  60. package/src/styles/tokens.json +1 -0
  61. package/src/styles/tokens.ts +11 -0
@@ -14,12 +14,15 @@ import styles from "./index.module.scss";
14
14
  import { jsx as _jsx } from "react/jsx-runtime";
15
15
  import { jsxs as _jsxs } from "react/jsx-runtime";
16
16
  var Group = Checkbox.Group;
17
+
18
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
19
+ var EMPTY_OPTIONS = [];
17
20
  var CheckboxGroup = function CheckboxGroup(props) {
18
21
  var className = props.className,
19
22
  outline = props.outline,
20
23
  hasAll = props.hasAll,
21
24
  _props$options = props.options,
22
- options = _props$options === void 0 ? [] : _props$options,
25
+ options = _props$options === void 0 ? EMPTY_OPTIONS : _props$options,
23
26
  value = props.value,
24
27
  onChange = props.onChange,
25
28
  _props$layout = props.layout,
@@ -22,6 +22,8 @@ import { Form } from "antd";
22
22
  import CodeMirror from "../../CodeMirror";
23
23
  import ItemContainer from "../ItemContainer";
24
24
  import { jsx as _jsx } from "react/jsx-runtime";
25
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
26
+ var EMPTY_RULES = [];
25
27
  var FormCodeMirror = function FormCodeMirror(props) {
26
28
  var _componentProps$readO;
27
29
  var _props$componentProps = props.componentProps,
@@ -29,7 +31,7 @@ var FormCodeMirror = function FormCodeMirror(props) {
29
31
  itemClassName = props.className,
30
32
  disabled = props.disabled,
31
33
  _props$rules = props.rules,
32
- rules = _props$rules === void 0 ? [] : _props$rules,
34
+ rules = _props$rules === void 0 ? EMPTY_RULES : _props$rules,
33
35
  name = props.name,
34
36
  formItemProps = _objectWithoutProperties(props, _excluded);
35
37
  var enableLint = componentProps.enableLint !== false;
@@ -24,10 +24,13 @@ import usePermissions from "../../../hooks/usePermissions";
24
24
  import { isLegacyHasSelectorBrowser } from "../../../utils/cssSupports";
25
25
  import { generateRandomStr } from "hsu-utils";
26
26
  import useLabelSize from "./_hooks/useLabelSize";
27
+ import useShallowStable from "../../../hooks/useShallowStable";
27
28
  import useInputSize from "./_hooks/useInputSize";
28
29
  import { jsx as _jsx } from "react/jsx-runtime";
29
30
  import { Fragment as _Fragment } from "react/jsx-runtime";
30
31
  import { jsxs as _jsxs } from "react/jsx-runtime";
32
+ // 解构默认值写成字面量会每次渲染新建一个对象,进依赖数组就让 memo 恒不命中;提到模块级常量
33
+ var EMPTY_TIPS = {};
31
34
  var ItemContainer = function ItemContainer(props) {
32
35
  var children = props.children,
33
36
  className = props.className,
@@ -55,7 +58,7 @@ var ItemContainer = function ItemContainer(props) {
55
58
  label = props.label,
56
59
  labelRender = props.labelRender,
57
60
  _props$tips = props.tips,
58
- tips = _props$tips === void 0 ? {} : _props$tips,
61
+ tips = _props$tips === void 0 ? EMPTY_TIPS : _props$tips,
59
62
  _props$horizontalAlig = props.horizontalAlignment,
60
63
  horizontalAlignment = _props$horizontalAlig === void 0 ? "start" : _props$horizontalAlig,
61
64
  _props$hideRequired = props.hideRequired,
@@ -80,7 +83,14 @@ var ItemContainer = function ItemContainer(props) {
80
83
  var _tips$icon = tips.icon,
81
84
  icon = _tips$icon === void 0 ? "material-symbols:help" : _tips$icon,
82
85
  iconClassName = tips.iconClassName,
83
- tipsConfig = _objectWithoutProperties(tips, _excluded2);
86
+ restTipsConfig = _objectWithoutProperties(tips, _excluded2);
87
+ // rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 memo 恒不命中。
88
+ // useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
89
+ var tipsConfig = useShallowStable(restTipsConfig);
90
+ // memo 只需要知道「有没有配 tips」,把对象本身放进依赖数组等于按引用比:
91
+ // 消费方写内联 `tips={{ ... }}`(最常见的写法)就让 label 的 memo 恒不命中。
92
+ // 拆成布尔量后依赖回到值语义。
93
+ var hasTips = Object.keys(tips).length > 0;
84
94
  var _usePermissions = usePermissions(hasPermi),
85
95
  permitted = _usePermissions.permitted;
86
96
  var cls = useMemo(function () {
@@ -91,7 +101,7 @@ var ItemContainer = function ItemContainer(props) {
91
101
  if (hideLabel) {
92
102
  return undefined;
93
103
  }
94
- return Object.keys(tips).length ? /*#__PURE__*/_jsxs(_Fragment, {
104
+ return hasTips ? /*#__PURE__*/_jsxs(_Fragment, {
95
105
  children: [/*#__PURE__*/_jsx("span", {
96
106
  className: classNames(styles.labelContent, labelClassName),
97
107
  children: /*#__PURE__*/_jsx("span", {
@@ -137,7 +147,7 @@ var ItemContainer = function ItemContainer(props) {
137
147
  children: labelExtra
138
148
  })]
139
149
  }) : undefined;
140
- }, [tips, labelClassName, label, tipsConfig, icon, iconClassName, labelExtra, labelExtraClassName, labelEllipsis, labelBreak, hideLabel]);
150
+ }, [hasTips, labelClassName, label, tipsConfig, icon, iconClassName, labelExtra, labelExtraClassName, labelEllipsis, labelBreak, hideLabel]);
141
151
  var _useLabelSize = useLabelSize({
142
152
  labelWidth: labelWidth,
143
153
  labelHeight: labelHeight,
@@ -167,7 +177,7 @@ var ItemContainer = function ItemContainer(props) {
167
177
  required: required,
168
178
  message: requiredMsg !== null && requiredMsg !== void 0 ? requiredMsg : en ? "".concat(PlaceholderDictEn[type || "INPUT"]) : "".concat(PlaceholderDict[type || "INPUT"])
169
179
  }].concat(_toConsumableArray(rules)),
170
- className: classNames(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, cls, true), styles.formItem, true), className !== null && className !== void 0 ? className : "", true), styles[layout], layout), styles[requiredPosition], requiredPosition), styles.hideRequired, hideRequired), styles[horizontalAlignment], layout === "horizontal" && horizontalAlignment), styles.hasLabelContent, !!Object.keys(tips).length), styles.hideAdditiona, hideAdditiona), styles.legacyHasLabelExtra, legacyHasSelector && layout === "vertical" && !hideLabel && !!label && !!labelExtra && !Object.keys(tips).length)),
180
+ className: classNames(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, cls, true), styles.formItem, true), className !== null && className !== void 0 ? className : "", true), styles[layout], layout), styles[requiredPosition], requiredPosition), styles.hideRequired, hideRequired), styles[horizontalAlignment], layout === "horizontal" && horizontalAlignment), styles.hasLabelContent, hasTips), styles.hideAdditiona, hideAdditiona), styles.legacyHasLabelExtra, legacyHasSelector && layout === "vertical" && !hideLabel && !!label && !!labelExtra && !hasTips)),
171
181
  colon: typeof colon === "boolean" ? colon : layout === "horizontal" && requiredPosition === "before",
172
182
  label: labelRender ? labelRender(_label) : _label,
173
183
  style: _objectSpread({
@@ -6,6 +6,18 @@ export interface ModalProps extends AntdModalProps {
6
6
  edgeDetection?: boolean;
7
7
  full?: boolean;
8
8
  titleButtonGroup?: ButtonProps[];
9
+ /**
10
+ * 高度**下界**。给「内容要等接口回来才画得出」的弹窗用:不给下界的话,
11
+ * 打开那一刻只有标题栏那么高,数据回来再撑开 —— 视觉上跳一下。
12
+ *
13
+ * - `true`:用标准档(`modalMinHeight`,700px),后台详情 / 列表 / 记录类弹窗都用它
14
+ * - 数字:px;字符串:原样当 CSS 长度
15
+ * - 不传:维持内容自适应 —— **短表单不要传**,否则平白留出一大片空白
16
+ *
17
+ * 它只是下界:内容更高照常撑开、照常滚动;并且在样式里与 `90vh` 取小,
18
+ * 矮窗口下不会被顶出屏幕。
19
+ */
20
+ minHeight?: number | string | boolean;
9
21
  }
10
22
  interface ModalFC extends React.FC<ModalProps> {
11
23
  confirm: typeof AntdModal.confirm;
@@ -1,5 +1,5 @@
1
1
  function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
2
- var _excluded = ["moveable", "className", "classNames", "open", "onCancel", "onOk", "afterClose", "edgeDetection", "full", "destroyOnClose", "destroyOnHidden", "footer", "title", "titleButtonGroup"];
2
+ var _excluded = ["moveable", "className", "classNames", "styles", "minHeight", "open", "onCancel", "onOk", "afterClose", "edgeDetection", "full", "destroyOnClose", "destroyOnHidden", "footer", "title", "titleButtonGroup"];
3
3
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
4
4
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
5
5
  function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@@ -14,14 +14,23 @@ import { useModalElements, useModalDrag } from "./_hooks";
14
14
  import Button from "../Button";
15
15
  import { mergeSemantic } from "../../utils/semantic";
16
16
  import { modalFuncs } from "../../feedback";
17
+ import { modalMinHeight } from "../../styles/tokens";
17
18
  import { jsx as _jsx } from "react/jsx-runtime";
18
19
  import { Fragment as _Fragment } from "react/jsx-runtime";
19
20
  import { jsxs as _jsxs } from "react/jsx-runtime";
21
+ /** `minHeight` prop → CSS 长度。`true` 走标准档,`false` / 不传等于不设下界 */
22
+ var resolveMinHeight = function resolveMinHeight(minHeight) {
23
+ if (minHeight === undefined || minHeight === false) return undefined;
24
+ if (minHeight === true) return "".concat(modalMinHeight, "px");
25
+ return typeof minHeight === "number" ? "".concat(minHeight, "px") : minHeight;
26
+ };
20
27
  var Modal = function Modal(props) {
21
28
  var _props$moveable = props.moveable,
22
29
  moveable = _props$moveable === void 0 ? true : _props$moveable,
23
30
  className = props.className,
24
31
  classNames = props.classNames,
32
+ outerStyles = props.styles,
33
+ minHeight = props.minHeight,
25
34
  open = props.open,
26
35
  onCancel = props.onCancel,
27
36
  onOk = props.onOk,
@@ -65,6 +74,7 @@ var Modal = function Modal(props) {
65
74
  var handleOk = function handleOk(e) {
66
75
  onOk === null || onOk === void 0 || onOk(e);
67
76
  };
77
+ var resolvedMinHeight = resolveMinHeight(minHeight);
68
78
  return /*#__PURE__*/_jsx(AntdModal, _objectSpread(_objectSpread({
69
79
  centered: true
70
80
  }, moadlConfig), {}, {
@@ -90,6 +100,17 @@ var Modal = function Modal(props) {
90
100
  body: "".concat(styles.body, " ").concat((_outer$body = outer.body) !== null && _outer$body !== void 0 ? _outer$body : ""),
91
101
  footer: "".concat(styles.footer, " ").concat((_outer$footer = outer.footer) !== null && _outer$footer !== void 0 ? _outer$footer : "", " ").concat(footer === false ? styles.noFooter : "")
92
102
  });
103
+ })
104
+ /* 下界通过 CSS 变量交给样式表,而不是直接写 `minHeight` 内联样式:
105
+ clamp(与 90vh 取小)必须在 CSS 里做——`min-height` 在 CSS 里赢过
106
+ `max-height`,内联写死的话矮窗口会被顶出屏幕 */,
107
+ styles: mergeSemantic(outerStyles, function (outer) {
108
+ var _outer$container2;
109
+ return _objectSpread(_objectSpread({}, outer), {}, {
110
+ container: _objectSpread(_objectSpread({}, (_outer$container2 = outer.container) !== null && _outer$container2 !== void 0 ? _outer$container2 : {}), resolvedMinHeight ? {
111
+ "--vita-modal-min-height": resolvedMinHeight
112
+ } : {})
113
+ });
93
114
  }),
94
115
  footer: footer,
95
116
  afterClose: function afterClose() {
@@ -31,6 +31,13 @@
31
31
  width: 100%;
32
32
  height: 100%;
33
33
  max-height: 90vh;
34
+ // 高度下界,由 `<Modal minHeight>` 写入 `--vita-modal-min-height`(不传就是 0)。
35
+ // 内容异步加载的弹窗不给下界的话,打开那一刻只有标题栏那么高,数据回来再撑开。
36
+ //
37
+ // 必须在这里与 90vh 取小:CSS 里 `min-height` 的优先级高于 `max-height`,
38
+ // 直接写 `min-height: 700px` 会让上面那行 `max-height: 90vh` 在矮窗口上失效,
39
+ // 弹窗被顶出屏幕。
40
+ min-height: min(var(--vita-modal-min-height, 0px), 90vh);
34
41
  padding: 0px 0px 15px;
35
42
  }
36
43
 
@@ -25,6 +25,9 @@ import classNames from "classnames";
25
25
  import moreImg from "./more.png";
26
26
  import { jsx as _jsx } from "react/jsx-runtime";
27
27
  import { jsxs as _jsxs } from "react/jsx-runtime";
28
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
29
+ var EMPTY_MENU = [];
30
+
28
31
  /**
29
32
  * Render an icon
30
33
  * @param icon - Icon configuration
@@ -51,7 +54,7 @@ var Operate = function Operate(props) {
51
54
  var title = props.title,
52
55
  className = props.className,
53
56
  _props$menu = props.menu,
54
- menu = _props$menu === void 0 ? [] : _props$menu,
57
+ menu = _props$menu === void 0 ? EMPTY_MENU : _props$menu,
55
58
  icon = props.icon,
56
59
  isDelete = props.delete,
57
60
  popconfirm = props.popconfirm,
@@ -18,11 +18,13 @@ import styles from "./index.module.scss";
18
18
  import usePaginationSync from "./_hooks/usePaginationSync";
19
19
  import { jsx as _jsx } from "react/jsx-runtime";
20
20
  import { jsxs as _jsxs } from "react/jsx-runtime";
21
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
22
+ var EMPTY_PAGE_SIZE_OPTIONS = [];
21
23
  var Pagination = function Pagination(props) {
22
24
  var _props$current, _props$pageSize2;
23
25
  var simple = props.simple,
24
26
  _props$pageSizeOption = props.pageSizeOptions,
25
- pageSizeOptions = _props$pageSizeOption === void 0 ? [] : _props$pageSizeOption,
27
+ pageSizeOptions = _props$pageSizeOption === void 0 ? EMPTY_PAGE_SIZE_OPTIONS : _props$pageSizeOption,
26
28
  showSizeChanger = props.showSizeChanger,
27
29
  _onShowSizeChange = props.onShowSizeChange,
28
30
  _onChange = props.onChange,
@@ -18,6 +18,9 @@ import { useAdaptiveColumnNum } from "./useAdaptiveColumnNum";
18
18
  import { useSearchExpand } from "./useSearchExpand";
19
19
  import { cleanSearchData, calculateVisibleItems, calculateButtonGroupAndColumnWidth, shouldDisplayExtraItem } from "../_utils";
20
20
  import styles from "../index.module.scss";
21
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
22
+ var EMPTY_SEARCH_ITEMS = [];
23
+
21
24
  /**
22
25
  * Extracts the common logic shared by all Search components
23
26
  */
@@ -25,7 +28,7 @@ export var useSearchCommon = function useSearchCommon(params) {
25
28
  var form = params.form,
26
29
  searchItems = params.searchItems,
27
30
  _params$moreSearchIte = params.moreSearchItems,
28
- moreSearchItems = _params$moreSearchIte === void 0 ? [] : _params$moreSearchIte,
31
+ moreSearchItems = _params$moreSearchIte === void 0 ? EMPTY_SEARCH_ITEMS : _params$moreSearchIte,
29
32
  searchData = params.searchData,
30
33
  hasPermi = params.hasPermi,
31
34
  beforeButtonGroup = params.beforeButtonGroup,
@@ -27,6 +27,8 @@ import Icon from "../../Icon";
27
27
  import { isLegacyHasSelectorBrowser } from "../../../utils/cssSupports";
28
28
  import { jsx as _jsx } from "react/jsx-runtime";
29
29
  import { jsxs as _jsxs } from "react/jsx-runtime";
30
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
31
+ var EMPTY_OPTIONS = [];
30
32
  var AutoCompleteSelect = function AutoCompleteSelect(props) {
31
33
  var prefix = props.prefix,
32
34
  suffix = props.suffix,
@@ -40,7 +42,7 @@ var AutoCompleteSelect = function AutoCompleteSelect(props) {
40
42
  customFilterOption = props.filterOption,
41
43
  onChange = props.onChange,
42
44
  _props$options = props.options,
43
- options = _props$options === void 0 ? [] : _props$options,
45
+ options = _props$options === void 0 ? EMPTY_OPTIONS : _props$options,
44
46
  _props$placement = props.placement,
45
47
  placement = _props$placement === void 0 ? "bottomLeft" : _props$placement,
46
48
  onDropdownVisibleChange = props.onDropdownVisibleChange,
@@ -20,15 +20,21 @@ import { Equal, generateRandomStr } from "hsu-utils";
20
20
  import "x-data-spreadsheet/dist/xspreadsheet.css";
21
21
  import classNames from "classnames";
22
22
  import { stox } from "./xlsxspread";
23
+ import useShallowStable from "../../hooks/useShallowStable";
23
24
  import { jsx as _jsx } from "react/jsx-runtime";
25
+ // 解构默认值写成字面量会每次渲染新建一个对象,进依赖数组就让 effect 每渲染必重跑;提到模块级常量
26
+ var EMPTY_X_OPTIONS = {};
24
27
  var Spreadsheet = function Spreadsheet(props) {
25
28
  var data = props.data,
26
29
  _props$xOptions = props.xOptions,
27
- xOptions = _props$xOptions === void 0 ? {} : _props$xOptions,
30
+ xOptions = _props$xOptions === void 0 ? EMPTY_X_OPTIONS : _props$xOptions,
28
31
  className = props.className;
29
32
  var _xOptions$showBottomT = xOptions.showBottomTool,
30
33
  showBottomTool = _xOptions$showBottomT === void 0 ? true : _xOptions$showBottomT,
31
- xOptionsRest = _objectWithoutProperties(xOptions, _excluded);
34
+ restXOptions = _objectWithoutProperties(xOptions, _excluded);
35
+ // rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 memo 恒不命中。
36
+ // useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
37
+ var xOptionsRest = useShallowStable(restXOptions);
32
38
  var ref = useRef(null);
33
39
  var containerRef = useRef(null);
34
40
  var id = useMemo(function () {
@@ -22,6 +22,9 @@ import usePermissions from "../../../hooks/usePermissions";
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";
25
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
26
+ var EMPTY_COLUMNS = [];
27
+
25
28
  // measureAlign columns: measured at 14px, consistent with the table body and autoWidth title measurement
26
29
  var MEASURE_ALIGN_FONT_SIZE = 14;
27
30
  // Canvas measureText only returns the advance width, about 1px narrower than the actual DOM rendering; add a 2px buffer to avoid squeezing the widest row
@@ -66,7 +69,7 @@ var getMeasureAlignWidth = function getMeasureAlignWidth(column, dataSource) {
66
69
  };
67
70
  var useTableColumns = function useTableColumns(params) {
68
71
  var _params$columns = params.columns,
69
- columns = _params$columns === void 0 ? [] : _params$columns,
72
+ columns = _params$columns === void 0 ? EMPTY_COLUMNS : _params$columns,
70
73
  scroll = params.scroll,
71
74
  autoWidth = params.autoWidth,
72
75
  dataSource = params.dataSource,
@@ -4,7 +4,7 @@ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o =
4
4
  function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
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
- import { useEffect, useState, useMemo, useRef } from "react";
7
+ import { useEffect, useState, useMemo, useRef, useCallback } from "react";
8
8
  import { Equal } from "hsu-utils";
9
9
  import { normalizeCheckedKeys } from "../_utils/normalizeCheckedKeys";
10
10
 
@@ -38,11 +38,14 @@ export var useExpandedKeys = function useExpandedKeys(expandedKeysProps, default
38
38
  }
39
39
  }, [defaultExpandedKeys, expandedKeysProps]);
40
40
 
41
- // Wrap setExpandedKeys to mark that the user has manually interacted
42
- var handleSetExpandedKeys = function handleSetExpandedKeys(keys) {
41
+ // Wrap setExpandedKeys to mark that the user has manually interacted.
42
+ // 必须 useCallback:这个函数被 Tree handleExpand 当依赖用,
43
+ // 每渲染新建一个就让那个 useCallback 恒不命中——展开回调每渲染换引用透给 antd Tree。
44
+ // 函数体只碰 ref 和状态 setter,两者都恒定,所以依赖数组是空的。
45
+ var handleSetExpandedKeys = useCallback(function (keys) {
43
46
  hasUserInteractedRef.current = true;
44
47
  setExpandedKeys(keys);
45
- };
48
+ }, []);
46
49
  return [expandedKeys, handleSetExpandedKeys];
47
50
  };
48
51
 
@@ -26,9 +26,12 @@ import { useClearNodeTitle } from "./_hooks/useClearNodeTitle";
26
26
  import { getExpandedKeysByLevel, getNodePath } from "./_utils";
27
27
  import TextEllipsis from "../TextEllipsis";
28
28
  import usePermissions from "../../hooks/usePermissions";
29
+ import useShallowStable from "../../hooks/useShallowStable";
29
30
  import { isLegacyHasSelectorBrowser } from "../../utils/cssSupports";
30
31
  import { jsx as _jsx } from "react/jsx-runtime";
31
32
  import { jsxs as _jsxs } from "react/jsx-runtime";
33
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
34
+ var EMPTY_TREE_DATA = [];
32
35
  var Tree = function Tree(props) {
33
36
  var title = props.title,
34
37
  _titleRender = props.titleRender,
@@ -38,7 +41,7 @@ var Tree = function Tree(props) {
38
41
  className = props.className,
39
42
  search = props.search,
40
43
  _props$treeData = props.treeData,
41
- treeData = _props$treeData === void 0 ? [] : _props$treeData,
44
+ treeData = _props$treeData === void 0 ? EMPTY_TREE_DATA : _props$treeData,
42
45
  onChange = props.onChange,
43
46
  onCheck = props.onCheck,
44
47
  onSelect = props.onSelect,
@@ -61,7 +64,10 @@ var Tree = function Tree(props) {
61
64
  _props$allowDeselect = props.allowDeselect,
62
65
  allowDeselect = _props$allowDeselect === void 0 ? true : _props$allowDeselect,
63
66
  titleSearchBarClassName = props.titleSearchBarClassName,
64
- treeConfig = _objectWithoutProperties(props, _excluded);
67
+ restTreeConfig = _objectWithoutProperties(props, _excluded);
68
+ // rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 memo 恒不命中。
69
+ // useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
70
+ var treeConfig = useShallowStable(restTreeConfig);
65
71
  var _usePermissions = usePermissions(hasPermi),
66
72
  permitted = _usePermissions.permitted;
67
73
  var legacyHasSelector = isLegacyHasSelectorBrowser();
@@ -28,6 +28,38 @@
28
28
  overflow-y: auto;
29
29
  }
30
30
 
31
+ /* ===== 滚动条占位不许吃掉菜单项的内缩 =====
32
+ 上面那条规则把「谁滚」定在了菜单根上,但没有把几何契约收完:滚动条一旦
33
+ **实占宽度**(消费方常见做法:槽宽恒定、只切 thumb 颜色,这样悬浮时不抖),
34
+ 它就从菜单根的内容盒里切走一条 —— 而菜单项的左右外边距量的正是这个内容盒。
35
+ 于是展开子菜单、菜单一超高,右边多出一条空白,左边纹丝不动:
36
+
37
+ 展开前 菜单项 8..220(左留白 8 / 右留白 10)
38
+ 展开后 菜单项 8..206(左留白 8 / 右留白 24)← 展开的那一项看着「右边距变大了」
39
+
40
+ `scrollbar-gutter: stable both-edges` 把这条槽**两侧对称保留**,且不论当下滚不滚
41
+ 都保留:左右留白从此相等,展开 / 收起也不再有宽度变化(连带把「展开那一瞬内容
42
+ 重排」一并去掉)。槽宽由浏览器按该元素实际的滚动条算,**不是写死的 14px** ——
43
+ 消费方把滚动条改窄改宽,留白跟着变。
44
+
45
+ 配套把菜单项自己的左右外边距清零:留白改由这条槽提供,不再叠加。不清的话
46
+ 留白变成 `8 + 槽宽`(实测 22/24),230px 的侧栏里「MCP 连接器」直接被截断。
47
+ 纵向节奏与嵌套缩进不受影响 —— antd 的行内缩进走的是 padding,不是 margin。
48
+
49
+ `@supports` 兜底:不认这个属性的浏览器维持原样,不会两头落空。 */
50
+ @supports (scrollbar-gutter: stable both-edges) {
51
+ &:global(.ant-menu-inline) {
52
+ scrollbar-gutter: stable both-edges;
53
+
54
+ :global(.ant-menu-item),
55
+ :global(.ant-menu-submenu-title) {
56
+ width: 100%;
57
+
58
+ margin-inline: 0px;
59
+ }
60
+ }
61
+ }
62
+
31
63
  /* 子菜单在展开 / 收起动画期间被 rc-motion 逐帧改高度。antd 的
32
64
  `.ant-motion-collapse` 自带 overflow hidden,这里补的是**动画结束、类名摘掉
33
65
  之后**那一帧:此时 ul 的高度刚回到 auto,若父级恰好差几像素,浏览器会先画出
@@ -93,6 +93,16 @@ export declare const modalWidth: {
93
93
  md: number;
94
94
  lg: number;
95
95
  };
96
+ /**
97
+ * 内容异步加载的弹窗的**高度下界**(`<Modal minHeight>` 的默认档)。
98
+ *
99
+ * 取 700 不是拍的:`Panel.List.Modal` 从一开始就把自己钉在 `height: 700px`,那是
100
+ * 「搜索行 + 工具条 + 表头 + 十行左右 + 分页」放得下的高度。详情 / 消息 / 记录这类
101
+ * 弹窗与它同处一套后台,用同一个下界,纵向节奏才是一套的。
102
+ *
103
+ * 它只是**下界**:内容更高照常撑开,并且在 CSS 里与 `90vh` 取小,矮窗口不会被顶出屏幕。
104
+ */
105
+ export declare const modalMinHeight: number;
96
106
  /**
97
107
  * 断点。与 `styles/_responsive.scss` 的 mixin 同源,所以 JS 判断不会和 CSS 媒体查询错开。
98
108
  */
@@ -63,6 +63,17 @@ export var controlTokens = raw.control;
63
63
  */
64
64
  export var modalWidth = raw.modalWidth;
65
65
 
66
+ /**
67
+ * 内容异步加载的弹窗的**高度下界**(`<Modal minHeight>` 的默认档)。
68
+ *
69
+ * 取 700 不是拍的:`Panel.List.Modal` 从一开始就把自己钉在 `height: 700px`,那是
70
+ * 「搜索行 + 工具条 + 表头 + 十行左右 + 分页」放得下的高度。详情 / 消息 / 记录这类
71
+ * 弹窗与它同处一套后台,用同一个下界,纵向节奏才是一套的。
72
+ *
73
+ * 它只是**下界**:内容更高照常撑开,并且在 CSS 里与 `90vh` 取小,矮窗口不会被顶出屏幕。
74
+ */
75
+ export var modalMinHeight = raw.modalMinHeight;
76
+
66
77
  /**
67
78
  * 断点。与 `styles/_responsive.scss` 的 mixin 同源,所以 JS 判断不会和 CSS 媒体查询错开。
68
79
  */
@@ -72,6 +72,7 @@
72
72
  "md": 800,
73
73
  "lg": 1200
74
74
  },
75
+ "modalMinHeight": 700,
75
76
  "breakpoint": {
76
77
  "sm": 640,
77
78
  "md": 768,
@@ -13,12 +13,15 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
13
13
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
14
14
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
15
15
  const Group = _antd.Checkbox.Group;
16
+
17
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
18
+ const EMPTY_OPTIONS = [];
16
19
  const CheckboxGroup = props => {
17
20
  const {
18
21
  className,
19
22
  outline,
20
23
  hasAll,
21
- options = [],
24
+ options = EMPTY_OPTIONS,
22
25
  value,
23
26
  onChange,
24
27
  layout = "horizontal",
@@ -10,12 +10,14 @@ var _CodeMirror = _interopRequireDefault(require("../../CodeMirror"));
10
10
  var _ItemContainer = _interopRequireDefault(require("../ItemContainer"));
11
11
  var _jsxRuntime = require("react/jsx-runtime");
12
12
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
+ // 解构默认值写成字面量会每次渲染新建一个数组,进依赖数组就让 memo 恒不命中;提到模块级常量
14
+ const EMPTY_RULES = [];
13
15
  const FormCodeMirror = props => {
14
16
  const {
15
17
  componentProps = {},
16
18
  className: itemClassName,
17
19
  disabled,
18
- rules = [],
20
+ rules = EMPTY_RULES,
19
21
  name,
20
22
  ...formItemProps
21
23
  } = props;
@@ -14,11 +14,14 @@ var _usePermissions = _interopRequireDefault(require("../../../hooks/usePermissi
14
14
  var _cssSupports = require("../../../utils/cssSupports");
15
15
  var _hsuUtils = require("hsu-utils");
16
16
  var _useLabelSize = _interopRequireDefault(require("./_hooks/useLabelSize"));
17
+ var _useShallowStable = _interopRequireDefault(require("../../../hooks/useShallowStable"));
17
18
  var _useInputSize = _interopRequireDefault(require("./_hooks/useInputSize"));
18
19
  var _jsxRuntime = require("react/jsx-runtime");
19
20
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20
21
  function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
21
22
  function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
23
+ // 解构默认值写成字面量会每次渲染新建一个对象,进依赖数组就让 memo 恒不命中;提到模块级常量
24
+ const EMPTY_TIPS = {};
22
25
  const ItemContainer = props => {
23
26
  const {
24
27
  children,
@@ -42,7 +45,7 @@ const ItemContainer = props => {
42
45
  colon,
43
46
  label,
44
47
  labelRender,
45
- tips = {},
48
+ tips = EMPTY_TIPS,
46
49
  horizontalAlignment = "start",
47
50
  hideRequired = false,
48
51
  labelClassName,
@@ -61,8 +64,15 @@ const ItemContainer = props => {
61
64
  const {
62
65
  icon = "material-symbols:help",
63
66
  iconClassName,
64
- ...tipsConfig
67
+ ...restTipsConfig
65
68
  } = tips;
69
+ // rest 解构出来的对象每次渲染都是新引用,直接进依赖数组会让 memo 恒不命中。
70
+ // useShallowStable 让它回到值语义:内容浅相等就复用同一引用,真变了立刻透出新引用。
71
+ const tipsConfig = (0, _useShallowStable.default)(restTipsConfig);
72
+ // memo 只需要知道「有没有配 tips」,把对象本身放进依赖数组等于按引用比:
73
+ // 消费方写内联 `tips={{ ... }}`(最常见的写法)就让 label 的 memo 恒不命中。
74
+ // 拆成布尔量后依赖回到值语义。
75
+ const hasTips = Object.keys(tips).length > 0;
66
76
  const {
67
77
  permitted
68
78
  } = (0, _usePermissions.default)(hasPermi);
@@ -72,7 +82,7 @@ const ItemContainer = props => {
72
82
  if (hideLabel) {
73
83
  return undefined;
74
84
  }
75
- return Object.keys(tips).length ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
85
+ return hasTips ? /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
76
86
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
77
87
  className: (0, _classnames.default)(_indexModule.default.labelContent, labelClassName),
78
88
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
@@ -124,7 +134,7 @@ const ItemContainer = props => {
124
134
  children: labelExtra
125
135
  })]
126
136
  }) : undefined;
127
- }, [tips, labelClassName, label, tipsConfig, icon, iconClassName, labelExtra, labelExtraClassName, labelEllipsis, labelBreak, hideLabel]);
137
+ }, [hasTips, labelClassName, label, tipsConfig, icon, iconClassName, labelExtra, labelExtraClassName, labelEllipsis, labelBreak, hideLabel]);
128
138
  const {
129
139
  width: _labelWidth,
130
140
  height: _labelHeight,
@@ -165,9 +175,9 @@ const ItemContainer = props => {
165
175
  [_indexModule.default[requiredPosition]]: requiredPosition,
166
176
  [_indexModule.default.hideRequired]: hideRequired,
167
177
  [_indexModule.default[horizontalAlignment]]: layout === "horizontal" && horizontalAlignment,
168
- [_indexModule.default.hasLabelContent]: !!Object.keys(tips).length,
178
+ [_indexModule.default.hasLabelContent]: hasTips,
169
179
  [_indexModule.default.hideAdditiona]: hideAdditiona,
170
- [_indexModule.default.legacyHasLabelExtra]: legacyHasSelector && layout === "vertical" && !hideLabel && !!label && !!labelExtra && !Object.keys(tips).length
180
+ [_indexModule.default.legacyHasLabelExtra]: legacyHasSelector && layout === "vertical" && !hideLabel && !!label && !!labelExtra && !hasTips
171
181
  }),
172
182
  colon: typeof colon === "boolean" ? colon : layout === "horizontal" && requiredPosition === "before",
173
183
  label: labelRender ? labelRender(_label) : _label,
@@ -6,6 +6,18 @@ export interface ModalProps extends AntdModalProps {
6
6
  edgeDetection?: boolean;
7
7
  full?: boolean;
8
8
  titleButtonGroup?: ButtonProps[];
9
+ /**
10
+ * 高度**下界**。给「内容要等接口回来才画得出」的弹窗用:不给下界的话,
11
+ * 打开那一刻只有标题栏那么高,数据回来再撑开 —— 视觉上跳一下。
12
+ *
13
+ * - `true`:用标准档(`modalMinHeight`,700px),后台详情 / 列表 / 记录类弹窗都用它
14
+ * - 数字:px;字符串:原样当 CSS 长度
15
+ * - 不传:维持内容自适应 —— **短表单不要传**,否则平白留出一大片空白
16
+ *
17
+ * 它只是下界:内容更高照常撑开、照常滚动;并且在样式里与 `90vh` 取小,
18
+ * 矮窗口下不会被顶出屏幕。
19
+ */
20
+ minHeight?: number | string | boolean;
9
21
  }
10
22
  interface ModalFC extends React.FC<ModalProps> {
11
23
  confirm: typeof AntdModal.confirm;
@@ -11,13 +11,22 @@ var _hooks = require("./_hooks");
11
11
  var _Button = _interopRequireDefault(require("../Button"));
12
12
  var _semantic = require("../../utils/semantic");
13
13
  var _feedback = require("../../feedback");
14
+ var _tokens = require("../../styles/tokens");
14
15
  var _jsxRuntime = require("react/jsx-runtime");
15
16
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
17
+ /** `minHeight` prop → CSS 长度。`true` 走标准档,`false` / 不传等于不设下界 */
18
+ const resolveMinHeight = minHeight => {
19
+ if (minHeight === undefined || minHeight === false) return undefined;
20
+ if (minHeight === true) return `${_tokens.modalMinHeight}px`;
21
+ return typeof minHeight === "number" ? `${minHeight}px` : minHeight;
22
+ };
16
23
  const Modal = props => {
17
24
  const {
18
25
  moveable = true,
19
26
  className,
20
27
  classNames,
28
+ styles: outerStyles,
29
+ minHeight,
21
30
  open,
22
31
  onCancel,
23
32
  onOk,
@@ -62,6 +71,7 @@ const Modal = props => {
62
71
  const handleOk = e => {
63
72
  onOk?.(e);
64
73
  };
74
+ const resolvedMinHeight = resolveMinHeight(minHeight);
65
75
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_antd.Modal, {
66
76
  centered: true,
67
77
  ...moadlConfig,
@@ -85,6 +95,18 @@ const Modal = props => {
85
95
  container: `${_indexModule.default.content} ${outer.container ?? ""}`,
86
96
  body: `${_indexModule.default.body} ${outer.body ?? ""}`,
87
97
  footer: `${_indexModule.default.footer} ${outer.footer ?? ""} ${footer === false ? _indexModule.default.noFooter : ""}`
98
+ }))
99
+ /* 下界通过 CSS 变量交给样式表,而不是直接写 `minHeight` 内联样式:
100
+ clamp(与 90vh 取小)必须在 CSS 里做——`min-height` 在 CSS 里赢过
101
+ `max-height`,内联写死的话矮窗口会被顶出屏幕 */,
102
+ styles: (0, _semantic.mergeSemantic)(outerStyles, outer => ({
103
+ ...outer,
104
+ container: {
105
+ ...(outer.container ?? {}),
106
+ ...(resolvedMinHeight ? {
107
+ "--vita-modal-min-height": resolvedMinHeight
108
+ } : {})
109
+ }
88
110
  })),
89
111
  footer: footer,
90
112
  afterClose: () => {