@niicojs/excel 0.2.5 → 0.2.7
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/index.cjs +190 -16
- package/dist/index.d.cts +30 -7
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +30 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +190 -16
- package/package.json +1 -1
- package/src/pivot-table.ts +86 -23
- package/src/styles.ts +64 -4
- package/src/types.ts +4 -2
- package/src/workbook.ts +4 -0
package/dist/index.js
CHANGED
|
@@ -1115,6 +1115,129 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1115
1115
|
}
|
|
1116
1116
|
}
|
|
1117
1117
|
|
|
1118
|
+
/**
|
|
1119
|
+
* Excel built-in number format IDs (0-163 are reserved).
|
|
1120
|
+
* These formats don't need to be defined in the numFmts element.
|
|
1121
|
+
*/ const BUILTIN_NUM_FMTS = new Map([
|
|
1122
|
+
[
|
|
1123
|
+
'General',
|
|
1124
|
+
0
|
|
1125
|
+
],
|
|
1126
|
+
[
|
|
1127
|
+
'0',
|
|
1128
|
+
1
|
|
1129
|
+
],
|
|
1130
|
+
[
|
|
1131
|
+
'0.00',
|
|
1132
|
+
2
|
|
1133
|
+
],
|
|
1134
|
+
[
|
|
1135
|
+
'#,##0',
|
|
1136
|
+
3
|
|
1137
|
+
],
|
|
1138
|
+
[
|
|
1139
|
+
'#,##0.00',
|
|
1140
|
+
4
|
|
1141
|
+
],
|
|
1142
|
+
[
|
|
1143
|
+
'0%',
|
|
1144
|
+
9
|
|
1145
|
+
],
|
|
1146
|
+
[
|
|
1147
|
+
'0.00%',
|
|
1148
|
+
10
|
|
1149
|
+
],
|
|
1150
|
+
[
|
|
1151
|
+
'0.00E+00',
|
|
1152
|
+
11
|
|
1153
|
+
],
|
|
1154
|
+
[
|
|
1155
|
+
'# ?/?',
|
|
1156
|
+
12
|
|
1157
|
+
],
|
|
1158
|
+
[
|
|
1159
|
+
'# ??/??',
|
|
1160
|
+
13
|
|
1161
|
+
],
|
|
1162
|
+
[
|
|
1163
|
+
'mm-dd-yy',
|
|
1164
|
+
14
|
|
1165
|
+
],
|
|
1166
|
+
[
|
|
1167
|
+
'd-mmm-yy',
|
|
1168
|
+
15
|
|
1169
|
+
],
|
|
1170
|
+
[
|
|
1171
|
+
'd-mmm',
|
|
1172
|
+
16
|
|
1173
|
+
],
|
|
1174
|
+
[
|
|
1175
|
+
'mmm-yy',
|
|
1176
|
+
17
|
|
1177
|
+
],
|
|
1178
|
+
[
|
|
1179
|
+
'h:mm AM/PM',
|
|
1180
|
+
18
|
|
1181
|
+
],
|
|
1182
|
+
[
|
|
1183
|
+
'h:mm:ss AM/PM',
|
|
1184
|
+
19
|
|
1185
|
+
],
|
|
1186
|
+
[
|
|
1187
|
+
'h:mm',
|
|
1188
|
+
20
|
|
1189
|
+
],
|
|
1190
|
+
[
|
|
1191
|
+
'h:mm:ss',
|
|
1192
|
+
21
|
|
1193
|
+
],
|
|
1194
|
+
[
|
|
1195
|
+
'm/d/yy h:mm',
|
|
1196
|
+
22
|
|
1197
|
+
],
|
|
1198
|
+
[
|
|
1199
|
+
'#,##0 ;(#,##0)',
|
|
1200
|
+
37
|
|
1201
|
+
],
|
|
1202
|
+
[
|
|
1203
|
+
'#,##0 ;[Red](#,##0)',
|
|
1204
|
+
38
|
|
1205
|
+
],
|
|
1206
|
+
[
|
|
1207
|
+
'#,##0.00;(#,##0.00)',
|
|
1208
|
+
39
|
|
1209
|
+
],
|
|
1210
|
+
[
|
|
1211
|
+
'#,##0.00;[Red](#,##0.00)',
|
|
1212
|
+
40
|
|
1213
|
+
],
|
|
1214
|
+
[
|
|
1215
|
+
'mm:ss',
|
|
1216
|
+
45
|
|
1217
|
+
],
|
|
1218
|
+
[
|
|
1219
|
+
'[h]:mm:ss',
|
|
1220
|
+
46
|
|
1221
|
+
],
|
|
1222
|
+
[
|
|
1223
|
+
'mmss.0',
|
|
1224
|
+
47
|
|
1225
|
+
],
|
|
1226
|
+
[
|
|
1227
|
+
'##0.0E+0',
|
|
1228
|
+
48
|
|
1229
|
+
],
|
|
1230
|
+
[
|
|
1231
|
+
'@',
|
|
1232
|
+
49
|
|
1233
|
+
]
|
|
1234
|
+
]);
|
|
1235
|
+
/**
|
|
1236
|
+
* Reverse lookup: built-in format ID -> format code
|
|
1237
|
+
*/ const BUILTIN_NUM_FMT_CODES = new Map(Array.from(BUILTIN_NUM_FMTS.entries()).map(([code, id])=>[
|
|
1238
|
+
id,
|
|
1239
|
+
code
|
|
1240
|
+
]));
|
|
1118
1241
|
/**
|
|
1119
1242
|
* Normalize a color to ARGB format (8 hex chars).
|
|
1120
1243
|
* Accepts: "#RGB", "#RRGGBB", "RGB", "RRGGBB", "AARRGGBB", "#AARRGGBB"
|
|
@@ -1305,7 +1428,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1305
1428
|
const font = this._fonts[xf.fontId];
|
|
1306
1429
|
const fill = this._fills[xf.fillId];
|
|
1307
1430
|
const border = this._borders[xf.borderId];
|
|
1308
|
-
|
|
1431
|
+
// Check custom formats first, then fall back to built-in format codes
|
|
1432
|
+
const numFmt = this._numFmts.get(xf.numFmtId) ?? BUILTIN_NUM_FMT_CODES.get(xf.numFmtId);
|
|
1309
1433
|
const style = {};
|
|
1310
1434
|
if (font) {
|
|
1311
1435
|
if (font.bold) style.bold = true;
|
|
@@ -1437,16 +1561,30 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1437
1561
|
return this._borders.length - 1;
|
|
1438
1562
|
}
|
|
1439
1563
|
_findOrCreateNumFmt(format) {
|
|
1440
|
-
// Check
|
|
1564
|
+
// Check built-in formats first (IDs 0-163)
|
|
1565
|
+
const builtinId = BUILTIN_NUM_FMTS.get(format);
|
|
1566
|
+
if (builtinId !== undefined) {
|
|
1567
|
+
return builtinId;
|
|
1568
|
+
}
|
|
1569
|
+
// Check if already exists in custom formats
|
|
1441
1570
|
for (const [id, code] of this._numFmts){
|
|
1442
1571
|
if (code === format) return id;
|
|
1443
1572
|
}
|
|
1444
|
-
// Create new
|
|
1445
|
-
const
|
|
1573
|
+
// Create new custom format (IDs 164+)
|
|
1574
|
+
const existingIds = Array.from(this._numFmts.keys());
|
|
1575
|
+
const id = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 164;
|
|
1446
1576
|
this._numFmts.set(id, format);
|
|
1447
1577
|
return id;
|
|
1448
1578
|
}
|
|
1449
1579
|
/**
|
|
1580
|
+
* Get or create a number format ID for the given format string.
|
|
1581
|
+
* Returns built-in IDs (0-163) for standard formats, or creates custom IDs (164+).
|
|
1582
|
+
* @param format - The number format string (e.g., '0.00', '#,##0', '$#,##0.00')
|
|
1583
|
+
*/ getOrCreateNumFmtId(format) {
|
|
1584
|
+
this._dirty = true;
|
|
1585
|
+
return this._findOrCreateNumFmt(format);
|
|
1586
|
+
}
|
|
1587
|
+
/**
|
|
1450
1588
|
* Check if styles have been modified
|
|
1451
1589
|
*/ get dirty() {
|
|
1452
1590
|
return this._dirty;
|
|
@@ -1626,6 +1764,7 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1626
1764
|
this._columnFields = [];
|
|
1627
1765
|
this._valueFields = [];
|
|
1628
1766
|
this._filterFields = [];
|
|
1767
|
+
this._styles = null;
|
|
1629
1768
|
this._name = name;
|
|
1630
1769
|
this._cache = cache;
|
|
1631
1770
|
this._targetSheet = targetSheet;
|
|
@@ -1660,6 +1799,13 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1660
1799
|
return this._pivotTableIndex;
|
|
1661
1800
|
}
|
|
1662
1801
|
/**
|
|
1802
|
+
* Set the styles reference for number format resolution
|
|
1803
|
+
* @internal
|
|
1804
|
+
*/ setStyles(styles) {
|
|
1805
|
+
this._styles = styles;
|
|
1806
|
+
return this;
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1663
1809
|
* Add a field to the row area
|
|
1664
1810
|
* @param fieldName - Name of the source field (column header)
|
|
1665
1811
|
*/ addRowField(fieldName) {
|
|
@@ -1689,23 +1835,40 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1689
1835
|
});
|
|
1690
1836
|
return this;
|
|
1691
1837
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1838
|
+
addValueField(fieldNameOrConfig, aggregation = 'sum', displayName, numberFormat) {
|
|
1839
|
+
// Normalize arguments to a common form
|
|
1840
|
+
let fieldName;
|
|
1841
|
+
let agg;
|
|
1842
|
+
let name;
|
|
1843
|
+
let format;
|
|
1844
|
+
if (typeof fieldNameOrConfig === 'object') {
|
|
1845
|
+
fieldName = fieldNameOrConfig.field;
|
|
1846
|
+
agg = fieldNameOrConfig.aggregation ?? 'sum';
|
|
1847
|
+
name = fieldNameOrConfig.name;
|
|
1848
|
+
format = fieldNameOrConfig.numberFormat;
|
|
1849
|
+
} else {
|
|
1850
|
+
fieldName = fieldNameOrConfig;
|
|
1851
|
+
agg = aggregation;
|
|
1852
|
+
name = displayName;
|
|
1853
|
+
format = numberFormat;
|
|
1854
|
+
}
|
|
1698
1855
|
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
1699
1856
|
if (fieldIndex < 0) {
|
|
1700
1857
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
1701
1858
|
}
|
|
1702
|
-
const defaultName = `${
|
|
1859
|
+
const defaultName = `${agg.charAt(0).toUpperCase() + agg.slice(1)} of ${fieldName}`;
|
|
1860
|
+
// Resolve numFmtId immediately if format is provided and styles are available
|
|
1861
|
+
let numFmtId;
|
|
1862
|
+
if (format && this._styles) {
|
|
1863
|
+
numFmtId = this._styles.getOrCreateNumFmtId(format);
|
|
1864
|
+
}
|
|
1703
1865
|
this._valueFields.push({
|
|
1704
1866
|
fieldName,
|
|
1705
1867
|
fieldIndex,
|
|
1706
1868
|
axis: 'value',
|
|
1707
|
-
aggregation,
|
|
1708
|
-
displayName:
|
|
1869
|
+
aggregation: agg,
|
|
1870
|
+
displayName: name || defaultName,
|
|
1871
|
+
numFmtId
|
|
1709
1872
|
});
|
|
1710
1873
|
return this;
|
|
1711
1874
|
}
|
|
@@ -1828,17 +1991,26 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1828
1991
|
}
|
|
1829
1992
|
// Data fields (values)
|
|
1830
1993
|
if (this._valueFields.length > 0) {
|
|
1831
|
-
const dataFieldNodes = this._valueFields.map((f)=>
|
|
1994
|
+
const dataFieldNodes = this._valueFields.map((f)=>{
|
|
1995
|
+
const attrs = {
|
|
1832
1996
|
name: f.displayName || f.fieldName,
|
|
1833
1997
|
fld: String(f.fieldIndex),
|
|
1834
1998
|
baseField: '0',
|
|
1835
1999
|
baseItem: '0',
|
|
1836
2000
|
subtotal: f.aggregation || 'sum'
|
|
1837
|
-
}
|
|
2001
|
+
};
|
|
2002
|
+
// Add numFmtId if it was resolved during addValueField
|
|
2003
|
+
if (f.numFmtId !== undefined) {
|
|
2004
|
+
attrs.numFmtId = String(f.numFmtId);
|
|
2005
|
+
}
|
|
2006
|
+
return createElement('dataField', attrs, []);
|
|
2007
|
+
});
|
|
1838
2008
|
children.push(createElement('dataFields', {
|
|
1839
2009
|
count: String(dataFieldNodes.length)
|
|
1840
2010
|
}, dataFieldNodes));
|
|
1841
2011
|
}
|
|
2012
|
+
// Check if any value field has a number format
|
|
2013
|
+
const hasNumberFormats = this._valueFields.some((f)=>f.numFmtId !== undefined);
|
|
1842
2014
|
// Pivot table style
|
|
1843
2015
|
children.push(createElement('pivotTableStyleInfo', {
|
|
1844
2016
|
name: 'PivotStyleMedium9',
|
|
@@ -1853,7 +2025,7 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
1853
2025
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
1854
2026
|
name: this._name,
|
|
1855
2027
|
cacheId: String(this._cache.cacheId),
|
|
1856
|
-
applyNumberFormats: '0',
|
|
2028
|
+
applyNumberFormats: hasNumberFormats ? '1' : '0',
|
|
1857
2029
|
applyBorderFormats: '0',
|
|
1858
2030
|
applyFontFormats: '0',
|
|
1859
2031
|
applyPatternFormats: '0',
|
|
@@ -2787,6 +2959,8 @@ const builder = new XMLBuilder(builderOptions);
|
|
|
2787
2959
|
// Create pivot table
|
|
2788
2960
|
const pivotTableIndex = this._pivotTables.length + 1;
|
|
2789
2961
|
const pivotTable = new PivotTable(config.name, cache, targetSheet, targetCell, targetAddr.row + 1, targetAddr.col, pivotTableIndex);
|
|
2962
|
+
// Set styles reference for number format resolution
|
|
2963
|
+
pivotTable.setStyles(this._styles);
|
|
2790
2964
|
this._pivotTables.push(pivotTable);
|
|
2791
2965
|
return pivotTable;
|
|
2792
2966
|
}
|
package/package.json
CHANGED
package/src/pivot-table.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { AggregationType, PivotFieldAxis } from './types';
|
|
1
|
+
import type { AggregationType, PivotFieldAxis, PivotValueConfig } from './types';
|
|
2
|
+
import type { Styles } from './styles';
|
|
2
3
|
import { PivotCache } from './pivot-cache';
|
|
3
4
|
import { createElement, stringifyXml, XmlNode } from './utils/xml';
|
|
4
5
|
|
|
@@ -11,6 +12,7 @@ interface FieldAssignment {
|
|
|
11
12
|
axis: PivotFieldAxis;
|
|
12
13
|
aggregation?: AggregationType;
|
|
13
14
|
displayName?: string;
|
|
15
|
+
numFmtId?: number;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
@@ -30,6 +32,7 @@ export class PivotTable {
|
|
|
30
32
|
private _filterFields: FieldAssignment[] = [];
|
|
31
33
|
|
|
32
34
|
private _pivotTableIndex: number;
|
|
35
|
+
private _styles: Styles | null = null;
|
|
33
36
|
|
|
34
37
|
constructor(
|
|
35
38
|
name: string,
|
|
@@ -84,6 +87,15 @@ export class PivotTable {
|
|
|
84
87
|
return this._pivotTableIndex;
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Set the styles reference for number format resolution
|
|
92
|
+
* @internal
|
|
93
|
+
*/
|
|
94
|
+
setStyles(styles: Styles): this {
|
|
95
|
+
this._styles = styles;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
/**
|
|
88
100
|
* Add a field to the row area
|
|
89
101
|
* @param fieldName - Name of the source field (column header)
|
|
@@ -123,25 +135,70 @@ export class PivotTable {
|
|
|
123
135
|
}
|
|
124
136
|
|
|
125
137
|
/**
|
|
126
|
-
* Add a field to the values area with aggregation
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
138
|
+
* Add a field to the values area with aggregation.
|
|
139
|
+
*
|
|
140
|
+
* Supports two call signatures:
|
|
141
|
+
* - Positional: `addValueField(fieldName, aggregation?, displayName?, numberFormat?)`
|
|
142
|
+
* - Object: `addValueField({ field, aggregation?, name?, numberFormat? })`
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* // Positional arguments
|
|
146
|
+
* pivot.addValueField('Sales', 'sum', 'Total Sales', '$#,##0.00');
|
|
147
|
+
*
|
|
148
|
+
* // Object form
|
|
149
|
+
* pivot.addValueField({ field: 'Sales', aggregation: 'sum', name: 'Total Sales', numberFormat: '$#,##0.00' });
|
|
130
150
|
*/
|
|
131
|
-
addValueField(
|
|
151
|
+
addValueField(config: PivotValueConfig): this;
|
|
152
|
+
addValueField(
|
|
153
|
+
fieldName: string,
|
|
154
|
+
aggregation?: AggregationType,
|
|
155
|
+
displayName?: string,
|
|
156
|
+
numberFormat?: string,
|
|
157
|
+
): this;
|
|
158
|
+
addValueField(
|
|
159
|
+
fieldNameOrConfig: string | PivotValueConfig,
|
|
160
|
+
aggregation: AggregationType = 'sum',
|
|
161
|
+
displayName?: string,
|
|
162
|
+
numberFormat?: string,
|
|
163
|
+
): this {
|
|
164
|
+
// Normalize arguments to a common form
|
|
165
|
+
let fieldName: string;
|
|
166
|
+
let agg: AggregationType;
|
|
167
|
+
let name: string | undefined;
|
|
168
|
+
let format: string | undefined;
|
|
169
|
+
|
|
170
|
+
if (typeof fieldNameOrConfig === 'object') {
|
|
171
|
+
fieldName = fieldNameOrConfig.field;
|
|
172
|
+
agg = fieldNameOrConfig.aggregation ?? 'sum';
|
|
173
|
+
name = fieldNameOrConfig.name;
|
|
174
|
+
format = fieldNameOrConfig.numberFormat;
|
|
175
|
+
} else {
|
|
176
|
+
fieldName = fieldNameOrConfig;
|
|
177
|
+
agg = aggregation;
|
|
178
|
+
name = displayName;
|
|
179
|
+
format = numberFormat;
|
|
180
|
+
}
|
|
181
|
+
|
|
132
182
|
const fieldIndex = this._cache.getFieldIndex(fieldName);
|
|
133
183
|
if (fieldIndex < 0) {
|
|
134
184
|
throw new Error(`Field not found in source data: ${fieldName}`);
|
|
135
185
|
}
|
|
136
186
|
|
|
137
|
-
const defaultName = `${
|
|
187
|
+
const defaultName = `${agg.charAt(0).toUpperCase() + agg.slice(1)} of ${fieldName}`;
|
|
188
|
+
|
|
189
|
+
// Resolve numFmtId immediately if format is provided and styles are available
|
|
190
|
+
let numFmtId: number | undefined;
|
|
191
|
+
if (format && this._styles) {
|
|
192
|
+
numFmtId = this._styles.getOrCreateNumFmtId(format);
|
|
193
|
+
}
|
|
138
194
|
|
|
139
195
|
this._valueFields.push({
|
|
140
196
|
fieldName,
|
|
141
197
|
fieldIndex,
|
|
142
198
|
axis: 'value',
|
|
143
|
-
aggregation,
|
|
144
|
-
displayName:
|
|
199
|
+
aggregation: agg,
|
|
200
|
+
displayName: name || defaultName,
|
|
201
|
+
numFmtId,
|
|
145
202
|
});
|
|
146
203
|
|
|
147
204
|
return this;
|
|
@@ -251,22 +308,28 @@ export class PivotTable {
|
|
|
251
308
|
|
|
252
309
|
// Data fields (values)
|
|
253
310
|
if (this._valueFields.length > 0) {
|
|
254
|
-
const dataFieldNodes = this._valueFields.map((f) =>
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
311
|
+
const dataFieldNodes = this._valueFields.map((f) => {
|
|
312
|
+
const attrs: Record<string, string> = {
|
|
313
|
+
name: f.displayName || f.fieldName,
|
|
314
|
+
fld: String(f.fieldIndex),
|
|
315
|
+
baseField: '0',
|
|
316
|
+
baseItem: '0',
|
|
317
|
+
subtotal: f.aggregation || 'sum',
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// Add numFmtId if it was resolved during addValueField
|
|
321
|
+
if (f.numFmtId !== undefined) {
|
|
322
|
+
attrs.numFmtId = String(f.numFmtId);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return createElement('dataField', attrs, []);
|
|
326
|
+
});
|
|
267
327
|
children.push(createElement('dataFields', { count: String(dataFieldNodes.length) }, dataFieldNodes));
|
|
268
328
|
}
|
|
269
329
|
|
|
330
|
+
// Check if any value field has a number format
|
|
331
|
+
const hasNumberFormats = this._valueFields.some((f) => f.numFmtId !== undefined);
|
|
332
|
+
|
|
270
333
|
// Pivot table style
|
|
271
334
|
children.push(
|
|
272
335
|
createElement(
|
|
@@ -290,7 +353,7 @@ export class PivotTable {
|
|
|
290
353
|
'xmlns:r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
|
|
291
354
|
name: this._name,
|
|
292
355
|
cacheId: String(this._cache.cacheId),
|
|
293
|
-
applyNumberFormats: '0',
|
|
356
|
+
applyNumberFormats: hasNumberFormats ? '1' : '0',
|
|
294
357
|
applyBorderFormats: '0',
|
|
295
358
|
applyFontFormats: '0',
|
|
296
359
|
applyPatternFormats: '0',
|
package/src/styles.ts
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
import type { CellStyle, BorderType } from './types';
|
|
2
2
|
import { parseXml, findElement, getChildren, getAttr, XmlNode, stringifyXml, createElement } from './utils/xml';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Excel built-in number format IDs (0-163 are reserved).
|
|
6
|
+
* These formats don't need to be defined in the numFmts element.
|
|
7
|
+
*/
|
|
8
|
+
const BUILTIN_NUM_FMTS: Map<string, number> = new Map([
|
|
9
|
+
['General', 0],
|
|
10
|
+
['0', 1],
|
|
11
|
+
['0.00', 2],
|
|
12
|
+
['#,##0', 3],
|
|
13
|
+
['#,##0.00', 4],
|
|
14
|
+
['0%', 9],
|
|
15
|
+
['0.00%', 10],
|
|
16
|
+
['0.00E+00', 11],
|
|
17
|
+
['# ?/?', 12],
|
|
18
|
+
['# ??/??', 13],
|
|
19
|
+
['mm-dd-yy', 14],
|
|
20
|
+
['d-mmm-yy', 15],
|
|
21
|
+
['d-mmm', 16],
|
|
22
|
+
['mmm-yy', 17],
|
|
23
|
+
['h:mm AM/PM', 18],
|
|
24
|
+
['h:mm:ss AM/PM', 19],
|
|
25
|
+
['h:mm', 20],
|
|
26
|
+
['h:mm:ss', 21],
|
|
27
|
+
['m/d/yy h:mm', 22],
|
|
28
|
+
['#,##0 ;(#,##0)', 37],
|
|
29
|
+
['#,##0 ;[Red](#,##0)', 38],
|
|
30
|
+
['#,##0.00;(#,##0.00)', 39],
|
|
31
|
+
['#,##0.00;[Red](#,##0.00)', 40],
|
|
32
|
+
['mm:ss', 45],
|
|
33
|
+
['[h]:mm:ss', 46],
|
|
34
|
+
['mmss.0', 47],
|
|
35
|
+
['##0.0E+0', 48],
|
|
36
|
+
['@', 49],
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Reverse lookup: built-in format ID -> format code
|
|
41
|
+
*/
|
|
42
|
+
const BUILTIN_NUM_FMT_CODES: Map<number, string> = new Map(
|
|
43
|
+
Array.from(BUILTIN_NUM_FMTS.entries()).map(([code, id]) => [id, code]),
|
|
44
|
+
);
|
|
45
|
+
|
|
4
46
|
/**
|
|
5
47
|
* Normalize a color to ARGB format (8 hex chars).
|
|
6
48
|
* Accepts: "#RGB", "#RRGGBB", "RGB", "RRGGBB", "AARRGGBB", "#AARRGGBB"
|
|
@@ -234,7 +276,8 @@ export class Styles {
|
|
|
234
276
|
const font = this._fonts[xf.fontId];
|
|
235
277
|
const fill = this._fills[xf.fillId];
|
|
236
278
|
const border = this._borders[xf.borderId];
|
|
237
|
-
|
|
279
|
+
// Check custom formats first, then fall back to built-in format codes
|
|
280
|
+
const numFmt = this._numFmts.get(xf.numFmtId) ?? BUILTIN_NUM_FMT_CODES.get(xf.numFmtId);
|
|
238
281
|
|
|
239
282
|
const style: CellStyle = {};
|
|
240
283
|
|
|
@@ -403,17 +446,34 @@ export class Styles {
|
|
|
403
446
|
}
|
|
404
447
|
|
|
405
448
|
private _findOrCreateNumFmt(format: string): number {
|
|
406
|
-
// Check
|
|
449
|
+
// Check built-in formats first (IDs 0-163)
|
|
450
|
+
const builtinId = BUILTIN_NUM_FMTS.get(format);
|
|
451
|
+
if (builtinId !== undefined) {
|
|
452
|
+
return builtinId;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Check if already exists in custom formats
|
|
407
456
|
for (const [id, code] of this._numFmts) {
|
|
408
457
|
if (code === format) return id;
|
|
409
458
|
}
|
|
410
459
|
|
|
411
|
-
// Create new
|
|
412
|
-
const
|
|
460
|
+
// Create new custom format (IDs 164+)
|
|
461
|
+
const existingIds = Array.from(this._numFmts.keys());
|
|
462
|
+
const id = existingIds.length > 0 ? Math.max(...existingIds) + 1 : 164;
|
|
413
463
|
this._numFmts.set(id, format);
|
|
414
464
|
return id;
|
|
415
465
|
}
|
|
416
466
|
|
|
467
|
+
/**
|
|
468
|
+
* Get or create a number format ID for the given format string.
|
|
469
|
+
* Returns built-in IDs (0-163) for standard formats, or creates custom IDs (164+).
|
|
470
|
+
* @param format - The number format string (e.g., '0.00', '#,##0', '$#,##0.00')
|
|
471
|
+
*/
|
|
472
|
+
getOrCreateNumFmtId(format: string): number {
|
|
473
|
+
this._dirty = true;
|
|
474
|
+
return this._findOrCreateNumFmt(format);
|
|
475
|
+
}
|
|
476
|
+
|
|
417
477
|
/**
|
|
418
478
|
* Check if styles have been modified
|
|
419
479
|
*/
|
package/src/types.ts
CHANGED
|
@@ -119,10 +119,12 @@ export type AggregationType = 'sum' | 'count' | 'average' | 'min' | 'max';
|
|
|
119
119
|
export interface PivotValueConfig {
|
|
120
120
|
/** Source field name (column header) */
|
|
121
121
|
field: string;
|
|
122
|
-
/** Aggregation function */
|
|
123
|
-
aggregation
|
|
122
|
+
/** Aggregation function (default: 'sum') */
|
|
123
|
+
aggregation?: AggregationType;
|
|
124
124
|
/** Display name (e.g., "Sum of Sales") */
|
|
125
125
|
name?: string;
|
|
126
|
+
/** Number format (e.g., '$#,##0.00', '0.00%') */
|
|
127
|
+
numberFormat?: string;
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
/**
|
package/src/workbook.ts
CHANGED