import type { RowNode } from '../types/row.types'; import type { GridApi } from '../core/grid-api'; import type { MasterDetailEngine } from '../engines/master-detail/master-detail-engine'; import type { DetailComponent } from '../types/detail-component.types'; /** Collaborators every {@link DetailComponentHost} needs. Built once by `DetailRowRenderer` and shared by every host it creates. */ export interface DetailComponentHostDeps { /** Owns the `masterDetail` config, detail height state, and the `ROW_DETAIL_EVENT` bus channel. */ readonly engine: MasterDetailEngine; /** Parent grid's public API, handed straight to components as `DetailContext.api`. */ readonly api: GridApi; /** Collapses a master row with the same shrink/fade the toggle chevron plays. Backs `DetailContext.collapse`. */ readonly collapse: (parentNodeId: string) => void; /** * Vertical space (px) a detail row spends on chrome rather than content — * padding on both edges plus the bottom border. Added back to every content * height the consumer supplies or the host measures; see * `DETAIL_ROW_VERTICAL_CHROME_PX` for why an inexact value is not a cosmetic * 1px. */ readonly verticalChrome: number; } /** * Owns one mounted custom detail component: its instance, its live * {@link DetailContext}, its auto-height tracking, and its teardown. * * One host exists per expanded master row and lives exactly as long as that * row's `DetailEntry` — including while the entry sits in `DetailRowRenderer`'s * collapsed LRU cache, which is what makes re-expanding a row restore the * component's own internal state instead of rebuilding it. * * ### Why the context is built from accessors * * `data`, `rowNode` and `props` are getters rather than snapshots. A component * holds its context for its whole lifetime, so a snapshot would go stale the * moment a row transaction rewrote the master row or a refresh re-resolved * props — and the only cure would be re-creating the component, which is * precisely what this feature exists to avoid. Getters keep one stable context * identity permanently current, at no allocation cost. */ export declare class DetailComponentHost { private readonly deps; private readonly autoHeight; private component; private context; /** * Inner content element the component renders into. Deliberately left * without a height of its own (see `.pg-detail-component-host`) so * {@link measureContentHeight} reads the component's natural size instead of * echoing back the row height Photon just assigned. */ private readonly hostEl; private props; /** Live detail `RowNode`. Replaced on every re-expand ({@link setRow}) so lookups never hold a node from a stale pipeline run. */ private detailRow; private resizeObserver; private measureFrame; /** Last content height pushed to the engine — lets a measurement pass bail before touching the height cache. */ private lastContentHeight; /** Tallest content height measured so far. What {@link settleHeight} falls back to, so giving up never clips content. */ private tallestContentHeight; /** Automatic corrections applied since the last explicit refresh. Budgeted by {@link MAX_AUTO_HEIGHT_CORRECTIONS}. */ private autoCorrections; /** Set once a component calls `ctx.updateHeight(px)` with an explicit value, or once auto-height settles: a deliberate size always outranks a measured one, so auto-measurement stands down for the rest of this host's life. */ private heightPinned; private destroyed; /** * @param deps - Shared collaborators, see {@link DetailComponentHostDeps}. * @param detailRow - The `type: 'detail'` node this host renders into. * @param containerEl - The padded `.pg-row--detail-container` element. * @param autoHeight - Whether the detail row's height tracks the component's * natural content height. `false` makes the host fill the fixed row height * instead (`detailAutoHeight: false` / `detailResizable`). */ constructor(deps: DetailComponentHostDeps, detailRow: RowNode, containerEl: HTMLElement, autoHeight: boolean); /** * Builds the context, resolves props, and creates the component. Called once * per host; a repeat call is a no-op, so a retried `sync()` can never * double-mount. */ mount(): void; /** * Points the host at the current pipeline's detail node after a re-expand * from the collapsed cache. The context reads through to `detailRow`, so * this is all a reused component needs to see live `data`/`rowNode` again. */ setRow(detailRow: RowNode): void; /** * Re-resolves props and asks the component to update in place. Falls back to * a destroy/re-create only when the component declines — `refresh` absent or * returning anything but `true` — which is also the only path a function * renderer has, since it exposes no refresh hook. */ refresh(): void; /** The mounted component instance, or `null` for a function renderer or a host that has not mounted yet. Backs `GridApi.getDetailComponent`. */ getComponent(): DetailComponent | null; /** Tears down the component, its observer, and its DOM. Idempotent. */ destroy(): void; /** * The one context object handed to the component, built from getters for * `data`/`rowNode`/`props` — see the class doc for why. */ private buildContext; /** * Creates the content from the configured renderer and mounts whatever it * produced. Shared by {@link mount} and the re-create branch of * {@link refresh}. * * The three forms are told apart here, not by the consumer: a static HTML * string, a `DetailComponent` class, or a function returning an element / * HTML string / nothing. */ private instantiate; private resolveProps; /** * Mounts whatever a renderer returned: an element is appended, an HTML * string is written in, and `undefined` means the renderer already wrote * into `ctx.containerEl` itself. */ private attachRoot; /** * The master row this detail section belongs to, resolved through the row * model on every access so row transactions are picked up without * re-creating the component. Falls back to the detail node's captured parent * and then to the detail node itself, so the context can never hand a * component `undefined` even if the master row was removed mid-render. */ private resolveMasterRow; private parentNodeId; /** * Routes a `ctx.emit` to its `masterDetail.events` handler and then to the * grid's event bus. A throwing handler is contained here: `emit` is normally * reached from inside a component's own DOM listener, and letting it escape * would abort whatever render or gesture is in flight. */ private emit; /** * Applies an explicit or measured **content** height, adding the detail * container's chrome so the component gets exactly the space it asked for. * Clamping to `detailMinHeight`/`detailMaxHeight` and the change detection * that avoids a redundant pipeline run both live in * `MasterDetailEngine.setDetailHeight`. * * An explicit height is honoured to the pixel; only a *measured* one goes * through the {@link HEIGHT_EPSILON_PX} deadband, since only a measurement * can be off by a rounding step it did not mean. */ private updateHeight; /** * Whether a freshly measured content height differs from the applied one by * enough to be worth a pipeline run. See {@link HEIGHT_EPSILON_PX} for why * a one-pixel difference specifically must not qualify. */ private isMeaningfulChange; private applyContentHeight; /** * Natural height of the rendered component, rounded up to a whole pixel. * * `scrollHeight` (not `offsetHeight`) so content overflowing a clamped row * still reports its true size; `Math.ceil` because a fractional measurement * would never compare equal to itself across frames and every inequality * costs a pipeline run. */ private measureContentHeight; /** * Tracks the component's natural height. * * Immune to the `ResizeObserver` feedback loop that observing the detail * *container* would cause: `.pg-detail-component-host` has no height of its * own, so what is measured is the component's content box, which does not * change when the row height around it does. The {@link lastContentHeight} * guard closes the remaining case — a component that does size itself off * the row — by refusing to re-push a height it already pushed. */ private observeContentHeight; /** * Coalesces height measurement into the next frame. Every trigger — the * observer, a mount, a refresh — funnels through here, so a burst of * mutations costs one layout read and at most one pipeline run. * * {@link heightPinned} is re-checked inside the callback, not only at * scheduling time: a component that pins a height *after* a measurement was * already queued must still win, or its deliberate size would be silently * overwritten one frame later. */ private scheduleMeasure; /** * One auto-height correction, spending from the * {@link MAX_AUTO_HEIGHT_CORRECTIONS} budget. * * The budget is the guard that keeps a misbehaving renderer from spinning the * pipeline: content whose height derives from the row it sits in closes the * measure → assign → measure loop, and without a bound it would run one full * pipeline refresh per frame forever. Naturally-sized content converges in a * round or two and never comes close to the limit. */ private measureAndApply; /** * Ends a diverging auto-height loop: pin the tallest height measured so far * (so nothing ends up clipped) and stop measuring. * * Reached only when the content's height depends on the row's, which Photon * cannot resolve for the consumer — hence a warning naming both fixes rather * than a silent clamp. */ private settleHeight; private cancelMeasure; /** Runs the component's own `destroy` hook, containing a throw so teardown of the surrounding entry always completes. */ private teardownComponent; } //# sourceMappingURL=detail-component-host.d.ts.map