import type { GridApi } from '../core/grid-api'; import type { EventBus } from '../event-bus/event-bus'; import type { RowNode } from '../types/row.types'; /** * A feature that installs itself into a grid instance from the outside. * * Plugins exist so large, optional subsystems (a scheduler timeline, a Gantt * layer, a pivot surface) can own DOM inside the grid and follow its * virtualization **without shipping in the core bundle**. Core never imports a * plugin implementation; a host that registers none pays nothing beyond an * `undefined` check. * * Lifecycle, all driven by `GridCore`: * * 1. `init(ctx)` — after the grid's DOM is mounted, columns and data are * loaded, and the `GridApi` is live, but **before** `GridEventType.READY`, * so a host's `onReady` already sees whatever the plugin installed. * 2. `onRenderWindow(w)` — once per rendered frame, if implemented. * 3. `destroy()` — **first** thing in `GridCore.destroy()`, ahead of the event * bus being cleared and the grid element being removed. * * A plugin that throws from `init` is quarantined: it receives no frames and no * `destroy()`, because a half-constructed object is more dangerous to tear down * than to leak. * * @example * ```ts * class RulerPlugin implements GridPlugin { * readonly id = 'ruler'; * private layer!: HTMLElement; * * init(ctx: PluginContext): void { * this.layer = ctx.mountLayer('ruler', { followRowOrigin: true }); * } * * onRenderWindow(w: RenderWindow): void { * // Row tops are rebased — see RenderWindow.rowOriginY. * for (const row of w.rows) paint(row.top - w.rowOriginY); * } * * destroy(): void { this.layer.remove(); } * } * * new GridCore(el, { columns, data, plugins: [new RulerPlugin()] }); * ``` */ export interface GridPlugin { /** Stable identifier, used for layer naming and error reporting. Must be unique per grid. */ readonly id: string; /** Human-readable name for diagnostics. Defaults to {@link id}. */ readonly name?: string; /** Installs the plugin. See the lifecycle note on {@link GridPlugin}. */ init(ctx: PluginContext): void; /** * Releases everything the plugin owns: DOM, listeners, observers, timers. * Not called if {@link init} threw. */ destroy(): void; /** * Called once per rendered frame with the window the grid just computed. * * This is the hot path — it runs inside the grid's own render, synchronously, * so it must not force layout or allocate per row. Equivalent to subscribing * via {@link PluginContext.onRenderWindow}; use that instead when a * sub-component needs its own independent teardown. */ onRenderWindow?(window: RenderWindow): void; } /** * The surface a plugin is given at {@link GridPlugin.init}. * * Deliberately a **curated façade rather than the internal `GridContext`**: * that context holds ~40 concrete engine classes, most of which are not part of * the public API, and handing it out would freeze every internal against * refactoring. Anything genuinely missing belongs on {@link GridApi} — the * surface that already carries a compatibility contract — not here. */ export interface PluginContext { /** The grid's public API. The escape hatch for anything not on this façade. */ readonly api: GridApi; /** * The grid's event bus. * * Note `GridEventType` is a closed union, so a plugin cannot add its own * members. Use {@link emit} to publish plugin-specific events instead. */ readonly eventBus: EventBus; /** The element the grid was constructed into. Plugin chrome outside the body goes here. */ readonly containerEl: HTMLElement; /** * This grid's `data-photon-grid-id`. Scope any injected `