@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.
Files changed (52) hide show
  1. package/dist/index.js +757 -646
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/form/CustomValidation.d.ts +10 -0
  4. package/dist/src/form/CustomValidation.d.ts.map +1 -0
  5. package/dist/src/form/Field.d.ts +31 -0
  6. package/dist/src/form/Field.d.ts.map +1 -0
  7. package/dist/src/form/FormController.d.ts +68 -0
  8. package/dist/src/form/FormController.d.ts.map +1 -0
  9. package/dist/{form → src/form}/FormField.d.ts +6 -3
  10. package/dist/src/form/FormField.d.ts.map +1 -0
  11. package/dist/src/form/NonullFormField.d.ts +10 -0
  12. package/dist/src/form/NonullFormField.d.ts.map +1 -0
  13. package/dist/src/index.d.ts +6 -0
  14. package/dist/src/index.d.ts.map +1 -0
  15. package/dist/src/types/DeepPartial.d.ts +6 -0
  16. package/dist/src/types/DeepPartial.d.ts.map +1 -0
  17. package/dist/src/types/Mixin.d.ts +2 -0
  18. package/dist/src/types/Mixin.d.ts.map +1 -0
  19. package/dist/src/utils/ensureImmerability.d.ts +2 -0
  20. package/dist/src/utils/ensureImmerability.d.ts.map +1 -0
  21. package/dist/src/utils/getId.d.ts.map +1 -0
  22. package/dist/src/utils/removeBy.d.ts.map +1 -0
  23. package/dist/test/test1.d.ts +2 -0
  24. package/dist/test/test1.d.ts.map +1 -0
  25. package/package.json +27 -27
  26. package/src/form/CustomValidation.ts +52 -0
  27. package/src/form/Field.ts +334 -194
  28. package/src/form/FormController.ts +310 -290
  29. package/src/form/FormField.ts +202 -199
  30. package/src/form/NonullFormField.ts +21 -0
  31. package/src/index.ts +5 -3
  32. package/src/types/DeepPartial.ts +7 -3
  33. package/src/types/Mixin.ts +2 -0
  34. package/src/utils/ensureImmerability.ts +30 -0
  35. package/src/utils/getId.ts +5 -5
  36. package/src/utils/removeBy.ts +11 -11
  37. package/test/test1.ts +52 -0
  38. package/tsconfig.json +7 -7
  39. package/vite.config.ts +18 -18
  40. package/dist/form/Field.d.ts +0 -17
  41. package/dist/form/Field.d.ts.map +0 -1
  42. package/dist/form/FormController.d.ts +0 -62
  43. package/dist/form/FormController.d.ts.map +0 -1
  44. package/dist/form/FormField.d.ts.map +0 -1
  45. package/dist/index.d.ts +0 -4
  46. package/dist/index.d.ts.map +0 -1
  47. package/dist/types/DeepPartial.d.ts +0 -4
  48. package/dist/types/DeepPartial.d.ts.map +0 -1
  49. package/dist/utils/getId.d.ts.map +0 -1
  50. package/dist/utils/removeBy.d.ts.map +0 -1
  51. /package/dist/{utils → src/utils}/getId.d.ts +0 -0
  52. /package/dist/{utils → src/utils}/removeBy.d.ts +0 -0
