@extend-ai/react-xlsx 0.7.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
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  useXlsxViewerImages: () => useXlsxViewerImages,
42
42
  useXlsxViewerSelection: () => useXlsxViewerSelection,
43
43
  useXlsxViewerTables: () => useXlsxViewerTables,
44
+ useXlsxViewerThumbnails: () => useXlsxViewerThumbnails,
44
45
  useXlsxViewerZoom: () => useXlsxViewerZoom
45
46
  });
46
47
  module.exports = __toCommonJS(index_exports);
@@ -4267,13 +4268,36 @@ function parseWorkbookTableMetadata(archive, workbookSheets) {
4267
4268
  }
4268
4269
  return [{
4269
4270
  displayName: tableNode.getAttribute("displayName") ?? void 0,
4271
+ headerRowCount: parseWorkbookTableCount(tableNode.getAttribute("headerRowCount"), 1),
4270
4272
  headerRowCellStyle: tableNode.getAttribute("headerRowCellStyle") ?? void 0,
4271
4273
  name: tableNode.getAttribute("name") ?? void 0,
4272
- 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)
4273
4277
  }];
4274
4278
  });
4275
4279
  });
4276
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
+ }
4277
4301
  function parseSqrefRanges(sqref) {
4278
4302
  if (!sqref) {
4279
4303
  return [];
@@ -6880,17 +6904,18 @@ function rangeContainsCell(range, cell) {
6880
6904
  function mapWorksheetTables(worksheet, metadataForSheet) {
6881
6905
  const rawTables = worksheet?.tables ?? [];
6882
6906
  return rawTables.flatMap((table, index) => {
6883
- const reference = typeof table.reference === "string" ? table.reference : "";
6884
- const parsedRange = parseA1RangeReference2(reference);
6885
- if (!parsedRange) {
6886
- return [];
6887
- }
6888
6907
  const rawColumns = Array.isArray(table.columns) ? table.columns : [];
6889
6908
  const rawName = typeof table.name === "string" ? table.name : `Table${index + 1}`;
6890
6909
  const rawDisplayName = typeof table.displayName === "string" ? table.displayName : typeof table.name === "string" ? table.name : `Table ${index + 1}`;
6891
6910
  const metadata = metadataForSheet?.find(
6892
- (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
6893
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
+ }
6894
6919
  return [{
6895
6920
  columns: rawColumns.map((column, columnIndex) => ({
6896
6921
  id: typeof column.id === "number" ? column.id ?? columnIndex + 1 : columnIndex + 1,
@@ -6899,17 +6924,47 @@ function mapWorksheetTables(worksheet, metadataForSheet) {
6899
6924
  })),
6900
6925
  displayName: rawDisplayName,
6901
6926
  end: parsedRange.end,
6902
- headerRowCount: typeof table.headerRowCount === "number" ? table.headerRowCount : 1,
6927
+ headerRowCount: metadata?.headerRowCount ?? resolveWorkbookTableCount(table.headerRowCount, 1),
6903
6928
  headerRowCellStyle: metadata?.headerRowCellStyle,
6904
6929
  name: rawName,
6905
6930
  reference,
6906
6931
  start: parsedRange.start,
6907
6932
  styleInfo: table.styleInfo,
6908
- totalsRowCount: typeof table.totalsRowCount === "number" ? table.totalsRowCount : 0,
6909
- totalsRowShown: Boolean(table.totalsRowShown)
6933
+ totalsRowCount: metadata?.totalsRowCount ?? resolveWorkbookTableCount(table.totalsRowCount, 0),
6934
+ totalsRowShown: metadata?.totalsRowShown ?? resolveWorkbookTableBoolean(table.totalsRowShown)
6910
6935
  }];
6911
6936
  });
6912
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
+ }
6913
6968
  function fileStem(fileName) {
6914
6969
  const normalized = fileName.trim();
6915
6970
  const lastDot = normalized.lastIndexOf(".");
@@ -7040,7 +7095,17 @@ async function resolveWorkbookBuffer({ file, src }, signal) {
7040
7095
  if (file) {
7041
7096
  buffer = file;
7042
7097
  } else if (src) {
7043
- 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
+ }
7044
7109
  if (!response.ok) {
7045
7110
  throw new Error(`Failed to fetch workbook (status ${response.status})`);
7046
7111
  }
@@ -7596,6 +7661,7 @@ function downloadUrl(src, fileName) {
7596
7661
  }
7597
7662
  function useXlsxViewerController(options) {
7598
7663
  const {
7664
+ allowResizeInReadOnly = false,
7599
7665
  deferLoadingAboveBytes = DEFAULT_DEFER_LOADING_ABOVE_BYTES,
7600
7666
  file,
7601
7667
  fileName,
@@ -7649,6 +7715,7 @@ function useXlsxViewerController(options) {
7649
7715
  const displayFileName = React.useMemo(() => resolveDisplayFileName(src, fileName), [fileName, src]);
7650
7716
  const shouldDeferLoading = deferLoadingAboveBytes > 0;
7651
7717
  const readOnly = requestedReadOnly || forcedReadOnly;
7718
+ const canResizeReadOnly = requestedReadOnly && allowResizeInReadOnly && !forcedReadOnly;
7652
7719
  const workerSupported = useWorker && typeof Worker !== "undefined";
7653
7720
  const shouldUseWorker = workerSupported && forcedReadOnly;
7654
7721
  const shouldForceReadOnlyForBuffer = React.useCallback((bufferByteLength) => !requestedReadOnly && readOnlyAboveBytes > 0 && bufferByteLength > readOnlyAboveBytes, [readOnlyAboveBytes, requestedReadOnly]);
@@ -8943,7 +9010,7 @@ function useXlsxViewerController(options) {
8943
9010
  refreshWorkbookState(workbook);
8944
9011
  }, [refreshWorkbookState, workbook]);
8945
9012
  const resizeColumn = React.useCallback((col, widthPx) => {
8946
- if (readOnly || !workbook || !activeSheet) {
9013
+ if (readOnly && !canResizeReadOnly || !workbook || !activeSheet) {
8947
9014
  return;
8948
9015
  }
8949
9016
  recordHistoryBeforeMutation();
@@ -8953,9 +9020,9 @@ function useXlsxViewerController(options) {
8953
9020
  pxToSheetColumnWidth(resolveContentSheetAxisPixels(widthPx, activeSheet.showGridLines))
8954
9021
  );
8955
9022
  refreshWorkbookState(workbook);
8956
- }, [activeSheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9023
+ }, [activeSheet, canResizeReadOnly, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8957
9024
  const resizeRow = React.useCallback((row, heightPx) => {
8958
- if (readOnly || !workbook || !activeSheet) {
9025
+ if (readOnly && !canResizeReadOnly || !workbook || !activeSheet) {
8959
9026
  return;
8960
9027
  }
8961
9028
  recordHistoryBeforeMutation();
@@ -8965,7 +9032,7 @@ function useXlsxViewerController(options) {
8965
9032
  pxToSheetRowHeight(resolveContentSheetAxisPixels(heightPx, activeSheet.showGridLines))
8966
9033
  );
8967
9034
  refreshWorkbookState(workbook);
8968
- }, [activeSheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9035
+ }, [activeSheet, canResizeReadOnly, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
8969
9036
  const resolveAnchoredObjectRect = React.useCallback((anchor, worksheet) => {
8970
9037
  const resolveAxisSum = (index, getSize) => {
8971
9038
  let total = 0;
@@ -16348,6 +16415,13 @@ var IMAGE_MIN_SIZE_PX = 16;
16348
16415
  var IMAGE_HANDLE_SIZE_PX = 10;
16349
16416
  var CANVAS_RESIZE_HIT_SLOP_PX = 8;
16350
16417
  var CANVAS_VIEWPORT_OVERSCAN_PX = 240;
16418
+ var THUMBNAIL_DEFAULT_MAX_DIMENSION = 192;
16419
+ var THUMBNAIL_FALLBACK_ROWS = 12;
16420
+ var THUMBNAIL_FALLBACK_COLS = 8;
16421
+ var THUMBNAIL_MAX_ROWS = 200;
16422
+ var THUMBNAIL_MAX_COLS = 80;
16423
+ var THUMBNAIL_MAX_SOURCE_HEIGHT_PX = 900;
16424
+ var THUMBNAIL_MAX_SOURCE_WIDTH_PX = 1440;
16351
16425
  var LIVE_ZOOM_COMMIT_IDLE_MS = 48;
16352
16426
  var WHEEL_ZOOM_SENSITIVITY = 25e-5;
16353
16427
  var WHEEL_LINE_DELTA_PX = 16;
@@ -18354,6 +18428,97 @@ function resolveImageHandleStyle(position, stroke, surface, scale = 1) {
18354
18428
  function useViewerPalette(isDark = false) {
18355
18429
  return isDark ? DARK_PALETTE : LIGHT_PALETTE;
18356
18430
  }
18431
+ function normalizeThumbnailResolution(resolution) {
18432
+ if (typeof resolution === "number" && Number.isFinite(resolution) && resolution > 0) {
18433
+ return {
18434
+ maxHeight: resolution,
18435
+ maxWidth: resolution
18436
+ };
18437
+ }
18438
+ const resolvedObject = typeof resolution === "object" && resolution ? resolution : void 0;
18439
+ return {
18440
+ maxHeight: resolvedObject?.maxHeight && Number.isFinite(resolvedObject.maxHeight) && resolvedObject.maxHeight > 0 ? resolvedObject.maxHeight : THUMBNAIL_DEFAULT_MAX_DIMENSION,
18441
+ maxWidth: resolvedObject?.maxWidth && Number.isFinite(resolvedObject.maxWidth) && resolvedObject.maxWidth > 0 ? resolvedObject.maxWidth : THUMBNAIL_DEFAULT_MAX_DIMENSION
18442
+ };
18443
+ }
18444
+ function resolveThumbnailOutputSize(sourceWidth, sourceHeight, resolution) {
18445
+ const normalized = normalizeThumbnailResolution(resolution);
18446
+ const safeSourceWidth = Math.max(1, sourceWidth);
18447
+ const safeSourceHeight = Math.max(1, sourceHeight);
18448
+ const scale = Math.min(
18449
+ normalized.maxWidth / safeSourceWidth,
18450
+ normalized.maxHeight / safeSourceHeight,
18451
+ 1
18452
+ );
18453
+ return {
18454
+ height: Math.max(1, Math.round(safeSourceHeight * scale)),
18455
+ maxHeight: normalized.maxHeight,
18456
+ maxWidth: normalized.maxWidth,
18457
+ scale,
18458
+ width: Math.max(1, Math.round(safeSourceWidth * scale))
18459
+ };
18460
+ }
18461
+ function resolveThumbnailAxisIndices({
18462
+ fallbackCount,
18463
+ getSizePx,
18464
+ maxCount,
18465
+ maxLogicalPixels,
18466
+ maxUsedIndex,
18467
+ minUsedIndex,
18468
+ precomputed
18469
+ }) {
18470
+ const sourceIndices = precomputed.length > 0 ? precomputed : Array.from({
18471
+ length: Math.max(fallbackCount, Math.max(0, maxUsedIndex + 1))
18472
+ }, (_, index) => index);
18473
+ const hasUsedRange = maxUsedIndex >= minUsedIndex && maxUsedIndex >= 0;
18474
+ const preferredStart = hasUsedRange ? Math.max(0, minUsedIndex) : 0;
18475
+ const preferredEnd = hasUsedRange ? maxUsedIndex : Math.max(0, preferredStart + fallbackCount - 1);
18476
+ const picked = [];
18477
+ let totalSize = 0;
18478
+ for (const actualIndex of sourceIndices) {
18479
+ if (actualIndex < preferredStart) {
18480
+ continue;
18481
+ }
18482
+ if (hasUsedRange && actualIndex > preferredEnd) {
18483
+ break;
18484
+ }
18485
+ const size = Math.max(1, getSizePx(actualIndex));
18486
+ picked.push(actualIndex);
18487
+ totalSize += size;
18488
+ if (picked.length >= maxCount || totalSize >= maxLogicalPixels) {
18489
+ break;
18490
+ }
18491
+ }
18492
+ if (picked.length > 0) {
18493
+ return picked;
18494
+ }
18495
+ for (const actualIndex of sourceIndices) {
18496
+ const size = Math.max(1, getSizePx(actualIndex));
18497
+ picked.push(actualIndex);
18498
+ totalSize += size;
18499
+ if (picked.length >= fallbackCount || picked.length >= maxCount || totalSize >= maxLogicalPixels) {
18500
+ break;
18501
+ }
18502
+ }
18503
+ return picked.length > 0 ? picked : [0];
18504
+ }
18505
+ function buildThumbnailAxisItems(actualIndices, getSizePx) {
18506
+ const items = [];
18507
+ let offset = 0;
18508
+ for (const actualIndex of actualIndices) {
18509
+ const size = Math.max(1, getSizePx(actualIndex));
18510
+ items.push({
18511
+ actualIndex,
18512
+ size,
18513
+ start: offset
18514
+ });
18515
+ offset += size;
18516
+ }
18517
+ return {
18518
+ items,
18519
+ totalSize: offset
18520
+ };
18521
+ }
18357
18522
  function columnLabel2(col) {
18358
18523
  let label = "";
18359
18524
  let nextValue = col;
@@ -19260,7 +19425,6 @@ function getTableHeaderColumn(table, row, col) {
19260
19425
  return table.columns[index] ?? null;
19261
19426
  }
19262
19427
  function DefaultTableHeaderMenu({
19263
- close,
19264
19428
  direction,
19265
19429
  sortAscending,
19266
19430
  sortDescending
@@ -19282,10 +19446,7 @@ function DefaultTableHeaderMenu({
19282
19446
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
19283
19447
  "button",
19284
19448
  {
19285
- onClick: () => {
19286
- sortAscending();
19287
- close();
19288
- },
19449
+ onClick: sortAscending,
19289
19450
  style: {
19290
19451
  background: direction === "ascending" ? "var(--xlsx-menu-active)" : "transparent",
19291
19452
  border: "none",
@@ -19303,10 +19464,7 @@ function DefaultTableHeaderMenu({
19303
19464
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
19304
19465
  "button",
19305
19466
  {
19306
- onClick: () => {
19307
- sortDescending();
19308
- close();
19309
- },
19467
+ onClick: sortDescending,
19310
19468
  style: {
19311
19469
  background: direction === "descending" ? "var(--xlsx-menu-active)" : "transparent",
19312
19470
  border: "none",
@@ -19325,6 +19483,25 @@ function DefaultTableHeaderMenu({
19325
19483
  }
19326
19484
  );
19327
19485
  }
19486
+ function resolveTableHeaderTriggerStyle(palette, zoomFactor) {
19487
+ return {
19488
+ alignItems: "center",
19489
+ background: "transparent",
19490
+ border: "none",
19491
+ color: palette.mutedText,
19492
+ cursor: "pointer",
19493
+ display: "inline-flex",
19494
+ fontSize: 10 * zoomFactor,
19495
+ height: 16 * zoomFactor,
19496
+ justifyContent: "center",
19497
+ padding: 0,
19498
+ position: "absolute",
19499
+ right: 4 * zoomFactor,
19500
+ top: 3 * zoomFactor,
19501
+ width: 16 * zoomFactor,
19502
+ zIndex: 6
19503
+ };
19504
+ }
19328
19505
  function SegmentedControl({
19329
19506
  items,
19330
19507
  onValueChange,
@@ -19833,6 +20010,109 @@ function resolveSelectionColors({
19833
20010
  stroke
19834
20011
  };
19835
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
+ }
19836
20116
  function buildConditionalFormatRuleKey(rule) {
19837
20117
  return `${rule.kind}:${rule.priority}:${rule.ranges.map((range) => `${range.start.row}:${range.start.col}-${range.end.row}:${range.end.col}`).join("|")}`;
19838
20118
  }
@@ -20692,8 +20972,20 @@ function XlsxGrid({
20692
20972
  const topBodyCanvasRef = React4.useRef(null);
20693
20973
  const leftBodyCanvasRef = React4.useRef(null);
20694
20974
  const cornerBodyCanvasRef = React4.useRef(null);
20695
- const topHeaderCanvasRef = React4.useRef(null);
20696
- 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);
20697
20989
  const cornerHeaderCanvasRef = React4.useRef(null);
20698
20990
  const selectionOverlayRef = React4.useRef(null);
20699
20991
  const activeValidationOverlayRef = React4.useRef(null);
@@ -20897,16 +21189,31 @@ function XlsxGrid({
20897
21189
  liveZoomTranslateX2,
20898
21190
  liveZoomTranslateY2
20899
21191
  );
20900
- const topHeaderCanvas = topHeaderCanvasRef.current;
20901
- if (topHeaderCanvas) {
20902
- topHeaderCanvas.style.transform = scrollDeltaX !== 0 ? `translate3d(${scrollDeltaX}px, 0, 0)` : "";
20903
- topHeaderCanvas.style.willChange = scrollDeltaX !== 0 ? "transform" : "";
20904
- }
20905
- const leftHeaderCanvas = leftHeaderCanvasRef.current;
20906
- if (leftHeaderCanvas) {
20907
- leftHeaderCanvas.style.transform = scrollDeltaY !== 0 ? `translate3d(0, ${scrollDeltaY}px, 0)` : "";
20908
- leftHeaderCanvas.style.willChange = scrollDeltaY !== 0 ? "transform" : "";
20909
- }
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
+ );
20910
21217
  }, [zoomScale]);
20911
21218
  const updateLiveGestureZoomState = React4.useCallback((nextState) => {
20912
21219
  const resolvedState = typeof nextState === "function" ? nextState(liveGestureZoomRef.current) : nextState;
@@ -21037,6 +21344,22 @@ function XlsxGrid({
21037
21344
  scrollRef.current.style.cursor = "";
21038
21345
  }
21039
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
+ }, []);
21040
21363
  const visibleRows = React4.useMemo(() => {
21041
21364
  return buildVisibleAxisIndices(
21042
21365
  activeSheet?.visibleRows ?? [],
@@ -21751,14 +22074,14 @@ function XlsxGrid({
21751
22074
  const handleScrollerScroll = React4.useCallback((event) => {
21752
22075
  const currentScroller = event.currentTarget;
21753
22076
  cachedScrollerRectRef.current = null;
21754
- syncDrawingViewport(currentScroller, { immediate: !experimentalCanvas });
22077
+ syncDrawingViewport(currentScroller, { immediate: true });
21755
22078
  if (currentScroller.scrollHeight - (currentScroller.scrollTop + currentScroller.clientHeight) < OPEN_GRID_VERTICAL_EDGE_PX) {
21756
22079
  setDisplayRowLimit((current) => current + OPEN_GRID_ROW_GROWTH);
21757
22080
  }
21758
22081
  if (currentScroller.scrollWidth - (currentScroller.scrollLeft + currentScroller.clientWidth) < OPEN_GRID_HORIZONTAL_EDGE_PX) {
21759
22082
  setDisplayColLimit((current) => current + OPEN_GRID_COL_GROWTH);
21760
22083
  }
21761
- }, [experimentalCanvas, syncDrawingViewport]);
22084
+ }, [syncDrawingViewport]);
21762
22085
  React4.useEffect(() => {
21763
22086
  const scroller = scrollRef.current;
21764
22087
  if (!scroller || !enableGestureZoom) {
@@ -23539,6 +23862,30 @@ function XlsxGrid({
23539
23862
  return null;
23540
23863
  }
23541
23864
  const direction = sortState && sortState.tableName === table.name && sortState.columnIndex === tableColumn.index ? sortState.direction : null;
23865
+ const triggerIcon = direction === "ascending" ? "\u25B2" : direction === "descending" ? "\u25BC" : "\u25BE";
23866
+ if (renderTableHeaderMenu) {
23867
+ return renderTableHeaderMenu({
23868
+ cell,
23869
+ column: tableColumn,
23870
+ direction,
23871
+ sortAscending: () => sortTable(table.name, tableColumn.index, "ascending"),
23872
+ sortDescending: () => sortTable(table.name, tableColumn.index, "descending"),
23873
+ table,
23874
+ triggerIcon,
23875
+ triggerProps: {
23876
+ "aria-haspopup": "menu",
23877
+ "aria-label": `Open menu for ${tableColumn.name}`,
23878
+ onClick: (event) => {
23879
+ event.stopPropagation();
23880
+ },
23881
+ onPointerDown: (event) => {
23882
+ event.stopPropagation();
23883
+ },
23884
+ style: resolveTableHeaderTriggerStyle(palette, zoomFactor),
23885
+ type: "button"
23886
+ }
23887
+ });
23888
+ }
23542
23889
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
23543
23890
  "button",
23544
23891
  {
@@ -23554,27 +23901,13 @@ function XlsxGrid({
23554
23901
  );
23555
23902
  },
23556
23903
  style: {
23557
- alignItems: "center",
23558
- background: "transparent",
23559
- border: "none",
23560
- color: palette.mutedText,
23561
- cursor: "pointer",
23562
- display: "inline-flex",
23563
- fontSize: 10 * zoomFactor,
23564
- height: 16 * zoomFactor,
23565
- justifyContent: "center",
23566
- padding: 0,
23567
- position: "absolute",
23568
- right: 4 * zoomFactor,
23569
- top: 3 * zoomFactor,
23570
- width: 16 * zoomFactor,
23571
- zIndex: 6
23904
+ ...resolveTableHeaderTriggerStyle(palette, zoomFactor)
23572
23905
  },
23573
23906
  type: "button",
23574
- children: direction === "ascending" ? "\u25B2" : direction === "descending" ? "\u25BC" : "\u25BE"
23907
+ children: triggerIcon
23575
23908
  }
23576
23909
  );
23577
- }, [effectiveTables, palette.mutedText, sortState, zoomFactor]);
23910
+ }, [effectiveTables, palette, renderTableHeaderMenu, sortState, sortTable, zoomFactor]);
23578
23911
  const resolveCanvasColumnHeaderRect = React4.useCallback((actualCol) => {
23579
23912
  const colIndex = colIndexByActual.get(actualCol);
23580
23913
  if (colIndex === void 0) {
@@ -23613,6 +23946,54 @@ function XlsxGrid({
23613
23946
  rowPrefixSums,
23614
23947
  stickyTopByRow
23615
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
+ );
23616
23997
  const resolveCanvasColumnResizeTarget = React4.useCallback((clientX) => {
23617
23998
  if (!canResizeHeaders) {
23618
23999
  return null;
@@ -23622,17 +24003,26 @@ function XlsxGrid({
23622
24003
  return null;
23623
24004
  }
23624
24005
  const localX = clientX - scrollerRect.left;
23625
- for (const column of canvasVisibleColItems) {
23626
- const rect = resolveCanvasColumnHeaderRect(column.actualCol);
23627
- if (!rect) {
23628
- 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 };
23629
24009
  }
23630
- if (Math.abs(localX - (rect.left + rect.width)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
23631
- 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;
23632
24022
  }
23633
24023
  }
23634
24024
  return null;
23635
- }, [canResizeHeaders, canvasVisibleColItems, resolveCanvasColumnHeaderRect]);
24025
+ }, [canvasColumnHeaderCells]);
23636
24026
  const resolveCanvasRowResizeTarget = React4.useCallback((clientY) => {
23637
24027
  if (!canResizeHeaders) {
23638
24028
  return null;
@@ -23642,17 +24032,26 @@ function XlsxGrid({
23642
24032
  return null;
23643
24033
  }
23644
24034
  const localY = clientY - scrollerRect.top;
23645
- for (const row of canvasVisibleRowItems) {
23646
- const rect = resolveCanvasRowHeaderRect(row.actualRow);
23647
- if (!rect) {
23648
- 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 };
23649
24038
  }
23650
- if (Math.abs(localY - (rect.top + rect.height)) <= CANVAS_RESIZE_HIT_SLOP_PX) {
23651
- 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;
23652
24051
  }
23653
24052
  }
23654
24053
  return null;
23655
- }, [canResizeHeaders, canvasVisibleRowItems, resolveCanvasRowHeaderRect]);
24054
+ }, [canvasRowHeaderCells]);
23656
24055
  const handleCanvasColumnHeaderPointerMove = React4.useCallback((event) => {
23657
24056
  if (resizeStateRef.current?.type === "column") {
23658
24057
  event.currentTarget.style.cursor = "col-resize";
@@ -23755,17 +24154,17 @@ function XlsxGrid({
23755
24154
  startColumnResize(event.pointerId, resizeTarget.actualCol, resizeTarget.width, event.clientX);
23756
24155
  return;
23757
24156
  }
23758
- const cell = resolvePointerCellFromClient(event.clientX, event.clientY);
23759
- if (!cell) {
24157
+ const actualCol = resolveCanvasColumnHeaderTarget(event.clientX);
24158
+ if (actualCol === null) {
23760
24159
  return;
23761
24160
  }
23762
24161
  event.preventDefault();
23763
24162
  focusGrid();
23764
24163
  const currentSelection = selectionRef.current;
23765
- const anchorCol = event.shiftKey && currentSelection ? currentSelection.start.col : cell.col;
24164
+ const anchorCol = event.shiftKey && currentSelection ? currentSelection.start.col : actualCol;
23766
24165
  const initialRange = normalizeRange2({
23767
24166
  start: { row: firstVisibleRow, col: anchorCol },
23768
- end: { row: lastVisibleRow, col: cell.col }
24167
+ end: { row: lastVisibleRow, col: actualCol }
23769
24168
  });
23770
24169
  const anchorColIndex = colIndexByActual.get(anchorCol);
23771
24170
  if (anchorColIndex === void 0) {
@@ -23775,7 +24174,7 @@ function XlsxGrid({
23775
24174
  event.pointerId,
23776
24175
  { row: firstVisibleRow, col: anchorCol },
23777
24176
  "column",
23778
- { row: firstVisibleRow, col: cell.col },
24177
+ { row: firstVisibleRow, col: actualCol },
23779
24178
  {
23780
24179
  contentScaleX: 1,
23781
24180
  contentScaleY: 1,
@@ -23796,9 +24195,8 @@ function XlsxGrid({
23796
24195
  firstVisibleRow,
23797
24196
  focusGrid,
23798
24197
  lastVisibleRow,
23799
- resolveCanvasColumnHeaderRect,
24198
+ resolveCanvasColumnHeaderTarget,
23800
24199
  resolveCanvasColumnResizeTarget,
23801
- resolvePointerCellFromClient,
23802
24200
  rowPrefixSums,
23803
24201
  startCellSelection
23804
24202
  ]);
@@ -23813,17 +24211,17 @@ function XlsxGrid({
23813
24211
  startRowResize(event.pointerId, resizeTarget.actualRow, resizeTarget.height, event.clientY);
23814
24212
  return;
23815
24213
  }
23816
- const cell = resolvePointerCellFromClient(event.clientX, event.clientY);
23817
- if (!cell) {
24214
+ const actualRow = resolveCanvasRowHeaderTarget(event.clientY);
24215
+ if (actualRow === null) {
23818
24216
  return;
23819
24217
  }
23820
24218
  event.preventDefault();
23821
24219
  focusGrid();
23822
24220
  const currentSelection = selectionRef.current;
23823
- const anchorRow = event.shiftKey && currentSelection ? currentSelection.start.row : cell.row;
24221
+ const anchorRow = event.shiftKey && currentSelection ? currentSelection.start.row : actualRow;
23824
24222
  const initialRange = normalizeRange2({
23825
24223
  start: { row: anchorRow, col: firstVisibleCol },
23826
- end: { row: cell.row, col: lastVisibleCol }
24224
+ end: { row: actualRow, col: lastVisibleCol }
23827
24225
  });
23828
24226
  const anchorRowIndex = rowIndexByActual.get(anchorRow);
23829
24227
  if (anchorRowIndex === void 0) {
@@ -23833,7 +24231,7 @@ function XlsxGrid({
23833
24231
  event.pointerId,
23834
24232
  { row: anchorRow, col: firstVisibleCol },
23835
24233
  "row",
23836
- { row: cell.row, col: firstVisibleCol },
24234
+ { row: actualRow, col: firstVisibleCol },
23837
24235
  {
23838
24236
  contentScaleX: 1,
23839
24237
  contentScaleY: 1,
@@ -23853,8 +24251,8 @@ function XlsxGrid({
23853
24251
  firstVisibleCol,
23854
24252
  focusGrid,
23855
24253
  lastVisibleCol,
24254
+ resolveCanvasRowHeaderTarget,
23856
24255
  resolveCanvasRowResizeTarget,
23857
- resolvePointerCellFromClient,
23858
24256
  rowIndexByActual,
23859
24257
  rowPrefixSums,
23860
24258
  startCellSelection
@@ -23864,7 +24262,7 @@ function XlsxGrid({
23864
24262
  return;
23865
24263
  }
23866
24264
  const dpr = typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
23867
- function configureCanvas(canvas, width, height) {
24265
+ function configureCanvas(canvas, width, height, options) {
23868
24266
  if (!canvas) {
23869
24267
  return null;
23870
24268
  }
@@ -23887,7 +24285,9 @@ function XlsxGrid({
23887
24285
  return null;
23888
24286
  }
23889
24287
  context.setTransform(dpr, 0, 0, dpr, 0, 0);
23890
- context.clearRect(0, 0, width, height);
24288
+ if (options?.clear !== false) {
24289
+ context.clearRect(0, 0, width, height);
24290
+ }
23891
24291
  return context;
23892
24292
  }
23893
24293
  const bodyWidth = Math.max(0, drawingViewport.width);
@@ -23919,12 +24319,24 @@ function XlsxGrid({
23919
24319
  rangeSignature,
23920
24320
  rowHeaderWidth,
23921
24321
  rowSignature: nextBodyCanvasSignature.rowSignature,
24322
+ viewportLeft: drawingViewport.left,
24323
+ viewportTop: drawingViewport.top,
23922
24324
  zoomFactor
23923
24325
  };
23924
24326
  const previousBodyCanvasSignature = paintedBodyCanvasSignatureRef.current;
23925
24327
  const previousHeaderCanvasSignature = paintedHeaderCanvasSignatureRef.current;
24328
+ const previousPaintedViewport = paintedDrawingViewportRef.current;
23926
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);
23927
- 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
+ );
23928
24340
  if (!shouldRepaintBody && !shouldRepaintHeaders) {
23929
24341
  return;
23930
24342
  }
@@ -23936,6 +24348,10 @@ function XlsxGrid({
23936
24348
  const leftBodyCanvasHeight2 = scrollBodyCanvasHeight2;
23937
24349
  const cornerBodyCanvasWidth2 = leftBodyCanvasWidth2;
23938
24350
  const cornerBodyCanvasHeight2 = topBodyCanvasHeight2;
24351
+ const topFrozenHeaderCanvasWidth2 = leftBodyCanvasWidth2;
24352
+ const topScrollHeaderCanvasWidth2 = scrollBodyCanvasWidth2;
24353
+ const leftFrozenHeaderCanvasHeight2 = topBodyCanvasHeight2;
24354
+ const leftScrollHeaderCanvasHeight2 = scrollBodyCanvasHeight2;
23939
24355
  const paneBounds = {
23940
24356
  corner: {
23941
24357
  height: cornerBodyCanvasHeight2,
@@ -23963,29 +24379,50 @@ function XlsxGrid({
23963
24379
  }
23964
24380
  };
23965
24381
  const bodyContexts = shouldRepaintBody ? {
23966
- corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth2, cornerBodyCanvasHeight2),
23967
- left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth2, leftBodyCanvasHeight2),
23968
- scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth2, scrollBodyCanvasHeight2),
23969
- 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 })
23970
24386
  } : {
23971
24387
  corner: null,
23972
24388
  left: null,
23973
24389
  scroll: null,
23974
24390
  top: null
23975
24391
  };
23976
- const topHeaderContext = shouldRepaintHeaders ? configureCanvas(topHeaderCanvasRef.current, bodyWidth, headerHeight) : null;
23977
- 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
+ };
23978
24406
  const cornerContext = shouldRepaintHeaders ? configureCanvas(cornerHeaderCanvasRef.current, rowHeaderWidth, headerHeight) : null;
23979
- 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)) {
23980
24408
  return;
23981
24409
  }
23982
24410
  const showGridLines = activeSheet?.showGridLines ?? true;
24411
+ const sheetSurface = resolveSheetSurface(activeSheet, palette);
23983
24412
  const deferredSpillTextsByPane = {
23984
24413
  corner: [],
23985
24414
  left: [],
23986
24415
  scroll: [],
23987
24416
  top: []
23988
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);
23989
24426
  const cellPaneOrder = ["scroll", "top", "left", "corner"];
23990
24427
  if (shouldRepaintBody) {
23991
24428
  for (const pane of Object.keys(bodyContexts)) {
@@ -23994,14 +24431,44 @@ function XlsxGrid({
23994
24431
  if (!context || bounds.width <= 0 || bounds.height <= 0) {
23995
24432
  continue;
23996
24433
  }
23997
- context.fillStyle = resolveSheetSurface(activeSheet, palette);
23998
- 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
+ }
23999
24465
  }
24000
24466
  for (const pane of cellPaneOrder) {
24001
24467
  const paneContext = bodyContexts[pane];
24002
24468
  const paneBoundsForCell = paneBounds[pane];
24469
+ const paneDirtyRects = bodyDirtyRectsByPane[pane];
24003
24470
  const paneAxisItems = canvasPaneAxisItems[pane];
24004
- 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) {
24005
24472
  continue;
24006
24473
  }
24007
24474
  const drawnMergedAnchorKeys = /* @__PURE__ */ new Set();
@@ -24041,11 +24508,14 @@ function XlsxGrid({
24041
24508
  if (localRect.left + drawableWidth < 0 || localRect.top + localRect.height < 0 || localRect.left > paneBoundsForCell.width || localRect.top > paneBoundsForCell.height) {
24042
24509
  continue;
24043
24510
  }
24511
+ if (!intersectsCanvasDirtyRects(localRect.left, localRect.top, drawableWidth, localRect.height, paneDirtyRects)) {
24512
+ continue;
24513
+ }
24044
24514
  const cellStyle = cellData.style;
24045
24515
  const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellStyle);
24046
- 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);
24047
24517
  const gradientFill = !cellData.conditionalColorScale && typeof cellStyle.backgroundImage === "string" ? resolveCanvasGradientFill(paneContext, localRect, cellStyle.backgroundImage) : null;
24048
- 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;
24049
24519
  paneContext.fillStyle = gradientFill ?? fillColor;
24050
24520
  paneContext.fillRect(localRect.left, localRect.top, localRect.width, localRect.height);
24051
24521
  if (cellData.chartHighlight) {
@@ -24399,55 +24869,139 @@ function XlsxGrid({
24399
24869
  }
24400
24870
  }
24401
24871
  }
24402
- if (shouldRepaintHeaders && topHeaderContext && leftHeaderContext && cornerContext) {
24403
- topHeaderContext.fillStyle = palette.headerSurface;
24404
- topHeaderContext.fillRect(0, 0, bodyWidth, headerHeight);
24405
- topHeaderContext.strokeStyle = palette.border;
24406
- topHeaderContext.lineWidth = 1;
24407
- for (const colItem of canvasVisibleColItems) {
24408
- const rect = resolveCanvasColumnHeaderRect(colItem.actualCol);
24409
- 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)) {
24410
24986
  continue;
24411
24987
  }
24412
- const selected = normalizedVisibleRange && colItem.actualCol >= normalizedVisibleRange.start.col && colItem.actualCol <= normalizedVisibleRange.end.col;
24413
- topHeaderContext.fillStyle = selected ? selectionHeaderSurface : palette.headerSurface;
24414
- topHeaderContext.fillRect(rect.left, 0, rect.width, headerHeight);
24415
- topHeaderContext.strokeStyle = palette.border;
24416
- topHeaderContext.beginPath();
24417
- topHeaderContext.moveTo(rect.left + rect.width - 0.5, 0);
24418
- topHeaderContext.lineTo(rect.left + rect.width - 0.5, headerHeight);
24419
- topHeaderContext.moveTo(rect.left, headerHeight - 0.5);
24420
- topHeaderContext.lineTo(rect.left + rect.width, headerHeight - 0.5);
24421
- topHeaderContext.stroke();
24422
- topHeaderContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24423
- topHeaderContext.fillStyle = palette.mutedText;
24424
- topHeaderContext.textAlign = "center";
24425
- topHeaderContext.textBaseline = "middle";
24426
- topHeaderContext.fillText(columnLabel2(colItem.actualCol), rect.left + rect.width / 2, headerHeight / 2);
24427
- }
24428
- leftHeaderContext.fillStyle = palette.rowHeaderSurface;
24429
- leftHeaderContext.fillRect(0, 0, rowHeaderWidth, bodyHeight);
24430
- leftHeaderContext.strokeStyle = palette.border;
24431
- leftHeaderContext.lineWidth = 1;
24432
- for (const rowItem of canvasVisibleRowItems) {
24433
- const rect = resolveCanvasRowHeaderRect(rowItem.actualRow);
24434
- if (!rect || rect.top + rect.height < displayHeaderHeight || rect.top > bodyHeight) {
24988
+ if (!row.isFrozen && !intersectsCanvasDirtyRects(0, row.localTop, rowHeaderWidth, row.height, leftScrollHeaderDirtyRects)) {
24435
24989
  continue;
24436
24990
  }
24437
- const selected = normalizedVisibleRange && rowItem.actualRow >= normalizedVisibleRange.start.row && rowItem.actualRow <= normalizedVisibleRange.end.row;
24438
- leftHeaderContext.fillStyle = selected ? selectionHeaderSurface : palette.rowHeaderSurface;
24439
- leftHeaderContext.fillRect(0, rect.top, rowHeaderWidth, rect.height);
24440
- leftHeaderContext.beginPath();
24441
- leftHeaderContext.moveTo(0, rect.top + rect.height - 0.5);
24442
- leftHeaderContext.lineTo(rowHeaderWidth, rect.top + rect.height - 0.5);
24443
- leftHeaderContext.moveTo(rowHeaderWidth - 0.5, rect.top);
24444
- leftHeaderContext.lineTo(rowHeaderWidth - 0.5, rect.top + rect.height);
24445
- leftHeaderContext.stroke();
24446
- leftHeaderContext.font = `600 ${11 * zoomFactor}px ui-sans-serif, system-ui, sans-serif`;
24447
- leftHeaderContext.fillStyle = palette.mutedText;
24448
- leftHeaderContext.textAlign = "center";
24449
- leftHeaderContext.textBaseline = "middle";
24450
- 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);
24451
25005
  }
24452
25006
  cornerContext.fillStyle = palette.rowHeaderSurface;
24453
25007
  cornerContext.fillRect(0, 0, rowHeaderWidth, headerHeight);
@@ -24471,7 +25025,9 @@ function XlsxGrid({
24471
25025
  }, [
24472
25026
  activeSheet,
24473
25027
  applyCanvasViewportCompensation,
25028
+ canvasColumnHeaderCells,
24474
25029
  canvasPaneAxisItems,
25030
+ canvasRowHeaderCells,
24475
25031
  canvasVisibleColItems,
24476
25032
  canvasVisibleRowItems,
24477
25033
  colIndexByActual,
@@ -24486,11 +25042,13 @@ function XlsxGrid({
24486
25042
  drawingViewport.height,
24487
25043
  drawingViewport.width,
24488
25044
  experimentalCanvas,
25045
+ frozenPaneBottom,
25046
+ frozenPaneRight,
24489
25047
  getCellData,
25048
+ getBodyBlitBufferCanvas,
25049
+ getHeaderBlitBufferCanvas,
24490
25050
  palette,
24491
25051
  resolveCellDisplayRect,
24492
- resolveCanvasColumnHeaderRect,
24493
- resolveCanvasRowHeaderRect,
24494
25052
  resolveMergeAnchorCell,
24495
25053
  resizeGuide,
24496
25054
  rowIndexByActual,
@@ -24784,6 +25342,10 @@ function XlsxGrid({
24784
25342
  const leftBodyCanvasHeight = scrollBodyCanvasHeight;
24785
25343
  const cornerBodyCanvasWidth = leftBodyCanvasWidth;
24786
25344
  const cornerBodyCanvasHeight = topBodyCanvasHeight;
25345
+ const topFrozenHeaderCanvasWidth = leftBodyCanvasWidth;
25346
+ const topScrollHeaderCanvasWidth = scrollBodyCanvasWidth;
25347
+ const leftFrozenHeaderCanvasHeight = topBodyCanvasHeight;
25348
+ const leftScrollHeaderCanvasHeight = scrollBodyCanvasHeight;
24787
25349
  const canvasBodyBaseStyle = {
24788
25350
  cursor: "cell",
24789
25351
  pointerEvents: "auto",
@@ -24819,22 +25381,38 @@ function XlsxGrid({
24819
25381
  top: displayHeaderHeight,
24820
25382
  zIndex: 31
24821
25383
  };
24822
- const canvasTopHeaderStyle = {
25384
+ const canvasHeaderBaseStyle = {
24823
25385
  cursor: "default",
24824
- display: drawingViewport.width > 0 && drawingViewport.height > 0 ? "block" : "none",
24825
- left: 0,
24826
25386
  pointerEvents: "auto",
24827
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,
24828
25401
  top: 0,
24829
25402
  zIndex: canvasHeaderOverlayZIndex
24830
25403
  };
24831
- const canvasLeftHeaderStyle = {
24832
- cursor: "default",
24833
- display: drawingViewport.width > 0 && drawingViewport.height > 0 ? "block" : "none",
25404
+ const canvasLeftFrozenHeaderStyle = {
25405
+ ...canvasHeaderBaseStyle,
25406
+ display: leftFrozenHeaderCanvasHeight > 0 && drawingViewport.width > 0 ? "block" : "none",
24834
25407
  left: 0,
24835
- pointerEvents: "auto",
24836
- position: "absolute",
24837
- 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,
24838
25416
  zIndex: canvasHeaderOverlayZIndex
24839
25417
  };
24840
25418
  const canvasCornerHeaderStyle = {
@@ -24843,6 +25421,7 @@ function XlsxGrid({
24843
25421
  pointerEvents: "none",
24844
25422
  position: "absolute",
24845
25423
  top: 0,
25424
+ transformOrigin: "0 0",
24846
25425
  zIndex: canvasHeaderOverlayZIndex + 1
24847
25426
  };
24848
25427
  const editingOverlayRect = experimentalCanvas && editingCell ? resolveCellDisplayRect(editingCell) : null;
@@ -25989,7 +26568,7 @@ function XlsxGrid({
25989
26568
  pointerEvents: "none",
25990
26569
  position: "absolute",
25991
26570
  top: 0,
25992
- transform: `translate(${-drawingViewport.left}px, ${-drawingViewport.top}px)`,
26571
+ transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
25993
26572
  width: totalWidth
25994
26573
  },
25995
26574
  children: paneDrawingNodes.scroll
@@ -26004,7 +26583,7 @@ function XlsxGrid({
26004
26583
  pointerEvents: "none",
26005
26584
  position: "absolute",
26006
26585
  top: 0,
26007
- transform: `translate(${-drawingViewport.left}px, ${-displayHeaderHeight}px)`,
26586
+ transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-displayHeaderHeight}px)`,
26008
26587
  width: totalWidth
26009
26588
  },
26010
26589
  children: paneDrawingNodes.top
@@ -26019,7 +26598,7 @@ function XlsxGrid({
26019
26598
  pointerEvents: "none",
26020
26599
  position: "absolute",
26021
26600
  top: 0,
26022
- transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top}px)`,
26601
+ transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
26023
26602
  width: totalWidth
26024
26603
  },
26025
26604
  children: paneDrawingNodes.left
@@ -26046,21 +26625,41 @@ function XlsxGrid({
26046
26625
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26047
26626
  "canvas",
26048
26627
  {
26049
- ref: topHeaderCanvasRef,
26628
+ ref: topFrozenHeaderCanvasRef,
26629
+ onPointerLeave: handleCanvasHeaderPointerLeave,
26630
+ onPointerMove: handleCanvasColumnHeaderPointerMove,
26631
+ onPointerDown: handleCanvasColumnHeaderPointerDown,
26632
+ style: canvasTopFrozenHeaderStyle
26633
+ }
26634
+ ),
26635
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26636
+ "canvas",
26637
+ {
26638
+ ref: topScrollHeaderCanvasRef,
26050
26639
  onPointerLeave: handleCanvasHeaderPointerLeave,
26051
26640
  onPointerMove: handleCanvasColumnHeaderPointerMove,
26052
26641
  onPointerDown: handleCanvasColumnHeaderPointerDown,
26053
- style: canvasTopHeaderStyle
26642
+ style: canvasTopScrollHeaderStyle
26054
26643
  }
26055
26644
  ),
26056
26645
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26057
26646
  "canvas",
26058
26647
  {
26059
- ref: leftHeaderCanvasRef,
26648
+ ref: leftFrozenHeaderCanvasRef,
26060
26649
  onPointerLeave: handleCanvasHeaderPointerLeave,
26061
26650
  onPointerMove: handleCanvasRowHeaderPointerMove,
26062
26651
  onPointerDown: handleCanvasRowHeaderPointerDown,
26063
- style: canvasLeftHeaderStyle
26652
+ style: canvasLeftFrozenHeaderStyle
26653
+ }
26654
+ ),
26655
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26656
+ "canvas",
26657
+ {
26658
+ ref: leftScrollHeaderCanvasRef,
26659
+ onPointerLeave: handleCanvasHeaderPointerLeave,
26660
+ onPointerMove: handleCanvasRowHeaderPointerMove,
26661
+ onPointerDown: handleCanvasRowHeaderPointerDown,
26662
+ style: canvasLeftScrollHeaderStyle
26064
26663
  }
26065
26664
  ),
26066
26665
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("canvas", { ref: cornerHeaderCanvasRef, style: canvasCornerHeaderStyle })
@@ -26370,7 +26969,7 @@ function XlsxGrid({
26370
26969
  }
26371
26970
  }
26372
26971
  ) : null,
26373
- openTableMenuState ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26972
+ !renderTableHeaderMenu && openTableMenuState ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26374
26973
  "div",
26375
26974
  {
26376
26975
  ref: tableMenuRef,
@@ -26381,22 +26980,23 @@ function XlsxGrid({
26381
26980
  top: openTableMenuState.top,
26382
26981
  zIndex: 50
26383
26982
  },
26384
- children: renderTableHeaderMenu ? renderTableHeaderMenu({
26385
- close: () => setOpenTableMenu(null),
26386
- column: openTableMenuState.column,
26387
- direction: sortState && sortState.tableName === openTableMenuState.table.name && sortState.columnIndex === openTableMenuState.column.index ? sortState.direction : null,
26388
- sortAscending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "ascending"),
26389
- sortDescending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "descending"),
26390
- table: openTableMenuState.table
26391
- }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26983
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26392
26984
  DefaultTableHeaderMenu,
26393
26985
  {
26394
- close: () => setOpenTableMenu(null),
26986
+ cell: { col: openTableMenuState.column.index + openTableMenuState.table.start.col, row: openTableMenuState.table.start.row },
26395
26987
  column: openTableMenuState.column,
26396
26988
  direction: sortState && sortState.tableName === openTableMenuState.table.name && sortState.columnIndex === openTableMenuState.column.index ? sortState.direction : null,
26397
- sortAscending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "ascending"),
26398
- sortDescending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "descending"),
26399
- table: openTableMenuState.table
26989
+ sortAscending: () => {
26990
+ sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "ascending");
26991
+ setOpenTableMenu(null);
26992
+ },
26993
+ sortDescending: () => {
26994
+ sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "descending");
26995
+ setOpenTableMenu(null);
26996
+ },
26997
+ table: openTableMenuState.table,
26998
+ triggerIcon: "\u25BE",
26999
+ triggerProps: { type: "button" }
26400
27000
  }
26401
27001
  )
26402
27002
  }
@@ -26827,6 +27427,476 @@ function useXlsxViewerCharts() {
26827
27427
  ]
26828
27428
  );
26829
27429
  }
27430
+ function useXlsxViewerThumbnails(options = {}) {
27431
+ const { workbook, sheets } = useXlsxViewer();
27432
+ const { isDark } = React4.useContext(ViewerAppearanceContext);
27433
+ const palette = useViewerPalette(isDark);
27434
+ const includeHeaders = options.includeHeaders ?? true;
27435
+ const resolution = options.resolution;
27436
+ const thumbnails = React4.useMemo(() => {
27437
+ return sheets.map((sheet, sheetIndex) => {
27438
+ const worksheet = workbook?.getSheet(sheet.workbookSheetIndex) ?? null;
27439
+ const showGridLines = sheet.showGridLines ?? true;
27440
+ const hiddenRowSet = new Set(sheet.hiddenRows ?? []);
27441
+ const hiddenColSet = new Set(sheet.hiddenCols ?? []);
27442
+ const visibleRows = buildVisibleAxisIndices(
27443
+ sheet.visibleRows ?? [],
27444
+ Math.max(THUMBNAIL_FALLBACK_ROWS, (sheet.maxUsedRow ?? -1) + THUMBNAIL_FALLBACK_ROWS + 1),
27445
+ sheet.maxUsedRow ?? -1,
27446
+ hiddenRowSet
27447
+ );
27448
+ const visibleCols = buildVisibleAxisIndices(
27449
+ sheet.visibleCols ?? [],
27450
+ Math.max(THUMBNAIL_FALLBACK_COLS, (sheet.maxUsedCol ?? -1) + THUMBNAIL_FALLBACK_COLS + 1),
27451
+ sheet.maxUsedCol ?? -1,
27452
+ hiddenColSet
27453
+ );
27454
+ const resolveColumnWidthPx = (actualCol) => {
27455
+ if (worksheet && !worksheet.isColumnHidden(actualCol)) {
27456
+ const width = worksheet.getColumnWidth(actualCol);
27457
+ if (width !== void 0 && width !== null) {
27458
+ return Math.max(
27459
+ resolveRenderedSheetAxisPixels(resolveSheetColumnWidthPixels(width, sheet.columnWidthCharacterWidthPx), showGridLines),
27460
+ DEFAULT_COL_WIDTH2 / 2
27461
+ );
27462
+ }
27463
+ }
27464
+ return Math.max(
27465
+ resolveRenderedSheetAxisPixels(
27466
+ sheet.colWidthOverridesPx[actualCol] ?? sheet.defaultColWidthPx ?? DEFAULT_COL_WIDTH2,
27467
+ showGridLines
27468
+ ),
27469
+ DEFAULT_COL_WIDTH2 / 2
27470
+ );
27471
+ };
27472
+ const resolveRowHeightPx = (actualRow) => {
27473
+ if (worksheet && !worksheet.isRowHidden(actualRow)) {
27474
+ const height = worksheet.getRowHeight(actualRow);
27475
+ if (height !== void 0 && height !== null) {
27476
+ return Math.max(
27477
+ resolveRenderedSheetAxisPixels(resolveSheetRowHeightPixels(height), showGridLines),
27478
+ DEFAULT_ROW_HEIGHT2 / 1.5
27479
+ );
27480
+ }
27481
+ }
27482
+ return Math.max(
27483
+ resolveRenderedSheetAxisPixels(
27484
+ sheet.rowHeightOverridesPx[actualRow] ?? sheet.defaultRowHeightPx ?? DEFAULT_ROW_HEIGHT2,
27485
+ showGridLines
27486
+ ),
27487
+ DEFAULT_ROW_HEIGHT2 / 1.5
27488
+ );
27489
+ };
27490
+ const previewRows = resolveThumbnailAxisIndices({
27491
+ fallbackCount: THUMBNAIL_FALLBACK_ROWS,
27492
+ getSizePx: resolveRowHeightPx,
27493
+ maxCount: THUMBNAIL_MAX_ROWS,
27494
+ maxLogicalPixels: THUMBNAIL_MAX_SOURCE_HEIGHT_PX,
27495
+ maxUsedIndex: sheet.maxUsedRow ?? -1,
27496
+ minUsedIndex: Math.max(0, sheet.minUsedRow ?? 0),
27497
+ precomputed: visibleRows
27498
+ });
27499
+ const previewCols = resolveThumbnailAxisIndices({
27500
+ fallbackCount: THUMBNAIL_FALLBACK_COLS,
27501
+ getSizePx: resolveColumnWidthPx,
27502
+ maxCount: THUMBNAIL_MAX_COLS,
27503
+ maxLogicalPixels: THUMBNAIL_MAX_SOURCE_WIDTH_PX,
27504
+ maxUsedIndex: sheet.maxUsedCol ?? -1,
27505
+ minUsedIndex: Math.max(0, sheet.minUsedCol ?? 0),
27506
+ precomputed: visibleCols
27507
+ });
27508
+ const rowAxis = buildThumbnailAxisItems(previewRows, resolveRowHeightPx);
27509
+ const colAxis = buildThumbnailAxisItems(previewCols, resolveColumnWidthPx);
27510
+ const rowItemByActual = new Map(rowAxis.items.map((item) => [item.actualIndex, item]));
27511
+ const colItemByActual = new Map(colAxis.items.map((item) => [item.actualIndex, item]));
27512
+ const headerHeight = includeHeaders ? HEADER_HEIGHT : 0;
27513
+ const rowHeaderWidth = includeHeaders ? ROW_HEADER_WIDTH : 0;
27514
+ const sourceWidth = rowHeaderWidth + colAxis.totalSize;
27515
+ const sourceHeight = headerHeight + rowAxis.totalSize;
27516
+ const outputSize = resolveThumbnailOutputSize(sourceWidth, sourceHeight, resolution);
27517
+ const sourceRange = {
27518
+ start: {
27519
+ col: previewCols[0] ?? 0,
27520
+ row: previewRows[0] ?? 0
27521
+ },
27522
+ end: {
27523
+ col: previewCols[previewCols.length - 1] ?? 0,
27524
+ row: previewRows[previewRows.length - 1] ?? 0
27525
+ }
27526
+ };
27527
+ const sparklineByCell = new Map(
27528
+ (sheet.sparklines ?? []).map((sparkline) => [`${sparkline.target.row}:${sparkline.target.col}`, sparkline])
27529
+ );
27530
+ const paint = (canvas) => {
27531
+ if (!canvas) {
27532
+ return false;
27533
+ }
27534
+ const context = canvas.getContext("2d");
27535
+ if (!context) {
27536
+ return false;
27537
+ }
27538
+ const dpr = typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
27539
+ const outputWidth = outputSize.width;
27540
+ const outputHeight = outputSize.height;
27541
+ const scale = outputSize.scale;
27542
+ const deviceWidth = Math.max(1, Math.round(outputWidth * dpr));
27543
+ const deviceHeight = Math.max(1, Math.round(outputHeight * dpr));
27544
+ if (canvas.width !== deviceWidth) {
27545
+ canvas.width = deviceWidth;
27546
+ }
27547
+ if (canvas.height !== deviceHeight) {
27548
+ canvas.height = deviceHeight;
27549
+ }
27550
+ if (canvas.style.width !== `${outputWidth}px`) {
27551
+ canvas.style.width = `${outputWidth}px`;
27552
+ }
27553
+ if (canvas.style.height !== `${outputHeight}px`) {
27554
+ canvas.style.height = `${outputHeight}px`;
27555
+ }
27556
+ context.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0);
27557
+ context.clearRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
27558
+ context.fillStyle = palette.canvas;
27559
+ context.fillRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
27560
+ context.fillStyle = resolveSheetSurface(sheet, palette);
27561
+ context.fillRect(rowHeaderWidth, headerHeight, Math.max(1, colAxis.totalSize), Math.max(1, rowAxis.totalSize));
27562
+ if (includeHeaders) {
27563
+ context.fillStyle = palette.headerSurface;
27564
+ context.fillRect(rowHeaderWidth, 0, Math.max(1, colAxis.totalSize), headerHeight);
27565
+ context.fillStyle = palette.rowHeaderSurface;
27566
+ context.fillRect(0, headerHeight, rowHeaderWidth, Math.max(1, rowAxis.totalSize));
27567
+ context.fillRect(0, 0, rowHeaderWidth, headerHeight);
27568
+ }
27569
+ if (!worksheet) {
27570
+ return true;
27571
+ }
27572
+ const conditionalFormatMetricsCache = /* @__PURE__ */ new Map();
27573
+ const cellRenderCache = /* @__PURE__ */ new Map();
27574
+ const getCellData = (row, col) => {
27575
+ const cacheKey = `${row}:${col}`;
27576
+ const cached = cellRenderCache.get(cacheKey);
27577
+ if (cached) {
27578
+ return cached;
27579
+ }
27580
+ if (worksheet.isMergedSecondary(row, col)) {
27581
+ const mergedSecondaryData = {
27582
+ isMergedSecondary: true,
27583
+ style: {},
27584
+ value: ""
27585
+ };
27586
+ cellRenderCache.set(cacheKey, mergedSecondaryData);
27587
+ return mergedSecondaryData;
27588
+ }
27589
+ const merge = worksheet.getMergeSpan(row, col);
27590
+ const inheritedStyle = resolveInheritedCellStyle2(sheet, row, col);
27591
+ const worksheetStyle = worksheet.getCellStyleAt(row, col) ?? null;
27592
+ const mergedStyle = mergeResolvedCellStyle(inheritedStyle, worksheetStyle, { replaceXfSubtrees: true });
27593
+ const alignment = mergedStyle?.alignment;
27594
+ const sparkline = sparklineByCell.get(cacheKey) ?? null;
27595
+ const sparklineValues = sparkline ? sparkline.range.start.row === sparkline.range.end.row ? Array.from(
27596
+ { length: Math.abs(sparkline.range.end.col - sparkline.range.start.col) + 1 },
27597
+ (_, index) => getCellNumericValue(
27598
+ worksheet,
27599
+ sparkline.range.start.row,
27600
+ Math.min(sparkline.range.start.col, sparkline.range.end.col) + index
27601
+ )
27602
+ ) : Array.from(
27603
+ { length: Math.abs(sparkline.range.end.row - sparkline.range.start.row) + 1 },
27604
+ (_, index) => getCellNumericValue(
27605
+ worksheet,
27606
+ Math.min(sparkline.range.start.row, sparkline.range.end.row) + index,
27607
+ sparkline.range.start.col
27608
+ )
27609
+ ) : null;
27610
+ const checkboxState = mergedStyle?.cellControl ? getCellBooleanValue(worksheet, row, col) : null;
27611
+ const nextData = {
27612
+ canvas: void 0,
27613
+ checkboxState,
27614
+ colSpan: merge?.colSpan,
27615
+ conditionalColorScale: resolveConditionalColorScaleForCell(
27616
+ row,
27617
+ col,
27618
+ worksheet,
27619
+ sheet,
27620
+ conditionalFormatMetricsCache
27621
+ ),
27622
+ conditionalDataBar: resolveConditionalDataBarForCell(
27623
+ row,
27624
+ col,
27625
+ worksheet,
27626
+ sheet,
27627
+ conditionalFormatMetricsCache
27628
+ ),
27629
+ conditionalIcon: resolveConditionalIconForCell(
27630
+ row,
27631
+ col,
27632
+ worksheet,
27633
+ sheet,
27634
+ conditionalFormatMetricsCache
27635
+ ),
27636
+ isMergedSecondary: false,
27637
+ rowSpan: merge?.rowSpan,
27638
+ sparkline: sparkline && sparklineValues ? { config: sparkline, values: sparklineValues } : null,
27639
+ style: buildCellStyle(mergedStyle, palette, sheet.themePalette, {
27640
+ showGridLines
27641
+ }),
27642
+ textRotationDeg: resolveSpreadsheetTextRotation(alignment?.textRotation),
27643
+ value: sparkline ? "" : checkboxState !== null ? "" : getCellDisplayValue(worksheet, row, col, sheet)
27644
+ };
27645
+ nextData.canvas = buildCanvasCellStyleCache(nextData.style);
27646
+ cellRenderCache.set(cacheKey, nextData);
27647
+ return nextData;
27648
+ };
27649
+ for (const rowItem of rowAxis.items) {
27650
+ for (const colItem of colAxis.items) {
27651
+ const cellData = getCellData(rowItem.actualIndex, colItem.actualIndex);
27652
+ if (cellData.isMergedSecondary) {
27653
+ continue;
27654
+ }
27655
+ const colSpan = Math.max(1, cellData.colSpan ?? 1);
27656
+ const rowSpan = Math.max(1, cellData.rowSpan ?? 1);
27657
+ let width = colItem.size;
27658
+ let height = rowItem.size;
27659
+ for (let colOffset = 1; colOffset < colSpan; colOffset += 1) {
27660
+ const nextCol = colItemByActual.get(colItem.actualIndex + colOffset);
27661
+ if (!nextCol) {
27662
+ break;
27663
+ }
27664
+ width += nextCol.size;
27665
+ }
27666
+ for (let rowOffset = 1; rowOffset < rowSpan; rowOffset += 1) {
27667
+ const nextRow = rowItemByActual.get(rowItem.actualIndex + rowOffset);
27668
+ if (!nextRow) {
27669
+ break;
27670
+ }
27671
+ height += nextRow.size;
27672
+ }
27673
+ const rect = {
27674
+ height,
27675
+ left: rowHeaderWidth + colItem.start,
27676
+ top: headerHeight + rowItem.start,
27677
+ width
27678
+ };
27679
+ const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellData.style);
27680
+ const gradientFill = typeof cellData.style.backgroundImage === "string" ? resolveCanvasGradientFill(context, rect, cellData.style.backgroundImage) : null;
27681
+ const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellData.style.backgroundColor === "string" ? cellData.style.backgroundColor : resolveSheetSurface(sheet, palette));
27682
+ const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellData.style.backgroundColor === "string" && cellData.style.backgroundColor !== resolveSheetSurface(sheet, palette);
27683
+ context.fillStyle = gradientFill ?? fillColor;
27684
+ context.fillRect(rect.left, rect.top, rect.width, rect.height);
27685
+ if (cellData.conditionalDataBar) {
27686
+ const barLeft = rect.left + 4;
27687
+ const barTop = rect.top + 4;
27688
+ const barWidth = Math.max(0, (rect.width - 8) * (cellData.conditionalDataBar.widthPercent / 100));
27689
+ const barHeight = Math.max(0, rect.height - 8);
27690
+ if (barWidth > 0 && barHeight > 0) {
27691
+ context.fillStyle = resolveCanvasDataBarFill(
27692
+ context,
27693
+ barLeft,
27694
+ barTop,
27695
+ barWidth,
27696
+ barHeight,
27697
+ cellData.conditionalDataBar
27698
+ );
27699
+ context.fillRect(barLeft, barTop, barWidth, barHeight);
27700
+ if (cellData.conditionalDataBar.border !== false && cellData.conditionalDataBar.borderColor) {
27701
+ context.strokeStyle = cellData.conditionalDataBar.borderColor;
27702
+ context.lineWidth = 1;
27703
+ context.strokeRect(barLeft + 0.5, barTop + 0.5, Math.max(0, barWidth - 1), Math.max(0, barHeight - 1));
27704
+ }
27705
+ }
27706
+ }
27707
+ if (showGridLines && !hasExplicitCellFill) {
27708
+ context.strokeStyle = palette.border;
27709
+ context.lineWidth = 1;
27710
+ context.beginPath();
27711
+ if (colItem.start === 0) {
27712
+ context.moveTo(rect.left + 0.5, rect.top);
27713
+ context.lineTo(rect.left + 0.5, rect.top + rect.height);
27714
+ }
27715
+ if (rowItem.start === 0) {
27716
+ context.moveTo(rect.left, rect.top + 0.5);
27717
+ context.lineTo(rect.left + rect.width, rect.top + 0.5);
27718
+ }
27719
+ context.moveTo(rect.left + rect.width - 0.5, rect.top);
27720
+ context.lineTo(rect.left + rect.width - 0.5, rect.top + rect.height);
27721
+ context.moveTo(rect.left, rect.top + rect.height - 0.5);
27722
+ context.lineTo(rect.left + rect.width, rect.top + rect.height - 0.5);
27723
+ context.stroke();
27724
+ }
27725
+ if (canvasCellStyle.topBorder) {
27726
+ strokeCanvasBorderSide(context, "top", rect, canvasCellStyle.topBorder);
27727
+ }
27728
+ if (canvasCellStyle.rightBorder) {
27729
+ strokeCanvasBorderSide(context, "right", rect, canvasCellStyle.rightBorder);
27730
+ }
27731
+ if (canvasCellStyle.bottomBorder) {
27732
+ strokeCanvasBorderSide(context, "bottom", rect, canvasCellStyle.bottomBorder);
27733
+ }
27734
+ if (canvasCellStyle.leftBorder) {
27735
+ strokeCanvasBorderSide(context, "left", rect, canvasCellStyle.leftBorder);
27736
+ }
27737
+ const padding = canvasCellStyle.padding;
27738
+ const contentLeft = rect.left + padding.left;
27739
+ const contentTop = rect.top + padding.top;
27740
+ const contentWidth = Math.max(0, rect.width - padding.left - padding.right);
27741
+ const contentHeight = Math.max(0, rect.height - padding.top - padding.bottom);
27742
+ const rawText = cellData.value ?? "";
27743
+ context.save();
27744
+ context.beginPath();
27745
+ context.rect(contentLeft, contentTop, contentWidth, contentHeight);
27746
+ context.clip();
27747
+ context.font = canvasCellStyle.baseFont;
27748
+ context.fillStyle = canvasCellStyle.textColor;
27749
+ context.textBaseline = "middle";
27750
+ if (cellData.checkboxState != null) {
27751
+ const boxSize = Math.min(14, contentWidth, contentHeight);
27752
+ const boxLeft = rect.left + (rect.width - boxSize) / 2;
27753
+ const boxTop = rect.top + (rect.height - boxSize) / 2;
27754
+ context.strokeStyle = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
27755
+ context.lineWidth = 1.25;
27756
+ context.strokeRect(boxLeft, boxTop, boxSize, boxSize);
27757
+ if (cellData.checkboxState) {
27758
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
27759
+ context.fillRect(boxLeft + 1.5, boxTop + 1.5, Math.max(0, boxSize - 3), Math.max(0, boxSize - 3));
27760
+ }
27761
+ } else if (cellData.sparkline) {
27762
+ const sparkline = cellData.sparkline.config;
27763
+ const sparklineValues = cellData.sparkline.values;
27764
+ const points = sparklineValues.map((value, index) => ({ index, value })).filter((entry) => typeof entry.value === "number" && Number.isFinite(entry.value));
27765
+ if (points.length > 1) {
27766
+ const minValue = Math.min(...points.map((entry) => entry.value));
27767
+ const maxValue = Math.max(...points.map((entry) => entry.value));
27768
+ const sparkLeft = contentLeft + 1;
27769
+ const sparkTop = contentTop + 2;
27770
+ const sparkWidth = Math.max(1, contentWidth - 2);
27771
+ const sparkHeight = Math.max(1, contentHeight - 4);
27772
+ const xStep = points.length > 1 ? sparkWidth / (points.length - 1) : 0;
27773
+ context.strokeStyle = sparkline.color ?? "#2563eb";
27774
+ context.lineCap = "round";
27775
+ context.lineJoin = "round";
27776
+ context.lineWidth = 1.25;
27777
+ context.beginPath();
27778
+ points.forEach((entry, index) => {
27779
+ const x = sparkLeft + index * xStep;
27780
+ const y = sparkTop + sparkHeight - clampSparklineValue(entry.value, minValue, maxValue) * sparkHeight;
27781
+ if (index === 0) {
27782
+ context.moveTo(x, y);
27783
+ } else {
27784
+ context.lineTo(x, y);
27785
+ }
27786
+ });
27787
+ context.stroke();
27788
+ }
27789
+ } else if (rawText.length > 0) {
27790
+ const align = canvasCellStyle.textAlign;
27791
+ context.textAlign = align;
27792
+ const textX = align === "right" ? contentLeft + contentWidth : align === "center" ? contentLeft + contentWidth / 2 : contentLeft;
27793
+ const textY = contentTop + contentHeight / 2;
27794
+ const trailingInset = cellData.conditionalIcon ? 18 : 0;
27795
+ const maxTextWidth = Math.max(0, contentWidth - trailingInset);
27796
+ if (cellData.textRotationDeg) {
27797
+ const rotationOriginX = contentLeft + contentWidth / 2;
27798
+ context.save();
27799
+ context.translate(rotationOriginX, textY);
27800
+ context.rotate(cellData.textRotationDeg * Math.PI / 180);
27801
+ context.fillText(
27802
+ rawText,
27803
+ align === "right" ? contentWidth / 2 : align === "center" ? 0 : -(contentWidth / 2),
27804
+ 0
27805
+ );
27806
+ context.restore();
27807
+ } else if (canvasCellStyle.usesWrappedText || rawText.includes("\n")) {
27808
+ const lines = wrapCanvasText(context, rawText, maxTextWidth);
27809
+ const lineHeight = resolveCanvasLineHeight(cellData.style, 12);
27810
+ const textBlockHeight = lines.length * lineHeight;
27811
+ let textBlockTop = contentTop;
27812
+ if (cellData.style.verticalAlign === "middle") {
27813
+ textBlockTop = contentTop + (contentHeight - textBlockHeight) / 2;
27814
+ } else if (cellData.style.verticalAlign !== "top") {
27815
+ textBlockTop = contentTop + contentHeight - textBlockHeight;
27816
+ }
27817
+ lines.forEach((line, lineIndex) => {
27818
+ context.fillText(line, textX, textBlockTop + lineIndex * lineHeight + lineHeight / 2);
27819
+ });
27820
+ } else {
27821
+ const text = canvasCellStyle.textOverflowEllipsis ? truncateCanvasText(context, rawText, maxTextWidth) : rawText;
27822
+ context.fillText(text, textX, textY);
27823
+ }
27824
+ }
27825
+ if (cellData.conditionalIcon) {
27826
+ const iconSize = 10;
27827
+ const iconX = rect.left + rect.width - (padding.right + iconSize + 4);
27828
+ const iconY = rect.top + rect.height / 2;
27829
+ drawCanvasConditionalIcon(context, cellData.conditionalIcon, iconX + iconSize / 2, iconY, iconSize);
27830
+ }
27831
+ context.restore();
27832
+ }
27833
+ }
27834
+ if (includeHeaders) {
27835
+ context.strokeStyle = palette.border;
27836
+ context.lineWidth = 1;
27837
+ context.font = "600 11px ui-sans-serif, system-ui, sans-serif";
27838
+ context.fillStyle = palette.mutedText;
27839
+ context.textBaseline = "middle";
27840
+ for (const colItem of colAxis.items) {
27841
+ const left = rowHeaderWidth + colItem.start;
27842
+ context.beginPath();
27843
+ context.moveTo(left + colItem.size - 0.5, 0);
27844
+ context.lineTo(left + colItem.size - 0.5, headerHeight);
27845
+ context.moveTo(left, headerHeight - 0.5);
27846
+ context.lineTo(left + colItem.size, headerHeight - 0.5);
27847
+ context.stroke();
27848
+ context.textAlign = "center";
27849
+ context.fillText(columnLabel2(colItem.actualIndex), left + colItem.size / 2, headerHeight / 2);
27850
+ }
27851
+ for (const rowItem of rowAxis.items) {
27852
+ const top = headerHeight + rowItem.start;
27853
+ context.beginPath();
27854
+ context.moveTo(0, top + rowItem.size - 0.5);
27855
+ context.lineTo(rowHeaderWidth, top + rowItem.size - 0.5);
27856
+ context.moveTo(rowHeaderWidth - 0.5, top);
27857
+ context.lineTo(rowHeaderWidth - 0.5, top + rowItem.size);
27858
+ context.stroke();
27859
+ context.textAlign = "center";
27860
+ context.fillText(`${rowItem.actualIndex + 1}`, rowHeaderWidth / 2, top + rowItem.size / 2);
27861
+ }
27862
+ context.beginPath();
27863
+ context.moveTo(rowHeaderWidth - 0.5, 0);
27864
+ context.lineTo(rowHeaderWidth - 0.5, headerHeight);
27865
+ context.moveTo(0, headerHeight - 0.5);
27866
+ context.lineTo(rowHeaderWidth, headerHeight - 0.5);
27867
+ context.stroke();
27868
+ }
27869
+ return true;
27870
+ };
27871
+ const plan = {
27872
+ aspectRatio: sourceWidth / Math.max(1, sourceHeight),
27873
+ contentHeight: rowAxis.totalSize,
27874
+ contentWidth: colAxis.totalSize,
27875
+ height: outputSize.height,
27876
+ paint,
27877
+ sourceRange,
27878
+ width: outputSize.width
27879
+ };
27880
+ return {
27881
+ ...plan,
27882
+ sheet,
27883
+ sheetIndex,
27884
+ workbookSheetIndex: sheet.workbookSheetIndex
27885
+ };
27886
+ });
27887
+ }, [includeHeaders, palette, resolution, sheets, workbook]);
27888
+ const paintThumbnail = React4.useCallback(
27889
+ (sheetIndex, canvas) => thumbnails[sheetIndex]?.paint(canvas) ?? false,
27890
+ [thumbnails]
27891
+ );
27892
+ return React4.useMemo(
27893
+ () => ({
27894
+ paintThumbnail,
27895
+ thumbnails
27896
+ }),
27897
+ [paintThumbnail, thumbnails]
27898
+ );
27899
+ }
26830
27900
  function XlsxViewer(props) {
26831
27901
  const contextController = React4.useContext(ViewerContext);
26832
27902
  if (props.controller) {
@@ -26856,6 +27926,7 @@ function DefaultXlsxToolbar() {
26856
27926
  useXlsxViewerImages,
26857
27927
  useXlsxViewerSelection,
26858
27928
  useXlsxViewerTables,
27929
+ useXlsxViewerThumbnails,
26859
27930
  useXlsxViewerZoom
26860
27931
  });
26861
27932
  //# sourceMappingURL=index.cjs.map