import { $n as CellEditor, B as FilterRuntime, Gn as QuerySupport, Hn as QueryCondition, L as FilterDef, Mn as RowHeight, Nn as RowStyle, Q as FilterTypeSpec, Qn as CellEditTarget, R as FilterOption, S as TableQueryParams, Un as QueryExtensions, Vn as QueryAggregate, Wn as QueryFilterGroup, Z as FilterTypeRegistry, Zn as CellEditCommit, _ as RowAction, a as ColorScheme, b as SortableValue, bn as ExtraRow, cn as GroupAggregatesFn, ct as ActiveFilterChip, d as Direction, dn as GroupSort, f as ExtraFilters, fn as GroupedFlatEntry, h as PaginationMode, hr as normalizeEditorOptions, ln as GroupNode, lt as ChipLabelResolver, m as PaginatedResponse, mt as FilterFormSource, n as BulkAction, o as ColumnDef, p as FilterValue, qn as SortLevel, r as BulkActionContext, rn as TableSource, rr as EditableColumnLike, u as ColumnHeaderController, un as GroupPaging, v as SortByOption, w as FacetMap, x as TableLabels, y as SortDirection, zn as TableStateMutators } from "./types-Cqk1_BXq.js"; import { A as UseColumnLayoutResult, C as ColumnLayoutState, D as PinSide, F as ColumnGroupRecord, I as ColumnInput, a as ExportViewEntry, f as CellSpanAppearance, m as GetCellSpan, s as ExportWriter, t as ExportPayload } from "./exportWriter-VdWUamzf.js"; import { i as AggregateSpec, r as AggregateOptions } from "./aggregate-C1nNljQr.js"; import { t as UrlStateAdapter } from "./adapter-BD3RX3cl.js"; import { CSSProperties, DragEvent, KeyboardEvent, MouseEvent, PointerEvent, ReactElement, ReactNode, RefCallback, RefObject } from "react"; import { VirtualItem } from "@tanstack/react-virtual"; //#region src/actions/contextMenuModel.d.ts /** Where the menu was opened. */ type ContextMenuTarget = { kind: "header"; columnKey: string; } | { kind: "row"; row: TRow; rowId: string; } | { kind: "cell"; row: TRow; rowId: string; columnKey: string; }; /** One entry in a context menu. */ interface ContextMenuItem { /** Stable identity, and the React key. */ key: string; /** The caption, already localized. */ label: string; /** Greyed and unselectable, but still announced — the menu keeps its shape. */ disabled?: boolean; /** Destructive, so a kit can colour it as such. */ danger?: boolean; /** Draw a divider above this entry. */ separatorBefore?: boolean; /** What selecting it does. The menu closes first, then this runs. */ onSelect: () => void; } /** The handlers a built-in entry needs, each optional. */ interface ContextMenuActions { /** Copy the selection, or this cell when nothing is selected. */ onCopy?: (target: ContextMenuTarget) => void; /** Cut — present only when the host wired `onCellCut`. */ onCut?: (target: ContextMenuTarget) => void; /** Sort by a column. */ onSort?: (columnKey: string, direction: "asc" | "desc") => void; /** Toggle a column's pin. */ onTogglePin?: (columnKey: string) => void; /** Hide a column. */ onHide?: (columnKey: string) => void; /** Open the filter UI on a column. */ onFilter?: (columnKey: string) => void; } //#endregion //#region src/actions/commandRegistry.d.ts /** A command, which is exactly a menu entry. */ type Command = ContextMenuItem; /** * The commands matching a query, in the order they were registered. * * Registration order is deliberate: it is the order the host chose, which * is more meaningful than alphabetical and does not move under the user as * they type. An empty query lists everything. * * @param commands - Every command available right now. * @param query - What the user has typed. * @returns The matches. */ declare function filterCommands(commands: readonly Command[], query: string): Command[]; /** What {@link tableCommands} needs to build the table-wide actions. */ interface TableCommandOptions { /** Labels for the built-in commands. */ labels: { print?: string; exportCsv?: string; clearAll?: string; }; /** Open the print dialog on the current view. */ onPrint?: () => void; /** Run the export the toolbar button runs. */ onExport?: () => void; /** Clear every active filter. */ onClearFilters?: () => void; /** Whether there is anything to clear, so the entry can say so. */ hasFilters?: boolean; } /** * The commands that belong to the table rather than to a target. * * Each appears only when its handler is wired, on the same rule the * context menus follow: an action the host has not connected is not * offered, because a palette entry that does nothing is worse than one * that is missing. * * @param options - The handlers and their labels. * @returns The table-wide commands, in display order. */ declare function tableCommands(options: TableCommandOptions): Command[]; //#endregion //#region src/actions/confirm.d.ts /** A confirmation request raised by a row or bulk action. */ interface ConfirmRequest { /** Dialog title. */ title: string; /** Dialog message. */ message: string; /** Confirm button label. */ confirmLabel: string; /** Cancel button label. */ cancelLabel: string; /** Marks the action destructive. */ danger?: boolean; /** Runs when the user accepts. */ onConfirm: () => void; } /** Shows a confirmation, then runs `onConfirm` if accepted. */ type ConfirmHandler = (request: ConfirmRequest) => void; /** * The default confirmation handler — a dependency-free `window.confirm`. * Adapters pass a styled handler when they have one. * * When no dialog exists at all (SSR, jsdom, some embedded webviews) the * action is DENIED: an environment that cannot ask must never approve a * destructive action on the user's behalf. Integrators in dialogless * environments pass their own `confirm` handler. */ declare const defaultConfirm: ConfirmHandler; /** * Normalize a `disabledReason` result. Per the action contract only a * *non-empty* string disables, so an empty string maps to `undefined` — * letting every adapter treat "disabled" as simply "reason is defined" and * fall back to the action label for tooltips. (A plain `|| undefined` would * trip `prefer-nullish-coalescing`; this keeps the falsy-empty intent.) */ declare function resolveDisabledReason(reason: string | undefined): string | undefined; /** * Run a row action, routing through `confirm` first when the action * declares a `confirm` block. * * @typeParam TRow - The row type. * @param action - The action to run. * @param row - The row it was triggered on. * @param confirm - The confirmation handler. * @param cancelLabel - Cancel label for the dialog. */ declare function runRowAction(action: RowAction, row: TRow, confirm: ConfirmHandler, cancelLabel: string): void; //#endregion //#region src/actions/useContextMenu.d.ts /** Where on screen the menu should appear. */ interface ContextMenuPoint { x: number; y: number; } //#endregion //#region src/actions/useBulkActionRunner.d.ts /** A bulk-action rejection as display text, or `null` when there is none. */ declare function bulkActionErrorMessage(error: unknown): string | null; /** How a bulk-action run ended — passed to `onComplete` on every run. */ type BulkActionOutcome = { status: "success"; } | { status: "error"; error: unknown; }; /** Options for {@link useBulkActionRunner}. */ interface UseBulkActionRunnerOptions { /** Confirmation handler for actions that declare a `confirm` block. */ confirm: ConfirmHandler; /** Cancel label for confirm dialogs. */ cancelLabel: string; /** * Called after EVERY run with its outcome — success or failure — so a * host can clear the selection on success and report failures. (Earlier * versions only called this on success, with no argument.) */ onComplete?: (outcome: BulkActionOutcome) => void; } /** The runner returned by {@link useBulkActionRunner}. */ interface BulkActionRunner { /** Key of the action currently running, or `null`. */ pending: string | null; /** * The value the last run rejected with, or `null`. Cleared when the * next run starts. */ error: unknown; /** * Run a bulk action against the given ids (confirming first if needed). * Omit `context` for the plain page-selection scope. */ run: (action: BulkAction, ids: string[], context?: BulkActionContext) => void; } /** * Headless runner for bulk actions: tracks the in-flight action key, * routes through the confirmation handler, catches rejections (exposed as * `error`, never an unhandled rejection), and calls `onComplete` with the * outcome of every run. Adapters render the buttons and call `run`. * * @param options - See {@link UseBulkActionRunnerOptions}. * @returns The {@link BulkActionRunner}. */ declare function useBulkActionRunner({ confirm, cancelLabel, onComplete }: UseBulkActionRunnerOptions): BulkActionRunner; //#endregion //#region src/selection/useSelection.d.ts /** Tri-state of the "select all visible" header control. */ type HeaderSelectionState = "all" | "some" | "none"; /** Options for {@link useSelection}. */ interface UseSelectionOptions { /** The currently visible rows. */ rows: readonly TRow[]; /** Stable id extractor for a row. */ getId: (row: TRow) => string; /** * When this value changes, the selection is cleared (the previously * selected ids may no longer be visible). Compose it from search / * page / active-filter count — e.g. `` `${search}|${page}` ``. */ resetKey?: unknown; /** * Controlled selection. When provided, the hook reads from this value * and reports every change request through `onSelectionChange` instead * of mutating its own state — the same controlled/uncontrolled split as * `useColumnLayout`. */ selectedIds?: readonly string[]; /** Change handler; required for the controlled mode to update. */ onSelectionChange?: (selectedIds: string[]) => void; } /** Selection state + actions returned by {@link useSelection}. */ interface SelectionState { /** The set of selected ids. */ selectedIds: ReadonlySet; /** Number of selected ids. */ selectedCount: number; /** Tri-state for the visible rows (`all` / `some` / `none`). */ headerState: HeaderSelectionState; /** Whether a specific id is selected. */ isSelected: (id: string) => boolean; /** Toggle a single id. */ toggle: (id: string) => void; /** * Toggle all leaf ids in a group (select missing, or deselect when all * selected) — one commit so memoized rows see a single selection change. */ toggleGroupLeaves: (leafIds: readonly string[]) => void; /** Toggle every visible id (select all, or clear all if already full). */ toggleAll: () => void; /** Clear the entire selection. */ clear: () => void; /** The visible ids, in row order. */ visibleIds: string[]; /** True when the user chose "select all matching" across every page. */ allMatching: boolean; /** Extend the selection to every matching row (across all pages). */ selectAllMatching: () => void; } /** * Headless multi-row selection. Tracks a set of ids, derives the header * tri-state from the visible rows, and clears itself when `resetKey` * changes so stale ids never linger after a filter/page change. * * @typeParam TRow - The row type. * @param options - See {@link UseSelectionOptions}. * @returns Selection state and actions. */ declare function useSelection(options: UseSelectionOptions): SelectionState; //#endregion //#region src/actions/useShortcuts.d.ts /** One shortcut: the chord, and what it runs. */ interface Shortcut { /** * The chord, as `"mod+k"`. `mod` is Cmd on a Mac and Ctrl elsewhere, * which is the only way to write one shortcut that is right on both. * Also accepts `ctrl`, `meta`, `alt` and `shift`. */ chord: string; /** The key of the command it runs. */ command: string; } /** The shortcuts a table has unless the host says otherwise. */ declare const DEFAULT_SHORTCUTS: readonly Shortcut[]; /** What {@link useShortcuts} needs. */ interface UseShortcutsOptions { /** Off unless the host armed it; nothing is bound when false. */ enabled: boolean; /** The shortcuts. Defaults to {@link DEFAULT_SHORTCUTS}. */ shortcuts?: readonly Shortcut[]; /** Run a command by key. Returning nothing is fine. */ onCommand: (command: string) => void; /** * Where to listen. Defaults to the document, which is what a * table-scoped palette wants: the shortcut has to work when focus is on * the table, in its toolbar, or nowhere in particular. */ target?: () => EventTarget | null; } /** * Bind a table's shortcuts. * * @param options - The shortcuts and what to do when one fires. */ declare function useShortcuts(options: UseShortcutsOptions): void; //#endregion //#region src/actions/useCommandPalette.d.ts /** How a host arms the palette. */ interface CommandPaletteOptions { /** * Extra commands, appended after the built-in ones. They are the same * objects the context menus take, so an action can be written once and * offered in both. */ commands?: readonly Command[]; /** * The shortcuts. Defaults to Cmd/Ctrl+K opening the palette; pass your * own to remap, or `[]` to bind nothing. */ shortcuts?: readonly Shortcut[]; } /** What {@link useCommandPalette} needs. */ interface UseCommandPaletteOptions extends TableCommandOptions { /** The prop as the host wrote it: `true`, an options object, or absent. */ commandPalette?: boolean | CommandPaletteOptions; labels: TableLabels; } /** What an adapter binds and renders. */ interface TableCommandPalette { /** Whether it is showing. */ open: boolean; /** Close it. */ close: () => void; /** Open it — for a toolbar button or a host control. */ show: () => void; /** Everything it lists. */ commands: readonly Command[]; } /** * Arm a table's command palette. * * @param options - The prop, the labels, and the handlers behind the * built-in commands. * @returns The open state and the commands. */ declare function useCommandPalette(options: UseCommandPaletteOptions): TableCommandPalette; //#endregion //#region src/actions/useTableContextMenu.d.ts /** How a host arms the context menu. */ interface ContextMenuOptions { /** * Extra entries, appended behind a divider so a custom action is never * mistaken for a built-in one. */ items?: (target: ContextMenuTarget) => readonly ContextMenuItem[]; } /** What {@link useTableContextMenu} needs. */ interface TableContextMenuOptions { /** The prop as the host wrote it: `true`, an options object, or absent. */ contextMenu?: boolean | ContextMenuOptions; columns: readonly ColumnDef[]; labels: TableLabels; /** The row behind an id, since the DOM only carries the id. */ rowFor: (rowId: string) => TRow | undefined; /** The handlers the built-in entries call. */ actions: ContextMenuActions; sortBy?: string; sortDir?: "asc" | "desc"; isPinned?: (columnKey: string) => boolean; } /** What an adapter binds and renders. */ interface TableContextMenu { /** Spread onto the element containing the headers, rows and cells. */ regionProps: Record; /** The entries for whatever is open; empty when nothing is. */ items: readonly ContextMenuItem[]; /** Where it was opened, or `null` when it is closed. */ at: ContextMenuPoint | null; /** Close it, putting focus back where it came from. */ close: () => void; } /** * Arm a table's context menu. * * @param options - The prop, the columns, and the handlers behind the * built-in entries. * @returns The props to bind and the state to render. */ declare function useTableContextMenu(options: TableContextMenuOptions): TableContextMenu; //#endregion //#region src/columns/columnMenuModel.d.ts /** Readable label for a column in the menu (header string → mobileLabel → key). */ declare function columnMenuLabel(column: ColumnDef): string; /** Edge a column is pinned to, or `undefined` when unpinned. */ type PinnedSide = PinSide | undefined; /** One row of the column-management menu, with its derived display state. */ interface ColumnMenuRow { column: ColumnDef; key: string; name: string; /** Hidden columns keep their position; only the eye toggles. */ hidden: boolean; /** Edge the column is pinned to, or `undefined` when unpinned. */ pinned: PinnedSide; /** Index in the full column order (visible + hidden) — the reorder target. */ index: number; /** False when `column.lockPosition` is set. */ canMove: boolean; /** False when `column.lockVisibility` is set. */ canHide: boolean; /** False when `column.lockPin` is set. */ canPin: boolean; /** False when `column.lockWidth` is set. */ canResize: boolean; /** True when the column declared `sortable`. */ canSort: boolean; /** True when the column declared a `filter`. */ canFilter: boolean; } /** * Toggle a DATA column's start pin: none ↔ start (`"start"` = the logical * inline-start edge, which is the right edge under `dir="rtl"`). Data columns * never pin to the END edge — that is reserved for the trailing actions column, * which has its own end-pin toggle. Pinning a leading data column to the * trailing edge has no value: it just sticky-travels across the row and * collides with the actions column. */ declare function nextPinSide(current: PinnedSide): PinnedSide; /** * The label for a data column's pin toggle — "Pin to start" when unpinned, * "Unpin" when pinned — so the accessible name always matches what the click * will do. (The actions column uses its own "Pin to end" / "Unpin" label.) */ declare function pinActionLabel(current: PinnedSide, labels: { pinStart: string; unpin: string; }): string; /** * Build the column-menu rows in the table's real order — visible and hidden * columns interleaved exactly as they appear (hiding never reorders the list). * Shared so all five adapters render an identical model and only differ in kit * markup. */ /** * Reserved layout key for the injected row-actions column. It is not a * `ColumnDef`, but the layout state treats keys opaquely, so the actions * column hides (`hidden: ["actions"]`) and end-pins * (`pinned: { actions: "end" }`) like any data column — adapters list it * in the Columns menu with a visibility toggle and an end-pin toggle (no * reorder/resize; it always trails). */ declare const ACTIONS_COLUMN_KEY = "actions"; /** * Reserved layout key for the injected row-reorder column. Same deal as * {@link ACTIONS_COLUMN_KEY}: not a `ColumnDef`, but hideable and * start-pinnable through the layout because the key is just a string. */ declare const REORDER_COLUMN_KEY = "reorder"; declare function columnMenuRows(allColumns: readonly ColumnDef[], layout: UseColumnLayoutResult): ColumnMenuRow[]; /** Keep rows whose name or key contains the query (case-insensitive). */ declare function filterColumnMenuRows(rows: readonly ColumnMenuRow[], query: string): ColumnMenuRow[]; /** Show every unlocked hidden column. */ declare function showAllColumns(rows: readonly ColumnMenuRow[], layout: UseColumnLayoutResult): void; /** Hide every unlocked visible column. */ declare function hideAllColumns(rows: readonly ColumnMenuRow[], layout: UseColumnLayoutResult): void; /** Unpin every unlocked pinned column. */ declare function unpinAllColumns(rows: readonly ColumnMenuRow[], layout: UseColumnLayoutResult): void; /** Restore one column's visibility, pin and width. Locks still apply. */ declare function resetColumnLayout(row: ColumnMenuRow, layout: UseColumnLayoutResult): void; /** One action in a per-column submenu. */ interface ColumnMenuAction { id: string; label: string; disabled: boolean; run: () => void; } /** What a submenu needs besides the row itself. */ interface ColumnMenuActionContext { labels: ColumnMenuLabels; layout: UseColumnLayoutResult; sortBy?: string; sortDir?: "asc" | "desc"; onSortColumn?: (key: string, dir: "asc" | "desc") => void; onAutoSizeColumn?: (key: string) => void; onFilterColumn?: (key: string) => void; } /** Sort, pin, hide, autosize, filter, reset — disabled when locked. */ declare function columnMenuActions(row: ColumnMenuRow, ctx: ColumnMenuActionContext): ColumnMenuAction[]; /** * Labels every adapter's column menu needs (pre-translated by the caller). * Hoisted here so the five adapters share one contract instead of * re-declaring it. */ interface ColumnMenuLabels { columns: string; pinStart: string; pinEnd: string; unpin: string; moveStart: string; moveEnd: string; resetColumns: string; /** "Size columns to content" — the menu's auto-size action. */ autoSizeColumns: string; showColumn: string; hideColumn: string; searchColumns: string; showAllColumns: string; hideAllColumns: string; unpinAllColumns: string; resetColumn: string; sortAscending: string; sortDescending: string; filterColumn: string; columnActions: string; autoSizeColumn: string; } /** The shared prop surface of every adapter's ``. */ interface ColumnMenuChromeProps { /** All declared columns (pre layout filtering). */ allColumns: ColumnDef[]; /** The user column-layout state + mutators. */ layout: UseColumnLayoutResult; /** Resolved labels. */ labels: ColumnMenuLabels; } //#endregion //#region src/columns/columnReorder.d.ts /** MIME type carrying the dragged column key during a reorder drag. */ declare const COLUMN_DND_MIME = "application/x-adapttable-column"; /** Props that make a whole menu ROW draggable (so the browser's drag image is * the full row — you see the column move). Pair with {@link columnDropProps}. */ interface ColumnRowDragProps { draggable: true; onDragStart: (event: DragEvent) => void; } /** * Build drag props for a column-menu row. The entire row is the drag handle, * matching the native drag-image so the reorder feels physical. * * @param key - Column key being reordered. */ declare function columnRowDragProps(key: string): ColumnRowDragProps; /** Props for a small, focusable reorder grip — keyboard a11y for the row drag. */ interface ColumnReorderKeyProps { role: "button"; tabIndex: 0; "aria-label": string; "data-adapttable-grip": ""; onKeyDown: (event: KeyboardEvent) => void; } /** * Build keyboard props for the reorder grip. Arrow keys move the column one * slot — the accessible equivalent of the pointer drag. Up/Down always mean * earlier/later in the order; Left/Right follow the writing direction, so in * RTL pressing ArrowRight moves the column toward the start (visually right). * * @param key - Column key being reordered. * @param index - The column's current index in the full order. * @param move - Layout mutator that moves a column to a new index. * @param label - Accessible label for the grip. */ declare function columnReorderKeyProps(key: string, index: number, move: (key: string, toIndex: number) => void, label: string): ColumnReorderKeyProps; /** Props for a row that accepts a dropped column, moving it to this index. */ interface ColumnDropProps { onDragOver: (event: DragEvent) => void; onDrop: (event: DragEvent) => void; } /** * Build drop props for a reorder target: moves the dragged column to this * row's `index` on drop. * * @param index - Target index the dragged column moves to. * @param move - Layout mutator that moves a column to a new index. */ declare function columnDropProps(index: number, move: (key: string, toIndex: number) => void): ColumnDropProps; /** Indicator attributes for a column-menu row during a reorder drag. */ interface ColumnDragRowAttrs { /** Present on the row being dragged (kits dim it). */ "data-dragging"?: ""; /** Present on the hovered drop target, with the insertion edge. */ "data-drop"?: "before" | "after"; } /** Live drag state + composed prop builders from {@link useColumnDragState}. */ interface ColumnDragState { /** Key currently being dragged, or `null` outside a drag. */ draggingKey: string | null; /** Hovered drop index, or `null`. */ overIndex: number | null; /** Drag props for a row — {@link columnRowDragProps} + state tracking. */ rowDragProps: (key: string, index: number) => ColumnRowDragProps & { onDragEnd: () => void; }; /** Drop props for a row — {@link columnDropProps} + hover tracking. */ dropProps: (index: number, move: (key: string, toIndex: number) => void) => ColumnDropProps; /** Indicator data-attributes for a row; style them with kit CSS. */ rowAttrs: (key: string, index: number) => ColumnDragRowAttrs; } /** * Drop-position feedback for the column-menu reorder. Composes the existing * drag/drop prop builders with the tracking they lack, so adapters can show * WHERE the dragged column will land instead of leaving the user to guess: * the dragged row carries `data-dragging` (dim it) and the hovered target * carries `data-drop="before" | "after"` (draw an insertion line on that * edge). State clears on drop, drag end, and drag cancel alike. */ declare function useColumnDragState(): ColumnDragState; //#endregion //#region src/columns/columnResize.d.ts /** * Props for a column-resize handle element. Modeled as a `button` (a focusable * `separator`/splitter would require `aria-valuenow/min/max`); ArrowLeft/Right * resize it for keyboard users. */ interface ColumnResizeHandleProps { role: "button"; tabIndex: 0; "aria-label": string; onPointerDown: (event: PointerEvent) => void; onKeyDown: (event: KeyboardEvent) => void; /** Double-click sizes the column to its content, as every grid does. */ onDoubleClick: (event: MouseEvent) => void; } /** * Build the props for a column-resize handle. Pointer drag resizes live; arrow * keys nudge by {@link COLUMN_RESIZE_STEP} for keyboard a11y. Width is measured * from the live cell, so columns need no preset width to be resizable. * * @param key - Column key being resized. * @param setWidth - Layout mutator that persists the new width. * @param label - Accessible label for the handle. */ declare function columnResizeHandleProps(key: string, setWidth: (key: string, width: number) => void, label: string): ColumnResizeHandleProps; //#endregion //#region src/columns/columnWidths.d.ts /** A column identity for width resolution — just its key and declared width. */ type WidthColumn = Pick, "key" | "width">; /** Fallback width (px) for a pinned column with no resolvable declared width. */ /** * Parse a declared column width to pixels, or `undefined` when it carries no * pixel value (relative units like `%`, `rem`, `fr` have no px here, so * `parseInt("50%")` → 50 would silently corrupt a sticky inset / min-width). */ declare function parsePxWidth(width: number | string | undefined): number | undefined; /** * A column's effective pixel width: a resize override (from the layout * `widths` map) wins over the declared width. `undefined` when neither * resolves to pixels. */ declare function resolveColumnWidth(column: WidthColumn, widths?: Readonly>): number | undefined; /** * Total pixel width of the fixed-width columns, plus any extra leading/trailing * fixed columns (selection checkbox, actions). Columns without a px width * contribute nothing, so a table with no declared widths returns `0` and is * never forced to a min-width. Adapters apply the result as the table's * `min-width` so a fixed-width table scrolls horizontally instead of squishing * its columns below their declared sizes. * * @typeParam TRow - The row type. * @param columns - The visible columns. * @param options - `widths` resize overrides; `extra` px for non-data columns. * @returns The min table width in px, or `0` when no column declares a width. */ declare function tableMinWidth(columns: readonly ColumnDef[], options?: { widths?: Readonly>; extra?: number; }): number; /** * The pixel width to RENDER a pinned column at: its resolved width, else * {@link FALLBACK_PIN_WIDTH}. Pin insets are summed from these same numbers * (see `pinOffset`), so applying this width to pinned header cells keeps * stacked pins flush — a natural-width pinned column would otherwise render * narrower or wider than the inset math assumed. * * @param column - The pinned column. * @param widths - Resize overrides from the column layout. */ declare function pinnedColumnWidth(column: WidthColumn, widths?: Readonly>): number; //#endregion //#region src/constants.d.ts /** Default rows-per-page. */ declare const DEFAULT_LIMIT = 25; /** Page-size options offered by adapter pagination controls. */ declare const PAGE_SIZE_OPTIONS: readonly [10, 25, 50, 100]; /** * Page-size options to render in a rows-per-page selector, guaranteeing every * given size is present. Pass the active `limit` alone, or `[limit, * defaultLimit]` so a host default (scale's 500, a shared-URL 15) stays * listed after the user picks 10 / 25 / 50 / 100 — otherwise it vanishes * and cannot be selected again. * * Off-list values are prepended in the order given, de-duplicated. * * @param limit - The currently-active page size, or several sizes that must * stay listed (active + the table's default page size). * @param sizes - The standard options to offer (defaults to {@link PAGE_SIZE_OPTIONS}). * @returns The options to render, with every given size guaranteed present. */ declare function pageSizeOptions(limit: number | readonly number[], sizes?: readonly number[]): readonly number[]; /** Default debounce (ms) for the search input before it commits to state. */ declare const SEARCH_DEBOUNCE_MS = 300; /** Default card-height estimate (px) for virtualized mobile layouts. */ declare const DEFAULT_CARD_SIZE_PX = 132; //#endregion //#region src/editing/editingEvents.d.ts /** Which commit unit produced an event. */ type EditUnit = "cell" | "row" | "batch"; /** * One lifecycle event. `columnKey` is the edited field for a cell, and empty * for a row or batch whose payload is the whole patch (or list of patches). */ interface EditEvent { /** The row as it was when the gesture started. */ row: TRow; /** Its stable id. */ rowId: string; /** The column, or `""` when the unit is the whole row or a batch. */ columnKey: string; /** What the reader arrived at — the parsed value, the patch, or the edits. */ value: unknown; /** What was there before. */ previousValue: unknown; /** Which commit unit fired this. */ unit: EditUnit; /** Why a commit was refused, or why a save rejected. */ error?: string; } /** A host callback that observes one kind of event. */ type EditEventHandler = (event: EditEvent) => void; /** The five observers a host may wire. All optional, all inert when omitted. */ interface EditLifecycle { /** An editor opened. */ onEditStart?: EditEventHandler; /** The reader threw the draft away. */ onEditCancel?: EditEventHandler; /** The host received the value. */ onEditCommit?: EditEventHandler; /** A validator refused the value; the editor stayed open. */ onValidationFail?: EditEventHandler; /** A save promise rejected. */ onEditError?: EditEventHandler; } //#endregion //#region src/editing/batchEditing.d.ts /** One row's pending changes. */ interface BatchRowEdit { /** The row as it was when the reader started changing it. */ row: TRow; /** Its stable id. */ rowId: string; /** Parsed values by column key — only the fields that actually changed. */ patch: Readonly>; } /** Headless batch-editing state. */ interface BatchEditingState { /** How many rows are waiting — what a "3 unsaved rows" line reads. */ count: number; /** Whether anything is waiting at all. */ pending: boolean; /** Whether this row has pending changes. */ isPending: (rowId: string) => boolean; /** This cell's draft, or the row's stored value when it has none. */ draftFor: (row: TRow, rowId: string, columnKey: string) => string; /** Whether this cell has been changed. */ isChanged: (rowId: string, columnKey: string) => boolean; /** Change one cell, without telling the host. */ setDraft: (row: TRow, rowId: string, columnKey: string, value: string) => void; /** Hand the host every pending row, as one list, then forget them. */ saveAll: () => void; /** Forget everything, restoring nothing — the drafts were never applied. */ cancelAll: () => void; /** Forget one row's changes. */ cancelRow: (rowId: string) => void; /** A digest of the pending drafts, for a row memo comparator. */ signature: string; } /** What {@link useBatchEditing} needs. */ interface UseBatchEditingOptions { /** * Whether batch editing is armed. Off by default: it changes when a commit * happens, which is a decision about the data rather than a preference. */ enabled?: boolean; /** The columns, for seeding drafts and parsing them back. */ columns: readonly EditableColumnLike[]; /** * Take every pending row at once. The table never writes to a row, and the * whole point of the mode is that this is called once. */ onBatchEdit?: (edits: readonly BatchRowEdit[]) => unknown; /** A row became pending. */ onEditStart?: EditEventHandler; /** Pending changes were thrown away. */ onEditCancel?: EditEventHandler; /** The host received the batch. */ onEditCommit?: EditEventHandler; } /** * Headless state for changing many rows and saving them together. * * @typeParam TRow - The row type. * @param options - See {@link UseBatchEditingOptions}. * @returns The state; inert unless `enabled`. */ declare function useBatchEditing(options: UseBatchEditingOptions): BatchEditingState; //#endregion //#region src/editing/dirtyCells.d.ts /** Dirty state for the whole table. */ interface DirtyCellState { /** Whether this cell holds a change nobody has confirmed. */ isDirty: (rowId: string, columnKey: string) => boolean; /** Whether any cell in this row does — what a row marker reads. */ isRowDirty: (rowId: string) => boolean; /** How many cells are waiting, for a "3 unsaved changes" line. */ count: number; /** Mark a cell changed. */ mark: (rowId: string, columnKey: string) => void; /** Clear one cell — a save confirmed, or a change undone. */ confirm: (rowId: string, columnKey: string) => void; /** Clear every cell in one row. */ confirmRow: (rowId: string) => void; /** Clear everything — what a successful refetch means. */ confirmAll: () => void; /** A digest of the marks, for a row memo comparator. */ signature: string; } /** What {@link useDirtyCells} needs. */ interface UseDirtyCellsOptions { /** * Whether to mark at all. Off by default: a mark is a claim about what the * server has agreed to, and a table whose host never says would be guessing. */ enabled?: boolean; } /** * Headless dirty-cell state for inline editing. * * @param options - See {@link UseDirtyCellsOptions}. * @returns The state; inert unless `enabled`. */ declare function useDirtyCells(options?: UseDirtyCellsOptions): DirtyCellState; //#endregion //#region src/editing/editConflict.d.ts /** How an unhandled conflict is resolved. */ type EditConflictPolicy = "keep" | "take" | "ask"; /** The host's choice, when it makes one. */ type EditConflictChoice = "keep" | "take"; /** * One conflict. `row` is what just arrived; `previous` is the snapshot the * editor opened against (or last accepted). */ interface EditConflict { /** The incoming row. */ row: TRow; /** The row as it was when the editor opened (or last accepted). */ previous: TRow; /** Its stable id. */ rowId: string; /** The column being edited. */ columnKey: string; /** What the reader has typed. */ draft: string; /** The incoming cell value. */ incomingValue: string; /** The cell value the editor opened against. */ previousValue: string; } /** What a host returns from {@link EditConflictHandler}. `void` defers to policy. */ type EditConflictHandler = (conflict: EditConflict) => EditConflictChoice | void; /** Headless conflict state for the active editor. */ interface EditConflictState { /** The conflict being asked about, or `null`. */ current: EditConflict | null; /** Whether this cell is the one in conflict. */ isConflict: (rowId: string, columnKey: string) => boolean; /** Keep the draft; accept the incoming row as the new snapshot. */ keep: () => void; /** Replace the draft with the incoming value. */ take: () => void; /** * Compare the open editor to the live rows. Call from the same effect that * discards a missing row — a conflict is that check one step milder. */ reconcile: (input: ReconcileLiveEdit) => void; /** Drop a conflict without choosing — the editor closed. */ clear: () => void; } /** What {@link EditConflictState.reconcile} needs to judge one live update. */ interface ReconcileLiveEdit { /** The active cell, or `null` when idle. */ active: { rowId: string; columnKey: string; } | null; /** The row the editor opened against. */ openedRow: TRow | undefined; /** The live draft. */ draft: string; /** The rendered row set. */ rows: readonly TRow[]; /** Columns, to read the edited field. */ columns: readonly EditableColumnLike[]; rowKey: (row: TRow) => string; /** Host version accessor — any change is a conflict, not just this cell. */ rowVersion?: (row: TRow) => string | number; policy: EditConflictPolicy; onEditConflict?: EditConflictHandler; /** Keep: new snapshot, same draft. */ keep: (row: TRow) => void; /** Take: new snapshot and the incoming value as the draft. */ take: (row: TRow, incomingValue: string) => void; } /** * Whether the live row disagrees with the snapshot the editor opened against. * * With `rowVersion`, any version change is a conflict — the host said the row * moved. Without it, only the edited column's stored value counts, so an * unrelated field updating does not steal the draft. */ declare function liveRowChanged(input: { opened: TRow; current: TRow; column: EditableColumnLike; rowVersion?: (row: TRow) => string | number; }): boolean; /** * Headless conflict state. Inert until {@link EditConflictState.reconcile} * sees a live row that disagrees with the open editor. */ declare function useEditConflict(): EditConflictState; //#endregion //#region src/editing/rowEditing.d.ts /** The drafts a row edit holds, by column key. */ type RowEditDrafts = Readonly>; /** Headless row-editing state. */ interface RowEditingState { /** The row being edited, or `null` when none is. */ activeRowId: string | null; /** Whether this row is the one being edited. */ isEditing: (rowId: string) => boolean; /** Every draft in the open row, by column key. */ drafts: RowEditDrafts; /** One column's draft in the open row. */ draftFor: (columnKey: string) => string; /** Open a row, seeding every editable column from its current value. */ begin: (row: TRow, rowId: string) => void; /** Replace one column's draft. */ setDraft: (columnKey: string, value: string) => void; /** * Hand the host everything the reader changed, as one patch, then close. * A no-op when nothing is open, and it reports nothing when nothing changed — * saving an untouched row is a write the host never asked for. */ save: () => void; /** Throw every draft away and close. */ cancel: () => void; /** Whether any draft differs from the row's stored value. */ isDirty: boolean; /** A digest of the open row's drafts, for a row memo comparator. */ signature: string; } /** What {@link useRowEditing} needs. */ interface UseRowEditingOptions { /** * Whether row editing is armed. Off by default: it changes the commit unit, * which is a decision about the data, not a preference. */ enabled?: boolean; /** The columns, for seeding drafts and parsing them back. */ columns: readonly EditableColumnLike[]; /** * Take everything the reader changed, as one patch of parsed values keyed by * column. The table never writes to a row. */ onRowEdit?: (row: TRow, patch: Readonly>) => unknown; /** An editor opened on this row. */ onEditStart?: EditEventHandler; /** The reader threw the drafts away. */ onEditCancel?: EditEventHandler; /** The host received the patch. */ onEditCommit?: EditEventHandler; } /** * Headless state for editing a row as one unit. * * @typeParam TRow - The row type. * @param options - See {@link UseRowEditingOptions}. * @returns The state; inert unless `enabled`. */ declare function useRowEditing(options: UseRowEditingOptions): RowEditingState; //#endregion //#region src/editing/saveState.d.ts /** What a cell's last save is doing. */ type CellSaveStatus = "saving" | "failed"; /** One cell's failed save, with what it takes to retry or undo it. */ interface FailedCellSave { /** The row as it was before the edit — what a rollback restores. */ previous: TRow; /** The value the reader tried to save. */ attempted: unknown; /** Why it failed, in a sentence a reader can read. */ message: string; } /** What {@link useCellSaveState} needs. */ interface UseCellSaveStateOptions { /** * Put the previous row back after a rejected save. Without it the table marks * the cell failed and leaves the value where it is — correct for a table that * refetches, wrong for one that applied the edit optimistically. */ onRollback?: (previous: TRow, columnKey: string) => void; /** Turn a rejection into the sentence the cell shows. */ formatError?: (error: unknown) => string; /** Observe a rejected save — never owns the outcome. */ onEditError?: EditEventHandler; } /** Per-cell save state for the whole table. */ interface CellSaveState { /** What this cell's last save is doing, if anything. */ statusFor: (rowId: string, columnKey: string) => CellSaveStatus | undefined; /** Why this cell's last save failed, if it did. */ failureFor: (rowId: string, columnKey: string) => FailedCellSave | undefined; /** * Watch one commit, and report how it went: `true` when the value reached * wherever it was going, `false` when it did not. * * The outcome comes back from here rather than being read off the state * afterwards, because a caller holding a render-old closure would read the * state as it was BEFORE the failure and conclude the save succeeded. */ track: (options: { rowId: string; columnKey: string; /** The row before the edit — what a rollback restores. */ previous: TRow; /** The value being saved. */ attempted: unknown; /** * The cell's previous value, when the caller has it. The error event * reports this as `previousValue`; without it the event uses the row. */ previousValue?: unknown; /** Whatever `onCellEdit` returned. */ result: unknown; }) => Promise; /** Put a failed cell's previous row back, and forget the failure. */ rollback: (rowId: string, columnKey: string) => void; /** Forget a cell's failure without restoring anything — what a retry does. */ clear: (rowId: string, columnKey: string) => void; /** A digest of the states, for a row memo comparator. */ signature: string; /** * Whether the table was told how to put a row back. An undo control offered * without one would do nothing when pressed. */ canRollback: boolean; } /** * Headless save state for inline editing. * * @typeParam TRow - The row type. * @param options - See {@link UseCellSaveStateOptions}. * @returns The state; inert until a commit returns a promise that rejects. */ declare function useCellSaveState(options?: UseCellSaveStateOptions): CellSaveState; //#endregion //#region src/editing/useCellEditing.d.ts /** Keyboard outcome from {@link CellEditingState.handleKeyDown}. */ type CellEditKeyAction = "commit" | "cancel" | "commit-advance"; /** Row/column context for Tab / Shift+Tab advance. */ interface CellEditNavigation { rows: readonly unknown[]; columns: readonly EditableColumnLike[]; rowKey: (row: unknown) => string; } /** Outcome of {@link CellEditingState.handleKeyDown}. */ interface CellEditKeyOutcome { action: CellEditKeyAction; commit: CellEditCommit | null; advanceTarget: CellEditTarget | null; } /** Headless cell-editing state returned by {@link useCellEditing}. */ interface CellEditingState { /** The cell currently being edited, or `null` when idle. */ active: CellEditTarget | null; /** Live draft string for the active editor. */ draft: string; /** Whether `(rowId, columnKey)` is the active cell. */ isActive: (rowId: string, columnKey: string) => boolean; /** * Start editing a cell. Re-beginning the same cell keeps the draft; * switching cells abandons the previous draft without committing. * Pass the row so lifecycle observers can name what opened. */ begin: (rowId: string, columnKey: string, initialValue: string, row?: unknown) => void; /** Update the draft without committing. */ setDraft: (value: string) => void; /** * Commit the draft. Returns the commit payload, or `null` when idle. * Clears the active cell. The table never mutates rows — callers must * apply the result through `onCellEdit` (see {@link applyCellEditCommit}). */ commit: () => CellEditCommit | null; /** * Cancel editing and clear the active cell (Escape). Adapters should * restore focus to the cell that was being edited. */ cancel: () => void; /** * Close the editor without treating it as a cancel — what a successful * validation does before handing the value to the host. Observers do not * hear about this; the commit event fires from the send instead. */ close: () => void; /** * Drop the active edit when its row leaves the current page/filter set * (no commit). No-op when idle or the row is still present. */ discardIfRowMissing: (rows: readonly unknown[], rowKey: (row: unknown) => string) => void; /** * The row the editor opened against, so a live update can be compared to * what the reader started from. */ openedRow: () => unknown; /** * Keep the draft and accept `row` as the new snapshot — the incoming * change is acknowledged, the typing is not. */ keepLive: (row: unknown) => void; /** * Replace the draft with `value` and accept `row` as the new snapshot — * the reader takes the incoming cell. */ takeLive: (row: unknown, value: string) => void; /** * Keyboard flow: * - Enter → commit * - Escape → cancel (adapters restore focus) * - Tab → commit and advance; Shift+Tab → commit and go previous * * Returns `null` when idle or for unrelated keys (so the input keeps * default behaviour). */ handleKeyDown: (event: { key: string; preventDefault: () => void; shiftKey?: boolean; }, navigation?: CellEditNavigation) => CellEditKeyOutcome | null; } /** What {@link useCellEditing} observes, when the host wired lifecycle events. */ interface UseCellEditingOptions { /** An editor opened. */ onEditStart?: EditEventHandler; /** The reader threw the draft away (Escape, or switching cells). */ onEditCancel?: EditEventHandler; } /** * Headless editing state machine: one active cell, draft value, and the * Enter / Escape / Tab keyboard flow. * * @typeParam TRow - The row type, when lifecycle observers are wired. * @param options - Optional start/cancel observers. * @returns The state machine. */ declare function useCellEditing(options?: UseCellEditingOptions): CellEditingState; //#endregion //#region src/editing/validation.d.ts /** Validate one edited value. Return a message to reject it, nothing to allow. */ type CellValidator = (value: unknown, row: TRow) => string | undefined | Promise; /** * Validate the row an edit would produce. * * Return a message for a row-level problem, a map of column key → message to * mark individual cells, or nothing to allow the commit. */ type RowValidator = (row: TRow) => string | Record | undefined | Promise | undefined>; /** A cell address, as the editing state spells it. */ interface ValidationTarget { rowId: string; columnKey: string; } /** Outcome of {@link EditValidationState.check}. */ interface ValidationCheckResult { /** Whether the commit may proceed. */ allowed: boolean; /** * Why it may not, when it may not. Absent when a newer check superseded * this one. */ error?: string; } /** Validation state for the whole table. */ interface EditValidationState { /** The message on one cell, if any. */ errorFor: (rowId: string, columnKey: string) => string | undefined; /** The row-level message, if any. */ rowErrorFor: (rowId: string) => string | undefined; /** Whether a cell's validators are still running. */ isValidating: (rowId: string, columnKey: string) => boolean; /** Whether any cell in this row carries a message. */ rowHasError: (rowId: string) => boolean; /** * Run the validators for one commit. * * `allowed` is whether the commit may proceed. A rejection also carries * `error` — the sentence the editor shows — so a caller that fires in the * same tick as the check does not have to wait for a render to read it. */ check: (options: { target: ValidationTarget; value: unknown; row: TRow; validateCell?: CellValidator; }) => Promise; /** Forget everything about one cell — what cancelling an edit does. */ clear: (rowId: string, columnKey: string) => void; /** Forget every message. */ clearAll: () => void; /** A digest of the messages, for a row memo comparator. */ signature: string; /** * Whether a row validator is armed. A cell with no validator of its own still * has to run the check when the table has one — a cross-field rule fires on * whichever cell was edited. */ hasRowValidator: boolean; } //#endregion //#region src/editing/editableCellController.d.ts /** Opt-in editing bundle from {@link TableChrome.editing}. */ interface EditableCellEditing { /** * The per-cell change channel. Return a promise and the cell shows it is * saving until that promise settles, and shows why if it rejects. * * Absent when the host wants row-level commits only: the bundle still exists * (row mode needs it) and every cell stays display-only until a reader opens * the row. */ onCellEdit?: (row: TRow, key: string, nextValue: unknown) => unknown; state: CellEditingState; /** * Validation, when the host declared any. A commit runs the validators first * and is dropped if one rejects — the editor stays open with the message on * it, so the reader fixes what they typed instead of losing it. */ validation?: EditValidationState; /** * Save state, when the host's `onCellEdit` returns promises. Inert for a * host that saves synchronously. */ saving?: CellSaveState; /** Dirty marks, when the host asked for them (`dirtyIndicators`). */ dirty?: DirtyCellState; /** * Row-mode state, when the host armed it. While a row is open its cells render * row editors instead of the per-cell activate control. */ rowEditing?: RowEditingState; /** * Batch state, when the host armed it. Every editable cell renders a field * and nothing reaches the host until the reader saves them all. */ batch?: BatchEditingState; /** Lifecycle observers — fire from the same place the transition happens. */ lifecycle?: EditLifecycle; /** Live-update conflict for the open editor, when one is being asked about. */ conflict?: EditConflictState; /** Labels for the conflict notice — already resolved. */ conflictLabels?: { message: string; keepMine: string; takeTheirs: string; theirsValue: (value: string) => string; }; } /** Display / edit mode for one cell. */ type EditableCellMode = "display" | "activatable" | "editing"; /** Controller returned by {@link editableCellController}. */ interface EditableCellController { mode: EditableCellMode; /** The validator's message for this cell, if it rejected the last commit. */ error?: string; /** Whether an async validator is still deciding about this cell. */ validating: boolean; /** What this cell's last save is doing: in flight, or failed. */ saveStatus?: CellSaveStatus; /** Why this cell's last save failed, and what it takes to undo it. */ saveFailure?: FailedCellSave; /** Whether this cell holds a change nobody has confirmed yet. */ isDirty: boolean; /** Whether an undo can be offered — the host said how to perform one. */ canRollback: boolean; /** Put the previous value back after a failed save. */ rollback: () => void; /** Forget a failed save without restoring anything. */ dismissFailure: () => void; /** Resolved editor when the column is editable; always set for activatable/editing. */ editor: CellEditor | null; /** Normalized select options (empty for text/number). */ selectOptions: ReturnType; draft: string; begin: () => void; setDraft: (value: string) => void; /** Commit the draft now, without waiting for Enter or a blur. */ commit: () => void; /** Abandon the draft, exactly as Escape does. */ cancel: () => void; /** Wire to the editor's keydown — Enter/Tab/Escape. */ onEditorKeyDown: (event: { key: string; preventDefault: () => void; shiftKey?: boolean; }) => void; /** Commit on blur (click-away). No-op when not editing. */ commitOnBlur: () => void; /** * A live row changed under this editor. Present only while the policy is * asking; Keep mine / Take theirs resolve it. */ conflict?: EditConflict; /** Labels for the conflict notice. */ conflictLabels?: { message: string; keepMine: string; takeTheirs: string; theirsValue: (value: string) => string; }; /** Keep the draft. */ keepConflict: () => void; /** Take the incoming value. */ takeConflict: () => void; } /** * Derive the per-cell editing controller. When `editing` is omitted (host * did not pass `onCellEdit`), always returns `mode: "display"` — zero UI * change for tables that never opted in. */ declare function editableCellController(options: { editing: EditableCellEditing | undefined; row: TRow; column: ColumnDef; rowId: string; rows: readonly TRow[]; columns: readonly ColumnDef[]; rowKey: (row: TRow) => string; }): EditableCellController; /** * Attach as a `ref` (or kit `inputRef`) so the editor receives focus when the * cell enters edit mode — replaces the `autoFocus` attribute (axe/a11y). * Accepts DOM nodes and kit refs that expose `.focus()` (e.g. antd InputRef). */ declare function focusEditorOnMount(node: { focus: () => void; } | null): void; /** * Whether any cell in a row holds a change nobody has confirmed. * * Read by every adapter's row so the mark exists at both scales: a reader * scanning a long table sees which rows are unsettled without hunting for the * cell inside them. * * @typeParam TRow - The row type. * @param editing - The editing bundle from the chrome. * @param rowId - The row's stable id. * @returns Whether to mark the row. */ declare function rowIsDirty(editing: EditableCellEditing | undefined, rowId: string): boolean; /** * Memo digest for one desktop/card row: `null` when editing is off (host * never passed `onCellEdit`); empty string when this row is idle; otherwise * `columnKey:draft` so only the active edit row re-renders on keystrokes. */ declare function rowEditingSignature(editing: EditableCellEditing | undefined, rowId: string): string | null; //#endregion //#region src/editing/EditableCellGate.d.ts /** Props for a kit-native editor while a cell is active. */ interface EditableCellEditorCtrl { draft: string; setDraft: (value: string) => void; onEditorKeyDown: (event: { key: string; preventDefault: () => void; shiftKey?: boolean; }) => void; commitOnBlur: () => void; editor: NonNullable["editor"]>; selectOptions: ReturnType["selectOptions"]; /** * A validator's message for this cell, when the last commit was rejected. * Wire it to the kit's own error surface (Mantine's `error`, MUI's * `helperText`, …) — and it is on the DOM either way, see `errorId`. */ error?: string; /** Whether an async validator is still deciding. */ validating: boolean; /** * `id` of the element holding the message. Put it on the editor's * `aria-describedby` so the message is announced with the field, and set * `aria-invalid` when `error` is set. */ errorId: string; /** * Attach as the editor's `ref` so the table decides what takes focus. * * In cell mode that is this editor, every time. In row mode a whole row opens * at once, and only the FIRST field should take focus — nine editors each * calling focus on mount would leave the reader at the last column of the row * they just opened. */ focusRef: (node: { focus: () => void; } | null) => void; /** A live row changed under this editor. */ conflict?: boolean; } /** * Opt-in cell wrapper: plain display when editing is off; double-click / * Enter / F2 to activate; kit supplies the editor via `renderEditor`. * * When `editing` is omitted this is a pure pass-through of `display` — * zero DOM / behavior change for tables that never opted into cell edit. */ interface EditableCellGateProps { readonly editing: EditableCellEditing | undefined; readonly row: TRow; readonly column: ColumnDef; readonly rowId: string; readonly rows: readonly TRow[]; readonly columns: readonly ColumnDef[]; readonly rowKey: (row: TRow) => string; /** Accessible name for the activate control. */ readonly editLabel: string; /** Optional class for the activate button (adapters' styling hook). */ readonly activateClassName?: string; /** Optional class for the validation message (adapters' styling hook). */ readonly errorClassName?: string; /** Optional class for a failed save's message. */ readonly saveErrorClassName?: string; /** Optional class for the undo control beside it. */ readonly rollbackClassName?: string; /** * Label for the undo control a failed save offers (`labels.undoEdit`). Omit * it and the message shows without one — right for a table that refetches * rather than rolling back. */ readonly undoLabel?: string; /** * Set by a kit whose own input renders the message — Mantine's `error`, MUI's * `helperText`. Those components own the input's `aria-describedby`, so a * second copy of the text would be both duplicated in the DOM and announced * twice. The gate then renders no message of its own and leaves the ARIA to * the kit. */ readonly kitRendersError?: boolean; readonly display: ReactNode; /** * Kit-native editor. Only called while this cell is the active edit. * Wire `value`/`onChange`/`onKeyDown`/`onBlur` from the controller. */ readonly renderEditor: (ctrl: EditableCellEditorCtrl) => ReactElement; /** Kit activate control and conflict / undo buttons. */ readonly slots: EditableCellSlots; } /** Kit activate control the gate calls while the cell is idle. */ interface EditableCellActivateProps { readonly title: string; readonly className?: string; readonly saveStatus: string | undefined; readonly dirty: boolean; readonly activateRef: (node: HTMLButtonElement | null) => void; readonly display: ReactNode; readonly onDoubleClick: (event: { preventDefault: () => void; stopPropagation: () => void; }) => void; readonly onClick: (event: { stopPropagation: () => void; }) => void; readonly onKeyDown: (event: { key: string; preventDefault: () => void; stopPropagation: () => void; }) => void; } /** Kit button the gate calls for conflict choices and undo. */ interface EditableCellButtonProps { readonly label: string; readonly part: string; readonly className?: string; readonly onMouseDown?: (event: { preventDefault: () => void; }) => void; readonly onClick: (event: { stopPropagation: () => void; }) => void; } /** Adapter-supplied controls for {@link EditableCellGate}. */ interface EditableCellSlots { readonly Activate: (props: EditableCellActivateProps) => ReactNode; readonly Button: (props: EditableCellButtonProps) => ReactNode; } /** * The ARIA a kit's editor needs when validation is in play. * * Spread onto the input or select: invalid marks the field, `describedby` * points at the message so it is read WITH the field rather than announced * once and lost, and busy says an async check is still deciding. * * @param ctrl - The editor controller the gate handed the kit. * @returns Attributes to spread; empty while the value is fine. */ declare function editorValidationProps(ctrl: EditableCellEditorCtrl): { "aria-invalid"?: true; "aria-describedby"?: string; "aria-busy"?: true; "data-conflict"?: ""; }; /** * Busy and conflict marks, for a kit whose own input owns `aria-invalid` * (Mantine, MUI). `data-conflict` still belongs on the field so the same * selector works on every kit; `aria-describedby` points at the notice * while one is up. * * @param ctrl - The editor controller the gate handed the kit. * @returns Attributes to spread; empty unless a check is running or a * conflict is being asked. */ declare function editorBusyProps(ctrl: EditableCellEditorCtrl): { "aria-busy"?: true; "aria-describedby"?: string; "data-conflict"?: ""; }; /** * Toggle a checkbox editor and commit in the same gesture. * * A checkbox has one gesture, so waiting for Enter or a blur would leave the * reader looking at a ticked box that has changed nothing. Safe to call * synchronously: the editing state writes its draft ref in the same tick, so the * commit that follows sees the new value. * * @param ctrl - The editor controller the gate handed the kit. * @param checked - The box's new state. */ declare function commitBooleanDraft(ctrl: EditableCellEditorCtrl, checked: boolean): void; /** * The draft for a native `` whose OS list is open) are not outside. */ declare function usePointerDismiss(open: boolean, dismiss: () => void, insideSelector: string): void; /** Open state + a source that honours {@link bindHeaderFilterDismiss}. */ declare function useHeaderFilterOverlay(props: { source: FilterFormSource; def: FilterDef; closeOnSelect?: boolean; registry?: FilterTypeRegistry; }, options?: { nestedSelector?: string; pointerDismiss?: boolean; }): { open: boolean; setOpen: (open: boolean) => void; source: FilterFormSource; sessionProps: HeaderFilterSessionProps; resetKey: number; }; //#endregion //#region src/filters/relativeDates.d.ts /** * Relative date tokens. The URL and Saved Views store the token, never a * resolved calendar day — "last 7 days" stays the last 7 days tomorrow. * {@link resolveRelativeRange} is the only place a token becomes a window. */ /** Named windows that need no extra number. */ declare const RELATIVE_NAMED: readonly ["today", "yesterday", "tomorrow", "thisWeek", "thisMonth", "previousMonth"]; /** A stored relative-date token (`last:7`, `today`, …). */ type RelativeDateToken = (typeof RELATIVE_NAMED)[number] | `last:${number}` | `next:${number}`; /** Inclusive local-time window a token resolves to. */ interface RelativeDateRange { startMs: number; endMs: number; } /** * Parse a stored token. Unknown strings (including ISO dates) return * `undefined` so a historical absolute bound is never treated as relative. */ declare function parseRelativeToken(raw: string | undefined): RelativeDateToken | undefined; /** True when `raw` is a relative token, not an absolute date. */ declare function isRelativeDateToken(raw: string | undefined): boolean; /** * Resolve a token against `now` (defaults to the current instant). The * frontend predicate and the server query path must both call this so a * shared link and a live table agree. */ declare function resolveRelativeRange(raw: string | undefined, now?: number | Date): RelativeDateRange | undefined; /** Build a counted token (`last:7`). `n` below 1 becomes 1. */ declare function countedRelativeToken(kind: "last" | "next", n: number): RelativeDateToken; /** Named window or a counted last/next kind (the N lives beside the select). */ type RelativePreset = (typeof RELATIVE_NAMED)[number] | "last" | "next"; /** Presets offered by the relative-date widget, in display order. */ declare const RELATIVE_PRESETS: readonly RelativePreset[]; /** `TableLabels` key for each relative preset. */ declare const RELATIVE_PRESET_LABEL_KEYS: { readonly today: "relToday"; readonly yesterday: "relYesterday"; readonly tomorrow: "relTomorrow"; readonly thisWeek: "relThisWeek"; readonly thisMonth: "relThisMonth"; readonly previousMonth: "relPreviousMonth"; readonly last: "relLastN"; readonly next: "relNextN"; }; /** Split a stored token into the widget's preset + N (N defaults to 7). */ declare function splitRelativeToken(raw: string): { preset: RelativePreset; n: number; }; /** Join a widget preset + N back into the stored token. */ declare function joinRelativeToken(preset: RelativePreset, n: number): RelativeDateToken; /** Chip / select wording for a stored token. */ declare function relativeTokenLabel(raw: string, labels: { relToday: string; relYesterday: string; relTomorrow: string; relThisWeek: string; relThisMonth: string; relPreviousMonth: string; relLastN: string; relNextN: string; }): string; //#endregion //#region src/filters/useFilterOptions.d.ts /** Resolved choices for a select/multiSelect control. */ interface ResolvedFilterOptions { /** The choices to render (empty while an async loader is in flight). */ options: readonly FilterOption[]; /** True while an async loader is fetching. */ loading: boolean; } /** * Resolve a definition's option source for the auto-built form: arrays are * used as-is, async loaders run once on mount (kit forms show their native * loading affordance meanwhile), and a leftover `"auto"` — possible only on * the server/source tiers, where there is no full dataset to derive from — * resolves to no options with a development warning. */ declare function useFilterOptions(def: Pick, "key" | "options">): ResolvedFilterOptions; //#endregion //#region src/filters/useFilterTreeChips.d.ts /** Build one chip label for a tree leaf. */ declare function filterTreeChipLabel(condition: QueryCondition, defs: readonly FilterDef[], labels: Required): string; /** Options for {@link useFilterTreeChips}. */ interface UseFilterTreeChipsOptions { readonly tree: QueryFilterGroup | undefined; readonly defs: readonly FilterDef[]; readonly labels: Required; readonly setFilterTree?: (tree: QueryFilterGroup | undefined) => void; } /** Flatten the tree into removable chips. */ declare function useFilterTreeChips(options: UseFilterTreeChipsOptions): readonly ActiveFilterChip[]; //#endregion //#region src/find/findMatches.d.ts /** What a find needs to know. */ interface FindMatchesOptions { /** What to look for. An empty or blank query matches nothing. */ query: string; /** The rows the browser holds, in table order. */ rows: readonly TRow[]; /** The columns as rendered — matches are addressed by their index. */ columns: readonly ColumnDef[]; /** Where the rendered window starts in the dataset. Zero unless paged. */ firstRowIndex?: number; } /** * Every cell whose text contains the query, in reading order. * * Case-insensitive, because nobody types the case of what they are looking * for. Only the LOADED rows are searched: a find cannot honestly claim a hit * in a row the browser has never seen, and saying "3 of 17" about rows that * are not there would be worse than saying nothing. * * @typeParam TRow - The row type. * @param options - See {@link FindMatchesOptions}. * @returns The matching cells, in absolute addresses. */ declare function findMatches(options: FindMatchesOptions): GridCell[]; /** A cell address as a string, for set membership without a nested scan. */ declare function matchKey(cell: GridCell): string; /** * The match keys as a set, so a cell can ask "am I a match" in constant time. * * A table of 500 rows × 12 columns asks that question 6,000 times per render; * scanning an array each time is the difference between a find that feels * instant and one that stutters. * * @param matches - The matches, from {@link findMatches}. * @returns Their keys. */ declare function matchKeySet(matches: readonly GridCell[]): ReadonlySet; /** * Step through the matches, wrapping at both ends. * * Wrapping is what a find does — reaching the last hit and pressing next * returns to the first, which is why a browser's find bar says "1 of 17" * again rather than stopping. * * @param index - Where the walk is now. * @param total - How many matches there are. * @param step - `1` for next, `-1` for previous. * @returns The next index, or `-1` when there is nothing to step through. */ declare function stepMatch(index: number, total: number, step: number): number; //#endregion //#region src/focus/clipboardRange.d.ts /** What a copy needs to know: the rectangle, and the data under it. */ interface ClipboardRangeOptions { /** The selected rectangle, in absolute addresses. */ range: CellRange; /** The rows the browser holds, in table order. */ rows: readonly TRow[]; /** The columns as rendered — a range's column indices address these. */ columns: readonly ColumnDef[]; /** Where the rendered window starts in the dataset. Zero unless paged. */ firstRowIndex?: number; /** Include a header row naming each column. Defaults to `false`. */ headers?: boolean; } /** * The selected rectangle as tab-separated text, ready for the clipboard. * * Rows outside what the browser holds are skipped rather than written blank: a * range can only cover loaded rows, and inventing empty ones would paste holes * into someone's spreadsheet. * * @typeParam TRow - The row type. * @param options - See {@link ClipboardRangeOptions}. * @returns TSV text; an empty string when the range covers nothing loaded. */ declare function clipboardRangeText(options: ClipboardRangeOptions): string; /** * Write text to the clipboard, reporting whether it landed. * * The async Clipboard API is unavailable outside a secure context and can be * refused by permission, so this answers with a boolean rather than throwing: * a copy that silently did nothing is the thing worth avoiding, and the caller * needs to know in order to say so. * * @param text - What to write. * @returns Whether the clipboard accepted it. */ declare function writeClipboardText(text: string): Promise; /** * Read text from the clipboard, or `null` when the browser will not give it. * * Reading is the more restricted half: Safari and Firefox gate `readText` * behind a permission or refuse it outside a user gesture. `null` says "no * text", never "empty paste", so a caller can tell the difference and say so. * * @returns The clipboard's text, or `null` when it is unavailable. */ declare function readClipboardText(): Promise; //#endregion //#region src/focus/fillRange.d.ts /** Which way a fill runs. Fills are one-axis, as in every spreadsheet. */ type FillDirection = "down" | "up" | "right" | "left"; /** * Which way a drag from the selection's corner to `to` is filling. * * A pointer wanders, so the axis is decided by the LARGER overflow rather than * by whichever edge was crossed first — otherwise a drag two rows down and one * column across would fill sideways because of a stray pixel. * * @param source - The selected rectangle the fill starts from. * @param to - The cell the pointer has reached. * @returns The direction, or `null` when `to` is still inside the selection. */ declare function fillDirection(source: CellRange, to: GridCell): FillDirection | null; /** * The rectangle a fill would cover — the selection plus what the drag reached. * * This is what gets highlighted while dragging, so the preview and the commit * can never disagree about which cells are involved. * * @param source - The selected rectangle. * @param to - The cell the pointer has reached. * @returns The union rectangle, or the source itself when nothing is added. */ declare function fillTargetRange(source: CellRange, to: GridCell): CellRange; /** What a fill needs to know to become edits. */ interface FillRangeOptions { /** The selected rectangle the values come from. */ source: CellRange; /** The cell the drag reached, or the far end of a keyboard fill. */ to: GridCell; /** The rows the browser holds, in table order. */ rows: readonly TRow[]; /** The columns as rendered — a range's column indices address these. */ columns: readonly ColumnDef[]; /** Where the rendered window starts in the dataset. Zero unless paged. */ firstRowIndex?: number; } /** * Turn a fill gesture into the edits it implies. * * Only cells OUTSIDE the selection are written: the source is what is being * carried, not something to overwrite with itself. Cells beyond the loaded rows * or the rendered columns are dropped rather than invented, and a column that * is not editable is skipped. * * @typeParam TRow - The row type. * @param options - See {@link FillRangeOptions}. * @returns The edits, in reading order. */ declare function fillRangeEdits(options: FillRangeOptions): CellEdit[]; //#endregion //#region src/focus/pasteRange.d.ts /** * Parse clipboard text into a grid of raw strings. * * Quoted fields keep their tabs and newlines: a spreadsheet writes `"a\tb"` for * a cell containing a tab, and splitting naively would turn one cell into two * and shift every column after it. * * @param text - The clipboard's text. * @returns Rows of raw cell strings; empty when there is nothing to paste. */ declare function parseClipboardTable(text: string): string[][]; /** What a paste needs to know to become edits. */ interface PasteRangeOptions { /** The clipboard's text. */ text: string; /** Where the paste starts — its top-left cell is the anchor. */ range: CellRange; /** The rows the browser holds, in table order. */ rows: readonly TRow[]; /** The columns as rendered — a range's column indices address these. */ columns: readonly ColumnDef[]; /** Where the rendered window starts in the dataset. Zero unless paged. */ firstRowIndex?: number; } /** * Turn clipboard text into the edits it implies, starting at the selection's * top-left cell. * * The clipboard's shape wins over the selection's: pasting a 3×2 block into a * single selected cell writes 3×2, which is what every spreadsheet does. Cells * falling outside the loaded rows or the rendered columns are dropped rather * than invented — a paste must never write into a row the browser has not got. * * A column that is not editable is skipped: paste is an edit, and an edit into a * read-only column is not one. * * @typeParam TRow - The row type. * @param options - See {@link PasteRangeOptions}. * @returns The edits, in row-major order. */ declare function pasteRangeEdits(options: PasteRangeOptions): CellEdit[]; /** The two ways a table can receive a paste. */ interface CellPasteHandlerOptions { /** Takes the batch whole — one transaction, one undo entry. */ onCellPaste?: (edits: CellEdit[]) => void; /** The ordinary inline-edit channel, used one edit at a time. */ onCellEdit?: (row: TRow, key: string, nextValue: unknown) => void; } /** * Resolve who receives a paste — `onCellPaste`, or `onCellEdit` one cell at a * time. See {@link batchEditHandler} for why the default is the edit channel. * * @typeParam TRow - The row type. * @param options - See {@link CellPasteHandlerOptions}. * @returns The handler, or `undefined` when the table takes no edits at all — * which leaves Ctrl/Cmd+V to the browser. */ declare function cellPasteHandler(options: CellPasteHandlerOptions): ((edits: CellEdit[]) => void) | undefined; /** The two ways a table can receive a fill. */ interface CellFillHandlerOptions { /** Takes the batch whole — one transaction, one undo entry. */ onCellFill?: (edits: CellEdit[]) => void; /** The ordinary inline-edit channel, used one edit at a time. */ onCellEdit?: (row: TRow, key: string, nextValue: unknown) => void; } /** * Resolve who receives a fill — `onCellFill`, or `onCellEdit` one cell at a * time. See {@link batchEditHandler} for why the default is the edit channel. * * @typeParam TRow - The row type. * @param options - See {@link CellFillHandlerOptions}. * @returns The handler, or `undefined` when the table takes no edits at all — * which is also when the fill handle is not rendered. */ declare function cellFillHandler(options: CellFillHandlerOptions): ((edits: CellEdit[]) => void) | undefined; //#endregion //#region src/props.d.ts /** * Where a host's own toolbar controls go. * * The toolbar reads Search · custom · Filters · Saved views · Columns · * Undo/Redo · Export · Add · Rows per page, and that order is the same in * every kit. These name the two places outside it, so a control can be * put before everything or after everything without an adapter having to * know what the control is. */ interface ToolbarSlots { /** Ahead of the search input. */ start?: ReactNode; /** After every built-in control, before the rows-per-page select. */ end?: ReactNode; } /** * A side panel docked beside the table. * * Controlled, because the control that opens it is yours: a table settings * button in `toolbarSlots`, an item in your own app bar, a route. The * table never invents a trigger for it, and `open` is the panel's key or * `null` for closed. */ interface SidePanelOptions { /** The panels, in tab order. */ panels: readonly SidePanelEntry[]; /** Which panel is showing, or `null` when the panel is closed. */ open: string | null; /** Called with the panel to show, or `null` when it should close. */ onOpenChange: (key: string | null) => void; /** * Which edge to dock to. `"end"` (default) is the right in a * left-to-right table and the left in a right-to-left one. */ side?: "start" | "end"; } /** * The UI-agnostic prop surface shared by every AdaptTable adapter's * ``. Adapters extend this with kit-specific extras (slots, * classNames, animation, …) so the common contract lives in one place. * * @typeParam TRow - The row type. */ interface BaseDataTableProps { /** Data + state contract from `useFrontendData` / `useQuerySource`. */ source: TableSource; /** Column definitions. A parent with `children` is a column group. */ columns: ColumnInput[]; /** Stable React key extractor for a row. */ rowKey: (row: TRow) => string; /** Trailing per-row actions. */ rowActions?: RowAction[]; /** * How the trailing actions column renders. Omit or `"buttons"` for the * horizontal strip. `"menu"` collapses visible actions into a 3-dot menu * using each kit's own Menu. {@link BaseDataTableProps.renderRowActions} * wins over this. */ rowActionsLayout?: RowActionsLayout; /** * Replace the trailing actions cell (desktop and mobile cards). Receives * the resolved action list (host + built-in duplicate / delete / pin). * When set, `rowActionsLayout` is ignored. The column still only appears * when there are row actions (or row-mode editing). */ renderRowActions?: RowActionsRenderer; /** Accessible label for the table. */ tableLabel?: string; /** Placeholder for the search input. */ searchPlaceholder?: string; /** Options for a mobile sort-by select. */ sortByOptions?: SortByOption[]; /** Pre-translated label overrides. */ labels?: TableLabels; /** Text direction. Defaults to `"ltr"`. */ dir?: Direction; /** * Active locale tag (e.g. `"ar"`, `"ar-EG"`). Drives per-column `i18n` * data-path resolution; labels stay a separate concern (`labels`). */ locale?: string; /** * Row density — independent of column pinning. `"comfortable"` (default) is * the roomy layout; `"compact"` tightens row height/padding. Each adapter * maps it to its kit's table size. */ density?: "comfortable" | "compact"; /** * Replace a mobile card's body with your own layout. * * The card's shell stays: list-item semantics, the selection checkbox, the * expand and tree toggles, reorder controls, row actions and the detail * panel all render around what you return, so a custom card cannot drop the * parts that make the list usable. The `card` argument hands you the fields * the built-in would have laid out — column, label and rendered value, * editors included — so this is a layout decision, not a re-implementation. * * Omit it and the built-in card renders, byte for byte. */ renderCard?: MobileCardRenderer; /** Force the mobile layout (otherwise resolved from the viewport). */ forceMobile?: boolean; /** * The width, in pixels, at or below which the card layout takes over. * Defaults to 768 — a phone in portrait. * * Raise it when the table lives in a sidebar or a split pane, where the * viewport says "desktop" while the table has a phone's width to work * with. Lower it when the table is the whole page and its columns are * narrow enough to survive. */ mobileBreakpoint?: number; /** * Initial state applied while the URL is silent about a key — e.g. * `defaults={{ limit: 10, sortBy: "name" }}`. The user's own changes * (and explicit URL params) always win. */ defaults?: Partial & { extra?: ExtraFilters; }; /** * Debounce for committing the search input to the source, in * milliseconds. Defaults to 300. */ searchDebounceMs?: number; /** * Pagination mode: `"paged"`, `"infinite"`, or `"auto"` (the default — * mobile resolves to infinite, desktop to paged). `virtualize` applies * in infinite mode; on a paged desktop table it is inert. */ paginationMode?: PaginationMode; /** * How many leading desktop-visible columns anchor the mobile identity * block. Never overrides an explicit `hideOnMobile: true` — the * author's hide always wins. */ mobileIdentityColumns?: number; /** Hover-prefetch callback fired on desktop row mouse-enter. */ prefetch?: (row: TRow) => void; /** * Row activation — fires on row click and on Enter when the row has focus. * Interactive children (action buttons, the selection checkbox, links) * keep their own behaviour and never trigger it. */ onRowClick?: (row: TRow) => void; /** Called whenever the materialized source rows change. */ onRowsChange?: (rows: readonly TRow[]) => void; /** * Inline cell-edit channel. Providing this (together with per-column * `editable`) activates editing — omit it and the table never opens an * editor, even if columns declare `editable`. The table never mutates * rows; apply `nextValue` in your own state / mutation. * * Return a promise and the cell shows it is saving until that promise * settles, and shows why if it rejects — with an undo when * {@link BaseDataTableProps.onEditRollback} says how to put the row back. */ onCellEdit?: (row: TRow, key: string, nextValue: unknown) => unknown; /** * Cut — Ctrl/Cmd+X, after the clipboard has accepted the copy. Requires * `cellNavigation`. * * The table clears nothing itself: what a cut removes is your decision, and * emptying cells before the clipboard took them would lose the data outright. */ onCellCut?: (range: CellRange) => void; /** * Paste — Ctrl/Cmd+V, with the clipboard already parsed into ordinary cell * edits. Requires `cellNavigation`. * * Omit it and every edit goes through `onCellEdit`, so a table that can be * edited can be pasted into with nothing extra wired. Provide it to take the * batch whole — one server round trip, one undo entry. * * Cells landing outside the loaded rows or the rendered columns are dropped * rather than invented, and a column that is not `editable` is skipped. */ onCellPaste?: (edits: CellEdit[]) => void; /** * Fill — the handle dragged from the selection's corner, or Ctrl/Cmd+D. * Requires `cellNavigation`. * * Same contract as `onCellPaste`: omit it and every edit goes through * `onCellEdit`, so the handle appears as soon as the table can be edited. * Provide it to take the batch whole. */ onCellFill?: (edits: CellEdit[]) => void; /** * Show what the selected cells add up to — count, sum, average, min and max * — in a strip below the table. Requires `cellNavigation`. * * The count covers every selected cell; the arithmetic covers the numeric * ones, so a rectangle spanning a name and a budget still has a sum. A * single cell shows nothing: it has no total worth reading. */ selectionStats?: boolean; /** * Remember edits so they can be undone — Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z or * Ctrl+Y with `cellNavigation`, and `table.editHistory` for your own buttons. * Pass `{ depth }` to change how many gestures are kept (50 by default). * * An undo does not rewrite your data: it COMMITS the previous value back * through `onCellEdit`, so whatever you wrapped around editing runs on the * way back exactly as it ran on the way out. One gesture is one entry, so a * paste of two hundred cells undoes in a single press. */ editHistory?: boolean | { depth?: number; }; /** * Show a find bar over the table — Ctrl/Cmd+F with `cellNavigation`, or * `table.find.setOpen(true)` from a control of your own. * * Find is not search: it leaves every row where it is and walks the cells * whose text contains the query, marking them for the kit to paint. It reads * what a cell SHOWS, and searches the loaded rows only — a hit it cannot take * you to would be a lie. */ findInTable?: boolean; /** * Conditional per-row class: `(row, index) => "overdue"` — appended to the * adapter's own row classes on desktop rows and mobile cards alike. */ rowClassName?: (row: TRow, index: number) => string | undefined; /** * Conditional per-row inline style: `(row, index) => ({ background })`. * Applied on desktop rows and mobile cards alike. Omit and nothing is set. */ rowStyle?: RowStyle; /** * Row height in px — a constant, or `(row, index) => number`. Sets the * row's height and the virtualizer's `estimateSize`. `measureElement` * still reports what the browser laid out. */ rowHeight?: RowHeight; /** * Row expansion: render a detail panel under a row. Its presence enables * the leading expand chevron on desktop rows and the detail section on * mobile cards; multiple rows may be open, keyed by row id. */ renderRowDetail?: (row: TRow) => ReactNode; /** * Row ids whose detail panel (or nested table) starts open. Uncontrolled * initial state — later toggles own the set. Omit and every row starts * closed. */ defaultExpandedRowIds?: readonly string[]; /** * A real table under a row instead of a blank panel. Name it after the row * and mount the kit's own `` with the defaults handed in: * * ```tsx * nestedTable={(row) => ({ * label: `Orders for ${row.name}`, * table: (defaults) => ( * order.id} * /> * ), * })} * ``` * * It is the same component the page uses, so sorting, selection, keyboard * navigation and accessibility come with it. The defaults are the ones a * table inside a row cannot do without — no URL state to fight its parent's * over, no second search box, the parent's density and labels. * * Return `undefined` for a row that has no nested table; with * `renderRowDetail` also set, those rows fall back to it. */ nestedTable?: NestedTableFor; /** * Gate a commit on a rule no single cell can answer — an end date before its * start, a total that must match its parts. Receives the row the edit WOULD * produce, not the stored one; return a message for a row-level problem, a map * of column key → message to mark individual cells, or nothing to allow it. * May be async. */ validateRow?: RowValidator; /** * Put a row back the way it was after a rejected save. * * A table that applies an edit optimistically has already shown the new * value, so a rejection has to restore the old one — and only the host can * write to its own rows. Without this the cell is marked failed and the value * stays put, which is right for a table that refetches instead. */ onEditRollback?: (previous: TRow, columnKey: string) => void; /** Turn a rejected save into the sentence its cell shows. */ formatEditError?: (error: unknown) => string; /** * Mark cells whose change nobody has confirmed yet — `data-dirty` on the cell * and on its row, so a reader can see what is still at risk. A cell clears * when its save resolves, when a rollback undoes it, or when the table is told * the value settled (`table.editing.dirty.confirm`). * * Off by default: a mark is a claim about what the server has agreed to, and a * table whose host never says would be guessing. */ dirtyIndicators?: boolean; /** * Edit a whole row at once instead of a cell at a time: every field opens * together, holds its draft, and reaches the host as ONE patch when the reader * saves. Requires {@link BaseDataTableProps.onRowEdit}. * * The right unit for a row whose fields constrain each other — a start and an * end date cannot be edited one at a time without passing through a state that * is invalid on the way. */ rowEditing?: boolean; /** * Take everything a row edit changed, as one patch of parsed values keyed by * column. The table never writes to a row. * * Return a promise and the row's controls show it is saving, exactly as a cell * does. */ onRowEdit?: (row: TRow, patch: Readonly>) => unknown; /** * Change many rows and save them together: every editable cell is a field, * nothing is sent until the reader saves, and one Cancel puts it all back. * The shape of a review pass — walk a list correcting values, write once. * Requires {@link BaseDataTableProps.onBatchEdit}. */ batchEditing?: boolean; /** * Take every pending row at once, as a list of `{ row, rowId, patch }`. Called * once per save, which is what lets a host make the whole batch one request. */ onBatchEdit?: (edits: readonly BatchRowEdit[]) => unknown; /** * Observe an editor opening. Fires for cell, row and batch units. The * handler cannot change the outcome — throwing is swallowed. */ onEditStart?: EditEventHandler; /** * Observe a cancel (Escape, Cancel, throwing a batch away). Not fired when * a successful commit merely closes the editor. */ onEditCancel?: EditEventHandler; /** * Observe a value reaching the host. Fires after parse and validation, at * the same moment as `onCellEdit` / `onRowEdit` / `onBatchEdit`. */ onEditCommit?: EditEventHandler; /** * Observe a validator refusing a value. The editor stays open with the * message; this is how analytics hears about it. */ onValidationFail?: EditEventHandler; /** * Observe a save promise rejecting. The cell is already marked failed; * this is the side-effect channel. */ onEditError?: EditEventHandler; /** * A row changed underneath an open editor. Return `"keep"` or `"take"` to * resolve it; return nothing and {@link BaseDataTableProps.editConflictPolicy} * decides. The default policy is `"ask"`. */ onEditConflict?: EditConflictHandler; /** * What to do when a live update disagrees with an open editor and the host * did not choose. `"ask"` (default) surfaces Keep mine / Take theirs. */ editConflictPolicy?: EditConflictPolicy; /** * Host version of a row. When set, any version change under an open editor * is a conflict, not only a change to the edited column. */ rowVersion?: (row: TRow) => string | number; /** * Add a row — an Add control appears in the toolbar as soon as this is set. * The host makes the row and stores it; it reaches the table through the * source like every other row, so it is editable, filterable and counted * from the moment it lands. */ onAddRow?: () => unknown; /** * Copy a row — a Duplicate action appears on every row. What a copy means * (which fields carry over, which reset, what id it gets) is the host's. */ onDuplicateRow?: (row: TRow) => unknown; /** Remove a row. A Delete row action appears as soon as this is set. */ onDeleteRow?: (row: TRow) => unknown; /** * Reorder a row — a drag handle appears in a reserved leading column as * soon as this is set. `from` / `to` are dataset-relative (page offset * included), and `row` is the one that moved. The table never mutates the * array; apply the move with `applyRowReorder` or your own write. * * Keyboard: Space lifts, arrows move, Space drops, Escape cancels. * Grouping or a tree refuses this with a `devWarn` — nested order is not * a flat splice. Mobile cards get up/down buttons rather than a grip. */ onRowReorder?: (from: number, to: number, row: TRow) => void; /** * Controlled row pins. `{ top, bottom }` lists of row ids that render * outside the virtual window — sticky above and below the scroll box — * so they are not drawn twice. Omit for the internal (uncontrolled) * lists. Grouping or a tree refuses this with a `devWarn`. * * Mobile cards get the same pin actions but no sticky chrome: a card * list is not a grid. */ pinnedRowIds?: RowPinState; /** * Pin-list change channel. Uncontrolled: an observer. Controlled: apply * the next lists to accept. Setting this (or {@link BaseDataTableProps.pinnedRowIds}) * is what arms the feature — omit both and nothing renders. */ onPinnedRowIdsChange?: (next: RowPinState) => void; /** * Per-cell row/column span. Return `{ colSpan, rowSpan }` for the origin; * covered cells are omitted from the row's cell list. Column-level * `colSpan` / `rowSpan` on the column def are the same * thing when every row of a column shares a rule. Omit both and every * kit still maps one cell per column. * * Mobile cards ignore geometry — a card is a list of fields. Spans are * derived from data, so nothing is written to the URL. */ getCellSpan?: GetCellSpan; /** * How a spanned cell is painted. Omit / `"merged"` is the spreadsheet look * (centered content, one fill). `"plain"` keeps today's 1×1 chrome so a * host can style a calendar-style bar on `data-cell-span`. */ cellSpanAppearance?: CellSpanAppearance; /** * Host-injected separator and full-width rows, spliced into the body * by `beforeRowId`. Omit the list and nothing is inserted. Extras are * content, not table state — nothing is written to the URL. Mobile * cards keep the same slots. */ extraRows?: readonly ExtraRow[]; /** * Delete without a confirmation dialog. Off by default — a delete is * destructive and the table cannot undo it. */ confirmDeleteRow?: boolean; /** * How an edit is applied to a row for {@link BaseDataTableProps.validateRow} * to judge. Defaults to a shallow spread keyed by the column key, which is * right when a column key IS the field; pass this when a column reads a * nested path. */ applyEdit?: (row: TRow, columnKey: string, value: unknown) => TRow; /** * Footer summary: map the CURRENT page's rows to per-column summary cells * (`{ budget: {total} }`). Rendered as a table footer row aligned * under its columns; keys absent from the result render empty cells. */ summaryRow?: (rows: readonly TRow[]) => Partial>; /** * Free slot under the table (above the pager). Not the column-aligned * summary row — that is {@link BaseDataTableProps.summaryRow}. */ tableFooter?: ReactNode; /** * Row grouping by column key — one key, or an ordered list for nested * groups: `groupBy={["team", "status"]}` puts each status inside its team, * and every header carries the count and aggregates of its whole subtree. * * Its presence (or `source.groupBy`) arms grouping chrome — omit it and the * table never inserts group header rows (package DNA: opt-in). Frontend tier * only; server-paginated sources get a devWarn and grouping is ignored. */ /** * Hierarchical rows: a row's children, for nested data. * * A tree is declared by the DATA — a folder contains files, a task has * subtasks — which is why it is not grouping: grouping answers a question * the reader asked and re-answers it when they change the question. * * Its presence arms the tree; omit it (and `getParentId`) and the table * renders a flat list exactly as before. */ getChildren?: (row: TRow) => readonly TRow[] | undefined; /** Hierarchical rows the other way round: a flat table with a parent column. */ getParentId?: (row: TRow) => string | undefined; /** * Whether a row has children that have not been fetched yet — a server tree * knows there is more before the browser does. */ hasChildren?: (row: TRow) => boolean; /** * Which column carries the chevron and the indent. Defaults to the first * rendered column, which is where a reader looks for a tree. */ treeColumn?: string; /** * Fetch a node's children when the reader opens it — a tree of any size * arrives one branch at a time. Pair it with `hasChildren` so a node the * browser has not fetched still shows a chevron. Resolve once the children * are in the data the table reads; the table re-walks the hierarchy itself * and needs nothing back. Its node carries a loading flag until then, and a * rejection leaves the node closed and clickable so a retry is the same * gesture as the first attempt. */ onLoadChildren?: (row: TRow) => void | Promise; /** Controlled tree expansion: the ids currently open. */ expandedIds?: readonly string[]; /** Fired after the table opens or closes a node. */ onExpandedIdsChange?: (ids: string[]) => void; groupBy?: string | readonly string[] | null; /** * Notification fired AFTER the grouping change is applied — the table * always performs the change itself. Take full control (e.g. a fully * controlled `groupBy`) through `source.setGroupBy` instead. * * Receives the keys as a list, empty when grouping was cleared. */ onGroupByChange?: (groupBy: readonly string[]) => void; /** * Per-group aggregate cells — **same signature as {@link summaryRow}**. * Called with each group's leaf rows. Omit for headers without subtotals. */ /** * Close every group with a footer row carrying its aggregates — the totals * read at the bottom of the group as well as the top, which is where a long * group's reader is by the time they need them. * * Needs `groupAggregates`: a footer with nothing to total is a blank row. * Nested groups each get their own, innermost first. The table's own * grand total is `summaryRow`, which already totals the whole set. */ groupFooters?: boolean; /** * Order groups within their parent: `"label"`, `"label-desc"`, `"count"`, * `"count-desc"`, or your own comparator over `{ value, label, level, * groupBy, leafRows }`. * * To sort groups by an aggregate, compare the same rows the aggregate reads * — `(a, b) => total(b.leafRows) - total(a.leafRows)` sorts by total * descending. Without this, groups keep the order the source's own sort * produced. */ groupSort?: GroupSort; /** * Show at most this many top-level groups at a time, with a row offering the * rest. A table grouped by customer can have ten thousand groups, and * rendering all of them to fill one screen is the mistake virtualization * exists to avoid. */ groupPageSize?: number; /** * Show at most this many rows inside each group, with a "load more in this * group" row beneath them. */ groupRowPageSize?: number; /** * Called when a reader asks for more rows inside a group — the hook a server * tier needs, since the rest of that group is not in the browser yet. The * table reveals what it already holds either way. */ onGroupLoadMore?: (groupKey: string) => void; /** * Keep only the groups this answers true for, at every level — the group * equivalent of a filter, working on aggregates rather than cells: * `(g) => total(g.leafRows) > 10_000`. * * A dropped group takes its leaves with it. Row filters still run first, so * this decides which of the SURVIVING rows' groups are worth showing. */ groupFilter?: (group: GroupNode) => boolean; groupAggregates?: (rows: readonly TRow[]) => Partial>; /** * Controlled collapsed group keys (ephemeral — not URL-synced). * Uncontrolled: internal {@link useGroupCollapse}. */ collapsedGroupIds?: readonly string[]; onCollapsedGroupIdsChange?: (ids: string[]) => void; /** Disable the built-in search box. */ /** * Render the search input. Positive polarity — `false` hides it. * @defaultValue true */ searchable?: boolean; /** * Opt into multi-column sorting: shift-click (or shift-Enter) on a header * adds the column to the sort chain (asc → desc → removed); a plain click * still single-sorts. Sorted headers expose `data-sort-index` for badges. */ multiSort?: boolean; /** Render the built-in "Columns" menu (show/hide, pin, reorder). */ enableColumnMenu?: boolean; /** Enable drag/keyboard column resize handles. Defaults to false (opt-in). */ resizableColumns?: boolean; /** Controlled column layout (hidden/order/pinned/widths). */ columnLayout?: ColumnLayoutState; /** Change handler for the controlled column layout. */ onColumnLayoutChange?: (next: ColumnLayoutState) => void; /** Initial column layout for the uncontrolled mode. */ defaultColumnLayout?: Partial; /** * Column-group headers gain a collapse toggle. Each group decides what * remains: an arrow stub, `collapsedKey`, or `collapsedRender`. State * lives on `columnLayout.collapsedGroups` and the URL (`colGroupCollapse`). * Omit and group headers stay static. */ collapsibleColumnGroups?: boolean; /** * Fixed-height scroll box (px). Enables sideways scrolling + column pinning; * the header and pinned columns pin within this box. Omit for page scroll. */ maxHeight?: number; /** Virtualize long infinite lists. Defaults to false. */ virtualize?: boolean; /** * Window the COLUMNS as well as the rows, for tables that are wide rather * than long: a hundred columns render as the two dozen a reader can see, * plus a margin, with the rest held open by two spacer cells. * * Needs a horizontal scroll container, so it applies with `maxHeight` or * pinned columns. Pinned columns are never windowed out — they are on screen * by definition — and the spacers are logical, so a wide RTL table scrolls * the right way. * * Not available in the Ant Design adapter, which renders through antd's own * ``: that component owns its column rendering, and windowing it from * outside would fight it rather than help. */ virtualizeColumns?: boolean; /** * Make the columns share the container's width instead of overflowing it. * * Columns with a `flex` take that share of the space; columns with a `width` * keep it; everything else divides what is left equally. `minWidth` and * `maxWidth` are respected either way, so a column never shrinks below what * it needs to be read. */ fitColumns?: boolean; /** Desktop row-size estimate in px. */ estimateRowSize?: number; /** Mobile card-size estimate in px. */ estimateCardSize?: number; /** Extra rows/cards rendered before and after the virtual window. */ virtualOverscan?: number; /** * Override for window-mode virtualization's scroll offset. * * When omitted, the list's document offset is measured so a table below * page chrome does not open with a blank gap. Pass a value only when you * already know that offset (tests, or a table whose position is fixed). */ virtualScrollMargin?: number; /** * The table's filters. Pass a declarative array and the adapter builds the * form with kit-native widgets (each definition also drives URL parsing, * chips and — on frontend data — the row predicate); pass JSX to draw the * form yourself. Column-level `filter` shorthands merge in; a `filters` * entry with the same key wins. */ filters?: readonly FilterDef[] | ReactNode; /** * Extra or replacement filter types merged onto the built-in registry. * A spec whose `type` matches a built-in replaces it. Omit and only * the built-ins are available. */ filterTypes?: readonly FilterTypeSpec[]; /** * Resolved filter definitions, used to label AND/OR tree chips. The * shell sets this from the declarative `filters` array; hosts that * call `useTableChrome` directly can pass the same defs the builder * receives. */ filterDefs?: readonly FilterDef[]; /** * How the filter container opens. One mode at a time — never stacked. * `"popover"` (default) anchors a light card under the Filters button * (no backdrop); `"drawer"` slides in a side panel with a real backdrop; * `"header"` is the compact per-column row and hides the toolbar button. * `headerFilters` is an alias for `"header"`. */ filtersMode?: "popover" | "drawer" | "header"; /** Per-filter-key chip label resolvers. */ filterLabels?: Readonly>; /** Extra chips driven by non-URL state, merged with the derived chips. */ extraChips?: readonly ActiveFilterChip[]; /** Override the active-filter count (defaults to the chip count). */ activeFilterCount?: number; /** * Notification fired AFTER the filters are cleared (drawer, chip strip, * no-results CTA) — the table always performs the clear itself. Take * full control through `source.clearExtras` instead. */ onClearFilters?: () => void; /** * Alias for `filtersMode="header"`: a per-column filter icon on the * header, bound to the same defs and extra bag as the panel. Desktop * only. Hides the toolbar Filters button unless `source.setFilterTree` * is set (the AND/OR tree has no column of its own). Omit the prop and * nothing extra renders. */ headerFilters?: boolean; /** * Close a header-filter popover after a finished single-control write * (a select/boolean value, or a valueless operator such as "Is empty"). * Off by default — picking an operator on a field that still has a value * input must not dismiss the overlay. Outside click and Escape always close. */ closeHeaderFilterOnSelect?: boolean; /** * Mount the per-field Filters form. Default on. Pass `false` to keep only * the AND/OR tree in that chrome — the field list is gone, not hidden. */ filterFields?: boolean; /** Bulk actions — enabling these turns on row selection. */ bulkActions?: BulkAction[]; /** Selection id extractor; defaults to `rowKey`. */ selectionGetId?: (row: TRow) => string; /** * Controlled selection. When provided, the table reads the selection from * this value and reports every change request through `onSelectionChange` * — the same controlled/uncontrolled split as `columnLayout`. Omit it for * the internal (uncontrolled) selection. */ selectedIds?: readonly string[]; /** * Selection change channel. Uncontrolled: an observer that fires with the * selected ids whenever the set changes — once on mount with the initial * (empty) selection, on every toggle/select-all, and on the automatic * reset when the search or a filter changes (the result set changed, so * stale ids never linger). Controlled (`selectedIds` provided): the * change-request handler — apply the ids to your state to accept. */ onSelectionChange?: (selectedIds: string[]) => void; /** * Opt-in CSV export toolbar button. Pass `true` for defaults * (`export.csv`, current page) or an options object for filename/scope. * Omit or `false` to hide the button. */ exportCsv?: boolean | ExportCsvOptions; /** * Opt into keyboard cell navigation. * * The table becomes ONE tab stop whose interior is reachable by arrow keys, * Home/End, Ctrl+Home/End and PageUp/PageDown, with `role="grid"`, absolute * `aria-rowindex` / `aria-colindex`, and a live region naming the focused * cell. Enter or F2 opens the editor when `onCellEdit` is set. * * Off by default, and off means absent: no role change, no `tabIndex`, no key * handler, no live region. Applies to the desktop table layout — mobile cards * are a list, not a grid, and keep their list semantics. */ cellNavigation?: boolean; /** * Offer a checkbox in every column header that selects that column. * Defaults to false, and needs {@link cellNavigation} to do anything. * * Ctrl/Cmd+click on a header already selects a column, and that gesture is * unchanged. It is also unreachable on a touch device — there is no Ctrl key * to hold — and undiscoverable to anyone who has not been told about it. This * is the same selection behind a control a finger can hit and a screen reader * can name. On a hovering pointer it holds its space and fades in on hover or * focus, so a wide header row is not a row of checkboxes; where there is no * hover it is always visible. */ columnSelectionCheckbox?: boolean; /** Inline toolbar slot for custom controls (view toggles, etc.). */ toolbar?: ReactNode; /** * Named regions of the toolbar, for controls that have to sit somewhere * specific rather than in the middle. * * `toolbar` is the middle region and stays exactly what it was: content * between the search input and the built-in buttons. These two are the * ends, which is where an app's own view switcher or a "back" control * belongs — ahead of everything, or after it. * * ```tsx * , end: }} * … * /> * ``` */ toolbarSlots?: ToolbarSlots; /** * Let the user choose the row density from the toolbar. Defaults to off. * * The `density` prop is what the table renders; this is the control that * changes it. Pair it with `useDensityUrlState` and the choice survives a * reload and travels in a shared link. */ densityChooser?: boolean; /** Called when the user picks a density. */ onDensityChange?: (next: "comfortable" | "compact") => void; /** * A fullscreen toggle in the toolbar. Defaults to off. * * Fullscreen hides everything outside the table, which is what makes it * useful and also what breaks overlays: a menu portalled to * `document.body` is inside the part being hidden. The table's own * overlays are re-pointed at the fullscreen element while it is on. * * The button hides itself where the browser will not allow fullscreen at * all — an embedded webview, a sandboxed frame — because a control that * cannot work is worse than no control. */ fullscreen?: boolean; /** * Open the print dialog on the current view. * * What gets printed is the host's: `printTable` opens a browser dialog and * `downloadExportFile` cannot, so the table asks and the host decides. * Wire this and it becomes a command in the palette and an entry anywhere * else commands are listed. Add {@link printButton} for a toolbar control * as well — opt-in chrome either way, never a permanent button. * * ```tsx * import { printTable } from "@adapttable/core/pdf"; * * printTable({ rows, columns })} … /> * ``` */ onPrint?: () => void; /** * A command palette, opened with Cmd/Ctrl+K. Defaults to off. * * It lists the table's own actions — print, export, clear filters, each * appearing only when wired — and anything you add. Its entries are the * same objects the context menus take, so an action is written once and * offered in both places rather than drifting between them. * * ```tsx * * ``` */ commandPalette?: boolean | CommandPaletteOptions; /** * Right-click menus for headers, rows and cells. Defaults to off. * * `true` takes the built-in entries — sort, filter, pin and hide on a * header; copy and cut on a cell — each appearing only when the handler * behind it is wired and the column allows it. Pass `{ items }` to append * your own, which land behind a divider so a custom action is never * mistaken for a built-in one. * * Every route in works: right-click, Shift+F10 and the menu key for the * keyboard, and a long press for touch. Escape closes and puts focus back * where it came from. */ contextMenu?: boolean | ContextMenuOptions; /** * Dock a settings panel beside the table. * * A popover is right for a control you touch once. It is wrong for * setting a table up — choosing columns, building a filter — because * that is iterative, and a popover closes when you look away with the * rows behind it. Omit this and nothing renders and nothing is bundled. * * ```tsx * const [panel, setPanel] = useState(null); * * setPanel("filters")}>Settings, * }} * sidePanel={{ * panels: [{ key: "filters", label: "Filters", content: }], * open: panel, * onOpenChange: setPanel, * }} * … * /> * ``` */ sidePanel?: SidePanelOptions; /** * Show a status bar under the table. Defaults to false. * * It reads how many rows are on screen, how many are selected, and what * a multi-cell selection adds up to — the line a spreadsheet user * glances at without thinking. The sums appear only with * `selectionStats` armed; the counts are always there. */ statusBar?: boolean; /** * Show Undo and Redo buttons in the toolbar. Defaults to false. * * The keyboard shortcuts and `table.editHistory` are the always-on path * — this is the visible one, for an app whose users will not find * Ctrl+Z. The buttons render only when `editHistory` is armed, and * disable rather than disappear when there is nothing to undo or redo, * so the toolbar does not change width as the user works. */ undoRedoButtons?: boolean; /** * Show a Print button in the toolbar. Defaults to false. * * The palette command is the always-on path once {@link onPrint} is wired * — this is the visible one, for an app whose users will not reach for * Cmd/Ctrl+K. It renders only when both are set: a button that opens * nothing would be worse than no button, so the option alone draws * nothing and the handler alone stays a command. */ printButton?: boolean; /** Confirmation handler for actions; defaults to `window.confirm`. */ confirm?: ConfirmHandler; /** Number of skeleton rows while loading. Defaults to the page size. */ skeletonRows?: number; /** * Top inset in px for the sticky header (`stickyHeader`) — e.g. the * height of an app bar it must clear. When the toolbar pins with the * header it parks at this inset too. Defaults to 0. */ stickyTop?: number; /** Keep the desktop table header sticky while scrolling. Defaults to false (opt-in). */ stickyHeader?: boolean; /** * Keep the toolbar (search, page size) sticky with the header. * Defaults to `stickyHeader` on page-scroll tables; pass `false` to * let the toolbar scroll away. Has no effect when the table already * scrolls in a box (`maxHeight`, or antd's native virtual scroller) — * the toolbar already sits outside that scroller. */ stickyToolbar?: boolean; /** Scroll back to the table when search/filter/page changes. Defaults to true. */ scrollToTopOnChange?: boolean; /** Extra gap below sticky chrome when scrolling back. Defaults to 8. */ scrollTopGap?: number; } //#endregion //#region src/rows/useHighlight.d.ts /** One highlighted cell. */ interface HighlightedCell { rowId: string; columnKey: string; } /** What {@link useHighlight} returns. */ interface HighlightState { /** Mark a row. Repeating it restarts the clock rather than stacking. */ flashRow: (rowId: string) => void; /** Mark one cell. */ flashCell: (cell: HighlightedCell) => void; /** Drop every mark now. */ clear: () => void; /** Whether this row is marked. */ isRowHighlighted: (rowId: string) => boolean; /** Whether this cell is marked. */ isCellHighlighted: (rowId: string, columnKey: string) => boolean; /** * Whether the mark should animate. False when the user asked for reduced * motion — the mark still appears, it simply does not move. */ animated: boolean; } /** * Highlight rows and cells for a moment. * * @param enabled - Off unless the host asked; every call is then inert. * @returns The controls and the current marks. */ declare function useHighlight(enabled: boolean): HighlightState; //#endregion //#region src/source/queryKey.d.ts /** Options for {@link tableQueryKey} and {@link tableQueryBaseKey}. */ interface TableQueryKeyOptions { /** * Namespace for this table, so two tables on one page never share a cache * entry. Defaults to `"table"` — name it when there is more than one. */ scope?: string; } /** * The key for one page of a view: stable across renders, distinct per page. * * @param query - The query the table emitted. * @param options - See {@link TableQueryKeyOptions}. */ declare function tableQueryKey(query: TableQuery, options?: TableQueryKeyOptions): readonly unknown[]; /** * The key shared by every page of a view — what to invalidate after a write. * * @param query - The query the table emitted. * @param options - See {@link TableQueryKeyOptions}. */ declare function tableQueryBaseKey(query: TableQuery, options?: TableQueryKeyOptions): readonly unknown[]; //#endregion //#region src/url/useDensityUrlState.d.ts /** The two layouts a table has. */ type Density = "comfortable" | "compact"; /** What {@link useDensityUrlState} needs. */ interface UseDensityUrlStateOptions { urlAdapter?: UrlStateAdapter; urlSync?: boolean; urlKey?: string; /** The density before anyone has chosen one. Defaults to comfortable. */ defaultDensity?: Density; } /** The controlled pair to spread onto the table. */ interface UseDensityUrlStateResult { density: Density; onDensityChange: (next: Density) => void; } /** * Keep the table's density in the URL. * * @param options - See {@link UseDensityUrlStateOptions}. * @returns The controlled pair to spread onto the table. */ declare function useDensityUrlState(options?: UseDensityUrlStateOptions): UseDensityUrlStateResult; //#endregion //#region src/editing/editHistory.d.ts /** One undoable gesture: what it wrote, and what was there before. */ interface EditHistoryEntry { /** The edits the gesture made, in the order it made them. */ redo: readonly CellEdit[]; /** The values those cells held before it — the inverse, in the same order. */ undo: readonly CellEdit[]; } /** What {@link useEditHistory} needs. */ interface UseEditHistoryOptions { /** Off unless the host asked for it; when false nothing is recorded. */ enabled: boolean; /** How many gestures to remember. Defaults to 50. */ depth?: number; /** The columns, for reading a cell's value before it changes. */ columns: readonly ColumnDef[]; /** The host's commit channel — every replay goes back out through it. */ onCellEdit?: (row: TRow, key: string, nextValue: unknown) => unknown; } /** What {@link useEditHistory} returns. */ interface EditHistoryState { /** * Whether the host armed a history at all. * * `canUndo` answers "is there something to put back", which is false * both when the feature is off and when nothing has been edited yet. * Chrome that should not exist without a history needs the other * question, and this is it. */ enabled: boolean; /** Whether anything can be undone right now. */ canUndo: boolean; /** Whether anything can be redone right now. */ canRedo: boolean; /** * Put the last gesture back, through the host's own commit channel. * * @returns How many cells were restored; zero when there was nothing to undo. */ undo: () => number; /** * Do the last undone gesture again. * * @returns How many cells were rewritten; zero when there was nothing to redo. */ redo: () => number; /** Forget everything — what a host calls when the data is replaced. */ clear: () => void; /** * Record a batch as ONE gesture and apply it. Returns the edits so a caller * can keep chaining; applies nothing when history is off, in which case the * caller's own handler still runs. */ record: (edits: readonly CellEdit[]) => void; } /** * The value a cell holds right now, unstringified. * * The editor's seed is a string because an input needs one; an undo needs the * VALUE, so that putting back the number 10 does not put back `"10"`. Same * priority the editor uses otherwise: an explicit `editValue`, then * `sortValue`, then the key's data path. * * @typeParam TRow - The row type. * @param row - The row being read. * @param column - The column being read. * @returns The current value, in whatever type the row holds it. */ declare function readCellValue(row: TRow, column: ColumnDef): unknown; /** * Remember edits so they can be replayed backwards. * * @typeParam TRow - The row type. * @param options - See {@link UseEditHistoryOptions}. * @returns The history controls; inert when `enabled` is false. */ declare function useEditHistory(options: UseEditHistoryOptions): EditHistoryState; /** The props a table needs for its history — the `editHistory` prop, resolved. */ interface TableEditHistoryProps { /** The `editHistory` prop as the host wrote it. */ editHistory?: boolean | { depth?: number; }; /** The columns, for reading a cell's value before it changes. */ columns: readonly ColumnDef[]; /** The host's commit channel. */ onCellEdit?: (row: TRow, key: string, nextValue: unknown) => unknown; } /** * The history a `` runs, plus the commit channel to hand the chrome. * * The returned `onCellEdit` records each inline commit as a one-cell gesture * before passing it on. Batch routes (paste, fill) must NOT go through it — * they record themselves through {@link asGesture}, so that two hundred pasted * cells undo in one press rather than two hundred. * * Both the shell and the antd adapter build their chrome this way, and this is * the one place the rule lives. * * @typeParam TRow - The row type. * @param props - See {@link TableEditHistoryProps}. * @returns The history state and the commit channel to give the chrome. */ declare function useTableEditHistory(props: TableEditHistoryProps): { history: EditHistoryState; onCellEdit: ((row: TRow, key: string, nextValue: unknown) => unknown) | undefined; }; /** * Wrap a batch handler so the whole batch is one undo entry. * * Recording happens before the handler runs: the inverse is read from the rows * as they are NOW, and a host that applies the edits synchronously would * otherwise have already changed them. * * @typeParam TRow - The row type. * @param apply - The resolved handler, or `undefined` when nothing receives it. * @param record - The history recorder. * @returns The wrapped handler, or `undefined` when there was none to wrap. */ declare function asGesture(apply: ((edits: CellEdit[]) => void) | undefined, record: (edits: readonly CellEdit[]) => void): ((edits: CellEdit[]) => void) | undefined; //#endregion //#region src/rows/rowMutations.d.ts /** How a table asks for a row to be added, copied or removed. */ interface RowMutationHandlers { /** * Add a row. Setting this puts an Add control in the toolbar; the host makes * the row and stores it, and it reaches the table through the source like * every other row — editable, filterable and countable from the moment it * lands, with nothing about it special. */ onAddRow?: () => unknown; /** * Copy a row. Setting this puts a Duplicate action on every row; what a copy * means — which fields carry over, which are reset, what id it gets — is the * host's, because only the host knows. */ onDuplicateRow?: (row: TRow) => unknown; /** * Remove a row. Setting this puts a Delete action on every row, behind a * confirmation dialog unless {@link RowMutationHandlers.confirmDeleteRow} is * `false`. */ onDeleteRow?: (row: TRow) => unknown; /** * Delete without asking first. Off by default: a delete is destructive and * the table cannot undo it. Hosts whose own UI already confirms — or whose * delete is reversible — turn it off. */ confirmDeleteRow?: boolean; } /** Row-mutation state: the toolbar's control and the per-row actions. */ interface RowMutationsState { /** Whether an Add control should render. */ canAdd: boolean; /** Ask for a new row. Inert without `onAddRow`. */ addRow: () => void; /** * Duplicate and Delete, in that order — empty when the host wired neither. * Appended to the host's own `rowActions`, so a delete stays last. */ actions: readonly RowAction[]; } /** What {@link useRowMutations} needs. */ interface UseRowMutationsOptions extends RowMutationHandlers { /** Resolved labels, for the action names and the delete dialog. */ labels: Required; } /** The key of the synthesized duplicate action. */ declare const DUPLICATE_ROW_ACTION_KEY = "adapttable:duplicate-row"; /** The key of the synthesized delete action. */ declare const DELETE_ROW_ACTION_KEY = "adapttable:delete-row"; /** * Headless add / duplicate / delete wiring. * * @typeParam TRow - The row type. * @param options - See {@link UseRowMutationsOptions}. * @returns The state; every action is absent until its handler is wired. */ declare function useRowMutations(options: UseRowMutationsOptions): RowMutationsState; //#endregion //#region src/useTableChrome.d.ts /** * The shared prop surface every adapter's toolbar sub-component needs. * Adapters render kit-specific markup from this; extracting it keeps the * identical shape from being re-declared (and flagged as duplication) in * each adapter. * * @typeParam TRow - The row type. */ interface ToolbarChromeProps { /** The headless table state + prop-getters. */ table: UseDataTableResult; /** Render the search input (default `true`). */ searchable?: boolean; /** Placeholder for the search input. */ searchPlaceholder?: string; /** Options for an explicit sort-by control. */ sortByOptions?: SortByOption[]; /** Extra caller-supplied toolbar content, in the middle region. */ toolbar?: ReactNode; /** Caller-supplied content for the two ends of the toolbar. */ toolbarSlots?: ToolbarSlots; /** * Put the last edit back. Set only when the host asked for the buttons * (`undoRedoButtons`) AND `editHistory` is armed, so an adapter renders * the pair on presence and never has to check two things. */ onUndo?: () => void; /** Do the last undone edit again. Present with {@link onUndo}. */ onRedo?: () => void; /** * Whether there is anything to undo. The button is disabled, not * hidden — a control that vanishes moves the ones beside it, and a * toolbar that reflows while someone is working is worse than a button * that is briefly unavailable. */ canUndo?: boolean; /** Whether there is anything to redo. */ canRedo?: boolean; /** `labels.undoEdit` — the undo button's caption. */ undoLabel?: string; /** `labels.redoEdit` — the redo button's caption. */ redoLabel?: string; /** * Open the print dialog. Set only when the host asked for the button * (`printButton`) AND wired `onPrint`, so an adapter renders on presence * and never has to check two things. */ onPrint?: () => void; /** `labels.print` — the print button's caption. */ printLabel?: string; /** The density the table is rendering, when the chooser is shown. */ density?: "comfortable" | "compact"; /** Change it. Present iff the host asked for the chooser. */ onDensityChange?: (next: "comfortable" | "compact") => void; /** Toggle fullscreen. Present iff asked for AND the browser allows it. */ onToggleFullscreen?: () => void; /** Whether the table is fullscreen right now, for the button's state. */ isFullscreen?: boolean; /** Whether a filters affordance should render. */ hasFilters: boolean; /** Number shown on the filters badge. */ activeFilterCount: number; /** Whether the filter container is open (drives `aria-expanded`). */ filtersOpen: boolean; /** Toggle the filter container (popover and drawer alike). */ onToggleFilters: () => void; /** * Bind to the trigger's `onPointerDown` (see * {@link useFilterTriggerToggle}) so a click on the open trigger CLOSES * the popover instead of racing the kit's outside-close and reopening. */ onFiltersTriggerPointerDown?: () => void; /** Whether to show the rows-per-page control (infinite mode). */ showRowsPerPage: boolean; /** * Built saved-views menu node, when the `savedViews` prop opts in. Renders * ahead of {@link columnMenu} so every adapter's toolbar reads * Filters · Saved views · Columns · Export CSV. */ savedViewsMenu?: ReactNode; /** Built column-menu node, when `enableColumnMenu` is set. */ columnMenu?: ReactNode; /** * When set, render the Export CSV toolbar button and call this on click. * Built by {@link makeExportCsvHandler} from the `exportCsv` prop. */ onExportCsv?: () => void; /** * True while a host-handled export (`exportCsv.request`) is still running. * * Adapters disable the Export button and mark it busy, so the same export * cannot be started twice and the user can see that something is happening. * Always false for the built-in browser export, which is synchronous. */ exportBusy?: boolean; /** * What the last export did — `"idle"`, `"busy"`, `"done"` or `"failed"` — * for a kit whose button shows more than a spinner. */ exportStatus?: ExportStatus; /** * Live-region text for the last export's outcome, empty until there is one. * Adapters render it through `ExportAnnouncer` beside the button: a download * is silent, so without it a screen-reader user cannot tell a finished export * from a failed one. */ exportAnnouncement?: string; /** * The export button's caption, naming the format it produces — CSV by * default, the writer's format otherwise, localized either way. Adapters * render this rather than `labels.exportCsv`, so a button never names a file * the user is not getting. */ exportLabel?: string; /** * When set, render an Add-row control and call this on click. Present iff * the host wired `onAddRow`, so the toolbar needs no second guard. */ onAddRow?: () => void; /** The Add control's caption, already localized. */ addRowLabel?: string; /** Text direction, for adapters whose toolbar needs explicit RTL hints. */ dir?: "ltr" | "rtl"; } /** * The shared prop surface every adapter's bulk-action bar needs. Extracted * so the identical shape isn't re-declared (and flagged as duplication) in * each adapter's chrome. */ interface BulkBarChromeProps { /** Current selection state. */ selection: SelectionState; /** * Total rows in the filtered set — drives the "select all N matching" * banner when a full page is selected and more rows match elsewhere. */ total: number; /** Caller-supplied bulk actions. */ bulkActions: BulkAction[]; /** Confirmation handler for actions that declare a `confirm` block. */ confirm: ConfirmHandler; /** Resolved labels. */ labels: Required; } /** * Which body region a `DataTable` should render. Named `TableBodyRegion` * (not `TableBody`) so it never collides with MUI's `TableBody` component * in consumer imports. */ type TableBodyRegion = "skeleton" | "empty" | "mobile" | "desktop"; /** The shared, UI-agnostic orchestration result for an adapter table. */ interface TableChrome { /** * The source as the VIEW sees it. Identical to the caller's source — * except with grouping armed, where the table renders the full filtered * set and this facade presents that set (full rows, one page, matching * total) so footer numbers, select-all scope and page-scope CSV export * agree with the screen. Adapters read THIS, never the raw source. */ source: TableSource; /** The headless table state + prop-getters. */ table: UseDataTableResult; /** Resolved mobile layout flag. */ isMobile: boolean; /** Resolved confirmation handler. */ confirm: ConfirmHandler; /** Row id extractor (selection id, falling back to rowKey). */ getRowId: (row: TRow) => string; /** Derived chips: label-driven merged with caller `extraChips`. */ mergedChips: readonly ActiveFilterChip[]; /** Active filter count (override, or merged chip count). */ activeFilterCount: number; /** Whether the resolved pagination mode is `"paged"`. */ isPaged: boolean; /** * The table root. Owned here so the width that drives progressive column * hiding is measured on the table itself, in both wiring paths. */ rootRef: RefObject; /** Column keys progressive hiding gave up at the current width. */ droppedColumns: readonly string[]; /** Which body region to render. */ body: TableBodyRegion; /** * The load failure to show in place of the body, or `undefined` when the * source is fine. Derived here so every adapter offers a retry on exactly * the same terms — one the source can actually perform. */ errorState?: TableErrorState; /** * Why the body is empty: `"noResults"` when an active search/filter * produced zero rows (offer a clear-filters CTA), `"noData"` when the * source itself is empty. Only meaningful while `body === "empty"`. */ emptyVariant: "noData" | "noResults"; /** * A background refresh is in flight (`isFetching` without `isLoading`): * rows on screen are potentially stale. Adapters show a subtle, * non-blocking indicator (thin progress bar / `aria-busy`). */ isRefreshing: boolean; /** * Clear-filters handler: the caller's `onClearFilters`, falling back to * `source.clearExtras` — so chips, the drawer and the no-results CTA can * always offer a working "clear". */ clearFilters: () => void; /** * Row-detail bundle — present iff `renderRowDetail` is set, so ONE guard * narrows both the renderer and the expansion state (no correlated * optionals to re-check). */ detail?: { /** The caller's detail-panel renderer. */render: (row: TRow) => ReactNode; /** Expansion state for the chevrons. */ expansion: RowExpansionState; }; /** * Inline editing bundle — present iff either channel is set (`onCellEdit` * for per-cell commits, `rowEditing` + `onRowEdit` for row-level ones), so * ONE guard narrows the host channel, the state machine, validation, save * state, dirty marks and row mode. Pass neither and editing stays fully * dormant: no UI, no keyboard. */ editing?: EditableCellEditing; /** * Adding, duplicating and deleting rows. Always present: `canAdd` says * whether the toolbar's Add control renders, and the duplicate and delete * actions are already folded into {@link TableChrome.rowActions}, so an * adapter that renders row actions gets both for free. */ rowMutations: RowMutationsState; /** * The row actions to render — the host's, plus duplicate and delete when * those are wired, and `undefined` when the reader hid the actions column. * Adapters read THIS rather than the `rowActions` prop. */ rowActions?: RowAction[]; /** * Whether an actions column exists at all, hidden or not — what the column * menu offers, and the one figure a hidden column must not change. */ hasRowActions: boolean; /** * Whether a reorder column exists at all, hidden or not — what the column * menu offers. False when grouping or a tree is armed (reorder is refused). */ hasRowReorder: boolean; /** * Headless row-reorder state. Present iff the host passed `onRowReorder`, * grouping/tree are off, and the column is visible. Adapters read THIS. */ rowReorder?: RowReorderState; /** * Headless row-pin state. Present iff the host passed `pinnedRowIds` or * `onPinnedRowIdsChange`, and grouping/tree are off. */ rowPinning?: RowPinningState; /** * Tree bundle — present iff the host declared a hierarchy (`getChildren` or * `getParentId`). A tree and a grouping are different models and can both be * dormant; a table that arms both renders the tree, since the rows' own * shape outranks a derived one. */ tree?: { /** The flattened hierarchy, in render order. */entries: readonly TreeEntry[]; /** Which nodes are open. */ expansion: TreeExpansionState; /** Which column carries the chevron and the indent. */ columnKey?: string; }; /** * Row-grouping bundle — present iff an effective `groupBy` is set AND the * source can supply a full filtered set (`allFilteredRows`). Omit * `groupBy` and grouping stays fully dormant (package DNA: opt-in). */ grouping?: { /** The grouping keys in order — one entry for a flat group, more for nested. */groupBy: readonly string[]; collapsed: GroupCollapseState; aggregates?: GroupAggregatesFn; /** Flat group-header + leaf entries for adapters to render. */ entries: readonly GroupedFlatEntry[]; setGroupBy: (key: GroupByInput) => void; /** Open every group. */ expandAll: () => void; /** Close every group, at every level. */ collapseAll: () => void; /** * Show the tree down to `depth` and no further — `0` leaves only the * outermost headers, `1` opens the first level inside them. */ collapseToDepth: (depth: number) => void; /** Reveal the next page of groups, or of one group's rows. */ showMore: (entry: { scope: "groups" | "rows"; groupKey?: string; }) => void; }; /** * The rows the editing layer must treat as present: the grouped leaf set * (in render order) while grouping renders the full filtered set, the * page slice otherwise. Adapters pass THIS — never `source.rows` — as the * `rows` context for editable cells, so an edit on a row outside the * current page slice survives and Tab-advance follows the rendered order. */ editingRows: readonly TRow[]; /** Whether the paged footer should render. */ showFooter: boolean; /** User column-layout state + mutators (visibility, order, …). */ columnLayout: UseColumnLayoutResult; /** Tree groups for the declared columns — collapse options, header align. */ columnGroups: ReadonlyMap>; /** All declared columns (pre layout/device filtering) for the column menu. */ allColumns: ColumnDef[]; /** * Opted-in features that cannot run. Empty when everything the host * asked for can. Adapters show these on the status bar (when it is * on) and as `data-adapttable-notices` on the root; the matching * control already looks off, disabled, or one-page. */ featureNotices: readonly FeatureNotice[]; } /** * Run the shared orchestration every adapter `` needs: resolve * the layout + confirm handler, build the headless table, merge filter * chips, compute the active-filter count, and decide which body region and * footer to show. Adapters then render their kit-specific markup from this. * * @typeParam TRow - The row type. * @param props - The adapter's {@link BaseDataTableProps}. * @returns The {@link TableChrome} orchestration result. */ /** * The undo/redo half of a toolbar's props, or nothing at all. * * Two conditions have to hold — the host asked for the buttons, and there * is a history for them to drive — and resolving both here means an * adapter renders the pair on `onUndo` being present and never has to * know that `editHistory` exists. Off, the object is empty and the props * are absent, which is what keeps an opted-out toolbar identical. */ /** * The density chooser and the fullscreen toggle, or nothing. * * Both resolve to present-or-absent rather than present-and-disabled, so an * adapter renders on presence. The fullscreen half folds in whether the * browser will allow it at all: a toggle that cannot work is worse than no * toggle, and an embedded webview is a real place where it cannot. */ interface ViewControlsToolbar { density?: "comfortable" | "compact"; onDensityChange?: (next: "comfortable" | "compact") => void; onToggleFullscreen?: () => void; isFullscreen?: boolean; } declare function viewControlsToolbar(props: { densityChooser?: boolean; density?: "comfortable" | "compact"; onDensityChange?: (next: "comfortable" | "compact") => void; fullscreen?: boolean; }, fullscreen: { supported: boolean; active: boolean; toggle: () => void; }): ViewControlsToolbar; declare function undoRedoToolbar(wanted: boolean | undefined, history: EditHistoryState, labels: TableLabels): Partial>; /** The print button's half of a toolbar's props. */ interface PrintToolbar { onPrint?: () => void; printLabel?: string; } /** * The print button's half of a toolbar's props, or nothing at all. * * Two conditions again — the host asked for the button, and there is a handler * for it to call — resolved here so an adapter renders on `onPrint` being * present. `onPrint` alone stays what it has always been: a palette command. * * Not generic, unlike {@link undoRedoToolbar}: neither prop mentions the row * type, and a `Partial>` return with no `TRow` in the * arguments infers `unknown` and widens the whole spread at every call site. */ declare function printToolbar(wanted: boolean | undefined, onPrint: (() => void) | undefined, labels: TableLabels): PrintToolbar; declare function useTableChrome(props: BaseDataTableProps): TableChrome; /** Result of {@link useChromeBodyData}. */ interface ChromeBodyData { /** Row/card window virtualization state (disabled unless eligible). */ virtualization: TableVirtualization; /** * When grouping is armed, the (possibly virtual-windowed) flat entries * adapters should render. `undefined` when grouping is dormant. */ groupingEntries?: readonly GroupedFlatEntry[]; /** * When a tree is armed, the (possibly virtual-windowed) entries adapters * should render. `undefined` when the table is flat. */ treeEntries?: readonly TreeEntry[]; /** Sentinel ref that auto-loads the next page in infinite mode. */ loadMoreRef: RefObject; /** Whether the load-more affordance applies (infinite mode, no error). */ canLoadMore: boolean; /** * Attach to the `maxHeight` scroll box (when one renders) so the virtual * window tracks the box's scrolling instead of the page's. Harmless to * attach when virtualization is off. */ virtualScrollRef: RefCallback; /** Top-pinned rows, removed from the virtual window. */ pinnedTopRows: readonly TRow[]; /** Bottom-pinned rows, removed from the virtual window. */ pinnedBottomRows: readonly TRow[]; } /** * The shared data-flow wiring between {@link useTableChrome} and an * adapter's body: window virtualization (eligible only for real rows in * infinite mode) and the infinite-scroll sentinel. Extracted because four * adapters repeated this block verbatim; antd opts out (it scrolls inside * its own `
` container). * * @typeParam TRow - The row type. * @param chrome - The {@link useTableChrome} result. * @param props - The adapter's {@link BaseDataTableProps}. * @returns Virtualization state + the load-more sentinel. */ declare function useChromeBodyData(chrome: TableChrome, props: BaseDataTableProps): ChromeBodyData; /** * The shared scroll-restoration wiring every adapter `` needs: * when search / sort / page / filters change, scroll the table back below * the sticky chrome. Extracted so the identical block isn't repeated (and * flagged as duplication) in each adapter. * * @typeParam TRow - The row type. * @param ref - The adapter's root element. * @param chrome - The {@link useTableChrome} result. * @param props - The adapter's {@link BaseDataTableProps}. */ declare function useChromeScrollReset(ref: RefObject, chrome: TableChrome, props: BaseDataTableProps): void; /** Pointer/click handlers returned by {@link useFilterTriggerToggle}. */ interface FilterTriggerToggle { onPointerDown: () => void; onClick: () => void; } declare function useFilterTriggerToggle(open: boolean, setOpen: (next: boolean | ((current: boolean) => boolean)) => void): FilterTriggerToggle; //#endregion //#region src/utils/humanizeKey.d.ts /** * Default column-header text from a key: the last dot-path segment, split on * camelCase / snake_case / kebab-case boundaries and title-cased. * `"hiredAt"` → `"Hired At"`, `"department.name"` → `"Name"`, * `"first_name"` → `"First Name"`. An empty/undefined `key` returns `""` so a * transiently-malformed column key can never crash a render. */ declare function humanizeKey(key: string): string; //#endregion //#region src/utils/localeTag.d.ts /** * ONE locale-tag resolution for everything locale-shaped: label presets * (`@adapttable/i18n`) and per-column `i18n` data paths resolve through * the same rules, so `locale="ar_EG"` can never pick Arabic labels while * missing the Arabic column paths. */ /** Normalize a BCP-47-ish tag: trim, `_` → `-`, lower-case. */ declare function normalizeLocaleTag(locale: string): string; /** * Resolve a locale against a set of available tags: the exact tag first * (case- and separator-insensitive), then its primary subtag. Returns the * ORIGINAL available tag so callers can index their own maps with it. */ declare function resolveLocaleTag(available: Iterable, locale: string): string | undefined; //#endregion //#region src/utils/path.d.ts /** * Safe dot-path lookup: `getPath(row, "department.name")`. Returns * `undefined` for any missing segment instead of throwing, so declarative * column keys can reach nested API payloads without optional-chaining * ceremony in user code. Also tolerates an empty/undefined `path` (returns * `undefined`) so a transiently-malformed column key can never crash a render. */ declare function getPath(value: unknown, path: string): unknown; //#endregion //#region src/url/routerAdapter.d.ts /** What {@link routerUrlAdapter} needs from the router. */ interface RouterUrlAdapterOptions { /** * The current query string, WITHOUT the leading `"?"`. Must come from the * router's own reactive source — `useSearchParams().toString()`, * `location.search.slice(1)` — so it changes when the route does. */ search: string; /** * Go to a new query string. `push` adds a history entry; the default is a * replace, because a table's every keystroke is not a page a user wants to * walk back through. */ navigate: (search: string, options: { push: boolean; }) => void; } /** * Build a `UrlStateAdapter` from a router's search string and navigate. * * Memoize it on `search` — the adapter is a value, and rebuilding it is how * the table learns the route changed. * * @example React Router * ```tsx * const [params] = useSearchParams(); * const navigate = useNavigate(); * const adapter = useMemo( * () => * routerUrlAdapter({ * search: params.toString(), * navigate: (search, { push }) => * navigate({ search }, { replace: !push }), * }), * [params, navigate] * ); * ``` * * @example Next.js App Router * ```tsx * const searchParams = useSearchParams(); * const pathname = usePathname(); * const router = useRouter(); * const adapter = useMemo( * () => * routerUrlAdapter({ * search: searchParams.toString(), * navigate: (search, { push }) => { * const url = search ? `${pathname}?${search}` : pathname; * (push ? router.push : router.replace)(url, { scroll: false }); * }, * }), * [searchParams, pathname, router] * ); * ``` * * @param options - The router's current search, and how to navigate. * @returns An adapter to hand the table as `urlAdapter`. */ declare function routerUrlAdapter({ search, navigate }: RouterUrlAdapterOptions): UrlStateAdapter; //#endregion //#region src/url/useColumnLayoutUrlState.d.ts /** Options for {@link useColumnLayoutUrlState}. */ interface UseColumnLayoutUrlStateOptions { /** URL-state backend. Defaults to the browser History API. */ urlAdapter?: UrlStateAdapter; /** When `false`, keep the layout in a local memory store. Defaults `true`. */ urlSync?: boolean; /** Layout applied when the URL carries no column layout yet. */ defaultColumnLayout?: Partial; /** * Namespace for this table's params, so multiple tables can share one URL * (`left.colHide`, `right.colPin`, …). Omit for the bare keys. */ urlKey?: string; } /** State + change handler returned by {@link useColumnLayoutUrlState}. */ interface UseColumnLayoutUrlStateResult { /** Current layout — from the URL, or the default when the URL is empty. */ layout: ColumnLayoutState; /** Persist a new layout into the URL. Wire to `onColumnLayoutChange`. */ onLayoutChange: (next: ColumnLayoutState) => void; } /** * Headless URL-synced column layout. Mirrors {@link useTableUrlState} for the * column dimension: which columns are hidden, pinned, reordered, or resized is * kept in the query string (or a local store when disabled), so reloads, * shared links, and re-mounts restore the exact layout. Feed the result into * a table's `columnLayout` / `onColumnLayoutChange`. * * `defaultColumnLayout` applies only while the URL carries no layout. When the user * explicitly empties the layout (e.g. unhides the last default-hidden * column), an empty `colHide=` marker records that emptiness — deleting every * param would resurrect the default on the next read. A change back to the * exact default clears the params instead, keeping shared URLs clean. * * @param options - See {@link UseColumnLayoutUrlStateOptions}. * @returns The current layout and a change handler that persists it. */ declare function useColumnLayoutUrlState(options?: UseColumnLayoutUrlStateOptions): UseColumnLayoutUrlStateResult; //#endregion //#region src/source/useFrontendData.d.ts /** Options for {@link useFrontendData}. */ interface UseFrontendDataOptions extends Pick { /** The source array. Filtered / sorted / sliced internally by state. */ data: readonly TRow[]; /** * How a row's id is derived — the same function {@link applyRowPatches} * used. Defaults to `String(row.id)` when the row has an `id`. */ getRowId?: (row: TRow) => string; /** * Project a row to its searchable text. Defaults to a flatten of the * row's own values; override to reach nested fields. */ getSearchText?: (row: TRow) => string; /** * Resolve a row's sort value for a column key. Falls back to the * matching column's `sortValue`. */ getSortValue?: (row: TRow, columnKey: string) => SortableValue; /** Columns — read for per-column `sortValue` when sorting. */ columns?: readonly ColumnDef[]; /** * Client-side filter predicate applied after search. Receives the active * `extra` filter bag (driven by the filter drawer's `setExtra` calls), so a * filter UI filters the rows with no extra wiring. Omit for no filtering. */ filterFn?: (row: TRow, extra: ExtraFilters) => boolean; /** * Evaluate the URL's AND/OR filter tree against a row. Omit and the * tree is stored but not applied (server tiers send it instead). */ filterTreeFn?: (row: TRow, tree: QueryFilterGroup) => boolean; /** Pagination mode. Defaults to `"auto"` (mobile → infinite). */ paginationMode?: PaginationMode; /** Forwarded error to display (e.g. from a query that produced `data`). */ error?: Error | null; /** Forwarded refetch. */ refetch?: () => Promise | void; /** Forwarded fetching flag. */ isFetching?: boolean; /** Forwarded loading flag. */ isLoading?: boolean; /** * Force the resolved mobile state instead of using a media query. * Primarily a testing/SSR seam. */ forceMobile?: boolean; } /** Default searchable-text projector: flatten a row's own values. */ declare function defaultSearchText(row: TRow): string; /** Default row id: `String(row.id)` when the row has a string/number id. */ declare function defaultFrontendRowId(row: TRow): string; /** * In-memory {@link TableSource}: reads URL/local state and filters, sorts, * and slices a caller-supplied array. The mirror of `useQuerySource` — * the table cannot tell which produced it. * * A {@link rowPatchLog} on `data` continues the live {@link IncrementalView} * so only touched rows re-run search, filters and sort. Spreading the * patched array drops the log and falls back to a full rebuild. * * @typeParam TRow - The row item type. * @param options - See {@link UseFrontendDataOptions}. * @returns A {@link TableSource} over the in-memory data. */ declare function useFrontendData(options: UseFrontendDataOptions): TableSource; //#endregion //#region src/source/useQuerySource.d.ts /** * The minimal shape `useQuerySource` reads from a `useInfiniteQuery` * result. Declared structurally so `@tanstack/react-query` stays an * optional, type-only peer dependency (no runtime import). * * @typeParam TPage - The page type returned by each fetch. */ interface InfiniteQueryLike { data: { pages: TPage[]; pageParams: unknown[]; } | undefined; isLoading: boolean; isFetching: boolean; isFetchingNextPage: boolean; hasNextPage: boolean; fetchNextPage: () => Promise | void; refetch: () => Promise | void; error: Error | null; } /** Project a fetched page to its rows (and optional total). */ type PageSelector = (page: TPage) => { rows: readonly TRow[]; total?: number; facets?: FacetMap; }; /** Options for {@link useQuerySource}. */ interface UseQuerySourceOptions extends Pick { /** * The caller's paginated-query hook, built on `useInfiniteQuery`. It * receives the merged params and must return an {@link InfiniteQueryLike}. */ usePaginatedQuery: (params: Partial) => InfiniteQueryLike; /** Page → `{ items, total }` selector. Defaults to reading {@link PaginatedResponse}. */ selectPage?: PageSelector; /** * Static params merged into every query call (e.g. a parent scope id). * The live table state always wins on collision: `page`, `limit`, * `search`, `sortBy`, `sortDir`, `groupBy` and `filters` come from the * table itself and can never be overridden here — seed state through * `defaults` instead. */ baseParams?: Partial; /** Pagination mode. Defaults to `"auto"` (mobile → infinite). */ paginationMode?: PaginationMode; /** Final scrubber on the merged params before they reach the query. */ sanitizeParams?: (params: Partial) => Partial; /** Force the resolved mobile state instead of a media query (test/SSR seam). */ forceMobile?: boolean; /** * What the endpoint can answer, exactly as {@link useServerData} takes it. * Only a declared capability is ever sent; an undeclared one is dropped * before the request rather than sent and ignored. */ supports?: QuerySupport; /** * Aggregates to ask the server for — `[{ key: "budget", fn: "sum" }]`. * * Sent only when the source declares `supports.aggregates`. With grouping * armed the server computes them per group; without it, over the whole * result set. */ aggregates?: readonly QueryAggregate[]; /** * The tree nodes the reader has open, when the hierarchy lives on the server. * Sent as `expandedIds` only if the source declares * `supports: { tree: true }`, so the response can carry the children of every * open branch alongside the page. Hold the same array in the table's * `expandedIds` and one piece of state drives both. */ expandedIds?: readonly string[]; /** * The token that opens the NEXT page, read from the page the query just * returned — pass it and declare `supports: { cursor: true }` to page by * cursor instead of by offset. * * Rows inserted or deleted mid-read shift every offset after them, which is * how an offset pager duplicates or skips rows; a cursor names a position in * the result rather than a distance into it. */ nextCursor?: (page: TPage) => string | null | undefined; /** * Filter keys to ask the server for distinct-value counts. Sent as * `query.facets` only when `supports.facets` is set. */ facetKeys?: readonly string[]; } /** * Server-paginated {@link TableSource}. Wraps a caller's * `useInfiniteQuery` hook and exposes the uniform contract: flattening * pages in infinite mode, returning the latest page in paged mode, and * keeping query params in sync with URL state. * * @returns A {@link TableSource} backed by the server query. */ declare function useQuerySource>(options: UseQuerySourceOptions): TableSource; //#endregion //#region src/filters/countFilters.d.ts /** Numeric comparison operators for count/usage filters. */ declare const COUNT_OPERATORS: readonly ["eq", "gte", "lte", "gt", "lt", "between"]; /** Numeric comparison operator. */ type CountOperator = (typeof COUNT_OPERATORS)[number]; /** State for one operator-driven count filter. */ interface CountFilterState { op?: CountOperator; value?: number; from?: number; to?: number; } /** Symbols used in compact chip labels. */ declare const COUNT_OPERATOR_SYMBOL: Record; /** Whether a count-filter state is complete enough to affect a query. */ declare function isCountFilterComplete(state: CountFilterState): boolean; /** Convert a state update to URL-extra values for one bucket. */ declare function countFilterExtra(bucket: string, state: CountFilterState): ExtraFilters; /** URL-extra update that clears every value for one bucket. */ declare function clearCountFilterExtra(bucket: string): ExtraFilters; /** Rehydrate one bucket's count-filter state from an extra-filter bag. */ declare function countFilterStateFromExtra(bucket: string, extra: Readonly>): CountFilterState; /** * Remove incomplete count filters from backend params while preserving any * unrelated params. This lets a UI keep partial state in the URL without * sending invalid operator/value pairs to an API. */ declare function sanitizeCountFilterParams

>(params: P, buckets: readonly string[]): P; /** Build a compact chip label for a complete count filter. */ declare function countFilterChipLabel(label: string, state: CountFilterState): string | undefined; //#endregion //#region src/filters/useExtraChips.d.ts /** Options for {@link useExtraChips}. */ interface UseExtraChipsOptions { /** A source's `extra` bag. */ readonly extra: ExtraFilters; /** A source's `setExtra` setter. */ readonly setExtra: (key: string, value: FilterValue) => void; /** * Map of filter key → label resolver. Only keys present here become * chips. Memoise this on the caller side when the resolver closes over * `t`/lookup data so the chip list stays stable across renders. */ readonly labels: Readonly>; } /** * Convenience wrapper over {@link useActiveFilterChips} that reads a * source's `extra` bag, applies the label resolvers, and wires each * chip's removal back to `setExtra`. The caller only declares the labels. * * @param options - See {@link UseExtraChipsOptions}. * @returns The derived chips. */ declare function useExtraChips({ extra, setExtra, labels }: UseExtraChipsOptions): ActiveFilterChip[]; //#endregion //#region src/sort/cycleSort.d.ts /** * Advance the three-step sort cycle for a column header click: * inactive → ascending → descending → cleared. * * @param current - The current sort pair (both fields unset when inactive). * @param key - The column key that was clicked. * @returns The next sort state. */ declare function nextSort(current: Partial, key: string): Partial; //#endregion //#region src/columns/autoSizeColumns.d.ts /** * The width a column needs for its widest rendered cell. * * Cells are found by the `data-column-key` every adapter's cells carry, so this * needs no per-kit knowledge and works the same in a table of divs. * * @param root - The table element (or any ancestor of its cells). * @param key - The column key to measure. * @returns The width in pixels, clamped to the resize bounds, or `null` when * the column has no cells on screen to measure. */ declare function measureColumnWidth(root: Element | null, key: string): number | null; /** * Size every rendered column to its content. * * @param root - The table element. * @param keys - The columns to size, in any order. * @param setWidth - The layout mutator that persists each width. * @returns How many columns were sized — zero when nothing was measurable. */ declare function autoSizeColumns(root: Element | null, keys: readonly string[], setWidth: (key: string, width: number) => void): number; //#endregion //#region src/columns/columnHeader.d.ts /** Default header caption: the explicit header, else a humanized key. */ declare function columnHeaderLabel(column: ColumnDef): ReactNode; /** Build the controller a custom header receives. */ declare function columnHeaderController(column: ColumnDef, extras?: { sortDir?: "asc" | "desc"; sortIndex?: number; toggleSort?: (event?: { shiftKey?: boolean; }) => void; }): ColumnHeaderController; /** Custom `renderHeader`, or the default caption. */ declare function resolveColumnHeader(column: ColumnDef, controller: ColumnHeaderController): ReactNode; /** Custom `renderFooter`, or the summary value as-is. */ declare function resolveColumnFooter(column: ColumnDef, value: ReactNode): ReactNode; /** True when any column wants a footer cell of its own. */ declare function columnsHaveFooter(columns: readonly ColumnDef[]): boolean; //#endregion //#region src/columns/visibleColumns.d.ts /** Which layout a table is rendering in. */ type TableLayout = "desktop" | "mobile"; /** * Resolve the columns visible for a layout. * * - Desktop: drops `hideOnDesktop` columns. * - Mobile: drops `hideOnMobile` columns, but the first three declared * desktop-visible columns WITHOUT an explicit `hideOnMobile` surface so * every card keeps a minimum identity block — an explicit hide always * wins over the identity default. Mobile-only columns (`hideOnDesktop` * without `hideOnMobile`) render here — they exist precisely for the * card layout. * * @typeParam TRow - The row type. * @param columns - All declared columns. * @param layout - The current layout. * @returns The columns to render, in declared order. */ declare function visibleColumns(columns: readonly ColumnDef[], layout: TableLayout, mobileIdentityColumns?: number): ColumnDef[]; //#endregion //#region src/layout/useHorizontalOverflow.d.ts /** Result of {@link useHorizontalOverflow}. */ interface HorizontalOverflow { /** Callback ref for the wrapper element to measure. */ ref: (node: E | null) => void; /** True while the wrapper's content is wider than the wrapper. */ overflowing: boolean; } /** * Whether an element's content overflows it horizontally, kept current via * `ResizeObserver`. Adapters use it to turn the table wrapper into a * horizontal scroller ONLY when the table is actually wider than its * container — an unconditional `overflow-x: auto` would make the wrapper a * scroll container and trap page-scroll sticky headers even when nothing * overflows. Under SSR (no `ResizeObserver`) it stays `false`. */ declare function useHorizontalOverflow(): HorizontalOverflow; //#endregion //#region src/hooks/useColorScheme.d.ts /** * Resolve a color-scheme preference to a concrete `"light"` or `"dark"`. * `"auto"` follows the OS via `prefers-color-scheme`; an explicit * preference is returned unchanged. Adapters map the result to their * theming (Mantine/MUI/Chakra color schemes, or a `data-theme` attribute * + CSS variables for the unstyled adapter). * * @param preference - `"light" | "dark" | "auto"`. Defaults to `"auto"`. * @returns The resolved scheme, `"light"` or `"dark"`. */ declare function useColorScheme(preference?: ColorScheme): "light" | "dark"; //#endregion //#region src/hooks/useDebounce.d.ts /** * Debounce a rapidly-changing value. The returned value only updates * after `delay` ms have elapsed without a new change. * * @typeParam T - The value type. * @param value - The source value. * @param delay - Debounce delay in milliseconds. Defaults to 300. * @returns The debounced value. */ declare function useDebounce(value: T, delay?: number): T; //#endregion //#region src/hooks/useInfiniteScroll.d.ts /** Options for {@link useInfiniteScroll}. */ interface UseInfiniteScrollOptions { /** Whether more pages remain to be fetched. */ hasNextPage: boolean; /** Whether a page fetch is currently in flight. */ isFetchingNextPage: boolean; /** Loads the next page; called when the sentinel scrolls into view. */ fetchNextPage: () => void; /** * Master switch. When `false` the observer is never attached (e.g. in * paged mode, or when the consumer wants explicit "Load more" only). * @defaultValue true */ enabled?: boolean; /** * `IntersectionObserver` root margin — how far before the sentinel enters * the viewport the next page is prefetched. * @defaultValue "200px" */ rootMargin?: string; /** * The current number of rendered rows. Pass `source.rows.length` so the * observer re-arms after each page loads: when freshly-loaded content is * still shorter than the viewport the sentinel stays in view, and * re-observing re-fires the callback to keep loading until the viewport * fills or there is no next page. Omit to disable this auto-continue. */ itemCount?: number; } /** * Auto-loads the next page when a sentinel element scrolls near the viewport, * turning a paginated {@link TableSource} into true infinite scroll. Attach * the returned ref to a small element rendered after the last row. * * SSR- and jsdom-safe: when `IntersectionObserver` is unavailable it no-ops, * so an accompanying "Load more" button remains the fallback. The latest * `fetchNextPage` is read from a ref, so passing a fresh closure each render * never re-subscribes the observer. * * @typeParam TElement - The sentinel element type. * @param options - See {@link UseInfiniteScrollOptions}. * @returns A ref to attach to the sentinel element. */ declare function useInfiniteScroll(options: UseInfiniteScrollOptions): RefObject; //#endregion //#region src/hooks/useIsMobile.d.ts /** * Whether the viewport is at or below the mobile breakpoint. * * The default is 768px — a phone in portrait. Raise it when the table lives * in a sidebar or a split pane, where the viewport says "desktop" but the * table has a phone's width to work with; lower it when the table is the * whole page and the columns are narrow enough to survive. * * @param px - The breakpoint in pixels. Defaults to 768. * @returns `true` on viewports at or below it. */ declare function useIsMobile(px?: number): boolean; //#endregion //#region src/hooks/useMediaQuery.d.ts /** * SSR-safe `matchMedia` hook built on `useSyncExternalStore`. Returns * `false` on the server and before hydration, then the live match. * * @param query - A CSS media query string, e.g. `"(max-width: 768px)"`. * @param defaultValue - Value used when `matchMedia` is unavailable (SSR). * @returns Whether the query currently matches. */ declare function useMediaQuery(query: string, defaultValue?: boolean): boolean; //#endregion //#region src/hooks/usePrefersReducedMotion.d.ts /** * Whether the user has requested reduced motion. Animation hooks gate on * this so AdaptTable never animates against an accessibility preference. * * @returns `true` when the user prefers reduced motion. */ declare function usePrefersReducedMotion(): boolean; //#endregion //#region src/hooks/useScrollToTableTop.d.ts /** Options for {@link useScrollToTableTop}. */ interface UseScrollToTableTopOptions { /** Table/container element to bring back below sticky chrome. */ ref: RefObject; /** Dependency values that represent table view changes. */ deps: readonly unknown[]; /** Master switch. Defaults to true. */ enabled?: boolean; /** Sticky top offset in px. Defaults to 0. */ offset?: number; /** Extra breathing room below the sticky chrome. Defaults to 8px. */ gap?: number; /** Scroll behavior. Defaults to smooth. */ behavior?: ScrollBehavior; } /** * Scroll the table back below sticky chrome after search/filter/page changes. * The initial render is skipped so deep links and restored browser positions * are not disturbed on first paint. */ declare function useScrollToTableTop({ ref, deps, enabled, offset, gap, behavior }: UseScrollToTableTopOptions): void; //#endregion //#region src/utils/stableKey.d.ts /** * Produce a deterministic string from any JSON-serialisable value so it * can be embedded in a query cache key (e.g. TanStack Query) without * changing identity on every render. * * Guarantees: * - Object keys are sorted, so `{ a, b }` and `{ b, a }` serialise * identically. * - Nested objects are normalised recursively. * - Array order is preserved (order is meaningful for lists). * - `undefined` values are dropped, mirroring how query strings and * `JSON.stringify` already treat them. * * @param input - Any value. Functions/symbols are not supported and will * serialise the way `JSON.stringify` handles them. * @returns A stable JSON string for the normalised input. * * @example * ```ts * stableKey({ b: 1, a: 2 }) === stableKey({ a: 2, b: 1 }); // true * ``` */ declare function stableKey(input: unknown): string; //#endregion //#region src/rows/patch.d.ts /** * Row patches — changing the data you already have, without refetching it. * * A save returns the updated record, a socket pushes a new one, a delete * succeeds. Refetching the page to reflect that costs a round trip and, worse, * throws away everything the user had going: the scroll position, which rows * were open, sometimes the selection. * * ```ts * const [rows, setRows] = useState(initial); * const onSaved = (row: Person) => * setRows((current) => applyRowPatches(current, [updateRow(row.id, row)], byId)); * ``` * * Two properties make that safe, and both are tested: * * - **Untouched rows keep their object identity.** React reconciles them as * unchanged, and anything memoized per row — a `computed` column's cache, * a `memo`'d cell — stays valid instead of recomputing for the whole page. * - **A patch that changes nothing returns the very same array.** Applying an * update whose values already match, or removing an id that is not there, * hands back the original reference, so a `setState` with it does not * re-render. * * Selection and expansion survive because both are keyed by row id, and a * patch never changes the id of a row it did not touch. * * This is a pure function over an array. The table never owns your data and * this does not make it start: you hold the rows, you apply the patch. */ /** Insert a row. Without `at`, it goes on the end. */ interface InsertPatch { type: "insert"; row: TRow; /** Zero-based position. Clamped into range; negative counts from the end. */ at?: number; } /** Merge changes into the row with this id. Absent id: nothing happens. */ interface UpdatePatch { type: "update"; id: string; changes: Partial; } /** Replace the row with this id, or append it when it is not there yet. */ interface UpsertPatch { type: "upsert"; row: TRow; } /** Drop the row with this id. Absent id: nothing happens. */ interface RemovePatch { type: "remove"; id: string; } /** One change to a row set. */ type RowPatch = InsertPatch | UpdatePatch | UpsertPatch | RemovePatch; /** Insert a row, optionally at a position. */ declare function insertRow(row: TRow, at?: number): InsertPatch; /** Merge changes into one row. */ declare function updateRow(id: string, changes: Partial): UpdatePatch; /** Replace a row, or add it if it is new. */ declare function upsertRow(row: TRow): UpsertPatch; /** Remove a row by id. */ declare function removeRow(id: string): RemovePatch; /** * One mutation {@link applyRowPatchesWithLog} actually performed. * * Incremental re-evaluation walks this list instead of scanning the row set * to find what changed. Indices are taken at the moment the event ran, so a * later event sees the array the earlier one left behind. */ type RowPatchEvent = { type: "insert"; id: string; row: TRow; index: number; } | { type: "remove"; id: string; row: TRow; index: number; } | { type: "update"; id: string; prev: TRow; next: TRow; index: number; }; /** * The result of applying patches, plus the events an incremental view needs * so it does not have to diff two 20k-row arrays to find one update. */ interface RowPatchLog { /** The row set after the patches — same contract as {@link applyRowPatches}. */ rows: readonly TRow[]; /** Empty when nothing changed; the original array is then on `rows`. */ events: readonly RowPatchEvent[]; } /** * The log {@link applyRowPatches} attached to a result array, when the * patches actually changed something. A host that already called * `applyRowPatches` (the Scale demo, a socket handler) can hand this to * the incremental view instead of applying the same patches twice. * * Spreading the result (`[...applyRowPatches(...)]`) drops the log — the * copy is a different array. * * @typeParam TRow - The row type. * @param rows - An array returned by {@link applyRowPatches}. * @returns The log, or `undefined` when this array was not produced by a * changing patch (or was copied). */ declare function rowPatchLog(rows: readonly TRow[]): RowPatchLog | undefined; /** * Apply patches to a row set, in order, and return the result. * * Returns the original array — the same reference — when no patch changed * anything. Rows that no patch touched keep their object identity. * * @typeParam TRow - The row type. * @param rows - The current rows. * @param patches - The changes to apply, in order. * @param getRowId - How a row's id is derived; the table's own `rowKey`. */ declare function applyRowPatches(rows: readonly TRow[], patches: readonly RowPatch[], getRowId: (row: TRow) => string): readonly TRow[]; /** * Apply patches and return the events each real mutation produced. * * {@link applyRowPatches} is this without the log. Incremental re-evaluation * sits on the log so a 200-update burst does not walk 20k rows looking for * what changed. * * @typeParam TRow - The row type. * @param rows - The current rows. * @param patches - The changes to apply, in order. * @param getRowId - How a row's id is derived; the table's own `rowKey`. */ declare function applyRowPatchesWithLog(rows: readonly TRow[], patches: readonly RowPatch[], getRowId: (row: TRow) => string): RowPatchLog; //#endregion //#region src/rows/incremental.d.ts /** How the table turns a row set into a filtered, sorted, grouped view. */ interface IncrementalViewConfig { /** How a row's id is derived; the table's own `rowKey`. */ getRowId: (row: TRow) => string; /** * Project a row to its searchable text. Defaults to a flatten of the * row's own values — the same default `useFrontendData` uses. */ getSearchText?: (row: TRow) => string; /** Active search term. Empty / omitted means no search. */ search?: string; /** Client-side extra-filter predicate. */ filterFn?: (row: TRow, extra: ExtraFilters) => boolean; /** The extra-filter bag `filterFn` reads. */ extra?: ExtraFilters; /** * Evaluate the AND/OR filter tree against a row. Omit and the tree is * stored but not applied — same seam as `useFrontendData`. */ filterTreeFn?: (row: TRow, tree: QueryFilterGroup) => boolean; /** The active filter tree, when there is one. */ filterTree?: QueryFilterGroup; /** Columns — sort and group values resolve through these. */ columns?: readonly ColumnDef[]; /** Override a column's sort value. */ getSortValue?: (row: TRow, columnKey: string) => SortableValue; /** Single-column sort. Ignored when `sortLevels` is non-empty. */ sortBy?: string; /** Single-column sort direction. */ sortDir?: SortDirection; /** Multi-column sort chain. Supersedes `sortBy` / `sortDir`. */ sortLevels?: readonly SortLevel[]; /** Group by one key, or several for a nested grouping. */ groupBy?: string | readonly string[]; /** Per-group cells — same signature as `summaryRow`. */ groupAggregates?: GroupAggregatesFn; /** Order groups within their parent. */ groupSort?: GroupSort; /** Keep only the groups this answers true for. */ groupFilter?: (group: GroupNode) => boolean; /** Close every group with a footer row. */ groupFooters?: boolean; /** Collapsed group keys. */ collapsedGroupIds?: ReadonlySet; /** Override the blank-group label. */ blankLabel?: string; /** Page size for top-level groups. */ groupPageSize?: number; /** Page size for leaves inside a group. */ rowPageSize?: number; /** How many extra groups / rows are currently revealed. */ paging?: GroupPaging; /** Grand-total mapper over the sorted (filtered) set. */ summaryRow?: (rows: readonly TRow[]) => Partial>; /** * Built-in aggregate spec for incremental totals. Used for the grand * total when `summaryRow` is omitted, and for group cells when * `groupAggregates` is omitted. */ aggregateSpec?: AggregateSpec; /** Options for {@link IncrementalViewConfig.aggregateSpec}. */ aggregateOptions?: AggregateOptions; } /** * A derived snapshot of a row set. The latest snapshot is the only one * {@link applyRowPatchesToView} may be called on — applying to a stale * snapshot rebuilds from its `rows` instead of continuing incrementally. */ interface IncrementalView { /** Source rows after patches — same contract as {@link applyRowPatches}. */ readonly rows: readonly TRow[]; /** After search, extra filters and the filter tree. */ readonly filtered: readonly TRow[]; /** After sort, or `filtered` when unsorted. */ readonly sorted: readonly TRow[]; /** Grouped flat model, when grouping is configured. */ readonly groups: readonly GroupedFlatEntry[] | undefined; /** Grand-total cells over `sorted`. */ readonly aggregates: Partial> | undefined; } /** * The snapshot {@link createIncrementalView} attached to a derived row * array (`rows` / `filtered` / `sorted`, or a page slice the host * attached). Spreading that array drops the link, same as * {@link rowPatchLog}. */ declare function incrementalViewOf(rows: readonly TRow[]): IncrementalView | undefined; /** * Point a derived array (a page slice) at the snapshot it came from, so * {@link incrementalViewOf} can find aggregates / groups without a * second argument. */ declare function attachIncrementalView(rows: readonly TRow[], view: IncrementalView): void; /** The config the snapshot was last built or reconfigured with. */ declare function incrementalViewConfig(view: IncrementalView): IncrementalViewConfig | undefined; /** * Merge new settings into a snapshot without walking the row set when * only grouping / summary extras changed. * * Filter, sort and search changes rebuild the derived arrays. Grouping * and summary extras rebuild only those stages and keep `filtered` / * `sorted` identity. A patch that only replaces a callback or a * `columns` array with the same keys returns the same view object — * hosts rebuild those every render, and a new view identity must not * ripple into the page slice. * * @typeParam TRow - The row type. * @param view - The latest snapshot. * @param patch - Fields to merge. `undefined` entries are ignored. */ declare function configureIncrementalView(view: IncrementalView, patch: Partial>): IncrementalView; /** * Default searchable-text projector: flatten a row's own values. Kept * here so this module does not import the React hook that publishes the * same helper on `useFrontendData`. */ declare function incrementalSearchText(row: TRow): string; /** * Build a snapshot by fully evaluating `rows`. Patches after this go * through {@link applyRowPatchesToView} so only touched rows are * re-evaluated. * * @typeParam TRow - The row type. * @param rows - The current source rows. * @param config - Filter / sort / group / aggregate settings. */ declare function createIncrementalView(rows: readonly TRow[], config: IncrementalViewConfig): IncrementalView; /** * Apply patches to a snapshot: {@link applyRowPatches} for the rows, then * incremental re-evaluation of every derived stage. * * @typeParam TRow - The row type. * @param view - The latest snapshot. * @param patches - The changes to apply, in order. */ declare function applyRowPatchesToView(view: IncrementalView, patches: readonly RowPatch[]): IncrementalView; /** * Continue a snapshot from a log {@link applyRowPatches} already produced. * Use this when the host applied the patches itself and the incremental * view must not apply them a second time. * * @typeParam TRow - The row type. * @param view - The snapshot taken against the pre-patch rows. * @param log - The log attached to the post-patch array. */ declare function applyRowPatchLogToView(view: IncrementalView, log: RowPatchLog): IncrementalView; //#endregion //#region src/tree/useLazyChildren.d.ts /** What {@link useLazyChildren} needs. */ interface UseLazyChildrenOptions { /** * Fetch a node's children. Resolve once they are in the data the table * reads — the table re-walks the tree from the rows it is given, so it needs * nothing back. */ onLoadChildren?: (row: TRow) => void | Promise; /** Whether a row's children are already in hand. */ hasLoadedChildren: (row: TRow) => boolean; /** Row identity. */ getRowId: (row: TRow) => string; } /** Lazy-loading state for a tree. */ interface LazyChildrenState { /** Nodes being fetched right now — what the chevron shows a spinner for. */ loadingIds: ReadonlySet; /** * Call before opening a node: fetches its children when they are missing. * Returns nothing — expansion is not blocked on the fetch, so the row opens * immediately and fills when the rows arrive. */ loadIfNeeded: (row: TRow) => void; /** Ids whose last fetch rejected, so a caller can offer a retry. */ failedIds: ReadonlySet; } /** * Track which nodes are fetching their children, and fetch on demand. * * @typeParam TRow - The row type. * @param options - See {@link UseLazyChildrenOptions}. * @returns The state; inert when no `onLoadChildren` is given. */ declare function useLazyChildren(options: UseLazyChildrenOptions): LazyChildrenState; //#endregion //#region src/editing/MultiSelectEditorChrome.d.ts /** One option's checkbox, rendered by the adapter with its kit's control. */ interface MultiSelectEditorCheckboxProps { /** The option's visible text. */ readonly label: ReactNode; /** The option's value — unique within the editor. */ readonly value: string; /** Whether the draft currently holds this value. */ readonly checked: boolean; /** Add or remove this value from the draft. */ readonly onToggle: () => void; /** * Present on the FIRST option only. Attach it to the kit's control so the * editor takes focus when the cell opens, exactly as a single-control editor * does through {@link EditableCellEditorCtrl.focusRef}. */ readonly focusRef?: (node: { focus: () => void; } | null) => void; /** * The editor's key handling — Enter commits, Escape cancels. It belongs on * the controls themselves rather than the group: a group is not an * interactive element, and keys arrive at whichever option has focus. */ readonly onKeyDown: (event: KeyboardEvent) => void; } /** Adapter-supplied controls for {@link MultiSelectEditorChrome}. */ interface MultiSelectEditorSlots { readonly Checkbox: (props: MultiSelectEditorCheckboxProps) => ReactNode; } /** Props for {@link MultiSelectEditorChrome}. */ interface MultiSelectEditorChromeProps { /** The active cell's editor controller. */ readonly ctrl: EditableCellEditorCtrl; /** Accessible name for the group — the table's edit label. */ readonly label: string; /** The adapter's key handling, already wired to `ctrl.onEditorKeyDown`. */ readonly onKeyDown: (event: KeyboardEvent) => void; readonly slots: MultiSelectEditorSlots; } /** * A group of kit checkboxes standing in for `