@alaarab/ogrid-core 2.1.14 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -524,23 +524,39 @@ function processClientSideData(data, columns, filters, sortBy, sortDirection) {
524
524
  const trimmed = val.value.trim();
525
525
  if (trimmed) {
526
526
  const lower = trimmed.toLowerCase();
527
- predicates.push((r) => String(getCellValue(r, col) ?? "").toLowerCase().includes(lower));
527
+ const textCache = /* @__PURE__ */ new Map();
528
+ for (let j = 0; j < data.length; j++) {
529
+ textCache.set(data[j], String(getCellValue(data[j], col) ?? "").toLowerCase());
530
+ }
531
+ predicates.push((r) => (textCache.get(r) ?? "").includes(lower));
528
532
  }
529
533
  break;
530
534
  }
531
535
  case "people": {
532
536
  const email = val.value.email.toLowerCase();
533
- predicates.push((r) => String(getCellValue(r, col) ?? "").toLowerCase() === email);
537
+ const peopleCache = /* @__PURE__ */ new Map();
538
+ for (let j = 0; j < data.length; j++) {
539
+ peopleCache.set(data[j], String(getCellValue(data[j], col) ?? "").toLowerCase());
540
+ }
541
+ predicates.push((r) => (peopleCache.get(r) ?? "") === email);
534
542
  break;
535
543
  }
