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 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. 自動列幅調整 (`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`)
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
- ### 4. スタイル (`style`)
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
- ### 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. ブラウザ環境でのダウンロード
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
- ### 8. タイムアウト設定
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
@@ -1,8 +1,9 @@
1
- import { SheetDef } from './types';
1
+ import { SheetConfig } from './types';
2
2
  export declare class XLKit {
3
3
  private workbook;
4
4
  constructor();
5
- addSheet<T>(def: SheetDef<T>, data: T[]): XLKit;
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
@@ -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;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"}
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(def, data) {
14
+ addSheet(config) {
16
15
  // Validate Sheet Name
17
- if (!def.name) {
16
+ if (!config.name) {
18
17
  throw new Error('Sheet name is required.');
19
18
  }
20
- if (def.name.length > 31) {
21
- throw new Error(`Sheet name "${def.name}" exceeds the maximum length of 31 characters.`);
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(def.name)) {
26
- throw new Error(`Sheet name "${def.name}" contains invalid characters (\\ / ? * [ ] :).`);
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(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.");
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
- 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)
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 val = row[col.key];
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 = def.autoWidth?.padding ?? 2;
51
- const constant = def.autoWidth?.charWidthConstant ?? 1.2;
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: 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
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. 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
- });
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
- // 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
- });
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
- // 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) };
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
- if (col.format) {
134
- const cell = sheetRow.getCell(colIndex + 1);
135
- if (typeof col.format === 'string') {
136
- cell.numFmt = col.format;
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
- cell.value = col.format(row[col.key]);
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
- // 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;
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 = dataStartRow + i;
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 = 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
- }
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
- // 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') {
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 (def.borders === 'outer') {
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 (def.borders === 'header-body') {
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(headerRowCount, c);
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 interface ColumnDef<T> {
16
- key: keyof T;
17
- header: string;
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' | 'horizontal';
20
- style?: XLStyle | ((val: any, row: T, index: number) => XLStyle);
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 HeaderCell {
24
- value: string;
25
- colSpan?: number;
26
- rowSpan?: number;
30
+ export interface TitleConfig {
31
+ label: string | string[];
27
32
  style?: XLStyle;
28
33
  }
29
- export interface HeaderConfig {
30
- rows?: (string | HeaderCell)[][];
31
- style?: XLStyle;
32
- borders?: 'header-body' | 'all' | 'none';
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 SheetDef<T> {
43
+ export interface SheetConfig {
35
44
  name: string;
36
- columns: ColumnDef<T>[];
37
- header?: HeaderConfig;
38
- rows?: {
39
- style?: (data: T, index: number) => XLStyle;
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;
@@ -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,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"}
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xlkit",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "files": [