Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@conform-to/react

Package Overview
Dependencies
Maintainers
1
Versions
66
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@conform-to/react - npm Package Compare versions

Comparing version 0.6.1 to 0.6.2

base.d.ts

3

helpers.d.ts

@@ -1,2 +0,3 @@

import { type FieldConfig, type Primitive, VALIDATION_UNDEFINED, VALIDATION_SKIPPED, INTENT } from '@conform-to/dom';
import { INTENT } from '@conform-to/dom';
import { type FieldConfig, type Primitive, VALIDATION_UNDEFINED, VALIDATION_SKIPPED } from './hooks';
import type { CSSProperties, HTMLInputTypeAttribute } from 'react';

@@ -3,0 +4,0 @@ interface FormControlProps {

@@ -7,2 +7,3 @@ 'use strict';

var dom = require('@conform-to/dom');
var hooks = require('./hooks.js');

@@ -93,12 +94,6 @@ /**

});
Object.defineProperty(exports, 'VALIDATION_SKIPPED', {
enumerable: true,
get: function () { return dom.VALIDATION_SKIPPED; }
});
Object.defineProperty(exports, 'VALIDATION_UNDEFINED', {
enumerable: true,
get: function () { return dom.VALIDATION_UNDEFINED; }
});
exports.VALIDATION_SKIPPED = hooks.VALIDATION_SKIPPED;
exports.VALIDATION_UNDEFINED = hooks.VALIDATION_UNDEFINED;
exports.input = input;
exports.select = select;
exports.textarea = textarea;

@@ -1,5 +0,26 @@

import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type FormMethod, type FormEncType, type Submission } from '@conform-to/dom';
import { type FieldConstraint, type FieldElement, type FieldsetConstraint, type Submission, type KeysOf, type ResolveType, getFormEncType, getFormMethod } from '@conform-to/dom';
import { type FormEvent, type RefObject } from 'react';
export interface FormConfig<Schema extends Record<string, any>, ClientSubmission extends Submission | Submission<Schema> = Submission> {
export type Primitive = null | undefined | string | number | boolean | Date;
export interface FieldConfig<Schema> extends FieldConstraint<Schema> {
id?: string;
name: string;
defaultValue?: FieldValue<Schema>;
initialError?: Record<string, string | string[]>;
form?: string;
descriptionId?: string;
errorId?: string;
/**
* The frist error of the field
*/
error?: string;
/**
* All of the field errors
*/
errors?: string[];
}
export type FieldValue<Schema> = Schema extends Primitive ? string : Schema extends File ? File : Schema extends Array<infer InnerType> ? Array<FieldValue<InnerType>> : Schema extends Record<string, any> ? {
[Key in KeysOf<Schema>]?: FieldValue<ResolveType<Schema, Key>>;
} : any;
export interface FormConfig<Output extends Record<string, any>, Input extends Record<string, any> = Output> {
/**
* If the form id is provided, Id for label,

@@ -34,3 +55,3 @@ * input and error elements will be derived.

*/
defaultValue?: FieldValue<Schema>;
defaultValue?: FieldValue<Input>;
/**

@@ -43,3 +64,3 @@ * An object describing the result of the last submission

*/
constraint?: FieldsetConstraint<Schema>;
constraint?: FieldsetConstraint<Input>;
/**

@@ -63,3 +84,3 @@ * Enable native validation before hydation.

formData: FormData;
}) => ClientSubmission;
}) => Submission | Submission<Output>;
/**

@@ -71,6 +92,6 @@ * The submit event handler of the form. It will be called

formData: FormData;
submission: ClientSubmission;
submission: Submission;
action: string;
encType: FormEncType;
method: FormMethod;
encType: ReturnType<typeof getFormEncType>;
method: ReturnType<typeof getFormMethod>;
}) => void;

@@ -103,3 +124,3 @@ }

*/
export declare function useForm<Schema extends Record<string, any>, ClientSubmission extends Submission | Submission<Schema> = Submission>(config?: FormConfig<Schema, ClientSubmission>): [Form, Fieldset<Schema>];
export declare function useForm<Output extends Record<string, any>, Input extends Record<string, any> = Output>(config?: FormConfig<Output, Input>): [Form, Fieldset<Input>];
/**

@@ -109,3 +130,3 @@ * A set of field configuration

export type Fieldset<Schema extends Record<string, any>> = {
[Key in keyof Schema]-?: FieldConfig<Schema[Key]>;
[Key in KeysOf<Schema>]-?: FieldConfig<ResolveType<Schema, Key>>;
};

@@ -142,4 +163,3 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {

/**
* Returns a list of key and config, with a group of helpers
* configuring buttons for list manipulation
* Returns a list of key and field config.
*

@@ -174,2 +194,33 @@ * @see https://conform.guide/api/react#usefieldlist

}): [RefObject<RefShape>, InputControl];
export declare const VALIDATION_UNDEFINED = "__undefined__";
export declare const VALIDATION_SKIPPED = "__skipped__";
export declare const FORM_ERROR_ELEMENT_NAME = "__form__";
/**
* Validate the form with the Constraint Validation API
* @see https://conform.guide/api/react#validateconstraint
*/
export declare function validateConstraint(options: {
form: HTMLFormElement;
formData?: FormData;
constraint?: Record<Lowercase<string>, (value: string, context: {
formData: FormData;
attributeValue: string;
}) => boolean>;
acceptMultipleErrors?: ({ name, intent, payload, }: {
name: string;
intent: string;
payload: Record<string, any>;
}) => boolean;
formatMessages?: ({ name, validity, constraint, defaultErrors, }: {
name: string;
validity: ValidityState;
constraint: Record<string, boolean>;
defaultErrors: string[];
}) => string[];
}): Submission;
export declare function reportSubmission(form: HTMLFormElement, submission: Submission): void;
/**
* Check if the current focus is on a intent button.
*/
export declare function isFocusedOnIntentButton(form: HTMLFormElement, intent: string): boolean;
export {};

@@ -10,2 +10,118 @@ 'use strict';

/**
* 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) {
var [noValidate, setNoValidate] = react.useState(defaultNoValidate || !validateBeforeHydrate);
react.useEffect(() => {
setNoValidate(true);
}, []);
return noValidate;
}
function useFormRef(userProvidedRef) {
var formRef = react.useRef(null);
return userProvidedRef !== null && userProvidedRef !== void 0 ? userProvidedRef : formRef;
}
function useConfigRef(config) {
var ref = react.useRef(config);
useSafeLayoutEffect(() => {
ref.current = config;
});
return ref;
}
function useFormReporter(ref, lastSubmission) {
var [submission, setSubmission] = react.useState(lastSubmission);
var report = react.useCallback((form, submission) => {
var event = new CustomEvent('conform', {
detail: submission.intent
});
form.dispatchEvent(event);
setSubmission(submission);
}, []);
react.useEffect(() => {
var form = ref.current;
if (!form || !lastSubmission) {
return;
}
report(form, lastSubmission);
}, [ref, lastSubmission, report]);
react.useEffect(() => {
var form = ref.current;
if (!form || !submission) {
return;
}
reportSubmission(form, submission);
}, [ref, submission]);
return report;
}
function useFormError(ref, config) {
var [error, setError] = react.useState(() => {
if (!config.initialError) {
return {};
}
var result = {};
for (var [name, message] of Object.entries(config.initialError)) {
var paths = dom.getPaths(name);
if (paths.length === 1) {
result[paths[0]] = normalizeError(message);
}
}
return result;
});
react.useEffect(() => {
var handleInvalid = event => {
var form = dom.getFormElement(ref.current);
var element = event.target;
if (!dom.isFieldElement(element) || element.form !== form || !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;
}
}
}
setError(prev => {
if (element.validationMessage === dom.getValidationMessage(prev[key])) {
return prev;
}
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, {
[key]: dom.getErrors(element.validationMessage)
});
});
event.preventDefault();
};
var handleReset = event => {
var form = dom.getFormElement(ref.current);
if (form && event.target === form) {
setError({});
}
};
document.addEventListener('reset', handleReset);
document.addEventListener('invalid', handleInvalid, true);
return () => {
document.removeEventListener('reset', handleReset);
document.removeEventListener('invalid', handleInvalid, true);
};
}, [ref, config.name]);
return [error, setError];
}
/**
* Returns properties required to hook into form events.

@@ -17,12 +133,11 @@ * Applied custom validation and define when error should be reported.

function useForm() {
var _config$lastSubmissio, _config$ref, _ref2, _config$lastSubmissio2;
var _ref, _config$lastSubmissio2;
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var configRef = react.useRef(config);
var formRef = react.useRef(null);
var [lastSubmission, setLastSubmission] = react.useState((_config$lastSubmissio = config.lastSubmission) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : null);
var configRef = useConfigRef(config);
var ref = useFormRef(config.ref);
var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
var report = useFormReporter(ref, config.lastSubmission);
var [errors, setErrors] = react.useState(() => {
if (!config.lastSubmission) {
return [];
}
return [].concat(config.lastSubmission.error['']);
var _config$lastSubmissio;
return normalizeError((_config$lastSubmissio = config.lastSubmission) === null || _config$lastSubmissio === void 0 ? void 0 : _config$lastSubmissio.error['']);
});

@@ -35,13 +150,8 @@ var initialError = react.useMemo(() => {

var scope = dom.getScope(submission.intent);
return Object.entries(submission.error).reduce((result, _ref) => {
var [name, message] = _ref;
if (name !== '' && (scope === null || scope === name)) {
result[name] = message;
}
return result;
}, {});
return scope === null ? submission.error : {
[scope]: submission.error[scope]
};
}, [config.lastSubmission]);
var ref = (_config$ref = config.ref) !== null && _config$ref !== void 0 ? _config$ref : formRef;
var fieldset = useFieldset(ref, {
defaultValue: (_ref2 = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.payload) !== null && _ref2 !== void 0 ? _ref2 : config.defaultValue,
defaultValue: (_ref = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.payload) !== null && _ref !== void 0 ? _ref : config.defaultValue,
initialError,

@@ -51,36 +161,7 @@ constraint: config.constraint,

});
var [noValidate, setNoValidate] = react.useState(config.noValidate || !config.fallbackNative);
useSafeLayoutEffect(() => {
configRef.current = config;
});
react.useEffect(() => {
setNoValidate(true);
}, []);
react.useEffect(() => {
var form = ref.current;
var submission = config.lastSubmission;
if (!form || !submission) {
return;
}
var listCommand = dom.parseListCommand(submission.intent);
if (listCommand) {
form.dispatchEvent(new CustomEvent('conform/list', {
detail: submission.intent
}));
}
setLastSubmission(submission);
}, [ref, config.lastSubmission]);
react.useEffect(() => {
var form = ref.current;
if (!form || !lastSubmission) {
return;
}
dom.reportSubmission(form, lastSubmission);
}, [ref, lastSubmission]);
react.useEffect(() => {
// Revalidate the form when input value is changed
var handleInput = event => {
// custom validate handler
var createValidateHandler = name => event => {
var field = event.target;
var form = ref.current;
var formConfig = configRef.current;
var {

@@ -90,30 +171,14 @@ initialReport = 'onSubmit',

shouldRevalidate = 'onInput'
} = formConfig;
if (!form || !dom.isFieldElement(field) || field.form !== form) {
} = configRef.current;
if (!form || !dom.isFocusableFormControl(field) || field.form !== form) {
return;
}
if (field.dataset.conformTouched ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
if (field.dataset.conformTouched ? shouldRevalidate === name : shouldValidate === name) {
dom.requestIntent(form, dom.validate(field.name));
}
};
var handleBlur = event => {
var field = event.target;
var form = ref.current;
var formConfig = configRef.current;
var {
initialReport = 'onSubmit',
shouldValidate = initialReport === 'onChange' ? 'onInput' : initialReport,
shouldRevalidate = 'onInput'
} = formConfig;
if (!form || !dom.isFieldElement(field) || field.form !== form) {
return;
}
if (field.dataset.conformTouched ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
dom.requestIntent(form, dom.validate(field.name));
}
};
var handleInvalid = event => {
var form = ref.current;
var field = event.target;
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== dom.FORM_ERROR_ELEMENT_NAME) {
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== FORM_ERROR_ELEMENT_NAME) {
return;

@@ -133,10 +198,10 @@ }

// Reset all field state
for (var field of form.elements) {
if (dom.isFieldElement(field)) {
delete field.dataset.conformTouched;
field.setCustomValidity('');
}
for (var element of dom.getFormControls(form)) {
delete element.dataset.conformTouched;
element.setCustomValidity('');
}
setErrors([]);
};
var handleInput = createValidateHandler('onInput');
var handleBlur = createValidateHandler('onBlur');
document.addEventListener('input', handleInput, true);

@@ -152,3 +217,3 @@ document.addEventListener('blur', handleBlur, true);

};
}, [ref]);
}, [ref, configRef]);
var form = {

@@ -169,30 +234,28 @@ ref,

try {
var _config$onValidate;
var _config$onValidate, _config$onValidate2;
var formData = dom.getFormData(form, submitter);
var getSubmission = (_config$onValidate = config.onValidate) !== null && _config$onValidate !== void 0 ? _config$onValidate : context => dom.parse(context.formData);
var submission = getSubmission({
var submission = (_config$onValidate = (_config$onValidate2 = config.onValidate) === null || _config$onValidate2 === void 0 ? void 0 : _config$onValidate2.call(config, {
form,
formData
});
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && Object.entries(submission.error).some(_ref3 => {
var [, message] = _ref3;
return message !== '' && ![].concat(message).includes(dom.VALIDATION_UNDEFINED);
}) || typeof config.onValidate !== 'undefined' && (submission.intent.startsWith('validate') || submission.intent.startsWith('list')) && Object.entries(submission.error).every(_ref4 => {
var [, message] = _ref4;
return ![].concat(message).includes(dom.VALIDATION_UNDEFINED);
})) {
var listCommand = dom.parseListCommand(submission.intent);
if (listCommand) {
form.dispatchEvent(new CustomEvent('conform/list', {
detail: submission.intent
}));
}
setLastSubmission(submission);
})) !== null && _config$onValidate !== void 0 ? _config$onValidate : dom.parse(formData);
var messages = Object.entries(submission.error).reduce((messages, _ref2) => {
var [, message] = _ref2;
return messages.concat(normalizeError(message));
}, []);
var shouldValidate = !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate);
var shouldFallbackToServer = messages.includes(VALIDATION_UNDEFINED);
var hasClientValidation = typeof config.onValidate !== 'undefined';
var isValid = messages.length === 0;
if (hasClientValidation && (dom.isSubmitting(submission.intent) ? shouldValidate && !isValid : !shouldFallbackToServer)) {
report(form, submission);
event.preventDefault();
} else {
var _config$onSubmit;
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, _rollupPluginBabelHelpers.objectSpread2({
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
formData,
submission
}, dom.getFormAttributes(form, submitter)));
submission,
action: dom.getFormAction(nativeEvent),
encType: dom.getFormEncType(nativeEvent),
method: dom.getFormMethod(nativeEvent)
});
}

@@ -222,64 +285,7 @@ } catch (e) {

function useFieldset(ref, config) {
var configRef = react.useRef(config);
var [error, setError] = react.useState(() => {
var initialError = config === null || config === void 0 ? void 0 : config.initialError;
if (!initialError) {
return {};
}
var result = {};
for (var [name, message] of Object.entries(initialError)) {
var [key, ...paths] = dom.getPaths(name);
if (typeof key === 'string' && paths.length === 0) {
result[key] = [].concat(message !== null && message !== void 0 ? message : []);
}
}
return result;
var [error] = useFormError(ref, {
initialError: config.initialError,
name: config.name
});
useSafeLayoutEffect(() => {
configRef.current = config;
});
react.useEffect(() => {
var invalidHandler = event => {
var _configRef$current$na;
var form = dom.getFormElement(ref.current);
var field = event.target;
var fieldsetName = (_configRef$current$na = configRef.current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) {
return;
}
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 (typeof key === 'string' && paths.length === 0) {
if (field.dataset.conformTouched) {
setError(prev => {
var prevMessage = dom.getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[key]);
if (prevMessage === field.validationMessage) {
return prev;
}
return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, prev), {}, {
[key]: dom.getErrors(field.validationMessage)
});
});
}
event.preventDefault();
}
};
var resetHandler = event => {
var form = dom.getFormElement(ref.current);
if (!form || event.target !== form) {
return;
}
setError({});
};
// The invalid event does not bubble and so listening on the capturing pharse is needed
document.addEventListener('invalid', invalidHandler, true);
document.addEventListener('reset', resetHandler);
return () => {
document.removeEventListener('invalid', invalidHandler, true);
document.removeEventListener('reset', resetHandler);
};
}, [ref]);
/**

@@ -299,4 +305,4 @@ * This allows us constructing the field at runtime as we have no information

var errors = error === null || error === void 0 ? void 0 : error[key];
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref5) => {
var [name, message] = _ref5;
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref3) => {
var [name, message] = _ref3;
var [field, ...paths] = dom.getPaths(name);

@@ -327,4 +333,3 @@ if (field === key) {

/**
* Returns a list of key and config, with a group of helpers
* configuring buttons for list manipulation
* Returns a list of key and field config.
*

@@ -334,13 +339,6 @@ * @see https://conform.guide/api/react#usefieldlist

function useFieldList(ref, config) {
var configRef = react.useRef(config);
var [error, setError] = react.useState(() => {
var initialError = [];
for (var [name, message] of Object.entries((_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {})) {
var _config$initialError;
var [index, ...paths] = dom.getPaths(name);
if (typeof index === 'number' && paths.length === 0) {
initialError[index] = [].concat(message !== null && message !== void 0 ? message : []);
}
}
return initialError;
var configRef = useConfigRef(config);
var [error, setError] = useFormError(ref, {
initialError: config.initialError,
name: config.name
});

@@ -351,32 +349,5 @@ var [entries, setEntries] = react.useState(() => {

});
useSafeLayoutEffect(() => {
configRef.current = config;
});
react.useEffect(() => {
var invalidHandler = event => {
var _configRef$current$na2;
var conformHandler = event => {
var form = dom.getFormElement(ref.current);
var field = event.target;
var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
if (!form || !dom.isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
return;
}
var [index, ...paths] = dom.getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
// Update the error only if the field belongs to the fieldset
if (typeof index === 'number' && paths.length === 0) {
if (field.dataset.conformTouched) {
setError(prev => {
var prevMessage = dom.getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[index]);
if (prevMessage === field.validationMessage) {
return prev;
}
return [...prev.slice(0, index), dom.getErrors(field.validationMessage), ...prev.slice(index + 1)];
});
}
event.preventDefault();
}
};
var listHandler = event => {
var form = dom.getFormElement(ref.current);
if (!form || event.target !== form) {

@@ -409,2 +380,8 @@ return;

setError(error => {
var errorList = [];
for (var [key, messages] of Object.entries(error)) {
if (typeof key === 'number') {
errorList[key] = messages;
}
}
switch (command.type) {

@@ -414,3 +391,3 @@ case 'append':

case 'replace':
return dom.updateList([...error], _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
errorList = dom.updateList(errorList, _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command), {}, {
payload: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, command.payload), {}, {

@@ -420,7 +397,10 @@ defaultValue: undefined

}));
break;
default:
{
return dom.updateList([...error], command);
errorList = dom.updateList(errorList, command);
break;
}
}
return Object.assign({}, errorList);
});

@@ -435,22 +415,19 @@ };

setEntries(Object.entries((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : [undefined]));
setError([]);
};
// @ts-expect-error Custom event: conform/list
document.addEventListener('conform/list', listHandler, true);
document.addEventListener('invalid', invalidHandler, true);
// @ts-expect-error Custom event: conform
document.addEventListener('conform', conformHandler, true);
document.addEventListener('reset', resetHandler);
return () => {
// @ts-expect-error Custom event: conform/list
document.removeEventListener('conform/list', listHandler, true);
document.removeEventListener('invalid', invalidHandler, true);
// @ts-expect-error Custom event: conform
document.removeEventListener('conform', conformHandler, true);
document.removeEventListener('reset', resetHandler);
};
}, [ref]);
return entries.map((_ref6, index) => {
var _config$initialError2, _config$defaultValue2;
var [key, defaultValue] = _ref6;
}, [ref, configRef, setError]);
return entries.map((_ref4, index) => {
var _config$initialError, _config$defaultValue2;
var [key, defaultValue] = _ref4;
var errors = error[index];
var initialError = Object.entries((_config$initialError2 = config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : {}).reduce((result, _ref7) => {
var [name, message] = _ref7;
var initialError = Object.entries((_config$initialError = config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {}).reduce((result, _ref5) => {
var [name, message] = _ref5;
var [field, ...paths] = dom.getPaths(name);

@@ -517,3 +494,3 @@ if (field === index) {

var ref = react.useRef(null);
var optionsRef = react.useRef(options);
var optionsRef = useConfigRef(options);
var changeDispatched = react.useRef(false);

@@ -523,5 +500,2 @@ var focusDispatched = react.useRef(false);

useSafeLayoutEffect(() => {
optionsRef.current = options;
});
useSafeLayoutEffect(() => {
var getInputElement = () => {

@@ -654,6 +628,154 @@ var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2;

};
}, []);
}, [optionsRef]);
return [ref, control];
}
var VALIDATION_UNDEFINED = '__undefined__';
var VALIDATION_SKIPPED = '__skipped__';
var FORM_ERROR_ELEMENT_NAME = '__form__';
/**
* Validate the form with the Constraint Validation API
* @see https://conform.guide/api/react#validateconstraint
*/
function validateConstraint(options) {
var _options$formData, _options$formatMessag;
var formData = (_options$formData = options === null || options === void 0 ? void 0 : options.formData) !== null && _options$formData !== void 0 ? _options$formData : new FormData(options.form);
var getDefaultErrors = (validity, result) => {
var errors = [];
if (validity.valueMissing) errors.push('required');
if (validity.typeMismatch || validity.badInput) errors.push('type');
if (validity.tooShort) errors.push('minLength');
if (validity.rangeUnderflow) errors.push('min');
if (validity.stepMismatch) errors.push('step');
if (validity.tooLong) errors.push('maxLength');
if (validity.rangeOverflow) errors.push('max');
if (validity.patternMismatch) errors.push('pattern');
for (var [constraintName, valid] of Object.entries(result)) {
if (!valid) {
errors.push(constraintName);
}
}
return errors;
};
var formatMessages = (_options$formatMessag = options === null || options === void 0 ? void 0 : options.formatMessages) !== null && _options$formatMessag !== void 0 ? _options$formatMessag : _ref6 => {
var {
defaultErrors
} = _ref6;
return defaultErrors;
};
return dom.parse(formData, {
resolve(payload, intent) {
var error = {};
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
var _loop = function _loop(element) {
if (dom.isFieldElement(element)) {
var _options$acceptMultip, _options$acceptMultip2;
var name = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var constraint = Object.entries(element.dataset).reduce((result, _ref7) => {
var [name, attributeValue = ''] = _ref7;
if (constraintPattern.test(name)) {
var _options$constraint;
var constraintName = name.slice(10).toLowerCase();
var _validate = (_options$constraint = options.constraint) === null || _options$constraint === void 0 ? void 0 : _options$constraint[constraintName];
if (typeof _validate === 'function') {
result[constraintName] = _validate(element.value, {
formData,
attributeValue
});
} else {
console.warn("Found an \"".concat(constraintName, "\" constraint with undefined definition; Please specify it on the validateConstraint API."));
}
}
return result;
}, {});
var errors = formatMessages({
name,
validity: element.validity,
constraint,
defaultErrors: getDefaultErrors(element.validity, constraint)
});
var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 ? 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];
}
}
};
for (var element of options.form.elements) {
_loop(element);
}
return {
error
};
}
});
}
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 === '') {
continue;
}
// We can't use empty string as button name
// As `form.element.namedItem('')` will always returns null
var elementName = name ? name : FORM_ERROR_ELEMENT_NAME;
var item = form.elements.namedItem(elementName);
if (item instanceof RadioNodeList) {
for (var field of item) {
if (field.type !== 'radio') {
console.warn('Repeated field name is not supported.');
continue;
}
}
}
if (item === null) {
// Create placeholder button to keep the error without contributing to the form data
var button = document.createElement('button');
button.name = elementName;
button.hidden = true;
button.dataset.conformTouched = 'true';
form.appendChild(button);
}
}
var scope = dom.getScope(submission.intent);
for (var element of dom.getFormControls(form)) {
var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var messages = normalizeError(submission.error[_elementName]);
if (scope === null || scope === _elementName) {
element.dataset.conformTouched = 'true';
}
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(dom.getValidationMessage(messages));
element.dispatchEvent(invalidEvent);
}
}
if (dom.isSubmitting(submission.intent) || isFocusedOnIntentButton(form, submission.intent)) {
if (scope) {
dom.focusFormControl(form, scope);
} else {
dom.focusFirstInvalidControl(form);
}
}
}
/**
* Check if the current focus is on a intent button.
*/
function isFocusedOnIntentButton(form, intent) {
var element = document.activeElement;
return dom.isFieldElement(element) && element.type === 'submit' && element.form === form && element.name === dom.INTENT && element.value === intent;
}
exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
exports.isFocusedOnIntentButton = isFocusedOnIntentButton;
exports.reportSubmission = reportSubmission;
exports.useFieldList = useFieldList;

