import type { InferActivityParams, RegisteredActivityName, } from "@stackflow/config"; import type { ComponentType, ReactNode } from "react"; import { preloadableLazyComponent } from "./utils/PreloadableLazyComponent"; import { inspect, PromiseStatus, reject, resolve, type SyncInspectablePromise, } from "./utils/SyncInspectablePromise"; export const STRUCTURED_ACTIVITY_COMPONENT_TYPE: unique symbol = Symbol( "STRUCTURED_ACTIVITY_COMPONENT_TYPE", ); export interface StructuredActivityComponentType

{ [STRUCTURED_ACTIVITY_COMPONENT_TYPE]: true; content: Content

| (() => Promise<{ default: Content

}>); layout?: Layout

; loading?: Loading

; errorHandler?: ErrorHandler

; } export function structuredActivityComponent< ActivityName extends RegisteredActivityName, >(options: { content: | Content> | (() => Promise<{ default: Content> }>); layout?: Layout>; loading?: Loading>; errorHandler?: ErrorHandler>; }): StructuredActivityComponentType> { return { ...options, [STRUCTURED_ACTIVITY_COMPONENT_TYPE]: true, }; } export function isStructuredActivityComponent( value: unknown, ): value is StructuredActivityComponentType<{}> { return ( value !== null && typeof value === "object" && STRUCTURED_ACTIVITY_COMPONENT_TYPE in value ); } export interface Content

{ component: ComponentType<{ params: P }>; } export function content( component: ComponentType<{ params: InferActivityParams }>, ): Content> { return { component }; } const ContentComponentMap = new WeakMap< StructuredActivityComponentType<{}>, { Component: Content<{}>["component"]; preload: () => Promise } >(); export function getContentComponent( structuredActivityComponent: StructuredActivityComponentType<{}>, ): { Component: Content<{}>["component"]; preload: () => Promise } { if (ContentComponentMap.has(structuredActivityComponent)) { return ContentComponentMap.get(structuredActivityComponent)!; } const { Component, preload } = preloadableLazyComponent(() => { const content = structuredActivityComponent.content; const contentPromise = resolve( typeof content === "function" ? content() : { default: content }, ); const state = inspect(contentPromise); if (state.status === PromiseStatus.FULFILLED) { return resolve({ default: state.value.default.component, }); } else if (state.status === PromiseStatus.REJECTED) { return reject(state.reason); } return resolve( contentPromise.then((value) => ({ default: value.default.component, })), ); }); ContentComponentMap.set(structuredActivityComponent, { Component, preload }); return { Component, preload }; } export interface Layout

{ component: ComponentType<{ params: P; children: ReactNode }>; } export function layout( component: ComponentType<{ params: InferActivityParams; children: ReactNode; }>, ): Layout> { return { component }; } export interface Loading

{ component: ComponentType<{ params: P }>; } export function loading( component: ComponentType<{ params: InferActivityParams }>, ): Loading> { return { component }; } export interface ErrorHandler

{ component: ComponentType<{ params: P; error: unknown; reset: () => void }>; boundary?: CustomErrorBoundary; } export type CustomErrorBoundary = ComponentType<{ children: ReactNode; renderFallback: (error: unknown, reset: () => void) => ReactNode; }>; export function errorHandler( component: ComponentType<{ params: InferActivityParams; error: unknown; reset: () => void; }>, options?: { boundary?: CustomErrorBoundary; }, ): ErrorHandler> { return { component, boundary: options?.boundary }; }