@cj-tech-master/excelts 4.1.0 → 4.2.0

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
@@ -12,9 +12,11 @@ export { Image } from "@excel/image";
12
12
  export * from "@excel/anchor";
13
13
  export { Table } from "@excel/table";
14
14
  export { DataValidations } from "@excel/data-validations";
15
+ export { FormCheckbox } from "@excel/form-control";
15
16
  export * from "@excel/enums";
16
17
  export * from "@excel/types";
17
18
  export type { PivotTable, PivotTableModel, PivotTableSource, CacheField, DataField, PivotTableSubtotal, ParsedCacheDefinition, ParsedCacheRecords } from "@excel/pivot-table";
19
+ export type { FormCheckboxModel, FormCheckboxOptions, FormControlRange, FormControlAnchor } from "@excel/form-control";
18
20
  import { WorkbookWriter } from "@excel/stream/workbook-writer.browser";
19
21
  import { WorkbookReader } from "@excel/stream/workbook-reader.browser";
20
22
  import { WorksheetWriter } from "@excel/stream/worksheet-writer";
@@ -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
  // =============================================================================
@@ -8,6 +8,7 @@ export { Image } from "@excel/image";
8
8
  export * from "@excel/anchor";
9
9
  export { Table } from "@excel/table";
10
10
  export { DataValidations } from "@excel/data-validations";
11
+ export { FormCheckbox } from "@excel/form-control";
11
12
  export { WorkbookWriter } from "@excel/stream/workbook-writer";
12
13
  export { WorkbookReader } from "@excel/stream/workbook-reader";
13
14
  export { WorksheetWriter } from "@excel/stream/worksheet-writer";
@@ -15,6 +16,7 @@ export { WorksheetReader } from "@excel/stream/worksheet-reader";
15
16
  export * from "@excel/enums";
16
17
  export * from "@excel/types";
17
18
  export type { PivotTable, PivotTableModel, PivotTableSource, CacheField, DataField, PivotTableSubtotal, ParsedCacheDefinition, ParsedCacheRecords } from "@excel/pivot-table";
19
+ export type { FormCheckboxModel, FormCheckboxOptions, FormControlRange, FormControlAnchor } from "@excel/form-control";
18
20
  export type { WorkbookReaderOptions, ParseEvent, SharedStringEvent, WorksheetReadyEvent, HyperlinksEvent } from "@excel/stream/workbook-reader";
19
21
  export type { WorksheetReaderOptions, WorksheetEvent, RowEvent, HyperlinkEvent, WorksheetHyperlink } from "@excel/stream/worksheet-reader";
20
22
  export type { WorkbookWriterOptions, ZipOptions, ZlibOptions } from "@excel/stream/workbook-writer";
