xlkit 1.0.5 → 1.1.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/README.md +21 -117
- package/dist/Sheetflow.d.ts +3 -3
- package/dist/Sheetflow.d.ts.map +1 -1
- package/dist/Sheetflow.js +154 -185
- package/dist/types.d.ts +29 -21
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,22 +73,7 @@ await createWorkbook().addSheet(userSheet, users).save('users.xlsx');
|
|
|
73
73
|
| `format` | `string` \| `Function` | 数値/日付のフォーマット文字列(例: `'$#,##0'`, `'yyyy-mm-dd'`)または変換関数。 |
|
|
74
74
|
| `style` | `Style` \| `Function` | 列全体のスタイル、または値に応じた条件付きスタイル関数。 |
|
|
75
75
|
|
|
76
|
-
### 2.
|
|
77
|
-
|
|
78
|
-
列幅の自動計算ロジックを微調整できます。
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
const sheet = defineSheet<User>({
|
|
82
|
-
// ...
|
|
83
|
-
autoWidth: {
|
|
84
|
-
padding: 4, // デフォルト: 2
|
|
85
|
-
headerIncluded: true, // ヘッダーの長さも考慮するか
|
|
86
|
-
charWidthConstant: 1.5 // 文字幅の係数(デフォルト: 1.2)
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### 3. 罫線 (`borders`)
|
|
76
|
+
### 2. 罫線 (`borders`)
|
|
92
77
|
|
|
93
78
|
シート全体の罫線プリセットを `borders` プロパティで指定できます。
|
|
94
79
|
|
|
@@ -106,35 +91,9 @@ const sheet = defineSheet<User>({
|
|
|
106
91
|
|
|
107
92
|
個別のセルに罫線を設定したい場合は、`style` プロパティ内の `border` で詳細に指定可能です。
|
|
108
93
|
|
|
109
|
-
###
|
|
94
|
+
### 3. スタイル (`style`)
|
|
110
95
|
|
|
111
|
-
フォント、背景色、配置などを詳細に設定できます。
|
|
112
|
-
|
|
113
|
-
| プロパティ | 説明 | 型・値の例 |
|
|
114
|
-
| :--- | :--- | :--- |
|
|
115
|
-
| **`font`** | | |
|
|
116
|
-
| `font.name` | フォント名 | `'Arial'`, `'MS Pゴシック'` |
|
|
117
|
-
| `font.size` | フォントサイズ | `11`, `12` |
|
|
118
|
-
| `font.bold` | 太字 | `true`, `false` |
|
|
119
|
-
| `font.italic` | 斜体 | `true`, `false` |
|
|
120
|
-
| `font.underline` | 下線 | `true`, `'single'`, `'double'` |
|
|
121
|
-
| `font.strike` | 取り消し線 | `true`, `false` |
|
|
122
|
-
| `font.color` | 文字色 | `'#FF0000'` (Hex), `{ argb: 'FFFF0000' }` |
|
|
123
|
-
| **`fill`** | | |
|
|
124
|
-
| `fill.color` | 背景色 (Solid) | `'#FFFF00'` (Hex) ※xlkit独自ショートカット |
|
|
125
|
-
| `fill.type` | 塗りつぶしタイプ | `'pattern'` |
|
|
126
|
-
| `fill.pattern` | パターン種類 | `'solid'`, `'darkGray'`, `'mediumGray'` |
|
|
127
|
-
| **`alignment`** | | |
|
|
128
|
-
| `alignment.horizontal` | 水平方向の配置 | `'left'`, `'center'`, `'right'`, `'justify'` |
|
|
129
|
-
| `alignment.vertical` | 垂直方向の配置 | `'top'`, `'middle'`, `'bottom'` |
|
|
130
|
-
| `alignment.wrapText` | 折り返し表示 | `true`, `false` |
|
|
131
|
-
| `alignment.indent` | インデント | `1`, `2` |
|
|
132
|
-
| `alignment.textRotation` | 文字の回転 | `45`, `90`, `'vertical'` |
|
|
133
|
-
| **`border`** | | |
|
|
134
|
-
| `border.top` | 上の罫線 | `{ style: 'thin', color: { argb: 'FF000000' } }` |
|
|
135
|
-
| `border.left` | 左の罫線 | (同上) |
|
|
136
|
-
| `border.bottom` | 下の罫線 | (同上) |
|
|
137
|
-
| `border.right` | 右の罫線 | (同上) |
|
|
96
|
+
フォント、背景色、配置などを詳細に設定できます。6桁のHexカラーコード (`#RRGGBB`) が使用可能です。
|
|
138
97
|
|
|
139
98
|
```typescript
|
|
140
99
|
style: {
|
|
@@ -153,21 +112,13 @@ style: {
|
|
|
153
112
|
wrapText: true
|
|
154
113
|
},
|
|
155
114
|
border: {
|
|
156
|
-
top: { style: 'thin', color: { argb: 'FF000000' } }
|
|
115
|
+
top: { style: 'thin', color: { argb: 'FF000000' } },
|
|
116
|
+
// ...
|
|
157
117
|
}
|
|
158
118
|
}
|
|
159
119
|
```
|
|
160
120
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
スタイルは以下の順序で適用(上書き)されます(下が優先):
|
|
164
|
-
|
|
165
|
-
1. **行定義 (`rows.style`)**: シート定義で指定した行全体のスタイル
|
|
166
|
-
2. **データ行スタイル (`row.style`)**: データオブジェクトに含まれる `style` プロパティ
|
|
167
|
-
3. **列定義 (`columns.style`)**: 列ごとのスタイル
|
|
168
|
-
4. **条件付き列スタイル**: `columns.style` が関数の場合
|
|
169
|
-
|
|
170
|
-
### 5. ヘッダー設定 (`header`)
|
|
121
|
+
### 4. ヘッダー設定 (`header`)
|
|
171
122
|
|
|
172
123
|
ヘッダー行のスタイルや構成をカスタマイズできます。
|
|
173
124
|
|
|
@@ -182,29 +133,20 @@ header: {
|
|
|
182
133
|
}
|
|
183
134
|
```
|
|
184
135
|
|
|
185
|
-
###
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
const data = [
|
|
202
|
-
{ name: 'Tom', age: 28 },
|
|
203
|
-
{ name: 'Mary', age: 25, style: { fill: { color: '#FFFF00' } } }, // Maryの行は黄色背景
|
|
204
|
-
];
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### 7. ブラウザ環境でのダウンロード
|
|
136
|
+
### 5. 行スタイル (`rows`)
|
|
137
|
+
|
|
138
|
+
行ごとのスタイル(縞模様など)を定義できます。
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
rows: {
|
|
142
|
+
style: (data, index) => {
|
|
143
|
+
// 偶数行に背景色をつける
|
|
144
|
+
return index % 2 === 0 ? { fill: { color: '#F0F0F0' } } : {};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 6. ブラウザ環境でのダウンロード
|
|
208
150
|
|
|
209
151
|
ブラウザ環境では、`download()` メソッドを使って簡単にExcelファイルをダウンロードできます。
|
|
210
152
|
|
|
@@ -218,7 +160,7 @@ await createWorkbook().addSheet(sheet, data).download('output.xlsx');
|
|
|
218
160
|
|
|
219
161
|
内部的には`saveToBuffer()`を呼び出してBlobを作成し、自動的にダウンロードを開始します。
|
|
220
162
|
|
|
221
|
-
###
|
|
163
|
+
### 7. タイムアウト設定
|
|
222
164
|
|
|
223
165
|
大量データ処理時のフリーズを防ぐため、`save()`、`saveToBuffer()`、`download()` にはデフォルトで10秒のタイムアウトが設定されています。
|
|
224
166
|
|
|
@@ -235,44 +177,6 @@ await createWorkbook().addSheet(sheet, data).download('output.xlsx', { timeout:
|
|
|
235
177
|
|
|
236
178
|
> **推奨**: 10万行以下のデータであればデフォルト設定で問題ありません。それ以上の大量データを扱う場合は、ファイル分割やストリーミング処理を検討してください。
|
|
237
179
|
|
|
238
|
-
### 9. 制約事項
|
|
239
|
-
|
|
240
|
-
- **予約語**: 列の `key` に `'style'` は使用できません。これは行ごとのスタイル定義プロパティとして予約されています。
|
|
241
|
-
- **シート名**: Excelの仕様により、シート名は31文字以内で、`\ / ? * [ ] :` の文字は使用できません。
|
|
242
|
-
|
|
243
|
-
## 高度な使い方
|
|
244
|
-
|
|
245
|
-
### 複数シートの作成
|
|
246
|
-
|
|
247
|
-
メソッドチェーンを使用して、1つのワークブックに複数のシートを追加できます。
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
await createWorkbook()
|
|
251
|
-
.addSheet(usersSheet, usersData)
|
|
252
|
-
.addSheet(productsSheet, productsData)
|
|
253
|
-
.save('data.xlsx');
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
### TypeScriptによる型安全性
|
|
257
|
-
|
|
258
|
-
ジェネリクスを使用することで、列定義のキーを型安全に保つことができます。
|
|
259
|
-
|
|
260
|
-
```typescript
|
|
261
|
-
interface User {
|
|
262
|
-
id: number;
|
|
263
|
-
name: string;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// keyは 'id' | 'name' に型推論され、誤字を防げます
|
|
267
|
-
const sheet = defineSheet<User>({
|
|
268
|
-
name: 'Users',
|
|
269
|
-
columns: [
|
|
270
|
-
{ key: 'id', ... }, // OK
|
|
271
|
-
{ key: 'nmae', ... } // Error: Type '"nmae"' is not assignable to type 'keyof User'
|
|
272
|
-
]
|
|
273
|
-
});
|
|
274
|
-
```
|
|
275
|
-
|
|
276
180
|
## ライセンス
|
|
277
181
|
|
|
278
182
|
MIT
|
package/dist/Sheetflow.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SheetConfig } from './types';
|
|
2
2
|
export declare class XLKit {
|
|
3
3
|
private workbook;
|
|
4
4
|
constructor();
|
|
5
|
-
addSheet
|
|
5
|
+
addSheet(config: SheetConfig): XLKit;
|
|
6
|
+
private isCellValueWithStyle;
|
|
6
7
|
save(path: string, options?: {
|
|
7
8
|
timeout?: number;
|
|
8
9
|
}): Promise<void>;
|
|
@@ -14,5 +15,4 @@ export declare class XLKit {
|
|
|
14
15
|
}): Promise<void>;
|
|
15
16
|
}
|
|
16
17
|
export declare function createWorkbook(): XLKit;
|
|
17
|
-
export declare function defineSheet<T>(def: SheetDef<T>): SheetDef<T>;
|
|
18
18
|
//# sourceMappingURL=Sheetflow.d.ts.map
|
package/dist/Sheetflow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sheetflow.d.ts","sourceRoot":"","sources":["../src/Sheetflow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"Sheetflow.d.ts","sourceRoot":"","sources":["../src/Sheetflow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAA+C,MAAM,SAAS,CAAC;AAGnF,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAmB;;IAMnC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK;IAkRpC,OAAO,CAAC,oBAAoB;IAQtB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBjE,YAAY,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,CAAC;IAajE,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAgBhF;AAED,wBAAgB,cAAc,IAAI,KAAK,CAEtC"}
|
package/dist/Sheetflow.js
CHANGED
|
@@ -5,39 +5,49 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.XLKit = void 0;
|
|
7
7
|
exports.createWorkbook = createWorkbook;
|
|
8
|
-
exports.defineSheet = defineSheet;
|
|
9
8
|
const exceljs_1 = __importDefault(require("exceljs"));
|
|
10
9
|
const style_1 = require("./utils/style");
|
|
11
10
|
class XLKit {
|
|
12
11
|
constructor() {
|
|
13
12
|
this.workbook = new exceljs_1.default.Workbook();
|
|
14
13
|
}
|
|
15
|
-
addSheet(
|
|
14
|
+
addSheet(config) {
|
|
16
15
|
// Validate Sheet Name
|
|
17
|
-
if (!
|
|
16
|
+
if (!config.name) {
|
|
18
17
|
throw new Error('Sheet name is required.');
|
|
19
18
|
}
|
|
20
|
-
if (
|
|
21
|
-
throw new Error(`Sheet name "${
|
|
19
|
+
if (config.name.length > 31) {
|
|
20
|
+
throw new Error(`Sheet name "${config.name}" exceeds the maximum length of 31 characters.`);
|
|
22
21
|
}
|
|
23
22
|
// Invalid characters: \ / ? * [ ] :
|
|
24
23
|
const invalidChars = /[\\/?*[\]:]/;
|
|
25
|
-
if (invalidChars.test(
|
|
26
|
-
throw new Error(`Sheet name "${
|
|
24
|
+
if (invalidChars.test(config.name)) {
|
|
25
|
+
throw new Error(`Sheet name "${config.name}" contains invalid characters (\\ / ? * [ ] :).`);
|
|
27
26
|
}
|
|
28
|
-
const sheet = this.workbook.addWorksheet(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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';
|
|
35
40
|
}
|
|
36
41
|
if (width === 'auto') {
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
39
48
|
data.forEach(row => {
|
|
40
|
-
const
|
|
49
|
+
const cellData = row[header.key];
|
|
50
|
+
const val = this.isCellValueWithStyle(cellData) ? cellData.value : cellData;
|
|
41
51
|
const str = val != null ? String(val) : '';
|
|
42
52
|
// Simple full-width check: count as 2 if char code > 255
|
|
43
53
|
let len = 0;
|
|
@@ -47,124 +57,137 @@ class XLKit {
|
|
|
47
57
|
if (len > maxLen)
|
|
48
58
|
maxLen = len;
|
|
49
59
|
});
|
|
50
|
-
const padding =
|
|
51
|
-
const constant =
|
|
60
|
+
const padding = autoWidthConfig.padding ?? 2;
|
|
61
|
+
const constant = autoWidthConfig.charWidthConstant ?? 1.2;
|
|
52
62
|
width = (maxLen + padding) * constant;
|
|
53
63
|
}
|
|
64
|
+
// Get header label text for ExcelJS
|
|
65
|
+
const headerText = typeof header.label === 'string'
|
|
66
|
+
? header.label
|
|
67
|
+
: header.label.value;
|
|
54
68
|
return {
|
|
55
|
-
header:
|
|
56
|
-
key: String(
|
|
57
|
-
width: typeof width === 'number' ? width : 15
|
|
58
|
-
style: col.style && typeof col.style === 'object' ? (0, style_1.mapStyle)(col.style) : undefined
|
|
69
|
+
header: headerText,
|
|
70
|
+
key: String(header.key),
|
|
71
|
+
width: typeof width === 'number' ? width : 15
|
|
59
72
|
};
|
|
60
73
|
});
|
|
61
74
|
sheet.columns = columns;
|
|
62
|
-
// 2.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
});
|
|
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
|
+
}
|
|
95
93
|
});
|
|
96
94
|
}
|
|
97
|
-
//
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
cell.
|
|
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
|
-
});
|
|
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 };
|
|
125
107
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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]) };
|
|
132
151
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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) };
|
|
137
162
|
}
|
|
138
163
|
else {
|
|
139
|
-
|
|
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);
|
|
140
179
|
}
|
|
141
180
|
}
|
|
142
181
|
});
|
|
143
|
-
sheetRow.commit();
|
|
144
182
|
});
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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;
|
|
183
|
+
// 7. Apply Vertical Merges
|
|
184
|
+
config.headers.forEach((header, colIndex) => {
|
|
185
|
+
if (header.merge === 'vertical') {
|
|
186
|
+
let startRow = headerRowIndex + 1; // First data row
|
|
164
187
|
let previousValue = null;
|
|
165
188
|
// Iterate from first data row to last
|
|
166
189
|
for (let i = 0; i < data.length; i++) {
|
|
167
|
-
const currentRowIndex =
|
|
190
|
+
const currentRowIndex = headerRowIndex + i + 1;
|
|
168
191
|
const cell = sheet.getCell(currentRowIndex, colIndex + 1);
|
|
169
192
|
const currentValue = cell.value;
|
|
170
193
|
if (i === 0) {
|
|
@@ -181,71 +204,14 @@ class XLKit {
|
|
|
181
204
|
}
|
|
182
205
|
}
|
|
183
206
|
// Handle the last group
|
|
184
|
-
const lastRowIndex =
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
}
|
|
207
|
+
const lastRowIndex = headerRowIndex + data.length;
|
|
208
|
+
if (lastRowIndex > startRow) {
|
|
209
|
+
sheet.mergeCells(startRow, colIndex + 1, lastRowIndex, colIndex + 1);
|
|
193
210
|
}
|
|
194
211
|
}
|
|
195
212
|
});
|
|
196
|
-
//
|
|
197
|
-
|
|
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') {
|
|
213
|
+
// 8. Apply Borders
|
|
214
|
+
if (config.borders === 'all') {
|
|
249
215
|
sheet.eachRow((row) => {
|
|
250
216
|
row.eachCell((cell) => {
|
|
251
217
|
cell.border = {
|
|
@@ -257,7 +223,7 @@ class XLKit {
|
|
|
257
223
|
});
|
|
258
224
|
});
|
|
259
225
|
}
|
|
260
|
-
else if (
|
|
226
|
+
else if (config.borders === 'outer') {
|
|
261
227
|
const lastRow = sheet.rowCount;
|
|
262
228
|
const lastCol = sheet.columnCount;
|
|
263
229
|
// Top & Bottom
|
|
@@ -275,16 +241,22 @@ class XLKit {
|
|
|
275
241
|
rightCell.border = { ...rightCell.border, right: { style: 'thin' } };
|
|
276
242
|
}
|
|
277
243
|
}
|
|
278
|
-
else if (
|
|
244
|
+
else if (config.borders === 'header-body') {
|
|
279
245
|
const lastCol = sheet.columnCount;
|
|
280
|
-
// Apply to the last row of the header
|
|
281
246
|
for (let c = 1; c <= lastCol; c++) {
|
|
282
|
-
const headerCell = sheet.getCell(
|
|
247
|
+
const headerCell = sheet.getCell(headerRowIndex, c);
|
|
283
248
|
headerCell.border = { ...headerCell.border, bottom: { style: 'medium' } };
|
|
284
249
|
}
|
|
285
250
|
}
|
|
286
251
|
return this;
|
|
287
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
|
+
}
|
|
288
260
|
async save(path, options) {
|
|
289
261
|
if (!path || path.trim() === '') {
|
|
290
262
|
throw new Error('File path cannot be empty.');
|
|
@@ -330,6 +302,3 @@ exports.XLKit = XLKit;
|
|
|
330
302
|
function createWorkbook() {
|
|
331
303
|
return new XLKit();
|
|
332
304
|
}
|
|
333
|
-
function defineSheet(def) {
|
|
334
|
-
return def;
|
|
335
|
-
}
|
package/dist/types.d.ts
CHANGED
|
@@ -12,35 +12,43 @@ export interface XLStyle {
|
|
|
12
12
|
alignment?: Partial<Alignment>;
|
|
13
13
|
border?: Partial<Border> | 'all' | 'outer' | 'header-body' | 'none';
|
|
14
14
|
}
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
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
|
+
};
|
|
18
25
|
width?: number | 'auto';
|
|
19
|
-
merge?: 'vertical'
|
|
20
|
-
style?: XLStyle | ((val: any, row:
|
|
26
|
+
merge?: 'vertical';
|
|
27
|
+
style?: XLStyle | ((val: any, row: any, index: number) => XLStyle);
|
|
21
28
|
format?: string | ((val: any) => string);
|
|
22
29
|
}
|
|
23
|
-
export interface
|
|
24
|
-
|
|
25
|
-
colSpan?: number;
|
|
26
|
-
rowSpan?: number;
|
|
30
|
+
export interface TitleConfig {
|
|
31
|
+
label: string | string[];
|
|
27
32
|
style?: XLStyle;
|
|
28
33
|
}
|
|
29
|
-
export interface
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
+
};
|
|
33
42
|
}
|
|
34
|
-
export interface
|
|
43
|
+
export interface SheetConfig {
|
|
35
44
|
name: string;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
};
|
|
41
|
-
defaultStyle?: XLStyle;
|
|
45
|
+
headers: HeaderDef[];
|
|
46
|
+
rows: any[];
|
|
47
|
+
title?: TitleConfig;
|
|
48
|
+
styles?: StylesConfig;
|
|
42
49
|
borders?: 'all' | 'outer' | 'header-body' | 'none';
|
|
43
|
-
autoWidth?: {
|
|
50
|
+
autoWidth?: boolean | {
|
|
51
|
+
enabled?: boolean;
|
|
44
52
|
padding?: number;
|
|
45
53
|
headerIncluded?: boolean;
|
|
46
54
|
charWidthConstant?: number;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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"}
|