svelte-formula
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -8,2 +8,33 @@ # Changelog | ||
## [0.5.0] 2021-02-17 | ||
### Added | ||
- New [Documentation Site](https://tanepiper.github.io/svelte-formula/) (still WIP) | ||
- Added `messages` option to FormulaOptions, this is a key/value `Object` for setting custom validation messages per | ||
error: | ||
```sveltehtml | ||
<script> | ||
import { formula } from 'svelte-formula' | ||
const { form } = formula({ | ||
messages: { | ||
valueMissing: 'Debes ingresar un nombre de usuario' | ||
} | ||
}) | ||
</script> | ||
``` | ||
- Add support for validation messages via HTML `data-*` attributes - these will always take presidency over items in | ||
the `messages` object if both exist. The format of the name should be a hyphen before any characters that are | ||
uppercase (e.g `valueMissing` becomes `data-value-missing`) | ||
```html | ||
<input type="text" name="postcode" required data-value-missing="Bitte geben Sie Ihre Postleitzahl ein" /> | ||
``` | ||
### Changed | ||
- More internal performance improvements | ||
## [0.4.0] 2021-02-16 | ||
@@ -10,0 +41,0 @@ |
import { FormEl } from '../types/forms'; | ||
import { ValidationRules } from '../types/validation'; | ||
import { ValidationRule } from '../types/validation'; | ||
import { FormulaStores } from '../types/formula'; | ||
import { FormulaOptions } from '../types/options'; | ||
/** | ||
@@ -8,4 +9,5 @@ * Extract the errors from the element validity - as it's not enumerable, it cannot be | ||
* @param el | ||
* @param custom | ||
*/ | ||
export declare function extractErrors(el: FormEl): Record<string, boolean>; | ||
export declare function extractErrors(el: FormEl, custom?: Record<string, boolean>): Record<string, boolean>; | ||
/** | ||
@@ -16,10 +18,9 @@ * Do form level validations | ||
*/ | ||
export declare function checkFormValidity(stores: FormulaStores, customValidators: ValidationRules): () => void; | ||
export declare function checkFormValidity(stores: FormulaStores, customValidators: ValidationRule): () => void; | ||
/** | ||
* Check the validity of a field and against custom validators | ||
* @param el | ||
* @param value | ||
* @param customValidators | ||
* @param name | ||
* @param options | ||
*/ | ||
export declare function checkValidity(el: FormEl, value: unknown | unknown[], customValidators?: ValidationRules): { | ||
export declare function checkValidity(name: string, options?: FormulaOptions): (el: FormEl, value: unknown | unknown[]) => { | ||
valid: boolean; | ||
@@ -26,0 +27,0 @@ message: string; |
import { FormEl } from '../types/forms'; | ||
import { ValidationRules } from '../types/validation'; | ||
import { FormulaStores } from '../types/formula'; | ||
export declare function createHandler(name: string, eventName: string, element: FormEl, groupElements: FormEl[], stores: FormulaStores, customValidators?: ValidationRules): () => void; | ||
import { FormulaOptions } from 'packages/svelte/formula/src/types/options'; | ||
/** | ||
* Create a handler for the passed element | ||
* @param name | ||
* @param eventName | ||
* @param element | ||
* @param groupElements | ||
* @param stores | ||
* @param options | ||
*/ | ||
export declare function createHandler(name: string, eventName: string, element: FormEl, groupElements: FormEl[], stores: FormulaStores, options: FormulaOptions): () => void; | ||
/** | ||
* Create a handler for a form element submission, when called it copies the contents | ||
@@ -7,0 +16,0 @@ * of the current value store to the submit store and then unsubscribes |
@@ -1,44 +0,58 @@ | ||
import { ExtractedFormInfo, FormEl } from '../types/forms'; | ||
import { ValidationRules } from '../types/validation'; | ||
import { FormEl, FormulaField } from '../types/forms'; | ||
import { FormulaOptions } from 'packages/svelte/formula/src/types/options'; | ||
/** | ||
* Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* doesn't have a special case | ||
* Create a data handler for any type of input field | ||
* @param name | ||
* @param element | ||
* @param groupElements | ||
* @param customValidators | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
export declare function extractData(name: string, element: FormEl, groupElements: FormEl[], customValidators?: ValidationRules): ExtractedFormInfo; | ||
export declare function createFieldExtract(name: string, elementGroup: FormEl[], options: FormulaOptions): (element: HTMLInputElement) => FormulaField; | ||
/** | ||
* Extract the data from an `<input type="checkbox"> element - this returns a boolean value | ||
* if a single checkbox. If multiple checkboxes are detected it returns an array value | ||
* Create a data handler for checkbox fields | ||
* @param name | ||
* @param element The element being checked | ||
* @param elements All elements from the name group | ||
* @param customValidators | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
export declare function extractCheckbox(name: string, element: HTMLInputElement, elements: HTMLInputElement[], customValidators?: ValidationRules): ExtractedFormInfo; | ||
export declare function createCheckboxExtract(name: string, elementGroup: FormEl[], options: FormulaOptions): (element: HTMLInputElement) => { | ||
valid: boolean; | ||
message: string; | ||
errors: Record<string, boolean>; | ||
name: string; | ||
value: boolean | string[]; | ||
}; | ||
/** | ||
* Extract the data from an `<input type="radio">` element, returning the value | ||
* only for checked items, this works both with initial values and when the user | ||
* selects a value | ||
* Create a data handler for radio groups | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
export declare function extractRadio(name: string, el: HTMLInputElement, customValidators?: ValidationRules): ExtractedFormInfo; | ||
export declare function createRadioExtract(name: string, options: FormulaOptions): (element: HTMLInputElement) => { | ||
valid: boolean; | ||
message: string; | ||
errors: Record<string, boolean>; | ||
name: string; | ||
value: string; | ||
}; | ||
/** | ||
* Extract the data from a `<select>` element - here we can support single values | ||
* or if the field is multiple it will return an array of values | ||
* Create a data handler for select fields | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
export declare function extractSelect(name: string, el: HTMLSelectElement, customValidators?: ValidationRules): ExtractedFormInfo; | ||
export declare function createSelectExtract(name: string, options: FormulaOptions): (element: HTMLSelectElement) => { | ||
valid: boolean; | ||
message: string; | ||
errors: Record<string, boolean>; | ||
name: string; | ||
value: string | any[]; | ||
}; | ||
/** | ||
* Extract data from the files | ||
* Create a data handler for form fields | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
export declare function extractFile(name: string, el: HTMLInputElement, customValidators?: ValidationRules): ExtractedFormInfo; | ||
export declare function createFileExtract(name: string, options: FormulaOptions): (element: HTMLInputElement) => { | ||
valid: boolean; | ||
message: string; | ||
errors: Record<string, boolean>; | ||
name: string; | ||
value: FileList; | ||
}; |
@@ -11,13 +11,1 @@ import { FormEl } from '../types/forms'; | ||
export declare function getAllFieldsWithValidity(rootEl: HTMLElement): FormEl[]; | ||
/** | ||
* Check if our checkbox has multiple values of the same name | ||
* @param name | ||
* @param elements | ||
*/ | ||
export declare function isMultiCheckbox(name: string, elements: FormEl[]): boolean; | ||
/** | ||
* Check if our checkbox has multiple values of the same name | ||
* @param name | ||
* @param elements | ||
*/ | ||
export declare function hasMultipleNames(name: string, elements: FormEl[]): boolean; |
import { FormEl } from '../types/forms'; | ||
import { FormulaStores } from '../types/formula'; | ||
import { ValidationFn } from '../types/validation'; | ||
import { FormulaOptions } from '../types/options'; | ||
/** | ||
* Create the initial value type for the current element, handling cases for multiple | ||
* values | ||
* @param name | ||
* @param el | ||
* @param groupElements | ||
* @param stores | ||
* @param customValidators | ||
*/ | ||
export declare function createInitialValues(name: string, el: FormEl, groupElements: FormEl[], stores: FormulaStores, customValidators: Record<string, ValidationFn>): void; | ||
/** | ||
* Get the initial value from the passed elements | ||
@@ -19,5 +9,5 @@ * @param name | ||
* @param stores | ||
* @param validations | ||
* @param options | ||
*/ | ||
export declare function getInitialValue(name: string, elements: FormEl[], stores: FormulaStores, validations: Record<string, ValidationFn>): void; | ||
export declare function getInitialValue(name: string, elements: FormEl[], stores: FormulaStores, options: FormulaOptions): void; | ||
/** | ||
@@ -24,0 +14,0 @@ * Create the stores for the instance |
{ | ||
"name": "svelte-formula", | ||
"description": "Reactive Forms for Svelte", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"keywords": [ | ||
@@ -16,9 +16,9 @@ "svelte", | ||
}, | ||
"homepage": "https://github.com/tanepiper/svelte-plugins/tree/main/packages/svelte/formula", | ||
"homepage": "https://tanepiper.github.io/svelte-formula/", | ||
"bugs": { | ||
"url": "https://github.com/tanepiper/svelte-plugins/issues" | ||
"url": "https://github.com/tanepiper/svelte-formula/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/tanepiper/svelte-plugins.git" | ||
"url": "https://github.com/tanepiper/svelte-formula.git" | ||
}, | ||
@@ -25,0 +25,0 @@ "peerDependencies": { |
@@ -7,2 +7,4 @@ # Svelte Formula | ||
[Documentation](https://tanepiper.github.io/svelte-formula/) | ||
Formula is a zero-configuration reactive form library for Svelte, currently in early development. | ||
@@ -26,7 +28,8 @@ | ||
| Options | Type | Description | | ||
| ---------------- | -------- | ----------------------------------------------------------- | | ||
| `locale` | `String` | Optional locale for `id` sorting when using multiple values | | ||
| `validators` | `Object` | An object containing custom validators for fields | | ||
| `formValidators` | `Object` | An object containing custom validators for the form | | ||
| Options | Type | Description | | ||
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------- | | ||
| `locale` | `String` | Optional locale for `id` sorting when using multiple values | | ||
| `messages` | `Object` | An object containing a key for each named field and an object containing key/value text with replacement text | | ||
| `validators` | `Object` | An object containing custom validators for fields | | ||
| `formValidators` | `Object` | An object containing custom validators for the form | | ||
@@ -87,4 +90,4 @@ ### Example | ||
A store that contains a key/value `Object` of errors on the form when using `formValidators` - unlike `validity` this only | ||
contains a key and string value that is any message from the validator. | ||
A store that contains a key/value `Object` of errors on the form when using `formValidators` - unlike `validity` this | ||
only contains a key and string value that is any message from the validator. | ||
@@ -132,2 +135,14 @@ ### isFormValid | ||
### Custom Validation Text & Localisation | ||
It's possible to provide custom localisation or text for error messages, they can be provided two ways: | ||
- In the `formula` constructor pass a `messages` object. They key should be the `name` of the field. The value is | ||
an `Object` containing the key of the error the message is for, and the value the replacement string. | ||
- In the HTML add a `data-*` property for the value, separating any camel case values with a hyphen (e.g. `valueMissing` | ||
becomes `data-value-missing`) | ||
In order of precedence, the data value will always come before the constructor options | ||
### Example | ||
@@ -141,2 +156,7 @@ | ||
const { form, validity } = formula({ | ||
messages: { | ||
username: { | ||
valueMissing: 'You really should put an email address in' | ||
} | ||
}, | ||
validators: { | ||
@@ -154,3 +174,4 @@ username: { | ||
<label for='signup-username'>Username</label> | ||
<input type='email' name='username' id='signup-username' required minlength='8' /> | ||
<input type='email' name='username' id='signup-username' required minlength='8' | ||
data-value-missing='This error message will appear before the one in the options' /> | ||
<div hidden={$validity?.email?.valid}>{$validity?.email?.message}</div> | ||
@@ -210,8 +231,9 @@ | ||
- [x] Custom form-level validation via `formula` options | ||
- [x] Support for localised messages for validation errors | ||
### Other Items | ||
- [ ] Add Unit Tests | ||
- [ ] Add full documentation | ||
- [ ] Add Unit Tests - IN PROGRESS | ||
- [ ] Add full documentation - IN PROGRESS | ||
Icon made by [Eucalyp](https://creativemarket.com/eucalyp) from [flaticon.com](https://www.flaticon.com) |
@@ -83,5 +83,6 @@ import { writable } from 'svelte/store'; | ||
* @param el | ||
* @param custom | ||
*/ | ||
function extractErrors(el) { | ||
function extractErrors(el, custom) { | ||
var output = {}; | ||
@@ -98,3 +99,3 @@ | ||
return output; | ||
return __assign(__assign({}, output), custom); | ||
} | ||
@@ -134,127 +135,158 @@ /** | ||
} | ||
function checkForCustomMessage(name, errors, el, options) { | ||
var messages = options.messages; | ||
var customMessages = messages && messages[name] || {}; | ||
var message; | ||
var dataSet = el.dataset; | ||
Object.keys(dataSet).forEach(function (key) { | ||
if (errors[key]) { | ||
message = dataSet[key]; | ||
} | ||
}); | ||
if (!message) { | ||
Object.keys(customMessages).forEach(function (key) { | ||
if (errors[key]) { | ||
message = customMessages[key]; | ||
} | ||
}); | ||
} | ||
return message; | ||
} | ||
/** | ||
* Check the validity of a field and against custom validators | ||
* @param el | ||
* @param value | ||
* @param customValidators | ||
* @param name | ||
* @param options | ||
*/ | ||
function checkValidity(el, value, customValidators) { | ||
var result = { | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
if ((value !== '' || value !== null) && customValidators) { | ||
var validators = Object.entries(customValidators); | ||
function checkValidity(name, options) { | ||
/** | ||
* Method called each time we want to do validity | ||
*/ | ||
return function (el, value) { | ||
el.setCustomValidity(''); | ||
for (var i = 0; i < validators.length; i++) { | ||
var _a = __read(validators[i], 2), | ||
name_2 = _a[0], | ||
validator = _a[1]; | ||
if (!options) { | ||
return { | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
} | ||
var message = validator(value); | ||
var validators = options.validators; | ||
var customErrors = {}; // Handle custom validation | ||
if (message === null) { | ||
continue; | ||
} | ||
if ((value !== '' || value !== null) && validators && validators[name]) { | ||
var customValidators = Object.entries(validators[name]); | ||
if (result.valid === true) { | ||
result.valid = false; | ||
result.message = message; | ||
result.errors[name_2] = true; | ||
} else { | ||
result.errors[name_2] = true; | ||
for (var i = 0; i < customValidators.length; i++) { | ||
var _a = __read(customValidators[i], 2), | ||
name_2 = _a[0], | ||
validator = _a[1]; | ||
var message_1 = validator(value); | ||
if (message_1 !== null) { | ||
if (!el.validationMessage) { | ||
el.setCustomValidity(message_1); | ||
} | ||
customErrors[name_2] = true; | ||
} | ||
} | ||
} | ||
} | ||
} // Check for any custom messages | ||
return result; | ||
var errors = extractErrors(el, customErrors); | ||
var message = checkForCustomMessage(name, errors, el, options); | ||
return { | ||
valid: el.checkValidity(), | ||
message: message || el.validationMessage, | ||
errors: errors | ||
}; | ||
}; | ||
} | ||
/** | ||
* Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* doesn't have a special case | ||
* Create a data handler for any type of input field | ||
* @param name | ||
* @param element | ||
* @param groupElements | ||
* @param customValidators | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
function extractData(name, element, groupElements, customValidators) { | ||
var validValue = element.value === '' || typeof element.value === 'undefined' ? '' : element.value; | ||
var value = groupElements.length > 1 ? groupElements.map(function (v) { | ||
if (v.id === element.id) { | ||
return validValue; | ||
} | ||
function createFieldExtract(name, elementGroup, options) { | ||
var validator = checkValidity(name, options); | ||
return function (element) { | ||
var validValue = element.value === '' || element.value === null || typeof element.value === 'undefined' ? '' : 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 | ||
return v.value; | ||
}).filter(function (v) { | ||
return v !== ''; | ||
}) : validValue; | ||
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; | ||
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; | ||
} | ||
} | ||
} | ||
var validity = checkValidity(element, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Extract the data from an `<input type="checkbox"> element - this returns a boolean value | ||
* if a single checkbox. If multiple checkboxes are detected it returns an array value | ||
* Create a data handler for checkbox fields | ||
* @param name | ||
* @param element The element being checked | ||
* @param elements All elements from the name group | ||
* @param customValidators | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
function extractCheckbox(name, element, elements, customValidators) { | ||
var value = elements.length > 1 ? elements.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; | ||
var validity = checkValidity(element, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
function createCheckboxExtract(name, elementGroup, options) { | ||
var validator = checkValidity(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)); | ||
}; | ||
} | ||
/** | ||
* Extract the data from an `<input type="radio">` element, returning the value | ||
* only for checked items, this works both with initial values and when the user | ||
* selects a value | ||
* Create a data handler for radio groups | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function extractRadio(name, el, customValidators) { | ||
var value = el.checked ? el.value : ''; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
function createRadioExtract(name, options) { | ||
var validator = checkValidity(name, options); | ||
return function (element) { | ||
var value = element.checked ? element.value : ''; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Extract the data from a `<select>` element - here we can support single values | ||
* or if the field is multiple it will return an array of values | ||
* Create a data handler for select fields | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function extractSelect(name, el, customValidators) { | ||
function createSelectExtract(name, options) { | ||
var validator = checkValidity(name, options); | ||
/** | ||
@@ -267,2 +299,3 @@ * As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
*/ | ||
function getMultiValue(collection) { | ||
@@ -278,23 +311,25 @@ var value = []; | ||
var value = el.multiple ? getMultiValue(el.selectedOptions) : el.value; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
return function (element) { | ||
var value = element.multiple ? getMultiValue(element.selectedOptions) : element.value; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Extract data from the files | ||
* Create a data handler for form fields | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function extractFile(name, el, customValidators) { | ||
var value = el.files; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
function createFileExtract(name, options) { | ||
var validator = checkValidity(name, options); | ||
return function (element) { | ||
var value = element.files; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
@@ -331,47 +366,50 @@ | ||
/** | ||
* Create an event handler for the passed event and handle the value type | ||
* @param name | ||
* @param groupElements | ||
* Creates an event handler for the passed element with it's data handler | ||
* @param extractor | ||
* @param stores | ||
* @param customValidators | ||
*/ | ||
function createEventHandler(name, groupElements, stores, customValidators) { | ||
function createHandlerForData(extractor, stores) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
valueUpdate(extractor(el), stores); | ||
}; | ||
} | ||
/** | ||
* Create a handler for the passed element | ||
* @param name | ||
* @param eventName | ||
* @param element | ||
* @param groupElements | ||
* @param stores | ||
* @param options | ||
*/ | ||
if (el instanceof HTMLSelectElement) { | ||
valueUpdate(extractSelect(name, el, customValidators), stores); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
valueUpdate(extractCheckbox(name, el, groupElements, customValidators), stores); | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
valueUpdate(extractFile(name, el, customValidators), stores); | ||
break; | ||
} | ||
function createHandler(name, eventName, element, groupElements, stores, options) { | ||
var extract; | ||
case 'radio': | ||
{ | ||
valueUpdate(extractRadio(name, el), stores); | ||
break; | ||
} | ||
if (element instanceof HTMLSelectElement) { | ||
extract = createSelectExtract(name, options); | ||
} else { | ||
switch (element.type) { | ||
case 'checkbox': | ||
extract = createCheckboxExtract(name, groupElements, options); | ||
break; | ||
default: | ||
{ | ||
valueUpdate(extractData(name, el, groupElements, customValidators), stores); | ||
} | ||
} | ||
case 'radio': | ||
extract = createRadioExtract(name, options); | ||
break; | ||
case 'file': | ||
extract = createFileExtract(name, options); | ||
break; | ||
default: | ||
extract = createFieldExtract(name, groupElements, options); | ||
} | ||
}; | ||
} | ||
} | ||
function createHandler(name, eventName, element, groupElements, stores, customValidators) { | ||
var handler = createEventHandler(name, groupElements, stores, customValidators); | ||
var handler = createHandlerForData(extract, stores); | ||
element.addEventListener(eventName, handler); | ||
@@ -420,56 +458,48 @@ return function () { | ||
/** | ||
* Create the initial value type for the current element, handling cases for multiple | ||
* values | ||
* Get the initial value from the passed elements | ||
* @param name | ||
* @param el | ||
* @param groupElements | ||
* @param elements | ||
* @param stores | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function createInitialValues(name, el, groupElements, stores, customValidators) { | ||
if (el instanceof HTMLSelectElement) { | ||
initValues(extractSelect(name, el, customValidators), stores); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
initValues(extractCheckbox(name, el, groupElements, customValidators), stores); | ||
break; | ||
} | ||
function getInitialValue(name, elements, stores, options) { | ||
for (var i = 0; i < elements.length; i++) { | ||
var el = elements[i]; | ||
var handler = void 0; | ||
case 'file': | ||
{ | ||
initValues(extractFile(name, el, customValidators), stores); | ||
break; | ||
} | ||
if (el instanceof HTMLSelectElement) { | ||
handler = createSelectExtract(name, options); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
handler = createCheckboxExtract(name, elements, options); | ||
break; | ||
} | ||
case 'radio': | ||
{ | ||
initValues(extractRadio(name, el), stores); | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
handler = createFileExtract(name, options); | ||
break; | ||
} | ||
default: | ||
{ | ||
initValues(extractData(name, el, groupElements, customValidators), stores); | ||
} | ||
case 'radio': | ||
{ | ||
handler = createRadioExtract(name, options); | ||
break; | ||
} | ||
default: | ||
{ | ||
handler = createFieldExtract(name, elements, options); | ||
} | ||
} | ||
} | ||
initValues(handler(el), stores); | ||
} | ||
} | ||
/** | ||
* Get the initial value from the passed elements | ||
* @param name | ||
* @param elements | ||
* @param stores | ||
* @param validations | ||
*/ | ||
function getInitialValue(name, elements, stores, validations) { | ||
elements.forEach(function (el) { | ||
return createInitialValues(name, el, elements, stores, validations); | ||
}); | ||
} | ||
/** | ||
* Create the stores for the instance | ||
@@ -529,3 +559,3 @@ */ | ||
return function () { | ||
__spread(elMap).forEach(function (_a) { | ||
return __spread(elMap).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
@@ -594,2 +624,10 @@ el = _b[0], | ||
}); | ||
__spread(handlers).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
el = _b[0], | ||
handler = _b[1]; | ||
return el.removeEventListener('blur', handler); | ||
}); | ||
} | ||
@@ -606,3 +644,3 @@ })(); | ||
return function () { | ||
__spread(handlers).forEach(function (_a) { | ||
return __spread(handlers).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
@@ -644,15 +682,12 @@ el = _b[0], | ||
groupedMap.forEach(function (_a) { | ||
var _b; | ||
var _b = __read(_a, 2), | ||
name = _b[0], | ||
elements = _b[1]; | ||
var _c = __read(_a, 2), | ||
name = _c[0], | ||
elements = _c[1]; | ||
var customValidations = (_b = options === null || options === void 0 ? void 0 : options.validators) === null || _b === void 0 ? void 0 : _b[name]; | ||
touchHandlers.add(createTouchHandlers(name, elements, stores)); | ||
getInitialValue(name, elements, stores, customValidations); | ||
getInitialValue(name, elements, stores, options); | ||
dirtyHandlers.add(createDirtyHandler(name, elements, stores)); | ||
elements.forEach(function (el) { | ||
if (el instanceof HTMLSelectElement) { | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, customValidations)); | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, options)); | ||
} else { | ||
@@ -669,3 +704,3 @@ switch (el.type) { | ||
{ | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, customValidations)); | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, options)); | ||
break; | ||
@@ -675,3 +710,3 @@ } | ||
default: | ||
keyupHandlers.add(createHandler(name, 'keyup', el, elements, stores, customValidations)); | ||
keyupHandlers.add(createHandler(name, 'keyup', el, elements, stores, options)); | ||
} | ||
@@ -678,0 +713,0 @@ } |
@@ -87,5 +87,6 @@ (function (global, factory) { | ||
* @param el | ||
* @param custom | ||
*/ | ||
function extractErrors(el) { | ||
function extractErrors(el, custom) { | ||
var output = {}; | ||
@@ -102,3 +103,3 @@ | ||
return output; | ||
return __assign(__assign({}, output), custom); | ||
} | ||
@@ -138,127 +139,158 @@ /** | ||
} | ||
function checkForCustomMessage(name, errors, el, options) { | ||
var messages = options.messages; | ||
var customMessages = messages && messages[name] || {}; | ||
var message; | ||
var dataSet = el.dataset; | ||
Object.keys(dataSet).forEach(function (key) { | ||
if (errors[key]) { | ||
message = dataSet[key]; | ||
} | ||
}); | ||
if (!message) { | ||
Object.keys(customMessages).forEach(function (key) { | ||
if (errors[key]) { | ||
message = customMessages[key]; | ||
} | ||
}); | ||
} | ||
return message; | ||
} | ||
/** | ||
* Check the validity of a field and against custom validators | ||
* @param el | ||
* @param value | ||
* @param customValidators | ||
* @param name | ||
* @param options | ||
*/ | ||
function checkValidity(el, value, customValidators) { | ||
var result = { | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
if ((value !== '' || value !== null) && customValidators) { | ||
var validators = Object.entries(customValidators); | ||
function checkValidity(name, options) { | ||
/** | ||
* Method called each time we want to do validity | ||
*/ | ||
return function (el, value) { | ||
el.setCustomValidity(''); | ||
for (var i = 0; i < validators.length; i++) { | ||
var _a = __read(validators[i], 2), | ||
name_2 = _a[0], | ||
validator = _a[1]; | ||
if (!options) { | ||
return { | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
} | ||
var message = validator(value); | ||
var validators = options.validators; | ||
var customErrors = {}; // Handle custom validation | ||
if (message === null) { | ||
continue; | ||
} | ||
if ((value !== '' || value !== null) && validators && validators[name]) { | ||
var customValidators = Object.entries(validators[name]); | ||
if (result.valid === true) { | ||
result.valid = false; | ||
result.message = message; | ||
result.errors[name_2] = true; | ||
} else { | ||
result.errors[name_2] = true; | ||
for (var i = 0; i < customValidators.length; i++) { | ||
var _a = __read(customValidators[i], 2), | ||
name_2 = _a[0], | ||
validator = _a[1]; | ||
var message_1 = validator(value); | ||
if (message_1 !== null) { | ||
if (!el.validationMessage) { | ||
el.setCustomValidity(message_1); | ||
} | ||
customErrors[name_2] = true; | ||
} | ||
} | ||
} | ||
} | ||
} // Check for any custom messages | ||
return result; | ||
var errors = extractErrors(el, customErrors); | ||
var message = checkForCustomMessage(name, errors, el, options); | ||
return { | ||
valid: el.checkValidity(), | ||
message: message || el.validationMessage, | ||
errors: errors | ||
}; | ||
}; | ||
} | ||
/** | ||
* Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* doesn't have a special case | ||
* Create a data handler for any type of input field | ||
* @param name | ||
* @param element | ||
* @param groupElements | ||
* @param customValidators | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
function extractData(name, element, groupElements, customValidators) { | ||
var validValue = element.value === '' || typeof element.value === 'undefined' ? '' : element.value; | ||
var value = groupElements.length > 1 ? groupElements.map(function (v) { | ||
if (v.id === element.id) { | ||
return validValue; | ||
} | ||
function createFieldExtract(name, elementGroup, options) { | ||
var validator = checkValidity(name, options); | ||
return function (element) { | ||
var validValue = element.value === '' || element.value === null || typeof element.value === 'undefined' ? '' : 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 | ||
return v.value; | ||
}).filter(function (v) { | ||
return v !== ''; | ||
}) : validValue; | ||
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; | ||
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; | ||
} | ||
} | ||
} | ||
var validity = checkValidity(element, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Extract the data from an `<input type="checkbox"> element - this returns a boolean value | ||
* if a single checkbox. If multiple checkboxes are detected it returns an array value | ||
* Create a data handler for checkbox fields | ||
* @param name | ||
* @param element The element being checked | ||
* @param elements All elements from the name group | ||
* @param customValidators | ||
* @param elementGroup | ||
* @param options | ||
*/ | ||
function extractCheckbox(name, element, elements, customValidators) { | ||
var value = elements.length > 1 ? elements.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; | ||
var validity = checkValidity(element, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
function createCheckboxExtract(name, elementGroup, options) { | ||
var validator = checkValidity(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)); | ||
}; | ||
} | ||
/** | ||
* Extract the data from an `<input type="radio">` element, returning the value | ||
* only for checked items, this works both with initial values and when the user | ||
* selects a value | ||
* Create a data handler for radio groups | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function extractRadio(name, el, customValidators) { | ||
var value = el.checked ? el.value : ''; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
function createRadioExtract(name, options) { | ||
var validator = checkValidity(name, options); | ||
return function (element) { | ||
var value = element.checked ? element.value : ''; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Extract the data from a `<select>` element - here we can support single values | ||
* or if the field is multiple it will return an array of values | ||
* Create a data handler for select fields | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function extractSelect(name, el, customValidators) { | ||
function createSelectExtract(name, options) { | ||
var validator = checkValidity(name, options); | ||
/** | ||
@@ -271,2 +303,3 @@ * As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
*/ | ||
function getMultiValue(collection) { | ||
@@ -282,23 +315,25 @@ var value = []; | ||
var value = el.multiple ? getMultiValue(el.selectedOptions) : el.value; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
return function (element) { | ||
var value = element.multiple ? getMultiValue(element.selectedOptions) : element.value; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
/** | ||
* Extract data from the files | ||
* Create a data handler for form fields | ||
* @param name | ||
* @param el | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function extractFile(name, el, customValidators) { | ||
var value = el.files; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
function createFileExtract(name, options) { | ||
var validator = checkValidity(name, options); | ||
return function (element) { | ||
var value = element.files; | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validator(element, value)); | ||
}; | ||
} | ||
@@ -335,47 +370,50 @@ | ||
/** | ||
* Create an event handler for the passed event and handle the value type | ||
* @param name | ||
* @param groupElements | ||
* Creates an event handler for the passed element with it's data handler | ||
* @param extractor | ||
* @param stores | ||
* @param customValidators | ||
*/ | ||
function createEventHandler(name, groupElements, stores, customValidators) { | ||
function createHandlerForData(extractor, stores) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
valueUpdate(extractor(el), stores); | ||
}; | ||
} | ||
/** | ||
* Create a handler for the passed element | ||
* @param name | ||
* @param eventName | ||
* @param element | ||
* @param groupElements | ||
* @param stores | ||
* @param options | ||
*/ | ||
if (el instanceof HTMLSelectElement) { | ||
valueUpdate(extractSelect(name, el, customValidators), stores); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
valueUpdate(extractCheckbox(name, el, groupElements, customValidators), stores); | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
valueUpdate(extractFile(name, el, customValidators), stores); | ||
break; | ||
} | ||
function createHandler(name, eventName, element, groupElements, stores, options) { | ||
var extract; | ||
case 'radio': | ||
{ | ||
valueUpdate(extractRadio(name, el), stores); | ||
break; | ||
} | ||
if (element instanceof HTMLSelectElement) { | ||
extract = createSelectExtract(name, options); | ||
} else { | ||
switch (element.type) { | ||
case 'checkbox': | ||
extract = createCheckboxExtract(name, groupElements, options); | ||
break; | ||
default: | ||
{ | ||
valueUpdate(extractData(name, el, groupElements, customValidators), stores); | ||
} | ||
} | ||
case 'radio': | ||
extract = createRadioExtract(name, options); | ||
break; | ||
case 'file': | ||
extract = createFileExtract(name, options); | ||
break; | ||
default: | ||
extract = createFieldExtract(name, groupElements, options); | ||
} | ||
}; | ||
} | ||
} | ||
function createHandler(name, eventName, element, groupElements, stores, customValidators) { | ||
var handler = createEventHandler(name, groupElements, stores, customValidators); | ||
var handler = createHandlerForData(extract, stores); | ||
element.addEventListener(eventName, handler); | ||
@@ -424,56 +462,48 @@ return function () { | ||
/** | ||
* Create the initial value type for the current element, handling cases for multiple | ||
* values | ||
* Get the initial value from the passed elements | ||
* @param name | ||
* @param el | ||
* @param groupElements | ||
* @param elements | ||
* @param stores | ||
* @param customValidators | ||
* @param options | ||
*/ | ||
function createInitialValues(name, el, groupElements, stores, customValidators) { | ||
if (el instanceof HTMLSelectElement) { | ||
initValues(extractSelect(name, el, customValidators), stores); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
initValues(extractCheckbox(name, el, groupElements, customValidators), stores); | ||
break; | ||
} | ||
function getInitialValue(name, elements, stores, options) { | ||
for (var i = 0; i < elements.length; i++) { | ||
var el = elements[i]; | ||
var handler = void 0; | ||
case 'file': | ||
{ | ||
initValues(extractFile(name, el, customValidators), stores); | ||
break; | ||
} | ||
if (el instanceof HTMLSelectElement) { | ||
handler = createSelectExtract(name, options); | ||
} else { | ||
switch (el.type) { | ||
case 'checkbox': | ||
{ | ||
handler = createCheckboxExtract(name, elements, options); | ||
break; | ||
} | ||
case 'radio': | ||
{ | ||
initValues(extractRadio(name, el), stores); | ||
break; | ||
} | ||
case 'file': | ||
{ | ||
handler = createFileExtract(name, options); | ||
break; | ||
} | ||
default: | ||
{ | ||
initValues(extractData(name, el, groupElements, customValidators), stores); | ||
} | ||
case 'radio': | ||
{ | ||
handler = createRadioExtract(name, options); | ||
break; | ||
} | ||
default: | ||
{ | ||
handler = createFieldExtract(name, elements, options); | ||
} | ||
} | ||
} | ||
initValues(handler(el), stores); | ||
} | ||
} | ||
/** | ||
* Get the initial value from the passed elements | ||
* @param name | ||
* @param elements | ||
* @param stores | ||
* @param validations | ||
*/ | ||
function getInitialValue(name, elements, stores, validations) { | ||
elements.forEach(function (el) { | ||
return createInitialValues(name, el, elements, stores, validations); | ||
}); | ||
} | ||
/** | ||
* Create the stores for the instance | ||
@@ -533,3 +563,3 @@ */ | ||
return function () { | ||
__spread(elMap).forEach(function (_a) { | ||
return __spread(elMap).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
@@ -598,2 +628,10 @@ el = _b[0], | ||
}); | ||
__spread(handlers).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
el = _b[0], | ||
handler = _b[1]; | ||
return el.removeEventListener('blur', handler); | ||
}); | ||
} | ||
@@ -610,3 +648,3 @@ })(); | ||
return function () { | ||
__spread(handlers).forEach(function (_a) { | ||
return __spread(handlers).forEach(function (_a) { | ||
var _b = __read(_a, 2), | ||
@@ -648,15 +686,12 @@ el = _b[0], | ||
groupedMap.forEach(function (_a) { | ||
var _b; | ||
var _b = __read(_a, 2), | ||
name = _b[0], | ||
elements = _b[1]; | ||
var _c = __read(_a, 2), | ||
name = _c[0], | ||
elements = _c[1]; | ||
var customValidations = (_b = options === null || options === void 0 ? void 0 : options.validators) === null || _b === void 0 ? void 0 : _b[name]; | ||
touchHandlers.add(createTouchHandlers(name, elements, stores)); | ||
getInitialValue(name, elements, stores, customValidations); | ||
getInitialValue(name, elements, stores, options); | ||
dirtyHandlers.add(createDirtyHandler(name, elements, stores)); | ||
elements.forEach(function (el) { | ||
if (el instanceof HTMLSelectElement) { | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, customValidations)); | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, options)); | ||
} else { | ||
@@ -673,3 +708,3 @@ switch (el.type) { | ||
{ | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, customValidations)); | ||
changeHandlers.add(createHandler(name, 'change', el, elements, stores, options)); | ||
break; | ||
@@ -679,3 +714,3 @@ } | ||
default: | ||
keyupHandlers.add(createHandler(name, 'keyup', el, elements, stores, customValidations)); | ||
keyupHandlers.add(createHandler(name, 'keyup', el, elements, stores, options)); | ||
} | ||
@@ -682,0 +717,0 @@ } |
@@ -11,7 +11,22 @@ /** | ||
*/ | ||
export interface ExtractedFormInfo { | ||
export interface FormulaField { | ||
/** | ||
* The name of the field being handled | ||
*/ | ||
name?: string; | ||
/** | ||
* The current value or values of the field | ||
*/ | ||
value: unknown | unknown[]; | ||
/** | ||
* If the field is valid | ||
*/ | ||
valid: boolean; | ||
/** | ||
* Any messages on the field from validation errors | ||
*/ | ||
message: string; | ||
/** | ||
* Any errors from validations from HTML or custom validation | ||
*/ | ||
errors: Record<string, boolean>; | ||
@@ -18,0 +33,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { CustomValidationRules, ValidationRules } from './validation'; | ||
import { CustomValidationMessages, ValidationRules, ValidationRule } from './validation'; | ||
/** | ||
@@ -7,13 +7,17 @@ * Optional settings for Formula | ||
/** | ||
* Override locale for sorting | ||
* Locale for i18n - currently not used | ||
*/ | ||
locale?: string; | ||
/** | ||
* Customised validity messages for each field error type | ||
*/ | ||
messages?: CustomValidationMessages; | ||
/** | ||
* Custom Validators for fields | ||
*/ | ||
validators?: CustomValidationRules; | ||
validators?: ValidationRules; | ||
/** | ||
* Validation rules for the entire form | ||
*/ | ||
formValidators?: ValidationRules; | ||
formValidators?: ValidationRule; | ||
} |
@@ -8,6 +8,10 @@ /** | ||
*/ | ||
export declare type ValidationRules = Record<string, ValidationFn>; | ||
export declare type ValidationRule = Record<string, ValidationFn>; | ||
/** | ||
* Custom validation rules for Formula | ||
*/ | ||
export declare type CustomValidationRules = Record<string, ValidationRules>; | ||
export declare type ValidationRules = Record<string, ValidationRule>; | ||
/** | ||
* Custom validation messages for field errors | ||
*/ | ||
export declare type CustomValidationMessages = Record<string, Record<string, string>>; |
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
95351
1601
233