import { r as registerInstance, h, H as Host, a as getElement } from './index-DBTJNfo7.js';

const masonryLayoutCss = () => `:host(limel-masonry-layout){display:block;position:relative;box-sizing:border-box;visibility:hidden}:host(limel-masonry-layout) ::slotted(*){opacity:0;box-sizing:border-box}:host(limel-masonry-layout.is-laid-out){visibility:visible}:host(limel-masonry-layout.is-laid-out) ::slotted(*){opacity:1}`;

const MasonryLayout = class {
    constructor(hostRef) {
        registerInstance(this, hostRef);
        /**
         * When `true`, items are placed left-to-right in DOM order.
         * When `false` (default), items are placed in the shortest column.
         */
        this.ordered = false;
        this.containerHeight = 0;
        this.hasRendered = false;
        this.pendingLayoutFrame = null;
    }
    onOrderedChange() {
        this.scheduleLayout();
    }
    componentDidLoad() {
        this.setupObservers();
        this.layoutItems();
        this.hasRendered = true;
    }
    connectedCallback() {
        if (this.hasRendered) {
            this.setupObservers();
            this.scheduleLayout();
        }
    }
    disconnectedCallback() {
        var _a, _b, _c;
        (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
        (_b = this.mutationObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
        (_c = this.itemResizeObserver) === null || _c === void 0 ? void 0 : _c.disconnect();
        if (this.pendingLayoutFrame !== null) {
            cancelAnimationFrame(this.pendingLayoutFrame);
        }
    }
    render() {
        const style = {
            height: `${this.containerHeight}rem`,
        };
        return (h(Host, { key: 'e41aecdf81962515dabef9b872cac4a44facf45d', style: style, class: { 'is-laid-out': this.hasRendered } }, h("slot", { key: '94e9603630597a47bef3861a076fca9a8e90dd3c' })));
    }
    setupObservers() {
        this.resizeObserver = new ResizeObserver(() => {
            this.scheduleLayout();
        });
        this.resizeObserver.observe(this.host);
        this.itemResizeObserver = new ResizeObserver(() => {
            this.scheduleLayout();
        });
        this.mutationObserver = new MutationObserver(() => {
            this.observeItemSizes();
            this.scheduleLayout();
        });
        this.mutationObserver.observe(this.host, { childList: true });
        this.observeItemSizes();
    }
    observeItemSizes() {
        var _a;
        (_a = this.itemResizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
        const items = this.getItems();
        for (const item of items) {
            this.itemResizeObserver.observe(item);
        }
    }
    getItems() {
        return [...this.host.children].filter((child) => child instanceof HTMLElement);
    }
    getRootFontSize() {
        return Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
    }
    pxToRem(px, rootFontSize) {
        return px / rootFontSize;
    }
    /**
     * Reads a CSS custom property from the host element and resolves
     * its value to pixels. Falls back to the provided default if the
     * property is not set.
     *
     * @param property - The CSS custom property name to read.
     * @param fallback - The fallback CSS value if the property is not set.
     * @param rootFontSize - The root font size in pixels for rem conversion.
     */
    getCssPropertyInPx(property, fallback, rootFontSize) {
        const value = getComputedStyle(this.host).getPropertyValue(property).trim() ||
            fallback;
        const number = Number.parseFloat(value);
        if (value.endsWith('px')) {
            return number;
        }
        // Assume rem for any other unit (rem is the project standard)
        return number * rootFontSize;
    }
    /**
     * Debounces layout recalculations using `requestAnimationFrame`
     * to coalesce multiple observer callbacks into a single layout
     * pass per frame, avoiding layout thrashing.
     */
    scheduleLayout() {
        if (this.pendingLayoutFrame !== null) {
            cancelAnimationFrame(this.pendingLayoutFrame);
        }
        this.pendingLayoutFrame = requestAnimationFrame(() => {
            this.pendingLayoutFrame = null;
            this.layoutItems();
        });
    }
    layoutItems() {
        const items = this.getItems();
        if (items.length === 0) {
            this.containerHeight = 0;
            return;
        }
        const hostWidth = this.host.offsetWidth;
        if (!hostWidth) {
            return;
        }
        const rootFontSize = this.getRootFontSize();
        const gapPx = this.getCssPropertyInPx('--masonry-layout-gap', '1rem', rootFontSize);
        const { columnCount, columnWidth } = this.calculateColumns(hostWidth, gapPx, rootFontSize);
        const columnHeights = this.positionItems(items, columnCount, columnWidth, gapPx, rootFontSize);
        const newHeight = this.pxToRem(Math.max(...columnHeights) - gapPx, rootFontSize);
        if (Math.abs(newHeight - this.containerHeight) > 0.1) {
            this.containerHeight = newHeight;
        }
    }
    calculateColumns(hostWidth, gapPx, rootFontSize) {
        const minColumnWidthPx = this.getCssPropertyInPx('--masonry-layout-min-column-width', '12rem', rootFontSize);
        const columnCount = Math.max(1, Math.floor((hostWidth + gapPx) / (minColumnWidthPx + gapPx)));
        const columnWidth = (hostWidth - (columnCount - 1) * gapPx) / columnCount;
        return { columnCount, columnWidth };
    }
    /**
     * Absolutely positions each item into the grid. In ordered mode,
     * items are placed left-to-right via round-robin. In default mode,
     * each item is placed in the shortest column for balanced heights.
     *
     * @param items - The child elements to position.
     * @param columnCount - The number of columns in the grid.
     * @param columnWidth - The width of each column in pixels.
     * @param gapPx - The gap between items in pixels.
     * @param rootFontSize - The root font size in pixels for rem conversion.
     * @returns The cumulative height of each column in pixels.
     */
    positionItems(items, columnCount, columnWidth, gapPx, rootFontSize) {
        const columnHeights = Array.from({ length: columnCount }, () => 0);
        let index = 0;
        for (const item of items) {
            const column = this.ordered
                ? index % columnCount
                : columnHeights.indexOf(Math.min(...columnHeights));
            const x = column * (columnWidth + gapPx);
            const y = columnHeights[column];
            item.style.position = 'absolute';
            item.style.left = `${this.pxToRem(x, rootFontSize)}rem`;
            item.style.top = `${this.pxToRem(y, rootFontSize)}rem`;
            item.style.width = `${this.pxToRem(columnWidth, rootFontSize)}rem`;
            if (!this.hasRendered) {
                this.staggerFadeIn(item, index);
            }
            columnHeights[column] += item.offsetHeight + gapPx;
            index++;
        }
        return columnHeights;
    }
    /**
     * Applies a staggered fade-in transition to an item.
     * Inline `transition` and `transitionDelay` styles are set
     * temporarily and cleaned up via a `transitionend` listener
     * so they don't interfere with the consumer's own transitions.
     *
     * The listener filters by `event.target` to avoid reacting to
     * bubbled events from descendant elements.
     *
     * Skipped entirely when `prefers-reduced-motion: reduce` is
     * active — items become visible instantly via the CSS
     * `is-laid-out` class instead.
     *
     * @param item - The element to fade in.
     * @param index - The item's position in the list, used to calculate the delay.
     */
    staggerFadeIn(item, index) {
        const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
        if (prefersReducedMotion) {
            return;
        }
        item.style.transition = 'opacity 0.2s cubic-bezier(0.2, 0, 0.2, 1)';
        item.style.transitionDelay = `${index * 20}ms`;
        const cleanup = (event) => {
            if (event.target !== item || event.propertyName !== 'opacity') {
                return;
            }
            item.style.transition = '';
            item.style.transitionDelay = '';
            item.removeEventListener('transitionend', cleanup);
        };
        item.addEventListener('transitionend', cleanup);
    }
    get host() { return getElement(this); }
    static get watchers() { return {
        "ordered": [{
                "onOrderedChange": 0
            }]
    }; }
};
MasonryLayout.style = masonryLayoutCss();

export { MasonryLayout as limel_masonry_layout };
