@vaadin/field-base
Version:
Vaadin field base mixins
233 lines (209 loc) • 6.64 kB
JavaScript
/**
* @license
* Copyright (c) 2021 - 2025 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { dedupeMixin } from '@open-wc/dedupe-mixin';
/**
* A mixin to store the reference to an input element
* and add input and change event listeners to it.
*
* @polymerMixin
*/
export const InputMixin = dedupeMixin(
(superclass) =>
class InputMixinClass extends superclass {
static get properties() {
return {
/**
* A reference to the input element controlled by the mixin.
* Any component implementing this mixin is expected to provide it
* by using `this._setInputElement(input)` Polymer API.
*
* A typical case is using `InputController` that does this automatically.
* However, the input element does not have to always be native <input>:
* as an example, <vaadin-combo-box-light> accepts other components.
*
* @protected
* @type {!HTMLElement}
*/
inputElement: {
type: Object,
readOnly: true,
observer: '_inputElementChanged',
sync: true,
},
/**
* String used to define input type.
* @protected
*/
type: {
type: String,
readOnly: true,
},
/**
* The value of the field.
*/
value: {
type: String,
value: '',
observer: '_valueChanged',
notify: true,
sync: true,
},
};
}
constructor() {
super();
this._boundOnInput = this._onInput.bind(this);
this._boundOnChange = this._onChange.bind(this);
}
/**
* Indicates whether the value is different from the default one.
* Override if the `value` property has a type other than `string`.
*
* @protected
*/
get _hasValue() {
return this.value != null && this.value !== '';
}
/**
* A property for accessing the input element's value.
*
* Override this getter if the property is different from the default `value` one.
*
* @protected
* @return {string}
*/
get _inputElementValueProperty() {
return 'value';
}
/**
* The input element's value.
*
* @protected
* @return {string}
*/
get _inputElementValue() {
return this.inputElement ? this.inputElement[this._inputElementValueProperty] : undefined;
}
/**
* The input element's value.
*
* @protected
*/
set _inputElementValue(value) {
if (this.inputElement) {
this.inputElement[this._inputElementValueProperty] = value;
}
}
/**
* Clear the value of the field.
*/
clear() {
this.value = '';
// Clear the input immediately without waiting for the observer.
// Otherwise, when using Lit, the old value would be restored.
this._inputElementValue = '';
}
/**
* Add event listeners to the input element instance.
* Override this method to add custom listeners.
* @param {!HTMLElement} input
* @protected
*/
_addInputListeners(input) {
input.addEventListener('input', this._boundOnInput);
input.addEventListener('change', this._boundOnChange);
}
/**
* Remove event listeners from the input element instance.
* @param {!HTMLElement} input
* @protected
*/
_removeInputListeners(input) {
input.removeEventListener('input', this._boundOnInput);
input.removeEventListener('change', this._boundOnChange);
}
/**
* A method to forward the value property set on the field
* programmatically back to the input element value.
* Override this method to perform additional checks,
* for example to skip this in certain conditions.
* @param {string} value
* @protected
*/
_forwardInputValue(value) {
// Value might be set before an input element is initialized.
// This case should be handled separately by a component that
// implements this mixin, for example in `connectedCallback`.
if (!this.inputElement) {
return;
}
this._inputElementValue = value != null ? value : '';
}
/**
* @param {HTMLElement | undefined} input
* @param {HTMLElement | undefined} oldInput
* @protected
*/
_inputElementChanged(input, oldInput) {
if (input) {
this._addInputListeners(input);
} else if (oldInput) {
this._removeInputListeners(oldInput);
}
}
/**
* An input event listener used to update the field value.
*
* @param {Event} event
* @protected
*/
_onInput(event) {
// In the case a custom web component is passed as `inputElement`,
// the actual native input element, on which the event occurred,
// can be inside shadow trees.
const target = event.composedPath()[0];
// Ignore fake input events e.g. used by clear button.
this.__userInput = event.isTrusted;
this.value = target.value;
this.__userInput = false;
}
/**
* A change event listener.
* Override this method with an actual implementation.
* @param {Event} _event
* @protected
*/
_onChange(_event) {}
/**
* Toggle the has-value attribute based on the value property.
*
* @param {boolean} hasValue
* @protected
*/
_toggleHasValue(hasValue) {
this.toggleAttribute('has-value', hasValue);
}
/**
* Observer called when a value property changes.
* @param {string | undefined} newVal
* @param {string | undefined} oldVal
* @protected
*/
_valueChanged(newVal, oldVal) {
this._toggleHasValue(this._hasValue);
// Setting initial value to empty string, do nothing.
if (newVal === '' && oldVal === undefined) {
return;
}
// Value is set by the user, no need to sync it back to input.
if (this.__userInput) {
return;
}
// Setting a value programmatically, sync it to input element.
this._forwardInputValue(newVal);
}
},
);