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.1 to 0.6.2

dom.d.ts

208

index.d.ts

@@ -1,203 +0,5 @@

export type Primitive = null | undefined | string | number | boolean | Date;
export type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement;
export interface FieldConfig<Schema = unknown> 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 keyof Schema]?: FieldValue<Schema[Key]>;
} : any;
export type FieldConstraint<Schema = any> = {
required?: boolean;
minLength?: number;
maxLength?: number;
min?: Schema extends number ? number : string | number;
max?: Schema extends number ? number : string | number;
step?: Schema extends number ? number : string | number;
multiple?: boolean;
pattern?: string;
};
export type FieldsetConstraint<Schema extends Record<string, any>> = {
[Key in keyof Schema]?: FieldConstraint<Schema[Key]>;
};
export type Submission<Schema extends Record<string, any> | unknown = unknown> = unknown extends Schema ? {
intent: string;
payload: Record<string, any>;
error: Record<string, string | string[]>;
} : {
intent: string;
payload: Record<string, any>;
value?: Schema;
error: Record<string, string | string[]>;
toJSON(): Submission;
};
export interface IntentButtonProps {
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;
/**
* Find the corresponding paths based on the formatted name
* @param name formatted name
* @returns paths
*/
export declare function getPaths(name: string): Array<string | number>;
export declare function getFormData(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): FormData;
export type FormMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
export type FormEncType = 'application/x-www-form-urlencoded' | 'multipart/form-data';
export declare function getFormAttributes(form: HTMLFormElement, submitter?: HTMLInputElement | HTMLButtonElement | null): {
action: string;
encType: FormEncType;
method: FormMethod;
};
export declare function getName(paths: Array<string | number>): string;
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__";
export declare const VALIDATION_SKIPPED = "__skipped__";
export declare function reportSubmission(form: HTMLFormElement, submission: Submission): void;
export declare function setValue<T>(target: any, paths: Array<string | number>, valueFn: (prev?: T) => T): void;
/**
* Creates an intent button on demand and trigger a form submit by clicking it.
*/
export declare function requestIntent(form: HTMLFormElement | undefined, buttonProps: {
value: string;
formNoValidate?: boolean;
}): void;
/**
* Returns the properties required to configure an intent button for validation
*
* @see https://conform.guide/api/react#validate
*/
export declare function validate(field?: string): IntentButtonProps;
export declare function getFormElement(element: HTMLFormElement | HTMLFieldSetElement | HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement | null): HTMLFormElement | null;
export declare function parse(payload: FormData | URLSearchParams): Submission;
export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
resolve?: (payload: Record<string, any>, intent: string) => {
value: Schema;
} | {
error: Record<string, string | string[]>;
};
}): Submission<Schema>;
export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
resolve?: (payload: Record<string, any>, intent: string) => Promise<{
value: Schema;
} | {
error: Record<string, string | string[]>;
}>;
}): Promise<Submission<Schema>>;
export declare function parse<Schema>(payload: FormData | URLSearchParams, options?: {
resolve?: (payload: Record<string, any>, intent: string) => ({
value: Schema;
} | {
error: Record<string, string | string[]>;
}) | Promise<{
value: Schema;
} | {
error: Record<string, string | string[]>;
}>;
}): Submission<Schema> | Promise<Submission<Schema>>;
export type ListCommand<Schema = unknown> = {
type: 'prepend';
scope: string;
payload: {
defaultValue: Schema;
};
} | {
type: 'append';
scope: string;
payload: {
defaultValue: Schema;
};
} | {
type: 'replace';
scope: string;
payload: {
defaultValue: Schema;
index: number;
};
} | {
type: 'remove';
scope: string;
payload: {
index: number;
};
} | {
type: 'reorder';
scope: string;
payload: {
from: number;
to: number;
};
};
export declare function parseListCommand<Schema = unknown>(intent: string): ListCommand<Schema> | null;
export declare function updateList<Schema>(list: Array<Schema>, command: ListCommand<Schema>): Array<Schema>;
export interface ListCommandButtonBuilder {
append<Schema>(name: string, payload?: {
defaultValue: Schema;
}): IntentButtonProps;
prepend<Schema>(name: string, payload?: {
defaultValue: Schema;
}): IntentButtonProps;
replace<Schema>(name: string, payload: {
defaultValue: Schema;
index: number;
}): IntentButtonProps;
remove(name: string, payload: {
index: number;
}): IntentButtonProps;
reorder(name: string, payload: {
from: number;
to: number;
}): IntentButtonProps;
}
/**
* Helpers to configure an intent button for modifying a list
*
* @see https://conform.guide/api/react#list
*/
export declare const list: ListCommandButtonBuilder;
/**
* 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 { type FormControl as FieldElement, isFormControl as isFieldElement, isFocusableFormControl, getFormAction, getFormControls, getFormElement, getFormEncType, getFormMethod, focusFirstInvalidControl, focusFormControl, createSubmitter, requestSubmit, } from './dom';
export { formatPaths as getName, getPaths, getFormData, getValidationMessage, getErrors, } from './formdata';
export { type ListCommand, INTENT, getScope, isSubmitting, validate, list, parseListCommand, updateList, requestIntent, } from './intent';
export { type Submission, parse } from './parse';
export { type FieldConstraint, type FieldsetConstraint, type ResolveType, type KeysOf, } from './types';

@@ -5,431 +5,33 @@ 'use strict';

var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
var dom = require('./dom.js');
var formdata = require('./formdata.js');
var intent = require('./intent.js');
var parse = require('./parse.js');
/**
* 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');
}
/**
* Find the corresponding paths based on the formatted name
* @param name formatted name
* @returns paths
*/
function getPaths(name) {
var pattern = /(\w*)\[(\d+)\]/;
if (!name) {
return [];
}
return name.split('.').flatMap(key => {
var matches = pattern.exec(key);
if (!matches) {
return key;
}
if (matches[1] === '') {
return Number(matches[2]);
}
return [matches[1], Number(matches[2])];
});
}
function getFormData(form, submitter) {
var payload = new FormData(form);
if (submitter !== null && submitter !== void 0 && submitter.name) {
payload.append(submitter.name, submitter.value);
}
return payload;
}
function getFormAttributes(form, submitter) {
var _ref, _submitter$getAttribu, _ref2, _submitter$getAttribu2, _submitter$getAttribu3;
var enforce = (value, list) => list.includes(value) ? value : list[0];
var action = (_ref = (_submitter$getAttribu = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formaction')) !== null && _submitter$getAttribu !== void 0 ? _submitter$getAttribu : form.getAttribute('action')) !== null && _ref !== void 0 ? _ref : "".concat(location.pathname).concat(location.search);
var method = (_ref2 = (_submitter$getAttribu2 = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formmethod')) !== null && _submitter$getAttribu2 !== void 0 ? _submitter$getAttribu2 : form.getAttribute('method')) !== null && _ref2 !== void 0 ? _ref2 : 'get';
var encType = (_submitter$getAttribu3 = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formenctype')) !== null && _submitter$getAttribu3 !== void 0 ? _submitter$getAttribu3 : form.enctype;
return {
action,
encType: enforce(encType, ['application/x-www-form-urlencoded', 'multipart/form-data']),
method: enforce(method, ['get', 'post', 'put', 'patch', 'delete'])
};
}
function getName(paths) {
return paths.reduce((name, path) => {
if (typeof path === 'number') {
return "".concat(name, "[").concat(path, "]");
}
if (name === '' || path === '') {
return [name, path].join('');
}
return [name, path].join('.');
}, '');
}
function getScope(intent) {
var _parseListCommand$sco, _parseListCommand;
var [type, ...rest] = intent.split('/');
switch (type) {
case 'validate':
return rest.length > 0 ? rest.join('/') : null;
case 'list':
return (_parseListCommand$sco = (_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) !== null && _parseListCommand$sco !== void 0 ? _parseListCommand$sco : null;
default:
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) {
return [].concat(errors !== null && errors !== void 0 ? errors : []).join(String.fromCharCode(31));
}
function getErrors(message) {
if (!message) {
return [];
}
return message.split(String.fromCharCode(31));
}
var FORM_ERROR_ELEMENT_NAME = '__form__';
var INTENT = '__intent__';
var VALIDATION_UNDEFINED = '__undefined__';
var VALIDATION_SKIPPED = '__skipped__';
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 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 _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 (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(getValidationMessage(messages));
element.dispatchEvent(invalidEvent);
}
if (!focusedFirstInvalidField && (isSubmitting || isFocusedOnIntentButton(form, submission.intent)) && shouldValidate && element.tagName !== 'BUTTON' && !element.validity.valid) {
element.focus();
focusedFirstInvalidField = true;
}
}
}
}
function setValue(target, paths, valueFn) {
var length = paths.length;
var lastIndex = length - 1;
var index = -1;
var pointer = target;
while (pointer != null && ++index < length) {
var _pointer$key;
var key = paths[index];
var next = paths[index + 1];
var newValue = index != lastIndex ? (_pointer$key = pointer[key]) !== null && _pointer$key !== void 0 ? _pointer$key : typeof next === 'number' ? [] : {} : valueFn(pointer[key]);
pointer[key] = newValue;
pointer = pointer[key];
}
}
/**
* Creates an intent button on demand and trigger a form submit by clicking it.
*/
function requestIntent(form, buttonProps) {
if (!form) {
console.warn('No form element is provided');
return;
}
var button = document.createElement('button');
button.name = INTENT;
button.value = buttonProps.value;
button.hidden = true;
if (buttonProps.formNoValidate) {
button.formNoValidate = true;
}
form.appendChild(button);
button.click();
form.removeChild(button);
}
/**
* Returns the properties required to configure an intent button for validation
*
* @see https://conform.guide/api/react#validate
*/
function validate(field) {
return {
name: INTENT,
value: field ? "validate/".concat(field) : 'validate',
formNoValidate: true
};
}
function getFormElement(element) {
var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
if (!form) {
return null;
}
return form;
}
function parse(payload, options) {
var submission = {
intent: 'submit',
payload: {},
error: {}
};
var _loop = function _loop(_value) {
if (_name2 === INTENT) {
if (typeof _value !== 'string' || submission.intent !== 'submit') {
throw new Error('The intent could only be set on a button');
}
submission.intent = _value;
} else {
var _paths = getPaths(_name2);
setValue(submission.payload, _paths, prev => {
if (!prev) {
return _value;
} else if (Array.isArray(prev)) {
return prev.concat(_value);
} else {
return [prev, _value];
}
});
}
};
for (var [_name2, _value] of payload.entries()) {
_loop(_value);
}
var command = parseListCommand(submission.intent);
if (command) {
var paths = getPaths(command.scope);
setValue(submission.payload, paths, list => {
if (typeof list !== 'undefined' && !Array.isArray(list)) {
throw new Error('The list command can only be applied to a list');
}
return updateList(list !== null && list !== void 0 ? list : [], command);
});
}
if (typeof (options === null || options === void 0 ? void 0 : options.resolve) === 'undefined') {
return submission;
}
var result = options.resolve(submission.payload, submission.intent);
var mergeResolveResult = resolved => {
var result = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission), resolved), {}, {
toJSON() {
return {
intent: this.intent,
payload: this.payload,
error: this.error
};
}
});
return result;
};
if (result instanceof Promise) {
return result.then(mergeResolveResult);
}
return mergeResolveResult(result);
}
function parseListCommand(intent) {
try {
var [group, type, scope, json] = intent.split('/');
if (group !== 'list' || !['prepend', 'append', 'replace', 'remove', 'reorder'].includes(type) || !scope) {
return null;
}
var _payload = JSON.parse(json);
return {
// @ts-expect-error
type,
scope,
payload: _payload
};
} catch (error) {
return null;
}
}
function updateList(list, command) {
switch (command.type) {
case 'prepend':
{
list.unshift(command.payload.defaultValue);
break;
}
case 'append':
{
list.push(command.payload.defaultValue);
break;
}
case 'replace':
{
list.splice(command.payload.index, 1, command.payload.defaultValue);
break;
}
case 'remove':
list.splice(command.payload.index, 1);
break;
case 'reorder':
list.splice(command.payload.to, 0, ...list.splice(command.payload.from, 1));
break;
default:
throw new Error('Unknown list command received');
}
return list;
}
/**
* Helpers to configure an intent button for modifying a list
*
* @see https://conform.guide/api/react#list
*/
var list = new Proxy({}, {
get(_target, type) {
switch (type) {
case 'append':
case 'prepend':
case 'replace':
case 'remove':
case 'reorder':
return function (scope) {
var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return {
name: INTENT,
value: "list/".concat(type, "/").concat(scope, "/").concat(JSON.stringify(payload)),
formNoValidate: true
};
};
}
}
});
/**
* 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 : _ref3 => {
var {
defaultErrors
} = _ref3;
return defaultErrors;
};
return parse(formData, {
resolve(payload, intent) {
var error = {};
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
var _loop2 = function _loop2(element) {
if (isFieldElement(element)) {
var _options$acceptMultip, _options$acceptMultip2;
var _name3 = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var constraint = Object.entries(element.dataset).reduce((result, _ref4) => {
var [name, attributeValue = ''] = _ref4;
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: _name3,
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: _name3,
payload,
intent
})) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
if (errors.length > 0) {
error[_name3] = shouldAcceptMultipleErrors ? errors : errors[0];
}
}
};
for (var element of options.form.elements) {
_loop2(element);
}
return {
error
};
}
});
}
exports.FORM_ERROR_ELEMENT_NAME = FORM_ERROR_ELEMENT_NAME;
exports.INTENT = INTENT;
exports.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
exports.getErrors = getErrors;
exports.getFormAttributes = getFormAttributes;
exports.getFormData = getFormData;
exports.getFormElement = getFormElement;
exports.getName = getName;
exports.getPaths = getPaths;
exports.getScope = getScope;
exports.getValidationMessage = getValidationMessage;
exports.isFieldElement = isFieldElement;
exports.isFocusedOnIntentButton = isFocusedOnIntentButton;
exports.list = list;
exports.parse = parse;
exports.parseListCommand = parseListCommand;
exports.reportSubmission = reportSubmission;
exports.requestIntent = requestIntent;
exports.setValue = setValue;
exports.updateList = updateList;
exports.validate = validate;
exports.validateConstraint = validateConstraint;
exports.createSubmitter = dom.createSubmitter;
exports.focusFirstInvalidControl = dom.focusFirstInvalidControl;
exports.focusFormControl = dom.focusFormControl;
exports.getFormAction = dom.getFormAction;
exports.getFormControls = dom.getFormControls;
exports.getFormElement = dom.getFormElement;
exports.getFormEncType = dom.getFormEncType;
exports.getFormMethod = dom.getFormMethod;
exports.isFieldElement = dom.isFormControl;
exports.isFocusableFormControl = dom.isFocusableFormControl;
exports.requestSubmit = dom.requestSubmit;
exports.getErrors = formdata.getErrors;
exports.getFormData = formdata.getFormData;
exports.getName = formdata.formatPaths;
exports.getPaths = formdata.getPaths;
exports.getValidationMessage = formdata.getValidationMessage;
exports.INTENT = intent.INTENT;
exports.getScope = intent.getScope;
exports.isSubmitting = intent.isSubmitting;
exports.list = intent.list;
exports.parseListCommand = intent.parseListCommand;
exports.requestIntent = intent.requestIntent;
exports.updateList = intent.updateList;
exports.validate = intent.validate;
exports.parse = parse.parse;

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

import { objectSpread2 as _objectSpread2 } from './_virtual/_rollupPluginBabelHelpers.js';
/**
* 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');
}
/**
* Find the corresponding paths based on the formatted name
* @param name formatted name
* @returns paths
*/
function getPaths(name) {
var pattern = /(\w*)\[(\d+)\]/;
if (!name) {
return [];
}
return name.split('.').flatMap(key => {
var matches = pattern.exec(key);
if (!matches) {
return key;
}
if (matches[1] === '') {
return Number(matches[2]);
}
return [matches[1], Number(matches[2])];
});
}
function getFormData(form, submitter) {
var payload = new FormData(form);
if (submitter !== null && submitter !== void 0 && submitter.name) {
payload.append(submitter.name, submitter.value);
}
return payload;
}
function getFormAttributes(form, submitter) {
var _ref, _submitter$getAttribu, _ref2, _submitter$getAttribu2, _submitter$getAttribu3;
var enforce = (value, list) => list.includes(value) ? value : list[0];
var action = (_ref = (_submitter$getAttribu = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formaction')) !== null && _submitter$getAttribu !== void 0 ? _submitter$getAttribu : form.getAttribute('action')) !== null && _ref !== void 0 ? _ref : "".concat(location.pathname).concat(location.search);
var method = (_ref2 = (_submitter$getAttribu2 = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formmethod')) !== null && _submitter$getAttribu2 !== void 0 ? _submitter$getAttribu2 : form.getAttribute('method')) !== null && _ref2 !== void 0 ? _ref2 : 'get';
var encType = (_submitter$getAttribu3 = submitter === null || submitter === void 0 ? void 0 : submitter.getAttribute('formenctype')) !== null && _submitter$getAttribu3 !== void 0 ? _submitter$getAttribu3 : form.enctype;
return {
action,
encType: enforce(encType, ['application/x-www-form-urlencoded', 'multipart/form-data']),
method: enforce(method, ['get', 'post', 'put', 'patch', 'delete'])
};
}
function getName(paths) {
return paths.reduce((name, path) => {
if (typeof path === 'number') {
return "".concat(name, "[").concat(path, "]");
}
if (name === '' || path === '') {
return [name, path].join('');
}
return [name, path].join('.');
}, '');
}
function getScope(intent) {
var _parseListCommand$sco, _parseListCommand;
var [type, ...rest] = intent.split('/');
switch (type) {
case 'validate':
return rest.length > 0 ? rest.join('/') : null;
case 'list':
return (_parseListCommand$sco = (_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) !== null && _parseListCommand$sco !== void 0 ? _parseListCommand$sco : null;
default:
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) {
return [].concat(errors !== null && errors !== void 0 ? errors : []).join(String.fromCharCode(31));
}
function getErrors(message) {
if (!message) {
return [];
}
return message.split(String.fromCharCode(31));
}
var FORM_ERROR_ELEMENT_NAME = '__form__';
var INTENT = '__intent__';
var VALIDATION_UNDEFINED = '__undefined__';
var VALIDATION_SKIPPED = '__skipped__';
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 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 _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 (!messages.includes(VALIDATION_SKIPPED) && !messages.includes(VALIDATION_UNDEFINED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(getValidationMessage(messages));
element.dispatchEvent(invalidEvent);
}
if (!focusedFirstInvalidField && (isSubmitting || isFocusedOnIntentButton(form, submission.intent)) && shouldValidate && element.tagName !== 'BUTTON' && !element.validity.valid) {
element.focus();
focusedFirstInvalidField = true;
}
}
}
}
function setValue(target, paths, valueFn) {
var length = paths.length;
var lastIndex = length - 1;
var index = -1;
var pointer = target;
while (pointer != null && ++index < length) {
var _pointer$key;
var key = paths[index];
var next = paths[index + 1];
var newValue = index != lastIndex ? (_pointer$key = pointer[key]) !== null && _pointer$key !== void 0 ? _pointer$key : typeof next === 'number' ? [] : {} : valueFn(pointer[key]);
pointer[key] = newValue;
pointer = pointer[key];
}
}
/**
* Creates an intent button on demand and trigger a form submit by clicking it.
*/
function requestIntent(form, buttonProps) {
if (!form) {
console.warn('No form element is provided');
return;
}
var button = document.createElement('button');
button.name = INTENT;
button.value = buttonProps.value;
button.hidden = true;
if (buttonProps.formNoValidate) {
button.formNoValidate = true;
}
form.appendChild(button);
button.click();
form.removeChild(button);
}
/**
* Returns the properties required to configure an intent button for validation
*
* @see https://conform.guide/api/react#validate
*/
function validate(field) {
return {
name: INTENT,
value: field ? "validate/".concat(field) : 'validate',
formNoValidate: true
};
}
function getFormElement(element) {
var form = element instanceof HTMLFormElement ? element : element === null || element === void 0 ? void 0 : element.form;
if (!form) {
return null;
}
return form;
}
function parse(payload, options) {
var submission = {
intent: 'submit',
payload: {},
error: {}
};
var _loop = function _loop(_value) {
if (_name2 === INTENT) {
if (typeof _value !== 'string' || submission.intent !== 'submit') {
throw new Error('The intent could only be set on a button');
}
submission.intent = _value;
} else {
var _paths = getPaths(_name2);
setValue(submission.payload, _paths, prev => {
if (!prev) {
return _value;
} else if (Array.isArray(prev)) {
return prev.concat(_value);
} else {
return [prev, _value];
}
});
}
};
for (var [_name2, _value] of payload.entries()) {
_loop(_value);
}
var command = parseListCommand(submission.intent);
if (command) {
var paths = getPaths(command.scope);
setValue(submission.payload, paths, list => {
if (typeof list !== 'undefined' && !Array.isArray(list)) {
throw new Error('The list command can only be applied to a list');
}
return updateList(list !== null && list !== void 0 ? list : [], command);
});
}
if (typeof (options === null || options === void 0 ? void 0 : options.resolve) === 'undefined') {
return submission;
}
var result = options.resolve(submission.payload, submission.intent);
var mergeResolveResult = resolved => {
var result = _objectSpread2(_objectSpread2(_objectSpread2({}, submission), resolved), {}, {
toJSON() {
return {
intent: this.intent,
payload: this.payload,
error: this.error
};
}
});
return result;
};
if (result instanceof Promise) {
return result.then(mergeResolveResult);
}
return mergeResolveResult(result);
}
function parseListCommand(intent) {
try {
var [group, type, scope, json] = intent.split('/');
if (group !== 'list' || !['prepend', 'append', 'replace', 'remove', 'reorder'].includes(type) || !scope) {
return null;
}
var _payload = JSON.parse(json);
return {
// @ts-expect-error
type,
scope,
payload: _payload
};
} catch (error) {
return null;
}
}
function updateList(list, command) {
switch (command.type) {
case 'prepend':
{
list.unshift(command.payload.defaultValue);
break;
}
case 'append':
{
list.push(command.payload.defaultValue);
break;
}
case 'replace':
{
list.splice(command.payload.index, 1, command.payload.defaultValue);
break;
}
case 'remove':
list.splice(command.payload.index, 1);
break;
case 'reorder':
list.splice(command.payload.to, 0, ...list.splice(command.payload.from, 1));
break;
default:
throw new Error('Unknown list command received');
}
return list;
}
/**
* Helpers to configure an intent button for modifying a list
*
* @see https://conform.guide/api/react#list
*/
var list = new Proxy({}, {
get(_target, type) {
switch (type) {
case 'append':
case 'prepend':
case 'replace':
case 'remove':
case 'reorder':
return function (scope) {
var payload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return {
name: INTENT,
value: "list/".concat(type, "/").concat(scope, "/").concat(JSON.stringify(payload)),
formNoValidate: true
};
};
}
}
});
/**
* 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 : _ref3 => {
var {
defaultErrors
} = _ref3;
return defaultErrors;
};
return parse(formData, {
resolve(payload, intent) {
var error = {};
var constraintPattern = /^constraint[A-Z][^A-Z]*$/;
var _loop2 = function _loop2(element) {
if (isFieldElement(element)) {
var _options$acceptMultip, _options$acceptMultip2;
var _name3 = element.name !== FORM_ERROR_ELEMENT_NAME ? element.name : '';
var constraint = Object.entries(element.dataset).reduce((result, _ref4) => {
var [name, attributeValue = ''] = _ref4;
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: _name3,
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: _name3,
payload,
intent
})) !== null && _options$acceptMultip !== void 0 ? _options$acceptMultip : false;
if (errors.length > 0) {
error[_name3] = shouldAcceptMultipleErrors ? errors : errors[0];
}
}
};
for (var element of options.form.elements) {
_loop2(element);
}
return {
error
};
}
});
}
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 };
export { createSubmitter, focusFirstInvalidControl, focusFormControl, getFormAction, getFormControls, getFormElement, getFormEncType, getFormMethod, isFormControl as isFieldElement, isFocusableFormControl, requestSubmit } from './dom.js';
export { getErrors, getFormData, formatPaths as getName, getPaths, getValidationMessage } from './formdata.js';
export { INTENT, getScope, isSubmitting, list, parseListCommand, requestIntent, updateList, validate } from './intent.js';
export { parse } from './parse.js';

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

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

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

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