@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.cjs CHANGED
@@ -6473,7 +6473,7 @@ function getSheetsWasmModule() {
6473
6473
  if (!wasmModulePromise) {
6474
6474
  wasmModulePromise = import("@dukelib/sheets-wasm").then(async (mod) => {
6475
6475
  if (configuredWasmSource !== void 0) {
6476
- await mod.default(configuredWasmSource);
6476
+ await mod.default({ module_or_path: configuredWasmSource });
6477
6477
  } else {
6478
6478
  await mod.default();
6479
6479
  }
@@ -7212,6 +7212,22 @@ function pushHistoryEntry(stack, entry) {
7212
7212
  function normalizeCellValue(value) {
7213
7213
  return value ?? "";
7214
7214
  }
7215
+ function cloneCellStyle(style) {
7216
+ if (!style || typeof style !== "object") {
7217
+ return style;
7218
+ }
7219
+ if (typeof structuredClone === "function") {
7220
+ try {
7221
+ return structuredClone(style);
7222
+ } catch {
7223
+ }
7224
+ }
7225
+ try {
7226
+ return JSON.parse(JSON.stringify(style));
7227
+ } catch {
7228
+ return style;
7229
+ }
7230
+ }
7215
7231
  function coerceUserEnteredValue(value) {
7216
7232
  const trimmed = value.trim();
7217
7233
  if (!trimmed) {
@@ -7234,9 +7250,12 @@ function coerceUserEnteredValue(value) {
7234
7250
  function applyCellMutationState(worksheet, cell, state) {
7235
7251
  if (state.formula) {
7236
7252
  worksheet.setFormula(cellAddressToA1(cell), state.formula);
7237
- return;
7253
+ } else {
7254
+ worksheet.setCell(cellAddressToA1(cell), normalizeCellValue(state.value));
7255
+ }
7256
+ if (state.style && typeof state.style === "object") {
7257
+ worksheet.setCellStyleAt(cell.row, cell.col, state.style);
7238
7258
  }
7239
- worksheet.setCell(cellAddressToA1(cell), normalizeCellValue(state.value));
7240
7259
  }
7241
7260
  function escapeHtml(value) {
7242
7261
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -7968,7 +7987,11 @@ function useXlsxViewerController(options) {
7968
7987
  return false;
7969
7988
  }, []);
7970
7989
  const ensureChartAssetsHydrated = React.useCallback((targetWorkbook, targetSheets) => {
7971
- if (chartAssetsRef.current || !targetWorkbook || !imageAssetsRef.current) {
7990
+ const currentAssets = chartAssetsRef.current;
7991
+ if (currentAssets && (currentAssets.chartOriginsById.size > 0 || !targetWorkbook || !imageAssetsRef.current)) {
7992
+ return currentAssets;
7993
+ }
7994
+ if (!targetWorkbook || !imageAssetsRef.current) {
7972
7995
  return chartAssetsRef.current;
7973
7996
  }
7974
7997
  const assets = loadWorkbookChartAssets(
@@ -8943,6 +8966,7 @@ function useXlsxViewerController(options) {
8943
8966
  }
8944
8967
  return {
8945
8968
  formula: worksheet.getFormulaAt(cell.row, cell.col) ?? null,
8969
+ style: worksheet.getCellStyleAt(cell.row, cell.col),
8946
8970
  value: worksheet.getCellAt(cell.row, cell.col).toJs()
8947
8971
  };
8948
8972
  }, [getActiveWorksheet]);
@@ -9087,6 +9111,7 @@ function useXlsxViewerController(options) {
9087
9111
  for (let col = startCol; col <= endCol; col += 1) {
9088
9112
  cells.push({
9089
9113
  formula: worksheet.getFormulaAt(row, col) ?? null,
9114
+ style: worksheet.getCellStyleAt(row, col),
9090
9115
  value: worksheet.getCellAt(row, col).toJs()
9091
9116
  });
9092
9117
  }
@@ -9253,11 +9278,27 @@ function useXlsxViewerController(options) {
9253
9278
  }, [getColumnWidthPx, getRowHeightPx]);
9254
9279
  const setChartRect = React.useCallback((id, rect) => {
9255
9280
  const hydratedChartAssets = ensureChartAssetsHydrated(workbook, sheets);
9281
+ console.info("[react-xlsx debug] setChartRect", {
9282
+ hasActiveSheet: Boolean(activeSheet),
9283
+ hasHydratedChartAssets: Boolean(hydratedChartAssets),
9284
+ hasImageAssets: Boolean(imageAssetsRef.current),
9285
+ hasWorkbook: Boolean(workbook),
9286
+ id,
9287
+ readOnly,
9288
+ rect
9289
+ });
9256
9290
  if (readOnly || !workbook || !activeSheet || !imageAssetsRef.current || !hydratedChartAssets) {
9257
9291
  return;
9258
9292
  }
9259
9293
  const worksheet = workbook.getSheet(activeSheet.workbookSheetIndex);
9260
9294
  const currentChart = getChartById(id);
9295
+ console.info("[react-xlsx debug] currentChart", {
9296
+ activeWorkbookSheetIndex: activeSheet.workbookSheetIndex,
9297
+ editable: currentChart?.editable,
9298
+ found: Boolean(currentChart),
9299
+ originCount: hydratedChartAssets.chartOriginsById.size,
9300
+ workbookSheetIndex: currentChart?.workbookSheetIndex
9301
+ });
9261
9302
  if (!currentChart || currentChart.editable === false || currentChart.workbookSheetIndex !== activeSheet.workbookSheetIndex) {
9262
9303
  return;
9263
9304
  }
@@ -9268,7 +9309,8 @@ function useXlsxViewerController(options) {
9268
9309
  getRowHeightPx: (row) => getRowHeightPx(worksheet, row)
9269
9310
  });
9270
9311
  recordHistoryBeforeMutation();
9271
- updateWorkbookChartAnchor(imageAssetsRef.current, hydratedChartAssets, id, nextAnchor);
9312
+ const didUpdateAnchor = updateWorkbookChartAnchor(imageAssetsRef.current, hydratedChartAssets, id, nextAnchor);
9313
+ console.info("[react-xlsx debug] updateWorkbookChartAnchor", { didUpdateAnchor, nextAnchor });
9272
9314
  hydratedChartAssets.chartsByWorkbookSheetIndex = hydratedChartAssets.chartsByWorkbookSheetIndex.map((sheetCharts) => sheetCharts.map((chart) => chart.id === id ? { ...chart, anchor: nextAnchor } : chart));
9273
9315
  setChartsByWorkbookSheetIndex((current) => current.map((sheetCharts) => sheetCharts.map((chart) => chart.id === id ? { ...chart, anchor: nextAnchor } : chart)));
9274
9316
  setRevision((current) => current + 1);
@@ -9484,8 +9526,12 @@ function useXlsxViewerController(options) {
9484
9526
  continue;
9485
9527
  }
9486
9528
  worksheet.setCell(cellAddressToA1({ row, col }), "");
9529
+ const after = captureCellMutationState(cell);
9530
+ if (!after) {
9531
+ continue;
9532
+ }
9487
9533
  mutations.push({
9488
- after: { formula: null, value: "" },
9534
+ after,
9489
9535
  before,
9490
9536
  cell
9491
9537
  });
@@ -9516,9 +9562,13 @@ function useXlsxViewerController(options) {
9516
9562
  }
9517
9563
  const nextValue = coerceUserEnteredValue(value);
9518
9564
  worksheet.setCell(cellAddressToA1(cell), nextValue);
9565
+ const after = captureCellMutationState(cell);
9566
+ if (!after) {
9567
+ return;
9568
+ }
9519
9569
  maybeRecalculateWorkbook(workbook);
9520
9570
  refreshWorkbookState(workbook);
9521
- recordCellEditHistory(cell, before, { formula: null, value: nextValue });
9571
+ recordCellEditHistory(cell, before, after);
9522
9572
  }, [captureCellMutationState, getActiveWorksheet, maybeRecalculateWorkbook, readOnly, recordCellEditHistory, refreshWorkbookState, workbook]);
9523
9573
  const setCellFormula = React.useCallback((cell, formula) => {
9524
9574
  const worksheet = getActiveWorksheet();
@@ -9535,13 +9585,31 @@ function useXlsxViewerController(options) {
9535
9585
  } else {
9536
9586
  worksheet.setFormula(cellAddressToA1(cell), formula);
9537
9587
  }
9588
+ const after = captureCellMutationState(cell);
9589
+ if (!after) {
9590
+ return;
9591
+ }
9538
9592
  maybeRecalculateWorkbook(workbook);
9539
9593
  refreshWorkbookState(workbook);
9540
- recordCellEditHistory(cell, before, {
9541
- formula: trimmedFormula || null,
9542
- value: trimmedFormula ? null : ""
9543
- });
9594
+ recordCellEditHistory(cell, before, after);
9544
9595
  }, [captureCellMutationState, getActiveWorksheet, maybeRecalculateWorkbook, readOnly, recordCellEditHistory, refreshWorkbookState, workbook]);
9596
+ const setCellStyle = React.useCallback((cell, style) => {
9597
+ const worksheet = getActiveWorksheet();
9598
+ if (readOnly || !worksheet || !workbook) {
9599
+ return;
9600
+ }
9601
+ const before = captureCellMutationState(cell);
9602
+ if (!before) {
9603
+ return;
9604
+ }
9605
+ worksheet.setCellStyleAt(cell.row, cell.col, style);
9606
+ const after = captureCellMutationState(cell);
9607
+ if (!after) {
9608
+ return;
9609
+ }
9610
+ refreshWorkbookState(workbook);
9611
+ recordCellEditHistory(cell, before, after);
9612
+ }, [captureCellMutationState, getActiveWorksheet, readOnly, recordCellEditHistory, refreshWorkbookState, workbook]);
9545
9613
  const setSelectedCellValue = React.useCallback((value) => {
9546
9614
  if (!activeCell) {
9547
9615
  return;
@@ -9554,6 +9622,51 @@ function useXlsxViewerController(options) {
9554
9622
  }
9555
9623
  setCellFormula(activeCell, formula);
9556
9624
  }, [activeCell, setCellFormula]);
9625
+ const setSelectedCellStyle = React.useCallback((style) => {
9626
+ if (!activeCell) {
9627
+ return;
9628
+ }
9629
+ setCellStyle(activeCell, style);
9630
+ }, [activeCell, setCellStyle]);
9631
+ const setRangeStyle = React.useCallback((range, style) => {
9632
+ const worksheet = getActiveWorksheet();
9633
+ if (readOnly || !worksheet || !workbook) {
9634
+ return;
9635
+ }
9636
+ const normalized = normalizeRange(range);
9637
+ const beforeStates = [];
9638
+ for (let row = normalized.start.row; row <= normalized.end.row; row += 1) {
9639
+ for (let col = normalized.start.col; col <= normalized.end.col; col += 1) {
9640
+ const cell = { row, col };
9641
+ const before = captureCellMutationState(cell);
9642
+ if (!before) {
9643
+ continue;
9644
+ }
9645
+ beforeStates.push({
9646
+ before,
9647
+ cell
9648
+ });
9649
+ }
9650
+ }
9651
+ if (beforeStates.length === 0) {
9652
+ return;
9653
+ }
9654
+ worksheet.setRangeStyle(rangeToA1(normalized), style);
9655
+ const mutations = [];
9656
+ for (const mutation of beforeStates) {
9657
+ const after = captureCellMutationState(mutation.cell);
9658
+ if (!after) {
9659
+ continue;
9660
+ }
9661
+ mutations.push({
9662
+ after,
9663
+ before: mutation.before,
9664
+ cell: mutation.cell
9665
+ });
9666
+ }
9667
+ refreshWorkbookState(workbook);
9668
+ recordRangeEditHistory(mutations, selection, activeCell);
9669
+ }, [activeCell, captureCellMutationState, getActiveWorksheet, readOnly, recordRangeEditHistory, refreshWorkbookState, selection, workbook]);
9557
9670
  const fillSelection = React.useCallback((targetRange) => {
9558
9671
  const worksheet = getActiveWorksheet();
9559
9672
  if (readOnly || !worksheet || !workbook || !selection) {
@@ -9580,19 +9693,22 @@ function useXlsxViewerController(options) {
9580
9693
  const sourceRow = sourceRange.start.row + (row - nextRange.start.row) % sourceHeight;
9581
9694
  const sourceCol = sourceRange.start.col + (col - nextRange.start.col) % sourceWidth;
9582
9695
  const sourceFormula = worksheet.getFormulaAt(sourceRow, sourceCol);
9696
+ const sourceStyle = cloneCellStyle(worksheet.getCellStyleAt(sourceRow, sourceCol));
9583
9697
  if (sourceFormula) {
9584
9698
  worksheet.setFormula(cellAddressToA1(targetCell), sourceFormula);
9585
- mutations.push({
9586
- after: { formula: sourceFormula, value: null },
9587
- before,
9588
- cell: targetCell
9589
- });
9699
+ } else {
9700
+ const sourceValue = normalizeCellValue(worksheet.getCellAt(sourceRow, sourceCol).toJs());
9701
+ worksheet.setCell(cellAddressToA1(targetCell), sourceValue);
9702
+ }
9703
+ if (sourceStyle && typeof sourceStyle === "object") {
9704
+ worksheet.setCellStyleAt(targetCell.row, targetCell.col, sourceStyle);
9705
+ }
9706
+ const after = captureCellMutationState(targetCell);
9707
+ if (!after) {
9590
9708
  continue;
9591
9709
  }
9592
- const sourceValue = normalizeCellValue(worksheet.getCellAt(sourceRow, sourceCol).toJs());
9593
- worksheet.setCell(cellAddressToA1(targetCell), sourceValue);
9594
9710
  mutations.push({
9595
- after: { formula: null, value: sourceValue },
9711
+ after,
9596
9712
  before,
9597
9713
  cell: targetCell
9598
9714
  });
@@ -9732,16 +9848,24 @@ function useXlsxViewerController(options) {
9732
9848
  }
9733
9849
  if (rawValue.startsWith("=") && rawValue.length > 1) {
9734
9850
  worksheet.setFormula(cellAddressToA1(nextCell), rawValue);
9851
+ const after = captureCellMutationState(nextCell);
9852
+ if (!after) {
9853
+ continue;
9854
+ }
9735
9855
  mutations.push({
9736
- after: { formula: rawValue, value: null },
9856
+ after,
9737
9857
  before,
9738
9858
  cell: nextCell
9739
9859
  });
9740
9860
  } else {
9741
9861
  const nextValue = coerceUserEnteredValue(rawValue);
9742
9862
  worksheet.setCell(cellAddressToA1(nextCell), nextValue);
9863
+ const after = captureCellMutationState(nextCell);
9864
+ if (!after) {
9865
+ continue;
9866
+ }
9743
9867
  mutations.push({
9744
- after: { formula: null, value: nextValue },
9868
+ after,
9745
9869
  before,
9746
9870
  cell: nextCell
9747
9871
  });
@@ -9792,8 +9916,12 @@ function useXlsxViewerController(options) {
9792
9916
  if (cell.formula) {
9793
9917
  worksheet.setFormula(cellAddressToA1(nextCell), cell.formula);
9794
9918
  if (before) {
9919
+ const after = captureCellMutationState(nextCell);
9920
+ if (!after) {
9921
+ continue;
9922
+ }
9795
9923
  mutations.push({
9796
- after: { formula: cell.formula, value: null },
9924
+ after,
9797
9925
  before,
9798
9926
  cell: nextCell
9799
9927
  });
@@ -9801,8 +9929,12 @@ function useXlsxViewerController(options) {
9801
9929
  } else {
9802
9930
  worksheet.setCell(cellAddressToA1(nextCell), cell.value);
9803
9931
  if (before) {
9932
+ const after = captureCellMutationState(nextCell);
9933
+ if (!after) {
9934
+ continue;
9935
+ }
9804
9936
  mutations.push({
9805
- after: { formula: null, value: cell.value },
9937
+ after,
9806
9938
  before,
9807
9939
  cell: nextCell
9808
9940
  });
@@ -10019,7 +10151,9 @@ function useXlsxViewerController(options) {
10019
10151
  resizeColumn,
10020
10152
  resizeRow,
10021
10153
  setCellFormula,
10154
+ setCellStyle,
10022
10155
  setCellValue,
10156
+ setRangeStyle,
10023
10157
  setZoomScale,
10024
10158
  setChartRect,
10025
10159
  setImageRect,
@@ -10038,6 +10172,7 @@ function useXlsxViewerController(options) {
10038
10172
  setActiveSheetIndex,
10039
10173
  setActiveTabIndex,
10040
10174
  setSelectedCellFormula,
10175
+ setSelectedCellStyle,
10041
10176
  setSelectedCellValue,
10042
10177
  sheets,
10043
10178
  shapes,
@@ -10121,7 +10256,9 @@ function useXlsxViewerController(options) {
10121
10256
  resizeColumn,
10122
10257
  resizeRow,
10123
10258
  setCellFormula,
10259
+ setCellStyle,
10124
10260
  setCellValue,
10261
+ setRangeStyle,
10125
10262
  setZoomScale,
10126
10263
  setChartRect,
10127
10264
  setImageRect,
@@ -10140,6 +10277,7 @@ function useXlsxViewerController(options) {
10140
10277
  setActiveSheetIndex,
10141
10278
  setActiveTabIndex,
10142
10279
  setSelectedCellFormula,
10280
+ setSelectedCellStyle,
10143
10281
  setSelectedCellValue,
10144
10282
  sheets,
10145
10283
  shapes,
@@ -16618,6 +16756,9 @@ var WHEEL_ZOOM_SENSITIVITY = 25e-5;
16618
16756
  var WHEEL_LINE_DELTA_PX = 16;
16619
16757
  var CHART_SOURCE_HIGHLIGHT_COLORS = ["#2563eb", "#dc2626", "#7c3aed", "#059669", "#ea580c", "#db2777"];
16620
16758
  var SHEET_SURFACE = "#ffffff";
16759
+ var DRAWING_SELECTION_STROKE = "#64748b";
16760
+ var DRAWING_SELECTION_HANDLE_FILL = "#ffffff";
16761
+ var DRAWING_SELECTION_HANDLE_SHADOW = "rgba(15, 23, 42, 0.18)";
16621
16762
  var DEFAULT_CELL_PADDING = "0 4px";
16622
16763
  var IMAGE_HANDLE_POSITIONS = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
16623
16764
  var CANVAS_CELL_STYLE_CACHE_LIMIT = 4096;
@@ -16703,6 +16844,38 @@ function measureCanvasTextWidth(context, text) {
16703
16844
  CANVAS_TEXT_MEASURE_CACHE_LIMIT
16704
16845
  );
16705
16846
  }
16847
+ function drawCanvasTextDecorations(context, {
16848
+ align,
16849
+ color,
16850
+ decoration,
16851
+ ellipsize = false,
16852
+ lineThroughY,
16853
+ maxWidth,
16854
+ text,
16855
+ textX,
16856
+ underlineY,
16857
+ zoomFactor
16858
+ }) {
16859
+ if (!decoration || text.length === 0) {
16860
+ return;
16861
+ }
16862
+ const measured = ellipsize && maxWidth !== void 0 ? Math.min(maxWidth, measureCanvasTextWidth(context, text)) : measureCanvasTextWidth(context, text);
16863
+ const startX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
16864
+ context.strokeStyle = color;
16865
+ context.lineWidth = Math.max(1, zoomFactor * 0.75);
16866
+ if (decoration.includes("underline")) {
16867
+ context.beginPath();
16868
+ context.moveTo(startX, underlineY);
16869
+ context.lineTo(startX + measured, underlineY);
16870
+ context.stroke();
16871
+ }
16872
+ if (decoration.includes("line-through")) {
16873
+ context.beginPath();
16874
+ context.moveTo(startX, lineThroughY);
16875
+ context.lineTo(startX + measured, lineThroughY);
16876
+ context.stroke();
16877
+ }
16878
+ }
16706
16879
  function getCachedCanvasPath2D(path) {
16707
16880
  if (typeof Path2D === "undefined") {
16708
16881
  return null;
@@ -17006,6 +17179,15 @@ function resolveCanvasLineHeight(style, fallbackFontSize = 12) {
17006
17179
  }
17007
17180
  return fontSizePx * 1.2;
17008
17181
  }
17182
+ function resolveCanvasTextMiddleY(verticalAlign, contentTop, contentHeight, lineHeight) {
17183
+ if (verticalAlign === "top") {
17184
+ return contentTop + lineHeight / 2;
17185
+ }
17186
+ if (verticalAlign === "middle") {
17187
+ return contentTop + contentHeight / 2;
17188
+ }
17189
+ return contentTop + contentHeight - lineHeight / 2;
17190
+ }
17009
17191
  function resolveCanvasWrapIndex(context, text, maxWidth) {
17010
17192
  if (text.length <= 1) {
17011
17193
  return text.length;
@@ -18748,7 +18930,9 @@ function resolveImageHandleStyle(position, stroke, surface, scale = 1) {
18748
18930
  const style = {
18749
18931
  backgroundColor: surface,
18750
18932
  border: `${Math.max(1, scale)}px solid ${stroke}`,
18751
- borderRadius: 6 * scale,
18933
+ borderRadius: 3 * scale,
18934
+ boxShadow: `0 1px ${3 * scale}px ${DRAWING_SELECTION_HANDLE_SHADOW}`,
18935
+ boxSizing: "border-box",
18752
18936
  cursor: IMAGE_HANDLE_CURSOR[position],
18753
18937
  height: handleSize,
18754
18938
  pointerEvents: "auto",
@@ -26236,8 +26420,9 @@ function XlsxGrid({
26236
26420
  const textColor = canvasCellStyle.textColor;
26237
26421
  const shouldEllipsizeText = canvasCellStyle.textOverflowEllipsis;
26238
26422
  const shouldWrapText = canvasCellStyle.usesWrappedText || rawText.includes("\n");
26423
+ const singleLineHeight = cellData.shrinkToFitFontSizePx ? resolveCanvasLineHeight(cellStyle, cellData.shrinkToFitFontSizePx) : resolveCanvasLineHeight(cellStyle, 12 * zoomFactor);
26239
26424
  if (cellData.textRotationDeg && rawText.length > 0) {
26240
- const textY = contentTop + contentHeight / 2;
26425
+ const textY = resolveCanvasTextMiddleY(cellStyle.verticalAlign, contentTop, contentHeight, singleLineHeight);
26241
26426
  const rotationOriginX = contentLeft + contentWidth / 2;
26242
26427
  paneContext.save();
26243
26428
  paneContext.translate(rotationOriginX, textY);
@@ -26263,20 +26448,21 @@ function XlsxGrid({
26263
26448
  wrappedLines.forEach((line, lineIndex) => {
26264
26449
  const textY = textBlockTop + lineIndex * lineHeight + lineHeight / 2;
26265
26450
  paneContext.fillText(line, textX, textY);
26266
- if (canvasCellStyle.textDecoration?.includes("underline") && line.length > 0) {
26267
- const measured = Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, line));
26268
- const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
26269
- paneContext.beginPath();
26270
- paneContext.moveTo(underlineStartX, textY + Math.max(2, lineHeight * 0.24));
26271
- paneContext.lineTo(underlineStartX + measured, textY + Math.max(2, lineHeight * 0.24));
26272
- paneContext.strokeStyle = textColor;
26273
- paneContext.lineWidth = Math.max(1, zoomFactor * 0.75);
26274
- paneContext.stroke();
26275
- }
26451
+ drawCanvasTextDecorations(paneContext, {
26452
+ align,
26453
+ color: textColor,
26454
+ decoration: canvasCellStyle.textDecoration,
26455
+ lineThroughY: textY,
26456
+ maxWidth: maxTextWidth,
26457
+ text: line,
26458
+ textX,
26459
+ underlineY: textY + Math.max(2, lineHeight * 0.24),
26460
+ zoomFactor
26461
+ });
26276
26462
  });
26277
26463
  } else if (spillMaxWidth != null) {
26278
26464
  const text = shouldEllipsizeText ? truncateCanvasText(paneContext, rawText, maxTextWidth) : rawText;
26279
- const textY = contentTop + contentHeight / 2;
26465
+ const textY = resolveCanvasTextMiddleY(cellStyle.verticalAlign, contentTop, contentHeight, singleLineHeight);
26280
26466
  deferredSpillTextsByPane[pane].push({
26281
26467
  align,
26282
26468
  clipHeight: contentHeight + textClipOverscan * 2,
@@ -26291,22 +26477,25 @@ function XlsxGrid({
26291
26477
  textDecoration: canvasCellStyle.textDecoration,
26292
26478
  textX,
26293
26479
  textY,
26480
+ lineThroughY: textY,
26294
26481
  underlineY: textY + 6 * zoomFactor
26295
26482
  });
26296
26483
  } else {
26297
26484
  const text = cellData.shrinkToFit ? rawText : shouldEllipsizeText ? truncateCanvasText(paneContext, rawText, maxTextWidth) : rawText;
26298
- const textY = contentTop + contentHeight / 2;
26485
+ const textY = resolveCanvasTextMiddleY(cellStyle.verticalAlign, contentTop, contentHeight, singleLineHeight);
26299
26486
  paneContext.fillText(text, textX, textY);
26300
- if (canvasCellStyle.textDecoration?.includes("underline") && text.length > 0) {
26301
- const measured = shouldEllipsizeText ? Math.min(maxTextWidth, measureCanvasTextWidth(paneContext, text)) : measureCanvasTextWidth(paneContext, text);
26302
- const underlineStartX = align === "right" ? textX - measured : align === "center" ? textX - measured / 2 : textX;
26303
- paneContext.beginPath();
26304
- paneContext.moveTo(underlineStartX, textY + 6 * zoomFactor);
26305
- paneContext.lineTo(underlineStartX + measured, textY + 6 * zoomFactor);
26306
- paneContext.strokeStyle = textColor;
26307
- paneContext.lineWidth = Math.max(1, zoomFactor * 0.75);
26308
- paneContext.stroke();
26309
- }
26487
+ drawCanvasTextDecorations(paneContext, {
26488
+ align,
26489
+ color: textColor,
26490
+ decoration: canvasCellStyle.textDecoration,
26491
+ ellipsize: shouldEllipsizeText,
26492
+ lineThroughY: textY,
26493
+ maxWidth: maxTextWidth,
26494
+ text,
26495
+ textX,
26496
+ underlineY: textY + 6 * zoomFactor,
26497
+ zoomFactor
26498
+ });
26310
26499
  }
26311
26500
  }
26312
26501
  if (cellData.conditionalIcon) {
@@ -26340,16 +26529,18 @@ function XlsxGrid({
26340
26529
  paneContext.textAlign = spillText.align;
26341
26530
  paneContext.textBaseline = "middle";
26342
26531
  paneContext.fillText(spillText.text, spillText.textX, spillText.textY);
26343
- if (spillText.textDecoration?.includes("underline") && spillText.text.length > 0) {
26344
- const measured = spillText.ellipsize ? Math.min(spillText.maxWidth, measureCanvasTextWidth(paneContext, spillText.text)) : measureCanvasTextWidth(paneContext, spillText.text);
26345
- const underlineStartX = spillText.align === "right" ? spillText.textX - measured : spillText.align === "center" ? spillText.textX - measured / 2 : spillText.textX;
26346
- paneContext.beginPath();
26347
- paneContext.moveTo(underlineStartX, spillText.underlineY);
26348
- paneContext.lineTo(underlineStartX + measured, spillText.underlineY);
26349
- paneContext.strokeStyle = spillText.color;
26350
- paneContext.lineWidth = Math.max(1, zoomFactor * 0.75);
26351
- paneContext.stroke();
26352
- }
26532
+ drawCanvasTextDecorations(paneContext, {
26533
+ align: spillText.align,
26534
+ color: spillText.color,
26535
+ decoration: spillText.textDecoration,
26536
+ ellipsize: spillText.ellipsize,
26537
+ lineThroughY: spillText.lineThroughY,
26538
+ maxWidth: spillText.maxWidth,
26539
+ text: spillText.text,
26540
+ textX: spillText.textX,
26541
+ underlineY: spillText.underlineY,
26542
+ zoomFactor
26543
+ });
26353
26544
  paneContext.restore();
26354
26545
  }
26355
26546
  }
@@ -27258,6 +27449,7 @@ function XlsxGrid({
27258
27449
  }
27259
27450
  const isFrozenDrawing = pane !== "scroll";
27260
27451
  const canEditImage = !readOnly && image.editable !== false;
27452
+ const drawingSelectionSurface = paletteIsDark(palette) ? palette.canvas : DRAWING_SELECTION_HANDLE_FILL;
27261
27453
  const style = {
27262
27454
  contain: "layout paint",
27263
27455
  height: rect.height,
@@ -27289,6 +27481,7 @@ function XlsxGrid({
27289
27481
  {
27290
27482
  style: {
27291
27483
  ...style,
27484
+ contain: "layout",
27292
27485
  overflow: "visible",
27293
27486
  pointerEvents: "none",
27294
27487
  zIndex: isFrozenDrawing ? image.zIndex + 22 : image.zIndex + 2
@@ -27298,8 +27491,8 @@ function XlsxGrid({
27298
27491
  "div",
27299
27492
  {
27300
27493
  style: {
27301
- border: `${Math.max(1, zoomFactor)}px solid ${selectionStroke}`,
27302
- boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${palette.surface}`,
27494
+ border: `${Math.max(1, zoomFactor)}px solid ${DRAWING_SELECTION_STROKE}`,
27495
+ boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${drawingSelectionSurface}`,
27303
27496
  boxSizing: "border-box",
27304
27497
  inset: 0,
27305
27498
  pointerEvents: "none",
@@ -27309,7 +27502,7 @@ function XlsxGrid({
27309
27502
  "div",
27310
27503
  {
27311
27504
  onPointerDown: (event) => startImageResize(event, image, rect, position),
27312
- style: resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor)
27505
+ style: resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor)
27313
27506
  },
27314
27507
  position
27315
27508
  )) : null
@@ -27321,7 +27514,7 @@ function XlsxGrid({
27321
27514
  startImageResize(event, image, rect, position);
27322
27515
  }
27323
27516
  },
27324
- style: canEditImage ? resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor) : { ...resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor), display: "none" }
27517
+ style: canEditImage ? resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor) : { ...resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor), display: "none" }
27325
27518
  }),
27326
27519
  image,
27327
27520
  rect
@@ -27329,8 +27522,8 @@ function XlsxGrid({
27329
27522
  "div",
27330
27523
  {
27331
27524
  style: {
27332
- border: `${Math.max(1, zoomFactor)}px solid ${selectionStroke}`,
27333
- boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${palette.surface}`,
27525
+ border: `${Math.max(1, zoomFactor)}px solid ${DRAWING_SELECTION_STROKE}`,
27526
+ boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${drawingSelectionSurface}`,
27334
27527
  boxSizing: "border-box",
27335
27528
  inset: 0,
27336
27529
  pointerEvents: "none",
@@ -27340,7 +27533,7 @@ function XlsxGrid({
27340
27533
  "div",
27341
27534
  {
27342
27535
  onPointerDown: (event) => startImageResize(event, image, rect, position),
27343
- style: resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor)
27536
+ style: resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor)
27344
27537
  },
27345
27538
  position
27346
27539
  )) : null
@@ -27378,6 +27571,7 @@ function XlsxGrid({
27378
27571
  }
27379
27572
  const isFrozenDrawing = pane !== "scroll";
27380
27573
  const canEditChart = !readOnly && chart.editable !== false;
27574
+ const drawingSelectionSurface = paletteIsDark(palette) ? palette.canvas : DRAWING_SELECTION_HANDLE_FILL;
27381
27575
  const style = {
27382
27576
  contain: "layout paint",
27383
27577
  height: rect.height,
@@ -27394,6 +27588,7 @@ function XlsxGrid({
27394
27588
  {
27395
27589
  style: {
27396
27590
  ...style,
27591
+ contain: "layout",
27397
27592
  overflow: "visible",
27398
27593
  pointerEvents: "none",
27399
27594
  zIndex: isFrozenDrawing ? chart.zIndex + 22 : chart.zIndex + 2
@@ -27402,8 +27597,8 @@ function XlsxGrid({
27402
27597
  "div",
27403
27598
  {
27404
27599
  style: {
27405
- border: `${Math.max(1, zoomFactor)}px solid ${selectionStroke}`,
27406
- boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${palette.surface}`,
27600
+ border: `${Math.max(1, zoomFactor)}px solid ${DRAWING_SELECTION_STROKE}`,
27601
+ boxShadow: `0 0 0 ${Math.max(1, zoomFactor)}px ${drawingSelectionSurface}`,
27407
27602
  boxSizing: "border-box",
27408
27603
  inset: 0,
27409
27604
  pointerEvents: "none",
@@ -27413,7 +27608,7 @@ function XlsxGrid({
27413
27608
  "div",
27414
27609
  {
27415
27610
  onPointerDown: (event) => startChartResize(event, chart, rect, position),
27416
- style: resolveImageHandleStyle(position, selectionStroke, palette.surface, zoomFactor)
27611
+ style: resolveImageHandleStyle(position, DRAWING_SELECTION_STROKE, drawingSelectionSurface, zoomFactor)
27417
27612
  },
27418
27613
  position
27419
27614
  )) : null
@@ -27693,6 +27888,22 @@ function XlsxGrid({
27693
27888
  }
27694
27889
  function installImageInteractionListeners(pointerId) {
27695
27890
  imageInteractionCleanupRef.current?.();
27891
+ const resolveInteractionRect = (interaction, clientX, clientY) => {
27892
+ const deltaX = clientX - interaction.startClientX;
27893
+ const deltaY = clientY - interaction.startClientY;
27894
+ return clampImageRect(
27895
+ interaction.type === "move" ? {
27896
+ ...interaction.baseRect,
27897
+ left: interaction.baseRect.left + deltaX,
27898
+ top: interaction.baseRect.top + deltaY
27899
+ } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, displayImageMinSize),
27900
+ {
27901
+ contentOffsetLeft: displayRowHeaderWidth,
27902
+ contentOffsetTop: displayHeaderHeight,
27903
+ minSizePx: displayImageMinSize
27904
+ }
27905
+ );
27906
+ };
27696
27907
  const handlePointerMove = (event) => {
27697
27908
  if (event.pointerId !== pointerId) {
27698
27909
  return;
@@ -27706,18 +27917,7 @@ function XlsxGrid({
27706
27917
  if (!interaction.didMove && (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3)) {
27707
27918
  interaction.didMove = true;
27708
27919
  }
27709
- const nextRect = clampImageRect(
27710
- interaction.type === "move" ? {
27711
- ...interaction.baseRect,
27712
- left: interaction.baseRect.left + deltaX,
27713
- top: interaction.baseRect.top + deltaY
27714
- } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, displayImageMinSize),
27715
- {
27716
- contentOffsetLeft: displayRowHeaderWidth,
27717
- contentOffsetTop: displayHeaderHeight,
27718
- minSizePx: displayImageMinSize
27719
- }
27720
- );
27920
+ const nextRect = resolveInteractionRect(interaction, event.clientX, event.clientY);
27721
27921
  scheduleImagePreviewRect({ id: interaction.imageId, rect: nextRect });
27722
27922
  };
27723
27923
  const cleanup = () => {
@@ -27740,18 +27940,20 @@ function XlsxGrid({
27740
27940
  imagePreviewRectRef.current = pendingPreview;
27741
27941
  setImagePreviewRect(pendingPreview);
27742
27942
  }
27743
- const preview = pendingPreview ?? imagePreviewRectRef.current;
27943
+ const finalRect = interaction ? resolveInteractionRect(interaction, event.clientX, event.clientY) : null;
27744
27944
  imageInteractionRef.current = null;
27745
27945
  imageInteractionCleanupRef.current = null;
27746
27946
  setInteractionMode("idle");
27747
27947
  document.body.style.cursor = "";
27748
27948
  document.body.style.userSelect = "";
27749
27949
  cleanup();
27750
- if (interaction && preview && preview.id === interaction.imageId) {
27950
+ if (interaction) {
27751
27951
  if (interaction.didMove) {
27752
27952
  skipNextImageClickRef.current = interaction.imageId;
27753
27953
  }
27754
- setImageRect(interaction.imageId, toLogicalRect(preview.rect));
27954
+ if (interaction.didMove && finalRect) {
27955
+ setImageRect(interaction.imageId, toLogicalRect(finalRect));
27956
+ }
27755
27957
  }
27756
27958
  imagePreviewRectRef.current = null;
27757
27959
  setImagePreviewRect(null);
@@ -27763,6 +27965,22 @@ function XlsxGrid({
27763
27965
  }
27764
27966
  function installChartInteractionListeners(pointerId) {
27765
27967
  chartInteractionCleanupRef.current?.();
27968
+ const resolveInteractionRect = (interaction, clientX, clientY) => {
27969
+ const deltaX = clientX - interaction.startClientX;
27970
+ const deltaY = clientY - interaction.startClientY;
27971
+ return clampImageRect(
27972
+ interaction.type === "move" ? {
27973
+ ...interaction.baseRect,
27974
+ left: interaction.baseRect.left + deltaX,
27975
+ top: interaction.baseRect.top + deltaY
27976
+ } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, 48 * zoomFactor),
27977
+ {
27978
+ contentOffsetLeft: displayRowHeaderWidth,
27979
+ contentOffsetTop: displayHeaderHeight,
27980
+ minSizePx: 48 * zoomFactor
27981
+ }
27982
+ );
27983
+ };
27766
27984
  const handlePointerMove = (event) => {
27767
27985
  if (event.pointerId !== pointerId) {
27768
27986
  return;
@@ -27776,18 +27994,7 @@ function XlsxGrid({
27776
27994
  if (!interaction.didMove && (Math.abs(deltaX) > 3 || Math.abs(deltaY) > 3)) {
27777
27995
  interaction.didMove = true;
27778
27996
  }
27779
- const nextRect = clampImageRect(
27780
- interaction.type === "move" ? {
27781
- ...interaction.baseRect,
27782
- left: interaction.baseRect.left + deltaX,
27783
- top: interaction.baseRect.top + deltaY
27784
- } : resizeImageRect(interaction.baseRect, interaction.handle, deltaX, deltaY, 48 * zoomFactor),
27785
- {
27786
- contentOffsetLeft: displayRowHeaderWidth,
27787
- contentOffsetTop: displayHeaderHeight,
27788
- minSizePx: 48 * zoomFactor
27789
- }
27790
- );
27997
+ const nextRect = resolveInteractionRect(interaction, event.clientX, event.clientY);
27791
27998
  scheduleChartPreviewRect({ id: interaction.chartId, rect: nextRect });
27792
27999
  };
27793
28000
  const cleanup = () => {
@@ -27810,18 +28017,20 @@ function XlsxGrid({
27810
28017
  chartPreviewRectRef.current = pendingPreview;
27811
28018
  setChartPreviewRect(pendingPreview);
27812
28019
  }
27813
- const preview = pendingPreview ?? chartPreviewRectRef.current;
28020
+ const finalRect = interaction ? resolveInteractionRect(interaction, event.clientX, event.clientY) : null;
27814
28021
  chartInteractionRef.current = null;
27815
28022
  chartInteractionCleanupRef.current = null;
27816
28023
  setInteractionMode("idle");
27817
28024
  document.body.style.cursor = "";
27818
28025
  document.body.style.userSelect = "";
27819
28026
  cleanup();
27820
- if (interaction && preview && preview.id === interaction.chartId) {
28027
+ if (interaction) {
27821
28028
  if (interaction.didMove) {
27822
28029
  skipNextChartClickRef.current = interaction.chartId;
27823
28030
  }
27824
- setChartRect(interaction.chartId, toLogicalRect(preview.rect));
28031
+ if (interaction.didMove && finalRect) {
28032
+ setChartRect(interaction.chartId, toLogicalRect(finalRect));
28033
+ }
27825
28034
  }
27826
28035
  chartPreviewRectRef.current = null;
27827
28036
  setChartPreviewRect(null);
@@ -28124,7 +28333,6 @@ function XlsxGrid({
28124
28333
  }
28125
28334
  gridKeyboardHandlerRef.current = handleGridKeyDown;
28126
28335
  const scrollerViewportProps = {
28127
- key: activeTabIndex,
28128
28336
  ref: scrollRef,
28129
28337
  "aria-colcount": Math.max(activeSheet?.colCount ?? 0, displayColLimit),
28130
28338
  "aria-keyshortcuts": "ArrowUp ArrowDown ArrowLeft ArrowRight Home End PageUp PageDown Control+Home Control+End",
@@ -28762,7 +28970,7 @@ function XlsxGrid({
28762
28970
  )
28763
28971
  }
28764
28972
  );
28765
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { backgroundColor: palette.canvas, display: "flex", flex: 1, minHeight: 0, minWidth: 0 }, children: renderScroller ? renderScroller({ children: scrollerContent, viewportProps: scrollerViewportProps }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ...scrollerViewportProps, children: scrollerContent }) });
28973
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { backgroundColor: palette.canvas, display: "flex", flex: 1, minHeight: 0, minWidth: 0 }, children: renderScroller ? renderScroller({ children: scrollerContent, viewportProps: scrollerViewportProps }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ...scrollerViewportProps, children: scrollerContent }, activeTabIndex) });
28766
28974
  }
28767
28975
  function XlsxViewerInner({
28768
28976
  allowResizeInReadOnly = false,
@@ -28967,8 +29175,11 @@ function useXlsxViewerEditing() {
28967
29175
  selectedFormula,
28968
29176
  selectedValue,
28969
29177
  setCellFormula,
29178
+ setCellStyle,
28970
29179
  setCellValue,
29180
+ setRangeStyle,
28971
29181
  setSelectedCellFormula,
29182
+ setSelectedCellStyle,
28972
29183
  setSelectedCellValue,
28973
29184
  undo,
28974
29185
  unmergeSelection
@@ -28995,8 +29206,11 @@ function useXlsxViewerEditing() {
28995
29206
  selectedFormula,
28996
29207
  selectedValue,
28997
29208
  setCellFormula,
29209
+ setCellStyle,
28998
29210
  setCellValue,
29211
+ setRangeStyle,
28999
29212
  setSelectedCellFormula,
29213
+ setSelectedCellStyle,
29000
29214
  setSelectedCellValue,
29001
29215
  undo,
29002
29216
  unmergeSelection
@@ -29022,8 +29236,11 @@ function useXlsxViewerEditing() {
29022
29236
  selectedFormula,
29023
29237
  selectedValue,
29024
29238
  setCellFormula,
29239
+ setCellStyle,
29025
29240
  setCellValue,
29241
+ setRangeStyle,
29026
29242
  setSelectedCellFormula,
29243
+ setSelectedCellStyle,
29027
29244
  setSelectedCellValue,
29028
29245
  undo,
29029
29246
  unmergeSelection
@@ -29694,13 +29911,14 @@ function useXlsxViewerThumbnails(options = {}) {
29694
29911
  const align = canvasCellStyle.textAlign;
29695
29912
  context.textAlign = align;
29696
29913
  const textX = align === "right" ? contentLeft + contentWidth : align === "center" ? contentLeft + contentWidth / 2 : contentLeft;
29697
- const textY = contentTop + contentHeight / 2;
29698
29914
  const trailingInset = cellData.conditionalIcon ? 18 : 0;
29699
29915
  const maxTextWidth = Math.max(0, contentWidth - trailingInset);
29916
+ const singleLineHeight = resolveCanvasLineHeight(cellData.style, 12);
29700
29917
  if (cellData.textRotationDeg) {
29918
+ const alignedTextY = resolveCanvasTextMiddleY(cellData.style.verticalAlign, contentTop, contentHeight, singleLineHeight);
29701
29919
  const rotationOriginX = contentLeft + contentWidth / 2;
29702
29920
  context.save();
29703
- context.translate(rotationOriginX, textY);
29921
+ context.translate(rotationOriginX, alignedTextY);
29704
29922
  context.rotate(cellData.textRotationDeg * Math.PI / 180);
29705
29923
  context.fillText(
29706
29924
  rawText,
@@ -29723,7 +29941,7 @@ function useXlsxViewerThumbnails(options = {}) {
29723
29941
  });
29724
29942
  } else {
29725
29943
  const text = canvasCellStyle.textOverflowEllipsis ? truncateCanvasText(context, rawText, maxTextWidth) : rawText;
29726
- context.fillText(text, textX, textY);
29944
+ context.fillText(text, textX, resolveCanvasTextMiddleY(cellData.style.verticalAlign, contentTop, contentHeight, singleLineHeight));
29727
29945
  }
29728
29946
  }
29729
29947
  if (cellData.conditionalIcon) {