@@ -663,1 +785,2 @@ exports.useFieldset = useFieldset;

exports.useInputEvent = useInputEvent;
exports.validateConstraint = validateConstraint;

@@ -1,3 +0,3 @@

export { type FieldConfig, type FieldsetConstraint, type Submission, parse, validateConstraint, list, validate, requestIntent, isFieldElement, } from '@conform-to/dom';
export { type Fieldset, type FieldsetConfig, type FormConfig, useForm, useFieldset, useFieldList, useInputEvent, } from './hooks';
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';
export * as conform from './helpers';

@@ -31,6 +31,2 @@ 'use strict';

});
Object.defineProperty(exports, 'validateConstraint', {
enumerable: true,
get: function () { return dom.validateConstraint; }
});
exports.useFieldList = hooks.useFieldList;

@@ -40,2 +36,3 @@ exports.useFieldset = hooks.useFieldset;

exports.useInputEvent = hooks.useInputEvent;
exports.validateConstraint = hooks.validateConstraint;
exports.conform = helpers;
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
export { INTENT, VALIDATION_SKIPPED, VALIDATION_UNDEFINED } from '@conform-to/dom';
export { INTENT } from '@conform-to/dom';
export { VALIDATION_SKIPPED, VALIDATION_UNDEFINED } from './hooks.js';

