import * as React from 'react'; declare function horizontalPadding(el: HTMLElement): number; /** One collapsible label, measured. */ type RowLabelFit = { /** Width it adds to the row when open, including the gap beside it. */ open: number; /** Width it is adding at this instant, part-way through a transition included. */ current: number; /** Keeps its label whatever the ladder decides — the selected item. */ pinned: boolean; }; type RowFitMeasurement = { /** Room the row has, inside the scroller's own padding. */ available: number; /** * Width the row's content wants right now. * * This must not come from the scroller's `scrollWidth` or from a * `width: fit-content` box. Both stop growing at the scroller's edge, which * is the one moment the ladder needs to know how far past it the content * reaches. Sum the row's children instead. */ content: number; /** Collapsible labels, in row order. */ labels: RowLabelFit[]; }; /** * Width the row's content wants, ignoring the scroller it has to fit inside. * * The row's own box stops answering this the moment the answer matters: `w-fit` * resolves to `min(max-content, available)`, so it clamps to the scroller * exactly when the content grows past it. Its children do keep their natural * width — they are `flex-none` — so add them up instead. * * `clampSelector` names the descendants that clamp the same way the row does * (a `w-fit` tablist, a toolbar inside a `w-max` row) and so have to be summed * rather than measured. */ declare function naturalRowWidth(el: HTMLElement, clampSelector?: string): number; /** * Prices every collapsible label in the row: what it costs to show, and what it * is costing at this instant. * * `scrollWidth` on the text is what makes a closed label quotable — it reports * the text's full width even from inside a track squeezed to zero, so the row * can be told what re-opening would cost without opening anything. A label * hidden with `sr-only` cannot answer this, which is why collapsing is a * zero-width grid track rather than a visually-hidden span. * * `isPinned` decides which labels the ladder may not spend. Each row answers it * differently — Radix keeps selection in `data-state`, the views toolbar knows * it in React — so the caller supplies it rather than the helper guessing. */ declare function measureRowLabels(content: HTMLElement, isPinned: (label: HTMLElement) => boolean, selector?: string): RowLabelFit[]; /** * How a horizontal row of controls gives up space, in order, as it runs out of * room: * * 1. Inactive labels drop to icon only (only items with both an icon and a * label can do this, so a label-only item never collapses into nothing). * 2. Trailing items move into an overflow menu, one at a time. * 3. Whatever is left scrolls, which in practice means one item plus the menu. * * Each step is applied in a layout effect and re-measured on the next render, so * the row settles before the browser paints. * * ## Rung 1 is priced, not guessed * * Given a `measure`, the row closes only as many labels as it has to, and works * out how many that is arithmetically: the width of the row with every label * closed, plus the width each label would add back, against the room available. * Both terms are read from the DOM every pass, so nothing is cached and nothing * goes stale when the font, the density, or the item set changes. * * The arithmetic is what keeps the row still. An earlier version asked the * question by doing it — open the labels, look, close them again if they did not * fit. That reads correctly and costs nothing while the change lands in a single * frame, but the moment labels animate, the failed attempt is a frame the user * sees: the row collapsed, flicked back open, and collapsed again. Pricing the * rung asks the same question without playing the answer. * * It also means a measurement taken mid-transition is still right. `current` is * whatever the label contributes at this instant, so subtracting it yields the * same closed-row width part-way through a transition as at either end of it, * and the ladder never has to wait for the row to stop moving. * * Without a `measure` the row keeps the older all-or-nothing collapse: one * boolean for every label, and a probe to find out when they can come back. * * ## Why growing waits for a quiet frame * * The two directions cost very different amounts. Shrinking only ever spends * rungs, so a whole gesture costs at most one rung per item no matter how many * resize events arrive. Growing has to re-derive from the top, because a rung * spent earlier may no longer be needed, and that re-expands the row and walks * every rung back down again, at one render and one forced layout per rung. * * A resize gesture delivers an event every frame, so doing that work per event * made dragging a secondary rail or a window edge pay the entire ladder on every * frame. Growth is therefore deferred to the first frame with no resize on it, * which is the frame the gesture stops. Shrinking stays immediate, because a * clipped row is a visible defect and the deferral would show it. */ declare function useRowFitLadder({ viewportRef, count, canCollapse, enabled, measure, }: { viewportRef: React.RefObject; count: number; /** Whether any item can trade its label for its icon. Rung 1 is skipped when false. */ canCollapse?: boolean; enabled: boolean; /** * Prices rung 1 for this row. Supplying it opts into closing labels one at a * time; without it every label closes together. */ measure?: () => RowFitMeasurement | null; }): { collapsed: boolean; openLabels: number; visible: number; }; export { type RowFitMeasurement, type RowLabelFit, horizontalPadding, measureRowLabels, naturalRowWidth, useRowFitLadder };