@cj-tech-master/excelts 4.0.4 → 4.1.0-canary.20260110032830.e7d8c4e

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 (92) hide show
  1. package/dist/browser/index.browser.d.ts +2 -0
  2. package/dist/browser/index.browser.js +1 -0
  3. package/dist/browser/index.d.ts +2 -0
  4. package/dist/browser/index.js +1 -0
  5. package/dist/browser/modules/excel/cell.js +39 -1
  6. package/dist/browser/modules/excel/enums.d.ts +2 -1
  7. package/dist/browser/modules/excel/enums.js +2 -1
  8. package/dist/browser/modules/excel/form-control.d.ts +157 -0
  9. package/dist/browser/modules/excel/form-control.js +267 -0
  10. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +1 -0
  11. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +19 -1
  12. package/dist/browser/modules/excel/table.d.ts +6 -2
  13. package/dist/browser/modules/excel/table.js +33 -5
  14. package/dist/browser/modules/excel/types.d.ts +5 -1
  15. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +4 -0
  16. package/dist/browser/modules/excel/utils/ooxml-paths.js +12 -2
  17. package/dist/browser/modules/excel/worksheet.d.ts +32 -0
  18. package/dist/browser/modules/excel/worksheet.js +44 -1
  19. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +2 -0
  20. package/dist/browser/modules/excel/xlsx/rel-type.js +3 -1
  21. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +24 -1
  22. package/dist/browser/modules/excel/xlsx/xform/core/feature-property-bag-xform.d.ts +8 -0
  23. package/dist/browser/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +36 -0
  24. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
  25. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
  26. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
  27. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
  28. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
  29. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
  30. package/dist/browser/modules/excel/xlsx/xform/style/style-xform.d.ts +2 -0
  31. package/dist/browser/modules/excel/xlsx/xform/style/style-xform.js +11 -0
  32. package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.d.ts +2 -0
  33. package/dist/browser/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
  34. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +3 -0
  35. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +43 -5
  36. package/dist/cjs/index.js +3 -1
  37. package/dist/cjs/modules/excel/cell.js +39 -1
  38. package/dist/cjs/modules/excel/enums.js +2 -1
  39. package/dist/cjs/modules/excel/form-control.js +270 -0
  40. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +19 -1
  41. package/dist/cjs/modules/excel/table.js +33 -5
  42. package/dist/cjs/modules/excel/utils/ooxml-paths.js +14 -2
  43. package/dist/cjs/modules/excel/worksheet.js +44 -1
  44. package/dist/cjs/modules/excel/xlsx/rel-type.js +3 -1
  45. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +23 -0
  46. package/dist/cjs/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +39 -0
  47. package/dist/cjs/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +55 -0
  48. package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +184 -0
  49. package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
  50. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +23 -0
  51. package/dist/cjs/modules/excel/xlsx/xform/style/style-xform.js +11 -0
  52. package/dist/cjs/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
  53. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +42 -4
  54. package/dist/esm/index.browser.js +1 -0
  55. package/dist/esm/index.js +1 -0
  56. package/dist/esm/modules/excel/cell.js +39 -1
  57. package/dist/esm/modules/excel/enums.js +2 -1
  58. package/dist/esm/modules/excel/form-control.js +267 -0
  59. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +19 -1
  60. package/dist/esm/modules/excel/table.js +33 -5
  61. package/dist/esm/modules/excel/utils/ooxml-paths.js +12 -2
  62. package/dist/esm/modules/excel/worksheet.js +44 -1
  63. package/dist/esm/modules/excel/xlsx/rel-type.js +3 -1
  64. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +24 -1
  65. package/dist/esm/modules/excel/xlsx/xform/core/feature-property-bag-xform.js +36 -0
  66. package/dist/esm/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
  67. package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
  68. package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +5 -0
  69. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
  70. package/dist/esm/modules/excel/xlsx/xform/style/style-xform.js +11 -0
  71. package/dist/esm/modules/excel/xlsx/xform/style/styles-xform.js +28 -4
  72. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +43 -5
  73. package/dist/iife/excelts.iife.js +629 -40
  74. package/dist/iife/excelts.iife.js.map +1 -1
  75. package/dist/iife/excelts.iife.min.js +30 -30
  76. package/dist/types/index.browser.d.ts +2 -0
  77. package/dist/types/index.d.ts +2 -0
  78. package/dist/types/modules/excel/enums.d.ts +2 -1
  79. package/dist/types/modules/excel/form-control.d.ts +157 -0
  80. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +1 -0
  81. package/dist/types/modules/excel/table.d.ts +6 -2
  82. package/dist/types/modules/excel/types.d.ts +5 -1
  83. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +4 -0
  84. package/dist/types/modules/excel/worksheet.d.ts +32 -0
  85. package/dist/types/modules/excel/xlsx/rel-type.d.ts +2 -0
  86. package/dist/types/modules/excel/xlsx/xform/core/feature-property-bag-xform.d.ts +8 -0
  87. package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
  88. package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
  89. package/dist/types/modules/excel/xlsx/xform/style/style-xform.d.ts +2 -0
  90. package/dist/types/modules/excel/xlsx/xform/style/styles-xform.d.ts +2 -0
  91. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +3 -0
  92. package/package.json +9 -9
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VmlDrawingXform = void 0;
4
+ const xml_stream_1 = require("../../../utils/xml-stream.js");
5
+ const base_xform_1 = require("../base-xform.js");
6
+ const vml_shape_xform_1 = require("../comment/vml-shape-xform.js");
7
+ const form_control_1 = require("../../../form-control.js");
8
+ class VmlDrawingXform extends base_xform_1.BaseXform {
9
+ constructor() {
10
+ super();
11
+ this.map = {
12
+ "v:shape": new vml_shape_xform_1.VmlShapeXform()
13
+ };
14
+ this.model = { comments: [], formControls: [] };
15
+ }
16
+ get tag() {
17
+ return "xml";
18
+ }
19
+ /**
20
+ * Render VML drawing containing both notes and form controls
21
+ */
22
+ render(xmlStream, model) {
23
+ const renderModel = model || this.model;
24
+ const hasComments = renderModel.comments && renderModel.comments.length > 0;
25
+ const hasFormControls = renderModel.formControls && renderModel.formControls.length > 0;
26
+ xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
27
+ xmlStream.openNode(this.tag, VmlDrawingXform.DRAWING_ATTRIBUTES);
28
+ // Shape layout - shared by both notes and form controls
29
+ xmlStream.openNode("o:shapelayout", { "v:ext": "edit" });
30
+ xmlStream.leafNode("o:idmap", { "v:ext": "edit", data: 1 });
31
+ xmlStream.closeNode();
32
+ // Shapetype 202 for notes/comments
33
+ if (hasComments) {
34
+ xmlStream.openNode("v:shapetype", {
35
+ id: "_x0000_t202",
36
+ coordsize: "21600,21600",
37
+ "o:spt": 202,
38
+ path: "m,l,21600r21600,l21600,xe"
39
+ });
40
+ xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
41
+ xmlStream.leafNode("v:path", { gradientshapeok: "t", "o:connecttype": "rect" });
42
+ xmlStream.closeNode();
43
+ }
44
+ // Shapetype 201 for form control checkboxes
45
+ if (hasFormControls) {
46
+ xmlStream.openNode("v:shapetype", {
47
+ id: "_x0000_t201",
48
+ coordsize: "21600,21600",
49
+ "o:spt": "201",
50
+ path: "m,l,21600r21600,l21600,xe"
51
+ });
52
+ xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
53
+ xmlStream.leafNode("v:path", {
54
+ shadowok: "f",
55
+ "o:extrusionok": "f",
56
+ strokeok: "f",
57
+ fillok: "f",
58
+ "o:connecttype": "rect"
59
+ });
60
+ xmlStream.leafNode("o:lock", { "v:ext": "edit", shapetype: "t" });
61
+ xmlStream.closeNode();
62
+ }
63
+ // Render comment shapes
64
+ if (hasComments) {
65
+ const comments = renderModel.comments;
66
+ for (let i = 0; i < comments.length; i++) {
67
+ this.map["v:shape"].render(xmlStream, comments[i], i);
68
+ }
69
+ }
70
+ // Render form control shapes
71
+ if (hasFormControls) {
72
+ for (const control of renderModel.formControls) {
73
+ this._renderCheckboxShape(xmlStream, control);
74
+ }
75
+ }
76
+ xmlStream.closeNode();
77
+ }
78
+ /**
79
+ * Render a checkbox form control shape
80
+ */
81
+ _renderCheckboxShape(xmlStream, control) {
82
+ const shapeAttrs = {
83
+ id: `_x0000_s${control.shapeId}`,
84
+ type: "#_x0000_t201",
85
+ style: form_control_1.FormCheckbox.getVmlStyle(control),
86
+ "o:insetmode": "auto",
87
+ fillcolor: "buttonFace [67]",
88
+ strokecolor: "windowText [64]",
89
+ "o:preferrelative": "t",
90
+ filled: "f",
91
+ stroked: "f"
92
+ };
93
+ xmlStream.openNode("v:shape", shapeAttrs);
94
+ // Fill element
95
+ xmlStream.leafNode("v:fill", { "o:detectmouseclick": "t" });
96
+ // Lock element
97
+ xmlStream.leafNode("o:lock", { "v:ext": "edit", text: "t" });
98
+ // Textbox for label
99
+ if (control.text) {
100
+ xmlStream.openNode("v:textbox", {
101
+ style: "mso-direction-alt:auto",
102
+ "o:singleclick": "t"
103
+ });
104
+ xmlStream.openNode("div", { style: "text-align:left" });
105
+ xmlStream.openNode("font", { face: "Tahoma", size: "160", color: "auto" });
106
+ xmlStream.writeText(control.text);
107
+ xmlStream.closeNode(); // font
108
+ xmlStream.closeNode(); // div
109
+ xmlStream.closeNode(); // v:textbox
110
+ }
111
+ // ClientData - the core of the checkbox control
112
+ xmlStream.openNode("x:ClientData", { ObjectType: "Checkbox" });
113
+ // Anchor position
114
+ xmlStream.openNode("x:Anchor");
115
+ xmlStream.writeText(form_control_1.FormCheckbox.getVmlAnchor(control));
116
+ xmlStream.closeNode();
117
+ // Print settings
118
+ xmlStream.leafNode("x:PrintObject", undefined, control.print ? "True" : "False");
119
+ xmlStream.leafNode("x:AutoFill", undefined, "False");
120
+ xmlStream.leafNode("x:AutoLine", undefined, "False");
121
+ xmlStream.leafNode("x:TextVAlign", undefined, "Center");
122
+ // Linked cell
123
+ if (control.link) {
124
+ xmlStream.leafNode("x:FmlaLink", undefined, control.link);
125
+ }
126
+ // 3D appearance
127
+ if (control.noThreeD) {
128
+ xmlStream.leafNode("x:NoThreeD");
129
+ }
130
+ // Checked state (0 = unchecked, 1 = checked, 2 = mixed)
131
+ xmlStream.leafNode("x:Checked", undefined, String(form_control_1.FormCheckbox.getVmlCheckedValue(control)));
132
+ xmlStream.closeNode(); // x:ClientData
133
+ xmlStream.closeNode(); // v:shape
134
+ }
135
+ // Parsing - delegate to VmlShapeXform for notes
136
+ parseOpen(node) {
137
+ if (this.parser) {
138
+ this.parser.parseOpen(node);
139
+ return true;
140
+ }
141
+ switch (node.name) {
142
+ case this.tag:
143
+ this.reset();
144
+ this.model = {
145
+ comments: [],
146
+ formControls: []
147
+ };
148
+ break;
149
+ default:
150
+ this.parser = this.map[node.name];
151
+ if (this.parser) {
152
+ this.parser.parseOpen(node);
153
+ }
154
+ break;
155
+ }
156
+ return true;
157
+ }
158
+ parseText(text) {
159
+ if (this.parser) {
160
+ this.parser.parseText(text);
161
+ }
162
+ }
163
+ parseClose(name) {
164
+ if (this.parser) {
165
+ if (!this.parser.parseClose(name)) {
166
+ this.model.comments.push(this.parser.model);
167
+ this.parser = undefined;
168
+ }
169
+ return true;
170
+ }
171
+ switch (name) {
172
+ case this.tag:
173
+ return false;
174
+ default:
175
+ return true;
176
+ }
177
+ }
178
+ }
179
+ exports.VmlDrawingXform = VmlDrawingXform;
180
+ VmlDrawingXform.DRAWING_ATTRIBUTES = {
181
+ "xmlns:v": "urn:schemas-microsoft-com:vml",
182
+ "xmlns:o": "urn:schemas-microsoft-com:office:office",
183
+ "xmlns:x": "urn:schemas-microsoft-com:office:excel"
184
+ };
@@ -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);
@@ -278,6 +278,29 @@ class WorkSheetXform extends base_xform_1.BaseXform {
278
278
  Target: (0, ooxml_paths_1.pivotTableRelTargetFromWorksheet)(pivotTable.tableNumber)
279
279
  });
