@conform-to/react 1.0.0 → 1.0.1
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 +16 -11
- package/context.d.ts +1 -1
- package/integrations.d.ts +12 -10
- package/integrations.js +149 -74
- package/integrations.mjs +145 -74
- package/package.json +2 -2
package/README
CHANGED
|
@@ -8,25 +8,30 @@
|
|
|
8
8
|
╚══════╝ ╚═════╝ ╚═╝ ╚══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
Version 1.0.
|
|
11
|
+
Version 1.0.1 / License MIT / Copyright (c) 2024 Edmund Hung
|
|
12
12
|
|
|
13
13
|
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.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
# Getting Started
|
|
16
16
|
|
|
17
17
|
Check out the overview and tutorial at our website https://conform.guide
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Features
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
- Progressive enhancement first APIs
|
|
22
|
+
- Type-safe field inference
|
|
23
|
+
- Fine-grained subscription
|
|
24
|
+
- Built-in accessibility helpers
|
|
25
|
+
- Automatic type coercion with Zod
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
* Examples: https://conform.guide/examples
|
|
25
|
-
* Complex structures: https://conform.guide/complex-structures
|
|
26
|
-
* UI Integrations: https://conform.guide/integrations
|
|
27
|
-
* Accessibility Guide: https://conform.guide/accessibility
|
|
28
|
-
* API Reference: https://conform.guide/references
|
|
27
|
+
# Documentation
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
- Validation: https://conform.guide/validation
|
|
30
|
+
- Nested object and Array: https://conform.guide/complex-structures
|
|
31
|
+
- UI Integrations: https://conform.guide/integration/ui-libraries
|
|
32
|
+
- Intent button: https://conform.guide/intent-button
|
|
33
|
+
- Accessibility Guide: https://conform.guide/accessibility
|
|
34
|
+
|
|
35
|
+
# Support
|
|
31
36
|
|
|
32
37
|
To report a bug, please open an issue on the repository at https://github.com/edmundhung/conform. For feature requests and questions, you can post them in the Discussions section.
|
package/context.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type FormEvent, type ReactElement, type ReactNode, type MutableRefObjec
|
|
|
3
3
|
export type Pretty<T> = {
|
|
4
4
|
[K in keyof T]: T[K];
|
|
5
5
|
} & {};
|
|
6
|
-
export type Primitive = string | number | boolean | Date | File | null | undefined;
|
|
6
|
+
export type Primitive = string | number | bigint | boolean | Date | File | null | undefined;
|
|
7
7
|
export type Metadata<Schema, FormSchema extends Record<string, unknown>, FormError = string[]> = {
|
|
8
8
|
key: string | undefined;
|
|
9
9
|
id: string;
|
package/integrations.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { type FieldElement } from '@conform-to/dom';
|
|
2
1
|
import { type Key } from 'react';
|
|
3
|
-
export type InputControl = {
|
|
4
|
-
value:
|
|
5
|
-
change: (value:
|
|
2
|
+
export type InputControl<Value> = {
|
|
3
|
+
value: Value | undefined;
|
|
4
|
+
change: (value: Value) => void;
|
|
6
5
|
focus: () => void;
|
|
7
6
|
blur: () => void;
|
|
8
7
|
};
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export
|
|
8
|
+
export declare function getFormElement(formId: string): HTMLFormElement;
|
|
9
|
+
export declare function getFieldElements(form: HTMLFormElement, name: string): Array<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>;
|
|
10
|
+
export declare function getEventTarget(form: HTMLFormElement, name: string): HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | null;
|
|
11
|
+
export declare function createDummySelect(form: HTMLFormElement, name: string, value?: string | string[] | undefined): HTMLSelectElement;
|
|
12
|
+
export declare function isDummySelect(element: HTMLElement): element is HTMLSelectElement;
|
|
13
|
+
export declare function updateFieldValue(element: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, value: string | string[]): void;
|
|
14
|
+
export declare function useInputControl<Value>(metaOrOptions: {
|
|
12
15
|
key?: Key | null | undefined;
|
|
13
16
|
name: string;
|
|
14
17
|
formId: string;
|
|
15
|
-
initialValue?:
|
|
16
|
-
}
|
|
17
|
-
export declare function useInputControl(metaOrOptions: InputControlOptions): InputControl;
|
|
18
|
+
initialValue?: Value | undefined;
|
|
19
|
+
}): InputControl<Value extends string ? string : string | string[]>;
|
package/integrations.js
CHANGED
|
@@ -3,52 +3,141 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
|
|
6
|
-
var dom = require('@conform-to/dom');
|
|
7
6
|
var react = require('react');
|
|
8
7
|
|
|
9
|
-
function
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
function getFormElement(formId) {
|
|
9
|
+
var element = document.forms.namedItem(formId);
|
|
10
|
+
if (!element) {
|
|
11
|
+
throw new Error('Form not found');
|
|
12
|
+
}
|
|
13
|
+
return element;
|
|
14
|
+
}
|
|
15
|
+
function getFieldElements(form, name) {
|
|
16
|
+
var field = form.elements.namedItem(name);
|
|
17
|
+
var elements = !field ? [] : field instanceof Element ? [field] : Array.from(field.values());
|
|
18
|
+
return elements.filter(element => element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement);
|
|
19
|
+
}
|
|
20
|
+
function getEventTarget(form, name) {
|
|
21
|
+
var _elements$;
|
|
22
|
+
var elements = getFieldElements(form, name);
|
|
23
|
+
return (_elements$ = elements[0]) !== null && _elements$ !== void 0 ? _elements$ : null;
|
|
24
|
+
}
|
|
25
|
+
function createDummySelect(form, name, value) {
|
|
26
|
+
var select = document.createElement('select');
|
|
27
|
+
var options = typeof value === 'string' ? [value] : value !== null && value !== void 0 ? value : [];
|
|
28
|
+
select.name = name;
|
|
29
|
+
select.multiple = true;
|
|
30
|
+
select.dataset.conform = 'true';
|
|
31
|
+
|
|
32
|
+
// To make sure the input is hidden but still focusable
|
|
33
|
+
select.setAttribute('aria-hidden', 'true');
|
|
34
|
+
select.tabIndex = -1;
|
|
35
|
+
select.style.position = 'absolute';
|
|
36
|
+
select.style.width = '1px';
|
|
37
|
+
select.style.height = '1px';
|
|
38
|
+
select.style.padding = '0';
|
|
39
|
+
select.style.margin = '-1px';
|
|
40
|
+
select.style.overflow = 'hidden';
|
|
41
|
+
select.style.clip = 'rect(0,0,0,0)';
|
|
42
|
+
select.style.whiteSpace = 'nowrap';
|
|
43
|
+
select.style.border = '0';
|
|
44
|
+
for (var option of options) {
|
|
45
|
+
select.options.add(new Option(option, option, true, true));
|
|
20
46
|
}
|
|
21
|
-
|
|
47
|
+
form.appendChild(select);
|
|
48
|
+
return select;
|
|
22
49
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
function isDummySelect(element) {
|
|
51
|
+
return element.dataset.conform === 'true';
|
|
52
|
+
}
|
|
53
|
+
function updateFieldValue(element, value) {
|
|
54
|
+
if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
|
|
55
|
+
element.checked = element.value === value;
|
|
56
|
+
} else if (element instanceof HTMLSelectElement && element.multiple) {
|
|
57
|
+
var selectedValue = Array.isArray(value) ? [...value] : [value];
|
|
58
|
+
for (var option of element.options) {
|
|
59
|
+
var index = selectedValue.indexOf(option.value);
|
|
60
|
+
var selected = index > -1;
|
|
61
|
+
|
|
62
|
+
// Update the selected state of the option
|
|
63
|
+
option.selected = selected;
|
|
64
|
+
// Remove the option from the selected array
|
|
65
|
+
if (selected) {
|
|
66
|
+
selectedValue.splice(index, 1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Add the remaining options to the select element only if it's a dummy element managed by conform
|
|
71
|
+
if (isDummySelect(element)) {
|
|
72
|
+
for (var _option of selectedValue) {
|
|
73
|
+
element.options.add(new Option(_option, _option, false, true));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} else if (element.value !== value) {
|
|
77
|
+
// No `change` event will be triggered on React if `element.value` is already updated
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Triggering react custom change event
|
|
81
|
+
* Solution based on dom-testing-library
|
|
82
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
83
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
84
|
+
*/
|
|
85
|
+
var {
|
|
86
|
+
set: valueSetter
|
|
87
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
88
|
+
var prototype = Object.getPrototypeOf(element);
|
|
89
|
+
var {
|
|
90
|
+
set: prototypeValueSetter
|
|
91
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
92
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
93
|
+
prototypeValueSetter.call(element, value);
|
|
94
|
+
} else {
|
|
95
|
+
if (valueSetter) {
|
|
96
|
+
valueSetter.call(element, value);
|
|
97
|
+
} else {
|
|
98
|
+
throw new Error('The given element does not have a value setter');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
27
101
|
}
|
|
28
|
-
var form = document.forms.namedItem(formId);
|
|
29
|
-
var input = document.createElement('input');
|
|
30
|
-
input.type = 'hidden';
|
|
31
|
-
input.name = name;
|
|
32
|
-
form === null || form === void 0 || form.appendChild(input);
|
|
33
|
-
return input;
|
|
34
102
|
}
|
|
35
103
|
function useInputControl(metaOrOptions) {
|
|
104
|
+
// If the initialValue is an array, it must be a string array without undefined values
|
|
105
|
+
// as there is no way to skip an entry in a multiple select when they all share the same name
|
|
106
|
+
var inputInitialValue = metaOrOptions.initialValue;
|
|
36
107
|
var eventDispatched = react.useRef({
|
|
37
108
|
change: false,
|
|
38
109
|
focus: false,
|
|
39
110
|
blur: false
|
|
40
111
|
});
|
|
41
112
|
var [key, setKey] = react.useState(metaOrOptions.key);
|
|
42
|
-
var [
|
|
113
|
+
var [initialValue, setInitialValue] = react.useState(inputInitialValue);
|
|
114
|
+
var [value, setValue] = react.useState(inputInitialValue);
|
|
43
115
|
if (key !== metaOrOptions.key) {
|
|
44
|
-
setValue(
|
|
116
|
+
setValue(inputInitialValue);
|
|
117
|
+
setInitialValue(inputInitialValue);
|
|
45
118
|
setKey(metaOrOptions.key);
|
|
46
119
|
}
|
|
120
|
+
react.useEffect(() => {
|
|
121
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
122
|
+
if (getEventTarget(form, metaOrOptions.name)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
createDummySelect(form, metaOrOptions.name, initialValue);
|
|
126
|
+
return () => {
|
|
127
|
+
var elements = getFieldElements(form, metaOrOptions.name);
|
|
128
|
+
for (var element of elements) {
|
|
129
|
+
if (isDummySelect(element)) {
|
|
130
|
+
element.remove();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}, [metaOrOptions.formId, metaOrOptions.name, initialValue]);
|
|
47
135
|
react.useEffect(() => {
|
|
48
136
|
var createEventListener = listener => {
|
|
49
137
|
return event => {
|
|
50
|
-
var
|
|
51
|
-
|
|
138
|
+
var _element$form;
|
|
139
|
+
var element = event.target;
|
|
140
|
+
if ((element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) && element.name === metaOrOptions.name && ((_element$form = element.form) === null || _element$form === void 0 ? void 0 : _element$form.id) === metaOrOptions.formId) {
|
|
52
141
|
eventDispatched.current[listener] = true;
|
|
53
142
|
}
|
|
54
143
|
};
|
|
@@ -69,68 +158,50 @@ function useInputControl(metaOrOptions) {
|
|
|
69
158
|
return {
|
|
70
159
|
change(value) {
|
|
71
160
|
if (!eventDispatched.current.change) {
|
|
72
|
-
var _element = getEventTarget(metaOrOptions.formId, metaOrOptions.name);
|
|
73
161
|
eventDispatched.current.change = true;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
162
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
163
|
+
var element = getEventTarget(form, metaOrOptions.name);
|
|
164
|
+
if (element) {
|
|
165
|
+
updateFieldValue(element, value);
|
|
78
166
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
} = Object.getOwnPropertyDescriptor(_element, 'value') || {};
|
|
88
|
-
var prototype = Object.getPrototypeOf(_element);
|
|
89
|
-
var {
|
|
90
|
-
set: prototypeValueSetter
|
|
91
|
-
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
92
|
-
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
93
|
-
prototypeValueSetter.call(_element, value);
|
|
94
|
-
} else {
|
|
95
|
-
if (valueSetter) {
|
|
96
|
-
valueSetter.call(_element, value);
|
|
97
|
-
} else {
|
|
98
|
-
throw new Error('The given element does not have a value setter');
|
|
99
|
-
}
|
|
100
|
-
}
|
|
167
|
+
// Dispatch input event with the updated input value
|
|
168
|
+
element.dispatchEvent(new InputEvent('input', {
|
|
169
|
+
bubbles: true
|
|
170
|
+
}));
|
|
171
|
+
// Dispatch change event (necessary for select to update the selected option)
|
|
172
|
+
element.dispatchEvent(new Event('change', {
|
|
173
|
+
bubbles: true
|
|
174
|
+
}));
|
|
101
175
|
}
|
|
102
|
-
|
|
103
|
-
// Dispatch input event with the updated input value
|
|
104
|
-
_element.dispatchEvent(new InputEvent('input', {
|
|
105
|
-
bubbles: true
|
|
106
|
-
}));
|
|
107
|
-
// Dispatch change event (necessary for select to update the selected option)
|
|
108
|
-
_element.dispatchEvent(new Event('change', {
|
|
109
|
-
bubbles: true
|
|
110
|
-
}));
|
|
111
176
|
}
|
|
112
177
|
setValue(value);
|
|
113
178
|
eventDispatched.current.change = false;
|
|
114
179
|
},
|
|
115
180
|
focus() {
|
|
116
181
|
if (!eventDispatched.current.focus) {
|
|
117
|
-
var _element2 = getEventTarget(metaOrOptions.formId, metaOrOptions.name);
|
|
118
182
|
eventDispatched.current.focus = true;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
183
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
184
|
+
var element = getEventTarget(form, metaOrOptions.name);
|
|
185
|
+
if (element) {
|
|
186
|
+
element.dispatchEvent(new FocusEvent('focusin', {
|
|
187
|
+
bubbles: true
|
|
188
|
+
}));
|
|
189
|
+
element.dispatchEvent(new FocusEvent('focus'));
|
|
190
|
+
}
|
|
123
191
|
}
|
|
124
192
|
eventDispatched.current.focus = false;
|
|
125
193
|
},
|
|
126
194
|
blur() {
|
|
127
195
|
if (!eventDispatched.current.blur) {
|
|
128
|
-
var _element3 = getEventTarget(metaOrOptions.formId, metaOrOptions.name);
|
|
129
196
|
eventDispatched.current.blur = true;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
197
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
198
|
+
var element = getEventTarget(form, metaOrOptions.name);
|
|
199
|
+
if (element) {
|
|
200
|
+
element.dispatchEvent(new FocusEvent('focusout', {
|
|
201
|
+
bubbles: true
|
|
202
|
+
}));
|
|
203
|
+
element.dispatchEvent(new FocusEvent('blur'));
|
|
204
|
+
}
|
|
134
205
|
}
|
|
135
206
|
eventDispatched.current.blur = false;
|
|
136
207
|
}
|
|
@@ -141,6 +212,10 @@ function useInputControl(metaOrOptions) {
|
|
|
141
212
|
});
|
|
142
213
|
}
|
|
143
214
|
|
|
215
|
+
exports.createDummySelect = createDummySelect;
|
|
144
216
|
exports.getEventTarget = getEventTarget;
|
|
145
|
-
exports.
|
|
217
|
+
exports.getFieldElements = getFieldElements;
|
|
218
|
+
exports.getFormElement = getFormElement;
|
|
219
|
+
exports.isDummySelect = isDummySelect;
|
|
220
|
+
exports.updateFieldValue = updateFieldValue;
|
|
146
221
|
exports.useInputControl = useInputControl;
|
package/integrations.mjs
CHANGED
|
@@ -1,50 +1,139 @@
|
|
|
1
1
|
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.mjs';
|
|
2
|
-
import { isFieldElement } from '@conform-to/dom';
|
|
3
2
|
import { useRef, useState, useEffect, useMemo } from 'react';
|
|
4
3
|
|
|
5
|
-
function
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
function getFormElement(formId) {
|
|
5
|
+
var element = document.forms.namedItem(formId);
|
|
6
|
+
if (!element) {
|
|
7
|
+
throw new Error('Form not found');
|
|
8
|
+
}
|
|
9
|
+
return element;
|
|
10
|
+
}
|
|
11
|
+
function getFieldElements(form, name) {
|
|
12
|
+
var field = form.elements.namedItem(name);
|
|
13
|
+
var elements = !field ? [] : field instanceof Element ? [field] : Array.from(field.values());
|
|
14
|
+
return elements.filter(element => element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement);
|
|
15
|
+
}
|
|
16
|
+
function getEventTarget(form, name) {
|
|
17
|
+
var _elements$;
|
|
18
|
+
var elements = getFieldElements(form, name);
|
|
19
|
+
return (_elements$ = elements[0]) !== null && _elements$ !== void 0 ? _elements$ : null;
|
|
20
|
+
}
|
|
21
|
+
function createDummySelect(form, name, value) {
|
|
22
|
+
var select = document.createElement('select');
|
|
23
|
+
var options = typeof value === 'string' ? [value] : value !== null && value !== void 0 ? value : [];
|
|
24
|
+
select.name = name;
|
|
25
|
+
select.multiple = true;
|
|
26
|
+
select.dataset.conform = 'true';
|
|
27
|
+
|
|
28
|
+
// To make sure the input is hidden but still focusable
|
|
29
|
+
select.setAttribute('aria-hidden', 'true');
|
|
30
|
+
select.tabIndex = -1;
|
|
31
|
+
select.style.position = 'absolute';
|
|
32
|
+
select.style.width = '1px';
|
|
33
|
+
select.style.height = '1px';
|
|
34
|
+
select.style.padding = '0';
|
|
35
|
+
select.style.margin = '-1px';
|
|
36
|
+
select.style.overflow = 'hidden';
|
|
37
|
+
select.style.clip = 'rect(0,0,0,0)';
|
|
38
|
+
select.style.whiteSpace = 'nowrap';
|
|
39
|
+
select.style.border = '0';
|
|
40
|
+
for (var option of options) {
|
|
41
|
+
select.options.add(new Option(option, option, true, true));
|
|
16
42
|
}
|
|
17
|
-
|
|
43
|
+
form.appendChild(select);
|
|
44
|
+
return select;
|
|
18
45
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
46
|
+
function isDummySelect(element) {
|
|
47
|
+
return element.dataset.conform === 'true';
|
|
48
|
+
}
|
|
49
|
+
function updateFieldValue(element, value) {
|
|
50
|
+
if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
|
|
51
|
+
element.checked = element.value === value;
|
|
52
|
+
} else if (element instanceof HTMLSelectElement && element.multiple) {
|
|
53
|
+
var selectedValue = Array.isArray(value) ? [...value] : [value];
|
|
54
|
+
for (var option of element.options) {
|
|
55
|
+
var index = selectedValue.indexOf(option.value);
|
|
56
|
+
var selected = index > -1;
|
|
57
|
+
|
|
58
|
+
// Update the selected state of the option
|
|
59
|
+
option.selected = selected;
|
|
60
|
+
// Remove the option from the selected array
|
|
61
|
+
if (selected) {
|
|
62
|
+
selectedValue.splice(index, 1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Add the remaining options to the select element only if it's a dummy element managed by conform
|
|
67
|
+
if (isDummySelect(element)) {
|
|
68
|
+
for (var _option of selectedValue) {
|
|
69
|
+
element.options.add(new Option(_option, _option, false, true));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else if (element.value !== value) {
|
|
73
|
+
// No `change` event will be triggered on React if `element.value` is already updated
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Triggering react custom change event
|
|
77
|
+
* Solution based on dom-testing-library
|
|
78
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
79
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
80
|
+
*/
|
|
81
|
+
var {
|
|
82
|
+
set: valueSetter
|
|
83
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
84
|
+
var prototype = Object.getPrototypeOf(element);
|
|
85
|
+
var {
|
|
86
|
+
set: prototypeValueSetter
|
|
87
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
88
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
89
|
+
prototypeValueSetter.call(element, value);
|
|
90
|
+
} else {
|
|
91
|
+
if (valueSetter) {
|
|
92
|
+
valueSetter.call(element, value);
|
|
93
|
+
} else {
|
|
94
|
+
throw new Error('The given element does not have a value setter');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
23
97
|
}
|
|
24
|
-
var form = document.forms.namedItem(formId);
|
|
25
|
-
var input = document.createElement('input');
|
|
26
|
-
input.type = 'hidden';
|
|
27
|
-
input.name = name;
|
|
28
|
-
form === null || form === void 0 || form.appendChild(input);
|
|
29
|
-
return input;
|
|
30
98
|
}
|
|
31
99
|
function useInputControl(metaOrOptions) {
|
|
100
|
+
// If the initialValue is an array, it must be a string array without undefined values
|
|
101
|
+
// as there is no way to skip an entry in a multiple select when they all share the same name
|
|
102
|
+
var inputInitialValue = metaOrOptions.initialValue;
|
|
32
103
|
var eventDispatched = useRef({
|
|
33
104
|
change: false,
|
|
34
105
|
focus: false,
|
|
35
106
|
blur: false
|
|
36
107
|
});
|
|
37
108
|
var [key, setKey] = useState(metaOrOptions.key);
|
|
38
|
-
var [
|
|
109
|
+
var [initialValue, setInitialValue] = useState(inputInitialValue);
|
|
110
|
+
var [value, setValue] = useState(inputInitialValue);
|
|
39
111
|
if (key !== metaOrOptions.key) {
|
|
40
|
-
setValue(
|
|
112
|
+
setValue(inputInitialValue);
|
|
113
|
+
setInitialValue(inputInitialValue);
|
|
41
114
|
setKey(metaOrOptions.key);
|
|
42
115
|
}
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
118
|
+
if (getEventTarget(form, metaOrOptions.name)) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
createDummySelect(form, metaOrOptions.name, initialValue);
|
|
122
|
+
return () => {
|
|
123
|
+
var elements = getFieldElements(form, metaOrOptions.name);
|
|
124
|
+
for (var element of elements) {
|
|
125
|
+
if (isDummySelect(element)) {
|
|
126
|
+
element.remove();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}, [metaOrOptions.formId, metaOrOptions.name, initialValue]);
|
|
43
131
|
useEffect(() => {
|
|
44
132
|
var createEventListener = listener => {
|
|
45
133
|
return event => {
|
|
46
|
-
var
|
|
47
|
-
|
|
134
|
+
var _element$form;
|
|
135
|
+
var element = event.target;
|
|
136
|
+
if ((element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement) && element.name === metaOrOptions.name && ((_element$form = element.form) === null || _element$form === void 0 ? void 0 : _element$form.id) === metaOrOptions.formId) {
|
|
48
137
|
eventDispatched.current[listener] = true;
|
|
49
138
|
}
|
|
50
139
|
};
|
|
@@ -65,68 +154,50 @@ function useInputControl(metaOrOptions) {
|
|
|
65
154
|
return {
|
|
66
155
|
change(value) {
|
|
67
156
|
if (!eventDispatched.current.change) {
|
|
68
|
-
var _element = getEventTarget(metaOrOptions.formId, metaOrOptions.name);
|
|
69
157
|
eventDispatched.current.change = true;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
158
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
159
|
+
var element = getEventTarget(form, metaOrOptions.name);
|
|
160
|
+
if (element) {
|
|
161
|
+
updateFieldValue(element, value);
|
|
74
162
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
} = Object.getOwnPropertyDescriptor(_element, 'value') || {};
|
|
84
|
-
var prototype = Object.getPrototypeOf(_element);
|
|
85
|
-
var {
|
|
86
|
-
set: prototypeValueSetter
|
|
87
|
-
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
88
|
-
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
89
|
-
prototypeValueSetter.call(_element, value);
|
|
90
|
-
} else {
|
|
91
|
-
if (valueSetter) {
|
|
92
|
-
valueSetter.call(_element, value);
|
|
93
|
-
} else {
|
|
94
|
-
throw new Error('The given element does not have a value setter');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
163
|
+
// Dispatch input event with the updated input value
|
|
164
|
+
element.dispatchEvent(new InputEvent('input', {
|
|
165
|
+
bubbles: true
|
|
166
|
+
}));
|
|
167
|
+
// Dispatch change event (necessary for select to update the selected option)
|
|
168
|
+
element.dispatchEvent(new Event('change', {
|
|
169
|
+
bubbles: true
|
|
170
|
+
}));
|
|
97
171
|
}
|
|
98
|
-
|
|
99
|
-
// Dispatch input event with the updated input value
|
|
100
|
-
_element.dispatchEvent(new InputEvent('input', {
|
|
101
|
-
bubbles: true
|
|
102
|
-
}));
|
|
103
|
-
// Dispatch change event (necessary for select to update the selected option)
|
|
104
|
-
_element.dispatchEvent(new Event('change', {
|
|
105
|
-
bubbles: true
|
|
106
|
-
}));
|
|
107
172
|
}
|
|
108
173
|
setValue(value);
|
|
109
174
|
eventDispatched.current.change = false;
|
|
110
175
|
},
|
|
111
176
|
focus() {
|
|
112
177
|
if (!eventDispatched.current.focus) {
|
|
113
|
-
var _element2 = getEventTarget(metaOrOptions.formId, metaOrOptions.name);
|
|
114
178
|
eventDispatched.current.focus = true;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
179
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
180
|
+
var element = getEventTarget(form, metaOrOptions.name);
|
|
181
|
+
if (element) {
|
|
182
|
+
element.dispatchEvent(new FocusEvent('focusin', {
|
|
183
|
+
bubbles: true
|
|
184
|
+
}));
|
|
185
|
+
element.dispatchEvent(new FocusEvent('focus'));
|
|
186
|
+
}
|
|
119
187
|
}
|
|
120
188
|
eventDispatched.current.focus = false;
|
|
121
189
|
},
|
|
122
190
|
blur() {
|
|
123
191
|
if (!eventDispatched.current.blur) {
|
|
124
|
-
var _element3 = getEventTarget(metaOrOptions.formId, metaOrOptions.name);
|
|
125
192
|
eventDispatched.current.blur = true;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
193
|
+
var form = getFormElement(metaOrOptions.formId);
|
|
194
|
+
var element = getEventTarget(form, metaOrOptions.name);
|
|
195
|
+
if (element) {
|
|
196
|
+
element.dispatchEvent(new FocusEvent('focusout', {
|
|
197
|
+
bubbles: true
|
|
198
|
+
}));
|
|
199
|
+
element.dispatchEvent(new FocusEvent('blur'));
|
|
200
|
+
}
|
|
130
201
|
}
|
|
131
202
|
eventDispatched.current.blur = false;
|
|
132
203
|
}
|
|
@@ -137,4 +208,4 @@ function useInputControl(metaOrOptions) {
|
|
|
137
208
|
});
|
|
138
209
|
}
|
|
139
210
|
|
|
140
|
-
export { getEventTarget,
|
|
211
|
+
export { createDummySelect, getEventTarget, getFieldElements, getFormElement, isDummySelect, updateFieldValue, useInputControl };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "Conform view adapter for react",
|
|
4
4
|
"homepage": "https://conform.guide",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "1.0.
|
|
6
|
+
"version": "1.0.1",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"module": "index.mjs",
|
|
9
9
|
"types": "index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"url": "https://github.com/edmundhung/conform/issues"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@conform-to/dom": "1.0.
|
|
33
|
+
"@conform-to/dom": "1.0.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/react": "^18.2.43",
|