/// /// 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 { Injectable, QueryList } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { AnchorPlacement } from '../../common/overlay/index'; import { FloatingActionButtonComponent } from './floating-action-button.component'; export type FloatingActionButtonDirection = AnchorPlacement; @Injectable() export class FloatingActionButtonsService { open$ = new BehaviorSubject(false); direction$ = new BehaviorSubject('top'); private _buttons: QueryList; open(): void { this.open$.next(true); } toggle(): void { this.open$.next(!this.open$.getValue()); } close(): void { this.open$.next(false); // make the first button tabbable again this.setPrimaryButtonFocusable(); } isHorizontal(): boolean { return this.direction$.value === 'left' || this.direction$.value === 'right'; } isVertical(): boolean { return this.direction$.value === 'top' || this.direction$.value === 'bottom'; } setButtons(buttons: QueryList): void { this._buttons = buttons; // make the first button tabbable (after a delay to prevent expression changed error) requestAnimationFrame(() => this.setPrimaryButtonFocusable()); } /** Make only the first button tabbable */ setPrimaryButtonFocusable(): void { this._buttons.forEach(btn => btn.tabindex$.next(btn.primary ? 0 : -1)); } focusPrimaryButton(): void { this.focus(this._buttons.find(btn => btn.primary)); } focus(button: FloatingActionButtonComponent): void { // if the button is not defined then do nothing if (!button) { return; } // set the button tab index this._buttons.forEach(btn => btn.tabindex$.next(button === btn ? 0 : -1)); // apply the focus button.focus(); } focusSibling(next: boolean): void { // if the buttons are not visible then do nothing if (this.open$.value === false) { return; } // get the current focused item const button = this.getFocusedButton(); if (next && button === this._buttons.last) { return this.focus(this._buttons.first); } else if (!next && button === this._buttons.first) { return this.focus(this._buttons.last); } // find the sibling button const sibling = this._buttons.toArray()[this.getButtonIndex(button) + (next ? 1 : -1)]; // focus the next button this.focus(sibling); } private getFocusedButton(): FloatingActionButtonComponent { return this._buttons.find(btn => btn.tabindex$.value === 0); } private getButtonIndex(button: FloatingActionButtonComponent): number { return this._buttons.toArray().findIndex(btn => btn === button); } }