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

@felte/react

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@felte/react - npm Package Compare versions

Comparing version 0.1.0 to 0.1.1

40

dist/index.d.ts

@@ -1,12 +0,21 @@

import { Obj, CreateSubmitHandlerConfig, Helpers, FormConfigWithInitialValues, FormConfigWithoutInitialValues, Errors, Touched } from "@felte/core";
import { Obj, Errors, Touched, TransWritable, CreateSubmitHandlerConfig, KnownHelpers, UnknownHelpers, FormConfigWithTransformFn, FormConfigWithoutTransformFn } from "@felte/core";
import { Readable, Writable } from "svelte/store";
type Accessor<T> = T extends Obj ? (<R>(selector: (storeValue: T) => R) => R) & ((path: string) => unknown) & (() => T) : (<R>(deriveFn: (storeValue: T) => R) => R) & (() => T);
type UnknownStores<Data extends Obj> = Omit<Stores<Data>, "data"> & {
data: Accessor<Data> & TransWritable<Data>;
};
type KnownStores<Data extends Obj> = Omit<Stores<Data>, "data"> & {
data: Accessor<Data> & Writable<Data>;
};
type Stores<Data extends Obj> = {
data: Accessor<Data>;
errors: Accessor<Errors<Data>>;
warnings: Accessor<Errors<Data>>;
touched: Accessor<Touched<Data>>;
isSubmitting: Accessor<boolean>;
isValid: Accessor<boolean>;
isDirty: Accessor<boolean>;
data: Accessor<Data> & Writable<Data>;
errors: Accessor<Errors<Data>> & Writable<Errors<Data>>;
warnings: Accessor<Errors<Data>> & Writable<Errors<Data>>;
touched: Accessor<Touched<Data>> & Writable<Touched<Data>>;
isSubmitting: Accessor<boolean> & Writable<boolean>;
isValid: Accessor<boolean> & Readable<boolean>;
isDirty: Accessor<boolean> & Writable<boolean>;
};
declare function useAccessor<T>(store: Writable<T>): Accessor<T> & Writable<T>;
declare function useAccessor<T>(store: Readable<T>): Accessor<T> & Readable<T>;
/** The return type for the `createForm` function. */

@@ -20,12 +29,7 @@ type Form<Data extends Obj> = {

createSubmitHandler(altConfig?: CreateSubmitHandlerConfig<Data>): (e?: Event) => void;
} & Stores<Data> & Helpers<Data>;
declare function useForm<Data extends Obj = Obj, Ext extends Obj = Obj>(config: FormConfigWithInitialValues<Data> & Ext): Form<Data>;
/**
* Creates the stores and `form` action to make the form reactive.
* In order to use auto-subscriptions with the stores, call this function at the top-level scope of the component.
*
* @param config - Configuration for the form itself. Since `initialValues` is not set (when only using the `form` action), `Data` will be undefined until the `form` element loads.
*/
declare function useForm<Data extends Obj = Obj, Ext extends Obj = Obj>(config: FormConfigWithoutInitialValues<Data> & Ext): Form<Data>;
export { useForm };
};
declare function useForm<Data extends Obj = Obj, Ext extends Obj = Obj>(config: FormConfigWithTransformFn<Data> & Ext): Form<Data> & UnknownHelpers<Data> & UnknownStores<Data>;
declare function useForm<Data extends Obj = Obj, Ext extends Obj = Obj>(config?: FormConfigWithoutTransformFn<Data> & Ext): Form<Data> & KnownHelpers<Data> & KnownStores<Data>;
export { FelteSubmitError } from '@felte/core';
export { useAccessor, useForm };
//# sourceMappingURL=index.d.ts.map

@@ -1,2 +0,1317 @@

