|
1 | | -import type { SvelteMap } from "svelte/reactivity"; |
| 1 | +import { SvelteMap } from "svelte/reactivity"; |
2 | 2 |
|
3 | 3 | import { getNodeByKeys, insertValue } from "@/lib/trie.js"; |
4 | 4 | import type { RPath } from "@/core/index.js"; |
5 | 5 |
|
6 | | -import type { FormErrorsMap } from "./errors.js"; |
7 | 6 | import type { ValidationError } from "./validator.js"; |
8 | | -import type { PathTrieRef } from "./model.js"; |
| 7 | +import type { PathTrieRef, Update } from "./model.js"; |
9 | 8 | import type { FieldPath } from "./id.js"; |
10 | 9 | import type { FieldState } from "./field-state.js"; |
11 | 10 |
|
@@ -57,28 +56,103 @@ export function internalRegisterFieldPath( |
57 | 56 | return p; |
58 | 57 | } |
59 | 58 |
|
60 | | -export function internalAssignErrors( |
61 | | - ref: PathTrieRef<FieldPath>, |
62 | | - map: FormErrorsMap, |
63 | | - errors: ReadonlyArray<ValidationError> |
64 | | -): FormErrorsMap { |
65 | | - map.clear(); |
66 | | - for (const { path, message } of errors) { |
67 | | - const p = internalRegisterFieldPath(ref, path); |
68 | | - const arr = map.get(p); |
69 | | - if (arr) { |
70 | | - arr.push(message); |
71 | | - } else { |
72 | | - map.set(p, [message]); |
73 | | - } |
74 | | - } |
75 | | - return map; |
76 | | -} |
77 | | - |
78 | 59 | export function internalHasFieldState( |
79 | 60 | map: SvelteMap<FieldPath, FieldState>, |
80 | 61 | path: FieldPath, |
81 | 62 | state: FieldState |
82 | 63 | ) { |
83 | 64 | return ((map.get(path) ?? 0) & state) > 0; |
84 | 65 | } |
| 66 | + |
| 67 | +/** |
| 68 | +This class must maintain two invariants: |
| 69 | +- Field errors list should contain at leas one error |
| 70 | +- Errors in the field errors list must be unique |
| 71 | +**/ |
| 72 | +export class FormErrors { |
| 73 | + #map = new SvelteMap< |
| 74 | + FieldPath, |
| 75 | + { |
| 76 | + set: Set<string>; |
| 77 | + array: string[]; |
| 78 | + } |
| 79 | + >(); |
| 80 | + |
| 81 | + constructor(private readonly ref: PathTrieRef<FieldPath>) {} |
| 82 | + |
| 83 | + assign(entries: Iterable<readonly [FieldPath, string[]]>) { |
| 84 | + this.#map.clear(); |
| 85 | + for (const entry of entries) { |
| 86 | + let array = entry[1]; |
| 87 | + const set = new Set(array); |
| 88 | + if (array.length > set.size) { |
| 89 | + array = Array.from(set); |
| 90 | + } |
| 91 | + this.#map.set(entry[0], { |
| 92 | + set, |
| 93 | + array, |
| 94 | + }); |
| 95 | + } |
| 96 | + return this; |
| 97 | + } |
| 98 | + |
| 99 | + updateErrors(errors: ReadonlyArray<ValidationError>): this { |
| 100 | + this.#map.clear(); |
| 101 | + for (const { path, message } of errors) { |
| 102 | + const p = internalRegisterFieldPath(this.ref, path); |
| 103 | + const field = this.#map.get(p); |
| 104 | + if (field) { |
| 105 | + const l = field.set.size; |
| 106 | + field.set.add(message); |
| 107 | + if (l < field.set.size) { |
| 108 | + field.array.push(message); |
| 109 | + } |
| 110 | + } else { |
| 111 | + const array = [message]; |
| 112 | + this.#map.set(p, { |
| 113 | + set: new Set(array), |
| 114 | + array, |
| 115 | + }); |
| 116 | + } |
| 117 | + } |
| 118 | + return this; |
| 119 | + } |
| 120 | + |
| 121 | + getFieldErrors(path: FieldPath): ReadonlyArray<string> | undefined { |
| 122 | + return this.#map.get(path)?.array; |
| 123 | + } |
| 124 | + |
| 125 | + updateFieldErrors(path: FieldPath, errors: Update<string[]>) { |
| 126 | + if (typeof errors === "function") { |
| 127 | + const arr = this.#map.get(path)?.array ?? []; |
| 128 | + errors = errors(arr); |
| 129 | + } |
| 130 | + if (errors.length > 0) { |
| 131 | + const set = new Set(errors); |
| 132 | + this.#map.set(path, { |
| 133 | + set, |
| 134 | + array: Array.from(set), |
| 135 | + }); |
| 136 | + } else { |
| 137 | + this.#map.delete(path); |
| 138 | + } |
| 139 | + return errors.length === 0; |
| 140 | + } |
| 141 | + |
| 142 | + hasErrors() { |
| 143 | + return this.#map.size > 0; |
| 144 | + } |
| 145 | + |
| 146 | + clear() { |
| 147 | + this.#map.clear() |
| 148 | + } |
| 149 | + |
| 150 | + *[Symbol.iterator]() { |
| 151 | + const casted: [FieldPath, string[]] = [[] as RPath as FieldPath, []]; |
| 152 | + for (const pair of this.#map) { |
| 153 | + casted[0] = pair[0]; |
| 154 | + casted[1] = pair[1].array; |
| 155 | + yield casted; |
| 156 | + } |
| 157 | + } |
| 158 | +} |
0 commit comments