280
280
  });
281
+ // prepare form controls (legacy checkboxes)
282
+ // Form controls share the VML file with comments, but need separate ctrlProp relationships
283
+ if (model.formControls && model.formControls.length > 0) {
284
+ // If no comments, we need to add the VML drawing relationship for form controls
285
+ if (model.comments.length === 0) {
286
+ rels.push({
287
+ Id: nextRid(rels),
288
+ Type: rel_type_1.RelType.VmlDrawing,
289
+ Target: (0, ooxml_paths_1.vmlDrawingRelTargetFromWorksheet)(model.id)
290
+ });
291
+ }
292
+ // Add ctrlProp relationships for each form control
293
+ for (const control of model.formControls) {
294
+ const globalCtrlPropId = options.formControlRefs.length + 1;
295
+ control.ctrlPropId = globalCtrlPropId;
296
+ rels.push({
297
+ Id: nextRid(rels),
298
+ Type: rel_type_1.RelType.CtrlProp,
299
+ Target: (0, ooxml_paths_1.ctrlPropRelTargetFromWorksheet)(globalCtrlPropId)
300
+ });
301
+ options.formControlRefs.push(globalCtrlPropId);
302
+ }
303
+ }
281
304
  // prepare ext items
282
305
  this.map.extLst.prepare(model, options);
283
306
  }
@@ -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,13 +20,15 @@ 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");
26
27
  const pivot_cache_definition_xform_1 = require("./xform/pivot-table/pivot-cache-definition-xform.js");
27
28
  const pivot_table_xform_1 = require("./xform/pivot-table/pivot-table-xform.js");
28
29
  const comments_xform_1 = require("./xform/comment/comments-xform.js");
29
- const vml_notes_xform_1 = require("./xform/comment/vml-notes-xform.js");
30
+ const vml_drawing_xform_1 = require("./xform/drawing/vml-drawing-xform.js");
31
+ const ctrl_prop_xform_1 = require("./xform/drawing/ctrl-prop-xform.js");
30
32
  const theme1_1 = require("./xml/theme1.js");
31
33
  const rel_type_1 = require("./rel-type.js");
32
34
  const stream_buf_1 = require("../utils/stream-buf.js");
@@ -206,6 +208,7 @@ class XLSX {
206
208
  this.addTables(zip, model);
207
209
  this.addPivotTables(zip, model);
208
210
  await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
211
+ await this.addFeaturePropertyBag(zip, model);
209
212
  await this.addMedia(zip, model);
210
213
  await Promise.all([this.addApp(zip, model), this.addCore(zip, model)]);
211
214
  await this.addWorkbook(zip, model);
@@ -703,7 +706,7 @@ class XLSX {
703
706
  model.drawingRels[name] = relationships;
704
707
  }
705
708
  async _processVmlDrawingEntry(entry, model, name) {
706
- const xform = new vml_notes_xform_1.VmlNotesXform();
709
+ const xform = new vml_drawing_xform_1.VmlDrawingXform();
707
710
  const vmlDrawing = await xform.parseStream(entry);
708
711
  model.vmlDrawings[(0, ooxml_paths_1.vmlDrawingRelTargetFromWorksheetName)(name)] = vmlDrawing;
709
712
  }
@@ -974,6 +977,14 @@ class XLSX {
974
977
  Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookSharedStrings
975
978
  });