@@ -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
@@ -0,0 +1,157 @@
1
+ import type { Worksheet } from "@excel/worksheet";
2
+ /**
3
+ * Form Control Checkbox - Legacy checkbox control compatible with Office 2007+ and WPS/LibreOffice
4
+ *
5
+ * Unlike the modern In-Cell Checkbox (which only works in Microsoft 365),
6
+ * Form Control Checkboxes are floating controls that work in virtually all
7
+ * spreadsheet applications.
8
+ */
9
+ /** EMU (English Metric Units) to pixels conversion factor at 96 DPI */
10
+ export declare const EMU_PER_PIXEL = 9525;
11
+ /** EMU to points conversion factor */
12
+ export declare const EMU_PER_POINT = 12700;
13
+ /** Anchor position for form control placement */
14
+ export interface FormControlAnchor {
15
+ /** Column index (0-based) */
16
+ col: number;
17
+ /** Column offset in EMUs (1 pixel ≈ 9525 EMUs at 96 DPI) */
18
+ colOff: number;
19
+ /** Row index (0-based) */
20
+ row: number;
21
+ /** Row offset in EMUs */
22
+ rowOff: number;
23
+ }
24
+ /** Checkbox state values */
25
+ export type CheckboxState = "Checked" | "Unchecked" | "Mixed";
26
+ /** Options for adding a form control checkbox */
27
+ export interface FormCheckboxOptions {
28
+ /** Cell reference where the checkbox value (TRUE/FALSE) will be stored */
29
+ link?: string;
30
+ /** Initial checked state */
31
+ checked?: boolean;
32
+ /** Label text displayed next to the checkbox */
33
+ text?: string;
34
+ /** Whether to use flat appearance (no 3D effect) */
35
+ noThreeD?: boolean;
36
+ /** Whether to print the checkbox */
37
+ print?: boolean;
38
+ }
39
+ /** Internal model for form control checkbox */
40
+ export interface FormCheckboxModel {
41
+ /** Unique shape ID (e.g., 1025, 1026, ...) */
42
+ shapeId: number;
43
+ /** Control property ID (rId in relationships) */
44
+ ctrlPropId: number;
45
+ /** Top-left anchor */
46
+ tl: FormControlAnchor;
47
+ /** Bottom-right anchor */
48
+ br: FormControlAnchor;
49
+ /** Cell link (e.g., "$A$1") */
50
+ link?: string;
51
+ /** Checked state */
52
+ checked: CheckboxState;
53
+ /** Label text */
54
+ text: string;
55
+ /** Use flat appearance */
56
+ noThreeD: boolean;
57
+ /** Print control */
58
+ print: boolean;
59
+ }
60
+ /** Range input for form control - can be a cell reference or position object */
61
+ export type FormControlRange = string | {
62
+ /** Top-left position */
63
+ tl: {
64
+ col: number;
65
+ row: number;
66
+ colOff?: number;
67
+ rowOff?: number;
68
+ } | string;
69
+ /** Bottom-right position (optional, defaults to reasonable size) */
70
+ br?: {
71
+ col: number;
72
+ row: number;
73
+ colOff?: number;
74
+ rowOff?: number;
75
+ } | string;
76
+ } | {
77
+ /** Start column (0-based) */
78
+ startCol: number;
79
+ /** Start row (0-based) */
80
+ startRow: number;
81
+ /** End column (0-based) */
82
+ endCol: number;
83
+ /** End row (0-based) */
84
+ endRow: number;
85
+ /** Column offset from start in EMUs */
86
+ startColOff?: number;
87
+ /** Row offset from start in EMUs */
88
+ startRowOff?: number;
89
+ /** Column offset from end in EMUs */
90
+ endColOff?: number;
91
+ /** Row offset from end in EMUs */
92
+ endRowOff?: number;
93
+ };
94
+ declare class FormCheckbox {
95
+ worksheet: Worksheet;
96
+ model: FormCheckboxModel;
97
+ constructor(worksheet: Worksheet, range: FormControlRange, options?: FormCheckboxOptions);
98
+ /**
99
+ * Get the checked state
100
+ */
101
+ get checked(): boolean;
102
+ /**
103
+ * Set the checked state
104
+ */
105
+ set checked(value: boolean);
106
+ /**
107
+ * Get the linked cell address
108
+ */
109
+ get link(): string | undefined;
110
+ /**
111
+ * Set the linked cell address
112
+ */
113
+ set link(value: string | undefined);
114
+ /**
115
+ * Get the label text
116
+ */
117
+ get text(): string;
118
+ /**
119
+ * Set the label text
120
+ */
121
+ set text(value: string);
122
+ /**
123
+ * Convert cell reference to absolute format (e.g., "A1" -> "$A$1")
124
+ */
125
+ private _toAbsoluteRef;
126
+ /**
127
+ * Parse range input into anchor positions
128
+ */
129
+ private _parseRange;
130
+ /**
131
+ * Convert anchor to VML anchor string format
132
+ * Format: "fromCol, fromColOff, fromRow, fromRowOff, toCol, toColOff, toRow, toRowOff"
133
+ * VML uses pixels for offsets
134
+ */
135
+ getVmlAnchor(): string;
136
+ /**
137
+ * Get VML style string for positioning
138
+ */
139
+ getVmlStyle(): string;
140
+ /**
141
+ * Get the numeric checked value for VML (0, 1, or 2)
142
+ */
143
+ getVmlCheckedValue(): number;
144
+ /**
145
+ * Convert anchor to VML anchor string format from model
146
+ */
147
+ static getVmlAnchor(model: FormCheckboxModel): string;
148
+ /**
149
+ * Get VML style string for positioning from model
150
+ */
151
+ static getVmlStyle(model: FormCheckboxModel): string;
152
+ /**
153
+ * Get the numeric checked value for VML from model (0, 1, or 2)
154
+ */
155
+ static getVmlCheckedValue(model: FormCheckboxModel): number;
156
+ }
157
+ export { FormCheckbox };
@@ -0,0 +1,267 @@
1
+ import { colCache } from "./utils/col-cache.js";
2
+ /**
3
+ * Form Control Checkbox - Legacy checkbox control compatible with Office 2007+ and WPS/LibreOffice
4
+ *
5
+ * Unlike the modern In-Cell Checkbox (which only works in Microsoft 365),
6
+ * Form Control Checkboxes are floating controls that work in virtually all
7
+ * spreadsheet applications.
8
+ */
9
+ // ============================================================================
10
+ // Constants (exported for use by xforms)
11
+ // ============================================================================
12
+ /** EMU (English Metric Units) to pixels conversion factor at 96 DPI */
13
+ export const EMU_PER_PIXEL = 9525;
14
+ /** EMU to points conversion factor */
15
+ export const EMU_PER_POINT = 12700;
16
+ /** Default column offset in EMUs (~15 pixels) */
17
+ const DEFAULT_COL_OFF = 142875;
18
+ /** Default row offset in EMUs (~3 pixels) */
19
+ const DEFAULT_ROW_OFF = 28575;
20
+ /** Default end column offset in EMUs (~29 pixels) */
21
+ const DEFAULT_END_COL_OFF = 276225;
22
+ /** Default end row offset in EMUs (~20 pixels) */
23
+ const DEFAULT_END_ROW_OFF = 190500;
24
+ // ============================================================================
25
+ // FormCheckbox Class
26
+ // ============================================================================
27
+ class FormCheckbox {
28
+ constructor(worksheet, range, options) {
29
+ this.worksheet = worksheet;
30
+ // Parse range to get anchors
31
+ const { tl, br } = this._parseRange(range);
32
+ // Generate shape ID (starting from 1025)
33
+ const existingCount = worksheet.formControls?.length || 0;
34
+ const shapeId = 1025 + existingCount;
35
+ // Parse link cell reference
36
+ let link;
37
+ if (options?.link) {
38
+ // Ensure absolute reference format
39
+ link = this._toAbsoluteRef(options.link);
40
+ }
41
+ // Note: ctrlPropId is set later in worksheet-xform.ts prepare() for global uniqueness
42
+ this.model = {
43
+ shapeId,
44
+ ctrlPropId: 0, // Placeholder, set during prepare()
45
+ tl,
46
+ br,
47
+ link,
48
+ checked: options?.checked ? "Checked" : "Unchecked",
49
+ text: options?.text ?? "",
50
+ noThreeD: options?.noThreeD ?? true,
51
+ print: options?.print ?? false
52
+ };
53
+ }
54
+ /**
55
+ * Get the checked state
56
+ */
57
+ get checked() {
58
+ return this.model.checked === "Checked";
59
+ }
60
+ /**
61
+ * Set the checked state
62
+ */
63
+ set checked(value) {
64
+ this.model.checked = value ? "Checked" : "Unchecked";
65
+ }
66
+ /**
67
+ * Get the linked cell address
68
+ */
69
+ get link() {
70
+ return this.model.link;
71
+ }
72
+ /**
73
+ * Set the linked cell address
74
+ */
75
+ set link(value) {
76
+ this.model.link = value ? this._toAbsoluteRef(value) : undefined;
77
+ }
78
+ /**
79
+ * Get the label text
80
+ */
81
+ get text() {
82
+ return this.model.text;
83
+ }
84
+ /**
85
+ * Set the label text
86
+ */
87
+ set text(value) {
88
+ this.model.text = value;
89
+ }
90
+ /**
91
+ * Convert cell reference to absolute format (e.g., "A1" -> "$A$1")
92
+ */
93
+ _toAbsoluteRef(ref) {
94
+ // If already absolute, return as-is
95
+ if (ref.includes("$")) {
96
+ return ref;
97
+ }
98
+ // Parse and convert
99
+ const addr = colCache.decodeAddress(ref);
100
+ return `$${colCache.n2l(addr.col)}$${addr.row}`;
101
+ }
102
+ /**
103
+ * Parse range input into anchor positions
104
+ */
105
+ _parseRange(range) {
106
+ let tl;
107
+ let br;
108
+ if (typeof range === "string") {
109
+ // Parse cell reference like "B2" or "B2:D3"
110
+ const decoded = colCache.decode(range);
111
+ if ("top" in decoded) {
112
+ // It's a range like "B2:D3"
113
+ tl = {
114
+ col: decoded.left - 1, // Convert to 0-based
115
+ colOff: DEFAULT_COL_OFF,
116
+ row: decoded.top - 1,
117
+ rowOff: DEFAULT_ROW_OFF
118
+ };
119
+ br = {
120
+ col: decoded.right - 1,
121
+ colOff: DEFAULT_END_COL_OFF,
122
+ row: decoded.bottom - 1,
123
+ rowOff: DEFAULT_END_ROW_OFF
124
+ };
125
+ }
126
+ else {
127
+ // Single cell reference - create default size checkbox
128
+ tl = {
129
+ col: decoded.col - 1,
130
+ colOff: DEFAULT_COL_OFF,
131
+ row: decoded.row - 1,
132
+ rowOff: DEFAULT_ROW_OFF
133
+ };
134
+ // Default size: about 2 columns wide, 1 row tall
135
+ br = {
136
+ col: decoded.col + 1,
137
+ colOff: DEFAULT_END_COL_OFF,
138
+ row: decoded.row,
139
+ rowOff: DEFAULT_END_ROW_OFF
140
+ };
141
+ }
142
+ }
143
+ else if ("startCol" in range) {
144
+ // startCol/startRow/endCol/endRow format (0-based)
145
+ tl = {
146
+ col: range.startCol,
147
+ colOff: range.startColOff ?? DEFAULT_COL_OFF,
148
+ row: range.startRow,
149
+ rowOff: range.startRowOff ?? DEFAULT_ROW_OFF
150
+ };
151
+ br = {
152
+ col: range.endCol,
153
+ colOff: range.endColOff ?? DEFAULT_END_COL_OFF,
154
+ row: range.endRow,
155
+ rowOff: range.endRowOff ?? DEFAULT_END_ROW_OFF
156
+ };
157
+ }
158
+ else {
159
+ // Object format with tl/br
160
+ if (typeof range.tl === "string") {
161
+ const decoded = colCache.decodeAddress(range.tl);
162
+ tl = {
163
+ col: decoded.col - 1,
164
+ colOff: DEFAULT_COL_OFF,
165
+ row: decoded.row - 1,
166
+ rowOff: DEFAULT_ROW_OFF
167
+ };
168
+ }
169
+ else {
170
+ tl = {
171
+ col: range.tl.col,
172
+ colOff: range.tl.colOff ?? DEFAULT_COL_OFF,
173
+ row: range.tl.row,
174
+ rowOff: range.tl.rowOff ?? DEFAULT_ROW_OFF
175
+ };
176
+ }
177
+ if (range.br) {
178
+ if (typeof range.br === "string") {
179
+ const decoded = colCache.decodeAddress(range.br);
180
+ br = {
181
+ col: decoded.col - 1,
182
+ colOff: DEFAULT_END_COL_OFF,
183
+ row: decoded.row - 1,
184
+ rowOff: DEFAULT_END_ROW_OFF
185
+ };
186
+ }
187
+ else {
188
+ br = {
189
+ col: range.br.col,
190
+ colOff: range.br.colOff ?? DEFAULT_END_COL_OFF,
191
+ row: range.br.row,
192
+ rowOff: range.br.rowOff ?? DEFAULT_END_ROW_OFF
193
+ };
194
+ }
195
+ }
196
+ else {
197
+ // Default size
198
+ br = {
199
+ col: tl.col + 2,
200
+ colOff: DEFAULT_END_COL_OFF,
201
+ row: tl.row + 1,
202
+ rowOff: DEFAULT_END_ROW_OFF
203
+ };
204
+ }
205
+ }
206
+ return { tl, br };
207
+ }
208
+ // =========================================================================
209
+ // Instance methods - delegate to static methods
210
+ // =========================================================================
211
+ /**
212
+ * Convert anchor to VML anchor string format
213
+ * Format: "fromCol, fromColOff, fromRow, fromRowOff, toCol, toColOff, toRow, toRowOff"
214
+ * VML uses pixels for offsets
215
+ */
216
+ getVmlAnchor() {
217
+ return FormCheckbox.getVmlAnchor(this.model);
218
+ }
219
+ /**
220
+ * Get VML style string for positioning
221
+ */
222
+ getVmlStyle() {
223
+ return FormCheckbox.getVmlStyle(this.model);
224
+ }
225
+ /**
226
+ * Get the numeric checked value for VML (0, 1, or 2)
227
+ */
228
+ getVmlCheckedValue() {
229
+ return FormCheckbox.getVmlCheckedValue(this.model);
230
+ }
231
+ // =========================================================================
232
+ // Static utility methods - can be used with FormCheckboxModel directly
233
+ // =========================================================================
234
+ /**
235
+ * Convert anchor to VML anchor string format from model
236
+ */
237
+ static getVmlAnchor(model) {
238
+ const { tl, br } = model;
239
+ const tlColOff = Math.round(tl.colOff / EMU_PER_PIXEL);
240
+ const tlRowOff = Math.round(tl.rowOff / EMU_PER_PIXEL);
241
+ const brColOff = Math.round(br.colOff / EMU_PER_PIXEL);
242
+ const brRowOff = Math.round(br.rowOff / EMU_PER_PIXEL);
243
+ return `${tl.col}, ${tlColOff}, ${tl.row}, ${tlRowOff}, ${br.col}, ${brColOff}, ${br.row}, ${brRowOff}`;
244
+ }
245
+ /**
246
+ * Get VML style string for positioning from model
247
+ */
248
+ static getVmlStyle(model) {
249
+ const marginLeft = Math.round(model.tl.colOff / EMU_PER_POINT);
250
+ const marginTop = Math.round(model.tl.rowOff / EMU_PER_POINT);
251
+ return `position:absolute;margin-left:${marginLeft}pt;margin-top:${marginTop}pt;width:96pt;height:18pt;z-index:1;visibility:visible`;
252
+ }
253
+ /**
254
+ * Get the numeric checked value for VML from model (0, 1, or 2)
255
+ */
256
+ static getVmlCheckedValue(model) {
257
+ switch (model.checked) {
258
+ case "Checked":
259
+ return 1;
260
+ case "Mixed":
261
+ return 2;
262
+ default:
263
+ return 0;
264
+ }
265
+ }
266
+ }
267
+ export { FormCheckbox };
@@ -64,3 +64,5 @@ export declare function pivotTableRelTargetFromWorksheetName(pivotName: string):
64
64
  export declare function tableRelTargetFromWorksheet(target: string): string;