@@ -4,0 +5,0 @@ /**

import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
import { getScope, parseListCommand, reportSubmission, getFormData, parse, VALIDATION_UNDEFINED, getFormAttributes, getPaths, getName, isFieldElement, requestIntent, validate, FORM_ERROR_ELEMENT_NAME, getErrors, getFormElement, getValidationMessage, updateList } from '@conform-to/dom';
import { useRef, useState, useMemo, useEffect, useLayoutEffect } from 'react';
import { getScope, getFormData, parse, isSubmitting, getFormAction, getFormEncType, getFormMethod, getPaths, getName, isFieldElement, getErrors, getFormControls, getFormElement, parseListCommand, updateList, getValidationMessage, focusFormControl, focusFirstInvalidControl, INTENT, isFocusableFormControl, requestIntent, validate } from '@conform-to/dom';
import { useState, useMemo, useEffect, useRef, useCallback, useLayoutEffect } from 'react';
/**
* 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) {
var [noValidate, setNoValidate] = useState(defaultNoValidate || !validateBeforeHydrate);
useEffect(() => {
setNoValidate(true);
}, []);
return noValidate;
}
function useFormRef(userProvidedRef) {
var formRef = useRef(null);
return userProvidedRef !== null && userProvidedRef !== void 0 ? userProvidedRef : formRef;
}
function useConfigRef(config) {
var ref = useRef(config);
useSafeLayoutEffect(() => {
ref.current = config;
});
return ref;
}
function useFormReporter(ref, lastSubmission) {
var [submission, setSubmission] = useState(lastSubmission);
var report = useCallback((form, submission) => {
var event = new CustomEvent('conform', {
detail: submission.intent
});
form.dispatchEvent(event);
setSubmission(submission);
}, []);
useEffect(() => {
var form = ref.current;
if (!form || !lastSubmission) {
return;
}
report(form, lastSubmission);
}, [ref, lastSubmission, report]);
useEffect(() => {
var form = ref.current;
if (!form || !submission) {
return;
}
reportSubmission(form, submission);
}, [ref, submission]);
return report;
}
function useFormError(ref, config) {
var [error, setError] = useState(() => {
if (!config.initialError) {
return {};
}
var result = {};
for (var [name, message] of Object.entries(config.initialError)) {
var paths = getPaths(name);
if (paths.length === 1) {
result[paths[0]] = normalizeError(message);
}
}
return result;
});
useEffect(() => {
var handleInvalid = event => {
var form = getFormElement(ref.current);
var element = event.target;
if (!isFieldElement(element) || element.form !== form || !element.dataset.conformTouched) {
return;
}
var key = element.name;
if (config.name) {
var scopePaths = getPaths(config.name);
var fieldPaths = 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;
}
}
}
setError(prev => {
if (element.validationMessage === getValidationMessage(prev[key])) {
return prev;
}
return _objectSpread2(_objectSpread2({}, prev), {}, {
[key]: getErrors(element.validationMessage)
});
});
event.preventDefault();
};
var handleReset = event => {
var form = getFormElement(ref.current);
if (form && event.target === form) {
setError({});
}
};
document.addEventListener('reset', handleReset);
document.addEventListener('invalid', handleInvalid, true);
return () => {
document.removeEventListener('reset', handleReset);
document.removeEventListener('invalid', handleInvalid, true);
};
}, [ref, config.name]);
return [error, setError];
}
/**
* Returns properties required to hook into form events.

@@ -12,12 +128,11 @@ * Applied custom validation and define when error should be reported.

function useForm() {
var _config$lastSubmissio, _config$ref, _ref2, _config$lastSubmissio2;
var _ref, _config$lastSubmissio2;
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var configRef = useRef(config);
var formRef = useRef(null);
var [lastSubmission, setLastSubmission] = useState((_config$lastSubmissio = config.lastSubmission) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : null);
var configRef = useConfigRef(config);
var ref = useFormRef(config.ref);
var noValidate = useNoValidate(config.noValidate, config.fallbackNative);
var report = useFormReporter(ref, config.lastSubmission);
var [errors, setErrors] = useState(() => {
if (!config.lastSubmission) {
return [];
}
return [].concat(config.lastSubmission.error['']);
var _config$lastSubmissio;
return normalizeError((_config$lastSubmissio = config.lastSubmission) === null || _config$lastSubmissio === void 0 ? void 0 : _config$lastSubmissio.error['']);
});

@@ -30,13 +145,8 @@ var initialError = useMemo(() => {

var scope = getScope(submission.intent);
return Object.entries(submission.error).reduce((result, _ref) => {
var [name, message] = _ref;
if (name !== '' && (scope === null || scope === name)) {
result[name] = message;
}
return result;
}, {});
return scope === null ? submission.error : {
[scope]: submission.error[scope]
};
}, [config.lastSubmission]);
var ref = (_config$ref = config.ref) !== null && _config$ref !== void 0 ? _config$ref : formRef;
var fieldset = useFieldset(ref, {
defaultValue: (_ref2 = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.payload) !== null && _ref2 !== void 0 ? _ref2 : config.defaultValue,
defaultValue: (_ref = (_config$lastSubmissio2 = config.lastSubmission) === null || _config$lastSubmissio2 === void 0 ? void 0 : _config$lastSubmissio2.payload) !== null && _ref !== void 0 ? _ref : config.defaultValue,
initialError,

@@ -46,36 +156,7 @@ constraint: config.constraint,

});
var [noValidate, setNoValidate] = useState(config.noValidate || !config.fallbackNative);
useSafeLayoutEffect(() => {
configRef.current = config;
});
useEffect(() => {
setNoValidate(true);
}, []);
useEffect(() => {
var form = ref.current;
var submission = config.lastSubmission;
if (!form || !submission) {
return;
}
var listCommand = parseListCommand(submission.intent);
if (listCommand) {
form.dispatchEvent(new CustomEvent('conform/list', {
detail: submission.intent
}));
}
setLastSubmission(submission);
}, [ref, config.lastSubmission]);
useEffect(() => {
var form = ref.current;
if (!form || !lastSubmission) {
return;
}
reportSubmission(form, lastSubmission);
}, [ref, lastSubmission]);
useEffect(() => {
// Revalidate the form when input value is changed
var handleInput = event => {
// custom validate handler
var createValidateHandler = name => event => {
var field = event.target;
var form = ref.current;
var formConfig = configRef.current;
var {

@@ -85,26 +166,10 @@ initialReport = 'onSubmit',

shouldRevalidate = 'onInput'
} = formConfig;
if (!form || !isFieldElement(field) || field.form !== form) {
} = configRef.current;
if (!form || !isFocusableFormControl(field) || field.form !== form) {
return;
}
if (field.dataset.conformTouched ? shouldRevalidate === 'onInput' : shouldValidate === 'onInput') {
if (field.dataset.conformTouched ? shouldRevalidate === name : shouldValidate === name) {
requestIntent(form, validate(field.name));
}
};
var handleBlur = event => {
var field = event.target;
var form = ref.current;
var formConfig = configRef.current;
var {
initialReport = 'onSubmit',
shouldValidate = initialReport === 'onChange' ? 'onInput' : initialReport,
shouldRevalidate = 'onInput'
} = formConfig;
if (!form || !isFieldElement(field) || field.form !== form) {
return;
}
if (field.dataset.conformTouched ? shouldRevalidate === 'onBlur' : shouldValidate === 'onBlur') {
requestIntent(form, validate(field.name));
}
};
var handleInvalid = event => {

@@ -128,10 +193,10 @@ var form = ref.current;

// Reset all field state
for (var field of form.elements) {
if (isFieldElement(field)) {
delete field.dataset.conformTouched;
field.setCustomValidity('');
}
for (var element of getFormControls(form)) {
delete element.dataset.conformTouched;
element.setCustomValidity('');
}
setErrors([]);
};
var handleInput = createValidateHandler('onInput');
var handleBlur = createValidateHandler('onBlur');
document.addEventListener('input', handleInput, true);

@@ -147,3 +212,3 @@ document.addEventListener('blur', handleBlur, true);

};
}, [ref]);
}, [ref, configRef]);
var form = {

@@ -164,30 +229,28 @@ ref,

try {
var _config$onValidate;
var _config$onValidate, _config$onValidate2;
var formData = getFormData(form, submitter);
var getSubmission = (_config$onValidate = config.onValidate) !== null && _config$onValidate !== void 0 ? _config$onValidate : context => parse(context.formData);
var submission = getSubmission({
var submission = (_config$onValidate = (_config$onValidate2 = config.onValidate) === null || _config$onValidate2 === void 0 ? void 0 : _config$onValidate2.call(config, {
form,
formData
});
if (!config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate) && Object.entries(submission.error).some(_ref3 => {
var [, message] = _ref3;
return message !== '' && ![].concat(message).includes(VALIDATION_UNDEFINED);
}) || typeof config.onValidate !== 'undefined' && (submission.intent.startsWith('validate') || submission.intent.startsWith('list')) && Object.entries(submission.error).every(_ref4 => {
var [, message] = _ref4;
return ![].concat(message).includes(VALIDATION_UNDEFINED);
})) {
var listCommand = parseListCommand(submission.intent);
if (listCommand) {
form.dispatchEvent(new CustomEvent('conform/list', {
detail: submission.intent
}));
}
setLastSubmission(submission);
})) !== null && _config$onValidate !== void 0 ? _config$onValidate : parse(formData);
var messages = Object.entries(submission.error).reduce((messages, _ref2) => {
var [, message] = _ref2;
return messages.concat(normalizeError(message));
}, []);
var shouldValidate = !config.noValidate && !(submitter !== null && submitter !== void 0 && submitter.formNoValidate);
var shouldFallbackToServer = messages.includes(VALIDATION_UNDEFINED);
var hasClientValidation = typeof config.onValidate !== 'undefined';
var isValid = messages.length === 0;
if (hasClientValidation && (isSubmitting(submission.intent) ? shouldValidate && !isValid : !shouldFallbackToServer)) {
report(form, submission);
event.preventDefault();
} else {
var _config$onSubmit;
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, _objectSpread2({
(_config$onSubmit = config.onSubmit) === null || _config$onSubmit === void 0 ? void 0 : _config$onSubmit.call(config, event, {
formData,
submission
}, getFormAttributes(form, submitter)));
submission,
action: getFormAction(nativeEvent),
encType: getFormEncType(nativeEvent),
method: getFormMethod(nativeEvent)
});
}

@@ -217,64 +280,7 @@ } catch (e) {

function useFieldset(ref, config) {
var configRef = useRef(config);
var [error, setError] = useState(() => {
var initialError = config === null || config === void 0 ? void 0 : config.initialError;
if (!initialError) {
return {};
}
var result = {};
for (var [name, message] of Object.entries(initialError)) {
var [key, ...paths] = getPaths(name);
if (typeof key === 'string' && paths.length === 0) {
result[key] = [].concat(message !== null && message !== void 0 ? message : []);
}
}
return result;
var [error] = useFormError(ref, {
initialError: config.initialError,
name: config.name
});
useSafeLayoutEffect(() => {
configRef.current = config;
});
useEffect(() => {
var invalidHandler = event => {
var _configRef$current$na;
var form = getFormElement(ref.current);
var field = event.target;
var fieldsetName = (_configRef$current$na = configRef.current.name) !== null && _configRef$current$na !== void 0 ? _configRef$current$na : '';
if (!form || !isFieldElement(field) || field.form !== form || !field.name.startsWith(fieldsetName)) {
return;
}
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 (typeof key === 'string' && paths.length === 0) {
if (field.dataset.conformTouched) {
setError(prev => {
var prevMessage = getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[key]);
if (prevMessage === field.validationMessage) {
return prev;
}
return _objectSpread2(_objectSpread2({}, prev), {}, {
[key]: getErrors(field.validationMessage)
});
});
}
event.preventDefault();
}
};
var resetHandler = event => {
var form = getFormElement(ref.current);
if (!form || event.target !== form) {
return;
}
setError({});
};
// The invalid event does not bubble and so listening on the capturing pharse is needed
document.addEventListener('invalid', invalidHandler, true);
document.addEventListener('reset', resetHandler);
return () => {
document.removeEventListener('invalid', invalidHandler, true);
document.removeEventListener('reset', resetHandler);
};
}, [ref]);
/**

@@ -294,4 +300,4 @@ * This allows us constructing the field at runtime as we have no information

var errors = error === null || error === void 0 ? void 0 : error[key];
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref5) => {
var [name, message] = _ref5;
var initialError = Object.entries((_fieldsetConfig$initi = fieldsetConfig.initialError) !== null && _fieldsetConfig$initi !== void 0 ? _fieldsetConfig$initi : {}).reduce((result, _ref3) => {
var [name, message] = _ref3;
var [field, ...paths] = getPaths(name);

@@ -322,4 +328,3 @@ if (field === key) {

/**
* Returns a list of key and config, with a group of helpers
* configuring buttons for list manipulation
* Returns a list of key and field config.
*

@@ -329,13 +334,6 @@ * @see https://conform.guide/api/react#usefieldlist

function useFieldList(ref, config) {
var configRef = useRef(config);
var [error, setError] = useState(() => {
var initialError = [];
for (var [name, message] of Object.entries((_config$initialError = config === null || config === void 0 ? void 0 : config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {})) {
var _config$initialError;
var [index, ...paths] = getPaths(name);
if (typeof index === 'number' && paths.length === 0) {
initialError[index] = [].concat(message !== null && message !== void 0 ? message : []);
}
}
return initialError;
var configRef = useConfigRef(config);
var [error, setError] = useFormError(ref, {
initialError: config.initialError,
name: config.name
});

@@ -346,32 +344,5 @@ var [entries, setEntries] = useState(() => {

});
useSafeLayoutEffect(() => {
configRef.current = config;
});
useEffect(() => {
var invalidHandler = event => {
var _configRef$current$na2;
var conformHandler = event => {
var form = getFormElement(ref.current);
var field = event.target;
var prefix = (_configRef$current$na2 = configRef.current.name) !== null && _configRef$current$na2 !== void 0 ? _configRef$current$na2 : '';
if (!form || !isFieldElement(field) || field.form !== form || !field.name.startsWith(prefix)) {
return;
}
var [index, ...paths] = getPaths(prefix.length > 0 ? field.name.slice(prefix.length) : field.name);
// Update the error only if the field belongs to the fieldset
if (typeof index === 'number' && paths.length === 0) {
if (field.dataset.conformTouched) {
setError(prev => {
var prevMessage = getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[index]);
if (prevMessage === field.validationMessage) {
return prev;
}
return [...prev.slice(0, index), getErrors(field.validationMessage), ...prev.slice(index + 1)];
});
}
event.preventDefault();
}
};
var listHandler = event => {
var form = getFormElement(ref.current);
if (!form || event.target !== form) {

@@ -404,2 +375,8 @@ return;

setError(error => {
var errorList = [];
for (var [key, messages] of Object.entries(error)) {
if (typeof key === 'number') {
errorList[key] = messages;
}
}
switch (command.type) {

@@ -409,3 +386,3 @@ case 'append':

case 'replace':
return updateList([...error], _objectSpread2(_objectSpread2({}, command), {}, {
errorList = updateList(errorList, _objectSpread2(_objectSpread2({}, command), {}, {
payload: _objectSpread2(_objectSpread2({}, command.payload), {}, {

@@ -415,7 +392,10 @@ defaultValue: undefined

}));
break;
default:
{
return updateList([...error], command);
errorList = updateList(errorList, command);
break;
}
}
return Object.assign({}, errorList);
});

@@ -430,22 +410,19 @@ };

setEntries(Object.entries((_configRef$current$de = configRef.current.defaultValue) !== null && _configRef$current$de !== void 0 ? _configRef$current$de : [undefined]));
setError([]);
};
// @ts-expect-error Custom event: conform/list
document.addEventListener('conform/list', listHandler, true);
document.addEventListener('invalid', invalidHandler, true);
// @ts-expect-error Custom event: conform
document.addEventListener('conform', conformHandler, true);
document.addEventListener('reset', resetHandler);
return () => {
// @ts-expect-error Custom event: conform/list
document.removeEventListener('conform/list', listHandler, true);
document.removeEventListener('invalid', invalidHandler, true);
// @ts-expect-error Custom event: conform
document.removeEventListener('conform', conformHandler, true);
document.removeEventListener('reset', resetHandler);
};
}, [ref]);
return entries.map((_ref6, index) => {
var _config$initialError2, _config$defaultValue2;
var [key, defaultValue] = _ref6;
}, [ref, configRef, setError]);
return entries.map((_ref4, index) => {
var _config$initialError, _config$defaultValue2;
var [key, defaultValue] = _ref4;
var errors = error[index];
var initialError = Object.entries((_config$initialError2 = config.initialError) !== null && _config$initialError2 !== void 0 ? _config$initialError2 : {}).reduce((result, _ref7) => {
var [name, message] = _ref7;
var initialError = Object.entries((_config$initialError = config.initialError) !== null && _config$initialError !== void 0 ? _config$initialError : {}).reduce((result, _ref5) => {
var [name, message] = _ref5;
var [field, ...paths] = getPaths(name);

@@ -512,3 +489,3 @@ if (field === index) {

var ref = useRef(null);
var optionsRef = useRef(options);
var optionsRef = useConfigRef(options);
var changeDispatched = useRef(false);

@@ -518,5 +495,2 @@ var focusDispatched = useRef(false);

useSafeLayoutEffect(() => {
optionsRef.current = options;
});
useSafeLayoutEffect(() => {
var getInputElement = () => {

@@ -649,6 +623,149 @@ var _optionsRef$current$g, _optionsRef$current, _optionsRef$current$g2;

};
}, []);
}, [optionsRef]);
return [ref, control];
}
var VALIDATION_UNDEFINED = '__undefined__';
var VALIDATION_SKIPPED = '__skipped__';
var FORM_ERROR_ELEMENT_NAME = '__form__';
export { useFieldList, useFieldset, useForm, useInputEvent };
/**
* Validate the form with the Constraint Validation API
* @see https://conform.guide/api/react#validateconstraint
*/
function validateConstraint(options) {
var _options$formData, _options$formatMessag;
var formData = (_options$formData = options === null || options === void 0 ? void 0 : options.formData) !== null && _options$formData !== void 0 ? _options$formData : new FormData(options.form);
var getDefaultErrors = (validity, result) => {
var errors = [];
if (validity.valueMissing) errors.push('required');
if (validity.typeMismatch || validity.badInput) errors.push('type');
if (validity.tooShort) errors.push('minLength');
if (validity.rangeUnderflow) errors.push('min');
if (validity.stepMismatch) errors.push('step');
if (validity.tooLong) errors.push('maxLength');
if (validity.rangeOverflow) errors.push('max');
if (validity.patternMismatch) errors.push('pattern');
for (var [constraintName, valid] of Object.entries(result)) {
if (!valid) {
errors.push(constraintName);
}
}
return errors;
};
var formatMessages = (_options$formatMessag = options === null || options === void 0 ? void 0 : options.formatMessages) !== null && _options$formatMessag !== void 0 ? _options$formatMessag : _ref6 => {
var {
defaultErrors
} = _ref6;
return defaultErrors;
};
return parse(formData, {
resolve(payload, intent) {
var error = {};
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
var _loop = function _loop(element) {
if (isFieldElement(element)) {
var _options$acceptMultip, _options$acceptMultip2;
var name = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var constraint = Object.entries(element.dataset).reduce((result, _ref7) => {
var [name, attributeValue = ''] = _ref7;
if (constraintPattern.test(name)) {
var _options$constraint;
var constraintName = name.slice(10).toLowerCase();
var _validate = (_options$constraint = options.constraint) === null || _options$constraint === void 0 ? void 0 : _options$constraint[constraintName];
if (typeof _validate === 'function') {
result[constraintName] = _validate(element.value, {
formData,
attributeValue
});
} else {
console.warn("Found an \"".concat(constraintName, "\" constraint with undefined definition; Please specify it on the validateConstraint API."));
}
}
return result;
}, {});
var errors = formatMessages({
name,
validity: element.validity,
constraint,
defaultErrors: getDefaultErrors(element.validity, constraint)
});
var shouldAcceptMultipleErrors = (_options$acceptMultip = options === null || options === void 0 ? 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];
}
}
};
for (var element of options.form.elements) {
_loop(element);
}
return {
error
};
}
});
}
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 === '') {
continue;
}
// We can't use empty string as button name
// As `form.element.namedItem('')` will always returns null
var elementName = name ? name : FORM_ERROR_ELEMENT_NAME;
var item = form.elements.namedItem(elementName);
if (item instanceof RadioNodeList) {
for (var field of item) {
if (field.type !== 'radio') {
console.warn('Repeated field name is not supported.');
continue;
}
}
}
if (item === null) {
// Create placeholder button to keep the error without contributing to the form data
var button = document.createElement('button');
button.name = elementName;
button.hidden = true;
button.dataset.conformTouched = 'true';
form.appendChild(button);
}
}
var scope = getScope(submission.intent);
for (var element of getFormControls(form)) {
var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var messages = normalizeError(submission.error[_elementName]);
if (scope === null || scope === _elementName) {
element.dataset.conformTouched = 'true';
}
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(getValidationMessage(messages));
element.dispatchEvent(invalidEvent);
}
}
if (isSubmitting(submission.intent) || isFocusedOnIntentButton(form, submission.intent)) {
if (scope) {
focusFormControl(form, scope);
} else {
focusFirstInvalidControl(form);
}
}
}
/**
* Check if the current focus is on a intent button.
*/
function isFocusedOnIntentButton(form, intent) {
var element = document.activeElement;
return isFieldElement(element) && element.type === 'submit' && element.form === form && element.name === INTENT && element.value === intent;
}
export { FORM_ERROR_ELEMENT_NAME, VALIDATION_SKIPPED, VALIDATION_UNDEFINED, isFocusedOnIntentButton, reportSubmission, useFieldList, useFieldset, useForm, useInputEvent, validateConstraint };

@@ -1,4 +0,4 @@

export { isFieldElement, list, parse, requestIntent, validate, validateConstraint } from '@conform-to/dom';
export { useFieldList, useFieldset, useForm, useInputEvent } from './hooks.js';
export { isFieldElement, list, parse, requestIntent, validate } from '@conform-to/dom';
export { useFieldList, useFieldset, useForm, useInputEvent, validateConstraint } from './hooks.js';
import * as helpers from './helpers.js';
export { helpers as conform };

@@ -6,3 +6,3 @@ {

"license": "MIT",
"version": "0.6.1",
"version": "0.6.2",
"main": "index.js",

@@ -24,3 +24,3 @@ "module": "module/index.js",

"dependencies": {
"@conform-to/dom": "0.6.1"
"@conform-to/dom": "0.6.2"
},

@@ -27,0 +27,0 @@ "peerDependencies": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc