UNPKG

@siberiaweb/components

Version:
774 lines (773 loc) 27.9 kB
import CSS from "./CSS"; import DropdownList from "../dropdown-list/DropdownList"; import DropdownListCloseEvent from "../dropdown-list/CloseEvent"; import DropdownListItemClickEvent from "../dropdown-list/ItemClickEvent"; import DropdownListOpenEvent from "../dropdown-list/OpenEvent"; import Icon from "../icon/Icon"; import Input from "../input/Input"; import SelectEvent from "./SelectEvent"; import UnselectEvent from "./UnselectEvent"; import "./Select.css"; /** * Поле выбора. */ export default class Select extends Input { /** * Конструктор. */ constructor() { super(); /** * Позиции. */ this.items = []; /** * Позиция для выбора по умолчанию. */ this.defaultSelectionItem = null; /** * Выбранная позиция. */ this.selectedItem = null; /** * Обработчик применения фильтра к позиции. */ this.onFilterItem = null; /** * Глобальный обработчик изменения фокуса. * * @param event Событие. */ this.documentFocusListener = (event) => { if (this.dropdownList.isOpened() && (event.target instanceof Node) && !this.contains(event.target)) { this.dropdownList.close(); } }; /** * Глобальный обработчик клика. * * @param event Событие. */ this.documentClickListener = (event) => { if (this.dropdownList.isOpened() && (event.target instanceof Node) && !this.contains(event.target)) { this.dropdownList.close(); } }; this.dropdownList = this.createDropdownList(); this.clearIcon = this.createClearIcon(); this.dropdownIcon = this.createDropdownIcon(); this.initSelectControl(); this.getInlineInput().appendChild(this.dropdownList); } /** * Наблюдаемые атрибуты. */ static get observedAttributes() { return Input.observedAttributes.concat([ Select.ATTR_CLEAR_ICON, Select.ATTR_DROPDOWN_ICON, Select.ATTR_FILTER_DISABLED, Select.ATTR_ITEM_HEIGHT, Select.ATTR_LIST_MAX_HEIGHT, Select.ATTR_LIST_POSITION ]); } /** * Создание выпадающего списка. */ createDropdownList() { let dropdownList = document.createElement(DropdownList.COMPONENT_NAME); dropdownList.addEventListener(DropdownListOpenEvent.EVENT_NAME, (event) => { if (this.disabled) { event.preventDefault(); return; } this.setListPosition(); this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_OPENED, true); if ((this.selectedItem !== null) && (this.selectedItem !== this.defaultSelectionItem) && (this.dropdownList.hasItem(this.selectedItem))) { dropdownList.selectItem(this.selectedItem, "center"); } else { dropdownList.selectFirstItem(); } }); dropdownList.addEventListener(DropdownListCloseEvent.EVENT_NAME, () => { this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_OPENED, false); if (this.selectedItem === null) { this.getControl().placeholder = ""; this.clear(); } else { if (this.selectedItem === this.defaultSelectionItem) { this.getControl().placeholder = this.selectedItem.getText(); this.clear(); } else { this.getControl().placeholder = ""; this.value = this.selectedItem.getText(); } } }); dropdownList.addEventListener(DropdownListItemClickEvent.EVENT_NAME, (e) => { if (this.disabled || this.readOnly) { return; } let event = e; if (this.selectItem(event.getItem())) { dropdownList.close(); } }); return dropdownList; } /** * Создание значка отмены выбора. */ createClearIcon() { let icon = document.createElement(Icon.COMPONENT_NAME); icon.classList.add(CSS.CLEAR_ICON); icon.addEventListener("click", () => { if (this.disabled || this.readOnly) { return; } this.unselectItem(); this.dropdownList.close(); }); return icon; } /** * Создание значка выпадающего списка. */ createDropdownIcon() { let icon = document.createElement(Icon.COMPONENT_NAME); icon.classList.add(CSS.DROPDOWN_ICON); icon.addEventListener("click", () => { if (this.disabled) { return; } if (this.dropdownList.isOpened()) { this.dropdownList.close(); } else { this.dropdownList.setItems(this.items); this.dropdownList.open(); } }); return icon; } /** * Установка позиции выпадающего списка относительно поля выбора. */ setListPosition() { this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_ABOVE, false); this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_BELOW, false); this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_CENTER, false); switch (this.listPosition) { case "above": this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_ABOVE, true); break; case "below": this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_BELOW, true); break; case "center": this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_CENTER, true); break; default: let selectRect = this.getBoundingClientRect(); let heightAbove = selectRect.top; let heightBelow = window.innerHeight - selectRect.bottom; if ((heightAbove >= this.dropdownList.maxHeight) && (this.dropdownList.maxHeight > heightBelow) && (heightBelow < heightAbove)) { this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_ABOVE, true); } else { this.toggleAttribute(Select.ATTR_DROPDOWN_LIST_BELOW, true); } break; } } /** * Вывод или скрытие значка очистки. */ checkDisplayClearIcon() { if (!this.displayClearIcon || this.readOnly || (this.selectedItem === null) || (this.selectedItem === this.defaultSelectionItem)) { this.clearIcon.remove(); } else { this.addTrailingIcon(this.clearIcon, true); } } /** * Инициализация элемента управления. */ initSelectControl() { this.getControl().autocomplete = "off"; this.getControl().addEventListener("blur", () => { this.dropdownList.close(); }); this.getControl().addEventListener("mousedown", (event) => { if ((event.button === 0) && !(event.altKey || event.ctrlKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.close(); } else { this.dropdownList.setItems(this.items); this.dropdownList.open(); } } }); this.getControl().addEventListener("keydown", (event) => { if ((event.key === "ArrowDown") && !(event.altKey || event.ctrlKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.selectNextItem(); event.preventDefault(); } } if ((event.key === "ArrowDown") && event.altKey && !(event.ctrlKey || event.shiftKey)) { if (!this.dropdownList.isOpened()) { this.dropdownList.setItems(this.items); this.dropdownList.open(); event.preventDefault(); } } if ((event.key === "ArrowUp") && !(event.altKey || event.ctrlKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.selectPrevItem(); event.preventDefault(); } } if ((event.key === "ArrowUp") && event.altKey && !(event.ctrlKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.close(); event.preventDefault(); } } if ((event.key === "PageDown") && !(event.altKey || event.ctrlKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.selectNextPageItem(); event.preventDefault(); } } if ((event.key === "PageUp") && !(event.altKey || event.ctrlKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.selectPrevPageItem(); event.preventDefault(); } } if ((event.key === "Home") && event.ctrlKey && !(event.altKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.selectFirstItem(); event.preventDefault(); } } if ((event.key === "End") && event.ctrlKey && !(event.altKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.selectLastItem(); event.preventDefault(); } } if ((event.key === "Enter") && !(event.altKey || event.ctrlKey || event.shiftKey)) { if (this.readOnly) { return; } if (this.dropdownList.isOpened()) { let dropdownListSelectedItem = this.getDropdownList().getSelectedItem(); if ((dropdownListSelectedItem !== null) && this.selectItem(dropdownListSelectedItem)) { this.dropdownList.close(); } event.preventDefault(); } } if ((event.key === "Escape") && !(event.altKey || event.ctrlKey || event.shiftKey)) { if (this.dropdownList.isOpened()) { this.dropdownList.close(); event.preventDefault(); } } if ((event.key === "Delete") && event.ctrlKey && !(event.altKey || event.shiftKey)) { if (this.readOnly) { return; } this.unselectItem(); event.preventDefault(); } }); this.getControl().addEventListener("input", () => { this.applyFilter(); this.dropdownList.open(); }); } /** * @override */ firstConnectedCallback() { super.firstConnectedCallback(); this.classList.add(CSS.SELECT); } /** * @override */ connectedCallback() { super.connectedCallback(); document.addEventListener("focus", this.documentFocusListener, { capture: true }); document.addEventListener("click", this.documentClickListener, { capture: true }); } /** * @override */ disconnectedCallback() { super.disconnectedCallback(); document.removeEventListener("focus", this.documentFocusListener, { capture: true }); document.removeEventListener("click", this.documentClickListener, { capture: true }); } /** * @override */ attrAutocompleteChange() { super.attrAutocompleteChange(); this.getControl().autocomplete = "off"; } /** * @override */ attrReadOnlyChange() { super.attrReadOnlyChange(); this.getControl().readOnly = this.readOnly || this.filterDisabled; this.checkDisplayClearIcon(); } /** * Обработка изменения атрибута "clear-icon". */ attrClearIconChange() { this.checkDisplayClearIcon(); } /** * Обработка изменения атрибута "dropdown-icon". */ attrDropdownIconChange() { if (this.displayDropdownIcon) { this.addTrailingIcon(this.dropdownIcon); } else { this.dropdownIcon.remove(); } } /** * Обработка изменения атрибута "filter-disabled". */ attrFilterDisabledChange() { this.getControl().readOnly = this.readOnly || this.filterDisabled; } /** * Обработка изменения атрибута "item-height". * * @param newValue Новое значение. */ attrItemHeightChange(newValue) { let value = newValue === null ? DropdownList.DEFAULT_ITEM_HEIGHT : parseInt(newValue); if (isNaN(value)) { value = DropdownList.DEFAULT_ITEM_HEIGHT; } this.dropdownList.itemHeight = value; } /** * Обработка изменения атрибута "list-max-height". * * @param newValue Новое значение. */ attrListMaxHeightChange(newValue) { let value = newValue === null ? DropdownList.DEFAULT_MAX_HEIGHT : parseInt(newValue); if (isNaN(value)) { value = DropdownList.DEFAULT_MAX_HEIGHT; } this.dropdownList.maxHeight = value; } /** * Обработка изменения атрибута "list-position". */ attrListPositionChange() { this.setListPosition(); } /** * @override */ attributeChangedCallback(name, oldValue, newValue) { super.attributeChangedCallback(name, oldValue, newValue); switch (name) { case Select.ATTR_CLEAR_ICON: this.attrClearIconChange(); break; case Select.ATTR_DROPDOWN_ICON: this.attrDropdownIconChange(); break; case Select.ATTR_FILTER_DISABLED: this.attrFilterDisabledChange(); break; case Select.ATTR_ITEM_HEIGHT: this.attrItemHeightChange(newValue); break; case Select.ATTR_LIST_MAX_HEIGHT: this.attrListMaxHeightChange(newValue); break; case Select.ATTR_LIST_POSITION: this.attrListPositionChange(); break; } } /** * Фильтр позиции. * * @param item Позиция. * @param text Текст. * * @returns Метод возвращает true, если позиция соответствует фильтру и false в противном случае. */ filterItem(item, text) { if (this.onFilterItem !== null) { return this.onFilterItem(item, text); } if (text === "") { return true; } let compareText = this.filterCaseSensitive ? item.getText() : item.getText().toLowerCase(); let compareFilter = this.filterCaseSensitive ? text : text.toLowerCase(); let index = compareText.indexOf(compareFilter); return this.filterByFirstSymbol ? (index === 0) : (index !== -1); } /** * Выбор позиции. * * @param item Позиция. * * @returns Метод возвращает true, если позиция выбрана и false в противном случае. */ selectItem(item) { if (item !== this.defaultSelectionItem) { if (!this.items.includes(item) || !this.dropdownList.isItemSelectable(item)) { return false; } } if (item === this.selectedItem) { return true; } if (!this.dispatchEvent(new SelectEvent(item))) { return false; } this.selectedItem = item; if (this.selectedItem === this.defaultSelectionItem) { this.getControl().placeholder = this.selectedItem.getText(); this.clear(); } else { this.value = this.selectedItem.getText(); } this.checkDisplayClearIcon(); return true; } /** * Выбор позиции по идентификатору. * * @param id Идентификатор. */ selectItemById(id) { let item = this.items.find((v) => { return v.getId() === id; }); if (item) { this.selectItem(item); } } /** * Отмена выбора позиции. */ unselectItem() { if ((this.selectedItem === null) || (this.selectedItem === this.defaultSelectionItem)) { return; } if (this.defaultSelectionItem !== null) { this.selectItem(this.defaultSelectionItem); } else { this.selectedItem = null; this.getControl().placeholder = ""; this.clear(); this.dispatchEvent(new UnselectEvent()); } this.checkDisplayClearIcon(); } /** * Установка позиций. * * @param items Позиции. * @param defaultSelectionItem Позиция для выбора по умолчанию. * @param excludeDefaultSelectionItem Исключить позицию для выбора по умолчанию из списка позиций. Опционально. По * умолчанию true. */ setItems(items, defaultSelectionItem = null, excludeDefaultSelectionItem = true) { this.dropdownList.unselectItem(); this.defaultSelectionItem = null; this.unselectItem(); this.items = Array.from(items); this.defaultSelectionItem = defaultSelectionItem; if (this.defaultSelectionItem !== null) { if (excludeDefaultSelectionItem) { let index = this.items.indexOf(this.defaultSelectionItem); if (index !== -1) { this.items.splice(index, 1); } } this.selectItem(this.defaultSelectionItem); } if (this.dropdownList.isOpened()) { this.dropdownList.setItems(this.items); } } /** * Очистка списка позиций. */ clearItems() { this.setItems([], this.defaultSelectionItem); } /** * Применение фильтра. */ applyFilter() { if (this.filterDisabled) { return; } let text = this.value; let items = this.items.filter((item) => { return item.isGroup() || this.filterItem(item, text); }); items = items.filter((item) => { if (!item.isGroup()) { return true; } return items.find((i) => { return i.getGroup() === item; }) !== undefined; }); this.dropdownList.setItems(items); this.dropdownList.selectFirstItem(); } /** * Получение выпадающего списка. */ getDropdownList() { return this.dropdownList; } /** * Получение позиций. */ getItems() { return this.items; } /** * Получение позиции для выбора по умолчанию. */ getDefaultSelectionItem() { return this.defaultSelectionItem; } /** * Получение выбранной позиции. */ getSelectedItem() { return this.selectedItem; } /** * Получение идентификатора выбранной позиции. */ getSelectedId() { return this.selectedItem === null ? null : this.selectedItem.getId(); } /** * Получение текста выбранной позиции. */ getSelectedText() { return this.selectedItem === null ? null : this.selectedItem.getText(); } /** * Получение признака отображения значка для отмены выбора. */ get displayClearIcon() { return this.hasAttribute(Select.ATTR_CLEAR_ICON); } /** * Установка признака отображения значка для отмены выбора. * * @param value Значение. */ set displayClearIcon(value) { this.toggleAttribute(Select.ATTR_CLEAR_ICON, value); } /** * Получение признака отображения значка выпадающего списка. */ get displayDropdownIcon() { return this.hasAttribute(Select.ATTR_DROPDOWN_ICON); } /** * Установка признака отображения значка выпадающего списка. * * @param value Значение. */ set displayDropdownIcon(value) { this.toggleAttribute(Select.ATTR_DROPDOWN_ICON, value); } /** * Получение признака применения фильтра начиная с первого символа текста позиции. */ get filterByFirstSymbol() { return this.hasAttribute(Select.ATTR_FILTER_BY_FIRST_SYMBOL); } /** * Установка признака применения фильтра, начиная с первого символа текста позиции. * * @param value Значение. */ set filterByFirstSymbol(value) { this.toggleAttribute(Select.ATTR_FILTER_BY_FIRST_SYMBOL, value); } /** * Получение признака отключенного фильтра позиций. */ get filterDisabled() { return this.hasAttribute(Select.ATTR_FILTER_DISABLED); } /** * Установка признака отключенного фильтра позиций. * * @param value Значение. */ set filterDisabled(value) { this.toggleAttribute(Select.ATTR_FILTER_DISABLED, value); } /** * Получение признака чувствительного к регистру фильтра позиций. */ get filterCaseSensitive() { return this.hasAttribute(Select.ATTR_FILTER_CASE_SENSITIVE); } /** * Установка признака чувствительного к регистру фильтра позиций. * * @param value Значение. */ set filterCaseSensitive(value) { this.toggleAttribute(Select.ATTR_FILTER_CASE_SENSITIVE, value); } /** * Получение размера позиции по вертикали. */ get itemHeight() { return this.dropdownList.itemHeight; } /** * Установка размера позиции по вертикали. */ set itemHeight(value) { this.setAttribute(Select.ATTR_ITEM_HEIGHT, value.toString()); } /** * Получение максимального размера списка по вертикали. */ get listMaxHeight() { return this.dropdownList.maxHeight; } /** * Установка максимального размера списка по вертикали. */ set listMaxHeight(value) { this.setAttribute(Select.ATTR_LIST_MAX_HEIGHT, value.toString()); } /** * Получение позиции выпадающего списка относительно поля выбора. */ get listPosition() { switch (this.getAttribute(Select.ATTR_LIST_POSITION)) { case "above": return "above"; case "below": return "below"; case "center": return "center"; default: return "auto"; } } /** * Установка позиции выпадающего списка относительно поля выбора. * * @param value Значение. */ set listPosition(value) { this.setAttribute(Select.ATTR_LIST_POSITION, "above"); } } /** * Наименование компонента. */ Select.COMPONENT_NAME = "sw-select"; /** * Значок для отмены выбора. */ Select.ATTR_CLEAR_ICON = "clear-icon"; /** * Значок выпадающего списка. */ Select.ATTR_DROPDOWN_ICON = "dropdown-icon"; /** * Применение фильтра, начиная с первого символа текста позиции. */ Select.ATTR_FILTER_BY_FIRST_SYMBOL = "filter-by-first-symbol"; /** * Чувствительный к регистру фильтр позиций. */ Select.ATTR_FILTER_CASE_SENSITIVE = "filter-case-sensitive"; /** * Фильтр позиций отключен. */ Select.ATTR_FILTER_DISABLED = "filter-disabled"; /** * Размер позиции по вертикали. */ Select.ATTR_ITEM_HEIGHT = "item-height"; /** * Выпадающий список открыт. */ Select.ATTR_DROPDOWN_LIST_OPENED = "list-opened"; /** * Выпадающий список располагается сверху поля выбора. */ Select.ATTR_DROPDOWN_LIST_ABOVE = "list-above"; /** * Выпадающий список располагается снизу поля выбора. */ Select.ATTR_DROPDOWN_LIST_BELOW = "list-below"; /** * Выпадающий список располагается по центру поля выбора. */ Select.ATTR_DROPDOWN_LIST_CENTER = "list-center"; /** * Максимальный размер выпадающего списка по вертикали. */ Select.ATTR_LIST_MAX_HEIGHT = "list-max-height"; /** * Позиция выпадающего списка относительно поля выбора. */ Select.ATTR_LIST_POSITION = "list-position"; Select.define(DropdownList); Select.define(Icon);