import { getExistingManifestContext, type ExistingManifestContextOptions } from './existing-manifest-context'; import type { ManifestFunction } from './manifest-function'; import type { ManifestComponentDeletion, ManifestScope } from './manifest-builder'; import { requireStringComponentField } from './_existing-component-fields'; /** * Initialization properties for {@link SduiPage}. */ export interface SduiPageProps { /** * Unique identifier for this page within the package (e.g. `'gym_today_schedule'`). * * Required. */ sduiPageId: string; /** * Display name shown as the tab label in the app (e.g. `'Today\'s schedule'`). * * Required. */ name: string; /** * The function that renders this page. Pass a live {@link ManifestFunction} reference, * or a raw api_name string for functions defined outside this manifest. * * Required. */ function: ManifestFunction | string; } /** * Defines an SDUI page and registers it with the manifest. * * An SDUI page is a custom UI surface backed by a {@link ManifestFunction}. It appears * as a tab in a Rippling {@link App}. You would normally define SDUI pages after the * functions they reference, then pass them to an `App` via `pages`. * * @example * ```ts * const todaySchedulePage = new SduiPage(manifest, { * sduiPageId: 'gym_today_schedule', * name: 'Today\'s schedule', * function: todayScheduleFn, * }); * const memberListPage = new SduiPage(manifest, { * sduiPageId: 'gym_members', * name: 'Members', * function: memberListFn, * }); * * new App(manifest, { * apiName: 'gym_membership_management_app', * name: 'Gym Membership Management', * pages: [memberListPage, todaySchedulePage], * }); * ``` * * @see {@link ManifestFunction} — provides the handler that renders this page. * @see {@link App} — hosts this page as a tab via `pages`. */ export class SduiPage { static readonly componentType = 'SDUI_PAGE' as const; static toDeletionIdentifier(sduiPageId: string): ManifestComponentDeletion { return { type: SduiPage.componentType, sdui_page_id: sduiPageId, }; } private readonly _sduiPageId: string; private readonly _name: string; private readonly _functionApiName: string; /** * @param scope - The manifest to register this page with. * @param props - Initialization properties. * @throws {Error} If `sduiPageId`, `name`, or `function` is missing. */ constructor(scope: ManifestScope, props: SduiPageProps) { if (!props.sduiPageId) throw new Error('sduiPageId is required'); if (!props.name) throw new Error('name is required'); if (!props.function) throw new Error('function is required'); this._sduiPageId = props.sduiPageId; this._name = props.name; this._functionApiName = typeof props.function === 'string' ? props.function : props.function.getApiName(); scope._register(this); } /** * Loads this SDUI page from the active existing-manifest JSON context. */ static loadFromExisting(sduiPageId: string, options: ExistingManifestContextOptions = {}): SduiPage { return getExistingManifestContext(options).loadSduiPage(sduiPageId); } /** @internal Hydrates an SDUI page from existing manifest wire JSON. */ static _fromExistingComponent( scope: ManifestScope, component: Record, resolveFunction: (apiName: string) => ManifestFunction, ): SduiPage { const functionApiName = requireStringComponentField( component, 'function_api_name', SduiPage.componentType, ); const props: SduiPageProps = { sduiPageId: requireStringComponentField(component, 'sdui_page_id', SduiPage.componentType), name: requireStringComponentField(component, 'name', SduiPage.componentType), function: resolveFunction(functionApiName), }; return new SduiPage(scope, props); } /** * Returns the unique page identifier (e.g. `'gym_today_schedule'`). */ getSduiPageId(): string { return this._sduiPageId; } /** * Returns the display name (tab label) of this page. */ getName(): string { return this._name; } /** * Returns the api_name of the function backing this page. */ getFunctionApiName(): string { return this._functionApiName; } /** * Serializes this SDUI page to the wire format consumed by the manifest install endpoint. * * @returns A plain object with `type: 'SDUI_PAGE'`. */ toDict(): Record { return { type: SduiPage.componentType, name: this._name, sdui_page_id: this._sduiPageId, function_api_name: this._functionApiName, }; } }