@lion/form-core
Advanced tools
Comparing version 0.16.0 to 0.17.0
# Systems >> Form >> Overview ||10 | ||
The Form System allows you to create complex forms with various validations in an easy way. | ||
This page should be used as a starting point when first using the Form System. | ||
It provides an overview of its essential building blocks and provides links to detailed explanations of most of its core concepts. | ||
## Features | ||
## Building Blocks | ||
- Built in [validate](https://github.com/ing-bank/lion/blob/7c52c12bfb025c98f93b5f0be984cedd3028a20a/docs/docs/systems/form/validate.md) for error/warning/info/success | ||
- Formatting of values | ||
- Accessible | ||
Our Form System is built from a set of very fundamental building blocks: `form control`s, `field`s and `fieldset`s. | ||
### Form Controls | ||
`Form control`s are the most fundamental building blocks of our Form System. | ||
They are the fundament of both `field`s, and `fieldset`s and provide a normalized, predictable API throughout the whole form. Every form element inherits from `FormControlMixin`. | ||
`FormControlMixin` creates the default html structure and accessibility is designed to be used in conjunction with the ValidateMixin and the FormatMixin. | ||
## Fields | ||
Fields (think of an input, textarea, select) are the actual form controls the end user interacts with. They extend `LionField`, which in turn uses the `FormControlMixin`. Fields provide a normalized API for both platform components and custom made form controls. | ||
On top of this, they feature: | ||
- [formatting/parsing/serializing](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/formatting-and-parsing.md) of view values. | ||
- Advanced [validation](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/validate.md) possibilities. | ||
- Creation of advanced user interaction scenarios via [interaction states](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/interaction-states.md). | ||
- Provision of labels and help texts in an easy, declarative manner. | ||
- Accessibility out of the box. | ||
- Advanced styling possibilities: map your own Design System to the internal HTML structure. | ||
`Form control`s are the most fundamental building block of the Forms. They are the basis of | ||
both `field`s and `fieldset`s, and the `form` itself. | ||
### Platform fields (wrappers) | ||
- [LionInput](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input/overview.md), a wrapper for `<input>`. | ||
- [LionTextarea](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/textarea/overview.md), a wrapper for `<textarea>`. | ||
- [LionSelect](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/select/overview.md), a wrapper for `<select>`. | ||
### Custom fields (wrappers) | ||
Whenever a native form control doesn't exist or is not sufficient, a [custom form field](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/guides/how-to/create-a-custom-field.md) should be created. One could think of components like: | ||
- [LionCombobox](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/combobox/overview.md), a custom implementation of a combobox. | ||
- [LionDate](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-date/overview.md), an alternative for `<input type="date">`. | ||
- [LionDatepicker](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-datepicker/overview.md), an alternative for `<input type="date">` including a calendar dropdown. | ||
- [LionListbox](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/listbox/overview.md), a custom implementation of a listbox. | ||
- [LionInputAmount](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-amount/overview.md), an alternative for `<input type="number">` special for amounts. | ||
- [LionInputEmail](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-email/overview.md), an alternative for `<input type="email">`. | ||
- [LionInputIban](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-iban/overview.md), an ING specific for an input with IBAN numbers. | ||
- [LionInputRange](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-range/overview.md), an alternative for `<input type="range">`. | ||
- [LionInputStepper](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-stepper/overview.md), an alternative for `<input type="number">`. | ||
- [LionSelectRich](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/select-rich/overview.md), an alternative for `<select>` with multiline options. | ||
### Choice Input Fields | ||
For form controls which return a `checked-state` you can use the `lion-choice-input` mixin. It is used in: | ||
- [LionCheckbox](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/checkbox-group/overview.md), a wrapper for `<input type="checkbox">`. | ||
- LionOption, an alternative for `<option>`. | ||
- [LionRadio](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/radio-group/overview.md), a wrapper for `<input type="radio">`. | ||
- [LionSwitch](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/switch/overview.md), a custom implementation of a switch. | ||
Which contains the following features: | ||
- Get or set the value of the choice - `choiceValue()`. | ||
- Get or set the modelValue (value and checked-state) of the choice - `.modelValue`. | ||
- Pre-select an option by setting the `checked` boolean attribute. | ||
## Fieldsets | ||
Fieldsets are groups of fields. They can be considered fields on their own as well, since they partly share the normalized API via `FormControlMixin`. Fieldsets are the basis for: | ||
- [LionFieldset](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/fieldset/overview.md), a wrapper around multiple input fields or other fieldsets. | ||
- [LionForm](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/form/overview.md), enhances the functionality of the native `<form>` component. | ||
- [LionCheckboxGroup](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/checkbox-group/overview.md), a wrapper component for multiple checkboxes. | ||
- [LionRadioGroup](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/radio-group/overview.md), a wrapper component for multiple radio inputs. | ||
## Other Resources | ||
- [Model Value](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/model-value.md) | ||
- [Formatting and parsing](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/formatting-and-parsing.md) | ||
- [Interaction states](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/interaction-states.md) | ||
- [Validation System](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/validate.md) |
{ | ||
"name": "@lion/form-core", | ||
"version": "0.16.0", | ||
"version": "0.17.0", | ||
"description": "Form-core contains all essential building blocks for creating form fields and fieldsets", | ||
@@ -41,4 +41,4 @@ "license": "MIT", | ||
"dependencies": { | ||
"@lion/core": "^0.21.0", | ||
"@lion/localize": "^0.23.0" | ||
"@lion/core": "^0.22.0", | ||
"@lion/localize": "^0.24.0" | ||
}, | ||
@@ -45,0 +45,0 @@ "keywords": [ |
# Systems >> Form >> Overview ||10 | ||
The Form System allows you to create complex forms with various validations in an easy way. | ||
This page should be used as a starting point when first using the Form System. | ||
It provides an overview of its essential building blocks and provides links to detailed explanations of most of its core concepts. | ||
## Features | ||
## Building Blocks | ||
- Built in [validate](https://github.com/ing-bank/lion/blob/7c52c12bfb025c98f93b5f0be984cedd3028a20a/docs/docs/systems/form/validate.md) for error/warning/info/success | ||
- Formatting of values | ||
- Accessible | ||
Our Form System is built from a set of very fundamental building blocks: `form control`s, `field`s and `fieldset`s. | ||
### Form Controls | ||
`Form control`s are the most fundamental building blocks of our Form System. | ||
They are the fundament of both `field`s, and `fieldset`s and provide a normalized, predictable API throughout the whole form. Every form element inherits from `FormControlMixin`. | ||
`FormControlMixin` creates the default html structure and accessibility is designed to be used in conjunction with the ValidateMixin and the FormatMixin. | ||
## Fields | ||
Fields (think of an input, textarea, select) are the actual form controls the end user interacts with. They extend `LionField`, which in turn uses the `FormControlMixin`. Fields provide a normalized API for both platform components and custom made form controls. | ||
On top of this, they feature: | ||
- [formatting/parsing/serializing](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/formatting-and-parsing.md) of view values. | ||
- Advanced [validation](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/validate.md) possibilities. | ||
- Creation of advanced user interaction scenarios via [interaction states](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/interaction-states.md). | ||
- Provision of labels and help texts in an easy, declarative manner. | ||
- Accessibility out of the box. | ||
- Advanced styling possibilities: map your own Design System to the internal HTML structure. | ||
`Form control`s are the most fundamental building block of the Forms. They are the basis of | ||
both `field`s and `fieldset`s, and the `form` itself. | ||
### Platform fields (wrappers) | ||
- [LionInput](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input/overview.md), a wrapper for `<input>`. | ||
- [LionTextarea](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/textarea/overview.md), a wrapper for `<textarea>`. | ||
- [LionSelect](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/select/overview.md), a wrapper for `<select>`. | ||
### Custom fields (wrappers) | ||
Whenever a native form control doesn't exist or is not sufficient, a [custom form field](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/guides/how-to/create-a-custom-field.md) should be created. One could think of components like: | ||
- [LionCombobox](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/combobox/overview.md), a custom implementation of a combobox. | ||
- [LionDate](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-date/overview.md), an alternative for `<input type="date">`. | ||
- [LionDatepicker](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-datepicker/overview.md), an alternative for `<input type="date">` including a calendar dropdown. | ||
- [LionListbox](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/listbox/overview.md), a custom implementation of a listbox. | ||
- [LionInputAmount](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-amount/overview.md), an alternative for `<input type="number">` special for amounts. | ||
- [LionInputEmail](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-email/overview.md), an alternative for `<input type="email">`. | ||
- [LionInputIban](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-iban/overview.md), an ING specific for an input with IBAN numbers. | ||
- [LionInputRange](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-range/overview.md), an alternative for `<input type="range">`. | ||
- [LionInputStepper](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/input-stepper/overview.md), an alternative for `<input type="number">`. | ||
- [LionSelectRich](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/select-rich/overview.md), an alternative for `<select>` with multiline options. | ||
### Choice Input Fields | ||
For form controls which return a `checked-state` you can use the `lion-choice-input` mixin. It is used in: | ||
- [LionCheckbox](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/checkbox-group/overview.md), a wrapper for `<input type="checkbox">`. | ||
- LionOption, an alternative for `<option>`. | ||
- [LionRadio](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/radio-group/overview.md), a wrapper for `<input type="radio">`. | ||
- [LionSwitch](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/switch/overview.md), a custom implementation of a switch. | ||
Which contains the following features: | ||
- Get or set the value of the choice - `choiceValue()`. | ||
- Get or set the modelValue (value and checked-state) of the choice - `.modelValue`. | ||
- Pre-select an option by setting the `checked` boolean attribute. | ||
## Fieldsets | ||
Fieldsets are groups of fields. They can be considered fields on their own as well, since they partly share the normalized API via `FormControlMixin`. Fieldsets are the basis for: | ||
- [LionFieldset](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/fieldset/overview.md), a wrapper around multiple input fields or other fieldsets. | ||
- [LionForm](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/form/overview.md), enhances the functionality of the native `<form>` component. | ||
- [LionCheckboxGroup](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/checkbox-group/overview.md), a wrapper component for multiple checkboxes. | ||
- [LionRadioGroup](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/components/radio-group/overview.md), a wrapper component for multiple radio inputs. | ||
## Other Resources | ||
- [Model Value](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/model-value.md) | ||
- [Formatting and parsing](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/formatting-and-parsing.md) | ||
- [Interaction states](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/interaction-states.md) | ||
- [Validation System](https://github.com/ing-bank/lion/blob/24b205837eb6a0111d8c80077dcab0b5040533e0/docs/fundamentals/systems/form/validate.md) |
export class FormElementsHaveNoError extends Validator { | ||
static get validatorName(): string; | ||
static getMessage(): Promise<string>; | ||
} | ||
import { Validator } from "../validate/Validator.js"; |
export type FormatMixin = import('../types/FormatMixinTypes').FormatMixin; | ||
export const FormatMixin: typeof import("../types/FormatMixinTypes").FormatImplementation; | ||
export type FormatOptions = import('@lion/localize/types/LocalizeMixinTypes').FormatNumberOptions; | ||
export type FormatOptions = import('../types/FormatMixinTypes').FormatOptions; | ||
export type ModelValueEventDetails = import('../types/FormControlMixinTypes.js').ModelValueEventDetails; |
@@ -10,3 +10,3 @@ /* eslint-disable class-methods-use-this */ | ||
* @typedef {import('../types/FormatMixinTypes').FormatMixin} FormatMixin | ||
* @typedef {import('@lion/localize/types/LocalizeMixinTypes').FormatNumberOptions} FormatOptions | ||
* @typedef {import('../types/FormatMixinTypes').FormatOptions} FormatOptions | ||
* @typedef {import('../types/FormControlMixinTypes.js').ModelValueEventDetails} ModelValueEventDetails | ||
@@ -111,4 +111,9 @@ */ | ||
/** | ||
* Preprocesses the viewValue before it's parsed to a modelValue. Can be used to filter | ||
* invalid input amongst others. | ||
* Preprocessors could be considered 'live formatters'. Their result is shown to the user | ||
* on keyup instead of after blurring the field. The biggest difference between preprocessors | ||
* and formatters is their moment of execution: preprocessors are run before modelValue is | ||
* computed (and work based on view value), whereas formatters are run after the parser (and | ||
* are based on modelValue) | ||
* Automatically formats code while typing. It depends on a preprocessro that smartly | ||
* updates the viewValue and caret position for best UX. | ||
* @example | ||
@@ -120,8 +125,9 @@ * ```js | ||
* } | ||
* ``` | ||
* @param {string} v - the raw value from the <input> after keyUp/Down event | ||
* @returns {string} preprocessedValue: the result of preprocessing for invalid input | ||
* @param {FormatOptions & { prevViewValue: string; currentCaretIndex: number }} opts - the raw value from the <input> after keyUp/Down event | ||
* @returns {{ viewValue:string; caretIndex:number; }|string|undefined} preprocessedValue: the result of preprocessing for invalid input | ||
*/ | ||
preprocessor(v) { | ||
return v; | ||
// eslint-disable-next-line no-unused-vars | ||
preprocessor(v, opts) { | ||
return undefined; | ||
} | ||
@@ -210,2 +216,3 @@ | ||
this.__preventRecursiveTrigger = false; | ||
this.__prevViewValue = this.value; | ||
} | ||
@@ -225,3 +232,2 @@ | ||
// For backwards compatibility we return an empty string: | ||
// - it triggers validation for required validators (see ValidateMixin.validate()) | ||
// - it can be expected by 3rd parties (for instance unit tests) | ||
@@ -232,3 +238,3 @@ // TODO(@tlouisse): In a breaking refactor of the Validation System, this behavior can be corrected. | ||
// A.2) Handle edge cases We might have no view value yet, for instance because | ||
// A.2) Handle edge cases. We might have no view value yet, for instance because | ||
// _inputNode.value was not available yet | ||
@@ -272,4 +278,3 @@ if (typeof value !== 'string') { | ||
this._isHandlingUserInput && | ||
this.hasFeedbackFor && | ||
this.hasFeedbackFor.length && | ||
this.hasFeedbackFor?.length && | ||
this.hasFeedbackFor.includes('error') && | ||
@@ -292,2 +297,4 @@ this._inputNode | ||
/** | ||
* Responds to modelValue changes in the synchronous cycle (most subclassers should listen to | ||
* the asynchronous cycle ('modelValue' in the .updated lifecycle)) | ||
* @param {{ modelValue: unknown; }[]} args | ||
@@ -331,3 +338,3 @@ * @protected | ||
if (!this.__isHandlingComposition) { | ||
this.value = this.preprocessor(this.value); | ||
this.__handlePreprocessor(); | ||
} | ||
@@ -342,7 +349,44 @@ const prevFormatted = this.formattedValue; | ||
} | ||
/** @type {string} */ | ||
this.__prevViewValue = this.value; | ||
} | ||
/** | ||
* Handle view value and caretIndex, depending on return type of .preprocessor. | ||
* @private | ||
*/ | ||
__handlePreprocessor() { | ||
const unprocessedValue = this.value; | ||
let currentCaretIndex = this.value.length; | ||
// Be gentle with Safari | ||
if ( | ||
this._inputNode && | ||
'selectionStart' in this._inputNode && | ||
/** @type {HTMLInputElement} */ (this._inputNode)?.type !== 'range' | ||
) { | ||
currentCaretIndex = /** @type {number} */ (this._inputNode.selectionStart); | ||
} | ||
const preprocessedValue = this.preprocessor(this.value, { | ||
...this.formatOptions, | ||
currentCaretIndex, | ||
prevViewValue: this.__prevViewValue, | ||
}); | ||
this.__prevViewValue = unprocessedValue; | ||
if (preprocessedValue === undefined) { | ||
// Make sure we do no set back original value, so we preserve | ||
// caret index (== selectionStart/selectionEnd) | ||
return; | ||
} | ||
if (typeof preprocessedValue === 'string') { | ||
this.value = preprocessedValue; | ||
} else if (typeof preprocessedValue === 'object') { | ||
const { viewValue, caretIndex } = preprocessedValue; | ||
this.value = viewValue; | ||
if (caretIndex && this._inputNode && 'selectionStart' in this._inputNode) { | ||
this._inputNode.selectionStart = caretIndex; | ||
this._inputNode.selectionEnd = caretIndex; | ||
} | ||
} | ||
} | ||
/** | ||
* Synchronization from `LionField.value` to `._inputNode.value` | ||
@@ -364,3 +408,3 @@ * - flow [1] will always be reflected back | ||
* modelValue change), this condition is checked. When enhancing it, it's recommended to | ||
* call `super._reflectBackOn()` | ||
* call via `return this._myExtraCondition && super._reflectBackOn()` | ||
* @overridable | ||
@@ -504,2 +548,5 @@ * @return {boolean} | ||
/** | ||
* @private | ||
*/ | ||
__onPaste() { | ||
@@ -525,2 +572,5 @@ this._isPasting = true; | ||
} | ||
/** @type {string} */ | ||
this.__prevViewValue = this.value; | ||
this._reflectBackFormattedValueToUser(); | ||
@@ -527,0 +577,0 @@ |
@@ -46,2 +46,3 @@ import { css, dedupeMixin, html, nothing, SlotMixin, DisabledMixin } from '@lion/core'; | ||
label: String, // FIXME: { attribute: false } breaks a bunch of tests, but shouldn't... | ||
labelSrOnly: { type: Boolean, attribute: 'label-sr-only', reflect: true }, | ||
helpText: { type: String, attribute: 'help-text' }, | ||
@@ -191,2 +192,8 @@ modelValue: { attribute: false }, | ||
/** | ||
* The label will only be visible for srceen readers when true | ||
* @type {boolean} | ||
*/ | ||
this.labelSrOnly = false; | ||
/** | ||
* The helpt text for the input node. | ||
@@ -704,2 +711,16 @@ * When no value is defined, textContent of [slot=help-text] will be used | ||
:host([label-sr-only]) .form-field__label { | ||
position: absolute; | ||
top: 0; | ||
width: 1px; | ||
height: 1px; | ||
overflow: hidden; | ||
clip-path: inset(100%); | ||
clip: rect(1px, 1px, 1px, 1px); | ||
white-space: nowrap; | ||
border: 0; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
/*********************** | ||
@@ -706,0 +727,0 @@ {block} .input-group |
@@ -1,2 +0,2 @@ | ||
declare const LionField_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@lion/core/types/SlotMixinTypes").SlotHost> & Pick<typeof import("@lion/core/types/SlotMixinTypes").SlotHost, "prototype"> & Pick<typeof LitElement, "prototype" | "_$litElement$" | "enabledWarnings" | "enableWarning" | "disableWarning" | "addInitializer" | "_initializers" | "elementProperties" | "properties" | "elementStyles" | "styles" | "observedAttributes" | "createProperty" | "shadowRootOptions"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/validate/ValidateMixinTypes.js").ValidateHost> & Pick<typeof import("../types/validate/ValidateMixinTypes.js").ValidateHost, "prototype" | "validationTypes"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/FormControlMixinTypes.js").FormControlHost> & Pick<typeof import("../types/FormControlMixinTypes.js").FormControlHost, "prototype" | "properties" | "styles"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/utils/SyncUpdatableMixinTypes.js").SyncUpdatableHost> & Pick<typeof import("../types/utils/SyncUpdatableMixinTypes.js").SyncUpdatableHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("@lion/core/types/DisabledMixinTypes").DisabledHost> & Pick<typeof import("@lion/core/types/DisabledMixinTypes").DisabledHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/src/types").ScopedElementsHost> & Pick<typeof import("@open-wc/scoped-elements/src/types").ScopedElementsHost, "prototype" | "shadowRootOptions" | "scopedElements"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/FormatMixinTypes.js").FormatHost> & Pick<typeof import("../types/FormatMixinTypes.js").FormatHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/FocusMixinTypes.js").FocusHost> & Pick<typeof import("../types/FocusMixinTypes.js").FocusHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/InteractionStateMixinTypes.js").InteractionStateHost> & Pick<typeof import("../types/InteractionStateMixinTypes.js").InteractionStateHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/registration/FormRegisteringMixinTypes.js").FormRegisteringHost> & Pick<typeof import("../types/registration/FormRegisteringMixinTypes.js").FormRegisteringHost, "prototype">; | ||
declare const LionField_base: typeof LitElement & import("@open-wc/dedupe-mixin").Constructor<import("@lion/core/types/SlotMixinTypes").SlotHost> & Pick<typeof import("@lion/core/types/SlotMixinTypes").SlotHost, "prototype"> & Pick<typeof LitElement, "prototype" | "_$litElement$" | "enabledWarnings" | "enableWarning" | "disableWarning" | "addInitializer" | "_initializers" | "elementProperties" | "properties" | "elementStyles" | "styles" | "observedAttributes" | "createProperty" | "shadowRootOptions"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/index.js").ValidateHost> & Pick<typeof import("../types/index.js").ValidateHost, "prototype" | "validationTypes"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/FormControlMixinTypes.js").FormControlHost> & Pick<typeof import("../types/FormControlMixinTypes.js").FormControlHost, "prototype" | "properties" | "styles"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/index.js").SyncUpdatableHost> & Pick<typeof import("../types/index.js").SyncUpdatableHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("@lion/core/types/DisabledMixinTypes").DisabledHost> & Pick<typeof import("@lion/core/types/DisabledMixinTypes").DisabledHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("@open-wc/scoped-elements/src/types").ScopedElementsHost> & Pick<typeof import("@open-wc/scoped-elements/src/types").ScopedElementsHost, "prototype" | "shadowRootOptions" | "scopedElements"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/FormatMixinTypes.js").FormatHost> & Pick<typeof import("../types/FormatMixinTypes.js").FormatHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/FocusMixinTypes.js").FocusHost> & Pick<typeof import("../types/FocusMixinTypes.js").FocusHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/InteractionStateMixinTypes.js").InteractionStateHost> & Pick<typeof import("../types/InteractionStateMixinTypes.js").InteractionStateHost, "prototype"> & import("@open-wc/dedupe-mixin").Constructor<import("../types/index.js").FormRegisteringHost> & Pick<typeof import("../types/index.js").FormRegisteringHost, "prototype">; | ||
/** | ||
@@ -3,0 +3,0 @@ * `LionField`: wraps <input>, <textarea>, <select> and other interactable elements. |
@@ -9,3 +9,3 @@ /** | ||
* The model(value) concept as implemented in lion-web is conceptually comparable to those found in | ||
* popular frameworks like Angular and Vue. | ||
* popular systems like Angular and Vue. | ||
@@ -12,0 +12,0 @@ * The Unparseable type is an addition on top of this that mainly is added for the following two |
@@ -9,3 +9,3 @@ /** | ||
* The model(value) concept as implemented in lion-web is conceptually comparable to those found in | ||
* popular frameworks like Angular and Vue. | ||
* popular systems like Angular and Vue. | ||
@@ -12,0 +12,0 @@ * The Unparseable type is an addition on top of this that mainly is added for the following two |
@@ -12,1 +12,13 @@ /** | ||
export type ValidationType = import('../../types/validate/ValidateMixinTypes').ValidationType; | ||
export type ValidateHost = import('../../types/validate/ValidateMixinTypes').ValidateHost; | ||
export type ValidateHostConstructor = typeof import('../../types/validate/ValidateMixinTypes').ValidateHost; | ||
export type ValidationResultEntry = { | ||
validator: Validator; | ||
outcome: boolean | string; | ||
}; | ||
export type ValidationStates = { | ||
[type: string]: { | ||
[validatorName: string]: string | boolean; | ||
}; | ||
}; | ||
import { Validator } from "./Validator.js"; |
@@ -20,2 +20,6 @@ /* eslint-disable class-methods-use-this, camelcase, no-param-reassign, max-classes-per-file */ | ||
* @typedef {import('../../types/validate/ValidateMixinTypes').ValidationType} ValidationType | ||
* @typedef {import('../../types/validate/ValidateMixinTypes').ValidateHost} ValidateHost | ||
* @typedef {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} ValidateHostConstructor | ||
* @typedef {{validator:Validator; outcome:boolean|string}} ValidationResultEntry | ||
* @typedef {{[type:string]: {[validatorName:string]:boolean|string}}} ValidationStates | ||
*/ | ||
@@ -107,4 +111,3 @@ | ||
feedback: () => { | ||
// @ts-ignore we load a polyfill to support createElement on shadowRoot | ||
const feedbackEl = this.shadowRoot.createElement('lion-validation-feedback'); | ||
const feedbackEl = this.createScopedElement('lion-validation-feedback'); | ||
feedbackEl.setAttribute('data-tag-name', 'lion-validation-feedback'); | ||
@@ -164,3 +167,3 @@ return feedbackEl; | ||
// TODO: [v1] make this fully private (preifix __)? | ||
// TODO: [v1] make this fully private (prefix __)? | ||
/** | ||
@@ -177,3 +180,3 @@ * A temporary storage to transition from hasFeedbackFor to showsFeedbackFor | ||
* @readOnly | ||
* @type {Object.<string, Object.<string, boolean>>} | ||
* @type {ValidationStates} | ||
*/ | ||
@@ -213,2 +216,3 @@ this.validationStates = {}; | ||
* The amount of feedback messages that will visible in LionValidationFeedback | ||
* @configurable | ||
* @protected | ||
@@ -219,3 +223,3 @@ */ | ||
/** | ||
* @type {Validator[]} | ||
* @type {ValidationResultEntry[]} | ||
* @private | ||
@@ -226,3 +230,3 @@ */ | ||
/** | ||
* @type {Validator[]} | ||
* @type {ValidationResultEntry[]} | ||
* @private | ||
@@ -234,3 +238,3 @@ */ | ||
* Aggregated result from sync Validators, async Validators and ResultValidators | ||
* @type {Validator[]} | ||
* @type {ValidationResultEntry[]} | ||
* @private | ||
@@ -241,3 +245,3 @@ */ | ||
/** | ||
* @type {Validator[]} | ||
* @type {ValidationResultEntry[]} | ||
* @private | ||
@@ -248,3 +252,3 @@ */ | ||
/** | ||
* @type {Validator[]} | ||
* @type {ValidationResultEntry[]} | ||
* @private | ||
@@ -256,3 +260,3 @@ */ | ||
* The updated children validity affects the validity of the parent. Helper to recompute | ||
* validatity of parent FormGroup | ||
* validity of parent FormGroup | ||
* @private | ||
@@ -262,5 +266,5 @@ */ | ||
/** @private */ | ||
this.__onValidatorUpdated = this.__onValidatorUpdated.bind(this); | ||
/** @protected */ | ||
this._onValidatorUpdated = this._onValidatorUpdated.bind(this); | ||
/** @protected */ | ||
this._updateFeedbackComponent = this._updateFeedbackComponent.bind(this); | ||
@@ -352,3 +356,3 @@ } | ||
* - change in the 'validators' array | ||
* - change in the config of an individual Validator | ||
* - change in the config of an individual Validator | ||
* | ||
@@ -400,2 +404,10 @@ * Three situations are handled: | ||
async __executeValidators() { | ||
/** | ||
* Allows Application Developer to wait for (async) validation | ||
* @example | ||
* ```js | ||
* await el.validateComplete; | ||
* ``` | ||
* @type {Promise<boolean>} | ||
*/ | ||
this.validateComplete = new Promise(resolve => { | ||
@@ -427,3 +439,3 @@ this.__validateCompleteResolve = resolve; | ||
if (requiredValidator) { | ||
this.__syncValidationResult = [requiredValidator]; | ||
this.__syncValidationResult = [{ validator: requiredValidator, outcome: true }]; | ||
} | ||
@@ -469,5 +481,8 @@ this.__finishValidation({ source: 'sync' }); | ||
if (syncValidators.length) { | ||
this.__syncValidationResult = syncValidators.filter(v => | ||
v.execute(value, v.param, { node: this }), | ||
); | ||
this.__syncValidationResult = syncValidators | ||
.map(v => ({ | ||
validator: v, | ||
outcome: /** @type {boolean|string} */ (v.execute(value, v.param, { node: this })), | ||
})) | ||
.filter(v => Boolean(v.outcome)); | ||
} | ||
@@ -487,6 +502,11 @@ this.__finishValidation({ source: 'sync', hasAsync }); | ||
const resultPromises = asyncValidators.map(v => v.execute(value, v.param, { node: this })); | ||
const booleanResults = await Promise.all(resultPromises); | ||
this.__asyncValidationResult = booleanResults | ||
.map((r, i) => asyncValidators[i]) // Create an array of Validators | ||
.filter((v, i) => booleanResults[i]); // Only leave the ones returning true | ||
const asyncExecutionResults = await Promise.all(resultPromises); | ||
this.__asyncValidationResult = asyncExecutionResults | ||
.map((r, i) => ({ | ||
validator: asyncValidators[i], | ||
outcome: /** @type {boolean|string} */ (asyncExecutionResults[i]), | ||
})) | ||
.filter(v => Boolean(v.outcome)); | ||
this.__finishValidation({ source: 'async' }); | ||
@@ -499,3 +519,3 @@ this.isPending = false; | ||
* step b (as explained in `validate()`), called by __finishValidation | ||
* @param {Validator[]} regularValidationResult result of steps 1-3 | ||
* @param {{validator:Validator; outcome: boolean|string;}[]} regularValidationResult result of steps 1-3 | ||
* @private | ||
@@ -511,9 +531,17 @@ */ | ||
return resultValidators.filter(v => | ||
v.executeOnResults({ | ||
regularValidationResult, | ||
prevValidationResult: this.__prevValidationResult, | ||
prevShownValidationResult: this.__prevShownValidationResult, | ||
}), | ||
); | ||
// Map everything to Validator[] for backwards compatibility | ||
return resultValidators | ||
.map(v => ({ | ||
validator: v, | ||
outcome: /** @type {boolean|string} */ ( | ||
v.executeOnResults({ | ||
regularValidationResult: regularValidationResult.map(entry => entry.validator), | ||
prevValidationResult: this.__prevValidationResult.map(entry => entry.validator), | ||
prevShownValidationResult: this.__prevShownValidationResult.map( | ||
entry => entry.validator, | ||
), | ||
}) | ||
), | ||
})) | ||
.filter(v => Boolean(v.outcome)); | ||
} | ||
@@ -534,10 +562,6 @@ | ||
this.__validationResult = [...resultOutCome, ...syncAndAsyncOutcome]; | ||
// this._storeResultsOnInstance(this.__validationResult); | ||
const ctor = | ||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ ( | ||
this.constructor | ||
); | ||
const ctor = /** @type {ValidateHostConstructor} */ (this.constructor); | ||
/** @type {Object.<string, Object.<string, boolean>>} */ | ||
/** @type {ValidationStates} */ | ||
const validationStates = ctor.validationTypes.reduce( | ||
@@ -547,12 +571,14 @@ (acc, type) => ({ ...acc, [type]: {} }), | ||
); | ||
this.__validationResult.forEach(v => { | ||
if (!validationStates[v.type]) { | ||
validationStates[v.type] = {}; | ||
this.__validationResult.forEach(({ validator, outcome }) => { | ||
if (!validationStates[validator.type]) { | ||
validationStates[validator.type] = {}; | ||
} | ||
const vCtor = /** @type {typeof Validator} */ (v.constructor); | ||
validationStates[v.type][vCtor.validatorName] = true; | ||
const vCtor = /** @type {typeof Validator} */ (validator.constructor); | ||
validationStates[validator.type][vCtor.validatorName] = outcome; | ||
}); | ||
this.validationStates = validationStates; | ||
this.hasFeedbackFor = [...new Set(this.__validationResult.map(v => v.type))]; | ||
this.hasFeedbackFor = [ | ||
...new Set(this.__validationResult.map(({ validator }) => validator.type)), | ||
]; | ||
@@ -563,4 +589,3 @@ /** private event that should be listened to by LionFieldSet */ | ||
if (this.__validateCompleteResolve) { | ||
// @ts-ignore [allow-private] | ||
this.__validateCompleteResolve(); | ||
this.__validateCompleteResolve(true); | ||
} | ||
@@ -580,5 +605,5 @@ } | ||
* @param {Event|CustomEvent} e | ||
* @private | ||
* @protected | ||
*/ | ||
__onValidatorUpdated(e) { | ||
_onValidatorUpdated(e) { | ||
if (e.type === 'param-changed' || e.type === 'config-changed') { | ||
@@ -598,3 +623,3 @@ this.validate(); | ||
if (v.removeEventListener) { | ||
v.removeEventListener(e, this.__onValidatorUpdated); | ||
v.removeEventListener(e, this._onValidatorUpdated); | ||
} | ||
@@ -614,6 +639,3 @@ }); | ||
} | ||
const ctor = | ||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ ( | ||
this.constructor | ||
); | ||
const ctor = /** @type {ValidateHostConstructor} */ (this.constructor); | ||
if (ctor.validationTypes.indexOf(v.type) === -1) { | ||
@@ -627,5 +649,11 @@ const vCtor = /** @type {typeof Validator} */ (v.constructor); | ||
} | ||
events.forEach(e => { | ||
/** Updated the code to fix issue #1607 to sync the calendar date with validators params | ||
* Here _onValidatorUpdated is responsible for responding to the event | ||
*/ | ||
events.forEach(eventName => { | ||
if (v.addEventListener) { | ||
v.addEventListener(e, this.__onValidatorUpdated); | ||
v.addEventListener(eventName, e => { | ||
// @ts-ignore for making validator param dynamic | ||
this._onValidatorUpdated(e, { validator: v }); | ||
}); | ||
} | ||
@@ -669,10 +697,10 @@ }); | ||
/** | ||
* @param {Validator[]} validators list of objects having a .getMessage method | ||
* @param {ValidationResultEntry[]} validationResults list of objects having a .getMessage method | ||
* @return {Promise.<FeedbackMessage[]>} | ||
* @private | ||
*/ | ||
async __getFeedbackMessages(validators) { | ||
async __getFeedbackMessages(validationResults) { | ||
let fieldName = await this.fieldName; | ||
return Promise.all( | ||
validators.map(async validator => { | ||
validationResults.map(async ({ validator, outcome }) => { | ||
if (validator.config.fieldName) { | ||
@@ -686,2 +714,3 @@ fieldName = await validator.config.fieldName; | ||
fieldName, | ||
outcome, | ||
}); | ||
@@ -721,6 +750,15 @@ return { message, type: validator.type, validator }; | ||
/** @type {Validator[]} */ | ||
this.__prioritizedResult = this._prioritizeAndFilterFeedback({ | ||
validationResult: this.__validationResult, | ||
const prioritizedValidators = this._prioritizeAndFilterFeedback({ | ||
validationResult: this.__validationResult.map(entry => entry.validator), | ||
}); | ||
this.__prioritizedResult = prioritizedValidators | ||
.map(v => { | ||
const found = /** @type {ValidationResultEntry} */ ( | ||
this.__validationResult.find(r => v === r.validator) | ||
); | ||
return found; | ||
}) | ||
.filter(Boolean); | ||
if (this.__prioritizedResult.length > 0) { | ||
@@ -764,3 +802,3 @@ this.__prevShownValidationResult = this.__prioritizedResult; | ||
/** | ||
* Allows the end user to specify when a feedback message should be shown | ||
* Allows the Application Developer to specify when a feedback message should be shown | ||
* @example | ||
@@ -770,3 +808,3 @@ * ```js | ||
* if (type === 'info') { | ||
* return return; | ||
* return true; | ||
* } else if (type === 'prefilledOnly') { | ||
@@ -809,3 +847,5 @@ * return meta.prefilled; | ||
/** @param {import('@lion/core').PropertyValues} changedProperties */ | ||
/** | ||
* @param {import('@lion/core').PropertyValues} changedProperties | ||
*/ | ||
updated(changedProperties) { | ||
@@ -818,6 +858,3 @@ super.updated(changedProperties); | ||
) { | ||
const ctor = | ||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ ( | ||
this.constructor | ||
); | ||
const ctor = /** @type {ValidateHostConstructor} */ (this.constructor); | ||
// Necessary typecast because types aren't smart enough to understand that we filter out undefined | ||
@@ -858,6 +895,3 @@ this.showsFeedbackFor = /** @type {string[]} */ ( | ||
_updateShouldShowFeedbackFor() { | ||
const ctor = | ||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ ( | ||
this.constructor | ||
); | ||
const ctor = /** @type {ValidateHostConstructor} */ (this.constructor); | ||
@@ -885,4 +919,4 @@ // Necessary typecast because types aren't smart enough to understand that we filter out undefined | ||
/** | ||
* Orders all active validators in this.__validationResult. Can | ||
* also filter out occurrences (based on interaction states) | ||
* Orders all active validators in this.__validationResult. | ||
* Can also filter out occurrences (based on interaction states) | ||
* @overridable | ||
@@ -894,6 +928,3 @@ * @param {{ validationResult: Validator[] }} opts | ||
_prioritizeAndFilterFeedback({ validationResult }) { | ||
const ctor = | ||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ ( | ||
this.constructor | ||
); | ||
const ctor = /** @type {ValidateHostConstructor} */ (this.constructor); | ||
const types = ctor.validationTypes; | ||
@@ -900,0 +931,0 @@ // Sort all validators based on the type provided. |
/** | ||
* @typedef {object} MessageData | ||
* @property {*} [MessageData.modelValue] | ||
* @property {string} [MessageData.fieldName] | ||
* @property {HTMLElement} [MessageData.formControl] | ||
* @property {string} [MessageData.type] | ||
* @property {Object.<string,?>} [MessageData.config] | ||
* @property {string} [MessageData.name] | ||
* @typedef {import('../../types/validate/validate').FeedbackMessageData} FeedbackMessageData | ||
* @typedef {import('../../types/validate/validate').ValidatorParam} ValidatorParam | ||
* @typedef {import('../../types/validate/validate').ValidatorConfig} ValidatorConfig | ||
* @typedef {import('../../types/validate/validate').ValidatorOutcome} ValidatorOutcome | ||
* @typedef {import('../../types/validate/validate').ValidatorName} ValidatorName | ||
* @typedef {import('../../types/validate/validate').ValidationType} ValidationType | ||
* @typedef {import('../FormControlMixin').FormControlHost} FormControlHost | ||
*/ | ||
export class Validator { | ||
static get validatorName(): string; | ||
static get async(): boolean; | ||
export class Validator extends EventTarget { | ||
/** | ||
* The name under which validation results get registered. For convience and predictability, this | ||
* should always be the same as the constructor name (since it will be obfuscated in js builds, | ||
* we need to provide it separately). | ||
* @type {ValidatorName} | ||
*/ | ||
static validatorName: ValidatorName; | ||
/** | ||
* Whether the validator is asynchronous or not. When true., this means execute function returns | ||
* a Promise. This can be handy for: | ||
* - server side calls | ||
* - validations that are dependent on lazy loaded resources (they can be async until the dependency | ||
* is loaded) | ||
* @type {boolean} | ||
*/ | ||
static async: boolean; | ||
/** | ||
* Called inside Validator.prototype._getMessage (see explanation). | ||
* @example | ||
* ```js | ||
* class MyValidator extends Validator { | ||
* static async getMessage() { | ||
* return 'lowest prio, defined globally by Validator author' | ||
* } | ||
* } | ||
* // globally overridden | ||
* MyValidator.getMessage = async() => 'overrides already configured message'; | ||
* ``` | ||
* @overridable | ||
* @param {MessageData} [data] | ||
* @returns {Promise<string|Node>} | ||
* @param {Partial<FeedbackMessageData>} [data] | ||
* @returns {Promise<string|Element>} | ||
*/ | ||
static getMessage(data?: MessageData | undefined): Promise<string | Node>; | ||
static getMessage(data?: Partial<import("../../types/validate/validate").FeedbackMessageData> | undefined): Promise<string | Element>; | ||
/** | ||
* | ||
* @param {?} [param] | ||
* @param {Object.<string,?>} [config] | ||
* @param {ValidatorParam} [param] | ||
* @param {ValidatorConfig} [config] | ||
*/ | ||
constructor(param?: unknown, config?: { | ||
[x: string]: any; | ||
} | undefined); | ||
/** @type {?} */ | ||
__param: unknown; | ||
/** @type {Object.<string,?>} */ | ||
__config: { | ||
[x: string]: unknown; | ||
}; | ||
constructor(param?: ValidatorParam, config?: import("../../types/validate/validate").ValidatorConfig | undefined); | ||
/** @type {ValidatorParam} */ | ||
__param: ValidatorParam; | ||
/** @type {ValidatorConfig} */ | ||
__config: ValidatorConfig; | ||
/** @type {ValidationType} */ | ||
type: any; | ||
/** | ||
* @desc The function that returns a Boolean | ||
* @param {?} [modelValue] | ||
* @param {?} [param] | ||
* @param {{}} [config] | ||
* @returns {Boolean|Promise<Boolean>} | ||
* The function that returns a validity outcome. When we need to show feedback, | ||
* it should return true, otherwise false. So when an error\info|warning|success message | ||
* needs to be shown, return true. For async Validators, the function can return a Promise. | ||
* It's also possible to return an enum. Let's say that a phone number can have multiple | ||
* states: 'invalid-country-code' | 'too-long' | 'too-short' | ||
* Those states can be retrieved in the getMessage | ||
* @param {any} modelValue | ||
* @param {ValidatorParam} [param] | ||
* @param {ValidatorConfig} [config] | ||
* @returns {ValidatorOutcome|Promise<ValidatorOutcome>} | ||
*/ | ||
execute(modelValue?: unknown, param?: unknown, config?: {} | undefined): boolean | Promise<boolean>; | ||
execute(modelValue: any, param?: ValidatorParam, config?: import("../../types/validate/validate").ValidatorConfig | undefined): ValidatorOutcome | Promise<ValidatorOutcome>; | ||
/** | ||
* The first argument of the constructor, for instance 3 in `new MinLength(3)`. Will | ||
* be stored on Validator instance and passed to `execute` function | ||
* @example | ||
* ```js | ||
* // Store reference to Validator instance | ||
* const myValidatorInstance = new MyValidator(1); | ||
* // Use this instance initially on a FormControl (that uses ValidateMixin) | ||
* render(html`<validatable-element .validators="${[myValidatorInstance]}"></validatable-element>`, document.body); | ||
* // Based on some event, we need to change the param | ||
* myValidatorInstance.param = 2; | ||
* ``` | ||
* @property {ValidatorParam} | ||
*/ | ||
set param(arg: any); | ||
get param(): any; | ||
set config(arg: { | ||
[x: string]: any; | ||
}); | ||
get config(): { | ||
[x: string]: any; | ||
}; | ||
/** | ||
* @overridable | ||
* @param {MessageData} [data] | ||
* @returns {Promise<string|Node>} | ||
* The second argument of the constructor, for instance | ||
* `new MinLength(3, {getFeedMessage: async () => 'too long'})`. | ||
* Will be stored on Validator instance and passed to `execute` function. | ||
* @example | ||
* ```js | ||
* // Store reference to Validator instance | ||
* const myValidatorInstance = new MyValidator(1, {getMessage() => 'x'}); | ||
* // Use this instance initially on a FormControl (that uses ValidateMixin) | ||
* render(html`<validatable-element .validators="${[myValidatorInstance]}"></validatable-element>`, document.body); | ||
* // Based on some event, we need to change the param | ||
* myValidatorInstance.config = {getMessage() => 'y'}; | ||
* ``` | ||
* @property {ValidatorConfig} | ||
*/ | ||
set config(arg: import("../../types/validate/validate").ValidatorConfig); | ||
get config(): import("../../types/validate/validate").ValidatorConfig; | ||
/** | ||
* This is a protected method that usually should not be overridden. It is called by ValidateMixin | ||
* and it gathers data to be passed to getMessage functions found: | ||
* - `this.config.getMessage`, locally provided by consumers of the Validator (overrides global getMessage) | ||
* - `MyValidator.getMessage`, globally provided by creators or consumers of the Validator | ||
* | ||
* Confusion can arise because of similarities with former mentioned methods. In that regard, a | ||
* better name for this function would have been _pepareDataAndCallHighestPrioGetMessage. | ||
* @example | ||
* ```js | ||
* class MyValidator extends Validator { | ||
* // ... | ||
* // 1. globally defined | ||
* static async getMessage() { | ||
* return 'lowest prio, defined globally by Validator author' | ||
* } | ||
* } | ||
* // 2. globally overridden | ||
* MyValidator.getMessage = async() => 'overrides already configured message'; | ||
* // 3. locally overridden | ||
* new MyValidator(myParam, { getMessage: async() => 'locally defined, always wins' }); | ||
* ``` | ||
* @param {Partial<FeedbackMessageData>} [data] | ||
* @returns {Promise<string|Element>} | ||
* @protected | ||
*/ | ||
protected _getMessage(data?: MessageData | undefined): Promise<string | Node>; | ||
protected _getMessage(data?: Partial<import("../../types/validate/validate").FeedbackMessageData> | undefined): Promise<string | Element>; | ||
/** | ||
* @param {HTMLElement} formControl | ||
* Validators are allowed to have knowledge about FormControls. | ||
* In some cases (in case of the Required Validator) we wanted to enhance accessibility by | ||
* adding [aria-required]. Also, it would be possible to write an advanced MinLength | ||
* Validator that adds a .preprocessor that restricts from typing too many characters | ||
* (like the native [minlength] validator). | ||
* Will be called when Validator is added to FormControl.validators. | ||
* @example | ||
* ```js | ||
* onFormControlConnect(formControl) { | ||
* if(formControl.inputNode) { | ||
* inputNode.setAttribute('aria-required', 'true'); | ||
* } | ||
* } | ||
* | ||
* ``` | ||
* @configurable | ||
* @param {FormControlHost} formControl | ||
*/ | ||
onFormControlConnect(formControl: HTMLElement): void; | ||
onFormControlConnect(formControl: FormControlHost): void; | ||
/** | ||
* @param {HTMLElement} formControl | ||
* Also see `onFormControlConnect`. | ||
* Will be called when Validator is removed from FormControl.validators. | ||
* @example | ||
* ```js | ||
* onFormControlDisconnect(formControl) { | ||
* if(formControl.inputNode) { | ||
* inputNode.removeAttribute('aria-required'); | ||
* } | ||
* } | ||
* @configurable | ||
* @param {FormControlHost} formControl | ||
*/ | ||
onFormControlDisconnect(formControl: HTMLElement): void; | ||
onFormControlDisconnect(formControl: FormControlHost): void; | ||
/** | ||
@@ -75,19 +172,9 @@ * @desc Used on async Validators, makes it able to do perf optimizations when there are | ||
abortExecution(): void; | ||
/** | ||
* @private | ||
*/ | ||
private __fakeExtendsEventTarget; | ||
addEventListener: ((type: string, listener: EventListener, opts?: Object | undefined) => void) | undefined; | ||
removeEventListener: ((type: string, listener: EventListener, opts?: Object | undefined) => void) | undefined; | ||
dispatchEvent: ((event: Event | CustomEvent) => boolean) | undefined; | ||
} | ||
export type MessageData = { | ||
modelValue?: any; | ||
fieldName?: string | undefined; | ||
formControl?: HTMLElement | undefined; | ||
type?: string | undefined; | ||
config?: { | ||
[x: string]: any; | ||
} | undefined; | ||
name?: string | undefined; | ||
}; | ||
export type FeedbackMessageData = import('../../types/validate/validate').FeedbackMessageData; | ||
export type ValidatorParam = import('../../types/validate/validate').ValidatorParam; | ||
export type ValidatorConfig = import('../../types/validate/validate').ValidatorConfig; | ||
export type ValidatorOutcome = import('../../types/validate/validate').ValidatorOutcome; | ||
export type ValidatorName = import('../../types/validate/validate').ValidatorName; | ||
export type ValidationType = any; | ||
export type FormControlHost = import('../FormControlMixin').FormControlHost; |
/** | ||
* @typedef {object} MessageData | ||
* @property {*} [MessageData.modelValue] | ||
* @property {string} [MessageData.fieldName] | ||
* @property {HTMLElement} [MessageData.formControl] | ||
* @property {string} [MessageData.type] | ||
* @property {Object.<string,?>} [MessageData.config] | ||
* @property {string} [MessageData.name] | ||
* @typedef {import('../../types/validate/validate').FeedbackMessageData} FeedbackMessageData | ||
* @typedef {import('../../types/validate/validate').ValidatorParam} ValidatorParam | ||
* @typedef {import('../../types/validate/validate').ValidatorConfig} ValidatorConfig | ||
* @typedef {import('../../types/validate/validate').ValidatorOutcome} ValidatorOutcome | ||
* @typedef {import('../../types/validate/validate').ValidatorName} ValidatorName | ||
* @typedef {import('../../types/validate/validate').ValidationType} ValidationType | ||
* @typedef {import('../FormControlMixin').FormControlHost} FormControlHost | ||
*/ | ||
export class Validator { | ||
// TODO: support attribute validators like <my-el my-validator=${dynamicParam}></my-el> => | ||
// register in a ValidateService that is read by Validator and adds these attrs in properties | ||
// object. | ||
// They would become like configurable | ||
// [global attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes) | ||
// for FormControls. | ||
export class Validator extends EventTarget { | ||
/** | ||
* | ||
* @param {?} [param] | ||
* @param {Object.<string,?>} [config] | ||
* @param {ValidatorParam} [param] | ||
* @param {ValidatorConfig} [config] | ||
*/ | ||
constructor(param, config) { | ||
this.__fakeExtendsEventTarget(); | ||
super(); | ||
/** @type {?} */ | ||
/** @type {ValidatorParam} */ | ||
this.__param = param; | ||
/** @type {Object.<string,?>} */ | ||
/** @type {ValidatorConfig} */ | ||
this.__config = config || {}; | ||
this.type = (config && config.type) || 'error'; // Default type supported by ValidateMixin | ||
/** @type {ValidationType} */ | ||
this.type = config?.type || 'error'; // Default type supported by ValidateMixin | ||
} | ||
static get validatorName() { | ||
return ''; | ||
} | ||
/** | ||
* The name under which validation results get registered. For convience and predictability, this | ||
* should always be the same as the constructor name (since it will be obfuscated in js builds, | ||
* we need to provide it separately). | ||
* @type {ValidatorName} | ||
*/ | ||
static validatorName = ''; | ||
static get async() { | ||
return false; | ||
} | ||
/** | ||
* Whether the validator is asynchronous or not. When true., this means execute function returns | ||
* a Promise. This can be handy for: | ||
* - server side calls | ||
* - validations that are dependent on lazy loaded resources (they can be async until the dependency | ||
* is loaded) | ||
* @type {boolean} | ||
*/ | ||
static async = false; | ||
/** | ||
* @desc The function that returns a Boolean | ||
* @param {?} [modelValue] | ||
* @param {?} [param] | ||
* @param {{}} [config] | ||
* @returns {Boolean|Promise<Boolean>} | ||
* The function that returns a validity outcome. When we need to show feedback, | ||
* it should return true, otherwise false. So when an error\info|warning|success message | ||
* needs to be shown, return true. For async Validators, the function can return a Promise. | ||
* It's also possible to return an enum. Let's say that a phone number can have multiple | ||
* states: 'invalid-country-code' | 'too-long' | 'too-short' | ||
* Those states can be retrieved in the getMessage | ||
* @param {any} modelValue | ||
* @param {ValidatorParam} [param] | ||
* @param {ValidatorConfig} [config] | ||
* @returns {ValidatorOutcome|Promise<ValidatorOutcome>} | ||
*/ | ||
@@ -54,7 +75,23 @@ // eslint-disable-next-line no-unused-vars, class-methods-use-this | ||
/** | ||
* The first argument of the constructor, for instance 3 in `new MinLength(3)`. Will | ||
* be stored on Validator instance and passed to `execute` function | ||
* @example | ||
* ```js | ||
* // Store reference to Validator instance | ||
* const myValidatorInstance = new MyValidator(1); | ||
* // Use this instance initially on a FormControl (that uses ValidateMixin) | ||
* render(html`<validatable-element .validators="${[myValidatorInstance]}"></validatable-element>`, document.body); | ||
* // Based on some event, we need to change the param | ||
* myValidatorInstance.param = 2; | ||
* ``` | ||
* @property {ValidatorParam} | ||
*/ | ||
set param(p) { | ||
this.__param = p; | ||
if (this.dispatchEvent) { | ||
this.dispatchEvent(new Event('param-changed')); | ||
} | ||
/** | ||
* This event is listened for by ValidateMixin. Whenever the validation parameter has | ||
* changed, the FormControl will revalidate itself | ||
*/ | ||
this.dispatchEvent(new Event('param-changed')); | ||
} | ||
@@ -66,7 +103,24 @@ | ||
/** | ||
* The second argument of the constructor, for instance | ||
* `new MinLength(3, {getFeedMessage: async () => 'too long'})`. | ||
* Will be stored on Validator instance and passed to `execute` function. | ||
* @example | ||
* ```js | ||
* // Store reference to Validator instance | ||
* const myValidatorInstance = new MyValidator(1, {getMessage() => 'x'}); | ||
* // Use this instance initially on a FormControl (that uses ValidateMixin) | ||
* render(html`<validatable-element .validators="${[myValidatorInstance]}"></validatable-element>`, document.body); | ||
* // Based on some event, we need to change the param | ||
* myValidatorInstance.config = {getMessage() => 'y'}; | ||
* ``` | ||
* @property {ValidatorConfig} | ||
*/ | ||
set config(c) { | ||
this.__config = c; | ||
if (this.dispatchEvent) { | ||
this.dispatchEvent(new Event('config-changed')); | ||
} | ||
/** | ||
* This event is listened for by ValidateMixin. Whenever the validation config has | ||
* changed, the FormControl will revalidate itself | ||
*/ | ||
this.dispatchEvent(new Event('config-changed')); | ||
} | ||
@@ -79,5 +133,25 @@ | ||
/** | ||
* @overridable | ||
* @param {MessageData} [data] | ||
* @returns {Promise<string|Node>} | ||
* This is a protected method that usually should not be overridden. It is called by ValidateMixin | ||
* and it gathers data to be passed to getMessage functions found: | ||
* - `this.config.getMessage`, locally provided by consumers of the Validator (overrides global getMessage) | ||
* - `MyValidator.getMessage`, globally provided by creators or consumers of the Validator | ||
* | ||
* Confusion can arise because of similarities with former mentioned methods. In that regard, a | ||
* better name for this function would have been _pepareDataAndCallHighestPrioGetMessage. | ||
* @example | ||
* ```js | ||
* class MyValidator extends Validator { | ||
* // ... | ||
* // 1. globally defined | ||
* static async getMessage() { | ||
* return 'lowest prio, defined globally by Validator author' | ||
* } | ||
* } | ||
* // 2. globally overridden | ||
* MyValidator.getMessage = async() => 'overrides already configured message'; | ||
* // 3. locally overridden | ||
* new MyValidator(myParam, { getMessage: async() => 'locally defined, always wins' }); | ||
* ``` | ||
* @param {Partial<FeedbackMessageData>} [data] | ||
* @returns {Promise<string|Element>} | ||
* @protected | ||
@@ -107,5 +181,16 @@ */ | ||
/** | ||
* Called inside Validator.prototype._getMessage (see explanation). | ||
* @example | ||
* ```js | ||
* class MyValidator extends Validator { | ||
* static async getMessage() { | ||
* return 'lowest prio, defined globally by Validator author' | ||
* } | ||
* } | ||
* // globally overridden | ||
* MyValidator.getMessage = async() => 'overrides already configured message'; | ||
* ``` | ||
* @overridable | ||
* @param {MessageData} [data] | ||
* @returns {Promise<string|Node>} | ||
* @param {Partial<FeedbackMessageData>} [data] | ||
* @returns {Promise<string|Element>} | ||
*/ | ||
@@ -118,3 +203,19 @@ // eslint-disable-next-line no-unused-vars | ||
/** | ||
* @param {HTMLElement} formControl | ||
* Validators are allowed to have knowledge about FormControls. | ||
* In some cases (in case of the Required Validator) we wanted to enhance accessibility by | ||
* adding [aria-required]. Also, it would be possible to write an advanced MinLength | ||
* Validator that adds a .preprocessor that restricts from typing too many characters | ||
* (like the native [minlength] validator). | ||
* Will be called when Validator is added to FormControl.validators. | ||
* @example | ||
* ```js | ||
* onFormControlConnect(formControl) { | ||
* if(formControl.inputNode) { | ||
* inputNode.setAttribute('aria-required', 'true'); | ||
* } | ||
* } | ||
* | ||
* ``` | ||
* @configurable | ||
* @param {FormControlHost} formControl | ||
*/ | ||
@@ -124,3 +225,13 @@ onFormControlConnect(formControl) {} // eslint-disable-line | ||
/** | ||
* @param {HTMLElement} formControl | ||
* Also see `onFormControlConnect`. | ||
* Will be called when Validator is removed from FormControl.validators. | ||
* @example | ||
* ```js | ||
* onFormControlDisconnect(formControl) { | ||
* if(formControl.inputNode) { | ||
* inputNode.removeAttribute('aria-required'); | ||
* } | ||
* } | ||
* @configurable | ||
* @param {FormControlHost} formControl | ||
*/ | ||
@@ -139,37 +250,2 @@ onFormControlDisconnect(formControl) {} // eslint-disable-line | ||
abortExecution() {} // eslint-disable-line | ||
/** | ||
* @private | ||
*/ | ||
__fakeExtendsEventTarget() { | ||
const delegate = document.createDocumentFragment(); | ||
/** | ||
* | ||
* @param {string} type | ||
* @param {EventListener} listener | ||
* @param {Object} [opts] | ||
*/ | ||
const delegatedAddEventListener = (type, listener, opts) => | ||
delegate.addEventListener(type, listener, opts); | ||
/** | ||
* @param {string} type | ||
* @param {EventListener} listener | ||
* @param {Object} [opts] | ||
*/ | ||
const delegatedRemoveEventListener = (type, listener, opts) => | ||
delegate.removeEventListener(type, listener, opts); | ||
/** | ||
* @param {Event|CustomEvent} event | ||
*/ | ||
const delegatedDispatchEvent = event => delegate.dispatchEvent(event); | ||
this.addEventListener = delegatedAddEventListener; | ||
this.removeEventListener = delegatedRemoveEventListener; | ||
this.dispatchEvent = delegatedDispatchEvent; | ||
} | ||
} | ||
@@ -176,0 +252,0 @@ |
export class IsDate extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MinDate extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MaxDate extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MinMaxDate extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class IsDateDisabled extends Validator { | ||
static get validatorName(): string; | ||
} | ||
import { Validator } from "../Validator.js"; |
export class IsNumber extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MinNumber extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MaxNumber extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MinMaxNumber extends Validator { | ||
static get validatorName(): string; | ||
} | ||
import { Validator } from "../Validator.js"; |
@@ -5,2 +5,3 @@ /** | ||
export class Required extends Validator { | ||
static get validatorName(): string; | ||
/** | ||
@@ -7,0 +8,0 @@ * In order to prevent accessibility violations, the aria-required attribute will |
export class IsString extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class EqualsLength extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MinLength extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MaxLength extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class MinMaxLength extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class IsEmail extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class Pattern extends Validator { | ||
static get validatorName(): string; | ||
} | ||
import { Validator } from "../Validator.js"; |
export class AlwaysInvalid extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class AlwaysValid extends Validator { | ||
static get validatorName(): string; | ||
} | ||
export class AsyncAlwaysValid extends AlwaysValid { | ||
static get async(): boolean; | ||
} | ||
export class AsyncAlwaysInvalid extends AlwaysValid { | ||
static get async(): boolean; | ||
} | ||
import { Validator } from "../src/validate/Validator.js"; |
export * from "./ExampleValidators.js"; | ||
export * from "./getFormControlMembers.js"; | ||
export * from "./mimicUserInput.js"; |
export * from './ExampleValidators.js'; | ||
export * from './getFormControlMembers.js'; | ||
export * from './mimicUserInput.js'; |
@@ -83,2 +83,39 @@ import { LitElement } from '@lion/core'; | ||
/** | ||
* N.B. For platform controls, the same would be achieved with <input aria-label="My label"> | ||
* However, since FormControl is usually not the activeElement (_inputNode is), this | ||
* will not have the desired effect on for instance lion-input | ||
*/ | ||
it('supports "label-sr-only" to make label visually hidden, but accessible for screen reader users', async () => { | ||
const el = /** @type {FormControlMixinClass} */ ( | ||
await fixture(html` | ||
<${tag} label-sr-only> | ||
<label slot="label">Email <span>address</span></label> | ||
${inputSlot} | ||
</${tag}>`) | ||
); | ||
const expectedValues = { | ||
position: 'absolute', | ||
top: '0px', | ||
width: '1px', | ||
height: '1px', | ||
overflow: 'hidden', | ||
clipPath: 'inset(100%)', | ||
clip: 'rect(1px, 1px, 1px, 1px)', | ||
whiteSpace: 'nowrap', | ||
borderWidth: '0px', | ||
margin: '0px', | ||
padding: '0px', | ||
}; | ||
const labelStyle = window.getComputedStyle( | ||
// @ts-ignore | ||
el.shadowRoot?.querySelector('.form-field__label'), | ||
); | ||
Object.entries(expectedValues).forEach(([key, val]) => { | ||
expect(labelStyle[key]).to.equal(val); | ||
}); | ||
}); | ||
it('can have a help-text', async () => { | ||
@@ -85,0 +122,0 @@ const elAttr = /** @type {FormControlMixinClass} */ ( |
@@ -51,3 +51,3 @@ import { LitElement } from '@lion/core'; | ||
expect(() => { | ||
new MyValidator().execute(); | ||
new MyValidator().execute(undefined); | ||
}).to.throw( | ||
@@ -65,2 +65,3 @@ 'A validator needs to have a name! Please set it via "static get validatorName() { return \'IsCat\'; }"', | ||
// @ts-ignore needed for test | ||
const vali = new MyValidator({}, { getMessage: 'This is the custom error message' }); | ||
@@ -81,4 +82,4 @@ const { getMessage } = getProtectedMembers(vali); | ||
it('receives a config object (optionally) as a second argument on instantiation', async () => { | ||
const vali = new Validator('myParam', { my: 'config' }); | ||
expect(vali.config).to.eql({ my: 'config' }); | ||
const vali = new Validator('myParam', { fieldName: 'X' }); | ||
expect(vali.config).to.eql({ fieldName: 'X' }); | ||
}); | ||
@@ -93,3 +94,3 @@ | ||
} | ||
const vali = new MyValidator('myParam', { my: 'config', getMessage: configSpy }); | ||
const vali = new MyValidator('myParam', { fieldName: 'X', getMessage: configSpy }); | ||
const { getMessage } = getProtectedMembers(vali); | ||
@@ -102,3 +103,3 @@ getMessage(); | ||
params: 'myParam', | ||
config: { my: 'config', getMessage: configSpy }, | ||
config: { fieldName: 'X', getMessage: configSpy }, | ||
}); | ||
@@ -122,3 +123,3 @@ }); | ||
} | ||
const vali = new MyValidator('myParam', { my: 'config' }); | ||
const vali = new MyValidator('myParam', { fieldName: 'X' }); | ||
const { getMessage } = getProtectedMembers(vali); | ||
@@ -131,3 +132,3 @@ getMessage(); | ||
params: 'myParam', | ||
config: { my: 'config' }, | ||
config: { fieldName: 'X' }, | ||
}); | ||
@@ -147,3 +148,3 @@ }); | ||
it('fires "config-changed" event on config change', async () => { | ||
const vali = new Validator('foo', { foo: 'bar' }); | ||
const vali = new Validator('foo', { fieldName: 'X' }); | ||
const cb = sinon.spy(() => {}); | ||
@@ -153,3 +154,3 @@ if (vali.addEventListener) { | ||
} | ||
vali.config = { bar: 'foo' }; | ||
vali.config = { fieldName: 'Y' }; | ||
expect(cb.callCount).to.equal(1); | ||
@@ -156,0 +157,0 @@ }); |
@@ -7,2 +7,3 @@ import { Constructor } from '@open-wc/dedupe-mixin'; | ||
export type FormatOptions = { mode: 'pasted' | 'auto' } & object; | ||
export declare class FormatHost { | ||
@@ -16,3 +17,3 @@ /** | ||
*/ | ||
parser(v: string, opts: FormatNumberOptions): unknown; | ||
parser(v: string, opts: FormatOptions): unknown; | ||
@@ -28,3 +29,3 @@ /** | ||
*/ | ||
formatter(v: unknown, opts?: FormatNumberOptions): string; | ||
formatter(v: unknown, opts?: FormatOptions): string; | ||
@@ -50,4 +51,9 @@ /** | ||
/** | ||
* Preprocesses the viewValue before it's parsed to a modelValue. Can be used to filter | ||
* invalid input amongst others. | ||
* Preprocessors could be considered 'live formatters'. Their result is shown to the user | ||
* on keyup instead of after blurring the field. The biggest difference between preprocessors | ||
* and formatters is their moment of execution: preprocessors are run before modelValue is | ||
* computed (and work based on view value), whereas formatters are run after the parser (and | ||
* are based on modelValue) | ||
* Automatically formats code while typing. It depends on a preprocessro that smartly | ||
* updates the viewValue and caret position for best UX. | ||
* @example | ||
@@ -59,7 +65,10 @@ * ```js | ||
* } | ||
* ``` | ||
* @param {string} v - the raw value from the <input> after keyUp/Down event | ||
* @returns {string} preprocessedValue: the result of preprocessing for invalid input | ||
* @param {FormatOptions & { prevViewValue: string; currentCaretIndex: number }} opts - the raw value from the <input> after keyUp/Down event | ||
* @returns {{ viewValue:string; caretIndex:number; }|string|undefined} preprocessedValue: the result of preprocessing for invalid input | ||
*/ | ||
preprocessor(v: string): string; | ||
preprocessor( | ||
v: string, | ||
options: FormatOptions & { prevViewValue: string; currentCaretIndex: number }, | ||
): { viewValue: string; caretIndex: number } | string | undefined; | ||
@@ -107,3 +116,3 @@ /** | ||
*/ | ||
formatOptions: FormatNumberOptions; | ||
formatOptions: FormatOptions; | ||
@@ -110,0 +119,0 @@ /** |
@@ -39,2 +39,4 @@ import { LitElement, nothing, TemplateResult, CSSResultArray } from '@lion/core'; | ||
value: string; | ||
selectionStart?: number; | ||
selectionEnd?: number; | ||
} | ||
@@ -41,0 +43,0 @@ |
@@ -107,3 +107,3 @@ import { LitElement } from '@lion/core'; | ||
* - change in the 'validators' array | ||
* - change in the config of an individual Validator | ||
* - change in the config of an individual Validator | ||
* | ||
@@ -110,0 +110,0 @@ * Three situations are handled: |
Sorry, the diff of this file is too big to display
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
1593585
138
43001
83
+ Added@lion/core@0.22.0(transitive)
+ Added@lion/localize@0.24.0(transitive)
- Removed@lion/core@0.21.1(transitive)
- Removed@lion/localize@0.23.0(transitive)
Updated@lion/core@^0.22.0
Updated@lion/localize@^0.24.0