xlkit 1.0.5 → 1.2.0

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/Sheetflow.js DELETED
@@ -1,335 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.XLKit = void 0;
7
- exports.createWorkbook = createWorkbook;
8
- exports.defineSheet = defineSheet;
9
- const exceljs_1 = __importDefault(require("exceljs"));
10
- const style_1 = require("./utils/style");
11
- class XLKit {
12
- constructor() {
13
- this.workbook = new exceljs_1.default.Workbook();
14
- }
15
- addSheet(def, data) {
16
- // Validate Sheet Name
17
- if (!def.name) {
18
- throw new Error('Sheet name is required.');
19
- }
20
- if (def.name.length > 31) {
21
- throw new Error(`Sheet name "${def.name}" exceeds the maximum length of 31 characters.`);
22
- }
23
- // Invalid characters: \ / ? * [ ] :
24
- const invalidChars = /[\\/?*[\]:]/;
25
- if (invalidChars.test(def.name)) {
26
- throw new Error(`Sheet name "${def.name}" contains invalid characters (\\ / ? * [ ] :).`);
27
- }
28
- const sheet = this.workbook.addWorksheet(def.name);
29
- // 1. Setup Columns
30
- const columns = def.columns.map((col, colIndex) => {
31
- let width = col.width;
32
- // Validate Column Key
33
- if (col.key === 'style') {
34
- throw new Error("Column key 'style' is reserved for row styling and cannot be used as a column key.");
35
- }
36
- if (width === 'auto') {
37
- let maxLen = col.header.length * (def.autoWidth?.headerIncluded !== false ? 1 : 0);
38
- // Check data length (sample first 100 rows for performance if needed, currently all)
39
- data.forEach(row => {
40
- const val = row[col.key];
41
- const str = val != null ? String(val) : '';
42
- // Simple full-width check: count as 2 if char code > 255
43
- let len = 0;
44
- for (let i = 0; i < str.length; i++) {
45
- len += str.charCodeAt(i) > 255 ? 2 : 1;
46
- }
47
- if (len > maxLen)
48
- maxLen = len;
49
- });
50
- const padding = def.autoWidth?.padding ?? 2;
51
- const constant = def.autoWidth?.charWidthConstant ?? 1.2;
52
- width = (maxLen + padding) * constant;
53
- }
54
- return {
55
- header: col.header,
56
- key: String(col.key),
57
- width: typeof width === 'number' ? width : 15,
58
- style: col.style && typeof col.style === 'object' ? (0, style_1.mapStyle)(col.style) : undefined
59
- };
60
- });
61
- sheet.columns = columns;
62
- // 2. Setup Headers (Multi-line support)
63
- let headerRowCount = 1;
64
- if (def.header?.rows) {
65
- headerRowCount = def.header.rows.length;
66
- // Clear default header row if we are using custom rows
67
- sheet.spliceRows(1, 1);
68
- def.header.rows.forEach((row, rowIndex) => {
69
- const currentSheetRowIndex = rowIndex + 1;
70
- const sheetRow = sheet.getRow(currentSheetRowIndex);
71
- let colIndex = 1;
72
- row.forEach((cellConfig) => {
73
- // Find next available cell (skip merged cells)
74
- while (sheet.getCell(currentSheetRowIndex, colIndex).isMerged) {
75
- colIndex++;
76
- }
77
- const cell = sheet.getCell(currentSheetRowIndex, colIndex);
78
- if (typeof cellConfig === 'string') {
79
- cell.value = cellConfig;
80
- colIndex++;
81
- }
82
- else {
83
- cell.value = cellConfig.value;
84
- if (cellConfig.style) {
85
- cell.style = { ...cell.style, ...(0, style_1.mapStyle)(cellConfig.style) };
86
- }
87
- const rowSpan = cellConfig.rowSpan || 1;
88
- const colSpan = cellConfig.colSpan || 1;
89
- if (rowSpan > 1 || colSpan > 1) {
90
- sheet.mergeCells(currentSheetRowIndex, colIndex, currentSheetRowIndex + rowSpan - 1, colIndex + colSpan - 1);
91
- }
92
- colIndex += colSpan;
93
- }
94
- });
95
- });
96
- }
97
- // 3. Add Data & Apply Row Styles
98
- const dataStartRow = headerRowCount + 1;
99
- data.forEach((row, index) => {
100
- const rowIndex = dataStartRow + index;
101
- // We can't use addRow easily because it appends to the end, but we might have gaps if we messed with rows?
102
- // Actually addRow is fine as long as we are consistent.
103
- // But since we might have complex headers, let's be explicit with getRow to be safe or just use addRow if we know we are at the end.
104
- // For safety with existing header logic, let's use explicit row rendering for data to ensure alignment.
105
- const sheetRow = sheet.getRow(rowIndex);
106
- def.columns.forEach((col, colIndex) => {
107
- const cell = sheetRow.getCell(colIndex + 1);
108
- cell.value = row[col.key];
109
- });
110
- // Apply row-level style from definition
111
- if (def.rows?.style) {
112
- const rowStyle = def.rows.style(row, index);
113
- const mappedStyle = (0, style_1.mapStyle)(rowStyle);
114
- sheetRow.eachCell((cell) => {
115
- cell.style = { ...cell.style, ...mappedStyle };
116
- });
117
- }
118
- // Apply row-level style from data (if 'style' property exists)
119
- if (row.style) {
120
- const dataRowStyle = row.style;
121
- const mappedStyle = (0, style_1.mapStyle)(dataRowStyle);
122
- sheetRow.eachCell((cell) => {
123
- cell.style = { ...cell.style, ...mappedStyle };
124
- });
125
- }
126
- // Apply column-level conditional styles
127
- def.columns.forEach((col, colIndex) => {
128
- if (typeof col.style === 'function') {
129
- const cell = sheetRow.getCell(colIndex + 1);
130
- const cellStyle = col.style(row[col.key], row, index);
131
- cell.style = { ...cell.style, ...(0, style_1.mapStyle)(cellStyle) };
132
- }
133
- if (col.format) {
134
- const cell = sheetRow.getCell(colIndex + 1);
135
- if (typeof col.format === 'string') {
136
- cell.numFmt = col.format;
137
- }
138
- else {
139
- cell.value = col.format(row[col.key]);
140
- }
141
- }
142
- });
143
- sheetRow.commit();
144
- });
145
- // 4. Apply Header Styles (Global)
146
- if (def.header?.style) {
147
- const mappedHeaderStyle = (0, style_1.mapStyle)(def.header.style);
148
- // Apply to all header rows
149
- for (let i = 1; i <= headerRowCount; i++) {
150
- const row = sheet.getRow(i);
151
- row.eachCell((cell) => {
152
- // Merge with existing style (cell specific style takes precedence if we did it right, but here we are applying global header style)
153
- // Usually global header style is base, cell specific is override.
154
- // But here we apply global AFTER. Let's apply it only if no style?
155
- // Or just merge.
156
- cell.style = { ...mappedHeaderStyle, ...cell.style };
157
- });
158
- }
159
- }
160
- // 5. Apply Vertical Merges
161
- def.columns.forEach((col, colIndex) => {
162
- if (col.merge === 'vertical') {
163
- let startRow = dataStartRow;
164
- let previousValue = null;
165
- // Iterate from first data row to last
166
- for (let i = 0; i < data.length; i++) {
167
- const currentRowIndex = dataStartRow + i;
168
- const cell = sheet.getCell(currentRowIndex, colIndex + 1);
169
- const currentValue = cell.value;
170
- if (i === 0) {
171
- previousValue = currentValue;
172
- continue;
173
- }
174
- // If value changed or it's the last row, process the merge
175
- if (currentValue !== previousValue) {
176
- if (currentRowIndex - 1 > startRow) {
177
- sheet.mergeCells(startRow, colIndex + 1, currentRowIndex - 1, colIndex + 1);
178
- }
179
- startRow = currentRowIndex;
180
- previousValue = currentValue;
181
- }
182
- }
183
- // Handle the last group
184
- const lastRowIndex = dataStartRow + data.length; // This is actually the row AFTER the last data row index if we use <
185
- // Wait, loop goes 0 to length-1.
186
- // If i=length-1 (last item), we check logic.
187
- // We need to handle the merge AFTER the loop for the final group.
188
- if (data.length > 0) {
189
- const finalRowIndex = dataStartRow + data.length - 1;
190
- if (finalRowIndex > startRow) {
191
- sheet.mergeCells(startRow, colIndex + 1, finalRowIndex, colIndex + 1);
192
- }
193
- }
194
- }
195
- });
196
- // 6. Apply Horizontal Merges
197
- // We iterate row by row
198
- for (let i = 0; i < data.length; i++) {
199
- const currentRowIndex = dataStartRow + i;
200
- const row = sheet.getRow(currentRowIndex);
201
- let startCol = 1;
202
- let previousValue = null;
203
- let merging = false;
204
- // We need to check which columns are candidates for horizontal merge.
205
- // A simple approach: iterate columns. If current col has merge='horizontal', try to merge with next.
206
- // But we need to group them.
207
- for (let c = 0; c < def.columns.length; c++) {
208
- const colDef = def.columns[c];
209
- const currentCell = row.getCell(c + 1);
210
- const currentValue = currentCell.value;
211
- if (colDef.merge === 'horizontal') {
212
- if (!merging) {
213
- merging = true;
214
- startCol = c + 1;
215
- previousValue = currentValue;
216
- }
217
- else {
218
- if (currentValue !== previousValue) {
219
- // End of a merge group
220
- if ((c + 1) - 1 > startCol) {
221
- sheet.mergeCells(currentRowIndex, startCol, currentRowIndex, c);
222
- }
223
- // Start new group
224
- startCol = c + 1;
225
- previousValue = currentValue;
226
- }
227
- }
228
- }
229
- else {
230
- if (merging) {
231
- // End of merge group because this column is not mergeable
232
- if ((c + 1) - 1 > startCol) {
233
- sheet.mergeCells(currentRowIndex, startCol, currentRowIndex, c);
234
- }
235
- merging = false;
236
- }
237
- }
238
- }
239
- // Check at end of row
240
- if (merging) {
241
- const lastCol = def.columns.length;
242
- if (lastCol > startCol) {
243
- sheet.mergeCells(currentRowIndex, startCol, currentRowIndex, lastCol);
244
- }
245
- }
246
- }
247
- // 7. Apply Borders
248
- if (def.borders === 'all') {
249
- sheet.eachRow((row) => {
250
- row.eachCell((cell) => {
251
- cell.border = {
252
- top: { style: 'thin' },
253
- left: { style: 'thin' },
254
- bottom: { style: 'thin' },
255
- right: { style: 'thin' }
256
- };
257
- });
258
- });
259
- }
260
- else if (def.borders === 'outer') {
261
- const lastRow = sheet.rowCount;
262
- const lastCol = sheet.columnCount;
263
- // Top & Bottom
264
- for (let c = 1; c <= lastCol; c++) {
265
- const topCell = sheet.getCell(1, c);
266
- topCell.border = { ...topCell.border, top: { style: 'thin' } };
267
- const bottomCell = sheet.getCell(lastRow, c);
268
- bottomCell.border = { ...bottomCell.border, bottom: { style: 'thin' } };
269
- }
270
- // Left & Right
271
- for (let r = 1; r <= lastRow; r++) {
272
- const leftCell = sheet.getCell(r, 1);
273
- leftCell.border = { ...leftCell.border, left: { style: 'thin' } };
274
- const rightCell = sheet.getCell(r, lastCol);
275
- rightCell.border = { ...rightCell.border, right: { style: 'thin' } };
276
- }
277
- }
278
- else if (def.borders === 'header-body') {
279
- const lastCol = sheet.columnCount;
280
- // Apply to the last row of the header
281
- for (let c = 1; c <= lastCol; c++) {
282
- const headerCell = sheet.getCell(headerRowCount, c);
283
- headerCell.border = { ...headerCell.border, bottom: { style: 'medium' } };
284
- }
285
- }
286
- return this;
287
- }
288
- async save(path, options) {
289
- if (!path || path.trim() === '') {
290
- throw new Error('File path cannot be empty.');
291
- }
292
- if (typeof process !== 'undefined' && process.versions && process.versions.node) {
293
- const timeout = options?.timeout ?? 10000; // Default 10s
294
- const writePromise = this.workbook.xlsx.writeFile(path);
295
- const timeoutPromise = new Promise((_, reject) => {
296
- setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout);
297
- });
298
- await Promise.race([writePromise, timeoutPromise]);
299
- }
300
- else {
301
- throw new Error('File system access is only available in Node.js environment. Use saveToBuffer() instead.');
302
- }
303
- }
304
- async saveToBuffer(options) {
305
- const timeout = options?.timeout ?? 10000; // Default 10s
306
- const writePromise = this.workbook.xlsx.writeBuffer();
307
- const timeoutPromise = new Promise((_, reject) => {
308
- setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout);
309
- });
310
- const buffer = await Promise.race([writePromise, timeoutPromise]);
311
- return new Uint8Array(buffer);
312
- }
313
- async download(filename, options) {
314
- if (typeof window === 'undefined' || typeof document === 'undefined') {
315
- throw new Error('download() is only available in browser environment. Use save() for Node.js or saveToBuffer() for custom handling.');
316
- }
317
- const buffer = await this.saveToBuffer(options);
318
- const blob = new Blob([buffer.buffer], {
319
- type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
320
- });
321
- const url = URL.createObjectURL(blob);
322
- const a = document.createElement('a');
323
- a.href = url;
324
- a.download = filename;
325
- a.click();
326
- URL.revokeObjectURL(url);
327
- }
328
- }
329
- exports.XLKit = XLKit;
330
- function createWorkbook() {
331
- return new XLKit();
332
- }
333
- function defineSheet(def) {
334
- return def;
335
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC"}
package/dist/types.d.ts DELETED
@@ -1,49 +0,0 @@
1
- import { Alignment, Border, Fill, Font } from 'exceljs';
2
- export type HexColor = string;
3
- export interface XLStyle {
4
- font?: Partial<Font> & {
5
- color?: HexColor | {
6
- argb: string;
7
- };
8
- };
9
- fill?: Partial<Fill> & {
10
- color?: HexColor;
11
- };
12
- alignment?: Partial<Alignment>;
13
- border?: Partial<Border> | 'all' | 'outer' | 'header-body' | 'none';
14
- }
15
- export interface ColumnDef<T> {
16
- key: keyof T;
17
- header: string;
18
- width?: number | 'auto';
19
- merge?: 'vertical' | 'horizontal';
20
- style?: XLStyle | ((val: any, row: T, index: number) => XLStyle);
21
- format?: string | ((val: any) => string);
22
- }
23
- export interface HeaderCell {
24
- value: string;
25
- colSpan?: number;
26
- rowSpan?: number;
27
- style?: XLStyle;
28
- }
29
- export interface HeaderConfig {
30
- rows?: (string | HeaderCell)[][];
31
- style?: XLStyle;
32
- borders?: 'header-body' | 'all' | 'none';
33
- }
34
- export interface SheetDef<T> {
35
- name: string;
36
- columns: ColumnDef<T>[];
37
- header?: HeaderConfig;
38
- rows?: {
39
- style?: (data: T, index: number) => XLStyle;
40
- };
41
- defaultStyle?: XLStyle;
42
- borders?: 'all' | 'outer' | 'header-body' | 'none';
43
- autoWidth?: {
44
- padding?: number;
45
- headerIncluded?: boolean;
46
- charWidthConstant?: number;
47
- };
48
- }
49
- //# sourceMappingURL=types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/D,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;QAAE,KAAK,CAAC,EAAE,QAAQ,GAAG;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;IAC/D,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;QAAE,KAAK,CAAC,EAAE,QAAQ,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,KAAK,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAC;CACrE;AAED,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAClC,KAAK,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACjE,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,CAAC,MAAM,GAAG,UAAU,CAAC,EAAE,EAAE,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,aAAa,GAAG,KAAK,GAAG,MAAM,CAAC;CAC1C;AAED,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;KAC7C,CAAC;IACF,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAC;IACnD,SAAS,CAAC,EAAE;QACV,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,CAAC;CACH"}
package/dist/types.js DELETED
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,2 +0,0 @@
1
- export declare function toArgb(hex: string): string;
2
- //# sourceMappingURL=color.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/utils/color.ts"],"names":[],"mappings":"AAAA,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY1C"}
@@ -1,17 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toArgb = toArgb;
4
- function toArgb(hex) {
5
- if (!hex)
6
- return 'FF000000';
7
- if (hex.startsWith('#')) {
8
- hex = hex.slice(1);
9
- }
10
- if (hex.length === 3) {
11
- hex = hex.split('').map(c => c + c).join('');
12
- }
13
- if (hex.length === 6) {
14
- return 'FF' + hex.toUpperCase();
15
- }
16
- return hex.toUpperCase(); // Assume already ARGB or invalid
17
- }
@@ -1,4 +0,0 @@
1
- import { Style } from 'exceljs';
2
- import { XLStyle } from '../types';
3
- export declare function mapStyle(style: XLStyle): Partial<Style>;
4
- //# sourceMappingURL=style.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../src/utils/style.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAW,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAGnC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAqCvD"}
@@ -1,39 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.mapStyle = mapStyle;
4
- const color_1 = require("./color");
5
- function mapStyle(style) {
6
- const excelStyle = {};
7
- if (style.font) {
8
- excelStyle.font = { ...style.font };
9
- if (typeof style.font.color === 'string') {
10
- excelStyle.font.color = { argb: (0, color_1.toArgb)(style.font.color) };
11
- }
12
- }
13
- if (style.fill) {
14
- if (style.fill.color) {
15
- excelStyle.fill = {
16
- type: 'pattern',
17
- pattern: 'solid',
18
- fgColor: { argb: (0, color_1.toArgb)(style.fill.color) },
19
- };
20
- }
21
- else {
22
- // If no color is specified but fill is provided, use it as-is
23
- excelStyle.fill = style.fill;
24
- }
25
- }
26
- if (style.alignment) {
27
- excelStyle.alignment = style.alignment;
28
- }
29
- if (style.border) {
30
- if (typeof style.border === 'string') {
31
- // Presets handled in main logic or expanded here
32
- // For now, we'll handle explicit border objects here
33
- }
34
- else {
35
- excelStyle.border = style.border;
36
- }
37
- }
38
- return excelStyle;
39
- }