Socket
Socket
Sign inDemoInstall

@conform-to/react

Package Overview
Dependencies
4
Maintainers
1
Versions
63
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.6.0-pre.0 to 0.6.0

21

helpers.d.ts

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

import { type FieldConfig, VALIDATION_SKIPPED, VALIDATION_UNDEFINED } from '@conform-to/dom';
import { type FieldConfig, type Primitive, VALIDATION_UNDEFINED, VALIDATION_SKIPPED, INTENT } from '@conform-to/dom';
import type { CSSProperties, HTMLInputTypeAttribute } from 'react';
interface FieldProps {
interface FormControlProps {
id?: string;

@@ -11,7 +11,7 @@ name: string;

style?: CSSProperties;
'aria-invalid': boolean;
'aria-describedby'?: string;
'aria-invalid'?: boolean;
'aria-hidden'?: boolean;
}
interface InputProps<Schema> extends FieldProps {
interface InputProps<Schema> extends FormControlProps {
type?: HTMLInputTypeAttribute;

@@ -29,7 +29,7 @@ minLength?: number;

}
interface SelectProps extends FieldProps {
interface SelectProps extends FormControlProps {
defaultValue?: string | number | readonly string[] | undefined;
multiple?: boolean;
}
interface TextareaProps extends FieldProps {
interface TextareaProps extends FormControlProps {
minLength?: number;

@@ -51,10 +51,9 @@ maxLength?: number;

}): InputProps<Schema>;
export declare function input<Schema extends any>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
export declare function select<Schema>(config: FieldConfig<Schema>, options?: {
export declare function input<Schema extends Primitive>(config: FieldConfig<Schema>, options?: InputOptions): InputProps<Schema>;
export declare function select(config: FieldConfig<Primitive | Primitive[]>, options?: {
hidden?: boolean;
}): SelectProps;
export declare function textarea<Schema>(config: FieldConfig<Schema>, options?: {
export declare function textarea(config: FieldConfig<Primitive>, options?: {
hidden?: boolean;
}): TextareaProps;
export declare const intent = "__intent__";
export { VALIDATION_UNDEFINED, VALIDATION_SKIPPED };
export { INTENT, VALIDATION_UNDEFINED, VALIDATION_SKIPPED };

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

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

@@ -23,11 +24,31 @@

};
function getFormControlProps(config, options) {
var _config$error;
var props = {
id: config.id,
name: config.name,
form: config.form,
required: config.required
};
if (config.id) {
props.id = config.id;
props['aria-describedby'] = config.errorId;
}
if (config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length) {
props['aria-invalid'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
props.autoFocus = true;
}
if (options !== null && options !== void 0 && options.hidden) {
props.style = hiddenStyle;
props.tabIndex = -1;
props['aria-hidden'] = true;
}
return props;
}
function input(config) {
var _config$initialError;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var attributes = {
id: config.id,
var props = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getFormControlProps(config, options)), {}, {
type: options.type,
name: config.name,
form: config.form,
required: config.required,
minLength: config.minLength,

@@ -39,71 +60,33 @@ maxLength: config.maxLength,

pattern: config.pattern,
multiple: config.multiple,
'aria-invalid': Boolean((_config$initialError = config.initialError) === null || _config$initialError === void 0 ? void 0 : _config$initialError.length),
'aria-describedby': config.errorId
};
if (options !== null && options !== void 0 && options.hidden) {
attributes.style = hiddenStyle;
attributes.tabIndex = -1;
attributes['aria-hidden'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
attributes.autoFocus = true;
}
multiple: config.multiple
});
if (options.type === 'checkbox' || options.type === 'radio') {
var _options$value;
attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
attributes.defaultChecked = config.defaultValue === attributes.value;
props.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
props.defaultChecked = config.defaultValue === props.value;
} else if (options.type !== 'file') {
attributes.defaultValue = config.defaultValue;
props.defaultValue = config.defaultValue;
}
return attributes;
return props;
}
function select(config, options) {
var _config$defaultValue, _config$initialError2;
var attributes = {
id: config.id,
name: config.name,
form: config.form,
defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
required: config.required,
multiple: config.multiple,
'aria-invalid': Boolean((_config$initialError2 = config.initialError) === null || _config$initialError2 === void 0 ? void 0 : _config$initialError2.length),
'aria-describedby': config.errorId
};
if (options !== null && options !== void 0 && options.hidden) {
attributes.style = hiddenStyle;
attributes.tabIndex = -1;
attributes['aria-hidden'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
attributes.autoFocus = true;
}
return attributes;
var props = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getFormControlProps(config, options)), {}, {
defaultValue: config.defaultValue,
multiple: config.multiple
});
return props;
}
function textarea(config, options) {
var _config$defaultValue2, _config$initialError3;
var attributes = {
id: config.id,
name: config.name,
form: config.form,
defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
required: config.required,
var props = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, getFormControlProps(config, options)), {}, {
defaultValue: config.defaultValue,
minLength: config.minLength,
maxLength: config.maxLength,
autoFocus: Boolean(config.initialError),
'aria-invalid': Boolean((_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : _config$initialError3.length),
'aria-describedby': config.errorId
};
if (options !== null && options !== void 0 && options.hidden) {
attributes.style = hiddenStyle;
attributes.tabIndex = -1;
attributes['aria-hidden'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
attributes.autoFocus = true;
}
return attributes;
maxLength: config.maxLength
});
return props;
}
var intent = '__intent__';
Object.defineProperty(exports, 'INTENT', {
enumerable: true,
get: function () { return dom.INTENT; }
});
Object.defineProperty(exports, 'VALIDATION_SKIPPED', {

@@ -118,4 +101,3 @@ enumerable: true,

exports.input = input;
exports.intent = intent;
exports.select = select;
exports.textarea = textarea;

@@ -21,5 +21,5 @@ import { type FieldConfig, type FieldElement, type FieldValue, type FieldsetConstraint, type FormMethod, type FormEncType, type Submission } from '@conform-to/dom';

/**
* An object describing the state from the last submission
* An object describing the result of the last submission
*/
state?: Submission;
lastSubmission?: Submission;
/**

@@ -64,13 +64,16 @@ * An object describing the constraint of each field

interface FormProps {
id?: string;
ref: RefObject<HTMLFormElement>;
id?: string;
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
noValidate: boolean;
'aria-invalid'?: 'true';
'aria-describedby'?: string;
}
interface Form<Schema extends Record<string, any>> {
interface Form {
id?: string;
errorId?: string;
error: string;
errors: string[];
ref: RefObject<HTMLFormElement>;
error: string;
props: FormProps;
config: FieldsetConfig<Schema>;
}

@@ -83,16 +86,8 @@ /**

*/
export declare function useForm<Schema extends Record<string, any>, ClientSubmission extends Submission | Submission<Schema> = Submission>(config?: FormConfig<Schema, ClientSubmission>): [Form<Schema>, Fieldset<Schema>];
export declare function useForm<Schema extends Record<string, any>, ClientSubmission extends Submission | Submission<Schema> = Submission>(config?: FormConfig<Schema, ClientSubmission>): [Form, Fieldset<Schema>];
/**
* All the information of the field, including state and config.
* A set of field configuration
*/
export type Field<Schema> = {
config: FieldConfig<Schema>;
error?: string;
errors?: string[];
};
/**
* A set of field information.
*/
export type Fieldset<Schema extends Record<string, any>> = {
[Key in keyof Schema]-?: Field<Schema[Key]>;
[Key in keyof Schema]-?: FieldConfig<Schema[Key]>;
};

@@ -117,3 +112,3 @@ export interface FieldsetConfig<Schema extends Record<string, any>> {

/**
* The id of the form, connecting each field to a form remotely.
* The id of the form, connecting each field to a form remotely
*/

@@ -137,6 +132,3 @@ form?: string;

key: string;
error: string | undefined;
errors: string[] | undefined;
config: FieldConfig<Payload>;
}>;
} & FieldConfig<Payload>>;
interface InputControl {

@@ -143,0 +135,0 @@ change: (eventOrValue: {

@@ -16,16 +16,15 @@ 'use strict';

function useForm() {
var _config$state;
var _config$lastSubmissio;
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var configRef = react.useRef(config);
var ref = react.useRef(null);
var [lastSubmission, setLastSubmission] = react.useState((_config$state = config.state) !== null && _config$state !== void 0 ? _config$state : null);
var [error, setError] = react.useState(() => {
if (!config.state) {
return '';
var [lastSubmission, setLastSubmission] = react.useState((_config$lastSubmissio = config.lastSubmission) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : null);
var [errors, setErrors] = react.useState(() => {
if (!config.lastSubmission) {
return [];
}
var message = config.state.error[''];
return dom.getValidationMessage(message);
return [].concat(config.lastSubmission.error['']);
});
var [uncontrolledState, setUncontrolledState] = react.useState(() => {
var submission = config.state;
var submission = config.lastSubmission;
if (!submission) {

@@ -36,2 +35,3 @@ return {

}
var scope = dom.getScope(submission.intent);
return {

@@ -41,3 +41,3 @@ defaultValue: submission.payload,

var [name, message] = _ref;
if (name !== '' && dom.shouldValidate(submission.intent, name)) {
if (name !== '' && (scope === null || scope === name)) {
result[name] = message;

@@ -63,3 +63,3 @@ }

var form = ref.current;
var submission = config.state;
var submission = config.lastSubmission;
if (!form || !submission) {

@@ -75,3 +75,3 @@ return;

setLastSubmission(submission);
}, [config.state]);
}, [config.lastSubmission]);
react.useEffect(() => {

@@ -111,3 +111,3 @@ var form = ref.current;

var field = event.target;
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== '__form__') {
if (!form || !dom.isFieldElement(field) || field.form !== form || field.name !== dom.FORM_ERROR_ELEMENT_NAME) {
return;

@@ -117,3 +117,3 @@ }

if (field.dataset.conformTouched) {
setError(field.validationMessage);
setErrors(dom.getErrors(field.validationMessage));
}

@@ -132,7 +132,6 @@ };

delete field.dataset.conformTouched;
field.setAttribute('aria-invalid', 'false');
field.setCustomValidity('');
}
}
setError('');
setErrors([]);
setUncontrolledState({

@@ -161,8 +160,7 @@ defaultValue: formConfig.defaultValue

var form = {
id: config.id,
ref,
error,
error: errors[0],
errors,
props: {
ref,
id: config.id,
noValidate,

@@ -210,5 +208,13 @@ onSubmit(event) {

}
},
config: fieldsetConfig
}
};
if (config.id) {
form.id = config.id;
form.errorId = "".concat(config.id, "-error");
form.props.id = form.id;
form.props['aria-describedby'] = form.errorId;
}
if (form.errorId && form.errors.length > 0) {
form.props['aria-invalid'] = 'true';
}
return [form, fieldset];

@@ -218,3 +224,3 @@ }

/**
* All the information of the field, including state and config.
* A set of field configuration
*/

@@ -247,3 +253,4 @@

for (var [key, _error] of Object.entries(uncontrolledState.initialError)) {
result[key] = dom.getErrors(dom.getValidationMessage(_error === null || _error === void 0 ? void 0 : _error['']));
var _error$;
result[key] = [].concat((_error$ = _error === null || _error === void 0 ? void 0 : _error['']) !== null && _error$ !== void 0 ? _error$ : []);
}

@@ -269,6 +276,2 @@ return result;

if (field.dataset.conformTouched) {
// Update the aria attribute only if it is set
if (field.getAttribute('aria-invalid')) {
field.setAttribute('aria-invalid', field.validationMessage !== '' ? 'true' : 'false');
}
setError(prev => {

@@ -325,15 +328,13 @@ var prevMessage = dom.getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[key]);

var errors = error === null || error === void 0 ? void 0 : error[key];
var field = {
config: _rollupPluginBabelHelpers.objectSpread2({
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
defaultValue: uncontrolledState.defaultValue[key],
initialError: uncontrolledState.initialError[key]
}, constraint),
var field = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, constraint), {}, {
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
defaultValue: uncontrolledState.defaultValue[key],
initialError: uncontrolledState.initialError[key],
error: errors === null || errors === void 0 ? void 0 : errors[0],
errors
};
});
if (fieldsetConfig.form) {
field.config.form = fieldsetConfig.form;
field.config.id = "".concat(fieldsetConfig.form, "-").concat(field.config.name);
field.config.errorId = "".concat(field.config.id, "-error");
field.form = fieldsetConfig.form;
field.id = "".concat(fieldsetConfig.form, "-").concat(field.name);
field.errorId = "".concat(field.id, "-error");
}

@@ -371,3 +372,6 @@ return field;

});
var [error, setError] = react.useState(() => uncontrolledState.initialError.map(error => dom.getErrors(dom.getValidationMessage(error === null || error === void 0 ? void 0 : error['']))));
var [error, setError] = react.useState(() => uncontrolledState.initialError.map(error => {
var _error$2;
return [].concat((_error$2 = error === null || error === void 0 ? void 0 : error['']) !== null && _error$2 !== void 0 ? _error$2 : []);
}));
var [entries, setEntries] = react.useState(() => {

@@ -482,3 +486,5 @@ var _config$defaultValue3;

defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index],
initialError: uncontrolledState.initialError[index]
initialError: uncontrolledState.initialError[index],
error: errors === null || errors === void 0 ? void 0 : errors[0],
errors
};

@@ -490,8 +496,5 @@ if (config.form) {

}
return {
key,
error: errors === null || errors === void 0 ? void 0 : errors[0],
errors,
config: fieldConfig
};
return _rollupPluginBabelHelpers.objectSpread2({
key
}, fieldConfig);
});

@@ -498,0 +501,0 @@ }

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

export { type FieldConfig, type FieldsetConstraint, type Submission, getFormElements, list, validate, requestIntent, requestSubmit, parse, validateConstraint, } from '@conform-to/dom';
export * from './hooks';
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 * as conform from './helpers';

@@ -11,5 +11,5 @@ 'use strict';

Object.defineProperty(exports, 'getFormElements', {
Object.defineProperty(exports, 'isFieldElement', {
enumerable: true,
get: function () { return dom.getFormElements; }
get: function () { return dom.isFieldElement; }
});

@@ -28,6 +28,2 @@ Object.defineProperty(exports, 'list', {

});
Object.defineProperty(exports, 'requestSubmit', {
enumerable: true,
get: function () { return dom.requestSubmit; }
});
Object.defineProperty(exports, 'validate', {

@@ -34,0 +30,0 @@ enumerable: true,

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

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

@@ -18,11 +19,31 @@ /**

};
function getFormControlProps(config, options) {
var _config$error;
var props = {
id: config.id,
name: config.name,
form: config.form,
required: config.required
};
if (config.id) {
props.id = config.id;
props['aria-describedby'] = config.errorId;
}
if (config.errorId && (_config$error = config.error) !== null && _config$error !== void 0 && _config$error.length) {
props['aria-invalid'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
props.autoFocus = true;
}
if (options !== null && options !== void 0 && options.hidden) {
props.style = hiddenStyle;
props.tabIndex = -1;
props['aria-hidden'] = true;
}
return props;
}
function input(config) {
var _config$initialError;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var attributes = {
id: config.id,
var props = _objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
type: options.type,
name: config.name,
form: config.form,
required: config.required,
minLength: config.minLength,

@@ -34,71 +55,29 @@ maxLength: config.maxLength,

pattern: config.pattern,
multiple: config.multiple,
'aria-invalid': Boolean((_config$initialError = config.initialError) === null || _config$initialError === void 0 ? void 0 : _config$initialError.length),
'aria-describedby': config.errorId
};
if (options !== null && options !== void 0 && options.hidden) {
attributes.style = hiddenStyle;
attributes.tabIndex = -1;
attributes['aria-hidden'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
attributes.autoFocus = true;
}
multiple: config.multiple
});
if (options.type === 'checkbox' || options.type === 'radio') {
var _options$value;
attributes.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
attributes.defaultChecked = config.defaultValue === attributes.value;
props.value = (_options$value = options.value) !== null && _options$value !== void 0 ? _options$value : 'on';
props.defaultChecked = config.defaultValue === props.value;
} else if (options.type !== 'file') {
attributes.defaultValue = config.defaultValue;
props.defaultValue = config.defaultValue;
}
return attributes;
return props;
}
function select(config, options) {
var _config$defaultValue, _config$initialError2;
var attributes = {
id: config.id,
name: config.name,
form: config.form,
defaultValue: config.multiple ? Array.isArray(config.defaultValue) ? config.defaultValue : [] : "".concat((_config$defaultValue = config.defaultValue) !== null && _config$defaultValue !== void 0 ? _config$defaultValue : ''),
required: config.required,
multiple: config.multiple,
'aria-invalid': Boolean((_config$initialError2 = config.initialError) === null || _config$initialError2 === void 0 ? void 0 : _config$initialError2.length),
'aria-describedby': config.errorId
};
if (options !== null && options !== void 0 && options.hidden) {
attributes.style = hiddenStyle;
attributes.tabIndex = -1;
attributes['aria-hidden'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
attributes.autoFocus = true;
}
return attributes;
var props = _objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
defaultValue: config.defaultValue,
multiple: config.multiple
});
return props;
}
function textarea(config, options) {
var _config$defaultValue2, _config$initialError3;
var attributes = {
id: config.id,
name: config.name,
form: config.form,
defaultValue: "".concat((_config$defaultValue2 = config.defaultValue) !== null && _config$defaultValue2 !== void 0 ? _config$defaultValue2 : ''),
required: config.required,
var props = _objectSpread2(_objectSpread2({}, getFormControlProps(config, options)), {}, {
defaultValue: config.defaultValue,
minLength: config.minLength,
maxLength: config.maxLength,
autoFocus: Boolean(config.initialError),
'aria-invalid': Boolean((_config$initialError3 = config.initialError) === null || _config$initialError3 === void 0 ? void 0 : _config$initialError3.length),
'aria-describedby': config.errorId
};
if (options !== null && options !== void 0 && options.hidden) {
attributes.style = hiddenStyle;
attributes.tabIndex = -1;
attributes['aria-hidden'] = true;
}
if (config.initialError && Object.entries(config.initialError).length > 0) {
attributes.autoFocus = true;
}
return attributes;
maxLength: config.maxLength
});
return props;
}
var intent = '__intent__';
export { input, intent, select, textarea };
export { input, select, textarea };
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
import { getValidationMessage, shouldValidate, parseListCommand, reportSubmission, getFormData, parse, VALIDATION_UNDEFINED, getFormAttributes, getPaths, getName, getErrors, isFieldElement, requestIntent, validate, getFormElement, updateList } from '@conform-to/dom';
import { getScope, parseListCommand, reportSubmission, getFormData, parse, VALIDATION_UNDEFINED, getFormAttributes, getPaths, getName, isFieldElement, requestIntent, validate, getFormElement, FORM_ERROR_ELEMENT_NAME, getErrors, getValidationMessage, updateList } from '@conform-to/dom';
import { useRef, useState, useEffect, useMemo, useLayoutEffect } from 'react';

@@ -12,16 +12,15 @@

function useForm() {
var _config$state;
var _config$lastSubmissio;
var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var configRef = useRef(config);
var ref = useRef(null);
var [lastSubmission, setLastSubmission] = useState((_config$state = config.state) !== null && _config$state !== void 0 ? _config$state : null);
var [error, setError] = useState(() => {
if (!config.state) {
return '';
var [lastSubmission, setLastSubmission] = useState((_config$lastSubmissio = config.lastSubmission) !== null && _config$lastSubmissio !== void 0 ? _config$lastSubmissio : null);
var [errors, setErrors] = useState(() => {
if (!config.lastSubmission) {
return [];
}
var message = config.state.error[''];
return getValidationMessage(message);
return [].concat(config.lastSubmission.error['']);
});
var [uncontrolledState, setUncontrolledState] = useState(() => {
var submission = config.state;
var submission = config.lastSubmission;
if (!submission) {

@@ -32,2 +31,3 @@ return {

}
var scope = getScope(submission.intent);
return {

@@ -37,3 +37,3 @@ defaultValue: submission.payload,

var [name, message] = _ref;
if (name !== '' && shouldValidate(submission.intent, name)) {
if (name !== '' && (scope === null || scope === name)) {
result[name] = message;

@@ -59,3 +59,3 @@ }

var form = ref.current;
var submission = config.state;
var submission = config.lastSubmission;
if (!form || !submission) {

@@ -71,3 +71,3 @@ return;

setLastSubmission(submission);
}, [config.state]);
}, [config.lastSubmission]);
useEffect(() => {

@@ -107,3 +107,3 @@ var form = ref.current;

var field = event.target;
if (!form || !isFieldElement(field) || field.form !== form || field.name !== '__form__') {
if (!form || !isFieldElement(field) || field.form !== form || field.name !== FORM_ERROR_ELEMENT_NAME) {
return;

@@ -113,3 +113,3 @@ }

if (field.dataset.conformTouched) {
setError(field.validationMessage);
setErrors(getErrors(field.validationMessage));
}

@@ -128,7 +128,6 @@ };

delete field.dataset.conformTouched;
field.setAttribute('aria-invalid', 'false');
field.setCustomValidity('');
}
}
setError('');
setErrors([]);
setUncontrolledState({

@@ -157,8 +156,7 @@ defaultValue: formConfig.defaultValue

var form = {
id: config.id,
ref,
error,
error: errors[0],
errors,
props: {
ref,
id: config.id,
noValidate,

@@ -206,5 +204,13 @@ onSubmit(event) {

}
},
config: fieldsetConfig
}
};
if (config.id) {
form.id = config.id;
form.errorId = "".concat(config.id, "-error");
form.props.id = form.id;
form.props['aria-describedby'] = form.errorId;
}
if (form.errorId && form.errors.length > 0) {
form.props['aria-invalid'] = 'true';
}
return [form, fieldset];

@@ -214,3 +220,3 @@ }

/**
* All the information of the field, including state and config.
* A set of field configuration
*/

@@ -243,3 +249,4 @@

for (var [key, _error] of Object.entries(uncontrolledState.initialError)) {
result[key] = getErrors(getValidationMessage(_error === null || _error === void 0 ? void 0 : _error['']));
var _error$;
result[key] = [].concat((_error$ = _error === null || _error === void 0 ? void 0 : _error['']) !== null && _error$ !== void 0 ? _error$ : []);
}

@@ -265,6 +272,2 @@ return result;

if (field.dataset.conformTouched) {
// Update the aria attribute only if it is set
if (field.getAttribute('aria-invalid')) {
field.setAttribute('aria-invalid', field.validationMessage !== '' ? 'true' : 'false');
}
setError(prev => {

@@ -321,15 +324,13 @@ var prevMessage = getValidationMessage(prev === null || prev === void 0 ? void 0 : prev[key]);

var errors = error === null || error === void 0 ? void 0 : error[key];
var field = {
config: _objectSpread2({
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
defaultValue: uncontrolledState.defaultValue[key],
initialError: uncontrolledState.initialError[key]
}, constraint),
var field = _objectSpread2(_objectSpread2({}, constraint), {}, {
name: fieldsetConfig.name ? "".concat(fieldsetConfig.name, ".").concat(key) : key,
defaultValue: uncontrolledState.defaultValue[key],
initialError: uncontrolledState.initialError[key],
error: errors === null || errors === void 0 ? void 0 : errors[0],
errors
};
});
if (fieldsetConfig.form) {
field.config.form = fieldsetConfig.form;
field.config.id = "".concat(fieldsetConfig.form, "-").concat(field.config.name);
field.config.errorId = "".concat(field.config.id, "-error");
field.form = fieldsetConfig.form;
field.id = "".concat(fieldsetConfig.form, "-").concat(field.name);
field.errorId = "".concat(field.id, "-error");
}

@@ -367,3 +368,6 @@ return field;

});
var [error, setError] = useState(() => uncontrolledState.initialError.map(error => getErrors(getValidationMessage(error === null || error === void 0 ? void 0 : error['']))));
var [error, setError] = useState(() => uncontrolledState.initialError.map(error => {
var _error$2;
return [].concat((_error$2 = error === null || error === void 0 ? void 0 : error['']) !== null && _error$2 !== void 0 ? _error$2 : []);
}));
var [entries, setEntries] = useState(() => {

@@ -478,3 +482,5 @@ var _config$defaultValue3;

defaultValue: defaultValue !== null && defaultValue !== void 0 ? defaultValue : uncontrolledState.defaultValue[index],
initialError: uncontrolledState.initialError[index]
initialError: uncontrolledState.initialError[index],
error: errors === null || errors === void 0 ? void 0 : errors[0],
errors
};

@@ -486,8 +492,5 @@ if (config.form) {

}
return {
key,
error: errors === null || errors === void 0 ? void 0 : errors[0],
errors,
config: fieldConfig
};
return _objectSpread2({
key
}, fieldConfig);
});

@@ -494,0 +497,0 @@ }

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

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

@@ -5,3 +5,3 @@ {

"license": "MIT",
"version": "0.6.0-pre.0",
"version": "0.6.0",
"main": "index.js",

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

"dependencies": {
"@conform-to/dom": "0.6.0-pre.0"
"@conform-to/dom": "0.6.0"
},

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

@@ -14,9 +14,8 @@ # @conform-to/react

- [conform](#conform)
- [parse](#parse)
- [validateConstraint](#validateconstraint)
- [list](#list)
- [validate](#validate)
- [requestIntent](#requestintent)
- [getFormElements](#getformelements)
- [hasError](#haserror)
- [parse](#parse)
- [shouldValidate](#shouldvalidate)
- [isFieldElement](#isfieldelement)

@@ -31,5 +30,5 @@ <!-- /aside -->

- Enabling customizing form validation behaviour.
- Capturing the error message and removes the error bubbles.
- Preparing all properties required to configure the dom elements.
- Enabling customizing validation logic.
- Capturing error message and removes the error bubbles.
- Preparing all properties required to configure the form elements.

@@ -58,8 +57,8 @@ ```tsx

*/
defaultValue: undefined;
defaultValue: undefined,
/**
* An object describing the state from the last submission
* The last submission result from the server
*/
state: undefined;
lastSubmission: undefined,

@@ -69,3 +68,3 @@ /**

*/
constraint: undefined;
constraint: undefined,

@@ -97,3 +96,3 @@ /**

*/
onSubmit(event, { formData, submission }) {
onSubmit(event, { formData, submission, action, encType, method }) {
// ...

@@ -173,3 +172,3 @@ },

form.ref,
address.config,
address,
);

@@ -181,9 +180,9 @@

<legned>Address</legend>
<input {...conform.input(street.config)} />
<input {...conform.input(street)} />
<div>{street.error}</div>
<input {...conform.input(zipcode.config)} />
<input {...conform.input(zipcode)} />
<div>{zipcode.error}</div>
<input {...conform.input(city.config)} />
<input {...conform.input(city)} />
<div>{city.error}</div>
<input {...conform.input(country.config)} />
<input {...conform.input(country)} />
<div>{country.error}</div>

@@ -214,3 +213,3 @@ </fieldset>

**conform** utilises the DOM as its context provider / input registry, which maintains a link between each input / button / fieldset with the form through the [form property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#properties). The ref object allows it to restrict the scope to elements associated to the same form only.
**conform** utilises the DOM as its context provider / input registry, which maintains a link between each input / button / fieldset with the form through the [form property](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#properties). The ref object allows it to restrict the scope to form elements associated to the same form only.

@@ -242,3 +241,3 @@ ```tsx

This hook enables you to work with [array](/docs/configuration.md#array) and support [list](#list) command button builder to modify a list. It can also be used with [useFieldset](#usefieldset) for [nested list](/docs/configuration.md#nested-list) at the same time.
This hook enables you to work with [array](/docs/configuration.md#array) and support the [list](#list) intent button builder to modify a list. It can also be used with [useFieldset](#usefieldset) for [nested list](/docs/configuration.md#nested-list) at the same time.

@@ -257,10 +256,10 @@ ```tsx

const [form, { items }] = useForm<Schema>();
const list = useFieldList(form.ref, items.config);
const itemsList = useFieldList(form.ref, items);
return (
<fieldset ref={ref}>
{list.map((item, index) => (
{itemsList.map((item, index) => (
<div key={item.key}>
{/* Setup an input per item */}
<input {...conform.input(item.config)} />
<input {...conform.input(item)} />

@@ -271,3 +270,3 @@ {/* Error of each item */}

{/* Setup a delete button (Note: It is `items` not `item`) */}
<button {...list.remove(items.config.name, { index })}>Delete</button>
<button {...list.remove(items.name, { index })}>Delete</button>
</div>

@@ -277,5 +276,3 @@ ))}

{/* Setup a button that can append a new row with optional default value */}
<button {...list.append(items.config.name, { defaultValue: '' })}>
add
</button>
<button {...list.append(items.name, { defaultValue: '' })}>add</button>
</fieldset>

@@ -299,5 +296,5 @@ );

const [form, { category }] = useForm();
const [value, setValue] = useState(category.config.defaultValue ?? '');
const [value, setValue] = useState(category.defaultValue ?? '');
const [ref, control] = useInputEvent({
onReset: () => setValue(category.config.defaultValue ?? ''),
onReset: () => setValue(category.defaultValue ?? ''),
});

@@ -311,3 +308,3 @@ const inputRef = useRef<HTMLInputElement>(null);

ref={ref}
{...conform.input(category.config, { hidden: true })}
{...conform.input(category, { hidden: true })}
onChange={(e) => setValue(e.target.value)}

@@ -340,5 +337,5 @@ onFocus={() => inputRef.current?.focus()}

It provides several helpers to remove the boilerplate when configuring a form control.
It provides several helpers to remove the boilerplate when configuring a form control and derives attributes for [accessibility](/docs/accessibility.md#configuration) concerns and helps [focus management](/docs/focus-management.md#focusing-before-javascript-is-loaded).
You are recommended to create a wrapper on top if you need to integrate with custom input component. As the helper derives attributes for [accessibility](/docs/accessibility.md#configuration) concerns and helps [focus management](/docs/focus-management.md#focusing-before-javascript-is-loaded).
You can also create a wrapper on top if you need to integrate with custom input component.

@@ -357,27 +354,27 @@ Before:

type="text"
name={title.config.name}
form={title.config.form}
defaultValue={title.config.defaultValue}
requried={title.config.required}
minLength={title.config.minLength}
maxLength={title.config.maxLength}
min={title.config.min}
max={title.config.max}
multiple={title.config.multiple}
pattern={title.config.pattern}
name={title.name}
form={title.form}
defaultValue={title.defaultValue}
requried={title.required}
minLength={title.minLength}
maxLength={title.maxLength}
min={title.min}
max={title.max}
multiple={title.multiple}
pattern={title.pattern}
/>
<textarea
name={description.config.name}
form={description.config.form}
defaultValue={description.config.defaultValue}
requried={description.config.required}
minLength={description.config.minLength}
maxLength={description.config.maxLength}
name={description.name}
form={description.form}
defaultValue={description.defaultValue}
requried={description.required}
minLength={description.minLength}
maxLength={description.maxLength}
/>
<select
name={category.config.name}
form={category.config.form}
defaultValue={category.config.defaultValue}
requried={category.config.required}
multiple={category.config.multiple}
name={category.name}
form={category.form}
defaultValue={category.defaultValue}
requried={category.required}
multiple={category.multiple}
>

@@ -401,5 +398,5 @@ {/* ... */}

<form {...form.props}>
<input {...conform.input(title.config, { type: 'text' })} />
<textarea {...conform.textarea(description.config)} />
<select {...conform.select(category.config)}>{/* ... */}</select>
<input {...conform.input(title, { type: 'text' })} />
<textarea {...conform.textarea(description)} />
<select {...conform.select(category)}>{/* ... */}</select>
</form>

@@ -412,5 +409,113 @@ );

### parse
It parses the formData based on the [naming convention](/docs/configuration.md#naming-convention) with the validation result from the resolver.
```tsx
import { parse } from '@conform-to/react';
const formData = new FormData();
const submission = parse(formData, {
resolve({ email, password }) {
const error: Record<string, string> = {};
if (typeof email !== 'string') {
error.email = 'Email is required';
} else if (!/^[^@]+@[^@]+$/.test(email)) {
error.email = 'Email is invalid';
}
if (typeof password !== 'string') {
error.password = 'Password is required';
}
if (error.email || error.password) {
return { error };
}
return {
value: { email, password },
};
},
});
```
---
### validateConstraint
This enable Constraint Validation with ability to enable custom constraint using data-attribute and customizing error messages. By default, the error message would be the attribute that triggered the error (e.g. `required` / `type` / 'minLength' etc).
```tsx
import { useForm, validateConstraint } from '@conform-to/react';
import { Form } from 'react-router-dom';
export default function SignupForm() {
const [form, { email, password, confirmPassword }] = useForm({
onValidate(context) {
// This enables validating each field based on the validity state and custom cosntraint if defined
return validateConstraint(
...context,
constraint: {
// Define custom constraint
match(value, { formData, attributeValue }) {
// Check if the value of the field match the value of another field
return value === formData.get(attributeValue);
},
});
}
});
return (
<Form method="post" {...form.props}>
<div>
<label>Email</label>
<input
name="email"
type="email"
required
pattern="[^@]+@[^@]+\\.[^@]+"
/>
{email.error === 'required' ? (
<div>Email is required</div>
) : email.error === 'type' ? (
<div>Email is invalid</div>
) : null}
</div>
<div>
<label>Password</label>
<input
name="password"
type="password"
required
/>
{password.error === 'required' ? (
<div>Password is required</div>
) : null}
</div>
<div>
<label>Confirm Password</label>
<input
name="confirmPassword"
type="password"
required
data-constraint-match="password"
/>
{confirmPassword.error === 'required' ? (
<div>Confirm Password is required</div>
) : confirmPassword.error === 'match' ? (
<div>Password does not match</div>
) : null}
</div>
<button>Signup</button>
</Form>
);
}
```
---
### list
It provides serveral helpers to configure a command button for [modifying a list](/docs/commands.md#modifying-a-list).
It provides serveral helpers to configure an intent button for [modifying a list](/docs/commands.md#modifying-a-list).

@@ -448,3 +553,3 @@ ```tsx

It returns the properties required to configure a command button for [validation](/docs/commands.md#validation).
It returns the properties required to configure an intent button for [validation](/docs/commands.md#validation).

@@ -485,3 +590,3 @@ ```tsx

const [form, { tasks }] = useForm();
const taskList = useFieldList(form.ref, tasks.config);
const taskList = useFieldList(form.ref, tasks);

@@ -496,3 +601,3 @@ const handleDrop = (from, to) =>

<div key={task.key}>
<input {...conform.input(task.config)} />
<input {...conform.input(task)} />
</div>

@@ -509,79 +614,20 @@ ))}

### getFormElements
### isFieldElement
It returns all _input_ / _select_ / _textarea_ or _button_ in the forms. Useful when looping through the form elements to validate each field manually.
This is an utility for checking if the provided element is a form element (_input_ / _select_ / _textarea_ or _button_) which also works as a type guard.
```tsx
import { useForm, parse, getFormElements } from '@conform-to/react';
export default function LoginForm() {
const [form] = useForm({
onValidate({ form, formData }) {
const submission = parse(formData);
for (const element of getFormElements(form)) {
switch (element.name) {
case 'email': {
if (element.validity.valueMissing) {
submission.error.push([element.name, 'Email is required']);
} else if (element.validity.typeMismatch) {
submission.error.push([element.name, 'Email is invalid']);
}
break;
}
case 'password': {
if (element.validity.valueMissing) {
submission.error.push([element.name, 'Password is required']);
}
break;
}
function Example() {
return (
<form
onFocus={(event) => {
if (isFieldElement(event.target)) {
// event.target is now considered one of the form elements type
}
}
return submission;
},
// ....
});
// ...
}}
>
{/* ... */}
</form>
);
}
```
---
### parse
It parses the formData based on the [naming convention](/docs/submission).
```tsx
import { parse } from '@conform-to/react';
const formData = new FormData();
const submission = parse(formData);
console.log(submission);
```
---
### shouldValidate
This helper checks if the scope of validation includes a specific field by checking the submission:
```tsx
import { shouldValidate } from '@conform-to/react';
/**
* The submission intent give us hint on what should be valdiated.
* If the intent is 'validate/:field', only the field with name matching must be validated.
* If the intent is undefined, everything should be validated (Default submission)
*/
const intent = 'validate/email';
// This will log 'true'
console.log(shouldValidate(intent, 'email'));
// This will log 'false'
console.log(shouldValidate(intent, 'password'));
```
SocketSocket SOC 2 Logo

Product

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

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc