svelte-formula
Advanced tools
Comparing version 0.8.7 to 0.9.0
@@ -8,2 +8,23 @@ # Changelog | ||
## [0.9.0] - 2021-03-08 | ||
### Removed | ||
- Group `reset` method - instead to reset a group of form instances call `group.init` with the initial data that was | ||
used to create the group. | ||
### Changed | ||
- Beaker now accepts `defaultValues` as an array (`group.init` will always overide this) | ||
- Internal refactoring to improve group handling | ||
- Removed global state stores for initial values, now only generated internally for reset methods | ||
- Touched and Dirty and Invalid fields now have attributes set | ||
### Fixed | ||
- `formValidity` in Beaker stores is now an array | ||
- Group stores no longer emit twice on changes | ||
- Groups correctly now use `MutationRecord` added and removed nodes to be the reference to form instances when adding | ||
and removing data | ||
## [0.8.7] - 2021-02-23 | ||
@@ -10,0 +31,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { Beaker, BeakerStores, Formula, FormulaError, FormulaOptions, FormulaStores } from './types'; | ||
import { Beaker, BeakerOptions, BeakerStores, Formula, FormulaError, FormulaOptions, FormulaStores } from './types'; | ||
export { Beaker, BeakerStores, Formula, FormulaError, FormulaOptions, FormulaStores }; | ||
@@ -29,2 +29,2 @@ /** | ||
*/ | ||
export declare function beaker<T extends Record<string, unknown | unknown[]>>(options?: FormulaOptions): Beaker<T>; | ||
export declare function beaker<T extends Record<string, unknown | unknown[]>>(options?: BeakerOptions): Beaker<T>; |
@@ -17,5 +17,4 @@ import { FormEl, FormulaField, FormulaOptions, FormulaStores } from '../../types'; | ||
* @param options | ||
* @param isGroup | ||
*/ | ||
export declare function createHandler<T extends Record<string, unknown | unknown[]>>(name: string, eventName: string, element: FormEl, groupElements: FormEl[], stores: FormulaStores<T>, options: FormulaOptions, isGroup?: boolean): () => void; | ||
export declare function createHandler<T extends Record<string, unknown | unknown[]>>(name: string, eventName: string, element: FormEl, groupElements: FormEl[], stores: FormulaStores<T>, options: FormulaOptions): () => void; | ||
/** | ||
@@ -22,0 +21,0 @@ * Create a handler for a form element submission, when called it copies the contents |
@@ -6,3 +6,4 @@ import { Formula, FormulaOptions, FormulaStores } from '../../types'; | ||
* @param globalStore | ||
* @param groupName | ||
*/ | ||
export declare function createForm<T extends Record<string, unknown | unknown[]>>(options: FormulaOptions, globalStore?: Map<string, FormulaStores<T>>): Formula<T>; | ||
export declare function createForm<T extends Record<string, unknown | unknown[]>>(options: FormulaOptions, globalStore?: Map<string, FormulaStores<T>>, groupName?: string): Formula<T>; |
import { FormEl, FormulaOptions, FormulaStores } from '../../types'; | ||
/** | ||
* Initialise the stores with data from the form, it will also use any default values provided | ||
* @param node | ||
* @param allGroups | ||
* @param stores | ||
* @param options | ||
*/ | ||
export declare function getInitialFormValues<T extends Record<string, unknown | unknown[]>>(node: HTMLElement, allGroups: [string, FormEl[]][], stores: FormulaStores<T>, options: FormulaOptions): void; | ||
/** | ||
* Create the form reset method | ||
@@ -15,2 +7,1 @@ | ||
export declare function createReset<T extends Record<string, unknown | unknown[]>>(node: HTMLElement, allGroups: [string, FormEl[]][], stores: FormulaStores<T>, options: FormulaOptions): () => void; | ||
export declare function cleanupDefaultValues(node: HTMLElement): void; |
@@ -1,2 +0,2 @@ | ||
import { Beaker, BeakerStores, FormulaOptions } from '../../types'; | ||
import { Beaker, BeakerOptions, BeakerStores } from '../../types'; | ||
/** | ||
@@ -7,2 +7,2 @@ * Creates a group, which is really just a collection of forms for row data | ||
*/ | ||
export declare function createGroup<T extends Record<string, unknown | unknown[]>>(options: FormulaOptions, beakerStores: Map<string, BeakerStores<T>>): Beaker<T>; | ||
export declare function createGroup<T extends Record<string, unknown | unknown[]>>(options: BeakerOptions, beakerStores: Map<string, BeakerStores<T>>): Beaker<T>; |
@@ -9,3 +9,3 @@ import { FormEl } from '../../types'; | ||
*/ | ||
export declare function getAllFieldsWithValidity(rootEl: HTMLElement): FormEl[]; | ||
export declare function getFormFields(rootEl: HTMLElement): FormEl[]; | ||
/** | ||
@@ -12,0 +12,0 @@ * Extract all fields from a group that are valid inputs with `name` property |
@@ -1,2 +0,2 @@ | ||
import { BeakerStores, FormulaOptions, FormulaStores } from '../../types'; | ||
import { BeakerOptions, BeakerStores, FormulaOptions, FormulaStores } from '../../types'; | ||
/** | ||
@@ -14,2 +14,2 @@ * Create the stores for the the form instance, these can be set using the `defaultValue` property | ||
*/ | ||
export declare function createGroupStores<T extends Record<string, unknown | unknown[]>>(options?: FormulaOptions): BeakerStores<T>; | ||
export declare function createGroupStores<T extends Record<string, unknown | unknown[]>>(options?: BeakerOptions): BeakerStores<T>; |
{ | ||
"name": "svelte-formula", | ||
"description": "Reactive Forms for Svelte", | ||
"version": "0.8.7", | ||
"version": "0.9.0", | ||
"keywords": [ | ||
@@ -6,0 +6,0 @@ "svelte", |
110
README.md
@@ -1,5 +0,11 @@ | ||
# Svelte Formula | ||
<div style='text-align: center'> | ||
![The Svelte Formula Logo](https://raw.githubusercontent.com/tanepiper/svelte-plugins/main/packages/docs-site/static/img/logo_256.png) | ||
# Formula + Beaker Δ→ | ||
**Reactive Forms for Svelte** | ||
![The Svelte Formula Logo](https://formula.svelte.codes/img/logo_256.png) | ||
</div> | ||
[![svelte-formula](https://img.shields.io/npm/v/svelte-formula?label=svelte-formula)](https://www.npmjs.com/package/svelte-formula) | ||
@@ -10,12 +16,5 @@ | ||
Formula is a library for [Svelte](https://svelte.dev) with features for creating **Zero Configuration** reactive forms | ||
and fully data-driven applications. | ||
`svelte-formula` is a Library for use with [Svelte](https://svelte.dev) that super-charges your ability to create rich | ||
data-driven for applications. | ||
Out-of-the box it's designed to work with HTML5 forms. Configuring your forms validation is as easy as setting | ||
attributes, and doesn't get in the way of things like Accessibility. You can also add additional validations, custom | ||
messages and computed values through optional configuration to enhance your forms and their UI. | ||
Formula also supports multi-row forms with [Beaker](https://formula.svelte.codes/docs/groups/beaker) - an API for working | ||
with rich forms for collections of data. | ||
## Install Instructions | ||
@@ -30,46 +29,61 @@ | ||
[Live Demo](https://svelte.dev/repl/3c3fe78a258a45779bd122d399560f19) | ||
```svelte | ||
<script> | ||
import { createEventDispatcher } from 'svelte'; | ||
import { get } from 'svelte/store' | ||
import { formula } from 'svelte-formula@0.8.2' | ||
const { form, isFormValid, validity, touched, submitValues } = formula(); | ||
Visit the [documentation](https://formula.svelte.codes) for more details API instructions. | ||
const dispatcher = createEventDispatcher() | ||
## Formula | ||
// Allow components to accept value that can be used as default values | ||
export let userName = ''; | ||
[Demo](https://svelte.dev/repl/dda29ae516284147871b58a4f1966315) | ||
// You can calculate values for valid UI states | ||
$: usernameInvalid = $touched?.userName && $validity?.userName?.invalid | ||
<div style='text-align: center; float: left; margin-right: 20px'> | ||
// Handle submission of data easily to parent components or services | ||
function submitForm() { | ||
dispatch('updateUser', { | ||
user: get(submitValues) | ||
}) | ||
} | ||
</script> | ||
![The Svelte Formula Logo](https://formula.svelte.codes/img/formula-small.png) | ||
<!-- Use as form element to get full form submission validation--> | ||
<form use:form on:submit|preventDefault={submitForm}> | ||
<div class="form-field"> | ||
<label for="userName">User Name</label> | ||
<input type="text" id="userName" name="userName" required minlength="8" class:error={usernameInvalid} bind:value={userName} /> | ||
<span hidden={!usernameInvalid}>{$validity?.userName?.message}</span> | ||
</div> | ||
<button disabled={!$isFormValid} type="submit">Update User Name</button> | ||
</form> | ||
</div> | ||
<style> | ||
.error { | ||
border: 1px solid hotpink; | ||
} | ||
</style> | ||
``` | ||
**Formula** is a library for creating _Zero Configuration_ reactive form components, and fully data-driven applications. | ||
**Zero-Configuration** means you need nothing more than a well-defined HTML5 form element to have fully reactive stores | ||
of data and form states. | ||
Visit the [documentation](https://formula.svelte.codes) for more details API instructions. | ||
Accessing the input requires only setting the `name` property, and for validation providing attributes like `require` | ||
or `minlength`. Formula supports single and multi-value inputs across all widely supported HTML inputs and extends them | ||
with checkbox groups and radio groups, and composite fields of values like text or number. | ||
Formula creates a form instance that contains Svelte [stores](https://formula.svelte.codes/docs/stores/stores) that | ||
contain value and validation information, and some | ||
additional [lifecycle methods](https://formula.svelte.codes/docs/lifecycle) that allow your to dynamically add and | ||
remove customisations, and reset or destroy the form. It also attempts to apply ARIA attributes to help with | ||
accessibility. | ||
### Extending Formula | ||
Formula also supports a bunch of [powerful options](https://formula.svelte.codes/docs/options) that provide additional | ||
validation, enrichment and custom messages. | ||
For example with the `enrich` [option](https://formula.svelte.codes/docs/options#enrich) | ||
and `enrichment` [store](https://formula.svelte.codes/docs/stores/stores-enrichment) you can provide functions that | ||
calculate additional computed values based on user input - for example calculating a password strength, or the length of | ||
text a user has entered. These are useful. | ||
Validations can be provided at the form and field level, and integrate with in-built browser validations to provide | ||
native messages, which can be customised for localisation. | ||
### Beaker | ||
[Demo](https://svelte.dev/repl/c146c7976360405cba9a696e3fee853b) | ||
<div style='text-align: center; float: left; margin-right: 20px'> | ||
![The Svelte Formula Logo](https://formula.svelte.codes/img/beaker-small.png) | ||
</div> | ||
**Beaker** take Formula and adds another layer for working with collections of data. | ||
Using row-based input you can create full form instances per row that are also fully reactive and feed into Beaker's | ||
collection store. | ||
Beaker also provides methods for setting, adding and removing items from the in-built stores, when can be used with | ||
Svelte's `{#each}{/each}` blocks to create a re-usable template in the component | ||
With this you can build applications such as multi-row editable tables or lists. See | ||
the [documentation](https://formula.svelte.codes/docs/groups/beaker) for more details and examples. |
@@ -83,3 +83,3 @@ import { get, writable } from 'svelte/store'; | ||
*/ | ||
function getAllFieldsWithValidity(rootEl) { | ||
function getFormFields(rootEl) { | ||
var nodeList = rootEl.querySelectorAll('*[name]:not([data-in-group])'); | ||
@@ -220,3 +220,4 @@ return Array.from(nodeList).filter(function (el) { | ||
el.setCustomValidity(''); // If there's no options, just return the current error | ||
el.setCustomValidity(''); | ||
el.removeAttribute('data-formula-invalid'); // If there's no options, just return the current error | ||
@@ -257,2 +258,7 @@ if (!options) { | ||
var valid = el.checkValidity(); | ||
if (!valid) { | ||
el.setAttribute('data-formula-invalid', 'true'); | ||
} | ||
return { | ||
@@ -648,7 +654,6 @@ valid: valid, | ||
* @param options | ||
* @param isGroup | ||
*/ | ||
function createHandler(name, eventName, element, groupElements, stores, options, isGroup) { | ||
function createHandler(name, eventName, element, groupElements, stores, options) { | ||
var _a; | ||
@@ -683,5 +688,2 @@ | ||
var initialValues = new Map(); | ||
var initialValidity = new Map(); | ||
var initialEnrichment = new Map(); | ||
/** | ||
@@ -744,5 +746,3 @@ * Initialise the stores with data from the form, it will also use any default values provided | ||
stores.enrichment.set(__assign({}, enrichmentValues)); | ||
initialValues.set(node, __assign({}, formValues)); | ||
initialValidity.set(node, __assign({}, validityValues)); | ||
initialEnrichment.set(node, __assign({}, enrichmentValues)); | ||
return [formValues, validityValues, enrichmentValues]; | ||
} | ||
@@ -754,12 +754,16 @@ /** | ||
function createReset(node, allGroups, stores, options) { | ||
var _a = __read(getInitialFormValues(node, allGroups, stores, options), 3), | ||
formValues = _a[0], | ||
validityValues = _a[1], | ||
enrichmentValues = _a[2]; | ||
/** | ||
* Resets the form to the initial values | ||
*/ | ||
return function () { | ||
var e_2, _a; | ||
var formValues = initialValues.get(node); | ||
var validityValues = initialValidity.get(node); | ||
var enrichment = initialEnrichment.get(node); | ||
stores.formValues.set(formValues); | ||
@@ -770,5 +774,5 @@ stores.validity.set(validityValues); | ||
})); | ||
stores.enrichment.set(enrichment); // Also override touched and dirty | ||
stores.enrichment.set(enrichmentValues); // Also override touched and dirty | ||
stores.touched.set(Object.keys(initialValues).reduce(function (val, key) { | ||
stores.touched.set(Object.keys(formValues).reduce(function (val, key) { | ||
var _a; | ||
@@ -778,3 +782,3 @@ | ||
}, {})); | ||
stores.dirty.set(Object.keys(initialValues).reduce(function (val, key) { | ||
stores.dirty.set(Object.keys(formValues).reduce(function (val, key) { | ||
var _a; | ||
@@ -808,16 +812,3 @@ | ||
} | ||
function cleanupDefaultValues(node) { | ||
if (initialValues.has(node)) { | ||
initialValues["delete"](node); | ||
} | ||
if (initialValidity.has(node)) { | ||
initialValidity["delete"](node); | ||
} | ||
if (initialEnrichment.has(node)) { | ||
initialEnrichment["delete"](node); | ||
} | ||
} | ||
/** | ||
@@ -857,2 +848,3 @@ * Creates the handler for a group of elements for the touch event on the group name, once an | ||
el.setAttribute('data-formula-touched', 'true'); | ||
el.removeEventListener('focus', handler); | ||
@@ -966,2 +958,3 @@ } | ||
el.setAttribute('data-formula-dirty', 'true'); | ||
el.removeEventListener('blur', handler); | ||
@@ -1142,13 +1135,40 @@ } | ||
function createGroupStores(options) { | ||
var _a; | ||
var initialValues = []; | ||
var initialFieldState = []; | ||
var initialValidity = []; | ||
var initialEnrichment = []; | ||
var initialFormValidity = []; | ||
if (((_a = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _a === void 0 ? void 0 : _a.length) > 0) { | ||
var defaultValues = options.defaultValues, | ||
rest_1 = __rest(options, ["defaultValues"]); | ||
var eachState = defaultValues.map(function (d) { | ||
return createFirstState(__assign(__assign({}, rest_1), { | ||
defaultValues: d | ||
})); | ||
}); | ||
for (var i = 0; i < eachState.length; i++) { | ||
initialValues = __spread(initialValues, [eachState[i].initialValues]); | ||
initialFieldState = __spread(initialFieldState, [eachState[i].initialFieldState]); | ||
initialValidity = __spread(initialValidity, [eachState[i].initialValidity]); | ||
initialEnrichment = __spread(initialEnrichment, [eachState[i].initialEnrichment]); | ||
initialFormValidity = __spread(initialFormValidity, [eachState[i].initialFormValidity]); | ||
} | ||
} | ||
return { | ||
formValues: writable([]), | ||
formValues: writable(initialValues), | ||
submitValues: writable([]), | ||
initialValues: writable([]), | ||
touched: writable([]), | ||
dirty: writable([]), | ||
validity: writable([]), | ||
formValidity: writable({}), | ||
initialValues: writable(initialValues), | ||
touched: writable(initialFieldState), | ||
dirty: writable(initialFieldState), | ||
validity: writable(initialValidity), | ||
formValidity: writable(initialFormValidity), | ||
isFormValid: writable(false), | ||
isFormReady: writable(false), | ||
enrichment: writable([]) | ||
enrichment: writable(initialEnrichment) | ||
}; | ||
@@ -1161,5 +1181,6 @@ } | ||
* @param globalStore | ||
* @param groupName | ||
*/ | ||
function createForm(options, globalStore) { | ||
function createForm(options, globalStore, groupName) { | ||
/** | ||
@@ -1173,2 +1194,3 @@ * Store for all keyup handlers than need removed when destroyed | ||
var stores = createFormStores(options); | ||
var isGroup = typeof groupName !== 'undefined'; | ||
var initialOptions = options; | ||
@@ -1183,7 +1205,6 @@ var submitHandler = undefined; | ||
* @param innerOpt | ||
* @param isGroup | ||
*/ | ||
function bindElements(node, innerOpt, isGroup) { | ||
var formElements = isGroup ? getGroupFields(node) : getAllFieldsWithValidity(node); | ||
function bindElements(node, innerOpt) { | ||
var formElements = isGroup ? getGroupFields(node) : getFormFields(node); | ||
node.setAttribute("data-beaker-" + (isGroup ? 'row' : 'form'), 'true'); | ||
@@ -1197,3 +1218,2 @@ setAriaContainer(node, isGroup); | ||
}, new Map())); | ||
getInitialFormValues(node, groupedMap, stores, innerOpt); | ||
innerReset = createReset(node, groupedMap, stores, innerOpt); // Loop over each group and setup up their initial touch and dirty handlers, | ||
@@ -1212,3 +1232,3 @@ // also get initial values | ||
if (isGroup) { | ||
el.setAttribute('data-in-group', 'true'); | ||
el.setAttribute('data-in-group', groupName); | ||
} | ||
@@ -1297,7 +1317,6 @@ | ||
* @param node | ||
* @param isGroup | ||
*/ | ||
form: function form(node, isGroup) { | ||
form: function form(node) { | ||
currentNode = node; | ||
bindElements(node, options, isGroup); | ||
bindElements(node, options); | ||
return { | ||
@@ -1329,3 +1348,2 @@ destroy: function destroy() { | ||
currentNode.id && globalStore && globalStore["delete"](name); | ||
cleanupDefaultValues(currentNode); | ||
}, | ||
@@ -1356,2 +1374,3 @@ | ||
var groupCounter = 0; | ||
/** | ||
@@ -1364,68 +1383,64 @@ * Creates a group, which is really just a collection of forms for row data | ||
function createGroup(options, beakerStores) { | ||
var stores = createGroupStores(); | ||
var groupId; | ||
var stores = createGroupStores(options); | ||
var groupName; | ||
var globalObserver; | ||
var formSet = new Set(); | ||
var instanceSet = new Set(); | ||
var subscriptions = new Map(); | ||
function destroyGroup() { | ||
__spread(subscriptions).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
key = _b[0], | ||
subs = _b[1]; | ||
var _a = options || {}, | ||
defaultValues = _a.defaultValues, | ||
formulaOptions = __rest(_a, ["defaultValues"]); | ||
if (key === 'isFormValid' || key === 'isFormReady') { | ||
stores[key].set(false); | ||
} else { | ||
stores[key].set([]); | ||
} | ||
var formulaInstances = new Map(); | ||
var formInstances = new Map(); | ||
/** | ||
* Called when the group forms need destroyed | ||
*/ | ||
subs.forEach(function (sub) { | ||
return sub(); | ||
}); | ||
}); | ||
subscriptions.clear(); | ||
instanceSet.forEach(function (instance) { | ||
function destroyGroup() { | ||
formInstances.forEach(function (instance) { | ||
return instance.destroy(); | ||
}); | ||
formSet.clear(); | ||
formInstances.clear(); | ||
formulaInstances.clear(); | ||
} | ||
/** | ||
* Called when a node it removed, it destroys it's form instance | ||
* @param removedNodes | ||
*/ | ||
function groupHasChanged(rows) { | ||
rows.forEach(function (row, i) { | ||
var form = createForm(options); | ||
var instance = form.form(row, true); | ||
formSet.add(form); | ||
instanceSet.add(instance); | ||
Object.entries(form.stores).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
key = _b[0], | ||
store = _b[1]; | ||
var unsub = store.subscribe(function (value) { | ||
stores[key].update(function (state) { | ||
if (Array.isArray(state)) { | ||
state.splice(i, 1, value); | ||
} else { | ||
state = value; | ||
} | ||
function nodesRemoved(removedNodes) { | ||
for (var i = 0; i < removedNodes.length; i++) { | ||
var row = removedNodes[i]; | ||
var instance = formInstances.get(row); | ||
return state; | ||
}); | ||
}); | ||
if (instance) { | ||
instance.destroy(); | ||
} | ||
if (!subscriptions.has(key)) { | ||
subscriptions.set(key, [unsub]); | ||
} else { | ||
var subs = subscriptions.get(key); | ||
subs.push(unsub); | ||
subscriptions.set(key, subs); | ||
} | ||
}); | ||
}); | ||
formInstances["delete"](row); | ||
formulaInstances["delete"](row); | ||
} | ||
stores.isFormReady.set(true); | ||
} | ||
/** | ||
* Called when a node is added, creates a new instance of a form for the row | ||
* @param addedNodes | ||
*/ | ||
function nodesAdded(addedNodes) { | ||
for (var i = 0; i < addedNodes.length; i++) { | ||
var row = addedNodes[i]; | ||
var form = createForm(__assign(__assign({}, formulaOptions), { | ||
defaultValues: defaultValues[i] | ||
}), undefined, groupName); | ||
formulaInstances.set(row, form); | ||
var instance = form.form(row, true); | ||
formInstances.set(row, instance); | ||
} | ||
stores.isFormReady.set(true); | ||
} | ||
/** | ||
* Set up the Observer for the group | ||
@@ -1437,6 +1452,10 @@ * @param node | ||
function setupGroupContainer(node) { | ||
globalObserver = new MutationObserver(function (mutations) { | ||
destroyGroup(); | ||
var rows = node.querySelectorAll(':scope > *'); | ||
groupHasChanged(Array.from(rows)); | ||
globalObserver = new MutationObserver(function (records) { | ||
stores.isFormReady.set(false); | ||
if (records[0].addedNodes.length > 0) { | ||
nodesAdded(Array.from(records[0].addedNodes)); | ||
} else if (records[0].removedNodes.length > 0) { | ||
nodesRemoved(Array.from(records[0].removedNodes)); | ||
} | ||
}); | ||
@@ -1447,3 +1466,3 @@ globalObserver.observe(node, { | ||
var rows = node.querySelectorAll(':scope > *'); | ||
groupHasChanged(Array.from(rows)); | ||
nodesAdded(Array.from(rows)); | ||
} | ||
@@ -1453,7 +1472,10 @@ | ||
group: function group(node) { | ||
groupId = node.id; | ||
if (node.id) { | ||
groupName = node.id; | ||
beakerStores.set(groupName, stores); | ||
} else { | ||
groupName = "beaker-group-" + groupCounter++; | ||
node.id = groupName; | ||
} | ||
if (groupId) { | ||
beakerStores.set(groupId, stores); | ||
} | ||
node.setAttribute('data-beaker-group', 'true'); | ||
@@ -1464,4 +1486,4 @@ node.setAttribute('aria-role', 'group'); | ||
destroy: function destroy() { | ||
if (groupId) { | ||
beakerStores["delete"](groupId); | ||
if (groupName) { | ||
beakerStores["delete"](groupName); | ||
} | ||
@@ -1475,14 +1497,13 @@ | ||
update: function update(options) { | ||
__spread(formSet).forEach(function (form) { | ||
__spread(formulaInstances).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
_ = _b[0], | ||
form = _b[1]; | ||
return form.updateForm(options); | ||
}); | ||
}, | ||
reset: function reset() { | ||
__spread(formSet).forEach(function (form) { | ||
return form.resetForm(); | ||
}); | ||
}, | ||
destroy: function destroy() { | ||
if (groupId) { | ||
beakerStores["delete"](groupId); | ||
if (groupName) { | ||
beakerStores["delete"](groupName); | ||
} | ||
@@ -1493,3 +1514,3 @@ | ||
}, | ||
forms: formSet, | ||
forms: formulaInstances, | ||
stores: stores, | ||
@@ -1504,2 +1525,8 @@ init: function init(items) { | ||
}, | ||
set: function set(index, item) { | ||
return stores.formValues.update(function (state) { | ||
state.splice(index, 1, item); | ||
return state; | ||
}); | ||
}, | ||
"delete": function _delete(index) { | ||
@@ -1506,0 +1533,0 @@ return stores.formValues.update(function (state) { |
@@ -87,3 +87,3 @@ (function (global, factory) { | ||
*/ | ||
function getAllFieldsWithValidity(rootEl) { | ||
function getFormFields(rootEl) { | ||
var nodeList = rootEl.querySelectorAll('*[name]:not([data-in-group])'); | ||
@@ -224,3 +224,4 @@ return Array.from(nodeList).filter(function (el) { | ||
el.setCustomValidity(''); // If there's no options, just return the current error | ||
el.setCustomValidity(''); | ||
el.removeAttribute('data-formula-invalid'); // If there's no options, just return the current error | ||
@@ -261,2 +262,7 @@ if (!options) { | ||
var valid = el.checkValidity(); | ||
if (!valid) { | ||
el.setAttribute('data-formula-invalid', 'true'); | ||
} | ||
return { | ||
@@ -652,7 +658,6 @@ valid: valid, | ||
* @param options | ||
* @param isGroup | ||
*/ | ||
function createHandler(name, eventName, element, groupElements, stores, options, isGroup) { | ||
function createHandler(name, eventName, element, groupElements, stores, options) { | ||
var _a; | ||
@@ -687,5 +692,2 @@ | ||
var initialValues = new Map(); | ||
var initialValidity = new Map(); | ||
var initialEnrichment = new Map(); | ||
/** | ||
@@ -748,5 +750,3 @@ * Initialise the stores with data from the form, it will also use any default values provided | ||
stores.enrichment.set(__assign({}, enrichmentValues)); | ||
initialValues.set(node, __assign({}, formValues)); | ||
initialValidity.set(node, __assign({}, validityValues)); | ||
initialEnrichment.set(node, __assign({}, enrichmentValues)); | ||
return [formValues, validityValues, enrichmentValues]; | ||
} | ||
@@ -758,12 +758,16 @@ /** | ||
function createReset(node, allGroups, stores, options) { | ||
var _a = __read(getInitialFormValues(node, allGroups, stores, options), 3), | ||
formValues = _a[0], | ||
validityValues = _a[1], | ||
enrichmentValues = _a[2]; | ||
/** | ||
* Resets the form to the initial values | ||
*/ | ||
return function () { | ||
var e_2, _a; | ||
var formValues = initialValues.get(node); | ||
var validityValues = initialValidity.get(node); | ||
var enrichment = initialEnrichment.get(node); | ||
stores.formValues.set(formValues); | ||
@@ -774,5 +778,5 @@ stores.validity.set(validityValues); | ||
})); | ||
stores.enrichment.set(enrichment); // Also override touched and dirty | ||
stores.enrichment.set(enrichmentValues); // Also override touched and dirty | ||
stores.touched.set(Object.keys(initialValues).reduce(function (val, key) { | ||
stores.touched.set(Object.keys(formValues).reduce(function (val, key) { | ||
var _a; | ||
@@ -782,3 +786,3 @@ | ||
}, {})); | ||
stores.dirty.set(Object.keys(initialValues).reduce(function (val, key) { | ||
stores.dirty.set(Object.keys(formValues).reduce(function (val, key) { | ||
var _a; | ||
@@ -812,16 +816,3 @@ | ||
} | ||
function cleanupDefaultValues(node) { | ||
if (initialValues.has(node)) { | ||
initialValues["delete"](node); | ||
} | ||
if (initialValidity.has(node)) { | ||
initialValidity["delete"](node); | ||
} | ||
if (initialEnrichment.has(node)) { | ||
initialEnrichment["delete"](node); | ||
} | ||
} | ||
/** | ||
@@ -861,2 +852,3 @@ * Creates the handler for a group of elements for the touch event on the group name, once an | ||
el.setAttribute('data-formula-touched', 'true'); | ||
el.removeEventListener('focus', handler); | ||
@@ -970,2 +962,3 @@ } | ||
el.setAttribute('data-formula-dirty', 'true'); | ||
el.removeEventListener('blur', handler); | ||
@@ -1146,13 +1139,40 @@ } | ||
function createGroupStores(options) { | ||
var _a; | ||
var initialValues = []; | ||
var initialFieldState = []; | ||
var initialValidity = []; | ||
var initialEnrichment = []; | ||
var initialFormValidity = []; | ||
if (((_a = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _a === void 0 ? void 0 : _a.length) > 0) { | ||
var defaultValues = options.defaultValues, | ||
rest_1 = __rest(options, ["defaultValues"]); | ||
var eachState = defaultValues.map(function (d) { | ||
return createFirstState(__assign(__assign({}, rest_1), { | ||
defaultValues: d | ||
})); | ||
}); | ||
for (var i = 0; i < eachState.length; i++) { | ||
initialValues = __spread(initialValues, [eachState[i].initialValues]); | ||
initialFieldState = __spread(initialFieldState, [eachState[i].initialFieldState]); | ||
initialValidity = __spread(initialValidity, [eachState[i].initialValidity]); | ||
initialEnrichment = __spread(initialEnrichment, [eachState[i].initialEnrichment]); | ||
initialFormValidity = __spread(initialFormValidity, [eachState[i].initialFormValidity]); | ||
} | ||
} | ||
return { | ||
formValues: store.writable([]), | ||
formValues: store.writable(initialValues), | ||
submitValues: store.writable([]), | ||
initialValues: store.writable([]), | ||
touched: store.writable([]), | ||
dirty: store.writable([]), | ||
validity: store.writable([]), | ||
formValidity: store.writable({}), | ||
initialValues: store.writable(initialValues), | ||
touched: store.writable(initialFieldState), | ||
dirty: store.writable(initialFieldState), | ||
validity: store.writable(initialValidity), | ||
formValidity: store.writable(initialFormValidity), | ||
isFormValid: store.writable(false), | ||
isFormReady: store.writable(false), | ||
enrichment: store.writable([]) | ||
enrichment: store.writable(initialEnrichment) | ||
}; | ||
@@ -1165,5 +1185,6 @@ } | ||
* @param globalStore | ||
* @param groupName | ||
*/ | ||
function createForm(options, globalStore) { | ||
function createForm(options, globalStore, groupName) { | ||
/** | ||
@@ -1177,2 +1198,3 @@ * Store for all keyup handlers than need removed when destroyed | ||
var stores = createFormStores(options); | ||
var isGroup = typeof groupName !== 'undefined'; | ||
var initialOptions = options; | ||
@@ -1187,7 +1209,6 @@ var submitHandler = undefined; | ||
* @param innerOpt | ||
* @param isGroup | ||
*/ | ||
function bindElements(node, innerOpt, isGroup) { | ||
var formElements = isGroup ? getGroupFields(node) : getAllFieldsWithValidity(node); | ||
function bindElements(node, innerOpt) { | ||
var formElements = isGroup ? getGroupFields(node) : getFormFields(node); | ||
node.setAttribute("data-beaker-" + (isGroup ? 'row' : 'form'), 'true'); | ||
@@ -1201,3 +1222,2 @@ setAriaContainer(node, isGroup); | ||
}, new Map())); | ||
getInitialFormValues(node, groupedMap, stores, innerOpt); | ||
innerReset = createReset(node, groupedMap, stores, innerOpt); // Loop over each group and setup up their initial touch and dirty handlers, | ||
@@ -1216,3 +1236,3 @@ // also get initial values | ||
if (isGroup) { | ||
el.setAttribute('data-in-group', 'true'); | ||
el.setAttribute('data-in-group', groupName); | ||
} | ||
@@ -1301,7 +1321,6 @@ | ||
* @param node | ||
* @param isGroup | ||
*/ | ||
form: function form(node, isGroup) { | ||
form: function form(node) { | ||
currentNode = node; | ||
bindElements(node, options, isGroup); | ||
bindElements(node, options); | ||
return { | ||
@@ -1333,3 +1352,2 @@ destroy: function destroy() { | ||
currentNode.id && globalStore && globalStore["delete"](name); | ||
cleanupDefaultValues(currentNode); | ||
}, | ||
@@ -1360,2 +1378,3 @@ | ||
var groupCounter = 0; | ||
/** | ||
@@ -1368,68 +1387,64 @@ * Creates a group, which is really just a collection of forms for row data | ||
function createGroup(options, beakerStores) { | ||
var stores = createGroupStores(); | ||
var groupId; | ||
var stores = createGroupStores(options); | ||
var groupName; | ||
var globalObserver; | ||
var formSet = new Set(); | ||
var instanceSet = new Set(); | ||
var subscriptions = new Map(); | ||
function destroyGroup() { | ||
__spread(subscriptions).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
key = _b[0], | ||
subs = _b[1]; | ||
var _a = options || {}, | ||
defaultValues = _a.defaultValues, | ||
formulaOptions = __rest(_a, ["defaultValues"]); | ||
if (key === 'isFormValid' || key === 'isFormReady') { | ||
stores[key].set(false); | ||
} else { | ||
stores[key].set([]); | ||
} | ||
var formulaInstances = new Map(); | ||
var formInstances = new Map(); | ||
/** | ||
* Called when the group forms need destroyed | ||
*/ | ||
subs.forEach(function (sub) { | ||
return sub(); | ||
}); | ||
}); | ||
subscriptions.clear(); | ||
instanceSet.forEach(function (instance) { | ||
function destroyGroup() { | ||
formInstances.forEach(function (instance) { | ||
return instance.destroy(); | ||
}); | ||
formSet.clear(); | ||
formInstances.clear(); | ||
formulaInstances.clear(); | ||
} | ||
/** | ||
* Called when a node it removed, it destroys it's form instance | ||
* @param removedNodes | ||
*/ | ||
function groupHasChanged(rows) { | ||
rows.forEach(function (row, i) { | ||
var form = createForm(options); | ||
var instance = form.form(row, true); | ||
formSet.add(form); | ||
instanceSet.add(instance); | ||
Object.entries(form.stores).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
key = _b[0], | ||
store = _b[1]; | ||
var unsub = store.subscribe(function (value) { | ||
stores[key].update(function (state) { | ||
if (Array.isArray(state)) { | ||
state.splice(i, 1, value); | ||
} else { | ||
state = value; | ||
} | ||
function nodesRemoved(removedNodes) { | ||
for (var i = 0; i < removedNodes.length; i++) { | ||
var row = removedNodes[i]; | ||
var instance = formInstances.get(row); | ||
return state; | ||
}); | ||
}); | ||
if (instance) { | ||
instance.destroy(); | ||
} | ||
if (!subscriptions.has(key)) { | ||
subscriptions.set(key, [unsub]); | ||
} else { | ||
var subs = subscriptions.get(key); | ||
subs.push(unsub); | ||
subscriptions.set(key, subs); | ||
} | ||
}); | ||
}); | ||
formInstances["delete"](row); | ||
formulaInstances["delete"](row); | ||
} | ||
stores.isFormReady.set(true); | ||
} | ||
/** | ||
* Called when a node is added, creates a new instance of a form for the row | ||
* @param addedNodes | ||
*/ | ||
function nodesAdded(addedNodes) { | ||
for (var i = 0; i < addedNodes.length; i++) { | ||
var row = addedNodes[i]; | ||
var form = createForm(__assign(__assign({}, formulaOptions), { | ||
defaultValues: defaultValues[i] | ||
}), undefined, groupName); | ||
formulaInstances.set(row, form); | ||
var instance = form.form(row, true); | ||
formInstances.set(row, instance); | ||
} | ||
stores.isFormReady.set(true); | ||
} | ||
/** | ||
* Set up the Observer for the group | ||
@@ -1441,6 +1456,10 @@ * @param node | ||
function setupGroupContainer(node) { | ||
globalObserver = new MutationObserver(function (mutations) { | ||
destroyGroup(); | ||
var rows = node.querySelectorAll(':scope > *'); | ||
groupHasChanged(Array.from(rows)); | ||
globalObserver = new MutationObserver(function (records) { | ||
stores.isFormReady.set(false); | ||
if (records[0].addedNodes.length > 0) { | ||
nodesAdded(Array.from(records[0].addedNodes)); | ||
} else if (records[0].removedNodes.length > 0) { | ||
nodesRemoved(Array.from(records[0].removedNodes)); | ||
} | ||
}); | ||
@@ -1451,3 +1470,3 @@ globalObserver.observe(node, { | ||
var rows = node.querySelectorAll(':scope > *'); | ||
groupHasChanged(Array.from(rows)); | ||
nodesAdded(Array.from(rows)); | ||
} | ||
@@ -1457,7 +1476,10 @@ | ||
group: function group(node) { | ||
groupId = node.id; | ||
if (node.id) { | ||
groupName = node.id; | ||
beakerStores.set(groupName, stores); | ||
} else { | ||
groupName = "beaker-group-" + groupCounter++; | ||
node.id = groupName; | ||
} | ||
if (groupId) { | ||
beakerStores.set(groupId, stores); | ||
} | ||
node.setAttribute('data-beaker-group', 'true'); | ||
@@ -1468,4 +1490,4 @@ node.setAttribute('aria-role', 'group'); | ||
destroy: function destroy() { | ||
if (groupId) { | ||
beakerStores["delete"](groupId); | ||
if (groupName) { | ||
beakerStores["delete"](groupName); | ||
} | ||
@@ -1479,14 +1501,13 @@ | ||
update: function update(options) { | ||
__spread(formSet).forEach(function (form) { | ||
__spread(formulaInstances).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
_ = _b[0], | ||
form = _b[1]; | ||
return form.updateForm(options); | ||
}); | ||
}, | ||
reset: function reset() { | ||
__spread(formSet).forEach(function (form) { | ||
return form.resetForm(); | ||
}); | ||
}, | ||
destroy: function destroy() { | ||
if (groupId) { | ||
beakerStores["delete"](groupId); | ||
if (groupName) { | ||
beakerStores["delete"](groupName); | ||
} | ||
@@ -1497,3 +1518,3 @@ | ||
}, | ||
forms: formSet, | ||
forms: formulaInstances, | ||
stores: stores, | ||
@@ -1508,2 +1529,8 @@ init: function init(items) { | ||
}, | ||
set: function set(index, item) { | ||
return stores.formValues.update(function (state) { | ||
state.splice(index, 1, item); | ||
return state; | ||
}); | ||
}, | ||
"delete": function _delete(index) { | ||
@@ -1510,0 +1537,0 @@ return stores.formValues.update(function (state) { |
@@ -34,3 +34,3 @@ import { Writable } from 'svelte/store'; | ||
*/ | ||
formValidity: Writable<Record<string, string | null>>; | ||
formValidity: Writable<Record<string, string | null>[]>; | ||
/** | ||
@@ -69,9 +69,5 @@ * A store containing a boolean value if the form is overall valid | ||
/** | ||
* Reset | ||
*/ | ||
reset: () => void; | ||
/** | ||
* Instance forms | ||
*/ | ||
forms: Set<Formula<T>>; | ||
forms: Map<HTMLElement, Formula<T>>; | ||
/** | ||
@@ -91,2 +87,6 @@ * Stores | ||
/** | ||
* Add an item to the group store | ||
*/ | ||
set: (index: number, item: T) => void; | ||
/** | ||
* Remove and item from the group store | ||
@@ -93,0 +93,0 @@ */ |
import { CustomValidationMessages, ValidationRule, ValidationRules } from './validation'; | ||
import { EnrichFields } from './enrich'; | ||
/** | ||
* Optional settings for Formula | ||
*/ | ||
export interface FormulaOptions { | ||
interface BaseOptions { | ||
/** | ||
* Locale for i18n - currently not used | ||
*/ | ||
locale?: string; | ||
/** | ||
* Provide customised messages to the application, these messages replace the default browser messages for the provided | ||
@@ -31,2 +24,7 @@ * error types and are useful for internationalisation or custom domain messages | ||
enrich?: EnrichFields; | ||
} | ||
/** | ||
* Optional settings for Formula | ||
*/ | ||
export interface FormulaOptions extends BaseOptions { | ||
/** | ||
@@ -37,1 +35,11 @@ * Default values are used as initial values for the form fields if there is no value already set on the form | ||
} | ||
/** | ||
* Optional settings for Beaker | ||
*/ | ||
export interface BeakerOptions extends BaseOptions { | ||
/** | ||
* Default values are used as initial values for the form fields if there is no value already set on the form | ||
*/ | ||
defaultValues?: Record<string, unknown | unknown[]>[]; | ||
} | ||
export {}; |
128725
3122
88