@conform-to/dom 1.6.1 → 1.7.0
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/README.md +1 -1
- package/dist/dom.d.ts +30 -9
- package/dist/dom.js +363 -14
- package/dist/dom.mjs +354 -14
- package/dist/form.d.ts +0 -10
- package/dist/form.js +5 -98
- package/dist/form.mjs +7 -99
- package/dist/formdata.d.ts +1 -0
- package/dist/formdata.js +36 -0
- package/dist/formdata.mjs +36 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -1
- package/dist/index.mjs +3 -3
- package/dist/vitest.config.d.ts +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
Version 1.
|
|
10
|
+
Version 1.7.0 / License MIT / Copyright (c) 2024 Edmund Hung
|
|
11
11
|
|
|
12
12
|
A type-safe form validation library utilizing web fundamentals to progressively enhance HTML Forms with full support for server frameworks like Remix and Next.js.
|
|
13
13
|
|
package/dist/dom.d.ts
CHANGED
|
@@ -3,19 +3,13 @@
|
|
|
3
3
|
* includes `<input>`, `<select>` and `<textarea>`.
|
|
4
4
|
*/
|
|
5
5
|
export type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
6
|
-
/**
|
|
7
|
-
* HTML Element that can be used as a form control,
|
|
8
|
-
* includes `<input>`, `<select>`, `<textarea>` and `<button>`.
|
|
9
|
-
*/
|
|
10
|
-
export type FormControl = FieldElement | HTMLButtonElement;
|
|
11
6
|
/**
|
|
12
7
|
* Form Control element. It can either be a submit button or a submit input.
|
|
13
8
|
*/
|
|
14
9
|
export type Submitter = HTMLInputElement | HTMLButtonElement;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export declare function isFormControl(element: unknown): element is FormControl;
|
|
10
|
+
export declare function isInputElement(element: Element): element is HTMLInputElement;
|
|
11
|
+
export declare function isSelectElement(element: Element): element is HTMLSelectElement;
|
|
12
|
+
export declare function isTextAreaElement(element: Element): element is HTMLTextAreaElement;
|
|
19
13
|
/**
|
|
20
14
|
* A type guard to check if the provided element is a field element, which
|
|
21
15
|
* is a form control excluding submit, button and reset type.
|
|
@@ -41,4 +35,31 @@ export declare function getFormMethod(event: SubmitEvent): 'GET' | 'POST' | 'PUT
|
|
|
41
35
|
* If the submitter is not mounted, it will be appended to the form and removed after submission.
|
|
42
36
|
*/
|
|
43
37
|
export declare function requestSubmit(form: HTMLFormElement | null | undefined, submitter: Submitter | null): void;
|
|
38
|
+
export declare function createFileList(value: File | File[]): FileList;
|
|
39
|
+
type InputCallback = (event: {
|
|
40
|
+
type: 'input' | 'reset' | 'mutation';
|
|
41
|
+
target: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
42
|
+
}) => void;
|
|
43
|
+
type FormCallback = (event: {
|
|
44
|
+
type: 'submit' | 'input' | 'reset' | 'mutation';
|
|
45
|
+
target: HTMLFormElement;
|
|
46
|
+
submitter?: HTMLInputElement | HTMLButtonElement | null;
|
|
47
|
+
}) => void;
|
|
48
|
+
export declare function createGlobalFormsObserver(): {
|
|
49
|
+
onFieldUpdate(callback: InputCallback): () => void;
|
|
50
|
+
onFormUpdate(callback: FormCallback): () => void;
|
|
51
|
+
dispose(): void;
|
|
52
|
+
};
|
|
53
|
+
export declare function change(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, value: string | string[] | File | File[] | FileList | null): void;
|
|
54
|
+
export declare function focus(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void;
|
|
55
|
+
export declare function blur(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement): void;
|
|
56
|
+
export declare function normalizeFieldValue(value: unknown): [string[] | null, FileList | null];
|
|
57
|
+
/**
|
|
58
|
+
* Updates the DOM element with the provided value and defaultValue.
|
|
59
|
+
*/
|
|
60
|
+
export declare function updateField(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, options: {
|
|
61
|
+
value?: unknown;
|
|
62
|
+
defaultValue?: unknown;
|
|
63
|
+
}): void;
|
|
64
|
+
export {};
|
|
44
65
|
//# sourceMappingURL=dom.d.ts.map
|
package/dist/dom.js
CHANGED
|
@@ -9,20 +9,18 @@ var util = require('./util.js');
|
|
|
9
9
|
* includes `<input>`, `<select>` and `<textarea>`.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* HTML Element that can be used as a form control,
|
|
14
|
-
* includes `<input>`, `<select>`, `<textarea>` and `<button>`.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
12
|
/**
|
|
18
13
|
* Form Control element. It can either be a submit button or a submit input.
|
|
19
14
|
*/
|
|
20
15
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
function
|
|
25
|
-
return element
|
|
16
|
+
function isInputElement(element) {
|
|
17
|
+
return element.tagName === 'INPUT';
|
|
18
|
+
}
|
|
19
|
+
function isSelectElement(element) {
|
|
20
|
+
return element.tagName === 'SELECT';
|
|
21
|
+
}
|
|
22
|
+
function isTextAreaElement(element) {
|
|
23
|
+
return element.tagName === 'TEXTAREA';
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
/**
|
|
@@ -30,7 +28,15 @@ function isFormControl(element) {
|
|
|
30
28
|
* is a form control excluding submit, button and reset type.
|
|
31
29
|
*/
|
|
32
30
|
function isFieldElement(element) {
|
|
33
|
-
|
|
31
|
+
if (element instanceof Element) {
|
|
32
|
+
if (isInputElement(element)) {
|
|
33
|
+
return element.type !== 'submit' && element.type !== 'button' && element.type !== 'reset';
|
|
34
|
+
}
|
|
35
|
+
if (isSelectElement(element) || isTextAreaElement(element)) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/**
|
|
@@ -87,18 +93,361 @@ function requestSubmit(form, submitter) {
|
|
|
87
93
|
if (typeof form.requestSubmit === 'function') {
|
|
88
94
|
form.requestSubmit(submitter);
|
|
89
95
|
} else {
|
|
90
|
-
var
|
|
96
|
+
var _event = new SubmitEvent('submit', {
|
|
91
97
|
bubbles: true,
|
|
92
98
|
cancelable: true,
|
|
93
99
|
submitter
|
|
94
100
|
});
|
|
95
|
-
form.dispatchEvent(
|
|
101
|
+
form.dispatchEvent(_event);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function createFileList(value) {
|
|
105
|
+
var dataTransfer = new DataTransfer();
|
|
106
|
+
if (Array.isArray(value)) {
|
|
107
|
+
for (var file of value) {
|
|
108
|
+
dataTransfer.items.add(file);
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
dataTransfer.items.add(value);
|
|
112
|
+
}
|
|
113
|
+
return dataTransfer.files;
|
|
114
|
+
}
|
|
115
|
+
function createGlobalFormsObserver() {
|
|
116
|
+
var inputListeners = new Set();
|
|
117
|
+
var formListeners = new Set();
|
|
118
|
+
var cleanup = null;
|
|
119
|
+
function initialize() {
|
|
120
|
+
var observer = new MutationObserver(handleMutation);
|
|
121
|
+
observer.observe(document.body, {
|
|
122
|
+
subtree: true,
|
|
123
|
+
childList: true,
|
|
124
|
+
attributeFilter: ['form', 'name', 'data-conform']
|
|
125
|
+
});
|
|
126
|
+
document.addEventListener('input', handleInput);
|
|
127
|
+
document.addEventListener('reset', handleReset);
|
|
128
|
+
document.addEventListener('submit', handleSubmit, true);
|
|
129
|
+
return () => {
|
|
130
|
+
document.removeEventListener('input', handleInput);
|
|
131
|
+
document.removeEventListener('reset', handleReset);
|
|
132
|
+
document.removeEventListener('submit', handleSubmit, true);
|
|
133
|
+
observer.disconnect();
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function handleInput(event) {
|
|
137
|
+
var target = event.target;
|
|
138
|
+
if (isFieldElement(target)) {
|
|
139
|
+
inputListeners.forEach(callback => callback({
|
|
140
|
+
type: 'input',
|
|
141
|
+
target
|
|
142
|
+
}));
|
|
143
|
+
var form = target.form;
|
|
144
|
+
if (form) {
|
|
145
|
+
formListeners.forEach(callback => callback({
|
|
146
|
+
type: 'input',
|
|
147
|
+
target: form
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function handleReset(event) {
|
|
153
|
+
var form = event.target;
|
|
154
|
+
if (form instanceof HTMLFormElement) {
|
|
155
|
+
// Reset event is fired before the form is reset, so we need to wait for the next tick
|
|
156
|
+
setTimeout(() => {
|
|
157
|
+
formListeners.forEach(callback => {
|
|
158
|
+
callback({
|
|
159
|
+
type: 'reset',
|
|
160
|
+
target: form
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
var _loop = function _loop(target) {
|
|
164
|
+
if (isFieldElement(target)) {
|
|
165
|
+
inputListeners.forEach(callback => {
|
|
166
|
+
callback({
|
|
167
|
+
type: 'reset',
|
|
168
|
+
target
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
for (var target of form.elements) {
|
|
174
|
+
_loop(target);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function handleSubmit(event) {
|
|
180
|
+
var target = event.target;
|
|
181
|
+
var submitter = event.submitter;
|
|
182
|
+
if (target instanceof HTMLFormElement) {
|
|
183
|
+
formListeners.forEach(callback => callback({
|
|
184
|
+
type: 'submit',
|
|
185
|
+
target,
|
|
186
|
+
submitter
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function handleMutation(mutations) {
|
|
191
|
+
var seenForms = new Set();
|
|
192
|
+
var seenInputs = new Set();
|
|
193
|
+
var collectInputs = node => {
|
|
194
|
+
if (isFieldElement(node)) {
|
|
195
|
+
return [node];
|
|
196
|
+
}
|
|
197
|
+
return node instanceof Element ? Array.from(node.querySelectorAll('input,select,textarea')) : [];
|
|
198
|
+
};
|
|
199
|
+
for (var mutation of mutations) {
|
|
200
|
+
switch (mutation.type) {
|
|
201
|
+
case 'childList':
|
|
202
|
+
{
|
|
203
|
+
var nodes = [...mutation.addedNodes, ...mutation.removedNodes];
|
|
204
|
+
for (var node of nodes) {
|
|
205
|
+
for (var input of collectInputs(node)) {
|
|
206
|
+
seenInputs.add(input);
|
|
207
|
+
if (input.form) {
|
|
208
|
+
seenForms.add(input.form);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
case 'attributes':
|
|
215
|
+
{
|
|
216
|
+
if (isFieldElement(mutation.target)) {
|
|
217
|
+
seenInputs.add(mutation.target);
|
|
218
|
+
if (mutation.target.form) {
|
|
219
|
+
seenForms.add(mutation.target.form);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
var _loop2 = function _loop2(target) {
|
|
227
|
+
formListeners.forEach(callback => {
|
|
228
|
+
callback({
|
|
229
|
+
type: 'mutation',
|
|
230
|
+
target
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
for (var target of seenForms) {
|
|
235
|
+
_loop2(target);
|
|
236
|
+
}
|
|
237
|
+
var _loop3 = function _loop3(_target) {
|
|
238
|
+
inputListeners.forEach(callback => {
|
|
239
|
+
callback({
|
|
240
|
+
type: 'mutation',
|
|
241
|
+
target: _target
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
for (var _target of seenInputs) {
|
|
246
|
+
_loop3(_target);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
onFieldUpdate(callback) {
|
|
251
|
+
var _cleanup;
|
|
252
|
+
cleanup = (_cleanup = cleanup) !== null && _cleanup !== void 0 ? _cleanup : initialize();
|
|
253
|
+
inputListeners.add(callback);
|
|
254
|
+
return () => {
|
|
255
|
+
inputListeners.delete(callback);
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
onFormUpdate(callback) {
|
|
259
|
+
var _cleanup2;
|
|
260
|
+
cleanup = (_cleanup2 = cleanup) !== null && _cleanup2 !== void 0 ? _cleanup2 : initialize();
|
|
261
|
+
formListeners.add(callback);
|
|
262
|
+
return () => {
|
|
263
|
+
formListeners.delete(callback);
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
dispose() {
|
|
267
|
+
var _cleanup3;
|
|
268
|
+
(_cleanup3 = cleanup) === null || _cleanup3 === void 0 || _cleanup3();
|
|
269
|
+
cleanup = null;
|
|
270
|
+
inputListeners.clear();
|
|
271
|
+
formListeners.clear();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function change(element, value) {
|
|
276
|
+
// The value should be set to the element before dispatching the event
|
|
277
|
+
updateField(element, {
|
|
278
|
+
value
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Dispatch input event with the updated input value
|
|
282
|
+
element.dispatchEvent(new InputEvent('input', {
|
|
283
|
+
bubbles: true
|
|
284
|
+
}));
|
|
285
|
+
// Dispatch change event (necessary for select to update the selected option)
|
|
286
|
+
element.dispatchEvent(new Event('change', {
|
|
287
|
+
bubbles: true
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
function focus(element) {
|
|
291
|
+
// Only focusin event will be bubbled
|
|
292
|
+
element.dispatchEvent(new FocusEvent('focusin', {
|
|
293
|
+
bubbles: true
|
|
294
|
+
}));
|
|
295
|
+
element.dispatchEvent(new FocusEvent('focus'));
|
|
296
|
+
}
|
|
297
|
+
function blur(element) {
|
|
298
|
+
// Only focusout event will be bubbled
|
|
299
|
+
element.dispatchEvent(new FocusEvent('focusout', {
|
|
300
|
+
bubbles: true
|
|
301
|
+
}));
|
|
302
|
+
element.dispatchEvent(new FocusEvent('blur'));
|
|
303
|
+
}
|
|
304
|
+
function normalizeFieldValue(value) {
|
|
305
|
+
if (typeof value === 'undefined') {
|
|
306
|
+
return [null, null];
|
|
307
|
+
}
|
|
308
|
+
if (value === null) {
|
|
309
|
+
return [[], createFileList([])];
|
|
310
|
+
}
|
|
311
|
+
if (typeof value === 'string') {
|
|
312
|
+
return [[value], null];
|
|
313
|
+
}
|
|
314
|
+
if (Array.isArray(value)) {
|
|
315
|
+
if (value.every(item => typeof item === 'string')) {
|
|
316
|
+
return [Array.from(value), null];
|
|
317
|
+
}
|
|
318
|
+
if (value.every(item => item instanceof File)) {
|
|
319
|
+
return [null, createFileList(value)];
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
if (value instanceof FileList) {
|
|
323
|
+
return [null, value];
|
|
324
|
+
}
|
|
325
|
+
if (value instanceof File) {
|
|
326
|
+
return [null, createFileList([value])];
|
|
327
|
+
}
|
|
328
|
+
return [null, null];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Updates the DOM element with the provided value and defaultValue.
|
|
333
|
+
*/
|
|
334
|
+
function updateField(element, options) {
|
|
335
|
+
var _value$;
|
|
336
|
+
var [value, file] = normalizeFieldValue(options.value);
|
|
337
|
+
var [defaultValue] = normalizeFieldValue(options.defaultValue);
|
|
338
|
+
if (isInputElement(element)) {
|
|
339
|
+
switch (element.type) {
|
|
340
|
+
case 'file':
|
|
341
|
+
{
|
|
342
|
+
element.files = file;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
case 'checkbox':
|
|
346
|
+
case 'radio':
|
|
347
|
+
{
|
|
348
|
+
if (value) {
|
|
349
|
+
var checked = value.includes(element.value);
|
|
350
|
+
if (element.type === 'checkbox' ? checked !== element.checked : checked) {
|
|
351
|
+
// Simulate a click to update the checked state
|
|
352
|
+
element.click();
|
|
353
|
+
}
|
|
354
|
+
element.checked = checked;
|
|
355
|
+
}
|
|
356
|
+
if (defaultValue) {
|
|
357
|
+
element.defaultChecked = defaultValue.includes(element.value);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} else if (isSelectElement(element)) {
|
|
363
|
+
var shouldUnselect = value && value.length === 0;
|
|
364
|
+
for (var option of element.options) {
|
|
365
|
+
if (value) {
|
|
366
|
+
var index = value.indexOf(option.value);
|
|
367
|
+
var selected = index > -1;
|
|
368
|
+
|
|
369
|
+
// Update the selected state of the option
|
|
370
|
+
if (option.selected !== selected) {
|
|
371
|
+
option.selected = selected;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Remove the option from the value array
|
|
375
|
+
if (selected) {
|
|
376
|
+
value.splice(index, 1);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (defaultValue) {
|
|
380
|
+
var _index = defaultValue.indexOf(option.value);
|
|
381
|
+
var _selected = _index > -1;
|
|
382
|
+
|
|
383
|
+
// Update the selected state of the option
|
|
384
|
+
if (option.defaultSelected !== _selected) {
|
|
385
|
+
option.defaultSelected = _selected;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Remove the option from the defaultValue array
|
|
389
|
+
if (_selected) {
|
|
390
|
+
defaultValue.splice(_index, 1);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// We have already removed all selected options from the value and defaultValue array at this point
|
|
396
|
+
var missingOptions = new Set([...(value !== null && value !== void 0 ? value : []), ...(defaultValue !== null && defaultValue !== void 0 ? defaultValue : [])]);
|
|
397
|
+
for (var optionValue of missingOptions) {
|
|
398
|
+
element.options.add(new Option(optionValue, optionValue, defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.includes(optionValue), value === null || value === void 0 ? void 0 : value.includes(optionValue)));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// If the select element is not multiple and the value is an empty array, unset the selected index
|
|
402
|
+
// This is to prevent the select element from showing the first option as selected
|
|
403
|
+
if (shouldUnselect) {
|
|
404
|
+
element.selectedIndex = -1;
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
var inputValue = (_value$ = value === null || value === void 0 ? void 0 : value[0]) !== null && _value$ !== void 0 ? _value$ : '';
|
|
409
|
+
if (element.value !== inputValue) {
|
|
410
|
+
/**
|
|
411
|
+
* Triggering react custom change event
|
|
412
|
+
* Solution based on dom-testing-library
|
|
413
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
414
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
415
|
+
*/
|
|
416
|
+
var {
|
|
417
|
+
set: valueSetter
|
|
418
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
419
|
+
var prototype = Object.getPrototypeOf(element);
|
|
420
|
+
var {
|
|
421
|
+
set: prototypeValueSetter
|
|
422
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
423
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
424
|
+
prototypeValueSetter.call(element, inputValue);
|
|
425
|
+
} else {
|
|
426
|
+
if (valueSetter) {
|
|
427
|
+
valueSetter.call(element, inputValue);
|
|
428
|
+
} else {
|
|
429
|
+
throw new Error('The given element does not have a value setter');
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (defaultValue) {
|
|
434
|
+
var _defaultValue$;
|
|
435
|
+
element.defaultValue = (_defaultValue$ = defaultValue[0]) !== null && _defaultValue$ !== void 0 ? _defaultValue$ : '';
|
|
96
436
|
}
|
|
97
437
|
}
|
|
98
438
|
|
|
439
|
+
exports.blur = blur;
|
|
440
|
+
exports.change = change;
|
|
441
|
+
exports.createFileList = createFileList;
|
|
442
|
+
exports.createGlobalFormsObserver = createGlobalFormsObserver;
|
|
443
|
+
exports.focus = focus;
|
|
99
444
|
exports.getFormAction = getFormAction;
|
|
100
445
|
exports.getFormEncType = getFormEncType;
|
|
101
446
|
exports.getFormMethod = getFormMethod;
|
|
102
447
|
exports.isFieldElement = isFieldElement;
|
|
103
|
-
exports.
|
|
448
|
+
exports.isInputElement = isInputElement;
|
|
449
|
+
exports.isSelectElement = isSelectElement;
|
|
450
|
+
exports.isTextAreaElement = isTextAreaElement;
|
|
451
|
+
exports.normalizeFieldValue = normalizeFieldValue;
|
|
104
452
|
exports.requestSubmit = requestSubmit;
|
|
453
|
+
exports.updateField = updateField;
|
package/dist/dom.mjs
CHANGED
|
@@ -5,20 +5,18 @@ import { invariant } from './util.mjs';
|
|
|
5
5
|
* includes `<input>`, `<select>` and `<textarea>`.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
* HTML Element that can be used as a form control,
|
|
10
|
-
* includes `<input>`, `<select>`, `<textarea>` and `<button>`.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
8
|
/**
|
|
14
9
|
* Form Control element. It can either be a submit button or a submit input.
|
|
15
10
|
*/
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
function
|
|
21
|
-
return element
|
|
12
|
+
function isInputElement(element) {
|
|
13
|
+
return element.tagName === 'INPUT';
|
|
14
|
+
}
|
|
15
|
+
function isSelectElement(element) {
|
|
16
|
+
return element.tagName === 'SELECT';
|
|
17
|
+
}
|
|
18
|
+
function isTextAreaElement(element) {
|
|
19
|
+
return element.tagName === 'TEXTAREA';
|
|
22
20
|
}
|
|
23
21
|
|
|
24
22
|
/**
|
|
@@ -26,7 +24,15 @@ function isFormControl(element) {
|
|
|
26
24
|
* is a form control excluding submit, button and reset type.
|
|
27
25
|
*/
|
|
28
26
|
function isFieldElement(element) {
|
|
29
|
-
|
|
27
|
+
if (element instanceof Element) {
|
|
28
|
+
if (isInputElement(element)) {
|
|
29
|
+
return element.type !== 'submit' && element.type !== 'button' && element.type !== 'reset';
|
|
30
|
+
}
|
|
31
|
+
if (isSelectElement(element) || isTextAreaElement(element)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
/**
|
|
@@ -83,13 +89,347 @@ function requestSubmit(form, submitter) {
|
|
|
83
89
|
if (typeof form.requestSubmit === 'function') {
|
|
84
90
|
form.requestSubmit(submitter);
|
|
85
91
|
} else {
|
|
86
|
-
var
|
|
92
|
+
var _event = new SubmitEvent('submit', {
|
|
87
93
|
bubbles: true,
|
|
88
94
|
cancelable: true,
|
|
89
95
|
submitter
|
|
90
96
|
});
|
|
91
|
-
form.dispatchEvent(
|
|
97
|
+
form.dispatchEvent(_event);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function createFileList(value) {
|
|
101
|
+
var dataTransfer = new DataTransfer();
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
for (var file of value) {
|
|
104
|
+
dataTransfer.items.add(file);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
dataTransfer.items.add(value);
|
|
108
|
+
}
|
|
109
|
+
return dataTransfer.files;
|
|
110
|
+
}
|
|
111
|
+
function createGlobalFormsObserver() {
|
|
112
|
+
var inputListeners = new Set();
|
|
113
|
+
var formListeners = new Set();
|
|
114
|
+
var cleanup = null;
|
|
115
|
+
function initialize() {
|
|
116
|
+
var observer = new MutationObserver(handleMutation);
|
|
117
|
+
observer.observe(document.body, {
|
|
118
|
+
subtree: true,
|
|
119
|
+
childList: true,
|
|
120
|
+
attributeFilter: ['form', 'name', 'data-conform']
|
|
121
|
+
});
|
|
122
|
+
document.addEventListener('input', handleInput);
|
|
123
|
+
document.addEventListener('reset', handleReset);
|
|
124
|
+
document.addEventListener('submit', handleSubmit, true);
|
|
125
|
+
return () => {
|
|
126
|
+
document.removeEventListener('input', handleInput);
|
|
127
|
+
document.removeEventListener('reset', handleReset);
|
|
128
|
+
document.removeEventListener('submit', handleSubmit, true);
|
|
129
|
+
observer.disconnect();
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function handleInput(event) {
|
|
133
|
+
var target = event.target;
|
|
134
|
+
if (isFieldElement(target)) {
|
|
135
|
+
inputListeners.forEach(callback => callback({
|
|
136
|
+
type: 'input',
|
|
137
|
+
target
|
|
138
|
+
}));
|
|
139
|
+
var form = target.form;
|
|
140
|
+
if (form) {
|
|
141
|
+
formListeners.forEach(callback => callback({
|
|
142
|
+
type: 'input',
|
|
143
|
+
target: form
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function handleReset(event) {
|
|
149
|
+
var form = event.target;
|
|
150
|
+
if (form instanceof HTMLFormElement) {
|
|
151
|
+
// Reset event is fired before the form is reset, so we need to wait for the next tick
|
|
152
|
+
setTimeout(() => {
|
|
153
|
+
formListeners.forEach(callback => {
|
|
154
|
+
callback({
|
|
155
|
+
type: 'reset',
|
|
156
|
+
target: form
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
var _loop = function _loop(target) {
|
|
160
|
+
if (isFieldElement(target)) {
|
|
161
|
+
inputListeners.forEach(callback => {
|
|
162
|
+
callback({
|
|
163
|
+
type: 'reset',
|
|
164
|
+
target
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
for (var target of form.elements) {
|
|
170
|
+
_loop(target);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function handleSubmit(event) {
|
|
176
|
+
var target = event.target;
|
|
177
|
+
var submitter = event.submitter;
|
|
178
|
+
if (target instanceof HTMLFormElement) {
|
|
179
|
+
formListeners.forEach(callback => callback({
|
|
180
|
+
type: 'submit',
|
|
181
|
+
target,
|
|
182
|
+
submitter
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function handleMutation(mutations) {
|
|
187
|
+
var seenForms = new Set();
|
|
188
|
+
var seenInputs = new Set();
|
|
189
|
+
var collectInputs = node => {
|
|
190
|
+
if (isFieldElement(node)) {
|
|
191
|
+
return [node];
|
|
192
|
+
}
|
|
193
|
+
return node instanceof Element ? Array.from(node.querySelectorAll('input,select,textarea')) : [];
|
|
194
|
+
};
|
|
195
|
+
for (var mutation of mutations) {
|
|
196
|
+
switch (mutation.type) {
|
|
197
|
+
case 'childList':
|
|
198
|
+
{
|
|
199
|
+
var nodes = [...mutation.addedNodes, ...mutation.removedNodes];
|
|
200
|
+
for (var node of nodes) {
|
|
201
|
+
for (var input of collectInputs(node)) {
|
|
202
|
+
seenInputs.add(input);
|
|
203
|
+
if (input.form) {
|
|
204
|
+
seenForms.add(input.form);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case 'attributes':
|
|
211
|
+
{
|
|
212
|
+
if (isFieldElement(mutation.target)) {
|
|
213
|
+
seenInputs.add(mutation.target);
|
|
214
|
+
if (mutation.target.form) {
|
|
215
|
+
seenForms.add(mutation.target.form);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
var _loop2 = function _loop2(target) {
|
|
223
|
+
formListeners.forEach(callback => {
|
|
224
|
+
callback({
|
|
225
|
+
type: 'mutation',
|
|
226
|
+
target
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
};
|
|
230
|
+
for (var target of seenForms) {
|
|
231
|
+
_loop2(target);
|
|
232
|
+
}
|
|
233
|
+
var _loop3 = function _loop3(_target) {
|
|
234
|
+
inputListeners.forEach(callback => {
|
|
235
|
+
callback({
|
|
236
|
+
type: 'mutation',
|
|
237
|
+
target: _target
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
for (var _target of seenInputs) {
|
|
242
|
+
_loop3(_target);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
onFieldUpdate(callback) {
|
|
247
|
+
var _cleanup;
|
|
248
|
+
cleanup = (_cleanup = cleanup) !== null && _cleanup !== void 0 ? _cleanup : initialize();
|
|
249
|
+
inputListeners.add(callback);
|
|
250
|
+
return () => {
|
|
251
|
+
inputListeners.delete(callback);
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
onFormUpdate(callback) {
|
|
255
|
+
var _cleanup2;
|
|
256
|
+
cleanup = (_cleanup2 = cleanup) !== null && _cleanup2 !== void 0 ? _cleanup2 : initialize();
|
|
257
|
+
formListeners.add(callback);
|
|
258
|
+
return () => {
|
|
259
|
+
formListeners.delete(callback);
|
|
260
|
+
};
|
|
261
|
+
},
|
|
262
|
+
dispose() {
|
|
263
|
+
var _cleanup3;
|
|
264
|
+
(_cleanup3 = cleanup) === null || _cleanup3 === void 0 || _cleanup3();
|
|
265
|
+
cleanup = null;
|
|
266
|
+
inputListeners.clear();
|
|
267
|
+
formListeners.clear();
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function change(element, value) {
|
|
272
|
+
// The value should be set to the element before dispatching the event
|
|
273
|
+
updateField(element, {
|
|
274
|
+
value
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Dispatch input event with the updated input value
|
|
278
|
+
element.dispatchEvent(new InputEvent('input', {
|
|
279
|
+
bubbles: true
|
|
280
|
+
}));
|
|
281
|
+
// Dispatch change event (necessary for select to update the selected option)
|
|
282
|
+
element.dispatchEvent(new Event('change', {
|
|
283
|
+
bubbles: true
|
|
284
|
+
}));
|
|
285
|
+
}
|
|
286
|
+
function focus(element) {
|
|
287
|
+
// Only focusin event will be bubbled
|
|
288
|
+
element.dispatchEvent(new FocusEvent('focusin', {
|
|
289
|
+
bubbles: true
|
|
290
|
+
}));
|
|
291
|
+
element.dispatchEvent(new FocusEvent('focus'));
|
|
292
|
+
}
|
|
293
|
+
function blur(element) {
|
|
294
|
+
// Only focusout event will be bubbled
|
|
295
|
+
element.dispatchEvent(new FocusEvent('focusout', {
|
|
296
|
+
bubbles: true
|
|
297
|
+
}));
|
|
298
|
+
element.dispatchEvent(new FocusEvent('blur'));
|
|
299
|
+
}
|
|
300
|
+
function normalizeFieldValue(value) {
|
|
301
|
+
if (typeof value === 'undefined') {
|
|
302
|
+
return [null, null];
|
|
303
|
+
}
|
|
304
|
+
if (value === null) {
|
|
305
|
+
return [[], createFileList([])];
|
|
306
|
+
}
|
|
307
|
+
if (typeof value === 'string') {
|
|
308
|
+
return [[value], null];
|
|
309
|
+
}
|
|
310
|
+
if (Array.isArray(value)) {
|
|
311
|
+
if (value.every(item => typeof item === 'string')) {
|
|
312
|
+
return [Array.from(value), null];
|
|
313
|
+
}
|
|
314
|
+
if (value.every(item => item instanceof File)) {
|
|
315
|
+
return [null, createFileList(value)];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (value instanceof FileList) {
|
|
319
|
+
return [null, value];
|
|
320
|
+
}
|
|
321
|
+
if (value instanceof File) {
|
|
322
|
+
return [null, createFileList([value])];
|
|
323
|
+
}
|
|
324
|
+
return [null, null];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Updates the DOM element with the provided value and defaultValue.
|
|
329
|
+
*/
|
|
330
|
+
function updateField(element, options) {
|
|
331
|
+
var _value$;
|
|
332
|
+
var [value, file] = normalizeFieldValue(options.value);
|
|
333
|
+
var [defaultValue] = normalizeFieldValue(options.defaultValue);
|
|
334
|
+
if (isInputElement(element)) {
|
|
335
|
+
switch (element.type) {
|
|
336
|
+
case 'file':
|
|
337
|
+
{
|
|
338
|
+
element.files = file;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
case 'checkbox':
|
|
342
|
+
case 'radio':
|
|
343
|
+
{
|
|
344
|
+
if (value) {
|
|
345
|
+
var checked = value.includes(element.value);
|
|
346
|
+
if (element.type === 'checkbox' ? checked !== element.checked : checked) {
|
|
347
|
+
// Simulate a click to update the checked state
|
|
348
|
+
element.click();
|
|
349
|
+
}
|
|
350
|
+
element.checked = checked;
|
|
351
|
+
}
|
|
352
|
+
if (defaultValue) {
|
|
353
|
+
element.defaultChecked = defaultValue.includes(element.value);
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} else if (isSelectElement(element)) {
|
|
359
|
+
var shouldUnselect = value && value.length === 0;
|
|
360
|
+
for (var option of element.options) {
|
|
361
|
+
if (value) {
|
|
362
|
+
var index = value.indexOf(option.value);
|
|
363
|
+
var selected = index > -1;
|
|
364
|
+
|
|
365
|
+
// Update the selected state of the option
|
|
366
|
+
if (option.selected !== selected) {
|
|
367
|
+
option.selected = selected;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Remove the option from the value array
|
|
371
|
+
if (selected) {
|
|
372
|
+
value.splice(index, 1);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (defaultValue) {
|
|
376
|
+
var _index = defaultValue.indexOf(option.value);
|
|
377
|
+
var _selected = _index > -1;
|
|
378
|
+
|
|
379
|
+
// Update the selected state of the option
|
|
380
|
+
if (option.defaultSelected !== _selected) {
|
|
381
|
+
option.defaultSelected = _selected;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Remove the option from the defaultValue array
|
|
385
|
+
if (_selected) {
|
|
386
|
+
defaultValue.splice(_index, 1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// We have already removed all selected options from the value and defaultValue array at this point
|
|
392
|
+
var missingOptions = new Set([...(value !== null && value !== void 0 ? value : []), ...(defaultValue !== null && defaultValue !== void 0 ? defaultValue : [])]);
|
|
393
|
+
for (var optionValue of missingOptions) {
|
|
394
|
+
element.options.add(new Option(optionValue, optionValue, defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.includes(optionValue), value === null || value === void 0 ? void 0 : value.includes(optionValue)));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// If the select element is not multiple and the value is an empty array, unset the selected index
|
|
398
|
+
// This is to prevent the select element from showing the first option as selected
|
|
399
|
+
if (shouldUnselect) {
|
|
400
|
+
element.selectedIndex = -1;
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
var inputValue = (_value$ = value === null || value === void 0 ? void 0 : value[0]) !== null && _value$ !== void 0 ? _value$ : '';
|
|
405
|
+
if (element.value !== inputValue) {
|
|
406
|
+
/**
|
|
407
|
+
* Triggering react custom change event
|
|
408
|
+
* Solution based on dom-testing-library
|
|
409
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
410
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
411
|
+
*/
|
|
412
|
+
var {
|
|
413
|
+
set: valueSetter
|
|
414
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
415
|
+
var prototype = Object.getPrototypeOf(element);
|
|
416
|
+
var {
|
|
417
|
+
set: prototypeValueSetter
|
|
418
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
419
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
420
|
+
prototypeValueSetter.call(element, inputValue);
|
|
421
|
+
} else {
|
|
422
|
+
if (valueSetter) {
|
|
423
|
+
valueSetter.call(element, inputValue);
|
|
424
|
+
} else {
|
|
425
|
+
throw new Error('The given element does not have a value setter');
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (defaultValue) {
|
|
430
|
+
var _defaultValue$;
|
|
431
|
+
element.defaultValue = (_defaultValue$ = defaultValue[0]) !== null && _defaultValue$ !== void 0 ? _defaultValue$ : '';
|
|
92
432
|
}
|
|
93
433
|
}
|
|
94
434
|
|
|
95
|
-
export { getFormAction, getFormEncType, getFormMethod, isFieldElement,
|
|
435
|
+
export { blur, change, createFileList, createGlobalFormsObserver, focus, getFormAction, getFormEncType, getFormMethod, isFieldElement, isInputElement, isSelectElement, isTextAreaElement, normalizeFieldValue, requestSubmit, updateField };
|
package/dist/form.d.ts
CHANGED
|
@@ -147,15 +147,5 @@ export type FormContext<Schema extends Record<string, any> = any, FormError = st
|
|
|
147
147
|
};
|
|
148
148
|
};
|
|
149
149
|
export declare function createFormContext<Schema extends Record<string, any>, FormError = string[], FormValue = Schema>(options: FormOptions<Schema, FormError, FormValue>): FormContext<Schema, FormError, FormValue>;
|
|
150
|
-
/**
|
|
151
|
-
* Updates the DOM element with the provided value.
|
|
152
|
-
*
|
|
153
|
-
* @param element The form element to update
|
|
154
|
-
* @param options The options to update the form element
|
|
155
|
-
*/
|
|
156
|
-
export declare function updateFieldValue(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, options: {
|
|
157
|
-
value?: string | string[];
|
|
158
|
-
defaultValue?: string | string[];
|
|
159
|
-
}): void;
|
|
160
150
|
export {};
|
|
161
151
|
//# sourceMappingURL=form.d.ts.map
|
package/dist/form.js
CHANGED
|
@@ -582,8 +582,8 @@ function createFormContext(options) {
|
|
|
582
582
|
var paths = formdata.getChildPaths(parentPaths, element.name);
|
|
583
583
|
if (paths) {
|
|
584
584
|
var value = formdata.getValue(intent.payload.value, formdata.formatPaths(paths));
|
|
585
|
-
|
|
586
|
-
value: typeof value === 'string' || Array.isArray(value) && value.every(item => typeof item === 'string') ? value :
|
|
585
|
+
dom.updateField(element, {
|
|
586
|
+
value: typeof value === 'string' || Array.isArray(value) && value.every(item => typeof item === 'string') ? value : null
|
|
587
587
|
});
|
|
588
588
|
|
|
589
589
|
// Update the element attribute to notify useControl / useInputControl hook
|
|
@@ -597,10 +597,10 @@ function createFormContext(options) {
|
|
|
597
597
|
{
|
|
598
598
|
var prefix = formdata.formatName(intent.payload.name, intent.payload.index);
|
|
599
599
|
for (var _element of formElement.elements) {
|
|
600
|
-
if (dom.isFieldElement(_element) && formdata.isPrefix(_element.name, prefix)) {
|
|
600
|
+
if (dom.isFieldElement(_element) && _element.name && formdata.isPrefix(_element.name, prefix)) {
|
|
601
601
|
var _value2 = formdata.getValue(meta.defaultValue, _element.name);
|
|
602
|
-
var defaultValue = typeof _value2 === 'string' || Array.isArray(_value2) && _value2.every(item => typeof item === 'string') ? _value2 :
|
|
603
|
-
|
|
602
|
+
var defaultValue = typeof _value2 === 'string' || Array.isArray(_value2) && _value2.every(item => typeof item === 'string') ? _value2 : null;
|
|
603
|
+
dom.updateField(_element, {
|
|
604
604
|
defaultValue,
|
|
605
605
|
value: defaultValue
|
|
606
606
|
});
|
|
@@ -638,97 +638,4 @@ function createFormContext(options) {
|
|
|
638
638
|
};
|
|
639
639
|
}
|
|
640
640
|
|
|
641
|
-
/**
|
|
642
|
-
* Updates the DOM element with the provided value.
|
|
643
|
-
*
|
|
644
|
-
* @param element The form element to update
|
|
645
|
-
* @param options The options to update the form element
|
|
646
|
-
*/
|
|
647
|
-
function updateFieldValue(element, options) {
|
|
648
|
-
var value = typeof options.value === 'undefined' ? null : Array.isArray(options.value) ? Array.from(options.value) : [options.value];
|
|
649
|
-
var defaultValue = typeof options.defaultValue === 'undefined' ? null : Array.isArray(options.defaultValue) ? Array.from(options.defaultValue) : [options.defaultValue];
|
|
650
|
-
if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
|
|
651
|
-
if (value) {
|
|
652
|
-
element.checked = value.includes(element.value);
|
|
653
|
-
}
|
|
654
|
-
if (defaultValue) {
|
|
655
|
-
element.defaultChecked = defaultValue.includes(element.value);
|
|
656
|
-
}
|
|
657
|
-
} else if (element instanceof HTMLSelectElement) {
|
|
658
|
-
// If the select element is not multiple and the value is an empty array, unset the selected index
|
|
659
|
-
// This is to prevent the select element from showing the first option as selected
|
|
660
|
-
if (value && value.length === 0 && !element.multiple) {
|
|
661
|
-
element.selectedIndex = -1;
|
|
662
|
-
}
|
|
663
|
-
for (var option of element.options) {
|
|
664
|
-
if (value) {
|
|
665
|
-
var index = value.indexOf(option.value);
|
|
666
|
-
var selected = index > -1;
|
|
667
|
-
|
|
668
|
-
// Update the selected state of the option
|
|
669
|
-
if (option.selected !== selected) {
|
|
670
|
-
option.selected = selected;
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
// Remove the option from the value array
|
|
674
|
-
if (selected) {
|
|
675
|
-
value.splice(index, 1);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
if (defaultValue) {
|
|
679
|
-
var _index = defaultValue.indexOf(option.value);
|
|
680
|
-
var _selected = _index > -1;
|
|
681
|
-
|
|
682
|
-
// Update the selected state of the option
|
|
683
|
-
if (option.selected !== _selected) {
|
|
684
|
-
option.defaultSelected = _selected;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// Remove the option from the defaultValue array
|
|
688
|
-
if (_selected) {
|
|
689
|
-
defaultValue.splice(_index, 1);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// We have already removed all selected options from the value and defaultValue array at this point
|
|
695
|
-
var missingOptions = new Set([...(value !== null && value !== void 0 ? value : []), ...(defaultValue !== null && defaultValue !== void 0 ? defaultValue : [])]);
|
|
696
|
-
for (var optionValue of missingOptions) {
|
|
697
|
-
element.options.add(new Option(optionValue, optionValue, defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.includes(optionValue), value === null || value === void 0 ? void 0 : value.includes(optionValue)));
|
|
698
|
-
}
|
|
699
|
-
} else {
|
|
700
|
-
if (value) {
|
|
701
|
-
var _value$;
|
|
702
|
-
/**
|
|
703
|
-
* Triggering react custom change event
|
|
704
|
-
* Solution based on dom-testing-library
|
|
705
|
-
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
706
|
-
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
707
|
-
*/
|
|
708
|
-
var inputValue = (_value$ = value[0]) !== null && _value$ !== void 0 ? _value$ : '';
|
|
709
|
-
var {
|
|
710
|
-
set: valueSetter
|
|
711
|
-
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
712
|
-
var prototype = Object.getPrototypeOf(element);
|
|
713
|
-
var {
|
|
714
|
-
set: prototypeValueSetter
|
|
715
|
-
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
716
|
-
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
717
|
-
prototypeValueSetter.call(element, inputValue);
|
|
718
|
-
} else {
|
|
719
|
-
if (valueSetter) {
|
|
720
|
-
valueSetter.call(element, inputValue);
|
|
721
|
-
} else {
|
|
722
|
-
throw new Error('The given element does not have a value setter');
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
if (defaultValue) {
|
|
727
|
-
var _defaultValue$;
|
|
728
|
-
element.defaultValue = (_defaultValue$ = defaultValue[0]) !== null && _defaultValue$ !== void 0 ? _defaultValue$ : '';
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
641
|
exports.createFormContext = createFormContext;
|
|
734
|
-
exports.updateFieldValue = updateFieldValue;
|
package/dist/form.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
|
|
2
2
|
import { flatten, formatName, getValue, isPlainObject, isPrefix, setValue, normalize, getFormData, getPaths, getChildPaths, formatPaths } from './formdata.mjs';
|
|
3
|
-
import { getFormAction, getFormEncType, getFormMethod, isFieldElement, requestSubmit } from './dom.mjs';
|
|
3
|
+
import { getFormAction, getFormEncType, getFormMethod, isFieldElement, requestSubmit, updateField } from './dom.mjs';
|
|
4
4
|
import { generateId, clone, invariant } from './util.mjs';
|
|
5
5
|
import { serialize, setListState, setListValue, setState, INTENT, serializeIntent, root, getSubmissionContext } from './submission.mjs';
|
|
6
6
|
|
|
@@ -578,8 +578,8 @@ function createFormContext(options) {
|
|
|
578
578
|
var paths = getChildPaths(parentPaths, element.name);
|
|
579
579
|
if (paths) {
|
|
580
580
|
var value = getValue(intent.payload.value, formatPaths(paths));
|
|
581
|
-
|
|
582
|
-
value: typeof value === 'string' || Array.isArray(value) && value.every(item => typeof item === 'string') ? value :
|
|
581
|
+
updateField(element, {
|
|
582
|
+
value: typeof value === 'string' || Array.isArray(value) && value.every(item => typeof item === 'string') ? value : null
|
|
583
583
|
});
|
|
584
584
|
|
|
585
585
|
// Update the element attribute to notify useControl / useInputControl hook
|
|
@@ -593,10 +593,10 @@ function createFormContext(options) {
|
|
|
593
593
|
{
|
|
594
594
|
var prefix = formatName(intent.payload.name, intent.payload.index);
|
|
595
595
|
for (var _element of formElement.elements) {
|
|
596
|
-
if (isFieldElement(_element) && isPrefix(_element.name, prefix)) {
|
|
596
|
+
if (isFieldElement(_element) && _element.name && isPrefix(_element.name, prefix)) {
|
|
597
597
|
var _value2 = getValue(meta.defaultValue, _element.name);
|
|
598
|
-
var defaultValue = typeof _value2 === 'string' || Array.isArray(_value2) && _value2.every(item => typeof item === 'string') ? _value2 :
|
|
599
|
-
|
|
598
|
+
var defaultValue = typeof _value2 === 'string' || Array.isArray(_value2) && _value2.every(item => typeof item === 'string') ? _value2 : null;
|
|
599
|
+
updateField(_element, {
|
|
600
600
|
defaultValue,
|
|
601
601
|
value: defaultValue
|
|
602
602
|
});
|
|
@@ -634,96 +634,4 @@ function createFormContext(options) {
|
|
|
634
634
|
};
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
-
|
|
638
|
-
* Updates the DOM element with the provided value.
|
|
639
|
-
*
|
|
640
|
-
* @param element The form element to update
|
|
641
|
-
* @param options The options to update the form element
|
|
642
|
-
*/
|
|
643
|
-
function updateFieldValue(element, options) {
|
|
644
|
-
var value = typeof options.value === 'undefined' ? null : Array.isArray(options.value) ? Array.from(options.value) : [options.value];
|
|
645
|
-
var defaultValue = typeof options.defaultValue === 'undefined' ? null : Array.isArray(options.defaultValue) ? Array.from(options.defaultValue) : [options.defaultValue];
|
|
646
|
-
if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
|
|
647
|
-
if (value) {
|
|
648
|
-
element.checked = value.includes(element.value);
|
|
649
|
-
}
|
|
650
|
-
if (defaultValue) {
|
|
651
|
-
element.defaultChecked = defaultValue.includes(element.value);
|
|
652
|
-
}
|
|
653
|
-
} else if (element instanceof HTMLSelectElement) {
|
|
654
|
-
// If the select element is not multiple and the value is an empty array, unset the selected index
|
|
655
|
-
// This is to prevent the select element from showing the first option as selected
|
|
656
|
-
if (value && value.length === 0 && !element.multiple) {
|
|
657
|
-
element.selectedIndex = -1;
|
|
658
|
-
}
|
|
659
|
-
for (var option of element.options) {
|
|
660
|
-
if (value) {
|
|
661
|
-
var index = value.indexOf(option.value);
|
|
662
|
-
var selected = index > -1;
|
|
663
|
-
|
|
664
|
-
// Update the selected state of the option
|
|
665
|
-
if (option.selected !== selected) {
|
|
666
|
-
option.selected = selected;
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// Remove the option from the value array
|
|
670
|
-
if (selected) {
|
|
671
|
-
value.splice(index, 1);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
if (defaultValue) {
|
|
675
|
-
var _index = defaultValue.indexOf(option.value);
|
|
676
|
-
var _selected = _index > -1;
|
|
677
|
-
|
|
678
|
-
// Update the selected state of the option
|
|
679
|
-
if (option.selected !== _selected) {
|
|
680
|
-
option.defaultSelected = _selected;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Remove the option from the defaultValue array
|
|
684
|
-
if (_selected) {
|
|
685
|
-
defaultValue.splice(_index, 1);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// We have already removed all selected options from the value and defaultValue array at this point
|
|
691
|
-
var missingOptions = new Set([...(value !== null && value !== void 0 ? value : []), ...(defaultValue !== null && defaultValue !== void 0 ? defaultValue : [])]);
|
|
692
|
-
for (var optionValue of missingOptions) {
|
|
693
|
-
element.options.add(new Option(optionValue, optionValue, defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue.includes(optionValue), value === null || value === void 0 ? void 0 : value.includes(optionValue)));
|
|
694
|
-
}
|
|
695
|
-
} else {
|
|
696
|
-
if (value) {
|
|
697
|
-
var _value$;
|
|
698
|
-
/**
|
|
699
|
-
* Triggering react custom change event
|
|
700
|
-
* Solution based on dom-testing-library
|
|
701
|
-
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
702
|
-
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
703
|
-
*/
|
|
704
|
-
var inputValue = (_value$ = value[0]) !== null && _value$ !== void 0 ? _value$ : '';
|
|
705
|
-
var {
|
|
706
|
-
set: valueSetter
|
|
707
|
-
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
708
|
-
var prototype = Object.getPrototypeOf(element);
|
|
709
|
-
var {
|
|
710
|
-
set: prototypeValueSetter
|
|
711
|
-
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
712
|
-
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
713
|
-
prototypeValueSetter.call(element, inputValue);
|
|
714
|
-
} else {
|
|
715
|
-
if (valueSetter) {
|
|
716
|
-
valueSetter.call(element, inputValue);
|
|
717
|
-
} else {
|
|
718
|
-
throw new Error('The given element does not have a value setter');
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
if (defaultValue) {
|
|
723
|
-
var _defaultValue$;
|
|
724
|
-
element.defaultValue = (_defaultValue$ = defaultValue[0]) !== null && _defaultValue$ !== void 0 ? _defaultValue$ : '';
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
export { createFormContext, updateFieldValue };
|
|
637
|
+
export { createFormContext };
|
package/dist/formdata.d.ts
CHANGED
|
@@ -64,4 +64,5 @@ export declare function flatten(data: unknown, options?: {
|
|
|
64
64
|
resolve?: (data: unknown) => unknown;
|
|
65
65
|
prefix?: string;
|
|
66
66
|
}): Record<string, unknown>;
|
|
67
|
+
export declare function deepEqual<Value>(prev: Value, next: Value): boolean;
|
|
67
68
|
//# sourceMappingURL=formdata.d.ts.map
|
package/dist/formdata.js
CHANGED
|
@@ -213,7 +213,43 @@ function flatten(data) {
|
|
|
213
213
|
}
|
|
214
214
|
return result;
|
|
215
215
|
}
|
|
216
|
+
function deepEqual(prev, next) {
|
|
217
|
+
if (prev === next) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
if (!prev || !next) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
if (Array.isArray(prev) && Array.isArray(next)) {
|
|
224
|
+
if (prev.length !== next.length) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
for (var i = 0; i < prev.length; i++) {
|
|
228
|
+
if (!deepEqual(prev[i], next[i])) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
if (isPlainObject(prev) && isPlainObject(next)) {
|
|
235
|
+
var prevKeys = Object.keys(prev);
|
|
236
|
+
var nextKeys = Object.keys(next);
|
|
237
|
+
if (prevKeys.length !== nextKeys.length) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
for (var key of prevKeys) {
|
|
241
|
+
if (!Object.prototype.hasOwnProperty.call(next, key) ||
|
|
242
|
+
// @ts-expect-error FIXME
|
|
243
|
+
!deepEqual(prev[key], next[key])) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
216
251
|
|
|
252
|
+
exports.deepEqual = deepEqual;
|
|
217
253
|
exports.flatten = flatten;
|
|
218
254
|
exports.formatName = formatName;
|
|
219
255
|
exports.formatPaths = formatPaths;
|
package/dist/formdata.mjs
CHANGED
|
@@ -209,5 +209,40 @@ function flatten(data) {
|
|
|
209
209
|
}
|
|
210
210
|
return result;
|
|
211
211
|
}
|
|
212
|
+
function deepEqual(prev, next) {
|
|
213
|
+
if (prev === next) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
if (!prev || !next) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (Array.isArray(prev) && Array.isArray(next)) {
|
|
220
|
+
if (prev.length !== next.length) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
for (var i = 0; i < prev.length; i++) {
|
|
224
|
+
if (!deepEqual(prev[i], next[i])) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (isPlainObject(prev) && isPlainObject(next)) {
|
|
231
|
+
var prevKeys = Object.keys(prev);
|
|
232
|
+
var nextKeys = Object.keys(next);
|
|
233
|
+
if (prevKeys.length !== nextKeys.length) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
for (var key of prevKeys) {
|
|
237
|
+
if (!Object.prototype.hasOwnProperty.call(next, key) ||
|
|
238
|
+
// @ts-expect-error FIXME
|
|
239
|
+
!deepEqual(prev[key], next[key])) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
212
247
|
|
|
213
|
-
export { flatten, formatName, formatPaths, getChildPaths, getFormData, getPaths, getValue, isFile, isPlainObject, isPrefix, normalize, setValue };
|
|
248
|
+
export { deepEqual, flatten, formatName, formatPaths, getChildPaths, getFormData, getPaths, getValue, isFile, isPlainObject, isPrefix, normalize, setValue };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type Combine, type Constraint, type ControlButtonProps, type FormId, type FieldName, type DefaultValue, type FormValue, type FormOptions, type FormState, type FormContext, type SubscriptionSubject, type SubscriptionScope, createFormContext as unstable_createFormContext,
|
|
2
|
-
export { type FieldElement, isFieldElement } from './dom';
|
|
1
|
+
export { type Combine, type Constraint, type ControlButtonProps, type FormId, type FieldName, type DefaultValue, type FormValue, type FormOptions, type FormState, type FormContext, type SubscriptionSubject, type SubscriptionScope, createFormContext as unstable_createFormContext, } from './form';
|
|
2
|
+
export { type FieldElement, isFieldElement, updateField as unstable_updateField, createFileList, createGlobalFormsObserver as unstable_createGlobalFormsObserver, focus as unstable_focus, change as unstable_change, blur as unstable_blur, } from './dom';
|
|
3
3
|
export { type Submission, type SubmissionResult, type Intent, INTENT, STATE, serializeIntent, parse, } from './submission';
|
|
4
|
-
export { getPaths, formatPaths, isPrefix } from './formdata';
|
|
4
|
+
export { getPaths, formatPaths, isPrefix, deepEqual as unstable_deepEqual, } from './formdata';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -10,8 +10,13 @@ var formdata = require('./formdata.js');
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
exports.unstable_createFormContext = form.createFormContext;
|
|
13
|
-
exports.
|
|
13
|
+
exports.createFileList = dom.createFileList;
|
|
14
14
|
exports.isFieldElement = dom.isFieldElement;
|
|
15
|
+
exports.unstable_blur = dom.blur;
|
|
16
|
+
exports.unstable_change = dom.change;
|
|
17
|
+
exports.unstable_createGlobalFormsObserver = dom.createGlobalFormsObserver;
|
|
18
|
+
exports.unstable_focus = dom.focus;
|
|
19
|
+
exports.unstable_updateField = dom.updateField;
|
|
15
20
|
exports.INTENT = submission.INTENT;
|
|
16
21
|
exports.STATE = submission.STATE;
|
|
17
22
|
exports.parse = submission.parse;
|
|
@@ -19,3 +24,4 @@ exports.serializeIntent = submission.serializeIntent;
|
|
|
19
24
|
exports.formatPaths = formdata.formatPaths;
|
|
20
25
|
exports.getPaths = formdata.getPaths;
|
|
21
26
|
exports.isPrefix = formdata.isPrefix;
|
|
27
|
+
exports.unstable_deepEqual = formdata.deepEqual;
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { createFormContext as unstable_createFormContext
|
|
2
|
-
export { isFieldElement } from './dom.mjs';
|
|
1
|
+
export { createFormContext as unstable_createFormContext } from './form.mjs';
|
|
2
|
+
export { createFileList, isFieldElement, blur as unstable_blur, change as unstable_change, createGlobalFormsObserver as unstable_createGlobalFormsObserver, focus as unstable_focus, updateField as unstable_updateField } from './dom.mjs';
|
|
3
3
|
export { INTENT, STATE, parse, serializeIntent } from './submission.mjs';
|
|
4
|
-
export { formatPaths, getPaths, isPrefix } from './formdata.mjs';
|
|
4
|
+
export { formatPaths, getPaths, isPrefix, deepEqual as unstable_deepEqual } from './formdata.mjs';
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "A set of opinionated helpers built on top of the Constraint Validation API",
|
|
4
4
|
"homepage": "https://conform.guide",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "1.
|
|
6
|
+
"version": "1.7.0",
|
|
7
7
|
"main": "./dist/index.js",
|
|
8
8
|
"module": "./dist/index.mjs",
|
|
9
9
|
"types": "./dist/index.d.ts",
|