@siberiaweb/components
Version:
774 lines (773 loc) • 27.9 kB
JavaScript
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);