@cj-tech-master/excelts 4.0.4-canary.20260109050555.4f97ebb → 4.0.4-canary.20260110000241.8ac37ef

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.
Files changed (55) hide show
  1. package/dist/browser/modules/excel/cell.js +39 -1
  2. package/dist/browser/modules/excel/enums.d.ts +2 -1
  3. package/dist/browser/modules/excel/enums.js +2 -1
  4. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +1 -0
  5. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +19 -1
  6. package/dist/browser/modules/excel/types.d.ts +5 -1
  7. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +2 -0
  8. package/dist/browser/modules/excel/utils/ooxml-paths.js +4 -2
  9. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +1 -0
  10. package/dist/browser/modules/excel/xlsx/rel-type.js +2 -1
  11. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +7 -0
  12. package/dist/browser/modules/excel/xlsx/xform/core/feature-property-bag-xform.d.ts +8 -0
  13. package/dist/browser/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +36 -0
  14. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
  15. package/dist/browser/modules/excel/xlsx/xform/style/style-xform.d.ts +2 -0
  16. package/dist/browser/modules/excel/xlsx/xform/style/style-xform.js +11 -0
  17. package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.d.ts +2 -0
  18. package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
  19. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +2 -0
  20. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +19 -0
  21. package/dist/cjs/modules/excel/cell.js +39 -1
  22. package/dist/cjs/modules/excel/enums.js +2 -1
  23. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +19 -1
  24. package/dist/cjs/modules/excel/utils/ooxml-paths.js +4 -2
  25. package/dist/cjs/modules/excel/xlsx/rel-type.js +2 -1
  26. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +7 -0
  27. package/dist/cjs/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +39 -0
  28. package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
  29. package/dist/cjs/modules/excel/xlsx/xform/style/style-xform.js +11 -0
  30. package/dist/cjs/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
  31. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +19 -0
  32. package/dist/esm/modules/excel/cell.js +39 -1
  33. package/dist/esm/modules/excel/enums.js +2 -1
  34. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +19 -1
  35. package/dist/esm/modules/excel/utils/ooxml-paths.js +4 -2
  36. package/dist/esm/modules/excel/xlsx/rel-type.js +2 -1
  37. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +7 -0
  38. package/dist/esm/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +36 -0
  39. package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
  40. package/dist/esm/modules/excel/xlsx/xform/style/style-xform.js +11 -0
  41. package/dist/esm/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
  42. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +19 -0
  43. package/dist/iife/excelts.iife.js +145 -8
  44. package/dist/iife/excelts.iife.js.map +1 -1
  45. package/dist/iife/excelts.iife.min.js +28 -28
  46. package/dist/types/modules/excel/enums.d.ts +2 -1
  47. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -0
  48. package/dist/types/modules/excel/types.d.ts +5 -1
  49. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +2 -0
  50. package/dist/types/modules/excel/xlsx/rel-type.d.ts +1 -0
  51. package/dist/types/modules/excel/xlsx/xform/core/feature-property-bag-xform.d.ts +8 -0
  52. package/dist/types/modules/excel/xlsx/xform/style/style-xform.d.ts +2 -0
  53. package/dist/types/modules/excel/xlsx/xform/style/styles-xform.d.ts +2 -0
  54. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +2 -0
  55. package/package.json +1 -1
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FeaturePropertyBagXform = void 0;
4
+ const base_xform_1 = require("../base-xform.js");
5
+ // FeaturePropertyBag is used to enable checkbox functionality
6
+ // This is a static XML file that MS Excel requires for checkboxes to work
7
+ class FeaturePropertyBagXform extends base_xform_1.BaseXform {
8
+ render(xmlStream) {
9
+ xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
10
+ xmlStream.openNode("FeaturePropertyBags", {
11
+ xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"
12
+ });
13
+ // Checkbox feature
14
+ xmlStream.leafNode("bag", { type: "Checkbox" });
15
+ // XFControls bag
16
+ xmlStream.openNode("bag", { type: "XFControls" });
17
+ xmlStream.leafNode("bagId", { k: "CellControl" }, "0");
18
+ xmlStream.closeNode();
19
+ // XFComplement bag
20
+ xmlStream.openNode("bag", { type: "XFComplement" });
21
+ xmlStream.leafNode("bagId", { k: "XFControls" }, "1");
22
+ xmlStream.closeNode();
23
+ // XFComplements bag
24
+ xmlStream.openNode("bag", { type: "XFComplements", extRef: "XFComplementsMapperExtRef" });
25
+ xmlStream.openNode("a", { k: "MappedFeaturePropertyBags" });
26
+ xmlStream.leafNode("bagId", {}, "2");
27
+ xmlStream.closeNode();
28
+ xmlStream.closeNode();
29
+ xmlStream.closeNode();
30
+ }
31
+ parseOpen() {
32
+ return false;
33
+ }
34
+ parseText() { }
35
+ parseClose() {
36
+ return false;
37
+ }
38
+ }
39
+ exports.FeaturePropertyBagXform = FeaturePropertyBagXform;
@@ -192,6 +192,11 @@ class CellXform extends base_xform_1.BaseXform {
192
192
  xmlStream.addAttribute("t", "b");
193
193
  xmlStream.leafNode("v", null, model.value ? "1" : "0");
194
194
  break;
195
+ case enums_1.Enums.ValueType.Checkbox:
196
+ // Checkboxes are stored as boolean values
197
+ xmlStream.addAttribute("t", "b");
198
+ xmlStream.leafNode("v", null, model.value ? "1" : "0");
199
+ break;
195
200
  case enums_1.Enums.ValueType.Error:
196
201
  xmlStream.addAttribute("t", "e");
197
202
  xmlStream.leafNode("v", null, model.value.error);
@@ -55,6 +55,17 @@ class StyleXform extends base_xform_1.BaseXform {
55
55
  if (model.protection) {
56
56
  this.map.protection.render(xmlStream, model.protection);
57
57
  }
58
+ // Add checkbox extLst if needed
59
+ if (model.checkbox && model.xfComplementIndex !== undefined) {
60
+ xmlStream.openNode("extLst");
61
+ xmlStream.openNode("ext", {
62
+ "xmlns:xfpb": "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag",
63
+ uri: "{C7286773-470A-42A8-94C5-96B5CB345126}"
64
+ });
65
+ xmlStream.leafNode("xfpb:xfComplement", { i: model.xfComplementIndex });
66
+ xmlStream.closeNode();
67
+ xmlStream.closeNode();
68
+ }
58
69
  xmlStream.closeNode();
59
70
  }
60
71
  parseOpen(node) {
@@ -85,6 +85,7 @@ class StylesXform extends base_xform_1.BaseXform {
85
85
  this._addFill({ type: "pattern", pattern: "none" });
86
86
  this._addFill({ type: "pattern", pattern: "gray125" });
87
87
  this.weakMap = new WeakMap();
88
+ this._hasCheckboxes = false;
88
89
  }
89
90
  render(xmlStream, model) {
90
91
  const renderModel = model || this.model;
@@ -225,12 +226,15 @@ class StylesXform extends base_xform_1.BaseXform {
225
226
  // default (zero) font
226
227
  this._addFont({ size: 11, color: { theme: 1 }, name: "Calibri", family: 2, scheme: "minor" });
227
228
  }
228
- // if we have seen this style object before, assume it has the same styleId
229
- if (this.weakMap && this.weakMap.has(model)) {
229
+ const type = cellType || enums_1.Enums.ValueType.Number;
230
+ // If we have seen this style object before, assume it has the same styleId.
231
+ // Do not cache by object identity for checkbox cells because the styleId must
232
+ // include checkbox-specific extLst, and the same style object may be reused
233
+ // for non-checkbox cells.
234
+ if (type !== enums_1.Enums.ValueType.Checkbox && this.weakMap && this.weakMap.has(model)) {
230
235
  return this.weakMap.get(model);
231
236
  }
232
237
  const style = {};
233
- const type = cellType || enums_1.Enums.ValueType.Number;
234
238
  if (model.numFmt) {
235
239
  style.numFmtId = this._addNumFmtStr(model.numFmt);
236
240
  }
@@ -261,8 +265,17 @@ class StylesXform extends base_xform_1.BaseXform {
261
265
  if (model.protection) {
262
266
  style.protection = model.protection;
263
267
  }
268
+ if (type === enums_1.Enums.ValueType.Checkbox) {
269
+ // Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
270
+ // Force applyAlignment="1" (without emitting an <alignment/> node) by providing
271
+ // an empty alignment object when none is specified.
272
+ this._hasCheckboxes = true;
273
+ style.alignment = style.alignment || {};
274
+ style.checkbox = true;
275
+ style.xfComplementIndex = 0;
276
+ }
264
277
  const styleId = this._addStyle(style);
265
- if (this.weakMap) {
278
+ if (type !== enums_1.Enums.ValueType.Checkbox && this.weakMap) {
266
279
  this.weakMap.set(model, styleId);
267
280
  }
268
281
  return styleId;
@@ -325,6 +338,10 @@ class StylesXform extends base_xform_1.BaseXform {
325
338
  getDxfStyle(id) {
326
339
  return this.model.dxfs[id];
327
340
  }
341
+ // Check if workbook uses checkbox feature
342
+ get hasCheckboxes() {
343
+ return !!this._hasCheckboxes;
344
+ }
328
345
  // =========================================================================
329
346
  // Private Interface
330
347
  _addStyle(style) {
@@ -461,12 +478,19 @@ class StylesXformMock extends StylesXform {
461
478
  // the styleId is returned. Note: cellType is used when numFmt not defined
462
479
  addStyleModel(model, cellType) {
463
480
  switch (cellType) {
481
+ case enums_1.Enums.ValueType.Checkbox:
482
+ // Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
483
+ // The mock style manager intentionally does not build those structures.
484
+ throw new Error("Checkbox cells require styles to be enabled (useStyles: true)");
464
485
  case enums_1.Enums.ValueType.Date:
465
486
  return this.dateStyleId;
466
487
  default:
467
488
  return 0;
468
489
  }
469
490
  }
491
+ get hasCheckboxes() {
492
+ return false;
493
+ }
470
494
  get dateStyleId() {
471
495
  if (!this._dateStyleId) {
472
496
  const dateStyle = {
@@ -20,6 +20,7 @@ const content_types_xform_1 = require("./xform/core/content-types-xform.js");
20
20
  const app_xform_1 = require("./xform/core/app-xform.js");
21
21
  const workbook_xform_1 = require("./xform/book/workbook-xform.js");
22
22
  const worksheet_xform_1 = require("./xform/sheet/worksheet-xform.js");
23
+ const feature_property_bag_xform_1 = require("./xform/core/feature-property-bag-xform.js");
23
24
  const drawing_xform_1 = require("./xform/drawing/drawing-xform.js");
24
25
  const table_xform_1 = require("./xform/table/table-xform.js");
25
26
  const pivot_cache_records_xform_1 = require("./xform/pivot-table/pivot-cache-records-xform.js");
@@ -206,6 +207,7 @@ class XLSX {
206
207
  this.addTables(zip, model);
207
208
  this.addPivotTables(zip, model);
208
209
  await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
210
+ await this.addFeaturePropertyBag(zip, model);
209
211
  await this.addMedia(zip, model);
210
212
  await Promise.all([this.addApp(zip, model), this.addCore(zip, model)]);
211
213
  await this.addWorkbook(zip, model);
@@ -974,6 +976,14 @@ class XLSX {
974
976
  Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookSharedStrings
975
977
  });
976
978
  }
979
+ // Add FeaturePropertyBag relationship if checkboxes are used
980
+ if (model.hasCheckboxes) {
981
+ relationships.push({
982
+ Id: `rId${count++}`,
983
+ Type: XLSX.RelType.FeaturePropertyBag,
984
+ Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookFeaturePropertyBag
985
+ });
986
+ }
977
987
  (model.pivotTables || []).forEach((pivotTable) => {
978
988
  pivotTable.rId = `rId${count++}`;
979
989
  relationships.push({
@@ -995,6 +1005,13 @@ class XLSX {
995
1005
  const xml = xform.toXml(relationships);
996
1006
  zip.append(xml, { name: ooxml_paths_1.OOXML_PATHS.xlWorkbookRels });
997
1007
  }
1008
+ async addFeaturePropertyBag(zip, model) {
1009
+ if (!model.hasCheckboxes) {
1010
+ return;
1011
+ }
1012
+ const xform = new feature_property_bag_xform_1.FeaturePropertyBagXform();
1013
+ zip.append(xform.toXml({}), { name: ooxml_paths_1.OOXML_PATHS.xlFeaturePropertyBag });
1014
+ }
998
1015
  async addSharedStrings(zip, model) {
999
1016
  if (model.sharedStrings && model.sharedStrings.count) {
1000
1017
  zip.append(model.sharedStrings.xml, { name: ooxml_paths_1.OOXML_PATHS.xlSharedStrings });
@@ -1149,6 +1166,8 @@ class XLSX {
1149
1166
  });
1150
1167
  worksheetXform.prepare(worksheet, worksheetOptions);
1151
1168
  });
1169
+ // ContentTypesXform expects this flag
1170
+ model.hasCheckboxes = model.styles.hasCheckboxes;
1152
1171
  }
1153
1172
  }
1154
1173
  exports.XLSX = XLSX;
@@ -809,6 +809,40 @@ class BooleanValue {
809
809
  return this.model.value.toString();
810
810
  }
811
811
  }
812
+ class CheckboxValue {
813
+ constructor(cell, value) {
814
+ this.model = {
815
+ address: cell.address,
816
+ type: Cell.Types.Checkbox,
817
+ value: value.checkbox
818
+ };
819
+ }
820
+ get value() {
821
+ return { checkbox: this.model.value };
822
+ }
823
+ set value(value) {
824
+ this.model.value = value.checkbox;
825
+ }
826
+ get type() {
827
+ return Cell.Types.Checkbox;
828
+ }
829
+ get effectiveType() {
830
+ return Cell.Types.Boolean;
831
+ }
832
+ get address() {
833
+ return this.model.address;
834
+ }
835
+ set address(value) {
836
+ this.model.address = value;
837
+ }
838
+ toCsvString() {
839
+ return this.model.value ? 1 : 0;
840
+ }
841
+ release() { }
842
+ toString() {
843
+ return this.model.value.toString();
844
+ }
845
+ }
812
846
  class ErrorValue {
813
847
  constructor(cell, value) {
814
848
  this.model = {
@@ -898,6 +932,9 @@ const Value = {
898
932
  return Cell.Types.Date;
899
933
  }
900
934
  if (typeof value === "object") {
935
+ if ("checkbox" in value && typeof value.checkbox === "boolean") {
936
+ return Cell.Types.Checkbox;
937
+ }
901
938
  if ("text" in value && value.text && "hyperlink" in value && value.hyperlink) {
902
939
  return Cell.Types.Hyperlink;
903
940
  }
@@ -930,7 +967,8 @@ const Value = {
930
967
  { t: Cell.Types.SharedString, f: SharedStringValue },
931
968
  { t: Cell.Types.RichText, f: RichTextValue },
932
969
  { t: Cell.Types.Boolean, f: BooleanValue },
933
- { t: Cell.Types.Error, f: ErrorValue }
970
+ { t: Cell.Types.Error, f: ErrorValue },
971
+ { t: Cell.Types.Checkbox, f: CheckboxValue }
934
972
  ].reduce((p, t) => {
935
973
  p[t.t] = t.f;
936
974
  return p;
@@ -11,7 +11,8 @@ export var ValueType;
11
11
  ValueType[ValueType["RichText"] = 8] = "RichText";
12
12
  ValueType[ValueType["Boolean"] = 9] = "Boolean";
13
13
  ValueType[ValueType["Error"] = 10] = "Error";
14
- ValueType[ValueType["JSON"] = 11] = "JSON"; // Internal type for JSON values that serialize as String
14
+ ValueType[ValueType["JSON"] = 11] = "JSON";
15
+ ValueType[ValueType["Checkbox"] = 12] = "Checkbox";
15
16
  })(ValueType || (ValueType = {}));
16
17
  export var FormulaType;
17
18
  (function (FormulaType) {
@@ -20,6 +20,7 @@ import { ContentTypesXform } from "../xlsx/xform/core/content-types-xform.js";
20
20
  import { AppXform } from "../xlsx/xform/core/app-xform.js";
21
21
  import { WorkbookXform } from "../xlsx/xform/book/workbook-xform.js";
22
22
  import { SharedStringsXform } from "../xlsx/xform/strings/shared-strings-xform.js";
23
+ import { FeaturePropertyBagXform } from "../xlsx/xform/core/feature-property-bag-xform.js";
23
24
  import { theme1Xml } from "../xlsx/xml/theme1.js";
24
25
  import { Writeable, stringToUint8Array } from "../../stream/index.js";
25
26
  import { mediaPath, OOXML_PATHS, OOXML_REL_TARGETS, worksheetRelTarget } from "../utils/ooxml-paths.js";
@@ -134,6 +135,7 @@ export class WorkbookWriterBase {
134
135
  this.addCore(),
135
136
  this.addSharedStrings(),
136
137
  this.addStyles(),
138
+ this.addFeaturePropertyBag(),
137
139
  this.addWorkbookRels()
138
140
  ]);
139
141
  await this.addWorkbook();
@@ -226,7 +228,8 @@ export class WorkbookWriterBase {
226
228
  worksheets: this._worksheets.filter(Boolean),
227
229
  sharedStrings: this.sharedStrings,
228
230
  commentRefs: this.commentRefs,
229
- media: this.media
231
+ media: this.media,
232
+ hasCheckboxes: this.styles.hasCheckboxes
230
233
  };
231
234
  const xform = new ContentTypesXform();
232
235
  this._addFile(xform.toXml(model), OOXML_PATHS.contentTypes);
@@ -280,6 +283,13 @@ export class WorkbookWriterBase {
280
283
  }
281
284
  return Promise.resolve();
282
285
  }
286
+ addFeaturePropertyBag() {
287
+ if (this.styles.hasCheckboxes) {
288
+ const xform = new FeaturePropertyBagXform();
289
+ this._addFile(xform.toXml({}), OOXML_PATHS.xlFeaturePropertyBag);
290
+ }
291
+ return Promise.resolve();
292
+ }
283
293
  addWorkbookRels() {
284
294
  let count = 1;
285
295
  const relationships = [
@@ -293,6 +303,14 @@ export class WorkbookWriterBase {
293
303
  Target: OOXML_REL_TARGETS.workbookSharedStrings
294
304
  });
295
305
  }
306
+ // Add FeaturePropertyBag relationship if checkboxes are used
307
+ if (this.styles.hasCheckboxes) {
308
+ relationships.push({
309
+ Id: `rId${count++}`,
310
+ Type: RelType.FeaturePropertyBag,
311
+ Target: OOXML_REL_TARGETS.workbookFeaturePropertyBag
312
+ });
313
+ }
296
314
  this._worksheets.forEach(ws => {
297
315
  if (ws) {
298
316
  ws.rId = `rId${count++}`;
@@ -7,7 +7,8 @@ export const OOXML_PATHS = {
7
7
  xlWorkbookRels: "xl/_rels/workbook.xml.rels",
8
8
  xlSharedStrings: "xl/sharedStrings.xml",
9
9
  xlStyles: "xl/styles.xml",
10
- xlTheme1: "xl/theme/theme1.xml"
10
+ xlTheme1: "xl/theme/theme1.xml",
11
+ xlFeaturePropertyBag: "xl/featurePropertyBag/featurePropertyBag.xml"
11
12
  };
12
13
  const worksheetXmlRegex = /^xl\/worksheets\/sheet(\d+)[.]xml$/;
13
14
  const worksheetRelsXmlRegex = /^xl\/worksheets\/_rels\/sheet(\d+)[.]xml[.]rels$/;
@@ -162,7 +163,8 @@ export const OOXML_REL_TARGETS = {
162
163
  // Targets inside xl/_rels/workbook.xml.rels (base: xl/)
163
164
  workbookStyles: "styles.xml",
164
165
  workbookSharedStrings: "sharedStrings.xml",
165
- workbookTheme1: "theme/theme1.xml"
166
+ workbookTheme1: "theme/theme1.xml",
167
+ workbookFeaturePropertyBag: "featurePropertyBag/featurePropertyBag.xml"
166
168
  };
167
169
  export function pivotCacheDefinitionRelTargetFromWorkbook(n) {
168
170
  // Target inside xl/_rels/workbook.xml.rels (base: xl/)
@@ -14,6 +14,7 @@ const RelType = {
14
14
  Table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table",
15
15
  PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
16
16
  PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
17
- PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
17
+ PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
18
+ FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"
18
19
  };
19
20
  export { RelType };
@@ -63,6 +63,13 @@ class ContentTypesXform extends BaseXform {
63
63
  PartName: toContentTypesPartName(OOXML_PATHS.xlStyles),
64
64
  ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
65
65
  });
66
+ // Add FeaturePropertyBag if checkboxes are used
67
+ if (model.hasCheckboxes) {
68
+ xmlStream.leafNode("Override", {
69
+ PartName: toContentTypesPartName(OOXML_PATHS.xlFeaturePropertyBag),
70
+ ContentType: "application/vnd.ms-excel.featurepropertybag+xml"
71
+ });
72
+ }
66
73
  const hasSharedStrings = model.sharedStrings && model.sharedStrings.count;
67
74
  if (hasSharedStrings) {
68
75
  xmlStream.leafNode("Override", {
@@ -0,0 +1,36 @@
1
+ import { BaseXform } from "../base-xform.js";
2
+ // FeaturePropertyBag is used to enable checkbox functionality
3
+ // This is a static XML file that MS Excel requires for checkboxes to work
4
+ class FeaturePropertyBagXform extends BaseXform {
5
+ render(xmlStream) {
6
+ xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
7
+ xmlStream.openNode("FeaturePropertyBags", {
8
+ xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag"
9
+ });
10
+ // Checkbox feature
11
+ xmlStream.leafNode("bag", { type: "Checkbox" });
12
+ // XFControls bag
13
+ xmlStream.openNode("bag", { type: "XFControls" });
14
+ xmlStream.leafNode("bagId", { k: "CellControl" }, "0");
15
+ xmlStream.closeNode();
16
+ // XFComplement bag
17
+ xmlStream.openNode("bag", { type: "XFComplement" });
18
+ xmlStream.leafNode("bagId", { k: "XFControls" }, "1");
19
+ xmlStream.closeNode();
20
+ // XFComplements bag
21
+ xmlStream.openNode("bag", { type: "XFComplements", extRef: "XFComplementsMapperExtRef" });
22
+ xmlStream.openNode("a", { k: "MappedFeaturePropertyBags" });
23
+ xmlStream.leafNode("bagId", {}, "2");
24
+ xmlStream.closeNode();
25
+ xmlStream.closeNode();
26
+ xmlStream.closeNode();
27
+ }
28
+ parseOpen() {
29
+ return false;
30
+ }
31
+ parseText() { }
32
+ parseClose() {
33
+ return false;
34
+ }
35
+ }
36
+ export { FeaturePropertyBagXform };
@@ -189,6 +189,11 @@ class CellXform extends BaseXform {
189
189
  xmlStream.addAttribute("t", "b");
190
190
  xmlStream.leafNode("v", null, model.value ? "1" : "0");
191
191
  break;
192
+ case Enums.ValueType.Checkbox:
193
+ // Checkboxes are stored as boolean values
194
+ xmlStream.addAttribute("t", "b");
195
+ xmlStream.leafNode("v", null, model.value ? "1" : "0");
196
+ break;
192
197
  case Enums.ValueType.Error:
193
198
  xmlStream.addAttribute("t", "e");
194
199
  xmlStream.leafNode("v", null, model.value.error);
@@ -52,6 +52,17 @@ class StyleXform extends BaseXform {
52
52
  if (model.protection) {
53
53
  this.map.protection.render(xmlStream, model.protection);
54
54
  }
55
+ // Add checkbox extLst if needed
56
+ if (model.checkbox && model.xfComplementIndex !== undefined) {
57
+ xmlStream.openNode("extLst");
58
+ xmlStream.openNode("ext", {
59
+ "xmlns:xfpb": "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag",
60
+ uri: "{C7286773-470A-42A8-94C5-96B5CB345126}"
61
+ });
62
+ xmlStream.leafNode("xfpb:xfComplement", { i: model.xfComplementIndex });
63
+ xmlStream.closeNode();
64
+ xmlStream.closeNode();
65
+ }
55
66
  xmlStream.closeNode();
56
67
  }
57
68
  parseOpen(node) {
@@ -82,6 +82,7 @@ class StylesXform extends BaseXform {
82
82
  this._addFill({ type: "pattern", pattern: "none" });
83
83
  this._addFill({ type: "pattern", pattern: "gray125" });
84
84
  this.weakMap = new WeakMap();
85
+ this._hasCheckboxes = false;
85
86
  }
86
87
  render(xmlStream, model) {
87
88
  const renderModel = model || this.model;
@@ -222,12 +223,15 @@ class StylesXform extends BaseXform {
222
223
  // default (zero) font
223
224
  this._addFont({ size: 11, color: { theme: 1 }, name: "Calibri", family: 2, scheme: "minor" });
224
225
  }
225
- // if we have seen this style object before, assume it has the same styleId
226
- if (this.weakMap && this.weakMap.has(model)) {
226
+ const type = cellType || Enums.ValueType.Number;
227
+ // If we have seen this style object before, assume it has the same styleId.
228
+ // Do not cache by object identity for checkbox cells because the styleId must
229
+ // include checkbox-specific extLst, and the same style object may be reused
230
+ // for non-checkbox cells.
231
+ if (type !== Enums.ValueType.Checkbox && this.weakMap && this.weakMap.has(model)) {
227
232
  return this.weakMap.get(model);
228
233
  }
229
234
  const style = {};
230
- const type = cellType || Enums.ValueType.Number;
231
235
  if (model.numFmt) {
232
236
  style.numFmtId = this._addNumFmtStr(model.numFmt);
233
237
  }
@@ -258,8 +262,17 @@ class StylesXform extends BaseXform {
258
262
  if (model.protection) {
259
263
  style.protection = model.protection;
260
264
  }
265
+ if (type === Enums.ValueType.Checkbox) {
266
+ // Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
267
+ // Force applyAlignment="1" (without emitting an <alignment/> node) by providing
268
+ // an empty alignment object when none is specified.
269
+ this._hasCheckboxes = true;
270
+ style.alignment = style.alignment || {};
271
+ style.checkbox = true;
272
+ style.xfComplementIndex = 0;
273
+ }
261
274
  const styleId = this._addStyle(style);
262
- if (this.weakMap) {
275
+ if (type !== Enums.ValueType.Checkbox && this.weakMap) {
263
276
  this.weakMap.set(model, styleId);
264
277
  }
265
278
  return styleId;
@@ -322,6 +335,10 @@ class StylesXform extends BaseXform {
322
335
  getDxfStyle(id) {
323
336
  return this.model.dxfs[id];
324
337
  }
338
+ // Check if workbook uses checkbox feature
339
+ get hasCheckboxes() {
340
+ return !!this._hasCheckboxes;
341
+ }
325
342
  // =========================================================================
326
343
  // Private Interface
327
344
  _addStyle(style) {
@@ -457,12 +474,19 @@ class StylesXformMock extends StylesXform {
457
474
  // the styleId is returned. Note: cellType is used when numFmt not defined
458
475
  addStyleModel(model, cellType) {
459
476
  switch (cellType) {
477
+ case Enums.ValueType.Checkbox:
478
+ // Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
479
+ // The mock style manager intentionally does not build those structures.
480
+ throw new Error("Checkbox cells require styles to be enabled (useStyles: true)");
460
481
  case Enums.ValueType.Date:
461
482
  return this.dateStyleId;
462
483
  default:
463
484
  return 0;
464
485
  }
465
486
  }
487
+ get hasCheckboxes() {
488
+ return false;
489
+ }
466
490
  get dateStyleId() {
467
491
  if (!this._dateStyleId) {
468
492
  const dateStyle = {
@@ -17,6 +17,7 @@ import { ContentTypesXform } from "./xform/core/content-types-xform.js";
17
17
  import { AppXform } from "./xform/core/app-xform.js";
18
18
  import { WorkbookXform } from "./xform/book/workbook-xform.js";
19
19
  import { WorkSheetXform } from "./xform/sheet/worksheet-xform.js";
20
+ import { FeaturePropertyBagXform } from "./xform/core/feature-property-bag-xform.js";
20
21
  import { DrawingXform } from "./xform/drawing/drawing-xform.js";
21
22
  import { TableXform } from "./xform/table/table-xform.js";
22
23
  import { PivotCacheRecordsXform } from "./xform/pivot-table/pivot-cache-records-xform.js";
@@ -203,6 +204,7 @@ class XLSX {
203
204
  this.addTables(zip, model);
204
205
  this.addPivotTables(zip, model);
205
206
  await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
207
+ await this.addFeaturePropertyBag(zip, model);
206
208
  await this.addMedia(zip, model);
207
209
  await Promise.all([this.addApp(zip, model), this.addCore(zip, model)]);
208
210
  await this.addWorkbook(zip, model);
@@ -971,6 +973,14 @@ class XLSX {
971
973
  Target: OOXML_REL_TARGETS.workbookSharedStrings
972
974
  });
973
975
  }
976
+ // Add FeaturePropertyBag relationship if checkboxes are used
977
+ if (model.hasCheckboxes) {
978
+ relationships.push({
979
+ Id: `rId${count++}`,
980
+ Type: XLSX.RelType.FeaturePropertyBag,
981
+ Target: OOXML_REL_TARGETS.workbookFeaturePropertyBag
982
+ });
983
+ }
974
984
  (model.pivotTables || []).forEach((pivotTable) => {
975
985
  pivotTable.rId = `rId${count++}`;
976
986
  relationships.push({
@@ -992,6 +1002,13 @@ class XLSX {
992
1002
  const xml = xform.toXml(relationships);
993
1003
  zip.append(xml, { name: OOXML_PATHS.xlWorkbookRels });
994
1004
  }
1005
+ async addFeaturePropertyBag(zip, model) {
1006
+ if (!model.hasCheckboxes) {
1007
+ return;
1008
+ }
1009
+ const xform = new FeaturePropertyBagXform();
1010
+ zip.append(xform.toXml({}), { name: OOXML_PATHS.xlFeaturePropertyBag });
1011
+ }
995
1012
  async addSharedStrings(zip, model) {
996
1013
  if (model.sharedStrings && model.sharedStrings.count) {
997
1014
  zip.append(model.sharedStrings.xml, { name: OOXML_PATHS.xlSharedStrings });
@@ -1146,6 +1163,8 @@ class XLSX {
1146
1163
  });
1147
1164
  worksheetXform.prepare(worksheet, worksheetOptions);
1148
1165
  });
1166
+ // ContentTypesXform expects this flag
1167
+ model.hasCheckboxes = model.styles.hasCheckboxes;
1149
1168
  }
1150
1169
  }
1151
1170
  XLSX.RelType = RelType;