@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.cjs CHANGED
@@ -4268,13 +4268,36 @@ function parseWorkbookTableMetadata(archive, workbookSheets) {
4268
4268
  }
4269
4269
  return [{
4270
4270
  displayName: tableNode.getAttribute("displayName") ?? void 0,
4271
+ headerRowCount: parseWorkbookTableCount(tableNode.getAttribute("headerRowCount"), 1),
4271
4272
  headerRowCellStyle: tableNode.getAttribute("headerRowCellStyle") ?? void 0,
4272
4273
  name: tableNode.getAttribute("name") ?? void 0,
4273
- reference: tableNode.getAttribute("ref") ?? void 0
4274
+ reference: tableNode.getAttribute("ref") ?? void 0,
4275
+ totalsRowCount: parseWorkbookTableCount(tableNode.getAttribute("totalsRowCount"), 0),
4276
+ totalsRowShown: parseWorkbookTableBoolean(tableNode.getAttribute("totalsRowShown"), false)
4274
4277
  }];
4275
4278
  });
4276
4279
  });
4277
4280
  }
4281
+ function parseWorkbookTableCount(value, fallback) {
4282
+ if (value === null) {
4283
+ return fallback;
4284
+ }
4285
+ const parsed = Number.parseInt(value, 10);
4286
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
4287
+ }
4288
+ function parseWorkbookTableBoolean(value, fallback) {
4289
+ if (value === null) {
4290
+ return fallback;
4291
+ }
4292
+ const normalized = value.trim().toLowerCase();
4293
+ if (normalized === "0" || normalized === "false" || normalized === "") {
4294
+ return false;
4295
+ }
4296
+ if (normalized === "1" || normalized === "true") {
4297
+ return true;
4298
+ }
4299
+ return fallback;
4300
+ }
4278
4301
  function parseSqrefRanges(sqref) {
4279
4302
  if (!sqref) {
4280
4303
  return [];
@@ -6881,17 +6904,18 @@ function rangeContainsCell(range, cell) {
6881
6904
  function mapWorksheetTables(worksheet, metadataForSheet) {
6882
6905
  const rawTables = worksheet?.tables ?? [];
6883
6906
  return rawTables.flatMap((table, index) => {
6884
- const reference = typeof table.reference === "string" ? table.reference : "";
6885
- const parsedRange = parseA1RangeReference2(reference);
6886
- if (!parsedRange) {
6887
- return [];
6888
- }
6889
6907
  const rawColumns = Array.isArray(table.columns) ? table.columns : [];
6890
6908
  const rawName = typeof table.name === "string" ? table.name : `Table${index + 1}`;
6891
6909
  const rawDisplayName = typeof table.displayName === "string" ? table.displayName : typeof table.name === "string" ? table.name : `Table ${index + 1}`;
6892
6910
  const metadata = metadataForSheet?.find(
6893
- (entry) => entry.name && entry.name === rawName || entry.displayName && entry.displayName === rawDisplayName || entry.reference && entry.reference === reference
6911
+ (entry) => entry.name && entry.name === rawName || entry.displayName && entry.displayName === rawDisplayName || entry.reference && entry.reference === table.reference
6894
6912
  );
6913
+ const rawReference = typeof table.reference === "string" ? table.reference : "";
6914
+ const reference = metadata?.reference ?? rawReference;
6915
+ const parsedRange = parseA1RangeReference2(reference);
6916
+ if (!parsedRange) {
6917
+ return [];
6918
+ }
6895
6919
  return [{
6896
6920
  columns: rawColumns.map((column, columnIndex) => ({
6897
6921
  id: typeof column.id === "number" ? column.id ?? columnIndex + 1 : columnIndex + 1,
@@ -6900,17 +6924,47 @@ function mapWorksheetTables(worksheet, metadataForSheet) {
6900
6924
  })),
6901
6925
  displayName: rawDisplayName,
6902
6926
  end: parsedRange.end,
6903
- headerRowCount: typeof table.headerRowCount === "number" ? table.headerRowCount : 1,
6927
+ headerRowCount: metadata?.headerRowCount ?? resolveWorkbookTableCount(table.headerRowCount, 1),
6904
6928
  headerRowCellStyle: metadata?.headerRowCellStyle,
6905
6929
  name: rawName,
6906
6930
  reference,
6907
6931
  start: parsedRange.start,
6908
6932
  styleInfo: table.styleInfo,
6909
- totalsRowCount: typeof table.totalsRowCount === "number" ? table.totalsRowCount : 0,
6910
- totalsRowShown: Boolean(table.totalsRowShown)
6933
+ totalsRowCount: metadata?.totalsRowCount ?? resolveWorkbookTableCount(table.totalsRowCount, 0),
6934
+ totalsRowShown: metadata?.totalsRowShown ?? resolveWorkbookTableBoolean(table.totalsRowShown)
6911
6935
  }];
6912
6936
  });
6913
6937
  }
6938
+ function resolveWorkbookTableCount(value, fallback) {
6939
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
6940
+ return value;
6941
+ }
6942
+ if (typeof value === "string") {
6943
+ const parsed = Number.parseInt(value, 10);
6944
+ if (Number.isFinite(parsed) && parsed >= 0) {
6945
+ return parsed;
6946
+ }
6947
+ }
6948
+ return fallback;
6949
+ }
6950
+ function resolveWorkbookTableBoolean(value) {
6951
+ if (typeof value === "boolean") {
6952
+ return value;
6953
+ }
6954
+ if (typeof value === "number") {
6955
+ return value !== 0;
6956
+ }
6957
+ if (typeof value === "string") {
6958
+ const normalized = value.trim().toLowerCase();
6959
+ if (normalized === "0" || normalized === "false" || normalized === "") {
6960
+ return false;
6961
+ }
6962
+ if (normalized === "1" || normalized === "true") {
6963
+ return true;
6964
+ }
6965
+ }
6966
+ return false;
6967
+ }
6914
6968
  function fileStem(fileName) {
6915
6969
  const normalized = fileName.trim();
6916
6970
  const lastDot = normalized.lastIndexOf(".");
@@ -7041,7 +7095,17 @@ async function resolveWorkbookBuffer({ file, src }, signal) {
7041
7095
  if (file) {
7042
7096
  buffer = file;
7043
7097
  } else if (src) {
7044
- const response = await fetch(src, { signal });
7098
+ let response;
7099
+ try {
7100
+ response = await fetch(src, { signal });
7101
+ } catch (error) {
7102
+ if (isAbortError(error)) {
7103
+ throw error;
7104
+ }
7105
+ throw new Error(
7106
+ "Failed to fetch workbook. The remote URL may be blocked by CORS, unavailable, or not directly downloadable from the browser."
7107
+ );
7108
+ }
7045
7109
  if (!response.ok) {
7046
7110
  throw new Error(`Failed to fetch workbook (status ${response.status})`);
7047
7111
  }
@@ -7597,6 +7661,7 @@ function downloadUrl(src, fileName) {
7597
7661
  }
7598
7662
  function useXlsxViewerController(options) {
7599
7663
  const {
7664
+ allowResizeInReadOnly = false,
7600
7665
  deferLoadingAboveBytes = DEFAULT_DEFER_LOADING_ABOVE_BYTES,
7601
7666
  file,
7602
7667
  fileName,
@@ -7650,6 +7715,7 @@ function useXlsxViewerController(options) {
7650
7715
  const displayFileName = React.useMemo(() => resolveDisplayFileName(src, fileName), [fileName, src]);
7651
7716
  const shouldDeferLoading = deferLoadingAboveBytes > 0;
7652
7717
  const readOnly = requestedReadOnly || forcedReadOnly;
7718
+ const canResizeReadOnly = requestedReadOnly && allowResizeInReadOnly && !forcedReadOnly;
7653
7719
  const workerSupported = useWorker && typeof Worker !== "undefined";
7654
7720
  const shouldUseWorker = workerSupported && forcedReadOnly;
7655
7721
  const shouldForceReadOnlyForBuffer = React.useCallback((bufferByteLength) => !requestedReadOnly && readOnlyAboveBytes > 0 && bufferByteLength > readOnlyAboveBytes, [readOnlyAboveBytes, requestedReadOnly]);
@@ -8944,7 +9010,7 @@ function useXlsxViewerController(options) {
8944
9010
  refreshWorkbookState(workbook);
8945
9011
  }, [refreshWorkbookState, workbook]);
8946
9012
  const resizeColumn = React.useCallback((col, widthPx) => {
8947
- if (readOnly || !workbook || !activeSheet) {
9013
+ if (readOnly && !canResizeReadOnly || !workbook || !activeSheet) {
8948
9014
  return;
8949
9015
  }
8950
9016
  recordHistoryBeforeMutation();
@@ -8954,9 +9020,9 @@ function useXlsxViewerController(options) {
8954
9020
  pxToSheetColumnWidth(resolveContentSheetAxisPixels(widthPx, activeSheet.showGridLines))
8955
9021
  );
8956
9022
  refreshWorkbookState(workbook);
8957
- }, [activeSheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9023
+ }, [activeSheet, canResizeReadOnly, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8958
9024
  const resizeRow = React.useCallback((row, heightPx) => {
8959
- if (readOnly || !workbook || !activeSheet) {
9025
+ if (readOnly && !canResizeReadOnly || !workbook || !activeSheet) {
8960
9026
  return;
8961
9027
  }
8962
9028
  recordHistoryBeforeMutation();
@@ -8966,7 +9032,7 @@ function useXlsxViewerController(options) {
8966
9032
  pxToSheetRowHeight(resolveContentSheetAxisPixels(heightPx, activeSheet.showGridLines))
8967
9033
  );
8968
9034
  refreshWorkbookState(workbook);
8969
- }, [activeSheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9035
+ }, [activeSheet, canResizeReadOnly, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8970
9036
  const resolveAnchoredObjectRect = React.useCallback((anchor, worksheet) => {
8971
9037
  const resolveAxisSum = (index, getSize) => {
8972
9038
  let total = 0;
@@ -19944,6 +20010,109 @@ function resolveSelectionColors({
19944
20010
  stroke
19945
20011
  };
19946
20012
  }
20013
+ function buildFullCanvasDirtyRect(width, height) {
20014
+ if (width <= 0 || height <= 0) {
20015
+ return [];
20016
+ }
20017
+ return [{
20018
+ height,
20019
+ left: 0,
20020
+ top: 0,
20021
+ width
20022
+ }];
20023
+ }
20024
+ function intersectsCanvasDirtyRects(left, top, width, height, dirtyRects) {
20025
+ if (dirtyRects.length === 0 || width <= 0 || height <= 0) {
20026
+ return false;
20027
+ }
20028
+ const right = left + width;
20029
+ const bottom = top + height;
20030
+ return dirtyRects.some((dirtyRect) => left < dirtyRect.left + dirtyRect.width && right > dirtyRect.left && top < dirtyRect.top + dirtyRect.height && bottom > dirtyRect.top);
20031
+ }
20032
+ function blitCanvasWithScrollDelta(canvas, context, bufferCanvas, dpr, width, height, deltaX, deltaY) {
20033
+ if (width <= 0 || height <= 0) {
20034
+ return [];
20035
+ }
20036
+ const clampedDeltaX = Math.max(-width, Math.min(width, deltaX));
20037
+ const clampedDeltaY = Math.max(-height, Math.min(height, deltaY));
20038
+ if (Math.abs(clampedDeltaX) < 0.01 && Math.abs(clampedDeltaY) < 0.01) {
20039
+ return [];
20040
+ }
20041
+ if (Math.abs(clampedDeltaX) >= width || Math.abs(clampedDeltaY) >= height) {
20042
+ return null;
20043
+ }
20044
+ const deviceWidth = Math.max(1, Math.round(width * dpr));
20045
+ const deviceHeight = Math.max(1, Math.round(height * dpr));
20046
+ if (bufferCanvas.width !== deviceWidth) {
20047
+ bufferCanvas.width = deviceWidth;
20048
+ }
20049
+ if (bufferCanvas.height !== deviceHeight) {
20050
+ bufferCanvas.height = deviceHeight;
20051
+ }
20052
+ const bufferContext = bufferCanvas.getContext("2d");
20053
+ if (!bufferContext) {
20054
+ return null;
20055
+ }
20056
+ bufferContext.setTransform(1, 0, 0, 1, 0, 0);
20057
+ bufferContext.clearRect(0, 0, deviceWidth, deviceHeight);
20058
+ bufferContext.drawImage(canvas, 0, 0);
20059
+ const deltaDeviceX = clampedDeltaX * dpr;
20060
+ const deltaDeviceY = clampedDeltaY * dpr;
20061
+ const overlapDeviceWidth = deviceWidth - Math.abs(deltaDeviceX);
20062
+ const overlapDeviceHeight = deviceHeight - Math.abs(deltaDeviceY);
20063
+ if (overlapDeviceWidth <= 0 || overlapDeviceHeight <= 0) {
20064
+ return null;
20065
+ }
20066
+ const sourceX = deltaDeviceX > 0 ? deltaDeviceX : 0;
20067
+ const sourceY = deltaDeviceY > 0 ? deltaDeviceY : 0;
20068
+ const destinationX = deltaDeviceX > 0 ? 0 : -deltaDeviceX;
20069
+ const destinationY = deltaDeviceY > 0 ? 0 : -deltaDeviceY;
20070
+ context.setTransform(1, 0, 0, 1, 0, 0);
20071
+ context.drawImage(
20072
+ bufferCanvas,
20073
+ sourceX,
20074
+ sourceY,
20075
+ overlapDeviceWidth,
20076
+ overlapDeviceHeight,
20077
+ destinationX,
20078
+ destinationY,
20079
+ overlapDeviceWidth,
20080
+ overlapDeviceHeight
20081
+ );
20082
+ context.setTransform(dpr, 0, 0, dpr, 0, 0);
20083
+ const dirtyRects = [];
20084
+ if (clampedDeltaX > 0) {
20085
+ dirtyRects.push({
20086
+ height,
20087
+ left: Math.max(0, width - clampedDeltaX),
20088
+ top: 0,
20089
+ width: Math.min(width, clampedDeltaX)
20090
+ });
20091
+ } else if (clampedDeltaX < 0) {
20092
+ dirtyRects.push({
20093
+ height,
20094
+ left: 0,
20095
+ top: 0,
20096
+ width: Math.min(width, -clampedDeltaX)
20097
+ });
20098
+ }
20099
+ if (clampedDeltaY > 0) {
20100
+ dirtyRects.push({
20101
+ height: Math.min(height, clampedDeltaY),
20102
+ left: 0,
20103
+ top: Math.max(0, height - clampedDeltaY),
20104
+ width
20105
+ });
20106
+ } else if (clampedDeltaY < 0) {
20107
+ dirtyRects.push({
20108
+ height: Math.min(height, -clampedDeltaY),
20109
+ left: 0,
20110
+ top: 0,
20111
+ width
20112
+ });
20113
+ }
20114
+ return dirtyRects;
20115
+ }
19947
20116
  function buildConditionalFormatRuleKey(rule) {
19948
20117
  return `${rule.kind}:${rule.priority}:${rule.ranges.map((range) => `${range.start.row}:${range.start.col}-${range.end.row}:${range.end.col}`).join("|")}`;
19949
20118
  }
@@ -20803,8 +20972,20 @@ function XlsxGrid({
20803
20972
  const topBodyCanvasRef = React4.useRef(null);
20804
20973
  const leftBodyCanvasRef = React4.useRef(null);
20805
20974
  const cornerBodyCanvasRef = React4.useRef(null);
20806
- const topHeaderCanvasRef = React4.useRef(null);
20807
- const leftHeaderCanvasRef = React4.useRef(null);
20975
+ const bodyBlitBufferCanvasRef = React4.useRef({
20976
+ corner: null,
20977
+ left: null,
20978
+ scroll: null,
20979
+ top: null
20980
+ });
20981
+ const headerBlitBufferCanvasRef = React4.useRef({
20982
+ left: null,
20983
+ top: null
20984
+ });
20985
+ const topFrozenHeaderCanvasRef = React4.useRef(null);
20986
+ const topScrollHeaderCanvasRef = React4.useRef(null);
20987
+ const leftFrozenHeaderCanvasRef = React4.useRef(null);
20988
+ const leftScrollHeaderCanvasRef = React4.useRef(null);
20808
20989
  const cornerHeaderCanvasRef = React4.useRef(null);
20809
20990
  const selectionOverlayRef = React4.useRef(null);
20810
20991
  const activeValidationOverlayRef = React4.useRef(null);
@@ -21008,16 +21189,31 @@ function XlsxGrid({
21008
21189
  liveZoomTranslateX2,
21009
21190
  liveZoomTranslateY2
21010
21191
  );
21011
- const topHeaderCanvas = topHeaderCanvasRef.current;
21012
- if (topHeaderCanvas) {
21013
- topHeaderCanvas.style.transform = scrollDeltaX !== 0 ? `translate3d(${scrollDeltaX}px, 0, 0)` : "";
21014
- topHeaderCanvas.style.willChange = scrollDeltaX !== 0 ? "transform" : "";
21015
- }
21016
- const leftHeaderCanvas = leftHeaderCanvasRef.current;
21017
- if (leftHeaderCanvas) {
21018
- leftHeaderCanvas.style.transform = scrollDeltaY !== 0 ? `translate3d(0, ${scrollDeltaY}px, 0)` : "";
21019
- leftHeaderCanvas.style.willChange = scrollDeltaY !== 0 ? "transform" : "";
21020
- }
21192
+ applyCanvasTransform(
21193
+ topScrollHeaderCanvasRef.current,
21194
+ scrollDeltaX + liveZoomTranslateX2,
21195
+ liveZoomTranslateY2
21196
+ );
21197
+ applyCanvasTransform(
21198
+ topFrozenHeaderCanvasRef.current,
21199
+ liveZoomTranslateX2,
21200
+ liveZoomTranslateY2
21201
+ );
21202
+ applyCanvasTransform(
21203
+ leftScrollHeaderCanvasRef.current,
21204
+ liveZoomTranslateX2,
21205
+ scrollDeltaY + liveZoomTranslateY2
21206
+ );
21207
+ applyCanvasTransform(
21208
+ leftFrozenHeaderCanvasRef.current,
21209
+ liveZoomTranslateX2,
21210
+ liveZoomTranslateY2
21211
+ );
21212
+ applyCanvasTransform(
21213
+ cornerHeaderCanvasRef.current,
21214
+ liveZoomTranslateX2,
21215
+ liveZoomTranslateY2
21216
+ );
21021
21217
  }, [zoomScale]);
21022
21218
  const updateLiveGestureZoomState = React4.useCallback((nextState) => {
21023
21219
  const resolvedState = typeof nextState === "function" ? nextState(liveGestureZoomRef.current) : nextState;
@@ -21148,6 +21344,22 @@ function XlsxGrid({
21148
21344
  scrollRef.current.style.cursor = "";
21149
21345
  }
21150
21346
  }, []);
21347
+ const getBodyBlitBufferCanvas = React4.useCallback((pane) => {
21348
+ let bufferCanvas = bodyBlitBufferCanvasRef.current[pane];
21349
+ if (!bufferCanvas && typeof document !== "undefined") {
21350
+ bufferCanvas = document.createElement("canvas");
21351
+ bodyBlitBufferCanvasRef.current[pane] = bufferCanvas;
21352
+ }
21353
+ return bufferCanvas;
21354
+ }, []);
21355
+ const getHeaderBlitBufferCanvas = React4.useCallback((axis) => {
21356
+ let bufferCanvas = headerBlitBufferCanvasRef.current[axis];
21357
+ if (!bufferCanvas && typeof document !== "undefined") {
21358
+ bufferCanvas = document.createElement("canvas");
21359
+ headerBlitBufferCanvasRef.current[axis] = bufferCanvas;
21360
+ }
21361
+ return bufferCanvas;
21362
+ }, []);
21151
21363
  const visibleRows = React4.useMemo(() => {
21152
21364
  return buildVisibleAxisIndices(
21153
21365
  activeSheet?.visibleRows ?? [],
@@ -21862,14 +22074,14 @@ function XlsxGrid({
21862
22074
  const handleScrollerScroll = React4.useCallback((event) => {
21863
22075
  const currentScroller = event.currentTarget;
21864
22076
  cachedScrollerRectRef.current = null;
21865
- syncDrawingViewport(currentScroller, { immediate: !experimentalCanvas });
22077
+ syncDrawingViewport(currentScroller, { immediate: true });
21866
22078
  if (currentScroller.scrollHeight - (currentScroller.scrollTop + currentScroller.clientHeight) < OPEN_GRID_VERTICAL_EDGE_PX) {
21867
22079
  setDisplayRowLimit((current) => current + OPEN_GRID_ROW_GROWTH);
21868
22080
  }
21869
22081
  if (currentScroller.scrollWidth - (currentScroller.scrollLeft + currentScroller.clientWidth) < OPEN_GRID_HORIZONTAL_EDGE_PX) {
21870
22082
  setDisplayColLimit((current) => current + OPEN_GRID_COL_GROWTH);
21871
22083
  }
21872
- }, [experimentalCanvas, syncDrawingViewport]);
22084
+ }, [syncDrawingViewport]);
21873
22085
  React4.useEffect(() => {
21874
22086
  const scroller = scrollRef.current;
21875
22087
  if (!scroller || !enableGestureZoom) {
@@ -23734,6 +23946,54 @@ function XlsxGrid({
23734
23946
  rowPrefixSums,
23735
23947
  stickyTopByRow
23736
23948
  ]);
23949
+ const canvasColumnHeaderCells = React4.useMemo(
23950
+ () => canvasVisibleColItems.flatMap((column) => {
23951
+ const rect = resolveCanvasColumnHeaderRect(column.actualCol);
23952
+ if (!rect) {
23953
+ return [];
23954
+ }
23955
+ const isFrozen = stickyLeftByCol.has(column.actualCol);
23956
+ return [{
23957
+ actualCol: column.actualCol,
23958
+ height: displayHeaderHeight,
23959
+ isFrozen,
23960
+ left: rect.left,
23961
+ localLeft: rect.left - (isFrozen ? displayRowHeaderWidth : frozenPaneRight),
23962
+ width: rect.width
23963
+ }];
23964
+ }),
23965
+ [
23966
+ canvasVisibleColItems,
23967
+ displayHeaderHeight,
23968
+ displayRowHeaderWidth,
23969
+ frozenPaneRight,
23970
+ resolveCanvasColumnHeaderRect,
23971
+ stickyLeftByCol
23972
+ ]
23973
+ );
23974
+ const canvasRowHeaderCells = React4.useMemo(
23975
+ () => canvasVisibleRowItems.flatMap((row) => {
23976
+ const rect = resolveCanvasRowHeaderRect(row.actualRow);
23977
+ if (!rect) {
23978
+ return [];
23979
+ }
23980
+ const isFrozen = stickyTopByRow.has(row.actualRow);
23981
+ return [{
23982
+ actualRow: row.actualRow,
23983
+ height: rect.height,
23984
+ isFrozen,
23985
+ localTop: rect.top - (isFrozen ? displayHeaderHeight : frozenPaneBottom),
23986
+ top: rect.top
23987
+ }];
23988
+ }),
23989
+ [
23990
+ canvasVisibleRowItems,
23991
+ displayHeaderHeight,
23992
+ frozenPaneBottom,
23993
+ resolveCanvasRowHeaderRect,
23994
+ stickyTopByRow
23995
+ ]
23996
+ );
23737
23997
  const resolveCanvasColumnResizeTarget = React4.useCallback((clientX) => {
23738
23998
  if (!canResizeHeaders) {
23739
23999
  return null;
@@ -23743,17 +24003,26 @@ function XlsxGrid({
23743
24003
  return null;
23744
24004
  }
23745
24005
  const localX = clientX - scrollerRect.left;
23746
- for (const column of canvasVisibleColItems) {
23747
- const rect = resolveCanvasColumnHeaderRect(column.actualCol);
23748
- if (!rect) {
23749
- continue;
24006
+ for (const column of canvasColumnHeaderCells) {
24007
+ if (Math.abs(localX - (column.left + column.width)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
24008
+ return { actualCol: column.actualCol, width: column.width };
23750
24009
  }
23751
- if (Math.abs(localX - (rect.left + rect.width)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
23752
- return { actualCol: column.actualCol, width: rect.width };
24010
+ }
24011
+ return null;
24012
+ }, [canResizeHeaders, canvasColumnHeaderCells]);
24013
+ const resolveCanvasColumnHeaderTarget = React4.useCallback((clientX) => {
24014
+ const scrollerRect = scrollRef.current?.getBoundingClientRect();
24015
+ if (!scrollerRect) {
24016
+ return null;
24017
+ }
24018
+ const localX = clientX - scrollerRect.left;
24019
+ for (const column of canvasColumnHeaderCells) {
24020
+ if (localX >= column.left && localX <= column.left + column.width) {
24021
+ return column.actualCol;
23753
24022
  }
23754
24023
  }
23755
24024
  return null;
23756
- }, [canResizeHeaders, canvasVisibleColItems, resolveCanvasColumnHeaderRect]);
24025
+ }, [canvasColumnHeaderCells]);
23757
24026
  const resolveCanvasRowResizeTarget = React4.useCallback((clientY) => {
23758
24027
  if (!canResizeHeaders) {
23759
24028
  return null;
@@ -23763,17 +24032,26 @@ function XlsxGrid({
23763
24032
  return null;
23764
24033
  }
23765
24034
  const localY = clientY - scrollerRect.top;
23766
- for (const row of canvasVisibleRowItems) {
23767
- const rect = resolveCanvasRowHeaderRect(row.actualRow);
23768
- if (!rect) {
23769
- continue;
24035
+ for (const row of canvasRowHeaderCells) {
24036
+ if (Math.abs(localY - (row.top + row.height)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
24037
+ return { actualRow: row.actualRow, height: row.height };
23770
24038
  }
23771
- if (Math.abs(localY - (rect.top + rect.height)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
23772
- return { actualRow: row.actualRow, height: rect.height };
24039
+ }
24040
+ return null;
24041
+ }, [canResizeHeaders, canvasRowHeaderCells]);
24042
+ const resolveCanvasRowHeaderTarget = React4.useCallback((clientY) => {
24043
+ const scrollerRect = scrollRef.current?.getBoundingClientRect();
24044
+ if (!scrollerRect) {
24045
+ return null;
24046
+ }
24047
+ const localY = clientY - scrollerRect.top;
24048
+ for (const row of canvasRowHeaderCells) {
24049
+ if (localY >= row.top && localY <= row.top + row.height) {
24050
+ return row.actualRow;
23773
24051
  }
23774
24052
  }
23775
24053
  return null;
23776
- }, [canResizeHeaders, canvasVisibleRowItems, resolveCanvasRowHeaderRect]);
24054
+ }, [canvasRowHeaderCells]);
23777
24055
  const handleCanvasColumnHeaderPointerMove = React4.useCallback((event) => {
23778
24056
  if (resizeStateRef.current?.type === "column") {
23779
24057
  event.currentTarget.style.cursor = "col-resize";
@@ -23876,17 +24154,17 @@ function XlsxGrid({
23876
24154
  startColumnResize(event.pointerId, resizeTarget.actualCol, resizeTarget.width, event.clientX);
23877
24155
  return;
23878
24156
  }
23879
- const cell = resolvePointerCellFromClient(event.clientX, event.clientY);
23880
- if (!cell) {
24157
+ const actualCol = resolveCanvasColumnHeaderTarget(event.clientX);
24158
+ if (actualCol === null) {
23881
24159
  return;
23882
24160
  }
23883
24161
  event.preventDefault();
23884
24162
  focusGrid();
23885
24163
  const currentSelection = selectionRef.current;
23886
- const anchorCol = event.shiftKey && currentSelection ? currentSelection.start.col : cell.col;
24164
+ const anchorCol = event.shiftKey && currentSelection ? currentSelection.start.col : actualCol;
23887
24165
  const initialRange = normalizeRange2({
23888
24166
  start: { row: firstVisibleRow, col: anchorCol },
23889
- end: { row: lastVisibleRow, col: cell.col }
24167
+ end: { row: lastVisibleRow, col: actualCol }
23890
24168
  });
23891
24169
  const anchorColIndex = colIndexByActual.get(anchorCol);
23892
24170
  if (anchorColIndex === void 0) {
@@ -23896,7 +24174,7 @@ function XlsxGrid({
23896
24174
  event.pointerId,
23897
24175
  { row: firstVisibleRow, col: anchorCol },
23898
24176
  "column",
23899
- { row: firstVisibleRow, col: cell.col },
24177
+ { row: firstVisibleRow, col: actualCol },
23900
24178
  {
23901
24179
  contentScaleX: 1,
23902
24180
  contentScaleY: 1,
@@ -23917,9 +24195,8 @@ function XlsxGrid({
23917
24195
  firstVisibleRow,
23918
24196
  focusGrid,
23919
24197
  lastVisibleRow,
23920
- resolveCanvasColumnHeaderRect,
24198
+ resolveCanvasColumnHeaderTarget,
23921
24199
  resolveCanvasColumnResizeTarget,
23922
- resolvePointerCellFromClient,
23923
24200
  rowPrefixSums,
23924
24201
  startCellSelection
23925
24202
  ]);
@@ -23934,17 +24211,17 @@ function XlsxGrid({
23934
24211
  startRowResize(event.pointerId, resizeTarget.actualRow, resizeTarget.height, event.clientY);
23935
24212
  return;
23936
24213
  }
23937
- const cell = resolvePointerCellFromClient(event.clientX, event.clientY);
23938
- if (!cell) {
24214
+ const actualRow = resolveCanvasRowHeaderTarget(event.clientY);
24215
+ if (actualRow === null) {
23939
24216
  return;
23940
24217
  }
23941
24218
  event.preventDefault();
23942
24219
  focusGrid();
23943
24220
  const currentSelection = selectionRef.current;
23944
- const anchorRow = event.shiftKey && currentSelection ? currentSelection.start.row : cell.row;
24221
+ const anchorRow = event.shiftKey && currentSelection ? currentSelection.start.row : actualRow;
23945
24222
  const initialRange = normalizeRange2({
23946
24223
  start: { row: anchorRow, col: firstVisibleCol },
23947
- end: { row: cell.row, col: lastVisibleCol }
24224
+ end: { row: actualRow, col: lastVisibleCol }
23948
24225
  });
23949
24226
  const anchorRowIndex = rowIndexByActual.get(anchorRow);
23950
24227
  if (anchorRowIndex === void 0) {
@@ -23954,7 +24231,7 @@ function XlsxGrid({
23954
24231
  event.pointerId,
23955
24232
  { row: anchorRow, col: firstVisibleCol },
23956
24233
  "row",
23957
- { row: cell.row, col: firstVisibleCol },
24234
+ { row: actualRow, col: firstVisibleCol },
23958
24235
  {
23959
24236
  contentScaleX: 1,
23960
24237
  contentScaleY: 1,
@@ -23974,8 +24251,8 @@ function XlsxGrid({
23974
24251
  firstVisibleCol,
23975
24252
  focusGrid,
23976
24253
  lastVisibleCol,
24254
+ resolveCanvasRowHeaderTarget,
23977
24255
  resolveCanvasRowResizeTarget,
23978
- resolvePointerCellFromClient,
23979
24256
  rowIndexByActual,
23980
24257
  rowPrefixSums,
23981
24258
  startCellSelection
@@ -23985,7 +24262,7 @@ function XlsxGrid({
23985
24262
  return;
23986
24263
  }
23987
24264
  const dpr = typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
23988
- function configureCanvas(canvas, width, height) {
24265
+ function configureCanvas(canvas, width, height, options) {
23989
24266
  if (!canvas) {
23990
24267
  return null;
23991
24268
  }
@@ -24008,7 +24285,9 @@ function XlsxGrid({
24008
24285
  return null;
24009
24286
  }
24010
24287
  context.setTransform(dpr, 0, 0, dpr, 0, 0);
24011
- context.clearRect(0, 0, width, height);
24288
+ if (options?.clear !== false) {
24289
+ context.clearRect(0, 0, width, height);
24290
+ }
24012
24291
  return context;
24013
24292
  }
24014
24293
  const bodyWidth = Math.max(0, drawingViewport.width);
@@ -24040,12 +24319,24 @@ function XlsxGrid({
24040
24319
  rangeSignature,
24041
24320
  rowHeaderWidth,
24042
24321
  rowSignature: nextBodyCanvasSignature.rowSignature,
24322
+ viewportLeft: drawingViewport.left,
24323
+ viewportTop: drawingViewport.top,
24043
24324
  zoomFactor
24044
24325
  };
24045
24326
  const previousBodyCanvasSignature = paintedBodyCanvasSignatureRef.current;
24046
24327
  const previousHeaderCanvasSignature = paintedHeaderCanvasSignatureRef.current;
24328
+ const previousPaintedViewport = paintedDrawingViewportRef.current;
24047
24329
  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);
24048
- 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);
24330
+ 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);
24331
+ const canBlitBody = Boolean(
24332
+ 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
24333
+ );
24334
+ const canBlitTopHeader = Boolean(
24335
+ 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
24336
+ );
24337
+ const canBlitLeftHeader = Boolean(
24338
+ 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
24339
+ );
24049
24340
  if (!shouldRepaintBody && !shouldRepaintHeaders) {
24050
24341
  return;
24051
24342
  }
@@ -24057,6 +24348,10 @@ function XlsxGrid({
24057
24348
  const leftBodyCanvasHeight2 = scrollBodyCanvasHeight2;
24058
24349
  const cornerBodyCanvasWidth2 = leftBodyCanvasWidth2;
24059
24350
  const cornerBodyCanvasHeight2 = topBodyCanvasHeight2;
24351
+ const topFrozenHeaderCanvasWidth2 = leftBodyCanvasWidth2;
24352
+ const topScrollHeaderCanvasWidth2 = scrollBodyCanvasWidth2;
24353
+ const leftFrozenHeaderCanvasHeight2 = topBodyCanvasHeight2;
24354
+ const leftScrollHeaderCanvasHeight2 = scrollBodyCanvasHeight2;
24060
24355
  const paneBounds = {
24061
24356
  corner: {
24062
24357
  height: cornerBodyCanvasHeight2,
@@ -24084,29 +24379,50 @@ function XlsxGrid({
24084
24379
  }
24085
24380
  };
24086
24381
  const bodyContexts = shouldRepaintBody ? {
24087
- corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth2, cornerBodyCanvasHeight2),
24088
- left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth2, leftBodyCanvasHeight2),
24089
- scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth2, scrollBodyCanvasHeight2),
24090
- top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth2, topBodyCanvasHeight2)
24382
+ corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth2, cornerBodyCanvasHeight2, { clear: !canBlitBody }),
24383
+ left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth2, leftBodyCanvasHeight2, { clear: !canBlitBody }),
24384
+ scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth2, scrollBodyCanvasHeight2, { clear: !canBlitBody }),
24385
+ top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth2, topBodyCanvasHeight2, { clear: !canBlitBody })
24091
24386
  } : {
24092
24387
  corner: null,
24093
24388
  left: null,
24094
24389
  scroll: null,
24095
24390
  top: null
24096
24391
  };
24097
- const topHeaderContext = shouldRepaintHeaders ? configureCanvas(topHeaderCanvasRef.current, bodyWidth, headerHeight) : null;
24098
- const leftHeaderContext = shouldRepaintHeaders ? configureCanvas(leftHeaderCanvasRef.current, rowHeaderWidth, bodyHeight) : null;
24392
+ const topHeaderContexts = shouldRepaintHeaders ? {
24393
+ frozen: configureCanvas(topFrozenHeaderCanvasRef.current, topFrozenHeaderCanvasWidth2, headerHeight),
24394
+ scroll: configureCanvas(topScrollHeaderCanvasRef.current, topScrollHeaderCanvasWidth2, headerHeight, { clear: !canBlitTopHeader })
24395
+ } : {
24396
+ frozen: null,
24397
+ scroll: null
24398
+ };
24399
+ const leftHeaderContexts = shouldRepaintHeaders ? {
24400
+ frozen: configureCanvas(leftFrozenHeaderCanvasRef.current, rowHeaderWidth, leftFrozenHeaderCanvasHeight2),
24401
+ scroll: configureCanvas(leftScrollHeaderCanvasRef.current, rowHeaderWidth, leftScrollHeaderCanvasHeight2, { clear: !canBlitLeftHeader })
24402
+ } : {
24403
+ frozen: null,
24404
+ scroll: null
24405
+ };
24099
24406
  const cornerContext = shouldRepaintHeaders ? configureCanvas(cornerHeaderCanvasRef.current, rowHeaderWidth, headerHeight) : null;
24100
- if (shouldRepaintBody && (!bodyContexts.scroll || !bodyContexts.top || !bodyContexts.left || !bodyContexts.corner) || shouldRepaintHeaders && (!topHeaderContext || !leftHeaderContext || !cornerContext)) {
24407
+ 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)) {
24101
24408
  return;
24102
24409
  }
24103
24410
  const showGridLines = activeSheet?.showGridLines ?? true;
24411
+ const sheetSurface = resolveSheetSurface(activeSheet, palette);
24104
24412
  const deferredSpillTextsByPane = {
24105
24413
  corner: [],
24106
24414
  left: [],
24107
24415
  scroll: [],
24108
24416
  top: []
24109
24417
  };
24418
+ const bodyDirtyRectsByPane = {
24419
+ corner: [],
24420
+ left: [],
24421
+ scroll: [],
24422
+ top: []
24423
+ };
24424
+ let topScrollHeaderDirtyRects = buildFullCanvasDirtyRect(topScrollHeaderCanvasWidth2, headerHeight);
24425
+ let leftScrollHeaderDirtyRects = buildFullCanvasDirtyRect(rowHeaderWidth, leftScrollHeaderCanvasHeight2);
24110
24426
  const cellPaneOrder = ["scroll", "top", "left", "corner"];
24111
24427
  if (shouldRepaintBody) {
24112
24428
  for (const pane of Object.keys(bodyContexts)) {
@@ -24115,14 +24431,44 @@ function XlsxGrid({
24115
24431
  if (!context || bounds.width <= 0 || bounds.height <= 0) {
24116
24432
  continue;
24117
24433
  }
24118
- context.fillStyle = resolveSheetSurface(activeSheet, palette);
24119
- context.fillRect(0, 0, bounds.width, bounds.height);
24434
+ let dirtyRects = buildFullCanvasDirtyRect(bounds.width, bounds.height);
24435
+ if (canBlitBody) {
24436
+ const bufferCanvas = getBodyBlitBufferCanvas(pane);
24437
+ const deltaX = pane === "scroll" || pane === "top" ? drawingViewport.left - previousPaintedViewport.left : 0;
24438
+ const deltaY = pane === "scroll" || pane === "left" ? drawingViewport.top - previousPaintedViewport.top : 0;
24439
+ if (bufferCanvas) {
24440
+ const blittedDirtyRects = blitCanvasWithScrollDelta(
24441
+ pane === "corner" ? cornerBodyCanvasRef.current : pane === "left" ? leftBodyCanvasRef.current : pane === "top" ? topBodyCanvasRef.current : scrollBodyCanvasRef.current,
24442
+ context,
24443
+ bufferCanvas,
24444
+ dpr,
24445
+ bounds.width,
24446
+ bounds.height,
24447
+ deltaX,
24448
+ deltaY
24449
+ );
24450
+ if (blittedDirtyRects) {
24451
+ dirtyRects = blittedDirtyRects;
24452
+ }
24453
+ }
24454
+ } else {
24455
+ context.clearRect(0, 0, bounds.width, bounds.height);
24456
+ }
24457
+ bodyDirtyRectsByPane[pane] = dirtyRects;
24458
+ if (dirtyRects.length === 0) {
24459
+ continue;
24460
+ }
24461
+ context.fillStyle = sheetSurface;
24462
+ for (const dirtyRect of dirtyRects) {
24463
+ context.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
24464
+ }
24120
24465
  }
24121
24466
  for (const pane of cellPaneOrder) {
24122
24467
  const paneContext = bodyContexts[pane];
24123
24468
  const paneBoundsForCell = paneBounds[pane];
24469
+ const paneDirtyRects = bodyDirtyRectsByPane[pane];
24124
24470
  const paneAxisItems = canvasPaneAxisItems[pane];
24125
- if (!paneContext || paneBoundsForCell.width <= 0 || paneBoundsForCell.height <= 0 || paneAxisItems.rows.length === 0 || paneAxisItems.cols.length === 0) {
24471
+ if (!paneContext || paneDirtyRects.length === 0 || paneBoundsForCell.width <= 0 || paneBoundsForCell.height <= 0 || paneAxisItems.rows.length === 0 || paneAxisItems.cols.length === 0) {
24126
24472
  continue;
24127
24473
  }
24128
24474
  const drawnMergedAnchorKeys = /* @__PURE__ */ new Set();
@@ -24162,11 +24508,14 @@ function XlsxGrid({
24162
24508
  if (localRect.left + drawableWidth < 0 || localRect.top + localRect.height < 0 || localRect.left > paneBoundsForCell.width || localRect.top > paneBoundsForCell.height) {
24163
24509
  continue;
24164
24510
  }
24511
+ if (!intersectsCanvasDirtyRects(localRect.left, localRect.top, drawableWidth, localRect.height, paneDirtyRects)) {
24512
+ continue;
24513
+ }
24165
24514
  const cellStyle = cellData.style;
24166
24515
  const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellStyle);
24167
- const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellStyle.backgroundColor === "string" ? cellStyle.backgroundColor : resolveSheetSurface(activeSheet, palette));
24516
+ const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellStyle.backgroundColor === "string" ? cellStyle.backgroundColor : sheetSurface);
24168
24517
  const gradientFill = !cellData.conditionalColorScale && typeof cellStyle.backgroundImage === "string" ? resolveCanvasGradientFill(paneContext, localRect, cellStyle.backgroundImage) : null;
24169
- const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellStyle.backgroundColor === "string" && cellStyle.backgroundColor !== resolveSheetSurface(activeSheet, palette);
24518
+ const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellStyle.backgroundColor === "string" && cellStyle.backgroundColor !== sheetSurface;
24170
24519
  paneContext.fillStyle = gradientFill ?? fillColor;
24171
24520
  paneContext.fillRect(localRect.left, localRect.top, localRect.width, localRect.height);
24172
24521
  if (cellData.chartHighlight) {
@@ -24520,55 +24869,139 @@ function XlsxGrid({
24520
24869
  }
24521
24870
  }
24522
24871
  }
24523
- if (shouldRepaintHeaders && topHeaderContext && leftHeaderContext && cornerContext) {
24524
- topHeaderContext.fillStyle = palette.headerSurface;
24525
- topHeaderContext.fillRect(0, 0, bodyWidth, headerHeight);
24526
- topHeaderContext.strokeStyle = palette.border;
24527
- topHeaderContext.lineWidth = 1;
24528
- for (const colItem of canvasVisibleColItems) {
24529
- const rect = resolveCanvasColumnHeaderRect(colItem.actualCol);
24530
- if (!rect || rect.left + rect.width < displayRowHeaderWidth || rect.left > bodyWidth) {
24872
+ if (shouldRepaintHeaders && cornerContext) {
24873
+ const topFrozenHeaderContext = topHeaderContexts.frozen;
24874
+ const topScrollHeaderContext = topHeaderContexts.scroll;
24875
+ const leftFrozenHeaderContext = leftHeaderContexts.frozen;
24876
+ const leftScrollHeaderContext = leftHeaderContexts.scroll;
24877
+ if (canBlitTopHeader) {
24878
+ const bufferCanvas = getHeaderBlitBufferCanvas("top");
24879
+ if (bufferCanvas && topScrollHeaderContext && topScrollHeaderCanvasRef.current) {
24880
+ const blittedDirtyRects = blitCanvasWithScrollDelta(
24881
+ topScrollHeaderCanvasRef.current,
24882
+ topScrollHeaderContext,
24883
+ bufferCanvas,
24884
+ dpr,
24885
+ topScrollHeaderCanvasWidth2,
24886
+ headerHeight,
24887
+ drawingViewport.left - previousPaintedViewport.left,
24888
+ 0
24889
+ );
24890
+ if (blittedDirtyRects) {
24891
+ topScrollHeaderDirtyRects = blittedDirtyRects;
24892
+ }
24893
+ }
24894
+ }
24895
+ if (canBlitLeftHeader) {
24896
+ const bufferCanvas = getHeaderBlitBufferCanvas("left");
24897
+ if (bufferCanvas && leftScrollHeaderContext && leftScrollHeaderCanvasRef.current) {
24898
+ const blittedDirtyRects = blitCanvasWithScrollDelta(
24899
+ leftScrollHeaderCanvasRef.current,
24900
+ leftScrollHeaderContext,
24901
+ bufferCanvas,
24902
+ dpr,
24903
+ rowHeaderWidth,
24904
+ leftScrollHeaderCanvasHeight2,
24905
+ 0,
24906
+ drawingViewport.top - previousPaintedViewport.top
24907
+ );
24908
+ if (blittedDirtyRects) {
24909
+ leftScrollHeaderDirtyRects = blittedDirtyRects;
24910
+ }
24911
+ }
24912
+ }
24913
+ if (topFrozenHeaderContext && topFrozenHeaderCanvasWidth2 > 0) {
24914
+ topFrozenHeaderContext.fillStyle = palette.headerSurface;
24915
+ topFrozenHeaderContext.fillRect(0, 0, topFrozenHeaderCanvasWidth2, headerHeight);
24916
+ }
24917
+ if (topScrollHeaderContext && topScrollHeaderCanvasWidth2 > 0) {
24918
+ topScrollHeaderContext.fillStyle = palette.headerSurface;
24919
+ for (const dirtyRect of topScrollHeaderDirtyRects) {
24920
+ topScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
24921
+ }
24922
+ }
24923
+ if (topFrozenHeaderContext) {
24924
+ topFrozenHeaderContext.strokeStyle = palette.border;
24925
+ topFrozenHeaderContext.lineWidth = 1;
24926
+ }
24927
+ if (topScrollHeaderContext) {
24928
+ topScrollHeaderContext.strokeStyle = palette.border;
24929
+ topScrollHeaderContext.lineWidth = 1;
24930
+ }
24931
+ for (const column of canvasColumnHeaderCells) {
24932
+ const paneContext = column.isFrozen ? topFrozenHeaderContext : topScrollHeaderContext;
24933
+ if (!paneContext) {
24934
+ continue;
24935
+ }
24936
+ if (column.localLeft + column.width < 0 || column.localLeft > (column.isFrozen ? topFrozenHeaderCanvasWidth2 : topScrollHeaderCanvasWidth2)) {
24937
+ continue;
24938
+ }
24939
+ if (!column.isFrozen && !intersectsCanvasDirtyRects(column.localLeft, 0, column.width, column.height, topScrollHeaderDirtyRects)) {
24940
+ continue;
24941
+ }
24942
+ const selected = normalizedVisibleRange && column.actualCol >= normalizedVisibleRange.start.col && column.actualCol <= normalizedVisibleRange.end.col;
24943
+ paneContext.fillStyle = selected ? selectionHeaderSurface : palette.headerSurface;
24944
+ paneContext.fillRect(column.localLeft, 0, column.width, column.height);
24945
+ paneContext.strokeStyle = palette.border;
24946
+ paneContext.beginPath();
24947
+ paneContext.moveTo(column.localLeft + column.width - 0.5, 0);
24948
+ paneContext.lineTo(column.localLeft + column.width - 0.5, column.height);
24949
+ paneContext.moveTo(column.localLeft, column.height - 0.5);
24950
+ paneContext.lineTo(column.localLeft + column.width, column.height - 0.5);
24951
+ paneContext.stroke();
24952
+ paneContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24953
+ paneContext.fillStyle = palette.mutedText;
24954
+ paneContext.textAlign = "center";
24955
+ paneContext.textBaseline = "middle";
24956
+ paneContext.fillText(
24957
+ columnLabel2(column.actualCol),
24958
+ column.localLeft + column.width / 2,
24959
+ column.height / 2
24960
+ );
24961
+ }
24962
+ if (leftFrozenHeaderContext && leftFrozenHeaderCanvasHeight2 > 0) {
24963
+ leftFrozenHeaderContext.fillStyle = palette.rowHeaderSurface;
24964
+ leftFrozenHeaderContext.fillRect(0, 0, rowHeaderWidth, leftFrozenHeaderCanvasHeight2);
24965
+ }
24966
+ if (leftScrollHeaderContext && leftScrollHeaderCanvasHeight2 > 0) {
24967
+ leftScrollHeaderContext.fillStyle = palette.rowHeaderSurface;
24968
+ for (const dirtyRect of leftScrollHeaderDirtyRects) {
24969
+ leftScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
24970
+ }
24971
+ }
24972
+ if (leftFrozenHeaderContext) {
24973
+ leftFrozenHeaderContext.strokeStyle = palette.border;
24974
+ leftFrozenHeaderContext.lineWidth = 1;
24975
+ }
24976
+ if (leftScrollHeaderContext) {
24977
+ leftScrollHeaderContext.strokeStyle = palette.border;
24978
+ leftScrollHeaderContext.lineWidth = 1;
24979
+ }
24980
+ for (const row of canvasRowHeaderCells) {
24981
+ const paneContext = row.isFrozen ? leftFrozenHeaderContext : leftScrollHeaderContext;
24982
+ if (!paneContext) {
24983
+ continue;
24984
+ }
24985
+ if (row.localTop + row.height < 0 || row.localTop > (row.isFrozen ? leftFrozenHeaderCanvasHeight2 : leftScrollHeaderCanvasHeight2)) {
24531
24986
  continue;
24532
24987
  }
24533
- const selected = normalizedVisibleRange && colItem.actualCol >= normalizedVisibleRange.start.col && colItem.actualCol <= normalizedVisibleRange.end.col;
24534
- topHeaderContext.fillStyle = selected ? selectionHeaderSurface : palette.headerSurface;
24535
- topHeaderContext.fillRect(rect.left, 0, rect.width, headerHeight);
24536
- topHeaderContext.strokeStyle = palette.border;
24537
- topHeaderContext.beginPath();
24538
- topHeaderContext.moveTo(rect.left + rect.width - 0.5, 0);
24539
- topHeaderContext.lineTo(rect.left + rect.width - 0.5, headerHeight);
24540
- topHeaderContext.moveTo(rect.left, headerHeight - 0.5);
24541
- topHeaderContext.lineTo(rect.left + rect.width, headerHeight - 0.5);
24542
- topHeaderContext.stroke();
24543
- topHeaderContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24544
- topHeaderContext.fillStyle = palette.mutedText;
24545
- topHeaderContext.textAlign = "center";
24546
- topHeaderContext.textBaseline = "middle";
24547
- topHeaderContext.fillText(columnLabel2(colItem.actualCol), rect.left + rect.width / 2, headerHeight / 2);
24548
- }
24549
- leftHeaderContext.fillStyle = palette.rowHeaderSurface;
24550
- leftHeaderContext.fillRect(0, 0, rowHeaderWidth, bodyHeight);
24551
- leftHeaderContext.strokeStyle = palette.border;
24552
- leftHeaderContext.lineWidth = 1;
24553
- for (const rowItem of canvasVisibleRowItems) {
24554
- const rect = resolveCanvasRowHeaderRect(rowItem.actualRow);
24555
- if (!rect || rect.top + rect.height < displayHeaderHeight || rect.top > bodyHeight) {
24988
+ if (!row.isFrozen && !intersectsCanvasDirtyRects(0, row.localTop, rowHeaderWidth, row.height, leftScrollHeaderDirtyRects)) {
24556
24989
  continue;
24557
24990
  }
24558
- const selected = normalizedVisibleRange && rowItem.actualRow >= normalizedVisibleRange.start.row && rowItem.actualRow <= normalizedVisibleRange.end.row;
24559
- leftHeaderContext.fillStyle = selected ? selectionHeaderSurface : palette.rowHeaderSurface;
24560
- leftHeaderContext.fillRect(0, rect.top, rowHeaderWidth, rect.height);
24561
- leftHeaderContext.beginPath();
24562
- leftHeaderContext.moveTo(0, rect.top + rect.height - 0.5);
24563
- leftHeaderContext.lineTo(rowHeaderWidth, rect.top + rect.height - 0.5);
24564
- leftHeaderContext.moveTo(rowHeaderWidth - 0.5, rect.top);
24565
- leftHeaderContext.lineTo(rowHeaderWidth - 0.5, rect.top + rect.height);
24566
- leftHeaderContext.stroke();
24567
- leftHeaderContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24568
- leftHeaderContext.fillStyle = palette.mutedText;
24569
- leftHeaderContext.textAlign = "center";
24570
- leftHeaderContext.textBaseline = "middle";
24571
- leftHeaderContext.fillText(`${rowItem.actualRow + 1}`, rowHeaderWidth / 2, rect.top + rect.height / 2);
24991
+ const selected = normalizedVisibleRange && row.actualRow >= normalizedVisibleRange.start.row && row.actualRow <= normalizedVisibleRange.end.row;
24992
+ paneContext.fillStyle = selected ? selectionHeaderSurface : palette.rowHeaderSurface;
24993
+ paneContext.fillRect(0, row.localTop, rowHeaderWidth, row.height);
24994
+ paneContext.beginPath();
24995
+ paneContext.moveTo(0, row.localTop + row.height - 0.5);
24996
+ paneContext.lineTo(rowHeaderWidth, row.localTop + row.height - 0.5);
24997
+ paneContext.moveTo(rowHeaderWidth - 0.5, row.localTop);
24998
+ paneContext.lineTo(rowHeaderWidth - 0.5, row.localTop + row.height);
24999
+ paneContext.stroke();
25000
+ paneContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
25001
+ paneContext.fillStyle = palette.mutedText;
25002
+ paneContext.textAlign = "center";
25003
+ paneContext.textBaseline = "middle";
25004
+ paneContext.fillText(`${row.actualRow + 1}`, rowHeaderWidth / 2, row.localTop + row.height / 2);
24572
25005
  }
24573
25006
  cornerContext.fillStyle = palette.rowHeaderSurface;
24574
25007
  cornerContext.fillRect(0, 0, rowHeaderWidth, headerHeight);
@@ -24592,7 +25025,9 @@ function XlsxGrid({
24592
25025
  }, [
24593
25026
  activeSheet,
24594
25027
  applyCanvasViewportCompensation,
25028
+ canvasColumnHeaderCells,
24595
25029
  canvasPaneAxisItems,
25030
+ canvasRowHeaderCells,
24596
25031
  canvasVisibleColItems,
24597
25032
  canvasVisibleRowItems,
24598
25033
  colIndexByActual,
@@ -24607,11 +25042,13 @@ function XlsxGrid({
24607
25042
  drawingViewport.height,
24608
25043
  drawingViewport.width,
24609
25044
  experimentalCanvas,
25045
+ frozenPaneBottom,
25046
+ frozenPaneRight,
24610
25047
  getCellData,
25048
+ getBodyBlitBufferCanvas,
25049
+ getHeaderBlitBufferCanvas,
24611
25050
  palette,
24612
25051
  resolveCellDisplayRect,
24613
- resolveCanvasColumnHeaderRect,
24614
- resolveCanvasRowHeaderRect,
24615
25052
  resolveMergeAnchorCell,
24616
25053
  resizeGuide,
24617
25054
  rowIndexByActual,
@@ -24905,6 +25342,10 @@ function XlsxGrid({
24905
25342
  const leftBodyCanvasHeight = scrollBodyCanvasHeight;
24906
25343
  const cornerBodyCanvasWidth = leftBodyCanvasWidth;
24907
25344
  const cornerBodyCanvasHeight = topBodyCanvasHeight;
25345
+ const topFrozenHeaderCanvasWidth = leftBodyCanvasWidth;
25346
+ const topScrollHeaderCanvasWidth = scrollBodyCanvasWidth;
25347
+ const leftFrozenHeaderCanvasHeight = topBodyCanvasHeight;
25348
+ const leftScrollHeaderCanvasHeight = scrollBodyCanvasHeight;
24908
25349
  const canvasBodyBaseStyle = {
24909
25350
  cursor: "cell",
24910
25351
  pointerEvents: "auto",
@@ -24940,22 +25381,38 @@ function XlsxGrid({
24940
25381
  top: displayHeaderHeight,
24941
25382
  zIndex: 31
24942
25383
  };
24943
- const canvasTopHeaderStyle = {
25384
+ const canvasHeaderBaseStyle = {
24944
25385
  cursor: "default",
24945
- display: drawingViewport.width > 0 && drawingViewport.height > 0 ? "block" : "none",
24946
- left: 0,
24947
25386
  pointerEvents: "auto",
24948
25387
  position: "absolute",
25388
+ transformOrigin: "0 0"
25389
+ };
25390
+ const canvasTopFrozenHeaderStyle = {
25391
+ ...canvasHeaderBaseStyle,
25392
+ display: topFrozenHeaderCanvasWidth > 0 && drawingViewport.height > 0 ? "block" : "none",
25393
+ left: displayRowHeaderWidth,
25394
+ top: 0,
25395
+ zIndex: canvasHeaderOverlayZIndex + 1
25396
+ };
25397
+ const canvasTopScrollHeaderStyle = {
25398
+ ...canvasHeaderBaseStyle,
25399
+ display: topScrollHeaderCanvasWidth > 0 && drawingViewport.height > 0 ? "block" : "none",
25400
+ left: frozenPaneRight,
24949
25401
  top: 0,
24950
25402
  zIndex: canvasHeaderOverlayZIndex
24951
25403
  };
24952
- const canvasLeftHeaderStyle = {
24953
- cursor: "default",
24954
- display: drawingViewport.width > 0 && drawingViewport.height > 0 ? "block" : "none",
25404
+ const canvasLeftFrozenHeaderStyle = {
25405
+ ...canvasHeaderBaseStyle,
25406
+ display: leftFrozenHeaderCanvasHeight > 0 && drawingViewport.width > 0 ? "block" : "none",
24955
25407
  left: 0,
24956
- pointerEvents: "auto",
24957
- position: "absolute",
24958
- top: 0,
25408
+ top: displayHeaderHeight,
25409
+ zIndex: canvasHeaderOverlayZIndex + 1
25410
+ };
25411
+ const canvasLeftScrollHeaderStyle = {
25412
+ ...canvasHeaderBaseStyle,
25413
+ display: leftScrollHeaderCanvasHeight > 0 && drawingViewport.width > 0 ? "block" : "none",
25414
+ left: 0,
25415
+ top: frozenPaneBottom,
24959
25416
  zIndex: canvasHeaderOverlayZIndex
24960
25417
  };
24961
25418
  const canvasCornerHeaderStyle = {
@@ -24964,6 +25421,7 @@ function XlsxGrid({
24964
25421
  pointerEvents: "none",
24965
25422
  position: "absolute",
24966
25423
  top: 0,
25424
+ transformOrigin: "0 0",
24967
25425
  zIndex: canvasHeaderOverlayZIndex + 1
24968
25426
  };
24969
25427
  const editingOverlayRect = experimentalCanvas && editingCell ? resolveCellDisplayRect(editingCell) : null;
@@ -26110,7 +26568,7 @@ function XlsxGrid({
26110
26568
  pointerEvents: "none",
26111
26569
  position: "absolute",
26112
26570
  top: 0,
26113
- transform: `translate(${-drawingViewport.left}px, ${-drawingViewport.top}px)`,
26571
+ transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
26114
26572
  width: totalWidth
26115
26573
  },
26116
26574
  children: paneDrawingNodes.scroll
@@ -26125,7 +26583,7 @@ function XlsxGrid({
26125
26583
  pointerEvents: "none",
26126
26584
  position: "absolute",
26127
26585
  top: 0,
26128
- transform: `translate(${-drawingViewport.left}px, ${-displayHeaderHeight}px)`,
26586
+ transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-displayHeaderHeight}px)`,
26129
26587
  width: totalWidth
26130
26588
  },
26131
26589
  children: paneDrawingNodes.top
@@ -26140,7 +26598,7 @@ function XlsxGrid({
26140
26598
  pointerEvents: "none",
26141
26599
  position: "absolute",
26142
26600
  top: 0,
26143
- transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top}px)`,
26601
+ transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
26144
26602
  width: totalWidth
26145
26603
  },
26146
26604
  children: paneDrawingNodes.left
@@ -26167,21 +26625,41 @@ function XlsxGrid({
26167
26625
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26168
26626
  "canvas",
26169
26627
  {
26170
- ref: topHeaderCanvasRef,
26628
+ ref: topFrozenHeaderCanvasRef,
26171
26629
  onPointerLeave: handleCanvasHeaderPointerLeave,
26172
26630
  onPointerMove: handleCanvasColumnHeaderPointerMove,
26173
26631
  onPointerDown: handleCanvasColumnHeaderPointerDown,
26174
- style: canvasTopHeaderStyle
26632
+ style: canvasTopFrozenHeaderStyle
26633
+ }
26634
+ ),
26635
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26636
+ "canvas",
26637
+ {
26638
+ ref: topScrollHeaderCanvasRef,
26639
+ onPointerLeave: handleCanvasHeaderPointerLeave,
26640
+ onPointerMove: handleCanvasColumnHeaderPointerMove,
26641
+ onPointerDown: handleCanvasColumnHeaderPointerDown,
26642
+ style: canvasTopScrollHeaderStyle
26643
+ }
26644
+ ),
26645
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26646
+ "canvas",
26647
+ {
26648
+ ref: leftFrozenHeaderCanvasRef,
26649
+ onPointerLeave: handleCanvasHeaderPointerLeave,
26650
+ onPointerMove: handleCanvasRowHeaderPointerMove,
26651
+ onPointerDown: handleCanvasRowHeaderPointerDown,
26652
+ style: canvasLeftFrozenHeaderStyle
26175
26653
  }
26176
26654
  ),
26177
26655
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26178
26656
  "canvas",
26179
26657
  {
26180
- ref: leftHeaderCanvasRef,
26658
+ ref: leftScrollHeaderCanvasRef,
26181
26659
  onPointerLeave: handleCanvasHeaderPointerLeave,
26182
26660
  onPointerMove: handleCanvasRowHeaderPointerMove,
26183
26661
  onPointerDown: handleCanvasRowHeaderPointerDown,
26184
- style: canvasLeftHeaderStyle
26662
+ style: canvasLeftScrollHeaderStyle
26185
26663
  }
26186
26664
  ),
26187
26665
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("canvas", { ref: cornerHeaderCanvasRef, style: canvasCornerHeaderStyle })