@ornery/ui-grid-react 0.1.4 → 0.1.6

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/dist/index.mjs CHANGED
@@ -1,13 +1,42 @@
1
+ // src/UiGrid.tsx
2
+ import React from "react";
3
+
1
4
  // src/useGridState.ts
2
5
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+
7
+ // src/gridStateMath.ts
8
+ import { gridColumnWidth } from "@ornery/ui-grid";
9
+ function orderVisibleColumns(columns, order) {
10
+ return [...columns].filter((column) => column.visible !== false).sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
11
+ }
12
+ function buildGridTemplateColumns(columns) {
13
+ return columns.map((column) => gridColumnWidth(column)).join(" ");
14
+ }
15
+ function resolveBenchmarkIterations(iterations, configuredIterations) {
16
+ return Math.max(1, iterations ?? configuredIterations ?? 25);
17
+ }
18
+ function formatPaginationSummary(totalItems, firstRowIndex, lastRowIndex) {
19
+ if (totalItems === 0) {
20
+ return "0-0 of 0";
21
+ }
22
+ return `${firstRowIndex + 1}-${lastRowIndex + 1} of ${totalItems}`;
23
+ }
24
+ function computeViewportHeightPx(viewportHeight, autoViewportHeight) {
25
+ return `${viewportHeight ?? autoViewportHeight ?? 560}px`;
26
+ }
27
+ function computeViewportRows(viewportHeight, rowHeight) {
28
+ return Math.max(1, Math.ceil((viewportHeight ?? 560) / (rowHeight ?? 44)));
29
+ }
30
+
31
+ // src/useGridState.ts
3
32
  import {
4
33
  createGridApi,
5
- getCellValue as getCellValue2,
34
+ getCellValue,
6
35
  setPathValue,
7
- SORT_DIRECTIONS as SORT_DIRECTIONS2,
8
- buildGridPipeline,
36
+ SORT_DIRECTIONS,
37
+ defaultGridEngine,
9
38
  resolveGridLabels,
10
- gridColumnWidth,
39
+ gridColumnWidth as gridColumnWidth2,
11
40
  headerLabel as coreHeaderLabel,
12
41
  gridSortButtonLabel,
13
42
  gridSortAriaSort,
@@ -59,584 +88,87 @@ import {
59
88
  FEATURE_INFINITE_SCROLL,
60
89
  FEATURE_COLUMN_MOVING,
61
90
  FEATURE_CSV_EXPORT,
62
- FEATURE_AUTO_RESIZE
91
+ FEATURE_AUTO_RESIZE,
92
+ FEATURE_PINNING,
93
+ buildInitialPinnedState,
94
+ computePinnedOffset,
95
+ isColumnPinnable,
96
+ isPinningEnabled,
97
+ applyGridSortStateCommand,
98
+ updateGridFilterCommand,
99
+ clearGridFiltersCommand,
100
+ clearGridGroupingCommand,
101
+ moveGridColumnCommand,
102
+ seekGridPaginationCommand,
103
+ setGridPaginationPageSizeCommand,
104
+ sortGridColumnCommand,
105
+ toggleGridRowExpansionCommand,
106
+ expandAllGridRowsCommand,
107
+ collapseAllGridRowsCommand,
108
+ toggleGridTreeRowCommand,
109
+ setGridTreeRowExpandedCommand,
110
+ expandAllGridTreeRowsCommand,
111
+ collapseAllGridTreeRowsCommand,
112
+ beginGridCellEditCommand,
113
+ commitGridCellEditCommand,
114
+ cancelGridCellEditCommand,
115
+ maybeRequestInfiniteScrollCommand,
116
+ completeGridInfiniteScrollDataLoadCommand,
117
+ resetGridInfiniteScrollCommand,
118
+ saveGridInfiniteScrollPercentageCommand,
119
+ setGridInfiniteScrollDirectionsCommand,
120
+ restoreGridStateCommand,
121
+ pinGridColumnCommand,
122
+ raiseGridRenderingComplete,
123
+ raiseGridRowsRendered,
124
+ raiseGridRowsVisibleChanged,
125
+ raiseGridCanvasHeightChanged,
126
+ raiseGridDimensionChanged,
127
+ raiseGridScrollBegin,
128
+ raiseGridScrollEnd,
129
+ raiseGridBenchmarkComplete,
130
+ downloadGridCsvFile,
131
+ observeGridHostSize
63
132
  } from "@ornery/ui-grid";
64
-
65
- // ../ui-grid/src/lib/grid/grid.core.pagination.ts
66
- function seekGridPage(page, totalPages) {
67
- return Math.min(Math.max(page, 1), Math.max(totalPages, 1));
68
- }
69
- function resolveGridPageSize(pageSize) {
70
- return Number.isFinite(pageSize) && pageSize > 0 ? pageSize : null;
71
- }
72
-
73
- // ../ui-grid/src/lib/grid/grid.constants.ts
74
- var SORT_DIRECTIONS = {
75
- asc: "asc",
76
- desc: "desc",
77
- none: "none"
78
- };
79
-
80
- // ../ui-grid/src/lib/grid/grid.utils.ts
81
- var PROTECTED_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
82
- function getPathValue(record, path) {
83
- return path.split(".").reduce((current, part) => {
84
- if (current === null || current === void 0 || typeof current !== "object") {
85
- return void 0;
86
- }
87
- if (PROTECTED_PATH_SEGMENTS.has(part)) {
88
- return void 0;
89
- }
90
- if (!Object.prototype.hasOwnProperty.call(current, part)) {
91
- return void 0;
92
- }
93
- return current[part];
94
- }, record);
95
- }
96
- function getCellValue(row, column) {
97
- if (column.valueGetter) {
98
- return column.valueGetter(row);
99
- }
100
- if (column.field) {
101
- return getPathValue(row, column.field);
102
- }
103
- return row[column.name];
104
- }
105
-
106
- // ../ui-grid/src/lib/grid/grid.core.identity.ts
107
- function buildGridSortState(columnName, direction) {
108
- return {
109
- columnName,
110
- direction: direction ?? SORT_DIRECTIONS.asc
111
- };
112
- }
113
-
114
- // ../ui-grid/src/lib/grid/grid.core.edit.ts
115
- function beginGridEditSession(rowId, columnName, editingValue) {
116
- const position = { rowId, columnName };
117
- return {
118
- focusedCell: position,
119
- editingCell: position,
120
- editingValue
121
- };
122
- }
123
- function clearGridEditSession() {
124
- return {
125
- editingCell: null,
126
- editingValue: ""
127
- };
128
- }
129
- function stringifyGridEditorValue(value) {
130
- if (value instanceof Date) {
131
- return value.toISOString().slice(0, 10);
133
+ function escapeCssSelectorValue(value) {
134
+ const nativeEscape = globalThis.CSS?.escape;
135
+ if (typeof nativeEscape === "function") {
136
+ return nativeEscape(value);
132
137
  }
133
- return value === null || value === void 0 ? "" : String(value);
134
- }
135
-
136
- // ../ui-grid/src/lib/grid/grid.core.infinite-scroll.ts
137
- function maybeRequestInfiniteScrollData(context) {
138
- if (context.state.dataLoading) {
139
- return { request: null, nextState: context.state };
140
- }
141
- if (context.state.scrollUp && context.startIndex <= context.threshold) {
142
- return {
143
- request: "top",
144
- nextState: {
145
- ...context.state,
146
- dataLoading: true,
147
- previousVisibleRows: context.visibleRows
148
- }
149
- };
150
- }
151
- if (context.state.scrollDown && context.startIndex + context.viewportRows >= Math.max(context.visibleRows - context.threshold, 0)) {
152
- return {
153
- request: "bottom",
154
- nextState: {
155
- ...context.state,
156
- dataLoading: true,
157
- previousVisibleRows: context.visibleRows
158
- }
159
- };
160
- }
161
- return { request: null, nextState: context.state };
162
- }
163
- function completeInfiniteScrollDataLoad(state, scrollUp, scrollDown) {
164
- return {
165
- ...state,
166
- scrollUp,
167
- scrollDown,
168
- dataLoading: false
169
- };
170
- }
171
- function resetInfiniteScrollState(scrollUp, scrollDown) {
172
- return {
173
- scrollUp,
174
- scrollDown,
175
- dataLoading: false,
176
- previousVisibleRows: 0
177
- };
178
- }
179
- function saveInfiniteScrollPercentage(state, visibleRows) {
180
- return {
181
- ...state,
182
- previousVisibleRows: visibleRows
183
- };
184
- }
185
- function setInfiniteScrollDirectionsState(state, scrollUp, scrollDown) {
186
- return {
187
- ...state,
188
- scrollUp,
189
- scrollDown
190
- };
191
- }
192
-
193
- // ../ui-grid/src/lib/grid/grid.core.row-state.ts
194
- function toggleGridRowExpanded(expandedRows, rowId) {
195
- const expanded = !expandedRows[rowId];
196
- return {
197
- expanded,
198
- nextExpandedRows: {
199
- ...expandedRows,
200
- [rowId]: expanded
138
+ let output = "";
139
+ for (let index = 0; index < value.length; index += 1) {
140
+ const codePoint = value.charCodeAt(index);
141
+ const character = value.charAt(index);
142
+ if (codePoint === 0) {
143
+ output += "\uFFFD";
144
+ continue;
201
145
  }
202
- };
203
- }
204
- function expandAllGridRows(rows) {
205
- const nextExpandedRows = {};
206
- for (const row of rows) {
207
- nextExpandedRows[row.id] = true;
208
- }
209
- return nextExpandedRows;
210
- }
211
- function setGridTreeRowExpanded(expandedTreeRows, rowId, expanded) {
212
- return {
213
- ...expandedTreeRows,
214
- [rowId]: expanded
215
- };
216
- }
217
- function toggleGridTreeRowExpanded(expandedTreeRows, rowId) {
218
- const expanded = !expandedTreeRows[rowId];
219
- return {
220
- expanded,
221
- nextExpandedTreeRows: setGridTreeRowExpanded(expandedTreeRows, rowId, expanded)
222
- };
223
- }
224
- function expandAllGridTreeRows(rows) {
225
- const nextExpandedTreeRows = {};
226
- for (const row of rows) {
227
- if (row.hasChildren) {
228
- nextExpandedTreeRows[row.id] = true;
146
+ const isControlCharacter = codePoint >= 1 && codePoint <= 31 || codePoint === 127;
147
+ const startsWithDigit = index === 0 && codePoint >= 48 && codePoint <= 57;
148
+ const secondCharDigitAfterHyphen = index === 1 && codePoint >= 48 && codePoint <= 57 && value.charCodeAt(0) === 45;
149
+ if (isControlCharacter || startsWithDigit || secondCharDigitAfterHyphen) {
150
+ output += `\\${codePoint.toString(16)} `;
151
+ continue;
229
152
  }
230
- }
231
- return nextExpandedTreeRows;
232
- }
233
-
234
- // ../ui-grid/src/lib/grid/grid.core.state.ts
235
- function normalizeGridSavedState(state) {
236
- const normalized = {};
237
- if (Array.isArray(state.columnOrder)) {
238
- normalized.columnOrder = state.columnOrder.filter(
239
- (columnName) => typeof columnName === "string" && isSafeStateKey(columnName)
240
- );
241
- }
242
- if (state.filters && typeof state.filters === "object") {
243
- normalized.filters = Object.entries(state.filters).reduce((accumulator, [key, value]) => {
244
- if (typeof key === "string" && isSafeStateKey(key) && typeof value === "string") {
245
- accumulator[key] = value;
246
- }
247
- return accumulator;
248
- }, {});
249
- }
250
- if (state.sort && typeof state.sort === "object") {
251
- normalized.sort = {
252
- columnName: typeof state.sort.columnName === "string" && isSafeStateKey(state.sort.columnName) ? state.sort.columnName : null,
253
- direction: state.sort.direction === SORT_DIRECTIONS.asc || state.sort.direction === SORT_DIRECTIONS.desc ? state.sort.direction : SORT_DIRECTIONS.none
254
- };
255
- }
256
- if (Array.isArray(state.grouping)) {
257
- normalized.grouping = state.grouping.filter(
258
- (columnName) => typeof columnName === "string" && isSafeStateKey(columnName)
259
- );
260
- }
261
- if (state.pagination && typeof state.pagination === "object") {
262
- const paginationCurrentPage = Number(state.pagination.paginationCurrentPage);
263
- const paginationPageSize = Number(state.pagination.paginationPageSize);
264
- normalized.pagination = {
265
- paginationCurrentPage: Number.isFinite(paginationCurrentPage) && paginationCurrentPage > 0 ? Math.floor(paginationCurrentPage) : 1,
266
- paginationPageSize: Number.isFinite(paginationPageSize) && paginationPageSize >= 0 ? Math.floor(paginationPageSize) : 0
267
- };
268
- }
269
- if (state.expandable && typeof state.expandable === "object") {
270
- normalized.expandable = normalizeBooleanMap(state.expandable);
271
- }
272
- if (state.treeView && typeof state.treeView === "object") {
273
- normalized.treeView = normalizeBooleanMap(state.treeView);
274
- }
275
- return normalized;
276
- }
277
- function normalizeBooleanMap(value) {
278
- return Object.entries(value).reduce((accumulator, [key, entry]) => {
279
- if (typeof key === "string" && isSafeStateKey(key) && typeof entry === "boolean") {
280
- accumulator[key] = entry;
153
+ if (index === 0 && value.length === 1 && codePoint === 45) {
154
+ output += `\\${character}`;
155
+ continue;
281
156
  }
282
- return accumulator;
283
- }, {});
284
- }
285
- function isSafeStateKey(value) {
286
- return value !== "__proto__" && value !== "constructor" && value !== "prototype";
287
- }
288
-
289
- // ../ui-grid/src/lib/grid/ui-grid.events.ts
290
- function raiseGridRenderingComplete(gridApi) {
291
- gridApi.core.raise.renderingComplete(gridApi);
292
- }
293
- function raiseGridRowsRendered(gridApi, rows) {
294
- gridApi.core.raise.rowsRendered(rows);
295
- }
296
- function raiseGridRowsVisibleChanged(gridApi, rows) {
297
- gridApi.core.raise.rowsVisibleChanged(rows);
298
- }
299
- function raiseGridCanvasHeightChanged(gridApi, oldHeight, newHeight) {
300
- gridApi.core.raise.canvasHeightChanged(oldHeight, newHeight);
301
- }
302
- function raiseGridDimensionChanged(gridApi, oldHeight, oldWidth, newHeight, newWidth) {
303
- gridApi.core.raise.gridDimensionChanged(oldHeight, oldWidth, newHeight, newWidth);
304
- }
305
- function raiseGridScrollBegin(gridApi) {
306
- gridApi.core.raise.scrollBegin();
307
- }
308
- function raiseGridScrollEnd(gridApi) {
309
- gridApi.core.raise.scrollEnd();
310
- }
311
- function raiseGridSortChanged(gridApi, sortState) {
312
- gridApi.core.raise.sortChanged(sortState.columnName, sortState.direction);
313
- }
314
- function raiseGridFilterChanged(gridApi, filters) {
315
- gridApi.core.raise.filterChanged(filters);
316
- }
317
- function raiseGridGroupingChanged(gridApi, groupByColumns) {
318
- gridApi.core.raise.groupingChanged(groupByColumns);
319
- }
320
- function raiseGridColumnOrderChanged(gridApi, order) {
321
- gridApi.core.raise.columnOrderChanged(order);
322
- }
323
- function raiseGridBenchmarkComplete(gridApi, result) {
324
- gridApi.core.raise.benchmarkComplete(result);
325
- }
326
- function raiseGridPaginationChanged(gridApi, currentPage, pageSize) {
327
- gridApi.pagination.raise.paginationChanged(currentPage, pageSize);
328
- }
329
- function raiseGridExpandableRowStateChanged(gridApi, row, expanded) {
330
- gridApi.expandable.raise.rowExpandedStateChanged(row, expanded);
331
- }
332
- function raiseGridTreeRowStateChanged(gridApi, row, expanded) {
333
- if (expanded) {
334
- gridApi.treeBase.raise.rowExpanded(row);
335
- return;
336
- }
337
- gridApi.treeBase.raise.rowCollapsed(row);
338
- }
339
- function raiseGridNeedMoreData(gridApi, request) {
340
- if (request === "top") {
341
- gridApi.infiniteScroll.raise.needLoadMoreDataTop();
342
- return;
343
- }
344
- gridApi.infiniteScroll.raise.needLoadMoreData();
345
- }
346
- function raiseGridBeginCellEdit(gridApi, rowEntity, column, triggerEvent) {
347
- gridApi.edit.raise.beginCellEdit(rowEntity, column, triggerEvent);
348
- }
349
- function raiseGridAfterCellEdit(gridApi, rowEntity, column, newValue, oldValue) {
350
- gridApi.edit.raise.afterCellEdit(rowEntity, column, newValue, oldValue);
351
- }
352
- function raiseGridCancelCellEdit(gridApi, rowEntity, column) {
353
- gridApi.edit.raise.cancelCellEdit(rowEntity, column);
354
- }
355
-
356
- // ../ui-grid/src/lib/grid/ui-grid.state.ts
357
- function moveArrayItem(items, fromIndex, toIndex) {
358
- const next = [...items];
359
- const [item] = next.splice(fromIndex, 1);
360
- if (item === void 0) {
361
- return next;
157
+ const isSafeCharacter = codePoint >= 128 || codePoint === 45 || codePoint === 95 || codePoint >= 48 && codePoint <= 57 || codePoint >= 65 && codePoint <= 90 || codePoint >= 97 && codePoint <= 122;
158
+ output += isSafeCharacter ? character : `\\${character}`;
362
159
  }
363
- next.splice(toIndex, 0, item);
364
- return next;
160
+ return output;
365
161
  }
366
- function moveGridColumnOrderState(current, fromIndex, toIndex) {
367
- return moveArrayItem(current, fromIndex, toIndex);
368
- }
369
- function createGridRestoreMutationPlan(state) {
370
- const normalizedState = normalizeGridSavedState(state);
371
- const plan = {};
372
- if (normalizedState.columnOrder) {
373
- plan.columnOrder = normalizedState.columnOrder;
374
- }
375
- if (normalizedState.filters) {
376
- plan.filters = normalizedState.filters;
377
- }
378
- if (normalizedState.sort) {
379
- plan.sort = normalizedState.sort;
380
- }
381
- if (normalizedState.grouping) {
382
- plan.grouping = normalizedState.grouping;
383
- }
384
- if (normalizedState.pagination) {
385
- plan.pagination = {
386
- currentPage: normalizedState.pagination.paginationCurrentPage,
387
- pageSize: normalizedState.pagination.paginationPageSize
388
- };
389
- }
390
- if (normalizedState.expandable) {
391
- plan.expandable = normalizedState.expandable;
392
- }
393
- if (normalizedState.treeView) {
394
- plan.treeView = normalizedState.treeView;
395
- }
396
- return plan;
397
- }
398
-
399
- // ../ui-grid/src/lib/grid/ui-grid.commands.ts
400
- function applyGridSortStateCommand(gridApi, setSortState, sortState) {
401
- setSortState(sortState);
402
- raiseGridSortChanged(gridApi, sortState);
403
- }
404
- function sortGridColumnCommand(gridApi, setSortState, columnName, direction) {
405
- applyGridSortStateCommand(gridApi, setSortState, buildGridSortState(columnName, direction));
406
- }
407
- function updateGridFilterCommand(gridApi, updateFilters, getFilters, columnName, value) {
408
- updateFilters((current) => ({
409
- ...current,
410
- [columnName]: value
411
- }));
412
- raiseGridFilterChanged(gridApi, getFilters());
413
- }
414
- function clearGridFiltersCommand(gridApi, setFilters) {
415
- const nextFilters = {};
416
- setFilters(nextFilters);
417
- raiseGridFilterChanged(gridApi, nextFilters);
418
- }
419
- function clearGridGroupingCommand(gridApi, setGroupByColumns, shouldRaise = true) {
420
- const nextGrouping = [];
421
- setGroupByColumns(nextGrouping);
422
- if (shouldRaise) {
423
- raiseGridGroupingChanged(gridApi, nextGrouping);
424
- }
425
- }
426
- function moveGridColumnCommand(gridApi, canMoveColumns, updateColumnOrder, fromIndex, toIndex) {
427
- if (!canMoveColumns) {
428
- return;
429
- }
430
- updateColumnOrder((current) => {
431
- const next = moveGridColumnOrderState(current, fromIndex, toIndex);
432
- raiseGridColumnOrderChanged(gridApi, next);
433
- return next;
434
- });
435
- }
436
- function seekGridPaginationCommand(gridApi, setCurrentPage, getTotalPages, getEffectivePageSize, page) {
437
- const nextPage = seekGridPage(page, getTotalPages());
438
- setCurrentPage(nextPage);
439
- raiseGridPaginationChanged(gridApi, nextPage, getEffectivePageSize());
440
- }
441
- function setGridPaginationPageSizeCommand(gridApi, setPageSize, setCurrentPage, pageSize) {
442
- const nextPageSize = resolveGridPageSize(pageSize);
443
- if (nextPageSize === null) {
444
- return;
445
- }
446
- setPageSize(nextPageSize);
447
- setCurrentPage(1);
448
- raiseGridPaginationChanged(gridApi, 1, nextPageSize);
449
- }
450
- function restoreGridStateCommand(gridApi, state, access) {
451
- const restorePlan = createGridRestoreMutationPlan(state);
452
- if (restorePlan.columnOrder) {
453
- access.setColumnOrder(restorePlan.columnOrder);
454
- }
455
- if (restorePlan.filters) {
456
- access.setActiveFilters(restorePlan.filters);
457
- raiseGridFilterChanged(gridApi, restorePlan.filters);
458
- }
459
- if (restorePlan.sort) {
460
- access.setSortState(restorePlan.sort);
461
- }
462
- if (restorePlan.grouping) {
463
- access.setGroupByColumns(restorePlan.grouping);
464
- raiseGridGroupingChanged(gridApi, restorePlan.grouping);
465
- }
466
- if (restorePlan.pagination) {
467
- access.setCurrentPage(restorePlan.pagination.currentPage);
468
- access.setPageSize(restorePlan.pagination.pageSize);
469
- raiseGridPaginationChanged(gridApi, restorePlan.pagination.currentPage, access.getEffectivePageSize());
470
- }
471
- if (restorePlan.expandable) {
472
- access.setExpandedRows(restorePlan.expandable);
473
- }
474
- if (restorePlan.treeView) {
475
- access.setExpandedTreeRows(restorePlan.treeView);
476
- }
477
- }
478
- function toggleGridRowExpansionCommand(gridApi, canExpandRows, currentExpandedRows, rowId, setExpandedRows, findRowById) {
479
- if (!canExpandRows) {
480
- return;
481
- }
482
- const { expanded, nextExpandedRows } = toggleGridRowExpanded(currentExpandedRows, rowId);
483
- setExpandedRows(nextExpandedRows);
484
- const gridRow = findRowById(rowId);
485
- if (!gridRow) {
486
- return;
487
- }
488
- gridRow.expanded = expanded;
489
- raiseGridExpandableRowStateChanged(gridApi, gridRow, expanded);
490
- }
491
- function expandAllGridRowsCommand(buildRows, data, setExpandedRows) {
492
- setExpandedRows(expandAllGridRows(buildRows(data)));
493
- }
494
- function collapseAllGridRowsCommand(setExpandedRows) {
495
- setExpandedRows({});
496
- }
497
- function toggleGridTreeRowCommand(gridApi, currentExpandedTreeRows, rowId, setExpandedTreeRows, findRowById) {
498
- const { expanded, nextExpandedTreeRows } = toggleGridTreeRowExpanded(currentExpandedTreeRows, rowId);
499
- setExpandedTreeRows(nextExpandedTreeRows);
500
- const gridRow = findRowById(rowId);
501
- if (gridRow) {
502
- raiseGridTreeRowStateChanged(gridApi, gridRow, expanded);
503
- }
504
- }
505
- function setGridTreeRowExpandedCommand(gridApi, currentExpandedTreeRows, rowId, expanded, setExpandedTreeRows, findRowById) {
506
- setExpandedTreeRows(setGridTreeRowExpanded(currentExpandedTreeRows, rowId, expanded));
507
- const gridRow = findRowById(rowId);
508
- if (gridRow) {
509
- raiseGridTreeRowStateChanged(gridApi, gridRow, expanded);
510
- }
511
- }
512
- function expandAllGridTreeRowsCommand(buildRows, data, setExpandedTreeRows) {
513
- setExpandedTreeRows(expandAllGridTreeRows(buildRows(data)));
514
- }
515
- function collapseAllGridTreeRowsCommand(setExpandedTreeRows) {
516
- setExpandedTreeRows({});
517
- }
518
- function beginGridCellEditCommand(gridApi, access, row, column, currentValue, triggerEvent, initialValue) {
519
- const nextEditSession = beginGridEditSession(
520
- row.id,
521
- column.name,
522
- initialValue ?? stringifyGridEditorValue(currentValue)
523
- );
524
- access.setFocusedCell(nextEditSession.focusedCell);
525
- access.setEditingCell(nextEditSession.editingCell);
526
- access.setEditingValue(nextEditSession.editingValue);
527
- raiseGridBeginCellEdit(gridApi, row.entity, column, triggerEvent);
528
- return nextEditSession.editingCell;
529
- }
530
- function commitGridCellEditCommand(gridApi, access) {
531
- const editingCell = access.getEditingCell();
532
- if (!editingCell) {
533
- return { committed: false };
534
- }
535
- const row = access.findRowById(editingCell.rowId);
536
- const column = access.findColumnByName(editingCell.columnName);
537
- if (!row || !column) {
538
- access.setEditingCell(null);
539
- return { committed: false };
540
- }
541
- const oldValue = getCellValue(row.entity, column);
542
- const newValue = access.parseEditedValue(column, access.getEditingValue(), oldValue);
543
- access.setCellValue(row.entity, column, newValue);
544
- const clearedEditSession = clearGridEditSession();
545
- access.setEditingCell(clearedEditSession.editingCell);
546
- raiseGridAfterCellEdit(gridApi, row.entity, column, newValue, oldValue);
547
- access.setEditingValue(clearedEditSession.editingValue);
548
- return {
549
- committed: true,
550
- focusTarget: { rowId: row.id, columnName: column.name },
551
- row,
552
- column
553
- };
554
- }
555
- function cancelGridCellEditCommand(gridApi, access) {
556
- const editingCell = access.getEditingCell();
557
- if (!editingCell) {
558
- return {};
559
- }
560
- const row = access.findRowById(editingCell.rowId);
561
- const column = access.findColumnByName(editingCell.columnName);
562
- const clearedEditSession = clearGridEditSession();
563
- access.setEditingCell(clearedEditSession.editingCell);
564
- access.setEditingValue(clearedEditSession.editingValue);
565
- if (!row || !column) {
566
- return {};
567
- }
568
- raiseGridCancelCellEdit(gridApi, row.entity, column);
569
- return { focusTarget: editingCell };
570
- }
571
- function maybeRequestInfiniteScrollCommand(gridApi, access) {
572
- if (!access.enabled || !access.virtualizationEnabled) {
573
- return;
574
- }
575
- const { request, nextState } = maybeRequestInfiniteScrollData({
576
- state: access.state,
577
- startIndex: access.startIndex,
578
- visibleRows: access.visibleRows,
579
- viewportRows: access.viewportRows,
580
- threshold: access.threshold
581
- });
582
- if (request === "top" || request === "bottom") {
583
- access.setState(nextState);
584
- raiseGridNeedMoreData(gridApi, request);
585
- }
586
- }
587
- function completeGridInfiniteScrollDataLoadCommand(currentState, setState, scrollUp, scrollDown) {
588
- setState(completeInfiniteScrollDataLoad(currentState, scrollUp, scrollDown));
589
- return Promise.resolve();
590
- }
591
- function resetGridInfiniteScrollCommand(setState, scrollUp, scrollDown) {
592
- setState(resetInfiniteScrollState(scrollUp, scrollDown));
593
- }
594
- function saveGridInfiniteScrollPercentageCommand(currentState, visibleRows, setState) {
595
- setState(saveInfiniteScrollPercentage(currentState, visibleRows));
596
- }
597
- function setGridInfiniteScrollDirectionsCommand(currentState, setState, scrollUp, scrollDown) {
598
- setState(setInfiniteScrollDirectionsState(currentState, scrollUp, scrollDown));
599
- }
600
-
601
- // ../ui-grid/src/lib/grid/ui-grid.host.ts
602
- function observeGridHostSize(hostElement, onSizeChange) {
603
- if (typeof ResizeObserver === "undefined") {
604
- return null;
605
- }
606
- const observer = new ResizeObserver((entries) => {
607
- const entry = entries[0];
608
- if (!entry) {
609
- return;
610
- }
611
- onSizeChange({
612
- height: Math.round(entry.contentRect.height),
613
- width: Math.round(entry.contentRect.width)
614
- });
615
- });
616
- observer.observe(hostElement);
617
- return observer;
618
- }
619
- function downloadGridCsvFile(csv, filename) {
620
- if (typeof Blob === "undefined" || typeof URL === "undefined" || typeof document === "undefined") {
621
- return;
622
- }
623
- const blob = new Blob([csv], { type: "text/csv;charset=utf-8" });
624
- const url = URL.createObjectURL(blob);
625
- const link = document.createElement("a");
626
- link.href = url;
627
- link.download = filename;
628
- link.click();
629
- URL.revokeObjectURL(url);
630
- }
631
-
632
- // src/useGridState.ts
633
162
  function useGridState(options, onRegisterApi) {
634
163
  const [activeFilters, setActiveFilters] = useState({});
635
164
  const [groupByColumns, setGroupByColumns] = useState([]);
636
165
  const [collapsedGroups, setCollapsedGroups] = useState({});
637
166
  const [columnOrder, setColumnOrder] = useState([]);
638
167
  const [hiddenRowReasons, setHiddenRowReasons] = useState({});
639
- const [sortState, setSortState] = useState({ columnName: null, direction: SORT_DIRECTIONS2.none });
168
+ const [sortState, setSortState] = useState({
169
+ columnName: null,
170
+ direction: SORT_DIRECTIONS.none
171
+ });
640
172
  const [focusedCell, setFocusedCell] = useState(null);
641
173
  const [editingCell, setEditingCell] = useState(null);
642
174
  const [editingValue, setEditingValue] = useState("");
@@ -652,6 +184,7 @@ function useGridState(options, onRegisterApi) {
652
184
  previousVisibleRows: 0
653
185
  });
654
186
  const [autoViewportHeight, setAutoViewportHeight] = useState(null);
187
+ const [pinnedColumns, setPinnedColumns] = useState({});
655
188
  const gridContainerRef = useRef(null);
656
189
  const initializedGridIdRef = useRef(null);
657
190
  const lastCanvasHeightRef = useRef(0);
@@ -683,6 +216,8 @@ function useGridState(options, onRegisterApi) {
683
216
  expandedRowsRef.current = expandedRows;
684
217
  const expandedTreeRowsRef = useRef(expandedTreeRows);
685
218
  expandedTreeRowsRef.current = expandedTreeRows;
219
+ const pinnedColumnsRef = useRef(pinnedColumns);
220
+ pinnedColumnsRef.current = pinnedColumns;
686
221
  const currentPageRef = useRef(currentPage);
687
222
  currentPageRef.current = currentPage;
688
223
  const pageSizeRef = useRef(pageSize);
@@ -693,13 +228,21 @@ function useGridState(options, onRegisterApi) {
693
228
  optionsRef.current = options;
694
229
  const rowSize = options.rowHeight ?? 44;
695
230
  const visibleColumns = useMemo(() => {
696
- const order = columnOrder;
697
- return [...options.columnDefs].filter((column) => column.visible !== false).sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
698
- }, [options.columnDefs, columnOrder]);
231
+ const orderedColumns = orderVisibleColumns(options.columnDefs, columnOrder);
232
+ const pinnedEntries = Object.entries(pinnedColumns);
233
+ if (pinnedEntries.length === 0) {
234
+ return orderedColumns;
235
+ }
236
+ const columnByName = new Map(orderedColumns.map((column) => [column.name, column]));
237
+ const pinnedLeft = pinnedEntries.filter(([, direction]) => direction === "left").map(([columnName]) => columnByName.get(columnName)).filter((column) => column !== void 0);
238
+ const pinnedRight = pinnedEntries.filter(([, direction]) => direction === "right").map(([columnName]) => columnByName.get(columnName)).filter((column) => column !== void 0);
239
+ const centerColumns = orderedColumns.filter((column) => pinnedColumns[column.name] === void 0);
240
+ return [...pinnedLeft, ...centerColumns, ...pinnedRight];
241
+ }, [options.columnDefs, columnOrder, pinnedColumns]);
699
242
  const visibleColumnsRef = useRef(visibleColumns);
700
243
  visibleColumnsRef.current = visibleColumns;
701
244
  const pipeline = useMemo(() => {
702
- return buildGridPipeline({
245
+ return defaultGridEngine.buildPipeline({
703
246
  options,
704
247
  columns: visibleColumns,
705
248
  activeFilters,
@@ -713,14 +256,39 @@ function useGridState(options, onRegisterApi) {
713
256
  pageSize,
714
257
  rowSize
715
258
  });
716
- }, [options, visibleColumns, activeFilters, sortState, groupByColumns, collapsedGroups, hiddenRowReasons, expandedRows, expandedTreeRows, currentPage, pageSize, rowSize]);
259
+ }, [
260
+ options,
261
+ visibleColumns,
262
+ activeFilters,
263
+ sortState,
264
+ groupByColumns,
265
+ collapsedGroups,
266
+ hiddenRowReasons,
267
+ expandedRows,
268
+ expandedTreeRows,
269
+ currentPage,
270
+ pageSize,
271
+ rowSize
272
+ ]);
717
273
  const pipelineRef = useRef(pipeline);
718
274
  pipelineRef.current = pipeline;
719
275
  const labels = useMemo(() => resolveGridLabels(options.labels), [options.labels]);
720
276
  const gridTemplateColumns = useMemo(
721
- () => visibleColumns.map((column) => gridColumnWidth(column)).join(" "),
277
+ () => buildGridTemplateColumns(visibleColumns),
722
278
  [visibleColumns]
723
279
  );
280
+ const isPinningEnabledFn = useCallback(() => {
281
+ return isPinningEnabled(optionsRef.current);
282
+ }, []);
283
+ const isColumnPinnableFn = useCallback((column) => {
284
+ return isColumnPinnable(optionsRef.current, column);
285
+ }, []);
286
+ const isPinnedFn = useCallback((column) => {
287
+ return pinnedColumnsRef.current[column.name] !== void 0;
288
+ }, []);
289
+ const pinnedOffsetFn = useCallback((column) => {
290
+ return computePinnedOffset(visibleColumnsRef.current, pinnedColumnsRef.current, column);
291
+ }, []);
724
292
  const resolveRowId = useCallback((row) => {
725
293
  return coreResolveGridRowId(optionsRef.current, row);
726
294
  }, []);
@@ -732,9 +300,12 @@ function useGridState(options, onRegisterApi) {
732
300
  expandedRowsRef.current
733
301
  );
734
302
  }, []);
735
- const findRowById = useCallback((rowId) => {
736
- return coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId);
737
- }, [buildRowsFromData]);
303
+ const findRowById = useCallback(
304
+ (rowId) => {
305
+ return coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId);
306
+ },
307
+ [buildRowsFromData]
308
+ );
738
309
  const canExpandRowsFn = useCallback(() => {
739
310
  return FEATURE_EXPANDABLE && canGridExpandRows(optionsRef.current);
740
311
  }, []);
@@ -743,7 +314,12 @@ function useGridState(options, onRegisterApi) {
743
314
  }, []);
744
315
  const getCurrentPageValueFn = useCallback((totalItems) => {
745
316
  const ti = totalItems ?? pipelineRef.current.totalItems;
746
- return coreGetCurrentPageValue(optionsRef.current, currentPageRef.current, ti, pageSizeRef.current);
317
+ return coreGetCurrentPageValue(
318
+ optionsRef.current,
319
+ currentPageRef.current,
320
+ ti,
321
+ pageSizeRef.current
322
+ );
747
323
  }, []);
748
324
  const getTotalPagesValueFn = useCallback((totalItems) => {
749
325
  const ti = totalItems ?? pipelineRef.current.totalItems;
@@ -751,32 +327,45 @@ function useGridState(options, onRegisterApi) {
751
327
  }, []);
752
328
  const getFirstRowIndexValueFn = useCallback((totalItems) => {
753
329
  const ti = totalItems ?? pipelineRef.current.totalItems;
754
- return coreGetFirstRowIndexValue(optionsRef.current, currentPageRef.current, ti, pageSizeRef.current);
330
+ return coreGetFirstRowIndexValue(
331
+ optionsRef.current,
332
+ currentPageRef.current,
333
+ ti,
334
+ pageSizeRef.current
335
+ );
755
336
  }, []);
756
337
  const getLastRowIndexValueFn = useCallback((totalItems) => {
757
338
  const ti = totalItems ?? pipelineRef.current.totalItems;
758
- return coreGetLastRowIndexValue(optionsRef.current, currentPageRef.current, ti, pageSizeRef.current);
759
- }, []);
760
- const isCellEditable = useCallback((row, column, triggerEvent) => {
761
- if (!FEATURE_CELL_EDIT) return false;
762
- const editable = column.enableCellEdit ?? optionsRef.current.enableCellEdit ?? false;
763
- if (!editable) return false;
764
- const condition = column.cellEditableCondition ?? optionsRef.current.cellEditableCondition ?? true;
765
- if (typeof condition === "boolean") return condition;
766
- const context = {
767
- row: row.entity,
768
- column,
769
- rowIndex: row.index,
770
- triggerEvent
771
- };
772
- return condition(context);
339
+ return coreGetLastRowIndexValue(
340
+ optionsRef.current,
341
+ currentPageRef.current,
342
+ ti,
343
+ pageSizeRef.current
344
+ );
773
345
  }, []);
346
+ const isCellEditable = useCallback(
347
+ (row, column, triggerEvent) => {
348
+ if (!FEATURE_CELL_EDIT) return false;
349
+ const editable = column.enableCellEdit ?? optionsRef.current.enableCellEdit ?? false;
350
+ if (!editable) return false;
351
+ const condition = column.cellEditableCondition ?? optionsRef.current.cellEditableCondition ?? true;
352
+ if (typeof condition === "boolean") return condition;
353
+ const context = {
354
+ row: row.entity,
355
+ column,
356
+ rowIndex: row.index,
357
+ triggerEvent
358
+ };
359
+ return condition(context);
360
+ },
361
+ []
362
+ );
774
363
  const shouldEditOnFocusFn = useCallback((column) => {
775
364
  return column.enableCellEditOnFocus ?? optionsRef.current.enableCellEditOnFocus ?? false;
776
365
  }, []);
777
366
  const focusRenderedCell = useCallback((position) => {
778
367
  const focusToken = ++renderedCellFocusTokenRef.current;
779
- const selector = `.body-cell[data-row-id="${position.rowId}"][data-col-name="${position.columnName}"]`;
368
+ const selector = `.body-cell[data-row-id="${escapeCssSelectorValue(position.rowId)}"][data-col-name="${escapeCssSelectorValue(position.columnName)}"]`;
780
369
  const doFocus = (retry = true) => {
781
370
  if (focusToken !== renderedCellFocusTokenRef.current) return;
782
371
  const container = gridContainerRef.current;
@@ -797,11 +386,12 @@ function useGridState(options, onRegisterApi) {
797
386
  if (focusToken !== editorFocusTokenRef.current) return;
798
387
  const ec = editingCellRef.current;
799
388
  if (!ec) return;
800
- const selector = `.cell-editor[data-row-id="${ec.rowId}"][data-col-name="${ec.columnName}"]`;
389
+ const selector = `.cell-editor[data-row-id="${escapeCssSelectorValue(ec.rowId)}"][data-col-name="${escapeCssSelectorValue(ec.columnName)}"]`;
801
390
  const doFocus = (retry = true) => {
802
391
  if (focusToken !== editorFocusTokenRef.current) return;
803
392
  const currentEc = editingCellRef.current;
804
- if (!currentEc || currentEc.rowId !== ec.rowId || currentEc.columnName !== ec.columnName) return;
393
+ if (!currentEc || currentEc.rowId !== ec.rowId || currentEc.columnName !== ec.columnName)
394
+ return;
805
395
  const container = gridContainerRef.current;
806
396
  if (!container) return;
807
397
  const input = container.querySelector(selector);
@@ -862,7 +452,11 @@ function useGridState(options, onRegisterApi) {
862
452
  gridApiRef.current.core.raise.groupingChanged(next);
863
453
  },
864
454
  clearGrouping: () => {
865
- clearGridGroupingCommand(gridApiRef.current, (grouping) => setGroupByColumns(grouping), false);
455
+ clearGridGroupingCommand(
456
+ gridApiRef.current,
457
+ (grouping) => setGroupByColumns(grouping),
458
+ false
459
+ );
866
460
  },
867
461
  benchmark: (iterations) => {
868
462
  return runBenchmarkFn(iterations);
@@ -957,7 +551,8 @@ function useGridState(options, onRegisterApi) {
957
551
  pageSize: pageSizeRef.current,
958
552
  totalItems: pipelineRef.current.totalItems,
959
553
  expandedRows: expandedRowsRef.current,
960
- expandedTreeRows: expandedTreeRowsRef.current
554
+ expandedTreeRows: expandedTreeRowsRef.current,
555
+ pinnedColumns: pinnedColumnsRef.current
961
556
  });
962
557
  },
963
558
  restoreState: (state) => {
@@ -970,6 +565,7 @@ function useGridState(options, onRegisterApi) {
970
565
  setPageSize: (ps) => setPageSize(ps),
971
566
  setExpandedRows: (e) => setExpandedRows(e),
972
567
  setExpandedTreeRows: (e) => setExpandedTreeRows(e),
568
+ setPinnedColumns: (p) => setPinnedColumns(p),
973
569
  getEffectivePageSize: () => effectivePageSizeFn(pipelineRef.current.totalItems)
974
570
  });
975
571
  },
@@ -982,20 +578,45 @@ function useGridState(options, onRegisterApi) {
982
578
  },
983
579
  endCellEdit: () => commitCellEditFn(),
984
580
  cancelCellEdit: () => cancelCellEditFn(),
985
- getEditingCell: () => editingCellRef.current
581
+ getEditingCell: () => editingCellRef.current,
582
+ pinColumn: (columnName, direction) => {
583
+ pinGridColumnCommand(
584
+ gridApiRef.current,
585
+ isPinningEnabledFn(),
586
+ (v) => setPinnedColumns(v),
587
+ () => pinnedColumnsRef.current,
588
+ columnName,
589
+ direction
590
+ );
591
+ }
986
592
  };
987
593
  gridApiRef.current = createGridApi(bindings);
988
594
  }
989
595
  const gridApi = gridApiRef.current;
990
- const seekPageFn = useCallback((page) => {
991
- seekGridPaginationCommand(
596
+ const seekPageFn = useCallback(
597
+ (page) => {
598
+ seekGridPaginationCommand(
599
+ gridApiRef.current,
600
+ (nextPage) => setCurrentPage(nextPage),
601
+ () => getTotalPagesValueFn(),
602
+ () => effectivePageSizeFn(pipelineRef.current.totalItems),
603
+ page
604
+ );
605
+ },
606
+ [getTotalPagesValueFn, effectivePageSizeFn]
607
+ );
608
+ const togglePinFn = useCallback((column) => {
609
+ const current = pinnedColumnsRef.current[column.name];
610
+ const next = current === "left" ? "right" : current === "right" ? "none" : "left";
611
+ pinGridColumnCommand(
992
612
  gridApiRef.current,
993
- (nextPage) => setCurrentPage(nextPage),
994
- () => getTotalPagesValueFn(),
995
- () => effectivePageSizeFn(pipelineRef.current.totalItems),
996
- page
613
+ isPinningEnabledFn(),
614
+ (v) => setPinnedColumns(v),
615
+ () => pinnedColumnsRef.current,
616
+ column.name,
617
+ next
997
618
  );
998
- }, [getTotalPagesValueFn, effectivePageSizeFn]);
619
+ }, []);
999
620
  const setPaginationPageSizeFn = useCallback((ps) => {
1000
621
  setGridPaginationPageSizeCommand(
1001
622
  gridApiRef.current,
@@ -1004,17 +625,20 @@ function useGridState(options, onRegisterApi) {
1004
625
  ps
1005
626
  );
1006
627
  }, []);
1007
- const toggleRowExpansionByRefFn = useCallback((row) => {
1008
- const rowId = coreResolveGridRowId(optionsRef.current, row);
1009
- toggleGridRowExpansionCommand(
1010
- gridApiRef.current,
1011
- FEATURE_EXPANDABLE && canGridExpandRows(optionsRef.current),
1012
- expandedRowsRef.current,
1013
- rowId,
1014
- (e) => setExpandedRows(e),
1015
- (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1016
- );
1017
- }, [buildRowsFromData]);
628
+ const toggleRowExpansionByRefFn = useCallback(
629
+ (row) => {
630
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
631
+ toggleGridRowExpansionCommand(
632
+ gridApiRef.current,
633
+ FEATURE_EXPANDABLE && canGridExpandRows(optionsRef.current),
634
+ expandedRowsRef.current,
635
+ rowId,
636
+ (e) => setExpandedRows(e),
637
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
638
+ );
639
+ },
640
+ [buildRowsFromData]
641
+ );
1018
642
  const expandAllRowsFn = useCallback(() => {
1019
643
  if (!canGridExpandRows(optionsRef.current)) return;
1020
644
  expandAllGridRowsCommand(
@@ -1024,88 +648,106 @@ function useGridState(options, onRegisterApi) {
1024
648
  );
1025
649
  }, [buildRowsFromData]);
1026
650
  const toggleAllRowsFn = useCallback(() => {
1027
- const allExpanded = areAllGridRowsExpanded(buildRowsFromData(optionsRef.current.data), expandedRowsRef.current);
651
+ const allExpanded = areAllGridRowsExpanded(
652
+ buildRowsFromData(optionsRef.current.data),
653
+ expandedRowsRef.current
654
+ );
1028
655
  if (allExpanded) {
1029
656
  collapseAllGridRowsCommand((e) => setExpandedRows(e));
1030
657
  } else {
1031
658
  expandAllRowsFn();
1032
659
  }
1033
660
  }, [buildRowsFromData, expandAllRowsFn]);
1034
- const toggleTreeRowByRefFn = useCallback((row) => {
1035
- const rowId = coreResolveGridRowId(optionsRef.current, row);
1036
- toggleGridTreeRowCommand(
1037
- gridApiRef.current,
1038
- expandedTreeRowsRef.current,
1039
- rowId,
1040
- (e) => setExpandedTreeRows(e),
1041
- (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1042
- );
1043
- }, [buildRowsFromData]);
1044
- const expandTreeRowByRefFn = useCallback((row) => {
1045
- const rowId = coreResolveGridRowId(optionsRef.current, row);
1046
- setGridTreeRowExpandedCommand(
1047
- gridApiRef.current,
1048
- expandedTreeRowsRef.current,
1049
- rowId,
1050
- true,
1051
- (e) => setExpandedTreeRows(e),
1052
- (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1053
- );
1054
- }, [buildRowsFromData]);
1055
- const collapseTreeRowByRefFn = useCallback((row) => {
1056
- const rowId = coreResolveGridRowId(optionsRef.current, row);
1057
- setGridTreeRowExpandedCommand(
1058
- gridApiRef.current,
1059
- expandedTreeRowsRef.current,
1060
- rowId,
1061
- false,
1062
- (e) => setExpandedTreeRows(e),
1063
- (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1064
- );
1065
- }, [buildRowsFromData]);
1066
- const startCellEditFn = useCallback((row, column, triggerEvent, initialValue) => {
1067
- const currentValue = getCellValue2(row.entity, column);
1068
- const focusToken = ++editorFocusTokenRef.current;
1069
- const ec = beginGridCellEditCommand(
1070
- gridApiRef.current,
1071
- {
1072
- setFocusedCell: (fc) => setFocusedCell(fc),
1073
- setEditingCell: (ec2) => setEditingCell(ec2),
1074
- setEditingValue: (ev) => setEditingValue(ev)
1075
- },
1076
- row,
1077
- column,
1078
- currentValue,
1079
- triggerEvent,
1080
- initialValue
1081
- );
1082
- if (ec) {
1083
- queueMicrotask(() => focusEditorInput(focusToken));
1084
- }
1085
- }, [focusEditorInput]);
1086
- const commitCellEditFn = useCallback((direction, restoreFocus = true) => {
1087
- const result = commitGridCellEditCommand(gridApiRef.current, {
1088
- getEditingCell: () => editingCellRef.current,
1089
- getEditingValue: () => editingValueRef.current,
1090
- setEditingCell: (ec) => setEditingCell(ec),
1091
- setEditingValue: (ev) => setEditingValue(ev),
1092
- findRowById: (rowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId),
1093
- findColumnByName: (columnName) => visibleColumnsRef.current.find((c) => c.name === columnName),
1094
- parseEditedValue: (column, value, oldValue) => parseGridEditedValue(column, value, oldValue),
1095
- setCellValue: (rowEntity, column, value) => {
1096
- const fieldPath = column.editModelField ?? column.field ?? column.name;
1097
- setPathValue(rowEntity, fieldPath, value);
661
+ const toggleTreeRowByRefFn = useCallback(
662
+ (row) => {
663
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
664
+ toggleGridTreeRowCommand(
665
+ gridApiRef.current,
666
+ expandedTreeRowsRef.current,
667
+ rowId,
668
+ (e) => setExpandedTreeRows(e),
669
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
670
+ );
671
+ },
672
+ [buildRowsFromData]
673
+ );
674
+ const expandTreeRowByRefFn = useCallback(
675
+ (row) => {
676
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
677
+ setGridTreeRowExpandedCommand(
678
+ gridApiRef.current,
679
+ expandedTreeRowsRef.current,
680
+ rowId,
681
+ true,
682
+ (e) => setExpandedTreeRows(e),
683
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
684
+ );
685
+ },
686
+ [buildRowsFromData]
687
+ );
688
+ const collapseTreeRowByRefFn = useCallback(
689
+ (row) => {
690
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
691
+ setGridTreeRowExpandedCommand(
692
+ gridApiRef.current,
693
+ expandedTreeRowsRef.current,
694
+ rowId,
695
+ false,
696
+ (e) => setExpandedTreeRows(e),
697
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
698
+ );
699
+ },
700
+ [buildRowsFromData]
701
+ );
702
+ const startCellEditFn = useCallback(
703
+ (row, column, triggerEvent, initialValue) => {
704
+ const currentValue = getCellValue(row.entity, column);
705
+ const focusToken = ++editorFocusTokenRef.current;
706
+ const ec = beginGridCellEditCommand(
707
+ gridApiRef.current,
708
+ {
709
+ setFocusedCell: (fc) => setFocusedCell(fc),
710
+ setEditingCell: (ec2) => setEditingCell(ec2),
711
+ setEditingValue: (ev) => setEditingValue(ev)
712
+ },
713
+ row,
714
+ column,
715
+ currentValue,
716
+ triggerEvent,
717
+ initialValue
718
+ );
719
+ if (ec) {
720
+ queueMicrotask(() => focusEditorInput(focusToken));
1098
721
  }
1099
- });
1100
- if (!result.committed || !result.row || !result.column || !result.focusTarget) return;
1101
- editorFocusTokenRef.current += 1;
1102
- if (direction) {
1103
- const moved = moveFocusFn(result.row, result.column, direction);
1104
- if (!moved) focusRenderedCell(result.focusTarget);
1105
- } else if (restoreFocus) {
1106
- focusRenderedCell(result.focusTarget);
1107
- }
1108
- }, [buildRowsFromData, focusRenderedCell]);
722
+ },
723
+ [focusEditorInput]
724
+ );
725
+ const commitCellEditFn = useCallback(
726
+ (direction, restoreFocus = true) => {
727
+ const result = commitGridCellEditCommand(gridApiRef.current, {
728
+ getEditingCell: () => editingCellRef.current,
729
+ getEditingValue: () => editingValueRef.current,
730
+ setEditingCell: (ec) => setEditingCell(ec),
731
+ setEditingValue: (ev) => setEditingValue(ev),
732
+ findRowById: (rowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId),
733
+ findColumnByName: (columnName) => visibleColumnsRef.current.find((c) => c.name === columnName),
734
+ parseEditedValue: (column, value, oldValue) => parseGridEditedValue(column, value, oldValue),
735
+ setCellValue: (rowEntity, column, value) => {
736
+ const fieldPath = column.editModelField ?? column.field ?? column.name;
737
+ setPathValue(rowEntity, fieldPath, value);
738
+ }
739
+ });
740
+ if (!result.committed || !result.row || !result.column || !result.focusTarget) return;
741
+ editorFocusTokenRef.current += 1;
742
+ if (direction) {
743
+ const moved = moveFocusFn(result.row, result.column, direction);
744
+ if (!moved) focusRenderedCell(result.focusTarget);
745
+ } else if (restoreFocus) {
746
+ focusRenderedCell(result.focusTarget);
747
+ }
748
+ },
749
+ [buildRowsFromData, focusRenderedCell]
750
+ );
1109
751
  const cancelCellEditFn = useCallback(() => {
1110
752
  const hadEditingCell = editingCellRef.current !== null;
1111
753
  const result = cancelGridCellEditCommand(gridApiRef.current, {
@@ -1119,26 +761,32 @@ function useGridState(options, onRegisterApi) {
1119
761
  editorFocusTokenRef.current += 1;
1120
762
  if (result.focusTarget) focusRenderedCell(result.focusTarget);
1121
763
  }, [buildRowsFromData, focusRenderedCell]);
1122
- const moveFocusFn = useCallback((row, column, direction, triggerEvent) => {
1123
- const nextCell = findNextGridCell({
1124
- rows: pipelineRef.current.visibleRows,
1125
- columns: visibleColumnsRef.current,
1126
- rowId: row.id,
1127
- columnName: column.name,
1128
- direction
1129
- });
1130
- if (!nextCell) return false;
1131
- setFocusedCell({ rowId: nextCell.row.id, columnName: nextCell.column.name });
1132
- focusRenderedCell({ rowId: nextCell.row.id, columnName: nextCell.column.name });
1133
- if (shouldEditOnFocusFn(nextCell.column) && isCellEditable(nextCell.row, nextCell.column, triggerEvent)) {
1134
- startCellEditFn(nextCell.row, nextCell.column, triggerEvent);
1135
- }
1136
- return true;
1137
- }, [focusRenderedCell, isCellEditable, shouldEditOnFocusFn, startCellEditFn]);
764
+ const moveFocusFn = useCallback(
765
+ (row, column, direction, triggerEvent) => {
766
+ const nextCell = findNextGridCell({
767
+ rows: pipelineRef.current.visibleRows,
768
+ columns: visibleColumnsRef.current,
769
+ rowId: row.id,
770
+ columnName: column.name,
771
+ direction
772
+ });
773
+ if (!nextCell) return false;
774
+ setFocusedCell({ rowId: nextCell.row.id, columnName: nextCell.column.name });
775
+ focusRenderedCell({ rowId: nextCell.row.id, columnName: nextCell.column.name });
776
+ if (shouldEditOnFocusFn(nextCell.column) && isCellEditable(nextCell.row, nextCell.column, triggerEvent)) {
777
+ startCellEditFn(nextCell.row, nextCell.column, triggerEvent);
778
+ }
779
+ return true;
780
+ },
781
+ [focusRenderedCell, isCellEditable, shouldEditOnFocusFn, startCellEditFn]
782
+ );
1138
783
  const runBenchmarkFn = useCallback((iterations) => {
1139
- const safeIterations = Math.max(1, iterations ?? optionsRef.current.benchmark?.iterations ?? 25);
784
+ const safeIterations = resolveBenchmarkIterations(
785
+ iterations,
786
+ optionsRef.current.benchmark?.iterations
787
+ );
1140
788
  const startedAt = performance.now();
1141
- let lastResult = buildGridPipeline({
789
+ let lastResult = defaultGridEngine.buildPipeline({
1142
790
  options: optionsRef.current,
1143
791
  columns: visibleColumnsRef.current,
1144
792
  activeFilters: activeFiltersRef.current,
@@ -1153,7 +801,7 @@ function useGridState(options, onRegisterApi) {
1153
801
  rowSize: optionsRef.current.rowHeight ?? 44
1154
802
  });
1155
803
  for (let i = 1; i < safeIterations; i++) {
1156
- lastResult = buildGridPipeline({
804
+ lastResult = defaultGridEngine.buildPipeline({
1157
805
  options: optionsRef.current,
1158
806
  columns: visibleColumnsRef.current,
1159
807
  activeFilters: activeFiltersRef.current,
@@ -1199,6 +847,7 @@ function useGridState(options, onRegisterApi) {
1199
847
  setExpandedTreeRows({});
1200
848
  setColumnOrder(options.columnDefs.map((column) => column.name));
1201
849
  setGroupByColumns(options.grouping?.groupBy ?? []);
850
+ setPinnedColumns(buildInitialPinnedState(options.columnDefs));
1202
851
  setCurrentPage(options.paginationCurrentPage ?? 1);
1203
852
  setPageSize(coreGetEffectivePageSize(options, 0, options.data.length));
1204
853
  setInfiniteScrollState({
@@ -1212,7 +861,7 @@ function useGridState(options, onRegisterApi) {
1212
861
  );
1213
862
  setSortState({
1214
863
  columnName: initialSort?.name ?? null,
1215
- direction: initialSort?.sort?.direction ?? SORT_DIRECTIONS2.none
864
+ direction: initialSort?.sort?.direction ?? SORT_DIRECTIONS.none
1216
865
  });
1217
866
  onRegisterApi?.(gridApi);
1218
867
  raiseGridRenderingComplete(gridApi);
@@ -1231,8 +880,15 @@ function useGridState(options, onRegisterApi) {
1231
880
  const container = gridContainerRef.current;
1232
881
  if (!container) return;
1233
882
  const observer = observeGridHostSize(container, ({ height: nextHeight, width: nextWidth }) => {
1234
- if (nextHeight === lastGridHeightRef.current && nextWidth === lastGridWidthRef.current) return;
1235
- raiseGridDimensionChanged(gridApi, lastGridHeightRef.current, lastGridWidthRef.current, nextHeight, nextWidth);
883
+ if (nextHeight === lastGridHeightRef.current && nextWidth === lastGridWidthRef.current)
884
+ return;
885
+ raiseGridDimensionChanged(
886
+ gridApi,
887
+ lastGridHeightRef.current,
888
+ lastGridWidthRef.current,
889
+ nextHeight,
890
+ nextWidth
891
+ );
1236
892
  lastGridHeightRef.current = nextHeight;
1237
893
  lastGridWidthRef.current = nextWidth;
1238
894
  if (!options.viewportHeight && nextHeight > 0) {
@@ -1250,42 +906,75 @@ function useGridState(options, onRegisterApi) {
1250
906
  const paginationCurrentPage = getCurrentPageValueFn();
1251
907
  const paginationTotalPages = getTotalPagesValueFn();
1252
908
  const paginationSelectedPageSize = effectivePageSizeFn(pipeline.totalItems);
1253
- const viewportHeightPx = `${options.viewportHeight ?? autoViewportHeight ?? 560}px`;
909
+ const viewportHeightPx = computeViewportHeightPx(options.viewportHeight, autoViewportHeight);
1254
910
  const headerLabelFn = useCallback((column) => coreHeaderLabel(column), []);
1255
- const isGroupItemFn = useCallback((item) => item.kind === "group", []);
1256
- const isExpandableItemFn = useCallback((item) => item.kind === "expandable", []);
911
+ const isGroupItemFn = useCallback(
912
+ (item) => item.kind === "group",
913
+ []
914
+ );
915
+ const isExpandableItemFn = useCallback(
916
+ (item) => item.kind === "expandable",
917
+ []
918
+ );
1257
919
  const isRowItemFn = useCallback((item) => item.kind === "row", []);
1258
- const isOddStripedRowFn = useCallback((item) => item.kind === "row" && item.visibleIndex % 2 === 0, []);
920
+ const isOddStripedRowFn = useCallback(
921
+ (item) => item.kind === "row" && item.visibleIndex % 2 === 0,
922
+ []
923
+ );
1259
924
  const sortDirectionFn = useCallback((column) => {
1260
- return sortStateRef.current.columnName === column.name ? sortStateRef.current.direction : SORT_DIRECTIONS2.none;
925
+ return sortStateRef.current.columnName === column.name ? sortStateRef.current.direction : SORT_DIRECTIONS.none;
1261
926
  }, []);
1262
- const sortButtonLabelFn = useCallback((column) => {
1263
- return gridSortButtonLabel(sortDirectionFn(column), labels);
1264
- }, [labels, sortDirectionFn]);
1265
- const sortAriaSortFn = useCallback((column) => {
1266
- return gridSortAriaSort(sortDirectionFn(column));
1267
- }, [sortDirectionFn]);
1268
- const groupingButtonLabelFn = useCallback((column) => {
1269
- return gridGroupingButtonLabel(isGridColumnGrouped(groupByColumnsRef.current, column), labels);
1270
- }, [labels]);
927
+ const sortButtonLabelFn = useCallback(
928
+ (column) => {
929
+ return gridSortButtonLabel(sortDirectionFn(column), labels);
930
+ },
931
+ [labels, sortDirectionFn]
932
+ );
933
+ const sortAriaSortFn = useCallback(
934
+ (column) => {
935
+ return gridSortAriaSort(sortDirectionFn(column));
936
+ },
937
+ [sortDirectionFn]
938
+ );
939
+ const groupingButtonLabelFn = useCallback(
940
+ (column) => {
941
+ return gridGroupingButtonLabel(
942
+ isGridColumnGrouped(groupByColumnsRef.current, column),
943
+ labels
944
+ );
945
+ },
946
+ [labels]
947
+ );
1271
948
  const filterValueFn = useCallback((columnName) => {
1272
949
  return activeFiltersRef.current[columnName] ?? "";
1273
950
  }, []);
1274
- const filterPlaceholderFn = useCallback((column) => {
1275
- return gridFilterPlaceholder(isGridColumnFilterable(optionsRef.current, column), labels);
1276
- }, [labels]);
951
+ const filterPlaceholderFn = useCallback(
952
+ (column) => {
953
+ return gridFilterPlaceholder(isGridColumnFilterable(optionsRef.current, column), labels);
954
+ },
955
+ [labels]
956
+ );
1277
957
  const isFilterInputDisabledFn = useCallback((column) => {
1278
958
  return !isGridColumnFilterable(optionsRef.current, column);
1279
959
  }, []);
1280
- const groupDisclosureLabelFn = useCallback((item) => {
1281
- return gridGroupDisclosureLabel(item.collapsed, labels);
1282
- }, [labels]);
1283
- const cellContextFn = useCallback((row, column) => {
1284
- return buildGridCellContext(row, column);
1285
- }, []);
1286
- const displayValueFn = useCallback((row, column) => {
1287
- return formatGridCellDisplayValue(cellContextFn(row, column));
1288
- }, [cellContextFn]);
960
+ const groupDisclosureLabelFn = useCallback(
961
+ (item) => {
962
+ return gridGroupDisclosureLabel(item.collapsed, labels);
963
+ },
964
+ [labels]
965
+ );
966
+ const cellContextFn = useCallback(
967
+ (row, column) => {
968
+ return buildGridCellContext(row, column);
969
+ },
970
+ []
971
+ );
972
+ const displayValueFn = useCallback(
973
+ (row, column) => {
974
+ return formatGridCellDisplayValue(cellContextFn(row, column));
975
+ },
976
+ [cellContextFn]
977
+ );
1289
978
  const isFocusedCellFn = useCallback((row, column) => {
1290
979
  return isGridCellPosition(focusedCellRef.current, row.id, column.name);
1291
980
  }, []);
@@ -1295,16 +984,19 @@ function useGridState(options, onRegisterApi) {
1295
984
  const editorInputTypeFn = useCallback((column) => {
1296
985
  return gridEditorInputType(column);
1297
986
  }, []);
1298
- const expandedContextFn = useCallback((row) => {
1299
- return {
1300
- $implicit: row.entity,
1301
- row: row.entity,
1302
- rowIndex: row.index,
1303
- expanded: true,
1304
- ...optionsRef.current.expandableRowScope ?? {}
1305
- };
1306
- }, []);
1307
- const columnWidthFn = useCallback((column) => gridColumnWidth(column), []);
987
+ const expandedContextFn = useCallback(
988
+ (row) => {
989
+ return {
990
+ $implicit: row.entity,
991
+ row: row.entity,
992
+ rowIndex: row.index,
993
+ expanded: true,
994
+ ...optionsRef.current.expandableRowScope ?? {}
995
+ };
996
+ },
997
+ []
998
+ );
999
+ const columnWidthFn = useCallback((column) => gridColumnWidth2(column), []);
1308
1000
  const isColumnSortableFn = useCallback((column) => {
1309
1001
  return isGridColumnSortable(optionsRef.current, column);
1310
1002
  }, []);
@@ -1314,15 +1006,21 @@ function useGridState(options, onRegisterApi) {
1314
1006
  const cellIndentFn = useCallback((row, column) => {
1315
1007
  return gridCellIndent(optionsRef.current, visibleColumnsRef.current, row, column);
1316
1008
  }, []);
1317
- const treeToggleLabelFn = useCallback((row) => {
1318
- return gridTreeToggleLabelForRow(expandedTreeRowsRef.current, row, labels);
1319
- }, [labels]);
1009
+ const treeToggleLabelFn = useCallback(
1010
+ (row) => {
1011
+ return gridTreeToggleLabelForRow(expandedTreeRowsRef.current, row, labels);
1012
+ },
1013
+ [labels]
1014
+ );
1320
1015
  const isTreeRowExpandedFn = useCallback((row) => {
1321
1016
  return isGridTreeRowExpanded(expandedTreeRowsRef.current, row);
1322
1017
  }, []);
1323
- const expandToggleLabelFn = useCallback((row) => {
1324
- return gridExpandToggleLabelForRow(row, labels);
1325
- }, [labels]);
1018
+ const expandToggleLabelFn = useCallback(
1019
+ (row) => {
1020
+ return gridExpandToggleLabelForRow(row, labels);
1021
+ },
1022
+ [labels]
1023
+ );
1326
1024
  const isGroupedFn = useCallback((column) => {
1327
1025
  return isGridColumnGrouped(groupByColumnsRef.current, column);
1328
1026
  }, []);
@@ -1337,8 +1035,7 @@ function useGridState(options, onRegisterApi) {
1337
1035
  }, []);
1338
1036
  const paginationSummaryFn = useCallback(() => {
1339
1037
  const ti = pipelineRef.current.totalItems;
1340
- if (ti === 0) return "0-0 of 0";
1341
- return `${getFirstRowIndexValueFn(ti) + 1}-${getLastRowIndexValueFn(ti) + 1} of ${ti}`;
1038
+ return formatPaginationSummary(ti, getFirstRowIndexValueFn(ti), getLastRowIndexValueFn(ti));
1342
1039
  }, [getFirstRowIndexValueFn, getLastRowIndexValueFn]);
1343
1040
  const pageSizeOptionsFn = useCallback(() => {
1344
1041
  return optionsRef.current.paginationPageSizes ?? [];
@@ -1351,10 +1048,10 @@ function useGridState(options, onRegisterApi) {
1351
1048
  }, []);
1352
1049
  const toggleSortFn = useCallback((column) => {
1353
1050
  if (!FEATURE_SORTING || !isGridColumnSortable(optionsRef.current, column)) return;
1354
- const currentDirection = sortStateRef.current.columnName === column.name ? sortStateRef.current.direction : SORT_DIRECTIONS2.none;
1355
- const nextDirection = currentDirection === SORT_DIRECTIONS2.none ? SORT_DIRECTIONS2.asc : currentDirection === SORT_DIRECTIONS2.asc ? SORT_DIRECTIONS2.desc : SORT_DIRECTIONS2.none;
1051
+ const currentDirection = sortStateRef.current.columnName === column.name ? sortStateRef.current.direction : SORT_DIRECTIONS.none;
1052
+ const nextDirection = currentDirection === SORT_DIRECTIONS.none ? SORT_DIRECTIONS.asc : currentDirection === SORT_DIRECTIONS.asc ? SORT_DIRECTIONS.desc : SORT_DIRECTIONS.none;
1356
1053
  applyGridSortStateCommand(gridApiRef.current, (state) => setSortState(state), {
1357
- columnName: nextDirection === SORT_DIRECTIONS2.none ? null : column.name,
1054
+ columnName: nextDirection === SORT_DIRECTIONS.none ? null : column.name,
1358
1055
  direction: nextDirection
1359
1056
  });
1360
1057
  }, []);
@@ -1385,108 +1082,130 @@ function useGridState(options, onRegisterApi) {
1385
1082
  [item.id]: !current[item.id]
1386
1083
  }));
1387
1084
  }, []);
1388
- const focusCellFn = useCallback((row, column, triggerEvent) => {
1389
- const nextFocusResult = buildGridFocusCellResult({
1390
- currentFocusedCell: focusedCellRef.current,
1391
- currentEditingCell: editingCellRef.current,
1392
- rowId: row.id,
1393
- columnName: column.name,
1394
- shouldEditOnFocus: shouldEditOnFocusFn(column),
1395
- isCellEditable: isCellEditable(row, column, triggerEvent)
1396
- });
1397
- setFocusedCell(nextFocusResult.focusedCell);
1398
- if (nextFocusResult.shouldBeginEdit) {
1399
- startCellEditFn(row, column, triggerEvent);
1400
- }
1401
- }, [isCellEditable, shouldEditOnFocusFn, startCellEditFn]);
1402
- const handleCellKeyDownFn = useCallback((row, column, event) => {
1403
- focusCellFn(row, column, event.nativeEvent);
1404
- switch (event.key) {
1405
- case "ArrowLeft":
1406
- event.preventDefault();
1407
- moveFocusFn(row, column, "left", event.nativeEvent);
1408
- return;
1409
- case "ArrowRight":
1410
- event.preventDefault();
1411
- moveFocusFn(row, column, "right", event.nativeEvent);
1412
- return;
1413
- case "ArrowUp":
1414
- event.preventDefault();
1415
- moveFocusFn(row, column, "up", event.nativeEvent);
1416
- return;
1417
- case "ArrowDown":
1085
+ const focusCellFn = useCallback(
1086
+ (row, column, triggerEvent) => {
1087
+ const nextFocusResult = buildGridFocusCellResult({
1088
+ currentFocusedCell: focusedCellRef.current,
1089
+ currentEditingCell: editingCellRef.current,
1090
+ rowId: row.id,
1091
+ columnName: column.name,
1092
+ shouldEditOnFocus: shouldEditOnFocusFn(column),
1093
+ isCellEditable: isCellEditable(row, column, triggerEvent)
1094
+ });
1095
+ setFocusedCell(nextFocusResult.focusedCell);
1096
+ if (nextFocusResult.shouldBeginEdit) {
1097
+ startCellEditFn(row, column, triggerEvent);
1098
+ }
1099
+ },
1100
+ [isCellEditable, shouldEditOnFocusFn, startCellEditFn]
1101
+ );
1102
+ const handleCellKeyDownFn = useCallback(
1103
+ (row, column, event) => {
1104
+ focusCellFn(row, column, event.nativeEvent);
1105
+ switch (event.key) {
1106
+ case "ArrowLeft":
1107
+ event.preventDefault();
1108
+ moveFocusFn(row, column, "left", event.nativeEvent);
1109
+ return;
1110
+ case "ArrowRight":
1111
+ event.preventDefault();
1112
+ moveFocusFn(row, column, "right", event.nativeEvent);
1113
+ return;
1114
+ case "ArrowUp":
1115
+ event.preventDefault();
1116
+ moveFocusFn(row, column, "up", event.nativeEvent);
1117
+ return;
1118
+ case "ArrowDown":
1119
+ event.preventDefault();
1120
+ moveFocusFn(row, column, "down", event.nativeEvent);
1121
+ return;
1122
+ case "Tab":
1123
+ event.preventDefault();
1124
+ moveFocusFn(row, column, event.shiftKey ? "left" : "right", event.nativeEvent);
1125
+ return;
1126
+ case "Enter":
1127
+ event.preventDefault();
1128
+ moveFocusFn(row, column, event.shiftKey ? "up" : "down", event.nativeEvent);
1129
+ return;
1130
+ case "F2":
1131
+ event.preventDefault();
1132
+ if (isCellEditable(row, column, event.nativeEvent)) {
1133
+ startCellEditFn(row, column, event.nativeEvent);
1134
+ }
1135
+ return;
1136
+ case "Backspace":
1137
+ case "Delete":
1138
+ if (isCellEditable(row, column, event.nativeEvent)) {
1139
+ event.preventDefault();
1140
+ startCellEditFn(row, column, event.nativeEvent, "");
1141
+ }
1142
+ return;
1143
+ default:
1144
+ break;
1145
+ }
1146
+ if (isPrintableGridKey(event.key, event.ctrlKey, event.metaKey, event.altKey) && isCellEditable(row, column, event.nativeEvent)) {
1418
1147
  event.preventDefault();
1419
- moveFocusFn(row, column, "down", event.nativeEvent);
1420
- return;
1421
- case "Tab":
1148
+ startCellEditFn(row, column, event.nativeEvent, event.key);
1149
+ }
1150
+ },
1151
+ [focusCellFn, moveFocusFn, isCellEditable, startCellEditFn]
1152
+ );
1153
+ const handleCellDoubleClickFn = useCallback(
1154
+ (row, column, event) => {
1155
+ focusCellFn(row, column, event.nativeEvent);
1156
+ if (isCellEditable(row, column, event.nativeEvent)) {
1157
+ startCellEditFn(row, column, event.nativeEvent);
1158
+ }
1159
+ },
1160
+ [focusCellFn, isCellEditable, startCellEditFn]
1161
+ );
1162
+ const updateEditingValueFn = useCallback((value) => {
1163
+ setEditingValue(value);
1164
+ }, []);
1165
+ const handleEditorKeyDownFn = useCallback(
1166
+ (event) => {
1167
+ if (event.key === "Escape") {
1422
1168
  event.preventDefault();
1423
- moveFocusFn(row, column, event.shiftKey ? "left" : "right", event.nativeEvent);
1169
+ cancelCellEditFn();
1424
1170
  return;
1425
- case "Enter":
1171
+ }
1172
+ if (event.key === "Enter") {
1426
1173
  event.preventDefault();
1427
- moveFocusFn(row, column, event.shiftKey ? "up" : "down", event.nativeEvent);
1174
+ commitCellEditFn(event.shiftKey ? "up" : "down");
1428
1175
  return;
1429
- case "F2":
1176
+ }
1177
+ if (event.key === "Tab") {
1430
1178
  event.preventDefault();
1431
- if (isCellEditable(row, column, event.nativeEvent)) {
1432
- startCellEditFn(row, column, event.nativeEvent);
1433
- }
1434
- return;
1435
- case "Backspace":
1436
- case "Delete":
1437
- if (isCellEditable(row, column, event.nativeEvent)) {
1438
- event.preventDefault();
1439
- startCellEditFn(row, column, event.nativeEvent, "");
1440
- }
1179
+ commitCellEditFn(event.shiftKey ? "left" : "right");
1180
+ }
1181
+ },
1182
+ [cancelCellEditFn, commitCellEditFn]
1183
+ );
1184
+ const handleEditorBlurFn = useCallback(
1185
+ (event) => {
1186
+ const ec = editingCellRef.current;
1187
+ const target = event.target;
1188
+ if (!ec || !target) return;
1189
+ if (target.dataset["rowId"] !== ec.rowId || target.dataset["colName"] !== ec.columnName)
1441
1190
  return;
1442
- default:
1443
- break;
1444
- }
1445
- if (isPrintableGridKey(event.key, event.ctrlKey, event.metaKey, event.altKey) && isCellEditable(row, column, event.nativeEvent)) {
1446
- event.preventDefault();
1447
- startCellEditFn(row, column, event.nativeEvent, event.key);
1448
- }
1449
- }, [focusCellFn, moveFocusFn, isCellEditable, startCellEditFn]);
1450
- const handleCellDoubleClickFn = useCallback((row, column, event) => {
1451
- focusCellFn(row, column, event.nativeEvent);
1452
- if (isCellEditable(row, column, event.nativeEvent)) {
1453
- startCellEditFn(row, column, event.nativeEvent);
1454
- }
1455
- }, [focusCellFn, isCellEditable, startCellEditFn]);
1456
- const updateEditingValueFn = useCallback((value) => {
1457
- setEditingValue(value);
1458
- }, []);
1459
- const handleEditorKeyDownFn = useCallback((event) => {
1460
- if (event.key === "Escape") {
1461
- event.preventDefault();
1462
- cancelCellEditFn();
1463
- return;
1464
- }
1465
- if (event.key === "Enter") {
1466
- event.preventDefault();
1467
- commitCellEditFn(event.shiftKey ? "up" : "down");
1468
- return;
1469
- }
1470
- if (event.key === "Tab") {
1471
- event.preventDefault();
1472
- commitCellEditFn(event.shiftKey ? "left" : "right");
1473
- }
1474
- }, [cancelCellEditFn, commitCellEditFn]);
1475
- const handleEditorBlurFn = useCallback((event) => {
1476
- const ec = editingCellRef.current;
1477
- const target = event.target;
1478
- if (!ec || !target) return;
1479
- if (target.dataset["rowId"] !== ec.rowId || target.dataset["colName"] !== ec.columnName) return;
1480
- commitCellEditFn(void 0, false);
1481
- }, [commitCellEditFn]);
1482
- const toggleRowExpansionFn = useCallback((row, event) => {
1483
- event?.stopPropagation();
1484
- toggleRowExpansionByRefFn(row);
1485
- }, [toggleRowExpansionByRefFn]);
1486
- const toggleTreeRowFn = useCallback((row, event) => {
1487
- event?.stopPropagation();
1488
- toggleTreeRowByRefFn(row);
1489
- }, [toggleTreeRowByRefFn]);
1191
+ commitCellEditFn(void 0, false);
1192
+ },
1193
+ [commitCellEditFn]
1194
+ );
1195
+ const toggleRowExpansionFn = useCallback(
1196
+ (row, event) => {
1197
+ event?.stopPropagation();
1198
+ toggleRowExpansionByRefFn(row);
1199
+ },
1200
+ [toggleRowExpansionByRefFn]
1201
+ );
1202
+ const toggleTreeRowFn = useCallback(
1203
+ (row, event) => {
1204
+ event?.stopPropagation();
1205
+ toggleTreeRowByRefFn(row);
1206
+ },
1207
+ [toggleTreeRowByRefFn]
1208
+ );
1490
1209
  const moveColumnFn = useCallback((fromIndex, toIndex) => {
1491
1210
  moveGridColumnCommand(
1492
1211
  gridApiRef.current,
@@ -1502,9 +1221,12 @@ function useGridState(options, onRegisterApi) {
1502
1221
  const previousPageFn = useCallback(() => {
1503
1222
  seekPageFn(getCurrentPageValueFn() - 1);
1504
1223
  }, [seekPageFn, getCurrentPageValueFn]);
1505
- const onPageSizeChangeFn = useCallback((value) => {
1506
- setPaginationPageSizeFn(Number(value));
1507
- }, [setPaginationPageSizeFn]);
1224
+ const onPageSizeChangeFn = useCallback(
1225
+ (value) => {
1226
+ setPaginationPageSizeFn(Number(value));
1227
+ },
1228
+ [setPaginationPageSizeFn]
1229
+ );
1508
1230
  const onViewportScrollFn = useCallback((startIndex) => {
1509
1231
  if (!scrollingRef.current) {
1510
1232
  scrollingRef.current = true;
@@ -1524,7 +1246,10 @@ function useGridState(options, onRegisterApi) {
1524
1246
  state: infiniteScrollStateRef.current,
1525
1247
  startIndex,
1526
1248
  visibleRows: pipelineRef.current.visibleRows.length,
1527
- viewportRows: Math.max(1, Math.ceil((optionsRef.current.viewportHeight ?? 560) / (optionsRef.current.rowHeight ?? 44))),
1249
+ viewportRows: computeViewportRows(
1250
+ optionsRef.current.viewportHeight,
1251
+ optionsRef.current.rowHeight
1252
+ ),
1528
1253
  threshold: optionsRef.current.infiniteScrollRowsFromEnd ?? 20,
1529
1254
  setState: (state) => setInfiniteScrollState(state)
1530
1255
  });
@@ -1624,30 +1349,62 @@ function useGridState(options, onRegisterApi) {
1624
1349
  onPageSizeChange: onPageSizeChangeFn,
1625
1350
  runBenchmark: runBenchmarkFn,
1626
1351
  exportCsv: exportCsvFn,
1627
- onViewportScroll: onViewportScrollFn
1352
+ onViewportScroll: onViewportScrollFn,
1353
+ // Pinning
1354
+ isPinned: isPinnedFn,
1355
+ pinnedOffset: pinnedOffsetFn,
1356
+ isPinningEnabled: isPinningEnabledFn,
1357
+ isColumnPinnable: isColumnPinnableFn,
1358
+ togglePin: togglePinFn,
1359
+ pinningFeature: FEATURE_PINNING
1628
1360
  };
1629
1361
  }
1630
1362
 
1631
1363
  // src/useVirtualScroll.ts
1632
1364
  import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
1365
+
1366
+ // src/virtualScrollMath.ts
1367
+ function calculateVirtualWindow(request) {
1368
+ const overscan = request.overscan ?? 3;
1369
+ if (request.itemCount <= 0 || request.itemSize <= 0) {
1370
+ return {
1371
+ visibleRange: { start: 0, end: 0 },
1372
+ totalHeight: Math.max(0, request.itemCount) * Math.max(0, request.itemSize),
1373
+ offsetY: 0
1374
+ };
1375
+ }
1376
+ const rawStart = Math.floor(request.scrollTop / request.itemSize) - overscan;
1377
+ const start = Math.max(0, rawStart);
1378
+ const rawEnd = rawStart + Math.ceil(request.viewportHeight / request.itemSize) + 2 * overscan;
1379
+ const end = Math.min(request.itemCount, rawEnd);
1380
+ return {
1381
+ visibleRange: { start, end },
1382
+ totalHeight: request.itemCount * request.itemSize,
1383
+ offsetY: start * request.itemSize
1384
+ };
1385
+ }
1386
+
1387
+ // src/useVirtualScroll.ts
1633
1388
  function useVirtualScroll(options) {
1634
1389
  const { itemCount, itemSize, viewportHeight, overscan = 3 } = options;
1635
1390
  const [scrollTop, setScrollTop] = useState2(0);
1636
1391
  const viewportRef = useRef2(null);
1637
- const rawStart = Math.floor(scrollTop / itemSize) - overscan;
1638
- const start = Math.max(0, rawStart);
1639
- const rawEnd = rawStart + Math.ceil(viewportHeight / itemSize) + 2 * overscan;
1640
- const end = Math.min(itemCount, rawEnd);
1641
- const totalHeight = itemCount * itemSize;
1642
- const offsetY = start * itemSize;
1392
+ const virtualWindow = calculateVirtualWindow({
1393
+ itemCount,
1394
+ itemSize,
1395
+ viewportHeight,
1396
+ overscan,
1397
+ scrollTop
1398
+ });
1643
1399
  const onScroll = useCallback2((event) => {
1644
1400
  setScrollTop(event.currentTarget.scrollTop);
1645
1401
  }, []);
1646
1402
  return {
1647
- visibleRange: { start, end },
1648
- totalHeight,
1649
- offsetY,
1403
+ visibleRange: virtualWindow.visibleRange,
1404
+ totalHeight: virtualWindow.totalHeight,
1405
+ offsetY: virtualWindow.offsetY,
1650
1406
  onScroll,
1407
+ setScrollTop,
1651
1408
  viewportRef,
1652
1409
  scrollTop
1653
1410
  };
@@ -1655,7 +1412,13 @@ function useVirtualScroll(options) {
1655
1412
 
1656
1413
  // src/UiGrid.tsx
1657
1414
  import { jsx, jsxs } from "react/jsx-runtime";
1658
- function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, className }) {
1415
+ function UiGrid({
1416
+ options,
1417
+ onRegisterApi,
1418
+ cellRenderer,
1419
+ expandableRenderer,
1420
+ className
1421
+ }) {
1659
1422
  const state = useGridState(options, onRegisterApi);
1660
1423
  const {
1661
1424
  pipeline,
@@ -1690,10 +1453,82 @@ function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, clas
1690
1453
  viewportHeight: options.viewportHeight ?? 560,
1691
1454
  overscan: 3
1692
1455
  });
1456
+ const headerGridRef = React.useRef(null);
1457
+ const filterGridRef = React.useRef(null);
1458
+ const [openPinMenuColumn, setOpenPinMenuColumn] = React.useState(null);
1459
+ const [headerStickyHeight, setHeaderStickyHeight] = React.useState(0);
1460
+ const [filterStickyHeight, setFilterStickyHeight] = React.useState(0);
1461
+ const stickyChromeHeight = headerStickyHeight + filterStickyHeight;
1462
+ const scrollContainerHeight = `${(options.viewportHeight ?? 560) + stickyChromeHeight}px`;
1463
+ const eventPathIncludesClass = React.useCallback((event, className2) => {
1464
+ const eventPath = typeof event.composedPath === "function" ? event.composedPath() : event.target ? [event.target] : [];
1465
+ return eventPath.some((target) => {
1466
+ if (!target || typeof target !== "object" || !("classList" in target)) {
1467
+ return false;
1468
+ }
1469
+ const classList = target.classList;
1470
+ return classList?.contains(className2) ?? false;
1471
+ });
1472
+ }, []);
1473
+ const isPinMenuOpen = React.useCallback(
1474
+ (column) => openPinMenuColumn === column.name,
1475
+ [openPinMenuColumn]
1476
+ );
1477
+ const pinButtonLabel = React.useCallback(
1478
+ (column) => state.isPinned(column) ? labels.unpin : labels.pinColumn,
1479
+ [labels, state]
1480
+ );
1481
+ const onPinTrigger = React.useCallback(
1482
+ (column, event) => {
1483
+ event?.stopPropagation();
1484
+ if (state.isPinned(column)) {
1485
+ setOpenPinMenuColumn(null);
1486
+ state.gridApi.pinning.pinColumn(column.name, "none");
1487
+ return;
1488
+ }
1489
+ setOpenPinMenuColumn((current) => current === column.name ? null : column.name);
1490
+ },
1491
+ [state]
1492
+ );
1493
+ const choosePinDirection = React.useCallback(
1494
+ (column, direction, event) => {
1495
+ event?.stopPropagation();
1496
+ setOpenPinMenuColumn(null);
1497
+ state.gridApi.pinning.pinColumn(column.name, direction);
1498
+ },
1499
+ [state]
1500
+ );
1501
+ React.useLayoutEffect(() => {
1502
+ setHeaderStickyHeight(headerGridRef.current?.offsetHeight ?? 0);
1503
+ setFilterStickyHeight(filterGridRef.current?.offsetHeight ?? 0);
1504
+ }, [visibleColumns, filteringFeature, options.enableFiltering]);
1505
+ React.useEffect(() => {
1506
+ if (!openPinMenuColumn) {
1507
+ return;
1508
+ }
1509
+ const handleDocumentClick = (event) => {
1510
+ if (eventPathIncludesClass(event, "pin-control")) {
1511
+ return;
1512
+ }
1513
+ setOpenPinMenuColumn(null);
1514
+ };
1515
+ const handleDocumentEscape = (event) => {
1516
+ if (event.key === "Escape") {
1517
+ setOpenPinMenuColumn(null);
1518
+ }
1519
+ };
1520
+ document.addEventListener("click", handleDocumentClick);
1521
+ document.addEventListener("keydown", handleDocumentEscape);
1522
+ return () => {
1523
+ document.removeEventListener("click", handleDocumentClick);
1524
+ document.removeEventListener("keydown", handleDocumentEscape);
1525
+ };
1526
+ }, [eventPathIncludesClass, openPinMenuColumn]);
1693
1527
  const itemsToRender = virtualizationEnabled ? displayItems.slice(virtualScroll.visibleRange.start, virtualScroll.visibleRange.end) : displayItems;
1694
- const onViewportScroll = (event) => {
1695
- virtualScroll.onScroll(event);
1696
- const startIndex = Math.floor(event.currentTarget.scrollTop / rowSize);
1528
+ const onGridTableScroll = (event) => {
1529
+ const bodyScrollTop = Math.max(0, event.currentTarget.scrollTop - stickyChromeHeight);
1530
+ virtualScroll.setScrollTop(bodyScrollTop);
1531
+ const startIndex = Math.floor(bodyScrollTop / rowSize);
1697
1532
  state.onViewportScroll(startIndex);
1698
1533
  };
1699
1534
  function renderDisplayItem(item) {
@@ -1719,7 +1554,16 @@ function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, clas
1719
1554
  " ",
1720
1555
  labels.groupRowsSuffix
1721
1556
  ] }),
1722
- /* @__PURE__ */ jsx("svg", { className: "toggle-icon group-disclosure-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: item.collapsed ? "M10 7l5 5-5 5z" : "M7 10l5 5 5-5z" }) }),
1557
+ /* @__PURE__ */ jsx(
1558
+ "svg",
1559
+ {
1560
+ className: "toggle-icon group-disclosure-icon",
1561
+ viewBox: "0 0 24 24",
1562
+ "aria-hidden": "true",
1563
+ focusable: false,
1564
+ children: /* @__PURE__ */ jsx("path", { d: item.collapsed ? "M10 7l5 5-5 5z" : "M7 10l5 5 5-5z" })
1565
+ }
1566
+ ),
1723
1567
  /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.groupDisclosureLabel(item) })
1724
1568
  ]
1725
1569
  },
@@ -1741,62 +1585,84 @@ function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, clas
1741
1585
  }
1742
1586
  if (item.kind !== "row") return null;
1743
1587
  const rowItem = item;
1744
- return visibleColumns.map((column) => /* @__PURE__ */ jsx(
1745
- "div",
1746
- {
1747
- className: cellClassName(rowItem, column),
1748
- "data-part": "body-cell",
1749
- role: "gridcell",
1750
- tabIndex: 0,
1751
- "data-row-id": rowItem.row.id,
1752
- "data-col-name": column.name,
1753
- onFocus: () => state.focusCell(rowItem.row, column),
1754
- onClick: () => state.focusCell(rowItem.row, column),
1755
- onDoubleClick: (e) => state.handleCellDoubleClick(rowItem.row, column, e),
1756
- onKeyDown: (e) => state.handleCellKeyDown(rowItem.row, column, e),
1757
- children: /* @__PURE__ */ jsxs("div", { className: "cell-shell", style: { paddingInlineStart: state.cellIndent(rowItem.row, column) }, children: [
1758
- treeViewFeature && state.showTreeToggle(rowItem.row, column) && /* @__PURE__ */ jsx(
1759
- "button",
1760
- {
1761
- type: "button",
1762
- className: "row-toggle row-toggle-tree",
1763
- "data-part": "tree-toggle",
1764
- "aria-label": state.treeToggleLabel(rowItem.row),
1765
- "aria-expanded": state.isTreeRowExpanded(rowItem.row),
1766
- onClick: (e) => state.toggleTreeRow(rowItem.row, e),
1767
- children: /* @__PURE__ */ jsx("svg", { className: "toggle-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: state.isTreeRowExpanded(rowItem.row) ? "M7 10l5 5 5-5z" : "M10 7l5 5-5 5z" }) })
1768
- }
1769
- ),
1770
- expandableFeature && state.showExpandToggle(rowItem.row, column) && /* @__PURE__ */ jsx(
1771
- "button",
1772
- {
1773
- type: "button",
1774
- className: "row-toggle row-toggle-expand",
1775
- "data-part": "expand-toggle",
1776
- "aria-label": state.expandToggleLabel(rowItem.row),
1777
- "aria-expanded": rowItem.row.expanded,
1778
- onClick: (e) => state.toggleRowExpansion(rowItem.row, e),
1779
- children: /* @__PURE__ */ jsx("svg", { className: "toggle-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: rowItem.row.expanded ? "M7 10l5 5 5-5z" : "M10 7l5 5-5 5z" }) })
1780
- }
1781
- ),
1782
- /* @__PURE__ */ jsx("span", { className: "cell-value", children: cellEditFeature && state.isEditingCell(rowItem.row, column) ? /* @__PURE__ */ jsx(
1783
- "input",
1588
+ return visibleColumns.map((column) => {
1589
+ const pinned = state.isPinned(column);
1590
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
1591
+ return /* @__PURE__ */ jsx(
1592
+ "div",
1593
+ {
1594
+ className: `${cellClassName(rowItem, column)}${pinned ? " is-pinned" : ""}`,
1595
+ "data-part": "body-cell",
1596
+ role: "gridcell",
1597
+ tabIndex: 0,
1598
+ "data-row-id": rowItem.row.id,
1599
+ "data-col-name": column.name,
1600
+ onFocus: () => state.focusCell(rowItem.row, column),
1601
+ onClick: () => state.focusCell(rowItem.row, column),
1602
+ onDoubleClick: (e) => state.handleCellDoubleClick(rowItem.row, column, e),
1603
+ onKeyDown: (e) => state.handleCellKeyDown(rowItem.row, column, e),
1604
+ style: {
1605
+ position: pinned ? "sticky" : void 0,
1606
+ left: pinOffset?.side === "left" ? pinOffset.offset : void 0,
1607
+ right: pinOffset?.side === "right" ? pinOffset.offset : void 0,
1608
+ zIndex: pinned ? 2 : void 0
1609
+ },
1610
+ children: /* @__PURE__ */ jsxs(
1611
+ "div",
1784
1612
  {
1785
- className: "cell-editor",
1786
- "data-row-id": rowItem.row.id,
1787
- "data-col-name": column.name,
1788
- "aria-label": state.headerLabel(column),
1789
- type: state.editorInputType(column),
1790
- defaultValue: editingValue,
1791
- onChange: (e) => state.updateEditingValue(e.target.value),
1792
- onKeyDown: (e) => state.handleEditorKeyDown(e),
1793
- onBlur: (e) => state.handleEditorBlur(e)
1613
+ className: "cell-shell",
1614
+ style: { paddingInlineStart: state.cellIndent(rowItem.row, column) },
1615
+ children: [
1616
+ treeViewFeature && state.showTreeToggle(rowItem.row, column) && /* @__PURE__ */ jsx(
1617
+ "button",
1618
+ {
1619
+ type: "button",
1620
+ className: "row-toggle row-toggle-tree",
1621
+ "data-part": "tree-toggle",
1622
+ "aria-label": state.treeToggleLabel(rowItem.row),
1623
+ "aria-expanded": state.isTreeRowExpanded(rowItem.row),
1624
+ onClick: (e) => state.toggleTreeRow(rowItem.row, e),
1625
+ children: /* @__PURE__ */ jsx("svg", { className: "toggle-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx(
1626
+ "path",
1627
+ {
1628
+ d: state.isTreeRowExpanded(rowItem.row) ? "M7 10l5 5 5-5z" : "M10 7l5 5-5 5z"
1629
+ }
1630
+ ) })
1631
+ }
1632
+ ),
1633
+ expandableFeature && state.showExpandToggle(rowItem.row, column) && /* @__PURE__ */ jsx(
1634
+ "button",
1635
+ {
1636
+ type: "button",
1637
+ className: "row-toggle row-toggle-expand",
1638
+ "data-part": "expand-toggle",
1639
+ "aria-label": state.expandToggleLabel(rowItem.row),
1640
+ "aria-expanded": rowItem.row.expanded,
1641
+ onClick: (e) => state.toggleRowExpansion(rowItem.row, e),
1642
+ children: /* @__PURE__ */ jsx("svg", { className: "toggle-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: rowItem.row.expanded ? "M7 10l5 5 5-5z" : "M10 7l5 5-5 5z" }) })
1643
+ }
1644
+ ),
1645
+ /* @__PURE__ */ jsx("span", { className: "cell-value", children: cellEditFeature && state.isEditingCell(rowItem.row, column) ? /* @__PURE__ */ jsx(
1646
+ "input",
1647
+ {
1648
+ className: "cell-editor",
1649
+ "data-row-id": rowItem.row.id,
1650
+ "data-col-name": column.name,
1651
+ "aria-label": state.headerLabel(column),
1652
+ type: state.editorInputType(column),
1653
+ defaultValue: editingValue,
1654
+ onChange: (e) => state.updateEditingValue(e.target.value),
1655
+ onKeyDown: (e) => state.handleEditorKeyDown(e),
1656
+ onBlur: (e) => state.handleEditorBlur(e)
1657
+ }
1658
+ ) : cellRenderer ? cellRenderer(state.cellContext(rowItem.row, column)) ?? state.displayValue(rowItem.row, column) : state.displayValue(rowItem.row, column) })
1659
+ ]
1794
1660
  }
1795
- ) : cellRenderer ? cellRenderer(state.cellContext(rowItem.row, column)) ?? state.displayValue(rowItem.row, column) : state.displayValue(rowItem.row, column) })
1796
- ] })
1797
- },
1798
- `${rowItem.row.id}-${column.name}`
1799
- ));
1661
+ )
1662
+ },
1663
+ `${rowItem.row.id}-${column.name}`
1664
+ );
1665
+ });
1800
1666
  }
1801
1667
  function cellClassName(item, column) {
1802
1668
  const classes = ["body-cell", "ui-grid-cell"];
@@ -1826,225 +1692,426 @@ function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, clas
1826
1692
  /* @__PURE__ */ jsx("p", { className: "deck", children: "Familiar `gridOptions` and `onRegisterApi`, built with React hooks, virtualization, grouping, sorting, filtering, and column ordering." })
1827
1693
  ] }),
1828
1694
  /* @__PURE__ */ jsxs("div", { className: "hero-actions", children: [
1829
- /* @__PURE__ */ jsx("button", { type: "button", className: "action action-secondary", "data-part": "action benchmark-action", onClick: () => state.runBenchmark(), children: "Benchmark" }),
1830
- csvExportFeature && /* @__PURE__ */ jsx("button", { type: "button", className: "action action-secondary", "data-part": "action export-action", onClick: () => state.exportCsv(), children: "Export CSV" }),
1695
+ /* @__PURE__ */ jsx(
1696
+ "button",
1697
+ {
1698
+ type: "button",
1699
+ className: "action action-secondary",
1700
+ "data-part": "action benchmark-action",
1701
+ onClick: () => state.runBenchmark(),
1702
+ children: "Benchmark"
1703
+ }
1704
+ ),
1705
+ csvExportFeature && /* @__PURE__ */ jsx(
1706
+ "button",
1707
+ {
1708
+ type: "button",
1709
+ className: "action action-secondary",
1710
+ "data-part": "action export-action",
1711
+ onClick: () => state.exportCsv(),
1712
+ children: "Export CSV"
1713
+ }
1714
+ ),
1831
1715
  /* @__PURE__ */ jsxs("div", { className: "stats-card", "data-part": "stats-card", children: [
1832
1716
  /* @__PURE__ */ jsx("span", { children: visibleRowCount }),
1833
1717
  /* @__PURE__ */ jsx("small", { children: labels.statsVisibleRows })
1834
1718
  ] })
1835
1719
  ] })
1836
1720
  ] }),
1837
- /* @__PURE__ */ jsxs("section", { className: "metrics-strip", "data-part": "metrics", "aria-label": "Grid performance metrics", children: [
1838
- /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1839
- /* @__PURE__ */ jsxs("strong", { children: [
1840
- pipelineMs.toFixed(2),
1841
- " ms"
1842
- ] }),
1843
- /* @__PURE__ */ jsx("span", { children: "pipeline" })
1844
- ] }),
1845
- /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1846
- /* @__PURE__ */ jsx("strong", { children: virtualizationEnabled ? "On" : "Off" }),
1847
- /* @__PURE__ */ jsx("span", { children: "virtualization" })
1848
- ] }),
1849
- /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1850
- /* @__PURE__ */ jsx("strong", { children: state.groupByColumns.length }),
1851
- /* @__PURE__ */ jsx("span", { children: "group columns" })
1852
- ] }),
1853
- /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1854
- /* @__PURE__ */ jsx("strong", { children: benchmarkResult?.averageMs?.toFixed(2) || "\u2014" }),
1855
- /* @__PURE__ */ jsx("span", { children: "benchmark avg" })
1856
- ] })
1857
- ] }),
1858
- /* @__PURE__ */ jsxs("section", { className: "grid-frame ui-grid", "data-part": "grid-frame", role: "grid", "aria-label": options.title ?? "Data grid", children: [
1859
- /* @__PURE__ */ jsxs("div", { className: "grid-toolbar", "data-part": "grid-toolbar", children: [
1860
- /* @__PURE__ */ jsxs("div", { children: [
1861
- /* @__PURE__ */ jsx("strong", { children: visibleRowCount }),
1862
- /* @__PURE__ */ jsxs("span", { children: [
1863
- labels.toolbarOf,
1864
- " ",
1865
- totalRows,
1866
- " ",
1867
- labels.toolbarRows
1721
+ /* @__PURE__ */ jsxs(
1722
+ "section",
1723
+ {
1724
+ className: "metrics-strip",
1725
+ "data-part": "metrics",
1726
+ "aria-label": "Grid performance metrics",
1727
+ children: [
1728
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1729
+ /* @__PURE__ */ jsxs("strong", { children: [
1730
+ pipelineMs.toFixed(2),
1731
+ " ms"
1732
+ ] }),
1733
+ /* @__PURE__ */ jsx("span", { children: "pipeline" })
1734
+ ] }),
1735
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1736
+ /* @__PURE__ */ jsx("strong", { children: virtualizationEnabled ? "On" : "Off" }),
1737
+ /* @__PURE__ */ jsx("span", { children: "virtualization" })
1738
+ ] }),
1739
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1740
+ /* @__PURE__ */ jsx("strong", { children: state.groupByColumns.length }),
1741
+ /* @__PURE__ */ jsx("span", { children: "group columns" })
1742
+ ] }),
1743
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
1744
+ /* @__PURE__ */ jsx("strong", { children: benchmarkResult?.averageMs?.toFixed(2) || "\u2014" }),
1745
+ /* @__PURE__ */ jsx("span", { children: "benchmark avg" })
1868
1746
  ] })
1869
- ] }),
1870
- /* @__PURE__ */ jsx("p", { children: "`gridOptions` compatibility layer: sorting, filtering, grouping, column moving, templating, and virtualized rendering." })
1871
- ] }),
1872
- /* @__PURE__ */ jsxs("div", { className: "grid-table ui-grid-contents-wrapper", "data-part": "grid-table", children: [
1873
- /* @__PURE__ */ jsx(
1874
- "div",
1875
- {
1876
- className: "header-grid ui-grid-header ui-grid-header-canvas",
1877
- "data-part": "header",
1878
- role: "row",
1879
- style: { gridTemplateColumns },
1880
- children: visibleColumns.map((column) => /* @__PURE__ */ jsxs(
1881
- "div",
1882
- {
1883
- className: `header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== "none" ? " is-active" : ""}`,
1884
- "data-part": "header-cell",
1885
- role: "columnheader",
1886
- "aria-sort": sortingFeature ? state.sortAriaSort(column) : void 0,
1887
- children: [
1888
- /* @__PURE__ */ jsx("span", { className: "header-label", children: state.headerLabel(column) }),
1889
- /* @__PURE__ */ jsxs("div", { className: "header-actions", children: [
1890
- sortingFeature && /* @__PURE__ */ jsxs(
1891
- "button",
1892
- {
1893
- type: "button",
1894
- className: `header-action${!state.isColumnSortable(column) ? " header-action-disabled" : ""}`,
1895
- disabled: !state.isColumnSortable(column),
1896
- "aria-label": state.sortButtonLabel(column),
1897
- title: state.sortButtonLabel(column),
1898
- onClick: () => state.toggleSort(column),
1899
- children: [
1900
- renderSortIcon(column),
1901
- /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.sortButtonLabel(column) })
1902
- ]
1903
- }
1904
- ),
1905
- groupingFeature && state.isGroupingEnabled() && column.enableGrouping !== false && /* @__PURE__ */ jsxs(
1906
- "button",
1907
- {
1908
- type: "button",
1909
- className: `chip-action${state.isGrouped(column) ? " chip-action-active" : ""}`,
1910
- "data-part": "group-toggle",
1911
- "aria-label": state.groupingButtonLabel(column),
1912
- title: state.groupingButtonLabel(column),
1913
- onClick: (e) => state.toggleGrouping(column, e),
1914
- children: [
1915
- /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: "M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" }) }),
1916
- /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.groupingButtonLabel(column) })
1917
- ]
1918
- }
1919
- )
1920
- ] })
1921
- ]
1922
- },
1923
- column.name
1924
- ))
1925
- }
1926
- ),
1927
- filteringFeature && state.isFilteringEnabled() && /* @__PURE__ */ jsx("div", { className: "filter-grid ui-grid-header", "data-part": "filters", style: { gridTemplateColumns }, children: visibleColumns.map((column) => /* @__PURE__ */ jsxs("label", { className: "filter-cell ui-grid-filter-container", "data-part": "filter-cell", children: [
1928
- /* @__PURE__ */ jsxs("span", { className: "sr-only ui-grid-sr-only", children: [
1929
- labels.filterColumn,
1930
- " ",
1931
- state.headerLabel(column)
1747
+ ]
1748
+ }
1749
+ ),
1750
+ /* @__PURE__ */ jsxs(
1751
+ "section",
1752
+ {
1753
+ className: "grid-frame ui-grid",
1754
+ "data-part": "grid-frame",
1755
+ role: "grid",
1756
+ "aria-label": options.title ?? "Data grid",
1757
+ children: [
1758
+ /* @__PURE__ */ jsxs("div", { className: "grid-toolbar", "data-part": "grid-toolbar", children: [
1759
+ /* @__PURE__ */ jsxs("div", { children: [
1760
+ /* @__PURE__ */ jsx("strong", { children: visibleRowCount }),
1761
+ /* @__PURE__ */ jsxs("span", { children: [
1762
+ labels.toolbarOf,
1763
+ " ",
1764
+ totalRows,
1765
+ " ",
1766
+ labels.toolbarRows
1767
+ ] })
1768
+ ] }),
1769
+ /* @__PURE__ */ jsx("p", { children: "`gridOptions` compatibility layer: sorting, filtering, grouping, column moving, templating, and virtualized rendering." })
1932
1770
  ] }),
1933
- /* @__PURE__ */ jsx(
1934
- "input",
1771
+ /* @__PURE__ */ jsxs(
1772
+ "div",
1935
1773
  {
1936
- className: "ui-grid-filter-input",
1937
- type: "text",
1938
- defaultValue: state.filterValue(column.name),
1939
- placeholder: state.filterPlaceholder(column),
1940
- disabled: state.isFilterInputDisabled(column),
1941
- onChange: (e) => state.updateFilter(column.name, e.target.value)
1774
+ className: "grid-table ui-grid-contents-wrapper",
1775
+ "data-part": "grid-table",
1776
+ style: virtualizationEnabled ? { height: scrollContainerHeight, overflowY: "auto" } : void 0,
1777
+ onScroll: virtualizationEnabled ? onGridTableScroll : void 0,
1778
+ children: [
1779
+ /* @__PURE__ */ jsx(
1780
+ "div",
1781
+ {
1782
+ className: "header-grid ui-grid-header ui-grid-header-canvas",
1783
+ "data-part": "header",
1784
+ role: "row",
1785
+ ref: headerGridRef,
1786
+ style: { gridTemplateColumns },
1787
+ children: visibleColumns.map((column) => {
1788
+ const pinned = state.isPinned(column);
1789
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
1790
+ const pinMenuOpen = isPinMenuOpen(column);
1791
+ return /* @__PURE__ */ jsxs(
1792
+ "div",
1793
+ {
1794
+ className: `header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== "none" ? " is-active" : ""}${pinned ? " is-pinned" : ""}${pinMenuOpen ? " is-pin-menu-open" : ""}`,
1795
+ "data-part": "header-cell",
1796
+ role: "columnheader",
1797
+ "aria-sort": sortingFeature ? state.sortAriaSort(column) : void 0,
1798
+ style: {
1799
+ position: pinned ? "sticky" : void 0,
1800
+ left: pinOffset?.side === "left" ? pinOffset.offset : void 0,
1801
+ right: pinOffset?.side === "right" ? pinOffset.offset : void 0,
1802
+ zIndex: pinMenuOpen ? 8 : pinned ? 2 : void 0
1803
+ },
1804
+ children: [
1805
+ /* @__PURE__ */ jsx("span", { className: "header-label", children: state.headerLabel(column) }),
1806
+ /* @__PURE__ */ jsxs("div", { className: "header-actions", children: [
1807
+ sortingFeature && /* @__PURE__ */ jsxs(
1808
+ "button",
1809
+ {
1810
+ type: "button",
1811
+ className: `header-action${!state.isColumnSortable(column) ? " header-action-disabled" : ""}`,
1812
+ disabled: !state.isColumnSortable(column),
1813
+ "aria-label": state.sortButtonLabel(column),
1814
+ title: state.sortButtonLabel(column),
1815
+ onClick: () => state.toggleSort(column),
1816
+ children: [
1817
+ renderSortIcon(column),
1818
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.sortButtonLabel(column) })
1819
+ ]
1820
+ }
1821
+ ),
1822
+ groupingFeature && state.isGroupingEnabled() && column.enableGrouping !== false && /* @__PURE__ */ jsxs(
1823
+ "button",
1824
+ {
1825
+ type: "button",
1826
+ className: `chip-action${state.isGrouped(column) ? " chip-action-active" : ""}`,
1827
+ "data-part": "group-toggle",
1828
+ "aria-label": state.groupingButtonLabel(column),
1829
+ title: state.groupingButtonLabel(column),
1830
+ onClick: (e) => state.toggleGrouping(column, e),
1831
+ children: [
1832
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: "M4 6h8v4H4V6Zm0 8h8v4H4v-4Zm10-8h6v4h-6V6Zm0 8h6v4h-6v-4Z" }) }),
1833
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.groupingButtonLabel(column) })
1834
+ ]
1835
+ }
1836
+ ),
1837
+ state.pinningFeature && state.isPinningEnabled() && state.isColumnPinnable(column) && /* @__PURE__ */ jsxs("div", { className: `pin-control${pinMenuOpen ? " pin-control-open" : ""}`, onClick: (event) => event.stopPropagation(), children: [
1838
+ /* @__PURE__ */ jsxs(
1839
+ "button",
1840
+ {
1841
+ type: "button",
1842
+ className: `chip-action pin-trigger${pinned || pinMenuOpen ? " chip-action-active" : ""}`,
1843
+ "data-part": "pin-toggle",
1844
+ "aria-label": pinButtonLabel(column),
1845
+ title: pinButtonLabel(column),
1846
+ "aria-haspopup": pinned ? void 0 : "menu",
1847
+ "aria-expanded": pinned ? void 0 : pinMenuOpen,
1848
+ onClick: (event) => onPinTrigger(column, event),
1849
+ children: [
1850
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: "M16 12V4h1V2H7v2h1v8l-2 2v2h5v6l1 1 1-1v-6h5v-2l-2-2z" }) }),
1851
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: pinButtonLabel(column) })
1852
+ ]
1853
+ }
1854
+ ),
1855
+ /* @__PURE__ */ jsxs(
1856
+ "div",
1857
+ {
1858
+ className: "pin-menu",
1859
+ "data-part": "pin-menu",
1860
+ role: "menu",
1861
+ "aria-label": "Pin options",
1862
+ "aria-hidden": !pinMenuOpen,
1863
+ children: [
1864
+ /* @__PURE__ */ jsxs(
1865
+ "button",
1866
+ {
1867
+ type: "button",
1868
+ className: "pin-menu-action",
1869
+ "data-part": "pin-left-action",
1870
+ role: "menuitem",
1871
+ "aria-label": labels.pinLeft,
1872
+ title: labels.pinLeft,
1873
+ tabIndex: pinMenuOpen ? 0 : -1,
1874
+ onClick: (event) => choosePinDirection(column, "left", event),
1875
+ children: [
1876
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: "M10 6 4 12l6 6v-4h10v-4H10V6z" }) }),
1877
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: labels.pinLeft })
1878
+ ]
1879
+ }
1880
+ ),
1881
+ /* @__PURE__ */ jsxs(
1882
+ "button",
1883
+ {
1884
+ type: "button",
1885
+ className: "pin-menu-action",
1886
+ "data-part": "pin-right-action",
1887
+ role: "menuitem",
1888
+ "aria-label": labels.pinRight,
1889
+ title: labels.pinRight,
1890
+ tabIndex: pinMenuOpen ? 0 : -1,
1891
+ onClick: (event) => choosePinDirection(column, "right", event),
1892
+ children: [
1893
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: "M14 6v4H4v4h10v4l6-6-6-6z" }) }),
1894
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: labels.pinRight })
1895
+ ]
1896
+ }
1897
+ )
1898
+ ]
1899
+ }
1900
+ )
1901
+ ] })
1902
+ ] })
1903
+ ]
1904
+ },
1905
+ column.name
1906
+ );
1907
+ })
1908
+ }
1909
+ ),
1910
+ filteringFeature && state.isFilteringEnabled() && /* @__PURE__ */ jsx(
1911
+ "div",
1912
+ {
1913
+ className: "filter-grid ui-grid-header",
1914
+ "data-part": "filters",
1915
+ ref: filterGridRef,
1916
+ style: { gridTemplateColumns, ["--ui-grid-header-sticky-top"]: `${headerStickyHeight}px` },
1917
+ children: visibleColumns.map((column) => {
1918
+ const pinned = state.isPinned(column);
1919
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
1920
+ return /* @__PURE__ */ jsxs(
1921
+ "label",
1922
+ {
1923
+ className: `filter-cell ui-grid-filter-container${pinned ? " is-pinned" : ""}`,
1924
+ "data-part": "filter-cell",
1925
+ style: {
1926
+ position: pinned ? "sticky" : void 0,
1927
+ left: pinOffset?.side === "left" ? pinOffset.offset : void 0,
1928
+ right: pinOffset?.side === "right" ? pinOffset.offset : void 0,
1929
+ zIndex: pinned ? 2 : void 0
1930
+ },
1931
+ children: [
1932
+ /* @__PURE__ */ jsxs("span", { className: "sr-only ui-grid-sr-only", children: [
1933
+ labels.filterColumn,
1934
+ " ",
1935
+ state.headerLabel(column)
1936
+ ] }),
1937
+ /* @__PURE__ */ jsx(
1938
+ "input",
1939
+ {
1940
+ className: "ui-grid-filter-input",
1941
+ type: "text",
1942
+ defaultValue: state.filterValue(column.name),
1943
+ placeholder: state.filterPlaceholder(column),
1944
+ disabled: state.isFilterInputDisabled(column),
1945
+ onChange: (e) => state.updateFilter(column.name, e.target.value)
1946
+ }
1947
+ )
1948
+ ]
1949
+ },
1950
+ column.name
1951
+ );
1952
+ })
1953
+ }
1954
+ ),
1955
+ displayItems.length > 0 ? virtualizationEnabled ? /* @__PURE__ */ jsx("div", { className: "grid-virtual-spacer", style: { height: `${virtualScroll.totalHeight}px` }, children: /* @__PURE__ */ jsx(
1956
+ "div",
1957
+ {
1958
+ className: "body-grid ui-grid-canvas grid-virtual-body",
1959
+ "data-part": "body",
1960
+ role: "rowgroup",
1961
+ style: {
1962
+ gridTemplateColumns,
1963
+ position: "absolute",
1964
+ top: `${virtualScroll.offsetY}px`,
1965
+ left: 0
1966
+ },
1967
+ children: itemsToRender.map(renderDisplayItem)
1968
+ }
1969
+ ) }) : /* @__PURE__ */ jsx(
1970
+ "div",
1971
+ {
1972
+ className: "body-grid ui-grid-canvas",
1973
+ "data-part": "body",
1974
+ role: "rowgroup",
1975
+ style: { gridTemplateColumns },
1976
+ children: displayItems.map(renderDisplayItem)
1977
+ }
1978
+ ) : /* @__PURE__ */ jsxs("div", { className: "empty-state ui-grid-no-row-overlay", "data-part": "empty-state", children: [
1979
+ /* @__PURE__ */ jsx("strong", { children: options.emptyMessage ?? labels.emptyHeading }),
1980
+ /* @__PURE__ */ jsx("p", { children: labels.emptyDescription })
1981
+ ] }),
1982
+ paginationFeature && state.showPaginationControls() && /* @__PURE__ */ jsxs(
1983
+ "footer",
1984
+ {
1985
+ className: "pagination-bar ui-grid-pagination",
1986
+ "data-part": "pagination",
1987
+ role: "navigation",
1988
+ "aria-label": labels.paginationPage,
1989
+ children: [
1990
+ /* @__PURE__ */ jsx("p", { children: state.paginationSummary() }),
1991
+ /* @__PURE__ */ jsxs("div", { className: "pagination-controls", children: [
1992
+ /* @__PURE__ */ jsxs(
1993
+ "button",
1994
+ {
1995
+ type: "button",
1996
+ className: "action action-secondary pagination-button",
1997
+ "aria-label": labels.paginationPrevious,
1998
+ disabled: paginationCurrentPage <= 1,
1999
+ onClick: () => state.previousPage(),
2000
+ children: [
2001
+ /* @__PURE__ */ jsx(
2002
+ "svg",
2003
+ {
2004
+ className: "pagination-icon",
2005
+ viewBox: "0 0 24 24",
2006
+ "aria-hidden": "true",
2007
+ focusable: false,
2008
+ children: /* @__PURE__ */ jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" })
2009
+ }
2010
+ ),
2011
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationPrevious })
2012
+ ]
2013
+ }
2014
+ ),
2015
+ /* @__PURE__ */ jsxs("span", { children: [
2016
+ labels.paginationPage,
2017
+ " ",
2018
+ paginationCurrentPage,
2019
+ " ",
2020
+ labels.paginationOf,
2021
+ " ",
2022
+ paginationTotalPages
2023
+ ] }),
2024
+ /* @__PURE__ */ jsxs(
2025
+ "button",
2026
+ {
2027
+ type: "button",
2028
+ className: "action action-secondary pagination-button",
2029
+ "aria-label": labels.paginationNext,
2030
+ disabled: paginationCurrentPage >= paginationTotalPages,
2031
+ onClick: () => state.nextPage(),
2032
+ children: [
2033
+ /* @__PURE__ */ jsx(
2034
+ "svg",
2035
+ {
2036
+ className: "pagination-icon",
2037
+ viewBox: "0 0 24 24",
2038
+ "aria-hidden": "true",
2039
+ focusable: false,
2040
+ children: /* @__PURE__ */ jsx("path", { d: "M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z" })
2041
+ }
2042
+ ),
2043
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationNext })
2044
+ ]
2045
+ }
2046
+ ),
2047
+ state.pageSizeOptions().length > 0 && /* @__PURE__ */ jsxs("label", { className: "pagination-size", children: [
2048
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationRows }),
2049
+ /* @__PURE__ */ jsx(
2050
+ "select",
2051
+ {
2052
+ "aria-label": labels.paginationRows,
2053
+ value: paginationSelectedPageSize,
2054
+ onChange: (e) => state.onPageSizeChange(e.target.value),
2055
+ children: state.pageSizeOptions().map((size) => /* @__PURE__ */ jsx("option", { value: size, children: size }, size))
2056
+ }
2057
+ )
2058
+ ] })
2059
+ ] })
2060
+ ]
2061
+ }
2062
+ )
2063
+ ]
1942
2064
  }
