UNPKG

@sixbell-telco/sdk

Version:

A collection of reusable components designed for use in Sixbell Telco Angular projects

190 lines (186 loc) 7.25 kB
import * as i0 from '@angular/core'; import { input, signal, afterRenderEffect, Directive } from '@angular/core'; /** * Directive that automatically focuses an element when it becomes visible. * * This directive uses multiple strategies to detect when an element becomes visible: * - IntersectionObserver to detect when the element enters the viewport * - MutationObserver to detect DOM changes that might affect visibility * - Polling at regular intervals as a fallback mechanism * * The directive works reliably with modals, dialogs, accordions, and other UI components * that dynamically show and hide content. * * @example * ```html * <!-- Basic usage --> * <input [stAutofocus]="true" /> * * <!-- Conditional autofocus --> * <input [stAutofocus]="shouldFocus" /> * * <!-- In a form inside a dialog --> * <st-dialog> * <input [stAutofocus]="true" /> * </st-dialog> * ``` * * @usageNotes * The directive takes a boolean input that determines whether autofocus should be applied. * When this value is true, the directive will attempt to focus the element once it becomes visible, * and will continue to monitor the element's visibility state in case it changes. * * This is particularly useful for: * - Elements in modals that need focus when opened * - Elements in accordions/collapsible sections that should receive focus when expanded * - Elements that are conditionally rendered and need focus when they appear * * @publicApi */ class AutofocusDirective { elementRef; /** * Controls whether autofocus should be applied to the element. * Set to true to enable autofocus. */ focus = input(false); /** Tracks the current visibility state of the element */ isVisible = signal(false); /** Keeps a reference so the afterRenderEffect can be destroyed with the directive */ focusAfterRenderRef; /** Collection of observers for cleanup */ observers = []; /** Interval used for polling visibility */ pollingInterval = null; /** Flag to prevent concurrent visibility checks */ checkingVisibility = false; constructor(elementRef) { this.elementRef = elementRef; this.focusAfterRenderRef = afterRenderEffect(() => { if (this.focus() && this.isVisible()) { this.elementRef.nativeElement.focus(); } }); } /** * Sets up visibility observers after the view is initialized */ ngAfterViewInit() { if (this.focus()) { // Set up both observer types this.setupIntersectionObserver(); this.setupMutationObserver(); // Lightweight polling (every 500ms) as fallback for edge cases this.pollingInterval = globalThis.setInterval(() => { this.checkVisibility(true); }, 500); // Initial visibility check this.checkVisibility(true); } } /** * Cleans up all observers and timers to prevent memory leaks */ ngOnDestroy() { // Cleanup all observers for (const observer of this.observers) { if (observer) { observer.disconnect(); } } this.observers = []; // Clear polling interval if (this.pollingInterval !== null) { clearInterval(this.pollingInterval); } this.focusAfterRenderRef.destroy(); } /** * Sets up the IntersectionObserver to detect when the element enters or leaves the viewport * @private */ setupIntersectionObserver() { if ('IntersectionObserver' in globalThis) { const intersectionObserver = new IntersectionObserver((entries) => { if (entries[0].isIntersecting) { this.checkVisibility(true); } else { this.updateVisibility(false); } }, { threshold: 0.1 }); intersectionObserver.observe(this.elementRef.nativeElement); this.observers.push(intersectionObserver); } } /** * Sets up MutationObserver to detect style/class changes that might affect visibility * Also monitors parent elements to detect when containers like modals appear/disappear * @private */ setupMutationObserver() { const mutationObserver = new MutationObserver(() => { this.checkVisibility(true); }); // Watch this element mutationObserver.observe(this.elementRef.nativeElement, { attributes: true, attributeFilter: ['style', 'class'], }); // Watch parent elements - important for modals, accordions, etc. let parent = this.elementRef.nativeElement.parentElement; let level = 0; // Watch up to 3 parent levels while (parent && level < 3) { mutationObserver.observe(parent, { attributes: true, attributeFilter: ['style', 'class'], childList: true, }); parent = parent.parentElement; level++; } this.observers.push(mutationObserver); } /** * Checks if the element is truly visible by examining computed style and dimensions * @param force Whether to force a check even if one is already in progress * @private */ checkVisibility(force = false) { if (this.checkingVisibility && !force) return; this.checkingVisibility = true; const el = this.elementRef.nativeElement; requestAnimationFrame(() => { const style = globalThis.getComputedStyle(el); const isNowVisible = el.offsetWidth > 0 && el.offsetHeight > 0 && style.visibility !== 'hidden' && style.display !== 'none'; this.updateVisibility(isNowVisible); this.checkingVisibility = false; }); } /** * Updates the visibility signal that drives the autofocus effect * @param visible The new visibility state * @private */ updateVisibility(visible) { if (this.isVisible() !== visible) { this.isVisible.set(visible); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AutofocusDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.0", type: AutofocusDirective, isStandalone: true, selector: "[stAutofocus]", inputs: { focus: { classPropertyName: "focus", publicName: "focus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: AutofocusDirective, decorators: [{ type: Directive, args: [{ selector: '[stAutofocus]', standalone: true, }] }], ctorParameters: () => [{ type: i0.ElementRef }] }); /** * Generated bundle index. Do not edit. */ export { AutofocusDirective }; //# sourceMappingURL=sixbell-telco-sdk-directives-auto-focus.mjs.map