import { ComponentType, ReactElement } from 'react'; import { ColumnDef as columnDefCore, ColumnDefInput, ColumnRenderer, DetailComponentConstructor, DetailRenderer, GridOptions, MasterDetailConfig, RendererOutput } from 'photon-grid-core'; export interface ReactRendererSpec { kind: 'component'; component: ComponentType>; props?: (params: unknown) => Record; } /** * `masterDetail.renderer` as accepted by the React wrapper: a React component * passed directly, or anything the core already accepts (a `DetailComponent` * class, a `(ctx) => HTMLElement | string` function, a static HTML string). * * ```tsx * renderer: OrderDetail // a React component * renderer: (ctx) => `

${ctx.data.account}

` // core: HTML from a function * renderer: '

Details

' // core: a static HTML string * ``` * * A bare function is treated as a React component only when its name is * capitalised — the same convention {@link isComponentRenderer} uses for * cells, since a lowercase React component is indistinguishable from a core * function renderer. For that case, wrap it explicitly with * {@link createReactDetailRenderer}. */ export type ReactDetailRenderer = ComponentType> | DetailRenderer; /** Core `MasterDetailConfig` with `renderer` widened to the React forms above. */ export type ReactMasterDetailConfig = Omit & { renderer?: ReactDetailRenderer; }; /** * Core `GridOptions` with `masterDetail.renderer` widened to accept React * components. Assignment-compatible with the core type, so an existing * `GridOptions` object passes through unchanged. */ export type PhotonGridOptions = Omit & { masterDetail?: ReactMasterDetailConfig; }; type RendererSlotValue = ((params: unknown) => RendererOutput) | ReactRendererSpec | ComponentType> | ReactElement | undefined; /** * React column definition. Built on the core's {@link ColumnDefInput}, so it * inherits the same relaxed contract: **only `field` is required**; `colId`, * `header` and `type` are optional and defaulted by the core (auto `colId`, * header from the field in Title Case, `type` defaulting to `'string'`). The * `renderer` slots additionally accept React components/specs. */ type columnDef = Omit & { /** * Accepts everything the core does — a built-in renderer by name * (`'country'`), a configured one (`{ name, options }`), a bare display * function — plus the React component/spec forms in the slot map, which the * adapter converts to plain functions. * * Also accepts a React component or {@link ReactRendererSpec} directly * (`renderer: MyCell`), which is shorthand for `renderer: { display: MyCell }`, * or an already-instantiated element (`renderer: `), shorthand for * `renderer: { display: }`. */ renderer?: ColumnRenderer | ReactRendererSpec | ComponentType> | ReactElement | { display?: RendererSlotValue; editor?: RendererSlotValue; option?: RendererSlotValue; filter?: RendererSlotValue; tooltip?: RendererSlotValue; group?: RendererSlotValue; header?: RendererSlotValue; summary?: RendererSlotValue; }; }; export declare class ReactRendererAdapter { private root?; private container?; private readonly entries; private observer?; private pendingFlush; observe(host: HTMLElement): void; adaptColumns(columns: columnDef[]): columnDefCore[]; /** * Converts React-flavoured grid options into what the core accepts. * * Currently that is `masterDetail.renderer`: a React component is wrapped in * a core `DetailComponent` class that owns its own React root and unmounts * it in `destroy()`. * * Unlike cell renderers, detail rows need no portal bookkeeping and no * `MutationObserver`: the core's detail lifecycle hands us explicit * `init`/`refresh`/`destroy` hooks, and there is at most one mounted detail * per expanded row. * * Returns the original object untouched when there is nothing React to * adapt, so the common case allocates nothing. */ adaptOptions(options: Partial): Partial; dispose(): void; private mountComponent; /** * Portal-mounts an already-constructed `ReactNode` — the output of calling * an inline `display`/`editor`/etc. function that returns JSX directly, * as opposed to a component reference invoked via `createElement`. * * There's no `component`/`props` pair to diff against on re-render (the * caller already produced the element), so unlike `mountComponent` this * always re-renders the cached entry on a key hit rather than trying to * skip an unchanged render. */ private mountElement; private flush; private renderFrame; private getComponent; private getRendererProps; private getBaseProps; private propsMatch; private getRendererKey; private unmountNode; private unmountHost; } export type { columnDef as ColumnDef }; /** * Wraps a React component in a core `DetailComponent` class. * * A class (rather than a `(ctx) => HTMLElement` function) because only the * class form gets `destroy()` — a React root that is merely detached from the * DOM is never unmounted, so its effects and subscriptions would outlive the * collapsed row. * * Each expanded row gets its own root mounted into `ctx.containerEl`. `refresh` * re-renders that root in place and returns `true`, so React reconciles rather * than the core tearing the component down and rebuilding it. * * Exported for the one case the bare form cannot express: a lowercase-named * function component, which is indistinguishable from a core function * renderer. `renderer: createReactDetailRenderer(myDetail)` settles it. */ export declare function createReactDetailRenderer(component: ComponentType>): DetailComponentConstructor;