@cj-tech-master/excelts 4.0.4-canary.20260110000241.8ac37ef → 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 (54) 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/form-control.d.ts +157 -0
  6. package/dist/browser/modules/excel/form-control.js +267 -0
  7. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +2 -0
  8. package/dist/browser/modules/excel/utils/ooxml-paths.js +8 -0
  9. package/dist/browser/modules/excel/worksheet.d.ts +32 -0
  10. package/dist/browser/modules/excel/worksheet.js +44 -1
  11. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +1 -0
  12. package/dist/browser/modules/excel/xlsx/rel-type.js +2 -1
  13. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +17 -1
  14. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
  15. package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
  16. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
  17. package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
  18. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
  19. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
  20. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +24 -5
  21. package/dist/cjs/index.js +3 -1
  22. package/dist/cjs/modules/excel/form-control.js +270 -0
  23. package/dist/cjs/modules/excel/utils/ooxml-paths.js +10 -0
  24. package/dist/cjs/modules/excel/worksheet.js +44 -1
  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 +16 -0
  27. package/dist/cjs/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +55 -0
  28. package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +184 -0
  29. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +23 -0
  30. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +23 -4
  31. package/dist/esm/index.browser.js +1 -0
  32. package/dist/esm/index.js +1 -0
  33. package/dist/esm/modules/excel/form-control.js +267 -0
  34. package/dist/esm/modules/excel/utils/ooxml-paths.js +8 -0
  35. package/dist/esm/modules/excel/worksheet.js +44 -1
  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 +17 -1
  38. package/dist/esm/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
  39. package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
  40. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
  41. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +24 -5
  42. package/dist/iife/excelts.iife.js +466 -30
  43. package/dist/iife/excelts.iife.js.map +1 -1
  44. package/dist/iife/excelts.iife.min.js +30 -30
  45. package/dist/types/index.browser.d.ts +2 -0
  46. package/dist/types/index.d.ts +2 -0
  47. package/dist/types/modules/excel/form-control.d.ts +157 -0
  48. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +2 -0
  49. package/dist/types/modules/excel/worksheet.d.ts +32 -0
  50. package/dist/types/modules/excel/xlsx/rel-type.d.ts +1 -0
  51. package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
  52. package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
  53. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
  54. package/package.json +2 -2
@@ -0,0 +1,22 @@
1
+ import { BaseXform } from "@excel/xlsx/xform/base-xform";
2
+ import type { FormCheckboxModel } from "@excel/form-control";
3
+ /**
4
+ * Control Properties Xform - Generates ctrlProp*.xml for form controls
5
+ *
6
+ * Each form control (checkbox, button, etc.) has an associated ctrlProp file
7
+ * that stores its properties like objectType, checked state, and linked cell.
8
+ */
9
+ declare class CtrlPropXform extends BaseXform {
10
+ model: FormCheckboxModel;
11
+ get tag(): string;
12
+ render(xmlStream: any, model?: FormCheckboxModel): void;
13
+ /**
14
+ * Generate XML string directly (convenience method)
15
+ * Uses render() internally to ensure consistency
16
+ */
17
+ toXml(model: FormCheckboxModel): string;
18
+ parseOpen(): boolean;
19
+ parseText(): void;
20
+ parseClose(): boolean;
21
+ }
22
+ export { CtrlPropXform };
@@ -0,0 +1,52 @@
1
+ import { XmlStream } from "../../../utils/xml-stream.js";
2
+ import { BaseXform } from "../base-xform.js";
3
+ /**
4
+ * Control Properties Xform - Generates ctrlProp*.xml for form controls
5
+ *
6
+ * Each form control (checkbox, button, etc.) has an associated ctrlProp file
7
+ * that stores its properties like objectType, checked state, and linked cell.
8
+ */
9
+ class CtrlPropXform extends BaseXform {
10
+ get tag() {
11
+ return "formControlPr";
12
+ }
13
+ render(xmlStream, model) {
14
+ const renderModel = model || this.model;
15
+ const attrs = {
16
+ xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
17
+ objectType: "CheckBox",
18
+ checked: renderModel.checked,
19
+ lockText: "1"
20
+ };
21
+ // Add linked cell reference
22
+ if (renderModel.link) {
23
+ attrs.fmlaLink = renderModel.link;
24
+ }
25
+ // Add noThreeD for flat appearance
26
+ if (renderModel.noThreeD) {
27
+ attrs.noThreeD = "1";
28
+ }
29
+ xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
30
+ xmlStream.leafNode(this.tag, attrs);
31
+ }
32
+ /**
33
+ * Generate XML string directly (convenience method)
34
+ * Uses render() internally to ensure consistency
35
+ */
36
+ toXml(model) {
37
+ const xmlStream = new XmlStream();
38
+ this.render(xmlStream, model);
39
+ return xmlStream.xml;
40
+ }
41
+ // Parsing not implemented - form controls are write-only for now
42
+ parseOpen() {
43
+ return true;
44
+ }
45
+ parseText() {
46
+ // Not implemented
47
+ }
48
+ parseClose() {
49
+ return false;
50
+ }
51
+ }
52
+ export { CtrlPropXform };
@@ -0,0 +1,44 @@
1
+ import { BaseXform } from "@excel/xlsx/xform/base-xform";
2
+ import { type FormCheckboxModel } from "@excel/form-control";
3
+ /**
4
+ * Unified VML Drawing Xform - Combines Notes (comments) and Form Controls
5
+ *
6
+ * Excel uses a single VML file per worksheet that can contain:
7
+ * - Comment/note shapes (shapetype 202)
8
+ * - Form control shapes (checkbox shapetype 201, etc.)
9
+ *
10
+ * This unified xform renders both into a single VML file.
11
+ */
12
+ interface VmlDrawingModel {
13
+ /** Comment/note shapes */
14
+ comments?: any[];
15
+ /** Form control checkboxes */
16
+ formControls?: FormCheckboxModel[];
17
+ }
18
+ declare class VmlDrawingXform extends BaseXform {
19
+ map: {
20
+ [key: string]: any;
21
+ };
22
+ parser: any;
23
+ model: VmlDrawingModel;
24
+ constructor();
25
+ get tag(): string;
26
+ /**
27
+ * Render VML drawing containing both notes and form controls
28
+ */
29
+ render(xmlStream: any, model?: VmlDrawingModel): void;
30
+ /**
31
+ * Render a checkbox form control shape
32
+ */
33
+ private _renderCheckboxShape;
34
+ parseOpen(node: any): boolean;
35
+ parseText(text: string): void;
36
+ parseClose(name: string): boolean;
37
+ static DRAWING_ATTRIBUTES: {
38
+ "xmlns:v": string;
39
+ "xmlns:o": string;
40
+ "xmlns:x": string;
41
+ };
42
+ }
43
+ export { VmlDrawingXform };
44
+ export type { VmlDrawingModel };
@@ -0,0 +1,181 @@
1
+ import { XmlStream } from "../../../utils/xml-stream.js";
2
+ import { BaseXform } from "../base-xform.js";
3
+ import { VmlShapeXform } from "../comment/vml-shape-xform.js";
4
+ import { FormCheckbox } from "../../../form-control.js";
5
+ class VmlDrawingXform extends BaseXform {
6
+ constructor() {
7
+ super();
8
+ this.map = {
9
+ "v:shape": new VmlShapeXform()
10
+ };
11
+ this.model = { comments: [], formControls: [] };
12
+ }
13
+ get tag() {
14
+ return "xml";
15
+ }
16
+ /**
17
+ * Render VML drawing containing both notes and form controls
18
+ */
19
+ render(xmlStream, model) {
20
+ const renderModel = model || this.model;
21
+ const hasComments = renderModel.comments && renderModel.comments.length > 0;
22
+ const hasFormControls = renderModel.formControls && renderModel.formControls.length > 0;
23
+ xmlStream.openXml(XmlStream.StdDocAttributes);
24
+ xmlStream.openNode(this.tag, VmlDrawingXform.DRAWING_ATTRIBUTES);
25
+ // Shape layout - shared by both notes and form controls
26
+ xmlStream.openNode("o:shapelayout", { "v:ext": "edit" });
27
+ xmlStream.leafNode("o:idmap", { "v:ext": "edit", data: 1 });
28
+ xmlStream.closeNode();
29
+ // Shapetype 202 for notes/comments
30
+ if (hasComments) {
31
+ xmlStream.openNode("v:shapetype", {
32
+ id: "_x0000_t202",
33
+ coordsize: "21600,21600",
34
+ "o:spt": 202,
35
+ path: "m,l,21600r21600,l21600,xe"
36
+ });
37
+ xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
38
+ xmlStream.leafNode("v:path", { gradientshapeok: "t", "o:connecttype": "rect" });
39
+ xmlStream.closeNode();
40
+ }
41
+ // Shapetype 201 for form control checkboxes
42
+ if (hasFormControls) {
43
+ xmlStream.openNode("v:shapetype", {
44
+ id: "_x0000_t201",
45
+ coordsize: "21600,21600",
46
+ "o:spt": "201",
47
+ path: "m,l,21600r21600,l21600,xe"
48
+ });
49
+ xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
50
+ xmlStream.leafNode("v:path", {
51
+ shadowok: "f",
52
+ "o:extrusionok": "f",
53
+ strokeok: "f",
54
+ fillok: "f",
55
+ "o:connecttype": "rect"
56
+ });
57
+ xmlStream.leafNode("o:lock", { "v:ext": "edit", shapetype: "t" });
58
+ xmlStream.closeNode();
59
+ }
60
+ // Render comment shapes
61
+ if (hasComments) {
62
+ const comments = renderModel.comments;
63
+ for (let i = 0; i < comments.length; i++) {
64
+ this.map["v:shape"].render(xmlStream, comments[i], i);
65
+ }
66
+ }
67
+ // Render form control shapes
68
+ if (hasFormControls) {
69
+ for (const control of renderModel.formControls) {
70
+ this._renderCheckboxShape(xmlStream, control);
71
+ }
72
+ }
73
+ xmlStream.closeNode();
74
+ }
75
+ /**
76
+ * Render a checkbox form control shape
77
+ */
78
+ _renderCheckboxShape(xmlStream, control) {
79
+ const shapeAttrs = {
80
+ id: `_x0000_s${control.shapeId}`,
81
+ type: "#_x0000_t201",
82
+ style: FormCheckbox.getVmlStyle(control),
83
+ "o:insetmode": "auto",
84
+ fillcolor: "buttonFace [67]",
85
+ strokecolor: "windowText [64]",
86
+ "o:preferrelative": "t",
87
+ filled: "f",
88
+ stroked: "f"
89
+ };
90
+ xmlStream.openNode("v:shape", shapeAttrs);
91
+ // Fill element
92
+ xmlStream.leafNode("v:fill", { "o:detectmouseclick": "t" });
93
+ // Lock element
94
+ xmlStream.leafNode("o:lock", { "v:ext": "edit", text: "t" });
95
+ // Textbox for label
96
+ if (control.text) {
97
+ xmlStream.openNode("v:textbox", {
98
+ style: "mso-direction-alt:auto",
99
+ "o:singleclick": "t"
100
+ });
101
+ xmlStream.openNode("div", { style: "text-align:left" });
102
+ xmlStream.openNode("font", { face: "Tahoma", size: "160", color: "auto" });
103
+ xmlStream.writeText(control.text);
104
+ xmlStream.closeNode(); // font
105
+ xmlStream.closeNode(); // div
106
+ xmlStream.closeNode(); // v:textbox
107
+ }
108
+ // ClientData - the core of the checkbox control
109
+ xmlStream.openNode("x:ClientData", { ObjectType: "Checkbox" });
110
+ // Anchor position
111
+ xmlStream.openNode("x:Anchor");
112
+ xmlStream.writeText(FormCheckbox.getVmlAnchor(control));
113
+ xmlStream.closeNode();
114
+ // Print settings
115
+ xmlStream.leafNode("x:PrintObject", undefined, control.print ? "True" : "False");
116
+ xmlStream.leafNode("x:AutoFill", undefined, "False");
117
+ xmlStream.leafNode("x:AutoLine", undefined, "False");
118
+ xmlStream.leafNode("x:TextVAlign", undefined, "Center");
119
+ // Linked cell
120
+ if (control.link) {
121
+ xmlStream.leafNode("x:FmlaLink", undefined, control.link);
122
+ }
123
+ // 3D appearance
124
+ if (control.noThreeD) {
125
+ xmlStream.leafNode("x:NoThreeD");
126
+ }
127
+ // Checked state (0 = unchecked, 1 = checked, 2 = mixed)
128
+ xmlStream.leafNode("x:Checked", undefined, String(FormCheckbox.getVmlCheckedValue(control)));
129
+ xmlStream.closeNode(); // x:ClientData
130
+ xmlStream.closeNode(); // v:shape
131
+ }
132
+ // Parsing - delegate to VmlShapeXform for notes
133
+ parseOpen(node) {
134
+ if (this.parser) {
135
+ this.parser.parseOpen(node);
136
+ return true;
137
+ }
138
+ switch (node.name) {
139
+ case this.tag:
140
+ this.reset();
141
+ this.model = {
142
+ comments: [],
143
+ formControls: []
144
+ };
145
+ break;
146
+ default:
147
+ this.parser = this.map[node.name];
148
+ if (this.parser) {
149
+ this.parser.parseOpen(node);
150
+ }
151
+ break;
152
+ }
153
+ return true;
154
+ }
155
+ parseText(text) {
156
+ if (this.parser) {
157
+ this.parser.parseText(text);
158
+ }
159
+ }
160
+ parseClose(name) {
161
+ if (this.parser) {
162
+ if (!this.parser.parseClose(name)) {
163
+ this.model.comments.push(this.parser.model);
164
+ this.parser = undefined;
165
+ }
166
+ return true;
167
+ }
168
+ switch (name) {
169
+ case this.tag:
170
+ return false;
171
+ default:
172
+ return true;
173
+ }
174
+ }
175
+ }
176
+ VmlDrawingXform.DRAWING_ATTRIBUTES = {
177
+ "xmlns:v": "urn:schemas-microsoft-com:vml",
178
+ "xmlns:o": "urn:schemas-microsoft-com:office:office",
179
+ "xmlns:x": "urn:schemas-microsoft-com:office:excel"
180
+ };
181
+ export { VmlDrawingXform };
@@ -26,7 +26,7 @@ import { ColBreaksXform } from "./col-breaks-xform.js";
26
26
  import { HeaderFooterXform } from "./header-footer-xform.js";
