{"version":3,"file":"Masonry.cjs","names":[],"sources":["../../../src/components/Masonry/Masonry.tsx"],"sourcesContent":["import {\n    useCallback,\n    useEffect,\n    useLayoutEffect,\n    useMemo,\n    useRef,\n    useState,\n    type HTMLAttributes,\n    type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/utils/cn\";\n\nimport { columnsFor, distribute } from \"./masonry-layout\";\nimport styles from \"./Masonry.module.css\";\n\n/** DOM attributes this component redefines. */\ntype OverriddenDomProps = \"children\";\n\nexport interface MasonryProps<T> extends Omit<HTMLAttributes<HTMLDivElement>, OverriddenDomProps> {\n    /** What to lay out. */\n    items: readonly T[];\n    /** Render one card. */\n    children: (item: T, index: number) => ReactNode;\n    /** Stable key per item. Index is a fallback nobody should rely on. */\n    itemKey?: (item: T, index: number) => string | number;\n    /**\n     * Column count: a fixed number, or a `width → columns` map read as\n     * \"from this width up\". Default `{ 0: 1, 640: 2, 1024: 3 }`.\n     */\n    columns?: number | Record<number, number>;\n    /** Gap between cards, any CSS length. Default `var(--tempest-space-4)`. */\n    gap?: string;\n}\n\n/** Default breakpoints: one column on a phone, three on a desktop. */\nconst DEFAULT_COLUMNS: Record<number, number> = { 0: 1, 640: 2, 1024: 3 };\n\n/**\n * Masonry layout: cards of uneven height packed into columns with an even bottom\n * edge.\n *\n * Measures the rendered cards and deals each one into the shortest column, rather\n * than using CSS `columns` or `grid-auto-flow: dense`. Both of those are one line\n * of CSS and neither does this job: CSS `columns` breaks a card across the column\n * boundary, and a dense grid keeps every row the height of its tallest cell, which\n * is the ragged bottom edge people reach for masonry to avoid.\n *\n * @example\n * <Masonry items={fotos} itemKey={(foto) => foto.id} columns={{ 0: 1, 700: 2, 1100: 4 }}>\n *     {(foto) => <img src={foto.url} alt={foto.alt} />}\n * </Masonry>\n */\nexport function Masonry<T>({\n    items,\n    children,\n    itemKey,\n    columns = DEFAULT_COLUMNS,\n    gap,\n    className,\n    style,\n    ...rest\n}: MasonryProps<T>) {\n    const wrapper = useRef<HTMLDivElement | null>(null);\n    const cells = useRef<Map<number, HTMLElement>>(new Map());\n    const [width, setWidth] = useState(0);\n    const [heights, setHeights] = useState<number[]>([]);\n\n    const columnCount = columnsFor(width, columns);\n\n    /**\n     * Track the container width, not the viewport.\n     *\n     * A masonry inside a drawer or a two-column page is narrower than the window,\n     * and a media query would give it desktop columns at 300px wide. `ResizeObserver`\n     * is what makes the breakpoint map mean \"this container\", which is the only\n     * useful reading.\n     */\n    useEffect(() => {\n        const node = wrapper.current;\n        if (!node || typeof ResizeObserver === \"undefined\") return;\n        const observer = new ResizeObserver(([entry]) => {\n            setWidth(entry.contentRect.width);\n        });\n        observer.observe(node);\n        setWidth(node.getBoundingClientRect().width);\n        return () => observer.disconnect();\n    }, []);\n\n    /**\n     * Re-read every card's height, and store it only when something moved.\n     *\n     * The equality check is what stops the loop: writing a new array on every pass\n     * would re-render, re-measure and write again. The first render lays out in\n     * source order with weight 1 each — never blank — and this pass re-deals with\n     * real numbers.\n     */\n    const measure = useCallback(() => {\n        const next = items.map((_, index) => {\n            const node = cells.current.get(index);\n            return node ? node.getBoundingClientRect().height : 1;\n        });\n        setHeights((current) =>\n            current.length === next.length && current.every((value, i) => value === next[i])\n                ? current\n                : next,\n        );\n    }, [items]);\n\n    useLayoutEffect(measure, [measure, columnCount, width]);\n\n    /**\n     * Re-measure when a card changes size on its own — an image finishing its\n     * download is the common one, and it is exactly the case a height measured at\n     * mount gets wrong.\n     */\n    useEffect(() => {\n        if (typeof ResizeObserver === \"undefined\") return;\n        const observer = new ResizeObserver(measure);\n        for (const node of cells.current.values()) observer.observe(node);\n        return () => observer.disconnect();\n    }, [measure, columnCount]);\n\n    const layout = useMemo(\n        () =>\n            distribute(\n                items.map((_, index) => heights[index] ?? 1),\n                columnCount,\n            ),\n        [items, heights, columnCount],\n    );\n\n    return (\n        <div\n            ref={wrapper}\n            className={cn(styles.wrapper, className)}\n            style={{ ...style, ...(gap ? { \"--tempest-masonry-gap\": gap } : {}) }}\n            {...rest}\n        >\n            {layout.map((columnItems, column) => (\n                <div key={column} className={styles.column}>\n                    {columnItems.map((index) => {\n                        const item = items[index];\n                        return (\n                            <div\n                                key={itemKey ? itemKey(item, index) : index}\n                                className={styles.item}\n                                ref={(node) => {\n                                    if (node) cells.current.set(index, node);\n                                    else cells.current.delete(index);\n                                }}\n                            >\n                                {children(item, index)}\n                            </div>\n                        );\n                    })}\n                </div>\n            ))}\n        </div>\n    );\n}\n"],"mappings":"gKAoCA,IAAM,EAA0C,CAAE,EAAG,EAAG,IAAK,EAAG,KAAM,CAAE,EAiBxE,SAAgB,EAAW,CACvB,QACA,WACA,UACA,UAAU,EACV,MACA,YACA,QACA,GAAG,GACa,CAChB,IAAM,GAAA,EAAU,EAAA,OAAA,CAA8B,IAAI,EAC5C,GAAA,EAAQ,EAAA,OAAA,CAAiC,IAAI,GAAK,EAClD,CAAC,EAAO,IAAA,EAAY,EAAA,SAAA,CAAS,CAAC,EAC9B,CAAC,EAAS,IAAA,EAAc,EAAA,SAAA,CAAmB,CAAC,CAAC,EAE7C,EAAc,EAAA,WAAW,EAAO,CAAO,GAU7C,EAAA,EAAA,UAAA,KAAgB,CACZ,IAAM,EAAO,EAAQ,QACrB,GAAI,CAAC,GAAQ,OAAO,eAAmB,IAAa,OACpD,IAAM,EAAW,IAAI,gBAAgB,CAAC,KAAW,CAC7C,EAAS,EAAM,YAAY,KAAK,CACpC,CAAC,EAGD,OAFA,EAAS,QAAQ,CAAI,EACrB,EAAS,EAAK,sBAAsB,CAAC,CAAC,KAAK,MAC9B,EAAS,WAAW,CACrC,EAAG,CAAC,CAAC,EAUL,IAAM,GAAA,EAAU,EAAA,YAAA,KAAkB,CAC9B,IAAM,EAAO,EAAM,KAAK,EAAG,IAAU,CACjC,IAAM,EAAO,EAAM,QAAQ,IAAI,CAAK,EACpC,OAAO,EAAO,EAAK,sBAAsB,CAAC,CAAC,OAAS,CACxD,CAAC,EACD,EAAY,GACR,EAAQ,SAAW,EAAK,QAAU,EAAQ,OAAO,EAAO,IAAM,IAAU,EAAK,EAAE,EACzE,EACA,CACV,CACJ,EAAG,CAAC,CAAK,CAAC,GAEV,EAAA,EAAA,gBAAA,CAAgB,EAAS,CAAC,EAAS,EAAa,CAAK,CAAC,GAOtD,EAAA,EAAA,UAAA,KAAgB,CACZ,GAAI,OAAO,eAAmB,IAAa,OAC3C,IAAM,EAAW,IAAI,eAAe,CAAO,EAC3C,IAAK,IAAM,KAAQ,EAAM,QAAQ,OAAO,EAAG,EAAS,QAAQ,CAAI,EAChE,UAAa,EAAS,WAAW,CACrC,EAAG,CAAC,EAAS,CAAW,CAAC,EAEzB,IAAM,GAAA,EAAS,EAAA,QAAA,KAEP,EAAA,WACI,EAAM,KAAK,EAAG,IAAU,EAAQ,IAAU,CAAC,EAC3C,CACJ,EACJ,CAAC,EAAO,EAAS,CAAW,CAChC,EAEA,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CACI,IAAK,EACL,UAAW,EAAA,GAAG,EAAA,QAAO,QAAS,CAAS,EACvC,MAAO,CAAE,GAAG,EAAO,GAAI,EAAM,CAAE,wBAAyB,CAAI,EAAI,CAAC,CAAG,EACpE,GAAI,EAEH,SAAA,EAAO,KAAK,EAAa,KACtB,EAAA,EAAA,IAAA,CAAC,MAAD,CAAkB,UAAW,EAAA,QAAO,OAC/B,SAAA,EAAY,IAAK,GAAU,CACxB,IAAM,EAAO,EAAM,GACnB,OACI,EAAA,EAAA,IAAA,CAAC,MAAD,CAEI,UAAW,EAAA,QAAO,KAClB,IAAM,GAAS,CACP,EAAM,EAAM,QAAQ,IAAI,EAAO,CAAI,EAClC,EAAM,QAAQ,OAAO,CAAK,CACnC,EAEC,SAAA,EAAS,EAAM,CAAK,CACpB,EARI,EAAU,EAAQ,EAAM,CAAK,EAAI,CAQrC,CAEb,CAAC,CACA,EAhBK,CAgBL,CACR,CACA,CAAA,CAEb"}