Skip to content

Commit 7b3b83d

Browse files
committed
feat(validation): add $touch/$reset methods and some return changes
fix #753
1 parent a9a5f29 commit 7b3b83d

File tree

3 files changed

+103
-22
lines changed

3 files changed

+103
-22
lines changed

docs/composable/validation/validation.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ type ValidatorObject<T> = {
6060
// typed validationObject
6161
type ValidationObject<T> = {
6262
$value: T | Ref<T>;
63+
$touch(): void;
64+
$reset(): void;
6365
} & Record<string, ValidatorFunction<T> | ValidatorObject<T>>;
6466

6567
const validationUsername = useValidation({
@@ -119,6 +121,8 @@ interface ValidationValue<T> {
119121
$errors: any[]; // array of errors
120122

121123
toObject(): T;
124+
$touch(): void;
125+
$reset(): void;
122126
}
123127

124128
// validator
@@ -212,6 +216,9 @@ form.personal.$anyInvalid;
212216
form.personal.$errors;
213217

214218
form.toObject(); // returns { settings: { email: '' }, personal: { name: { first: '', last: '' } } }
219+
220+
form.$touch(); // sets all the validations to `$dirty: true`
221+
form.$reset(); // sets all the validations to `$dirty: false`
215222
```
216223

217224
```ts

packages/vue-composable/__tests__/validation/validation.spec.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ describe("validation", () => {
215215
});
216216

217217
expect(v.input.$errors).toMatchObject([$message]);
218-
expect(v.otherInput.$errors).toMatchObject([]);
218+
expect(v.otherInput.$errors).toMatchObject([true]);
219219
});
220220

221221
describe("object", () => {
@@ -402,4 +402,55 @@ describe("validation", () => {
402402
);
403403
});
404404
});
405+
406+
describe("touch/reset", () => {
407+
it("should run validation", () => {
408+
const required = (x: any) => !!x;
409+
const v = useValidation({
410+
test: {
411+
$value: "",
412+
required,
413+
},
414+
deep: {
415+
v1: {
416+
$value: "",
417+
required,
418+
},
419+
v2: {
420+
$value: "",
421+
required,
422+
},
423+
},
424+
});
425+
426+
expect(v.$anyDirty).toBe(false);
427+
428+
expect(v.test.$dirty).toBe(false);
429+
expect(v.test.$anyInvalid).toBe(true);
430+
v.test.$touch();
431+
expect(v.test.$dirty).toBe(true);
432+
v.test.$reset();
433+
expect(v.test.$dirty).toBe(false);
434+
435+
expect(v.deep.v1.$dirty).toBe(false);
436+
expect(v.deep.v2.$dirty).toBe(false);
437+
v.deep.$touch();
438+
expect(v.deep.v1.$dirty).toBe(true);
439+
expect(v.deep.v2.$dirty).toBe(true);
440+
v.deep.$reset();
441+
expect(v.deep.v1.$dirty).toBe(false);
442+
expect(v.deep.v2.$dirty).toBe(false);
443+
444+
v.$touch();
445+
expect(v.$anyDirty).toBe(true);
446+
expect(v.test.$dirty).toBe(true);
447+
expect(v.deep.v1.$dirty).toBe(true);
448+
expect(v.deep.v2.$dirty).toBe(true);
449+
v.$reset();
450+
expect(v.test.$dirty).toBe(false);
451+
expect(v.deep.v1.$dirty).toBe(false);
452+
expect(v.deep.v2.$dirty).toBe(false);
453+
expect(v.$anyDirty).toBe(false);
454+
});
455+
});
405456
});

packages/vue-composable/src/validation/validation.ts

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ interface ValidationValue<T> {
2727
$errors: Array<any>;
2828
$anyInvalid: boolean;
2929

30-
// $touch(): void;
31-
// $reset(): void;
30+
$touch(): void;
31+
$reset(): void;
3232
}
3333

