From 52a2a385ec6e65c7eaaed0a67615c45aba07de64 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Sun, 17 Oct 2021 02:52:58 +0200 Subject: [PATCH] feat: added slot typings for components closes #3534 (#3537) --- packages/vee-validate/src/ErrorMessage.ts | 20 +++++-- packages/vee-validate/src/Field.ts | 65 +++++++++++++++++++---- packages/vee-validate/src/FieldArray.ts | 13 ++++- packages/vee-validate/src/Form.ts | 41 ++++++++++++-- 4 files changed, 120 insertions(+), 19 deletions(-) diff --git a/packages/vee-validate/src/ErrorMessage.ts b/packages/vee-validate/src/ErrorMessage.ts index dbfac7d2b..adc4a94f7 100644 --- a/packages/vee-validate/src/ErrorMessage.ts +++ b/packages/vee-validate/src/ErrorMessage.ts @@ -1,8 +1,12 @@ -import { inject, h, defineComponent, computed, resolveDynamicComponent } from 'vue'; +import { inject, h, defineComponent, computed, resolveDynamicComponent, VNode } from 'vue'; import { FormContextKey } from './symbols'; import { normalizeChildren } from './utils'; -export const ErrorMessage = defineComponent({ +interface ErrorMessageSlotProps { + message: string | undefined; +} + +const ErrorMessageImpl = defineComponent({ name: 'ErrorMessage', props: { as: { @@ -20,7 +24,7 @@ export const ErrorMessage = defineComponent({ return form?.errors.value[props.name]; }); - function slotProps() { + function slotProps(): ErrorMessageSlotProps { return { message: message.value, }; @@ -33,7 +37,7 @@ export const ErrorMessage = defineComponent({ } const tag = (props.as ? resolveDynamicComponent(props.as) : props.as) as string; - const children = normalizeChildren(tag, ctx, slotProps); + const children = normalizeChildren(tag, ctx, slotProps as any); const attrs = { role: 'alert', @@ -56,3 +60,11 @@ export const ErrorMessage = defineComponent({ }; }, }); + +export const ErrorMessage = ErrorMessageImpl as typeof ErrorMessageImpl & { + new (): { + $slots: { + default: (arg: ErrorMessageSlotProps) => VNode[]; + }; + }; +}; diff --git a/packages/vee-validate/src/Field.ts b/packages/vee-validate/src/Field.ts index 137745c56..e939cff4f 100644 --- a/packages/vee-validate/src/Field.ts +++ b/packages/vee-validate/src/Field.ts @@ -1,9 +1,21 @@ -import { h, defineComponent, toRef, SetupContext, resolveDynamicComponent, computed, watch, PropType } from 'vue'; +import { + h, + defineComponent, + toRef, + SetupContext, + resolveDynamicComponent, + computed, + watch, + PropType, + VNode, +} from 'vue'; import { getConfig } from './config'; import { RuleExpression, useField } from './useField'; import { normalizeChildren, hasCheckedAttr, shouldHaveValueBinding, isPropPresent, normalizeEventValue } from './utils'; import { toNumber } from '../../shared'; import { IS_ABSENT } from './symbols'; +import { FieldMeta } from './types'; +import { FieldContext } from '.'; interface ValidationTriggersProps { validateOnMount: boolean; @@ -13,7 +25,30 @@ interface ValidationTriggersProps { validateOnModelUpdate: boolean; } -export const Field = defineComponent({ +interface FieldBindingObject { + name: string; + onBlur: (e: Event) => unknown; + onInput: (e: Event) => unknown; + onChange: (e: Event) => unknown; + 'onUpdate:modelValue'?: ((e: TValue) => unknown) | undefined; + value?: unknown; + checked?: boolean; +} + +interface FieldSlotProps + extends Pick< + FieldContext, + 'validate' | 'resetField' | 'handleChange' | 'handleReset' | 'handleBlur' | 'setTouched' | 'setErrors' + > { + field: FieldBindingObject; + value: TValue; + meta: FieldMeta; + errors: string[]; + errorMessage: string | undefined; + handleInput: FieldContext['handleChange']; +} + +const FieldImpl = defineComponent({ name: 'Field', inheritAttrs: false, props: { @@ -135,11 +170,15 @@ export const Field = defineComponent({ const fieldProps = computed(() => { const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = resolveValidationTriggers(props); - const baseOnBlur = [handleBlur, ctx.attrs.onBlur, validateOnBlur ? validateField : undefined].filter(Boolean); - const baseOnInput = [(e: unknown) => onChangeHandler(e, validateOnInput), ctx.attrs.onInput].filter(Boolean); - const baseOnChange = [(e: unknown) => onChangeHandler(e, validateOnChange), ctx.attrs.onChange].filter(Boolean); + const baseOnBlur: any = [handleBlur, ctx.attrs.onBlur, validateOnBlur ? validateField : undefined].filter( + Boolean + ); + const baseOnInput: any = [(e: unknown) => onChangeHandler(e, validateOnInput), ctx.attrs.onInput].filter(Boolean); + const baseOnChange: any = [(e: unknown) => onChangeHandler(e, validateOnChange), ctx.attrs.onChange].filter( + Boolean + ); - const attrs: Record = { + const attrs: FieldBindingObject = { name: props.name, onBlur: baseOnBlur, onInput: baseOnInput, @@ -147,7 +186,7 @@ export const Field = defineComponent({ }; if (validateOnModelUpdate) { - attrs['onUpdate:modelValue'] = [onChangeHandler]; + attrs['onUpdate:modelValue'] = [onChangeHandler] as any; } if (hasCheckedAttr(ctx.attrs.type) && checked) { @@ -177,7 +216,7 @@ export const Field = defineComponent({ } }); - function slotProps() { + function slotProps(): FieldSlotProps { return { field: fieldProps.value, value: value.value, @@ -205,7 +244,7 @@ export const Field = defineComponent({ return () => { const tag = resolveDynamicComponent(resolveTag(props, ctx)) as string; - const children = normalizeChildren(tag, ctx, slotProps); + const children = normalizeChildren(tag, ctx, slotProps as any); if (tag) { return h( @@ -261,3 +300,11 @@ function resolveInitialValue(props: Record, ctx: SetupContext) => VNode[]; + }; + }; +}; diff --git a/packages/vee-validate/src/FieldArray.ts b/packages/vee-validate/src/FieldArray.ts index b5186e99b..9249ea458 100644 --- a/packages/vee-validate/src/FieldArray.ts +++ b/packages/vee-validate/src/FieldArray.ts @@ -1,8 +1,9 @@ -import { defineComponent, toRef } from 'vue'; +import { defineComponent, toRef, UnwrapRef, VNode } from 'vue'; +import { FieldArrayContext } from './types'; import { useFieldArray } from './useFieldArray'; import { normalizeChildren } from './utils'; -export const FieldArray = defineComponent({ +const FieldArrayImpl = defineComponent({ name: 'FieldArray', inheritAttrs: false, props: { @@ -44,3 +45,11 @@ export const FieldArray = defineComponent({ }; }, }); + +export const FieldArray = FieldArrayImpl as typeof FieldArrayImpl & { + new (): { + $slots: { + default: (arg: UnwrapRef) => VNode[]; + }; + }; +}; diff --git a/packages/vee-validate/src/Form.ts b/packages/vee-validate/src/Form.ts index 1e1dec632..42fee1996 100644 --- a/packages/vee-validate/src/Form.ts +++ b/packages/vee-validate/src/Form.ts @@ -1,9 +1,34 @@ -import { h, defineComponent, toRef, resolveDynamicComponent, PropType } from 'vue'; +import { h, defineComponent, toRef, resolveDynamicComponent, PropType, VNode, UnwrapRef } from 'vue'; import { useForm } from './useForm'; import { SubmissionHandler, InvalidSubmissionHandler } from './types'; import { isEvent, normalizeChildren } from './utils'; +import { FormContext } from '.'; -export const Form = defineComponent({ +type FormSlotProps = UnwrapRef< + Pick< + FormContext, + | 'meta' + | 'errors' + | 'values' + | 'isSubmitting' + | 'submitCount' + | 'validate' + | 'validateField' + | 'handleReset' + | 'submitForm' + | 'setErrors' + | 'setFieldError' + | 'setFieldValue' + | 'setValues' + | 'setFieldTouched' + | 'setTouched' + | 'resetForm' + > +> & { + handleSubmit: (evt: Event | SubmissionHandler, onSubmit?: SubmissionHandler) => Promise; +}; + +const FormImpl = defineComponent({ name: 'Form', inheritAttrs: false, props: { @@ -89,7 +114,7 @@ export const Form = defineComponent({ return handleSubmit(onSuccess as SubmissionHandler>, props.onInvalidSubmit)(evt as Event); } - function slotProps() { + function slotProps(): FormSlotProps { return { meta: meta.value, errors: errors.value, @@ -127,7 +152,7 @@ export const Form = defineComponent({ return function renderForm() { // avoid resolving the form component as itself const tag = props.as === 'form' ? props.as : (resolveDynamicComponent(props.as) as string); - const children = normalizeChildren(tag, ctx, slotProps); + const children = normalizeChildren(tag, ctx, slotProps as any); if (!props.as) { return children; @@ -155,3 +180,11 @@ export const Form = defineComponent({ }; }, }); + +export const Form = FormImpl as typeof FormImpl & { + new (): { + $slots: { + default: (arg: FormSlotProps) => VNode[]; + }; + }; +};