UNPKG

bitmovin-player-ui

Version:
221 lines (178 loc) 6.14 kB
import { ListSelector, ListSelectorConfig } from '../lists/ListSelector'; import { DOM } from '../../DOM'; import { i18n } from '../../localization/i18n'; import { PlayerAPI } from 'bitmovin-player'; import { UIInstanceManager } from '../../UIManager'; import { UIContainer } from '../UIContainer'; import { PlayerUtils } from '../../utils/PlayerUtils'; import { ViewMode } from '../Component'; const DocumentDropdownClosedEvents = [ 'mousemove', 'mouseenter', 'mouseleave', 'touchstart', 'touchmove', 'touchend', 'pointermove', 'click', 'keydown', 'keypress', 'keyup', 'blur', ]; const SelectDropdownClosedEvents = ['change', 'keyup', 'mouseup']; const DropdownOpenedEvents: [string, (event: Event) => boolean][] = [ ['click', () => true], ['keydown', (event: KeyboardEvent) => [' ', 'ArrowUp', 'ArrowDown'].includes(event.key)], ['mousedown', () => true], ]; const Timeout = 100; /** * A simple select box providing the possibility to select a single item out of a list of available items. * * DOM example: * <code> * <select class='ui-selectbox'> * <option value='key'>label</option> * ... * </select> * </code> * * @category Components */ export class SelectBox extends ListSelector<ListSelectorConfig> { private selectElement: DOM | undefined; private dropdownCloseListenerTimeoutId = 0; private removeDropdownCloseListeners = () => {}; private uiContainer: UIContainer | undefined; private removeDropdownOpenedListeners = () => {}; private uiWrapperElement: DOM | undefined; constructor(config: ListSelectorConfig = {}) { super(config); this.config = this.mergeConfig( config, { cssClass: 'ui-selectbox', }, this.config, ); } protected toDomElement(): DOM { this.selectElement = new DOM( 'select', { id: this.config.id, class: this.getCssClasses(), 'aria-label': i18n.performLocalization(this.config.ariaLabel), }, this, ); this.onDisabled.subscribe(this.closeDropdown); this.onHide.subscribe(this.closeDropdown); this.addDropdownOpenedListeners(); this.updateDomItems(); this.selectElement.on('change', this.onChange); return this.selectElement; } configure(player: PlayerAPI, uimanager: UIInstanceManager) { super.configure(player, uimanager); this.uiContainer = uimanager.getUI(); this.uiContainer?.onPlayerStateChange().subscribe(this.onPlayerStateChange); this.uiWrapperElement = uimanager.uiWrapperElement; } private readonly onChange = () => { const value = this.selectElement.val(); this.onItemSelectedEvent(value, false); }; private getSelectElement() { return this.selectElement?.get()?.[0]; } protected updateDomItems(selectedValue: string = null) { if (this.selectElement === undefined) { return; } // Delete all children this.selectElement.empty(); // Add updated children for (const item of this.items) { const optionElement = new DOM('option', { value: String(item.key), }).html(i18n.performLocalization(item.label)); if (item.key === String(selectedValue)) { // convert selectedValue to string to catch 'null'/null case optionElement.attr('selected', 'selected'); } this.selectElement.append(optionElement); } } protected onItemAddedEvent(value: string) { super.onItemAddedEvent(value); this.updateDomItems(this.selectedItem); } protected onItemRemovedEvent(value: string) { super.onItemRemovedEvent(value); this.updateDomItems(this.selectedItem); } protected onItemSelectedEvent(value: string, updateDomItems: boolean = true) { super.onItemSelectedEvent(value); if (updateDomItems) { this.updateDomItems(value); } } public readonly closeDropdown = () => { const select = this.getSelectElement(); if (select === undefined) { return; } select.blur(); }; private readonly onPlayerStateChange = (_: UIContainer, state: PlayerUtils.PlayerState) => { if ([PlayerUtils.PlayerState.Idle, PlayerUtils.PlayerState.Finished].includes(state)) { this.closeDropdown(); } }; private onDropdownOpened = () => { clearTimeout(this.dropdownCloseListenerTimeoutId); this.dropdownCloseListenerTimeoutId = window.setTimeout(() => this.addDropdownCloseListeners(), Timeout); this.onViewModeChangedEvent(ViewMode.Persistent); }; private onDropdownClosed = () => { clearTimeout(this.dropdownCloseListenerTimeoutId); this.removeDropdownCloseListeners(); this.onViewModeChangedEvent(ViewMode.Temporary); }; private addDropdownCloseListeners() { this.removeDropdownCloseListeners(); clearTimeout(this.dropdownCloseListenerTimeoutId); DocumentDropdownClosedEvents.forEach(event => this.uiWrapperElement.on(event, this.onDropdownClosed, true)); SelectDropdownClosedEvents.forEach(event => this.selectElement.on(event, this.onDropdownClosed, true)); this.removeDropdownCloseListeners = () => { DocumentDropdownClosedEvents.forEach(event => this.uiWrapperElement.off(event, this.onDropdownClosed, true)); SelectDropdownClosedEvents.forEach(event => this.selectElement.off(event, this.onDropdownClosed, true)); }; } private addDropdownOpenedListeners() { const removeListenerFunctions: (() => void)[] = []; this.removeDropdownOpenedListeners(); for (const [event, filter] of DropdownOpenedEvents) { const listener = (event: Event) => { if (filter(event)) { this.onDropdownOpened(); } }; removeListenerFunctions.push(() => this.selectElement.off(event, listener, true)); this.selectElement.on(event, listener, true); } this.removeDropdownOpenedListeners = () => { for (const remove of removeListenerFunctions) { remove(); } }; } release() { super.release(); this.removeDropdownCloseListeners(); this.removeDropdownOpenedListeners(); clearTimeout(this.dropdownCloseListenerTimeoutId); } }