@ornery/ui-grid-react 0.1.4 → 0.1.5

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,39 @@
1
1
  // src/useGridState.ts
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
+
4
+ // src/gridStateMath.ts
5
+ import { gridColumnWidth } from "@ornery/ui-grid";
6
+ function orderVisibleColumns(columns, order) {
7
+ return [...columns].filter((column) => column.visible !== false).sort((left, right) => order.indexOf(left.name) - order.indexOf(right.name));
8
+ }
9
+ function buildGridTemplateColumns(columns) {
10
+ return columns.map((column) => gridColumnWidth(column)).join(" ");
11
+ }
12
+ function resolveBenchmarkIterations(iterations, configuredIterations) {
13
+ return Math.max(1, iterations ?? configuredIterations ?? 25);
14
+ }
15
+ function formatPaginationSummary(totalItems, firstRowIndex, lastRowIndex) {
16
+ if (totalItems === 0) {
17
+ return "0-0 of 0";
18
+ }
19
+ return `${firstRowIndex + 1}-${lastRowIndex + 1} of ${totalItems}`;
20
+ }
21
+ function computeViewportHeightPx(viewportHeight, autoViewportHeight) {
22
+ return `${viewportHeight ?? autoViewportHeight ?? 560}px`;
23
+ }
24
+ function computeViewportRows(viewportHeight, rowHeight) {
25
+ return Math.max(1, Math.ceil((viewportHeight ?? 560) / (rowHeight ?? 44)));
26
+ }
27
+
28
+ // src/useGridState.ts
3
29
  import {
4
30
  createGridApi,
5
31
  getCellValue as getCellValue2,
6
32
  setPathValue,
7
33
  SORT_DIRECTIONS as SORT_DIRECTIONS2,
8
- buildGridPipeline,
34
+ defaultGridEngine,
9
35
  resolveGridLabels,
10
- gridColumnWidth,
36
+ gridColumnWidth as gridColumnWidth2,
11
37
  headerLabel as coreHeaderLabel,
12
38
  gridSortButtonLabel,
13
39
  gridSortAriaSort,
@@ -59,7 +85,8 @@ import {
59
85
  FEATURE_INFINITE_SCROLL,
60
86
  FEATURE_COLUMN_MOVING,
61
87
  FEATURE_CSV_EXPORT,
62
- FEATURE_AUTO_RESIZE
88
+ FEATURE_AUTO_RESIZE,
89
+ FEATURE_PINNING
63
90
  } from "@ornery/ui-grid";
64
91
 
65
92
  // ../ui-grid/src/lib/grid/grid.core.pagination.ts
@@ -240,12 +267,15 @@ function normalizeGridSavedState(state) {
240
267
  );
241
268
  }
242
269
  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
- }, {});
270
+ normalized.filters = Object.entries(state.filters).reduce(
271
+ (accumulator, [key, value]) => {
272
+ if (typeof key === "string" && isSafeStateKey(key) && typeof value === "string") {
273
+ accumulator[key] = value;
274
+ }
275
+ return accumulator;
276
+ },
277
+ {}
278
+ );
249
279
  }