65
65
  export declare function tableRelTargetFromWorksheetName(name: string): string;
66
66
  export declare function mediaRelTargetFromRels(filename: string): string;
67
+ export declare function ctrlPropPath(id: number | string): string;
68
+ export declare function ctrlPropRelTargetFromWorksheet(id: number | string): string;
@@ -209,3 +209,11 @@ export function mediaRelTargetFromRels(filename) {
209
209
  // Target from a rels file located under xl/*/_rels (base is one level deeper than xl/)
210
210
  return `../media/${filename}`;
211
211
  }
212
+ // Form Control (ctrlProps) path functions
213
+ export function ctrlPropPath(id) {
214
+ return `xl/ctrlProps/ctrlProp${id}.xml`;
215
+ }
216
+ export function ctrlPropRelTargetFromWorksheet(id) {
217
+ // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
218
+ return `../ctrlProps/ctrlProp${id}.xml`;
219
+ }
@@ -5,6 +5,7 @@ import type { Cell, FormulaResult } from "@excel/cell";
5
5
  import { Image, type ImageModel } from "@excel/image";
6
6
  import { Table, type TableModel } from "@excel/table";
7
7
  import { DataValidations } from "@excel/data-validations";
8
+ import { FormCheckbox, type FormCheckboxModel, type FormCheckboxOptions, type FormControlRange } from "@excel/form-control";
8
9
  import { type PivotTable, type PivotTableModel } from "@excel/pivot-table";
