@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.js CHANGED
@@ -3592,7 +3592,6 @@ var SPREADSHEET_NS = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"
3592
3592
  var CONTENT_TYPES_NS = "http://schemas.openxmlformats.org/package/2006/content-types";
3593
3593
  var DRAWING_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing";
3594
3594
  var VML_DRAWING_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing";
3595
- var CTRL_PROP_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp";
3596
3595
  var IMAGE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
3597
3596
  var DRAWING_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.drawing+xml";
3598
3597
  var EMU_PER_PIXEL2 = 9525;
@@ -5035,6 +5034,124 @@ function parseSpreadsheetBooleanNode(node) {
5035
5034
  }
5036
5035
  return parseSpreadsheetBooleanValue(node.getAttribute("val") ?? node.textContent);
5037
5036
  }
5037
+ function mapDukeFormControlAnchor(anchor) {
5038
+ return {
5039
+ from: {
5040
+ col: anchor.fromCol,
5041
+ colOffsetEmu: anchor.fromColOffset,
5042
+ row: anchor.fromRow,
5043
+ rowOffsetEmu: anchor.fromRowOffset
5044
+ },
5045
+ kind: "two-cell",
5046
+ to: {
5047
+ col: anchor.toCol,
5048
+ colOffsetEmu: anchor.toColOffset,
5049
+ row: anchor.toRow,
5050
+ rowOffsetEmu: anchor.toRowOffset
5051
+ }
5052
+ };
5053
+ }
5054
+ function mapDukeFormControlKind(kind) {
5055
+ switch (kind.kind) {
5056
+ case "button":
5057
+ return { caption: kind.caption, kind: "button", label: normalizeControlLabel(kind.caption) };
5058
+ case "checkbox":
5059
+ return {
5060
+ caption: kind.caption,
5061
+ checked: kind.state === "checked",
5062
+ kind: "checkbox",
5063
+ label: normalizeControlLabel(kind.caption),
5064
+ linkedCell: kind.cellLink,
5065
+ no3D: kind.no3D,
5066
+ state: kind.state
5067
+ };
5068
+ case "optionButton":
5069
+ return {
5070
+ caption: kind.caption,
5071
+ checked: kind.state === "checked",
5072
+ firstInGroup: kind.firstInGroup,
5073
+ kind: "radio",
5074
+ label: normalizeControlLabel(kind.caption),
5075
+ linkedCell: kind.cellLink,
5076
+ no3D: kind.no3D,
5077
+ state: kind.state
5078
+ };
5079
+ case "label":
5080
+ return { caption: kind.caption, kind: "label", label: normalizeControlLabel(kind.caption) };
5081
+ case "groupBox":
5082
+ return {
5083
+ caption: kind.caption,
5084
+ kind: "group-box",
5085
+ label: normalizeControlLabel(kind.caption),
5086
+ no3D: kind.no3D
5087
+ };
5088
+ case "listBox":
5089
+ return {
5090
+ inputRange: kind.inputRange,
5091
+ kind: "listbox",
5092
+ linkedCell: kind.cellLink,
5093
+ no3D: kind.no3D,
5094
+ selected: [...kind.selected],
5095
+ selection: kind.selection
5096
+ };
5097
+ case "dropdown":
5098
+ return {
5099
+ inputRange: kind.inputRange,
5100
+ kind: "dropdown",
5101
+ lines: kind.lines,
5102
+ linkedCell: kind.cellLink,
5103
+ no3D: kind.no3D,
5104
+ selected: kind.selected
5105
+ };
5106
+ case "scrollbar":
5107
+ return {
5108
+ horizontal: kind.horizontal,
5109
+ increment: kind.increment,
5110
+ kind: "scrollbar",
5111
+ linkedCell: kind.cellLink,
5112
+ max: kind.max,
5113
+ min: kind.min,
5114
+ page: kind.page,
5115
+ value: kind.value
5116
+ };
5117
+ case "spinner":
5118
+ return {
5119
+ increment: kind.increment,
5120
+ kind: "spinner",
5121
+ linkedCell: kind.cellLink,
5122
+ max: kind.max,
5123
+ min: kind.min,
5124
+ value: kind.value
5125
+ };
5126
+ }
5127
+ }
5128
+ function mapDukeFormControl(control, controlIndex, workbookSheetIndex) {
5129
+ return {
5130
+ anchor: mapDukeFormControlAnchor(control.anchor),
5131
+ controlIndex,
5132
+ dukeAnchor: { ...control.anchor },
5133
+ editAs: control.anchor.editAs,
5134
+ hidden: false,
5135
+ id: `form-control-${workbookSheetIndex}-${controlIndex}`,
5136
+ locked: control.locked,
5137
+ name: control.name,
5138
+ printable: control.printable,
5139
+ sheetIndex: workbookSheetIndex,
5140
+ workbookSheetIndex,
5141
+ zIndex: controlIndex + 1,
5142
+ ...mapDukeFormControlKind(control.kind)
5143
+ };
5144
+ }
5145
+ function collectWorkbookFormControls(workbook) {
5146
+ return Array.from({ length: workbook.sheetCount }, (_, workbookSheetIndex) => {
5147
+ try {
5148
+ const controls = workbook.getSheet(workbookSheetIndex).formControls;
5149
+ return Array.isArray(controls) ? controls.map((control, controlIndex) => mapDukeFormControl(control, controlIndex, workbookSheetIndex)) : [];
5150
+ } catch {
5151
+ return [];
5152
+ }
5153
+ });
5154
+ }
5038
5155
  function parseFormControlKind(rawType) {
5039
5156
  const normalized = (rawType ?? "").trim().toLowerCase();
5040
5157
  switch (normalized) {
@@ -5800,27 +5917,10 @@ function parseSheetFormControlNodes(archive, sheetPath) {
5800
5917
  }
5801
5918
  return getLocalElements(sheetDocument, "control").map((controlNode) => ({
5802
5919
  anchor: parseAnchor(getFirstDescendant(controlNode, "anchor") ?? controlNode),
5803
- controlRelationshipId: getRelationshipId(controlNode),
5804
5920
  name: controlNode.getAttribute("name") ?? void 0,
5805
5921
  shapeId: parseFormControlShapeId(controlNode.getAttribute("shapeId"))
5806
5922
  }));
5807
5923
  }
5808
- function parseCtrlPropDocument(archive, ctrlPropPath) {
5809
- const xml = readArchiveText2(archive, ctrlPropPath);
5810
- if (!xml) {
5811
- return null;
5812
- }
5813
- const document2 = parseXml2(xml);
5814
- const root = document2?.documentElement;
5815
- if (!root) {
5816
- return null;
5817
- }
5818
- return {
5819
- checked: parseSpreadsheetBooleanValue(root.getAttribute("checked")),
5820
- linkedCell: root.getAttribute("fmlaLink") ?? void 0,
5821
- objectType: root.getAttribute("objectType") ?? void 0
5822
- };
5823
- }
5824
5924
  function parseVmlFormControls(archive, vmlDrawingPath) {
5825
5925
  const xml = readArchiveText2(archive, vmlDrawingPath);
5826
5926
  if (!xml) {
@@ -5878,21 +5978,18 @@ function parseSheetFormControls(archive, sheetPath, sheetRelationships, workbook
5878
5978
  if (!controlNode.anchor) {
5879
5979
  return;
5880
5980
  }
5881
- const ctrlPropRelationship = controlNode.controlRelationshipId ? sheetRelationships.get(controlNode.controlRelationshipId) ?? null : null;
5882
- const ctrlPropPath = ctrlPropRelationship?.type === CTRL_PROP_REL_TYPE ? ctrlPropRelationship.target : null;
5883
- const ctrlProp = ctrlPropPath ? parseCtrlPropDocument(archive, ctrlPropPath) : null;
5884
5981
  const vmlControl = controlNode.shapeId !== null ? vmlControlsByShapeId.get(controlNode.shapeId) ?? null : null;
5885
- const kind = parseFormControlKind(ctrlProp?.objectType ?? vmlControl?.objectType);
5982
+ const kind = parseFormControlKind(vmlControl?.objectType);
5886
5983
  parsedControls.push({
5887
5984
  anchor: controlNode.anchor,
5888
- checked: ctrlProp?.checked ?? vmlControl?.checked,
5985
+ checked: vmlControl?.checked,
5889
5986
  fontFamily: vmlControl?.fontFamily,
5890
5987
  fontSizePt: vmlControl?.fontSizePt,
5891
5988
  hidden: vmlControl?.hidden ?? false,
5892
5989
  id: `form-control-${workbookSheetIndex}-${index}`,
5893
5990
  kind,
5894
5991
  label: vmlControl?.label,
5895
- linkedCell: ctrlProp?.linkedCell ?? vmlControl?.linkedCell,
5992
+ linkedCell: vmlControl?.linkedCell,
5896
5993
  name: controlNode.name,
5897
5994
  sheetIndex: workbookSheetIndex,
5898
5995
  textAlign: vmlControl?.textAlign,
@@ -5978,6 +6075,92 @@ function enrichFormControlsWithHiddenShapes(formControls, shapes, sheetState) {
5978
6075
  };
5979
6076
  });
5980
6077
  }
6078
+ function getFormControlAnchorStart(anchor) {
6079
+ return anchor.kind === "absolute" ? null : { col: anchor.from.col, row: anchor.from.row };
6080
+ }
6081
+ function scoreParsedFormControlMatch(dukeControl, parsedControl, parsedIndex) {
6082
+ if (dukeControl.kind !== parsedControl.kind && parsedControl.kind !== "unknown") {
6083
+ return Number.NEGATIVE_INFINITY;
6084
+ }
6085
+ let score = 10;
6086
+ const dukeName = normalizeControlLabel(dukeControl.name)?.toLowerCase();
6087
+ const parsedName = normalizeControlLabel(parsedControl.name)?.toLowerCase();
6088
+ if (dukeName && parsedName) {
6089
+ if (dukeName !== parsedName) {
6090
+ return Number.NEGATIVE_INFINITY;
6091
+ }
6092
+ score += 100;
6093
+ }
6094
+ const dukeStart = getFormControlAnchorStart(dukeControl.anchor);
6095
+ const parsedStart = getFormControlAnchorStart(parsedControl.anchor);
6096
+ if (dukeStart && parsedStart && dukeStart.col === parsedStart.col && dukeStart.row === parsedStart.row) {
6097
+ score += 40;
6098
+ }
6099
+ if (dukeControl.controlIndex === parsedIndex) {
6100
+ score += 5;
6101
+ }
6102
+ return score;
6103
+ }
6104
+ function areDukeFormControlAnchorsEqual(left, right) {
6105
+ if (!left || !right) {
6106
+ return true;
6107
+ }
6108
+ 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;
6109
+ }
6110
+ function mergeDukeAndParsedFormControls(dukeControls, parsedControls, keepUnmatchedParsedControl = () => true) {
6111
+ const usedParsedIndexes = /* @__PURE__ */ new Set();
6112
+ const mergedControls = dukeControls.map((dukeControl) => {
6113
+ let bestIndex = -1;
6114
+ let bestScore = Number.NEGATIVE_INFINITY;
6115
+ parsedControls.forEach((parsedControl2, parsedIndex) => {
6116
+ if (usedParsedIndexes.has(parsedIndex)) {
6117
+ return;
6118
+ }
6119
+ const score = scoreParsedFormControlMatch(dukeControl, parsedControl2, parsedIndex);
6120
+ if (score > bestScore) {
6121
+ bestIndex = parsedIndex;
6122
+ bestScore = score;
6123
+ }
6124
+ });
6125
+ const parsedControl = bestIndex >= 0 && bestScore >= 10 ? parsedControls[bestIndex] : null;
6126
+ if (!parsedControl) {
6127
+ return dukeControl;
6128
+ }
6129
+ usedParsedIndexes.add(bestIndex);
6130
+ const dukeAnchorChanged = !areDukeFormControlAnchorsEqual(
6131
+ parsedControl.dukeAnchor,
6132
+ dukeControl.dukeAnchor
6133
+ );
6134
+ return {
6135
+ ...parsedControl,
6136
+ ...dukeControl,
6137
+ anchor: dukeAnchorChanged ? dukeControl.anchor : parsedControl.anchor,
6138
+ fontFamily: parsedControl.fontFamily,
6139
+ fontSizePt: parsedControl.fontSizePt,
6140
+ hidden: parsedControl.hidden,
6141
+ id: parsedControl.id,
6142
+ label: dukeControl.label ?? parsedControl.label,
6143
+ name: dukeControl.name ?? parsedControl.name,
6144
+ textAlign: parsedControl.textAlign,
6145
+ textColor: parsedControl.textColor,
6146
+ zIndex: parsedControl.zIndex
6147
+ };
6148
+ });
6149
+ parsedControls.forEach((parsedControl, parsedIndex) => {
6150
+ if (!usedParsedIndexes.has(parsedIndex) && keepUnmatchedParsedControl(parsedControl)) {
6151
+ mergedControls.push(parsedControl);
6152
+ }
6153
+ });
6154
+ return mergedControls.sort((left, right) => left.zIndex - right.zIndex);
6155
+ }
6156
+ function refreshWorkbookFormControls(workbook, currentControlsByWorkbookSheetIndex) {
6157
+ const dukeControlsByWorkbookSheetIndex = collectWorkbookFormControls(workbook);
6158
+ return dukeControlsByWorkbookSheetIndex.map((dukeControls, workbookSheetIndex) => mergeDukeAndParsedFormControls(
6159
+ dukeControls,
6160
+ currentControlsByWorkbookSheetIndex[workbookSheetIndex] ?? [],
6161
+ (control) => control.controlIndex === void 0
6162
+ ));
6163
+ }
5981
6164
  function revokeWorkbookImageAssets(assets) {
5982
6165
  if (!assets) {
5983
6166
  return;
@@ -6010,7 +6193,7 @@ function parseWorkbookStructureAssetsFromArchive(archive, options) {
6010
6193
  workbookSheets
6011
6194
  };
6012
6195
  }
6013
- function parseWorkbookImageAssets(bytes) {
6196
+ function parseWorkbookImageAssets(bytes, workbookOrFormControls) {
6014
6197
  const archive = unzipSync(bytes);
6015
6198
  const {
6016
6199
  contentTypes,
@@ -6029,6 +6212,7 @@ function parseWorkbookImageAssets(bytes) {
6029
6212
  const shapesByWorkbookSheetIndex = [];
6030
6213
  const sheetOrigins = [];
6031
6214
  const imageOriginsById = /* @__PURE__ */ new Map();
6215
+ const dukeFormControlsByWorkbookSheetIndex = Array.isArray(workbookOrFormControls) ? workbookOrFormControls : collectWorkbookFormControls(workbookOrFormControls);
6032
6216
  workbookSheets.forEach((sheet, workbookSheetIndex) => {
6033
6217
  const sheetRelationships = parseRelationships(archive, relsPathForDocument(sheet.path), sheet.path);
6034
6218
  const attachments = [];
@@ -6074,7 +6258,10 @@ function parseWorkbookImageAssets(bytes) {
6074
6258
  shapeList,
6075
6259
  sheetStatesByWorkbookSheetIndex[workbookSheetIndex] ?? null
6076
6260
  );
6077
- formControlsByWorkbookSheetIndex[workbookSheetIndex] = enrichedFormControlList;
6261
+ formControlsByWorkbookSheetIndex[workbookSheetIndex] = mergeDukeAndParsedFormControls(
6262
+ dukeFormControlsByWorkbookSheetIndex[workbookSheetIndex] ?? [],
6263
+ enrichedFormControlList
6264
+ );
6078
6265
  imagesByWorkbookSheetIndex[workbookSheetIndex] = imageList;
6079
6266
  shapesByWorkbookSheetIndex[workbookSheetIndex] = visibleShapeList;
6080
6267
  sheetOrigins[workbookSheetIndex] = attachments.length > 0 ? {
@@ -7148,6 +7335,144 @@ function parseA1RangeReference2(reference) {
7148
7335
  }
7149
7336
  return normalizeRange({ start, end });
7150
7337
  }
7338
+ function resolveWorkbookReference(workbook, defaultWorkbookSheetIndex, rawReference, resolvingNamedRange = false) {
7339
+ const reference = rawReference.trim().replace(/^=/, "");
7340
+ if (!reference) {
7341
+ return null;
7342
+ }
7343
+ let workbookSheetIndex = defaultWorkbookSheetIndex;
7344
+ let rangeReference = reference;
7345
+ const bangIndex = reference.lastIndexOf("!");
7346
+ if (bangIndex >= 0) {
7347
+ let sheetName = reference.slice(0, bangIndex).trim();
7348
+ rangeReference = reference.slice(bangIndex + 1).trim();
7349
+ if (sheetName.startsWith("'") && sheetName.endsWith("'")) {
7350
+ sheetName = sheetName.slice(1, -1).replace(/''/g, "'");
7351
+ }
7352
+ const resolvedSheetIndex = workbook.sheetIndex(sheetName);
7353
+ if (resolvedSheetIndex === void 0) {
7354
+ return null;
7355
+ }
7356
+ workbookSheetIndex = resolvedSheetIndex;
7357
+ } else if (!resolvingNamedRange) {
7358
+ const namedRange = workbook.getNamedRange(reference);
7359
+ if (namedRange) {
7360
+ return resolveWorkbookReference(workbook, defaultWorkbookSheetIndex, namedRange, true);
7361
+ }
7362
+ }
7363
+ const range = parseA1RangeReference2(rangeReference.replace(/\$/g, ""));
7364
+ if (!range || workbookSheetIndex < 0 || workbookSheetIndex >= workbook.sheetCount) {
7365
+ return null;
7366
+ }
7367
+ try {
7368
+ return {
7369
+ range,
7370
+ workbookSheetIndex,
7371
+ worksheet: workbook.getSheet(workbookSheetIndex)
7372
+ };
7373
+ } catch {
7374
+ return null;
7375
+ }
7376
+ }
7377
+ function formControlInputFromDukeControl(control) {
7378
+ return {
7379
+ anchor: { ...control.anchor },
7380
+ kind: { ...control.kind },
7381
+ locked: control.locked,
7382
+ name: control.name,
7383
+ printable: control.printable
7384
+ };
7385
+ }
7386
+ function getFormControlCellLink(kind) {
7387
+ return "cellLink" in kind ? kind.cellLink : void 0;
7388
+ }
7389
+ function anchorContainsControl(group, control) {
7390
+ const groupAnchor = group.anchor;
7391
+ const controlAnchor = control.anchor;
7392
+ return controlAnchor.fromCol >= groupAnchor.fromCol && controlAnchor.fromCol <= groupAnchor.toCol && controlAnchor.fromRow >= groupAnchor.fromRow && controlAnchor.fromRow <= groupAnchor.toRow;
7393
+ }
7394
+ function resolveOptionButtonGroupIndexes(controls, targetIndex) {
7395
+ const target = controls[targetIndex];
7396
+ if (!target || target.kind.kind !== "optionButton") {
7397
+ return [];
7398
+ }
7399
+ if (target.kind.cellLink) {
7400
+ const targetCellLink = target.kind.cellLink;
7401
+ return controls.flatMap((control, index) => control.kind.kind === "optionButton" && control.kind.cellLink === targetCellLink ? [index] : []);
7402
+ }
7403
+ const containingGroups = controls.map((control, index) => ({ control, index })).filter(({ control }) => control.kind.kind === "groupBox" && anchorContainsControl(control, target)).sort((left, right) => {
7404
+ const leftArea = (left.control.anchor.toCol - left.control.anchor.fromCol) * (left.control.anchor.toRow - left.control.anchor.fromRow);
7405
+ const rightArea = (right.control.anchor.toCol - right.control.anchor.fromCol) * (right.control.anchor.toRow - right.control.anchor.fromRow);
7406
+ return leftArea - rightArea;
7407
+ });
7408
+ const containingGroup = containingGroups[0]?.control;
7409
+ if (containingGroup) {
7410
+ return controls.flatMap((control, index) => control.kind.kind === "optionButton" && anchorContainsControl(containingGroup, control) ? [index] : []);
7411
+ }
7412
+ const optionIndexes = controls.flatMap((control, index) => control.kind.kind === "optionButton" ? [index] : []);
7413
+ const targetPosition = optionIndexes.indexOf(targetIndex);
7414
+ if (targetPosition < 0) {
7415
+ return [];
7416
+ }
7417
+ let startPosition = targetPosition;
7418
+ while (startPosition > 0) {
7419
+ const control = controls[optionIndexes[startPosition] ?? -1];
7420
+ if (control?.kind.kind === "optionButton" && control.kind.firstInGroup) {
7421
+ break;
7422
+ }
7423
+ startPosition -= 1;
7424
+ }
7425
+ let endPosition = targetPosition + 1;
7426
+ while (endPosition < optionIndexes.length) {
7427
+ const control = controls[optionIndexes[endPosition] ?? -1];
7428
+ if (control?.kind.kind === "optionButton" && control.kind.firstInGroup) {
7429
+ break;
7430
+ }
7431
+ endPosition += 1;
7432
+ }
7433
+ return optionIndexes.slice(startPosition, endPosition);
7434
+ }
7435
+ function resolveLinkedCellValue(controls, controlIndex) {
7436
+ const control = controls[controlIndex];
7437
+ if (!control) {
7438
+ return void 0;
7439
+ }
7440
+ switch (control.kind.kind) {
7441
+ case "checkbox":
7442
+ return control.kind.state === "mixed" ? "#N/A" : control.kind.state === "checked";
7443
+ case "optionButton": {
7444
+ const groupIndexes = resolveOptionButtonGroupIndexes(controls, controlIndex);
7445
+ const checkedIndex = groupIndexes.find((index) => controls[index]?.kind.kind === "optionButton" && controls[index]?.kind.state === "checked");
7446
+ const groupPosition = checkedIndex === void 0 ? -1 : groupIndexes.indexOf(checkedIndex);
7447
+ return groupPosition >= 0 ? groupPosition + 1 : "";
7448
+ }
7449
+ case "dropdown":
7450
+ return control.kind.selected === void 0 ? "" : control.kind.selected + 1;
7451
+ case "listBox":
7452
+ return control.kind.selection === "single" && control.kind.selected.length === 1 ? (control.kind.selected[0] ?? -1) + 1 : "";
7453
+ case "scrollbar":
7454
+ case "spinner":
7455
+ return control.kind.value;
7456
+ default:
7457
+ return void 0;
7458
+ }
7459
+ }
7460
+ function syncFormControlLinkedCell(workbook, workbookSheetIndex, controls, controlIndex) {
7461
+ const control = controls[controlIndex];
7462
+ if (!control) {
7463
+ return;
7464
+ }
7465
+ const cellLink = getFormControlCellLink(control.kind);
7466
+ const value = resolveLinkedCellValue(controls, controlIndex);
7467
+ if (!cellLink || value === void 0) {
7468
+ return;
7469
+ }
7470
+ const target = resolveWorkbookReference(workbook, workbookSheetIndex, cellLink);
7471
+ if (!target || target.range.start.row !== target.range.end.row || target.range.start.col !== target.range.end.col) {
7472
+ return;
7473
+ }
7474
+ target.worksheet.setCell(cellAddressToA1(target.range.start), value);
7475
+ }
7151
7476
  function parseWorksheetFreezePanes(worksheet) {
7152
7477
  const rawFreezePanes = worksheet.freezePanes;
7153
7478
  const row = typeof rawFreezePanes?.row === "number" && rawFreezePanes.row >= 0 ? rawFreezePanes.row : null;
@@ -7914,7 +8239,7 @@ function createBasicWorkbookAssets(workbook) {
7914
8239
  const objectUrls = [];
7915
8240
  return {
7916
8241
  archive: {},
7917
- formControlsByWorkbookSheetIndex: Array.from({ length: workbook.sheetCount }, () => []),
8242
+ formControlsByWorkbookSheetIndex: collectWorkbookFormControls(workbook),
7918
8243
  imageOriginsById: /* @__PURE__ */ new Map(),
7919
8244
  imagesByWorkbookSheetIndex: collectWorksheetApiImages(workbook, objectUrls),
7920
8245
  namedCellStyleByName: {},
@@ -7932,7 +8257,7 @@ function loadWorkbookImageAssets(bytes, workbook, skipXmlParsing = false) {
7932
8257
  if (shouldSkipXmlParsingForWorkbook(bytes, skipXmlParsing) || !isZipWorkbook(bytes)) {
7933
8258
  return createBasicWorkbookAssets(workbook);
7934
8259
  }
7935
- const parsedAssets = parseWorkbookImageAssets(bytes);
8260
+ const parsedAssets = parseWorkbookImageAssets(bytes, workbook);
7936
8261
  const apiImagesByWorkbookSheetIndex = collectWorksheetApiImages(workbook, parsedAssets.objectUrls);
7937
8262
  const imagesByWorkbookSheetIndex = Array.from(
7938
8263
  { length: Math.max(workbook.sheetCount, parsedAssets.imagesByWorkbookSheetIndex.length, apiImagesByWorkbookSheetIndex.length) },
@@ -8249,6 +8574,12 @@ function useXlsxViewerController(options) {
8249
8574
  };
8250
8575
  }, [skipXmlParsing]);
8251
8576
  const refreshWorkbookState = React.useCallback((targetWorkbook) => {
8577
+ const currentFormControls = imageAssetsRef.current?.formControlsByWorkbookSheetIndex ?? [];
8578
+ const nextFormControls = refreshWorkbookFormControls(targetWorkbook, currentFormControls);
8579
+ if (imageAssetsRef.current) {
8580
+ imageAssetsRef.current.formControlsByWorkbookSheetIndex = nextFormControls;
8581
+ }
8582
+ setFormControlsByWorkbookSheetIndex(nextFormControls);
8252
8583
  const nextSheets = buildSheetList(
8253
8584
  targetWorkbook,
8254
8585
  imageAssetsRef.current?.sheetStatesByWorkbookSheetIndex,
@@ -8370,12 +8701,24 @@ function useXlsxViewerController(options) {
8370
8701
  if (!effectiveSkipXmlParsing && hasIncompleteWorkerChartSnapshot(snapshot)) {
8371
8702
  throw new Error("Worker chart payload incomplete");
8372
8703
  }
8704
+ const workerImageAssets = effectiveSkipXmlParsing ? null : parseWorkbookImageAssets(
8705
+ new Uint8Array(buffer),
8706
+ snapshot.formControlsByWorkbookSheetIndex
8707
+ );
8708
+ if (!isCurrent || abortController.signal.aborted) {
8709
+ revokeWorkbookImageAssets(workerImageAssets);
8710
+ return;
8711
+ }
8712
+ setImageAssets(workerImageAssets);
8373
8713
  setWorkbook(null);
8374
8714
  setSheets(snapshot.sheets);
8375
8715
  setChartsByWorkbookSheetIndex(snapshot.chartsByWorkbookSheetIndex);
8376
8716
  setChartsheets(snapshot.chartsheets);
8377
8717
  setTabs(snapshot.tabs);
8378
8718
  chartAssetsRef.current = null;
8719
+ if (!workerImageAssets) {
8720
+ setFormControlsByWorkbookSheetIndex(snapshot.formControlsByWorkbookSheetIndex);
8721
+ }
8379
8722
  setWorkerTablesByWorkbookSheetIndex(snapshot.tablesByWorkbookSheetIndex);
8380
8723
  setShouldAutoCalculate(false);
8381
8724
  setIsWorkerBacked(true);
@@ -8595,6 +8938,7 @@ function useXlsxViewerController(options) {
8595
8938
  setChartsheets(snapshot.chartsheets);
8596
8939
  setTabs(snapshot.tabs);
8597
8940
  chartAssetsRef.current = null;
8941
+ setFormControlsByWorkbookSheetIndex(snapshot.formControlsByWorkbookSheetIndex);
8598
8942
  setWorkerTablesByWorkbookSheetIndex(snapshot.tablesByWorkbookSheetIndex);
8599
8943
  setShouldAutoCalculate(false);
8600
8944
  setIsWorkerBacked(true);
@@ -8716,6 +9060,20 @@ function useXlsxViewerController(options) {
8716
9060
  }
8717
9061
  return workbook.getSheet(activeSheet.workbookSheetIndex);
8718
9062
  }, [activeSheet, workbook]);
9063
+ const getFormControlWorksheet = React.useCallback((sheetIndex = activeSheetIndex) => {
9064
+ const targetSheet = sheets[sheetIndex];
9065
+ if (!workbook || !targetSheet) {
9066
+ return null;
9067
+ }
9068
+ try {
9069
+ return {
9070
+ workbookSheetIndex: targetSheet.workbookSheetIndex,
9071
+ worksheet: workbook.getSheet(targetSheet.workbookSheetIndex)
9072
+ };
9073
+ } catch {
9074
+ return null;
9075
+ }
9076
+ }, [activeSheetIndex, sheets, workbook]);
8719
9077
  const tables = React.useMemo(
8720
9078
  () => isWorkerBacked ? workerTablesByWorkbookSheetIndex[activeSheet?.workbookSheetIndex ?? -1] ?? [] : mapWorksheetTables(getActiveWorksheet()),
8721
9079
  [activeSheet?.workbookSheetIndex, getActiveWorksheet, isWorkerBacked, revision, workerTablesByWorkbookSheetIndex]
@@ -8855,6 +9213,33 @@ function useXlsxViewerController(options) {
8855
9213
  return (formControlsByWorkbookSheetIndex[targetSheet.workbookSheetIndex] ?? []).map(mapPublicFormControl);
8856
9214
  }, [activeSheetIndex, formControlsByWorkbookSheetIndex, mapPublicFormControl, sheets]);
8857
9215
  const formControls = React.useMemo(() => getSheetFormControls(activeSheetIndex), [activeSheetIndex, getSheetFormControls]);
9216
+ const getFormControlItems = React.useCallback((controlIndex, sheetIndex = activeSheetIndex) => {
9217
+ const target = getFormControlWorksheet(sheetIndex);
9218
+ if (!workbook || !target) {
9219
+ const targetSheet = sheets[sheetIndex];
9220
+ if (!targetSheet) {
9221
+ return [];
9222
+ }
9223
+ const control2 = (formControlsByWorkbookSheetIndex[targetSheet.workbookSheetIndex] ?? []).find((entry) => entry.controlIndex === controlIndex);
9224
+ return control2?.items?.slice() ?? [];
9225
+ }
9226
+ const control = target.worksheet.formControls[controlIndex];
9227
+ const inputRange = control?.kind.kind === "listBox" || control?.kind.kind === "dropdown" ? control.kind.inputRange : void 0;
9228
+ if (!inputRange) {
9229
+ return [];
9230
+ }
9231
+ const source = resolveWorkbookReference(workbook, target.workbookSheetIndex, inputRange);
9232
+ if (!source) {
9233
+ return [];
9234
+ }
9235
+ const items = [];
9236
+ for (let row = source.range.start.row; row <= source.range.end.row; row += 1) {
9237
+ for (let col = source.range.start.col; col <= source.range.end.col; col += 1) {
9238
+ items.push(source.worksheet.getFormattedValueAt(row, col));
9239
+ }
9240
+ }
9241
+ return items;
9242
+ }, [activeSheetIndex, formControlsByWorkbookSheetIndex, getFormControlWorksheet, sheets, workbook]);
8858
9243
  const getSheetShapes = React.useCallback((sheetIndex = activeSheetIndex) => {
8859
9244
  const targetSheet = sheets[sheetIndex];
8860
9245
  if (!targetSheet) {
@@ -9252,6 +9637,89 @@ function useXlsxViewerController(options) {
9252
9637
  redoStackRef.current = [];
9253
9638
  setHistoryRevision((current) => current + 1);
9254
9639
  }, [createHistoryEntry]);
9640
+ const addFormControl = React.useCallback((input, sheetIndex = activeSheetIndex) => {
9641
+ const target = getFormControlWorksheet(sheetIndex);
9642
+ if (readOnly || !workbook || !target) {
9643
+ return null;
9644
+ }
9645
+ recordHistoryBeforeMutation();
9646
+ const controlIndex = target.worksheet.addFormControl(input);
9647
+ const controls = target.worksheet.formControls;
9648
+ const addedControl = controls[controlIndex];
9649
+ if (addedControl?.kind.kind === "optionButton" && addedControl.kind.state === "checked") {
9650
+ const groupIndexes = resolveOptionButtonGroupIndexes(controls, controlIndex);
9651
+ for (const siblingIndex of groupIndexes) {
9652
+ if (siblingIndex === controlIndex) {
9653
+ continue;
9654
+ }
9655
+ const sibling = target.worksheet.formControls[siblingIndex];
9656
+ if (sibling?.kind.kind === "optionButton" && sibling.kind.state !== "unchecked") {
9657
+ target.worksheet.setFormControl(siblingIndex, {
9658
+ ...formControlInputFromDukeControl(sibling),
9659
+ kind: { ...sibling.kind, state: "unchecked" }
9660
+ });
9661
+ }
9662
+ }
9663
+ }
9664
+ syncFormControlLinkedCell(
9665
+ workbook,
9666
+ target.workbookSheetIndex,
9667
+ target.worksheet.formControls,
9668
+ controlIndex
9669
+ );
9670
+ maybeRecalculateWorkbook(workbook);
9671
+ refreshWorkbookState(workbook);
9672
+ return controlIndex;
9673
+ }, [activeSheetIndex, getFormControlWorksheet, maybeRecalculateWorkbook, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9674
+ const updateFormControl = React.useCallback((controlIndex, patch, sheetIndex = activeSheetIndex) => {
9675
+ const target = getFormControlWorksheet(sheetIndex);
9676
+ const currentControl = target?.worksheet.formControls[controlIndex];
9677
+ if (readOnly || !workbook || !target || !currentControl) {
9678
+ return false;
9679
+ }
9680
+ recordHistoryBeforeMutation();
9681
+ const nextInput = {
9682
+ ...formControlInputFromDukeControl(currentControl),
9683
+ ...patch,
9684
+ anchor: patch.anchor ? { ...patch.anchor } : { ...currentControl.anchor },
9685
+ kind: patch.kind ? { ...patch.kind } : { ...currentControl.kind }
9686
+ };
9687
+ if (nextInput.kind.kind === "optionButton" && nextInput.kind.state === "checked") {
9688
+ const groupIndexes = resolveOptionButtonGroupIndexes(target.worksheet.formControls, controlIndex);
9689
+ for (const siblingIndex of groupIndexes) {
9690
+ if (siblingIndex === controlIndex) {
9691
+ continue;
9692
+ }
9693
+ const sibling = target.worksheet.formControls[siblingIndex];
9694
+ if (sibling?.kind.kind === "optionButton" && sibling.kind.state !== "unchecked") {
9695
+ target.worksheet.setFormControl(siblingIndex, {
9696
+ ...formControlInputFromDukeControl(sibling),
9697
+ kind: { ...sibling.kind, state: "unchecked" }
9698
+ });
9699
+ }
9700
+ }
9701
+ }
9702
+ target.worksheet.setFormControl(controlIndex, nextInput);
9703
+ syncFormControlLinkedCell(
9704
+ workbook,
9705
+ target.workbookSheetIndex,
9706
+ target.worksheet.formControls,
9707
+ controlIndex
9708
+ );
9709
+ maybeRecalculateWorkbook(workbook);
9710
+ refreshWorkbookState(workbook);
9711
+ return true;
9712
+ }, [activeSheetIndex, getFormControlWorksheet, maybeRecalculateWorkbook, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9713
+ const removeFormControl = React.useCallback((controlIndex, sheetIndex = activeSheetIndex) => {
9714
+ const target = getFormControlWorksheet(sheetIndex);
9715
+ if (readOnly || !workbook || !target || !target.worksheet.formControls[controlIndex]) {
9716
+ return false;
9717
+ }
9718
+ recordHistoryBeforeMutation();
9719
+ target.worksheet.removeFormControl(controlIndex);
9720
+ refreshWorkbookState(workbook);
9721
+ return true;
9722
+ }, [activeSheetIndex, getFormControlWorksheet, readOnly, recordHistoryBeforeMutation, refreshWorkbookState, workbook]);
9255
9723
  const recordCellEditHistory = React.useCallback((cell, before, after) => {
9256
9724
  if (!activeSheet || isApplyingHistoryRef.current) {
9257
9725
  return;
@@ -10060,8 +10528,15 @@ function useXlsxViewerController(options) {
10060
10528
  }
10061
10529
  workbook.addSheet(candidate);
10062
10530
  sheetOriginsRef.current = [...sheetOriginsRef.current, null];
10531
+ setFormControlsByWorkbookSheetIndex((current) => [...current, []]);
10063
10532
  setImagesByWorkbookSheetIndex((current) => [...current, []]);
10064
10533
  setShapesByWorkbookSheetIndex((current) => [...current, []]);
10534
+ if (imageAssetsRef.current) {
10535
+ imageAssetsRef.current.formControlsByWorkbookSheetIndex = [
10536
+ ...imageAssetsRef.current.formControlsByWorkbookSheetIndex,
10537
+ []
10538
+ ];
10539
+ }
10065
10540
  const nextSheets = buildSheetList(
10066
10541
  workbook,
10067
10542
  imageAssetsRef.current?.sheetStatesByWorkbookSheetIndex,
@@ -10091,9 +10566,13 @@ function useXlsxViewerController(options) {
10091
10566
  recordHistoryBeforeMutation();
10092
10567
  workbook.removeSheet(activeSheet.workbookSheetIndex);
10093
10568
  sheetOriginsRef.current = sheetOriginsRef.current.filter((_, index) => index !== activeSheet.workbookSheetIndex);
10569
+ setFormControlsByWorkbookSheetIndex((current) => current.filter((_, index) => index !== activeSheet.workbookSheetIndex));
10094
10570
  setImagesByWorkbookSheetIndex((current) => current.filter((_, index) => index !== activeSheet.workbookSheetIndex));
10095
10571
  setShapesByWorkbookSheetIndex((current) => current.filter((_, index) => index !== activeSheet.workbookSheetIndex));
10096
10572
  if (imageAssetsRef.current) {
10573
+ imageAssetsRef.current.formControlsByWorkbookSheetIndex = imageAssetsRef.current.formControlsByWorkbookSheetIndex.filter(
10574
+ (_, index) => index !== activeSheet.workbookSheetIndex
10575
+ );
10097
10576
  imageAssetsRef.current.sheetStatesByWorkbookSheetIndex = imageAssetsRef.current.sheetStatesByWorkbookSheetIndex.filter(
10098
10577
  (_, index) => index !== activeSheet.workbookSheetIndex
10099
10578
  );
@@ -10394,6 +10873,7 @@ function useXlsxViewerController(options) {
10394
10873
  activeSheetIndex,
10395
10874
  activeTab,
10396
10875
  activeTabIndex,
10876
+ addFormControl,
10397
10877
  addSheet,
10398
10878
  canRedo,
10399
10879
  canDownload: Boolean(file ?? src),
@@ -10433,6 +10913,7 @@ function useXlsxViewerController(options) {
10433
10913
  getClipboardData,
10434
10914
  getCellDisplayValue: getCellDisplayValue2,
10435
10915
  getCellFormula,
10916
+ getFormControlItems,
10436
10917
  getCellSnapshotAsync: isWorkerBacked ? getCellSnapshotAsync : void 0,
10437
10918
  getActiveWorksheet,
10438
10919
  getRowsBatchAsync: isWorkerBacked ? getRowsBatchAsync : void 0,
@@ -10450,6 +10931,7 @@ function useXlsxViewerController(options) {
10450
10931
  pasteStructuredClipboardData,
10451
10932
  pasteText,
10452
10933
  removeActiveSheet,
10934
+ removeFormControl,
10453
10935
  readOnly,
10454
10936
  recalculate,
10455
10937
  redo,
@@ -10500,6 +10982,7 @@ function useXlsxViewerController(options) {
10500
10982
  undo,
10501
10983
  unmergeSelection,
10502
10984
  updateChart,
10985
+ updateFormControl,
10503
10986
  workbook,
10504
10987
  zoomIn,
10505
10988
  zoomOut,
@@ -10512,6 +10995,7 @@ function useXlsxViewerController(options) {
10512
10995
  activeSheetIndex,
10513
10996
  activeTab,
10514
10997
  activeTabIndex,
10998
+ addFormControl,
10515
10999
  addSheet,
10516
11000
  canLoadDeferred,
10517
11001
  canRedo,
@@ -10548,6 +11032,7 @@ function useXlsxViewerController(options) {
10548
11032
  getClipboardData,
10549
11033
  getCellDisplayValue2,
10550
11034
  getCellFormula,
11035
+ getFormControlItems,
10551
11036
  getCellSnapshotAsync,
10552
11037
  getActiveWorksheet,
10553
11038
  historyRevision,
@@ -10565,6 +11050,7 @@ function useXlsxViewerController(options) {
10565
11050
  pasteStructuredClipboardData,
10566
11051
  pasteText,
10567
11052
  removeActiveSheet,
11053
+ removeFormControl,
10568
11054
  readOnly,
10569
11055
  recalculate,
10570
11056
  redo,
@@ -10615,6 +11101,7 @@ function useXlsxViewerController(options) {
10615
11101
  undo,
10616
11102
  unmergeSelection,
10617
11103
  updateChart,
11104
+ updateFormControl,
10618
11105
  workbook,
10619
11106
  zoomIn,
10620
11107
  zoomOut,
@@ -20304,6 +20791,40 @@ function getCellBooleanValue(worksheet, row, col) {
20304
20791
  }
20305
20792
  return null;
20306
20793
  }
20794
+ function getBatchedCellNumericValue(cell) {
20795
+ if (!cell) {
20796
+ return null;
20797
+ }
20798
+ const text = cell.value.trim();
20799
+ if (text.length === 0) {
20800
+ return null;
20801
+ }
20802
+ if (text.toLowerCase() === "true") {
20803
+ return 1;
20804
+ }
20805
+ if (text.toLowerCase() === "false") {
20806
+ return 0;
20807
+ }
20808
+ const value = Number(text.replace(/,/g, ""));
20809
+ return Number.isFinite(value) ? value : null;
20810
+ }
20811
+ function getBatchedCellBooleanValue(cell) {
20812
+ if (!cell) {
20813
+ return null;
20814
+ }
20815
+ const text = cell.value.trim().toLowerCase();
20816
+ if (text.length === 0) {
20817
+ return null;
20818
+ }
20819
+ if (text === "true") {
20820
+ return true;
20821
+ }
20822
+ if (text === "false") {
20823
+ return false;
20824
+ }
20825
+ const value = Number(text);
20826
+ return Number.isFinite(value) ? value !== 0 : null;
20827
+ }
20307
20828
  function parseA1RangeReference3(reference) {
20308
20829
  const [startRef, endRef = startRef] = reference.split(":");
20309
20830
  const start = parseA1CellReference3(startRef ?? "");
@@ -21603,13 +22124,90 @@ function renderConditionalIcon(icon, scale = 1) {
21603
22124
  }
21604
22125
  );
21605
22126
  }
21606
- function renderCheckboxControl(checked, palette, scale = 1) {
22127
+ function isFormControlChecked(control) {
22128
+ return control.state ? control.state === "checked" : Boolean(control.checked);
22129
+ }
22130
+ function isFormControlMixed(control) {
22131
+ return control.state === "mixed";
22132
+ }
22133
+ function buildFormControlKindInput(control) {
22134
+ switch (control.kind) {
22135
+ case "button":
22136
+ return { caption: control.caption ?? control.label ?? "", kind: "button" };
22137
+ case "checkbox":
22138
+ return {
22139
+ caption: control.caption ?? control.label ?? "",
22140
+ cellLink: control.linkedCell,
22141
+ kind: "checkbox",
22142
+ no3D: control.no3D ?? false,
22143
+ state: control.state ?? (control.checked ? "checked" : "unchecked")
22144
+ };
22145
+ case "radio":
22146
+ return {
22147
+ caption: control.caption ?? control.label ?? "",
22148
+ cellLink: control.linkedCell,
22149
+ firstInGroup: control.firstInGroup,
22150
+ kind: "optionButton",
22151
+ no3D: control.no3D ?? false,
22152
+ state: control.state === "checked" ? "checked" : "unchecked"
22153
+ };
22154
+ case "label":
22155
+ return { caption: control.caption ?? control.label ?? "", kind: "label" };
22156
+ case "group-box":
22157
+ return {
22158
+ caption: control.caption ?? control.label ?? "",
22159
+ kind: "groupBox",
22160
+ no3D: control.no3D ?? false
22161
+ };
22162
+ case "listbox":
22163
+ return {
22164
+ cellLink: control.linkedCell,
22165
+ inputRange: control.inputRange,
22166
+ kind: "listBox",
22167
+ no3D: control.no3D ?? false,
22168
+ selected: Array.isArray(control.selected) ? control.selected : control.selected === void 0 ? [] : [control.selected],
22169
+ selection: control.selection ?? "single"
22170
+ };
22171
+ case "dropdown":
22172
+ return {
22173
+ cellLink: control.linkedCell,
22174
+ inputRange: control.inputRange,
22175
+ kind: "dropdown",
22176
+ lines: control.lines ?? 8,
22177
+ no3D: control.no3D ?? false,
22178
+ selected: typeof control.selected === "number" ? control.selected : void 0
22179
+ };
22180
+ case "scrollbar":
22181
+ return {
22182
+ cellLink: control.linkedCell,
22183
+ horizontal: control.horizontal ?? true,
22184
+ increment: control.increment ?? 1,
22185
+ kind: "scrollbar",
22186
+ max: control.max ?? 100,
22187
+ min: control.min ?? 0,
22188
+ page: control.page ?? 10,
22189
+ value: control.value ?? control.min ?? 0
22190
+ };
22191
+ case "spinner":
22192
+ return {
22193
+ cellLink: control.linkedCell,
22194
+ increment: control.increment ?? 1,
22195
+ kind: "spinner",
22196
+ max: control.max ?? 100,
22197
+ min: control.min ?? 0,
22198
+ value: control.value ?? control.min ?? 0
22199
+ };
22200
+ default:
22201
+ return null;
22202
+ }
22203
+ }
22204
+ function renderCheckboxControl(checked, palette, scale = 1, mixed = false) {
21607
22205
  const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
21608
- const fill = checked ? paletteIsDark(palette) ? "#60a5fa" : "#2563eb" : "transparent";
22206
+ const fill = checked || mixed ? paletteIsDark(palette) ? "#60a5fa" : "#2563eb" : "transparent";
21609
22207
  const check = paletteIsDark(palette) ? "#020617" : "#ffffff";
21610
22208
  return /* @__PURE__ */ jsxs3("svg", { "aria-hidden": "true", height: 14 * scale, style: { display: "block" }, viewBox: "0 0 16 16", width: 14 * scale, children: [
21611
22209
  /* @__PURE__ */ jsx3("rect", { fill, height: 11, rx: 2, ry: 2, stroke, strokeWidth: 1.2, width: 11, x: 2.5, y: 2.5 }),
21612
- checked ? /* @__PURE__ */ jsx3(
22210
+ mixed ? /* @__PURE__ */ jsx3("path", { d: "M5 8h6", fill: "none", stroke: check, strokeLinecap: "round", strokeWidth: 1.8 }) : checked ? /* @__PURE__ */ jsx3(
21613
22211
  "path",
21614
22212
  {
21615
22213
  d: "M5 8.1 7.1 10.2 11.3 5.8",
@@ -21779,6 +22377,8 @@ function drawStaticFormControl(context, control, rect, palette, zoomFactor, shee
21779
22377
  const textColor = control.textColor ?? "#000000";
21780
22378
  const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
21781
22379
  const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
22380
+ const controlChecked = isFormControlChecked(control);
22381
+ const controlMixed = isFormControlMixed(control);
21782
22382
  context.save();
21783
22383
  context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
21784
22384
  context.textBaseline = "middle";
@@ -21819,14 +22419,19 @@ function drawStaticFormControl(context, control, rect, palette, zoomFactor, shee
21819
22419
  const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
21820
22420
  if (control.kind === "checkbox") {
21821
22421
  context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
21822
- if (control.checked) {
22422
+ if (controlChecked || controlMixed) {
21823
22423
  context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
21824
22424
  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));
21825
22425
  context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
21826
22426
  context.beginPath();
21827
- context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
21828
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
21829
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
22427
+ if (controlMixed) {
22428
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.25, rect.top + rect.height / 2);
22429
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.75, rect.top + rect.height / 2);
22430
+ } else {
22431
+ context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
22432
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
22433
+ context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
22434
+ }
21830
22435
  context.stroke();
21831
22436
  }
21832
22437
  textLeft += iconSize + 4 * zoomFactor;
@@ -21834,7 +22439,7 @@ function drawStaticFormControl(context, control, rect, palette, zoomFactor, shee
21834
22439
  context.beginPath();
21835
22440
  context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
21836
22441
  context.stroke();
21837
- if (control.checked) {
22442
+ if (controlChecked) {
21838
22443
  context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
21839
22444
  context.beginPath();
21840
22445
  context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
@@ -22470,7 +23075,10 @@ function XlsxGrid({
22470
23075
  getCellStyle,
22471
23076
  loadingComponent,
22472
23077
  loadingState,
23078
+ onFormControlAction,
23079
+ onFormControlChange,
22473
23080
  renderChartLoading,
23081
+ renderFormControl,
22474
23082
  palette,
22475
23083
  renderImage,
22476
23084
  renderImageSelection,
@@ -22507,6 +23115,7 @@ function XlsxGrid({
22507
23115
  getRowsBatchAsync,
22508
23116
  getClipboardData,
22509
23117
  getCellDisplayValue: getControllerCellDisplayValue,
23118
+ getFormControlItems,
22510
23119
  images,
22511
23120
  shapes,
22512
23121
  isLoadDeferred,
@@ -22544,9 +23153,13 @@ function XlsxGrid({
22544
23153
  sortTable,
22545
23154
  tables,
22546
23155
  undo,
23156
+ updateFormControl,
22547
23157
  workbook,
22548
23158
  zoomScale
22549
23159
  } = controller;
23160
+ const formControlItemsById = React4.useMemo(() => new Map(
23161
+ formControls.flatMap((control) => control.controlIndex !== void 0 && (control.kind === "dropdown" || control.kind === "listbox") ? [[control.id, getFormControlItems(control.controlIndex, control.sheetIndex)]] : [])
23162
+ ), [formControls, getFormControlItems]);
22550
23163
  const canResizeHeaders = !readOnly || allowResizeInReadOnly;
22551
23164
  const scrollRef = React4.useRef(null);
22552
23165
  const wrapperRef = React4.useRef(null);
@@ -25030,14 +25643,7 @@ function XlsxGrid({
25030
25643
  () => shouldBakeCanvasStaticDrawings ? shapeRects.filter(({ shape }) => shape.hyperlink) : shapeRects,
25031
25644
  [shapeRects, shouldBakeCanvasStaticDrawings]
25032
25645
  );
25033
- const bakedFormControlRects = React4.useMemo(
25034
- () => shouldBakeCanvasStaticDrawings ? formControlRects : [],
25035
- [formControlRects, shouldBakeCanvasStaticDrawings]
25036
- );
25037
- const domFormControlRects = React4.useMemo(
25038
- () => shouldBakeCanvasStaticDrawings ? [] : formControlRects,
25039
- [formControlRects, shouldBakeCanvasStaticDrawings]
25040
- );
25646
+ const domFormControlRects = formControlRects;
25041
25647
  const bakedImageRects = React4.useMemo(
25042
25648
  () => shouldBakeCanvasImages ? imageRects.filter(({ image }) => !image.hyperlink && selectedImageId !== image.id) : [],
25043
25649
  [imageRects, selectedImageId, shouldBakeCanvasImages]
@@ -25054,11 +25660,9 @@ function XlsxGrid({
25054
25660
  return "";
25055
25661
  }
25056
25662
  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("|");
25057
- 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("|");
25058
25663
  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("|");
25059
- return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${controlSignature}:${imageSignature}`;
25664
+ return `${revision}:${canvasImageLoadVersion}:${shapeSignature}:${imageSignature}`;
25060
25665
  }, [
25061
- bakedFormControlRects,
25062
25666
  bakedImageRects,
25063
25667
  bakedShapeRects,
25064
25668
  canvasImageLoadVersion,
@@ -26593,12 +27197,6 @@ function XlsxGrid({
26593
27197
  shape,
26594
27198
  zIndex: shape.zIndex
26595
27199
  })),
26596
- ...bakedFormControlRects.map(({ control, rect }) => ({
26597
- control,
26598
- kind: "formControl",
26599
- rect,
26600
- zIndex: control.zIndex
26601
- })),
26602
27200
  ...bakedImageRects.map(({ image, rect }) => ({
26603
27201
  image,
26604
27202
  kind: "image",
@@ -26749,90 +27347,6 @@ function XlsxGrid({
26749
27347
  drawBakedShapeText(context, shape, localLeft, localTop, rect.width, rect.height);
26750
27348
  context.restore();
26751
27349
  };
26752
- const drawBakedFormControl = (context, control, rect) => {
26753
- const label = resolveFormControlLabel(control);
26754
- const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
26755
- const textColor = control.textColor ?? "#000000";
26756
- const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
26757
- const iconSize = Math.min(14 * zoomFactor, Math.max(0, rect.height - 4 * zoomFactor));
26758
- context.save();
26759
- context.font = `400 ${fontSizePx}px ${control.fontFamily ?? "Calibri, sans-serif"}`;
26760
- context.textBaseline = "middle";
26761
- context.fillStyle = textColor;
26762
- context.strokeStyle = stroke;
26763
- context.lineWidth = Math.max(1, zoomFactor);
26764
- if (control.kind === "group-box") {
26765
- const labelInset = label ? Math.max(7, fontSizePx * 0.5) : 0;
26766
- drawCanvasRoundedRect(context, rect.left, rect.top + labelInset, rect.width, Math.max(0, rect.height - labelInset), 2 * zoomFactor);
26767
- context.stroke();
26768
- if (label) {
26769
- context.fillStyle = SHEET_SURFACE;
26770
- const labelWidth = Math.min(rect.width - 16 * zoomFactor, measureCanvasTextWidth(context, label) + 8 * zoomFactor);
26771
- context.fillRect(rect.left + 8 * zoomFactor, rect.top, labelWidth, fontSizePx * 1.2);
26772
- context.fillStyle = textColor;
26773
- context.textAlign = "left";
26774
- context.fillText(label, rect.left + 12 * zoomFactor, rect.top + fontSizePx * 0.55);
26775
- }
26776
- context.restore();
26777
- return;
26778
- }
26779
- if (control.kind === "button" || control.kind === "dropdown" || control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
26780
- if (control.kind === "button") {
26781
- const gradient = context.createLinearGradient(rect.left, rect.top, rect.left, rect.top + rect.height);
26782
- gradient.addColorStop(0, "#f8fafc");
26783
- gradient.addColorStop(1, "#e2e8f0");
26784
- context.fillStyle = gradient;
26785
- } else {
26786
- context.fillStyle = "transparent";
26787
- }
26788
- drawCanvasRoundedRect(context, rect.left, rect.top, rect.width, rect.height, control.kind === "button" ? 4 * zoomFactor : 2 * zoomFactor);
26789
- if (control.kind === "button") {
26790
- context.fill();
26791
- }
26792
- context.stroke();
26793
- }
26794
- let textLeft = rect.left + 2 * zoomFactor;
26795
- const textRightInset = control.kind === "dropdown" ? 18 * zoomFactor : 6 * zoomFactor;
26796
- if (control.kind === "checkbox") {
26797
- context.strokeRect(rect.left + 2 * zoomFactor, rect.top + (rect.height - iconSize) / 2, iconSize, iconSize);
26798
- if (control.checked) {
26799
- context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
26800
- 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));
26801
- context.strokeStyle = paletteIsDark(palette) ? "#020617" : "#ffffff";
26802
- context.beginPath();
26803
- context.moveTo(rect.left + 2 * zoomFactor + iconSize * 0.24, rect.top + rect.height / 2 + iconSize * 0.06);
26804
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.45, rect.top + rect.height / 2 + iconSize * 0.26);
26805
- context.lineTo(rect.left + 2 * zoomFactor + iconSize * 0.8, rect.top + rect.height / 2 - iconSize * 0.2);
26806
- context.stroke();
26807
- }
26808
- textLeft += iconSize + 4 * zoomFactor;
26809
- } else if (control.kind === "radio") {
26810
- context.beginPath();
26811
- context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize / 2, 0, Math.PI * 2);
26812
- context.stroke();
26813
- if (control.checked) {
26814
- context.fillStyle = paletteIsDark(palette) ? "#60a5fa" : "#2563eb";
26815
- context.beginPath();
26816
- context.arc(rect.left + 2 * zoomFactor + iconSize / 2, rect.top + rect.height / 2, iconSize * 0.25, 0, Math.PI * 2);
26817
- context.fill();
26818
- }
26819
- textLeft += iconSize + 4 * zoomFactor;
26820
- }
26821
- if (label) {
26822
- const maxTextWidth = Math.max(0, rect.left + rect.width - textLeft - textRightInset);
26823
- const text = truncateCanvasText(context, label, maxTextWidth);
26824
- context.fillStyle = textColor;
26825
- context.textAlign = control.textAlign === "right" ? "right" : control.textAlign === "center" ? "center" : "left";
26826
- const textX = context.textAlign === "right" ? rect.left + rect.width - textRightInset : context.textAlign === "center" ? textLeft + maxTextWidth / 2 : textLeft;
26827
- context.fillText(text, textX, rect.top + rect.height / 2);
26828
- }
26829
- if (control.kind === "dropdown") {
26830
- context.fillStyle = textColor;
26831
- context.textAlign = "center";
26832
- context.fillText("\u25BC", rect.left + rect.width - 10 * zoomFactor, rect.top + rect.height / 2);
26833
- }
26834
- context.restore();
26835
- };
26836
27350
  const drawBakedCanvasDrawings = (pane, context, dirtyRects) => {
26837
27351
  if (bakedCanvasDrawingEntries.length === 0 || dirtyRects.length === 0) {
26838
27352
  return;
@@ -26847,8 +27361,6 @@ function XlsxGrid({
26847
27361
  }
26848
27362
  if (entry.kind === "shape") {
26849
27363
  drawBakedShape(context, entry.shape, localRect);
26850
- } else if (entry.kind === "formControl") {
26851
- drawBakedFormControl(context, entry.control, localRect);
26852
27364
  } else {
26853
27365
  const imageElement = getCanvasImage(entry.image);
26854
27366
  if (imageElement) {
@@ -27542,7 +28054,6 @@ function XlsxGrid({
27542
28054
  activeSheet,
27543
28055
  applyCanvasViewportCompensation,
27544
28056
  bakedCanvasDrawingSignature,
27545
- bakedFormControlRects,
27546
28057
  bakedImageRects,
27547
28058
  bakedShapeRects,
27548
28059
  canvasColumnHeaderCells,
@@ -28125,10 +28636,22 @@ function XlsxGrid({
28125
28636
  const fontSizePx = Math.max(9 * zoomFactor, (control.fontSizePt ?? 9) * 96 / 72 * zoomFactor);
28126
28637
  const stroke = paletteIsDark(palette) ? "#cbd5e1" : "#475569";
28127
28638
  const textColor = control.textColor ?? "#000000";
28639
+ const canMutateControl = !readOnly && control.controlIndex !== void 0 && [
28640
+ "checkbox",
28641
+ "dropdown",
28642
+ "listbox",
28643
+ "radio",
28644
+ "scrollbar",
28645
+ "spinner"
28646
+ ].includes(control.kind);
28647
+ const canActivateButton = !readOnly && control.kind === "button" && Boolean(onFormControlAction);
28648
+ const isInteractiveControl = canMutateControl || canActivateButton;
28649
+ const controlItems = formControlItemsById.get(control.id) ?? [];
28128
28650
  const commonStyle = {
28129
28651
  alignItems: control.kind === "group-box" ? "stretch" : "center",
28130
28652
  color: textColor,
28131
28653
  contain: "layout paint",
28654
+ cursor: isInteractiveControl ? "pointer" : void 0,
28132
28655
  display: "flex",
28133
28656
  fontFamily: control.fontFamily ?? "Calibri, sans-serif",
28134
28657
  fontSize: fontSizePx,
@@ -28139,12 +28662,98 @@ function XlsxGrid({
28139
28662
  left: rect.left,
28140
28663
  lineHeight: 1.2,
28141
28664
  overflow: "hidden",
28142
- pointerEvents: "none",
28665
+ pointerEvents: isInteractiveControl ? "auto" : "none",
28143
28666
  position: "absolute",
28144
28667
  top: rect.top,
28145
28668
  width: rect.width,
28146
28669
  zIndex: isFrozenDrawing ? control.zIndex + 20 : control.zIndex
28147
28670
  };
28671
+ const stopControlPointerEvent = (event) => {
28672
+ event.stopPropagation();
28673
+ };
28674
+ const commitControlKind = (kind, nextControl) => {
28675
+ if (readOnly || control.controlIndex === void 0) {
28676
+ return false;
28677
+ }
28678
+ const didUpdate = updateFormControl(control.controlIndex, { kind }, control.sheetIndex);
28679
+ if (didUpdate) {
28680
+ onFormControlChange?.({
28681
+ control: { ...control, ...nextControl },
28682
+ previousControl: control,
28683
+ type: "change"
28684
+ });
28685
+ }
28686
+ return didUpdate;
28687
+ };
28688
+ const setControlState = (state = "unchecked") => {
28689
+ const kind = buildFormControlKindInput(control);
28690
+ if (kind?.kind === "checkbox") {
28691
+ return commitControlKind(
28692
+ { ...kind, state },
28693
+ { checked: state === "checked", state }
28694
+ );
28695
+ }
28696
+ if (kind?.kind === "optionButton" && state !== "mixed") {
28697
+ return commitControlKind(
28698
+ { ...kind, state },
28699
+ { checked: state === "checked", state }
28700
+ );
28701
+ }
28702
+ return false;
28703
+ };
28704
+ const setControlSelected = (selected) => {
28705
+ const kind = buildFormControlKindInput(control);
28706
+ if (kind?.kind === "dropdown") {
28707
+ const nextSelected = typeof selected === "number" ? selected : void 0;
28708
+ return commitControlKind(
28709
+ { ...kind, selected: nextSelected },
28710
+ { selected: nextSelected }
28711
+ );
28712
+ }
28713
+ if (kind?.kind === "listBox") {
28714
+ const nextSelected = Array.isArray(selected) ? selected : typeof selected === "number" ? [selected] : [];
28715
+ return commitControlKind(
28716
+ { ...kind, selected: nextSelected },
28717
+ { selected: nextSelected }
28718
+ );
28719
+ }
28720
+ return false;
28721
+ };
28722
+ const setControlValue = (value) => {
28723
+ if (!Number.isFinite(value)) {
28724
+ return false;
28725
+ }
28726
+ const kind = buildFormControlKindInput(control);
28727
+ if (kind?.kind !== "scrollbar" && kind?.kind !== "spinner") {
28728
+ return false;
28729
+ }
28730
+ const nextValue = Math.max(kind.min, Math.min(kind.max, value));
28731
+ return commitControlKind({ ...kind, value: nextValue }, { value: nextValue });
28732
+ };
28733
+ const activateControl = () => {
28734
+ if (!readOnly && control.kind === "button") {
28735
+ onFormControlAction?.({ control, type: "activate" });
28736
+ }
28737
+ };
28738
+ if (renderFormControl) {
28739
+ return /* @__PURE__ */ jsx3(React4.Fragment, { children: renderFormControl({
28740
+ activate: activateControl,
28741
+ checked: isFormControlChecked(control),
28742
+ control,
28743
+ disabled: !isInteractiveControl,
28744
+ items: controlItems,
28745
+ label: controlLabel,
28746
+ mixed: isFormControlMixed(control),
28747
+ readOnly,
28748
+ rect,
28749
+ setSelected: setControlSelected,
28750
+ setState: setControlState,
28751
+ setValue: setControlValue,
28752
+ stopPropagation: stopControlPointerEvent,
28753
+ style: commonStyle,
28754
+ zoomFactor
28755
+ }) }, `${pane}-${control.id}`);
28756
+ }
28148
28757
  if (control.kind === "group-box") {
28149
28758
  const hasLabel = controlLabel.length > 0;
28150
28759
  return /* @__PURE__ */ jsx3(
@@ -28186,15 +28795,83 @@ function XlsxGrid({
28186
28795
  `${pane}-${control.id}`
28187
28796
  );
28188
28797
  }
28189
- let content;
28190
28798
  if (control.kind === "radio") {
28191
- content = renderRadioControl(Boolean(control.checked), palette, zoomFactor);
28192
- } else if (control.kind === "checkbox") {
28193
- content = renderCheckboxControl(Boolean(control.checked), palette, zoomFactor);
28194
- } else if (control.kind === "button") {
28799
+ const checked = isFormControlChecked(control);
28800
+ return /* @__PURE__ */ jsxs3(
28801
+ "button",
28802
+ {
28803
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28804
+ "aria-checked": checked,
28805
+ disabled: !readOnly && control.controlIndex === void 0,
28806
+ onClick: (event) => {
28807
+ event.stopPropagation();
28808
+ if (!checked) {
28809
+ setControlState("checked");
28810
+ }
28811
+ },
28812
+ onPointerDown: stopControlPointerEvent,
28813
+ role: "radio",
28814
+ style: {
28815
+ ...commonStyle,
28816
+ background: "transparent",
28817
+ border: 0,
28818
+ boxSizing: "border-box",
28819
+ padding: `0 ${Math.max(1, zoomFactor)}px`
28820
+ },
28821
+ tabIndex: readOnly ? -1 : void 0,
28822
+ type: "button",
28823
+ children: [
28824
+ renderRadioControl(checked, palette, zoomFactor),
28825
+ controlLabel ? /* @__PURE__ */ jsx3("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }) : null
28826
+ ]
28827
+ },
28828
+ `${pane}-${control.id}`
28829
+ );
28830
+ }
28831
+ if (control.kind === "checkbox") {
28832
+ const checked = isFormControlChecked(control);
28833
+ const mixed = isFormControlMixed(control);
28834
+ return /* @__PURE__ */ jsxs3(
28835
+ "button",
28836
+ {
28837
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28838
+ "aria-checked": mixed ? "mixed" : checked,
28839
+ disabled: !readOnly && control.controlIndex === void 0,
28840
+ onClick: (event) => {
28841
+ event.stopPropagation();
28842
+ const nextState = checked && !mixed ? "unchecked" : "checked";
28843
+ setControlState(nextState);
28844
+ },
28845
+ onPointerDown: stopControlPointerEvent,
28846
+ role: "checkbox",
28847
+ style: {
28848
+ ...commonStyle,
28849
+ background: "transparent",
28850
+ border: 0,
28851
+ boxSizing: "border-box",
28852
+ padding: `0 ${Math.max(1, zoomFactor)}px`
28853
+ },
28854
+ tabIndex: readOnly ? -1 : void 0,
28855
+ type: "button",
28856
+ children: [
28857
+ renderCheckboxControl(checked, palette, zoomFactor, mixed),
28858
+ controlLabel ? /* @__PURE__ */ jsx3("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }) : null
28859
+ ]
28860
+ },
28861
+ `${pane}-${control.id}`
28862
+ );
28863
+ }
28864
+ if (control.kind === "button") {
28195
28865
  return /* @__PURE__ */ jsx3(
28196
- "div",
28866
+ "button",
28197
28867
  {
28868
+ "aria-disabled": readOnly || !onFormControlAction,
28869
+ disabled: !readOnly && !onFormControlAction,
28870
+ onClick: (event) => {
28871
+ event.stopPropagation();
28872
+ activateControl();
28873
+ },
28874
+ onPointerDown: stopControlPointerEvent,
28198
28875
  style: {
28199
28876
  ...commonStyle,
28200
28877
  background: "linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%)",
@@ -28205,68 +28882,157 @@ function XlsxGrid({
28205
28882
  padding: `0 ${6 * zoomFactor}px`,
28206
28883
  textAlign: "center"
28207
28884
  },
28885
+ tabIndex: readOnly ? -1 : void 0,
28886
+ type: "button",
28208
28887
  children: controlLabel
28209
28888
  },
28210
28889
  `${pane}-${control.id}`
28211
28890
  );
28212
- } else if (control.kind === "dropdown") {
28891
+ }
28892
+ if (control.kind === "dropdown") {
28893
+ const selected = typeof control.selected === "number" ? control.selected : void 0;
28213
28894
  return /* @__PURE__ */ jsxs3(
28214
- "div",
28895
+ "select",
28215
28896
  {
28897
+ "aria-disabled": readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28898
+ "aria-label": controlLabel || control.name || "Dropdown form control",
28899
+ disabled: !readOnly && (control.controlIndex === void 0 || controlItems.length === 0),
28900
+ onChange: (event) => {
28901
+ event.stopPropagation();
28902
+ const nextSelected = event.currentTarget.value === "" ? void 0 : Number(event.currentTarget.value);
28903
+ setControlSelected(nextSelected);
28904
+ },
28905
+ onClick: stopControlPointerEvent,
28906
+ onPointerDown: stopControlPointerEvent,
28216
28907
  style: {
28217
28908
  ...commonStyle,
28909
+ backgroundColor: SHEET_SURFACE,
28218
28910
  border: `${Math.max(1, zoomFactor)}px solid ${stroke}`,
28219
28911
  borderRadius: 2 * zoomFactor,
28220
28912
  boxSizing: "border-box",
28221
- justifyContent: "space-between",
28222
- padding: `0 ${6 * zoomFactor}px`
28913
+ padding: `0 ${4 * zoomFactor}px`
28223
28914
  },
28915
+ tabIndex: readOnly ? -1 : void 0,
28916
+ value: selected === void 0 ? "" : String(selected),
28224
28917
  children: [
28225
- /* @__PURE__ */ jsx3("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }),
28226
- /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", style: { fontSize: fontSizePx * 0.85 }, children: "\u25BC" })
28918
+ selected === void 0 ? /* @__PURE__ */ jsx3("option", { value: "", children: controlLabel }) : null,
28919
+ controlItems.map((item, index) => /* @__PURE__ */ jsx3("option", { value: index, children: item }, index))
28227
28920
  ]
28228
28921
  },
28229
28922
  `${pane}-${control.id}`
28230
28923
  );
28231
- } else if (control.kind === "editbox" || control.kind === "listbox" || control.kind === "scrollbar" || control.kind === "spinner" || control.kind === "unknown") {
28924
+ }
28925
+ if (control.kind === "listbox") {
28926
+ const selected = Array.isArray(control.selected) ? control.selected : control.selected === void 0 ? [] : [control.selected];
28927
+ const allowsMultipleSelection = (control.selection ?? "single") !== "single";
28232
28928
  return /* @__PURE__ */ jsx3(
28233
- "div",
28929
+ "select",
28234
28930
  {
28931
+ "aria-disabled": readOnly || control.controlIndex === void 0 || controlItems.length === 0,
28932
+ "aria-label": controlLabel || control.name || "List box form control",
28933
+ disabled: !readOnly && (control.controlIndex === void 0 || controlItems.length === 0),
28934
+ multiple: allowsMultipleSelection,
28935
+ onChange: (event) => {
28936
+ event.stopPropagation();
28937
+ const nextSelected = Array.from(event.currentTarget.selectedOptions, (option) => Number(option.value));
28938
+ setControlSelected(nextSelected);
28939
+ },
28940
+ onClick: stopControlPointerEvent,
28941
+ onPointerDown: stopControlPointerEvent,
28942
+ size: Math.max(2, Math.min(controlItems.length || 2, Math.floor(rect.height / Math.max(1, fontSizePx * 1.45)))),
28235
28943
  style: {
28236
28944
  ...commonStyle,
28945
+ alignItems: void 0,
28946
+ backgroundColor: SHEET_SURFACE,
28237
28947
  border: `${Math.max(1, zoomFactor)}px solid ${stroke}`,
28238
28948
  borderRadius: 2 * zoomFactor,
28239
28949
  boxSizing: "border-box",
28240
- padding: `0 ${6 * zoomFactor}px`
28950
+ display: "block",
28951
+ padding: 0
28241
28952
  },
28242
- children: /* @__PURE__ */ jsx3("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel })
28953
+ tabIndex: readOnly ? -1 : void 0,
28954
+ value: allowsMultipleSelection ? selected.map(String) : selected[0] === void 0 ? "" : String(selected[0]),
28955
+ children: controlItems.map((item, index) => /* @__PURE__ */ jsx3("option", { value: index, children: item }, index))
28243
28956
  },
28244
28957
  `${pane}-${control.id}`
28245
28958
  );
28246
- } else {
28247
- content = null;
28248
28959
  }
28249
- return /* @__PURE__ */ jsxs3(
28960
+ if (control.kind === "scrollbar") {
28961
+ const min = control.min ?? 0;
28962
+ const max = Math.max(min, control.max ?? 100);
28963
+ return /* @__PURE__ */ jsx3(
28964
+ "input",
28965
+ {
28966
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28967
+ "aria-label": controlLabel || control.name || "Scrollbar form control",
28968
+ disabled: !readOnly && control.controlIndex === void 0,
28969
+ max,
28970
+ min,
28971
+ onChange: (event) => {
28972
+ event.stopPropagation();
28973
+ setControlValue(event.currentTarget.valueAsNumber);
28974
+ },
28975
+ onClick: stopControlPointerEvent,
28976
+ onPointerDown: stopControlPointerEvent,
28977
+ step: Math.max(1, control.increment ?? 1),
28978
+ style: {
28979
+ ...commonStyle,
28980
+ accentColor: paletteIsDark(palette) ? "#60a5fa" : "#2563eb",
28981
+ boxSizing: "border-box",
28982
+ padding: 0,
28983
+ writingMode: control.horizontal === false ? "vertical-lr" : void 0
28984
+ },
28985
+ tabIndex: readOnly ? -1 : void 0,
28986
+ type: "range",
28987
+ value: Math.max(min, Math.min(max, control.value ?? min))
28988
+ },
28989
+ `${pane}-${control.id}`
28990
+ );
28991
+ }
28992
+ if (control.kind === "spinner") {
28993
+ const min = control.min ?? 0;
28994
+ const max = Math.max(min, control.max ?? 100);
28995
+ return /* @__PURE__ */ jsx3(
28996
+ "input",
28997
+ {
28998
+ "aria-disabled": readOnly || control.controlIndex === void 0,
28999
+ "aria-label": controlLabel || control.name || "Spinner form control",
29000
+ disabled: !readOnly && control.controlIndex === void 0,
29001
+ max,
29002
+ min,
29003
+ onChange: (event) => {
29004
+ event.stopPropagation();
29005
+ setControlValue(event.currentTarget.valueAsNumber);
29006
+ },
29007
+ onClick: stopControlPointerEvent,
29008
+ onPointerDown: stopControlPointerEvent,
29009
+ step: Math.max(1, control.increment ?? 1),
29010
+ style: {
29011
+ ...commonStyle,
29012
+ backgroundColor: SHEET_SURFACE,
29013
+ border: `${Math.max(1, zoomFactor)}px solid ${stroke}`,
29014
+ borderRadius: 2 * zoomFactor,
29015
+ boxSizing: "border-box",
29016
+ padding: `0 ${4 * zoomFactor}px`
29017
+ },
29018
+ tabIndex: readOnly ? -1 : void 0,
29019
+ type: "number",
29020
+ value: Math.max(min, Math.min(max, control.value ?? min))
29021
+ },
29022
+ `${pane}-${control.id}`
29023
+ );
29024
+ }
29025
+ return /* @__PURE__ */ jsx3(
28250
29026
  "div",
28251
29027
  {
28252
29028
  style: {
28253
29029
  ...commonStyle,
29030
+ border: control.kind === "editbox" || control.kind === "unknown" ? `${Math.max(1, zoomFactor)}px solid ${stroke}` : void 0,
29031
+ borderRadius: 2 * zoomFactor,
29032
+ boxSizing: "border-box",
28254
29033
  padding: `0 ${Math.max(1, zoomFactor)}px`
28255
29034
  },
28256
- children: [
28257
- content,
28258
- controlLabel ? /* @__PURE__ */ jsx3(
28259
- "span",
28260
- {
28261
- style: {
28262
- overflow: "hidden",
28263
- textOverflow: "ellipsis",
28264
- whiteSpace: "nowrap"
28265
- },
28266
- children: controlLabel
28267
- }
28268
- ) : null
28269
- ]
29035
+ children: controlLabel ? /* @__PURE__ */ jsx3("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: controlLabel }) : null
28270
29036
  },
28271
29037
  `${pane}-${control.id}`
28272
29038
  );
@@ -28553,7 +29319,7 @@ function XlsxGrid({
28553
29319
  };
28554
29320
  const drawingViewportCacheSignature = `${Math.floor(drawingViewport.left / CANVAS_VIEWPORT_OVERSCAN_PX)}:${Math.floor(drawingViewport.top / CANVAS_VIEWPORT_OVERSCAN_PX)}:${drawingViewport.width}:${drawingViewport.height}`;
28555
29321
  const previousPaneDrawingNodes = paneDrawingNodesCacheRef.current;
28556
- 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;
29322
+ 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;
28557
29323
  const paneDrawingNodes = canReusePaneDrawingNodes ? previousPaneDrawingNodes.value : !showImages ? {
28558
29324
  corner: null,
28559
29325
  left: null,
@@ -28589,12 +29355,16 @@ function XlsxGrid({
28589
29355
  paneDrawingNodesCacheRef.current = {
28590
29356
  chartRects,
28591
29357
  drawingViewportSignature: drawingViewportCacheSignature,
29358
+ formControlItemsById,
28592
29359
  formControlRects: domFormControlRects,
28593
29360
  imageRects: domImageRects,
28594
29361
  isChartsLoading,
29362
+ onFormControlAction,
29363
+ onFormControlChange,
28595
29364
  palette,
28596
29365
  readOnly,
28597
29366
  renderChartLoading,
29367
+ renderFormControl,
28598
29368
  renderImage,
28599
29369
  renderImageSelection,
28600
29370
  selectedChartElement,
@@ -28603,6 +29373,7 @@ function XlsxGrid({
28603
29373
  selectionStroke,
28604
29374
  shapeRects: domShapeRects,
28605
29375
  showImages,
29376
+ updateFormControl,
28606
29377
  value: paneDrawingNodes
28607
29378
  };
28608
29379
  }
@@ -29829,7 +30600,10 @@ function XlsxViewerInner({
29829
30600
  isDark = false,
29830
30601
  loadingComponent,
29831
30602
  loadingState,
30603
+ onFormControlAction,
30604
+ onFormControlChange,
29832
30605
  renderChartLoading,
30606
+ renderFormControl,
29833
30607
  renderImage,
29834
30608
  renderImageSelection,
29835
30609
  renderScroller,
@@ -29891,8 +30665,11 @@ function XlsxViewerInner({
29891
30665
  getCellStyle,
29892
30666
  loadingComponent,
29893
30667
  loadingState,
30668
+ onFormControlAction,
30669
+ onFormControlChange,
29894
30670
  palette,
29895
30671
  renderChartLoading,
30672
+ renderFormControl,
29896
30673
  renderImage,
29897
30674
  renderImageSelection,
29898
30675
  renderScroller,
@@ -29997,6 +30774,7 @@ function useXlsxViewerZoom() {
29997
30774
  }
29998
30775
  function useXlsxViewerEditing() {
29999
30776
  const {
30777
+ addFormControl,
30000
30778
  addSheet,
30001
30779
  canRedo,
30002
30780
  canUndo,
@@ -30004,14 +30782,18 @@ function useXlsxViewerEditing() {
30004
30782
  copySelectionToClipboard,
30005
30783
  defineNamedRange,
30006
30784
  fillSelection,
30785
+ formControls,
30007
30786
  getClipboardData,
30008
30787
  getCellDisplayValue: getCellDisplayValue2,
30009
30788
  getCellFormula,
30789
+ getFormControlItems,
30790
+ getSheetFormControls,
30010
30791
  mergeSelection,
30011
30792
  pasteFromClipboard,
30012
30793
  pasteStructuredClipboardData,
30013
30794
  pasteText,
30014
30795
  removeActiveSheet,
30796
+ removeFormControl,
30015
30797
  readOnly,
30016
30798
  redo,
30017
30799
  selectedCellFormula,
@@ -30028,10 +30810,12 @@ function useXlsxViewerEditing() {
30028
30810
  setSelectedCellStyle,
30029
30811
  setSelectedCellValue,
30030
30812
  undo,
30031
- unmergeSelection
30813
+ unmergeSelection,
30814
+ updateFormControl
30032
30815
  } = useXlsxViewer();
30033
30816
  return React4.useMemo(
30034
30817
  () => ({
30818
+ addFormControl,
30035
30819
  addSheet,
30036
30820
  canRedo,
30037
30821
  canUndo,
@@ -30039,14 +30823,18 @@ function useXlsxViewerEditing() {
30039
30823
  copySelectionToClipboard,
30040
30824
  defineNamedRange,
30041
30825
  fillSelection,
30826
+ formControls,
30042
30827
  getClipboardData,
30043
30828
  getCellDisplayValue: getCellDisplayValue2,
30044
30829
  getCellFormula,
30830
+ getFormControlItems,
30831
+ getSheetFormControls,
30045
30832
  mergeSelection,
30046
30833
  pasteFromClipboard,
30047
30834
  pasteStructuredClipboardData,
30048
30835
  pasteText,
30049
30836
  removeActiveSheet,
30837
+ removeFormControl,
30050
30838
  readOnly,
30051
30839
  redo,
30052
30840
  selectedCellFormula,
@@ -30063,9 +30851,11 @@ function useXlsxViewerEditing() {
30063
30851
  setSelectedCellStyle,
30064
30852
  setSelectedCellValue,
30065
30853
  undo,
30066
- unmergeSelection
30854
+ unmergeSelection,
30855
+ updateFormControl
30067
30856
  }),
30068
30857
  [
30858
+ addFormControl,
30069
30859
  addSheet,
30070
30860
  canRedo,
30071
30861
  canUndo,
@@ -30073,14 +30863,18 @@ function useXlsxViewerEditing() {
30073
30863
  copySelectionToClipboard,
30074
30864
  defineNamedRange,
30075
30865
  fillSelection,
30866
+ formControls,
30076
30867
  getClipboardData,
30077
30868
  getCellDisplayValue2,
30078
30869
  getCellFormula,
30870
+ getFormControlItems,
30871
+ getSheetFormControls,
30079
30872
  mergeSelection,
30080
30873
  pasteFromClipboard,
30081
30874
  pasteStructuredClipboardData,
30082
30875
  pasteText,
30083
30876
  removeActiveSheet,
30877
+ removeFormControl,
30084
30878
  readOnly,
30085
30879
  redo,
30086
30880
  selectedCellFormula,
@@ -30097,7 +30891,8 @@ function useXlsxViewerEditing() {
30097
30891
  setSelectedCellStyle,
30098
30892
  setSelectedCellValue,
30099
30893
  undo,
30100
- unmergeSelection
30894
+ unmergeSelection,
30895
+ updateFormControl
30101
30896
  ]
30102
30897
  );
30103
30898
  }
@@ -30295,20 +31090,87 @@ function useXlsxViewerCharts() {
30295
31090
  );
30296
31091
  }
30297
31092
  function useXlsxViewerThumbnails(options = {}) {
30298
- const { getSheetFormControls, getSheetImages, getSheetShapes, workbook, sheets } = useXlsxViewer();
31093
+ const {
31094
+ getRowsBatchAsync,
31095
+ getSheetFormControls,
31096
+ getSheetImages,
31097
+ getSheetShapes,
31098
+ workbook,
31099
+ sheets
31100
+ } = useXlsxViewer();
30299
31101
  const { isDark } = React4.useContext(ViewerAppearanceContext);
30300
31102
  const palette = useViewerPalette(isDark);
30301
31103
  const includeHeaders = options.includeHeaders ?? true;
30302
31104
  const resolution = options.resolution;
30303
31105
  const thumbnailImageCacheRef = React4.useRef(/* @__PURE__ */ new Map());
31106
+ const workerThumbnailRowBatchCacheRef = React4.useRef(/* @__PURE__ */ new Map());
31107
+ const workerThumbnailRowBatchRequestRef = React4.useRef(
31108
+ /* @__PURE__ */ new Map()
31109
+ );
30304
31110
  const isMountedRef = React4.useRef(false);
30305
31111
  const [thumbnailImageLoadVersion, setThumbnailImageLoadVersion] = React4.useState(0);
31112
+ const [workerThumbnailRowBatchVersion, setWorkerThumbnailRowBatchVersion] = React4.useState(0);
30306
31113
  React4.useEffect(() => {
30307
31114
  isMountedRef.current = true;
30308
31115
  return () => {
30309
31116
  isMountedRef.current = false;
30310
31117
  };
30311
31118
  }, []);
31119
+ React4.useEffect(() => {
31120
+ const currentSheets = new Set(sheets);
31121
+ for (const sheet of workerThumbnailRowBatchCacheRef.current.keys()) {
31122
+ if (!currentSheets.has(sheet)) {
31123
+ workerThumbnailRowBatchCacheRef.current.delete(sheet);
31124
+ }
31125
+ }
31126
+ for (const sheet of workerThumbnailRowBatchRequestRef.current.keys()) {
31127
+ if (!currentSheets.has(sheet)) {
31128
+ workerThumbnailRowBatchRequestRef.current.delete(sheet);
31129
+ }
31130
+ }
31131
+ }, [sheets]);
31132
+ const getWorkerThumbnailRowBatch = React4.useCallback((sheet, startRow, endRow) => {
31133
+ const cachedBatch = workerThumbnailRowBatchCacheRef.current.get(sheet);
31134
+ if (cachedBatch && startRow >= cachedBatch.startRow && endRow <= cachedBatch.endRow) {
31135
+ return cachedBatch;
31136
+ }
31137
+ if (!getRowsBatchAsync) {
31138
+ return null;
31139
+ }
31140
+ const pendingRequest = workerThumbnailRowBatchRequestRef.current.get(sheet);
31141
+ if (!pendingRequest) {
31142
+ const request = {
31143
+ endRow: Math.max(endRow, cachedBatch?.endRow ?? endRow),
31144
+ startRow: Math.min(startRow, cachedBatch?.startRow ?? startRow)
31145
+ };
31146
+ workerThumbnailRowBatchRequestRef.current.set(sheet, request);
31147
+ void getRowsBatchAsync(
31148
+ sheet.workbookSheetIndex,
31149
+ request.startRow,
31150
+ request.endRow - request.startRow + 1
31151
+ ).then((rows) => {
31152
+ if (workerThumbnailRowBatchRequestRef.current.get(sheet) !== request) {
31153
+ return;
31154
+ }
31155
+ workerThumbnailRowBatchRequestRef.current.delete(sheet);
31156
+ if (!rows) {
31157
+ return;
31158
+ }
31159
+ workerThumbnailRowBatchCacheRef.current.set(
31160
+ sheet,
31161
+ buildWorksheetBatchWindow(rows, sheet, request.startRow, request.endRow)
31162
+ );
31163
+ if (isMountedRef.current) {
31164
+ setWorkerThumbnailRowBatchVersion((version) => version + 1);
31165
+ }
31166
+ }).catch(() => {
31167
+ if (workerThumbnailRowBatchRequestRef.current.get(sheet) === request) {
31168
+ workerThumbnailRowBatchRequestRef.current.delete(sheet);
31169
+ }
31170
+ });
31171
+ }
31172
+ return null;
31173
+ }, [getRowsBatchAsync]);
30312
31174
  const getThumbnailImage = React4.useCallback((image) => {
30313
31175
  if (typeof Image === "undefined") {
30314
31176
  return null;
@@ -30534,6 +31396,10 @@ function useXlsxViewerThumbnails(options = {}) {
30534
31396
  if (!canvas) {
30535
31397
  return false;
30536
31398
  }
31399
+ const workerRowBatch = worksheet ? null : getWorkerThumbnailRowBatch(sheet, sourceRange.start.row, sourceRange.end.row);
31400
+ if (!worksheet && !workerRowBatch) {
31401
+ return false;
31402
+ }
30537
31403
  const context = canvas.getContext("2d", { alpha: false });
30538
31404
  if (!context) {
30539
31405
  return false;
@@ -30570,9 +31436,6 @@ function useXlsxViewerThumbnails(options = {}) {
30570
31436
  context.fillRect(0, headerHeight, rowHeaderWidth, Math.max(1, rowAxis.totalSize));
30571
31437
  context.fillRect(0, 0, rowHeaderWidth, headerHeight);
30572
31438
  }
30573
- if (!worksheet) {
30574
- return true;
30575
- }
30576
31439
  const conditionalFormatMetricsCache = /* @__PURE__ */ new Map();
30577
31440
  const cellRenderCache = /* @__PURE__ */ new Map();
30578
31441
  const getCellData = (row, col) => {
@@ -30581,7 +31444,9 @@ function useXlsxViewerThumbnails(options = {}) {
30581
31444
  if (cached) {
30582
31445
  return cached;
30583
31446
  }
30584
- if (worksheet.isMergedSecondary(row, col)) {
31447
+ const batchCoversRow = workerRowBatch ? row >= workerRowBatch.startRow && row <= workerRowBatch.endRow : false;
31448
+ const batchedCell = batchCoversRow ? workerRowBatch?.cells.get(cacheKey) : void 0;
31449
+ if (batchedCell?.isMergedSecondary || worksheet?.isMergedSecondary(row, col)) {
30585
31450
  const mergedSecondaryData = {
30586
31451
  isMergedSecondary: true,
30587
31452
  style: {},
@@ -30590,53 +31455,34 @@ function useXlsxViewerThumbnails(options = {}) {
30590
31455
  cellRenderCache.set(cacheKey, mergedSecondaryData);
30591
31456
  return mergedSecondaryData;
30592
31457
  }
30593
- const merge = worksheet.getMergeSpan(row, col);
31458
+ const merge = worksheet ? worksheet.getMergeSpan(row, col) : batchedCell?.mergeSpan;
30594
31459
  const inheritedStyle = resolveInheritedCellStyle2(sheet, row, col);
30595
- const worksheetStyle = worksheet.getCellStyleAt(row, col) ?? null;
31460
+ const worksheetStyle = worksheet ? worksheet.getCellStyleAt(row, col) ?? null : batchedCell?.style ?? null;
30596
31461
  const mergedStyle = mergeResolvedCellStyle(inheritedStyle, worksheetStyle, { replaceXfSubtrees: true });
30597
31462
  const alignment = mergedStyle?.alignment;
30598
31463
  const sparkline = sparklineByCell.get(cacheKey) ?? null;
31464
+ const getNumericValue = (targetRow, targetCol) => worksheet ? getCellNumericValue(worksheet, targetRow, targetCol) : getBatchedCellNumericValue(workerRowBatch?.cells.get(`${targetRow}:${targetCol}`));
30599
31465
  const sparklineValues = sparkline ? sparkline.range.start.row === sparkline.range.end.row ? Array.from(
30600
31466
  { length: Math.abs(sparkline.range.end.col - sparkline.range.start.col) + 1 },
30601
- (_, index) => getCellNumericValue(
30602
- worksheet,
31467
+ (_, index) => getNumericValue(
30603
31468
  sparkline.range.start.row,
30604
31469
  Math.min(sparkline.range.start.col, sparkline.range.end.col) + index
30605
31470
  )
30606
31471
  ) : Array.from(
30607
31472
  { length: Math.abs(sparkline.range.end.row - sparkline.range.start.row) + 1 },
30608
- (_, index) => getCellNumericValue(
30609
- worksheet,
31473
+ (_, index) => getNumericValue(
30610
31474
  Math.min(sparkline.range.start.row, sparkline.range.end.row) + index,
30611
31475
  sparkline.range.start.col
30612
31476
  )
30613
31477
  ) : null;
30614
- const checkboxState = mergedStyle?.cellControl ? getCellBooleanValue(worksheet, row, col) : null;
31478
+ const checkboxState = mergedStyle?.cellControl ? worksheet ? getCellBooleanValue(worksheet, row, col) : getBatchedCellBooleanValue(batchedCell) : null;
30615
31479
  const nextData = {
30616
31480
  canvas: void 0,
30617
31481
  checkboxState,
30618
31482
  colSpan: merge?.colSpan,
30619
- conditionalColorScale: resolveConditionalColorScaleForCell(
30620
- row,
30621
- col,
30622
- worksheet,
30623
- sheet,
30624
- conditionalFormatMetricsCache
30625
- ),
30626
- conditionalDataBar: resolveConditionalDataBarForCell(
30627
- row,
30628
- col,
30629
- worksheet,
30630
- sheet,
30631
- conditionalFormatMetricsCache
30632
- ),
30633
- conditionalIcon: resolveConditionalIconForCell(
30634
- row,
30635
- col,
30636
- worksheet,
30637
- sheet,
30638
- conditionalFormatMetricsCache
30639
- ),
31483
+ conditionalColorScale: worksheet ? resolveConditionalColorScaleForCell(row, col, worksheet, sheet, conditionalFormatMetricsCache) : null,
31484
+ conditionalDataBar: worksheet ? resolveConditionalDataBarForCell(row, col, worksheet, sheet, conditionalFormatMetricsCache) : null,
31485
+ conditionalIcon: worksheet ? resolveConditionalIconForCell(row, col, worksheet, sheet, conditionalFormatMetricsCache) : null,
30640
31486
  isMergedSecondary: false,
30641
31487
  rowSpan: merge?.rowSpan,
30642
31488
  sparkline: sparkline && sparklineValues ? { config: sparkline, values: sparklineValues } : null,
@@ -30644,7 +31490,7 @@ function useXlsxViewerThumbnails(options = {}) {
30644
31490
  showGridLines
30645
31491
  }),
30646
31492
  textRotationDeg: resolveSpreadsheetTextRotation(alignment?.textRotation),
30647
- value: sparkline ? "" : checkboxState !== null ? "" : getCellDisplayValue(worksheet, row, col, sheet)
31493
+ value: sparkline ? "" : checkboxState !== null ? "" : worksheet ? getCellDisplayValue(worksheet, row, col, sheet) : batchedCell?.value ?? ""
30648
31494
  };
30649
31495
  nextData.canvas = buildCanvasCellStyleCache(nextData.style);
30650
31496
  cellRenderCache.set(cacheKey, nextData);
@@ -30918,12 +31764,14 @@ function useXlsxViewerThumbnails(options = {}) {
30918
31764
  getSheetFormControls,
30919
31765
  getSheetImages,
30920
31766
  getSheetShapes,
31767
+ getWorkerThumbnailRowBatch,
30921
31768
  getThumbnailImage,
30922
31769
  includeHeaders,
30923
31770
  palette,
30924
31771
  resolution,
30925
31772
  sheets,
30926
31773
  thumbnailImageLoadVersion,
31774
+ workerThumbnailRowBatchVersion,
30927
31775
  workbook
30928
31776
  ]);
30929
31777
  const paintThumbnail = React4.useCallback(