@zat-design/sisyphus-react 4.5.0-beta.1 → 4.5.0-beta.3

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.
@@ -22,7 +22,7 @@ const Validator = ({
22
22
  const {
23
23
  hasError,
24
24
  firstErrorIndex,
25
- firstErrorRowKey
25
+ firstErrorColumnKey
26
26
  } = await runUnifiedValidation({
27
27
  form: config.form,
28
28
  name: config.name,
@@ -38,9 +38,9 @@ const Validator = ({
38
38
  config?.tableRef?.current?.scrollTo?.({
39
39
  index: firstErrorIndex
40
40
  });
41
- // 横向:等错误行渲染后,下一帧把错误列滚入视口(虚拟表格横向需 scrollTo({ left }))
41
+ // 横向:把报错列滚入视口(虚拟表格列被虚拟化,按表头列宽算 scrollLeft,与行渲染无关)
42
42
  requestAnimationFrame(() => {
43
- scrollVirtualToErrorColumn(config?.tableRef, firstErrorRowKey);
43
+ scrollVirtualToErrorColumn(config?.tableRef, columns || [], firstErrorColumnKey);
44
44
  });
45
45
  });
46
46
  }
@@ -90,16 +90,22 @@ export declare const getDisabled: ({ globalControl, formDisabled, column, tabled
90
90
  */
91
91
  export declare const handleScrollToError: () => void;
92
92
  /**
93
- * 虚拟表格:横向滚动定位到「首个错误行」内的报错单元格。
93
+ * 把「首个报错列的 cellKey」映射成它在表头 th 中的可见列索引。
94
+ * 列序 = [选择列?(hasSelection 时占位 1), ...用户列];names 列的 cellKey 用 splitNames 结果。
95
+ * @returns 表头 th 下标;未命中返回 -1
96
+ */
97
+ export declare const resolveErrorColumnIndex: (columns: any[], cellKey: string, hasSelection: boolean) => number;
98
+ /**
99
+ * 虚拟表格:横向滚动定位到「首个报错列」。
94
100
  *
95
- * 背景:虚拟表格的横向滚动不是原生 DOM scroll(holder 的 overflow-x hidden),
96
- * rc-virtual-list 用 transform 模拟,只能通过 antd Table ref 的 scrollTo({ left }) 驱动;
97
- * 而 scrollTo({ index }) 仅做纵向行定位。保存校验失败后纵向已滚到错误行,但错误列可能仍在
98
- * 横向视口外(如错误列靠左、表格当前滚到最右)→ 看不到红字。此函数补齐横向定位。
101
+ * 背景:rc-table 虚拟表格对「列」也做了虚拟化(BodyGrid 仅渲染横向视口内的列),
102
+ * 保存校验失败时报错列可能根本未渲染进 body DOM,无法用单元格测量定位(旧实现据此失效)。
103
+ * 而表头(thead)不被列虚拟化——渲染全部列且为真实测量宽度,故改以表头为列宽来源:
104
+ * 累加报错列之前各列宽、减去 fixed-left 列宽(固定列悬浮,不占滚动偏移)得目标 scrollLeft。
99
105
  *
100
- * 目标 scrollLeft = 错误单元格前置兄弟列宽累计 - fixed-left 列宽(fixed 列悬浮,不占滚动偏移)。
106
+ * 注意:ref.nativeElement 仅为 body 滚动容器,表头在其外层 table-container 内,需向上查找。
101
107
  */
102
- export declare const scrollVirtualToErrorColumn: (tableRef: React.MutableRefObject<any>, rowKey?: string | null) => void;
108
+ export declare const scrollVirtualToErrorColumn: (tableRef: React.MutableRefObject<any>, columns: any[], cellKey?: string | null) => void;
103
109
  /**
104
110
  * 深copy一个对象,并过滤掉其中的React节点
105
111
  * @param value 需要深拷贝的对象
@@ -276,6 +276,13 @@ export const getDisabled = ({
276
276
  return convertToBoolean(column?.disabled ?? columnFieldProps?.disabled ?? tabledDisabled);
277
277
  };
278
278
 
279
+ /**
280
+ * 判断单元格是否为「左固定列」。
281
+ * antd6 固定列类名为 *-cell-fix-start(逻辑属性,支持 RTL),不再是旧版的 *-cell-fix-left;
282
+ * 兼容两者,避免漏算固定列宽导致报错单元格被固定列遮挡。
283
+ */
284
+ const isFixStartCell = el => /table-cell-fix-(start|left)/.test(el.className || '');
285
+
279
286
  /**
280
287
  * 表格自动滚动到报错位置
281
288
  */
@@ -297,7 +304,7 @@ export const handleScrollToError = () => {
297
304
  width = 0
298
305
  } = previousSibling.getBoundingClientRect() || {};
299
306
  childOffsetLeft += width;
300
- if (previousSibling?.classList?.contains?.('ant-table-cell-fix-left')) {
307
+ if (isFixStartCell(previousSibling)) {
301
308
  childFixedLeft += width;
302
309
  }
303
310
  }
@@ -326,41 +333,62 @@ export const handleScrollToError = () => {
326
333
  };
327
334
 
328
335
  /**
329
- * 虚拟表格:横向滚动定位到「首个错误行」内的报错单元格。
336
+ * 把「首个报错列的 cellKey」映射成它在表头 th 中的可见列索引。
337
+ * 列序 = [选择列?(hasSelection 时占位 1), ...用户列];names 列的 cellKey 用 splitNames 结果。
338
+ * @returns 表头 th 下标;未命中返回 -1
339
+ */
340
+ export const resolveErrorColumnIndex = (columns = [], cellKey, hasSelection) => {
341
+ const idx = (columns || []).findIndex(col => {
342
+ if (Array.isArray(col?.names)) {
343
+ return splitNames(col.names) === cellKey;
344
+ }
345
+ return String(col?.name ?? col?.dataIndex ?? col?.key) === cellKey;
346
+ });
347
+ if (idx < 0) {
348
+ return -1;
349
+ }
350
+ return hasSelection ? idx + 1 : idx;
351
+ };
352
+
353
+ /**
354
+ * 虚拟表格:横向滚动定位到「首个报错列」。
330
355
  *
331
- * 背景:虚拟表格的横向滚动不是原生 DOM scroll(holder 的 overflow-x hidden),
332
- * rc-virtual-list 用 transform 模拟,只能通过 antd Table ref 的 scrollTo({ left }) 驱动;
333
- * 而 scrollTo({ index }) 仅做纵向行定位。保存校验失败后纵向已滚到错误行,但错误列可能仍在
334
- * 横向视口外(如错误列靠左、表格当前滚到最右)→ 看不到红字。此函数补齐横向定位。
356
+ * 背景:rc-table 虚拟表格对「列」也做了虚拟化(BodyGrid 仅渲染横向视口内的列),
357
+ * 保存校验失败时报错列可能根本未渲染进 body DOM,无法用单元格测量定位(旧实现据此失效)。
358
+ * 而表头(thead)不被列虚拟化——渲染全部列且为真实测量宽度,故改以表头为列宽来源:
359
+ * 累加报错列之前各列宽、减去 fixed-left 列宽(固定列悬浮,不占滚动偏移)得目标 scrollLeft。
335
360
  *
336
- * 目标 scrollLeft = 错误单元格前置兄弟列宽累计 - fixed-left 列宽(fixed 列悬浮,不占滚动偏移)。
361
+ * 注意:ref.nativeElement 仅为 body 滚动容器,表头在其外层 table-container 内,需向上查找。
337
362
  */
338
- export const scrollVirtualToErrorColumn = (tableRef, rowKey) => {
363
+ export const scrollVirtualToErrorColumn = (tableRef, columns, cellKey) => {
339
364
  const ref = tableRef?.current;
340
365
  const root = ref?.nativeElement;
341
- if (!ref?.scrollTo || !root || rowKey == null) {
366
+ if (!ref?.scrollTo || !root || !cellKey) {
367
+ return;
368
+ }
369
+ // body 容器在外层 table-container 内,表头与其同级,需从 container 取表头
370
+ const container = root.closest('[class*="table-container"]') || root.parentElement;
371
+ const headerCells = container?.querySelectorAll('thead [class*="table-cell"]');
372
+ if (!headerCells?.length) {
342
373
  return;
343
374
  }
344
- const rowEl = root.querySelector(`[data-row-key="${rowKey}"]`);
345
- // 取错误行内首个报错单元格(DOM 顺序 = 列顺序,首个即最靠左的错误列)
346
- const errorCell = rowEl?.querySelector('[class*="form-item-has-error"]')?.closest('[class*="table-cell"]');
347
- if (!errorCell) {
375
+ const hasSelection = !!container?.querySelector('thead [class*="selection-column"]');
376
+ const targetIndex = resolveErrorColumnIndex(columns, cellKey, hasSelection);
377
+ // -1 未命中 / 0 为首列(含 fixed-left),无需横向滚动
378
+ if (targetIndex <= 0 || targetIndex >= headerCells.length) {
348
379
  return;
349
380
  }
350
381
  let offsetLeft = 0;
351
382
  let fixedLeft = 0;
352
- let previousSibling = errorCell.previousElementSibling;
353
- while (previousSibling) {
354
- if (previousSibling.nodeType === 1) {
355
- const {
356
- width = 0
357
- } = previousSibling.getBoundingClientRect() || {};
358
- offsetLeft += width;
359
- if (previousSibling.classList?.contains?.('ant-table-cell-fix-left')) {
360
- fixedLeft += width;
361
- }
383
+ for (let i = 0; i < targetIndex; i += 1) {
384
+ const cell = headerCells[i];
385
+ const {
386
+ width = 0
387
+ } = cell.getBoundingClientRect() || {};
388
+ offsetLeft += width;
389
+ if (isFixStartCell(cell)) {
390
+ fixedLeft += width;
362
391
  }
363
- previousSibling = previousSibling.previousElementSibling;
364
392
  }
365
393
  ref.scrollTo({
366
394
  left: Math.max(0, offsetLeft - fixedLeft)
@@ -46,6 +46,8 @@ export interface ValidateAllResult {
46
46
  firstErrorPath: (string | number)[] | null;
47
47
  /** 首个错误行的 rowKey(用于横向滚动时定位错误行 DOM),无错误为 null */
48
48
  firstErrorRowKey: string | null;
49
+ /** 首个错误列的 cellKey(names 列为 splitNames,普通列为列名),用于横向滚动定位列,无错误为 null */
50
+ firstErrorColumnKey: string | null;
49
51
  }
50
52
  /**
51
53
  * 对 form store 中 name 路径下的全量数组数据,逐行逐列用真实规则校验(脱离 DOM)。
@@ -82,5 +84,6 @@ export declare const runUnifiedValidation: ({ form, name, columns, config, error
82
84
  hasError: boolean;
83
85
  firstErrorIndex: number | null;
84
86
  firstErrorRowKey: string | null;
87
+ firstErrorColumnKey: string | null;
85
88
  }>;
86
89
  export {};
@@ -199,6 +199,7 @@ export const validateAllRows = async ({
199
199
  const errorMap = {};
200
200
  let firstErrorPath = null;
201
201
  let firstErrorRowKey = null;
202
+ let firstErrorColumnKey = null;
202
203
  for (let index = 0; index < list.length; index += 1) {
203
204
  const rowData = list[index];
204
205
  // 行可能为空位(单行编辑增删残留),跳过
@@ -244,6 +245,7 @@ export const validateAllRows = async ({
244
245
  if (!firstErrorPath) {
245
246
  firstErrorPath = built.namePath;
246
247
  firstErrorRowKey = rowKey;
248
+ firstErrorColumnKey = built.cellKey;
247
249
  }
248
250
  }
249
251
  }
@@ -252,7 +254,8 @@ export const validateAllRows = async ({
252
254
  hasError: !!firstErrorPath,
253
255
  errorMap,
254
256
  firstErrorPath,
255
- firstErrorRowKey
257
+ firstErrorRowKey,
258
+ firstErrorColumnKey
256
259
  };
257
260
  };
258
261
 
@@ -301,7 +304,8 @@ export const runUnifiedValidation = async ({
301
304
  hasError,
302
305
  errorMap,
303
306
  firstErrorPath,
304
- firstErrorRowKey
307
+ firstErrorRowKey,
308
+ firstErrorColumnKey
305
309
  } = await validateAllRows({
306
310
  form,
307
311
  name,
@@ -314,6 +318,7 @@ export const runUnifiedValidation = async ({
314
318
  return {
315
319
  hasError,
316
320
  firstErrorIndex,
317
- firstErrorRowKey
321
+ firstErrorRowKey,
322
+ firstErrorColumnKey
318
323
  };
319
324
  };
@@ -77,32 +77,33 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
77
77
  component?: React.ReactNode | ReactiveFunction<any, React.ReactNode>;
78
78
  isView?: boolean;
79
79
  style?: React.CSSProperties;
80
+ trim?: boolean;
81
+ normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
80
82
  children?: React.ReactNode | ((form: FormInstance<any>) => React.ReactNode);
83
+ prefixCls?: string;
84
+ trigger?: string;
85
+ id?: string;
81
86
  className?: string;
87
+ rootClassName?: string;
88
+ status?: "" | "warning" | "error" | "success" | "validating";
89
+ getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
82
90
  hidden?: boolean;
83
- id?: string;
84
91
  onReset?: () => void;
92
+ vertical?: boolean;
93
+ desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
85
94
  validateTrigger?: string | false | string[];
86
95
  preserve?: boolean;
87
- trim?: boolean;
88
- normalize?: (value: any, prevValue: any, allValues: import("@rc-component/form/lib/interface").Store) => any;
89
96
  clearNotShow?: boolean;
90
97
  labelAlign?: import("antd/es/form/interface").FormLabelAlign;
91
- prefixCls?: string;
92
98
  colon?: boolean;
93
99
  layout?: import("antd/es/form/Form").FormItemLayout;
94
100
  labelCol?: import("antd").ColProps;
95
101
  wrapperCol?: import("antd").ColProps;
96
- rootClassName?: string;
97
- status?: "" | "validating" | "warning" | "error" | "success";
98
- vertical?: boolean;
99
102
  htmlFor?: string;
100
103
  getValueFromEvent?: (...args: import("@rc-component/form/lib/interface").EventArgs) => any;
101
104
  shouldUpdate?: import("@rc-component/form/lib/Field").ShouldUpdate<any>;
102
- trigger?: string;
103
105
  validateDebounce?: number;
104
106
  valuePropName?: string;
105
- getValueProps?: ((value: any) => Record<string, unknown>) & ((value: any) => Record<string, unknown>);
106
107
  messageVariables?: Record<string, string>;
107
108
  initialValue?: any;
108
109
  onMetaChange?: (meta: import("@rc-component/form/lib/Field").MetaEvent) => void;
@@ -112,7 +113,7 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
112
113
  hasFeedback?: boolean | {
113
114
  icons: import("antd/es/form/FormItem").FeedbackIcons;
114
115
  };
115
- validateStatus?: "" | "validating" | "warning" | "error" | "success";
116
+ validateStatus?: "" | "warning" | "error" | "success" | "validating";
116
117
  help?: React.ReactNode;
117
118
  fieldId?: string;
118
119
  valueType?: import("../../../render/propsType").ProFormValueType;
@@ -126,7 +127,6 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
126
127
  upperCase?: boolean;
127
128
  toISOString?: boolean;
128
129
  toCSTString?: boolean;
129
- desensitization?: [number, number] | ReactiveFunction<any, [number, number]>;
130
130
  name: any;
131
131
  dependencies: any[];
132
132
  tooltip: string | {
@@ -141,7 +141,7 @@ export declare const useFormItemProps: (column: FlexibleGroupColumnType, context
141
141
  * 创建组件属性
142
142
  */
143
143
  export declare const createComponentProps: (column: FlexibleGroupColumnType, formItemProps: any) => {
144
- componentProps: import("lodash").Omit<any, "clearNotShow" | "format" | "valueType" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "precision">;
144
+ componentProps: import("lodash").Omit<any, "format" | "clearNotShow" | "valueType" | "switchValue" | "dependNames" | "toISOString" | "toCSTString" | "precision">;
145
145
  formItemTransform: {
146
146
  getValueProps: any;
147
147
  normalize: any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zat-design/sisyphus-react",
3
- "version": "4.5.0-beta.1",
3
+ "version": "4.5.0-beta.3",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "es",