976
979
  }
980
+ // Add FeaturePropertyBag relationship if checkboxes are used
981
+ if (model.hasCheckboxes) {
982
+ relationships.push({
983
+ Id: `rId${count++}`,
984
+ Type: XLSX.RelType.FeaturePropertyBag,
985
+ Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookFeaturePropertyBag
986
+ });
987
+ }
977
988
  (model.pivotTables || []).forEach((pivotTable) => {
978
989
  pivotTable.rId = `rId${count++}`;
979
990
  relationships.push({
@@ -995,6 +1006,13 @@ class XLSX {
995
1006
  const xml = xform.toXml(relationships);
996
1007
  zip.append(xml, { name: ooxml_paths_1.OOXML_PATHS.xlWorkbookRels });
997
1008
  }
1009
+ async addFeaturePropertyBag(zip, model) {
1010
+ if (!model.hasCheckboxes) {
1011
+ return;
1012
+ }
1013
+ const xform = new feature_property_bag_xform_1.FeaturePropertyBagXform();
1014
+ zip.append(xform.toXml({}), { name: ooxml_paths_1.OOXML_PATHS.xlFeaturePropertyBag });
1015
+ }
998
1016
  async addSharedStrings(zip, model) {
999
1017
  if (model.sharedStrings && model.sharedStrings.count) {
1000
1018
  zip.append(model.sharedStrings.xml, { name: ooxml_paths_1.OOXML_PATHS.xlSharedStrings });
@@ -1014,7 +1032,8 @@ class XLSX {
1014
1032
  const worksheetXform = new worksheet_xform_1.WorkSheetXform();
1015
1033
  const relationshipsXform = new relationships_xform_1.RelationshipsXform();
1016
1034
  const commentsXform = new comments_xform_1.CommentsXform();
1017
- const vmlNotesXform = new vml_notes_xform_1.VmlNotesXform();
1035
+ const vmlDrawingXform = new vml_drawing_xform_1.VmlDrawingXform();
1036
+ const ctrlPropXform = new ctrl_prop_xform_1.CtrlPropXform();
1018
1037
  model.worksheets.forEach((worksheet, index) => {
1019
1038
  const fileIndex = worksheet.fileIndex || index + 1;
1020
1039
  let xmlStream = new xml_stream_1.XmlStream();
@@ -1025,14 +1044,30 @@ class XLSX {
1025
1044
  relationshipsXform.render(xmlStream, worksheet.rels);
1026
1045
  zip.append(xmlStream.xml, { name: (0, ooxml_paths_1.worksheetRelsPath)(fileIndex) });
1027
1046
  }
1047
+ // Generate comments XML (separate from VML)
1028
1048
  if (worksheet.comments.length > 0) {
1029
1049
  xmlStream = new xml_stream_1.XmlStream();
1030
1050
  commentsXform.render(xmlStream, worksheet);
1031
1051
  zip.append(xmlStream.xml, { name: (0, ooxml_paths_1.commentsPath)(fileIndex) });
1052
+ }
1053
+ // Generate unified VML drawing (contains both notes and form controls)
1054
+ const hasComments = worksheet.comments.length > 0;
1055
+ const hasFormControls = worksheet.formControls && worksheet.formControls.length > 0;
1056
+ if (hasComments || hasFormControls) {
1032
1057
  xmlStream = new xml_stream_1.XmlStream();
1033
- vmlNotesXform.render(xmlStream, worksheet);
1058
+ vmlDrawingXform.render(xmlStream, {
1059
+ comments: hasComments ? worksheet.comments : [],
1060
+ formControls: hasFormControls ? worksheet.formControls : []
1061
+ });
1034
1062
  zip.append(xmlStream.xml, { name: (0, ooxml_paths_1.vmlDrawingPath)(fileIndex) });
1035
1063
  }
1064
+ // Generate ctrlProp files for form controls
1065
+ if (hasFormControls) {
1066
+ worksheet.formControls.forEach((control) => {
1067
+ const xml = ctrlPropXform.toXml(control);
1068
+ zip.append(xml, { name: (0, ooxml_paths_1.ctrlPropPath)(control.ctrlPropId) });
1069
+ });
1070
+ }
1036
1071
  });
1037
1072
  }
1038
1073
  addDrawings(zip, model) {
@@ -1138,6 +1173,7 @@ class XLSX {
1138
1173
  };
1139
1174
  worksheetOptions.drawings = model.drawings = [];
1140
1175
  worksheetOptions.commentRefs = model.commentRefs = [];
1176
+ worksheetOptions.formControlRefs = model.formControlRefs = [];
1141
1177
  let tableCount = 0;
1142
1178
  model.tables = [];
1143
1179
  model.worksheets.forEach((worksheet) => {
@@ -1149,6 +1185,8 @@ class XLSX {
1149
1185
  });
1150
1186
  worksheetXform.prepare(worksheet, worksheetOptions);
1151
1187
  });
1188
+ // ContentTypesXform expects this flag
1189
+ model.hasCheckboxes = model.styles.hasCheckboxes;
1152
1190
  }
1153
1191
  }
1154
1192
  exports.XLSX = XLSX;
@@ -15,6 +15,7 @@ export { Image } from "./modules/excel/image.js";
15
15
  export * from "./modules/excel/anchor.js";
16
16
  export { Table } from "./modules/excel/table.js";
17
17
  export { DataValidations } from "./modules/excel/data-validations.js";
18
+ export { FormCheckbox } from "./modules/excel/form-control.js";
18
19
  // =============================================================================
19
20
  // Enums
20
21
  // =============================================================================
package/dist/esm/index.js CHANGED
@@ -11,6 +11,7 @@ export { Image } from "./modules/excel/image.js";
11
11
  export * from "./modules/excel/anchor.js";
12
12
  export { Table } from "./modules/excel/table.js";
13
13
  export { DataValidations } from "./modules/excel/data-validations.js";
14
+ export { FormCheckbox } from "./modules/excel/form-control.js";
14
15
  // =============================================================================
15
16
  // Node.js Only: Streaming Classes
16
17
  // These can also be accessed via Workbook.createStreamWriter/createStreamReader
@@ -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) {