svelte-formula
Advanced tools
Comparing version 0.6.0 to 0.7.0
@@ -8,2 +8,33 @@ # Changelog | ||
## [0.7.0] 2021-02-19 | ||
### Added | ||
- `defaultValues` option that allows default values to be set on fields - supports single and multi-value properties - | ||
these values are only applied if there is no value already bound to the field. | ||
```sveltehtml | ||
<script> | ||
import {formula} from 'svelte-formula'; | ||
const { form } = formula({ | ||
defaultValues: { | ||
textField: 'Initial Value', | ||
numberField: 42, | ||
checkBox: true, | ||
multiValue: ['option1', 'option3'] | ||
} | ||
}) | ||
</script> | ||
``` | ||
- Added `isFormReady` store - Formula stores are created immediately when using the `formula` method, previously they | ||
were always empty objects filled with keys from parsing the form. This meant early binding would cause an error and | ||
forced the use of `?.` operators in the templates. This store can now be used as `{#if $isFormReady}`, reducing the | ||
need for the number of conditionals in templates | ||
- `initialValues` store that contains the values at form initialisation, this is generated from merging any initial | ||
element values merged with any potential default values | ||
- `formReset` function that when called will reset the form to the pristine state at setup time | ||
## [0.6.0] 2021-02-18 | ||
@@ -10,0 +41,0 @@ |
import { FormEl, FormulaField } from '../types/forms'; | ||
import { FormulaOptions } from '../types/options'; | ||
import { FormulaStores } from 'svelte-formula'; | ||
/** | ||
@@ -8,28 +9,4 @@ * Create a data handler for any type of input field | ||
* @param options | ||
* @param stores | ||
*/ | ||
export declare function createFieldExtract(name: string, elementGroup: FormEl[], options: FormulaOptions): (element: HTMLInputElement) => FormulaField; | ||
/** | ||
* Create a data handler for checkbox fields | ||
* @param name | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
export declare function createCheckboxExtract(name: string, elementGroup: FormEl[], options: FormulaOptions): (element: HTMLInputElement) => FormulaField; | ||
/** | ||
* Create a data handler for radio groups | ||
* @param name | ||
* @param options | ||
*/ | ||
export declare function createRadioExtract(name: string, options: FormulaOptions): (element: HTMLInputElement) => FormulaField; | ||
/** | ||
* Create a data handler for select fields | ||
* @param name | ||
* @param options | ||
*/ | ||
export declare function createSelectExtract(name: string, options: FormulaOptions): (element: HTMLSelectElement) => FormulaField; | ||
/** | ||
* Create a data handler for form fields | ||
* @param name | ||
* @param options | ||
*/ | ||
export declare function createFileExtract(name: string, options: FormulaOptions): (element: HTMLInputElement) => FormulaField; | ||
export declare function createFieldExtract(name: string, elementGroup: FormEl[], options: FormulaOptions, stores: FormulaStores): (element: FormEl, isInit?: boolean, isReset?: boolean) => FormulaField; |
@@ -15,2 +15,3 @@ import { FormulaOptions } from '../types/options'; | ||
destroy: () => void; | ||
reset: () => void; | ||
}; |
@@ -1,15 +0,15 @@ | ||
import { FormEl, FormulaField } from '../types/forms'; | ||
import { FormEl } from '../types/forms'; | ||
import { FormulaStores } from '../types/formula'; | ||
import { FormulaOptions } from '../types/options'; | ||
/** | ||
* Get the initial value from the passed elements | ||
* @param name | ||
* @param elements | ||
* Initialise the stores with data from the form, it will also use any default values provided | ||
* @param allGroups | ||
* @param stores | ||
* @param options | ||
*/ | ||
export declare function getInitialValue(name: string, elements: FormEl[], options: FormulaOptions): FormulaField; | ||
export declare function initialValues(allGroups: [string, FormEl[]][], stores: FormulaStores, options: FormulaOptions): void; | ||
export declare function getInitialFormValues(allGroups: [string, FormEl[]][], stores: FormulaStores, options: FormulaOptions): void; | ||
/** | ||
* Create the stores for the instance | ||
* Create the form reset method | ||
*/ | ||
export declare function createStores(): FormulaStores; | ||
export declare function createReset(allGroups: [string, FormEl[]][], stores: FormulaStores, options: FormulaOptions): () => void; |
{ | ||
"name": "svelte-formula", | ||
"description": "Reactive Forms for Svelte", | ||
"version": "0.6.0", | ||
"version": "0.7.0", | ||
"keywords": [ | ||
@@ -9,3 +9,5 @@ "svelte", | ||
"forms", | ||
"reactive" | ||
"reactive", | ||
"data", | ||
"validation" | ||
], | ||
@@ -12,0 +14,0 @@ "license": "MIT", |
@@ -1,2 +0,2 @@ | ||
import { writable } from 'svelte/store'; | ||
import { get, writable } from 'svelte/store'; | ||
@@ -255,115 +255,167 @@ /*! ***************************************************************************** | ||
/** | ||
* Create a data handler for any type of input field | ||
* @param name | ||
* @param elementGroup | ||
* @param options | ||
* As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
* a for loop instead | ||
* @private | ||
* @internal | ||
* @param collection | ||
*/ | ||
function createFieldExtract(name, elementGroup, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var validValue = typeof element.value !== 'number' && !element.value ? '' : element.value; | ||
var value = elementGroup.length > 1 ? elementGroup.map(function (v) { | ||
return v.id === element.id ? validValue : v.value; | ||
}).filter(function (v) { | ||
return v !== ''; | ||
}) : validValue; // Parse number values | ||
function getMultiSelectOptionValues(collection) { | ||
var value = []; | ||
if (['number', 'range'].includes(element.getAttribute('type'))) { | ||
if (Array.isArray(value)) { | ||
value = value.length > 0 ? value.map(function (v) { | ||
return parseFloat(v); | ||
}) : []; | ||
} else { | ||
value = value !== '' ? parseFloat(value) : null; | ||
} | ||
for (var i = 0; i < collection.length; i++) { | ||
if (collection[i].selected) { | ||
value.push(collection[i].value); | ||
} | ||
} | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
return value; | ||
} | ||
/** | ||
* Create a data handler for checkbox fields | ||
* @param name | ||
* Sets the element value | ||
* @param element | ||
* @param value | ||
* @param isMultiValue | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
function createCheckboxExtract(name, elementGroup, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var value = elementGroup.length > 1 ? elementGroup.map(function (e) { | ||
return e.id === element.id ? element.checked && element.value || null : e.checked && e.value || null; | ||
}).filter(function (v) { | ||
return v !== null; | ||
}) : element.checked; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
function setElementValue(element, value, isMultiValue, elementGroup) { | ||
if (isMultiValue) { | ||
elementGroup.forEach(function (el, i) { | ||
if (el.type === 'checkbox') { | ||
el.checked = value.includes(el.value); | ||
} else { | ||
el.value = value[i]; | ||
} | ||
}); | ||
} else { | ||
if (element instanceof HTMLSelectElement) { | ||
for (var i = 0; i < element.options.length; i++) { | ||
var el = element.options[i]; | ||
el.selected = value.includes(el.value); | ||
} | ||
} else if (element.type === 'radio') { | ||
elementGroup.forEach(function (el) { | ||
el.checked = value === el.value; | ||
}); | ||
} else if (element.type === 'file') { | ||
element.files = value instanceof FileList ? value : null; | ||
console.log(value); | ||
} else { | ||
element.value = value; | ||
} | ||
} | ||
} | ||
/** | ||
* Create a data handler for radio groups | ||
* @param name | ||
* @param options | ||
* Get the value or values from an element | ||
* @param element | ||
* @param isMultiValue | ||
* @param elementGroup | ||
*/ | ||
function createRadioExtract(name, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var value = element.checked ? element.value : ''; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
function getElementValues(element, isMultiValue, elementGroup) { | ||
var elValue; | ||
if (element instanceof HTMLSelectElement) { | ||
elValue = element.multiple ? getMultiSelectOptionValues(element.options) : element.value || null; | ||
} else { | ||
switch (element.type) { | ||
case 'number': | ||
case 'range': | ||
{ | ||
elValue = isMultiValue ? elementGroup.map(function (v) { | ||
return v.id === element.id ? parseFloat(element.value) : parseFloat(v.value); | ||
}).filter(function (v) { | ||
return !isNaN(v); | ||
}) : function () { | ||
var val = parseFloat(element.value); | ||
return !isNaN(val) ? val : null; | ||
}(); | ||
break; | ||
} | ||
case 'checkbox': | ||
{ | ||
elValue = isMultiValue ? elementGroup.map(function (e) { | ||
return e.id === element.id ? element.checked && element.value : e.checked && e.value; | ||
}).filter(Boolean) : element.checked; | ||
break; | ||
} | ||
case 'radio': | ||
{ | ||
elValue = element.checked ? element.value : null; | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
elValue = element.files; | ||
break; | ||
} | ||
default: | ||
{ | ||
elValue = isMultiValue ? elementGroup.map(function (v) { | ||
return v.id === element.id ? element.value : v.value; | ||
}) : element.value || null; | ||
} | ||
} | ||
} | ||
return elValue; | ||
} | ||
/** | ||
* Create a data handler for select fields | ||
* Create a data handler for any type of input field | ||
* @param name | ||
* @param elementGroup | ||
* @param options | ||
* @param stores | ||
*/ | ||
function createSelectExtract(name, options) { | ||
function createFieldExtract(name, elementGroup, options, stores) { | ||
var validator = createValidationChecker(name, options); | ||
var isMultiValue = function () { | ||
if (elementGroup[0].type === 'radio') { | ||
return false; | ||
} | ||
return !elementGroup[0].multiple; | ||
}() && elementGroup.length > 1; | ||
/** | ||
* As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
* a for loop instead | ||
* @private | ||
* @internal | ||
* @param collection | ||
* Function called on every element update, can also be called at initial value | ||
* Welcome to edge-case hell | ||
*/ | ||
function getMultiValue(collection) { | ||
var value = []; | ||
for (var i = 0; i < collection.length; i++) { | ||
value.push(collection[i].value); | ||
return function (element, isInit, isReset) { | ||
var _a, _b, _c, _d; | ||
var value; | ||
if (isInit && ((_a = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _a === void 0 ? void 0 : _a[name])) { | ||
value = isMultiValue ? ((_b = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _b === void 0 ? void 0 : _b[name]) || [] : ((_c = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _c === void 0 ? void 0 : _c[name]) || ''; | ||
} else { | ||
var storeValue = (_d = get(stores.formValues)) === null || _d === void 0 ? void 0 : _d[name]; | ||
value = storeValue ? storeValue : isMultiValue ? [] : ''; | ||
} | ||
return value; | ||
} | ||
if (!isReset) { | ||
var elValue = getElementValues(element, isMultiValue, elementGroup); | ||
return function (element) { | ||
var value = element.multiple ? getMultiValue(element.selectedOptions) : element.value; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Create a data handler for form fields | ||
* @param name | ||
* @param options | ||
*/ | ||
if (isInit && (isMultiValue || element.type === 'select-multiple')) { | ||
value = elValue.length === 0 ? value : elValue; | ||
} else if (!isReset && elValue !== null) { | ||
value = elValue; | ||
} | ||
} | ||
function createFileExtract(name, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var value = element.files; | ||
if (isInit || isReset) { | ||
setElementValue(element, value, isMultiValue, elementGroup); | ||
} | ||
return __assign({ | ||
@@ -473,25 +525,3 @@ name: name, | ||
var extract; | ||
if (element instanceof HTMLSelectElement) { | ||
extract = createSelectExtract(name, options); | ||
} else { | ||
switch (element.type) { | ||
case 'checkbox': | ||
extract = createCheckboxExtract(name, groupElements, options); | ||
break; | ||
case 'radio': | ||
extract = createRadioExtract(name, options); | ||
break; | ||
case 'file': | ||
extract = createFileExtract(name, options); | ||
break; | ||
default: | ||
extract = createFieldExtract(name, groupElements, options); | ||
} | ||
} | ||
var extract = createFieldExtract(name, groupElements, options, stores); | ||
var enrich; | ||
@@ -523,45 +553,13 @@ | ||
var initialValues = {}; | ||
var initialValidity = {}; | ||
var initialEnrichment = {}; | ||
/** | ||
* Get the initial value from the passed elements | ||
* @param name | ||
* @param elements | ||
* Initialise the stores with data from the form, it will also use any default values provided | ||
* @param allGroups | ||
* @param stores | ||
* @param options | ||
*/ | ||
function getInitialValue(name, elements, options) { | ||
var el = elements[0]; | ||
var handler; | ||
if (el instanceof HTMLSelectElement) { | ||
handler = createSelectExtract(name, options); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
handler = createCheckboxExtract(name, elements, options); | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
handler = createFileExtract(name, options); | ||
break; | ||
} | ||
case 'radio': | ||
{ | ||
handler = createRadioExtract(name, options); | ||
break; | ||
} | ||
default: | ||
{ | ||
handler = createFieldExtract(name, elements, options); | ||
} | ||
} | ||
} | ||
return handler(el); | ||
} | ||
function initialValues(allGroups, stores, options) { | ||
function getInitialFormValues(allGroups, stores, options) { | ||
var e_1, _a; | ||
@@ -571,6 +569,2 @@ | ||
var finalResult = {}; | ||
var finalValidity = {}; | ||
var finalEnrichment = {}; | ||
try { | ||
@@ -582,14 +576,15 @@ for (var allGroups_1 = __values(allGroups), allGroups_1_1 = allGroups_1.next(); !allGroups_1_1.done; allGroups_1_1 = allGroups_1.next()) { | ||
var details = getInitialValue(key, elements, options); | ||
finalResult[key] = details.value; | ||
finalValidity[key] = { | ||
invalid: details.invalid, | ||
valid: details.valid, | ||
message: details.message, | ||
errors: details.errors | ||
}; | ||
var extract = createFieldExtract(key, elements, options, stores); | ||
var _d = extract(elements[0], true), | ||
name_1 = _d.name, | ||
value = _d.value, | ||
validity = __rest(_d, ["name", "value"]); | ||
initialValues[key] = value; | ||
initialValidity[key] = validity; | ||
if ((_b = options === null || options === void 0 ? void 0 : options.enrich) === null || _b === void 0 ? void 0 : _b[key]) { | ||
var enrich = createEnrichField(key, options); | ||
finalEnrichment[key] = enrich(details.value); | ||
initialEnrichment[key] = enrich(value); | ||
} | ||
@@ -609,23 +604,61 @@ } | ||
stores.formValues.set(finalResult); | ||
stores.validity.set(finalValidity); | ||
stores.isFormValid.set(Object.values(finalValidity).every(function (v) { | ||
stores.formValues.set(initialValues); | ||
stores.initialValues.set(initialValues); | ||
stores.validity.set(initialValidity); | ||
stores.isFormValid.set(Object.values(initialValidity).every(function (v) { | ||
return v.valid; | ||
})); | ||
stores.enrichment.set(finalEnrichment); | ||
stores.enrichment.set(initialEnrichment); | ||
} | ||
/** | ||
* Create the stores for the instance | ||
* Create the form reset method | ||
*/ | ||
function createStores() { | ||
return { | ||
formValues: writable({}), | ||
submitValues: writable({}), | ||
touched: writable({}), | ||
dirty: writable({}), | ||
validity: writable({}), | ||
formValidity: writable({}), | ||
isFormValid: writable(false), | ||
enrichment: writable({}) | ||
function createReset(allGroups, stores, options) { | ||
/** | ||
* Resets the form to the initial values | ||
*/ | ||
return function () { | ||
var e_2, _a; | ||
stores.formValues.set(initialValues); | ||
stores.validity.set(initialValidity); | ||
stores.isFormValid.set(Object.values(initialValidity).every(function (v) { | ||
return v.valid; | ||
})); | ||
stores.enrichment.set(initialEnrichment); // Also override touched and dirty | ||
stores.touched.set(Object.keys(initialValues).reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = false, _a)); | ||
}, {})); | ||
stores.dirty.set(Object.keys(initialValues).reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = false, _a)); | ||
}, {})); | ||
try { | ||
// Update the elements | ||
for (var allGroups_2 = __values(allGroups), allGroups_2_1 = allGroups_2.next(); !allGroups_2_1.done; allGroups_2_1 = allGroups_2.next()) { | ||
var _b = __read(allGroups_2_1.value, 2), | ||
key = _b[0], | ||
elements = _b[1]; | ||
var extract = createFieldExtract(key, elements, options, stores); | ||
extract(elements[0], false, true); | ||
} | ||
} catch (e_2_1) { | ||
e_2 = { | ||
error: e_2_1 | ||
}; | ||
} finally { | ||
try { | ||
if (allGroups_2_1 && !allGroups_2_1.done && (_a = allGroups_2["return"])) _a.call(allGroups_2); | ||
} finally { | ||
if (e_2) throw e_2.error; | ||
} | ||
} | ||
}; | ||
@@ -729,2 +762,13 @@ } | ||
/** | ||
* Check if two arrays match | ||
* @param array1 | ||
* @param array2 | ||
*/ | ||
function matchingArrays(array1, array2) { | ||
return array1.every(function (e) { | ||
return array2.includes(e); | ||
}); | ||
} | ||
/** | ||
* Creates the handler for a group of elements for the dirty event on the group name, once an | ||
@@ -742,2 +786,3 @@ * element in the group has been changed, all element blur handlers will be removed | ||
function createDirtyHandler(name, elements, stores) { | ||
@@ -799,10 +844,3 @@ var e_1, _a; | ||
if (Array.isArray(v[groupName])) { | ||
var newVal = new Set(v[groupName]); | ||
var existing_1 = new Set(startValue); | ||
var same = __spread(newVal).every(function (e) { | ||
return __spread(existing_1).includes(e); | ||
}); | ||
if (!same) { | ||
if (!matchingArrays(v[groupName], startValue)) { | ||
stores.dirty.update(function (state) { | ||
@@ -865,5 +903,5 @@ var _a; | ||
var submitHandler = undefined; | ||
var unsub; // eslint-disable-line | ||
var unsub = function unsub() {}; // eslint-disable-line | ||
var innerReset; | ||
/** | ||
@@ -875,3 +913,2 @@ * Internal method to do binding of te action element | ||
function bindElements(node, innerOpt) { | ||
@@ -885,3 +922,4 @@ var formElements = getAllFieldsWithValidity(node); // Group elements by name | ||
initialValues(groupedMap, stores, innerOpt); // Loop over each group and setup up their initial touch and dirty handlers, | ||
getInitialFormValues(groupedMap, stores, innerOpt); | ||
innerReset = createReset(groupedMap, stores, innerOpt); // Loop over each group and setup up their initial touch and dirty handlers, | ||
// also get initial values | ||
@@ -936,2 +974,4 @@ | ||
} | ||
stores.isFormReady.set(true); | ||
} // Keep a instance of the passed node around | ||
@@ -986,2 +1026,3 @@ | ||
update: function update(updatedOpts) { | ||
stores.isFormReady.set(false); | ||
cleanupSubscriptions(); | ||
@@ -991,2 +1032,3 @@ bindElements(currentNode, updatedOpts); | ||
destroy: function destroy() { | ||
stores.isFormReady.set(false); | ||
cleanupSubscriptions(); | ||
@@ -997,2 +1039,5 @@ | ||
} | ||
}, | ||
reset: function reset() { | ||
return innerReset(); | ||
} | ||
@@ -1003,2 +1048,65 @@ }; | ||
/** | ||
* Create the stores for the the form instance, these can be set using the `defaultValue` property | ||
* of FormulaOptions | ||
* @param options | ||
* | ||
* @returns An object containing the stores for the form instance | ||
*/ | ||
function createStores(options) { | ||
var initialKeys = Object.keys((options === null || options === void 0 ? void 0 : options.defaultValues) || {}); | ||
var initialStates = initialKeys.reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = false, _a)); | ||
}, {}); | ||
var initialValidity = initialKeys.reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = { | ||
valid: true, | ||
invalid: false, | ||
message: '', | ||
errors: {} | ||
}, _a)); | ||
}, {}); | ||
var initialFormValidity = Object.keys((options === null || options === void 0 ? void 0 : options.formValidators) || {}).reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = '', _a)); | ||
}, {}); | ||
var initialEnrichment = Object.entries((options === null || options === void 0 ? void 0 : options.enrich) || {}).reduce(function (value, _a) { | ||
var _b; | ||
var _c = __read(_a, 2), | ||
key = _c[0], | ||
fns = _c[1]; | ||
return __assign(__assign({}, value), (_b = {}, _b[key] = Object.entries(fns).reduce(function (v, _a) { | ||
var _b; | ||
var _c, _d; | ||
var _e = __read(_a, 2), | ||
k = _e[0], | ||
fn = _e[1]; | ||
return __assign(__assign({}, v), (_b = {}, _b[k] = ((_c = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _c === void 0 ? void 0 : _c[key]) ? fn((_d = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _d === void 0 ? void 0 : _d[key]) : undefined, _b)); | ||
}, {}), _b)); | ||
}, {}); | ||
return { | ||
formValues: writable((options === null || options === void 0 ? void 0 : options.defaultValues) || {}), | ||
submitValues: writable({}), | ||
initialValues: writable((options === null || options === void 0 ? void 0 : options.defaultValues) || {}), | ||
touched: writable(initialStates), | ||
dirty: writable(initialStates), | ||
validity: writable(initialValidity), | ||
formValidity: writable(initialFormValidity), | ||
isFormValid: writable(false), | ||
isFormReady: writable(false), | ||
enrichment: writable(initialEnrichment) | ||
}; | ||
} | ||
/** | ||
* A global map of stores for elements with an `id` property and the `use` directive, | ||
@@ -1023,3 +1131,3 @@ * if no ID is used the store is not added | ||
// Create a store object for this instance, if there is an `id` on the element the stores will be added to formulaStores | ||
var stores = createStores(); | ||
var stores = createStores(options); | ||
@@ -1029,3 +1137,4 @@ var _a = createForm(stores, options, formulaStores), | ||
update = _a.update, | ||
destroy = _a.destroy; | ||
destroy = _a.destroy, | ||
reset = _a.reset; | ||
@@ -1035,3 +1144,4 @@ return __assign({ | ||
updateForm: update, | ||
destroyForm: destroy | ||
destroyForm: destroy, | ||
resetForm: reset | ||
}, stores); | ||
@@ -1038,0 +1148,0 @@ } |
@@ -259,115 +259,167 @@ (function (global, factory) { | ||
/** | ||
* Create a data handler for any type of input field | ||
* @param name | ||
* @param elementGroup | ||
* @param options | ||
* As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
* a for loop instead | ||
* @private | ||
* @internal | ||
* @param collection | ||
*/ | ||
function createFieldExtract(name, elementGroup, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var validValue = typeof element.value !== 'number' && !element.value ? '' : element.value; | ||
var value = elementGroup.length > 1 ? elementGroup.map(function (v) { | ||
return v.id === element.id ? validValue : v.value; | ||
}).filter(function (v) { | ||
return v !== ''; | ||
}) : validValue; // Parse number values | ||
function getMultiSelectOptionValues(collection) { | ||
var value = []; | ||
if (['number', 'range'].includes(element.getAttribute('type'))) { | ||
if (Array.isArray(value)) { | ||
value = value.length > 0 ? value.map(function (v) { | ||
return parseFloat(v); | ||
}) : []; | ||
} else { | ||
value = value !== '' ? parseFloat(value) : null; | ||
} | ||
for (var i = 0; i < collection.length; i++) { | ||
if (collection[i].selected) { | ||
value.push(collection[i].value); | ||
} | ||
} | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
return value; | ||
} | ||
/** | ||
* Create a data handler for checkbox fields | ||
* @param name | ||
* Sets the element value | ||
* @param element | ||
* @param value | ||
* @param isMultiValue | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
function createCheckboxExtract(name, elementGroup, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var value = elementGroup.length > 1 ? elementGroup.map(function (e) { | ||
return e.id === element.id ? element.checked && element.value || null : e.checked && e.value || null; | ||
}).filter(function (v) { | ||
return v !== null; | ||
}) : element.checked; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
function setElementValue(element, value, isMultiValue, elementGroup) { | ||
if (isMultiValue) { | ||
elementGroup.forEach(function (el, i) { | ||
if (el.type === 'checkbox') { | ||
el.checked = value.includes(el.value); | ||
} else { | ||
el.value = value[i]; | ||
} | ||
}); | ||
} else { | ||
if (element instanceof HTMLSelectElement) { | ||
for (var i = 0; i < element.options.length; i++) { | ||
var el = element.options[i]; | ||
el.selected = value.includes(el.value); | ||
} | ||
} else if (element.type === 'radio') { | ||
elementGroup.forEach(function (el) { | ||
el.checked = value === el.value; | ||
}); | ||
} else if (element.type === 'file') { | ||
element.files = value instanceof FileList ? value : null; | ||
console.log(value); | ||
} else { | ||
element.value = value; | ||
} | ||
} | ||
} | ||
/** | ||
* Create a data handler for radio groups | ||
* @param name | ||
* @param options | ||
* Get the value or values from an element | ||
* @param element | ||
* @param isMultiValue | ||
* @param elementGroup | ||
*/ | ||
function createRadioExtract(name, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var value = element.checked ? element.value : ''; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
function getElementValues(element, isMultiValue, elementGroup) { | ||
var elValue; | ||
if (element instanceof HTMLSelectElement) { | ||
elValue = element.multiple ? getMultiSelectOptionValues(element.options) : element.value || null; | ||
} else { | ||
switch (element.type) { | ||
case 'number': | ||
case 'range': | ||
{ | ||
elValue = isMultiValue ? elementGroup.map(function (v) { | ||
return v.id === element.id ? parseFloat(element.value) : parseFloat(v.value); | ||
}).filter(function (v) { | ||
return !isNaN(v); | ||
}) : function () { | ||
var val = parseFloat(element.value); | ||
return !isNaN(val) ? val : null; | ||
}(); | ||
break; | ||
} | ||
case 'checkbox': | ||
{ | ||
elValue = isMultiValue ? elementGroup.map(function (e) { | ||
return e.id === element.id ? element.checked && element.value : e.checked && e.value; | ||
}).filter(Boolean) : element.checked; | ||
break; | ||
} | ||
case 'radio': | ||
{ | ||
elValue = element.checked ? element.value : null; | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
elValue = element.files; | ||
break; | ||
} | ||
default: | ||
{ | ||
elValue = isMultiValue ? elementGroup.map(function (v) { | ||
return v.id === element.id ? element.value : v.value; | ||
}) : element.value || null; | ||
} | ||
} | ||
} | ||
return elValue; | ||
} | ||
/** | ||
* Create a data handler for select fields | ||
* Create a data handler for any type of input field | ||
* @param name | ||
* @param elementGroup | ||
* @param options | ||
* @param stores | ||
*/ | ||
function createSelectExtract(name, options) { | ||
function createFieldExtract(name, elementGroup, options, stores) { | ||
var validator = createValidationChecker(name, options); | ||
var isMultiValue = function () { | ||
if (elementGroup[0].type === 'radio') { | ||
return false; | ||
} | ||
return !elementGroup[0].multiple; | ||
}() && elementGroup.length > 1; | ||
/** | ||
* As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
* a for loop instead | ||
* @private | ||
* @internal | ||
* @param collection | ||
* Function called on every element update, can also be called at initial value | ||
* Welcome to edge-case hell | ||
*/ | ||
function getMultiValue(collection) { | ||
var value = []; | ||
for (var i = 0; i < collection.length; i++) { | ||
value.push(collection[i].value); | ||
return function (element, isInit, isReset) { | ||
var _a, _b, _c, _d; | ||
var value; | ||
if (isInit && ((_a = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _a === void 0 ? void 0 : _a[name])) { | ||
value = isMultiValue ? ((_b = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _b === void 0 ? void 0 : _b[name]) || [] : ((_c = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _c === void 0 ? void 0 : _c[name]) || ''; | ||
} else { | ||
var storeValue = (_d = store.get(stores.formValues)) === null || _d === void 0 ? void 0 : _d[name]; | ||
value = storeValue ? storeValue : isMultiValue ? [] : ''; | ||
} | ||
return value; | ||
} | ||
if (!isReset) { | ||
var elValue = getElementValues(element, isMultiValue, elementGroup); | ||
return function (element) { | ||
var value = element.multiple ? getMultiValue(element.selectedOptions) : element.value; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Create a data handler for form fields | ||
* @param name | ||
* @param options | ||
*/ | ||
if (isInit && (isMultiValue || element.type === 'select-multiple')) { | ||
value = elValue.length === 0 ? value : elValue; | ||
} else if (!isReset && elValue !== null) { | ||
value = elValue; | ||
} | ||
} | ||
function createFileExtract(name, options) { | ||
var validator = createValidationChecker(name, options); | ||
return function (element) { | ||
var value = element.files; | ||
if (isInit || isReset) { | ||
setElementValue(element, value, isMultiValue, elementGroup); | ||
} | ||
return __assign({ | ||
@@ -477,25 +529,3 @@ name: name, | ||
var extract; | ||
if (element instanceof HTMLSelectElement) { | ||
extract = createSelectExtract(name, options); | ||
} else { | ||
switch (element.type) { | ||
case 'checkbox': | ||
extract = createCheckboxExtract(name, groupElements, options); | ||
break; | ||
case 'radio': | ||
extract = createRadioExtract(name, options); | ||
break; | ||
case 'file': | ||
extract = createFileExtract(name, options); | ||
break; | ||
default: | ||
extract = createFieldExtract(name, groupElements, options); | ||
} | ||
} | ||
var extract = createFieldExtract(name, groupElements, options, stores); | ||
var enrich; | ||
@@ -527,45 +557,13 @@ | ||
var initialValues = {}; | ||
var initialValidity = {}; | ||
var initialEnrichment = {}; | ||
/** | ||
* Get the initial value from the passed elements | ||
* @param name | ||
* @param elements | ||
* Initialise the stores with data from the form, it will also use any default values provided | ||
* @param allGroups | ||
* @param stores | ||
* @param options | ||
*/ | ||
function getInitialValue(name, elements, options) { | ||
var el = elements[0]; | ||
var handler; | ||
if (el instanceof HTMLSelectElement) { | ||
handler = createSelectExtract(name, options); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
handler = createCheckboxExtract(name, elements, options); | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
handler = createFileExtract(name, options); | ||
break; | ||
} | ||
case 'radio': | ||
{ | ||
handler = createRadioExtract(name, options); | ||
break; | ||
} | ||
default: | ||
{ | ||
handler = createFieldExtract(name, elements, options); | ||
} | ||
} | ||
} | ||
return handler(el); | ||
} | ||
function initialValues(allGroups, stores, options) { | ||
function getInitialFormValues(allGroups, stores, options) { | ||
var e_1, _a; | ||
@@ -575,6 +573,2 @@ | ||
var finalResult = {}; | ||
var finalValidity = {}; | ||
var finalEnrichment = {}; | ||
try { | ||
@@ -586,14 +580,15 @@ for (var allGroups_1 = __values(allGroups), allGroups_1_1 = allGroups_1.next(); !allGroups_1_1.done; allGroups_1_1 = allGroups_1.next()) { | ||
var details = getInitialValue(key, elements, options); | ||
finalResult[key] = details.value; | ||
finalValidity[key] = { | ||
invalid: details.invalid, | ||
valid: details.valid, | ||
message: details.message, | ||
errors: details.errors | ||
}; | ||
var extract = createFieldExtract(key, elements, options, stores); | ||
var _d = extract(elements[0], true), | ||
name_1 = _d.name, | ||
value = _d.value, | ||
validity = __rest(_d, ["name", "value"]); | ||
initialValues[key] = value; | ||
initialValidity[key] = validity; | ||
if ((_b = options === null || options === void 0 ? void 0 : options.enrich) === null || _b === void 0 ? void 0 : _b[key]) { | ||
var enrich = createEnrichField(key, options); | ||
finalEnrichment[key] = enrich(details.value); | ||
initialEnrichment[key] = enrich(value); | ||
} | ||
@@ -613,23 +608,61 @@ } | ||
stores.formValues.set(finalResult); | ||
stores.validity.set(finalValidity); | ||
stores.isFormValid.set(Object.values(finalValidity).every(function (v) { | ||
stores.formValues.set(initialValues); | ||
stores.initialValues.set(initialValues); | ||
stores.validity.set(initialValidity); | ||
stores.isFormValid.set(Object.values(initialValidity).every(function (v) { | ||
return v.valid; | ||
})); | ||
stores.enrichment.set(finalEnrichment); | ||
stores.enrichment.set(initialEnrichment); | ||
} | ||
/** | ||
* Create the stores for the instance | ||
* Create the form reset method | ||
*/ | ||
function createStores() { | ||
return { | ||
formValues: store.writable({}), | ||
submitValues: store.writable({}), | ||
touched: store.writable({}), | ||
dirty: store.writable({}), | ||
validity: store.writable({}), | ||
formValidity: store.writable({}), | ||
isFormValid: store.writable(false), | ||
enrichment: store.writable({}) | ||
function createReset(allGroups, stores, options) { | ||
/** | ||
* Resets the form to the initial values | ||
*/ | ||
return function () { | ||
var e_2, _a; | ||
stores.formValues.set(initialValues); | ||
stores.validity.set(initialValidity); | ||
stores.isFormValid.set(Object.values(initialValidity).every(function (v) { | ||
return v.valid; | ||
})); | ||
stores.enrichment.set(initialEnrichment); // Also override touched and dirty | ||
stores.touched.set(Object.keys(initialValues).reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = false, _a)); | ||
}, {})); | ||
stores.dirty.set(Object.keys(initialValues).reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = false, _a)); | ||
}, {})); | ||
try { | ||
// Update the elements | ||
for (var allGroups_2 = __values(allGroups), allGroups_2_1 = allGroups_2.next(); !allGroups_2_1.done; allGroups_2_1 = allGroups_2.next()) { | ||
var _b = __read(allGroups_2_1.value, 2), | ||
key = _b[0], | ||
elements = _b[1]; | ||
var extract = createFieldExtract(key, elements, options, stores); | ||
extract(elements[0], false, true); | ||
} | ||
} catch (e_2_1) { | ||
e_2 = { | ||
error: e_2_1 | ||
}; | ||
} finally { | ||
try { | ||
if (allGroups_2_1 && !allGroups_2_1.done && (_a = allGroups_2["return"])) _a.call(allGroups_2); | ||
} finally { | ||
if (e_2) throw e_2.error; | ||
} | ||
} | ||
}; | ||
@@ -733,2 +766,13 @@ } | ||
/** | ||
* Check if two arrays match | ||
* @param array1 | ||
* @param array2 | ||
*/ | ||
function matchingArrays(array1, array2) { | ||
return array1.every(function (e) { | ||
return array2.includes(e); | ||
}); | ||
} | ||
/** | ||
* Creates the handler for a group of elements for the dirty event on the group name, once an | ||
@@ -746,2 +790,3 @@ * element in the group has been changed, all element blur handlers will be removed | ||
function createDirtyHandler(name, elements, stores) { | ||
@@ -803,10 +848,3 @@ var e_1, _a; | ||
if (Array.isArray(v[groupName])) { | ||
var newVal = new Set(v[groupName]); | ||
var existing_1 = new Set(startValue); | ||
var same = __spread(newVal).every(function (e) { | ||
return __spread(existing_1).includes(e); | ||
}); | ||
if (!same) { | ||
if (!matchingArrays(v[groupName], startValue)) { | ||
stores.dirty.update(function (state) { | ||
@@ -869,5 +907,5 @@ var _a; | ||
var submitHandler = undefined; | ||
var unsub; // eslint-disable-line | ||
var unsub = function unsub() {}; // eslint-disable-line | ||
var innerReset; | ||
/** | ||
@@ -879,3 +917,2 @@ * Internal method to do binding of te action element | ||
function bindElements(node, innerOpt) { | ||
@@ -889,3 +926,4 @@ var formElements = getAllFieldsWithValidity(node); // Group elements by name | ||
initialValues(groupedMap, stores, innerOpt); // Loop over each group and setup up their initial touch and dirty handlers, | ||
getInitialFormValues(groupedMap, stores, innerOpt); | ||
innerReset = createReset(groupedMap, stores, innerOpt); // Loop over each group and setup up their initial touch and dirty handlers, | ||
// also get initial values | ||
@@ -940,2 +978,4 @@ | ||
} | ||
stores.isFormReady.set(true); | ||
} // Keep a instance of the passed node around | ||
@@ -990,2 +1030,3 @@ | ||
update: function update(updatedOpts) { | ||
stores.isFormReady.set(false); | ||
cleanupSubscriptions(); | ||
@@ -995,2 +1036,3 @@ bindElements(currentNode, updatedOpts); | ||
destroy: function destroy() { | ||
stores.isFormReady.set(false); | ||
cleanupSubscriptions(); | ||
@@ -1001,2 +1043,5 @@ | ||
} | ||
}, | ||
reset: function reset() { | ||
return innerReset(); | ||
} | ||
@@ -1007,2 +1052,65 @@ }; | ||
/** | ||
* Create the stores for the the form instance, these can be set using the `defaultValue` property | ||
* of FormulaOptions | ||
* @param options | ||
* | ||
* @returns An object containing the stores for the form instance | ||
*/ | ||
function createStores(options) { | ||
var initialKeys = Object.keys((options === null || options === void 0 ? void 0 : options.defaultValues) || {}); | ||
var initialStates = initialKeys.reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = false, _a)); | ||
}, {}); | ||
var initialValidity = initialKeys.reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = { | ||
valid: true, | ||
invalid: false, | ||
message: '', | ||
errors: {} | ||
}, _a)); | ||
}, {}); | ||
var initialFormValidity = Object.keys((options === null || options === void 0 ? void 0 : options.formValidators) || {}).reduce(function (val, key) { | ||
var _a; | ||
return __assign(__assign({}, val), (_a = {}, _a[key] = '', _a)); | ||
}, {}); | ||
var initialEnrichment = Object.entries((options === null || options === void 0 ? void 0 : options.enrich) || {}).reduce(function (value, _a) { | ||
var _b; | ||
var _c = __read(_a, 2), | ||
key = _c[0], | ||
fns = _c[1]; | ||
return __assign(__assign({}, value), (_b = {}, _b[key] = Object.entries(fns).reduce(function (v, _a) { | ||
var _b; | ||
var _c, _d; | ||
var _e = __read(_a, 2), | ||
k = _e[0], | ||
fn = _e[1]; | ||
return __assign(__assign({}, v), (_b = {}, _b[k] = ((_c = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _c === void 0 ? void 0 : _c[key]) ? fn((_d = options === null || options === void 0 ? void 0 : options.defaultValues) === null || _d === void 0 ? void 0 : _d[key]) : undefined, _b)); | ||
}, {}), _b)); | ||
}, {}); | ||
return { | ||
formValues: store.writable((options === null || options === void 0 ? void 0 : options.defaultValues) || {}), | ||
submitValues: store.writable({}), | ||
initialValues: store.writable((options === null || options === void 0 ? void 0 : options.defaultValues) || {}), | ||
touched: store.writable(initialStates), | ||
dirty: store.writable(initialStates), | ||
validity: store.writable(initialValidity), | ||
formValidity: store.writable(initialFormValidity), | ||
isFormValid: store.writable(false), | ||
isFormReady: store.writable(false), | ||
enrichment: store.writable(initialEnrichment) | ||
}; | ||
} | ||
/** | ||
* A global map of stores for elements with an `id` property and the `use` directive, | ||
@@ -1027,3 +1135,3 @@ * if no ID is used the store is not added | ||
// Create a store object for this instance, if there is an `id` on the element the stores will be added to formulaStores | ||
var stores = createStores(); | ||
var stores = createStores(options); | ||
@@ -1033,3 +1141,4 @@ var _a = createForm(stores, options, formulaStores), | ||
update = _a.update, | ||
destroy = _a.destroy; | ||
destroy = _a.destroy, | ||
reset = _a.reset; | ||
@@ -1039,3 +1148,4 @@ return __assign({ | ||
updateForm: update, | ||
destroyForm: destroy | ||
destroyForm: destroy, | ||
resetForm: reset | ||
}, stores); | ||
@@ -1042,0 +1152,0 @@ } |
import { Writable } from 'svelte/store'; | ||
import { FormulaError, FormValues } from './forms'; | ||
import { FormulaOptions } from 'packages/svelte/formula/src/types/options'; | ||
import { FormulaOptions } from './options'; | ||
/** | ||
@@ -17,2 +17,6 @@ * The stores available in Formula | ||
/** | ||
* A store containing the initial values | ||
*/ | ||
initialValues: Writable<FormValues>; | ||
/** | ||
* A store containing the touched status of each named field | ||
@@ -38,2 +42,6 @@ */ | ||
/** | ||
* Observable value if the form state is ready to be used | ||
*/ | ||
isFormReady: Writable<boolean>; | ||
/** | ||
* A store containing additional field enrichment | ||
@@ -62,2 +70,6 @@ */ | ||
destroyForm: () => void; | ||
/** | ||
* Resets the form to the initial value | ||
*/ | ||
resetForm: () => void; | ||
} |
import { CustomValidationMessages, ValidationRule, ValidationRules } from './validation'; | ||
import { EnrichFields } from './enrich'; | ||
import { FormValues } from 'svelte-formula'; | ||
/** | ||
@@ -31,2 +32,6 @@ * Optional settings for Formula | ||
enrich?: EnrichFields; | ||
/** | ||
* Default values are used as initial values for the form fields if there is no value already set on the form | ||
*/ | ||
defaultValues?: FormValues; | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
121928
22
2260