xlkit 1.1.0 → 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,304 +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
- const exceljs_1 = __importDefault(require("exceljs"));
9
- const style_1 = require("./utils/style");
10
- class XLKit {
11
- constructor() {
12
- this.workbook = new exceljs_1.default.Workbook();
13
- }
14
- addSheet(config) {
15
- // Validate Sheet Name
16
- if (!config.name) {
17
- throw new Error('Sheet name is required.');
18
- }
19
- if (config.name.length > 31) {
20
- throw new Error(`Sheet name "${config.name}" exceeds the maximum length of 31 characters.`);
21
- }
22
- // Invalid characters: \ / ? * [ ] :
23
- const invalidChars = /[\\/?*[\]:]/;
24
- if (invalidChars.test(config.name)) {
25
- throw new Error(`Sheet name "${config.name}" contains invalid characters (\\ / ? * [ ] :).`);
26
- }
27
- const sheet = this.workbook.addWorksheet(config.name);
28
- const data = config.rows;
29
- // Handle autoWidth
30
- const autoWidthConfig = typeof config.autoWidth === 'boolean'
31
- ? { enabled: config.autoWidth }
32
- : config.autoWidth || {};
33
- const autoWidthEnabled = autoWidthConfig.enabled !== false;
34
- // 1. Setup Columns & Headers
35
- const columns = config.headers.map((header, colIndex) => {
36
- let width = header.width;
37
- // Apply autoWidth if no width specified and autoWidth is enabled
38
- if (!width && autoWidthEnabled) {
39
- width = 'auto';
40
- }
41
- if (width === 'auto') {
42
- // Get header label text
43
- const headerText = typeof header.label === 'string'
44
- ? header.label
45
- : header.label.value;
46
- let maxLen = headerText.length * (autoWidthConfig.headerIncluded !== false ? 1 : 0);
47
- // Check data length
48
- data.forEach(row => {
49
- const cellData = row[header.key];
50
- const val = this.isCellValueWithStyle(cellData) ? cellData.value : cellData;
51
- const str = val != null ? String(val) : '';
52
- // Simple full-width check: count as 2 if char code > 255
53
- let len = 0;
54
- for (let i = 0; i < str.length; i++) {
55
- len += str.charCodeAt(i) > 255 ? 2 : 1;
56
- }
57
- if (len > maxLen)
58
- maxLen = len;
59
- });
60
- const padding = autoWidthConfig.padding ?? 2;
61
- const constant = autoWidthConfig.charWidthConstant ?? 1.2;
62
- width = (maxLen + padding) * constant;
63
- }
64
- // Get header label text for ExcelJS
65
- const headerText = typeof header.label === 'string'
66
- ? header.label
67
- : header.label.value;
68
- return {
69
- header: headerText,
70
- key: String(header.key),
71
- width: typeof width === 'number' ? width : 15
72
- };
73
- });
74
- sheet.columns = columns;
75
- // 2. Apply Title Rows (if any)
76
- if (config.title) {
77
- const titleLabels = Array.isArray(config.title.label)
78
- ? config.title.label
79
- : [config.title.label];
80
- titleLabels.forEach(titleText => {
81
- const titleRow = sheet.insertRow(1, [titleText]);
82
- // Merge title across all columns
83
- if (config.headers.length > 1) {
84
- sheet.mergeCells(1, 1, 1, config.headers.length);
85
- }
86
- // Apply title style
87
- if (config.title.style) {
88
- const mappedTitleStyle = (0, style_1.mapStyle)(config.title.style);
89
- titleRow.eachCell((cell) => {
90
- cell.style = { ...cell.style, ...mappedTitleStyle };
91
- });
92
- }
93
- });
94
- }
95
- // Calculate header row index (after title rows)
96
- const titleRowCount = config.title
97
- ? (Array.isArray(config.title.label) ? config.title.label.length : 1)
98
- : 0;
99
- const headerRowIndex = titleRowCount + 1;
100
- // 3. Apply Header Cell Styles (from headers[].label.style)
101
- const headerRow = sheet.getRow(headerRowIndex);
102
- config.headers.forEach((header, colIndex) => {
103
- if (typeof header.label === 'object' && header.label.style) {
104
- const cell = headerRow.getCell(colIndex + 1);
105
- const mappedStyle = (0, style_1.mapStyle)(header.label.style);
106
- cell.style = { ...cell.style, ...mappedStyle };
107
- }
108
- });
109
- // 4. Apply Header Row Style (from styles.header)
110
- if (config.styles?.header) {
111
- const mappedHeaderStyle = (0, style_1.mapStyle)(config.styles.header);
112
- headerRow.eachCell((cell) => {
113
- cell.style = { ...cell.style, ...mappedHeaderStyle };
114
- });
115
- }
116
- // 5. Apply styles.all to header row
117
- if (config.styles?.all) {
118
- const mappedAllStyle = (0, style_1.mapStyle)(config.styles.all);
119
- headerRow.eachCell((cell) => {
120
- cell.style = { ...mappedAllStyle, ...cell.style };
121
- });
122
- }
123
- // 6. Add Data & Apply Styles
124
- data.forEach((rowData, rowIndex) => {
125
- const rowValues = {};
126
- // Extract values from row data
127
- config.headers.forEach(header => {
128
- const cellData = rowData[header.key];
129
- rowValues[header.key] = this.isCellValueWithStyle(cellData) ? cellData.value : cellData;
130
- });
131
- const addedRow = sheet.addRow(rowValues);
132
- const excelRowIndex = headerRowIndex + rowIndex + 1;
133
- // Apply styles to each cell
134
- config.headers.forEach((header, colIndex) => {
135
- const cell = addedRow.getCell(colIndex + 1);
136
- const cellData = rowData[header.key];
137
- const cellValue = this.isCellValueWithStyle(cellData) ? cellData.value : cellData;
138
- // Apply styles in priority order
139
- let finalStyle = {};
140
- // 1. styles.all
141
- if (config.styles?.all) {
142
- finalStyle = { ...finalStyle, ...(0, style_1.mapStyle)(config.styles.all) };
143
- }
144
- // 2. styles.body
145
- if (config.styles?.body) {
146
- finalStyle = { ...finalStyle, ...(0, style_1.mapStyle)(config.styles.body) };
147
- }
148
- // 3. styles.column[key]
149
- if (config.styles?.column?.[header.key]) {
150
- finalStyle = { ...finalStyle, ...(0, style_1.mapStyle)(config.styles.column[header.key]) };
151
- }
152
- // 4. styles.row(data, index)
153
- if (config.styles?.row) {
154
- const rowStyle = config.styles.row(rowData, rowIndex);
155
- finalStyle = { ...finalStyle, ...(0, style_1.mapStyle)(rowStyle) };
156
- }
157
- // 5. headers[].style (object or function)
158
- if (header.style) {
159
- if (typeof header.style === 'function') {
160
- const cellStyle = header.style(cellValue, rowData, rowIndex);
161
- finalStyle = { ...finalStyle, ...(0, style_1.mapStyle)(cellStyle) };
162
- }
163
- else {
164
- finalStyle = { ...finalStyle, ...(0, style_1.mapStyle)(header.style) };
165
- }
166
- }
167
- // 6. rows[].{key}.style (highest priority)
168
- if (this.isCellValueWithStyle(cellData) && cellData.style) {
169
- finalStyle = { ...finalStyle, ...(0, style_1.mapStyle)(cellData.style) };
170
- }
171
- cell.style = finalStyle;
172
- // Apply format
173
- if (header.format) {
174
- if (typeof header.format === 'string') {
175
- cell.numFmt = header.format;
176
- }
177
- else {
178
- cell.value = header.format(cellValue);
179
- }
180
- }
181
- });
182
- });
183
- // 7. Apply Vertical Merges
184
- config.headers.forEach((header, colIndex) => {
185
- if (header.merge === 'vertical') {
186
- let startRow = headerRowIndex + 1; // First data row
187
- let previousValue = null;
188
- // Iterate from first data row to last
189
- for (let i = 0; i < data.length; i++) {
190
- const currentRowIndex = headerRowIndex + i + 1;
191
- const cell = sheet.getCell(currentRowIndex, colIndex + 1);
192
- const currentValue = cell.value;
193
- if (i === 0) {
194
- previousValue = currentValue;
195
- continue;
196
- }
197
- // If value changed or it's the last row, process the merge
198
- if (currentValue !== previousValue) {
199
- if (currentRowIndex - 1 > startRow) {
200
- sheet.mergeCells(startRow, colIndex + 1, currentRowIndex - 1, colIndex + 1);
201
- }
202
- startRow = currentRowIndex;
203
- previousValue = currentValue;
204
- }
205
- }
206
- // Handle the last group
207
- const lastRowIndex = headerRowIndex + data.length;
208
- if (lastRowIndex > startRow) {
209
- sheet.mergeCells(startRow, colIndex + 1, lastRowIndex, colIndex + 1);
210
- }
211
- }
212
- });
213
- // 8. Apply Borders
214
- if (config.borders === 'all') {
215
- sheet.eachRow((row) => {
216
- row.eachCell((cell) => {
217
- cell.border = {
218
- top: { style: 'thin' },
219
- left: { style: 'thin' },
220
- bottom: { style: 'thin' },
221
- right: { style: 'thin' }
222
- };
223
- });
224
- });
225
- }
226
- else if (config.borders === 'outer') {
227
- const lastRow = sheet.rowCount;
228
- const lastCol = sheet.columnCount;
229
- // Top & Bottom
230
- for (let c = 1; c <= lastCol; c++) {
231
- const topCell = sheet.getCell(1, c);
232
- topCell.border = { ...topCell.border, top: { style: 'thin' } };
233
- const bottomCell = sheet.getCell(lastRow, c);
234
- bottomCell.border = { ...bottomCell.border, bottom: { style: 'thin' } };
235
- }
236
- // Left & Right
237
- for (let r = 1; r <= lastRow; r++) {
238
- const leftCell = sheet.getCell(r, 1);
239
- leftCell.border = { ...leftCell.border, left: { style: 'thin' } };
240
- const rightCell = sheet.getCell(r, lastCol);
241
- rightCell.border = { ...rightCell.border, right: { style: 'thin' } };
242
- }
243
- }
244
- else if (config.borders === 'header-body') {
245
- const lastCol = sheet.columnCount;
246
- for (let c = 1; c <= lastCol; c++) {
247
- const headerCell = sheet.getCell(headerRowIndex, c);
248
- headerCell.border = { ...headerCell.border, bottom: { style: 'medium' } };
249
- }
250
- }
251
- return this;
252
- }
253
- isCellValueWithStyle(val) {
254
- return val !== null &&
255
- typeof val === 'object' &&
256
- 'value' in val &&
257
- !Array.isArray(val) &&
258
- !(val instanceof Date);
259
- }
260
- async save(path, options) {
261
- if (!path || path.trim() === '') {
262
- throw new Error('File path cannot be empty.');
263
- }
264
- if (typeof process !== 'undefined' && process.versions && process.versions.node) {
265
- const timeout = options?.timeout ?? 10000; // Default 10s
266
- const writePromise = this.workbook.xlsx.writeFile(path);
267
- const timeoutPromise = new Promise((_, reject) => {
268
- setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout);
269
- });
270
- await Promise.race([writePromise, timeoutPromise]);
271
- }
272
- else {
273
- throw new Error('File system access is only available in Node.js environment. Use saveToBuffer() instead.');
274
- }
275
- }
276
- async saveToBuffer(options) {
277
- const timeout = options?.timeout ?? 10000; // Default 10s
278
- const writePromise = this.workbook.xlsx.writeBuffer();
279
- const timeoutPromise = new Promise((_, reject) => {
280
- setTimeout(() => reject(new Error(`Operation timed out after ${timeout}ms`)), timeout);
281
- });
282
- const buffer = await Promise.race([writePromise, timeoutPromise]);
283
- return new Uint8Array(buffer);
284
- }
285
- async download(filename, options) {
286
- if (typeof window === 'undefined' || typeof document === 'undefined') {
287
- throw new Error('download() is only available in browser environment. Use save() for Node.js or saveToBuffer() for custom handling.');
288
- }
289
- const buffer = await this.saveToBuffer(options);
290
- const blob = new Blob([buffer.buffer], {
291
- type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
292
- });
293
- const url = URL.createObjectURL(blob);
294
- const a = document.createElement('a');
295
- a.href = url;
296
- a.download = filename;
297
- a.click();
298
- URL.revokeObjectURL(url);
299
- }
300
- }
301
- exports.XLKit = XLKit;
302
- function createWorkbook() {
303
- return new XLKit();
304
- }
@@ -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,57 +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 type CellValue = any | {
16
- value: any;
17
- style?: XLStyle;
18
- };
19
- export interface HeaderDef {
20
- key: string;
21
- label: string | {
22
- value: string;
23
- style?: XLStyle;
24
- };
25
- width?: number | 'auto';
26
- merge?: 'vertical';
27
- style?: XLStyle | ((val: any, row: any, index: number) => XLStyle);
28
- format?: string | ((val: any) => string);
29
- }
30
- export interface TitleConfig {
31
- label: string | string[];
32
- style?: XLStyle;
33
- }
34
- export interface StylesConfig {
35
- all?: XLStyle;
36
- header?: XLStyle;
37
- body?: XLStyle;
38
- row?: (data: any, index: number) => XLStyle;
39
- column?: {
40
- [key: string]: XLStyle;
41
- };
42
- }
43
- export interface SheetConfig {
44
- name: string;
45
- headers: HeaderDef[];
46
- rows: any[];
47
- title?: TitleConfig;
48
- styles?: StylesConfig;
49
- borders?: 'all' | 'outer' | 'header-body' | 'none';
50
- autoWidth?: boolean | {
51
- enabled?: boolean;
52
- padding?: number;
53
- headerIncluded?: boolean;
54
- charWidthConstant?: number;
55
- };
56
- }
57
- //# 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;AAGD,MAAM,MAAM,SAAS,GAAG,GAAG,GAAG;IAC5B,KAAK,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACnD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACnE,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,MAAM,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC;CACrC;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,CAAC;IACnD,SAAS,CAAC,EAAE,OAAO,GAAG;QACpB,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,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
- }