import * as i0 from '@angular/core';
import { InjectionToken, viewChild, inject, ChangeDetectorRef, PLATFORM_ID, signal, EventEmitter, ViewEncapsulation, ChangeDetectionStrategy, Component, Injector, TemplateRef, Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { isPlatformBrowser } from '@angular/common';
import { BasePortalOutlet, CdkPortalOutlet, TemplatePortal, ComponentPortal } from '@angular/cdk/portal';
import { Button } from '@ngstarter-ui/components/button';
import { Overlay, OverlayConfig } from '@angular/cdk/overlay';

/** Injection token that can be used to access the data that was passed in to a snack bar. */
const SNACK_BAR_DATA = new InjectionToken('SnackBarData');
/** Injection token that can be used to specify default snack bar. */
const SNACK_BAR_CONFIG = new InjectionToken('SnackBarConfig');
/** Function to provide the snack bar configuration. */
function provideSnackBarConfig(config) {
    return {
        provide: SNACK_BAR_CONFIG,
        useValue: config,
    };
}
class SnackBarConfig {
    /** The politeness level for the MatAriaLiveAnnouncer announcement. */
    politeness = 'assertive';
    /** Message to be announced by the LiveAnnouncer. */
    announcementMessage = '';
    /** The view container that should be used for the snack bar's origin. */
    viewContainerRef;
    /** The duration in milliseconds to wait before automatically closing the snack bar. */
    duration = 0;
    /** Extra CSS classes to be added to the snack bar container. */
    panelClass;
    /** Text layout direction for the snack bar. */
    direction;
    /** The horizontal position to place the snack bar. */
    horizontalPosition = 'center';
    /** The vertical position to place the snack bar. */
    verticalPosition = 'bottom';
    /** Data being injected into the child component. */
    data = null;
}

class SnackBarRef {
    containerInstance;
    _overlayRef;
    /** The instance of the component making up the content of the snack bar. */
    instance;
    /** Subject for notifying the user that the snack bar has been dismissed. */
    _afterDismissed = new Subject();
    /** Subject for notifying the user that the snack bar has opened and appeared. */
    _afterOpened = new Subject();
    constructor(containerInstance, _overlayRef) {
        this.containerInstance = containerInstance;
        this._overlayRef = _overlayRef;
        containerInstance._onExit.subscribe(() => {
            this._overlayRef.dispose();
            this._afterDismissed.next();
            this._afterDismissed.complete();
        });
        containerInstance._onEnter.subscribe(() => {
            this._afterOpened.next();
            this._afterOpened.complete();
        });
    }
    /** Dismisses the snack bar. */
    dismiss() {
        if (!this._afterDismissed.closed) {
            this.containerInstance.exit();
        }
    }
    /** Gets an observable that is notified when the snack bar is finished closing. */
    afterDismissed() {
        return this._afterDismissed.asObservable();
    }
    /** Gets an observable that is notified when the snack bar has opened and appeared. */
    afterOpened() {
        return this._afterOpened.asObservable();
    }
}

class SnackBarContainer extends BasePortalOutlet {
    snackBarConfig;
    _portalOutlet = viewChild.required(CdkPortalOutlet);
    _cdr = inject(ChangeDetectorRef);
    _platformId = inject(PLATFORM_ID);
    /** State of the snack bar animation. */
    animationState = signal('void', ...(ngDevMode ? [{ debugName: "animationState" }] : /* istanbul ignore next */ []));
    leaving = signal(false, ...(ngDevMode ? [{ debugName: "leaving" }] : /* istanbul ignore next */ []));
    /** Event emitted when the snack bar exit animation is finished. */
    _onExit = new EventEmitter();
    /** Event emitted when the snack bar enter animation is finished. */
    _onEnter = new EventEmitter();
    constructor(snackBarConfig) {
        super();
        this.snackBarConfig = snackBarConfig;
        if (this.snackBarConfig.verticalPosition === 'top') {
            this.animationState.set('hidden-top');
        }
        else {
            this.animationState.set('hidden-bottom');
        }
    }
    /** Attach a component portal as content to this snack bar container. */
    attachComponentPortal(portal) {
        this._assertNotAttached();
        return this._portalOutlet().attachComponentPortal(portal);
    }
    /** Attach a template portal as content to this snack bar container. */
    attachTemplatePortal(portal) {
        this._assertNotAttached();
        return this._portalOutlet().attachTemplatePortal(portal);
    }
    /** Begin animation of snack bar entrance into view. */
    enter() {
        // Ensure we are not in leaving state
        this.leaving.set(false);
        if (!isPlatformBrowser(this._platformId)) {
            this.animationState.set('visible');
            this._cdr.markForCheck();
            return;
        }
        // Use requestAnimationFrame to ensure the initial state (hidden-top/bottom)
        // is applied to the DOM before we change it to 'visible'.
        // We use two nested requestAnimationFrames to ensure at least one paint cycle has occurred.
        // The first rAF captures the current state, the second one allows for the state to be applied
        // and then triggers the transition.
        requestAnimationFrame(() => {
            // Force a reflow to ensure the initial styles are applied.
            // This is sometimes necessary in addition to rAF in some browsers/environments.
            if (typeof document !== 'undefined' && document.body) {
                void document.body.offsetHeight;
            }
            requestAnimationFrame(() => {
                this.animationState.set('visible');
                this._cdr.markForCheck();
            });
        });
    }
    /** Begin animation of snack bar exit from view. */
    exit() {
        const initialState = this.animationState();
        const targetState = this.snackBarConfig.verticalPosition === 'top' ? 'hidden-top' : 'hidden-bottom';
        if (initialState === targetState) {
            this._onExit.emit();
            return;
        }
        // Mark as leaving to use faster transition timing
        this.leaving.set(true);
        this.animationState.set(targetState);
        this._cdr.markForCheck();
    }
    /** Handle end of transition. */
    onTransitionEnd(event) {
        if (event.propertyName !== 'transform' && event.propertyName !== 'opacity') {
            return;
        }
        const state = this.animationState();
        if (state === 'visible' && event.propertyName === 'transform') {
            this._onEnter.emit();
        }
        else if ((state === 'hidden-top' || state === 'hidden-bottom')) {
            if (event.propertyName === 'opacity' || event.propertyName === 'transform') {
                this._onExit.emit();
            }
        }
    }
    /** Asserts that no content is already attached to the container. */
    _assertNotAttached() {
        if (this._portalOutlet().hasAttached()) {
            throw Error('Attempting to attach snack bar content after content has already been attached.');
        }
    }
    ngOnDestroy() {
        this._onExit.complete();
        this._onEnter.complete();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SnackBarContainer, deps: [{ token: SnackBarConfig }], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.4", type: SnackBarContainer, isStandalone: true, selector: "ngs-snack-bar-container", host: { classAttribute: "snack-bar-container-host" }, viewQueries: [{ propertyName: "_portalOutlet", first: true, predicate: CdkPortalOutlet, descendants: true, isSignal: true }], exportAs: ["ngsSnackBarContainer"], usesInheritance: true, ngImport: i0, template: "<div\n  class=\"snack-bar-container\"\n  [class.visible]=\"animationState() === 'visible'\"\n  [class.hidden-top]=\"animationState() === 'hidden-top'\"\n  [class.hidden-bottom]=\"animationState() === 'hidden-bottom'\"\n  [class.leaving]=\"leaving()\"\n  (transitionend)=\"onTransitionEnd($event)\"\n>\n  <ng-template cdkPortalOutlet/>\n</div>\n", styles: [".snack-bar-container{border-radius:4px;box-sizing:border-box;display:block;margin:calc(var(--spacing, .25rem) * 5);max-width:33vw;min-width:344px;min-height:54px;transform-origin:center;padding:0;opacity:0;transition-property:transform,opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(0,0,.2,1)}.snack-bar-container.leaving{transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,1,1)}.snack-bar-container.hidden-top{opacity:0;transform:translateY(-100%)}.snack-bar-container.hidden-bottom{opacity:0;transform:translateY(100%)}.snack-bar-container.visible{opacity:1;transform:translateY(0)}@media(max-width:599px){.snack-bar-container .snack-bar-container{min-width:100%;margin:0;border-radius:0}}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SnackBarContainer, decorators: [{
            type: Component,
            args: [{ selector: 'ngs-snack-bar-container', exportAs: 'ngsSnackBarContainer', standalone: true, imports: [CdkPortalOutlet], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
                        'class': 'snack-bar-container-host'
                    }, template: "<div\n  class=\"snack-bar-container\"\n  [class.visible]=\"animationState() === 'visible'\"\n  [class.hidden-top]=\"animationState() === 'hidden-top'\"\n  [class.hidden-bottom]=\"animationState() === 'hidden-bottom'\"\n  [class.leaving]=\"leaving()\"\n  (transitionend)=\"onTransitionEnd($event)\"\n>\n  <ng-template cdkPortalOutlet/>\n</div>\n", styles: [".snack-bar-container{border-radius:4px;box-sizing:border-box;display:block;margin:calc(var(--spacing, .25rem) * 5);max-width:33vw;min-width:344px;min-height:54px;transform-origin:center;padding:0;opacity:0;transition-property:transform,opacity;transition-duration:.15s;transition-timing-function:cubic-bezier(0,0,.2,1)}.snack-bar-container.leaving{transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,1,1)}.snack-bar-container.hidden-top{opacity:0;transform:translateY(-100%)}.snack-bar-container.hidden-bottom{opacity:0;transform:translateY(100%)}.snack-bar-container.visible{opacity:1;transform:translateY(0)}@media(max-width:599px){.snack-bar-container .snack-bar-container{min-width:100%;margin:0;border-radius:0}}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
        }], ctorParameters: () => [{ type: SnackBarConfig }], propDecorators: { _portalOutlet: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkPortalOutlet), { isSignal: true }] }] } });

