Path: blob/main/src/vs/base/browser/ui/selectBox/selectBoxNative.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as dom from '../../dom.js';6import { EventType, Gesture } from '../../touch.js';7import { ISelectBoxDelegate, ISelectBoxOptions, ISelectBoxStyles, ISelectData, ISelectOptionItem } from './selectBox.js';8import * as arrays from '../../../common/arrays.js';9import { Emitter, Event } from '../../../common/event.js';10import { KeyCode } from '../../../common/keyCodes.js';11import { Disposable } from '../../../common/lifecycle.js';12import { isMacintosh } from '../../../common/platform.js';1314export class SelectBoxNative extends Disposable implements ISelectBoxDelegate {1516private selectElement: HTMLSelectElement;17private selectBoxOptions: ISelectBoxOptions;18private options: ISelectOptionItem[];19private selected = 0;20private readonly _onDidSelect: Emitter<ISelectData>;21private styles: ISelectBoxStyles;2223constructor(options: ISelectOptionItem[], selected: number, styles: ISelectBoxStyles, selectBoxOptions?: ISelectBoxOptions) {24super();25this.selectBoxOptions = selectBoxOptions || Object.create(null);2627this.options = [];2829this.selectElement = document.createElement('select');3031this.selectElement.className = 'monaco-select-box';3233if (typeof this.selectBoxOptions.ariaLabel === 'string') {34this.selectElement.setAttribute('aria-label', this.selectBoxOptions.ariaLabel);35}3637if (typeof this.selectBoxOptions.ariaDescription === 'string') {38this.selectElement.setAttribute('aria-description', this.selectBoxOptions.ariaDescription);39}4041this._onDidSelect = this._register(new Emitter<ISelectData>());4243this.styles = styles;4445this.registerListeners();46this.setOptions(options, selected);47}4849private registerListeners() {50this._register(Gesture.addTarget(this.selectElement));51[EventType.Tap].forEach(eventType => {52this._register(dom.addDisposableListener(this.selectElement, eventType, (e) => {53this.selectElement.focus();54}));55});5657this._register(dom.addStandardDisposableListener(this.selectElement, 'click', (e) => {58dom.EventHelper.stop(e, true);59}));6061this._register(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => {62this.selectElement.title = e.target.value;63this._onDidSelect.fire({64index: e.target.selectedIndex,65selected: e.target.value66});67}));6869this._register(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => {70let showSelect = false;7172if (isMacintosh) {73if (e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.Space) {74showSelect = true;75}76} else {77if (e.keyCode === KeyCode.DownArrow && e.altKey || e.keyCode === KeyCode.Space || e.keyCode === KeyCode.Enter) {78showSelect = true;79}80}8182if (showSelect) {83// Space, Enter, is used to expand select box, do not propagate it (prevent action bar action run)84e.stopPropagation();85}86}));87}8889public get onDidSelect(): Event<ISelectData> {90return this._onDidSelect.event;91}9293public setOptions(options: ISelectOptionItem[], selected?: number): void {9495if (!this.options || !arrays.equals(this.options, options)) {96this.options = options;97this.selectElement.options.length = 0;9899this.options.forEach((option, index) => {100this.selectElement.add(this.createOption(option.text, index, option.isDisabled));101});102103}104105if (selected !== undefined) {106this.select(selected);107}108}109110public select(index: number): void {111if (this.options.length === 0) {112this.selected = 0;113} else if (index >= 0 && index < this.options.length) {114this.selected = index;115} else if (index > this.options.length - 1) {116// Adjust index to end of list117// This could make client out of sync with the select118this.select(this.options.length - 1);119} else if (this.selected < 0) {120this.selected = 0;121}122123this.selectElement.selectedIndex = this.selected;124if ((this.selected < this.options.length) && typeof this.options[this.selected].text === 'string') {125this.selectElement.title = this.options[this.selected].text;126} else {127this.selectElement.title = '';128}129}130131public setAriaLabel(label: string): void {132this.selectBoxOptions.ariaLabel = label;133this.selectElement.setAttribute('aria-label', label);134}135136public focus(): void {137if (this.selectElement) {138this.selectElement.tabIndex = 0;139this.selectElement.focus();140}141}142143public blur(): void {144if (this.selectElement) {145this.selectElement.tabIndex = -1;146this.selectElement.blur();147}148}149150public setEnabled(enable: boolean): void {151this.selectElement.disabled = !enable;152}153154public setFocusable(focusable: boolean): void {155this.selectElement.tabIndex = focusable ? 0 : -1;156}157158public render(container: HTMLElement): void {159container.classList.add('select-container');160container.appendChild(this.selectElement);161this.setOptions(this.options, this.selected);162this.applyStyles();163}164165public style(styles: ISelectBoxStyles): void {166this.styles = styles;167this.applyStyles();168}169170public applyStyles(): void {171172// Style native select173if (this.selectElement) {174this.selectElement.style.backgroundColor = this.styles.selectBackground ?? '';175this.selectElement.style.color = this.styles.selectForeground ?? '';176this.selectElement.style.borderColor = this.styles.selectBorder ?? '';177}178179}180181private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement {182const option = document.createElement('option');183option.value = value;184option.text = value;185option.disabled = !!disabled;186187return option;188}189}190191192