@alaarab/ogrid-react 2.1.15 → 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 +384 -12
- package/dist/types/hooks/useDataGridTableOrchestration.d.ts +5 -0
- package/dist/types/hooks/useOGridDataFetching.d.ts +2 -0
- package/dist/types/hooks/useVirtualScroll.d.ts +14 -4
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/dataGridTypes.d.ts +8 -0
- package/dist/types/utils/index.d.ts +1 -1
- package/package.json +2 -2
package/dist/esm/index.js
CHANGED
|
@@ -715,6 +715,285 @@ function calculateDropTarget(params) {
|
|
|
715
715
|
}
|
|
716
716
|
return { targetIndex, indicatorX };
|
|
717
717
|
}
|
|
718
|
+
function computeVisibleColumnRange(scrollLeft, columnWidths, containerWidth, overscan = 2) {
|
|
719
|
+
if (columnWidths.length === 0 || containerWidth <= 0) {
|
|
720
|
+
return { startIndex: 0, endIndex: -1, leftOffset: 0, rightOffset: 0 };
|
|
721
|
+
}
|
|
722
|
+
let cumWidth = 0;
|
|
723
|
+
let rawStart = columnWidths.length;
|
|
724
|
+
let rawEnd = -1;
|
|
725
|
+
for (let i = 0; i < columnWidths.length; i++) {
|
|
726
|
+
const colStart = cumWidth;
|
|
727
|
+
cumWidth += columnWidths[i];
|
|
728
|
+
if (cumWidth > scrollLeft && rawStart === columnWidths.length) {
|
|
729
|
+
rawStart = i;
|
|
730
|
+
}
|
|
731
|
+
if (colStart < scrollLeft + containerWidth) {
|
|
732
|
+
rawEnd = i;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (rawStart > rawEnd) {
|
|
736
|
+
return { startIndex: 0, endIndex: -1, leftOffset: 0, rightOffset: 0 };
|
|
737
|
+
}
|
|
738
|
+
const startIndex = Math.max(0, rawStart - overscan);
|
|
739
|
+
const endIndex = Math.min(columnWidths.length - 1, rawEnd + overscan);
|
|
740
|
+
let leftOffset = 0;
|
|
741
|
+
for (let i = 0; i < startIndex; i++) {
|
|
742
|
+
leftOffset += columnWidths[i];
|
|
743
|
+
}
|
|
744
|
+
let rightOffset = 0;
|
|
745
|
+
for (let i = endIndex + 1; i < columnWidths.length; i++) {
|
|
746
|
+
rightOffset += columnWidths[i];
|
|
747
|
+
}
|
|
748
|
+
return { startIndex, endIndex, leftOffset, rightOffset };
|
|
749
|
+
}
|
|
750
|
+
function partitionColumnsForVirtualization(visibleCols, columnRange, pinnedColumns) {
|
|
751
|
+
const pinnedLeft = [];
|
|
752
|
+
const pinnedRight = [];
|
|
753
|
+
const unpinned = [];
|
|
754
|
+
for (const col of visibleCols) {
|
|
755
|
+
const pin = pinnedColumns?.[col.columnId];
|
|
756
|
+
if (pin === "left") pinnedLeft.push(col);
|
|
757
|
+
else if (pin === "right") pinnedRight.push(col);
|
|
758
|
+
else unpinned.push(col);
|
|
759
|
+
}
|
|
760
|
+
if (!columnRange || columnRange.endIndex < 0) {
|
|
761
|
+
return {
|
|
762
|
+
pinnedLeft,
|
|
763
|
+
virtualizedUnpinned: unpinned,
|
|
764
|
+
pinnedRight,
|
|
765
|
+
leftSpacerWidth: 0,
|
|
766
|
+
rightSpacerWidth: 0
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const virtualizedUnpinned = unpinned.slice(columnRange.startIndex, columnRange.endIndex + 1);
|
|
770
|
+
return {
|
|
771
|
+
pinnedLeft,
|
|
772
|
+
virtualizedUnpinned,
|
|
773
|
+
pinnedRight,
|
|
774
|
+
leftSpacerWidth: columnRange.leftOffset,
|
|
775
|
+
rightSpacerWidth: columnRange.rightOffset
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function workerBody() {
|
|
779
|
+
const ctx = self;
|
|
780
|
+
ctx.onmessage = (e) => {
|
|
781
|
+
const msg = e.data;
|
|
782
|
+
if (msg.type !== "sort-filter") return;
|
|
783
|
+
const { requestId, values, filters, sort } = msg;
|
|
784
|
+
const rowCount = values.length;
|
|
785
|
+
let indices = [];
|
|
786
|
+
const filterEntries = Object.entries(filters);
|
|
787
|
+
if (filterEntries.length === 0) {
|
|
788
|
+
indices = new Array(rowCount);
|
|
789
|
+
for (let i = 0; i < rowCount; i++) indices[i] = i;
|
|
790
|
+
} else {
|
|
791
|
+
for (let r = 0; r < rowCount; r++) {
|
|
792
|
+
let pass = true;
|
|
793
|
+
for (let f = 0; f < filterEntries.length; f++) {
|
|
794
|
+
const colIdx = Number(filterEntries[f][0]);
|
|
795
|
+
const filter = filterEntries[f][1];
|
|
796
|
+
const cellVal = values[r][colIdx];
|
|
797
|
+
switch (filter.type) {
|
|
798
|
+
case "text": {
|
|
799
|
+
const trimmed = filter.value.trim().toLowerCase();
|
|
800
|
+
if (trimmed && !String(cellVal ?? "").toLowerCase().includes(trimmed)) {
|
|
801
|
+
pass = false;
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case "multiSelect": {
|
|
806
|
+
if (filter.value.length > 0) {
|
|
807
|
+
const set = new Set(filter.value);
|
|
808
|
+
if (!set.has(String(cellVal ?? ""))) {
|
|
809
|
+
pass = false;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
case "date": {
|
|
815
|
+
if (cellVal == null) {
|
|
816
|
+
pass = false;
|
|
817
|
+
break;
|
|
818
|
+
}
|
|
819
|
+
const ts = new Date(String(cellVal)).getTime();
|
|
820
|
+
if (isNaN(ts)) {
|
|
821
|
+
pass = false;
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
if (filter.value.from) {
|
|
825
|
+
const fromTs = (/* @__PURE__ */ new Date(filter.value.from + "T00:00:00")).getTime();
|
|
826
|
+
if (ts < fromTs) {
|
|
827
|
+
pass = false;
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (filter.value.to) {
|
|
832
|
+
const toTs = (/* @__PURE__ */ new Date(filter.value.to + "T23:59:59.999")).getTime();
|
|
833
|
+
if (ts > toTs) {
|
|
834
|
+
pass = false;
|
|
835
|
+
break;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (!pass) break;
|
|
842
|
+
}
|
|
843
|
+
if (pass) indices.push(r);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
if (sort) {
|
|
847
|
+
const { columnIndex, direction } = sort;
|
|
848
|
+
const dir = direction === "asc" ? 1 : -1;
|
|
849
|
+
indices.sort((a, b) => {
|
|
850
|
+
const av = values[a][columnIndex];
|
|
851
|
+
const bv = values[b][columnIndex];
|
|
852
|
+
if (av == null && bv == null) return 0;
|
|
853
|
+
if (av == null) return -1 * dir;
|
|
854
|
+
if (bv == null) return 1 * dir;
|
|
855
|
+
if (typeof av === "number" && typeof bv === "number") {
|
|
856
|
+
return av === bv ? 0 : av > bv ? dir : -dir;
|
|
857
|
+
}
|
|
858
|
+
const sa = String(av).toLowerCase();
|
|
859
|
+
const sb = String(bv).toLowerCase();
|
|
860
|
+
return sa === sb ? 0 : sa > sb ? dir : -dir;
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
const response = {
|
|
864
|
+
type: "sort-filter-result",
|
|
865
|
+
requestId,
|
|
866
|
+
indices
|
|
867
|
+
};
|
|
868
|
+
ctx.postMessage(response);
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
var workerInstance = null;
|
|
872
|
+
var requestCounter = 0;
|
|
873
|
+
var pendingRequests = /* @__PURE__ */ new Map();
|
|
874
|
+
function createSortFilterWorker() {
|
|
875
|
+
if (workerInstance) return workerInstance;
|
|
876
|
+
if (typeof Worker === "undefined" || typeof Blob === "undefined" || typeof URL === "undefined") {
|
|
877
|
+
return null;
|
|
878
|
+
}
|
|
879
|
+
try {
|
|
880
|
+
const fnStr = workerBody.toString();
|
|
881
|
+
const blob = new Blob(
|
|
882
|
+
[`(${fnStr})()`],
|
|
883
|
+
{ type: "application/javascript" }
|
|
884
|
+
);
|
|
885
|
+
const url = URL.createObjectURL(blob);
|
|
886
|
+
workerInstance = new Worker(url);
|
|
887
|
+
URL.revokeObjectURL(url);
|
|
888
|
+
workerInstance.onmessage = (e) => {
|
|
889
|
+
const { requestId, indices } = e.data;
|
|
890
|
+
const pending = pendingRequests.get(requestId);
|
|
891
|
+
if (pending) {
|
|
892
|
+
pendingRequests.delete(requestId);
|
|
893
|
+
pending.resolve(indices);
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
workerInstance.onerror = (err) => {
|
|
897
|
+
for (const [id, pending] of pendingRequests) {
|
|
898
|
+
pending.reject(new Error(err.message || "Worker error"));
|
|
899
|
+
pendingRequests.delete(id);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
return workerInstance;
|
|
903
|
+
} catch {
|
|
904
|
+
return null;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
function extractValueMatrix(data, columns) {
|
|
908
|
+
const matrix = new Array(data.length);
|
|
909
|
+
for (let r = 0; r < data.length; r++) {
|
|
910
|
+
const row = new Array(columns.length);
|
|
911
|
+
for (let c = 0; c < columns.length; c++) {
|
|
912
|
+
const val = getCellValue(data[r], columns[c]);
|
|
913
|
+
if (val == null) {
|
|
914
|
+
row[c] = null;
|
|
915
|
+
} else if (typeof val === "string" || typeof val === "number" || typeof val === "boolean") {
|
|
916
|
+
row[c] = val;
|
|
917
|
+
} else {
|
|
918
|
+
row[c] = String(val);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
matrix[r] = row;
|
|
922
|
+
}
|
|
923
|
+
return matrix;
|
|
924
|
+
}
|
|
925
|
+
function processClientSideDataAsync(data, columns, filters, sortBy, sortDirection) {
|
|
926
|
+
if (sortBy) {
|
|
927
|
+
const sortCol = columns.find((c) => c.columnId === sortBy);
|
|
928
|
+
if (sortCol?.compare) {
|
|
929
|
+
return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
const worker = createSortFilterWorker();
|
|
933
|
+
if (!worker) {
|
|
934
|
+
return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
|
|
935
|
+
}
|
|
936
|
+
const columnIndexMap = /* @__PURE__ */ new Map();
|
|
937
|
+
for (let i = 0; i < columns.length; i++) {
|
|
938
|
+
columnIndexMap.set(columns[i].columnId, i);
|
|
939
|
+
}
|
|
940
|
+
const values = extractValueMatrix(data, columns);
|
|
941
|
+
const columnMeta = columns.map((col, idx) => ({
|
|
942
|
+
type: col.type ?? "text",
|
|
943
|
+
index: idx
|
|
944
|
+
}));
|
|
945
|
+
const workerFilters = {};
|
|
946
|
+
for (const col of columns) {
|
|
947
|
+
const filterKey = getFilterField(col);
|
|
948
|
+
const val = filters[filterKey];
|
|
949
|
+
if (!val) continue;
|
|
950
|
+
const colIdx = columnIndexMap.get(col.columnId);
|
|
951
|
+
if (colIdx === void 0) continue;
|
|
952
|
+
switch (val.type) {
|
|
953
|
+
case "text":
|
|
954
|
+
workerFilters[colIdx] = { type: "text", value: val.value };
|
|
955
|
+
break;
|
|
956
|
+
case "multiSelect":
|
|
957
|
+
workerFilters[colIdx] = { type: "multiSelect", value: val.value };
|
|
958
|
+
break;
|
|
959
|
+
case "date":
|
|
960
|
+
workerFilters[colIdx] = { type: "date", value: { from: val.value.from, to: val.value.to } };
|
|
961
|
+
break;
|
|
962
|
+
// 'people' filter has a UserLike object — fall back to sync
|
|
963
|
+
case "people":
|
|
964
|
+
return Promise.resolve(processClientSideData(data, columns, filters, sortBy, sortDirection));
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
let sort;
|
|
968
|
+
if (sortBy) {
|
|
969
|
+
const sortColIdx = columnIndexMap.get(sortBy);
|
|
970
|
+
if (sortColIdx !== void 0) {
|
|
971
|
+
sort = { columnIndex: sortColIdx, direction: sortDirection ?? "asc" };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const requestId = ++requestCounter;
|
|
975
|
+
return new Promise((resolve, reject) => {
|
|
976
|
+
pendingRequests.set(requestId, {
|
|
977
|
+
resolve: (indices) => {
|
|
978
|
+
const result = new Array(indices.length);
|
|
979
|
+
for (let i = 0; i < indices.length; i++) {
|
|
980
|
+
result[i] = data[indices[i]];
|
|
981
|
+
}
|
|
982
|
+
resolve(result);
|
|
983
|
+
},
|
|
984
|
+
reject
|
|
985
|
+
});
|
|
986
|
+
const request = {
|
|
987
|
+
type: "sort-filter",
|
|
988
|
+
requestId,
|
|
989
|
+
values,
|
|
990
|
+
columnMeta,
|
|
991
|
+
filters: workerFilters,
|
|
992
|
+
sort
|
|
993
|
+
};
|
|
994
|
+
worker.postMessage(request);
|
|
995
|
+
});
|
|
996
|
+
}
|
|
718
997
|
function getHeaderFilterConfig(col, input) {
|
|
719
998
|
const filterable = isFilterConfig(col.filterable) ? col.filterable : null;
|
|
720
999
|
const filterType = filterable?.type ?? "none";
|
|
@@ -1719,11 +1998,13 @@ function useOGridDataFetching(params) {
|
|
|
1719
1998
|
page,
|
|
1720
1999
|
pageSize,
|
|
1721
2000
|
onError,
|
|
1722
|
-
onFirstDataRendered
|
|
2001
|
+
onFirstDataRendered,
|
|
2002
|
+
workerSort
|
|
1723
2003
|
} = params;
|
|
1724
2004
|
const isClientSide = !isServerSide;
|
|
2005
|
+
const useWorker = workerSort === true || workerSort === "auto" && displayData.length > 5e3;
|
|
1725
2006
|
const clientItemsAndTotal = useMemo(() => {
|
|
1726
|
-
if (!isClientSide) return null;
|
|
2007
|
+
if (!isClientSide || useWorker) return null;
|
|
1727
2008
|
const rows = processClientSideData(
|
|
1728
2009
|
displayData,
|
|
1729
2010
|
columns,
|
|
@@ -1735,7 +2016,29 @@ function useOGridDataFetching(params) {
|
|
|
1735
2016
|
const start = (page - 1) * pageSize;
|
|
1736
2017
|
const paged = rows.slice(start, start + pageSize);
|
|
1737
2018
|
return { items: paged, totalCount: total };
|
|
1738
|
-
}, [isClientSide, displayData, columns, stableFilters, sort.field, sort.direction, page, pageSize]);
|
|
2019
|
+
}, [isClientSide, useWorker, displayData, columns, stableFilters, sort.field, sort.direction, page, pageSize]);
|
|
2020
|
+
const [asyncItems, setAsyncItems] = useState(null);
|
|
2021
|
+
const asyncIdRef = useRef(0);
|
|
2022
|
+
useEffect(() => {
|
|
2023
|
+
if (!isClientSide || !useWorker) {
|
|
2024
|
+
setAsyncItems(null);
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
const id = ++asyncIdRef.current;
|
|
2028
|
+
processClientSideDataAsync(
|
|
2029
|
+
displayData,
|
|
2030
|
+
columns,
|
|
2031
|
+
stableFilters,
|
|
2032
|
+
sort.field,
|
|
2033
|
+
sort.direction
|
|
2034
|
+
).then((rows) => {
|
|
2035
|
+
if (id !== asyncIdRef.current) return;
|
|
2036
|
+
const total = rows.length;
|
|
2037
|
+
const start = (page - 1) * pageSize;
|
|
2038
|
+
const paged = rows.slice(start, start + pageSize);
|
|
2039
|
+
setAsyncItems({ items: paged, totalCount: total });
|
|
2040
|
+
});
|
|
2041
|
+
}, [isClientSide, useWorker, displayData, columns, stableFilters, sort.field, sort.direction, page, pageSize]);
|
|
1739
2042
|
const [serverItems, setServerItems] = useState([]);
|
|
1740
2043
|
const [serverTotalCount, setServerTotalCount] = useState(0);
|
|
1741
2044
|
const [serverLoading, setServerLoading] = useState(true);
|
|
@@ -1769,8 +2072,9 @@ function useOGridDataFetching(params) {
|
|
|
1769
2072
|
if (id === fetchIdRef.current) setServerLoading(false);
|
|
1770
2073
|
});
|
|
1771
2074
|
}, [isServerSide, page, pageSize, sort.field, sort.direction, stableFilters, refreshCounter, dataSourceRef, onErrorRef]);
|
|
1772
|
-
const
|
|
1773
|
-
const
|
|
2075
|
+
const clientResult = clientItemsAndTotal ?? asyncItems;
|
|
2076
|
+
const displayItems = isClientSide && clientResult ? clientResult.items : serverItems;
|
|
2077
|
+
const displayTotalCount = isClientSide && clientResult ? clientResult.totalCount : serverTotalCount;
|
|
1774
2078
|
const onFirstDataRenderedRef = useLatestRef(onFirstDataRendered);
|
|
1775
2079
|
const firstDataRenderedRef = useRef(false);
|
|
1776
2080
|
useEffect(() => {
|
|
@@ -1877,6 +2181,7 @@ function useOGrid(props, ref) {
|
|
|
1877
2181
|
virtualScroll,
|
|
1878
2182
|
rowHeight,
|
|
1879
2183
|
density = "normal",
|
|
2184
|
+
workerSort,
|
|
1880
2185
|
"aria-label": ariaLabel,
|
|
1881
2186
|
"aria-labelledby": ariaLabelledBy
|
|
1882
2187
|
} = props;
|
|
@@ -1950,7 +2255,8 @@ function useOGrid(props, ref) {
|
|
|
1950
2255
|
page: paginationState.page,
|
|
1951
2256
|
pageSize: paginationState.pageSize,
|
|
1952
2257
|
onError,
|
|
1953
|
-
onFirstDataRendered
|
|
2258
|
+
onFirstDataRendered,
|
|
2259
|
+
workerSort
|
|
1954
2260
|
});
|
|
1955
2261
|
useEffect(() => {
|
|
1956
2262
|
const items = dataFetchingState.displayItems;
|
|
@@ -2008,7 +2314,13 @@ function useOGrid(props, ref) {
|
|
|
2008
2314
|
[selectedRows, onSelectionChange]
|
|
2009
2315
|
);
|
|
2010
2316
|
const [columnWidthOverrides, setColumnWidthOverrides] = useState({});
|
|
2011
|
-
const [pinnedOverrides, setPinnedOverrides] = useState({
|
|
2317
|
+
const [pinnedOverrides, setPinnedOverrides] = useState(() => {
|
|
2318
|
+
const initial = {};
|
|
2319
|
+
for (const col of flattenColumns(columnsProp)) {
|
|
2320
|
+
if (col.pinned) initial[col.columnId] = col.pinned;
|
|
2321
|
+
}
|
|
2322
|
+
return initial;
|
|
2323
|
+
});
|
|
2012
2324
|
const handleColumnResized = useCallback(
|
|
2013
2325
|
(columnId, width) => {
|
|
2014
2326
|
setColumnWidthOverrides((prev) => ({ ...prev, [columnId]: width }));
|
|
@@ -6023,7 +6335,10 @@ function useVirtualScroll(params) {
|
|
|
6023
6335
|
enabled,
|
|
6024
6336
|
overscan = 5,
|
|
6025
6337
|
threshold = DEFAULT_PASSTHROUGH_THRESHOLD,
|
|
6026
|
-
containerRef
|
|
6338
|
+
containerRef,
|
|
6339
|
+
columnVirtualization = false,
|
|
6340
|
+
columnWidths,
|
|
6341
|
+
columnOverscan = 2
|
|
6027
6342
|
} = params;
|
|
6028
6343
|
useEffect(() => {
|
|
6029
6344
|
validateVirtualScrollConfig({ enabled, rowHeight });
|
|
@@ -6082,11 +6397,51 @@ function useVirtualScroll(params) {
|
|
|
6082
6397
|
},
|
|
6083
6398
|
[isActive, containerRef, rowHeight]
|
|
6084
6399
|
);
|
|
6400
|
+
const [scrollLeft, setScrollLeft] = useState(0);
|
|
6401
|
+
const scrollLeftRaf = useRef(0);
|
|
6402
|
+
const onHorizontalScroll = useCallback(
|
|
6403
|
+
(sl) => {
|
|
6404
|
+
if (scrollLeftRaf.current) cancelAnimationFrame(scrollLeftRaf.current);
|
|
6405
|
+
scrollLeftRaf.current = requestAnimationFrame(() => {
|
|
6406
|
+
scrollLeftRaf.current = 0;
|
|
6407
|
+
setScrollLeft(sl);
|
|
6408
|
+
});
|
|
6409
|
+
},
|
|
6410
|
+
[]
|
|
6411
|
+
);
|
|
6412
|
+
useEffect(() => {
|
|
6413
|
+
return () => {
|
|
6414
|
+
if (scrollLeftRaf.current) cancelAnimationFrame(scrollLeftRaf.current);
|
|
6415
|
+
};
|
|
6416
|
+
}, []);
|
|
6417
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
6418
|
+
useEffect(() => {
|
|
6419
|
+
if (!columnVirtualization) return;
|
|
6420
|
+
const el = containerRef.current;
|
|
6421
|
+
if (!el) return;
|
|
6422
|
+
setContainerWidth(el.clientWidth);
|
|
6423
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
6424
|
+
const ro = new ResizeObserver((entries) => {
|
|
6425
|
+
if (entries.length > 0) {
|
|
6426
|
+
setContainerWidth(entries[0].contentRect.width);
|
|
6427
|
+
}
|
|
6428
|
+
});
|
|
6429
|
+
ro.observe(el);
|
|
6430
|
+
return () => ro.disconnect();
|
|
6431
|
+
}, [columnVirtualization, containerRef]);
|
|
6432
|
+
const columnRange = useMemo(() => {
|
|
6433
|
+
if (!columnVirtualization || !columnWidths || columnWidths.length === 0 || containerWidth <= 0) {
|
|
6434
|
+
return null;
|
|
6435
|
+
}
|
|
6436
|
+
return computeVisibleColumnRange(scrollLeft, columnWidths, containerWidth, columnOverscan);
|
|
6437
|
+
}, [columnVirtualization, columnWidths, containerWidth, scrollLeft, columnOverscan]);
|
|
6085
6438
|
return {
|
|
6086
6439
|
virtualizer: isActive ? virtualizer : null,
|
|
6087
6440
|
totalHeight,
|
|
6088
6441
|
visibleRange: activeRange,
|
|
6089
|
-
scrollToIndex
|
|
6442
|
+
scrollToIndex,
|
|
6443
|
+
columnRange,
|
|
6444
|
+
onHorizontalScroll: columnVirtualization ? onHorizontalScroll : void 0
|
|
6090
6445
|
};
|
|
6091
6446
|
}
|
|
6092
6447
|
function useListVirtualizer(opts) {
|
|
@@ -6197,13 +6552,28 @@ function useDataGridTableOrchestration(params) {
|
|
|
6197
6552
|
});
|
|
6198
6553
|
const virtualScrollEnabled = virtualScroll?.enabled === true;
|
|
6199
6554
|
const virtualRowHeight = virtualScroll?.rowHeight ?? 36;
|
|
6200
|
-
const
|
|
6555
|
+
const columnVirtualization = virtualScroll?.columns === true;
|
|
6556
|
+
const unpinnedColumnWidths = useMemo(() => {
|
|
6557
|
+
if (!columnVirtualization) return void 0;
|
|
6558
|
+
const widths = [];
|
|
6559
|
+
for (const col of visibleCols) {
|
|
6560
|
+
const pin = pinnedColumns?.[col.columnId];
|
|
6561
|
+
if (!pin) {
|
|
6562
|
+
widths.push(getColumnWidth(col));
|
|
6563
|
+
}
|
|
6564
|
+
}
|
|
6565
|
+
return widths;
|
|
6566
|
+
}, [columnVirtualization, visibleCols, pinnedColumns, getColumnWidth]);
|
|
6567
|
+
const { visibleRange, columnRange, onHorizontalScroll } = useVirtualScroll({
|
|
6201
6568
|
totalRows: items.length,
|
|
6202
6569
|
rowHeight: virtualRowHeight,
|
|
6203
6570
|
enabled: virtualScrollEnabled,
|
|
6204
6571
|
overscan: virtualScroll?.overscan,
|
|
6205
6572
|
threshold: virtualScroll?.threshold,
|
|
6206
|
-
containerRef: wrapperRef
|
|
6573
|
+
containerRef: wrapperRef,
|
|
6574
|
+
columnVirtualization,
|
|
6575
|
+
columnWidths: unpinnedColumnWidths,
|
|
6576
|
+
columnOverscan: virtualScroll?.columnOverscan
|
|
6207
6577
|
});
|
|
6208
6578
|
const editCallbacks = useMemo(
|
|
6209
6579
|
() => ({ commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit }),
|
|
@@ -6259,6 +6629,8 @@ function useDataGridTableOrchestration(params) {
|
|
|
6259
6629
|
virtualScrollEnabled,
|
|
6260
6630
|
virtualRowHeight,
|
|
6261
6631
|
visibleRange,
|
|
6632
|
+
columnRange,
|
|
6633
|
+
onHorizontalScroll,
|
|
6262
6634
|
// Derived from props
|
|
6263
6635
|
items,
|
|
6264
6636
|
columns,
|
|
@@ -7561,4 +7933,4 @@ function renderFilterContent(filterType, state, options, isLoadingOptions, selec
|
|
|
7561
7933
|
return null;
|
|
7562
7934
|
}
|
|
7563
7935
|
|
|
7564
|
-
export { BaseColumnHeaderMenu, BaseDropIndicator, BaseEmptyState, BaseInlineCellEditor, BaseLoadingOverlay, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CURSOR_CELL_STYLE, CellDescriptorCache, CellErrorBoundary, DEFAULT_MIN_COLUMN_WIDTH, DateFilterContent, EmptyState, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GRID_ROOT_STYLE, GridContextMenu, MAX_PAGE_BUTTONS, MarchingAntsOverlay, NOOP3 as NOOP, OGridLayout, PAGE_SIZE_OPTIONS, POPOVER_ANCHOR_STYLE, PREVENT_DEFAULT, ROW_NUMBER_COLUMN_WIDTH, STOP_PROPAGATION, SideBar, StatusBar, UndoRedoStack, areGridRowPropsEqual, booleanParser, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps2 as buildInlineEditorProps, buildPopoverEditorProps2 as buildPopoverEditorProps, clampSelectionToBounds, computeAggregations, computeAutoScrollSpeed, computeTabNavigation, createOGrid, currencyParser, dateParser, deriveFilterOptionsFromData, editorInputStyle, editorWrapperStyle, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellInteractionProps, getCellRenderDescriptor, getCellValue, getColumnHeaderFilterStateParams, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getDateFilterContentProps, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getStatusBarParts, isInSelectionRange, isRowInRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, processClientSideData, rangesEqual, renderFilterContent, resolveCellDisplayContent2 as resolveCellDisplayContent, resolveCellStyle2 as resolveCellStyle, richSelectDropdownStyle, richSelectNoMatchesStyle, richSelectOptionHighlightedStyle, richSelectOptionStyle, richSelectWrapperStyle, selectChevronStyle, selectDisplayStyle, selectEditorStyle, toUserLike, triggerCsvDownload, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnMeta, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableOrchestration, useDateFilterState, useDebounce, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useLatestRef, useListVirtualizer, useMultiSelectFilterState, useOGrid, usePaginationControls, usePeopleFilterState, useRichSelectState, useRowSelection, useSelectState, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll };
|
|
7936
|
+
export { BaseColumnHeaderMenu, BaseDropIndicator, BaseEmptyState, BaseInlineCellEditor, BaseLoadingOverlay, CELL_PADDING, CHECKBOX_COLUMN_WIDTH, COLUMN_HEADER_MENU_ITEMS, CURSOR_CELL_STYLE, CellDescriptorCache, CellErrorBoundary, DEFAULT_MIN_COLUMN_WIDTH, DateFilterContent, EmptyState, GRID_BORDER_RADIUS, GRID_CONTEXT_MENU_ITEMS, GRID_ROOT_STYLE, GridContextMenu, MAX_PAGE_BUTTONS, MarchingAntsOverlay, NOOP3 as NOOP, OGridLayout, PAGE_SIZE_OPTIONS, POPOVER_ANCHOR_STYLE, PREVENT_DEFAULT, ROW_NUMBER_COLUMN_WIDTH, STOP_PROPAGATION, SideBar, StatusBar, UndoRedoStack, areGridRowPropsEqual, booleanParser, buildCsvHeader, buildCsvRows, buildHeaderRows, buildInlineEditorProps2 as buildInlineEditorProps, buildPopoverEditorProps2 as buildPopoverEditorProps, clampSelectionToBounds, computeAggregations, computeAutoScrollSpeed, computeTabNavigation, createOGrid, currencyParser, dateParser, deriveFilterOptionsFromData, editorInputStyle, editorWrapperStyle, emailParser, escapeCsvValue, exportToCsv, findCtrlArrowTarget, flattenColumns, formatCellValueForTsv, formatSelectionAsTsv, formatShortcut, getCellInteractionProps, getCellRenderDescriptor, getCellValue, getColumnHeaderFilterStateParams, getColumnHeaderMenuItems, getContextMenuHandlers, getDataGridStatusBarConfig, getDateFilterContentProps, getFilterField, getHeaderFilterConfig, getMultiSelectFilterFields, getPaginationViewModel, getStatusBarParts, isInSelectionRange, isRowInRange, mergeFilter, normalizeSelectionRange, numberParser, parseTsvClipboard, parseValue, partitionColumnsForVirtualization, processClientSideData, rangesEqual, renderFilterContent, resolveCellDisplayContent2 as resolveCellDisplayContent, resolveCellStyle2 as resolveCellStyle, richSelectDropdownStyle, richSelectNoMatchesStyle, richSelectOptionHighlightedStyle, richSelectOptionStyle, richSelectWrapperStyle, selectChevronStyle, selectDisplayStyle, selectEditorStyle, toUserLike, triggerCsvDownload, useActiveCell, useCellEditing, useCellSelection, useClipboard, useColumnChooserState, useColumnHeaderFilterState, useColumnMeta, useColumnReorder, useColumnResize, useContextMenu, useDataGridState, useDataGridTableOrchestration, useDateFilterState, useDebounce, useFillHandle, useFilterOptions, useInlineCellEditorState, useKeyboardNavigation, useLatestRef, useListVirtualizer, useMultiSelectFilterState, useOGrid, usePaginationControls, usePeopleFilterState, useRichSelectState, useRowSelection, useSelectState, useSideBarState, useTableLayout, useTextFilterState, useUndoRedo, useVirtualScroll };
|
|
@@ -4,6 +4,7 @@ import type { DataGridLayoutState, DataGridRowSelectionState, DataGridEditingSta
|
|
|
4
4
|
import type { UseColumnResizeResult } from './useColumnResize';
|
|
5
5
|
import type { UseColumnReorderResult } from './useColumnReorder';
|
|
6
6
|
import type { UseVirtualScrollResult } from './useVirtualScroll';
|
|
7
|
+
import type { IVisibleColumnRange } from '@alaarab/ogrid-core';
|
|
7
8
|
import type { HeaderFilterConfigInput, CellRenderDescriptorInput } from '../utils';
|
|
8
9
|
import type { IStatusBarProps, RowId, HeaderRow } from '../types';
|
|
9
10
|
import { CellDescriptorCache } from '@alaarab/ogrid-core';
|
|
@@ -32,6 +33,10 @@ export interface UseDataGridTableOrchestrationResult<T> {
|
|
|
32
33
|
virtualScrollEnabled: boolean;
|
|
33
34
|
virtualRowHeight: number;
|
|
34
35
|
visibleRange: UseVirtualScrollResult['visibleRange'];
|
|
36
|
+
/** Visible column range for horizontal virtualization (null when disabled). */
|
|
37
|
+
columnRange: IVisibleColumnRange | null;
|
|
38
|
+
/** Callback for horizontal scroll events (column virtualization). */
|
|
39
|
+
onHorizontalScroll?: (scrollLeft: number) => void;
|
|
35
40
|
items: T[];
|
|
36
41
|
columns: IOGridDataGridProps<T>['columns'];
|
|
37
42
|
getRowId: IOGridDataGridProps<T>['getRowId'];
|
|
@@ -14,6 +14,8 @@ export interface UseOGridDataFetchingParams<T> {
|
|
|
14
14
|
pageSize: number;
|
|
15
15
|
onError?: (err: unknown) => void;
|
|
16
16
|
onFirstDataRendered?: () => void;
|
|
17
|
+
/** Worker sort mode: true=always, 'auto'=when data > 5000 rows, false=sync. */
|
|
18
|
+
workerSort?: boolean | 'auto';
|
|
17
19
|
}
|
|
18
20
|
export interface UseOGridDataFetchingState<T> {
|
|
19
21
|
displayItems: T[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Virtualizer } from '@tanstack/react-virtual';
|
|
2
2
|
import type { RefObject } from 'react';
|
|
3
|
-
import type { IVisibleRange } from '@alaarab/ogrid-core';
|
|
3
|
+
import type { IVisibleRange, IVisibleColumnRange } from '@alaarab/ogrid-core';
|
|
4
4
|
export type { IVirtualScrollConfig } from '@alaarab/ogrid-core';
|
|
5
5
|
export interface UseVirtualScrollParams {
|
|
6
6
|
/** Total number of rows in the data set. */
|
|
@@ -18,6 +18,12 @@ export interface UseVirtualScrollParams {
|
|
|
18
18
|
threshold?: number;
|
|
19
19
|
/** Ref to the scrollable container element. */
|
|
20
20
|
containerRef: RefObject<HTMLElement | null>;
|
|
21
|
+
/** Enable column virtualization (only render visible columns). */
|
|
22
|
+
columnVirtualization?: boolean;
|
|
23
|
+
/** Column widths for horizontal virtualization (unpinned columns only). */
|
|
24
|
+
columnWidths?: number[];
|
|
25
|
+
/** Column overscan count. Default: 2. */
|
|
26
|
+
columnOverscan?: number;
|
|
21
27
|
}
|
|
22
28
|
export interface UseVirtualScrollResult {
|
|
23
29
|
/** The TanStack virtualizer instance (null when disabled). */
|
|
@@ -28,11 +34,15 @@ export interface UseVirtualScrollResult {
|
|
|
28
34
|
visibleRange: IVisibleRange;
|
|
29
35
|
/** Scroll to a specific row index. */
|
|
30
36
|
scrollToIndex: (index: number) => void;
|
|
37
|
+
/** Visible column range for horizontal virtualization (null when column virtualization disabled). */
|
|
38
|
+
columnRange: IVisibleColumnRange | null;
|
|
39
|
+
/** Callback to attach to scroll container's onScroll for horizontal tracking. */
|
|
40
|
+
onHorizontalScroll?: (scrollLeft: number) => void;
|
|
31
41
|
}
|
|
32
42
|
/**
|
|
33
|
-
* Wraps TanStack Virtual for row virtualization.
|
|
43
|
+
* Wraps TanStack Virtual for row virtualization, with optional column virtualization.
|
|
34
44
|
* When disabled or when totalRows < threshold, returns a pass-through (all rows visible).
|
|
35
|
-
* @param params - Total rows, row height, enabled flag, overscan, threshold, and
|
|
36
|
-
* @returns Virtualizer instance, total height, visible range, and
|
|
45
|
+
* @param params - Total rows, row height, enabled flag, overscan, threshold, container ref, and column virtualization params.
|
|
46
|
+
* @returns Virtualizer instance, total height, visible range, scrollToIndex, columnRange, and onHorizontalScroll.
|
|
37
47
|
*/
|
|
38
48
|
export declare function useVirtualScroll(params: UseVirtualScrollParams): UseVirtualScrollResult;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { BaseDropIndicator } from './components/BaseDropIndicator';
|
|
|
32
32
|
export type { BaseDropIndicatorProps } from './components/BaseDropIndicator';
|
|
33
33
|
export { DateFilterContent, getColumnHeaderFilterStateParams, getDateFilterContentProps, } from './components/ColumnHeaderFilterContent';
|
|
34
34
|
export type { IColumnHeaderFilterProps, DateFilterContentProps, DateFilterClassNames, } from './components/ColumnHeaderFilterContent';
|
|
35
|
-
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, CellDescriptorCache, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, areGridRowPropsEqual, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, UndoRedoStack, } from './utils';
|
|
35
|
+
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, getHeaderFilterConfig, getCellRenderDescriptor, CellDescriptorCache, isRowInRange, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, partitionColumnsForVirtualization, areGridRowPropsEqual, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, UndoRedoStack, } from './utils';
|
|
36
36
|
export type { CsvColumn, StatusBarPart, StatusBarPartsInput, GridContextMenuItem, GridContextMenuHandlerProps, PaginationViewModel, HeaderFilterConfigInput, HeaderFilterConfig, CellRenderDescriptorInput, CellRenderDescriptor, CellRenderMode, CellInteractionHandlers, ParseValueResult, AggregationResult, GridRowComparatorProps, IColumnHeaderMenuItem, ColumnHeaderMenuInput, ColumnHeaderMenuHandlers, } from './utils';
|
|
37
37
|
export { renderFilterContent } from './components/ColumnHeaderFilterRenderers';
|
|
38
38
|
export type { FilterContentRenderers, MultiSelectRendererProps, TextRendererProps, PeopleRendererProps, DateRendererProps, } from './components/ColumnHeaderFilterRenderers';
|
|
@@ -80,6 +80,14 @@ interface IOGridBaseProps<T> {
|
|
|
80
80
|
rowHeight?: number;
|
|
81
81
|
/** Cell spacing/density preset. Controls cell padding throughout the grid. Default: 'normal'. */
|
|
82
82
|
density?: 'compact' | 'normal' | 'comfortable';
|
|
83
|
+
/**
|
|
84
|
+
* Offload sorting to a Web Worker to avoid blocking the main thread.
|
|
85
|
+
* - `true`: always use worker sort
|
|
86
|
+
* - `'auto'`: use worker sort when data.length > 5000
|
|
87
|
+
* - `false` (default): use synchronous sort
|
|
88
|
+
* Columns with custom `compare` functions fall back to synchronous sort.
|
|
89
|
+
*/
|
|
90
|
+
workerSort?: boolean | 'auto';
|
|
83
91
|
/** Fires once when the grid first renders with data (useful for restoring column state). */
|
|
84
92
|
onFirstDataRendered?: () => void;
|
|
85
93
|
/** Called when server-side fetchPage fails. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, computeNextSortState, measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, applyFillValues, computeArrowNavigation, applyCellDeletion, applyRangeRowSelection, computeRowSelectionState, UndoRedoStack, buildCellIndex, } from '@alaarab/ogrid-core';
|
|
1
|
+
export { escapeCsvValue, buildCsvHeader, buildCsvRows, exportToCsv, triggerCsvDownload, getCellValue, flattenColumns, buildHeaderRows, getFilterField, mergeFilter, deriveFilterOptionsFromData, getMultiSelectFilterFields, getStatusBarParts, getDataGridStatusBarConfig, getPaginationViewModel, PAGE_SIZE_OPTIONS, MAX_PAGE_BUTTONS, GRID_CONTEXT_MENU_ITEMS, COLUMN_HEADER_MENU_ITEMS, getContextMenuHandlers, getColumnHeaderMenuItems, formatShortcut, parseValue, numberParser, currencyParser, dateParser, emailParser, booleanParser, computeAggregations, processClientSideData, partitionColumnsForVirtualization, computeNextSortState, measureColumnContentWidth, AUTOSIZE_EXTRA_PX, AUTOSIZE_MAX_PX, findCtrlArrowTarget, computeTabNavigation, rangesEqual, clampSelectionToBounds, computeAutoScrollSpeed, formatCellValueForTsv, formatSelectionAsTsv, parseTsvClipboard, applyPastedValues, applyCutClear, applyFillValues, computeArrowNavigation, applyCellDeletion, applyRangeRowSelection, computeRowSelectionState, UndoRedoStack, buildCellIndex, } from '@alaarab/ogrid-core';
|
|
2
2
|
export type { CsvColumn, StatusBarPart, StatusBarPartsInput, GridContextMenuItem, GridContextMenuHandlerProps, PaginationViewModel, ParseValueResult, AggregationResult, IColumnHeaderMenuItem, ColumnHeaderMenuInput, ColumnHeaderMenuHandlers, ArrowNavigationContext, ArrowNavigationResult, } from '@alaarab/ogrid-core';
|
|
3
3
|
export { getHeaderFilterConfig, getCellRenderDescriptor, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from './dataGridViewModel';
|
|
4
4
|
export { CellDescriptorCache } from '@alaarab/ogrid-core';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "OGrid React – React hooks, headless components, and utilities for OGrid data grids.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"node": ">=18"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@alaarab/ogrid-core": "2.
|
|
42
|
+
"@alaarab/ogrid-core": "2.2.0",
|
|
43
43
|
"@tanstack/react-virtual": "^3.0.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|