diff --git a/docs/en-US/component/form.md b/docs/en-US/component/form.md index 3f720c7795..be3c4bd858 100644 --- a/docs/en-US/component/form.md +++ b/docs/en-US/component/form.md @@ -160,15 +160,16 @@ form/accessibility ### Form Exposes -| Name | Description | Type | -| ------------------ | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- | -| validate | Validate the whole form. Receives a callback or returns `Promise`. | ^[Function]`(callback?: FormValidateCallback) => Promise` | -| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` | -| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable \| undefined) => void` | -| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` | -| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable \| undefined) => void` | -| fields ^(2.7.3) | Get all fields context. | ^[array]`FormItemContext[]` | -| getField ^(2.10.2) | Get a field context. | ^[Function]`(prop: FormItemProp) => FormItemContext \| undefined` | +| Name | Description | Type | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| validate | Validate the whole form. Receives a callback or returns `Promise`. | ^[Function]`(callback?: FormValidateCallback) => Promise` | +| validateField | Validate specified fields. | ^[Function]`(props?: Arrayable \| undefined, callback?: FormValidateCallback \| undefined) => FormValidationResult` | +| resetFields | Reset specified fields and remove validation result. | ^[Function]`(props?: Arrayable \| undefined) => void` | +| scrollToField | Scroll to the specified fields. | ^[Function]`(prop: FormItemProp) => void` | +| clearValidate | Clear validation messages for all or specified fields. | ^[Function]`(props?: Arrayable \| undefined) => void` | +| fields ^(2.7.3) | Get all fields context. | ^[array]`FormItemContext[]` | +| getField ^(2.10.2) | Get a field context. | ^[Function]`(prop: FormItemProp) => FormItemContext \| undefined` | +| setInitialValues ^(2.13.1) | Set initial values for form fields. When `resetFields` is called, fields will reset to these values. Only fields present in `initModel` will be updated. | ^[Function]`(initModel: Record) => void` | ## FormItem API @@ -211,14 +212,15 @@ If you don't want to trigger the validator based on input events, set the `valid ### FormItem Exposes -| Name | Description | Type | -| --------------- | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | -| size | Form item size. | ^[object]`ComputedRef<'' \| 'large' \| 'default' \| 'small'>` | -| validateMessage | Validation message. | ^[object]`Ref` | -| validateState | Validation state. | ^[object]`Ref<'' \| 'error' \| 'validating' \| 'success'>` | -| validate | Validate form item. | ^[Function]`(trigger: string, callback?: FormValidateCallback \| undefined) => FormValidationResult` | -| resetField | Reset current field and remove validation result. | ^[Function]`() => void` | -| clearValidate | Remove validation status of the field. | ^[Function]`() => void` | +| Name | Description | Type | +| ------------------------- | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| size | Form item size. | ^[object]`ComputedRef<'' \| 'large' \| 'default' \| 'small'>` | +| validateMessage | Validation message. | ^[object]`Ref` | +| validateState | Validation state. | ^[object]`Ref<'' \| 'error' \| 'validating' \| 'success'>` | +| validate | Validate form item. | ^[Function]`(trigger: string, callback?: FormValidateCallback \| undefined) => FormValidationResult` | +| resetField | Reset current field and remove validation result. | ^[Function]`() => void` | +| clearValidate | Remove validation status of the field. | ^[Function]`() => void` | +| setInitialValue ^(2.13.1) | Set initial value for this field. When `resetField` is called, the field will reset to this value. | ^[Function]`(value: any) => void` | ## Type Declarations @@ -297,6 +299,7 @@ type FormItemContext = FormItemProps & { ) => FormValidationResult resetField(): void clearValidate(): void + setInitialValue: (value: any) => void } ``` diff --git a/packages/components/form/__tests__/form-item.test.tsx b/packages/components/form/__tests__/form-item.test.tsx index ba36425c4f..1af36ca69f 100644 --- a/packages/components/form/__tests__/form-item.test.tsx +++ b/packages/components/form/__tests__/form-item.test.tsx @@ -232,4 +232,91 @@ describe('ElFormItem', () => { expect(labelSlot.exists()).toBe(true) expect(labelSlot.text()).toBe(AXIOM) }) + + describe('setInitialValue', () => { + it('should allow setting custom initial value for reset', async () => { + vi.useFakeTimers() + const form = reactive({ + username: 'original', + }) + const wrapper = mount({ + setup() { + return { form } + }, + render() { + return ( +
+ + + +
+ ) + }, + }) + + await nextTick() + + const formItemRef = wrapper.findComponent({ ref: 'usernameItem' }) + .vm as FormItemInstance + + // Set custom initial value + formItemRef.setInitialValue('customInitial') + await nextTick() + + // Modify field value + form.username = 'modified' + await nextTick() + + // Reset field + formItemRef.resetField() + await nextTick() + vi.runAllTimers() + await nextTick() + + // Should reset to custom initial value + expect(form.username).toBe('customInitial') + + vi.useRealTimers() + }) + + it('should handle undefined initial values', async () => { + vi.useFakeTimers() + const form = reactive({ + value: 'original', + }) + const wrapper = mount({ + setup() { + return { form } + }, + render() { + return ( +
+ + + +
+ ) + }, + }) + + await nextTick() + + const formItemRef = wrapper.findComponent({ ref: 'valueItem' }) + .vm as FormItemInstance + + // Test undefined + formItemRef.setInitialValue(undefined) + form.value = 'changed' + await nextTick() + + formItemRef.resetField() + await nextTick() + vi.runAllTimers() + await nextTick() + + expect(form.value).toBe(undefined) + + vi.useRealTimers() + }) + }) }) diff --git a/packages/components/form/__tests__/form.test.tsx b/packages/components/form/__tests__/form.test.tsx index 4c297e4e64..0f3ad817e9 100644 --- a/packages/components/form/__tests__/form.test.tsx +++ b/packages/components/form/__tests__/form.test.tsx @@ -125,14 +125,14 @@ describe('Form', () => { labelWidth="150px" model={form} > - + - + @@ -1020,4 +1020,183 @@ describe('Form', () => { expect(onSubmit).toHaveBeenCalled() }) }) + + describe('setInitialValues', () => { + it('should update initial values for all fields', async () => { + vi.useFakeTimers() + const form = reactive({ + name: '', + age: '', + address: '', + }) + const wrapper = mount({ + setup() { + return { form } + }, + render() { + return ( +
+ + + + + + + + + +
+ ) + }, + }) + + await nextTick() + + const formRef = wrapper.findComponent({ ref: 'formRef' }) + .vm as FormInstance + + // Set new initial values + formRef.setInitialValues({ + name: 'InitName', + age: '30', + address: 'Los Angeles', + }) + await nextTick() + + // Modify form values + form.name = 'Modified' + form.age = '40' + form.address = 'Chicago' + await nextTick() + + // Reset form, should reset to values set by setInitialValues + formRef.resetFields() + await nextTick() + vi.runAllTimers() + await nextTick() + + expect(form.name).toBe('InitName') + expect(form.age).toBe('30') + expect(form.address).toBe('Los Angeles') + + vi.useRealTimers() + }) + + it('should work with partial field updates', async () => { + vi.useFakeTimers() + const form = reactive({ + name: 'DefaultName', + age: '20', + address: 'DefaultAddress', + }) + const wrapper = mount({ + setup() { + return { form } + }, + render() { + return ( +
+ + + + + + + + + +
+ ) + }, + }) + + await nextTick() + + const formRef = wrapper.findComponent({ ref: 'formRef' }) + .vm as FormInstance + + // Only update initial values for partial fields + formRef.setInitialValues({ + name: 'NewInitName', + age: '25', + }) + await nextTick() + + // Modify all fields + form.name = 'Modified' + form.age = '30' + form.address = 'ModifiedAddress' + await nextTick() + + // Reset form + formRef.resetFields() + await nextTick() + vi.runAllTimers() + await nextTick() + + // name and age should reset to new initial values + expect(form.name).toBe('NewInitName') + expect(form.age).toBe('25') + // address has no new initial value set, should keep original mounted value + expect(form.address).toBe('DefaultAddress') + + vi.useRealTimers() + }) + }) + + describe('setInitialValue (FormItem)', () => { + it('should update initial value for single field', async () => { + vi.useFakeTimers() + const form = reactive({ + name: '', + age: '', + }) + const wrapper = mount({ + setup() { + return { form } + }, + render() { + return ( +
+ + + + + + +
+ ) + }, + }) + + await nextTick() + + const formRef = wrapper.findComponent({ ref: 'formRef' }) + .vm as FormInstance + const nameItem = wrapper.findComponent({ ref: 'nameItem' }) + .vm as FormItemInstance + + // Set new initial value only for name field + nameItem.setInitialValue('CustomInitName') + await nextTick() + + // Modify field values + form.name = 'Modified' + form.age = '30' + await nextTick() + + // Reset form + formRef.resetFields() + await nextTick() + vi.runAllTimers() + await nextTick() + + // name should reset to custom initial value + expect(form.name).toBe('CustomInitName') + // age should reset to original mounted value + expect(form.age).toBe('') + + vi.useRealTimers() + }) + }) }) diff --git a/packages/components/form/src/form-item.vue b/packages/components/form/src/form-item.vue index 80e170f2e6..7854f6d70a 100644 --- a/packages/components/form/src/form-item.vue +++ b/packages/components/form/src/form-item.vue @@ -362,6 +362,10 @@ const removeInputId: FormItemContext['removeInputId'] = (id: string) => { inputIds.value = inputIds.value.filter((listId) => listId !== id) } +const setInitialValue: FormItemContext['setInitialValue'] = (value: any) => { + initialValue = clone(value) +} + watch( () => props.error, (val) => { @@ -393,6 +397,7 @@ const context: FormItemContext = reactive({ clearValidate, validate, propString, + setInitialValue, }) provide(formItemContextKey, context) @@ -433,5 +438,9 @@ defineExpose({ * @description Reset current field and remove validation result. */ resetField, + /** + * @description Set initial value for this field. When `resetField` is called, the field will reset to this value. + */ + setInitialValue, }) diff --git a/packages/components/form/src/form.vue b/packages/components/form/src/form.vue index a77c009d06..db8f144fea 100644 --- a/packages/components/form/src/form.vue +++ b/packages/components/form/src/form.vue @@ -6,7 +6,8 @@ diff --git a/packages/components/form/src/types.ts b/packages/components/form/src/types.ts index 930826b652..aed111675f 100644 --- a/packages/components/form/src/types.ts +++ b/packages/components/form/src/types.ts @@ -46,6 +46,7 @@ export type FormContext = FormProps & addField: (field: FormItemContext) => void removeField: (field: FormItemContext) => void resetFields: (props?: Arrayable) => void + setInitialValues: (initModel: Record) => void clearValidate: (props?: Arrayable) => void validateField: ( props?: Arrayable, @@ -72,4 +73,5 @@ export interface FormItemContext extends FormItemProps { ) => FormValidationResult resetField(): void clearValidate(): void + setInitialValue: (value: any) => void }