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.5.1 to 0.6.0-pre.0

17

_virtual/_rollupPluginBabelHelpers.js

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

function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {

@@ -40,4 +41,20 @@ Object.defineProperty(obj, key, {

}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
exports.defineProperty = _defineProperty;
exports.objectSpread2 = _objectSpread2;
exports.toPrimitive = _toPrimitive;
exports.toPropertyKey = _toPropertyKey;

122

index.d.ts

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

export declare type Primitive = null | undefined | string | number | boolean | Date;
export declare type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement;
export type Primitive = null | undefined | string | number | boolean | Date;
export type FieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement | HTMLButtonElement;
export interface FieldConfig<Schema = unknown> extends FieldConstraint<Schema> {

@@ -7,10 +7,10 @@ id?: string;

defaultValue?: FieldValue<Schema>;
initialError?: Array<[string, string]>;
initialError?: Record<string, string | string[]>;
form?: string;
errorId?: string;
}
export declare 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 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]>;
} : unknown;
export declare type FieldConstraint<Schema = any> = {
} : any;
export type FieldConstraint<Schema = any> = {
required?: boolean;

@@ -25,13 +25,18 @@ minLength?: number;

};
export declare type FieldsetConstraint<Schema extends Record<string, any>> = {
export type FieldsetConstraint<Schema extends Record<string, any>> = {
[Key in keyof Schema]?: FieldConstraint<Schema[Key]>;
};
export declare type Submission<Schema = unknown> = {
type: string;
intent?: string;
value: FieldValue<Schema>;
error: Array<[string, string]>;
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 CommandButtonProps<Name extends string = string> {
name: `conform/${Name}`;
export interface IntentButtonProps {
name: '__intent__';
value: string;

@@ -44,5 +49,15 @@ formNoValidate?: boolean;

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 shouldValidate(submission: Submission, name: string): boolean;
export declare function hasError(error: Array<[string, string]>, name?: string): boolean;
export declare function shouldValidate(intent: string, name: string): boolean;
export declare function getValidationMessage(errors?: string | string[]): string;
export declare function getErrors(message: string | undefined): string[];
export declare const VALIDATION_UNDEFINED = "__undefined__";
export declare const VALIDATION_SKIPPED = "__skipped__";
export declare function reportSubmission(form: HTMLFormElement, submission: Submission): void;

@@ -57,5 +72,8 @@ export declare function setValue<T>(target: any, paths: Array<string | number>, valueFn: (prev?: T) => T): void;

/**
* Creates a command button on demand and trigger a form submit by clicking it.
* Creates an intent button on demand and trigger a form submit by clicking it.
*/
export declare function requestCommand(form: HTMLFormElement | undefined, buttonProps: CommandButtonProps): void;
export declare function requestIntent(form: HTMLFormElement | undefined, buttonProps: {
value: string;
formNoValidate?: boolean;
}): void;
/**

@@ -66,8 +84,32 @@ * Returns the properties required to configure a command button for validation

*/
export declare function validate(field?: string): CommandButtonProps<'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 focus(field: FieldElement): void;
export declare function getSubmissionType(name: string): string | null;
export declare function parse<Schema extends Record<string, any>>(payload: FormData | URLSearchParams): Submission<Schema>;
export declare type ListCommand<Schema = unknown> = {
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';

@@ -105,23 +147,22 @@ scope: string;

};
export declare function parseListCommand<Schema = unknown>(data: string): ListCommand<Schema>;
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 declare function handleList<Schema>(submission: Submission<Schema>): Submission<Schema>;
export interface ListCommandButtonBuilder {
append<Schema>(name: string, payload?: {
defaultValue: Schema;
}): CommandButtonProps<'list'>;
}): IntentButtonProps;
prepend<Schema>(name: string, payload?: {
defaultValue: Schema;
}): CommandButtonProps<'list'>;
}): IntentButtonProps;
replace<Schema>(name: string, payload: {
defaultValue: Schema;
index: number;
}): CommandButtonProps<'list'>;
}): IntentButtonProps;
remove(name: string, payload: {
index: number;
}): CommandButtonProps<'list'>;
}): IntentButtonProps;
reorder(name: string, payload: {
from: number;
to: number;
}): CommandButtonProps<'list'>;
}): IntentButtonProps;
}

