UNPKG

@fsegurai/ngx-sidenav

Version:

Angular component for creating a resizeable sidenav with drag and drop functionality.

824 lines 83.6 kB
import * as i0 from '@angular/core';
import { input, ViewEncapsulation, Component, inject, ViewContainerRef, Directive, signal, computed, Injectable, output, viewChild, HostListener, HostBinding } from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import * as i1 from '@angular/material/icon';
import { MatIconModule } from '@angular/material/icon';
import * as i2 from '@angular/material/tooltip';
import { MatTooltipModule } from '@angular/material/tooltip';
import { NgOptimizedImage } from '@angular/common';

class LinkItem {
    constructor() {
        // ? === Public inputs signals ===
        this.routerLink = input(null, ...(ngDevMode ? [{ debugName: "routerLink" }] : []));
        this.routerLinkActiveOptions = input({ exact: true }, ...(ngDevMode ? [{ debugName: "routerLinkActiveOptions" }] : []));
        this.isTitle = input(false, ...(ngDevMode ? [{ debugName: "isTitle" }] : []));
        this.isParent = input(false, ...(ngDevMode ? [{ debugName: "isParent" }] : []));
        this.isDisabled = input(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: LinkItem, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.1.3", type: LinkItem, isStandalone: true, selector: "ngx-sidenav-link-item", inputs: { routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, routerLinkActiveOptions: { classPropertyName: "routerLinkActiveOptions", publicName: "routerLinkActiveOptions", isSignal: true, isRequired: false, transformFunction: null }, isTitle: { classPropertyName: "isTitle", publicName: "isTitle", isSignal: true, isRequired: false, transformFunction: null }, isParent: { classPropertyName: "isParent", publicName: "isParent", isSignal: true, isRequired: false, transformFunction: null }, isDisabled: { classPropertyName: "isDisabled", publicName: "isDisabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<a class=\"link-item\"\n   [class.is-parent]=\"isParent()\"\n   [class.is-title]=\"isTitle()\"\n   [routerLink]=\"routerLink()\"\n   routerLinkActive=\"active\"\n   [routerLinkActiveOptions]=\"routerLinkActiveOptions()\">\n\n  <span class=\"link-content-container\">\n    <ng-content></ng-content>\n  </span>\n</a>", styles: [".link-item{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;gap:8px;box-sizing:border-box;padding:var(--sidenav-link-padding, 8px 12px);margin-left:var(--sidenav-link-margin-left, 12px);min-width:calc(var(--min-expanded-sidenav-width, 180px) - 32px);text-decoration:none;color:var(--sidenav-link-text, #222);background:var(--sidenav-link-bg, transparent);transition:background .18s cubic-bezier(.4,0,.2,1),color .18s cubic-bezier(.4,0,.2,1),border-color .18s cubic-bezier(.4,0,.2,1);cursor:pointer;outline:none;border-left:2px solid var(--sidenav-link-border, #bdbdbd)}.link-item.is-title,.link-item.is-parent{border-left:none;font-weight:700;color:var(--sidenav-link-title-text, inherit);background:var(--sidenav-link-title-bg, transparent);margin:.5rem 0;text-decoration:none!important;cursor:default;pointer-events:none}.link-item.is-parent:not(.is-title){color:var(--sidenav-link-parent-text, inherit);background:var(--sidenav-link-parent-bg, inherit);cursor:pointer;pointer-events:auto}.link-item:not(.is-title):not(.is-parent){cursor:pointer;pointer-events:auto}.link-item:not(.is-title):not(.is-parent):hover,.link-item:not(.is-title):not(.is-parent):focus-visible{background:var(--sidenav-link-hover-bg);border-left-color:var(--sidenav-link-hover-border);color:var(--sidenav-link-hover-text);text-decoration:none;outline:2px solid var(--sidenav-link-focus);outline-offset:2px}.link-item .link-content-container{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.link-item.active{font-weight:600;background:var(--sidenav-link-active-bg);color:var(--sidenav-link-active-text);border-left-color:var(--sidenav-link-active-border);border-left-width:3px;outline:2px solid var(--sidenav-link-active-outline);outline-offset:1px}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }], encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: LinkItem, decorators: [{
            type: Component,
            args: [{ selector: 'ngx-sidenav-link-item', imports: [
                        RouterLink,
                        RouterLinkActive,
                    ], encapsulation: ViewEncapsulation.None, template: "<a class=\"link-item\"\n   [class.is-parent]=\"isParent()\"\n   [class.is-title]=\"isTitle()\"\n   [routerLink]=\"routerLink()\"\n   routerLinkActive=\"active\"\n   [routerLinkActiveOptions]=\"routerLinkActiveOptions()\">\n\n  <span class=\"link-content-container\">\n    <ng-content></ng-content>\n  </span>\n</a>", styles: [".link-item{display:flex;flex-direction:row;align-items:center;justify-content:flex-start;gap:8px;box-sizing:border-box;padding:var(--sidenav-link-padding, 8px 12px);margin-left:var(--sidenav-link-margin-left, 12px);min-width:calc(var(--min-expanded-sidenav-width, 180px) - 32px);text-decoration:none;color:var(--sidenav-link-text, #222);background:var(--sidenav-link-bg, transparent);transition:background .18s cubic-bezier(.4,0,.2,1),color .18s cubic-bezier(.4,0,.2,1),border-color .18s cubic-bezier(.4,0,.2,1);cursor:pointer;outline:none;border-left:2px solid var(--sidenav-link-border, #bdbdbd)}.link-item.is-title,.link-item.is-parent{border-left:none;font-weight:700;color:var(--sidenav-link-title-text, inherit);background:var(--sidenav-link-title-bg, transparent);margin:.5rem 0;text-decoration:none!important;cursor:default;pointer-events:none}.link-item.is-parent:not(.is-title){color:var(--sidenav-link-parent-text, inherit);background:var(--sidenav-link-parent-bg, inherit);cursor:pointer;pointer-events:auto}.link-item:not(.is-title):not(.is-parent){cursor:pointer;pointer-events:auto}.link-item:not(.is-title):not(.is-parent):hover,.link-item:not(.is-title):not(.is-parent):focus-visible{background:var(--sidenav-link-hover-bg);border-left-color:var(--sidenav-link-hover-border);color:var(--sidenav-link-hover-text);text-decoration:none;outline:2px solid var(--sidenav-link-focus);outline-offset:2px}.link-item .link-content-container{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.link-item.active{font-weight:600;background:var(--sidenav-link-active-bg);color:var(--sidenav-link-active-text);border-left-color:var(--sidenav-link-active-border);border-left-width:3px;outline:2px solid var(--sidenav-link-active-outline);outline-offset:1px}\n"] }]
        }] });

class ContentAreaDirective {
    constructor() {
        this._viewContainerRef = inject(ViewContainerRef);
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ContentAreaDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
    static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.1.3", type: ContentAreaDirective, isStandalone: true, selector: "[ngxSidenavContentArea]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: ContentAreaDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[ngxSidenavContentArea]',
                }]
        }] });

/**
 * Determines if the given item is a MenuStackItem.
 *
 * @param {SidenavStackItem} item - The item to check.
 * @return {boolean} True if the item is a MenuStackItem, otherwise false.
 */
function isMenuStackItem(item) {
    return item.type === 'menu';
}
/**
 * Determines if the given stack item is a ComponentStackItem.
 *
 * @param {SidenavStackItem} item - The stack item to evaluate.
 * @return {boolean} - Returns true if the item is of type ComponentStackItem, otherwise false.
 */
function isComponentStackItem(item) {
    return item.type === 'component';
}

