| /******************************************************************************** |
| * Copyright (c) 2020 Contributors to the Eclipse Foundation |
| * |
| * See the NOTICE file(s) distributed with this work for additional |
| * information regarding copyright ownership. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| ********************************************************************************/ |
| |
| import {ConnectedPosition} from "@angular/cdk/overlay"; |
| import {Component, ElementRef, EventEmitter, forwardRef, Input, Output, ViewChild} from "@angular/core"; |
| import {NG_VALUE_ACCESSOR} from "@angular/forms"; |
| import {EKeyboardKeys} from "../../../../../util/events"; |
| import {DropDownDirective} from "../../../../layout/drop-down"; |
| import {AbstractControlValueAccessorComponent} from "../../../common"; |
| import {ISelectOption} from "../../model"; |
| |
| @Component({ |
| selector: "app-select", |
| templateUrl: "./select.component.html", |
| styleUrls: ["./select.component.scss"], |
| providers: [ |
| { |
| provide: NG_VALUE_ACCESSOR, |
| useExisting: forwardRef(() => SelectComponent), |
| multi: true |
| } |
| ] |
| }) |
| export class SelectComponent<T = any> extends AbstractControlValueAccessorComponent<T> { |
| |
| private static id = 0; |
| |
| /** |
| * This ID is placed on the toggle button of the drop down menu |
| */ |
| @Input() |
| public appId = `CalendarControlComponent${SelectComponent.id++}`; |
| |
| /** |
| * Text which is placed in the drop down button as a placeholder text if nothing is selected |
| */ |
| @Input() |
| public appPlaceholder = ""; |
| |
| /** |
| * List of selectable options displayed in the drop down menu |
| */ |
| @Input() |
| public appOptions: ISelectOption<T>[] = []; |
| |
| /** |
| * Shows select with less padding if set. |
| */ |
| @Input() |
| public appSmall = false; |
| |
| @Input() |
| public appMaxWidth: string; |
| |
| /** |
| * Outputs when the drop down is closed for parent components to react to. |
| */ |
| @Output() |
| public appClose = new EventEmitter<void>(); |
| |
| @ViewChild(DropDownDirective) |
| public dropDown: DropDownDirective; |
| |
| @ViewChild("toggleButtonRef", {static: true}) |
| public toggleButton: ElementRef<HTMLElement>; |
| |
| public readonly connectedPositions: ConnectedPosition[] = [ |
| { |
| originX: "start", |
| originY: "bottom", |
| overlayX: "start", |
| overlayY: "top", |
| offsetX: 4, |
| panelClass: "bottom" |
| }, |
| { |
| originX: "start", |
| originY: "top", |
| overlayX: "start", |
| overlayY: "bottom", |
| offsetX: 4, |
| panelClass: "top" |
| }, |
| { |
| originX: "start", |
| originY: "center", |
| overlayX: "start", |
| overlayY: "center", |
| panelClass: "center", |
| offsetX: 4 |
| } |
| ]; |
| |
| /** |
| * Toggles the drop down menu to open or to close |
| * @param openOrClose If true, the menu is opened, if false it is closed, if unset it is toggled. |
| */ |
| public toggle(openOrClose?: boolean) { |
| this.dropDown.toggle(openOrClose); |
| if (this.dropDown.isOpen) { |
| this.toggleButton.nativeElement.focus(); |
| } |
| } |
| |
| public onClickOnOption(value: any) { |
| this.toggle(false); |
| if (this.appDisabled) { |
| return; |
| } |
| this.writeValue(value, true); |
| } |
| |
| public onKeyDown(event: KeyboardEvent) { |
| switch (event?.key) { |
| case EKeyboardKeys.ESCAPE: |
| case EKeyboardKeys.TAB: |
| return this.toggle(false); |
| case EKeyboardKeys.ARROW_DOWN: |
| case EKeyboardKeys.ARROW_RIGHT: { |
| const value = this.getNextValue(); |
| if (this.appDisabled || value == null) { |
| return; |
| } |
| event.preventDefault(); |
| return this.writeValue(value, true); |
| } |
| case EKeyboardKeys.ARROW_UP: |
| case EKeyboardKeys.ARROW_LEFT: { |
| const value = this.getPreviousValue(); |
| if (this.appDisabled || value == null) { |
| return; |
| } |
| event.preventDefault(); |
| return this.writeValue(value, true); |
| } |
| } |
| } |
| |
| public getNextValue(): T { |
| if (!Array.isArray(this.appOptions)) { |
| return; |
| } |
| |
| if (this.appValue == null) { |
| return this.appOptions[0]?.value; |
| } else { |
| const index = this.appOptions.findIndex((o) => o?.value === this.appValue) + 1; |
| return this.appOptions[index < this.appOptions?.length ? index : 0]?.value; |
| } |
| } |
| |
| public getPreviousValue(): T { |
| if (!Array.isArray(this.appOptions)) { |
| return; |
| } |
| |
| if (this.appValue == null) { |
| return this.appOptions[this.appOptions.length - 1]?.value; |
| } else { |
| const index = this.appOptions.findIndex((o) => o?.value === this.appValue) - 1; |
| return this.appOptions[index > -1 ? index : this.appOptions.length - 1]?.value; |
| } |
| } |
| |
| } |