@@ -134,1 +175,24 @@ /**

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;

@@ -7,2 +7,17 @@ '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>> | '';
function isFieldElement(element) {

@@ -37,2 +52,14 @@ return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');

}
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) {

@@ -49,39 +76,52 @@ return paths.reduce((name, path) => {

}
function shouldValidate(submission, name) {
return submission.type === 'submit' || submission.type === 'validate' && (submission.intent === '' || submission.intent === name);
function shouldValidate(intent, name) {
var _parseListCommand;
var [type] = intent.split('/', 1);
switch (type) {
case 'validate':
return intent === 'validate' || intent === "validate/".concat(name);
case 'list':
return ((_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) === name;
default:
return true;
}
}
function hasError(error, name) {
return typeof error.find(_ref => {
var [fieldName, message] = _ref;
return (typeof name === 'undefined' || name === fieldName) && message !== '';
}) !== 'undefined';
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 VALIDATION_UNDEFINED = '__undefined__';
var VALIDATION_SKIPPED = '__skipped__';
function reportSubmission(form, submission) {
var messageByName = new Map();
for (var [_name, message] of submission.error) {
if (!messageByName.has(_name)) {
// Only keep the first error message (for now)
messageByName.set(_name, message);
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__';
var item = form.elements.namedItem(elementName);
if (item instanceof RadioNodeList) {
for (var field of item) {
if (field.type !== 'radio') {
throw new Error('Repeated field name is not supported');
}
// We can't use empty string as button name
// As `form.element.namedItem('')` will always returns null
var elementName = _name ? _name : '__form__';
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';
item = button;
form.appendChild(button);
}
}
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);
}
}

@@ -91,12 +131,12 @@ for (var element of form.elements) {

var _elementName = element.name !== '__form__' ? element.name : '';
var _message = messageByName.get(_elementName);
var elementShouldValidate = shouldValidate(submission, _elementName);
var _message = submission.error[_elementName];
var elementShouldValidate = shouldValidate(submission.intent, _elementName);
if (elementShouldValidate) {
element.dataset.conformTouched = 'true';
}
if (typeof _message !== 'undefined' || elementShouldValidate) {
if (typeof _message === 'undefined' || ![].concat(_message).includes(VALIDATION_SKIPPED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(_message !== null && _message !== void 0 ? _message : '');
element.setCustomValidity(getValidationMessage(_message));
element.dispatchEvent(invalidEvent);

@@ -140,5 +180,5 @@ }

/**
* Creates a command button on demand and trigger a form submit by clicking it.
* Creates an intent button on demand and trigger a form submit by clicking it.
*/
function requestCommand(form, buttonProps) {
function requestIntent(form, buttonProps) {
if (!form) {

@@ -149,3 +189,3 @@ console.warn('No form element is provided');

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

@@ -168,4 +208,4 @@ button.hidden = true;

return {
name: 'conform/validate',
value: field !== null && field !== void 0 ? field : '',
name: '__intent__',
value: field ? "validate/".concat(field) : 'validate',
formNoValidate: true

@@ -188,66 +228,76 @@ };

}
function getSubmissionType(name) {
var prefix = 'conform/';
if (!name.startsWith(prefix) || name.length <= prefix.length) {
return null;
}
return name.slice(prefix.length);
}
function parse(payload) {
var hasCommand = false;
function parse(payload, options) {
var submission = {
type: 'submit',
value: {},
error: []
intent: 'submit',
payload: {},
error: {}
};
try {
var _loop = function _loop(value, _name2) {
var submissionType = getSubmissionType(_name2);
if (submissionType) {
if (typeof value !== 'string') {
throw new Error('The conform command could not be used on a file input');
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];
}
if (hasCommand) {
throw new Error('The conform command could only be set on a button');
}
submission = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, submission), {}, {
type: submissionType,
intent: value
});
hasCommand = true;
} else {
var paths = getPaths(_name2);
setValue(submission.value, 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, _name2);
});
}
switch (submission.type) {
case 'list':
submission = handleList(submission);
break;
}
} catch (e) {
submission.error.push(['', e instanceof Error ? e.message : 'Invalid payload received']);
};
for (var [_name2, _value] of payload.entries()) {
_loop(_value);
}
return submission;
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(data) {
function parseListCommand(intent) {
try {
var command = JSON.parse(data);
if (typeof command.type !== 'string' || !['prepend', 'append', 'replace', 'remove', 'reorder', 'combine'].includes(command.type)) {
throw new Error("Unknown list command received: ".concat(command.type));
var [group, type, scope, json] = intent.split('/');
if (group !== 'list' || !['prepend', 'append', 'replace', 'remove', 'reorder'].includes(type) || !scope) {
return null;
}
return command;
var _payload = JSON.parse(json);
return {
// @ts-expect-error
type,
scope,
payload: _payload
};
} catch (error) {
throw new Error("Invalid list command: \"".concat(data, "\"; ").concat(error));
return null;
}

@@ -283,17 +333,2 @@ }

}
function handleList(submission) {
var _submission$intent;
if (submission.type !== 'list') {
return submission;
}
var command = parseListCommand((_submission$intent = submission.intent) !== null && _submission$intent !== void 0 ? _submission$intent : '');
var paths = getPaths(command.scope);
setValue(submission.value, 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);
});
return submission;
}
/**

@@ -315,8 +350,4 @@ * Helpers to configure a command button for modifying a list

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

@@ -329,3 +360,88 @@ };

/**
* 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__' ? '' : 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.VALIDATION_SKIPPED = VALIDATION_SKIPPED;
exports.VALIDATION_UNDEFINED = VALIDATION_UNDEFINED;
exports.focus = focus;
exports.getErrors = getErrors;
exports.getFormAttributes = getFormAttributes;
exports.getFormData = getFormData;

@@ -336,5 +452,3 @@ exports.getFormElement = getFormElement;

exports.getPaths = getPaths;
exports.getSubmissionType = getSubmissionType;
exports.handleList = handleList;
exports.hasError = hasError;
exports.getValidationMessage = getValidationMessage;
exports.isFieldElement = isFieldElement;

@@ -345,3 +459,3 @@ exports.list = list;

exports.reportSubmission = reportSubmission;
exports.requestCommand = requestCommand;
exports.requestIntent = requestIntent;
exports.requestSubmit = requestSubmit;

@@ -352,1 +466,2 @@ exports.setValue = setValue;

exports.validate = validate;
exports.validateConstraint = validateConstraint;

@@ -23,2 +23,3 @@ function ownKeys(object, enumerableOnly) {

function _defineProperty(obj, key, value) {
key = _toPropertyKey(key);
if (key in obj) {

@@ -36,3 +37,17 @@ Object.defineProperty(obj, key, {

}
function _toPrimitive(input, hint) {
if (typeof input !== "object" || input === null) return input;
var prim = input[Symbol.toPrimitive];
if (prim !== undefined) {
var res = prim.call(input, hint || "default");
if (typeof res !== "object") return res;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return (hint === "string" ? String : Number)(input);
}
function _toPropertyKey(arg) {
var key = _toPrimitive(arg, "string");
return typeof key === "symbol" ? key : String(key);
}
export { _defineProperty as defineProperty, _objectSpread2 as objectSpread2 };
export { _defineProperty as defineProperty, _objectSpread2 as objectSpread2, _toPrimitive as toPrimitive, _toPropertyKey as toPropertyKey };
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>> | '';
function isFieldElement(element) {

@@ -32,2 +47,14 @@ return element instanceof Element && (element.tagName === 'INPUT' || element.tagName === 'SELECT' || element.tagName === 'TEXTAREA' || element.tagName === 'BUTTON');

}
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) {

@@ -44,39 +71,52 @@ return paths.reduce((name, path) => {

}
function shouldValidate(submission, name) {
return submission.type === 'submit' || submission.type === 'validate' && (submission.intent === '' || submission.intent === name);
function shouldValidate(intent, name) {
var _parseListCommand;
var [type] = intent.split('/', 1);
switch (type) {
case 'validate':
return intent === 'validate' || intent === "validate/".concat(name);
case 'list':
return ((_parseListCommand = parseListCommand(intent)) === null || _parseListCommand === void 0 ? void 0 : _parseListCommand.scope) === name;
default:
return true;
}
}
function hasError(error, name) {
return typeof error.find(_ref => {
var [fieldName, message] = _ref;
return (typeof name === 'undefined' || name === fieldName) && message !== '';
}) !== 'undefined';
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 VALIDATION_UNDEFINED = '__undefined__';
var VALIDATION_SKIPPED = '__skipped__';
function reportSubmission(form, submission) {
var messageByName = new Map();
for (var [_name, message] of submission.error) {
if (!messageByName.has(_name)) {
// Only keep the first error message (for now)
messageByName.set(_name, message);
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__';
var item = form.elements.namedItem(elementName);
if (item instanceof RadioNodeList) {
for (var field of item) {
if (field.type !== 'radio') {
throw new Error('Repeated field name is not supported');
}
// We can't use empty string as button name
// As `form.element.namedItem('')` will always returns null
var elementName = _name ? _name : '__form__';
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';
item = button;
form.appendChild(button);
}
}
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);
}
}

@@ -86,12 +126,12 @@ for (var element of form.elements) {

var _elementName = element.name !== '__form__' ? element.name : '';
var _message = messageByName.get(_elementName);
var elementShouldValidate = shouldValidate(submission, _elementName);
var _message = submission.error[_elementName];
var elementShouldValidate = shouldValidate(submission.intent, _elementName);
if (elementShouldValidate) {
element.dataset.conformTouched = 'true';
}
if (typeof _message !== 'undefined' || elementShouldValidate) {
if (typeof _message === 'undefined' || ![].concat(_message).includes(VALIDATION_SKIPPED)) {
var invalidEvent = new Event('invalid', {
cancelable: true
});
element.setCustomValidity(_message !== null && _message !== void 0 ? _message : '');
element.setCustomValidity(getValidationMessage(_message));
element.dispatchEvent(invalidEvent);

@@ -135,5 +175,5 @@ }

/**
* Creates a command button on demand and trigger a form submit by clicking it.
* Creates an intent button on demand and trigger a form submit by clicking it.
*/
function requestCommand(form, buttonProps) {
function requestIntent(form, buttonProps) {
if (!form) {

@@ -144,3 +184,3 @@ console.warn('No form element is provided');

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

@@ -163,4 +203,4 @@ button.hidden = true;

return {
name: 'conform/validate',
value: field !== null && field !== void 0 ? field : '',
name: '__intent__',
value: field ? "validate/".concat(field) : 'validate',
formNoValidate: true

@@ -183,66 +223,76 @@ };

}
function getSubmissionType(name) {
var prefix = 'conform/';
if (!name.startsWith(prefix) || name.length <= prefix.length) {
return null;
}
return name.slice(prefix.length);
}
function parse(payload) {
var hasCommand = false;
function parse(payload, options) {
var submission = {
type: 'submit',
value: {},
error: []
intent: 'submit',
payload: {},
error: {}
};
try {
var _loop = function _loop(value, _name2) {
var submissionType = getSubmissionType(_name2);
if (submissionType) {
if (typeof value !== 'string') {
throw new Error('The conform command could not be used on a file input');
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];
}
if (hasCommand) {
throw new Error('The conform command could only be set on a button');
}
submission = _objectSpread2(_objectSpread2({}, submission), {}, {
type: submissionType,
intent: value
});
hasCommand = true;
} else {
var paths = getPaths(_name2);
setValue(submission.value, 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, _name2);
});
}
switch (submission.type) {
case 'list':
submission = handleList(submission);
break;
}
} catch (e) {
submission.error.push(['', e instanceof Error ? e.message : 'Invalid payload received']);
};
for (var [_name2, _value] of payload.entries()) {
_loop(_value);
}
return submission;
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(data) {
function parseListCommand(intent) {
try {
var command = JSON.parse(data);
if (typeof command.type !== 'string' || !['prepend', 'append', 'replace', 'remove', 'reorder', 'combine'].includes(command.type)) {
throw new Error("Unknown list command received: ".concat(command.type));
var [group, type, scope, json] = intent.split('/');
if (group !== 'list' || !['prepend', 'append', 'replace', 'remove', 'reorder'].includes(type) || !scope) {
return null;
}
return command;
var _payload = JSON.parse(json);
return {
// @ts-expect-error
type,
scope,
payload: _payload
};
} catch (error) {
throw new Error("Invalid list command: \"".concat(data, "\"; ").concat(error));
return null;
}

@@ -278,17 +328,2 @@ }

}
function handleList(submission) {
var _submission$intent;
if (submission.type !== 'list') {
return submission;
}
var command = parseListCommand((_submission$intent = submission.intent) !== null && _submission$intent !== void 0 ? _submission$intent : '');
var paths = getPaths(command.scope);
setValue(submission.value, 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);
});
return submission;
}
/**

@@ -310,8 +345,4 @@ * Helpers to configure a command button for modifying a list

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

@@ -324,2 +355,83 @@ };

export { focus, getFormData, getFormElement, getFormElements, getName, getPaths, getSubmissionType, handleList, hasError, isFieldElement, list, parse, parseListCommand, reportSubmission, requestCommand, requestSubmit, setValue, shouldValidate, updateList, validate };
/**
* 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__' ? '' : 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 { 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 };

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

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

@@ -8,0 +8,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