@favian/headwind-ui
Version:
Headless UI for Angular - Styleless Angular components to integrate with Tailwind CSS
976 lines (953 loc) • 103 kB
JavaScript
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