@goodie-forms/core 1.0.0-alpha → 1.1.1-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +757 -646
- package/dist/index.js.map +1 -1
- package/dist/src/form/CustomValidation.d.ts +10 -0
- package/dist/src/form/CustomValidation.d.ts.map +1 -0
- package/dist/src/form/Field.d.ts +31 -0
- package/dist/src/form/Field.d.ts.map +1 -0
- package/dist/src/form/FormController.d.ts +68 -0
- package/dist/src/form/FormController.d.ts.map +1 -0
- package/dist/{form → src/form}/FormField.d.ts +6 -3
- package/dist/src/form/FormField.d.ts.map +1 -0
- package/dist/src/form/NonullFormField.d.ts +10 -0
- package/dist/src/form/NonullFormField.d.ts.map +1 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/types/DeepPartial.d.ts +6 -0
- package/dist/src/types/DeepPartial.d.ts.map +1 -0
- package/dist/src/types/Mixin.d.ts +2 -0
- package/dist/src/types/Mixin.d.ts.map +1 -0
- package/dist/src/utils/ensureImmerability.d.ts +2 -0
- package/dist/src/utils/ensureImmerability.d.ts.map +1 -0
- package/dist/src/utils/getId.d.ts.map +1 -0
- package/dist/src/utils/removeBy.d.ts.map +1 -0
- package/dist/test/test1.d.ts +2 -0
- package/dist/test/test1.d.ts.map +1 -0
- package/package.json +27 -27
- package/src/form/CustomValidation.ts +52 -0
- package/src/form/Field.ts +334 -194
- package/src/form/FormController.ts +310 -290
- package/src/form/FormField.ts +202 -199
- package/src/form/NonullFormField.ts +21 -0
- package/src/index.ts +5 -3
- package/src/types/DeepPartial.ts +7 -3
- package/src/types/Mixin.ts +2 -0
- package/src/utils/ensureImmerability.ts +30 -0
- package/src/utils/getId.ts +5 -5
- package/src/utils/removeBy.ts +11 -11
- package/test/test1.ts +52 -0
- package/tsconfig.json +7 -7
- package/vite.config.ts +18 -18
- package/dist/form/Field.d.ts +0 -17
- package/dist/form/Field.d.ts.map +0 -1
- package/dist/form/FormController.d.ts +0 -62
- package/dist/form/FormController.d.ts.map +0 -1
- package/dist/form/FormField.d.ts.map +0 -1
- package/dist/index.d.ts +0 -4
- package/dist/index.d.ts.map +0 -1
- package/dist/types/DeepPartial.d.ts +0 -4
- package/dist/types/DeepPartial.d.ts.map +0 -1
- package/dist/utils/getId.d.ts.map +0 -1
- package/dist/utils/removeBy.d.ts.map +0 -1
- /package/dist/{utils → src/utils}/getId.d.ts +0 -0
- /package/dist/{utils → src/utils}/removeBy.d.ts +0 -0
package/src/form/Field.ts
CHANGED
|
@@ -1,194 +1,334 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
:
|
|
19
|
-
|
|
20
|
-
?
|
|
21
|
-
:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
1
|
+
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
|
|
3
|
+
export namespace Field {
|
|
4
|
+
type Unfoldable<T> = T & { _____foldMark?: never } & {};
|
|
5
|
+
|
|
6
|
+
export type Paths<TShape extends object> = PathsImpl<CanonicalPaths<TShape>>;
|
|
7
|
+
|
|
8
|
+
type PathsImpl<T> = T extends string
|
|
9
|
+
? T extends `${string}[*]${string}`
|
|
10
|
+
?
|
|
11
|
+
| ReplaceAll<T, "[*]", "[0]">
|
|
12
|
+
| Unfoldable<ReplaceAll<T, "[*]", `[${number}]`>>
|
|
13
|
+
: T
|
|
14
|
+
: never;
|
|
15
|
+
|
|
16
|
+
type CanonicalPaths<TShape extends object> = {
|
|
17
|
+
[K in keyof TShape & string]: NonNullable<TShape[K]> extends (
|
|
18
|
+
...args: any[]
|
|
19
|
+
) => any
|
|
20
|
+
? never
|
|
21
|
+
: NonNullable<TShape[K]> extends (infer U)[]
|
|
22
|
+
? U extends object
|
|
23
|
+
? K | `${K}[*]` | `${K}[*].${CanonicalPaths<NonNullable<U>>}`
|
|
24
|
+
: K | `${K}[*]`
|
|
25
|
+
: NonNullable<TShape[K]> extends object
|
|
26
|
+
? K | `${K}.${CanonicalPaths<NonNullable<TShape[K]>>}`
|
|
27
|
+
: K;
|
|
28
|
+
}[keyof TShape & string];
|
|
29
|
+
|
|
30
|
+
type ReplaceAll<
|
|
31
|
+
TString extends string,
|
|
32
|
+
TMatch extends string,
|
|
33
|
+
TReplace extends string | number
|
|
34
|
+
> = TString extends `${infer A}${TMatch}${infer B}`
|
|
35
|
+
? `${A}${TReplace}${ReplaceAll<B, TMatch, TReplace>}`
|
|
36
|
+
: TString;
|
|
37
|
+
|
|
38
|
+
export type GetValue<TShape, TPath extends string> = GetValueImpl<
|
|
39
|
+
TShape,
|
|
40
|
+
NormalizePath<TPath>
|
|
41
|
+
>;
|
|
42
|
+
|
|
43
|
+
type GetValueImpl<
|
|
44
|
+
TShape,
|
|
45
|
+
TPath extends string
|
|
46
|
+
> = TPath extends `${infer Head}.${infer Tail}`
|
|
47
|
+
? GetValueImpl<ResolveFragment<NonNullable<TShape>, Head>, Tail>
|
|
48
|
+
: ResolveFragment<NonNullable<TShape>, TPath>;
|
|
49
|
+
|
|
50
|
+
type NormalizePath<TPath extends string> =
|
|
51
|
+
TPath extends `${infer A}[${infer B}]${infer Rest}`
|
|
52
|
+
? NormalizePath<`${A}.${B}${Rest}`>
|
|
53
|
+
: TPath extends `.${infer R}`
|
|
54
|
+
? NormalizePath<R>
|
|
55
|
+
: TPath;
|
|
56
|
+
|
|
57
|
+
type ResolveFragment<
|
|
58
|
+
TShape,
|
|
59
|
+
TFragment extends string
|
|
60
|
+
> = TFragment extends `${number}`
|
|
61
|
+
? TShape extends readonly (infer U)[]
|
|
62
|
+
? U
|
|
63
|
+
: never
|
|
64
|
+
: TFragment extends keyof TShape
|
|
65
|
+
? TShape[TFragment]
|
|
66
|
+
: never;
|
|
67
|
+
|
|
68
|
+
export function deepEqual(
|
|
69
|
+
a: any,
|
|
70
|
+
b: any,
|
|
71
|
+
customComparator?: (a: any, b: any) => boolean | undefined
|
|
72
|
+
) {
|
|
73
|
+
if (a === b) return true;
|
|
74
|
+
|
|
75
|
+
if (a === null || b === null) return false;
|
|
76
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
77
|
+
|
|
78
|
+
// Arrays
|
|
79
|
+
if (Array.isArray(a)) {
|
|
80
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
81
|
+
for (let i = 0; i < a.length; i++) {
|
|
82
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Allow custom comparison
|
|
88
|
+
if (customComparator != null) {
|
|
89
|
+
const result = customComparator(a, b);
|
|
90
|
+
if (result !== undefined) return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Dates
|
|
94
|
+
if (a instanceof Date && b instanceof Date) {
|
|
95
|
+
return a.getTime() === b.getTime();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// RegExp
|
|
99
|
+
if (a instanceof RegExp && b instanceof RegExp) {
|
|
100
|
+
return a.source === b.source && a.flags === b.flags;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Map
|
|
104
|
+
if (a instanceof Map && b instanceof Map) {
|
|
105
|
+
if (a.size !== b.size) return false;
|
|
106
|
+
for (const [key, val] of a) {
|
|
107
|
+
if (!b.has(key) || !deepEqual(val, b.get(key))) return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Set
|
|
113
|
+
if (a instanceof Set && b instanceof Set) {
|
|
114
|
+
if (a.size !== b.size) return false;
|
|
115
|
+
for (const val of a) {
|
|
116
|
+
if (!b.has(val)) return false;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Plain / class objects
|
|
122
|
+
if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const keysA = Object.keys(a);
|
|
127
|
+
const keysB = Object.keys(b);
|
|
128
|
+
|
|
129
|
+
if (keysA.length !== keysB.length) return false;
|
|
130
|
+
|
|
131
|
+
for (const key of keysA) {
|
|
132
|
+
if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
|
|
133
|
+
if (!deepEqual(a[key], b[key])) return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function diff<T>(
|
|
140
|
+
prev: readonly T[],
|
|
141
|
+
next: readonly T[],
|
|
142
|
+
equals: (a: T, b: T) => boolean,
|
|
143
|
+
filter?: (a: T) => boolean
|
|
144
|
+
) {
|
|
145
|
+
const added: T[] = [];
|
|
146
|
+
const removed: T[] = [];
|
|
147
|
+
const unchanged: T[] = [];
|
|
148
|
+
|
|
149
|
+
for (const n of next) {
|
|
150
|
+
if (prev.some((p) => equals(p, n))) {
|
|
151
|
+
if (filter?.(n) ?? true) unchanged.push(n);
|
|
152
|
+
} else {
|
|
153
|
+
if (filter?.(n) ?? true) added.push(n);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (const p of prev) {
|
|
158
|
+
if (!next.some((n) => equals(p, n))) {
|
|
159
|
+
if (filter?.(p) ?? true) removed.push(p);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { added, removed, unchanged };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function parsePathFragments(path: string) {
|
|
167
|
+
const result: Array<string | number> = [];
|
|
168
|
+
|
|
169
|
+
let i = 0;
|
|
170
|
+
|
|
171
|
+
while (i < path.length) {
|
|
172
|
+
const char = path[i];
|
|
173
|
+
|
|
174
|
+
// dot separator
|
|
175
|
+
if (char === ".") {
|
|
176
|
+
i++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// bracket index: [123]
|
|
181
|
+
if (char === "[") {
|
|
182
|
+
i++; // skip '['
|
|
183
|
+
let num = "";
|
|
184
|
+
|
|
185
|
+
while (i < path.length && path[i] !== "]") {
|
|
186
|
+
num += path[i];
|
|
187
|
+
i++;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
i++; // skip ']'
|
|
191
|
+
|
|
192
|
+
if (!num || !/^\d+$/.test(num)) {
|
|
193
|
+
throw new Error(`Invalid array index in path: ${path}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
result.push(Number(num));
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// identifier
|
|
201
|
+
let key = "";
|
|
202
|
+
while (i < path.length && /[^\.\[]/.test(path[i])) {
|
|
203
|
+
key += path[i];
|
|
204
|
+
i++;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (key) {
|
|
208
|
+
result.push(key);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function parsePath(
|
|
216
|
+
fragments: readonly (PropertyKey | StandardSchemaV1.PathSegment)[]
|
|
217
|
+
) {
|
|
218
|
+
let result = "";
|
|
219
|
+
|
|
220
|
+
for (const fragment of fragments) {
|
|
221
|
+
const pathFragment =
|
|
222
|
+
typeof fragment === "object" && "key" in fragment
|
|
223
|
+
? fragment.key
|
|
224
|
+
: fragment;
|
|
225
|
+
|
|
226
|
+
if (typeof pathFragment === "number") {
|
|
227
|
+
result += `[${pathFragment}]`;
|
|
228
|
+
} else {
|
|
229
|
+
if (result.length > 0) {
|
|
230
|
+
result += ".";
|
|
231
|
+
}
|
|
232
|
+
result += pathFragment.toString();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function isDescendant(parentPath: string, childPath: string) {
|
|
240
|
+
const parentFrags = parsePathFragments(parentPath);
|
|
241
|
+
const childFrags = parsePathFragments(childPath);
|
|
242
|
+
|
|
243
|
+
if (parentFrags.length >= childFrags.length) return false;
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i < parentFrags.length; i++) {
|
|
246
|
+
if (parentFrags[i] !== childFrags[i]) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function getValue<
|
|
255
|
+
TShape extends object,
|
|
256
|
+
TPath extends Field.Paths<TShape>
|
|
257
|
+
>(data: TShape, path: TPath): Field.GetValue<TShape, TPath> | undefined {
|
|
258
|
+
if (data == null) return undefined;
|
|
259
|
+
|
|
260
|
+
const pathFragments = parsePathFragments(path);
|
|
261
|
+
|
|
262
|
+
let current: any = data;
|
|
263
|
+
|
|
264
|
+
for (const pathFragment of pathFragments) {
|
|
265
|
+
if (current == null) return undefined;
|
|
266
|
+
current = current[pathFragment];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return current;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function setValue<
|
|
273
|
+
TShape extends object,
|
|
274
|
+
TPath extends Field.Paths<TShape>
|
|
275
|
+
>(data: TShape, key: TPath, value: Field.GetValue<TShape, TPath>) {
|
|
276
|
+
return modifyValue(data, key, () => value);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function modifyValue<
|
|
280
|
+
TShape extends object,
|
|
281
|
+
TPath extends Field.Paths<TShape>
|
|
282
|
+
>(
|
|
283
|
+
data: TShape,
|
|
284
|
+
path: TPath,
|
|
285
|
+
modifier: (
|
|
286
|
+
currentValue: Field.GetValue<TShape, TPath>
|
|
287
|
+
) => Field.GetValue<TShape, TPath> | void
|
|
288
|
+
) {
|
|
289
|
+
const pathFragments = parsePathFragments(path);
|
|
290
|
+
|
|
291
|
+
let current: any = data;
|
|
292
|
+
|
|
293
|
+
for (let i = 0; i < pathFragments.length - 1; i++) {
|
|
294
|
+
const pathFragment = pathFragments[i];
|
|
295
|
+
const nextFragment = pathFragments[i + 1];
|
|
296
|
+
|
|
297
|
+
if (current[pathFragment] == null) {
|
|
298
|
+
current[pathFragment] = typeof nextFragment === "number" ? [] : {};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
current = current[pathFragment];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const lastFragment = pathFragments[pathFragments.length - 1];
|
|
305
|
+
|
|
306
|
+
const oldValue = current[lastFragment];
|
|
307
|
+
const newValue = modifier(oldValue);
|
|
308
|
+
|
|
309
|
+
if (newValue !== undefined) {
|
|
310
|
+
current[lastFragment] = newValue;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function deleteValue<
|
|
315
|
+
TShape extends object,
|
|
316
|
+
TPath extends Field.Paths<TShape>
|
|
317
|
+
>(data: TShape, path: TPath) {
|
|
318
|
+
const pathFragments = parsePathFragments(path);
|
|
319
|
+
|
|
320
|
+
let current: any = data;
|
|
321
|
+
|
|
322
|
+
for (let i = 0; i < pathFragments.length - 1; i++) {
|
|
323
|
+
const pathFragment = pathFragments[i];
|
|
324
|
+
|
|
325
|
+
if (current[pathFragment] == null) return;
|
|
326
|
+
|
|
327
|
+
current = current[pathFragment];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const lastFragment = pathFragments[pathFragments.length - 1];
|
|
331
|
+
|
|
332
|
+
delete current[lastFragment];
|
|
333
|
+
}
|
|
334
|
+
}
|