// LocalStorage keys
const STORAGE_KEY_WIDTH = 'ngx-sidenav-width';
const STORAGE_KEY_EXPANDED = 'ngx-sidenav-expanded';
const STORAGE_KEY_STACK = 'ngx-sidenav-stack';
const STORAGE_KEY_CLEAR_ON_RELOAD = 'ngx-sidenav-clear-on-reload';
class SidenavService {
    // ! === Private constants ===
    #DEBOUNCE_DELAY; // Debounce delay for localStorage writes
    #TRANSITION_DURATION; // Default transition duration in milliseconds
    // ! === Private reactive signals ===
    #minWidth; // Minimum width of the sidenav
    #maxWidth; // Maximum width of the sidenav
    #isExpanded;
    #sidenavWidth;
    #stack;
    // ! === Private internals ===
    #shouldPersistStack;
    #stackItemCounter;
    #contentArea;
    constructor() {
        // ? Public constants
        this.COLLAPSE_WIDTH = 30; // Minimum width for collapse
        this.INITIAL_WIDTH = 300; // Default initial width of the sidenav
        // ! === Private constants ===
        this.#DEBOUNCE_DELAY = 300; // Debounce delay for localStorage writes
        this.#TRANSITION_DURATION = 400; // Default transition duration in milliseconds
        // ! === Private reactive signals ===
        this.#minWidth = signal(250, ...(ngDevMode ? [{ debugName: "#minWidth" }] : [])); // Minimum width of the sidenav
        this.#maxWidth = signal(600, ...(ngDevMode ? [{ debugName: "#maxWidth" }] : [])); // Maximum width of the sidenav
        this.#isExpanded = signal(true, ...(ngDevMode ? [{ debugName: "#isExpanded" }] : []));
        this.#sidenavWidth = signal(this.INITIAL_WIDTH, ...(ngDevMode ? [{ debugName: "#sidenavWidth" }] : []));
        this.#stack = signal([], ...(ngDevMode ? [{ debugName: "#stack" }] : []));
        // ? === Public readonly signals ===
        this.minWidth = this.#minWidth.asReadonly();
        this.maxWidth = this.#maxWidth.asReadonly();
        this.isExpanded = this.#isExpanded.asReadonly();
        this.sidenavWidth = this.#sidenavWidth.asReadonly();
        this.currentMenu = computed(() => {
            const current = this.#stack().at(-1); // Get the last item in the stack
            // Return early if there's no item or if it's not a valid MenuStackItem
            return current && isMenuStackItem(current) ? current : undefined;
        }, ...(ngDevMode ? [{ debugName: "currentMenu" }] : []));
        this.canGoBack = computed(() => this.#stack().length > 1, ...(ngDevMode ? [{ debugName: "canGoBack" }] : []));
        // ? === Public non-reactive state flags ===
        this.isSlidingInFromRight = false;
        this.isSlidingInFromLeft = false;
        // ! === Private internals ===
        this.#shouldPersistStack = true;
        this.#stackItemCounter = 0;
        /**
         * A Map object that stores timeout identifiers returned by the `setTimeout` function.
         * The keys in the Map are string identifiers, representing unique names or categories for each timeout.
         * The values are the actual timeout identifiers, as returned by the `setTimeout` function.
         * This allows for efficient tracking and management of multiple timeouts,
         * such as clearing or updating them when necessary.
         */
        this.#timeouts = new Map();
        // Clear-once flag logic
        const shouldClear = localStorage.getItem(STORAGE_KEY_CLEAR_ON_RELOAD) === 'true';
        if (shouldClear) {
            localStorage.removeItem(STORAGE_KEY_STACK);
            localStorage.removeItem(STORAGE_KEY_CLEAR_ON_RELOAD); // Reset flag
            this.#shouldPersistStack = false; // 👈 prevent saving again
        }
        // Restore basic config
        const storedWidth = Number(localStorage.getItem(STORAGE_KEY_WIDTH));
        if (!isNaN(storedWidth))
            this.#sidenavWidth.set(storedWidth);
        this.#isExpanded.set(localStorage.getItem(STORAGE_KEY_EXPANDED) === 'true');
        // Save stack when the user exits
        window.addEventListener('beforeunload', () => {
            if (this.#shouldPersistStack)
                this.#saveStack();
        });
    }
    /**
     * Marks the application or specific data to be cleared on the next reload
     * by setting a flag in the local storage.
     *
     * @return {void} This method does not return a value.
     */
    clearOnNextReload() {
        localStorage.setItem(STORAGE_KEY_CLEAR_ON_RELOAD, 'true');
    }
    /**
     * Configures the parameters for the sidenav, including minimum width, maximum width,
     * and initial width. Updates the sidenav properties and optionally stores the
     * initial width in local storage.
     *
     * @param {Object} config - Configuration object for the sidenav.
     * @param {number} [config.minWidth] - Specifies the minimum width of the sidenav.
     * @param {number} [config.maxWidth] - Specifies the maximum width of the sidenav.
     * @param {number} [config.initialWidth] - Specifies the initial width of the sidenav.
     * @return {void}
     */
    setConfig({ minWidth, maxWidth, initialWidth }) {
        if (minWidth)
            this.#minWidth.set(minWidth);
        if (maxWidth)
            this.#maxWidth.set(maxWidth);
        if (initialWidth) {
            this.#sidenavWidth.set(initialWidth);
            localStorage.setItem(STORAGE_KEY_WIDTH, String(initialWidth));
        }
        else if (!this.#sidenavWidth()) {
            this.#sidenavWidth.set(this.INITIAL_WIDTH);
            localStorage.setItem(STORAGE_KEY_WIDTH, String(this.#sidenavWidth()));
        }
    }
    /**
     * Adjusts the width of the sidenav, constraining it within the defined minimum and maximum bounds.
     * The adjusted width is stored both in a state variable and in local storage.
     *
     * @param {number} width - The desired width of the sidenav to be set.
     * @return {void} This method does not return a value.
     */
    setSidenavWidth(width) {
        const clampedWidth = Math.min(Math.max(width, this.#minWidth()), this.#maxWidth());
        this.#sidenavWidth.set(clampedWidth);
        this.#debouncedSave(STORAGE_KEY_WIDTH, clampedWidth);
    }
    /**
     * Toggles the state of the sidenav between expanded and collapsed.
     * Updates the local storage to reflect the new state.
     *
     * @return {void} Does not return a value.
     */
    toggleSidenav() {
        this.#isExpanded.update(v => {
            this.#debouncedSave(STORAGE_KEY_EXPANDED, !v);
            return !v;
        });
    }
    /**
     * Sets the dynamic content area by assigning the provided host to an internal property.
     *
     * @param {ContentAreaDirective} host The ContentAreaDirective instance to be set as the dynamic content area.
     * @return {void} No return value.
     */
    setDynamicContentArea(host) {
        this.#contentArea = host;
    }
    /**
     * Pushes a new stack item onto the sidenav stack. The item can be either a menu stack item
     * or a component stack item. Based on the type of the item, the method processes and adds it
     * to the stack accordingly. Additionally, certain UI elements and animations are updated or triggered.
     *
     * @param {SidenavStackItem} item - The stack item to be added. This can either represent a menu
     *                                  (with title, badge, items) or a component (with data and behavior).
     * @return {Promise<void>} - A promise that resolves once the push operation and associated
     *                           animations have been completed.
     */
    push(item) {
        const stackItem = item.type === 'menu'
            ? this.#createMenuStackItem(item.items, item.title, item.badge, item.badgeColor)
            : this.#createComponentStackItem(item.component, item.componentData);
        this.#stack.update(stack => [...stack, stackItem]);
        if (isMenuStackItem(stackItem)) {
            this.#clearContent();
        }
        else {
            this.#setContent(stackItem.component);
        }
        if (this.#shouldPersistStack)
            this.#saveStack();
        return this.#animateInFromTheRight();
    }
    /**
     * Removes the top item from the navigation stack and updates the display.
     * If the stack contains only one or no items, it resolves immediately without making changes.
     * Adjusts the content displayed based on the type of the new top item in the stack.
     * Persists the stack state if configured to do so.
     * Executes an animation to visually represent the stack update.
     *
     * @return {Promise<void>} A promise that resolves when the operation is complete.
     */
    pop() {
        if (this.#stack().length <= 1)
            return Promise.resolve();
        this.#stack.update(stack => stack.slice(0, -1));
        const previous = this.#stack().at(-1);
        if (!previous)
            return Promise.resolve();
        if (isMenuStackItem(previous)) {
            this.#clearContent();
        }
        else if (isComponentStackItem(previous)) {
            this.#setContent(previous.component);
        }
        if (this.#shouldPersistStack)
            this.#saveStack();
        return this.#animateInFromTheLeft();
    }
    /**
     * Restores the stack from local storage if available; otherwise, initializes the stack from the provided root items.
     *
     * @param {StackMenuItem[]} rootItems - An array of StackMenuItem objects representing the root stack menu items.
     * @return {void} Does not return any value.
     */
    restoreStackHybrid(rootItems) {
        const raw = localStorage.getItem(STORAGE_KEY_STACK);
        if (raw)
            this.restoreStackFromStorage();
        if (!this.#stack().length)
            this.restoreStackFromMenuRoot(rootItems);
    }
    /**
     * Restores the stack state from the browser's localStorage. The method retrieves a serialized stack,
     * validates its type, and deserializes appropriate items, reconstructing the stack content.
     * Invalid or unparsable data will result in an empty stack being restored.
     *
     * @return {void} Does not return any value.
     */
    restoreStackFromStorage() {
        const raw = localStorage.getItem(STORAGE_KEY_STACK);
        if (!raw)
            return;
        try {
            const parsed = JSON.parse(raw);
            // Validate type safety
            if (!Array.isArray(parsed)) {
                console.error('Invalid stack format in localStorage:', parsed);
                return;
            }
            this.#clearContent();
            this.#stack.set(parsed
                .filter(item => item.type === 'menu')
                .map(item => this.#deserializeMenuItem(item)));
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : 'Unknown error';
            console.warn('Failed to restore stack from localStorage:', errorMessage);
            this.#stack.set([]);
        }
    }
    /**
     * Restores the menu stack to reflect the current application route based on the given menu root items.
     * It clears the existing menu stack, rebuilds it starting from the root items, and persists the updated stack.
     *
     * @param {StackMenuItem[]} rootItems - An array of StackMenuItem objects representing the root menu structure.
     * @return {void} This method does not return a value.
     */
    restoreStackFromMenuRoot(rootItems) {
        const currentRoute = location.pathname;
        // Always reset the stack
        this.#clearContent();
        this.#stack.set([]);
        // Always start with the root
        this.#stack.update(stack => [...stack, this.#createMenuStackItem(rootItems)]);
        const path = this.#findPathToRoute(rootItems, currentRoute);
        if (!path.length) {
            this.#saveStack();
            return;
        }
        let currentChildren = rootItems;
        for (const item of path) {
            const match = currentChildren.find(i => i.route === item.route || i.label === item.label);
            if (!match)
                continue;
            if (match.children?.length) {
                this.#stack.update(stack => [
                    ...stack,
                    this.#createMenuStackItem(match.children, match.label, match.badge, match.badgeColor),
                ]);
                currentChildren = match.children;
            }
        }
        this.#saveStack(); // Persist
    }
    /**
     * Sets the content of the content area by creating and attaching the provided component.
     * It ensures the content area is not null and the component is a valid constructor function
     * before attempting to create it.
     *
     * @param {Component<unknown>} component - The component to be created and attached to the content area.
     * @return {void} Does not return a value.
     */
    #setContent(component) {
        if (!this.#contentArea)
            return;
        this.#contentArea._viewContainerRef.clear();
        // Type guard to ensure a component is a valid constructor function
        if (!component || typeof component !== 'function') {
            console.warn('Attempted to create a component with an invalid type:', component);
            return;
        }
        this.#contentArea._viewContainerRef.createComponent(component);
    }
    /**
     * Clears the content of the private content area by removing all child views from the view container reference.
     *
     * @return {void} This method does not return any value.
     */
    #clearContent() {
        this.#contentArea?._viewContainerRef.clear();
    }
    /**
     * Animates the element by sliding it in from the left.
     * Sets the `isSlidingInFromLeft` flag to true during the animation.
     * Waits for the duration specified by `sidenavTransitionDuration` before resetting the flag.
     *
     * @return {Promise<void>} A promise that resolves after the animation is complete.
     */
    async #animateInFromTheLeft() {
        this.isSlidingInFromLeft = true;
        await this.#sleep(this.#TRANSITION_DURATION);
        this.isSlidingInFromLeft = false;
    }
    /**
     * Handles the animation by sliding in an element from the right side.
     * Temporarily sets the sliding state to true, waits for the defined transition duration,
     * and then resets the sliding state to false.
     *
     * @return {Promise<void>} A promise that resolves when the animation is complete.
     */
    async #animateInFromTheRight() {
        this.isSlidingInFromRight = true;
        await this.#sleep(this.#TRANSITION_DURATION);
        this.isSlidingInFromRight = false;
    }
    /**
     * Pauses the execution for the specified duration.
     *
     * @param {number} ms - The duration to sleep in milliseconds.
     * @return {Promise<void>} A Promise that resolves after the specified duration.
     */
    #sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
    }
    /**
     * Finds the path to a specific route within a nested structure of menu items.
     *
     * @param {StackMenuItem[]} items - The array of menu items to search through.
     * @param {string} route - The route to find within the menu items.
     * @param {StackMenuItem[]} path - The current path being constructed (default is an empty array).
     * @return {StackMenuItem[]} - An array representing the path to the specified route. If the route is not found, it returns an empty array.
     */
    #findPathToRoute(items, route, path = []) {
        for (const item of items) {
            const newPath = [...path, item];
            if (item.route === route) {
                return newPath;
            }
            if (item.children?.length) {
                const found = this.#findPathToRoute(item.children, route, newPath);
                if (found.length)
                    return found;
            }
        }
        return [];
    }
    /**
     * Creates and returns a menu stack item object with specified properties.
     * Performance optimized with counter-based IDs instead of UUIDs.
     */
    #createMenuStackItem(items, title, badge, badgeColor) {
        return {
            type: 'menu',
            items,
            title,
            badge,
            badgeColor,
            id: `menu-${++this.#stackItemCounter}`, // Much faster than crypto.randomUUID()
            timestamp: Date.now(),
        };
    }
    /**
     * Creates a new component stack item with the specified component and optional data.
     * Performance optimized with counter-based IDs.
     */
    #createComponentStackItem(component, data) {
        return {
            type: 'component',
            component,
            componentData: data,
            id: `component-${++this.#stackItemCounter}`, // Much faster than crypto.randomUUID()
            timestamp: Date.now(),
        };
    }
    /**
     * Deserializes a SerializableStackItem object and converts it into a MenuStackItem.
     * Uses fallback ID generation for better performance.
     */
    #deserializeMenuItem(item) {
        return {
            type: 'menu',
            items: item.items,
            title: item.title,
            badge: item.badge,
            badgeColor: item.badgeColor,
            id: item.id || `menu-${++this.#stackItemCounter}`, // Use counter instead of crypto.randomUUID()
            timestamp: item.timestamp || Date.now(),
        };
    }
    /**
     * Persists the current stack of menu items into localStorage if the stack persistence is enabled.
     * Filters the stack to include only menu stack items, serializes the necessary properties, and stores the result.
     *
     * @return {void} This method does not return any value.
     */
    #saveStack() {
        if (!this.#shouldPersistStack)
            return;
        const serialized = this.#stack()
            .filter(isMenuStackItem)
            .map(item => ({
            type: 'menu',
            title: item.title,
            badge: item.badge,
            badgeColor: item.badgeColor,
            items: item.items,
            id: item.id,
            timestamp: item.timestamp,
        }));
        localStorage.setItem(STORAGE_KEY_STACK, JSON.stringify(serialized));
    }
    /**
     * A Map object that stores timeout identifiers returned by the `setTimeout` function.
     * The keys in the Map are string identifiers, representing unique names or categories for each timeout.
     * The values are the actual timeout identifiers, as returned by the `setTimeout` function.
     * This allows for efficient tracking and management of multiple timeouts,
     * such as clearing or updating them when necessary.
     */
    #timeouts;
    /**
     * Saves a key-value pair to localStorage with a debounced mechanism to prevent rapid consecutive writes.
     *
     * @param {string} key - The key under which the value will be stored in localStorage.
     * @param {unknown} value - The value to be stored, which will be converted to a string.
     * @return {void} This method does not return a value.
     */
    #debouncedSave(key, value) {
        const existingTimeout = this.#timeouts.get(key);
        if (existingTimeout !== undefined) {
            clearTimeout(existingTimeout);
        }
        this.#timeouts.set(key, setTimeout(() => {
            const stringValue = typeof value === 'string' ? value : String(value);
            localStorage.setItem(key, stringValue);
            this.#timeouts.delete(key);
        }, this.#DEBOUNCE_DELAY));
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SidenavService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SidenavService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SidenavService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root',
                }]
        }], ctorParameters: () => [] });