536
544
  case "date": {
537
545
  const dv = val.value;
538
546
  const fromTs = dv.from ? (/* @__PURE__ */ new Date(dv.from + "T00:00:00")).getTime() : NaN;
539
547
  const toTs = dv.to ? (/* @__PURE__ */ new Date(dv.to + "T23:59:59.999")).getTime() : NaN;
548
+ const dateCache = /* @__PURE__ */ new Map();
549
+ for (let j = 0; j < data.length; j++) {
550
+ const cellVal = getCellValue(data[j], col);
551
+ if (cellVal == null) {
552
+ dateCache.set(data[j], NaN);
553
+ } else {
554
+ const t = new Date(String(cellVal)).getTime();
555
+ dateCache.set(data[j], Number.isNaN(t) ? NaN : t);
556
+ }
557
+ }
540
558
  predicates.push((r) => {
541
- const cellVal = getCellValue(r, col);
542
- if (cellVal == null) return false;
543
- const cellTs = new Date(String(cellVal)).getTime();
559
+ const cellTs = dateCache.get(r) ?? NaN;
544
560
  if (Number.isNaN(cellTs)) return false;
545
561
  if (!Number.isNaN(fromTs) && cellTs < fromTs) return false;
546
562
  if (!Number.isNaN(toTs) && cellTs > toTs) return false;
@@ -722,6 +738,66 @@ function calculateDropTarget(params) {
722
738
  }
723
739
 
724
740
  // src/utils/virtualScroll.ts
741
+ function computeVisibleColumnRange(scrollLeft, columnWidths, containerWidth, overscan = 2) {
742
+ if (columnWidths.length === 0 || containerWidth <= 0) {
743
+ return { startIndex: 0, endIndex: -1, leftOffset: 0, rightOffset: 0 };
744
+ }
745
+ let cumWidth = 0;
746
+ let rawStart = columnWidths.length;
747
+ let rawEnd = -1;
748
+ for (let i = 0; i < columnWidths.length; i++) {
749
+ const colStart = cumWidth;
750
+ cumWidth += columnWidths[i];
751
+ if (cumWidth > scrollLeft && rawStart === columnWidths.length) {
752
+ rawStart = i;
753
+ }
754
+ if (colStart < scrollLeft + containerWidth) {
755
+ rawEnd = i;
756
+ }
757
+ }
758
+ if (rawStart > rawEnd) {
759
+ return { startIndex: 0, endIndex: -1, leftOffset: 0, rightOffset: 0 };
760
+ }
761
+ const startIndex = Math.max(0, rawStart - overscan);
762
+ const endIndex = Math.min(columnWidths.length - 1, rawEnd + overscan);
763
+ let leftOffset = 0;
764
+ for (let i = 0; i < startIndex; i++) {
765
+ leftOffset += columnWidths[i];
766
+ }
767
+ let rightOffset = 0;
768
+ for (let i = endIndex + 1; i < columnWidths.length; i++) {
769
+ rightOffset += columnWidths[i];
770
+ }
771
+ return { startIndex, endIndex, leftOffset, rightOffset };
772
+ }
773
+ function partitionColumnsForVirtualization(visibleCols, columnRange, pinnedColumns) {
774
+ const pinnedLeft = [];
775
+ const pinnedRight = [];
776
+ const unpinned = [];
777
+ for (const col of visibleCols) {
778
+ const pin = pinnedColumns?.[col.columnId];
779
+ if (pin === "left") pinnedLeft.push(col);
780
+ else if (pin === "right") pinnedRight.push(col);
781
+ else unpinned.push(col);
782
+ }
783
+ if (!columnRange || columnRange.endIndex < 0) {
784
+ return {
785
+ pinnedLeft,
786
+ virtualizedUnpinned: unpinned,
787
+ pinnedRight,
788
+ leftSpacerWidth: 0,
789
+ rightSpacerWidth: 0
790
+ };
791
+ }
792
+ const virtualizedUnpinned = unpinned.slice(columnRange.startIndex, columnRange.endIndex + 1);
793
+ return {
794
+ pinnedLeft,
795
+ virtualizedUnpinned,
796
+ pinnedRight,
797
+ leftSpacerWidth: columnRange.leftOffset,
798
+ rightSpacerWidth: columnRange.rightOffset
799
+ };
800
+ }
725
801
  function computeVisibleRange(scrollTop, rowHeight, containerHeight, totalRows, overscan = 5) {
726
802
  if (totalRows <= 0 || rowHeight <= 0 || containerHeight <= 0) {
727
803
  return { startIndex: 0, endIndex: 0, offsetTop: 0, offsetBottom: 0 };
@@ -750,6 +826,239 @@ function getScrollTopForRow(rowIndex, rowHeight, containerHeight, align = "start
750
826
  }
751
827
  }
752
828
 
829
+ // src/workers/sortFilterWorker.ts
830
+ function workerBody() {
831
+ const ctx = self;
832
+ ctx.onmessage = (e) => {
833
+ const msg = e.data;
834
+ if (msg.type !== "sort-filter") return;
835
+ const { requestId, values, filters, sort } = msg;
836
+ const rowCount = values.length;
837
+ let indices = [];
838
+ const filterEntries = Object.entries(filters);
839
+ if (filterEntries.length === 0) {
840
+ indices = new Array(rowCount);
841
+ for (let i = 0; i < rowCount; i++) indices[i] = i;
842
+ } else {
843
+ for (let r = 0; r < rowCount; r++) {
844
+ let pass = true;
845
+ for (let f = 0; f < filterEntries.length; f++) {
846
+ const colIdx = Number(filterEntries[f][0]);
847
+ const filter = filterEntries[f][1];
848
+ const cellVal = values[r][colIdx];
849
+ switch (filter.type) {
850
+ case "text": {
851
+ const trimmed = filter.value.trim().toLowerCase();
852
+ if (trimmed && !String(cellVal ?? "").toLowerCase().includes(trimmed)) {
853
+ pass = false;
854
+ }
855
+ break;
856
+ }
857
+ case "multiSelect": {
858
+ if (filter.value.length > 0) {
859
+ const set = new Set(filter.value);
860
+ if (!set.has(String(cellVal ?? ""))) {
861
+ pass = false;
862
+ }
863
+ }
864
+ break;
865
+ }
866
+ case "date": {
867
+ if (cellVal == null) {
868
+ pass = false;
869
+ break;
870
+ }
871
+ const ts = new Date(String(cellVal)).getTime();
872
+ if (isNaN(ts)) {
873
+ pass = false;
874
+ break;
875
+ }
876
+ if (filter.value.from) {
877
+ const fromTs = (/* @__PURE__ */ new Date(filter.value.from + "T00:00:00")).getTime();
878
+ if (ts < fromTs) {
879
+ pass = false;
880
+ break;
881
+ }
882
+ }
883
+ if (filter.value.to) {
884
+ const toTs = (/* @__PURE__ */ new Date(filter.value.to + "T23:59:59.999")).getTime();
885
+ if (ts > toTs) {
886
+ pass = false;
887
+ break;
888
+ }
889
+ }
890
+ break;
891
+ }
892
+ }
893
+ if (!pass) break;
894
+ }
895
+ if (pass) indices.push(r);
896
+ }
897
+ }
898
+ if (sort) {
899
+ const { columnIndex, direction } = sort;
900
+ const dir = direction === "asc" ? 1 : -1;
901
+ indices.sort((a, b) => {
902
+ const av = values[a][columnIndex];
903
+ const bv = values[b][columnIndex];
904
+ if (av == null && bv == null) return 0;
905
+ if (av == null) return -1 * dir;
906
+ if (bv == null) return 1 * dir;
907
+ if (typeof av === "number" && typeof bv === "number") {
908
+ return av === bv ? 0 : av > bv ? dir : -dir;
909
+ }
910
+ const sa = String(av).toLowerCase();
911
+ const sb = String(bv).toLowerCase();
912
+ return sa === sb ? 0 : sa > sb ? dir : -dir;
913
+ });
914
+ }
915
+ const response = {
916
+ type: "sort-filter-result",
917
+ requestId,
918
+ indices
919
+ };
920
+ ctx.postMessage(response);
921
+ };
922
+ }
923
+
924
+ // src/utils/workerSortFilter.ts
925
+ var workerInstance = null;
926
+ var requestCounter = 0;
927
+ var pendingRequests = /* @__PURE__ */ new Map();
928
+ function createSortFilterWorker() {
929
+ if (workerInstance) return workerInstance;
930
+ if (typeof Worker === "undefined" || typeof Blob === "undefined" || typeof URL === "undefined") {
931
+ return null;
932
+ }
933
+ try {
934
+ const fnStr = workerBody.toString();
935
+ const blob = new Blob(
936
+ [`(${fnStr})()`],
937
+ { type: "application/javascript" }
938
+ );
939
+ const url = URL.createObjectURL(blob);
940
+ workerInstance = new Worker(url);
941
+ URL.revokeObjectURL(url);
942
+ workerInstance.onmessage = (e) => {
943
+ const { requestId, indices } = e.data;
944
+ const pending = pendingRequests.get(requestId);
945
+ if (pending) {
946
+ pendingRequests.delete(requestId);
947
+ pending.resolve(indices);
948
+ }
949
+ };
950
+ workerInstance.onerror = (err) => {
951
+ for (const [id, pending] of pendingRequests) {
952
+ pending.reject(new Error(err.message || "Worker error"));
953
+ pendingRequests.delete(id);
954
+ }
955
+ };
956
+ return workerInstance;
957
+ } catch {
958
+ return null;
959
+ }
960
+ }
961
+ function terminateSortFilterWorker() {
962
+ if (workerInstance) {
963
+ workerInstance.terminate();
964
+ workerInstance = null;
965
+ }
966
+ for (const [id, pending] of pendingRequests) {
967
+ pending.reject(new Error("Worker terminated"));
968
+ pendingRequests.delete(id);
969
+ }
970
+ }
971
+ function extractValueMatrix(data, columns) {
972
+ const matrix = new Array(data.length);
973
+ for (let r = 0; r < data.length; r++) {
974
+ const row = new Array(columns.length);
975
+ for (let c = 0; c < columns.length; c++) {
976
+ const val = getCellValue(data[r], columns[c]);
977
+ if (val == null) {
978
+ row[c] = null;
979
+ } else if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
980
+ row[c] = val;
981
+ } else {
982
+ row[c] = String(val);
983
+ }
984
+ }
985
+ matrix[r] = row;
986
+ }
987
+ return matrix;
988
+ }
989
+ function processClientSideDataAsync(data, columns, filters, sortBy, sortDirection) {
990
+ if (sortBy) {
991
+ const sortCol = columns.find((c) => c.columnId === sortBy);
992
+ if (sortCol?.compare) {
993
+ return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
994
+ }
995
+ }
996
+ const worker = createSortFilterWorker();
997
+ if (!worker) {
998
+ return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
999
+ }
1000
+ const columnIndexMap = /* @__PURE__ */ new Map();
1001
+ for (let i = 0; i < columns.length; i++) {
1002
+ columnIndexMap.set(columns[i].columnId, i);
1003
+ }
1004
+ const values = extractValueMatrix(data, columns);
1005
+ const columnMeta = columns.map((col, idx) => ({
1006
+ type: col.type ?? "text",
1007
+ index: idx
1008
+ }));
1009
+ const workerFilters = {};
1010
+ for (const col of columns) {
1011
+ const filterKey = getFilterField(col);
1012
+ const val = filters[filterKey];
1013
+ if (!val) continue;
1014
+ const colIdx = columnIndexMap.get(col.columnId);
1015
+ if (colIdx === void 0) continue;
1016
+ switch (val.type) {
1017
+ case "text":
1018
+ workerFilters[colIdx] = { type: "text", value: val.value };
1019
+ break;
1020
+ case "multiSelect":
1021
+ workerFilters[colIdx] = { type: "multiSelect", value: val.value };
1022
+ break;
1023
+ case "date":
1024
+ workerFilters[colIdx] = { type: "date", value: { from: val.value.from, to: val.value.to } };
1025
+ break;
1026
+ // 'people' filter has a UserLike object — fall back to sync
1027
+ case "people":
1028
+ return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
1029
+ }
1030
+ }
1031
+ let sort;
1032
+ if (sortBy) {
1033
+ const sortColIdx = columnIndexMap.get(sortBy);
1034
+ if (sortColIdx !== void 0) {
1035
+ sort = { columnIndex: sortColIdx, direction: sortDirection ?? "asc" };
1036
+ }
1037
+ }
1038
+ const requestId = ++requestCounter;
1039
+ return new Promise((resolve, reject) => {
1040
+ pendingRequests.set(requestId, {
1041
+ resolve: (indices) => {
1042
+ const result = new Array(indices.length);
1043
+ for (let i = 0; i < indices.length; i++) {
1044
+ result[i] = data[indices[i]];
1045
+ }
1046
+ resolve(result);
1047
+ },
1048
+ reject
1049
+ });
1050
+ const request = {
1051
+ type: "sort-filter",
1052
+ requestId,
1053
+ values,
1054
+ columnMeta,
1055
+ filters: workerFilters,
1056
+ sort
1057
+ };
1058
+ worker.postMessage(request);
1059
+ });
1060
+ }
1061
+
753
1062
  // src/utils/dataGridViewModel.ts
754
1063
  function getHeaderFilterConfig(col, input) {
755
1064
  const filterable = isFilterConfig(col.filterable) ? col.filterable : null;
@@ -1311,7 +1620,7 @@ function computeRowSelectionState(selectedIds, items, getRowId) {
1311
1620
  if (selectedIds.size === 0 || items.length === 0) {
1312
1621
  return { allSelected: false, someSelected: false };
1313
1622
  }
1314
- const allSelected = items.every((item) => selectedIds.has(getRowId(item)));
1623
+ const allSelected = selectedIds.size >= items.length && items.every((item) => selectedIds.has(getRowId(item)));
1315
1624
  const someSelected = !allSelected && selectedIds.size > 0;
1316
1625
  return { allSelected, someSelected };
1317
1626
  }
@@ -1636,4 +1945,4 @@ var Z_INDEX = {
1636
1945
  CONTEXT_MENU: 1e4
1637
1946
  };
1638
1947
 
1639
- export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CellDescriptorCache, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, MAX_PAGE_BUTTONS, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, ROW_NUMBER_COLUMN_WIDTH, SIDEBAR_TRANSITION_MS, UndoRedoStack, Z_INDEX, applyCellDeletion, applyCutClear, applyFillValues, applyPastedValues, applyRangeRowSelection, areGridRowPropsEqual, booleanParser, buildCellIndex, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps, buildPopoverEditorProps, calculateDropTarget, clampSelectionToBounds, computeAggregations, computeArrowNavigation, computeAutoScrollSpeed, computeNextSortState, computeRowSelectionState, computeTabNavigation, computeTotalHeight, computeVisibleRange, currencyParser, dateParser, debounce, deriveFilterOptionsFromData, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellRenderDescriptor, getCellValue, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getPinStateForColumn, getScrollTopForRow, getStatusBarParts, injectGlobalStyles, isColumnEditable, isFilterConfig, isInSelectionRange, isRowInRange, measureColumnContentWidth, measureRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, processClientSideData, rangesEqual, reorderColumnArray, resolveCellDisplayContent, resolveCellStyle, toUserLike, triggerCsvDownload, validateColumns, validateRowIds, validateVirtualScrollConfig };
1948
+ export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CellDescriptorCache, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, MAX_PAGE_BUTTONS, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, ROW_NUMBER_COLUMN_WIDTH, SIDEBAR_TRANSITION_MS, UndoRedoStack, Z_INDEX, applyCellDeletion, applyCutClear, applyFillValues, applyPastedValues, applyRangeRowSelection, areGridRowPropsEqual, booleanParser, buildCellIndex, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps, buildPopoverEditorProps, calculateDropTarget, clampSelectionToBounds, computeAggregations, computeArrowNavigation, computeAutoScrollSpeed, computeNextSortState, computeRowSelectionState, computeTabNavigation, computeTotalHeight, computeVisibleColumnRange, computeVisibleRange, createSortFilterWorker, currencyParser, dateParser, debounce, deriveFilterOptionsFromData, emailParser, escapeCsvValue, exportToCsv, extractValueMatrix, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellRenderDescriptor, getCellValue, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getPinStateForColumn, getScrollTopForRow, getStatusBarParts, injectGlobalStyles, isColumnEditable, isFilterConfig, isInSelectionRange, isRowInRange, measureColumnContentWidth, measureRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, partitionColumnsForVirtualization, processClientSideData, processClientSideDataAsync, rangesEqual, reorderColumnArray, resolveCellDisplayContent, resolveCellStyle, terminateSortFilterWorker, toUserLike, triggerCsvDownload, validateColumns, validateRowIds, validateVirtualScrollConfig };
@@ -22,8 +22,10 @@ export { areGridRowPropsEqual, isRowInRange } from './utils';
22
22
  export type { GridRowComparatorProps } from './utils';
23
23
  export { getPinStateForColumn, reorderColumnArray, calculateDropTarget, } from './utils';
24
24
  export type { ColumnPinState, IDropTarget, ICalculateDropTargetParams } from './utils';
25
- export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from './utils';
26
- export type { IVisibleRange } from './utils';
25
+ export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, computeVisibleColumnRange, partitionColumnsForVirtualization, } from './utils';
26
+ export type { IVisibleRange, IVisibleColumnRange } from './utils';
27
+ export { createSortFilterWorker, terminateSortFilterWorker, extractValueMatrix, processClientSideDataAsync, } from './utils';
28
+ export type { SortFilterRequest, SortFilterResponse } from './utils';
27
29
  export { getHeaderFilterConfig, getCellRenderDescriptor, CellDescriptorCache, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './utils';
28
30
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from './utils';
29
31
  export { debounce } from './utils';
@@ -143,6 +143,10 @@ export interface IVirtualScrollConfig {
143
143
  * higher values keep small grids fully rendered (no scroll offset artifacts).
144
144
  */
145
145
  threshold?: number;
146
+ /** Enable column virtualization — only render visible columns (default: false). */
147
+ columns?: boolean;
148
+ /** Number of extra columns to render outside the visible area (default: 2). */
149
+ columnOverscan?: number;
146
150
  }
147
151
  /** Configuration for column reordering via drag-and-drop. */
148
152
  export interface IColumnReorderConfig {
@@ -19,8 +19,10 @@ export { areGridRowPropsEqual, isRowInRange } from './gridRowComparator';
19
19
  export type { GridRowComparatorProps } from './gridRowComparator';
20
20
  export { getPinStateForColumn, reorderColumnArray, calculateDropTarget, } from './columnReorder';
21
21
  export type { ColumnPinState, IDropTarget, ICalculateDropTargetParams } from './columnReorder';
22
- export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, } from './virtualScroll';
23
- export type { IVisibleRange } from './virtualScroll';
22
+ export { computeVisibleRange, computeTotalHeight, getScrollTopForRow, computeVisibleColumnRange, partitionColumnsForVirtualization, } from './virtualScroll';
23
+ export type { IVisibleRange, IVisibleColumnRange } from './virtualScroll';
24
+ export { createSortFilterWorker, terminateSortFilterWorker, extractValueMatrix, processClientSideDataAsync, } from './workerSortFilter';
25
+ export type { SortFilterRequest, SortFilterResponse } from '../workers/sortFilterWorker';
24
26
  export { getHeaderFilterConfig, getCellRenderDescriptor, CellDescriptorCache, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, } from './dataGridViewModel';
25
27
  export type { HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, } from './dataGridViewModel';
26
28
  export { debounce } from './debounce';
@@ -1,3 +1,37 @@
1
+ import type { IColumnDef } from '../types';
2
+ /** The visible column range for horizontal virtualization. */
3
+ export interface IVisibleColumnRange {
4
+ /** First visible unpinned column index (inclusive, accounting for overscan). */
5
+ startIndex: number;
6
+ /** Last visible unpinned column index (inclusive, accounting for overscan). */
7
+ endIndex: number;
8
+ /** Pixel width of the left spacer (for columns before the visible range). */
9
+ leftOffset: number;
10
+ /** Pixel width of the right spacer (for columns after the visible range). */
11
+ rightOffset: number;
12
+ }
13
+ /**
14
+ * Compute the range of columns visible in the horizontal viewport.
15
+ * Linear scan over cumulative widths to find first/last visible column.
16
+ *
17
+ * @param scrollLeft - Current horizontal scroll offset (px)
18
+ * @param columnWidths - Array of widths for each unpinned column (px)
19
+ * @param containerWidth - Visible width of the scroll container (px)
20
+ * @param overscan - Number of extra columns to render on each side (default: 2)
21
+ * @returns The visible column range with start/end indices and left/right spacer widths
22
+ */
23
+ export declare function computeVisibleColumnRange(scrollLeft: number, columnWidths: number[], containerWidth: number, overscan?: number): IVisibleColumnRange;
24
+ /**
25
+ * Partition visible columns into pinned-left, virtualized-unpinned, and pinned-right.
26
+ * Pinned columns always render. Only unpinned columns in the visible range render.
27
+ */
28
+ export declare function partitionColumnsForVirtualization<T>(visibleCols: IColumnDef<T>[], columnRange: IVisibleColumnRange | null, pinnedColumns?: Record<string, 'left' | 'right'>): {
29
+ pinnedLeft: IColumnDef<T>[];
30
+ virtualizedUnpinned: IColumnDef<T>[];
31
+ pinnedRight: IColumnDef<T>[];
32
+ leftSpacerWidth: number;
33
+ rightSpacerWidth: number;
34
+ };
1
35
  /** The visible row range and spacer offsets for virtual scrolling. */
2
36
  export interface IVisibleRange {
3
37
  /** First visible row index (inclusive, accounting for overscan). */
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Main-thread wrapper for the sort/filter Web Worker.
3
+ * Falls back to synchronous processClientSideData when:
4
+ * - Worker API is unavailable (SSR, jsdom)
5
+ * - Sort column has a custom `compare` function
6
+ */
7
+ import type { IColumnDef, IFilters } from '../types';
8
+ /**
9
+ * Create (or reuse) the sort/filter Web Worker from an inline Blob URL.
10
+ * Returns null if the Worker API is unavailable.
11
+ */
12
+ export declare function createSortFilterWorker(): Worker | null;
13
+ /**
14
+ * Terminate the sort/filter worker and clean up.
15
+ */
16
+ export declare function terminateSortFilterWorker(): void;
17
+ /**
18
+ * Build a flat value matrix from data and columns.
19
+ * Each cell is extracted via getCellValue and coerced to a primitive.
20
+ */
21
+ export declare function extractValueMatrix<T>(data: T[], columns: IColumnDef<T>[]): (string | number | boolean | null)[][];
22
+ /**
23
+ * Async version of processClientSideData that offloads to a Web Worker.
24
+ *
25
+ * Falls back to synchronous processing when:
26
+ * - Worker API is unavailable
27
+ * - Sort column has a custom `compare` function (not serializable)
28
+ */
29
+ export declare function processClientSideDataAsync<T>(data: T[], columns: IColumnDef<T>[], filters: IFilters, sortBy?: string, sortDirection?: 'asc' | 'desc'): Promise<T[]>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Web Worker script for offloading sort/filter to a background thread.
3
+ *
4
+ * Operates on flat primitives (no IColumnDef references) so it can be
5
+ * serialized into an inline Blob URL. The main thread sends a value matrix
6
+ * and column metadata; the worker applies filters + sort and returns row indices.
7
+ */
8
+ export interface SortFilterRequest {
9
+ type: 'sort-filter';
10
+ requestId: number;
11
+ /** Flat value matrix: values[row][col] */
12
+ values: (string | number | boolean | null)[][];
13
+ /** Column metadata (only columns that participate in filter/sort). */
14
+ columnMeta: {
15
+ type: 'text' | 'numeric' | 'date' | 'boolean';
16
+ index: number;
17
+ }[];
18
+ /** Active filters keyed by column index in the values matrix. */
19
+ filters: Record<number, {
20
+ type: 'text';
21
+ value: string;
22
+ } | {
23
+ type: 'multiSelect';
24
+ value: string[];
25
+ } | {
26
+ type: 'date';
27
+ value: {
28
+ from?: string;
29
+ to?: string;
30
+ };
31
+ }>;
32
+ /** Sort spec (optional). */
33
+ sort?: {
34
+ columnIndex: number;
35
+ direction: 'asc' | 'desc';
36
+ };
37
+ }
38
+ export interface SortFilterResponse {
39
+ type: 'sort-filter-result';
40
+ requestId: number;
41
+ /** Sorted/filtered row indices into the original data array. */
42
+ indices: number[];
43
+ }
44
+ /**
45
+ * The worker function body. This is stringified into an inline Blob URL
46
+ * by the main-thread wrapper, so it must be fully self-contained.
47
+ */
48
+ export declare function workerBody(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid-core",
3
- "version": "2.1.14",
3
+ "version": "2.2.0",
4
4
  "description": "OGrid core – framework-agnostic types, algorithms, and utilities for OGrid data grids.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",