Your IP : 3.138.192.209
import {Type, Dom, Tag, Loc} from 'main.core';
import {EventEmitter} from 'main.core.events';
import {Menu, Popup, MenuItemOptions, PopupOptions} from 'main.popup';
import './select.css';
import 'ui.'
export type SelectOption = {
label: string;
value: string;
}
export type SelectOptions = {
options: SelectOption[];
value: string;
placeholder: string;
isSearchable: boolean;
containerClassname: string;
popupParams: PopupOptions;
}
const ScrollDirection = Object.freeze({
TOP: -1,
BOTTOM: 1,
NONE: 0,
})
export class Select extends EventEmitter
{
#placeholder: string = '';
#isSearchable: boolean = false;
#isSearching: boolean = false;
#searchValue: string = '';
#selectedOption: SelectOption | null = null;
#options: SelectOption[] = [];
#container: HTMLElement | null = null;
#containerClassname: string = '';
#menu: Menu | null = null;
#emptySearchPopup: Popup | null = null;
#highlightedOptionIndex: number = 0;
#popupParams: PopupOptions = {};
constructor(options: SelectOptions)
{
super();
this.setEventNamespace('BX.UI.Select');
this.#placeholder = Type.isString(options.placeholder) ? options.placeholder : '';
this.#isSearchable = options.isSearchable === true || false;
this.#options = Array.isArray(options.options) ? options.options : [];
this.#popupParams = Type.isPlainObject(options.popupParams) ? options.popupParams : {};
this.#selectedOption = this.#findOptionByValue(options.value) || null;
this.#containerClassname = Type.isString(options?.containerClassname) ? options.containerClassname : '';
this.#highlightedOptionIndex = this.#getOptionIndex(this.#selectedOption?.value) || 0;
this.#renderContainer();
}
renderTo(targetContainer: HTMLElement): HTMLElement | null
{
if (Type.isDomNode(targetContainer))
{
Dom.clean(targetContainer);
this.#renderContainer();
Dom.append(this.#container, targetContainer);
return targetContainer;
}
return null;
}
#renderContainer(): HTMLElement
{
this.#container = Tag.render`
<div class="${this.#getContainerClassname()}">
<div class="ui-ctl-after ui-ctl-icon-angle"></div>
<input
class="ui-ctl-element"
type="text"
placeholder="${this.#placeholder}"
${this.#isInputReadonly() ? 'readonly' : ''}
value="${this.#selectedOption?.label || ''}"
oninput="${(e) => { this.#handleInput(e)}}"
onfocus="${(e) => { this.#handleFocus(e) }}"
onblur="${(e) => {this.#handleBlur(e) }}"
onmouseup="${(e) => {this.#handleInputClick(e) }}"
onkeydown="${(e) => {this.#handleKeyDown(e) }}"
>
</div>
`;
return this.#container;
}
#isInputReadonly(): boolean
{
return !this.#isSearchable || (!this.isMenuShown() && !this.#emptySearchPopup);
}
#handleInputClick(): void
{
if (this.getInput() === document.activeElement)
{
setTimeout(() => {
this.showMenu();
}, 100);
}
}
showMenu(): void
{
if (!this.#menu)
{
this.#createMenu();
}
this.#menu.show();
this.#updateMenu();
}
#createMenu(): Menu
{
const {width} = Dom.getPosition(this.#container);
const events = this.#popupParams?.events ? this.#popupParams?.events : {};
this.#menu = new Menu({
width,
bindElement: this.#container,
items: this.#getMenuItems(),
closeByEsc: true,
className: 'select-menu-popup',
...this.#popupParams,
events: {
...events,
onAfterClose: () => {
if (!this.#emptySearchPopup)
{
this.#searchValue = '';
this.#setSelectedOption(this.#selectedOption);
this.#updateSelect();
if (events.onAfterClose)
{
events.onAfterClose();
}
}
},
},
});
return this.#menu;
}
hideMenu(): void
{
if (this.#menu)
{
this.#menu.close();
}
}
#getMenuItems(): MenuItemOptions[]
{
if (this.#isSearching)
{
return this.#getFilteredOptions()
.map((option, index) => {
return this.#getMenuItemFromOption(option, index === this.#highlightedOptionIndex)
}
);
}
else
{
return this.#options
.map((option, index) => {
return this.#getMenuItemFromOption(option, index === this.#highlightedOptionIndex)
}
);
}
}
#handleInput(e): void
{
e.preventDefault();
this.#highlightedOptionIndex = 0;
this.#isSearching = true;
this.#searchValue = e.target.value;
this.#updateMenu();
}
#handleKeyDown(e): void
{
const {keyCode} = e;
const arrowUpKeyCode = 38;
const arrowDownKeyCode = 40;
const enterKeyCode = 13;
const spaceKeyCode = 32;
switch (keyCode)
{
case enterKeyCode: this.#handleEnterKey(e); break;
case spaceKeyCode: this.#handleSpaceKey(e); break;
case arrowUpKeyCode: this.#handleArrowUpKey(e); break;
case arrowDownKeyCode: this.#handleArrowDownKey(e); break;
}
}
#handleSpaceKey(e): void
{
if (!this.isMenuShown() && !this.#emptySearchPopup)
{
e.preventDefault();
this.showMenu();
this.#updateSelect();
}
}
#handleArrowUpKey(e): void
{
e.preventDefault();
if (!this.isMenuShown() || this.#highlightedOptionIndex === 0)
{
return;
}
this.#highlightedOptionIndex--;
this.#scrollToHighlightedItem();
this.#highlightOption(this.#highlightedOptionIndex);
}
#handleArrowDownKey(e): void
{
e.preventDefault();
if (!this.isMenuShown() || this.#highlightedOptionIndex === this.#getMenuItems().length - 1)
{
return;
}
this.#highlightedOptionIndex++;
this.#scrollToHighlightedItem();
this.#highlightOption(this.#highlightedOptionIndex);
}
#handleEnterKey(e): void
{
e.preventDefault();
const options = this.#getFilteredOptions();
this.#selectedOption = options[this.#highlightedOptionIndex];
this.hideMenu();
}
#updateMenu(): void
{
if (!this.#menu)
{
return;
}
this.#options.forEach(({value}) => {
this.#menu.removeMenuItem(value, {
destroyEmptyPopup: false,
});
});
const filteredOptions = this.#getFilteredOptions(this.#searchValue);
if (filteredOptions.length)
{
if (!this.isMenuShown())
{
this.showMenu();
}
this.#hideEmptySearchPopup();
filteredOptions.forEach((option, index) => {
this.#menu.addMenuItem(this.#getMenuItemFromOption(option, index === this.#highlightedOptionIndex), null);
});
this.#scrollToHighlightedItem();
this.#highlightOption(this.#highlightedOptionIndex);
}
else
{
this.#showEmptySearchPopup();
this.hideMenu();
}
}
#getMenuItemFromOption(option, isHoverOption: boolean = false): MenuItemOptions
{
const isHover = isHoverOption === true;
const className = `ui-select__menu-item menu-popup-no-icon ${isHover ? 'menu-popup-item-open' : ''}`;
return ({
id: option.value,
text: option.label,
onclick: () => {
this.#selectedOption = option;
},
className,
});
}
#getFilteredOptions(): SelectOption[]
{
return this.#options.filter(this.#getOptionFilter(this.#searchValue));
}
#getOptionFilter(searchStr): (option: SelectOption) => boolean
{
const lowerCaseSearchStr = Type.isString(searchStr) ? searchStr.toLowerCase() : '';
return (option) => {
const lowerCaseOptionLabel = option.label.toLowerCase();
return lowerCaseOptionLabel.indexOf(lowerCaseSearchStr) === 0;
};
}
#showEmptySearchPopup(): void
{
if (!this.#emptySearchPopup || !this.#emptySearchPopup?.isShown())
{
const {width} = Dom.getPosition(this.#container);
const events = this.#popupParams?.events ? this.#popupParams.events : {};
this.#emptySearchPopup = new Popup({
width,
bindElement: this.#container,
content: Loc.getMessage('UI_SELECT_NOTHING_FOUND'),
closeByEsc: true,
...this.#popupParams,
events: {
...events,
onAfterClose: () => {
this.#emptySearchPopup = null;
this.#setSelectedOption(this.#selectedOption);
if (!this.isMenuShown())
{
this.#searchValue = '';
this.#updateSelect();
if (events.onAfterClose)
{
events.onAfterClose();
}
}
}
}
});
this.#emptySearchPopup.show();
}
}
#hideEmptySearchPopup(): void
{
if (this.#emptySearchPopup)
{
this.#emptySearchPopup.destroy();
this.#emptySearchPopup = null;
}
}
#setSelectedOption(option: SelectOption): void
{
if (!option)
{
this.#selectedOption = null;
return;
}
this.emit('update', option.value);
this.#searchValue = '';
const input = this.getInput();
input.value = option.label;
this.#highlightedOptionIndex = this.#getOptionIndex(option.value);
this.#selectedOption = option;
}
getInput(): HTMLElement | null
{
return this.#container.querySelector('input');
}
getValue(): string
{
return this.#selectedOption?.value || '';
}
setValue(value: string): void
{
const option = this.#findOptionByValue(value);
this.#setSelectedOption(option);
}
#findOptionByValue(value: string): SelectOption | null
{
return this.#options.find((option) => {
return option.value === value;
});
}
#highlightOption(optionIndex: number): void
{
if (!this.#menu)
{
return;
}
const menuItems = this.#menu.itemsContainer.children;
for (let i = 0; i < menuItems.length; i++)
{
const item = menuItems.item(i);
Dom.removeClass(item, 'menu-popup-item-open');
if (i === optionIndex)
{
Dom.addClass(item, 'menu-popup-item-open');
}
}
}
#scrollToHighlightedItem(): void
{
const popupContent: HTMLElement = this.#menu.getPopupWindow().getContentContainer();
const menuItems: HTMLCollection = this.#menu.itemsContainer.children;
const highlightedItem = menuItems.item(this.#highlightedOptionIndex);
const {
height: popupContentHeight,
} = Dom.getPosition(popupContent);
const {
height: highlightedItemHeight,
} = Dom.getPosition(highlightedItem);
const direction = this.#getScrollDirectionToHighlightedItem(popupContent, highlightedItem);
if (direction !== ScrollDirection.NONE)
{
popupContent.scroll({
left: 0,
top: (highlightedItemHeight * (this.#highlightedOptionIndex) + direction * popupContentHeight),
behavior: "smooth",
});
}
}
#getScrollDirectionToHighlightedItem(popupContent: HTMLElement, highlightedItem: HTMLElement): number
{
const {
bottom: popupContentBottom,
top: popupContentTop
} = Dom.getPosition(popupContent);
const {
bottom: highlightedItemBottom,
top: highlightedItemTop
} = Dom.getPosition(highlightedItem);
if (popupContentTop > highlightedItemTop)
{
return ScrollDirection.TOP;
}
else if (popupContentBottom < highlightedItemBottom)
{
return ScrollDirection.BOTTOM;
}
else {
return ScrollDirection.NONE;
}
}
#getOptionIndex(optionValue: string): number
{
return this.#options.findIndex((option) => {
return option.value === optionValue;
});
}
isMenuShown(): boolean
{
return this.#menu && this.#menu.getPopupWindow().isShown();
}
#handleBlur(): void
{
this.hideMenu();
this.#hideEmptySearchPopup();
}
#handleFocus(e): void
{
setTimeout(() => {
this.showMenu();
this.#updateSelect();
}, 100);
e.preventDefault();
}
#updateSelect(): void
{
this.#updateInput();
this.#updateContainerClassname();
}
#updateInput(): void
{
const input = this.getInput();
if (this.#isInputReadonly())
{
input.setAttribute('readonly', 'readonly');
}
else
{
input.removeAttribute('readonly');
}
}
#updateContainerClassname(): void
{
this.#container.className = this.#getContainerClassname();
}
#getContainerClassname(): string
{
const openMenuClassnameModifier = this.isMenuShown() || this.#emptySearchPopup ? '--open' : '';
return `ui-select ui-ctl ui-ctl-after-icon ui-ctl-dropdown ${this.#containerClassname} ${openMenuClassnameModifier}`;
}
}