UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

193 lines 8.83 kB
import * as React from 'react'; import { type ChannelKey, type PeerId } from "./coordinatePreference.mjs"; /** * Options for {@link useCoordinated}. See `coordinatePreference` for * the underlying semantics; only React-specific behaviors are * documented here. */ export interface UseCoordinatedOptions<TValue, TPreload> { /** * Coordination scope. All peers (component instances) that share a * `channelKey` participate in the same layout-shift barrier. Pass * `null` to opt out of coordination entirely (the hook becomes a * plain pass-through of the underlying `[value, setValue]`). */ channelKey: ChannelKey | null; /** * Stable identifier for *this* peer within the channel. Defaults to * a freshly generated id on mount. Override when stable cross-mount * identity matters (e.g. for analytics / debugging). */ peerId?: PeerId; /** * Return `true` when applying this target would visibly shift * layout — the peer joins the channel-wide barrier so all such * peers commit together. Return `false` for non-disruptive changes * — the peer commits lazily on its own self-serial chain. See * `coordinatePreference` for the full semantics. */ causesLayoutShift: (target: TValue) => boolean; /** * Optional async work to run before the barrier commits (for * `causesLayoutShift === true`) or before a lazy commit (for * `false`). The resolved value is handed to `onCommit`. */ preload?: (target: TValue, signal: AbortSignal) => TPreload | Promise<TPreload>; /** * Hook fired inside the coordinated commit, before the visible * value flips. Useful for installing precomputed payloads into * neighboring state. The visible `value` returned from this hook * always lags `pendingValue` until coordination settles, so this * runs *with* the value flip, not before it. * * Also fires once on first mount, with the initial preloaded * payload, so consumers can install precomputed state on hydration * without a separate code path. * * Under normal conditions `preloaded` is whatever this peer's * `preload` resolved to. It may still be `undefined` when: * - the barrier force-resolved at `ultimateTimeoutMs` (a sibling * peer crashed / hung; accompanied by a console warning), * - `preload` threw (logged via `console.error`, treated as a * no-op so the rest of the channel still commits), or * - `preload` explicitly returned `undefined`. * * Handlers should tolerate the undefined case and fall back to a * synchronous render path rather than throwing. */ onCommit?: (target: TValue, preloaded: TPreload | undefined) => void; /** * See {@link AnnounceOptions.minWaitMs}. */ minWaitMs?: number; /** * See {@link AnnounceOptions.multiPeerExtraMinWaitMs}. */ multiPeerExtraMinWaitMs?: number; /** * See {@link AnnounceOptions.lazyMinWaitMs}. */ lazyMinWaitMs?: number; /** * See {@link AnnounceOptions.gracePeriodMs}. */ gracePeriodMs?: number; /** * See {@link AnnounceOptions.ultimateTimeoutMs}. */ ultimateTimeoutMs?: number; /** * Controls whether `isCoordinating` flips *during* the preload * or *after* it. `pendingValue` (the user-facing "intent" * signal) always flips synchronously regardless of this flag — * toolbars and other affordances stay responsive on click. * * - `false` (default) — defer `isCoordinating` until the * originator's `preload` settles. Use this when the preload * is CPU-bound (parsing, syntax highlighting, layout * measurement, etc.) and the consumer drives a visible * animation off `isCoordinating`. Running the animation * concurrently with the preload would steal main-thread time * from the compositor and produce a janky transition; with * the flip deferred the animation only starts once the heavy * work is done. * - `true` — flip `isCoordinating` synchronously on the * originating setter call, so the animation runs in parallel * with the preload. Use this when the preload is I/O-bound * (network fetches, `localStorage` reads, etc.) so the * animation and the I/O roundtrip overlap. * * The coordinator always yields to the browser (via * `scheduler.yield()` when available, otherwise a `setTimeout` * macrotask) before invoking `preload`, so even synchronous * preloads settle one macrotask after the originating setter * call. This lets the intermediate loading state paint before * the (potentially CPU-bound) preload monopolizes the main * thread. This flag has no effect when `preload` is omitted; * the flip is synchronous either way. * * Only the originator's flip is affected. Sibling peers picked * up via `notifySiblings` still observe the receiver flow's * synchronous flip, because their `isCoordinating` is driven * by the originator's broadcast rather than a local click. */ animateDuringPreload?: boolean; /** * Scheduling priority for lazy-path commits. * * - `'idle'` (default) — lazy-path commits are deferred via * `requestIdleCallback` so the browser can yield to in-flight * paints and input. Use when the lazy peer's commit itself is * main-thread heavy (DOM reconciliation of a freshly * transformed tree, etc.). * - `'normal'` — the commit lands as soon as the preload * resolves. Use for I/O-bound `preload`s where the commit is * cheap and you want each peer's swap to surface immediately; * otherwise idle scheduling can cluster commits together near * the slowest peer's settle. * * Has no effect when this peer takes the barrier path — barrier * commits are batched synchronously inside the barrier's resolve * microtask regardless. */ lazyCommitPriority?: 'idle' | 'normal'; } export interface UseCoordinatedExtras<TValue> { /** * The most recently announced target value. Equals the committed * `value` when no coordination is in flight. Useful for showing an * optimistic preview UI (toolbar selection, etc.) that should react * instantly to a click even when the visible content has a pending * barrier. */ pendingValue: TValue; /** * `true` while a coordination is in flight that this peer can drive * an animation off of. For receivers and barrier joiners that lands * synchronously with the announce. For originators the flip is * controlled by `animateDuringPreload`: with the default * `animateDuringPreload: false`, `isCoordinating` stays `false` * until the originator's `preload` settles, then flips `true` for * the remainder of the barrier; with `animateDuringPreload: true`, * it flips synchronously on the originating setter call so the * animation overlaps with an I/O-bound preload. Use * {@link pendingValue} to drive intent-based affordances (toolbar * selection, etc.) that should react instantly to a click * regardless of this flag. Surfaces as `data-coordinating` on * consumers. */ isCoordinating: boolean; /** * `true` once the grace period has elapsed with the barrier still * unresolved. Only set on the originating peer. Surface as a * "waiting" affordance to the user. */ isWaitingForPeers: boolean; } /** * Coordinate a piece of state across sibling component instances on * the same channel, so that visually disruptive value changes commit * in a single layout pass rather than independently. Designed as a * thin wrapper around any `useState`-shaped primitive (e.g. * `useLocalStorageState`, `usePreference`, plain `useState`). * * **Originator flow** — calling the returned `setValue`: * 1. `pendingValue` updates synchronously to the requested target * 2. The coordinator runs `preload` (per phase rules) and waits for * sibling peers (phase 1 only) * 3. When the barrier resolves, the underlying `setValue` is called * and this hook's visible `value` flips, so the swap is * consistent with the optional `onCommit` side-effect * * **Receiver flow** — when the underlying `value` changes from outside * (e.g. a storage event from another tab): * 1. `pendingValue` updates to match * 2. Coordination runs locally (this tab's peers run their own * phase-1 barrier) * 3. The visible `value` returned by this hook is held at the * previous value until the barrier resolves, then flips * * Pass `channelKey: null` to disable coordination — the hook becomes * a transparent pass-through of the underlying tuple. */ export declare function useCoordinated<TValue, TPreload = void>(underlying: [TValue, (next: TValue) => void], options: UseCoordinatedOptions<TValue, TPreload>): [TValue, React.Dispatch<React.SetStateAction<TValue>>, UseCoordinatedExtras<TValue>];