@hi-ui/form 4.2.2 → 4.3.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @hi-ui/form
2
2
 
3
+ ## 4.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#3006](https://github.com/XiaoMi/hiui/pull/3006) [`4540c217a`](https://github.com/XiaoMi/hiui/commit/4540c217ade6749c38ee58cefcfe94322889b929) Thanks [@zyprepare](https://github.com/zyprepare)! - feat: Add scrollToFirstError api
8
+
3
9
  ## 4.2.2
4
10
 
5
11
  ### Patch Changes
@@ -43,7 +43,8 @@ var FormItem = function FormItem(_a) {
43
43
  rest = tslib.__rest(_a, ["className", "children", "field", "valueType", "rules", "valuePropName", "valueChangeFuncPropName", "valueDispatchTransform", "valueConnectTransform", "validateTrigger", "render"]);
44
44
  var _useFormContext = context.useFormContext(),
45
45
  prefixCls = _useFormContext.prefixCls,
46
- showRequiredOnValidateRequired = _useFormContext.showRequiredOnValidateRequired;
46
+ showRequiredOnValidateRequired = _useFormContext.showRequiredOnValidateRequired,
47
+ formItemsRef = _useFormContext.formItemsRef;
47
48
  var fieldRules = useFormField.useFiledRules({
48
49
  field: field,
49
50
  rules: rules,
@@ -59,6 +60,9 @@ var FormItem = function FormItem(_a) {
59
60
  return required;
60
61
  }, [required, showRequiredOnValidateRequired, fieldRules]);
61
62
  return /*#__PURE__*/React__default["default"].createElement(FormLabel.FormLabel, Object.assign({}, rest, {
63
+ ref: function ref(_ref) {
64
+ field && formItemsRef.current.set(field.toString(), _ref);
65
+ },
62
66
  required: showRequired,
63
67
  // @ts-ignore
64
68
  formMessage: /*#__PURE__*/React__default["default"].createElement(FormMessage.FormMessage, {
@@ -17,6 +17,7 @@ var _regeneratorRuntime = require('@babel/runtime/regenerator');
17
17
  var tslib = require('tslib');
18
18
  var index = require('./utils/index.js');
19
19
  var React = require('react');
20
+ var scrollIntoView = require('scroll-into-view-if-needed');
20
21
  var useLatest = require('@hi-ui/use-latest');
21
22
  var typeAssertion = require('@hi-ui/type-assertion');
22
23
  var funcUtils = require('@hi-ui/func-utils');
@@ -29,6 +30,7 @@ function _interopDefaultCompat(e) {
29
30
  }
30
31
  var _regeneratorRuntime__default = /*#__PURE__*/_interopDefaultCompat(_regeneratorRuntime);
31
32
  var React__default = /*#__PURE__*/_interopDefaultCompat(React);
33
+ var scrollIntoView__default = /*#__PURE__*/_interopDefaultCompat(scrollIntoView);
32
34
  var EMPTY_RULES = {};
33
35
  var EMPTY_ERRORS = {};
34
36
  var EMPTY_TOUCHED = {};
@@ -51,7 +53,8 @@ var useForm = function useForm(_a) {
51
53
  validateAfterTouched = _a$validateAfterTouch === void 0 ? false : _a$validateAfterTouch,
52
54
  _a$validateTrigger = _a.validateTrigger,
53
55
  validateTriggerProp = _a$validateTrigger === void 0 ? DEFAULT_VALIDATE_TRIGGER : _a$validateTrigger,
54
- rest = tslib.__rest(_a, ["initialValues", "initialErrors", "initialTouched", "lazyValidate", "onValuesChange", "onReset", "onSubmit", "rules", "validateAfterTouched", "validateTrigger"]);
56
+ scrollToFirstError = _a.scrollToFirstError,
57
+ rest = tslib.__rest(_a, ["initialValues", "initialErrors", "initialTouched", "lazyValidate", "onValuesChange", "onReset", "onSubmit", "rules", "validateAfterTouched", "validateTrigger", "scrollToFirstError"]);
55
58
  /**
56
59
  * 处理校验触发器,保证 memo 依赖的是数组每个项,避免无效重渲染
57
60
  */
@@ -60,6 +63,10 @@ var useForm = function useForm(_a) {
60
63
  var validateTriggersMemo = React.useMemo(function () {
61
64
  return validateTrigger;
62
65
  }, validateTrigger);
66
+ var formItemsMp = React.useMemo(function () {
67
+ return new Map();
68
+ }, []);
69
+ var formItemsRef = React.useRef(formItemsMp);
63
70
  /**
64
71
  * 收集 Field 的校验器注册表
65
72
  */
@@ -85,6 +92,18 @@ var useForm = function useForm(_a) {
85
92
  // const getFieldNames = useCallback(() => Object.keys(formStateRef.current.values as any), [
86
93
  // formStateRef,
87
94
  // ])
95
+ var getFormItemNode = React.useCallback(function (fieldName) {
96
+ return formItemsRef.current.get(fieldName.toString());
97
+ }, []);
98
+ var scrollToNode = React.useCallback(function (fieldName, options) {
99
+ if (options === void 0) {
100
+ options = {};
101
+ }
102
+ scrollIntoView__default["default"](getFormItemNode(fieldName), Object.assign({
103
+ scrollMode: 'if-needed',
104
+ block: 'nearest'
105
+ }, options));
106
+ }, [getFormItemNode]);
88
107
  var getFieldValue = React.useCallback(function (fieldName) {
89
108
  return objectUtils.getNested(formStateRef.current.values, fieldName);
90
109
  }, [formStateRef]);
@@ -183,6 +202,7 @@ var useForm = function useForm(_a) {
183
202
  type: 'SET_VALIDATING',
184
203
  payload: true
185
204
  });
205
+ var firstError = false;
186
206
  return Promise.all(fieldNames.map(function (fieldName) {
187
207
  var value = getFieldValue(fieldName);
188
208
  var fieldValidation = getValidation(fieldName);
@@ -191,6 +211,10 @@ var useForm = function useForm(_a) {
191
211
  }
192
212
  // catch 错误,保证检验所有表单项
193
213
  return fieldValidation.validate(value)["catch"](function (error) {
214
+ if (scrollToFirstError && !firstError) {
215
+ firstError = true;
216
+ scrollToNode(fieldName, _typeof(scrollToFirstError) === 'object' ? scrollToFirstError : {});
217
+ }
194
218
  // 第一个出错,即退出校验
195
219
  if (lazyValidate) {
196
220
  throw error;
@@ -257,7 +281,7 @@ var useForm = function useForm(_a) {
257
281
  });
258
282
  return combinedError;
259
283
  });
260
- }, [getRegisteredKeys, getFieldValue, getValidation, lazyValidate]);
284
+ }, [getFieldValue, getRegisteredKeys, getValidation, lazyValidate, scrollToFirstError, scrollToNode]);
261
285
  /**
262
286
  * 控件值更新策略
263
287
  */
@@ -542,7 +566,8 @@ var useForm = function useForm(_a) {
542
566
  validateValue: validateField,
543
567
  getFieldsValue: getFieldsValue,
544
568
  setFieldsValue: setFieldsValue,
545
- getFieldsError: getFieldsError
569
+ getFieldsError: getFieldsError,
570
+ formItemsRef: formItemsRef
546
571
  });
547
572
  };
548
573
  function formReducer(state, action) {
@@ -31,7 +31,8 @@ var FormItem = function FormItem(_a) {
31
31
  rest = __rest(_a, ["className", "children", "field", "valueType", "rules", "valuePropName", "valueChangeFuncPropName", "valueDispatchTransform", "valueConnectTransform", "validateTrigger", "render"]);
32
32
  var _useFormContext = useFormContext(),
33
33
  prefixCls = _useFormContext.prefixCls,
34
- showRequiredOnValidateRequired = _useFormContext.showRequiredOnValidateRequired;
34
+ showRequiredOnValidateRequired = _useFormContext.showRequiredOnValidateRequired,
35
+ formItemsRef = _useFormContext.formItemsRef;
35
36
  var fieldRules = useFiledRules({
36
37
  field: field,
37
38
  rules: rules,
@@ -47,6 +48,9 @@ var FormItem = function FormItem(_a) {
47
48
  return required;
48
49
  }, [required, showRequiredOnValidateRequired, fieldRules]);
49
50
  return /*#__PURE__*/React.createElement(FormLabel, Object.assign({}, rest, {
51
+ ref: function ref(_ref) {
52
+ field && formItemsRef.current.set(field.toString(), _ref);
53
+ },
50
54
  required: showRequired,
51
55
  // @ts-ignore
52
56
  formMessage: /*#__PURE__*/React.createElement(FormMessage, {
@@ -1,3 +1,4 @@
1
+ import _typeof from "@babel/runtime/helpers/esm/typeof";
1
2
  /** @LICENSE
2
3
  * @hi-ui/form
3
4
  * https://github.com/XiaoMi/hiui/tree/master/packages/ui/form#readme
@@ -10,7 +11,8 @@
10
11
  import _regeneratorRuntime from '@babel/runtime/regenerator';
11
12
  import { __rest, __awaiter } from 'tslib';
12
13
  import { stringify, parse, mergeValues, isValidField } from './utils/index.js';
13
- import React, { useMemo, useReducer, useCallback, useRef } from 'react';
14
+ import React, { useMemo, useRef, useReducer, useCallback } from 'react';
15
+ import scrollIntoView from 'scroll-into-view-if-needed';
14
16
  import { useLatestRef, useLatestCallback } from '@hi-ui/use-latest';
15
17
  import { isArray, isFunction, isObjectLike } from '@hi-ui/type-assertion';
16
18
  import { callAllFuncs } from '@hi-ui/func-utils';
@@ -38,7 +40,8 @@ var useForm = function useForm(_a) {
38
40
  validateAfterTouched = _a$validateAfterTouch === void 0 ? false : _a$validateAfterTouch,
39
41
  _a$validateTrigger = _a.validateTrigger,
40
42
  validateTriggerProp = _a$validateTrigger === void 0 ? DEFAULT_VALIDATE_TRIGGER : _a$validateTrigger,
41
- rest = __rest(_a, ["initialValues", "initialErrors", "initialTouched", "lazyValidate", "onValuesChange", "onReset", "onSubmit", "rules", "validateAfterTouched", "validateTrigger"]);
43
+ scrollToFirstError = _a.scrollToFirstError,
44
+ rest = __rest(_a, ["initialValues", "initialErrors", "initialTouched", "lazyValidate", "onValuesChange", "onReset", "onSubmit", "rules", "validateAfterTouched", "validateTrigger", "scrollToFirstError"]);
42
45
  /**
43
46
  * 处理校验触发器,保证 memo 依赖的是数组每个项,避免无效重渲染
44
47
  */
@@ -47,6 +50,10 @@ var useForm = function useForm(_a) {
47
50
  var validateTriggersMemo = useMemo(function () {
48
51
  return validateTrigger;
49
52
  }, validateTrigger);
53
+ var formItemsMp = useMemo(function () {
54
+ return new Map();
55
+ }, []);
56
+ var formItemsRef = useRef(formItemsMp);
50
57
  /**
51
58
  * 收集 Field 的校验器注册表
52
59
  */
@@ -72,6 +79,18 @@ var useForm = function useForm(_a) {
72
79
  // const getFieldNames = useCallback(() => Object.keys(formStateRef.current.values as any), [
73
80
  // formStateRef,
74
81
  // ])
82
+ var getFormItemNode = useCallback(function (fieldName) {
83
+ return formItemsRef.current.get(fieldName.toString());
84
+ }, []);
85
+ var scrollToNode = useCallback(function (fieldName, options) {
86
+ if (options === void 0) {
87
+ options = {};
88
+ }
89
+ scrollIntoView(getFormItemNode(fieldName), Object.assign({
90
+ scrollMode: 'if-needed',
91
+ block: 'nearest'
92
+ }, options));
93
+ }, [getFormItemNode]);
75
94
  var getFieldValue = useCallback(function (fieldName) {
76
95
  return getNested(formStateRef.current.values, fieldName);
77
96
  }, [formStateRef]);
@@ -170,6 +189,7 @@ var useForm = function useForm(_a) {
170
189
  type: 'SET_VALIDATING',
171
190
  payload: true
172
191
  });
192
+ var firstError = false;
173
193
  return Promise.all(fieldNames.map(function (fieldName) {
174
194
  var value = getFieldValue(fieldName);
175
195
  var fieldValidation = getValidation(fieldName);
@@ -178,6 +198,10 @@ var useForm = function useForm(_a) {
178
198
  }
179
199
  // catch 错误,保证检验所有表单项
180
200
  return fieldValidation.validate(value)["catch"](function (error) {
201
+ if (scrollToFirstError && !firstError) {
202
+ firstError = true;
203
+ scrollToNode(fieldName, _typeof(scrollToFirstError) === 'object' ? scrollToFirstError : {});
204
+ }
181
205
  // 第一个出错,即退出校验
182
206
  if (lazyValidate) {
183
207
  throw error;
@@ -244,7 +268,7 @@ var useForm = function useForm(_a) {
244
268
  });
245
269
  return combinedError;
246
270
  });
247
- }, [getRegisteredKeys, getFieldValue, getValidation, lazyValidate]);
271
+ }, [getFieldValue, getRegisteredKeys, getValidation, lazyValidate, scrollToFirstError, scrollToNode]);
248
272
  /**
249
273
  * 控件值更新策略
250
274
  */
@@ -529,7 +553,8 @@ var useForm = function useForm(_a) {
529
553
  validateValue: validateField,
530
554
  getFieldsValue: getFieldsValue,
531
555
  setFieldsValue: setFieldsValue,
532
- getFieldsError: getFieldsError
556
+ getFieldsError: getFieldsError,
557
+ formItemsRef: formItemsRef
533
558
  });
534
559
  };
535
560
  function formReducer(state, action) {
@@ -1,5 +1,6 @@
1
- import React from 'react';
1
+ import React, { MutableRefObject } from 'react';
2
2
  import { UseFormReturn } from './use-form';
3
+ import { FormFieldPath } from './types';
3
4
  export interface FormContextProps extends UseFormReturn {
4
5
  labelWidth: React.ReactText;
5
6
  labelPlacement: 'left' | 'right' | 'top';
@@ -8,6 +9,7 @@ export interface FormContextProps extends UseFormReturn {
8
9
  showRequiredOnValidateRequired: boolean;
9
10
  showValidateMessage: boolean;
10
11
  prefixCls: string;
12
+ formItemsRef: MutableRefObject<Map<FormFieldPath, HTMLDivElement | null>>;
11
13
  }
12
14
  export declare const FormProvider: React.Provider<Omit<FormContextProps, "rootProps"> | null>;
13
15
  export declare const useFormContext: () => Omit<FormContextProps, "rootProps">;
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
+ import { StandardBehaviorOptions as ScrollOptions } from 'scroll-into-view-if-needed';
2
3
  import { FormState, FormFieldCollection, FormErrors, FormRuleModel, FormFieldPath, FormErrorMessage, FormSetState } from './types';
3
- export declare const useForm: <Values = Record<string, any>>({ initialValues, initialErrors, initialTouched, lazyValidate, onValuesChange, onReset, onSubmit, rules, validateAfterTouched, validateTrigger: validateTriggerProp, ...rest }: UseFormProps<Values>) => {
4
+ export declare const useForm: <Values = Record<string, any>>({ initialValues, initialErrors, initialTouched, lazyValidate, onValuesChange, onReset, onSubmit, rules, validateAfterTouched, validateTrigger: validateTriggerProp, scrollToFirstError, ...rest }: UseFormProps<Values>) => {
4
5
  setFormState: (stateOrFunc: FormSetState<Values>) => void;
5
6
  setFieldValue: (field: FormFieldPath, value: unknown, shouldValidate?: boolean | undefined) => void;
6
7
  setFieldError: (field: FormFieldPath, errorMessage: FormErrorMessage | undefined) => void;
@@ -24,6 +25,7 @@ export declare const useForm: <Values = Record<string, any>>({ initialValues, in
24
25
  getFieldsValue: () => any;
25
26
  setFieldsValue: (fieldsState: Record<string, any> | Function) => void;
26
27
  getFieldsError: () => any;
28
+ formItemsRef: React.MutableRefObject<Map<any, any>>;
27
29
  values: any;
28
30
  errors: FormErrors<unknown>;
29
31
  touched: import("./types").FormTouched<unknown>;
@@ -74,5 +76,9 @@ export interface UseFormProps<T = Record<string, any>> {
74
76
  * 重置时回调
75
77
  */
76
78
  onReset?: (values: T) => void | Promise<any>;
79
+ /**
80
+ * 提交失败自动滚动到第一个错误字段,配置参考:https://github.com/scroll-into-view/scroll-into-view-if-needed?tab=readme-ov-file#options
81
+ */
82
+ scrollToFirstError?: boolean | ScrollOptions;
77
83
  }
78
84
  export declare type UseFormReturn = ReturnType<typeof useForm>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hi-ui/form",
3
- "version": "4.2.2",
3
+ "version": "4.3.0",
4
4
  "description": "A sub-package for @hi-ui/hiui.",
5
5
  "keywords": [],
6
6
  "author": "HiUI <mi-hiui@xiaomi.com>",
@@ -52,7 +52,8 @@
52
52
  "@hi-ui/object-utils": "^4.0.4",
53
53
  "@hi-ui/type-assertion": "^4.0.4",
54
54
  "@hi-ui/use-latest": "^4.0.4",
55
- "async-validator": "^4.0.7"
55
+ "async-validator": "^4.0.7",
56
+ "scroll-into-view-if-needed": "^3.1.0"
56
57
  },
57
58
  "peerDependencies": {
58
59
  "@hi-ui/core": ">=4.0.8",