27
27
  import { ConditionalFormattingsXform } from "./cf/conditional-formattings-xform.js";
28
28
  import { ExtLstXform } from "./ext-lst-xform.js";
29
- import { commentsRelTargetFromWorksheet, drawingRelTargetFromWorksheet, mediaRelTargetFromRels, pivotTableRelTargetFromWorksheet, tableRelTargetFromWorksheet, vmlDrawingRelTargetFromWorksheet } from "../../../utils/ooxml-paths.js";
29
+ import { commentsRelTargetFromWorksheet, ctrlPropRelTargetFromWorksheet, drawingRelTargetFromWorksheet, mediaRelTargetFromRels, pivotTableRelTargetFromWorksheet, tableRelTargetFromWorksheet, vmlDrawingRelTargetFromWorksheet } from "../../../utils/ooxml-paths.js";
30
30
  const mergeRule = (rule, extRule) => {
31
31
  Object.keys(extRule).forEach(key => {
32
32
  const value = rule[key];
@@ -275,6 +275,29 @@ class WorkSheetXform extends BaseXform {
275
275
  Target: pivotTableRelTargetFromWorksheet(pivotTable.tableNumber)
276
276
  });
277
277
  });
278
+ // prepare form controls (legacy checkboxes)
279
+ // Form controls share the VML file with comments, but need separate ctrlProp relationships
280
+ if (model.formControls && model.formControls.length > 0) {
281
+ // If no comments, we need to add the VML drawing relationship for form controls
282
+ if (model.comments.length === 0) {
283
+ rels.push({
284
+ Id: nextRid(rels),
285
+ Type: RelType.VmlDrawing,
286
+ Target: vmlDrawingRelTargetFromWorksheet(model.id)
287
+ });
288
+ }
289
+ // Add ctrlProp relationships for each form control
290
+ for (const control of model.formControls) {
291
+ const globalCtrlPropId = options.formControlRefs.length + 1;
292
+ control.ctrlPropId = globalCtrlPropId;
293
+ rels.push({
294
+ Id: nextRid(rels),
295
+ Type: RelType.CtrlProp,
296
+ Target: ctrlPropRelTargetFromWorksheet(globalCtrlPropId)
297
+ });
298
+ options.formControlRefs.push(globalCtrlPropId);
299
+ }
300
+ }
278
301
  // prepare ext items
