@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,270 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FormCheckbox = exports.EMU_PER_POINT = exports.EMU_PER_PIXEL = void 0;
4
+ const col_cache_1 = require("./utils/col-cache.js");
5
+ /**
6
+ * Form Control Checkbox - Legacy checkbox control compatible with Office 2007+ and WPS/LibreOffice
7
+ *
8
+ * Unlike the modern In-Cell Checkbox (which only works in Microsoft 365),
9
+ * Form Control Checkboxes are floating controls that work in virtually all
10
+ * spreadsheet applications.
11
+ */
12
+ // ============================================================================
13
+ // Constants (exported for use by xforms)
14
+ // ============================================================================
15
+ /** EMU (English Metric Units) to pixels conversion factor at 96 DPI */
16
+ exports.EMU_PER_PIXEL = 9525;
17
+ /** EMU to points conversion factor */
18
+ exports.EMU_PER_POINT = 12700;
19
+ /** Default column offset in EMUs (~15 pixels) */
20
+ const DEFAULT_COL_OFF = 142875;
21
+ /** Default row offset in EMUs (~3 pixels) */
22
+ const DEFAULT_ROW_OFF = 28575;
23
+ /** Default end column offset in EMUs (~29 pixels) */
24
+ const DEFAULT_END_COL_OFF = 276225;
25
+ /** Default end row offset in EMUs (~20 pixels) */
26
+ const DEFAULT_END_ROW_OFF = 190500;
27
+ // ============================================================================
28
+ // FormCheckbox Class
29
+ // ============================================================================
30
+ class FormCheckbox {
31
+ constructor(worksheet, range, options) {
32
+ this.worksheet = worksheet;
33
+ // Parse range to get anchors
34
+ const { tl, br } = this._parseRange(range);
35
+ // Generate shape ID (starting from 1025)
36
+ const existingCount = worksheet.formControls?.length || 0;
37
+ const shapeId = 1025 + existingCount;
38
+ // Parse link cell reference
39
+ let link;
40
+ if (options?.link) {
41
+ // Ensure absolute reference format
42
+ link = this._toAbsoluteRef(options.link);
43
+ }
44
+ // Note: ctrlPropId is set later in worksheet-xform.ts prepare() for global uniqueness
45
+ this.model = {
46
+ shapeId,
47
+ ctrlPropId: 0, // Placeholder, set during prepare()
48
+ tl,
49
+ br,
50
+ link,
51
+ checked: options?.checked ? "Checked" : "Unchecked",
52
+ text: options?.text ?? "",
53
+ noThreeD: options?.noThreeD ?? true,
54
+ print: options?.print ?? false
55
+ };
56
+ }
57
+ /**
58
+ * Get the checked state
59
+ */
60
+ get checked() {
61
+ return this.model.checked === "Checked";
62
+ }
63
+ /**
64
+ * Set the checked state
65
+ */
66
+ set checked(value) {
67
+ this.model.checked = value ? "Checked" : "Unchecked";
68
+ }
69
+ /**
70
+ * Get the linked cell address
71
+ */
72
+ get link() {
73
+ return this.model.link;
74
+ }
75
+ /**
76
+ * Set the linked cell address
77
+ */
78
+ set link(value) {
79
+ this.model.link = value ? this._toAbsoluteRef(value) : undefined;
80
+ }
81
+ /**
82
+ * Get the label text
83
+ */
84
+ get text() {
85
+ return this.model.text;
86
+ }
87
+ /**
88
+ * Set the label text
89
+ */
90
+ set text(value) {
91
+ this.model.text = value;
92
+ }
93
+ /**
94
+ * Convert cell reference to absolute format (e.g., "A1" -> "$A$1")
95
+ */
96
+ _toAbsoluteRef(ref) {
97
+ // If already absolute, return as-is
98
+ if (ref.includes("$")) {
99
+ return ref;
100
+ }
101
+ // Parse and convert
102
+ const addr = col_cache_1.colCache.decodeAddress(ref);
103
+ return `$${col_cache_1.colCache.n2l(addr.col)}$${addr.row}`;
104
+ }
105
+ /**
106
+ * Parse range input into anchor positions
107
+ */
108
+ _parseRange(range) {
109
+ let tl;
110
+ let br;
111
+ if (typeof range === "string") {
112
+ // Parse cell reference like "B2" or "B2:D3"
113
+ const decoded = col_cache_1.colCache.decode(range);
114
+ if ("top" in decoded) {
115
+ // It's a range like "B2:D3"
116
+ tl = {
117
+ col: decoded.left - 1, // Convert to 0-based
118
+ colOff: DEFAULT_COL_OFF,
119
+ row: decoded.top - 1,
120
+ rowOff: DEFAULT_ROW_OFF
121
+ };
122
+ br = {
123
+ col: decoded.right - 1,
124
+ colOff: DEFAULT_END_COL_OFF,
125
+ row: decoded.bottom - 1,
126
+ rowOff: DEFAULT_END_ROW_OFF
127
+ };
128
+ }
129
+ else {
130
+ // Single cell reference - create default size checkbox
131
+ tl = {
132
+ col: decoded.col - 1,
133
+ colOff: DEFAULT_COL_OFF,
134
+ row: decoded.row - 1,
135
+ rowOff: DEFAULT_ROW_OFF
136
+ };
137
+ // Default size: about 2 columns wide, 1 row tall
138
+ br = {
139
+ col: decoded.col + 1,
140
+ colOff: DEFAULT_END_COL_OFF,
141
+ row: decoded.row,
142
+ rowOff: DEFAULT_END_ROW_OFF
143
+ };
144
+ }
145
+ }
146
+ else if ("startCol" in range) {
147
+ // startCol/startRow/endCol/endRow format (0-based)
148
+ tl = {
149
+ col: range.startCol,
150
+ colOff: range.startColOff ?? DEFAULT_COL_OFF,
151
+ row: range.startRow,
152
+ rowOff: range.startRowOff ?? DEFAULT_ROW_OFF
153
+ };
154
+ br = {
155
+ col: range.endCol,
156
+ colOff: range.endColOff ?? DEFAULT_END_COL_OFF,
157
+ row: range.endRow,
158
+ rowOff: range.endRowOff ?? DEFAULT_END_ROW_OFF
159
+ };
160
+ }
161
+ else {
162
+ // Object format with tl/br
163
+ if (typeof range.tl === "string") {
164
+ const decoded = col_cache_1.colCache.decodeAddress(range.tl);
165
+ tl = {
166
+ col: decoded.col - 1,
167
+ colOff: DEFAULT_COL_OFF,
168
+ row: decoded.row - 1,
169
+ rowOff: DEFAULT_ROW_OFF
170
+ };
171
+ }
172
+ else {
173
+ tl = {
174
+ col: range.tl.col,
175
+ colOff: range.tl.colOff ?? DEFAULT_COL_OFF,
176
+ row: range.tl.row,
177
+ rowOff: range.tl.rowOff ?? DEFAULT_ROW_OFF
178
+ };
179
+ }
180
+ if (range.br) {
181
+ if (typeof range.br === "string") {
182
+ const decoded = col_cache_1.colCache.decodeAddress(range.br);
183
+ br = {
184
+ col: decoded.col - 1,
185
+ colOff: DEFAULT_END_COL_OFF,
186
+ row: decoded.row - 1,
187
+ rowOff: DEFAULT_END_ROW_OFF
188
+ };
189
+ }
190
+ else {
191
+ br = {
192
+ col: range.br.col,
193
+ colOff: range.br.colOff ?? DEFAULT_END_COL_OFF,
194
+ row: range.br.row,
195
+ rowOff: range.br.rowOff ?? DEFAULT_END_ROW_OFF
196
+ };
197
+ }
198
+ }
199
+ else {
200
+ // Default size
201
+ br = {
202
+ col: tl.col + 2,
203
+ colOff: DEFAULT_END_COL_OFF,
204
+ row: tl.row + 1,
205
+ rowOff: DEFAULT_END_ROW_OFF
206
+ };
207
+ }
208
+ }
209
+ return { tl, br };
210
+ }
211
+ // =========================================================================
212
+ // Instance methods - delegate to static methods
213
+ // =========================================================================
214
+ /**
215
+ * Convert anchor to VML anchor string format
216
+ * Format: "fromCol, fromColOff, fromRow, fromRowOff, toCol, toColOff, toRow, toRowOff"
217
+ * VML uses pixels for offsets
218
+ */
219
+ getVmlAnchor() {
220
+ return FormCheckbox.getVmlAnchor(this.model);
221
+ }
222
+ /**
223
+ * Get VML style string for positioning
224
+ */
225
+ getVmlStyle() {
226
+ return FormCheckbox.getVmlStyle(this.model);
227
+ }
228
+ /**
229
+ * Get the numeric checked value for VML (0, 1, or 2)
230
+ */
231
+ getVmlCheckedValue() {
232
+ return FormCheckbox.getVmlCheckedValue(this.model);
233
+ }
234
+ // =========================================================================
235
+ // Static utility methods - can be used with FormCheckboxModel directly
236
+ // =========================================================================
237
+ /**
238
+ * Convert anchor to VML anchor string format from model
239
+ */
240
+ static getVmlAnchor(model) {
241
+ const { tl, br } = model;
242
+ const tlColOff = Math.round(tl.colOff / exports.EMU_PER_PIXEL);
243
+ const tlRowOff = Math.round(tl.rowOff / exports.EMU_PER_PIXEL);
244
+ const brColOff = Math.round(br.colOff / exports.EMU_PER_PIXEL);
245
+ const brRowOff = Math.round(br.rowOff / exports.EMU_PER_PIXEL);
246
+ return `${tl.col}, ${tlColOff}, ${tl.row}, ${tlRowOff}, ${br.col}, ${brColOff}, ${br.row}, ${brRowOff}`;
247
+ }
248
+ /**
249
+ * Get VML style string for positioning from model
250
+ */
251
+ static getVmlStyle(model) {
252
+ const marginLeft = Math.round(model.tl.colOff / exports.EMU_PER_POINT);
253
+ const marginTop = Math.round(model.tl.rowOff / exports.EMU_PER_POINT);
254
+ return `position:absolute;margin-left:${marginLeft}pt;margin-top:${marginTop}pt;width:96pt;height:18pt;z-index:1;visibility:visible`;
255
+ }
256
+ /**
257
+ * Get the numeric checked value for VML from model (0, 1, or 2)
258
+ */
259
+ static getVmlCheckedValue(model) {
260
+ switch (model.checked) {
261
+ case "Checked":
262
+ return 1;
263
+ case "Mixed":
264
+ return 2;
265
+ default:
266
+ return 0;
267
+ }
268
+ }
269
+ }
270
+ exports.FormCheckbox = FormCheckbox;
@@ -23,6 +23,7 @@ const content_types_xform_1 = require("../xlsx/xform/core/content-types-xform.js
23
23
  const app_xform_1 = require("../xlsx/xform/core/app-xform.js");
24
24
  const workbook_xform_1 = require("../xlsx/xform/book/workbook-xform.js");
25
25
  const shared_strings_xform_1 = require("../xlsx/xform/strings/shared-strings-xform.js");
26
+ const feature_property_bag_xform_1 = require("../xlsx/xform/core/feature-property-bag-xform.js");
26
27
  const theme1_1 = require("../xlsx/xml/theme1.js");
27
28
  const _stream_1 = require("../../stream/index.js");
28
29
  const ooxml_paths_1 = require("../utils/ooxml-paths.js");
@@ -137,6 +138,7 @@ class WorkbookWriterBase {
137
138
  this.addCore(),
138
139
  this.addSharedStrings(),
139
140
  this.addStyles(),
141
+ this.addFeaturePropertyBag(),
140
142
  this.addWorkbookRels()
141
143
  ]);
