@cj-tech-master/excelts 4.1.0 → 4.2.0-canary.20260110080706.375ff37
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/THIRD_PARTY_NOTICES.md +112 -0
- 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 +7 -7
- /package/dist/{LICENSE → iife/LICENSE} +0 -0
|
@@ -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;
|
|
@@ -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",
|
|
@@ -260,3 +262,11 @@ function mediaRelTargetFromRels(filename) {
|
|
|
260
262
|
// Target from a rels file located under xl/*/_rels (base is one level deeper than xl/)
|
|
261
263
|
return `../media/${filename}`;
|
|
262
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;
|
|
@@ -18,6 +18,7 @@ const RelType = {
|
|
|
18
18
|
PivotCacheDefinition: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheDefinition",
|
|
19
19
|
PivotCacheRecords: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotCacheRecords",
|
|
20
20
|
PivotTable: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/pivotTable",
|
|
21
|
-
FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag"
|
|
21
|
+
FeaturePropertyBag: "http://schemas.microsoft.com/office/2022/11/relationships/FeaturePropertyBag",
|
|
22
|
+
CtrlProp: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp"
|
|
22
23
|
};
|
|
23
24
|
exports.RelType = RelType;
|
|
@@ -108,6 +108,22 @@ class ContentTypesXform extends base_xform_1.BaseXform {
|
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
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
|
+
}
|
|
111
127
|
xmlStream.leafNode("Override", {
|
|
112
128
|
PartName: (0, ooxml_paths_1.toContentTypesPartName)(ooxml_paths_1.OOXML_PATHS.docPropsCore),
|
|
113
129
|
ContentType: "application/vnd.openxmlformats-package.core-properties+xml"
|
|
@@ -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;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VmlDrawingXform = void 0;
|
|
4
|
+
const xml_stream_1 = require("../../../utils/xml-stream.js");
|
|
5
|
+
const base_xform_1 = require("../base-xform.js");
|
|
6
|
+
const vml_shape_xform_1 = require("../comment/vml-shape-xform.js");
|
|
7
|
+
const form_control_1 = require("../../../form-control.js");
|
|
8
|
+
class VmlDrawingXform extends base_xform_1.BaseXform {
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
this.map = {
|
|
12
|
+
"v:shape": new vml_shape_xform_1.VmlShapeXform()
|
|
13
|
+
};
|
|
14
|
+
this.model = { comments: [], formControls: [] };
|
|
15
|
+
}
|
|
16
|
+
get tag() {
|
|
17
|
+
return "xml";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Render VML drawing containing both notes and form controls
|
|
21
|
+
*/
|
|
22
|
+
render(xmlStream, model) {
|
|
23
|
+
const renderModel = model || this.model;
|
|
24
|
+
const hasComments = renderModel.comments && renderModel.comments.length > 0;
|
|
25
|
+
const hasFormControls = renderModel.formControls && renderModel.formControls.length > 0;
|
|
26
|
+
xmlStream.openXml(xml_stream_1.XmlStream.StdDocAttributes);
|
|
27
|
+
xmlStream.openNode(this.tag, VmlDrawingXform.DRAWING_ATTRIBUTES);
|
|
28
|
+
// Shape layout - shared by both notes and form controls
|
|
29
|
+
xmlStream.openNode("o:shapelayout", { "v:ext": "edit" });
|
|
30
|
+
xmlStream.leafNode("o:idmap", { "v:ext": "edit", data: 1 });
|
|
31
|
+
xmlStream.closeNode();
|
|
32
|
+
// Shapetype 202 for notes/comments
|
|
33
|
+
if (hasComments) {
|
|
34
|
+
xmlStream.openNode("v:shapetype", {
|
|
35
|
+
id: "_x0000_t202",
|
|
36
|
+
coordsize: "21600,21600",
|
|
37
|
+
"o:spt": 202,
|
|
38
|
+
path: "m,l,21600r21600,l21600,xe"
|
|
39
|
+
});
|
|
40
|
+
xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
|
|
41
|
+
xmlStream.leafNode("v:path", { gradientshapeok: "t", "o:connecttype": "rect" });
|
|
42
|
+
xmlStream.closeNode();
|
|
43
|
+
}
|
|
44
|
+
// Shapetype 201 for form control checkboxes
|
|
45
|
+
if (hasFormControls) {
|
|
46
|
+
xmlStream.openNode("v:shapetype", {
|
|
47
|
+
id: "_x0000_t201",
|
|
48
|
+
coordsize: "21600,21600",
|
|
49
|
+
"o:spt": "201",
|
|
50
|
+
path: "m,l,21600r21600,l21600,xe"
|
|
51
|
+
});
|
|
52
|
+
xmlStream.leafNode("v:stroke", { joinstyle: "miter" });
|
|
53
|
+
xmlStream.leafNode("v:path", {
|
|
54
|
+
shadowok: "f",
|
|
55
|
+
"o:extrusionok": "f",
|
|
56
|
+
strokeok: "f",
|
|
57
|
+
fillok: "f",
|
|
58
|
+
"o:connecttype": "rect"
|
|
59
|
+
});
|
|
60
|
+
xmlStream.leafNode("o:lock", { "v:ext": "edit", shapetype: "t" });
|
|
61
|
+
xmlStream.closeNode();
|
|
62
|
+
}
|
|
63
|
+
// Render comment shapes
|
|
64
|
+
if (hasComments) {
|
|
65
|
+
const comments = renderModel.comments;
|
|
66
|
+
for (let i = 0; i < comments.length; i++) {
|
|
67
|
+
this.map["v:shape"].render(xmlStream, comments[i], i);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Render form control shapes
|
|
71
|
+
if (hasFormControls) {
|
|
72
|
+
for (const control of renderModel.formControls) {
|
|
73
|
+
this._renderCheckboxShape(xmlStream, control);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
xmlStream.closeNode();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Render a checkbox form control shape
|
|
80
|
+
*/
|
|
81
|
+
_renderCheckboxShape(xmlStream, control) {
|
|
82
|
+
const shapeAttrs = {
|
|
83
|
+
id: `_x0000_s${control.shapeId}`,
|
|
84
|
+
type: "#_x0000_t201",
|
|
85
|
+
style: form_control_1.FormCheckbox.getVmlStyle(control),
|
|
86
|
+
"o:insetmode": "auto",
|
|
87
|
+
fillcolor: "buttonFace [67]",
|
|
88
|
+
strokecolor: "windowText [64]",
|
|
89
|
+
"o:preferrelative": "t",
|
|
90
|
+
filled: "f",
|
|
91
|
+
stroked: "f"
|
|
92
|
+
};
|
|
93
|
+
xmlStream.openNode("v:shape", shapeAttrs);
|
|
94
|
+
// Fill element
|
|
95
|
+
xmlStream.leafNode("v:fill", { "o:detectmouseclick": "t" });
|
|
96
|
+
// Lock element
|
|
97
|
+
xmlStream.leafNode("o:lock", { "v:ext": "edit", text: "t" });
|
|
98
|
+
// Textbox for label
|
|
99
|
+
if (control.text) {
|
|
100
|
+
xmlStream.openNode("v:textbox", {
|
|
101
|
+
style: "mso-direction-alt:auto",
|
|
102
|
+
"o:singleclick": "t"
|
|
103
|
+
});
|
|
104
|
+
xmlStream.openNode("div", { style: "text-align:left" });
|
|
105
|
+
xmlStream.openNode("font", { face: "Tahoma", size: "160", color: "auto" });
|
|
106
|
+
xmlStream.writeText(control.text);
|
|
107
|
+
xmlStream.closeNode(); // font
|
|
108
|
+
xmlStream.closeNode(); // div
|
|
109
|
+
xmlStream.closeNode(); // v:textbox
|
|
110
|
+
}
|
|
111
|
+
// ClientData - the core of the checkbox control
|
|
112
|
+
xmlStream.openNode("x:ClientData", { ObjectType: "Checkbox" });
|
|
113
|
+
// Anchor position
|
|
114
|
+
xmlStream.openNode("x:Anchor");
|
|
115
|
+
xmlStream.writeText(form_control_1.FormCheckbox.getVmlAnchor(control));
|
|
116
|
+
xmlStream.closeNode();
|
|
117
|
+
// Print settings
|
|
118
|
+
xmlStream.leafNode("x:PrintObject", undefined, control.print ? "True" : "False");
|
|
119
|
+
xmlStream.leafNode("x:AutoFill", undefined, "False");
|
|
120
|
+
xmlStream.leafNode("x:AutoLine", undefined, "False");
|
|
121
|
+
xmlStream.leafNode("x:TextVAlign", undefined, "Center");
|
|
122
|
+
// Linked cell
|
|
123
|
+
if (control.link) {
|
|
124
|
+
xmlStream.leafNode("x:FmlaLink", undefined, control.link);
|
|
125
|
+
}
|
|
126
|
+
// 3D appearance
|
|
127
|
+
if (control.noThreeD) {
|
|
128
|
+
xmlStream.leafNode("x:NoThreeD");
|
|
129
|
+
}
|
|
130
|
+
// Checked state (0 = unchecked, 1 = checked, 2 = mixed)
|
|
131
|
+
xmlStream.leafNode("x:Checked", undefined, String(form_control_1.FormCheckbox.getVmlCheckedValue(control)));
|
|
132
|
+
xmlStream.closeNode(); // x:ClientData
|
|
133
|
+
xmlStream.closeNode(); // v:shape
|
|
134
|
+
}
|
|
135
|
+
// Parsing - delegate to VmlShapeXform for notes
|
|
136
|
+
parseOpen(node) {
|
|
137
|
+
if (this.parser) {
|
|
138
|
+
this.parser.parseOpen(node);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
switch (node.name) {
|
|
142
|
+
case this.tag:
|
|
143
|
+
this.reset();
|
|
144
|
+
this.model = {
|
|
145
|
+
comments: [],
|
|
146
|
+
formControls: []
|
|
147
|
+
};
|
|
148
|
+
break;
|
|
149
|
+
default:
|
|
150
|
+
this.parser = this.map[node.name];
|
|
151
|
+
if (this.parser) {
|
|
152
|
+
this.parser.parseOpen(node);
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
parseText(text) {
|
|
159
|
+
if (this.parser) {
|
|
160
|
+
this.parser.parseText(text);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
parseClose(name) {
|
|
164
|
+
if (this.parser) {
|
|
165
|
+
if (!this.parser.parseClose(name)) {
|
|
166
|
+
this.model.comments.push(this.parser.model);
|
|
167
|
+
this.parser = undefined;
|
|
168
|
+
}
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
switch (name) {
|
|
172
|
+
case this.tag:
|
|
173
|
+
return false;
|
|
174
|
+
default:
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.VmlDrawingXform = VmlDrawingXform;
|
|
180
|
+
VmlDrawingXform.DRAWING_ATTRIBUTES = {
|
|
181
|
+
"xmlns:v": "urn:schemas-microsoft-com:vml",
|
|
182
|
+
"xmlns:o": "urn:schemas-microsoft-com:office:office",
|
|
183
|
+
"xmlns:x": "urn:schemas-microsoft-com:office:excel"
|
|
184
|
+
};
|
|
@@ -278,6 +278,29 @@ class WorkSheetXform extends base_xform_1.BaseXform {
|
|
|
278
278
|
Target: (0, ooxml_paths_1.pivotTableRelTargetFromWorksheet)(pivotTable.tableNumber)
|
|
279
279
|
});
|
|
280
280
|
});
|
|
281
|
+
// prepare form controls (legacy checkboxes)
|
|
282
|
+
// Form controls share the VML file with comments, but need separate ctrlProp relationships
|
|
283
|
+
if (model.formControls && model.formControls.length > 0) {
|
|
284
|
+
// If no comments, we need to add the VML drawing relationship for form controls
|
|
285
|
+
if (model.comments.length === 0) {
|
|
286
|
+
rels.push({
|
|
287
|
+
Id: nextRid(rels),
|
|
288
|
+
Type: rel_type_1.RelType.VmlDrawing,
|
|
289
|
+
Target: (0, ooxml_paths_1.vmlDrawingRelTargetFromWorksheet)(model.id)
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
// Add ctrlProp relationships for each form control
|
|
293
|
+
for (const control of model.formControls) {
|
|
294
|
+
const globalCtrlPropId = options.formControlRefs.length + 1;
|
|
295
|
+
control.ctrlPropId = globalCtrlPropId;
|
|
296
|
+
rels.push({
|
|
297
|
+
Id: nextRid(rels),
|
|
298
|
+
Type: rel_type_1.RelType.CtrlProp,
|
|
299
|
+
Target: (0, ooxml_paths_1.ctrlPropRelTargetFromWorksheet)(globalCtrlPropId)
|
|
300
|
+
});
|
|
301
|
+
options.formControlRefs.push(globalCtrlPropId);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
281
304
|
// prepare ext items
|
|
282
305
|
this.map.extLst.prepare(model, options);
|
|
283
306
|
}
|