UNPKG

@favian/headwind-ui

Version:

Headless UI for Angular - Styleless Angular components to integrate with Tailwind CSS

976 lines (953 loc) 103 kB
import * as i0 from '@angular/core'; import { Directive, EventEmitter, Injectable, ViewContainerRef, Component, Output, ViewChild, ContentChild, Input, HostListener, inject, Renderer2, ElementRef, PLATFORM_ID, Inject, booleanAttribute, ViewEncapsulation, ContentChildren } from '@angular/core'; import { BehaviorSubject, combineLatest } from 'rxjs'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { NgControl } from '@angular/forms'; import { isPlatformBrowser } from '@angular/common'; class HeadwindAccordionContentDirective { constructor(templateRef) { this.templateRef = templateRef; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionContentDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindAccordionContentDirective, isStandalone: true, selector: "ng-template[headwindAccordionContent]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionContentDirective, decorators: [{ type: Directive, args: [{ selector: 'ng-template[headwindAccordionContent]', standalone: true, }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); class HeadwindAccordionService { constructor() { /** Emits to toggle opened status */ this.toggleOpened = new EventEmitter(); this.opened$ = new BehaviorSubject(false); } get opened() { return this.opened$.value; } set opened(value) { this.opened$.next(value); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionService, decorators: [{ type: Injectable }] }); class HeadwindAccordionComponent { constructor(_headwindAccordionService) { this._headwindAccordionService = _headwindAccordionService; this.openedChange = new EventEmitter(); this._headwindAccordionService.toggleOpened.pipe(takeUntilDestroyed()).subscribe(() => { const opened = !this.opened; this.opened = opened; // to toggle content this.openedChange.emit(opened); }); } get opened() { return this._headwindAccordionService.opened; } set opened(value) { this._headwindAccordionService.opened = value; if (value) { this.open(); } else { this.close(); } } ngAfterViewInit() { // use timeout to prevent NG0100 error this._openTimeoutId = setTimeout(() => { if (this.opened) { this.open(); } }); } ngOnDestroy() { clearTimeout(this._openTimeoutId); } toggle() { if (this.opened) { this.close(); } else { this.open(); } } open() { if (this.accordionContentContainer && this.accordionContent && !this._contentEmbeddedViewRef) { this._contentEmbeddedViewRef = this.accordionContentContainer.createEmbeddedView(this.accordionContent.templateRef); } this.openedChange.emit(true); this._headwindAccordionService.opened = true; } close() { this._contentEmbeddedViewRef?.destroy(); delete this._contentEmbeddedViewRef; this.openedChange.emit(false); this._headwindAccordionService.opened = false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionComponent, deps: [{ token: HeadwindAccordionService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindAccordionComponent, isStandalone: true, selector: "headwind-accordion", inputs: { opened: "opened" }, outputs: { openedChange: "openedChange" }, host: { properties: { "class.headwind-opened": "opened" }, classAttribute: "headwind-accordion" }, providers: [HeadwindAccordionService], queries: [{ propertyName: "accordionContent", first: true, predicate: HeadwindAccordionContentDirective, descendants: true }], viewQueries: [{ propertyName: "accordionContentContainer", first: true, predicate: ["accordionContentContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<ng-content></ng-content>\r\n\r\n<ng-container #accordionContentContainer></ng-container>\r\n", styles: [""] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionComponent, decorators: [{ type: Component, args: [{ selector: 'headwind-accordion', standalone: true, imports: [], providers: [HeadwindAccordionService], host: { class: 'headwind-accordion', ['[class.headwind-opened]']: 'opened', }, template: "<ng-content></ng-content>\r\n\r\n<ng-container #accordionContentContainer></ng-container>\r\n" }] }], ctorParameters: () => [{ type: HeadwindAccordionService }], propDecorators: { openedChange: [{ type: Output }], accordionContentContainer: [{ type: ViewChild, args: ['accordionContentContainer', { read: ViewContainerRef }] }], accordionContent: [{ type: ContentChild, args: [HeadwindAccordionContentDirective, { descendants: true }] }], opened: [{ type: Input }] } }); class HeadwindAccordionButtonComponent { constructor(_headwindAccordionService) { this._headwindAccordionService = _headwindAccordionService; } get opened() { return this._headwindAccordionService.opened; } onHostClick() { this._headwindAccordionService.toggleOpened.emit(); } onHostSpaceKeydown(event) { event.stopPropagation(); event.preventDefault(); this._headwindAccordionService.toggleOpened.emit(); } onHostEnterKeydown(event) { event.stopPropagation(); event.preventDefault(); this._headwindAccordionService.toggleOpened.emit(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionButtonComponent, deps: [{ token: HeadwindAccordionService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindAccordionButtonComponent, isStandalone: true, selector: "headwind-accordion-button", host: { attributes: { "tabindex": "0", "role": "button" }, listeners: { "click": "onHostClick()", "keydown.space": "onHostSpaceKeydown($event)", "keydown.enter": "onHostEnterKeydown($event)" }, properties: { "attr.aria-expanded": "opened" }, classAttribute: "headwind-accordion-button" }, ngImport: i0, template: "<ng-content></ng-content>\r\n", styles: [""] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAccordionButtonComponent, decorators: [{ type: Component, args: [{ selector: 'headwind-accordion-button', standalone: true, imports: [], host: { tabindex: '0', role: 'button', class: 'headwind-accordion-button', ['[attr.aria-expanded]']: 'opened', }, template: "<ng-content></ng-content>\r\n" }] }], ctorParameters: () => [{ type: HeadwindAccordionService }], propDecorators: { onHostClick: [{ type: HostListener, args: ['click'] }], onHostSpaceKeydown: [{ type: HostListener, args: ['keydown.space', ['$event']] }], onHostEnterKeydown: [{ type: HostListener, args: ['keydown.enter', ['$event']] }] } }); class HeadwindControlValueAccessor { constructor() { this.isDisabled = false; this.onChange = (value) => { }; this.onTouched = () => { }; this._internalNgControl = inject(NgControl, { optional: true }); this._internalRenderer = inject(Renderer2); this._internalElementRef = inject(ElementRef); if (this._internalNgControl) { this._internalNgControl.valueAccessor = this; } } registerOnChange(fn = (value) => { }) { this.onChange = fn; } registerOnTouched(fn = () => { }) { this.onTouched = fn; } setDisabledState(isDisabled) { this.isDisabled = isDisabled; if (isDisabled) { this._setAttribute('disabled'); } else { this._removeAttribute('disabled'); } } updateValue(value) { this.onChange(value); this.writeValue(value); } _setAttribute(name) { this._internalRenderer.setAttribute(this._internalElementRef.nativeElement, name, ''); } _removeAttribute(name) { this._internalRenderer.removeAttribute(this._internalElementRef.nativeElement, name); } } const HeadwindErrors = { headwindError0001: '[HEADWIND_E0001] HeadwindOptionsOverlayDirective should be bound to HeadwindOptionsOverlayComponent to open options of HeadwindSelectComponent', headwindError0002: '[HEADWIND_E0002] HeadwindOptionsOverlayComponent is required in HeadwindSelectComponent to open options', headwindError0003: '[HEADWIND_E0003] HeadwindPopoverOverlayDirective should be bound to HeadwindPopoverOverlayComponent to open popover of HeadwindPopoverComponent', headwindError0004: '[HEADWIND_E0004] HeadwindPopoverOverlayComponent is required in HeadwindPopoverComponent to open popover', }; class HeadwindPlatformService { constructor(_platformId) { this._platformId = _platformId; } get isBrowser() { return isPlatformBrowser(this._platformId); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPlatformService, deps: [{ token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPlatformService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPlatformService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: Object, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }] }); class HeadwindClickDetector { constructor(_headwindPlatformService) { this._headwindPlatformService = _headwindPlatformService; this.outsideClick = new EventEmitter(); this.insideClick = new EventEmitter(); this._mouseDownDetectedFromOutside = false; this._mouseDownDetectedFromInside = false; this._windowMousedownListener = (event) => { if (!this._container) { console.error(HeadwindErrors.headwindError0003); return; } this._mouseDownDetectedFromOutside = event.target instanceof Node && !this._container.contains(event.target); this._mouseDownDetectedFromInside = event.target instanceof Node && this._container.contains(event.target); }; this._windowMouseupListener = (event) => { if (!this._container) { console.error(HeadwindErrors.headwindError0003); return; } if (this._mouseDownDetectedFromOutside && event.target instanceof Node && !this._container.contains(event.target)) { this.outsideClick.emit(); } if (this._mouseDownDetectedFromInside && event.target instanceof Node && this._container.contains(event.target)) { this.insideClick.emit(); } }; } ngOnDestroy() { this.unregister(); } register(container) { this._container = container; this._removeEventListeners(); this._addEventListeners(); } unregister() { delete this._container; this._removeEventListeners(); } _addEventListeners() { if (this._headwindPlatformService.isBrowser) { window.addEventListener('mousedown', this._windowMousedownListener); window.addEventListener('mouseup', this._windowMouseupListener); } } _removeEventListeners() { if (this._headwindPlatformService.isBrowser) { window.removeEventListener('mousedown', this._windowMousedownListener); window.removeEventListener('mouseup', this._windowMouseupListener); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindClickDetector, deps: [{ token: HeadwindPlatformService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindClickDetector }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindClickDetector, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: HeadwindPlatformService }] }); class HeadwindCheckboxService { constructor() { this.checked$ = new BehaviorSubject(false); } get checked() { return this.checked$.value; } set checked(value) { this.checked$.next(value); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxService, decorators: [{ type: Injectable }] }); class HeadwindCheckboxComponent extends HeadwindControlValueAccessor { constructor(_headwindCheckboxService) { super(); this._headwindCheckboxService = _headwindCheckboxService; this.checkedChange = new EventEmitter(); } get disabled() { return this.isDisabled; } set disabled(value) { this.setDisabledState(value); } get checked() { return this._headwindCheckboxService.checked; } set checked(value) { this._headwindCheckboxService.checked = value; } onHostClick() { this.toggle(); } onHostBlur() { this.onTouched(); } onHostSpaceKeydown(event) { event.stopPropagation(); event.preventDefault(); this.toggle(); } writeValue(obj) { this.checked = obj; } updateValue(value) { super.updateValue(value); this.checkedChange.emit(value); } check() { if (this.disabled) { return; } this.updateValue(true); } uncheck() { if (this.disabled) { return; } this.updateValue(false); } toggle() { if (this.checked) { this.uncheck(); } else { this.check(); } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxComponent, deps: [{ token: HeadwindCheckboxService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "18.2.13", type: HeadwindCheckboxComponent, isStandalone: true, selector: "headwind-checkbox", inputs: { disabled: ["disabled", "disabled", booleanAttribute], checked: ["checked", "checked", booleanAttribute] }, outputs: { checkedChange: "checkedChange" }, host: { attributes: { "tabindex": "0", "role": "checkbox" }, listeners: { "click": "onHostClick()", "blur": "onHostBlur()", "keydown.space": "onHostSpaceKeydown($event)" }, properties: { "attr.aria-checked": "checked", "class.headwind-checked": "checked" }, classAttribute: "headwind-checkbox" }, providers: [HeadwindClickDetector, HeadwindCheckboxService], usesInheritance: true, ngImport: i0, template: "<ng-content></ng-content>\r\n", styles: [""] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxComponent, decorators: [{ type: Component, args: [{ selector: 'headwind-checkbox', standalone: true, imports: [], providers: [HeadwindClickDetector, HeadwindCheckboxService], host: { tabindex: '0', class: 'headwind-checkbox', role: 'checkbox', ['[attr.aria-checked]']: 'checked', ['[class.headwind-checked]']: 'checked', }, template: "<ng-content></ng-content>\r\n" }] }], ctorParameters: () => [{ type: HeadwindCheckboxService }], propDecorators: { checkedChange: [{ type: Output }], disabled: [{ type: Input, args: [{ transform: booleanAttribute }] }], checked: [{ type: Input, args: [{ transform: booleanAttribute }] }], onHostClick: [{ type: HostListener, args: ['click'] }], onHostBlur: [{ type: HostListener, args: ['blur'] }], onHostSpaceKeydown: [{ type: HostListener, args: ['keydown.space', ['$event']] }] } }); class HeadwindCheckboxCheckedDirective { constructor(templateRef) { this.templateRef = templateRef; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxCheckedDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindCheckboxCheckedDirective, isStandalone: true, selector: "ng-template[headwindCheckboxChecked]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxCheckedDirective, decorators: [{ type: Directive, args: [{ selector: 'ng-template[headwindCheckboxChecked]', standalone: true, }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); class HeadwindCheckboxButtonComponent { constructor(_destroyRef, _headwindCheckboxService) { this._destroyRef = _destroyRef; this._headwindCheckboxService = _headwindCheckboxService; } get checked() { return this._headwindCheckboxService.checked; } ngAfterViewInit() { // use timeout to prevent NG0100 error this._timeoutId = setTimeout(() => { this._headwindCheckboxService.checked$.pipe(takeUntilDestroyed(this._destroyRef)).subscribe((checked) => { if (this.checkboxCheckedContainer && this.checkboxChecked) { if (checked) { if (!this._checkedEmbeddedViewRef) { this._checkedEmbeddedViewRef = this.checkboxCheckedContainer.createEmbeddedView(this.checkboxChecked.templateRef); } } else { this._checkedEmbeddedViewRef?.destroy(); delete this._checkedEmbeddedViewRef; } } }); }); } ngOnDestroy() { clearTimeout(this._timeoutId); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxButtonComponent, deps: [{ token: i0.DestroyRef }, { token: HeadwindCheckboxService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindCheckboxButtonComponent, isStandalone: true, selector: "headwind-checkbox-button", host: { classAttribute: "headwind-checkbox-button" }, queries: [{ propertyName: "checkboxChecked", first: true, predicate: HeadwindCheckboxCheckedDirective, descendants: true }], viewQueries: [{ propertyName: "checkboxCheckedContainer", first: true, predicate: ["checkboxCheckedContainer"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<ng-container #checkboxCheckedContainer></ng-container>\r\n", styles: [""] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindCheckboxButtonComponent, decorators: [{ type: Component, args: [{ selector: 'headwind-checkbox-button', standalone: true, imports: [], host: { class: 'headwind-checkbox-button', }, template: "<ng-container #checkboxCheckedContainer></ng-container>\r\n" }] }], ctorParameters: () => [{ type: i0.DestroyRef }, { type: HeadwindCheckboxService }], propDecorators: { checkboxCheckedContainer: [{ type: ViewChild, args: ['checkboxCheckedContainer', { read: ViewContainerRef }] }], checkboxChecked: [{ type: ContentChild, args: [HeadwindCheckboxCheckedDirective, { descendants: true }] }] } }); class HeadwindPopoverService { constructor() { /** Emits to open popover */ this.openPopover = new EventEmitter(); /** Emits to close popover */ this.closePopover = new EventEmitter(); /** Popover opened status */ this.popoverOpened$ = new BehaviorSubject(false); } get popoverOpened() { return this.popoverOpened$.value; } set popoverOpened(value) { this.popoverOpened$.next(value); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverService }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverService, decorators: [{ type: Injectable }] }); class HeadwindWindowService { /** * Returns whether there is space for an element at the bottom of the Window * @param y - Y-axis position of the top of the element * @param height - Height of the element */ isBottomSpaceAvailable(y, height) { return y + height < window.innerHeight; } /** * Returns whether there is space for an element at the top of the Window * @param y - Y-axis position of the bottom of the element * @param height - Height of the element */ isTopSpaceAvailable(y, height) { return y - height > 0; } isRightSpaceAvailable(x, width) { return x + width < window.innerWidth; } isLeftSpaceAvailable(x, width) { return x - width > 0; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindWindowService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindWindowService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindWindowService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class HeadwindPopoverOverlayComponent { constructor(_renderer, _elementRef, _headwindWindowService, _headwindClickDetector, _headwindPopoverService) { this._renderer = _renderer; this._elementRef = _elementRef; this._headwindWindowService = _headwindWindowService; this._headwindClickDetector = _headwindClickDetector; this._headwindPopoverService = _headwindPopoverService; this.direction = 'vertical'; this.directionPriority = 'bottom'; this.pinPriority = 'left'; this.actualDirectionChange = new EventEmitter(); this.actualPinChange = new EventEmitter(); this._headwindClickDetector.outsideClick.pipe(takeUntilDestroyed()).subscribe(() => { if (this._headwindPopoverService.popoverOpened) { this._headwindPopoverService.closePopover.emit(); } }); } ngAfterViewInit() { this._headwindClickDetector.register(this._elementRef.nativeElement); } updatePosition(button) { if (this._elementRef) { const host = this._elementRef.nativeElement; // clear all styles this._renderer.removeStyle(host, 'position'); this._renderer.removeStyle(host, 'top'); this._renderer.removeStyle(host, 'bottom'); this._renderer.removeStyle(host, 'left'); this._renderer.removeStyle(host, 'right'); this._renderer.setStyle(host, 'position', 'fixed'); const buttonDomRect = button.getBoundingClientRect(); const hostDomRect = host.getBoundingClientRect(); switch (this.direction) { case 'vertical': { this._updateVerticalDirectionByPriority(host, hostDomRect, buttonDomRect); this._updateVerticalDirectionPin(host, hostDomRect, buttonDomRect); break; } case 'horizontal': { this._updateHorizontalDirectionByPriority(host, hostDomRect, buttonDomRect); this._updateHorizontalDirectionPin(host, hostDomRect, buttonDomRect); break; } } } } _updateVerticalDirectionByPriority(host, hostDomRect, buttonDomRect) { if (this.directionPriority === 'top') { if (this._headwindWindowService.isTopSpaceAvailable(buttonDomRect.top, hostDomRect.height)) { this._renderer.setStyle(host, 'bottom', `calc(100% - ${buttonDomRect.top}px)`); this._emitActualDirectionTop(); } else if (this._headwindWindowService.isBottomSpaceAvailable(buttonDomRect.bottom, hostDomRect.height)) { this._renderer.setStyle(host, 'top', buttonDomRect.bottom + 'px'); this._emitActualDirectionBottom(); } else { this._renderer.setStyle(host, 'bottom', `calc(100% - ${buttonDomRect.top}px)`); this._emitActualDirectionTop(); } } else { if (this._headwindWindowService.isBottomSpaceAvailable(buttonDomRect.bottom, hostDomRect.height)) { this._renderer.setStyle(host, 'top', buttonDomRect.bottom + 'px'); this._emitActualDirectionBottom(); } else if (this._headwindWindowService.isTopSpaceAvailable(buttonDomRect.top, hostDomRect.height)) { this._renderer.setStyle(host, 'bottom', `calc(100% - ${buttonDomRect.top}px)`); this._emitActualDirectionTop(); } else { this._renderer.setStyle(host, 'top', buttonDomRect.bottom + 'px'); this._emitActualDirectionBottom(); } } } _updateHorizontalDirectionByPriority(host, hostDomRect, buttonDomRect) { if (this.directionPriority === 'left') { if (this._headwindWindowService.isLeftSpaceAvailable(buttonDomRect.left, hostDomRect.width)) { this._renderer.setStyle(host, 'right', `calc(100% - ${buttonDomRect.left}px)`); this._emitActualDirectionLeft(); } else if (this._headwindWindowService.isRightSpaceAvailable(buttonDomRect.right, hostDomRect.width)) { this._renderer.setStyle(host, 'left', buttonDomRect.right + 'px'); this._emitActualDirectionRight(); } else { this._renderer.setStyle(host, 'right', `calc(100% - ${buttonDomRect.left}px)`); this._emitActualDirectionLeft(); } } else { if (this._headwindWindowService.isRightSpaceAvailable(buttonDomRect.right, hostDomRect.width)) { this._renderer.setStyle(host, 'left', buttonDomRect.right + 'px'); this._emitActualDirectionRight(); } else if (this._headwindWindowService.isLeftSpaceAvailable(buttonDomRect.left, hostDomRect.width)) { this._renderer.setStyle(host, 'right', `calc(100% - ${buttonDomRect.left}px)`); this._emitActualDirectionLeft(); } else { this._renderer.setStyle(host, 'left', buttonDomRect.right + 'px'); this._emitActualDirectionRight(); } } } _updateVerticalDirectionPin(host, hostDomRect, buttonDomRect) { if (this.pinPriority === 'right') { if (this._headwindWindowService.isLeftSpaceAvailable(buttonDomRect.right, hostDomRect.width)) { this._renderer.setStyle(host, 'right', `calc(100% - ${buttonDomRect.right}px)`); this._emitActualPinRight(); } else if (this._headwindWindowService.isRightSpaceAvailable(buttonDomRect.left, hostDomRect.width)) { this._renderer.setStyle(host, 'left', buttonDomRect.left + 'px'); this._emitActualPinLeft(); } else { this._renderer.setStyle(host, 'right', `calc(100% - ${buttonDomRect.right}px)`); this._emitActualPinRight(); } } else { if (this._headwindWindowService.isRightSpaceAvailable(buttonDomRect.left, hostDomRect.width)) { this._renderer.setStyle(host, 'left', buttonDomRect.left + 'px'); this._emitActualPinLeft(); } else if (this._headwindWindowService.isLeftSpaceAvailable(buttonDomRect.right, hostDomRect.width)) { this._renderer.setStyle(host, 'right', `calc(100% - ${buttonDomRect.right}px)`); this._emitActualPinRight(); } else { this._renderer.setStyle(host, 'left', buttonDomRect.left + 'px'); this._emitActualPinLeft(); } } } _updateHorizontalDirectionPin(host, hostDomRect, buttonDomRect) { if (this.pinPriority === 'bottom') { if (this._headwindWindowService.isTopSpaceAvailable(buttonDomRect.bottom, hostDomRect.height)) { this._renderer.setStyle(host, 'bottom', `calc(100% - ${buttonDomRect.bottom}px)`); this._emitActualPinTop(); } else if (this._headwindWindowService.isBottomSpaceAvailable(buttonDomRect.top, hostDomRect.height)) { this._renderer.setStyle(host, 'top', buttonDomRect.top + 'px'); this._emitActualPinBottom(); } else { this._renderer.setStyle(host, 'bottom', `calc(100% - ${buttonDomRect.bottom}px)`); this._emitActualPinTop(); } } else { if (this._headwindWindowService.isBottomSpaceAvailable(buttonDomRect.top, hostDomRect.height)) { this._renderer.setStyle(host, 'top', buttonDomRect.top + 'px'); this._emitActualPinBottom(); } else if (this._headwindWindowService.isTopSpaceAvailable(buttonDomRect.bottom, hostDomRect.height)) { this._renderer.setStyle(host, 'bottom', `calc(100% - ${buttonDomRect.bottom}px)`); this._emitActualPinTop(); } else { this._renderer.setStyle(host, 'top', buttonDomRect.top + 'px'); this._emitActualPinBottom(); } } } _emitActualDirectionTop() { this.actualDirectionChange.emit('top'); } _emitActualDirectionBottom() { this.actualDirectionChange.emit('bottom'); } _emitActualDirectionLeft() { this.actualDirectionChange.emit('left'); } _emitActualDirectionRight() { this.actualDirectionChange.emit('right'); } _emitActualPinTop() { this.actualPinChange.emit('top'); } _emitActualPinBottom() { this.actualPinChange.emit('bottom'); } _emitActualPinLeft() { this.actualPinChange.emit('left'); } _emitActualPinRight() { this.actualPinChange.emit('right'); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverOverlayComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: HeadwindWindowService }, { token: HeadwindClickDetector }, { token: HeadwindPopoverService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindPopoverOverlayComponent, isStandalone: true, selector: "headwind-popover-overlay", inputs: { direction: "direction", directionPriority: "directionPriority", pinPriority: "pinPriority" }, outputs: { actualDirectionChange: "actualDirectionChange", actualPinChange: "actualPinChange" }, host: { classAttribute: "headwind-popover-overlay" }, providers: [HeadwindClickDetector], ngImport: i0, template: "<ng-content></ng-content>\r\n", styles: [""] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverOverlayComponent, decorators: [{ type: Component, args: [{ selector: 'headwind-popover-overlay', standalone: true, imports: [], host: { class: 'headwind-popover-overlay', }, providers: [HeadwindClickDetector], template: "<ng-content></ng-content>\r\n" }] }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: HeadwindWindowService }, { type: HeadwindClickDetector }, { type: HeadwindPopoverService }], propDecorators: { direction: [{ type: Input }], directionPriority: [{ type: Input }], pinPriority: [{ type: Input }], actualDirectionChange: [{ type: Output }], actualPinChange: [{ type: Output }] } }); class HeadwindPopoverOverlayDirective { constructor(templateRef) { this.templateRef = templateRef; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverOverlayDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindPopoverOverlayDirective, isStandalone: true, selector: "[headwindPopoverOverlay]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverOverlayDirective, decorators: [{ type: Directive, args: [{ selector: '[headwindPopoverOverlay]', standalone: true, }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); class HeadwindPopoverButtonDirective { constructor(_elementRef, _headwindPopoverService) { this._elementRef = _elementRef; this._headwindPopoverService = _headwindPopoverService; } get nativeElement() { return this._elementRef.nativeElement; } onHostClick() { this._headwindPopoverService.openPopover.emit(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverButtonDirective, deps: [{ token: i0.ElementRef }, { token: HeadwindPopoverService }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindPopoverButtonDirective, isStandalone: true, selector: "[headwindPopoverButton]", host: { listeners: { "click": "onHostClick()" }, classAttribute: "headwind-popover-button" }, ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverButtonDirective, decorators: [{ type: Directive, args: [{ selector: '[headwindPopoverButton]', standalone: true, host: { class: 'headwind-popover-button', }, }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: HeadwindPopoverService }], propDecorators: { onHostClick: [{ type: HostListener, args: ['click'] }] } }); class HeadwindOverlayOutletComponent { constructor(_headwindOverlayService) { this._headwindOverlayService = _headwindOverlayService; } onWindowEscapeKeydown() { this._headwindOverlayService.closeLatest(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindOverlayOutletComponent, deps: [{ token: HeadwindOverlayService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindOverlayOutletComponent, isStandalone: true, selector: "headwind-overlay-outlet", host: { listeners: { "window:keydown.escape": "onWindowEscapeKeydown()" }, classAttribute: "headwind-overlay-outlet" }, viewQueries: [{ propertyName: "viewContainerRef", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef }], ngImport: i0, template: "<ng-container #container></ng-container>\r\n", styles: [".headwind-overlay-outlet{display:block;z-index:9999;pointer-events:none;position:fixed;inset:0}.headwind-overlay-outlet>*{pointer-events:auto}\n"], encapsulation: i0.ViewEncapsulation.None }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindOverlayOutletComponent, decorators: [{ type: Component, args: [{ selector: 'headwind-overlay-outlet', standalone: true, imports: [], host: { class: 'headwind-overlay-outlet', }, encapsulation: ViewEncapsulation.None, template: "<ng-container #container></ng-container>\r\n", styles: [".headwind-overlay-outlet{display:block;z-index:9999;pointer-events:none;position:fixed;inset:0}.headwind-overlay-outlet>*{pointer-events:auto}\n"] }] }], ctorParameters: () => [{ type: HeadwindOverlayService }], propDecorators: { viewContainerRef: [{ type: ViewChild, args: ['container', { read: ViewContainerRef }] }], onWindowEscapeKeydown: [{ type: HostListener, args: ['window:keydown.escape'] }] } }); class HeadwindOverlayService { constructor(_applicationRef) { this._applicationRef = _applicationRef; this._openedViewRefs = []; } get viewContainerRef() { if (!this._cachedViewContainerRef) { const rootViewContainerRef = this._applicationRef.components[0].injector.get(ViewContainerRef); const overlayOutletRef = rootViewContainerRef.createComponent(HeadwindOverlayOutletComponent); overlayOutletRef.changeDetectorRef.detectChanges(); this._cachedViewContainerRef = overlayOutletRef.instance.viewContainerRef; } return this._cachedViewContainerRef; } /** * Open a template as overlay * @param templateRef - TemplateRef to open as an overlay * @param onDestroy - Callback to call when destroying EmbeddedViewRef */ open(templateRef, onDestroy) { const embeddedViewRef = this.viewContainerRef.createEmbeddedView(templateRef); this._openedViewRefs.push(embeddedViewRef); embeddedViewRef.onDestroy(() => { this._openedViewRefs = this._openedViewRefs.filter((_openedViewRef) => _openedViewRef !== embeddedViewRef); if (onDestroy) { onDestroy(); } }); return embeddedViewRef; } closeLatest() { const latestViewRef = this._openedViewRefs[this._openedViewRefs.length - 1]; // item is removed from the `onDestroy()` method latestViewRef?.destroy(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindOverlayService, deps: [{ token: i0.ApplicationRef }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindOverlayService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindOverlayService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: i0.ApplicationRef }] }); class HeadwindAnimator { constructor() { this._animating = false; this._registeredListeners = []; } get hasListeners() { return this._registeredListeners.length > 0; } addListener(listener) { this.removeListener(listener); // remove duplicated one this._registeredListeners.push(listener); if (this.hasListeners && !this._animating) { this._animating = true; this._runAnimation(); } } removeListener(listener) { this._registeredListeners = this._registeredListeners.filter((item) => item !== listener); if (!this.hasListeners) { this._stopAnimation(); } } _runAnimation() { this._registeredListeners.forEach((listener) => listener()); if (this._animating) { this._frame = requestAnimationFrame(() => this._runAnimation()); } } _stopAnimation() { cancelAnimationFrame(this._frame); this._animating = false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAnimator, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAnimator, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindAnimator, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); class HeadwindPopoverComponent { constructor(_headwindOverlayService, _headwindPopoverService, _headwindAnimator) { this._headwindOverlayService = _headwindOverlayService; this._headwindPopoverService = _headwindPopoverService; this._headwindAnimator = _headwindAnimator; this._popoverPositionListener = () => { if (this.popoverButton) { this.popoverOverlay?.updatePosition(this.popoverButton.nativeElement); } }; this._headwindPopoverService.openPopover.pipe(takeUntilDestroyed()).subscribe(() => { this.open(); }); this._headwindPopoverService.closePopover.pipe(takeUntilDestroyed()).subscribe(() => { this.close(); }); } ngOnDestroy() { clearTimeout(this._openTimeoutId); clearTimeout(this._closeTimeoutId); } open() { if (!this.popoverOverlayTemplate) { console.error(HeadwindErrors.headwindError0003); return; } if (this._popoverEmbeddedViewRef) { return; } this._popoverEmbeddedViewRef = this._headwindOverlayService.open(this.popoverOverlayTemplate.templateRef, () => this._afterClosed()); this._headwindPopoverService.popoverOpened = true; this._headwindAnimator.addListener(this._popoverPositionListener); clearTimeout(this._openTimeoutId); this._openTimeoutId = setTimeout(() => { if (!this.popoverOverlay) { console.error(HeadwindErrors.headwindError0004); return; } }); } close() { this._popoverEmbeddedViewRef?.destroy(); this._afterClosed(); } _afterClosed() { clearTimeout(this._closeTimeoutId); // to prevent instant re-opening after closing, set a delay this._closeTimeoutId = setTimeout(() => { this._headwindAnimator.removeListener(this._popoverPositionListener); this._headwindPopoverService.popoverOpened = false; delete this._popoverEmbeddedViewRef; }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverComponent, deps: [{ token: HeadwindOverlayService }, { token: HeadwindPopoverService }, { token: HeadwindAnimator }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindPopoverComponent, isStandalone: true, selector: "headwind-popover", host: { classAttribute: "headwind-popover" }, providers: [HeadwindPopoverService], queries: [{ propertyName: "popoverOverlayTemplate", first: true, predicate: HeadwindPopoverOverlayDirective, descendants: true }, { propertyName: "popoverOverlay", first: true, predicate: HeadwindPopoverOverlayComponent, descendants: true }, { propertyName: "popoverButton", first: true, predicate: HeadwindPopoverButtonDirective, descendants: true }], ngImport: i0, template: "<ng-content></ng-content>\r\n", styles: [""] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindPopoverComponent, decorators: [{ type: Component, args: [{ selector: 'headwind-popover', standalone: true, imports: [], host: { class: 'headwind-popover', }, providers: [HeadwindPopoverService], template: "<ng-content></ng-content>\r\n" }] }], ctorParameters: () => [{ type: HeadwindOverlayService }, { type: HeadwindPopoverService }, { type: HeadwindAnimator }], propDecorators: { popoverOverlayTemplate: [{ type: ContentChild, args: [HeadwindPopoverOverlayDirective, { descendants: true }] }], popoverOverlay: [{ type: ContentChild, args: [HeadwindPopoverOverlayComponent, { descendants: true }] }], popoverButton: [{ type: ContentChild, args: [HeadwindPopoverButtonDirective, { descendants: true }] }] } }); class HeadwindRadioSelectedDirective { constructor(templateRef) { this.templateRef = templateRef; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindRadioSelectedDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.13", type: HeadwindRadioSelectedDirective, isStandalone: true, selector: "ng-template[headwindRadioSelected]", ngImport: i0 }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: HeadwindRadioSelectedDirective, decorators: [{ type: Directive, args: [{ selector: 'ng-template[headwindRadioSelected]', standalone: true, }] }], ctorParameters: () => [{ type: i0.TemplateRef }] }); class HeadwindRadioButtonComponent { constructor() { /** Internal selected status to show and hide selected icon */ this._selected = false; } get selected() { return this._selected; } set selected(value) { this._selected = value; this._renderRadioSelected(); } ngAfterViewInit() { this._renderRadioSelected(); } _renderRadioSelected() { if (this.radioSelectedContainer && this.radioSelected) { if (this.selected) { if (!this._selectedEmbeddedViewRef) { this._selectedEmbeddedViewRef = this.radioSelectedContainer.createEmbeddedView(this.radioSelected.templateRef); } } else { this._selectedEmbeddedViewRef?.dest