142
144
  await this.addWorkbook();
@@ -229,7 +231,8 @@ class WorkbookWriterBase {
229
231
  worksheets: this._worksheets.filter(Boolean),
230
232
  sharedStrings: this.sharedStrings,
231
233
  commentRefs: this.commentRefs,
232
- media: this.media
234
+ media: this.media,
235
+ hasCheckboxes: this.styles.hasCheckboxes
233
236
  };
234
237
  const xform = new content_types_xform_1.ContentTypesXform();
235
238
  this._addFile(xform.toXml(model), ooxml_paths_1.OOXML_PATHS.contentTypes);
@@ -283,6 +286,13 @@ class WorkbookWriterBase {
283
286
  }
284
287
  return Promise.resolve();
285
288
  }
289
+ addFeaturePropertyBag() {
290
+ if (this.styles.hasCheckboxes) {
291
+ const xform = new feature_property_bag_xform_1.FeaturePropertyBagXform();
292
+ this._addFile(xform.toXml({}), ooxml_paths_1.OOXML_PATHS.xlFeaturePropertyBag);
293
+ }
294
+ return Promise.resolve();
295
+ }
286
296
  addWorkbookRels() {
287
297
  let count = 1;
288
298
  const relationships = [
@@ -296,6 +306,14 @@ class WorkbookWriterBase {
296
306
  Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookSharedStrings
297
307
  });
298
308
  }
