@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.cjs CHANGED
@@ -4575,6 +4575,10 @@ function parseSheetState(archive, path, options) {
4575
4575
  let hasVerticalMerges = false;
4576
4576
  let maxHorizontalMergeEndCol = -1;
4577
4577
  let maxVerticalMergeEndRow = -1;
4578
+ let minContentCol = Number.POSITIVE_INFINITY;
4579
+ let minContentRow = Number.POSITIVE_INFINITY;
4580
+ let maxContentCol = -1;
4581
+ let maxContentRow = -1;
4578
4582
  const columnWidthCharacterWidthPx = measureColumnCharacterWidthPx(
4579
4583
  options?.defaultFont?.family,
4580
4584
  options?.defaultFont?.sizePt
@@ -4587,6 +4591,26 @@ function parseSheetState(archive, path, options) {
4587
4591
  sheetViewNode?.getAttribute("zoomScale") ?? sheetViewNode?.getAttribute("zoomScaleNormal") ?? Number.NaN
4588
4592
  );
4589
4593
  const zoomScale = Number.isFinite(rawZoomScale) && rawZoomScale > 0 ? rawZoomScale : 100;
4594
+ const trackContentCell = (cellRef) => {
4595
+ if (!cellRef) {
4596
+ return;
4597
+ }
4598
+ const cell = parseA1CellReference(cellRef);
4599
+ if (!cell) {
4600
+ return;
4601
+ }
4602
+ minContentCol = Math.min(minContentCol, cell.col);
4603
+ minContentRow = Math.min(minContentRow, cell.row);
4604
+ maxContentCol = Math.max(maxContentCol, cell.col);
4605
+ maxContentRow = Math.max(maxContentRow, cell.row);
4606
+ };
4607
+ const isMeaningfulCellNode = (cellNode) => {
4608
+ if (getFirstChild(cellNode, "f") || getFirstChild(cellNode, "is")) {
4609
+ return true;
4610
+ }
4611
+ const valueNode = getFirstChild(cellNode, "v");
4612
+ return Boolean(valueNode && (valueNode.textContent ?? "").length > 0);
4613
+ };
4590
4614
  getLocalElements(document2, "row").forEach((rowNode) => {
4591
4615
  const rowIndex = Number(rowNode.getAttribute("r") ?? 0) - 1;
4592
4616
  const height = Number(rowNode.getAttribute("ht") ?? Number.NaN);
@@ -4601,17 +4625,36 @@ function parseSheetState(archive, path, options) {
4601
4625
  if (rowIndex >= 0 && isHidden) {
4602
4626
  hiddenRows.add(rowIndex);
4603
4627
  }
4604
- if (includeCachedFormulaValues) {
4605
- getChildElements(rowNode, "c").forEach((cellNode) => {
4628
+ getChildElements(rowNode, "c").forEach((cellNode) => {
4629
+ const cellRef = cellNode.getAttribute("r");
4630
+ if (isMeaningfulCellNode(cellNode)) {
4631
+ trackContentCell(cellRef);
4632
+ }
4633
+ if (includeCachedFormulaValues) {
4606
4634
  const formulaNode = getFirstChild(cellNode, "f");
4607
4635
  const valueNode = getFirstChild(cellNode, "v");
4608
- const cellRef = cellNode.getAttribute("r");
4609
4636
  if (formulaNode && valueNode && cellRef) {
4610
4637
  cachedFormulaValues[cellRef] = valueNode.textContent ?? "";
4611
4638
  }
4612
- });
4639
+ }
4640
+ });
4641
+ });
4642
+ getLocalElements(document2, "mergeCell").forEach((mergeNode) => {
4643
+ const reference = mergeNode.getAttribute("ref");
4644
+ const range = reference ? parseA1RangeReference(reference) : null;
4645
+ if (!range) {
4646
+ return;
4647
+ }
4648
+ if (range.end.col > range.start.col) {
4649
+ hasHorizontalMerges = true;
4650
+ maxHorizontalMergeEndCol = Math.max(maxHorizontalMergeEndCol, range.end.col);
4651
+ }
4652
+ if (range.end.row > range.start.row) {
4653
+ hasVerticalMerges = true;
4654
+ maxVerticalMergeEndRow = Math.max(maxVerticalMergeEndRow, range.end.row);
4613
4655
  }
4614
4656
  });
4657
+ const maxMetadataCol = Math.max(maxContentCol, maxHorizontalMergeEndCol, 0) + 256;
4615
4658
  getLocalElements(document2, "col").forEach((colNode) => {
4616
4659
  const min = Number(colNode.getAttribute("min") ?? 0) - 1;
4617
4660
  const max = Number(colNode.getAttribute("max") ?? 0) - 1;
@@ -4623,7 +4666,7 @@ function parseSheetState(archive, path, options) {
4623
4666
  return;
4624
4667
  }
4625
4668
  }
4626
- for (let col = min; col <= max; col += 1) {
4669
+ for (let col = min; col <= Math.min(max, maxMetadataCol); col += 1) {
4627
4670
  if (col >= 0) {
4628
4671
  if (Number.isFinite(width)) {
4629
4672
  const widthPx = sheetColumnWidthToPixels(width, columnWidthCharacterWidthPx);
@@ -4638,21 +4681,6 @@ function parseSheetState(archive, path, options) {
4638
4681
  }
4639
4682
  }
4640
4683
  });
4641
- getLocalElements(document2, "mergeCell").forEach((mergeNode) => {
4642
- const reference = mergeNode.getAttribute("ref");
4643
- const range = reference ? parseA1RangeReference(reference) : null;
4644
- if (!range) {
4645
- return;
4646
- }
4647
- if (range.end.col > range.start.col) {
4648
- hasHorizontalMerges = true;
4649
- maxHorizontalMergeEndCol = Math.max(maxHorizontalMergeEndCol, range.end.col);
4650
- }
4651
- if (range.end.row > range.start.row) {
4652
- hasVerticalMerges = true;
4653
- maxVerticalMergeEndRow = Math.max(maxVerticalMergeEndRow, range.end.row);
4654
- }
4655
- });
4656
4684
  return {
4657
4685
  cachedFormulaValues,
4658
4686
  columnWidthCharacterWidthPx,
@@ -4665,6 +4693,10 @@ function parseSheetState(archive, path, options) {
4665
4693
  hasVerticalMerges,
4666
4694
  maxHorizontalMergeEndCol,
4667
4695
  maxVerticalMergeEndRow,
4696
+ maxContentCol,
4697
+ maxContentRow,
4698
+ minContentCol: Number.isFinite(minContentCol) ? minContentCol : -1,
4699
+ minContentRow: Number.isFinite(minContentRow) ? minContentRow : -1,
4668
4700
  hiddenCols: [...hiddenCols].sort((left, right) => left - right),
4669
4701
  hiddenRows: [...hiddenRows].sort((left, right) => left - right),
4670
4702
  rowHeightOverridesPx,
@@ -6694,6 +6726,20 @@ function resolveDisplayFileName(src, fileName) {
6694
6726
  return lastSegment;
6695
6727
  }
6696
6728
  }
6729
+ function resolveSheetDisplayUsedRange(usedRange, sheetState) {
6730
+ const [minRow, minCol, maxRow, maxCol] = usedRange;
6731
+ const maxMeaningfulRow = Math.max(sheetState?.maxContentRow ?? -1, sheetState?.maxVerticalMergeEndRow ?? -1);
6732
+ const maxMeaningfulCol = Math.max(sheetState?.maxContentCol ?? -1, sheetState?.maxHorizontalMergeEndCol ?? -1);
6733
+ if (maxMeaningfulRow < 0 && maxMeaningfulCol < 0) {
6734
+ return usedRange;
6735
+ }
6736
+ return [
6737
+ sheetState?.minContentRow !== void 0 && sheetState.minContentRow >= 0 ? Math.min(minRow, sheetState.minContentRow) : minRow,
6738
+ sheetState?.minContentCol !== void 0 && sheetState.minContentCol >= 0 ? Math.min(minCol, sheetState.minContentCol) : minCol,
6739
+ maxMeaningfulRow >= 0 ? Math.min(maxRow, maxMeaningfulRow) : maxRow,
6740
+ maxMeaningfulCol >= 0 ? Math.min(maxCol, maxMeaningfulCol) : maxCol
6741
+ ];
6742
+ }
6697
6743
  function buildSheetList(workbook, sheetStatesByWorkbookSheetIndex, themePalette, styleById, namedCellStyleByName, tableStyleByName, showHiddenSheets = false) {
6698
6744
  const sheets = [];
6699
6745
  for (let index = 0; index < workbook.sheetCount; index += 1) {
@@ -6760,7 +6806,7 @@ function buildSheetList(workbook, sheetStatesByWorkbookSheetIndex, themePalette,
6760
6806
  });
6761
6807
  continue;
6762
6808
  }
6763
- const [minRow, minCol, maxRow, maxCol] = usedRange;
6809
+ const [minRow, minCol, maxRow, maxCol] = resolveSheetDisplayUsedRange(usedRange, sheetState);
6764
6810
  let visibleRowsCache = null;
6765
6811
  let visibleColsCache = null;
6766
6812
  let rowHeightsCache = null;
@@ -16502,7 +16548,11 @@ var SELECTION_DRAG_THRESHOLD_PX = 4;
16502
16548
  var IMAGE_MIN_SIZE_PX = 16;
16503
16549
  var IMAGE_HANDLE_SIZE_PX = 10;
16504
16550
  var CANVAS_RESIZE_HIT_SLOP_PX = 8;
16505
- var CANVAS_VIEWPORT_OVERSCAN_PX = 240;
16551
+ var CANVAS_VIEWPORT_OVERSCAN_PX = 480;
16552
+ var CANVAS_SCROLL_BUFFER_PX = 480;
16553
+ var CANVAS_DEFERRED_VIEWPORT_SYNC_THRESHOLD_PX = CANVAS_SCROLL_BUFFER_PX - 96;
16554
+ var CANVAS_IMMEDIATE_VIEWPORT_SYNC_THRESHOLD_PX = CANVAS_SCROLL_BUFFER_PX * 1.5;
16555
+ var CANVAS_DIRTY_CELL_CULL_MARGIN_PX = CANVAS_VIEWPORT_OVERSCAN_PX * 2;
16506
16556
  var THUMBNAIL_DEFAULT_MAX_DIMENSION = 192;
16507
16557
  var THUMBNAIL_FALLBACK_ROWS = 12;
16508
16558
  var THUMBNAIL_FALLBACK_COLS = 8;
@@ -16517,6 +16567,12 @@ var CHART_SOURCE_HIGHLIGHT_COLORS = ["#2563eb", "#dc2626", "#7c3aed", "#059669",
16517
16567
  var SHEET_SURFACE = "#ffffff";
16518
16568
  var DEFAULT_CELL_PADDING = "0 4px";
16519
16569
  var IMAGE_HANDLE_POSITIONS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
16570
+ var CANVAS_CELL_STYLE_CACHE_LIMIT = 4096;
16571
+ var CANVAS_TEXT_MEASURE_CACHE_LIMIT = 2e4;
16572
+ var CANVAS_TEXT_TRUNCATE_CACHE_LIMIT = 4096;
16573
+ var CANVAS_TEXT_WRAP_CACHE_LIMIT = 2048;
16574
+ var CANVAS_PATH2D_CACHE_LIMIT = 512;
16575
+ var EMPTY_VIRTUAL_ITEMS = [];
16520
16576
  var IMAGE_HANDLE_CURSOR = {
16521
16577
  e: "ew-resize",
16522
16578
  n: "ns-resize",
@@ -16560,6 +16616,50 @@ var NUMERIC_LENGTH_STYLE_KEYS = /* @__PURE__ */ new Set([
16560
16616
  "top",
16561
16617
  "width"
16562
16618
  ]);
16619
+ var canvasCellStyleCache = /* @__PURE__ */ new Map();
16620
+ var canvasTextMeasureCache = /* @__PURE__ */ new Map();
16621
+ var canvasTextTruncateCache = /* @__PURE__ */ new Map();
16622
+ var canvasTextWrapCache = /* @__PURE__ */ new Map();
16623
+ var canvasPath2DCache = /* @__PURE__ */ new Map();
16624
+ function rememberBoundedCacheValue(cache, key, value, limit) {
16625
+ if (cache.size >= limit) {
16626
+ const oldestKey = cache.keys().next().value;
16627
+ if (oldestKey !== void 0) {
16628
+ cache.delete(oldestKey);
16629
+ }
16630
+ }
16631
+ cache.set(key, value);
16632
+ return value;
16633
+ }
16634
+ function roundCanvasCacheWidth(width) {
16635
+ return Math.round(width * 4) / 4;
16636
+ }
16637
+ function measureCanvasTextWidth(context, text) {
16638
+ if (text.length === 0) {
16639
+ return 0;
16640
+ }
16641
+ const cacheKey = `${context.font}\0${text}`;
16642
+ const cached = canvasTextMeasureCache.get(cacheKey);
16643
+ if (cached !== void 0) {
16644
+ return cached;
16645
+ }
16646
+ return rememberBoundedCacheValue(
16647
+ canvasTextMeasureCache,
16648
+ cacheKey,
16649
+ context.measureText(text).width,
16650
+ CANVAS_TEXT_MEASURE_CACHE_LIMIT
16651
+ );
16652
+ }
16653
+ function getCachedCanvasPath2D(path) {
16654
+ if (typeof Path2D === "undefined") {
16655
+ return null;
16656
+ }
16657
+ const cached = canvasPath2DCache.get(path);
16658
+ if (cached) {
16659
+ return cached;
16660
+ }
16661
+ return rememberBoundedCacheValue(canvasPath2DCache, path, new Path2D(path), CANVAS_PATH2D_CACHE_LIMIT);
16662
+ }
16563
16663
  function scaleCssLengthExpression(value, scale) {
16564
16664
  if (scale === 1) {
16565
16665
  return value;
@@ -16740,7 +16840,6 @@ function strokeCanvasBorderSide(context, side, rect, border) {
16740
16840
  const right = rect.left + rect.width;
16741
16841
  const top = rect.top;
16742
16842
  const bottom = rect.top + rect.height;
16743
- context.save();
16744
16843
  context.strokeStyle = border.color;
16745
16844
  context.lineWidth = border.width;
16746
16845
  applyCanvasBorderDash(context, border.style, border.width);
@@ -16770,13 +16869,22 @@ function strokeCanvasBorderSide(context, side, rect, border) {
16770
16869
  } else {
16771
16870
  strokeLine(0);
16772
16871
  }
16773
- context.restore();
16872
+ context.setLineDash([]);
16873
+ context.lineCap = "butt";
16874
+ context.lineWidth = 1;
16774
16875
  }
16775
16876
  function truncateCanvasText(context, text, maxWidth) {
16776
16877
  if (maxWidth <= 0 || text.length === 0) {
16777
16878
  return "";
16778
16879
  }
16779
- if (context.measureText(text).width <= maxWidth) {
16880
+ const roundedMaxWidth = roundCanvasCacheWidth(maxWidth);
16881
+ const cacheKey = `${context.font}\0${roundedMaxWidth}\0${text}`;
16882
+ const cached = canvasTextTruncateCache.get(cacheKey);
16883
+ if (cached !== void 0) {
16884
+ return cached;
16885
+ }
16886
+ if (measureCanvasTextWidth(context, text) <= maxWidth) {
16887
+ rememberBoundedCacheValue(canvasTextTruncateCache, cacheKey, text, CANVAS_TEXT_TRUNCATE_CACHE_LIMIT);
16780
16888
  return text;
16781
16889
  }
16782
16890
  const ellipsis = "\u2026";
@@ -16785,13 +16893,18 @@ function truncateCanvasText(context, text, maxWidth) {
16785
16893
  while (low < high) {
16786
16894
  const mid = Math.ceil((low + high) / 2);
16787
16895
  const candidate = `${text.slice(0, mid)}${ellipsis}`;
16788
- if (context.measureText(candidate).width <= maxWidth) {
16896
+ if (measureCanvasTextWidth(context, candidate) <= maxWidth) {
16789
16897
  low = mid;
16790
16898
  } else {
16791
16899
  high = mid - 1;
16792
16900
  }
16793
16901
  }
16794
- return low <= 0 ? ellipsis : `${text.slice(0, low)}${ellipsis}`;
16902
+ return rememberBoundedCacheValue(
16903
+ canvasTextTruncateCache,
16904
+ cacheKey,
16905
+ low <= 0 ? ellipsis : `${text.slice(0, low)}${ellipsis}`,
16906
+ CANVAS_TEXT_TRUNCATE_CACHE_LIMIT
16907
+ );
16795
16908
  }
16796
16909
  function resolveCanvasFontSizePx(style, fallbackSize = 12) {
16797
16910
  const rawFontSize = style.fontSize;
@@ -16850,7 +16963,7 @@ function resolveCanvasWrapIndex(context, text, maxWidth) {
16850
16963
  while (low <= high) {
16851
16964
  const mid = Math.floor((low + high) / 2);
16852
16965
  const candidate = text.slice(0, mid);
16853
- if (context.measureText(candidate).width <= maxWidth) {
16966
+ if (measureCanvasTextWidth(context, candidate) <= maxWidth) {
16854
16967
  best = mid;
16855
16968
  low = mid + 1;
16856
16969
  } else {
@@ -16867,6 +16980,12 @@ function wrapCanvasText(context, text, maxWidth) {
16867
16980
  return text.replace(/\r\n?/g, "\n").split("\n");
16868
16981
  }
16869
16982
  const normalized = text.replace(/\r\n?/g, "\n");
16983
+ const roundedMaxWidth = roundCanvasCacheWidth(maxWidth);
16984
+ const cacheKey = `${context.font}\0${roundedMaxWidth}\0${normalized}`;
16985
+ const cached = canvasTextWrapCache.get(cacheKey);
16986
+ if (cached) {
16987
+ return cached;
16988
+ }
16870
16989
  const paragraphs = normalized.split("\n");
16871
16990
  const lines = [];
16872
16991
  for (const paragraph of paragraphs) {
@@ -16876,7 +16995,7 @@ function wrapCanvasText(context, text, maxWidth) {
16876
16995
  }
16877
16996
  let remaining = paragraph;
16878
16997
  while (remaining.length > 0) {
16879
- if (context.measureText(remaining).width <= maxWidth) {
16998
+ if (measureCanvasTextWidth(context, remaining) <= maxWidth) {
16880
16999
  lines.push(remaining);
16881
17000
  break;
16882
17001
  }
@@ -16894,10 +17013,31 @@ function wrapCanvasText(context, text, maxWidth) {
16894
17013
  remaining = remaining.slice(breakIndex).replace(/^\s+/g, "");
16895
17014
  }
16896
17015
  }
16897
- return lines;
17016
+ return rememberBoundedCacheValue(canvasTextWrapCache, cacheKey, lines, CANVAS_TEXT_WRAP_CACHE_LIMIT);
16898
17017
  }
16899
17018
  function buildCanvasCellStyleCache(style) {
16900
- return {
17019
+ const cacheKey = [
17020
+ style.font,
17021
+ style.fontStyle,
17022
+ style.fontWeight,
17023
+ style.fontSize,
17024
+ style.fontFamily,
17025
+ style.borderTop,
17026
+ style.borderRight,
17027
+ style.borderBottom,
17028
+ style.borderLeft,
17029
+ style.padding,
17030
+ style.textAlign,
17031
+ style.color,
17032
+ style.textDecoration,
17033
+ style.textOverflow,
17034
+ style.whiteSpace
17035
+ ].map((value) => value == null ? "" : String(value)).join("");
17036
+ const cached = canvasCellStyleCache.get(cacheKey);
17037
+ if (cached) {
17038
+ return cached;
17039
+ }
17040
+ return rememberBoundedCacheValue(canvasCellStyleCache, cacheKey, {
16901
17041
  baseFont: resolveCanvasFont(style, 12),
16902
17042
  bottomBorder: parseCanvasBorderDeclaration(style.borderBottom),
16903
17043
  leftBorder: parseCanvasBorderDeclaration(style.borderLeft),
@@ -16909,7 +17049,7 @@ function buildCanvasCellStyleCache(style) {
16909
17049
  textOverflowEllipsis: style.textOverflow === "ellipsis",
16910
17050
  topBorder: parseCanvasBorderDeclaration(style.borderTop),
16911
17051
  usesWrappedText: style.whiteSpace === "pre-wrap"
16912
- };
17052
+ }, CANVAS_CELL_STYLE_CACHE_LIMIT);
16913
17053
  }
16914
17054
  function splitCssGradientArgs(value) {
16915
17055
  const parts = [];
@@ -17462,6 +17602,25 @@ function resolveImageAnchorExtents(image) {
17462
17602
  maxRow: Math.max(image.anchor.from.row, image.anchor.to.row)
17463
17603
  };
17464
17604
  }
17605
+ function resolveAnchoredBounds(anchor) {
17606
+ if (anchor.kind === "absolute") {
17607
+ return null;
17608
+ }
17609
+ if (anchor.kind === "one-cell") {
17610
+ return {
17611
+ maxCol: anchor.from.col,
17612
+ maxRow: anchor.from.row,
17613
+ minCol: anchor.from.col,
17614
+ minRow: anchor.from.row
17615
+ };
17616
+ }
17617
+ return {
17618
+ maxCol: Math.max(anchor.from.col, anchor.to.col),
17619
+ maxRow: Math.max(anchor.from.row, anchor.to.row),
17620
+ minCol: Math.min(anchor.from.col, anchor.to.col),
17621
+ minRow: Math.min(anchor.from.row, anchor.to.row)
17622
+ };
17623
+ }
17465
17624
  function resolveShapeAnchorExtents(shape) {
17466
17625
  if (shape.anchor.kind === "absolute") {
17467
17626
  return { maxCol: 0, maxRow: 0 };
@@ -18418,6 +18577,44 @@ function resolveShapeVector(shape) {
18418
18577
  }
18419
18578
  return buildPresetShapePath(shape);
18420
18579
  }
18580
+ function drawCanvasRoundedRect(context, left, top, width, height, radius) {
18581
+ const safeRadius = Math.max(0, Math.min(radius, width / 2, height / 2));
18582
+ context.beginPath();
18583
+ context.moveTo(left + safeRadius, top);
18584
+ context.lineTo(left + width - safeRadius, top);
18585
+ context.quadraticCurveTo(left + width, top, left + width, top + safeRadius);
18586
+ context.lineTo(left + width, top + height - safeRadius);
18587
+ context.quadraticCurveTo(left + width, top + height, left + width - safeRadius, top + height);
18588
+ context.lineTo(left + safeRadius, top + height);
18589
+ context.quadraticCurveTo(left, top + height, left, top + height - safeRadius);
18590
+ context.lineTo(left, top + safeRadius);
18591
+ context.quadraticCurveTo(left, top, left + safeRadius, top);
18592
+ context.closePath();
18593
+ }
18594
+ function applyCanvasShapeDash(context, dash, lineWidth) {
18595
+ if (!dash) {
18596
+ context.setLineDash([]);
18597
+ return;
18598
+ }
18599
+ const unit = Math.max(1, lineWidth);
18600
+ switch (dash) {
18601
+ case "dash":
18602
+ context.setLineDash([4 * unit, 3 * unit]);
18603
+ break;
18604
+ case "dashDot":
18605
+ context.setLineDash([4 * unit, 2 * unit, unit, 2 * unit]);
18606
+ break;
18607
+ case "dot":
18608
+ context.setLineDash([unit, 2 * unit]);
18609
+ break;
18610
+ case "lgDash":
18611
+ context.setLineDash([8 * unit, 3 * unit]);
18612
+ break;
18613
+ default:
18614
+ context.setLineDash([]);
18615
+ break;
18616
+ }
18617
+ }
18421
18618
  function resolveShapeLineEndMarker(type, markerId, color, strokeWidth, rect, viewBox) {
18422
18619
  if (type !== "triangle") {
18423
18620
  return null;
@@ -18607,6 +18804,71 @@ function buildThumbnailAxisItems(actualIndices, getSizePx) {
18607
18804
  totalSize: offset
18608
18805
  };
18609
18806
  }
18807
+ function resolveThumbnailAxisAbsoluteOffset(actualIndices, getSizePx, actualIndex) {
18808
+ let total = 0;
18809
+ for (const candidate of actualIndices) {
18810
+ if (candidate >= actualIndex) {
18811
+ break;
18812
+ }
18813
+ total += Math.max(1, getSizePx(candidate));
18814
+ }
18815
+ return total;
18816
+ }
18817
+ function resolveThumbnailAxisMarkerOffset({
18818
+ actualIndex,
18819
+ actualIndices,
18820
+ getSizePx,
18821
+ offsetEmu,
18822
+ previewStartActualIndex
18823
+ }) {
18824
+ return resolveThumbnailAxisAbsoluteOffset(actualIndices, getSizePx, actualIndex) - resolveThumbnailAxisAbsoluteOffset(actualIndices, getSizePx, previewStartActualIndex) + emuToPixels(offsetEmu);
18825
+ }
18826
+ function resolveThumbnailAnchoredRect(anchor, visibleRows, visibleCols, getRowHeightPx, getColWidthPx, previewRows, previewCols, options) {
18827
+ const previewStartRow = previewRows[0] ?? 0;
18828
+ const previewStartCol = previewCols[0] ?? 0;
18829
+ const previewOriginX = resolveThumbnailAxisAbsoluteOffset(visibleCols, getColWidthPx, previewStartCol);
18830
+ const previewOriginY = resolveThumbnailAxisAbsoluteOffset(visibleRows, getRowHeightPx, previewStartRow);
18831
+ const resolveMarkerLeft = (col, colOffsetEmu) => options.rowHeaderWidth + resolveThumbnailAxisMarkerOffset({
18832
+ actualIndex: col,
18833
+ actualIndices: visibleCols,
18834
+ getSizePx: getColWidthPx,
18835
+ offsetEmu: colOffsetEmu,
18836
+ previewStartActualIndex: previewStartCol
18837
+ });
18838
+ const resolveMarkerTop = (row, rowOffsetEmu) => options.headerHeight + resolveThumbnailAxisMarkerOffset({
18839
+ actualIndex: row,
18840
+ actualIndices: visibleRows,
18841
+ getSizePx: getRowHeightPx,
18842
+ offsetEmu: rowOffsetEmu,
18843
+ previewStartActualIndex: previewStartRow
18844
+ });
18845
+ if (anchor.kind === "absolute") {
18846
+ return {
18847
+ height: Math.max(1, emuToPixels(anchor.sizeEmu.cy)),
18848
+ left: options.rowHeaderWidth + emuToPixels(anchor.positionEmu.x) - previewOriginX,
18849
+ top: options.headerHeight + emuToPixels(anchor.positionEmu.y) - previewOriginY,
18850
+ width: Math.max(1, emuToPixels(anchor.sizeEmu.cx))
18851
+ };
18852
+ }
18853
+ if (anchor.kind === "one-cell") {
18854
+ return {
18855
+ height: Math.max(1, emuToPixels(anchor.sizeEmu.cy)),
18856
+ left: resolveMarkerLeft(anchor.from.col, anchor.from.colOffsetEmu),
18857
+ top: resolveMarkerTop(anchor.from.row, anchor.from.rowOffsetEmu),
18858
+ width: Math.max(1, emuToPixels(anchor.sizeEmu.cx))
18859
+ };
18860
+ }
18861
+ const left = resolveMarkerLeft(anchor.from.col, anchor.from.colOffsetEmu);
18862
+ const top = resolveMarkerTop(anchor.from.row, anchor.from.rowOffsetEmu);
18863
+ const right = resolveMarkerLeft(anchor.to.col, anchor.to.colOffsetEmu);
18864
+ const bottom = resolveMarkerTop(anchor.to.row, anchor.to.rowOffsetEmu);
18865
+ return {
18866
+ height: Math.max(1, bottom - top),
18867
+ left,
18868
+ top,
18869
+ width: Math.max(1, right - left)
18870
+ };
18871
+ }
18610
18872
  function columnLabel2(col) {
18611
18873
  let label = "";
18612
18874
  let nextValue = col;
@@ -19020,12 +19282,12 @@ function measureTextWidth(value, style) {
19020
19282
  return value.length * 7;
19021
19283
  }
19022
19284
  textMeasureCanvas ??= document.createElement("canvas");
19023
- const context = textMeasureCanvas.getContext("2d");
19285
+ const context = textMeasureCanvas.getContext("2d", { alpha: false });
19024
19286
  if (!context) {
19025
19287
  return value.length * 7;
19026
19288
  }
19027
19289
  context.font = buildCanvasFont(style);
19028
- return context.measureText(value).width;
19290
+ return measureCanvasTextWidth(context, value);
19029
19291
  }
19030
19292
  function measureWrappedTextHeight(value, style, widthPx) {
19031
19293
  if (!value) {
@@ -19039,7 +19301,7 @@ function measureWrappedTextHeight(value, style, widthPx) {
19039
19301
  return fallbackLineCount * lineHeight + padding.top + padding.bottom;
19040
19302
  }
19041
19303
  textMeasureCanvas ??= document.createElement("canvas");
19042
- const context = textMeasureCanvas.getContext("2d");
19304
+ const context = textMeasureCanvas.getContext("2d", { alpha: false });
19043
19305
  if (!context) {
19044
19306
  return fallbackLineCount * lineHeight + padding.top + padding.bottom;
19045
19307
  }
@@ -20137,7 +20399,7 @@ function blitCanvasWithScrollDelta(canvas, context, bufferCanvas, dpr, width, he
20137
20399
  if (bufferCanvas.height !== deviceHeight) {
20138
20400
  bufferCanvas.height = deviceHeight;
20139
20401
  }
20140
- const bufferContext = bufferCanvas.getContext("2d");
20402
+ const bufferContext = bufferCanvas.getContext("2d", { alpha: false });
20141
20403
  if (!bufferContext) {
20142
20404
  return null;
20143
20405
  }
@@ -20392,6 +20654,222 @@ function resolveFormControlLabel(control) {
20392
20654
  }
20393
20655
  return label.replace(/\u00a0/g, " ").replace(/^\s+/, "");
20394
20656
  }
20657
+ function drawStaticShapeText(context, shape, left, top, width, height, zoomFactor) {
20658
+ if (shape.paragraphs.length === 0) {
20659
+ return;
20660
+ }
20661
+ const inset = shape.textBox?.insetPx;
20662
+ const paddingLeft = (inset?.left ?? 6) * zoomFactor;
20663
+ const paddingRight = (inset?.right ?? 6) * zoomFactor;
20664
+ const paddingTop = (inset?.top ?? 4) * zoomFactor;
20665
+ const paddingBottom = (inset?.bottom ?? 4) * zoomFactor;
20666
+ const textLeft = left + paddingLeft;
20667
+ const textTop = top + paddingTop;
20668
+ const textWidth = Math.max(0, width - paddingLeft - paddingRight);
20669
+ const textHeight = Math.max(0, height - paddingTop - paddingBottom);
20670
+ if (textWidth <= 0 || textHeight <= 0) {
20671
+ return;
20672
+ }
20673
+ const lineMetrics = shape.paragraphs.map((paragraph) => {
20674
+ const lineHeight = Math.max(
20675
+ 12 * zoomFactor,
20676
+ ...paragraph.runs.map((run) => (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor * 1.2)
20677
+ );
20678
+ const widthPx = paragraph.runs.reduce((total, run) => {
20679
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
20680
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
20681
+ return total + measureCanvasTextWidth(context, run.text);
20682
+ }, 0);
20683
+ return { lineHeight, widthPx };
20684
+ });
20685
+ const totalTextHeight = lineMetrics.reduce((total, metric) => total + metric.lineHeight, 0);
20686
+ let y = textTop;
20687
+ if (shape.textBox?.verticalAlign === "middle") {
20688
+ y = textTop + Math.max(0, (textHeight - totalTextHeight) / 2);
20689
+ } else if (shape.textBox?.verticalAlign === "bottom") {
20690
+ y = textTop + Math.max(0, textHeight - totalTextHeight);
20691
+ }
20692
+ context.save();
20693
+ context.beginPath();
20694
+ context.rect(textLeft, textTop, textWidth, textHeight);
20695
+ context.clip();
20696
+ context.textBaseline = "middle";
20697
+ shape.paragraphs.forEach((paragraph, paragraphIndex) => {
20698
+ const metric = lineMetrics[paragraphIndex] ?? { lineHeight: 14 * zoomFactor, widthPx: 0 };
20699
+ let x = textLeft;
20700
+ const align = paragraph.align ?? shape.textBox?.horizontalAlign ?? "left";
20701
+ if (align === "center") {
20702
+ x = textLeft + Math.max(0, (textWidth - metric.widthPx) / 2);
20703
+ } else if (align === "right") {
20704
+ x = textLeft + Math.max(0, textWidth - metric.widthPx);
20705
+ }
20706
+ paragraph.runs.forEach((run) => {
20707
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
20708
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
20709
+ context.fillStyle = run.color ?? "#000000";
20710
+ context.textAlign = "left";
20711
+ const textY = y + metric.lineHeight / 2;
20712
+ context.fillText(run.text, x, textY);
20713
+ if (run.underline && run.text.length > 0) {
20714
+ const textWidthPx = measureCanvasTextWidth(context, run.text);
20715
+ context.strokeStyle = run.color ?? "#000000";
20716
+ context.lineWidth = Math.max(1, zoomFactor * 0.75);
20717
+ context.beginPath();
20718
+ context.moveTo(x, textY + Math.max(2, fontSize * 0.28));
20719
+ context.lineTo(x + textWidthPx, textY + Math.max(2, fontSize * 0.28));
20720
+ context.stroke();
20721
+ }
20722
+ x += measureCanvasTextWidth(context, run.text);
20723
+ });
20724
+ y += metric.lineHeight;
20725
+ });
20726
+ context.restore();
20727
+ }
20728
+ function drawStaticShape(context, shape, rect, zoomFactor) {
20729
+ const fillColor = shape.fill?.none ? "transparent" : shape.fill?.color ?? "transparent";
20730
+ const strokeColor = shape.stroke?.none ? "transparent" : shape.stroke?.color ?? "transparent";
20731
+ const lineWidth = Math.max(0, (shape.stroke?.widthPx ?? (shape.geometry === "line" ? 2 : 1)) * zoomFactor);
20732
+ const vectorShape = resolveShapeVector(shape);
20733
+ const opacity = Math.min(shape.fill?.opacity ?? 1, shape.stroke?.opacity ?? 1);
20734
+ context.save();
20735
+ context.translate(rect.left + rect.width / 2, rect.top + rect.height / 2);
20736
+ if (shape.rotationDeg) {
20737
+ context.rotate(shape.rotationDeg * Math.PI / 180);
20738
+ }
20739
+ context.scale(shape.flipH ? -1 : 1, shape.flipV ? -1 : 1);
20740
+ context.globalAlpha *= opacity;
20741
+ context.lineWidth = lineWidth;
20742
+ context.strokeStyle = strokeColor;
20743
+ context.fillStyle = fillColor;
20744
+ applyCanvasShapeDash(context, shape.stroke?.dash, lineWidth);
20745
+ const localLeft = -rect.width / 2;
20746
+ const localTop = -rect.height / 2;
20747
+ if (vectorShape && typeof Path2D !== "undefined") {
20748
+ context.save();
20749
+ context.translate(localLeft, localTop);
20750
+ context.scale(
20751
+ rect.width / Math.max(1, vectorShape.viewBox.width),
20752
+ rect.height / Math.max(1, vectorShape.viewBox.height)
20753
+ );
20754
+ const path = getCachedCanvasPath2D(vectorShape.path);
20755
+ if (!path) {
20756
+ context.restore();
20757
+ context.restore();
20758
+ return;
20759
+ }
20760
+ if (fillColor !== "transparent") {
20761
+ context.fill(path);
20762
+ }
20763
+ if (strokeColor !== "transparent" && lineWidth > 0) {
20764
+ context.stroke(path);
20765
+ }
20766
+ context.restore();
20767
+ } else if (shape.geometry === "ellipse") {
20768
+ context.beginPath();
20769
+ context.ellipse(0, 0, rect.width / 2, rect.height / 2, 0, 0, Math.PI * 2);
20770
+ if (fillColor !== "transparent") {
20771
+ context.fill();
20772
+ }
20773
+ if (strokeColor !== "transparent" && lineWidth > 0) {
20774
+ context.stroke();
20775
+ }
20776
+ } else {
20777
+ const radius = shape.geometry === "roundRect" ? 12 * zoomFactor : 0;
20778
+ drawCanvasRoundedRect(context, localLeft, localTop, rect.width, rect.height, radius);
20779
+ if (fillColor !== "transparent") {
20780
+ context.fill();
20781
+ }
20782
+ if (strokeColor !== "transparent" && lineWidth > 0) {
20783
+ context.stroke();
20784
+ }
20785
+ }
20786
+ drawStaticShapeText(context, shape, localLeft, localTop, rect.width, rect.height, zoomFactor);
20787
+ context.restore();
20788
+ }
20789
+ function drawStaticFormControl(context, control, rect, palette, zoomFactor, sheetSurface = SHEET_SURFACE) {
20790
+ const label = resolveFormControlLabel(control);
20791
+ const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
20792
+ const textColor = control.textColor ?? "#000000";
20793
+ const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
20794
+ const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
20795
+ context.save();
20796
+ context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
20797
+ context.textBaseline = "middle";
20798
+ context.fillStyle = textColor;
20799
+ context.strokeStyle = stroke;
20800
+ context.lineWidth = Math.max(1, zoomFactor);
20801
+ if (control.kind === "group-box") {
20802
+ const labelInset = label ? Math.max(7, fontSizePx * 0.5) : 0;
20803
+ drawCanvasRoundedRect(context, rect.left, rect.top + labelInset, rect.width, Math.max(0, rect.height - labelInset), 2 * zoomFactor);
20804
+ context.stroke();
20805
+ if (label) {
20806
+ context.fillStyle = sheetSurface;
20807
+ const labelWidth = Math.min(rect.width - 16 * zoomFactor, measureCanvasTextWidth(context, label) + 8 * zoomFactor);
20808
+ context.fillRect(rect.left + 8 * zoomFactor, rect.top, labelWidth, fontSizePx * 1.2);
20809
+ context.fillStyle = textColor;
20810
+ context.textAlign = "left";
20811
+ context.fillText(label, rect.left + 12 * zoomFactor, rect.top + fontSizePx * 0.55);
20812
+ }
20813
+ context.restore();
20814
+ return;
20815
+ }
20816
+ if (control.kind === "button" || control.kind === "dropdown" || control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
20817
+ if (control.kind === "button") {
20818
+ const gradient = context.createLinearGradient(rect.left, rect.top, rect.left, rect.top + rect.height);
20819
+ gradient.addColorStop(0, "#f8fafc");
20820
+ gradient.addColorStop(1, "#e2e8f0");
20821
+ context.fillStyle = gradient;
20822
+ } else {
20823
+ context.fillStyle = "transparent";
20824
+ }
20825
+ drawCanvasRoundedRect(context, rect.left, rect.top, rect.width, rect.height, control.kind === "button" ? 4 * zoomFactor : 2 * zoomFactor);
20826
+ if (control.kind === "button") {
20827
+ context.fill();
20828
+ }
20829
+ context.stroke();
20830
+ }
20831
+ let textLeft = rect.left + 2 * zoomFactor;
20832
+ const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
20833
+ if (control.kind === "checkbox") {
20834
+ context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
20835
+ if (control.checked) {
20836
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
20837
+ 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));
20838
+ context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
20839
+ context.beginPath();
20840
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
20841
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
20842
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
20843
+ context.stroke();
20844
+ }
20845
+ textLeft += iconSize + 4 * zoomFactor;
20846
+ } else if (control.kind === "radio") {
20847
+ context.beginPath();
20848
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
20849
+ context.stroke();
20850
+ if (control.checked) {
20851
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
20852
+ context.beginPath();
20853
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
20854
+ context.fill();
20855
+ }
20856
+ textLeft += iconSize + 4 * zoomFactor;
20857
+ }
20858
+ if (label) {
20859
+ const maxTextWidth = Math.max(0, rect.left + rect.width - textLeft - textRightInset);
20860
+ const text = truncateCanvasText(context, label, maxTextWidth);
20861
+ context.fillStyle = textColor;
20862
+ context.textAlign = control.textAlign === "right" ? "right" : control.textAlign === "center" ? "center" : "left";
20863
+ const textX = context.textAlign === "right" ? rect.left + rect.width - textRightInset : context.textAlign === "center" ? textLeft + maxTextWidth / 2 : textLeft;
20864
+ context.fillText(text, textX, rect.top + rect.height / 2);
20865
+ }
20866
+ if (control.kind === "dropdown") {
20867
+ context.fillStyle = textColor;
20868
+ context.textAlign = "center";
20869
+ context.fillText("\u25BC", rect.left + rect.width - 10 * zoomFactor, rect.top + rect.height / 2);
20870
+ }
20871
+ context.restore();
20872
+ }
20395
20873
  function resolveConditionalDataBarForCell(row, col, worksheet, sheet, metricsCache) {
20396
20874
  const rules = sheet?.conditionalFormatRules ?? [];
20397
20875
  const matchingRule = rules.find(
@@ -20994,6 +21472,8 @@ function XlsxGrid({
20994
21472
  selectionHeaderColor,
20995
21473
  showImages = true
20996
21474
  }) {
21475
+ const xlsxCanvasProfileTarget = typeof window !== "undefined" ? window.__xlsxCanvasProfile : void 0;
21476
+ const xlsxGridRenderStart = xlsxCanvasProfileTarget ? performance.now() : 0;
20997
21477
  const {
20998
21478
  activeCell,
20999
21479
  activeSheet,
@@ -21077,6 +21557,11 @@ function XlsxGrid({
21077
21557
  const leftFrozenHeaderCanvasRef = React4.useRef(null);
21078
21558
  const leftScrollHeaderCanvasRef = React4.useRef(null);
21079
21559
  const cornerHeaderCanvasRef = React4.useRef(null);
21560
+ const canvasScrollOverlayContentRef = React4.useRef(null);
21561
+ const canvasTopOverlayContentRef = React4.useRef(null);
21562
+ const canvasLeftOverlayContentRef = React4.useRef(null);
21563
+ const canvasCornerOverlayContentRef = React4.useRef(null);
21564
+ const canvasImageCacheRef = React4.useRef(/* @__PURE__ */ new Map());
21080
21565
  const selectionOverlayRef = React4.useRef(null);
21081
21566
  const activeValidationOverlayRef = React4.useRef(null);
21082
21567
  const fillHandleRef = React4.useRef(null);
@@ -21105,6 +21590,7 @@ function XlsxGrid({
21105
21590
  const pendingLiveZoomCommitRef = React4.useRef(null);
21106
21591
  const touchPinchStateRef = React4.useRef(null);
21107
21592
  const safariPinchStartZoomRef = React4.useRef(null);
21593
+ const canvasTransformStyleCacheRef = React4.useRef(/* @__PURE__ */ new WeakMap());
21108
21594
  const displayedSelectionRef = React4.useRef(null);
21109
21595
  const firstVisibleColRef = React4.useRef(void 0);
21110
21596
  const lastVisibleColRef = React4.useRef(void 0);
@@ -21130,12 +21616,19 @@ function XlsxGrid({
21130
21616
  const [fillPreviewRange, setFillPreviewRange] = React4.useState(null);
21131
21617
  const [chartPreviewRect, setChartPreviewRect] = React4.useState(null);
21132
21618
  const [liveGestureZoom, setLiveGestureZoom] = React4.useState(null);
21619
+ const [canvasImageLoadVersion, setCanvasImageLoadVersion] = React4.useState(0);
21133
21620
  const liveDrawingViewportRef = React4.useRef({
21134
21621
  height: 0,
21135
21622
  left: 0,
21136
21623
  top: 0,
21137
21624
  width: 0
21138
21625
  });
21626
+ const canvasLayoutMetricsRef = React4.useRef({
21627
+ displayHeaderHeight: HEADER_HEIGHT,
21628
+ displayRowHeaderWidth: ROW_HEADER_WIDTH,
21629
+ frozenPaneBottom: HEADER_HEIGHT,
21630
+ frozenPaneRight: ROW_HEADER_WIDTH
21631
+ });
21139
21632
  const paintedDrawingViewportRef = React4.useRef({
21140
21633
  height: 0,
21141
21634
  left: 0,
@@ -21245,6 +21738,12 @@ function XlsxGrid({
21245
21738
  const applyCanvasViewportCompensation = React4.useCallback((liveViewport) => {
21246
21739
  const nextLiveViewport = liveViewport ?? liveDrawingViewportRef.current;
21247
21740
  const paintedViewport = paintedDrawingViewportRef.current;
21741
+ const {
21742
+ displayHeaderHeight: liveDisplayHeaderHeight,
21743
+ displayRowHeaderWidth: liveDisplayRowHeaderWidth,
21744
+ frozenPaneBottom: liveFrozenPaneBottom,
21745
+ frozenPaneRight: liveFrozenPaneRight
21746
+ } = canvasLayoutMetricsRef.current;
21248
21747
  const currentLiveGestureZoom = liveGestureZoomRef.current;
21249
21748
  const isLiveZooming2 = currentLiveGestureZoom !== null && zoomScale === currentLiveGestureZoom.baseZoomScale;
21250
21749
  const liveZoomScale2 = isLiveZooming2 ? Math.max(0.1, currentLiveGestureZoom.targetZoomScale / currentLiveGestureZoom.baseZoomScale) : 1;
@@ -21252,12 +21751,41 @@ function XlsxGrid({
21252
21751
  const scrollDeltaY = paintedViewport.top - nextLiveViewport.top;
21253
21752
  const liveZoomTranslateX2 = isLiveZooming2 ? currentLiveGestureZoom.anchor.x * (1 - liveZoomScale2) : 0;
21254
21753
  const liveZoomTranslateY2 = isLiveZooming2 ? currentLiveGestureZoom.anchor.y * (1 - liveZoomScale2) : 0;
21255
- const applyCanvasTransform = (canvas, translateX, translateY) => {
21256
- if (!canvas) {
21754
+ const applyElementTransform = (element, transform, willChange, transformOrigin = null) => {
21755
+ if (!element) {
21257
21756
  return;
21258
21757
  }
21259
- canvas.style.transform = translateX !== 0 || translateY !== 0 || liveZoomScale2 !== 1 ? `translate3d(${translateX}px, ${translateY}px, 0) scale(${liveZoomScale2})` : "";
21260
- canvas.style.willChange = translateX !== 0 || translateY !== 0 || liveZoomScale2 !== 1 ? "transform" : "";
21758
+ const cached = canvasTransformStyleCacheRef.current.get(element);
21759
+ if (cached?.transform !== transform || element.style.transform !== transform) {
21760
+ element.style.transform = transform;
21761
+ }
21762
+ if (cached?.willChange !== willChange || element.style.willChange !== willChange) {
21763
+ element.style.willChange = willChange;
21764
+ }
21765
+ if (transformOrigin !== null && (cached?.transformOrigin !== transformOrigin || element.style.transformOrigin !== transformOrigin)) {
21766
+ element.style.transformOrigin = transformOrigin;
21767
+ }
21768
+ canvasTransformStyleCacheRef.current.set(element, {
21769
+ transform,
21770
+ transformOrigin,
21771
+ willChange
21772
+ });
21773
+ };
21774
+ const applyCanvasTransform = (canvas, translateX, translateY) => {
21775
+ const shouldTransform = translateX !== 0 || translateY !== 0 || liveZoomScale2 !== 1;
21776
+ applyElementTransform(
21777
+ canvas,
21778
+ shouldTransform ? `translate3d(${translateX}px, ${translateY}px, 0) scale(${liveZoomScale2})` : "",
21779
+ shouldTransform ? "transform" : ""
21780
+ );
21781
+ };
21782
+ const applyOverlayTransform = (element, translateX, translateY) => {
21783
+ applyElementTransform(
21784
+ element,
21785
+ liveZoomScale2 !== 1 ? `translate3d(${translateX + liveZoomTranslateX2}px, ${translateY + liveZoomTranslateY2}px, 0) scale(${liveZoomScale2})` : `translate3d(${translateX}px, ${translateY}px, 0)`,
21786
+ "transform",
21787
+ "0 0"
21788
+ );
21261
21789
  };
21262
21790
  applyCanvasTransform(
21263
21791
  scrollBodyCanvasRef.current,
@@ -21304,6 +21832,26 @@ function XlsxGrid({
21304
21832
  liveZoomTranslateX2,
21305
21833
  liveZoomTranslateY2
21306
21834
  );
21835
+ applyOverlayTransform(
21836
+ canvasScrollOverlayContentRef.current,
21837
+ -nextLiveViewport.left - liveFrozenPaneRight,
21838
+ -nextLiveViewport.top - liveFrozenPaneBottom
21839
+ );
21840
+ applyOverlayTransform(
21841
+ canvasTopOverlayContentRef.current,
21842
+ -nextLiveViewport.left - liveFrozenPaneRight,
21843
+ -liveDisplayHeaderHeight
21844
+ );
21845
+ applyOverlayTransform(
21846
+ canvasLeftOverlayContentRef.current,
21847
+ -liveDisplayRowHeaderWidth,
21848
+ -nextLiveViewport.top - liveFrozenPaneBottom
21849
+ );
21850
+ applyOverlayTransform(
21851
+ canvasCornerOverlayContentRef.current,
21852
+ -liveDisplayRowHeaderWidth,
21853
+ -liveDisplayHeaderHeight
21854
+ );
21307
21855
  }, [zoomScale]);
21308
21856
  const updateLiveGestureZoomState = React4.useCallback((nextState) => {
21309
21857
  const resolvedState = typeof nextState === "function" ? nextState(liveGestureZoomRef.current) : nextState;
@@ -21406,6 +21954,11 @@ function XlsxGrid({
21406
21954
  if (matchesLiveViewport && matchesStateViewport) {
21407
21955
  return;
21408
21956
  }
21957
+ 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);
21958
+ if (!shouldCommitDeferredViewport) {
21959
+ pendingDrawingViewportRef.current = null;
21960
+ return;
21961
+ }
21409
21962
  pendingDrawingViewportRef.current = nextViewport;
21410
21963
  if (drawingViewportFrameRef.current !== null) {
21411
21964
  return;
@@ -21693,6 +22246,12 @@ function XlsxGrid({
21693
22246
  ) : displayRowHeaderWidth,
21694
22247
  [displayActualColWidths, displayDefaultColWidth, displayRowHeaderWidth, frozenCols, stickyLeftByCol]
21695
22248
  );
22249
+ canvasLayoutMetricsRef.current = {
22250
+ displayHeaderHeight,
22251
+ displayRowHeaderWidth,
22252
+ frozenPaneBottom,
22253
+ frozenPaneRight
22254
+ };
21696
22255
  const rowPrefixSumsRef = React4.useRef(rowPrefixSums);
21697
22256
  const colPrefixSumsRef = React4.useRef(colPrefixSums);
21698
22257
  const firstVisibleRow = visibleRows[0];
@@ -21744,6 +22303,7 @@ function XlsxGrid({
21744
22303
  }, [charts, images, shapes]);
21745
22304
  const shouldVirtualizeRows = visibleRows.length > 0;
21746
22305
  const shouldVirtualizeCols = visibleCols.length > 0 && frozenCols.length === 0;
22306
+ const shouldUseDomVirtualizer = !experimentalCanvas;
21747
22307
  const getScrollElement = React4.useCallback(() => scrollRef.current, []);
21748
22308
  const estimateRowSize = React4.useCallback(
21749
22309
  (index) => displayEffectiveRowHeights[index] ?? displayDefaultRowHeight,
@@ -21757,6 +22317,7 @@ function XlsxGrid({
21757
22317
  const getColItemKey = React4.useCallback((index) => visibleCols[index] ?? index, [visibleCols]);
21758
22318
  const rowVirtualizer = (0, import_react_virtual.useVirtualizer)({
21759
22319
  count: visibleRows.length,
22320
+ enabled: shouldUseDomVirtualizer,
21760
22321
  estimateSize: estimateRowSize,
21761
22322
  getScrollElement,
21762
22323
  getItemKey: getRowItemKey,
@@ -21764,14 +22325,15 @@ function XlsxGrid({
21764
22325
  });
21765
22326
  const colVirtualizer = (0, import_react_virtual.useVirtualizer)({
21766
22327
  count: visibleCols.length,
22328
+ enabled: shouldUseDomVirtualizer,
21767
22329
  estimateSize: estimateColSize,
21768
22330
  getScrollElement,
21769
22331
  getItemKey: getColItemKey,
21770
22332
  horizontal: true,
21771
22333
  overscan: 6
21772
22334
  });
21773
- const currentRowVirtualItems = rowVirtualizer.getVirtualItems();
21774
- const currentColVirtualItems = colVirtualizer.getVirtualItems();
22335
+ const currentRowVirtualItems = shouldUseDomVirtualizer ? rowVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ITEMS;
22336
+ const currentColVirtualItems = shouldUseDomVirtualizer ? colVirtualizer.getVirtualItems() : EMPTY_VIRTUAL_ITEMS;
21775
22337
  const frozenRowVirtualIndices = React4.useMemo(
21776
22338
  () => frozenRows.map((row) => rowIndexByActual.get(row)).filter((index) => index !== void 0),
21777
22339
  [frozenRows, rowIndexByActual]
@@ -22007,10 +22569,12 @@ function XlsxGrid({
22007
22569
  scroller.scrollTop = scroller.scrollTop / previousZoomFactor * zoomFactor;
22008
22570
  }
22009
22571
  previousZoomFactorRef.current = zoomFactor;
22010
- rowVirtualizer.measure();
22011
- colVirtualizer.measure();
22572
+ if (shouldUseDomVirtualizer) {
22573
+ rowVirtualizer.measure();
22574
+ colVirtualizer.measure();
22575
+ }
22012
22576
  syncDrawingViewport(scroller, { immediate: true });
22013
- }, [syncDrawingViewport, zoomFactor]);
22577
+ }, [shouldUseDomVirtualizer, syncDrawingViewport, zoomFactor]);
22014
22578
  React4.useLayoutEffect(() => {
22015
22579
  syncDrawingViewport(scrollRef.current, { immediate: true });
22016
22580
  }, [activeSheet, activeTabIndex, displayColLimit, displayRowLimit, syncDrawingViewport, zoomFactor]);
@@ -22106,7 +22670,7 @@ function XlsxGrid({
22106
22670
  const initialUsedCol = resolveFirstUsedVisibleIndex(visibleCols, activeSheet?.minUsedCol ?? -1);
22107
22671
  const initialScrollTop = initialUsedRow > 0 ? sumPrefixRange(actualRowPrefixSums, 0, initialUsedRow - 1) : 0;
22108
22672
  const initialScrollLeft = initialUsedCol > 0 ? sumPrefixRange(actualColPrefixSums, 0, initialUsedCol - 1) : 0;
22109
- if (shouldVirtualizeRows) {
22673
+ if (shouldUseDomVirtualizer && shouldVirtualizeRows) {
22110
22674
  rowVirtualizer.scrollToOffset(initialScrollTop);
22111
22675
  } else if (scrollRef.current) {
22112
22676
  scrollRef.current.scrollTop = initialScrollTop;
@@ -22114,7 +22678,7 @@ function XlsxGrid({
22114
22678
  if (scrollRef.current) {
22115
22679
  scrollRef.current.scrollLeft = initialScrollLeft;
22116
22680
  }
22117
- if (shouldVirtualizeCols && frozenCols.length === 0) {
22681
+ if (shouldUseDomVirtualizer && shouldVirtualizeCols && frozenCols.length === 0) {
22118
22682
  colVirtualizer.scrollToOffset(initialScrollLeft);
22119
22683
  }
22120
22684
  if (scrollRef.current) {
@@ -22135,6 +22699,7 @@ function XlsxGrid({
22135
22699
  isWorkerBacked,
22136
22700
  shouldVirtualizeCols,
22137
22701
  shouldVirtualizeRows,
22702
+ shouldUseDomVirtualizer,
22138
22703
  syncDrawingViewport,
22139
22704
  rowVirtualizer,
22140
22705
  visibleCols,
@@ -22154,24 +22719,40 @@ function XlsxGrid({
22154
22719
  setPendingNavigation(null);
22155
22720
  }, [activeSheetIndex, pendingNavigation, selectCell]);
22156
22721
  React4.useEffect(() => {
22157
- if (shouldVirtualizeRows) {
22722
+ if (shouldUseDomVirtualizer && shouldVirtualizeRows) {
22158
22723
  rowVirtualizer.measure();
22159
22724
  }
22160
- if (shouldVirtualizeCols) {
22725
+ if (shouldUseDomVirtualizer && shouldVirtualizeCols) {
22161
22726
  colVirtualizer.measure();
22162
22727
  }
22163
- }, [activeSheetIndex, revision, shouldVirtualizeCols, shouldVirtualizeRows, visibleCols.length, visibleRows.length]);
22728
+ }, [
22729
+ activeSheetIndex,
22730
+ revision,
22731
+ shouldUseDomVirtualizer,
22732
+ shouldVirtualizeCols,
22733
+ shouldVirtualizeRows,
22734
+ visibleCols.length,
22735
+ visibleRows.length
22736
+ ]);
22164
22737
  const handleScrollerScroll = React4.useCallback((event) => {
22165
22738
  const currentScroller = event.currentTarget;
22166
22739
  cachedScrollerRectRef.current = null;
22167
- syncDrawingViewport(currentScroller, { immediate: true });
22740
+ const paintedViewport = paintedDrawingViewportRef.current;
22741
+ 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;
22742
+ syncDrawingViewport(currentScroller, { immediate: shouldSyncDrawingViewportImmediately });
22168
22743
  if (currentScroller.scrollHeight - (currentScroller.scrollTop + currentScroller.clientHeight) < OPEN_GRID_VERTICAL_EDGE_PX) {
22169
- setDisplayRowLimit((current) => current + OPEN_GRID_ROW_GROWTH);
22744
+ setDisplayRowLimit((current) => {
22745
+ const nextLimit = current + OPEN_GRID_ROW_GROWTH;
22746
+ return readOnly && current < maxRowDisplayLimit ? Math.min(maxRowDisplayLimit, nextLimit) : nextLimit;
22747
+ });
22170
22748
  }
22171
22749
  if (currentScroller.scrollWidth - (currentScroller.scrollLeft + currentScroller.clientWidth) < OPEN_GRID_HORIZONTAL_EDGE_PX) {
22172
- setDisplayColLimit((current) => current + OPEN_GRID_COL_GROWTH);
22750
+ setDisplayColLimit((current) => {
22751
+ const nextLimit = current + OPEN_GRID_COL_GROWTH;
22752
+ return readOnly && current < maxColDisplayLimit ? Math.min(maxColDisplayLimit, nextLimit) : nextLimit;
22753
+ });
22173
22754
  }
22174
- }, [syncDrawingViewport]);
22755
+ }, [maxColDisplayLimit, maxRowDisplayLimit, readOnly, syncDrawingViewport]);
22175
22756
  React4.useEffect(() => {
22176
22757
  const scroller = scrollRef.current;
22177
22758
  if (!scroller || !enableGestureZoom) {
@@ -23077,14 +23658,14 @@ function XlsxGrid({
23077
23658
  },
23078
23659
  left: {
23079
23660
  cols: frozenColItems,
23080
- rows: frozenRows.length > 0 ? canvasVisibleRowItems : scrollRowItems
23661
+ rows: scrollRowItems
23081
23662
  },
23082
23663
  scroll: {
23083
- cols: frozenCols.length > 0 ? canvasVisibleColItems : scrollColItems,
23084
- rows: frozenRows.length > 0 ? canvasVisibleRowItems : scrollRowItems
23664
+ cols: scrollColItems,
23665
+ rows: scrollRowItems
23085
23666
  },
23086
23667
  top: {
23087
- cols: frozenCols.length > 0 ? canvasVisibleColItems : scrollColItems,
23668
+ cols: scrollColItems,
23088
23669
  rows: frozenRowItems
23089
23670
  }
23090
23671
  };
@@ -23218,6 +23799,51 @@ function XlsxGrid({
23218
23799
  visibleRows
23219
23800
  ]
23220
23801
  );
23802
+ const shouldBakeCanvasStaticDrawings = Boolean(experimentalCanvas && readOnly && showImages);
23803
+ const shouldBakeCanvasImages = Boolean(shouldBakeCanvasStaticDrawings && !renderImage && !renderImageSelection);
23804
+ const bakedShapeRects = React4.useMemo(
23805
+ () => shouldBakeCanvasStaticDrawings ? shapeRects.filter(({ shape }) => !shape.hyperlink) : [],
23806
+ [shapeRects, shouldBakeCanvasStaticDrawings]
23807
+ );
23808
+ const domShapeRects = React4.useMemo(
23809
+ () => shouldBakeCanvasStaticDrawings ? shapeRects.filter(({ shape }) => shape.hyperlink) : shapeRects,
23810
+ [shapeRects, shouldBakeCanvasStaticDrawings]
23811
+ );
23812
+ const bakedFormControlRects = React4.useMemo(
23813
+ () => shouldBakeCanvasStaticDrawings ? formControlRects : [],
23814
+ [formControlRects, shouldBakeCanvasStaticDrawings]
23815
+ );
23816
+ const domFormControlRects = React4.useMemo(
23817
+ () => shouldBakeCanvasStaticDrawings ? [] : formControlRects,
23818
+ [formControlRects, shouldBakeCanvasStaticDrawings]
23819
+ );
23820
+ const bakedImageRects = React4.useMemo(
23821
+ () => shouldBakeCanvasImages ? imageRects.filter(({ image }) => !image.hyperlink && selectedImageId !== image.id) : [],
23822
+ [imageRects, selectedImageId, shouldBakeCanvasImages]
23823
+ );
23824
+ const domImageRects = React4.useMemo(
23825
+ () => shouldBakeCanvasImages ? imageRects.filter(({ image }) => image.hyperlink || selectedImageId === image.id) : imageRects,
23826
+ [imageRects, selectedImageId, shouldBakeCanvasImages]
23827
+ );
23828
+ const hasCanvasDomDrawingOverlays = Boolean(
23829
+ showImages && (chartRects.length > 0 || domShapeRects.length > 0 || domFormControlRects.length > 0 || domImageRects.length > 0)
23830
+ );
23831
+ const bakedCanvasDrawingSignature = React4.useMemo(() => {
23832
+ if (!shouldBakeCanvasStaticDrawings) {
23833
+ return "";
23834
+ }
23835
+ 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("|");
23836
+ 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("|");
23837
+ 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("|");
23838
+ return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${controlSignature}:${imageSignature}`;
23839
+ }, [
23840
+ bakedFormControlRects,
23841
+ bakedImageRects,
23842
+ bakedShapeRects,
23843
+ canvasImageLoadVersion,
23844
+ revision,
23845
+ shouldBakeCanvasStaticDrawings
23846
+ ]);
23221
23847
  const resolveMountedCellOverlayRect = React4.useCallback((element) => {
23222
23848
  const wrapper = wrapperRef.current;
23223
23849
  if (!wrapper) {
@@ -24086,6 +24712,24 @@ function XlsxGrid({
24086
24712
  rowPrefixSums,
24087
24713
  stickyTopByRow
24088
24714
  ]);
24715
+ const scrollBodyViewportWidth = Math.max(0, drawingViewport.width - frozenPaneRight);
24716
+ const scrollBodyViewportHeight = Math.max(0, drawingViewport.height - frozenPaneBottom);
24717
+ const canvasScrollBufferX = Math.min(CANVAS_SCROLL_BUFFER_PX, scrollBodyViewportWidth);
24718
+ const canvasScrollBufferY = Math.min(CANVAS_SCROLL_BUFFER_PX, scrollBodyViewportHeight);
24719
+ const scrollBodyCanvasLeft = frozenPaneRight - canvasScrollBufferX;
24720
+ const scrollBodyCanvasTop = frozenPaneBottom - canvasScrollBufferY;
24721
+ const scrollBodyCanvasWidth = scrollBodyViewportWidth + canvasScrollBufferX * 2;
24722
+ const scrollBodyCanvasHeight = scrollBodyViewportHeight + canvasScrollBufferY * 2;
24723
+ const topBodyCanvasWidth = scrollBodyCanvasWidth;
24724
+ const topBodyCanvasHeight = Math.max(0, frozenPaneBottom - displayHeaderHeight);
24725
+ const leftBodyCanvasWidth = Math.max(0, frozenPaneRight - displayRowHeaderWidth);
24726
+ const leftBodyCanvasHeight = scrollBodyCanvasHeight;
24727
+ const cornerBodyCanvasWidth = leftBodyCanvasWidth;
24728
+ const cornerBodyCanvasHeight = topBodyCanvasHeight;
24729
+ const topFrozenHeaderCanvasWidth = leftBodyCanvasWidth;
24730
+ const topScrollHeaderCanvasWidth = scrollBodyCanvasWidth;
24731
+ const leftFrozenHeaderCanvasHeight = topBodyCanvasHeight;
24732
+ const leftScrollHeaderCanvasHeight = scrollBodyCanvasHeight;
24089
24733
  const canvasColumnHeaderCells = React4.useMemo(
24090
24734
  () => canvasVisibleColItems.flatMap((column) => {
24091
24735
  const rect = resolveCanvasColumnHeaderRect(column.actualCol);
@@ -24098,7 +24742,7 @@ function XlsxGrid({
24098
24742
  height: displayHeaderHeight,
24099
24743
  isFrozen,
24100
24744
  left: rect.left,
24101
- localLeft: rect.left - (isFrozen ? displayRowHeaderWidth : frozenPaneRight),
24745
+ localLeft: rect.left - (isFrozen ? displayRowHeaderWidth : scrollBodyCanvasLeft),
24102
24746
  width: rect.width
24103
24747
  }];
24104
24748
  }),
@@ -24106,8 +24750,8 @@ function XlsxGrid({
24106
24750
  canvasVisibleColItems,
24107
24751
  displayHeaderHeight,
24108
24752
  displayRowHeaderWidth,
24109
- frozenPaneRight,
24110
24753
  resolveCanvasColumnHeaderRect,
24754
+ scrollBodyCanvasLeft,
24111
24755
  stickyLeftByCol
24112
24756
  ]
24113
24757
  );
@@ -24122,15 +24766,15 @@ function XlsxGrid({
24122
24766
  actualRow: row.actualRow,
24123
24767
  height: rect.height,
24124
24768
  isFrozen,
24125
- localTop: rect.top - (isFrozen ? displayHeaderHeight : frozenPaneBottom),
24769
+ localTop: rect.top - (isFrozen ? displayHeaderHeight : scrollBodyCanvasTop),
24126
24770
  top: rect.top
24127
24771
  }];
24128
24772
  }),
24129
24773
  [
24130
24774
  canvasVisibleRowItems,
24131
24775
  displayHeaderHeight,
24132
- frozenPaneBottom,
24133
24776
  resolveCanvasRowHeaderRect,
24777
+ scrollBodyCanvasTop,
24134
24778
  stickyTopByRow
24135
24779
  ]
24136
24780
  );
@@ -24397,10 +25041,46 @@ function XlsxGrid({
24397
25041
  rowPrefixSums,
24398
25042
  startCellSelection
24399
25043
  ]);
25044
+ const getCanvasImage = React4.useCallback((image) => {
25045
+ if (typeof Image === "undefined") {
25046
+ return null;
25047
+ }
25048
+ const cacheKey = image.id;
25049
+ const cached = canvasImageCacheRef.current.get(cacheKey);
25050
+ if (cached && cached.src === image.src) {
25051
+ return cached.loaded && !cached.failed ? cached.image : null;
25052
+ }
25053
+ const imageElement = new Image();
25054
+ const entry = {
25055
+ failed: false,
25056
+ image: imageElement,
25057
+ loaded: false,
25058
+ src: image.src
25059
+ };
25060
+ canvasImageCacheRef.current.set(cacheKey, entry);
25061
+ imageElement.onload = () => {
25062
+ entry.loaded = true;
25063
+ setCanvasImageLoadVersion((version) => version + 1);
25064
+ };
25065
+ imageElement.onerror = () => {
25066
+ entry.failed = true;
25067
+ setCanvasImageLoadVersion((version) => version + 1);
25068
+ };
25069
+ imageElement.src = image.src;
25070
+ return null;
25071
+ }, []);
24400
25072
  React4.useLayoutEffect(() => {
24401
25073
  if (!experimentalCanvas) {
24402
25074
  return;
24403
25075
  }
25076
+ const canvasProfileTarget = typeof window !== "undefined" ? window.__xlsxCanvasProfile : void 0;
25077
+ const canvasProfileStart = canvasProfileTarget ? performance.now() : 0;
25078
+ let canvasProfileBodyStart = 0;
25079
+ let canvasProfileBodyMs = 0;
25080
+ let canvasProfileCulledCells = 0;
25081
+ let canvasProfileDirtyRects = 0;
25082
+ let canvasProfileLookedUpCells = 0;
25083
+ let canvasProfilePaintedCells = 0;
24404
25084
  const dpr = typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
24405
25085
  function configureCanvas(canvas, width, height, options) {
24406
25086
  if (!canvas) {
@@ -24420,7 +25100,7 @@ function XlsxGrid({
24420
25100
  if (canvas.style.height !== `${height}px`) {
24421
25101
  canvas.style.height = `${height}px`;
24422
25102
  }
24423
- const context = canvas.getContext("2d");
25103
+ const context = canvas.getContext("2d", { alpha: false });
24424
25104
  if (!context) {
24425
25105
  return null;
24426
25106
  }
@@ -24439,6 +25119,7 @@ function XlsxGrid({
24439
25119
  const rangeSignature = buildRangeSignature(normalizedVisibleRange);
24440
25120
  const nextBodyCanvasSignature = {
24441
25121
  activeSheet: activeSheet ?? null,
25122
+ bakedDrawingSignature: bakedCanvasDrawingSignature,
24442
25123
  bodyHeight,
24443
25124
  bodyWidth,
24444
25125
  colSignature: buildRenderedAxisSignature(canvasVisibleColItems, (item) => item.index),
@@ -24466,10 +25147,10 @@ function XlsxGrid({
24466
25147
  const previousBodyCanvasSignature = paintedBodyCanvasSignatureRef.current;
24467
25148
  const previousHeaderCanvasSignature = paintedHeaderCanvasSignatureRef.current;
24468
25149
  const previousPaintedViewport = paintedDrawingViewportRef.current;
24469
- 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);
25150
+ 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);
24470
25151
  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);
24471
25152
  const canBlitBody = Boolean(
24472
- 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
25153
+ 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
24473
25154
  );
24474
25155
  const canBlitTopHeader = Boolean(
24475
25156
  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
@@ -24480,49 +25161,37 @@ function XlsxGrid({
24480
25161
  if (!shouldRepaintBody && !shouldRepaintHeaders) {
24481
25162
  return;
24482
25163
  }
24483
- const scrollBodyCanvasWidth2 = Math.max(0, drawingViewport.width - frozenPaneRight);
24484
- const scrollBodyCanvasHeight2 = Math.max(0, drawingViewport.height - frozenPaneBottom);
24485
- const topBodyCanvasWidth2 = scrollBodyCanvasWidth2;
24486
- const topBodyCanvasHeight2 = Math.max(0, frozenPaneBottom - displayHeaderHeight);
24487
- const leftBodyCanvasWidth2 = Math.max(0, frozenPaneRight - displayRowHeaderWidth);
24488
- const leftBodyCanvasHeight2 = scrollBodyCanvasHeight2;
24489
- const cornerBodyCanvasWidth2 = leftBodyCanvasWidth2;
24490
- const cornerBodyCanvasHeight2 = topBodyCanvasHeight2;
24491
- const topFrozenHeaderCanvasWidth2 = leftBodyCanvasWidth2;
24492
- const topScrollHeaderCanvasWidth2 = scrollBodyCanvasWidth2;
24493
- const leftFrozenHeaderCanvasHeight2 = topBodyCanvasHeight2;
24494
- const leftScrollHeaderCanvasHeight2 = scrollBodyCanvasHeight2;
24495
25164
  const paneBounds = {
24496
25165
  corner: {
24497
- height: cornerBodyCanvasHeight2,
25166
+ height: cornerBodyCanvasHeight,
24498
25167
  left: displayRowHeaderWidth,
24499
25168
  top: displayHeaderHeight,
24500
- width: cornerBodyCanvasWidth2
25169
+ width: cornerBodyCanvasWidth
24501
25170
  },
24502
25171
  left: {
24503
- height: leftBodyCanvasHeight2,
25172
+ height: leftBodyCanvasHeight,
24504
25173
  left: displayRowHeaderWidth,
24505
- top: frozenPaneBottom,
24506
- width: leftBodyCanvasWidth2
25174
+ top: scrollBodyCanvasTop,
25175
+ width: leftBodyCanvasWidth
24507
25176
  },
24508
25177
  scroll: {
24509
- height: scrollBodyCanvasHeight2,
24510
- left: frozenPaneRight,
24511
- top: frozenPaneBottom,
24512
- width: scrollBodyCanvasWidth2
25178
+ height: scrollBodyCanvasHeight,
25179
+ left: scrollBodyCanvasLeft,
25180
+ top: scrollBodyCanvasTop,
25181
+ width: scrollBodyCanvasWidth
24513
25182
  },
24514
25183
  top: {
24515
- height: topBodyCanvasHeight2,
24516
- left: frozenPaneRight,
25184
+ height: topBodyCanvasHeight,
25185
+ left: scrollBodyCanvasLeft,
24517
25186
  top: displayHeaderHeight,
24518
- width: topBodyCanvasWidth2
25187
+ width: topBodyCanvasWidth
24519
25188
  }
24520
25189
  };
24521
25190
  const bodyContexts = shouldRepaintBody ? {
24522
- corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth2, cornerBodyCanvasHeight2, { clear: !canBlitBody }),
24523
- left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth2, leftBodyCanvasHeight2, { clear: !canBlitBody }),
24524
- scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth2, scrollBodyCanvasHeight2, { clear: !canBlitBody }),
24525
- top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth2, topBodyCanvasHeight2, { clear: !canBlitBody })
25191
+ corner: configureCanvas(cornerBodyCanvasRef.current, cornerBodyCanvasWidth, cornerBodyCanvasHeight, { clear: !canBlitBody }),
25192
+ left: configureCanvas(leftBodyCanvasRef.current, leftBodyCanvasWidth, leftBodyCanvasHeight, { clear: !canBlitBody }),
25193
+ scroll: configureCanvas(scrollBodyCanvasRef.current, scrollBodyCanvasWidth, scrollBodyCanvasHeight, { clear: !canBlitBody }),
25194
+ top: configureCanvas(topBodyCanvasRef.current, topBodyCanvasWidth, topBodyCanvasHeight, { clear: !canBlitBody })
24526
25195
  } : {
24527
25196
  corner: null,
24528
25197
  left: null,
@@ -24530,21 +25199,21 @@ function XlsxGrid({
24530
25199
  top: null
24531
25200
  };
24532
25201
  const topHeaderContexts = shouldRepaintHeaders ? {
24533
- frozen: configureCanvas(topFrozenHeaderCanvasRef.current, topFrozenHeaderCanvasWidth2, headerHeight),
24534
- scroll: configureCanvas(topScrollHeaderCanvasRef.current, topScrollHeaderCanvasWidth2, headerHeight, { clear: !canBlitTopHeader })
25202
+ frozen: configureCanvas(topFrozenHeaderCanvasRef.current, topFrozenHeaderCanvasWidth, headerHeight),
25203
+ scroll: configureCanvas(topScrollHeaderCanvasRef.current, topScrollHeaderCanvasWidth, headerHeight, { clear: !canBlitTopHeader })
24535
25204
  } : {
24536
25205
  frozen: null,
24537
25206
  scroll: null
24538
25207
  };
24539
25208
  const leftHeaderContexts = shouldRepaintHeaders ? {
24540
- frozen: configureCanvas(leftFrozenHeaderCanvasRef.current, rowHeaderWidth, leftFrozenHeaderCanvasHeight2),
24541
- scroll: configureCanvas(leftScrollHeaderCanvasRef.current, rowHeaderWidth, leftScrollHeaderCanvasHeight2, { clear: !canBlitLeftHeader })
25209
+ frozen: configureCanvas(leftFrozenHeaderCanvasRef.current, rowHeaderWidth, leftFrozenHeaderCanvasHeight),
25210
+ scroll: configureCanvas(leftScrollHeaderCanvasRef.current, rowHeaderWidth, leftScrollHeaderCanvasHeight, { clear: !canBlitLeftHeader })
24542
25211
  } : {
24543
25212
  frozen: null,
24544
25213
  scroll: null
24545
25214
  };
24546
25215
  const cornerContext = shouldRepaintHeaders ? configureCanvas(cornerHeaderCanvasRef.current, rowHeaderWidth, headerHeight) : null;
24547
- 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)) {
25216
+ 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)) {
24548
25217
  return;
24549
25218
  }
24550
25219
  const showGridLines = activeSheet?.showGridLines ?? true;
@@ -24561,10 +25230,282 @@ function XlsxGrid({
24561
25230
  scroll: [],
24562
25231
  top: []
24563
25232
  };
24564
- let topScrollHeaderDirtyRects = buildFullCanvasDirtyRect(topScrollHeaderCanvasWidth2, headerHeight);
24565
- let leftScrollHeaderDirtyRects = buildFullCanvasDirtyRect(rowHeaderWidth, leftScrollHeaderCanvasHeight2);
25233
+ let topScrollHeaderDirtyRects = buildFullCanvasDirtyRect(topScrollHeaderCanvasWidth, headerHeight);
25234
+ let leftScrollHeaderDirtyRects = buildFullCanvasDirtyRect(rowHeaderWidth, leftScrollHeaderCanvasHeight);
25235
+ const bakedCanvasDrawingEntries = shouldBakeCanvasStaticDrawings ? [
25236
+ ...bakedShapeRects.map(({ rect, shape }) => ({
25237
+ kind: "shape",
25238
+ rect,
25239
+ shape,
25240
+ zIndex: shape.zIndex
25241
+ })),
25242
+ ...bakedFormControlRects.map(({ control, rect }) => ({
25243
+ control,
25244
+ kind: "formControl",
25245
+ rect,
25246
+ zIndex: control.zIndex
25247
+ })),
25248
+ ...bakedImageRects.map(({ image, rect }) => ({
25249
+ image,
25250
+ kind: "image",
25251
+ rect,
25252
+ zIndex: image.zIndex
25253
+ }))
25254
+ ].sort((left, right) => left.zIndex - right.zIndex) : [];
25255
+ const resolveBakedCanvasLocalRect = (rect, pane) => {
25256
+ const bounds = paneBounds[pane];
25257
+ const scrollX = pane === "scroll" || pane === "top" ? drawingViewport.left : 0;
25258
+ const scrollY = pane === "scroll" || pane === "left" ? drawingViewport.top : 0;
25259
+ return {
25260
+ height: rect.height,
25261
+ left: rect.left - scrollX - bounds.left,
25262
+ top: rect.top - scrollY - bounds.top,
25263
+ width: rect.width
25264
+ };
25265
+ };
25266
+ const drawBakedShapeText = (context, shape, left, top, width, height) => {
25267
+ if (shape.paragraphs.length === 0) {
25268
+ return;
25269
+ }
25270
+ const inset = shape.textBox?.insetPx;
25271
+ const paddingLeft = (inset?.left ?? 6) * zoomFactor;
25272
+ const paddingRight = (inset?.right ?? 6) * zoomFactor;
25273
+ const paddingTop = (inset?.top ?? 4) * zoomFactor;
25274
+ const paddingBottom = (inset?.bottom ?? 4) * zoomFactor;
25275
+ const textLeft = left + paddingLeft;
25276
+ const textTop = top + paddingTop;
25277
+ const textWidth = Math.max(0, width - paddingLeft - paddingRight);
25278
+ const textHeight = Math.max(0, height - paddingTop - paddingBottom);
25279
+ if (textWidth <= 0 || textHeight <= 0) {
25280
+ return;
25281
+ }
25282
+ const lineMetrics = shape.paragraphs.map((paragraph) => {
25283
+ const lineHeight = Math.max(
25284
+ 12 * zoomFactor,
25285
+ ...paragraph.runs.map((run) => (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor * 1.2)
25286
+ );
25287
+ const widthPx = paragraph.runs.reduce((total, run) => {
25288
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
25289
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
25290
+ return total + measureCanvasTextWidth(context, run.text);
25291
+ }, 0);
25292
+ return { lineHeight, widthPx };
25293
+ });
25294
+ const totalTextHeight = lineMetrics.reduce((total, metric) => total + metric.lineHeight, 0);
25295
+ let y = textTop;
25296
+ if (shape.textBox?.verticalAlign === "middle") {
25297
+ y = textTop + Math.max(0, (textHeight - totalTextHeight) / 2);
25298
+ } else if (shape.textBox?.verticalAlign === "bottom") {
25299
+ y = textTop + Math.max(0, textHeight - totalTextHeight);
25300
+ }
25301
+ context.save();
25302
+ context.beginPath();
25303
+ context.rect(textLeft, textTop, textWidth, textHeight);
25304
+ context.clip();
25305
+ context.textBaseline = "middle";
25306
+ shape.paragraphs.forEach((paragraph, paragraphIndex) => {
25307
+ const metric = lineMetrics[paragraphIndex] ?? { lineHeight: 14 * zoomFactor, widthPx: 0 };
25308
+ let x = textLeft;
25309
+ const align = paragraph.align ?? shape.textBox?.horizontalAlign ?? "left";
25310
+ if (align === "center") {
25311
+ x = textLeft + Math.max(0, (textWidth - metric.widthPx) / 2);
25312
+ } else if (align === "right") {
25313
+ x = textLeft + Math.max(0, textWidth - metric.widthPx);
25314
+ }
25315
+ paragraph.runs.forEach((run) => {
25316
+ const fontSize = (run.fontSizePt ?? 11) * 96 / 72 * zoomFactor;
25317
+ context.font = `${run.italic ? "italic " : ""}${run.bold ? "700 " : "400 "}${fontSize}px ${run.fontFamily ?? "Calibri, sans-serif"}`;
25318
+ context.fillStyle = run.color ?? "#000000";
25319
+ context.textAlign = "left";
25320
+ const textY = y + metric.lineHeight / 2;
25321
+ context.fillText(run.text, x, textY);
25322
+ if (run.underline && run.text.length > 0) {
25323
+ const textWidthPx = measureCanvasTextWidth(context, run.text);
25324
+ context.strokeStyle = run.color ?? "#000000";
25325
+ context.lineWidth = Math.max(1, zoomFactor * 0.75);
25326
+ context.beginPath();
25327
+ context.moveTo(x, textY + Math.max(2, fontSize * 0.28));
25328
+ context.lineTo(x + textWidthPx, textY + Math.max(2, fontSize * 0.28));
25329
+ context.stroke();
25330
+ }
25331
+ x += measureCanvasTextWidth(context, run.text);
25332
+ });
25333
+ y += metric.lineHeight;
25334
+ });
25335
+ context.restore();
25336
+ };
25337
+ const drawBakedShape = (context, shape, rect) => {
25338
+ const fillColor = shape.fill?.none ? "transparent" : shape.fill?.color ?? "transparent";
25339
+ const strokeColor = shape.stroke?.none ? "transparent" : shape.stroke?.color ?? "transparent";
25340
+ const lineWidth = Math.max(0, (shape.stroke?.widthPx ?? (shape.geometry === "line" ? 2 : 1)) * zoomFactor);
25341
+ const vectorShape = resolveShapeVector(shape);
25342
+ const opacity = Math.min(shape.fill?.opacity ?? 1, shape.stroke?.opacity ?? 1);
25343
+ context.save();
25344
+ context.translate(rect.left + rect.width / 2, rect.top + rect.height / 2);
25345
+ if (shape.rotationDeg) {
25346
+ context.rotate(shape.rotationDeg * Math.PI / 180);
25347
+ }
25348
+ context.scale(shape.flipH ? -1 : 1, shape.flipV ? -1 : 1);
25349
+ context.globalAlpha *= opacity;
25350
+ context.lineWidth = lineWidth;
25351
+ context.strokeStyle = strokeColor;
25352
+ context.fillStyle = fillColor;
25353
+ applyCanvasShapeDash(context, shape.stroke?.dash, lineWidth);
25354
+ const localLeft = -rect.width / 2;
25355
+ const localTop = -rect.height / 2;
25356
+ if (vectorShape && typeof Path2D !== "undefined") {
25357
+ context.save();
25358
+ context.translate(localLeft, localTop);
25359
+ context.scale(
25360
+ rect.width / Math.max(1, vectorShape.viewBox.width),
25361
+ rect.height / Math.max(1, vectorShape.viewBox.height)
25362
+ );
25363
+ const path = getCachedCanvasPath2D(vectorShape.path);
25364
+ if (!path) {
25365
+ context.restore();
25366
+ context.restore();
25367
+ return;
25368
+ }
25369
+ if (fillColor !== "transparent") {
25370
+ context.fill(path);
25371
+ }
25372
+ if (strokeColor !== "transparent" && lineWidth > 0) {
25373
+ context.stroke(path);
25374
+ }
25375
+ context.restore();
25376
+ } else if (shape.geometry === "ellipse") {
25377
+ context.beginPath();
25378
+ context.ellipse(0, 0, rect.width / 2, rect.height / 2, 0, 0, Math.PI * 2);
25379
+ if (fillColor !== "transparent") {
25380
+ context.fill();
25381
+ }
25382
+ if (strokeColor !== "transparent" && lineWidth > 0) {
25383
+ context.stroke();
25384
+ }
25385
+ } else {
25386
+ const radius = shape.geometry === "roundRect" ? 12 * zoomFactor : 0;
25387
+ drawCanvasRoundedRect(context, localLeft, localTop, rect.width, rect.height, radius);
25388
+ if (fillColor !== "transparent") {
25389
+ context.fill();
25390
+ }
25391
+ if (strokeColor !== "transparent" && lineWidth > 0) {
25392
+ context.stroke();
25393
+ }
25394
+ }
25395
+ drawBakedShapeText(context, shape, localLeft, localTop, rect.width, rect.height);
25396
+ context.restore();
25397
+ };
25398
+ const drawBakedFormControl = (context, control, rect) => {
25399
+ const label = resolveFormControlLabel(control);
25400
+ const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
25401
+ const textColor = control.textColor ?? "#000000";
25402
+ const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
25403
+ const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
25404
+ context.save();
25405
+ context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
25406
+ context.textBaseline = "middle";
25407
+ context.fillStyle = textColor;
25408
+ context.strokeStyle = stroke;
25409
+ context.lineWidth = Math.max(1, zoomFactor);
25410
+ if (control.kind === "group-box") {
25411
+ const labelInset = label ? Math.max(7, fontSizePx * 0.5) : 0;
25412
+ drawCanvasRoundedRect(context, rect.left, rect.top + labelInset, rect.width, Math.max(0, rect.height - labelInset), 2 * zoomFactor);
25413
+ context.stroke();
25414
+ if (label) {
25415
+ context.fillStyle = SHEET_SURFACE;
25416
+ const labelWidth = Math.min(rect.width - 16 * zoomFactor, measureCanvasTextWidth(context, label) + 8 * zoomFactor);
25417
+ context.fillRect(rect.left + 8 * zoomFactor, rect.top, labelWidth, fontSizePx * 1.2);
25418
+ context.fillStyle = textColor;
25419
+ context.textAlign = "left";
25420
+ context.fillText(label, rect.left + 12 * zoomFactor, rect.top + fontSizePx * 0.55);
25421
+ }
25422
+ context.restore();
25423
+ return;
25424
+ }
25425
+ if (control.kind === "button" || control.kind === "dropdown" || control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
25426
+ if (control.kind === "button") {
25427
+ const gradient = context.createLinearGradient(rect.left, rect.top, rect.left, rect.top + rect.height);
25428
+ gradient.addColorStop(0, "#f8fafc");
25429
+ gradient.addColorStop(1, "#e2e8f0");
25430
+ context.fillStyle = gradient;
25431
+ } else {
25432
+ context.fillStyle = "transparent";
25433
+ }
25434
+ drawCanvasRoundedRect(context, rect.left, rect.top, rect.width, rect.height, control.kind === "button" ? 4 * zoomFactor : 2 * zoomFactor);
25435
+ if (control.kind === "button") {
25436
+ context.fill();
25437
+ }
25438
+ context.stroke();
25439
+ }
25440
+ let textLeft = rect.left + 2 * zoomFactor;
25441
+ const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
25442
+ if (control.kind === "checkbox") {
25443
+ context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
25444
+ if (control.checked) {
25445
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
25446
+ 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));
25447
+ context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
25448
+ context.beginPath();
25449
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
25450
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
25451
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
25452
+ context.stroke();
25453
+ }
25454
+ textLeft += iconSize + 4 * zoomFactor;
25455
+ } else if (control.kind === "radio") {
25456
+ context.beginPath();
25457
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
25458
+ context.stroke();
25459
+ if (control.checked) {
25460
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
25461
+ context.beginPath();
25462
+ context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
25463
+ context.fill();
25464
+ }
25465
+ textLeft += iconSize + 4 * zoomFactor;
25466
+ }
25467
+ if (label) {
25468
+ const maxTextWidth = Math.max(0, rect.left + rect.width - textLeft - textRightInset);
25469
+ const text = truncateCanvasText(context, label, maxTextWidth);
25470
+ context.fillStyle = textColor;
25471
+ context.textAlign = control.textAlign === "right" ? "right" : control.textAlign === "center" ? "center" : "left";
25472
+ const textX = context.textAlign === "right" ? rect.left + rect.width - textRightInset : context.textAlign === "center" ? textLeft + maxTextWidth / 2 : textLeft;
25473
+ context.fillText(text, textX, rect.top + rect.height / 2);
25474
+ }
25475
+ if (control.kind === "dropdown") {
25476
+ context.fillStyle = textColor;
25477
+ context.textAlign = "center";
25478
+ context.fillText("\u25BC", rect.left + rect.width - 10 * zoomFactor, rect.top + rect.height / 2);
25479
+ }
25480
+ context.restore();
25481
+ };
25482
+ const drawBakedCanvasDrawings = (pane, context, dirtyRects) => {
25483
+ if (bakedCanvasDrawingEntries.length === 0 || dirtyRects.length === 0) {
25484
+ return;
25485
+ }
25486
+ for (const entry of bakedCanvasDrawingEntries) {
25487
+ if (resolveDrawingPane(entry.rect) !== pane) {
25488
+ continue;
25489
+ }
25490
+ const localRect = resolveBakedCanvasLocalRect(entry.rect, pane);
25491
+ 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)) {
25492
+ continue;
25493
+ }
25494
+ if (entry.kind === "shape") {
25495
+ drawBakedShape(context, entry.shape, localRect);
25496
+ } else if (entry.kind === "formControl") {
25497
+ drawBakedFormControl(context, entry.control, localRect);
25498
+ } else {
25499
+ const imageElement = getCanvasImage(entry.image);
25500
+ if (imageElement) {
25501
+ context.drawImage(imageElement, localRect.left, localRect.top, localRect.width, localRect.height);
25502
+ }
25503
+ }
25504
+ }
25505
+ };
24566
25506
  const cellPaneOrder = ["scroll", "top", "left", "corner"];
24567
25507
  if (shouldRepaintBody) {
25508
+ canvasProfileBodyStart = canvasProfileTarget ? performance.now() : 0;
24568
25509
  for (const pane of Object.keys(bodyContexts)) {
24569
25510
  const context = bodyContexts[pane];
24570
25511
  const bounds = paneBounds[pane];
@@ -24595,6 +25536,7 @@ function XlsxGrid({
24595
25536
  context.clearRect(0, 0, bounds.width, bounds.height);
24596
25537
  }
24597
25538
  bodyDirtyRectsByPane[pane] = dirtyRects;
25539
+ canvasProfileDirtyRects += dirtyRects.length;
24598
25540
  if (dirtyRects.length === 0) {
24599
25541
  continue;
24600
25542
  }
@@ -24611,6 +25553,22 @@ function XlsxGrid({
24611
25553
  if (!paneContext || paneDirtyRects.length === 0 || paneBoundsForCell.width <= 0 || paneBoundsForCell.height <= 0 || paneAxisItems.rows.length === 0 || paneAxisItems.cols.length === 0) {
24612
25554
  continue;
24613
25555
  }
25556
+ let hasPendingGridlinePath = false;
25557
+ const enqueueGridlinePath = () => {
25558
+ if (!hasPendingGridlinePath) {
25559
+ paneContext.beginPath();
25560
+ hasPendingGridlinePath = true;
25561
+ }
25562
+ };
25563
+ const flushPendingGridlines = () => {
25564
+ if (!hasPendingGridlinePath) {
25565
+ return;
25566
+ }
25567
+ paneContext.strokeStyle = palette.border;
25568
+ paneContext.lineWidth = 1;
25569
+ paneContext.stroke();
25570
+ hasPendingGridlinePath = false;
25571
+ };
24614
25572
  const drawnMergedAnchorKeys = /* @__PURE__ */ new Set();
24615
25573
  for (const rowItem of paneAxisItems.rows) {
24616
25574
  for (const colItem of paneAxisItems.cols) {
@@ -24618,6 +25576,33 @@ function XlsxGrid({
24618
25576
  const anchorCell = resolveMergeAnchorCell(cell);
24619
25577
  const anchorKey = `${anchorCell.row}:${anchorCell.col}`;
24620
25578
  let drawCell = anchorCell;
25579
+ const drawRowIndex = rowIndexByActual.get(drawCell.row);
25580
+ const drawColIndex = colIndexByActual.get(drawCell.col);
25581
+ if (drawRowIndex === void 0 || drawColIndex === void 0) {
25582
+ continue;
25583
+ }
25584
+ const baseCellLeft = displayRowHeaderWidth + (colPrefixSums[drawColIndex] ?? 0);
25585
+ const baseCellTop = displayHeaderHeight + (rowPrefixSums[drawRowIndex] ?? 0);
25586
+ const useFrozenHorizontalPosition = pane === "left" || pane === "corner";
25587
+ const useFrozenVerticalPosition = pane === "top" || pane === "corner";
25588
+ const roughLocalRect = {
25589
+ height: displayEffectiveRowHeights[drawRowIndex] ?? rowItem.size,
25590
+ left: (useFrozenHorizontalPosition ? stickyLeftByCol.get(drawCell.col) ?? baseCellLeft - drawingViewport.left : baseCellLeft - drawingViewport.left) - paneBoundsForCell.left,
25591
+ top: (useFrozenVerticalPosition ? stickyTopByRow.get(drawCell.row) ?? baseCellTop - drawingViewport.top : baseCellTop - drawingViewport.top) - paneBoundsForCell.top,
25592
+ width: displayEffectiveColWidths[drawColIndex] ?? colItem.size
25593
+ };
25594
+ const isMergedSecondaryProbe = anchorCell.row !== cell.row || anchorCell.col !== cell.col;
25595
+ if (!isMergedSecondaryProbe && !intersectsCanvasDirtyRects(
25596
+ roughLocalRect.left - CANVAS_DIRTY_CELL_CULL_MARGIN_PX,
25597
+ roughLocalRect.top - CANVAS_DIRTY_CELL_CULL_MARGIN_PX,
25598
+ roughLocalRect.width + CANVAS_DIRTY_CELL_CULL_MARGIN_PX * 2,
25599
+ roughLocalRect.height + CANVAS_DIRTY_CELL_CULL_MARGIN_PX * 2,
25600
+ paneDirtyRects
25601
+ )) {
25602
+ canvasProfileCulledCells += 1;
25603
+ continue;
25604
+ }
25605
+ canvasProfileLookedUpCells += 1;
24621
25606
  let cellData = getCellData(drawCell.row, drawCell.col);
24622
25607
  if ((cellData.colSpan || cellData.rowSpan) && drawnMergedAnchorKeys.has(anchorKey)) {
24623
25608
  continue;
@@ -24628,16 +25613,9 @@ function XlsxGrid({
24628
25613
  if (cellData.colSpan || cellData.rowSpan) {
24629
25614
  drawnMergedAnchorKeys.add(anchorKey);
24630
25615
  }
24631
- const drawRowIndex = rowIndexByActual.get(drawCell.row);
24632
- const drawColIndex = colIndexByActual.get(drawCell.col);
24633
- if (drawRowIndex === void 0 || drawColIndex === void 0) {
24634
- continue;
24635
- }
24636
25616
  const displayRect = cellData.colSpan || cellData.rowSpan ? resolveCellDisplayRect(drawCell) : null;
24637
- const baseLeft = displayRect?.left ?? displayRowHeaderWidth + (colPrefixSums[drawColIndex] ?? 0);
24638
- const baseTop = displayRect?.top ?? displayHeaderHeight + (rowPrefixSums[drawRowIndex] ?? 0);
24639
- const useFrozenHorizontalPosition = pane === "left" || pane === "corner";
24640
- const useFrozenVerticalPosition = pane === "top" || pane === "corner";
25617
+ const baseLeft = displayRect?.left ?? baseCellLeft;
25618
+ const baseTop = displayRect?.top ?? baseCellTop;
24641
25619
  const localRect = {
24642
25620
  height: displayRect?.height ?? (displayEffectiveRowHeights[drawRowIndex] ?? rowItem.size),
24643
25621
  left: (useFrozenHorizontalPosition ? stickyLeftByCol.get(drawCell.col) ?? baseLeft - drawingViewport.left : baseLeft - drawingViewport.left) - paneBoundsForCell.left,
@@ -24649,13 +25627,18 @@ function XlsxGrid({
24649
25627
  continue;
24650
25628
  }
24651
25629
  if (!intersectsCanvasDirtyRects(localRect.left, localRect.top, drawableWidth, localRect.height, paneDirtyRects)) {
25630
+ canvasProfileCulledCells += 1;
24652
25631
  continue;
24653
25632
  }
25633
+ canvasProfilePaintedCells += 1;
24654
25634
  const cellStyle = cellData.style;
24655
25635
  const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellStyle);
24656
25636
  const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellStyle.backgroundColor === "string" ? cellStyle.backgroundColor : sheetSurface);
24657
25637
  const gradientFill = !cellData.conditionalColorScale && typeof cellStyle.backgroundImage === "string" ? resolveCanvasGradientFill(paneContext, localRect, cellStyle.backgroundImage) : null;
24658
25638
  const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellStyle.backgroundColor === "string" && cellStyle.backgroundColor !== sheetSurface;
25639
+ if (hasExplicitCellFill || cellData.chartHighlight) {
25640
+ flushPendingGridlines();
25641
+ }
24659
25642
  paneContext.fillStyle = gradientFill ?? fillColor;
24660
25643
  paneContext.fillRect(localRect.left, localRect.top, localRect.width, localRect.height);
24661
25644
  if (cellData.chartHighlight) {
@@ -24701,10 +25684,8 @@ function XlsxGrid({
24701
25684
  const bottomNeighborTopBorder = bottomNeighborData?.isMergedSecondary ? null : bottomNeighborData?.canvas?.topBorder ?? parseCanvasBorderDeclaration(bottomNeighborData?.style.borderTop);
24702
25685
  const resolvedRightBorder = resolveCanvasBoundaryBorder(rightBorder, rightNeighborLeftBorder);
24703
25686
  const resolvedBottomBorder = resolveCanvasBoundaryBorder(bottomBorder, bottomNeighborTopBorder);
24704
- if (showGridLines && !hasExplicitCellFill) {
24705
- paneContext.strokeStyle = palette.border;
24706
- paneContext.lineWidth = 1;
24707
- paneContext.beginPath();
25687
+ if (showGridLines && !hasExplicitCellFill && (!resolvedRightBorder || !resolvedBottomBorder)) {
25688
+ enqueueGridlinePath();
24708
25689
  if (!resolvedRightBorder) {
24709
25690
  paneContext.moveTo(localRect.left + localRect.width - 0.5, localRect.top);
24710
25691
  paneContext.lineTo(localRect.left + localRect.width - 0.5, localRect.top + localRect.height);
@@ -24713,7 +25694,9 @@ function XlsxGrid({
24713
25694
  paneContext.moveTo(localRect.left, localRect.top + localRect.height - 0.5);
24714
25695
  paneContext.lineTo(localRect.left + localRect.width, localRect.top + localRect.height - 0.5);
24715
25696
  }
24716
- paneContext.stroke();
25697
+ }
25698
+ if (topBorder || resolvedRightBorder || resolvedBottomBorder || leftBorder || cellData.chartHighlight) {
25699
+ flushPendingGridlines();
24717
25700
  }
24718
25701
  if (topBorder && drawRowIndex === 0) {
24719
25702
  strokeCanvasBorderSide(paneContext, "top", localRect, topBorder);
@@ -24746,17 +25729,26 @@ function XlsxGrid({
24746
25729
  strokeCanvasBorderSide(paneContext, "left", localRect, highlightBorder);
24747
25730
  }
24748
25731
  }
25732
+ const rawText = cellData.value ?? "";
25733
+ const shouldDrawCanvasContent = cellData.checkboxState != null || cellData.sparkline || rawText.length > 0 || cellData.conditionalIcon || cellData.isTableHeader;
25734
+ if (!shouldDrawCanvasContent) {
25735
+ continue;
25736
+ }
24749
25737
  const padding = canvasCellStyle.padding;
24750
25738
  const contentLeft = localRect.left + padding.left;
24751
25739
  const contentTop = localRect.top + padding.top;
24752
25740
  const contentWidth = Math.max(0, localRect.width - padding.left - padding.right);
24753
25741
  const contentHeight = Math.max(0, localRect.height - padding.top - padding.bottom);
25742
+ if (contentWidth <= 0 || contentHeight <= 0) {
25743
+ continue;
25744
+ }
24754
25745
  const activeFontSizePx = cellData.shrinkToFitFontSizePx ?? resolveCanvasFontSizePx(cellStyle, 12 * zoomFactor);
24755
25746
  const textClipOverscan = Math.max(
24756
25747
  1,
24757
25748
  zoomFactor * 1.5,
24758
25749
  activeFontSizePx * (cellData.textRotationDeg ? 0.75 : 0.18)
24759
25750
  );
25751
+ flushPendingGridlines();
24760
25752
  paneContext.save();
24761
25753
  paneContext.beginPath();
24762
25754
  paneContext.rect(
@@ -24888,7 +25880,6 @@ function XlsxGrid({
24888
25880
  const trailingInset = (cellData.conditionalIcon ? 18 * zoomFactor : 0) + (cellData.isTableHeader ? 16 * zoomFactor : 0);
24889
25881
  const spillMaxWidth = cellData.spillWidth && cellData.spillWidth > 0 ? Math.max(0, cellData.spillWidth - trailingInset) : null;
24890
25882
  const maxTextWidth = spillMaxWidth ?? Math.max(0, contentWidth - trailingInset);
24891
- const rawText = cellData.value ?? "";
24892
25883
  const textColor = canvasCellStyle.textColor;
24893
25884
  const shouldEllipsizeText = canvasCellStyle.textOverflowEllipsis;
24894
25885
  const shouldWrapText = canvasCellStyle.usesWrappedText || rawText.includes("\n");
@@ -24920,7 +25911,7 @@ function XlsxGrid({
24920
25911
  const textY = textBlockTop + lineIndex * lineHeight + lineHeight / 2;
24921
25912
  paneContext.fillText(line, textX, textY);
24922
25913
  if (canvasCellStyle.textDecoration?.includes("underline") && line.length > 0) {
24923
- const measured = Math.min(maxTextWidth, paneContext.measureText(line).width);
25914
+ const measured = Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, line));
24924
25915
  const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
24925
25916
  paneContext.beginPath();
24926
25917
  paneContext.moveTo(underlineStartX, textY + Math.max(2, lineHeight * 0.24));
@@ -24954,7 +25945,7 @@ function XlsxGrid({
24954
25945
  const textY = contentTop + contentHeight / 2;
24955
25946
  paneContext.fillText(text, textX, textY);
24956
25947
  if (canvasCellStyle.textDecoration?.includes("underline") && text.length > 0) {
24957
- const measured = shouldEllipsizeText ? Math.min(maxTextWidth, paneContext.measureText(text).width) : paneContext.measureText(text).width;
25948
+ const measured = shouldEllipsizeText ? Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, text)) : measureCanvasTextWidth(paneContext, text);
24958
25949
  const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
24959
25950
  paneContext.beginPath();
24960
25951
  paneContext.moveTo(underlineStartX, textY + 6 * zoomFactor);
@@ -24979,6 +25970,7 @@ function XlsxGrid({
24979
25970
  paneContext.restore();
24980
25971
  }
24981
25972
  }
25973
+ flushPendingGridlines();
24982
25974
  }
24983
25975
  for (const pane of cellPaneOrder) {
24984
25976
  const paneContext = bodyContexts[pane];
@@ -24996,7 +25988,7 @@ function XlsxGrid({
24996
25988
  paneContext.textBaseline = "middle";
24997
25989
  paneContext.fillText(spillText.text, spillText.textX, spillText.textY);
24998
25990
  if (spillText.textDecoration?.includes("underline") && spillText.text.length > 0) {
24999
- const measured = spillText.ellipsize ? Math.min(spillText.maxWidth, paneContext.measureText(spillText.text).width) : paneContext.measureText(spillText.text).width;
25991
+ const measured = spillText.ellipsize ? Math.min(spillText.maxWidth, measureCanvasTextWidth(paneContext, spillText.text)) : measureCanvasTextWidth(paneContext, spillText.text);
25000
25992
  const underlineStartX = spillText.align === "right" ? spillText.textX - measured : spillText.align === "center" ? spillText.textX - measured / 2 : spillText.textX;
25001
25993
  paneContext.beginPath();
25002
25994
  paneContext.moveTo(underlineStartX, spillText.underlineY);
@@ -25008,6 +26000,14 @@ function XlsxGrid({
25008
26000
  paneContext.restore();
25009
26001
  }
25010
26002
  }
26003
+ for (const pane of cellPaneOrder) {
26004
+ const paneContext = bodyContexts[pane];
26005
+ if (!paneContext) {
26006
+ continue;
26007
+ }
26008
+ drawBakedCanvasDrawings(pane, paneContext, bodyDirtyRectsByPane[pane]);
26009
+ }
26010
+ canvasProfileBodyMs = canvasProfileTarget ? performance.now() - canvasProfileBodyStart : 0;
25011
26011
  }
25012
26012
  if (shouldRepaintHeaders && cornerContext) {
25013
26013
  const topFrozenHeaderContext = topHeaderContexts.frozen;
@@ -25022,7 +26022,7 @@ function XlsxGrid({
25022
26022
  topScrollHeaderContext,
25023
26023
  bufferCanvas,
25024
26024
  dpr,
25025
- topScrollHeaderCanvasWidth2,
26025
+ topScrollHeaderCanvasWidth,
25026
26026
  headerHeight,
25027
26027
  drawingViewport.left - previousPaintedViewport.left,
25028
26028
  0
@@ -25041,7 +26041,7 @@ function XlsxGrid({
25041
26041
  bufferCanvas,
25042
26042
  dpr,
25043
26043
  rowHeaderWidth,
25044
- leftScrollHeaderCanvasHeight2,
26044
+ leftScrollHeaderCanvasHeight,
25045
26045
  0,
25046
26046
  drawingViewport.top - previousPaintedViewport.top
25047
26047
  );
@@ -25050,11 +26050,11 @@ function XlsxGrid({
25050
26050
  }
25051
26051
  }
25052
26052
  }
25053
- if (topFrozenHeaderContext && topFrozenHeaderCanvasWidth2 > 0) {
26053
+ if (topFrozenHeaderContext && topFrozenHeaderCanvasWidth > 0) {
25054
26054
  topFrozenHeaderContext.fillStyle = palette.headerSurface;
25055
- topFrozenHeaderContext.fillRect(0, 0, topFrozenHeaderCanvasWidth2, headerHeight);
26055
+ topFrozenHeaderContext.fillRect(0, 0, topFrozenHeaderCanvasWidth, headerHeight);
25056
26056
  }
25057
- if (topScrollHeaderContext && topScrollHeaderCanvasWidth2 > 0) {
26057
+ if (topScrollHeaderContext && topScrollHeaderCanvasWidth > 0) {
25058
26058
  topScrollHeaderContext.fillStyle = palette.headerSurface;
25059
26059
  for (const dirtyRect of topScrollHeaderDirtyRects) {
25060
26060
  topScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
@@ -25073,7 +26073,7 @@ function XlsxGrid({
25073
26073
  if (!paneContext) {
25074
26074
  continue;
25075
26075
  }
25076
- if (column.localLeft + column.width < 0 || column.localLeft > (column.isFrozen ? topFrozenHeaderCanvasWidth2 : topScrollHeaderCanvasWidth2)) {
26076
+ if (column.localLeft + column.width < 0 || column.localLeft > (column.isFrozen ? topFrozenHeaderCanvasWidth : topScrollHeaderCanvasWidth)) {
25077
26077
  continue;
25078
26078
  }
25079
26079
  if (!column.isFrozen && !intersectsCanvasDirtyRects(column.localLeft, 0, column.width, column.height, topScrollHeaderDirtyRects)) {
@@ -25099,11 +26099,11 @@ function XlsxGrid({
25099
26099
  column.height / 2
25100
26100
  );
25101
26101
  }
25102
- if (leftFrozenHeaderContext && leftFrozenHeaderCanvasHeight2 > 0) {
26102
+ if (leftFrozenHeaderContext && leftFrozenHeaderCanvasHeight > 0) {
25103
26103
  leftFrozenHeaderContext.fillStyle = palette.rowHeaderSurface;
25104
- leftFrozenHeaderContext.fillRect(0, 0, rowHeaderWidth, leftFrozenHeaderCanvasHeight2);
26104
+ leftFrozenHeaderContext.fillRect(0, 0, rowHeaderWidth, leftFrozenHeaderCanvasHeight);
25105
26105
  }
25106
- if (leftScrollHeaderContext && leftScrollHeaderCanvasHeight2 > 0) {
26106
+ if (leftScrollHeaderContext && leftScrollHeaderCanvasHeight > 0) {
25107
26107
  leftScrollHeaderContext.fillStyle = palette.rowHeaderSurface;
25108
26108
  for (const dirtyRect of leftScrollHeaderDirtyRects) {
25109
26109
  leftScrollHeaderContext.fillRect(dirtyRect.left, dirtyRect.top, dirtyRect.width, dirtyRect.height);
@@ -25122,7 +26122,7 @@ function XlsxGrid({
25122
26122
  if (!paneContext) {
25123
26123
  continue;
25124
26124
  }
25125
- if (row.localTop + row.height < 0 || row.localTop > (row.isFrozen ? leftFrozenHeaderCanvasHeight2 : leftScrollHeaderCanvasHeight2)) {
26125
+ if (row.localTop + row.height < 0 || row.localTop > (row.isFrozen ? leftFrozenHeaderCanvasHeight : leftScrollHeaderCanvasHeight)) {
25126
26126
  continue;
25127
26127
  }
25128
26128
  if (!row.isFrozen && !intersectsCanvasDirtyRects(0, row.localTop, rowHeaderWidth, row.height, leftScrollHeaderDirtyRects)) {
@@ -25162,9 +26162,29 @@ function XlsxGrid({
25162
26162
  paintedHeaderCanvasSignatureRef.current = nextHeaderCanvasSignature;
25163
26163
  }
25164
26164
  applyCanvasViewportCompensation();
26165
+ if (canvasProfileTarget) {
26166
+ canvasProfileTarget.push({
26167
+ bodyMs: canvasProfileBodyMs,
26168
+ culledCells: canvasProfileCulledCells,
26169
+ dirtyRects: canvasProfileDirtyRects,
26170
+ lookedUpCells: canvasProfileLookedUpCells,
26171
+ paintedCells: canvasProfilePaintedCells,
26172
+ repaintBody: shouldRepaintBody,
26173
+ repaintHeaders: shouldRepaintHeaders,
26174
+ renderToLayoutMs: canvasProfileStart - xlsxGridRenderStart,
26175
+ totalMs: performance.now() - canvasProfileStart
26176
+ });
26177
+ if (canvasProfileTarget.length > 500) {
26178
+ canvasProfileTarget.splice(0, canvasProfileTarget.length - 500);
26179
+ }
26180
+ }
25165
26181
  }, [
25166
26182
  activeSheet,
25167
26183
  applyCanvasViewportCompensation,
26184
+ bakedCanvasDrawingSignature,
26185
+ bakedFormControlRects,
26186
+ bakedImageRects,
26187
+ bakedShapeRects,
25168
26188
  canvasColumnHeaderCells,
25169
26189
  canvasPaneAxisItems,
25170
26190
  canvasRowHeaderCells,
@@ -25185,15 +26205,16 @@ function XlsxGrid({
25185
26205
  frozenPaneBottom,
25186
26206
  frozenPaneRight,
25187
26207
  getCellData,
26208
+ getCanvasImage,
25188
26209
  getBodyBlitBufferCanvas,
25189
26210
  getHeaderBlitBufferCanvas,
25190
26211
  palette,
25191
26212
  resolveCellDisplayRect,
25192
26213
  resolveMergeAnchorCell,
25193
- resizeGuide,
25194
26214
  rowIndexByActual,
25195
26215
  rowPrefixSums,
25196
26216
  selectionHeaderSurface,
26217
+ shouldBakeCanvasStaticDrawings,
25197
26218
  stickyLeftByCol,
25198
26219
  stickyTopByRow,
25199
26220
  visibleCols,
@@ -25411,7 +26432,7 @@ function XlsxGrid({
25411
26432
  start: virtualRow?.start ?? (rowPrefixSums[index] ?? 0)
25412
26433
  };
25413
26434
  });
25414
- const totalHeight = shouldVirtualizeRows ? rowVirtualizer.getTotalSize() : rowPrefixSums[rowPrefixSums.length - 1] ?? 0;
26435
+ const totalHeight = shouldUseDomVirtualizer && shouldVirtualizeRows ? rowVirtualizer.getTotalSize() : rowPrefixSums[rowPrefixSums.length - 1] ?? 0;
25415
26436
  const totalWidth = totalContentWidth + displayRowHeaderWidth;
25416
26437
  const sheetContentHeight = displayHeaderHeight + totalHeight;
25417
26438
  const isLiveZooming = liveGestureZoom !== null && zoomScale === liveGestureZoom.baseZoomScale;
@@ -25485,18 +26506,6 @@ function XlsxGrid({
25485
26506
  width: 0,
25486
26507
  zIndex: canvasHeaderOverlayZIndex
25487
26508
  };
25488
- const scrollBodyCanvasWidth = Math.max(0, drawingViewport.width - frozenPaneRight);
25489
- const scrollBodyCanvasHeight = Math.max(0, drawingViewport.height - frozenPaneBottom);
25490
- const topBodyCanvasWidth = scrollBodyCanvasWidth;
25491
- const topBodyCanvasHeight = Math.max(0, frozenPaneBottom - displayHeaderHeight);
25492
- const leftBodyCanvasWidth = Math.max(0, frozenPaneRight - displayRowHeaderWidth);
25493
- const leftBodyCanvasHeight = scrollBodyCanvasHeight;
25494
- const cornerBodyCanvasWidth = leftBodyCanvasWidth;
25495
- const cornerBodyCanvasHeight = topBodyCanvasHeight;
25496
- const topFrozenHeaderCanvasWidth = leftBodyCanvasWidth;
25497
- const topScrollHeaderCanvasWidth = scrollBodyCanvasWidth;
25498
- const leftFrozenHeaderCanvasHeight = topBodyCanvasHeight;
25499
- const leftScrollHeaderCanvasHeight = scrollBodyCanvasHeight;
25500
26509
  const canvasBodyBaseStyle = {
25501
26510
  cursor: "cell",
25502
26511
  pointerEvents: "auto",
@@ -25508,13 +26517,13 @@ function XlsxGrid({
25508
26517
  const canvasScrollBodyStyle = {
25509
26518
  ...canvasBodyBaseStyle,
25510
26519
  display: scrollBodyCanvasWidth > 0 && scrollBodyCanvasHeight > 0 ? "block" : "none",
25511
- left: frozenPaneRight,
25512
- top: frozenPaneBottom
26520
+ left: scrollBodyCanvasLeft,
26521
+ top: scrollBodyCanvasTop
25513
26522
  };
25514
26523
  const canvasTopBodyStyle = {
25515
26524
  ...canvasBodyBaseStyle,
25516
26525
  display: topBodyCanvasWidth > 0 && topBodyCanvasHeight > 0 ? "block" : "none",
25517
- left: frozenPaneRight,
26526
+ left: scrollBodyCanvasLeft,
25518
26527
  top: displayHeaderHeight,
25519
26528
  zIndex: 30
25520
26529
  };
@@ -25522,7 +26531,7 @@ function XlsxGrid({
25522
26531
  ...canvasBodyBaseStyle,
25523
26532
  display: leftBodyCanvasWidth > 0 && leftBodyCanvasHeight > 0 ? "block" : "none",
25524
26533
  left: displayRowHeaderWidth,
25525
- top: frozenPaneBottom,
26534
+ top: scrollBodyCanvasTop,
25526
26535
  zIndex: 30
25527
26536
  };
25528
26537
  const canvasCornerBodyStyle = {
@@ -25548,7 +26557,7 @@ function XlsxGrid({
25548
26557
  const canvasTopScrollHeaderStyle = {
25549
26558
  ...canvasHeaderBaseStyle,
25550
26559
  display: topScrollHeaderCanvasWidth > 0 && drawingViewport.height > 0 ? "block" : "none",
25551
- left: frozenPaneRight,
26560
+ left: scrollBodyCanvasLeft,
25552
26561
  top: 0,
25553
26562
  zIndex: canvasHeaderOverlayZIndex
25554
26563
  };
@@ -25563,7 +26572,7 @@ function XlsxGrid({
25563
26572
  ...canvasHeaderBaseStyle,
25564
26573
  display: leftScrollHeaderCanvasHeight > 0 && drawingViewport.width > 0 ? "block" : "none",
25565
26574
  left: 0,
25566
- top: frozenPaneBottom,
26575
+ top: scrollBodyCanvasTop,
25567
26576
  zIndex: canvasHeaderOverlayZIndex
25568
26577
  };
25569
26578
  const canvasCornerHeaderStyle = {
@@ -26119,26 +27128,26 @@ function XlsxGrid({
26119
27128
  };
26120
27129
  const canvasScrollOverlayPaneStyle = {
26121
27130
  ...canvasOverlayPaneBaseStyle,
26122
- display: scrollBodyCanvasWidth > 0 && scrollBodyCanvasHeight > 0 ? "block" : "none",
26123
- height: scrollBodyCanvasHeight,
27131
+ display: scrollBodyViewportWidth > 0 && scrollBodyViewportHeight > 0 ? "block" : "none",
27132
+ height: scrollBodyViewportHeight,
26124
27133
  left: frozenPaneRight,
26125
27134
  top: frozenPaneBottom,
26126
- width: scrollBodyCanvasWidth,
27135
+ width: scrollBodyViewportWidth,
26127
27136
  zIndex: 20
26128
27137
  };
26129
27138
  const canvasTopOverlayPaneStyle = {
26130
27139
  ...canvasOverlayPaneBaseStyle,
26131
- display: topBodyCanvasWidth > 0 && topBodyCanvasHeight > 0 ? "block" : "none",
27140
+ display: scrollBodyViewportWidth > 0 && topBodyCanvasHeight > 0 ? "block" : "none",
26132
27141
  height: topBodyCanvasHeight,
26133
27142
  left: frozenPaneRight,
26134
27143
  top: displayHeaderHeight,
26135
- width: topBodyCanvasWidth,
27144
+ width: scrollBodyViewportWidth,
26136
27145
  zIndex: 35
26137
27146
  };
26138
27147
  const canvasLeftOverlayPaneStyle = {
26139
27148
  ...canvasOverlayPaneBaseStyle,
26140
- display: leftBodyCanvasWidth > 0 && leftBodyCanvasHeight > 0 ? "block" : "none",
26141
- height: leftBodyCanvasHeight,
27149
+ display: leftBodyCanvasWidth > 0 && scrollBodyViewportHeight > 0 ? "block" : "none",
27150
+ height: scrollBodyViewportHeight,
26142
27151
  left: displayRowHeaderWidth,
26143
27152
  top: frozenPaneBottom,
26144
27153
  width: leftBodyCanvasWidth,
@@ -26153,8 +27162,9 @@ function XlsxGrid({
26153
27162
  width: cornerBodyCanvasWidth,
26154
27163
  zIndex: 36
26155
27164
  };
27165
+ const drawingViewportCacheSignature = `${Math.floor(drawingViewport.left / CANVAS_VIEWPORT_OVERSCAN_PX)}:${Math.floor(drawingViewport.top / CANVAS_VIEWPORT_OVERSCAN_PX)}:${drawingViewport.width}:${drawingViewport.height}`;
26156
27166
  const previousPaneDrawingNodes = paneDrawingNodesCacheRef.current;
26157
- 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;
27167
+ 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;
26158
27168
  const paneDrawingNodes = canReusePaneDrawingNodes ? previousPaneDrawingNodes.value : !showImages ? {
26159
27169
  corner: null,
26160
27170
  left: null,
@@ -26163,35 +27173,35 @@ function XlsxGrid({
26163
27173
  } : {
26164
27174
  corner: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
26165
27175
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "corner")),
26166
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "corner")),
26167
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "corner")),
26168
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "corner"))
27176
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "corner")),
27177
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "corner")),
27178
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "corner"))
26169
27179
  ] }),
26170
27180
  left: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
26171
27181
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "left")),
26172
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "left")),
26173
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "left")),
26174
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "left"))
27182
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "left")),
27183
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "left")),
27184
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "left"))
26175
27185
  ] }),
26176
27186
  scroll: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
26177
27187
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "scroll")),
26178
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "scroll")),
26179
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "scroll")),
26180
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "scroll"))
27188
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "scroll")),
27189
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "scroll")),
27190
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "scroll"))
26181
27191
  ] }),
26182
27192
  top: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
26183
27193
  chartRects.map(({ chart, rect }) => renderChartDrawing(chart, rect, "top")),
26184
- shapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "top")),
26185
- formControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "top")),
26186
- imageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "top"))
27194
+ domShapeRects.map(({ shape, rect }) => renderShapeDrawing(shape, rect, "top")),
27195
+ domFormControlRects.map(({ control, rect }) => renderFormControlDrawing(control, rect, "top")),
27196
+ domImageRects.map(({ image, rect }) => renderImageDrawing(image, rect, "top"))
26187
27197
  ] })
26188
27198
  };
26189
27199
  if (!canReusePaneDrawingNodes) {
26190
27200
  paneDrawingNodesCacheRef.current = {
26191
27201
  chartRects,
26192
- drawingViewport,
26193
- formControlRects,
26194
- imageRects,
27202
+ drawingViewportSignature: drawingViewportCacheSignature,
27203
+ formControlRects: domFormControlRects,
27204
+ imageRects: domImageRects,
26195
27205
  isChartsLoading,
26196
27206
  palette,
26197
27207
  readOnly,
@@ -26201,7 +27211,7 @@ function XlsxGrid({
26201
27211
  selectedChartId,
26202
27212
  selectedImageId,
26203
27213
  selectionStroke,
26204
- shapeRects,
27214
+ shapeRects: domShapeRects,
26205
27215
  showImages,
26206
27216
  value: paneDrawingNodes
26207
27217
  };
@@ -26709,10 +27719,11 @@ function XlsxGrid({
26709
27719
  style: canvasCornerBodyStyle
26710
27720
  }
26711
27721
  ),
26712
- showImages ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
27722
+ hasCanvasDomDrawingOverlays ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
26713
27723
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: canvasScrollOverlayPaneStyle, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26714
27724
  "div",
26715
27725
  {
27726
+ ref: canvasScrollOverlayContentRef,
26716
27727
  style: {
26717
27728
  height: sheetContentHeight,
26718
27729
  left: 0,
@@ -26720,6 +27731,7 @@ function XlsxGrid({
26720
27731
  position: "absolute",
26721
27732
  top: 0,
26722
27733
  transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
27734
+ transformOrigin: "0 0",
26723
27735
  width: totalWidth
26724
27736
  },
26725
27737
  children: paneDrawingNodes.scroll
@@ -26728,6 +27740,7 @@ function XlsxGrid({
26728
27740
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: canvasTopOverlayPaneStyle, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26729
27741
  "div",
26730
27742
  {
27743
+ ref: canvasTopOverlayContentRef,
26731
27744
  style: {
26732
27745
  height: sheetContentHeight,
26733
27746
  left: 0,
@@ -26735,6 +27748,7 @@ function XlsxGrid({
26735
27748
  position: "absolute",
26736
27749
  top: 0,
26737
27750
  transform: `translate(${-drawingViewport.left - frozenPaneRight}px, ${-displayHeaderHeight}px)`,
27751
+ transformOrigin: "0 0",
26738
27752
  width: totalWidth
26739
27753
  },
26740
27754
  children: paneDrawingNodes.top
@@ -26743,6 +27757,7 @@ function XlsxGrid({
26743
27757
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: canvasLeftOverlayPaneStyle, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26744
27758
  "div",
26745
27759
  {
27760
+ ref: canvasLeftOverlayContentRef,
26746
27761
  style: {
26747
27762
  height: sheetContentHeight,
26748
27763
  left: 0,
@@ -26750,6 +27765,7 @@ function XlsxGrid({
26750
27765
  position: "absolute",
26751
27766
  top: 0,
26752
27767
  transform: `translate(${-displayRowHeaderWidth}px, ${-drawingViewport.top - frozenPaneBottom}px)`,
27768
+ transformOrigin: "0 0",
26753
27769
  width: totalWidth
26754
27770
  },
26755
27771
  children: paneDrawingNodes.left
@@ -26758,6 +27774,7 @@ function XlsxGrid({
26758
27774
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: canvasCornerOverlayPaneStyle, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26759
27775
  "div",
26760
27776
  {
27777
+ ref: canvasCornerOverlayContentRef,
26761
27778
  style: {
26762
27779
  height: sheetContentHeight,
26763
27780
  left: 0,
@@ -26765,6 +27782,7 @@ function XlsxGrid({
26765
27782
  position: "absolute",
26766
27783
  top: 0,
26767
27784
  transform: `translate(${-displayRowHeaderWidth}px, ${-displayHeaderHeight}px)`,
27785
+ transformOrigin: "0 0",
26768
27786
  width: totalWidth
26769
27787
  },
26770
27788
  children: paneDrawingNodes.corner
@@ -27581,27 +28599,95 @@ function useXlsxViewerCharts() {
27581
28599
  );
27582
28600
  }
27583
28601
  function useXlsxViewerThumbnails(options = {}) {
27584
- const { workbook, sheets } = useXlsxViewer();
28602
+ const { getSheetFormControls, getSheetImages, getSheetShapes, workbook, sheets } = useXlsxViewer();
27585
28603
  const { isDark } = React4.useContext(ViewerAppearanceContext);
27586
28604
  const palette = useViewerPalette(isDark);
27587
28605
  const includeHeaders = options.includeHeaders ?? true;
27588
28606
  const resolution = options.resolution;
28607
+ const thumbnailImageCacheRef = React4.useRef(/* @__PURE__ */ new Map());
28608
+ const isMountedRef = React4.useRef(false);
28609
+ const [thumbnailImageLoadVersion, setThumbnailImageLoadVersion] = React4.useState(0);
28610
+ React4.useEffect(() => {
28611
+ isMountedRef.current = true;
28612
+ return () => {
28613
+ isMountedRef.current = false;
28614
+ };
28615
+ }, []);
28616
+ const getThumbnailImage = React4.useCallback((image) => {
28617
+ if (typeof Image === "undefined") {
28618
+ return null;
28619
+ }
28620
+ const cacheKey = image.id;
28621
+ const cached = thumbnailImageCacheRef.current.get(cacheKey);
28622
+ if (cached && cached.src === image.src) {
28623
+ return cached.loaded && !cached.failed ? cached.image : null;
28624
+ }
28625
+ const imageElement = new Image();
28626
+ const entry = {
28627
+ failed: false,
28628
+ image: imageElement,
28629
+ loaded: false,
28630
+ src: image.src
28631
+ };
28632
+ thumbnailImageCacheRef.current.set(cacheKey, entry);
28633
+ imageElement.onload = () => {
28634
+ entry.loaded = true;
28635
+ if (isMountedRef.current) {
28636
+ setThumbnailImageLoadVersion((version) => version + 1);
28637
+ }
28638
+ };
28639
+ imageElement.onerror = () => {
28640
+ entry.failed = true;
28641
+ if (isMountedRef.current) {
28642
+ setThumbnailImageLoadVersion((version) => version + 1);
28643
+ }
28644
+ };
28645
+ imageElement.src = image.src;
28646
+ return null;
28647
+ }, []);
27589
28648
  const thumbnails = React4.useMemo(() => {
27590
28649
  return sheets.map((sheet, sheetIndex) => {
27591
28650
  const worksheet = workbook?.getSheet(sheet.workbookSheetIndex) ?? null;
28651
+ const sheetImages = getSheetImages(sheetIndex).filter((image) => image.src).sort((left, right) => left.zIndex - right.zIndex);
28652
+ const sheetShapes = getSheetShapes(sheetIndex);
28653
+ const sheetFormControls = getSheetFormControls(sheetIndex).filter((control) => !control.hidden);
28654
+ const drawingBounds = [
28655
+ ...sheetImages.map((image) => image.anchor),
28656
+ ...sheetShapes.map((shape) => shape.anchor),
28657
+ ...sheetFormControls.map((control) => control.anchor)
28658
+ ].reduce((bounds, anchor) => {
28659
+ const drawingBound = resolveAnchoredBounds(anchor) ?? {
28660
+ maxCol: 0,
28661
+ maxRow: 0,
28662
+ minCol: 0,
28663
+ minRow: 0
28664
+ };
28665
+ return {
28666
+ maxCol: Math.max(bounds?.maxCol ?? drawingBound.maxCol, drawingBound.maxCol),
28667
+ maxRow: Math.max(bounds?.maxRow ?? drawingBound.maxRow, drawingBound.maxRow),
28668
+ minCol: Math.min(bounds?.minCol ?? drawingBound.minCol, drawingBound.minCol),
28669
+ minRow: Math.min(bounds?.minRow ?? drawingBound.minRow, drawingBound.minRow)
28670
+ };
28671
+ }, null);
28672
+ const sheetMinUsedRow = (sheet.maxUsedRow ?? -1) >= (sheet.minUsedRow ?? 0) ? sheet.minUsedRow ?? 0 : void 0;
28673
+ const sheetMinUsedCol = (sheet.maxUsedCol ?? -1) >= (sheet.minUsedCol ?? 0) ? sheet.minUsedCol ?? 0 : void 0;
28674
+ const effectiveMinUsedRow = Math.max(0, Math.min(sheetMinUsedRow ?? drawingBounds?.minRow ?? 0, drawingBounds?.minRow ?? sheetMinUsedRow ?? 0));
28675
+ const effectiveMinUsedCol = Math.max(0, Math.min(sheetMinUsedCol ?? drawingBounds?.minCol ?? 0, drawingBounds?.minCol ?? sheetMinUsedCol ?? 0));
28676
+ const effectiveMaxUsedRow = Math.max(sheet.maxUsedRow ?? -1, drawingBounds?.maxRow ?? -1);
28677
+ const effectiveMaxUsedCol = Math.max(sheet.maxUsedCol ?? -1, drawingBounds?.maxCol ?? -1);
27592
28678
  const showGridLines = sheet.showGridLines ?? true;
27593
28679
  const hiddenRowSet = new Set(sheet.hiddenRows ?? []);
27594
28680
  const hiddenColSet = new Set(sheet.hiddenCols ?? []);
27595
28681
  const visibleRows = buildVisibleAxisIndices(
27596
28682
  sheet.visibleRows ?? [],
27597
- Math.max(THUMBNAIL_FALLBACK_ROWS, (sheet.maxUsedRow ?? -1) + THUMBNAIL_FALLBACK_ROWS + 1),
27598
- sheet.maxUsedRow ?? -1,
28683
+ Math.max(THUMBNAIL_FALLBACK_ROWS, effectiveMaxUsedRow + THUMBNAIL_FALLBACK_ROWS + 1),
28684
+ effectiveMaxUsedRow,
27599
28685
  hiddenRowSet
27600
28686
  );
27601
28687
  const visibleCols = buildVisibleAxisIndices(
27602
28688
  sheet.visibleCols ?? [],
27603
- Math.max(THUMBNAIL_FALLBACK_COLS, (sheet.maxUsedCol ?? -1) + THUMBNAIL_FALLBACK_COLS + 1),
27604
- sheet.maxUsedCol ?? -1,
28689
+ Math.max(THUMBNAIL_FALLBACK_COLS, effectiveMaxUsedCol + THUMBNAIL_FALLBACK_COLS + 1),
28690
+ effectiveMaxUsedCol,
27605
28691
  hiddenColSet
27606
28692
  );
27607
28693
  const resolveColumnWidthPx = (actualCol) => {
@@ -27645,8 +28731,8 @@ function useXlsxViewerThumbnails(options = {}) {
27645
28731
  getSizePx: resolveRowHeightPx,
27646
28732
  maxCount: THUMBNAIL_MAX_ROWS,
27647
28733
  maxLogicalPixels: THUMBNAIL_MAX_SOURCE_HEIGHT_PX,
27648
- maxUsedIndex: sheet.maxUsedRow ?? -1,
27649
- minUsedIndex: Math.max(0, sheet.minUsedRow ?? 0),
28734
+ maxUsedIndex: effectiveMaxUsedRow,
28735
+ minUsedIndex: effectiveMinUsedRow,
27650
28736
  precomputed: visibleRows
27651
28737
  });
27652
28738
  const previewCols = resolveThumbnailAxisIndices({
@@ -27654,8 +28740,8 @@ function useXlsxViewerThumbnails(options = {}) {
27654
28740
  getSizePx: resolveColumnWidthPx,
27655
28741
  maxCount: THUMBNAIL_MAX_COLS,
27656
28742
  maxLogicalPixels: THUMBNAIL_MAX_SOURCE_WIDTH_PX,
27657
- maxUsedIndex: sheet.maxUsedCol ?? -1,
27658
- minUsedIndex: Math.max(0, sheet.minUsedCol ?? 0),
28743
+ maxUsedIndex: effectiveMaxUsedCol,
28744
+ minUsedIndex: effectiveMinUsedCol,
27659
28745
  precomputed: visibleCols
27660
28746
  });
27661
28747
  const rowAxis = buildThumbnailAxisItems(previewRows, resolveRowHeightPx);
@@ -27680,11 +28766,79 @@ function useXlsxViewerThumbnails(options = {}) {
27680
28766
  const sparklineByCell = new Map(
27681
28767
  (sheet.sparklines ?? []).map((sparkline) => [`${sparkline.target.row}:${sparkline.target.col}`, sparkline])
27682
28768
  );
28769
+ const thumbnailImageRects = sheetImages.map((image) => ({
28770
+ image,
28771
+ rect: resolveThumbnailAnchoredRect(
28772
+ image.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 thumbnailShapeRects = sheetShapes.map((shape) => ({
28786
+ rect: resolveThumbnailAnchoredRect(
28787
+ shape.anchor,
28788
+ visibleRows,
28789
+ visibleCols,
28790
+ resolveRowHeightPx,
28791
+ resolveColumnWidthPx,
28792
+ previewRows,
28793
+ previewCols,
28794
+ {
28795
+ headerHeight,
28796
+ rowHeaderWidth
28797
+ }
28798
+ ),
28799
+ shape
28800
+ })).filter(({ rect }) => rect.left + rect.width >= rowHeaderWidth && rect.top + rect.height >= headerHeight && rect.left <= sourceWidth && rect.top <= sourceHeight);
28801
+ const thumbnailFormControlRects = sheetFormControls.map((control) => ({
28802
+ control,
28803
+ rect: resolveThumbnailAnchoredRect(
28804
+ control.anchor,
28805
+ visibleRows,
28806
+ visibleCols,
28807
+ resolveRowHeightPx,
28808
+ resolveColumnWidthPx,
28809
+ previewRows,
28810
+ previewCols,
28811
+ {
28812
+ headerHeight,
28813
+ rowHeaderWidth
28814
+ }
28815
+ )
28816
+ })).filter(({ rect }) => rect.left + rect.width >= rowHeaderWidth && rect.top + rect.height >= headerHeight && rect.left <= sourceWidth && rect.top <= sourceHeight);
28817
+ const thumbnailDrawingEntries = [
28818
+ ...thumbnailShapeRects.map(({ rect, shape }) => ({
28819
+ kind: "shape",
28820
+ rect,
28821
+ shape,
28822
+ zIndex: shape.zIndex
28823
+ })),
28824
+ ...thumbnailFormControlRects.map(({ control, rect }) => ({
28825
+ control,
28826
+ kind: "formControl",
28827
+ rect,
28828
+ zIndex: control.zIndex
28829
+ })),
28830
+ ...thumbnailImageRects.map(({ image, rect }) => ({
28831
+ image,
28832
+ kind: "image",
28833
+ rect,
28834
+ zIndex: image.zIndex
28835
+ }))
28836
+ ].sort((left, right) => left.zIndex - right.zIndex);
27683
28837
  const paint = (canvas) => {
27684
28838
  if (!canvas) {
27685
28839
  return false;
27686
28840
  }
27687
- const context = canvas.getContext("2d");
28841
+ const context = canvas.getContext("2d", { alpha: false });
27688
28842
  if (!context) {
27689
28843
  return false;
27690
28844
  }
@@ -27708,9 +28862,10 @@ function useXlsxViewerThumbnails(options = {}) {
27708
28862
  }
27709
28863
  context.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0);
27710
28864
  context.clearRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
28865
+ const thumbnailSheetSurface = resolveSheetSurface(sheet, palette);
27711
28866
  context.fillStyle = palette.canvas;
27712
28867
  context.fillRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
27713
- context.fillStyle = resolveSheetSurface(sheet, palette);
28868
+ context.fillStyle = thumbnailSheetSurface;
27714
28869
  context.fillRect(rowHeaderWidth, headerHeight, Math.max(1, colAxis.totalSize), Math.max(1, rowAxis.totalSize));
27715
28870
  if (includeHeaders) {
27716
28871
  context.fillStyle = palette.headerSurface;
@@ -27831,8 +28986,8 @@ function useXlsxViewerThumbnails(options = {}) {
27831
28986
  };
27832
28987
  const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellData.style);
27833
28988
  const gradientFill = typeof cellData.style.backgroundImage === "string" ? resolveCanvasGradientFill(context, rect, cellData.style.backgroundImage) : null;
27834
- const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellData.style.backgroundColor === "string" ? cellData.style.backgroundColor : resolveSheetSurface(sheet, palette));
27835
- const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellData.style.backgroundColor === "string" && cellData.style.backgroundColor !== resolveSheetSurface(sheet, palette);
28989
+ const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellData.style.backgroundColor === "string" ? cellData.style.backgroundColor : thumbnailSheetSurface);
28990
+ const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellData.style.backgroundColor === "string" && cellData.style.backgroundColor !== thumbnailSheetSurface;
27836
28991
  context.fillStyle = gradientFill ?? fillColor;
27837
28992
  context.fillRect(rect.left, rect.top, rect.width, rect.height);
27838
28993
  if (cellData.conditionalDataBar) {
@@ -27887,12 +29042,19 @@ function useXlsxViewerThumbnails(options = {}) {
27887
29042
  if (canvasCellStyle.leftBorder) {
27888
29043
  strokeCanvasBorderSide(context, "left", rect, canvasCellStyle.leftBorder);
27889
29044
  }
29045
+ const rawText = cellData.value ?? "";
29046
+ const shouldDrawThumbnailContent = cellData.checkboxState != null || cellData.sparkline || rawText.length > 0 || cellData.conditionalIcon;
29047
+ if (!shouldDrawThumbnailContent) {
29048
+ continue;
29049
+ }
27890
29050
  const padding = canvasCellStyle.padding;
27891
29051
  const contentLeft = rect.left + padding.left;
27892
29052
  const contentTop = rect.top + padding.top;
27893
29053
  const contentWidth = Math.max(0, rect.width - padding.left - padding.right);
27894
29054
  const contentHeight = Math.max(0, rect.height - padding.top - padding.bottom);
27895
- const rawText = cellData.value ?? "";
29055
+ if (contentWidth <= 0 || contentHeight <= 0) {
29056
+ continue;
29057
+ }
27896
29058
  context.save();
27897
29059
  context.beginPath();
27898
29060
  context.rect(contentLeft, contentTop, contentWidth, contentHeight);
@@ -27984,6 +29146,25 @@ function useXlsxViewerThumbnails(options = {}) {
27984
29146
  context.restore();
27985
29147
  }
27986
29148
  }
29149
+ if (thumbnailDrawingEntries.length > 0) {
29150
+ context.save();
29151
+ context.beginPath();
29152
+ context.rect(rowHeaderWidth, headerHeight, Math.max(1, colAxis.totalSize), Math.max(1, rowAxis.totalSize));
29153
+ context.clip();
29154
+ for (const entry of thumbnailDrawingEntries) {
29155
+ if (entry.kind === "shape") {
29156
+ drawStaticShape(context, entry.shape, entry.rect, 1);
29157
+ } else if (entry.kind === "formControl") {
29158
+ drawStaticFormControl(context, entry.control, entry.rect, palette, 1, thumbnailSheetSurface);
29159
+ } else {
29160
+ const imageElement = getThumbnailImage(entry.image);
29161
+ if (imageElement) {
29162
+ context.drawImage(imageElement, entry.rect.left, entry.rect.top, entry.rect.width, entry.rect.height);
29163
+ }
29164
+ }
29165
+ }
29166
+ context.restore();
29167
+ }
27987
29168
  if (includeHeaders) {
27988
29169
  context.strokeStyle = palette.border;
27989
29170
  context.lineWidth = 1;
@@ -28037,7 +29218,18 @@ function useXlsxViewerThumbnails(options = {}) {
28037
29218
  workbookSheetIndex: sheet.workbookSheetIndex
28038
29219
  };
28039
29220
  });
28040
- }, [includeHeaders, palette, resolution, sheets, workbook]);
29221
+ }, [
29222
+ getSheetFormControls,
29223
+ getSheetImages,
29224
+ getSheetShapes,
29225
+ getThumbnailImage,
29226
+ includeHeaders,
29227
+ palette,
29228
+ resolution,
29229
+ sheets,
29230
+ thumbnailImageLoadVersion,
29231
+ workbook
29232
+ ]);
28041
29233
  const paintThumbnail = React4.useCallback(
28042
29234
  (sheetIndex, canvas) => thumbnails[sheetIndex]?.paint(canvas) ?? false,
28043
29235
  [thumbnails]