/// /// 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 { AfterViewInit, Directive, ElementRef, EventEmitter, inject, Input, OnDestroy, OnInit, Output, } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @Directive({ selector: '[uxOverflowObserver], [uxOverflowHorizontalObserver], [uxOverflowVerticalObserver]', exportAs: 'ux-overflow-observer', }) export class OverflowDirective implements OnInit, AfterViewInit, OnDestroy { private readonly _elementRef = inject(ElementRef); /** Allow a observable to be used to check for overflow */ @Input() trigger: Observable; /** Allow overflow to be within a range before emitting */ @Input() tolerance: number = 0; /** Emit when there is a change to the overflow state - horizontal or vertical */ @Output() uxOverflowObserver = new EventEmitter(); /** Emit when there is a change to overflow on the horizontal axis */ @Output() uxOverflowHorizontalObserver = new EventEmitter(); /** Emit when there is a change to overflow on the vertical axis */ @Output() uxOverflowVerticalObserver = new EventEmitter(); /** Store the overflow state on both axis */ private _state = { horizontalOverflow: false, verticalOverflow: false }; /** Unsubscribe from all the observables */ private readonly _onDestroy = new Subject(); /** Set up the trigger if specified */ ngOnInit(): void { if (this.trigger) { this.trigger.pipe(takeUntil(this._onDestroy)).subscribe(() => this.checkForOverflow()); } } /** Perform an intial check for overflow */ ngAfterViewInit(): void { requestAnimationFrame(() => this.checkForOverflow()); } /** Unsubscribe from the trigger */ ngOnDestroy(): void { this._onDestroy.next(); this._onDestroy.complete(); } /** Programmatically trigger check for overflow */ checkForOverflow(): void { const { offsetWidth, offsetHeight, scrollWidth, scrollHeight } = this._elementRef.nativeElement; const horizontalOverflow = scrollWidth - offsetWidth > this.tolerance; const verticalOverflow = scrollHeight - offsetHeight > this.tolerance; if (horizontalOverflow !== this._state.horizontalOverflow) { this.uxOverflowHorizontalObserver.emit(horizontalOverflow); } if (verticalOverflow !== this._state.verticalOverflow) { this.uxOverflowVerticalObserver.emit(verticalOverflow); } if ( horizontalOverflow !== this._state.horizontalOverflow || verticalOverflow !== this._state.verticalOverflow ) { this.uxOverflowObserver.emit(horizontalOverflow || verticalOverflow); } // store the state this._state = { horizontalOverflow, verticalOverflow }; } }