279
302
  this.map.extLst.prepare(model, options);
280
303
  }
@@ -93,6 +93,7 @@ declare class XLSX {
93
93
  PivotCacheRecords: string;
94
94
  PivotTable: string;
95
95
  FeaturePropertyBag: string;
96
+ CtrlProp: string;
96
97
  };
97
98
  constructor(workbook: Workbook);
98
99
  /**
@@ -24,7 +24,8 @@ import { PivotCacheRecordsXform } from "./xform/pivot-table/pivot-cache-records-
24
24
  import { PivotCacheDefinitionXform } from "./xform/pivot-table/pivot-cache-definition-xform.js";
25
25
  import { PivotTableXform } from "./xform/pivot-table/pivot-table-xform.js";
26
26
  import { CommentsXform } from "./xform/comment/comments-xform.js";
27
- import { VmlNotesXform } from "./xform/comment/vml-notes-xform.js";
27
+ import { VmlDrawingXform } from "./xform/drawing/vml-drawing-xform.js";
28
+ import { CtrlPropXform } from "./xform/drawing/ctrl-prop-xform.js";
28
29
  import { theme1Xml } from "./xml/theme1.js";
29
30
  import { RelType } from "./rel-type.js";
30
31
  import { StreamBuf } from "../utils/stream-buf.js";
@@ -32,7 +33,7 @@ import { bufferToString, base64ToUint8Array } from "../../../utils/utils.browser
32
33
  import { StreamingZip, ZipDeflateFile } from "../../archive/streaming-zip.js";
33
34
  import { ZipParser } from "../../archive/index.browser.js";
34
35
  import { PassThrough, concatUint8Arrays } from "../../stream/index.browser.js";
35
- import { commentsPath, commentsRelTargetFromWorksheetName, drawingPath, drawingRelsPath, OOXML_REL_TARGETS, pivotTableRelTargetFromWorksheetName, pivotCacheDefinitionRelTargetFromWorkbook, getCommentsIndexFromPath, getDrawingNameFromPath, getDrawingNameFromRelsPath, getMediaFilenameFromPath, mediaPath, getPivotCacheDefinitionNameFromPath, getPivotCacheDefinitionNameFromRelsPath, getPivotCacheRecordsNameFromPath, getPivotTableNameFromPath, getPivotTableNameFromRelsPath, pivotCacheDefinitionPath, pivotCacheDefinitionRelsPath, pivotCacheDefinitionRelTargetFromPivotTable, pivotCacheRecordsPath, pivotCacheRecordsRelTarget, pivotTablePath, pivotTableRelsPath, getTableNameFromPath, tablePath, tableRelTargetFromWorksheetName, themePath, getThemeNameFromPath, getVmlDrawingNameFromPath, getWorksheetNoFromWorksheetPath, getWorksheetNoFromWorksheetRelsPath, isBinaryEntryPath, normalizeZipPath, OOXML_PATHS, vmlDrawingRelTargetFromWorksheetName, vmlDrawingPath, worksheetPath, worksheetRelsPath } from "../utils/ooxml-paths.js";
36
+ import { commentsPath, commentsRelTargetFromWorksheetName, ctrlPropPath, drawingPath, drawingRelsPath, OOXML_REL_TARGETS, pivotTableRelTargetFromWorksheetName, pivotCacheDefinitionRelTargetFromWorkbook, getCommentsIndexFromPath, getDrawingNameFromPath, getDrawingNameFromRelsPath, getMediaFilenameFromPath, mediaPath, getPivotCacheDefinitionNameFromPath, getPivotCacheDefinitionNameFromRelsPath, getPivotCacheRecordsNameFromPath, getPivotTableNameFromPath, getPivotTableNameFromRelsPath, pivotCacheDefinitionPath, pivotCacheDefinitionRelsPath, pivotCacheDefinitionRelTargetFromPivotTable, pivotCacheRecordsPath, pivotCacheRecordsRelTarget, pivotTablePath, pivotTableRelsPath, getTableNameFromPath, tablePath, tableRelTargetFromWorksheetName, themePath, getThemeNameFromPath, getVmlDrawingNameFromPath, getWorksheetNoFromWorksheetPath, getWorksheetNoFromWorksheetRelsPath, isBinaryEntryPath, normalizeZipPath, OOXML_PATHS, vmlDrawingRelTargetFromWorksheetName, vmlDrawingPath, worksheetPath, worksheetRelsPath } from "../utils/ooxml-paths.js";
36
37
  class StreamingZipWriterAdapter {
37
38
  constructor(options) {
38
39
  this.events = new Map();
@@ -702,7 +703,7 @@ class XLSX {
702
703
  model.drawingRels[name] = relationships;
703
704
  }
704
705
  async _processVmlDrawingEntry(entry, model, name) {
705
- const xform = new VmlNotesXform();
706
+ const xform = new VmlDrawingXform();
706
707
  const vmlDrawing = await xform.parseStream(entry);
707
708
  model.vmlDrawings[vmlDrawingRelTargetFromWorksheetName(name)] = vmlDrawing;
708
709
  }
@@ -1028,7 +1029,8 @@ class XLSX {
1028
1029
  const worksheetXform = new WorkSheetXform();
1029
1030
  const relationshipsXform = new RelationshipsXform();
1030
1031
  const commentsXform = new CommentsXform();
1031
- const vmlNotesXform = new VmlNotesXform();
1032
+ const vmlDrawingXform = new VmlDrawingXform();
1033
+ const ctrlPropXform = new CtrlPropXform();
1032
1034
  model.worksheets.forEach((worksheet, index) => {
1033
1035
  const fileIndex = worksheet.fileIndex || index + 1;
1034
1036
  let xmlStream = new XmlStream();
@@ -1039,14 +1041,30 @@ class XLSX {
1039
1041
  relationshipsXform.render(xmlStream, worksheet.rels);
1040
1042
  zip.append(xmlStream.xml, { name: worksheetRelsPath(fileIndex) });
1041
1043
  }
1044
+ // Generate comments XML (separate from VML)
1042
1045
  if (worksheet.comments.length > 0) {
1043
1046
  xmlStream = new XmlStream();
1044
1047
  commentsXform.render(xmlStream, worksheet);
1045
1048
  zip.append(xmlStream.xml, { name: commentsPath(fileIndex) });
1049
+ }
1050
+ // Generate unified VML drawing (contains both notes and form controls)
1051
+ const hasComments = worksheet.comments.length > 0;
1052
+ const hasFormControls = worksheet.formControls && worksheet.formControls.length > 0;
1053
+ if (hasComments || hasFormControls) {
1046
1054
  xmlStream = new XmlStream();
1047
- vmlNotesXform.render(xmlStream, worksheet);
1055
+ vmlDrawingXform.render(xmlStream, {
1056
+ comments: hasComments ? worksheet.comments : [],
1057
+ formControls: hasFormControls ? worksheet.formControls : []
1058
+ });
1048
1059
  zip.append(xmlStream.xml, { name: vmlDrawingPath(fileIndex) });
1049
1060
  }
1061
+ // Generate ctrlProp files for form controls
1062
+ if (hasFormControls) {
1063
+ worksheet.formControls.forEach((control) => {
1064
+ const xml = ctrlPropXform.toXml(control);
1065
+ zip.append(xml, { name: ctrlPropPath(control.ctrlPropId) });
1066
+ });
1067
+ }
1050
1068
  });
1051
1069
  }
1052
1070
  addDrawings(zip, model) {
@@ -1152,6 +1170,7 @@ class XLSX {
1152
1170
  };
1153
1171
  worksheetOptions.drawings = model.drawings = [];
1154
1172
  worksheetOptions.commentRefs = model.commentRefs = [];
1173
+ worksheetOptions.formControlRefs = model.formControlRefs = [];
1155
1174
  let tableCount = 0;
1156
1175
  model.tables = [];
1157
1176
  model.worksheets.forEach((worksheet) => {
package/dist/cjs/index.js CHANGED
@@ -17,7 +17,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
17
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.CsvFormatterStream = exports.CsvParserStream = exports.WorksheetReader = exports.WorksheetWriter = exports.WorkbookReader = exports.WorkbookWriter = exports.DataValidations = exports.Table = exports.Image = exports.Range = exports.Cell = exports.Column = exports.Row = exports.Worksheet = exports.Workbook = void 0;
20
+ exports.CsvFormatterStream = exports.CsvParserStream = exports.WorksheetReader = exports.WorksheetWriter = exports.WorkbookReader = exports.WorkbookWriter = exports.FormCheckbox = exports.DataValidations = exports.Table = exports.Image = exports.Range = exports.Cell = exports.Column = exports.Row = exports.Worksheet = exports.Workbook = void 0;
21
21
  var workbook_1 = require("./modules/excel/workbook.js");
22
22
  Object.defineProperty(exports, "Workbook", { enumerable: true, get: function () { return workbook_1.Workbook; } });
23
23
  var worksheet_1 = require("./modules/excel/worksheet.js");
@@ -37,6 +37,8 @@ var table_1 = require("./modules/excel/table.js");
37
37
  Object.defineProperty(exports, "Table", { enumerable: true, get: function () { return table_1.Table; } });
38
38
  var data_validations_1 = require("./modules/excel/data-validations.js");
39
39
  Object.defineProperty(exports, "DataValidations", { enumerable: true, get: function () { return data_validations_1.DataValidations; } });
40
+ var form_control_1 = require("./modules/excel/form-control.js");
41
+ Object.defineProperty(exports, "FormCheckbox", { enumerable: true, get: function () { return form_control_1.FormCheckbox; } });
40
42
  // =============================================================================
41
43
  // Node.js Only: Streaming Classes
42
44
  // These can also be accessed via Workbook.createStreamWriter/createStreamReader