import type { RefObject } from "react"; import type { ColumnChoice, GridLayout } from "./chooseColumns.ts"; /** * The height-first column rule, wired to a real element. * * `chooseColumns` is the decision; this is the measuring. The split * is the point — the rule is a table of numbers anyone can check in * Node, and everything that needs a browser is quarantined here * behind seams. * * ## What it measures, and from where * * Two different sources, on purpose: * * - **Inline size comes from a container**, observed with a * `ResizeObserver`. A grid sitting beside a rail has less room * than `window.innerWidth` claims, and this library is built * around the case where the component's container is what * changed and the window never moved. The source app read * `window.innerWidth` with a bare `resize` listener; that is the * one part of the port that is not a lift. * - **Block size comes from the viewport**, behind an injected * resolver. It has to: the question is "will this scroll", and a * grid in normal flow is exactly as tall as its contents, so * measuring the element always answers "it fits." * * ⚠️ **The observed element must not itself be capped by the * answer.** `contentMaxInlineSize` narrows the content when the * count is low; if the ref sits on the element that cap is applied * to, the fold eats its own output and can never widen again — one * column forever, with no error anywhere. `AdaptiveGrid` prevents * this structurally by measuring an uncapped outer box and capping * an inner one, and a caller wiring the hook by hand owes itself * the same shape. * * ## Composing with a shell * * `contentMaxInlineSize` is a CSS length meant to be handed * straight to a shell's main region as its max inline size, so the * page chrome and the grid widen together rather than the grid * rattling around inside a fixed column. */ /** * Where the available block size comes from, and how to hear about * it changing. The shape `useSyncExternalStore` wants, and the same * `get`/`subscribe` seam `useColorScheme` takes its resolver in. * * Injected rather than hardcoded for the reason every seam in this * library is: an Electron window, a fixed-height kiosk panel, and a * test all have a block size, and none of them is * `window.innerHeight`. */ export type BlockSizeResolver = { get: () => number; subscribe: (listener: () => void) => () => void; }; /** * Where a manual choice is remembered between visits. * * Deliberately not a storage *key* on its own: the source app * hardcoded `"rip-deck.layout-columns"`, which is exactly the kind * of app-namespaced string a shared library must never invent for * its consumers. */ export type ColumnPersistence = { read: () => string | null; write: (value: string) => void; }; /** * `window.innerHeight`, as a resolver. * * Guards `typeof window`, so importing this in a server render is * safe: it degrades to an inert zero rather than throwing on a * missing global. A zero block size resolves to one column, which * is the right thing to send down the wire — it is the layout that * is correct everywhere, and it widens on the client. */ export declare const viewportBlockSizeResolver: () => BlockSizeResolver; /** * `localStorage` under a caller-supplied key. * * Reads and writes are wrapped: `localStorage` throws in a * sandboxed iframe and in some privacy modes, and a grid that * cannot remember a choice should still honour the one just made. */ export declare const localStorageColumnPersistence: (key: string) => ColumnPersistence; /** * A stored choice, or `"auto"` for anything unreadable. * * Every failure lands on `"auto"` on purpose: a corrupt key, a * browser that refuses storage, a number somebody typed into * devtools. The default is the mode that works everywhere, so a bad * value costs a preference rather than a page. */ export declare const readStoredChoice: ({ maxColumns, persistence, }: { maxColumns?: number; persistence: ColumnPersistence | undefined; }) => ColumnChoice; export declare const useAdaptiveColumns: ({ blockSizeResolver, chromeBlockSize, columnInlineSize, gutterInlineSize, gap, itemBlockSize, itemCount, layout, maxAutoColumns, maxManualColumns, minColumnInlineSize, persistence, singleInlineSize, storageKey, }: { /** @see BlockSizeResolver. Defaults to the viewport. */ blockSizeResolver?: BlockSizeResolver; /** Everything down the page that is not this grid, in CSS px. */ chromeBlockSize?: number; /** Override `contentInlineSize.column`. */ columnInlineSize?: string; /** Override `contentInlineSize.gutter`. */ gutterInlineSize?: string; /** Space between tracks for the fill layout. */ gap?: number; /** One item's block size in CSS px. Measure it; err high. */ itemBlockSize: number; /** How many items the grid is being asked to fit. */ itemCount: number; /** Opt in to width-first media layout. */ layout?: GridLayout; /** The most columns the automatic answer will take. */ maxAutoColumns?: number; /** The most a person may ask for. One above the auto cap. */ maxManualColumns?: number; /** The narrowest a column may be, in CSS px. */ minColumnInlineSize?: number; /** * Where a manual choice is remembered. Defaults to * `localStorage` when `storageKey` is given, and to nothing at * all when it is not — a grid with no key keeps a choice for the * session and writes nowhere. */ persistence?: ColumnPersistence; /** Override `contentInlineSize.single`. */ singleInlineSize?: string; /** * The `localStorage` key a manual choice is remembered under. * * **Caller-supplied and namespaced by the app** — one origin * often serves several things that would each like a `columns` * key, and a shared library has no business picking that name. */ storageKey?: string; }) => { /** What the automatic rule wants right now. */ autoColumns: number; /** What to draw. */ columns: number; /** * The cap for the content around the grid, as a CSS length. Hand * it to a shell's main region. */ contentMaxInlineSize: string; /** * Attach to the element whose inline size the grid may spend. * * ⚠️ Never the element `contentMaxInlineSize` is applied to. */ containerRef: RefObject; /** * Has the container been given a box yet? * * False in two situations that look identical from here and are * both "no measurement has happened": the frame before * `ResizeObserver` delivers its first record, and any time the * container sits inside a `display: none` ancestor — which is * what the `hidden` attribute is, and what a tab panel, a * collapsed disclosure and a closed `
` all use. * * The distinction matters because a zero inline size is not a * measurement of zero. It is the absence of one, and a consumer * that treats it as a number lays out for a 0px container and * then reads the correction as a change. */ isLaidOut: boolean; /** What was picked, `"auto"` included. */ choice: ColumnChoice; setChoice: (choice: ColumnChoice) => void; }; //# sourceMappingURL=useAdaptiveColumns.d.ts.map