Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ export function merge(target: any, source: any) {
/**
* Constructs a path with dot paths for arrays to use brackets to be compatible with vee-validate path syntax
*/
export function normalizeFormPath(path: string): string {
const pathArr = path.split('.');
export function normalizeFormPath(path: Array<string | number> | string): string {
const pathArr = Array.isArray(path) ? path : path.split('.');
if (!pathArr.length) {
return '';
}
Expand Down
10 changes: 8 additions & 2 deletions packages/valibot/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { PartialDeep } from 'type-fest';
import { cleanupNonNestedPath, isNotNestedPath, type TypedSchema, type TypedSchemaError } from 'vee-validate';
import {
cleanupNonNestedPath,
isNotNestedPath,
getPathSegments,
type TypedSchema,
type TypedSchemaError,
} from 'vee-validate';
import {
InferOutput,
InferInput,
Expand Down Expand Up @@ -155,7 +161,7 @@ function getSchemaForPath(
return schema.entries[cleanupNonNestedPath(path)];
}

const paths = (path || '').split(/\.|\[(\d+)\]/).filter(Boolean);
const paths = getPathSegments(path);

let currentSchema: BaseSchema<unknown, unknown, BaseIssue<unknown>> = schema;
for (let i = 0; i <= paths.length; i++) {
Expand Down
2 changes: 1 addition & 1 deletion packages/vee-validate/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export { validate, validateObjectSchema as validateObject } from './validate';
export { defineRule } from './defineRule';
export { configure } from './config';
export { normalizeRules, isNotNestedPath, cleanupNonNestedPath } from './utils';
export { normalizeRules, isNotNestedPath, cleanupNonNestedPath, getPathSegments } from './utils';
export { Field, FieldBindingObject, ComponentFieldBindingObject, FieldSlotProps } from './Field';
export { Form, FormSlotProps } from './Form';
export { FieldArray } from './FieldArray';
Expand Down
4 changes: 2 additions & 2 deletions packages/vee-validate/src/types/forms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,9 @@ export interface PrivateFormContext<
stageInitialValue(path: string, value: unknown, updateOriginal?: boolean): void;
unsetInitialValue(path: string): void;
handleSubmit: HandleSubmitFactory<TValues, TOutput> & { withControlled: HandleSubmitFactory<TValues, TOutput> };
setFieldInitialValue(path: string, value: unknown, updateOriginal?: boolean): void;
setFieldInitialValue(path: Array<string | number> | string, value: unknown, updateOriginal?: boolean): void;
createPathState<TPath extends Path<TValues>>(
path: MaybeRef<TPath>,
path: MaybeRefOrGetter<Array<string | number> | TPath>,
config?: Partial<PathStateConfig<TOutput[TPath]>>,
): PathState<PathValue<TValues, TPath>>;
getPathState<TPath extends Path<TValues>>(path: TPath): PathState<PathValue<TValues, TPath>> | undefined;
Expand Down
16 changes: 12 additions & 4 deletions packages/vee-validate/src/useField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function useField<TValue = unknown>(
}

function _useField<TValue = unknown>(
path: MaybeRefOrGetter<string>,
path: MaybeRefOrGetter<Array<string | number> | string>,
rules?: MaybeRef<RuleExpression<TValue>>,
opts?: Partial<FieldOptions<TValue>>,
): FieldContext<TValue> {
Expand Down Expand Up @@ -143,7 +143,7 @@ function _useField<TValue = unknown>(
});

const isTyped = !isCallable(validator.value) && isTypedSchema(toValue(rules));
const { id, value, initialValue, meta, setState, errors, flags } = useFieldState<TValue>(name, {
const { id, value, initialValue, meta, setState, errors, flags } = useFieldState<TValue>(path, {
modelValue,
form,
bails,
Expand Down Expand Up @@ -325,7 +325,11 @@ function _useField<TValue = unknown>(
return;
}

meta.validated ? validateWithStateMutation() : validateValidStateOnly();
if (meta.validated) {
validateWithStateMutation();
} else {
validateValidStateOnly();
}
},
{
deep: true,
Expand Down Expand Up @@ -399,7 +403,11 @@ function _useField<TValue = unknown>(

const shouldValidate = !isEqual(deps, oldDeps);
if (shouldValidate) {
meta.validated ? validateWithStateMutation() : validateValidStateOnly();
if (meta.validated) {
validateWithStateMutation();
} else {
validateValidStateOnly();
}
}
});

Expand Down
26 changes: 13 additions & 13 deletions packages/vee-validate/src/useFieldState.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { computed, isRef, reactive, ref, Ref, unref, watch, MaybeRef, MaybeRefOrGetter, toValue } from 'vue';
import { FieldMeta, FieldState, FieldValidator, InputType, PrivateFormContext, PathState, TypedSchema } from './types';
import { getFromPath, isEqual, normalizeErrorItem } from './utils';
import { getFromPath, isEqual, normalizeErrorItem, serializePath } from './utils';

export interface StateSetterInit<TValue = unknown> extends FieldState<TValue> {
initialValue: TValue;
}

export interface FieldStateComposable<TValue = unknown> {
id: number;
path: MaybeRef<string>;
path: MaybeRefOrGetter<Array<string | number> | string>;
meta: FieldMeta<TValue>;
value: Ref<TValue>;
flags: PathState['__flags'];
Expand All @@ -30,7 +30,7 @@ export interface StateInit<TInput = unknown, TOutput = TInput> {
let ID_COUNTER = 0;

export function useFieldState<TValue = unknown>(
path: MaybeRef<string>,
path: MaybeRefOrGetter<Array<string | number> | string>,
init: Partial<StateInit<TValue>>,
): FieldStateComposable<TValue> {
const { value, initialValue, setInitialValue } = _useFieldValue<TValue>(path, init.modelValue, init.form);
Expand Down Expand Up @@ -86,11 +86,11 @@ export function useFieldState<TValue = unknown>(
}

if ('errors' in state) {
init.form?.setFieldError(unref(path), state.errors);
init.form?.setFieldError(serializePath(toValue(path)), state.errors);
}

if ('touched' in state) {
init.form?.setFieldTouched(unref(path), state.touched ?? false);
init.form?.setFieldTouched(serializePath(toValue(path)), state.touched ?? false);
}

if ('initialValue' in state) {
Expand Down Expand Up @@ -120,7 +120,7 @@ interface FieldValueComposable<TValue = unknown> {
* Creates the field value and resolves the initial value
*/
export function _useFieldValue<TValue = unknown>(
path: MaybeRef<string>,
path: MaybeRefOrGetter<Array<string | number> | string>,
modelValue?: MaybeRef<TValue>,
form?: PrivateFormContext,
): FieldValueComposable<TValue> {
Expand All @@ -131,7 +131,7 @@ export function _useFieldValue<TValue = unknown>(
return unref(modelRef) as TValue;
}

return getFromPath<TValue>(form.initialValues.value, unref(path), unref(modelRef)) as TValue;
return getFromPath<TValue>(form.initialValues.value, toValue(path), unref(modelRef)) as TValue;
}

function setInitialValue(value: TValue) {
Expand All @@ -140,7 +140,7 @@ export function _useFieldValue<TValue = unknown>(
return;
}

form.setFieldInitialValue(unref(path), value, true);
form.setFieldInitialValue(toValue(path), value, true);
}

const initialValue = computed(resolveInitialValue);
Expand All @@ -161,14 +161,14 @@ export function _useFieldValue<TValue = unknown>(
// prioritize model value over form values
// #3429
const currentValue = resolveModelValue(modelValue, form, initialValue, path);
form.stageInitialValue(unref(path), currentValue, true);
form.stageInitialValue(serializePath(toValue(path)), currentValue, true);
// otherwise use a computed setter that triggers the `setFieldValue`
const value = computed<TValue>({
get() {
return getFromPath<TValue>(form.values, unref(path)) as TValue;
return getFromPath<TValue>(form.values, toValue(path)) as TValue;
},
set(newVal) {
form.setFieldValue(unref(path), newVal, false);
form.setFieldValue(serializePath(toValue(path)), newVal, false);
},
}) as Ref<TValue>;

Expand All @@ -189,7 +189,7 @@ function resolveModelValue<TValue>(
modelValue: MaybeRef<TValue> | undefined,
form: PrivateFormContext,
initialValue: MaybeRef<TValue> | undefined,
path: MaybeRef<string>,
path: MaybeRefOrGetter<Array<string | number> | string>,
): TValue {
if (isRef(modelValue)) {
return unref(modelValue);
Expand All @@ -199,7 +199,7 @@ function resolveModelValue<TValue>(
return modelValue;
}

return getFromPath(form.values, unref(path), unref(initialValue)) as TValue;
return getFromPath(form.values, toValue(path), unref(initialValue)) as TValue;
}

/**
Expand Down
24 changes: 16 additions & 8 deletions packages/vee-validate/src/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import {
omit,
debounceNextTick,
normalizeEventValue,
serializePath,
} from './utils';
import { FormContextKey, PublicFormContextKey } from './symbols';
import { validateTypedSchema, validateObjectSchema } from './validate';
Expand Down Expand Up @@ -271,11 +272,11 @@ export function useForm<
const schema = opts?.validationSchema;

function createPathState<TPath extends Path<TValues>>(
path: MaybeRefOrGetter<TPath>,
path: MaybeRefOrGetter<Array<string | number> | TPath>,
config?: Partial<PathStateConfig<TOutput[TPath]>>,
): PathState<TValues[TPath], TOutput[TPath]> {
const initialValue = computed(() => getFromPath(initialValues.value, toValue(path)));
const pathStateExists = pathStateLookup.value[toValue(path)];
const pathStateExists = pathStateLookup.value[serializePath(toValue(path))];
const isCheckboxOrRadio = config?.type === 'checkbox' || config?.type === 'radio';
if (pathStateExists && isCheckboxOrRadio) {
pathStateExists.multiple = true;
Expand Down Expand Up @@ -343,12 +344,13 @@ export function useForm<
}) as PathState<TValues[TPath], TOutput[TPath]>;

pathStates.value.push(state);
pathStateLookup.value[pathValue] = state;
const pathString = serializePath(pathValue);
pathStateLookup.value[pathString] = state;
rebuildPathLookup();

if (errors.value[pathValue] && !initialErrors[pathValue]) {
if (errors.value[pathString] && !initialErrors[pathString]) {
nextTick(() => {
validateField(pathValue, { mode: 'silent' });
validateField(pathString, { mode: 'silent' });
});
}

Expand All @@ -357,7 +359,8 @@ export function useForm<
watch(path, newPath => {
rebuildPathLookup();
const nextValue = deepCopy(currentValue.value);
pathStateLookup.value[newPath] = state;
const pathString = serializePath(newPath);
pathStateLookup.value[pathString] = state;

nextTick(() => {
setInPath(formValues, newPath, nextValue);
Expand Down Expand Up @@ -842,7 +845,12 @@ export function useForm<
setFieldError(toValue(state.path) as Path<TValues>, undefined);
});

opts?.force ? forceSetValues(newValues, false) : setValues(newValues, false);
if (opts?.force) {
forceSetValues(newValues, false);
} else {
setValues(newValues, false);
}

setErrors(resetState?.errors || {});
submitCount.value = resetState?.submitCount || 0;
nextTick(() => {
Expand Down Expand Up @@ -963,7 +971,7 @@ export function useForm<
}
}

function setFieldInitialValue(path: string, value: unknown, updateOriginal = false) {
function setFieldInitialValue(path: Array<string | number> | string, value: unknown, updateOriginal = false) {
setInPath(initialValues.value, path, deepCopy(value));
if (updateOriginal) {
setInPath(originalInitialValues.value, path, deepCopy(value));
Expand Down
4 changes: 3 additions & 1 deletion packages/vee-validate/src/utils/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ export function isEmptyContainer(value: unknown): boolean {
/**
* Checks if the path opted out of nested fields using `[fieldName]` syntax
*/
export function isNotNestedPath(path: string) {
export function isNotNestedPath(path: Array<string | number> | string): path is string {
if (Array.isArray(path)) return false;

return /^\[.+\]$/i.test(path);
}

Expand Down
Loading