@extend-ai/react-xlsx 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  useXlsxViewerImages: () => useXlsxViewerImages,
42
42
  useXlsxViewerSelection: () => useXlsxViewerSelection,
43
43
  useXlsxViewerTables: () => useXlsxViewerTables,
44
+ useXlsxViewerThumbnails: () => useXlsxViewerThumbnails,
44
45
  useXlsxViewerZoom: () => useXlsxViewerZoom
45
46
  });
46
47
  module.exports = __toCommonJS(index_exports);
@@ -16348,6 +16349,13 @@ var IMAGE_MIN_SIZE_PX = 16;
16348
16349
  var IMAGE_HANDLE_SIZE_PX = 10;
16349
16350
  var CANVAS_RESIZE_HIT_SLOP_PX = 8;
16350
16351
  var CANVAS_VIEWPORT_OVERSCAN_PX = 240;
16352
+ var THUMBNAIL_DEFAULT_MAX_DIMENSION = 192;
16353
+ var THUMBNAIL_FALLBACK_ROWS = 12;
16354
+ var THUMBNAIL_FALLBACK_COLS = 8;
16355
+ var THUMBNAIL_MAX_ROWS = 200;
16356
+ var THUMBNAIL_MAX_COLS = 80;
16357
+ var THUMBNAIL_MAX_SOURCE_HEIGHT_PX = 900;
16358
+ var THUMBNAIL_MAX_SOURCE_WIDTH_PX = 1440;
16351
16359
  var LIVE_ZOOM_COMMIT_IDLE_MS = 48;
16352
16360
  var WHEEL_ZOOM_SENSITIVITY = 25e-5;
16353
16361
  var WHEEL_LINE_DELTA_PX = 16;