1943
2065
  )
1944
- ] }, column.name)) }),
1945
- displayItems.length > 0 ? virtualizationEnabled ? /* @__PURE__ */ jsx(
1946
- "div",
1947
- {
1948
- className: "grid-viewport ui-grid-viewport",
1949
- "data-part": "viewport",
1950
- ref: virtualScroll.viewportRef,
1951
- style: { height: viewportHeightPx, overflow: "auto", position: "relative" },
1952
- onScroll: onViewportScroll,
1953
- children: /* @__PURE__ */ jsx("div", { style: { height: `${virtualScroll.totalHeight}px`, position: "relative" }, children: /* @__PURE__ */ jsx(
1954
- "div",
1955
- {
1956
- className: "body-grid ui-grid-canvas",
1957
- "data-part": "body",
1958
- role: "rowgroup",
1959
- style: {
1960
- gridTemplateColumns,
1961
- position: "absolute",
1962
- top: 0,
1963
- left: 0,
1964
- right: 0,
1965
- transform: `translateY(${virtualScroll.offsetY}px)`
1966
- },
1967
- children: itemsToRender.map(renderDisplayItem)
1968
- }
1969
- ) })
1970
- }
1971
- ) : /* @__PURE__ */ jsx(
1972
- "div",
1973
- {
1974
- className: "body-grid ui-grid-canvas",
1975
- "data-part": "body",
1976
- role: "rowgroup",
1977
- style: { gridTemplateColumns },
1978
- children: displayItems.map(renderDisplayItem)
1979
- }
1980
- ) : /* @__PURE__ */ jsxs("div", { className: "empty-state ui-grid-no-row-overlay", "data-part": "empty-state", children: [
1981
- /* @__PURE__ */ jsx("strong", { children: options.emptyMessage ?? labels.emptyHeading }),
1982
- /* @__PURE__ */ jsx("p", { children: labels.emptyDescription })
1983
- ] }),
1984
- paginationFeature && state.showPaginationControls() && /* @__PURE__ */ jsxs("footer", { className: "pagination-bar ui-grid-pagination", "data-part": "pagination", role: "navigation", "aria-label": labels.paginationPage, children: [
1985
- /* @__PURE__ */ jsx("p", { children: state.paginationSummary() }),
1986
- /* @__PURE__ */ jsxs("div", { className: "pagination-controls", children: [
1987
- /* @__PURE__ */ jsxs(
1988
- "button",
1989
- {
1990
- type: "button",
1991
- className: "action action-secondary pagination-button",
1992
- "aria-label": labels.paginationPrevious,
1993
- disabled: paginationCurrentPage <= 1,
1994
- onClick: () => state.previousPage(),
1995
- children: [
1996
- /* @__PURE__ */ jsx("svg", { className: "pagination-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) }),
1997
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationPrevious })
1998
- ]
1999
- }
2000
- ),
2001
- /* @__PURE__ */ jsxs("span", { children: [
2002
- labels.paginationPage,
2003
- " ",
2004
- paginationCurrentPage,
2005
- " ",
2006
- labels.paginationOf,
2007
- " ",
2008
- paginationTotalPages
2009
- ] }),
2010
- /* @__PURE__ */ jsxs(
2011
- "button",
2012
- {
2013
- type: "button",
2014
- className: "action action-secondary pagination-button",
2015
- "aria-label": labels.paginationNext,
2016
- disabled: paginationCurrentPage >= paginationTotalPages,
2017
- onClick: () => state.nextPage(),
2018
- children: [
2019
- /* @__PURE__ */ jsx("svg", { className: "pagination-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx("path", { d: "M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z" }) }),
2020
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationNext })
2021
- ]
2022
- }
2023
- ),
2024
- state.pageSizeOptions().length > 0 && /* @__PURE__ */ jsxs("label", { className: "pagination-size", children: [
2025
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationRows }),
2026
- /* @__PURE__ */ jsx(
2027
- "select",
2028
- {
2029
- "aria-label": labels.paginationRows,
2030
- value: paginationSelectedPageSize,
2031
- onChange: (e) => state.onPageSizeChange(e.target.value),
2032
- children: state.pageSizeOptions().map((size) => /* @__PURE__ */ jsx("option", { value: size, children: size }, size))
2033
- }
2034
- )
2035
- ] })
2036
- ] })
2037
- ] })
2038
- ] })
2039
- ] })
2066
+ ]
2067
+ }
2068
+ )
2040
2069
  ] }) });