9
10
  import type { Workbook } from "@excel/workbook";
10
11
  import type { AddImageRange, AutoFilter, CellValue, ColBreak, ConditionalFormattingOptions, DataValidation, RowBreak, RowValues, TableProperties, WorksheetProperties, WorksheetView } from "@excel/types";
@@ -104,6 +105,7 @@ interface WorksheetModel {
104
105
  tables: TableModel[];
105
106
  pivotTables: PivotTable[];
106
107
  conditionalFormattings: ConditionalFormattingOptions[];
108
+ formControls: FormCheckboxModel[];
107
109
  cols?: ColumnModel[];
108
110
  rows?: RowModel[];
109
111
  dimensions?: Range;
@@ -135,6 +137,7 @@ declare class Worksheet {
135
137
  };
136
138
  pivotTables: PivotTable[];
137
139
  conditionalFormattings: ConditionalFormattingOptions[];
140
+ formControls: FormCheckbox[];
138
141
  private _headerRowCount?;
139
142
  constructor(options: WorksheetOptions);
140
143
  get name(): string;
@@ -295,6 +298,35 @@ declare class Worksheet {
295
298
  */
296
299
  addBackgroundImage(imageId: string | number): void;
297
300
  getBackgroundImageId(): string | undefined;
301
+ /**
302
+ * Add a form control checkbox to the worksheet.
303
+ *
304
+ * Form control checkboxes are the legacy style that work in Office 2007+,
305
+ * WPS Office, LibreOffice, and other spreadsheet applications.
306
+ *
307
+ * Unlike modern in-cell checkboxes (which only work in Microsoft 365),
308
+ * form control checkboxes are floating controls positioned over cells.
309
+ *
310
+ * @param range - Cell reference (e.g., "B2") or range (e.g., "B2:D3") for positioning
311
+ * @param options - Checkbox options
312
+ * @returns The created FormCheckbox instance
313
+ *
314
+ * @example
315
+ * // Simple checkbox at B2
316
+ * ws.addFormCheckbox("B2");
317
+ *
318
+ * // Checkbox with label and linked cell
319
+ * ws.addFormCheckbox("B2:D3", {
320
+ * text: "Accept terms",
321
+ * link: "A2",
322
+ * checked: false
323
+ * });
324
+ */
325
+ addFormCheckbox(range: FormControlRange, options?: FormCheckboxOptions): FormCheckbox;
326
+ /**
327
+ * Get all form control checkboxes in the worksheet
328
+ */
329
+ getFormCheckboxes(): FormCheckbox[];
298
330
  /**
299
331
  * Protect the worksheet with optional password and options
300
332
  */
@@ -6,6 +6,7 @@ import { Enums } from "./enums.js";
6
6
  import { Image } from "./image.js";
7
7
  import { Table } from "./table.js";
8
8
  import { DataValidations } from "./data-validations.js";
9
+ import { FormCheckbox } from "./form-control.js";
9
10
  import { Encryptor } from "./utils/encryptor.browser.js";
10
11
  import { uint8ArrayToBase64 } from "../../utils/utils.browser.js";
11
12
  import { makePivotTable } from "./pivot-table.js";
@@ -92,6 +93,8 @@ class Worksheet {
92
93
  this.tables = {};
93
94
  this.pivotTables = [];
94
95
  this.conditionalFormattings = [];
96
+ // for form controls (legacy checkboxes, etc.)
97
+ this.formControls = [];
95
98
  }
96
99
  get name() {
97
100
  return this._name;
@@ -745,6 +748,43 @@ class Worksheet {
745
748
  return image && image.imageId;
746
749
  }
747
750
  // =========================================================================
751
+ // Form Controls (Legacy Checkboxes)
752
+ /**
753
+ * Add a form control checkbox to the worksheet.
754
+ *
755
+ * Form control checkboxes are the legacy style that work in Office 2007+,
756
+ * WPS Office, LibreOffice, and other spreadsheet applications.
757
+ *
758
+ * Unlike modern in-cell checkboxes (which only work in Microsoft 365),
759
+ * form control checkboxes are floating controls positioned over cells.
760
+ *
761
+ * @param range - Cell reference (e.g., "B2") or range (e.g., "B2:D3") for positioning
762
+ * @param options - Checkbox options
763
+ * @returns The created FormCheckbox instance
764
+ *
765
+ * @example
766
+ * // Simple checkbox at B2
767
+ * ws.addFormCheckbox("B2");
768
+ *
769
+ * // Checkbox with label and linked cell
770
+ * ws.addFormCheckbox("B2:D3", {
771
+ * text: "Accept terms",
772
+ * link: "A2",
773
+ * checked: false
774
+ * });
775
+ */
776
+ addFormCheckbox(range, options) {
777
+ const checkbox = new FormCheckbox(this, range, options);
778
+ this.formControls.push(checkbox);
779
+ return checkbox;
780
+ }
781
+ /**
782
+ * Get all form control checkboxes in the worksheet
783
+ */
784
+ getFormCheckboxes() {
785
+ return this.formControls;
786
+ }
787
+ // =========================================================================
748
788
  // Worksheet Protection
749
789
  /**
750
790
  * Protect the worksheet with optional password and options
@@ -853,7 +893,8 @@ class Worksheet {
853
893
  sheetProtection: this.sheetProtection,
854
894
  tables: Object.values(this.tables).map(table => table.model),
855
895
  pivotTables: this.pivotTables,
856
- conditionalFormattings: this.conditionalFormattings
896
+ conditionalFormattings: this.conditionalFormattings,
897
+ formControls: this.formControls.map(fc => fc.model)
857
898
  };
858
899
  // =================================================
859
900
  // columns
@@ -919,6 +960,8 @@ class Worksheet {
919
960
  }, {});
920
961
  this.pivotTables = value.pivotTables;
921
962
  this.conditionalFormattings = value.conditionalFormattings;
963
+ // Form controls are currently write-only (not parsed from XLSX)
964
+ this.formControls = [];
922
965
  }
923
966
  }
924
967
  export { Worksheet };
@@ -16,5 +16,6 @@ declare const RelType: {
16
16
  PivotCacheRecords: string;
17
17
  PivotTable: string;
18
18
  FeaturePropertyBag: string;
19
+ CtrlProp: string;
19
20
  };
20
21
  export { RelType };
@@ -15,6 +15,7 @@ const RelType = {
15
15
  PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
16
16
  PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
17
17
  PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
18
- FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"
18
+ FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag",
19
+ CtrlProp: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp"
19
20
  };
20
21
  export { RelType };
@@ -1,6 +1,6 @@
1
1
  import { XmlStream } from "../../../utils/xml-stream.js";
2
2
  import { BaseXform } from "../base-xform.js";
3
- import { OOXML_PATHS, commentsPathFromName, drawingPath, pivotCacheDefinitionPath, pivotCacheRecordsPath, pivotTablePath, tablePath, toContentTypesPartName, worksheetPath } from "../../../utils/ooxml-paths.js";
3
+ import { OOXML_PATHS, commentsPathFromName, ctrlPropPath, drawingPath, pivotCacheDefinitionPath, pivotCacheRecordsPath, pivotTablePath, tablePath, toContentTypesPartName, worksheetPath } from "../../../utils/ooxml-paths.js";
4
4
  // used for rendering the [Content_Types].xml file
5
5
  // not used for parsing
6
6
  class ContentTypesXform extends BaseXform {
@@ -105,6 +105,22 @@ class ContentTypesXform extends BaseXform {
105
105
  });
106
106
  });
107
107
  }
108
+ // Add form control (ctrlProps) content types
109
+ if (model.formControlRefs) {
110
+ // Ensure vml extension is declared (may already be declared for comments)
111
+ if (!model.commentRefs) {
112
+ xmlStream.leafNode("Default", {
113
+ Extension: "vml",
114
+ ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
115
+ });
116
+ }
117
+ for (const ctrlPropId of model.formControlRefs) {
118
+ xmlStream.leafNode("Override", {
119
+ PartName: toContentTypesPartName(ctrlPropPath(ctrlPropId)),
120
+ ContentType: "application/vnd.ms-excel.controlproperties+xml"
121
+ });
122
+ }
123
+ }
108
124
  xmlStream.leafNode("Override", {
109
125
  PartName: toContentTypesPartName(OOXML_PATHS.docPropsCore),
110
126
  ContentType: "application/vnd.openxmlformats-package.core-properties+xml"