Skip to content

Commit

Permalink
feat: added slot typings for components closes #3534 (#3537)
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Oct 17, 2021
1 parent 1735dc0 commit 52a2a38
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 19 deletions.
20 changes: 16 additions & 4 deletions 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: {
Expand All @@ -20,7 +24,7 @@ export const ErrorMessage = defineComponent({
return form?.errors.value[props.name];
});

function slotProps() {
function slotProps(): ErrorMessageSlotProps {
return {
message: message.value,
};
Expand All @@ -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',
Expand All @@ -56,3 +60,11 @@ export const ErrorMessage = defineComponent({
};
},
});

export const ErrorMessage = ErrorMessageImpl as typeof ErrorMessageImpl & {
new (): {
$slots: {
default: (arg: ErrorMessageSlotProps) => VNode[];
};
};
};
65 changes: 56 additions & 9 deletions 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;
Expand All @@ -13,7 +25,30 @@ interface ValidationTriggersProps {
validateOnModelUpdate: boolean;
}

export const Field = defineComponent({
interface FieldBindingObject<TValue = unknown> {
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<TValue = unknown>
extends Pick<
FieldContext,
'validate' | 'resetField' | 'handleChange' | 'handleReset' | 'handleBlur' | 'setTouched' | 'setErrors'
> {
field: FieldBindingObject<TValue>;
value: TValue;
meta: FieldMeta<TValue>;
errors: string[];
errorMessage: string | undefined;
handleInput: FieldContext['handleChange'];
}

const FieldImpl = defineComponent({
name: 'Field',
inheritAttrs: false,
props: {
Expand Down Expand Up @@ -135,19 +170,23 @@ 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<string, any> = {
const attrs: FieldBindingObject<unknown> = {
name: props.name,
onBlur: baseOnBlur,
onInput: baseOnInput,
onChange: baseOnChange,
};

if (validateOnModelUpdate) {
attrs['onUpdate:modelValue'] = [onChangeHandler];
attrs['onUpdate:modelValue'] = [onChangeHandler] as any;
}

if (hasCheckedAttr(ctx.attrs.type) && checked) {
Expand Down Expand Up @@ -177,7 +216,7 @@ export const Field = defineComponent({
}
});

function slotProps() {
function slotProps(): FieldSlotProps {
return {
field: fieldProps.value,
value: value.value,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -261,3 +300,11 @@ function resolveInitialValue(props: Record<string, unknown>, ctx: SetupContext<a

return isPropPresent(props, 'modelValue') ? props.modelValue : undefined;
}

export const Field = FieldImpl as typeof FieldImpl & {
new (): {
$slots: {
default: (arg: FieldSlotProps<unknown>) => VNode[];
};
};
};
13 changes: 11 additions & 2 deletions 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: {
Expand Down Expand Up @@ -44,3 +45,11 @@ export const FieldArray = defineComponent({
};
},
});

export const FieldArray = FieldArrayImpl as typeof FieldArrayImpl & {
new (): {
$slots: {
default: (arg: UnwrapRef<FieldArrayContext>) => VNode[];
};
};
};
41 changes: 37 additions & 4 deletions 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<unknown>;
};

const FormImpl = defineComponent({
name: 'Form',
inheritAttrs: false,
props: {
Expand Down Expand Up @@ -89,7 +114,7 @@ export const Form = defineComponent({
return handleSubmit(onSuccess as SubmissionHandler<Record<string, unknown>>, props.onInvalidSubmit)(evt as Event);
}

function slotProps() {
function slotProps(): FormSlotProps {
return {
meta: meta.value,
errors: errors.value,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -155,3 +180,11 @@ export const Form = defineComponent({
};
},
});

export const Form = FormImpl as typeof FormImpl & {
new (): {
$slots: {
default: (arg: FormSlotProps) => VNode[];
};
};
};

0 comments on commit 52a2a38

Please sign in to comment.