@mui/internal-docs-infra
Version:
MUI Infra - internal documentation creation tools.
439 lines • 19.7 kB
text/typescript
/**
* Generic same-tab preference coordinator. Its primary purpose is
* to **fold many concurrent value changes into a single layout-shift
* commit**: sibling component instances that share a `channelKey`
* coordinate so the visible flip happens together rather than as a
* cascade of independent re-layouts.
*
* Each peer self-classifies a given target value via
* `causesLayoutShift` (consulted for non-originator peers only;
* originators always take the barrier path so a user click always
* feels coordinated):
*
* - **`causesLayoutShift(target) === true`** — the peer joins a
* channel-wide barrier. Every joining peer's `preload` runs
* serially across the channel (so no main-thread contention
* while we prepare the swap), and all `onCommit`s fire together
* in a single microtask once everyone is ready. Use for changes
* that visibly resize content (collapse/expand, code transforms,
* image swaps with different aspect ratios).
* - **`causesLayoutShift(target) === false`** — the peer runs its
* `preload`+`commit` on its own self-serial chain. Multiple
* peers' lazy chains run concurrently with each other and with
* any in-flight barrier. Use for changes that are visually
* non-disruptive (e.g. updating a value that only shows on hover).
*
* Different peers may classify the same target differently — each
* peer's classification governs only that peer's path through the
* coordinator.
*
* **Cross-tab behavior is intentionally out of scope.** Tabs sync via
* the underlying state primitive (`useLocalStorageState` etc.); this
* coordinator only handles peers in the same JS context. A receiving
* tab independently runs its own barrier across its local peers,
* which is sequenced naturally after the originator's commit because
* the originator defers the underlying `setValue` write until its
* own barrier commits (see `useCoordinated`).
*
* No React, no DOM, no BroadcastChannel — pure module-scoped state
* suitable for any state primitive.
*
* **Browser-only.** All state (channels, barriers, lazy queues) is
* held in module scope and would persist across requests if this
* module were ever evaluated in a long-lived server-side runtime.
* The consuming surface is the `useCoordinated` React hook, whose
* `registerPeer`/`announceTarget` calls are gated behind
* `useLayoutEffect`/event handlers and therefore never reached
* during SSR. Do not import this module from server-side code
* paths that fan out per request.
*/
/** Identifier assigned to each peer at registration time. */
export type PeerId = string;
/** Channel scope. Peers sharing a `channelKey` coordinate with each other. */
export type ChannelKey = string;
/**
* Sentinel returned by the coordinator's `announceTarget` when no
* commit is needed for this peer (e.g. its current value already
* matches the target). Always-defined to keep the API ergonomic.
*/
export interface AnnounceHandle {
/**
* Abort a pending announcement (e.g. before the preload has run, or
* before the barrier has resolved). After `cancel()` no `onCommit`
* fires for this peer.
*/
cancel(): void;
/**
* Resolves when this peer's `onCommit` has fired or the announcement
* has been cancelled. Useful for tests and for callers that want to
* `await` a settled coordination.
*/
settled: Promise<void>;
}
export interface AnnounceOptions<TValue, TPreload> {
/**
* Per-target classifier consulted for **non-originator peers only**.
* Return `true` when applying this target would visibly shift
* layout — the peer will join the channel-wide barrier so all
* such peers commit together. Return `false` for non-disruptive
* changes — the peer will commit lazily on its own self-serial
* chain. Originators (the peer the user is directly interacting
* with) always take the barrier path regardless of this
* classifier, so a click always feels coordinated with whichever
* peers join. Different peers may classify the same target
* differently.
*/
causesLayoutShift: (target: TValue) => boolean;
/**
* Per-peer preload work. The returned value is handed back to
* `onCommit`. Receives an `AbortSignal` that fires if the
* announcement is cancelled (e.g. superseded by a newer target).
* Barrier peers run their `preload` in serial across the channel;
* lazy peers run their own self-serial chain. May be omitted for
* pure value-flip use cases.
*/
preload?: (target: TValue, signal: AbortSignal) => TPreload | Promise<TPreload>;
/**
* Fired when this peer's slice of the coordination has settled.
* For the barrier path this is inside the batched commit (all
* barrier peers' `onCommit`s run in the same microtask). For the
* lazy path this is immediately after this peer's own preload
* completes.
*
* `preloaded` may be `undefined` even when `preload` was provided:
* - the barrier force-resolved at `ultimateTimeoutMs` before this
* peer's slow preload settled,
* - the preload threw (logged via `console.error`, treated as a
* no-op so the rest of the channel still commits), or
* - the preload returned `undefined`/no value.
*
* A superseded announce does not call this `onCommit` — the
* superseding announce takes over and only its `onCommit` runs.
*
* Callers must tolerate `preloaded === undefined` and fall back to
* a synchronous render path (or skip the side effect entirely)
* rather than throwing.
*/
onCommit: (target: TValue, preloaded: TPreload | undefined) => void;
/**
* Minimum wall-clock time before commit, measured from
* `announceTime`. On the barrier path: the barrier stays open at
* least this long so consumers can play an exit animation on the
* outgoing state. On the lazy path: the per-peer self-serial chain
* waits at least this long after `announceTime` (anchored, not
* relative to when preload resolves) so a non-layout-shift peer
* can land its swap on the same wall-clock window as a sibling
* barrier instead of cascading. Default 0.
*/
minWaitMs?: number;
/**
* Additional wait applied to `minWaitMs` on the barrier path when
* more than one peer is registered on the channel at the time the
* barrier opens. Lets callers express "no extra delay when this
* demo is alone, but give late siblings a frame to join when they
* exist" without leaking solo-peer churn through a baseline
* `minWaitMs`. Default 0.
*/
multiPeerExtraMinWaitMs?: number;
/**
* Overrides `minWaitMs` for the lazy path only. Useful when
* non-layout-shift peers should land their swap *after* the
* sibling barrier has finished its expand-swap-collapse window
* (e.g. `2 * minWaitMs`) so the page settles in one paint instead
* of cascading. Falls back to `minWaitMs` when unset.
*/
lazyMinWaitMs?: number;
/**
* Lazy-path opt-in: when a same-target barrier is pending at the
* moment this peer announces, run its `preload` concurrently with
* the barrier's preloads instead of waiting for the barrier to
* commit. Use only for I/O-bound preloads that don't tax the main
* thread (e.g. fetching a JSON payload) — main-thread-heavy
* preloads (parsing, highlighting, layout measurement) should
* leave this `false` so the barrier's layout-shifting peers get
* uncontended CPU time to settle their swap.
*
* The lazy peer's `onCommit` still waits until the render after
* the barrier commits, regardless of this flag — the visible flip
* never lands before the layout-shifting siblings have painted.
*
* No effect when no barrier exists at announce time (the lazy
* pipeline runs immediately on its own clock).
*
* Default `false`.
*/
preloadAll?: boolean;
/**
* Lazy-path only: scheduling priority for the per-peer commit.
*
* - `'idle'` (default) — the commit is scheduled via
* `requestIdleCallback` so the browser can yield to
* higher-priority work (input, in-flight barrier paints)
* before the swap lands. Useful 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, without an idle defer. Use this for I/O-bound
* preloads 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,
* defeating the visible "cascade" the lazy path is meant to
* provide.
*
* Has no effect on the barrier path — barrier commits are batched
* synchronously inside the barrier's resolve microtask regardless.
*
* `'idle'` falls back to a synchronous commit if
* `requestIdleCallback` isn't available on `globalThis`.
*/
lazyCommitPriority?: 'idle' | 'normal';
/**
* Time past `minWaitMs` after which `onWaitingForPeers` fires if
* the barrier still hasn't resolved. The barrier itself keeps
* waiting up to `ultimateTimeoutMs`. Default 300ms.
*/
gracePeriodMs?: number;
/**
* Absolute ceiling past announcement after which the barrier
* force-resolves and logs a warning, regardless of outstanding
* peers. Default 10s.
*/
ultimateTimeoutMs?: number;
/**
* Called once when `gracePeriodMs` elapses with the barrier still
* unresolved. Only meaningful for originators (the peer that the
* user is directly interacting with) so they can surface a
* "waiting for peers" indicator.
*/
onWaitingForPeers?: () => void;
/**
* Whether this peer originated the change (user click) versus
* received it from elsewhere (storage event from another tab). Only
* affects which peer's `onWaitingForPeers` may fire and which peer
* opens the channel-wide barrier.
*/
isOriginator: boolean;
/** Wall-clock anchor (`Date.now()`) for barrier timers. */
announceTime: number;
}
/**
* Fired on a registered peer when *another* peer in the same channel
* calls `announceTarget`. Lets a peer learn about a sibling-driven
* change without having to wait for the underlying state primitive
* (e.g. `useLocalStorageState`) to echo the new value back — that
* echo only happens after the originator commits, which itself is
* gated on every sibling joining the barrier. Without this hook
* sibling peers would deadlock the barrier until
* `ultimateTimeoutMs` for any same-tab coordination where the
* underlying primitive only notifies after the originator's write.
*
* Implementations should typically call into their local equivalent
* of `runCoordination(target, isOriginator=false)` so the peer
* joins the active barrier (or kicks off its own lazy chain on the
* same wall-clock window). Implementations must be idempotent for
* repeated calls with the same `target` because notifications can
* fan out from each subsequent join.
*/
export type OnSiblingAnnounce<TValue> = (target: TValue) => void;
interface RegisteredPeer<TValue> {
id: PeerId;
/**
* Last value this peer reported via `reportValue`. Used to skip
* peers that are already at the target when classifying barrier
* expectations. Initialized lazily on first `reportValue`.
*/
currentValue: {
has: false;
} | {
has: true;
value: TValue;
};
/**
* Optional notifier invoked when *another* peer on the channel
* announces a target. See {@link OnSiblingAnnounce}.
*/
onSiblingAnnounce?: OnSiblingAnnounce<TValue>;
/**
* In-flight lazy-path work, keyed by the AbortController used to
* cancel it. The value is the target each entry is committing to,
* so barrier creation can selectively skip a peer only when its
* pending lazy work matches the new barrier's target.
* Cancelled when the peer is unregistered or a new lazy-path
* announcement supersedes a still-queued one.
*/
lazyInFlight: Map<AbortController, TValue>;
/**
* Per-peer serialization queue for lazy-path announcements. Each
* entry is a starter callback that kicks off its preload + commit
* pipeline. We drain via callback chaining (not Promise.then) so
* the entire pipeline stays on macrotasks — important for tests
* driving fake timers without microtask drains.
*/
lazyQueue: Array<() => void>;
lazyActive: boolean;
}
interface BarrierWaiter<TValue, TPreload> {
peerId: PeerId;
isOriginator: boolean;
preloaded: {
has: false;
} | {
has: true;
value: TPreload | undefined;
};
onCommit: (target: TValue, preloaded: TPreload | undefined) => void;
onWaitingForPeers?: () => void;
/** Resolves the waiter's `settled` promise. */
settle: () => void;
/** Cancel handle for the waiter's enqueued preload work. */
abort: AbortController;
}
interface PendingBarrier<TValue, TPreload> {
target: TValue;
/**
* Wall-clock anchor (`Date.now()`) recorded when the barrier was
* opened. Used by {@link getBarrierAnnounceTime} so late-joining
* peers can align their local timers to the originator's window
* instead of restarting a fresh one.
*/
announceTime: number;
/**
* Performance mark name recorded when the barrier was opened.
* Used as the start mark when measuring the barrier's resolution
* duration so a single {@link PerformanceObserver} entry captures
* the full open → resolve window for the channel.
*/
openMark?: string;
/**
* Peers expected to participate. Set when the barrier opens and on
* each waiter join (peers register themselves as they classify the
* target as 'high'). The barrier resolves when every expected peer
* has its `preloaded` field populated AND the minimum wait has
* elapsed.
*/
waiters: Map<PeerId, BarrierWaiter<TValue, TPreload>>;
/**
* Peers that explicitly opted out of this barrier by taking the
* lazy path for the same `target`. The barrier may resolve once
* `waiters.size + skipped.size >= channel.peers.size`.
*/
skipped: Set<PeerId>;
/** `true` once the minimum-wait timer has fired. */
minWaitPassed: boolean;
minWaitTimer: ReturnType<typeof setTimeout>;
waitingForPeersTimer: ReturnType<typeof setTimeout>;
waitingForPeersNotified: boolean;
ultimateTimer: ReturnType<typeof setTimeout>;
ultimateTimeoutMs: number;
/**
* Callbacks queued by lazy peers that announced the same target
* while this barrier was pending. Fired one macrotask after every
* waiter's `onCommit` runs, so the lazy peers' commits land in
* the render *after* the barrier's batched commit — keeping the
* main thread clear while the layout-shifting siblings paint.
*/
deferredLazyReleases: Array<() => void>;
}
interface Channel<TValue> {
channelKey: ChannelKey;
peers: Map<PeerId, RegisteredPeer<TValue>>;
/**
* `true` once any peer has called `announceTarget` on this channel
* since the channel was created. Surfaced by
* {@link hasEverAnnounced} so callers can distinguish "first
* paint, nobody has interacted" from "someone interacted then
* everyone settled". Never reset — a channel that's been
* announced on and then emptied will still report `true` until
* its last peer unregisters and the channel disposes.
*/
hasEverAnnounced: boolean;
/**
* Channel-wide serial queue for barrier-path preloads. Each barrier-path
* announcement appends its preload work; only one preload runs at a
* time across all this-tab barrier-path peers, preventing main-thread
* contention when many sibling demos all need to precompute.
*/
barrierTail: Promise<unknown>;
/**
* At most one pending barrier-path barrier per encoded target value.
* Keyed by the result of `encodeTarget` so any hashable target
* works.
*/
pendingBarriers: Map<string, PendingBarrier<TValue, unknown>>;
}
/**
* Set a custom target encoder. Returns a function that restores the
* previous encoder. Intended for tests; consumed via
* `coordinatePreference.testUtils`.
*/
declare function setTargetEncoder(impl: (value: unknown) => string): () => void;
/**
* Register a peer with a channel. Returns an `unregister` function
* that removes the peer; calling it cancels any in-flight lazy-path
* work owned by the peer and drops it from any open barrier-path barriers.
*
* Pass `onSiblingAnnounce` to learn about target announcements made
* by other peers on the channel — this is what lets a peer join the
* originator's barrier window without waiting for the underlying
* state primitive to echo the new value (which only happens after
* the originator commits, creating a deadlock when every peer is
* waiting on it).
*/
export declare function registerPeer<TValue>(channelKey: ChannelKey, peerId: PeerId, onSiblingAnnounce?: OnSiblingAnnounce<TValue>): () => void;
/**
* Report a peer's current value to the coordinator. Used to exclude
* already-at-target peers from barrier expectations.
*/
export declare function reportValue<TValue>(channelKey: ChannelKey, peerId: PeerId, currentValue: TValue): void;
/**
* Announce a target value for this peer. Routes into the barrier or
* lazy path based on `causesLayoutShift(target)`.
*
* For the barrier path: the peer joins the channel-wide barrier for
* this target (creating it if needed), enqueues its `preload` into
* the channel's serial queue, and awaits the barrier's batched
* commit.
*
* For the lazy path: the peer enqueues `preload` + `onCommit` onto
* its own self-serial chain and returns immediately. Multiple peers'
* lazy chains run concurrently with each other and with any
* in-flight barrier work.
*/
export declare function announceTarget<TValue, TPreload>(channelKey: ChannelKey, peerId: PeerId, target: TValue, options: AnnounceOptions<TValue, TPreload>): AnnounceHandle;
/**
* Returns `true` if any peer has ever called `announceTarget` on this
* channel since the channel was created (i.e., since the first peer
* registered without an existing channel object). Useful for
* first-render reconciliation: a peer that wakes up post-hydration
* and finds the channel "fresh" (no announcements yet) can safely
* fast-forward its committed value to the latest underlying value
* without going through a barrier, because no peer is mid-animation.
*
* Returns `false` when the channel doesn't exist (no peers have
* registered yet) or exists but hasn't seen an announce.
*/
export declare function hasEverAnnounced(channelKey: ChannelKey): boolean;
/**
* Returns the `announceTime` recorded when the active barrier for
* `target` was opened, or `null` if no barrier is currently pending
* for that target on `channelKey`. Late-joining peers can use this
* to anchor their local timers to the originator's wall-clock window
* instead of restarting a fresh one \u2014 e.g. a peer whose state
* propagated 200ms after the originator's click should commit 200ms
* earlier than its local `Date.now()` would suggest, so the visible
* paint lines up.
*/
export declare function getBarrierAnnounceTime<TValue>(channelKey: ChannelKey, target: TValue): number | null;
/**
* Internal handles for the `coordinatePreference.testUtils` sibling.
*
* Not part of the public API. Do not import this from production
* code or from tests directly — use the helpers re-exported from
* `./coordinatePreference.testUtils` instead so that the boundary
* between runtime API and test affordances stays clear.
*/
export declare const __testInternals: {
channels: Map<string, Channel<unknown>>;
setTargetEncoder: typeof setTargetEncoder;
};
export {};