svelte-formula
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -8,2 +8,66 @@ # Changelog | ||
## [0.2.0] 2021-02-15 | ||
### Changed | ||
- `formValid` now `isFormValid` | ||
### Added | ||
- Support for custom field-level validators via the `validators` property of the `formula` options. Validators are provided as an | ||
object - the key is the `name` of the fields, and the value is another object containing the validators. Each | ||
validator is has a key that is the name of the validation and a function that returns a string if the validation fails or | ||
`null` if it passes. The string message is used to display the message, the key is added to the `errors` object in `validity` | ||
```sveltehtml | ||
<script> | ||
import { formula } from 'svelte-formula' | ||
import { calculateStrong } from '../libs/password' | ||
const { form, formValues, submitValues, validity, touched, dirty, formValid } = formula({ | ||
validators: { | ||
password: { | ||
isStrong: (value: string) => calculateStrong(value) ? null : 'Your password is too weak' | ||
}, | ||
'invoice-ids': { | ||
invoicePrefix: (values: string[]) => values.every(value => value.startsWith('INV-')) ? null : 'Your invoice IDs must all begin with INV-' | ||
} | ||
} | ||
}); | ||
</script> | ||
... | ||
<input type='password' name='password' required minlength='8' class:error={$touched?.password && $validity?.username?.invalid}/> | ||
<div hidden={$validity?.password?.valid}>{$validity?.password?.message}</div> | ||
<div hidden={$validity?.password?.errors?.isStrong}>You have a strong password!</div> | ||
... | ||
<input type='text' id='invoice-1' name='invoice-ids' /> | ||
<input type='text' id='invoice-2' name='invoice-ids' /> | ||
<input type='text' id='invoice-3' name='invoice-ids' /> | ||
<div hidden={$validity?.['invoice-ids']?.valid}>{$validity?.['invoice-ids']?.message}</div> | ||
``` | ||
- Support for custom form-level validators via the `formValidators` property of the `formula` options. Validators are provided as an | ||
object - Each validator has a key that is the name of the validation, and a function that returns a string if the validation fails or | ||
`null` if it passes. The error message are available via the `formValidity` store. | ||
```sveltehtml | ||
<script> | ||
import { formula } from 'svelte-formula' | ||
const { form, submitValues, submitValidity, formValid } = formula({ | ||
formValidators: { | ||
passwordsMatch: (values) => values.password === values.passwordMatch ? null : 'Your passwords must match' | ||
} | ||
}) | ||
</script> | ||
<input type='password' name='password' required minlength='8'> | ||
<input type='password' name='passwordMatch' required minlength='8'> | ||
<div hidden="{!$submitValidity?.passwordsMatch}">{$submitValidity?.passwordsMatch}</div> | ||
``` | ||
### Fixed | ||
- Correctly pass options to form creation | ||
## [0.1.1] 2021-02-15 | ||
@@ -18,3 +82,4 @@ | ||
the same name REQUIRES and `id` property. Returns an array sorted on the `id` property alphabetically in the users' | ||
locale if detected (and always falls back to `en` if not), if this needs to be overridden it can be passed to the formula constructor (e.g `formula({ locale: 'de' });`) | ||
locale if detected (and always falls back to `en` if not), if this needs to be overridden it can be passed to the | ||
formula constructor (e.g `formula({ locale: 'de' });`) | ||
@@ -21,0 +86,0 @@ ## [0.1.0] 2021-02-15 |
@@ -31,2 +31,7 @@ import { Writable } from 'svelte/store'; | ||
/** | ||
* Store containing form-level validity if providing custom validators for the entire form | ||
* @typedef Writable<Record<string, string>> | ||
*/ | ||
formValidity: Writable<Record<string, string>>; | ||
/** | ||
* Store with the current touched state of elements | ||
@@ -50,3 +55,3 @@ * @typedef Writable<Record<string, boolean>> | ||
*/ | ||
formValid: Writable<boolean>; | ||
isFormValid: Writable<boolean>; | ||
}; |
@@ -1,2 +0,4 @@ | ||
import { FormEl } from '../types/forms'; | ||
import { Writable } from 'svelte/store'; | ||
import { FormEl, FormValues } from '../types/forms'; | ||
import { ValidationRules } from '../types/validation'; | ||
/** | ||
@@ -8,1 +10,20 @@ * Extract the errors from the element validity - as it's not enumerable, it cannot be | ||
export declare function extractErrors(el: FormEl): Record<string, boolean>; | ||
/** | ||
* Do form level validations | ||
* @param formValues | ||
* @param formValidity | ||
* @param isFormValid | ||
* @param customValidators | ||
*/ | ||
export declare function checkFormValidity(formValues: Writable<FormValues>, formValidity: Writable<Record<string, string>>, isFormValid: Writable<boolean>, customValidators: ValidationRules): () => void; | ||
/** | ||
* Check the validity of a field and against custom validators | ||
* @param el | ||
* @param value | ||
* @param customValidators | ||
*/ | ||
export declare function checkValidity(el: FormEl, value: unknown | unknown[], customValidators?: ValidationRules): { | ||
valid: boolean; | ||
message: string; | ||
errors: Record<string, boolean>; | ||
}; |
import { Writable } from 'svelte/store'; | ||
import { FormErrors } from '../types/forms'; | ||
import { ValidationRules } from '../types/validation'; | ||
/** | ||
@@ -9,4 +10,5 @@ * Create a generic value event handler | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
export declare function createValueHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>, updateMultiple?: any): (event: KeyboardEvent | MouseEvent) => void; | ||
export declare function createValueHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>, updateMultiple?: any, customValidators?: ValidationRules): (event: KeyboardEvent | MouseEvent) => void; | ||
/** | ||
@@ -18,4 +20,5 @@ * Create a handler for checkbox elements | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
export declare function createCheckHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>, updateMultiple?: any): (event: KeyboardEvent | MouseEvent) => void; | ||
export declare function createCheckHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>, updateMultiple?: any, customValidators?: ValidationRules): (event: KeyboardEvent | MouseEvent) => void; | ||
/** | ||
@@ -26,4 +29,5 @@ * Create a handler for radio elements | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
export declare function createRadioHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>): (event: KeyboardEvent | MouseEvent) => void; | ||
export declare function createRadioHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>, customValidators?: ValidationRules): (event: KeyboardEvent | MouseEvent) => void; | ||
/** | ||
@@ -34,10 +38,11 @@ * Create a handler for select elements | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
export declare function createSelectHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>): (event: KeyboardEvent | MouseEvent) => void; | ||
export declare function createSelectHandler(values: Writable<Record<string, unknown>>, errors: Writable<FormErrors>, isValid: Writable<boolean>, customValidators?: ValidationRules): (event: KeyboardEvent | MouseEvent) => void; | ||
/** | ||
* Create a handler for a form element submission, when called it copies the contents | ||
* of the current value store to the submit store and then unsubscribes | ||
* @param values | ||
* @param submit | ||
* @param formValues | ||
* @param submitValues | ||
*/ | ||
export declare function createSubmitHandler(values: Writable<Record<string, unknown>>, submit: Writable<Record<string, unknown>>): () => void; | ||
export declare function createSubmitHandler(formValues: Writable<Record<string, unknown>>, submitValues: Writable<Record<string, unknown>>): () => void; |
import { ExtractedFormInfo, FormEl } from '../types/forms'; | ||
import { ValidationRules } from '../types/validation'; | ||
/** | ||
@@ -7,10 +8,5 @@ * Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
export declare function extractData(el: FormEl, updateMultiple?: any): { | ||
name: string; | ||
value: any; | ||
valid: boolean; | ||
message: string; | ||
errors: Record<string, boolean>; | ||
}; | ||
export declare function extractData(el: FormEl, updateMultiple?: any, customValidators?: ValidationRules): ExtractedFormInfo; | ||
/** | ||
@@ -21,4 +17,5 @@ * Extract the data from an `<input type="checkbox"> element - this returns a boolean value | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
export declare function extractCheckbox(el: HTMLInputElement, updateMultiple?: any): ExtractedFormInfo; | ||
export declare function extractCheckbox(el: HTMLInputElement, updateMultiple?: any, customValidators?: ValidationRules): ExtractedFormInfo; | ||
/** | ||
@@ -29,4 +26,5 @@ * Extract the data from an `<input type="radio">` element, returning the value | ||
* @param el | ||
* @param customValidators | ||
*/ | ||
export declare function extractRadio(el: HTMLInputElement): ExtractedFormInfo; | ||
export declare function extractRadio(el: HTMLInputElement, customValidators?: ValidationRules): ExtractedFormInfo; | ||
/** | ||
@@ -36,3 +34,4 @@ * Extract the data from a `<select>` element - here we can support single values | ||
* @param el | ||
* @param customValidators | ||
*/ | ||
export declare function extractSelect(el: HTMLSelectElement): ExtractedFormInfo; | ||
export declare function extractSelect(el: HTMLSelectElement, customValidators?: ValidationRules): ExtractedFormInfo; |
import { FormErrors, FormValues } from '../types/forms'; | ||
import { Writable } from 'svelte/store'; | ||
import { FormulaOptions } from '../types/options'; | ||
export declare function createForm(values: Writable<FormValues>, submit: Writable<FormValues>, errors: Writable<FormErrors>, isValid: Writable<boolean>, touched: Writable<Record<string, boolean>>, dirty: Writable<Record<string, boolean>>, options?: FormulaOptions): (node: HTMLElement) => { | ||
export declare function createForm({ formValues, submitValues, formValidity, validity, isFormValid, touched, dirty, options, }: { | ||
formValues: Writable<FormValues>; | ||
submitValues: Writable<FormValues>; | ||
formValidity: Writable<Record<string, string>>; | ||
validity: Writable<FormErrors>; | ||
isFormValid: Writable<boolean>; | ||
touched: Writable<Record<string, boolean>>; | ||
dirty: Writable<Record<string, boolean>>; | ||
options?: FormulaOptions; | ||
}): (node: HTMLElement) => { | ||
destroy: () => void; | ||
}; |
{ | ||
"name": "svelte-formula", | ||
"description": "Reactive Forms for Svelte", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"keywords": [ | ||
@@ -6,0 +6,0 @@ "svelte", |
117
README.md
@@ -25,5 +25,7 @@ # Svelte Formula | ||
| Options | Type | Description | | ||
| ------- | -------- | ----------------------------------------------------------- | | ||
| `local` | `string` | Optional locale for `id` sorting when using multiple values | | ||
| 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 | | ||
@@ -39,3 +41,2 @@ ### Example | ||
<script> | ||
import { onDestroy } from 'svelte' | ||
import { formula } from 'svelte-formula' | ||
@@ -45,4 +46,4 @@ | ||
const valueSub = formValues.subscribe(values => console.log(values)); | ||
const submitSub = submitValues.subscribe(values => console.log(values)); | ||
$: console.log($formValues); | ||
$: console.log($submitValues); | ||
@@ -53,6 +54,2 @@ function handleSubmit(e) { | ||
onDestroy(() => { | ||
valueSub(); | ||
submitSub(); | ||
}) | ||
@@ -64,3 +61,3 @@ </script> | ||
<label for='username'>Username</label> | ||
<input type='text' id='username' name='username' required /> | ||
<input type='email' id='username' name='username' required /> | ||
<span hidden={$validity?.username?.valid}>{ validity?.username?.message }</span> | ||
@@ -80,2 +77,7 @@ </div> | ||
### dirty | ||
A store that updates when fields are marked as dirty - contains an `Object` with key/value of `name` and `Boolean`, | ||
fields where the user triggers a `blur` event, and the value has been changed from the original value. | ||
### formValues | ||
@@ -87,2 +89,12 @@ | ||
### formValidity | ||
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. | ||
### isFormValid | ||
A store that is a single `Boolean` value if the form is currently valid or not - this only emits when the form validity | ||
changes | ||
### submitValues | ||
@@ -93,2 +105,7 @@ | ||
### touched | ||
A store that updates when fields are marked as touched - contains an `Object` with key/value of `name` and `Boolean`, | ||
fields are added to the store as they receive a `focus` event | ||
### validity | ||
@@ -104,19 +121,72 @@ | ||
the [Constraint Validation](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation#the_constraint_validation_api) | ||
API. | ||
API, or from custom `validators` in the options. | ||
### formValid | ||
## Custom Field Validations | ||
A store that is a single `Boolean` value if the form is currently valid or not - this only emits when the form validity | ||
changes | ||
While Formula is zero-config and uses validations set on the HTML5 elements, it's also possible to provide custom | ||
validations via the `validations` option passed to `formula` on initialisation. | ||
### touched | ||
First provide the `name` of the field as a key, and the value as an `Object` - each object contains further keys per | ||
error, and a function that does the validation - the result should be a `string` if it fails, or `null` if it passes. | ||
A store that updates when fields are marked as touched - contains an `Object` with key/value of `name` and `Boolean`, | ||
fields are added to the store as they receive a `focus` event | ||
Custom validation will always be added to the `errors` object, however the `message` - HTML validations will always take | ||
precedence over custom validations (e.g. `<input required>` message will always be before your custom message) | ||
### dirty | ||
Custom validators also support multi-value fields, but in this case an `id` must be set on each field to provide | ||
uniqueness (such as an index value) | ||
A store that updates when fields are marked as dirty - contains an `Object` with key/value of `name` and `Boolean`, | ||
fields where the user triggers a `blur` event, and the value has been changed from the original value. | ||
### Example | ||
```sveltehtml | ||
<script> | ||
import { formula } from 'svelte-formula'; | ||
const { form, validity } = formula({ | ||
validators: { | ||
username: { | ||
inCompanyDomain: (value: string) => value.includes('@ourdomain.com') ? null : 'Your username must contain an @ourdomain.com email' | ||
}, | ||
invoiceIds: { | ||
validInvoiceId: (values: string[]) => values.every(value => value.startsWith('INV-')) ? null : 'Your invoice IDs must start with INV-' | ||
} | ||
} | ||
}); | ||
</script> | ||
<form use:form> | ||
<label for='signup-username'>Username</label> | ||
<input type='email' name='username' id='signup-username' required minlength='8' /> | ||
<div hidden={$validity?.email?.valid}>{$validity?.email?.message}</div> | ||
<input type='text' name='invoiceIds' id='1' /> | ||
<input type='text' name='invoiceIds' id='2' /> | ||
<input type='text' name='invoiceIds' id='3' /> | ||
</form> | ||
``` | ||
## Form Level Validation | ||
Form level validations can also be provided using `formValidators` - these validators are passed the entire set of | ||
values and allow for validation across different values (like password matching, or ensuring a selection is made in one | ||
field based on another form field condition) | ||
```sveltehtml | ||
<script> | ||
import { formula } from 'svelte-formula'; | ||
const { form, formValidity } = formula({ | ||
formValidators: { | ||
passwordsMatch: (values) => values.password === values.passwordMatch ? null : 'Your passwords must match' | ||
} | ||
}) | ||
</script> | ||
<form use:form> | ||
<input type='password' name='password' required minlength='8'> | ||
<input type='password' name='passwordMatch' required minlength='8'> | ||
<div hidden={!$formValidity?.passwordsMatch}>{$formValidity?.passwordsMatch}</div> | ||
</form> | ||
``` | ||
## Roadmap | ||
@@ -139,2 +209,7 @@ | ||
### Validation | ||
- [x] Custom field-level validation via `formula` options | ||
- [x] Custom form-level validation via `formula` options | ||
### Other Items | ||
@@ -141,0 +216,0 @@ |
@@ -94,2 +94,3 @@ import { writable } from 'svelte/store'; | ||
*/ | ||
function extractErrors(el) { | ||
@@ -109,14 +110,46 @@ var output = {}; | ||
} | ||
/** | ||
* Do form level validations | ||
* @param formValues | ||
* @param formValidity | ||
* @param isFormValid | ||
* @param customValidators | ||
*/ | ||
function checkFormValidity(formValues, formValidity, isFormValid, customValidators) { | ||
return formValues.subscribe(function (values) { | ||
formValidity.set({}); | ||
var validators = Object.entries(customValidators); | ||
var _loop_1 = function _loop_1(i) { | ||
var _a = __read(validators[i], 2), | ||
name_1 = _a[0], | ||
validator = _a[1]; | ||
var invalid = validator(values); | ||
if (invalid) { | ||
formValidity.update(function (state) { | ||
var _a; | ||
return __assign(__assign({}, state), (_a = {}, _a[name_1] = invalid, _a)); | ||
}); | ||
isFormValid.set(false); | ||
} | ||
}; | ||
for (var i = 0; i < validators.length; i++) { | ||
_loop_1(i); | ||
} | ||
}); | ||
} | ||
/** | ||
* Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* doesn't have a special case | ||
* Check the validity of a field and against custom validators | ||
* @param el | ||
* @param updateMultiple | ||
* @param value | ||
* @param customValidators | ||
*/ | ||
function extractData(el, updateMultiple) { | ||
return { | ||
name: el.getAttribute('name'), | ||
value: updateMultiple ? updateMultiple(el.id, el.value) : el.value, | ||
function checkValidity(el, value, customValidators) { | ||
var result = { | ||
valid: el.checkValidity(), | ||
@@ -126,4 +159,48 @@ message: el.validationMessage, | ||
}; | ||
if (customValidators) { | ||
var validators = Object.entries(customValidators); | ||
for (var i = 0; i < validators.length; i++) { | ||
var _a = __read(validators[i], 2), | ||
name_2 = _a[0], | ||
validator = _a[1]; | ||
var message = validator(value); | ||
if (message === null) { | ||
continue; | ||
} | ||
if (result.valid === true) { | ||
result.valid = false; | ||
result.message = message; | ||
result.errors[name_2] = true; | ||
} else { | ||
result.errors[name_2] = true; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* doesn't have a special case | ||
* @param el | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
function extractData(el, updateMultiple, customValidators) { | ||
var name = el.getAttribute('name'); | ||
var value = updateMultiple ? updateMultiple(el.id, el.value) : el.value; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
/** | ||
* Extract the data from an `<input type="checkbox"> element - this returns a boolean value | ||
@@ -133,12 +210,13 @@ * if a single checkbox. If multiple checkboxes are detected it returns an array value | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
function extractCheckbox(el, updateMultiple) { | ||
return { | ||
name: el.getAttribute('name'), | ||
value: updateMultiple ? updateMultiple(el.checked, el.value) : el.checked, | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
function extractCheckbox(el, updateMultiple, customValidators) { | ||
var name = el.getAttribute('name'); | ||
var value = updateMultiple ? updateMultiple(el.checked, el.value) : el.checked; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
@@ -150,12 +228,13 @@ /** | ||
* @param el | ||
* @param customValidators | ||
*/ | ||
function extractRadio(el) { | ||
return { | ||
name: el.getAttribute('name'), | ||
value: el.checked ? el.value : '', | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
function extractRadio(el, customValidators) { | ||
var name = el.getAttribute('name'); | ||
var value = el.checked ? el.value : ''; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
@@ -166,5 +245,6 @@ /** | ||
* @param el | ||
* @param customValidators | ||
*/ | ||
function extractSelect(el) { | ||
function extractSelect(el, customValidators) { | ||
/** | ||
@@ -187,9 +267,9 @@ * As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
return { | ||
name: el.getAttribute('name'), | ||
value: el.multiple ? getMultiValue(el.selectedOptions) : el.value, | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
var name = el.getAttribute('name'); | ||
var value = el.multiple ? getMultiValue(el.selectedOptions) : el.value; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
@@ -233,9 +313,10 @@ | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
function createValueHandler(values, errors, isValid, updateMultiple) { | ||
function createValueHandler(values, errors, isValid, updateMultiple, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractData(el, updateMultiple); | ||
var details = extractData(el, updateMultiple, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -250,8 +331,9 @@ }; | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
function createCheckHandler(values, errors, isValid, updateMultiple) { | ||
function createCheckHandler(values, errors, isValid, updateMultiple, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractCheckbox(el, updateMultiple); | ||
var details = extractCheckbox(el, updateMultiple, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -265,8 +347,9 @@ }; | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
function createRadioHandler(values, errors, isValid) { | ||
function createRadioHandler(values, errors, isValid, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractRadio(el); | ||
var details = extractRadio(el, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -280,8 +363,9 @@ }; | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
function createSelectHandler(values, errors, isValid) { | ||
function createSelectHandler(values, errors, isValid, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractSelect(el); | ||
var details = extractSelect(el, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -293,10 +377,10 @@ }; | ||
* of the current value store to the submit store and then unsubscribes | ||
* @param values | ||
* @param submit | ||
* @param formValues | ||
* @param submitValues | ||
*/ | ||
function createSubmitHandler(values, submit) { | ||
function createSubmitHandler(formValues, submitValues) { | ||
return function () { | ||
return values.subscribe(function (v) { | ||
return submit.set(v); | ||
return formValues.subscribe(function (v) { | ||
return submitValues.set(v); | ||
})(); | ||
@@ -526,44 +610,54 @@ }; | ||
function createForm(values, submit, errors, isValid, touched, dirty, options) { | ||
function createForm(_a) { | ||
var formValues = _a.formValues, | ||
submitValues = _a.submitValues, | ||
formValidity = _a.formValidity, | ||
validity = _a.validity, | ||
isFormValid = _a.isFormValid, | ||
touched = _a.touched, | ||
dirty = _a.dirty, | ||
options = _a.options; | ||
return function form(node) { | ||
var keyupHandlers = new Map(); | ||
var changeHandlers = new Map(); | ||
var submitHander = undefined; | ||
var submitHandler = undefined; | ||
var formElements = getAllFieldsWithValidity(node); | ||
formElements.forEach(function (el) { | ||
// Create a single touch handler for each element, this is removed after it has first been focused | ||
var _a; // Create a single touch handler for each element, this is removed after it has first been focused | ||
createTouchHandler(el, touched); | ||
createInitialValues(el, formElements, values, errors, touched); | ||
createDirtyHandler(el, dirty, values); | ||
createInitialValues(el, formElements, formValues, validity, touched); | ||
createDirtyHandler(el, dirty, formValues); | ||
var name = el.getAttribute('name'); | ||
var customValidations = (_a = options === null || options === void 0 ? void 0 : options.validators) === null || _a === void 0 ? void 0 : _a[name]; | ||
if (el instanceof HTMLSelectElement) { | ||
var handler = createSelectHandler(values, errors, isValid); | ||
var handler = createSelectHandler(formValues, validity, isFormValid, customValidations); | ||
el.addEventListener('change', handler); | ||
changeHandlers.set(el, handler); | ||
} else if (el.type === 'radio') { | ||
var handler = createRadioHandler(values, errors, isValid); | ||
var handler = createRadioHandler(formValues, validity, isFormValid, customValidations); | ||
el.addEventListener('change', handler); | ||
changeHandlers.set(el, handler); | ||
} else if (el.type === 'checkbox') { | ||
var name_1 = el.getAttribute('name'); | ||
var isMultiple = isMultiCheckbox(name_1, formElements); | ||
var isMultiple = isMultiCheckbox(name, formElements); | ||
var updateMultiple = void 0; | ||
if (isMultiple) { | ||
updateMultiple = checkboxMultiUpdate(name_1); | ||
updateMultiple = checkboxMultiUpdate(name); | ||
} | ||
var handler = createCheckHandler(values, errors, isValid, updateMultiple); | ||
var handler = createCheckHandler(formValues, validity, isFormValid, updateMultiple, customValidations); | ||
el.addEventListener('change', handler); | ||
changeHandlers.set(el, handler); | ||
} else { | ||
var name_2 = el.getAttribute('name'); | ||
var isMultiple = hasMultipleNames(name_2, formElements); | ||
var isMultiple = hasMultipleNames(name, formElements); | ||
var updateMultiple = void 0; | ||
if (isMultiple) { | ||
updateMultiple = inputMultiUpdate(name_2, options === null || options === void 0 ? void 0 : options.locale); | ||
updateMultiple = inputMultiUpdate(name, options === null || options === void 0 ? void 0 : options.locale); | ||
} | ||
var handler = createValueHandler(values, errors, isValid, updateMultiple); | ||
var handler = createValueHandler(formValues, validity, isFormValid, updateMultiple, customValidations); | ||
el.addEventListener('keyup', handler); | ||
@@ -574,6 +668,11 @@ keyupHandlers.set(el, handler); | ||
var unsub = function unsub() {}; | ||
if (options === null || options === void 0 ? void 0 : options.formValidators) { | ||
unsub = checkFormValidity(formValues, formValidity, isFormValid, options.formValidators); | ||
} | ||
if (node instanceof HTMLFormElement) { | ||
var submitHandler = createSubmitHandler(values, submit); | ||
submitHandler = createSubmitHandler(formValues, submitValues); | ||
node.addEventListener('submit', submitHandler); | ||
submitHander = submitHandler; | ||
} | ||
@@ -583,2 +682,4 @@ | ||
destroy: function destroy() { | ||
unsub(); | ||
__spread(keyupHandlers).forEach(function (_a) { | ||
@@ -600,4 +701,4 @@ var _b = __read(_a, 2), | ||
if (submitHander) { | ||
node.removeEventListener('submit', submitHander); | ||
if (submitHandler) { | ||
node.removeEventListener('submit', submitHandler); | ||
} | ||
@@ -623,3 +724,4 @@ } | ||
var validity = writable({}); | ||
var formValid = writable(false); | ||
var formValidity = writable({}); | ||
var isFormValid = writable(false); | ||
return { | ||
@@ -630,3 +732,12 @@ /** | ||
*/ | ||
form: createForm(formValues, submitValues, validity, formValid, touched, dirty), | ||
form: createForm({ | ||
formValues: formValues, | ||
submitValues: submitValues, | ||
formValidity: formValidity, | ||
validity: validity, | ||
isFormValid: isFormValid, | ||
touched: touched, | ||
dirty: dirty, | ||
options: options | ||
}), | ||
@@ -646,2 +757,8 @@ /** | ||
/** | ||
* Store containing form-level validity if providing custom validators for the entire form | ||
* @typedef Writable<Record<string, string>> | ||
*/ | ||
formValidity: formValidity, | ||
/** | ||
* Store with the current touched state of elements | ||
@@ -668,3 +785,3 @@ * @typedef Writable<Record<string, boolean>> | ||
*/ | ||
formValid: formValid | ||
isFormValid: isFormValid | ||
}; | ||
@@ -671,0 +788,0 @@ } |
@@ -98,2 +98,3 @@ (function (global, factory) { | ||
*/ | ||
function extractErrors(el) { | ||
@@ -113,14 +114,46 @@ var output = {}; | ||
} | ||
/** | ||
* Do form level validations | ||
* @param formValues | ||
* @param formValidity | ||
* @param isFormValid | ||
* @param customValidators | ||
*/ | ||
function checkFormValidity(formValues, formValidity, isFormValid, customValidators) { | ||
return formValues.subscribe(function (values) { | ||
formValidity.set({}); | ||
var validators = Object.entries(customValidators); | ||
var _loop_1 = function _loop_1(i) { | ||
var _a = __read(validators[i], 2), | ||
name_1 = _a[0], | ||
validator = _a[1]; | ||
var invalid = validator(values); | ||
if (invalid) { | ||
formValidity.update(function (state) { | ||
var _a; | ||
return __assign(__assign({}, state), (_a = {}, _a[name_1] = invalid, _a)); | ||
}); | ||
isFormValid.set(false); | ||
} | ||
}; | ||
for (var i = 0; i < validators.length; i++) { | ||
_loop_1(i); | ||
} | ||
}); | ||
} | ||
/** | ||
* Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* doesn't have a special case | ||
* Check the validity of a field and against custom validators | ||
* @param el | ||
* @param updateMultiple | ||
* @param value | ||
* @param customValidators | ||
*/ | ||
function extractData(el, updateMultiple) { | ||
return { | ||
name: el.getAttribute('name'), | ||
value: updateMultiple ? updateMultiple(el.id, el.value) : el.value, | ||
function checkValidity(el, value, customValidators) { | ||
var result = { | ||
valid: el.checkValidity(), | ||
@@ -130,4 +163,48 @@ message: el.validationMessage, | ||
}; | ||
if (customValidators) { | ||
var validators = Object.entries(customValidators); | ||
for (var i = 0; i < validators.length; i++) { | ||
var _a = __read(validators[i], 2), | ||
name_2 = _a[0], | ||
validator = _a[1]; | ||
var message = validator(value); | ||
if (message === null) { | ||
continue; | ||
} | ||
if (result.valid === true) { | ||
result.valid = false; | ||
result.message = message; | ||
result.errors[name_2] = true; | ||
} else { | ||
result.errors[name_2] = true; | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* Generic handle for extracting data from an `<input>` or `<textarea>` element that | ||
* doesn't have a special case | ||
* @param el | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
function extractData(el, updateMultiple, customValidators) { | ||
var name = el.getAttribute('name'); | ||
var value = updateMultiple ? updateMultiple(el.id, el.value) : el.value; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
/** | ||
* Extract the data from an `<input type="checkbox"> element - this returns a boolean value | ||
@@ -137,12 +214,13 @@ * if a single checkbox. If multiple checkboxes are detected it returns an array value | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
function extractCheckbox(el, updateMultiple) { | ||
return { | ||
name: el.getAttribute('name'), | ||
value: updateMultiple ? updateMultiple(el.checked, el.value) : el.checked, | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
function extractCheckbox(el, updateMultiple, customValidators) { | ||
var name = el.getAttribute('name'); | ||
var value = updateMultiple ? updateMultiple(el.checked, el.value) : el.checked; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
@@ -154,12 +232,13 @@ /** | ||
* @param el | ||
* @param customValidators | ||
*/ | ||
function extractRadio(el) { | ||
return { | ||
name: el.getAttribute('name'), | ||
value: el.checked ? el.value : '', | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
function extractRadio(el, customValidators) { | ||
var name = el.getAttribute('name'); | ||
var value = el.checked ? el.value : ''; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
@@ -170,5 +249,6 @@ /** | ||
* @param el | ||
* @param customValidators | ||
*/ | ||
function extractSelect(el) { | ||
function extractSelect(el, customValidators) { | ||
/** | ||
@@ -191,9 +271,9 @@ * As the `HTMLCollectionOf` is not iterable, we have to loop over it with | ||
return { | ||
name: el.getAttribute('name'), | ||
value: el.multiple ? getMultiValue(el.selectedOptions) : el.value, | ||
valid: el.checkValidity(), | ||
message: el.validationMessage, | ||
errors: extractErrors(el) | ||
}; | ||
var name = el.getAttribute('name'); | ||
var value = el.multiple ? getMultiValue(el.selectedOptions) : el.value; | ||
var validity = checkValidity(el, value, customValidators); | ||
return __assign({ | ||
name: name, | ||
value: value | ||
}, validity); | ||
} | ||
@@ -237,9 +317,10 @@ | ||
* @param updateMultiple | ||
* @param customValidators | ||
*/ | ||
function createValueHandler(values, errors, isValid, updateMultiple) { | ||
function createValueHandler(values, errors, isValid, updateMultiple, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractData(el, updateMultiple); | ||
var details = extractData(el, updateMultiple, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -254,8 +335,9 @@ }; | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
function createCheckHandler(values, errors, isValid, updateMultiple) { | ||
function createCheckHandler(values, errors, isValid, updateMultiple, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractCheckbox(el, updateMultiple); | ||
var details = extractCheckbox(el, updateMultiple, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -269,8 +351,9 @@ }; | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
function createRadioHandler(values, errors, isValid) { | ||
function createRadioHandler(values, errors, isValid, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractRadio(el); | ||
var details = extractRadio(el, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -284,8 +367,9 @@ }; | ||
* @param isValid | ||
* @param customValidators | ||
*/ | ||
function createSelectHandler(values, errors, isValid) { | ||
function createSelectHandler(values, errors, isValid, customValidators) { | ||
return function (event) { | ||
var el = event.currentTarget || event.target; | ||
var details = extractSelect(el); | ||
var details = extractSelect(el, customValidators); | ||
valueUpdate(details, values, errors, isValid); | ||
@@ -297,10 +381,10 @@ }; | ||
* of the current value store to the submit store and then unsubscribes | ||
* @param values | ||
* @param submit | ||
* @param formValues | ||
* @param submitValues | ||
*/ | ||
function createSubmitHandler(values, submit) { | ||
function createSubmitHandler(formValues, submitValues) { | ||
return function () { | ||
return values.subscribe(function (v) { | ||
return submit.set(v); | ||
return formValues.subscribe(function (v) { | ||
return submitValues.set(v); | ||
})(); | ||
@@ -530,44 +614,54 @@ }; | ||
function createForm(values, submit, errors, isValid, touched, dirty, options) { | ||
function createForm(_a) { | ||
var formValues = _a.formValues, | ||
submitValues = _a.submitValues, | ||
formValidity = _a.formValidity, | ||
validity = _a.validity, | ||
isFormValid = _a.isFormValid, | ||
touched = _a.touched, | ||
dirty = _a.dirty, | ||
options = _a.options; | ||
return function form(node) { | ||
var keyupHandlers = new Map(); | ||
var changeHandlers = new Map(); | ||
var submitHander = undefined; | ||
var submitHandler = undefined; | ||
var formElements = getAllFieldsWithValidity(node); | ||
formElements.forEach(function (el) { | ||
// Create a single touch handler for each element, this is removed after it has first been focused | ||
var _a; // Create a single touch handler for each element, this is removed after it has first been focused | ||
createTouchHandler(el, touched); | ||
createInitialValues(el, formElements, values, errors, touched); | ||
createDirtyHandler(el, dirty, values); | ||
createInitialValues(el, formElements, formValues, validity, touched); | ||
createDirtyHandler(el, dirty, formValues); | ||
var name = el.getAttribute('name'); | ||
var customValidations = (_a = options === null || options === void 0 ? void 0 : options.validators) === null || _a === void 0 ? void 0 : _a[name]; | ||
if (el instanceof HTMLSelectElement) { | ||
var handler = createSelectHandler(values, errors, isValid); | ||
var handler = createSelectHandler(formValues, validity, isFormValid, customValidations); | ||
el.addEventListener('change', handler); | ||
changeHandlers.set(el, handler); | ||
} else if (el.type === 'radio') { | ||
var handler = createRadioHandler(values, errors, isValid); | ||
var handler = createRadioHandler(formValues, validity, isFormValid, customValidations); | ||
el.addEventListener('change', handler); | ||
changeHandlers.set(el, handler); | ||
} else if (el.type === 'checkbox') { | ||
var name_1 = el.getAttribute('name'); | ||
var isMultiple = isMultiCheckbox(name_1, formElements); | ||
var isMultiple = isMultiCheckbox(name, formElements); | ||
var updateMultiple = void 0; | ||
if (isMultiple) { | ||
updateMultiple = checkboxMultiUpdate(name_1); | ||
updateMultiple = checkboxMultiUpdate(name); | ||
} | ||
var handler = createCheckHandler(values, errors, isValid, updateMultiple); | ||
var handler = createCheckHandler(formValues, validity, isFormValid, updateMultiple, customValidations); | ||
el.addEventListener('change', handler); | ||
changeHandlers.set(el, handler); | ||
} else { | ||
var name_2 = el.getAttribute('name'); | ||
var isMultiple = hasMultipleNames(name_2, formElements); | ||
var isMultiple = hasMultipleNames(name, formElements); | ||
var updateMultiple = void 0; | ||
if (isMultiple) { | ||
updateMultiple = inputMultiUpdate(name_2, options === null || options === void 0 ? void 0 : options.locale); | ||
updateMultiple = inputMultiUpdate(name, options === null || options === void 0 ? void 0 : options.locale); | ||
} | ||
var handler = createValueHandler(values, errors, isValid, updateMultiple); | ||
var handler = createValueHandler(formValues, validity, isFormValid, updateMultiple, customValidations); | ||
el.addEventListener('keyup', handler); | ||
@@ -578,6 +672,11 @@ keyupHandlers.set(el, handler); | ||
var unsub = function unsub() {}; | ||
if (options === null || options === void 0 ? void 0 : options.formValidators) { | ||
unsub = checkFormValidity(formValues, formValidity, isFormValid, options.formValidators); | ||
} | ||
if (node instanceof HTMLFormElement) { | ||
var submitHandler = createSubmitHandler(values, submit); | ||
submitHandler = createSubmitHandler(formValues, submitValues); | ||
node.addEventListener('submit', submitHandler); | ||
submitHander = submitHandler; | ||
} | ||
@@ -587,2 +686,4 @@ | ||
destroy: function destroy() { | ||
unsub(); | ||
__spread(keyupHandlers).forEach(function (_a) { | ||
@@ -604,4 +705,4 @@ var _b = __read(_a, 2), | ||
if (submitHander) { | ||
node.removeEventListener('submit', submitHander); | ||
if (submitHandler) { | ||
node.removeEventListener('submit', submitHandler); | ||
} | ||
@@ -627,3 +728,4 @@ } | ||
var validity = store.writable({}); | ||
var formValid = store.writable(false); | ||
var formValidity = store.writable({}); | ||
var isFormValid = store.writable(false); | ||
return { | ||
@@ -634,3 +736,12 @@ /** | ||
*/ | ||
form: createForm(formValues, submitValues, validity, formValid, touched, dirty), | ||
form: createForm({ | ||
formValues: formValues, | ||
submitValues: submitValues, | ||
formValidity: formValidity, | ||
validity: validity, | ||
isFormValid: isFormValid, | ||
touched: touched, | ||
dirty: dirty, | ||
options: options | ||
}), | ||
@@ -650,2 +761,8 @@ /** | ||
/** | ||
* Store containing form-level validity if providing custom validators for the entire form | ||
* @typedef Writable<Record<string, string>> | ||
*/ | ||
formValidity: formValidity, | ||
/** | ||
* Store with the current touched state of elements | ||
@@ -672,3 +789,3 @@ * @typedef Writable<Record<string, boolean>> | ||
*/ | ||
formValid: formValid | ||
isFormValid: isFormValid | ||
}; | ||
@@ -675,0 +792,0 @@ } |
@@ -0,1 +1,2 @@ | ||
import { CustomValidationRules, ValidationRules } from './validation'; | ||
/** | ||
@@ -9,2 +10,10 @@ * Optional settings for Formula | ||
locale?: string; | ||
/** | ||
* Custom Validators for fields | ||
*/ | ||
validators?: CustomValidationRules; | ||
/** | ||
* Validation rules for the entire form | ||
*/ | ||
formValidators?: ValidationRules; | ||
} |
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
97315
19
1654
211