Skip to content

Commit

Permalink
fixed collection validation
Browse files Browse the repository at this point in the history
  • Loading branch information
victorgarciaesgi committed Oct 18, 2023
1 parent 9629180 commit 3bd6621
Show file tree
Hide file tree
Showing 27 changed files with 409 additions and 135 deletions.
17 changes: 0 additions & 17 deletions packages/core/src/core/defineCustomValidators.ts

This file was deleted.

21 changes: 21 additions & 0 deletions packages/core/src/core/defineRegleOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AllRulesDeclarations, RegleBehaviourOptions } from '../types';
import { createUseRegleComposable } from './useRegle';

/**
* Root function that allows you to define project-wise all your custom validators or overwrite default ones
*
* It will return utility functions that let you build type-safe forms
*
* @param customRules
*/
export function defineRegleOptions<TCustomRules extends Partial<AllRulesDeclarations>>({
rules,
options,
}: {
rules?: () => TCustomRules;
options?: RegleBehaviourOptions;
}) {
const useRegle = createUseRegleComposable<TCustomRules>(rules, options);

return useRegle;
}
4 changes: 2 additions & 2 deletions packages/core/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { createRule, unwrapRuleParameters } from './createRule';
export * from './useRegle';
export { defineCustomValidators } from './defineCustomValidators';
export { useRegle } from './useRegle';
export { defineRegleOptions } from './defineRegleOptions';
26 changes: 22 additions & 4 deletions packages/core/src/core/useRegle/useRegle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,46 @@ import {
$InternalReglePartialValidationTree,
AllRulesDeclarations,
Regle,
RegleBehaviourOptions,
RegleErrorTree,
ReglePartialValidationTree,
RegleStatus,
} from '../../types';
import { useStateProperties } from './useStateProperties';
import { PartialDeep } from 'type-fest';
import { DeepSafeFormState } from 'types/core/useRegle.type';
import { PartialDeep, RequiredDeep } from 'type-fest';
import { DeepSafeFormState } from 'types/core/useRegle.types';
import { DeepMaybeRef } from 'types/utils';

