@alaarab/ogrid-js 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
|
@@ -502,23 +502,39 @@ function processClientSideData(data, columns, filters, sortBy, sortDirection) {
|
|
|
502
502
|
const trimmed = val.value.trim();
|
|
503
503
|
if (trimmed) {
|
|
504
504
|
const lower = trimmed.toLowerCase();
|
|
505
|
-
|
|
505
|
+
const textCache = /* @__PURE__ */ new Map();
|
|
506
|
+
for (let j = 0; j < data.length; j++) {
|
|
507
|
+
textCache.set(data[j], String(getCellValue(data[j], col) ?? "").toLowerCase());
|
|
508
|
+
}
|
|
509
|
+
predicates.push((r) => (textCache.get(r) ?? "").includes(lower));
|
|
506
510
|
}
|
|
507
511
|
break;
|
|
508
512
|
}
|
|
509
513
|
case "people": {
|
|
510
514
|
const email = val.value.email.toLowerCase();
|
|
511
|
-
|
|
515
|
+
const peopleCache = /* @__PURE__ */ new Map();
|
|
516
|
+
for (let j = 0; j < data.length; j++) {
|
|
517
|
+
peopleCache.set(data[j], String(getCellValue(data[j], col) ?? "").toLowerCase());
|
|
518
|
+
}
|
|
519
|
+
predicates.push((r) => (peopleCache.get(r) ?? "") === email);
|
|
512
520
|
break;
|
|
513
521
|
}
|
|
514
522
|
case "date": {
|
|
515
523
|
const dv = val.value;
|
|
516
524
|
const fromTs = dv.from ? (/* @__PURE__ */ new Date(dv.from + "T00:00:00")).getTime() : NaN;
|
|
517
525
|
const toTs = dv.to ? (/* @__PURE__ */ new Date(dv.to + "T23:59:59.999")).getTime() : NaN;
|
|
526
|
+
const dateCache = /* @__PURE__ */ new Map();
|
|
527
|
+
for (let j = 0; j < data.length; j++) {
|
|
528
|
+
const cellVal = getCellValue(data[j], col);
|
|
529
|
+
if (cellVal == null) {
|
|
530
|
+
dateCache.set(data[j], NaN);
|
|
531
|
+
} else {
|
|
532
|
+
const t = new Date(String(cellVal)).getTime();
|
|
533
|
+
dateCache.set(data[j], Number.isNaN(t) ? NaN : t);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
518
536
|
predicates.push((r) => {
|
|
519
|
-
const
|
|
520
|
-
if (cellVal == null) return false;
|
|
521
|
-
const cellTs = new Date(String(cellVal)).getTime();
|
|
537
|
+
const cellTs = dateCache.get(r) ?? NaN;
|
|
522
538
|
if (Number.isNaN(cellTs)) return false;
|
|
523
539
|
if (!Number.isNaN(fromTs) && cellTs < fromTs) return false;
|
|
524
540
|
if (!Number.isNaN(toTs) && cellTs > toTs) return false;
|
|
@@ -694,6 +710,66 @@ function calculateDropTarget(params) {
|
|
|
694
710
|
}
|
|
695
711
|
return { targetIndex, indicatorX };
|
|
696
712
|
}
|
|
713
|
+
function computeVisibleColumnRange(scrollLeft, columnWidths, containerWidth, overscan = 2) {
|
|
714
|
+
if (columnWidths.length === 0 || containerWidth <= 0) {
|
|
715
|
+
return { startIndex: 0, endIndex: -1, leftOffset: 0, rightOffset: 0 };
|
|
716
|
+
}
|
|
717
|
+
let cumWidth = 0;
|
|
718
|
+
let rawStart = columnWidths.length;
|
|
719
|
+
let rawEnd = -1;
|
|
720
|
+
for (let i = 0; i < columnWidths.length; i++) {
|
|
721
|
+
const colStart = cumWidth;
|
|
722
|
+
cumWidth += columnWidths[i];
|
|
723
|
+
if (cumWidth > scrollLeft && rawStart === columnWidths.length) {
|
|
724
|
+
rawStart = i;
|
|
725
|
+
}
|
|
726
|
+
if (colStart < scrollLeft + containerWidth) {
|
|
727
|
+
rawEnd = i;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (rawStart > rawEnd) {
|
|
731
|
+
return { startIndex: 0, endIndex: -1, leftOffset: 0, rightOffset: 0 };
|
|
732
|
+
}
|
|
733
|
+
const startIndex = Math.max(0, rawStart - overscan);
|
|
734
|
+
const endIndex = Math.min(columnWidths.length - 1, rawEnd + overscan);
|
|
735
|
+
let leftOffset = 0;
|
|
736
|
+
for (let i = 0; i < startIndex; i++) {
|
|
737
|
+
leftOffset += columnWidths[i];
|
|
738
|
+
}
|
|
739
|
+
let rightOffset = 0;
|
|
740
|
+
for (let i = endIndex + 1; i < columnWidths.length; i++) {
|
|
741
|
+
rightOffset += columnWidths[i];
|
|
742
|
+
}
|
|
743
|
+
return { startIndex, endIndex, leftOffset, rightOffset };
|
|
744
|
+
}
|
|
745
|
+
function partitionColumnsForVirtualization(visibleCols, columnRange, pinnedColumns) {
|
|
746
|
+
const pinnedLeft = [];
|
|
747
|
+
const pinnedRight = [];
|
|
748
|
+
const unpinned = [];
|
|
749
|
+
for (const col of visibleCols) {
|
|
750
|
+
const pin = pinnedColumns?.[col.columnId];
|
|
751
|
+
if (pin === "left") pinnedLeft.push(col);
|
|
752
|
+
else if (pin === "right") pinnedRight.push(col);
|
|
753
|
+
else unpinned.push(col);
|
|
754
|
+
}
|
|
755
|
+
if (!columnRange || columnRange.endIndex < 0) {
|
|
756
|
+
return {
|
|
757
|
+
pinnedLeft,
|
|
758
|
+
virtualizedUnpinned: unpinned,
|
|
759
|
+
pinnedRight,
|
|
760
|
+
leftSpacerWidth: 0,
|
|
761
|
+
rightSpacerWidth: 0
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
const virtualizedUnpinned = unpinned.slice(columnRange.startIndex, columnRange.endIndex + 1);
|
|
765
|
+
return {
|
|
766
|
+
pinnedLeft,
|
|
767
|
+
virtualizedUnpinned,
|
|
768
|
+
pinnedRight,
|
|
769
|
+
leftSpacerWidth: columnRange.leftOffset,
|
|
770
|
+
rightSpacerWidth: columnRange.rightOffset
|
|
771
|
+
};
|
|
772
|
+
}
|
|
697
773
|
function computeVisibleRange(scrollTop, rowHeight, containerHeight, totalRows, overscan = 5) {
|
|
698
774
|
if (totalRows <= 0 || rowHeight <= 0 || containerHeight <= 0) {
|
|
699
775
|
return { startIndex: 0, endIndex: 0, offsetTop: 0, offsetBottom: 0 };
|
|
@@ -721,6 +797,235 @@ function getScrollTopForRow(rowIndex, rowHeight, containerHeight, align = "start
|
|
|
721
797
|
return Math.max(0, rowTop - containerHeight + rowHeight);
|
|
722
798
|
}
|
|
723
799
|
}
|
|
800
|
+
function workerBody() {
|
|
801
|
+
const ctx = self;
|
|
802
|
+
ctx.onmessage = (e) => {
|
|
803
|
+
const msg = e.data;
|
|
804
|
+
if (msg.type !== "sort-filter") return;
|
|
805
|
+
const { requestId, values, filters, sort } = msg;
|
|
806
|
+
const rowCount = values.length;
|
|
807
|
+
let indices = [];
|
|
808
|
+
const filterEntries = Object.entries(filters);
|
|
809
|
+
if (filterEntries.length === 0) {
|
|
810
|
+
indices = new Array(rowCount);
|
|
811
|
+
for (let i = 0; i < rowCount; i++) indices[i] = i;
|
|
812
|
+
} else {
|
|
813
|
+
for (let r = 0; r < rowCount; r++) {
|
|
814
|
+
let pass = true;
|
|
815
|
+
for (let f = 0; f < filterEntries.length; f++) {
|
|
816
|
+
const colIdx = Number(filterEntries[f][0]);
|
|
817
|
+
const filter = filterEntries[f][1];
|
|
818
|
+
const cellVal = values[r][colIdx];
|
|
819
|
+
switch (filter.type) {
|
|
820
|
+
case "text": {
|
|
821
|
+
const trimmed = filter.value.trim().toLowerCase();
|
|
822
|
+
if (trimmed && !String(cellVal ?? "").toLowerCase().includes(trimmed)) {
|
|
823
|
+
pass = false;
|
|
824
|
+
}
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
case "multiSelect": {
|
|
828
|
+
if (filter.value.length > 0) {
|
|
829
|
+
const set = new Set(filter.value);
|
|
830
|
+
if (!set.has(String(cellVal ?? ""))) {
|
|
831
|
+
pass = false;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
case "date": {
|
|
837
|
+
if (cellVal == null) {
|
|
838
|
+
pass = false;
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
const ts = new Date(String(cellVal)).getTime();
|
|
842
|
+
if (isNaN(ts)) {
|
|
843
|
+
pass = false;
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
if (filter.value.from) {
|
|
847
|
+
const fromTs = (/* @__PURE__ */ new Date(filter.value.from + "T00:00:00")).getTime();
|
|
848
|
+
if (ts < fromTs) {
|
|
849
|
+
pass = false;
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (filter.value.to) {
|
|
854
|
+
const toTs = (/* @__PURE__ */ new Date(filter.value.to + "T23:59:59.999")).getTime();
|
|
855
|
+
if (ts > toTs) {
|
|
856
|
+
pass = false;
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (!pass) break;
|
|
864
|
+
}
|
|
865
|
+
if (pass) indices.push(r);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (sort) {
|
|
869
|
+
const { columnIndex, direction } = sort;
|
|
870
|
+
const dir = direction === "asc" ? 1 : -1;
|
|
871
|
+
indices.sort((a, b) => {
|
|
872
|
+
const av = values[a][columnIndex];
|
|
873
|
+
const bv = values[b][columnIndex];
|
|
874
|
+
if (av == null && bv == null) return 0;
|
|
875
|
+
if (av == null) return -1 * dir;
|
|
876
|
+
if (bv == null) return 1 * dir;
|
|
877
|
+
if (typeof av === "number" && typeof bv === "number") {
|
|
878
|
+
return av === bv ? 0 : av > bv ? dir : -dir;
|
|
879
|
+
}
|
|
880
|
+
const sa = String(av).toLowerCase();
|
|
881
|
+
const sb = String(bv).toLowerCase();
|
|
882
|
+
return sa === sb ? 0 : sa > sb ? dir : -dir;
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
const response = {
|
|
886
|
+
type: "sort-filter-result",
|
|
887
|
+
requestId,
|
|
888
|
+
indices
|
|
889
|
+
};
|
|
890
|
+
ctx.postMessage(response);
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
var workerInstance = null;
|
|
894
|
+
var requestCounter = 0;
|
|
895
|
+
var pendingRequests = /* @__PURE__ */ new Map();
|
|
896
|
+
function createSortFilterWorker() {
|
|
897
|
+
if (workerInstance) return workerInstance;
|
|
898
|
+
if (typeof Worker === "undefined" || typeof Blob === "undefined" || typeof URL === "undefined") {
|
|
899
|
+
return null;
|
|
900
|
+
}
|
|
901
|
+
try {
|
|
902
|
+
const fnStr = workerBody.toString();
|
|
903
|
+
const blob = new Blob(
|
|
904
|
+
[`(${fnStr})()`],
|
|
905
|
+
{ type: "application/javascript" }
|
|
906
|
+
);
|
|
907
|
+
const url = URL.createObjectURL(blob);
|
|
908
|
+
workerInstance = new Worker(url);
|
|
909
|
+
URL.revokeObjectURL(url);
|
|
910
|
+
workerInstance.onmessage = (e) => {
|
|
911
|
+
const { requestId, indices } = e.data;
|
|
912
|
+
const pending = pendingRequests.get(requestId);
|
|
913
|
+
if (pending) {
|
|
914
|
+
pendingRequests.delete(requestId);
|
|
915
|
+
pending.resolve(indices);
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
workerInstance.onerror = (err) => {
|
|
919
|
+
for (const [id, pending] of pendingRequests) {
|
|
920
|
+
pending.reject(new Error(err.message || "Worker error"));
|
|
921
|
+
pendingRequests.delete(id);
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
return workerInstance;
|
|
925
|
+
} catch {
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function terminateSortFilterWorker() {
|
|
930
|
+
if (workerInstance) {
|
|
931
|
+
workerInstance.terminate();
|
|
932
|
+
workerInstance = null;
|
|
933
|
+
}
|
|
934
|
+
for (const [id, pending] of pendingRequests) {
|
|
935
|
+
pending.reject(new Error("Worker terminated"));
|
|
936
|
+
pendingRequests.delete(id);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function extractValueMatrix(data, columns) {
|
|
940
|
+
const matrix = new Array(data.length);
|
|
941
|
+
for (let r = 0; r < data.length; r++) {
|
|
942
|
+
const row = new Array(columns.length);
|
|
943
|
+
for (let c = 0; c < columns.length; c++) {
|
|
944
|
+
const val = getCellValue(data[r], columns[c]);
|
|
945
|
+
if (val == null) {
|
|
946
|
+
row[c] = null;
|
|
947
|
+
} else if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
948
|
+
row[c] = val;
|
|
949
|
+
} else {
|
|
950
|
+
row[c] = String(val);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
matrix[r] = row;
|
|
954
|
+
}
|
|
955
|
+
return matrix;
|
|
956
|
+
}
|
|
957
|
+
function processClientSideDataAsync(data, columns, filters, sortBy, sortDirection) {
|
|
958
|
+
if (sortBy) {
|
|
959
|
+
const sortCol = columns.find((c) => c.columnId === sortBy);
|
|
960
|
+
if (sortCol?.compare) {
|
|
961
|
+
return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
const worker = createSortFilterWorker();
|
|
965
|
+
if (!worker) {
|
|
966
|
+
return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
|
|
967
|
+
}
|
|
968
|
+
const columnIndexMap = /* @__PURE__ */ new Map();
|
|
969
|
+
for (let i = 0; i < columns.length; i++) {
|
|
970
|
+
columnIndexMap.set(columns[i].columnId, i);
|
|
971
|
+
}
|
|
972
|
+
const values = extractValueMatrix(data, columns);
|
|
973
|
+
const columnMeta = columns.map((col, idx) => ({
|
|
974
|
+
type: col.type ?? "text",
|
|
975
|
+
index: idx
|
|
976
|
+
}));
|
|
977
|
+
const workerFilters = {};
|
|
978
|
+
for (const col of columns) {
|
|
979
|
+
const filterKey = getFilterField(col);
|
|
980
|
+
const val = filters[filterKey];
|
|
981
|
+
if (!val) continue;
|
|
982
|
+
const colIdx = columnIndexMap.get(col.columnId);
|
|
983
|
+
if (colIdx === void 0) continue;
|
|
984
|
+
switch (val.type) {
|
|
985
|
+
case "text":
|
|
986
|
+
workerFilters[colIdx] = { type: "text", value: val.value };
|
|
987
|
+
break;
|
|
988
|
+
case "multiSelect":
|
|
989
|
+
workerFilters[colIdx] = { type: "multiSelect", value: val.value };
|
|
990
|
+
break;
|
|
991
|
+
case "date":
|
|
992
|
+
workerFilters[colIdx] = { type: "date", value: { from: val.value.from, to: val.value.to } };
|
|
993
|
+
break;
|
|
994
|
+
// 'people' filter has a UserLike object — fall back to sync
|
|
995
|
+
case "people":
|
|
996
|
+
return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
let sort;
|
|
1000
|
+
if (sortBy) {
|
|
1001
|
+
const sortColIdx = columnIndexMap.get(sortBy);
|
|
1002
|
+
if (sortColIdx !== void 0) {
|
|
1003
|
+
sort = { columnIndex: sortColIdx, direction: sortDirection ?? "asc" };
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const requestId = ++requestCounter;
|
|
1007
|
+
return new Promise((resolve, reject) => {
|
|
1008
|
+
pendingRequests.set(requestId, {
|
|
1009
|
+
resolve: (indices) => {
|
|
1010
|
+
const result = new Array(indices.length);
|
|
1011
|
+
for (let i = 0; i < indices.length; i++) {
|
|
1012
|
+
result[i] = data[indices[i]];
|
|
1013
|
+
}
|
|
1014
|
+
resolve(result);
|
|
1015
|
+
},
|
|
1016
|
+
reject
|
|
1017
|
+
});
|
|
1018
|
+
const request = {
|
|
1019
|
+
type: "sort-filter",
|
|
1020
|
+
requestId,
|
|
1021
|
+
values,
|
|
1022
|
+
columnMeta,
|
|
1023
|
+
filters: workerFilters,
|
|
1024
|
+
sort
|
|
1025
|
+
};
|
|
1026
|
+
worker.postMessage(request);
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
724
1029
|
function getHeaderFilterConfig(col, input) {
|
|
725
1030
|
const filterable = isFilterConfig(col.filterable) ? col.filterable : null;
|
|
726
1031
|
const filterType = filterable?.type ?? "none";
|
|
@@ -1261,7 +1566,7 @@ function computeRowSelectionState(selectedIds, items, getRowId) {
|
|
|
1261
1566
|
if (selectedIds.size === 0 || items.length === 0) {
|
|
1262
1567
|
return { allSelected: false, someSelected: false };
|
|
1263
1568
|
}
|
|
1264
|
-
const allSelected = items.every((item) => selectedIds.has(getRowId(item)));
|
|
1569
|
+
const allSelected = selectedIds.size >= items.length && items.every((item) => selectedIds.has(getRowId(item)));
|
|
1265
1570
|
const someSelected = !allSelected && selectedIds.size > 0;
|
|
1266
1571
|
return { allSelected, someSelected };
|
|
1267
1572
|
}
|
|
@@ -1651,6 +1956,7 @@ var GridState = class {
|
|
|
1651
1956
|
this._ariaLabel = options.ariaLabel;
|
|
1652
1957
|
this._stickyHeader = options.stickyHeader ?? true;
|
|
1653
1958
|
this._fullScreen = options.fullScreen ?? false;
|
|
1959
|
+
this._workerSort = options.workerSort ?? false;
|
|
1654
1960
|
if (!this._dataSource) {
|
|
1655
1961
|
this._filterOptions = deriveFilterOptionsFromData(
|
|
1656
1962
|
this._data,
|
|
@@ -1754,6 +2060,31 @@ var GridState = class {
|
|
|
1754
2060
|
const items = filtered.slice(startIdx, endIdx);
|
|
1755
2061
|
return { items, totalCount };
|
|
1756
2062
|
}
|
|
2063
|
+
/** Whether worker sort should be used for the current data set. */
|
|
2064
|
+
get useWorkerSort() {
|
|
2065
|
+
return this._workerSort === true || this._workerSort === "auto" && this._data.length > 5e3;
|
|
2066
|
+
}
|
|
2067
|
+
/**
|
|
2068
|
+
* Async version of getProcessedItems that offloads sort/filter to a Web Worker.
|
|
2069
|
+
* Falls back to sync when worker sort is not active.
|
|
2070
|
+
*/
|
|
2071
|
+
async getProcessedItemsAsync() {
|
|
2072
|
+
if (this.isServerSide || !this.useWorkerSort) {
|
|
2073
|
+
return this.getProcessedItems();
|
|
2074
|
+
}
|
|
2075
|
+
const filtered = await processClientSideDataAsync(
|
|
2076
|
+
this._data,
|
|
2077
|
+
this._columns,
|
|
2078
|
+
this._filters,
|
|
2079
|
+
this._sort?.field,
|
|
2080
|
+
this._sort?.direction
|
|
2081
|
+
);
|
|
2082
|
+
const totalCount = filtered.length;
|
|
2083
|
+
const startIdx = (this._page - 1) * this._pageSize;
|
|
2084
|
+
const endIdx = startIdx + this._pageSize;
|
|
2085
|
+
const items = filtered.slice(startIdx, endIdx);
|
|
2086
|
+
return { items, totalCount };
|
|
2087
|
+
}
|
|
1757
2088
|
// --- Server-side fetch ---
|
|
1758
2089
|
fetchServerData() {
|
|
1759
2090
|
if (!this._dataSource) return;
|
|
@@ -1958,6 +2289,17 @@ var GridState = class {
|
|
|
1958
2289
|
};
|
|
1959
2290
|
|
|
1960
2291
|
// src/renderer/TableRenderer.ts
|
|
2292
|
+
function rangeBounds(r) {
|
|
2293
|
+
return {
|
|
2294
|
+
minR: Math.min(r.startRow, r.endRow),
|
|
2295
|
+
maxR: Math.max(r.startRow, r.endRow),
|
|
2296
|
+
minC: Math.min(r.startCol, r.endCol),
|
|
2297
|
+
maxC: Math.max(r.startCol, r.endCol)
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
function inBounds(b, row, col) {
|
|
2301
|
+
return row >= b.minR && row <= b.maxR && col >= b.minC && col <= b.maxC;
|
|
2302
|
+
}
|
|
1961
2303
|
var TableRenderer = class {
|
|
1962
2304
|
constructor(container, state) {
|
|
1963
2305
|
this.table = null;
|
|
@@ -2132,6 +2474,9 @@ var TableRenderer = class {
|
|
|
2132
2474
|
this.table = document.createElement("table");
|
|
2133
2475
|
this.table.className = "ogrid-table";
|
|
2134
2476
|
this.table.setAttribute("role", "grid");
|
|
2477
|
+
if (this.virtualScrollState) {
|
|
2478
|
+
this.table.setAttribute("data-virtual-scroll", "");
|
|
2479
|
+
}
|
|
2135
2480
|
this.thead = document.createElement("thead");
|
|
2136
2481
|
if (this.state.stickyHeader) {
|
|
2137
2482
|
this.thead.classList.add("ogrid-sticky-header");
|
|
@@ -2227,6 +2572,13 @@ var TableRenderer = class {
|
|
|
2227
2572
|
const lastSelection = this.lastSelectionRange;
|
|
2228
2573
|
const lastCopy = this.lastCopyRange;
|
|
2229
2574
|
const lastCut = this.lastCutRange;
|
|
2575
|
+
const selBounds = selectionRange ? rangeBounds(selectionRange) : null;
|
|
2576
|
+
const lastSelBounds = lastSelection ? rangeBounds(lastSelection) : null;
|
|
2577
|
+
const copyBounds = copyRange ? rangeBounds(copyRange) : null;
|
|
2578
|
+
const lastCopyBounds = lastCopy ? rangeBounds(lastCopy) : null;
|
|
2579
|
+
const cutBounds = cutRange ? rangeBounds(cutRange) : null;
|
|
2580
|
+
const lastCutBounds = lastCut ? rangeBounds(lastCut) : null;
|
|
2581
|
+
const colOffset = this.getColOffset();
|
|
2230
2582
|
const cells = this.tbody.querySelectorAll("td[data-row-index][data-col-index]");
|
|
2231
2583
|
for (let i = 0; i < cells.length; i++) {
|
|
2232
2584
|
const el = cells[i];
|
|
@@ -2234,7 +2586,6 @@ var TableRenderer = class {
|
|
|
2234
2586
|
if (!coords) continue;
|
|
2235
2587
|
const rowIndex = coords.rowIndex;
|
|
2236
2588
|
const globalColIndex = coords.colIndex;
|
|
2237
|
-
const colOffset = this.getColOffset();
|
|
2238
2589
|
const colIndex = globalColIndex - colOffset;
|
|
2239
2590
|
const wasActive = lastActive && lastActive.rowIndex === rowIndex && lastActive.columnIndex === globalColIndex;
|
|
2240
2591
|
const isActive = activeCell && activeCell.rowIndex === rowIndex && activeCell.columnIndex === globalColIndex;
|
|
@@ -2245,28 +2596,30 @@ var TableRenderer = class {
|
|
|
2245
2596
|
el.setAttribute("data-active-cell", "true");
|
|
2246
2597
|
el.style.outline = "2px solid var(--ogrid-accent, #0078d4)";
|
|
2247
2598
|
}
|
|
2248
|
-
const wasInRange =
|
|
2249
|
-
const isInRange =
|
|
2250
|
-
|
|
2599
|
+
const wasInRange = lastSelBounds && inBounds(lastSelBounds, rowIndex, colIndex);
|
|
2600
|
+
const isInRange = selBounds && inBounds(selBounds, rowIndex, colIndex);
|
|
2601
|
+
const showRange = isInRange && !isActive;
|
|
2602
|
+
const showedRange = wasInRange && !(lastActive && lastActive.rowIndex === rowIndex && lastActive.columnIndex === globalColIndex);
|
|
2603
|
+
if (showedRange && !showRange) {
|
|
2251
2604
|
el.removeAttribute("data-in-range");
|
|
2252
2605
|
el.style.backgroundColor = "";
|
|
2253
|
-
} else if (
|
|
2606
|
+
} else if (showRange && !showedRange) {
|
|
2254
2607
|
el.setAttribute("data-in-range", "true");
|
|
2255
2608
|
el.style.backgroundColor = "var(--ogrid-range-bg, rgba(33, 115, 70, 0.12))";
|
|
2256
2609
|
}
|
|
2257
|
-
const wasInCopy =
|
|
2258
|
-
const isInCopy =
|
|
2610
|
+
const wasInCopy = lastCopyBounds && inBounds(lastCopyBounds, rowIndex, colIndex);
|
|
2611
|
+
const isInCopy = copyBounds && inBounds(copyBounds, rowIndex, colIndex);
|
|
2259
2612
|
if (wasInCopy && !isInCopy) {
|
|
2260
|
-
if (!isActive && !(
|
|
2613
|
+
if (!isActive && !(cutBounds && inBounds(cutBounds, rowIndex, colIndex))) {
|
|
2261
2614
|
el.style.outline = "";
|
|
2262
2615
|
}
|
|
2263
2616
|
} else if (isInCopy && !wasInCopy) {
|
|
2264
2617
|
el.style.outline = "1px dashed var(--ogrid-fg-muted, rgba(0, 0, 0, 0.5))";
|
|
2265
2618
|
}
|
|
2266
|
-
const wasInCut =
|
|
2267
|
-
const isInCut =
|
|
2619
|
+
const wasInCut = lastCutBounds && inBounds(lastCutBounds, rowIndex, colIndex);
|
|
2620
|
+
const isInCut = cutBounds && inBounds(cutBounds, rowIndex, colIndex);
|
|
2268
2621
|
if (wasInCut && !isInCut) {
|
|
2269
|
-
if (!isActive && !(
|
|
2622
|
+
if (!isActive && !(copyBounds && inBounds(copyBounds, rowIndex, colIndex))) {
|
|
2270
2623
|
el.style.outline = "";
|
|
2271
2624
|
}
|
|
2272
2625
|
} else if (isInCut && !wasInCut) {
|
|
@@ -2536,6 +2889,23 @@ var TableRenderer = class {
|
|
|
2536
2889
|
this.tbody.appendChild(topSpacer);
|
|
2537
2890
|
}
|
|
2538
2891
|
}
|
|
2892
|
+
const colVirtActive = vs?.columnVirtualizationEnabled === true && vs.columnRange != null;
|
|
2893
|
+
let renderCols = visibleCols;
|
|
2894
|
+
let colGlobalIndexMap = null;
|
|
2895
|
+
let colLeftSpacerWidth = 0;
|
|
2896
|
+
let colRightSpacerWidth = 0;
|
|
2897
|
+
if (colVirtActive && vs) {
|
|
2898
|
+
const partition = partitionColumnsForVirtualization(
|
|
2899
|
+
visibleCols,
|
|
2900
|
+
vs.columnRange,
|
|
2901
|
+
this.interactionState?.pinnedColumns
|
|
2902
|
+
);
|
|
2903
|
+
const combined = [...partition.pinnedLeft, ...partition.virtualizedUnpinned, ...partition.pinnedRight];
|
|
2904
|
+
colGlobalIndexMap = combined.map((c) => visibleCols.indexOf(c));
|
|
2905
|
+
renderCols = combined;
|
|
2906
|
+
colLeftSpacerWidth = partition.leftSpacerWidth;
|
|
2907
|
+
colRightSpacerWidth = partition.rightSpacerWidth;
|
|
2908
|
+
}
|
|
2539
2909
|
for (let rowIndex = startIndex; rowIndex <= endIndex; rowIndex++) {
|
|
2540
2910
|
const item = items[rowIndex];
|
|
2541
2911
|
if (!item) continue;
|
|
@@ -2575,9 +2945,18 @@ var TableRenderer = class {
|
|
|
2575
2945
|
td.textContent = String(rowNumberOffset + rowIndex + 1);
|
|
2576
2946
|
tr.appendChild(td);
|
|
2577
2947
|
}
|
|
2578
|
-
|
|
2579
|
-
const
|
|
2580
|
-
|
|
2948
|
+
if (colLeftSpacerWidth > 0) {
|
|
2949
|
+
const spacerTd = document.createElement("td");
|
|
2950
|
+
spacerTd.style.width = `${colLeftSpacerWidth}px`;
|
|
2951
|
+
spacerTd.style.minWidth = `${colLeftSpacerWidth}px`;
|
|
2952
|
+
spacerTd.style.padding = "0";
|
|
2953
|
+
spacerTd.style.border = "none";
|
|
2954
|
+
spacerTd.setAttribute("aria-hidden", "true");
|
|
2955
|
+
tr.appendChild(spacerTd);
|
|
2956
|
+
}
|
|
2957
|
+
for (let colIndex = 0; colIndex < renderCols.length; colIndex++) {
|
|
2958
|
+
const col = renderCols[colIndex];
|
|
2959
|
+
const globalColIndex = (colGlobalIndexMap ? colGlobalIndexMap[colIndex] : colIndex) + colOffset;
|
|
2581
2960
|
const td = document.createElement("td");
|
|
2582
2961
|
td.className = "ogrid-cell";
|
|
2583
2962
|
td.setAttribute("data-column-id", col.columnId);
|
|
@@ -2645,6 +3024,15 @@ var TableRenderer = class {
|
|
|
2645
3024
|
}
|
|
2646
3025
|
tr.appendChild(td);
|
|
2647
3026
|
}
|
|
3027
|
+
if (colRightSpacerWidth > 0) {
|
|
3028
|
+
const spacerTd = document.createElement("td");
|
|
3029
|
+
spacerTd.style.width = `${colRightSpacerWidth}px`;
|
|
3030
|
+
spacerTd.style.minWidth = `${colRightSpacerWidth}px`;
|
|
3031
|
+
spacerTd.style.padding = "0";
|
|
3032
|
+
spacerTd.style.border = "none";
|
|
3033
|
+
spacerTd.setAttribute("aria-hidden", "true");
|
|
3034
|
+
tr.appendChild(spacerTd);
|
|
3035
|
+
}
|
|
2648
3036
|
this.tbody.appendChild(tr);
|
|
2649
3037
|
}
|
|
2650
3038
|
if (isVirtual && vs) {
|
|
@@ -3989,7 +4377,16 @@ var VirtualScrollState = class {
|
|
|
3989
4377
|
this._totalRows = 0;
|
|
3990
4378
|
this.rafId = 0;
|
|
3991
4379
|
this._ro = null;
|
|
4380
|
+
this._resizeRafId = 0;
|
|
3992
4381
|
this._cachedRange = { startIndex: 0, endIndex: -1, offsetTop: 0, offsetBottom: 0 };
|
|
4382
|
+
// Column virtualization
|
|
4383
|
+
this._scrollLeft = 0;
|
|
4384
|
+
this._scrollLeftRafId = 0;
|
|
4385
|
+
this._containerWidth = 0;
|
|
4386
|
+
this._columnWidths = [];
|
|
4387
|
+
this._cachedColumnRange = null;
|
|
4388
|
+
this._roWidth = null;
|
|
4389
|
+
this._resizeWidthRafId = 0;
|
|
3993
4390
|
this._config = config ?? { enabled: false };
|
|
3994
4391
|
validateVirtualScrollConfig(this._config);
|
|
3995
4392
|
}
|
|
@@ -4018,6 +4415,71 @@ var VirtualScrollState = class {
|
|
|
4018
4415
|
get totalHeight() {
|
|
4019
4416
|
return computeTotalHeight(this._totalRows, this._config.rowHeight ?? DEFAULT_ROW_HEIGHT);
|
|
4020
4417
|
}
|
|
4418
|
+
/** Whether column virtualization is active. */
|
|
4419
|
+
get columnVirtualizationEnabled() {
|
|
4420
|
+
return this._config.columns === true;
|
|
4421
|
+
}
|
|
4422
|
+
/** Get the current visible column range (null when column virtualization is disabled). */
|
|
4423
|
+
get columnRange() {
|
|
4424
|
+
return this._cachedColumnRange;
|
|
4425
|
+
}
|
|
4426
|
+
/** Set the unpinned column widths for horizontal virtualization. */
|
|
4427
|
+
setColumnWidths(widths) {
|
|
4428
|
+
this._columnWidths = widths;
|
|
4429
|
+
this.recomputeColumnRange();
|
|
4430
|
+
}
|
|
4431
|
+
/** Handle horizontal scroll events. RAF-throttled. */
|
|
4432
|
+
handleHorizontalScroll(scrollLeft) {
|
|
4433
|
+
if (!this.columnVirtualizationEnabled) return;
|
|
4434
|
+
if (this._scrollLeftRafId) cancelAnimationFrame(this._scrollLeftRafId);
|
|
4435
|
+
this._scrollLeftRafId = requestAnimationFrame(() => {
|
|
4436
|
+
this._scrollLeftRafId = 0;
|
|
4437
|
+
this._scrollLeft = scrollLeft;
|
|
4438
|
+
this.recomputeColumnRange();
|
|
4439
|
+
});
|
|
4440
|
+
}
|
|
4441
|
+
/** Observe a container element for width changes (column virtualization). */
|
|
4442
|
+
observeContainerWidth(el) {
|
|
4443
|
+
this.disconnectWidthObserver();
|
|
4444
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
4445
|
+
this._roWidth = new ResizeObserver((entries) => {
|
|
4446
|
+
if (entries.length === 0) return;
|
|
4447
|
+
this._containerWidth = entries[0].contentRect.width;
|
|
4448
|
+
if (this._resizeWidthRafId) cancelAnimationFrame(this._resizeWidthRafId);
|
|
4449
|
+
this._resizeWidthRafId = requestAnimationFrame(() => {
|
|
4450
|
+
this._resizeWidthRafId = 0;
|
|
4451
|
+
this.recomputeColumnRange();
|
|
4452
|
+
});
|
|
4453
|
+
});
|
|
4454
|
+
this._roWidth.observe(el);
|
|
4455
|
+
}
|
|
4456
|
+
this._containerWidth = el.clientWidth;
|
|
4457
|
+
}
|
|
4458
|
+
disconnectWidthObserver() {
|
|
4459
|
+
if (this._roWidth) {
|
|
4460
|
+
this._roWidth.disconnect();
|
|
4461
|
+
this._roWidth = null;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
/** Recompute visible column range and emit if changed. */
|
|
4465
|
+
recomputeColumnRange() {
|
|
4466
|
+
if (!this.columnVirtualizationEnabled || this._columnWidths.length === 0 || this._containerWidth <= 0) {
|
|
4467
|
+
if (this._cachedColumnRange !== null) {
|
|
4468
|
+
this._cachedColumnRange = null;
|
|
4469
|
+
this.emitter.emit("columnRangeChanged", { columnRange: null });
|
|
4470
|
+
}
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4473
|
+
const overscan = this._config.columnOverscan ?? 2;
|
|
4474
|
+
const newRange = computeVisibleColumnRange(this._scrollLeft, this._columnWidths, this._containerWidth, overscan);
|
|
4475
|
+
const prev = this._cachedColumnRange;
|
|
4476
|
+
if (!prev || prev.startIndex !== newRange.startIndex || prev.endIndex !== newRange.endIndex) {
|
|
4477
|
+
this._cachedColumnRange = newRange;
|
|
4478
|
+
this.emitter.emit("columnRangeChanged", { columnRange: newRange });
|
|
4479
|
+
} else {
|
|
4480
|
+
this._cachedColumnRange = newRange;
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4021
4483
|
/** Handle scroll events from the table container. RAF-throttled. */
|
|
4022
4484
|
handleScroll(scrollTop) {
|
|
4023
4485
|
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
@@ -4052,11 +4514,13 @@ var VirtualScrollState = class {
|
|
|
4052
4514
|
this.disconnectObserver();
|
|
4053
4515
|
if (typeof ResizeObserver !== "undefined") {
|
|
4054
4516
|
this._ro = new ResizeObserver((entries) => {
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4517
|
+
if (entries.length === 0) return;
|
|
4518
|
+
this._containerHeight = entries[0].contentRect.height;
|
|
4519
|
+
if (this._resizeRafId) cancelAnimationFrame(this._resizeRafId);
|
|
4520
|
+
this._resizeRafId = requestAnimationFrame(() => {
|
|
4521
|
+
this._resizeRafId = 0;
|
|
4058
4522
|
this.recompute();
|
|
4059
|
-
}
|
|
4523
|
+
});
|
|
4060
4524
|
});
|
|
4061
4525
|
this._ro.observe(el);
|
|
4062
4526
|
}
|
|
@@ -4092,13 +4556,21 @@ var VirtualScrollState = class {
|
|
|
4092
4556
|
this.emitter.on("rangeChanged", handler);
|
|
4093
4557
|
return () => this.emitter.off("rangeChanged", handler);
|
|
4094
4558
|
}
|
|
4559
|
+
onColumnRangeChanged(handler) {
|
|
4560
|
+
this.emitter.on("columnRangeChanged", handler);
|
|
4561
|
+
return () => this.emitter.off("columnRangeChanged", handler);
|
|
4562
|
+
}
|
|
4095
4563
|
onConfigChanged(handler) {
|
|
4096
4564
|
this.emitter.on("configChanged", handler);
|
|
4097
4565
|
return () => this.emitter.off("configChanged", handler);
|
|
4098
4566
|
}
|
|
4099
4567
|
destroy() {
|
|
4100
4568
|
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
4569
|
+
if (this._resizeRafId) cancelAnimationFrame(this._resizeRafId);
|
|
4570
|
+
if (this._scrollLeftRafId) cancelAnimationFrame(this._scrollLeftRafId);
|
|
4571
|
+
if (this._resizeWidthRafId) cancelAnimationFrame(this._resizeWidthRafId);
|
|
4101
4572
|
this.disconnectObserver();
|
|
4573
|
+
this.disconnectWidthObserver();
|
|
4102
4574
|
this.emitter.removeAllListeners();
|
|
4103
4575
|
}
|
|
4104
4576
|
};
|
|
@@ -4744,7 +5216,7 @@ var FillHandleState = class {
|
|
|
4744
5216
|
endCol: end.endCol
|
|
4745
5217
|
});
|
|
4746
5218
|
this.setSelectionRange(norm);
|
|
4747
|
-
this.setActiveCell({ rowIndex:
|
|
5219
|
+
this.setActiveCell({ rowIndex: start.startRow, columnIndex: start.startCol + this.params.colOffset });
|
|
4748
5220
|
this.applyFillValuesFromCore(norm, start);
|
|
4749
5221
|
this._isFillDragging = false;
|
|
4750
5222
|
this.fillDragStart = null;
|
|
@@ -4996,7 +5468,8 @@ var MarchingAntsOverlay = class {
|
|
|
4996
5468
|
const clipRange = this.copyRange ?? this.cutRange;
|
|
4997
5469
|
const selRect = this.selectionRange ? measureRange2(this.container, this.selectionRange, this.colOffset) : null;
|
|
4998
5470
|
const clipRangeMatchesSel = this.selectionRange != null && clipRange != null && rangesEqual(this.selectionRange, clipRange);
|
|
4999
|
-
|
|
5471
|
+
const isSingleCell = this.selectionRange != null && this.selectionRange.startRow === this.selectionRange.endRow && this.selectionRange.startCol === this.selectionRange.endCol;
|
|
5472
|
+
if (selRect && !clipRangeMatchesSel && !isSingleCell) {
|
|
5000
5473
|
if (!this.selSvg) {
|
|
5001
5474
|
this.selSvg = this.createSvg(4);
|
|
5002
5475
|
this.container.appendChild(this.selSvg);
|
|
@@ -6387,16 +6860,25 @@ var OGrid = class {
|
|
|
6387
6860
|
this.renderer.setVirtualScrollState(this.virtualScrollState);
|
|
6388
6861
|
const handleScroll = () => {
|
|
6389
6862
|
this.virtualScrollState?.handleScroll(this.tableContainer.scrollTop);
|
|
6863
|
+
this.virtualScrollState?.handleHorizontalScroll(this.tableContainer.scrollLeft);
|
|
6390
6864
|
};
|
|
6391
6865
|
this.tableContainer.addEventListener("scroll", handleScroll, { passive: true });
|
|
6392
6866
|
this.unsubscribes.push(() => {
|
|
6393
6867
|
this.tableContainer.removeEventListener("scroll", handleScroll);
|
|
6394
6868
|
});
|
|
6869
|
+
if (options.virtualScroll?.columns) {
|
|
6870
|
+
this.virtualScrollState.observeContainerWidth(this.tableContainer);
|
|
6871
|
+
}
|
|
6395
6872
|
this.unsubscribes.push(
|
|
6396
6873
|
this.virtualScrollState.onRangeChanged(() => {
|
|
6397
6874
|
this.renderingHelper.updateRendererInteractionState();
|
|
6398
6875
|
})
|
|
6399
6876
|
);
|
|
6877
|
+
this.unsubscribes.push(
|
|
6878
|
+
this.virtualScrollState.onColumnRangeChanged(() => {
|
|
6879
|
+
this.renderingHelper.updateRendererInteractionState();
|
|
6880
|
+
})
|
|
6881
|
+
);
|
|
6400
6882
|
this.api.scrollToRow = (index, opts) => {
|
|
6401
6883
|
this.virtualScrollState?.scrollToRow(index, this.tableContainer, opts?.align);
|
|
6402
6884
|
};
|
|
@@ -6639,4 +7121,4 @@ var OGrid = class {
|
|
|
6639
7121
|
}
|
|
6640
7122
|
};
|
|
6641
7123
|
|
|
6642
|
-
export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CellDescriptorCache, ClipboardState, ColumnChooser, ColumnPinningState, ColumnReorderState, ColumnResizeState, ContextMenu, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, EventEmitter, FillHandleState, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GridState, HeaderFilter, HeaderFilterState, InlineCellEditor, KeyboardNavState, MAX_PAGE_BUTTONS, MarchingAntsOverlay, OGrid, OGridEventWiring, OGridRendering, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, PaginationControls, ROW_NUMBER_COLUMN_WIDTH, RowSelectionState, SIDEBAR_TRANSITION_MS, SelectionState, SideBar, SideBarState, StatusBar, TableLayoutState, TableRenderer, UndoRedoStack, UndoRedoState, VirtualScrollState, 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 };
|
|
7124
|
+
export { AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CellDescriptorCache, ClipboardState, ColumnChooser, ColumnPinningState, ColumnReorderState, ColumnResizeState, ContextMenu, DEFAULT_DEBOUNCE_MS, DEFAULT_MIN_COLUMN_WIDTH, EventEmitter, FillHandleState, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GridState, HeaderFilter, HeaderFilterState, InlineCellEditor, KeyboardNavState, MAX_PAGE_BUTTONS, MarchingAntsOverlay, OGrid, OGridEventWiring, OGridRendering, PAGE_SIZE_OPTIONS, PEOPLE_SEARCH_DEBOUNCE_MS, PaginationControls, ROW_NUMBER_COLUMN_WIDTH, RowSelectionState, SIDEBAR_TRANSITION_MS, SelectionState, SideBar, SideBarState, StatusBar, TableLayoutState, TableRenderer, UndoRedoStack, UndoRedoState, VirtualScrollState, 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 };
|
package/dist/styles/ogrid.css
CHANGED
|
@@ -329,8 +329,16 @@
|
|
|
329
329
|
border-bottom: 1px solid var(--ogrid-border, #e8e8e8);
|
|
330
330
|
position: relative;
|
|
331
331
|
color: var(--ogrid-fg, #242424);
|
|
332
|
+
contain: content;
|
|
332
333
|
}
|
|
333
334
|
|
|
335
|
+
/* Pinned columns need contain: none because contain breaks position: sticky */
|
|
336
|
+
.ogrid-table td[data-pinned='left'],
|
|
337
|
+
.ogrid-table td[data-pinned='right'] { contain: none; }
|
|
338
|
+
|
|
339
|
+
/* content-visibility: auto on rows for non-virtualized grids */
|
|
340
|
+
.ogrid-table:not([data-virtual-scroll]) tbody tr { content-visibility: auto; }
|
|
341
|
+
|
|
334
342
|
.ogrid-table tbody tr:last-child td {
|
|
335
343
|
border-bottom: none;
|
|
336
344
|
}
|
|
@@ -494,10 +502,11 @@
|
|
|
494
502
|
padding: 6px 12px;
|
|
495
503
|
box-sizing: border-box;
|
|
496
504
|
font-size: 12px;
|
|
505
|
+
line-height: 20px;
|
|
497
506
|
color: var(--ogrid-muted, #616161);
|
|
498
507
|
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
499
508
|
border-top: 1px solid var(--ogrid-border, #e0e0e0);
|
|
500
|
-
min-height:
|
|
509
|
+
min-height: 33px;
|
|
501
510
|
}
|
|
502
511
|
|
|
503
512
|
.ogrid-status-part {
|
|
@@ -28,6 +28,7 @@ export declare class GridState<T> {
|
|
|
28
28
|
private _ariaLabel?;
|
|
29
29
|
private _stickyHeader;
|
|
30
30
|
private _fullScreen;
|
|
31
|
+
private _workerSort;
|
|
31
32
|
private _filterOptions;
|
|
32
33
|
private _columnOrder;
|
|
33
34
|
private _visibleColsCache;
|
|
@@ -60,6 +61,16 @@ export declare class GridState<T> {
|
|
|
60
61
|
items: T[];
|
|
61
62
|
totalCount: number;
|
|
62
63
|
};
|
|
64
|
+
/** Whether worker sort should be used for the current data set. */
|
|
65
|
+
get useWorkerSort(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Async version of getProcessedItems that offloads sort/filter to a Web Worker.
|
|
68
|
+
* Falls back to sync when worker sort is not active.
|
|
69
|
+
*/
|
|
70
|
+
getProcessedItemsAsync(): Promise<{
|
|
71
|
+
items: T[];
|
|
72
|
+
totalCount: number;
|
|
73
|
+
}>;
|
|
63
74
|
private fetchServerData;
|
|
64
75
|
setData(data: T[]): void;
|
|
65
76
|
setPage(page: number): void;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type { IVirtualScrollConfig, IVisibleRange } from '@alaarab/ogrid-core';
|
|
1
|
+
import type { IVirtualScrollConfig, IVisibleRange, IVisibleColumnRange } from '@alaarab/ogrid-core';
|
|
2
2
|
interface VirtualScrollEvents extends Record<string, unknown> {
|
|
3
3
|
rangeChanged: {
|
|
4
4
|
visibleRange: IVisibleRange;
|
|
5
5
|
};
|
|
6
|
+
columnRangeChanged: {
|
|
7
|
+
columnRange: IVisibleColumnRange | null;
|
|
8
|
+
};
|
|
6
9
|
configChanged: {
|
|
7
10
|
config: IVirtualScrollConfig;
|
|
8
11
|
};
|
|
@@ -19,7 +22,15 @@ export declare class VirtualScrollState {
|
|
|
19
22
|
private _totalRows;
|
|
20
23
|
private rafId;
|
|
21
24
|
private _ro;
|
|
25
|
+
private _resizeRafId;
|
|
22
26
|
private _cachedRange;
|
|
27
|
+
private _scrollLeft;
|
|
28
|
+
private _scrollLeftRafId;
|
|
29
|
+
private _containerWidth;
|
|
30
|
+
private _columnWidths;
|
|
31
|
+
private _cachedColumnRange;
|
|
32
|
+
private _roWidth;
|
|
33
|
+
private _resizeWidthRafId;
|
|
23
34
|
constructor(config?: IVirtualScrollConfig);
|
|
24
35
|
/** Whether virtual scrolling is active (enabled + meets the row threshold). */
|
|
25
36
|
get enabled(): boolean;
|
|
@@ -31,6 +42,19 @@ export declare class VirtualScrollState {
|
|
|
31
42
|
get visibleRange(): IVisibleRange;
|
|
32
43
|
/** Get the total scrollable height for all rows. */
|
|
33
44
|
get totalHeight(): number;
|
|
45
|
+
/** Whether column virtualization is active. */
|
|
46
|
+
get columnVirtualizationEnabled(): boolean;
|
|
47
|
+
/** Get the current visible column range (null when column virtualization is disabled). */
|
|
48
|
+
get columnRange(): IVisibleColumnRange | null;
|
|
49
|
+
/** Set the unpinned column widths for horizontal virtualization. */
|
|
50
|
+
setColumnWidths(widths: number[]): void;
|
|
51
|
+
/** Handle horizontal scroll events. RAF-throttled. */
|
|
52
|
+
handleHorizontalScroll(scrollLeft: number): void;
|
|
53
|
+
/** Observe a container element for width changes (column virtualization). */
|
|
54
|
+
observeContainerWidth(el: HTMLElement): void;
|
|
55
|
+
private disconnectWidthObserver;
|
|
56
|
+
/** Recompute visible column range and emit if changed. */
|
|
57
|
+
private recomputeColumnRange;
|
|
34
58
|
/** Handle scroll events from the table container. RAF-throttled. */
|
|
35
59
|
handleScroll(scrollTop: number): void;
|
|
36
60
|
/** Scroll the container to bring a specific row into view. */
|
|
@@ -45,6 +69,7 @@ export declare class VirtualScrollState {
|
|
|
45
69
|
/** Recompute visible range and emit if changed. */
|
|
46
70
|
private recompute;
|
|
47
71
|
onRangeChanged(handler: (data: VirtualScrollEvents['rangeChanged']) => void): () => void;
|
|
72
|
+
onColumnRangeChanged(handler: (data: VirtualScrollEvents['columnRangeChanged']) => void): () => void;
|
|
48
73
|
onConfigChanged(handler: (data: VirtualScrollEvents['configChanged']) => void): () => void;
|
|
49
74
|
destroy(): void;
|
|
50
75
|
}
|
|
@@ -100,6 +100,13 @@ export interface OGridOptions<T> {
|
|
|
100
100
|
onFirstDataRendered?: () => void;
|
|
101
101
|
/** Virtual scrolling configuration. */
|
|
102
102
|
virtualScroll?: IVirtualScrollConfig;
|
|
103
|
+
/**
|
|
104
|
+
* Offload sorting to a Web Worker to avoid blocking the main thread.
|
|
105
|
+
* - `true`: always use worker sort
|
|
106
|
+
* - `'auto'`: use worker sort when data.length > 5000
|
|
107
|
+
* - `false` (default): use synchronous sort
|
|
108
|
+
*/
|
|
109
|
+
workerSort?: boolean | 'auto';
|
|
103
110
|
/** Fixed row height in pixels. Overrides default row height (36px). */
|
|
104
111
|
rowHeight?: number;
|
|
105
112
|
/** Cell spacing/density preset. Controls cell padding throughout the grid. Default: 'normal'. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "OGrid vanilla JS – framework-free data grid with sorting, filtering, pagination, and spreadsheet-style editing.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"node": ">=18"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@alaarab/ogrid-core": "2.
|
|
39
|
+
"@alaarab/ogrid-core": "2.2.0"
|
|
40
40
|
},
|
|
41
41
|
"sideEffects": [
|
|
42
42
|
"**/*.css"
|