250
280
  if (state.sort && typeof state.sort === "object") {
251
281
  normalized.sort = {
@@ -272,6 +302,17 @@ function normalizeGridSavedState(state) {
272
302
  if (state.treeView && typeof state.treeView === "object") {
273
303
  normalized.treeView = normalizeBooleanMap(state.treeView);
274
304
  }
305
+ if (state.pinning && typeof state.pinning === "object") {
306
+ normalized.pinning = Object.entries(state.pinning).reduce(
307
+ (acc, [key, value]) => {
308
+ if (typeof key === "string" && isSafeStateKey(key) && (value === "left" || value === "right")) {
309
+ acc[key] = value;
310
+ }
311
+ return acc;
312
+ },
313
+ {}
314
+ );
315
+ }
275
316
  return normalized;
276
317
  }
277
318
  function normalizeBooleanMap(value) {
@@ -286,6 +327,69 @@ function isSafeStateKey(value) {
286
327
  return value !== "__proto__" && value !== "constructor" && value !== "prototype";
287
328
  }
288
329
 
330
+ // ../ui-grid/src/lib/grid/grid.core.pinning.ts
331
+ function isPinningEnabled(options) {
332
+ return options.enablePinning === true;
333
+ }
334
+ function isColumnPinnable(options, column) {
335
+ return isPinningEnabled(options) && column.enablePinning !== false;
336
+ }
337
+ function pinColumnState(current, columnName, direction) {
338
+ const next = { ...current };
339
+ if (direction === "none") {
340
+ delete next[columnName];
341
+ } else {
342
+ next[columnName] = direction;
343
+ }
344
+ return next;
345
+ }
346
+ function buildInitialPinnedState(columns) {
347
+ const state = {};
348
+ for (const col of columns) {
349
+ if (col.pinnedLeft) state[col.name] = "left";
350
+ else if (col.pinnedRight) state[col.name] = "right";
351
+ }
352
+ return state;
353
+ }
354
+ function computePinnedOffset(visibleColumns, pinnedColumns, column) {
355
+ const direction = pinnedColumns[column.name];
356
+ if (!direction) return null;
357
+ function resolveColumnWidthForOffset(column2) {
358
+ const w = column2.width;
359
+ if (!w) return "11rem";
360
+ if (w.includes("fr") || w.includes("minmax")) return "11rem";
361
+ return w;
362
+ }
363
+ if (direction === "left") {
364
+ const offsetParts = [];
365
+ for (const col of visibleColumns) {
366
+ if (col.name === column.name) break;
367
+ if (pinnedColumns[col.name] === "left") {
368
+ offsetParts.push(resolveColumnWidthForOffset(col));
369
+ }
370
+ }
371
+ return {
372
+ side: "left",
373
+ offset: offsetParts.length > 0 ? `calc(${offsetParts.join(" + ")})` : "0px"
374
+ };
375
+ }
376
+ if (direction === "right") {
377
+ const offsetParts = [];
378
+ const reversed = [...visibleColumns].reverse();
379
+ for (const col of reversed) {
380
+ if (col.name === column.name) break;
381
+ if (pinnedColumns[col.name] === "right") {
382
+ offsetParts.push(resolveColumnWidthForOffset(col));
383
+ }
384
+ }
385
+ return {
386
+ side: "right",
387
+ offset: offsetParts.length > 0 ? `calc(${offsetParts.join(" + ")})` : "0px"
388
+ };
389
+ }
390
+ return null;
391
+ }
392
+
289
393
  // ../ui-grid/src/lib/grid/ui-grid.events.ts
290
394
  function raiseGridRenderingComplete(gridApi) {
291
395
  gridApi.core.raise.renderingComplete(gridApi);
@@ -352,6 +456,9 @@ function raiseGridAfterCellEdit(gridApi, rowEntity, column, newValue, oldValue)
352
456
  function raiseGridCancelCellEdit(gridApi, rowEntity, column) {
353
457
  gridApi.edit.raise.cancelCellEdit(rowEntity, column);
354
458
  }
459
+ function raiseGridColumnPinned(gridApi, columnName, direction) {
460
+ gridApi.pinning.raise.columnPinned(columnName, direction);
461
+ }
355
462
 
356
463
  // ../ui-grid/src/lib/grid/ui-grid.state.ts
357
464
  function moveArrayItem(items, fromIndex, toIndex) {
@@ -393,6 +500,9 @@ function createGridRestoreMutationPlan(state) {
393
500
  if (normalizedState.treeView) {
394
501
  plan.treeView = normalizedState.treeView;
395
502
  }
503
+ if (normalizedState.pinning) {
504
+ plan.pinning = normalizedState.pinning;
505
+ }
396
506
  return plan;
397
507
  }
398
508
 
@@ -433,6 +543,12 @@ function moveGridColumnCommand(gridApi, canMoveColumns, updateColumnOrder, fromI
433
543
  return next;
434
544
  });
435
545
  }
546
+ function pinGridColumnCommand(gridApi, isPinningEnabled2, setPinnedColumns, getCurrentPinnedColumns, columnName, direction) {
547
+ if (!isPinningEnabled2) return;
548
+ const next = pinColumnState(getCurrentPinnedColumns(), columnName, direction);
549
+ setPinnedColumns(next);
550
+ raiseGridColumnPinned(gridApi, columnName, direction);
551
+ }
436
552
  function seekGridPaginationCommand(gridApi, setCurrentPage, getTotalPages, getEffectivePageSize, page) {
437
553
  const nextPage = seekGridPage(page, getTotalPages());
438
554
  setCurrentPage(nextPage);
@@ -466,7 +582,11 @@ function restoreGridStateCommand(gridApi, state, access) {
466
582
  if (restorePlan.pagination) {
467
583
  access.setCurrentPage(restorePlan.pagination.currentPage);
468
584
  access.setPageSize(restorePlan.pagination.pageSize);
469
- raiseGridPaginationChanged(gridApi, restorePlan.pagination.currentPage, access.getEffectivePageSize());
585
+ raiseGridPaginationChanged(
586
+ gridApi,
587
+ restorePlan.pagination.currentPage,
588
+ access.getEffectivePageSize()
589
+ );
470
590
  }
471
591
  if (restorePlan.expandable) {
472
592
  access.setExpandedRows(restorePlan.expandable);
@@ -474,6 +594,12 @@ function restoreGridStateCommand(gridApi, state, access) {
474
594
  if (restorePlan.treeView) {
475
595
  access.setExpandedTreeRows(restorePlan.treeView);
476
596
  }
597
+ if (restorePlan.pinning && typeof access.setPinnedColumns === "function") {
598
+ access.setPinnedColumns(restorePlan.pinning);
599
+ for (const [col, dir] of Object.entries(restorePlan.pinning)) {
600
+ raiseGridColumnPinned(gridApi, col, dir);
601
+ }
602
+ }
477
603
  }
478
604
  function toggleGridRowExpansionCommand(gridApi, canExpandRows, currentExpandedRows, rowId, setExpandedRows, findRowById) {
479
605
  if (!canExpandRows) {
@@ -495,7 +621,10 @@ function collapseAllGridRowsCommand(setExpandedRows) {
495
621
  setExpandedRows({});
496
622
  }
497
623
  function toggleGridTreeRowCommand(gridApi, currentExpandedTreeRows, rowId, setExpandedTreeRows, findRowById) {
498
- const { expanded, nextExpandedTreeRows } = toggleGridTreeRowExpanded(currentExpandedTreeRows, rowId);
624
+ const { expanded, nextExpandedTreeRows } = toggleGridTreeRowExpanded(
625
+ currentExpandedTreeRows,
626
+ rowId
627
+ );
499
628
  setExpandedTreeRows(nextExpandedTreeRows);
500
629
  const gridRow = findRowById(rowId);
501
630
  if (gridRow) {
@@ -630,13 +759,45 @@ function downloadGridCsvFile(csv, filename) {
630
759
  }
631
760
 
632
761
  // src/useGridState.ts
762
+ function escapeCssSelectorValue(value) {
763
+ const nativeEscape = globalThis.CSS?.escape;
764
+ if (typeof nativeEscape === "function") {
765
+ return nativeEscape(value);
766
+ }
767
+ let output = "";
768
+ for (let index = 0; index < value.length; index += 1) {
769
+ const codePoint = value.charCodeAt(index);
770
+ const character = value.charAt(index);
771
+ if (codePoint === 0) {
772
+ output += "\uFFFD";
773
+ continue;
774
+ }
775
+ const isControlCharacter = codePoint >= 1 && codePoint <= 31 || codePoint === 127;
776
+ const startsWithDigit = index === 0 && codePoint >= 48 && codePoint <= 57;
777
+ const secondCharDigitAfterHyphen = index === 1 && codePoint >= 48 && codePoint <= 57 && value.charCodeAt(0) === 45;
778
+ if (isControlCharacter || startsWithDigit || secondCharDigitAfterHyphen) {
779
+ output += `\\${codePoint.toString(16)} `;
780
+ continue;
781
+ }
782
+ if (index === 0 && value.length === 1 && codePoint === 45) {
783
+ output += `\\${character}`;
784
+ continue;
785
+ }
786
+ const isSafeCharacter = codePoint >= 128 || codePoint === 45 || codePoint === 95 || codePoint >= 48 && codePoint <= 57 || codePoint >= 65 && codePoint <= 90 || codePoint >= 97 && codePoint <= 122;
787
+ output += isSafeCharacter ? character : `\\${character}`;
788
+ }
789
+ return output;
790
+ }
633
791
  function useGridState(options, onRegisterApi) {
634
792
  const [activeFilters, setActiveFilters] = useState({});
635
793
  const [groupByColumns, setGroupByColumns] = useState([]);
636
794
  const [collapsedGroups, setCollapsedGroups] = useState({});
637
795
  const [columnOrder, setColumnOrder] = useState([]);
638
796
  const [hiddenRowReasons, setHiddenRowReasons] = useState({});
639
- const [sortState, setSortState] = useState({ columnName: null, direction: SORT_DIRECTIONS2.none });
797
+ const [sortState, setSortState] = useState({
798
+ columnName: null,
799
+ direction: SORT_DIRECTIONS2.none
800
+ });
640
801
  const [focusedCell, setFocusedCell] = useState(null);
641
802
  const [editingCell, setEditingCell] = useState(null);
642
803
  const [editingValue, setEditingValue] = useState("");
@@ -652,6 +813,7 @@ function useGridState(options, onRegisterApi) {
652
813
  previousVisibleRows: 0
653
814
  });
654
815
  const [autoViewportHeight, setAutoViewportHeight] = useState(null);
816
+ const [pinnedColumns, setPinnedColumns] = useState({});
655
817
  const gridContainerRef = useRef(null);
656
818
  const initializedGridIdRef = useRef(null);
657
819
  const lastCanvasHeightRef = useRef(0);
@@ -683,6 +845,8 @@ function useGridState(options, onRegisterApi) {
683
845
  expandedRowsRef.current = expandedRows;
684
846
  const expandedTreeRowsRef = useRef(expandedTreeRows);
685
847
  expandedTreeRowsRef.current = expandedTreeRows;
848
+ const pinnedColumnsRef = useRef(pinnedColumns);
849
+ pinnedColumnsRef.current = pinnedColumns;
686
850
  const currentPageRef = useRef(currentPage);
687
851
  currentPageRef.current = currentPage;
688
852
  const pageSizeRef = useRef(pageSize);
@@ -693,13 +857,12 @@ function useGridState(options, onRegisterApi) {
693
857
  optionsRef.current = options;
694
858
  const rowSize = options.rowHeight ?? 44;
695
859
  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));
860
+ return orderVisibleColumns(options.columnDefs, columnOrder);
698
861
  }, [options.columnDefs, columnOrder]);
699
862
  const visibleColumnsRef = useRef(visibleColumns);
700
863
  visibleColumnsRef.current = visibleColumns;
701
864
  const pipeline = useMemo(() => {
702
- return buildGridPipeline({
865
+ return defaultGridEngine.buildPipeline({
703
866
  options,
704
867
  columns: visibleColumns,
705
868
  activeFilters,
@@ -713,14 +876,39 @@ function useGridState(options, onRegisterApi) {
713
876
  pageSize,
714
877
  rowSize
715
878
  });
716
- }, [options, visibleColumns, activeFilters, sortState, groupByColumns, collapsedGroups, hiddenRowReasons, expandedRows, expandedTreeRows, currentPage, pageSize, rowSize]);
879
+ }, [
880
+ options,
881
+ visibleColumns,
882
+ activeFilters,
883
+ sortState,
884
+ groupByColumns,
885
+ collapsedGroups,
886
+ hiddenRowReasons,
887
+ expandedRows,
888
+ expandedTreeRows,
889
+ currentPage,
890
+ pageSize,
891
+ rowSize
892
+ ]);
717
893
  const pipelineRef = useRef(pipeline);
718
894
  pipelineRef.current = pipeline;
719
895
  const labels = useMemo(() => resolveGridLabels(options.labels), [options.labels]);
720
896
  const gridTemplateColumns = useMemo(
721
- () => visibleColumns.map((column) => gridColumnWidth(column)).join(" "),
897
+ () => buildGridTemplateColumns(visibleColumns),
722
898
  [visibleColumns]
723
899
  );
900
+ const isPinningEnabledFn = useCallback(() => {
901
+ return isPinningEnabled(optionsRef.current);
902
+ }, []);
903
+ const isColumnPinnableFn = useCallback((column) => {
904
+ return isColumnPinnable(optionsRef.current, column);
905
+ }, []);
906
+ const isPinnedFn = useCallback((column) => {
907
+ return pinnedColumnsRef.current[column.name] !== void 0;
908
+ }, []);
909
+ const pinnedOffsetFn = useCallback((column) => {
910
+ return computePinnedOffset(visibleColumnsRef.current, pinnedColumnsRef.current, column);
911
+ }, []);
724
912
  const resolveRowId = useCallback((row) => {
725
913
  return coreResolveGridRowId(optionsRef.current, row);
726
914
  }, []);
@@ -732,9 +920,12 @@ function useGridState(options, onRegisterApi) {
732
920
  expandedRowsRef.current
733
921
  );
734
922
  }, []);
735
- const findRowById = useCallback((rowId) => {
736
- return coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId);
737
- }, [buildRowsFromData]);
923
+ const findRowById = useCallback(
924
+ (rowId) => {
925
+ return coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId);
926
+ },
927
+ [buildRowsFromData]
928
+ );
738
929
  const canExpandRowsFn = useCallback(() => {
739
930
  return FEATURE_EXPANDABLE && canGridExpandRows(optionsRef.current);
740
931
  }, []);
@@ -743,7 +934,12 @@ function useGridState(options, onRegisterApi) {
743
934
  }, []);
744
935
  const getCurrentPageValueFn = useCallback((totalItems) => {
745
936
  const ti = totalItems ?? pipelineRef.current.totalItems;
746
- return coreGetCurrentPageValue(optionsRef.current, currentPageRef.current, ti, pageSizeRef.current);
937
+ return coreGetCurrentPageValue(
938
+ optionsRef.current,
939
+ currentPageRef.current,
940
+ ti,
941
+ pageSizeRef.current
942
+ );
747
943
  }, []);
748
944
  const getTotalPagesValueFn = useCallback((totalItems) => {
749
945
  const ti = totalItems ?? pipelineRef.current.totalItems;
@@ -751,32 +947,45 @@ function useGridState(options, onRegisterApi) {
751
947
  }, []);
752
948
  const getFirstRowIndexValueFn = useCallback((totalItems) => {
753
949
  const ti = totalItems ?? pipelineRef.current.totalItems;
754
- return coreGetFirstRowIndexValue(optionsRef.current, currentPageRef.current, ti, pageSizeRef.current);
950
+ return coreGetFirstRowIndexValue(
951
+ optionsRef.current,
952
+ currentPageRef.current,
953
+ ti,
954
+ pageSizeRef.current
955
+ );
755
956
  }, []);
756
957
  const getLastRowIndexValueFn = useCallback((totalItems) => {
757
958
  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);
959
+ return coreGetLastRowIndexValue(
960
+ optionsRef.current,
961
+ currentPageRef.current,
962
+ ti,
963
+ pageSizeRef.current
964
+ );
773
965
  }, []);
966
+ const isCellEditable = useCallback(
967
+ (row, column, triggerEvent) => {
968
+ if (!FEATURE_CELL_EDIT) return false;
969
+ const editable = column.enableCellEdit ?? optionsRef.current.enableCellEdit ?? false;
970
+ if (!editable) return false;
971
+ const condition = column.cellEditableCondition ?? optionsRef.current.cellEditableCondition ?? true;
972
+ if (typeof condition === "boolean") return condition;
973
+ const context = {
974
+ row: row.entity,
975
+ column,
976
+ rowIndex: row.index,
977
+ triggerEvent
978
+ };
979
+ return condition(context);
980
+ },
981
+ []
982
+ );
774
983
  const shouldEditOnFocusFn = useCallback((column) => {
775
984
  return column.enableCellEditOnFocus ?? optionsRef.current.enableCellEditOnFocus ?? false;
776
985
  }, []);
777
986
  const focusRenderedCell = useCallback((position) => {
778
987
  const focusToken = ++renderedCellFocusTokenRef.current;
779
- const selector = `.body-cell[data-row-id="${position.rowId}"][data-col-name="${position.columnName}"]`;
988
+ const selector = `.body-cell[data-row-id="${escapeCssSelectorValue(position.rowId)}"][data-col-name="${escapeCssSelectorValue(position.columnName)}"]`;
780
989
  const doFocus = (retry = true) => {
781
990
  if (focusToken !== renderedCellFocusTokenRef.current) return;
782
991
  const container = gridContainerRef.current;
@@ -797,11 +1006,12 @@ function useGridState(options, onRegisterApi) {
797
1006
  if (focusToken !== editorFocusTokenRef.current) return;
798
1007
  const ec = editingCellRef.current;
799
1008
  if (!ec) return;
800
- const selector = `.cell-editor[data-row-id="${ec.rowId}"][data-col-name="${ec.columnName}"]`;
1009
+ const selector = `.cell-editor[data-row-id="${escapeCssSelectorValue(ec.rowId)}"][data-col-name="${escapeCssSelectorValue(ec.columnName)}"]`;
801
1010
  const doFocus = (retry = true) => {
802
1011
  if (focusToken !== editorFocusTokenRef.current) return;
803
1012
  const currentEc = editingCellRef.current;
804
- if (!currentEc || currentEc.rowId !== ec.rowId || currentEc.columnName !== ec.columnName) return;
1013
+ if (!currentEc || currentEc.rowId !== ec.rowId || currentEc.columnName !== ec.columnName)
1014
+ return;
805
1015
  const container = gridContainerRef.current;
806
1016
  if (!container) return;
807
1017
  const input = container.querySelector(selector);
@@ -862,7 +1072,11 @@ function useGridState(options, onRegisterApi) {
862
1072
  gridApiRef.current.core.raise.groupingChanged(next);
863
1073
  },
864
1074
  clearGrouping: () => {
865
- clearGridGroupingCommand(gridApiRef.current, (grouping) => setGroupByColumns(grouping), false);
1075
+ clearGridGroupingCommand(
1076
+ gridApiRef.current,
1077
+ (grouping) => setGroupByColumns(grouping),
1078
+ false
1079
+ );
866
1080
  },
867
1081
  benchmark: (iterations) => {
868
1082
  return runBenchmarkFn(iterations);
@@ -957,7 +1171,8 @@ function useGridState(options, onRegisterApi) {
957
1171
  pageSize: pageSizeRef.current,
958
1172
  totalItems: pipelineRef.current.totalItems,
959
1173
  expandedRows: expandedRowsRef.current,
960
- expandedTreeRows: expandedTreeRowsRef.current
1174
+ expandedTreeRows: expandedTreeRowsRef.current,
1175
+ pinnedColumns: pinnedColumnsRef.current
961
1176
  });
962
1177
  },
963
1178
  restoreState: (state) => {
@@ -970,6 +1185,7 @@ function useGridState(options, onRegisterApi) {
970
1185
  setPageSize: (ps) => setPageSize(ps),
971
1186
  setExpandedRows: (e) => setExpandedRows(e),
972
1187
  setExpandedTreeRows: (e) => setExpandedTreeRows(e),
1188
+ setPinnedColumns: (p) => setPinnedColumns(p),
973
1189
  getEffectivePageSize: () => effectivePageSizeFn(pipelineRef.current.totalItems)
974
1190
  });
975
1191
  },
@@ -982,20 +1198,45 @@ function useGridState(options, onRegisterApi) {
982
1198
  },
983
1199
  endCellEdit: () => commitCellEditFn(),
984
1200
  cancelCellEdit: () => cancelCellEditFn(),
985
- getEditingCell: () => editingCellRef.current
1201
+ getEditingCell: () => editingCellRef.current,
1202
+ pinColumn: (columnName, direction) => {
1203
+ pinGridColumnCommand(
1204
+ gridApiRef.current,
1205
+ isPinningEnabledFn(),
1206
+ (v) => setPinnedColumns(v),
1207
+ () => pinnedColumnsRef.current,
1208
+ columnName,
1209
+ direction
1210
+ );
1211
+ }
986
1212
  };
987
1213
  gridApiRef.current = createGridApi(bindings);
988
1214
  }
989
1215
  const gridApi = gridApiRef.current;
990
- const seekPageFn = useCallback((page) => {
991
- seekGridPaginationCommand(
1216
+ const seekPageFn = useCallback(
1217
+ (page) => {
1218
+ seekGridPaginationCommand(
1219
+ gridApiRef.current,
1220
+ (nextPage) => setCurrentPage(nextPage),
1221
+ () => getTotalPagesValueFn(),
1222
+ () => effectivePageSizeFn(pipelineRef.current.totalItems),
1223
+ page
1224
+ );
1225
+ },
1226
+ [getTotalPagesValueFn, effectivePageSizeFn]
1227
+ );
1228
+ const togglePinFn = useCallback((column) => {
1229
+ const current = pinnedColumnsRef.current[column.name];
1230
+ const next = current === "left" ? "right" : current === "right" ? "none" : "left";
1231
+ pinGridColumnCommand(
992
1232
  gridApiRef.current,
993
- (nextPage) => setCurrentPage(nextPage),
994
- () => getTotalPagesValueFn(),
995
- () => effectivePageSizeFn(pipelineRef.current.totalItems),
996
- page
1233
+ isPinningEnabledFn(),
1234
+ (v) => setPinnedColumns(v),
1235
+ () => pinnedColumnsRef.current,
1236
+ column.name,
1237
+ next
997
1238
  );
998
- }, [getTotalPagesValueFn, effectivePageSizeFn]);
1239
+ }, []);
999
1240
  const setPaginationPageSizeFn = useCallback((ps) => {
1000
1241
  setGridPaginationPageSizeCommand(
1001
1242
  gridApiRef.current,
@@ -1004,17 +1245,20 @@ function useGridState(options, onRegisterApi) {
1004
1245
  ps
1005
1246
  );
1006
1247
  }, []);
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]);
1248
+ const toggleRowExpansionByRefFn = useCallback(
1249
+ (row) => {
1250
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
1251
+ toggleGridRowExpansionCommand(
1252
+ gridApiRef.current,
1253
+ FEATURE_EXPANDABLE && canGridExpandRows(optionsRef.current),
1254
+ expandedRowsRef.current,
1255
+ rowId,
1256
+ (e) => setExpandedRows(e),
1257
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1258
+ );
1259
+ },
1260
+ [buildRowsFromData]
1261
+ );
1018
1262
  const expandAllRowsFn = useCallback(() => {
1019
1263
  if (!canGridExpandRows(optionsRef.current)) return;
1020
1264
  expandAllGridRowsCommand(
@@ -1024,88 +1268,106 @@ function useGridState(options, onRegisterApi) {
1024
1268
  );
1025
1269
  }, [buildRowsFromData]);
1026
1270
  const toggleAllRowsFn = useCallback(() => {
1027
- const allExpanded = areAllGridRowsExpanded(buildRowsFromData(optionsRef.current.data), expandedRowsRef.current);
1271
+ const allExpanded = areAllGridRowsExpanded(
1272
+ buildRowsFromData(optionsRef.current.data),
1273
+ expandedRowsRef.current
1274
+ );
1028
1275
  if (allExpanded) {
1029
1276
  collapseAllGridRowsCommand((e) => setExpandedRows(e));
1030
1277
  } else {
1031
1278
  expandAllRowsFn();
1032
1279
  }
1033
1280
  }, [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);
1281
+ const toggleTreeRowByRefFn = useCallback(
1282
+ (row) => {
1283
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
1284
+ toggleGridTreeRowCommand(
1285
+ gridApiRef.current,
1286
+ expandedTreeRowsRef.current,
1287
+ rowId,
1288
+ (e) => setExpandedTreeRows(e),
1289
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1290
+ );
1291
+ },
1292
+ [buildRowsFromData]
1293
+ );
1294
+ const expandTreeRowByRefFn = useCallback(
1295
+ (row) => {
1296
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
1297
+ setGridTreeRowExpandedCommand(
1298
+ gridApiRef.current,
1299
+ expandedTreeRowsRef.current,
1300
+ rowId,
1301
+ true,
1302
+ (e) => setExpandedTreeRows(e),
1303
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1304
+ );
1305
+ },
1306
+ [buildRowsFromData]
1307
+ );
1308
+ const collapseTreeRowByRefFn = useCallback(
1309
+ (row) => {
1310
+ const rowId = coreResolveGridRowId(optionsRef.current, row);
1311
+ setGridTreeRowExpandedCommand(
1312
+ gridApiRef.current,
1313
+ expandedTreeRowsRef.current,
1314
+ rowId,
1315
+ false,
1316
+ (e) => setExpandedTreeRows(e),
1317
+ (resolvedRowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), resolvedRowId)
1318
+ );
1319
+ },
1320
+ [buildRowsFromData]
1321
+ );
1322
+ const startCellEditFn = useCallback(
1323
+ (row, column, triggerEvent, initialValue) => {
1324
+ const currentValue = getCellValue2(row.entity, column);
1325
+ const focusToken = ++editorFocusTokenRef.current;
1326
+ const ec = beginGridCellEditCommand(
1327
+ gridApiRef.current,
1328
+ {
1329
+ setFocusedCell: (fc) => setFocusedCell(fc),
1330
+ setEditingCell: (ec2) => setEditingCell(ec2),
1331
+ setEditingValue: (ev) => setEditingValue(ev)
1332
+ },
1333
+ row,
1334
+ column,
1335
+ currentValue,
1336
+ triggerEvent,
1337
+ initialValue
1338
+ );
1339
+ if (ec) {
1340
+ queueMicrotask(() => focusEditorInput(focusToken));
1098
1341
  }
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]);
1342
+ },
1343
+ [focusEditorInput]
1344
+ );
1345
+ const commitCellEditFn = useCallback(
1346
+ (direction, restoreFocus = true) => {
1347
+ const result = commitGridCellEditCommand(gridApiRef.current, {
1348
+ getEditingCell: () => editingCellRef.current,
1349
+ getEditingValue: () => editingValueRef.current,
1350
+ setEditingCell: (ec) => setEditingCell(ec),
1351
+ setEditingValue: (ev) => setEditingValue(ev),
1352
+ findRowById: (rowId) => coreFindGridRowById(buildRowsFromData(optionsRef.current.data), rowId),
1353
+ findColumnByName: (columnName) => visibleColumnsRef.current.find((c) => c.name === columnName),
1354
+ parseEditedValue: (column, value, oldValue) => parseGridEditedValue(column, value, oldValue),
1355
+ setCellValue: (rowEntity, column, value) => {
1356
+ const fieldPath = column.editModelField ?? column.field ?? column.name;
1357
+ setPathValue(rowEntity, fieldPath, value);
1358
+ }
1359
+ });
1360
+ if (!result.committed || !result.row || !result.column || !result.focusTarget) return;
1361
+ editorFocusTokenRef.current += 1;
1362
+ if (direction) {
1363
+ const moved = moveFocusFn(result.row, result.column, direction);
1364
+ if (!moved) focusRenderedCell(result.focusTarget);
1365
+ } else if (restoreFocus) {
1366
+ focusRenderedCell(result.focusTarget);
1367
+ }
1368
+ },
1369
+ [buildRowsFromData, focusRenderedCell]
1370
+ );
1109
1371
  const cancelCellEditFn = useCallback(() => {
1110
1372
  const hadEditingCell = editingCellRef.current !== null;
1111
1373
  const result = cancelGridCellEditCommand(gridApiRef.current, {
@@ -1119,26 +1381,32 @@ function useGridState(options, onRegisterApi) {
1119
1381
  editorFocusTokenRef.current += 1;
1120
1382
  if (result.focusTarget) focusRenderedCell(result.focusTarget);
1121
1383
  }, [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]);
1384
+ const moveFocusFn = useCallback(
1385
+ (row, column, direction, triggerEvent) => {
1386
+ const nextCell = findNextGridCell({
1387
+ rows: pipelineRef.current.visibleRows,
1388
+ columns: visibleColumnsRef.current,
1389
+ rowId: row.id,
1390
+ columnName: column.name,
1391
+ direction
1392
+ });
1393
+ if (!nextCell) return false;
1394
+ setFocusedCell({ rowId: nextCell.row.id, columnName: nextCell.column.name });
1395
+ focusRenderedCell({ rowId: nextCell.row.id, columnName: nextCell.column.name });
1396
+ if (shouldEditOnFocusFn(nextCell.column) && isCellEditable(nextCell.row, nextCell.column, triggerEvent)) {
1397
+ startCellEditFn(nextCell.row, nextCell.column, triggerEvent);
1398
+ }
1399
+ return true;
1400
+ },
1401
+ [focusRenderedCell, isCellEditable, shouldEditOnFocusFn, startCellEditFn]
1402
+ );
1138
1403
  const runBenchmarkFn = useCallback((iterations) => {
1139
- const safeIterations = Math.max(1, iterations ?? optionsRef.current.benchmark?.iterations ?? 25);
1404
+ const safeIterations = resolveBenchmarkIterations(
1405
+ iterations,
1406
+ optionsRef.current.benchmark?.iterations
1407
+ );
1140
1408
  const startedAt = performance.now();
1141
- let lastResult = buildGridPipeline({
1409
+ let lastResult = defaultGridEngine.buildPipeline({
1142
1410
  options: optionsRef.current,
1143
1411
  columns: visibleColumnsRef.current,
1144
1412
  activeFilters: activeFiltersRef.current,
@@ -1153,7 +1421,7 @@ function useGridState(options, onRegisterApi) {
1153
1421
  rowSize: optionsRef.current.rowHeight ?? 44
1154
1422
  });
1155
1423
  for (let i = 1; i < safeIterations; i++) {
1156
- lastResult = buildGridPipeline({
1424
+ lastResult = defaultGridEngine.buildPipeline({
1157
1425
  options: optionsRef.current,
1158
1426
  columns: visibleColumnsRef.current,
1159
1427
  activeFilters: activeFiltersRef.current,
@@ -1199,6 +1467,7 @@ function useGridState(options, onRegisterApi) {
1199
1467
  setExpandedTreeRows({});
1200
1468
  setColumnOrder(options.columnDefs.map((column) => column.name));
1201
1469
  setGroupByColumns(options.grouping?.groupBy ?? []);
1470
+ setPinnedColumns(buildInitialPinnedState(options.columnDefs));
1202
1471
  setCurrentPage(options.paginationCurrentPage ?? 1);
1203
1472
  setPageSize(coreGetEffectivePageSize(options, 0, options.data.length));
1204
1473
  setInfiniteScrollState({
@@ -1231,8 +1500,15 @@ function useGridState(options, onRegisterApi) {
1231
1500
  const container = gridContainerRef.current;
1232
1501
  if (!container) return;
1233
1502
  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);
1503
+ if (nextHeight === lastGridHeightRef.current && nextWidth === lastGridWidthRef.current)
1504
+ return;
1505
+ raiseGridDimensionChanged(
1506
+ gridApi,
1507
+ lastGridHeightRef.current,
1508
+ lastGridWidthRef.current,
1509
+ nextHeight,
1510
+ nextWidth
1511
+ );
1236
1512
  lastGridHeightRef.current = nextHeight;
1237
1513
  lastGridWidthRef.current = nextWidth;
1238
1514
  if (!options.viewportHeight && nextHeight > 0) {
@@ -1250,42 +1526,75 @@ function useGridState(options, onRegisterApi) {
1250
1526
  const paginationCurrentPage = getCurrentPageValueFn();
1251
1527
  const paginationTotalPages = getTotalPagesValueFn();
1252
1528
  const paginationSelectedPageSize = effectivePageSizeFn(pipeline.totalItems);
1253
- const viewportHeightPx = `${options.viewportHeight ?? autoViewportHeight ?? 560}px`;
1529
+ const viewportHeightPx = computeViewportHeightPx(options.viewportHeight, autoViewportHeight);
1254
1530
  const headerLabelFn = useCallback((column) => coreHeaderLabel(column), []);
1255
- const isGroupItemFn = useCallback((item) => item.kind === "group", []);
1256
- const isExpandableItemFn = useCallback((item) => item.kind === "expandable", []);
1531
+ const isGroupItemFn = useCallback(
1532
+ (item) => item.kind === "group",
1533
+ []
1534
+ );
1535
+ const isExpandableItemFn = useCallback(
1536
+ (item) => item.kind === "expandable",
1537
+ []
1538
+ );
1257
1539
  const isRowItemFn = useCallback((item) => item.kind === "row", []);
1258
- const isOddStripedRowFn = useCallback((item) => item.kind === "row" && item.visibleIndex % 2 === 0, []);
1540
+ const isOddStripedRowFn = useCallback(
1541
+ (item) => item.kind === "row" && item.visibleIndex % 2 === 0,
1542
+ []
1543
+ );
1259
1544
  const sortDirectionFn = useCallback((column) => {
1260
1545
  return sortStateRef.current.columnName === column.name ? sortStateRef.current.direction : SORT_DIRECTIONS2.none;
1261
1546
  }, []);
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]);
1547
+ const sortButtonLabelFn = useCallback(
1548
+ (column) => {
1549
+ return gridSortButtonLabel(sortDirectionFn(column), labels);
1550
+ },
1551
+ [labels, sortDirectionFn]
1552
+ );
1553
+ const sortAriaSortFn = useCallback(
1554
+ (column) => {
1555
+ return gridSortAriaSort(sortDirectionFn(column));
1556
+ },
1557
+ [sortDirectionFn]
1558
+ );
1559
+ const groupingButtonLabelFn = useCallback(
1560
+ (column) => {
1561
+ return gridGroupingButtonLabel(
1562
+ isGridColumnGrouped(groupByColumnsRef.current, column),
1563
+ labels
1564
+ );
1565
+ },
1566
+ [labels]
1567
+ );
1271
1568
  const filterValueFn = useCallback((columnName) => {
1272
1569
  return activeFiltersRef.current[columnName] ?? "";
1273
1570
  }, []);
1274
- const filterPlaceholderFn = useCallback((column) => {
1275
- return gridFilterPlaceholder(isGridColumnFilterable(optionsRef.current, column), labels);
1276
- }, [labels]);
1571
+ const filterPlaceholderFn = useCallback(
1572
+ (column) => {
1573
+ return gridFilterPlaceholder(isGridColumnFilterable(optionsRef.current, column), labels);
1574
+ },
1575
+ [labels]
1576
+ );
1277
1577
  const isFilterInputDisabledFn = useCallback((column) => {
1278
1578
  return !isGridColumnFilterable(optionsRef.current, column);
1279
1579
  }, []);
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]);
1580
+ const groupDisclosureLabelFn = useCallback(
1581
+ (item) => {
1582
+ return gridGroupDisclosureLabel(item.collapsed, labels);
1583
+ },
1584
+ [labels]
1585
+ );
1586
+ const cellContextFn = useCallback(
1587
+ (row, column) => {
1588
+ return buildGridCellContext(row, column);
1589
+ },
1590
+ []
1591
+ );
1592
+ const displayValueFn = useCallback(
1593
+ (row, column) => {
1594
+ return formatGridCellDisplayValue(cellContextFn(row, column));
1595
+ },
1596
+ [cellContextFn]
1597
+ );
1289
1598
  const isFocusedCellFn = useCallback((row, column) => {
1290
1599
  return isGridCellPosition(focusedCellRef.current, row.id, column.name);
1291
1600
  }, []);
@@ -1295,16 +1604,19 @@ function useGridState(options, onRegisterApi) {
1295
1604
  const editorInputTypeFn = useCallback((column) => {
1296
1605
  return gridEditorInputType(column);
1297
1606
  }, []);
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), []);
1607
+ const expandedContextFn = useCallback(
1608
+ (row) => {
1609
+ return {
1610
+ $implicit: row.entity,
1611
+ row: row.entity,
1612
+ rowIndex: row.index,
1613
+ expanded: true,
1614
+ ...optionsRef.current.expandableRowScope ?? {}
1615
+ };
1616
+ },
1617
+ []
1618
+ );
1619
+ const columnWidthFn = useCallback((column) => gridColumnWidth2(column), []);
1308
1620
  const isColumnSortableFn = useCallback((column) => {
1309
1621
  return isGridColumnSortable(optionsRef.current, column);
1310
1622
  }, []);
@@ -1314,15 +1626,21 @@ function useGridState(options, onRegisterApi) {
1314
1626
  const cellIndentFn = useCallback((row, column) => {
1315
1627
  return gridCellIndent(optionsRef.current, visibleColumnsRef.current, row, column);
1316
1628
  }, []);
1317
- const treeToggleLabelFn = useCallback((row) => {
1318
- return gridTreeToggleLabelForRow(expandedTreeRowsRef.current, row, labels);
1319
- }, [labels]);
1629
+ const treeToggleLabelFn = useCallback(
1630
+ (row) => {
1631
+ return gridTreeToggleLabelForRow(expandedTreeRowsRef.current, row, labels);
1632
+ },
1633
+ [labels]
1634
+ );
1320
1635
  const isTreeRowExpandedFn = useCallback((row) => {
1321
1636
  return isGridTreeRowExpanded(expandedTreeRowsRef.current, row);
1322
1637
  }, []);
1323
- const expandToggleLabelFn = useCallback((row) => {
1324
- return gridExpandToggleLabelForRow(row, labels);
1325
- }, [labels]);
1638
+ const expandToggleLabelFn = useCallback(
1639
+ (row) => {
1640
+ return gridExpandToggleLabelForRow(row, labels);
1641
+ },
1642
+ [labels]
1643
+ );
1326
1644
  const isGroupedFn = useCallback((column) => {
1327
1645
  return isGridColumnGrouped(groupByColumnsRef.current, column);
1328
1646
  }, []);
@@ -1337,8 +1655,7 @@ function useGridState(options, onRegisterApi) {
1337
1655
  }, []);
1338
1656
  const paginationSummaryFn = useCallback(() => {
1339
1657
  const ti = pipelineRef.current.totalItems;
1340
- if (ti === 0) return "0-0 of 0";
1341
- return `${getFirstRowIndexValueFn(ti) + 1}-${getLastRowIndexValueFn(ti) + 1} of ${ti}`;
1658
+ return formatPaginationSummary(ti, getFirstRowIndexValueFn(ti), getLastRowIndexValueFn(ti));
1342
1659
  }, [getFirstRowIndexValueFn, getLastRowIndexValueFn]);
1343
1660
  const pageSizeOptionsFn = useCallback(() => {
1344
1661
  return optionsRef.current.paginationPageSizes ?? [];
@@ -1385,108 +1702,130 @@ function useGridState(options, onRegisterApi) {
1385
1702
  [item.id]: !current[item.id]
1386
1703
  }));
1387
1704
  }, []);
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":
1705
+ const focusCellFn = useCallback(
1706
+ (row, column, triggerEvent) => {
1707
+ const nextFocusResult = buildGridFocusCellResult({
1708
+ currentFocusedCell: focusedCellRef.current,
1709
+ currentEditingCell: editingCellRef.current,
1710
+ rowId: row.id,
1711
+ columnName: column.name,
1712
+ shouldEditOnFocus: shouldEditOnFocusFn(column),
1713
+ isCellEditable: isCellEditable(row, column, triggerEvent)
1714
+ });
1715
+ setFocusedCell(nextFocusResult.focusedCell);
1716
+ if (nextFocusResult.shouldBeginEdit) {
1717
+ startCellEditFn(row, column, triggerEvent);
1718
+ }
1719
+ },
1720
+ [isCellEditable, shouldEditOnFocusFn, startCellEditFn]
1721
+ );
1722
+ const handleCellKeyDownFn = useCallback(
1723
+ (row, column, event) => {
1724
+ focusCellFn(row, column, event.nativeEvent);
1725
+ switch (event.key) {
1726
+ case "ArrowLeft":
1727
+ event.preventDefault();
1728
+ moveFocusFn(row, column, "left", event.nativeEvent);
1729
+ return;
1730
+ case "ArrowRight":
1731
+ event.preventDefault();
1732
+ moveFocusFn(row, column, "right", event.nativeEvent);
1733
+ return;
1734
+ case "ArrowUp":
1735
+ event.preventDefault();
1736
+ moveFocusFn(row, column, "up", event.nativeEvent);
1737
+ return;
1738
+ case "ArrowDown":
1739
+ event.preventDefault();
1740
+ moveFocusFn(row, column, "down", event.nativeEvent);
1741
+ return;
1742
+ case "Tab":
1743
+ event.preventDefault();
1744
+ moveFocusFn(row, column, event.shiftKey ? "left" : "right", event.nativeEvent);
1745
+ return;
1746
+ case "Enter":
1747
+ event.preventDefault();
1748
+ moveFocusFn(row, column, event.shiftKey ? "up" : "down", event.nativeEvent);
1749
+ return;
1750
+ case "F2":
1751
+ event.preventDefault();
1752
+ if (isCellEditable(row, column, event.nativeEvent)) {
1753
+ startCellEditFn(row, column, event.nativeEvent);
1754
+ }
1755
+ return;
1756
+ case "Backspace":
1757
+ case "Delete":
1758
+ if (isCellEditable(row, column, event.nativeEvent)) {
1759
+ event.preventDefault();
1760
+ startCellEditFn(row, column, event.nativeEvent, "");
1761
+ }
1762
+ return;
1763
+ default:
1764
+ break;
1765
+ }
1766
+ if (isPrintableGridKey(event.key, event.ctrlKey, event.metaKey, event.altKey) && isCellEditable(row, column, event.nativeEvent)) {
1418
1767
  event.preventDefault();
1419
- moveFocusFn(row, column, "down", event.nativeEvent);
1420
- return;
1421
- case "Tab":
1768
+ startCellEditFn(row, column, event.nativeEvent, event.key);
1769
+ }
1770
+ },
1771
+ [focusCellFn, moveFocusFn, isCellEditable, startCellEditFn]
1772
+ );
1773
+ const handleCellDoubleClickFn = useCallback(
1774
+ (row, column, event) => {
1775
+ focusCellFn(row, column, event.nativeEvent);
1776
+ if (isCellEditable(row, column, event.nativeEvent)) {
1777
+ startCellEditFn(row, column, event.nativeEvent);
1778
+ }
1779
+ },
1780
+ [focusCellFn, isCellEditable, startCellEditFn]
1781
+ );
1782
+ const updateEditingValueFn = useCallback((value) => {
1783
+ setEditingValue(value);
1784
+ }, []);
1785
+ const handleEditorKeyDownFn = useCallback(
1786
+ (event) => {
1787
+ if (event.key === "Escape") {
1422
1788
  event.preventDefault();
1423
- moveFocusFn(row, column, event.shiftKey ? "left" : "right", event.nativeEvent);
1789
+ cancelCellEditFn();
1424
1790
  return;
1425
- case "Enter":
1791
+ }
1792
+ if (event.key === "Enter") {
1426
1793
  event.preventDefault();
1427
- moveFocusFn(row, column, event.shiftKey ? "up" : "down", event.nativeEvent);
1794
+ commitCellEditFn(event.shiftKey ? "up" : "down");
1428
1795
  return;
1429
- case "F2":
1796
+ }
1797
+ if (event.key === "Tab") {
1430
1798
  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
- }
1799
+ commitCellEditFn(event.shiftKey ? "left" : "right");
1800
+ }
1801
+ },
1802
+ [cancelCellEditFn, commitCellEditFn]
1803
+ );
1804
+ const handleEditorBlurFn = useCallback(
1805
+ (event) => {
1806
+ const ec = editingCellRef.current;
1807
+ const target = event.target;
1808
+ if (!ec || !target) return;
1809
+ if (target.dataset["rowId"] !== ec.rowId || target.dataset["colName"] !== ec.columnName)
1441
1810
  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]);
1811
+ commitCellEditFn(void 0, false);
1812
+ },
1813
+ [commitCellEditFn]
1814
+ );
1815
+ const toggleRowExpansionFn = useCallback(
1816
+ (row, event) => {
1817
+ event?.stopPropagation();
1818
+ toggleRowExpansionByRefFn(row);
1819
+ },
1820
+ [toggleRowExpansionByRefFn]
1821
+ );
1822
+ const toggleTreeRowFn = useCallback(
1823
+ (row, event) => {
1824
+ event?.stopPropagation();
1825
+ toggleTreeRowByRefFn(row);
1826
+ },
1827
+ [toggleTreeRowByRefFn]
1828
+ );
1490
1829
  const moveColumnFn = useCallback((fromIndex, toIndex) => {
1491
1830
  moveGridColumnCommand(
1492
1831
  gridApiRef.current,
@@ -1502,9 +1841,12 @@ function useGridState(options, onRegisterApi) {
1502
1841
  const previousPageFn = useCallback(() => {
1503
1842
  seekPageFn(getCurrentPageValueFn() - 1);
1504
1843
  }, [seekPageFn, getCurrentPageValueFn]);
1505
- const onPageSizeChangeFn = useCallback((value) => {
1506
- setPaginationPageSizeFn(Number(value));
1507
- }, [setPaginationPageSizeFn]);
1844
+ const onPageSizeChangeFn = useCallback(
1845
+ (value) => {
1846
+ setPaginationPageSizeFn(Number(value));
1847
+ },
1848
+ [setPaginationPageSizeFn]
1849
+ );
1508
1850
  const onViewportScrollFn = useCallback((startIndex) => {
1509
1851
  if (!scrollingRef.current) {
1510
1852
  scrollingRef.current = true;
@@ -1524,7 +1866,10 @@ function useGridState(options, onRegisterApi) {
1524
1866
  state: infiniteScrollStateRef.current,
1525
1867
  startIndex,
1526
1868
  visibleRows: pipelineRef.current.visibleRows.length,
1527
- viewportRows: Math.max(1, Math.ceil((optionsRef.current.viewportHeight ?? 560) / (optionsRef.current.rowHeight ?? 44))),
1869
+ viewportRows: computeViewportRows(
1870
+ optionsRef.current.viewportHeight,
1871
+ optionsRef.current.rowHeight
1872
+ ),
1528
1873
  threshold: optionsRef.current.infiniteScrollRowsFromEnd ?? 20,
1529
1874
  setState: (state) => setInfiniteScrollState(state)
1530
1875
  });
@@ -1624,29 +1969,60 @@ function useGridState(options, onRegisterApi) {
1624
1969
  onPageSizeChange: onPageSizeChangeFn,
1625
1970
  runBenchmark: runBenchmarkFn,
1626
1971
  exportCsv: exportCsvFn,
1627
- onViewportScroll: onViewportScrollFn
1972
+ onViewportScroll: onViewportScrollFn,
1973
+ // Pinning
1974
+ isPinned: isPinnedFn,
1975
+ pinnedOffset: pinnedOffsetFn,
1976
+ isPinningEnabled: isPinningEnabledFn,
1977
+ isColumnPinnable: isColumnPinnableFn,
1978
+ togglePin: togglePinFn,
1979
+ pinningFeature: FEATURE_PINNING
1628
1980
  };
1629
1981
  }
1630
1982
 
1631
1983
  // src/useVirtualScroll.ts
1632
1984
  import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
1985
+
1986
+ // src/virtualScrollMath.ts
1987
+ function calculateVirtualWindow(request) {
1988
+ const overscan = request.overscan ?? 3;
1989
+ if (request.itemCount <= 0 || request.itemSize <= 0) {
1990
+ return {
1991
+ visibleRange: { start: 0, end: 0 },
1992
+ totalHeight: Math.max(0, request.itemCount) * Math.max(0, request.itemSize),
1993
+ offsetY: 0
1994
+ };
1995
+ }
1996
+ const rawStart = Math.floor(request.scrollTop / request.itemSize) - overscan;
1997
+ const start = Math.max(0, rawStart);
1998
+ const rawEnd = rawStart + Math.ceil(request.viewportHeight / request.itemSize) + 2 * overscan;
1999
+ const end = Math.min(request.itemCount, rawEnd);
2000
+ return {
2001
+ visibleRange: { start, end },
2002
+ totalHeight: request.itemCount * request.itemSize,
2003
+ offsetY: start * request.itemSize
2004
+ };
2005
+ }
2006
+
2007
+ // src/useVirtualScroll.ts
1633
2008
  function useVirtualScroll(options) {
1634
2009
  const { itemCount, itemSize, viewportHeight, overscan = 3 } = options;
1635
2010
  const [scrollTop, setScrollTop] = useState2(0);
1636
2011
  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;
2012
+ const virtualWindow = calculateVirtualWindow({
2013
+ itemCount,
2014
+ itemSize,
2015
+ viewportHeight,
2016
+ overscan,
2017
+ scrollTop
2018
+ });
1643
2019
  const onScroll = useCallback2((event) => {
1644
2020
  setScrollTop(event.currentTarget.scrollTop);
1645
2021
  }, []);
1646
2022
  return {
1647
- visibleRange: { start, end },
1648
- totalHeight,
1649
- offsetY,
2023
+ visibleRange: virtualWindow.visibleRange,
2024
+ totalHeight: virtualWindow.totalHeight,
2025
+ offsetY: virtualWindow.offsetY,
1650
2026
  onScroll,
1651
2027
  viewportRef,
1652
2028
  scrollTop
@@ -1655,7 +2031,13 @@ function useVirtualScroll(options) {
1655
2031
 
1656
2032
  // src/UiGrid.tsx
1657
2033
  import { jsx, jsxs } from "react/jsx-runtime";
1658
- function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, className }) {
2034
+ function UiGrid({
2035
+ options,
2036
+ onRegisterApi,
2037
+ cellRenderer,
2038
+ expandableRenderer,
2039
+ className
2040
+ }) {
1659
2041
  const state = useGridState(options, onRegisterApi);
1660
2042
  const {
1661
2043
  pipeline,
@@ -1719,7 +2101,16 @@ function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, clas
1719
2101
  " ",
1720
2102
  labels.groupRowsSuffix
1721
2103
  ] }),
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" }) }),
2104
+ /* @__PURE__ */ jsx(
2105
+ "svg",
2106
+ {
2107
+ className: "toggle-icon group-disclosure-icon",
2108
+ viewBox: "0 0 24 24",
2109
+ "aria-hidden": "true",
2110
+ focusable: false,
2111
+ children: /* @__PURE__ */ jsx("path", { d: item.collapsed ? "M10 7l5 5-5 5z" : "M7 10l5 5 5-5z" })
2112
+ }
2113
+ ),
1723
2114
  /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.groupDisclosureLabel(item) })
1724
2115
  ]
1725
2116
  },
@@ -1741,62 +2132,84 @@ function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, clas
1741
2132
  }
1742
2133
  if (item.kind !== "row") return null;
1743
2134
  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",
2135
+ return visibleColumns.map((column) => {
2136
+ const pinned = state.isPinned(column);
2137
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
2138
+ return /* @__PURE__ */ jsx(
2139
+ "div",
2140
+ {
2141
+ className: `${cellClassName(rowItem, column)}${pinned ? " is-pinned" : ""}`,
2142
+ "data-part": "body-cell",
2143
+ role: "gridcell",
2144
+ tabIndex: 0,
2145
+ "data-row-id": rowItem.row.id,
2146
+ "data-col-name": column.name,
2147
+ onFocus: () => state.focusCell(rowItem.row, column),
2148
+ onClick: () => state.focusCell(rowItem.row, column),
2149
+ onDoubleClick: (e) => state.handleCellDoubleClick(rowItem.row, column, e),
2150
+ onKeyDown: (e) => state.handleCellKeyDown(rowItem.row, column, e),
2151
+ style: {
2152
+ position: pinned ? "sticky" : void 0,
2153
+ left: pinOffset?.side === "left" ? pinOffset.offset : void 0,
2154
+ right: pinOffset?.side === "right" ? pinOffset.offset : void 0,
2155
+ zIndex: pinned ? 2 : void 0
2156
+ },
2157
+ children: /* @__PURE__ */ jsxs(
2158
+ "div",
1784
2159
  {
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)
2160
+ className: "cell-shell",
2161
+ style: { paddingInlineStart: state.cellIndent(rowItem.row, column) },
2162
+ children: [
2163
+ treeViewFeature && state.showTreeToggle(rowItem.row, column) && /* @__PURE__ */ jsx(
2164
+ "button",
2165
+ {
2166
+ type: "button",
2167
+ className: "row-toggle row-toggle-tree",
2168
+ "data-part": "tree-toggle",
2169
+ "aria-label": state.treeToggleLabel(rowItem.row),
2170
+ "aria-expanded": state.isTreeRowExpanded(rowItem.row),
2171
+ onClick: (e) => state.toggleTreeRow(rowItem.row, e),
2172
+ children: /* @__PURE__ */ jsx("svg", { className: "toggle-icon", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: false, children: /* @__PURE__ */ jsx(
2173
+ "path",
2174
+ {
2175
+ d: state.isTreeRowExpanded(rowItem.row) ? "M7 10l5 5 5-5z" : "M10 7l5 5-5 5z"
2176
+ }
2177
+ ) })
2178
+ }
2179
+ ),
2180
+ expandableFeature && state.showExpandToggle(rowItem.row, column) && /* @__PURE__ */ jsx(
2181
+ "button",
2182
+ {
2183
+ type: "button",
2184
+ className: "row-toggle row-toggle-expand",
2185
+ "data-part": "expand-toggle",
2186
+ "aria-label": state.expandToggleLabel(rowItem.row),
2187
+ "aria-expanded": rowItem.row.expanded,
2188
+ onClick: (e) => state.toggleRowExpansion(rowItem.row, e),
2189
+ 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" }) })
2190
+ }
2191
+ ),
2192
+ /* @__PURE__ */ jsx("span", { className: "cell-value", children: cellEditFeature && state.isEditingCell(rowItem.row, column) ? /* @__PURE__ */ jsx(
2193
+ "input",
2194
+ {
2195
+ className: "cell-editor",
2196
+ "data-row-id": rowItem.row.id,
2197
+ "data-col-name": column.name,
2198
+ "aria-label": state.headerLabel(column),
2199
+ type: state.editorInputType(column),
2200
+ defaultValue: editingValue,
2201
+ onChange: (e) => state.updateEditingValue(e.target.value),
2202
+ onKeyDown: (e) => state.handleEditorKeyDown(e),
2203
+ onBlur: (e) => state.handleEditorBlur(e)
2204
+ }
2205
+ ) : cellRenderer ? cellRenderer(state.cellContext(rowItem.row, column)) ?? state.displayValue(rowItem.row, column) : state.displayValue(rowItem.row, column) })
2206
+ ]
1794
2207
  }
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
- ));
2208
+ )
2209
+ },
2210
+ `${rowItem.row.id}-${column.name}`
2211
+ );
2212
+ });
1800
2213
  }
1801
2214
  function cellClassName(item, column) {
1802
2215
  const classes = ["body-cell", "ui-grid-cell"];
@@ -1826,225 +2239,364 @@ function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, clas
1826
2239
  /* @__PURE__ */ jsx("p", { className: "deck", children: "Familiar `gridOptions` and `onRegisterApi`, built with React hooks, virtualization, grouping, sorting, filtering, and column ordering." })
1827
2240
  ] }),
1828
2241
  /* @__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" }),
2242
+ /* @__PURE__ */ jsx(
2243
+ "button",
2244
+ {
2245
+ type: "button",
2246
+ className: "action action-secondary",
2247
+ "data-part": "action benchmark-action",
2248
+ onClick: () => state.runBenchmark(),
2249
+ children: "Benchmark"
2250
+ }
2251
+ ),
2252
+ csvExportFeature && /* @__PURE__ */ jsx(
2253
+ "button",
2254
+ {
2255
+ type: "button",
2256
+ className: "action action-secondary",
2257
+ "data-part": "action export-action",
2258
+ onClick: () => state.exportCsv(),
2259
+ children: "Export CSV"
2260
+ }
2261
+ ),
1831
2262
  /* @__PURE__ */ jsxs("div", { className: "stats-card", "data-part": "stats-card", children: [
1832
2263
  /* @__PURE__ */ jsx("span", { children: visibleRowCount }),
1833
2264
  /* @__PURE__ */ jsx("small", { children: labels.statsVisibleRows })
1834
2265
  ] })
1835
2266
  ] })
1836
2267
  ] }),
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
2268
+ /* @__PURE__ */ jsxs(
2269
+ "section",
2270
+ {
2271
+ className: "metrics-strip",
2272
+ "data-part": "metrics",
2273
+ "aria-label": "Grid performance metrics",
2274
+ children: [
2275
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
2276
+ /* @__PURE__ */ jsxs("strong", { children: [
2277
+ pipelineMs.toFixed(2),
2278
+ " ms"
2279
+ ] }),
2280
+ /* @__PURE__ */ jsx("span", { children: "pipeline" })
2281
+ ] }),
2282
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
2283
+ /* @__PURE__ */ jsx("strong", { children: virtualizationEnabled ? "On" : "Off" }),
2284
+ /* @__PURE__ */ jsx("span", { children: "virtualization" })
2285
+ ] }),
2286
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
2287
+ /* @__PURE__ */ jsx("strong", { children: state.groupByColumns.length }),
2288
+ /* @__PURE__ */ jsx("span", { children: "group columns" })
2289
+ ] }),
2290
+ /* @__PURE__ */ jsxs("article", { "data-part": "metric-card", children: [
2291
+ /* @__PURE__ */ jsx("strong", { children: benchmarkResult?.averageMs?.toFixed(2) || "\u2014" }),
2292
+ /* @__PURE__ */ jsx("span", { children: "benchmark avg" })
1868
2293
  ] })
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(
2294
+ ]
2295
+ }
2296
+ ),
2297
+ /* @__PURE__ */ jsxs(
2298
+ "section",
2299
+ {
2300
+ className: "grid-frame ui-grid",
2301
+ "data-part": "grid-frame",
2302
+ role: "grid",
2303
+ "aria-label": options.title ?? "Data grid",
2304
+ children: [
2305
+ /* @__PURE__ */ jsxs("div", { className: "grid-toolbar", "data-part": "grid-toolbar", children: [
2306
+ /* @__PURE__ */ jsxs("div", { children: [
2307
+ /* @__PURE__ */ jsx("strong", { children: visibleRowCount }),
2308
+ /* @__PURE__ */ jsxs("span", { children: [
2309
+ labels.toolbarOf,
2310
+ " ",
2311
+ totalRows,
2312
+ " ",
2313
+ labels.toolbarRows
2314
+ ] })
2315
+ ] }),
2316
+ /* @__PURE__ */ jsx("p", { children: "`gridOptions` compatibility layer: sorting, filtering, grouping, column moving, templating, and virtualized rendering." })
2317
+ ] }),
2318
+ /* @__PURE__ */ jsxs("div", { className: "grid-table ui-grid-contents-wrapper", "data-part": "grid-table", children: [
2319
+ /* @__PURE__ */ jsx(
2320
+ "div",
2321
+ {
2322
+ className: "header-grid ui-grid-header ui-grid-header-canvas",
2323
+ "data-part": "header",
2324
+ role: "row",
2325
+ style: { gridTemplateColumns },
2326
+ children: visibleColumns.map((column) => {
2327
+ const pinned = state.isPinned(column);
2328
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
2329
+ return /* @__PURE__ */ jsxs(
2330
+ "div",
2331
+ {
2332
+ className: `header-cell ui-grid-header-cell${sortingFeature && state.sortDirection(column) !== "none" ? " is-active" : ""}${pinned ? " is-pinned" : ""}`,
2333
+ "data-part": "header-cell",
2334
+ role: "columnheader",
2335
+ "aria-sort": sortingFeature ? state.sortAriaSort(column) : void 0,
2336
+ style: {
2337
+ position: pinned ? "sticky" : void 0,
2338
+ left: pinOffset?.side === "left" ? pinOffset.offset : void 0,
2339
+ right: pinOffset?.side === "right" ? pinOffset.offset : void 0,
2340
+ zIndex: pinned ? 2 : void 0
2341
+ },
2342
+ children: [
2343
+ /* @__PURE__ */ jsx("span", { className: "header-label", children: state.headerLabel(column) }),
2344
+ /* @__PURE__ */ jsxs("div", { className: "header-actions", children: [
2345
+ sortingFeature && /* @__PURE__ */ jsxs(
2346
+ "button",
2347
+ {
2348
+ type: "button",
2349
+ className: `header-action${!state.isColumnSortable(column) ? " header-action-disabled" : ""}`,
2350
+ disabled: !state.isColumnSortable(column),
2351
+ "aria-label": state.sortButtonLabel(column),
2352
+ title: state.sortButtonLabel(column),
2353
+ onClick: () => state.toggleSort(column),
2354
+ children: [
2355
+ renderSortIcon(column),
2356
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.sortButtonLabel(column) })
2357
+ ]
2358
+ }
2359
+ ),
2360
+ groupingFeature && state.isGroupingEnabled() && column.enableGrouping !== false && /* @__PURE__ */ jsxs(
2361
+ "button",
2362
+ {
2363
+ type: "button",
2364
+ className: `chip-action${state.isGrouped(column) ? " chip-action-active" : ""}`,
2365
+ "data-part": "group-toggle",
2366
+ "aria-label": state.groupingButtonLabel(column),
2367
+ title: state.groupingButtonLabel(column),
2368
+ onClick: (e) => state.toggleGrouping(column, e),
2369
+ children: [
2370
+ /* @__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" }) }),
2371
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.groupingButtonLabel(column) })
2372
+ ]
2373
+ }
2374
+ ),
2375
+ state.pinningFeature && state.isPinningEnabled() && state.isColumnPinnable(column) && /* @__PURE__ */ jsxs(
2376
+ "button",
2377
+ {
2378
+ type: "button",
2379
+ className: `chip-action${pinned ? " chip-action-active" : ""}`,
2380
+ "data-part": "pin-toggle",
2381
+ "aria-label": pinned ? labels.unpin : labels.pinLeft,
2382
+ title: pinned ? labels.unpin : labels.pinLeft,
2383
+ onClick: () => state.togglePin(column),
2384
+ children: [
2385
+ /* @__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" }) }),
2386
+ /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: pinned ? labels.unpin : labels.pinLeft })
2387
+ ]
2388
+ }
2389
+ )
2390
+ ] })
2391
+ ]
2392
+ },
2393
+ column.name
2394
+ );
2395
+ })
2396
+ }
2397
+ ),
2398
+ filteringFeature && state.isFilteringEnabled() && /* @__PURE__ */ jsx(
2399
+ "div",
2400
+ {
2401
+ className: "filter-grid ui-grid-header",
2402
+ "data-part": "filters",
2403
+ style: { gridTemplateColumns },
2404
+ children: visibleColumns.map((column) => {
2405
+ const pinned = state.isPinned(column);
2406
+ const pinOffset = pinned ? state.pinnedOffset(column) : null;
2407
+ return /* @__PURE__ */ jsxs(
2408
+ "label",
2409
+ {
2410
+ className: `filter-cell ui-grid-filter-container${pinned ? " is-pinned" : ""}`,
2411
+ "data-part": "filter-cell",
2412
+ style: {
2413
+ position: pinned ? "sticky" : void 0,
2414
+ left: pinOffset?.side === "left" ? pinOffset.offset : void 0,
2415
+ right: pinOffset?.side === "right" ? pinOffset.offset : void 0,
2416
+ zIndex: pinned ? 2 : void 0
2417
+ },
2418
+ children: [
2419
+ /* @__PURE__ */ jsxs("span", { className: "sr-only ui-grid-sr-only", children: [
2420
+ labels.filterColumn,
2421
+ " ",
2422
+ state.headerLabel(column)
2423
+ ] }),
2424
+ /* @__PURE__ */ jsx(
2425
+ "input",
2426
+ {
2427
+ className: "ui-grid-filter-input",
2428
+ type: "text",
2429
+ defaultValue: state.filterValue(column.name),
2430
+ placeholder: state.filterPlaceholder(column),
2431
+ disabled: state.isFilterInputDisabled(column),
2432
+ onChange: (e) => state.updateFilter(column.name, e.target.value)
2433
+ }
2434
+ )
2435
+ ]
2436
+ },
2437
+ column.name
2438
+ );
2439
+ })
2440
+ }
2441
+ ),
2442
+ displayItems.length > 0 ? virtualizationEnabled ? /* @__PURE__ */ jsx(
1881
2443
  "div",
1882
2444
  {
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,
2445
+ className: "grid-viewport ui-grid-viewport",
2446
+ "data-part": "viewport",
2447
+ ref: virtualScroll.viewportRef,
2448
+ style: { height: viewportHeightPx, overflow: "auto", position: "relative" },
2449
+ onScroll: onViewportScroll,
2450
+ children: /* @__PURE__ */ jsx("div", { style: { height: `${virtualScroll.totalHeight}px`, position: "relative" }, children: /* @__PURE__ */ jsx(
2451
+ "div",
2452
+ {
2453
+ className: "body-grid ui-grid-canvas",
2454
+ "data-part": "body",
2455
+ role: "rowgroup",
2456
+ style: {
2457
+ gridTemplateColumns,
2458
+ position: "absolute",
2459
+ top: 0,
2460
+ left: 0,
2461
+ right: 0,
2462
+ transform: `translateY(${virtualScroll.offsetY}px)`
2463
+ },
2464
+ children: itemsToRender.map(renderDisplayItem)
2465
+ }
2466
+ ) })
2467
+ }
2468
+ ) : /* @__PURE__ */ jsx(
2469
+ "div",
2470
+ {
2471
+ className: "body-grid ui-grid-canvas",
2472
+ "data-part": "body",
2473
+ role: "rowgroup",
2474
+ style: { gridTemplateColumns },
2475
+ children: displayItems.map(renderDisplayItem)
2476
+ }
2477
+ ) : /* @__PURE__ */ jsxs("div", { className: "empty-state ui-grid-no-row-overlay", "data-part": "empty-state", children: [
2478
+ /* @__PURE__ */ jsx("strong", { children: options.emptyMessage ?? labels.emptyHeading }),
2479
+ /* @__PURE__ */ jsx("p", { children: labels.emptyDescription })
2480
+ ] }),
2481
+ paginationFeature && state.showPaginationControls() && /* @__PURE__ */ jsxs(
2482
+ "footer",
2483
+ {
2484
+ className: "pagination-bar ui-grid-pagination",
2485
+ "data-part": "pagination",
2486
+ role: "navigation",
2487
+ "aria-label": labels.paginationPage,
1887
2488
  children: [
1888
- /* @__PURE__ */ jsx("span", { className: "header-label", children: state.headerLabel(column) }),
1889
- /* @__PURE__ */ jsxs("div", { className: "header-actions", children: [
1890
- sortingFeature && /* @__PURE__ */ jsxs(
2489
+ /* @__PURE__ */ jsx("p", { children: state.paginationSummary() }),
2490
+ /* @__PURE__ */ jsxs("div", { className: "pagination-controls", children: [
2491
+ /* @__PURE__ */ jsxs(
1891
2492
  "button",
1892
2493
  {
1893
2494
  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),
2495
+ className: "action action-secondary pagination-button",
2496
+ "aria-label": labels.paginationPrevious,
2497
+ disabled: paginationCurrentPage <= 1,
2498
+ onClick: () => state.previousPage(),
1899
2499
  children: [
1900
- renderSortIcon(column),
1901
- /* @__PURE__ */ jsx("span", { className: "sr-only ui-grid-sr-only", children: state.sortButtonLabel(column) })
2500
+ /* @__PURE__ */ jsx(
2501
+ "svg",
2502
+ {
2503
+ className: "pagination-icon",
2504
+ viewBox: "0 0 24 24",
2505
+ "aria-hidden": "true",
2506
+ focusable: false,
2507
+ children: /* @__PURE__ */ jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" })
2508
+ }
2509
+ ),
2510
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationPrevious })
1902
2511
  ]
1903
2512
  }
1904
2513
  ),
1905
- groupingFeature && state.isGroupingEnabled() && column.enableGrouping !== false && /* @__PURE__ */ jsxs(
2514
+ /* @__PURE__ */ jsxs("span", { children: [
2515
+ labels.paginationPage,
2516
+ " ",
2517
+ paginationCurrentPage,
2518
+ " ",
2519
+ labels.paginationOf,
2520
+ " ",
2521
+ paginationTotalPages
2522
+ ] }),
2523
+ /* @__PURE__ */ jsxs(
1906
2524
  "button",
1907
2525
  {
1908
2526
  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),
2527
+ className: "action action-secondary pagination-button",
2528
+ "aria-label": labels.paginationNext,
2529
+ disabled: paginationCurrentPage >= paginationTotalPages,
2530
+ onClick: () => state.nextPage(),
1914
2531
  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) })
2532
+ /* @__PURE__ */ jsx(
2533
+ "svg",
2534
+ {
2535
+ className: "pagination-icon",
2536
+ viewBox: "0 0 24 24",
2537
+ "aria-hidden": "true",
2538
+ focusable: false,
2539
+ children: /* @__PURE__ */ jsx("path", { d: "M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z" })
2540
+ }
2541
+ ),
2542
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationNext })
1917
2543
  ]
1918
2544
  }
1919
- )
2545
+ ),
2546
+ state.pageSizeOptions().length > 0 && /* @__PURE__ */ jsxs("label", { className: "pagination-size", children: [
2547
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: labels.paginationRows }),
2548
+ /* @__PURE__ */ jsx(
2549
+ "select",
2550
+ {
2551
+ "aria-label": labels.paginationRows,
2552
+ value: paginationSelectedPageSize,
2553
+ onChange: (e) => state.onPageSizeChange(e.target.value),
2554
+ children: state.pageSizeOptions().map((size) => /* @__PURE__ */ jsx("option", { value: size, children: size }, size))
2555
+ }
2556
+ )
2557
+ ] })
1920
2558
  ] })
1921
2559
  ]
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)
1932
- ] }),
1933
- /* @__PURE__ */ jsx(
1934
- "input",
1935
- {
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)
1942
- }
1943
- )
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
2560
  }
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
- ] })
2561
+ )
2036
2562
  ] })
2037
- ] })
2038
- ] })
2039
- ] })
2563
+ ]
2564
+ }
2565
+ )
2040
2566
  ] }) });
