UNPKG

@siberiaweb/components

Version:
647 lines (646 loc) 21.2 kB
import CSS from "./CSS"; import Icon from "../icon/Icon"; import WebComponent from "@siberiaweb/webcomponent/lib/WebComponent"; import "./Input.css"; /** * Поле ввода. */ export default class Input extends WebComponent { /** * Конструктор. */ constructor() { super(); /** * Максимальная длина вводимого значения. */ this._maxLength = Input.DEFAULT_MAX_LENGTH; /** * Маска. */ this.mask = null; /** * Признак необходимости пропуска обработки изменения атрибута "tabindex". */ this.skipAttrTabIndexChange = false; this.control = this.createControl(); this.labelElement = this.createLabel(); this.inlineInput = this.createInlineInput(); this.controlContainer = this.createControlContainer(); this.leadingIconContainer = this.createLeadingIconContainer(); this.trailingIconContainer = this.createTrailingIconContainer(); this.helperContainer = this.createHelperContainer(); this.controlContainer.appendChild(this.control); this.inlineInput.appendChild(this.leadingIconContainer); this.inlineInput.appendChild(this.labelElement); this.inlineInput.appendChild(this.controlContainer); this.inlineInput.appendChild(this.trailingIconContainer); this.lightDOMFragment.appendChild(this.inlineInput); this.lightDOMFragment.appendChild(this.helperContainer); } /** * Наблюдаемые атрибуты. */ static get observedAttributes() { return WebComponent.observedAttributes.concat([ Input.ATTR_AUTOCOMPLETE, Input.ATTR_AUTOFOCUS, Input.ATTR_DISABLED, Input.ATTR_HELPER_TEXT, Input.ATTR_LABEL, Input.ATTR_MAX_LENGTH, Input.ATTR_NAME, Input.ATTR_READ_ONLY, Input.ATTR_REQUIRED, Input.ATTR_SPELLCHECK, Input.ATTR_TAB_INDEX, Input.ATTR_VALUE ]); } /** * Создание встроенного ввода. */ createInlineInput() { let container = document.createElement("div"); container.classList.add(CSS.INLINE_INPUT); return container; } /** * Создание контейнера поля ввода. */ createControlContainer() { let container = document.createElement("div"); container.classList.add(CSS.CONTROL_CONTAINER); return container; } /** * Создание контейнера значков, располагающихся слева от поля ввода. */ createLeadingIconContainer() { let container = document.createElement("div"); container.classList.add(CSS.ICONS_CONTAINER, CSS.LEADING_ICONS_CONTAINER); return container; } /** * Создание контейнера значков, располагающихся справа от поля ввода. */ createTrailingIconContainer() { let container = document.createElement("div"); container.classList.add(CSS.ICONS_CONTAINER, CSS.TRAILING_ICONS_CONTAINER); return container; } /** * Создание вспомогательного контейнера. */ createHelperContainer() { let container = document.createElement("div"); container.classList.add(CSS.HELPER_CONTAINER); return container; } /** * Создание элемента управления. */ createControl() { let control = document.createElement("input"); control.autocomplete = Input.DEFAULT_AUTOCOMPLETE; control.spellcheck = false; control.type = "text"; control.addEventListener("focus", () => { this.toggleAttribute(Input.ATTR_FOCUSED, true); }); control.addEventListener("blur", () => { this.toggleAttribute(Input.ATTR_FOCUSED, false); }); let lastKeydown = null; control.addEventListener("keydown", (event) => { lastKeydown = event.key; }); let savedValue = control.value; control.addEventListener("input", () => { if (this.mask !== null) { let selectionStart = control.selectionStart; let selectionEnd = control.selectionEnd; let caretAtEnd = control.value.length === control.selectionStart; control.value = this.mask.getMaskedValue(control.value); control.selectionStart = selectionStart; control.selectionEnd = selectionEnd; if (savedValue === control.value) { if (lastKeydown === "Delete") { control.selectionStart = selectionStart === null ? null : ++selectionStart; control.selectionEnd = selectionEnd === null ? null : ++selectionEnd; } } else if (caretAtEnd) { control.selectionStart = control.value.length; control.selectionEnd = control.value.length; } savedValue = control.value; } this.updateInputState(); }); control.addEventListener("animationstart", (event) => { switch (event.animationName) { case CSS.AUTOFILL_START: this.toggleAttribute(Input.ATTR_AUTOFILLED, true); break; case CSS.AUTOFILL_END: this.toggleAttribute(Input.ATTR_AUTOFILLED, false); break; } }); new MutationObserver(() => { this.updateInputState(); }) .observe(control, { attributeFilter: ["placeholder"], attributes: true }); return control; } /** * Создание метки. */ createLabel() { return document.createElement("label"); } /** * Добавление значка. * * @param container Контейнер значков. * @param icon Значок. * @param first Добавить значок первым. */ addIcon(container, icon, first) { if (container.contains(icon)) { return icon; } if ((container.children.length === 0) || !first) { container.appendChild(icon); } else { container.insertBefore(icon, container.children[0]); } icon.addEventListener("mousedown", (event) => { // Элемент управления не теряет фокус при нажатии на значок. event.preventDefault(); }); return icon; } /** * Обновление состояния ввода. */ updateInputState() { if (this.isEmpty()) { this.toggleAttribute(Input.ATTR_TEXT_ENTERED, this.control.placeholder.length !== 0); } else { this.toggleAttribute(Input.ATTR_TEXT_ENTERED, true); } } /** * @override */ firstConnectedCallback() { super.firstConnectedCallback(); this.classList.add(CSS.INPUT); } /** * Обработка изменения атрибута "autocomplete". */ attrAutocompleteChange() { this.control.autocomplete = this.autocomplete; } /** * Обработка изменения атрибута "autofocus". */ attrAutofocusChange() { if (this.hasAttribute(Input.ATTR_AUTOFOCUS)) { window.requestAnimationFrame(() => { this.control.focus(); }); } } /** * Обработка изменения атрибута "disabled". */ attrDisabledChange() { this.control.disabled = this.disabled; } /** * Обработка изменения атрибута "helper-text. */ attrHelperTextChange() { this.helperContainer.classList.remove(CSS.ERROR_MESSAGE); this.helperContainer.innerText = this.helperText; } /** * Обработка изменения атрибута "label-text". */ attrLabelTextChange() { this.labelElement.innerText = this.label; } /** * Обработка изменения атрибута "maxlength". * * @param newValue Значение. */ attrMaxLengthChange(newValue) { let value = newValue === null ? Input.DEFAULT_MAX_LENGTH : parseInt(newValue); if (isNaN(value) || (value < 0) || (value > Input.DEFAULT_MAX_LENGTH)) { value = Input.DEFAULT_MAX_LENGTH; } this._maxLength = value; this.control.maxLength = this._maxLength; } /** * Обработка изменения атрибута "name". */ attrNameChange() { this.control.name = this.name; } /** * Обработка изменения атрибута "readonly". */ attrReadOnlyChange() { this.control.readOnly = this.readOnly; } /** * Обработка изменения атрибута "required". */ attrRequiredChange() { this.control.required = this.required; } /** * Обработка изменения атрибута "spellcheck". */ attrSpellcheckChange() { this.control.spellcheck = this.spellcheck; } /** * Обработка изменения атрибута "tabindex". * * @param newValue Новое значение. */ attrTabIndexChange(newValue) { if (this.skipAttrTabIndexChange) { this.skipAttrTabIndexChange = false; return; } if (newValue === null) { this.control.removeAttribute("tabindex"); } else { let value = parseInt(newValue); if (isNaN(value)) { value = 0; } this.control.tabIndex = value; } this.skipAttrTabIndexChange = true; this.tabIndex = -1; } /** * Обработка изменения атрибута "value". * * @param newValue Новое значение. */ attrValueChange(newValue) { this.control.value = newValue === null ? "" : newValue; this.updateInputState(); } /** * @override */ attributeChangedCallback(name, oldValue, newValue) { super.attributeChangedCallback(name, oldValue, newValue); switch (name) { case Input.ATTR_AUTOCOMPLETE: this.attrAutocompleteChange(); break; case Input.ATTR_AUTOFOCUS: this.attrAutofocusChange(); break; case Input.ATTR_DISABLED: this.attrDisabledChange(); break; case Input.ATTR_HELPER_TEXT: this.attrHelperTextChange(); break; case Input.ATTR_LABEL: this.attrLabelTextChange(); break; case Input.ATTR_MAX_LENGTH: this.attrMaxLengthChange(newValue); break; case Input.ATTR_NAME: this.attrNameChange(); break; case Input.ATTR_READ_ONLY: this.attrReadOnlyChange(); break; case Input.ATTR_REQUIRED: this.attrRequiredChange(); break; case Input.ATTR_SPELLCHECK: this.attrSpellcheckChange(); break; case Input.ATTR_TAB_INDEX: this.attrTabIndexChange(newValue); break; case Input.ATTR_VALUE: this.attrValueChange(newValue); break; } } /** * Получение встроенного поля ввода. */ getInlineInput() { return this.inlineInput; } /** * Добавление значка слева от поля ввода. * * @param icon Значок. * @param first Добавить значок первым. Опционально. По умолчанию false. */ addLeadingIcon(icon, first = false) { return this.addIcon(this.leadingIconContainer, icon, first); } /** * Добавление значка справа от поля ввода. * * @param icon Значок. * @param first Добавить значок первым. Опционально. По умолчанию false. */ addTrailingIcon(icon, first = false) { return this.addIcon(this.trailingIconContainer, icon, first); } /** * Вывод сообщения об ошибке. * * @param message Сообщение. * @param hideOnInput Скрыть сообщение при вводе. Опционально. По умолчанию true. */ showErrorMessage(message, hideOnInput = true) { this.helperContainer.classList.add(CSS.ERROR_MESSAGE); this.helperContainer.innerText = message; if (hideOnInput) { this.control.addEventListener("input", () => { this.hideErrorMessage(); }, { once: true }); } } /** * Скрытие сообщения об ошибке. */ hideErrorMessage() { this.helperText = this.helperText; } /** * Получение элемента управления. */ getControl() { return this.control; } /** * @override */ focus(options) { this.control.focus(options); } /** * @override */ blur() { this.control.blur(); } /** * Установка маски. * * @param value Значение. */ setMask(value) { this.mask = value; this.control.maxLength = this.mask.getMaxLength(); } /** * Получение чистого значения. * * @return Метод возвращает чистое значение, если для поля ввода установлена маска, иначе просто значение. */ getCleanValue() { return this.mask === null ? this.value : this.mask.getCleanValue(this.value); } /** * Получение признака автоматического заполнения. */ get autocomplete() { return this.getAttributeOrDefault(Input.ATTR_AUTOCOMPLETE, Input.DEFAULT_AUTOCOMPLETE); } /** * Установка признака автоматического заполнения. * * @param value Значение. */ set autocomplete(value) { this.setAttribute(Input.ATTR_AUTOCOMPLETE, value); } /** * Получение признака, что элемент отключен. */ get disabled() { return this.hasAttribute(Input.ATTR_DISABLED); } /** * Установка признака, что элемент отключен. * * @param value Значение. */ set disabled(value) { this.toggleAttribute(Input.ATTR_DISABLED, value); } /** * Получение вспомогательного текста. */ get helperText() { return this.getAttributeOrDefault(Input.ATTR_HELPER_TEXT, ""); } /** * Установка вспомогательного текста. * * @param value Значение. */ set helperText(value) { this.setAttribute(Input.ATTR_HELPER_TEXT, value); } /** * Получение метки. */ get label() { return this.getAttributeOrDefault(Input.ATTR_LABEL, ""); } /** * Установка метки. * * @param value Значение. */ set label(value) { this.setAttribute(Input.ATTR_LABEL, value); } /** * Получение максимальной длины вводимого значения. */ get maxLength() { return this._maxLength; } /** * Установка максимальной длины вводимого значения. */ set maxLength(value) { this.setAttribute(Input.ATTR_MAX_LENGTH, value.toString()); } /** * Получение уникального имени элемента формы. */ get name() { return this.getAttributeOrDefault(Input.ATTR_NAME, ""); } /** * Установка уникального имени элемента формы. * * @param value Значение. */ set name(value) { this.setAttribute(Input.ATTR_NAME, value); } /** * Получение признака только для чтения. */ get readOnly() { return this.hasAttribute(Input.ATTR_READ_ONLY); } /** * Установка признака только для чтения. * * @param value Значение. */ set readOnly(value) { this.toggleAttribute(Input.ATTR_READ_ONLY, value); } /** * Получение признака, что значение обязательно для заполнения. */ get required() { return this.hasAttribute(Input.ATTR_REQUIRED); } /** * Установка признака, что значение обязательно для заполнения. * * @param value Значение. */ set required(value) { this.toggleAttribute(Input.ATTR_REQUIRED, value); } /** * Получение значения. */ get value() { return this.control.value; } /** * Установка значения. * * @param value Значение. */ set value(value) { this.setAttribute(Input.ATTR_VALUE, value); } /** * Очистка. */ clear() { this.value = ""; } /** * Проверка, что поле ввода пусто. */ isEmpty() { return this.value.length === 0; } } /** * Наименование компонента. */ Input.COMPONENT_NAME = "sw-input"; /** * Автоматическое заполнение. */ Input.ATTR_AUTOCOMPLETE = "autocomplete"; /** * Поле ввода заполнено автоматически. */ Input.ATTR_AUTOFILLED = "autofilled"; /** * Автоматическая установка фокуса после загрузки страницы. */ Input.ATTR_AUTOFOCUS = "autofocus"; /** * Элемент отключен. */ Input.ATTR_DISABLED = "disabled"; /** * Фокус ввода на элементе. */ Input.ATTR_FOCUSED = "focused"; /** * Вспомогательный текст. */ Input.ATTR_HELPER_TEXT = "helper-text"; /** * Метка. */ Input.ATTR_LABEL = "label"; /** * Максимальная длина вводимого значения. */ Input.ATTR_MAX_LENGTH = "maxlength"; /** * Уникальное имя элемента формы. */ Input.ATTR_NAME = "name"; /** * Значение предназначено только для чтения. */ Input.ATTR_READ_ONLY = "readonly"; /** * Значение обязательно для заполнения. */ Input.ATTR_REQUIRED = "required"; /** * Проверка правописания. */ Input.ATTR_SPELLCHECK = "spellcheck"; /** * Индекс последовательности перехода. */ Input.ATTR_TAB_INDEX = "tabindex"; /** * В поле ввода введен текст или указан заполнитель. */ Input.ATTR_TEXT_ENTERED = "text-entered"; /** * Значение. */ Input.ATTR_VALUE = "value"; /** * Значение по умолчанию для автоматического заполнения. */ Input.DEFAULT_AUTOCOMPLETE = "off"; /** * Значение по умолчанию для максимальной длины вводимого значения. */ Input.DEFAULT_MAX_LENGTH = 524288; Input.define(Icon);