3434
interface ValidatorResult {
@@ -40,6 +40,9 @@ interface ValidationGroupResult {
4040
$anyDirty: boolean;
4141
$errors: Array<any>;
4242
$anyInvalid: boolean;
43+
44+
$touch(): void;
45+
$reset(): void;
4346
}
4447

4548
interface ValidatorResultPromise {
@@ -132,7 +135,7 @@ const buildValidationFunction = (
132135
) => {
133136
const $promise: Ref<Promise<boolean> | null> = ref(null);
134137
const $pending = ref(false);
135-
const $error = ref<Error | string>();
138+
const $error = ref<Error | string | true>();
136139
const $invalid = ref(false);
137140
let context: any = undefined;
138141

@@ -147,7 +150,8 @@ const buildValidationFunction = (
147150
} else {
148151
$invalid.value = !result;
149152
}
150-
$error.value = $invalid.value ? m.value : undefined;
153+
// @ts-ignore
154+
$error.value = $invalid.value ? m.value || true : undefined;
151155
} catch (e) {
152156
$invalid.value = true;
153157
throw e;
@@ -179,11 +183,17 @@ const buildValidationFunction = (
179183
);
180184
});
181185

186+
function $touch() {
187+
onChange(r.value);
188+
}
189+
182190
return {
183191
$promise,
184192
$pending,
185193
$invalid,
186194
$error,
195+
196+
$touch,
187197
};
188198
};
189199

@@ -196,19 +206,21 @@ const buildValidationValue = (
196206
? v
197207
: { $validator: v, $message: undefined };
198208

199-
const { $pending, $promise, $invalid, $error } = buildValidationFunction(
200-
r,
201-
$validator,
202-
ref($message),
203-
handlers
204-
);
209+
const {
210+
$pending,
211+
$promise,
212+
$invalid,
213+
$error,
214+
$touch,
215+
} = buildValidationFunction(r, $validator, ref($message), handlers);
205216

206217
return {
207218
$pending,
208219
$error,
209220
$promise,
210221
$invalid,
211222
$message,
223+
$touch,
212224
...$rest,
213225
} as any;
214226
};
@@ -234,6 +246,8 @@ const buildValidation = <T>(
234246
);
235247

236248
(r as any)["$dirty"] = $dirty;
249+
(r as any)["$reset"] = () => ($dirty.value = false);
250+
(r as any)["$touch"] = () => ($dirty.value = true);
237251

238252
// @ts-ignore
239253
r.toObject = () => unwrap($value);
@@ -251,13 +265,8 @@ const buildValidation = <T>(
251265
handlers
252266
);
253267

254-
r[k] = {
255-
...validation,
256-
$value,
257-
toObject() {
258-
return unwrap($value);
259-
},
260-
} as any;
268+
// @ts-expect-error no valid type
269+
r[k] = validation;
261270
} else {
262271
const validation = buildValidation(
263272
(o as Record<string, any>)[k],
@@ -279,7 +288,7 @@ const buildValidation = <T>(
279288
validations
280289
.map((x) => x.$error)
281290
.map((x) => unwrap(x))
282-
.filter(Boolean)
291+
.filter((x) => x !== undefined)
283292
);
284293
// $anyDirty = computed(() => validations.some(x => !!x));
285294
$anyInvalid = computed(() =>
@@ -292,7 +301,6 @@ const buildValidation = <T>(
292301
return Object.keys(validation)
293302
.filter((x) => x[0] !== "$")
294303
.reduce((p, c) => {
295-
debugger;
296304
//@ts-ignore
297305
p[c] = validation[c].toObject();
298306
return p;
@@ -305,15 +313,15 @@ const buildValidation = <T>(
305313
$errors = computed(() => {
306314
return validations
307315
.map((x) => unwrap(x.$errors))
308-
.filter(Boolean)
316+
.filter((x) => x !== undefined)
309317
.filter((x) => {
310318
return x.some(Boolean);
311319
});
312320
});
313321
$anyDirty = computed(() => {
314322
return validations.some((x) => {
315323
return (
316-
x.$anyDirty ||
324+
unwrap(x.$anyDirty) ||
317325
(isBoolean(unwrap((x as any).$dirty)) &&
318326
unwrap((x as any).$dirty))
319327
);
@@ -345,6 +353,21 @@ const buildValidation = <T>(
345353

346354
if ($anyDirty) {
347355
(r[k] as any).$anyDirty = $anyDirty;
356+
357+
const keys = Object.keys(r[k]).filter(
358+
(x) => x[0] !== "$" && isObject(r[k][x])
359+
);
360+
r[k].$touch = () => {
361+
// r[k].
362+
keys.forEach((m) => {
363+
r[k][m].$touch?.();
364+
});
365+
};
366+
r[k].$reset = () => {
367+
keys.forEach((m) => {
368+
r[k][m].$reset?.();
369+
});
370+
};
348371
}
349372
}
350373
}

0 commit comments

Comments
 (0)