diff --git a/libs/core/src/__tests__/form/field.spec.ts b/libs/core/src/__tests__/form/field.spec.ts index 746eb7b..f5c05a5 100644 --- a/libs/core/src/__tests__/form/field.spec.ts +++ b/libs/core/src/__tests__/form/field.spec.ts @@ -164,4 +164,36 @@ describe('Form fields', () => { expect(wrapper.html()).toMatchSnapshot(); }); + + it('it can invalidate a field', async () => { + expect.assertions(1); + + const field = defineField({ + component: 'input', + name: 'element', + ref: ref(''), + }); + + const {instance: form} = await generateTestForm<{element: string}>([field]); + const fieldInstance = form.getField('element'); + fieldInstance.setInvalid(); + + expect(fieldInstance?.isValid.value).toBe(false); + }); + + it('can add an error to a field', async () => { + expect.assertions(1); + + const field = defineField({ + component: 'input', + name: 'element', + ref: ref(''), + }); + + const {instance: form} = await generateTestForm<{element: string}>([field]); + const fieldInstance = form.getField('element'); + fieldInstance.addError('error message'); + + expect(fieldInstance?.errors.value).toEqual(['error message']); + }); }); diff --git a/libs/core/src/form/Field.ts b/libs/core/src/form/Field.ts index 2816024..320b0c1 100644 --- a/libs/core/src/form/Field.ts +++ b/libs/core/src/form/Field.ts @@ -1,11 +1,11 @@ // noinspection JSUnusedGlobalSymbols -import {ref, watch, toValue, reactive, type UnwrapNestedRefs, computed, markRaw, type Ref} from 'vue'; +import {ref, watch, toValue, reactive, type UnwrapNestedRefs, computed, markRaw, type Ref, isRef} from 'vue'; import {isDefined} from '@vueuse/core'; import {type CustomHookItem, createHookManager} from '@myparcel-vfb/hook-manager'; import {isOfType, asyncEvery, type PromiseOr} from '@myparcel/ts-utils'; import {isRequired} from '../validators'; import {normalizeFieldConfiguration} from '../utils/normalizeFieldConfiguration'; -import {useDynamicWatcher} from '../utils'; +import {useDynamicWatcher, updateMaybeRef} from '../utils'; import { type ToRecord, type FieldConfiguration, @@ -130,35 +130,6 @@ export class Field, - resolvedConfig: FieldConfiguration, - ): void { - ( - [ - [config.visibleWhen, this.isVisible, resolvedConfig.visible], - [config.disabledWhen, this.isDisabled, resolvedConfig.disabled], - [config.optionalWhen, this.isOptional, resolvedConfig.optional], - [config.readOnlyWhen, this.isReadOnly, resolvedConfig.readOnly], - ] satisfies [ - undefined | ((instance: FieldInstance) => PromiseOr), - Ref, - boolean | undefined, - ][] - ).forEach(([configProperty, computedProperty, staticProperty]) => { - if (!isDefined(configProperty)) { - return; - } - - computedProperty.value = staticProperty; - - // @ts-expect-erro todo - const stopHandler = useDynamicWatcher(() => configProperty(this), computedProperty); - - this.destroyHandles.value.push(stopHandler); - }); - } - public blur = async (): Promise => { await this.hooks.execute('beforeBlur', this, toValue(this.ref)); @@ -189,19 +160,27 @@ export class Field => { @@ -251,6 +230,35 @@ export class Field, + resolvedConfig: FieldConfiguration, + ): void { + ( + [ + [config.visibleWhen, this.isVisible, resolvedConfig.visible], + [config.disabledWhen, this.isDisabled, resolvedConfig.disabled], + [config.optionalWhen, this.isOptional, resolvedConfig.optional], + [config.readOnlyWhen, this.isReadOnly, resolvedConfig.readOnly], + ] satisfies [ + undefined | ((instance: FieldInstance) => PromiseOr), + Ref, + boolean | undefined, + ][] + ).forEach(([configProperty, computedProperty, staticProperty]) => { + if (!isDefined(configProperty)) { + return; + } + + // @ts-expect-error todo + computedProperty.value = staticProperty; + + const stopHandler = useDynamicWatcher(() => configProperty(this), computedProperty); + + this.destroyHandles.value.push(stopHandler); + }); + } + private createValidators(config: FieldConfiguration): void { let validators: Validator[] = []; diff --git a/libs/core/src/types/field.types.ts b/libs/core/src/types/field.types.ts index 6905160..8d92a49 100644 --- a/libs/core/src/types/field.types.ts +++ b/libs/core/src/types/field.types.ts @@ -249,7 +249,11 @@ export interface FieldInstance = UnwrapNestedRefs>; diff --git a/libs/core/src/utils/createField.spec.ts b/libs/core/src/utils/createField.spec.ts index b031d78..5a92856 100644 --- a/libs/core/src/utils/createField.spec.ts +++ b/libs/core/src/utils/createField.spec.ts @@ -26,7 +26,7 @@ describe('createField', () => { const field = createField({name: 'test', ref: ref('value'), component: 'input'}); expect(field).toStrictEqual({ - Component: expect.objectContaining({render: expect.any(Function)}), + Component: expect.any(Function), Errors: expect.objectContaining({__asyncLoader: expect.any(Function)}), Label: expect.objectContaining({__asyncLoader: expect.any(Function)}), field: {name: 'test', ref: 'value', component: 'input'}, @@ -38,7 +38,7 @@ describe('createField', () => { const field = createField({name: 'test', ref: ref('value2'), component: testComponent}); expect(field).toStrictEqual({ - Component: expect.objectContaining({render: expect.any(Function)}), + Component: expect.any(Function), Errors: expect.objectContaining({__asyncLoader: expect.any(Function)}), Label: expect.objectContaining({__asyncLoader: expect.any(Function)}), field: {name: 'test', ref: 'value2', component: testComponent}, diff --git a/libs/core/src/utils/createField.ts b/libs/core/src/utils/createField.ts index 695b9df..8146d9d 100644 --- a/libs/core/src/utils/createField.ts +++ b/libs/core/src/utils/createField.ts @@ -21,6 +21,8 @@ import FormElementWrapper from '../components/FormElementWrapper'; const createMainComponent = ( field: FieldConfiguration, + attrs: Record, + slots: Record, ): Component => { return defineComponent({ setup() { @@ -62,7 +64,19 @@ const createMainComponent = ()) as Type extends undefined ? undefined : Ref, - Component: markRaw(createMainComponent(field)), + Component: markRaw((_, ctx) => h(createMainComponent(field, ctx.attrs, ctx.slots))), Errors: createAsyncComponent(() => createErrorComponent(field)), Label: createAsyncComponent(() => createLabelComponent(field)), }); diff --git a/libs/core/src/utils/index.ts b/libs/core/src/utils/index.ts index a725a1d..4fdcb10 100644 --- a/libs/core/src/utils/index.ts +++ b/libs/core/src/utils/index.ts @@ -4,6 +4,7 @@ export * from './defineField'; export * from './defineForm'; export * from './generateFieldName'; export * from './markComponentAsRaw'; +export * from './updateMaybeRef'; export * from './useDynamicWatcher'; export {getDefaultFormConfiguration} from './getDefaultFormConfiguration'; diff --git a/libs/core/src/utils/updateMaybeRef.ts b/libs/core/src/utils/updateMaybeRef.ts new file mode 100644 index 0000000..934c0b6 --- /dev/null +++ b/libs/core/src/utils/updateMaybeRef.ts @@ -0,0 +1,10 @@ +import {isRef, type MaybeRef} from 'vue'; + +export const updateMaybeRef = (maybeRef: MaybeRef, value: T): void => { + if (isRef(maybeRef)) { + maybeRef.value = value; + return; + } + + maybeRef = value; +};