/// /// Copyright 2015-2026 Micro Focus or one of its affiliates. /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { DOWN_ARROW, ENTER, ESCAPE, LEFT_ARROW, RIGHT_ARROW, UP_ARROW, } from '@angular/cdk/keycodes'; import { AsyncPipe } from '@angular/common'; import { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, HostListener, inject, Input, OnDestroy, ViewChild, HostAttributeToken, } from '@angular/core'; import { BehaviorSubject, Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; import { FocusIndicatorDirective } from '../../directives/accessibility/focus-indicator/focus-indicator.directive'; import { TooltipDirective } from '../tooltip/index'; import { FloatingActionButtonsService } from './floating-action-buttons.service'; @Component({ selector: 'ux-floating-action-button', templateUrl: './floating-action-button.component.html', changeDetection: ChangeDetectionStrategy.OnPush, preserveWhitespaces: false, imports: [FocusIndicatorDirective, AsyncPipe], }) export class FloatingActionButtonComponent implements AfterViewInit, OnDestroy { readonly fab = inject(FloatingActionButtonsService); private readonly _tooltip = inject(TooltipDirective, { optional: true }); /** Define the aria label for the button */ @Input('aria-label') ariaLabel: string; /** Access the element ref of the button element */ @ViewChild('button', { static: true }) button: ElementRef; /** Determine if this is the primary button in the set */ primary: boolean = false; /** Store the tabindex */ tabindex$ = new BehaviorSubject(-1); /** Unsubscribe from all observables on component destroy */ private readonly _onDestroy = new Subject(); constructor() { const primary = inject(new HostAttributeToken('fab-primary'), { optional: true }); this.primary = primary !== null; } ngAfterViewInit(): void { if (this._tooltip) { // ensure the tooltip gets hidden when the button is hidden this.fab.open$ .pipe( takeUntil(this._onDestroy), filter(isOpen => !isOpen && !this.primary) ) .subscribe(() => this._tooltip.hide()); } } ngOnDestroy(): void { this._onDestroy.next(); this._onDestroy.complete(); } focus(): void { this.button.nativeElement.focus(); } onFocus(): void { // ensure the tooltip gets shown if (this._tooltip) { this._tooltip.show(); } } onBlur(): void { // ensure the tooltip gets hidden if (this._tooltip) { this._tooltip.hide(); } } close(): void { this.fab.close(); } @HostListener('keydown', ['$event']) onKeydown(event: KeyboardEvent): void { switch (event.which) { case UP_ARROW: if (this.fab.isVertical()) { this.fab.focusSibling(this.fab.direction$.value !== 'bottom'); event.preventDefault(); } break; case DOWN_ARROW: if (this.fab.isVertical()) { this.fab.focusSibling(this.fab.direction$.value === 'bottom'); event.preventDefault(); } break; case LEFT_ARROW: if (this.fab.isHorizontal()) { this.fab.focusSibling(this.fab.direction$.value !== 'right'); event.preventDefault(); } break; case RIGHT_ARROW: if (this.fab.isHorizontal()) { this.fab.focusSibling(this.fab.direction$.value === 'right'); event.preventDefault(); } break; case ENTER: this.fab.focusPrimaryButton(); break; case ESCAPE: this.fab.focusPrimaryButton(); this.fab.close(); break; } } }