commit
7661ae665e
4 changed files with 170 additions and 134 deletions
|
|
@ -4,29 +4,32 @@ It is designed to work in conjunction with `LionField`.
|
|||
|
||||
## Concepts of different values
|
||||
|
||||
### modelValue
|
||||
The model value is the result of the parser function.
|
||||
It should be considered as the internal value used for validation and reasoning/logic.
|
||||
### model value
|
||||
The model value is the result of the parser function. It will be stored as `.modelValue`
|
||||
and should be considered the internal value used for validation and reasoning/logic.
|
||||
The model value is 'ready for consumption' by the outside world (think of a Date object
|
||||
or a float). The modelValue can(and is recommended to) be used as both input value and
|
||||
output value of the `<lion-field>`
|
||||
or a float). It can(and is recommended to) be used as both input value and
|
||||
output value of the `LionField`.
|
||||
|
||||
Examples:
|
||||
- For a date input: a String '20/01/1999' will be converted to new Date('1999/01/20')
|
||||
- For a number input: a formatted String '1.234,56' will be converted to a Number: 1234.56
|
||||
- For a date input: a String '20/01/1999' will be converted to `new Date('1999/01/20')`
|
||||
- For a number input: a formatted String '1.234,56' will be converted to a Number: `1234.56`
|
||||
|
||||
### formattedValue
|
||||
The view value is the result of the formatter function (when available).
|
||||
The result will be stored in the native inputElement (usually an input[type=text]).
|
||||
### view value
|
||||
The view value is the result of the formatter function.
|
||||
It will be stored as `.formattedValue` and synchronized to `.value` (a viewValue setter that
|
||||
allows to synchronize to `.inputElement`).
|
||||
Synchronization happens conditionally and is (by default) the result of a blur. Other conditions
|
||||
(like error state/validity and whether the a model value was set programatically) also play a role.
|
||||
|
||||
Examples:
|
||||
- For a date input, this would be '20/01/1999' (dependent on locale).
|
||||
- For a number input, this could be '1,234.56' (a String representation of modelValue
|
||||
1234.56)
|
||||
|
||||
### serializedValue
|
||||
The serialized version of the model value.
|
||||
This value exists for maximal compatibility with the platform API.
|
||||
### serialized value
|
||||
This is the serialized version of the model value.
|
||||
It exists for maximal compatibility with the platform API.
|
||||
The serialized value can be an interface in context where data binding is not supported
|
||||
and a serialized string needs to be set.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,48 @@ import { dedupeMixin } from '@lion/core';
|
|||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
||||
import { Unparseable } from '@lion/validate';
|
||||
|
||||
// For a future breaking release:
|
||||
// - do not allow the private `.formattedValue` as property that can be set to
|
||||
// trigger a computation loop.
|
||||
// - do not fire events for those private and protected concepts
|
||||
// - simplify _calculateValues: recursive trigger lock can be omitted, since need for connecting
|
||||
// the loop via sync observers is not needed anymore.
|
||||
// - consider `formatOn` as an overridable function, by default something like:
|
||||
// `(!__isHandlingUserInput || !errorState) && !focused`
|
||||
// This would allow for more advanced scenarios, like formatting an input whenever it becomes valid.
|
||||
// This would make formattedValue as a concept obsolete, since for maximum flexibility, the
|
||||
// formattedValue condition needs to be evaluated right before syncing back to the view
|
||||
|
||||
/**
|
||||
* @desc Designed to be applied on top of a LionField
|
||||
* @desc Designed to be applied on top of a LionField.
|
||||
* To understand all concepts within the Mixin, please consult the flow diagram in the
|
||||
* documentation.
|
||||
*
|
||||
* ## Flows
|
||||
* FormatMixin supports these two main flows:
|
||||
* 1) Application Developer sets `.modelValue`:
|
||||
* Flow: `.modelValue` -> `.formattedValue` -> `.inputElement.value`
|
||||
* -> `.serializedValue`
|
||||
* 2) End user interacts with field:
|
||||
* Flow: `@user-input-changed` -> `.modelValue` -> `.formattedValue` - (debounce till reflect condition (formatOn) is met) -> `.inputElement.value`
|
||||
* -> `.serializedValue`
|
||||
* [1] Application Developer sets `.modelValue`:
|
||||
* Flow: `.modelValue` (formatter) -> `.formattedValue` -> `.inputElement.value`
|
||||
* (serializer) -> `.serializedValue`
|
||||
* [2] End user interacts with field:
|
||||
* Flow: `@user-input-changed` (parser) -> `.modelValue` (formatter) -> `.formattedValue` - (debounce till reflect condition (formatOn) is met) -> `.inputElement.value`
|
||||
* (serializer) -> `.serializedValue`
|
||||
*
|
||||
* @mixinFunction
|
||||
* For backwards compatibility with the platform, we also support `.value` as an api. In that case
|
||||
* the flow will be like [2], without the debounce.
|
||||
*
|
||||
* ## Difference between value, viewValue and formattedValue
|
||||
* A viewValue is a concept rather than a property. To be compatible with the platform api, the
|
||||
* property for the concept of viewValue is thus called `.value`.
|
||||
* When reading code and docs, one should be aware that the term viewValue is mostly used, but the
|
||||
* terms can be used interchangeably.
|
||||
* The `.formattedValue` should be seen as the 'scheduled' viewValue. It is computed realtime and
|
||||
* stores the output of formatter. It will replace viewValue. once condition `formatOn` is met.
|
||||
* Another difference is that formattedValue lives on `LionField`, whereas viewValue is shared
|
||||
* across `LionField` and `.inputElement`.
|
||||
*
|
||||
* For restoring serialized values fetched from a server, we could consider one extra flow:
|
||||
* [3] Application Developer sets `.serializedValue`:
|
||||
* Flow: serializedValue (deserializer) -> `.modelValue` (formatter) -> `.formattedValue` -> `.inputElement.value`
|
||||
*/
|
||||
export const FormatMixin = dedupeMixin(
|
||||
superclass =>
|
||||
|
|
@ -28,9 +58,9 @@ export const FormatMixin = dedupeMixin(
|
|||
/**
|
||||
* The model value is the result of the parser function(when available).
|
||||
* It should be considered as the internal value used for validation and reasoning/logic.
|
||||
* The model value is 'ready for consumption' by the outside world (think of a Date object
|
||||
* or a float). The modelValue can(and is recommended to) be used as both input value and
|
||||
* output value of the <lion-field>
|
||||
* The model value is 'ready for consumption' by the outside world (think of a Date
|
||||
* object or a float). The modelValue can(and is recommended to) be used as both input
|
||||
* value and output value of the `LionField`.
|
||||
*
|
||||
* Examples:
|
||||
* - For a date input: a String '20/01/1999' will be converted to new Date('1999/01/20')
|
||||
|
|
@ -49,6 +79,8 @@ export const FormatMixin = dedupeMixin(
|
|||
* - For a date input, this would be '20/01/1999' (dependent on locale).
|
||||
* - For a number input, this could be '1,234.56' (a String representation of modelValue
|
||||
* 1234.56)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
formattedValue: {
|
||||
type: String,
|
||||
|
|
@ -67,6 +99,7 @@ export const FormatMixin = dedupeMixin(
|
|||
*
|
||||
* When no parser is available, the value is usually the same as the formattedValue
|
||||
* (being inputElement.value)
|
||||
*
|
||||
*/
|
||||
serializedValue: {
|
||||
type: String,
|
||||
|
|
@ -99,11 +132,6 @@ export const FormatMixin = dedupeMixin(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* === Formatting and parsing ====
|
||||
* To understand all concepts below, please consult the flow diagrams in the documentation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts formattedValue to modelValue
|
||||
* For instance, a localized date to a Date Object
|
||||
|
|
@ -115,10 +143,11 @@ export const FormatMixin = dedupeMixin(
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts modelValue to formattedValue (formattedValue will be synced with <input>.value)
|
||||
* For instance, a Date object to a localized date
|
||||
* @param {Object} value - modelValue: can be an Object, Number, String depending on the input
|
||||
* type(date, number, email etc)
|
||||
* Converts modelValue to formattedValue (formattedValue will be synced with
|
||||
* `.inputElement.value`)
|
||||
* For instance, a Date object to a localized date.
|
||||
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
|
||||
* input type(date, number, email etc)
|
||||
* @returns {String} formattedValue
|
||||
*/
|
||||
formatter(v) {
|
||||
|
|
@ -126,10 +155,10 @@ export const FormatMixin = dedupeMixin(
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts modelValue to serializedValue (<lion-field>.value).
|
||||
* Converts `.modelValue` to `.serializedValue`
|
||||
* For instance, a Date object to an iso formatted date string
|
||||
* @param {Object} value - modelValue: can be an Object, Number, String depending on the input
|
||||
* type(date, number, email etc)
|
||||
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
|
||||
* input type(date, number, email etc)
|
||||
* @returns {String} serializedValue
|
||||
*/
|
||||
serializer(v) {
|
||||
|
|
@ -137,10 +166,10 @@ export const FormatMixin = dedupeMixin(
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts <lion-field>.value to modelValue
|
||||
* Converts `LionField.value` to `.modelValue`
|
||||
* For instance, an iso formatted date string to a Date object
|
||||
* @param {Object} value - modelValue: can be an Object, Number, String depending on the input
|
||||
* type(date, number, email etc)
|
||||
* @param {Object} value - modelValue: can be an Object, Number, String depending on the
|
||||
* input type(date, number, email etc)
|
||||
* @returns {Object} modelValue
|
||||
*/
|
||||
deserializer(v) {
|
||||
|
|
@ -149,9 +178,8 @@ export const FormatMixin = dedupeMixin(
|
|||
|
||||
/**
|
||||
* Responsible for storing all representations(modelValue, serializedValue, formattedValue
|
||||
* and value) of the input value.
|
||||
* Prevents infinite loops, so all value observers can be treated like they will only be
|
||||
* called once, without indirectly calling other observers.
|
||||
* and value) of the input value. Prevents infinite loops, so all value observers can be
|
||||
* treated like they will only be called once, without indirectly calling other observers.
|
||||
* (in fact, some are called twice, but the __preventRecursiveTrigger lock prevents the
|
||||
* second call from having effect).
|
||||
*
|
||||
|
|
@ -189,7 +217,7 @@ export const FormatMixin = dedupeMixin(
|
|||
// 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)
|
||||
// TODO: In a breaking refactor of the Validation System, this behaviot can be corrected.
|
||||
// TODO: In a breaking refactor of the Validation System, this behavior can be corrected.
|
||||
return '';
|
||||
}
|
||||
|
||||
|
|
@ -197,8 +225,8 @@ export const FormatMixin = dedupeMixin(
|
|||
// inputElement.value was not available yet
|
||||
if (typeof value !== 'string') {
|
||||
// This means there is nothing to find inside the view that can be of
|
||||
// interest to the Application Developer or needed to store for future form state
|
||||
// retrieval.
|
||||
// interest to the Application Developer or needed to store for future
|
||||
// form state retrieval.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
|
@ -215,24 +243,28 @@ export const FormatMixin = dedupeMixin(
|
|||
}
|
||||
|
||||
__callFormatter() {
|
||||
if (this.modelValue instanceof Unparseable) {
|
||||
return this.modelValue.viewValue;
|
||||
}
|
||||
|
||||
// - Why check for this.errorState?
|
||||
// We only want to format values that are considered valid. For best UX,
|
||||
// we only 'reward' valid inputs.
|
||||
// - Why check for __isHandlingUserInput?
|
||||
// Downwards sync is prevented whenever we are in a `@user-input-changed` flow.
|
||||
// If we are in a 'imperatively set `.modelValue`' flow, we want to reflect back
|
||||
// Downwards sync is prevented whenever we are in an `@user-input-changed` flow, [2].
|
||||
// If we are in a 'imperatively set `.modelValue`' flow, [1], we want to reflect back
|
||||
// the value, no matter what.
|
||||
// This means, whenever we are in errorState, we and modelValue is set
|
||||
// This means, whenever we are in errorState and modelValue is set
|
||||
// imperatively, we DO want to format a value (it is the only way to get meaningful
|
||||
// input into `.inputElement` with modelValue as input)
|
||||
|
||||
if (this.__isHandlingUserInput && this.errorState && this.inputElement) {
|
||||
return this.inputElement ? this.value : undefined;
|
||||
}
|
||||
|
||||
if (this.modelValue instanceof Unparseable) {
|
||||
// When the modelValue currently is unparseable, we need to sync back the supplied
|
||||
// viewValue. In flow [2], this should not be needed.
|
||||
// In flow [1] (we restore a previously stored modelValue) we should sync down, however.
|
||||
return this.modelValue.viewValue;
|
||||
}
|
||||
|
||||
return this.formatter(this.modelValue, this.formatOptions);
|
||||
}
|
||||
|
||||
|
|
@ -242,19 +274,19 @@ export const FormatMixin = dedupeMixin(
|
|||
this._dispatchModelValueChangedEvent(...args);
|
||||
}
|
||||
|
||||
// TODO: investigate if this also can be solved by using 'hasChanged' on property accessor
|
||||
// inside choiceInputs
|
||||
/**
|
||||
* This is wrapped in a distinct method, so that parents can control when the changed event is
|
||||
* fired. For instance: when modelValue is an object, a deep comparison is needed first
|
||||
* This is wrapped in a distinct method, so that parents can control when the changed event
|
||||
* is fired. For objects, a deep comparison might be needed.
|
||||
*/
|
||||
_dispatchModelValueChangedEvent() {
|
||||
/** @event model-value-changed */
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('model-value-changed', { bubbles: true, composed: true }),
|
||||
);
|
||||
}
|
||||
|
||||
_onFormattedValueChanged() {
|
||||
/** @deprecated */
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('formatted-value-changed', {
|
||||
bubbles: true,
|
||||
|
|
@ -265,6 +297,7 @@ export const FormatMixin = dedupeMixin(
|
|||
}
|
||||
|
||||
_onSerializedValueChanged() {
|
||||
/** @deprecated */
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('serialized-value-changed', {
|
||||
bubbles: true,
|
||||
|
|
@ -275,33 +308,32 @@ export const FormatMixin = dedupeMixin(
|
|||
}
|
||||
|
||||
/**
|
||||
* Synchronization from <input>.value to <lion-field>.formattedValue
|
||||
* Synchronization from `.inputElement.value` to `LionField` (flow [2])
|
||||
*/
|
||||
_syncValueUpwards() {
|
||||
// Downwards syncing should only happen for <lion-field>.value changes from 'above'
|
||||
// Downwards syncing should only happen for `LionField`.value changes from 'above'
|
||||
// This triggers _onModelValueChanged and connects user input to the
|
||||
// parsing/formatting/serializing loop
|
||||
this.modelValue = this.__callParser(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronization from <lion-field>.value to <input>.value
|
||||
* Synchronization from `LionField.value` to `.inputElement.value`
|
||||
* - flow [1] will always be reflected back
|
||||
* - flow [2] will not be reflected back when this flow was triggered via
|
||||
* `@user-input-changed` (this will happen later, when `formatOn` condition is met)
|
||||
*/
|
||||
_reflectBackFormattedValueToUser() {
|
||||
// Downwards syncing 'back and forth' prevents change event from being fired in IE.
|
||||
// So only sync when the source of new <lion-field>.value change was not the 'input' event
|
||||
// of inputElement
|
||||
if (!this.__isHandlingUserInput) {
|
||||
// Text 'undefined' should not end up in <input>
|
||||
this.value = typeof this.formattedValue !== 'undefined' ? this.formattedValue : '';
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: rename to __dispatchNormalizedInputEvent?
|
||||
// This can be called whenever the view value should be updated. Dependent on component type
|
||||
// ("input" for <input> or "change" for <select>(mainly for IE)) a different event should be used
|
||||
// as "source" for the "user-input-changed" event (which can be seen as an abstraction layer on
|
||||
// top of other events (input, change, whatever))
|
||||
// ("input" for <input> or "change" for <select>(mainly for IE)) a different event should be
|
||||
// used as source for the "user-input-changed" event (which can be seen as an abstraction
|
||||
// layer on top of other events (input, change, whatever))
|
||||
_proxyInputEvent() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('user-input-changed', {
|
||||
|
|
@ -313,8 +345,7 @@ export const FormatMixin = dedupeMixin(
|
|||
|
||||
_onUserInputChanged() {
|
||||
// Upwards syncing. Most properties are delegated right away, value is synced to
|
||||
// <lion-field>, to be able to act on (imperatively set) value changes
|
||||
|
||||
// `LionField`, to be able to act on (imperatively set) value changes
|
||||
this.__isHandlingUserInput = true;
|
||||
this._syncValueUpwards();
|
||||
this.__isHandlingUserInput = false;
|
||||
|
|
@ -338,9 +369,9 @@ export const FormatMixin = dedupeMixin(
|
|||
this.addEventListener('user-input-changed', this._onUserInputChanged);
|
||||
// Connect the value found in <input> to the formatting/parsing/serializing loop as a
|
||||
// fallback mechanism. Assume the user uses the value property of the
|
||||
// <lion-field>(recommended api) as the api (this is a downwards sync).
|
||||
// However, when no value is specified on <lion-field>, have support for sync of the real
|
||||
// input to the <lion-field> (upwards sync).
|
||||
// `LionField`(recommended api) as the api (this is a downwards sync).
|
||||
// However, when no value is specified on `LionField`, have support for sync of the real
|
||||
// input to the `LionField` (upwards sync).
|
||||
if (typeof this.modelValue === 'undefined') {
|
||||
this._syncValueUpwards();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,39 +1,43 @@
|
|||
import { DelegateMixin, SlotMixin } from '@lion/core';
|
||||
import { LionLitElement } from '@lion/core/src/LionLitElement.js';
|
||||
import { DelegateMixin, SlotMixin, LitElement } from '@lion/core';
|
||||
import { ElementMixin } from '@lion/core/src/ElementMixin.js';
|
||||
import { CssClassMixin } from '@lion/core/src/CssClassMixin.js';
|
||||
import { ObserverMixin } from '@lion/core/src/ObserverMixin.js';
|
||||
import { ValidateMixin } from '@lion/validate';
|
||||
|
||||
import { FormControlMixin } from './FormControlMixin.js';
|
||||
import { InteractionStateMixin } from './InteractionStateMixin.js'; // applies FocusMixin
|
||||
import { FormatMixin } from './FormatMixin.js';
|
||||
import { FocusMixin } from './FocusMixin.js';
|
||||
|
||||
/* eslint-disable wc/guard-super-call */
|
||||
|
||||
// TODO:
|
||||
// - Consider exporting as FieldMixin
|
||||
// - Add submitted prop to InteractionStateMixin
|
||||
// - Find a better way to do value delegation via attr
|
||||
|
||||
/**
|
||||
* LionField: wraps components input, textarea and select and potentially others
|
||||
* (checkbox group, radio group)
|
||||
* `LionField`: wraps <input>, <textarea>, <select> and other interactable elements.
|
||||
* Also it would follow a nice hierarchy: lion-form -> lion-fieldset -> lion-field
|
||||
*
|
||||
* Note: We don't support placeholders, because we have a helper text and
|
||||
* placeholders confuse the user with accessibility needs.
|
||||
*
|
||||
* Please see the docs for in depth information.
|
||||
*
|
||||
* @example
|
||||
* <lion-field name="myName">
|
||||
* <label slot="label">My Input</label>
|
||||
* <input type="text" slot="input">
|
||||
* </lion-field>
|
||||
*
|
||||
* Note: We do not support placeholders, because we have a helper text and
|
||||
* placeholders confuse the user with accessibility needs.
|
||||
*
|
||||
* @customElement
|
||||
*/
|
||||
|
||||
// TODO: Consider exporting as FieldMixin
|
||||
// eslint-disable-next-line max-len, no-unused-vars
|
||||
export class LionField extends FormControlMixin(
|
||||
InteractionStateMixin(
|
||||
FocusMixin(
|
||||
FormatMixin(
|
||||
ValidateMixin(
|
||||
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LionLitElement))))),
|
||||
CssClassMixin(ElementMixin(DelegateMixin(SlotMixin(ObserverMixin(LitElement))))),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -65,14 +69,7 @@ export class LionField extends FormControlMixin(
|
|||
};
|
||||
}
|
||||
|
||||
static get asyncObservers() {
|
||||
return {
|
||||
...super.asyncObservers,
|
||||
_setDisabledClass: ['disabled'],
|
||||
};
|
||||
}
|
||||
|
||||
// We don't delegate, because we want to 'preprocess' via _setValueAndPreserveCaret
|
||||
// We don't delegate, because we want to preserve caret position via _setValueAndPreserveCaret
|
||||
set value(value) {
|
||||
// if not yet connected to dom can't change the value
|
||||
if (this.inputElement) {
|
||||
|
|
@ -85,29 +82,26 @@ export class LionField extends FormControlMixin(
|
|||
return (this.inputElement && this.inputElement.value) || '';
|
||||
}
|
||||
|
||||
_setDisabledClass() {
|
||||
this.classList[this.disabled ? 'add' : 'remove']('state-disabled');
|
||||
static get asyncObservers() {
|
||||
return {
|
||||
...super.asyncObservers,
|
||||
_setDisabledClass: ['disabled'],
|
||||
};
|
||||
}
|
||||
|
||||
resetInteractionState() {
|
||||
if (super.resetInteractionState) super.resetInteractionState();
|
||||
// TODO: add submitted prop to InteractionStateMixin ?
|
||||
this.submitted = false;
|
||||
}
|
||||
|
||||
/* * * * * * * *
|
||||
Lifecycle */
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
this._onChange = this._onChange.bind(this);
|
||||
this.inputElement.addEventListener('change', this._onChange);
|
||||
this._delegateInitialValueAttr(); // TODO: find a better way to do this
|
||||
this._delegateInitialValueAttr();
|
||||
this._setDisabledClass();
|
||||
this.classList.add('form-field');
|
||||
this.classList.add('form-field'); // eslint-disable-line
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
|
||||
if (this.__parentFormGroup) {
|
||||
const event = new CustomEvent('form-element-unregister', {
|
||||
detail: { element: this },
|
||||
|
|
@ -118,6 +112,10 @@ export class LionField extends FormControlMixin(
|
|||
this.inputElement.removeEventListener('change', this._onChange);
|
||||
}
|
||||
|
||||
_setDisabledClass() {
|
||||
this.classList[this.disabled ? 'add' : 'remove']('state-disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not done via 'get delegations', because this.inputElement.setAttribute('value')
|
||||
* does not trigger a value change
|
||||
|
|
@ -129,33 +127,37 @@ export class LionField extends FormControlMixin(
|
|||
}
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
Public Methods (also notice delegated methods that are available on host) */
|
||||
resetInteractionState() {
|
||||
if (super.resetInteractionState) {
|
||||
super.resetInteractionState();
|
||||
}
|
||||
this.submitted = false;
|
||||
}
|
||||
|
||||
clear() {
|
||||
// Let validationMixin and interactionStateMixin clear their invalid and dirty/touched states
|
||||
// respectively
|
||||
if (super.clear) super.clear();
|
||||
if (super.clear) {
|
||||
// Let validationMixin and interactionStateMixin clear their
|
||||
// invalid and dirty/touched states respectively
|
||||
super.clear();
|
||||
}
|
||||
this.value = ''; // can't set null here, because IE11 treats it as a string
|
||||
}
|
||||
|
||||
/* * * * * * * * * *
|
||||
Event Handlers */
|
||||
|
||||
_onChange() {
|
||||
if (super._onChange) super._onChange();
|
||||
if (super._onChange) {
|
||||
super._onChange();
|
||||
}
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('user-input-changed', {
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
this.modelValue = this.parser(this.value);
|
||||
}
|
||||
|
||||
/* * * * * * * * * * * *
|
||||
Observer Handlers */
|
||||
_onValueChanged({ value }) {
|
||||
if (super._onValueChanged) super._onValueChanged();
|
||||
if (super._onValueChanged) {
|
||||
super._onValueChanged();
|
||||
}
|
||||
// For styling purposes, make it known the input field is not empty
|
||||
this.classList[value ? 'add' : 'remove']('state-filled');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
unsafeStatic,
|
||||
triggerFocusFor,
|
||||
triggerBlurFor,
|
||||
aTimeout,
|
||||
} from '@open-wc/testing';
|
||||
import { unsafeHTML } from '@lion/core';
|
||||
import sinon from 'sinon';
|
||||
|
|
@ -152,25 +153,24 @@ describe('<lion-field>', () => {
|
|||
expect(lionField.inputElement.selectionEnd).to.equal(2);
|
||||
});
|
||||
|
||||
// TODO: add pointerEvents test
|
||||
// TODO: why is this a describe?
|
||||
describe(`<lion-field> with <input disabled>${nameSuffix}`, () => {
|
||||
it('has a class "state-disabled"', async () => {
|
||||
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
|
||||
expect(lionField.classList.contains('state-disabled')).to.equal(false);
|
||||
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(false);
|
||||
// TODO: add pointerEvents test for disabled
|
||||
it('has a class "state-disabled"', async () => {
|
||||
const lionField = await fixture(`<${tagString}>${inputSlotString}</${tagString}>`);
|
||||
expect(lionField.classList.contains('state-disabled')).to.equal(false);
|
||||
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(false);
|
||||
|
||||
lionField.disabled = true;
|
||||
await lionField.updateComplete;
|
||||
expect(lionField.classList.contains('state-disabled')).to.equal(true);
|
||||
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
||||
lionField.disabled = true;
|
||||
await lionField.updateComplete;
|
||||
await aTimeout();
|
||||
|
||||
const disabledlionField = await fixture(
|
||||
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
|
||||
);
|
||||
expect(disabledlionField.classList.contains('state-disabled')).to.equal(true);
|
||||
expect(disabledlionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
||||
});
|
||||
expect(lionField.classList.contains('state-disabled')).to.equal(true);
|
||||
expect(lionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
||||
|
||||
const disabledlionField = await fixture(
|
||||
`<${tagString} disabled>${inputSlotString}</${tagString}>`,
|
||||
);
|
||||
expect(disabledlionField.classList.contains('state-disabled')).to.equal(true);
|
||||
expect(disabledlionField.inputElement.hasAttribute('disabled')).to.equal(true);
|
||||
});
|
||||
|
||||
describe(`A11y${nameSuffix}`, () => {
|
||||
|
|
@ -355,7 +355,7 @@ describe('<lion-field>', () => {
|
|||
|
||||
mimicUserInput(lionField, 'foo');
|
||||
expect(formatterSpy.callCount).to.equal(1);
|
||||
expect(lionField.formattedValue).to.equal('foo');
|
||||
expect(lionField.value).to.equal('foo');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue