@conform-to/react
Advanced tools
Comparing version 0.7.4 to 0.8.0-pre.0
@@ -41,6 +41,6 @@ import { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED } from '@conform-to/dom'; | ||
type BaseOptions = { | ||
ariaAttributes?: false; | ||
ariaAttributes?: true; | ||
description?: boolean; | ||
} | { | ||
ariaAttributes: true; | ||
description?: boolean; | ||
ariaAttributes: false; | ||
}; | ||
@@ -68,3 +68,3 @@ type ControlOptions = BaseOptions & { | ||
export declare function textarea<Schema extends Primitive | undefined | unknown>(config: FieldConfig<Schema>, options?: ControlOptions): TextareaProps; | ||
export declare function fieldset<Schema extends Record<string, any> | undefined | unknown>(config: FieldConfig<Schema>, options?: BaseOptions): FormControlProps; | ||
export declare function fieldset<Schema extends Record<string, unknown> | undefined | unknown>(config: FieldConfig<Schema>, options?: BaseOptions): FormControlProps; | ||
export { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED }; |
@@ -20,4 +20,6 @@ 'use strict'; | ||
} | ||
function getFormElementProps(config, options) { | ||
var _config$error, _config$error2; | ||
function getFormElementProps(config) { | ||
var _options$ariaAttribut, _config$error, _config$error2; | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
var hasAriaAttributes = (_options$ariaAttribut = options.ariaAttributes) !== null && _options$ariaAttribut !== void 0 ? _options$ariaAttribut : true; | ||
return cleanup({ | ||
@@ -27,4 +29,4 @@ id: config.id, | ||
form: config.form, | ||
'aria-invalid': options !== null && options !== void 0 && options.ariaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined, | ||
'aria-describedby': options !== null && options !== void 0 && options.ariaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options !== null && options !== void 0 && options.description ? config.descriptionId : undefined].reduce((result, id) => { | ||
'aria-invalid': hasAriaAttributes && config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length ? true : undefined, | ||
'aria-describedby': hasAriaAttributes ? [config.errorId && (_config$error2 = config.error) !== null && _config$error2 !== void 0 && _config$error2.length ? config.errorId : undefined, config.descriptionId && options.ariaAttributes !== false && options.description ? config.descriptionId : undefined].reduce((result, id) => { | ||
if (!result) { | ||
@@ -31,0 +33,0 @@ return id; |
@@ -8,3 +8,3 @@ import { type FieldConstraint, type FieldElement, type FieldsetConstraint, type Submission, type KeysOf, type ResolveType, getFormEncType, getFormMethod, parseIntent } from '@conform-to/dom'; | ||
defaultValue?: FieldValue<Schema>; | ||
initialError?: Record<string, string | string[]>; | ||
initialError?: Record<string, string[]>; | ||
form?: string; | ||
@@ -30,2 +30,7 @@ descriptionId?: string; | ||
}; | ||
interface ReportOptions { | ||
formError?: string[]; | ||
resetForm?: boolean; | ||
} | ||
export declare function report(submission: Submission, options?: ReportOptions): SubmissionResult; | ||
export interface FormConfig<Output extends Record<string, any>, Input extends Record<string, any> = Output> { | ||
@@ -112,3 +117,3 @@ /** | ||
errorId?: string; | ||
error: string; | ||
error: string | undefined; | ||
errors: string[]; | ||
@@ -143,3 +148,3 @@ ref: RefObject<HTMLFormElement>; | ||
*/ | ||
initialError?: Record<string, string | string[]>; | ||
initialError?: Record<string, string[]>; | ||
/** | ||
@@ -178,3 +183,2 @@ * An object describing the constraint of each field | ||
} | ||
export declare function useEventListeners(type: string, ref: RefObject<FieldElement>): void; | ||
/** | ||
@@ -204,7 +208,2 @@ * Returns a ref object and a set of helpers that dispatch corresponding dom event. | ||
}) => boolean>; | ||
acceptMultipleErrors?: ({ name, intent, payload, }: { | ||
name: string; | ||
intent: string; | ||
payload: Record<string, any>; | ||
}) => boolean; | ||
formatMessages?: ({ name, validity, constraint, defaultErrors, }: { | ||
@@ -217,4 +216,5 @@ name: string; | ||
}): Submission; | ||
export declare function getUniqueKey(): string; | ||
export declare function reportSubmission(form: HTMLFormElement, submission: SubmissionResult): void; | ||
export declare function getScope(intent: ReturnType<typeof parseIntent>): string | null; | ||
export {}; |
99
hooks.js
@@ -9,2 +9,13 @@ 'use strict'; | ||
function report(submission, options) { | ||
var _submission$error$; | ||
return { | ||
intent: submission.intent, | ||
payload: options !== null && options !== void 0 && options.resetForm ? null : submission.payload, | ||
error: options !== null && options !== void 0 && options.formError ? _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission.error), {}, { | ||
'': options.formError.concat((_submission$error$ = submission.error['']) !== null && _submission$error$ !== void 0 ? _submission$error$ : []) | ||
}) : submission.error | ||
}; | ||
} | ||
/** | ||
@@ -14,12 +25,2 @@ * Properties to be applied to the form element | ||
/** | ||
* Normalize error to an array of string. | ||
*/ | ||
function normalizeError(error) { | ||
if (!error) { | ||
// This treat both empty string and undefined as no error. | ||
return []; | ||
} | ||
return [].concat(error); | ||
} | ||
function useNoValidate(defaultNoValidate, validateBeforeHydrate) { | ||
@@ -83,5 +84,5 @@ var [noValidate, setNoValidate] = react.useState(defaultNoValidate || !validateBeforeHydrate); | ||
for (var [name, message] of Object.entries(config.initialError)) { | ||
var paths = dom.getPaths(name); | ||
if (paths.length === 1) { | ||
result[paths[0]] = normalizeError(message); | ||
var [path, ...restPaths] = dom.getPaths(name); | ||
if (typeof path !== 'undefined' && restPaths.length === 0) { | ||
result[path] = message; | ||
} | ||
@@ -93,29 +94,20 @@ } | ||
var handleInvalid = event => { | ||
var _config$name; | ||
var form = dom.getFormElement(ref.current); | ||
var element = event.target; | ||
if (!dom.isFieldElement(element) || element.form !== form || !element.dataset.conformTouched) { | ||
var prefix = (_config$name = config.name) !== null && _config$name !== void 0 ? _config$name : ''; | ||
if (!dom.isFieldElement(element) || element.form !== form || !element.name.startsWith(prefix) || !element.dataset.conformTouched) { | ||
return; | ||
} | ||
var key = element.name; | ||
if (config.name) { | ||
var scopePaths = dom.getPaths(config.name); | ||
var fieldPaths = dom.getPaths(element.name); | ||
for (var i = 0; i <= scopePaths.length; i++) { | ||
var path = fieldPaths[i]; | ||
if (i < scopePaths.length) { | ||
// Skip if the field is not in the scope | ||
if (path !== scopePaths[i]) { | ||
return; | ||
} | ||
} else { | ||
key = path; | ||
} | ||
} | ||
var name = element.name.slice(prefix.length); | ||
var [path, ...restPaths] = dom.getPaths(name); | ||
if (typeof path === 'undefined' || restPaths.length > 0) { | ||
return; | ||
} | ||
setError(prev => { | ||
if (element.validationMessage === dom.getValidationMessage(prev[key])) { | ||
if (element.validationMessage === dom.getValidationMessage(prev[path])) { | ||
return prev; | ||
} | ||
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, { | ||
[key]: dom.getErrors(element.validationMessage) | ||
[path]: dom.getErrors(element.validationMessage) | ||
}); | ||
@@ -148,3 +140,3 @@ }); | ||
function useForm() { | ||
var _config$lastSubmissio2, _config$lastSubmissio3; | ||
var _config$lastSubmissio3, _config$lastSubmissio4; | ||
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
@@ -156,6 +148,7 @@ var configRef = useConfigRef(config); | ||
var [errors, setErrors] = react.useState(() => { | ||
var _config$lastSubmissio; | ||
return normalizeError((_config$lastSubmissio = config.lastSubmission) === null || _config$lastSubmissio === void 0 ? void 0 : _config$lastSubmissio.error['']); | ||
var _config$lastSubmissio, _config$lastSubmissio2; | ||
return (_config$lastSubmissio = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.error['']) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : []; | ||
}); | ||
var initialError = react.useMemo(() => { | ||
var _submission$error$sco; | ||
var submission = config.lastSubmission; | ||
@@ -167,4 +160,7 @@ if (!submission) { | ||
var scope = getScope(intent); | ||
return scope === null ? submission.error : { | ||
[scope]: submission.error[scope] | ||
if (typeof scope !== 'string') { | ||
return submission.error; | ||
} | ||
return { | ||
[scope]: (_submission$error$sco = submission.error[scope]) !== null && _submission$error$sco !== void 0 ? _submission$error$sco : [] | ||
}; | ||
@@ -175,3 +171,3 @@ }, [config.lastSubmission]); | ||
var [defaultValueFromLastSubmission, setDefaultValueFromLastSubmission] = react.useState( // @ts-expect-error defaultValue is not in Submission type | ||
(_config$lastSubmissio2 = (_config$lastSubmissio3 = config.lastSubmission) === null || _config$lastSubmissio3 === void 0 ? void 0 : _config$lastSubmissio3.payload) !== null && _config$lastSubmissio2 !== void 0 ? _config$lastSubmissio2 : null); | ||
(_config$lastSubmissio3 = (_config$lastSubmissio4 = config.lastSubmission) === null || _config$lastSubmissio4 === void 0 ? void 0 : _config$lastSubmissio4.payload) !== null && _config$lastSubmissio3 !== void 0 ? _config$lastSubmissio3 : null); | ||
var fieldset = useFieldset(ref, { | ||
@@ -263,3 +259,3 @@ defaultValue: defaultValueFromLastSubmission !== null && defaultValueFromLastSubmission !== void 0 ? defaultValueFromLastSubmission : config.defaultValue, | ||
var [, error] = _ref; | ||
for (var message of normalizeError(error)) { | ||
for (var message of error) { | ||
if (message === dom.VALIDATION_UNDEFINED) { | ||
@@ -405,3 +401,3 @@ result.shouldServerValidate = true; | ||
// Generate a random key to avoid conflicts | ||
crypto.getRandomValues(new Uint32Array(1))[0].toString(36), intent.payload.defaultValue] | ||
getUniqueKey(), intent.payload.defaultValue] | ||
})); | ||
@@ -488,3 +484,2 @@ default: | ||
var useSafeLayoutEffect = typeof document === 'undefined' ? react.useEffect : react.useLayoutEffect; | ||
/** | ||
@@ -655,3 +650,3 @@ * Returns a ref object and a set of helpers that dispatch corresponding dom event. | ||
return dom.parse(formData, { | ||
resolve(payload, intent) { | ||
resolve() { | ||
var error = {}; | ||
@@ -661,3 +656,2 @@ var constraintPattern = /^constraint[A-Z][^A-Z]*$/; | ||
if (dom.isFieldElement(_element3)) { | ||
var _options$acceptMultip, _options$acceptMultip2; | ||
var name = _element3.name !== FORM_ERROR_ELEMENT_NAME ? _element3.name : ''; | ||
@@ -688,9 +682,4 @@ var constraint = Object.entries(_element3.dataset).reduce((result, _ref6) => { | ||
}); | ||
var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 || (_options$acceptMultip2 = options.acceptMultipleErrors) === null || _options$acceptMultip2 === void 0 ? void 0 : _options$acceptMultip2.call(options, { | ||
name, | ||
payload, | ||
intent | ||
})) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false; | ||
if (errors.length > 0) { | ||
error[name] = shouldAcceptMultipleErrors ? errors : errors[0]; | ||
error[name] = errors; | ||
} | ||
@@ -708,6 +697,13 @@ } | ||
} | ||
function getUniqueKey() { | ||
var [value] = crypto.getRandomValues(new Uint32Array(1)); | ||
if (!value) { | ||
throw new Error('Fail to generate an unique key'); | ||
} | ||
return value.toString(36); | ||
} | ||
function reportSubmission(form, submission) { | ||
for (var [name, message] of Object.entries(submission.error)) { | ||
// There is no need to create a placeholder button if all we want is to reset the error | ||
if (message === '') { | ||
if (message.length === 0) { | ||
continue; | ||
@@ -741,4 +737,5 @@ } | ||
for (var _element4 of dom.getFormControls(form)) { | ||
var _submission$error$_el; | ||
var _elementName = _element4.name !== FORM_ERROR_ELEMENT_NAME ? _element4.name : ''; | ||
var messages = normalizeError(submission.error[_elementName]); | ||
var messages = (_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : []; | ||
if (scope === null || scope === _elementName) { | ||
@@ -771,2 +768,4 @@ _element4.dataset.conformTouched = 'true'; | ||
exports.getScope = getScope; | ||
exports.getUniqueKey = getUniqueKey; | ||
exports.report = report; | ||
exports.reportSubmission = reportSubmission; | ||
@@ -773,0 +772,0 @@ exports.useFieldList = useFieldList; |
export { type FieldsetConstraint, type Submission, parse, list, validate, requestIntent, isFieldElement, } from '@conform-to/dom'; | ||
export { type Fieldset, type FieldConfig, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, validateConstraint, } from './hooks.js'; | ||
export { type Fieldset, type FieldConfig, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, validateConstraint, report, } from './hooks.js'; | ||
export * as conform from './helpers.js'; |
@@ -31,2 +31,3 @@ 'use strict'; | ||
}); | ||
exports.report = hooks.report; | ||
exports.useFieldList = hooks.useFieldList; | ||
@@ -33,0 +34,0 @@ exports.useFieldset = hooks.useFieldset; |
@@ -6,3 +6,3 @@ { | ||
"license": "MIT", | ||
"version": "0.7.4", | ||
"version": "0.8.0-pre.0", | ||
"main": "index.js", | ||
@@ -34,3 +34,3 @@ "module": "index.mjs", | ||
"dependencies": { | ||
"@conform-to/dom": "0.7.4" | ||
"@conform-to/dom": "0.8.0-pre.0" | ||
}, | ||
@@ -37,0 +37,0 @@ "peerDependencies": { |
@@ -376,3 +376,2 @@ # @conform-to/react | ||
type: 'text', | ||
ariaAttributes: true, | ||
})} | ||
@@ -397,12 +396,12 @@ /> | ||
resolve({ email, password }) { | ||
const error: Record<string, string> = {}; | ||
const error: Record<string, string[]> = {}; | ||
if (typeof email !== 'string') { | ||
error.email = 'Email is required'; | ||
error.email = ['Email is required']; | ||
} else if (!/^[^@]+@[^@]+$/.test(email)) { | ||
error.email = 'Email is invalid'; | ||
error.email = ['Email is invalid']; | ||
} | ||
if (typeof password !== 'string') { | ||
error.password = 'Password is required'; | ||
error.password = ['Password is required']; | ||
} | ||
@@ -409,0 +408,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
100988
2107
607
+ Added@conform-to/dom@0.8.0-pre.0(transitive)
- Removed@conform-to/dom@0.7.4(transitive)
Updated@conform-to/dom@0.8.0-pre.0