UNPKG

@mui/internal-docs-infra

Version:

MUI Infra - internal documentation creation tools.

408 lines 19.2 kB
import type * as React from 'react'; import type { SettleGate } from "../useCoordinated/createSettleGate.mjs"; /** * Provided by a {@link CoordinatedLazy} to its fallback subtree while the * fallback is shown. Carries the upward hoist channel and the * nested-suppression flag. Generalizes `CodeHighlighterFallbackContext` * (`{ extraVariants, setFallbackHasts, onHookCalled }`). */ export interface CoordinatedFallbackContextValue { /** * Hoist a keyed value up to the swap so it can be folded into the consumer's * `ready` decision and handed down to the full content via * {@link CoordinatedContentContextValue}. Generalizes `setFallbackHasts`. */ hoist?: (key: string, value: unknown) => void; /** * Signal that the fallback's hoist hook ran. The generic swap force-mounts * the fallback on its own, so this is optional - consumers (e.g. * `CodeHighlighter`) use it to validate that the loading component wired its * hoist hook. Generalizes `onHookCalled`. */ onReady?: () => void; /** * `true` when this instance is nested inside an outer `CoordinatedLazy` still * showing its own fallback. The inner stays in fallback while set, collapsing * a "fallback -> content -> fallback -> content" flicker into one transition. * Generalizes `isNestedInsideOuterFallback`. */ isNested?: boolean; /** Arbitrary parent->fallback data (generalizes `extraVariants`). */ data?: Record<string, unknown>; } /** * The data the fallback hoisted, handed down to the full content so it can use * what the fallback fetched (e.g. a DEFLATE dictionary). Read via * {@link useCoordinatedContent}. */ export interface CoordinatedContentContextValue { hoisted: Record<string, unknown>; /** * The content calls this once it has loaded (its dynamic import resolved), so * the swap can register readiness with the settle gate. Used by `LazyContent`. */ reportReady?: () => void; /** * The loading fallback to show *while a dynamically-imported content loads*. * After the swap reveals the content, a `LazyContent` shows this as its own * Suspense fallback during the `import()` - so the same placeholder the swap * showed keeps covering the load, with no empty flash. Generalizes "hand the * `ContentLoading` to the lazy content". */ fallback?: React.ReactNode; } /** Result of {@link useCoordinatedFallback}. */ export interface UseCoordinatedFallbackResult { /** Parent->fallback data provided via {@link CoordinatedFallbackContextValue.data}. */ data?: Record<string, unknown>; /** Whether this fallback's `CoordinatedLazy` is nested inside an outer, still-loading one. */ isNested: boolean; } /** Options for {@link useCoordinatedSwap}. */ export interface UseCoordinatedSwapOptions { /** Whether the content's data is ready to display. */ ready: boolean; /** Hold the swap while real async work is still in flight even though `ready`. */ defer?: boolean; /** * Hold the settle gate open WITHOUT re-showing the fallback - the content stays * rendered while the page-wide coordination waits. Unlike `defer` (which holds * the rendered fallback), this only affects gate registration, for content that * has swapped in but is still finishing deferred work it gates internally (e.g. * the code highlighter rendering plain text, then highlighting in place). */ holdGate?: boolean; /** Whether a fallback element exists to show. */ hasFallback: boolean; /** Skip the fallback entirely. */ skipFallback?: boolean; /** Additionally hold the swap until the fallback hoists at least once. */ requireHoist?: boolean; /** * Hold the swap until the content reports it has loaded. The consumer mounts * the content (e.g. a `LazyContent`) while the fallback is shown so it can * load in the background, returning `null` until ready and then calling the * content context's `reportReady` - so a code-split content component loads * behind the placeholder and reveals only once its chunk has arrived. */ awaitContent?: boolean; /** * Settle gate to register this swap with. When omitted, the ambient gate from * a surrounding coordinator (e.g. the `useStream` controller, via * {@link CoordinatedGateContext}) is used instead. The page-global gate is * always registered on top of either, so a page-wide coordinated commit waits * for the swap regardless. */ gate?: SettleGate; /** Arbitrary parent->fallback data exposed via the fallback context. */ data?: Record<string, unknown>; /** * Fired as soon as the fallback hoists data, with the hoisted map. Lets the * consumer kick off dynamic `import()`s of heavy helpers it can tell from the * data it will need - in parallel with loading the full content, instead of * the content mounting and then requesting them in a serial roundtrip. Should * be idempotent (the module cache dedups within a graph); cross-instance * dedup is the layout provider's job. */ preload?: (hoisted: Record<string, unknown>) => void; } /** Result of {@link useCoordinatedSwap}. */ export interface UseCoordinatedSwapResult { /** Whether the fallback branch should mount this render. */ showFallback: boolean; /** Context value to provide to the fallback subtree. */ fallbackContext: CoordinatedFallbackContextValue; /** Data hoisted up from the fallback so far, keyed. */ hoisted: Record<string, unknown>; /** `true` while the fallback is being shown. */ loading: boolean; /** In `awaitContent` mode, whether the content has reported it loaded. */ contentReady: boolean; /** Passed to the content (via the content context) so it can report it loaded. */ reportContentReady: () => void; /** * Hoist a keyed value up to the swap directly (the same channel the fallback's * `useCoordinatedFallback` uses). Lets the consumer populate the hoisted map * from outside the fallback subtree - e.g. a client-loaded data path that has * no fallback mounted but still needs to feed the hoisted dictionary. */ hoist: (key: string, value: unknown) => void; } /** Props for {@link CoordinatedLazy}. */ export interface CoordinatedLazyProps { /** Full content, shown after the swap. Pre-rendered on the server. */ content: React.ReactNode; /** Loading placeholder; force-mounted once so its hoist hook runs. */ fallback?: React.ReactNode; /** Whether the content's data is ready to display. */ ready: boolean; /** Hold the swap while real async work is in flight even though `ready`. */ defer?: boolean; /** * Hold the settle gate open without re-showing the fallback (content stays * rendered). See {@link UseCoordinatedSwapOptions.holdGate}. */ holdGate?: boolean; /** Skip the fallback entirely. */ skipFallback?: boolean; /** Hold the swap until the fallback hoists at least once. */ requireHoist?: boolean; /** * Hold the swap until the content reports it has loaded, mounting the content * behind the fallback so a code-split (e.g. `LazyContent`) content can load in * the background and reveal only once its chunk has arrived. See * {@link UseCoordinatedSwapOptions.awaitContent}. */ awaitContent?: boolean; /** * Settle gate to register this swap with. When omitted, the ambient gate from * a surrounding coordinator (e.g. the `useStream` controller, via * {@link CoordinatedGateContext}) is used instead. The page-global gate is * always registered on top of either, so a page-wide coordinated commit waits * for the swap regardless. */ gate?: SettleGate; /** Arbitrary parent->fallback data exposed to the fallback subtree. */ data?: Record<string, unknown>; /** * Speculative preload hook. See {@link UseCoordinatedSwapOptions.preload}: * fired with the hoisted data so the consumer can start dynamic imports of * helpers in parallel with loading the full content. */ preload?: (hoisted: Record<string, unknown>) => void; } /** * A dynamic import of a component, in the standard `() => import('./X')` shape. * The imported module's `default` export is the component. The component may be * authored as a server OR client component: on the client it is only ever * rendered when the consumer routed to the client path, and a server-only * component should resolve (via a conditional export) to a client stub that * throws if rendered - so importing it is harmless, only execution is guarded. */ export type LazyComponentImport<T> = () => Promise<{ default: React.ComponentType<T>; }>; /** * Props the full chunk content receives. Mirrors `ContentProps<T>`: the user's * generic props `T` are merged in, plus the resolved `data` (of type `P`) and a * `loading` flag (`false` once the full data is in). */ export type ChunkContentProps<T extends {} = {}, P = unknown> = { data?: P; loading: boolean; /** * Re-run the chunk's `data`-mode loader and swap in fresh data, keeping the * current data visible meanwhile (stale-while-revalidate). Client-only; a * no-op for non-`data` sources. Provided by the framework's client renderer. */ refresh?: () => Promise<void>; /** `true` while a background refresh is in flight (the current data stays). */ revalidating?: boolean; } & T; /** * Props the loading placeholder receives. Mirrors `ContentLoadingProps<T>`: * `loading` is always `true`, and `data` carries whatever initial/preloaded * value is available (of type `P`). */ export type ChunkLoadingProps<T extends {} = {}, P = unknown> = { data?: P; loading: true; } & T; /** Decide whether the preloaded value is enough to render the full content. */ export type IsLoaded<P = unknown> = (preloaded: P | undefined) => boolean; /** Decide whether the preloaded value is enough to render the initial state. */ export type IsInitial<P = unknown> = (preloaded: P | undefined) => boolean; /** * Result of a `urls`-mode loader: the chunk URLs to load individually, rather * than the data itself. `lastChunk` marks the final URL for last-chunk * completion when the total isn't known up front. */ export interface StreamUrlsResult { chunks: URL[]; lastChunk?: boolean; } /** * Where a chunk's data comes from - a **discriminated union** on `mode`, so * each strategy is strongly typed with no overloads or runtime return-type * sniffing: * * - `'data'` - load the chunk's data directly (optionally with a quick * `initial` value first). * - `'urls'` - split into per-chunk URLs (`loadUrls`), then load each URL's * data (`loadChunk`); supports an initial pass. * - `'stream'` - push chunks into the passed array over time and `yield` after * each, for progressive reveal (the generator's return is the last-chunk * signal). */ export type StreamSource<P = unknown, O = unknown> = { mode: 'data'; /** Load the chunk's full data. */ load: (options: O, signal: AbortSignal) => Promise<P>; /** Optional quick initial value rendered before the full load resolves. */ initial?: (options: O) => P; } | { mode: 'urls'; /** Resolve the per-chunk URLs (not the data) for the full content. */ loadUrls: (options: O, signal: AbortSignal) => Promise<StreamUrlsResult>; /** Load one chunk's data from its URL. */ loadChunk: (url: URL, options: O, signal: AbortSignal) => Promise<P>; /** Optional initial URL set for a quick first paint. */ initialUrls?: (options: O) => StreamUrlsResult; /** Optional initial loader for a single chunk URL. */ initialChunk?: (url: URL, options: O) => P; } | { mode: 'stream'; /** * Push chunks into `chunks` and `yield` after each; returning ends the * stream (last-chunk completion). Runs on the client (incremental * `setState`) and as the isomorphic driver the server awaits. */ stream: (chunks: P[], options: O, signal: AbortSignal) => AsyncGenerator<void, void, void>; }; /** Swap timing forwarded to the underlying `CoordinatedLazy`. */ export interface ChunkSwapConfig { defer?: boolean; requireHoist?: boolean; channelKey?: string | null; } /** Configuration for {@link createCoordinatedLazy}. */ export interface CreateChunkConfig<T extends {} = {}, P = unknown, O = unknown> { /** The full content component. */ ChunkContent: React.ComponentType<ChunkContentProps<T, P>>; /** The loading placeholder. Defaults to a component that renders `null`. */ ChunkLoading?: React.ComponentType<ChunkLoadingProps<T, P>>; /** Whether the preloaded value suffices for the full content. */ isLoaded?: IsLoaded<P>; /** Whether the preloaded value suffices for the initial state. */ isInitial?: IsInitial<P>; /** * Data source (discriminated by `mode`). Its loader functions run **on the * server only** - a `data`-mode source is executed by `ChunkServerLoader` * (`source.load` for the full content, `source.initial` for a quick streamed * paint) and never serialized into a Client Component. To load on the *client*, * supply the source through a {@link ChunkProvider} (which lazily imports it) * rather than this field. (Calling `useChunk` directly inside your own client * component with a `source` is still fine - no server/client boundary is * crossed there.) */ source?: StreamSource<P, O>; /** * Server component rendered (under Suspense) to produce the full content. * Always dynamically imported, and only imported when the render decision * routes to it - so it never reaches the client bundle. */ Loader?: LazyComponentImport<ChunkContentProps<T, P>>; /** Server component rendered (under Suspense) to produce the initial state. */ InitialLoader?: LazyComponentImport<ChunkContentProps<T, P>>; /** Swap timing forwarded to `CoordinatedLazy`. */ swap?: ChunkSwapConfig; /** Default options passed to the source loaders. */ loaderOptions?: O; /** * The `ChunkContent` component performs its own client-side loading and * fallback->content swap. When set, the client-driven render modes render * `ChunkContent` directly (with `loading: true`) instead of wrapping it in the * framework's {@link useChunk}+swap (`CoordinatedLazyClient`) - so a * self-managing content (e.g. one already built on `useCoordinatedSwap`) is not * double-swapped. Server and content/`content-initial` modes are unaffected. */ contentManagesSwap?: boolean; /** * Opt into stale-while-revalidate: once the chunk has loaded, automatically * re-run the loader once on the first idle period (via `requestIdleCallback`) * to refresh potentially-stale data in the background. Client-only. The chunk * keeps showing its current data while the refresh is in flight. */ revalidateOnIdle?: boolean; } /** * The chunk config without the server-only loader functions * (`source`/`Loader`/`InitialLoader`). This is what the `'use client'` * {@link CoordinatedLazyClient} accepts: `createCoordinatedLazy` strips those * fields before constructing the client element, so the type system guarantees * no loader function is ever serialized across the server/client boundary. A * client-loaded chunk gets its source from a {@link ChunkProvider} in context, * not from this config. */ export type ClientChunkConfig<T extends {} = {}, P = unknown, O = unknown> = Omit<CreateChunkConfig<T, P, O>, 'source' | 'Loader' | 'InitialLoader'>; /** The branch of the render decision that applies for a chunk. */ export type ChunkRenderMode = 'content' | 'content-initial' | 'server-initial' | 'server-loader' | 'attempt-initial-client'; /** Already-evaluated inputs to `resolveChunkRender` (decoupled from config shape). */ export interface ChunkRenderInputs { /** Evaluated `isLoaded(preloaded)` (or the controlled override). */ isLoaded: boolean; /** Evaluated `isInitial(preloaded)`. */ isInitial: boolean; /** A server initial is configured: an `InitialLoader`, or a `data`-mode `source.initial`. */ hasServerInitial: boolean; /** A server full loader is configured: a `Loader`, or a `data`-mode `source.load`. */ hasServerLoader: boolean; } /** Result of `resolveChunkRender`. */ export interface ChunkRenderDecision { mode: ChunkRenderMode; loading: boolean; } /** Props accepted by the component returned from {@link createCoordinatedLazy}. */ export interface ChunkComponentProps<T extends {} = {}, P = unknown, O = unknown> { /** Build-time/precomputed value for this chunk. */ preloaded?: P; /** Authoritative/controlled value: render content directly, never the loaders. */ controlled?: boolean; /** * Force client-side rendering: ignore the server `Loader`/`InitialLoader` for * this render so the decision routes to a content/client branch instead. Lets * a consumer that *configures* server loaders statically opt out of them * per-render (e.g. when no server loading functions are available, or the * caller explicitly wants the client to drive). Has no effect once * `isLoaded`/`controlled` already render the content. */ forceClient?: boolean; /** * Per-render override for the `isInitial` decision input (whether the initial * paint is already in hand). Mirrors how `controlled` overrides `isLoaded`: * lets a consumer whose initial-readiness depends on per-render context it * cannot express as a pure `config.isInitial(preloaded)` predicate compute it * in its own router and pass the result. Takes precedence over * `config.isInitial`. */ isInitial?: boolean; /** * For the server render modes (`server-loader`/`server-initial`), block the * server render on the loader instead of streaming a fallback: render the * server loader *without* a Suspense boundary, so its content lands in the * initial HTML (e.g. for no-JS / crawler SSR). When unset (the default) the * loader streams under Suspense, showing `ChunkLoading` until it resolves. */ awaitServerLoad?: boolean; /** * Skip the initial-loader stage: ignore the `InitialLoader` / source-`initial` * for this render so a not-yet-loaded chunk loads the full content directly * rather than fetching a quick initial first. For consumers that have no * loading UI to show an initial paint into (so a 2-stage initial->full load * would be wasted). */ skipInitialLoad?: boolean; /** Per-render loader options (merged over the config's `loaderOptions`). */ loaderOptions?: O; /** User generic props forwarded to `ChunkContent` / `ChunkLoading`. */ userProps?: T; /** Settle gate to register with (defaults to the surrounding controller / page). */ gate?: SettleGate; } /** Props for `LazyContent` / `LazyContentServer`. */ export interface LazyContentProps<T extends {} = {}> { /** Dynamic import of the component to render. */ content: LazyComponentImport<T>; /** Props forwarded to the imported component. */ props?: T; /** Placeholder shown while the module loads. Defaults to `null`. */ fallback?: React.ReactNode; /** * Additional settle gate to report readiness to once the component has loaded * and mounted (e.g. a `useStream` controller gate). The page-global gate is * always registered too. Client path only - the server path streams via * Suspense and has no client gate to report to. */ gate?: SettleGate; }