@conform-to/react
Advanced tools
Comparing version 0.3.1 to 0.4.0-pre.0
@@ -25,3 +25,3 @@ 'use strict'; | ||
if (typeof config.initialError !== 'undefined') { | ||
if (config.initialError && config.initialError.length > 0) { | ||
attributes.autoFocus = true; | ||
@@ -47,7 +47,6 @@ } | ||
required: config.required, | ||
multiple: config.multiple, | ||
autoFocus: typeof config.initialError !== 'undefined' ? Boolean(config.initialError) : undefined | ||
multiple: config.multiple | ||
}; | ||
if (typeof config.initialError !== 'undefined') { | ||
if (config.initialError && config.initialError.length > 0) { | ||
attributes.autoFocus = true; | ||
@@ -71,3 +70,3 @@ } | ||
if (typeof config.initialError !== 'undefined') { | ||
if (config.initialError && config.initialError.length > 0) { | ||
attributes.autoFocus = true; | ||
@@ -74,0 +73,0 @@ } |
@@ -1,4 +0,9 @@ | ||
import { type FieldConfig, type FieldError, type FieldValue, type FieldElement, type FieldsetConstraint, type ListCommand, type Primitive } from '@conform-to/dom'; | ||
import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type FormState, type ListCommand, type Primitive, type Submission } from '@conform-to/dom'; | ||
import { type InputHTMLAttributes, type FormEvent, type RefObject } from 'react'; | ||
export interface FormConfig { | ||
interface FormContext<Schema extends Record<string, any>> { | ||
form: HTMLFormElement; | ||
formData: FormData; | ||
submission: Submission<Schema>; | ||
} | ||
export interface FormConfig<Schema extends Record<string, any>> { | ||
/** | ||
@@ -12,2 +17,10 @@ * Define when the error should be reported initially. | ||
/** | ||
* An object representing the initial value of the form. | ||
*/ | ||
defaultValue?: FieldValue<Schema>; | ||
/** | ||
* An object describing the state from the last submission | ||
*/ | ||
state?: FormState<Schema>; | ||
/** | ||
* Enable native validation before hydation. | ||
@@ -27,3 +40,3 @@ * | ||
*/ | ||
validate?: (form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null) => void; | ||
onValidate?: (context: FormContext<Schema>) => boolean; | ||
/** | ||
@@ -33,3 +46,3 @@ * The submit event handler of the form. It will be called | ||
*/ | ||
onSubmit?: (event: FormEvent<HTMLFormElement>) => void; | ||
onSubmit?: (event: FormEvent<HTMLFormElement>, context: FormContext<Schema>) => void; | ||
} | ||
@@ -44,2 +57,8 @@ /** | ||
} | ||
interface Form<Schema extends Record<string, any>> { | ||
ref: RefObject<HTMLFormElement>; | ||
error: string; | ||
props: FormProps; | ||
config: FieldsetConfig<Schema>; | ||
} | ||
/** | ||
@@ -49,5 +68,5 @@ * Returns properties required to hook into form events. | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#useform | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#useform | ||
*/ | ||
export declare function useForm(config?: FormConfig): FormProps; | ||
export declare function useForm<Schema extends Record<string, any>>(config?: FormConfig<Schema>): Form<Schema>; | ||
/** | ||
@@ -78,3 +97,3 @@ * All the information of the field, including state and config. | ||
*/ | ||
initialError?: FieldError<Schema>['details']; | ||
initialError?: Array<[string, string]>; | ||
/** | ||
@@ -92,3 +111,3 @@ * An object describing the constraint of each field | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldset | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldset | ||
*/ | ||
@@ -120,3 +139,3 @@ export declare function useFieldset<Schema extends Record<string, any>>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config?: FieldsetConfig<Schema>): Fieldset<Schema>; | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldlist | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldlist | ||
*/ | ||
@@ -151,7 +170,7 @@ export declare function useFieldList<Payload = any>(ref: RefObject<HTMLFormElement | HTMLFieldSetElement>, config: FieldConfig<Array<Payload>>): [ | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usecontrolledinput | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usecontrolledinput | ||
*/ | ||
export declare function useControlledInput<Element extends { | ||
focus: () => void; | ||
} = HTMLInputElement, Schema extends Primitive = Primitive>(field: FieldConfig<Schema>): [ShadowInputProps, InputControl<Element>]; | ||
} = HTMLInputElement, Schema extends Primitive = Primitive>(config: FieldConfig<Schema>): [ShadowInputProps, InputControl<Element>]; | ||
export {}; |
478
hooks.js
@@ -14,24 +14,56 @@ 'use strict'; | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#useform | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#useform | ||
*/ | ||
function useForm() { | ||
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var { | ||
validate | ||
} = config; | ||
var configRef = react.useRef(config); | ||
var ref = react.useRef(null); | ||
var [error, setError] = react.useState(() => { | ||
var _config$state$error$f, _config$state, _config$state$error; | ||
var [, message] = (_config$state$error$f = (_config$state = config.state) === null || _config$state === void 0 ? void 0 : (_config$state$error = _config$state.error) === null || _config$state$error === void 0 ? void 0 : _config$state$error.find(_ref => { | ||
var [key] = _ref; | ||
return key === ''; | ||
})) !== null && _config$state$error$f !== void 0 ? _config$state$error$f : []; | ||
return message !== null && message !== void 0 ? message : ''; | ||
}); | ||
var [fieldsetConfig, setFieldsetConfig] = react.useState(() => { | ||
var _config$state$error2, _config$state2, _config$state3, _config$state$value, _config$state4; | ||
var error = (_config$state$error2 = (_config$state2 = config.state) === null || _config$state2 === void 0 ? void 0 : _config$state2.error) !== null && _config$state$error2 !== void 0 ? _config$state$error2 : []; | ||
var scope = (_config$state3 = config.state) === null || _config$state3 === void 0 ? void 0 : _config$state3.scope; | ||
return { | ||
defaultValue: (_config$state$value = (_config$state4 = config.state) === null || _config$state4 === void 0 ? void 0 : _config$state4.value) !== null && _config$state$value !== void 0 ? _config$state$value : config.defaultValue, | ||
initialError: error.filter(_ref2 => { | ||
var [name] = _ref2; | ||
return name !== '' && dom.getSubmissionType(name) === null && (!scope || scope.includes(name)); | ||
}) | ||
}; | ||
}); | ||
var [noValidate, setNoValidate] = react.useState(config.noValidate || !config.fallbackNative); | ||
react.useEffect(() => { | ||
configRef.current = config; | ||
}); | ||
react.useEffect(() => { | ||
setNoValidate(true); | ||
}, []); | ||
react.useEffect(() => { | ||
// Initialize form validation messages | ||
if (ref.current) { | ||
validate === null || validate === void 0 ? void 0 : validate(ref.current); | ||
} // Revalidate the form when input value is changed | ||
var form = ref.current; | ||
if (!form || !config.state) { | ||
return; | ||
} | ||
if (!dom.reportValidity(form, config.state)) { | ||
dom.focusFirstInvalidField(form, config.state.scope); | ||
} | ||
dom.requestSubmit(form); | ||
}, [config.state]); | ||
react.useEffect(() => { | ||
// Revalidate the form when input value is changed | ||
var handleInput = event => { | ||
var field = event.target; | ||
var form = ref.current; | ||
var formConfig = configRef.current; | ||
@@ -42,16 +74,8 @@ if (!form || !dom.isFieldElement(field) || field.form !== form) { | ||
validate === null || validate === void 0 ? void 0 : validate(form); | ||
if (formConfig.initialReport === 'onChange') { | ||
field.dataset.conformTouched = 'true'; | ||
} | ||
if (!config.noValidate) { | ||
if (config.initialReport === 'onChange') { | ||
field.dataset.conformTouched = 'true'; | ||
} // Field validity might be changed due to cross reference | ||
for (var _field of form.elements) { | ||
if (dom.isFieldElement(_field) && _field.dataset.conformTouched) { | ||
// Report latest error for all touched fields | ||
_field.checkValidity(); | ||
} | ||
} | ||
if (field.dataset.conformTouched) { | ||
dom.requestValidate(form, field.name); | ||
} | ||
@@ -63,13 +87,32 @@ }; | ||
var form = ref.current; | ||
var formConfig = configRef.current; | ||
if (!form || !dom.isFieldElement(field) || field.form !== form || config.noValidate || config.initialReport !== 'onBlur') { | ||
if (!form || !dom.isFieldElement(field) || field.form !== form) { | ||
return; | ||
} | ||
field.dataset.conformTouched = 'true'; | ||
field.reportValidity(); | ||
if (formConfig.initialReport === 'onBlur' && !field.dataset.conformTouched) { | ||
field.dataset.conformTouched = 'true'; | ||
dom.requestValidate(form, field.name); | ||
} | ||
}; | ||
var handleInvalid = event => { | ||
var form = dom.getFormElement(ref.current); | ||
var field = event.target; | ||
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '') { | ||
return; | ||
} | ||
event.preventDefault(); | ||
if (field.dataset.conformTouched) { | ||
setError(field.validationMessage); | ||
} | ||
}; | ||
var handleReset = event => { | ||
var form = ref.current; | ||
var formConfig = configRef.current; | ||
@@ -84,13 +127,11 @@ if (!form || event.target !== form) { | ||
delete field.dataset.conformTouched; | ||
field.setCustomValidity(''); | ||
} | ||
} | ||
/** | ||
* The reset event is triggered before form reset happens. | ||
* This make sure the form to be revalidated with initial values. | ||
*/ | ||
setTimeout(() => { | ||
validate === null || validate === void 0 ? void 0 : validate(form); | ||
}, 0); | ||
setError(''); | ||
setFieldsetConfig({ | ||
defaultValue: formConfig.defaultValue, | ||
initialError: [] | ||
}); | ||
}; | ||
@@ -107,2 +148,3 @@ /** | ||
document.addEventListener('blur', handleBlur, true); | ||
document.addEventListener('invalid', handleInvalid, true); | ||
document.addEventListener('reset', handleReset); | ||
@@ -112,50 +154,74 @@ return () => { | ||
document.removeEventListener('blur', handleBlur, true); | ||
document.removeEventListener('invalid', handleInvalid, true); | ||
document.removeEventListener('reset', handleReset); | ||
}; | ||
}, [validate, config.initialReport, config.noValidate]); | ||
}, []); | ||
return { | ||
ref, | ||
noValidate, | ||
error, | ||
props: { | ||
ref, | ||
noValidate, | ||
onSubmit(event) { | ||
var form = event.currentTarget; | ||
var nativeEvent = event.nativeEvent; | ||
var submitter = nativeEvent.submitter instanceof HTMLButtonElement || nativeEvent.submitter instanceof HTMLInputElement ? nativeEvent.submitter : null; // Validating the form with the submitter value | ||
onSubmit(event) { | ||
var form = event.currentTarget; | ||
var nativeEvent = event.nativeEvent; | ||
var submitter = nativeEvent.submitter; | ||
validate === null || validate === void 0 ? void 0 : validate(form, submitter); | ||
/** | ||
* It checks defaultPrevented to confirm if the submission is intentional | ||
* This is utilized by `useFieldList` to modify the list state when the submit | ||
* event is captured and revalidate the form with new fields without triggering | ||
* a form submission at the same time. | ||
*/ | ||
for (var element of form.elements) { | ||
if (dom.isFieldElement(element) && element.name === '') { | ||
setError(element.validationMessage); | ||
break; | ||
} | ||
} | ||
/** | ||
* It checks defaultPrevented to confirm if the submission is intentional | ||
* This is utilized by `useFieldList` to modify the list state when the submit | ||
* event is captured and revalidate the form with new fields without triggering | ||
* a form submission at the same time. | ||
*/ | ||
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && !event.defaultPrevented) { | ||
var focused = false; | ||
for (var field of form.elements) { | ||
if (dom.isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; // Focus on the first invalid field | ||
if (!submitter || event.defaultPrevented) { | ||
event.preventDefault(); | ||
return; | ||
} | ||
if (!focused && !field.validity.valid && field.tagName !== 'BUTTON') { | ||
field.focus(); | ||
focused = true; | ||
var formData = dom.getFormData(form, submitter); | ||
var submission = dom.parse(formData); | ||
var context = { | ||
form, | ||
formData, | ||
submission | ||
}; // Touch all fields only if the submitter is not a command button | ||
if (!submission.type) { | ||
for (var field of form.elements) { | ||
if (dom.isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; | ||
} | ||
} | ||
} // Check the validity of the form | ||
} | ||
if (!event.currentTarget.reportValidity()) { | ||
event.preventDefault(); | ||
if (typeof config.onValidate === 'function' && !config.noValidate && !submitter.formNoValidate) { | ||
try { | ||
if (!config.onValidate(context)) { | ||
dom.focusFirstInvalidField(form); | ||
event.preventDefault(); | ||
} | ||
} catch (e) { | ||
console.warn(e); | ||
} | ||
} | ||
} | ||
if (!event.defaultPrevented) { | ||
var _config$onSubmit; | ||
if (!event.defaultPrevented) { | ||
var _config$onSubmit; | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event); | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, context); | ||
} | ||
} | ||
} | ||
}, | ||
config: fieldsetConfig | ||
}; | ||
@@ -168,10 +234,43 @@ } | ||
function useFieldset(ref, config) { | ||
var configRef = react.useRef(config); | ||
var [uncontrolledState, setUncontrolledState] = react.useState( // @ts-expect-error | ||
() => { | ||
var _config$defaultValue; | ||
var initialError = {}; | ||
for (var [name, message] of (_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : []) { | ||
var _config$initialError; | ||
var [key, ...paths] = dom.getPaths(name); | ||
if (typeof key === 'string') { | ||
var _initialError$key; | ||
var scopedName = dom.getName(paths); | ||
var entries = (_initialError$key = initialError[key]) !== null && _initialError$key !== void 0 ? _initialError$key : []; | ||
if (scopedName === '' && entries.length > 0 && entries[0][0] !== '') { | ||
initialError[key] = [[scopedName, message], ...entries]; | ||
} else { | ||
initialError[key] = [...entries, [scopedName, message]]; | ||
} | ||
} | ||
} | ||
return { | ||
defaultValue: (_config$defaultValue = config === null || config === void 0 ? void 0 : config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : {}, | ||
initialError | ||
}; | ||
}); | ||
var [error, setError] = react.useState(() => { | ||
var result = {}; | ||
for (var [key, _error] of Object.entries((_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {})) { | ||
var _config$initialError; | ||
for (var [key, entries] of Object.entries(uncontrolledState.initialError)) { | ||
var _entries$; | ||
if (_error !== null && _error !== void 0 && _error.message) { | ||
result[key] = _error.message; | ||
var [name, message] = (_entries$ = entries === null || entries === void 0 ? void 0 : entries[0]) !== null && _entries$ !== void 0 ? _entries$ : []; | ||
if (name === '') { | ||
result[key] = message !== null && message !== void 0 ? message : ''; | ||
} | ||
@@ -183,2 +282,5 @@ } | ||
react.useEffect(() => { | ||
configRef.current = config; | ||
}); | ||
react.useEffect(() => { | ||
/** | ||
@@ -192,9 +294,12 @@ * Reset the error state of each field if its validity is changed. | ||
setError(prev => { | ||
var _configRef$current$na, _configRef$current; | ||
var next = prev; | ||
var fieldsetName = (_configRef$current$na = (_configRef$current = configRef.current) === null || _configRef$current === void 0 ? void 0 : _configRef$current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : ''; | ||
for (var field of form.elements) { | ||
if (dom.isFieldElement(field)) { | ||
var key = dom.getKey(field.name, config === null || config === void 0 ? void 0 : config.name); | ||
if (dom.isFieldElement(field) && field.name.startsWith(fieldsetName)) { | ||
var [key, ...paths] = dom.getPaths(fieldsetName.length > 0 ? field.name.slice(fieldsetName.length + 1) : field.name); | ||
if (key) { | ||
if (typeof key === 'string' && paths.length === 0) { | ||
var _next$key, _next; | ||
@@ -235,25 +340,31 @@ | ||
var invalidHandler = event => { | ||
var _configRef$current$na2, _configRef$current2; | ||
var form = dom.getFormElement(ref.current); | ||
var field = event.target; | ||
var fieldsetName = (_configRef$current$na2 = (_configRef$current2 = configRef.current) === null || _configRef$current2 === void 0 ? void 0 : _configRef$current2.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : ''; | ||
if (!form || !dom.isFieldElement(field) || field.form !== form) { | ||
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) { | ||
return; | ||
} | ||
var key = dom.getKey(field.name, config === null || config === void 0 ? void 0 : config.name); // Update the error only if the field belongs to the fieldset | ||
var [key, ...paths] = dom.getPaths(fieldsetName.length > 0 ? field.name.slice(fieldsetName.length + 1) : field.name); // Update the error only if the field belongs to the fieldset | ||
if (key) { | ||
setError(prev => { | ||
var _prev$key; | ||
if (typeof key === 'string' && paths.length === 0) { | ||
if (field.dataset.conformTouched) { | ||
setError(prev => { | ||
var _prev$key; | ||
var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : ''; | ||
var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : ''; | ||
if (prevMessage === field.validationMessage) { | ||
return prev; | ||
} | ||
if (prevMessage === field.validationMessage) { | ||
return prev; | ||
} | ||
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { | ||
[key]: field.validationMessage | ||
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { | ||
[key]: field.validationMessage | ||
}); | ||
}); | ||
}); | ||
} | ||
event.preventDefault(); | ||
@@ -275,2 +386,4 @@ } | ||
var resetHandler = event => { | ||
var _fieldsetConfig$defau; | ||
var form = dom.getFormElement(ref.current); | ||
@@ -282,2 +395,8 @@ | ||
var fieldsetConfig = configRef.current; | ||
setUncontrolledState({ | ||
// @ts-expect-error | ||
defaultValue: (_fieldsetConfig$defau = fieldsetConfig === null || fieldsetConfig === void 0 ? void 0 : fieldsetConfig.defaultValue) !== null && _fieldsetConfig$defau !== void 0 ? _fieldsetConfig$defau : {}, | ||
initialError: {} | ||
}); | ||
setError({}); | ||
@@ -297,22 +416,3 @@ }; | ||
}; | ||
}, [ref, config === null || config === void 0 ? void 0 : config.name]); | ||
react.useEffect(() => { | ||
setError(prev => { | ||
var next = prev; | ||
for (var [key, _error2] of Object.entries((_config$initialError2 = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : {})) { | ||
var _config$initialError2; | ||
if (next[key] !== (_error2 === null || _error2 === void 0 ? void 0 : _error2.message)) { | ||
var _error2$message; | ||
next = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, next), {}, { | ||
[key]: (_error2$message = _error2 === null || _error2 === void 0 ? void 0 : _error2.message) !== null && _error2$message !== void 0 ? _error2$message : '' | ||
}); | ||
} | ||
} | ||
return next; | ||
}); | ||
}, [config === null || config === void 0 ? void 0 : config.name, config === null || config === void 0 ? void 0 : config.initialError]); | ||
}, [ref]); | ||
/** | ||
@@ -326,3 +426,3 @@ * This allows us constructing the field at runtime as we have no information | ||
get(_target, key) { | ||
var _constraint, _config$defaultValue, _config$initialError$, _config$initialError3, _config$initialError4, _config$initialError5, _config$initialError6, _error$key; | ||
var _fieldsetConfig$const, _error$key; | ||
@@ -333,9 +433,10 @@ if (typeof key !== 'string') { | ||
var constraint = config === null || config === void 0 ? void 0 : (_constraint = config.constraint) === null || _constraint === void 0 ? void 0 : _constraint[key]; | ||
var fieldsetConfig = config !== null && config !== void 0 ? config : {}; | ||
var constraint = (_fieldsetConfig$const = fieldsetConfig.constraint) === null || _fieldsetConfig$const === void 0 ? void 0 : _fieldsetConfig$const[key]; | ||
var field = { | ||
config: _rollupPluginBabelHelpers.objectSpread2({ | ||
name: config !== null && config !== void 0 && config.name ? "".concat(config.name, ".").concat(key) : key, | ||
form: config === null || config === void 0 ? void 0 : config.form, | ||
defaultValue: config === null || config === void 0 ? void 0 : (_config$defaultValue = config.defaultValue) === null || _config$defaultValue === void 0 ? void 0 : _config$defaultValue[key], | ||
initialError: (_config$initialError$ = config === null || config === void 0 ? void 0 : (_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : (_config$initialError4 = _config$initialError3[key]) === null || _config$initialError4 === void 0 ? void 0 : _config$initialError4.details) !== null && _config$initialError$ !== void 0 ? _config$initialError$ : config === null || config === void 0 ? void 0 : (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[key]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.message | ||
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key, | ||
form: fieldsetConfig.form, | ||
defaultValue: uncontrolledState.defaultValue[key], | ||
initialError: uncontrolledState.initialError[key] | ||
}, constraint), | ||
@@ -354,21 +455,51 @@ error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : '' | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldlist | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldlist | ||
*/ | ||
function useFieldList(ref, config) { | ||
var [entries, setEntries] = react.useState(() => { | ||
var configRef = react.useRef(config); | ||
var [uncontrolledState, setUncontrolledState] = react.useState(() => { | ||
var _config$defaultValue2; | ||
return Object.entries((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [undefined]); | ||
var initialError = []; | ||
for (var [name, message] of (_config$initialError2 = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : []) { | ||
var _config$initialError2; | ||
var [index, ...paths] = dom.getPaths(name); | ||
if (typeof index === 'number') { | ||
var _initialError$index; | ||
var scopedName = dom.getName(paths); | ||
var _entries = (_initialError$index = initialError[index]) !== null && _initialError$index !== void 0 ? _initialError$index : []; | ||
if (scopedName === '' && _entries.length > 0 && _entries[0][0] !== '') { | ||
initialError[index] = [[scopedName, message], ..._entries]; | ||
} else { | ||
initialError[index] = [..._entries, [scopedName, message]]; | ||
} | ||
} | ||
} | ||
return { | ||
defaultValue: (_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [], | ||
initialError | ||
}; | ||
}); | ||
var list = entries.map((_ref, index) => { | ||
var _config$defaultValue3, _config$initialError7, _config$initialError8; | ||
var [entries, setEntries] = react.useState(() => { | ||
var _config$defaultValue3; | ||
var [key, defaultValue] = _ref; | ||
return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]); | ||
}); | ||
var list = entries.map((_ref3, index) => { | ||
var [key, defaultValue] = _ref3; | ||
return { | ||
key, | ||
config: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), {}, { | ||
config: { | ||
name: "".concat(config.name, "[").concat(index, "]"), | ||
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_config$defaultValue3 = config.defaultValue) === null || _config$defaultValue3 === void 0 ? void 0 : _config$defaultValue3[index], | ||
initialError: (_config$initialError7 = config.initialError) === null || _config$initialError7 === void 0 ? void 0 : (_config$initialError8 = _config$initialError7[index]) === null || _config$initialError8 === void 0 ? void 0 : _config$initialError8.details | ||
}) | ||
form: config.form, | ||
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index], | ||
initialError: uncontrolledState.initialError[index] | ||
} | ||
}; | ||
@@ -386,5 +517,6 @@ }); | ||
return { | ||
name: dom.listCommandKey, | ||
value: dom.serializeListCommand(config.name, { | ||
name: 'conform/list', | ||
value: JSON.stringify({ | ||
type, | ||
scope: config.name, | ||
payload | ||
@@ -400,34 +532,15 @@ }), | ||
react.useEffect(() => { | ||
setEntries(prevEntries => { | ||
var _config$defaultValue4; | ||
var nextEntries = Object.entries((_config$defaultValue4 = config.defaultValue) !== null && _config$defaultValue4 !== void 0 ? _config$defaultValue4 : [undefined]); | ||
if (prevEntries.length !== nextEntries.length) { | ||
return nextEntries; | ||
} | ||
for (var i = 0; i < prevEntries.length; i++) { | ||
var [prevKey, prevValue] = prevEntries[i]; | ||
var [nextKey, nextValue] = nextEntries[i]; | ||
if (prevKey !== nextKey || prevValue !== nextValue) { | ||
return nextEntries; | ||
} | ||
} // No need to rerender in this case | ||
return prevEntries; | ||
}); | ||
configRef.current = config; | ||
}); | ||
react.useEffect(() => { | ||
var submitHandler = event => { | ||
var form = dom.getFormElement(ref.current); | ||
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== dom.listCommandKey) { | ||
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') { | ||
return; | ||
} | ||
var [name, command] = dom.parseListCommand(event.submitter.value); | ||
var command = dom.parseListCommand(event.submitter.value); | ||
if (name !== config.name) { | ||
if (command.scope !== configRef.current.name) { | ||
// Ensure the scope of the listener are limited to specific field name | ||
@@ -437,11 +550,19 @@ return; | ||
switch (command.type) { | ||
case 'append': | ||
case 'prepend': | ||
case 'replace': | ||
command.payload.defaultValue = ["".concat(Date.now()), command.payload.defaultValue]; | ||
break; | ||
} | ||
setEntries(entries => { | ||
switch (command.type) { | ||
case 'append': | ||
case 'prepend': | ||
case 'replace': | ||
return dom.updateList([...(entries !== null && entries !== void 0 ? entries : [])], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, { | ||
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, { | ||
defaultValue: ["".concat(Date.now()), command.payload.defaultValue] | ||
}) | ||
})); | ||
setEntries(entries => dom.updateList([...(entries !== null && entries !== void 0 ? entries : [])], command)); | ||
default: | ||
{ | ||
return dom.updateList([...(entries !== null && entries !== void 0 ? entries : [])], command); | ||
} | ||
} | ||
}); | ||
event.preventDefault(); | ||
@@ -451,3 +572,3 @@ }; | ||
var resetHandler = event => { | ||
var _config$defaultValue5; | ||
var _fieldConfig$defaultV, _fieldConfig$defaultV2; | ||
@@ -460,3 +581,8 @@ var form = dom.getFormElement(ref.current); | ||
setEntries(Object.entries((_config$defaultValue5 = config.defaultValue) !== null && _config$defaultValue5 !== void 0 ? _config$defaultValue5 : [])); | ||
var fieldConfig = configRef.current; | ||
setUncontrolledState({ | ||
defaultValue: (_fieldConfig$defaultV = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV !== void 0 ? _fieldConfig$defaultV : [], | ||
initialError: [] | ||
}); | ||
setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined])); | ||
}; | ||
@@ -470,3 +596,3 @@ | ||
}; | ||
}, [ref, config.name, config.defaultValue]); | ||
}, [ref]); | ||
return [list, control]; | ||
@@ -480,10 +606,15 @@ } | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usecontrolledinput | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usecontrolledinput | ||
*/ | ||
function useControlledInput(field) { | ||
var _field$defaultValue; | ||
function useControlledInput(config) { | ||
var _config$defaultValue4; | ||
var ref = react.useRef(null); | ||
var inputRef = react.useRef(null); | ||
var [value, setValue] = react.useState("".concat((_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : '')); | ||
var configRef = react.useRef(config); | ||
var [uncontrolledState, setUncontrolledState] = react.useState({ | ||
defaultValue: config.defaultValue, | ||
initialError: config.initialError | ||
}); | ||
var [value, setValue] = react.useState("".concat((_config$defaultValue4 = config.defaultValue) !== null && _config$defaultValue4 !== void 0 ? _config$defaultValue4 : '')); | ||
@@ -515,2 +646,27 @@ var handleChange = eventOrValue => { | ||
react.useEffect(() => { | ||
configRef.current = config; | ||
}); | ||
react.useEffect(() => { | ||
var resetHandler = event => { | ||
var _configRef$current$de; | ||
var form = dom.getFormElement(ref.current); | ||
if (!form || event.target !== form) { | ||
return; | ||
} | ||
setUncontrolledState({ | ||
defaultValue: configRef.current.defaultValue, | ||
initialError: configRef.current.initialError | ||
}); | ||
setValue("".concat((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : '')); | ||
}; | ||
document.addEventListener('reset', resetHandler); | ||
return () => { | ||
document.removeEventListener('reset', resetHandler); | ||
}; | ||
}, []); | ||
return [_rollupPluginBabelHelpers.objectSpread2({ | ||
@@ -536,3 +692,3 @@ ref, | ||
}, helpers.input(field, { | ||
}, helpers.input(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, config), uncontrolledState), { | ||
type: 'text' | ||
@@ -539,0 +695,0 @@ })), { |
@@ -1,3 +0,3 @@ | ||
export { type FormState, type FieldsetConstraint, type Schema, type Submission, createSubmission, createValidate, } from '@conform-to/dom'; | ||
export { type FieldsetConstraint, type FormState, type Submission, hasError, isFieldElement, parse, reportValidity, } from '@conform-to/dom'; | ||
export * from './hooks'; | ||
export * as conform from './helpers'; |
16
index.js
@@ -11,10 +11,18 @@ 'use strict'; | ||
Object.defineProperty(exports, 'createSubmission', { | ||
Object.defineProperty(exports, 'hasError', { | ||
enumerable: true, | ||
get: function () { return dom.createSubmission; } | ||
get: function () { return dom.hasError; } | ||
}); | ||
Object.defineProperty(exports, 'createValidate', { | ||
Object.defineProperty(exports, 'isFieldElement', { | ||
enumerable: true, | ||
get: function () { return dom.createValidate; } | ||
get: function () { return dom.isFieldElement; } | ||
}); | ||
Object.defineProperty(exports, 'parse', { | ||
enumerable: true, | ||
get: function () { return dom.parse; } | ||
}); | ||
Object.defineProperty(exports, 'reportValidity', { | ||
enumerable: true, | ||
get: function () { return dom.reportValidity; } | ||
}); | ||
exports.useControlledInput = hooks.useControlledInput; | ||
@@ -21,0 +29,0 @@ exports.useFieldList = hooks.useFieldList; |
@@ -21,3 +21,3 @@ function input(config) { | ||
if (typeof config.initialError !== 'undefined') { | ||
if (config.initialError && config.initialError.length > 0) { | ||
attributes.autoFocus = true; | ||
@@ -43,7 +43,6 @@ } | ||
required: config.required, | ||
multiple: config.multiple, | ||
autoFocus: typeof config.initialError !== 'undefined' ? Boolean(config.initialError) : undefined | ||
multiple: config.multiple | ||
}; | ||
if (typeof config.initialError !== 'undefined') { | ||
if (config.initialError && config.initialError.length > 0) { | ||
attributes.autoFocus = true; | ||
@@ -67,3 +66,3 @@ } | ||
if (typeof config.initialError !== 'undefined') { | ||
if (config.initialError && config.initialError.length > 0) { | ||
attributes.autoFocus = true; | ||
@@ -70,0 +69,0 @@ } |
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js'; | ||
import { isFieldElement, listCommandKey, serializeListCommand, getFormElement, getKey, parseListCommand, updateList } from '@conform-to/dom'; | ||
import { getSubmissionType, reportValidity, focusFirstInvalidField, requestSubmit, isFieldElement, getFormData, parse, getPaths, getName, requestValidate, getFormElement, parseListCommand, updateList } from '@conform-to/dom'; | ||
import { useRef, useState, useEffect } from 'react'; | ||
@@ -10,24 +10,56 @@ import { input } from './helpers.js'; | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#useform | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#useform | ||
*/ | ||
function useForm() { | ||
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var { | ||
validate | ||
} = config; | ||
var configRef = useRef(config); | ||
var ref = useRef(null); | ||
var [error, setError] = useState(() => { | ||
var _config$state$error$f, _config$state, _config$state$error; | ||
var [, message] = (_config$state$error$f = (_config$state = config.state) === null || _config$state === void 0 ? void 0 : (_config$state$error = _config$state.error) === null || _config$state$error === void 0 ? void 0 : _config$state$error.find(_ref => { | ||
var [key] = _ref; | ||
return key === ''; | ||
})) !== null && _config$state$error$f !== void 0 ? _config$state$error$f : []; | ||
return message !== null && message !== void 0 ? message : ''; | ||
}); | ||
var [fieldsetConfig, setFieldsetConfig] = useState(() => { | ||
var _config$state$error2, _config$state2, _config$state3, _config$state$value, _config$state4; | ||
var error = (_config$state$error2 = (_config$state2 = config.state) === null || _config$state2 === void 0 ? void 0 : _config$state2.error) !== null && _config$state$error2 !== void 0 ? _config$state$error2 : []; | ||
var scope = (_config$state3 = config.state) === null || _config$state3 === void 0 ? void 0 : _config$state3.scope; | ||
return { | ||
defaultValue: (_config$state$value = (_config$state4 = config.state) === null || _config$state4 === void 0 ? void 0 : _config$state4.value) !== null && _config$state$value !== void 0 ? _config$state$value : config.defaultValue, | ||
initialError: error.filter(_ref2 => { | ||
var [name] = _ref2; | ||
return name !== '' && getSubmissionType(name) === null && (!scope || scope.includes(name)); | ||
}) | ||
}; | ||
}); | ||
var [noValidate, setNoValidate] = useState(config.noValidate || !config.fallbackNative); | ||
useEffect(() => { | ||
configRef.current = config; | ||
}); | ||
useEffect(() => { | ||
setNoValidate(true); | ||
}, []); | ||
useEffect(() => { | ||
// Initialize form validation messages | ||
if (ref.current) { | ||
validate === null || validate === void 0 ? void 0 : validate(ref.current); | ||
} // Revalidate the form when input value is changed | ||
var form = ref.current; | ||
if (!form || !config.state) { | ||
return; | ||
} | ||
if (!reportValidity(form, config.state)) { | ||
focusFirstInvalidField(form, config.state.scope); | ||
} | ||
requestSubmit(form); | ||
}, [config.state]); | ||
useEffect(() => { | ||
// Revalidate the form when input value is changed | ||
var handleInput = event => { | ||
var field = event.target; | ||
var form = ref.current; | ||
var formConfig = configRef.current; | ||
@@ -38,16 +70,8 @@ if (!form || !isFieldElement(field) || field.form !== form) { | ||
validate === null || validate === void 0 ? void 0 : validate(form); | ||
if (formConfig.initialReport === 'onChange') { | ||
field.dataset.conformTouched = 'true'; | ||
} | ||
if (!config.noValidate) { | ||
if (config.initialReport === 'onChange') { | ||
field.dataset.conformTouched = 'true'; | ||
} // Field validity might be changed due to cross reference | ||
for (var _field of form.elements) { | ||
if (isFieldElement(_field) && _field.dataset.conformTouched) { | ||
// Report latest error for all touched fields | ||
_field.checkValidity(); | ||
} | ||
} | ||
if (field.dataset.conformTouched) { | ||
requestValidate(form, field.name); | ||
} | ||
@@ -59,13 +83,32 @@ }; | ||
var form = ref.current; | ||
var formConfig = configRef.current; | ||
if (!form || !isFieldElement(field) || field.form !== form || config.noValidate || config.initialReport !== 'onBlur') { | ||
if (!form || !isFieldElement(field) || field.form !== form) { | ||
return; | ||
} | ||
field.dataset.conformTouched = 'true'; | ||
field.reportValidity(); | ||
if (formConfig.initialReport === 'onBlur' && !field.dataset.conformTouched) { | ||
field.dataset.conformTouched = 'true'; | ||
requestValidate(form, field.name); | ||
} | ||
}; | ||
var handleInvalid = event => { | ||
var form = getFormElement(ref.current); | ||
var field = event.target; | ||
if (!form || !isFieldElement(field) || field.form !== form || field.name !== '') { | ||
return; | ||
} | ||
event.preventDefault(); | ||
if (field.dataset.conformTouched) { | ||
setError(field.validationMessage); | ||
} | ||
}; | ||
var handleReset = event => { | ||
var form = ref.current; | ||
var formConfig = configRef.current; | ||
@@ -80,13 +123,11 @@ if (!form || event.target !== form) { | ||
delete field.dataset.conformTouched; | ||
field.setCustomValidity(''); | ||
} | ||
} | ||
/** | ||
* The reset event is triggered before form reset happens. | ||
* This make sure the form to be revalidated with initial values. | ||
*/ | ||
setTimeout(() => { | ||
validate === null || validate === void 0 ? void 0 : validate(form); | ||
}, 0); | ||
setError(''); | ||
setFieldsetConfig({ | ||
defaultValue: formConfig.defaultValue, | ||
initialError: [] | ||
}); | ||
}; | ||
@@ -103,2 +144,3 @@ /** | ||
document.addEventListener('blur', handleBlur, true); | ||
document.addEventListener('invalid', handleInvalid, true); | ||
document.addEventListener('reset', handleReset); | ||
@@ -108,50 +150,74 @@ return () => { | ||
document.removeEventListener('blur', handleBlur, true); | ||
document.removeEventListener('invalid', handleInvalid, true); | ||
document.removeEventListener('reset', handleReset); | ||
}; | ||
}, [validate, config.initialReport, config.noValidate]); | ||
}, []); | ||
return { | ||
ref, | ||
noValidate, | ||
error, | ||
props: { | ||
ref, | ||
noValidate, | ||
onSubmit(event) { | ||
var form = event.currentTarget; | ||
var nativeEvent = event.nativeEvent; | ||
var submitter = nativeEvent.submitter instanceof HTMLButtonElement || nativeEvent.submitter instanceof HTMLInputElement ? nativeEvent.submitter : null; // Validating the form with the submitter value | ||
onSubmit(event) { | ||
var form = event.currentTarget; | ||
var nativeEvent = event.nativeEvent; | ||
var submitter = nativeEvent.submitter; | ||
validate === null || validate === void 0 ? void 0 : validate(form, submitter); | ||
/** | ||
* It checks defaultPrevented to confirm if the submission is intentional | ||
* This is utilized by `useFieldList` to modify the list state when the submit | ||
* event is captured and revalidate the form with new fields without triggering | ||
* a form submission at the same time. | ||
*/ | ||
for (var element of form.elements) { | ||
if (isFieldElement(element) && element.name === '') { | ||
setError(element.validationMessage); | ||
break; | ||
} | ||
} | ||
/** | ||
* It checks defaultPrevented to confirm if the submission is intentional | ||
* This is utilized by `useFieldList` to modify the list state when the submit | ||
* event is captured and revalidate the form with new fields without triggering | ||
* a form submission at the same time. | ||
*/ | ||
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && !event.defaultPrevented) { | ||
var focused = false; | ||
for (var field of form.elements) { | ||
if (isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; // Focus on the first invalid field | ||
if (!submitter || event.defaultPrevented) { | ||
event.preventDefault(); | ||
return; | ||
} | ||
if (!focused && !field.validity.valid && field.tagName !== 'BUTTON') { | ||
field.focus(); | ||
focused = true; | ||
var formData = getFormData(form, submitter); | ||
var submission = parse(formData); | ||
var context = { | ||
form, | ||
formData, | ||
submission | ||
}; // Touch all fields only if the submitter is not a command button | ||
if (!submission.type) { | ||
for (var field of form.elements) { | ||
if (isFieldElement(field)) { | ||
// Mark the field as touched | ||
field.dataset.conformTouched = 'true'; | ||
} | ||
} | ||
} // Check the validity of the form | ||
} | ||
if (!event.currentTarget.reportValidity()) { | ||
event.preventDefault(); | ||
if (typeof config.onValidate === 'function' && !config.noValidate && !submitter.formNoValidate) { | ||
try { | ||
if (!config.onValidate(context)) { | ||
focusFirstInvalidField(form); | ||
event.preventDefault(); | ||
} | ||
} catch (e) { | ||
console.warn(e); | ||
} | ||
} | ||
} | ||
if (!event.defaultPrevented) { | ||
var _config$onSubmit; | ||
if (!event.defaultPrevented) { | ||
var _config$onSubmit; | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event); | ||
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, context); | ||
} | ||
} | ||
} | ||
}, | ||
config: fieldsetConfig | ||
}; | ||
@@ -164,10 +230,43 @@ } | ||
function useFieldset(ref, config) { | ||
var configRef = useRef(config); | ||
var [uncontrolledState, setUncontrolledState] = useState( // @ts-expect-error | ||
() => { | ||
var _config$defaultValue; | ||
var initialError = {}; | ||
for (var [name, message] of (_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : []) { | ||
var _config$initialError; | ||
var [key, ...paths] = getPaths(name); | ||
if (typeof key === 'string') { | ||
var _initialError$key; | ||
var scopedName = getName(paths); | ||
var entries = (_initialError$key = initialError[key]) !== null && _initialError$key !== void 0 ? _initialError$key : []; | ||
if (scopedName === '' && entries.length > 0 && entries[0][0] !== '') { | ||
initialError[key] = [[scopedName, message], ...entries]; | ||
} else { | ||
initialError[key] = [...entries, [scopedName, message]]; | ||
} | ||
} | ||
} | ||
return { | ||
defaultValue: (_config$defaultValue = config === null || config === void 0 ? void 0 : config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : {}, | ||
initialError | ||
}; | ||
}); | ||
var [error, setError] = useState(() => { | ||
var result = {}; | ||
for (var [key, _error] of Object.entries((_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {})) { | ||
var _config$initialError; | ||
for (var [key, entries] of Object.entries(uncontrolledState.initialError)) { | ||
var _entries$; | ||
if (_error !== null && _error !== void 0 && _error.message) { | ||
result[key] = _error.message; | ||
var [name, message] = (_entries$ = entries === null || entries === void 0 ? void 0 : entries[0]) !== null && _entries$ !== void 0 ? _entries$ : []; | ||
if (name === '') { | ||
result[key] = message !== null && message !== void 0 ? message : ''; | ||
} | ||
@@ -179,2 +278,5 @@ } | ||
useEffect(() => { | ||
configRef.current = config; | ||
}); | ||
useEffect(() => { | ||
/** | ||
@@ -188,9 +290,12 @@ * Reset the error state of each field if its validity is changed. | ||
setError(prev => { | ||
var _configRef$current$na, _configRef$current; | ||
var next = prev; | ||
var fieldsetName = (_configRef$current$na = (_configRef$current = configRef.current) === null || _configRef$current === void 0 ? void 0 : _configRef$current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : ''; | ||
for (var field of form.elements) { | ||
if (isFieldElement(field)) { | ||
var key = getKey(field.name, config === null || config === void 0 ? void 0 : config.name); | ||
if (isFieldElement(field) && field.name.startsWith(fieldsetName)) { | ||
var [key, ...paths] = getPaths(fieldsetName.length > 0 ? field.name.slice(fieldsetName.length + 1) : field.name); | ||
if (key) { | ||
if (typeof key === 'string' && paths.length === 0) { | ||
var _next$key, _next; | ||
@@ -231,25 +336,31 @@ | ||
var invalidHandler = event => { | ||
var _configRef$current$na2, _configRef$current2; | ||
var form = getFormElement(ref.current); | ||
var field = event.target; | ||
var fieldsetName = (_configRef$current$na2 = (_configRef$current2 = configRef.current) === null || _configRef$current2 === void 0 ? void 0 : _configRef$current2.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : ''; | ||
if (!form || !isFieldElement(field) || field.form !== form) { | ||
if (!form || !isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) { | ||
return; | ||
} | ||
var key = getKey(field.name, config === null || config === void 0 ? void 0 : config.name); // Update the error only if the field belongs to the fieldset | ||
var [key, ...paths] = getPaths(fieldsetName.length > 0 ? field.name.slice(fieldsetName.length + 1) : field.name); // Update the error only if the field belongs to the fieldset | ||
if (key) { | ||
setError(prev => { | ||
var _prev$key; | ||
if (typeof key === 'string' && paths.length === 0) { | ||
if (field.dataset.conformTouched) { | ||
setError(prev => { | ||
var _prev$key; | ||
var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : ''; | ||
var prevMessage = (_prev$key = prev === null || prev === void 0 ? void 0 : prev[key]) !== null && _prev$key !== void 0 ? _prev$key : ''; | ||
if (prevMessage === field.validationMessage) { | ||
return prev; | ||
} | ||
if (prevMessage === field.validationMessage) { | ||
return prev; | ||
} | ||
return _objectSpread2(_objectSpread2({}, prev), {}, { | ||
[key]: field.validationMessage | ||
return _objectSpread2(_objectSpread2({}, prev), {}, { | ||
[key]: field.validationMessage | ||
}); | ||
}); | ||
}); | ||
} | ||
event.preventDefault(); | ||
@@ -271,2 +382,4 @@ } | ||
var resetHandler = event => { | ||
var _fieldsetConfig$defau; | ||
var form = getFormElement(ref.current); | ||
@@ -278,2 +391,8 @@ | ||
var fieldsetConfig = configRef.current; | ||
setUncontrolledState({ | ||
// @ts-expect-error | ||
defaultValue: (_fieldsetConfig$defau = fieldsetConfig === null || fieldsetConfig === void 0 ? void 0 : fieldsetConfig.defaultValue) !== null && _fieldsetConfig$defau !== void 0 ? _fieldsetConfig$defau : {}, | ||
initialError: {} | ||
}); | ||
setError({}); | ||
@@ -293,22 +412,3 @@ }; | ||
}; | ||
}, [ref, config === null || config === void 0 ? void 0 : config.name]); | ||
useEffect(() => { | ||
setError(prev => { | ||
var next = prev; | ||
for (var [key, _error2] of Object.entries((_config$initialError2 = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : {})) { | ||
var _config$initialError2; | ||
if (next[key] !== (_error2 === null || _error2 === void 0 ? void 0 : _error2.message)) { | ||
var _error2$message; | ||
next = _objectSpread2(_objectSpread2({}, next), {}, { | ||
[key]: (_error2$message = _error2 === null || _error2 === void 0 ? void 0 : _error2.message) !== null && _error2$message !== void 0 ? _error2$message : '' | ||
}); | ||
} | ||
} | ||
return next; | ||
}); | ||
}, [config === null || config === void 0 ? void 0 : config.name, config === null || config === void 0 ? void 0 : config.initialError]); | ||
}, [ref]); | ||
/** | ||
@@ -322,3 +422,3 @@ * This allows us constructing the field at runtime as we have no information | ||
get(_target, key) { | ||
var _constraint, _config$defaultValue, _config$initialError$, _config$initialError3, _config$initialError4, _config$initialError5, _config$initialError6, _error$key; | ||
var _fieldsetConfig$const, _error$key; | ||
@@ -329,9 +429,10 @@ if (typeof key !== 'string') { | ||
var constraint = config === null || config === void 0 ? void 0 : (_constraint = config.constraint) === null || _constraint === void 0 ? void 0 : _constraint[key]; | ||
var fieldsetConfig = config !== null && config !== void 0 ? config : {}; | ||
var constraint = (_fieldsetConfig$const = fieldsetConfig.constraint) === null || _fieldsetConfig$const === void 0 ? void 0 : _fieldsetConfig$const[key]; | ||
var field = { | ||
config: _objectSpread2({ | ||
name: config !== null && config !== void 0 && config.name ? "".concat(config.name, ".").concat(key) : key, | ||
form: config === null || config === void 0 ? void 0 : config.form, | ||
defaultValue: config === null || config === void 0 ? void 0 : (_config$defaultValue = config.defaultValue) === null || _config$defaultValue === void 0 ? void 0 : _config$defaultValue[key], | ||
initialError: (_config$initialError$ = config === null || config === void 0 ? void 0 : (_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : (_config$initialError4 = _config$initialError3[key]) === null || _config$initialError4 === void 0 ? void 0 : _config$initialError4.details) !== null && _config$initialError$ !== void 0 ? _config$initialError$ : config === null || config === void 0 ? void 0 : (_config$initialError5 = config.initialError) === null || _config$initialError5 === void 0 ? void 0 : (_config$initialError6 = _config$initialError5[key]) === null || _config$initialError6 === void 0 ? void 0 : _config$initialError6.message | ||
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key, | ||
form: fieldsetConfig.form, | ||
defaultValue: uncontrolledState.defaultValue[key], | ||
initialError: uncontrolledState.initialError[key] | ||
}, constraint), | ||
@@ -350,21 +451,51 @@ error: (_error$key = error === null || error === void 0 ? void 0 : error[key]) !== null && _error$key !== void 0 ? _error$key : '' | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usefieldlist | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usefieldlist | ||
*/ | ||
function useFieldList(ref, config) { | ||
var [entries, setEntries] = useState(() => { | ||
var configRef = useRef(config); | ||
var [uncontrolledState, setUncontrolledState] = useState(() => { | ||
var _config$defaultValue2; | ||
return Object.entries((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [undefined]); | ||
var initialError = []; | ||
for (var [name, message] of (_config$initialError2 = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : []) { | ||
var _config$initialError2; | ||
var [index, ...paths] = getPaths(name); | ||
if (typeof index === 'number') { | ||
var _initialError$index; | ||
var scopedName = getName(paths); | ||
var _entries = (_initialError$index = initialError[index]) !== null && _initialError$index !== void 0 ? _initialError$index : []; | ||
if (scopedName === '' && _entries.length > 0 && _entries[0][0] !== '') { | ||
initialError[index] = [[scopedName, message], ..._entries]; | ||
} else { | ||
initialError[index] = [..._entries, [scopedName, message]]; | ||
} | ||
} | ||
} | ||
return { | ||
defaultValue: (_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : [], | ||
initialError | ||
}; | ||
}); | ||
var list = entries.map((_ref, index) => { | ||
var _config$defaultValue3, _config$initialError7, _config$initialError8; | ||
var [entries, setEntries] = useState(() => { | ||
var _config$defaultValue3; | ||
var [key, defaultValue] = _ref; | ||
return Object.entries((_config$defaultValue3 = config.defaultValue) !== null && _config$defaultValue3 !== void 0 ? _config$defaultValue3 : [undefined]); | ||
}); | ||
var list = entries.map((_ref3, index) => { | ||
var [key, defaultValue] = _ref3; | ||
return { | ||
key, | ||
config: _objectSpread2(_objectSpread2({}, config), {}, { | ||
config: { | ||
name: "".concat(config.name, "[").concat(index, "]"), | ||
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : (_config$defaultValue3 = config.defaultValue) === null || _config$defaultValue3 === void 0 ? void 0 : _config$defaultValue3[index], | ||
initialError: (_config$initialError7 = config.initialError) === null || _config$initialError7 === void 0 ? void 0 : (_config$initialError8 = _config$initialError7[index]) === null || _config$initialError8 === void 0 ? void 0 : _config$initialError8.details | ||
}) | ||
form: config.form, | ||
defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index], | ||
initialError: uncontrolledState.initialError[index] | ||
} | ||
}; | ||
@@ -382,5 +513,6 @@ }); | ||
return { | ||
name: listCommandKey, | ||
value: serializeListCommand(config.name, { | ||
name: 'conform/list', | ||
value: JSON.stringify({ | ||
type, | ||
scope: config.name, | ||
payload | ||
@@ -396,34 +528,15 @@ }), | ||
useEffect(() => { | ||
setEntries(prevEntries => { | ||
var _config$defaultValue4; | ||
var nextEntries = Object.entries((_config$defaultValue4 = config.defaultValue) !== null && _config$defaultValue4 !== void 0 ? _config$defaultValue4 : [undefined]); | ||
if (prevEntries.length !== nextEntries.length) { | ||
return nextEntries; | ||
} | ||
for (var i = 0; i < prevEntries.length; i++) { | ||
var [prevKey, prevValue] = prevEntries[i]; | ||
var [nextKey, nextValue] = nextEntries[i]; | ||
if (prevKey !== nextKey || prevValue !== nextValue) { | ||
return nextEntries; | ||
} | ||
} // No need to rerender in this case | ||
return prevEntries; | ||
}); | ||
configRef.current = config; | ||
}); | ||
useEffect(() => { | ||
var submitHandler = event => { | ||
var form = getFormElement(ref.current); | ||
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== listCommandKey) { | ||
if (!form || event.target !== form || !(event.submitter instanceof HTMLButtonElement) || event.submitter.name !== 'conform/list') { | ||
return; | ||
} | ||
var [name, command] = parseListCommand(event.submitter.value); | ||
var command = parseListCommand(event.submitter.value); | ||
if (name !== config.name) { | ||
if (command.scope !== configRef.current.name) { | ||
// Ensure the scope of the listener are limited to specific field name | ||
@@ -433,11 +546,19 @@ return; | ||
switch (command.type) { | ||
case 'append': | ||
case 'prepend': | ||
case 'replace': | ||
command.payload.defaultValue = ["".concat(Date.now()), command.payload.defaultValue]; | ||
break; | ||
} | ||
setEntries(entries => { | ||
switch (command.type) { | ||
case 'append': | ||
case 'prepend': | ||
case 'replace': | ||
return updateList([...(entries !== null && entries !== void 0 ? entries : [])], _objectSpread2(_objectSpread2({}, command), {}, { | ||
payload: _objectSpread2(_objectSpread2({}, command.payload), {}, { | ||
defaultValue: ["".concat(Date.now()), command.payload.defaultValue] | ||
}) | ||
})); | ||
setEntries(entries => updateList([...(entries !== null && entries !== void 0 ? entries : [])], command)); | ||
default: | ||
{ | ||
return updateList([...(entries !== null && entries !== void 0 ? entries : [])], command); | ||
} | ||
} | ||
}); | ||
event.preventDefault(); | ||
@@ -447,3 +568,3 @@ }; | ||
var resetHandler = event => { | ||
var _config$defaultValue5; | ||
var _fieldConfig$defaultV, _fieldConfig$defaultV2; | ||
@@ -456,3 +577,8 @@ var form = getFormElement(ref.current); | ||
setEntries(Object.entries((_config$defaultValue5 = config.defaultValue) !== null && _config$defaultValue5 !== void 0 ? _config$defaultValue5 : [])); | ||
var fieldConfig = configRef.current; | ||
setUncontrolledState({ | ||
defaultValue: (_fieldConfig$defaultV = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV !== void 0 ? _fieldConfig$defaultV : [], | ||
initialError: [] | ||
}); | ||
setEntries(Object.entries((_fieldConfig$defaultV2 = fieldConfig.defaultValue) !== null && _fieldConfig$defaultV2 !== void 0 ? _fieldConfig$defaultV2 : [undefined])); | ||
}; | ||
@@ -466,3 +592,3 @@ | ||
}; | ||
}, [ref, config.name, config.defaultValue]); | ||
}, [ref]); | ||
return [list, control]; | ||
@@ -476,10 +602,15 @@ } | ||
* | ||
* @see https://github.com/edmundhung/conform/tree/v0.3.1/packages/conform-react/README.md#usecontrolledinput | ||
* @see https://github.com/edmundhung/conform/tree/v0.4.0-pre.0/packages/conform-react/README.md#usecontrolledinput | ||
*/ | ||
function useControlledInput(field) { | ||
var _field$defaultValue; | ||
function useControlledInput(config) { | ||
var _config$defaultValue4; | ||
var ref = useRef(null); | ||
var inputRef = useRef(null); | ||
var [value, setValue] = useState("".concat((_field$defaultValue = field.defaultValue) !== null && _field$defaultValue !== void 0 ? _field$defaultValue : '')); | ||
var configRef = useRef(config); | ||
var [uncontrolledState, setUncontrolledState] = useState({ | ||
defaultValue: config.defaultValue, | ||
initialError: config.initialError | ||
}); | ||
var [value, setValue] = useState("".concat((_config$defaultValue4 = config.defaultValue) !== null && _config$defaultValue4 !== void 0 ? _config$defaultValue4 : '')); | ||
@@ -511,2 +642,27 @@ var handleChange = eventOrValue => { | ||
useEffect(() => { | ||
configRef.current = config; | ||
}); | ||
useEffect(() => { | ||
var resetHandler = event => { | ||
var _configRef$current$de; | ||
var form = getFormElement(ref.current); | ||
if (!form || event.target !== form) { | ||
return; | ||
} | ||
setUncontrolledState({ | ||
defaultValue: configRef.current.defaultValue, | ||
initialError: configRef.current.initialError | ||
}); | ||
setValue("".concat((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : '')); | ||
}; | ||
document.addEventListener('reset', resetHandler); | ||
return () => { | ||
document.removeEventListener('reset', resetHandler); | ||
}; | ||
}, []); | ||
return [_objectSpread2({ | ||
@@ -532,3 +688,3 @@ ref, | ||
}, input(field, { | ||
}, input(_objectSpread2(_objectSpread2({}, config), uncontrolledState), { | ||
type: 'text' | ||
@@ -535,0 +691,0 @@ })), { |
@@ -1,4 +0,4 @@ | ||
export { createSubmission, createValidate } from '@conform-to/dom'; | ||
export { hasError, isFieldElement, parse, reportValidity } from '@conform-to/dom'; | ||
export { useControlledInput, useFieldList, useFieldset, useForm } from './hooks.js'; | ||
import * as helpers from './helpers.js'; | ||
export { helpers as conform }; |
@@ -5,3 +5,3 @@ { | ||
"license": "MIT", | ||
"version": "0.3.1", | ||
"version": "0.4.0-pre.0", | ||
"main": "index.js", | ||
@@ -23,3 +23,3 @@ "module": "module/index.js", | ||
"dependencies": { | ||
"@conform-to/dom": "0.3.1" | ||
"@conform-to/dom": "0.4.0-pre.0" | ||
}, | ||
@@ -26,0 +26,0 @@ "peerDependencies": { |
78797
1532
+ Added@conform-to/dom@0.4.0-pre.0(transitive)
- Removed@conform-to/dom@0.3.1(transitive)
Updated@conform-to/dom@0.4.0-pre.0