@svgrid/grid 1.0.2 → 1.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.
Files changed (143) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +137 -39
  3. package/dist/GridFooter.svelte +164 -0
  4. package/dist/GridFooter.svelte.d.ts +29 -0
  5. package/dist/GridMenus.svelte +570 -0
  6. package/dist/GridMenus.svelte.d.ts +31 -0
  7. package/dist/SvGrid.controller.svelte.d.ts +429 -0
  8. package/dist/SvGrid.controller.svelte.js +1732 -0
  9. package/dist/SvGrid.css +1709 -0
  10. package/dist/SvGrid.helpers.d.ts +35 -0
  11. package/dist/SvGrid.helpers.js +160 -0
  12. package/dist/SvGrid.svelte +344 -7043
  13. package/dist/SvGrid.svelte.d.ts +4 -357
  14. package/dist/SvGrid.types.d.ts +436 -0
  15. package/dist/SvGrid.types.js +1 -0
  16. package/dist/SvGridChart.svelte +1060 -23
  17. package/dist/SvGridChart.svelte.d.ts +17 -0
  18. package/dist/build-api.d.ts +5 -0
  19. package/dist/build-api.js +527 -0
  20. package/dist/cell-render.d.ts +15 -0
  21. package/dist/cell-render.js +246 -0
  22. package/dist/cell-values.d.ts +28 -0
  23. package/dist/cell-values.js +89 -0
  24. package/dist/chart.d.ts +370 -3
  25. package/dist/chart.js +1135 -42
  26. package/dist/clipboard.d.ts +15 -0
  27. package/dist/clipboard.js +356 -0
  28. package/dist/columns.d.ts +30 -0
  29. package/dist/columns.js +277 -0
  30. package/dist/core.d.ts +1 -1
  31. package/dist/css.d.ts +3 -0
  32. package/dist/editing.d.ts +24 -0
  33. package/dist/editing.js +343 -0
  34. package/dist/editors/cell-editors.d.ts +1 -1
  35. package/dist/facet-buckets.d.ts +13 -0
  36. package/dist/facet-buckets.js +54 -0
  37. package/dist/features.d.ts +5 -0
  38. package/dist/features.js +30 -0
  39. package/dist/filter-operators.d.ts +16 -0
  40. package/dist/filter-operators.js +69 -0
  41. package/dist/hyperformula-adapter.d.ts +82 -0
  42. package/dist/hyperformula-adapter.js +73 -0
  43. package/dist/index.d.ts +5 -2
  44. package/dist/index.js +5 -2
  45. package/dist/keyboard-handlers.d.ts +7 -0
  46. package/dist/keyboard-handlers.js +197 -0
  47. package/dist/menus.d.ts +40 -0
  48. package/dist/menus.js +389 -0
  49. package/dist/named-views.d.ts +27 -0
  50. package/dist/named-views.js +39 -0
  51. package/dist/row-resize.d.ts +43 -0
  52. package/dist/row-resize.js +158 -0
  53. package/dist/scroll-sync.d.ts +9 -0
  54. package/dist/scroll-sync.js +86 -0
  55. package/dist/selection.d.ts +26 -0
  56. package/dist/selection.js +387 -0
  57. package/dist/spreadsheet.d.ts +80 -0
  58. package/dist/spreadsheet.js +194 -0
  59. package/dist/summaries.d.ts +12 -0
  60. package/dist/summaries.js +65 -0
  61. package/dist/svgrid-wrapper.types.d.ts +12 -1
  62. package/dist/svgrid.comments-autocomplete.test.d.ts +1 -0
  63. package/dist/svgrid.comments-autocomplete.test.js +96 -0
  64. package/dist/svgrid.context-menu.test.d.ts +1 -0
  65. package/dist/svgrid.context-menu.test.js +102 -0
  66. package/dist/svgrid.new-features.wrapper.test.js +30 -4
  67. package/dist/svgrid.wrapper.test.js +27 -1
  68. package/dist/virtualization/column-virtualizer.d.ts +2 -0
  69. package/dist/virtualization/scroll-scaling.d.ts +28 -0
  70. package/dist/virtualization/scroll-scaling.js +64 -0
  71. package/dist/virtualization/scroll-scaling.test.d.ts +1 -0
  72. package/dist/virtualization/scroll-scaling.test.js +86 -0
  73. package/dist/virtualization/svelte-virtualizer.svelte.d.ts +2 -0
  74. package/dist/virtualization/svelte-virtualizer.svelte.js +2 -0
  75. package/dist/virtualization/virtualizer.d.ts +7 -0
  76. package/dist/virtualization/virtualizer.js +30 -0
  77. package/package.json +1 -1
  78. package/src/GridFooter.svelte +164 -0
  79. package/src/GridMenus.svelte +570 -0
  80. package/src/SvGrid.controller.svelte.ts +2195 -0
  81. package/src/SvGrid.css +1747 -0
  82. package/src/SvGrid.helpers.test.ts +415 -0
  83. package/src/SvGrid.helpers.ts +185 -0
  84. package/src/SvGrid.svelte +348 -7043
  85. package/src/SvGrid.types.ts +456 -0
  86. package/src/SvGridChart.svelte +1060 -23
  87. package/src/build-api.coverage.test.ts +532 -0
  88. package/src/build-api.ts +663 -0
  89. package/src/cell-render.test.ts +451 -0
  90. package/src/cell-render.ts +426 -0
  91. package/src/cell-values.ts +114 -0
  92. package/src/chart-export.test.ts +370 -0
  93. package/src/chart.coverage.test.ts +814 -0
  94. package/src/chart.ts +1352 -47
  95. package/src/clipboard.test.ts +731 -0
  96. package/src/clipboard.ts +524 -0
  97. package/src/collaboration.coverage.test.ts +220 -0
  98. package/src/columns.test.ts +702 -0
  99. package/src/columns.ts +419 -0
  100. package/src/core.ts +8 -0
  101. package/src/css.d.ts +3 -0
  102. package/src/editing.test.ts +837 -0
  103. package/src/editing.ts +513 -0
  104. package/src/editors/cell-editors.coverage.test.ts +156 -0
  105. package/src/editors/cell-editors.ts +1 -0
  106. package/src/facet-buckets.test.ts +353 -0
  107. package/src/facet-buckets.ts +67 -0
  108. package/src/features.ts +128 -0
  109. package/src/filter-operators.test.ts +174 -0
  110. package/src/filter-operators.ts +87 -0
  111. package/src/hyperformula-adapter.test.ts +256 -0
  112. package/src/hyperformula-adapter.ts +124 -0
  113. package/src/index.ts +37 -0
  114. package/src/keyboard-handlers.coverage.test.ts +560 -0
  115. package/src/keyboard-handlers.ts +353 -0
  116. package/src/keyboard.ts +97 -97
  117. package/src/menus.test.ts +620 -0
  118. package/src/menus.ts +554 -0
  119. package/src/named-views.coverage.test.ts +210 -0
  120. package/src/named-views.ts +48 -0
  121. package/src/row-resize.test.ts +369 -0
  122. package/src/row-resize.ts +171 -0
  123. package/src/scroll-sync.test.ts +330 -0
  124. package/src/scroll-sync.ts +216 -0
  125. package/src/selection.test.ts +722 -0
  126. package/src/selection.ts +545 -0
  127. package/src/server-data-source.coverage.test.ts +180 -0
  128. package/src/spreadsheet.test.ts +445 -0
  129. package/src/spreadsheet.ts +246 -0
  130. package/src/summaries.ts +204 -0
  131. package/src/sv-grid-scrollbar.ts +13 -1
  132. package/src/svgrid-wrapper.types.ts +12 -1
  133. package/src/svgrid.behavior.test.ts +22 -0
  134. package/src/svgrid.comments-autocomplete.test.ts +112 -0
  135. package/src/svgrid.context-menu.test.ts +126 -0
  136. package/src/svgrid.interaction.test.ts +30 -0
  137. package/src/svgrid.new-features.wrapper.test.ts +65 -4
  138. package/src/svgrid.wrapper.test.ts +27 -1
  139. package/src/test-setup.ts +9 -6
  140. package/src/virtualization/scroll-scaling.test.ts +148 -0
  141. package/src/virtualization/scroll-scaling.ts +121 -0
  142. package/src/virtualization/svelte-virtualizer.svelte.ts +2 -0
  143. package/src/virtualization/virtualizer.ts +26 -0