309
+ // Add FeaturePropertyBag relationship if checkboxes are used
310
+ if (this.styles.hasCheckboxes) {
311
+ relationships.push({
312
+ Id: `rId${count++}`,
313
+ Type: rel_type_1.RelType.FeaturePropertyBag,
314
+ Target: ooxml_paths_1.OOXML_REL_TARGETS.workbookFeaturePropertyBag
315
+ });
316
+ }
299
317
  this._worksheets.forEach(ws => {
300
318
  if (ws) {
301
319
  ws.rId = `rId${count++}`;
@@ -60,6 +60,26 @@ class Table {
60
60
  this.worksheet = worksheet;
61
61
  if (table) {
62
62
  this.table = table;
63
+ // When loading tables from xlsx, Excel stores table ranges and cell values in the worksheet,
64
+ // but may not embed row data into the table definition. Hydrate rows from the worksheet so
65
+ // table mutations (e.g. addRow) can correctly expand table ranges and serialize.
66
+ if (Array.isArray(table.rows) && table.rows.length === 0 && table.tableRef) {
67
+ const decoded = col_cache_1.colCache.decode(table.tableRef);
68
+ if ("dimensions" in decoded) {
69
+ const startRow = decoded.top + (table.headerRow === false ? 0 : 1);
70
+ const endRow = decoded.bottom - (table.totalsRow === true ? 1 : 0);
71
+ if (endRow >= startRow) {
72
+ for (let r = startRow; r <= endRow; r++) {
73
+ const row = worksheet.getRow(r);
74
+ const values = [];
75
+ for (let c = decoded.left; c <= decoded.right; c++) {
76
+ values.push(row.getCell(c).value);
77
+ }
78
+ table.rows.push(values);
79
+ }
80
+ }
81
+ }
82
+ }
63
83
  // check things are ok first
64
84
  this.validate();
65
85
  this.store();
@@ -136,9 +156,10 @@ class Table {
136
156
  const { row, col } = table.tl;
137
157
  assert(row > 0, "Table must be on valid row");
138
158
  assert(col > 0, "Table must be on valid col");
139
- const { width, filterHeight, tableHeight } = this;
140
- // autoFilterRef is a range that includes optional headers only
141
- table.autoFilterRef = col_cache_1.colCache.encode(row, col, row + filterHeight - 1, col + width - 1);
159
+ const { width, tableHeight } = this;
160
+ // autoFilterRef is a single-row range that targets the header row only.
161
+ // Excel uses this for filter buttons; including data rows can break filter rendering.
162
+ table.autoFilterRef = col_cache_1.colCache.encode(row, col, row, col + width - 1);
142
163
  // tableRef is a range that includes optional headers and totals
143
164
  table.tableRef = col_cache_1.colCache.encode(row, col, row + tableHeight - 1, col + width - 1);
144
165
  table.columns.forEach((column, i) => {
@@ -307,8 +328,9 @@ class Table {
307
328
  }
308
329
  }
309
330
  this.store();
331
+ this._cache = undefined;
310
332
  }
311
- addRow(values, rowNumber) {
333
+ addRow(values, rowNumber, options) {
312
334
  // Add a row of data, either insert at rowNumber or append
313
335
  this.cacheState();
314
336
  if (rowNumber === undefined) {
@@ -317,11 +339,17 @@ class Table {
317
339
  else {
318
340
  this.table.rows.splice(rowNumber, 0, values);
319
341
  }
342
+ if (options?.commit !== false) {
343
+ this.commit();
344
+ }
320
345
  }
321
- removeRows(rowIndex, count = 1) {
346
+ removeRows(rowIndex, count = 1, options) {
322
347
  // Remove a rows of data
323
348
  this.cacheState();
324
349
  this.table.rows.splice(rowIndex, count);
350
+ if (options?.commit !== false) {
351
+ this.commit();
352
+ }
325
353
  }
326
354
  getColumn(colIndex) {
327
355
  const column = this.table.columns[colIndex];
@@ -49,6 +49,8 @@ exports.pivotTableRelTargetFromWorksheetName = pivotTableRelTargetFromWorksheetN
49
49
  exports.tableRelTargetFromWorksheet = tableRelTargetFromWorksheet;
50
50
  exports.tableRelTargetFromWorksheetName = tableRelTargetFromWorksheetName;
51
51
  exports.mediaRelTargetFromRels = mediaRelTargetFromRels;
52
+ exports.ctrlPropPath = ctrlPropPath;
53
+ exports.ctrlPropRelTargetFromWorksheet = ctrlPropRelTargetFromWorksheet;
52
54
  exports.OOXML_PATHS = {
53
55
  contentTypes: "[Content_Types].xml",
54
56
  rootRels: "_rels/.rels",
@@ -58,7 +60,8 @@ exports.OOXML_PATHS = {
58
60
  xlWorkbookRels: "xl/_rels/workbook.xml.rels",
59
61
  xlSharedStrings: "xl/sharedStrings.xml",
60
62
  xlStyles: "xl/styles.xml",
61
- xlTheme1: "xl/theme/theme1.xml"
63
+ xlTheme1: "xl/theme/theme1.xml",
64
+ xlFeaturePropertyBag: "xl/featurePropertyBag/featurePropertyBag.xml"
62
65
  };
63
66
  const worksheetXmlRegex = /^xl\/worksheets\/sheet(\d+)[.]xml$/;
64
67
  const worksheetRelsXmlRegex = /^xl\/worksheets\/_rels\/sheet(\d+)[.]xml[.]rels$/;
@@ -213,7 +216,8 @@ exports.OOXML_REL_TARGETS = {
213
216
  // Targets inside xl/_rels/workbook.xml.rels (base: xl/)
214
217
  workbookStyles: "styles.xml",
215
218
  workbookSharedStrings: "sharedStrings.xml",
216
- workbookTheme1: "theme/theme1.xml"
219
+ workbookTheme1: "theme/theme1.xml",
220
+ workbookFeaturePropertyBag: "featurePropertyBag/featurePropertyBag.xml"
217
221
  };
218
222
  function pivotCacheDefinitionRelTargetFromWorkbook(n) {
219
223
  // Target inside xl/_rels/workbook.xml.rels (base: xl/)
@@ -258,3 +262,11 @@ function mediaRelTargetFromRels(filename) {
258
262
  // Target from a rels file located under xl/*/_rels (base is one level deeper than xl/)
259
263
  return `../media/${filename}`;
260
264
  }
265
+ // Form Control (ctrlProps) path functions
266
+ function ctrlPropPath(id) {
267
+ return `xl/ctrlProps/ctrlProp${id}.xml`;
268
+ }
269
+ function ctrlPropRelTargetFromWorksheet(id) {
270
+ // Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
271
+ return `../ctrlProps/ctrlProp${id}.xml`;
272
+ }
@@ -9,6 +9,7 @@ const enums_1 = require("./enums.js");
9
9
  const image_1 = require("./image.js");
10
10
  const table_1 = require("./table.js");
11
11
  const data_validations_1 = require("./data-validations.js");
12
+ const form_control_1 = require("./form-control.js");
12
13
  const encryptor_1 = require("./utils/encryptor.js");
13
14
  const utils_1 = require("../../utils/utils.js");
14
15
  const pivot_table_1 = require("./pivot-table.js");
@@ -95,6 +96,8 @@ class Worksheet {
95
96
  this.tables = {};
96
97
  this.pivotTables = [];
97
98
  this.conditionalFormattings = [];
99
+ // for form controls (legacy checkboxes, etc.)
100
+ this.formControls = [];
98
101
  }
99
102
  get name() {
100
103
  return this._name;
@@ -748,6 +751,43 @@ class Worksheet {
748
751
  return image && image.imageId;
749
752
  }
750
753
  // =========================================================================
754
+ // Form Controls (Legacy Checkboxes)
755
+ /**
756
+ * Add a form control checkbox to the worksheet.
757
+ *
758
+ * Form control checkboxes are the legacy style that work in Office 2007+,
759
+ * WPS Office, LibreOffice, and other spreadsheet applications.
760
+ *
761
+ * Unlike modern in-cell checkboxes (which only work in Microsoft 365),
762
+ * form control checkboxes are floating controls positioned over cells.
763
+ *
764
+ * @param range - Cell reference (e.g., "B2") or range (e.g., "B2:D3") for positioning
765
+ * @param options - Checkbox options
766
+ * @returns The created FormCheckbox instance
767
+ *
768
+ * @example
769
+ * // Simple checkbox at B2
770
+ * ws.addFormCheckbox("B2");
771
+ *
772
+ * // Checkbox with label and linked cell
773
+ * ws.addFormCheckbox("B2:D3", {
774
+ * text: "Accept terms",
775
+ * link: "A2",
776
+ * checked: false
777
+ * });
778
+ */
779
+ addFormCheckbox(range, options) {
780
+ const checkbox = new form_control_1.FormCheckbox(this, range, options);
781
+ this.formControls.push(checkbox);
782
+ return checkbox;
783
+ }
784
+ /**
785
+ * Get all form control checkboxes in the worksheet
786
+ */
787
+ getFormCheckboxes() {
788
+ return this.formControls;
789
+ }
790
+ // =========================================================================
751
791
  // Worksheet Protection
752
792
  /**
753
793
  * Protect the worksheet with optional password and options
@@ -856,7 +896,8 @@ class Worksheet {
856
896
  sheetProtection: this.sheetProtection,
857
897
  tables: Object.values(this.tables).map(table => table.model),
858
898
  pivotTables: this.pivotTables,
859
- conditionalFormattings: this.conditionalFormattings
899
+ conditionalFormattings: this.conditionalFormattings,
900
+ formControls: this.formControls.map(fc => fc.model)
860
901
  };
861
902
  // =================================================
862
903
  // columns
@@ -922,6 +963,8 @@ class Worksheet {
922
963
  }, {});
923
964
  this.pivotTables = value.pivotTables;
924
965
  this.conditionalFormattings = value.conditionalFormattings;
966
+ // Form controls are currently write-only (not parsed from XLSX)
967
+ this.formControls = [];
925
968
  }
926
969
  }
927
970
  exports.Worksheet = Worksheet;
@@ -17,6 +17,8 @@ const RelType = {
17
17
  Table: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table",
18
18
  PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
19
19
  PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
20
- PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable"
20
+ PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
21
+ FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag",
22
+ CtrlProp: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp"
21
23
  };
22
24
  exports.RelType = RelType;
@@ -66,6 +66,13 @@ class ContentTypesXform extends base_xform_1.BaseXform {
66
66
  PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.xlStyles),
67
67
  ContentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
68
68
  });
69
+ // Add FeaturePropertyBag if checkboxes are used
70
+ if (model.hasCheckboxes) {
71
+ xmlStream.leafNode("Override", {
72
+ PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.xlFeaturePropertyBag),
73
+ ContentType: "application/vnd.ms-excel.featurepropertybag+xml"
74
+ });
75
+ }
69
76
  const hasSharedStrings = model.sharedStrings && model.sharedStrings.count;
70
77
  if (hasSharedStrings) {
71
78
  xmlStream.leafNode("Override", {
@@ -101,6 +108,22 @@ class ContentTypesXform extends base_xform_1.BaseXform {
101
108
  });
102
109
  });
103
110
  }
111
+ // Add form control (ctrlProps) content types
112
+ if (model.formControlRefs) {
113
+ // Ensure vml extension is declared (may already be declared for comments)
114
+ if (!model.commentRefs) {
115
+ xmlStream.leafNode("Default", {
116
+ Extension: "vml",
117
+ ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
118
+ });
119
+ }
120
+ for (const ctrlPropId of model.formControlRefs) {
121
+ xmlStream.leafNode("Override", {
122
+ PartName: (0, ooxml_paths_1.toContentTypesPartName)((0, ooxml_paths_1.ctrlPropPath)(ctrlPropId)),
123
+ ContentType: "application/vnd.ms-excel.controlproperties+xml"
124
+ });
125
+ }
126
+ }
104
127
  xmlStream.leafNode("Override", {
105
128
  PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.docPropsCore),
106
129
  ContentType: "application/vnd.openxmlformats-package.core-properties+xml"
@@ -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;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CtrlPropXform = void 0;
4
+ const xml_stream_1 = require("../../../utils/xml-stream.js");
5
+ const base_xform_1 = require("../base-xform.js");
6
+ /**
7
+ * Control Properties Xform - Generates ctrlProp*.xml for form controls
8
+ *
9
+ * Each form control (checkbox, button, etc.) has an associated ctrlProp file
10
+ * that stores its properties like objectType, checked state, and linked cell.
11
+ */
12
+ class CtrlPropXform extends base_xform_1.BaseXform {
13
+ get tag() {
14
+ return "formControlPr";
15
+ }
16
+ render(xmlStream, model) {
17
+ const renderModel = model || this.model;
18
+ const attrs = {
19
+ xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
20
+ objectType: "CheckBox",
21
+ checked: renderModel.checked,
22
+ lockText: "1"
23
+ };
24
+ // Add linked cell reference
25
+ if (renderModel.link) {
26
+ attrs.fmlaLink = renderModel.link;
27
+ }
28
+ // Add noThreeD for flat appearance
29
+ if (renderModel.noThreeD) {
30
+ attrs.noThreeD = "1";
31
+ }
32
+ xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
33
+ xmlStream.leafNode(this.tag, attrs);
34
+ }
35
+ /**
36
+ * Generate XML string directly (convenience method)
37
+ * Uses render() internally to ensure consistency
38
+ */
39
+ toXml(model) {
40
+ const xmlStream = new xml_stream_1.XmlStream();
41
+ this.render(xmlStream, model);
42
+ return xmlStream.xml;
43
+ }
44
+ // Parsing not implemented - form controls are write-only for now
45
+ parseOpen() {
46
+ return true;
47
+ }
48
+ parseText() {
49
+ // Not implemented
50
+ }
51
+ parseClose() {
52
+ return false;
53
+ }
54
+ }
55
+ exports.CtrlPropXform = CtrlPropXform;