@extend-ai/react-xlsx 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6423,7 +6423,7 @@ function getSheetsWasmModule() {
6423
6423
  if (!wasmModulePromise) {
6424
6424
  wasmModulePromise = import("@dukelib/sheets-wasm").then(async (mod) => {
6425
6425
  if (configuredWasmSource !== void 0) {
6426
- await mod.default(configuredWasmSource);
6426
+ await mod.default({ module_or_path: configuredWasmSource });
6427
6427
  } else {
6428
6428
  await mod.default();
6429
6429
  }
@@ -7161,6 +7161,22 @@ function pushHistoryEntry(stack, entry) {
7161
7161
  function normalizeCellValue(value) {
7162
7162
  return value ?? "";
7163
7163
  }
7164
+ function cloneCellStyle(style) {
7165
+ if (!style || typeof style !== "object") {
7166
+ return style;
7167
+ }
7168
+ if (typeof structuredClone === "function") {
7169
+ try {
7170
+ return structuredClone(style);
7171
+ } catch {
7172
+ }
7173
+ }
7174
+ try {
7175
+ return JSON.parse(JSON.stringify(style));
7176
+ } catch {
7177
+ return style;
7178
+ }
7179
+ }
7164
7180
  function coerceUserEnteredValue(value) {
7165
7181
  const trimmed = value.trim();
7166
7182
  if (!trimmed) {
@@ -7183,9 +7199,12 @@ function coerceUserEnteredValue(value) {
7183
7199
  function applyCellMutationState(worksheet, cell, state) {
7184
7200
  if (state.formula) {
7185
7201
  worksheet.setFormula(cellAddressToA1(cell), state.formula);
7186
- return;
7202
+ } else {
7203
+ worksheet.setCell(cellAddressToA1(cell), normalizeCellValue(state.value));
7204
+ }
7205
+ if (state.style && typeof state.style === "object") {
7206
+ worksheet.setCellStyleAt(cell.row, cell.col, state.style);
7187
7207
  }
7188
- worksheet.setCell(cellAddressToA1(cell), normalizeCellValue(state.value));
7189
7208
  }
7190
7209
  function escapeHtml(value) {
7191
7210
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -7917,7 +7936,11 @@ function useXlsxViewerController(options) {
7917
7936
  return false;
7918
7937
  }, []);
7919
7938
  const ensureChartAssetsHydrated = React.useCallback((targetWorkbook, targetSheets) => {
7920
- if (chartAssetsRef.current || !targetWorkbook || !imageAssetsRef.current) {
7939
+ const currentAssets = chartAssetsRef.current;
7940
+ if (currentAssets && (currentAssets.chartOriginsById.size > 0 || !targetWorkbook || !imageAssetsRef.current)) {
7941
+ return currentAssets;
7942
+ }
7943
+ if (!targetWorkbook || !imageAssetsRef.current) {
7921
7944
  return chartAssetsRef.current;
7922
7945
  }
7923
7946
  const assets = loadWorkbookChartAssets(
@@ -8892,6 +8915,7 @@ function useXlsxViewerController(options) {
8892
8915
  }
8893
8916
  return {
8894
8917
  formula: worksheet.getFormulaAt(cell.row, cell.col) ?? null,
8918
+ style: worksheet.getCellStyleAt(cell.row, cell.col),
8895
8919
  value: worksheet.getCellAt(cell.row, cell.col).toJs()
8896
8920
  };
8897
8921
  }, [getActiveWorksheet]);
@@ -9036,6 +9060,7 @@ function useXlsxViewerController(options) {
9036
9060
  for (let col = startCol; col <= endCol; col += 1) {
9037
9061
  cells.push({
9038
9062
  formula: worksheet.getFormulaAt(row, col) ?? null,
9063
+ style: worksheet.getCellStyleAt(row, col),
9039
9064
  value: worksheet.getCellAt(row, col).toJs()
9040
9065
  });
9041
9066
  }
@@ -9202,11 +9227,27 @@ function useXlsxViewerController(options) {
9202
9227
  }, [getColumnWidthPx, getRowHeightPx]);
9203
9228
  const setChartRect = React.useCallback((id, rect) => {
9204
9229
  const hydratedChartAssets = ensureChartAssetsHydrated(workbook, sheets);
9230
+ console.info("[react-xlsx debug] setChartRect", {
9231
+ hasActiveSheet: Boolean(activeSheet),
9232
+ hasHydratedChartAssets: Boolean(hydratedChartAssets),
9233
+ hasImageAssets: Boolean(imageAssetsRef.current),
9234
+ hasWorkbook: Boolean(workbook),
9235
+ id,
9236
+ readOnly,
9237
+ rect
9238
+ });
9205
9239
  if (readOnly || !workbook || !activeSheet || !imageAssetsRef.current || !hydratedChartAssets) {
9206
9240
  return;
9207
9241
  }
9208
9242
  const worksheet = workbook.getSheet(activeSheet.workbookSheetIndex);
9209
9243
  const currentChart = getChartById(id);
9244
+ console.info("[react-xlsx debug] currentChart", {
9245
+ activeWorkbookSheetIndex: activeSheet.workbookSheetIndex,
9246
+ editable: currentChart?.editable,
9247
+ found: Boolean(currentChart),
9248
+ originCount: hydratedChartAssets.chartOriginsById.size,
9249
+ workbookSheetIndex: currentChart?.workbookSheetIndex
9250
+ });
9210
9251
  if (!currentChart || currentChart.editable === false || currentChart.workbookSheetIndex !== activeSheet.workbookSheetIndex) {
9211
9252
  return;
9212
9253
  }
@@ -9217,7 +9258,8 @@ function useXlsxViewerController(options) {
9217
9258
  getRowHeightPx: (row) => getRowHeightPx(worksheet, row)
9218
9259
  });
9219
9260
  recordHistoryBeforeMutation();
9220
- updateWorkbookChartAnchor(imageAssetsRef.current, hydratedChartAssets, id, nextAnchor);
9261
+ const didUpdateAnchor = updateWorkbookChartAnchor(imageAssetsRef.current, hydratedChartAssets, id, nextAnchor);
9262
+ console.info("[react-xlsx debug] updateWorkbookChartAnchor", { didUpdateAnchor, nextAnchor });
9221
9263
  hydratedChartAssets.chartsByWorkbookSheetIndex = hydratedChartAssets.chartsByWorkbookSheetIndex.map((sheetCharts) => sheetCharts.map((chart) => chart.id === id ? { ...chart, anchor: nextAnchor } : chart));
9222
9264
  setChartsByWorkbookSheetIndex((current) => current.map((sheetCharts) => sheetCharts.map((chart) => chart.id === id ? { ...chart, anchor: nextAnchor } : chart)));
9223
9265
  setRevision((current) => current + 1);
@@ -9433,8 +9475,12 @@ function useXlsxViewerController(options) {
9433
9475
  continue;
9434
9476
  }
9435
9477
  worksheet.setCell(cellAddressToA1({ row, col }), "");
9478
+ const after = captureCellMutationState(cell);
9479
+ if (!after) {
9480
+ continue;
9481
+ }
9436
9482
  mutations.push({
9437
- after: { formula: null, value: "" },
9483
+ after,
9438
9484
  before,
9439
9485
  cell
9440
9486
  });
@@ -9465,9 +9511,13 @@ function useXlsxViewerController(options) {
9465
9511
  }
9466
9512
  const nextValue = coerceUserEnteredValue(value);
9467
9513
  worksheet.setCell(cellAddressToA1(cell), nextValue);
9514
+ const after = captureCellMutationState(cell);
9515
+ if (!after) {
9516
+ return;
9517
+ }
9468
9518
  maybeRecalculateWorkbook(workbook);
9469
9519
  refreshWorkbookState(workbook);
9470
- recordCellEditHistory(cell, before, { formula: null, value: nextValue });
9520
+ recordCellEditHistory(cell, before, after);
9471
9521
  }, [captureCellMutationState, getActiveWorksheet, maybeRecalculateWorkbook, readOnly, recordCellEditHistory, refreshWorkbookState, workbook]);
9472
9522
  const setCellFormula = React.useCallback((cell, formula) => {
9473
9523
  const worksheet = getActiveWorksheet();
@@ -9484,13 +9534,31 @@ function useXlsxViewerController(options) {
9484
9534
  } else {
9485
9535
  worksheet.setFormula(cellAddressToA1(cell), formula);
9486
9536
  }
9537
+ const after = captureCellMutationState(cell);
9538
+ if (!after) {
9539
+ return;
9540
+ }
9487
9541
  maybeRecalculateWorkbook(workbook);
9488
9542
  refreshWorkbookState(workbook);
9489
- recordCellEditHistory(cell, before, {
9490
- formula: trimmedFormula || null,
9491
- value: trimmedFormula ? null : ""
9492
- });
9543
+ recordCellEditHistory(cell, before, after);
9493
9544
  }, [captureCellMutationState, getActiveWorksheet, maybeRecalculateWorkbook, readOnly, recordCellEditHistory, refreshWorkbookState, workbook]);
9545
+ const setCellStyle = React.useCallback((cell, style) => {
9546
+ const worksheet = getActiveWorksheet();
9547
+ if (readOnly || !worksheet || !workbook) {
9548
+ return;
9549
+ }
9550
+ const before = captureCellMutationState(cell);
9551
+ if (!before) {
9552
+ return;
9553
+ }
9554
+ worksheet.setCellStyleAt(cell.row, cell.col, style);
9555
+ const after = captureCellMutationState(cell);
9556
+ if (!after) {
9557
+ return;
9558
+ }
9559
+ refreshWorkbookState(workbook);
9560
+ recordCellEditHistory(cell, before, after);
9561
+ }, [captureCellMutationState, getActiveWorksheet, readOnly, recordCellEditHistory, refreshWorkbookState, workbook]);
9494
9562
  const setSelectedCellValue = React.useCallback((value) => {
9495
9563
  if (!activeCell) {
9496
9564
  return;
@@ -9503,6 +9571,51 @@ function useXlsxViewerController(options) {
9503
9571
  }
9504
9572
  setCellFormula(activeCell, formula);
9505
9573
  }, [activeCell, setCellFormula]);
9574
+ const setSelectedCellStyle = React.useCallback((style) => {
9575
+ if (!activeCell) {
9576
+ return;
9577
+ }
9578
+ setCellStyle(activeCell, style);
9579
+ }, [activeCell, setCellStyle]);
9580
+ const setRangeStyle = React.useCallback((range, style) => {
9581
+ const worksheet = getActiveWorksheet();
9582
+ if (readOnly || !worksheet || !workbook) {
9583
+ return;
9584
+ }
9585
+ const normalized = normalizeRange(range);
9586
+ const beforeStates = [];
9587
+ for (let row = normalized.start.row; row <= normalized.end.row; row += 1) {
9588
+ for (let col = normalized.start.col; col <= normalized.end.col; col += 1) {
9589
+ const cell = { row, col };
9590
+ const before = captureCellMutationState(cell);
9591
+ if (!before) {
9592
+ continue;
9593
+ }
9594
+ beforeStates.push({
9595
+ before,
9596
+ cell
9597
+ });
9598
+ }
9599
+ }
9600
+ if (beforeStates.length === 0) {
9601
+ return;
9602
+ }
9603
+ worksheet.setRangeStyle(rangeToA1(normalized), style);
9604
+ const mutations = [];
9605
+ for (const mutation of beforeStates) {
9606
+ const after = captureCellMutationState(mutation.cell);
9607
+ if (!after) {
9608
+ continue;
9609
+ }
9610
+ mutations.push({
9611
+ after,
9612
+ before: mutation.before,
9613
+ cell: mutation.cell
9614
+ });
9615
+ }
9616
+ refreshWorkbookState(workbook);
9617
+ recordRangeEditHistory(mutations, selection, activeCell);
9618
+ }, [activeCell, captureCellMutationState, getActiveWorksheet, readOnly, recordRangeEditHistory, refreshWorkbookState, selection, workbook]);
9506
9619
  const fillSelection = React.useCallback((targetRange) => {
9507
9620
  const worksheet = getActiveWorksheet();
9508
9621
  if (readOnly || !worksheet || !workbook || !selection) {
@@ -9529,19 +9642,22 @@ function useXlsxViewerController(options) {
9529
9642
  const sourceRow = sourceRange.start.row + (row - nextRange.start.row) % sourceHeight;
9530
9643
  const sourceCol = sourceRange.start.col + (col - nextRange.start.col) % sourceWidth;
9531
9644
  const sourceFormula = worksheet.getFormulaAt(sourceRow, sourceCol);
9645
+ const sourceStyle = cloneCellStyle(worksheet.getCellStyleAt(sourceRow, sourceCol));
9532
9646
  if (sourceFormula) {
9533
9647
  worksheet.setFormula(cellAddressToA1(targetCell), sourceFormula);
9534
- mutations.push({
9535
- after: { formula: sourceFormula, value: null },
9536
- before,
9537
- cell: targetCell
9538
- });
9648
+ } else {
9649
+ const sourceValue = normalizeCellValue(worksheet.getCellAt(sourceRow, sourceCol).toJs());
9650
+ worksheet.setCell(cellAddressToA1(targetCell), sourceValue);
9651
+ }
9652
+ if (sourceStyle && typeof sourceStyle === "object") {
9653
+ worksheet.setCellStyleAt(targetCell.row, targetCell.col, sourceStyle);
9654
+ }
9655
+ const after = captureCellMutationState(targetCell);
9656
+ if (!after) {
9539
9657
  continue;
9540
9658
  }
9541
- const sourceValue = normalizeCellValue(worksheet.getCellAt(sourceRow, sourceCol).toJs());
9542
- worksheet.setCell(cellAddressToA1(targetCell), sourceValue);
9543
9659
  mutations.push({
9544
- after: { formula: null, value: sourceValue },
9660
+ after,
9545
9661
  before,
9546
9662
  cell: targetCell
9547
9663
  });
@@ -9681,16 +9797,24 @@ function useXlsxViewerController(options) {
9681
9797
  }
9682
9798
  if (rawValue.startsWith("=") && rawValue.length > 1) {
9683
9799
  worksheet.setFormula(cellAddressToA1(nextCell), rawValue);
9800
+ const after = captureCellMutationState(nextCell);
9801
+ if (!after) {
9802
+ continue;
9803
+ }
9684
9804
  mutations.push({
9685
- after: { formula: rawValue, value: null },
9805
+ after,
9686
9806
  before,
9687
9807
  cell: nextCell
9688
9808
  });
9689
9809
  } else {
9690
9810
  const nextValue = coerceUserEnteredValue(rawValue);
9691
9811
  worksheet.setCell(cellAddressToA1(nextCell), nextValue);
9812
+ const after = captureCellMutationState(nextCell);
9813
+ if (!after) {
9814
+ continue;
9815
+ }
9692
9816
  mutations.push({
9693
- after: { formula: null, value: nextValue },
9817
+ after,
9694
9818
  before,
9695
9819
  cell: nextCell
9696
9820
  });
@@ -9741,8 +9865,12 @@ function useXlsxViewerController(options) {
9741
9865
  if (cell.formula) {
9742
9866
  worksheet.setFormula(cellAddressToA1(nextCell), cell.formula);
9743
9867
  if (before) {
9868
+ const after = captureCellMutationState(nextCell);
9869
+ if (!after) {
9870
+ continue;
9871
+ }
9744
9872
  mutations.push({
9745
- after: { formula: cell.formula, value: null },
9873
+ after,
9746
9874
  before,
9747
9875
  cell: nextCell
9748
9876
  });
@@ -9750,8 +9878,12 @@ function useXlsxViewerController(options) {
9750
9878
  } else {
9751
9879
  worksheet.setCell(cellAddressToA1(nextCell), cell.value);
9752
9880
  if (before) {
9881
+ const after = captureCellMutationState(nextCell);
9882
+ if (!after) {
9883
+ continue;
9884
+ }
9753
9885
  mutations.push({
9754
- after: { formula: null, value: cell.value },
9886
+ after,
9755
9887
  before,
9756
9888
  cell: nextCell
9757
9889
  });
@@ -9968,7 +10100,9 @@ function useXlsxViewerController(options) {
9968
10100
  resizeColumn,
9969
10101
  resizeRow,
9970
10102
  setCellFormula,
10103
+ setCellStyle,
9971
10104
  setCellValue,
10105
+ setRangeStyle,
9972
10106
  setZoomScale,
9973
10107
  setChartRect,
9974
10108
  setImageRect,
@@ -9987,6 +10121,7 @@ function useXlsxViewerController(options) {
9987
10121
  setActiveSheetIndex,
9988
10122
  setActiveTabIndex,
9989
10123
  setSelectedCellFormula,
10124
+ setSelectedCellStyle,
9990
10125
  setSelectedCellValue,
9991
10126
  sheets,
9992
10127
  shapes,
@@ -10070,7 +10205,9 @@ function useXlsxViewerController(options) {
10070
10205
  resizeColumn,
10071
10206
  resizeRow,
10072
10207
  setCellFormula,
10208
+ setCellStyle,
10073
10209
  setCellValue,
10210
+ setRangeStyle,
10074
10211
  setZoomScale,
10075
10212
  setChartRect,
10076
10213
  setImageRect,
@@ -10089,6 +10226,7 @@ function useXlsxViewerController(options) {
10089
10226
  setActiveSheetIndex,
10090
10227
  setActiveTabIndex,
10091
10228
  setSelectedCellFormula,
10229
+ setSelectedCellStyle,
10092
10230
  setSelectedCellValue,
10093
10231
  sheets,
10094
10232
  shapes,
@@ -16584,6 +16722,9 @@ var WHEEL_ZOOM_SENSITIVITY = 25e-5;
16584
16722
  var WHEEL_LINE_DELTA_PX = 16;
16585
16723
  var CHART_SOURCE_HIGHLIGHT_COLORS = ["#2563eb", "#dc2626", "#7c3aed", "#059669", "#ea580c", "#db2777"];
16586
16724
  var SHEET_SURFACE = "#ffffff";
16725
+ var DRAWING_SELECTION_STROKE = "#64748b";
16726
+ var DRAWING_SELECTION_HANDLE_FILL = "#ffffff";
16727
+ var DRAWING_SELECTION_HANDLE_SHADOW = "rgba(15, 23, 42, 0.18)";
16587
16728
  var DEFAULT_CELL_PADDING = "0 4px";
16588
16729
  var IMAGE_HANDLE_POSITIONS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
16589
16730
  var CANVAS_CELL_STYLE_CACHE_LIMIT = 4096;
@@ -16669,6 +16810,38 @@ function measureCanvasTextWidth(context, text) {
16669
16810
  CANVAS_TEXT_MEASURE_CACHE_LIMIT
16670
16811
  );
16671
16812
  }
16813
+ function drawCanvasTextDecorations(context, {
16814
+ align,
16815
+ color,
16816
+ decoration,
16817
+ ellipsize = false,
16818
+ lineThroughY,
16819
+ maxWidth,
16820
+ text,
16821
+ textX,
16822
+ underlineY,
16823
+ zoomFactor
16824
+ }) {
16825
+ if (!decoration || text.length === 0) {
16826
+ return;
16827
+ }
16828
+ const measured = ellipsize && maxWidth !== void 0 ? Math.min(maxWidth, measureCanvasTextWidth(context, text)) : measureCanvasTextWidth(context, text);
16829
+ const startX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
16830
+ context.strokeStyle = color;
16831
+ context.lineWidth = Math.max(1, zoomFactor * 0.75);
16832
+ if (decoration.includes("underline")) {
16833
+ context.beginPath();
16834
+ context.moveTo(startX, underlineY);
16835
+ context.lineTo(startX + measured, underlineY);
16836
+ context.stroke();
16837
+ }
16838
+ if (decoration.includes("line-through")) {
16839
+ context.beginPath();
16840
+ context.moveTo(startX, lineThroughY);
16841
+ context.lineTo(startX + measured, lineThroughY);
16842
+ context.stroke();
16843
+ }
16844
+ }
16672
16845
  function getCachedCanvasPath2D(path) {
16673
16846
  if (typeof Path2D === "undefined") {
16674
16847
  return null;
@@ -16972,6 +17145,15 @@ function resolveCanvasLineHeight(style, fallbackFontSize = 12) {
16972
17145
  }
16973
17146
  return fontSizePx * 1.2;
16974
17147
  }
17148
+ function resolveCanvasTextMiddleY(verticalAlign, contentTop, contentHeight, lineHeight) {
17149
+ if (verticalAlign === "top") {
17150
+ return contentTop + lineHeight / 2;
17151
+ }
17152
+ if (verticalAlign === "middle") {
17153
+ return contentTop + contentHeight / 2;
17154
+ }
17155
+ return contentTop + contentHeight - lineHeight / 2;
17156
+ }
16975
17157
  function resolveCanvasWrapIndex(context, text, maxWidth) {
16976
17158
  if (text.length <= 1) {
16977
17159
  return text.length;
@@ -18714,7 +18896,9 @@ function resolveImageHandleStyle(position, stroke, surface, scale = 1) {
18714
18896
  const style = {
18715
18897
  backgroundColor: surface,
18716
18898
  border: `${Math.max(1, scale)}px solid ${stroke}`,
18717
- borderRadius: 6 * scale,
18899
+ borderRadius: 3 * scale,
18900
+ boxShadow: `0 1px ${3 * scale}px ${DRAWING_SELECTION_HANDLE_SHADOW}`,
18901
+ boxSizing: "border-box",
18718
18902
  cursor: IMAGE_HANDLE_CURSOR[position],
18719
18903
  height: handleSize,
18720
18904
  pointerEvents: "auto",
@@ -26202,8 +26386,9 @@ function XlsxGrid({
26202
26386
  const textColor = canvasCellStyle.textColor;
26203
26387
  const shouldEllipsizeText = canvasCellStyle.textOverflowEllipsis;
26204
26388
  const shouldWrapText = canvasCellStyle.usesWrappedText || rawText.includes("\n");
26389
+ const singleLineHeight = cellData.shrinkToFitFontSizePx ? resolveCanvasLineHeight(cellStyle, cellData.shrinkToFitFontSizePx) : resolveCanvasLineHeight(cellStyle, 12 * zoomFactor);
26205
26390
  if (cellData.textRotationDeg && rawText.length > 0) {
26206
- const textY = contentTop + contentHeight / 2;
26391
+ const textY = resolveCanvasTextMiddleY(cellStyle.verticalAlign, contentTop, contentHeight, singleLineHeight);
26207
26392
  const rotationOriginX = contentLeft + contentWidth / 2;
26208
26393
  paneContext.save();
26209
26394
  paneContext.translate(rotationOriginX, textY);
@@ -26229,20 +26414,21 @@ function XlsxGrid({
26229
26414
  wrappedLines.forEach((line, lineIndex) => {
26230
26415
  const textY = textBlockTop + lineIndex * lineHeight + lineHeight / 2;
26231
26416
  paneContext.fillText(line, textX, textY);
26232
- if (canvasCellStyle.textDecoration?.includes("underline") && line.length > 0) {
26233
- const measured = Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, line));
26234
- const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
26235
- paneContext.beginPath();
26236
- paneContext.moveTo(underlineStartX, textY + Math.max(2, lineHeight * 0.24));
26237
- paneContext.lineTo(underlineStartX + measured, textY + Math.max(2, lineHeight * 0.24));
26238
- paneContext.strokeStyle = textColor;
26239
- paneContext.lineWidth = Math.max(1, zoomFactor * 0.75);
26240
- paneContext.stroke();
26241
- }
26417
+ drawCanvasTextDecorations(paneContext, {
26418
+ align,
26419
+ color: textColor,
26420
+ decoration: canvasCellStyle.textDecoration,
26421
+ lineThroughY: textY,
26422
+ maxWidth: maxTextWidth,
26423
+ text: line,
26424
+ textX,
26425
+ underlineY: textY + Math.max(2, lineHeight * 0.24),
26426
+ zoomFactor
26427
+ });
26242
26428
  });
26243
26429
  } else if (spillMaxWidth != null) {
26244
26430
  const text = shouldEllipsizeText ? truncateCanvasText(paneContext, rawText, maxTextWidth) : rawText;
26245
- const textY = contentTop + contentHeight / 2;
26431
+ const textY = resolveCanvasTextMiddleY(cellStyle.verticalAlign, contentTop, contentHeight, singleLineHeight);
26246
26432
  deferredSpillTextsByPane[pane].push({
26247
26433
  align,
26248
26434
  clipHeight: contentHeight + textClipOverscan * 2,
@@ -26257,22 +26443,25 @@ function XlsxGrid({
26257
26443
  textDecoration: canvasCellStyle.textDecoration,
26258
26444
  textX,
26259
26445
  textY,
26446
+ lineThroughY: textY,
26260
26447
  underlineY: textY + 6 * zoomFactor
26261
26448
  });
26262
26449
  } else {
26263
26450
  const text = cellData.shrinkToFit ? rawText : shouldEllipsizeText ? truncateCanvasText(paneContext, rawText, maxTextWidth) : rawText;
26264
- const textY = contentTop + contentHeight / 2;
26451
+ const textY = resolveCanvasTextMiddleY(cellStyle.verticalAlign, contentTop, contentHeight, singleLineHeight);
26265
26452
  paneContext.fillText(text, textX, textY);
26266
- if (canvasCellStyle.textDecoration?.includes("underline") && text.length > 0) {
26267
- const measured = shouldEllipsizeText ? Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, text)) : measureCanvasTextWidth(paneContext, text);
26268
- const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
26269
- paneContext.beginPath();
26270
- paneContext.moveTo(underlineStartX, textY + 6 * zoomFactor);
26271
- paneContext.lineTo(underlineStartX + measured, textY + 6 * zoomFactor);
26272
- paneContext.strokeStyle = textColor;
26273
- paneContext.lineWidth = Math.max(1, zoomFactor * 0.75);
26274
- paneContext.stroke();
26275
- }
26453
+ drawCanvasTextDecorations(paneContext, {
26454
+ align,
26455
+ color: textColor,
26456
+ decoration: canvasCellStyle.textDecoration,
26457
+ ellipsize: shouldEllipsizeText,
26458
+ lineThroughY: textY,
26459
+ maxWidth: maxTextWidth,
26460
+ text,
26461
+ textX,
26462
+ underlineY: textY + 6 * zoomFactor,
26463
+ zoomFactor
26464
+ });
26276
26465
  }
26277
26466
  }
26278
26467
  if (cellData.conditionalIcon) {
@@ -26306,16 +26495,18 @@ function XlsxGrid({
26306
26495
  paneContext.textAlign = spillText.align;
26307
26496
  paneContext.textBaseline = "middle";
26308
26497
  paneContext.fillText(spillText.text, spillText.textX, spillText.textY);
26309
- if (spillText.textDecoration?.includes("underline") && spillText.text.length > 0) {
26310
- const measured = spillText.ellipsize ? Math.min(spillText.maxWidth, measureCanvasTextWidth(paneContext, spillText.text)) : measureCanvasTextWidth(paneContext, spillText.text);
26311
- const underlineStartX = spillText.align === "right" ? spillText.textX - measured : spillText.align === "center" ? spillText.textX - measured / 2 : spillText.textX;
26312
- paneContext.beginPath();
26313
- paneContext.moveTo(underlineStartX, spillText.underlineY);
26314
- paneContext.lineTo(underlineStartX + measured, spillText.underlineY);
26315
- paneContext.strokeStyle = spillText.color;
26316
- paneContext.lineWidth = Math.max(1, zoomFactor * 0.75);
26317
- paneContext.stroke();
26318
- }
26498
+ drawCanvasTextDecorations(paneContext, {
26499
+ align: spillText.align,
26500
+ color: spillText.color,
26501
+ decoration: spillText.textDecoration,
26502
+ ellipsize: spillText.ellipsize,
26503
+ lineThroughY: spillText.lineThroughY,
26504
+ maxWidth: spillText.maxWidth,
26505
+ text: spillText.text,
26506
+ textX: spillText.textX,
26507
+ underlineY: spillText.underlineY,
26508
+ zoomFactor
26509
+ });
26319
26510
  paneContext.restore();
26320
26511
  }
26321
26512
  }
@@ -27224,6 +27415,7 @@ function XlsxGrid({
27224
27415
  }
27225
27416
  const isFrozenDrawing = pane !== "scroll";
27226
27417
  const canEditImage = !readOnly && image.editable !== false;
27418
+ const drawingSelectionSurface = paletteIsDark(palette) ? palette.canvas : DRAWING_SELECTION_HANDLE_FILL;
27227
27419
  const style = {
27228
27420
  contain: "layout paint",
27229
27421
  height: rect.height,
@@ -27255,6 +27447,7 @@ function XlsxGrid({
27255
27447
  {
27256
27448
  style: {
27257
27449
  ...style,
27450
+ contain: "layout",
27258
27451
  overflow: "visible",
27259
27452
  pointerEvents: "none",
27260
27453
  zIndex: isFrozenDrawing ? image.zIndex + 22 : image.zIndex + 2
@@ -27264,8 +27457,8 @@ function XlsxGrid({
27264
27457
  "div",
27265
27458
  {
27266
27459
  style: {
27267
- border: `${Math.max(1, zoomFactor)}px solid ${selectionStroke}`,
27268
- boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${palette.surface}`,
27460
+ border: `${Math.max(1, zoomFactor)}px solid ${DRAWING_SELECTION_STROKE}`,
27461
+ boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${drawingSelectionSurface}`,
27269
27462
  boxSizing: "border-box",
27270
27463
  inset: 0,
27271
27464
  pointerEvents: "none",
@@ -27275,7 +27468,7 @@ function XlsxGrid({
27275
27468
  "div",
27276
27469
  {
27277
27470
  onPointerDown: (event) => startImageResize(event, image, rect, position),
27278
- style: resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor)
27471
+ style: resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor)
27279
27472
  },
27280
27473
  position
27281
27474
  )) : null
@@ -27287,7 +27480,7 @@ function XlsxGrid({
27287
27480
  startImageResize(event, image, rect, position);
27288
27481
  }
27289
27482
  },
27290
- style: canEditImage ? resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor) : { ...resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor), display: "none" }
27483
+ style: canEditImage ? resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor) : { ...resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor), display: "none" }
27291
27484
  }),
27292
27485
  image,
27293
27486
  rect
@@ -27295,8 +27488,8 @@ function XlsxGrid({
27295
27488
  "div",
27296
27489
  {
27297
27490
  style: {
27298
- border: `${Math.max(1, zoomFactor)}px solid ${selectionStroke}`,
27299
- boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${palette.surface}`,
27491
+ border: `${Math.max(1, zoomFactor)}px solid ${DRAWING_SELECTION_STROKE}`,
27492
+ boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${drawingSelectionSurface}`,
27300
27493
  boxSizing: "border-box",
27301
27494
  inset: 0,
27302
27495
  pointerEvents: "none",
@@ -27306,7 +27499,7 @@ function XlsxGrid({
27306
27499
  "div",
27307
27500
  {
27308
27501
  onPointerDown: (event) => startImageResize(event, image, rect, position),
27309
- style: resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor)
27502
+ style: resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor)
27310
27503
  },
27311
27504
  position
27312
27505
  )) : null
@@ -27344,6 +27537,7 @@ function XlsxGrid({
27344
27537
  }
27345
27538
  const isFrozenDrawing = pane !== "scroll";
27346
27539
  const canEditChart = !readOnly && chart.editable !== false;
27540
+ const drawingSelectionSurface = paletteIsDark(palette) ? palette.canvas : DRAWING_SELECTION_HANDLE_FILL;
27347
27541
  const style = {
27348
27542
  contain: "layout paint",
27349
27543
  height: rect.height,
@@ -27360,6 +27554,7 @@ function XlsxGrid({
27360
27554
  {
27361
27555
  style: {
27362
27556
  ...style,
27557
+ contain: "layout",
27363
27558
  overflow: "visible",
27364
27559
  pointerEvents: "none",
27365
27560
  zIndex: isFrozenDrawing ? chart.zIndex + 22 : chart.zIndex + 2
@@ -27368,8 +27563,8 @@ function XlsxGrid({
27368
27563
  "div",
27369
27564
  {
27370
27565
  style: {
27371
- border: `${Math.max(1, zoomFactor)}px solid ${selectionStroke}`,
27372
- boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${palette.surface}`,
27566
+ border: `${Math.max(1, zoomFactor)}px solid ${DRAWING_SELECTION_STROKE}`,
27567
+ boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${drawingSelectionSurface}`,
27373
27568
  boxSizing: "border-box",
27374
27569
  inset: 0,
27375
27570
  pointerEvents: "none",
@@ -27379,7 +27574,7 @@ function XlsxGrid({
27379
27574
  "div",
27380
27575
  {
27381
27576
  onPointerDown: (event) => startChartResize(event, chart, rect, position),
27382
- style: resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor)
27577
+ style: resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor)
27383
27578
  },
27384
27579
  position
27385
27580
  )) : null
@@ -27659,6 +27854,22 @@ function XlsxGrid({
27659
27854
  }
27660
27855
  function installImageInteractionListeners(pointerId) {
27661
27856
  imageInteractionCleanupRef.current?.();
27857
+ const resolveInteractionRect = (interaction, clientX, clientY) => {
27858
+ const deltaX = clientX - interaction.startClientX;
27859
+ const deltaY = clientY - interaction.startClientY;
27860
+ return clampImageRect(
27861
+ interaction.type === "move" ? {
27862
+ ...interaction.baseRect,
27863
+ left: interaction.baseRect.left + deltaX,
27864
+ top: interaction.baseRect.top + deltaY
27865
+ } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, displayImageMinSize),
27866
+ {
27867
+ contentOffsetLeft: displayRowHeaderWidth,
27868
+ contentOffsetTop: displayHeaderHeight,
27869
+ minSizePx: displayImageMinSize
27870
+ }
27871
+ );
27872
+ };
27662
27873
  const handlePointerMove = (event) => {
27663
27874
  if (event.pointerId !== pointerId) {
27664
27875
  return;
@@ -27672,18 +27883,7 @@ function XlsxGrid({
27672
27883
  if (!interaction.didMove && (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3)) {
27673
27884
  interaction.didMove = true;
27674
27885
  }
27675
- const nextRect = clampImageRect(
27676
- interaction.type === "move" ? {
27677
- ...interaction.baseRect,
27678
- left: interaction.baseRect.left + deltaX,
27679
- top: interaction.baseRect.top + deltaY
27680
- } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, displayImageMinSize),
27681
- {
27682
- contentOffsetLeft: displayRowHeaderWidth,
27683
- contentOffsetTop: displayHeaderHeight,
27684
- minSizePx: displayImageMinSize
27685
- }
27686
- );
27886
+ const nextRect = resolveInteractionRect(interaction, event.clientX, event.clientY);
27687
27887
  scheduleImagePreviewRect({ id: interaction.imageId, rect: nextRect });
27688
27888
  };
27689
27889
  const cleanup = () => {
@@ -27706,18 +27906,20 @@ function XlsxGrid({
27706
27906
  imagePreviewRectRef.current = pendingPreview;
27707
27907
  setImagePreviewRect(pendingPreview);
27708
27908
  }
27709
- const preview = pendingPreview ?? imagePreviewRectRef.current;
27909
+ const finalRect = interaction ? resolveInteractionRect(interaction, event.clientX, event.clientY) : null;
27710
27910
  imageInteractionRef.current = null;
27711
27911
  imageInteractionCleanupRef.current = null;
27712
27912
  setInteractionMode("idle");
27713
27913
  document.body.style.cursor = "";
27714
27914
  document.body.style.userSelect = "";
27715
27915
  cleanup();
27716
- if (interaction && preview && preview.id === interaction.imageId) {
27916
+ if (interaction) {
27717
27917
  if (interaction.didMove) {
27718
27918
  skipNextImageClickRef.current = interaction.imageId;
27719
27919
  }
27720
- setImageRect(interaction.imageId, toLogicalRect(preview.rect));
27920
+ if (interaction.didMove && finalRect) {
27921
+ setImageRect(interaction.imageId, toLogicalRect(finalRect));
27922
+ }
27721
27923
  }
27722
27924
  imagePreviewRectRef.current = null;
27723
27925
  setImagePreviewRect(null);
@@ -27729,6 +27931,22 @@ function XlsxGrid({
27729
27931
  }
27730
27932
  function installChartInteractionListeners(pointerId) {
27731
27933
  chartInteractionCleanupRef.current?.();
27934
+ const resolveInteractionRect = (interaction, clientX, clientY) => {
27935
+ const deltaX = clientX - interaction.startClientX;
27936
+ const deltaY = clientY - interaction.startClientY;
27937
+ return clampImageRect(
27938
+ interaction.type === "move" ? {
27939
+ ...interaction.baseRect,
27940
+ left: interaction.baseRect.left + deltaX,
27941
+ top: interaction.baseRect.top + deltaY
27942
+ } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, 48 * zoomFactor),
27943
+ {
27944
+ contentOffsetLeft: displayRowHeaderWidth,
27945
+ contentOffsetTop: displayHeaderHeight,
27946
+ minSizePx: 48 * zoomFactor
27947
+ }
27948
+ );
27949
+ };
27732
27950
  const handlePointerMove = (event) => {
27733
27951
  if (event.pointerId !== pointerId) {
27734
27952
  return;
@@ -27742,18 +27960,7 @@ function XlsxGrid({
27742
27960
  if (!interaction.didMove && (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3)) {
27743
27961
  interaction.didMove = true;
27744
27962
  }
27745
- const nextRect = clampImageRect(
27746
- interaction.type === "move" ? {
27747
- ...interaction.baseRect,
27748
- left: interaction.baseRect.left + deltaX,
27749
- top: interaction.baseRect.top + deltaY
27750
- } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, 48 * zoomFactor),
27751
- {
27752
- contentOffsetLeft: displayRowHeaderWidth,
27753
- contentOffsetTop: displayHeaderHeight,
27754
- minSizePx: 48 * zoomFactor
27755
- }
27756
- );
27963
+ const nextRect = resolveInteractionRect(interaction, event.clientX, event.clientY);
27757
27964
  scheduleChartPreviewRect({ id: interaction.chartId, rect: nextRect });
27758
27965
  };
27759
27966
  const cleanup = () => {
@@ -27776,18 +27983,20 @@ function XlsxGrid({
27776
27983
  chartPreviewRectRef.current = pendingPreview;
27777
27984
  setChartPreviewRect(pendingPreview);
27778
27985
  }
27779
- const preview = pendingPreview ?? chartPreviewRectRef.current;
27986
+ const finalRect = interaction ? resolveInteractionRect(interaction, event.clientX, event.clientY) : null;
27780
27987
  chartInteractionRef.current = null;
27781
27988
  chartInteractionCleanupRef.current = null;
27782
27989
  setInteractionMode("idle");
27783
27990
  document.body.style.cursor = "";
27784
27991
  document.body.style.userSelect = "";
27785
27992
  cleanup();
27786
- if (interaction && preview && preview.id === interaction.chartId) {
27993
+ if (interaction) {
27787
27994
  if (interaction.didMove) {
27788
27995
  skipNextChartClickRef.current = interaction.chartId;
27789
27996
  }
27790
- setChartRect(interaction.chartId, toLogicalRect(preview.rect));
27997
+ if (interaction.didMove && finalRect) {
27998
+ setChartRect(interaction.chartId, toLogicalRect(finalRect));
27999
+ }
27791
28000
  }
27792
28001
  chartPreviewRectRef.current = null;
27793
28002
  setChartPreviewRect(null);
@@ -28090,7 +28299,6 @@ function XlsxGrid({
28090
28299
  }
28091
28300
  gridKeyboardHandlerRef.current = handleGridKeyDown;
28092
28301
  const scrollerViewportProps = {
28093
- key: activeTabIndex,
28094
28302
  ref: scrollRef,
28095
28303
  "aria-colcount": Math.max(activeSheet?.colCount ?? 0, displayColLimit),
28096
28304
  "aria-keyshortcuts": "ArrowUp ArrowDown ArrowLeft ArrowRight Home End PageUp PageDown Control+Home Control+End",
@@ -28728,7 +28936,7 @@ function XlsxGrid({
28728
28936
  )
28729
28937
  }
28730
28938
  );
28731
- return /* @__PURE__ */ jsx3("div", { style: { backgroundColor: palette.canvas, display: "flex", flex: 1, minHeight: 0, minWidth: 0 }, children: renderScroller ? renderScroller({ children: scrollerContent, viewportProps: scrollerViewportProps }) : /* @__PURE__ */ jsx3("div", { ...scrollerViewportProps, children: scrollerContent }) });
28939
+ return /* @__PURE__ */ jsx3("div", { style: { backgroundColor: palette.canvas, display: "flex", flex: 1, minHeight: 0, minWidth: 0 }, children: renderScroller ? renderScroller({ children: scrollerContent, viewportProps: scrollerViewportProps }) : /* @__PURE__ */ jsx3("div", { ...scrollerViewportProps, children: scrollerContent }, activeTabIndex) });
28732
28940
  }
28733
28941
  function XlsxViewerInner({
28734
28942
  allowResizeInReadOnly = false,
@@ -28933,8 +29141,11 @@ function useXlsxViewerEditing() {
28933
29141
  selectedFormula,
28934
29142
  selectedValue,
28935
29143
  setCellFormula,
29144
+ setCellStyle,
28936
29145
  setCellValue,
29146
+ setRangeStyle,
28937
29147
  setSelectedCellFormula,
29148
+ setSelectedCellStyle,
28938
29149
  setSelectedCellValue,
28939
29150
  undo,
28940
29151
  unmergeSelection
@@ -28961,8 +29172,11 @@ function useXlsxViewerEditing() {
28961
29172
  selectedFormula,
28962
29173
  selectedValue,
28963
29174
  setCellFormula,
29175
+ setCellStyle,
28964
29176
  setCellValue,
29177
+ setRangeStyle,
28965
29178
  setSelectedCellFormula,
29179
+ setSelectedCellStyle,
28966
29180
  setSelectedCellValue,
28967
29181
  undo,
28968
29182
  unmergeSelection
@@ -28988,8 +29202,11 @@ function useXlsxViewerEditing() {
28988
29202
  selectedFormula,
28989
29203
  selectedValue,
28990
29204
  setCellFormula,
29205
+ setCellStyle,
28991
29206
  setCellValue,
29207
+ setRangeStyle,
28992
29208
  setSelectedCellFormula,
29209
+ setSelectedCellStyle,
28993
29210
  setSelectedCellValue,
28994
29211
  undo,
28995
29212
  unmergeSelection
@@ -29660,13 +29877,14 @@ function useXlsxViewerThumbnails(options = {}) {
29660
29877
  const align = canvasCellStyle.textAlign;
29661
29878
  context.textAlign = align;
29662
29879
  const textX = align === "right" ? contentLeft + contentWidth : align === "center" ? contentLeft + contentWidth / 2 : contentLeft;
29663
- const textY = contentTop + contentHeight / 2;
29664
29880
  const trailingInset = cellData.conditionalIcon ? 18 : 0;
29665
29881
  const maxTextWidth = Math.max(0, contentWidth - trailingInset);
29882
+ const singleLineHeight = resolveCanvasLineHeight(cellData.style, 12);
29666
29883
  if (cellData.textRotationDeg) {
29884
+ const alignedTextY = resolveCanvasTextMiddleY(cellData.style.verticalAlign, contentTop, contentHeight, singleLineHeight);
29667
29885
  const rotationOriginX = contentLeft + contentWidth / 2;
29668
29886
  context.save();
29669
- context.translate(rotationOriginX, textY);
29887
+ context.translate(rotationOriginX, alignedTextY);
29670
29888
  context.rotate(cellData.textRotationDeg * Math.PI / 180);
29671
29889
  context.fillText(
29672
29890
  rawText,
@@ -29689,7 +29907,7 @@ function useXlsxViewerThumbnails(options = {}) {
29689
29907
  });
29690
29908
  } else {
29691
29909
  const text = canvasCellStyle.textOverflowEllipsis ? truncateCanvasText(context, rawText, maxTextWidth) : rawText;
29692
- context.fillText(text, textX, textY);
29910
+ context.fillText(text, textX, resolveCanvasTextMiddleY(cellData.style.verticalAlign, contentTop, contentHeight, singleLineHeight));
29693
29911
  }
29694
29912
  }
29695
29913
  if (cellData.conditionalIcon) {