@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
193 lines • 8.83 kB
text/typescript
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>];