"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var react = require('react');
/** @ignore */
function _some(obj, pred) {
const keys = Object.keys(obj);
return keys.some((key) => pred(obj[key]));
}
/** @ignore */
function _mapValues(obj, updater) {
const keys = Object.keys(obj);
return keys.reduce((acc, key) => (Object.assign(Object.assign({}, acc), { [key]: updater(obj[key]) })), {});
}
/** @ignore */
function _isPlainObject(value) {
return Object.prototype.toString.call(value) === '[object Object]';
}
/** @ignore */
function _cloneDeep(obj) {
return Object.keys(obj || {}).reduce((res, key) => (Object.assign(Object.assign({}, res), { [key]: _isPlainObject(obj[key])
? _cloneDeep(obj[key])
: Array.isArray(obj[key])
? [...obj[key]]
: obj[key] })), {});
}
function handleArray(value) {
return function (propVal) {
if (_isPlainObject(propVal))
return deepSet(propVal, value);
return value;
};
}
/**
* @category Helper
*/
function deepSet(obj, value) {
return _mapValues(obj, (prop) => _isPlainObject(prop)
? deepSet(prop, value)
: Array.isArray(prop)
? prop.map(handleArray(value))
: value);
}
/** @ignore */
function _mergeWith(...args) {
const customizer = args.pop();
const obj = _cloneDeep(args.shift());
if (args.length === 0)
return obj;
for (const source of args) {
if (!source)
continue;
const keys = Object.keys(source);
for (const key of keys) {
const rsValue = customizer(obj[key], source[key]);
if (typeof rsValue !== 'undefined') {
obj[key] = rsValue;
}
else if (_isPlainObject(source[key]) && _isPlainObject(obj[key])) {
obj[key] = _mergeWith(obj[key], source[key], customizer);
}
else if (Array.isArray(source[key])) {
obj[key] = source[key].map((val, i) => {
if (!_isPlainObject(val))
return val;
const newObj = Array.isArray(obj[key]) ? obj[key][i] : obj[key];
return _mergeWith(newObj, val, customizer);
});
}
else if (_isPlainObject(source[key])) {
const defaultObj = deepSet(_cloneDeep(source[key]), undefined);
obj[key] = _mergeWith(defaultObj, source[key], customizer);
}
else if (typeof source[key] !== 'undefined') {
obj[key] = source[key];
}
}
}
return obj;
}
function defaultsCustomizer(objValue, srcValue) {
if (_isPlainObject(objValue) && _isPlainObject(srcValue))
return;
if (Array.isArray(srcValue)) {
if (srcValue.some(_isPlainObject))
return;
const objArray = Array.isArray(objValue) ? objValue : [];
return srcValue.map((value, index) => { var _a; return (_a = objArray[index]) !== null && _a !== void 0 ? _a : value; });
}
if (typeof objValue !== 'undefined')
return objValue;
}
/** @ignore */
function _defaultsDeep(...args) {
return _mergeWith(...args, defaultsCustomizer);
}
/** @ignore */
function _merge(...args) {
return _mergeWith(...args, () => undefined);
}
/* From: https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_get */
/** @ignore */
function _get(obj, path, defaultValue) {
const travel = (regexp) => String.prototype.split
.call(path, regexp)
.filter(Boolean)
.reduce((res, key) => (res !== null && res !== undefined ? res[key] : res), obj);
const result = travel(/[,[\]]+?/) || travel(/[,[\].]+?/);
return result === undefined || result === obj ? defaultValue : result;
}
/* From: https://stackoverflow.com/a/54733755 */
/** @ignore */
function _set(obj, path, value) {
if (Object(obj) !== obj)
obj = {};
// When obj is not an object
else if (typeof obj !== 'undefined')
obj = _cloneDeep(obj);
// If not yet an array, get the keys from the string-path
let newPath = !Array.isArray(path)
? path.toString().match(/[^.[\]]+/g) || []
: path;
newPath.slice(0, -1).reduce((a, c, i // Iterate all of them except the last one
) => Object(a[c]) === a[c] // Does the key exist and is its value an object?
? // Yes: then follow that path
a[c]
: // No: create the key. Is the next key a potential array-index?
(a[c] =
Math.abs(Number(newPath[i + 1])) >> 0 === +newPath[i + 1]
? [] // Yes: assign a new array object
: {}), // No: assign a new plain object
obj)[newPath[newPath.length - 1]] = value; // Finally assign the value to the last key
return obj; // Return the top-level object to allow chaining
}
function _unset(obj, path) {
var _a;
if (Object(obj) !== obj)
return;
// When obj is not an object
else if (typeof obj !== 'undefined')
obj = _cloneDeep(obj);
// If not yet an array, get the keys from the string-path
let newPath = !Array.isArray(path)
? path.toString().match(/[^.[\]]+/g) || []
: path;
(_a = newPath.slice(0, -1).reduce((a, c) => Object(a[c]) === a[c] // Does the key exist and is its value an object?
? // Yes: then follow that path
a[c]
: undefined, obj)) === null || _a === void 0 ? true : delete _a[newPath[newPath.length - 1]];
return obj; // Return the top-level object to allow chaining
}
/** @ignore */
function _update(obj, path, updater) {
if (Object(obj) !== obj)
obj = {}; // When obj is not an object
// If not yet an array, get the keys from the string-path
let newPath = path.toString().match(/[^.[\]]+/g) || [];
newPath.slice(0, -1).reduce((a, c, i // Iterate all of them except the last one
) => Object(a[c]) === a[c] // Does the key exist and is its value an object?
? // Yes: then follow that path
a[c]
: // No: create the key. Is the next key a potential array-index?
(a[c] =
Math.abs(Number(newPath[i + 1])) >> 0 === +newPath[i + 1]
? [] // Yes: assign a new array object
: {}), // No: assign a new plain object
obj)[newPath[newPath.length - 1]] = updater(_get(obj, path)); // Finally assign the value to the last key
return obj; // Return the top-level object to allow chaining
}
/**
* @category Helper
*/
function deepSome(obj, pred) {
return _some(obj, (value) => _isPlainObject(value) ? deepSome(value, pred) : pred(value));
}
/**
* @ignore
*/
function getIndex(el) {
return el.hasAttribute('data-felte-index')
? Number(el.dataset.felteIndex)
: undefined;
}
/**
* @category Helper
*/
function isInputElement(el) {
var _a;
return ((_a = el) === null || _a === void 0 ? void 0 : _a.nodeName) === 'INPUT';
}
/**
* @category Helper
*/
function isTextAreaElement(el) {
var _a;
return ((_a = el) === null || _a === void 0 ? void 0 : _a.nodeName) === 'TEXTAREA';
}
/**
* @category Helper
*/
function isSelectElement(el) {
var _a;
return ((_a = el) === null || _a === void 0 ? void 0 : _a.nodeName) === 'SELECT';
}
/**
* @category Helper
*/
function isFieldSetElement(el) {
var _a;
return ((_a = el) === null || _a === void 0 ? void 0 : _a.nodeName) === 'FIELDSET';
}
/**
* @category Helper
*/
function isFormControl(el) {
return isInputElement(el) || isTextAreaElement(el) || isSelectElement(el);
}
/**
* @category Helper
*/
function isElement(el) {
return el.nodeType === Node.ELEMENT_NODE;
}
/**
* @category Helper
*/
function getPath(el, name) {
const index = getIndex(el);
let path = '';
if (name) {
path = name;
}
else if (isFormControl(el)) {
path = el.name;
}
path = typeof index === 'undefined' ? path : `${path}[${index}]`;
let parent = el.parentNode;
if (!parent)
return path;
while (parent && parent.nodeName !== 'FORM') {
if (isFieldSetElement(parent) && parent.name) {
const index = getIndex(parent);
const fieldsetName = typeof index === 'undefined' ? parent.name : `${parent.name}[${index}]`;
path = `${fieldsetName}.${path}`;
}
parent = parent.parentNode;
}
return path;
}
function getPathFromDataset(el) {
const fieldSetName = el.dataset.felteFieldset;
const index = getIndex(el);
const fieldName = typeof index === 'undefined' ? el.name : `${el.name}[${index}]`;
return fieldSetName ? `${fieldSetName}.${fieldName}` : fieldName;
}
/**
* @category Helper
*/
function shouldIgnore(el) {
let parent = el;
while (parent && parent.nodeName !== 'FORM') {
if (parent.hasAttribute('data-felte-ignore'))
return true;
parent = parent.parentElement;
}
return false;
}
function getValue(storeValue, selectorOrPath) {
if (!_isPlainObject(storeValue) || !selectorOrPath)
return storeValue;
if (typeof selectorOrPath === 'string') {
return _get(storeValue, selectorOrPath);
}
return selectorOrPath(storeValue);
}
/**
* @ignore
*/
function getFormControls(el) {
if (isFormControl(el))
return [el];
if (el.childElementCount === 0)
return [];
const foundControls = new Set();
for (const child of el.children) {
if (isFormControl(child))
foundControls.add(child);
if (isFieldSetElement(child)) {
for (const fieldsetChild of child.elements) {
if (isFormControl(fieldsetChild))
foundControls.add(fieldsetChild);
}
}
if (child.childElementCount > 0)
getFormControls(child).forEach((value) => foundControls.add(value));
}
return Array.from(foundControls);
}
/**
* @ignore
*/
function addAttrsFromFieldset(fieldSet) {
for (const element of fieldSet.elements) {
if (!isFormControl(element) && !isFieldSetElement(element))
continue;
if (fieldSet.name && element.name) {
const index = getIndex(fieldSet);
const fieldsetName = typeof index === 'undefined'
? fieldSet.name
: `${fieldSet.name}[${index}]`;
element.dataset.felteFieldset = fieldSet.dataset.felteFieldset
? `${fieldSet.dataset.felteFieldset}.${fieldsetName}`
: fieldsetName;
}
if (fieldSet.hasAttribute('data-felte-keep-on-remove') &&
!element.hasAttribute('data-felte-keep-on-remove')) {
element.dataset.felteKeepOnRemove = fieldSet.dataset.felteKeepOnRemove;
}
}
}
/** @ignore */
function getInputTextOrNumber(el) {
if (el.type.match(/^(number|range)$/)) {
return !el.value ? undefined : +el.value;
}
else {
return el.value;
}
}
/**
* @ignore
*/
function getFormDefaultValues(node) {
var _a;
let defaultData = {};
for (const el of node.elements) {
if (isFieldSetElement(el))
addAttrsFromFieldset(el);
if (!isFormControl(el) || !el.name)
continue;
const elName = getPath(el);
const index = getIndex(el);
if (isInputElement(el)) {
if (el.type === 'checkbox') {
if (typeof _get(defaultData, elName) === 'undefined') {
const checkboxes = Array.from(node.querySelectorAll(`[name="${el.name}"]`)).filter((checkbox) => {
if (!isFormControl(checkbox))
return false;
if (typeof index !== 'undefined') {
const felteIndex = Number(checkbox.dataset.felteIndex);
return felteIndex === index;
}
return elName === getPath(checkbox);
});
if (checkboxes.length === 1) {
defaultData = _set(defaultData, elName, el.checked);
continue;
}
defaultData = _set(defaultData, elName, el.checked ? [el.value] : []);
continue;
}
if (Array.isArray(_get(defaultData, elName)) && el.checked) {
_update(defaultData, elName, (value) => {
if (typeof index !== 'undefined' && !Array.isArray(value))
value = [];
return [...value, el.value];
});
}
continue;
}
if (el.type === 'radio') {
if (_get(defaultData, elName))
continue;
defaultData = _set(defaultData, elName, el.checked ? el.value : undefined);
continue;
}
if (el.type === 'file') {
defaultData = _set(defaultData, elName, el.multiple ? Array.from(el.files || []) : (_a = el.files) === null || _a === void 0 ? void 0 : _a[0]);
continue;
}
}
const inputValue = getInputTextOrNumber(el);
defaultData = _set(defaultData, elName, inputValue);
}
return { defaultData };
}
function setControlValue(el, value) {
if (!isFormControl(el))
return;
const fieldValue = value;
if (isInputElement(el)) {
if (el.type === 'checkbox') {
const checkboxesDefaultData = fieldValue;
if (typeof checkboxesDefaultData === 'undefined' ||
typeof checkboxesDefaultData === 'boolean') {
el.checked = !!checkboxesDefaultData;
return;
}
if (Array.isArray(checkboxesDefaultData)) {
if (checkboxesDefaultData.includes(el.value)) {
el.checked = true;
}
else {
el.checked = false;
}
}
return;
}
if (el.type === 'radio') {
const radioValue = fieldValue;
if (el.value === radioValue)
el.checked = true;
else
el.checked = false;
return;
}
if (el.type === 'file') {
el.files = null;
el.value = '';
return;
}
}
el.value = String(fieldValue !== null && fieldValue !== void 0 ? fieldValue : '');
}
/** Sets the form inputs value to match the data object provided. */
function setForm(node, data) {
for (const el of node.elements) {
if (isFieldSetElement(el))
addAttrsFromFieldset(el);
if (!isFormControl(el) || !el.name)
continue;
const elName = getPath(el);
setControlValue(el, _get(data, elName));
}
}
function executeCustomizer(objValue, srcValue) {
if (_isPlainObject(objValue) || _isPlainObject(srcValue))
return;
if (objValue === null)
return srcValue;
if (srcValue === null)
return objValue;
if (!objValue || !srcValue)
return;
if (!Array.isArray(objValue))
objValue = [objValue];
if (!Array.isArray(srcValue))
srcValue = [srcValue];
return [...objValue, ...srcValue];
}
async function executeValidation(values, validations) {
if (!validations)
return;
if (!Array.isArray(validations))
return validations(values);
const errorArray = await Promise.all(validations.map((v) => v(values)));
return _mergeWith(...errorArray, executeCustomizer);
}
function executeTransforms(values, transforms) {
if (!transforms)
return values;
if (!Array.isArray(transforms))
return transforms(values);
return transforms.reduce((res, t) => t(res), values);
}
function subscribe$1(store, ...callbacks) {
const unsub = store.subscribe(...callbacks);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
function get(store) {
let value = undefined;
subscribe$1(store, (_) => (value = _))();
return value;
}
class FelteSubmitError extends Error {
constructor(message, response) {
super(message);
this.name = 'FelteSubmitError';
this.response = response;
}
}
async function defaultOnSubmit(form, values) {
const { action, method } = form;
const response = await fetch(action, {
method,
body: JSON.stringify(values),
headers: { 'Content-Type': 'application/json' },
});
if (response.ok)
return;
throw new FelteSubmitError('An error occurred while the form was being submitted.', await response.json().catch(() => undefined));
}
function createFormAction({ helpers, stores, config, extender, _setFormNode, _getFormNode, _getInitialValues, _setCurrentExtenders, _getCurrentExtenders, }) {
const { setFields, setTouched, reset, validate, addValidator, addWarnValidator, addTransformer, setInitialValues, } = helpers;
const { data, errors, warnings, touched, isSubmitting, isDirty } = stores;
function createSubmitHandler(altConfig) {
var _a, _b, _c, _d;
const onSubmit = (_a = altConfig === null || altConfig === void 0 ? void 0 : altConfig.onSubmit) !== null && _a !== void 0 ? _a : config.onSubmit;
const validate = (_b = altConfig === null || altConfig === void 0 ? void 0 : altConfig.validate) !== null && _b !== void 0 ? _b : config.validate;
const warn = (_c = altConfig === null || altConfig === void 0 ? void 0 : altConfig.warn) !== null && _c !== void 0 ? _c : config.warn;
const onError = (_d = altConfig === null || altConfig === void 0 ? void 0 : altConfig.onError) !== null && _d !== void 0 ? _d : config.onError;
return async function handleSubmit(event) {
const formNode = _getFormNode();
if (!formNode && !onSubmit)
return;
event === null || event === void 0 ? void 0 : event.preventDefault();
isSubmitting.set(true);
const currentData = get(data);
const currentErrors = await executeValidation(currentData, validate);
const currentWarnings = await executeValidation(currentData, warn);
if (currentWarnings)
warnings.set(_merge(deepSet(currentData, null), currentWarnings));
touched.update((t) => {
return deepSet(t, true);
});
if (currentErrors) {
errors.set(currentErrors);
const hasErrors = deepSome(currentErrors, (error) => !!error);
if (hasErrors) {
_getCurrentExtenders().forEach((extender) => {
var _a;
return (_a = extender.onSubmitError) === null || _a === void 0 ? void 0 : _a.call(extender, {
data: currentData,
errors: currentErrors,
});
});
isSubmitting.set(false);
return;
}
}
try {
if (onSubmit) {
await onSubmit(currentData, {
form: formNode,
controls: formNode && Array.from(formNode.elements).filter(isFormControl),
config: Object.assign(Object.assign({}, config), altConfig),
});
}
else if (formNode) {
await defaultOnSubmit(formNode, currentData);
}
}
catch (e) {
if (!onError)
throw e;
const serverErrors = onError(e);
if (serverErrors) {
errors.set(serverErrors);
_getCurrentExtenders().forEach((extender) => {
var _a;
return (_a = extender.onSubmitError) === null || _a === void 0 ? void 0 : _a.call(extender, {
data: currentData,
errors: serverErrors,
});
});
}
}
finally {
isSubmitting.set(false);
}
};
}
const handleSubmit = createSubmitHandler();
function form(node) {
if (!node.requestSubmit)
node.requestSubmit = handleSubmit;
function callExtender(stage) {
return function (extender) {
return extender({
form: node,
stage,
controls: Array.from(node.elements).filter(isFormControl),
data,
errors,
warnings,
touched,
config,
addValidator,
addWarnValidator,
addTransformer,
setFields,
validate,
reset,
});
};
}
function proxyInputs() {
for (const control of Array.from(node.elements).filter(isFormControl)) {
if (shouldIgnore(control) || !control.name)
continue;
let propName = 'value';
if (isInputElement(control) &&
['checkbox', 'radio'].includes(control.type)) {
propName = 'checked';
}
if (isInputElement(control) && control.type === 'file') {
propName = 'files';
}
const prop = Object.getOwnPropertyDescriptor(isSelectElement(control)
? HTMLSelectElement.prototype
: isTextAreaElement(control)
? HTMLTextAreaElement.prototype
: HTMLInputElement.prototype, propName);
Object.defineProperty(control, propName, {
configurable: true,
set(newValue) {
var _a;
(_a = prop === null || prop === void 0 ? void 0 : prop.set) === null || _a === void 0 ? void 0 : _a.call(control, newValue);
if (isInputElement(control)) {
if (control.type === 'checkbox')
return setCheckboxValues(control);
if (control.type === 'radio')
return setRadioValues(control);
if (control.type === 'file')
return setFileValue(control);
}
const inputValue = isSelectElement(control)
? control.value
: getInputTextOrNumber(control);
data.update(($data) => {
return _set($data, getPath(control), inputValue);
});
},
get: prop === null || prop === void 0 ? void 0 : prop.get,
});
}
}
_setCurrentExtenders(extender.map(callExtender('MOUNT')));
node.noValidate = !!config.validate;
const { defaultData } = getFormDefaultValues(node);
_setFormNode(node);
setInitialValues(_merge(_cloneDeep(defaultData), _getInitialValues()));
setFields(_getInitialValues());
touched.set(deepSet(_getInitialValues(), false));
function setCheckboxValues(target) {
const index = getIndex(target);
const elPath = getPath(target);
const checkboxes = Array.from(node.querySelectorAll(`[name="${target.name}"]`)).filter((checkbox) => {
if (!isFormControl(checkbox))
return false;
if (typeof index !== 'undefined') {
const felteIndex = Number(checkbox.dataset.felteIndex);
return felteIndex === index;
}
return elPath === getPath(checkbox);
});
if (checkboxes.length === 0)
return;
if (checkboxes.length === 1) {
return data.update(($data) => _set($data, getPath(target), target.checked));
}
return data.update(($data) => {
return _set($data, getPath(target), checkboxes
.filter(isInputElement)
.filter((el) => el.checked)
.map((el) => el.value));
});
}
function setRadioValues(target) {
const radios = node.querySelectorAll(`[name="${target.name}"]`);
const checkedRadio = Array.from(radios).find((el) => isInputElement(el) && el.checked);
data.update(($data) => _set($data, getPath(target), checkedRadio === null || checkedRadio === void 0 ? void 0 : checkedRadio.value));
}
function setFileValue(target) {
const files = target.files;
data.update(($data) => {
return _set($data, getPath(target), target.multiple ? Array.from(files !== null && files !== void 0 ? files : []) : files === null || files === void 0 ? void 0 : files[0]);
});
}
function handleInput(e) {
var _a;
const target = e.target;
if (!target ||
!isFormControl(target) ||
isSelectElement(target) ||
shouldIgnore(target))
return;
if (['checkbox', 'radio', 'file'].includes(target.type))
return;
if (!target.name)
return;
if ((_a = config.touchTriggerEvents) === null || _a === void 0 ? void 0 : _a.input)
setTouched(getPath(target), true);
isDirty.set(true);
const inputValue = getInputTextOrNumber(target);
data.update(($data) => {
return _set($data, getPath(target), inputValue);
});
}
function handleChange(e) {
var _a;
const target = e.target;
if (!target || !isFormControl(target) || shouldIgnore(target))
return;
if (!target.name)
return;
if ((_a = config.touchTriggerEvents) === null || _a === void 0 ? void 0 : _a.change)
setTouched(getPath(target), true);
if (isSelectElement(target) ||
['checkbox', 'radio', 'file'].includes(target.type)) {
isDirty.set(true);
}
if (isSelectElement(target)) {
data.update(($data) => {
return _set($data, getPath(target), target.value);
});
}
if (!isInputElement(target))
return;
if (target.type === 'checkbox')
setCheckboxValues(target);
if (target.type === 'radio')
setRadioValues(target);
if (target.type === 'file')
setFileValue(target);
}
function handleBlur(e) {
var _a;
const target = e.target;
if (!target || !isFormControl(target) || shouldIgnore(target))
return;
if (!target.name)
return;
if ((_a = config.touchTriggerEvents) === null || _a === void 0 ? void 0 : _a.blur)
setTouched(getPath(target), true);
}
const mutationOptions = { childList: true, subtree: true };
function unsetTaggedForRemove(formControls) {
for (const control of formControls) {
if (control.hasAttribute('data-felte-keep-on-remove') &&
control.dataset.felteKeepOnRemove !== 'false')
continue;
data.update(($data) => {
return _unset($data, getPathFromDataset(control));
});
touched.update(($touched) => {
return _unset($touched, getPathFromDataset(control));
});
errors.update(($errors) => {
return _unset($errors, getPathFromDataset(control));
});
warnings.update(($warnings) => {
return _unset($warnings, getPathFromDataset(control));
});
}
}
function mutationCallback(mutationList) {
for (const mutation of mutationList) {
if (mutation.type !== 'childList')
continue;
if (mutation.addedNodes.length > 0) {
proxyInputs();
const shouldUpdate = Array.from(mutation.addedNodes).some((node) => {
if (!isElement(node))
return false;
if (isFormControl(node))
return true;
const formControls = getFormControls(node);
return formControls.length > 0;
});
if (!shouldUpdate)
continue;
_getCurrentExtenders().forEach((extender) => { var _a; return (_a = extender.destroy) === null || _a === void 0 ? void 0 : _a.call(extender); });
_setCurrentExtenders(extender.map(callExtender('UPDATE')));
const { defaultData: newDefaultData } = getFormDefaultValues(node);
const newDefaultTouched = deepSet(newDefaultData, false);
data.update(($data) => _defaultsDeep($data, newDefaultData));
touched.update(($touched) => {
return _defaultsDeep($touched, newDefaultTouched);
});
}
if (mutation.removedNodes.length > 0) {
for (const removedNode of mutation.removedNodes) {
if (!isElement(removedNode))
continue;
const formControls = getFormControls(removedNode);
if (formControls.length === 0)
continue;
_getCurrentExtenders().forEach((extender) => { var _a; return (_a = extender.destroy) === null || _a === void 0 ? void 0 : _a.call(extender); });
_setCurrentExtenders(extender.map(callExtender('UPDATE')));
unsetTaggedForRemove(formControls);
}
}
}
}
const observer = new MutationObserver(mutationCallback);
observer.observe(node, mutationOptions);
proxyInputs();
node.addEventListener('input', handleInput);
node.addEventListener('change', handleChange);
node.addEventListener('focusout', handleBlur);
node.addEventListener('submit', handleSubmit);
const unsubscribeErrors = errors.subscribe(($errors) => {
for (const el of node.elements) {
if (!isFormControl(el) || !el.name)
continue;
const fieldErrors = _get($errors, getPath(el));
const message = Array.isArray(fieldErrors)
? fieldErrors.join('\n')
: typeof fieldErrors === 'string'
? fieldErrors
: undefined;
if (message === el.dataset.felteValidationMessage)
continue;
if (message) {
el.dataset.felteValidationMessage = message;
el.setAttribute('aria-invalid', 'true');
}
else {
delete el.dataset.felteValidationMessage;
el.removeAttribute('aria-invalid');
}
}
});
return {
destroy() {
observer.disconnect();
node.removeEventListener('input', handleInput);
node.removeEventListener('change', handleChange);
node.removeEventListener('focusout', handleBlur);
node.removeEventListener('submit', handleSubmit);
unsubscribeErrors();
_getCurrentExtenders().forEach((extender) => { var _a; return (_a = extender.destroy) === null || _a === void 0 ? void 0 : _a.call(extender); });
},
};
}
return {
form,
createSubmitHandler,
handleSubmit,
};
}
function isUpdater(value) {
return typeof value === 'function';
}
function createSetHelper(storeSetter) {
const setHelper = (pathOrValue, valueOrUpdater) => {
if (typeof pathOrValue === 'string') {
const path = pathOrValue;
storeSetter((oldValue) => {
const newValue = isUpdater(valueOrUpdater)
? valueOrUpdater(_get(oldValue, path))
: valueOrUpdater;
return _set(oldValue, path, newValue);
});
}
else {
storeSetter((oldValue) => isUpdater(pathOrValue) ? pathOrValue(oldValue) : pathOrValue);
}
};
return setHelper;
}
function createHelpers({ stores, config, }) {
var _a;
const { data, touched, errors, warnings, isDirty, isSubmitting } = stores;
const setData = createSetHelper(data.update);
const setTouched = createSetHelper(touched.update);
const setErrors = createSetHelper(errors.update);
const setWarnings = createSetHelper(warnings.update);
function updateFields(updater) {
setData((oldData) => {
const newData = updater(oldData);
if (formNode)
setForm(formNode, newData);
return newData;
});
}
const setFields = (pathOrValue, valueOrUpdater, shouldTouch) => {
const fieldsSetter = createSetHelper(updateFields);
fieldsSetter(pathOrValue, valueOrUpdater);
if (typeof pathOrValue === 'string' && shouldTouch) {
setTouched(pathOrValue, true);
}
};
function unsetField(path) {
data.update(($data) => {
const newData = _unset($data, path);
if (formNode)
setForm(formNode, newData);
return newData;
});
touched.update(($touched) => {
return _unset($touched, path);
});
errors.update(($errors) => {
return _unset($errors, path);
});
warnings.update(($warnings) => {
return _unset($warnings, path);
});
}
function resetField(path) {
const initialValue = _get(initialValues, path);
data.update(($data) => {
const newData = _set($data, path, initialValue);
if (formNode)
setForm(formNode, newData);
return newData;
});
touched.update(($touched) => {
return _set($touched, path, false);
});
errors.update(($errors) => {
return _set($errors, path, null);
});
warnings.update(($warnings) => {
return _set($warnings, path, null);
});
}
const setIsSubmitting = createSetHelper(isSubmitting.update);
const setIsDirty = createSetHelper(isDirty.update);
async function validate() {
const currentData = get(data);
setTouched((t) => {
return deepSet(t, true);
});
const currentErrors = await executeValidation(currentData, config.validate);
const currentWarnings = await executeValidation(currentData, config.warn);
warnings.set(_merge(deepSet(currentData, null), currentWarnings || {}));
errors.set(currentErrors || {});
return currentErrors;
}
let formNode;
let initialValues = ((_a = config.initialValues) !== null && _a !== void 0 ? _a : {});
function reset() {
setFields(_cloneDeep(initialValues));
setTouched(($touched) => deepSet($touched, false));
isDirty.set(false);
}
return {
public: {
setData,
setFields,
setTouched,
setErrors,
setWarnings,
setIsSubmitting,
setIsDirty,
validate,
reset,
unsetField,
resetField,
setInitialValues: (values) => {
initialValues = values;
},
},
private: {
_setFormNode: (node) => {
formNode = node;
},
_getFormNode: () => formNode,
_getInitialValues: () => initialValues,
},
};
}
function errorFilterer(errValue, touchValue) {
if (_isPlainObject(touchValue))
return;
if (Array.isArray(touchValue)) {
if (touchValue.some(_isPlainObject))
return;
const errArray = Array.isArray(errValue) ? errValue : [];
return touchValue.map((value, index) => (value && errArray[index]) || null);
}
return (touchValue && errValue) || null;
}
function createStores(storeFactory, config) {
const initialValues = config.initialValues
? executeTransforms(_cloneDeep(config.initialValues), config.transform)
: {};
const data = storeFactory(initialValues);
const initialErrors = deepSet(initialValues, null);
const errors = storeFactory(initialErrors);
const filteredErrors = storeFactory(_cloneDeep(initialErrors));
const filteredErrorsSet = filteredErrors.set;
const initialWarnings = deepSet(initialValues, null);
const warnings = storeFactory(initialWarnings);
const initialTouched = deepSet(initialValues, false);
const touched = storeFactory(initialTouched);
const isValid = storeFactory(!config.validate);
const isSubmitting = storeFactory(false);
const isDirty = storeFactory(false);
async function validateErrors($data) {
let currentErrors = {};
if (!config.validate || !$data)
return;
currentErrors = await executeValidation($data, config.validate);
errors.set(currentErrors || {});
}
async function validateWarnings($data) {
let currentWarnings = {};
if (!config.warn || !$data)
return;
currentWarnings = await executeValidation($data, config.warn);
warnings.set(_merge(deepSet($data, null), currentWarnings || {}));
}
const dataUnsubscriber = data.subscribe(($data) => {
validateErrors($data);
validateWarnings($data);
});
let touchedValue = initialTouched;
let errorsValue = initialErrors;
let firstCalled = false;
const errorsUnsubscriber = errors.subscribe(($errors) => {
if (!firstCalled) {
firstCalled = true;
isValid.set(!config.validate);
}
else {
const hasErrors = deepSome($errors, (error) => !!error);
isValid.set(!hasErrors);
}
errorsValue = $errors;
const mergedErrors = _mergeWith($errors, touchedValue, errorFilterer);
filteredErrorsSet(mergedErrors);
});
const touchedUnsubscriber = touched.subscribe(($touched) => {
touchedValue = $touched;
const mergedErrors = _mergeWith(errorsValue, $touched, errorFilterer);
filteredErrorsSet(mergedErrors);
});
function cleanup() {
dataUnsubscriber();
errorsUnsubscriber();
touchedUnsubscriber();
}
filteredErrors.set = errors.set;
filteredErrors.update = errors.update;
return {
data,
errors: filteredErrors,
warnings,
touched,
isValid,
isSubmitting,
isDirty,
cleanup,
};
}
function createForm(config, adapters) {
var _a, _b;
(_a = config.extend) !== null && _a !== void 0 ? _a : (config.extend = []);
(_b = config.touchTriggerEvents) !== null && _b !== void 0 ? _b : (config.touchTriggerEvents = { change: true, blur: true });
if (config.validate && !Array.isArray(config.validate))
config.validate = [config.validate];
if (config.transform && !Array.isArray(config.transform))
config.transform = [config.transform];
if (config.warn && !Array.isArray(config.warn))
config.warn = [config.warn];
function addValidator(validator) {
if (!config.validate) {
config.validate = [validator];
}
else {
config.validate = [
...config.validate,
validator,
];
}
}
function addWarnValidator(validator) {
if (!config.warn) {
config.warn = [validator];
}
else {
config.warn = [...config.warn, validator];
}
}
function addTransformer(transformer) {
if (!config.transform) {
config.transform = [transformer];
}
else {
config.transform = [
...config.transform,
transformer,
];
}
}
const extender = Array.isArray(config.extend)
? config.extend
: [config.extend];
let currentExtenders = [];
const { isSubmitting, data, errors, warnings, touched, isValid, isDirty, cleanup, } = createStores(adapters.storeFactory, config);
const originalUpdate = data.update;
const originalSet = data.set;
const transUpdate = (updater) => originalUpdate((values) => executeTransforms(updater(values), config.transform));
const transSet = (values) => originalSet(executeTransforms(values, config.transform));
const clonedData = Object.assign(Object.assign({}, data), { set: transSet, update: transUpdate });
data.update = transUpdate;
const helpers = createHelpers({
extender,
config,
addValidator,
addTransformer,
stores: {
data: clonedData,
errors,
warnings,
touched,
isValid,
isSubmitting,
isDirty,
},
});
currentExtenders = extender.map((extender) => extender({
stage: 'SETUP',
errors,
warnings,
touched,
data: clonedData,
config,
addValidator,
addWarnValidator,
addTransformer,
setFields: helpers.public.setFields,
reset: helpers.public.reset,
validate: helpers.public.validate,
}));
const _getCurrentExtenders = () => currentExtenders;
const _setCurrentExtenders = (extenders) => {
currentExtenders = extenders;
};
function dataSetCustomizer(dataValue, initialValue) {
if (_isPlainObject(dataValue))
return;
return dataValue !== initialValue;
}
function dataSetTouchedCustomizer(dataValue, touchedValue) {
if (_isPlainObject(dataValue))
return;
return touchedValue || dataValue;
}
function newDataSet(values) {
touched.update((current) => {
const changed = _mergeWith(_cloneDeep(values), config.initialValues, dataSetCustomizer);
return _mergeWith(changed, current, dataSetTouchedCustomizer);
});
isDirty.set(true);
return clonedData.set(values);
}
const { form, createSubmitHandler, handleSubmit } = createFormAction(Object.assign({ config, stores: {
data: clonedData,
touched,
errors,
warnings,
isSubmitting,
isValid,
isDirty,
}, helpers: Object.assign(Object.assign({}, helpers.public), { addTransformer,
addValidator,
addWarnValidator }), extender,
_getCurrentExtenders,
_setCurrentExtenders }, helpers.private));
data.set = newDataSet;
return Object.assign({ data,
errors,
warnings,
touched,
isValid,
isSubmitting,
isDirty,
form,
handleSubmit,
createSubmitHandler,
cleanup }, helpers.public);
}
function noop() { }
function safe_not_equal(a, b) {
return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');
}
function subscribe(store, ...callbacks) {
if (store == null) {
return noop;
}
const unsub = store.subscribe(...callbacks);
return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;
}
function get_store_value(store) {
let value;
subscribe(store, _ => value = _)();
return value;
}
const subscriber_queue = [];
/**
* Create a `Writable` store that allows both updating and reading by subscription.
* @param {*=}value initial value
* @param {StartStopNotifier=}start start and stop notifications for subscriptions
*/
function writable(value, start = noop) {
let stop;
const subscribers = new Set();
function set(new_value) {
if (safe_not_equal(value, new_value)) {
value = new_value;
if (stop) { // store is ready
const run_queue = !subscriber_queue.length;
for (const subscriber of subscribers) {
subscriber[1]();
subscriber_queue.push(subscriber, value);
}
if (run_queue) {
for (let i = 0; i < subscriber_queue.length; i += 2) {
subscriber_queue[i][0](subscriber_queue[i + 1]);
}
subscriber_queue.length = 0;
}
}
}
}
function update(fn) {
set(fn(value));
}
function subscribe(run, invalidate = noop) {
const subscriber = [run, invalidate];
subscribers.add(subscriber);
if (subscribers.size === 1) {
stop = start(set) || noop;
}
run(value);
return () => {
subscribers.delete(subscriber);
if (subscribers.size === 0) {
stop();
stop = null;
}
};
}
return { set, update, subscribe };
}
function isWritable(store) {
return !!store.set;
}
function useAccessor(store) {
const [, setUpdate] = react.useState({});
const currentValue = react.useRef(get_store_value(store));
const values = react.useRef({});
const subscribedRef = react.useRef(false);
const accessor = react.useCallback((selectorOrPath) => {
var _a;
const subscribed = subscribedRef.current;
if (!selectorOrPath) {
subscribedRef.current = true;
return currentValue.current;
}
if (typeof subscribed === 'boolean') {
subscribedRef.current || (subscribedRef.current = {
[selectorOrPath.toString()]: selectorOrPath,
});
}
else {
subscribed[selectorOrPath.toString()] = selectorOrPath;
}
return ((_a = values.current[selectorOrPath.toString()]) !== null && _a !== void 0 ? _a : getValue(currentValue.current, selectorOrPath));
}, []);
accessor.subscribe = store.subscribe;
if (isWritable(store)) {
accessor.set = store.set;
accessor.update = store.update;
}
react.useEffect(() => {
return store.subscribe(($store) => {
currentValue.current = $store;
if (!subscribedRef.current)
return;
if (subscribedRef.current === true)
return setUpdate({});
let hasChanged = false;
const keys = Object.keys(subscribedRef.current);
for (const key of keys) {
const selector = subscribedRef.current[key];
const newValue = getValue($store, selector);
if (typeof values.current[selector.toString()] === 'undefined') {
values.current[selector.toString()] = newValue;
}
if (newValue !== values.current[selector.toString()]) {
values.current[selector.toString()] = newValue;
hasChanged = true;
}
}
if (hasChanged)
setUpdate({});
});
}, []);
return accessor;
}
/*! *****************************************************************************

@@ -15,3 +1330,63 @@ Copyright (c) Microsoft Corporation.

PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */function t(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(n=Object.getOwnPropertySymbols(e);i<n.length;i++)t.indexOf(n[i])<0&&Object.prototype.propertyIsEnumerable.call(e,n[i])&&(r[n[i]]=e[n[i]])}return r}function r(e){return"[object Object]"===Object.prototype.toString.call(e)}function n(e){return Object.keys(e||{}).reduce(((t,i)=>Object.assign(Object.assign({},t),{[i]:r(e[i])?n(e[i]):Array.isArray(e[i])?[...e[i]]:e[i]})),{})}function i(e,t){return function(e,t){return Object.keys(e).reduce(((r,n)=>Object.assign(Object.assign({},r),{[n]:t(e[n])})),{})}(e,(e=>r(e)?i(e,t):Array.isArray(e)?e.map(function(e){return function(t){return r(t)?i(t,e):e}}(t)):t))}function o(...e){const t=e.pop(),a=n(e.shift());if(0===e.length)return a;for(const u of e){if(!u)continue;const e=Object.keys(u);for(const s of e){const e=t(a[s],u[s]);if(void 0!==e)a[s]=e;else if(r(u[s])&&r(a[s]))a[s]=o(a[s],u[s],t);else if(Array.isArray(u[s]))a[s]=u[s].map(((e,n)=>r(e)?o(Array.isArray(a[s])?a[s][n]:a[s],e,t):e));else if(r(u[s])){const e=i(n(u[s]),void 0);a[s]=o(e,u[s],t)}else void 0!==u[s]&&(a[s]=u[s])}}return a}function a(e,t){if(!r(e)||!r(t)){if(Array.isArray(t)){if(t.some(r))return;const n=Array.isArray(e)?e:[];return t.map(((e,t)=>{var r;return null!==(r=n[t])&&void 0!==r?r:e}))}return void 0!==e?e:void 0}}function u(...e){return o(...e,a)}function s(...e){return o(...e,(()=>{}))}function c(e,t,r){const n=r=>String.prototype.split.call(t,r).filter(Boolean).reduce(((e,t)=>null!=e?e[t]:e),e),i=n(/[,[\]]+?/)||n(/[,[\].]+?/);return void 0===i||i===e?r:i}function l(e,t,r){Object(e)!==e?e={}:void 0!==e&&(e=n(e));let i=Array.isArray(t)?t:t.toString().match(/[^.[\]]+/g)||[];return i.slice(0,-1).reduce(((e,t,r)=>Object(e[t])===e[t]?e[t]:e[t]=Math.abs(Number(i[r+1]))>>0==+i[r+1]?[]:{}),e)[i[i.length-1]]=r,e}function d(e,t){var r;if(Object(e)!==e)return;void 0!==e&&(e=n(e));let i=Array.isArray(t)?t:t.toString().match(/[^.[\]]+/g)||[];return null===(r=i.slice(0,-1).reduce(((e,t)=>Object(e[t])===e[t]?e[t]:void 0),e))||void 0===r||delete r[i[i.length-1]],e}function f(e,t,r){Object(e)!==e&&(e={});let n=t.toString().match(/[^.[\]]+/g)||[];return n.slice(0,-1).reduce(((e,t,r)=>Object(e[t])===e[t]?e[t]:e[t]=Math.abs(Number(n[r+1]))>>0==+n[r+1]?[]:{}),e)[n[n.length-1]]=r(c(e,t)),e}function v(e,t){return function(e,t){return Object.keys(e).some((r=>t(e[r])))}(e,(e=>r(e)?v(e,t):t(e)))}function m(e){return e.hasAttribute("data-felte-index")?Number(e.dataset.felteIndex):void 0}function y(e){var t;return"INPUT"===(null===(t=e)||void 0===t?void 0:t.nodeName)}function b(e){var t;return"TEXTAREA"===(null===(t=e)||void 0===t?void 0:t.nodeName)}function p(e){var t;return"SELECT"===(null===(t=e)||void 0===t?void 0:t.nodeName)}function g(e){var t;return"FIELDSET"===(null===(t=e)||void 0===t?void 0:t.nodeName)}function h(e){return y(e)||b(e)||p(e)}function A(e){return e.nodeType===Node.ELEMENT_NODE}function O(e,t){const r=m(e);let n="";t?n=t:h(e)&&(n=e.name),n=void 0===r?n:`${n}[${r}]`;let i=e.parentNode;if(!i)return n;for(;i&&"FORM"!==i.nodeName;){if(g(i)&&i.name){const e=m(i);n=`${void 0===e?i.name:`${i.name}[${e}]`}.${n}`}i=i.parentNode}return n}function E(e){const t=e.dataset.felteFieldset,r=m(e),n=void 0===r?e.name:`${e.name}[${r}]`;return t?`${t}.${n}`:n}function S(e){let t=e;for(;t&&"FORM"!==t.nodeName;){if(t.hasAttribute("data-felte-ignore"))return!0;t=t.parentElement}return!1}function w(e,t){return r(e)&&t?"string"==typeof t?c(e,t):t(e):e}function j(e){if(h(e))return[e];if(0===e.childElementCount)return[];const t=new Set;for(const r of e.children){if(h(r)&&t.add(r),g(r))for(const e of r.elements)h(e)&&t.add(e);r.childElementCount>0&&j(r).forEach((e=>t.add(e)))}return Array.from(t)}function x(e){for(const t of e.elements)if(h(t)||g(t)){if(e.name&&t.name){const r=m(e),n=void 0===r?e.name:`${e.name}[${r}]`;t.dataset.felteFieldset=e.dataset.felteFieldset?`${e.dataset.felteFieldset}.${n}`:n}"true"!==e.dataset.felteUnsetOnRemove||t.hasAttribute("data-felte-unset-on-remove")||(t.dataset.felteUnsetOnRemove="true")}}function V(e){return e.type.match(/^(number|range)$/)?e.value?+e.value:void 0:e.value}function k(e){var t;let r={};for(const n of e.elements){if(g(n)&&x(n),!h(n)||!n.name)continue;const i=O(n),o=m(n);if(y(n)){if("checkbox"===n.type){if(void 0===c(r,i)){if(1===Array.from(e.querySelectorAll(`[name="${n.name}"]`)).filter((e=>!!h(e)&&(void 0!==o?Number(e.dataset.felteIndex)===o:i===O(e)))).length){r=l(r,i,n.checked);continue}r=l(r,i,n.checked?[n.value]:[]);continue}Array.isArray(c(r,i))&&n.checked&&f(r,i,(e=>(void 0===o||Array.isArray(e)||(e=[]),[...e,n.value])));continue}if("radio"===n.type){if(c(r,i))continue;r=l(r,i,n.checked?n.value:void 0);continue}if("file"===n.type){r=l(r,i,n.multiple?Array.from(n.files||[]):null===(t=n.files)||void 0===t?void 0:t[0]);continue}}const a=V(n);r=l(r,i,a)}return{defaultData:r}}function N(e,t){if(!h(e))return;const r=t;if(y(e)){if("checkbox"===e.type){const t=r;return void 0===t||"boolean"==typeof t?void(e.checked=!!t):void(Array.isArray(t)&&(t.includes(e.value)?e.checked=!0:e.checked=!1))}if("radio"===e.type){const t=r;return void(e.value===t?e.checked=!0:e.checked=!1)}if("file"===e.type)return e.files=null,void(e.value="")}e.value=String(null!=r?r:"")}function T(e,t){for(const r of e.elements)g(r)&&x(r),h(r)&&r.name&&N(r,c(t,O(r)))}function F(e,t){if(!r(e)&&!r(t)){if(null===e)return t;if(null===t)return e;if(e&&t)return Array.isArray(e)||(e=[e]),Array.isArray(t)||(t=[t]),[...e,...t]}}async function D(e,t){if(t)return Array.isArray(t)?o(...await Promise.all(t.map((t=>t(e)))),F):t(e)}function $(e,t){return t?Array.isArray(t)?t.reduce(((e,t)=>t(e)),e):t(e):e}function L(e){let t;return function(e,...t){const r=e.subscribe(...t);return r.unsubscribe?()=>r.unsubscribe():r}(e,(e=>t=e))(),t}function I(e){return"function"==typeof e}function M(e){return(t,r)=>{if("string"==typeof t){const n=t;e((e=>{const t=I(r)?r(c(e,n)):r;return l(e,n,t)}))}else e((e=>I(t)?t(e):t))}}function _(e,t){if(!r(t)){if(Array.isArray(t)){if(t.some(r))return;const n=Array.isArray(e)?e:[];return t.map(((e,t)=>e&&n[t]||null))}return t&&e||null}}function R(e,t){const r=t.initialValues?n(t.initialValues):{},a=e(r),u=i(r,null),c=e(u),l=e(n(u)),d=e(i(r,null)),f=i(r,!1),m=e(f),y=e(!t.validate),b=e(!1),p=e(!1),g=a.subscribe((e=>{!async function(e){let r={};t.validate&&e&&(r=await D(e,t.validate),c.set(r||{}))}(e),async function(e){let r={};t.warn&&e&&(r=await D(e,t.warn),d.set(s(i(e,null),r||{})))}(e)}));let h=f,A=u,O=!1;const E=c.subscribe((e=>{if(O){const t=v(e,(e=>!!e));y.set(!t)}else O=!0,y.set(!t.validate);A=e;const r=o(e,h,_);l.set(r)})),S=m.subscribe((e=>{h=e;const t=o(A,e,_);l.set(t)}));return{data:a,errors:Object.assign(Object.assign({},l),{set:c.set,update:c.update,subscribe:l.subscribe}),warnings:d,touched:m,isValid:y,isSubmitting:b,isDirty:p,cleanup:function(){g(),E(),S()}}}function C(){}function P(e){let t;return function(e,...t){if(null==e)return C;const r=e.subscribe(...t);return r.unsubscribe?()=>r.unsubscribe():r}(e,(e=>t=e))(),t}const q=[];function H(e,t=C){let r;const n=new Set;function i(t){if(function(e,t){return e!=e?t==t:e!==t||e&&"object"==typeof e||"function"==typeof e}(e,t)&&(e=t,r)){const t=!q.length;for(const t of n)t[1](),q.push(t,e);if(t){for(let e=0;e<q.length;e+=2)q[e][0](q[e+1]);q.length=0}}}return{set:i,update:function(t){i(t(e))},subscribe:function(o,a=C){const u=[o,a];return n.add(u),1===n.size&&(r=t(i)||C),o(e),()=>{n.delete(u),0===n.size&&(r(),r=null)}}}}function W(t){const[,r]=e.useState({}),n=e.useRef(P(t)),i=e.useRef({}),o=e.useRef(!1),a=e.useCallback((e=>{var t;const r=o.current;return e?("boolean"==typeof r?o.current||(o.current=[e]):r.every((t=>t.toString()!==e.toString()))&&r.push(e),null!==(t=i.current[e.toString()])&&void 0!==t?t:w(n.current,e)):(o.current=!0,n.current)}),[]);return e.useEffect((()=>t.subscribe((e=>{if(n.current=e,!o.current)return;if(!0===o.current)return r({});let t=!1;for(const r of o.current){const n=w(e,r);void 0===i.current[r.toString()]&&(i.current[r.toString()]=n),n!==i.current[r.toString()]&&(i.current[r.toString()]=n,t=!0)}t&&r({})}))),[]),a}exports.useForm=function(a){const f=e.useRef(),g=function(t){const r=e.useRef();return void 0===r.current&&(r.current=t()),r.current}((()=>{const e=function(e,t){var a,f;function g(t){e.validate?e.validate=[...e.validate,t]:e.validate=[t]}function w(t){e.warn?e.warn=[...e.warn,t]:e.warn=[t]}function x(t){e.transform?e.transform=[...e.transform,t]:e.transform=[t]}null!==(a=e.extend)&&void 0!==a||(e.extend=[]),null!==(f=e.touchTriggerEvents)&&void 0!==f||(e.touchTriggerEvents={change:!0,blur:!0}),e.validate&&!Array.isArray(e.validate)&&(e.validate=[e.validate]),e.transform&&!Array.isArray(e.transform)&&(e.transform=[e.transform]),e.warn&&!Array.isArray(e.warn)&&(e.warn=[e.warn]);const N=Array.isArray(e.extend)?e.extend:[e.extend];let F=[];const{isSubmitting:I,data:_,errors:C,warnings:P,touched:q,isValid:H,isDirty:W,cleanup:U}=R(t.storeFactory,e),z=_.update,B=_.set;_.update=t=>z((r=>$(t(r),e.transform))),_.set=t=>B($(t,e.transform));const X=function({stores:e,config:t}){var r;const{data:o,touched:a,errors:u,warnings:c,isDirty:l,isSubmitting:d}=e,f=M(o.update),v=M(a.update),m=M(u.update),y=M(c.update);function b(e){f((t=>{const r=e(t);return A&&T(A,r),r}))}const p=(e,t,r)=>{M(b)(e,t),"string"==typeof e&&r&&v(e,!0)},g=M(d.update),h=M(l.update);let A,O=null!==(r=t.initialValues)&&void 0!==r?r:{};return{public:{reset:function(){p(n(O)),v((e=>i(e,!1))),l.set(!1)},setData:f,setFields:p,setTouched:v,setErrors:m,setWarnings:y,setIsSubmitting:g,setIsDirty:h,validate:async function(){const e=L(o);v((e=>i(e,!0)));const r=await D(e,t.validate),n=await D(e,t.warn);return c.set(s(i(e,null),n||{})),u.set(r||{}),r},setInitialValues:e=>{O=e}},private:{_setFormNode:e=>{A=e},_getFormNode:()=>A,_getInitialValues:()=>O}}}({extender:N,config:e,addValidator:g,addTransformer:x,stores:{data:_,errors:C,warnings:P,touched:q,isValid:H,isSubmitting:I,isDirty:W}});function G(e,t){if(!r(e))return e!==t}function J(e,t){if(!r(e))return t||e}F=N.map((t=>t({errors:C,warnings:P,touched:q,data:_,config:e,addValidator:g,addWarnValidator:w,addTransformer:x,setFields:X.public.setFields,reset:X.public.reset,validate:X.public.validate})));const{form:K,createSubmitHandler:Q,handleSubmit:Y}=function({helpers:e,stores:t,config:r,extender:o,_setFormNode:a,_getFormNode:f,_getInitialValues:g,_setCurrentExtenders:w,_getCurrentExtenders:x}){const{setFields:N,setTouched:T,reset:F,validate:$,addValidator:I,addWarnValidator:M,addTransformer:_,setInitialValues:R}=e,{data:C,errors:P,warnings:q,touched:H,isSubmitting:W,isDirty:U}=t;function z(e){var t,n,o,a;const u=null!==(t=null==e?void 0:e.onSubmit)&&void 0!==t?t:r.onSubmit,c=null!==(n=null==e?void 0:e.validate)&&void 0!==n?n:r.validate,l=null!==(o=null==e?void 0:e.warn)&&void 0!==o?o:r.warn,d=null!==(a=null==e?void 0:e.onError)&&void 0!==a?a:r.onError;return async function(t){const n=f();null==t||t.preventDefault(),W.set(!0);const o=L(C),a=await D(o,c),m=await D(o,l);if(m&&q.set(s(i(o,null),m)),H.update((e=>i(e,!0))),a&&(P.set(a),v(a,(e=>!!e))))return x().forEach((e=>{var t;return null===(t=e.onSubmitError)||void 0===t?void 0:t.call(e,{data:o,errors:a})})),void W.set(!1);try{await u(o,{form:n,controls:n&&Array.from(n.elements).filter(h),config:Object.assign(Object.assign({},r),e)})}catch(e){if(!d)throw e;const t=d(e);t&&(P.set(t),x().forEach((e=>{var r;return null===(r=e.onSubmitError)||void 0===r?void 0:r.call(e,{data:o,errors:t})})))}finally{W.set(!1)}}}const B=z();return{form:function(e){function t(t){return t({form:e,controls:Array.from(e.elements).filter(h),data:C,errors:P,warnings:q,touched:H,config:r,addValidator:I,addWarnValidator:M,addTransformer:_,setFields:N,validate:$,reset:F})}function f(){for(const t of Array.from(e.elements).filter(h)){if(S(t)||!t.name)continue;let e="value";y(t)&&["checkbox","radio"].includes(t.type)&&(e="checked"),y(t)&&"file"===t.type&&(e="files");const r=Object.getOwnPropertyDescriptor(p(t)?HTMLSelectElement.prototype:b(t)?HTMLTextAreaElement.prototype:HTMLInputElement.prototype,e);Object.defineProperty(t,e,{configurable:!0,set(e){var n;if(null===(n=null==r?void 0:r.set)||void 0===n||n.call(t,e),y(t)){if("checkbox"===t.type)return D(t);if("radio"===t.type)return L(t);if("file"===t.type)return W(t)}const i=p(t)?t.value:V(t);C.update((e=>l(e,O(t),i)))},get:null==r?void 0:r.get})}}e.requestSubmit||(e.requestSubmit=B),w(o.map(t)),e.noValidate=!!r.validate;const{defaultData:v}=k(e);function D(t){const r=m(t),n=O(t),i=Array.from(e.querySelectorAll(`[name="${t.name}"]`)).filter((e=>!!h(e)&&(void 0!==r?Number(e.dataset.felteIndex)===r:n===O(e))));if(0!==i.length)return 1===i.length?C.update((e=>l(e,O(t),t.checked))):C.update((e=>l(e,O(t),i.filter(y).filter((e=>e.checked)).map((e=>e.value)))))}function L(t){const r=e.querySelectorAll(`[name="${t.name}"]`),n=Array.from(r).find((e=>y(e)&&e.checked));C.update((e=>l(e,O(t),null==n?void 0:n.value)))}function W(e){const t=e.files;C.update((r=>l(r,O(e),e.multiple?Array.from(null!=t?t:[]):null==t?void 0:t[0])))}function z(e){var t;const n=e.target;if(!n||!h(n)||p(n)||S(n))return;if(["checkbox","radio","file"].includes(n.type))return;if(!n.name)return;(null===(t=r.touchTriggerEvents)||void 0===t?void 0:t.input)&&T(O(n),!0),U.set(!0);const i=V(n);C.update((e=>l(e,O(n),i)))}function X(e){var t;const n=e.target;n&&h(n)&&!S(n)&&n.name&&((null===(t=r.touchTriggerEvents)||void 0===t?void 0:t.change)&&T(O(n),!0),(p(n)||["checkbox","radio","file"].includes(n.type))&&U.set(!0),p(n)&&C.update((e=>l(e,O(n),n.value))),y(n)&&("checkbox"===n.type&&D(n),"radio"===n.type&&L(n),"file"===n.type&&W(n)))}function G(e){var t;const n=e.target;n&&h(n)&&!S(n)&&n.name&&(null===(t=r.touchTriggerEvents)||void 0===t?void 0:t.blur)&&T(O(n),!0)}function J(e){for(const t of e)"true"===t.dataset.felteUnsetOnRemove&&C.update((e=>d(e,E(t))))}a(e),R(s(n(v),g())),N(g()),H.set(i(g(),!1));const K=new MutationObserver((function(r){for(const n of r)if("childList"===n.type){if(n.addedNodes.length>0){if(f(),!Array.from(n.addedNodes).some((e=>!!A(e)&&(!!h(e)||j(e).length>0))))continue;x().forEach((e=>{var t;return null===(t=e.destroy)||void 0===t?void 0:t.call(e)})),w(o.map(t));const{defaultData:r}=k(e),a=i(r,!1);C.update((e=>u(e,r))),H.update((e=>u(e,a)))}if(n.removedNodes.length>0)for(const e of n.removedNodes){if(!A(e))continue;const r=j(e);0!==r.length&&(x().forEach((e=>{var t;return null===(t=e.destroy)||void 0===t?void 0:t.call(e)})),w(o.map(t)),J(r))}}}));K.observe(e,{childList:!0,subtree:!0}),f(),e.addEventListener("input",z),e.addEventListener("change",X),e.addEventListener("focusout",G),e.addEventListener("submit",B);const Q=P.subscribe((t=>{for(const r of e.elements){if(!h(r)||!r.name)continue;const e=c(t,O(r)),n=Array.isArray(e)?e.join("\n"):"string"==typeof e?e:void 0;n!==r.dataset.felteValidationMessage&&(n?(r.dataset.felteValidationMessage=n,r.setAttribute("aria-invalid","true")):(delete r.dataset.felteValidationMessage,r.removeAttribute("aria-invalid")))}}));return{destroy(){K.disconnect(),e.removeEventListener("input",z),e.removeEventListener("change",X),e.removeEventListener("focusout",G),e.removeEventListener("submit",B),Q(),x().forEach((e=>{var t;return null===(t=e.destroy)||void 0===t?void 0:t.call(e)}))}}},createSubmitHandler:z,handleSubmit:B}}(Object.assign({config:e,stores:{data:_,touched:q,errors:C,warnings:P,isSubmitting:I,isValid:H,isDirty:W},helpers:Object.assign(Object.assign({},X.public),{addTransformer:x,addValidator:g,addWarnValidator:w}),extender:N,_getCurrentExtenders:()=>F,_setCurrentExtenders:e=>{F=e}},X.private));return Object.assign({data:Object.assign(Object.assign({},_),{set:function(t){return q.update((r=>{const i=o(n(t),e.initialValues,G);return o(i,r,J)})),W.set(!0),_.set(t)}}),errors:C,warnings:P,touched:q,isValid:H,isSubmitting:I,isDirty:W,form:K,handleSubmit:Y,createSubmitHandler:Q,cleanup:U},X.public)}(a,{storeFactory:H}),{form:g}=e,w=t(e,["form"]);return Object.assign({form:e=>{if(!e)return;const{destroy:t}=g(e);f.current=t}},w)})),{cleanup:w}=g,x=t(g,["cleanup"]),N=W(x.data),F=W(x.errors),I=W(x.touched),_=W(x.warnings),C=W(x.isSubmitting),P=W(x.isDirty),q=W(x.isValid);return e.useEffect((()=>()=>{var e;w(),null===(e=f.current)||void 0===e||e.call(f)}),[]),Object.assign(Object.assign({},x),{data:N,errors:F,warnings:_,touched:I,isSubmitting:C,isDirty:P,isValid:q})};
***************************************************************************** */
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function useConst(setup) {
const ref = react.useRef();
if (ref.current === undefined) {
ref.current = setup();
}
return ref.current;
}
function useForm(config) {
const destroyRef = react.useRef();
const _a = useConst(() => {
const _a = createForm(config !== null && config !== void 0 ? config : {}, {
storeFactory: writable,
}), { form: coreForm } = _a, rest = __rest(_a, ["form"]);
const form = (node) => {
if (!node)
return;
const { destroy } = coreForm(node);
destroyRef.current = destroy;
};
return Object.assign({ form }, rest);
}), { cleanup } = _a, rest = __rest(_a, ["cleanup"]);
const data = useAccessor(rest.data);
const errors = useAccessor(rest.errors);
const touched = useAccessor(rest.touched);
const warnings = useAccessor(rest.warnings);
const isSubmitting = useAccessor(rest.isSubmitting);
const isDirty = useAccessor(rest.isDirty);
const isValid = useAccessor(rest.isValid);
react.useEffect(() => {
return () => {
var _a;
cleanup();
(_a = destroyRef.current) === null || _a === void 0 ? void 0 : _a.call(destroyRef);
};
}, []);
return Object.assign(Object.assign({}, rest), { data,
errors,
warnings,
touched,
isSubmitting,
isDirty,
isValid });
}
exports.FelteSubmitError = FelteSubmitError;
exports.useAccessor = useAccessor;
exports.useForm = useForm;
//# sourceMappingURL=index.js.map
{
"name": "@felte/react",
"version": "0.1.0",
"version": "0.1.1",
"description": "An extensible form library for ReactJS",

@@ -9,2 +9,6 @@ "main": "dist/index.js",

"types": "dist/index.d.ts",
"type": "module",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"sideEffects": false,

@@ -59,3 +63,3 @@ "author": "Pablo Berganza <pablo@berganza.dev>",

},
"readme": "# @felte/react\n\n[![Bundle size](https://img.shields.io/bundlephobia/min/@felte/react)](https://bundlephobia.com/result?p=@felte/react)\n[![NPM Version](https://img.shields.io/npm/v/@felte/react)](https://www.npmjs.com/package/@felte/react)\n\nFelte is an extensible form library originally built for Svelte but easily integrated with React using this package. Felte, on its most simple form, only requires you to set a `ref` to your form element to work. No custom `Field`or `Form` components are needed, making custom styles really easy to do.\n\n**STATUS**: Perfectly useable, although since we are still pre 1.0.0 there might be some breaking changes between minor versions. It's recommended to keep your versions pinned and check changelogs when upgrading a minor version. It should be compatible with every other extender except the one's specifically made for a framework.\n\n## Features\n\n- Single action to make your form reactive.\n- Use HTML5 native elements to create your form. (Only the `name` attribute is necessary).\n- No re-renders at all unless you need to use a specific field's value within your component.\n- Provides stores and helper functions to handle more complex use cases.\n- No assumptions on your validation strategy. Use any validation library you want or write your own strategy.\n- Handles addition and removal of form controls during runtime.\n- Official solutions for error reporting using `reporter` packages.\n- Well tested. Currently at [99% code coverage](https://app.codecov.io/gh/pablo-abc/felte) and constantly working on improving test quality.\n- Supports validation with [yup](./packages/validator-yup/README.md), [zod](./packages/validator-zod/README.md) and [superstruct](./packages/validator-superstruct/README.md).\n- Easily [extend its functionality](https://felte.dev/docs/react/extending-felte).\n\n## Simple ussage example\n\n```jsx\nimport React, { useEffect } from 'react';\nimport { useForm } from '@felte/react';\n\nfunction Form() {\n const { form } = useForm({\n onSubmit: (values) => console.log(values),\n });\n\n return (\n <form ref={form}>\n <input name=\"email\" />\n <input name=\"password\" type=\"password\" />\n <button type=\"submit\">Submit</button>\n </form>\n );\n}\n```\n\n## Installation\n\n```sh\nnpm install --save @felte/react\n\n# Or, if you use yarn\n\nyarn add @felte/react\n```\n\n## Usage\n\nFelte exports a hook called `useForm` that accepts a configuration object with the following interface:\n\n```typescript\ntype ValidationFunction<Data extends Obj> = (\n values: Data\n) => Errors<Data> | undefined | Promise<Errors<Data> | undefined>;\n\ntype SubmitContext<Data extends Obj> = {\n form?: HTMLFormElement;\n controls?: FormControl[];\n config: FormConfig<Data>;\n};\n\ninterface FormConfig<D extends Record<string, unknown>> {\n initialValues?: D;\n validate?: ValidationFunction<Data> | ValidationFunction<Data>[];\n warn?: ValidationFunction<Data> | ValidationFunction<Data>[];\n onSubmit: (values: D, context: SubmitContext) => void;\n onError?: (errors: unknown) => void | Errors<D>;\n extend?: Extender | Extender[];\n}\n```\n\n- `initialValues` refers to the initial values of the form.\n- `validate` is a custom validation function that must return an object with the same shape as `data`, but with error messages or `undefined` as values. It can be an array of functions whose validation errors will be merged.\n- `warn` is a custom validation function that must return an object with the same shape as `data`, but with warning messages or `undefined` as values. It can be an array of functions whose validation errors will be merged.\n- `onSubmit` is the function that will be executed when the form is submited.\n- `onError` is a an optional function that will run if the submit throws an exception. It will contain the error catched. If you return an object with the same shape as `Errors`, these errors can be reported by a reporter.\n- `extend` a function or list of functions to extend Felte's behaviour. Currently it can be used to add `reporters` to Felte, these can handle error reporting for you. You can read more about them in [Felte's documentation](https://felte.dev/docs/react/reporters).\n\nWhen `useForm` is called it returns an object with everything you'll need to handle your forms. The most important property from this object is `form` which is a function that you'll need to pass as a `ref` to your `form` element. This is all you need in most cases for Felte to keep track of your form's state as lon as your fields are using native HTML input/select/textarea elements with a `name` attribute. The simple usage example shown previously showcases its use.\n\nSince all the data is handled within Felte, in a best case scenario, using this package to handle your forms won't trigger _any_ component re-renders at all unless necessary. For example, if you need the value of a field, error or warning within a component. For this, Felte's `useForm` also returns accessors to the values, warnings, errors, and other information of the store. These are used as functions that optionally accept a path or selector function to retrieve a specific property of the stores that contain objects Using selectors/paths would make it so your component _only_ re-renders when the property selected changes. E.g. let's say we have a sign-in form and we need to use the value of the `email` field of your form for some reason:\n\n```jsx\nimport { useEffect } from 'react';\nimport { useForm } from '@felte/react';\n\nfunction Form() {\n const { data, form } = useForm({ onSubmit: console.log });\n\n // We subscribe ONLY to the `email`, re-renders are not trigger if any other\n // value changes.\n // useValue also accepts a string path as a second argument,\n // but this is not type-safe if using TypeScript.\n const email = data(($data) => $data.email);\n\n useEffect(() => {\n console.log(email);\n }, [email]);\n\n return (\n <form ref={form}>\n <input name=\"email\" />\n <input name=\"password\" type=\"password\" />\n <button type=\"submit\">Submit</button>\n </form>\n )\n}\n```\n\nThese `accessors` are _NOT_ hooks, so feel free to call them wherever you want in your component. Even within your JSX! If using a selector, you can even use it to obtain derived values:\n\n```jsx\nimport { useEffect } from 'react';\nimport { useForm } from '@felte/react';\n\nfunction Form() {\n const { data, form } = useForm({\n // We set initial values so they're not `undefined` on initialization.\n initialValues: { email: '', password: '' },\n onSubmit: console.log,\n });\n\n return (\n <form ref={form}>\n <input name=\"email\" />\n <input name=\"password\" type=\"password\" />\n {/* The component will only re-render when the length of the password changes */}\n <span>Your password is {data(($data) => $data.password.length)} characters long</span>\n <button type=\"submit\">Submit</button>\n </form>\n )\n}\n```\n\nNeeding these subscription should be unnecessary in most cases, since most use-cases should be able to be done within Felte, such as validation and submission of your form values.\n\n#### Nested forms\n\nFelte supports the usage of nested objects for forms by setting the name of an input to the format of `object.prop`. It supports multiple levels. The behaviour is the same as previously explained, taking the default values from the `value` and/or `checked` attributes when appropriate.\n\n```html\n<form ref={form}>\n <input name=\"account.email\" />\n <input name=\"account.password\" />\n <input name=\"profile.firstName\" />\n <input name=\"profile.lastName\" />\n <input type=\"submit\" value=\"Create account\" />\n</form>\n```\n\nYou can also \"namespace\" the inputs using the `fieldset` tag like this:\n\n```html\n<form ref={form}>\n <fieldset name=\"account\">\n <input name=\"email\" />\n <input name=\"password\" />\n </fieldset>\n <fieldset name=\"profile\">\n <input name=\"firstName\" />\n <input name=\"lastName\" />\n </fieldset>\n <input type=\"submit\" value=\"Create account\" />\n</form>\n```\n\nBoth of these would result in a data object with this shape:\n\n```js\n{\n account: {\n email: '',\n password: '',\n },\n profile: {\n firstName: '',\n lastName: '',\n },\n}\n```\n\n#### Dynamic forms\n\nYou can freely add/remove fields from the form and Felte will handle it.\n\n```html\n<form ref={form}>\n <fieldset name=\"account\">\n <input name=\"email\">\n <input name=\"password\">\n </fieldset>\n <Show when={condition()}>\n <fieldset name=\"profile\" data-felte-unset-on-remove=true>\n <input name=\"firstName\">\n <input name=\"lastName\" data-felte-unset-on-remove=false>\n </fieldset>\n </Show>\n <input type=\"submit\" value=\"Create account\">\n</form>\n```\n\nThe `data-felte-unset-on-remove=true` tells Felte to remove the property from the data object when the HTML element is removed from the DOM. By default this is false. If you do not set this attribute to `true`, the properties from the removed elements will remain in the data object untouched.\n\nYou can set the `data-felte-unset-on-remove=true` attribute to a `fieldset` element and all the elements contained within the fieldset will be unset on removal of the node, unless any element within the fieldset element have `data-felte-unset-on-remove` set to false.\n\n> Felte takes any value that is not `true` as `false` on the `data-felte-unset-on-remove` attribute.\n"
"readme": "# @felte/react\n\n[![Bundle size](https://img.shields.io/bundlephobia/min/@felte/react)](https://bundlephobia.com/result?p=@felte/react)\n[![NPM Version](https://img.shields.io/npm/v/@felte/react)](https://www.npmjs.com/package/@felte/react)\n\nFelte is an extensible form library originally built for Svelte but easily integrated with React using this package. Felte, on its most simple form, only requires you to set a `ref` to your form element to work. No custom `Field`or `Form` components are needed, making custom styles really easy to do. You can see it in action in this [CodeSandbox demo](https://codesandbox.io/s/felte-react-demo-q2xxw?file=/src/App.js)\n\n## Features\n\n- Single action to make your form reactive.\n- Use HTML5 native elements to create your form. (Only the `name` attribute is necessary).\n- No re-renders at all unless you need to use a specific field's value within your component.\n- Provides stores and helper functions to handle more complex use cases.\n- No assumptions on your validation strategy. Use any validation library you want or write your own strategy.\n- Handles addition and removal of form controls during runtime.\n- Official solutions for error reporting using `reporter` packages.\n- Well tested. Currently at [99% code coverage](https://app.codecov.io/gh/pablo-abc/felte) and constantly working on improving test quality.\n- Supports validation with [yup](./packages/validator-yup/README.md), [zod](./packages/validator-zod/README.md) and [superstruct](./packages/validator-superstruct/README.md).\n- Easily [extend its functionality](https://felte.dev/docs/react/extending-felte).\n\n## Simple ussage example\n\n```jsx\nimport React, { useEffect } from 'react';\nimport { useForm } from '@felte/react';\n\nfunction Form() {\n const { form } = useForm({\n onSubmit: (values) => console.log(values),\n });\n\n return (\n <form ref={form}>\n <input name=\"email\" />\n <input name=\"password\" type=\"password\" />\n <button type=\"submit\">Submit</button>\n </form>\n );\n}\n```\n\nIf your `onSubmit` would only require you to send your data to a server (either via `POST` or `GET`) you don't even need an `onSubmit` handler by using the `action` and `method` attributes:\n\n```jsx\nimport React, { useEffect } from 'react';\nimport { useForm } from '@felte/react';\n\nfunction Form() {\n const { form } = useForm();\n\n return (\n <form ref={form} action=\"/example-signin\" method=\"post\">\n <input name=\"email\" />\n <input name=\"password\" type=\"password\" />\n <button type=\"submit\">Submit</button>\n </form>\n );\n}\n```\n\n## Installation\n\n```sh\nnpm install --save @felte/react\n\n# Or, if you use yarn\n\nyarn add @felte/react\n```\n\n## Usage\n\nTo learn more about how to use `@felte/react` to handle your forms, check the [official documentation](https://felte.dev/docs/react/getting-started).\n"
}