@@ -18354,6 +18362,97 @@ function resolveImageHandleStyle(position, stroke, surface, scale = 1) {
18354
18362
  function useViewerPalette(isDark = false) {
18355
18363
  return isDark ? DARK_PALETTE : LIGHT_PALETTE;
18356
18364
  }
18365
+ function normalizeThumbnailResolution(resolution) {
18366
+ if (typeof resolution === "number" && Number.isFinite(resolution) && resolution > 0) {
18367
+ return {
18368
+ maxHeight: resolution,
18369
+ maxWidth: resolution
18370
+ };
18371
+ }
18372
+ const resolvedObject = typeof resolution === "object" && resolution ? resolution : void 0;
18373
+ return {
18374
+ maxHeight: resolvedObject?.maxHeight && Number.isFinite(resolvedObject.maxHeight) && resolvedObject.maxHeight > 0 ? resolvedObject.maxHeight : THUMBNAIL_DEFAULT_MAX_DIMENSION,
18375
+ maxWidth: resolvedObject?.maxWidth && Number.isFinite(resolvedObject.maxWidth) && resolvedObject.maxWidth > 0 ? resolvedObject.maxWidth : THUMBNAIL_DEFAULT_MAX_DIMENSION
18376
+ };
18377
+ }
18378
+ function resolveThumbnailOutputSize(sourceWidth, sourceHeight, resolution) {
18379
+ const normalized = normalizeThumbnailResolution(resolution);
18380
+ const safeSourceWidth = Math.max(1, sourceWidth);
18381
+ const safeSourceHeight = Math.max(1, sourceHeight);
18382
+ const scale = Math.min(
18383
+ normalized.maxWidth / safeSourceWidth,
18384
+ normalized.maxHeight / safeSourceHeight,
18385
+ 1
18386
+ );
18387
+ return {
18388
+ height: Math.max(1, Math.round(safeSourceHeight * scale)),
18389
+ maxHeight: normalized.maxHeight,
18390
+ maxWidth: normalized.maxWidth,
18391
+ scale,
18392
+ width: Math.max(1, Math.round(safeSourceWidth * scale))
18393
+ };
18394
+ }
18395
+ function resolveThumbnailAxisIndices({
18396
+ fallbackCount,
18397
+ getSizePx,
18398
+ maxCount,
18399
+ maxLogicalPixels,
18400
+ maxUsedIndex,
18401
+ minUsedIndex,
18402
+ precomputed
18403
+ }) {
18404
+ const sourceIndices = precomputed.length > 0 ? precomputed : Array.from({
18405
+ length: Math.max(fallbackCount, Math.max(0, maxUsedIndex + 1))
18406
+ }, (_, index) => index);
18407
+ const hasUsedRange = maxUsedIndex >= minUsedIndex && maxUsedIndex >= 0;
18408
+ const preferredStart = hasUsedRange ? Math.max(0, minUsedIndex) : 0;
18409
+ const preferredEnd = hasUsedRange ? maxUsedIndex : Math.max(0, preferredStart + fallbackCount - 1);
18410
+ const picked = [];
18411
+ let totalSize = 0;
18412
+ for (const actualIndex of sourceIndices) {
18413
+ if (actualIndex < preferredStart) {
18414
+ continue;
18415
+ }
18416
+ if (hasUsedRange && actualIndex > preferredEnd) {
18417
+ break;
18418
+ }
18419
+ const size = Math.max(1, getSizePx(actualIndex));
18420
+ picked.push(actualIndex);
18421
+ totalSize += size;
18422
+ if (picked.length >= maxCount || totalSize >= maxLogicalPixels) {
18423
+ break;
18424
+ }
18425
+ }
18426
+ if (picked.length > 0) {
18427
+ return picked;
18428
+ }
18429
+ for (const actualIndex of sourceIndices) {
18430
+ const size = Math.max(1, getSizePx(actualIndex));
18431
+ picked.push(actualIndex);
18432
+ totalSize += size;
18433
+ if (picked.length >= fallbackCount || picked.length >= maxCount || totalSize >= maxLogicalPixels) {
18434
+ break;
18435
+ }
18436
+ }
18437
+ return picked.length > 0 ? picked : [0];
18438
+ }
18439
+ function buildThumbnailAxisItems(actualIndices, getSizePx) {
18440
+ const items = [];
18441
+ let offset = 0;
18442
+ for (const actualIndex of actualIndices) {
18443
+ const size = Math.max(1, getSizePx(actualIndex));
18444
+ items.push({
18445
+ actualIndex,
18446
+ size,
18447
+ start: offset
18448
+ });
18449
+ offset += size;
18450
+ }
18451
+ return {
18452
+ items,
18453
+ totalSize: offset
18454
+ };
18455
+ }
18357
18456
  function columnLabel2(col) {
18358
18457
  let label = "";
18359
18458
  let nextValue = col;
@@ -19260,7 +19359,6 @@ function getTableHeaderColumn(table, row, col) {
19260
19359
  return table.columns[index] ?? null;
19261
19360
  }
19262
19361
  function DefaultTableHeaderMenu({
19263
- close,
19264
19362
  direction,
19265
19363
  sortAscending,
19266
19364
  sortDescending
@@ -19282,10 +19380,7 @@ function DefaultTableHeaderMenu({
19282
19380
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
19283
19381
  "button",
19284
19382
  {
19285
- onClick: () => {
19286
- sortAscending();
19287
- close();
19288
- },
19383
+ onClick: sortAscending,
19289
19384
  style: {
19290
19385
  background: direction === "ascending" ? "var(--xlsx-menu-active)" : "transparent",
19291
19386
  border: "none",
@@ -19303,10 +19398,7 @@ function DefaultTableHeaderMenu({
19303
19398
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
19304
19399
  "button",
19305
19400
  {
19306
- onClick: () => {
19307
- sortDescending();
19308
- close();
19309
- },
19401
+ onClick: sortDescending,
19310
19402
  style: {
19311
19403
  background: direction === "descending" ? "var(--xlsx-menu-active)" : "transparent",
19312
19404
  border: "none",
@@ -19325,6 +19417,25 @@ function DefaultTableHeaderMenu({
19325
19417
  }
19326
19418
  );
19327
19419
  }
19420
+ function resolveTableHeaderTriggerStyle(palette, zoomFactor) {
19421
+ return {
19422
+ alignItems: "center",
19423
+ background: "transparent",
19424
+ border: "none",
19425
+ color: palette.mutedText,
19426
+ cursor: "pointer",
19427
+ display: "inline-flex",
19428
+ fontSize: 10 * zoomFactor,
19429
+ height: 16 * zoomFactor,
19430
+ justifyContent: "center",
19431
+ padding: 0,
19432
+ position: "absolute",
19433
+ right: 4 * zoomFactor,
19434
+ top: 3 * zoomFactor,
19435
+ width: 16 * zoomFactor,
19436
+ zIndex: 6
19437
+ };
19438
+ }
19328
19439
  function SegmentedControl({
19329
19440
  items,
19330
19441
  onValueChange,
@@ -23539,6 +23650,30 @@ function XlsxGrid({
23539
23650
  return null;
23540
23651
  }
23541
23652
  const direction = sortState && sortState.tableName === table.name && sortState.columnIndex === tableColumn.index ? sortState.direction : null;
23653
+ const triggerIcon = direction === "ascending" ? "\u25B2" : direction === "descending" ? "\u25BC" : "\u25BE";
23654
+ if (renderTableHeaderMenu) {
23655
+ return renderTableHeaderMenu({
23656
+ cell,
23657
+ column: tableColumn,
23658
+ direction,
23659
+ sortAscending: () => sortTable(table.name, tableColumn.index, "ascending"),
23660
+ sortDescending: () => sortTable(table.name, tableColumn.index, "descending"),
23661
+ table,
23662
+ triggerIcon,
23663
+ triggerProps: {
23664
+ "aria-haspopup": "menu",
23665
+ "aria-label": `Open menu for ${tableColumn.name}`,
23666
+ onClick: (event) => {
23667
+ event.stopPropagation();
23668
+ },
23669
+ onPointerDown: (event) => {
23670
+ event.stopPropagation();
23671
+ },
23672
+ style: resolveTableHeaderTriggerStyle(palette, zoomFactor),
23673
+ type: "button"
23674
+ }
23675
+ });
23676
+ }
23542
23677
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
23543
23678
  "button",
23544
23679
  {
@@ -23554,27 +23689,13 @@ function XlsxGrid({
23554
23689
  );
23555
23690
  },
23556
23691
  style: {
23557
- alignItems: "center",
23558
- background: "transparent",
23559
- border: "none",
23560
- color: palette.mutedText,
23561
- cursor: "pointer",
23562
- display: "inline-flex",
23563
- fontSize: 10 * zoomFactor,
23564
- height: 16 * zoomFactor,
23565
- justifyContent: "center",
23566
- padding: 0,
23567
- position: "absolute",
23568
- right: 4 * zoomFactor,
23569
- top: 3 * zoomFactor,
23570
- width: 16 * zoomFactor,
23571
- zIndex: 6
23692
+ ...resolveTableHeaderTriggerStyle(palette, zoomFactor)
23572
23693
  },
23573
23694
  type: "button",
23574
- children: direction === "ascending" ? "\u25B2" : direction === "descending" ? "\u25BC" : "\u25BE"
23695
+ children: triggerIcon
23575
23696
  }
23576
23697
  );
23577
- }, [effectiveTables, palette.mutedText, sortState, zoomFactor]);
23698
+ }, [effectiveTables, palette, renderTableHeaderMenu, sortState, sortTable, zoomFactor]);
23578
23699
  const resolveCanvasColumnHeaderRect = React4.useCallback((actualCol) => {
23579
23700
  const colIndex = colIndexByActual.get(actualCol);
23580
23701
  if (colIndex === void 0) {
@@ -26370,7 +26491,7 @@ function XlsxGrid({
26370
26491
  }
26371
26492
  }
26372
26493
  ) : null,
26373
- openTableMenuState ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26494
+ !renderTableHeaderMenu && openTableMenuState ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26374
26495
  "div",
26375
26496
  {
26376
26497
  ref: tableMenuRef,
@@ -26381,22 +26502,23 @@ function XlsxGrid({
26381
26502
  top: openTableMenuState.top,
26382
26503
  zIndex: 50
26383
26504
  },
26384
- children: renderTableHeaderMenu ? renderTableHeaderMenu({
26385
- close: () => setOpenTableMenu(null),
26386
- column: openTableMenuState.column,
26387
- direction: sortState && sortState.tableName === openTableMenuState.table.name && sortState.columnIndex === openTableMenuState.column.index ? sortState.direction : null,
26388
- sortAscending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "ascending"),
26389
- sortDescending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "descending"),
26390
- table: openTableMenuState.table
26391
- }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26505
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
26392
26506
  DefaultTableHeaderMenu,
26393
26507
  {
26394
- close: () => setOpenTableMenu(null),
26508
+ cell: { col: openTableMenuState.column.index + openTableMenuState.table.start.col, row: openTableMenuState.table.start.row },
26395
26509
  column: openTableMenuState.column,
26396
26510
  direction: sortState && sortState.tableName === openTableMenuState.table.name && sortState.columnIndex === openTableMenuState.column.index ? sortState.direction : null,
26397
- sortAscending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "ascending"),
26398
- sortDescending: () => sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "descending"),
26399
- table: openTableMenuState.table
26511
+ sortAscending: () => {
26512
+ sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "ascending");
26513
+ setOpenTableMenu(null);
26514
+ },
26515
+ sortDescending: () => {
26516
+ sortTable(openTableMenuState.table.name, openTableMenuState.column.index, "descending");
26517
+ setOpenTableMenu(null);
26518
+ },
26519
+ table: openTableMenuState.table,
26520
+ triggerIcon: "\u25BE",
26521
+ triggerProps: { type: "button" }
26400
26522
  }
26401
26523
  )
26402
26524
  }
@@ -26827,6 +26949,476 @@ function useXlsxViewerCharts() {
26827
26949
  ]
26828
26950
  );
26829
26951
  }
26952
+ function useXlsxViewerThumbnails(options = {}) {
26953
+ const { workbook, sheets } = useXlsxViewer();
26954
+ const { isDark } = React4.useContext(ViewerAppearanceContext);
26955
+ const palette = useViewerPalette(isDark);
26956
+ const includeHeaders = options.includeHeaders ?? true;
26957
+ const resolution = options.resolution;
26958
+ const thumbnails = React4.useMemo(() => {
26959
+ return sheets.map((sheet, sheetIndex) => {
26960
+ const worksheet = workbook?.getSheet(sheet.workbookSheetIndex) ?? null;
26961
+ const showGridLines = sheet.showGridLines ?? true;
26962
+ const hiddenRowSet = new Set(sheet.hiddenRows ?? []);
26963
+ const hiddenColSet = new Set(sheet.hiddenCols ?? []);
26964
+ const visibleRows = buildVisibleAxisIndices(
26965
+ sheet.visibleRows ?? [],
26966
+ Math.max(THUMBNAIL_FALLBACK_ROWS, (sheet.maxUsedRow ?? -1) + THUMBNAIL_FALLBACK_ROWS + 1),
26967
+ sheet.maxUsedRow ?? -1,
26968
+ hiddenRowSet
26969
+ );
26970
+ const visibleCols = buildVisibleAxisIndices(
26971
+ sheet.visibleCols ?? [],
26972
+ Math.max(THUMBNAIL_FALLBACK_COLS, (sheet.maxUsedCol ?? -1) + THUMBNAIL_FALLBACK_COLS + 1),
26973
+ sheet.maxUsedCol ?? -1,
26974
+ hiddenColSet
26975
+ );
26976
+ const resolveColumnWidthPx = (actualCol) => {
26977
+ if (worksheet && !worksheet.isColumnHidden(actualCol)) {
26978
+ const width = worksheet.getColumnWidth(actualCol);
26979
+ if (width !== void 0 && width !== null) {
26980
+ return Math.max(
26981
+ resolveRenderedSheetAxisPixels(resolveSheetColumnWidthPixels(width, sheet.columnWidthCharacterWidthPx), showGridLines),
26982
+ DEFAULT_COL_WIDTH2 / 2
26983
+ );
26984
+ }
26985
+ }
26986
+ return Math.max(
26987
+ resolveRenderedSheetAxisPixels(
26988
+ sheet.colWidthOverridesPx[actualCol] ?? sheet.defaultColWidthPx ?? DEFAULT_COL_WIDTH2,
26989
+ showGridLines
26990
+ ),
26991
+ DEFAULT_COL_WIDTH2 / 2
26992
+ );
26993
+ };
26994
+ const resolveRowHeightPx = (actualRow) => {
26995
+ if (worksheet && !worksheet.isRowHidden(actualRow)) {
26996
+ const height = worksheet.getRowHeight(actualRow);
26997
+ if (height !== void 0 && height !== null) {
26998
+ return Math.max(
26999
+ resolveRenderedSheetAxisPixels(resolveSheetRowHeightPixels(height), showGridLines),
27000
+ DEFAULT_ROW_HEIGHT2 / 1.5
27001
+ );
27002
+ }
27003
+ }
27004
+ return Math.max(
27005
+ resolveRenderedSheetAxisPixels(
27006
+ sheet.rowHeightOverridesPx[actualRow] ?? sheet.defaultRowHeightPx ?? DEFAULT_ROW_HEIGHT2,
27007
+ showGridLines
27008
+ ),
27009
+ DEFAULT_ROW_HEIGHT2 / 1.5
27010
+ );
27011
+ };
27012
+ const previewRows = resolveThumbnailAxisIndices({
27013
+ fallbackCount: THUMBNAIL_FALLBACK_ROWS,
27014
+ getSizePx: resolveRowHeightPx,
27015
+ maxCount: THUMBNAIL_MAX_ROWS,
27016
+ maxLogicalPixels: THUMBNAIL_MAX_SOURCE_HEIGHT_PX,
27017
+ maxUsedIndex: sheet.maxUsedRow ?? -1,
27018
+ minUsedIndex: Math.max(0, sheet.minUsedRow ?? 0),
27019
+ precomputed: visibleRows
27020
+ });
27021
+ const previewCols = resolveThumbnailAxisIndices({
27022
+ fallbackCount: THUMBNAIL_FALLBACK_COLS,
27023
+ getSizePx: resolveColumnWidthPx,
27024
+ maxCount: THUMBNAIL_MAX_COLS,
27025
+ maxLogicalPixels: THUMBNAIL_MAX_SOURCE_WIDTH_PX,
27026
+ maxUsedIndex: sheet.maxUsedCol ?? -1,
27027
+ minUsedIndex: Math.max(0, sheet.minUsedCol ?? 0),
27028
+ precomputed: visibleCols
27029
+ });
27030
+ const rowAxis = buildThumbnailAxisItems(previewRows, resolveRowHeightPx);
27031
+ const colAxis = buildThumbnailAxisItems(previewCols, resolveColumnWidthPx);
27032
+ const rowItemByActual = new Map(rowAxis.items.map((item) => [item.actualIndex, item]));
27033
+ const colItemByActual = new Map(colAxis.items.map((item) => [item.actualIndex, item]));
27034
+ const headerHeight = includeHeaders ? HEADER_HEIGHT : 0;
27035
+ const rowHeaderWidth = includeHeaders ? ROW_HEADER_WIDTH : 0;
27036
+ const sourceWidth = rowHeaderWidth + colAxis.totalSize;
27037
+ const sourceHeight = headerHeight + rowAxis.totalSize;
27038
+ const outputSize = resolveThumbnailOutputSize(sourceWidth, sourceHeight, resolution);
27039
+ const sourceRange = {
27040
+ start: {
27041
+ col: previewCols[0] ?? 0,
27042
+ row: previewRows[0] ?? 0
27043
+ },
27044
+ end: {
27045
+ col: previewCols[previewCols.length - 1] ?? 0,
27046
+ row: previewRows[previewRows.length - 1] ?? 0
27047
+ }
27048
+ };
27049
+ const sparklineByCell = new Map(
27050
+ (sheet.sparklines ?? []).map((sparkline) => [`${sparkline.target.row}:${sparkline.target.col}`, sparkline])
27051
+ );
27052
+ const paint = (canvas) => {
27053
+ if (!canvas) {
27054
+ return false;
27055
+ }
27056
+ const context = canvas.getContext("2d");
27057
+ if (!context) {
27058
+ return false;
27059
+ }
27060
+ const dpr = typeof window === "undefined" ? 1 : Math.max(1, window.devicePixelRatio || 1);
27061
+ const outputWidth = outputSize.width;
27062
+ const outputHeight = outputSize.height;
27063
+ const scale = outputSize.scale;
27064
+ const deviceWidth = Math.max(1, Math.round(outputWidth * dpr));
27065
+ const deviceHeight = Math.max(1, Math.round(outputHeight * dpr));
27066
+ if (canvas.width !== deviceWidth) {
27067
+ canvas.width = deviceWidth;
27068
+ }
27069
+ if (canvas.height !== deviceHeight) {
27070
+ canvas.height = deviceHeight;
27071
+ }
27072
+ if (canvas.style.width !== `${outputWidth}px`) {
27073
+ canvas.style.width = `${outputWidth}px`;
27074
+ }
27075
+ if (canvas.style.height !== `${outputHeight}px`) {
27076
+ canvas.style.height = `${outputHeight}px`;
27077
+ }
27078
+ context.setTransform(dpr * scale, 0, 0, dpr * scale, 0, 0);
27079
+ context.clearRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
27080
+ context.fillStyle = palette.canvas;
27081
+ context.fillRect(0, 0, Math.max(1, sourceWidth), Math.max(1, sourceHeight));
27082
+ context.fillStyle = resolveSheetSurface(sheet, palette);
27083
+ context.fillRect(rowHeaderWidth, headerHeight, Math.max(1, colAxis.totalSize), Math.max(1, rowAxis.totalSize));
27084
+ if (includeHeaders) {
27085
+ context.fillStyle = palette.headerSurface;
27086
+ context.fillRect(rowHeaderWidth, 0, Math.max(1, colAxis.totalSize), headerHeight);
27087
+ context.fillStyle = palette.rowHeaderSurface;
27088
+ context.fillRect(0, headerHeight, rowHeaderWidth, Math.max(1, rowAxis.totalSize));
27089
+ context.fillRect(0, 0, rowHeaderWidth, headerHeight);
27090
+ }
27091
+ if (!worksheet) {
27092
+ return true;
27093
+ }
27094
+ const conditionalFormatMetricsCache = /* @__PURE__ */ new Map();
27095
+ const cellRenderCache = /* @__PURE__ */ new Map();
27096
+ const getCellData = (row, col) => {
27097
+ const cacheKey = `${row}:${col}`;
27098
+ const cached = cellRenderCache.get(cacheKey);
27099
+ if (cached) {
27100
+ return cached;
27101
+ }
27102
+ if (worksheet.isMergedSecondary(row, col)) {
27103
+ const mergedSecondaryData = {
27104
+ isMergedSecondary: true,
27105
+ style: {},
27106
+ value: ""
27107
+ };
27108
+ cellRenderCache.set(cacheKey, mergedSecondaryData);
27109
+ return mergedSecondaryData;
27110
+ }
27111
+ const merge = worksheet.getMergeSpan(row, col);
27112
+ const inheritedStyle = resolveInheritedCellStyle2(sheet, row, col);
27113
+ const worksheetStyle = worksheet.getCellStyleAt(row, col) ?? null;
27114
+ const mergedStyle = mergeResolvedCellStyle(inheritedStyle, worksheetStyle, { replaceXfSubtrees: true });
27115
+ const alignment = mergedStyle?.alignment;
27116
+ const sparkline = sparklineByCell.get(cacheKey) ?? null;
27117
+ const sparklineValues = sparkline ? sparkline.range.start.row === sparkline.range.end.row ? Array.from(
27118
+ { length: Math.abs(sparkline.range.end.col - sparkline.range.start.col) + 1 },
27119
+ (_, index) => getCellNumericValue(
27120
+ worksheet,
27121
+ sparkline.range.start.row,
27122
+ Math.min(sparkline.range.start.col, sparkline.range.end.col) + index
27123
+ )
27124
+ ) : Array.from(
27125
+ { length: Math.abs(sparkline.range.end.row - sparkline.range.start.row) + 1 },
27126
+ (_, index) => getCellNumericValue(
27127
+ worksheet,
27128
+ Math.min(sparkline.range.start.row, sparkline.range.end.row) + index,
27129
+ sparkline.range.start.col
27130
+ )
27131
+ ) : null;
27132
+ const checkboxState = mergedStyle?.cellControl ? getCellBooleanValue(worksheet, row, col) : null;
27133
+ const nextData = {
27134
+ canvas: void 0,
27135
+ checkboxState,
27136
+ colSpan: merge?.colSpan,
27137
+ conditionalColorScale: resolveConditionalColorScaleForCell(
27138
+ row,
27139
+ col,
27140
+ worksheet,
27141
+ sheet,
27142
+ conditionalFormatMetricsCache
27143
+ ),
27144
+ conditionalDataBar: resolveConditionalDataBarForCell(
27145
+ row,
27146
+ col,
27147
+ worksheet,
27148
+ sheet,
27149
+ conditionalFormatMetricsCache
27150
+ ),
27151
+ conditionalIcon: resolveConditionalIconForCell(
27152
+ row,
27153
+ col,
27154
+ worksheet,
27155
+ sheet,
27156
+ conditionalFormatMetricsCache
27157
+ ),
27158
+ isMergedSecondary: false,
27159
+ rowSpan: merge?.rowSpan,
27160
+ sparkline: sparkline && sparklineValues ? { config: sparkline, values: sparklineValues } : null,
27161
+ style: buildCellStyle(mergedStyle, palette, sheet.themePalette, {
27162
+ showGridLines
27163
+ }),
27164
+ textRotationDeg: resolveSpreadsheetTextRotation(alignment?.textRotation),
27165
+ value: sparkline ? "" : checkboxState !== null ? "" : getCellDisplayValue(worksheet, row, col, sheet)
27166
+ };
27167
+ nextData.canvas = buildCanvasCellStyleCache(nextData.style);
27168
+ cellRenderCache.set(cacheKey, nextData);
27169
+ return nextData;
27170
+ };
27171
+ for (const rowItem of rowAxis.items) {
27172
+ for (const colItem of colAxis.items) {
27173
+ const cellData = getCellData(rowItem.actualIndex, colItem.actualIndex);
27174
+ if (cellData.isMergedSecondary) {
27175
+ continue;
27176
+ }
27177
+ const colSpan = Math.max(1, cellData.colSpan ?? 1);
27178
+ const rowSpan = Math.max(1, cellData.rowSpan ?? 1);
27179
+ let width = colItem.size;
27180
+ let height = rowItem.size;
27181
+ for (let colOffset = 1; colOffset < colSpan; colOffset += 1) {
27182
+ const nextCol = colItemByActual.get(colItem.actualIndex + colOffset);
27183
+ if (!nextCol) {
27184
+ break;
27185
+ }
27186
+ width += nextCol.size;
27187
+ }
27188
+ for (let rowOffset = 1; rowOffset < rowSpan; rowOffset += 1) {
27189
+ const nextRow = rowItemByActual.get(rowItem.actualIndex + rowOffset);
27190
+ if (!nextRow) {
27191
+ break;
27192
+ }
27193
+ height += nextRow.size;
27194
+ }
27195
+ const rect = {
27196
+ height,
27197
+ left: rowHeaderWidth + colItem.start,
27198
+ top: headerHeight + rowItem.start,
27199
+ width
27200
+ };
27201
+ const canvasCellStyle = cellData.canvas ?? buildCanvasCellStyleCache(cellData.style);
27202
+ const gradientFill = typeof cellData.style.backgroundImage === "string" ? resolveCanvasGradientFill(context, rect, cellData.style.backgroundImage) : null;
27203
+ const fillColor = cellData.conditionalColorScale?.color ?? (typeof cellData.style.backgroundColor === "string" ? cellData.style.backgroundColor : resolveSheetSurface(sheet, palette));
27204
+ const hasExplicitCellFill = cellData.conditionalColorScale !== null || gradientFill !== null || typeof cellData.style.backgroundColor === "string" && cellData.style.backgroundColor !== resolveSheetSurface(sheet, palette);
27205
+ context.fillStyle = gradientFill ?? fillColor;
27206
+ context.fillRect(rect.left, rect.top, rect.width, rect.height);
27207
+ if (cellData.conditionalDataBar) {
27208
+ const barLeft = rect.left + 4;
27209
+ const barTop = rect.top + 4;
27210
+ const barWidth = Math.max(0, (rect.width - 8) * (cellData.conditionalDataBar.widthPercent / 100));
27211
+ const barHeight = Math.max(0, rect.height - 8);
27212
+ if (barWidth > 0 && barHeight > 0) {
27213
+ context.fillStyle = resolveCanvasDataBarFill(
27214
+ context,
27215
+ barLeft,
27216
+ barTop,
27217
+ barWidth,
27218
+ barHeight,
27219
+ cellData.conditionalDataBar
27220
+ );
27221
+ context.fillRect(barLeft, barTop, barWidth, barHeight);
27222
+ if (cellData.conditionalDataBar.border !== false && cellData.conditionalDataBar.borderColor) {
27223
+ context.strokeStyle = cellData.conditionalDataBar.borderColor;
27224
+ context.lineWidth = 1;
27225
+ context.strokeRect(barLeft + 0.5, barTop + 0.5, Math.max(0, barWidth - 1), Math.max(0, barHeight - 1));
27226
+ }
27227
+ }
27228
+ }
27229
+ if (showGridLines && !hasExplicitCellFill) {
27230
+ context.strokeStyle = palette.border;
27231
+ context.lineWidth = 1;
27232
+ context.beginPath();
27233
+ if (colItem.start === 0) {
27234
+ context.moveTo(rect.left + 0.5, rect.top);
27235
+ context.lineTo(rect.left + 0.5, rect.top + rect.height);
27236
+ }
27237
+ if (rowItem.start === 0) {
27238
+ context.moveTo(rect.left, rect.top + 0.5);
27239
+ context.lineTo(rect.left + rect.width, rect.top + 0.5);
27240
+ }
27241
+ context.moveTo(rect.left + rect.width - 0.5, rect.top);
27242
+ context.lineTo(rect.left + rect.width - 0.5, rect.top + rect.height);
27243
+ context.moveTo(rect.left, rect.top + rect.height - 0.5);
27244
+ context.lineTo(rect.left + rect.width, rect.top + rect.height - 0.5);
27245
+ context.stroke();
27246
+ }
27247
+ if (canvasCellStyle.topBorder) {
27248
+ strokeCanvasBorderSide(context, "top", rect, canvasCellStyle.topBorder);
27249
+ }
27250
+ if (canvasCellStyle.rightBorder) {
27251
+ strokeCanvasBorderSide(context, "right", rect, canvasCellStyle.rightBorder);
27252
+ }
27253
+ if (canvasCellStyle.bottomBorder) {
27254
+ strokeCanvasBorderSide(context, "bottom", rect, canvasCellStyle.bottomBorder);
27255
+ }
27256
+ if (canvasCellStyle.leftBorder) {
27257
+ strokeCanvasBorderSide(context, "left", rect, canvasCellStyle.leftBorder);
27258
+ }
27259
+ const padding = canvasCellStyle.padding;
27260
+ const contentLeft = rect.left + padding.left;
27261
+ const contentTop = rect.top + padding.top;
27262
+ const contentWidth = Math.max(0, rect.width - padding.left - padding.right);
27263
+ const contentHeight = Math.max(0, rect.height - padding.top - padding.bottom);
27264
+ const rawText = cellData.value ?? "";
27265
+ context.save();
27266
+ context.beginPath();
27267
+ context.rect(contentLeft, contentTop, contentWidth, contentHeight);
27268
+ context.clip();
27269
+ context.font = canvasCellStyle.baseFont;
27270
+ context.fillStyle = canvasCellStyle.textColor;
27271
+ context.textBaseline = "middle";
27272
+ if (cellData.checkboxState != null) {
27273
+ const boxSize = Math.min(14, contentWidth, contentHeight);
27274
+ const boxLeft = rect.left + (rect.width - boxSize) / 2;
27275
+ const boxTop = rect.top + (rect.height - boxSize) / 2;
27276
+ context.strokeStyle = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
27277
+ context.lineWidth = 1.25;
27278
+ context.strokeRect(boxLeft, boxTop, boxSize, boxSize);
27279
+ if (cellData.checkboxState) {
27280
+ context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
27281
+ context.fillRect(boxLeft + 1.5, boxTop + 1.5, Math.max(0, boxSize - 3), Math.max(0, boxSize - 3));
27282
+ }
27283
+ } else if (cellData.sparkline) {
27284
+ const sparkline = cellData.sparkline.config;
27285
+ const sparklineValues = cellData.sparkline.values;
27286
+ const points = sparklineValues.map((value, index) => ({ index, value })).filter((entry) => typeof entry.value === "number" && Number.isFinite(entry.value));
27287
+ if (points.length > 1) {
27288
+ const minValue = Math.min(...points.map((entry) => entry.value));
27289
+ const maxValue = Math.max(...points.map((entry) => entry.value));
27290
+ const sparkLeft = contentLeft + 1;
27291
+ const sparkTop = contentTop + 2;
27292
+ const sparkWidth = Math.max(1, contentWidth - 2);
27293
+ const sparkHeight = Math.max(1, contentHeight - 4);
27294
+ const xStep = points.length > 1 ? sparkWidth / (points.length - 1) : 0;
27295
+ context.strokeStyle = sparkline.color ?? "#2563eb";
27296
+ context.lineCap = "round";
27297
+ context.lineJoin = "round";
27298
+ context.lineWidth = 1.25;
27299
+ context.beginPath();
27300
+ points.forEach((entry, index) => {
27301
+ const x = sparkLeft + index * xStep;
27302
+ const y = sparkTop + sparkHeight - clampSparklineValue(entry.value, minValue, maxValue) * sparkHeight;
27303
+ if (index === 0) {
27304
+ context.moveTo(x, y);
27305
+ } else {
27306
+ context.lineTo(x, y);
27307
+ }
27308
+ });
27309
+ context.stroke();
27310
+ }
27311
+ } else if (rawText.length > 0) {
27312
+ const align = canvasCellStyle.textAlign;
27313
+ context.textAlign = align;
27314
+ const textX = align === "right" ? contentLeft + contentWidth : align === "center" ? contentLeft + contentWidth / 2 : contentLeft;
27315
+ const textY = contentTop + contentHeight / 2;
27316
+ const trailingInset = cellData.conditionalIcon ? 18 : 0;
27317
+ const maxTextWidth = Math.max(0, contentWidth - trailingInset);
27318
+ if (cellData.textRotationDeg) {
27319
+ const rotationOriginX = contentLeft + contentWidth / 2;
27320
+ context.save();
27321
+ context.translate(rotationOriginX, textY);
27322
+ context.rotate(cellData.textRotationDeg * Math.PI / 180);
27323
+ context.fillText(
27324
+ rawText,
27325
+ align === "right" ? contentWidth / 2 : align === "center" ? 0 : -(contentWidth / 2),
27326
+ 0
27327
+ );
27328
+ context.restore();
27329
+ } else if (canvasCellStyle.usesWrappedText || rawText.includes("\n")) {
27330
+ const lines = wrapCanvasText(context, rawText, maxTextWidth);
27331
+ const lineHeight = resolveCanvasLineHeight(cellData.style, 12);
27332
+ const textBlockHeight = lines.length * lineHeight;
27333
+ let textBlockTop = contentTop;
27334
+ if (cellData.style.verticalAlign === "middle") {
27335
+ textBlockTop = contentTop + (contentHeight - textBlockHeight) / 2;
27336
+ } else if (cellData.style.verticalAlign !== "top") {
27337
+ textBlockTop = contentTop + contentHeight - textBlockHeight;
27338
+ }
27339
+ lines.forEach((line, lineIndex) => {
27340
+ context.fillText(line, textX, textBlockTop + lineIndex * lineHeight + lineHeight / 2);
27341
+ });
27342
+ } else {
27343
+ const text = canvasCellStyle.textOverflowEllipsis ? truncateCanvasText(context, rawText, maxTextWidth) : rawText;
27344
+ context.fillText(text, textX, textY);
27345
+ }
27346
+ }
27347
+ if (cellData.conditionalIcon) {
27348
+ const iconSize = 10;
27349
+ const iconX = rect.left + rect.width - (padding.right + iconSize + 4);
27350
+ const iconY = rect.top + rect.height / 2;
27351
+ drawCanvasConditionalIcon(context, cellData.conditionalIcon, iconX + iconSize / 2, iconY, iconSize);
27352
+ }
27353
+ context.restore();
27354
+ }
27355
+ }
27356
+ if (includeHeaders) {
27357
+ context.strokeStyle = palette.border;
27358
+ context.lineWidth = 1;
27359
+ context.font = "600 11px ui-sans-serif, system-ui, sans-serif";
27360
+ context.fillStyle = palette.mutedText;
27361
+ context.textBaseline = "middle";
27362
+ for (const colItem of colAxis.items) {
27363
+ const left = rowHeaderWidth + colItem.start;
27364
+ context.beginPath();
27365
+ context.moveTo(left + colItem.size - 0.5, 0);
27366
+ context.lineTo(left + colItem.size - 0.5, headerHeight);
27367
+ context.moveTo(left, headerHeight - 0.5);
27368
+ context.lineTo(left + colItem.size, headerHeight - 0.5);
27369
+ context.stroke();
27370
+ context.textAlign = "center";
27371
+ context.fillText(columnLabel2(colItem.actualIndex), left + colItem.size / 2, headerHeight / 2);
27372
+ }
27373
+ for (const rowItem of rowAxis.items) {
27374
+ const top = headerHeight + rowItem.start;
27375
+ context.beginPath();
27376
+ context.moveTo(0, top + rowItem.size - 0.5);
27377
+ context.lineTo(rowHeaderWidth, top + rowItem.size - 0.5);
27378
+ context.moveTo(rowHeaderWidth - 0.5, top);
27379
+ context.lineTo(rowHeaderWidth - 0.5, top + rowItem.size);
27380
+ context.stroke();
27381
+ context.textAlign = "center";
27382
+ context.fillText(`${rowItem.actualIndex + 1}`, rowHeaderWidth / 2, top + rowItem.size / 2);
27383
+ }
27384
+ context.beginPath();
27385
+ context.moveTo(rowHeaderWidth - 0.5, 0);
27386
+ context.lineTo(rowHeaderWidth - 0.5, headerHeight);
27387
+ context.moveTo(0, headerHeight - 0.5);
27388
+ context.lineTo(rowHeaderWidth, headerHeight - 0.5);
27389
+ context.stroke();
27390
+ }
27391
+ return true;
27392
+ };
27393
+ const plan = {
27394
+ aspectRatio: sourceWidth / Math.max(1, sourceHeight),
27395
+ contentHeight: rowAxis.totalSize,
27396
+ contentWidth: colAxis.totalSize,
27397
+ height: outputSize.height,
27398
+ paint,
27399
+ sourceRange,
27400
+ width: outputSize.width
27401
+ };
27402
+ return {
27403
+ ...plan,
27404
+ sheet,
27405
+ sheetIndex,
27406
+ workbookSheetIndex: sheet.workbookSheetIndex
27407
+ };
27408
+ });
27409
+ }, [includeHeaders, palette, resolution, sheets, workbook]);
27410
+ const paintThumbnail = React4.useCallback(
27411
+ (sheetIndex, canvas) => thumbnails[sheetIndex]?.paint(canvas) ?? false,
27412
+ [thumbnails]
27413
+ );
27414
+ return React4.useMemo(
27415
+ () => ({
27416
+ paintThumbnail,
27417
+ thumbnails
27418
+ }),
27419
+ [paintThumbnail, thumbnails]
27420
+ );
27421
+ }
26830
27422
  function XlsxViewer(props) {
26831
27423
  const contextController = React4.useContext(ViewerContext);
26832
27424
  if (props.controller) {
@@ -26856,6 +27448,7 @@ function DefaultXlsxToolbar() {
26856
27448
  useXlsxViewerImages,
26857
27449
  useXlsxViewerSelection,
26858
27450
  useXlsxViewerTables,
27451
+ useXlsxViewerThumbnails,
26859
27452
  useXlsxViewerZoom
26860
27453
  });
26861
27454
  //# sourceMappingURL=index.cjs.map