@siberiaweb/components
Version:
647 lines (646 loc) • 21.2 kB
JavaScript
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);