@@ -19,6 +19,23 @@ type Props = {
19
19
  formatValue?: (value: number) => string;
20
20
  /** Fired when a category / slice is clicked (drill into the grid). */
21
21
  onSelect?: (selection: ChartSelection) => void;
22
+ /** Like `onSelect`, but only fires when the spec carries `rowIds` and
23
+ * includes them in the payload. Use this to filter / highlight the
24
+ * source grid when the user clicks a chart element. */
25
+ onDrill?: (selection: ChartSelection) => void;
26
+ /** Allow drag-to-zoom on the plot area. Double-click resets.
27
+ * Only meaningful for cartesian charts (not pie). Default false. */
28
+ zoomable?: boolean;
29
+ /** Show a compact brush / mini-map under the main chart. A draggable
30
+ * window selects what the main chart shows. Pairs naturally with
31
+ * `zoomable`. Default false. */
32
+ brush?: boolean;
33
+ /** Height of the brush strip in pixels. Default 88. */
34
+ brushHeight?: number;
35
+ /** Show a small toolbar above the chart: reset-zoom (when zoomed) and
36
+ * export (PNG / SVG / copy). Default true when zoomable or onDrill is
37
+ * set, otherwise false. Set to `false` to hide explicitly. */
38
+ toolbar?: boolean;
22
39
  };
