/** * Extensibility types for the JUDO UI Runtime. * * Provides type-safe contracts for: * - Custom component implementations (customImplementation="true" elements) * - Page/dialog action lifecycle overrides * - Visual property overrides * * These types are consumed by: * - @judo/core (CustomizationsProvider, resolution hooks) * - @judo/codegen (generated type-safe wrappers) * - Consumer applications (manual or generated registrations) */ import type { ComponentType, ReactElement, ReactNode } from "react"; import type { ActionDefinition } from "./structural/actions"; import type { MenuLayout } from "./enums"; import type { Action, Application, NavigationController, NavigationItem, PageDefinition } from "./structural/application"; import type { Button } from "./visual/buttons"; import type { ButtonGroup } from "./visual/buttons"; import type { Formatted, IconImage, Label, Text } from "./visual/display"; import type { BinaryTypeInput, Checkbox, DateInput, DateTimeInput, EnumerationCombo, EnumerationRadio, EnumerationToggleButtonbar, NumericInput, PasswordInput, Switch, TextArea, TextInput, TimeInput, TrinaryLogicCombo } from "./visual/inputs"; import type { Divider, Flex, PageContainer, Spacer, VisualElement } from "./visual/layout"; import type { Link } from "./visual/link"; import type { Column, Filter, Table } from "./visual/table"; import type { TabController } from "./visual/tabs"; /** * Props passed to every custom component implementation. * The element prop is typed to the specific VisualElement subtype. */ export interface CustomComponentProps { /** The VisualElement model object */ element: T; /** The page definition this element belongs to */ page: PageDefinition; /** Action dispatch function */ onDispatch?: (action: Action, additionalContext?: Record) => Promise; } /** * A custom component implementation. * Can use any runtime hooks (useVisualBinding, useDataStore, useNavigation, etc.) */ export type CustomComponent = ComponentType>; /** * Maps each concrete VisualElement @type discriminator string * to its corresponding TypeScript interface. * * Used by ComponentInterceptor for type-safe interceptor registration. */ export interface ElementTypeMap { Flex: Flex; PageContainer: PageContainer; Spacer: Spacer; Divider: Divider; Text: Text; Label: Label; Formatted: Formatted; IconImage: IconImage; TabController: TabController; Table: Table; Column: Column; Filter: Filter; Link: Link; ButtonGroup: ButtonGroup; Button: Button; TextInput: TextInput; TextArea: TextArea; NumericInput: NumericInput; DateInput: DateInput; DateTimeInput: DateTimeInput; TimeInput: TimeInput; Checkbox: Checkbox; Switch: Switch; EnumerationCombo: EnumerationCombo; EnumerationRadio: EnumerationRadio; EnumerationToggleButtonbar: EnumerationToggleButtonbar; TrinaryLogicCombo: TrinaryLogicCombo; BinaryTypeInput: BinaryTypeInput; PasswordInput: PasswordInput; } /** * Union of all valid VisualElement @type discriminator strings. */ export type ElementTypeName = keyof ElementTypeMap; /** * Interceptor function for a visual element type. * Called for EVERY element of the matching @type before default rendering. * * Return a React component to replace the default rendering for this element, * or null/undefined to fall through to default rendering. * * @param element - The VisualElement model instance (typed to the specific subtype) * @param page - The PageDefinition this element belongs to * @returns A CustomComponent to render, or null/undefined for default rendering * * @example * ```typescript * const interceptor: ComponentInterceptor<'TextInput'> = (element, page) => { * if (element.attributeType?.name?.endsWith('Email')) { * return EmailInputComponent; * } * return null; // default rendering * }; * ``` */ export type ComponentInterceptor = (element: ElementTypeMap[K], page: PageDefinition) => CustomComponent | null | undefined; /** * Loose interceptor signature for runtime use. * At runtime the concrete element type is not statically known, * so we widen to VisualElement / CustomComponent. */ export type AnyComponentInterceptor = (element: VisualElement, page: PageDefinition) => CustomComponent | null | undefined; /** * Type-safe map of element type interceptors. * Each key is a VisualElement @type discriminator, and the interceptor * function receives the correctly-typed element instance. */ export type ComponentInterceptors = { [K in ElementTypeName]?: ComponentInterceptor; }; /** * Context available to all action lifecycle methods. * Extends the runtime action handler context with metadata and callDefault. */ export interface ActionLifecycleContext { /** The Action model object being executed */ action: Action; /** The ActionDefinition (action.actionDefinition) */ actionDefinition: ActionDefinition; /** The page this action belongs to */ page: PageDefinition; /** The transfer being acted upon */ transfer?: unknown; /** Selected rows for bulk operations */ selectedRows?: unknown[]; /** Dialog closer if action is in a dialog */ closeDialog?: (result?: unknown) => void; /** Navigation context */ navigation?: unknown; /** Data store */ data?: unknown; /** Model registry */ registry?: unknown; /** Validation context */ validation?: unknown; /** Notification service */ notifications?: unknown; /** Whether the component is eager (aggregated) */ isEager?: boolean; /** Current page container type */ pageType?: "TABLE" | "FORM" | "VIEW"; /** Whether the page is in edit mode */ isEditMode?: boolean; /** Whether this is a page-level action */ isPageAction?: boolean; /** Call the built-in default implementation for this lifecycle phase */ callDefault: () => Promise; /** Additional arbitrary context properties */ [key: string]: unknown; } /** * Standard lifecycle methods available on every action. * All methods are optional — unoverridden methods use built-in defaults. */ export interface ActionLifecycle { /** * Called before the action executes. * Return `false` to cancel execution. Return `void` or `true` to proceed. * Use for: custom validation, confirmation dialogs, pre-fetch. */ before?(context: ActionLifecycleContext): Promise; /** * The core action execution. * If provided, REPLACES the built-in execution entirely. * The built-in handler does NOT run when this is overridden * (unless callDefault() is called explicitly). */ execute?(context: ActionLifecycleContext): Promise; /** * Called after successful execution. * Receives the result of execute(). * Use for: notifications, navigation, data refresh, side effects. */ after?(context: ActionLifecycleContext, result: unknown): Promise; /** * Called when execution throws. * Return `true` to suppress built-in error handling. * Return `false`/`void` to let default error handling proceed. */ onError?(context: ActionLifecycleContext, error: unknown): Promise; } /** * Set of visual properties that can be overridden per element. */ export interface VisualPropertySet { /** Override element visibility */ hidden?: boolean; /** Override element disabled state */ disabled?: boolean; /** Override element required state */ required?: boolean; /** Override element label text */ label?: string; /** Override element read-only state */ readOnly?: boolean; /** Additional CSS class names */ className?: string; /** Arbitrary custom data passed to the element's component */ custom?: Record; } /** * @internal Runtime format for visual property overrides. * * Keys are element sourceIds resolved internally by the generated `createCustomizations()` factory. * **Developers never construct this type directly.** Use the typed factory * where keys are human-readable element names. */ export type VisualPropertyOverrides = Record>; /** * Hook type for providing visual property overrides. * Called within the page's React tree — can use any hooks. */ export type VisualPropertiesHook = () => VisualPropertyOverrides; /** * Hook function that produces scoped MUI ThemeOptions for a named sub-theme. * * Receives the current (parent) MUI Theme and optional transfer data. * Returns partial ThemeOptions that are deep-merged onto the parent theme * via `createTheme(parentTheme, subThemeHook(parentTheme, data))`. * * Sub-themes nest naturally: a child element's sub-theme is merged on top * of its parent's sub-theme, giving inner themes higher precedence. * * @param parentTheme - The current MUI Theme (may already be a sub-theme) * @param data - Optional transfer data available in the current binding scope * @returns Partial ThemeOptions to merge onto the parent theme * * @example * ```typescript * import type { Theme, ThemeOptions } from "@mui/material/styles"; * import { alpha } from "@mui/material/styles"; * * const cardSubTheme: SubThemeHook = (parentTheme: Theme) => ({ * components: { * MuiPaper: { * styleOverrides: { * root: { background: alpha(parentTheme.palette.secondary.main, 0.97) }, * }, * }, * }, * }); * ``` */ export type SubThemeHook = (parentTheme: any, data?: Record) => Record; /** * Contextual options describing the failing operation, passed to the error * handler interceptor. Mirrors the React template's `ErrorHandlingOption`. * * All fields are optional — the runtime supplies whatever it knows about the * failing dispatch (e.g. the key/name of the operation or CRUD action that * triggered the error). */ export interface ErrorHandlingOption { /** Human-readable identifier of the operation/action that failed. */ key?: string; /** Arbitrary additional context about the failing call. */ [key: string]: unknown; } /** * Error handler interceptor hook. * * Mirrors the React template's `ErrorHandlerInterceptorHook`. Lets applications * intercept REST errors before the runtime's default error handling (toasts / * validation messages / fault dialogs) runs. * * `interceptError` is invoked **only** when `shouldInterceptError` returned * `true` for the same error; otherwise the runtime's default handling runs. * * @example * ```typescript * const errorHandlerInterceptor: ErrorHandlerInterceptorHook = { * shouldInterceptError: (error) => (error as { status?: number }).status === 409, * interceptError: (error, options) => { * showConflictDialog(error, options?.key); * }, * }; * ``` */ export interface ErrorHandlerInterceptorHook { /** * Decide whether this interceptor handles the given error. * Return `true` to suppress the runtime's default handling and route the * error to `interceptError`; return `false` to fall through to default handling. */ shouldInterceptError(error: unknown, options?: ErrorHandlingOption): boolean; /** * Perform custom handling for an error previously accepted by * `shouldInterceptError`. Never called unless `shouldInterceptError` returned `true`. * * @param error - The error thrown by the failing dispatch. * @param options - Contextual options describing the failing operation. * @param payload - Optional request payload that triggered the error. * @param dataPath - Optional data path associated with the failing call. */ interceptError(error: unknown, options?: ErrorHandlingOption, payload?: unknown, dataPath?: string): void; } /** * On-blur action function. * * Invoked by `InputRenderer` when an input/relation element modelled with the * `onBlur` flag loses focus (or completes a relation selection). Mirrors the * React template's `onBlurAction(data, storeDiff, editMode, submit)`. * * For table containers only `submit()` is meaningful — `data`, `storeDiff` and * `editMode` reflect the row/edit context when available. * * @param data - An eager copy of the container's `*Stored` data at blur time. * @param storeDiff - Apply a field change to the container data: `storeDiff(attribute, value)`. * @param editMode - Whether the container is currently in edit mode. * @param submit - Submit the container (e.g. trigger save/update). */ export type BlurActionFn = (data: Record | undefined, storeDiff: (attribute: string, value: unknown) => void, editMode: boolean, submit: () => void) => void; /** * Navigation interceptor hook. * * Mirrors the React template's `NavigationInterceptorHook`. Lets applications * decorate every outgoing navigation (e.g. to persist "sticky" search params) * and run logic once on initial page load. * * @example * ```typescript * const navigationInterceptor: NavigationInterceptorHook = { * decorateNavigation: (to) => `${to}${to.includes("?") ? "&" : "?"}tenant=acme`, * onBeforeInitialLoad: () => { restoreStickyParams(); }, * }; * ``` */ export interface NavigationInterceptorHook { /** * Decorate an outgoing navigation destination. Receives the destination as a * string (the runtime stringifies partial paths via `stringifyPath` first) * and returns the (possibly modified) destination string the runtime routes to. */ decorateNavigation(to: string): string; /** * Optional hook invoked exactly once during initial page load, before the * first model-driven navigation. Not invoked on subsequent navigations. */ onBeforeInitialLoad?(): void; /** * Helper supplied by the runtime for converting a partial path object to a * string. Applications may call it from `decorateNavigation`. */ stringifyPath?(to: string | Record): string; } /** * A single global hotkey registration. * * Mirrors the metadata the React template's generated `hotkeys.tsx` uses to * render its "list of hotkeys" dialog and bind shortcuts to action hooks. */ export interface GlobalHotkey { /** * Key combination, in `react-hotkeys-hook` syntax (e.g. `"ctrl+k"`, * `"meta+shift+p"`). */ keys: string; /** Handler invoked when the key combination is pressed. */ handler: () => void; /** Human-readable label shown in the built-in "list of hotkeys" dialog. */ label?: string; } /** * Global hotkey registration surface for `CustomizationsConfig`. * * Either a declarative list of {@link GlobalHotkey} entries that the app-shell * binds (and renders in the built-in "list of hotkeys" dialog), or a free-form * React hook/component the runtime mounts once inside the provider tree (giving * apps full control, matching the template's `registerGlobalHotkeys`). */ export interface GlobalHotkeysConfig { /** Declarative hotkey list bound by the runtime and shown in the built-in dialog. */ hotkeys?: GlobalHotkey[]; /** * Free-form registration component/hook mounted once inside the app-shell * provider tree. Use for full control over key binding. */ Component?: ComponentType; /** When `true`, render the built-in "list of hotkeys" dialog from {@link GlobalHotkeysConfig.hotkeys}. */ showDialog?: boolean; } /** * @internal Runtime format for action overrides on a page. * * Keys are sourceIds resolved internally by the generated `createCustomizations()` factory. * **Developers never construct this type directly.** Use the typed factory with * human-readable action names instead. * * @see The generated `createCustomizations()` factory for the developer-facing API. */ export type PageActionOverrides = Record>; /** * Page-scoped customization bundle. * * Groups all customizations for a single page/dialog together: * action overrides, visual property overrides, and element-level behaviors. * * @internal This is the runtime format consumed by `CustomizationsProvider`. * **Developers should not construct `PageCustomization` objects directly.** * Instead, use the generated `createCustomizations()` factory which provides * type-safe, human-readable page and element names. * * @example * ```typescript * // Correct: use the generated type-safe factory * import { createCustomizations } from './generated'; * * const customizations = createCustomizations({ * pages: { * UserListPage: { * actions: useUserListPageActions, * visualProperties: () => ({ nameField: { hidden: true } }), * typeaheadProviders: { * cityField: async (text) => fetchCitySuggestions(text), * }, * }, * }, * }); * ``` */ export interface PageCustomization { /** * @internal Hook returning action lifecycle overrides for this page. * Keys in the returned map are sourceIds (resolved by factory). */ actions?: () => PageActionOverrides; /** * @internal Hook returning visual property overrides for this page. * Keys in the returned map are sourceIds (resolved by factory). */ visualProperties?: VisualPropertiesHook; /** * @internal Typeahead / autocomplete providers for text inputs on this page. * * Keys are element sourceIds (resolved from human-readable names by the * generated `createCustomizations()` factory). */ typeaheadProviders?: Record Promise>; /** * @internal Row highlighting configuration for tables on this page. * * Keys are element sourceIds (resolved from human-readable names by the * generated `createCustomizations()` factory). */ tableRowHighlighting?: Record; /** * @internal Enumeration option filter functions for enum inputs on this page. * * Keys are element sourceIds (resolved from human-readable names by the * generated `createCustomizations()` factory). */ enumOptionFilter?: Record; /** * @internal Date/DateTime validation prop providers for date inputs on this page. * * Keys are element sourceIds (resolved from human-readable names by the * generated `createCustomizations()` factory). */ dateValidationProps?: Record; /** * @internal Column customizer functions for table columns on this page. * * Keys are element sourceIds (resolved from human-readable names by the * generated `createCustomizations()` factory). */ columnCustomizers?: Record; /** * @internal Item container configuration for tables with `representationComponent: 'CARD'` or `'TAG'`. * * Keys are element sourceIds (resolved from human-readable names by the * generated `createCustomizations()` factory). */ itemContainerConfigs?: Record; /** * @internal On-blur action functions for input/relation elements on this page. * * Keys are element sourceIds (resolved from human-readable names by the * generated `createCustomizations()` factory, which emits typed * `onBlurAction` method names). */ blurActions?: Record; } /** * Configuration for a single table row highlighting rule. * * Each rule defines a visual style (background color, optional text color) * and a condition function evaluated per row. Rules are evaluated in order — * the first matching rule determines the row's styling. */ export interface TableRowHighlightConfig { /** Unique CSS-safe name for this rule (used as CSS class name suffix) */ name: string; /** Human-readable label displayed in the highlight legend */ label: string; /** Background color applied to matching rows (CSS color value) */ backgroundColor: string; /** Optional text color applied to matching rows (CSS color value) */ color?: string; /** Condition function — return true to apply this highlight to the row */ condition: (params: { row: Record; }) => boolean; } /** * Filter function for enumeration option lists. * * Receives the current transfer data (from the data store) and the default * option array produced by `convertEnumOptions()`. Returns the options that * should actually be rendered — may filter, reorder, or replace entries. * * @param data - Current transfer data (may be `undefined` before data is loaded) * @param options - Default `{ value, label }` options from the model * @returns The filtered/reordered options to render */ export type EnumOptionFilterFn = (data: Record | undefined, options: Array<{ value: string; label: string; }>) => Array<{ value: string; label: string; }>; /** * Dynamic validation constraints for date/datetime inputs. * * All properties are optional — only specified constraints are applied. * `disableFuture` and `disablePast` are resolved to `maxDate = today` and * `minDate = today` respectively at render time. */ export interface DateValidationProps { /** Earliest selectable date */ minDate?: Date; /** Latest selectable date */ maxDate?: Date; /** When true, dates after today are not selectable */ disableFuture?: boolean; /** When true, dates before today are not selectable */ disablePast?: boolean; } /** * Function returning dynamic date validation constraints. * * Receives the current transfer data (from the data store) and returns * `DateValidationProps` that constrain the date picker. * * @param data - Current transfer data (may be `undefined` before data is loaded) * @returns Validation constraints to apply to the date input */ export type DateValidationFn = (data: Record | undefined) => DateValidationProps; /** * Column customizer function for overriding MUI DataGrid column definitions. * * Receives the original `GridColDef` built by the framework from the Column * model element, and returns a (possibly modified) `GridColDef`. * * The `column` parameter provides the Column model element for inspection * (e.g., to read `attributeType`, `label`, etc.). * * @param colDef - The default GridColDef built by TableRenderer * @param column - The Column model element * @returns The customized GridColDef (may be the same object or a new one) * * @example * ```typescript * const currencyColumnCustomizer: ColumnCustomizerFn = (colDef, column) => ({ * ...colDef, * renderCell: (params) => `$${Number(params.value).toFixed(2)}`, * }); * ``` */ export type ColumnCustomizerFn = (colDef: Record, column: Column) => Record; /** * Props passed to custom item components in card/tag container rendering. */ export interface ItemComponentProps { /** The row transfer data */ row: Record; /** Column definitions from the table model */ columns: Column[]; /** Callback to dispatch a row-level action */ onRowClick?: () => void; /** Callback to remove this item (present when RemoveActionDefinition exists) */ onDelete?: () => void; } /** * Props passed to custom toolbar components in card/tag container rendering. */ export interface ItemToolbarProps { /** The table model element */ element: Table; /** Total number of items */ totalCount?: number; } /** * Configuration for item container rendering of tables. * * When `representationComponent` is `CARD` or `TAG`, the `TableRenderer` * renders table data as a grid of cards or tags instead of a DataGrid. * This config allows overriding the item component, toolbar, and layout. */ export interface ItemContainerConfig { /** Layout direction for items. @default 'vertical' */ layout?: "horizontal" | "vertical"; /** Custom item component replacing the default card/tag renderer */ ItemElement?: ComponentType; /** Custom toolbar component replacing the default toolbar */ ToolbarElement?: ComponentType; } /** * @internal Runtime format for the complete customizations configuration. * * This is the **internal** type consumed by `CustomizationsProvider`. * **Developers should never construct `CustomizationsConfig` directly.** * * Instead, use the generated `createCustomizations()` factory which provides: * - Type-safe, human-readable page and element names * - Compile-time validation of action names, component names, and element names * - Automatic name→sourceId resolution (sourceIds are never visible to developers) * * @example * ```typescript * // Correct: use the generated type-safe factory * import { createCustomizations } from './generated'; * * const customizations = createCustomizations({ * pages: { * UserListPage: { * actions: useUserListPageActions, * visualProperties: () => ({ searchBox: { hidden: true } }), * }, * }, * components: { * OrderSummaryWidget: OrderSummaryComponent, * }, * componentInterceptors: { * TextInput: (element, page) => * element.attributeType?.name?.endsWith('Email') ? EmailInput : null, * }, * footerText: '© 2026 Acme Corp', * }); * ``` */ /** * Props contract for a custom AppBar layout slot component. * * Receives drawer width, menu click handler, logo, extra content, * and navigation controller for horizontal mode rendering. * * @see `DefaultAppBar` from `@judo/app-shell` for the built-in implementation. */ export interface LayoutAppBarProps { /** Active drawer width in pixels (0 when drawer is closed or horizontal mode). */ drawerWidth: number; /** Click handler for the hamburger menu button. When undefined, the button is hidden (horizontal mode). */ onMenuClick?: () => void; /** Custom logo ReactNode for the AppBar. Falls back to application name. */ logo?: ReactNode; /** Additional content injected into the AppBar toolbar (before locale/theme toggles). */ extra?: ReactNode; /** Current menu layout mode (VERTICAL or HORIZONTAL). */ menuLayout?: MenuLayout; /** Navigation controller for horizontal menu rendering. */ navigationController?: NavigationController; } /** * Props contract for a custom NavigationDrawer layout slot component. * * Only rendered in VERTICAL menu layout mode. * Receives visibility/collapsed state, navigation controller, and drawer logos. * * @see `DefaultNavigationDrawer` from `@judo/app-shell` for the built-in implementation. */ export interface LayoutNavigationDrawerProps { /** Whether the drawer is mounted/visible (forwarded to MUI Drawer's `open` prop). * Independent from `collapsed`: a visible drawer can be either full-width or narrow. */ isVisible: boolean; /** Whether the drawer is in collapsed (narrow) mode. */ collapsed: boolean; /** Callback to toggle the collapsed state. */ onCollapse: () => void; /** Drawer width in pixels (0 when closed). */ width: number; /** Navigation controller providing menu items and actions. */ navigationController?: NavigationController; /** Custom logo/image shown at the top of the drawer. */ drawerLogo?: ReactNode; /** Smaller logo/icon shown when the drawer is collapsed. Falls back to drawerLogo. */ drawerLogoCollapsed?: ReactNode; } /** * Props contract for a custom Footer layout slot component. * * The built-in footer resolves text from `CustomizationsConfig.footerText`. * A custom footer component can use `useCustomizationsOptional()?.getFooterText()` * to access the same configuration, or ignore it entirely. * * @see `DefaultFooter` from `@judo/app-shell` for the built-in implementation. */ export type LayoutFooterProps = Record; /** * Props contract for a custom Breadcrumbs layout slot component. * * @see `DefaultBreadcrumbs` from `@judo/app-shell` for the built-in implementation. */ export interface LayoutBreadcrumbsProps { /** Optional CSS class name. */ className?: string; } export interface CustomizationsConfig { /** * @internal Page-scoped customization bundles. * Keys are PageDefinition sourceIds (resolved by the factory from names). */ pages?: Record; /** * @internal Custom component implementations (keyed by sourceId). * Resolved by the factory from human-readable component names. * * Highest priority — wins over type interceptors and default rendering. */ components?: Record; /** * Component interceptors by element @type. * Called for every element of the matching type. * Return a component to override rendering, or null for default. * * Priority: sourceId (components) > type interceptor > default rendering. * * @example * ```typescript * { * componentInterceptors: { * TextInput: (element, page) => { * if (element.attributeType?.name?.endsWith('Email')) { * return EmailInputComponent; * } * return null; * }, * Table: (element, page) => MyCustomTableComponent, * } * } * ``` */ componentInterceptors?: ComponentInterceptors; /** * Sub-theme providers keyed by sub-theme name. * * When a VisualElement has `subTheme` set, the runtime looks up the matching * provider here and wraps the element tree in a scoped MUI `ThemeProvider`. * Sub-themes nest with increasing precedence (inner overrides outer). */ subThemeProviders?: Record; /** * Redirect handler component rendered at the `/_redirect` route. * * Renders inside the Router with full access to React hooks including * `useSearchParams()`, `useNavigate()`, `useLocation()`, etc. * * Use cases: OAuth callbacks, deep-linking from emails, external redirects. */ redirectHandler?: ComponentType; /** * Additional custom routes injected into the application router. * * Inserted after model-derived page routes and before the 404 catch-all. * Route elements render inside the Router with full hook access. */ customRoutes?: Array<{ path: string; element: ReactElement; }>; /** * Menu customization function. * * Called with navigation items from the model's `NavigationController.items`. * The returned array replaces the original items — add, remove, reorder freely. * Applied in both vertical and horizontal navigation layouts. */ menuCustomizer?: (items: NavigationItem[]) => NavigationItem[]; /** * Footer text displayed at the bottom of the application shell. * Accepts a static string or a function returning a string. */ footerText?: string | (() => string); /** * Custom hero component rendered in the AppBar for authenticated actors. * * When set, replaces the built-in `DefaultHeroComponent` on the right-hand * side of the top AppBar. The default hero ships out of the box and shows * an actor chip, avatar, and a dropdown with profile info and sign-out. * * Only rendered when the actor requires authentication * (`requiresAuth === true`); anonymous actors never display a hero. * * The component receives the authenticated principal data and the * current actor name so it can render personalised content (avatar, * greeting, role badge, etc.) while still providing access to * sign-out and profile actions. */ heroComponent?: ComponentType; /** * Custom settings page component rendered at the `/settings` route. * * The `/settings` route is always registered — when this property is * not provided, a built-in placeholder page is rendered instead. * The DefaultHeroComponent always shows a "Settings" menu item. * The component is fully owned by the application — the framework * provides only the route and navigation wiring. */ settingsPage?: ComponentType; /** * Custom guest page component rendered when `Application.supportGuestAccess` * is `true` and the user is not authenticated. * * When set, replaces the built-in `DefaultGuestPage`. The component receives * the `Application` model and a `signIn` callback to trigger OIDC login. * * When not set and guest access is enabled, the framework renders * `DefaultGuestPage` which shows the application title and a "Sign In" button. * * When guest access is disabled (`supportGuestAccess` is `false` or absent), * unauthenticated users are redirected to OIDC automatically (existing behavior). */ guestComponent?: ComponentType; /** * Custom AppBar component replacing the built-in application header. * * Receives drawer width, menu click handler, logo, extra content, * and navigation controller for horizontal mode rendering. * The built-in `DefaultAppBar` is exported from `@judo/app-shell` * so you can wrap it with additional behavior. * * @see {@link LayoutAppBarProps} for the full props contract. */ appBarComponent?: ComponentType; /** * Custom NavigationDrawer component replacing the built-in vertical drawer. * * Only rendered in VERTICAL menu layout mode. * Receives visibility/collapsed state, navigation controller, and drawer logos. * The built-in `DefaultNavigationDrawer` is exported from `@judo/app-shell` * so you can wrap it with additional behavior. * * @see {@link LayoutNavigationDrawerProps} for the full props contract. */ navigationDrawerComponent?: ComponentType; /** * Custom Footer component replacing the built-in application footer. * * When set, fully replaces the built-in footer (including `footerText` resolution). * The built-in `DefaultFooter` is exported from `@judo/app-shell` * so you can wrap it with additional behavior. * * @see {@link LayoutFooterProps} for the full props contract. */ footerComponent?: ComponentType; /** * Custom Breadcrumbs component replacing the built-in page stack breadcrumbs. * * The built-in `DefaultBreadcrumbs` is exported from `@judo/app-shell` * so you can wrap it with additional behavior. * * @see {@link LayoutBreadcrumbsProps} for the full props contract. */ breadcrumbsComponent?: ComponentType; /** * Error handler interceptor. * * When set, the runtime checks `shouldInterceptError(error, options?)` before * running its default error handling for a failed action dispatch. When it * returns `true`, `interceptError(error, options?, payload?, dataPath?)` runs * instead of the default toast/validation/fault handling; when it returns * `false`, the default handling runs unchanged. * * @see {@link ErrorHandlerInterceptorHook} */ errorHandlerInterceptor?: ErrorHandlerInterceptorHook; /** * Navigation interceptor for decorating outgoing navigations (e.g. sticky * search params) and running logic once on initial load. * * @see {@link NavigationInterceptorHook} */ navigationInterceptor?: NavigationInterceptorHook; /** * Global keyboard shortcut registration. Either a declarative hotkey list * the runtime binds (with an optional built-in "list of hotkeys" dialog) or a * free-form component/hook mounted once inside the app-shell provider tree. * * @see {@link GlobalHotkeysConfig} */ globalHotkeys?: GlobalHotkeysConfig; } /** * Props passed to a custom guest page component. * * Provided when `Application.supportGuestAccess` is `true` and the user * is not yet authenticated. The component should offer a way to trigger * sign-in (e.g., a "Sign In" button calling `signIn()`). * * @see {@link CustomizationsConfig.guestComponent} */ export interface GuestPageProps { /** The active Application model (for branding: title, logo, etc.). */ application: Application; /** Trigger the OIDC sign-in redirect flow. */ signIn: () => void; } /** * Props passed to a custom hero component in the AppBar. * * @see {@link CustomizationsConfig.heroComponent} */ export interface HeroComponentProps { /** Authenticated principal data (name, email, username, …). */ principal: { email?: string; name?: string; preferredUsername?: string; givenName?: string; familyName?: string; [key: string]: unknown; } | null; /** The current actor / role name from the application model. */ actorName: string; /** Triggers the sign-out / redirect flow. */ signOut: () => void; /** * Navigate to the actor's profile page. * Defined when `Application.profilePage` is set in the model. * When undefined the hero should hide the Profile action. */ onNavigateToProfile?: () => void; /** * Navigate to the settings page. * Always provided — the framework registers a `/settings` route * with either the custom `CustomizationsConfig.settingsPage` or * a built-in placeholder. */ onNavigateToSettings: () => void; /** * URL of the principal's profile picture. * Resolved from the `isProfilePicture` attribute on the principal's * ClassType when the matching value is present in the principal data. * When defined the hero avatar should display this image. */ profilePictureUrl?: string; } //# sourceMappingURL=extensibility.d.ts.map