import { RenderWork } from '../render-work.js'; import { DrawingBlock, DrawingMeasure, Fragment, Line, ParagraphBlock, SdtMetadata, TableBlock, TableFragment, TableMeasure } from '../../../../contracts/src/index.js'; import { ResolvePhysicalFamily } from '../../../../../shared/font-system/src/index.js'; import { FragmentRenderContext } from '../renderer.js'; import { TableRowRole } from './row-role.js'; import { SdtAncestorOptions, SdtBoundaryOptions } from '../sdt/container.js'; type ApplyStylesFn = (el: HTMLElement, styles: Partial) => void; /** * Dependencies required for rendering a table fragment. * * Encapsulates all external dependencies needed to render a table, including * document access, rendering context, pre-resolved table data, and helper functions. */ export type TableRenderDependencies = { /** Document object for creating DOM elements */ doc: Document; /** Table fragment to render (contains dimensions and row range) */ fragment: TableFragment; /** Rendering context (section info, etc.) */ context: FragmentRenderContext; /** Pre-extracted TableBlock (replaces blockLookup.get()) */ block: TableBlock; /** Pre-extracted TableMeasure (replaces blockLookup.get()) */ measure: TableMeasure; /** Pre-computed cell spacing in pixels */ cellSpacingPx: number; /** Pre-computed effective column widths (fragment.columnWidths ?? measure.columnWidths) */ effectiveColumnWidths: number[]; /** Optional SDT boundary overrides for container styling */ sdtBoundary?: SdtBoundaryOptions; /** Ancestor SDT key used to suppress duplicate container chrome in nested tables */ ancestorContainerKey?: string | null; /** Ancestor SDT metadata used to suppress duplicate id-less container chrome in nested tables */ ancestorContainerSdt?: SdtMetadata | null; /** Ancestor SDT keys used to suppress duplicate container chrome in nested tables */ ancestorContainerKeys?: SdtAncestorOptions['ancestorContainerKeys']; /** Ancestor SDT metadata chain used to suppress duplicate id-less container chrome in nested tables */ ancestorContainerSdts?: SdtAncestorOptions['ancestorContainerSdts']; /** Receives notification when this table fragment or descendants render SDT container chrome */ onSdtContainerChrome?: () => void; /** Built-in SDT chrome rendering mode. */ chrome?: 'default' | 'none'; /** Function to render a line of paragraph content */ renderLine: (block: ParagraphBlock, line: Line, context: FragmentRenderContext, lineIndex: number, isLastLine: boolean, resolvedListTextStartPx?: number) => HTMLElement | RenderWork; /** Optional callback invoked after a table line's final styles/markers are applied. */ captureLineSnapshot?: (lineEl: HTMLElement, context: FragmentRenderContext, options?: { inTableParagraph?: boolean; wrapperEl?: HTMLElement; }) => void; /** Function to render drawing content (images, shapes, shape groups) */ renderDrawingContent?: (block: DrawingBlock, interactionHost: HTMLElement, measure: DrawingMeasure) => HTMLElement; /** Function to apply fragment positioning and dimensions */ applyFragmentFrame: (el: HTMLElement, fragment: Fragment) => void; /** Function to apply SDT metadata as data attributes */ applySdtDataset: (el: HTMLElement | null, metadata?: SdtMetadata | null) => void; /** Function to apply container SDT metadata as data attributes */ applyContainerSdtDataset?: (el: HTMLElement | null, metadata?: SdtMetadata | null) => void; /** Function to apply CSS styles to an element */ applyStyles: ApplyStylesFn; /** * Per-document logical->physical font resolver for in-cell list markers and drop caps. Threaded * from the renderer's per-document resolver so they paint the same physical family they were * measured in. Undefined falls back to the global resolver. */ resolvePhysical?: ResolvePhysicalFamily; }; /** * Renders a table fragment as a DOM element. * * Creates a container div with absolutely-positioned rows and cells. Handles: * - Table border overlays for outer borders * - Border collapse settings * - Cell spacing * - Row-by-row rendering with proper positioning * - Metadata embedding for interactive table resizing * * **Error Handling:** * If the document is unavailable, returns an error placeholder instead of * throwing. Table block/measure validation is performed by the caller before * invoking this helper. * * **SDT Container Styling:** * If the table block has SDT metadata (`block.attrs?.sdt`), applies appropriate * container styling via `applySdtContainerChrome()`: * - Document sections: Gray border with hover tooltip * - Structured content blocks: Blue border with label * Uses type-safe helper functions to avoid unsafe type assertions. * * **Metadata Embedding:** * Embeds column boundary metadata in the `data-table-boundaries` attribute * using a compact JSON format: * ```json * { * "columns": [ * {"i": 0, "x": 0, "w": 100, "min": 25, "r": 1}, * {"i": 1, "x": 100, "w": 150, "min": 30, "r": 1} * ], * "rows": [ * {"i": 0, "y": 0, "h": 30, "min": 10, "r": 1}, * {"i": 1, "y": 34, "h": 25, "min": 10, "r": 1} * ] * } * ``` * Where for columns: i=index, x=position, w=width, min=minWidth, r=resizable(0/1) * Where for rows: i=index, y=position, h=height, min=minHeight, r=resizable(0/1) * * **Edge Cases:** * - Missing metadata: Element created without data-table-boundaries attribute * - Empty columnBoundaries: Creates empty columns array in JSON * - Missing block ID: Element created without data-sd-block-id attribute * * @param deps - All dependencies required for rendering * @returns HTMLElement containing the rendered table fragment, or error placeholder * * @example * ```typescript * const tableElement = renderTableFragment({ * doc: document, * fragment: tableFragment, * context: renderContext, * block: tableBlock, * measure: tableMeasure, * cellSpacingPx: 0, * effectiveColumnWidths: tableMeasure.columnWidths, * renderLine, * applyFragmentFrame, * applySdtDataset, * applyStyles * }); * container.appendChild(tableElement); * ``` */ export declare function renderTableFragmentWork(deps: TableRenderDependencies): RenderWork; export declare const renderTableFragment: (deps: TableRenderDependencies) => HTMLElement; /** One painted cell: the cell element plus any border overlays appended after it. */ export type TablePaintCellGroup = { rowIndex: number; cellIndex: number; role: TableRowRole; elements: Element[]; }; export type TablePaintLedger = { block: TableBlock; measure: TableMeasure; fragment: TableFragment; context: FragmentRenderContext; effectiveColumnWidths: readonly number[]; cellSpacingPx: number; chrome: 'default' | 'none' | undefined; groups: TablePaintCellGroup[]; /** Rowspan continuation ("ghost") cells painted outside the cell groups. */ ghostCells: number; /** The table root or a descendant rendered SDT container chrome. */ sdtChrome: boolean; }; export declare function readTablePaintLedger(container: HTMLElement): TablePaintLedger | undefined; /** * Resolves the immutable source identity of a painted cell. The default is the * cell object itself; a host whose paint blocks are pm-rebased clones supplies * the clone's source owner so unchanged cells keep one identity across edits. */ export type TableCellSourceIdentity = (block: TableBlock, rowIndex: number, cellIndex: number) => object | null; export type TableFragmentPatchInput = TableRenderDependencies & { /** The fragment element painted by an earlier `renderTableFragmentWork`. */ container: HTMLElement; cellSourceIdentity?: TableCellSourceIdentity; }; export type TableFragmentPatchPlan = { input: TableFragmentPatchInput; previous: TablePaintLedger; /** Rendered rows in paint order with the cells whose DOM must be rebuilt. */ rows: Array<{ rowIndex: number; role: TableRowRole; y: number; changed: Set; }>; /** Unchanged cell groups and the pm delta their position attributes move by. */ reused: Array<{ group: TablePaintCellGroup; pmDelta: number; }>; cellsChanged: number; }; export type TableFragmentPatchResult = { cellsRendered: number; cellsReused: number; }; /** * Decide whether `container` can be brought to `input`'s table without a * rebuild. Every unproven condition yields null; the plan names exactly the * cells to repaint. Pure: it mutates nothing. */ export declare function planTableFragmentPatch(input: TableFragmentPatchInput): TableFragmentPatchPlan | null; /** Apply a plan from {@link planTableFragmentPatch}; the container stays the fragment element. */ export declare function applyTableFragmentPatchWork(plan: TableFragmentPatchPlan): RenderWork; export {};