@extend-ai/react-xlsx 0.8.3 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4527,6 +4527,10 @@ function parseSheetState(archive, path, options) {
4527
4527
  let hasVerticalMerges = false;
4528
4528
  let maxHorizontalMergeEndCol = -1;
4529
4529
  let maxVerticalMergeEndRow = -1;
4530
+ let minContentCol = Number.POSITIVE_INFINITY;
4531
+ let minContentRow = Number.POSITIVE_INFINITY;
4532
+ let maxContentCol = -1;
4533
+ let maxContentRow = -1;
4530
4534
  const columnWidthCharacterWidthPx = measureColumnCharacterWidthPx(
4531
4535
  options?.defaultFont?.family,
4532
4536
  options?.defaultFont?.sizePt
@@ -4539,6 +4543,26 @@ function parseSheetState(archive, path, options) {
4539
4543
  sheetViewNode?.getAttribute("zoomScale") ?? sheetViewNode?.getAttribute("zoomScaleNormal") ?? Number.NaN
4540
4544
  );
4541
4545
  const zoomScale = Number.isFinite(rawZoomScale) && rawZoomScale > 0 ? rawZoomScale : 100;
4546
+ const trackContentCell = (cellRef) => {
4547
+ if (!cellRef) {
4548
+ return;
4549
+ }
4550
+ const cell = parseA1CellReference(cellRef);
4551
+ if (!cell) {
4552
+ return;
4553
+ }
4554
+ minContentCol = Math.min(minContentCol, cell.col);
4555
+ minContentRow = Math.min(minContentRow, cell.row);
4556
+ maxContentCol = Math.max(maxContentCol, cell.col);
4557
+ maxContentRow = Math.max(maxContentRow, cell.row);
4558
+ };
4559
+ const isMeaningfulCellNode = (cellNode) => {
4560
+ if (getFirstChild(cellNode, "f") || getFirstChild(cellNode, "is")) {
4561
+ return true;
4562
+ }
4563
+ const valueNode = getFirstChild(cellNode, "v");
4564
+ return Boolean(valueNode && (valueNode.textContent ?? "").length > 0);
4565
+ };
4542
4566
  getLocalElements(document2, "row").forEach((rowNode) => {
4543
4567
  const rowIndex = Number(rowNode.getAttribute("r") ?? 0) - 1;
4544
4568
  const height = Number(rowNode.getAttribute("ht") ?? Number.NaN);
@@ -4553,17 +4577,36 @@ function parseSheetState(archive, path, options) {
4553
4577
  if (rowIndex >= 0 && isHidden) {
4554
4578
  hiddenRows.add(rowIndex);
4555
4579
  }
4556
- if (includeCachedFormulaValues) {
4557
- getChildElements(rowNode, "c").forEach((cellNode) => {
4580
+ getChildElements(rowNode, "c").forEach((cellNode) => {
4581
+ const cellRef = cellNode.getAttribute("r");
4582
+ if (isMeaningfulCellNode(cellNode)) {
4583
+ trackContentCell(cellRef);
4584
+ }
4585
+ if (includeCachedFormulaValues) {
4558
4586
  const formulaNode = getFirstChild(cellNode, "f");
4559
4587
  const valueNode = getFirstChild(cellNode, "v");
4560
- const cellRef = cellNode.getAttribute("r");
4561
4588
  if (formulaNode && valueNode && cellRef) {
4562
4589
  cachedFormulaValues[cellRef] = valueNode.textContent ?? "";
4563
4590
  }
4564
- });
4591
+ }
4592
+ });
4593
+ });
4594
+ getLocalElements(document2, "mergeCell").forEach((mergeNode) => {
4595
+ const reference = mergeNode.getAttribute("ref");
4596
+ const range = reference ? parseA1RangeReference(reference) : null;
4597
+ if (!range) {
4598
+ return;
4599
+ }
4600
+ if (range.end.col > range.start.col) {
4601
+ hasHorizontalMerges = true;
4602
+ maxHorizontalMergeEndCol = Math.max(maxHorizontalMergeEndCol, range.end.col);
4603
+ }
4604
+ if (range.end.row > range.start.row) {
4605
+ hasVerticalMerges = true;
4606
+ maxVerticalMergeEndRow = Math.max(maxVerticalMergeEndRow, range.end.row);
4565
4607
  }
4566
4608
  });
4609
+ const maxMetadataCol = Math.max(maxContentCol, maxHorizontalMergeEndCol, 0) + 256;
4567
4610
  getLocalElements(document2, "col").forEach((colNode) => {
4568
4611
  const min = Number(colNode.getAttribute("min") ?? 0) - 1;
4569
4612
  const max = Number(colNode.getAttribute("max") ?? 0) - 1;
@@ -4575,7 +4618,7 @@ function parseSheetState(archive, path, options) {
4575
4618
  return;
4576
4619
  }
4577
4620
  }
4578
- for (let col = min; col <= max; col += 1) {
4621
+ for (let col = min; col <= Math.min(max, maxMetadataCol); col += 1) {
4579
4622
  if (col >= 0) {
4580
4623
  if (Number.isFinite(width)) {
4581
4624
  const widthPx = sheetColumnWidthToPixels(width, columnWidthCharacterWidthPx);
@@ -4590,21 +4633,6 @@ function parseSheetState(archive, path, options) {
4590
4633
  }
4591
4634
  }
4592
4635
  });
4593
- getLocalElements(document2, "mergeCell").forEach((mergeNode) => {
4594
- const reference = mergeNode.getAttribute("ref");
4595
- const range = reference ? parseA1RangeReference(reference) : null;
4596
- if (!range) {
4597
- return;
4598
- }
4599
- if (range.end.col > range.start.col) {
4600
- hasHorizontalMerges = true;
4601
- maxHorizontalMergeEndCol = Math.max(maxHorizontalMergeEndCol, range.end.col);
4602
- }
4603
- if (range.end.row > range.start.row) {
4604
- hasVerticalMerges = true;
4605
- maxVerticalMergeEndRow = Math.max(maxVerticalMergeEndRow, range.end.row);
4606
- }
4607
- });
4608
4636
  return {
4609
4637
  cachedFormulaValues,
4610
4638
  columnWidthCharacterWidthPx,
@@ -4617,6 +4645,10 @@ function parseSheetState(archive, path, options) {
4617
4645
  hasVerticalMerges,
4618
4646
  maxHorizontalMergeEndCol,
4619
4647
  maxVerticalMergeEndRow,
4648
+ maxContentCol,
4649
+ maxContentRow,
4650
+ minContentCol: Number.isFinite(minContentCol) ? minContentCol : -1,
4651
+ minContentRow: Number.isFinite(minContentRow) ? minContentRow : -1,
4620
4652
  hiddenCols: [...hiddenCols].sort((left, right) => left - right),
4621
4653
  hiddenRows: [...hiddenRows].sort((left, right) => left - right),
4622
4654
  rowHeightOverridesPx,
@@ -6645,6 +6677,20 @@ function resolveDisplayFileName(src, fileName) {
6645
6677
  return lastSegment;
6646
6678
  }
6647
6679
  }
6680
+ function resolveSheetDisplayUsedRange(usedRange, sheetState) {
6681
+ const [minRow, minCol, maxRow, maxCol] = usedRange;
6682
+ const maxMeaningfulRow = Math.max(sheetState?.maxContentRow ?? -1, sheetState?.maxVerticalMergeEndRow ?? -1);
6683
+ const maxMeaningfulCol = Math.max(sheetState?.maxContentCol ?? -1, sheetState?.maxHorizontalMergeEndCol ?? -1);
6684
+ if (maxMeaningfulRow < 0 && maxMeaningfulCol < 0) {
6685
+ return usedRange;
6686
+ }
6687
+ return [
6688
+ sheetState?.minContentRow !== void 0 && sheetState.minContentRow >= 0 ? Math.min(minRow, sheetState.minContentRow) : minRow,
6689
+ sheetState?.minContentCol !== void 0 && sheetState.minContentCol >= 0 ? Math.min(minCol, sheetState.minContentCol) : minCol,
6690
+ maxMeaningfulRow >= 0 ? Math.min(maxRow, maxMeaningfulRow) : maxRow,
6691
+ maxMeaningfulCol >= 0 ? Math.min(maxCol, maxMeaningfulCol) : maxCol
6692
+ ];
6693
+ }
6648
6694
  function buildSheetList(workbook, sheetStatesByWorkbookSheetIndex, themePalette, styleById, namedCellStyleByName, tableStyleByName, showHiddenSheets = false) {
6649
6695
  const sheets = [];
6650
6696
  for (let index = 0; index < workbook.sheetCount; index += 1) {
@@ -6711,7 +6757,7 @@ function buildSheetList(workbook, sheetStatesByWorkbookSheetIndex, themePalette,
6711
6757
  });
6712
6758
  continue;
6713
6759
  }
6714
- const [minRow, minCol, maxRow, maxCol] = usedRange;
6760
+ const [minRow, minCol, maxRow, maxCol] = resolveSheetDisplayUsedRange(usedRange, sheetState);
6715
6761
  let visibleRowsCache = null;
6716
6762
  let visibleColsCache = null;
6717
6763
  let rowHeightsCache = null;
@@ -16470,7 +16516,11 @@ var SELECTION_DRAG_THRESHOLD_PX = 4;
16470
16516
  var IMAGE_MIN_SIZE_PX = 16;
16471
16517
  var IMAGE_HANDLE_SIZE_PX = 10;
16472
16518
  var CANVAS_RESIZE_HIT_SLOP_PX = 8;
16473
- var CANVAS_VIEWPORT_OVERSCAN_PX = 240;
16519
+ var CANVAS_VIEWPORT_OVERSCAN_PX = 480;
16520
+ var CANVAS_SCROLL_BUFFER_PX = 480;
16521
+ var CANVAS_DEFERRED_VIEWPORT_SYNC_THRESHOLD_PX = CANVAS_SCROLL_BUFFER_PX - 96;
16522
+ var CANVAS_IMMEDIATE_VIEWPORT_SYNC_THRESHOLD_PX = CANVAS_SCROLL_BUFFER_PX * 1.5;
16523
+ var CANVAS_DIRTY_CELL_CULL_MARGIN_PX = CANVAS_VIEWPORT_OVERSCAN_PX * 2;
16474
16524
  var THUMBNAIL_DEFAULT_MAX_DIMENSION = 192;
16475
16525
  var THUMBNAIL_FALLBACK_ROWS = 12;
16476
16526
  var THUMBNAIL_FALLBACK_COLS = 8;
@@ -16485,6 +16535,12 @@ var CHART_SOURCE_HIGHLIGHT_COLORS = ["#2563eb", "#dc2626", "#7c3aed", "#059669",
16485
16535
  var SHEET_SURFACE = "#ffffff";
16486
16536
  var DEFAULT_CELL_PADDING = "0 4px";
16487
16537
  var IMAGE_HANDLE_POSITIONS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
16538
+ var CANVAS_CELL_STYLE_CACHE_LIMIT = 4096;
16539
+ var CANVAS_TEXT_MEASURE_CACHE_LIMIT = 2e4;
16540
+ var CANVAS_TEXT_TRUNCATE_CACHE_LIMIT = 4096;
16541
+ var CANVAS_TEXT_WRAP_CACHE_LIMIT = 2048;
16542
+ var CANVAS_PATH2D_CACHE_LIMIT = 512;
16543
+ var EMPTY_VIRTUAL_ITEMS = [];
16488
16544
  var IMAGE_HANDLE_CURSOR = {
16489
16545
  e: "ew-resize",
16490
16546
  n: "ns-resize",
@@ -16528,6 +16584,50 @@ var NUMERIC_LENGTH_STYLE_KEYS = /* @__PURE__ */ new Set([
16528
16584
  "top",
16529
16585
  "width"
16530
16586
  ]);
16587
+ var canvasCellStyleCache = /* @__PURE__ */ new Map();
16588
+ var canvasTextMeasureCache = /* @__PURE__ */ new Map();
16589
+ var canvasTextTruncateCache = /* @__PURE__ */ new Map();
16590
+ var canvasTextWrapCache = /* @__PURE__ */ new Map();
16591
+ var canvasPath2DCache = /* @__PURE__ */ new Map();
16592
+ function rememberBoundedCacheValue(cache, key, value, limit) {
16593
+ if (cache.size >= limit) {
16594
+ const oldestKey = cache.keys().next().value;
16595
+ if (oldestKey !== void 0) {
16596
+ cache.delete(oldestKey);
16597
+ }
16598
+ }
16599
+ cache.set(key, value);
16600
+ return value;
16601
+ }
16602
+ function roundCanvasCacheWidth(width) {
16603
+ return Math.round(width * 4) / 4;
16604
+ }
16605
+ function measureCanvasTextWidth(context, text) {
16606
+ if (text.length === 0) {
16607
+ return 0;
16608
+ }
16609
+ const cacheKey = `${context.font}\0${text}`;
16610
+ const cached = canvasTextMeasureCache.get(cacheKey);
16611
+ if (cached !== void 0) {
16612
+ return cached;
16613
+ }
16614
+ return rememberBoundedCacheValue(
16615
+ canvasTextMeasureCache,
16616
+ cacheKey,
16617
+ context.measureText(text).width,
16618
+ CANVAS_TEXT_MEASURE_CACHE_LIMIT
16619
+ );
16620
+ }
16621
+ function getCachedCanvasPath2D(path) {
16622
+ if (typeof Path2D === "undefined") {
16623
+ return null;
16624
+ }
16625
+ const cached = canvasPath2DCache.get(path);
16626
+ if (cached) {
16627
+ return cached;
16628
+ }
16629
+ return rememberBoundedCacheValue(canvasPath2DCache, path, new Path2D(path), CANVAS_PATH2D_CACHE_LIMIT);
16630
+ }
16531
16631
  function scaleCssLengthExpression(value, scale) {
16532
16632
  if (scale === 1) {
16533
16633
  return value;
@@ -16708,7 +16808,6 @@ function strokeCanvasBorderSide(context, side, rect, border) {
16708
16808
  const right = rect.left + rect.width;
16709
16809
  const top = rect.top;
16710
16810
  const bottom = rect.top + rect.height;
16711
- context.save();
16712
16811
  context.strokeStyle = border.color;
16713
16812
  context.lineWidth = border.width;
16714
16813
  applyCanvasBorderDash(context, border.style, border.width);
@@ -16738,13 +16837,22 @@ function strokeCanvasBorderSide(context, side, rect, border) {
16738
16837
  } else {
16739
16838
  strokeLine(0);
16740
16839
  }
16741
- context.restore();
16840
+ context.setLineDash([]);
16841
+ context.lineCap = "butt";
16842
+ context.lineWidth = 1;
16742
16843
  }
16743
16844
  function truncateCanvasText(context, text, maxWidth) {
16744
16845
  if (maxWidth <= 0 || text.length === 0) {
16745
16846
  return "";
16746
16847
  }
16747
- if (context.measureText(text).width <= maxWidth) {
16848
+ const roundedMaxWidth = roundCanvasCacheWidth(maxWidth);
16849
+ const cacheKey = `${context.font}\0${roundedMaxWidth}\0${text}`;
16850
+ const cached = canvasTextTruncateCache.get(cacheKey);
16851
+ if (cached !== void 0) {
16852
+ return cached;
16853
+ }
16854
+ if (measureCanvasTextWidth(context, text) <= maxWidth) {
16855
+ rememberBoundedCacheValue(canvasTextTruncateCache, cacheKey, text, CANVAS_TEXT_TRUNCATE_CACHE_LIMIT);
16748
16856
  return text;
16749
16857
  }
16750
16858
  const ellipsis = "\u2026";
@@ -16753,13 +16861,18 @@ function truncateCanvasText(context, text, maxWidth) {
16753
16861
  while (low < high) {
16754
16862
  const mid = Math.ceil((low + high) / 2);
16755
16863
  const candidate = `${text.slice(0, mid)}${ellipsis}`;
16756
- if (context.measureText(candidate).width <= maxWidth) {
16864
+ if (measureCanvasTextWidth(context, candidate) <= maxWidth) {
16757
16865
  low = mid;
16758
16866
  } else {
16759
16867
  high = mid - 1;
16760
16868
  }
16761
16869
  }
16762
- return low <= 0 ? ellipsis : `${text.slice(0, low)}${ellipsis}`;
16870
+ return rememberBoundedCacheValue(
16871
+ canvasTextTruncateCache,
16872
+ cacheKey,
16873
+ low <= 0 ? ellipsis : `${text.slice(0, low)}${ellipsis}`,
16874
+ CANVAS_TEXT_TRUNCATE_CACHE_LIMIT
16875
+ );
16763
16876
  }
16764
16877
  function resolveCanvasFontSizePx(style, fallbackSize = 12) {
16765
16878
  const rawFontSize = style.fontSize;
@@ -16818,7 +16931,7 @@ function resolveCanvasWrapIndex(context, text, maxWidth) {
16818
16931
  while (low <= high) {
16819
16932
  const mid = Math.floor((low + high) / 2);
16820
16933
  const candidate = text.slice(0, mid);
16821
- if (context.measureText(candidate).width <= maxWidth) {
16934
+ if (measureCanvasTextWidth(context, candidate) <= maxWidth) {
16822
16935
  best = mid;
16823
16936
  low = mid + 1;
16824
16937
  } else {
@@ -16835,6 +16948,12 @@ function wrapCanvasText(context, text, maxWidth) {
16835
16948
  return text.replace(/\r\n?/g, "\n").split("\n");
16836
16949
  }
16837
16950
  const normalized = text.replace(/\r\n?/g, "\n");
16951
+ const roundedMaxWidth = roundCanvasCacheWidth(maxWidth);
16952
+ const cacheKey = `${context.font}\0${roundedMaxWidth}\0${normalized}`;
16953
+ const cached = canvasTextWrapCache.get(cacheKey);
16954
+ if (cached) {
16955
+ return cached;
16956
+ }
16838
16957
  const paragraphs = normalized.split("\n");
16839
16958
  const lines = [];
16840
16959
  for (const paragraph of paragraphs) {
@@ -16844,7 +16963,7 @@ function wrapCanvasText(context, text, maxWidth) {
16844
16963
  }
16845
16964
  let remaining = paragraph;
16846
16965
  while (remaining.length > 0) {
16847
- if (context.measureText(remaining).width <= maxWidth) {
16966
+ if (measureCanvasTextWidth(context, remaining) <= maxWidth) {
16848
16967
  lines.push(remaining);
16849
16968
  break;
16850
16969
  }
@@ -16862,10 +16981,31 @@ function wrapCanvasText(context, text, maxWidth) {
16862
16981
  remaining = remaining.slice(breakIndex).replace(/^\s+/g, "");
16863
16982
  }
16864
16983
  }
16865
- return lines;
16984
+ return rememberBoundedCacheValue(canvasTextWrapCache, cacheKey, lines, CANVAS_TEXT_WRAP_CACHE_LIMIT);
16866
16985
  }
16867
16986
  function buildCanvasCellStyleCache(style) {
16868
- return {
16987
+ const cacheKey = [
16988
+ style.font,
16989
+ style.fontStyle,
16990
+ style.fontWeight,
16991
+ style.fontSize,
16992
+ style.fontFamily,
16993
+ style.borderTop,
16994
+ style.borderRight,
16995
+ style.borderBottom,
16996
+ style.borderLeft,
16997
+ style.padding,
16998
+ style.textAlign,
16999
+ style.color,
17000
+ style.textDecoration,
17001
+ style.textOverflow,
17002
+ style.whiteSpace
17003
+ ].map((value) => value == null ? "" : String(value)).join("");
17004
+ const cached = canvasCellStyleCache.get(cacheKey);
17005
+ if (cached) {
17006
+ return cached;
17007
+ }
17008
+ return rememberBoundedCacheValue(canvasCellStyleCache, cacheKey, {
16869
17009
  baseFont: resolveCanvasFont(style, 12),
16870
17010
  bottomBorder: parseCanvasBorderDeclaration(style.borderBottom),
16871
17011
  leftBorder: parseCanvasBorderDeclaration(style.borderLeft),
@@ -16877,7 +17017,7 @@ function buildCanvasCellStyleCache(style) {
16877
17017
  textOverflowEllipsis: style.textOverflow === "ellipsis",
16878
17018
  topBorder: parseCanvasBorderDeclaration(style.borderTop),
16879
17019
  usesWrappedText: style.whiteSpace === "pre-wrap"
16880
- };
17020
+ }, CANVAS_CELL_STYLE_CACHE_LIMIT);
16881
17021
  }
16882
17022
  function splitCssGradientArgs(value) {
16883
17023
  const parts = [];
@@ -17430,6 +17570,25 @@ function resolveImageAnchorExtents(image) {
17430
17570
  maxRow: Math.max(image.anchor.from.row, image.anchor.to.row)
17431
17571
  };
17432
17572
  }
17573
+ function resolveAnchoredBounds(anchor) {
17574
+ if (anchor.kind === "absolute") {
17575
+ return null;
17576
+ }
17577
+ if (anchor.kind === "one-cell") {
17578
+ return {
17579
+ maxCol: anchor.from.col,
17580
+ maxRow: anchor.from.row,
17581
+ minCol: anchor.from.col,
17582
+ minRow: anchor.from.row
17583
+ };
17584
+ }
17585
+ return {
17586
+ maxCol: Math.max(anchor.from.col, anchor.to.col),
17587
+ maxRow: Math.max(anchor.from.row, anchor.to.row),
17588
+ minCol: Math.min(anchor.from.col, anchor.to.col),
17589
+ minRow: Math.min(anchor.from.row, anchor.to.row)
17590
+ };
17591
+ }
17433
17592
  function resolveShapeAnchorExtents(shape) {
17434
17593
  if (shape.anchor.kind === "absolute") {
17435
17594
  return { maxCol: 0, maxRow: 0 };
@@ -18386,6 +18545,44 @@ function resolveShapeVector(shape) {
18386
18545
  }
18387
18546
  return buildPresetShapePath(shape);
18388
18547
  }
18548
+ function drawCanvasRoundedRect(context, left, top, width, height, radius) {
18549
+ const safeRadius = Math.max(0, Math.min(radius, width / 2, height / 2));
18550
+ context.beginPath();
18551
+ context.moveTo(left + safeRadius, top);
18552
+ context.lineTo(left + width - safeRadius, top);
18553
+ context.quadraticCurveTo(left + width, top, left + width, top + safeRadius);
18554
+ context.lineTo(left + width, top + height - safeRadius);
18555
+ context.quadraticCurveTo(left + width, top + height, left + width - safeRadius, top + height);
18556
+ context.lineTo(left + safeRadius, top + height);
18557
+ context.quadraticCurveTo(left, top + height, left, top + height - safeRadius);
18558
+ context.lineTo(left, top + safeRadius);
18559
+ context.quadraticCurveTo(left, top, left + safeRadius, top);
18560
+ context.closePath();
18561
+ }
18562
+ function applyCanvasShapeDash(context, dash, lineWidth) {
18563
+ if (!dash) {
18564
+ context.setLineDash([]);
18565
+ return;
18566
+ }
18567
+ const unit = Math.max(1, lineWidth);
18568
+ switch (dash) {
18569
+ case "dash":
18570
+ context.setLineDash([4 * unit, 3 * unit]);
18571
+ break;
18572
+ case "dashDot":
18573
+ context.setLineDash([4 * unit, 2 * unit, unit, 2 * unit]);
18574
+ break;
18575
+ case "dot":
18576
+ context.setLineDash([unit, 2 * unit]);
18577
+ break;
18578
+ case "lgDash":
18579
+ context.setLineDash([8 * unit, 3 * unit]);
18580
+ break;
18581
+ default:
18582
+ context.setLineDash([]);
18583
+ break;
18584
+ }
18585
+ }
18389
18586
  function resolveShapeLineEndMarker(type, markerId, color, strokeWidth, rect, viewBox) {
18390
18587
  if (type !== "triangle") {
18391
18588
  return null;
@@ -18575,6 +18772,71 @@ function buildThumbnailAxisItems(actualIndices, getSizePx) {
18575
18772
  totalSize: offset
18576
18773
  };
18577
18774
  }
18775
+ function resolveThumbnailAxisAbsoluteOffset(actualIndices, getSizePx, actualIndex) {
18776
+ let total = 0;
18777
+ for (const candidate of actualIndices) {
18778
+ if (candidate >= actualIndex) {
18779
+ break;
18780
+ }
18781
+ total += Math.max(1, getSizePx(candidate));
18782
+ }
18783
+ return total;
18784
+ }
18785
+ function resolveThumbnailAxisMarkerOffset({
18786
+ actualIndex,
18787
+ actualIndices,
18788
+ getSizePx,
18789
+ offsetEmu,
18790
+ previewStartActualIndex
18791
+ }) {
18792
+ return resolveThumbnailAxisAbsoluteOffset(actualIndices, getSizePx, actualIndex) - resolveThumbnailAxisAbsoluteOffset(actualIndices, getSizePx, previewStartActualIndex) + emuToPixels(offsetEmu);
18793
+ }
18794
+ function resolveThumbnailAnchoredRect(anchor, visibleRows, visibleCols, getRowHeightPx, getColWidthPx, previewRows, previewCols, options) {
18795
+ const previewStartRow = previewRows[0] ?? 0;
18796
+ const previewStartCol = previewCols[0] ?? 0;
18797
+ const previewOriginX = resolveThumbnailAxisAbsoluteOffset(visibleCols, getColWidthPx, previewStartCol);
18798
+ const previewOriginY = resolveThumbnailAxisAbsoluteOffset(visibleRows, getRowHeightPx, previewStartRow);
18799
+ const resolveMarkerLeft = (col, colOffsetEmu) => options.rowHeaderWidth + resolveThumbnailAxisMarkerOffset({
18800
+ actualIndex: col,
18801
+ actualIndices: visibleCols,
18802
+ getSizePx: getColWidthPx,
18803
+ offsetEmu: colOffsetEmu,
18804
+ previewStartActualIndex: previewStartCol
18805
+ });
18806
+ const resolveMarkerTop = (row, rowOffsetEmu) => options.headerHeight + resolveThumbnailAxisMarkerOffset({
18807
+ actualIndex: row,
18808
+ actualIndices: visibleRows,
18809
+ getSizePx: getRowHeightPx,
18810
+ offsetEmu: rowOffsetEmu,
18811
+ previewStartActualIndex: previewStartRow
18812
+ });
18813
+ if (anchor.kind === "absolute") {
18814
+ return {
18815
+ height: Math.max(1, emuToPixels(anchor.sizeEmu.cy)),
18816
+ left: options.rowHeaderWidth + emuToPixels(anchor.positionEmu.x) - previewOriginX,
18817
+ top: options.headerHeight + emuToPixels(anchor.positionEmu.y) - previewOriginY,
18818
+ width: Math.max(1, emuToPixels(anchor.sizeEmu.cx))
18819
+ };
18820
+ }
18821
+ if (anchor.kind === "one-cell") {
18822
+ return {
18823
+ height: Math.max(1, emuToPixels(anchor.sizeEmu.cy)),
18824
+ left: resolveMarkerLeft(anchor.from.col, anchor.from.colOffsetEmu),
18825
+ top: resolveMarkerTop(anchor.from.row, anchor.from.rowOffsetEmu),
18826
+ width: Math.max(1, emuToPixels(anchor.sizeEmu.cx))
18827
+ };
18828
+ }
18829
+ const left = resolveMarkerLeft(anchor.from.col, anchor.from.colOffsetEmu);
18830
+ const top = resolveMarkerTop(anchor.from.row, anchor.from.rowOffsetEmu);
18831
+ const right = resolveMarkerLeft(anchor.to.col, anchor.to.colOffsetEmu);
18832
+ const bottom = resolveMarkerTop(anchor.to.row, anchor.to.rowOffsetEmu);
18833
+ return {
18834
+ height: Math.max(1, bottom - top),
18835
+ left,
18836
+ top,
18837
+ width: Math.max(1, right - left)
18838
+ };
18839
+ }
18578
18840
  function columnLabel2(col) {
18579
18841
  let label = "";
18580
18842
  let nextValue = col;
@@ -18988,12 +19250,12 @@ function measureTextWidth(value, style) {
18988
19250
  return value.length * 7;
18989
19251
  }
18990
19252
  textMeasureCanvas ??= document.createElement("canvas");
18991
- const context = textMeasureCanvas.getContext("2d");
19253
+ const context = textMeasureCanvas.getContext("2d", { alpha: false });
18992
19254
  if (!context) {
18993
19255
  return value.length * 7;
18994
19256
  }
18995
19257
  context.font = buildCanvasFont(style);
18996
- return context.measureText(value).width;
19258
+ return measureCanvasTextWidth(context, value);
18997
19259
  }
18998
19260
  function measureWrappedTextHeight(value, style, widthPx) {
18999
19261
  if (!value) {
@@ -19007,7 +19269,7 @@ function measureWrappedTextHeight(value, style, widthPx) {
19007
19269
  return fallbackLineCount * lineHeight + padding.top + padding.bottom;
19008
19270
  }
19009
19271
  textMeasureCanvas ??= document.createElement("canvas");
19010
- const context = textMeasureCanvas.getContext("2d");
19272
+ const context = textMeasureCanvas.getContext("2d", { alpha: false });
19011
19273
  if (!context) {
19012
19274
  return fallbackLineCount * lineHeight + padding.top + padding.bottom;
19013
19275
  }
@@ -20105,7 +20367,7 @@ function blitCanvasWithScrollDelta(canvas, context, bufferCanvas, dpr, width, he
20105
20367
  if (bufferCanvas.height !== deviceHeight) {
20106
20368
  bufferCanvas.height = deviceHeight;
20107
20369
  }
20108
- const bufferContext = bufferCanvas.getContext("2d");
20370
+ const bufferContext = bufferCanvas.getContext("2d", { alpha: false });
20109
20371
  if (!bufferContext) {
20110
20372
  return null;
20111
20373
  }
@@ -20360,6 +20622,222 @@ function resolveFormControlLabel(control) {
20360
20622
  }
20361
20623
  return label.replace(/\u00a0/g, " ").replace(/^\s+/, "");
20362
20624
  }
20625
+ function drawStaticShapeText(context, shape, left, top, width, height, zoomFactor) {
20626
+ if (shape.paragraphs.length === 0) {
20627
+ return;
20628
+ }
20629
+ const inset = shape.textBox?.insetPx;
20630
+ const paddingLeft = (inset?.left ?? 6) * zoomFactor;
20631
+ const paddingRight = (inset?.right ?? 6) * zoomFactor;
20632
+ const paddingTop = (inset?.top ?? 4) * zoomFactor;
20633
+ const paddingBottom = (inset?.bottom ?? 4) * zoomFactor;
20634
+ const textLeft = left + paddingLeft;
20635
+ const textTop = top + paddingTop;
20636
+ const textWidth = Math.max(0, width - paddingLeft - paddingRight);
20637
+ const textHeight = Math.max(0, height - paddingTop - paddingBottom);
20638
+ if (textWidth <= 0 || textHeight <= 0) {
20639
+ return;
20640
+ }
20641
+ const lineMetrics = shape.paragraphs.map((paragraph) => {
20642
+ const lineHeight = Math.max(
20643
+ 12 * zoomFactor,
20644
+ ...paragraph.runs.map((run) => (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor * 1.2)
20645
+ );
20646
+ const widthPx = paragraph.runs.reduce((total, run) => {
20647
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
20648
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
20649
+ return total + measureCanvasTextWidth(context, run.text);
20650
+ }, 0);
20651
+ return { lineHeight, widthPx };
20652
+ });
20653
+ const totalTextHeight = lineMetrics.reduce((total, metric) => total + metric.lineHeight, 0);
20654
+ let y = textTop;
20655
+ if (shape.textBox?.verticalAlign === "middle") {
20656
+ y = textTop + Math.max(0, (textHeight - totalTextHeight) / 2);
20657
+ } else if (shape.textBox?.verticalAlign === "bottom") {
20658
+ y = textTop + Math.max(0, textHeight - totalTextHeight);
20659
+ }
20660
+ context.save();
20661
+ context.beginPath();
20662
+ context.rect(textLeft, textTop, textWidth, textHeight);
20663
+ context.clip();
20664
+ context.textBaseline = "middle";
20665
+ shape.paragraphs.forEach((paragraph, paragraphIndex) => {
20666
+ const metric = lineMetrics[paragraphIndex] ?? { lineHeight: 14 * zoomFactor, widthPx: 0 };
20667
+ let x = textLeft;
20668
+ const align = paragraph.align ?? shape.textBox?.horizontalAlign ?? "left";
20669
+ if (align === "center") {
20670
+ x = textLeft + Math.max(0, (textWidth - metric.widthPx) / 2);
20671
+ } else if (align === "right") {
20672
+ x = textLeft + Math.max(0, textWidth - metric.widthPx);
20673
+ }
20674
+ paragraph.runs.forEach((run) => {
20675
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
20676
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
20677
+ context.fillStyle = run.color ?? "#000000";
20678
+ context.textAlign = "left";
20679
+ const textY = y + metric.lineHeight / 2;
20680
+ context.fillText(run.text, x, textY);
20681
+ if (run.underline && run.text.length > 0) {
20682
+ const textWidthPx = measureCanvasTextWidth(context, run.text);
20683
+ context.strokeStyle = run.color ?? "#000000";
20684
+ context.lineWidth = Math.max(1, zoomFactor * 0.75);
20685
+ context.beginPath();
20686
+ context.moveTo(x, textY + Math.max(2, fontSize * 0.28));
20687
+ context.lineTo(x + textWidthPx, textY + Math.max(2, fontSize * 0.28));
20688
+ context.stroke();
20689
+ }
20690
+ x += measureCanvasTextWidth(context, run.text);
20691
+ });
20692
+ y += metric.lineHeight;
20693
+ });
20694
+ context.restore();
20695
+ }
20696
+ function drawStaticShape(context, shape, rect, zoomFactor) {
20697
+ const fillColor = shape.fill?.none ? "transparent" : shape.fill?.color ?? "transparent";
20698
+ const strokeColor = shape.stroke?.none ? "transparent" : shape.stroke?.color ?? "transparent";
20699
+ const lineWidth = Math.max(0, (shape.stroke?.widthPx ?? (shape.geometry === "line" ? 2 : 1)) * zoomFactor);
20700
+ const vectorShape = resolveShapeVector(shape);
20701
+ const opacity = Math.min(shape.fill?.opacity ?? 1, shape.stroke?.opacity ?? 1);
20702
+ context.save();
20703
+ context.translate(rect.left + rect.width / 2, rect.top + rect.height / 2);
20704
+ if (shape.rotationDeg) {
20705
+ context.rotate(shape.rotationDeg * Math.PI / 180);
20706
+ }
20707
+ context.scale(shape.flipH ? -1 : 1, shape.flipV ? -1 : 1);
20708
+ context.globalAlpha *= opacity;
20709
+ context.lineWidth = lineWidth;
20710
+ context.strokeStyle = strokeColor;
20711
+ context.fillStyle = fillColor;
20712
+ applyCanvasShapeDash(context, shape.stroke?.dash, lineWidth);
20713
+ const localLeft = -rect.width / 2;
20714
+ const localTop = -rect.height / 2;
20715
+ if (vectorShape && typeof Path2D !== "undefined") {
20716
+ context.save();
20717
+ context.translate(localLeft, localTop);
20718
+ context.scale(
20719
+ rect.width / Math.max(1, vectorShape.viewBox.width),
20720
+ rect.height / Math.max(1, vectorShape.viewBox.height)
20721
+ );
20722
+ const path = getCachedCanvasPath2D(vectorShape.path);
20723
+ if (!path) {
20724
+ context.restore();
20725
+ context.restore();
20726
+ return;
20727
+ }
20728
+ if (fillColor !== "transparent") {
20729
+ context.fill(path);
20730
+ }
20731
+ if (strokeColor !== "transparent" && lineWidth > 0) {
20732
+ context.stroke(path);
20733
+ }
20734
+ context.restore();
20735
+ } else if (shape.geometry === "ellipse") {
20736
+ context.beginPath();
20737
+ context.ellipse(0, 0, rect.width / 2, rect.height / 2, 0, 0, Math.PI * 2);
20738
+ if (fillColor !== "transparent") {
20739
+ context.fill();
20740
+ }
20741
+ if (strokeColor !== "transparent" && lineWidth > 0) {
20742
+ context.stroke();
20743
+ }
20744
+ } else {
20745
+ const radius = shape.geometry === "roundRect" ? 12 * zoomFactor : 0;
20746
+ drawCanvasRoundedRect(context, localLeft, localTop, rect.width, rect.height, radius);
20747
+ if (fillColor !== "transparent") {
20748
+ context.fill();
20749
+ }
20750
+ if (strokeColor !== "transparent" && lineWidth > 0) {
20751
+ context.stroke();
20752
+ }
20753
+ }
20754
+ drawStaticShapeText(context, shape, localLeft, localTop, rect.width, rect.height, zoomFactor);
20755
+ context.restore();
20756
+ }
20757
+ function drawStaticFormControl(context, control, rect, palette, zoomFactor, sheetSurface = SHEET_SURFACE) {
20758
+ const label = resolveFormControlLabel(control);
20759
+ const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
20760
+ const textColor = control.textColor ?? "#000000";
20761
+ const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
20762
+ const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
20763
+ context.save();
20764
+ context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
20765
+ context.textBaseline = "middle";
20766
+ context.fillStyle = textColor;
20767
+ context.strokeStyle = stroke;
20768
+ context.lineWidth = Math.max(1, zoomFactor);
20769
+ if (control.kind === "group-box") {
20770
+ const labelInset = label ? Math.max(7, fontSizePx * 0.5) : 0;
20771
+ drawCanvasRoundedRect(context, rect.left, rect.top + labelInset, rect.width, Math.max(0, rect.height - labelInset), 2 * zoomFactor);
20772
+ context.stroke();
20773
+ if (label) {
20774
+ context.fillStyle = sheetSurface;
20775
+ const labelWidth = Math.min(rect.width - 16 * zoomFactor, measureCanvasTextWidth(context, label) + 8 * zoomFactor);
20776
+ context.fillRect(rect.left + 8 * zoomFactor, rect.top, labelWidth, fontSizePx * 1.2);
20777
+ context.fillStyle = textColor;
20778
+ context.textAlign = "left";
20779
+ context.fillText(label, rect.left + 12 * zoomFactor, rect.top + fontSizePx * 0.55);
20780
+ }
20781
+ context.restore();
20782
+ return;
20783
+ }
20784
+ if (control.kind === "button" || control.kind === "dropdown" || control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
20785
+ if (control.kind === "button") {
20786
+ const gradient = context.createLinearGradient(rect.left, rect.top, rect.left, rect.top + rect.height);
20787
+ gradient.addColorStop(0, "#f8fafc");
20788
+ gradient.addColorStop(1, "#e2e8f0");
20789
+ context.fillStyle = gradient;
20790
+ } else {
20791
+ context.fillStyle = "transparent";
20792
+ }
20793
+ drawCanvasRoundedRect(context, rect.left, rect.top, rect.width, rect.height, control.kind === "button" ? 4 * zoomFactor : 2 * zoomFactor);
20794
+ if (control.kind === "button") {
20795
+ context.fill();
20796
+ }
20797
+ context.stroke();
20798
+ }
20799
+ let textLeft = rect.left + 2 * zoomFactor;
20800
+ const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
20801
+ if (control.kind === "checkbox") {
20802
+ context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
20803
+ if (control.checked) {
20804
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
20805
+ context.fillRect(rect.left + 2 * zoomFactor + 1.5, rect.top + (rect.height - iconSize) / 2 + 1.5, Math.max(0, iconSize - 3), Math.max(0, iconSize - 3));
20806
+ context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
20807
+ context.beginPath();
20808
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
20809
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
20810
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
20811
+ context.stroke();
20812
+ }
20813
+ textLeft += iconSize + 4 * zoomFactor;
20814
+ } else if (control.kind === "radio") {
20815
+ context.beginPath();
20816
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
20817
+ context.stroke();
20818
+ if (control.checked) {
20819
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
20820
+ context.beginPath();
20821
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
20822
+ context.fill();
20823
+ }
20824
+ textLeft += iconSize + 4 * zoomFactor;
20825
+ }
20826
+ if (label) {
20827
+ const maxTextWidth = Math.max(0, rect.left + rect.width - textLeft - textRightInset);
20828
+ const text = truncateCanvasText(context, label, maxTextWidth);
20829
+ context.fillStyle = textColor;
20830
+ context.textAlign = control.textAlign === "right" ? "right" : control.textAlign === "center" ? "center" : "left";
20831
+ const textX = context.textAlign === "right" ? rect.left + rect.width - textRightInset : context.textAlign === "center" ? textLeft + maxTextWidth / 2 : textLeft;
20832
+ context.fillText(text, textX, rect.top + rect.height / 2);
20833
+ }
20834
+ if (control.kind === "dropdown") {
20835
+ context.fillStyle = textColor;
20836
+ context.textAlign = "center";
20837
+ context.fillText("\u25BC", rect.left + rect.width - 10 * zoomFactor, rect.top + rect.height / 2);
20838
+ }
20839
+ context.restore();
20840
+ }
20363
20841
  function resolveConditionalDataBarForCell(row, col, worksheet, sheet, metricsCache) {
20364
20842
  const rules = sheet?.conditionalFormatRules ?? [];
20365
20843
  const matchingRule = rules.find(
@@ -20962,6 +21440,8 @@ function XlsxGrid({
20962
21440
  selectionHeaderColor,
20963
21441
  showImages = true
20964
21442
  }) {
21443
+ const xlsxCanvasProfileTarget = typeof window !== "undefined" ? window.__xlsxCanvasProfile : void 0;
21444
+ const xlsxGridRenderStart = xlsxCanvasProfileTarget ? performance.now() : 0;
20965
21445
  const {
20966
21446
  activeCell,
20967
21447
  activeSheet,
@@ -21045,6 +21525,11 @@ function XlsxGrid({
21045
21525
  const leftFrozenHeaderCanvasRef = React4.useRef(null);
21046
21526
  const leftScrollHeaderCanvasRef = React4.useRef(null);
21047
21527
  const cornerHeaderCanvasRef = React4.useRef(null);
21528
+ const canvasScrollOverlayContentRef = React4.useRef(null);
21529
+ const canvasTopOverlayContentRef = React4.useRef(null);
21530
+ const canvasLeftOverlayContentRef = React4.useRef(null);
21531
+ const canvasCornerOverlayContentRef = React4.useRef(null);
21532
+ const canvasImageCacheRef = React4.useRef(/* @__PURE__ */ new Map());
21048
21533
  const selectionOverlayRef = React4.useRef(null);
21049
21534
  const activeValidationOverlayRef = React4.useRef(null);
21050
21535
  const fillHandleRef = React4.useRef(null);
@@ -21073,6 +21558,7 @@ function XlsxGrid({
21073
21558
  const pendingLiveZoomCommitRef = React4.useRef(null);
21074
21559
  const touchPinchStateRef = React4.useRef(null);
21075
21560
  const safariPinchStartZoomRef = React4.useRef(null);
21561
+ const canvasTransformStyleCacheRef = React4.useRef(/* @__PURE__ */ new WeakMap());
21076
21562
  const displayedSelectionRef = React4.useRef(null);
21077
21563
  const firstVisibleColRef = React4.useRef(void 0);
21078
21564
  const lastVisibleColRef = React4.useRef(void 0);
@@ -21098,12 +21584,19 @@ function XlsxGrid({
21098
21584
  const [fillPreviewRange, setFillPreviewRange] = React4.useState(null);
21099
21585
  const [chartPreviewRect, setChartPreviewRect] = React4.useState(null);
21100
21586
  const [liveGestureZoom, setLiveGestureZoom] = React4.useState(null);
21587
+ const [canvasImageLoadVersion, setCanvasImageLoadVersion] = React4.useState(0);
21101
21588
  const liveDrawingViewportRef = React4.useRef({
21102
21589
  height: 0,
21103
21590
  left: 0,
21104
21591
  top: 0,
21105
21592
  width: 0
21106
21593
  });
21594
+ const canvasLayoutMetricsRef = React4.useRef({
21595
+ displayHeaderHeight: HEADER_HEIGHT,
21596
+ displayRowHeaderWidth: ROW_HEADER_WIDTH,
21597
+ frozenPaneBottom: HEADER_HEIGHT,
21598
+ frozenPaneRight: ROW_HEADER_WIDTH
21599
+ });
21107
21600
  const paintedDrawingViewportRef = React4.useRef({
21108
21601
  height: 0,
21109
21602
  left: 0,
@@ -21213,6 +21706,12 @@ function XlsxGrid({
21213
21706
  const applyCanvasViewportCompensation = React4.useCallback((liveViewport) => {
21214
21707
  const nextLiveViewport = liveViewport ?? liveDrawingViewportRef.current;
21215
21708
  const paintedViewport = paintedDrawingViewportRef.current;
21709
+ const {
21710
+ displayHeaderHeight: liveDisplayHeaderHeight,
21711
+ displayRowHeaderWidth: liveDisplayRowHeaderWidth,
21712
+ frozenPaneBottom: liveFrozenPaneBottom,
21713
+ frozenPaneRight: liveFrozenPaneRight
21714
+ } = canvasLayoutMetricsRef.current;
21216
21715
  const currentLiveGestureZoom = liveGestureZoomRef.current;
21217
21716
  const isLiveZooming2 = currentLiveGestureZoom !== null && zoomScale === currentLiveGestureZoom.baseZoomScale;
21218
21717
  const liveZoomScale2 = isLiveZooming2 ? Math.max(0.1, currentLiveGestureZoom.targetZoomScale / currentLiveGestureZoom.baseZoomScale) : 1;
@@ -21220,12 +21719,41 @@ function XlsxGrid({
21220
21719
  const scrollDeltaY = paintedViewport.top - nextLiveViewport.top;
21221
21720
  const liveZoomTranslateX2 = isLiveZooming2 ? currentLiveGestureZoom.anchor.x * (1 - liveZoomScale2) : 0;
21222
21721
  const liveZoomTranslateY2 = isLiveZooming2 ? currentLiveGestureZoom.anchor.y * (1 - liveZoomScale2) : 0;
21223
- const applyCanvasTransform = (canvas, translateX, translateY) => {
21224
- if (!canvas) {
21722
+ const applyElementTransform = (element, transform, willChange, transformOrigin = null) => {
21723
+ if (!element) {
21225
21724
  return;
21226
21725
  }
21227
- canvas.style.transform = translateX !== 0 || translateY !== 0 || liveZoomScale2 !== 1 ? `translate3d(${translateX}px, ${translateY}px, 0) scale(${liveZoomScale2})` : "";
21228
- canvas.style.willChange = translateX !== 0 || translateY !== 0 || liveZoomScale2 !== 1 ? "transform" : "";
21726
+ const cached = canvasTransformStyleCacheRef.current.get(element);
21727
+ if (cached?.transform !== transform || element.style.transform !== transform) {
21728
+ element.style.transform = transform;
21729
+ }
21730
+ if (cached?.willChange !== willChange || element.style.willChange !== willChange) {
21731
+ element.style.willChange = willChange;
21732
+ }
21733
+ if (transformOrigin !== null && (cached?.transformOrigin !== transformOrigin || element.style.transformOrigin !== transformOrigin)) {
21734
+ element.style.transformOrigin = transformOrigin;
21735
+ }
21736
+ canvasTransformStyleCacheRef.current.set(element, {
21737
+ transform,
21738
+ transformOrigin,
21739
+ willChange
21740
+ });
21741
+ };
21742
+ const applyCanvasTransform = (canvas, translateX, translateY) => {
21743
+ const shouldTransform = translateX !== 0 || translateY !== 0 || liveZoomScale2 !== 1;
21744
+ applyElementTransform(
21745
+ canvas,
21746
+ shouldTransform ? `translate3d(${translateX}px, ${translateY}px, 0) scale(${liveZoomScale2})` : "",
21747
+ shouldTransform ? "transform" : ""
21748
+ );
21749
+ };
21750
+ const applyOverlayTransform = (element, translateX, translateY) => {
21751
+ applyElementTransform(
21752
+ element,
21753
+ liveZoomScale2 !== 1 ? `translate3d(${translateX + liveZoomTranslateX2}px, ${translateY + liveZoomTranslateY2}px, 0) scale(${liveZoomScale2})` : `translate3d(${translateX}px, ${translateY}px, 0)`,
21754
+ "transform",
21755
+ "0 0"
21756
+ );
21229
21757
  };
21230
21758
  applyCanvasTransform(
21231
21759
  scrollBodyCanvasRef.current,
@@ -21272,6 +21800,26 @@ function XlsxGrid({
21272
21800
  liveZoomTranslateX2,
21273
21801
  liveZoomTranslateY2
21274
21802
  );
21803
+ applyOverlayTransform(
21804
+ canvasScrollOverlayContentRef.current,
21805
+ -nextLiveViewport.left - liveFrozenPaneRight,
21806
+ -nextLiveViewport.top - liveFrozenPaneBottom
21807
+ );
21808
+ applyOverlayTransform(
21809
+ canvasTopOverlayContentRef.current,
21810
+ -nextLiveViewport.left - liveFrozenPaneRight,
21811
+ -liveDisplayHeaderHeight
21812
+ );
21813
+ applyOverlayTransform(
21814
+ canvasLeftOverlayContentRef.current,
21815
+ -liveDisplayRowHeaderWidth,
21816
+ -nextLiveViewport.top - liveFrozenPaneBottom
21817
+ );
21818
+ applyOverlayTransform(
21819
+ canvasCornerOverlayContentRef.current,
21820
+ -liveDisplayRowHeaderWidth,
21821
+ -liveDisplayHeaderHeight
21822
+ );
21275
21823
  }, [zoomScale]);
21276
21824
  const updateLiveGestureZoomState = React4.useCallback((nextState) => {
21277
21825
  const resolvedState = typeof nextState === "function" ? nextState(liveGestureZoomRef.current) : nextState;
@@ -21374,6 +21922,11 @@ function XlsxGrid({
21374
21922
  if (matchesLiveViewport && matchesStateViewport) {
21375
21923
  return;
21376
21924
  }
21925
+ const shouldCommitDeferredViewport = !matchesStateViewport && (stateViewport.width !== nextViewport.width || stateViewport.height !== nextViewport.height || Math.abs(nextViewport.left - stateViewport.left) >= CANVAS_DEFERRED_VIEWPORT_SYNC_THRESHOLD_PX || Math.abs(nextViewport.top - stateViewport.top) >= CANVAS_DEFERRED_VIEWPORT_SYNC_THRESHOLD_PX);
21926
+ if (!shouldCommitDeferredViewport) {
21927
+ pendingDrawingViewportRef.current = null;
21928
+ return;
21929
+ }
21377
21930
  pendingDrawingViewportRef.current = nextViewport;
21378
21931
  if (drawingViewportFrameRef.current !== null) {
21379
21932
  return;
@@ -21661,6 +22214,12 @@ function XlsxGrid({
21661
22214
  ) : displayRowHeaderWidth,
21662
22215
  [displayActualColWidths, displayDefaultColWidth, displayRowHeaderWidth, frozenCols, stickyLeftByCol]
21663
22216
  );
22217
+ canvasLayoutMetricsRef.current = {
22218
+ displayHeaderHeight,
22219
+ displayRowHeaderWidth,
22220
+ frozenPaneBottom,
22221
+ frozenPaneRight
22222
+ };
21664
22223
  const rowPrefixSumsRef = React4.useRef(rowPrefixSums);
21665
22224
  const colPrefixSumsRef = React4.useRef(colPrefixSums);
21666
22225
  const firstVisibleRow = visibleRows[0];
@@ -21712,6 +22271,7 @@ function XlsxGrid({
21712
22271
  }, [charts, images, shapes]);
21713
22272
  const shouldVirtualizeRows = visibleRows.length > 0;
21714
22273
  const shouldVirtualizeCols = visibleCols.length > 0 && frozenCols.length === 0;
22274
+ const shouldUseDomVirtualizer = !experimentalCanvas;
21715
22275
  const getScrollElement = React4.useCallback(() => scrollRef.current, []);
21716
22276
  const estimateRowSize = React4.useCallback(
21717
22277
  (index) => displayEffectiveRowHeights[index] ?? displayDefaultRowHeight,
@@ -21725,6 +22285,7 @@ function XlsxGrid({
21725
22285
  const getColItemKey = React4.useCallback((index) => visibleCols[index] ?? index, [visibleCols]);
21726
22286
  const rowVirtualizer = useVirtualizer({
21727
22287
  count: visibleRows.length,
22288
+ enabled: shouldUseDomVirtualizer,
21728
22289
  estimateSize: estimateRowSize,
21729
22290
  getScrollElement,
21730
22291
  getItemKey: getRowItemKey,
@@ -21732,14 +22293,15 @@ function XlsxGrid({
21732
22293
  });
21733
22294
  const colVirtualizer = useVirtualizer({
21734
22295
  count: visibleCols.length,
22296
+ enabled: shouldUseDomVirtualizer,
21735
22297
  estimateSize: estimateColSize,
21736
22298
  getScrollElement,
21737
22299
  getItemKey: getColItemKey,
21738
22300
  horizontal: true,
21739
22301
  overscan: 6
21740
22302
  });
21741
- const currentRowVirtualItems = rowVirtualizer.getVirtualItems();
21742
- const currentColVirtualItems = colVirtualizer.getVirtualItems();
22303
+ const currentRowVirtualItems = shouldUseDomVirtualizer ? rowVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ITEMS;
22304
+ const currentColVirtualItems = shouldUseDomVirtualizer ? colVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ITEMS;
21743
22305
  const frozenRowVirtualIndices = React4.useMemo(
21744
22306
  () => frozenRows.map((row) => rowIndexByActual.get(row)).filter((index) => index !== void 0),
21745
22307
  [frozenRows, rowIndexByActual]
@@ -21975,10 +22537,12 @@ function XlsxGrid({
21975
22537
  scroller.scrollTop = scroller.scrollTop / previousZoomFactor * zoomFactor;
21976
22538
  }
21977
22539
  previousZoomFactorRef.current = zoomFactor;
21978
- rowVirtualizer.measure();
21979
- colVirtualizer.measure();
22540
+ if (shouldUseDomVirtualizer) {
22541
+ rowVirtualizer.measure();
22542
+ colVirtualizer.measure();
22543
+ }
21980
22544
  syncDrawingViewport(scroller, { immediate: true });
21981
- }, [syncDrawingViewport, zoomFactor]);
22545
+ }, [shouldUseDomVirtualizer, syncDrawingViewport, zoomFactor]);
21982
22546
  React4.useLayoutEffect(() => {
21983
22547
  syncDrawingViewport(scrollRef.current, { immediate: true });
21984
22548
  }, [activeSheet, activeTabIndex, displayColLimit, displayRowLimit, syncDrawingViewport, zoomFactor]);
@@ -22074,7 +22638,7 @@ function XlsxGrid({
22074
22638
  const initialUsedCol = resolveFirstUsedVisibleIndex(visibleCols, activeSheet?.minUsedCol ?? -1);
22075
22639
  const initialScrollTop = initialUsedRow > 0 ? sumPrefixRange(actualRowPrefixSums, 0, initialUsedRow - 1) : 0;
22076
22640
  const initialScrollLeft = initialUsedCol > 0 ? sumPrefixRange(actualColPrefixSums, 0, initialUsedCol - 1) : 0;
22077
- if (shouldVirtualizeRows) {
22641
+ if (shouldUseDomVirtualizer && shouldVirtualizeRows) {
22078
22642
  rowVirtualizer.scrollToOffset(initialScrollTop);
22079
22643
  } else if (scrollRef.current) {
22080
22644
  scrollRef.current.scrollTop = initialScrollTop;
@@ -22082,7 +22646,7 @@ function XlsxGrid({
22082
22646
  if (scrollRef.current) {
22083
22647
  scrollRef.current.scrollLeft = initialScrollLeft;
22084
22648
  }
22085
- if (shouldVirtualizeCols && frozenCols.length === 0) {
22649
+ if (shouldUseDomVirtualizer && shouldVirtualizeCols && frozenCols.length === 0) {
22086
22650
  colVirtualizer.scrollToOffset(initialScrollLeft);
22087
22651
  }
22088
22652
  if (scrollRef.current) {
@@ -22103,6 +22667,7 @@ function XlsxGrid({
22103
22667
  isWorkerBacked,
22104
22668
  shouldVirtualizeCols,
22105
22669
  shouldVirtualizeRows,
22670
+ shouldUseDomVirtualizer,
22106
22671
  syncDrawingViewport,
22107
22672
  rowVirtualizer,
22108
22673
  visibleCols,
@@ -22122,24 +22687,40 @@ function XlsxGrid({
22122
22687
  setPendingNavigation(null);
22123
22688
  }, [activeSheetIndex, pendingNavigation, selectCell]);
22124
22689
  React4.useEffect(() => {
22125
- if (shouldVirtualizeRows) {
22690
+ if (shouldUseDomVirtualizer && shouldVirtualizeRows) {
22126
22691
  rowVirtualizer.measure();
22127
22692
  }
22128
- if (shouldVirtualizeCols) {
22693
+ if (shouldUseDomVirtualizer && shouldVirtualizeCols) {
22129
22694
  colVirtualizer.measure();
22130
22695
  }
22131
- }, [activeSheetIndex, revision, shouldVirtualizeCols, shouldVirtualizeRows, visibleCols.length, visibleRows.length]);
22696
+ }, [
22697
+ activeSheetIndex,
22698
+ revision,
22699
+ shouldUseDomVirtualizer,
22700
+ shouldVirtualizeCols,
22701
+ shouldVirtualizeRows,
22702
+ visibleCols.length,
22703
+ visibleRows.length
22704
+ ]);
22132
22705
  const handleScrollerScroll = React4.useCallback((event) => {
22133
22706
  const currentScroller = event.currentTarget;
22134
22707
  cachedScrollerRectRef.current = null;
22135
- syncDrawingViewport(currentScroller, { immediate: true });
22708
+ const paintedViewport = paintedDrawingViewportRef.current;
22709
+ const shouldSyncDrawingViewportImmediately = Math.abs(currentScroller.scrollLeft - paintedViewport.left) > CANVAS_IMMEDIATE_VIEWPORT_SYNC_THRESHOLD_PX || Math.abs(currentScroller.scrollTop - paintedViewport.top) > CANVAS_IMMEDIATE_VIEWPORT_SYNC_THRESHOLD_PX;
22710
+ syncDrawingViewport(currentScroller, { immediate: shouldSyncDrawingViewportImmediately });
22136
22711
  if (currentScroller.scrollHeight - (currentScroller.scrollTop + currentScroller.clientHeight) < OPEN_GRID_VERTICAL_EDGE_PX) {
22137
- setDisplayRowLimit((current) => current + OPEN_GRID_ROW_GROWTH);
22712
+ setDisplayRowLimit((current) => {
22713
+ const nextLimit = current + OPEN_GRID_ROW_GROWTH;
22714
+ return readOnly && current < maxRowDisplayLimit ? Math.min(maxRowDisplayLimit, nextLimit) : nextLimit;
22715
+ });
22138
22716
  }
22139
22717
  if (currentScroller.scrollWidth - (currentScroller.scrollLeft + currentScroller.clientWidth) < OPEN_GRID_HORIZONTAL_EDGE_PX) {
22140
- setDisplayColLimit((current) => current + OPEN_GRID_COL_GROWTH);
22718
+ setDisplayColLimit((current) => {
22719
+ const nextLimit = current + OPEN_GRID_COL_GROWTH;
22720
+ return readOnly && current < maxColDisplayLimit ? Math.min(maxColDisplayLimit, nextLimit) : nextLimit;
22721
+ });
22141
22722
  }
22142
- }, [syncDrawingViewport]);
22723
+ }, [maxColDisplayLimit, maxRowDisplayLimit, readOnly, syncDrawingViewport]);
22143
22724
  React4.useEffect(() => {
22144
22725
  const scroller = scrollRef.current;
22145
22726
  if (!scroller || !enableGestureZoom) {
@@ -23045,14 +23626,14 @@ function XlsxGrid({
23045
23626
  },
23046
23627
  left: {
23047
23628
  cols: frozenColItems,
23048
- rows: frozenRows.length > 0 ? canvasVisibleRowItems : scrollRowItems
23629
+ rows: scrollRowItems
23049
23630
  },
23050
23631
  scroll: {
23051
- cols: frozenCols.length > 0 ? canvasVisibleColItems : scrollColItems,
23052
- rows: frozenRows.length > 0 ? canvasVisibleRowItems : scrollRowItems
23632
+ cols: scrollColItems,
23633
+ rows: scrollRowItems
23053
23634
  },
23054
23635
  top: {
23055
- cols: frozenCols.length > 0 ? canvasVisibleColItems : scrollColItems,
23636
+ cols: scrollColItems,
23056
23637
  rows: frozenRowItems
23057
23638
  }
23058
23639
  };
@@ -23186,6 +23767,51 @@ function XlsxGrid({
23186
23767
  visibleRows
23187
23768
  ]
23188
23769
  );
23770
+ const shouldBakeCanvasStaticDrawings = Boolean(experimentalCanvas && readOnly && showImages);
23771
+ const shouldBakeCanvasImages = Boolean(shouldBakeCanvasStaticDrawings && !renderImage && !renderImageSelection);
23772
+ const bakedShapeRects = React4.useMemo(
23773
+ () => shouldBakeCanvasStaticDrawings ? shapeRects.filter(({ shape }) => !shape.hyperlink) : [],
23774
+ [shapeRects, shouldBakeCanvasStaticDrawings]
23775
+ );
23776
+ const domShapeRects = React4.useMemo(
23777
+ () => shouldBakeCanvasStaticDrawings ? shapeRects.filter(({ shape }) => shape.hyperlink) : shapeRects,
23778
+ [shapeRects, shouldBakeCanvasStaticDrawings]
23779
+ );
23780
+ const bakedFormControlRects = React4.useMemo(
23781
+ () => shouldBakeCanvasStaticDrawings ? formControlRects : [],
23782
+ [formControlRects, shouldBakeCanvasStaticDrawings]
23783
+ );
23784
+ const domFormControlRects = React4.useMemo(
23785
+ () => shouldBakeCanvasStaticDrawings ? [] : formControlRects,
23786
+ [formControlRects, shouldBakeCanvasStaticDrawings]
23787
+ );
23788
+ const bakedImageRects = React4.useMemo(
23789
+ () => shouldBakeCanvasImages ? imageRects.filter(({ image }) => !image.hyperlink && selectedImageId !== image.id) : [],
23790
+ [imageRects, selectedImageId, shouldBakeCanvasImages]
23791
+ );
23792
+ const domImageRects = React4.useMemo(
23793
+ () => shouldBakeCanvasImages ? imageRects.filter(({ image }) => image.hyperlink || selectedImageId === image.id) : imageRects,
23794
+ [imageRects, selectedImageId, shouldBakeCanvasImages]
23795
+ );
23796
+ const hasCanvasDomDrawingOverlays = Boolean(
23797
+ showImages && (chartRects.length > 0 || domShapeRects.length > 0 || domFormControlRects.length > 0 || domImageRects.length > 0)
23798
+ );
23799
+ const bakedCanvasDrawingSignature = React4.useMemo(() => {
23800
+ if (!shouldBakeCanvasStaticDrawings) {
23801
+ return "";
23802
+ }
23803
+ const shapeSignature = bakedShapeRects.map(({ rect, shape }) => `s:${shape.id}:${shape.zIndex}:${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`).join("|");
23804
+ const controlSignature = bakedFormControlRects.map(({ control, rect }) => `f:${control.id}:${control.zIndex}:${Boolean(control.checked)}:${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`).join("|");
23805
+ const imageSignature = bakedImageRects.map(({ image, rect }) => `i:${image.id}:${image.src}:${image.zIndex}:${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`).join("|");
23806
+ return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${controlSignature}:${imageSignature}`;
23807
+ }, [
23808
+ bakedFormControlRects,
23809
+ bakedImageRects,
23810
+ bakedShapeRects,
23811
+ canvasImageLoadVersion,
23812
+ revision,
23813
+ shouldBakeCanvasStaticDrawings
23814
+ ]);
23189
23815
  const resolveMountedCellOverlayRect = React4.useCallback((element) => {
23190
23816
  const wrapper = wrapperRef.current;
23191
23817
  if (!wrapper) {
@@ -24054,6 +24680,24 @@ function XlsxGrid({
24054
24680
  rowPrefixSums,
24055
24681
  stickyTopByRow
24056
24682
  ]);
24683
+ const scrollBodyViewportWidth = Math.max(0, drawingViewport.width - frozenPaneRight);
24684
+ const scrollBodyViewportHeight = Math.max(0, drawingViewport.height - frozenPaneBottom);
24685
+ const canvasScrollBufferX = Math.min(CANVAS_SCROLL_BUFFER_PX, scrollBodyViewportWidth);
24686
+ const canvasScrollBufferY = Math.min(CANVAS_SCROLL_BUFFER_PX, scrollBodyViewportHeight);
24687
+ const scrollBodyCanvasLeft = frozenPaneRight - canvasScrollBufferX;
24688
+ const scrollBodyCanvasTop = frozenPaneBottom - canvasScrollBufferY;
24689
+ const scrollBodyCanvasWidth = scrollBodyViewportWidth + canvasScrollBufferX * 2;
24690
+ const scrollBodyCanvasHeight = scrollBodyViewportHeight + canvasScrollBufferY * 2;
24691
+ const topBodyCanvasWidth = scrollBodyCanvasWidth;
24692
+ const topBodyCanvasHeight = Math.max(0, frozenPaneBottom - displayHeaderHeight);
24693
+ const leftBodyCanvasWidth = Math.max(0, frozenPaneRight - displayRowHeaderWidth);
24694
+ const leftBodyCanvasHeight = scrollBodyCanvasHeight;
24695
+ const cornerBodyCanvasWidth = leftBodyCanvasWidth;
24696
+ const cornerBodyCanvasHeight = topBodyCanvasHeight;
24697
+ const topFrozenHeaderCanvasWidth = leftBodyCanvasWidth;
24698
+ const topScrollHeaderCanvasWidth = scrollBodyCanvasWidth;
24699
+ const leftFrozenHeaderCanvasHeight = topBodyCanvasHeight;
24700
+ const leftScrollHeaderCanvasHeight = scrollBodyCanvasHeight;
24057
24701
  const canvasColumnHeaderCells = React4.useMemo(
24058
24702
  () => canvasVisibleColItems.flatMap((column) => {
24059
24703
  const rect = resolveCanvasColumnHeaderRect(column.actualCol);
@@ -24066,7 +24710,7 @@ function XlsxGrid({
24066
24710
  height: displayHeaderHeight,
24067
24711
  isFrozen,
24068
24712
  left: rect.left,
24069
- localLeft: rect.left - (isFrozen ? displayRowHeaderWidth : frozenPaneRight),
24713
+ localLeft: rect.left - (isFrozen ? displayRowHeaderWidth : scrollBodyCanvasLeft),
24070
24714
  width: rect.width
24071
24715
  }];
24072
24716
  }),
@@ -24074,8 +24718,8 @@ function XlsxGrid({
24074
24718
  canvasVisibleColItems,
24075
24719
  displayHeaderHeight,
24076
24720
  displayRowHeaderWidth,
24077
- frozenPaneRight,
24078
24721
  resolveCanvasColumnHeaderRect,
24722
+ scrollBodyCanvasLeft,
24079
24723
  stickyLeftByCol
24080
24724
  ]
24081
24725
  );
@@ -24090,15 +24734,15 @@ function XlsxGrid({
24090
24734
  actualRow: row.actualRow,
24091
24735
  height: rect.height,
24092
24736
  isFrozen,
24093
- localTop: rect.top - (isFrozen ? displayHeaderHeight : frozenPaneBottom),
24737
+ localTop: rect.top - (isFrozen ? displayHeaderHeight : scrollBodyCanvasTop),
24094
24738
  top: rect.top
24095
24739
  }];
24096
24740
  }),
24097
24741
  [
24098
24742
  canvasVisibleRowItems,
24099
24743
  displayHeaderHeight,
24100
- frozenPaneBottom,
24101
24744
  resolveCanvasRowHeaderRect,
24745
+ scrollBodyCanvasTop,
24102
24746
  stickyTopByRow
24103
24747
  ]
24104
24748
  );
@@ -24365,10 +25009,46 @@ function XlsxGrid({
24365
25009
  rowPrefixSums,
24366
25010
  startCellSelection
24367
25011
  ]);
25012
+ const getCanvasImage = React4.useCallback((image) => {
25013
+ if (typeof Image === "undefined") {
25014
+ return null;
25015
+ }
25016
+ const cacheKey = image.id;
25017
+ const cached = canvasImageCacheRef.current.get(cacheKey);
25018
+ if (cached && cached.src === image.src) {
25019
+ return cached.loaded && !cached.failed ? cached.image : null;
25020
+ }
25021
+ const imageElement = new Image();
25022
+ const entry = {
25023
+ failed: false,
25024
+ image: imageElement,
25025
+ loaded: false,
25026
+ src: image.src
25027
+ };
25028
+ canvasImageCacheRef.current.set(cacheKey, entry);
25029
+ imageElement.onload = () => {
25030
+ entry.loaded = true;
25031
+ setCanvasImageLoadVersion((version) => version + 1);
25032
+ };
25033
+ imageElement.onerror = () => {
25034
+ entry.failed = true;
25035
+ setCanvasImageLoadVersion((version) => version + 1);
25036
+ };
25037
+ imageElement.src = image.src;
25038
+ return null;
25039
+ }, []);
24368
25040
  React4.useLayoutEffect(() => {
24369
25041
  if (!experimentalCanvas) {
24370
25042
  return;
24371
25043
  }
25044
+ const canvasProfileTarget = typeof window !== "undefined" ? window.__xlsxCanvasProfile : void 0;
25045
+ const canvasProfileStart = canvasProfileTarget ? performance.now() : 0;
25046
+ let canvasProfileBodyStart = 0;
25047
+ let canvasProfileBodyMs = 0;
25048
+ let canvasProfileCulledCells = 0;
25049
+ let canvasProfileDirtyRects = 0;
25050
+ let canvasProfileLookedUpCells = 0;
25051
+ let canvasProfilePaintedCells = 0;
24372
25052
  const dpr = typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
24373
25053
  function configureCanvas(canvas, width, height, options) {
24374
25054
  if (!canvas) {
@@ -24388,7 +25068,7 @@ function XlsxGrid({
24388
25068
  if (canvas.style.height !== `${height}px`) {
24389
25069
  canvas.style.height = `${height}px`;
24390
25070
  }
24391
- const context = canvas.getContext("2d");
25071
+ const context = canvas.getContext("2d", { alpha: false });
24392
25072
  if (!context) {
24393
25073
  return null;
24394
25074
  }
@@ -24407,6 +25087,7 @@ function XlsxGrid({
24407
25087
  const rangeSignature = buildRangeSignature(normalizedVisibleRange);
24408
25088
  const nextBodyCanvasSignature = {
24409
25089
  activeSheet: activeSheet ?? null,
25090
+ bakedDrawingSignature: bakedCanvasDrawingSignature,
24410
25091
  bodyHeight,
24411
25092
  bodyWidth,
24412
25093
  colSignature: buildRenderedAxisSignature(canvasVisibleColItems, (item) => item.index),
@@ -24434,10 +25115,10 @@ function XlsxGrid({
24434
25115
  const previousBodyCanvasSignature = paintedBodyCanvasSignatureRef.current;
24435
25116
  const previousHeaderCanvasSignature = paintedHeaderCanvasSignatureRef.current;
24436
25117
  const previousPaintedViewport = paintedDrawingViewportRef.current;
24437
- 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);
25118
+ const shouldRepaintBody = !(previousBodyCanvasSignature && previousBodyCanvasSignature.activeSheet === nextBodyCanvasSignature.activeSheet && previousBodyCanvasSignature.bakedDrawingSignature === nextBodyCanvasSignature.bakedDrawingSignature && 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);
24438
25119
  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);
24439
25120
  const canBlitBody = Boolean(
24440
- 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
25121
+ shouldRepaintBody && previousBodyCanvasSignature && previousBodyCanvasSignature.activeSheet === nextBodyCanvasSignature.activeSheet && previousBodyCanvasSignature.bakedDrawingSignature === nextBodyCanvasSignature.bakedDrawingSignature && 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
24441
25122
  );
24442
25123
  const canBlitTopHeader = Boolean(
24443
25124
  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
@@ -24448,49 +25129,37 @@ function XlsxGrid({
24448
25129
  if (!shouldRepaintBody && !shouldRepaintHeaders) {
24449
25130
  return;
24450
25131
  }
24451
- const scrollBodyCanvasWidth2 = Math.max(0, drawingViewport.width - frozenPaneRight);
24452
- const scrollBodyCanvasHeight2 = Math.max(0, drawingViewport.height - frozenPaneBottom);
24453
- const topBodyCanvasWidth2 = scrollBodyCanvasWidth2;
24454
- const topBodyCanvasHeight2 = Math.max(0, frozenPaneBottom - displayHeaderHeight);
24455
- const leftBodyCanvasWidth2 = Math.max(0, frozenPaneRight - displayRowHeaderWidth);
24456
- const leftBodyCanvasHeight2 = scrollBodyCanvasHeight2;
24457
- const cornerBodyCanvasWidth2 = leftBodyCanvasWidth2;
24458
- const cornerBodyCanvasHeight2 = topBodyCanvasHeight2;
24459
- const topFrozenHeaderCanvasWidth2 = leftBodyCanvasWidth2;
24460
- const topScrollHeaderCanvasWidth2 = scrollBodyCanvasWidth2;
24461
- const leftFrozenHeaderCanvasHeight2 = topBodyCanvasHeight2;
24462
- const leftScrollHeaderCanvasHeight2 = scrollBodyCanvasHeight2;
24463
25132
  const paneBounds = {
24464
25133
  corner: {
24465
- height: cornerBodyCanvasHeight2,
25134
+ height: cornerBodyCanvasHeight,
24466
25135
  left: displayRowHeaderWidth,
24467
25136
  top: displayHeaderHeight,
24468
- width: cornerBodyCanvasWidth2
25137
+ width: cornerBodyCanvasWidth
24469
25138
  },
24470
25139
  left: {
24471
- height: leftBodyCanvasHeight2,
25140
+ height: leftBodyCanvasHeight,
24472
25141
  left: displayRowHeaderWidth,
24473
- top: frozenPaneBottom,
24474
- width: leftBodyCanvasWidth2
25142
+ top: scrollBodyCanvasTop,
25143
+ width: leftBodyCanvasWidth
24475
25144
  },
24476
25145
  scroll: {
24477
- height: scrollBodyCanvasHeight2,
24478
- left: frozenPaneRight,
24479
- top: frozenPaneBottom,
24480
- width: scrollBodyCanvasWidth2
25146
+ height: scrollBodyCanvasHeight,
25147
+ left: scrollBodyCanvasLeft,
25148
+ top: scrollBodyCanvasTop,
25149
+ width: scrollBodyCanvasWidth
24481
25150
  },
24482
25151
  top: {
24483
- height: topBodyCanvasHeight2,
24484
- left: frozenPaneRight,
25152
+ height: topBodyCanvasHeight,
25153
+ left: scrollBodyCanvasLeft,
24485
25154
  top: displayHeaderHeight,
24486
- width: topBodyCanvasWidth2
25155
+ width: topBodyCanvasWidth
24487
25156
  }
24488
25157
  };
24489
25158
  const bodyContexts = shouldRepaintBody ? {
24490
- corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth2, cornerBodyCanvasHeight2, { clear: !canBlitBody }),
24491
- left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth2, leftBodyCanvasHeight2, { clear: !canBlitBody }),
24492
- scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth2, scrollBodyCanvasHeight2, { clear: !canBlitBody }),
24493
- top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth2, topBodyCanvasHeight2, { clear: !canBlitBody })
25159
+ corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth, cornerBodyCanvasHeight, { clear: !canBlitBody }),
25160
+ left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth, leftBodyCanvasHeight, { clear: !canBlitBody }),
25161
+ scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth, scrollBodyCanvasHeight, { clear: !canBlitBody }),
25162
+ top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth, topBodyCanvasHeight, { clear: !canBlitBody })
24494
25163
  } : {
24495
25164
  corner: null,
24496
25165
  left: null,
@@ -24498,21 +25167,21 @@ function XlsxGrid({
24498
25167
  top: null
24499
25168
  };
24500
25169
  const topHeaderContexts = shouldRepaintHeaders ? {
24501
- frozen: configureCanvas(topFrozenHeaderCanvasRef.current, topFrozenHeaderCanvasWidth2, headerHeight),
24502
- scroll: configureCanvas(topScrollHeaderCanvasRef.current, topScrollHeaderCanvasWidth2, headerHeight, { clear: !canBlitTopHeader })
25170
+ frozen: configureCanvas(topFrozenHeaderCanvasRef.current, topFrozenHeaderCanvasWidth, headerHeight),
25171
+ scroll: configureCanvas(topScrollHeaderCanvasRef.current, topScrollHeaderCanvasWidth, headerHeight, { clear: !canBlitTopHeader })
24503
25172
  } : {
24504
25173
  frozen: null,
24505
25174
  scroll: null
24506
25175
  };
24507
25176
  const leftHeaderContexts = shouldRepaintHeaders ? {
24508
- frozen: configureCanvas(leftFrozenHeaderCanvasRef.current, rowHeaderWidth, leftFrozenHeaderCanvasHeight2),
24509
- scroll: configureCanvas(leftScrollHeaderCanvasRef.current, rowHeaderWidth, leftScrollHeaderCanvasHeight2, { clear: !canBlitLeftHeader })
25177
+ frozen: configureCanvas(leftFrozenHeaderCanvasRef.current, rowHeaderWidth, leftFrozenHeaderCanvasHeight),
25178
+ scroll: configureCanvas(leftScrollHeaderCanvasRef.current, rowHeaderWidth, leftScrollHeaderCanvasHeight, { clear: !canBlitLeftHeader })
24510
25179
  } : {
24511
25180
  frozen: null,
24512
25181
  scroll: null
24513
25182
  };
24514
25183
  const cornerContext = shouldRepaintHeaders ? configureCanvas(cornerHeaderCanvasRef.current, rowHeaderWidth, headerHeight) : null;
24515
- 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)) {
25184
+ if (shouldRepaintBody && (!bodyContexts.scroll || !bodyContexts.top || !bodyContexts.left || !bodyContexts.corner) || shouldRepaintHeaders && (!cornerContext || topFrozenHeaderCanvasWidth > 0 && !topHeaderContexts.frozen || topScrollHeaderCanvasWidth > 0 && !topHeaderContexts.scroll || leftFrozenHeaderCanvasHeight > 0 && !leftHeaderContexts.frozen || leftScrollHeaderCanvasHeight > 0 && !leftHeaderContexts.scroll)) {
24516
25185
  return;
24517
25186
  }
24518
25187
  const showGridLines = activeSheet?.showGridLines ?? true;
@@ -24529,10 +25198,282 @@ function XlsxGrid({
24529
25198
  scroll: [],
24530
25199
  top: []
24531
25200
  };
24532
- let topScrollHeaderDirtyRects = buildFullCanvasDirtyRect(topScrollHeaderCanvasWidth2, headerHeight);
24533
- let leftScrollHeaderDirtyRects = buildFullCanvasDirtyRect(rowHeaderWidth, leftScrollHeaderCanvasHeight2);
25201
+ let topScrollHeaderDirtyRects = buildFullCanvasDirtyRect(topScrollHeaderCanvasWidth, headerHeight);
25202
+ let leftScrollHeaderDirtyRects = buildFullCanvasDirtyRect(rowHeaderWidth, leftScrollHeaderCanvasHeight);
25203
+ const bakedCanvasDrawingEntries = shouldBakeCanvasStaticDrawings ? [
25204
+ ...bakedShapeRects.map(({ rect, shape }) => ({
25205
+ kind: "shape",
25206
+ rect,
25207
+ shape,
25208
+ zIndex: shape.zIndex
25209
+ })),
25210
+ ...bakedFormControlRects.map(({ control, rect }) => ({
25211
+ control,
25212
+ kind: "formControl",
25213
+ rect,
25214
+ zIndex: control.zIndex
25215
+ })),
25216
+ ...bakedImageRects.map(({ image, rect }) => ({
25217
+ image,
25218
+ kind: "image",
25219
+ rect,
25220
+ zIndex: image.zIndex
25221
+ }))
25222
+ ].sort((left, right) => left.zIndex - right.zIndex) : [];
25223
+ const resolveBakedCanvasLocalRect = (rect, pane) => {
25224
+ const bounds = paneBounds[pane];
25225
+ const scrollX = pane === "scroll" || pane === "top" ? drawingViewport.left : 0;
25226
+ const scrollY = pane === "scroll" || pane === "left" ? drawingViewport.top : 0;
25227
+ return {
25228
+ height: rect.height,
25229
+ left: rect.left - scrollX - bounds.left,
25230
+ top: rect.top - scrollY - bounds.top,
25231
+ width: rect.width
25232
+ };
25233
+ };
25234
+ const drawBakedShapeText = (context, shape, left, top, width, height) => {
25235
+ if (shape.paragraphs.length === 0) {
25236
+ return;
25237
+ }
25238
+ const inset = shape.textBox?.insetPx;
25239
+ const paddingLeft = (inset?.left ?? 6) * zoomFactor;
25240
+ const paddingRight = (inset?.right ?? 6) * zoomFactor;
25241
+ const paddingTop = (inset?.top ?? 4) * zoomFactor;
25242
+ const paddingBottom = (inset?.bottom ?? 4) * zoomFactor;
25243
+ const textLeft = left + paddingLeft;
25244
+ const textTop = top + paddingTop;
25245
+ const textWidth = Math.max(0, width - paddingLeft - paddingRight);
25246
+ const textHeight = Math.max(0, height - paddingTop - paddingBottom);
25247
+ if (textWidth <= 0 || textHeight <= 0) {
25248
+ return;
25249
+ }
25250
+ const lineMetrics = shape.paragraphs.map((paragraph) => {
25251
+ const lineHeight = Math.max(
25252
+ 12 * zoomFactor,
25253
+ ...paragraph.runs.map((run) => (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor * 1.2)
25254
+ );
25255
+ const widthPx = paragraph.runs.reduce((total, run) => {
25256
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
25257
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
25258
+ return total + measureCanvasTextWidth(context, run.text);
25259
+ }, 0);
25260
+ return { lineHeight, widthPx };
25261
+ });
25262
+ const totalTextHeight = lineMetrics.reduce((total, metric) => total + metric.lineHeight, 0);
25263
+ let y = textTop;
25264
+ if (shape.textBox?.verticalAlign === "middle") {
25265
+ y = textTop + Math.max(0, (textHeight - totalTextHeight) / 2);
25266
+ } else if (shape.textBox?.verticalAlign === "bottom") {
25267
+ y = textTop + Math.max(0, textHeight - totalTextHeight);
25268
+ }
25269
+ context.save();
25270
+ context.beginPath();
25271
+ context.rect(textLeft, textTop, textWidth, textHeight);
25272
+ context.clip();
25273
+ context.textBaseline = "middle";
25274
+ shape.paragraphs.forEach((paragraph, paragraphIndex) => {
25275
+ const metric = lineMetrics[paragraphIndex] ?? { lineHeight: 14 * zoomFactor, widthPx: 0 };
25276
+ let x = textLeft;
25277
+ const align = paragraph.align ?? shape.textBox?.horizontalAlign ?? "left";
25278
+ if (align === "center") {
25279
+ x = textLeft + Math.max(0, (textWidth - metric.widthPx) / 2);
25280
+ } else if (align === "right") {
25281
+ x = textLeft + Math.max(0, textWidth - metric.widthPx);
25282
+ }
25283
+ paragraph.runs.forEach((run) => {
25284
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
25285
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
25286
+ context.fillStyle = run.color ?? "#000000";
25287
+ context.textAlign = "left";
25288
+ const textY = y + metric.lineHeight / 2;
25289
+ context.fillText(run.text, x, textY);
25290
+ if (run.underline && run.text.length > 0) {
25291
+ const textWidthPx = measureCanvasTextWidth(context, run.text);
25292
+ context.strokeStyle = run.color ?? "#000000";
25293
+ context.lineWidth = Math.max(1, zoomFactor * 0.75);
25294
+ context.beginPath();
25295
+ context.moveTo(x, textY + Math.max(2, fontSize * 0.28));
25296
+ context.lineTo(x + textWidthPx, textY + Math.max(2, fontSize * 0.28));
25297
+ context.stroke();
25298
+ }
25299
+ x += measureCanvasTextWidth(context, run.text);
25300
+ });
25301
+ y += metric.lineHeight;
25302
+ });
25303
+ context.restore();
25304
+ };
25305
+ const drawBakedShape = (context, shape, rect) => {
25306
+ const fillColor = shape.fill?.none ? "transparent" : shape.fill?.color ?? "transparent";
25307
+ const strokeColor = shape.stroke?.none ? "transparent" : shape.stroke?.color ?? "transparent";
25308
+ const lineWidth = Math.max(0, (shape.stroke?.widthPx ?? (shape.geometry === "line" ? 2 : 1)) * zoomFactor);
25309
+ const vectorShape = resolveShapeVector(shape);
25310
+ const opacity = Math.min(shape.fill?.opacity ?? 1, shape.stroke?.opacity ?? 1);
25311
+ context.save();
25312
+ context.translate(rect.left + rect.width / 2, rect.top + rect.height / 2);
25313
+ if (shape.rotationDeg) {
25314
+ context.rotate(shape.rotationDeg * Math.PI / 180);
25315
+ }
25316
+ context.scale(shape.flipH ? -1 : 1, shape.flipV ? -1 : 1);
25317
+ context.globalAlpha *= opacity;
25318
+ context.lineWidth = lineWidth;
25319
+ context.strokeStyle = strokeColor;
25320
+ context.fillStyle = fillColor;
25321
+ applyCanvasShapeDash(context, shape.stroke?.dash, lineWidth);
25322
+ const localLeft = -rect.width / 2;
25323
+ const localTop = -rect.height / 2;
25324
+ if (vectorShape && typeof Path2D !== "undefined") {
25325
+ context.save();
25326
+ context.translate(localLeft, localTop);
25327
+ context.scale(
25328
+ rect.width / Math.max(1, vectorShape.viewBox.width),
25329
+ rect.height / Math.max(1, vectorShape.viewBox.height)
25330
+ );
25331
+ const path = getCachedCanvasPath2D(vectorShape.path);
25332
+ if (!path) {
25333
+ context.restore();
25334
+ context.restore();
25335
+ return;
25336
+ }
25337
+ if (fillColor !== "transparent") {
25338
+ context.fill(path);
25339
+ }
25340
+ if (strokeColor !== "transparent" && lineWidth > 0) {
25341
+ context.stroke(path);
25342
+ }
25343
+ context.restore();
25344
+ } else if (shape.geometry === "ellipse") {
25345
+ context.beginPath();
25346
+ context.ellipse(0, 0, rect.width / 2, rect.height / 2, 0, 0, Math.PI * 2);
25347
+ if (fillColor !== "transparent") {
25348
+ context.fill();
25349
+ }
25350
+ if (strokeColor !== "transparent" && lineWidth > 0) {
25351
+ context.stroke();
25352
+ }
25353
+ } else {
25354
+ const radius = shape.geometry === "roundRect" ? 12 * zoomFactor : 0;
25355
+ drawCanvasRoundedRect(context, localLeft, localTop, rect.width, rect.height, radius);
25356
+ if (fillColor !== "transparent") {
25357
+ context.fill();
25358
+ }
25359
+ if (strokeColor !== "transparent" && lineWidth > 0) {
25360
+ context.stroke();
25361
+ }
25362
+ }
25363
+ drawBakedShapeText(context, shape, localLeft, localTop, rect.width, rect.height);
25364
+ context.restore();
25365
+ };
25366
+ const drawBakedFormControl = (context, control, rect) => {
25367
+ const label = resolveFormControlLabel(control);
25368
+ const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
25369
+ const textColor = control.textColor ?? "#000000";
25370
+ const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
25371
+ const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
25372
+ context.save();
25373
+ context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
25374
+ context.textBaseline = "middle";
25375
+ context.fillStyle = textColor;
25376
+ context.strokeStyle = stroke;
25377
+ context.lineWidth = Math.max(1, zoomFactor);
25378
+ if (control.kind === "group-box") {
25379
+ const labelInset = label ? Math.max(7, fontSizePx * 0.5) : 0;
25380
+ drawCanvasRoundedRect(context, rect.left, rect.top + labelInset, rect.width, Math.max(0, rect.height - labelInset), 2 * zoomFactor);
25381
+ context.stroke();
25382
+ if (label) {
25383
+ context.fillStyle = SHEET_SURFACE;
25384
+ const labelWidth = Math.min(rect.width - 16 * zoomFactor, measureCanvasTextWidth(context, label) + 8 * zoomFactor);
25385
+ context.fillRect(rect.left + 8 * zoomFactor, rect.top, labelWidth, fontSizePx * 1.2);
25386
+ context.fillStyle = textColor;
25387
+ context.textAlign = "left";
25388
+ context.fillText(label, rect.left + 12 * zoomFactor, rect.top + fontSizePx * 0.55);
25389
+ }
25390
+ context.restore();
25391
+ return;
25392
+ }
25393
+ if (control.kind === "button" || control.kind === "dropdown" || control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
25394
+ if (control.kind === "button") {
25395
+ const gradient = context.createLinearGradient(rect.left, rect.top, rect.left, rect.top + rect.height);
25396
+ gradient.addColorStop(0, "#f8fafc");
25397
+ gradient.addColorStop(1, "#e2e8f0");
25398
+ context.fillStyle = gradient;
25399
+ } else {
25400
+ context.fillStyle = "transparent";
25401
+ }
25402
+ drawCanvasRoundedRect(context, rect.left, rect.top, rect.width, rect.height, control.kind === "button" ? 4 * zoomFactor : 2 * zoomFactor);
25403
+ if (control.kind === "button") {
25404
+ context.fill();
25405
+ }
25406
+ context.stroke();
25407
+ }
25408
+ let textLeft = rect.left + 2 * zoomFactor;
25409
+ const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
25410
+ if (control.kind === "checkbox") {
25411
+ context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
25412
+ if (control.checked) {
25413
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
25414
+ context.fillRect(rect.left + 2 * zoomFactor + 1.5, rect.top + (rect.height - iconSize) / 2 + 1.5, Math.max(0, iconSize - 3), Math.max(0, iconSize - 3));
25415
+ context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
25416
+ context.beginPath();
25417
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
25418
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
25419
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
25420
+ context.stroke();
25421
+ }
25422
+ textLeft += iconSize + 4 * zoomFactor;
25423
+ } else if (control.kind === "radio") {
25424
+ context.beginPath();
25425
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
25426
+ context.stroke();
25427
+ if (control.checked) {
25428
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
25429
+ context.beginPath();
25430
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
25431
+ context.fill();
25432
+ }
25433
+ textLeft += iconSize + 4 * zoomFactor;
25434
+ }
25435
+ if (label) {
25436
+ const maxTextWidth = Math.max(0, rect.left + rect.width - textLeft - textRightInset);
25437
+ const text = truncateCanvasText(context, label, maxTextWidth);
25438
+ context.fillStyle = textColor;
25439
+ context.textAlign = control.textAlign === "right" ? "right" : control.textAlign === "center" ? "center" : "left";
25440
+ const textX = context.textAlign === "right" ? rect.left + rect.width - textRightInset : context.textAlign === "center" ? textLeft + maxTextWidth / 2 : textLeft;
25441
+ context.fillText(text, textX, rect.top + rect.height / 2);
25442
+ }
25443
+ if (control.kind === "dropdown") {
25444
+ context.fillStyle = textColor;
25445
+ context.textAlign = "center";
25446
+ context.fillText("\u25BC", rect.left + rect.width - 10 * zoomFactor, rect.top + rect.height / 2);
25447
+ }
25448
+ context.restore();
25449
+ };
25450
+ const drawBakedCanvasDrawings = (pane, context, dirtyRects) => {
25451
+ if (bakedCanvasDrawingEntries.length === 0 || dirtyRects.length === 0) {
25452
+ return;
25453
+ }
25454
+ for (const entry of bakedCanvasDrawingEntries) {
25455
+ if (resolveDrawingPane(entry.rect) !== pane) {
25456
+ continue;
25457
+ }
25458
+ const localRect = resolveBakedCanvasLocalRect(entry.rect, pane);
25459
+ if (localRect.left + localRect.width < 0 || localRect.top + localRect.height < 0 || localRect.left > paneBounds[pane].width || localRect.top > paneBounds[pane].height || !intersectsCanvasDirtyRects(localRect.left, localRect.top, localRect.width, localRect.height, dirtyRects)) {
25460
+ continue;
25461
+ }
25462
+ if (entry.kind === "shape") {
25463
+ drawBakedShape(context, entry.shape, localRect);
25464
+ } else if (entry.kind === "formControl") {
25465
+ drawBakedFormControl(context, entry.control, localRect);
25466
+ } else {
25467
+ const imageElement = getCanvasImage(entry.image);
25468
+ if (imageElement) {
25469
+ context.drawImage(imageElement, localRect.left, localRect.top, localRect.width, localRect.height);
25470
+ }
25471
+ }
25472
+ }
25473
+ };
24534
25474
  const cellPaneOrder = ["scroll", "top", "left", "corner"];
24535
25475
  if (shouldRepaintBody) {
25476
+ canvasProfileBodyStart = canvasProfileTarget ? performance.now() : 0;
24536
25477
  for (const pane of Object.keys(bodyContexts)) {
24537
25478
  const context = bodyContexts[pane];
24538
25479
  const bounds = paneBounds[pane];
@@ -24563,6 +25504,7 @@ function XlsxGrid({
24563
25504
  context.clearRect(0, 0, bounds.width, bounds.height);
24564
25505
  }
24565
25506
  bodyDirtyRectsByPane[pane] = dirtyRects;
25507
+ canvasProfileDirtyRects += dirtyRects.length;
24566
25508
  if (dirtyRects.length === 0) {
24567
25509
  continue;
24568
25510
  }
@@ -24579,6 +25521,22 @@ function XlsxGrid({
24579
25521
  if (!paneContext || paneDirtyRects.length === 0 || paneBoundsForCell.width <= 0 || paneBoundsForCell.height <= 0 || paneAxisItems.rows.length === 0 || paneAxisItems.cols.length === 0) {
24580
25522
  continue;
24581
25523
  }
25524
+ let hasPendingGridlinePath = false;
25525
+ const enqueueGridlinePath = () => {
25526
+ if (!hasPendingGridlinePath) {
25527
+ paneContext.beginPath();
25528
+ hasPendingGridlinePath = true;
25529
+ }
25530
+ };
25531
+ const flushPendingGridlines = () => {
25532
+ if (!hasPendingGridlinePath) {
25533
+ return;
25534
+ }
25535
+ paneContext.strokeStyle = palette.border;
25536
+ paneContext.lineWidth = 1;
25537
+ paneContext.stroke();
25538
+ hasPendingGridlinePath = false;
25539
+ };
24582
25540
  const drawnMergedAnchorKeys = /* @__PURE__ */ new Set();
24583
25541
  for (const rowItem of paneAxisItems.rows) {
24584
25542
  for (const colItem of paneAxisItems.cols) {
@@ -24586,6 +25544,33 @@ function XlsxGrid({
24586
25544
  const anchorCell = resolveMergeAnchorCell(cell);
24587
25545
  const anchorKey = `${anchorCell.row}:${anchorCell.col}`;
24588
25546
  let drawCell = anchorCell;
25547
+ const drawRowIndex = rowIndexByActual.get(drawCell.row);
25548
+ const drawColIndex = colIndexByActual.get(drawCell.col);
25549
+ if (drawRowIndex === void 0 || drawColIndex === void 0) {
25550
+ continue;
25551
+ }
25552
+ const baseCellLeft = displayRowHeaderWidth + (colPrefixSums[drawColIndex] ?? 0);
25553
+ const baseCellTop = displayHeaderHeight + (rowPrefixSums[drawRowIndex] ?? 0);
25554
+ const useFrozenHorizontalPosition = pane === "left" || pane === "corner";
25555
+ const useFrozenVerticalPosition = pane === "top" || pane === "corner";
25556
+ const roughLocalRect = {
25557
+ height: displayEffectiveRowHeights[drawRowIndex] ?? rowItem.size,
25558
+ left: (useFrozenHorizontalPosition ? stickyLeftByCol.get(drawCell.col) ?? baseCellLeft - drawingViewport.left : baseCellLeft - drawingViewport.left) - paneBoundsForCell.left,
25559
+ top: (useFrozenVerticalPosition ? stickyTopByRow.get(drawCell.row) ?? baseCellTop - drawingViewport.top : baseCellTop - drawingViewport.top) - paneBoundsForCell.top,
25560
+ width: displayEffectiveColWidths[drawColIndex] ?? colItem.size
25561
+ };
25562
+ const isMergedSecondaryProbe = anchorCell.row !== cell.row || anchorCell.col !== cell.col;
25563
+ if (!isMergedSecondaryProbe && !intersectsCanvasDirtyRects(
25564
+ roughLocalRect.left - CANVAS_DIRTY_CELL_CULL_MARGIN_PX,
25565
+ roughLocalRect.top - CANVAS_DIRTY_CELL_CULL_MARGIN_PX,
25566
+ roughLocalRect.width + CANVAS_DIRTY_CELL_CULL_MARGIN_PX * 2,
25567
+ roughLocalRect.height + CANVAS_DIRTY_CELL_CULL_MARGIN_PX * 2,
25568
+ paneDirtyRects
25569
+ )) {
25570
+ canvasProfileCulledCells += 1;
25571
+ continue;
25572
+ }
25573
+ canvasProfileLookedUpCells += 1;
24589
25574
  let cellData = getCellData(drawCell.row, drawCell.col);
24590
25575
  if ((cellData.colSpan || cellData.rowSpan) && drawnMergedAnchorKeys.has(anchorKey)) {
24591
25576
  continue;
@@ -24596,16 +25581,9 @@ function XlsxGrid({
24596
25581
  if (cellData.colSpan || cellData.rowSpan) {
24597
25582
  drawnMergedAnchorKeys.add(anchorKey);
24598
25583
  }
24599
- const drawRowIndex = rowIndexByActual.get(drawCell.row);
24600
- const drawColIndex = colIndexByActual.get(drawCell.col);
24601
- if (drawRowIndex === void 0 || drawColIndex === void 0) {
24602
- continue;
24603
- }
24604
25584
  const displayRect = cellData.colSpan || cellData.rowSpan ? resolveCellDisplayRect(drawCell) : null;
24605
- const baseLeft = displayRect?.left ?? displayRowHeaderWidth + (colPrefixSums[drawColIndex] ?? 0);
24606
- const baseTop = displayRect?.top ?? displayHeaderHeight + (rowPrefixSums[drawRowIndex] ?? 0);
24607
- const useFrozenHorizontalPosition = pane === "left" || pane === "corner";
24608
- const useFrozenVerticalPosition = pane === "top" || pane === "corner";
25585
+ const baseLeft = displayRect?.left ?? baseCellLeft;
25586
+ const baseTop = displayRect?.top ?? baseCellTop;
24609
25587
  const localRect = {
24610
25588
  height: displayRect?.height ?? (displayEffectiveRowHeights[drawRowIndex] ?? rowItem.size),
24611
25589
  left: (useFrozenHorizontalPosition ? stickyLeftByCol.get(drawCell.col) ?? baseLeft - drawingViewport.left : baseLeft - drawingViewport.left) - paneBoundsForCell.left,
@@ -24617,13 +25595,18 @@ function XlsxGrid({
24617
25595
  continue;
24618
25596
  }
24619
25597
  if (!intersectsCanvasDirtyRects(localRect.left, localRect.top, drawableWidth, localRect.height, paneDirtyRects)) {
25598
+ canvasProfileCulledCells += 1;
24620
25599
  continue;
24621
25600
  }
25601
+ canvasProfilePaintedCells += 1;
24622
25602
  const cellStyle = cellData.style;
24623
25603
  const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellStyle);
24624
25604
  const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellStyle.backgroundColor === "string" ? cellStyle.backgroundColor : sheetSurface);
24625
25605
  const gradientFill = !cellData.conditionalColorScale && typeof cellStyle.backgroundImage === "string" ? resolveCanvasGradientFill(paneContext, localRect, cellStyle.backgroundImage) : null;
24626
25606
  const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellStyle.backgroundColor === "string" && cellStyle.backgroundColor !== sheetSurface;
25607
+ if (hasExplicitCellFill || cellData.chartHighlight) {
25608
+ flushPendingGridlines();
25609
+ }
24627
25610
  paneContext.fillStyle = gradientFill ?? fillColor;
24628
25611
  paneContext.fillRect(localRect.left, localRect.top, localRect.width, localRect.height);
24629
25612
  if (cellData.chartHighlight) {
@@ -24669,10 +25652,8 @@ function XlsxGrid({
24669
25652
  const bottomNeighborTopBorder = bottomNeighborData?.isMergedSecondary ? null : bottomNeighborData?.canvas?.topBorder ?? parseCanvasBorderDeclaration(bottomNeighborData?.style.borderTop);
24670
25653
  const resolvedRightBorder = resolveCanvasBoundaryBorder(rightBorder, rightNeighborLeftBorder);
24671
25654
  const resolvedBottomBorder = resolveCanvasBoundaryBorder(bottomBorder, bottomNeighborTopBorder);
24672
- if (showGridLines && !hasExplicitCellFill) {
24673
- paneContext.strokeStyle = palette.border;
24674
- paneContext.lineWidth = 1;
24675
- paneContext.beginPath();
25655
+ if (showGridLines && !hasExplicitCellFill && (!resolvedRightBorder || !resolvedBottomBorder)) {
25656
+ enqueueGridlinePath();
24676
25657
  if (!resolvedRightBorder) {
24677
25658
  paneContext.moveTo(localRect.left + localRect.width - 0.5, localRect.top);
24678
25659
  paneContext.lineTo(localRect.left + localRect.width - 0.5, localRect.top + localRect.height);
@@ -24681,7 +25662,9 @@ function XlsxGrid({
24681
25662
  paneContext.moveTo(localRect.left, localRect.top + localRect.height - 0.5);
24682
25663
  paneContext.lineTo(localRect.left + localRect.width, localRect.top + localRect.height - 0.5);
24683
25664
  }
24684
- paneContext.stroke();
25665
+ }
25666
+ if (topBorder || resolvedRightBorder || resolvedBottomBorder || leftBorder || cellData.chartHighlight) {
25667
+ flushPendingGridlines();
24685
25668
  }
24686
25669
  if (topBorder && drawRowIndex === 0) {
24687
25670
  strokeCanvasBorderSide(paneContext, "top", localRect, topBorder);
@@ -24714,17 +25697,26 @@ function XlsxGrid({
24714
25697
  strokeCanvasBorderSide(paneContext, "left", localRect, highlightBorder);
24715
25698
  }
24716
25699
  }
25700
+ const rawText = cellData.value ?? "";
25701
+ const shouldDrawCanvasContent = cellData.checkboxState != null || cellData.sparkline || rawText.length > 0 || cellData.conditionalIcon || cellData.isTableHeader;
25702
+ if (!shouldDrawCanvasContent) {
25703
+ continue;
25704
+ }
24717
25705
  const padding = canvasCellStyle.padding;
24718
25706
  const contentLeft = localRect.left + padding.left;
24719
25707
  const contentTop = localRect.top + padding.top;
24720
25708
  const contentWidth = Math.max(0, localRect.width - padding.left - padding.right);
24721
25709
  const contentHeight = Math.max(0, localRect.height - padding.top - padding.bottom);
25710
+ if (contentWidth <= 0 || contentHeight <= 0) {
25711
+ continue;
25712
+ }
24722
25713
  const activeFontSizePx = cellData.shrinkToFitFontSizePx ?? resolveCanvasFontSizePx(cellStyle, 12 * zoomFactor);
24723
25714
  const textClipOverscan = Math.max(
24724
25715
  1,
24725
25716
  zoomFactor * 1.5,
24726
25717
  activeFontSizePx * (cellData.textRotationDeg ? 0.75 : 0.18)
24727
25718
  );
25719
+ flushPendingGridlines();
24728
25720
  paneContext.save();
24729
25721
  paneContext.beginPath();
24730
25722
  paneContext.rect(
@@ -24856,7 +25848,6 @@ function XlsxGrid({
24856
25848
  const trailingInset = (cellData.conditionalIcon ? 18 * zoomFactor : 0) + (cellData.isTableHeader ? 16 * zoomFactor : 0);
24857
25849
  const spillMaxWidth = cellData.spillWidth && cellData.spillWidth > 0 ? Math.max(0, cellData.spillWidth - trailingInset) : null;
24858
25850
  const maxTextWidth = spillMaxWidth ?? Math.max(0, contentWidth - trailingInset);
24859
- const rawText = cellData.value ?? "";
24860
25851
  const textColor = canvasCellStyle.textColor;
24861
25852
  const shouldEllipsizeText = canvasCellStyle.textOverflowEllipsis;
24862
25853
  const shouldWrapText = canvasCellStyle.usesWrappedText || rawText.includes("\n");
@@ -24888,7 +25879,7 @@ function XlsxGrid({
24888
25879
  const textY = textBlockTop + lineIndex * lineHeight + lineHeight / 2;
24889
25880
  paneContext.fillText(line, textX, textY);
24890
25881
  if (canvasCellStyle.textDecoration?.includes("underline") && line.length > 0) {
24891
- const measured = Math.min(maxTextWidth, paneContext.measureText(line).width);
25882
+ const measured = Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, line));
24892
25883
  const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
24893
25884
  paneContext.beginPath();
24894
25885
  paneContext.moveTo(underlineStartX, textY + Math.max(2, lineHeight * 0.24));
@@ -24922,7 +25913,7 @@ function XlsxGrid({
24922
25913
  const textY = contentTop + contentHeight / 2;
24923
25914
  paneContext.fillText(text, textX, textY);
24924
25915
  if (canvasCellStyle.textDecoration?.includes("underline") && text.length > 0) {
24925
- const measured = shouldEllipsizeText ? Math.min(maxTextWidth, paneContext.measureText(text).width) : paneContext.measureText(text).width;
25916
+ const measured = shouldEllipsizeText ? Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, text)) : measureCanvasTextWidth(paneContext, text);
24926
25917
  const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
24927
25918
  paneContext.beginPath();
24928
25919
  paneContext.moveTo(underlineStartX, textY + 6 * zoomFactor);
@@ -24947,6 +25938,7 @@ function XlsxGrid({
24947
25938
  paneContext.restore();
24948
25939
  }
24949
25940
  }
25941
+ flushPendingGridlines();
24950
25942
  }
24951
25943
  for (const pane of cellPaneOrder) {
24952
25944
  const paneContext = bodyContexts[pane];
@@ -24964,7 +25956,7 @@ function XlsxGrid({
24964
25956
  paneContext.textBaseline = "middle";
24965
25957
  paneContext.fillText(spillText.text, spillText.textX, spillText.textY);
24966
25958
  if (spillText.textDecoration?.includes("underline") && spillText.text.length > 0) {
24967
- const measured = spillText.ellipsize ? Math.min(spillText.maxWidth, paneContext.measureText(spillText.text).width) : paneContext.measureText(spillText.text).width;
25959
+ const measured = spillText.ellipsize ? Math.min(spillText.maxWidth, measureCanvasTextWidth(paneContext, spillText.text)) : measureCanvasTextWidth(paneContext, spillText.text);
24968
25960
  const underlineStartX = spillText.align === "right" ? spillText.textX - measured : spillText.align === "center" ? spillText.textX - measured / 2 : spillText.textX;
24969
25961
  paneContext.beginPath();
24970
25962
  paneContext.moveTo(underlineStartX, spillText.underlineY);
@@ -24976,6 +25968,14 @@ function XlsxGrid({
24976
25968
  paneContext.restore();
24977
25969
  }
24978
25970
  }
25971
+ for (const pane of cellPaneOrder) {
25972
+ const paneContext = bodyContexts[pane];
25973
+ if (!paneContext) {
25974
+ continue;
25975
+ }
25976
+ drawBakedCanvasDrawings(pane, paneContext, bodyDirtyRectsByPane[pane]);
25977
+ }
25978
+ canvasProfileBodyMs = canvasProfileTarget ? performance.now() - canvasProfileBodyStart : 0;
24979
25979
  }
24980
25980
  if (shouldRepaintHeaders && cornerContext) {
24981
25981
  const topFrozenHeaderContext = topHeaderContexts.frozen;
@@ -24990,7 +25990,7 @@ function XlsxGrid({
24990
25990
  topScrollHeaderContext,
24991
25991
  bufferCanvas,
24992
25992
  dpr,
24993
- topScrollHeaderCanvasWidth2,
25993
+ topScrollHeaderCanvasWidth,
24994
25994
  headerHeight,
24995
25995
  drawingViewport.left - previousPaintedViewport.left,
24996
25996
  0
@@ -25009,7 +26009,7 @@ function XlsxGrid({
25009
26009
  bufferCanvas,
25010
26010
  dpr,
25011
26011
  rowHeaderWidth,
25012
- leftScrollHeaderCanvasHeight2,
26012
+ leftScrollHeaderCanvasHeight,
25013
26013
  0,
25014
26014
  drawingViewport.top - previousPaintedViewport.top
25015
26015
  );
@@ -25018,11 +26018,11 @@ function XlsxGrid({
25018
26018
  }
25019
26019
  }
25020
26020
  }
25021
- if (topFrozenHeaderContext && topFrozenHeaderCanvasWidth2 > 0) {
26021
+ if (topFrozenHeaderContext && topFrozenHeaderCanvasWidth > 0) {
25022
26022
  topFrozenHeaderContext.fillStyle = palette.headerSurface;
25023
- topFrozenHeaderContext.fillRect(0, 0, topFrozenHeaderCanvasWidth2, headerHeight);
26023
+ topFrozenHeaderContext.fillRect(0, 0, topFrozenHeaderCanvasWidth, headerHeight);
25024
26024
  }
25025
- if (topScrollHeaderContext && topScrollHeaderCanvasWidth2 > 0) {
26025
+ if (topScrollHeaderContext && topScrollHeaderCanvasWidth > 0) {
25026
26026
  topScrollHeaderContext.fillStyle = palette.headerSurface;
25027
26027
  for (const dirtyRect of topScrollHeaderDirtyRects) {
25028
26028
  topScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
@@ -25041,7 +26041,7 @@ function XlsxGrid({
25041
26041
  if (!paneContext) {
25042
26042
  continue;
25043
26043
  }
25044
- if (column.localLeft + column.width < 0 || column.localLeft > (column.isFrozen ? topFrozenHeaderCanvasWidth2 : topScrollHeaderCanvasWidth2)) {
26044
+ if (column.localLeft + column.width < 0 || column.localLeft > (column.isFrozen ? topFrozenHeaderCanvasWidth : topScrollHeaderCanvasWidth)) {
25045
26045
  continue;
25046
26046
  }
25047
26047
  if (!column.isFrozen && !intersectsCanvasDirtyRects(column.localLeft, 0, column.width, column.height, topScrollHeaderDirtyRects)) {
@@ -25067,11 +26067,11 @@ function XlsxGrid({
25067
26067
  column.height / 2
25068
26068
  );
25069
26069
  }
25070
- if (leftFrozenHeaderContext && leftFrozenHeaderCanvasHeight2 > 0) {
26070
+ if (leftFrozenHeaderContext && leftFrozenHeaderCanvasHeight > 0) {
25071
26071
  leftFrozenHeaderContext.fillStyle = palette.rowHeaderSurface;
25072
- leftFrozenHeaderContext.fillRect(0, 0, rowHeaderWidth, leftFrozenHeaderCanvasHeight2);
26072
+ leftFrozenHeaderContext.fillRect(0, 0, rowHeaderWidth, leftFrozenHeaderCanvasHeight);
25073
26073
  }
25074
- if (leftScrollHeaderContext && leftScrollHeaderCanvasHeight2 > 0) {
26074
+ if (leftScrollHeaderContext && leftScrollHeaderCanvasHeight > 0) {
25075
26075
  leftScrollHeaderContext.fillStyle = palette.rowHeaderSurface;
25076
26076
  for (const dirtyRect of leftScrollHeaderDirtyRects) {
25077
26077
  leftScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
@@ -25090,7 +26090,7 @@ function XlsxGrid({
25090
26090
  if (!paneContext) {
25091
26091
  continue;
25092
26092
  }
25093
- if (row.localTop + row.height < 0 || row.localTop > (row.isFrozen ? leftFrozenHeaderCanvasHeight2 : leftScrollHeaderCanvasHeight2)) {
26093
+ if (row.localTop + row.height < 0 || row.localTop > (row.isFrozen ? leftFrozenHeaderCanvasHeight : leftScrollHeaderCanvasHeight)) {
25094
26094
  continue;
25095
26095
  }
25096
26096
  if (!row.isFrozen && !intersectsCanvasDirtyRects(0, row.localTop, rowHeaderWidth, row.height, leftScrollHeaderDirtyRects)) {
@@ -25130,9 +26130,29 @@ function XlsxGrid({
25130
26130
  paintedHeaderCanvasSignatureRef.current = nextHeaderCanvasSignature;
25131
26131
  }
25132
26132
  applyCanvasViewportCompensation();
26133
+ if (canvasProfileTarget) {
26134
+ canvasProfileTarget.push({
26135
+ bodyMs: canvasProfileBodyMs,
26136
+ culledCells: canvasProfileCulledCells,
26137
+ dirtyRects: canvasProfileDirtyRects,
26138
+ lookedUpCells: canvasProfileLookedUpCells,
26139
+ paintedCells: canvasProfilePaintedCells,
26140
+ repaintBody: shouldRepaintBody,
26141
+ repaintHeaders: shouldRepaintHeaders,
26142
+ renderToLayoutMs: canvasProfileStart - xlsxGridRenderStart,
26143
+ totalMs: performance.now() - canvasProfileStart
26144
+ });
26145
+ if (canvasProfileTarget.length > 500) {
26146
+ canvasProfileTarget.splice(0, canvasProfileTarget.length - 500);
26147
+ }
26148
+ }
25133
26149
  }, [
25134
26150
  activeSheet,
25135
26151
  applyCanvasViewportCompensation,
26152
+ bakedCanvasDrawingSignature,
26153
+ bakedFormControlRects,
26154
+ bakedImageRects,
26155
+ bakedShapeRects,
25136
26156
  canvasColumnHeaderCells,
25137
26157
  canvasPaneAxisItems,
25138
26158
  canvasRowHeaderCells,
@@ -25153,15 +26173,16 @@ function XlsxGrid({
25153
26173
  frozenPaneBottom,
25154
26174
  frozenPaneRight,
25155
26175
  getCellData,
26176
+ getCanvasImage,
25156
26177
  getBodyBlitBufferCanvas,
25157
26178
  getHeaderBlitBufferCanvas,
25158
26179
  palette,
25159
26180
  resolveCellDisplayRect,
25160
26181
  resolveMergeAnchorCell,
25161
- resizeGuide,
25162
26182
  rowIndexByActual,
25163
26183
  rowPrefixSums,
25164
26184
  selectionHeaderSurface,
26185
+ shouldBakeCanvasStaticDrawings,
25165
26186
  stickyLeftByCol,
25166
26187
  stickyTopByRow,
25167
26188
  visibleCols,
@@ -25379,7 +26400,7 @@ function XlsxGrid({
25379
26400
  start: virtualRow?.start ?? (rowPrefixSums[index] ?? 0)
25380
26401
  };
25381
26402
  });
25382
- const totalHeight = shouldVirtualizeRows ? rowVirtualizer.getTotalSize() : rowPrefixSums[rowPrefixSums.length - 1] ?? 0;
26403
+ const totalHeight = shouldUseDomVirtualizer && shouldVirtualizeRows ? rowVirtualizer.getTotalSize() : rowPrefixSums[rowPrefixSums.length - 1] ?? 0;
25383
26404
  const totalWidth = totalContentWidth + displayRowHeaderWidth;
25384
26405
  const sheetContentHeight = displayHeaderHeight + totalHeight;
25385
26406
  const isLiveZooming = liveGestureZoom !== null && zoomScale === liveGestureZoom.baseZoomScale;
@@ -25453,18 +26474,6 @@ function XlsxGrid({
25453
26474
  width: 0,
25454
26475
  zIndex: canvasHeaderOverlayZIndex
25455
26476
  };
25456
- const scrollBodyCanvasWidth = Math.max(0, drawingViewport.width - frozenPaneRight);
25457
- const scrollBodyCanvasHeight = Math.max(0, drawingViewport.height - frozenPaneBottom);
25458
- const topBodyCanvasWidth = scrollBodyCanvasWidth;
25459
- const topBodyCanvasHeight = Math.max(0, frozenPaneBottom - displayHeaderHeight);
25460
- const leftBodyCanvasWidth = Math.max(0, frozenPaneRight - displayRowHeaderWidth);
25461
- const leftBodyCanvasHeight = scrollBodyCanvasHeight;
25462
- const cornerBodyCanvasWidth = leftBodyCanvasWidth;
25463
- const cornerBodyCanvasHeight = topBodyCanvasHeight;
25464
- const topFrozenHeaderCanvasWidth = leftBodyCanvasWidth;
25465
- const topScrollHeaderCanvasWidth = scrollBodyCanvasWidth;
25466
- const leftFrozenHeaderCanvasHeight = topBodyCanvasHeight;
25467
- const leftScrollHeaderCanvasHeight = scrollBodyCanvasHeight;
25468
26477
  const canvasBodyBaseStyle = {
25469
26478
  cursor: "cell",
25470
26479
  pointerEvents: "auto",
@@ -25476,13 +26485,13 @@ function XlsxGrid({
25476
26485
  const canvasScrollBodyStyle = {
25477
26486
  ...canvasBodyBaseStyle,
25478
26487
  display: scrollBodyCanvasWidth > 0 && scrollBodyCanvasHeight > 0 ? "block" : "none",
25479
- left: frozenPaneRight,
25480
- top: frozenPaneBottom
26488
+ left: scrollBodyCanvasLeft,
26489
+ top: scrollBodyCanvasTop
25481
26490
  };
25482
26491
  const canvasTopBodyStyle = {
25483
26492
  ...canvasBodyBaseStyle,
25484
26493
  display: topBodyCanvasWidth > 0 && topBodyCanvasHeight > 0 ? "block" : "none",
25485
- left: frozenPaneRight,
26494
+ left: scrollBodyCanvasLeft,
25486
26495
  top: displayHeaderHeight,
25487
26496
  zIndex: 30
25488
26497
  };
@@ -25490,7 +26499,7 @@ function XlsxGrid({
25490
26499
  ...canvasBodyBaseStyle,
25491
26500
  display: leftBodyCanvasWidth > 0 && leftBodyCanvasHeight > 0 ? "block" : "none",
25492
26501
  left: displayRowHeaderWidth,
25493
- top: frozenPaneBottom,
26502
+ top: scrollBodyCanvasTop,
25494
26503
  zIndex: 30
25495
26504
  };
25496
26505
  const canvasCornerBodyStyle = {
@@ -25516,7 +26525,7 @@ function XlsxGrid({
25516
26525
  const canvasTopScrollHeaderStyle = {
25517
26526
  ...canvasHeaderBaseStyle,
25518
26527
  display: topScrollHeaderCanvasWidth > 0 && drawingViewport.height > 0 ? "block" : "none",
25519
- left: frozenPaneRight,
26528
+ left: scrollBodyCanvasLeft,
25520
26529
  top: 0,
25521
26530
  zIndex: canvasHeaderOverlayZIndex
25522
26531
  };
@@ -25531,7 +26540,7 @@ function XlsxGrid({
25531
26540
  ...canvasHeaderBaseStyle,
25532
26541
  display: leftScrollHeaderCanvasHeight > 0 && drawingViewport.width > 0 ? "block" : "none",
25533
26542
  left: 0,
25534
- top: frozenPaneBottom,
26543
+ top: scrollBodyCanvasTop,
25535
26544
  zIndex: canvasHeaderOverlayZIndex
25536
26545
  };
25537
26546
  const canvasCornerHeaderStyle = {
@@ -26087,26 +27096,26 @@ function XlsxGrid({
26087
27096
  };
26088
27097
  const canvasScrollOverlayPaneStyle = {
26089
27098
  ...canvasOverlayPaneBaseStyle,
26090
- display: scrollBodyCanvasWidth > 0 && scrollBodyCanvasHeight > 0 ? "block" : "none",
26091
- height: scrollBodyCanvasHeight,
27099
+ display: scrollBodyViewportWidth > 0 && scrollBodyViewportHeight > 0 ? "block" : "none",
27100
+ height: scrollBodyViewportHeight,
26092
27101
  left: frozenPaneRight,
26093
27102
  top: frozenPaneBottom,
26094
- width: scrollBodyCanvasWidth,
27103
+ width: scrollBodyViewportWidth,
26095
27104
  zIndex: 20
26096
27105
  };
26097
27106
  const canvasTopOverlayPaneStyle = {
26098
27107
  ...canvasOverlayPaneBaseStyle,
26099
- display: topBodyCanvasWidth > 0 && topBodyCanvasHeight > 0 ? "block" : "none",
27108
+ display: scrollBodyViewportWidth > 0 && topBodyCanvasHeight > 0 ? "block" : "none",
26100
27109
  height: topBodyCanvasHeight,
26101
27110
  left: frozenPaneRight,
26102
27111
  top: displayHeaderHeight,
26103
- width: topBodyCanvasWidth,
27112
+ width: scrollBodyViewportWidth,
26104
27113
  zIndex: 35
26105
27114
  };
26106
27115
  const canvasLeftOverlayPaneStyle = {
26107
27116
  ...canvasOverlayPaneBaseStyle,
26108
- display: leftBodyCanvasWidth > 0 && leftBodyCanvasHeight > 0 ? "block" : "none",
26109
- height: leftBodyCanvasHeight,
27117
+ display: leftBodyCanvasWidth > 0 && scrollBodyViewportHeight > 0 ? "block" : "none",
27118
+ height: scrollBodyViewportHeight,
26110
27119
  left: displayRowHeaderWidth,
26111
27120
  top: frozenPaneBottom,
26112
27121
  width: leftBodyCanvasWidth,
@@ -26121,8 +27130,9 @@ function XlsxGrid({
26121
27130
  width: cornerBodyCanvasWidth,
26122
27131
  zIndex: 36
26123
27132
  };
27133
+ const drawingViewportCacheSignature = `${Math.floor(drawingViewport.left / CANVAS_VIEWPORT_OVERSCAN_PX)}:${Math.floor(drawingViewport.top / CANVAS_VIEWPORT_OVERSCAN_PX)}:${drawingViewport.width}:${drawingViewport.height}`;
26124
27134
  const previousPaneDrawingNodes = paneDrawingNodesCacheRef.current;
26125
- const canReusePaneDrawingNodes = previousPaneDrawingNodes !== null && previousPaneDrawingNodes.showImages === showImages && previousPaneDrawingNodes.chartRects === chartRects && previousPaneDrawingNodes.formControlRects === formControlRects && previousPaneDrawingNodes.shapeRects === shapeRects && previousPaneDrawingNodes.imageRects === imageRects && previousPaneDrawingNodes.selectedChartId === selectedChartId && previousPaneDrawingNodes.selectedImageId === selectedImageId && previousPaneDrawingNodes.readOnly === readOnly && previousPaneDrawingNodes.selectionStroke === selectionStroke && previousPaneDrawingNodes.renderChartLoading === renderChartLoading && previousPaneDrawingNodes.renderImage === renderImage && previousPaneDrawingNodes.renderImageSelection === renderImageSelection && previousPaneDrawingNodes.isChartsLoading === isChartsLoading && previousPaneDrawingNodes.palette === palette && previousPaneDrawingNodes.drawingViewport.left === drawingViewport.left && previousPaneDrawingNodes.drawingViewport.top === drawingViewport.top && previousPaneDrawingNodes.drawingViewport.width === drawingViewport.width && previousPaneDrawingNodes.drawingViewport.height === drawingViewport.height;
27135
+ const canReusePaneDrawingNodes = previousPaneDrawingNodes !== null && previousPaneDrawingNodes.showImages === showImages && previousPaneDrawingNodes.chartRects === chartRects && previousPaneDrawingNodes.formControlRects === domFormControlRects && previousPaneDrawingNodes.shapeRects === domShapeRects && previousPaneDrawingNodes.imageRects === domImageRects && previousPaneDrawingNodes.selectedChartId === selectedChartId && previousPaneDrawingNodes.selectedImageId === selectedImageId && previousPaneDrawingNodes.readOnly === readOnly && previousPaneDrawingNodes.selectionStroke === selectionStroke && previousPaneDrawingNodes.renderChartLoading === renderChartLoading && previousPaneDrawingNodes.renderImage === renderImage && previousPaneDrawingNodes.renderImageSelection === renderImageSelection && previousPaneDrawingNodes.isChartsLoading === isChartsLoading && previousPaneDrawingNodes.palette === palette && previousPaneDrawingNodes.drawingViewportSignature === drawingViewportCacheSignature;
26126
27136
  const paneDrawingNodes = canReusePaneDrawingNodes ? previousPaneDrawingNodes.value : !showImages ? {
26127
27137
  corner: null,
26128
27138
  left: null,
@@ -26131,35 +27141,35 @@ function XlsxGrid({
26131
27141
  } : {
26132
27142
  corner: /* @__PURE__ */ jsxs3(Fragment4, { children: [
26133
27143
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "corner")),
26134
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "corner")),
26135
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "corner")),
26136
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "corner"))
27144
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "corner")),
27145
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "corner")),
27146
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "corner"))
26137
27147
  ] }),
26138
27148
  left: /* @__PURE__ */ jsxs3(Fragment4, { children: [
26139
27149
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "left")),
26140
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "left")),
26141
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "left")),
26142
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "left"))
27150
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "left")),
27151
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "left")),
27152
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "left"))
26143
27153
  ] }),
26144
27154
  scroll: /* @__PURE__ */ jsxs3(Fragment4, { children: [
26145
27155
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "scroll")),
26146
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "scroll")),
26147
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "scroll")),
26148
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "scroll"))
27156
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "scroll")),
27157
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "scroll")),
27158
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "scroll"))
26149
27159
  ] }),
26150
27160
  top: /* @__PURE__ */ jsxs3(Fragment4, { children: [
26151
27161
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "top")),
26152
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "top")),
26153
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "top")),
26154
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "top"))
27162
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "top")),
27163
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "top")),
27164
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "top"))
26155
27165
  ] })
26156
27166
  };
26157
27167
  if (!canReusePaneDrawingNodes) {
26158
27168
  paneDrawingNodesCacheRef.current = {
26159
27169
  chartRects,
26160
- drawingViewport,
26161
- formControlRects,
26162
- imageRects,
27170
+ drawingViewportSignature: drawingViewportCacheSignature,
27171
+ formControlRects: domFormControlRects,
27172
+ imageRects: domImageRects,
26163
27173
  isChartsLoading,
26164
27174
  palette,
26165
27175
  readOnly,
@@ -26169,7 +27179,7 @@ function XlsxGrid({
26169
27179
  selectedChartId,
26170
27180
  selectedImageId,
26171
27181
  selectionStroke,
26172
- shapeRects,
27182
+ shapeRects: domShapeRects,
26173
27183
  showImages,
26174
27184
  value: paneDrawingNodes
26175
27185
  };
@@ -26677,10 +27687,11 @@ function XlsxGrid({
26677
27687
  style: canvasCornerBodyStyle
26678
27688
  }
26679
27689
  ),
26680
- showImages ? /* @__PURE__ */ jsxs3(Fragment4, { children: [
27690
+ hasCanvasDomDrawingOverlays ? /* @__PURE__ */ jsxs3(Fragment4, { children: [
26681
27691
  /* @__PURE__ */ jsx3("div", { style: canvasScrollOverlayPaneStyle, children: /* @__PURE__ */ jsx3(
26682
27692
  "div",
26683
27693
  {
27694
+ ref: canvasScrollOverlayContentRef,
26684
27695
  style: {
26685
27696
  height: sheetContentHeight,
26686
27697
  left: 0,
@@ -26688,6 +27699,7 @@ function XlsxGrid({
26688
27699
  position: "absolute",
26689
27700
  top: 0,
26690
27701
  transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
27702
+ transformOrigin: "0 0",
26691
27703
  width: totalWidth
26692
27704
  },
26693
27705
  children: paneDrawingNodes.scroll
@@ -26696,6 +27708,7 @@ function XlsxGrid({
26696
27708
  /* @__PURE__ */ jsx3("div", { style: canvasTopOverlayPaneStyle, children: /* @__PURE__ */ jsx3(
26697
27709
  "div",
26698
27710
  {
27711
+ ref: canvasTopOverlayContentRef,
26699
27712
  style: {
26700
27713
  height: sheetContentHeight,
26701
27714
  left: 0,
@@ -26703,6 +27716,7 @@ function XlsxGrid({
26703
27716
  position: "absolute",
26704
27717
  top: 0,
26705
27718
  transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-displayHeaderHeight}px)`,
27719
+ transformOrigin: "0 0",
26706
27720
  width: totalWidth
26707
27721
  },
26708
27722
  children: paneDrawingNodes.top
@@ -26711,6 +27725,7 @@ function XlsxGrid({
26711
27725
  /* @__PURE__ */ jsx3("div", { style: canvasLeftOverlayPaneStyle, children: /* @__PURE__ */ jsx3(
26712
27726
  "div",
26713
27727
  {
27728
+ ref: canvasLeftOverlayContentRef,
26714
27729
  style: {
26715
27730
  height: sheetContentHeight,
26716
27731
  left: 0,
@@ -26718,6 +27733,7 @@ function XlsxGrid({
26718
27733
  position: "absolute",
26719
27734
  top: 0,
26720
27735
  transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
27736
+ transformOrigin: "0 0",
26721
27737
  width: totalWidth
26722
27738
  },
26723
27739
  children: paneDrawingNodes.left
@@ -26726,6 +27742,7 @@ function XlsxGrid({
26726
27742
  /* @__PURE__ */ jsx3("div", { style: canvasCornerOverlayPaneStyle, children: /* @__PURE__ */ jsx3(
26727
27743
  "div",
26728
27744
  {
27745
+ ref: canvasCornerOverlayContentRef,
26729
27746
  style: {
26730
27747
  height: sheetContentHeight,
26731
27748
  left: 0,
@@ -26733,6 +27750,7 @@ function XlsxGrid({
26733
27750
  position: "absolute",
26734
27751
  top: 0,
26735
27752
  transform: `translate(${-displayRowHeaderWidth}px, ${-displayHeaderHeight}px)`,
27753
+ transformOrigin: "0 0",
26736
27754
  width: totalWidth
26737
27755
  },
26738
27756
  children: paneDrawingNodes.corner
@@ -27549,27 +28567,95 @@ function useXlsxViewerCharts() {
27549
28567
  );
27550
28568
  }
27551
28569
  function useXlsxViewerThumbnails(options = {}) {
27552
- const { workbook, sheets } = useXlsxViewer();
28570
+ const { getSheetFormControls, getSheetImages, getSheetShapes, workbook, sheets } = useXlsxViewer();
27553
28571
  const { isDark } = React4.useContext(ViewerAppearanceContext);
27554
28572
  const palette = useViewerPalette(isDark);
27555
28573
  const includeHeaders = options.includeHeaders ?? true;
27556
28574
  const resolution = options.resolution;
28575
+ const thumbnailImageCacheRef = React4.useRef(/* @__PURE__ */ new Map());
28576
+ const isMountedRef = React4.useRef(false);
28577
+ const [thumbnailImageLoadVersion, setThumbnailImageLoadVersion] = React4.useState(0);
28578
+ React4.useEffect(() => {
28579
+ isMountedRef.current = true;
28580
+ return () => {
28581
+ isMountedRef.current = false;
28582
+ };
28583
+ }, []);
28584
+ const getThumbnailImage = React4.useCallback((image) => {
28585
+ if (typeof Image === "undefined") {
28586
+ return null;
28587
+ }
28588
+ const cacheKey = image.id;
28589
+ const cached = thumbnailImageCacheRef.current.get(cacheKey);
28590
+ if (cached && cached.src === image.src) {
28591
+ return cached.loaded && !cached.failed ? cached.image : null;
28592
+ }
28593
+ const imageElement = new Image();
28594
+ const entry = {
28595
+ failed: false,
28596
+ image: imageElement,
28597
+ loaded: false,
28598
+ src: image.src
28599
+ };
28600
+ thumbnailImageCacheRef.current.set(cacheKey, entry);
28601
+ imageElement.onload = () => {
28602
+ entry.loaded = true;
28603
+ if (isMountedRef.current) {
28604
+ setThumbnailImageLoadVersion((version) => version + 1);
28605
+ }
28606
+ };
28607
+ imageElement.onerror = () => {
28608
+ entry.failed = true;
28609
+ if (isMountedRef.current) {
28610
+ setThumbnailImageLoadVersion((version) => version + 1);
28611
+ }
28612
+ };
28613
+ imageElement.src = image.src;
28614
+ return null;
28615
+ }, []);
27557
28616
  const thumbnails = React4.useMemo(() => {
27558
28617
  return sheets.map((sheet, sheetIndex) => {
27559
28618
  const worksheet = workbook?.getSheet(sheet.workbookSheetIndex) ?? null;
28619
+ const sheetImages = getSheetImages(sheetIndex).filter((image) => image.src).sort((left, right) => left.zIndex - right.zIndex);
28620
+ const sheetShapes = getSheetShapes(sheetIndex);
28621
+ const sheetFormControls = getSheetFormControls(sheetIndex).filter((control) => !control.hidden);
28622
+ const drawingBounds = [
28623
+ ...sheetImages.map((image) => image.anchor),
28624
+ ...sheetShapes.map((shape) => shape.anchor),
28625
+ ...sheetFormControls.map((control) => control.anchor)
28626
+ ].reduce((bounds, anchor) => {
28627
+ const drawingBound = resolveAnchoredBounds(anchor) ?? {
28628
+ maxCol: 0,
28629
+ maxRow: 0,
28630
+ minCol: 0,
28631
+ minRow: 0
28632
+ };
28633
+ return {
28634
+ maxCol: Math.max(bounds?.maxCol ?? drawingBound.maxCol, drawingBound.maxCol),
28635
+ maxRow: Math.max(bounds?.maxRow ?? drawingBound.maxRow, drawingBound.maxRow),
28636
+ minCol: Math.min(bounds?.minCol ?? drawingBound.minCol, drawingBound.minCol),
28637
+ minRow: Math.min(bounds?.minRow ?? drawingBound.minRow, drawingBound.minRow)
28638
+ };
28639
+ }, null);
28640
+ const sheetMinUsedRow = (sheet.maxUsedRow ?? -1) >= (sheet.minUsedRow ?? 0) ? sheet.minUsedRow ?? 0 : void 0;
28641
+ const sheetMinUsedCol = (sheet.maxUsedCol ?? -1) >= (sheet.minUsedCol ?? 0) ? sheet.minUsedCol ?? 0 : void 0;
28642
+ const effectiveMinUsedRow = Math.max(0, Math.min(sheetMinUsedRow ?? drawingBounds?.minRow ?? 0, drawingBounds?.minRow ?? sheetMinUsedRow ?? 0));
28643
+ const effectiveMinUsedCol = Math.max(0, Math.min(sheetMinUsedCol ?? drawingBounds?.minCol ?? 0, drawingBounds?.minCol ?? sheetMinUsedCol ?? 0));
28644
+ const effectiveMaxUsedRow = Math.max(sheet.maxUsedRow ?? -1, drawingBounds?.maxRow ?? -1);
28645
+ const effectiveMaxUsedCol = Math.max(sheet.maxUsedCol ?? -1, drawingBounds?.maxCol ?? -1);
27560
28646
  const showGridLines = sheet.showGridLines ?? true;
27561
28647
  const hiddenRowSet = new Set(sheet.hiddenRows ?? []);
27562
28648
  const hiddenColSet = new Set(sheet.hiddenCols ?? []);
27563
28649
  const visibleRows = buildVisibleAxisIndices(
27564
28650
  sheet.visibleRows ?? [],
27565
- Math.max(THUMBNAIL_FALLBACK_ROWS, (sheet.maxUsedRow ?? -1) + THUMBNAIL_FALLBACK_ROWS + 1),
27566
- sheet.maxUsedRow ?? -1,
28651
+ Math.max(THUMBNAIL_FALLBACK_ROWS, effectiveMaxUsedRow + THUMBNAIL_FALLBACK_ROWS + 1),
28652
+ effectiveMaxUsedRow,
27567
28653
  hiddenRowSet
27568
28654
  );
27569
28655
  const visibleCols = buildVisibleAxisIndices(
27570
28656
  sheet.visibleCols ?? [],
27571
- Math.max(THUMBNAIL_FALLBACK_COLS, (sheet.maxUsedCol ?? -1) + THUMBNAIL_FALLBACK_COLS + 1),
27572
- sheet.maxUsedCol ?? -1,
28657
+ Math.max(THUMBNAIL_FALLBACK_COLS, effectiveMaxUsedCol + THUMBNAIL_FALLBACK_COLS + 1),
28658
+ effectiveMaxUsedCol,
27573
28659
  hiddenColSet
27574
28660
  );
27575
28661
  const resolveColumnWidthPx = (actualCol) => {
@@ -27613,8 +28699,8 @@ function useXlsxViewerThumbnails(options = {}) {
27613
28699
  getSizePx: resolveRowHeightPx,
27614
28700
  maxCount: THUMBNAIL_MAX_ROWS,
27615
28701
  maxLogicalPixels: THUMBNAIL_MAX_SOURCE_HEIGHT_PX,
27616
- maxUsedIndex: sheet.maxUsedRow ?? -1,
27617
- minUsedIndex: Math.max(0, sheet.minUsedRow ?? 0),
28702
+ maxUsedIndex: effectiveMaxUsedRow,
28703
+ minUsedIndex: effectiveMinUsedRow,
27618
28704
  precomputed: visibleRows
27619
28705
  });
27620
28706
  const previewCols = resolveThumbnailAxisIndices({
@@ -27622,8 +28708,8 @@ function useXlsxViewerThumbnails(options = {}) {
27622
28708
  getSizePx: resolveColumnWidthPx,
27623
28709
  maxCount: THUMBNAIL_MAX_COLS,
27624
28710
  maxLogicalPixels: THUMBNAIL_MAX_SOURCE_WIDTH_PX,
27625
- maxUsedIndex: sheet.maxUsedCol ?? -1,
27626
- minUsedIndex: Math.max(0, sheet.minUsedCol ?? 0),
28711
+ maxUsedIndex: effectiveMaxUsedCol,
28712
+ minUsedIndex: effectiveMinUsedCol,
27627
28713
  precomputed: visibleCols
27628
28714
  });
27629
28715
  const rowAxis = buildThumbnailAxisItems(previewRows, resolveRowHeightPx);
@@ -27648,11 +28734,79 @@ function useXlsxViewerThumbnails(options = {}) {
27648
28734
  const sparklineByCell = new Map(
27649
28735
  (sheet.sparklines ?? []).map((sparkline) => [`${sparkline.target.row}:${sparkline.target.col}`, sparkline])
27650
28736
  );
28737
+ const thumbnailImageRects = sheetImages.map((image) => ({
28738
+ image,
28739
+ rect: resolveThumbnailAnchoredRect(
28740
+ image.anchor,
28741
+ visibleRows,
28742
+ visibleCols,
28743
+ resolveRowHeightPx,
28744
+ resolveColumnWidthPx,
28745
+ previewRows,
28746
+ previewCols,
28747
+ {
28748
+ headerHeight,
28749
+ rowHeaderWidth
28750
+ }
28751
+ )
28752
+ })).filter(({ rect }) => rect.left + rect.width >= rowHeaderWidth && rect.top + rect.height >= headerHeight && rect.left <= sourceWidth && rect.top <= sourceHeight);
28753
+ const thumbnailShapeRects = sheetShapes.map((shape) => ({
28754
+ rect: resolveThumbnailAnchoredRect(
28755
+ shape.anchor,
28756
+ visibleRows,
28757
+ visibleCols,
28758
+ resolveRowHeightPx,
28759
+ resolveColumnWidthPx,
28760
+ previewRows,
28761
+ previewCols,
28762
+ {
28763
+ headerHeight,
28764
+ rowHeaderWidth
28765
+ }
28766
+ ),
28767
+ shape
28768
+ })).filter(({ rect }) => rect.left + rect.width >= rowHeaderWidth && rect.top + rect.height >= headerHeight && rect.left <= sourceWidth && rect.top <= sourceHeight);
28769
+ const thumbnailFormControlRects = sheetFormControls.map((control) => ({
28770
+ control,
28771
+ rect: resolveThumbnailAnchoredRect(
28772
+ control.anchor,
28773
+ visibleRows,
28774
+ visibleCols,
28775
+ resolveRowHeightPx,
28776
+ resolveColumnWidthPx,
28777
+ previewRows,
28778
+ previewCols,
28779
+ {
28780
+ headerHeight,
28781
+ rowHeaderWidth
28782
+ }
28783
+ )
28784
+ })).filter(({ rect }) => rect.left + rect.width >= rowHeaderWidth && rect.top + rect.height >= headerHeight && rect.left <= sourceWidth && rect.top <= sourceHeight);
28785
+ const thumbnailDrawingEntries = [
28786
+ ...thumbnailShapeRects.map(({ rect, shape }) => ({
28787
+ kind: "shape",
28788
+ rect,
28789
+ shape,
28790
+ zIndex: shape.zIndex
28791
+ })),
28792
+ ...thumbnailFormControlRects.map(({ control, rect }) => ({
28793
+ control,
28794
+ kind: "formControl",
28795
+ rect,
28796
+ zIndex: control.zIndex
28797
+ })),
28798
+ ...thumbnailImageRects.map(({ image, rect }) => ({
28799
+ image,
28800
+ kind: "image",
28801
+ rect,
28802
+ zIndex: image.zIndex
28803
+ }))
28804
+ ].sort((left, right) => left.zIndex - right.zIndex);
27651
28805
  const paint = (canvas) => {
27652
28806
  if (!canvas) {
27653
28807
  return false;
27654
28808
  }
27655
- const context = canvas.getContext("2d");
28809
+ const context = canvas.getContext("2d", { alpha: false });
27656
28810
  if (!context) {
27657
28811
  return false;
27658
28812
  }
@@ -27676,9 +28830,10 @@ function useXlsxViewerThumbnails(options = {}) {
27676
28830
  }
27677
28831
  context.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0);
27678
28832
  context.clearRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
28833
+ const thumbnailSheetSurface = resolveSheetSurface(sheet, palette);
27679
28834
  context.fillStyle = palette.canvas;
27680
28835
  context.fillRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
27681
- context.fillStyle = resolveSheetSurface(sheet, palette);
28836
+ context.fillStyle = thumbnailSheetSurface;
27682
28837
  context.fillRect(rowHeaderWidth, headerHeight, Math.max(1, colAxis.totalSize), Math.max(1, rowAxis.totalSize));
27683
28838
  if (includeHeaders) {
27684
28839
  context.fillStyle = palette.headerSurface;
@@ -27799,8 +28954,8 @@ function useXlsxViewerThumbnails(options = {}) {
27799
28954
  };
27800
28955
  const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellData.style);
27801
28956
  const gradientFill = typeof cellData.style.backgroundImage === "string" ? resolveCanvasGradientFill(context, rect, cellData.style.backgroundImage) : null;
27802
- const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellData.style.backgroundColor === "string" ? cellData.style.backgroundColor : resolveSheetSurface(sheet, palette));
27803
- const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellData.style.backgroundColor === "string" && cellData.style.backgroundColor !== resolveSheetSurface(sheet, palette);
28957
+ const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellData.style.backgroundColor === "string" ? cellData.style.backgroundColor : thumbnailSheetSurface);
28958
+ const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellData.style.backgroundColor === "string" && cellData.style.backgroundColor !== thumbnailSheetSurface;
27804
28959
  context.fillStyle = gradientFill ?? fillColor;
27805
28960
  context.fillRect(rect.left, rect.top, rect.width, rect.height);
27806
28961
  if (cellData.conditionalDataBar) {
@@ -27855,12 +29010,19 @@ function useXlsxViewerThumbnails(options = {}) {
27855
29010
  if (canvasCellStyle.leftBorder) {
27856
29011
  strokeCanvasBorderSide(context, "left", rect, canvasCellStyle.leftBorder);
27857
29012
  }
29013
+ const rawText = cellData.value ?? "";
29014
+ const shouldDrawThumbnailContent = cellData.checkboxState != null || cellData.sparkline || rawText.length > 0 || cellData.conditionalIcon;
29015
+ if (!shouldDrawThumbnailContent) {
29016
+ continue;
29017
+ }
27858
29018
  const padding = canvasCellStyle.padding;
27859
29019
  const contentLeft = rect.left + padding.left;
27860
29020
  const contentTop = rect.top + padding.top;
27861
29021
  const contentWidth = Math.max(0, rect.width - padding.left - padding.right);
27862
29022
  const contentHeight = Math.max(0, rect.height - padding.top - padding.bottom);
27863
- const rawText = cellData.value ?? "";
29023
+ if (contentWidth <= 0 || contentHeight <= 0) {
29024
+ continue;
29025
+ }
27864
29026
  context.save();
27865
29027
  context.beginPath();
27866
29028
  context.rect(contentLeft, contentTop, contentWidth, contentHeight);
@@ -27952,6 +29114,25 @@ function useXlsxViewerThumbnails(options = {}) {
27952
29114
  context.restore();
27953
29115
  }
27954
29116
  }
29117
+ if (thumbnailDrawingEntries.length > 0) {
29118
+ context.save();
29119
+ context.beginPath();
29120
+ context.rect(rowHeaderWidth, headerHeight, Math.max(1, colAxis.totalSize), Math.max(1, rowAxis.totalSize));
29121
+ context.clip();
29122
+ for (const entry of thumbnailDrawingEntries) {
29123
+ if (entry.kind === "shape") {
29124
+ drawStaticShape(context, entry.shape, entry.rect, 1);
29125
+ } else if (entry.kind === "formControl") {
29126
+ drawStaticFormControl(context, entry.control, entry.rect, palette, 1, thumbnailSheetSurface);
29127
+ } else {
29128
+ const imageElement = getThumbnailImage(entry.image);
29129
+ if (imageElement) {
29130
+ context.drawImage(imageElement, entry.rect.left, entry.rect.top, entry.rect.width, entry.rect.height);
29131
+ }
29132
+ }
29133
+ }
29134
+ context.restore();
29135
+ }
27955
29136
  if (includeHeaders) {
27956
29137
  context.strokeStyle = palette.border;
27957
29138
  context.lineWidth = 1;
@@ -28005,7 +29186,18 @@ function useXlsxViewerThumbnails(options = {}) {
28005
29186
  workbookSheetIndex: sheet.workbookSheetIndex
28006
29187
  };
28007
29188
  });
28008
- }, [includeHeaders, palette, resolution, sheets, workbook]);
29189
+ }, [
29190
+ getSheetFormControls,
29191
+ getSheetImages,
29192
+ getSheetShapes,
29193
+ getThumbnailImage,
29194
+ includeHeaders,
29195
+ palette,
29196
+ resolution,
29197
+ sheets,
29198
+ thumbnailImageLoadVersion,
29199
+ workbook
29200
+ ]);
28009
29201
  const paintThumbnail = React4.useCallback(
28010
29202
  (sheetIndex, canvas) => thumbnails[sheetIndex]?.paint(canvas) ?? false,
28011
29203
  [thumbnails]