23
40
  declare const SvGridChart: import("svelte").Component<Props, {}, "">;
24
41
  type SvGridChart = ReturnType<typeof SvGridChart>;
@@ -0,0 +1,5 @@
1
+ import { type RowData, type SvGridApi, type TableFeatures } from "./index";
2
+ import "./sv-grid-scrollbar";
3
+ export declare function createGridApi<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(ctx: any): {
4
+ buildApi: () => SvGridApi<TFeatures, TData>;
5
+ };
@@ -0,0 +1,527 @@
1
+ import "./sv-grid-scrollbar";
2
+ import { isGroupRow, columnDefMatchesId, } from "./cell-values";
3
+ export function createGridApi(ctx) {
4
+ function buildApi() {
5
+ const findColumn = (columnId) => ctx.grid.getAllColumns().find((column) => column.id === columnId);
6
+ return {
7
+ getCellValue(rowIndex, columnId) {
8
+ const row = ctx.internalData[rowIndex];
9
+ const column = findColumn(columnId);
10
+ if (!row || !column)
11
+ return undefined;
12
+ if (column.columnDef.accessorFn)
13
+ return column.columnDef.accessorFn(row);
14
+ if (column.columnDef.field)
15
+ return row[column.columnDef.field];
16
+ return undefined;
17
+ },
18
+ setCellValue(rowIndex, columnId, value) {
19
+ const row = ctx.internalData[rowIndex];
20
+ const column = findColumn(columnId);
21
+ if (!row || !column?.columnDef.field)
22
+ return;
23
+ const next = ctx.internalData.slice();
24
+ next[rowIndex] = { ...row, [column.columnDef.field]: value };
25
+ ctx.internalData = next;
26
+ },
27
+ selectCells(ranges) {
28
+ // Engine supports a single anchor/focus rectangle today. Honour
29
+ // the first entry; ignore the rest. Empty array clears.
30
+ if (!ranges || ranges.length === 0) {
31
+ ctx.selectionRange = { anchor: null, focus: null };
32
+ return;
33
+ }
34
+ const [r1, c1, r2, c2] = ranges[0];
35
+ const rowCount = ctx.internalData.length;
36
+ const colCount = ctx.allColumns.length;
37
+ if (rowCount === 0 || colCount === 0)
38
+ return;
39
+ // Clamp to the visible grid bounds so callers can pass open-ended
40
+ // values like `[0, 0, Infinity, Infinity]` to mean "select all".
41
+ const aRow = Math.max(0, Math.min(rowCount - 1, Math.min(r1, r2)));
42
+ const fRow = Math.max(0, Math.min(rowCount - 1, Math.max(r1, r2)));
43
+ const aCol = Math.max(0, Math.min(colCount - 1, Math.min(c1, c2)));
44
+ const fCol = Math.max(0, Math.min(colCount - 1, Math.max(c1, c2)));
45
+ ctx.selectionRange = {
46
+ anchor: { rowIndex: aRow, colIndex: aCol },
47
+ focus: { rowIndex: fRow, colIndex: fCol },
48
+ };
49
+ // Move the active cell to the range's top-left so keyboard
50
+ // navigation continues from there.
51
+ ctx.setActiveCell(aRow, aCol);
52
+ },
53
+ getSelected() {
54
+ const a = ctx.selectionRange.anchor;
55
+ const f = ctx.selectionRange.focus;
56
+ if (!a || !f)
57
+ return [];
58
+ const r1 = Math.min(a.rowIndex, f.rowIndex);
59
+ const r2 = Math.max(a.rowIndex, f.rowIndex);
60
+ const c1 = Math.min(a.colIndex, f.colIndex);
61
+ const c2 = Math.max(a.colIndex, f.colIndex);
62
+ return [[r1, c1, r2, c2]];
63
+ },
64
+ addRow(row, position = "bottom") {
65
+ this.addRows([row], position);
66
+ },
67
+ addRows(rows, position = "bottom") {
68
+ const next = ctx.internalData.slice();
69
+ if (position === "top")
70
+ next.unshift(...rows);
71
+ else if (position === "bottom")
72
+ next.push(...rows);
73
+ else if (typeof position === "number")
74
+ next.splice(Math.max(0, Math.min(position, next.length)), 0, ...rows);
75
+ ctx.internalData = next;
76
+ },
77
+ removeRow(rowIndex) {
78
+ this.removeRows([rowIndex]);
79
+ },
80
+ removeRows(rowIndices) {
81
+ const drop = new Set(rowIndices);
82
+ ctx.internalData = ctx.internalData.filter((_, i) => !drop.has(i));
83
+ },
84
+ applyTransaction(tx) {
85
+ const getId = ctx.props.getRowId;
86
+ let next = ctx.internalData.slice();
87
+ let added = 0;
88
+ let updated = 0;
89
+ let removed = 0;
90
+ if (tx.remove?.length) {
91
+ const removeIds = new Set();
92
+ const removeRefs = new Set();
93
+ for (const r of tx.remove) {
94
+ if (typeof r === "string")
95
+ removeIds.add(r);
96
+ else
97
+ removeRefs.add(r);
98
+ }
99
+ next = next.filter((row, i) => {
100
+ const hit = removeRefs.has(row) ||
101
+ (getId ? removeIds.has(getId(row, i)) : false);
102
+ if (hit)
103
+ removed += 1;
104
+ return !hit;
105
+ });
106
+ }
107
+ if (tx.update?.length && getId) {
108
+ const byId = new Map();
109
+ for (const u of tx.update)
110
+ byId.set(getId(u, 0), u);
111
+ next = next.map((row, i) => {
112
+ const u = byId.get(getId(row, i));
113
+ if (u) {
114
+ updated += 1;
115
+ return u;
116
+ }
117
+ return row;
118
+ });
119
+ }
120
+ if (tx.add?.length) {
121
+ next.push(...tx.add);
122
+ added += tx.add.length;
123
+ }
124
+ ctx.internalData = next;
125
+ return { added, updated, removed };
126
+ },
127
+ addColumn(column, position = "right") {
128
+ this.addColumns([column], position);
129
+ },
130
+ addColumns(columns, position = "right") {
131
+ const next = ctx.internalColumns.slice();
132
+ if (position === "left")
133
+ next.unshift(...columns);
134
+ else if (position === "right")
135
+ next.push(...columns);
136
+ else if (typeof position === "number")
137
+ next.splice(Math.max(0, Math.min(position, next.length)), 0, ...columns);
138
+ ctx.internalColumns = next;
139
+ },
140
+ removeColumn(columnId) {
141
+ ctx.internalColumns = ctx.internalColumns.filter((def) => !columnDefMatchesId(def, columnId));
142
+ },
143
+ setColumnVisible(columnId, visible) {
144
+ if (visible) {
145
+ const next = { ...ctx.hiddenColumns };
146
+ delete next[columnId];
147
+ ctx.hiddenColumns = next;
148
+ }
149
+ else {
150
+ ctx.hiddenColumns = { ...ctx.hiddenColumns, [columnId]: true };
151
+ }
152
+ },
153
+ isColumnVisible(columnId) {
154
+ return !ctx.hiddenColumns[columnId];
155
+ },
156
+ setSort(columnId, direction) {
157
+ // Honour the column's `sortable` opt-out. Asking the engine
158
+ // whether the column can sort folds in both the feature toggle
159
+ // AND the per-column flag.
160
+ const col = ctx.allColumns.find((c) => c.id === columnId);
161
+ if (col && !col.getCanSort?.())
162
+ return;
163
+ const clauses = direction
164
+ ? [{ id: columnId, desc: direction === "desc" }]
165
+ : (ctx.grid.getState().sorting ?? []).filter((entry) => entry.id !== columnId);
166
+ ctx.grid.store.setState((prev) => ({ ...prev, sorting: clauses }));
167
+ },
168
+ clearSort() {
169
+ ctx.grid.store.setState((prev) => ({ ...prev, sorting: [] }));
170
+ },
171
+ setGroupBy(columnIds) {
172
+ ctx.grid.setGrouping([...columnIds]);
173
+ },
174
+ setFilter(columnId, filter) {
175
+ // Honour the column's `filterable` opt-out. Same approach as
176
+ // `setSort` - `getCanFilter()` already combines the feature flag
177
+ // and the per-column field.
178
+ const col = ctx.allColumns.find((c) => c.id === columnId);
179
+ if (col && !col.getCanFilter?.())
180
+ return;
181
+ if (!filter) {
182
+ this.clearFilter(columnId);
183
+ return;
184
+ }
185
+ ctx.filterMenuValues = {
186
+ ...ctx.filterMenuValues,
187
+ [columnId]: {
188
+ operator: filter.operator,
189
+ value: filter.value ?? "",
190
+ ...(filter.operator === "between"
191
+ ? { valueTo: filter.valueTo ?? "" }
192
+ : {}),
193
+ },
194
+ };
195
+ },
196
+ setFacetFilter(columnId, values) {
197
+ // Empty array OR null both mean "clear this column's facet" -
198
+ // mirrors how the column menu treats unchecking the last value.
199
+ if (!values || values.length === 0) {
200
+ if (ctx.valueFilters[columnId]) {
201
+ const next = { ...ctx.valueFilters };
202
+ delete next[columnId];
203
+ ctx.valueFilters = next;
204
+ }
205
+ return;
206
+ }
207
+ ctx.valueFilters = {
208
+ ...ctx.valueFilters,
209
+ [columnId]: new Set(values),
210
+ };
211
+ },
212
+ clearFilter(columnId) {
213
+ ctx.clearColumnFilter(columnId);
214
+ },
215
+ clearAllFilters() {
216
+ // Wipe every filter surface in one go: column-menu, filter-row, set
217
+ // filters, and the global search box.
218
+ ctx.filterMenuValues = {};
219
+ ctx.filterRowValues = {};
220
+ ctx.valueFilters = {};
221
+ if (ctx.globalFilter !== "")
222
+ ctx.globalFilter = "";
223
+ },
224
+ getFilters() {
225
+ // Return a defensive copy so callers can't mutate internal state.
226
+ const out = {};
227
+ for (const [columnId, filter] of Object.entries(ctx.filterMenuValues)) {
228
+ out[columnId] = {
229
+ operator: filter.operator,
230
+ value: filter.value,
231
+ ...(filter.operator === "between" && filter.valueTo
232
+ ? { valueTo: filter.valueTo }
233
+ : {}),
234
+ };
235
+ }
236
+ return out;
237
+ },
238
+ getDisplayedRows() {
239
+ // `allRows` is the final, post-filter, post-sort, post-group,
240
+ // post-pagination list - exactly what the body renders. Skip group
241
+ // header rows (they wrap an aggregate, not a TData row).
242
+ const out = [];
243
+ for (const row of ctx.allRows) {
244
+ if (row.subRows && row.subRows.length > 0)
245
+ continue;
246
+ out.push(row.original);
247
+ }
248
+ return out;
249
+ },
250
+ getData() {
251
+ return ctx.internalData;
252
+ },
253
+ getColumns() {
254
+ // Snapshot every column in visual order with its human-readable
255
+ // label and visibility flag. Used by external code (exporters,
256
+ // column pickers) so they don't have to re-walk the columnDef
257
+ // tree themselves.
258
+ return ctx.allColumns.map((c) => ({
259
+ id: c.id,
260
+ field: c.columnDef.field,
261
+ header: typeof c.columnDef.header === "string"
262
+ ? c.columnDef.header
263
+ : c.id,
264
+ visible: !ctx.hiddenColumns[c.id],
265
+ }));
266
+ },
267
+ clearRowSelection() {
268
+ // Wipe the internal selection map AND emit the change so any
269
+ // consumer holding a derived `selectedRows` resets too.
270
+ ctx.grid.setRowSelection(() => ({}));
271
+ ctx.props.onRowSelectionChange?.({}, []);
272
+ },
273
+ setColumnWidth(columnId, width) {
274
+ const clamped = Math.max(ctx.MIN_COLUMN_WIDTH, Math.floor(width));
275
+ ctx.columnWidths = { ...ctx.columnWidths, [columnId]: clamped };
276
+ },
277
+ getColumnWidths() {
278
+ // Resolve every column's effective width by consulting both the
279
+ // resize overrides (`columnWidths`) AND the columnDef defaults,
280
+ // so the snapshot is round-trippable through setColumnWidth.
281
+ const out = {};
282
+ for (const c of ctx.allColumns)
283
+ out[c.id] = ctx.getColumnWidth(c.id);
284
+ return out;
285
+ },
286
+ autosizeColumn(columnId) {
287
+ ctx.autosizeColumn(columnId);
288
+ },
289
+ autosizeAllColumns() {
290
+ ctx.autosizeAllColumns();
291
+ },
292
+ setColumnPinning(pinning) {
293
+ // Defensive copy + dedupe so callers can't mutate our state.
294
+ const left = Array.from(new Set(pinning.left ?? []));
295
+ const right = Array.from(new Set(pinning.right ?? []));
296
+ ctx.columnPinning = { left, right };
297
+ },
298
+ getColumnPinning() {
299
+ return {
300
+ left: ctx.columnPinning.left.slice(),
301
+ right: ctx.columnPinning.right.slice(),
302
+ };
303
+ },
304
+ // ---- Column reorder
305
+ setColumnOrder(order) {
306
+ ctx.setColumnOrderInternal(order);
307
+ },
308
+ getColumnOrder() {
309
+ return ctx.getCurrentColumnOrder();
310
+ },
311
+ setRowExpanded(id, expanded) {
312
+ ctx.grid.setExpanded((prev) => ({ ...prev, [id]: !!expanded }));
313
+ },
314
+ expandAllGroups() {
315
+ // Walk the current grouped row model and flip every group node on.
316
+ const next = {};
317
+ const walk = (rows) => {
318
+ for (const row of rows) {
319
+ if (row.getCanExpand?.())
320
+ next[row.id] = true;
321
+ const sub = row.subRows;
322
+ if (sub && sub.length > 0)
323
+ walk(sub);
324
+ }
325
+ };
326
+ walk(ctx.grid.getRowModel().rows);
327
+ ctx.grid.setExpanded(() => next);
328
+ },
329
+ collapseAllGroups() {
330
+ ctx.grid.setExpanded(() => ({}));
331
+ },
332
+ // ---- Undo / redo (history + pointer)
333
+ undo() {
334
+ if (ctx.historyPtr < 0)
335
+ return false;
336
+ const step = ctx.history[ctx.historyPtr];
337
+ if (!step)
338
+ return false;
339
+ ctx.applyHistoryStep(step, 'undo');
340
+ ctx.historyPtr -= 1;
341
+ ctx.historyVersion += 1;
342
+ return true;
343
+ },
344
+ redo() {
345
+ if (ctx.historyPtr >= ctx.history.length - 1)
346
+ return false;
347
+ const step = ctx.history[ctx.historyPtr + 1];
348
+ if (!step)
349
+ return false;
350
+ ctx.applyHistoryStep(step, 'redo');
351
+ ctx.historyPtr += 1;
352
+ ctx.historyVersion += 1;
353
+ return true;
354
+ },
355
+ canUndo() { void ctx.historyVersion; return ctx.historyPtr >= 0; },
356
+ canRedo() { void ctx.historyVersion; return ctx.historyPtr < ctx.history.length - 1; },
357
+ clearHistory() { ctx.history = []; ctx.historyPtr = -1; ctx.historyVersion += 1; },
358
+ // ---- Find
359
+ openFind() { ctx.findOpen = true; },
360
+ closeFind() { ctx.findOpen = false; ctx.findQuery = ''; },
361
+ setFindQuery(q) { ctx.findQuery = q; ctx.findHitIndex = 0; },
362
+ getFindHits() { return ctx.findHits.slice(); },
363
+ // ---- Row selection (read + write)
364
+ getSelectedRows() {
365
+ const out = [];
366
+ for (const row of ctx.allRows) {
367
+ if (isGroupRow(row))
368
+ continue;
369
+ if (ctx.rowSelectionState[row.id])
370
+ out.push(row.original);
371
+ }
372
+ return out;
373
+ },
374
+ getSelectedRowIds() {
375
+ const out = [];
376
+ for (const row of ctx.allRows) {
377
+ if (!isGroupRow(row) && ctx.rowSelectionState[row.id])
378
+ out.push(row.id);
379
+ }
380
+ return out;
381
+ },
382
+ selectRows(ids, additive = false) {
383
+ const set = new Set(ids);
384
+ ctx.grid.setRowSelection((prev) => {
385
+ const next = additive ? { ...prev } : {};
386
+ for (const id of set)
387
+ next[id] = true;
388
+ return next;
389
+ });
390
+ },
391
+ selectAllRows() {
392
+ const next = {};
393
+ for (const row of ctx.allRows)
394
+ if (!isGroupRow(row))
395
+ next[row.id] = true;
396
+ ctx.grid.setRowSelection(() => next);
397
+ },
398
+ toggleRowSelected(id) {
399
+ ctx.toggleRowSelectionById(id);
400
+ },
401
+ // ---- Pagination
402
+ getPageInfo() {
403
+ const { pageIndex, pageSize } = ctx.paginationState;
404
+ const total = ctx.allRowsBeforePagination.length;
405
+ const pageCount = Math.max(1, Math.ceil(total / Math.max(1, pageSize)));
406
+ return { pageIndex, pageSize, pageCount, total };
407
+ },
408
+ setPage(pageIndex) {
409
+ const total = ctx.allRowsBeforePagination.length;
410
+ const pageCount = Math.max(1, Math.ceil(total / Math.max(1, ctx.paginationState.pageSize)));
411
+ ctx.goToPage(Math.max(0, Math.min(pageIndex, pageCount - 1)));
412
+ },
413
+ nextPage() {
414
+ this.setPage(ctx.paginationState.pageIndex + 1);
415
+ },
416
+ prevPage() {
417
+ this.setPage(ctx.paginationState.pageIndex - 1);
418
+ },
419
+ firstPage() {
420
+ this.setPage(0);
421
+ },
422
+ lastPage() {
423
+ const total = ctx.allRowsBeforePagination.length;
424
+ const pageCount = Math.max(1, Math.ceil(total / Math.max(1, ctx.paginationState.pageSize)));
425
+ this.setPage(pageCount - 1);
426
+ },
427
+ setPageSize(size) {
428
+ // Calls the component-level helper (lexical scope), not this method.
429
+ ctx.setPageSize(Math.max(1, Math.floor(size)));
430
+ },
431
+ // ---- Navigation / scrolling
432
+ scrollToRow(rowIndex) {
433
+ if (!ctx.scrollContainer)
434
+ return;
435
+ const maxIndex = Math.max(0, ctx.allRows.length - 1);
436
+ const clamped = Math.max(0, Math.min(rowIndex, maxIndex));
437
+ // Logical offset from the virtualizer (honors uniform OR per-row
438
+ // sizing), then mapped into the capped DOM scroll space so it lands
439
+ // correctly on huge grids where the DOM height is scaled down.
440
+ const logical = ctx.virtualizer.getOffsetForIndex(clamped);
441
+ ctx.scrollContainer.scrollTop = ctx.logicalToDomRowOffset(logical);
442
+ ctx.scheduleScrollSync(ctx.scrollContainer.scrollTop, ctx.scrollContainer.scrollLeft);
443
+ },
444
+ getActiveCell() {
445
+ const a = ctx.activeCell;
446
+ if (!a || a.rowIndex < 0 || a.colIndex < 0)
447
+ return null;
448
+ const col = ctx.allColumns[a.colIndex];
449
+ return {
450
+ rowIndex: a.rowIndex,
451
+ colIndex: a.colIndex,
452
+ columnId: col?.id ?? "",
453
+ };
454
+ },
455
+ setActiveCell(rowIndex, colIndex) {
456
+ const r = Math.max(0, Math.min(rowIndex, Math.max(0, ctx.allRows.length - 1)));
457
+ const c = Math.max(0, Math.min(colIndex, Math.max(0, ctx.allColumns.length - 1)));
458
+ // Calls the component-level helper (lexical scope), not this method.
459
+ ctx.setActiveCell(r, c);
460
+ },
461
+ // ---- View state (save / restore)
462
+ getState() {
463
+ return {
464
+ sorting: (ctx.grid.getState().sorting ?? []).map((s) => ({ id: s.id, desc: s.desc })),
465
+ grouping: [...(ctx.grid.getState().grouping ?? [])],
466
+ pagination: {
467
+ pageIndex: ctx.paginationState.pageIndex,
468
+ pageSize: ctx.paginationState.pageSize,
469
+ },
470
+ columnWidths: this.getColumnWidths(),
471
+ columnPinning: this.getColumnPinning(),
472
+ columnOrder: ctx.getCurrentColumnOrder(),
473
+ hiddenColumns: Object.keys(ctx.hiddenColumns).filter((k) => ctx.hiddenColumns[k]),
474
+ globalFilter: ctx.globalFilter,
475
+ columnFilters: this.getFilters(),
476
+ facetFilters: Object.fromEntries(Object.entries(ctx.valueFilters).map(([k, set]) => [
477
+ k,
478
+ Array.from(set),
479
+ ])),
480
+ };
481
+ },
482
+ setState(state) {
483
+ if (state.sorting) {
484
+ const sorting = state.sorting.map((s) => ({ id: s.id, desc: s.desc }));
485
+ ctx.grid.store.setState((prev) => ({ ...prev, sorting }));
486
+ }
487
+ if (state.grouping)
488
+ ctx.grid.setGrouping([...state.grouping]);
489
+ if (state.pagination) {
490
+ const p = state.pagination;
491
+ ctx.grid.setPagination((prev) => ({ ...prev, ...p }));
492
+ }
493
+ if (state.columnWidths)
494
+ ctx.columnWidths = { ...state.columnWidths };
495
+ if (state.columnPinning)
496
+ ctx.columnPinning = {
497
+ left: [...(state.columnPinning.left ?? [])],
498
+ right: [...(state.columnPinning.right ?? [])],
499
+ };
500
+ if (state.columnOrder)
501
+ ctx.setColumnOrderInternal(state.columnOrder);
502
+ if (state.hiddenColumns) {
503
+ const next = {};
504
+ for (const id of state.hiddenColumns)
505
+ next[id] = true;
506
+ ctx.hiddenColumns = next;
507
+ }
508
+ if (state.globalFilter !== undefined)
509
+ ctx.globalFilter = state.globalFilter;
510
+ if (state.columnFilters)
511
+ ctx.filterMenuValues = { ...state.columnFilters };
512
+ if (state.facetFilters) {
513
+ const next = {};
514
+ for (const [k, arr] of Object.entries(state.facetFilters))
515
+ next[k] = new Set(arr);
516
+ ctx.valueFilters = next;
517
+ }
518
+ },
519
+ refresh() {
520
+ ctx.grid.store.setState((prev) => ({ ...prev }));
521
+ },
522
+ };
523
+ }
524
+ return {
525
+ buildApi,
526
+ };
527
+ }
@@ -0,0 +1,15 @@
1
+ import { type CellEditorOption, type Column, type Row, type RowData, type TableFeatures } from "./index";
2
+ import "./sv-grid-scrollbar";
3
+ import { type ResolvedCellFormat } from "./conditional-formatting";
4
+ export declare function createCellRender<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(ctx: any): {
5
+ cellConditionalFormat: (row: Row<TData>, column: Column<TData>, value: unknown) => ResolvedCellFormat | null;
6
+ computeRowClass: (row: Row<TData>, rowIndex: number) => string;
7
+ computeCellClass: (row: Row<TData>, column: Column<TData>) => string;
8
+ computeCellTooltip: (row: Row<TData>, column: Column<TData>) => string | null;
9
+ computeCellNote: (row: Row<TData>, column: Column<TData>) => string | null;
10
+ getColumnEditorOptions: (column: Column<TData>, row?: Row<TData> | null) => CellEditorOption[];
11
+ formatListCellValue: (column: Column<TData>, value: unknown, row?: Row<TData> | null) => string;
12
+ formatCellValue: (column: Column<TData>, value: unknown, row: Row<TData>) => string;
13
+ formatPinnedValue: (column: Column<TData>, value: unknown) => string;
14
+ computePinnedCellClass: (rowData: TData, column: Column<TData>) => string;
15
+ };