export function createUseRegleComposable<TCustomRules extends Partial<AllRulesDeclarations>>(
customRules?: () => TCustomRules
customRules?: () => TCustomRules,
options?: RegleBehaviourOptions
) {
const globalOptions: RequiredDeep<RegleBehaviourOptions> = {
autoDirty: options?.autoDirty ?? true,
lazy: options?.lazy ?? false,
rewardEarly: options?.rewardEarly ?? false,
};

function useRegle<
TState extends Record<string, any>,
TRules extends ReglePartialValidationTree<TState, Partial<AllRulesDeclarations> & TCustomRules>,
>(state: Ref<TState>, rulesFactory: (() => TRules) | ComputedRef<TRules>): Regle<TState, TRules> {
>(
state: Ref<TState>,
rulesFactory: (() => TRules) | ComputedRef<TRules>,
options?: Partial<DeepMaybeRef<Required<RegleBehaviourOptions>>>
): Regle<TState, TRules> {
const scopeRules = isRef(rulesFactory) ? rulesFactory : computed(rulesFactory);
const resolvedOptions: DeepMaybeRef<RequiredDeep<RegleBehaviourOptions>> = {
...globalOptions,
...options,
};

const initialState = shallowRef<TState>(structuredClone(toRaw(state.value)));

const { $regle, errors } = useStateProperties(
scopeRules as ComputedRef<$InternalReglePartialValidationTree>,
state,
resolvedOptions,
customRules
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,73 @@
import { Ref, reactive, ref, toRef, watch, watchEffect } from 'vue';
import { RequiredDeep } from 'type-fest';
import { Ref, reactive, ref, toRef, toRefs, watch } from 'vue';
import type {
$InternalFormPropertyTypes,
$InternalRegleCollectionRuleDecl,
$InternalRegleCollectionStatus,
$InternalRegleFieldStatus,
$InternalRegleStatusType,
CustomRulesDeclarationTree,
RegleBehaviourOptions,
} from '../../../types';
import { DeepMaybeRef } from '../../../types';
import { RegleStorage } from '../../useStorage';
import { createReactiveFieldStatus } from './createReactiveFieldStatus';
import { createReactiveChildrenStatus } from './createReactiveNestedStatus';
import { RegleStorage } from '../../useStorage';
import { randomId } from '../../../utils/randomId';

interface CreateReactiveCollectionStatusArgs {
state: Ref<unknown>;
rulesDef: Ref<$InternalRegleCollectionRuleDecl>;
customMessages?: CustomRulesDeclarationTree;
path: string;
storage: RegleStorage;
options: DeepMaybeRef<RequiredDeep<RegleBehaviourOptions>>;
}

function createCollectionElement({
path,
index,
options,
storage,
value,
customMessages,
rules,
}: {
path: string;
index: number;
value: any[];
customMessages?: CustomRulesDeclarationTree;
storage: RegleStorage;
options: DeepMaybeRef<RequiredDeep<RegleBehaviourOptions>>;
rules: $InternalFormPropertyTypes;
}): $InternalRegleStatusType | null {
const $path = `${path}.${index}`;
const $id = randomId();

if (!value[index].$id) {
Object.defineProperties(value[index], {
$id: {
value: $id,
},
});
}

const $state = toRefs(value);
const $status = createReactiveChildrenStatus({
state: $state[index],
rulesDef: toRef(() => rules),
customMessages,
path: $path,
storage,
options,
});

if ($status) {
$status.$id = value[index].$id ?? $id;
storage.addArrayStatus($status.$id!, $status);
}

return $status;
}

export function createReactiveCollectionStatus({
Expand All @@ -24,6 +76,7 @@ export function createReactiveCollectionStatus({
customMessages,
path,
storage,
options,
}: CreateReactiveCollectionStatusArgs): $InternalRegleCollectionStatus | null {
if (Array.isArray(state.value) && !rulesDef.value.$each) {
return null;
Expand All @@ -45,17 +98,18 @@ export function createReactiveCollectionStatus({
customMessages,
path,
storage,
options,
});

if (Array.isArray(state.value) && $each) {
$eachStatus.value = state.value
.map((value, index) => {
const $path = `${path}.${index}`;
return createReactiveChildrenStatus({
state: toRef(() => value),
rulesDef: toRef(() => $each),
customMessages,
path: $path,
return createCollectionElement({
path,
rules: $each,
value: state.value as any[],
index,
options,
storage,
});
})
Expand All @@ -65,6 +119,43 @@ export function createReactiveCollectionStatus({
}
}

function updateChildrenStatus() {
const { $each } = rulesDef.value;
if (Array.isArray(state.value) && $eachStatus.value && $each) {
state.value.forEach((value, index) => {
if (value.$id) {
const previousStatus = storage.getArrayStatus(value.$id);
if (previousStatus) {
$eachStatus.value[index] = previousStatus;
}
} else {
const newElement = createCollectionElement({
value: state.value as any[],
rules: $each,
customMessages,
path,
storage,
options,
index,
});
if (newElement) {
$eachStatus.value[index] = newElement;
}
}
});
}

// cleanup removed elements from array

if ($eachStatus.value) {
const deletedItems = $eachStatus.value.filter(($each) => {
return Array.isArray(state.value) && !state.value.find((val) => val.$id === $each.$id);
});

deletedItems.forEach((item) => item.$unwatch());
}
}

function $unwatch() {
if ($unwatchState) {
$unwatchState();
Expand All @@ -83,16 +174,46 @@ export function createReactiveCollectionStatus({
$unwatchState = watch(
state,
() => {
createStatus();
updateChildrenStatus();
},
{ deep: true, flush: 'sync' }
);
}

function $touch(): void {
$fieldStatus.value.$touch();
$eachStatus.value.forEach(($each) => {
$each.$touch();
});
}

function $reset(): void {
$fieldStatus.value.$reset();
$eachStatus.value.forEach(($each) => {
$each.$reset();
});
}

async function $validate(): Promise<boolean> {
try {
const results = await Promise.all(
$eachStatus.value.map((rule) => {
return rule.$validate();
})
);
return results.every((value) => !!value);
} catch (e) {
return false;
}
}

return reactive({
...$fieldStatus.value,
$each: $eachStatus,
$validate,
$unwatch,
$watch,
$touch,
$reset,
}) satisfies $InternalRegleCollectionStatus;
}
Loading

0 comments on commit 3bd6621

Please sign in to comment.