import { type RefObject } from "react"; /** * Returns a debounced view of `value`, updated only after `delay` ms of no * change. Use for search inputs to avoid querying on every keystroke. * * setState runs only inside setTimeout (async) — compliant with * react-hooks/set-state-in-effect. */ export declare function useDebouncedValue(value: T, delay?: number): T; /** * Returns true while `ms` haven't elapsed since `signal` last flipped truthy. * setState is scheduled asynchronously (setTimeout 0 / ms) — Rules of React safe. */ export declare function useTimeoutFlag(signal: unknown, ms?: number): boolean; /** * Controlled-ness latch for `value`/`defaultValue`/`onValueChange` controls whose empty state is * `undefined` (pickers carrying `Date`/`DateRange`). A control counts as controlled once a DEFINED * `value` has EVER been passed: - mounted with a defined `value` → controlled, and a later * `value={undefined}` stays controlled-EMPTY (not mistaken for uncontrolled); - mounted with * `value={undefined}` (an empty form that later restores a saved value) → uncontrolled until the * first defined value arrives, then PROMOTES to controlled for good. */ export declare function useControlledLatch(valueIsDefined: boolean): boolean; export declare function useMediaQuery(query: string): boolean; export declare function useIsMobile(): boolean; /** * WCAG 2.1.1 — a region that scrolls must be operable by keyboard. Tabbing to a focusable child * scrolls the container, so a region whose content IS focusable needs nothing; one whose content is * inert (plain text) strands its overflow for anyone not using a pointer and must therefore take * focus itself via `tabindex="0"`. Which case applies depends on the RENDERED size and content — * the same pagination strip is fine at 1440px and unreachable at 375px, and a panel full of text * only overflows once it is resized — so it is measured at runtime and kept in sync as the element * resizes or its content changes. * * The attribute is written imperatively rather than rendered: `react-resizable-panels` applies our * className to a nested div it owns, which no prop can reach. * * @param element the scroll container itself (state, not a ref, so the effect re-runs when it mounts) */ export declare function useScrollableRegionTabIndex(element: HTMLElement | null): void; /** * Does this scroll box actually have somewhere to scroll, on the axes it is allowed to scroll on? * * Drives BOTH halves of a scroll region: the `tabindex="0"` a keyboard user needs to reach the * overflow (WCAG 2.1.1 / axe `scrollable-region-focusable`) AND the `role`/name that stop needs in * order not to be an anonymous one (gh#817 for the table, gh#821 for `ScrollArea`). A stop that * scrolls nothing is pure noise, and a name on it is noise too, so both are withheld until there is * overflow to reach. * * The measurement may only ever REMOVE the stop, never withhold it on a guess: a box that has not * been laid out reports 0 for `clientWidth`/`clientHeight` (the server render, jsdom, a * `display:none` ancestor, the frame before first layout), and that is not evidence that nothing * overflows. Reading it as "no overflow" would strand the overflow from every keyboard user — a * worse failure than an extra tab stop — so an unmeasured box counts as scrolling. * * `axis` is the box's OWN `overflow`, not a preference: an axis it does not scroll on is `hidden` * there, so overflow on that axis is CLIPPED rather than reachable, and measuring it would keep a * tab stop that scrolls nothing. `ScrollArea`'s `orientation` is exactly this union. */ export declare function useScrollsOnAxis(ref: RefObject, enabled: boolean, axis: "horizontal" | "vertical" | "both"): boolean; /** A table's wrapper scrolls on one axis only, so it asks the one question it has (gh#817). */ export declare function useScrollsHorizontally(ref: RefObject, enabled: boolean): boolean; /** * Nearest scrollable ancestor — the box an element actually scrolls inside — else `null`, which is * what `IntersectionObserver` already spells "the document viewport". * * Lifted VERBATIM out of `src/components/layout/page-container.tsx`, where it was private to * `footerReveal="onScroll"` (gh#827). It is exported because the callers that need `useInView` to * measure against a scroll PANE rather than the viewport have to name that pane, and every one of * them would otherwise write this walk again. */ export declare function scrollParent(el: HTMLElement | null): HTMLElement | null; /** * Has this element entered the viewport (or `root`) yet? * * The ONE `IntersectionObserver` wrapper in the library, and it has to stay that way: before * gh#827 the only observer in `src/components/` was `PageContainer`'s private `useFooterReveal`, * so the next component that needed one copied it rather than finding it. Callers today are * `Reveal on="view"` (gh#829, the first), `PageContainer footerReveal="onScroll"` and `Affix` * (gh#827). `root` exists because two of the three measure against a scrolling PANE rather than * the viewport; `scrollParent` above is how they name it. * * Borrowed from Motion's `useInView` (https://motion.dev/docs/react-use-in-view): `once` and * `amount` keep their names, their types and their defaults, including `amount`'s * `"some" | "all" | number` and Motion's own `{ some: 0, all: 1 }` threshold mapping. * * ## Unobservable counts as IN VIEW * * The server render, jsdom, a browser without `IntersectionObserver`, and `enabled: false` all * report `true`. A caller that hides content until this returns `true` therefore shows it — the * only safe direction, because the alternative is content that is never revealed at all. Callers * MUST keep it that way: visibility is never gated on an observer that might not exist. * * ## `assumeInView` — which way to be wrong for the ONE frame before the first entry * * The hook normally flips to `false` the moment a live observer exists, before that observer has * said anything, because a caller that HIDES on `false` would otherwise paint its content in and * then take it away again. That is right for `Reveal` and wrong for the two callers that ACT on * `false`: `PageContainer` would reveal its sticky footer for a frame, and `Affix` would fix a bar * over the page before measuring it. Those pass `assumeInView`, which leaves the answer `true` * until the observer actually reports — so neither ever acts on a measurement it has not taken. * * ## `amount` is clamped to what the element can actually reach * * `threshold` is the ratio of the intersection to the TARGET's own box * (https://www.w3.org/TR/intersection-observer/#dom-intersectionobserverentry-intersectionratio), so * an element taller than the root can never reach `1`: `amount: "all"` on a full-height section * would hide it forever. The requested ratio is clamped at observe time to the largest ratio the * element's measured box can attain inside the root. */ export declare function useInView(ref: RefObject, { enabled, once, amount, root, rootMargin, assumeInView, }?: { /** `false` skips the observer entirely and reports `true`. */ enabled?: boolean; /** Stop observing after the first entry, so the element never reports out-of-view again. */ once?: boolean; /** How much must be visible — `"some"` (any pixel) | `"all"` | a 0..1 ratio. */ amount?: "some" | "all" | number; /** Scroll container to measure against. `null` → the document viewport. */ root?: Element | null; /** * `IntersectionObserver` `rootMargin` — grows or shrinks the clipping box before the test. * * `Affix` is why it exists: it observes a hairline SENTINEL at the pinning edge and grows the * box a million pixels past the line, so that "has not reached the line yet" and "is far below * it" are one answer and the pin reduces to `!inView` in both directions. */ rootMargin?: string; /** Stay `true` until the observer reports, instead of flipping to `false` on mount. */ assumeInView?: boolean; }): boolean;