class SidenavMenu {
    constructor() {
        // ! === Private Services ===
        this._sidenav = inject(SidenavService);
        // ? === Public inputs signals ===
        this.items = input([], ...(ngDevMode ? [{ debugName: "items" }] : []));
        this.parentLabel = input(undefined, ...(ngDevMode ? [{ debugName: "parentLabel" }] : []));
        this.parentBadge = input(undefined, ...(ngDevMode ? [{ debugName: "parentBadge" }] : []));
        this.parentBadgeColor = input(undefined, ...(ngDevMode ? [{ debugName: "parentBadgeColor" }] : []));
        // * === Protected computed signals ===
        this.canGoBack = this._sidenav.canGoBack;
        this.currentMenu = this._sidenav.currentMenu;
    }
    onBack() {
        this._sidenav.pop();
    }
    onMenuItemClick(item) {
        if (item.children?.length) {
            const stackItem = {
                type: 'menu',
                items: item.children,
                title: item.label,
                badge: item.badge,
                badgeColor: item.badgeColor,
            };
            // this._sidenav.push(item.children, item.label, item.badge, item.badgeColor);
            this._sidenav.push(stackItem);
        }
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SidenavMenu, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: SidenavMenu, isStandalone: true, selector: "ngx-sidenav-menu", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, parentLabel: { classPropertyName: "parentLabel", publicName: "parentLabel", isSignal: true, isRequired: false, transformFunction: null }, parentBadge: { classPropertyName: "parentBadge", publicName: "parentBadge", isSignal: true, isRequired: false, transformFunction: null }, parentBadgeColor: { classPropertyName: "parentBadgeColor", publicName: "parentBadgeColor", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let goBack = canGoBack();\n@let menuItems = items().length ? items() : (currentMenu()?.items || []);\n\n<ul class=\"sidenav-menu\">\n  @if (goBack) {\n    <li>\n      <ngx-sidenav-link-item (click)=\"onBack()\" [isParent]=\"true\">\n        <div class=\"sidenav-menu-back-btn\">\n          <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n            arrow_back\n          </mat-icon>\n          <span class=\"sidenav-menu-label\">{{ parentLabel() }}</span>\n          @if (parentBadge() && parentBadgeColor()) {\n            <span [class]=\"`sidenav-menu-badge ${parentBadgeColor()}`\">{{ parentBadge() }}</span>\n          }\n        </div>\n      </ngx-sidenav-link-item>\n    </li>\n  }\n\n  @for (item of menuItems; track item) {\n    <li>\n      @if (item.route && !(item.children?.length) && (!item.isParent && !item.isTitle)) {\n        <ngx-sidenav-link-item\n            [routerLink]=\"item.route\"\n            [routerLinkActiveOptions]=\"{ exact: true }\">\n          <div class=\"sidenav-menu-label-container\">\n            @if (item.icon) {\n              @switch (item.iconType) {\n                @case ('svg') {\n                  <img\n                      class=\"sidenav-menu-icon\"\n                      [ngSrc]=\"item.icon\"\n                      alt=\"{{ item.label }} Path\"\n                      width=\"24\"\n                      height=\"24\"\n                      priority>\n                }\n                @case ('material') {\n                  <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n                    {{ item.icon }}\n                  </mat-icon>\n                }\n              }\n            }\n            <span class=\"sidenav-menu-label\">{{ item.label }}</span>\n            @if (item.badge) {\n              <span [class]=\"`sidenav-menu-badge ${item.badgeColor}`\">{{ item.badge }}</span>\n            }\n          </div>\n        </ngx-sidenav-link-item>\n      } @else if (item.children?.length) {\n        <ngx-sidenav-link-item (click)=\"onMenuItemClick(item)\">\n          <div class=\"sidenav-menu-label-container\">\n            @if (item.icon) {\n              @switch (item.iconType) {\n                @case ('svg') {\n                  <img\n                      class=\"sidenav-menu-icon\"\n                      [ngSrc]=\"item.icon\"\n                      alt=\"{{ item.label }} Path\"\n                      width=\"24\"\n                      height=\"24\"\n                      priority>\n                }\n                @case ('material') {\n                  <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n                    {{ item.icon }}\n                  </mat-icon>\n                }\n              }\n            }\n            <span class=\"sidenav-menu-label\">{{ item.label }}</span>\n            <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n              chevron_right\n            </mat-icon>\n            @if (item.badge) {\n              <span [class]=\"`sidenav-menu-badge ${item.badgeColor}`\">{{ item.badge }}</span>\n            }\n          </div>\n        </ngx-sidenav-link-item>\n      } @else {\n        <ngx-sidenav-link-item (click)=\"onBack()\" [isTitle]=\"true\">\n          <div class=\"sidenav-menu-title-container\">\n            @if (item.icon) {\n              @switch (item.iconType) {\n                @case ('svg') {\n                  <img\n                      class=\"sidenav-menu-icon\"\n                      [ngSrc]=\"item.icon\"\n                      alt=\"{{ item.label }} Path\"\n                      width=\"24\"\n                      height=\"24\"\n                      priority>\n                }\n                @case ('material') {\n                  <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n                    {{ item.icon }}\n                  </mat-icon>\n                }\n              }\n            }\n            <span class=\"sidenav-menu-title\">{{ item.label }}</span>\n          </div>\n        </ngx-sidenav-link-item>\n      }\n    </li>\n  }\n</ul>\n", styles: [".sidenav-menu{list-style:none;margin:0;padding:0}.sidenav-menu li{margin:0;padding:0}.sidenav-menu li>ngx-sidenav-link-item{width:100%;border-radius:4px;margin:2px 0;box-shadow:none;transition:box-shadow .18s cubic-bezier(.4,0,.2,1),background .18s cubic-bezier(.4,0,.2,1)}.sidenav-menu li>ngx-sidenav-link-item:not(.is-title):not(.is-parent){cursor:pointer;pointer-events:auto}.sidenav-menu li>ngx-sidenav-link-item:not(.is-title):not(.is-parent):hover,.sidenav-menu li>ngx-sidenav-link-item:not(.is-title):not(.is-parent):focus-visible{background:var(--sidenav-link-hover-bg);box-shadow:0 1px 4px 0 var(--sidenav-link-hover-shadow, rgba(0, 0, 0, .04));outline:2px solid var(--sidenav-link-focus);outline-offset:2px}.sidenav-menu li>ngx-sidenav-link-item.active{background:var(--sidenav-link-active-bg, #e3f2fd);box-shadow:0 2px 8px 0 var(--sidenav-link-active-shadow, rgba(0, 0, 0, .08));font-weight:600;outline:2px solid var(--sidenav-link-active-outline, #1976d2);outline-offset:1px}.sidenav-menu li>ngx-sidenav-link-item.is-title,.sidenav-menu li>ngx-sidenav-link-item.is-parent{background:var(--sidenav-link-title-bg, transparent);color:var(--sidenav-link-title-text, #444);font-weight:700;cursor:default;pointer-events:none;box-shadow:none;outline:none}.sidenav-menu li>ngx-sidenav-link-item.is-parent:not(.is-title){background:var(--sidenav-link-parent-bg, #f5f5f5);color:var(--sidenav-link-parent-text, #333);cursor:pointer;pointer-events:auto}.sidenav-menu li>ngx-sidenav-link-item .sidenav-menu-label-container{font-weight:500;letter-spacing:.01em;font-size:15px;color:var(--sidenav-link-text, #222);transition:color .18s cubic-bezier(.4,0,.2,1)}.sidenav-menu li>ngx-sidenav-link-item.active .sidenav-menu-label-container{color:var(--sidenav-link-active-text, #1976d2)}.sidenav-menu li>ngx-sidenav-link-item:hover .sidenav-menu-label-container,.sidenav-menu li>ngx-sidenav-link-item:focus-visible .sidenav-menu-label-container{color:var(--sidenav-link-hover-text, #1976d2)}.sidenav-menu li>ngx-sidenav-menu{margin-left:16px;border-left:2px solid var(--sidenav-link-border, #bdbdbd);padding-left:8px}.sidenav-menu .sidenav-menu-back-btn,.sidenav-menu .sidenav-menu-title-container,.sidenav-menu .sidenav-menu-label-container{display:flex;flex-direction:row;align-items:center;gap:8px;text-decoration:none}.sidenav-menu .sidenav-menu-icon{font-size:20px;display:inline-flex;align-items:center}.sidenav-menu .sidenav-menu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidenav-menu .sidenav-menu-badge{display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;padding:2px 6px;border-radius:4px;text-transform:var(--badge-text-transform, capitalize);white-space:nowrap;cursor:default;color:color-mix(in srgb,var(--badge-theme, #000) 100%,transparent);background-color:color-mix(in srgb,var(--badge-theme, #000) 20%,transparent);border:1px solid color-mix(in srgb,var(--badge-theme, #000) 60%,rgba(0,0,0,.062745098));backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);box-shadow:inset 0 0 0 1px #0000000a;transition:all .2s ease}.sidenav-menu .sidenav-menu-badge.class,.sidenav-menu .sidenav-menu-badge.undecorated_class{--badge-theme: var(--badge-class)}.sidenav-menu .sidenav-menu-badge.constant,.sidenav-menu .sidenav-menu-badge.const{--badge-theme: var(--badge-constant)}.sidenav-menu .sidenav-menu-badge.decorator{--badge-theme: var(--badge-decorator)}.sidenav-menu .sidenav-menu-badge.directive{--badge-theme: var(--badge-directive)}.sidenav-menu .sidenav-menu-badge.element{--badge-theme: var(--badge-element)}.sidenav-menu .sidenav-menu-badge.enum{--badge-theme: var(--badge-enum)}.sidenav-menu .sidenav-menu-badge.function{--badge-theme: var(--badge-function)}.sidenav-menu .sidenav-menu-badge.interface{--badge-theme: var(--badge-interface)}.sidenav-menu .sidenav-menu-badge.pipe{--badge-theme: var(--badge-pipe)}.sidenav-menu .sidenav-menu-badge.impure-pipe{--badge-theme: var(--badge-impure-pipe)}.sidenav-menu .sidenav-menu-badge.module,.sidenav-menu .sidenav-menu-badge.ng_module{--badge-theme: var(--badge-module)}.sidenav-menu .sidenav-menu-badge.alias,.sidenav-menu .sidenav-menu-badge.type_alias{--badge-theme: var(--badge-alias)}.sidenav-menu .sidenav-menu-badge.block{--badge-theme: var(--badge-block)}.sidenav-menu .sidenav-menu-badge.developer_preview,.sidenav-menu .sidenav-menu-badge.deprecated{--badge-theme: var(--badge-deprecated)}.sidenav-menu .sidenav-menu-title{white-space:normal;overflow-wrap:break-word;word-wrap:break-word;max-width:100%}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: LinkItem, selector: "ngx-sidenav-link-item", inputs: ["routerLink", "routerLinkActiveOptions", "isTitle", "isParent", "isDisabled"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: NgOptimizedImage, selector: "img[ngSrc]", inputs: ["ngSrc", "ngSrcset", "sizes", "width", "height", "decoding", "loading", "priority", "loaderParams", "disableOptimizedSrcset", "fill", "placeholder", "placeholderConfig", "src", "srcset"] }], encapsulation: i0.ViewEncapsulation.None }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: SidenavMenu, decorators: [{
            type: Component,
            args: [{ selector: 'ngx-sidenav-menu', imports: [RouterLink, LinkItem, MatIconModule, NgOptimizedImage], encapsulation: ViewEncapsulation.None, template: "@let goBack = canGoBack();\n@let menuItems = items().length ? items() : (currentMenu()?.items || []);\n\n<ul class=\"sidenav-menu\">\n  @if (goBack) {\n    <li>\n      <ngx-sidenav-link-item (click)=\"onBack()\" [isParent]=\"true\">\n        <div class=\"sidenav-menu-back-btn\">\n          <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n            arrow_back\n          </mat-icon>\n          <span class=\"sidenav-menu-label\">{{ parentLabel() }}</span>\n          @if (parentBadge() && parentBadgeColor()) {\n            <span [class]=\"`sidenav-menu-badge ${parentBadgeColor()}`\">{{ parentBadge() }}</span>\n          }\n        </div>\n      </ngx-sidenav-link-item>\n    </li>\n  }\n\n  @for (item of menuItems; track item) {\n    <li>\n      @if (item.route && !(item.children?.length) && (!item.isParent && !item.isTitle)) {\n        <ngx-sidenav-link-item\n            [routerLink]=\"item.route\"\n            [routerLinkActiveOptions]=\"{ exact: true }\">\n          <div class=\"sidenav-menu-label-container\">\n            @if (item.icon) {\n              @switch (item.iconType) {\n                @case ('svg') {\n                  <img\n                      class=\"sidenav-menu-icon\"\n                      [ngSrc]=\"item.icon\"\n                      alt=\"{{ item.label }} Path\"\n                      width=\"24\"\n                      height=\"24\"\n                      priority>\n                }\n                @case ('material') {\n                  <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n                    {{ item.icon }}\n                  </mat-icon>\n                }\n              }\n            }\n            <span class=\"sidenav-menu-label\">{{ item.label }}</span>\n            @if (item.badge) {\n              <span [class]=\"`sidenav-menu-badge ${item.badgeColor}`\">{{ item.badge }}</span>\n            }\n          </div>\n        </ngx-sidenav-link-item>\n      } @else if (item.children?.length) {\n        <ngx-sidenav-link-item (click)=\"onMenuItemClick(item)\">\n          <div class=\"sidenav-menu-label-container\">\n            @if (item.icon) {\n              @switch (item.iconType) {\n                @case ('svg') {\n                  <img\n                      class=\"sidenav-menu-icon\"\n                      [ngSrc]=\"item.icon\"\n                      alt=\"{{ item.label }} Path\"\n                      width=\"24\"\n                      height=\"24\"\n                      priority>\n                }\n                @case ('material') {\n                  <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n                    {{ item.icon }}\n                  </mat-icon>\n                }\n              }\n            }\n            <span class=\"sidenav-menu-label\">{{ item.label }}</span>\n            <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n              chevron_right\n            </mat-icon>\n            @if (item.badge) {\n              <span [class]=\"`sidenav-menu-badge ${item.badgeColor}`\">{{ item.badge }}</span>\n            }\n          </div>\n        </ngx-sidenav-link-item>\n      } @else {\n        <ngx-sidenav-link-item (click)=\"onBack()\" [isTitle]=\"true\">\n          <div class=\"sidenav-menu-title-container\">\n            @if (item.icon) {\n              @switch (item.iconType) {\n                @case ('svg') {\n                  <img\n                      class=\"sidenav-menu-icon\"\n                      [ngSrc]=\"item.icon\"\n                      alt=\"{{ item.label }} Path\"\n                      width=\"24\"\n                      height=\"24\"\n                      priority>\n                }\n                @case ('material') {\n                  <mat-icon role=\"img\" class=\"material-symbols-outlined\">\n                    {{ item.icon }}\n                  </mat-icon>\n                }\n              }\n            }\n            <span class=\"sidenav-menu-title\">{{ item.label }}</span>\n          </div>\n        </ngx-sidenav-link-item>\n      }\n    </li>\n  }\n</ul>\n", styles: [".sidenav-menu{list-style:none;margin:0;padding:0}.sidenav-menu li{margin:0;padding:0}.sidenav-menu li>ngx-sidenav-link-item{width:100%;border-radius:4px;margin:2px 0;box-shadow:none;transition:box-shadow .18s cubic-bezier(.4,0,.2,1),background .18s cubic-bezier(.4,0,.2,1)}.sidenav-menu li>ngx-sidenav-link-item:not(.is-title):not(.is-parent){cursor:pointer;pointer-events:auto}.sidenav-menu li>ngx-sidenav-link-item:not(.is-title):not(.is-parent):hover,.sidenav-menu li>ngx-sidenav-link-item:not(.is-title):not(.is-parent):focus-visible{background:var(--sidenav-link-hover-bg);box-shadow:0 1px 4px 0 var(--sidenav-link-hover-shadow, rgba(0, 0, 0, .04));outline:2px solid var(--sidenav-link-focus);outline-offset:2px}.sidenav-menu li>ngx-sidenav-link-item.active{background:var(--sidenav-link-active-bg, #e3f2fd);box-shadow:0 2px 8px 0 var(--sidenav-link-active-shadow, rgba(0, 0, 0, .08));font-weight:600;outline:2px solid var(--sidenav-link-active-outline, #1976d2);outline-offset:1px}.sidenav-menu li>ngx-sidenav-link-item.is-title,.sidenav-menu li>ngx-sidenav-link-item.is-parent{background:var(--sidenav-link-title-bg, transparent);color:var(--sidenav-link-title-text, #444);font-weight:700;cursor:default;pointer-events:none;box-shadow:none;outline:none}.sidenav-menu li>ngx-sidenav-link-item.is-parent:not(.is-title){background:var(--sidenav-link-parent-bg, #f5f5f5);color:var(--sidenav-link-parent-text, #333);cursor:pointer;pointer-events:auto}.sidenav-menu li>ngx-sidenav-link-item .sidenav-menu-label-container{font-weight:500;letter-spacing:.01em;font-size:15px;color:var(--sidenav-link-text, #222);transition:color .18s cubic-bezier(.4,0,.2,1)}.sidenav-menu li>ngx-sidenav-link-item.active .sidenav-menu-label-container{color:var(--sidenav-link-active-text, #1976d2)}.sidenav-menu li>ngx-sidenav-link-item:hover .sidenav-menu-label-container,.sidenav-menu li>ngx-sidenav-link-item:focus-visible .sidenav-menu-label-container{color:var(--sidenav-link-hover-text, #1976d2)}.sidenav-menu li>ngx-sidenav-menu{margin-left:16px;border-left:2px solid var(--sidenav-link-border, #bdbdbd);padding-left:8px}.sidenav-menu .sidenav-menu-back-btn,.sidenav-menu .sidenav-menu-title-container,.sidenav-menu .sidenav-menu-label-container{display:flex;flex-direction:row;align-items:center;gap:8px;text-decoration:none}.sidenav-menu .sidenav-menu-icon{font-size:20px;display:inline-flex;align-items:center}.sidenav-menu .sidenav-menu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidenav-menu .sidenav-menu-badge{display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:600;padding:2px 6px;border-radius:4px;text-transform:var(--badge-text-transform, capitalize);white-space:nowrap;cursor:default;color:color-mix(in srgb,var(--badge-theme, #000) 100%,transparent);background-color:color-mix(in srgb,var(--badge-theme, #000) 20%,transparent);border:1px solid color-mix(in srgb,var(--badge-theme, #000) 60%,rgba(0,0,0,.062745098));backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);box-shadow:inset 0 0 0 1px #0000000a;transition:all .2s ease}.sidenav-menu .sidenav-menu-badge.class,.sidenav-menu .sidenav-menu-badge.undecorated_class{--badge-theme: var(--badge-class)}.sidenav-menu .sidenav-menu-badge.constant,.sidenav-menu .sidenav-menu-badge.const{--badge-theme: var(--badge-constant)}.sidenav-menu .sidenav-menu-badge.decorator{--badge-theme: var(--badge-decorator)}.sidenav-menu .sidenav-menu-badge.directive{--badge-theme: var(--badge-directive)}.sidenav-menu .sidenav-menu-badge.element{--badge-theme: var(--badge-element)}.sidenav-menu .sidenav-menu-badge.enum{--badge-theme: var(--badge-enum)}.sidenav-menu .sidenav-menu-badge.function{--badge-theme: var(--badge-function)}.sidenav-menu .sidenav-menu-badge.interface{--badge-theme: var(--badge-interface)}.sidenav-menu .sidenav-menu-badge.pipe{--badge-theme: var(--badge-pipe)}.sidenav-menu .sidenav-menu-badge.impure-pipe{--badge-theme: var(--badge-impure-pipe)}.sidenav-menu .sidenav-menu-badge.module,.sidenav-menu .sidenav-menu-badge.ng_module{--badge-theme: var(--badge-module)}.sidenav-menu .sidenav-menu-badge.alias,.sidenav-menu .sidenav-menu-badge.type_alias{--badge-theme: var(--badge-alias)}.sidenav-menu .sidenav-menu-badge.block{--badge-theme: var(--badge-block)}.sidenav-menu .sidenav-menu-badge.developer_preview,.sidenav-menu .sidenav-menu-badge.deprecated{--badge-theme: var(--badge-deprecated)}.sidenav-menu .sidenav-menu-title{white-space:normal;overflow-wrap:break-word;word-wrap:break-word;max-width:100%}\n"] }]
        }] });

