lion/packages/ui/components/form-core/NativeTextFieldMixin.js
2022-10-31 16:55:07 +01:00

149 lines
4.6 KiB
JavaScript

import { dedupeMixin } from '@open-wc/dedupe-mixin';
import { FormControlMixin } from './FormControlMixin.js';
import { FocusMixin } from './FocusMixin.js';
import { FormatMixin } from './FormatMixin.js';
/**
* @typedef {import('../types/NativeTextFieldMixinTypes').NativeTextFieldMixin} NativeTextFieldMixin
* @type {NativeTextFieldMixin}
* @param {import('@open-wc/dedupe-mixin').Constructor<import('@lion/core').LitElement>} superclass} superclass
*/
const NativeTextFieldMixinImplementation = superclass =>
// @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051
class NativeTextFieldMixin extends FormatMixin(FocusMixin(FormControlMixin(superclass))) {
/** @type {any} */
static get properties() {
return {
autocomplete: { type: String, reflect: true },
};
}
constructor() {
super();
/**
* Delegates this property to input/textarea/select.
* @type {string | undefined}
*/
this.autocomplete = undefined;
}
/**
* @protected
* @type {HTMLInputElement | HTMLTextAreaElement}
*/
get _inputNode() {
return /** @type {HTMLInputElement | HTMLTextAreaElement} */ (super._inputNode);
}
/** @type {number} */
get selectionStart() {
const native = this._inputNode;
if (native && native.selectionStart) {
return native.selectionStart;
}
return 0;
}
set selectionStart(value) {
const native = this._inputNode;
if (native && native.selectionStart) {
native.selectionStart = value;
}
}
/** @type {number} */
get selectionEnd() {
const native = this._inputNode;
if (native && native.selectionEnd) {
return native.selectionEnd;
}
return 0;
}
set selectionEnd(value) {
const native = this._inputNode;
if (native && native.selectionEnd) {
native.selectionEnd = value;
}
}
/**
* The view value. Will be delegated to `._inputNode.value`
* @override FormatMixin
*/
get value() {
return (this._inputNode && this._inputNode.value) || this.__value || '';
}
/**
* @param {string} value
* @override FormatMixin - 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._inputNode) {
// Only set if newValue is new, fix for Safari bug: https://github.com/ing-bank/lion/issues/1415
if (this._inputNode.value !== value) {
this._setValueAndPreserveCaret(value);
}
/** @type {string | undefined} */
this.__value = undefined;
} else {
this.__value = value;
}
}
/**
* Restores the cursor to its original position after updating the value.
* @param {string} newValue The value that should be saved.
* @protected
*/
_setValueAndPreserveCaret(newValue) {
// Only preserve caret if focused (changing selectionStart will move focus in Safari)
if (this.focused) {
// Not all elements might have selection, and even if they have the
// right properties, accessing them might throw an exception (like for
// <input type=number>)
try {
// SelectElement doesn't have selectionStart/selectionEnd
if (!(this._inputNode instanceof HTMLSelectElement)) {
const start = this._inputNode.selectionStart;
this._inputNode.value = newValue;
// The cursor automatically jumps to the end after re-setting the value,
// so restore it to its original position.
this._inputNode.selectionStart = start;
this._inputNode.selectionEnd = start;
}
} catch (error) {
// Just set the value and give up on the caret.
this._inputNode.value = newValue;
}
} else {
this._inputNode.value = newValue;
}
}
/**
* @override FormatMixin
*/
_reflectBackFormattedValueToUser() {
super._reflectBackFormattedValueToUser();
if (this._reflectBackOn() && this.focused) {
try {
// try/catch, because Safari is a bit sensitive here
this._inputNode.selectionStart = this._inputNode.value.length;
// eslint-disable-next-line no-empty
} catch (_) {}
}
}
/**
* @configure FocusMixin
*/
get _focusableNode() {
return this._inputNode;
}
};
export const NativeTextFieldMixin = dedupeMixin(NativeTextFieldMixinImplementation);