remix-validated-form 5.0.2 → 5.1.1-beta.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.turbo/turbo-build.log +152 -8
- package/dist/index.cjs.js +898 -63
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.esm.js +876 -15
- package/dist/index.esm.js.map +1 -1
- package/package.json +4 -4
- package/src/ValidatedForm.tsx +0 -427
- package/src/hooks.ts +0 -160
- package/src/index.ts +0 -12
- package/src/internal/MultiValueMap.ts +0 -44
- package/src/internal/constants.ts +0 -4
- package/src/internal/flatten.ts +0 -12
- package/src/internal/formContext.ts +0 -13
- package/src/internal/getInputProps.test.ts +0 -251
- package/src/internal/getInputProps.ts +0 -94
- package/src/internal/hooks.ts +0 -217
- package/src/internal/hydratable.ts +0 -28
- package/src/internal/logic/getCheckboxChecked.ts +0 -10
- package/src/internal/logic/getRadioChecked.ts +0 -18
- package/src/internal/logic/nestedObjectToPathObject.ts +0 -63
- package/src/internal/logic/requestSubmit.test.tsx +0 -24
- package/src/internal/logic/requestSubmit.ts +0 -103
- package/src/internal/state/arrayUtil.ts +0 -451
- package/src/internal/state/controlledFields.ts +0 -86
- package/src/internal/state/createFormStore.ts +0 -591
- package/src/internal/state/fieldArray.tsx +0 -197
- package/src/internal/state/storeHooks.ts +0 -9
- package/src/internal/state/types.ts +0 -1
- package/src/internal/submissionCallbacks.ts +0 -15
- package/src/internal/util.ts +0 -39
- package/src/server.ts +0 -53
- package/src/unreleased/formStateHooks.ts +0 -170
- package/src/userFacingFormContext.ts +0 -147
- package/src/validation/createValidator.ts +0 -53
- package/src/validation/types.ts +0 -72
- package/tsconfig.json +0 -8
@@ -1,24 +0,0 @@
|
|
1
|
-
import { render } from "@testing-library/react";
|
2
|
-
import React, { createRef } from "react";
|
3
|
-
import { describe, expect, it, vi } from "vitest";
|
4
|
-
import { requestSubmit } from "./requestSubmit";
|
5
|
-
|
6
|
-
describe("requestSubmit polyfill", () => {
|
7
|
-
it("should polyfill requestSubmit", () => {
|
8
|
-
const submit = vi.fn();
|
9
|
-
const ref = createRef<HTMLFormElement>();
|
10
|
-
render(
|
11
|
-
<form
|
12
|
-
onSubmit={(event) => {
|
13
|
-
event.preventDefault();
|
14
|
-
submit();
|
15
|
-
}}
|
16
|
-
ref={ref}
|
17
|
-
>
|
18
|
-
<input name="test" value="testing" />
|
19
|
-
</form>
|
20
|
-
);
|
21
|
-
requestSubmit(ref.current!);
|
22
|
-
expect(submit).toHaveBeenCalledTimes(1);
|
23
|
-
});
|
24
|
-
});
|
@@ -1,103 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Ponyfill of the HTMLFormElement.requestSubmit() method.
|
3
|
-
* Based on polyfill from: https://github.com/javan/form-request-submit-polyfill/blob/main/form-request-submit-polyfill.js
|
4
|
-
*/
|
5
|
-
export const requestSubmit = (
|
6
|
-
element: HTMLFormElement,
|
7
|
-
submitter?: HTMLElement
|
8
|
-
) => {
|
9
|
-
// In vitest, let's test the polyfill.
|
10
|
-
// Cypress will test the native implementation by nature of using chrome.
|
11
|
-
if (
|
12
|
-
typeof Object.getPrototypeOf(element).requestSubmit === "function" &&
|
13
|
-
!import.meta.vitest
|
14
|
-
) {
|
15
|
-
element.requestSubmit(submitter);
|
16
|
-
return;
|
17
|
-
}
|
18
|
-
|
19
|
-
if (submitter) {
|
20
|
-
validateSubmitter(element, submitter);
|
21
|
-
submitter.click();
|
22
|
-
return;
|
23
|
-
}
|
24
|
-
|
25
|
-
const dummySubmitter = document.createElement("input");
|
26
|
-
dummySubmitter.type = "submit";
|
27
|
-
dummySubmitter.hidden = true;
|
28
|
-
element.appendChild(dummySubmitter);
|
29
|
-
dummySubmitter.click();
|
30
|
-
element.removeChild(dummySubmitter);
|
31
|
-
};
|
32
|
-
|
33
|
-
function validateSubmitter(element: HTMLFormElement, submitter: HTMLElement) {
|
34
|
-
// Should be redundant, but here for completeness
|
35
|
-
const isHtmlElement = submitter instanceof HTMLElement;
|
36
|
-
if (!isHtmlElement) {
|
37
|
-
raise(TypeError, "parameter 1 is not of type 'HTMLElement'");
|
38
|
-
}
|
39
|
-
|
40
|
-
const hasSubmitType =
|
41
|
-
"type" in submitter && (submitter as HTMLInputElement).type === "submit";
|
42
|
-
if (!hasSubmitType)
|
43
|
-
raise(TypeError, "The specified element is not a submit button");
|
44
|
-
|
45
|
-
const isForCorrectForm =
|
46
|
-
"form" in submitter && (submitter as HTMLInputElement).form === element;
|
47
|
-
if (!isForCorrectForm)
|
48
|
-
raise(
|
49
|
-
DOMException,
|
50
|
-
"The specified element is not owned by this form element",
|
51
|
-
"NotFoundError"
|
52
|
-
);
|
53
|
-
}
|
54
|
-
|
55
|
-
interface ErrorConstructor {
|
56
|
-
new (message: string, name?: string): Error;
|
57
|
-
}
|
58
|
-
|
59
|
-
function raise(
|
60
|
-
errorConstructor: ErrorConstructor,
|
61
|
-
message: string,
|
62
|
-
name?: string
|
63
|
-
): never {
|
64
|
-
throw new errorConstructor(
|
65
|
-
"Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".",
|
66
|
-
name
|
67
|
-
);
|
68
|
-
}
|
69
|
-
|
70
|
-
if (import.meta.vitest) {
|
71
|
-
const { it, expect } = import.meta.vitest;
|
72
|
-
it("should validate the submitter", () => {
|
73
|
-
const form = document.createElement("form");
|
74
|
-
document.body.appendChild(form);
|
75
|
-
|
76
|
-
const submitter = document.createElement("input");
|
77
|
-
expect(() => validateSubmitter(null as any, null as any)).toThrow();
|
78
|
-
expect(() => validateSubmitter(form, null as any)).toThrow();
|
79
|
-
expect(() => validateSubmitter(form, submitter)).toThrow();
|
80
|
-
expect(() =>
|
81
|
-
validateSubmitter(form, document.createElement("div"))
|
82
|
-
).toThrow();
|
83
|
-
|
84
|
-
submitter.type = "submit";
|
85
|
-
expect(() => validateSubmitter(form, submitter)).toThrow();
|
86
|
-
|
87
|
-
form.appendChild(submitter);
|
88
|
-
expect(() => validateSubmitter(form, submitter)).not.toThrow();
|
89
|
-
|
90
|
-
form.removeChild(submitter);
|
91
|
-
expect(() => validateSubmitter(form, submitter)).toThrow();
|
92
|
-
|
93
|
-
document.body.appendChild(submitter);
|
94
|
-
form.id = "test-form";
|
95
|
-
submitter.setAttribute("form", "test-form");
|
96
|
-
expect(() => validateSubmitter(form, submitter)).not.toThrow();
|
97
|
-
|
98
|
-
const button = document.createElement("button");
|
99
|
-
button.type = "submit";
|
100
|
-
form.appendChild(button);
|
101
|
-
expect(() => validateSubmitter(form, button)).not.toThrow();
|
102
|
-
});
|
103
|
-
}
|
@@ -1,451 +0,0 @@
|
|
1
|
-
import { getPath, setPath } from "set-get";
|
2
|
-
import invariant from "tiny-invariant";
|
3
|
-
|
4
|
-
////
|
5
|
-
// All of these array helpers are written in a way that mutates the original array.
|
6
|
-
// This is because we're working with immer.
|
7
|
-
////
|
8
|
-
|
9
|
-
export const getArray = (values: any, field: string): unknown[] => {
|
10
|
-
const value = getPath(values, field);
|
11
|
-
if (value === undefined || value === null) {
|
12
|
-
const newValue: unknown[] = [];
|
13
|
-
setPath(values, field, newValue);
|
14
|
-
return newValue;
|
15
|
-
}
|
16
|
-
invariant(
|
17
|
-
Array.isArray(value),
|
18
|
-
`FieldArray: defaultValue value for ${field} must be an array, null, or undefined`
|
19
|
-
);
|
20
|
-
return value;
|
21
|
-
};
|
22
|
-
|
23
|
-
export const sparseCopy = <T>(array: T[]): T[] => array.slice();
|
24
|
-
|
25
|
-
export const swap = (array: unknown[], indexA: number, indexB: number) => {
|
26
|
-
const itemA = array[indexA];
|
27
|
-
const itemB = array[indexB];
|
28
|
-
|
29
|
-
const hasItemA = indexA in array;
|
30
|
-
const hasItemB = indexB in array;
|
31
|
-
|
32
|
-
// If we're dealing with a sparse array (i.e. one of the indeces doesn't exist),
|
33
|
-
// we should keep it sparse
|
34
|
-
if (hasItemA) {
|
35
|
-
array[indexB] = itemA;
|
36
|
-
} else {
|
37
|
-
delete array[indexB];
|
38
|
-
}
|
39
|
-
|
40
|
-
if (hasItemB) {
|
41
|
-
array[indexA] = itemB;
|
42
|
-
} else {
|
43
|
-
delete array[indexA];
|
44
|
-
}
|
45
|
-
};
|
46
|
-
|
47
|
-
// A splice that can handle sparse arrays
|
48
|
-
function sparseSplice(
|
49
|
-
array: unknown[],
|
50
|
-
start: number,
|
51
|
-
deleteCount?: number,
|
52
|
-
item?: unknown
|
53
|
-
) {
|
54
|
-
// Inserting an item into an array won't behave as we need it to if the array isn't
|
55
|
-
// at least as long as the start index. We can force the array to be long enough like this.
|
56
|
-
if (array.length < start && item) {
|
57
|
-
array.length = start;
|
58
|
-
}
|
59
|
-
|
60
|
-
// If we just pass item in, it'll be undefined and splice will delete the item.
|
61
|
-
if (arguments.length === 4) return array.splice(start, deleteCount!, item);
|
62
|
-
else if (arguments.length === 3) return array.splice(start, deleteCount);
|
63
|
-
return array.splice(start);
|
64
|
-
}
|
65
|
-
|
66
|
-
export const move = (array: unknown[], from: number, to: number) => {
|
67
|
-
const [item] = sparseSplice(array, from, 1);
|
68
|
-
sparseSplice(array, to, 0, item);
|
69
|
-
};
|
70
|
-
|
71
|
-
export const insert = (array: unknown[], index: number, value: unknown) => {
|
72
|
-
sparseSplice(array, index, 0, value);
|
73
|
-
};
|
74
|
-
|
75
|
-
export const insertEmpty = (array: unknown[], index: number) => {
|
76
|
-
const tail = sparseSplice(array, index);
|
77
|
-
tail.forEach((item, i) => {
|
78
|
-
sparseSplice(array, index + i + 1, 0, item);
|
79
|
-
});
|
80
|
-
};
|
81
|
-
|
82
|
-
export const remove = (array: unknown[], index: number) => {
|
83
|
-
sparseSplice(array, index, 1);
|
84
|
-
};
|
85
|
-
|
86
|
-
export const replace = (array: unknown[], index: number, value: unknown) => {
|
87
|
-
sparseSplice(array, index, 1, value);
|
88
|
-
};
|
89
|
-
|
90
|
-
/**
|
91
|
-
* The purpose of this helper is to make it easier to update `fieldErrors` and `touchedFields`.
|
92
|
-
* We key those objects by full paths to the fields.
|
93
|
-
* When we're doing array mutations, that makes it difficult to update those objects.
|
94
|
-
*/
|
95
|
-
export const mutateAsArray = (
|
96
|
-
field: string,
|
97
|
-
obj: Record<string, any>,
|
98
|
-
mutate: (arr: any[]) => void
|
99
|
-
) => {
|
100
|
-
const beforeKeys = new Set<string>();
|
101
|
-
const arr: any[] = [];
|
102
|
-
|
103
|
-
for (const [key, value] of Object.entries(obj)) {
|
104
|
-
if (key.startsWith(field) && key !== field) {
|
105
|
-
beforeKeys.add(key);
|
106
|
-
setPath(arr, key.substring(field.length), value);
|
107
|
-
}
|
108
|
-
}
|
109
|
-
|
110
|
-
mutate(arr);
|
111
|
-
for (const key of beforeKeys) {
|
112
|
-
delete obj[key];
|
113
|
-
}
|
114
|
-
|
115
|
-
const newKeys = getDeepArrayPaths(arr);
|
116
|
-
for (const key of newKeys) {
|
117
|
-
const val = getPath(arr, key);
|
118
|
-
if (val !== undefined) {
|
119
|
-
obj[`${field}${key}`] = val;
|
120
|
-
}
|
121
|
-
}
|
122
|
-
};
|
123
|
-
|
124
|
-
const getDeepArrayPaths = (obj: any, basePath: string = ""): string[] => {
|
125
|
-
// This only needs to handle arrays and plain objects
|
126
|
-
// and we can assume the first call is always an array.
|
127
|
-
|
128
|
-
if (Array.isArray(obj)) {
|
129
|
-
return obj.flatMap((item, index) =>
|
130
|
-
getDeepArrayPaths(item, `${basePath}[${index}]`)
|
131
|
-
);
|
132
|
-
}
|
133
|
-
|
134
|
-
if (typeof obj === "object") {
|
135
|
-
return Object.keys(obj).flatMap((key) =>
|
136
|
-
getDeepArrayPaths(obj[key], `${basePath}.${key}`)
|
137
|
-
);
|
138
|
-
}
|
139
|
-
|
140
|
-
return [basePath];
|
141
|
-
};
|
142
|
-
|
143
|
-
if (import.meta.vitest) {
|
144
|
-
const { describe, expect, it } = import.meta.vitest;
|
145
|
-
|
146
|
-
// Count the actual number of items in the array
|
147
|
-
// instead of just getting the length.
|
148
|
-
// This is useful for validating that sparse arrays are handled correctly.
|
149
|
-
const countArrayItems = (arr: any[]) => {
|
150
|
-
let count = 0;
|
151
|
-
arr.forEach(() => count++);
|
152
|
-
return count;
|
153
|
-
};
|
154
|
-
|
155
|
-
describe("getArray", () => {
|
156
|
-
it("shoud get a deeply nested array that can be mutated to update the nested value", () => {
|
157
|
-
const values = {
|
158
|
-
d: [
|
159
|
-
{ foo: "bar", baz: [true, false] },
|
160
|
-
{ e: true, f: "hi" },
|
161
|
-
],
|
162
|
-
};
|
163
|
-
const result = getArray(values, "d[0].baz");
|
164
|
-
const finalValues = {
|
165
|
-
d: [
|
166
|
-
{ foo: "bar", baz: [true, false, true] },
|
167
|
-
{ e: true, f: "hi" },
|
168
|
-
],
|
169
|
-
};
|
170
|
-
|
171
|
-
expect(result).toEqual([true, false]);
|
172
|
-
result.push(true);
|
173
|
-
expect(values).toEqual(finalValues);
|
174
|
-
});
|
175
|
-
|
176
|
-
it("should return an empty array that can be mutated if result is null or undefined", () => {
|
177
|
-
const values = {};
|
178
|
-
const result = getArray(values, "a.foo[0].bar");
|
179
|
-
const finalValues = {
|
180
|
-
a: { foo: [{ bar: ["Bob ross"] }] },
|
181
|
-
};
|
182
|
-
|
183
|
-
expect(result).toEqual([]);
|
184
|
-
result.push("Bob ross");
|
185
|
-
expect(values).toEqual(finalValues);
|
186
|
-
});
|
187
|
-
|
188
|
-
it("should throw if the value is defined and not an array", () => {
|
189
|
-
const values = { foo: "foo" };
|
190
|
-
expect(() => getArray(values, "foo")).toThrow();
|
191
|
-
});
|
192
|
-
});
|
193
|
-
|
194
|
-
describe("swap", () => {
|
195
|
-
it("should swap two items", () => {
|
196
|
-
const array = [1, 2, 3];
|
197
|
-
swap(array, 0, 1);
|
198
|
-
expect(array).toEqual([2, 1, 3]);
|
199
|
-
});
|
200
|
-
|
201
|
-
it("should work for sparse arrays", () => {
|
202
|
-
// A bit of a sanity check for native array behavior
|
203
|
-
const arr = [] as any[];
|
204
|
-
arr[0] = true;
|
205
|
-
swap(arr, 0, 2);
|
206
|
-
|
207
|
-
expect(countArrayItems(arr)).toEqual(1);
|
208
|
-
expect(0 in arr).toBe(false);
|
209
|
-
expect(2 in arr).toBe(true);
|
210
|
-
expect(arr[2]).toEqual(true);
|
211
|
-
});
|
212
|
-
});
|
213
|
-
|
214
|
-
describe("move", () => {
|
215
|
-
it("should move an item to a new index", () => {
|
216
|
-
const array = [1, 2, 3];
|
217
|
-
move(array, 0, 1);
|
218
|
-
expect(array).toEqual([2, 1, 3]);
|
219
|
-
});
|
220
|
-
|
221
|
-
it("should work with sparse arrays", () => {
|
222
|
-
const array = [1];
|
223
|
-
move(array, 0, 2);
|
224
|
-
|
225
|
-
expect(countArrayItems(array)).toEqual(1);
|
226
|
-
expect(array).toEqual([undefined, undefined, 1]);
|
227
|
-
});
|
228
|
-
});
|
229
|
-
|
230
|
-
describe("insert", () => {
|
231
|
-
it("should insert an item at a new index", () => {
|
232
|
-
const array = [1, 2, 3];
|
233
|
-
insert(array, 1, 4);
|
234
|
-
expect(array).toEqual([1, 4, 2, 3]);
|
235
|
-
});
|
236
|
-
|
237
|
-
it("should be able to insert falsey values", () => {
|
238
|
-
const array = [1, 2, 3];
|
239
|
-
insert(array, 1, null);
|
240
|
-
expect(array).toEqual([1, null, 2, 3]);
|
241
|
-
});
|
242
|
-
|
243
|
-
it("should handle sparse arrays", () => {
|
244
|
-
const array: any[] = [];
|
245
|
-
array[2] = true;
|
246
|
-
insert(array, 0, true);
|
247
|
-
|
248
|
-
expect(countArrayItems(array)).toEqual(2);
|
249
|
-
expect(array).toEqual([true, undefined, undefined, true]);
|
250
|
-
});
|
251
|
-
});
|
252
|
-
|
253
|
-
describe("insertEmpty", () => {
|
254
|
-
it("should insert an empty item at a given index", () => {
|
255
|
-
const array = [1, 2, 3];
|
256
|
-
insertEmpty(array, 1);
|
257
|
-
// eslint-disable-next-line no-sparse-arrays
|
258
|
-
expect(array).toStrictEqual([1, , 2, 3]);
|
259
|
-
expect(array).not.toStrictEqual([1, undefined, 2, 3]);
|
260
|
-
});
|
261
|
-
|
262
|
-
it("should work with already sparse arrays", () => {
|
263
|
-
// eslint-disable-next-line no-sparse-arrays
|
264
|
-
const array = [, , 1, , 2, , 3];
|
265
|
-
insertEmpty(array, 3);
|
266
|
-
// eslint-disable-next-line no-sparse-arrays
|
267
|
-
expect(array).toStrictEqual([, , 1, , , 2, , 3]);
|
268
|
-
expect(array).not.toStrictEqual([
|
269
|
-
undefined,
|
270
|
-
undefined,
|
271
|
-
1,
|
272
|
-
undefined,
|
273
|
-
undefined,
|
274
|
-
2,
|
275
|
-
undefined,
|
276
|
-
3,
|
277
|
-
]);
|
278
|
-
});
|
279
|
-
});
|
280
|
-
|
281
|
-
describe("remove", () => {
|
282
|
-
it("should remove an item at a given index", () => {
|
283
|
-
const array = [1, 2, 3];
|
284
|
-
remove(array, 1);
|
285
|
-
expect(array).toEqual([1, 3]);
|
286
|
-
});
|
287
|
-
|
288
|
-
it("should handle sparse arrays", () => {
|
289
|
-
const array: any[] = [];
|
290
|
-
array[2] = true;
|
291
|
-
remove(array, 0);
|
292
|
-
|
293
|
-
expect(countArrayItems(array)).toEqual(1);
|
294
|
-
expect(array).toEqual([undefined, true]);
|
295
|
-
});
|
296
|
-
});
|
297
|
-
|
298
|
-
describe("replace", () => {
|
299
|
-
it("should replace an item at a given index", () => {
|
300
|
-
const array = [1, 2, 3];
|
301
|
-
replace(array, 1, 4);
|
302
|
-
expect(array).toEqual([1, 4, 3]);
|
303
|
-
});
|
304
|
-
|
305
|
-
it("should handle sparse arrays", () => {
|
306
|
-
const array: any[] = [];
|
307
|
-
array[2] = true;
|
308
|
-
replace(array, 0, true);
|
309
|
-
expect(countArrayItems(array)).toEqual(2);
|
310
|
-
expect(array).toEqual([true, undefined, true]);
|
311
|
-
});
|
312
|
-
});
|
313
|
-
|
314
|
-
describe("mutateAsArray", () => {
|
315
|
-
it("should handle swap", () => {
|
316
|
-
const values = {
|
317
|
-
myField: "something",
|
318
|
-
"myField[0]": "foo",
|
319
|
-
"myField[2]": "bar",
|
320
|
-
otherField: "baz",
|
321
|
-
"otherField[0]": "something else",
|
322
|
-
};
|
323
|
-
mutateAsArray("myField", values, (arr) => {
|
324
|
-
swap(arr, 0, 2);
|
325
|
-
});
|
326
|
-
expect(values).toEqual({
|
327
|
-
myField: "something",
|
328
|
-
"myField[0]": "bar",
|
329
|
-
"myField[2]": "foo",
|
330
|
-
otherField: "baz",
|
331
|
-
"otherField[0]": "something else",
|
332
|
-
});
|
333
|
-
});
|
334
|
-
|
335
|
-
it("should swap sparse arrays", () => {
|
336
|
-
const values = {
|
337
|
-
myField: "something",
|
338
|
-
"myField[0]": "foo",
|
339
|
-
otherField: "baz",
|
340
|
-
"otherField[0]": "something else",
|
341
|
-
};
|
342
|
-
mutateAsArray("myField", values, (arr) => {
|
343
|
-
swap(arr, 0, 2);
|
344
|
-
});
|
345
|
-
expect(values).toEqual({
|
346
|
-
myField: "something",
|
347
|
-
"myField[2]": "foo",
|
348
|
-
otherField: "baz",
|
349
|
-
"otherField[0]": "something else",
|
350
|
-
});
|
351
|
-
});
|
352
|
-
|
353
|
-
it("should handle arrays with nested values", () => {
|
354
|
-
const values = {
|
355
|
-
myField: "something",
|
356
|
-
"myField[0].title": "foo",
|
357
|
-
"myField[0].note": "bar",
|
358
|
-
"myField[2].title": "other",
|
359
|
-
"myField[2].note": "other",
|
360
|
-
otherField: "baz",
|
361
|
-
"otherField[0]": "something else",
|
362
|
-
};
|
363
|
-
mutateAsArray("myField", values, (arr) => {
|
364
|
-
swap(arr, 0, 2);
|
365
|
-
});
|
366
|
-
expect(values).toEqual({
|
367
|
-
myField: "something",
|
368
|
-
"myField[0].title": "other",
|
369
|
-
"myField[0].note": "other",
|
370
|
-
"myField[2].title": "foo",
|
371
|
-
"myField[2].note": "bar",
|
372
|
-
otherField: "baz",
|
373
|
-
"otherField[0]": "something else",
|
374
|
-
});
|
375
|
-
});
|
376
|
-
|
377
|
-
it("should handle move", () => {
|
378
|
-
const values = {
|
379
|
-
myField: "something",
|
380
|
-
"myField[0]": "foo",
|
381
|
-
"myField[1]": "bar",
|
382
|
-
"myField[2]": "baz",
|
383
|
-
"otherField[0]": "something else",
|
384
|
-
};
|
385
|
-
mutateAsArray("myField", values, (arr) => {
|
386
|
-
move(arr, 0, 2);
|
387
|
-
});
|
388
|
-
expect(values).toEqual({
|
389
|
-
myField: "something",
|
390
|
-
"myField[0]": "bar",
|
391
|
-
"myField[1]": "baz",
|
392
|
-
"myField[2]": "foo",
|
393
|
-
"otherField[0]": "something else",
|
394
|
-
});
|
395
|
-
});
|
396
|
-
|
397
|
-
it("should not create keys for `undefined`", () => {
|
398
|
-
const values = {
|
399
|
-
"myField[0]": "foo",
|
400
|
-
};
|
401
|
-
mutateAsArray("myField", values, (arr) => {
|
402
|
-
arr.unshift(undefined);
|
403
|
-
});
|
404
|
-
expect(Object.keys(values)).toHaveLength(1);
|
405
|
-
expect(values).toEqual({
|
406
|
-
"myField[1]": "foo",
|
407
|
-
});
|
408
|
-
});
|
409
|
-
|
410
|
-
it("should handle remove", () => {
|
411
|
-
const values = {
|
412
|
-
myField: "something",
|
413
|
-
"myField[0]": "foo",
|
414
|
-
"myField[1]": "bar",
|
415
|
-
"myField[2]": "baz",
|
416
|
-
"otherField[0]": "something else",
|
417
|
-
};
|
418
|
-
mutateAsArray("myField", values, (arr) => {
|
419
|
-
remove(arr, 1);
|
420
|
-
});
|
421
|
-
expect(values).toEqual({
|
422
|
-
myField: "something",
|
423
|
-
"myField[0]": "foo",
|
424
|
-
"myField[1]": "baz",
|
425
|
-
"otherField[0]": "something else",
|
426
|
-
});
|
427
|
-
expect("myField[2]" in values).toBe(false);
|
428
|
-
});
|
429
|
-
});
|
430
|
-
|
431
|
-
describe("getDeepArrayPaths", () => {
|
432
|
-
it("should return all paths recursively", () => {
|
433
|
-
const obj = [
|
434
|
-
true,
|
435
|
-
true,
|
436
|
-
[true, true],
|
437
|
-
{ foo: true, bar: { baz: true, test: [true] } },
|
438
|
-
];
|
439
|
-
|
440
|
-
expect(getDeepArrayPaths(obj, "myField")).toEqual([
|
441
|
-
"myField[0]",
|
442
|
-
"myField[1]",
|
443
|
-
"myField[2][0]",
|
444
|
-
"myField[2][1]",
|
445
|
-
"myField[3].foo",
|
446
|
-
"myField[3].bar.baz",
|
447
|
-
"myField[3].bar.test[0]",
|
448
|
-
]);
|
449
|
-
});
|
450
|
-
});
|
451
|
-
}
|
@@ -1,86 +0,0 @@
|
|
1
|
-
import { useCallback, useEffect } from "react";
|
2
|
-
import { InternalFormContextValue } from "../formContext";
|
3
|
-
import { useFieldDefaultValue } from "../hooks";
|
4
|
-
import { useFormStore } from "./storeHooks";
|
5
|
-
import { InternalFormId } from "./types";
|
6
|
-
|
7
|
-
export const useControlledFieldValue = (
|
8
|
-
context: InternalFormContextValue,
|
9
|
-
field: string
|
10
|
-
) => {
|
11
|
-
const value = useFormStore(context.formId, (state) =>
|
12
|
-
state.controlledFields.getValue(field)
|
13
|
-
);
|
14
|
-
const isFormHydrated = useFormStore(
|
15
|
-
context.formId,
|
16
|
-
(state) => state.isHydrated
|
17
|
-
);
|
18
|
-
const defaultValue = useFieldDefaultValue(field, context);
|
19
|
-
|
20
|
-
return isFormHydrated ? value : defaultValue;
|
21
|
-
};
|
22
|
-
|
23
|
-
export const useRegisterControlledField = (
|
24
|
-
context: InternalFormContextValue,
|
25
|
-
field: string
|
26
|
-
) => {
|
27
|
-
const resolveUpdate = useFormStore(
|
28
|
-
context.formId,
|
29
|
-
(state) => state.controlledFields.valueUpdateResolvers[field]
|
30
|
-
);
|
31
|
-
useEffect(() => {
|
32
|
-
resolveUpdate?.();
|
33
|
-
}, [resolveUpdate]);
|
34
|
-
|
35
|
-
const register = useFormStore(
|
36
|
-
context.formId,
|
37
|
-
(state) => state.controlledFields.register
|
38
|
-
);
|
39
|
-
const unregister = useFormStore(
|
40
|
-
context.formId,
|
41
|
-
(state) => state.controlledFields.unregister
|
42
|
-
);
|
43
|
-
useEffect(() => {
|
44
|
-
register(field);
|
45
|
-
return () => unregister(field);
|
46
|
-
}, [context.formId, field, register, unregister]);
|
47
|
-
};
|
48
|
-
|
49
|
-
export const useControllableValue = (
|
50
|
-
context: InternalFormContextValue,
|
51
|
-
field: string
|
52
|
-
) => {
|
53
|
-
useRegisterControlledField(context, field);
|
54
|
-
|
55
|
-
const setControlledFieldValue = useFormStore(
|
56
|
-
context.formId,
|
57
|
-
(state) => state.controlledFields.setValue
|
58
|
-
);
|
59
|
-
const setValue = useCallback(
|
60
|
-
(value: unknown) => setControlledFieldValue(field, value),
|
61
|
-
[field, setControlledFieldValue]
|
62
|
-
);
|
63
|
-
|
64
|
-
const value = useControlledFieldValue(context, field);
|
65
|
-
|
66
|
-
return [value, setValue] as const;
|
67
|
-
};
|
68
|
-
|
69
|
-
export const useUpdateControllableValue = (formId: InternalFormId) => {
|
70
|
-
const setValue = useFormStore(
|
71
|
-
formId,
|
72
|
-
(state) => state.controlledFields.setValue
|
73
|
-
);
|
74
|
-
return useCallback(
|
75
|
-
(field: string, value: unknown) => setValue(field, value),
|
76
|
-
[setValue]
|
77
|
-
);
|
78
|
-
};
|
79
|
-
|
80
|
-
export const useAwaitValue = (formId: InternalFormId) => {
|
81
|
-
const awaitValue = useFormStore(
|
82
|
-
formId,
|
83
|
-
(state) => state.controlledFields.awaitValueUpdate
|
84
|
-
);
|
85
|
-
return useCallback((field: string) => awaitValue(field), [awaitValue]);
|
86
|
-
};
|