@conform-to/react 0.5.0 → 0.5.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.md +54 -1
- package/helpers.d.ts +13 -7
- package/helpers.js +32 -2
- package/hooks.d.ts +26 -2
- package/hooks.js +180 -14
- package/index.js +1 -0
- package/module/helpers.js +32 -2
- package/module/hooks.js +181 -16
- package/module/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,8 +9,12 @@
|
|
|
9
9
|
- [useForm](#useform)
|
|
10
10
|
- [useFieldset](#usefieldset)
|
|
11
11
|
- [useFieldList](#usefieldlist)
|
|
12
|
+
- [useInputEvent](#useinputevent)
|
|
12
13
|
- [useControlledInput](#usecontrolledinput)
|
|
13
14
|
- [conform](#conform)
|
|
15
|
+
- [list](#list)
|
|
16
|
+
- [validate](#validate)
|
|
17
|
+
- [requestCommand](#requestcommand)
|
|
14
18
|
- [getFormElements](#getformelements)
|
|
15
19
|
- [hasError](#haserror)
|
|
16
20
|
- [parse](#parse)
|
|
@@ -278,14 +282,63 @@ function Example() {
|
|
|
278
282
|
|
|
279
283
|
---
|
|
280
284
|
|
|
285
|
+
### useInputEvent
|
|
286
|
+
|
|
287
|
+
It returns a ref object and a set of helpers that dispatch corresponding dom event.
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
import { useForm, useInputEvent } from '@conform-to/react';
|
|
291
|
+
import { Select, MenuItem } from '@mui/material';
|
|
292
|
+
import { useState, useRef } from 'react';
|
|
293
|
+
|
|
294
|
+
function MuiForm() {
|
|
295
|
+
const [form, { category }] = useForm();
|
|
296
|
+
const [value, setValue] = useState(category.config.defaultValue ?? '');
|
|
297
|
+
const [ref, control] = useInputEvent({
|
|
298
|
+
onReset: () => setValue(category.config.defaultValue ?? ''),
|
|
299
|
+
});
|
|
300
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<form {...form.props}>
|
|
304
|
+
{/* Render a shadow input somewhere */}
|
|
305
|
+
<input
|
|
306
|
+
ref={ref}
|
|
307
|
+
{...conform.input(category.config, { hidden: true })}
|
|
308
|
+
onChange={(e) => setValue(e.target.value)}
|
|
309
|
+
onFocus={() => inputRef.current?.focus()}
|
|
310
|
+
/>
|
|
311
|
+
|
|
312
|
+
{/* MUI Select is a controlled component */}
|
|
313
|
+
<TextField
|
|
314
|
+
label="Category"
|
|
315
|
+
inputRef={inputRef}
|
|
316
|
+
value={value}
|
|
317
|
+
onChange={control.change}
|
|
318
|
+
onBlur={control.blur}
|
|
319
|
+
select
|
|
320
|
+
>
|
|
321
|
+
<MenuItem value="">Please select</MenuItem>
|
|
322
|
+
<MenuItem value="a">Category A</MenuItem>
|
|
323
|
+
<MenuItem value="b">Category B</MenuItem>
|
|
324
|
+
<MenuItem value="c">Category C</MenuItem>
|
|
325
|
+
</TextField>
|
|
326
|
+
</form>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
281
333
|
### useControlledInput
|
|
282
334
|
|
|
335
|
+
> This API is deprecated and replaced with the [useInputEvent](#useinputevent) hook.
|
|
336
|
+
|
|
283
337
|
It returns the properties required to configure a shadow input for validation and helper to integrate it. This is particularly useful when [integrating custom input components](/docs/integrations.md#custom-input-component) like dropdown and datepicker.
|
|
284
338
|
|
|
285
339
|
```tsx
|
|
286
340
|
import { useForm, useControlledInput } from '@conform-to/react';
|
|
287
341
|
import { Select, MenuItem } from '@mui/material';
|
|
288
|
-
import { useRef } from 'react';
|
|
289
342
|
|
|
290
343
|
function MuiForm() {
|
|
291
344
|
const [form, { category }] = useForm();
|
package/helpers.d.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { FieldConfig } from '@conform-to/dom';
|
|
2
|
-
import type { HTMLInputTypeAttribute } from 'react';
|
|
2
|
+
import type { CSSProperties, HTMLInputTypeAttribute } from 'react';
|
|
3
3
|
interface FieldProps {
|
|
4
4
|
id?: string;
|
|
5
5
|
name: string;
|
|
6
6
|
form?: string;
|
|
7
7
|
required?: boolean;
|
|
8
8
|
autoFocus?: boolean;
|
|
9
|
+
tabIndex?: number;
|
|
10
|
+
style?: CSSProperties;
|
|
9
11
|
'aria-invalid': boolean;
|
|
10
12
|
'aria-describedby'?: string;
|
|
13
|
+
'aria-hidden'?: boolean;
|
|
11
14
|
}
|
|
12
15
|
interface InputProps<Schema> extends FieldProps {
|
|
13
16
|
type?: HTMLInputTypeAttribute;
|
|
@@ -33,18 +36,21 @@ interface TextareaProps extends FieldProps {
|
|
|
33
36
|
}
|
|
34
37
|
declare type InputOptions = {
|
|
35
38
|
type: 'checkbox' | 'radio';
|
|
39
|
+
hidden?: boolean;
|
|
36
40
|
value?: string;
|
|
37
41
|
} | {
|
|
38
|
-
type
|
|
39
|
-
|
|
40
|
-
} | {
|
|
41
|
-
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden' | 'file'>;
|
|
42
|
+
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden'>;
|
|
43
|
+
hidden?: boolean;
|
|
42
44
|
value?: never;
|
|
43
45
|
};
|
|
44
46
|
export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: {
|
|
45
47
|
type: 'file';
|
|
46
48
|
}): InputProps<Schema>;
|
|
47
49
|
export declare function input<Schema extends any>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
|
|
48
|
-
export declare function select<Schema>(config: FieldConfig<Schema
|
|
49
|
-
|
|
50
|
+
export declare function select<Schema>(config: FieldConfig<Schema>, options?: {
|
|
51
|
+
hidden?: boolean;
|
|
52
|
+
}): SelectProps;
|
|
53
|
+
export declare function textarea<Schema>(config: FieldConfig<Schema>, options?: {
|
|
54
|
+
hidden?: boolean;
|
|
55
|
+
}): TextareaProps;
|
|
50
56
|
export {};
|
package/helpers.js
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Style to make the input element visually hidden
|
|
7
|
+
* Based on the `sr-only` class from tailwindcss
|
|
8
|
+
*/
|
|
9
|
+
var hiddenStyle = {
|
|
10
|
+
position: 'absolute',
|
|
11
|
+
width: '1px',
|
|
12
|
+
height: '1px',
|
|
13
|
+
padding: 0,
|
|
14
|
+
margin: '-1px',
|
|
15
|
+
overflow: 'hidden',
|
|
16
|
+
clip: 'rect(0,0,0,0)',
|
|
17
|
+
whiteSpace: 'nowrap',
|
|
18
|
+
border: 0
|
|
19
|
+
};
|
|
5
20
|
function input(config) {
|
|
6
21
|
var _config$initialError;
|
|
7
22
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -21,6 +36,11 @@ function input(config) {
|
|
|
21
36
|
'aria-invalid': Boolean((_config$initialError = config.initialError) === null || _config$initialError === void 0 ? void 0 : _config$initialError.length),
|
|
22
37
|
'aria-describedby': config.errorId
|
|
23
38
|
};
|
|
39
|
+
if (options !== null && options !== void 0 && options.hidden) {
|
|
40
|
+
attributes.style = hiddenStyle;
|
|
41
|
+
attributes.tabIndex = -1;
|
|
42
|
+
attributes['aria-hidden'] = true;
|
|
43
|
+
}
|
|
24
44
|
if (config.initialError && config.initialError.length > 0) {
|
|
25
45
|
attributes.autoFocus = true;
|
|
26
46
|
}
|
|
@@ -33,7 +53,7 @@ function input(config) {
|
|
|
33
53
|
}
|
|
34
54
|
return attributes;
|
|
35
55
|
}
|
|
36
|
-
function select(config) {
|
|
56
|
+
function select(config, options) {
|
|
37
57
|
var _config$defaultValue, _config$initialError2;
|
|
38
58
|
var attributes = {
|
|
39
59
|
id: config.id,
|
|
@@ -45,12 +65,17 @@ function select(config) {
|
|
|
45
65
|
'aria-invalid': Boolean((_config$initialError2 = config.initialError) === null || _config$initialError2 === void 0 ? void 0 : _config$initialError2.length),
|
|
46
66
|
'aria-describedby': config.errorId
|
|
47
67
|
};
|
|
68
|
+
if (options !== null && options !== void 0 && options.hidden) {
|
|
69
|
+
attributes.style = hiddenStyle;
|
|
70
|
+
attributes.tabIndex = -1;
|
|
71
|
+
attributes['aria-hidden'] = true;
|
|
72
|
+
}
|
|
48
73
|
if (config.initialError && config.initialError.length > 0) {
|
|
49
74
|
attributes.autoFocus = true;
|
|
50
75
|
}
|
|
51
76
|
return attributes;
|
|
52
77
|
}
|
|
53
|
-
function textarea(config) {
|
|
78
|
+
function textarea(config, options) {
|
|
54
79
|
var _config$defaultValue2, _config$initialError3;
|
|
55
80
|
var attributes = {
|
|
56
81
|
id: config.id,
|
|
@@ -64,6 +89,11 @@ function textarea(config) {
|
|
|
64
89
|
'aria-invalid': Boolean((_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : _config$initialError3.length),
|
|
65
90
|
'aria-describedby': config.errorId
|
|
66
91
|
};
|
|
92
|
+
if (options !== null && options !== void 0 && options.hidden) {
|
|
93
|
+
attributes.style = hiddenStyle;
|
|
94
|
+
attributes.tabIndex = -1;
|
|
95
|
+
attributes['aria-hidden'] = true;
|
|
96
|
+
}
|
|
67
97
|
if (config.initialError && config.initialError.length > 0) {
|
|
68
98
|
attributes.autoFocus = true;
|
|
69
99
|
}
|
package/hooks.d.ts
CHANGED
|
@@ -136,7 +136,7 @@ export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormEleme
|
|
|
136
136
|
interface ShadowInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
137
137
|
ref: RefObject<HTMLInputElement>;
|
|
138
138
|
}
|
|
139
|
-
interface
|
|
139
|
+
interface LegacyInputControl<Element extends {
|
|
140
140
|
focus: () => void;
|
|
141
141
|
}> {
|
|
142
142
|
ref: RefObject<Element>;
|
|
@@ -154,9 +154,33 @@ interface InputControl<Element extends {
|
|
|
154
154
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
155
155
|
* introduces custom input mode.
|
|
156
156
|
*
|
|
157
|
+
* @deprecated Please use the `useInputEvent` hook instead
|
|
157
158
|
* @see https://conform.guide/api/react#usecontrolledinput
|
|
158
159
|
*/
|
|
159
160
|
export declare function useControlledInput<Element extends {
|
|
160
161
|
focus: () => void;
|
|
161
|
-
} = HTMLInputElement, Schema extends Primitive = Primitive>(config: FieldConfig<Schema>): [ShadowInputProps,
|
|
162
|
+
} = HTMLInputElement, Schema extends Primitive = Primitive>(config: FieldConfig<Schema>): [ShadowInputProps, LegacyInputControl<Element>];
|
|
163
|
+
interface InputControl {
|
|
164
|
+
change: (eventOrValue: {
|
|
165
|
+
target: {
|
|
166
|
+
value: string;
|
|
167
|
+
};
|
|
168
|
+
} | string) => void;
|
|
169
|
+
focus: () => void;
|
|
170
|
+
blur: () => void;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Returns a ref object and a set of helpers that dispatch corresponding dom event.
|
|
174
|
+
*
|
|
175
|
+
* @see https://conform.guide/api/react#useinputevent
|
|
176
|
+
*/
|
|
177
|
+
export declare function useInputEvent<RefShape extends FieldElement = HTMLInputElement>(options?: {
|
|
178
|
+
onSubmit?: (event: SubmitEvent) => void;
|
|
179
|
+
onReset?: (event: Event) => void;
|
|
180
|
+
}): [RefObject<RefShape>, InputControl];
|
|
181
|
+
export declare function useInputEvent<RefShape extends Exclude<any, FieldElement>>(options: {
|
|
182
|
+
getElement: (ref: RefShape | null) => FieldElement | null | undefined;
|
|
183
|
+
onSubmit?: (event: SubmitEvent) => void;
|
|
184
|
+
onReset?: (event: Event) => void;
|
|
185
|
+
}): [RefObject<RefShape>, InputControl];
|
|
162
186
|
export {};
|
package/hooks.js
CHANGED
|
@@ -489,6 +489,7 @@ function useFieldList(ref, config) {
|
|
|
489
489
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
490
490
|
* introduces custom input mode.
|
|
491
491
|
*
|
|
492
|
+
* @deprecated Please use the `useInputEvent` hook instead
|
|
492
493
|
* @see https://conform.guide/api/react#usecontrolledinput
|
|
493
494
|
*/
|
|
494
495
|
function useControlledInput(config) {
|
|
@@ -544,24 +545,13 @@ function useControlledInput(config) {
|
|
|
544
545
|
}, []);
|
|
545
546
|
return [_rollupPluginBabelHelpers.objectSpread2({
|
|
546
547
|
ref,
|
|
547
|
-
style: {
|
|
548
|
-
position: 'absolute',
|
|
549
|
-
width: '1px',
|
|
550
|
-
height: '1px',
|
|
551
|
-
padding: 0,
|
|
552
|
-
margin: '-1px',
|
|
553
|
-
overflow: 'hidden',
|
|
554
|
-
clip: 'rect(0,0,0,0)',
|
|
555
|
-
whiteSpace: 'nowrap',
|
|
556
|
-
borderWidth: 0
|
|
557
|
-
},
|
|
558
|
-
tabIndex: -1,
|
|
559
|
-
'aria-hidden': true,
|
|
560
548
|
onFocus() {
|
|
561
549
|
var _inputRef$current;
|
|
562
550
|
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
563
551
|
}
|
|
564
|
-
}, helpers.input(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), uncontrolledState)
|
|
552
|
+
}, helpers.input(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), uncontrolledState), {
|
|
553
|
+
hidden: true
|
|
554
|
+
})), {
|
|
565
555
|
ref: inputRef,
|
|
566
556
|
value,
|
|
567
557
|
onChange: handleChange,
|
|
@@ -570,7 +560,183 @@ function useControlledInput(config) {
|
|
|
570
560
|
}];
|
|
571
561
|
}
|
|
572
562
|
|
|
563
|
+
/**
|
|
564
|
+
* Triggering react custom change event
|
|
565
|
+
* Solution based on dom-testing-library
|
|
566
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
567
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
568
|
+
*/
|
|
569
|
+
function setNativeValue(element, value) {
|
|
570
|
+
if (element.value === value) {
|
|
571
|
+
// It will not trigger a change event if `element.value` is the same as the set value
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
var {
|
|
575
|
+
set: valueSetter
|
|
576
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
577
|
+
var prototype = Object.getPrototypeOf(element);
|
|
578
|
+
var {
|
|
579
|
+
set: prototypeValueSetter
|
|
580
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
581
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
582
|
+
prototypeValueSetter.call(element, value);
|
|
583
|
+
} else {
|
|
584
|
+
if (valueSetter) {
|
|
585
|
+
valueSetter.call(element, value);
|
|
586
|
+
} else {
|
|
587
|
+
throw new Error('The given element does not have a value setter');
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* useLayoutEffect is client-only.
|
|
594
|
+
* This basically makes it a no-op on server
|
|
595
|
+
*/
|
|
596
|
+
var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect;
|
|
597
|
+
function useInputEvent(options) {
|
|
598
|
+
var ref = react.useRef(null);
|
|
599
|
+
var optionsRef = react.useRef(options);
|
|
600
|
+
var changeDispatched = react.useRef(false);
|
|
601
|
+
var focusDispatched = react.useRef(false);
|
|
602
|
+
var blurDispatched = react.useRef(false);
|
|
603
|
+
useSafeLayoutEffect(() => {
|
|
604
|
+
optionsRef.current = options;
|
|
605
|
+
});
|
|
606
|
+
useSafeLayoutEffect(() => {
|
|
607
|
+
var getInputElement = () => {
|
|
608
|
+
var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2;
|
|
609
|
+
return (_optionsRef$current$g = (_optionsRef$current = optionsRef.current) === null || _optionsRef$current === void 0 ? void 0 : (_optionsRef$current$g2 = _optionsRef$current.getElement) === null || _optionsRef$current$g2 === void 0 ? void 0 : _optionsRef$current$g2.call(_optionsRef$current, ref.current)) !== null && _optionsRef$current$g !== void 0 ? _optionsRef$current$g : ref.current;
|
|
610
|
+
};
|
|
611
|
+
var inputHandler = event => {
|
|
612
|
+
var input = getInputElement();
|
|
613
|
+
if (input && event.target === input) {
|
|
614
|
+
changeDispatched.current = true;
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
var focusHandler = event => {
|
|
618
|
+
var input = getInputElement();
|
|
619
|
+
if (input && event.target === input) {
|
|
620
|
+
focusDispatched.current = true;
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
var blurHandler = event => {
|
|
624
|
+
var input = getInputElement();
|
|
625
|
+
if (input && event.target === input) {
|
|
626
|
+
blurDispatched.current = true;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
var submitHandler = event => {
|
|
630
|
+
var input = getInputElement();
|
|
631
|
+
if (input !== null && input !== void 0 && input.form && event.target === input.form) {
|
|
632
|
+
var _optionsRef$current2, _optionsRef$current2$;
|
|
633
|
+
(_optionsRef$current2 = optionsRef.current) === null || _optionsRef$current2 === void 0 ? void 0 : (_optionsRef$current2$ = _optionsRef$current2.onSubmit) === null || _optionsRef$current2$ === void 0 ? void 0 : _optionsRef$current2$.call(_optionsRef$current2, event);
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
var resetHandler = event => {
|
|
637
|
+
var input = getInputElement();
|
|
638
|
+
if (input !== null && input !== void 0 && input.form && event.target === input.form) {
|
|
639
|
+
var _optionsRef$current3, _optionsRef$current3$;
|
|
640
|
+
(_optionsRef$current3 = optionsRef.current) === null || _optionsRef$current3 === void 0 ? void 0 : (_optionsRef$current3$ = _optionsRef$current3.onReset) === null || _optionsRef$current3$ === void 0 ? void 0 : _optionsRef$current3$.call(_optionsRef$current3, event);
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
document.addEventListener('input', inputHandler, true);
|
|
644
|
+
document.addEventListener('focus', focusHandler, true);
|
|
645
|
+
document.addEventListener('blur', blurHandler, true);
|
|
646
|
+
document.addEventListener('submit', submitHandler);
|
|
647
|
+
document.addEventListener('reset', resetHandler);
|
|
648
|
+
return () => {
|
|
649
|
+
document.removeEventListener('input', inputHandler, true);
|
|
650
|
+
document.removeEventListener('focus', focusHandler, true);
|
|
651
|
+
document.removeEventListener('blur', blurHandler, true);
|
|
652
|
+
document.removeEventListener('submit', submitHandler);
|
|
653
|
+
document.removeEventListener('reset', resetHandler);
|
|
654
|
+
};
|
|
655
|
+
}, []);
|
|
656
|
+
var control = react.useMemo(() => {
|
|
657
|
+
var getInputElement = () => {
|
|
658
|
+
var _optionsRef$current$g3, _optionsRef$current4, _optionsRef$current4$;
|
|
659
|
+
return (_optionsRef$current$g3 = (_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 ? void 0 : (_optionsRef$current4$ = _optionsRef$current4.getElement) === null || _optionsRef$current4$ === void 0 ? void 0 : _optionsRef$current4$.call(_optionsRef$current4, ref.current)) !== null && _optionsRef$current$g3 !== void 0 ? _optionsRef$current$g3 : ref.current;
|
|
660
|
+
};
|
|
661
|
+
return {
|
|
662
|
+
change(eventOrValue) {
|
|
663
|
+
var input = getInputElement();
|
|
664
|
+
if (!input) {
|
|
665
|
+
console.warn('Missing input ref; No change-related events will be dispatched');
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (changeDispatched.current) {
|
|
669
|
+
changeDispatched.current = false;
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
var previousValue = input.value;
|
|
673
|
+
var nextValue = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value;
|
|
674
|
+
|
|
675
|
+
// This make sure no event is dispatched on the first effect run
|
|
676
|
+
if (nextValue === previousValue) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Dispatch beforeinput event before updating the input value
|
|
681
|
+
input.dispatchEvent(new Event('beforeinput', {
|
|
682
|
+
bubbles: true
|
|
683
|
+
}));
|
|
684
|
+
// Update the input value to trigger a change event
|
|
685
|
+
setNativeValue(input, nextValue);
|
|
686
|
+
// Dispatch input event with the updated input value
|
|
687
|
+
input.dispatchEvent(new InputEvent('input', {
|
|
688
|
+
bubbles: true
|
|
689
|
+
}));
|
|
690
|
+
// Reset the dispatched flag
|
|
691
|
+
changeDispatched.current = false;
|
|
692
|
+
},
|
|
693
|
+
focus() {
|
|
694
|
+
var input = getInputElement();
|
|
695
|
+
if (!input) {
|
|
696
|
+
console.warn('Missing input ref; No focus-related events will be dispatched');
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
if (focusDispatched.current) {
|
|
700
|
+
focusDispatched.current = false;
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
var focusinEvent = new FocusEvent('focusin', {
|
|
704
|
+
bubbles: true
|
|
705
|
+
});
|
|
706
|
+
var focusEvent = new FocusEvent('focus');
|
|
707
|
+
input.dispatchEvent(focusinEvent);
|
|
708
|
+
input.dispatchEvent(focusEvent);
|
|
709
|
+
|
|
710
|
+
// Reset the dispatched flag
|
|
711
|
+
focusDispatched.current = false;
|
|
712
|
+
},
|
|
713
|
+
blur() {
|
|
714
|
+
var input = getInputElement();
|
|
715
|
+
if (!input) {
|
|
716
|
+
console.warn('Missing input ref; No blur-related events will be dispatched');
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
if (blurDispatched.current) {
|
|
720
|
+
blurDispatched.current = false;
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
var focusoutEvent = new FocusEvent('focusout', {
|
|
724
|
+
bubbles: true
|
|
725
|
+
});
|
|
726
|
+
var blurEvent = new FocusEvent('blur');
|
|
727
|
+
input.dispatchEvent(focusoutEvent);
|
|
728
|
+
input.dispatchEvent(blurEvent);
|
|
729
|
+
|
|
730
|
+
// Reset the dispatched flag
|
|
731
|
+
blurDispatched.current = false;
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
}, []);
|
|
735
|
+
return [ref, control];
|
|
736
|
+
}
|
|
737
|
+
|
|
573
738
|
exports.useControlledInput = useControlledInput;
|
|
574
739
|
exports.useFieldList = useFieldList;
|
|
575
740
|
exports.useFieldset = useFieldset;
|
|
576
741
|
exports.useForm = useForm;
|
|
742
|
+
exports.useInputEvent = useInputEvent;
|
package/index.js
CHANGED
package/module/helpers.js
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Style to make the input element visually hidden
|
|
3
|
+
* Based on the `sr-only` class from tailwindcss
|
|
4
|
+
*/
|
|
5
|
+
var hiddenStyle = {
|
|
6
|
+
position: 'absolute',
|
|
7
|
+
width: '1px',
|
|
8
|
+
height: '1px',
|
|
9
|
+
padding: 0,
|
|
10
|
+
margin: '-1px',
|
|
11
|
+
overflow: 'hidden',
|
|
12
|
+
clip: 'rect(0,0,0,0)',
|
|
13
|
+
whiteSpace: 'nowrap',
|
|
14
|
+
border: 0
|
|
15
|
+
};
|
|
1
16
|
function input(config) {
|
|
2
17
|
var _config$initialError;
|
|
3
18
|
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -17,6 +32,11 @@ function input(config) {
|
|
|
17
32
|
'aria-invalid': Boolean((_config$initialError = config.initialError) === null || _config$initialError === void 0 ? void 0 : _config$initialError.length),
|
|
18
33
|
'aria-describedby': config.errorId
|
|
19
34
|
};
|
|
35
|
+
if (options !== null && options !== void 0 && options.hidden) {
|
|
36
|
+
attributes.style = hiddenStyle;
|
|
37
|
+
attributes.tabIndex = -1;
|
|
38
|
+
attributes['aria-hidden'] = true;
|
|
39
|
+
}
|
|
20
40
|
if (config.initialError && config.initialError.length > 0) {
|
|
21
41
|
attributes.autoFocus = true;
|
|
22
42
|
}
|
|
@@ -29,7 +49,7 @@ function input(config) {
|
|
|
29
49
|
}
|
|
30
50
|
return attributes;
|
|
31
51
|
}
|
|
32
|
-
function select(config) {
|
|
52
|
+
function select(config, options) {
|
|
33
53
|
var _config$defaultValue, _config$initialError2;
|
|
34
54
|
var attributes = {
|
|
35
55
|
id: config.id,
|
|
@@ -41,12 +61,17 @@ function select(config) {
|
|
|
41
61
|
'aria-invalid': Boolean((_config$initialError2 = config.initialError) === null || _config$initialError2 === void 0 ? void 0 : _config$initialError2.length),
|
|
42
62
|
'aria-describedby': config.errorId
|
|
43
63
|
};
|
|
64
|
+
if (options !== null && options !== void 0 && options.hidden) {
|
|
65
|
+
attributes.style = hiddenStyle;
|
|
66
|
+
attributes.tabIndex = -1;
|
|
67
|
+
attributes['aria-hidden'] = true;
|
|
68
|
+
}
|
|
44
69
|
if (config.initialError && config.initialError.length > 0) {
|
|
45
70
|
attributes.autoFocus = true;
|
|
46
71
|
}
|
|
47
72
|
return attributes;
|
|
48
73
|
}
|
|
49
|
-
function textarea(config) {
|
|
74
|
+
function textarea(config, options) {
|
|
50
75
|
var _config$defaultValue2, _config$initialError3;
|
|
51
76
|
var attributes = {
|
|
52
77
|
id: config.id,
|
|
@@ -60,6 +85,11 @@ function textarea(config) {
|
|
|
60
85
|
'aria-invalid': Boolean((_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : _config$initialError3.length),
|
|
61
86
|
'aria-describedby': config.errorId
|
|
62
87
|
};
|
|
88
|
+
if (options !== null && options !== void 0 && options.hidden) {
|
|
89
|
+
attributes.style = hiddenStyle;
|
|
90
|
+
attributes.tabIndex = -1;
|
|
91
|
+
attributes['aria-hidden'] = true;
|
|
92
|
+
}
|
|
63
93
|
if (config.initialError && config.initialError.length > 0) {
|
|
64
94
|
attributes.autoFocus = true;
|
|
65
95
|
}
|
package/module/hooks.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
|
|
2
2
|
import { shouldValidate, reportSubmission, getFormData, parse, isFieldElement, hasError, getPaths, getName, requestCommand, validate, getFormElement, parseListCommand, updateList } from '@conform-to/dom';
|
|
3
|
-
import { useRef, useState, useEffect } from 'react';
|
|
3
|
+
import { useRef, useState, useEffect, useMemo, useLayoutEffect } from 'react';
|
|
4
4
|
import { input } from './helpers.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -485,6 +485,7 @@ function useFieldList(ref, config) {
|
|
|
485
485
|
* This is particular useful when integrating dropdown and datepicker whichs
|
|
486
486
|
* introduces custom input mode.
|
|
487
487
|
*
|
|
488
|
+
* @deprecated Please use the `useInputEvent` hook instead
|
|
488
489
|
* @see https://conform.guide/api/react#usecontrolledinput
|
|
489
490
|
*/
|
|
490
491
|
function useControlledInput(config) {
|
|
@@ -540,24 +541,13 @@ function useControlledInput(config) {
|
|
|
540
541
|
}, []);
|
|
541
542
|
return [_objectSpread2({
|
|
542
543
|
ref,
|
|
543
|
-
style: {
|
|
544
|
-
position: 'absolute',
|
|
545
|
-
width: '1px',
|
|
546
|
-
height: '1px',
|
|
547
|
-
padding: 0,
|
|
548
|
-
margin: '-1px',
|
|
549
|
-
overflow: 'hidden',
|
|
550
|
-
clip: 'rect(0,0,0,0)',
|
|
551
|
-
whiteSpace: 'nowrap',
|
|
552
|
-
borderWidth: 0
|
|
553
|
-
},
|
|
554
|
-
tabIndex: -1,
|
|
555
|
-
'aria-hidden': true,
|
|
556
544
|
onFocus() {
|
|
557
545
|
var _inputRef$current;
|
|
558
546
|
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 ? void 0 : _inputRef$current.focus();
|
|
559
547
|
}
|
|
560
|
-
}, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState)
|
|
548
|
+
}, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState), {
|
|
549
|
+
hidden: true
|
|
550
|
+
})), {
|
|
561
551
|
ref: inputRef,
|
|
562
552
|
value,
|
|
563
553
|
onChange: handleChange,
|
|
@@ -566,4 +556,179 @@ function useControlledInput(config) {
|
|
|
566
556
|
}];
|
|
567
557
|
}
|
|
568
558
|
|
|
569
|
-
|
|
559
|
+
/**
|
|
560
|
+
* Triggering react custom change event
|
|
561
|
+
* Solution based on dom-testing-library
|
|
562
|
+
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
|
|
563
|
+
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
|
|
564
|
+
*/
|
|
565
|
+
function setNativeValue(element, value) {
|
|
566
|
+
if (element.value === value) {
|
|
567
|
+
// It will not trigger a change event if `element.value` is the same as the set value
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
var {
|
|
571
|
+
set: valueSetter
|
|
572
|
+
} = Object.getOwnPropertyDescriptor(element, 'value') || {};
|
|
573
|
+
var prototype = Object.getPrototypeOf(element);
|
|
574
|
+
var {
|
|
575
|
+
set: prototypeValueSetter
|
|
576
|
+
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
|
|
577
|
+
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
|
|
578
|
+
prototypeValueSetter.call(element, value);
|
|
579
|
+
} else {
|
|
580
|
+
if (valueSetter) {
|
|
581
|
+
valueSetter.call(element, value);
|
|
582
|
+
} else {
|
|
583
|
+
throw new Error('The given element does not have a value setter');
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* useLayoutEffect is client-only.
|
|
590
|
+
* This basically makes it a no-op on server
|
|
591
|
+
*/
|
|
592
|
+
var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect;
|
|
593
|
+
function useInputEvent(options) {
|
|
594
|
+
var ref = useRef(null);
|
|
595
|
+
var optionsRef = useRef(options);
|
|
596
|
+
var changeDispatched = useRef(false);
|
|
597
|
+
var focusDispatched = useRef(false);
|
|
598
|
+
var blurDispatched = useRef(false);
|
|
599
|
+
useSafeLayoutEffect(() => {
|
|
600
|
+
optionsRef.current = options;
|
|
601
|
+
});
|
|
602
|
+
useSafeLayoutEffect(() => {
|
|
603
|
+
var getInputElement = () => {
|
|
604
|
+
var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2;
|
|
605
|
+
return (_optionsRef$current$g = (_optionsRef$current = optionsRef.current) === null || _optionsRef$current === void 0 ? void 0 : (_optionsRef$current$g2 = _optionsRef$current.getElement) === null || _optionsRef$current$g2 === void 0 ? void 0 : _optionsRef$current$g2.call(_optionsRef$current, ref.current)) !== null && _optionsRef$current$g !== void 0 ? _optionsRef$current$g : ref.current;
|
|
606
|
+
};
|
|
607
|
+
var inputHandler = event => {
|
|
608
|
+
var input = getInputElement();
|
|
609
|
+
if (input && event.target === input) {
|
|
610
|
+
changeDispatched.current = true;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
var focusHandler = event => {
|
|
614
|
+
var input = getInputElement();
|
|
615
|
+
if (input && event.target === input) {
|
|
616
|
+
focusDispatched.current = true;
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
var blurHandler = event => {
|
|
620
|
+
var input = getInputElement();
|
|
621
|
+
if (input && event.target === input) {
|
|
622
|
+
blurDispatched.current = true;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
var submitHandler = event => {
|
|
626
|
+
var input = getInputElement();
|
|
627
|
+
if (input !== null && input !== void 0 && input.form && event.target === input.form) {
|
|
628
|
+
var _optionsRef$current2, _optionsRef$current2$;
|
|
629
|
+
(_optionsRef$current2 = optionsRef.current) === null || _optionsRef$current2 === void 0 ? void 0 : (_optionsRef$current2$ = _optionsRef$current2.onSubmit) === null || _optionsRef$current2$ === void 0 ? void 0 : _optionsRef$current2$.call(_optionsRef$current2, event);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
var resetHandler = event => {
|
|
633
|
+
var input = getInputElement();
|
|
634
|
+
if (input !== null && input !== void 0 && input.form && event.target === input.form) {
|
|
635
|
+
var _optionsRef$current3, _optionsRef$current3$;
|
|
636
|
+
(_optionsRef$current3 = optionsRef.current) === null || _optionsRef$current3 === void 0 ? void 0 : (_optionsRef$current3$ = _optionsRef$current3.onReset) === null || _optionsRef$current3$ === void 0 ? void 0 : _optionsRef$current3$.call(_optionsRef$current3, event);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
document.addEventListener('input', inputHandler, true);
|
|
640
|
+
document.addEventListener('focus', focusHandler, true);
|
|
641
|
+
document.addEventListener('blur', blurHandler, true);
|
|
642
|
+
document.addEventListener('submit', submitHandler);
|
|
643
|
+
document.addEventListener('reset', resetHandler);
|
|
644
|
+
return () => {
|
|
645
|
+
document.removeEventListener('input', inputHandler, true);
|
|
646
|
+
document.removeEventListener('focus', focusHandler, true);
|
|
647
|
+
document.removeEventListener('blur', blurHandler, true);
|
|
648
|
+
document.removeEventListener('submit', submitHandler);
|
|
649
|
+
document.removeEventListener('reset', resetHandler);
|
|
650
|
+
};
|
|
651
|
+
}, []);
|
|
652
|
+
var control = useMemo(() => {
|
|
653
|
+
var getInputElement = () => {
|
|
654
|
+
var _optionsRef$current$g3, _optionsRef$current4, _optionsRef$current4$;
|
|
655
|
+
return (_optionsRef$current$g3 = (_optionsRef$current4 = optionsRef.current) === null || _optionsRef$current4 === void 0 ? void 0 : (_optionsRef$current4$ = _optionsRef$current4.getElement) === null || _optionsRef$current4$ === void 0 ? void 0 : _optionsRef$current4$.call(_optionsRef$current4, ref.current)) !== null && _optionsRef$current$g3 !== void 0 ? _optionsRef$current$g3 : ref.current;
|
|
656
|
+
};
|
|
657
|
+
return {
|
|
658
|
+
change(eventOrValue) {
|
|
659
|
+
var input = getInputElement();
|
|
660
|
+
if (!input) {
|
|
661
|
+
console.warn('Missing input ref; No change-related events will be dispatched');
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (changeDispatched.current) {
|
|
665
|
+
changeDispatched.current = false;
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
var previousValue = input.value;
|
|
669
|
+
var nextValue = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value;
|
|
670
|
+
|
|
671
|
+
// This make sure no event is dispatched on the first effect run
|
|
672
|
+
if (nextValue === previousValue) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Dispatch beforeinput event before updating the input value
|
|
677
|
+
input.dispatchEvent(new Event('beforeinput', {
|
|
678
|
+
bubbles: true
|
|
679
|
+
}));
|
|
680
|
+
// Update the input value to trigger a change event
|
|
681
|
+
setNativeValue(input, nextValue);
|
|
682
|
+
// Dispatch input event with the updated input value
|
|
683
|
+
input.dispatchEvent(new InputEvent('input', {
|
|
684
|
+
bubbles: true
|
|
685
|
+
}));
|
|
686
|
+
// Reset the dispatched flag
|
|
687
|
+
changeDispatched.current = false;
|
|
688
|
+
},
|
|
689
|
+
focus() {
|
|
690
|
+
var input = getInputElement();
|
|
691
|
+
if (!input) {
|
|
692
|
+
console.warn('Missing input ref; No focus-related events will be dispatched');
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (focusDispatched.current) {
|
|
696
|
+
focusDispatched.current = false;
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
var focusinEvent = new FocusEvent('focusin', {
|
|
700
|
+
bubbles: true
|
|
701
|
+
});
|
|
702
|
+
var focusEvent = new FocusEvent('focus');
|
|
703
|
+
input.dispatchEvent(focusinEvent);
|
|
704
|
+
input.dispatchEvent(focusEvent);
|
|
705
|
+
|
|
706
|
+
// Reset the dispatched flag
|
|
707
|
+
focusDispatched.current = false;
|
|
708
|
+
},
|
|
709
|
+
blur() {
|
|
710
|
+
var input = getInputElement();
|
|
711
|
+
if (!input) {
|
|
712
|
+
console.warn('Missing input ref; No blur-related events will be dispatched');
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (blurDispatched.current) {
|
|
716
|
+
blurDispatched.current = false;
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
var focusoutEvent = new FocusEvent('focusout', {
|
|
720
|
+
bubbles: true
|
|
721
|
+
});
|
|
722
|
+
var blurEvent = new FocusEvent('blur');
|
|
723
|
+
input.dispatchEvent(focusoutEvent);
|
|
724
|
+
input.dispatchEvent(blurEvent);
|
|
725
|
+
|
|
726
|
+
// Reset the dispatched flag
|
|
727
|
+
blurDispatched.current = false;
|
|
728
|
+
}
|
|
729
|
+
};
|
|
730
|
+
}, []);
|
|
731
|
+
return [ref, control];
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export { useControlledInput, useFieldList, useFieldset, useForm, useInputEvent };
|
package/module/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { getFormElements, hasError, list, parse, requestCommand, requestSubmit, shouldValidate, validate } from '@conform-to/dom';
|
|
2
|
-
export { useControlledInput, useFieldList, useFieldset, useForm } from './hooks.js';
|
|
2
|
+
export { useControlledInput, useFieldList, useFieldset, useForm, useInputEvent } from './hooks.js';
|
|
3
3
|
import * as helpers from './helpers.js';
|
|
4
4
|
export { helpers as conform };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@conform-to/react",
|
|
3
3
|
"description": "Conform view adapter for react",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.1",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"module": "module/index.js",
|
|
8
8
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"url": "https://github.com/edmundhung/conform/issues"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@conform-to/dom": "0.5.
|
|
22
|
+
"@conform-to/dom": "0.5.1"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"react": ">=16.8"
|