/** * `ScrollRegion` — a horizontally scrollable container that becomes a keyboard * target ONLY while it actually overflows. * * A pane that scrolls must be reachable without a pointer (WCAG 2.1.1), which * needs `tabIndex`, `role="region"` and a name. Applying those unconditionally * would litter every screen with phantom landmarks and dead tab stops for tables * that happen to fit, so the attributes are driven by a live measurement: * `scrollWidth > clientWidth`, re-taken whenever the container or its content * resizes and whenever the content is replaced. * * `useOverflowRegion` is the same measurement for a component that IS its own * scrolling box (a `
`, a JSON pane) and therefore cannot be wrapped in a * `` without gaining a second scroller. `useProseScrollRegions` applies the * rules to the surfaces React never renders — the tables and code blocks inside * `dangerouslySetInnerHTML` (rendered README/markdown), which cannot be wrapped * in a component and are therefore instrumented imperatively. * * Both hooks hand back a CALLBACK REF for the element they measure, rather than * reading one the caller holds: a ref object tells a hook nothing about WHEN it * is filled, so a hook that read one would never instrument an element that * mounts later than the hook (a README that arrives with the second render) and * would never notice one being swapped for another. */ import type { CSSProperties, ReactNode, Ref, RefCallback } from 'react'; import { type OverflowAxis } from './overflow-measure'; export type { OverflowAxis } from './overflow-measure'; export interface ScrollRegionProps { /** The region's accessible name, applied only while it actually scrolls. */ readonly label: string; readonly children: ReactNode; readonly className?: string; readonly style?: CSSProperties; /** * The dimension overflow is measured on. `horizontal` (the default) fits a pane * that scrolls sideways; `vertical` fits a bounded, capped-height box that scrolls * down. The caller styles the box's own overflow to match. */ readonly axis?: OverflowAxis; /** * The one arbitrary attribute this component forwards. The measured attributes * — `tabindex`, `role` and `aria-label` — are the component's own and appear * and disappear with the overflow, so a general attribute spread would let a * caller set them statically and defeat the measurement. A test hook cannot, * which is why it is the exception rather than the first of a set. */ readonly 'data-testid'?: string; /** A consumer ref for the scrolling `div` itself. */ readonly ref?: Ref; } /** The fallback name per instrumented surface, when no heading precedes it. */ export interface ProseScrollLabels { readonly table?: string; readonly pre?: string; } /** The attribute set a scrolling box wears; every value is absent while it fits. */ export interface OverflowRegionAttributes { /** Attach to the scrolling element; it is what the measurement follows. */ readonly ref: RefCallback ; readonly tabIndex?: 0; readonly role?: 'region'; readonly 'aria-label'?: string; } /** * The conditional region attributes for a scrolling box, alongside the ref that * names it. The ref must go on the scrolling box ITSELF — a ` `, a JSON * pane, or the `div` `ScrollRegion` renders. Use the hook when wrapping the * content in a `ScrollRegion` would give the surface a second scroller. * * The box is measured as it is attached, whenever it or any child resizes, and * whenever its content changes — replaced, appended, or edited in place. A * replaced child is a NEW element, so the registration is re-pointed at the * current children before each measurement, adding and dropping only the ones * that actually changed; both sides of that are registrations on the shared * observers, not observers of this mount's own. The only DOM change a * measurement can cause is the region ATTRIBUTES this hook returns, and * attributes are deliberately left unobserved — that, not an absence of * mutation, is what stops the pair from re-triggering each other. * * @param ref - a consumer ref that wants the same element, or `undefined`. * @param label - its accessible name, applied only while it actually scrolls. * @param axis - the dimension to measure overflow on (`horizontal` by default; * `vertical` for a bounded, capped-height box that scrolls down). */ export declare function useOverflowRegion(ref: Ref| undefined, label: string, axis?: OverflowAxis): OverflowRegionAttributes; export declare function ScrollRegion({ label, children, className, style, axis, 'data-testid': testId, ref, }: ScrollRegionProps): import("react").JSX.Element; /** * Instruments the scrollable surfaces under the element the returned ref is * attached to — every ` ` and every `
` — as scroll regions. React * cannot wrap markup it did not create, so this walks the DOM instead. A table * is moved into a `div.tai-scroll-region` (once — the pass is idempotent) * because the table itself is not the scrolling box; a `` already IS its * own scrolling box, so it is instrumented in place. Either way the scrolling * element carries the same conditional `tabindex`/`role`/`aria-label` as * `ScrollRegion`. * * The name is the nearest heading preceding the surface — read for all of them * in one document-order pass — so a reader landing on the region hears which * section it belongs to; `labels` covers a surface with no heading above it, and * a name shared by several surfaces is numbered so no two regions answer to the * same one. * * The pass runs from the ref callback rather than an effect, which puts it in * the commit phase, BEFORE the browser paints: `.tai-prose table` is * `width: 100%` with no overflow of its own — the scroller exists only on the * wrapper this builds — so a pass that ran after paint would show one frame at * 320/360 px with the table overflowing the document, then jolt sideways. A * passive-effect probe measured exactly that: at first paint wrappers=0 and the * table's parent was `.tai-prose`. * * Injected HTML is replaced wholesale when its source changes, so the pass is * re-run from a `MutationObserver` on the subtree rather than on attachment * alone. * * @param labels - the names for surfaces with no preceding heading. * @returns the ref for the element whose subtree holds the injected markup. */ export declare function useProseScrollRegions(labels?: ProseScrollLabels): RefCallback; //# sourceMappingURL=scroll-region.d.ts.map