package/src/form/Field.ts CHANGED
@@ -1,194 +1,334 @@
1
- export namespace Field {
2
- export type Paths<TShape extends object> = {
3
- [K in keyof TShape & string]: NonNullable<TShape[K]> extends (
4
- ...args: any[]
5
- ) => any
6
- ? never
7
- : NonNullable<TShape[K]> extends object
8
- ? K | `${K}.${Paths<NonNullable<TShape[K]>>}`
9
- : K;
10
- }[keyof TShape & string];
11
-
12
- export type GetValue<
13
- TShape extends object,
14
- TPath extends string,
15
- > = TPath extends `${infer K}.${infer Rest}`
16
- ? K extends keyof TShape
17
- ? GetValue<NonNullable<TShape[K]>, Rest>
18
- : never
19
- : TPath extends keyof TShape
20
- ? TShape[TPath]
21
- : never;
22
-
23
- export function getValue<
24
- TShape extends object,
25
- TPath extends Field.Paths<TShape>,
26
- >(data: TShape, path: TPath): Field.GetValue<TShape, TPath> | undefined {
27
- if (data == null) return undefined;
28
-
29
- const parts = (path as string).split(".");
30
-
31
- let current: any = data;
32
-
33
- for (const part of parts) {
34
- if (current == null) return undefined;
35
- current = current[part];
36
- }
37
-
38
- return current;
39
- }
40
-
41
- export function deepEqual(
42
- a: any,
43
- b: any,
44
- customComparator?: (a: any, b: any) => boolean | undefined,
45
- ) {
46
- if (a === b) return true;
47
-
48
- if (a === null || b === null) return false;
49
- if (typeof a !== "object" || typeof b !== "object") return false;
50
-
51
- // Arrays
52
- if (Array.isArray(a)) {
53
- if (!Array.isArray(b) || a.length !== b.length) return false;
54
- for (let i = 0; i < a.length; i++) {
55
- if (!deepEqual(a[i], b[i])) return false;
56
- }
57
- return true;
58
- }
59
-
60
- // Allow custom comparison
61
- if (customComparator != null) {
62
- const result = customComparator(a, b);
63
- if (result !== undefined) return result;
64
- }
65
-
66
- // Dates
67
- if (a instanceof Date && b instanceof Date) {
68
- return a.getTime() === b.getTime();
69
- }
70
-
71
- // RegExp
72
- if (a instanceof RegExp && b instanceof RegExp) {
73
- return a.source === b.source && a.flags === b.flags;
74
- }
75
-
76
- // Map
77
- if (a instanceof Map && b instanceof Map) {
78
- if (a.size !== b.size) return false;
79
- for (const [key, val] of a) {
80
- if (!b.has(key) || !deepEqual(val, b.get(key))) return false;
81
- }
82
- return true;
83
- }
84
-
85
- // Set
86
- if (a instanceof Set && b instanceof Set) {
87
- if (a.size !== b.size) return false;
88
- for (const val of a) {
89
- if (!b.has(val)) return false;
90
- }
91
- return true;
92
- }
93
-
94
- // Plain / class objects
95
- if (Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
96
- return false;
97
- }
98
-
99
- const keysA = Object.keys(a);
100
- const keysB = Object.keys(b);
101
-
102
- if (keysA.length !== keysB.length) return false;
103
-
104
- for (const key of keysA) {
105
- if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
106
- if (!deepEqual(a[key], b[key])) return false;
107
- }
108
-
109
- return true;
110
- }
111
-
112
- export function diff<T>(
113
- prev: readonly T[],
114
- next: readonly T[],
115
- equals: (a: T, b: T) => boolean,
116
- filter?: (a: T) => boolean,
117
- ) {
118
- const added: T[] = [];
119
- const removed: T[] = [];
120
- const unchanged: T[] = [];
121
-
122
- for (const n of next) {
123
- if (prev.some((p) => equals(p, n))) {
124
- if (filter?.(n) ?? true) unchanged.push(n);
125
- } else {
126
- if (filter?.(n) ?? true) added.push(n);
127
- }
128
- }
129
-
130
- for (const p of prev) {
131
- if (!next.some((n) => equals(p, n))) {
132
- if (filter?.(p) ?? true) removed.push(p);
133
- }
134
- }
135
-
136
- return { added, removed, unchanged };
137
- }
138
-
139
- export function setValue<
140
- TShape extends object,
141
- TPath extends Field.Paths<TShape>,
142
- >(data: TShape, key: TPath, value: Field.GetValue<TShape, TPath>) {
143
- return modifyValue(data, key, () => value);
144
- }
145
-
146
- export function modifyValue<
147
- TShape extends object,
148
- TPath extends Field.Paths<TShape>,
149
- >(
150
- data: TShape,
151
- key: TPath,
152
- modifier: (
153
- currentValue: Field.GetValue<TShape, TPath>,
154
- ) => Field.GetValue<TShape, TPath> | void,
155
- ) {
156
- const parts = (key as string).split(".");
157
-
158
- let current: any = data;
159
-
160
- for (let i = 0; i < parts.length - 1; i++) {
161
- const part = parts[i];
162
- if (current[part] == null) {
163
- current[part] = {};
164
- }
165
- current = current[part];
166
- }
167
-
168
- const oldValue = current[parts[parts.length - 1]];
169
- const newValue = modifier(oldValue);
170
-
171
- if (newValue !== undefined) {
172
- current[parts[parts.length - 1]] = newValue;
173
- }
174
- }
175
-
176
- export function deleteValue<
177
- TShape extends object,
178
- TPath extends Field.Paths<TShape>,
179
- >(data: TShape, key: TPath) {
180
- const parts = (key as string).split(".");
181
-
182
- let current: any = data;
183
-
184
- for (let i = 0; i < parts.length - 1; i++) {
185
- const part = parts[i];
186
- if (current[part] == null) {
187
- return;
188
- }
189
- current = current[part];
190
- }
191
-
192
- delete current[parts[parts.length - 1]];
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
+ }