@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
@@ -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;
@@ -10,7 +10,8 @@ export declare enum ValueType {
10
10
  RichText = 8,
11
11
  Boolean = 9,
12
12
  Error = 10,
13
- JSON = 11
13
+ JSON = 11,// Internal type for JSON values that serialize as String
14
+ Checkbox = 12
14
15
  }
15
16
  export declare enum FormulaType {
16
17
  None = 0,
@@ -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) {
@@ -132,6 +132,7 @@ export declare abstract class WorkbookWriterBase<TWorksheetWriter extends Worksh
132
132
  addApp(): Promise<void>;
133
133
  addCore(): Promise<void>;
134
134
  addSharedStrings(): Promise<void>;
135
+ addFeaturePropertyBag(): Promise<void>;
135
136
  addWorkbookRels(): Promise<void>;
136
137
  addWorkbook(): Promise<void>;
137
138
  _finalize(): Promise<this>;
@@ -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.browser.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++}`;
@@ -52,8 +52,12 @@ declare class Table {
52
52
  set model(value: TableModel);
53
53
  cacheState(): void;
54
54
  commit(): void;
55
- addRow(values: CellValue[], rowNumber?: number): void;
56
- removeRows(rowIndex: number, count?: number): void;
55
+ addRow(values: CellValue[], rowNumber?: number, options?: {
56
+ commit?: boolean;
57
+ }): void;
58
+ removeRows(rowIndex: number, count?: number, options?: {
59
+ commit?: boolean;
60
+ }): void;
57
61
  getColumn(colIndex: number): Column;
58
62
  addColumn(column: TableColumnProperties, values: CellValue[], colIndex?: number): void;
59
63
  removeColumns(colIndex: number, count?: number): void;
@@ -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];
@@ -274,7 +274,11 @@ export interface CellSharedFormulaValue {
274
274
  result?: number | string | boolean | Date | CellErrorValue;
275
275
  date1904?: boolean;
276
276
  }
277
- export type CellValue = null | number | string | boolean | Date | undefined | CellErrorValue | CellRichTextValue | CellHyperlinkValue | CellFormulaValue | CellArrayFormulaValue | CellSharedFormulaValue;
277
+ export interface CellCheckboxValue {
278
+ /** Indicates this is a checkbox value */
279
+ checkbox: boolean;
280
+ }
281
+ export type CellValue = null | number | string | boolean | Date | undefined | CellErrorValue | CellRichTextValue | CellHyperlinkValue | CellFormulaValue | CellArrayFormulaValue | CellSharedFormulaValue | CellCheckboxValue;
278
282
  export interface CommentMargins {
279
283
  insetmode: "auto" | "custom";
280
284
  inset: number[];
@@ -8,6 +8,7 @@ export declare const OOXML_PATHS: {
8
8
  readonly xlSharedStrings: "xl/sharedStrings.xml";
9
9
  readonly xlStyles: "xl/styles.xml";
10
10
  readonly xlTheme1: "xl/theme/theme1.xml";
11
+ readonly xlFeaturePropertyBag: "xl/featurePropertyBag/featurePropertyBag.xml";
11
12
  };
12
13
  export declare function normalizeZipPath(path: string): string;
13
14
  export declare function getWorksheetNoFromWorksheetPath(path: string): number | undefined;
@@ -50,6 +51,7 @@ export declare const OOXML_REL_TARGETS: {
50
51
  readonly workbookStyles: "styles.xml";
51
52
  readonly workbookSharedStrings: "sharedStrings.xml";
52
53
  readonly workbookTheme1: "theme/theme1.xml";
54
+ readonly workbookFeaturePropertyBag: "featurePropertyBag/featurePropertyBag.xml";
53
55
  };
54
56
  export declare function pivotCacheDefinitionRelTargetFromWorkbook(n: number | string): string;
55
57
  export declare function commentsRelTargetFromWorksheet(sheetId: number | string): string;
@@ -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/)
@@ -15,5 +15,6 @@ declare const RelType: {
15
15
  PivotCacheDefinition: string;
16
16
  PivotCacheRecords: string;
17
17
  PivotTable: string;
18
+ FeaturePropertyBag: string;
18
19
  };
19
20
  export { RelType };
@@ -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,8 @@
1
+ import { BaseXform } from "@excel/xlsx/xform/base-xform";
2
+ declare class FeaturePropertyBagXform extends BaseXform {
3
+ render(xmlStream: any): void;
4
+ parseOpen(): boolean;
5
+ parseText(): void;
6
+ parseClose(): boolean;
7
+ }
8
+ export { FeaturePropertyBagXform };
@@ -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 };
@@ -189,6 +189,11 @@ class CellXform extends BaseXform {
189
189
  xmlStream.addAttribute("t", "b");
190
190
  xmlStream.leafNode("v", null, model.value ? "1" : "0");
191
191
  break;
192
+ case Enums.ValueType.Checkbox:
193
+ // Checkboxes are stored as boolean values
194
+ xmlStream.addAttribute("t", "b");
195
+ xmlStream.leafNode("v", null, model.value ? "1" : "0");
196
+ break;
192
197
  case Enums.ValueType.Error:
193
198
  xmlStream.addAttribute("t", "e");
194
199
  xmlStream.leafNode("v", null, model.value.error);
@@ -9,6 +9,8 @@ interface StyleModel {
9
9
  xfId?: number;
10
10
  alignment?: any;
11
11
  protection?: any;
12
+ checkbox?: boolean;
13
+ xfComplementIndex?: number;
12
14
  }
13
15
  interface StyleOptions {
14
16
  xfId?: boolean;
@@ -52,6 +52,17 @@ class StyleXform extends BaseXform {
52
52
  if (model.protection) {
53
53
  this.map.protection.render(xmlStream, model.protection);
54
54
  }
55
+ // Add checkbox extLst if needed
56
+ if (model.checkbox && model.xfComplementIndex !== undefined) {
57
+ xmlStream.openNode("extLst");
58
+ xmlStream.openNode("ext", {
59
+ "xmlns:xfpb": "http://schemas.microsoft.com/office/spreadsheetml/2022/featurepropertybag",
60
+ uri: "{C7286773-470A-42A8-94C5-96B5CB345126}"
61
+ });
62
+ xmlStream.leafNode("xfpb:xfComplement", { i: model.xfComplementIndex });
63
+ xmlStream.closeNode();
64
+ xmlStream.closeNode();
65
+ }
55
66
  xmlStream.closeNode();
56
67
  }
57
68
  parseOpen(node) {
@@ -11,6 +11,7 @@ interface StylesModel {
11
11
  declare class StylesXform extends BaseXform {
12
12
  private index?;
13
13
  private weakMap?;
14
+ private _hasCheckboxes?;
14
15
  parser: any;
15
16
  static Mock: typeof StylesXform;
16
17
  constructor(initialise?: boolean);
@@ -24,6 +25,7 @@ declare class StylesXform extends BaseXform {
24
25
  getStyleModel(id: number): any;
25
26
  addDxfStyle(style: any): number;
26
27
  getDxfStyle(id: number): any;
28
+ get hasCheckboxes(): boolean;
27
29
  _addStyle(style: any): number;
28
30
  _addNumFmtStr(formatCode: string): number;
29
31
  _addFont(font: any): number;
@@ -82,6 +82,7 @@ class StylesXform extends BaseXform {
82
82
  this._addFill({ type: "pattern", pattern: "none" });
83
83
  this._addFill({ type: "pattern", pattern: "gray125" });
84
84
  this.weakMap = new WeakMap();
85
+ this._hasCheckboxes = false;
85
86
  }
86
87
  render(xmlStream, model) {
87
88
  const renderModel = model || this.model;
@@ -222,12 +223,15 @@ class StylesXform extends BaseXform {
222
223
  // default (zero) font
223
224
  this._addFont({ size: 11, color: { theme: 1 }, name: "Calibri", family: 2, scheme: "minor" });
224
225
  }
225
- // if we have seen this style object before, assume it has the same styleId
226
- if (this.weakMap && this.weakMap.has(model)) {
226
+ const type = cellType || Enums.ValueType.Number;
227
+ // If we have seen this style object before, assume it has the same styleId.
228
+ // Do not cache by object identity for checkbox cells because the styleId must
229
+ // include checkbox-specific extLst, and the same style object may be reused
230
+ // for non-checkbox cells.
231
+ if (type !== Enums.ValueType.Checkbox && this.weakMap && this.weakMap.has(model)) {
227
232
  return this.weakMap.get(model);
228
233
  }
229
234
  const style = {};
230
- const type = cellType || Enums.ValueType.Number;
231
235
  if (model.numFmt) {
232
236
  style.numFmtId = this._addNumFmtStr(model.numFmt);
233
237
  }
@@ -258,8 +262,17 @@ class StylesXform extends BaseXform {
258
262
  if (model.protection) {
259
263
  style.protection = model.protection;
260
264
  }
265
+ if (type === Enums.ValueType.Checkbox) {
266
+ // Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
267
+ // Force applyAlignment="1" (without emitting an <alignment/> node) by providing
268
+ // an empty alignment object when none is specified.
269
+ this._hasCheckboxes = true;
270
+ style.alignment = style.alignment || {};
271
+ style.checkbox = true;
272
+ style.xfComplementIndex = 0;
273
+ }
261
274
  const styleId = this._addStyle(style);
262
- if (this.weakMap) {
275
+ if (type !== Enums.ValueType.Checkbox && this.weakMap) {
263
276
  this.weakMap.set(model, styleId);
264
277
  }
265
278
  return styleId;
@@ -322,6 +335,10 @@ class StylesXform extends BaseXform {
322
335
  getDxfStyle(id) {
323
336
  return this.model.dxfs[id];
324
337
  }
338
+ // Check if workbook uses checkbox feature
339
+ get hasCheckboxes() {
340
+ return !!this._hasCheckboxes;
341
+ }
325
342
  // =========================================================================
326
343
  // Private Interface
327
344
  _addStyle(style) {
@@ -457,12 +474,19 @@ class StylesXformMock extends StylesXform {
457
474
  // the styleId is returned. Note: cellType is used when numFmt not defined
458
475
  addStyleModel(model, cellType) {
459
476
  switch (cellType) {
477
+ case Enums.ValueType.Checkbox:
478
+ // Checkbox rendering relies on style extensions (extLst) and workbook-level parts.
479
+ // The mock style manager intentionally does not build those structures.
480
+ throw new Error("Checkbox cells require styles to be enabled (useStyles: true)");
460
481
  case Enums.ValueType.Date:
461
482
  return this.dateStyleId;
462
483
  default:
463
484
  return 0;
464
485
  }
465
486
  }
487
+ get hasCheckboxes() {
488
+ return false;
489
+ }
466
490
  get dateStyleId() {
467
491
  if (!this._dateStyleId) {
468
492
  const dateStyle = {
@@ -92,6 +92,7 @@ declare class XLSX {
92
92
  PivotCacheDefinition: string;
93
93
  PivotCacheRecords: string;
94
94
  PivotTable: string;
95
+ FeaturePropertyBag: string;
95
96
  };
96
97
  constructor(workbook: Workbook);
97
98
  /**
@@ -195,6 +196,7 @@ declare class XLSX {
195
196
  addThemes(zip: IZipWriter, model: any): Promise<void>;
196
197
  addOfficeRels(zip: IZipWriter, _model: any): Promise<void>;
197
198
  addWorkbookRels(zip: IZipWriter, model: any): Promise<void>;
199
+ addFeaturePropertyBag(zip: IZipWriter, model: any): Promise<void>;
198
200
  addSharedStrings(zip: IZipWriter, model: any): Promise<void>;
199
201
  addStyles(zip: IZipWriter, model: any): Promise<void>;
200
202
  addWorkbook(zip: IZipWriter, model: any): Promise<void>;
@@ -17,6 +17,7 @@ import { ContentTypesXform } from "./xform/core/content-types-xform.js";
17
17
  import { AppXform } from "./xform/core/app-xform.js";
18
18
  import { WorkbookXform } from "./xform/book/workbook-xform.js";
19
19
  import { WorkSheetXform } from "./xform/sheet/worksheet-xform.js";
20
+ import { FeaturePropertyBagXform } from "./xform/core/feature-property-bag-xform.js";
20
21
  import { DrawingXform } from "./xform/drawing/drawing-xform.js";
21
22
  import { TableXform } from "./xform/table/table-xform.js";
22
23
  import { PivotCacheRecordsXform } from "./xform/pivot-table/pivot-cache-records-xform.js";
@@ -203,6 +204,7 @@ class XLSX {
203
204
  this.addTables(zip, model);
204
205
  this.addPivotTables(zip, model);
205
206
  await Promise.all([this.addThemes(zip, model), this.addStyles(zip, model)]);
207
+ await this.addFeaturePropertyBag(zip, model);
206
208
  await this.addMedia(zip, model);
207
209
  await Promise.all([this.addApp(zip, model), this.addCore(zip, model)]);
208
210
  await this.addWorkbook(zip, model);
@@ -971,6 +973,14 @@ class XLSX {
971
973
  Target: OOXML_REL_TARGETS.workbookSharedStrings
972
974
  });
973
975
  }
976
+ // Add FeaturePropertyBag relationship if checkboxes are used
977
+ if (model.hasCheckboxes) {
978
+ relationships.push({
979
+ Id: `rId${count++}`,
980
+ Type: XLSX.RelType.FeaturePropertyBag,
981
+ Target: OOXML_REL_TARGETS.workbookFeaturePropertyBag
982
+ });
983
+ }
974
984
  (model.pivotTables || []).forEach((pivotTable) => {
975
985
  pivotTable.rId = `rId${count++}`;
976
986
  relationships.push({
@@ -992,6 +1002,13 @@ class XLSX {
992
1002
  const xml = xform.toXml(relationships);
993
1003
  zip.append(xml, { name: OOXML_PATHS.xlWorkbookRels });
994
1004
  }
1005
+ async addFeaturePropertyBag(zip, model) {
1006
+ if (!model.hasCheckboxes) {
1007
+ return;
1008
+ }
1009
+ const xform = new FeaturePropertyBagXform();
1010
+ zip.append(xform.toXml({}), { name: OOXML_PATHS.xlFeaturePropertyBag });
1011
+ }
995
1012
  async addSharedStrings(zip, model) {
996
1013
  if (model.sharedStrings && model.sharedStrings.count) {
997
1014
  zip.append(model.sharedStrings.xml, { name: OOXML_PATHS.xlSharedStrings });
@@ -1146,6 +1163,8 @@ class XLSX {
1146
1163
  });
1147
1164
  worksheetXform.prepare(worksheet, worksheetOptions);
1148
1165
  });
1166
+ // ContentTypesXform expects this flag
1167
+ model.hasCheckboxes = model.styles.hasCheckboxes;
1149
1168
  }
1150
1169
  }
1151
1170
  XLSX.RelType = RelType;
@@ -813,6 +813,40 @@ class BooleanValue {
813
813
  return this.model.value.toString();
814
814
  }
815
815
  }
816
+ class CheckboxValue {
817
+ constructor(cell, value) {
818
+ this.model = {
819
+ address: cell.address,
820
+ type: Cell.Types.Checkbox,
821
+ value: value.checkbox
822
+ };
823
+ }
824
+ get value() {
825
+ return { checkbox: this.model.value };
826
+ }
827
+ set value(value) {
828
+ this.model.value = value.checkbox;
829
+ }
830
+ get type() {
831
+ return Cell.Types.Checkbox;
832
+ }
833
+ get effectiveType() {
834
+ return Cell.Types.Boolean;
835
+ }
836
+ get address() {
837
+ return this.model.address;
838
+ }
839
+ set address(value) {
840
+ this.model.address = value;
841
+ }
842
+ toCsvString() {
843
+ return this.model.value ? 1 : 0;
844
+ }
845
+ release() { }
846
+ toString() {
847
+ return this.model.value.toString();
848
+ }
849
+ }
816
850
  class ErrorValue {
817
851
  constructor(cell, value) {
818
852
  this.model = {
@@ -902,6 +936,9 @@ const Value = {
902
936
  return Cell.Types.Date;
903
937
  }
904
938
  if (typeof value === "object") {
939
+ if ("checkbox" in value && typeof value.checkbox === "boolean") {
940
+ return Cell.Types.Checkbox;
941
+ }
905
942
  if ("text" in value && value.text && "hyperlink" in value && value.hyperlink) {
906
943
  return Cell.Types.Hyperlink;
907
944
  }
@@ -934,7 +971,8 @@ const Value = {
934
971
  { t: Cell.Types.SharedString, f: SharedStringValue },
935
972
  { t: Cell.Types.RichText, f: RichTextValue },
936
973
  { t: Cell.Types.Boolean, f: BooleanValue },
937
- { t: Cell.Types.Error, f: ErrorValue }
974
+ { t: Cell.Types.Error, f: ErrorValue },
975
+ { t: Cell.Types.Checkbox, f: CheckboxValue }
938
976
  ].reduce((p, t) => {
939
977
  p[t.t] = t.f;
940
978
  return p;
@@ -14,7 +14,8 @@ var ValueType;
14
14
  ValueType[ValueType["RichText"] = 8] = "RichText";
15
15
  ValueType[ValueType["Boolean"] = 9] = "Boolean";
16
16
  ValueType[ValueType["Error"] = 10] = "Error";
17
- ValueType[ValueType["JSON"] = 11] = "JSON"; // Internal type for JSON values that serialize as String
17
+ ValueType[ValueType["JSON"] = 11] = "JSON";
18
+ ValueType[ValueType["Checkbox"] = 12] = "Checkbox";
18
19
  })(ValueType || (exports.ValueType = ValueType = {}));
19
20
  var FormulaType;
20
21
  (function (FormulaType) {