@extend-ai/react-xlsx 0.14.0 → 0.14.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
@@ -6243,7 +6243,7 @@ function parseWorkbookStructureAssetsFromArchive(archive, options) {
6243
6243
  workbookSheets
6244
6244
  };
6245
6245
  }
6246
- function parseWorkbookImageAssets(bytes, workbook) {
6246
+ function parseWorkbookImageAssets(bytes, workbookOrFormControls) {
6247
6247
  const archive = (0, import_fflate2.unzipSync)(bytes);
6248
6248
  const {
6249
6249
  contentTypes,
@@ -6262,7 +6262,7 @@ function parseWorkbookImageAssets(bytes, workbook) {
6262
6262
  const shapesByWorkbookSheetIndex = [];
6263
6263
  const sheetOrigins = [];
6264
6264
  const imageOriginsById = /* @__PURE__ */ new Map();
6265
- const dukeFormControlsByWorkbookSheetIndex = collectWorkbookFormControls(workbook);
6265
+ const dukeFormControlsByWorkbookSheetIndex = Array.isArray(workbookOrFormControls) ? workbookOrFormControls : collectWorkbookFormControls(workbookOrFormControls);
6266
6266
  workbookSheets.forEach((sheet, workbookSheetIndex) => {
6267
6267
  const sheetRelationships = parseRelationships(archive, relsPathForDocument(sheet.path), sheet.path);
6268
6268
  const attachments = [];
@@ -8752,13 +8752,24 @@ function useXlsxViewerController(options) {
8752
8752
  if (!effectiveSkipXmlParsing && hasIncompleteWorkerChartSnapshot(snapshot)) {
8753
8753
  throw new Error("Worker chart payload incomplete");
8754
8754
  }
8755
+ const workerImageAssets = effectiveSkipXmlParsing ? null : parseWorkbookImageAssets(
8756
+ new Uint8Array(buffer),
8757
+ snapshot.formControlsByWorkbookSheetIndex
8758
+ );
8759
+ if (!isCurrent || abortController.signal.aborted) {
8760
+ revokeWorkbookImageAssets(workerImageAssets);
8761
+ return;
8762
+ }
8763
+ setImageAssets(workerImageAssets);
8755
8764
  setWorkbook(null);
8756
8765
  setSheets(snapshot.sheets);
8757
8766
  setChartsByWorkbookSheetIndex(snapshot.chartsByWorkbookSheetIndex);
8758
8767
  setChartsheets(snapshot.chartsheets);
8759
8768
  setTabs(snapshot.tabs);
8760
8769
  chartAssetsRef.current = null;
8761
- setFormControlsByWorkbookSheetIndex(snapshot.formControlsByWorkbookSheetIndex);
8770
+ if (!workerImageAssets) {
8771
+ setFormControlsByWorkbookSheetIndex(snapshot.formControlsByWorkbookSheetIndex);
8772
+ }
8762
8773
  setWorkerTablesByWorkbookSheetIndex(snapshot.tablesByWorkbookSheetIndex);
8763
8774
  setShouldAutoCalculate(false);
8764
8775
  setIsWorkerBacked(true);
@@ -9256,7 +9267,12 @@ function useXlsxViewerController(options) {
9256
9267
  const getFormControlItems = React.useCallback((controlIndex, sheetIndex = activeSheetIndex) => {
9257
9268
  const target = getFormControlWorksheet(sheetIndex);
9258
9269
  if (!workbook || !target) {
9259
- return [];
9270
+ const targetSheet = sheets[sheetIndex];
9271
+ if (!targetSheet) {
9272
+ return [];
9273
+ }
9274
+ const control2 = (formControlsByWorkbookSheetIndex[targetSheet.workbookSheetIndex] ?? []).find((entry) => entry.controlIndex === controlIndex);
9275
+ return control2?.items?.slice() ?? [];
9260
9276
  }
9261
9277
  const control = target.worksheet.formControls[controlIndex];
9262
9278
  const inputRange = control?.kind.kind === "listBox" || control?.kind.kind === "dropdown" ? control.kind.inputRange : void 0;
@@ -9274,7 +9290,7 @@ function useXlsxViewerController(options) {
9274
9290
  }
9275
9291
  }
9276
9292
  return items;
9277
- }, [activeSheetIndex, getFormControlWorksheet, workbook]);
9293
+ }, [activeSheetIndex, formControlsByWorkbookSheetIndex, getFormControlWorksheet, sheets, workbook]);
9278
9294
  const getSheetShapes = React.useCallback((sheetIndex = activeSheetIndex) => {
9279
9295
  const targetSheet = sheets[sheetIndex];
9280
9296
  if (!targetSheet) {
@@ -20809,6 +20825,40 @@ function getCellBooleanValue(worksheet, row, col) {
20809
20825
  }
20810
20826
  return null;
20811
20827
  }
20828
+ function getBatchedCellNumericValue(cell) {
20829
+ if (!cell) {
20830
+ return null;
20831
+ }
20832
+ const text = cell.value.trim();
20833
+ if (text.length === 0) {
20834
+ return null;
20835
+ }
20836
+ if (text.toLowerCase() === "true") {
20837
+ return 1;
20838
+ }
20839
+ if (text.toLowerCase() === "false") {
20840
+ return 0;
20841
+ }
20842
+ const value = Number(text.replace(/,/g, ""));
20843
+ return Number.isFinite(value) ? value : null;
20844
+ }
20845
+ function getBatchedCellBooleanValue(cell) {
20846
+ if (!cell) {
20847
+ return null;
20848
+ }
20849
+ const text = cell.value.trim().toLowerCase();
20850
+ if (text.length === 0) {
20851
+ return null;
20852
+ }
20853
+ if (text === "true") {
20854
+ return true;
20855
+ }
20856
+ if (text === "false") {
20857
+ return false;
20858
+ }
20859
+ const value = Number(text);
20860
+ return Number.isFinite(value) ? value !== 0 : null;
20861
+ }
20812
20862
  function parseA1RangeReference3(reference) {
20813
20863
  const [startRef, endRef = startRef] = reference.split(":");
20814
20864
  const start = parseA1CellReference3(startRef ?? "");
@@ -23062,6 +23112,7 @@ function XlsxGrid({
23062
23112
  onFormControlAction,
23063
23113
  onFormControlChange,
23064
23114
  renderChartLoading,
23115
+ renderFormControl,
23065
23116
  palette,
23066
23117
  renderImage,
23067
23118
  renderImageSelection,
@@ -25626,14 +25677,7 @@ function XlsxGrid({
25626
25677
  () => shouldBakeCanvasStaticDrawings ? shapeRects.filter(({ shape }) => shape.hyperlink) : shapeRects,
25627
25678
  [shapeRects, shouldBakeCanvasStaticDrawings]
25628
25679
  );
25629
- const bakedFormControlRects = React4.useMemo(
25630
- () => shouldBakeCanvasStaticDrawings ? formControlRects : [],
25631
- [formControlRects, shouldBakeCanvasStaticDrawings]
25632
- );
25633
- const domFormControlRects = React4.useMemo(
25634
- () => shouldBakeCanvasStaticDrawings ? [] : formControlRects,
25635
- [formControlRects, shouldBakeCanvasStaticDrawings]
25636
- );
25680
+ const domFormControlRects = formControlRects;
25637
25681
  const bakedImageRects = React4.useMemo(
25638
25682
  () => shouldBakeCanvasImages ? imageRects.filter(({ image }) => !image.hyperlink && selectedImageId !== image.id) : [],
25639
25683
  [imageRects, selectedImageId, shouldBakeCanvasImages]
@@ -25650,11 +25694,9 @@ function XlsxGrid({
25650
25694
  return "";
25651
25695
  }
25652
25696
  const shapeSignature = bakedShapeRects.map(({ rect, shape }) => `s:${shape.id}:${shape.zIndex}:${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`).join("|");
25653
- const controlSignature = bakedFormControlRects.map(({ control, rect }) => `f:${control.id}:${control.zIndex}:${control.state ?? Boolean(control.checked)}:${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`).join("|");
25654
25697
  const imageSignature = bakedImageRects.map(({ image, rect }) => `i:${image.id}:${image.src}:${image.zIndex}:${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`).join("|");
25655
- return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${controlSignature}:${imageSignature}`;
25698
+ return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${imageSignature}`;
25656
25699
  }, [
25657
- bakedFormControlRects,
25658
25700
  bakedImageRects,
25659
25701
  bakedShapeRects,
25660
25702
  canvasImageLoadVersion,
@@ -27189,12 +27231,6 @@ function XlsxGrid({
27189
27231
  shape,
27190
27232
  zIndex: shape.zIndex
27191
27233
  })),
27192
- ...bakedFormControlRects.map(({ control, rect }) => ({
27193
- control,
27194
- kind: "formControl",
27195
- rect,
27196
- zIndex: control.zIndex
27197
- })),
27198
27234
  ...bakedImageRects.map(({ image, rect }) => ({
27199
27235
  image,
27200
27236
  kind: "image",
@@ -27345,97 +27381,6 @@ function XlsxGrid({
27345
27381
  drawBakedShapeText(context, shape, localLeft, localTop, rect.width, rect.height);
27346
27382
  context.restore();
27347
27383
  };
27348
- const drawBakedFormControl = (context, control, rect) => {
27349
- const label = resolveFormControlLabel(control);
27350
- const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
27351
- const textColor = control.textColor ?? "#000000";
27352
- const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
27353
- const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
27354
- const controlChecked = isFormControlChecked(control);
27355
- const controlMixed = isFormControlMixed(control);
27356
- context.save();
27357
- context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
27358
- context.textBaseline = "middle";
27359
- context.fillStyle = textColor;
27360
- context.strokeStyle = stroke;
27361
- context.lineWidth = Math.max(1, zoomFactor);
27362
- if (control.kind === "group-box") {
27363
- const labelInset = label ? Math.max(7, fontSizePx * 0.5) : 0;
27364
- drawCanvasRoundedRect(context, rect.left, rect.top + labelInset, rect.width, Math.max(0, rect.height - labelInset), 2 * zoomFactor);
27365
- context.stroke();
27366
- if (label) {
27367
- context.fillStyle = SHEET_SURFACE;
27368
- const labelWidth = Math.min(rect.width - 16 * zoomFactor, measureCanvasTextWidth(context, label) + 8 * zoomFactor);
27369
- context.fillRect(rect.left + 8 * zoomFactor, rect.top, labelWidth, fontSizePx * 1.2);
27370
- context.fillStyle = textColor;
27371
- context.textAlign = "left";
27372
- context.fillText(label, rect.left + 12 * zoomFactor, rect.top + fontSizePx * 0.55);
27373
- }
27374
- context.restore();
27375
- return;
27376
- }
27377
- if (control.kind === "button" || control.kind === "dropdown" || control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
27378
- if (control.kind === "button") {
27379
- const gradient = context.createLinearGradient(rect.left, rect.top, rect.left, rect.top + rect.height);
27380
- gradient.addColorStop(0, "#f8fafc");
27381
- gradient.addColorStop(1, "#e2e8f0");
27382
- context.fillStyle = gradient;
27383
- } else {
27384
- context.fillStyle = "transparent";
27385
- }
27386
- drawCanvasRoundedRect(context, rect.left, rect.top, rect.width, rect.height, control.kind === "button" ? 4 * zoomFactor : 2 * zoomFactor);
27387
- if (control.kind === "button") {
27388
- context.fill();
27389
- }
27390
- context.stroke();
27391
- }
27392
- let textLeft = rect.left + 2 * zoomFactor;
27393
- const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
27394
- if (control.kind === "checkbox") {
27395
- context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
27396
- if (controlChecked || controlMixed) {
27397
- context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
27398
- context.fillRect(rect.left + 2 * zoomFactor + 1.5, rect.top + (rect.height - iconSize) / 2 + 1.5, Math.max(0, iconSize - 3), Math.max(0, iconSize - 3));
27399
- context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
27400
- context.beginPath();
27401
- if (controlMixed) {
27402
- context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.25, rect.top + rect.height / 2);
27403
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.75, rect.top + rect.height / 2);
27404
- } else {
27405
- context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
27406
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
27407
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
27408
- }
27409
- context.stroke();
27410
- }
27411
- textLeft += iconSize + 4 * zoomFactor;
27412
- } else if (control.kind === "radio") {
27413
- context.beginPath();
27414
- context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
27415
- context.stroke();
27416
- if (controlChecked) {
27417
- context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
27418
- context.beginPath();
27419
- context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
27420
- context.fill();
27421
- }
27422
- textLeft += iconSize + 4 * zoomFactor;
27423
- }
27424
- if (label) {
27425
- const maxTextWidth = Math.max(0, rect.left + rect.width - textLeft - textRightInset);
27426
- const text = truncateCanvasText(context, label, maxTextWidth);
27427
- context.fillStyle = textColor;
27428
- context.textAlign = control.textAlign === "right" ? "right" : control.textAlign === "center" ? "center" : "left";
27429
- const textX = context.textAlign === "right" ? rect.left + rect.width - textRightInset : context.textAlign === "center" ? textLeft + maxTextWidth / 2 : textLeft;
27430
- context.fillText(text, textX, rect.top + rect.height / 2);
27431
- }
27432
- if (control.kind === "dropdown") {
27433
- context.fillStyle = textColor;
27434
- context.textAlign = "center";
27435
- context.fillText("\u25BC", rect.left + rect.width - 10 * zoomFactor, rect.top + rect.height / 2);
27436
- }
27437
- context.restore();
27438
- };
27439
27384
  const drawBakedCanvasDrawings = (pane, context, dirtyRects) => {
27440
27385
  if (bakedCanvasDrawingEntries.length === 0 || dirtyRects.length === 0) {
27441
27386
  return;
@@ -27450,8 +27395,6 @@ function XlsxGrid({
27450
27395
  }
27451
27396
  if (entry.kind === "shape") {
27452
27397
  drawBakedShape(context, entry.shape, localRect);
27453
- } else if (entry.kind === "formControl") {
27454
- drawBakedFormControl(context, entry.control, localRect);
27455
27398
  } else {
27456
27399
  const imageElement = getCanvasImage(entry.image);
27457
27400
  if (imageElement) {
@@ -28145,7 +28088,6 @@ function XlsxGrid({
28145
28088
  activeSheet,
28146
28089
  applyCanvasViewportCompensation,
28147
28090
  bakedCanvasDrawingSignature,
28148
- bakedFormControlRects,
28149
28091
  bakedImageRects,
28150
28092
  bakedShapeRects,
28151
28093
  canvasColumnHeaderCells,
@@ -28764,8 +28706,8 @@ function XlsxGrid({
28764
28706
  event.stopPropagation();
28765
28707
  };
28766
28708
  const commitControlKind = (kind, nextControl) => {
28767
- if (control.controlIndex === void 0) {
28768
- return;
28709
+ if (readOnly || control.controlIndex === void 0) {
28710
+ return false;
28769
28711
  }
28770
28712
  const didUpdate = updateFormControl(control.controlIndex, { kind }, control.sheetIndex);
28771
28713
  if (didUpdate) {
@@ -28775,7 +28717,77 @@ function XlsxGrid({
28775
28717
  type: "change"
28776
28718
  });
28777
28719
  }
28720
+ return didUpdate;
28778
28721
  };
28722
+ const setControlState = (state = "unchecked") => {
28723
+ const kind = buildFormControlKindInput(control);
28724
+ if (kind?.kind === "checkbox") {
28725
+ return commitControlKind(
28726
+ { ...kind, state },
28727
+ { checked: state === "checked", state }
28728
+ );
28729
+ }
28730
+ if (kind?.kind === "optionButton" && state !== "mixed") {
28731
+ return commitControlKind(
28732
+ { ...kind, state },
28733
+ { checked: state === "checked", state }
28734
+ );
28735
+ }
28736
+ return false;
28737
+ };
28738
+ const setControlSelected = (selected) => {
28739
+ const kind = buildFormControlKindInput(control);
28740
+ if (kind?.kind === "dropdown") {
28741
+ const nextSelected = typeof selected === "number" ? selected : void 0;
28742
+ return commitControlKind(
28743
+ { ...kind, selected: nextSelected },
28744
+ { selected: nextSelected }
28745
+ );
28746
+ }
28747
+ if (kind?.kind === "listBox") {
28748
+ const nextSelected = Array.isArray(selected) ? selected : typeof selected === "number" ? [selected] : [];
28749
+ return commitControlKind(
28750
+ { ...kind, selected: nextSelected },
28751
+ { selected: nextSelected }
28752
+ );
28753
+ }
28754
+ return false;
28755
+ };
28756
+ const setControlValue = (value) => {
28757
+ if (!Number.isFinite(value)) {
28758
+ return false;
28759
+ }
28760
+ const kind = buildFormControlKindInput(control);
28761
+ if (kind?.kind !== "scrollbar" && kind?.kind !== "spinner") {
28762
+ return false;
28763
+ }
28764
+ const nextValue = Math.max(kind.min, Math.min(kind.max, value));
28765
+ return commitControlKind({ ...kind, value: nextValue }, { value: nextValue });
28766
+ };
28767
+ const activateControl = () => {
28768
+ if (!readOnly && control.kind === "button") {
28769
+ onFormControlAction?.({ control, type: "activate" });
28770
+ }
28771
+ };
28772
+ if (renderFormControl) {
28773
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(React4.Fragment, { children: renderFormControl({
28774
+ activate: activateControl,
28775
+ checked: isFormControlChecked(control),
28776
+ control,
28777
+ disabled: !isInteractiveControl,
28778
+ items: controlItems,
28779
+ label: controlLabel,
28780
+ mixed: isFormControlMixed(control),
28781
+ readOnly,
28782
+ rect,
28783
+ setSelected: setControlSelected,
28784
+ setState: setControlState,
28785
+ setValue: setControlValue,
28786
+ stopPropagation: stopControlPointerEvent,
28787
+ style: commonStyle,
28788
+ zoomFactor
28789
+ }) }, `${pane}-${control.id}`);
28790
+ }
28779
28791
  if (control.kind === "group-box") {
28780
28792
  const hasLabel = controlLabel.length > 0;
28781
28793
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -28819,19 +28831,16 @@ function XlsxGrid({
28819
28831
  }
28820
28832
  if (control.kind === "radio") {
28821
28833
  const checked = isFormControlChecked(control);
28822
- const kind = buildFormControlKindInput(control);
28823
28834
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
28824
28835
  "button",
28825
28836
  {
28837
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28826
28838
  "aria-checked": checked,
28827
- disabled: readOnly || control.controlIndex === void 0,
28839
+ disabled: !readOnly && control.controlIndex === void 0,
28828
28840
  onClick: (event) => {
28829
28841
  event.stopPropagation();
28830
- if (!checked && kind?.kind === "optionButton") {
28831
- commitControlKind(
28832
- { ...kind, state: "checked" },
28833
- { checked: true, state: "checked" }
28834
- );
28842
+ if (!checked) {
28843
+ setControlState("checked");
28835
28844
  }
28836
28845
  },
28837
28846
  onPointerDown: stopControlPointerEvent,
@@ -28843,6 +28852,7 @@ function XlsxGrid({
28843
28852
  boxSizing: "border-box",
28844
28853
  padding: `0 ${Math.max(1, zoomFactor)}px`
28845
28854
  },
28855
+ tabIndex: readOnly ? -1 : void 0,
28846
28856
  type: "button",
28847
28857
  children: [
28848
28858
  renderRadioControl(checked, palette, zoomFactor),
@@ -28855,21 +28865,16 @@ function XlsxGrid({
28855
28865
  if (control.kind === "checkbox") {
28856
28866
  const checked = isFormControlChecked(control);
28857
28867
  const mixed = isFormControlMixed(control);
28858
- const kind = buildFormControlKindInput(control);
28859
28868
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
28860
28869
  "button",
28861
28870
  {
28871
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28862
28872
  "aria-checked": mixed ? "mixed" : checked,
28863
- disabled: readOnly || control.controlIndex === void 0,
28873
+ disabled: !readOnly && control.controlIndex === void 0,
28864
28874
  onClick: (event) => {
28865
28875
  event.stopPropagation();
28866
- if (kind?.kind === "checkbox") {
28867
- const nextState = checked && !mixed ? "unchecked" : "checked";
28868
- commitControlKind(
28869
- { ...kind, state: nextState },
28870
- { checked: nextState === "checked", state: nextState }
28871
- );
28872
- }
28876
+ const nextState = checked && !mixed ? "unchecked" : "checked";
28877
+ setControlState(nextState);
28873
28878
  },
28874
28879
  onPointerDown: stopControlPointerEvent,
28875
28880
  role: "checkbox",
@@ -28880,6 +28885,7 @@ function XlsxGrid({
28880
28885
  boxSizing: "border-box",
28881
28886
  padding: `0 ${Math.max(1, zoomFactor)}px`
28882
28887
  },
28888
+ tabIndex: readOnly ? -1 : void 0,
28883
28889
  type: "button",
28884
28890
  children: [
28885
28891
  renderCheckboxControl(checked, palette, zoomFactor, mixed),
@@ -28893,10 +28899,11 @@ function XlsxGrid({
28893
28899
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28894
28900
  "button",
28895
28901
  {
28896
- disabled: readOnly || !onFormControlAction,
28902
+ "aria-disabled": readOnly || !onFormControlAction,
28903
+ disabled: !readOnly && !onFormControlAction,
28897
28904
  onClick: (event) => {
28898
28905
  event.stopPropagation();
28899
- onFormControlAction?.({ control, type: "activate" });
28906
+ activateControl();
28900
28907
  },
28901
28908
  onPointerDown: stopControlPointerEvent,
28902
28909
  style: {
@@ -28909,6 +28916,7 @@ function XlsxGrid({
28909
28916
  padding: `0 ${6 * zoomFactor}px`,
28910
28917
  textAlign: "center"
28911
28918
  },
28919
+ tabIndex: readOnly ? -1 : void 0,
28912
28920
  type: "button",
28913
28921
  children: controlLabel
28914
28922
  },
@@ -28916,22 +28924,17 @@ function XlsxGrid({
28916
28924
  );
28917
28925
  }
28918
28926
  if (control.kind === "dropdown") {
28919
- const kind = buildFormControlKindInput(control);
28920
28927
  const selected = typeof control.selected === "number" ? control.selected : void 0;
28921
28928
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
28922
28929
  "select",
28923
28930
  {
28931
+ "aria-disabled": readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28924
28932
  "aria-label": controlLabel || control.name || "Dropdown form control",
28925
- disabled: readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28933
+ disabled: !readOnly && (control.controlIndex === void 0 || controlItems.length === 0),
28926
28934
  onChange: (event) => {
28927
28935
  event.stopPropagation();
28928
- if (kind?.kind === "dropdown") {
28929
- const nextSelected = event.currentTarget.value === "" ? void 0 : Number(event.currentTarget.value);
28930
- commitControlKind(
28931
- { ...kind, selected: nextSelected },
28932
- { selected: nextSelected }
28933
- );
28934
- }
28936
+ const nextSelected = event.currentTarget.value === "" ? void 0 : Number(event.currentTarget.value);
28937
+ setControlSelected(nextSelected);
28935
28938
  },
28936
28939
  onClick: stopControlPointerEvent,
28937
28940
  onPointerDown: stopControlPointerEvent,
@@ -28943,6 +28946,7 @@ function XlsxGrid({
28943
28946
  boxSizing: "border-box",
28944
28947
  padding: `0 ${4 * zoomFactor}px`
28945
28948
  },
28949
+ tabIndex: readOnly ? -1 : void 0,
28946
28950
  value: selected === void 0 ? "" : String(selected),
28947
28951
  children: [
28948
28952
  selected === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: controlLabel }) : null,
@@ -28953,24 +28957,19 @@ function XlsxGrid({
28953
28957
  );
28954
28958
  }
28955
28959
  if (control.kind === "listbox") {
28956
- const kind = buildFormControlKindInput(control);
28957
28960
  const selected = Array.isArray(control.selected) ? control.selected : control.selected === void 0 ? [] : [control.selected];
28958
28961
  const allowsMultipleSelection = (control.selection ?? "single") !== "single";
28959
28962
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28960
28963
  "select",
28961
28964
  {
28965
+ "aria-disabled": readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28962
28966
  "aria-label": controlLabel || control.name || "List box form control",
28963
- disabled: readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28967
+ disabled: !readOnly && (control.controlIndex === void 0 || controlItems.length === 0),
28964
28968
  multiple: allowsMultipleSelection,
28965
28969
  onChange: (event) => {
28966
28970
  event.stopPropagation();
28967
- if (kind?.kind === "listBox") {
28968
- const nextSelected = Array.from(event.currentTarget.selectedOptions, (option) => Number(option.value));
28969
- commitControlKind(
28970
- { ...kind, selected: nextSelected },
28971
- { selected: nextSelected }
28972
- );
28973
- }
28971
+ const nextSelected = Array.from(event.currentTarget.selectedOptions, (option) => Number(option.value));
28972
+ setControlSelected(nextSelected);
28974
28973
  },
28975
28974
  onClick: stopControlPointerEvent,
28976
28975
  onPointerDown: stopControlPointerEvent,
@@ -28985,6 +28984,7 @@ function XlsxGrid({
28985
28984
  display: "block",
28986
28985
  padding: 0
28987
28986
  },
28987
+ tabIndex: readOnly ? -1 : void 0,
28988
28988
  value: allowsMultipleSelection ? selected.map(String) : selected[0] === void 0 ? "" : String(selected[0]),
28989
28989
  children: controlItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: index, children: item }, index))
28990
28990
  },
@@ -28992,22 +28992,19 @@ function XlsxGrid({
28992
28992
  );
28993
28993
  }
28994
28994
  if (control.kind === "scrollbar") {
28995
- const kind = buildFormControlKindInput(control);
28996
28995
  const min = control.min ?? 0;
28997
28996
  const max = Math.max(min, control.max ?? 100);
28998
28997
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28999
28998
  "input",
29000
28999
  {
29000
+ "aria-disabled": readOnly || control.controlIndex === void 0,
29001
29001
  "aria-label": controlLabel || control.name || "Scrollbar form control",
29002
- disabled: readOnly || control.controlIndex === void 0,
29002
+ disabled: !readOnly && control.controlIndex === void 0,
29003
29003
  max,
29004
29004
  min,
29005
29005
  onChange: (event) => {
29006
29006
  event.stopPropagation();
29007
- if (kind?.kind === "scrollbar") {
29008
- const value = event.currentTarget.valueAsNumber;
29009
- commitControlKind({ ...kind, value }, { value });
29010
- }
29007
+ setControlValue(event.currentTarget.valueAsNumber);
29011
29008
  },
29012
29009
  onClick: stopControlPointerEvent,
29013
29010
  onPointerDown: stopControlPointerEvent,
@@ -29019,6 +29016,7 @@ function XlsxGrid({
29019
29016
  padding: 0,
29020
29017
  writingMode: control.horizontal === false ? "vertical-lr" : void 0
29021
29018
  },
29019
+ tabIndex: readOnly ? -1 : void 0,
29022
29020
  type: "range",
29023
29021
  value: Math.max(min, Math.min(max, control.value ?? min))
29024
29022
  },
@@ -29026,22 +29024,19 @@ function XlsxGrid({
29026
29024
  );
29027
29025
  }
29028
29026
  if (control.kind === "spinner") {
29029
- const kind = buildFormControlKindInput(control);
29030
29027
  const min = control.min ?? 0;
29031
29028
  const max = Math.max(min, control.max ?? 100);
29032
29029
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
29033
29030
  "input",
29034
29031
  {
29032
+ "aria-disabled": readOnly || control.controlIndex === void 0,
29035
29033
  "aria-label": controlLabel || control.name || "Spinner form control",
29036
- disabled: readOnly || control.controlIndex === void 0,
29034
+ disabled: !readOnly && control.controlIndex === void 0,
29037
29035
  max,
29038
29036
  min,
29039
29037
  onChange: (event) => {
29040
29038
  event.stopPropagation();
29041
- if (kind?.kind === "spinner" && Number.isFinite(event.currentTarget.valueAsNumber)) {
29042
- const value = Math.max(min, Math.min(max, event.currentTarget.valueAsNumber));
29043
- commitControlKind({ ...kind, value }, { value });
29044
- }
29039
+ setControlValue(event.currentTarget.valueAsNumber);
29045
29040
  },
29046
29041
  onClick: stopControlPointerEvent,
29047
29042
  onPointerDown: stopControlPointerEvent,
@@ -29054,6 +29049,7 @@ function XlsxGrid({
29054
29049
  boxSizing: "border-box",
29055
29050
  padding: `0 ${4 * zoomFactor}px`
29056
29051
  },
29052
+ tabIndex: readOnly ? -1 : void 0,
29057
29053
  type: "number",
29058
29054
  value: Math.max(min, Math.min(max, control.value ?? min))
29059
29055
  },
@@ -29357,7 +29353,7 @@ function XlsxGrid({
29357
29353
  };
29358
29354
  const drawingViewportCacheSignature = `${Math.floor(drawingViewport.left / CANVAS_VIEWPORT_OVERSCAN_PX)}:${Math.floor(drawingViewport.top / CANVAS_VIEWPORT_OVERSCAN_PX)}:${drawingViewport.width}:${drawingViewport.height}`;
29359
29355
  const previousPaneDrawingNodes = paneDrawingNodesCacheRef.current;
29360
- const canReusePaneDrawingNodes = previousPaneDrawingNodes !== null && previousPaneDrawingNodes.showImages === showImages && previousPaneDrawingNodes.chartRects === chartRects && previousPaneDrawingNodes.formControlRects === domFormControlRects && previousPaneDrawingNodes.shapeRects === domShapeRects && previousPaneDrawingNodes.imageRects === domImageRects && previousPaneDrawingNodes.selectedChartId === selectedChartId && previousPaneDrawingNodes.selectedImageId === selectedImageId && previousPaneDrawingNodes.readOnly === readOnly && previousPaneDrawingNodes.selectionStroke === selectionStroke && previousPaneDrawingNodes.renderChartLoading === renderChartLoading && previousPaneDrawingNodes.renderImage === renderImage && previousPaneDrawingNodes.renderImageSelection === renderImageSelection && previousPaneDrawingNodes.isChartsLoading === isChartsLoading && previousPaneDrawingNodes.selectedChartElement === selectedChartElement && previousPaneDrawingNodes.palette === palette && previousPaneDrawingNodes.drawingViewportSignature === drawingViewportCacheSignature;
29356
+ const canReusePaneDrawingNodes = previousPaneDrawingNodes !== null && previousPaneDrawingNodes.showImages === showImages && previousPaneDrawingNodes.updateFormControl === updateFormControl && previousPaneDrawingNodes.chartRects === chartRects && previousPaneDrawingNodes.formControlRects === domFormControlRects && previousPaneDrawingNodes.formControlItemsById === formControlItemsById && previousPaneDrawingNodes.shapeRects === domShapeRects && previousPaneDrawingNodes.imageRects === domImageRects && previousPaneDrawingNodes.selectedChartId === selectedChartId && previousPaneDrawingNodes.selectedImageId === selectedImageId && previousPaneDrawingNodes.readOnly === readOnly && previousPaneDrawingNodes.onFormControlAction === onFormControlAction && previousPaneDrawingNodes.onFormControlChange === onFormControlChange && previousPaneDrawingNodes.selectionStroke === selectionStroke && previousPaneDrawingNodes.renderChartLoading === renderChartLoading && previousPaneDrawingNodes.renderFormControl === renderFormControl && previousPaneDrawingNodes.renderImage === renderImage && previousPaneDrawingNodes.renderImageSelection === renderImageSelection && previousPaneDrawingNodes.isChartsLoading === isChartsLoading && previousPaneDrawingNodes.selectedChartElement === selectedChartElement && previousPaneDrawingNodes.palette === palette && previousPaneDrawingNodes.drawingViewportSignature === drawingViewportCacheSignature;
29361
29357
  const paneDrawingNodes = canReusePaneDrawingNodes ? previousPaneDrawingNodes.value : !showImages ? {
29362
29358
  corner: null,
29363
29359
  left: null,
@@ -29393,12 +29389,16 @@ function XlsxGrid({
29393
29389
  paneDrawingNodesCacheRef.current = {
29394
29390
  chartRects,
29395
29391
  drawingViewportSignature: drawingViewportCacheSignature,
29392
+ formControlItemsById,
29396
29393
  formControlRects: domFormControlRects,
29397
29394
  imageRects: domImageRects,
29398
29395
  isChartsLoading,
29396
+ onFormControlAction,
29397
+ onFormControlChange,
29399
29398
  palette,
29400
29399
  readOnly,
29401
29400
  renderChartLoading,
29401
+ renderFormControl,
29402
29402
  renderImage,
29403
29403
  renderImageSelection,
29404
29404
  selectedChartElement,
@@ -29407,6 +29407,7 @@ function XlsxGrid({
29407
29407
  selectionStroke,
29408
29408
  shapeRects: domShapeRects,
29409
29409
  showImages,
29410
+ updateFormControl,
29410
29411
  value: paneDrawingNodes
29411
29412
  };
29412
29413
  }
@@ -30636,6 +30637,7 @@ function XlsxViewerInner({
30636
30637
  onFormControlAction,
30637
30638
  onFormControlChange,
30638
30639
  renderChartLoading,
30640
+ renderFormControl,
30639
30641
  renderImage,
30640
30642
  renderImageSelection,
30641
30643
  renderScroller,
@@ -30701,6 +30703,7 @@ function XlsxViewerInner({
30701
30703
  onFormControlChange,
30702
30704
  palette,
30703
30705
  renderChartLoading,
30706
+ renderFormControl,
30704
30707
  renderImage,
30705
30708
  renderImageSelection,
30706
30709
  renderScroller,
@@ -31121,20 +31124,87 @@ function useXlsxViewerCharts() {
31121
31124
  );
31122
31125
  }
31123
31126
  function useXlsxViewerThumbnails(options = {}) {
31124
- const { getSheetFormControls, getSheetImages, getSheetShapes, workbook, sheets } = useXlsxViewer();
31127
+ const {
31128
+ getRowsBatchAsync,
31129
+ getSheetFormControls,
31130
+ getSheetImages,
31131
+ getSheetShapes,
31132
+ workbook,
31133
+ sheets
31134
+ } = useXlsxViewer();
31125
31135
  const { isDark } = React4.useContext(ViewerAppearanceContext);
31126
31136
  const palette = useViewerPalette(isDark);
31127
31137
  const includeHeaders = options.includeHeaders ?? true;
31128
31138
  const resolution = options.resolution;
31129
31139
  const thumbnailImageCacheRef = React4.useRef(/* @__PURE__ */ new Map());
31140
+ const workerThumbnailRowBatchCacheRef = React4.useRef(/* @__PURE__ */ new Map());
31141
+ const workerThumbnailRowBatchRequestRef = React4.useRef(
31142
+ /* @__PURE__ */ new Map()
31143
+ );
31130
31144
  const isMountedRef = React4.useRef(false);
31131
31145
  const [thumbnailImageLoadVersion, setThumbnailImageLoadVersion] = React4.useState(0);
31146
+ const [workerThumbnailRowBatchVersion, setWorkerThumbnailRowBatchVersion] = React4.useState(0);
31132
31147
  React4.useEffect(() => {
31133
31148
  isMountedRef.current = true;
31134
31149
  return () => {
31135
31150
  isMountedRef.current = false;
31136
31151
  };
31137
31152
  }, []);
31153
+ React4.useEffect(() => {
31154
+ const currentSheets = new Set(sheets);
31155
+ for (const sheet of workerThumbnailRowBatchCacheRef.current.keys()) {
31156
+ if (!currentSheets.has(sheet)) {
31157
+ workerThumbnailRowBatchCacheRef.current.delete(sheet);
31158
+ }
31159
+ }
31160
+ for (const sheet of workerThumbnailRowBatchRequestRef.current.keys()) {
31161
+ if (!currentSheets.has(sheet)) {
31162
+ workerThumbnailRowBatchRequestRef.current.delete(sheet);
31163
+ }
31164
+ }
31165
+ }, [sheets]);
31166
+ const getWorkerThumbnailRowBatch = React4.useCallback((sheet, startRow, endRow) => {
31167
+ const cachedBatch = workerThumbnailRowBatchCacheRef.current.get(sheet);
31168
+ if (cachedBatch && startRow >= cachedBatch.startRow && endRow <= cachedBatch.endRow) {
31169
+ return cachedBatch;
31170
+ }
31171
+ if (!getRowsBatchAsync) {
31172
+ return null;
31173
+ }
31174
+ const pendingRequest = workerThumbnailRowBatchRequestRef.current.get(sheet);
31175
+ if (!pendingRequest) {
31176
+ const request = {
31177
+ endRow: Math.max(endRow, cachedBatch?.endRow ?? endRow),
31178
+ startRow: Math.min(startRow, cachedBatch?.startRow ?? startRow)
31179
+ };
31180
+ workerThumbnailRowBatchRequestRef.current.set(sheet, request);
31181
+ void getRowsBatchAsync(
31182
+ sheet.workbookSheetIndex,
31183
+ request.startRow,
31184
+ request.endRow - request.startRow + 1
31185
+ ).then((rows) => {
31186
+ if (workerThumbnailRowBatchRequestRef.current.get(sheet) !== request) {
31187
+ return;
31188
+ }
31189
+ workerThumbnailRowBatchRequestRef.current.delete(sheet);
31190
+ if (!rows) {
31191
+ return;
31192
+ }
31193
+ workerThumbnailRowBatchCacheRef.current.set(
31194
+ sheet,
31195
+ buildWorksheetBatchWindow(rows, sheet, request.startRow, request.endRow)
31196
+ );
31197
+ if (isMountedRef.current) {
31198
+ setWorkerThumbnailRowBatchVersion((version) => version + 1);
31199
+ }
31200
+ }).catch(() => {
31201
+ if (workerThumbnailRowBatchRequestRef.current.get(sheet) === request) {
31202
+ workerThumbnailRowBatchRequestRef.current.delete(sheet);
31203
+ }
31204
+ });
31205
+ }
31206
+ return null;
31207
+ }, [getRowsBatchAsync]);
31138
31208
  const getThumbnailImage = React4.useCallback((image) => {
31139
31209
  if (typeof Image === "undefined") {
31140
31210
  return null;
@@ -31360,6 +31430,10 @@ function useXlsxViewerThumbnails(options = {}) {
31360
31430
  if (!canvas) {
31361
31431
  return false;
31362
31432
  }
31433
+ const workerRowBatch = worksheet ? null : getWorkerThumbnailRowBatch(sheet, sourceRange.start.row, sourceRange.end.row);
31434
+ if (!worksheet && !workerRowBatch) {
31435
+ return false;
31436
+ }
31363
31437
  const context = canvas.getContext("2d", { alpha: false });
31364
31438
  if (!context) {
31365
31439
  return false;
@@ -31396,9 +31470,6 @@ function useXlsxViewerThumbnails(options = {}) {
31396
31470
  context.fillRect(0, headerHeight, rowHeaderWidth, Math.max(1, rowAxis.totalSize));
31397
31471
  context.fillRect(0, 0, rowHeaderWidth, headerHeight);
31398
31472
  }
31399
- if (!worksheet) {
31400
- return true;
31401
- }
31402
31473
  const conditionalFormatMetricsCache = /* @__PURE__ */ new Map();
31403
31474
  const cellRenderCache = /* @__PURE__ */ new Map();
31404
31475
  const getCellData = (row, col) => {
@@ -31407,7 +31478,9 @@ function useXlsxViewerThumbnails(options = {}) {
31407
31478
  if (cached) {
31408
31479
  return cached;
31409
31480
  }
31410
- if (worksheet.isMergedSecondary(row, col)) {
31481
+ const batchCoversRow = workerRowBatch ? row >= workerRowBatch.startRow && row <= workerRowBatch.endRow : false;
31482
+ const batchedCell = batchCoversRow ? workerRowBatch?.cells.get(cacheKey) : void 0;
31483
+ if (batchedCell?.isMergedSecondary || worksheet?.isMergedSecondary(row, col)) {
31411
31484
  const mergedSecondaryData = {
31412
31485
  isMergedSecondary: true,
31413
31486
  style: {},
@@ -31416,53 +31489,34 @@ function useXlsxViewerThumbnails(options = {}) {
31416
31489
  cellRenderCache.set(cacheKey, mergedSecondaryData);
31417
31490
  return mergedSecondaryData;
31418
31491
  }
31419
- const merge = worksheet.getMergeSpan(row, col);
31492
+ const merge = worksheet ? worksheet.getMergeSpan(row, col) : batchedCell?.mergeSpan;
31420
31493
  const inheritedStyle = resolveInheritedCellStyle2(sheet, row, col);
31421
- const worksheetStyle = worksheet.getCellStyleAt(row, col) ?? null;
31494
+ const worksheetStyle = worksheet ? worksheet.getCellStyleAt(row, col) ?? null : batchedCell?.style ?? null;
31422
31495
  const mergedStyle = mergeResolvedCellStyle(inheritedStyle, worksheetStyle, { replaceXfSubtrees: true });
31423
31496
  const alignment = mergedStyle?.alignment;
31424
31497
  const sparkline = sparklineByCell.get(cacheKey) ?? null;
31498
+ const getNumericValue = (targetRow, targetCol) => worksheet ? getCellNumericValue(worksheet, targetRow, targetCol) : getBatchedCellNumericValue(workerRowBatch?.cells.get(`${targetRow}:${targetCol}`));
31425
31499
  const sparklineValues = sparkline ? sparkline.range.start.row === sparkline.range.end.row ? Array.from(
31426
31500
  { length: Math.abs(sparkline.range.end.col - sparkline.range.start.col) + 1 },
31427
- (_, index) => getCellNumericValue(
31428
- worksheet,
31501
+ (_, index) => getNumericValue(
31429
31502
  sparkline.range.start.row,
31430
31503
  Math.min(sparkline.range.start.col, sparkline.range.end.col) + index
31431
31504
  )
31432
31505
  ) : Array.from(
31433
31506
  { length: Math.abs(sparkline.range.end.row - sparkline.range.start.row) + 1 },
31434
- (_, index) => getCellNumericValue(
31435
- worksheet,
31507
+ (_, index) => getNumericValue(
31436
31508
  Math.min(sparkline.range.start.row, sparkline.range.end.row) + index,
31437
31509
  sparkline.range.start.col
31438
31510
  )
31439
31511
  ) : null;
31440
- const checkboxState = mergedStyle?.cellControl ? getCellBooleanValue(worksheet, row, col) : null;
31512
+ const checkboxState = mergedStyle?.cellControl ? worksheet ? getCellBooleanValue(worksheet, row, col) : getBatchedCellBooleanValue(batchedCell) : null;
31441
31513
  const nextData = {
31442
31514
  canvas: void 0,
31443
31515
  checkboxState,
31444
31516
  colSpan: merge?.colSpan,
31445
- conditionalColorScale: resolveConditionalColorScaleForCell(
31446
- row,
31447
- col,
31448
- worksheet,
31449
- sheet,
31450
- conditionalFormatMetricsCache
31451
- ),
31452
- conditionalDataBar: resolveConditionalDataBarForCell(
31453
- row,
31454
- col,
31455
- worksheet,
31456
- sheet,
31457
- conditionalFormatMetricsCache
31458
- ),
31459
- conditionalIcon: resolveConditionalIconForCell(
31460
- row,
31461
- col,
31462
- worksheet,
31463
- sheet,
31464
- conditionalFormatMetricsCache
31465
- ),
31517
+ conditionalColorScale: worksheet ? resolveConditionalColorScaleForCell(row, col, worksheet, sheet, conditionalFormatMetricsCache) : null,
31518
+ conditionalDataBar: worksheet ? resolveConditionalDataBarForCell(row, col, worksheet, sheet, conditionalFormatMetricsCache) : null,
31519
+ conditionalIcon: worksheet ? resolveConditionalIconForCell(row, col, worksheet, sheet, conditionalFormatMetricsCache) : null,
31466
31520
  isMergedSecondary: false,
31467
31521
  rowSpan: merge?.rowSpan,
31468
31522
  sparkline: sparkline && sparklineValues ? { config: sparkline, values: sparklineValues } : null,
@@ -31470,7 +31524,7 @@ function useXlsxViewerThumbnails(options = {}) {
31470
31524
  showGridLines
31471
31525
  }),
31472
31526
  textRotationDeg: resolveSpreadsheetTextRotation(alignment?.textRotation),
31473
- value: sparkline ? "" : checkboxState !== null ? "" : getCellDisplayValue(worksheet, row, col, sheet)
31527
+ value: sparkline ? "" : checkboxState !== null ? "" : worksheet ? getCellDisplayValue(worksheet, row, col, sheet) : batchedCell?.value ?? ""
31474
31528
  };
31475
31529
  nextData.canvas = buildCanvasCellStyleCache(nextData.style);
31476
31530
  cellRenderCache.set(cacheKey, nextData);
@@ -31744,12 +31798,14 @@ function useXlsxViewerThumbnails(options = {}) {
31744
31798
  getSheetFormControls,
31745
31799
  getSheetImages,
31746
31800
  getSheetShapes,
31801
+ getWorkerThumbnailRowBatch,
31747
31802
  getThumbnailImage,
31748
31803
  includeHeaders,
31749
31804
  palette,
31750
31805
  resolution,
31751
31806
  sheets,
31752
31807
  thumbnailImageLoadVersion,
31808
+ workerThumbnailRowBatchVersion,
31753
31809
  workbook
31754
31810
  ]);
31755
31811
  const paintThumbnail = React4.useCallback(