diff --git a/packages/cdk/forms/__tests__/formArray.spec.ts b/packages/cdk/forms/__tests__/formArray.spec.ts index 9da4fa7d1..46340f4a9 100644 --- a/packages/cdk/forms/__tests__/formArray.spec.ts +++ b/packages/cdk/forms/__tests__/formArray.spec.ts @@ -25,7 +25,7 @@ const basicValue = { control: '', array: ['', ''], group: { control: '' } } describe('formArray.ts', () => { describe('basic work', () => { - let array: FormArray + let array: FormArray beforeEach(() => { array = new FormArray([newFormGroup()]) @@ -192,7 +192,7 @@ describe('formArray.ts', () => { }) describe('trigger work', () => { - let array: FormArray + let array: FormArray // eslint-disable-next-line @typescript-eslint/no-explicit-any const _validator = (value: any) => { return value[0].control === 'test' ? undefined : ({ test: {} } as ValidateErrors) @@ -305,7 +305,7 @@ describe('formArray.ts', () => { }) describe('disabled work', () => { - let array: FormArray + let array: FormArray test('default disabled work', async () => { array = new FormArray( diff --git a/packages/cdk/forms/__tests__/formGroup.spec.ts b/packages/cdk/forms/__tests__/formGroup.spec.ts index 2231472a0..7efbc2ba5 100644 --- a/packages/cdk/forms/__tests__/formGroup.spec.ts +++ b/packages/cdk/forms/__tests__/formGroup.spec.ts @@ -145,9 +145,9 @@ describe('formGroup.ts', () => { expect(group.get('control')).toEqual(control) expect(group.get('array')).toEqual(array) expect(group.get('group')).toEqual(groupChild) - expect(group.get('group.control')).toEqual((groupChild as FormGroup<{ control: string }>).controls.value.control) - expect(group.get(['array', 0])).toEqual((array as FormArray).controls.value[0]) - expect(group.get('array')!.get(0)).toEqual((array as FormArray).controls.value[0]) + expect(group.get('group.control')).toEqual(groupChild.controls.value.control) + expect(group.get(['array', 0])).toEqual(array.controls.value[0]) + expect(group.get('array')!.get(0)).toEqual(array.controls.value[0]) expect(group.get(undefined as never)).toBeUndefined() expect(group.get('')).toBeUndefined() diff --git a/packages/cdk/forms/__tests__/useForms.spec.ts b/packages/cdk/forms/__tests__/useForms.spec.ts index ae4cbdbd4..87676ad8c 100644 --- a/packages/cdk/forms/__tests__/useForms.spec.ts +++ b/packages/cdk/forms/__tests__/useForms.spec.ts @@ -6,21 +6,36 @@ import { Validators } from '../src/validators' interface BasicGroup { control1: string control2?: number - array: (string | number)[] - group: { + array1: (string | number)[] + array2: { control: string }[] + group1: { + control: string | number + } + group2: { control: string | number } } -const basicValue: BasicGroup = { control1: '', control2: undefined, array: ['', 1], group: { control: '' } } +const basicValue: BasicGroup = { + control1: '', + control2: undefined, + array1: ['', 1], + array2: [{ control: '0' }, { control: '1' }], + group1: { control: 1 }, + group2: { control: '2' }, +} describe('useForms.ts', () => { test('basic work', async () => { const group = useFormGroup({ control1: ['', Validators.required], control2: [undefined, { trigger: 'blur', validators: Validators.required }], - array: useFormArray([[''], [1]]), - group: useFormGroup({ control: useFormControl('') }), + array1: useFormArray([[''], [1]]), + array2: useFormArray([{ control: ['0'] }, { control: ['1'] }]), + group1: useFormGroup({ control: useFormControl(1) }), + group2: { + control: ['2'], + }, }) expect(group.getValue()).toEqual(basicValue) diff --git a/packages/cdk/forms/demo/Basic.vue b/packages/cdk/forms/demo/Basic.vue index 829a51d1f..bfe3109f8 100644 --- a/packages/cdk/forms/demo/Basic.vue +++ b/packages/cdk/forms/demo/Basic.vue @@ -22,19 +22,17 @@ import CustomInput from './CustomInput.vue' const { required, min, max, email } = Validators -const address = useFormGroup({ - city: ['', required], - street: ['', required], - zip: [''], -}) - -const remarks = useFormArray([['remark0'], ['remark1'], ['remark2']]) +const remarks = useFormArray([['remark0'], ['remark1'], ['remark2']]) const formGroup = useFormGroup({ name: ['tom', required], age: [18, [required, min(1), max(30)]], email: ['', [email]], - address: address, + address: { + city: ['', required], + street: ['', required], + zip: [''], + }, remarks: remarks, }) diff --git a/packages/cdk/forms/docs/Index.zh.md b/packages/cdk/forms/docs/Index.zh.md index 03a70dfba..2070f232c 100644 --- a/packages/cdk/forms/docs/Index.zh.md +++ b/packages/cdk/forms/docs/Index.zh.md @@ -22,19 +22,19 @@ export function useFormGroup( | 名称 | 说明 | 类型 | 默认值 | 备注 | | --- | --- | --- | --- | --- | | `config` | 控件组配置项 | `GroupConfig` | - | 每个子控件的 `key` 就是配置项的 `key` | +| `validatorOptions` | 控件组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) | | `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | 只针对当前控件组的值进行验证 | | `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | 只针对当前控件组的值进行验证 | -| `validatorOptions` | 控件组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) | ```ts type ControlConfig = | [T] + | [T, ValidatorOptions] | [T, ValidatorFn | ValidatorFn[]] | [T, ValidatorFn | ValidatorFn[], AsyncValidatorFn | AsyncValidatorFn[]] - | [T, ValidatorOptions] type GroupConfig = { - [K in keyof T]: ControlConfig | AbstractControl | FormGroup + [K in keyof T]: ControlConfig | GroupConfig | AbstractControl } ``` @@ -54,12 +54,12 @@ export function useFormArray( | 名称 | 说明 | 类型 | 默认值 | 备注 | | --- | --- | --- | --- | --- | | `config` | 控件数组配置项 | `ArrayConfig` | - | 每个子控件的 `key` 就是配置项的 `index` | +| `validatorOptions` | 控件数组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) | | `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | 只针对当前控件数组的值进行验证 | | `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | 只针对当前控件数组的值进行验证 | -| `validatorOptions` | 控件数组验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) | ```ts -type ArrayConfig = Array> | ControlConfig> | ArrayElement> +type ArrayConfig = Array | GroupConfig | AbstractControl> ``` ### useFormControl @@ -78,9 +78,9 @@ export function useFormControl( | 名称 | 说明 | 类型 | 默认值 | 备注 | | --- | --- | --- | --- | --- | | `initValue` | 控件初始值 | `any` | - | - | +| `validatorOptions` | 控件验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) | | `validators` | 一个同步验证器函数或数组 | `ValidatorFn \| ValidatorFn[]` | - | - | | `asyncValidators` | 一个异步验证器函数或数组 | `AsyncValidatorFn \| AsyncValidatorFn[]` | - | - | -| `validatorOptions` | 控件验证配置项 | `ValidatorOptions` | - | 参见[ValidatorOptions](#ValidatorOptions) | ### ValidatorOptions diff --git a/packages/cdk/forms/docs/Overview.zh.md b/packages/cdk/forms/docs/Overview.zh.md index 63c574915..4e9b01462 100644 --- a/packages/cdk/forms/docs/Overview.zh.md +++ b/packages/cdk/forms/docs/Overview.zh.md @@ -108,7 +108,7 @@ const address = useFormGroup({ zip: [''], }) -const remarks = useFormArray([['remark0'], ['remark1'], ['remark2']]) +const remarks = useFormArray([['remark0'], ['remark1'], ['remark2']]) const formGroup = useFormGroup({ name: ['tom', required], diff --git a/packages/cdk/forms/src/controls.ts b/packages/cdk/forms/src/controls.ts index 119855de6..18b4f3adb 100644 --- a/packages/cdk/forms/src/controls.ts +++ b/packages/cdk/forms/src/controls.ts @@ -27,7 +27,6 @@ import { hasOwnProperty } from '@idux/cdk/utils' import { type AsyncValidatorFn, - type TriggerType, type ValidateError, type ValidateErrors, type ValidateStatus, @@ -151,7 +150,7 @@ export abstract class AbstractControl { * Possible values: `'change'` | `'blur'` | `'submit'` * Default value: `'change'` */ - get trigger(): TriggerType { + get trigger(): 'change' | 'blur' | 'submit' { return this._trigger ?? this._parent?.trigger ?? 'change' } @@ -181,7 +180,7 @@ export abstract class AbstractControl { private _validators: ValidatorFn | undefined private _asyncValidators: AsyncValidatorFn | undefined private _parent: AbstractControl | undefined - private _trigger?: TriggerType + private _trigger?: 'change' | 'blur' | 'submit' private _trim?: boolean constructor( @@ -205,7 +204,10 @@ export abstract class AbstractControl { * * `dirty`: Marks it dirty, default is false. * * `blur`: Marks it blurred, default is false. */ - abstract setValue(value: T | Partial, options?: { dirty?: boolean; blur?: boolean }): void + abstract setValue( + value: T | Partial | Partial>[], + options?: { dirty?: boolean; blur?: boolean }, + ): void /** * The aggregate value of the control. @@ -756,9 +758,9 @@ export class FormGroup extends AbstractControl { } } -export class FormArray extends AbstractControl { - readonly controls!: ComputedRef>[]> - protected _controls!: ShallowRef>[]> +export class FormArray extends AbstractControl { + readonly controls!: ComputedRef[]> + protected _controls!: ShallowRef[]> /** * Length of the control array. @@ -769,7 +771,7 @@ export class FormArray extends AbstractControl { /** * An array of child controls. Each child control is given an index where it is registered. */ - controls: AbstractControl>[], + controls: AbstractControl[], validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[], ) { @@ -784,27 +786,27 @@ export class FormArray extends AbstractControl { this._watchDirty() } - setValue(value: Partial>[], options?: { dirty?: boolean; blur?: boolean }): void { + setValue(value: T extends object ? Partial[] : T[], options?: { dirty?: boolean; blur?: boolean }): void { value.forEach((item, index) => { const control = this.at(index) if (control) { - control.setValue(item, options) + control.setValue(item!, options) } }) } - getValue(options: { skipDisabled?: boolean } = {}): T { + getValue(options: { skipDisabled?: boolean } = {}): T[] { const { skipDisabled } = options return this._controls.value .filter(control => !skipDisabled || !control.disabled.value) - .map(control => control.getValue(options)) as T + .map(control => control.getValue(options)) } - protected _calculateInitValue(): T { + protected _calculateInitValue(): T[] { return this.getValue() } - protected _forEachControls(cb: (v: AbstractControl, k: keyof T) => void): void { + protected _forEachControls(cb: (v: AbstractControl, k: number) => void): void { this._controls.value.forEach(cb) } @@ -813,7 +815,7 @@ export class FormArray extends AbstractControl { * * @param index Index in the array to retrieve the control */ - at(index: number): AbstractControl> | undefined { + at(index: number): AbstractControl | undefined { return this._controls.value[index] } @@ -822,7 +824,7 @@ export class FormArray extends AbstractControl { * * @param control Form control to be inserted */ - push(control: AbstractControl>): void { + push(control: AbstractControl): void { control.setParent(this as AbstractControl) this._controls.value = [...this._controls.value, control] } @@ -833,7 +835,7 @@ export class FormArray extends AbstractControl { * @param index Index in the array to insert the control * @param control Form control to be inserted */ - insert(index: number, control: AbstractControl>): void { + insert(index: number, control: AbstractControl): void { control.setParent(this as AbstractControl) const controls = [...this._controls.value] controls.splice(index, 0, control) @@ -857,7 +859,7 @@ export class FormArray extends AbstractControl { * @param index Index in the array to replace the control * @param control The `AbstractControl` control to replace the existing control */ - setControl(index: number, control: AbstractControl>): void { + setControl(index: number, control: AbstractControl): void { control.setParent(this as AbstractControl) const controls = [...this._controls.value] controls.splice(index, 1, control) diff --git a/packages/cdk/forms/src/types.ts b/packages/cdk/forms/src/types.ts index 5d7787689..8022d965c 100644 --- a/packages/cdk/forms/src/types.ts +++ b/packages/cdk/forms/src/types.ts @@ -34,13 +34,11 @@ export interface AsyncValidatorFn { (value: any, control: AbstractControl): Promise } -export type TriggerType = 'change' | 'blur' | 'submit' - export interface ValidatorOptions { disabled?: boolean name?: string example?: string - trigger?: TriggerType + trigger?: 'change' | 'blur' | 'submit' validators?: ValidatorFn | ValidatorFn[] asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] /** diff --git a/packages/cdk/forms/src/useForms.ts b/packages/cdk/forms/src/useForms.ts index 603075724..65d18b7d9 100644 --- a/packages/cdk/forms/src/useForms.ts +++ b/packages/cdk/forms/src/useForms.ts @@ -7,32 +7,32 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { AbstractControl, ArrayElement, GroupControls } from './controls' -import type { AsyncValidatorFn, ValidatorFn, ValidatorOptions } from './types' - -import { FormArray, FormControl, FormGroup } from './controls' +import { type AbstractControl, FormArray, FormControl, FormGroup, type GroupControls } from './controls' import { isAbstractControl } from './typeof' +import { type AsyncValidatorFn, type ValidatorFn, type ValidatorOptions } from './types' type ControlConfig = | [T] + | [T, ValidatorOptions] | [T, ValidatorFn | ValidatorFn[]] | [T, ValidatorFn | ValidatorFn[], AsyncValidatorFn | AsyncValidatorFn[]] - | [T, ValidatorOptions] type GroupConfig = { - [K in keyof T]: ControlConfig | AbstractControl | FormGroup + [K in keyof T]: ControlConfig | GroupConfig | AbstractControl } -export function useFormGroup = Record>( +type ArrayConfig = Array | GroupConfig | AbstractControl> + +export function useFormGroup( config: GroupConfig, validatorOptions?: ValidatorOptions, ): FormGroup -export function useFormGroup = Record>( +export function useFormGroup( config: GroupConfig, validators?: ValidatorFn | ValidatorFn[], asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[], ): FormGroup -export function useFormGroup = Record>( +export function useFormGroup( config: GroupConfig, validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions, asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[], @@ -41,18 +41,13 @@ export function useFormGroup = Record return new FormGroup(controls, validatorOrOptions, asyncValidators) } -type ArrayConfig = Array> | ControlConfig>> - -export function useFormArray( - config: ArrayConfig, - validatorOptions?: ValidatorOptions, -): FormArray -export function useFormArray( +export function useFormArray(config: ArrayConfig, validatorOptions?: ValidatorOptions): FormArray +export function useFormArray( config: ArrayConfig, validators?: ValidatorFn | ValidatorFn[], asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[], ): FormArray -export function useFormArray( +export function useFormArray( config: ArrayConfig, validatorOrOptions?: ValidatorFn | ValidatorFn[] | ValidatorOptions, asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[], @@ -83,11 +78,16 @@ function reduceControls(config: GroupConfig): GroupControls { return controls } -function createControl(config: AbstractControl | ControlConfig): AbstractControl { +function createControl(config: AbstractControl | ControlConfig | GroupConfig): AbstractControl { if (isAbstractControl(config)) { return config - } else { - const [initValue, validatorOrOptions, asyncValidator] = config as ControlConfig + } + + if (Array.isArray(config)) { + const [initValue, validatorOrOptions, asyncValidator] = config return new FormControl(initValue, validatorOrOptions, asyncValidator) } + + const controls = reduceControls(config as GroupConfig) + return new FormGroup(controls) as unknown as AbstractControl } diff --git a/packages/components/form/__tests__/form.spec.ts b/packages/components/form/__tests__/form.spec.ts index 566ee12d2..d1d044c17 100644 --- a/packages/components/form/__tests__/form.spec.ts +++ b/packages/components/form/__tests__/form.spec.ts @@ -334,29 +334,29 @@ describe('Form', () => { expect(wrapper.find('.ix-form-item-control-tooltip .ix-icon-up').exists()).toBe(true) }) - test('extraMessage work', async () => { - let extraMessage = 'extraMessage' + test('description work', async () => { + let description = 'extraMessage' const wrapper = FormItemMount({ - props: { label: 'Username', extraMessage }, + props: { label: 'Username', description }, slots: { default: () => h(IxInput) }, }) - expect(wrapper.find('.ix-form-item-extra-message').text()).toBe(extraMessage) + expect(wrapper.find('.ix-form-item-description').text()).toBe(description) - extraMessage = 'extraMessage2' - await wrapper.setProps({ extraMessage }) + description = 'extraMessage2' + await wrapper.setProps({ description }) - expect(wrapper.find('.ix-form-item-extra-message').text()).toBe(extraMessage) + expect(wrapper.find('.ix-form-item-description').text()).toBe(description) }) test('extraMessage slot work', async () => { - const extraMessage = 'extraMessage' + const description = 'extraMessage' const wrapper = FormItemMount({ - props: { label: 'Username', extraMessage }, - slots: { default: () => h(IxInput), extraMessage: () => 'extraMessage slot' }, + props: { label: 'Username', description }, + slots: { default: () => h(IxInput), description: () => 'description slot' }, }) - expect(wrapper.find('.ix-form-item-extra-message').text()).toBe('extraMessage slot') + expect(wrapper.find('.ix-form-item-description').text()).toBe('description slot') }) test('label work', async () => { diff --git a/packages/components/form/demo/Basic.vue b/packages/components/form/demo/Basic.vue index 6a673fa8a..4ce34ed5c 100644 --- a/packages/components/form/demo/Basic.vue +++ b/packages/components/form/demo/Basic.vue @@ -28,34 +28,28 @@ -