@hsu-react/ui 0.0.10 → 0.0.11

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.
package/README.md CHANGED
@@ -30,10 +30,10 @@ export default function App() {
30
30
  - `ConfigProvider.permissions` — 当前用户权限码,驱动所有 `hasPermi` 的显隐校验。
31
31
  - `ConfigProvider.request` — 注入 HTTP 请求实现(库本身不绑定 HTTP 客户端)。
32
32
 
33
- 可选引入全局 antd 观感覆盖:
33
+ 可选引入全局 antd 观感覆盖(产物为 scss,宿主项目需具备 scss 编译能力):
34
34
 
35
35
  ```ts
36
- import "@hsu-react/ui/es/styles/antd-overload.less";
36
+ import "@hsu-react/ui/es/styles/antd-overload.scss";
37
37
  ```
38
38
 
39
39
  ## 开发
@@ -25,11 +25,13 @@ var useEllipsisTooltip = function useEllipsisTooltip(tooltipConfig) {
25
25
  if (column.ellipsis && !column.render && column.dataIndex) {
26
26
  return _objectSpread(_objectSpread({}, column), {}, {
27
27
  render: function render(text) {
28
- var _column$width, _column$width2, _column$ellipsisPosit;
28
+ var _column$ellipsisPosit;
29
29
  // 获取实际的显示值
30
30
  var displayValue = typeof text === "string" || typeof text === "number" ? text : String(text !== null && text !== void 0 ? text : "");
31
+
32
+ // 不传 width:由 TextEllipsis 实测单元格实际渲染宽度作为 Tooltip 宽度
33
+ // (声明的列宽经 fixedWidth 比例分配后与实际宽度并不一致)
31
34
  return /*#__PURE__*/React.createElement(TextEllipsis, {
32
- width: Math.max(Number((_column$width = column.width) !== null && _column$width !== void 0 ? _column$width : 0), 300) === 300 ? undefined : Math.max(Number((_column$width2 = column.width) !== null && _column$width2 !== void 0 ? _column$width2 : 0), 300),
33
35
  tooltipConfig: tooltipConfig,
34
36
  children: displayValue,
35
37
  ellipsisPosition: (_column$ellipsisPosit = column.ellipsisPosition) !== null && _column$ellipsisPosit !== void 0 ? _column$ellipsisPosit : tooltipConfig === null || tooltipConfig === void 0 ? void 0 : tooltipConfig.ellipsisPosition,
@@ -31,6 +31,7 @@ interface UseTableColumnsParams<T extends AnyObject> {
31
31
  bordered?: boolean;
32
32
  rowSelection?: TableRowSelection<T>;
33
33
  expandable?: TableProps<T>["expandable"];
34
+ tdPadding?: string;
34
35
  }
35
36
  declare const useTableColumns: <T extends AnyObject>(params: UseTableColumnsParams<T>) => {
36
37
  _columns: ColumnsType<T>;
@@ -26,9 +26,30 @@ import { jsxs as _jsxs } from "react/jsx-runtime";
26
26
  var MEASURE_ALIGN_FONT_SIZE = 14;
27
27
  // Canvas measureText 仅返回 advance width,较 DOM 实际渲染偏窄约 1px,补 2px 缓冲避免最宽行被挤压
28
28
  var MEASURE_ALIGN_BUFFER_PX = 2;
29
- // 单元格左右内边距合计(--td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
29
+ // 单元格左右内边距合计的默认值(默认 --td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
30
30
  var MEASURE_ALIGN_CELL_PADDING_PX = 16;
31
31
 
32
+ /**
33
+ * 解析 tdPadding(CSS padding 简写)的左右内边距合计;
34
+ * 未传或解析不出时回退默认值(与默认 --td-padding: 4px 8px 一致)。
35
+ */
36
+ var getCellHorizontalPadding = function getCellHorizontalPadding(tdPadding) {
37
+ if (!tdPadding) return MEASURE_ALIGN_CELL_PADDING_PX;
38
+ var parts = tdPadding.trim().split(/\s+/).map(function (v) {
39
+ return parseFloat(v);
40
+ });
41
+ if (!parts.length || parts.some(function (v) {
42
+ return Number.isNaN(v);
43
+ })) {
44
+ return MEASURE_ALIGN_CELL_PADDING_PX;
45
+ }
46
+
47
+ // CSS 简写:1 值=四边;2/3 值=第 2 个为左右;4 值=右+左
48
+ if (parts.length === 1) return parts[0] * 2;
49
+ if (parts.length === 4) return parts[1] + parts[3];
50
+ return parts[1] * 2;
51
+ };
52
+
32
53
  // 计算 measureAlign 列的定宽(取本列所有行展示文本的最大像素宽)
33
54
  var getMeasureAlignWidth = function getMeasureAlignWidth(column, dataSource) {
34
55
  if (!(dataSource !== null && dataSource !== void 0 && dataSource.length)) return 0;
@@ -64,7 +85,8 @@ var useTableColumns = function useTableColumns(params) {
64
85
  cls = params.cls,
65
86
  bordered = params.bordered,
66
87
  rowSelection = params.rowSelection,
67
- expandable = params.expandable;
88
+ expandable = params.expandable,
89
+ tdPadding = params.tdPadding;
68
90
  var _usePermissions = usePermissions(),
69
91
  checkPermission = _usePermissions.checkPermission;
70
92
  var _useState = useState(null),
@@ -132,7 +154,7 @@ var useTableColumns = function useTableColumns(params) {
132
154
  if (item.measureAlign && typeof item.width === "number") {
133
155
  var measured = getMeasureAlignWidth(item, dataSource);
134
156
  if (measured > 0) {
135
- item.width = Math.max(item.width, measured + MEASURE_ALIGN_CELL_PADDING_PX);
157
+ item.width = Math.max(item.width, measured + getCellHorizontalPadding(tdPadding));
136
158
  }
137
159
  }
138
160
  return item;
@@ -144,7 +166,7 @@ var useTableColumns = function useTableColumns(params) {
144
166
  });
145
167
  });
146
168
  return newColumns;
147
- }, [autoWidth, columns, dataSource, checkPermission, scroll]);
169
+ }, [autoWidth, columns, dataSource, checkPermission, scroll, tdPadding]);
148
170
 
149
171
  // 渲染列标题
150
172
  var renderColumnTitle = useCallback(function (column) {
@@ -126,7 +126,8 @@ var Table = function Table(props) {
126
126
  cls: cls,
127
127
  bordered: bordered,
128
128
  rowSelection: rowSelection,
129
- expandable: expandable
129
+ expandable: expandable,
130
+ tdPadding: tdPadding
130
131
  }),
131
132
  _columns = _useTableColumns._columns,
132
133
  renderedColumns = _useTableColumns.renderedColumns,
@@ -46,6 +46,11 @@ var TextEllipsis = function TextEllipsis(_ref) {
46
46
  _useState4 = _slicedToArray(_useState3, 2),
47
47
  truncatedText = _useState4[0],
48
48
  setTruncatedText = _useState4[1];
49
+ // 容器实际渲染宽度:未声明 width 时(如表格列宽动态分配)作为 Tooltip 宽度
50
+ var _useState5 = useState(0),
51
+ _useState6 = _slicedToArray(_useState5, 2),
52
+ measuredWidth = _useState6[0],
53
+ setMeasuredWidth = _useState6[1];
49
54
  useEffect(function () {
50
55
  var element = textRef.current;
51
56
  var measureElement = measureRef.current;
@@ -101,27 +106,40 @@ var TextEllipsis = function TextEllipsis(_ref) {
101
106
  return result;
102
107
  };
103
108
  var textContent = getTextContent(children);
104
- var isTextOverflow = element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
105
- setIsOverflow(isTextOverflow);
109
+ var update = function update() {
110
+ var isTextOverflow = element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
111
+ setIsOverflow(isTextOverflow);
112
+ setMeasuredWidth(element.clientWidth);
106
113
 
107
- // 如果是省略前面且文本溢出,需要计算截断后的文本
108
- if (ellipsisPosition === "start" && isTextOverflow && textContent) {
109
- var maxWidth = element.clientWidth;
110
- var truncated = calculateTruncatedText(textContent, maxWidth);
111
- setTruncatedText(truncated);
112
- } else {
113
- setTruncatedText(children);
114
- }
114
+ // 如果是省略前面且文本溢出,需要计算截断后的文本
115
+ if (ellipsisPosition === "start" && isTextOverflow && textContent) {
116
+ var maxWidth = element.clientWidth;
117
+ var truncated = calculateTruncatedText(textContent, maxWidth);
118
+ setTruncatedText(truncated);
119
+ } else {
120
+ setTruncatedText(children);
121
+ }
122
+ };
123
+ update();
124
+
125
+ // 容器宽度动态变化(如表格列宽重分配、窗口 resize)时重测溢出与宽度
126
+ var resizeObserver = new ResizeObserver(update);
127
+ resizeObserver.observe(element);
128
+ return function () {
129
+ return resizeObserver.disconnect();
130
+ };
115
131
  }, [children, ellipsisPosition, style]);
116
132
 
117
- // 计算 Tooltip 的宽度
133
+ // 计算 Tooltip 的宽度:显式 width 优先;未声明(或百分比)时用容器实际渲染宽度,
134
+ // 实测不到再回退 defaultWidth
118
135
  var getTooltipWidth = function getTooltipWidth() {
119
136
  var _tooltipConfig$defaul;
120
137
  var defaultWidth = (_tooltipConfig$defaul = tooltipConfig === null || tooltipConfig === void 0 ? void 0 : tooltipConfig.defaultWidth) !== null && _tooltipConfig$defaul !== void 0 ? _tooltipConfig$defaul : DEFAULT_TOOLTIP_WIDTH;
121
- if (!width) return defaultWidth;
138
+ var dynamicWidth = measuredWidth > 0 ? measuredWidth : defaultWidth;
139
+ if (!width) return dynamicWidth;
122
140
  if (typeof width === "number") return width;
123
- if (typeof width === "string" && width.includes("%")) return defaultWidth;
124
- return parseInt(width) || defaultWidth;
141
+ if (typeof width === "string" && width.includes("%")) return dynamicWidth;
142
+ return parseInt(width) || dynamicWidth;
125
143
  };
126
144
  var tooltipWidth = getTooltipWidth();
127
145
 
@@ -30,8 +30,10 @@ const useEllipsisTooltip = tooltipConfig => {
30
30
  render: text => {
31
31
  // 获取实际的显示值
32
32
  const displayValue = typeof text === "string" || typeof text === "number" ? text : String(text ?? "");
33
+
34
+ // 不传 width:由 TextEllipsis 实测单元格实际渲染宽度作为 Tooltip 宽度
35
+ // (声明的列宽经 fixedWidth 比例分配后与实际宽度并不一致)
33
36
  return /*#__PURE__*/_react.default.createElement(_TextEllipsis.default, {
34
- width: Math.max(Number(column.width ?? 0), 300) === 300 ? undefined : Math.max(Number(column.width ?? 0), 300),
35
37
  tooltipConfig: tooltipConfig,
36
38
  children: displayValue,
37
39
  ellipsisPosition: column.ellipsisPosition ?? tooltipConfig?.ellipsisPosition,
@@ -31,6 +31,7 @@ interface UseTableColumnsParams<T extends AnyObject> {
31
31
  bordered?: boolean;
32
32
  rowSelection?: TableRowSelection<T>;
33
33
  expandable?: TableProps<T>["expandable"];
34
+ tdPadding?: string;
34
35
  }
35
36
  declare const useTableColumns: <T extends AnyObject>(params: UseTableColumnsParams<T>) => {
36
37
  _columns: ColumnsType<T>;
@@ -19,9 +19,26 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
19
19
  const MEASURE_ALIGN_FONT_SIZE = 14;
20
20
  // Canvas measureText 仅返回 advance width,较 DOM 实际渲染偏窄约 1px,补 2px 缓冲避免最宽行被挤压
21
21
  const MEASURE_ALIGN_BUFFER_PX = 2;
22
- // 单元格左右内边距合计(--td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
22
+ // 单元格左右内边距合计的默认值(默认 --td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
23
23
  const MEASURE_ALIGN_CELL_PADDING_PX = 16;
24
24
 
25
+ /**
26
+ * 解析 tdPadding(CSS padding 简写)的左右内边距合计;
27
+ * 未传或解析不出时回退默认值(与默认 --td-padding: 4px 8px 一致)。
28
+ */
29
+ const getCellHorizontalPadding = tdPadding => {
30
+ if (!tdPadding) return MEASURE_ALIGN_CELL_PADDING_PX;
31
+ const parts = tdPadding.trim().split(/\s+/).map(v => parseFloat(v));
32
+ if (!parts.length || parts.some(v => Number.isNaN(v))) {
33
+ return MEASURE_ALIGN_CELL_PADDING_PX;
34
+ }
35
+
36
+ // CSS 简写:1 值=四边;2/3 值=第 2 个为左右;4 值=右+左
37
+ if (parts.length === 1) return parts[0] * 2;
38
+ if (parts.length === 4) return parts[1] + parts[3];
39
+ return parts[1] * 2;
40
+ };
41
+
25
42
  // 计算 measureAlign 列的定宽(取本列所有行展示文本的最大像素宽)
26
43
  const getMeasureAlignWidth = (column, dataSource) => {
27
44
  if (!dataSource?.length) return 0;
@@ -57,7 +74,8 @@ const useTableColumns = params => {
57
74
  cls,
58
75
  bordered,
59
76
  rowSelection,
60
- expandable
77
+ expandable,
78
+ tdPadding
61
79
  } = params;
62
80
  const {
63
81
  checkPermission
@@ -117,7 +135,7 @@ const useTableColumns = params => {
117
135
  if (item.measureAlign && typeof item.width === "number") {
118
136
  const measured = getMeasureAlignWidth(item, dataSource);
119
137
  if (measured > 0) {
120
- item.width = Math.max(item.width, measured + MEASURE_ALIGN_CELL_PADDING_PX);
138
+ item.width = Math.max(item.width, measured + getCellHorizontalPadding(tdPadding));
121
139
  }
122
140
  }
123
141
  return item;
@@ -132,7 +150,7 @@ const useTableColumns = params => {
132
150
  };
133
151
  });
134
152
  return newColumns;
135
- }, [autoWidth, columns, dataSource, checkPermission, scroll]);
153
+ }, [autoWidth, columns, dataSource, checkPermission, scroll, tdPadding]);
136
154
 
137
155
  // 渲染列标题
138
156
  const renderColumnTitle = (0, _react.useCallback)(column => {
@@ -125,7 +125,8 @@ const Table = props => {
125
125
  cls,
126
126
  bordered,
127
127
  rowSelection,
128
- expandable
128
+ expandable,
129
+ tdPadding
129
130
  });
130
131
  (0, _useSetTableHeight.default)({
131
132
  pagination,
@@ -29,6 +29,8 @@ const TextEllipsis = ({
29
29
  const measureRef = (0, _react.useRef)(null);
30
30
  const [isOverflow, setIsOverflow] = (0, _react.useState)(false);
31
31
  const [truncatedText, setTruncatedText] = (0, _react.useState)(children);
32
+ // 容器实际渲染宽度:未声明 width 时(如表格列宽动态分配)作为 Tooltip 宽度
33
+ const [measuredWidth, setMeasuredWidth] = (0, _react.useState)(0);
32
34
  (0, _react.useEffect)(() => {
33
35
  const element = textRef.current;
34
36
  const measureElement = measureRef.current;
@@ -84,26 +86,37 @@ const TextEllipsis = ({
84
86
  return result;
85
87
  };
86
88
  const textContent = getTextContent(children);
87
- const isTextOverflow = element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
88
- setIsOverflow(isTextOverflow);
89
+ const update = () => {
90
+ const isTextOverflow = element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
91
+ setIsOverflow(isTextOverflow);
92
+ setMeasuredWidth(element.clientWidth);
89
93
 
90
- // 如果是省略前面且文本溢出,需要计算截断后的文本
91
- if (ellipsisPosition === "start" && isTextOverflow && textContent) {
92
- const maxWidth = element.clientWidth;
93
- const truncated = calculateTruncatedText(textContent, maxWidth);
94
- setTruncatedText(truncated);
95
- } else {
96
- setTruncatedText(children);
97
- }
94
+ // 如果是省略前面且文本溢出,需要计算截断后的文本
95
+ if (ellipsisPosition === "start" && isTextOverflow && textContent) {
96
+ const maxWidth = element.clientWidth;
97
+ const truncated = calculateTruncatedText(textContent, maxWidth);
98
+ setTruncatedText(truncated);
99
+ } else {
100
+ setTruncatedText(children);
101
+ }
102
+ };
103
+ update();
104
+
105
+ // 容器宽度动态变化(如表格列宽重分配、窗口 resize)时重测溢出与宽度
106
+ const resizeObserver = new ResizeObserver(update);
107
+ resizeObserver.observe(element);
108
+ return () => resizeObserver.disconnect();
98
109
  }, [children, ellipsisPosition, style]);
99
110
 
100
- // 计算 Tooltip 的宽度
111
+ // 计算 Tooltip 的宽度:显式 width 优先;未声明(或百分比)时用容器实际渲染宽度,
112
+ // 实测不到再回退 defaultWidth
101
113
  const getTooltipWidth = () => {
102
114
  const defaultWidth = tooltipConfig?.defaultWidth ?? DEFAULT_TOOLTIP_WIDTH;
103
- if (!width) return defaultWidth;
115
+ const dynamicWidth = measuredWidth > 0 ? measuredWidth : defaultWidth;
116
+ if (!width) return dynamicWidth;
104
117
  if (typeof width === "number") return width;
105
- if (typeof width === "string" && width.includes("%")) return defaultWidth;
106
- return parseInt(width) || defaultWidth;
118
+ if (typeof width === "string" && width.includes("%")) return dynamicWidth;
119
+ return parseInt(width) || dynamicWidth;
107
120
  };
108
121
  const tooltipWidth = getTooltipWidth();
109
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hsu-react/ui",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "一套基于 React + Ant Design 的中后台业务组件库",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,11 +32,9 @@ const useEllipsisTooltip = <T extends AnyObject>(
32
32
  ? text
33
33
  : String(text ?? "");
34
34
 
35
+ // 不传 width:由 TextEllipsis 实测单元格实际渲染宽度作为 Tooltip 宽度
36
+ // (声明的列宽经 fixedWidth 比例分配后与实际宽度并不一致)
35
37
  return React.createElement(TextEllipsis, {
36
- width:
37
- Math.max(Number(column.width ?? 0), 300) === 300
38
- ? undefined
39
- : Math.max(Number(column.width ?? 0), 300),
40
38
  tooltipConfig: tooltipConfig,
41
39
  children: displayValue,
42
40
  ellipsisPosition:
@@ -23,9 +23,30 @@ import { PaginationProps } from "../_components/Pagination";
23
23
  const MEASURE_ALIGN_FONT_SIZE = 14;
24
24
  // Canvas measureText 仅返回 advance width,较 DOM 实际渲染偏窄约 1px,补 2px 缓冲避免最宽行被挤压
25
25
  const MEASURE_ALIGN_BUFFER_PX = 2;
26
- // 单元格左右内边距合计(--td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
26
+ // 单元格左右内边距合计的默认值(默认 --td-padding: 4px 8px),用于把列宽抬到能放下定宽内容块
27
27
  const MEASURE_ALIGN_CELL_PADDING_PX = 16;
28
28
 
29
+ /**
30
+ * 解析 tdPadding(CSS padding 简写)的左右内边距合计;
31
+ * 未传或解析不出时回退默认值(与默认 --td-padding: 4px 8px 一致)。
32
+ */
33
+ const getCellHorizontalPadding = (tdPadding?: string): number => {
34
+ if (!tdPadding) return MEASURE_ALIGN_CELL_PADDING_PX;
35
+
36
+ const parts = tdPadding
37
+ .trim()
38
+ .split(/\s+/)
39
+ .map((v) => parseFloat(v));
40
+ if (!parts.length || parts.some((v) => Number.isNaN(v))) {
41
+ return MEASURE_ALIGN_CELL_PADDING_PX;
42
+ }
43
+
44
+ // CSS 简写:1 值=四边;2/3 值=第 2 个为左右;4 值=右+左
45
+ if (parts.length === 1) return parts[0] * 2;
46
+ if (parts.length === 4) return parts[1] + parts[3];
47
+ return parts[1] * 2;
48
+ };
49
+
29
50
  // 计算 measureAlign 列的定宽(取本列所有行展示文本的最大像素宽)
30
51
  const getMeasureAlignWidth = <T extends AnyObject>(
31
52
  column: ColumnType<T>,
@@ -65,6 +86,7 @@ interface UseTableColumnsParams<T extends AnyObject> {
65
86
  bordered?: boolean;
66
87
  rowSelection?: TableRowSelection<T>;
67
88
  expandable?: TableProps<T>["expandable"];
89
+ tdPadding?: string;
68
90
  }
69
91
 
70
92
  const useTableColumns = <T extends AnyObject>(
@@ -94,6 +116,7 @@ const useTableColumns = <T extends AnyObject>(
94
116
  bordered,
95
117
  rowSelection,
96
118
  expandable,
119
+ tdPadding,
97
120
  } = params;
98
121
  const { checkPermission } = usePermissions();
99
122
  const [tableWidth, setTableWidth] = useState<number | null>(null);
@@ -171,7 +194,7 @@ const useTableColumns = <T extends AnyObject>(
171
194
  if (measured > 0) {
172
195
  item.width = Math.max(
173
196
  item.width,
174
- measured + MEASURE_ALIGN_CELL_PADDING_PX
197
+ measured + getCellHorizontalPadding(tdPadding)
175
198
  );
176
199
  }
177
200
  }
@@ -190,7 +213,7 @@ const useTableColumns = <T extends AnyObject>(
190
213
  });
191
214
 
192
215
  return newColumns;
193
- }, [autoWidth, columns, dataSource, checkPermission, scroll]);
216
+ }, [autoWidth, columns, dataSource, checkPermission, scroll, tdPadding]);
194
217
 
195
218
  // 渲染列标题
196
219
  const renderColumnTitle = useCallback(
@@ -82,6 +82,8 @@ export default () => (
82
82
  | measureAlign | 开启定宽右对齐(配合列 `align: "center"` 使用) | `boolean` | - |
83
83
  | measureText | 测量宽度所用文本;当单元格由 `render` 产出复合/JSX 内容时,用它提供与展示一致的文本(默认取 `dataIndex` 原始值) | `(record) => string` | - |
84
84
 
85
+ - 列宽自适应:声明的 `width` 只作下限——实际列宽会按「本列最宽内容块的测量宽 + 单元格左右内边距」自动抬升,出现超长值(如 `+3357.52%`)时内容不会被声明宽度截断。
86
+
85
87
  > 注意:不适用于 `render` 返回 `{ children, rowSpan }` 这类合并单元格场景。
86
88
 
87
89
  > 另导出 `TableDrag`,用于行拖拽排序场景。
@@ -222,6 +222,7 @@ const Table: TableFC = <T extends AnyObject>(props: TableProps<T>) => {
222
222
  bordered,
223
223
  rowSelection,
224
224
  expandable,
225
+ tdPadding,
225
226
  });
226
227
 
227
228
  useSetTableHeight({
@@ -37,7 +37,7 @@ export default () => (
37
37
  | --- | --- | --- | --- |
38
38
  | children | 要显示的文本内容 | `ReactNode` | - |
39
39
  | width | 容器宽度,用于计算 Tooltip 宽度 | `number \| string` | - |
40
- | tooltipConfig | Tooltip 配置(可附加 `defaultWidth`) | `Omit<TooltipProps, 'title' \| 'children'> & { defaultWidth?: number }` | - |
40
+ | tooltipConfig | Tooltip 配置(可附加 `defaultWidth`:容器实际宽度实测不到时的兜底宽) | `Omit<TooltipProps, 'title' \| 'children'> & { defaultWidth?: number }` | - |
41
41
  | disabled | 是否禁用 Tooltip(即使溢出也不显示) | `boolean` | `false` |
42
42
  | ellipsisPosition | 省略号位置,`start` 省略前面,`end` 省略后面 | `'start' \| 'end'` | `'end'` |
43
43
  | style | 文本自定义样式 | `React.CSSProperties` | - |
@@ -45,6 +45,8 @@ const TextEllipsis: React.FC<TextEllipsisProps> = ({
45
45
  const measureRef = useRef<HTMLSpanElement>(null);
46
46
  const [isOverflow, setIsOverflow] = useState(false);
47
47
  const [truncatedText, setTruncatedText] = useState<ReactNode>(children);
48
+ // 容器实际渲染宽度:未声明 width 时(如表格列宽动态分配)作为 Tooltip 宽度
49
+ const [measuredWidth, setMeasuredWidth] = useState(0);
48
50
 
49
51
  useEffect(() => {
50
52
  const element = textRef.current;
@@ -105,29 +107,42 @@ const TextEllipsis: React.FC<TextEllipsisProps> = ({
105
107
  };
106
108
 
107
109
  const textContent = getTextContent(children);
108
- const isTextOverflow =
109
- element.scrollWidth > element.clientWidth ||
110
- element.scrollHeight > element.clientHeight;
111
-
112
- setIsOverflow(isTextOverflow);
113
-
114
- // 如果是省略前面且文本溢出,需要计算截断后的文本
115
- if (ellipsisPosition === "start" && isTextOverflow && textContent) {
116
- const maxWidth = element.clientWidth;
117
- const truncated = calculateTruncatedText(textContent, maxWidth);
118
- setTruncatedText(truncated);
119
- } else {
120
- setTruncatedText(children);
121
- }
110
+
111
+ const update = () => {
112
+ const isTextOverflow =
113
+ element.scrollWidth > element.clientWidth ||
114
+ element.scrollHeight > element.clientHeight;
115
+
116
+ setIsOverflow(isTextOverflow);
117
+ setMeasuredWidth(element.clientWidth);
118
+
119
+ // 如果是省略前面且文本溢出,需要计算截断后的文本
120
+ if (ellipsisPosition === "start" && isTextOverflow && textContent) {
121
+ const maxWidth = element.clientWidth;
122
+ const truncated = calculateTruncatedText(textContent, maxWidth);
123
+ setTruncatedText(truncated);
124
+ } else {
125
+ setTruncatedText(children);
126
+ }
127
+ };
128
+
129
+ update();
130
+
131
+ // 容器宽度动态变化(如表格列宽重分配、窗口 resize)时重测溢出与宽度
132
+ const resizeObserver = new ResizeObserver(update);
133
+ resizeObserver.observe(element);
134
+ return () => resizeObserver.disconnect();
122
135
  }, [children, ellipsisPosition, style]);
123
136
 
124
- // 计算 Tooltip 的宽度
137
+ // 计算 Tooltip 的宽度:显式 width 优先;未声明(或百分比)时用容器实际渲染宽度,
138
+ // 实测不到再回退 defaultWidth
125
139
  const getTooltipWidth = () => {
126
140
  const defaultWidth = tooltipConfig?.defaultWidth ?? DEFAULT_TOOLTIP_WIDTH;
127
- if (!width) return defaultWidth;
141
+ const dynamicWidth = measuredWidth > 0 ? measuredWidth : defaultWidth;
142
+ if (!width) return dynamicWidth;
128
143
  if (typeof width === "number") return width;
129
- if (typeof width === "string" && width.includes("%")) return defaultWidth;
130
- return parseInt(width) || defaultWidth;
144
+ if (typeof width === "string" && width.includes("%")) return dynamicWidth;
145
+ return parseInt(width) || dynamicWidth;
131
146
  };
132
147
 
133
148
  const tooltipWidth = getTooltipWidth();