@cj-tech-master/excelts 4.0.4-canary.20260110000241.8ac37ef → 4.1.0-canary.20260110032830.e7d8c4e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.browser.d.ts +2 -0
- package/dist/browser/index.browser.js +1 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/modules/excel/form-control.d.ts +157 -0
- package/dist/browser/modules/excel/form-control.js +267 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +2 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.js +8 -0
- package/dist/browser/modules/excel/worksheet.d.ts +32 -0
- package/dist/browser/modules/excel/worksheet.js +44 -1
- package/dist/browser/modules/excel/xlsx/rel-type.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/rel-type.js +2 -1
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +17 -1
- package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +24 -5
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/modules/excel/form-control.js +270 -0
- package/dist/cjs/modules/excel/utils/ooxml-paths.js +10 -0
- package/dist/cjs/modules/excel/worksheet.js +44 -1
- package/dist/cjs/modules/excel/xlsx/rel-type.js +2 -1
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +16 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +55 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +184 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +23 -0
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +23 -4
- package/dist/esm/index.browser.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/modules/excel/form-control.js +267 -0
- package/dist/esm/modules/excel/utils/ooxml-paths.js +8 -0
- package/dist/esm/modules/excel/worksheet.js +44 -1
- package/dist/esm/modules/excel/xlsx/rel-type.js +2 -1
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +17 -1
- package/dist/esm/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.js +52 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/vml-drawing-xform.js +181 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +24 -1
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +24 -5
- package/dist/iife/excelts.iife.js +466 -30
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +30 -30
- package/dist/types/index.browser.d.ts +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/modules/excel/form-control.d.ts +157 -0
- package/dist/types/modules/excel/utils/ooxml-paths.d.ts +2 -0
- package/dist/types/modules/excel/worksheet.d.ts +32 -0
- package/dist/types/modules/excel/xlsx/rel-type.d.ts +1 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/ctrl-prop-xform.d.ts +22 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/vml-drawing-xform.d.ts +44 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +1 -0
- package/package.json +2 -2
|
@@ -27,7 +27,8 @@ const pivot_cache_records_xform_1 = require("./xform/pivot-table/pivot-cache-rec
|
|
|
27
27
|
const pivot_cache_definition_xform_1 = require("./xform/pivot-table/pivot-cache-definition-xform.js");
|
|
28
28
|
const pivot_table_xform_1 = require("./xform/pivot-table/pivot-table-xform.js");
|
|
29
29
|
const comments_xform_1 = require("./xform/comment/comments-xform.js");
|
|
30
|
-
const
|
|
30
|
+
const vml_drawing_xform_1 = require("./xform/drawing/vml-drawing-xform.js");
|
|
31
|
+
const ctrl_prop_xform_1 = require("./xform/drawing/ctrl-prop-xform.js");
|
|
31
32
|
const theme1_1 = require("./xml/theme1.js");
|
|
32
33
|
const rel_type_1 = require("./rel-type.js");
|
|
33
34
|
const stream_buf_1 = require("../utils/stream-buf.js");
|
|
@@ -705,7 +706,7 @@ class XLSX {
|
|
|
705
706
|
model.drawingRels[name] = relationships;
|
|
706
707
|
}
|
|
707
708
|
async _processVmlDrawingEntry(entry, model, name) {
|
|
708
|
-
const xform = new
|
|
709
|
+
const xform = new vml_drawing_xform_1.VmlDrawingXform();
|
|
709
710
|
const vmlDrawing = await xform.parseStream(entry);
|
|
710
711
|
model.vmlDrawings[(0, ooxml_paths_1.vmlDrawingRelTargetFromWorksheetName)(name)] = vmlDrawing;
|
|
711
712
|
}
|
|
@@ -1031,7 +1032,8 @@ class XLSX {
|
|
|
1031
1032
|
const worksheetXform = new worksheet_xform_1.WorkSheetXform();
|
|
1032
1033
|
const relationshipsXform = new relationships_xform_1.RelationshipsXform();
|
|
1033
1034
|
const commentsXform = new comments_xform_1.CommentsXform();
|
|
1034
|
-
const
|
|
1035
|
+
const vmlDrawingXform = new vml_drawing_xform_1.VmlDrawingXform();
|
|
1036
|
+
const ctrlPropXform = new ctrl_prop_xform_1.CtrlPropXform();
|
|
1035
1037
|
model.worksheets.forEach((worksheet, index) => {
|
|
1036
1038
|
const fileIndex = worksheet.fileIndex || index + 1;
|
|
1037
1039
|
let xmlStream = new xml_stream_1.XmlStream();
|
|
@@ -1042,14 +1044,30 @@ class XLSX {
|
|
|
1042
1044
|
relationshipsXform.render(xmlStream, worksheet.rels);
|
|
1043
1045
|
zip.append(xmlStream.xml, { name: (0, ooxml_paths_1.worksheetRelsPath)(fileIndex) });
|
|
1044
1046
|
}
|
|
1047
|
+
// Generate comments XML (separate from VML)
|
|
1045
1048
|
if (worksheet.comments.length > 0) {
|
|
1046
1049
|
xmlStream = new xml_stream_1.XmlStream();
|
|
1047
1050
|
commentsXform.render(xmlStream, worksheet);
|
|
1048
1051
|
zip.append(xmlStream.xml, { name: (0, ooxml_paths_1.commentsPath)(fileIndex) });
|
|
1052
|
+
}
|
|
1053
|
+
// Generate unified VML drawing (contains both notes and form controls)
|
|
1054
|
+
const hasComments = worksheet.comments.length > 0;
|
|
1055
|
+
const hasFormControls = worksheet.formControls && worksheet.formControls.length > 0;
|
|
1056
|
+
if (hasComments || hasFormControls) {
|
|
1049
1057
|
xmlStream = new xml_stream_1.XmlStream();
|
|
1050
|
-
|
|
1058
|
+
vmlDrawingXform.render(xmlStream, {
|
|
1059
|
+
comments: hasComments ? worksheet.comments : [],
|
|
1060
|
+
formControls: hasFormControls ? worksheet.formControls : []
|
|
1061
|
+
});
|
|
1051
1062
|
zip.append(xmlStream.xml, { name: (0, ooxml_paths_1.vmlDrawingPath)(fileIndex) });
|
|
1052
1063
|
}
|
|
1064
|
+
// Generate ctrlProp files for form controls
|
|
1065
|
+
if (hasFormControls) {
|
|
1066
|
+
worksheet.formControls.forEach((control) => {
|
|
1067
|
+
const xml = ctrlPropXform.toXml(control);
|
|
1068
|
+
zip.append(xml, { name: (0, ooxml_paths_1.ctrlPropPath)(control.ctrlPropId) });
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1053
1071
|
});
|
|
1054
1072
|
}
|
|
1055
1073
|
addDrawings(zip, model) {
|
|
@@ -1155,6 +1173,7 @@ class XLSX {
|
|
|
1155
1173
|
};
|
|
1156
1174
|
worksheetOptions.drawings = model.drawings = [];
|
|
1157
1175
|
worksheetOptions.commentRefs = model.commentRefs = [];
|
|
1176
|
+
worksheetOptions.formControlRefs = model.formControlRefs = [];
|
|
1158
1177
|
let tableCount = 0;
|
|
1159
1178
|
model.tables = [];
|
|
1160
1179
|
model.worksheets.forEach((worksheet) => {
|
|
@@ -15,6 +15,7 @@ export { Image } from "./modules/excel/image.js";
|
|
|
15
15
|
export * from "./modules/excel/anchor.js";
|
|
16
16
|
export { Table } from "./modules/excel/table.js";
|
|
17
17
|
export { DataValidations } from "./modules/excel/data-validations.js";
|
|
18
|
+
export { FormCheckbox } from "./modules/excel/form-control.js";
|
|
18
19
|
// =============================================================================
|
|
19
20
|
// Enums
|
|
20
21
|
// =============================================================================
|
package/dist/esm/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { Image } from "./modules/excel/image.js";
|
|
|
11
11
|
export * from "./modules/excel/anchor.js";
|
|
12
12
|
export { Table } from "./modules/excel/table.js";
|
|
13
13
|
export { DataValidations } from "./modules/excel/data-validations.js";
|
|
14
|
+
export { FormCheckbox } from "./modules/excel/form-control.js";
|
|
14
15
|
// =============================================================================
|
|
15
16
|
// Node.js Only: Streaming Classes
|
|
16
17
|
// These can also be accessed via Workbook.createStreamWriter/createStreamReader
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { colCache } from "./utils/col-cache.js";
|
|
2
|
+
/**
|
|
3
|
+
* Form Control Checkbox - Legacy checkbox control compatible with Office 2007+ and WPS/LibreOffice
|
|
4
|
+
*
|
|
5
|
+
* Unlike the modern In-Cell Checkbox (which only works in Microsoft 365),
|
|
6
|
+
* Form Control Checkboxes are floating controls that work in virtually all
|
|
7
|
+
* spreadsheet applications.
|
|
8
|
+
*/
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Constants (exported for use by xforms)
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/** EMU (English Metric Units) to pixels conversion factor at 96 DPI */
|
|
13
|
+
export const EMU_PER_PIXEL = 9525;
|
|
14
|
+
/** EMU to points conversion factor */
|
|
15
|
+
export const EMU_PER_POINT = 12700;
|
|
16
|
+
/** Default column offset in EMUs (~15 pixels) */
|
|
17
|
+
const DEFAULT_COL_OFF = 142875;
|
|
18
|
+
/** Default row offset in EMUs (~3 pixels) */
|
|
19
|
+
const DEFAULT_ROW_OFF = 28575;
|
|
20
|
+
/** Default end column offset in EMUs (~29 pixels) */
|
|
21
|
+
const DEFAULT_END_COL_OFF = 276225;
|
|
22
|
+
/** Default end row offset in EMUs (~20 pixels) */
|
|
23
|
+
const DEFAULT_END_ROW_OFF = 190500;
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// FormCheckbox Class
|
|
26
|
+
// ============================================================================
|
|
27
|
+
class FormCheckbox {
|
|
28
|
+
constructor(worksheet, range, options) {
|
|
29
|
+
this.worksheet = worksheet;
|
|
30
|
+
// Parse range to get anchors
|
|
31
|
+
const { tl, br } = this._parseRange(range);
|
|
32
|
+
// Generate shape ID (starting from 1025)
|
|
33
|
+
const existingCount = worksheet.formControls?.length || 0;
|
|
34
|
+
const shapeId = 1025 + existingCount;
|
|
35
|
+
// Parse link cell reference
|
|
36
|
+
let link;
|
|
37
|
+
if (options?.link) {
|
|
38
|
+
// Ensure absolute reference format
|
|
39
|
+
link = this._toAbsoluteRef(options.link);
|
|
40
|
+
}
|
|
41
|
+
// Note: ctrlPropId is set later in worksheet-xform.ts prepare() for global uniqueness
|
|
42
|
+
this.model = {
|
|
43
|
+
shapeId,
|
|
44
|
+
ctrlPropId: 0, // Placeholder, set during prepare()
|
|
45
|
+
tl,
|
|
46
|
+
br,
|
|
47
|
+
link,
|
|
48
|
+
checked: options?.checked ? "Checked" : "Unchecked",
|
|
49
|
+
text: options?.text ?? "",
|
|
50
|
+
noThreeD: options?.noThreeD ?? true,
|
|
51
|
+
print: options?.print ?? false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get the checked state
|
|
56
|
+
*/
|
|
57
|
+
get checked() {
|
|
58
|
+
return this.model.checked === "Checked";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Set the checked state
|
|
62
|
+
*/
|
|
63
|
+
set checked(value) {
|
|
64
|
+
this.model.checked = value ? "Checked" : "Unchecked";
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get the linked cell address
|
|
68
|
+
*/
|
|
69
|
+
get link() {
|
|
70
|
+
return this.model.link;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Set the linked cell address
|
|
74
|
+
*/
|
|
75
|
+
set link(value) {
|
|
76
|
+
this.model.link = value ? this._toAbsoluteRef(value) : undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get the label text
|
|
80
|
+
*/
|
|
81
|
+
get text() {
|
|
82
|
+
return this.model.text;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Set the label text
|
|
86
|
+
*/
|
|
87
|
+
set text(value) {
|
|
88
|
+
this.model.text = value;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Convert cell reference to absolute format (e.g., "A1" -> "$A$1")
|
|
92
|
+
*/
|
|
93
|
+
_toAbsoluteRef(ref) {
|
|
94
|
+
// If already absolute, return as-is
|
|
95
|
+
if (ref.includes("$")) {
|
|
96
|
+
return ref;
|
|
97
|
+
}
|
|
98
|
+
// Parse and convert
|
|
99
|
+
const addr = colCache.decodeAddress(ref);
|
|
100
|
+
return `$${colCache.n2l(addr.col)}$${addr.row}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse range input into anchor positions
|
|
104
|
+
*/
|
|
105
|
+
_parseRange(range) {
|
|
106
|
+
let tl;
|
|
107
|
+
let br;
|
|
108
|
+
if (typeof range === "string") {
|
|
109
|
+
// Parse cell reference like "B2" or "B2:D3"
|
|
110
|
+
const decoded = colCache.decode(range);
|
|
111
|
+
if ("top" in decoded) {
|
|
112
|
+
// It's a range like "B2:D3"
|
|
113
|
+
tl = {
|
|
114
|
+
col: decoded.left - 1, // Convert to 0-based
|
|
115
|
+
colOff: DEFAULT_COL_OFF,
|
|
116
|
+
row: decoded.top - 1,
|
|
117
|
+
rowOff: DEFAULT_ROW_OFF
|
|
118
|
+
};
|
|
119
|
+
br = {
|
|
120
|
+
col: decoded.right - 1,
|
|
121
|
+
colOff: DEFAULT_END_COL_OFF,
|
|
122
|
+
row: decoded.bottom - 1,
|
|
123
|
+
rowOff: DEFAULT_END_ROW_OFF
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// Single cell reference - create default size checkbox
|
|
128
|
+
tl = {
|
|
129
|
+
col: decoded.col - 1,
|
|
130
|
+
colOff: DEFAULT_COL_OFF,
|
|
131
|
+
row: decoded.row - 1,
|
|
132
|
+
rowOff: DEFAULT_ROW_OFF
|
|
133
|
+
};
|
|
134
|
+
// Default size: about 2 columns wide, 1 row tall
|
|
135
|
+
br = {
|
|
136
|
+
col: decoded.col + 1,
|
|
137
|
+
colOff: DEFAULT_END_COL_OFF,
|
|
138
|
+
row: decoded.row,
|
|
139
|
+
rowOff: DEFAULT_END_ROW_OFF
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if ("startCol" in range) {
|
|
144
|
+
// startCol/startRow/endCol/endRow format (0-based)
|
|
145
|
+
tl = {
|
|
146
|
+
col: range.startCol,
|
|
147
|
+
colOff: range.startColOff ?? DEFAULT_COL_OFF,
|
|
148
|
+
row: range.startRow,
|
|
149
|
+
rowOff: range.startRowOff ?? DEFAULT_ROW_OFF
|
|
150
|
+
};
|
|
151
|
+
br = {
|
|
152
|
+
col: range.endCol,
|
|
153
|
+
colOff: range.endColOff ?? DEFAULT_END_COL_OFF,
|
|
154
|
+
row: range.endRow,
|
|
155
|
+
rowOff: range.endRowOff ?? DEFAULT_END_ROW_OFF
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Object format with tl/br
|
|
160
|
+
if (typeof range.tl === "string") {
|
|
161
|
+
const decoded = colCache.decodeAddress(range.tl);
|
|
162
|
+
tl = {
|
|
163
|
+
col: decoded.col - 1,
|
|
164
|
+
colOff: DEFAULT_COL_OFF,
|
|
165
|
+
row: decoded.row - 1,
|
|
166
|
+
rowOff: DEFAULT_ROW_OFF
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
tl = {
|
|
171
|
+
col: range.tl.col,
|
|
172
|
+
colOff: range.tl.colOff ?? DEFAULT_COL_OFF,
|
|
173
|
+
row: range.tl.row,
|
|
174
|
+
rowOff: range.tl.rowOff ?? DEFAULT_ROW_OFF
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (range.br) {
|
|
178
|
+
if (typeof range.br === "string") {
|
|
179
|
+
const decoded = colCache.decodeAddress(range.br);
|
|
180
|
+
br = {
|
|
181
|
+
col: decoded.col - 1,
|
|
182
|
+
colOff: DEFAULT_END_COL_OFF,
|
|
183
|
+
row: decoded.row - 1,
|
|
184
|
+
rowOff: DEFAULT_END_ROW_OFF
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
br = {
|
|
189
|
+
col: range.br.col,
|
|
190
|
+
colOff: range.br.colOff ?? DEFAULT_END_COL_OFF,
|
|
191
|
+
row: range.br.row,
|
|
192
|
+
rowOff: range.br.rowOff ?? DEFAULT_END_ROW_OFF
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// Default size
|
|
198
|
+
br = {
|
|
199
|
+
col: tl.col + 2,
|
|
200
|
+
colOff: DEFAULT_END_COL_OFF,
|
|
201
|
+
row: tl.row + 1,
|
|
202
|
+
rowOff: DEFAULT_END_ROW_OFF
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { tl, br };
|
|
207
|
+
}
|
|
208
|
+
// =========================================================================
|
|
209
|
+
// Instance methods - delegate to static methods
|
|
210
|
+
// =========================================================================
|
|
211
|
+
/**
|
|
212
|
+
* Convert anchor to VML anchor string format
|
|
213
|
+
* Format: "fromCol, fromColOff, fromRow, fromRowOff, toCol, toColOff, toRow, toRowOff"
|
|
214
|
+
* VML uses pixels for offsets
|
|
215
|
+
*/
|
|
216
|
+
getVmlAnchor() {
|
|
217
|
+
return FormCheckbox.getVmlAnchor(this.model);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get VML style string for positioning
|
|
221
|
+
*/
|
|
222
|
+
getVmlStyle() {
|
|
223
|
+
return FormCheckbox.getVmlStyle(this.model);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get the numeric checked value for VML (0, 1, or 2)
|
|
227
|
+
*/
|
|
228
|
+
getVmlCheckedValue() {
|
|
229
|
+
return FormCheckbox.getVmlCheckedValue(this.model);
|
|
230
|
+
}
|
|
231
|
+
// =========================================================================
|
|
232
|
+
// Static utility methods - can be used with FormCheckboxModel directly
|
|
233
|
+
// =========================================================================
|
|
234
|
+
/**
|
|
235
|
+
* Convert anchor to VML anchor string format from model
|
|
236
|
+
*/
|
|
237
|
+
static getVmlAnchor(model) {
|
|
238
|
+
const { tl, br } = model;
|
|
239
|
+
const tlColOff = Math.round(tl.colOff / EMU_PER_PIXEL);
|
|
240
|
+
const tlRowOff = Math.round(tl.rowOff / EMU_PER_PIXEL);
|
|
241
|
+
const brColOff = Math.round(br.colOff / EMU_PER_PIXEL);
|
|
242
|
+
const brRowOff = Math.round(br.rowOff / EMU_PER_PIXEL);
|
|
243
|
+
return `${tl.col}, ${tlColOff}, ${tl.row}, ${tlRowOff}, ${br.col}, ${brColOff}, ${br.row}, ${brRowOff}`;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get VML style string for positioning from model
|
|
247
|
+
*/
|
|
248
|
+
static getVmlStyle(model) {
|
|
249
|
+
const marginLeft = Math.round(model.tl.colOff / EMU_PER_POINT);
|
|
250
|
+
const marginTop = Math.round(model.tl.rowOff / EMU_PER_POINT);
|
|
251
|
+
return `position:absolute;margin-left:${marginLeft}pt;margin-top:${marginTop}pt;width:96pt;height:18pt;z-index:1;visibility:visible`;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Get the numeric checked value for VML from model (0, 1, or 2)
|
|
255
|
+
*/
|
|
256
|
+
static getVmlCheckedValue(model) {
|
|
257
|
+
switch (model.checked) {
|
|
258
|
+
case "Checked":
|
|
259
|
+
return 1;
|
|
260
|
+
case "Mixed":
|
|
261
|
+
return 2;
|
|
262
|
+
default:
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
export { FormCheckbox };
|
|
@@ -209,3 +209,11 @@ export function mediaRelTargetFromRels(filename) {
|
|
|
209
209
|
// Target from a rels file located under xl/*/_rels (base is one level deeper than xl/)
|
|
210
210
|
return `../media/${filename}`;
|
|
211
211
|
}
|
|
212
|
+
// Form Control (ctrlProps) path functions
|
|
213
|
+
export function ctrlPropPath(id) {
|
|
214
|
+
return `xl/ctrlProps/ctrlProp${id}.xml`;
|
|
215
|
+
}
|
|
216
|
+
export function ctrlPropRelTargetFromWorksheet(id) {
|
|
217
|
+
// Target inside xl/worksheets/_rels/sheetN.xml.rels (base: xl/worksheets/)
|
|
218
|
+
return `../ctrlProps/ctrlProp${id}.xml`;
|
|
219
|
+
}
|
|
@@ -6,6 +6,7 @@ import { Enums } from "./enums.js";
|
|
|
6
6
|
import { Image } from "./image.js";
|
|
7
7
|
import { Table } from "./table.js";
|
|
8
8
|
import { DataValidations } from "./data-validations.js";
|
|
9
|
+
import { FormCheckbox } from "./form-control.js";
|
|
9
10
|
import { Encryptor } from "./utils/encryptor.js";
|
|
10
11
|
import { uint8ArrayToBase64 } from "../../utils/utils.js";
|
|
11
12
|
import { makePivotTable } from "./pivot-table.js";
|
|
@@ -92,6 +93,8 @@ class Worksheet {
|
|
|
92
93
|
this.tables = {};
|
|
93
94
|
this.pivotTables = [];
|
|
94
95
|
this.conditionalFormattings = [];
|
|
96
|
+
// for form controls (legacy checkboxes, etc.)
|
|
97
|
+
this.formControls = [];
|
|
95
98
|
}
|
|
96
99
|
get name() {
|
|
97
100
|
return this._name;
|
|
@@ -745,6 +748,43 @@ class Worksheet {
|
|
|
745
748
|
return image && image.imageId;
|
|
746
749
|
}
|
|
747
750
|
// =========================================================================
|
|
751
|
+
// Form Controls (Legacy Checkboxes)
|
|
752
|
+
/**
|
|
753
|
+
* Add a form control checkbox to the worksheet.
|
|
754
|
+
*
|
|
755
|
+
* Form control checkboxes are the legacy style that work in Office 2007+,
|
|
756
|
+
* WPS Office, LibreOffice, and other spreadsheet applications.
|
|
757
|
+
*
|
|
758
|
+
* Unlike modern in-cell checkboxes (which only work in Microsoft 365),
|
|
759
|
+
* form control checkboxes are floating controls positioned over cells.
|
|
760
|
+
*
|
|
761
|
+
* @param range - Cell reference (e.g., "B2") or range (e.g., "B2:D3") for positioning
|
|
762
|
+
* @param options - Checkbox options
|
|
763
|
+
* @returns The created FormCheckbox instance
|
|
764
|
+
*
|
|
765
|
+
* @example
|
|
766
|
+
* // Simple checkbox at B2
|
|
767
|
+
* ws.addFormCheckbox("B2");
|
|
768
|
+
*
|
|
769
|
+
* // Checkbox with label and linked cell
|
|
770
|
+
* ws.addFormCheckbox("B2:D3", {
|
|
771
|
+
* text: "Accept terms",
|
|
772
|
+
* link: "A2",
|
|
773
|
+
* checked: false
|
|
774
|
+
* });
|
|
775
|
+
*/
|
|
776
|
+
addFormCheckbox(range, options) {
|
|
777
|
+
const checkbox = new FormCheckbox(this, range, options);
|
|
778
|
+
this.formControls.push(checkbox);
|
|
779
|
+
return checkbox;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Get all form control checkboxes in the worksheet
|
|
783
|
+
*/
|
|
784
|
+
getFormCheckboxes() {
|
|
785
|
+
return this.formControls;
|
|
786
|
+
}
|
|
787
|
+
// =========================================================================
|
|
748
788
|
// Worksheet Protection
|
|
749
789
|
/**
|
|
750
790
|
* Protect the worksheet with optional password and options
|
|
@@ -853,7 +893,8 @@ class Worksheet {
|
|
|
853
893
|
sheetProtection: this.sheetProtection,
|
|
854
894
|
tables: Object.values(this.tables).map(table => table.model),
|
|
855
895
|
pivotTables: this.pivotTables,
|
|
856
|
-
conditionalFormattings: this.conditionalFormattings
|
|
896
|
+
conditionalFormattings: this.conditionalFormattings,
|
|
897
|
+
formControls: this.formControls.map(fc => fc.model)
|
|
857
898
|
};
|
|
858
899
|
// =================================================
|
|
859
900
|
// columns
|
|
@@ -919,6 +960,8 @@ class Worksheet {
|
|
|
919
960
|
}, {});
|
|
920
961
|
this.pivotTables = value.pivotTables;
|
|
921
962
|
this.conditionalFormattings = value.conditionalFormattings;
|
|
963
|
+
// Form controls are currently write-only (not parsed from XLSX)
|
|
964
|
+
this.formControls = [];
|
|
922
965
|
}
|
|
923
966
|
}
|
|
924
967
|
export { Worksheet };
|
|
@@ -15,6 +15,7 @@ const RelType = {
|
|
|
15
15
|
PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
|
|
16
16
|
PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
|
|
17
17
|
PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
|
|
18
|
-
FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"
|
|
18
|
+
FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag",
|
|
19
|
+
CtrlProp: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp"
|
|
19
20
|
};
|
|
20
21
|
export { RelType };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XmlStream } from "../../../utils/xml-stream.js";
|
|
2
2
|
import { BaseXform } from "../base-xform.js";
|
|
3
|
-
import { OOXML_PATHS, commentsPathFromName, drawingPath, pivotCacheDefinitionPath, pivotCacheRecordsPath, pivotTablePath, tablePath, toContentTypesPartName, worksheetPath } from "../../../utils/ooxml-paths.js";
|
|
3
|
+
import { OOXML_PATHS, commentsPathFromName, ctrlPropPath, drawingPath, pivotCacheDefinitionPath, pivotCacheRecordsPath, pivotTablePath, tablePath, toContentTypesPartName, worksheetPath } from "../../../utils/ooxml-paths.js";
|
|
4
4
|
// used for rendering the [Content_Types].xml file
|
|
5
5
|
// not used for parsing
|
|
6
6
|
class ContentTypesXform extends BaseXform {
|
|
@@ -105,6 +105,22 @@ class ContentTypesXform extends BaseXform {
|
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
107
|
}
|
|
108
|
+
// Add form control (ctrlProps) content types
|
|
109
|
+
if (model.formControlRefs) {
|
|
110
|
+
// Ensure vml extension is declared (may already be declared for comments)
|
|
111
|
+
if (!model.commentRefs) {
|
|
112
|
+
xmlStream.leafNode("Default", {
|
|
113
|
+
Extension: "vml",
|
|
114
|
+
ContentType: "application/vnd.openxmlformats-officedocument.vmlDrawing"
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
for (const ctrlPropId of model.formControlRefs) {
|
|
118
|
+
xmlStream.leafNode("Override", {
|
|
119
|
+
PartName: toContentTypesPartName(ctrlPropPath(ctrlPropId)),
|
|
120
|
+
ContentType: "application/vnd.ms-excel.controlproperties+xml"
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
108
124
|
xmlStream.leafNode("Override", {
|
|
109
125
|
PartName: toContentTypesPartName(OOXML_PATHS.docPropsCore),
|
|
110
126
|
ContentType: "application/vnd.openxmlformats-package.core-properties+xml"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { XmlStream } from "../../../utils/xml-stream.js";
|
|
2
|
+
import { BaseXform } from "../base-xform.js";
|
|
3
|
+
/**
|
|
4
|
+
* Control Properties Xform - Generates ctrlProp*.xml for form controls
|
|
5
|
+
*
|
|
6
|
+
* Each form control (checkbox, button, etc.) has an associated ctrlProp file
|
|
7
|
+
* that stores its properties like objectType, checked state, and linked cell.
|
|
8
|
+
*/
|
|
9
|
+
class CtrlPropXform extends BaseXform {
|
|
10
|
+
get tag() {
|
|
11
|
+
return "formControlPr";
|
|
12
|
+
}
|
|
13
|
+
render(xmlStream, model) {
|
|
14
|
+
const renderModel = model || this.model;
|
|
15
|
+
const attrs = {
|
|
16
|
+
xmlns: "http://schemas.microsoft.com/office/spreadsheetml/2009/9/main",
|
|
17
|
+
objectType: "CheckBox",
|
|
18
|
+
checked: renderModel.checked,
|
|
19
|
+
lockText: "1"
|
|
20
|
+
};
|
|
21
|
+
// Add linked cell reference
|
|
22
|
+
if (renderModel.link) {
|
|
23
|
+
attrs.fmlaLink = renderModel.link;
|
|
24
|
+
}
|
|
25
|
+
// Add noThreeD for flat appearance
|
|
26
|
+
if (renderModel.noThreeD) {
|
|
27
|
+
attrs.noThreeD = "1";
|
|
28
|
+
}
|
|
29
|
+
xmlStream.openXml({ version: "1.0", encoding: "UTF-8", standalone: "yes" });
|
|
30
|
+
xmlStream.leafNode(this.tag, attrs);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate XML string directly (convenience method)
|
|
34
|
+
* Uses render() internally to ensure consistency
|
|
35
|
+
*/
|
|
36
|
+
toXml(model) {
|
|
37
|
+
const xmlStream = new XmlStream();
|
|
38
|
+
this.render(xmlStream, model);
|
|
39
|
+
return xmlStream.xml;
|
|
40
|
+
}
|
|
41
|
+
// Parsing not implemented - form controls are write-only for now
|
|
42
|
+
parseOpen() {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
parseText() {
|
|
46
|
+
// Not implemented
|
|
47
|
+
}
|
|
48
|
+
parseClose() {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export { CtrlPropXform };
|