class Sidenav {
    constructor() {
        // ! === Private Services ===
        this._sidenav = inject(SidenavService);
        // ? === Public inputs signals ===
        this.collapseWidth = input(this._sidenav.COLLAPSE_WIDTH, ...(ngDevMode ? [{ debugName: "collapseWidth" }] : [])); // Minimum width for collapse
        this.minWidth = input(this._sidenav.minWidth(), ...(ngDevMode ? [{ debugName: "minWidth" }] : [])); // Minimum width for sidenav
        this.maxWidth = input(this._sidenav.maxWidth(), ...(ngDevMode ? [{ debugName: "maxWidth" }] : [])); // Maximum width for sidenav
        this.initialWidth = input(this._sidenav.INITIAL_WIDTH, ...(ngDevMode ? [{ debugName: "initialWidth" }] : [])); // Initial width for sidenav
        this.clearCacheOnReload = input(false, ...(ngDevMode ? [{ debugName: "clearCacheOnReload" }] : [])); // Clear cache on reload
        this.position = input('left', ...(ngDevMode ? [{ debugName: "position" }] : []));
        this.direction = input('ltr', ...(ngDevMode ? [{ debugName: "direction" }] : [])); // RTL support
        this.backdrop = input(false, ...(ngDevMode ? [{ debugName: "backdrop" }] : [])); // Backdrop support
        this.canCollapse = input(true, ...(ngDevMode ? [{ debugName: "canCollapse" }] : [])); // Allow collapsing
        this.canResize = input(true, ...(ngDevMode ? [{ debugName: "canResize" }] : [])); // Allow resizing
        this.menuIconCollapse = input('menu_open', ...(ngDevMode ? [{ debugName: "menuIconCollapse" }] : [])); // Icon for collapsed menu
        this.menuIconExpand = input('menu', ...(ngDevMode ? [{ debugName: "menuIconExpand" }] : [])); // Icon for expanded menu
        // ? === Public output signals ===
        this.opened = output();
        this.closed = output();
        this.resized = output();
        // ! === Private view children ===
        this.sidenavContentArea = viewChild(ContentAreaDirective, ...(ngDevMode ? [{ debugName: "sidenavContentArea" }] : []));
        // * === Protected computed signals ===
        this.currentMenu = this._sidenav.currentMenu;
        this.resizingEvent = {
            isResizing: false,
            startingCursorX: 0,
            startingWidth: 0,
        };
        // ! === Private reactive signals ===
        this.#expanded = this._sidenav.isExpanded;
        this.#width = this._sidenav.sidenavWidth;
    }
    // ! === Private reactive signals ===
    #expanded;
    #width;
    ngOnInit() {
        const sidenavContentArea = this.sidenavContentArea();
        if (!sidenavContentArea)
            throw new Error('Sidenav content area is not defined');
        this._sidenav.setDynamicContentArea(sidenavContentArea);
        this._sidenav.setConfig({
            minWidth: this.minWidth(),
            maxWidth: this.maxWidth(),
            initialWidth: this.initialWidth(),
        });
        const rootItems = this.currentMenu()?.items ?? [];
        this._sidenav.restoreStackHybrid(rootItems); // 👈 key line
        this.#updateSidenavVariables(); // Set initial CSS variables for better reactivity
        this.#clearSidenavCache();
    }
    /**
     * Getter method to determine if resizing is in progress.
     *
     * @return {boolean} Returns true if a resizing event is currently active, false otherwise.
     */
    get isResizing() {
        return this.resizingEvent.isResizing;
    }
    /**
     * Determines if the element is in an expanded state by invoking the `expanded` method.
     *
     * @return {boolean} Returns true if the element is expanded, otherwise false.
     */
    get isExpanded() {
        return this.#expanded();
    }
    /**
     * Retrieves the dynamic width of the host element based on the sidenav service
     * and the component's expanded or collapsed state.
     *
     * @return {string} The calculated width of the host element as a string, e.g., "250 px" or a CSS variable value.
     */
    get hostWidth() {
        // Always use the width from the service for reactivity
        const width = this.#width();
        return this.isExpanded
            ? `${width}px`
            : 'var(--sidenav-collapsed-width, 56px)';
    }
    /**
     * Determines if the sidenav is positioned to the right.
     *
     * @return {boolean} Returns true if the sidenav's position is 'right', false otherwise.
     */
    get isRight() {
        return this.position() === 'right';
    }
    /**
     * Updates the width of the sidenav during a resize operation based on mouse movement.
     *
     * @param {MouseEvent} event The mouse event containing the current position of the cursor.
     * @return {void}
     */
    updateSidenavWidth(event) {
        if (!this.resizingEvent.isResizing)
            return;
        const cursorDeltaX = event.clientX - this.resizingEvent.startingCursorX;
        const newWidth = this.resizingEvent.startingWidth + cursorDeltaX;
        this.#calculateSidenavWidth(newWidth);
    }
    /**
     * Stops the resizing action and resets the cursor style when the mouse button is released.
     *
     * @return {void} Does not return a value.
     */
    stopResizing() {
        if (this.resizingEvent.isResizing) {
            this.resizingEvent.isResizing = false;
            document.body.style.cursor = '';
        }
    }
    /**
     * Handles the keydown events for specific elements and performs actions such as resizing a sidebar or toggling navigation.
     *
     * @param {KeyboardEvent} event The keyboard event triggered by the user.
     * @return {void} Does not return a value.
     */
    handleKeydown(event) {
        if (event.target && event.target.classList.contains('resize-handle')) {
            if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
                const delta = event.key === 'ArrowLeft' ? -10 : 10;
                const newWidth = this.#width() + delta;
                this.#calculateSidenavWidth(newWidth);
                event.preventDefault();
            }
        }
        if (event.key === 'Escape' && this.#expanded()) {
            this.toggleNav();
            event.preventDefault();
        }
    }
    /**
     * Checks the sliding orientation of the side navigation.
     *
     * @param {'left' | 'right'} sideNav - Specifies the side navigation position (left or right).
     * @return {boolean} Returns true if the sliding orientation matches the specified side navigation position, otherwise false.
     */
    checkSlideOrientation(sideNav) {
        if (this.position() === 'right') {
            return sideNav === 'right' ? this._sidenav.isSlidingInFromRight : this._sidenav.isSlidingInFromLeft;
        }
        return sideNav === 'left' ? this._sidenav.isSlidingInFromLeft : this._sidenav.isSlidingInFromRight;
    }
    /**
     * Toggles the state of the navigation menu by opening it if it is closed or closing it if it is open.
     * Emits an event to indicate whether the navigation menu has been opened or closed.
     *
     * @return {void} This method does not return a value.
     */
    toggleNav() {
        this._sidenav.toggleSidenav();
        if (this.#expanded()) {
            this.closed.emit();
        }
        else {
            this.opened.emit();
        }
    }
    /**
     * Initializes the resizing process for the sidenav component when it is in an expanded state.
     *
     * @param {MouseEvent | TouchEvent} event - The event triggered to start resizing, can be a mouse or touch event.
     * @return {void} This method does not return a value.
     */
    startResizing(event) {
        if (!this.isExpanded)
            return; // Only allow resizing when expanded
        event.preventDefault();
        event.stopPropagation();
        const clientX = this.#getClientX(event);
        this.resizingEvent = {
            isResizing: true,
            startingCursorX: clientX,
            startingWidth: this.#width(),
        };
        document.body.style.cursor = 'ew-resize';
    }
    /**
     * Updates the CSS variables related to the side navigation layout.
     * Sets the values for collapsed width, minimum width, and maximum width
     * dynamically using the corresponding methods for calculation.
     *
     * @return {void} Does not return a value.
     */
    #updateSidenavVariables() {
        document.documentElement.style.setProperty('--sidenav-collapsed-width', `${this.collapseWidth()}px`);
        document.documentElement.style.setProperty('--sidenav-min-width', `${this.minWidth()}px`);
        document.documentElement.style.setProperty('--sidenav-max-width', `${this.maxWidth()}px`);
    }
    /**
     * Adjusts the sidenav width to the specified value while respecting the minimum and maximum width constraints.
     *
     * @param {number} newWidth The new desired width for the sidenav.
     * @return {void} This method does not return a value.
     */
    #calculateSidenavWidth(newWidth) {
        const minWidth = this.minWidth() ?? parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidenav-min-width'), 10);
        const maxWidth = this.maxWidth() ?? parseInt(getComputedStyle(document.documentElement).getPropertyValue('--sidenav-max-width'), 10);
        if (minWidth)
            newWidth = Math.max(minWidth, newWidth);
        if (maxWidth)
            newWidth = Math.min(maxWidth, newWidth);
        this._sidenav.setSidenavWidth(newWidth);
        this.resized.emit(newWidth);
    }
    /**
     * Clears the sidenav cache if the conditions are met.
     * Specifically, it checks whether the cache should be cleared on reload
     * and triggers a clear operation for the sidenav on the next reload.
     *
     * @return {void} No return value.
     */
    #clearSidenavCache() {
        if (this.clearCacheOnReload())
            this._sidenav.clearOnNextReload();
    }
    /**
     * Retrieves the clientX coordinate from a MouseEvent or the first touchpoint in a TouchEvent.
     *
     * @param {MouseEvent | TouchEvent} event - The input event from which the clientX coordinate is extracted. Can be a MouseEvent or a TouchEvent.
     * @return {number} The clientX coordinate of the event. If the event is a MouseEvent, returns its clientX value. If the event is a TouchEvent with at least one touchpoint, it returns the clientX value of the first touch. Returns 0 if no valid clientX value is found.
     */
    #getClientX(event) {
        if (event instanceof MouseEvent) {
            return event.clientX;
        }
        else if (event instanceof TouchEvent && event.touches.length > 0) {
            return event.touches[0].clientX;
        }
        return 0;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: Sidenav, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.3", type: Sidenav, isStandalone: true, selector: "ngx-sidenav", inputs: { collapseWidth: { classPropertyName: "collapseWidth", publicName: "collapseWidth", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, initialWidth: { classPropertyName: "initialWidth", publicName: "initialWidth", isSignal: true, isRequired: false, transformFunction: null }, clearCacheOnReload: { classPropertyName: "clearCacheOnReload", publicName: "clearCacheOnReload", isSignal: true, isRequired: false, transformFunction: null }, position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, backdrop: { classPropertyName: "backdrop", publicName: "backdrop", isSignal: true, isRequired: false, transformFunction: null }, canCollapse: { classPropertyName: "canCollapse", publicName: "canCollapse", isSignal: true, isRequired: false, transformFunction: null }, canResize: { classPropertyName: "canResize", publicName: "canResize", isSignal: true, isRequired: false, transformFunction: null }, menuIconCollapse: { classPropertyName: "menuIconCollapse", publicName: "menuIconCollapse", isSignal: true, isRequired: false, transformFunction: null }, menuIconExpand: { classPropertyName: "menuIconExpand", publicName: "menuIconExpand", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { opened: "opened", closed: "closed", resized: "resized" }, host: { listeners: { "window:mousemove": "updateSidenavWidth($event)", "window:mouseup": "stopResizing()", "keydown": "handleKeydown($event)" }, properties: { "class.resizing": "this.isResizing", "class.is-expanded": "this.isExpanded", "style.width": "this.hostWidth", "class.sidenav-right": "this.isRight" } }, viewQueries: [{ propertyName: "sidenavContentArea", first: true, predicate: ContentAreaDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@let menu = currentMenu();\n@let resizing = resizingEvent;\n\n<!-- Enhanced Toggle Button - moved outside container to always be visible -->\n@if (menu && canCollapse()) {\n  <button\n      class=\"sidenav-toggle-btn\"\n      [class.expanded]=\"isExpanded\"\n      [class.rotated]=\"position() === 'right'\"\n      type=\"button\"\n      (click)=\"toggleNav()\"\n      [attr.aria-label]=\"'Menu Toggle Button'\"\n      tabindex=\"0\">\n    <mat-icon role=\"button\" class=\"material-symbols-outlined\">\n      {{ isExpanded ? menuIconCollapse() : menuIconExpand() }}\n    </mat-icon>\n  </button>\n}\n\n<div class=\"sidenav-container\"\n     [class.is-expanded]=\"isExpanded\"\n     [class.slide-in-from-left]=\"checkSlideOrientation('left')\"\n     [class.slide-in-from-right]=\"checkSlideOrientation('right')\"\n     [attr.dir]=\"direction()\"\n     role=\"navigation\"\n     [attr.aria-label]=\"'Side navigation'\"\n     [attr.aria-expanded]=\"isExpanded\"\n     [attr.aria-orientation]=\"position() === 'left' ? 'vertical' : 'horizontal'\"\n     [attr.aria-modal]=\"backdrop() ? isExpanded : null\"\n     [attr.tabindex]=\"isExpanded ? 0 : -1\">\n\n  <div class=\"sidenav-content\">\n    <!-- Section to load components menu -->\n    <ng-template ngxSidenavContentArea></ng-template>\n\n    <!-- Section to load items menu -->\n    @if (menu) {\n      <ngx-sidenav-menu\n          [items]=\"menu.items\"\n          [parentLabel]=\"menu.title\"\n          [parentBadge]=\"menu.badge\"\n          [parentBadgeColor]=\"menu.badgeColor\"/>\n    }\n  </div>\n</div>\n\n<!-- Optional Backdrop for modal mode -->\n@if (backdrop() && isExpanded) {\n  <div class=\"sidenav-backdrop\"\n       tabindex=\"-1\"\n       aria-hidden=\"true\"\n       (click)=\"toggleNav()\">\n  </div>\n}\n\n<!-- Enhanced Resize Handle with better icon -->\n@if (canResize()) {\n  <div class=\"resize-handle\"\n       tabindex=\"0\"\n       [class.resizing]=\"resizing.isResizing\"\n       (mousedown)=\"startResizing($event)\"\n       (touchstart)=\"startResizing($event)\"\n       [attr.aria-label]=\"'Resize navigation panel'\"\n       role=\"separator\">\n\n    <!-- Always visible resize indicator -->\n    <div class=\"resize-indicator\"></div>\n\n    @if (isExpanded) {\n      <!-- Enhanced resize icon with better styling -->\n      <div class=\"resize-icon\" matTooltip=\"Resize navigation panel\" matTooltipPosition=\"above\"\n           [matTooltipPosition]=\"position() === 'left' ? 'right' : 'left'\">\n        <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <!-- Vertical grip dots pattern for better resize indication -->\n          <circle cx=\"8\" cy=\"5\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"9\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"13\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"17\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"21\" r=\"1\"/>\n\n          <circle cx=\"12\" cy=\"3\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"7\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"11\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"15\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"19\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"23\" r=\"1\"/>\n\n          <circle cx=\"16\" cy=\"5\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"9\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"13\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"17\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"21\" r=\"1\"/>\n        </svg>\n      </div>\n    }\n  </div>\n}\n", styles: [":host{height:100vh;display:block;position:relative;box-sizing:border-box;min-width:var(--sidenav-collapsed-width, 56px);max-width:var(--sidenav-max-width, 400px);transition:width .3s cubic-bezier(.02,.68,.63,.98),min-width .3s cubic-bezier(.02,.68,.63,.98),max-width .3s cubic-bezier(.02,.68,.63,.98);will-change:width,min-width,max-width;background:none;border-right:none;border-left:none;box-shadow:none;padding-inline:var(--sidenav-padding-inline, 1rem);padding-block:var(--sidenav-padding-block, 2rem)}:host.is-expanded{min-width:var(--sidenav-min-width, 180px);overflow:visible}:host.is-expanded .sidenav-container{visibility:visible}:host.is-expanded .toggle-button{right:-14px}:host:not(.is-expanded) .toggle-button{transform:translate(50%)}:host:not(.is-expanded) .sidenav-container{visibility:hidden;pointer-events:none}:host.resizing{transition:none;-webkit-user-select:none;user-select:none}:host.sidenav-right{right:0;left:auto;border-right:none}:host.sidenav-right .toggle-button{left:-14px;right:auto}:host.sidenav-right .resize-handle{left:0;right:auto;border-radius:0 4px 4px 0}.sidenav-container{position:relative;height:100%;background:var(--surface-container);transition:all .3s cubic-bezier(.4,0,.2,1)}.sidenav-container .sidenav-content{height:100%;overflow-y:auto;overflow-x:hidden}.sidenav-container .sidenav-content::-webkit-scrollbar{width:8px}.sidenav-container .sidenav-content::-webkit-scrollbar-track{background:transparent;border-radius:4px}.sidenav-container .sidenav-content::-webkit-scrollbar-thumb{background:var(--outline-variant, #c7c5d0);border-radius:4px}.sidenav-container .sidenav-content::-webkit-scrollbar-thumb:hover{background:var(--outline, #79747e)}.sidenav-toggle-btn{position:absolute;top:45px;right:-17px;z-index:1005;display:flex;align-items:center;gap:8px;padding:5px;background:var(--surface-container-high, #f3f3f3);border:1px solid var(--outline-variant, #c7c5d0);border-radius:20px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 8px #0000001a}.sidenav-toggle-btn:hover{background:var(--surface-container-highest, #eaddff);box-shadow:0 4px 16px #00000026;transform:scale(1.05)}.sidenav-toggle-btn:focus{outline:2px solid var(--primary, #6750a4);outline-offset:2px}.sidenav-toggle-btn.rotated{transform:rotate(180deg)}.sidenav-toggle-btn .back-label{font-size:.875rem;font-weight:500}.sidenav-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000052;z-index:999;cursor:pointer;animation:fadeIn .3s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.resize-handle{position:absolute;top:0;right:-8px;width:16px;height:100%;cursor:col-resize;z-index:1002;display:flex;align-items:center;justify-content:center;background:transparent;transition:all .2s cubic-bezier(.4,0,.2,1)}.resize-handle .resize-indicator{position:absolute;width:3px;height:100%;background:var(--outline-variant, #c7c5d0);border-radius:2px;opacity:.7;transition:all .2s cubic-bezier(.4,0,.2,1)}.resize-handle .resize-icon{width:32px;height:48px;background:var(--surface-container-high, #f3f3f3);border:1px solid var(--outline-variant, #c7c5d0);border-radius:16px;display:flex;align-items:center;justify-content:center;opacity:0;transform:scale(.8);transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 12px #00000026;z-index:1}.resize-handle .resize-icon svg{width:20px;height:20px;color:var(--on-surface-variant, #49454f)}.resize-handle:hover .resize-indicator,.resize-handle:focus .resize-indicator{background:var(--primary, #6750a4);opacity:1;width:4px}.resize-handle:hover .resize-icon,.resize-handle:focus .resize-icon{opacity:1;transform:scale(1);background:var(--primary-container, #eaddff);border-color:var(--primary, #6750a4)}.resize-handle:hover .resize-icon svg,.resize-handle:focus .resize-icon svg{color:var(--on-primary-container, #21005d)}.resize-handle:focus{outline:none}.resize-handle:focus .resize-icon{outline:2px solid var(--primary, #6750a4);outline-offset:2px}.resize-handle.resizing .resize-indicator{background:var(--primary, #6750a4);opacity:1;width:4px}.resize-handle.resizing .resize-icon{opacity:1;transform:scale(1.1);background:var(--primary, #6750a4);border-color:var(--primary, #6750a4)}.resize-handle.resizing .resize-icon svg{color:var(--on-primary, #ffffff)}.resize-handle.mobile-enhanced{width:20px;right:-10px}.resize-handle.mobile-enhanced .resize-grip{position:relative;display:flex;align-items:center;justify-content:center}.resize-handle.mobile-enhanced .resize-icon-enhanced{width:40px;height:56px;background:var(--surface-container-high, #f3f3f3);border:1px solid var(--outline-variant, #c7c5d0);border-radius:20px;display:flex;align-items:center;justify-content:center;opacity:0;transform:scale(.8);transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 12px #00000026}.resize-handle.mobile-enhanced .resize-icon-enhanced svg{width:24px;height:24px;color:var(--on-surface-variant, #49454f)}.resize-handle.mobile-enhanced:hover .resize-icon-enhanced{opacity:1;transform:scale(1);background:var(--primary-container, #eaddff);border-color:var(--primary, #6750a4)}.resize-handle.mobile-enhanced:hover .resize-icon-enhanced svg{color:var(--on-primary-container, #21005d)}.resize-handle.mobile-enhanced.resizing .resize-icon-enhanced{opacity:1;transform:scale(1.1);background:var(--primary, #6750a4);border-color:var(--primary, #6750a4)}.resize-handle.mobile-enhanced.resizing .resize-icon-enhanced svg{color:var(--on-primary, #ffffff)}@media (max-width: 768px){.resize-handle.hide-on-mobile{display:none}}@media (max-width: 768px){:host .sidenav-container{border-radius:0 16px 16px 0;box-shadow:0 8px 32px #0000003d;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}:host .resize-handle{display:none}}@media (prefers-reduced-motion: reduce){.sidenav-container,.toggle-button,.resize-handle,.resize-icon-enhanced{transition-duration:.1s}.sidenav-backdrop{animation-duration:.1s}}\n"], dependencies: [{ kind: "directive", type: ContentAreaDirective, selector: "[ngxSidenavContentArea]" }, { kind: "component", type: SidenavMenu, selector: "ngx-sidenav-menu", inputs: ["items", "parentLabel", "parentBadge", "parentBadgeColor"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.3", ngImport: i0, type: Sidenav, decorators: [{
            type: Component,
            args: [{ selector: 'ngx-sidenav', imports: [ContentAreaDirective, SidenavMenu, MatIconModule, MatTooltipModule], template: "@let menu = currentMenu();\n@let resizing = resizingEvent;\n\n<!-- Enhanced Toggle Button - moved outside container to always be visible -->\n@if (menu && canCollapse()) {\n  <button\n      class=\"sidenav-toggle-btn\"\n      [class.expanded]=\"isExpanded\"\n      [class.rotated]=\"position() === 'right'\"\n      type=\"button\"\n      (click)=\"toggleNav()\"\n      [attr.aria-label]=\"'Menu Toggle Button'\"\n      tabindex=\"0\">\n    <mat-icon role=\"button\" class=\"material-symbols-outlined\">\n      {{ isExpanded ? menuIconCollapse() : menuIconExpand() }}\n    </mat-icon>\n  </button>\n}\n\n<div class=\"sidenav-container\"\n     [class.is-expanded]=\"isExpanded\"\n     [class.slide-in-from-left]=\"checkSlideOrientation('left')\"\n     [class.slide-in-from-right]=\"checkSlideOrientation('right')\"\n     [attr.dir]=\"direction()\"\n     role=\"navigation\"\n     [attr.aria-label]=\"'Side navigation'\"\n     [attr.aria-expanded]=\"isExpanded\"\n     [attr.aria-orientation]=\"position() === 'left' ? 'vertical' : 'horizontal'\"\n     [attr.aria-modal]=\"backdrop() ? isExpanded : null\"\n     [attr.tabindex]=\"isExpanded ? 0 : -1\">\n\n  <div class=\"sidenav-content\">\n    <!-- Section to load components menu -->\n    <ng-template ngxSidenavContentArea></ng-template>\n\n    <!-- Section to load items menu -->\n    @if (menu) {\n      <ngx-sidenav-menu\n          [items]=\"menu.items\"\n          [parentLabel]=\"menu.title\"\n          [parentBadge]=\"menu.badge\"\n          [parentBadgeColor]=\"menu.badgeColor\"/>\n    }\n  </div>\n</div>\n\n<!-- Optional Backdrop for modal mode -->\n@if (backdrop() && isExpanded) {\n  <div class=\"sidenav-backdrop\"\n       tabindex=\"-1\"\n       aria-hidden=\"true\"\n       (click)=\"toggleNav()\">\n  </div>\n}\n\n<!-- Enhanced Resize Handle with better icon -->\n@if (canResize()) {\n  <div class=\"resize-handle\"\n       tabindex=\"0\"\n       [class.resizing]=\"resizing.isResizing\"\n       (mousedown)=\"startResizing($event)\"\n       (touchstart)=\"startResizing($event)\"\n       [attr.aria-label]=\"'Resize navigation panel'\"\n       role=\"separator\">\n\n    <!-- Always visible resize indicator -->\n    <div class=\"resize-indicator\"></div>\n\n    @if (isExpanded) {\n      <!-- Enhanced resize icon with better styling -->\n      <div class=\"resize-icon\" matTooltip=\"Resize navigation panel\" matTooltipPosition=\"above\"\n           [matTooltipPosition]=\"position() === 'left' ? 'right' : 'left'\">\n        <svg viewBox=\"0 0 24 24\" fill=\"currentColor\">\n          <!-- Vertical grip dots pattern for better resize indication -->\n          <circle cx=\"8\" cy=\"5\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"9\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"13\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"17\" r=\"1\"/>\n          <circle cx=\"8\" cy=\"21\" r=\"1\"/>\n\n          <circle cx=\"12\" cy=\"3\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"7\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"11\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"15\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"19\" r=\"1\"/>\n          <circle cx=\"12\" cy=\"23\" r=\"1\"/>\n\n          <circle cx=\"16\" cy=\"5\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"9\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"13\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"17\" r=\"1\"/>\n          <circle cx=\"16\" cy=\"21\" r=\"1\"/>\n        </svg>\n      </div>\n    }\n  </div>\n}\n", styles: [":host{height:100vh;display:block;position:relative;box-sizing:border-box;min-width:var(--sidenav-collapsed-width, 56px);max-width:var(--sidenav-max-width, 400px);transition:width .3s cubic-bezier(.02,.68,.63,.98),min-width .3s cubic-bezier(.02,.68,.63,.98),max-width .3s cubic-bezier(.02,.68,.63,.98);will-change:width,min-width,max-width;background:none;border-right:none;border-left:none;box-shadow:none;padding-inline:var(--sidenav-padding-inline, 1rem);padding-block:var(--sidenav-padding-block, 2rem)}:host.is-expanded{min-width:var(--sidenav-min-width, 180px);overflow:visible}:host.is-expanded .sidenav-container{visibility:visible}:host.is-expanded .toggle-button{right:-14px}:host:not(.is-expanded) .toggle-button{transform:translate(50%)}:host:not(.is-expanded) .sidenav-container{visibility:hidden;pointer-events:none}:host.resizing{transition:none;-webkit-user-select:none;user-select:none}:host.sidenav-right{right:0;left:auto;border-right:none}:host.sidenav-right .toggle-button{left:-14px;right:auto}:host.sidenav-right .resize-handle{left:0;right:auto;border-radius:0 4px 4px 0}.sidenav-container{position:relative;height:100%;background:var(--surface-container);transition:all .3s cubic-bezier(.4,0,.2,1)}.sidenav-container .sidenav-content{height:100%;overflow-y:auto;overflow-x:hidden}.sidenav-container .sidenav-content::-webkit-scrollbar{width:8px}.sidenav-container .sidenav-content::-webkit-scrollbar-track{background:transparent;border-radius:4px}.sidenav-container .sidenav-content::-webkit-scrollbar-thumb{background:var(--outline-variant, #c7c5d0);border-radius:4px}.sidenav-container .sidenav-content::-webkit-scrollbar-thumb:hover{background:var(--outline, #79747e)}.sidenav-toggle-btn{position:absolute;top:45px;right:-17px;z-index:1005;display:flex;align-items:center;gap:8px;padding:5px;background:var(--surface-container-high, #f3f3f3);border:1px solid var(--outline-variant, #c7c5d0);border-radius:20px;cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 8px #0000001a}.sidenav-toggle-btn:hover{background:var(--surface-container-highest, #eaddff);box-shadow:0 4px 16px #00000026;transform:scale(1.05)}.sidenav-toggle-btn:focus{outline:2px solid var(--primary, #6750a4);outline-offset:2px}.sidenav-toggle-btn.rotated{transform:rotate(180deg)}.sidenav-toggle-btn .back-label{font-size:.875rem;font-weight:500}.sidenav-backdrop{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#00000052;z-index:999;cursor:pointer;animation:fadeIn .3s cubic-bezier(.4,0,.2,1)}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.resize-handle{position:absolute;top:0;right:-8px;width:16px;height:100%;cursor:col-resize;z-index:1002;display:flex;align-items:center;justify-content:center;background:transparent;transition:all .2s cubic-bezier(.4,0,.2,1)}.resize-handle .resize-indicator{position:absolute;width:3px;height:100%;background:var(--outline-variant, #c7c5d0);border-radius:2px;opacity:.7;transition:all .2s cubic-bezier(.4,0,.2,1)}.resize-handle .resize-icon{width:32px;height:48px;background:var(--surface-container-high, #f3f3f3);border:1px solid var(--outline-variant, #c7c5d0);border-radius:16px;display:flex;align-items:center;justify-content:center;opacity:0;transform:scale(.8);transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 12px #00000026;z-index:1}.resize-handle .resize-icon svg{width:20px;height:20px;color:var(--on-surface-variant, #49454f)}.resize-handle:hover .resize-indicator,.resize-handle:focus .resize-indicator{background:var(--primary, #6750a4);opacity:1;width:4px}.resize-handle:hover .resize-icon,.resize-handle:focus .resize-icon{opacity:1;transform:scale(1);background:var(--primary-container, #eaddff);border-color:var(--primary, #6750a4)}.resize-handle:hover .resize-icon svg,.resize-handle:focus .resize-icon svg{color:var(--on-primary-container, #21005d)}.resize-handle:focus{outline:none}.resize-handle:focus .resize-icon{outline:2px solid var(--primary, #6750a4);outline-offset:2px}.resize-handle.resizing .resize-indicator{background:var(--primary, #6750a4);opacity:1;width:4px}.resize-handle.resizing .resize-icon{opacity:1;transform:scale(1.1);background:var(--primary, #6750a4);border-color:var(--primary, #6750a4)}.resize-handle.resizing .resize-icon svg{color:var(--on-primary, #ffffff)}.resize-handle.mobile-enhanced{width:20px;right:-10px}.resize-handle.mobile-enhanced .resize-grip{position:relative;display:flex;align-items:center;justify-content:center}.resize-handle.mobile-enhanced .resize-icon-enhanced{width:40px;height:56px;background:var(--surface-container-high, #f3f3f3);border:1px solid var(--outline-variant, #c7c5d0);border-radius:20px;display:flex;align-items:center;justify-content:center;opacity:0;transform:scale(.8);transition:all .3s cubic-bezier(.4,0,.2,1);box-shadow:0 4px 12px #00000026}.resize-handle.mobile-enhanced .resize-icon-enhanced svg{width:24px;height:24px;color:var(--on-surface-variant, #49454f)}.resize-handle.mobile-enhanced:hover .resize-icon-enhanced{opacity:1;transform:scale(1);background:var(--primary-container, #eaddff);border-color:var(--primary, #6750a4)}.resize-handle.mobile-enhanced:hover .resize-icon-enhanced svg{color:var(--on-primary-container, #21005d)}.resize-handle.mobile-enhanced.resizing .resize-icon-enhanced{opacity:1;transform:scale(1.1);background:var(--primary, #6750a4);border-color:var(--primary, #6750a4)}.resize-handle.mobile-enhanced.resizing .resize-icon-enhanced svg{color:var(--on-primary, #ffffff)}@media (max-width: 768px){.resize-handle.hide-on-mobile{display:none}}@media (max-width: 768px){:host .sidenav-container{border-radius:0 16px 16px 0;box-shadow:0 8px 32px #0000003d;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}:host .resize-handle{display:none}}@media (prefers-reduced-motion: reduce){.sidenav-container,.toggle-button,.resize-handle,.resize-icon-enhanced{transition-duration:.1s}.sidenav-backdrop{animation-duration:.1s}}\n"] }]
        }], propDecorators: { isResizing: [{
                type: HostBinding,
                args: ['class.resizing']
            }], isExpanded: [{
                type: HostBinding,
                args: ['class.is-expanded']
            }], hostWidth: [{
                type: HostBinding,
                args: ['style.width']
            }], isRight: [{
                type: HostBinding,
                args: ['class.sidenav-right']
            }], updateSidenavWidth: [{
                type: HostListener,
                args: ['window:mousemove', ['$event']]
            }], stopResizing: [{
                type: HostListener,
                args: ['window:mouseup']
            }], handleKeydown: [{
                type: HostListener,
                args: ['keydown', ['$event']]
            }] } });

/*
 * Public API Surface of code-editor
 */

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

export { ContentAreaDirective, LinkItem, Sidenav, SidenavMenu, SidenavService, isComponentStackItem, isMenuStackItem };
//# sourceMappingURL=fsegurai-ngx-sidenav.mjs.map