2041
2070
  }
2042
2071
 
2072
+ // src/mountUiGrid.tsx
2073
+ import React2 from "react";
2074
+ import { createRoot } from "react-dom/client";
2075
+ function mountUiGrid(container, props) {
2076
+ const root = createRoot(container);
2077
+ root.render(React2.createElement(UiGrid, props));
2078
+ return root;
2079
+ }
2080
+
2081
+ // src/rustWasmGridEngine.ts
2082
+ import { registerRustWasmGridEngine } from "@ornery/ui-grid";
2083
+ var uiGridWasmModulePath = "../../../dist/ui-grid-wasm-web/ui_grid_wasm.js";
2084
+ var uiGridWasmBinaryPath = "/dist/ui-grid-wasm-web/ui_grid_wasm_bg.wasm";
2085
+ function registerReactUiGridWasmEngineFromModule(module) {
2086
+ registerRustWasmGridEngine({
2087
+ buildPipeline(context) {
2088
+ return module.build_pipeline_js(context);
2089
+ }
2090
+ });
2091
+ }
2092
+ async function enableReactUiGridWasmEngine() {
2093
+ const module = await import(
2094
+ /* @vite-ignore */
2095
+ uiGridWasmModulePath
2096
+ );
2097
+ await module.default(uiGridWasmBinaryPath);
2098
+ registerReactUiGridWasmEngineFromModule(module);
2099
+ }
2100
+
2043
2101
  // src/index.ts
2044
2102
  import { DEFAULT_GRID_LABELS } from "@ornery/ui-grid";
2045
2103
  export {
2046
2104
  DEFAULT_GRID_LABELS,
2047
2105
  UiGrid,
2106
+ buildGridTemplateColumns,
2107
+ computeViewportHeightPx,
2108
+ computeViewportRows,
2109
+ enableReactUiGridWasmEngine,
2110
+ formatPaginationSummary,
2111
+ mountUiGrid,
2112
+ orderVisibleColumns,
2113
+ registerReactUiGridWasmEngineFromModule,
2114
+ resolveBenchmarkIterations,
2048
2115
  useGridState,
2049
2116
  useVirtualScroll
2050
2117
  };