diff --git a/src/packages/form/doc.en-US.md b/src/packages/form/doc.en-US.md index 53c3d2cedd..02ac1eb0d3 100644 --- a/src/packages/form/doc.en-US.md +++ b/src/packages/form/doc.en-US.md @@ -80,6 +80,7 @@ import { Form } from '@nutui/nutui-react' | name | form name | `any` | `-` | | labelPosition | The position of the form item label | `top` \| `left` \| `right` | `right` | | starPosition | The red star position of the required form item label | `left` \| `right` | `left` | +| validateTrigger | uniformly set the timing for fields to trigger validation | `string` \| `string[]` | `onChange` | | onFinish | Triggered after verification is successful | `(values: any) => void` | `-` | | onFinishFailed | Triggered when any form item fails validation | `(values: any, errorFields: any) => void` | `-` | @@ -125,7 +126,8 @@ Form.useForm() creates a Form instance, which is used to manage all data states. | --- | --- | --- | | getFieldValue | Get the value of the corresponding field name | `(name: NamePath) => any` | | getFieldsValue | Get values by a set of field names. Return according to the corresponding structure. Default return mounted field value, but you can use getFieldsValue(true) to get all values | `(name: NamePath \| boolean) => any` | -| setFieldsValue | set field values | `(values) => void` | +| setFieldsValue | Set the value of the form (the value will be passed directly to the form store. If you do not want the object passed in to be modified, please copy it and pass it in) | `(values) => void` | +| setFieldValue | Set the value of the corresponding field name | `(name: NamePath, value: T) => void` | | resetFields | Reset form prompt state | `() => void` | | submit | method to submit a form for validation | `Promise` | diff --git a/src/packages/form/doc.md b/src/packages/form/doc.md index ad2b2aadd3..96231aede1 100644 --- a/src/packages/form/doc.md +++ b/src/packages/form/doc.md @@ -78,6 +78,7 @@ import { Form } from '@nutui/nutui-react' | name | 表单名称 | `any` | `-` | | labelPosition | 表单项 label 的位置 | `top` \| `left` \| `right` | `right` | | starPosition | 必填表单项 label 的红色星标位置 | `left` \| `right` | `left` | +| validateTrigger | 统一设置字段触发验证的时机 | `string` \| `string[]` | `onChange` | | onFinish | 校验成功后触发 | `(values: any) => void` | `-` | | onFinishFailed | 任一表单项被校验失败后触发 | `(values: any, errorFields: any) => void` | `-` | @@ -124,7 +125,8 @@ Form.useForm()创建 Form 实例,用于管理所有数据状态。 | --- | --- | --- | | getFieldValue | 获取对应字段名的值 | `(name: NamePath) => any` | | getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 getFieldsValue(true) 时返回所有值 | `(name: NamePath \| boolean) => any` | -| setFieldsValue | 设置表单的值 | `(values) => void` | +| setFieldsValue | 设置表单的值(该值将直接传入 form store 中。如果你不希望传入对象被修改,请克隆后传入) | `(values) => void` | +| setFieldValue | 设置对应字段名的值 | `(name: NamePath, value: T) => void` | | resetFields | 重置表单提示状态 | `() => void` | | submit | 提交表单进行校验的方法 | `Promise` | diff --git a/src/packages/form/doc.taro.md b/src/packages/form/doc.taro.md index c4cec28d2e..6612146ccc 100644 --- a/src/packages/form/doc.taro.md +++ b/src/packages/form/doc.taro.md @@ -78,6 +78,7 @@ import { Form } from '@nutui/nutui-react-taro' | name | 表单名称 | `any` | `-` | | labelPosition | 表单项 label 的位置 | \`\`'top' | 'left'\` | \`'right'\`\` | | starPosition | 必填表单项 label 的红色星标位置 | `left` \| `right` | `left` | +| validateTrigger | 统一设置字段触发验证的时机 | `string` \| `string[]` | `onChange` | | onFinish | 校验成功后触发 | `(values: any) => void` | `-` | | onFinishFailed | 任一表单项被校验失败后触发 | `(values: any, errorFields: any) => void` | `-` | @@ -124,7 +125,8 @@ Form.useForm()创建 Form 实例,用于管理所有数据状态。 | --- | --- | --- | | getFieldValue | 获取对应字段名的值 | `(name: NamePath) => any` | | getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 getFieldsValue(true) 时返回所有值 | `(name: NamePath \| boolean) => any` | -| setFieldsValue | 设置表单的值 | `(values) => void` | +| setFieldsValue | 设置表单的值(该值将直接传入 form store 中。如果你不希望传入对象被修改,请克隆后传入) | `(values) => void` | +| setFieldValue | 设置对应字段名的值 | `(name: NamePath, value: T) => void` | | resetFields | 重置表单提示状态 | `() => void` | | submit | 提交表单进行校验的方法 | `Promise` | diff --git a/src/packages/form/doc.zh-TW.md b/src/packages/form/doc.zh-TW.md index 741f7a0620..330b645132 100644 --- a/src/packages/form/doc.zh-TW.md +++ b/src/packages/form/doc.zh-TW.md @@ -79,6 +79,7 @@ import { Form } from '@nutui/nutui-react' | label | 标签名 | `ReactNode` | `-` | | labelPosition | 錶單項 label 的位置 | `top` \| `left` \| `right` | `right` | | starPosition | 必填錶單項 label 的紅色星標位置 | `left` \| `right` | `left` | +| validateTrigger | 統一設定字段觸發驗證的時機 | `string` \| `string[]` | `onChange` | | onFinish | 校驗成功後觸發 | `(values: any) => void` | `-` | | onFinishFailed | 任一錶單項被校驗失敗後觸發 | `(values: any, errorFields: any) => void` | `-` | @@ -124,7 +125,8 @@ Form.useForm()創建 Form 實例,用於管理所有數據狀態。 | --- | --- | --- | | getFieldValue | 獲取對應字段名的值 | `(name: NamePath) => any` | | getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 getFieldsValue(true) 时返回所有值 | `(name: NamePath \| boolean) => any` | -| setFieldsValue | 設置錶單的值 | `(values) => void` | +| setFieldsValue | 設定表單的值(該值將直接傳入 form store 中。如果你不希望傳入物件被修改,請複製後傳入) | `(values) => void` | +| setFieldValue | 設定對應欄位名的值 | `(name: NamePath, value: T) => void` | | resetFields | 重置錶單提示狀態 | `() => void` | | submit | 提交錶單進行校驗的方法 | `Promise` | diff --git a/src/packages/form/form.scss b/src/packages/form/form.scss index b4374f71e4..5cabfe1cd1 100644 --- a/src/packages/form/form.scss +++ b/src/packages/form/form.scss @@ -1,71 +1,3 @@ @import '../cellgroup/cellgroup.scss'; @import '../cell/cell.scss'; @import '../formitem/formitem.scss'; - -.form-layout-right .nut-form-item-label { - text-align: right; - padding-right: 24px; - white-space: nowrap; -} - -.form-layout-left .nut-form-item-label { - position: relative; - text-align: left; - padding-left: 12px; - white-space: nowrap; - - .required { - display: block; - line-height: 1.5; - position: absolute; - left: 0.1em; - } -} - -.form-layout-top .nut-form-item { - flex-direction: column; - align-items: flex-start; - white-space: nowrap; -} - -.form-layout-top .nut-form-item-label { - padding-bottom: 4px; - display: block; - padding-right: 24px; -} - -.form-layout-top .nut-form-item-body { - margin-left: 0; - width: 100%; -} - -[dir='rtl'] .form-layout-right .nut-form-item-label, -.nut-rtl .form-layout-right .nut-form-item-label { - text-align: left; - padding-right: 0; - padding-left: 24px; -} - -[dir='rtl'] .form-layout-left .nut-form-item-label, -.nut-rtl .form-layout-left .nut-form-item-label { - text-align: right; - padding-left: 0; - padding-right: 12px; - - .required { - left: auto; - right: 0.1em; - } -} - -[dir='rtl'] .form-layout-top .nut-form-item-label, -.nut-rtl .form-layout-top .nut-form-item-label { - padding-right: 0; - padding-left: 24px; -} - -[dir='rtl'] .form-layout-top .nut-form-item-body, -.nut-rtl .form-layout-top .nut-form-item-body { - margin-left: 0; - margin-right: 0; -} diff --git a/src/packages/form/form.taro.tsx b/src/packages/form/form.taro.tsx index 06c2dcedc6..eb71e19d76 100644 --- a/src/packages/form/form.taro.tsx +++ b/src/packages/form/form.taro.tsx @@ -1,4 +1,5 @@ import React, { ReactNode } from 'react' +import { Form as TForm } from '@tarojs/components' import classNames from 'classnames' import { Context } from './context' import { SECRET, useForm } from './useform.taro' @@ -11,7 +12,9 @@ export interface FormProps extends BasicComponent { initialValues: any name: string form: any + disabled: boolean divider: boolean + validateTrigger: string | string[] | false labelPosition: 'top' | 'left' | 'right' starPosition: 'left' | 'right' onFinish: (values: any) => void @@ -22,7 +25,9 @@ const defaultProps = { ...ComponentDefaults, labelPosition: 'right', starPosition: 'left', + disabled: false, divider: false, + validateTrigger: 'onChange', onFinish: (values) => {}, onFinishFailed: (values, errorFields) => {}, } as FormProps @@ -43,8 +48,10 @@ export const Form = React.forwardRef>( children, initialValues, divider, + disabled, onFinish, onFinishFailed, + validateTrigger, labelPosition, starPosition, form, @@ -77,7 +84,7 @@ export const Form = React.forwardRef>( } return ( -
>( }} > - {children} + + {children} + {footer ? ( {footer} ) : null} -
+ ) } ) diff --git a/src/packages/form/form.tsx b/src/packages/form/form.tsx index caf6331332..5c4a9b83d5 100644 --- a/src/packages/form/form.tsx +++ b/src/packages/form/form.tsx @@ -11,7 +11,9 @@ export interface FormProps extends BasicComponent { initialValues: any name: string form: any + disabled: boolean divider: boolean + validateTrigger: string | string[] | false labelPosition: 'top' | 'left' | 'right' starPosition: 'left' | 'right' onFinish: (values: any) => void @@ -22,7 +24,9 @@ const defaultProps = { ...ComponentDefaults, labelPosition: 'right', starPosition: 'left', + disabled: false, divider: false, + validateTrigger: 'onChange', onFinish: (values) => {}, onFinishFailed: (values, errorFields) => {}, } as FormProps @@ -43,8 +47,10 @@ export const Form = React.forwardRef>( children, initialValues, divider, + disabled, onFinish, onFinishFailed, + validateTrigger, labelPosition, starPosition, form, @@ -96,7 +102,11 @@ export const Form = React.forwardRef>( }} > - {children} + + {children} + {footer ? ( {footer} ) : null} diff --git a/src/packages/form/types.ts b/src/packages/form/types.ts index 5b474c0dbb..e0350f8809 100644 --- a/src/packages/form/types.ts +++ b/src/packages/form/types.ts @@ -1,5 +1,6 @@ export interface FormItemRuleWithoutValidator { [key: string]: any + regex?: RegExp required?: boolean message?: string @@ -20,8 +21,9 @@ export interface Store { export interface FormInstance { getFieldValue: (name: NamePath) => StoreValue + setFieldValue: (name: NamePath, value: T) => void getFieldsValue: (nameList: NamePath[] | true) => { [key: NamePath]: any } - setFieldsValue: (value: any) => void + setFieldsValue: (value: Store) => void resetFields: (fields?: NamePath[]) => void submit: () => void getInternal: (secret: string) => any diff --git a/src/packages/form/useform.taro.ts b/src/packages/form/useform.taro.ts index be4ca8b3de..4336d9277f 100644 --- a/src/packages/form/useform.taro.ts +++ b/src/packages/form/useform.taro.ts @@ -1,11 +1,12 @@ import { useRef } from 'react' import Schema from 'async-validator' +import { merge } from '@/utils/merge' import { - Store, Callbacks, - FormInstance, FormFieldEntity, + FormInstance, NamePath, + Store, } from './types' export const SECRET = 'NUT_FORM_INTERNAL' @@ -74,16 +75,21 @@ class FormStore { return fieldsValue } + updateStore(nextStore: Store) { + this.store = nextStore + } + /** * 设置 form 的初始值,之后在 reset 的时候使用 * @param values * @param init */ - setInitialValues = (values: Store, init: boolean) => { + setInitialValues = (initialValues: Store, init: boolean) => { + this.initialValues = initialValues || {} if (init) { - this.initialValues = values - this.store = values + const nextStore = merge(initialValues, this.store) + this.updateStore(nextStore) } } @@ -91,11 +97,9 @@ class FormStore { * 存储组件数据 * @param newStore { [name]: newValue } */ - setFieldsValue = (newStore: any, needValidate = true) => { - this.store = { - ...this.store, - ...newStore, - } + setFieldsValue = (newStore: any) => { + const nextStore = merge(this.store, newStore) + this.updateStore(nextStore) this.fieldEntities.forEach((entity: FormFieldEntity) => { const { name } = entity.props Object.keys(newStore).forEach((key) => { @@ -113,7 +117,13 @@ class FormStore { item.entity.onStoreChange('update') } }) - needValidate && this.validateFields() + } + + setFieldValue = (name: NamePath, value: T) => { + const store = { + [name]: value, + } + this.setFieldsValue(store) } setCallback = (callback: Callbacks) => { @@ -125,6 +135,12 @@ class FormStore { validateEntities = async (entity: FormFieldEntity, errs: any[]) => { const { name, rules = [] } = entity.props + + if (!name) { + console.warn('Form field missing name property') + return + } + const descriptor: any = {} if (rules.length) { // 多条校验规则 @@ -142,7 +158,7 @@ class FormStore { // validator.messages() try { await validator.validate({ [name]: this.store?.[name] }) - } catch ({ errors }: any) { + } catch ({ errors }) { if (errors) { errs.push(...(errors as any[])) this.errors[name] = errors @@ -186,7 +202,8 @@ class FormStore { resetFields = () => { this.errors.length = 0 - this.store = this.initialValues + const nextStore = merge({}, this.initialValues) + this.updateStore(nextStore) this.fieldEntities.forEach((entity: FormFieldEntity) => { entity.onStoreChange('reset') }) @@ -226,6 +243,7 @@ class FormStore { getFieldValue: this.getFieldValue, getFieldsValue: this.getFieldsValue, setFieldsValue: this.setFieldsValue, + setFieldValue: this.setFieldValue, resetFields: this.resetFields, validateFields: this.validateFields, submit: this.submit, @@ -245,5 +263,5 @@ export const useForm = (form?: FormInstance): [FormInstance] => { formRef.current = formStore.getForm() as FormInstance } } - return [formRef.current] + return [formRef.current as FormInstance] } diff --git a/src/packages/form/useform.ts b/src/packages/form/useform.ts index 2b2b413a3b..4336d9277f 100644 --- a/src/packages/form/useform.ts +++ b/src/packages/form/useform.ts @@ -1,11 +1,12 @@ import { useRef } from 'react' import Schema from 'async-validator' +import { merge } from '@/utils/merge' import { - Store, Callbacks, - FormInstance, FormFieldEntity, + FormInstance, NamePath, + Store, } from './types' export const SECRET = 'NUT_FORM_INTERNAL' @@ -74,16 +75,21 @@ class FormStore { return fieldsValue } + updateStore(nextStore: Store) { + this.store = nextStore + } + /** * 设置 form 的初始值,之后在 reset 的时候使用 * @param values * @param init */ - setInitialValues = (values: Store, init: boolean) => { + setInitialValues = (initialValues: Store, init: boolean) => { + this.initialValues = initialValues || {} if (init) { - this.initialValues = values - this.store = values + const nextStore = merge(initialValues, this.store) + this.updateStore(nextStore) } } @@ -91,11 +97,9 @@ class FormStore { * 存储组件数据 * @param newStore { [name]: newValue } */ - setFieldsValue = (newStore: any, needValidate = true) => { - this.store = { - ...this.store, - ...newStore, - } + setFieldsValue = (newStore: any) => { + const nextStore = merge(this.store, newStore) + this.updateStore(nextStore) this.fieldEntities.forEach((entity: FormFieldEntity) => { const { name } = entity.props Object.keys(newStore).forEach((key) => { @@ -113,7 +117,13 @@ class FormStore { item.entity.onStoreChange('update') } }) - needValidate && this.validateFields() + } + + setFieldValue = (name: NamePath, value: T) => { + const store = { + [name]: value, + } + this.setFieldsValue(store) } setCallback = (callback: Callbacks) => { @@ -123,48 +133,61 @@ class FormStore { } } + validateEntities = async (entity: FormFieldEntity, errs: any[]) => { + const { name, rules = [] } = entity.props + + if (!name) { + console.warn('Form field missing name property') + return + } + + const descriptor: any = {} + if (rules.length) { + // 多条校验规则 + if (rules.length > 1) { + descriptor[name] = [] + rules.forEach((v: any) => { + descriptor[name].push(v) + }) + } else { + descriptor[name] = rules[0] + } + } + const validator = new Schema(descriptor) + // 此处合并无值message 没有意义? + // validator.messages() + try { + await validator.validate({ [name]: this.store?.[name] }) + } catch ({ errors }) { + if (errors) { + errs.push(...(errors as any[])) + this.errors[name] = errors + } + } finally { + if (!errs || errs.length === 0) { + this.errors[name] = [] + } + } + + entity.onStoreChange('validate') + } + validateFields = async (nameList?: NamePath[]) => { - let filterEntitys = [] - const errs = [] + let filterEntities = [] this.errors.length = 0 if (!nameList || nameList.length === 0) { - filterEntitys = this.fieldEntities + filterEntities = this.fieldEntities } else { - filterEntitys = this.fieldEntities.filter(({ props: { name } }) => + filterEntities = this.fieldEntities.filter(({ props: { name } }) => nameList.includes(name) ) } - for (const entity of filterEntitys) { - const { name, rules = [] } = entity.props - const descriptor: any = {} - if (rules.length) { - // 多条校验规则 - if (rules.length > 1) { - descriptor[name] = [] - rules.forEach((v: any) => { - descriptor[name].push(v) - }) - } else { - descriptor[name] = rules[0] - } - } - const validator = new Schema(descriptor) - // 此处合并无值message 没有意义? - // validator.messages() - try { - await validator.validate({ [name]: this.store?.[name] }) - } catch ({ errors }: any) { - if (errors) { - errs.push(...(errors as any[])) - this.errors[name] = errors - } - } finally { - if (!errs || errs.length === 0) { - this.errors[name] = [] - } - } - entity.onStoreChange('validate') - } + const errs: any[] = [] + await Promise.all( + filterEntities.map(async (entity) => { + await this.validateEntities(entity, errs) + }) + ) return errs } @@ -179,7 +202,8 @@ class FormStore { resetFields = () => { this.errors.length = 0 - this.store = this.initialValues + const nextStore = merge({}, this.initialValues) + this.updateStore(nextStore) this.fieldEntities.forEach((entity: FormFieldEntity) => { entity.onStoreChange('reset') }) @@ -219,6 +243,7 @@ class FormStore { getFieldValue: this.getFieldValue, getFieldsValue: this.getFieldsValue, setFieldsValue: this.setFieldsValue, + setFieldValue: this.setFieldValue, resetFields: this.resetFields, validateFields: this.validateFields, submit: this.submit, @@ -238,5 +263,5 @@ export const useForm = (form?: FormInstance): [FormInstance] => { formRef.current = formStore.getForm() as FormInstance } } - return [formRef.current] + return [formRef.current as FormInstance] } diff --git a/src/packages/formitem/formitem.scss b/src/packages/formitem/formitem.scss index 77678cc0c7..dcd497071b 100644 --- a/src/packages/formitem/formitem.scss +++ b/src/packages/formitem/formitem.scss @@ -1,38 +1,34 @@ .nut-form-item { display: flex; - &.error { - &.line { - &::before { - border-bottom: 1px solid $form-item-error-line-color; - transform: scaleX(1); - transition: transform 200ms cubic-bezier(0, 0, 0.2, 1) 0ms; - } - } + &-disabled { + opacity: 0.4; + pointer-events: none; } &-label { + display: flex; + flex-direction: row; font-size: $form-item-label-font-size; font-weight: normal; width: $form-item-label-width; margin-right: $form-item-label-margin-right; - flex: none !important; - display: inline-block !important; + flex: 0 0 auto; word-wrap: break-word; text-align: $form-item-label-text-align; - - .required { - &::before { - content: '*'; - color: $form-item-required-color; - margin-right: $form-item-required-margin-right; - } - } } + &-label-required { + color: $form-item-required-color; + margin-right: $form-item-required-margin-right; + } + .nut-form-item-labeltxt { + font-size: 12px; + height: 10px; + } &-body { flex: 1; - display: flex !important; + display: flex; flex-direction: column; &-slots { @@ -59,8 +55,7 @@ } .nut-textarea { - padding: 0 !important; - + padding: 0; .nut-textarea-textarea { font: inherit; text-align: $form-item-body-input-text-align; @@ -75,6 +70,7 @@ } } } + [dir='rtl'] .nut-form-item, .nut-rtl .nut-form-item { &-label { @@ -108,3 +104,72 @@ text-align: right; } } + +/* position */ + +.nut-form-item-label-right { + justify-content: flex-end; + padding-right: 24px; + white-space: nowrap; +} + +.nut-form-item-label-left { + position: relative; + padding-left: 12px; + white-space: nowrap; +} + +.nut-form-item-label-left-required { + display: block; + line-height: 1.5; + position: absolute; + left: 0.1em; +} + +.nut-form-item-top { + flex-direction: column; + align-items: flex-start; + white-space: nowrap; +} + +.nut-form-item-label-top { + display: block; + padding-bottom: 4px; + padding-right: 24px; +} + +.nut-form-item-body-top { + margin-left: 0; + width: 100%; +} + +[dir='rtl'] .form-layout-right .nut-form-item-label, +.nut-rtl .form-layout-right .nut-form-item-label { + text-align: left; + padding-right: 0; + padding-left: 24px; +} + +[dir='rtl'] .form-layout-left .nut-form-item-label, +.nut-rtl .form-layout-left .nut-form-item-label { + text-align: right; + padding-left: 0; + padding-right: 12px; + + .required { + left: auto; + right: 0.1em; + } +} + +[dir='rtl'] .form-layout-top .nut-form-item-label, +.nut-rtl .form-layout-top .nut-form-item-label { + padding-right: 0; + padding-left: 24px; +} + +[dir='rtl'] .form-layout-top .nut-form-item-body, +.nut-rtl .form-layout-top .nut-form-item-body { + margin-left: 0; + margin-right: 0; +} diff --git a/src/packages/formitem/formitem.taro.tsx b/src/packages/formitem/formitem.taro.tsx index 4d17aea4f0..12f29e9f30 100644 --- a/src/packages/formitem/formitem.taro.tsx +++ b/src/packages/formitem/formitem.taro.tsx @@ -1,9 +1,11 @@ import React, { ReactNode } from 'react' +import { Text, View } from '@tarojs/components' import { BaseFormField } from './types' import { Context } from '../form/context' import Cell from '@/packages/cell/index.taro' import { BasicComponent, ComponentDefaults } from '@/utils/typings' import { isForwardRefComponent } from '@/utils/is-forward-ref-component' +import { toArray } from '@/utils/to-array' import { SECRET } from '@/packages/form/useform.taro' type TextAlign = @@ -34,7 +36,7 @@ export interface FormItemProps shouldUpdate: boolean noStyle: boolean children: ReactNode | ((obj: any) => React.ReactNode) - align?: 'flex-start' | 'center' | 'flex-end' + align: 'flex-start' | 'center' | 'flex-end' } const defaultProps = { @@ -44,7 +46,6 @@ const defaultProps = { label: '', rules: [{ required: false, message: '' }], errorMessageAlign: 'left', - validateTrigger: 'onChange', shouldUpdate: false, noStyle: false, } as FormItemProps @@ -57,7 +58,7 @@ export class FormItem extends React.Component< static contextType: any = Context - declare context: React.ContextType + context!: React.ContextType private cancelRegister: any @@ -75,7 +76,8 @@ export class FormItem extends React.Component< componentDidMount() { // Form设置initialValues时的处理 - const { store = {}, setInitialValues } = this.context.getInternal(SECRET) + const { store = {}, setInitialValues } = + this.context.formInstance.getInternal(SECRET) if ( this.props.initialValue && this.props.name && @@ -87,7 +89,8 @@ export class FormItem extends React.Component< ) } // 注册组件实例到FormStore - const { registerField, registerUpdate } = this.context.getInternal(SECRET) + const { registerField, registerUpdate } = + this.context.formInstance.getInternal(SECRET) this.cancelRegister = registerField(this) // 这里需要增加事件监听,因为此实现属于依赖触发 this.eventOff = registerUpdate(this, this.props.shouldUpdate) @@ -104,17 +107,23 @@ export class FormItem extends React.Component< // children添加value属性和onChange事件 getControlled = (children: React.ReactElement) => { - const { setFieldsValue, getFieldValue } = this.context - const { dispatch } = this.context.getInternal(SECRET) + const { setFieldsValue, getFieldValue } = this.context.formInstance + const { dispatch } = this.context.formInstance.getInternal(SECRET) const { name = '' } = this.props if (children?.props?.defaultValue) { - console.warn('通过 initialValue 设置初始值') + if (process.env.NODE_ENV !== 'production') { + console.warn( + '[NutUI] FormItem:', + '请通过 initialValue 设置初始值,而不是 defaultValue' + ) + } } const fieldValue = getFieldValue(name) const controlled = { ...children.props, + className: children.props.className, [this.props.valuePropName || 'value']: fieldValue !== undefined ? fieldValue : this.props.initialValue, [this.props.trigger || 'onChange']: (...args: any) => { @@ -129,30 +138,27 @@ export class FormItem extends React.Component< if (this.props.getValueFromEvent) { next = this.props.getValueFromEvent(...args) } - setFieldsValue({ [name]: next }, false) + setFieldsValue({ [name]: next }) }, } const { validateTrigger } = this.props - let validateTriggers: string[] = [this.props.trigger || 'onChange'] - if (validateTrigger) { - validateTriggers = - typeof validateTrigger === 'string' - ? [validateTrigger] - : [...validateTrigger] - validateTriggers.forEach((trigger) => { - const originTrigger = controlled[trigger] - controlled[trigger] = (...args: any) => { - if (originTrigger) { - originTrigger(...args) - } - if (this.props.rules && this.props.rules.length) { - dispatch({ - name: this.props.name, - }) - } + const mergedValidateTrigger = + validateTrigger || this.context.validateTrigger + + const validateTriggers: string[] = toArray(mergedValidateTrigger) + validateTriggers.forEach((trigger) => { + const originTrigger = controlled[trigger] + controlled[trigger] = (...args: any) => { + if (originTrigger) { + originTrigger(...args) } - }) - } + if (this.props.rules && this.props.rules.length) { + dispatch({ + name: this.props.name, + }) + } + } + }) if (isForwardRefComponent(children)) { controlled.ref = (componentInstance: any) => { @@ -180,13 +186,20 @@ export class FormItem extends React.Component< onStoreChange = (type?: string) => { if (type === 'reset') { - this.context.errors[this.props.name as string] = [] + this.context.formInstance.errors[this.props.name as string] = [] this.refresh() } else { this.forceUpdate() } } + getClassNameWithDirection(className: string) { + if (className && this.context.labelPosition) { + return `${className} ${className}-${this.context.labelPosition}` + } + return className + } + renderLayout = (childNode: React.ReactNode) => { const { label, @@ -195,44 +208,48 @@ export class FormItem extends React.Component< rules, className, style, - align, errorMessageAlign, + align, } = { ...defaultProps, ...this.props, } const requiredInRules = rules?.some((rule: any) => rule.required) - const item = name ? this.context.errors[name] : [] + const item = name ? this.context.formInstance.errors[name] : [] - const { starPosition } = this.context + const { starPosition } = this.context.formInstance const renderStar = (required || requiredInRules) && ( - + * ) const renderLabel = ( <> {starPosition === 'left' ? renderStar : null} - {label} + {label} {starPosition === 'right' ? renderStar : null} ) return ( - this.props.onClick && this.props.onClick(e, this.componentRef) + this.props.onClick && this.props.onClick(e as any, this.componentRef) } > {label ? ( -
+ {renderLabel} -
+ ) : null} -
-
{childNode}
-
+ {childNode} + {item?.[0]?.message} -
-
+ +
) } @@ -256,13 +273,19 @@ export class FormItem extends React.Component< this.getControlled(child as React.ReactElement) ) } else { - returnChildNode = child(this.context) + returnChildNode = child(this.context.formInstance) } + return ( - {this.props.noStyle - ? returnChildNode - : this.renderLayout(returnChildNode)} + + {this.props.noStyle + ? returnChildNode + : this.renderLayout(returnChildNode)} + ) } diff --git a/src/packages/formitem/formitem.tsx b/src/packages/formitem/formitem.tsx index 8ccd66f007..f67bd269e6 100644 --- a/src/packages/formitem/formitem.tsx +++ b/src/packages/formitem/formitem.tsx @@ -4,6 +4,7 @@ import { Context } from '../form/context' import Cell from '@/packages/cell' import { BasicComponent, ComponentDefaults } from '@/utils/typings' import { isForwardRefComponent } from '@/utils/is-forward-ref-component' +import { toArray } from '@/utils/to-array' import { SECRET } from '@/packages/form/useform' type TextAlign = @@ -34,7 +35,7 @@ export interface FormItemProps shouldUpdate: boolean noStyle: boolean children: ReactNode | ((obj: any) => React.ReactNode) - align?: 'flex-start' | 'center' | 'flex-end' + align: 'flex-start' | 'center' | 'flex-end' } const defaultProps = { @@ -44,7 +45,6 @@ const defaultProps = { label: '', rules: [{ required: false, message: '' }], errorMessageAlign: 'left', - validateTrigger: 'onChange', shouldUpdate: false, noStyle: false, } as FormItemProps @@ -57,7 +57,7 @@ export class FormItem extends React.Component< static contextType: any = Context - declare context: React.ContextType + context!: React.ContextType private cancelRegister: any @@ -75,7 +75,8 @@ export class FormItem extends React.Component< componentDidMount() { // Form设置initialValues时的处理 - const { store = {}, setInitialValues } = this.context.getInternal(SECRET) + const { store = {}, setInitialValues } = + this.context.formInstance.getInternal(SECRET) if ( this.props.initialValue && this.props.name && @@ -87,7 +88,8 @@ export class FormItem extends React.Component< ) } // 注册组件实例到FormStore - const { registerField, registerUpdate } = this.context.getInternal(SECRET) + const { registerField, registerUpdate } = + this.context.formInstance.getInternal(SECRET) this.cancelRegister = registerField(this) // 这里需要增加事件监听,因为此实现属于依赖触发 this.eventOff = registerUpdate(this, this.props.shouldUpdate) @@ -104,16 +106,23 @@ export class FormItem extends React.Component< // children添加value属性和onChange事件 getControlled = (children: React.ReactElement) => { - const { setFieldsValue, getFieldValue } = this.context - const { dispatch } = this.context.getInternal(SECRET) + const { setFieldsValue, getFieldValue } = this.context.formInstance + const { dispatch } = this.context.formInstance.getInternal(SECRET) const { name = '' } = this.props if (children?.props?.defaultValue) { - console.warn('通过 initialValue 设置初始值') + if (process.env.NODE_ENV !== 'production') { + console.warn( + '[NutUI] FormItem:', + '请通过 initialValue 设置初始值,而不是 defaultValue' + ) + } } + const fieldValue = getFieldValue(name) const controlled = { ...children.props, + className: children.props.className, [this.props.valuePropName || 'value']: fieldValue !== undefined ? fieldValue : this.props.initialValue, [this.props.trigger || 'onChange']: (...args: any) => { @@ -128,30 +137,27 @@ export class FormItem extends React.Component< if (this.props.getValueFromEvent) { next = this.props.getValueFromEvent(...args) } - setFieldsValue({ [name]: next }, false) + setFieldsValue({ [name]: next }) }, } const { validateTrigger } = this.props - let validateTriggers: string[] = [this.props.trigger || 'onChange'] - if (validateTrigger) { - validateTriggers = - typeof validateTrigger === 'string' - ? [validateTrigger] - : [...validateTrigger] - validateTriggers.forEach((trigger) => { - const originTrigger = controlled[trigger] - controlled[trigger] = (...args: any) => { - if (originTrigger) { - originTrigger(...args) - } - if (this.props.rules && this.props.rules.length) { - dispatch({ - name: this.props.name, - }) - } + const mergedValidateTrigger = + validateTrigger || this.context.validateTrigger + + const validateTriggers: string[] = toArray(mergedValidateTrigger) + validateTriggers.forEach((trigger) => { + const originTrigger = controlled[trigger] + controlled[trigger] = (...args: any) => { + if (originTrigger) { + originTrigger(...args) } - }) - } + if (this.props.rules && this.props.rules.length) { + dispatch({ + name: this.props.name, + }) + } + } + }) if (isForwardRefComponent(children)) { controlled.ref = (componentInstance: any) => { @@ -179,13 +185,20 @@ export class FormItem extends React.Component< onStoreChange = (type?: string) => { if (type === 'reset') { - this.context.errors[this.props.name as string] = [] + this.context.formInstance.errors[this.props.name as string] = [] this.refresh() } else { this.forceUpdate() } } + getClassNameWithDirection(className: string) { + if (className && this.context.labelPosition) { + return `${className} ${className}-${this.context.labelPosition}` + } + return className + } + renderLayout = (childNode: React.ReactNode) => { const { label, @@ -202,22 +215,22 @@ export class FormItem extends React.Component< } const requiredInRules = rules?.some((rule: any) => rule.required) - const item = name ? this.context.errors[name] : [] + const item = name ? this.context.formInstance.errors[name] : [] - const { starPosition } = this.context + const { starPosition } = this.context.formInstance const renderStar = (required || requiredInRules) && ( - +
*
) const renderLabel = ( <> {starPosition === 'left' ? renderStar : null} - {label} + {label} {starPosition === 'right' ? renderStar : null} ) return ( @@ -225,11 +238,15 @@ export class FormItem extends React.Component< } > {label ? ( -
+
{renderLabel}
) : null} -
+
{childNode}
{item && item.length > 0 && (
- {this.props.noStyle - ? returnChildNode - : this.renderLayout(returnChildNode)} +
+ {this.props.noStyle + ? returnChildNode + : this.renderLayout(returnChildNode)} +
) }