2041
2567
  }
2042
2568
 
2569
+ // src/rustWasmGridEngine.ts
2570
+ import { registerRustWasmGridEngine } from "@ornery/ui-grid";
2571
+ var uiGridWasmModulePath = "../../../dist/ui-grid-wasm/ui_grid_wasm.js";
2572
+ function registerReactUiGridWasmEngineFromModule(module) {
2573
+ registerRustWasmGridEngine({
2574
+ buildPipeline(context) {
2575
+ return module.build_pipeline_js(context);
2576
+ }
2577
+ });
2578
+ }
2579
+ async function enableReactUiGridWasmEngine() {
2580
+ const module = await import(
2581
+ /* @vite-ignore */
2582
+ uiGridWasmModulePath
2583
+ );
2584
+ registerReactUiGridWasmEngineFromModule(module);
2585
+ }
2586
+
2043
2587
  // src/index.ts
2044
2588
  import { DEFAULT_GRID_LABELS } from "@ornery/ui-grid";
2045
2589
  export {
2046
2590
  DEFAULT_GRID_LABELS,
2047
2591
  UiGrid,
2592
+ buildGridTemplateColumns,
2593
+ computeViewportHeightPx,
2594
+ computeViewportRows,
2595
+ enableReactUiGridWasmEngine,
2596
+ formatPaginationSummary,
2597
+ orderVisibleColumns,
2598
+ registerReactUiGridWasmEngineFromModule,
2599
+ resolveBenchmarkIterations,
2048
2600
  useGridState,
2049
2601
  useVirtualScroll
2050
2602
  };