class SimpleSnackBar {
    snackBarRef = inject(SnackBarRef);
    data = inject(SNACK_BAR_DATA);
    /** Performs the action on the snack bar. */
    action() {
        this.snackBarRef.dismiss();
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SimpleSnackBar, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.4", type: SimpleSnackBar, isStandalone: true, selector: "ngs-simple-snack-bar", exportAs: ["ngsSimpleSnackBar"], ngImport: i0, template: "<span class=\"simple-snack-bar-message\">{{ data.message }}</span>\n@if (data.action) {\n  <div class=\"simple-snack-bar-action\">\n    <button ngsButton=\"filled\" (click)=\"action()\">{{ data.action }}</button>\n  </div>\n}\n", styles: [":host{display:flex;justify-content:space-between;align-items:center;font-size:var(--ngs-font-size-sm);color:var(--ngs-color-neutral-100);border-radius:var(--ngs-radius-xl);padding:0 calc(var(--spacing, .25rem) * 2.5);background:var(--ngs-color-neutral-950)}:host .simple-snack-bar-message{padding:14px 0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;flex:1 1 auto}:host .simple-snack-bar-action{flex:0 0 auto;margin-left:8px}:host:has(.simple-snack-bar-action){padding-inline-end:calc(var(--spacing, .25rem) * 1.5)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"], dependencies: [{ kind: "component", type: Button, selector: "    button[ngsButton], button[ngsIconButton],    a[ngsButton], a[ngsIconButton]  ", inputs: ["ngsButton", "ngsIconButton", "loading", "disabled", "disabledInteractive", "disableRipple", "reverse", "fullWidth", "hideTextOnMobile"], exportAs: ["ngsButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SimpleSnackBar, decorators: [{
            type: Component,
            args: [{ selector: 'ngs-simple-snack-bar', exportAs: 'ngsSimpleSnackBar', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [
                        Button
                    ], template: "<span class=\"simple-snack-bar-message\">{{ data.message }}</span>\n@if (data.action) {\n  <div class=\"simple-snack-bar-action\">\n    <button ngsButton=\"filled\" (click)=\"action()\">{{ data.action }}</button>\n  </div>\n}\n", styles: [":host{display:flex;justify-content:space-between;align-items:center;font-size:var(--ngs-font-size-sm);color:var(--ngs-color-neutral-100);border-radius:var(--ngs-radius-xl);padding:0 calc(var(--spacing, .25rem) * 2.5);background:var(--ngs-color-neutral-950)}:host .simple-snack-bar-message{padding:14px 0;text-overflow:ellipsis;white-space:nowrap;overflow:hidden;flex:1 1 auto}:host .simple-snack-bar-action{flex:0 0 auto;margin-left:8px}:host:has(.simple-snack-bar-action){padding-inline-end:calc(var(--spacing, .25rem) * 1.5)}\n/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */\n"] }]
        }] });

class SnackBar {
    _overlay = inject(Overlay);
    _injector = inject(Injector);
    _parentSnackBar = inject(SnackBar, { optional: true, skipSelf: true });
    _defaultConfig = inject(SNACK_BAR_CONFIG, { optional: true }) ?? new SnackBarConfig();
    /** Reference to the currently-opened snack bar. */
    _openedSnackBarRef = null;
    /**
     * Opens a snack bar with a message and an optional action.
     */
    open(message, action, config) {
        const _config = { ...this._defaultConfig, ...config };
        _config.data = { message, action };
        return this.openFromComponent(SimpleSnackBar, _config);
    }
    /**
     * Opens a snack bar with a custom component.
     */
    openFromComponent(component, config) {
        return this._attach(component, config);
    }
    /**
     * Opens a snack bar with a custom template.
     */
    openFromTemplate(template, config) {
        return this._attach(template, config);
    }
    /**
     * Dismisses the currently-visible snack bar.
     */
    dismiss() {
        if (this._openedSnackBarRef) {
            this._openedSnackBarRef.dismiss();
        }
    }
    /**
     * Attaches the snack bar container and the appropriate content.
     */
    _attach(content, config) {
        const _config = { ...this._defaultConfig, ...config };
        const overlayRef = this._createOverlay(_config);
        const container = this._attachContainer(overlayRef, _config);
        const snackBarRef = new SnackBarRef(container, overlayRef);
        if (content instanceof TemplateRef) {
            const portal = new TemplatePortal(content, null, {
                $implicit: _config.data,
                snackBarRef,
            });
            snackBarRef.instance = container.attachTemplatePortal(portal);
        }
        else {
            const injector = this._createInjector(_config, snackBarRef);
            const portal = new ComponentPortal(content, undefined, injector);
            const contentRef = container.attachComponentPortal(portal);
            snackBarRef.instance = contentRef.instance;
        }
        // If there is another snack bar already opened, dismiss it before showing the new one.
        if (this._openedSnackBarRef) {
            const oldSnackBarRef = this._openedSnackBarRef;
            oldSnackBarRef.afterDismissed().subscribe(() => {
                container.enter();
            });
            oldSnackBarRef.dismiss();
        }
        else {
            container.enter();
        }
        this._openedSnackBarRef = snackBarRef;
        snackBarRef.afterDismissed().subscribe(() => {
            if (this._openedSnackBarRef === snackBarRef) {
                this._openedSnackBarRef = null;
            }
        });
        if (_config.duration && _config.duration > 0) {
            setTimeout(() => snackBarRef.dismiss(), _config.duration);
        }
        return snackBarRef;
    }
    /**
     * Creates a new overlay and configures it for the snack bar.
     */
    _createOverlay(config) {
        const overlayConfig = new OverlayConfig({
            direction: config.direction,
            hasBackdrop: false,
            scrollStrategy: this._overlay.scrollStrategies.noop(),
            positionStrategy: this._overlay.position()
                .global()
                .centerHorizontally()
                .bottom('0'),
        });
        // Custom positioning based on config
        const positionStrategy = this._overlay.position().global();
        if (config.horizontalPosition === 'start' || config.horizontalPosition === 'left') {
            positionStrategy.left('0');
        }
        else if (config.horizontalPosition === 'end' || config.horizontalPosition === 'right') {
            positionStrategy.right('0');
        }
        else {
            positionStrategy.centerHorizontally();
        }
        if (config.verticalPosition === 'top') {
            positionStrategy.top('0');
        }
        else {
            positionStrategy.bottom('0');
        }
        overlayConfig.positionStrategy = positionStrategy;
        return this._overlay.create(overlayConfig);
    }
    /**
     * Attaches the snack bar container to the overlay.
     */
    _attachContainer(overlayRef, config) {
        const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
        const injector = Injector.create({
            parent: userInjector || this._injector,
            providers: [{ provide: SnackBarConfig, useValue: config }],
        });
        const containerPortal = new ComponentPortal(SnackBarContainer, config.viewContainerRef, injector);
        const containerRef = overlayRef.attach(containerPortal);
        return containerRef.instance;
    }
    /**
     * Creates an injector to be used inside of a snack bar component.
     */
    _createInjector(config, snackBarRef) {
        const userInjector = config && config.viewContainerRef && config.viewContainerRef.injector;
        return Injector.create({
            parent: userInjector || this._injector,
            providers: [
                { provide: SnackBarRef, useValue: snackBarRef },
                { provide: SNACK_BAR_DATA, useValue: config.data },
            ],
        });
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SnackBar, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SnackBar, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImport: i0, type: SnackBar, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { SNACK_BAR_CONFIG, SNACK_BAR_DATA, SimpleSnackBar, SnackBar, SnackBarConfig, SnackBarContainer, SnackBarRef, provideSnackBarConfig };
//# sourceMappingURL=ngstarter-ui-components-snack-bar.mjs.map
