@extend-ai/react-xlsx 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4220,13 +4220,36 @@ function parseWorkbookTableMetadata(archive, workbookSheets) {
4220
4220
  }
4221
4221
  return [{
4222
4222
  displayName: tableNode.getAttribute("displayName") ?? void 0,
4223
+ headerRowCount: parseWorkbookTableCount(tableNode.getAttribute("headerRowCount"), 1),
4223
4224
  headerRowCellStyle: tableNode.getAttribute("headerRowCellStyle") ?? void 0,
4224
4225
  name: tableNode.getAttribute("name") ?? void 0,
4225
- reference: tableNode.getAttribute("ref") ?? void 0
4226
+ reference: tableNode.getAttribute("ref") ?? void 0,
4227
+ totalsRowCount: parseWorkbookTableCount(tableNode.getAttribute("totalsRowCount"), 0),
4228
+ totalsRowShown: parseWorkbookTableBoolean(tableNode.getAttribute("totalsRowShown"), false)
4226
4229
  }];
4227
4230
  });
4228
4231
  });
4229
4232
  }
4233
+ function parseWorkbookTableCount(value, fallback) {
4234
+ if (value === null) {
4235
+ return fallback;
4236
+ }
4237
+ const parsed = Number.parseInt(value, 10);
4238
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
4239
+ }
4240
+ function parseWorkbookTableBoolean(value, fallback) {
4241
+ if (value === null) {
4242
+ return fallback;
4243
+ }
4244
+ const normalized = value.trim().toLowerCase();
4245
+ if (normalized === "0" || normalized === "false" || normalized === "") {
4246
+ return false;
4247
+ }
4248
+ if (normalized === "1" || normalized === "true") {
4249
+ return true;
4250
+ }
4251
+ return fallback;
4252
+ }
4230
4253
  function parseSqrefRanges(sqref) {
4231
4254
  if (!sqref) {
4232
4255
  return [];
@@ -6832,17 +6855,18 @@ function rangeContainsCell(range, cell) {
6832
6855
  function mapWorksheetTables(worksheet, metadataForSheet) {
6833
6856
  const rawTables = worksheet?.tables ?? [];
6834
6857
  return rawTables.flatMap((table, index) => {
6835
- const reference = typeof table.reference === "string" ? table.reference : "";
6836
- const parsedRange = parseA1RangeReference2(reference);
6837
- if (!parsedRange) {
6838
- return [];
6839
- }
6840
6858
  const rawColumns = Array.isArray(table.columns) ? table.columns : [];
6841
6859
  const rawName = typeof table.name === "string" ? table.name : `Table${index + 1}`;
6842
6860
  const rawDisplayName = typeof table.displayName === "string" ? table.displayName : typeof table.name === "string" ? table.name : `Table ${index + 1}`;
6843
6861
  const metadata = metadataForSheet?.find(
6844
- (entry) => entry.name && entry.name === rawName || entry.displayName && entry.displayName === rawDisplayName || entry.reference && entry.reference === reference
6862
+ (entry) => entry.name && entry.name === rawName || entry.displayName && entry.displayName === rawDisplayName || entry.reference && entry.reference === table.reference
6845
6863
  );
6864
+ const rawReference = typeof table.reference === "string" ? table.reference : "";
6865
+ const reference = metadata?.reference ?? rawReference;
6866
+ const parsedRange = parseA1RangeReference2(reference);
6867
+ if (!parsedRange) {
6868
+ return [];
6869
+ }
6846
6870
  return [{
6847
6871
  columns: rawColumns.map((column, columnIndex) => ({
6848
6872
  id: typeof column.id === "number" ? column.id ?? columnIndex + 1 : columnIndex + 1,
@@ -6851,17 +6875,47 @@ function mapWorksheetTables(worksheet, metadataForSheet) {
6851
6875
  })),
6852
6876
  displayName: rawDisplayName,
6853
6877
  end: parsedRange.end,
6854
- headerRowCount: typeof table.headerRowCount === "number" ? table.headerRowCount : 1,
6878
+ headerRowCount: metadata?.headerRowCount ?? resolveWorkbookTableCount(table.headerRowCount, 1),
6855
6879
  headerRowCellStyle: metadata?.headerRowCellStyle,
6856
6880
  name: rawName,
6857
6881
  reference,
6858
6882
  start: parsedRange.start,
6859
6883
  styleInfo: table.styleInfo,
6860
- totalsRowCount: typeof table.totalsRowCount === "number" ? table.totalsRowCount : 0,
6861
- totalsRowShown: Boolean(table.totalsRowShown)
6884
+ totalsRowCount: metadata?.totalsRowCount ?? resolveWorkbookTableCount(table.totalsRowCount, 0),
6885
+ totalsRowShown: metadata?.totalsRowShown ?? resolveWorkbookTableBoolean(table.totalsRowShown)
6862
6886
  }];
6863
6887
  });
6864
6888
  }
6889
+ function resolveWorkbookTableCount(value, fallback) {
6890
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
6891
+ return value;
6892
+ }
6893
+ if (typeof value === "string") {
6894
+ const parsed = Number.parseInt(value, 10);
6895
+ if (Number.isFinite(parsed) && parsed >= 0) {
6896
+ return parsed;
6897
+ }
6898
+ }
6899
+ return fallback;
6900
+ }
6901
+ function resolveWorkbookTableBoolean(value) {
6902
+ if (typeof value === "boolean") {
6903
+ return value;
6904
+ }
6905
+ if (typeof value === "number") {
6906
+ return value !== 0;
6907
+ }
6908
+ if (typeof value === "string") {
6909
+ const normalized = value.trim().toLowerCase();
6910
+ if (normalized === "0" || normalized === "false" || normalized === "") {
6911
+ return false;
6912
+ }
6913
+ if (normalized === "1" || normalized === "true") {
6914
+ return true;
6915
+ }
6916
+ }
6917
+ return false;
6918
+ }
6865
6919
  function fileStem(fileName) {
6866
6920
  const normalized = fileName.trim();
6867
6921
  const lastDot = normalized.lastIndexOf(".");
@@ -6992,7 +7046,17 @@ async function resolveWorkbookBuffer({ file, src }, signal) {
6992
7046
  if (file) {
6993
7047
  buffer = file;
6994
7048
  } else if (src) {
6995
- const response = await fetch(src, { signal });
7049
+ let response;
7050
+ try {
7051
+ response = await fetch(src, { signal });
7052
+ } catch (error) {
7053
+ if (isAbortError(error)) {
7054
+ throw error;
7055
+ }
7056
+ throw new Error(
7057
+ "Failed to fetch workbook. The remote URL may be blocked by CORS, unavailable, or not directly downloadable from the browser."
7058
+ );
7059
+ }
6996
7060
  if (!response.ok) {
6997
7061
  throw new Error(`Failed to fetch workbook (status ${response.status})`);
6998
7062
  }
@@ -7548,6 +7612,7 @@ function downloadUrl(src, fileName) {
7548
7612
  }
7549
7613
  function useXlsxViewerController(options) {
7550
7614
  const {
7615
+ allowResizeInReadOnly = false,
7551
7616
  deferLoadingAboveBytes = DEFAULT_DEFER_LOADING_ABOVE_BYTES,
7552
7617
  file,
7553
7618
  fileName,
@@ -7601,6 +7666,7 @@ function useXlsxViewerController(options) {
7601
7666
  const displayFileName = React.useMemo(() => resolveDisplayFileName(src, fileName), [fileName, src]);
7602
7667
  const shouldDeferLoading = deferLoadingAboveBytes > 0;
7603
7668
  const readOnly = requestedReadOnly || forcedReadOnly;
7669
+ const canResizeReadOnly = requestedReadOnly && allowResizeInReadOnly && !forcedReadOnly;
7604
7670
  const workerSupported = useWorker && typeof Worker !== "undefined";
7605
7671
  const shouldUseWorker = workerSupported && forcedReadOnly;
7606
7672
  const shouldForceReadOnlyForBuffer = React.useCallback((bufferByteLength) => !requestedReadOnly && readOnlyAboveBytes > 0 && bufferByteLength > readOnlyAboveBytes, [readOnlyAboveBytes, requestedReadOnly]);
@@ -8895,7 +8961,7 @@ function useXlsxViewerController(options) {
8895
8961
  refreshWorkbookState(workbook);
8896
8962
  }, [refreshWorkbookState, workbook]);
8897
8963
  const resizeColumn = React.useCallback((col, widthPx) => {
8898
- if (readOnly || !workbook || !activeSheet) {
8964
+ if (readOnly && !canResizeReadOnly || !workbook || !activeSheet) {
8899
8965
  return;
8900
8966
  }
8901
8967
  recordHistoryBeforeMutation();
@@ -8905,9 +8971,9 @@ function useXlsxViewerController(options) {
8905
8971
  pxToSheetColumnWidth(resolveContentSheetAxisPixels(widthPx, activeSheet.showGridLines))
8906
8972
  );
8907
8973
  refreshWorkbookState(workbook);
8908
- }, [activeSheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8974
+ }, [activeSheet, canResizeReadOnly, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8909
8975
  const resizeRow = React.useCallback((row, heightPx) => {
8910
- if (readOnly || !workbook || !activeSheet) {
8976
+ if (readOnly && !canResizeReadOnly || !workbook || !activeSheet) {
8911
8977
  return;
8912
8978
  }
8913
8979
  recordHistoryBeforeMutation();
@@ -8917,7 +8983,7 @@ function useXlsxViewerController(options) {
8917
8983
  pxToSheetRowHeight(resolveContentSheetAxisPixels(heightPx, activeSheet.showGridLines))
8918
8984
  );
8919
8985
  refreshWorkbookState(workbook);
8920
- }, [activeSheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8986
+ }, [activeSheet, canResizeReadOnly, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8921
8987
  const resolveAnchoredObjectRect = React.useCallback((anchor, worksheet) => {
8922
8988
  const resolveAxisSum = (index, getSize) => {
8923
8989
  let total = 0;
@@ -19912,6 +19978,109 @@ function resolveSelectionColors({
19912
19978
  stroke
19913
19979
  };
19914
19980
  }
19981
+ function buildFullCanvasDirtyRect(width, height) {
19982
+ if (width <= 0 || height <= 0) {
19983
+ return [];
19984
+ }
19985
+ return [{
19986
+ height,
19987
+ left: 0,
19988
+ top: 0,
19989
+ width
19990
+ }];
19991
+ }
19992
+ function intersectsCanvasDirtyRects(left, top, width, height, dirtyRects) {
19993
+ if (dirtyRects.length === 0 || width <= 0 || height <= 0) {
19994
+ return false;
19995
+ }
19996
+ const right = left + width;
19997
+ const bottom = top + height;
19998
+ return dirtyRects.some((dirtyRect) => left < dirtyRect.left + dirtyRect.width && right > dirtyRect.left && top < dirtyRect.top + dirtyRect.height && bottom > dirtyRect.top);
19999
+ }
20000
+ function blitCanvasWithScrollDelta(canvas, context, bufferCanvas, dpr, width, height, deltaX, deltaY) {
20001
+ if (width <= 0 || height <= 0) {
20002
+ return [];
20003
+ }
20004
+ const clampedDeltaX = Math.max(-width, Math.min(width, deltaX));
20005
+ const clampedDeltaY = Math.max(-height, Math.min(height, deltaY));
20006
+ if (Math.abs(clampedDeltaX) < 0.01 && Math.abs(clampedDeltaY) < 0.01) {
20007
+ return [];
20008
+ }
20009
+ if (Math.abs(clampedDeltaX) >= width || Math.abs(clampedDeltaY) >= height) {
20010
+ return null;
20011
+ }
20012
+ const deviceWidth = Math.max(1, Math.round(width * dpr));
20013
+ const deviceHeight = Math.max(1, Math.round(height * dpr));
20014
+ if (bufferCanvas.width !== deviceWidth) {
20015
+ bufferCanvas.width = deviceWidth;
20016
+ }
20017
+ if (bufferCanvas.height !== deviceHeight) {
20018
+ bufferCanvas.height = deviceHeight;
20019
+ }
20020
+ const bufferContext = bufferCanvas.getContext("2d");
20021
+ if (!bufferContext) {
20022
+ return null;
20023
+ }
20024
+ bufferContext.setTransform(1, 0, 0, 1, 0, 0);
20025
+ bufferContext.clearRect(0, 0, deviceWidth, deviceHeight);
20026
+ bufferContext.drawImage(canvas, 0, 0);
20027
+ const deltaDeviceX = clampedDeltaX * dpr;
20028
+ const deltaDeviceY = clampedDeltaY * dpr;
20029
+ const overlapDeviceWidth = deviceWidth - Math.abs(deltaDeviceX);
20030
+ const overlapDeviceHeight = deviceHeight - Math.abs(deltaDeviceY);
20031
+ if (overlapDeviceWidth <= 0 || overlapDeviceHeight <= 0) {
20032
+ return null;
20033
+ }
20034
+ const sourceX = deltaDeviceX > 0 ? deltaDeviceX : 0;
20035
+ const sourceY = deltaDeviceY > 0 ? deltaDeviceY : 0;
20036
+ const destinationX = deltaDeviceX > 0 ? 0 : -deltaDeviceX;
20037
+ const destinationY = deltaDeviceY > 0 ? 0 : -deltaDeviceY;
20038
+ context.setTransform(1, 0, 0, 1, 0, 0);
20039
+ context.drawImage(
20040
+ bufferCanvas,
20041
+ sourceX,
20042
+ sourceY,
20043
+ overlapDeviceWidth,
20044
+ overlapDeviceHeight,
20045
+ destinationX,
20046
+ destinationY,
20047
+ overlapDeviceWidth,
20048
+ overlapDeviceHeight
20049
+ );
20050
+ context.setTransform(dpr, 0, 0, dpr, 0, 0);
20051
+ const dirtyRects = [];
20052
+ if (clampedDeltaX > 0) {
20053
+ dirtyRects.push({
20054
+ height,
20055
+ left: Math.max(0, width - clampedDeltaX),
20056
+ top: 0,
20057
+ width: Math.min(width, clampedDeltaX)
20058
+ });
20059
+ } else if (clampedDeltaX < 0) {
20060
+ dirtyRects.push({
20061
+ height,
20062
+ left: 0,
20063
+ top: 0,
20064
+ width: Math.min(width, -clampedDeltaX)
20065
+ });
20066
+ }
20067
+ if (clampedDeltaY > 0) {
20068
+ dirtyRects.push({
20069
+ height: Math.min(height, clampedDeltaY),
20070
+ left: 0,
20071
+ top: Math.max(0, height - clampedDeltaY),
20072
+ width
20073
+ });
20074
+ } else if (clampedDeltaY < 0) {
20075
+ dirtyRects.push({
20076
+ height: Math.min(height, -clampedDeltaY),
20077
+ left: 0,
20078
+ top: 0,
20079
+ width
20080
+ });
20081
+ }
20082
+ return dirtyRects;
20083
+ }
19915
20084
  function buildConditionalFormatRuleKey(rule) {
19916
20085
  return `${rule.kind}:${rule.priority}:${rule.ranges.map((range) => `${range.start.row}:${range.start.col}-${range.end.row}:${range.end.col}`).join("|")}`;
19917
20086
  }
@@ -20771,8 +20940,20 @@ function XlsxGrid({
20771
20940
  const topBodyCanvasRef = React4.useRef(null);
20772
20941
  const leftBodyCanvasRef = React4.useRef(null);
20773
20942
  const cornerBodyCanvasRef = React4.useRef(null);
20774
- const topHeaderCanvasRef = React4.useRef(null);
20775
- const leftHeaderCanvasRef = React4.useRef(null);
20943
+ const bodyBlitBufferCanvasRef = React4.useRef({
20944
+ corner: null,
20945
+ left: null,
20946
+ scroll: null,
20947
+ top: null
20948
+ });
20949
+ const headerBlitBufferCanvasRef = React4.useRef({
20950
+ left: null,
20951
+ top: null
20952
+ });
20953
+ const topFrozenHeaderCanvasRef = React4.useRef(null);
20954
+ const topScrollHeaderCanvasRef = React4.useRef(null);
20955
+ const leftFrozenHeaderCanvasRef = React4.useRef(null);
20956
+ const leftScrollHeaderCanvasRef = React4.useRef(null);
20776
20957
  const cornerHeaderCanvasRef = React4.useRef(null);
20777
20958
  const selectionOverlayRef = React4.useRef(null);
20778
20959
  const activeValidationOverlayRef = React4.useRef(null);
@@ -20976,16 +21157,31 @@ function XlsxGrid({
20976
21157
  liveZoomTranslateX2,
20977
21158
  liveZoomTranslateY2
20978
21159
  );
20979
- const topHeaderCanvas = topHeaderCanvasRef.current;
20980
- if (topHeaderCanvas) {
20981
- topHeaderCanvas.style.transform = scrollDeltaX !== 0 ? `translate3d(${scrollDeltaX}px, 0, 0)` : "";
20982
- topHeaderCanvas.style.willChange = scrollDeltaX !== 0 ? "transform" : "";
20983
- }
20984
- const leftHeaderCanvas = leftHeaderCanvasRef.current;
20985
- if (leftHeaderCanvas) {
20986
- leftHeaderCanvas.style.transform = scrollDeltaY !== 0 ? `translate3d(0, ${scrollDeltaY}px, 0)` : "";
20987
- leftHeaderCanvas.style.willChange = scrollDeltaY !== 0 ? "transform" : "";
20988
- }
21160
+ applyCanvasTransform(
21161
+ topScrollHeaderCanvasRef.current,
21162
+ scrollDeltaX + liveZoomTranslateX2,
21163
+ liveZoomTranslateY2
21164
+ );
21165
+ applyCanvasTransform(
21166
+ topFrozenHeaderCanvasRef.current,
21167
+ liveZoomTranslateX2,
21168
+ liveZoomTranslateY2
21169
+ );
21170
+ applyCanvasTransform(
21171
+ leftScrollHeaderCanvasRef.current,
21172
+ liveZoomTranslateX2,
21173
+ scrollDeltaY + liveZoomTranslateY2
21174
+ );
21175
+ applyCanvasTransform(
21176
+ leftFrozenHeaderCanvasRef.current,
21177
+ liveZoomTranslateX2,
21178
+ liveZoomTranslateY2
21179
+ );
21180
+ applyCanvasTransform(
21181
+ cornerHeaderCanvasRef.current,
21182
+ liveZoomTranslateX2,
21183
+ liveZoomTranslateY2
21184
+ );
20989
21185
  }, [zoomScale]);
20990
21186
  const updateLiveGestureZoomState = React4.useCallback((nextState) => {
20991
21187
  const resolvedState = typeof nextState === "function" ? nextState(liveGestureZoomRef.current) : nextState;
@@ -21116,6 +21312,22 @@ function XlsxGrid({
21116
21312
  scrollRef.current.style.cursor = "";
21117
21313
  }
21118
21314
  }, []);
21315
+ const getBodyBlitBufferCanvas = React4.useCallback((pane) => {
21316
+ let bufferCanvas = bodyBlitBufferCanvasRef.current[pane];
21317
+ if (!bufferCanvas && typeof document !== "undefined") {
21318
+ bufferCanvas = document.createElement("canvas");
21319
+ bodyBlitBufferCanvasRef.current[pane] = bufferCanvas;
21320
+ }
21321
+ return bufferCanvas;
21322
+ }, []);
21323
+ const getHeaderBlitBufferCanvas = React4.useCallback((axis) => {
21324
+ let bufferCanvas = headerBlitBufferCanvasRef.current[axis];
21325
+ if (!bufferCanvas && typeof document !== "undefined") {
21326
+ bufferCanvas = document.createElement("canvas");
21327
+ headerBlitBufferCanvasRef.current[axis] = bufferCanvas;
21328
+ }
21329
+ return bufferCanvas;
21330
+ }, []);
21119
21331
  const visibleRows = React4.useMemo(() => {
21120
21332
  return buildVisibleAxisIndices(
21121
21333
  activeSheet?.visibleRows ?? [],
@@ -21830,14 +22042,14 @@ function XlsxGrid({
21830
22042
  const handleScrollerScroll = React4.useCallback((event) => {
21831
22043
  const currentScroller = event.currentTarget;
21832
22044
  cachedScrollerRectRef.current = null;
21833
- syncDrawingViewport(currentScroller, { immediate: !experimentalCanvas });
22045
+ syncDrawingViewport(currentScroller, { immediate: true });
21834
22046
  if (currentScroller.scrollHeight - (currentScroller.scrollTop + currentScroller.clientHeight) < OPEN_GRID_VERTICAL_EDGE_PX) {
21835
22047
  setDisplayRowLimit((current) => current + OPEN_GRID_ROW_GROWTH);
21836
22048
  }
21837
22049
  if (currentScroller.scrollWidth - (currentScroller.scrollLeft + currentScroller.clientWidth) < OPEN_GRID_HORIZONTAL_EDGE_PX) {
21838
22050
  setDisplayColLimit((current) => current + OPEN_GRID_COL_GROWTH);
21839
22051
  }
21840
- }, [experimentalCanvas, syncDrawingViewport]);
22052
+ }, [syncDrawingViewport]);
21841
22053
  React4.useEffect(() => {
21842
22054
  const scroller = scrollRef.current;
21843
22055
  if (!scroller || !enableGestureZoom) {
@@ -23702,6 +23914,54 @@ function XlsxGrid({
23702
23914
  rowPrefixSums,
23703
23915
  stickyTopByRow
23704
23916
  ]);
23917
+ const canvasColumnHeaderCells = React4.useMemo(
23918
+ () => canvasVisibleColItems.flatMap((column) => {
23919
+ const rect = resolveCanvasColumnHeaderRect(column.actualCol);
23920
+ if (!rect) {
23921
+ return [];
23922
+ }
23923
+ const isFrozen = stickyLeftByCol.has(column.actualCol);
23924
+ return [{
23925
+ actualCol: column.actualCol,
23926
+ height: displayHeaderHeight,
23927
+ isFrozen,
23928
+ left: rect.left,
23929
+ localLeft: rect.left - (isFrozen ? displayRowHeaderWidth : frozenPaneRight),
23930
+ width: rect.width
23931
+ }];
23932
+ }),
23933
+ [
23934
+ canvasVisibleColItems,
23935
+ displayHeaderHeight,
23936
+ displayRowHeaderWidth,
23937
+ frozenPaneRight,
23938
+ resolveCanvasColumnHeaderRect,
23939
+ stickyLeftByCol
23940
+ ]
23941
+ );
23942
+ const canvasRowHeaderCells = React4.useMemo(
23943
+ () => canvasVisibleRowItems.flatMap((row) => {
23944
+ const rect = resolveCanvasRowHeaderRect(row.actualRow);
23945
+ if (!rect) {
23946
+ return [];
23947
+ }
23948
+ const isFrozen = stickyTopByRow.has(row.actualRow);
23949
+ return [{
23950
+ actualRow: row.actualRow,
23951
+ height: rect.height,
23952
+ isFrozen,
23953
+ localTop: rect.top - (isFrozen ? displayHeaderHeight : frozenPaneBottom),
23954
+ top: rect.top
23955
+ }];
23956
+ }),
23957
+ [
23958
+ canvasVisibleRowItems,
23959
+ displayHeaderHeight,
23960
+ frozenPaneBottom,
23961
+ resolveCanvasRowHeaderRect,
23962
+ stickyTopByRow
23963
+ ]
23964
+ );
23705
23965
  const resolveCanvasColumnResizeTarget = React4.useCallback((clientX) => {
23706
23966
  if (!canResizeHeaders) {
23707
23967
  return null;
@@ -23711,17 +23971,26 @@ function XlsxGrid({
23711
23971
  return null;
23712
23972
  }
23713
23973
  const localX = clientX - scrollerRect.left;
23714
- for (const column of canvasVisibleColItems) {
23715
- const rect = resolveCanvasColumnHeaderRect(column.actualCol);
23716
- if (!rect) {
23717
- continue;
23974
+ for (const column of canvasColumnHeaderCells) {
23975
+ if (Math.abs(localX - (column.left + column.width)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
23976
+ return { actualCol: column.actualCol, width: column.width };
23718
23977
  }
23719
- if (Math.abs(localX - (rect.left + rect.width)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
23720
- return { actualCol: column.actualCol, width: rect.width };
23978
+ }
23979
+ return null;
23980
+ }, [canResizeHeaders, canvasColumnHeaderCells]);
23981
+ const resolveCanvasColumnHeaderTarget = React4.useCallback((clientX) => {
23982
+ const scrollerRect = scrollRef.current?.getBoundingClientRect();
23983
+ if (!scrollerRect) {
23984
+ return null;
23985
+ }
23986
+ const localX = clientX - scrollerRect.left;
23987
+ for (const column of canvasColumnHeaderCells) {
23988
+ if (localX >= column.left && localX <= column.left + column.width) {
23989
+ return column.actualCol;
23721
23990
  }
23722
23991
  }
23723
23992
  return null;
23724
- }, [canResizeHeaders, canvasVisibleColItems, resolveCanvasColumnHeaderRect]);
23993
+ }, [canvasColumnHeaderCells]);
23725
23994
  const resolveCanvasRowResizeTarget = React4.useCallback((clientY) => {
23726
23995
  if (!canResizeHeaders) {
23727
23996
  return null;
@@ -23731,17 +24000,26 @@ function XlsxGrid({
23731
24000
  return null;
23732
24001
  }
23733
24002
  const localY = clientY - scrollerRect.top;
23734
- for (const row of canvasVisibleRowItems) {
23735
- const rect = resolveCanvasRowHeaderRect(row.actualRow);
23736
- if (!rect) {
23737
- continue;
24003
+ for (const row of canvasRowHeaderCells) {
24004
+ if (Math.abs(localY - (row.top + row.height)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
24005
+ return { actualRow: row.actualRow, height: row.height };
23738
24006
  }
23739
- if (Math.abs(localY - (rect.top + rect.height)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
23740
- return { actualRow: row.actualRow, height: rect.height };
24007
+ }
24008
+ return null;
24009
+ }, [canResizeHeaders, canvasRowHeaderCells]);
24010
+ const resolveCanvasRowHeaderTarget = React4.useCallback((clientY) => {
24011
+ const scrollerRect = scrollRef.current?.getBoundingClientRect();
24012
+ if (!scrollerRect) {
24013
+ return null;
24014
+ }
24015
+ const localY = clientY - scrollerRect.top;
24016
+ for (const row of canvasRowHeaderCells) {
24017
+ if (localY >= row.top && localY <= row.top + row.height) {
24018
+ return row.actualRow;
23741
24019
  }
23742
24020
  }
23743
24021
  return null;
23744
- }, [canResizeHeaders, canvasVisibleRowItems, resolveCanvasRowHeaderRect]);
24022
+ }, [canvasRowHeaderCells]);
23745
24023
  const handleCanvasColumnHeaderPointerMove = React4.useCallback((event) => {
23746
24024
  if (resizeStateRef.current?.type === "column") {
23747
24025
  event.currentTarget.style.cursor = "col-resize";
@@ -23844,17 +24122,17 @@ function XlsxGrid({
23844
24122
  startColumnResize(event.pointerId, resizeTarget.actualCol, resizeTarget.width, event.clientX);
23845
24123
  return;
23846
24124
  }
23847
- const cell = resolvePointerCellFromClient(event.clientX, event.clientY);
23848
- if (!cell) {
24125
+ const actualCol = resolveCanvasColumnHeaderTarget(event.clientX);
24126
+ if (actualCol === null) {
23849
24127
  return;
23850
24128
  }
23851
24129
  event.preventDefault();
23852
24130
  focusGrid();
23853
24131
  const currentSelection = selectionRef.current;
23854
- const anchorCol = event.shiftKey && currentSelection ? currentSelection.start.col : cell.col;
24132
+ const anchorCol = event.shiftKey && currentSelection ? currentSelection.start.col : actualCol;
23855
24133
  const initialRange = normalizeRange2({
23856
24134
  start: { row: firstVisibleRow, col: anchorCol },
23857
- end: { row: lastVisibleRow, col: cell.col }
24135
+ end: { row: lastVisibleRow, col: actualCol }
23858
24136
  });
23859
24137
  const anchorColIndex = colIndexByActual.get(anchorCol);
23860
24138
  if (anchorColIndex === void 0) {
@@ -23864,7 +24142,7 @@ function XlsxGrid({
23864
24142
  event.pointerId,
23865
24143
  { row: firstVisibleRow, col: anchorCol },
23866
24144
  "column",
23867
- { row: firstVisibleRow, col: cell.col },
24145
+ { row: firstVisibleRow, col: actualCol },
23868
24146
  {
23869
24147
  contentScaleX: 1,
23870
24148
  contentScaleY: 1,
@@ -23885,9 +24163,8 @@ function XlsxGrid({
23885
24163
  firstVisibleRow,
23886
24164
  focusGrid,
23887
24165
  lastVisibleRow,
23888
- resolveCanvasColumnHeaderRect,
24166
+ resolveCanvasColumnHeaderTarget,
23889
24167
  resolveCanvasColumnResizeTarget,
23890
- resolvePointerCellFromClient,
23891
24168
  rowPrefixSums,
23892
24169
  startCellSelection
23893
24170
  ]);
@@ -23902,17 +24179,17 @@ function XlsxGrid({
23902
24179
  startRowResize(event.pointerId, resizeTarget.actualRow, resizeTarget.height, event.clientY);
23903
24180
  return;
23904
24181
  }
23905
- const cell = resolvePointerCellFromClient(event.clientX, event.clientY);
23906
- if (!cell) {
24182
+ const actualRow = resolveCanvasRowHeaderTarget(event.clientY);
24183
+ if (actualRow === null) {
23907
24184
  return;
23908
24185
  }
23909
24186
  event.preventDefault();
23910
24187
  focusGrid();
23911
24188
  const currentSelection = selectionRef.current;
23912
- const anchorRow = event.shiftKey && currentSelection ? currentSelection.start.row : cell.row;
24189
+ const anchorRow = event.shiftKey && currentSelection ? currentSelection.start.row : actualRow;
23913
24190
  const initialRange = normalizeRange2({
23914
24191
  start: { row: anchorRow, col: firstVisibleCol },
23915
- end: { row: cell.row, col: lastVisibleCol }
24192
+ end: { row: actualRow, col: lastVisibleCol }
23916
24193
  });
23917
24194
  const anchorRowIndex = rowIndexByActual.get(anchorRow);
23918
24195
  if (anchorRowIndex === void 0) {
@@ -23922,7 +24199,7 @@ function XlsxGrid({
23922
24199
  event.pointerId,
23923
24200
  { row: anchorRow, col: firstVisibleCol },
23924
24201
  "row",
23925
- { row: cell.row, col: firstVisibleCol },
24202
+ { row: actualRow, col: firstVisibleCol },
23926
24203
  {
23927
24204
  contentScaleX: 1,
23928
24205
  contentScaleY: 1,
@@ -23942,8 +24219,8 @@ function XlsxGrid({
23942
24219
  firstVisibleCol,
23943
24220
  focusGrid,
23944
24221
  lastVisibleCol,
24222
+ resolveCanvasRowHeaderTarget,
23945
24223
  resolveCanvasRowResizeTarget,
23946
- resolvePointerCellFromClient,
23947
24224
  rowIndexByActual,
23948
24225
  rowPrefixSums,
23949
24226
  startCellSelection
@@ -23953,7 +24230,7 @@ function XlsxGrid({
23953
24230
  return;
23954
24231
  }
23955
24232
  const dpr = typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
23956
- function configureCanvas(canvas, width, height) {
24233
+ function configureCanvas(canvas, width, height, options) {
23957
24234
  if (!canvas) {
23958
24235
  return null;
23959
24236
  }
@@ -23976,7 +24253,9 @@ function XlsxGrid({
23976
24253
  return null;
23977
24254
  }
23978
24255
  context.setTransform(dpr, 0, 0, dpr, 0, 0);
23979
- context.clearRect(0, 0, width, height);
24256
+ if (options?.clear !== false) {
24257
+ context.clearRect(0, 0, width, height);
24258
+ }
23980
24259
  return context;
23981
24260
  }
23982
24261
  const bodyWidth = Math.max(0, drawingViewport.width);
@@ -24008,12 +24287,24 @@ function XlsxGrid({
24008
24287
  rangeSignature,
24009
24288
  rowHeaderWidth,
24010
24289
  rowSignature: nextBodyCanvasSignature.rowSignature,
24290
+ viewportLeft: drawingViewport.left,
24291
+ viewportTop: drawingViewport.top,
24011
24292
  zoomFactor
24012
24293
  };
24013
24294
  const previousBodyCanvasSignature = paintedBodyCanvasSignatureRef.current;
24014
24295
  const previousHeaderCanvasSignature = paintedHeaderCanvasSignatureRef.current;
24296
+ const previousPaintedViewport = paintedDrawingViewportRef.current;
24015
24297
  const shouldRepaintBody = !(previousBodyCanvasSignature && previousBodyCanvasSignature.activeSheet === nextBodyCanvasSignature.activeSheet && previousBodyCanvasSignature.bodyHeight === nextBodyCanvasSignature.bodyHeight && previousBodyCanvasSignature.bodyWidth === nextBodyCanvasSignature.bodyWidth && previousBodyCanvasSignature.colSignature === nextBodyCanvasSignature.colSignature && previousBodyCanvasSignature.getCellData === nextBodyCanvasSignature.getCellData && previousBodyCanvasSignature.headerHeight === nextBodyCanvasSignature.headerHeight && previousBodyCanvasSignature.palette === nextBodyCanvasSignature.palette && previousBodyCanvasSignature.rowHeaderWidth === nextBodyCanvasSignature.rowHeaderWidth && previousBodyCanvasSignature.rowSignature === nextBodyCanvasSignature.rowSignature && previousBodyCanvasSignature.zoomFactor === nextBodyCanvasSignature.zoomFactor);
24016
- const shouldRepaintHeaders = !(previousHeaderCanvasSignature && previousHeaderCanvasSignature.activeSheet === nextHeaderCanvasSignature.activeSheet && previousHeaderCanvasSignature.bodyHeight === nextHeaderCanvasSignature.bodyHeight && previousHeaderCanvasSignature.bodyWidth === nextHeaderCanvasSignature.bodyWidth && previousHeaderCanvasSignature.colSignature === nextHeaderCanvasSignature.colSignature && previousHeaderCanvasSignature.headerHeight === nextHeaderCanvasSignature.headerHeight && previousHeaderCanvasSignature.palette === nextHeaderCanvasSignature.palette && previousHeaderCanvasSignature.rangeSignature === nextHeaderCanvasSignature.rangeSignature && previousHeaderCanvasSignature.rowHeaderWidth === nextHeaderCanvasSignature.rowHeaderWidth && previousHeaderCanvasSignature.rowSignature === nextHeaderCanvasSignature.rowSignature && previousHeaderCanvasSignature.zoomFactor === nextHeaderCanvasSignature.zoomFactor);
24298
+ const shouldRepaintHeaders = !(previousHeaderCanvasSignature && previousHeaderCanvasSignature.activeSheet === nextHeaderCanvasSignature.activeSheet && previousHeaderCanvasSignature.bodyHeight === nextHeaderCanvasSignature.bodyHeight && previousHeaderCanvasSignature.bodyWidth === nextHeaderCanvasSignature.bodyWidth && previousHeaderCanvasSignature.colSignature === nextHeaderCanvasSignature.colSignature && previousHeaderCanvasSignature.headerHeight === nextHeaderCanvasSignature.headerHeight && previousHeaderCanvasSignature.palette === nextHeaderCanvasSignature.palette && previousHeaderCanvasSignature.rangeSignature === nextHeaderCanvasSignature.rangeSignature && previousHeaderCanvasSignature.rowHeaderWidth === nextHeaderCanvasSignature.rowHeaderWidth && previousHeaderCanvasSignature.rowSignature === nextHeaderCanvasSignature.rowSignature && previousHeaderCanvasSignature.viewportLeft === nextHeaderCanvasSignature.viewportLeft && previousHeaderCanvasSignature.viewportTop === nextHeaderCanvasSignature.viewportTop && previousHeaderCanvasSignature.zoomFactor === nextHeaderCanvasSignature.zoomFactor);
24299
+ const canBlitBody = Boolean(
24300
+ shouldRepaintBody && previousBodyCanvasSignature && previousBodyCanvasSignature.activeSheet === nextBodyCanvasSignature.activeSheet && previousBodyCanvasSignature.bodyHeight === nextBodyCanvasSignature.bodyHeight && previousBodyCanvasSignature.bodyWidth === nextBodyCanvasSignature.bodyWidth && previousBodyCanvasSignature.getCellData === nextBodyCanvasSignature.getCellData && previousBodyCanvasSignature.headerHeight === nextBodyCanvasSignature.headerHeight && previousBodyCanvasSignature.palette === nextBodyCanvasSignature.palette && previousBodyCanvasSignature.rowHeaderWidth === nextBodyCanvasSignature.rowHeaderWidth && previousBodyCanvasSignature.zoomFactor === nextBodyCanvasSignature.zoomFactor
24301
+ );
24302
+ const canBlitTopHeader = Boolean(
24303
+ shouldRepaintHeaders && previousHeaderCanvasSignature && previousHeaderCanvasSignature.activeSheet === nextHeaderCanvasSignature.activeSheet && previousHeaderCanvasSignature.bodyHeight === nextHeaderCanvasSignature.bodyHeight && previousHeaderCanvasSignature.bodyWidth === nextHeaderCanvasSignature.bodyWidth && previousHeaderCanvasSignature.headerHeight === nextHeaderCanvasSignature.headerHeight && previousHeaderCanvasSignature.palette === nextHeaderCanvasSignature.palette && previousHeaderCanvasSignature.rangeSignature === nextHeaderCanvasSignature.rangeSignature && previousHeaderCanvasSignature.rowHeaderWidth === nextHeaderCanvasSignature.rowHeaderWidth && previousHeaderCanvasSignature.zoomFactor === nextHeaderCanvasSignature.zoomFactor
24304
+ );
24305
+ const canBlitLeftHeader = Boolean(
24306
+ shouldRepaintHeaders && previousHeaderCanvasSignature && previousHeaderCanvasSignature.activeSheet === nextHeaderCanvasSignature.activeSheet && previousHeaderCanvasSignature.bodyHeight === nextHeaderCanvasSignature.bodyHeight && previousHeaderCanvasSignature.bodyWidth === nextHeaderCanvasSignature.bodyWidth && previousHeaderCanvasSignature.headerHeight === nextHeaderCanvasSignature.headerHeight && previousHeaderCanvasSignature.palette === nextHeaderCanvasSignature.palette && previousHeaderCanvasSignature.rangeSignature === nextHeaderCanvasSignature.rangeSignature && previousHeaderCanvasSignature.rowHeaderWidth === nextHeaderCanvasSignature.rowHeaderWidth && previousHeaderCanvasSignature.zoomFactor === nextHeaderCanvasSignature.zoomFactor
24307
+ );
24017
24308
  if (!shouldRepaintBody && !shouldRepaintHeaders) {
24018
24309
  return;
24019
24310
  }
@@ -24025,6 +24316,10 @@ function XlsxGrid({
24025
24316
  const leftBodyCanvasHeight2 = scrollBodyCanvasHeight2;
24026
24317
  const cornerBodyCanvasWidth2 = leftBodyCanvasWidth2;
24027
24318
  const cornerBodyCanvasHeight2 = topBodyCanvasHeight2;
24319
+ const topFrozenHeaderCanvasWidth2 = leftBodyCanvasWidth2;
24320
+ const topScrollHeaderCanvasWidth2 = scrollBodyCanvasWidth2;
24321
+ const leftFrozenHeaderCanvasHeight2 = topBodyCanvasHeight2;
24322
+ const leftScrollHeaderCanvasHeight2 = scrollBodyCanvasHeight2;
24028
24323
  const paneBounds = {
24029
24324
  corner: {
24030
24325
  height: cornerBodyCanvasHeight2,
@@ -24052,29 +24347,50 @@ function XlsxGrid({
24052
24347
  }
24053
24348
  };
24054
24349
  const bodyContexts = shouldRepaintBody ? {
24055
- corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth2, cornerBodyCanvasHeight2),
24056
- left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth2, leftBodyCanvasHeight2),
24057
- scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth2, scrollBodyCanvasHeight2),
24058
- top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth2, topBodyCanvasHeight2)
24350
+ corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth2, cornerBodyCanvasHeight2, { clear: !canBlitBody }),
24351
+ left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth2, leftBodyCanvasHeight2, { clear: !canBlitBody }),
24352
+ scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth2, scrollBodyCanvasHeight2, { clear: !canBlitBody }),
24353
+ top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth2, topBodyCanvasHeight2, { clear: !canBlitBody })
24059
24354
  } : {
24060
24355
  corner: null,
24061
24356
  left: null,
24062
24357
  scroll: null,
24063
24358
  top: null
24064
24359
  };
24065
- const topHeaderContext = shouldRepaintHeaders ? configureCanvas(topHeaderCanvasRef.current, bodyWidth, headerHeight) : null;
24066
- const leftHeaderContext = shouldRepaintHeaders ? configureCanvas(leftHeaderCanvasRef.current, rowHeaderWidth, bodyHeight) : null;
24360
+ const topHeaderContexts = shouldRepaintHeaders ? {
24361
+ frozen: configureCanvas(topFrozenHeaderCanvasRef.current, topFrozenHeaderCanvasWidth2, headerHeight),
24362
+ scroll: configureCanvas(topScrollHeaderCanvasRef.current, topScrollHeaderCanvasWidth2, headerHeight, { clear: !canBlitTopHeader })
24363
+ } : {
24364
+ frozen: null,
24365
+ scroll: null
24366
+ };
24367
+ const leftHeaderContexts = shouldRepaintHeaders ? {
24368
+ frozen: configureCanvas(leftFrozenHeaderCanvasRef.current, rowHeaderWidth, leftFrozenHeaderCanvasHeight2),
24369
+ scroll: configureCanvas(leftScrollHeaderCanvasRef.current, rowHeaderWidth, leftScrollHeaderCanvasHeight2, { clear: !canBlitLeftHeader })
24370
+ } : {
24371
+ frozen: null,
24372
+ scroll: null
24373
+ };
24067
24374
  const cornerContext = shouldRepaintHeaders ? configureCanvas(cornerHeaderCanvasRef.current, rowHeaderWidth, headerHeight) : null;
24068
- if (shouldRepaintBody && (!bodyContexts.scroll || !bodyContexts.top || !bodyContexts.left || !bodyContexts.corner) || shouldRepaintHeaders && (!topHeaderContext || !leftHeaderContext || !cornerContext)) {
24375
+ if (shouldRepaintBody && (!bodyContexts.scroll || !bodyContexts.top || !bodyContexts.left || !bodyContexts.corner) || shouldRepaintHeaders && (!cornerContext || topFrozenHeaderCanvasWidth2 > 0 && !topHeaderContexts.frozen || topScrollHeaderCanvasWidth2 > 0 && !topHeaderContexts.scroll || leftFrozenHeaderCanvasHeight2 > 0 && !leftHeaderContexts.frozen || leftScrollHeaderCanvasHeight2 > 0 && !leftHeaderContexts.scroll)) {
24069
24376
  return;
24070
24377
  }
24071
24378
  const showGridLines = activeSheet?.showGridLines ?? true;
24379
+ const sheetSurface = resolveSheetSurface(activeSheet, palette);
24072
24380
  const deferredSpillTextsByPane = {
24073
24381
  corner: [],
24074
24382
  left: [],
24075
24383
  scroll: [],
24076
24384
  top: []
24077
24385
  };
24386
+ const bodyDirtyRectsByPane = {
24387
+ corner: [],
24388
+ left: [],
24389
+ scroll: [],
24390
+ top: []
24391
+ };
24392
+ let topScrollHeaderDirtyRects = buildFullCanvasDirtyRect(topScrollHeaderCanvasWidth2, headerHeight);
24393
+ let leftScrollHeaderDirtyRects = buildFullCanvasDirtyRect(rowHeaderWidth, leftScrollHeaderCanvasHeight2);
24078
24394
  const cellPaneOrder = ["scroll", "top", "left", "corner"];
24079
24395
  if (shouldRepaintBody) {
24080
24396
  for (const pane of Object.keys(bodyContexts)) {
@@ -24083,14 +24399,44 @@ function XlsxGrid({
24083
24399
  if (!context || bounds.width <= 0 || bounds.height <= 0) {
24084
24400
  continue;
24085
24401
  }
24086
- context.fillStyle = resolveSheetSurface(activeSheet, palette);
24087
- context.fillRect(0, 0, bounds.width, bounds.height);
24402
+ let dirtyRects = buildFullCanvasDirtyRect(bounds.width, bounds.height);
24403
+ if (canBlitBody) {
24404
+ const bufferCanvas = getBodyBlitBufferCanvas(pane);
24405
+ const deltaX = pane === "scroll" || pane === "top" ? drawingViewport.left - previousPaintedViewport.left : 0;
24406
+ const deltaY = pane === "scroll" || pane === "left" ? drawingViewport.top - previousPaintedViewport.top : 0;
24407
+ if (bufferCanvas) {
24408
+ const blittedDirtyRects = blitCanvasWithScrollDelta(
24409
+ pane === "corner" ? cornerBodyCanvasRef.current : pane === "left" ? leftBodyCanvasRef.current : pane === "top" ? topBodyCanvasRef.current : scrollBodyCanvasRef.current,
24410
+ context,
24411
+ bufferCanvas,
24412
+ dpr,
24413
+ bounds.width,
24414
+ bounds.height,
24415
+ deltaX,
24416
+ deltaY
24417
+ );
24418
+ if (blittedDirtyRects) {
24419
+ dirtyRects = blittedDirtyRects;
24420
+ }
24421
+ }
24422
+ } else {
24423
+ context.clearRect(0, 0, bounds.width, bounds.height);
24424
+ }
24425
+ bodyDirtyRectsByPane[pane] = dirtyRects;
24426
+ if (dirtyRects.length === 0) {
24427
+ continue;
24428
+ }
24429
+ context.fillStyle = sheetSurface;
24430
+ for (const dirtyRect of dirtyRects) {
24431
+ context.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
24432
+ }
24088
24433
  }
24089
24434
  for (const pane of cellPaneOrder) {
24090
24435
  const paneContext = bodyContexts[pane];
24091
24436
  const paneBoundsForCell = paneBounds[pane];
24437
+ const paneDirtyRects = bodyDirtyRectsByPane[pane];
24092
24438
  const paneAxisItems = canvasPaneAxisItems[pane];
24093
- if (!paneContext || paneBoundsForCell.width <= 0 || paneBoundsForCell.height <= 0 || paneAxisItems.rows.length === 0 || paneAxisItems.cols.length === 0) {
24439
+ if (!paneContext || paneDirtyRects.length === 0 || paneBoundsForCell.width <= 0 || paneBoundsForCell.height <= 0 || paneAxisItems.rows.length === 0 || paneAxisItems.cols.length === 0) {
24094
24440
  continue;
24095
24441
  }
24096
24442
  const drawnMergedAnchorKeys = /* @__PURE__ */ new Set();
@@ -24130,11 +24476,14 @@ function XlsxGrid({
24130
24476
  if (localRect.left + drawableWidth < 0 || localRect.top + localRect.height < 0 || localRect.left > paneBoundsForCell.width || localRect.top > paneBoundsForCell.height) {
24131
24477
  continue;
24132
24478
  }
24479
+ if (!intersectsCanvasDirtyRects(localRect.left, localRect.top, drawableWidth, localRect.height, paneDirtyRects)) {
24480
+ continue;
24481
+ }
24133
24482
  const cellStyle = cellData.style;
24134
24483
  const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellStyle);
24135
- const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellStyle.backgroundColor === "string" ? cellStyle.backgroundColor : resolveSheetSurface(activeSheet, palette));
24484
+ const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellStyle.backgroundColor === "string" ? cellStyle.backgroundColor : sheetSurface);
24136
24485
  const gradientFill = !cellData.conditionalColorScale && typeof cellStyle.backgroundImage === "string" ? resolveCanvasGradientFill(paneContext, localRect, cellStyle.backgroundImage) : null;
24137
- const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellStyle.backgroundColor === "string" && cellStyle.backgroundColor !== resolveSheetSurface(activeSheet, palette);
24486
+ const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellStyle.backgroundColor === "string" && cellStyle.backgroundColor !== sheetSurface;
24138
24487
  paneContext.fillStyle = gradientFill ?? fillColor;
24139
24488
  paneContext.fillRect(localRect.left, localRect.top, localRect.width, localRect.height);
24140
24489
  if (cellData.chartHighlight) {
@@ -24488,55 +24837,139 @@ function XlsxGrid({
24488
24837
  }
24489
24838
  }
24490
24839
  }
24491
- if (shouldRepaintHeaders && topHeaderContext && leftHeaderContext && cornerContext) {
24492
- topHeaderContext.fillStyle = palette.headerSurface;
24493
- topHeaderContext.fillRect(0, 0, bodyWidth, headerHeight);
24494
- topHeaderContext.strokeStyle = palette.border;
24495
- topHeaderContext.lineWidth = 1;
24496
- for (const colItem of canvasVisibleColItems) {
24497
- const rect = resolveCanvasColumnHeaderRect(colItem.actualCol);
24498
- if (!rect || rect.left + rect.width < displayRowHeaderWidth || rect.left > bodyWidth) {
24840
+ if (shouldRepaintHeaders && cornerContext) {
24841
+ const topFrozenHeaderContext = topHeaderContexts.frozen;
24842
+ const topScrollHeaderContext = topHeaderContexts.scroll;
24843
+ const leftFrozenHeaderContext = leftHeaderContexts.frozen;
24844
+ const leftScrollHeaderContext = leftHeaderContexts.scroll;
24845
+ if (canBlitTopHeader) {
24846
+ const bufferCanvas = getHeaderBlitBufferCanvas("top");
24847
+ if (bufferCanvas && topScrollHeaderContext && topScrollHeaderCanvasRef.current) {
24848
+ const blittedDirtyRects = blitCanvasWithScrollDelta(
24849
+ topScrollHeaderCanvasRef.current,
24850
+ topScrollHeaderContext,
24851
+ bufferCanvas,
24852
+ dpr,
24853
+ topScrollHeaderCanvasWidth2,
24854
+ headerHeight,
24855
+ drawingViewport.left - previousPaintedViewport.left,
24856
+ 0
24857
+ );
24858
+ if (blittedDirtyRects) {
24859
+ topScrollHeaderDirtyRects = blittedDirtyRects;
24860
+ }
24861
+ }
24862
+ }
24863
+ if (canBlitLeftHeader) {
24864
+ const bufferCanvas = getHeaderBlitBufferCanvas("left");
24865
+ if (bufferCanvas && leftScrollHeaderContext && leftScrollHeaderCanvasRef.current) {
24866
+ const blittedDirtyRects = blitCanvasWithScrollDelta(
24867
+ leftScrollHeaderCanvasRef.current,
24868
+ leftScrollHeaderContext,
24869
+ bufferCanvas,
24870
+ dpr,
24871
+ rowHeaderWidth,
24872
+ leftScrollHeaderCanvasHeight2,
24873
+ 0,
24874
+ drawingViewport.top - previousPaintedViewport.top
24875
+ );
24876
+ if (blittedDirtyRects) {
24877
+ leftScrollHeaderDirtyRects = blittedDirtyRects;
24878
+ }
24879
+ }
24880
+ }
24881
+ if (topFrozenHeaderContext && topFrozenHeaderCanvasWidth2 > 0) {
24882
+ topFrozenHeaderContext.fillStyle = palette.headerSurface;
24883
+ topFrozenHeaderContext.fillRect(0, 0, topFrozenHeaderCanvasWidth2, headerHeight);
24884
+ }
24885
+ if (topScrollHeaderContext && topScrollHeaderCanvasWidth2 > 0) {
24886
+ topScrollHeaderContext.fillStyle = palette.headerSurface;
24887
+ for (const dirtyRect of topScrollHeaderDirtyRects) {
24888
+ topScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
24889
+ }
24890
+ }
24891
+ if (topFrozenHeaderContext) {
24892
+ topFrozenHeaderContext.strokeStyle = palette.border;
24893
+ topFrozenHeaderContext.lineWidth = 1;
24894
+ }
24895
+ if (topScrollHeaderContext) {
24896
+ topScrollHeaderContext.strokeStyle = palette.border;
24897
+ topScrollHeaderContext.lineWidth = 1;
24898
+ }
24899
+ for (const column of canvasColumnHeaderCells) {
24900
+ const paneContext = column.isFrozen ? topFrozenHeaderContext : topScrollHeaderContext;
24901
+ if (!paneContext) {
24902
+ continue;
24903
+ }
24904
+ if (column.localLeft + column.width < 0 || column.localLeft > (column.isFrozen ? topFrozenHeaderCanvasWidth2 : topScrollHeaderCanvasWidth2)) {
24905
+ continue;
24906
+ }
24907
+ if (!column.isFrozen && !intersectsCanvasDirtyRects(column.localLeft, 0, column.width, column.height, topScrollHeaderDirtyRects)) {
24908
+ continue;
24909
+ }
24910
+ const selected = normalizedVisibleRange && column.actualCol >= normalizedVisibleRange.start.col && column.actualCol <= normalizedVisibleRange.end.col;
24911
+ paneContext.fillStyle = selected ? selectionHeaderSurface : palette.headerSurface;
24912
+ paneContext.fillRect(column.localLeft, 0, column.width, column.height);
24913
+ paneContext.strokeStyle = palette.border;
24914
+ paneContext.beginPath();
24915
+ paneContext.moveTo(column.localLeft + column.width - 0.5, 0);
24916
+ paneContext.lineTo(column.localLeft + column.width - 0.5, column.height);
24917
+ paneContext.moveTo(column.localLeft, column.height - 0.5);
24918
+ paneContext.lineTo(column.localLeft + column.width, column.height - 0.5);
24919
+ paneContext.stroke();
24920
+ paneContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24921
+ paneContext.fillStyle = palette.mutedText;
24922
+ paneContext.textAlign = "center";
24923
+ paneContext.textBaseline = "middle";
24924
+ paneContext.fillText(
24925
+ columnLabel2(column.actualCol),
24926
+ column.localLeft + column.width / 2,
24927
+ column.height / 2
24928
+ );
24929
+ }
24930
+ if (leftFrozenHeaderContext && leftFrozenHeaderCanvasHeight2 > 0) {
24931
+ leftFrozenHeaderContext.fillStyle = palette.rowHeaderSurface;
24932
+ leftFrozenHeaderContext.fillRect(0, 0, rowHeaderWidth, leftFrozenHeaderCanvasHeight2);
24933
+ }
24934
+ if (leftScrollHeaderContext && leftScrollHeaderCanvasHeight2 > 0) {
24935
+ leftScrollHeaderContext.fillStyle = palette.rowHeaderSurface;
24936
+ for (const dirtyRect of leftScrollHeaderDirtyRects) {
24937
+ leftScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
24938
+ }
24939
+ }
24940
+ if (leftFrozenHeaderContext) {
24941
+ leftFrozenHeaderContext.strokeStyle = palette.border;
24942
+ leftFrozenHeaderContext.lineWidth = 1;
24943
+ }
24944
+ if (leftScrollHeaderContext) {
24945
+ leftScrollHeaderContext.strokeStyle = palette.border;
24946
+ leftScrollHeaderContext.lineWidth = 1;
24947
+ }
24948
+ for (const row of canvasRowHeaderCells) {
24949
+ const paneContext = row.isFrozen ? leftFrozenHeaderContext : leftScrollHeaderContext;
24950
+ if (!paneContext) {
24951
+ continue;
24952
+ }
24953
+ if (row.localTop + row.height < 0 || row.localTop > (row.isFrozen ? leftFrozenHeaderCanvasHeight2 : leftScrollHeaderCanvasHeight2)) {
24499
24954
  continue;
24500
24955
  }
24501
- const selected = normalizedVisibleRange && colItem.actualCol >= normalizedVisibleRange.start.col && colItem.actualCol <= normalizedVisibleRange.end.col;
24502
- topHeaderContext.fillStyle = selected ? selectionHeaderSurface : palette.headerSurface;
24503
- topHeaderContext.fillRect(rect.left, 0, rect.width, headerHeight);
24504
- topHeaderContext.strokeStyle = palette.border;
24505
- topHeaderContext.beginPath();
24506
- topHeaderContext.moveTo(rect.left + rect.width - 0.5, 0);
24507
- topHeaderContext.lineTo(rect.left + rect.width - 0.5, headerHeight);
24508
- topHeaderContext.moveTo(rect.left, headerHeight - 0.5);
24509
- topHeaderContext.lineTo(rect.left + rect.width, headerHeight - 0.5);
24510
- topHeaderContext.stroke();
24511
- topHeaderContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24512
- topHeaderContext.fillStyle = palette.mutedText;
24513
- topHeaderContext.textAlign = "center";
24514
- topHeaderContext.textBaseline = "middle";
24515
- topHeaderContext.fillText(columnLabel2(colItem.actualCol), rect.left + rect.width / 2, headerHeight / 2);
24516
- }
24517
- leftHeaderContext.fillStyle = palette.rowHeaderSurface;
24518
- leftHeaderContext.fillRect(0, 0, rowHeaderWidth, bodyHeight);
24519
- leftHeaderContext.strokeStyle = palette.border;
24520
- leftHeaderContext.lineWidth = 1;
24521
- for (const rowItem of canvasVisibleRowItems) {
24522
- const rect = resolveCanvasRowHeaderRect(rowItem.actualRow);
24523
- if (!rect || rect.top + rect.height < displayHeaderHeight || rect.top > bodyHeight) {
24956
+ if (!row.isFrozen && !intersectsCanvasDirtyRects(0, row.localTop, rowHeaderWidth, row.height, leftScrollHeaderDirtyRects)) {
24524
24957
  continue;
24525
24958
  }
24526
- const selected = normalizedVisibleRange && rowItem.actualRow >= normalizedVisibleRange.start.row && rowItem.actualRow <= normalizedVisibleRange.end.row;
24527
- leftHeaderContext.fillStyle = selected ? selectionHeaderSurface : palette.rowHeaderSurface;
24528
- leftHeaderContext.fillRect(0, rect.top, rowHeaderWidth, rect.height);
24529
- leftHeaderContext.beginPath();
24530
- leftHeaderContext.moveTo(0, rect.top + rect.height - 0.5);
24531
- leftHeaderContext.lineTo(rowHeaderWidth, rect.top + rect.height - 0.5);
24532
- leftHeaderContext.moveTo(rowHeaderWidth - 0.5, rect.top);
24533
- leftHeaderContext.lineTo(rowHeaderWidth - 0.5, rect.top + rect.height);
24534
- leftHeaderContext.stroke();
24535
- leftHeaderContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24536
- leftHeaderContext.fillStyle = palette.mutedText;
24537
- leftHeaderContext.textAlign = "center";
24538
- leftHeaderContext.textBaseline = "middle";
24539
- leftHeaderContext.fillText(`${rowItem.actualRow + 1}`, rowHeaderWidth / 2, rect.top + rect.height / 2);
24959
+ const selected = normalizedVisibleRange && row.actualRow >= normalizedVisibleRange.start.row && row.actualRow <= normalizedVisibleRange.end.row;
24960
+ paneContext.fillStyle = selected ? selectionHeaderSurface : palette.rowHeaderSurface;
24961
+ paneContext.fillRect(0, row.localTop, rowHeaderWidth, row.height);
24962
+ paneContext.beginPath();
24963
+ paneContext.moveTo(0, row.localTop + row.height - 0.5);
24964
+ paneContext.lineTo(rowHeaderWidth, row.localTop + row.height - 0.5);
24965
+ paneContext.moveTo(rowHeaderWidth - 0.5, row.localTop);
24966
+ paneContext.lineTo(rowHeaderWidth - 0.5, row.localTop + row.height);
24967
+ paneContext.stroke();
24968
+ paneContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24969
+ paneContext.fillStyle = palette.mutedText;
24970
+ paneContext.textAlign = "center";
24971
+ paneContext.textBaseline = "middle";
24972
+ paneContext.fillText(`${row.actualRow + 1}`, rowHeaderWidth / 2, row.localTop + row.height / 2);
24540
24973
  }
24541
24974
  cornerContext.fillStyle = palette.rowHeaderSurface;
24542
24975
  cornerContext.fillRect(0, 0, rowHeaderWidth, headerHeight);
@@ -24560,7 +24993,9 @@ function XlsxGrid({
24560
24993
  }, [
24561
24994
  activeSheet,
24562
24995
  applyCanvasViewportCompensation,
24996
+ canvasColumnHeaderCells,
24563
24997
  canvasPaneAxisItems,
24998
+ canvasRowHeaderCells,
24564
24999
  canvasVisibleColItems,
24565
25000
  canvasVisibleRowItems,
24566
25001
  colIndexByActual,
@@ -24575,11 +25010,13 @@ function XlsxGrid({
24575
25010
  drawingViewport.height,
24576
25011
  drawingViewport.width,
24577
25012
  experimentalCanvas,
25013
+ frozenPaneBottom,
25014
+ frozenPaneRight,
24578
25015
  getCellData,
25016
+ getBodyBlitBufferCanvas,
25017
+ getHeaderBlitBufferCanvas,
24579
25018
  palette,
24580
25019
  resolveCellDisplayRect,
24581
- resolveCanvasColumnHeaderRect,
24582
- resolveCanvasRowHeaderRect,
24583
25020
  resolveMergeAnchorCell,
24584
25021
  resizeGuide,
24585
25022
  rowIndexByActual,
@@ -24873,6 +25310,10 @@ function XlsxGrid({
24873
25310
  const leftBodyCanvasHeight = scrollBodyCanvasHeight;
24874
25311
  const cornerBodyCanvasWidth = leftBodyCanvasWidth;
24875
25312
  const cornerBodyCanvasHeight = topBodyCanvasHeight;
25313
+ const topFrozenHeaderCanvasWidth = leftBodyCanvasWidth;
25314
+ const topScrollHeaderCanvasWidth = scrollBodyCanvasWidth;
25315
+ const leftFrozenHeaderCanvasHeight = topBodyCanvasHeight;
25316
+ const leftScrollHeaderCanvasHeight = scrollBodyCanvasHeight;
24876
25317
  const canvasBodyBaseStyle = {
24877
25318
  cursor: "cell",
24878
25319
  pointerEvents: "auto",
@@ -24908,22 +25349,38 @@ function XlsxGrid({
24908
25349
  top: displayHeaderHeight,
24909
25350
  zIndex: 31
24910
25351
  };
24911
- const canvasTopHeaderStyle = {
25352
+ const canvasHeaderBaseStyle = {
24912
25353
  cursor: "default",
24913
- display: drawingViewport.width > 0 && drawingViewport.height > 0 ? "block" : "none",
24914
- left: 0,
24915
25354
  pointerEvents: "auto",
24916
25355
  position: "absolute",
25356
+ transformOrigin: "0 0"
25357
+ };
25358
+ const canvasTopFrozenHeaderStyle = {
25359
+ ...canvasHeaderBaseStyle,
25360
+ display: topFrozenHeaderCanvasWidth > 0 && drawingViewport.height > 0 ? "block" : "none",
25361
+ left: displayRowHeaderWidth,
25362
+ top: 0,
25363
+ zIndex: canvasHeaderOverlayZIndex + 1
25364
+ };
25365
+ const canvasTopScrollHeaderStyle = {
25366
+ ...canvasHeaderBaseStyle,
25367
+ display: topScrollHeaderCanvasWidth > 0 && drawingViewport.height > 0 ? "block" : "none",
25368
+ left: frozenPaneRight,
24917
25369
  top: 0,
24918
25370
  zIndex: canvasHeaderOverlayZIndex
24919
25371
  };
24920
- const canvasLeftHeaderStyle = {
24921
- cursor: "default",
24922
- display: drawingViewport.width > 0 && drawingViewport.height > 0 ? "block" : "none",
25372
+ const canvasLeftFrozenHeaderStyle = {
25373
+ ...canvasHeaderBaseStyle,
25374
+ display: leftFrozenHeaderCanvasHeight > 0 && drawingViewport.width > 0 ? "block" : "none",
24923
25375
  left: 0,
24924
- pointerEvents: "auto",
24925
- position: "absolute",
24926
- top: 0,
25376
+ top: displayHeaderHeight,
25377
+ zIndex: canvasHeaderOverlayZIndex + 1
25378
+ };
25379
+ const canvasLeftScrollHeaderStyle = {
25380
+ ...canvasHeaderBaseStyle,
25381
+ display: leftScrollHeaderCanvasHeight > 0 && drawingViewport.width > 0 ? "block" : "none",
25382
+ left: 0,
25383
+ top: frozenPaneBottom,
24927
25384
  zIndex: canvasHeaderOverlayZIndex
24928
25385
  };
24929
25386
  const canvasCornerHeaderStyle = {
@@ -24932,6 +25389,7 @@ function XlsxGrid({
24932
25389
  pointerEvents: "none",
24933
25390
  position: "absolute",
24934
25391
  top: 0,
25392
+ transformOrigin: "0 0",
24935
25393
  zIndex: canvasHeaderOverlayZIndex + 1
24936
25394
  };
24937
25395
  const editingOverlayRect = experimentalCanvas && editingCell ? resolveCellDisplayRect(editingCell) : null;
@@ -26078,7 +26536,7 @@ function XlsxGrid({
26078
26536
  pointerEvents: "none",
26079
26537
  position: "absolute",
26080
26538
  top: 0,
26081
- transform: `translate(${-drawingViewport.left}px, ${-drawingViewport.top}px)`,
26539
+ transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
26082
26540
  width: totalWidth
26083
26541
  },
26084
26542
  children: paneDrawingNodes.scroll
@@ -26093,7 +26551,7 @@ function XlsxGrid({
26093
26551
  pointerEvents: "none",
26094
26552
  position: "absolute",
26095
26553
  top: 0,
26096
- transform: `translate(${-drawingViewport.left}px, ${-displayHeaderHeight}px)`,
26554
+ transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-displayHeaderHeight}px)`,
26097
26555
  width: totalWidth
26098
26556
  },
26099
26557
  children: paneDrawingNodes.top
@@ -26108,7 +26566,7 @@ function XlsxGrid({
26108
26566
  pointerEvents: "none",
26109
26567
  position: "absolute",
26110
26568
  top: 0,
26111
- transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top}px)`,
26569
+ transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
26112
26570
  width: totalWidth
26113
26571
  },
26114
26572
  children: paneDrawingNodes.left
@@ -26135,21 +26593,41 @@ function XlsxGrid({
26135
26593
  /* @__PURE__ */ jsx3(
26136
26594
  "canvas",
26137
26595
  {
26138
- ref: topHeaderCanvasRef,
26596
+ ref: topFrozenHeaderCanvasRef,
26139
26597
  onPointerLeave: handleCanvasHeaderPointerLeave,
26140
26598
  onPointerMove: handleCanvasColumnHeaderPointerMove,
26141
26599
  onPointerDown: handleCanvasColumnHeaderPointerDown,
26142
- style: canvasTopHeaderStyle
26600
+ style: canvasTopFrozenHeaderStyle
26601
+ }
26602
+ ),
26603
+ /* @__PURE__ */ jsx3(
26604
+ "canvas",
26605
+ {
26606
+ ref: topScrollHeaderCanvasRef,
26607
+ onPointerLeave: handleCanvasHeaderPointerLeave,
26608
+ onPointerMove: handleCanvasColumnHeaderPointerMove,
26609
+ onPointerDown: handleCanvasColumnHeaderPointerDown,
26610
+ style: canvasTopScrollHeaderStyle
26611
+ }
26612
+ ),
26613
+ /* @__PURE__ */ jsx3(
26614
+ "canvas",
26615
+ {
26616
+ ref: leftFrozenHeaderCanvasRef,
26617
+ onPointerLeave: handleCanvasHeaderPointerLeave,
26618
+ onPointerMove: handleCanvasRowHeaderPointerMove,
26619
+ onPointerDown: handleCanvasRowHeaderPointerDown,
26620
+ style: canvasLeftFrozenHeaderStyle
26143
26621
  }
26144
26622
  ),
26145
26623
  /* @__PURE__ */ jsx3(
26146
26624
  "canvas",
26147
26625
  {
26148
- ref: leftHeaderCanvasRef,
26626
+ ref: leftScrollHeaderCanvasRef,
26149
26627
  onPointerLeave: handleCanvasHeaderPointerLeave,
26150
26628
  onPointerMove: handleCanvasRowHeaderPointerMove,
26151
26629
  onPointerDown: handleCanvasRowHeaderPointerDown,
26152
- style: canvasLeftHeaderStyle
26630
+ style: canvasLeftScrollHeaderStyle
26153
26631
  }
26154
26632
  ),
26155
26633
  /* @__PURE__ */ jsx3("canvas", { ref: cornerHeaderCanvasRef, style: canvasCornerHeaderStyle })