@@ -6,6 +6,4 @@ # @felte/react

Felte is an extensible form library originally built for Svelte but easily integrated with React using this package. Felte, on its most simple form, only requires you to set a `ref` to your form element to work. No custom `Field`or `Form` components are needed, making custom styles really easy to do.
Felte is an extensible form library originally built for Svelte but easily integrated with React using this package. Felte, on its most simple form, only requires you to set a `ref` to your form element to work. No custom `Field`or `Form` components are needed, making custom styles really easy to do. You can see it in action in this [CodeSandbox demo](https://codesandbox.io/s/felte-react-demo-q2xxw?file=/src/App.js)
**STATUS**: Perfectly useable, although since we are still pre 1.0.0 there might be some breaking changes between minor versions. It's recommended to keep your versions pinned and check changelogs when upgrading a minor version. It should be compatible with every other extender except the one's specifically made for a framework.
## Features

@@ -45,67 +43,13 @@

## Installation
If your `onSubmit` would only require you to send your data to a server (either via `POST` or `GET`) you don't even need an `onSubmit` handler by using the `action` and `method` attributes:
```sh
npm install --save @felte/react
# Or, if you use yarn
yarn add @felte/react
```
## Usage
Felte exports a hook called `useForm` that accepts a configuration object with the following interface:
```typescript
type ValidationFunction<Data extends Obj> = (
values: Data
) => Errors<Data> | undefined | Promise<Errors<Data> | undefined>;
type SubmitContext<Data extends Obj> = {
form?: HTMLFormElement;
controls?: FormControl[];
config: FormConfig<Data>;
};
interface FormConfig<D extends Record<string, unknown>> {
initialValues?: D;
validate?: ValidationFunction<Data> | ValidationFunction<Data>[];
warn?: ValidationFunction<Data> | ValidationFunction<Data>[];
onSubmit: (values: D, context: SubmitContext) => void;
onError?: (errors: unknown) => void | Errors<D>;
extend?: Extender | Extender[];
}
```
- `initialValues` refers to the initial values of the form.
- `validate` is a custom validation function that must return an object with the same shape as `data`, but with error messages or `undefined` as values. It can be an array of functions whose validation errors will be merged.
- `warn` is a custom validation function that must return an object with the same shape as `data`, but with warning messages or `undefined` as values. It can be an array of functions whose validation errors will be merged.
- `onSubmit` is the function that will be executed when the form is submited.
- `onError` is a an optional function that will run if the submit throws an exception. It will contain the error catched. If you return an object with the same shape as `Errors`, these errors can be reported by a reporter.
- `extend` a function or list of functions to extend Felte's behaviour. Currently it can be used to add `reporters` to Felte, these can handle error reporting for you. You can read more about them in [Felte's documentation](https://felte.dev/docs/react/reporters).
When `useForm` is called it returns an object with everything you'll need to handle your forms. The most important property from this object is `form` which is a function that you'll need to pass as a `ref` to your `form` element. This is all you need in most cases for Felte to keep track of your form's state as lon as your fields are using native HTML input/select/textarea elements with a `name` attribute. The simple usage example shown previously showcases its use.
Since all the data is handled within Felte, in a best case scenario, using this package to handle your forms won't trigger _any_ component re-renders at all unless necessary. For example, if you need the value of a field, error or warning within a component. For this, Felte's `useForm` also returns accessors to the values, warnings, errors, and other information of the store. These are used as functions that optionally accept a path or selector function to retrieve a specific property of the stores that contain objects Using selectors/paths would make it so your component _only_ re-renders when the property selected changes. E.g. let's say we have a sign-in form and we need to use the value of the `email` field of your form for some reason:
```jsx
import { useEffect } from 'react';
import React, { useEffect } from 'react';
import { useForm } from '@felte/react';
function Form() {
const { data, form } = useForm({ onSubmit: console.log });
const { form } = useForm();
// We subscribe ONLY to the `email`, re-renders are not trigger if any other
// value changes.
// useValue also accepts a string path as a second argument,
// but this is not type-safe if using TypeScript.
const email = data(($data) => $data.email);
useEffect(() => {
console.log(email);
}, [email]);
return (
<form ref={form}>
<form ref={form} action="/example-signin" method="post">
<input name="email" />

@@ -115,102 +59,18 @@ <input name="password" type="password" />

</form>
)
);
}
```
These `accessors` are _NOT_ hooks, so feel free to call them wherever you want in your component. Even within your JSX! If using a selector, you can even use it to obtain derived values:
## Installation
```jsx
import { useEffect } from 'react';
import { useForm } from '@felte/react';
```sh
npm install --save @felte/react
function Form() {
const { data, form } = useForm({
// We set initial values so they're not `undefined` on initialization.
initialValues: { email: '', password: '' },
onSubmit: console.log,
});
# Or, if you use yarn
return (
<form ref={form}>
<input name="email" />
<input name="password" type="password" />
{/* The component will only re-render when the length of the password changes */}
<span>Your password is {data(($data) => $data.password.length)} characters long</span>
<button type="submit">Submit</button>
</form>
)
}
yarn add @felte/react
```
Needing these subscription should be unnecessary in most cases, since most use-cases should be able to be done within Felte, such as validation and submission of your form values.
## Usage
#### Nested forms
Felte supports the usage of nested objects for forms by setting the name of an input to the format of `object.prop`. It supports multiple levels. The behaviour is the same as previously explained, taking the default values from the `value` and/or `checked` attributes when appropriate.
```html
<form ref={form}>
<input name="account.email" />
<input name="account.password" />
<input name="profile.firstName" />
<input name="profile.lastName" />
<input type="submit" value="Create account" />
</form>
```
You can also "namespace" the inputs using the `fieldset` tag like this:
```html
<form ref={form}>
<fieldset name="account">
<input name="email" />
<input name="password" />
</fieldset>
<fieldset name="profile">
<input name="firstName" />
<input name="lastName" />
</fieldset>
<input type="submit" value="Create account" />
</form>
```
Both of these would result in a data object with this shape:
```js
{
account: {
email: '',
password: '',
},
profile: {
firstName: '',
lastName: '',
},
}
```
#### Dynamic forms
You can freely add/remove fields from the form and Felte will handle it.
```html
<form ref={form}>
<fieldset name="account">
<input name="email">
<input name="password">
</fieldset>
<Show when={condition()}>
<fieldset name="profile" data-felte-unset-on-remove=true>
<input name="firstName">
<input name="lastName" data-felte-unset-on-remove=false>
</fieldset>
</Show>
<input type="submit" value="Create account">
</form>
```
The `data-felte-unset-on-remove=true` tells Felte to remove the property from the data object when the HTML element is removed from the DOM. By default this is false. If you do not set this attribute to `true`, the properties from the removed elements will remain in the data object untouched.
You can set the `data-felte-unset-on-remove=true` attribute to a `fieldset` element and all the elements contained within the fieldset will be unset on removal of the node, unless any element within the fieldset element have `data-felte-unset-on-remove` set to false.
> Felte takes any value that is not `true` as `false` on the `data-felte-unset-on-remove` attribute.
To learn more about how to use `@felte/react` to handle your forms, check the [official documentation](https://felte.dev/docs/react/getting-started).

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

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