@cj-tech-master/excelts 4.0.4-canary.20260109045014.00b0344 → 4.0.4-canary.20260110000241.8ac37ef

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