@jh-grid/jhgrid-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,1294 @@
1
+ // Theme
2
+
3
+ export interface GridTheme {
4
+ headerBg?: string;
5
+ headerText?: string;
6
+ headerBorder?: string;
7
+ rowEven?: string;
8
+ rowOdd?: string;
9
+ cellBorder?: string;
10
+ cellText?: string;
11
+ loadingText?: string;
12
+ /**
13
+ * Bar drawn in a cell whose row has not arrived yet, in place of leaving it blank. Defaults to
14
+ * `'#E4E7EB'`.
15
+ */
16
+ skeletonBar?: string;
17
+ /**
18
+ * The lighter band that sweeps across those bars, drawn over them so it reads as light passing
19
+ * rather than as a second thing on the row. Held still when the viewer has asked for reduced
20
+ * motion. Defaults to `'rgba(255,255,255,0.65)'`.
21
+ */
22
+ skeletonSheen?: string;
23
+ cellPadding?: number;
24
+ selectionColor?: string;
25
+ selectionFill?: string;
26
+ selRowBg?: string;
27
+ /**
28
+ * Wash painted over the row under the pointer. Defaults to a neutral `rgba(0,0,0,0.045)`, kept
29
+ * distinct from `selRowBg` so hover and selection stay tellable apart on the same row. A theme
30
+ * with a dark body should set a light value instead; a falsy value turns the highlight off.
31
+ */
32
+ hoverRowBg?: string | null;
33
+ scrollbarBg?: string;
34
+ scrollbarThumb?: string;
35
+ scrollbarRadius?:number;
36
+ frozenBorder?: string;
37
+ groupHeaderBg?: string;
38
+ groupHeaderText?:string;
39
+ footerBg?: string;
40
+ footerText?: string;
41
+ filterIconBg?: string;
42
+ sortIconBg?: string;
43
+ filterIconColor?: string;
44
+ sortIconColor?: string;
45
+ headerIconColor?: string;
46
+ /** Border color for the Excel-style double-line drawn on column headers adjacent to hidden columns. Defaults to `'#94A3B8'`. */
47
+ hiddenColIndicator?: string;
48
+ /** Background tint applied to editable cells when `editableCols` is not `'*'`. Defaults to `undefined` (no tint). */
49
+ editableCellBg?: string;
50
+ /** Background tint applied to readonly cells when `editableCols` is not `'*'`. Defaults to `'#F5F5F5'` (light gray). */
51
+ readonlyCellBg?: string;
52
+ /** `CellRenderers.image()` cell background while an image is loading. Defaults to `'#F1F5F9'`. */
53
+ imagePlaceholderBg?: string;
54
+ /** `CellRenderers.image()` cell background on load failure. Defaults to `'#FEE2E2'`. */
55
+ imageErrorBg?: string;
56
+ /** `CellRenderers.image()` "broken image" X glyph color. Defaults to `'#DC2626'`. */
57
+ imageErrorIcon?: string;
58
+ fontSize?: number;
59
+ /** Header cell font size in px. Defaults to `fontSize` when omitted. */
60
+ headerFontSize?: number;
61
+ fontFamily?: string;
62
+ /** BCP-47 tag (e.g. `'en-US'`, `'ko-KR'`) that `CellRenderers.number/date/currency` fall back to when a column doesn't pin its own `locale`. Set from {@link JHGridOptions.locale}. */
63
+ locale?: string;
64
+
65
+ // ── Cell state ───────────────────────────────────────────────────────────────────────────
66
+ /** Outline drawn around a cell whose value fails its column's `validation`. Defaults to `'#dc2626'`. */
67
+ invalidCellBorder?: string;
68
+
69
+ // ── Rows marked for deletion ─────────────────────────────────────────────────────────────
70
+ /** Wash over a row marked by `deleteRow()` but not yet committed. Defaults to `'rgba(239,68,68,0.10)'`. */
71
+ deletedRowFill?: string;
72
+ /** Strike-through line drawn across that row's text. Defaults to `'rgba(239,68,68,0.55)'`. */
73
+ deletedRowStrike?: string;
74
+
75
+ // ── Drag feedback ────────────────────────────────────────────────────────────────────────
76
+ /** Drop shadow under the ghost that follows the pointer during a column-header drag. Defaults to `'rgba(15,23,42,0.30)'`. */
77
+ dragGhostShadow?: string;
78
+ /** Fill of the band showing where a dragged column or row would land. Defaults to `'rgba(59,130,246,0.12)'`. */
79
+ dragIndicatorFill?: string;
80
+ /** The insertion line itself, at the edge of that band. Defaults to `'#3b82f6'`. */
81
+ dragIndicatorLine?: string;
82
+
83
+ /*
84
+ * ── DOM surfaces ─────────────────────────────────────────────────────────────────────────
85
+ * Everything below styles the parts of the grid that are real DOM rather than canvas — the
86
+ * filter panel, context menus, the column chooser, cell editors, and the pager. They are
87
+ * listed here because `theme` reaches them, but they are also bridged to CSS custom
88
+ * properties (`--jhg-overlay-bg`, `--jhg-pager-bg`, …; see `GRID_CLASSES`). Each is emitted
89
+ * as `var(--jhg-…, <theme value>)`: nothing in the library sets the variable, so the theme
90
+ * value normally wins and a consumer who declares the variable takes over. Style through
91
+ * whichever suits — the theme for a value known at construction, the variable for one a
92
+ * stylesheet decides.
93
+ */
94
+ /** Background of panels, menus and dialogs. Defaults to `'#FFFFFF'`. */
95
+ overlayBg?: string;
96
+ /** Their outer border. Defaults to `'#D0D0D0'`. */
97
+ overlayBorder?: string;
98
+ /** Header strip inside a panel or dialog. Defaults to `'#F5F5F5'`. */
99
+ overlayHeaderBg?: string;
100
+ /** Hairline between sections of a panel. Defaults to `'#E0E0E0'`. */
101
+ overlayDivider?: string;
102
+ /** Primary text on those surfaces. Defaults to `'#212121'`. */
103
+ overlayText?: string;
104
+ /** Secondary text — labels, counts. Defaults to `'#595959'`. */
105
+ overlayMutedText?: string;
106
+ /** Placeholder and hint text. Defaults to `'#909090'`. */
107
+ overlayHintText?: string;
108
+ /** Hover background for a row in a list, such as a filter checklist. Defaults to `'#F5F5F5'`. */
109
+ overlayHoverBg?: string;
110
+ /** Hover — and keyboard-focus — background for a menu item. Defaults to `'#F0F0F0'`. */
111
+ overlayItemHoverBg?: string;
112
+ /** Shadow under panels and dialogs. Defaults to `'0 4px 16px rgba(0,0,0,0.15)'`. */
113
+ overlayShadow?: string;
114
+ /** Shadow under context menus, which sit closer to the surface. Defaults to `'0 4px 12px rgba(0,0,0,0.15)'`. */
115
+ overlayMenuShadow?: string;
116
+ /** Text on an accent-filled control, such as the filter panel's apply button. Defaults to `'#FFFFFF'`. */
117
+ overlayAccentText?: string;
118
+
119
+ /** Pager bar background. Only rendered with `pagination` enabled. Defaults to `'#f8fafc'`. */
120
+ pagerBg?: string;
121
+ /** Pager bar text. Defaults to `'#1e293b'`. */
122
+ pagerText?: string;
123
+ /** Border between the pager bar and the grid. Defaults to `'#e2e8f0'`. */
124
+ pagerBorder?: string;
125
+ /** Page-button background. Defaults to `'#ffffff'`. */
126
+ pagerButtonBg?: string;
127
+ /** Page-button border. Defaults to `'#cbd5e1'`. */
128
+ pagerButtonBorder?: string;
129
+ }
130
+
131
+ // Cell Renderers
132
+
133
+ export interface CellRendererArgs {
134
+ x: number;
135
+ y: number;
136
+ w: number;
137
+ h: number;
138
+ value: unknown;
139
+ rowData: Record<string, unknown>;
140
+ rowIndex: number;
141
+ colIndex: number;
142
+ theme: Required<GridTheme>;
143
+ padding: number;
144
+ }
145
+
146
+ export type CellRendererFn = (ctx: CanvasRenderingContext2D, args: CellRendererArgs) => void;
147
+
148
+ // Cell context menu extension point (see JHGridOptions.cellContextMenuExtraItems)
149
+
150
+ export interface CellContextMenuItemContext {
151
+ row: number;
152
+ col: number;
153
+ field: string;
154
+ rowData: Record<string, unknown> | null;
155
+ /** Viewport coordinates of the invoking click (unlike the grid's internal canvas-local x/y) —
156
+ * for positioning a host-built DOM overlay (e.g. a comment editor) at the invoked cell. */
157
+ clientX: number;
158
+ clientY: number;
159
+ }
160
+
161
+ export interface CellContextMenuItem {
162
+ label: string;
163
+ onClick: (ctx: CellContextMenuItemContext) => void;
164
+ disabled?: boolean;
165
+ }
166
+
167
+ // Cell decorator extension point (see JHGridOptions.cellDecorator)
168
+
169
+ export interface CellDecoratorArgs extends CellRendererArgs {
170
+ field: string;
171
+ }
172
+
173
+ export declare const CellRenderers: {
174
+ progressBar(opts?: { max?: number; showLabel?: boolean }): CellRendererFn;
175
+ badge(opts?: { colorMap?: Record<string, { bg?: string; fg?: string }> }): CellRendererFn;
176
+ checkmark(opts?: { trueColor?: string; falseColor?: string; showFalse?: boolean }): CellRendererFn;
177
+ /**
178
+ * Renders `value` (an image URL or `data:` URI) scaled to fill the cell, via a shared,
179
+ * byte-budgeted LRU cache that decodes through `fetch()` + `createImageBitmap()` resized to
180
+ * the cell's on-screen size — a large source image shown as a small thumbnail only ever costs
181
+ * a small decoded bitmap, not a full-resolution one. Non-blocking: shows a placeholder while
182
+ * loading and redraws itself automatically once the image resolves. `fit: 'cover'` (default)
183
+ * crops to fill like CSS `object-fit: cover`; `'contain'` letterboxes to show the whole image.
184
+ */
185
+ image(opts?: { fit?: 'cover' | 'contain'; radius?: number }): CellRendererFn;
186
+ number(opts?: { locale?: string; decimals?: number }): CellRendererFn;
187
+ /**
188
+ * `format` accepts a `YYYY`/`MM`/`DD`/`HH`/`mm`/`ss` pattern (locale-agnostic), or the
189
+ * special value `'locale'` to format via `Intl.DateTimeFormat` using `locale` (or the
190
+ * grid's {@link JHGridOptions.locale} when omitted).
191
+ */
192
+ date(opts?: { format?: string; locale?: string; dateStyle?: 'full' | 'long' | 'medium' | 'short'; align?: 'left' | 'center' | 'right' }): CellRendererFn;
193
+ currency(opts?: { locale?: string; currency?: string }): CellRendererFn;
194
+ /** 드롭다운 셀 렌더러: 현재 값 + ▾ 화살표 표시 */
195
+ dropdown(opts?: { placeholder?: string }): CellRendererFn;
196
+ /** 다중선택 셀 렌더러: 선택된 값 목록 + ▾ 화살표 표시 */
197
+ multiselect(opts?: { placeholder?: string }): CellRendererFn;
198
+ /** 체크박스 셀 렌더러: 체크/미체크 박스 표시 */
199
+ checkbox(opts?: { checkedColor?: string; size?: number }): CellRendererFn;
200
+ /** 버튼 셀 렌더러: 셀 전체를 하나의 클릭 가능한 버튼으로 표시 */
201
+ button(opts?: {
202
+ label?: string | ((rowData: Record<string, unknown> | null, rowIndex: number) => string | null);
203
+ disabled?: boolean | ((rowData: Record<string, unknown> | null, rowIndex: number) => boolean);
204
+ variant?: ButtonVariant | ((rowData: Record<string, unknown> | null, rowIndex: number) => ButtonVariant);
205
+ }): CellRendererFn;
206
+ };
207
+
208
+ /** Registers (or overrides) a `CellRenderers` entry. */
209
+ export declare function registerCellRenderer(name: string, factory: (...args: any[]) => CellRendererFn): void;
210
+
211
+ /** Alias exported from Renderer */
212
+ export { CellRenderers as BuiltinRenderers };
213
+
214
+ // Cell Editors
215
+
216
+ export declare const CellEditors: Record<string, (...args: any[]) => unknown>;
217
+
218
+ /** Registers (or overrides) a `CellEditors` entry — the editor counterpart of `registerCellRenderer`. */
219
+ export declare function registerCellEditor(name: string, factory: (...args: any[]) => unknown): void;
220
+
221
+ // Column Definition
222
+
223
+ /** 드롭다운 옵션 항목 — 문자열 또는 { value, label } 객체 */
224
+ export type DropdownOption = string | { value: string; label?: string };
225
+
226
+ export interface ColumnDef {
227
+ field: string;
228
+ label?: string;
229
+ align?: 'left' | 'center' | 'right';
230
+ headerAlign?: 'left' | 'center' | 'right';
231
+ /**
232
+ * Group-header path, outermost → innermost: a single label (`'개인정보'`, one level) or an
233
+ * array (`['역량평가', '정량평가']`, N levels). Consecutive columns sharing the same label at a
234
+ * given depth render under one merged header cell (see {@link HeaderRowDef}), without having to
235
+ * hand-write `opts.headerRows`. Ignored when `opts.headerRows` is set explicitly (needed for
236
+ * non-contiguous groups).
237
+ */
238
+ group?: string | string[];
239
+ width?: number;
240
+ /** Built-in renderer name (e.g. 'progressBar') or a custom renderer function */
241
+ renderer?: string | CellRendererFn;
242
+ /** 컬럼 편집 타입. 'dropdown' / 'multiselect' / 'checkbox' / 'button' / 'date' / 'image' 지정 시 렌더러도 자동 적용 */
243
+ type?: 'text' | 'dropdown' | 'multiselect' | 'checkbox' | 'button' | 'date' | 'image';
244
+ /** Date format for type: 'date' columns (e.g. 'YYYY-MM-DD', 'YY/MM/DD'). Applied to both rendering and clipboard copy. Default: 'YYYY-MM-DD'. */
245
+ format?: string;
246
+ /** Custom editor: a CellEditors key (e.g. 'date') or a direct editor function receiving the editor context */
247
+ editor?: string | ((ctx: Record<string, unknown>) => { value: string; remove: () => void } | null | undefined);
248
+ /** Options forwarded to the named CellEditors factory when editor is a string key */
249
+ editorOptions?: Record<string, unknown>;
250
+ /**
251
+ * 드롭다운 옵션 목록. type이 'dropdown'일 때 사용.
252
+ * - 문자열 배열: ['선택1', '선택2']
253
+ * - 객체 배열: [{ value: 'A', label: '선택 A' }]
254
+ * - 함수: (rowData) => DropdownOption[] (행 데이터 기반 동적 옵션)
255
+ */
256
+ options?: DropdownOption[] | ((rowData: Record<string, unknown>) => DropdownOption[]);
257
+ editable?: boolean;
258
+ /** Declarative validation rules, checked on every commit to this column's cells. */
259
+ validation?: ColumnValidation;
260
+ /** 버튼 설정. type이 'button'일 때 사용. */
261
+ button?: ButtonColumnDef;
262
+ /**
263
+ * 그룹헤더 행/푸터에 표시할 집계 함수. 내장 타입(`sum`/`avg`/`min`/`max`)은 `Number(row[field])`로
264
+ * 캐스팅해 계산하며 `NaN`은 무시한다. `count`는 non-null 값 개수. 커스텀 `fn`은 그룹(또는 전체)에
265
+ * 속한 원본 row 배열을 받아 값을 계산하고, `format`으로 표시 문자열을 지정할 수 있다.
266
+ *
267
+ * 행 그룹핑을 제공하는 플러그인(`setGrouping`)이 설치되어 있을 때만 의미가 있다. 플러그인 없이
268
+ * 지정하면 값은 보관되지만 아무 곳에도 표시되지 않는다 — 집계를 읽고 그리는 쪽이 플러그인이다.
269
+ */
270
+ aggregate?: 'sum' | 'avg' | 'count' | 'min' | 'max' | {
271
+ fn: (rows: Record<string, unknown>[], field: string) => number | string;
272
+ format?: (value: number | string) => string;
273
+ };
274
+ /** When true, renders a clickable checkbox in this column's last header row. Fires `onHeaderCheckboxChange` on click. */
275
+ headerCheckbox?: boolean;
276
+ }
277
+
278
+ export type ButtonVariant = 'primary' | 'success' | 'danger' | 'neutral';
279
+
280
+ export interface ButtonColumnDef {
281
+ /**
282
+ * 버튼 라벨. 고정 문자열 또는 (rowData, rowIndex) => string|null 함수.
283
+ * 함수가 null/''을 반환하면 해당 행에는 버튼을 그리지 않는다 (조건부 숨김).
284
+ */
285
+ label?: string | ((rowData: Record<string, unknown> | null, rowIndex: number) => string | null);
286
+ /** 버튼 클릭(또는 셀 선택 후 Space/Enter/F2) 시 호출된다. editableCols 여부와 무관하게 동작한다. */
287
+ onClick: (rowIndex: number, rowData: Record<string, unknown> | null, field: string) => void;
288
+ /** 고정 또는 행별 비활성화 여부. true인 동안은 onClick이 호출되지 않는다. */
289
+ disabled?: boolean | ((rowData: Record<string, unknown> | null, rowIndex: number) => boolean);
290
+ /** 버튼 색상. 고정값 또는 (rowData, rowIndex) => ButtonVariant 함수. 기본값 'primary'. */
291
+ variant?: ButtonVariant | ((rowData: Record<string, unknown> | null, rowIndex: number) => ButtonVariant);
292
+ }
293
+
294
+ export interface ColumnValidation {
295
+ /** Value must be non-empty (after trimming) to pass. */
296
+ required?: boolean;
297
+ /** Value must match this pattern (ignored when empty and not required). */
298
+ pattern?: RegExp | string;
299
+ /** Numeric minimum (value is coerced with Number()). */
300
+ min?: number;
301
+ /** Numeric maximum (value is coerced with Number()). */
302
+ max?: number;
303
+ /** Minimum string length. */
304
+ minLength?: number;
305
+ /** Maximum string length. */
306
+ maxLength?: number;
307
+ /**
308
+ * Custom check, run after all built-in rules pass. Return `true` (valid),
309
+ * `false` (invalid — uses `message` or the default i18n message), or a
310
+ * string (invalid — used verbatim as the error message).
311
+ */
312
+ validator?: (value: string, rowData: Record<string, unknown>) => boolean | string;
313
+ /** Overrides the default i18n message for every built-in rule above. */
314
+ message?: string;
315
+ }
316
+
317
+ // Header Group
318
+
319
+ export interface HeaderRowDef {
320
+ label?: string;
321
+ /** Field names this group spans — updates automatically after column reorder */
322
+ fields?: string[];
323
+ colspan?: number;
324
+ rowspan?: number;
325
+ align?: 'left' | 'center' | 'right';
326
+ }
327
+
328
+ /**
329
+ * Computes grouped-header cell layout from `opts.headerRows` + the current column order — the
330
+ * same layout engine the canvas header draw uses internally, exposed publicly so a custom
331
+ * exporter/renderer can reproduce the same merged-header shape. Returns the same cell shape as
332
+ * {@link ExcelHeaderCell} (declared further below, alongside `ExcelExportSchema`).
333
+ */
334
+ export declare function computeHeaderCells(headerRows: HeaderRowDef[][] | undefined, columns: string[]): ExcelHeaderCell[];
335
+
336
+ // Data Source
337
+
338
+ export interface GridMeta {
339
+ totalRows: number;
340
+ columns: string[];
341
+ }
342
+
343
+ export interface GridData {
344
+ rows: Record<string, unknown>[];
345
+ }
346
+
347
+ export interface GridFilterState {
348
+ sorts: { field: string; dir: 'asc' | 'desc' }[];
349
+ /**
350
+ * Per-column filter values. A plain `string` is a substring-match text filter
351
+ * (setFilter()); a `string[]` is a Set filter's exact-match checkbox selection
352
+ * (setFilterValues()) — interpretation of both is entirely up to fetchData/fetchMeta.
353
+ */
354
+ filters: Record<string, string | string[]>;
355
+ /** Global quick filter term (setQuickFilter()), '' when inactive. Interpretation (which
356
+ * columns it searches, substring vs exact) is entirely up to fetchData/fetchMeta. */
357
+ quickFilter: string;
358
+ }
359
+
360
+ // Grid State (serializable snapshot)
361
+
362
+ export interface GridState {
363
+ columns: string[];
364
+ columnWidths: Record<string, number>;
365
+ hiddenColumns: string[];
366
+ frozenCols: number;
367
+ frozenColsRight: number;
368
+ sorts: { field: string; dir: 'asc' | 'desc' }[];
369
+ /** See {@link GridFilterState.filters} — string (text filter) or string[] (Set filter) per field. */
370
+ filters: Record<string, string | string[]>;
371
+ /** See {@link GridFilterState.quickFilter}. */
372
+ quickFilter: string;
373
+ /**
374
+ * Row-grouping state, or `null` when the grid isn't grouped. Always present in the snapshot
375
+ * shape (even as `null`), so a `getState()`/`setState()` round-trip never drops this key.
376
+ * Single-field grouping keeps the legacy shape; multi-field grouping's `collapsedKeys` are
377
+ * path arrays.
378
+ */
379
+ grouping: { field: string; collapsedKeys: string[] } | { fields: string[]; collapsedKeys: string[][] } | null;
380
+ /** Tree/hierarchical row state, or `null` when unused. Same always-present shape as {@link GridState.grouping}. */
381
+ treeData: { idField: string; parentField: string; collapsedKeys: string[][] } | null;
382
+ /**
383
+ * Active color filters, field → color (e.g. `{ COL_4: 'rgba(22,163,74,0.14)' }`), or `null`
384
+ * when none are set. Same always-present shape as {@link GridState.grouping}.
385
+ */
386
+ colorFilters: Record<string, string> | null;
387
+ /** Selected row indices (rowSelection mode 'single'|'multi'). Always present (as `[]` when nothing is selected) — row selection is installed unconditionally, even on a base `@jhgrid/jhgrid` import. */
388
+ selectedRows: number[];
389
+ /** `field -> checked` for every `headerCheckbox` column's header checkbox (see `setHeaderCheckbox`/`getHeaderCheckbox`). */
390
+ headerCheckboxState: Record<string, boolean>;
391
+ /**
392
+ * Definitions of columns added via `addColumn()` that haven't been committed yet (see
393
+ * `getNewColumns()`/`commitColumns()`) — restored via `_addColumnImpl` before column order, so a
394
+ * `setState()` round-trip doesn't silently drop a locally-added column. Function-valued def
395
+ * fields (a custom `renderer`/`editor`, function-form `options`, a `validation.validator`, a
396
+ * `button.onClick`) survive an in-memory round-trip but won't survive `JSON.stringify`/`parse` —
397
+ * same inherent limitation as any function-valued columnDefs entry.
398
+ */
399
+ localColumns: (Omit<ColumnDef, 'field'> & { field: string })[];
400
+ /** Field names of server columns marked for deletion via `deleteColumn()` but not yet committed (see `getDeletedColumns()`/`commitColumns()`). */
401
+ deletedColumns: string[];
402
+ /**
403
+ * Unsaved row work — the row counterpart of {@link GridState.localColumns} /
404
+ * {@link GridState.deletedColumns}, which have always been carried here.
405
+ *
406
+ * Everything is named by **server index**, never by screen position: a screen position only
407
+ * means something alongside the exact arrangement that produced it, and the point of a snapshot
408
+ * is to outlive that. `restoring` therefore assumes the same result set — the same query, the
409
+ * same underlying rows. Restore against changed server data and the indices name different
410
+ * records, the same way {@link GridState.filters} assumes the fields still exist.
411
+ *
412
+ * `setState` treats it as a replacement, not an addition: restoring twice does not duplicate.
413
+ * A snapshot taken before this field existed leaves the grid's current row work alone.
414
+ */
415
+ rowChanges: {
416
+ /**
417
+ * Rows from `addRow()`. `anchor` is the server index the row sits in front of (equal to the
418
+ * row count when it was appended at the end). Each row carries its own cell edits, because
419
+ * keyed separately they would be two lists that have to agree about ordering.
420
+ */
421
+ added: { anchor: number; data: Record<string, unknown>; edits: Record<string, string> }[];
422
+ /** Server indices removed from the screen — see {@link JHGrid.getRemovedRows}. */
423
+ removed: number[];
424
+ /** Server indices marked for deletion — see {@link JHGrid.getDeletedRows}. */
425
+ marked: number[];
426
+ /** Unsaved cell edits on server rows, as `serverIndex -> field -> value`. */
427
+ edits: Record<number, Record<string, string>>;
428
+ };
429
+ }
430
+
431
+ // i18n
432
+
433
+ export interface GridI18n {
434
+ loading?: string;
435
+ loadError?: string;
436
+ /** Shown when a paste (`Ctrl+V`) drops some values because they fell outside the pasted range. */
437
+ pasteTruncated?: (n: number) => string;
438
+ /** Shown when a copy (`Ctrl+C`) omits some rows because they hadn't loaded yet. */
439
+ copyIncomplete?: (n: number) => string;
440
+ noData?: string;
441
+ emptyCell?: string;
442
+ ariaGrid?: string;
443
+ sortAsc?: string;
444
+ sortDesc?: string;
445
+ sortShiftHint?: string;
446
+ rowNumberLabel?: string;
447
+ filterLabel?: string;
448
+ filterPlaceholder?: string;
449
+ filterApply?: string;
450
+ filterReset?: string;
451
+ filterResetAll?: string;
452
+ filterClose?: string;
453
+ filterDialog?: (col: string) => string;
454
+ filterColorLabel?: string;
455
+ filterValuesLabel?: string;
456
+ filterSelectAll?: string;
457
+ /** Placeholder for the tag filter's search box (see {@link JHGridOptions.fetchFilterValues}). */
458
+ filterTagPlaceholder?: string;
459
+ /** Prefixes the tag filter's suggestion list when it was scanned from loaded rows rather than {@link JHGridOptions.fetchFilterValues}. */
460
+ filterTagLocalScope?: string;
461
+ /** Shown in the tag filter's suggestion list when nothing matches the typed query. */
462
+ filterTagNoMatch?: string;
463
+ /** Shown below the tag filter's minimum-character threshold (see {@link JHGridOptions.filterValueMinChars}). */
464
+ filterTagMinChars?: (n: number) => string;
465
+ /** Shown when every matching value in the tag filter's suggestion list has already been picked. */
466
+ filterTagAllSelected?: string;
467
+ /** Shown when the tag filter's suggestion list is capped and more matches exist than are shown. */
468
+ filterTagMore?: (n: number) => string;
469
+ /** Label above the tag filter's tray of picked values. */
470
+ filterTagSelected?: (n: number) => string;
471
+ /** Label for the tag filter's "contains" substring-match fallback chip/option. */
472
+ filterTagContains?: (q: string) => string;
473
+ /** `aria-label` for a tag filter chip's remove ("×") button. */
474
+ filterTagRemove?: (v: string) => string;
475
+ colFreeze?: string;
476
+ colUnfreeze?: string;
477
+ colFreezeRight?: string;
478
+ colUnfreezeRight?: string;
479
+ colVisibility?: string;
480
+ colChooserTitle?: string;
481
+ colChooserApply?: string;
482
+ colChooserCancel?: string;
483
+ colChooserSelectAll?: string;
484
+ /** Column header context menu: insert a new column to the left. */
485
+ colInsertLeft?: string;
486
+ /** Column header context menu: insert a new column to the right. */
487
+ colInsertRight?: string;
488
+ /** Title of the dialog opened by {@link colInsertLeft}/{@link colInsertRight}. */
489
+ colInsertTitle?: string;
490
+ /** Placeholder of the new-column-name input in that dialog. */
491
+ colInsertPlaceholder?: string;
492
+ /** Confirm button label in that dialog. */
493
+ colInsertConfirm?: string;
494
+ /** Cancel button label in that dialog. */
495
+ colInsertCancel?: string;
496
+ /** Column header context menu: mark this column for deletion (see {@link JHGrid.deleteColumn}). */
497
+ colDelete?: string;
498
+ /** Column header context menu: clear a pending deletion mark (see {@link JHGrid.undeleteColumn}). */
499
+ colUndelete?: string;
500
+ /** Row context menu: insert a blank row at the very top of the dataset. */
501
+ rowInsertTop?: string;
502
+ /** Row context menu: insert a blank row at the very bottom of the dataset. */
503
+ rowInsertBottom?: string;
504
+ /** Row context menu: insert a blank row directly above this one. */
505
+ rowInsertAbove?: string;
506
+ /** Row context menu: insert a blank row directly below this one. */
507
+ rowInsertBelow?: string;
508
+ /** Label for the "add row" control (e.g. a toolbar button a host wires up itself). */
509
+ rowAddEnd?: string;
510
+ /** Row context menu label for deleting a locally-added (unsaved) row — always removed outright. */
511
+ rowDelete?: string;
512
+ /** Row context menu label for marking a server row deleted (`deleteRow(i, { permanent: false })`). */
513
+ rowDeleteMark?: string;
514
+ /** Row context menu label for permanently removing a server row (`deleteRow(i, { permanent: true })`). */
515
+ rowDeletePermanent?: string;
516
+ /** Row context menu: clear a deletion mark, or restore a permanently-removed row. */
517
+ rowUndelete?: string;
518
+ editAriaLabel?: (col: string, row: number) => string;
519
+ announceCell?: (row: number, col: string, value: string) => string;
520
+ /** Screen-reader announcement when a column header receives keyboard focus. */
521
+ columnHeaderAnnounce?: (col: string) => string;
522
+ /** Screen-reader announcement when a row-number cell receives keyboard focus. */
523
+ rowHeaderAnnounce?: (row: number) => string;
524
+ /** Screen-reader announcement after a row drag-reorder completes (see {@link JHGridOptions.rowReorder}). */
525
+ rowReorderAnnounce?: (from: number, to: number) => string;
526
+ /** Screen-reader announcement after a sort is applied from the filter/sort panel. */
527
+ sortAppliedAnnounce?: (col: string, dir: 'asc' | 'desc') => string;
528
+ /** Screen-reader announcement after a filter is applied from the filter/sort panel. */
529
+ filterAppliedAnnounce?: (col: string) => string;
530
+ /** Screen-reader announcement after a single column's filter is cleared. */
531
+ filterClearedAnnounce?: (col: string) => string;
532
+ /** Screen-reader announcement after {@link JHGrid.clearFilters} clears every filter and sort. */
533
+ allFiltersClearedAnnounce?: string;
534
+ /** Screen-reader announcement when the row-selection set changes (`n` = newly selected count, `0` = cleared). */
535
+ rowsSelectedAnnounce?: (n: number) => string;
536
+ unsavedEditsWarning?: string;
537
+ exportCsvFilename?: string;
538
+ /** Default filename for an Excel-format export, kept separate from `exportCsvFilename` above since `exportCsv()` always uses that one. */
539
+ exportExcelFilename?: string;
540
+ exportSheetName?: string;
541
+ printButton?: string;
542
+ validationRequired?: (col: string) => string;
543
+ validationPattern?: (col: string) => string;
544
+ validationMin?: (col: string, min: number) => string;
545
+ validationMax?: (col: string, max: number) => string;
546
+ validationMinLength?: (col: string, len: number) => string;
547
+ validationMaxLength?: (col: string, len: number) => string;
548
+ validationInvalid?: (col: string) => string;
549
+ pagerFirst?: string;
550
+ pagerPrev?: string;
551
+ pagerNext?: string;
552
+ pagerLast?: string;
553
+ pagerPageLabel?: (page: number, pageCount: number) => string;
554
+ aggSum?: string;
555
+ aggAvg?: string;
556
+ aggCount?: string;
557
+ aggMin?: string;
558
+ aggMax?: string;
559
+ groupLabel?: (field: string, key: string, count: number) => string;
560
+ groupFooterLabel?: string;
561
+ }
562
+
563
+ /** Korean locale strings — pass as i18n option for Korean UI */
564
+ export declare const KO_I18N: Required<GridI18n>;
565
+
566
+ /** Japanese locale strings — pass as i18n option for Japanese UI */
567
+ export declare const JA_I18N: Required<GridI18n>;
568
+
569
+ /** Simplified Chinese locale strings — pass as i18n option for Chinese UI */
570
+ export declare const ZH_I18N: Required<GridI18n>;
571
+
572
+ // Pagination
573
+
574
+ export interface PaginationOptions {
575
+ /** Switches from continuous virtual scrolling to classic fixed-size pages. */
576
+ enabled: boolean;
577
+ /**
578
+ * Rows per page. Fixed at construction time (not changeable at runtime).
579
+ * Also becomes the effective `chunkSize` — `chunkSize` is ignored when set.
580
+ * Default: 50.
581
+ */
582
+ pageSize?: number;
583
+ }
584
+
585
+ // Constructor Options
586
+
587
+ export interface JHGridOptions {
588
+ container: string | Element;
589
+ /**
590
+ * Exactly one data source is required: `fetchMeta`+`fetchData` (server-paged), `fetchPage`
591
+ * (single-callback server variant), or `data` (in-memory array, see below). Typed as optional
592
+ * here because either alternative also satisfies the grid; the constructor throws at runtime
593
+ * if none of the three is provided.
594
+ */
595
+ fetchMeta?: (state?: GridFilterState | null) => Promise<GridMeta>;
596
+ fetchData?: (page: number, size: number, state?: GridFilterState | null) => Promise<GridData>;
597
+ /**
598
+ * `fetchMeta`+`fetchData` collapsed into one call, for a backend that returns the page and the
599
+ * total together — a `COUNT(*) OVER()` alongside the paged rows, say. Resolve
600
+ * `{ rows, totalRows }`, plus `columns` unless `columnDefs` names them.
601
+ *
602
+ * `state` is the sort and filter the grid wants applied, exactly as {@link fetchData} receives
603
+ * it: honour it server-side and return `totalRows` for the filtered result, not the table. A
604
+ * callback that ignores the argument still works — it simply never filters or sorts, which is
605
+ * what every `fetchPage` grid did before the argument was passed at all.
606
+ *
607
+ * The grid asks for chunk 0 once at boot even though it needs both the count and the rows from
608
+ * it, so a plain `(page, size) => …` implementation is not called twice for the same page.
609
+ *
610
+ * Only one data source applies; supplying this alongside `fetchMeta`/`fetchData` leaves those
611
+ * two in charge.
612
+ */
613
+ fetchPage?: (page: number, size: number, state?: GridFilterState | null) => Promise<GridData & { totalRows: number; columns?: string[] }>;
614
+ /**
615
+ * Convenience alternative to `fetchMeta`+`fetchData` for a dataset that already fits in
616
+ * memory (prototyping, small/medium lookup tables, tests). Columns are inferred from
617
+ * `columnDefs` if given, else from the keys of `data[0]`. Filtering/sorting/quick-filter are
618
+ * applied against the array directly with the same semantics a host's own `fetchMeta`/
619
+ * `fetchData` are expected to follow (see {@link GridFilterState}) — there is no indexing, so
620
+ * this re-scans the full array on every state change and isn't a fit for very large datasets.
621
+ * Ignored if `fetchMeta`/`fetchData`/`fetchPage` is also provided.
622
+ */
623
+ data?: Record<string, unknown>[];
624
+ width?: number;
625
+ height?: number;
626
+ rowHeight?: number;
627
+ colWidth?: number;
628
+ headerHeight?: number;
629
+ /**
630
+ * Fade duration in ms for the row-hover highlight as it appears and disappears. The highlight
631
+ * itself always tracks the pointer instantly; only the entrance/exit eases. `0` makes it
632
+ * instant. Forced to 0 when the user has `prefers-reduced-motion: reduce` set. Default: 110.
633
+ */
634
+ hoverFadeMs?: number;
635
+ /**
636
+ * Travel time in ms for the selection box when it moves to a new cell or range. Snaps instead
637
+ * of easing during a range/fill drag, when the selection appears or clears, and when a move
638
+ * arrives before the previous one has landed (held arrow keys). `0` disables it. Forced to 0
639
+ * under `prefers-reduced-motion: reduce`. Default: 90.
640
+ */
641
+ selectionMoveMs?: number;
642
+ /**
643
+ * Glide length in ms for mouse-wheel scrolling. Deltas smaller than one row — a precision
644
+ * trackpad's dense stream, which is already smooth — are applied immediately regardless.
645
+ * `0` applies every wheel delta immediately. Forced to 0 under
646
+ * `prefers-reduced-motion: reduce`. Default: 120.
647
+ */
648
+ scrollEaseMs?: number;
649
+ /**
650
+ * How long a displaced column takes to travel to its new slot during a column-header drag. The
651
+ * reorder itself happens as the pointer crosses each boundary either way; this only controls
652
+ * whether the columns it displaces slide there or appear there. A reorder arriving before the
653
+ * previous slide has landed snaps it. `0` puts columns straight into place. Forced to 0 under
654
+ * `prefers-reduced-motion: reduce`. Default: 220.
655
+ */
656
+ columnSlideMs?: number;
657
+ /** When true, header cell labels wrap across multiple lines instead of being clipped with ellipsis. Default: false. */
658
+ wrapHeader?: boolean;
659
+ scrollbarSize?: number;
660
+ chunkSize?: number;
661
+ /** Maximum number of chunks kept in memory (LRU). Default: 50 */
662
+ maxCachedChunks?: number;
663
+ /**
664
+ * Enables classic fixed-size pagination (a built-in pager bar with
665
+ * Prev/Next/page-number controls) instead of continuous virtual scrolling.
666
+ * Row indices everywhere in the public API (getEdits(), onCellChange, setCellValue())
667
+ * stay global/absolute regardless of this option — pagination only changes what's
668
+ * scrollable/visible at once.
669
+ */
670
+ pagination?: PaginationOptions;
671
+ frozenCols?: number;
672
+ frozenColsRight?: number;
673
+ /**
674
+ * Enables drag-to-reorder rows from the row-number gutter (requires `showRowNumbers: true`
675
+ * to have somewhere to grab). Unlike column reorder, this requires materializing the entire
676
+ * filtered/sorted dataset client-side first (row data, unlike column metadata, isn't
677
+ * normally resident in memory under server-paged virtualization).
678
+ * Runs one full-dataset scan via `fetchData` after every load/reload; dragging is disabled
679
+ * until that scan completes, and while row grouping or tree data is active. Default: false.
680
+ */
681
+ rowReorder?: boolean;
682
+ editableCols?: string[] | '*';
683
+ /**
684
+ * What {@link JHGrid.deleteRow} does to a **server** row when the call doesn't say.
685
+ *
686
+ * - `'mark'` (default) — the row stays on screen, dimmed with a strikethrough, and is reported
687
+ * by {@link JHGrid.getDeletedRows}. Suits a screen with an explicit save step, where the user
688
+ * should be able to change their mind before committing.
689
+ * - `'permanent'` — the row comes off the screen and is reported by
690
+ * {@link JHGrid.getRemovedRows}. Suits a list that commits as you go.
691
+ *
692
+ * Either way the server is untouched; the grid only records the choice. Rows added with
693
+ * {@link JHGrid.addRow} ignore this — they were never sent anywhere, so there is nothing to
694
+ * mark and they always go immediately.
695
+ *
696
+ * `deleteRow(i, { permanent })` overrides it per call, because one screen can legitimately need
697
+ * both — marking a saved record while discarding a draft, say.
698
+ */
699
+ deleteMode?: 'mark' | 'permanent';
700
+ /**
701
+ * Narrows which items appear in the right-click row menu (opened from the row-number gutter).
702
+ * `false` turns the menu off entirely; an array keeps only the named items; omitted (default)
703
+ * shows all of them, unchanged from before this option existed.
704
+ *
705
+ * Valid keys: `'row-insert-above'`, `'row-insert-below'`, `'row-insert-top'`,
706
+ * `'row-insert-bottom'`, `'row-delete'`. The four insert placements are each their own key;
707
+ * every delete-related item (mark / permanent / plain delete / undelete) is gated by the single
708
+ * `'row-delete'` key, since which of those actually renders for a given row is row state, not
709
+ * something a host would pick independently.
710
+ */
711
+ rowContextMenuItems?: false | ('row-insert-above' | 'row-insert-below' | 'row-insert-top' | 'row-insert-bottom' | 'row-delete')[];
712
+ /**
713
+ * Narrows which items appear in the column header's right-click menu. Same shape as
714
+ * {@link JHGridOptions.rowContextMenuItems}: `false` disables the menu, an array keeps only the
715
+ * named items, omitted shows all of them.
716
+ *
717
+ * Valid keys: `'freeze'`, `'freeze-right'`, `'visibility'`, `'insert-left'`, `'insert-right'`,
718
+ * `'delete'`. `'freeze'`/`'freeze-right'` each cover both their freeze and unfreeze label/action
719
+ * for that slot, and `'delete'` covers both delete and undelete, same reasoning as the row menu.
720
+ */
721
+ colContextMenuItems?: false | ('freeze' | 'freeze-right' | 'visibility' | 'insert-left' | 'insert-right' | 'delete')[];
722
+ /**
723
+ * Narrows which items appear in the plain-cell right-click menu. Same shape as
724
+ * {@link JHGridOptions.rowContextMenuItems}: `false` disables the menu, an array keeps only the
725
+ * named items, omitted shows all of them.
726
+ *
727
+ * Valid keys: `'col-insert-left'`, `'col-insert-right'`, `'col-delete'`, `'row-insert-below'`,
728
+ * `'row-delete'`. Unrelated to {@link JHGridOptions.cellContextMenuExtraItems} below — that adds
729
+ * items instead of filtering these, and always applies regardless of this option.
730
+ */
731
+ cellContextMenuItems?: false | ('col-insert-left' | 'col-insert-right' | 'col-delete' | 'row-insert-below' | 'row-delete')[];
732
+ /**
733
+ * Adds custom items to the plain-cell right-click menu, after whichever built-in items
734
+ * {@link JHGridOptions.cellContextMenuItems} left in place (if any). Called fresh each time the
735
+ * menu opens for a given cell; return `null`/`undefined`/`[]` to add nothing for that cell.
736
+ *
737
+ * This is the one general-purpose extension point for host- or plugin-supplied context menu
738
+ * actions — the grid renders the label, routes the click to `onClick`, and closes the menu
739
+ * afterwards, same as a built-in item. It has no opinion about what `onClick` does.
740
+ */
741
+ cellContextMenuExtraItems?: (ctx: CellContextMenuItemContext) => CellContextMenuItem[] | null | undefined;
742
+ columnDefs?: ColumnDef[];
743
+ /** Multi-row header groups. Each element is one group-header row. */
744
+ headerRows?: HeaderRowDef[][];
745
+ /** Show a built-in row-number column on the left. Default: true. */
746
+ showRowNumbers?: boolean;
747
+ /** Width of the row-number column in pixels. Default: 50. */
748
+ rowNumberWidth?: number;
749
+ responsive?: boolean;
750
+ rowSelection?: 'none' | 'single' | 'multi';
751
+ /** Fields to hide on initial render */
752
+ hiddenColumns?: string[];
753
+ theme?: GridTheme;
754
+ /**
755
+ * BCP-47 tag (e.g. `'en'`, `'ko'`, `'ko-KR'`) selecting a built-in UI text pack
756
+ * (see {@link KO_I18N}; unmatched tags fall back to English) and the default
757
+ * locale `CellRenderers.number/date/currency` format with. Default: `'en-US'`.
758
+ * `i18n` (below) is applied on top and always wins per-key.
759
+ */
760
+ locale?: string;
761
+ i18n?: GridI18n;
762
+ ariaLabel?: string;
763
+
764
+ // Callbacks
765
+ /** oldValue is the pre-edit value: the prior edit if the cell was already dirty, otherwise the row's original data. */
766
+ onCellChange?: (params: { row: number; field: string; newValue: string; oldValue: string }) => void;
767
+ /** Fired when the cell/range selection changes. null = selection cleared. */
768
+ onSelectionChange?: (sel: { type: 'single'; row: number; col: number } | { type: 'range'; r1: number; c1: number; r2: number; c2: number } | null) => void;
769
+ /** Fired after sorts are applied or cleared. Empty array = all sorts cleared. */
770
+ onSort?: (sorts: { field: string; dir: 'asc' | 'desc' }[] | null) => void;
771
+ /** Fired after any filter is applied, reset, or cleared. */
772
+ onFilter?: (filters: Record<string, string | string[]>) => void;
773
+ /**
774
+ * Looks up candidate values for a column's header filter, given whatever the user has typed.
775
+ * Return a bounded list; only what fits the query is useful, and the panel shows it as-is.
776
+ *
777
+ * The tag picker itself is **not** conditional on this — it appears on any column whose values
778
+ * could not be enumerated locally (the ones where the checklist does not appear). What this
779
+ * changes is where the candidates come from. Omit it and they are scanned out of the rows
780
+ * already loaded, and the list is labelled as covering only those; supply it and it answers for
781
+ * the whole column, label included. Worth supplying whenever the grid is paging a table far
782
+ * larger than what is cached, since that is exactly when the local sample is thinnest.
783
+ *
784
+ * Tags are combined with OR and applied as a `string[]`, the same shape the value checklist
785
+ * sends. If the typed text matches nothing the user can still fall back to a substring filter,
786
+ * which arrives as a plain `string`. A column carries one or the other, never both.
787
+ */
788
+ fetchFilterValues?: (field: string, query: string) => Promise<Array<string | number | null>>;
789
+ /**
790
+ * Characters the tag filter waits for before looking candidates up. Below it the panel says what
791
+ * it is waiting for and still offers the substring fallback, which needs no lookup. One
792
+ * character against a large column is the most expensive query this control can issue and the
793
+ * least selective answer it can get. Applies to the built-in local scan as much as to
794
+ * {@link fetchFilterValues}. Set `1` for the pre-existing behaviour. Default: 2.
795
+ */
796
+ filterValueMinChars?: number;
797
+ /** Fired after column drag-reorder completes. columns = new visible field order. */
798
+ onColumnReorder?: (columns: string[]) => void;
799
+ /** Fired after a column is resized (mouse-up). */
800
+ onColumnResize?: (field: string, width: number) => void;
801
+ /** Fired after a row's individual height is resized by dragging a boundary in the row-number gutter (mouse-up). */
802
+ onRowHeightResize?: (rowIndex: number, height: number) => void;
803
+ /**
804
+ * Fired after a row drag-reorder completes (requires `rowReorder: true`).
805
+ * `fromIndex`/`toIndex` are absolute row indices at drop time; `rowData` is the moved row's
806
+ * raw data. Row-indexed transient state (edits, selection, undo history) is cleared on
807
+ * reorder the same way it is on a sort/filter change — persist `toIndex` server-side here if
808
+ * the new order needs to survive a refresh.
809
+ */
810
+ onRowReorder?: (fromIndex: number, toIndex: number, rowData: Record<string, unknown>) => void;
811
+ /** Fired when the row-selection set changes. */
812
+ onRowSelect?: (selectedRows: number[]) => void;
813
+ /** Fired after the current page changes (goToPage/nextPage/prevPage), only when it actually changed. */
814
+ onPageChange?: (page: number, pageCount: number) => void;
815
+ onRender?: () => void;
816
+ /** Fired when a data chunk fails to load. */
817
+ onChunkError?: (err: Error) => void;
818
+ /**
819
+ * Fired whenever a cell's validity changes as a result of an edit (or undo/redo
820
+ * of one). `message` is the validation error, or null when the cell becomes valid.
821
+ */
822
+ onValidationError?: (row: number, field: string, message: string | null) => void;
823
+ /** Fired when a header checkbox (column with `headerCheckbox: true`) is clicked. */
824
+ onHeaderCheckboxChange?: (field: string, checked: boolean) => void;
825
+ /**
826
+ * Optional callback returning a CSS color string for a row, or null/undefined for default.
827
+ * Called on every render — keep it fast.
828
+ * @example rowHighlighter: (row) => row.status === 'ERROR' ? 'rgba(239,68,68,0.12)' : null
829
+ */
830
+ rowHighlighter?: (rowData: Record<string, unknown> | null, rowIndex: number) => string | null | undefined;
831
+ /**
832
+ * Optional callback returning a CSS color string for an individual cell's background,
833
+ * or null/undefined for no override. Painted on top of the row background/highlight
834
+ * (rowHighlighter, selection) and beneath cell content/renderers. Called for every
835
+ * visible cell on every render — keep it fast. `rowData` is null for rows not yet
836
+ * loaded from the server, so check for that before reading fields. Exceptions thrown
837
+ * here are caught and logged; they don't interrupt rendering.
838
+ * @example cellBackground: (row, rowIndex, field) => row && field === 'score' && row.score < 60 ? '#fee2e2' : null
839
+ */
840
+ cellBackground?: (rowData: Record<string, unknown> | null, rowIndex: number, field: string, colIndex: number) => string | null | undefined;
841
+ /**
842
+ * Optional callback that draws on top of a cell after everything else in it (content,
843
+ * gridlines, strikethrough, validation border) — close to the call shape of a
844
+ * `columnDefs[i].renderer` (`(ctx, args) => void`), but invoked for every rendered cell instead
845
+ * of replacing one column's content, and `args` includes `field` since a decorator (unlike a
846
+ * column renderer, already scoped to one column) needs it to tell columns apart. Use it for a
847
+ * small corner mark, icon, or badge that layers on top of whatever the cell already shows.
848
+ * Called for every visible, loaded cell on every render — keep it fast, and do nothing (return
849
+ * without drawing) for cells that need no mark. Exceptions thrown here are caught and logged;
850
+ * they don't interrupt rendering.
851
+ * @example cellDecorator: (ctx, { x, y, w, field, rowIndex }) => { if (hasFlag(rowIndex, field)) { ctx.fillStyle = 'red'; ctx.beginPath(); ctx.moveTo(x + w - 8, y); ctx.lineTo(x + w, y); ctx.lineTo(x + w, y + 8); ctx.fill(); } }
852
+ */
853
+ cellDecorator?: (ctx: CanvasRenderingContext2D, args: CellDecoratorArgs) => void;
854
+ /**
855
+ * Optional callback returning tooltip text to show while the pointer idles over a cell, or
856
+ * null/undefined for none. Same call shape as `cellBackground`. Shown immediately (no hover
857
+ * delay), and takes priority over the built-in overflow-text tooltip and over anything a plugin
858
+ * supplies via its own `cellTooltip` — a validation error on the cell still wins over this.
859
+ * `rowData` is null for rows not yet loaded from the server.
860
+ * @example cellTooltip: (row, rowIndex, field) => hasNote(rowIndex, field) ? getNote(rowIndex, field) : null
861
+ */
862
+ cellTooltip?: (rowData: Record<string, unknown> | null, rowIndex: number, field: string, colIndex: number) => string | null | undefined;
863
+ }
864
+
865
+ // Excel export schema
866
+
867
+ export interface ExcelHeaderCell {
868
+ row: number;
869
+ col: number;
870
+ colspan: number;
871
+ rowspan: number;
872
+ label: string | null;
873
+ align: string | null;
874
+ isLeaf: boolean;
875
+ }
876
+
877
+ export interface ExcelExportSchema {
878
+ sheetName: string;
879
+ columns: string[];
880
+ columnLabels: string[];
881
+ headerCells: ExcelHeaderCell[] | undefined;
882
+ includeHeaders: boolean;
883
+ colWidths: number[];
884
+ headerBg: string;
885
+ headerText: string;
886
+ headerBorder: string;
887
+ }
888
+
889
+ // JHGrid
890
+
891
+ /**
892
+ * A plugin object passed to {@link JHGrid.use}. Every hook is optional; core calls whichever
893
+ * ones a plugin happens to implement and falls back to plain behavior everywhere else, which
894
+ * is why this is an open shape rather than a fixed list of members.
895
+ */
896
+ export interface JHGridPlugin {
897
+ /** Called once when the plugin is first installed. */
898
+ install?: (grid: typeof JHGrid) => void;
899
+ [hook: string]: unknown;
900
+ }
901
+
902
+ export declare class JHGrid {
903
+ /**
904
+ * Installs a plugin. Installing the same object twice is a no-op. Plugin packages call this
905
+ * on import, so an application normally never has to — import the package and its API appears
906
+ * on the grid instance.
907
+ */
908
+ static use(plugin: JHGridPlugin): void;
909
+
910
+ constructor(opts: JHGridOptions);
911
+
912
+ // Boot
913
+ /**
914
+ * Resolves once the first `fetchMeta`/`fetchData` boot has settled — either way, including
915
+ * on failure — so that `this._columns` and friends are populated and any structural API will
916
+ * actually run instead of silently no-op'ing while a boot is still in flight.
917
+ *
918
+ * Await it before calling one straight after construction:
919
+ * ```js
920
+ * const grid = new JHGrid({ ... });
921
+ * await grid.ready();
922
+ * grid.hideColumn('email');
923
+ * ```
924
+ * Not needed inside a user-triggered handler (a button's `onclick`, say) — by then the page,
925
+ * and therefore the initial boot, has necessarily finished.
926
+ */
927
+ ready(): Promise<void>;
928
+
929
+ /**
930
+ * Schedules a repaint without touching data, scroll, edits, filters, or sort — for when
931
+ * something a `cellBackground`/`rowHighlighter` callback reads changed outside the grid (host
932
+ * state a plugin keeps, say) and the next frame needs to pick it up. Much cheaper than
933
+ * {@link JHGrid.refresh} when nothing about the data itself changed.
934
+ */
935
+ repaint(): void;
936
+
937
+ // Data reload
938
+ /** Reload all data from scratch (clears edits, filters, column widths, scroll). */
939
+ refresh(): void;
940
+
941
+ // Scroll
942
+ /**
943
+ * Scroll so that rowIndex is at the top of the viewport. When pagination is
944
+ * enabled, switches to the page containing rowIndex first.
945
+ */
946
+ scrollTo(rowIndex: number): void;
947
+
948
+ // Pagination
949
+ /** Jumps to a 0-based page, clamped to a valid page. No-op if pagination is not enabled. */
950
+ goToPage(page: number): void;
951
+ /** Advances to the next page. No-op at the last page or if pagination is not enabled. */
952
+ nextPage(): void;
953
+ /** Goes back to the previous page. No-op at the first page or if pagination is not enabled. */
954
+ prevPage(): void;
955
+ /** Returns the current 0-based page index, or 0 if pagination is not enabled. */
956
+ getCurrentPage(): number;
957
+ /** Returns the total number of pages, or 1 if pagination is not enabled. */
958
+ getPageCount(): number;
959
+
960
+ // Edit management
961
+ /** Return all unsaved edits: { [rowIndex]: { [fieldName]: value } } */
962
+ getEdits(): Record<number, Record<string, string>>;
963
+ /** Discard all unsaved edits and re-draw. */
964
+ clearEdits(): void;
965
+ /**
966
+ * Programmatically sets a single cell's value through the same edit/validation/
967
+ * undo/onCellChange pipeline as a manual edit (checkbox toggle, paste, fill).
968
+ * Throws if `row`/`field`/`value` are the wrong type, or `field` names an
969
+ * unknown column.
970
+ */
971
+ setCellValue(row: number, field: string, value: string): void;
972
+ /**
973
+ * Bulk counterpart to setCellValue() — applies every entry through the same
974
+ * pipeline as one edit/undo step and a single redraw, instead of one redraw
975
+ * per cell. Use this for large-scale updates (e.g. a header checkbox toggling
976
+ * every filtered row) where looping setCellValue() would redraw once per row.
977
+ * Throws under the same conditions as setCellValue().
978
+ */
979
+ setCellValues(entries: Array<{ row: number; field: string; value: string }>): void;
980
+
981
+ // Validation
982
+ /** Returns true if no cell currently fails its column's `validation` rules. */
983
+ isValid(): boolean;
984
+ /** Return all currently invalid cells: { [rowIndex]: { [fieldName]: message } } */
985
+ getInvalidCells(): Record<number, Record<string, string>>;
986
+ /**
987
+ * Re-validates every currently loaded (cached) row plus locally added rows
988
+ * against each column's `validation` rules. Like autoFitColumns()/printGrid(),
989
+ * only rows already loaded into the client cache are checked.
990
+ */
991
+ validateAll(): Record<number, Record<string, string>>;
992
+
993
+ // Undo / Redo
994
+ /**
995
+ * Undoes the last undoable action: a cell edit (single, paste, fill, or clear),
996
+ * or row/column add/delete/undelete.
997
+ * Also bound to Ctrl/Cmd+Z. No-op if there is nothing to undo.
998
+ */
999
+ undo(): void;
1000
+ /** Re-applies the last undone action. Also bound to Ctrl/Cmd+Y and Ctrl/Cmd+Shift+Z. No-op if there is nothing to redo. */
1001
+ redo(): void;
1002
+ /** Returns true if undo() would currently have an effect. */
1003
+ canUndo(): boolean;
1004
+ /** Returns true if redo() would currently have an effect. */
1005
+ canRedo(): boolean;
1006
+
1007
+ // Row selection
1008
+ /** Return the list of currently selected row indices (sorted ascending). Requires `rowSelection: 'single' | 'multi'`. */
1009
+ getSelectedRows(): number[];
1010
+ /** Deselect all rows. */
1011
+ clearRowSelection(): void;
1012
+
1013
+ // Filter / sort
1014
+ /** Remove all active filters/sort and reload data. */
1015
+ clearFilters(): void;
1016
+ /** Set a single column filter programmatically and reload. Pass null/'' to remove. */
1017
+ setFilter(field: string, value: string | null): void;
1018
+ /**
1019
+ * Set a Set-filter (exact-match checkbox selection, as opposed to setFilter()'s substring
1020
+ * match) for a single column and reload. Pass null/undefined to remove.
1021
+ */
1022
+ setFilterValues(field: string, values: string[] | null): void;
1023
+ /** Remove a single column filter (text or Set) and reload. */
1024
+ removeFilter(field: string): void;
1025
+ /**
1026
+ * Set the global quick filter (a single term searched across every column — interpretation
1027
+ * is entirely up to fetchData/fetchMeta, via state.quickFilter) and reload. Pass null/''
1028
+ * to clear. There is no built-in search box; wire this to your own input.
1029
+ */
1030
+ setQuickFilter(value: string | null): void;
1031
+ /** Current quick filter term ('' when inactive). */
1032
+ getQuickFilter(): string;
1033
+ /** Shorthand for setQuickFilter(''). */
1034
+ clearQuickFilter(): void;
1035
+ /** Replace all active sorts with a single sort key, then reload. */
1036
+ setSort(field: string, dir?: 'asc' | 'desc'): void;
1037
+ /** Remove the sort for a specific field and reload. No-op if not sorted. */
1038
+ removeSort(field: string): void;
1039
+ /** Clear all active sorts and reload. */
1040
+ clearSort(): void;
1041
+
1042
+ // Row data
1043
+ /** Return the current data for a row (with unsaved edits applied), or null if not loaded. */
1044
+ getRowData(rowIndex: number): Record<string, unknown> | null;
1045
+ /**
1046
+ * Returns the row's pre-edit snapshot -- what {@link getRowData} would have returned for this
1047
+ * index before any unsaved cell edits were applied. Stays correct across any
1048
+ * {@link addRow}/{@link deleteRow} calls that happen afterward (including ones from the
1049
+ * built-in row context menu), unlike a snapshot the caller took itself at fetch time. Useful
1050
+ * for a "what changed" diff, or as a stable WHERE-clause anchor on tables with no primary key.
1051
+ */
1052
+ getOriginalRowData(rowIndex: number): Record<string, unknown> | null;
1053
+ /** True if the row at this index was added via {@link addRow} and has no server-side counterpart yet. */
1054
+ isNewRow(rowIndex: number): boolean;
1055
+ /**
1056
+ * Tells the grid that the row at `rowIndex` has just been persisted with `savedData` as its
1057
+ * confirmed server-side value. Patches the cache in place and drops any pending edit for that
1058
+ * row, so the next edit's {@link getOriginalRowData}/{@link getEdits} picks up from here --
1059
+ * without the full reset (scroll position, filters, sort, selection) {@link refresh} does.
1060
+ * Meant for a host that saves incrementally (e.g. auto-save on commit).
1061
+ *
1062
+ * No-op if {@link isNewRow} is true for this index -- a brand new row has no server slot yet to
1063
+ * patch. Use {@link acknowledgeInsert} once that row's own create request lands instead.
1064
+ */
1065
+ acknowledgeSave(rowIndex: number, savedData: Record<string, unknown>): void;
1066
+ /**
1067
+ * Tells the grid that the row at `rowIndex` (added via {@link addRow}, anywhere -- a plain
1068
+ * append, an explicit `index`, or the row context menu) has just been created on the server with
1069
+ * `savedData` as its confirmed value. Turns it into an ordinary server row in place -- so the
1070
+ * next edit on it is treated as an update, not another insert -- without {@link refresh}'s full
1071
+ * reset (scroll position, filters, sort, selection).
1072
+ *
1073
+ * Returns `true` if the promotion happened, `false` if `rowIndex` was not a local (still-unsaved)
1074
+ * row -- nothing to promote.
1075
+ */
1076
+ acknowledgeInsert(rowIndex: number, savedData: Record<string, unknown>): boolean;
1077
+
1078
+ // Column auto-fit
1079
+ /**
1080
+ * Resizes one or more columns to fit their content (header label + cached data).
1081
+ * Columns with a custom renderer are skipped.
1082
+ * Pass no arguments to fit all visible columns.
1083
+ */
1084
+ autoFitColumns(...fields: string[]): void;
1085
+
1086
+ // Row height
1087
+ /** Changes the default pixel height used by every row without its own override. Throws if height is not a positive finite number. */
1088
+ setRowHeight(height: number): void;
1089
+ /** Sets rowIndex's individual pixel height (Excel-style row resize — same as dragging a row-number gutter boundary). Throws if height is not a positive finite number or rowIndex is out of range. */
1090
+ setRowHeight(rowIndex: number, height: number): void;
1091
+ /** Returns rowIndex's current pixel height (its own override, or the shared default). */
1092
+ getRowHeight(rowIndex: number): number;
1093
+ /** Clears rowIndex's individual height override, if any, back to the shared default. */
1094
+ resetRowHeight(rowIndex: number): void;
1095
+
1096
+ // Column visibility
1097
+ /** Hide a column. Has no effect if already hidden. */
1098
+ hideColumn(field: string): void;
1099
+ /** Show a hidden column, restoring it at its original position. */
1100
+ showColumn(field: string): void;
1101
+ /** Returns true if the column is currently visible. */
1102
+ isColumnVisible(field: string): boolean;
1103
+ /** Returns the list of currently hidden field names. */
1104
+ getHiddenColumns(): string[];
1105
+
1106
+ // Column add / delete (client-side)
1107
+ /**
1108
+ * Adds a new column (client-side only). Returns false if the field already exists
1109
+ * (visible, hidden, or deleted). Pass `index` to insert at a specific visual
1110
+ * position; defaults to the end. If both immediate neighbors of the insertion
1111
+ * point belong to the same opts.headerRows group, the new column is folded into
1112
+ * that group too.
1113
+ */
1114
+ addColumn(field: string, def?: Omit<ColumnDef, 'field'>, opts?: { index?: number }): boolean;
1115
+ /**
1116
+ * Deletes a column. Local columns (added via addColumn) are removed immediately.
1117
+ * Server columns are hidden from view — call getDeletedColumns() to collect
1118
+ * their field names for server-side processing, or undeleteColumn() to restore.
1119
+ */
1120
+ deleteColumn(field: string): boolean;
1121
+ /** Removes the deletion mark from a server column, restoring it at its original position. */
1122
+ undeleteColumn(field: string): void;
1123
+ /** Returns shallow copies of the definitions of all locally added columns (via addColumn). */
1124
+ getNewColumns(): (Omit<ColumnDef, 'field'> & { field: string })[];
1125
+ /** Returns the field names of all server columns marked for deletion. */
1126
+ getDeletedColumns(): string[];
1127
+ /**
1128
+ * Call after your own save request has persisted pending column changes to the server — marks
1129
+ * them as no longer pending (getNewColumns() / getDeletedColumns() stop reporting them) without
1130
+ * touching the rendered grid. Pass a field name, an array of field names, or omit to commit every
1131
+ * pending column change at once. Fields with no pending add/delete are silently ignored.
1132
+ */
1133
+ commitColumns(fields?: string | string[]): void;
1134
+
1135
+ // State serialization
1136
+ /** Serialize current column order, widths, visibility, sort and filters. */
1137
+ getState(): GridState;
1138
+ /**
1139
+ * Restore a previously serialized state. Unknown fields are ignored, so a snapshot from an
1140
+ * older version still applies as far as it goes.
1141
+ *
1142
+ * Resolves once the grid is showing that state. If the snapshot carries `sorts`, `filters` or
1143
+ * `quickFilter`, those only describe what the *server* should return, so the grid asks again and
1144
+ * the promise waits for the answer — otherwise the header would claim a filter over rows that
1145
+ * were never re-fetched. A snapshot that only moves columns around has nothing to ask for and
1146
+ * resolves immediately.
1147
+ *
1148
+ * Awaiting it matters when you read the grid afterwards; the synchronous parts (columns, widths,
1149
+ * visibility) are already applied by the time it returns, so a caller that just restores and
1150
+ * walks away can ignore it.
1151
+ */
1152
+ setState(state: Partial<GridState>): Promise<void>;
1153
+
1154
+ // Print
1155
+ /** Open a print-ready popup window containing the currently loaded rows. */
1156
+ printGrid(opts?: { title?: string; includeHeaders?: boolean }): void;
1157
+
1158
+ // Export
1159
+ /**
1160
+ * Download data as a CSV file.
1161
+ * By default only loaded (cached) chunks are included — pass `full: true`
1162
+ * to fetch every row matching the current filter/sort state (text filters
1163
+ * via the server, plus any active color filter) and export the complete
1164
+ * filtered dataset.
1165
+ */
1166
+ exportCsv(opts?: {
1167
+ filename?: string;
1168
+ delimiter?: string;
1169
+ includeHeaders?: boolean;
1170
+ bom?: boolean;
1171
+ /** When true, fetches every row matching the current filter/sort state (including any active color filter) and exports the complete filtered dataset. Default: false. */
1172
+ full?: boolean;
1173
+ }): Promise<void>;
1174
+
1175
+ // Row add / delete (client-side)
1176
+ /**
1177
+ * Adds a new row (client-side only). By default it appends at the bottom; `{ index }` puts it
1178
+ * at that visual position instead — **anywhere**, including between server rows. Returns the
1179
+ * zero-based visual index of the new row.
1180
+ *
1181
+ * The row is anchored to the record it precedes, not to the screen position, so it stays where
1182
+ * you put it as rows above are added or removed. A sort or filter is the one thing that moves
1183
+ * it: the anchor described a place in the old ordering, which the new one replaces, so the row
1184
+ * survives but goes to the end. It is never hidden by a filter — a blank new row matches
1185
+ * almost nothing, and hiding it is indistinguishable from having lost it.
1186
+ */
1187
+ addRow(rowData?: Record<string, unknown>, opts?: { index?: number }): number;
1188
+ /**
1189
+ * Deletes a row. What that means depends on where the row came from:
1190
+ *
1191
+ * - A local row (added via `addRow`) is always removed outright, whatever `permanent` says.
1192
+ * It was never sent anywhere, so there is nothing to tell a server about and nothing to mark.
1193
+ * - A server row is **marked** by default: dimmed with a strikethrough, still on screen, still
1194
+ * undoable, and reported by {@link getDeletedRows}.
1195
+ * - `{ permanent: true }` takes the server row off the screen instead. Rows below it move up.
1196
+ * It is reported by {@link getRemovedRows} and can still be brought back with
1197
+ * {@link undeleteRow}.
1198
+ *
1199
+ * Neither server case touches the server — the grid only records what you chose. A sort or
1200
+ * filter discards both kinds, since they are recorded against a row numbering the reload
1201
+ * replaces.
1202
+ */
1203
+ deleteRow(rowIndex: number, opts?: { permanent?: boolean }): void;
1204
+ /**
1205
+ * Undoes a `deleteRow`: clears the mark, or puts back a row removed with
1206
+ * `{ permanent: true }`. A removed row has no screen position while it is gone, so name it by
1207
+ * the server index {@link getRemovedRows} reported. No-op if the row is neither.
1208
+ */
1209
+ undeleteRow(rowIndex: number): void;
1210
+ /** Returns shallow copies of all locally added rows (via addRow). */
1211
+ getNewRows(): Record<string, unknown>[];
1212
+ /**
1213
+ * Server indices of rows marked for deletion — the ones still on screen with a strikethrough.
1214
+ * Server indices rather than screen positions, so removing some other row cannot change what
1215
+ * this names.
1216
+ */
1217
+ getDeletedRows(): number[];
1218
+ /**
1219
+ * Server indices of rows removed from the screen via `deleteRow(i, { permanent: true })`.
1220
+ * Kept separate from {@link getDeletedRows} because the two mean different things to whoever
1221
+ * chose them — a mark is still being decided, a removal has been decided — but both still need
1222
+ * deleting server-side.
1223
+ */
1224
+ getRemovedRows(): number[];
1225
+
1226
+ // Header checkbox
1227
+ /** Programmatically set the checked state of a header checkbox column and redraw. */
1228
+ setHeaderCheckbox(field: string, checked: boolean): void;
1229
+ /** Returns the current checked state of a header checkbox column. */
1230
+ getHeaderCheckbox(field: string): boolean;
1231
+
1232
+ // Lifecycle
1233
+ /** Remove all event listeners and clear the container DOM. */
1234
+ destroy(): void;
1235
+ }
1236
+
1237
+ // Renderer (advanced use)
1238
+
1239
+ export declare const DEFAULT_THEME: Required<GridTheme>;
1240
+
1241
+ export declare class Renderer {
1242
+ constructor(canvas: HTMLCanvasElement, opts: JHGridOptions & { theme: Required<GridTheme> });
1243
+ }
1244
+
1245
+ // DataManager (advanced use)
1246
+
1247
+ export declare class DataManager {
1248
+ onChunkLoaded: (() => void) | null;
1249
+ constructor(opts: {
1250
+ fetchData: JHGridOptions['fetchData'];
1251
+ chunkSize?: number;
1252
+ /** LRU limit — evicts oldest chunk when exceeded. Default: 50 */
1253
+ maxChunks?: number;
1254
+ });
1255
+ getRow(rowIndex: number): Record<string, unknown> | null;
1256
+ prefetch(startRow: number, endRow: number): void;
1257
+ setFetch(fn: JHGridOptions['fetchData']): void;
1258
+ forEachLoaded(callback: (row: Record<string, unknown>, rowIndex: number) => void): void;
1259
+ clear(): void;
1260
+ }
1261
+
1262
+ // Misc
1263
+
1264
+ /**
1265
+ * Class names applied to the grid's DOM surfaces (the canvas-painted body is styled through
1266
+ * {@link GridTheme} instead). Exposed so consumer code can target a surface without hardcoding
1267
+ * the strings — see "Styling with your own CSS" in the README for the full table and the
1268
+ * matching `--jhg-*` custom properties.
1269
+ */
1270
+ export declare const GRID_CLASSES: {
1271
+ root: string;
1272
+ loading: string;
1273
+ empty: string;
1274
+ tooltip: string;
1275
+ overlay: string;
1276
+ panel: string;
1277
+ dialog: string;
1278
+ menu: string;
1279
+ menuItem: string;
1280
+ btn: string;
1281
+ swatch: string;
1282
+ pager: string;
1283
+ pagerBtn: string;
1284
+ editor: string;
1285
+ };
1286
+
1287
+ export declare const VERSION: string;
1288
+
1289
+ export declare const SUPPORTED_BROWSERS: {
1290
+ chrome: number;
1291
+ edge: number;
1292
+ firefox: number;
1293
+ safari: number;
1294
+ };