xlkit 1.0.3 → 1.0.5
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/LICENSE +21 -0
- package/README.md +117 -21
- package/dist/Sheetflow.d.ts.map +1 -1
- package/dist/Sheetflow.js +146 -23
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 xlkit contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -73,7 +73,22 @@ 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.
|
|
76
|
+
### 2. 自動列幅調整 (`autoWidth`)
|
|
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`)
|
|
77
92
|
|
|
78
93
|
シート全体の罫線プリセットを `borders` プロパティで指定できます。
|
|
79
94
|
|
|
@@ -91,9 +106,35 @@ const sheet = defineSheet<User>({
|
|
|
91
106
|
|
|
92
107
|
個別のセルに罫線を設定したい場合は、`style` プロパティ内の `border` で詳細に指定可能です。
|
|
93
108
|
|
|
94
|
-
###
|
|
109
|
+
### 4. スタイル (`style`)
|
|
95
110
|
|
|
96
|
-
フォント、背景色、配置などを詳細に設定できます。
|
|
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` | 右の罫線 | (同上) |
|
|
97
138
|
|
|
98
139
|
```typescript
|
|
99
140
|
style: {
|
|
@@ -112,13 +153,21 @@ style: {
|
|
|
112
153
|
wrapText: true
|
|
113
154
|
},
|
|
114
155
|
border: {
|
|
115
|
-
top: { style: 'thin', color: { argb: 'FF000000' } }
|
|
116
|
-
// ...
|
|
156
|
+
top: { style: 'thin', color: { argb: 'FF000000' } }
|
|
117
157
|
}
|
|
118
158
|
}
|
|
119
159
|
```
|
|
120
160
|
|
|
121
|
-
|
|
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`)
|
|
122
171
|
|
|
123
172
|
ヘッダー行のスタイルや構成をカスタマイズできます。
|
|
124
173
|
|
|
@@ -133,20 +182,29 @@ header: {
|
|
|
133
182
|
}
|
|
134
183
|
```
|
|
135
184
|
|
|
136
|
-
###
|
|
137
|
-
|
|
138
|
-
行ごとのスタイル(縞模様など)を定義できます。
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
rows: {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
|
|
185
|
+
### 6. 行スタイル (`rows`)
|
|
186
|
+
|
|
187
|
+
行ごとのスタイル(縞模様など)を定義できます。
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
rows: {
|
|
191
|
+
style: (data, index) => {
|
|
192
|
+
// 偶数行に背景色をつける
|
|
193
|
+
return index % 2 === 0 ? { fill: { color: '#F0F0F0' } } : {};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
また、データ行に `style` プロパティを含めることで、特定の行にスタイルを適用することも可能です。
|
|
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. ブラウザ環境でのダウンロード
|
|
150
208
|
|
|
151
209
|
ブラウザ環境では、`download()` メソッドを使って簡単にExcelファイルをダウンロードできます。
|
|
152
210
|
|
|
@@ -160,7 +218,7 @@ await createWorkbook().addSheet(sheet, data).download('output.xlsx');
|
|
|
160
218
|
|
|
161
219
|
内部的には`saveToBuffer()`を呼び出してBlobを作成し、自動的にダウンロードを開始します。
|
|
162
220
|
|
|
163
|
-
###
|
|
221
|
+
### 8. タイムアウト設定
|
|
164
222
|
|
|
165
223
|
大量データ処理時のフリーズを防ぐため、`save()`、`saveToBuffer()`、`download()` にはデフォルトで10秒のタイムアウトが設定されています。
|
|
166
224
|
|
|
@@ -177,6 +235,44 @@ await createWorkbook().addSheet(sheet, data).download('output.xlsx', { timeout:
|
|
|
177
235
|
|
|
178
236
|
> **推奨**: 10万行以下のデータであればデフォルト設定で問題ありません。それ以上の大量データを扱う場合は、ファイル分割やストリーミング処理を検討してください。
|
|
179
237
|
|
|
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
|
+
|
|
180
276
|
## ライセンス
|
|
181
277
|
|
|
182
278
|
MIT
|
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,QAAQ,EAAsB,MAAM,SAAS,CAAC;AAGvD,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAmB;;IAMnC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK;
|
|
1
|
+
{"version":3,"file":"Sheetflow.d.ts","sourceRoot":"","sources":["../src/Sheetflow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAsB,MAAM,SAAS,CAAC;AAGvD,qBAAa,KAAK;IAChB,OAAO,CAAC,QAAQ,CAAmB;;IAMnC,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,KAAK;IAsTzC,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;CAiBhF;AAED,wBAAgB,cAAc,IAAI,KAAK,CAEtC;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAE5D"}
|
package/dist/Sheetflow.js
CHANGED
|
@@ -26,9 +26,13 @@ class XLKit {
|
|
|
26
26
|
throw new Error(`Sheet name "${def.name}" contains invalid characters (\\ / ? * [ ] :).`);
|
|
27
27
|
}
|
|
28
28
|
const sheet = this.workbook.addWorksheet(def.name);
|
|
29
|
-
// 1. Setup Columns
|
|
29
|
+
// 1. Setup Columns
|
|
30
30
|
const columns = def.columns.map((col, colIndex) => {
|
|
31
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
|
+
}
|
|
32
36
|
if (width === 'auto') {
|
|
33
37
|
let maxLen = col.header.length * (def.autoWidth?.headerIncluded !== false ? 1 : 0);
|
|
34
38
|
// Check data length (sample first 100 rows for performance if needed, currently all)
|
|
@@ -55,26 +59,79 @@ class XLKit {
|
|
|
55
59
|
};
|
|
56
60
|
});
|
|
57
61
|
sheet.columns = columns;
|
|
58
|
-
// 2.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
111
|
if (def.rows?.style) {
|
|
63
|
-
const rowStyle = def.rows.style(row,
|
|
112
|
+
const rowStyle = def.rows.style(row, index);
|
|
64
113
|
const mappedStyle = (0, style_1.mapStyle)(rowStyle);
|
|
65
|
-
|
|
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) => {
|
|
66
123
|
cell.style = { ...cell.style, ...mappedStyle };
|
|
67
124
|
});
|
|
68
125
|
}
|
|
69
126
|
// Apply column-level conditional styles
|
|
70
127
|
def.columns.forEach((col, colIndex) => {
|
|
71
128
|
if (typeof col.style === 'function') {
|
|
72
|
-
const cell =
|
|
73
|
-
const cellStyle = col.style(row[col.key], row,
|
|
129
|
+
const cell = sheetRow.getCell(colIndex + 1);
|
|
130
|
+
const cellStyle = col.style(row[col.key], row, index);
|
|
74
131
|
cell.style = { ...cell.style, ...(0, style_1.mapStyle)(cellStyle) };
|
|
75
132
|
}
|
|
76
133
|
if (col.format) {
|
|
77
|
-
const cell =
|
|
134
|
+
const cell = sheetRow.getCell(colIndex + 1);
|
|
78
135
|
if (typeof col.format === 'string') {
|
|
79
136
|
cell.numFmt = col.format;
|
|
80
137
|
}
|
|
@@ -83,23 +140,31 @@ class XLKit {
|
|
|
83
140
|
}
|
|
84
141
|
}
|
|
85
142
|
});
|
|
143
|
+
sheetRow.commit();
|
|
86
144
|
});
|
|
87
|
-
//
|
|
145
|
+
// 4. Apply Header Styles (Global)
|
|
88
146
|
if (def.header?.style) {
|
|
89
|
-
const headerRow = sheet.getRow(1);
|
|
90
147
|
const mappedHeaderStyle = (0, style_1.mapStyle)(def.header.style);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|
|
94
159
|
}
|
|
95
|
-
//
|
|
160
|
+
// 5. Apply Vertical Merges
|
|
96
161
|
def.columns.forEach((col, colIndex) => {
|
|
97
162
|
if (col.merge === 'vertical') {
|
|
98
|
-
let startRow =
|
|
163
|
+
let startRow = dataStartRow;
|
|
99
164
|
let previousValue = null;
|
|
100
165
|
// Iterate from first data row to last
|
|
101
166
|
for (let i = 0; i < data.length; i++) {
|
|
102
|
-
const currentRowIndex =
|
|
167
|
+
const currentRowIndex = dataStartRow + i;
|
|
103
168
|
const cell = sheet.getCell(currentRowIndex, colIndex + 1);
|
|
104
169
|
const currentValue = cell.value;
|
|
105
170
|
if (i === 0) {
|
|
@@ -116,13 +181,70 @@ class XLKit {
|
|
|
116
181
|
}
|
|
117
182
|
}
|
|
118
183
|
// Handle the last group
|
|
119
|
-
const lastRowIndex = data.length
|
|
120
|
-
|
|
121
|
-
|
|
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
|
+
}
|
|
122
193
|
}
|
|
123
194
|
}
|
|
124
195
|
});
|
|
125
|
-
//
|
|
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
|
|
126
248
|
if (def.borders === 'all') {
|
|
127
249
|
sheet.eachRow((row) => {
|
|
128
250
|
row.eachCell((cell) => {
|
|
@@ -155,8 +277,9 @@ class XLKit {
|
|
|
155
277
|
}
|
|
156
278
|
else if (def.borders === 'header-body') {
|
|
157
279
|
const lastCol = sheet.columnCount;
|
|
280
|
+
// Apply to the last row of the header
|
|
158
281
|
for (let c = 1; c <= lastCol; c++) {
|
|
159
|
-
const headerCell = sheet.getCell(
|
|
282
|
+
const headerCell = sheet.getCell(headerRowCount, c);
|
|
160
283
|
headerCell.border = { ...headerCell.border, bottom: { style: 'medium' } };
|
|
161
284
|
}
|
|
162
285
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -16,12 +16,18 @@ export interface ColumnDef<T> {
|
|
|
16
16
|
key: keyof T;
|
|
17
17
|
header: string;
|
|
18
18
|
width?: number | 'auto';
|
|
19
|
-
merge?: 'vertical';
|
|
19
|
+
merge?: 'vertical' | 'horizontal';
|
|
20
20
|
style?: XLStyle | ((val: any, row: T, index: number) => XLStyle);
|
|
21
21
|
format?: string | ((val: any) => string);
|
|
22
22
|
}
|
|
23
|
+
export interface HeaderCell {
|
|
24
|
+
value: string;
|
|
25
|
+
colSpan?: number;
|
|
26
|
+
rowSpan?: number;
|
|
27
|
+
style?: XLStyle;
|
|
28
|
+
}
|
|
23
29
|
export interface HeaderConfig {
|
|
24
|
-
rows
|
|
30
|
+
rows?: (string | HeaderCell)[][];
|
|
25
31
|
style?: XLStyle;
|
|
26
32
|
borders?: 'header-body' | 'all' | 'none';
|
|
27
33
|
}
|
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;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,CAAC;
|
|
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"}
|