@conform-to/react
Advanced tools
Comparing version 0.5.0 to 0.5.1
import type { FieldConfig } from '@conform-to/dom'; | ||
import type { HTMLInputTypeAttribute } from 'react'; | ||
import type { CSSProperties, HTMLInputTypeAttribute } from 'react'; | ||
interface FieldProps { | ||
@@ -9,4 +9,7 @@ id?: string; | ||
autoFocus?: boolean; | ||
tabIndex?: number; | ||
style?: CSSProperties; | ||
'aria-invalid': boolean; | ||
'aria-describedby'?: string; | ||
'aria-hidden'?: boolean; | ||
} | ||
@@ -37,9 +40,8 @@ interface InputProps<Schema> extends FieldProps { | ||
type: 'checkbox' | 'radio'; | ||
hidden?: boolean; | ||
value?: string; | ||
} | { | ||
type: 'file'; | ||
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden'>; | ||
hidden?: boolean; | ||
value?: never; | ||
} | { | ||
type?: Exclude<HTMLInputTypeAttribute, 'button' | 'submit' | 'hidden' | 'file'>; | ||
value?: never; | ||
}; | ||
@@ -50,4 +52,8 @@ export declare function input<Schema extends File | File[]>(config: FieldConfig<Schema>, options: { | ||
export declare function input<Schema extends any>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>; | ||
export declare function select<Schema>(config: FieldConfig<Schema>): SelectProps; | ||
export declare function textarea<Schema>(config: FieldConfig<Schema>): TextareaProps; | ||
export declare function select<Schema>(config: FieldConfig<Schema>, options?: { | ||
hidden?: boolean; | ||
}): SelectProps; | ||
export declare function textarea<Schema>(config: FieldConfig<Schema>, options?: { | ||
hidden?: boolean; | ||
}): TextareaProps; | ||
export {}; |
@@ -5,2 +5,17 @@ 'use strict'; | ||
/** | ||
* Style to make the input element visually hidden | ||
* Based on the `sr-only` class from tailwindcss | ||
*/ | ||
var hiddenStyle = { | ||
position: 'absolute', | ||
width: '1px', | ||
height: '1px', | ||
padding: 0, | ||
margin: '-1px', | ||
overflow: 'hidden', | ||
clip: 'rect(0,0,0,0)', | ||
whiteSpace: 'nowrap', | ||
border: 0 | ||
}; | ||
function input(config) { | ||
@@ -25,2 +40,7 @@ var _config$initialError; | ||
}; | ||
if (options !== null && options !== void 0 && options.hidden) { | ||
attributes.style = hiddenStyle; | ||
attributes.tabIndex = -1; | ||
attributes['aria-hidden'] = true; | ||
} | ||
if (config.initialError && config.initialError.length > 0) { | ||
@@ -38,3 +58,3 @@ attributes.autoFocus = true; | ||
} | ||
function select(config) { | ||
function select(config, options) { | ||
var _config$defaultValue, _config$initialError2; | ||
@@ -51,2 +71,7 @@ var attributes = { | ||
}; | ||
if (options !== null && options !== void 0 && options.hidden) { | ||
attributes.style = hiddenStyle; | ||
attributes.tabIndex = -1; | ||
attributes['aria-hidden'] = true; | ||
} | ||
if (config.initialError && config.initialError.length > 0) { | ||
@@ -57,3 +82,3 @@ attributes.autoFocus = true; | ||
} | ||
function textarea(config) { | ||
function textarea(config, options) { | ||
var _config$defaultValue2, _config$initialError3; | ||
@@ -72,2 +97,7 @@ var attributes = { | ||
}; | ||
if (options !== null && options !== void 0 && options.hidden) { | ||
attributes.style = hiddenStyle; | ||
attributes.tabIndex = -1; | ||
attributes['aria-hidden'] = true; | ||
} | ||
if (config.initialError && config.initialError.length > 0) { | ||
@@ -74,0 +104,0 @@ attributes.autoFocus = true; |
@@ -139,3 +139,3 @@ import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type Primitive, type Submission } from '@conform-to/dom'; | ||
} | ||
interface InputControl<Element extends { | ||
interface LegacyInputControl<Element extends { | ||
focus: () => void; | ||
@@ -158,2 +158,3 @@ }> { | ||
* | ||
* @deprecated Please use the `useInputEvent` hook instead | ||
* @see https://conform.guide/api/react#usecontrolledinput | ||
@@ -163,3 +164,26 @@ */ | ||
focus: () => void; | ||
} = HTMLInputElement, Schema extends Primitive = Primitive>(config: FieldConfig<Schema>): [ShadowInputProps, InputControl<Element>]; | ||
} = HTMLInputElement, Schema extends Primitive = Primitive>(config: FieldConfig<Schema>): [ShadowInputProps, LegacyInputControl<Element>]; | ||
interface InputControl { | ||
change: (eventOrValue: { | ||
target: { | ||
value: string; | ||
}; | ||
} | string) => void; | ||
focus: () => void; | ||
blur: () => void; | ||
} | ||
/** | ||
* Returns a ref object and a set of helpers that dispatch corresponding dom event. | ||
* | ||
* @see https://conform.guide/api/react#useinputevent | ||
*/ | ||
export declare function useInputEvent<RefShape extends FieldElement = HTMLInputElement>(options?: { | ||
onSubmit?: (event: SubmitEvent) => void; | ||
onReset?: (event: Event) => void; | ||
}): [RefObject<RefShape>, InputControl]; | ||
export declare function useInputEvent<RefShape extends Exclude<any, FieldElement>>(options: { | ||
getElement: (ref: RefShape | null) => FieldElement | null | undefined; | ||
onSubmit?: (event: SubmitEvent) => void; | ||
onReset?: (event: Event) => void; | ||
}): [RefObject<RefShape>, InputControl]; | ||
export {}; |
194
hooks.js
@@ -492,2 +492,3 @@ 'use strict'; | ||
* | ||
* @deprecated Please use the `useInputEvent` hook instead | ||
* @see https://conform.guide/api/react#usecontrolledinput | ||
@@ -548,15 +549,2 @@ */ | ||
ref, | ||
style: { | ||
position: 'absolute', | ||
width: '1px', | ||
height: '1px', | ||
padding: 0, | ||
margin: '-1px', | ||
overflow: 'hidden', | ||
clip: 'rect(0,0,0,0)', | ||
whiteSpace: 'nowrap', | ||
borderWidth: 0 | ||
}, | ||
tabIndex: -1, | ||
'aria-hidden': true, | ||
onFocus() { | ||
@@ -566,3 +554,5 @@ var _inputRef$current; | ||
} | ||
}, helpers.input(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), uncontrolledState))), { | ||
}, helpers.input(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), uncontrolledState), { | ||
hidden: true | ||
})), { | ||
ref: inputRef, | ||
@@ -576,2 +566,177 @@ value, | ||
/** | ||
* Triggering react custom change event | ||
* Solution based on dom-testing-library | ||
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776 | ||
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123 | ||
*/ | ||
function setNativeValue(element, value) { | ||
if (element.value === value) { | ||
// It will not trigger a change event if `element.value` is the same as the set value | ||
return; | ||
} | ||
var { | ||
set: valueSetter | ||
} = Object.getOwnPropertyDescriptor(element, 'value') || {}; | ||
var prototype = Object.getPrototypeOf(element); | ||
var { | ||
set: prototypeValueSetter | ||
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {}; | ||
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) { | ||
prototypeValueSetter.call(element, value); | ||
} else { | ||
if (valueSetter) { | ||
valueSetter.call(element, value); | ||
} else { | ||
throw new Error('The given element does not have a value setter'); | ||
} | ||
} | ||
} | ||
/** | ||
* useLayoutEffect is client-only. | ||
* This basically makes it a no-op on server | ||
*/ | ||
var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect; | ||
function useInputEvent(options) { | ||
var ref = react.useRef(null); | ||
var optionsRef = react.useRef(options); | ||
var changeDispatched = react.useRef(false); | ||
var focusDispatched = react.useRef(false); | ||
var blurDispatched = react.useRef(false); | ||
useSafeLayoutEffect(() => { | ||
optionsRef.current = options; | ||
}); | ||
useSafeLayoutEffect(() => { | ||
var getInputElement = () => { | ||
var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2; | ||
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; | ||
}; | ||
var inputHandler = event => { | ||
var input = getInputElement(); | ||
if (input && event.target === input) { | ||
changeDispatched.current = true; | ||
} | ||
}; | ||
var focusHandler = event => { | ||
var input = getInputElement(); | ||
if (input && event.target === input) { | ||
focusDispatched.current = true; | ||
} | ||
}; | ||
var blurHandler = event => { | ||
var input = getInputElement(); | ||
if (input && event.target === input) { | ||
blurDispatched.current = true; | ||
} | ||
}; | ||
var submitHandler = event => { | ||
var input = getInputElement(); | ||
if (input !== null && input !== void 0 && input.form && event.target === input.form) { | ||
var _optionsRef$current2, _optionsRef$current2$; | ||
(_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); | ||
} | ||
}; | ||
var resetHandler = event => { | ||
var input = getInputElement(); | ||
if (input !== null && input !== void 0 && input.form && event.target === input.form) { | ||
var _optionsRef$current3, _optionsRef$current3$; | ||
(_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); | ||
} | ||
}; | ||
document.addEventListener('input', inputHandler, true); | ||
document.addEventListener('focus', focusHandler, true); | ||
document.addEventListener('blur', blurHandler, true); | ||
document.addEventListener('submit', submitHandler); | ||
document.addEventListener('reset', resetHandler); | ||
return () => { | ||
document.removeEventListener('input', inputHandler, true); | ||
document.removeEventListener('focus', focusHandler, true); | ||
document.removeEventListener('blur', blurHandler, true); | ||
document.removeEventListener('submit', submitHandler); | ||
document.removeEventListener('reset', resetHandler); | ||
}; | ||
}, []); | ||
var control = react.useMemo(() => { | ||
var getInputElement = () => { | ||
var _optionsRef$current$g3, _optionsRef$current4, _optionsRef$current4$; | ||
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; | ||
}; | ||
return { | ||
change(eventOrValue) { | ||
var input = getInputElement(); | ||
if (!input) { | ||
console.warn('Missing input ref; No change-related events will be dispatched'); | ||
return; | ||
} | ||
if (changeDispatched.current) { | ||
changeDispatched.current = false; | ||
return; | ||
} | ||
var previousValue = input.value; | ||
var nextValue = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value; | ||
// This make sure no event is dispatched on the first effect run | ||
if (nextValue === previousValue) { | ||
return; | ||
} | ||
// Dispatch beforeinput event before updating the input value | ||
input.dispatchEvent(new Event('beforeinput', { | ||
bubbles: true | ||
})); | ||
// Update the input value to trigger a change event | ||
setNativeValue(input, nextValue); | ||
// Dispatch input event with the updated input value | ||
input.dispatchEvent(new InputEvent('input', { | ||
bubbles: true | ||
})); | ||
// Reset the dispatched flag | ||
changeDispatched.current = false; | ||
}, | ||
focus() { | ||
var input = getInputElement(); | ||
if (!input) { | ||
console.warn('Missing input ref; No focus-related events will be dispatched'); | ||
return; | ||
} | ||
if (focusDispatched.current) { | ||
focusDispatched.current = false; | ||
return; | ||
} | ||
var focusinEvent = new FocusEvent('focusin', { | ||
bubbles: true | ||
}); | ||
var focusEvent = new FocusEvent('focus'); | ||
input.dispatchEvent(focusinEvent); | ||
input.dispatchEvent(focusEvent); | ||
// Reset the dispatched flag | ||
focusDispatched.current = false; | ||
}, | ||
blur() { | ||
var input = getInputElement(); | ||
if (!input) { | ||
console.warn('Missing input ref; No blur-related events will be dispatched'); | ||
return; | ||
} | ||
if (blurDispatched.current) { | ||
blurDispatched.current = false; | ||
return; | ||
} | ||
var focusoutEvent = new FocusEvent('focusout', { | ||
bubbles: true | ||
}); | ||
var blurEvent = new FocusEvent('blur'); | ||
input.dispatchEvent(focusoutEvent); | ||
input.dispatchEvent(blurEvent); | ||
// Reset the dispatched flag | ||
blurDispatched.current = false; | ||
} | ||
}; | ||
}, []); | ||
return [ref, control]; | ||
} | ||
exports.useControlledInput = useControlledInput; | ||
@@ -581,1 +746,2 @@ exports.useFieldList = useFieldList; | ||
exports.useForm = useForm; | ||
exports.useInputEvent = useInputEvent; |
@@ -47,2 +47,3 @@ 'use strict'; | ||
exports.useForm = hooks.useForm; | ||
exports.useInputEvent = hooks.useInputEvent; | ||
exports.conform = helpers; |
@@ -0,1 +1,16 @@ | ||
/** | ||
* Style to make the input element visually hidden | ||
* Based on the `sr-only` class from tailwindcss | ||
*/ | ||
var hiddenStyle = { | ||
position: 'absolute', | ||
width: '1px', | ||
height: '1px', | ||
padding: 0, | ||
margin: '-1px', | ||
overflow: 'hidden', | ||
clip: 'rect(0,0,0,0)', | ||
whiteSpace: 'nowrap', | ||
border: 0 | ||
}; | ||
function input(config) { | ||
@@ -20,2 +35,7 @@ var _config$initialError; | ||
}; | ||
if (options !== null && options !== void 0 && options.hidden) { | ||
attributes.style = hiddenStyle; | ||
attributes.tabIndex = -1; | ||
attributes['aria-hidden'] = true; | ||
} | ||
if (config.initialError && config.initialError.length > 0) { | ||
@@ -33,3 +53,3 @@ attributes.autoFocus = true; | ||
} | ||
function select(config) { | ||
function select(config, options) { | ||
var _config$defaultValue, _config$initialError2; | ||
@@ -46,2 +66,7 @@ var attributes = { | ||
}; | ||
if (options !== null && options !== void 0 && options.hidden) { | ||
attributes.style = hiddenStyle; | ||
attributes.tabIndex = -1; | ||
attributes['aria-hidden'] = true; | ||
} | ||
if (config.initialError && config.initialError.length > 0) { | ||
@@ -52,3 +77,3 @@ attributes.autoFocus = true; | ||
} | ||
function textarea(config) { | ||
function textarea(config, options) { | ||
var _config$defaultValue2, _config$initialError3; | ||
@@ -67,2 +92,7 @@ var attributes = { | ||
}; | ||
if (options !== null && options !== void 0 && options.hidden) { | ||
attributes.style = hiddenStyle; | ||
attributes.tabIndex = -1; | ||
attributes['aria-hidden'] = true; | ||
} | ||
if (config.initialError && config.initialError.length > 0) { | ||
@@ -69,0 +99,0 @@ attributes.autoFocus = true; |
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js'; | ||
import { shouldValidate, reportSubmission, getFormData, parse, isFieldElement, hasError, getPaths, getName, requestCommand, validate, getFormElement, parseListCommand, updateList } from '@conform-to/dom'; | ||
import { useRef, useState, useEffect } from 'react'; | ||
import { useRef, useState, useEffect, useMemo, useLayoutEffect } from 'react'; | ||
import { input } from './helpers.js'; | ||
@@ -488,2 +488,3 @@ | ||
* | ||
* @deprecated Please use the `useInputEvent` hook instead | ||
* @see https://conform.guide/api/react#usecontrolledinput | ||
@@ -544,15 +545,2 @@ */ | ||
ref, | ||
style: { | ||
position: 'absolute', | ||
width: '1px', | ||
height: '1px', | ||
padding: 0, | ||
margin: '-1px', | ||
overflow: 'hidden', | ||
clip: 'rect(0,0,0,0)', | ||
whiteSpace: 'nowrap', | ||
borderWidth: 0 | ||
}, | ||
tabIndex: -1, | ||
'aria-hidden': true, | ||
onFocus() { | ||
@@ -562,3 +550,5 @@ var _inputRef$current; | ||
} | ||
}, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState))), { | ||
}, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState), { | ||
hidden: true | ||
})), { | ||
ref: inputRef, | ||
@@ -572,2 +562,177 @@ value, | ||
export { useControlledInput, useFieldList, useFieldset, useForm }; | ||
/** | ||
* Triggering react custom change event | ||
* Solution based on dom-testing-library | ||
* @see https://github.com/facebook/react/issues/10135#issuecomment-401496776 | ||
* @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123 | ||
*/ | ||
function setNativeValue(element, value) { | ||
if (element.value === value) { | ||
// It will not trigger a change event if `element.value` is the same as the set value | ||
return; | ||
} | ||
var { | ||
set: valueSetter | ||
} = Object.getOwnPropertyDescriptor(element, 'value') || {}; | ||
var prototype = Object.getPrototypeOf(element); | ||
var { | ||
set: prototypeValueSetter | ||
} = Object.getOwnPropertyDescriptor(prototype, 'value') || {}; | ||
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) { | ||
prototypeValueSetter.call(element, value); | ||
} else { | ||
if (valueSetter) { | ||
valueSetter.call(element, value); | ||
} else { | ||
throw new Error('The given element does not have a value setter'); | ||
} | ||
} | ||
} | ||
/** | ||
* useLayoutEffect is client-only. | ||
* This basically makes it a no-op on server | ||
*/ | ||
var useSafeLayoutEffect = typeof document === 'undefined' ? useEffect : useLayoutEffect; | ||
function useInputEvent(options) { | ||
var ref = useRef(null); | ||
var optionsRef = useRef(options); | ||
var changeDispatched = useRef(false); | ||
var focusDispatched = useRef(false); | ||
var blurDispatched = useRef(false); | ||
useSafeLayoutEffect(() => { | ||
optionsRef.current = options; | ||
}); | ||
useSafeLayoutEffect(() => { | ||
var getInputElement = () => { | ||
var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2; | ||
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; | ||
}; | ||
var inputHandler = event => { | ||
var input = getInputElement(); | ||
if (input && event.target === input) { | ||
changeDispatched.current = true; | ||
} | ||
}; | ||
var focusHandler = event => { | ||
var input = getInputElement(); | ||
if (input && event.target === input) { | ||
focusDispatched.current = true; | ||
} | ||
}; | ||
var blurHandler = event => { | ||
var input = getInputElement(); | ||
if (input && event.target === input) { | ||
blurDispatched.current = true; | ||
} | ||
}; | ||
var submitHandler = event => { | ||
var input = getInputElement(); | ||
if (input !== null && input !== void 0 && input.form && event.target === input.form) { | ||
var _optionsRef$current2, _optionsRef$current2$; | ||
(_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); | ||
} | ||
}; | ||
var resetHandler = event => { | ||
var input = getInputElement(); | ||
if (input !== null && input !== void 0 && input.form && event.target === input.form) { | ||
var _optionsRef$current3, _optionsRef$current3$; | ||
(_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); | ||
} | ||
}; | ||
document.addEventListener('input', inputHandler, true); | ||
document.addEventListener('focus', focusHandler, true); | ||
document.addEventListener('blur', blurHandler, true); | ||
document.addEventListener('submit', submitHandler); | ||
document.addEventListener('reset', resetHandler); | ||
return () => { | ||
document.removeEventListener('input', inputHandler, true); | ||
document.removeEventListener('focus', focusHandler, true); | ||
document.removeEventListener('blur', blurHandler, true); | ||
document.removeEventListener('submit', submitHandler); | ||
document.removeEventListener('reset', resetHandler); | ||
}; | ||
}, []); | ||
var control = useMemo(() => { | ||
var getInputElement = () => { | ||
var _optionsRef$current$g3, _optionsRef$current4, _optionsRef$current4$; | ||
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; | ||
}; | ||
return { | ||
change(eventOrValue) { | ||
var input = getInputElement(); | ||
if (!input) { | ||
console.warn('Missing input ref; No change-related events will be dispatched'); | ||
return; | ||
} | ||
if (changeDispatched.current) { | ||
changeDispatched.current = false; | ||
return; | ||
} | ||
var previousValue = input.value; | ||
var nextValue = typeof eventOrValue === 'string' ? eventOrValue : eventOrValue.target.value; | ||
// This make sure no event is dispatched on the first effect run | ||
if (nextValue === previousValue) { | ||
return; | ||
} | ||
// Dispatch beforeinput event before updating the input value | ||
input.dispatchEvent(new Event('beforeinput', { | ||
bubbles: true | ||
})); | ||
// Update the input value to trigger a change event | ||
setNativeValue(input, nextValue); | ||
// Dispatch input event with the updated input value | ||
input.dispatchEvent(new InputEvent('input', { | ||
bubbles: true | ||
})); | ||
// Reset the dispatched flag | ||
changeDispatched.current = false; | ||
}, | ||
focus() { | ||
var input = getInputElement(); | ||
if (!input) { | ||
console.warn('Missing input ref; No focus-related events will be dispatched'); | ||
return; | ||
} | ||
if (focusDispatched.current) { | ||
focusDispatched.current = false; | ||
return; | ||
} | ||
var focusinEvent = new FocusEvent('focusin', { | ||
bubbles: true | ||
}); | ||
var focusEvent = new FocusEvent('focus'); | ||
input.dispatchEvent(focusinEvent); | ||
input.dispatchEvent(focusEvent); | ||
// Reset the dispatched flag | ||
focusDispatched.current = false; | ||
}, | ||
blur() { | ||
var input = getInputElement(); | ||
if (!input) { | ||
console.warn('Missing input ref; No blur-related events will be dispatched'); | ||
return; | ||
} | ||
if (blurDispatched.current) { | ||
blurDispatched.current = false; | ||
return; | ||
} | ||
var focusoutEvent = new FocusEvent('focusout', { | ||
bubbles: true | ||
}); | ||
var blurEvent = new FocusEvent('blur'); | ||
input.dispatchEvent(focusoutEvent); | ||
input.dispatchEvent(blurEvent); | ||
// Reset the dispatched flag | ||
blurDispatched.current = false; | ||
} | ||
}; | ||
}, []); | ||
return [ref, control]; | ||
} | ||
export { useControlledInput, useFieldList, useFieldset, useForm, useInputEvent }; |
export { getFormElements, hasError, list, parse, requestCommand, requestSubmit, shouldValidate, validate } from '@conform-to/dom'; | ||
export { useControlledInput, useFieldList, useFieldset, useForm } from './hooks.js'; | ||
export { useControlledInput, useFieldList, useFieldset, useForm, useInputEvent } from './hooks.js'; | ||
import * as helpers from './helpers.js'; | ||
export { helpers as conform }; |
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "0.5.0", | ||
"version": "0.5.1", | ||
"main": "index.js", | ||
@@ -23,3 +23,3 @@ "module": "module/index.js", | ||
"dependencies": { | ||
"@conform-to/dom": "0.5.0" | ||
"@conform-to/dom": "0.5.1" | ||
}, | ||
@@ -26,0 +26,0 @@ "peerDependencies": { |
@@ -12,4 +12,8 @@ # @conform-to/react | ||
- [useFieldList](#usefieldlist) | ||
- [useInputEvent](#useinputevent) | ||
- [useControlledInput](#usecontrolledinput) | ||
- [conform](#conform) | ||
- [list](#list) | ||
- [validate](#validate) | ||
- [requestCommand](#requestcommand) | ||
- [getFormElements](#getformelements) | ||
@@ -282,4 +286,54 @@ - [hasError](#haserror) | ||
### useInputEvent | ||
It returns a ref object and a set of helpers that dispatch corresponding dom event. | ||
```tsx | ||
import { useForm, useInputEvent } from '@conform-to/react'; | ||
import { Select, MenuItem } from '@mui/material'; | ||
import { useState, useRef } from 'react'; | ||
function MuiForm() { | ||
const [form, { category }] = useForm(); | ||
const [value, setValue] = useState(category.config.defaultValue ?? ''); | ||
const [ref, control] = useInputEvent({ | ||
onReset: () => setValue(category.config.defaultValue ?? ''), | ||
}); | ||
const inputRef = useRef<HTMLInputElement>(null); | ||
return ( | ||
<form {...form.props}> | ||
{/* Render a shadow input somewhere */} | ||
<input | ||
ref={ref} | ||
{...conform.input(category.config, { hidden: true })} | ||
onChange={(e) => setValue(e.target.value)} | ||
onFocus={() => inputRef.current?.focus()} | ||
/> | ||
{/* MUI Select is a controlled component */} | ||
<TextField | ||
label="Category" | ||
inputRef={inputRef} | ||
value={value} | ||
onChange={control.change} | ||
onBlur={control.blur} | ||
select | ||
> | ||
<MenuItem value="">Please select</MenuItem> | ||
<MenuItem value="a">Category A</MenuItem> | ||
<MenuItem value="b">Category B</MenuItem> | ||
<MenuItem value="c">Category C</MenuItem> | ||
</TextField> | ||
</form> | ||
); | ||
} | ||
``` | ||
--- | ||
### useControlledInput | ||
> This API is deprecated and replaced with the [useInputEvent](#useinputevent) hook. | ||
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. | ||
@@ -290,3 +344,2 @@ | ||
import { Select, MenuItem } from '@mui/material'; | ||
import { useRef } from 'react'; | ||
@@ -293,0 +346,0 @@ function MuiForm() { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
104052
2237
644
78903
1
27
+ Added@conform-to/dom@0.5.1(transitive)
- Removed@conform-to/dom@0.5.0(transitive)
Updated@conform-to/dom@0.5.1