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

@conform-to/dom

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/dom - npm Package Compare versions

Comparing version 0.6.0-pre.0 to 0.6.0

35

index.d.ts

@@ -10,2 +10,10 @@ export type Primitive = null | undefined | string | number | boolean | Date;

errorId?: string;
/**
* The frist error of the field
*/
error?: string;
/**
* All of the field errors
*/
errors?: string[];
}

@@ -40,8 +48,15 @@ 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> ? {

export interface IntentButtonProps {
name: '__intent__';
name: typeof INTENT;
value: string;
formNoValidate?: boolean;
}
/**
* Check if the provided reference is a form element (_input_ / _select_ / _textarea_ or _button_)
*/
export declare function isFieldElement(element: unknown): element is FieldElement;
export declare function getFormElements(form: HTMLFormElement): FieldElement[];
/**
* Find the corresponding paths based on the formatted name
* @param name formatted name
* @returns paths
*/
export declare function getPaths(name: string): Array<string | number>;

@@ -57,5 +72,8 @@ export declare function getFormData(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): FormData;

export declare function getName(paths: Array<string | number>): string;
export declare function shouldValidate(intent: string, name: string): boolean;
export declare function getScope(intent: string): string | null;
export declare function isFocusedOnIntentButton(form: HTMLFormElement, intent: string): boolean;
export declare function getValidationMessage(errors?: string | string[]): string;
export declare function getErrors(message: string | undefined): string[];
export declare const FORM_ERROR_ELEMENT_NAME = "__form__";
export declare const INTENT = "__intent__";
export declare const VALIDATION_UNDEFINED = "__undefined__";

@@ -66,8 +84,2 @@ export declare const VALIDATION_SKIPPED = "__skipped__";

/**
* The ponyfill of `HTMLFormElement.requestSubmit()`
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
* @see https://caniuse.com/?search=requestSubmit
*/
export declare function requestSubmit(form: HTMLFormElement, submitter?: HTMLButtonElement | HTMLInputElement): void;
/**
* Creates an intent button on demand and trigger a form submit by clicking it.

@@ -80,3 +92,3 @@ */

/**
* Returns the properties required to configure a command button for validation
* Returns the properties required to configure an intent button for validation
*

@@ -87,3 +99,2 @@ * @see https://conform.guide/api/react#validate

export declare function getFormElement(element: HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null): HTMLFormElement | null;
export declare function focus(field: FieldElement): void;
export declare function parse(payload: FormData | URLSearchParams): Submission;

@@ -170,3 +181,3 @@ export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {

/**
* Helpers to configure a command button for modifying a list
* Helpers to configure an intent button for modifying a list
*

@@ -173,0 +184,0 @@ * @see https://conform.guide/api/react#list

@@ -7,23 +7,14 @@ 'use strict';

// type Join<K, P> = P extends string | number ?
// K extends string | number ?
// `${K}${"" extends P ? "" : "."}${P}`
// : never : never;
// type DottedPaths<T> = T extends object ?
// { [K in keyof T]-?: K extends string | number ?
// `${K}` | Join<K, DottedPaths<T[K]>>
// : never
// }[keyof T] : ""
// type Pathfix<T> = T extends `${infer Prefix}.${number}${infer Postfix}` ? `${Prefix}[${number}]${Pathfix<Postfix>}` : T;
// type Path<Schema> = Pathfix<DottedPaths<Schema>> | '';
/**
* Check if the provided reference is a form element (_input_ / _select_ / _textarea_ or _button_)
*/
function isFieldElement(element) {
return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
}
function getFormElements(form) {
return Array.from(form.elements).filter(isFieldElement);
}
/**
* Find the corresponding paths based on the formatted name
* @param name formatted name
* @returns paths
*/
function getPaths(name) {

@@ -75,14 +66,18 @@ var pattern = /(\w*)\[(\d+)\]/;

}
function shouldValidate(intent, name) {
var _parseListCommand;
var [type] = intent.split('/', 1);
function getScope(intent) {
var _parseListCommand$sco, _parseListCommand;
var [type, ...rest] = intent.split('/');
switch (type) {
case 'validate':
return intent === 'validate' || intent === "validate/".concat(name);
return rest.length > 0 ? rest.join('/') : null;
case 'list':
return ((_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) === name;
return (_parseListCommand$sco = (_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) !== null && _parseListCommand$sco !== void 0 ? _parseListCommand$sco : null;
default:
return true;
return null;
}
}
function isFocusedOnIntentButton(form, intent) {
var element = document.activeElement;
return isFieldElement(element) && element.tagName === 'BUTTON' && element.form === form && element.name === INTENT && element.value === intent;
}
function getValidationMessage(errors) {

@@ -97,2 +92,4 @@ return [].concat(errors !== null && errors !== void 0 ? errors : []).join(String.fromCharCode(31));

}
var FORM_ERROR_ELEMENT_NAME = '__form__';
var INTENT = '__intent__';
var VALIDATION_UNDEFINED = '__undefined__';

@@ -109,3 +106,3 @@ var VALIDATION_SKIPPED = '__skipped__';

// As `form.element.namedItem('')` will always returns null
var elementName = _name ? _name : '__form__';
var elementName = _name ? _name : FORM_ERROR_ELEMENT_NAME;
var item = form.elements.namedItem(elementName);

@@ -129,19 +126,24 @@ if (item instanceof RadioNodeList) {

}
var focusedFirstInvalidField = false;
var scope = getScope(submission.intent);
var isSubmitting = submission.intent.slice(0, submission.intent.indexOf('/')) !== 'validate' && parseListCommand(submission.intent) === null;
for (var element of form.elements) {
if (isFieldElement(element) && element.willValidate) {
var _elementName = element.name !== '__form__' ? element.name : '';
var _message = submission.error[_elementName];
var elementShouldValidate = shouldValidate(submission.intent, _elementName);
if (elementShouldValidate) {
var _submission$error$_el;
var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var messages = [].concat((_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : []);
var shouldValidate = scope === null || scope === _elementName;
if (shouldValidate) {
element.dataset.conformTouched = 'true';
}
if (typeof _message === 'undefined' || ![].concat(_message).includes(VALIDATION_SKIPPED)) {
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(getValidationMessage(_message));
element.setCustomValidity(getValidationMessage(messages));
element.dispatchEvent(invalidEvent);
}
if (elementShouldValidate && !element.validity.valid) {
focus(element);
if (!focusedFirstInvalidField && (isSubmitting || isFocusedOnIntentButton(form, submission.intent)) && shouldValidate && element.tagName !== 'BUTTON' && !element.validity.valid) {
element.focus();
focusedFirstInvalidField = true;
}

@@ -167,16 +169,2 @@ }

/**
* The ponyfill of `HTMLFormElement.requestSubmit()`
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
* @see https://caniuse.com/?search=requestSubmit
*/
function requestSubmit(form, submitter) {
var submitEvent = new SubmitEvent('submit', {
bubbles: true,
cancelable: true,
submitter
});
form.dispatchEvent(submitEvent);
}
/**
* Creates an intent button on demand and trigger a form submit by clicking it.

@@ -190,3 +178,3 @@ */

var button = document.createElement('button');
button.name = '__intent__';
button.name = INTENT;
button.value = buttonProps.value;

@@ -203,3 +191,3 @@ button.hidden = true;

/**
* Returns the properties required to configure a command button for validation
* Returns the properties required to configure an intent button for validation
*

@@ -210,3 +198,3 @@ * @see https://conform.guide/api/react#validate

return {
name: '__intent__',
name: INTENT,
value: field ? "validate/".concat(field) : 'validate',

@@ -223,9 +211,2 @@ formNoValidate: true

}
function focus(field) {
var currentFocus = document.activeElement;
if (!isFieldElement(currentFocus) || currentFocus.tagName !== 'BUTTON' || currentFocus.form !== field.form) {
return;
}
field.focus();
}
function parse(payload, options) {

@@ -238,3 +219,3 @@ var submission = {

var _loop = function _loop(_value) {
if (_name2 === '__intent__') {
if (_name2 === INTENT) {
if (typeof _value !== 'string' || submission.intent !== 'submit') {

@@ -337,3 +318,3 @@ throw new Error('The intent could only be set on a button');

/**
* Helpers to configure a command button for modifying a list
* Helpers to configure an intent button for modifying a list
*

@@ -353,3 +334,3 @@ * @see https://conform.guide/api/react#list

return {
name: '__intent__',
name: INTENT,
value: "list/".concat(type, "/").concat(scope, "/").concat(JSON.stringify(payload)),

@@ -400,3 +381,3 @@ formNoValidate: true

var _options$acceptMultip, _options$acceptMultip2;
var _name3 = element.name === '__form__' ? '' : element.name;
var _name3 = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var constraint = Object.entries(element.dataset).reduce((result, _ref4) => {

@@ -445,5 +426,6 @@ var [name, attributeValue = ''] = _ref4;

exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
exports.INTENT = INTENT;
exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
exports.focus = focus;
exports.getErrors = getErrors;

@@ -453,7 +435,8 @@ exports.getFormAttributes = getFormAttributes;

exports.getFormElement = getFormElement;
exports.getFormElements = getFormElements;
exports.getName = getName;
exports.getPaths = getPaths;
exports.getScope = getScope;
exports.getValidationMessage = getValidationMessage;
exports.isFieldElement = isFieldElement;
exports.isFocusedOnIntentButton = isFocusedOnIntentButton;
exports.list = list;

@@ -464,7 +447,5 @@ exports.parse = parse;

exports.requestIntent = requestIntent;
exports.requestSubmit = requestSubmit;
exports.setValue = setValue;
exports.shouldValidate = shouldValidate;
exports.updateList = updateList;
exports.validate = validate;
exports.validateConstraint = validateConstraint;
import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
// type Join<K, P> = P extends string | number ?
// K extends string | number ?
// `${K}${"" extends P ? "" : "."}${P}`
// : never : never;
// type DottedPaths<T> = T extends object ?
// { [K in keyof T]-?: K extends string | number ?
// `${K}` | Join<K, DottedPaths<T[K]>>
// : never
// }[keyof T] : ""
// type Pathfix<T> = T extends `${infer Prefix}.${number}${infer Postfix}` ? `${Prefix}[${number}]${Pathfix<Postfix>}` : T;
// type Path<Schema> = Pathfix<DottedPaths<Schema>> | '';
/**
* Check if the provided reference is a form element (_input_ / _select_ / _textarea_ or _button_)
*/
function isFieldElement(element) {
return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');
}
function getFormElements(form) {
return Array.from(form.elements).filter(isFieldElement);
}
/**
* Find the corresponding paths based on the formatted name
* @param name formatted name
* @returns paths
*/
function getPaths(name) {

@@ -70,14 +61,18 @@ var pattern = /(\w*)\[(\d+)\]/;

}
function shouldValidate(intent, name) {
var _parseListCommand;
var [type] = intent.split('/', 1);
function getScope(intent) {
var _parseListCommand$sco, _parseListCommand;
var [type, ...rest] = intent.split('/');
switch (type) {
case 'validate':
return intent === 'validate' || intent === "validate/".concat(name);
return rest.length > 0 ? rest.join('/') : null;
case 'list':
return ((_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) === name;
return (_parseListCommand$sco = (_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) !== null && _parseListCommand$sco !== void 0 ? _parseListCommand$sco : null;
default:
return true;
return null;
}
}
function isFocusedOnIntentButton(form, intent) {
var element = document.activeElement;
return isFieldElement(element) && element.tagName === 'BUTTON' && element.form === form && element.name === INTENT && element.value === intent;
}
function getValidationMessage(errors) {

@@ -92,2 +87,4 @@ return [].concat(errors !== null && errors !== void 0 ? errors : []).join(String.fromCharCode(31));

}
var FORM_ERROR_ELEMENT_NAME = '__form__';
var INTENT = '__intent__';
var VALIDATION_UNDEFINED = '__undefined__';

@@ -104,3 +101,3 @@ var VALIDATION_SKIPPED = '__skipped__';

// As `form.element.namedItem('')` will always returns null
var elementName = _name ? _name : '__form__';
var elementName = _name ? _name : FORM_ERROR_ELEMENT_NAME;
var item = form.elements.namedItem(elementName);

@@ -124,19 +121,24 @@ if (item instanceof RadioNodeList) {

}
var focusedFirstInvalidField = false;
var scope = getScope(submission.intent);
var isSubmitting = submission.intent.slice(0, submission.intent.indexOf('/')) !== 'validate' && parseListCommand(submission.intent) === null;
for (var element of form.elements) {
if (isFieldElement(element) && element.willValidate) {
var _elementName = element.name !== '__form__' ? element.name : '';
var _message = submission.error[_elementName];
var elementShouldValidate = shouldValidate(submission.intent, _elementName);
if (elementShouldValidate) {
var _submission$error$_el;
var _elementName = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var messages = [].concat((_submission$error$_el = submission.error[_elementName]) !== null && _submission$error$_el !== void 0 ? _submission$error$_el : []);
var shouldValidate = scope === null || scope === _elementName;
if (shouldValidate) {
element.dataset.conformTouched = 'true';
}
if (typeof _message === 'undefined' || ![].concat(_message).includes(VALIDATION_SKIPPED)) {
if (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(getValidationMessage(_message));
element.setCustomValidity(getValidationMessage(messages));
element.dispatchEvent(invalidEvent);
}
if (elementShouldValidate && !element.validity.valid) {
focus(element);
if (!focusedFirstInvalidField && (isSubmitting || isFocusedOnIntentButton(form, submission.intent)) && shouldValidate && element.tagName !== 'BUTTON' && !element.validity.valid) {
element.focus();
focusedFirstInvalidField = true;
}

@@ -162,16 +164,2 @@ }

/**
* The ponyfill of `HTMLFormElement.requestSubmit()`
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/requestSubmit
* @see https://caniuse.com/?search=requestSubmit
*/
function requestSubmit(form, submitter) {
var submitEvent = new SubmitEvent('submit', {
bubbles: true,
cancelable: true,
submitter
});
form.dispatchEvent(submitEvent);
}
/**
* Creates an intent button on demand and trigger a form submit by clicking it.

@@ -185,3 +173,3 @@ */

var button = document.createElement('button');
button.name = '__intent__';
button.name = INTENT;
button.value = buttonProps.value;

@@ -198,3 +186,3 @@ button.hidden = true;

/**
* Returns the properties required to configure a command button for validation
* Returns the properties required to configure an intent button for validation
*

@@ -205,3 +193,3 @@ * @see https://conform.guide/api/react#validate

return {
name: '__intent__',
name: INTENT,
value: field ? "validate/".concat(field) : 'validate',

@@ -218,9 +206,2 @@ formNoValidate: true

}
function focus(field) {
var currentFocus = document.activeElement;
if (!isFieldElement(currentFocus) || currentFocus.tagName !== 'BUTTON' || currentFocus.form !== field.form) {
return;
}
field.focus();
}
function parse(payload, options) {

@@ -233,3 +214,3 @@ var submission = {

var _loop = function _loop(_value) {
if (_name2 === '__intent__') {
if (_name2 === INTENT) {
if (typeof _value !== 'string' || submission.intent !== 'submit') {

@@ -332,3 +313,3 @@ throw new Error('The intent could only be set on a button');

/**
* Helpers to configure a command button for modifying a list
* Helpers to configure an intent button for modifying a list
*

@@ -348,3 +329,3 @@ * @see https://conform.guide/api/react#list

return {
name: '__intent__',
name: INTENT,
value: "list/".concat(type, "/").concat(scope, "/").concat(JSON.stringify(payload)),

@@ -395,3 +376,3 @@ formNoValidate: true

var _options$acceptMultip, _options$acceptMultip2;
var _name3 = element.name === '__form__' ? '' : element.name;
var _name3 = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var constraint = Object.entries(element.dataset).reduce((result, _ref4) => {

@@ -440,2 +421,2 @@ var [name, attributeValue = ''] = _ref4;

export { VALIDATION_SKIPPED, VALIDATION_UNDEFINED, focus, getErrors, getFormAttributes, getFormData, getFormElement, getFormElements, getName, getPaths, getValidationMessage, isFieldElement, list, parse, parseListCommand, reportSubmission, requestIntent, requestSubmit, setValue, shouldValidate, updateList, validate, validateConstraint };
export { FORM_ERROR_ELEMENT_NAME, INTENT, VALIDATION_SKIPPED, VALIDATION_UNDEFINED, getErrors, getFormAttributes, getFormData, getFormElement, getName, getPaths, getScope, getValidationMessage, isFieldElement, isFocusedOnIntentButton, list, parse, parseListCommand, reportSubmission, requestIntent, setValue, updateList, validate, validateConstraint };

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

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

@@ -8,0 +8,0 @@ "module": "module/index.js",

@@ -5,8 +5,14 @@ # @conform-to/dom

Conform is a form validation library built on top of the [Constraint Validation](https://caniuse.com/constraint-validation) API.
Conform is a progressive enhancement first form validation library for [Remix](https://remix.run)
- **Progressive Enhancement**: It is designed based on the [HTML specification](https://html.spec.whatwg.org/dev/form-control-infrastructure.html#the-constraint-validation-api). From validating the form to reporting error messages for each field, if you don't like part of the solution, just replace it with your own.
- **Framework Agnostic**: The DOM is the only dependency. Conform makes use of native [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) exclusively. You don't have to use React / Vue / Svelte to utilise this library.
- **Flexible Setup**: It can validates fields anywhere in the dom with the help of [form attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#form). Also enables CSS pseudo-classes like `:valid` and `:invalid`, allowing flexible styling across your form without the need to manipulate the class names.
### Highlights
- Focused on progressive enhancment by default
- Simplifed intergration through event delegation
- Server first validation with Zod / Yup schema support
- Field name inference with type checking
- Focus management
- Accessibility support
- About 5kb compressed
Checkout the [repository](https://github.com/edmundhung/conform) if you want to know more!
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