@extend-ai/react-xlsx 0.13.4 → 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
@@ -3642,7 +3642,6 @@ var SPREADSHEET_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
3642
3642
  var CONTENT_TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types";
3643
3643
  var DRAWING_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
3644
3644
  var VML_DRAWING_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing";
3645
- var CTRL_PROP_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp";
3646
3645
  var IMAGE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
3647
3646
  var DRAWING_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.drawing+xml";
3648
3647
  var EMU_PER_PIXEL2 = 9525;
@@ -5085,6 +5084,124 @@ function parseSpreadsheetBooleanNode(node) {
5085
5084
  }
5086
5085
  return parseSpreadsheetBooleanValue(node.getAttribute("val") ?? node.textContent);
5087
5086
  }
5087
+ function mapDukeFormControlAnchor(anchor) {
5088
+ return {
5089
+ from: {
5090
+ col: anchor.fromCol,
5091
+ colOffsetEmu: anchor.fromColOffset,
5092
+ row: anchor.fromRow,
5093
+ rowOffsetEmu: anchor.fromRowOffset
5094
+ },
5095
+ kind: "two-cell",
5096
+ to: {
5097
+ col: anchor.toCol,
5098
+ colOffsetEmu: anchor.toColOffset,
5099
+ row: anchor.toRow,
5100
+ rowOffsetEmu: anchor.toRowOffset
5101
+ }
5102
+ };
5103
+ }
5104
+ function mapDukeFormControlKind(kind) {
5105
+ switch (kind.kind) {
5106
+ case "button":
5107
+ return { caption: kind.caption, kind: "button", label: normalizeControlLabel(kind.caption) };
5108
+ case "checkbox":
5109
+ return {
5110
+ caption: kind.caption,
5111
+ checked: kind.state === "checked",
5112
+ kind: "checkbox",
5113
+ label: normalizeControlLabel(kind.caption),
5114
+ linkedCell: kind.cellLink,
5115
+ no3D: kind.no3D,
5116
+ state: kind.state
5117
+ };
5118
+ case "optionButton":
5119
+ return {
5120
+ caption: kind.caption,
5121
+ checked: kind.state === "checked",
5122
+ firstInGroup: kind.firstInGroup,
5123
+ kind: "radio",
5124
+ label: normalizeControlLabel(kind.caption),
5125
+ linkedCell: kind.cellLink,
5126
+ no3D: kind.no3D,
5127
+ state: kind.state
5128
+ };
5129
+ case "label":
5130
+ return { caption: kind.caption, kind: "label", label: normalizeControlLabel(kind.caption) };
5131
+ case "groupBox":
5132
+ return {
5133
+ caption: kind.caption,
5134
+ kind: "group-box",
5135
+ label: normalizeControlLabel(kind.caption),
5136
+ no3D: kind.no3D
5137
+ };
5138
+ case "listBox":
5139
+ return {
5140
+ inputRange: kind.inputRange,
5141
+ kind: "listbox",
5142
+ linkedCell: kind.cellLink,
5143
+ no3D: kind.no3D,
5144
+ selected: [...kind.selected],
5145
+ selection: kind.selection
5146
+ };
5147
+ case "dropdown":
5148
+ return {
5149
+ inputRange: kind.inputRange,
5150
+ kind: "dropdown",
5151
+ lines: kind.lines,
5152
+ linkedCell: kind.cellLink,
5153
+ no3D: kind.no3D,
5154
+ selected: kind.selected
5155
+ };
5156
+ case "scrollbar":
5157
+ return {
5158
+ horizontal: kind.horizontal,
5159
+ increment: kind.increment,
5160
+ kind: "scrollbar",
5161
+ linkedCell: kind.cellLink,
5162
+ max: kind.max,
5163
+ min: kind.min,
5164
+ page: kind.page,
5165
+ value: kind.value
5166
+ };
5167
+ case "spinner":
5168
+ return {
5169
+ increment: kind.increment,
5170
+ kind: "spinner",
5171
+ linkedCell: kind.cellLink,
5172
+ max: kind.max,
5173
+ min: kind.min,
5174
+ value: kind.value
5175
+ };
5176
+ }
5177
+ }
5178
+ function mapDukeFormControl(control, controlIndex, workbookSheetIndex) {
5179
+ return {
5180
+ anchor: mapDukeFormControlAnchor(control.anchor),
5181
+ controlIndex,
5182
+ dukeAnchor: { ...control.anchor },
5183
+ editAs: control.anchor.editAs,
5184
+ hidden: false,
5185
+ id: `form-control-${workbookSheetIndex}-${controlIndex}`,
5186
+ locked: control.locked,
5187
+ name: control.name,
5188
+ printable: control.printable,
5189
+ sheetIndex: workbookSheetIndex,
5190
+ workbookSheetIndex,
5191
+ zIndex: controlIndex + 1,
5192
+ ...mapDukeFormControlKind(control.kind)
5193
+ };
5194
+ }
5195
+ function collectWorkbookFormControls(workbook) {
5196
+ return Array.from({ length: workbook.sheetCount }, (_, workbookSheetIndex) => {
5197
+ try {
5198
+ const controls = workbook.getSheet(workbookSheetIndex).formControls;
5199
+ return Array.isArray(controls) ? controls.map((control, controlIndex) => mapDukeFormControl(control, controlIndex, workbookSheetIndex)) : [];
5200
+ } catch {
5201
+ return [];
5202
+ }
5203
+ });
5204
+ }
5088
5205
  function parseFormControlKind(rawType) {
5089
5206
  const normalized = (rawType ?? "").trim().toLowerCase();
5090
5207
  switch (normalized) {
@@ -5850,27 +5967,10 @@ function parseSheetFormControlNodes(archive, sheetPath) {
5850
5967
  }
5851
5968
  return getLocalElements(sheetDocument, "control").map((controlNode) => ({
5852
5969
  anchor: parseAnchor(getFirstDescendant(controlNode, "anchor") ?? controlNode),
5853
- controlRelationshipId: getRelationshipId(controlNode),
5854
5970
  name: controlNode.getAttribute("name") ?? void 0,
5855
5971
  shapeId: parseFormControlShapeId(controlNode.getAttribute("shapeId"))
5856
5972
  }));
5857
5973
  }
5858
- function parseCtrlPropDocument(archive, ctrlPropPath) {
5859
- const xml = readArchiveText2(archive, ctrlPropPath);
5860
- if (!xml) {
5861
- return null;
5862
- }
5863
- const document2 = parseXml2(xml);
5864
- const root = document2?.documentElement;
5865
- if (!root) {
5866
- return null;
5867
- }
5868
- return {
5869
- checked: parseSpreadsheetBooleanValue(root.getAttribute("checked")),
5870
- linkedCell: root.getAttribute("fmlaLink") ?? void 0,
5871
- objectType: root.getAttribute("objectType") ?? void 0
5872
- };
5873
- }
5874
5974
  function parseVmlFormControls(archive, vmlDrawingPath) {
5875
5975
  const xml = readArchiveText2(archive, vmlDrawingPath);
5876
5976
  if (!xml) {
@@ -5928,21 +6028,18 @@ function parseSheetFormControls(archive, sheetPath, sheetRelationships, workbook
5928
6028
  if (!controlNode.anchor) {
5929
6029
  return;
5930
6030
  }
5931
- const ctrlPropRelationship = controlNode.controlRelationshipId ? sheetRelationships.get(controlNode.controlRelationshipId) ?? null : null;
5932
- const ctrlPropPath = ctrlPropRelationship?.type === CTRL_PROP_REL_TYPE ? ctrlPropRelationship.target : null;
5933
- const ctrlProp = ctrlPropPath ? parseCtrlPropDocument(archive, ctrlPropPath) : null;
5934
6031
  const vmlControl = controlNode.shapeId !== null ? vmlControlsByShapeId.get(controlNode.shapeId) ?? null : null;
5935
- const kind = parseFormControlKind(ctrlProp?.objectType ?? vmlControl?.objectType);
6032
+ const kind = parseFormControlKind(vmlControl?.objectType);
5936
6033
  parsedControls.push({
5937
6034
  anchor: controlNode.anchor,
5938
- checked: ctrlProp?.checked ?? vmlControl?.checked,
6035
+ checked: vmlControl?.checked,
5939
6036
  fontFamily: vmlControl?.fontFamily,
5940
6037
  fontSizePt: vmlControl?.fontSizePt,
5941
6038
  hidden: vmlControl?.hidden ?? false,
5942
6039
  id: `form-control-${workbookSheetIndex}-${index}`,
5943
6040
  kind,
5944
6041
  label: vmlControl?.label,
5945
- linkedCell: ctrlProp?.linkedCell ?? vmlControl?.linkedCell,
6042
+ linkedCell: vmlControl?.linkedCell,
5946
6043
  name: controlNode.name,
5947
6044
  sheetIndex: workbookSheetIndex,
5948
6045
  textAlign: vmlControl?.textAlign,
@@ -6028,6 +6125,92 @@ function enrichFormControlsWithHiddenShapes(formControls, shapes, sheetState) {
6028
6125
  };
6029
6126
  });
6030
6127
  }
6128
+ function getFormControlAnchorStart(anchor) {
6129
+ return anchor.kind === "absolute" ? null : { col: anchor.from.col, row: anchor.from.row };
6130
+ }
6131
+ function scoreParsedFormControlMatch(dukeControl, parsedControl, parsedIndex) {
6132
+ if (dukeControl.kind !== parsedControl.kind && parsedControl.kind !== "unknown") {
6133
+ return Number.NEGATIVE_INFINITY;
6134
+ }
6135
+ let score = 10;
6136
+ const dukeName = normalizeControlLabel(dukeControl.name)?.toLowerCase();
6137
+ const parsedName = normalizeControlLabel(parsedControl.name)?.toLowerCase();
6138
+ if (dukeName && parsedName) {
6139
+ if (dukeName !== parsedName) {
6140
+ return Number.NEGATIVE_INFINITY;
6141
+ }
6142
+ score += 100;
6143
+ }
6144
+ const dukeStart = getFormControlAnchorStart(dukeControl.anchor);
6145
+ const parsedStart = getFormControlAnchorStart(parsedControl.anchor);
6146
+ if (dukeStart && parsedStart && dukeStart.col === parsedStart.col && dukeStart.row === parsedStart.row) {
6147
+ score += 40;
6148
+ }
6149
+ if (dukeControl.controlIndex === parsedIndex) {
6150
+ score += 5;
6151
+ }
6152
+ return score;
6153
+ }
6154
+ function areDukeFormControlAnchorsEqual(left, right) {
6155
+ if (!left || !right) {
6156
+ return true;
6157
+ }
6158
+ return left.editAs === right.editAs && left.fromCol === right.fromCol && left.fromColOffset === right.fromColOffset && left.fromRow === right.fromRow && left.fromRowOffset === right.fromRowOffset && left.toCol === right.toCol && left.toColOffset === right.toColOffset && left.toRow === right.toRow && left.toRowOffset === right.toRowOffset;
6159
+ }
6160
+ function mergeDukeAndParsedFormControls(dukeControls, parsedControls, keepUnmatchedParsedControl = () => true) {
6161
+ const usedParsedIndexes = /* @__PURE__ */ new Set();
6162
+ const mergedControls = dukeControls.map((dukeControl) => {
6163
+ let bestIndex = -1;
6164
+ let bestScore = Number.NEGATIVE_INFINITY;
6165
+ parsedControls.forEach((parsedControl2, parsedIndex) => {
6166
+ if (usedParsedIndexes.has(parsedIndex)) {
6167
+ return;
6168
+ }
6169
+ const score = scoreParsedFormControlMatch(dukeControl, parsedControl2, parsedIndex);
6170
+ if (score > bestScore) {
6171
+ bestIndex = parsedIndex;
6172
+ bestScore = score;
6173
+ }
6174
+ });
6175
+ const parsedControl = bestIndex >= 0 && bestScore >= 10 ? parsedControls[bestIndex] : null;
6176
+ if (!parsedControl) {
6177
+ return dukeControl;
6178
+ }
6179
+ usedParsedIndexes.add(bestIndex);
6180
+ const dukeAnchorChanged = !areDukeFormControlAnchorsEqual(
6181
+ parsedControl.dukeAnchor,
6182
+ dukeControl.dukeAnchor
6183
+ );
6184
+ return {
6185
+ ...parsedControl,
6186
+ ...dukeControl,
6187
+ anchor: dukeAnchorChanged ? dukeControl.anchor : parsedControl.anchor,
6188
+ fontFamily: parsedControl.fontFamily,
6189
+ fontSizePt: parsedControl.fontSizePt,
6190
+ hidden: parsedControl.hidden,
6191
+ id: parsedControl.id,
6192
+ label: dukeControl.label ?? parsedControl.label,
6193
+ name: dukeControl.name ?? parsedControl.name,
6194
+ textAlign: parsedControl.textAlign,
6195
+ textColor: parsedControl.textColor,
6196
+ zIndex: parsedControl.zIndex
6197
+ };
6198
+ });
6199
+ parsedControls.forEach((parsedControl, parsedIndex) => {
6200
+ if (!usedParsedIndexes.has(parsedIndex) && keepUnmatchedParsedControl(parsedControl)) {
6201
+ mergedControls.push(parsedControl);
6202
+ }
6203
+ });
6204
+ return mergedControls.sort((left, right) => left.zIndex - right.zIndex);
6205
+ }
6206
+ function refreshWorkbookFormControls(workbook, currentControlsByWorkbookSheetIndex) {
6207
+ const dukeControlsByWorkbookSheetIndex = collectWorkbookFormControls(workbook);
6208
+ return dukeControlsByWorkbookSheetIndex.map((dukeControls, workbookSheetIndex) => mergeDukeAndParsedFormControls(
6209
+ dukeControls,
6210
+ currentControlsByWorkbookSheetIndex[workbookSheetIndex] ?? [],
6211
+ (control) => control.controlIndex === void 0
6212
+ ));
6213
+ }
6031
6214
  function revokeWorkbookImageAssets(assets) {
6032
6215
  if (!assets) {
6033
6216
  return;
@@ -6060,7 +6243,7 @@ function parseWorkbookStructureAssetsFromArchive(archive, options) {
6060
6243
  workbookSheets
6061
6244
  };
6062
6245
  }
6063
- function parseWorkbookImageAssets(bytes) {
6246
+ function parseWorkbookImageAssets(bytes, workbookOrFormControls) {
6064
6247
  const archive = (0, import_fflate2.unzipSync)(bytes);
6065
6248
  const {
6066
6249
  contentTypes,
@@ -6079,6 +6262,7 @@ function parseWorkbookImageAssets(bytes) {
6079
6262
  const shapesByWorkbookSheetIndex = [];
6080
6263
  const sheetOrigins = [];
6081
6264
  const imageOriginsById = /* @__PURE__ */ new Map();
6265
+ const dukeFormControlsByWorkbookSheetIndex = Array.isArray(workbookOrFormControls) ? workbookOrFormControls : collectWorkbookFormControls(workbookOrFormControls);
6082
6266
  workbookSheets.forEach((sheet, workbookSheetIndex) => {
6083
6267
  const sheetRelationships = parseRelationships(archive, relsPathForDocument(sheet.path), sheet.path);
6084
6268
  const attachments = [];
@@ -6124,7 +6308,10 @@ function parseWorkbookImageAssets(bytes) {
6124
6308
  shapeList,
6125
6309
  sheetStatesByWorkbookSheetIndex[workbookSheetIndex] ?? null
6126
6310
  );
6127
- formControlsByWorkbookSheetIndex[workbookSheetIndex] = enrichedFormControlList;
6311
+ formControlsByWorkbookSheetIndex[workbookSheetIndex] = mergeDukeAndParsedFormControls(
6312
+ dukeFormControlsByWorkbookSheetIndex[workbookSheetIndex] ?? [],
6313
+ enrichedFormControlList
6314
+ );
6128
6315
  imagesByWorkbookSheetIndex[workbookSheetIndex] = imageList;
6129
6316
  shapesByWorkbookSheetIndex[workbookSheetIndex] = visibleShapeList;
6130
6317
  sheetOrigins[workbookSheetIndex] = attachments.length > 0 ? {
@@ -7199,6 +7386,144 @@ function parseA1RangeReference2(reference) {
7199
7386
  }
7200
7387
  return normalizeRange({ start, end });
7201
7388
  }
7389
+ function resolveWorkbookReference(workbook, defaultWorkbookSheetIndex, rawReference, resolvingNamedRange = false) {
7390
+ const reference = rawReference.trim().replace(/^=/, "");
7391
+ if (!reference) {
7392
+ return null;
7393
+ }
7394
+ let workbookSheetIndex = defaultWorkbookSheetIndex;
7395
+ let rangeReference = reference;
7396
+ const bangIndex = reference.lastIndexOf("!");
7397
+ if (bangIndex >= 0) {
7398
+ let sheetName = reference.slice(0, bangIndex).trim();
7399
+ rangeReference = reference.slice(bangIndex + 1).trim();
7400
+ if (sheetName.startsWith("'") && sheetName.endsWith("'")) {
7401
+ sheetName = sheetName.slice(1, -1).replace(/''/g, "'");
7402
+ }
7403
+ const resolvedSheetIndex = workbook.sheetIndex(sheetName);
7404
+ if (resolvedSheetIndex === void 0) {
7405
+ return null;
7406
+ }
7407
+ workbookSheetIndex = resolvedSheetIndex;
7408
+ } else if (!resolvingNamedRange) {
7409
+ const namedRange = workbook.getNamedRange(reference);
7410
+ if (namedRange) {
7411
+ return resolveWorkbookReference(workbook, defaultWorkbookSheetIndex, namedRange, true);
7412
+ }
7413
+ }
7414
+ const range = parseA1RangeReference2(rangeReference.replace(/\$/g, ""));
7415
+ if (!range || workbookSheetIndex < 0 || workbookSheetIndex >= workbook.sheetCount) {
7416
+ return null;
7417
+ }
7418
+ try {
7419
+ return {
7420
+ range,
7421
+ workbookSheetIndex,
7422
+ worksheet: workbook.getSheet(workbookSheetIndex)
7423
+ };
7424
+ } catch {
7425
+ return null;
7426
+ }
7427
+ }
7428
+ function formControlInputFromDukeControl(control) {
7429
+ return {
7430
+ anchor: { ...control.anchor },
7431
+ kind: { ...control.kind },
7432
+ locked: control.locked,
7433
+ name: control.name,
7434
+ printable: control.printable
7435
+ };
7436
+ }
7437
+ function getFormControlCellLink(kind) {
7438
+ return "cellLink" in kind ? kind.cellLink : void 0;
7439
+ }
7440
+ function anchorContainsControl(group, control) {
7441
+ const groupAnchor = group.anchor;
7442
+ const controlAnchor = control.anchor;
7443
+ return controlAnchor.fromCol >= groupAnchor.fromCol && controlAnchor.fromCol <= groupAnchor.toCol && controlAnchor.fromRow >= groupAnchor.fromRow && controlAnchor.fromRow <= groupAnchor.toRow;
7444
+ }
7445
+ function resolveOptionButtonGroupIndexes(controls, targetIndex) {
7446
+ const target = controls[targetIndex];
7447
+ if (!target || target.kind.kind !== "optionButton") {
7448
+ return [];
7449
+ }
7450
+ if (target.kind.cellLink) {
7451
+ const targetCellLink = target.kind.cellLink;
7452
+ return controls.flatMap((control, index) => control.kind.kind === "optionButton" && control.kind.cellLink === targetCellLink ? [index] : []);
7453
+ }
7454
+ const containingGroups = controls.map((control, index) => ({ control, index })).filter(({ control }) => control.kind.kind === "groupBox" && anchorContainsControl(control, target)).sort((left, right) => {
7455
+ const leftArea = (left.control.anchor.toCol - left.control.anchor.fromCol) * (left.control.anchor.toRow - left.control.anchor.fromRow);
7456
+ const rightArea = (right.control.anchor.toCol - right.control.anchor.fromCol) * (right.control.anchor.toRow - right.control.anchor.fromRow);
7457
+ return leftArea - rightArea;
7458
+ });
7459
+ const containingGroup = containingGroups[0]?.control;
7460
+ if (containingGroup) {
7461
+ return controls.flatMap((control, index) => control.kind.kind === "optionButton" && anchorContainsControl(containingGroup, control) ? [index] : []);
7462
+ }
7463
+ const optionIndexes = controls.flatMap((control, index) => control.kind.kind === "optionButton" ? [index] : []);
7464
+ const targetPosition = optionIndexes.indexOf(targetIndex);
7465
+ if (targetPosition < 0) {
7466
+ return [];
7467
+ }
7468
+ let startPosition = targetPosition;
7469
+ while (startPosition > 0) {
7470
+ const control = controls[optionIndexes[startPosition] ?? -1];
7471
+ if (control?.kind.kind === "optionButton" && control.kind.firstInGroup) {
7472
+ break;
7473
+ }
7474
+ startPosition -= 1;
7475
+ }
7476
+ let endPosition = targetPosition + 1;
7477
+ while (endPosition < optionIndexes.length) {
7478
+ const control = controls[optionIndexes[endPosition] ?? -1];
7479
+ if (control?.kind.kind === "optionButton" && control.kind.firstInGroup) {
7480
+ break;
7481
+ }
7482
+ endPosition += 1;
7483
+ }
7484
+ return optionIndexes.slice(startPosition, endPosition);
7485
+ }
7486
+ function resolveLinkedCellValue(controls, controlIndex) {
7487
+ const control = controls[controlIndex];
7488
+ if (!control) {
7489
+ return void 0;
7490
+ }
7491
+ switch (control.kind.kind) {
7492
+ case "checkbox":
7493
+ return control.kind.state === "mixed" ? "#N/A" : control.kind.state === "checked";
7494
+ case "optionButton": {
7495
+ const groupIndexes = resolveOptionButtonGroupIndexes(controls, controlIndex);
7496
+ const checkedIndex = groupIndexes.find((index) => controls[index]?.kind.kind === "optionButton" && controls[index]?.kind.state === "checked");
7497
+ const groupPosition = checkedIndex === void 0 ? -1 : groupIndexes.indexOf(checkedIndex);
7498
+ return groupPosition >= 0 ? groupPosition + 1 : "";
7499
+ }
7500
+ case "dropdown":
7501
+ return control.kind.selected === void 0 ? "" : control.kind.selected + 1;
7502
+ case "listBox":
7503
+ return control.kind.selection === "single" && control.kind.selected.length === 1 ? (control.kind.selected[0] ?? -1) + 1 : "";
7504
+ case "scrollbar":
7505
+ case "spinner":
7506
+ return control.kind.value;
7507
+ default:
7508
+ return void 0;
7509
+ }
7510
+ }
7511
+ function syncFormControlLinkedCell(workbook, workbookSheetIndex, controls, controlIndex) {
7512
+ const control = controls[controlIndex];
7513
+ if (!control) {
7514
+ return;
7515
+ }
7516
+ const cellLink = getFormControlCellLink(control.kind);
7517
+ const value = resolveLinkedCellValue(controls, controlIndex);
7518
+ if (!cellLink || value === void 0) {
7519
+ return;
7520
+ }
7521
+ const target = resolveWorkbookReference(workbook, workbookSheetIndex, cellLink);
7522
+ if (!target || target.range.start.row !== target.range.end.row || target.range.start.col !== target.range.end.col) {
7523
+ return;
7524
+ }
7525
+ target.worksheet.setCell(cellAddressToA1(target.range.start), value);
7526
+ }
7202
7527
  function parseWorksheetFreezePanes(worksheet) {
7203
7528
  const rawFreezePanes = worksheet.freezePanes;
7204
7529
  const row = typeof rawFreezePanes?.row === "number" && rawFreezePanes.row >= 0 ? rawFreezePanes.row : null;
@@ -7965,7 +8290,7 @@ function createBasicWorkbookAssets(workbook) {
7965
8290
  const objectUrls = [];
7966
8291
  return {
7967
8292
  archive: {},
7968
- formControlsByWorkbookSheetIndex: Array.from({ length: workbook.sheetCount }, () => []),
8293
+ formControlsByWorkbookSheetIndex: collectWorkbookFormControls(workbook),
7969
8294
  imageOriginsById: /* @__PURE__ */ new Map(),
7970
8295
  imagesByWorkbookSheetIndex: collectWorksheetApiImages(workbook, objectUrls),
7971
8296
  namedCellStyleByName: {},
@@ -7983,7 +8308,7 @@ function loadWorkbookImageAssets(bytes, workbook, skipXmlParsing = false) {
7983
8308
  if (shouldSkipXmlParsingForWorkbook(bytes, skipXmlParsing) || !isZipWorkbook(bytes)) {
7984
8309
  return createBasicWorkbookAssets(workbook);
7985
8310
  }
7986
- const parsedAssets = parseWorkbookImageAssets(bytes);
8311
+ const parsedAssets = parseWorkbookImageAssets(bytes, workbook);
7987
8312
  const apiImagesByWorkbookSheetIndex = collectWorksheetApiImages(workbook, parsedAssets.objectUrls);
7988
8313
  const imagesByWorkbookSheetIndex = Array.from(
7989
8314
  { length: Math.max(workbook.sheetCount, parsedAssets.imagesByWorkbookSheetIndex.length, apiImagesByWorkbookSheetIndex.length) },
@@ -8300,6 +8625,12 @@ function useXlsxViewerController(options) {
8300
8625
  };
8301
8626
  }, [skipXmlParsing]);
8302
8627
  const refreshWorkbookState = React.useCallback((targetWorkbook) => {
8628
+ const currentFormControls = imageAssetsRef.current?.formControlsByWorkbookSheetIndex ?? [];
8629
+ const nextFormControls = refreshWorkbookFormControls(targetWorkbook, currentFormControls);
8630
+ if (imageAssetsRef.current) {
8631
+ imageAssetsRef.current.formControlsByWorkbookSheetIndex = nextFormControls;
8632
+ }
8633
+ setFormControlsByWorkbookSheetIndex(nextFormControls);
8303
8634
  const nextSheets = buildSheetList(
8304
8635
  targetWorkbook,
8305
8636
  imageAssetsRef.current?.sheetStatesByWorkbookSheetIndex,
@@ -8421,12 +8752,24 @@ function useXlsxViewerController(options) {
8421
8752
  if (!effectiveSkipXmlParsing && hasIncompleteWorkerChartSnapshot(snapshot)) {
8422
8753
  throw new Error("Worker chart payload incomplete");
8423
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);
8424
8764
  setWorkbook(null);
8425
8765
  setSheets(snapshot.sheets);
8426
8766
  setChartsByWorkbookSheetIndex(snapshot.chartsByWorkbookSheetIndex);
8427
8767
  setChartsheets(snapshot.chartsheets);
8428
8768
  setTabs(snapshot.tabs);
8429
8769
  chartAssetsRef.current = null;
8770
+ if (!workerImageAssets) {
8771
+ setFormControlsByWorkbookSheetIndex(snapshot.formControlsByWorkbookSheetIndex);
8772
+ }
8430
8773
  setWorkerTablesByWorkbookSheetIndex(snapshot.tablesByWorkbookSheetIndex);
8431
8774
  setShouldAutoCalculate(false);
8432
8775
  setIsWorkerBacked(true);
@@ -8646,6 +8989,7 @@ function useXlsxViewerController(options) {
8646
8989
  setChartsheets(snapshot.chartsheets);
8647
8990
  setTabs(snapshot.tabs);
8648
8991
  chartAssetsRef.current = null;
8992
+ setFormControlsByWorkbookSheetIndex(snapshot.formControlsByWorkbookSheetIndex);
8649
8993
  setWorkerTablesByWorkbookSheetIndex(snapshot.tablesByWorkbookSheetIndex);
8650
8994
  setShouldAutoCalculate(false);
8651
8995
  setIsWorkerBacked(true);
@@ -8767,6 +9111,20 @@ function useXlsxViewerController(options) {
8767
9111
  }
8768
9112
  return workbook.getSheet(activeSheet.workbookSheetIndex);
8769
9113
  }, [activeSheet, workbook]);
9114
+ const getFormControlWorksheet = React.useCallback((sheetIndex = activeSheetIndex) => {
9115
+ const targetSheet = sheets[sheetIndex];
9116
+ if (!workbook || !targetSheet) {
9117
+ return null;
9118
+ }
9119
+ try {
9120
+ return {
9121
+ workbookSheetIndex: targetSheet.workbookSheetIndex,
9122
+ worksheet: workbook.getSheet(targetSheet.workbookSheetIndex)
9123
+ };
9124
+ } catch {
9125
+ return null;
9126
+ }
9127
+ }, [activeSheetIndex, sheets, workbook]);
8770
9128
  const tables = React.useMemo(
8771
9129
  () => isWorkerBacked ? workerTablesByWorkbookSheetIndex[activeSheet?.workbookSheetIndex ?? -1] ?? [] : mapWorksheetTables(getActiveWorksheet()),
8772
9130
  [activeSheet?.workbookSheetIndex, getActiveWorksheet, isWorkerBacked, revision, workerTablesByWorkbookSheetIndex]
@@ -8906,6 +9264,33 @@ function useXlsxViewerController(options) {
8906
9264
  return (formControlsByWorkbookSheetIndex[targetSheet.workbookSheetIndex] ?? []).map(mapPublicFormControl);
8907
9265
  }, [activeSheetIndex, formControlsByWorkbookSheetIndex, mapPublicFormControl, sheets]);
8908
9266
  const formControls = React.useMemo(() => getSheetFormControls(activeSheetIndex), [activeSheetIndex, getSheetFormControls]);
9267
+ const getFormControlItems = React.useCallback((controlIndex, sheetIndex = activeSheetIndex) => {
9268
+ const target = getFormControlWorksheet(sheetIndex);
9269
+ if (!workbook || !target) {
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() ?? [];
9276
+ }
9277
+ const control = target.worksheet.formControls[controlIndex];
9278
+ const inputRange = control?.kind.kind === "listBox" || control?.kind.kind === "dropdown" ? control.kind.inputRange : void 0;
9279
+ if (!inputRange) {
9280
+ return [];
9281
+ }
9282
+ const source = resolveWorkbookReference(workbook, target.workbookSheetIndex, inputRange);
9283
+ if (!source) {
9284
+ return [];
9285
+ }
9286
+ const items = [];
9287
+ for (let row = source.range.start.row; row <= source.range.end.row; row += 1) {
9288
+ for (let col = source.range.start.col; col <= source.range.end.col; col += 1) {
9289
+ items.push(source.worksheet.getFormattedValueAt(row, col));
9290
+ }
9291
+ }
9292
+ return items;
9293
+ }, [activeSheetIndex, formControlsByWorkbookSheetIndex, getFormControlWorksheet, sheets, workbook]);
8909
9294
  const getSheetShapes = React.useCallback((sheetIndex = activeSheetIndex) => {
8910
9295
  const targetSheet = sheets[sheetIndex];
8911
9296
  if (!targetSheet) {
@@ -9303,6 +9688,89 @@ function useXlsxViewerController(options) {
9303
9688
  redoStackRef.current = [];
9304
9689
  setHistoryRevision((current) => current + 1);
9305
9690
  }, [createHistoryEntry]);
9691
+ const addFormControl = React.useCallback((input, sheetIndex = activeSheetIndex) => {
9692
+ const target = getFormControlWorksheet(sheetIndex);
9693
+ if (readOnly || !workbook || !target) {
9694
+ return null;
9695
+ }
9696
+ recordHistoryBeforeMutation();
9697
+ const controlIndex = target.worksheet.addFormControl(input);
9698
+ const controls = target.worksheet.formControls;
9699
+ const addedControl = controls[controlIndex];
9700
+ if (addedControl?.kind.kind === "optionButton" && addedControl.kind.state === "checked") {
9701
+ const groupIndexes = resolveOptionButtonGroupIndexes(controls, controlIndex);
9702
+ for (const siblingIndex of groupIndexes) {
9703
+ if (siblingIndex === controlIndex) {
9704
+ continue;
9705
+ }
9706
+ const sibling = target.worksheet.formControls[siblingIndex];
9707
+ if (sibling?.kind.kind === "optionButton" && sibling.kind.state !== "unchecked") {
9708
+ target.worksheet.setFormControl(siblingIndex, {
9709
+ ...formControlInputFromDukeControl(sibling),
9710
+ kind: { ...sibling.kind, state: "unchecked" }
9711
+ });
9712
+ }
9713
+ }
9714
+ }
9715
+ syncFormControlLinkedCell(
9716
+ workbook,
9717
+ target.workbookSheetIndex,
9718
+ target.worksheet.formControls,
9719
+ controlIndex
9720
+ );
9721
+ maybeRecalculateWorkbook(workbook);
9722
+ refreshWorkbookState(workbook);
9723
+ return controlIndex;
9724
+ }, [activeSheetIndex, getFormControlWorksheet, maybeRecalculateWorkbook, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9725
+ const updateFormControl = React.useCallback((controlIndex, patch, sheetIndex = activeSheetIndex) => {
9726
+ const target = getFormControlWorksheet(sheetIndex);
9727
+ const currentControl = target?.worksheet.formControls[controlIndex];
9728
+ if (readOnly || !workbook || !target || !currentControl) {
9729
+ return false;
9730
+ }
9731
+ recordHistoryBeforeMutation();
9732
+ const nextInput = {
9733
+ ...formControlInputFromDukeControl(currentControl),
9734
+ ...patch,
9735
+ anchor: patch.anchor ? { ...patch.anchor } : { ...currentControl.anchor },
9736
+ kind: patch.kind ? { ...patch.kind } : { ...currentControl.kind }
9737
+ };
9738
+ if (nextInput.kind.kind === "optionButton" && nextInput.kind.state === "checked") {
9739
+ const groupIndexes = resolveOptionButtonGroupIndexes(target.worksheet.formControls, controlIndex);
9740
+ for (const siblingIndex of groupIndexes) {
9741
+ if (siblingIndex === controlIndex) {
9742
+ continue;
9743
+ }
9744
+ const sibling = target.worksheet.formControls[siblingIndex];
9745
+ if (sibling?.kind.kind === "optionButton" && sibling.kind.state !== "unchecked") {
9746
+ target.worksheet.setFormControl(siblingIndex, {
9747
+ ...formControlInputFromDukeControl(sibling),
9748
+ kind: { ...sibling.kind, state: "unchecked" }
9749
+ });
9750
+ }
9751
+ }
9752
+ }
9753
+ target.worksheet.setFormControl(controlIndex, nextInput);
9754
+ syncFormControlLinkedCell(
9755
+ workbook,
9756
+ target.workbookSheetIndex,
9757
+ target.worksheet.formControls,
9758
+ controlIndex
9759
+ );
9760
+ maybeRecalculateWorkbook(workbook);
9761
+ refreshWorkbookState(workbook);
9762
+ return true;
9763
+ }, [activeSheetIndex, getFormControlWorksheet, maybeRecalculateWorkbook, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9764
+ const removeFormControl = React.useCallback((controlIndex, sheetIndex = activeSheetIndex) => {
9765
+ const target = getFormControlWorksheet(sheetIndex);
9766
+ if (readOnly || !workbook || !target || !target.worksheet.formControls[controlIndex]) {
9767
+ return false;
9768
+ }
9769
+ recordHistoryBeforeMutation();
9770
+ target.worksheet.removeFormControl(controlIndex);
9771
+ refreshWorkbookState(workbook);
9772
+ return true;
9773
+ }, [activeSheetIndex, getFormControlWorksheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9306
9774
  const recordCellEditHistory = React.useCallback((cell, before, after) => {
9307
9775
  if (!activeSheet || isApplyingHistoryRef.current) {
9308
9776
  return;
@@ -10111,8 +10579,15 @@ function useXlsxViewerController(options) {
10111
10579
  }
10112
10580
  workbook.addSheet(candidate);
10113
10581
  sheetOriginsRef.current = [...sheetOriginsRef.current, null];
10582
+ setFormControlsByWorkbookSheetIndex((current) => [...current, []]);
10114
10583
  setImagesByWorkbookSheetIndex((current) => [...current, []]);
10115
10584
  setShapesByWorkbookSheetIndex((current) => [...current, []]);
10585
+ if (imageAssetsRef.current) {
10586
+ imageAssetsRef.current.formControlsByWorkbookSheetIndex = [
10587
+ ...imageAssetsRef.current.formControlsByWorkbookSheetIndex,
10588
+ []
10589
+ ];
10590
+ }
10116
10591
  const nextSheets = buildSheetList(
10117
10592
  workbook,
10118
10593
  imageAssetsRef.current?.sheetStatesByWorkbookSheetIndex,
@@ -10142,9 +10617,13 @@ function useXlsxViewerController(options) {
10142
10617
  recordHistoryBeforeMutation();
10143
10618
  workbook.removeSheet(activeSheet.workbookSheetIndex);
10144
10619
  sheetOriginsRef.current = sheetOriginsRef.current.filter((_, index) => index !== activeSheet.workbookSheetIndex);
10620
+ setFormControlsByWorkbookSheetIndex((current) => current.filter((_, index) => index !== activeSheet.workbookSheetIndex));
10145
10621
  setImagesByWorkbookSheetIndex((current) => current.filter((_, index) => index !== activeSheet.workbookSheetIndex));
10146
10622
  setShapesByWorkbookSheetIndex((current) => current.filter((_, index) => index !== activeSheet.workbookSheetIndex));
10147
10623
  if (imageAssetsRef.current) {
10624
+ imageAssetsRef.current.formControlsByWorkbookSheetIndex = imageAssetsRef.current.formControlsByWorkbookSheetIndex.filter(
10625
+ (_, index) => index !== activeSheet.workbookSheetIndex
10626
+ );
10148
10627
  imageAssetsRef.current.sheetStatesByWorkbookSheetIndex = imageAssetsRef.current.sheetStatesByWorkbookSheetIndex.filter(
10149
10628
  (_, index) => index !== activeSheet.workbookSheetIndex
10150
10629
  );
@@ -10445,6 +10924,7 @@ function useXlsxViewerController(options) {
10445
10924
  activeSheetIndex,
10446
10925
  activeTab,
10447
10926
  activeTabIndex,
10927
+ addFormControl,
10448
10928
  addSheet,
10449
10929
  canRedo,
10450
10930
  canDownload: Boolean(file ?? src),
@@ -10484,6 +10964,7 @@ function useXlsxViewerController(options) {
10484
10964
  getClipboardData,
10485
10965
  getCellDisplayValue: getCellDisplayValue2,
10486
10966
  getCellFormula,
10967
+ getFormControlItems,
10487
10968
  getCellSnapshotAsync: isWorkerBacked ? getCellSnapshotAsync : void 0,
10488
10969
  getActiveWorksheet,
10489
10970
  getRowsBatchAsync: isWorkerBacked ? getRowsBatchAsync : void 0,
@@ -10501,6 +10982,7 @@ function useXlsxViewerController(options) {
10501
10982
  pasteStructuredClipboardData,
10502
10983
  pasteText,
10503
10984
  removeActiveSheet,
10985
+ removeFormControl,
10504
10986
  readOnly,
10505
10987
  recalculate,
10506
10988
  redo,
@@ -10551,6 +11033,7 @@ function useXlsxViewerController(options) {
10551
11033
  undo,
10552
11034
  unmergeSelection,
10553
11035
  updateChart,
11036
+ updateFormControl,
10554
11037
  workbook,
10555
11038
  zoomIn,
10556
11039
  zoomOut,
@@ -10563,6 +11046,7 @@ function useXlsxViewerController(options) {
10563
11046
  activeSheetIndex,
10564
11047
  activeTab,
10565
11048
  activeTabIndex,
11049
+ addFormControl,
10566
11050
  addSheet,
10567
11051
  canLoadDeferred,
10568
11052
  canRedo,
@@ -10599,6 +11083,7 @@ function useXlsxViewerController(options) {
10599
11083
  getClipboardData,
10600
11084
  getCellDisplayValue2,
10601
11085
  getCellFormula,
11086
+ getFormControlItems,
10602
11087
  getCellSnapshotAsync,
10603
11088
  getActiveWorksheet,
10604
11089
  historyRevision,
@@ -10616,6 +11101,7 @@ function useXlsxViewerController(options) {
10616
11101
  pasteStructuredClipboardData,
10617
11102
  pasteText,
10618
11103
  removeActiveSheet,
11104
+ removeFormControl,
10619
11105
  readOnly,
10620
11106
  recalculate,
10621
11107
  redo,
@@ -10666,6 +11152,7 @@ function useXlsxViewerController(options) {
10666
11152
  undo,
10667
11153
  unmergeSelection,
10668
11154
  updateChart,
11155
+ updateFormControl,
10669
11156
  workbook,
10670
11157
  zoomIn,
10671
11158
  zoomOut,
@@ -20338,6 +20825,40 @@ function getCellBooleanValue(worksheet, row, col) {
20338
20825
  }
20339
20826
  return null;
20340
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
+ }
20341
20862
  function parseA1RangeReference3(reference) {
20342
20863
  const [startRef, endRef = startRef] = reference.split(":");
20343
20864
  const start = parseA1CellReference3(startRef ?? "");
@@ -21637,13 +22158,90 @@ function renderConditionalIcon(icon, scale = 1) {
21637
22158
  }
21638
22159
  );
21639
22160
  }
21640
- function renderCheckboxControl(checked, palette, scale = 1) {
22161
+ function isFormControlChecked(control) {
22162
+ return control.state ? control.state === "checked" : Boolean(control.checked);
22163
+ }
22164
+ function isFormControlMixed(control) {
22165
+ return control.state === "mixed";
22166
+ }
22167
+ function buildFormControlKindInput(control) {
22168
+ switch (control.kind) {
22169
+ case "button":
22170
+ return { caption: control.caption ?? control.label ?? "", kind: "button" };
22171
+ case "checkbox":
22172
+ return {
22173
+ caption: control.caption ?? control.label ?? "",
22174
+ cellLink: control.linkedCell,
22175
+ kind: "checkbox",
22176
+ no3D: control.no3D ?? false,
22177
+ state: control.state ?? (control.checked ? "checked" : "unchecked")
22178
+ };
22179
+ case "radio":
22180
+ return {
22181
+ caption: control.caption ?? control.label ?? "",
22182
+ cellLink: control.linkedCell,
22183
+ firstInGroup: control.firstInGroup,
22184
+ kind: "optionButton",
22185
+ no3D: control.no3D ?? false,
22186
+ state: control.state === "checked" ? "checked" : "unchecked"
22187
+ };
22188
+ case "label":
22189
+ return { caption: control.caption ?? control.label ?? "", kind: "label" };
22190
+ case "group-box":
22191
+ return {
22192
+ caption: control.caption ?? control.label ?? "",
22193
+ kind: "groupBox",
22194
+ no3D: control.no3D ?? false
22195
+ };
22196
+ case "listbox":
22197
+ return {
22198
+ cellLink: control.linkedCell,
22199
+ inputRange: control.inputRange,
22200
+ kind: "listBox",
22201
+ no3D: control.no3D ?? false,
22202
+ selected: Array.isArray(control.selected) ? control.selected : control.selected === void 0 ? [] : [control.selected],
22203
+ selection: control.selection ?? "single"
22204
+ };
22205
+ case "dropdown":
22206
+ return {
22207
+ cellLink: control.linkedCell,
22208
+ inputRange: control.inputRange,
22209
+ kind: "dropdown",
22210
+ lines: control.lines ?? 8,
22211
+ no3D: control.no3D ?? false,
22212
+ selected: typeof control.selected === "number" ? control.selected : void 0
22213
+ };
22214
+ case "scrollbar":
22215
+ return {
22216
+ cellLink: control.linkedCell,
22217
+ horizontal: control.horizontal ?? true,
22218
+ increment: control.increment ?? 1,
22219
+ kind: "scrollbar",
22220
+ max: control.max ?? 100,
22221
+ min: control.min ?? 0,
22222
+ page: control.page ?? 10,
22223
+ value: control.value ?? control.min ?? 0
22224
+ };
22225
+ case "spinner":
22226
+ return {
22227
+ cellLink: control.linkedCell,
22228
+ increment: control.increment ?? 1,
22229
+ kind: "spinner",
22230
+ max: control.max ?? 100,
22231
+ min: control.min ?? 0,
22232
+ value: control.value ?? control.min ?? 0
22233
+ };
22234
+ default:
22235
+ return null;
22236
+ }
22237
+ }
22238
+ function renderCheckboxControl(checked, palette, scale = 1, mixed = false) {
21641
22239
  const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
21642
- const fill = checked ? paletteIsDark(palette) ? "#60a5fa" : "#2563eb" : "transparent";
22240
+ const fill = checked || mixed ? paletteIsDark(palette) ? "#60a5fa" : "#2563eb" : "transparent";
21643
22241
  const check = paletteIsDark(palette) ? "#020617" : "#ffffff";
21644
22242
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { "aria-hidden": "true", height: 14 * scale, style: { display: "block" }, viewBox: "0 0 16 16", width: 14 * scale, children: [
21645
22243
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { fill, height: 11, rx: 2, ry: 2, stroke, strokeWidth: 1.2, width: 11, x: 2.5, y: 2.5 }),
21646
- checked ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
22244
+ mixed ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M5 8h6", fill: "none", stroke: check, strokeLinecap: "round", strokeWidth: 1.8 }) : checked ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
21647
22245
  "path",
21648
22246
  {
21649
22247
  d: "M5 8.1 7.1 10.2 11.3 5.8",
@@ -21813,6 +22411,8 @@ function drawStaticFormControl(context, control, rect, palette, zoomFactor, shee
21813
22411
  const textColor = control.textColor ?? "#000000";
21814
22412
  const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
21815
22413
  const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
22414
+ const controlChecked = isFormControlChecked(control);
22415
+ const controlMixed = isFormControlMixed(control);
21816
22416
  context.save();
21817
22417
  context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
21818
22418
  context.textBaseline = "middle";
@@ -21853,14 +22453,19 @@ function drawStaticFormControl(context, control, rect, palette, zoomFactor, shee
21853
22453
  const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
21854
22454
  if (control.kind === "checkbox") {
21855
22455
  context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
21856
- if (control.checked) {
22456
+ if (controlChecked || controlMixed) {
21857
22457
  context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
21858
22458
  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));
21859
22459
  context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
21860
22460
  context.beginPath();
21861
- context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
21862
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
21863
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
22461
+ if (controlMixed) {
22462
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.25, rect.top + rect.height / 2);
22463
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.75, rect.top + rect.height / 2);
22464
+ } else {
22465
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
22466
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
22467
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
22468
+ }
21864
22469
  context.stroke();
21865
22470
  }
21866
22471
  textLeft += iconSize + 4 * zoomFactor;
@@ -21868,7 +22473,7 @@ function drawStaticFormControl(context, control, rect, palette, zoomFactor, shee
21868
22473
  context.beginPath();
21869
22474
  context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
21870
22475
  context.stroke();
21871
- if (control.checked) {
22476
+ if (controlChecked) {
21872
22477
  context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
21873
22478
  context.beginPath();
21874
22479
  context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
@@ -22504,7 +23109,10 @@ function XlsxGrid({
22504
23109
  getCellStyle,
22505
23110
  loadingComponent,
22506
23111
  loadingState,
23112
+ onFormControlAction,
23113
+ onFormControlChange,
22507
23114
  renderChartLoading,
23115
+ renderFormControl,
22508
23116
  palette,
22509
23117
  renderImage,
22510
23118
  renderImageSelection,
@@ -22541,6 +23149,7 @@ function XlsxGrid({
22541
23149
  getRowsBatchAsync,
22542
23150
  getClipboardData,
22543
23151
  getCellDisplayValue: getControllerCellDisplayValue,
23152
+ getFormControlItems,
22544
23153
  images,
22545
23154
  shapes,
22546
23155
  isLoadDeferred,
@@ -22578,9 +23187,13 @@ function XlsxGrid({
22578
23187
  sortTable,
22579
23188
  tables,
22580
23189
  undo,
23190
+ updateFormControl,
22581
23191
  workbook,
22582
23192
  zoomScale
22583
23193
  } = controller;
23194
+ const formControlItemsById = React4.useMemo(() => new Map(
23195
+ formControls.flatMap((control) => control.controlIndex !== void 0 && (control.kind === "dropdown" || control.kind === "listbox") ? [[control.id, getFormControlItems(control.controlIndex, control.sheetIndex)]] : [])
23196
+ ), [formControls, getFormControlItems]);
22584
23197
  const canResizeHeaders = !readOnly || allowResizeInReadOnly;
22585
23198
  const scrollRef = React4.useRef(null);
22586
23199
  const wrapperRef = React4.useRef(null);
@@ -25064,14 +25677,7 @@ function XlsxGrid({
25064
25677
  () => shouldBakeCanvasStaticDrawings ? shapeRects.filter(({ shape }) => shape.hyperlink) : shapeRects,
25065
25678
  [shapeRects, shouldBakeCanvasStaticDrawings]
25066
25679
  );
25067
- const bakedFormControlRects = React4.useMemo(
25068
- () => shouldBakeCanvasStaticDrawings ? formControlRects : [],
25069
- [formControlRects, shouldBakeCanvasStaticDrawings]
25070
- );
25071
- const domFormControlRects = React4.useMemo(
25072
- () => shouldBakeCanvasStaticDrawings ? [] : formControlRects,
25073
- [formControlRects, shouldBakeCanvasStaticDrawings]
25074
- );
25680
+ const domFormControlRects = formControlRects;
25075
25681
  const bakedImageRects = React4.useMemo(
25076
25682
  () => shouldBakeCanvasImages ? imageRects.filter(({ image }) => !image.hyperlink && selectedImageId !== image.id) : [],
25077
25683
  [imageRects, selectedImageId, shouldBakeCanvasImages]
@@ -25088,11 +25694,9 @@ function XlsxGrid({
25088
25694
  return "";
25089
25695
  }
25090
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("|");
25091
- const controlSignature = bakedFormControlRects.map(({ control, rect }) => `f:${control.id}:${control.zIndex}:${Boolean(control.checked)}:${Math.round(rect.left)}:${Math.round(rect.top)}:${Math.round(rect.width)}:${Math.round(rect.height)}`).join("|");
25092
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("|");
25093
- return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${controlSignature}:${imageSignature}`;
25698
+ return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${imageSignature}`;
25094
25699
  }, [
25095
- bakedFormControlRects,
25096
25700
  bakedImageRects,
25097
25701
  bakedShapeRects,
25098
25702
  canvasImageLoadVersion,
@@ -26627,12 +27231,6 @@ function XlsxGrid({
26627
27231
  shape,
26628
27232
  zIndex: shape.zIndex
26629
27233
  })),
26630
- ...bakedFormControlRects.map(({ control, rect }) => ({
26631
- control,
26632
- kind: "formControl",
26633
- rect,
26634
- zIndex: control.zIndex
26635
- })),
26636
27234
  ...bakedImageRects.map(({ image, rect }) => ({
26637
27235
  image,
26638
27236
  kind: "image",
@@ -26783,90 +27381,6 @@ function XlsxGrid({
26783
27381
  drawBakedShapeText(context, shape, localLeft, localTop, rect.width, rect.height);
26784
27382
  context.restore();
26785
27383
  };
26786
- const drawBakedFormControl = (context, control, rect) => {
26787
- const label = resolveFormControlLabel(control);
26788
- const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
26789
- const textColor = control.textColor ?? "#000000";
26790
- const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
26791
- const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
26792
- context.save();
26793
- context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
26794
- context.textBaseline = "middle";
26795
- context.fillStyle = textColor;
26796
- context.strokeStyle = stroke;
26797
- context.lineWidth = Math.max(1, zoomFactor);
26798
- if (control.kind === "group-box") {
26799
- const labelInset = label ? Math.max(7, fontSizePx * 0.5) : 0;
26800
- drawCanvasRoundedRect(context, rect.left, rect.top + labelInset, rect.width, Math.max(0, rect.height - labelInset), 2 * zoomFactor);
26801
- context.stroke();
26802
- if (label) {
26803
- context.fillStyle = SHEET_SURFACE;
26804
- const labelWidth = Math.min(rect.width - 16 * zoomFactor, measureCanvasTextWidth(context, label) + 8 * zoomFactor);
26805
- context.fillRect(rect.left + 8 * zoomFactor, rect.top, labelWidth, fontSizePx * 1.2);
26806
- context.fillStyle = textColor;
26807
- context.textAlign = "left";
26808
- context.fillText(label, rect.left + 12 * zoomFactor, rect.top + fontSizePx * 0.55);
26809
- }
26810
- context.restore();
26811
- return;
26812
- }
26813
- if (control.kind === "button" || control.kind === "dropdown" || control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
26814
- if (control.kind === "button") {
26815
- const gradient = context.createLinearGradient(rect.left, rect.top, rect.left, rect.top + rect.height);
26816
- gradient.addColorStop(0, "#f8fafc");
26817
- gradient.addColorStop(1, "#e2e8f0");
26818
- context.fillStyle = gradient;
26819
- } else {
26820
- context.fillStyle = "transparent";
26821
- }
26822
- drawCanvasRoundedRect(context, rect.left, rect.top, rect.width, rect.height, control.kind === "button" ? 4 * zoomFactor : 2 * zoomFactor);
26823
- if (control.kind === "button") {
26824
- context.fill();
26825
- }
26826
- context.stroke();
26827
- }
26828
- let textLeft = rect.left + 2 * zoomFactor;
26829
- const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
26830
- if (control.kind === "checkbox") {
26831
- context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
26832
- if (control.checked) {
26833
- context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
26834
- 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));
26835
- context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
26836
- context.beginPath();
26837
- context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
26838
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
26839
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
26840
- context.stroke();
26841
- }
26842
- textLeft += iconSize + 4 * zoomFactor;
26843
- } else if (control.kind === "radio") {
26844
- context.beginPath();
26845
- context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
26846
- context.stroke();
26847
- if (control.checked) {
26848
- context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
26849
- context.beginPath();
26850
- context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
26851
- context.fill();
26852
- }
26853
- textLeft += iconSize + 4 * zoomFactor;
26854
- }
26855
- if (label) {
26856
- const maxTextWidth = Math.max(0, rect.left + rect.width - textLeft - textRightInset);
26857
- const text = truncateCanvasText(context, label, maxTextWidth);
26858
- context.fillStyle = textColor;
26859
- context.textAlign = control.textAlign === "right" ? "right" : control.textAlign === "center" ? "center" : "left";
26860
- const textX = context.textAlign === "right" ? rect.left + rect.width - textRightInset : context.textAlign === "center" ? textLeft + maxTextWidth / 2 : textLeft;
26861
- context.fillText(text, textX, rect.top + rect.height / 2);
26862
- }
26863
- if (control.kind === "dropdown") {
26864
- context.fillStyle = textColor;
26865
- context.textAlign = "center";
26866
- context.fillText("\u25BC", rect.left + rect.width - 10 * zoomFactor, rect.top + rect.height / 2);
26867
- }
26868
- context.restore();
26869
- };
26870
27384
  const drawBakedCanvasDrawings = (pane, context, dirtyRects) => {
26871
27385
  if (bakedCanvasDrawingEntries.length === 0 || dirtyRects.length === 0) {
26872
27386
  return;
@@ -26881,8 +27395,6 @@ function XlsxGrid({
26881
27395
  }
26882
27396
  if (entry.kind === "shape") {
26883
27397
  drawBakedShape(context, entry.shape, localRect);
26884
- } else if (entry.kind === "formControl") {
26885
- drawBakedFormControl(context, entry.control, localRect);
26886
27398
  } else {
26887
27399
  const imageElement = getCanvasImage(entry.image);
26888
27400
  if (imageElement) {
@@ -27576,7 +28088,6 @@ function XlsxGrid({
27576
28088
  activeSheet,
27577
28089
  applyCanvasViewportCompensation,
27578
28090
  bakedCanvasDrawingSignature,
27579
- bakedFormControlRects,
27580
28091
  bakedImageRects,
27581
28092
  bakedShapeRects,
27582
28093
  canvasColumnHeaderCells,
@@ -28159,10 +28670,22 @@ function XlsxGrid({
28159
28670
  const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
28160
28671
  const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
28161
28672
  const textColor = control.textColor ?? "#000000";
28673
+ const canMutateControl = !readOnly && control.controlIndex !== void 0 && [
28674
+ "checkbox",
28675
+ "dropdown",
28676
+ "listbox",
28677
+ "radio",
28678
+ "scrollbar",
28679
+ "spinner"
28680
+ ].includes(control.kind);
28681
+ const canActivateButton = !readOnly && control.kind === "button" && Boolean(onFormControlAction);
28682
+ const isInteractiveControl = canMutateControl || canActivateButton;
28683
+ const controlItems = formControlItemsById.get(control.id) ?? [];
28162
28684
  const commonStyle = {
28163
28685
  alignItems: control.kind === "group-box" ? "stretch" : "center",
28164
28686
  color: textColor,
28165
28687
  contain: "layout paint",
28688
+ cursor: isInteractiveControl ? "pointer" : void 0,
28166
28689
  display: "flex",
28167
28690
  fontFamily: control.fontFamily ?? "Calibri, sans-serif",
28168
28691
  fontSize: fontSizePx,
@@ -28173,12 +28696,98 @@ function XlsxGrid({
28173
28696
  left: rect.left,
28174
28697
  lineHeight: 1.2,
28175
28698
  overflow: "hidden",
28176
- pointerEvents: "none",
28699
+ pointerEvents: isInteractiveControl ? "auto" : "none",
28177
28700
  position: "absolute",
28178
28701
  top: rect.top,
28179
28702
  width: rect.width,
28180
28703
  zIndex: isFrozenDrawing ? control.zIndex + 20 : control.zIndex
28181
28704
  };
28705
+ const stopControlPointerEvent = (event) => {
28706
+ event.stopPropagation();
28707
+ };
28708
+ const commitControlKind = (kind, nextControl) => {
28709
+ if (readOnly || control.controlIndex === void 0) {
28710
+ return false;
28711
+ }
28712
+ const didUpdate = updateFormControl(control.controlIndex, { kind }, control.sheetIndex);
28713
+ if (didUpdate) {
28714
+ onFormControlChange?.({
28715
+ control: { ...control, ...nextControl },
28716
+ previousControl: control,
28717
+ type: "change"
28718
+ });
28719
+ }
28720
+ return didUpdate;
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
+ }
28182
28791
  if (control.kind === "group-box") {
28183
28792
  const hasLabel = controlLabel.length > 0;
28184
28793
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -28220,15 +28829,83 @@ function XlsxGrid({
28220
28829
  `${pane}-${control.id}`
28221
28830
  );
28222
28831
  }
28223
- let content;
28224
28832
  if (control.kind === "radio") {
28225
- content = renderRadioControl(Boolean(control.checked), palette, zoomFactor);
28226
- } else if (control.kind === "checkbox") {
28227
- content = renderCheckboxControl(Boolean(control.checked), palette, zoomFactor);
28228
- } else if (control.kind === "button") {
28833
+ const checked = isFormControlChecked(control);
28834
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
28835
+ "button",
28836
+ {
28837
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28838
+ "aria-checked": checked,
28839
+ disabled: !readOnly && control.controlIndex === void 0,
28840
+ onClick: (event) => {
28841
+ event.stopPropagation();
28842
+ if (!checked) {
28843
+ setControlState("checked");
28844
+ }
28845
+ },
28846
+ onPointerDown: stopControlPointerEvent,
28847
+ role: "radio",
28848
+ style: {
28849
+ ...commonStyle,
28850
+ background: "transparent",
28851
+ border: 0,
28852
+ boxSizing: "border-box",
28853
+ padding: `0 ${Math.max(1, zoomFactor)}px`
28854
+ },
28855
+ tabIndex: readOnly ? -1 : void 0,
28856
+ type: "button",
28857
+ children: [
28858
+ renderRadioControl(checked, palette, zoomFactor),
28859
+ controlLabel ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }) : null
28860
+ ]
28861
+ },
28862
+ `${pane}-${control.id}`
28863
+ );
28864
+ }
28865
+ if (control.kind === "checkbox") {
28866
+ const checked = isFormControlChecked(control);
28867
+ const mixed = isFormControlMixed(control);
28868
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
28869
+ "button",
28870
+ {
28871
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28872
+ "aria-checked": mixed ? "mixed" : checked,
28873
+ disabled: !readOnly && control.controlIndex === void 0,
28874
+ onClick: (event) => {
28875
+ event.stopPropagation();
28876
+ const nextState = checked && !mixed ? "unchecked" : "checked";
28877
+ setControlState(nextState);
28878
+ },
28879
+ onPointerDown: stopControlPointerEvent,
28880
+ role: "checkbox",
28881
+ style: {
28882
+ ...commonStyle,
28883
+ background: "transparent",
28884
+ border: 0,
28885
+ boxSizing: "border-box",
28886
+ padding: `0 ${Math.max(1, zoomFactor)}px`
28887
+ },
28888
+ tabIndex: readOnly ? -1 : void 0,
28889
+ type: "button",
28890
+ children: [
28891
+ renderCheckboxControl(checked, palette, zoomFactor, mixed),
28892
+ controlLabel ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }) : null
28893
+ ]
28894
+ },
28895
+ `${pane}-${control.id}`
28896
+ );
28897
+ }
28898
+ if (control.kind === "button") {
28229
28899
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28230
- "div",
28900
+ "button",
28231
28901
  {
28902
+ "aria-disabled": readOnly || !onFormControlAction,
28903
+ disabled: !readOnly && !onFormControlAction,
28904
+ onClick: (event) => {
28905
+ event.stopPropagation();
28906
+ activateControl();
28907
+ },
28908
+ onPointerDown: stopControlPointerEvent,
28232
28909
  style: {
28233
28910
  ...commonStyle,
28234
28911
  background: "linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%)",
@@ -28239,68 +28916,157 @@ function XlsxGrid({
28239
28916
  padding: `0 ${6 * zoomFactor}px`,
28240
28917
  textAlign: "center"
28241
28918
  },
28919
+ tabIndex: readOnly ? -1 : void 0,
28920
+ type: "button",
28242
28921
  children: controlLabel
28243
28922
  },
28244
28923
  `${pane}-${control.id}`
28245
28924
  );
28246
- } else if (control.kind === "dropdown") {
28925
+ }
28926
+ if (control.kind === "dropdown") {
28927
+ const selected = typeof control.selected === "number" ? control.selected : void 0;
28247
28928
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
28248
- "div",
28929
+ "select",
28249
28930
  {
28931
+ "aria-disabled": readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28932
+ "aria-label": controlLabel || control.name || "Dropdown form control",
28933
+ disabled: !readOnly && (control.controlIndex === void 0 || controlItems.length === 0),
28934
+ onChange: (event) => {
28935
+ event.stopPropagation();
28936
+ const nextSelected = event.currentTarget.value === "" ? void 0 : Number(event.currentTarget.value);
28937
+ setControlSelected(nextSelected);
28938
+ },
28939
+ onClick: stopControlPointerEvent,
28940
+ onPointerDown: stopControlPointerEvent,
28250
28941
  style: {
28251
28942
  ...commonStyle,
28943
+ backgroundColor: SHEET_SURFACE,
28252
28944
  border: `${Math.max(1, zoomFactor)}px solid ${stroke}`,
28253
28945
  borderRadius: 2 * zoomFactor,
28254
28946
  boxSizing: "border-box",
28255
- justifyContent: "space-between",
28256
- padding: `0 ${6 * zoomFactor}px`
28947
+ padding: `0 ${4 * zoomFactor}px`
28257
28948
  },
28949
+ tabIndex: readOnly ? -1 : void 0,
28950
+ value: selected === void 0 ? "" : String(selected),
28258
28951
  children: [
28259
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }),
28260
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", style: { fontSize: fontSizePx * 0.85 }, children: "\u25BC" })
28952
+ selected === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: controlLabel }) : null,
28953
+ controlItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: index, children: item }, index))
28261
28954
  ]
28262
28955
  },
28263
28956
  `${pane}-${control.id}`
28264
28957
  );
28265
- } else if (control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
28958
+ }
28959
+ if (control.kind === "listbox") {
28960
+ const selected = Array.isArray(control.selected) ? control.selected : control.selected === void 0 ? [] : [control.selected];
28961
+ const allowsMultipleSelection = (control.selection ?? "single") !== "single";
28266
28962
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28267
- "div",
28963
+ "select",
28268
28964
  {
28965
+ "aria-disabled": readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28966
+ "aria-label": controlLabel || control.name || "List box form control",
28967
+ disabled: !readOnly && (control.controlIndex === void 0 || controlItems.length === 0),
28968
+ multiple: allowsMultipleSelection,
28969
+ onChange: (event) => {
28970
+ event.stopPropagation();
28971
+ const nextSelected = Array.from(event.currentTarget.selectedOptions, (option) => Number(option.value));
28972
+ setControlSelected(nextSelected);
28973
+ },
28974
+ onClick: stopControlPointerEvent,
28975
+ onPointerDown: stopControlPointerEvent,
28976
+ size: Math.max(2, Math.min(controlItems.length || 2, Math.floor(rect.height / Math.max(1, fontSizePx * 1.45)))),
28269
28977
  style: {
28270
28978
  ...commonStyle,
28979
+ alignItems: void 0,
28980
+ backgroundColor: SHEET_SURFACE,
28271
28981
  border: `${Math.max(1, zoomFactor)}px solid ${stroke}`,
28272
28982
  borderRadius: 2 * zoomFactor,
28273
28983
  boxSizing: "border-box",
28274
- padding: `0 ${6 * zoomFactor}px`
28984
+ display: "block",
28985
+ padding: 0
28275
28986
  },
28276
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel })
28987
+ tabIndex: readOnly ? -1 : void 0,
28988
+ value: allowsMultipleSelection ? selected.map(String) : selected[0] === void 0 ? "" : String(selected[0]),
28989
+ children: controlItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: index, children: item }, index))
28277
28990
  },
28278
28991
  `${pane}-${control.id}`
28279
28992
  );
28280
- } else {
28281
- content = null;
28282
28993
  }
28283
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
28994
+ if (control.kind === "scrollbar") {
28995
+ const min = control.min ?? 0;
28996
+ const max = Math.max(min, control.max ?? 100);
28997
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28998
+ "input",
28999
+ {
29000
+ "aria-disabled": readOnly || control.controlIndex === void 0,
29001
+ "aria-label": controlLabel || control.name || "Scrollbar form control",
29002
+ disabled: !readOnly && control.controlIndex === void 0,
29003
+ max,
29004
+ min,
29005
+ onChange: (event) => {
29006
+ event.stopPropagation();
29007
+ setControlValue(event.currentTarget.valueAsNumber);
29008
+ },
29009
+ onClick: stopControlPointerEvent,
29010
+ onPointerDown: stopControlPointerEvent,
29011
+ step: Math.max(1, control.increment ?? 1),
29012
+ style: {
29013
+ ...commonStyle,
29014
+ accentColor: paletteIsDark(palette) ? "#60a5fa" : "#2563eb",
29015
+ boxSizing: "border-box",
29016
+ padding: 0,
29017
+ writingMode: control.horizontal === false ? "vertical-lr" : void 0
29018
+ },
29019
+ tabIndex: readOnly ? -1 : void 0,
29020
+ type: "range",
29021
+ value: Math.max(min, Math.min(max, control.value ?? min))
29022
+ },
29023
+ `${pane}-${control.id}`
29024
+ );
29025
+ }
29026
+ if (control.kind === "spinner") {
29027
+ const min = control.min ?? 0;
29028
+ const max = Math.max(min, control.max ?? 100);
29029
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
29030
+ "input",
29031
+ {
29032
+ "aria-disabled": readOnly || control.controlIndex === void 0,
29033
+ "aria-label": controlLabel || control.name || "Spinner form control",
29034
+ disabled: !readOnly && control.controlIndex === void 0,
29035
+ max,
29036
+ min,
29037
+ onChange: (event) => {
29038
+ event.stopPropagation();
29039
+ setControlValue(event.currentTarget.valueAsNumber);
29040
+ },
29041
+ onClick: stopControlPointerEvent,
29042
+ onPointerDown: stopControlPointerEvent,
29043
+ step: Math.max(1, control.increment ?? 1),
29044
+ style: {
29045
+ ...commonStyle,
29046
+ backgroundColor: SHEET_SURFACE,
29047
+ border: `${Math.max(1, zoomFactor)}px solid ${stroke}`,
29048
+ borderRadius: 2 * zoomFactor,
29049
+ boxSizing: "border-box",
29050
+ padding: `0 ${4 * zoomFactor}px`
29051
+ },
29052
+ tabIndex: readOnly ? -1 : void 0,
29053
+ type: "number",
29054
+ value: Math.max(min, Math.min(max, control.value ?? min))
29055
+ },
29056
+ `${pane}-${control.id}`
29057
+ );
29058
+ }
29059
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28284
29060
  "div",
28285
29061
  {
28286
29062
  style: {
28287
29063
  ...commonStyle,
29064
+ border: control.kind === "editbox" || control.kind === "unknown" ? `${Math.max(1, zoomFactor)}px solid ${stroke}` : void 0,
29065
+ borderRadius: 2 * zoomFactor,
29066
+ boxSizing: "border-box",
28288
29067
  padding: `0 ${Math.max(1, zoomFactor)}px`
28289
29068
  },
28290
- children: [
28291
- content,
28292
- controlLabel ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
28293
- "span",
28294
- {
28295
- style: {
28296
- overflow: "hidden",
28297
- textOverflow: "ellipsis",
28298
- whiteSpace: "nowrap"
28299
- },
28300
- children: controlLabel
28301
- }
28302
- ) : null
28303
- ]
29069
+ children: controlLabel ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }) : null
28304
29070
  },
28305
29071
  `${pane}-${control.id}`
28306
29072
  );
@@ -28587,7 +29353,7 @@ function XlsxGrid({
28587
29353
  };
28588
29354
  const drawingViewportCacheSignature = `${Math.floor(drawingViewport.left / CANVAS_VIEWPORT_OVERSCAN_PX)}:${Math.floor(drawingViewport.top / CANVAS_VIEWPORT_OVERSCAN_PX)}:${drawingViewport.width}:${drawingViewport.height}`;
28589
29355
  const previousPaneDrawingNodes = paneDrawingNodesCacheRef.current;
28590
- 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;
28591
29357
  const paneDrawingNodes = canReusePaneDrawingNodes ? previousPaneDrawingNodes.value : !showImages ? {
28592
29358
  corner: null,
28593
29359
  left: null,
@@ -28623,12 +29389,16 @@ function XlsxGrid({
28623
29389
  paneDrawingNodesCacheRef.current = {
28624
29390
  chartRects,
28625
29391
  drawingViewportSignature: drawingViewportCacheSignature,
29392
+ formControlItemsById,
28626
29393
  formControlRects: domFormControlRects,
28627
29394
  imageRects: domImageRects,
28628
29395
  isChartsLoading,
29396
+ onFormControlAction,
29397
+ onFormControlChange,
28629
29398
  palette,
28630
29399
  readOnly,
28631
29400
  renderChartLoading,
29401
+ renderFormControl,
28632
29402
  renderImage,
28633
29403
  renderImageSelection,
28634
29404
  selectedChartElement,
@@ -28637,6 +29407,7 @@ function XlsxGrid({
28637
29407
  selectionStroke,
28638
29408
  shapeRects: domShapeRects,
28639
29409
  showImages,
29410
+ updateFormControl,
28640
29411
  value: paneDrawingNodes
28641
29412
  };
28642
29413
  }
@@ -29863,7 +30634,10 @@ function XlsxViewerInner({
29863
30634
  isDark = false,
29864
30635
  loadingComponent,
29865
30636
  loadingState,
30637
+ onFormControlAction,
30638
+ onFormControlChange,
29866
30639
  renderChartLoading,
30640
+ renderFormControl,
29867
30641
  renderImage,
29868
30642
  renderImageSelection,
29869
30643
  renderScroller,
@@ -29925,8 +30699,11 @@ function XlsxViewerInner({
29925
30699
  getCellStyle,
29926
30700
  loadingComponent,
29927
30701
  loadingState,
30702
+ onFormControlAction,
30703
+ onFormControlChange,
29928
30704
  palette,
29929
30705
  renderChartLoading,
30706
+ renderFormControl,
29930
30707
  renderImage,
29931
30708
  renderImageSelection,
29932
30709
  renderScroller,
@@ -30031,6 +30808,7 @@ function useXlsxViewerZoom() {
30031
30808
  }
30032
30809
  function useXlsxViewerEditing() {
30033
30810
  const {
30811
+ addFormControl,
30034
30812
  addSheet,
30035
30813
  canRedo,
30036
30814
  canUndo,
@@ -30038,14 +30816,18 @@ function useXlsxViewerEditing() {
30038
30816
  copySelectionToClipboard,
30039
30817
  defineNamedRange,
30040
30818
  fillSelection,
30819
+ formControls,
30041
30820
  getClipboardData,
30042
30821
  getCellDisplayValue: getCellDisplayValue2,
30043
30822
  getCellFormula,
30823
+ getFormControlItems,
30824
+ getSheetFormControls,
30044
30825
  mergeSelection,
30045
30826
  pasteFromClipboard,
30046
30827
  pasteStructuredClipboardData,
30047
30828
  pasteText,
30048
30829
  removeActiveSheet,
30830
+ removeFormControl,
30049
30831
  readOnly,
30050
30832
  redo,
30051
30833
  selectedCellFormula,
@@ -30062,10 +30844,12 @@ function useXlsxViewerEditing() {
30062
30844
  setSelectedCellStyle,
30063
30845
  setSelectedCellValue,
30064
30846
  undo,
30065
- unmergeSelection
30847
+ unmergeSelection,
30848
+ updateFormControl
30066
30849
  } = useXlsxViewer();
30067
30850
  return React4.useMemo(
30068
30851
  () => ({
30852
+ addFormControl,
30069
30853
  addSheet,
30070
30854
  canRedo,
30071
30855
  canUndo,
@@ -30073,14 +30857,18 @@ function useXlsxViewerEditing() {
30073
30857
  copySelectionToClipboard,
30074
30858
  defineNamedRange,
30075
30859
  fillSelection,
30860
+ formControls,
30076
30861
  getClipboardData,
30077
30862
  getCellDisplayValue: getCellDisplayValue2,
30078
30863
  getCellFormula,
30864
+ getFormControlItems,
30865
+ getSheetFormControls,
30079
30866
  mergeSelection,
30080
30867
  pasteFromClipboard,
30081
30868
  pasteStructuredClipboardData,
30082
30869
  pasteText,
30083
30870
  removeActiveSheet,
30871
+ removeFormControl,
30084
30872
  readOnly,
30085
30873
  redo,
30086
30874
  selectedCellFormula,
@@ -30097,9 +30885,11 @@ function useXlsxViewerEditing() {
30097
30885
  setSelectedCellStyle,
30098
30886
  setSelectedCellValue,
30099
30887
  undo,
30100
- unmergeSelection
30888
+ unmergeSelection,
30889
+ updateFormControl
30101
30890
  }),
30102
30891
  [
30892
+ addFormControl,
30103
30893
  addSheet,
30104
30894
  canRedo,
30105
30895
  canUndo,
@@ -30107,14 +30897,18 @@ function useXlsxViewerEditing() {
30107
30897
  copySelectionToClipboard,
30108
30898
  defineNamedRange,
30109
30899
  fillSelection,
30900
+ formControls,
30110
30901
  getClipboardData,
30111
30902
  getCellDisplayValue2,
30112
30903
  getCellFormula,
30904
+ getFormControlItems,
30905
+ getSheetFormControls,
30113
30906
  mergeSelection,
30114
30907
  pasteFromClipboard,
30115
30908
  pasteStructuredClipboardData,
30116
30909
  pasteText,
30117
30910
  removeActiveSheet,
30911
+ removeFormControl,
30118
30912
  readOnly,
30119
30913
  redo,
30120
30914
  selectedCellFormula,
@@ -30131,7 +30925,8 @@ function useXlsxViewerEditing() {
30131
30925
  setSelectedCellStyle,
30132
30926
  setSelectedCellValue,
30133
30927
  undo,
30134
- unmergeSelection
30928
+ unmergeSelection,
30929
+ updateFormControl
30135
30930
  ]
30136
30931
  );
30137
30932
  }
@@ -30329,20 +31124,87 @@ function useXlsxViewerCharts() {
30329
31124
  );
30330
31125
  }
30331
31126
  function useXlsxViewerThumbnails(options = {}) {
30332
- const { getSheetFormControls, getSheetImages, getSheetShapes, workbook, sheets } = useXlsxViewer();
31127
+ const {
31128
+ getRowsBatchAsync,
31129
+ getSheetFormControls,
31130
+ getSheetImages,
31131
+ getSheetShapes,
31132
+ workbook,
31133
+ sheets
31134
+ } = useXlsxViewer();
30333
31135
  const { isDark } = React4.useContext(ViewerAppearanceContext);
30334
31136
  const palette = useViewerPalette(isDark);
30335
31137
  const includeHeaders = options.includeHeaders ?? true;
30336
31138
  const resolution = options.resolution;
30337
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
+ );
30338
31144
  const isMountedRef = React4.useRef(false);
30339
31145
  const [thumbnailImageLoadVersion, setThumbnailImageLoadVersion] = React4.useState(0);
31146
+ const [workerThumbnailRowBatchVersion, setWorkerThumbnailRowBatchVersion] = React4.useState(0);
30340
31147
  React4.useEffect(() => {
30341
31148
  isMountedRef.current = true;
30342
31149
  return () => {
30343
31150
  isMountedRef.current = false;
30344
31151
  };
30345
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]);
30346
31208
  const getThumbnailImage = React4.useCallback((image) => {
30347
31209
  if (typeof Image === "undefined") {
30348
31210
  return null;
@@ -30568,6 +31430,10 @@ function useXlsxViewerThumbnails(options = {}) {
30568
31430
  if (!canvas) {
30569
31431
  return false;
30570
31432
  }
31433
+ const workerRowBatch = worksheet ? null : getWorkerThumbnailRowBatch(sheet, sourceRange.start.row, sourceRange.end.row);
31434
+ if (!worksheet && !workerRowBatch) {
31435
+ return false;
31436
+ }
30571
31437
  const context = canvas.getContext("2d", { alpha: false });
30572
31438
  if (!context) {
30573
31439
  return false;
@@ -30604,9 +31470,6 @@ function useXlsxViewerThumbnails(options = {}) {
30604
31470
  context.fillRect(0, headerHeight, rowHeaderWidth, Math.max(1, rowAxis.totalSize));
30605
31471
  context.fillRect(0, 0, rowHeaderWidth, headerHeight);
30606
31472
  }
30607
- if (!worksheet) {
30608
- return true;
30609
- }
30610
31473
  const conditionalFormatMetricsCache = /* @__PURE__ */ new Map();
30611
31474
  const cellRenderCache = /* @__PURE__ */ new Map();
30612
31475
  const getCellData = (row, col) => {
@@ -30615,7 +31478,9 @@ function useXlsxViewerThumbnails(options = {}) {
30615
31478
  if (cached) {
30616
31479
  return cached;
30617
31480
  }
30618
- 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)) {
30619
31484
  const mergedSecondaryData = {
30620
31485
  isMergedSecondary: true,
30621
31486
  style: {},
@@ -30624,53 +31489,34 @@ function useXlsxViewerThumbnails(options = {}) {
30624
31489
  cellRenderCache.set(cacheKey, mergedSecondaryData);
30625
31490
  return mergedSecondaryData;
30626
31491
  }
30627
- const merge = worksheet.getMergeSpan(row, col);
31492
+ const merge = worksheet ? worksheet.getMergeSpan(row, col) : batchedCell?.mergeSpan;
30628
31493
  const inheritedStyle = resolveInheritedCellStyle2(sheet, row, col);
30629
- const worksheetStyle = worksheet.getCellStyleAt(row, col) ?? null;
31494
+ const worksheetStyle = worksheet ? worksheet.getCellStyleAt(row, col) ?? null : batchedCell?.style ?? null;
30630
31495
  const mergedStyle = mergeResolvedCellStyle(inheritedStyle, worksheetStyle, { replaceXfSubtrees: true });
30631
31496
  const alignment = mergedStyle?.alignment;
30632
31497
  const sparkline = sparklineByCell.get(cacheKey) ?? null;
31498
+ const getNumericValue = (targetRow, targetCol) => worksheet ? getCellNumericValue(worksheet, targetRow, targetCol) : getBatchedCellNumericValue(workerRowBatch?.cells.get(`${targetRow}:${targetCol}`));
30633
31499
  const sparklineValues = sparkline ? sparkline.range.start.row === sparkline.range.end.row ? Array.from(
30634
31500
  { length: Math.abs(sparkline.range.end.col - sparkline.range.start.col) + 1 },
30635
- (_, index) => getCellNumericValue(
30636
- worksheet,
31501
+ (_, index) => getNumericValue(
30637
31502
  sparkline.range.start.row,
30638
31503
  Math.min(sparkline.range.start.col, sparkline.range.end.col) + index
30639
31504
  )
30640
31505
  ) : Array.from(
30641
31506
  { length: Math.abs(sparkline.range.end.row - sparkline.range.start.row) + 1 },
30642
- (_, index) => getCellNumericValue(
30643
- worksheet,
31507
+ (_, index) => getNumericValue(
30644
31508
  Math.min(sparkline.range.start.row, sparkline.range.end.row) + index,
30645
31509
  sparkline.range.start.col
30646
31510
  )
30647
31511
  ) : null;
30648
- const checkboxState = mergedStyle?.cellControl ? getCellBooleanValue(worksheet, row, col) : null;
31512
+ const checkboxState = mergedStyle?.cellControl ? worksheet ? getCellBooleanValue(worksheet, row, col) : getBatchedCellBooleanValue(batchedCell) : null;
30649
31513
  const nextData = {
30650
31514
  canvas: void 0,
30651
31515
  checkboxState,
30652
31516
  colSpan: merge?.colSpan,
30653
- conditionalColorScale: resolveConditionalColorScaleForCell(
30654
- row,
30655
- col,
30656
- worksheet,
30657
- sheet,
30658
- conditionalFormatMetricsCache
30659
- ),
30660
- conditionalDataBar: resolveConditionalDataBarForCell(
30661
- row,
30662
- col,
30663
- worksheet,
30664
- sheet,
30665
- conditionalFormatMetricsCache
30666
- ),
30667
- conditionalIcon: resolveConditionalIconForCell(
30668
- row,
30669
- col,
30670
- worksheet,
30671
- sheet,
30672
- conditionalFormatMetricsCache
30673
- ),
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,
30674
31520
  isMergedSecondary: false,
30675
31521
  rowSpan: merge?.rowSpan,
30676
31522
  sparkline: sparkline && sparklineValues ? { config: sparkline, values: sparklineValues } : null,
@@ -30678,7 +31524,7 @@ function useXlsxViewerThumbnails(options = {}) {
30678
31524
  showGridLines
30679
31525
  }),
30680
31526
  textRotationDeg: resolveSpreadsheetTextRotation(alignment?.textRotation),
30681
- value: sparkline ? "" : checkboxState !== null ? "" : getCellDisplayValue(worksheet, row, col, sheet)
31527
+ value: sparkline ? "" : checkboxState !== null ? "" : worksheet ? getCellDisplayValue(worksheet, row, col, sheet) : batchedCell?.value ?? ""
30682
31528
  };
30683
31529
  nextData.canvas = buildCanvasCellStyleCache(nextData.style);
30684
31530
  cellRenderCache.set(cacheKey, nextData);
@@ -30952,12 +31798,14 @@ function useXlsxViewerThumbnails(options = {}) {
30952
31798
  getSheetFormControls,
30953
31799
  getSheetImages,
30954
31800
  getSheetShapes,
31801
+ getWorkerThumbnailRowBatch,
30955
31802
  getThumbnailImage,
30956
31803
  includeHeaders,
30957
31804
  palette,
30958
31805
  resolution,
30959
31806
  sheets,
30960
31807
  thumbnailImageLoadVersion,
31808
+ workerThumbnailRowBatchVersion,
30961
31809
  workbook
30962
31810
  ]);
30963
31811
  const paintThumbnail = React4.useCallback(