xlkit 1.2.0 → 1.2.1
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 +841 -0
- package/package.json +3 -7
- package/bin/cli.js +0 -15
- package/demo/index.html +0 -12
- package/demo/main.tsx +0 -23
- package/demo/vite.config.ts +0 -7
package/README.md
ADDED
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
# xlkit
|
|
2
|
+
|
|
3
|
+
**宣言的なExcel生成ライブラリ** - コードを見れば最終的なExcelの構造がわかる
|
|
4
|
+
|
|
5
|
+
ExcelJSをベースに、より直感的で宣言的なAPIを提供します。
|
|
6
|
+
|
|
7
|
+
## ExcelJSとの比較
|
|
8
|
+
|
|
9
|
+
| 観点 | ExcelJS(命令的) | xlkit(宣言的) |
|
|
10
|
+
|------|------------------|----------------|
|
|
11
|
+
| 書き方 | セルを1つずつ操作 | 最終形を宣言 |
|
|
12
|
+
| 見通し | コードから結果が見えづらい | コードから結果が見える |
|
|
13
|
+
| 例え | jQuery | React |
|
|
14
|
+
|
|
15
|
+
## インストール
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install xlkit
|
|
19
|
+
# または
|
|
20
|
+
pnpm add xlkit
|
|
21
|
+
# または
|
|
22
|
+
yarn add xlkit
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## クイックスタート
|
|
26
|
+
|
|
27
|
+
### Node.js
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { xlkit } from "xlkit";
|
|
31
|
+
|
|
32
|
+
const salesData = [
|
|
33
|
+
{ name: "りんご", price: 100, quantity: 50 },
|
|
34
|
+
{ name: "みかん", price: 80, quantity: 100 },
|
|
35
|
+
{ name: "バナナ", price: 120, quantity: 30 },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const output = await xlkit()
|
|
39
|
+
.sheet("売上")
|
|
40
|
+
.table({
|
|
41
|
+
preset: "basic",
|
|
42
|
+
columns: [
|
|
43
|
+
{ key: "name", label: "商品名" },
|
|
44
|
+
{ key: "price", label: "価格" },
|
|
45
|
+
{ key: "quantity", label: "数量" },
|
|
46
|
+
],
|
|
47
|
+
data: salesData,
|
|
48
|
+
})
|
|
49
|
+
.getNode();
|
|
50
|
+
|
|
51
|
+
await output.saveToFile("report.xlsx");
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### ブラウザ
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { xlkit } from "xlkit";
|
|
58
|
+
|
|
59
|
+
const output = await xlkit()
|
|
60
|
+
.sheet("データ")
|
|
61
|
+
.table({
|
|
62
|
+
preset: "basic",
|
|
63
|
+
columns: [
|
|
64
|
+
{ key: "name", label: "名前" },
|
|
65
|
+
{ key: "value", label: "値" },
|
|
66
|
+
],
|
|
67
|
+
data: [
|
|
68
|
+
{ name: "項目A", value: 100 },
|
|
69
|
+
{ name: "項目B", value: 200 },
|
|
70
|
+
],
|
|
71
|
+
})
|
|
72
|
+
.getBrowser();
|
|
73
|
+
|
|
74
|
+
await output.download("data.xlsx");
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## API リファレンス
|
|
80
|
+
|
|
81
|
+
<details>
|
|
82
|
+
<summary><strong>エントリーポイント・メソッド一覧</strong></summary>
|
|
83
|
+
|
|
84
|
+
### xlkit()
|
|
85
|
+
|
|
86
|
+
WorkbookBuilderを返すファクトリ関数。
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { xlkit } from "xlkit";
|
|
90
|
+
|
|
91
|
+
const builder = xlkit();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### WorkbookBuilder / SheetBuilder メソッド
|
|
95
|
+
|
|
96
|
+
| メソッド | 戻り値 | 説明 |
|
|
97
|
+
|---------|--------|------|
|
|
98
|
+
| `sheet(name?)` | `SheetBuilder` | シートを追加(名前省略時は自動生成) |
|
|
99
|
+
| `table(options)` | `this` | テーブルを追加 |
|
|
100
|
+
| `text(input)` | `this` | テキストを追加 |
|
|
101
|
+
| `image(options)` | `this` | 画像を追加 |
|
|
102
|
+
| `space(lines?)` | `this` | 空行を追加(デフォルト: 1行) |
|
|
103
|
+
| `getNode()` | `Promise<NodeOutput>` | Node.js用出力オブジェクトを取得 |
|
|
104
|
+
| `getBrowser()` | `Promise<BrowserOutput>` | ブラウザ用出力オブジェクトを取得 |
|
|
105
|
+
|
|
106
|
+
</details>
|
|
107
|
+
|
|
108
|
+
<details>
|
|
109
|
+
<summary><strong>テーブルオプション(TableOptions)</strong></summary>
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
.table({
|
|
113
|
+
preset?: "basic" | "minimal" | "striped",
|
|
114
|
+
columns: Column<T>[],
|
|
115
|
+
data: T[],
|
|
116
|
+
autoWidth?: "all" | "body" | false,
|
|
117
|
+
mergeSameValues?: boolean,
|
|
118
|
+
style?: TableStyle,
|
|
119
|
+
border?: BorderStyle,
|
|
120
|
+
conditionalStyle?: (row: T, col: keyof T) => CellStyle | {},
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
| オプション | 型 | デフォルト | 説明 |
|
|
125
|
+
|-----------|-----|-----------|------|
|
|
126
|
+
| `preset` | `"basic"` \| `"minimal"` \| `"striped"` | - | プリセットスタイル |
|
|
127
|
+
| `columns` | `Column<T>[]` | **必須** | カラム定義 |
|
|
128
|
+
| `data` | `T[]` | **必須** | データ配列 |
|
|
129
|
+
| `autoWidth` | `"all"` \| `"body"` \| `false` | `false` | 列幅自動調整 |
|
|
130
|
+
| `mergeSameValues` | `boolean` | `false` | 同じ値のセルを縦方向にマージ |
|
|
131
|
+
| `style` | `TableStyle` | - | テーブル全体のスタイル |
|
|
132
|
+
| `border` | `BorderStyle` | - | 罫線設定 |
|
|
133
|
+
| `conditionalStyle` | `function` | - | 条件付きスタイル |
|
|
134
|
+
|
|
135
|
+
</details>
|
|
136
|
+
|
|
137
|
+
<details>
|
|
138
|
+
<summary><strong>カラム定義(Column)</strong></summary>
|
|
139
|
+
|
|
140
|
+
### LeafColumn(通常のカラム)
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
{
|
|
144
|
+
key: keyof T & string, // データのキー
|
|
145
|
+
label: string, // ヘッダーテキスト
|
|
146
|
+
style?: CellStyle, // この列のデフォルトスタイル
|
|
147
|
+
mergeSameValues?: boolean, // この列で同値マージするか
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### ParentColumn(マルチヘッダー用)
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
{
|
|
155
|
+
label: string, // 親ヘッダーテキスト
|
|
156
|
+
children: Column<T>[], // 子カラム(再帰的に定義可能)
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**マルチヘッダーの例:**
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
columns: [
|
|
164
|
+
{
|
|
165
|
+
label: "商品情報",
|
|
166
|
+
children: [
|
|
167
|
+
{ key: "category", label: "カテゴリ" },
|
|
168
|
+
{ key: "name", label: "商品名" },
|
|
169
|
+
],
|
|
170
|
+
},
|
|
171
|
+
{ key: "price", label: "価格" },
|
|
172
|
+
]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
結果:
|
|
176
|
+
```
|
|
177
|
+
| 商品情報 | 価格 |
|
|
178
|
+
| カテゴリ | 商品名 | |
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
</details>
|
|
182
|
+
|
|
183
|
+
<details>
|
|
184
|
+
<summary><strong>スタイル(CellStyle)</strong></summary>
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
{
|
|
188
|
+
// フォント
|
|
189
|
+
fontFamily?: string,
|
|
190
|
+
fontSize?: number,
|
|
191
|
+
bold?: boolean,
|
|
192
|
+
italic?: boolean,
|
|
193
|
+
underline?: boolean,
|
|
194
|
+
strike?: boolean,
|
|
195
|
+
|
|
196
|
+
// 色
|
|
197
|
+
color?: string, // 文字色 "#RRGGBB"
|
|
198
|
+
fill?: string, // 背景色 "#RRGGBB"
|
|
199
|
+
|
|
200
|
+
// 配置
|
|
201
|
+
align?: "left" | "center" | "right",
|
|
202
|
+
|
|
203
|
+
// 書式
|
|
204
|
+
format?: "string" | "number" | "date",
|
|
205
|
+
decimalPlaces?: number,
|
|
206
|
+
thousandsSeparator?: boolean,
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| プロパティ | 型 | 説明 |
|
|
211
|
+
|-----------|-----|------|
|
|
212
|
+
| `fontFamily` | `string` | フォント名 |
|
|
213
|
+
| `fontSize` | `number` | フォントサイズ |
|
|
214
|
+
| `bold` | `boolean` | 太字 |
|
|
215
|
+
| `italic` | `boolean` | 斜体 |
|
|
216
|
+
| `underline` | `boolean` | 下線 |
|
|
217
|
+
| `strike` | `boolean` | 取り消し線 |
|
|
218
|
+
| `color` | `string` | 文字色(#RRGGBB形式) |
|
|
219
|
+
| `fill` | `string` | 背景色(#RRGGBB形式) |
|
|
220
|
+
| `align` | `"left"` \| `"center"` \| `"right"` | 水平配置 |
|
|
221
|
+
| `format` | `"string"` \| `"number"` \| `"date"` | セル書式 |
|
|
222
|
+
| `decimalPlaces` | `number` | 小数点以下の桁数 |
|
|
223
|
+
| `thousandsSeparator` | `boolean` | 3桁区切りを使用 |
|
|
224
|
+
|
|
225
|
+
### テーブルスタイル(TableStyle)
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
{
|
|
229
|
+
header?: CellStyle, // ヘッダー行のスタイル
|
|
230
|
+
body?: CellStyle, // ボディ行のスタイル
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### スタイルの優先度(低 → 高)
|
|
235
|
+
|
|
236
|
+
1. プリセット
|
|
237
|
+
2. テーブルスタイル(`style.header` / `style.body`)
|
|
238
|
+
3. 列スタイル(`columns[].style`)
|
|
239
|
+
4. 条件付きスタイル(`conditionalStyle`)
|
|
240
|
+
5. セル単位スタイル(`data[]._style`)
|
|
241
|
+
|
|
242
|
+
</details>
|
|
243
|
+
|
|
244
|
+
<details>
|
|
245
|
+
<summary><strong>罫線(BorderStyle)</strong></summary>
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
{
|
|
249
|
+
outline?: LineStyle, // 外枠(テーブル全体)
|
|
250
|
+
headerBody?: LineStyle, // ヘッダーとボディの境界
|
|
251
|
+
headerInner?: LineStyle, // ヘッダー内部
|
|
252
|
+
bodyInner?: LineStyle, // ボディ内部
|
|
253
|
+
borderColor?: string, // 罫線色 "#RRGGBB"
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
| プロパティ | 型 | 説明 |
|
|
258
|
+
|-----------|-----|------|
|
|
259
|
+
| `outline` | `LineStyle` | テーブル外枠の罫線 |
|
|
260
|
+
| `headerBody` | `LineStyle` | ヘッダーとボディの境界線 |
|
|
261
|
+
| `headerInner` | `LineStyle` | ヘッダー内部の罫線 |
|
|
262
|
+
| `bodyInner` | `LineStyle` | ボディ内部の罫線 |
|
|
263
|
+
| `borderColor` | `string` | 罫線の色(#RRGGBB形式) |
|
|
264
|
+
|
|
265
|
+
### LineStyle
|
|
266
|
+
|
|
267
|
+
`"thin"` | `"medium"` | `"thick"` | `"dotted"` | `"dashed"` | `"double"`
|
|
268
|
+
|
|
269
|
+
| LineStyle | 説明 |
|
|
270
|
+
|-----------|------|
|
|
271
|
+
| `thin` | 細線 |
|
|
272
|
+
| `medium` | 中線 |
|
|
273
|
+
| `thick` | 太線 |
|
|
274
|
+
| `dotted` | 点線 |
|
|
275
|
+
| `dashed` | 破線 |
|
|
276
|
+
| `double` | 二重線 |
|
|
277
|
+
|
|
278
|
+
</details>
|
|
279
|
+
|
|
280
|
+
<details>
|
|
281
|
+
<summary><strong>画像オプション(ImageOptions)</strong></summary>
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
.image({
|
|
285
|
+
source: Buffer | string, // Bufferまたはファイルパス
|
|
286
|
+
width?: number, // 幅(ピクセル)
|
|
287
|
+
height?: number, // 高さ(ピクセル)
|
|
288
|
+
row?: number, // 行位置(0-indexed)
|
|
289
|
+
col?: number, // 列位置(0-indexed)
|
|
290
|
+
})
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
| プロパティ | 型 | 説明 |
|
|
294
|
+
|-----------|-----|------|
|
|
295
|
+
| `source` | `Buffer` \| `string` | 画像データまたはファイルパス |
|
|
296
|
+
| `width` | `number` | 画像の幅(ピクセル) |
|
|
297
|
+
| `height` | `number` | 画像の高さ(ピクセル) |
|
|
298
|
+
| `row` | `number` | 配置する行(0から開始) |
|
|
299
|
+
| `col` | `number` | 配置する列(0から開始) |
|
|
300
|
+
|
|
301
|
+
</details>
|
|
302
|
+
|
|
303
|
+
<details>
|
|
304
|
+
<summary><strong>テキストオプション(TextInput)</strong></summary>
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// シンプルなテキスト
|
|
308
|
+
.text("タイトル")
|
|
309
|
+
|
|
310
|
+
// スタイル付きテキスト
|
|
311
|
+
.text({
|
|
312
|
+
value: "重要なテキスト",
|
|
313
|
+
style: { bold: true, fontSize: 14, color: "#FF0000" }
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
| 形式 | 説明 |
|
|
318
|
+
|------|------|
|
|
319
|
+
| `string` | プレーンテキスト |
|
|
320
|
+
| `{ value, style? }` | スタイル付きテキスト |
|
|
321
|
+
|
|
322
|
+
</details>
|
|
323
|
+
|
|
324
|
+
<details>
|
|
325
|
+
<summary><strong>出力(NodeOutput / BrowserOutput)</strong></summary>
|
|
326
|
+
|
|
327
|
+
### Node.js出力
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
const output = await xlkit().sheet().table({...}).getNode();
|
|
331
|
+
|
|
332
|
+
// ファイルに保存
|
|
333
|
+
await output.saveToFile("./report.xlsx");
|
|
334
|
+
|
|
335
|
+
// Bufferとして取得(API応答などに利用)
|
|
336
|
+
const buffer = await output.toBuffer();
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### ブラウザ出力
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
const output = await xlkit().sheet().table({...}).getBrowser();
|
|
343
|
+
|
|
344
|
+
// ダウンロード
|
|
345
|
+
await output.download("report.xlsx");
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
</details>
|
|
349
|
+
|
|
350
|
+
<details>
|
|
351
|
+
<summary><strong>読み取りAPI</strong></summary>
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { read } from "xlkit";
|
|
355
|
+
|
|
356
|
+
// ファイルパスまたはBufferから読み込み
|
|
357
|
+
const workbook = await read("report.xlsx");
|
|
358
|
+
// または
|
|
359
|
+
const workbook = await read(buffer);
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### WorkbookReader
|
|
363
|
+
|
|
364
|
+
| プロパティ/メソッド | 戻り値 | 説明 |
|
|
365
|
+
|-------------------|--------|------|
|
|
366
|
+
| `sheetNames` | `string[]` | シート名の一覧 |
|
|
367
|
+
| `sheetCount` | `number` | シート数 |
|
|
368
|
+
| `sheet(name)` | `SheetReader` | 名前でシートを取得 |
|
|
369
|
+
| `sheetAt(index)` | `SheetReader` | インデックスでシートを取得 |
|
|
370
|
+
|
|
371
|
+
### SheetReader
|
|
372
|
+
|
|
373
|
+
| プロパティ/メソッド | 戻り値 | 説明 |
|
|
374
|
+
|-------------------|--------|------|
|
|
375
|
+
| `name` | `string` | シート名 |
|
|
376
|
+
| `rowCount` | `number` | 行数 |
|
|
377
|
+
| `columnCount` | `number` | 列数 |
|
|
378
|
+
| `mergedCells` | `string[]` | マージ情報("A1:B2"形式) |
|
|
379
|
+
| `cell(address)` | `CellReader` | A1形式でセルを取得 |
|
|
380
|
+
| `cellAt(row, col)` | `CellReader` | 行・列番号でセルを取得 |
|
|
381
|
+
|
|
382
|
+
### CellReader
|
|
383
|
+
|
|
384
|
+
| プロパティ | 戻り値 | 説明 |
|
|
385
|
+
|-----------|--------|------|
|
|
386
|
+
| `value` | `string \| number \| boolean \| null` | セルの値 |
|
|
387
|
+
| `style` | `CellStyle \| undefined` | セルのスタイル |
|
|
388
|
+
| `border` | `CellBorder \| undefined` | セルの罫線情報 |
|
|
389
|
+
|
|
390
|
+
</details>
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## 使い方例
|
|
395
|
+
|
|
396
|
+
> **参考**: より多くの使用例は[結合テスト](https://github.com/yn1323/xlkit/tree/main/tests)を参照してください。
|
|
397
|
+
|
|
398
|
+
<details>
|
|
399
|
+
<summary><strong>基本的なテーブル</strong></summary>
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
const output = await xlkit()
|
|
403
|
+
.sheet("データ")
|
|
404
|
+
.table({
|
|
405
|
+
columns: [
|
|
406
|
+
{ key: "name", label: "商品名" },
|
|
407
|
+
{ key: "price", label: "価格" },
|
|
408
|
+
],
|
|
409
|
+
data: [
|
|
410
|
+
{ name: "りんご", price: 100 },
|
|
411
|
+
{ name: "みかん", price: 80 },
|
|
412
|
+
],
|
|
413
|
+
})
|
|
414
|
+
.getNode();
|
|
415
|
+
|
|
416
|
+
await output.saveToFile("basic.xlsx");
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
</details>
|
|
420
|
+
|
|
421
|
+
<details>
|
|
422
|
+
<summary><strong>プリセット使用</strong></summary>
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// basic: 青ヘッダー + 全罫線
|
|
426
|
+
.table({
|
|
427
|
+
preset: "basic",
|
|
428
|
+
columns: [...],
|
|
429
|
+
data: [...],
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
// minimal: ヘッダー太字のみ、罫線なし
|
|
433
|
+
.table({
|
|
434
|
+
preset: "minimal",
|
|
435
|
+
columns: [...],
|
|
436
|
+
data: [...],
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
// striped: 青ヘッダー + 奇数行グレー背景
|
|
440
|
+
.table({
|
|
441
|
+
preset: "striped",
|
|
442
|
+
columns: [...],
|
|
443
|
+
data: [...],
|
|
444
|
+
})
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
</details>
|
|
448
|
+
|
|
449
|
+
<details>
|
|
450
|
+
<summary><strong>複数シート</strong></summary>
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
const output = await xlkit()
|
|
454
|
+
.sheet("売上")
|
|
455
|
+
.table({
|
|
456
|
+
preset: "basic",
|
|
457
|
+
columns: [
|
|
458
|
+
{ key: "name", label: "商品名" },
|
|
459
|
+
{ key: "sales", label: "売上" },
|
|
460
|
+
],
|
|
461
|
+
data: salesData,
|
|
462
|
+
})
|
|
463
|
+
.sheet("在庫") // 2つ目のシート
|
|
464
|
+
.table({
|
|
465
|
+
preset: "basic",
|
|
466
|
+
columns: [
|
|
467
|
+
{ key: "name", label: "商品名" },
|
|
468
|
+
{ key: "stock", label: "在庫数" },
|
|
469
|
+
],
|
|
470
|
+
data: stockData,
|
|
471
|
+
})
|
|
472
|
+
.getNode();
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
</details>
|
|
476
|
+
|
|
477
|
+
<details>
|
|
478
|
+
<summary><strong>カラム別スタイル</strong></summary>
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
.table({
|
|
482
|
+
columns: [
|
|
483
|
+
{ key: "name", label: "名前" },
|
|
484
|
+
{ key: "important", label: "重要", style: { bold: true } },
|
|
485
|
+
{ key: "warning", label: "警告", style: { color: "#FF0000" } },
|
|
486
|
+
{ key: "highlight", label: "ハイライト", style: { fill: "#FFFF00" } },
|
|
487
|
+
],
|
|
488
|
+
data: [...],
|
|
489
|
+
})
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
</details>
|
|
493
|
+
|
|
494
|
+
<details>
|
|
495
|
+
<summary><strong>マルチヘッダー(階層ヘッダー)</strong></summary>
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
.table({
|
|
499
|
+
columns: [
|
|
500
|
+
{
|
|
501
|
+
label: "商品情報",
|
|
502
|
+
children: [
|
|
503
|
+
{ key: "category", label: "カテゴリ" },
|
|
504
|
+
{ key: "name", label: "商品名" },
|
|
505
|
+
],
|
|
506
|
+
},
|
|
507
|
+
{ key: "price", label: "価格" },
|
|
508
|
+
{ key: "quantity", label: "数量" },
|
|
509
|
+
],
|
|
510
|
+
data: [
|
|
511
|
+
{ category: "食品", name: "りんご", price: 100, quantity: 50 },
|
|
512
|
+
{ category: "食品", name: "みかん", price: 80, quantity: 100 },
|
|
513
|
+
],
|
|
514
|
+
})
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
3階層も可能:
|
|
518
|
+
|
|
519
|
+
```typescript
|
|
520
|
+
{
|
|
521
|
+
label: "商品",
|
|
522
|
+
children: [
|
|
523
|
+
{
|
|
524
|
+
label: "詳細",
|
|
525
|
+
children: [
|
|
526
|
+
{ key: "category", label: "カテゴリ" },
|
|
527
|
+
{ key: "name", label: "商品名" },
|
|
528
|
+
],
|
|
529
|
+
},
|
|
530
|
+
],
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
</details>
|
|
535
|
+
|
|
536
|
+
<details>
|
|
537
|
+
<summary><strong>同値セルマージ</strong></summary>
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
// テーブル全体でマージ
|
|
541
|
+
.table({
|
|
542
|
+
mergeSameValues: true,
|
|
543
|
+
columns: [
|
|
544
|
+
{ key: "category", label: "カテゴリ" },
|
|
545
|
+
{ key: "name", label: "商品名" },
|
|
546
|
+
{ key: "price", label: "価格" },
|
|
547
|
+
],
|
|
548
|
+
data: [
|
|
549
|
+
{ category: "食品", name: "りんご", price: 100 },
|
|
550
|
+
{ category: "食品", name: "みかん", price: 80 }, // 「食品」が縦マージされる
|
|
551
|
+
{ category: "家電", name: "テレビ", price: 50000 },
|
|
552
|
+
{ category: "家電", name: "冷蔵庫", price: 80000 }, // 「家電」が縦マージされる
|
|
553
|
+
],
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
// 列単位でマージ
|
|
557
|
+
.table({
|
|
558
|
+
columns: [
|
|
559
|
+
{ key: "category", label: "カテゴリ", mergeSameValues: true }, // この列のみマージ
|
|
560
|
+
{ key: "name", label: "商品名" }, // マージしない
|
|
561
|
+
{ key: "price", label: "価格" },
|
|
562
|
+
],
|
|
563
|
+
data: [...],
|
|
564
|
+
})
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
</details>
|
|
568
|
+
|
|
569
|
+
<details>
|
|
570
|
+
<summary><strong>罫線</strong></summary>
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
// 外枠のみ
|
|
574
|
+
.table({
|
|
575
|
+
columns: [...],
|
|
576
|
+
data: [...],
|
|
577
|
+
border: { outline: "medium" },
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
// ヘッダー下線のみ
|
|
581
|
+
.table({
|
|
582
|
+
columns: [...],
|
|
583
|
+
data: [...],
|
|
584
|
+
border: { headerBody: "thin" },
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
// フルカスタマイズ
|
|
588
|
+
.table({
|
|
589
|
+
columns: [...],
|
|
590
|
+
data: [...],
|
|
591
|
+
border: {
|
|
592
|
+
outline: "medium",
|
|
593
|
+
headerBody: "medium",
|
|
594
|
+
headerInner: "thin",
|
|
595
|
+
bodyInner: "thin",
|
|
596
|
+
borderColor: "#000080",
|
|
597
|
+
},
|
|
598
|
+
})
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
</details>
|
|
602
|
+
|
|
603
|
+
<details>
|
|
604
|
+
<summary><strong>画像挿入</strong></summary>
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
import { readFileSync } from "fs";
|
|
608
|
+
|
|
609
|
+
const logoBuffer = readFileSync("./logo.png");
|
|
610
|
+
|
|
611
|
+
const output = await xlkit()
|
|
612
|
+
.sheet("レポート")
|
|
613
|
+
.text({ value: "月次レポート", style: { bold: true, fontSize: 16 } })
|
|
614
|
+
.space(1)
|
|
615
|
+
.image({
|
|
616
|
+
source: logoBuffer,
|
|
617
|
+
width: 150,
|
|
618
|
+
height: 75,
|
|
619
|
+
})
|
|
620
|
+
.space(2)
|
|
621
|
+
.table({
|
|
622
|
+
preset: "basic",
|
|
623
|
+
columns: [...],
|
|
624
|
+
data: [...],
|
|
625
|
+
})
|
|
626
|
+
.getNode();
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
</details>
|
|
630
|
+
|
|
631
|
+
<details>
|
|
632
|
+
<summary><strong>テキスト + テーブル組み合わせ</strong></summary>
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
const output = await xlkit()
|
|
636
|
+
.sheet("レポート")
|
|
637
|
+
.text({ value: "売上レポート", style: { bold: true, fontSize: 16 } })
|
|
638
|
+
.text("2024年1月分")
|
|
639
|
+
.space(2)
|
|
640
|
+
.table({
|
|
641
|
+
preset: "basic",
|
|
642
|
+
columns: [...],
|
|
643
|
+
data: [...],
|
|
644
|
+
})
|
|
645
|
+
.space(1)
|
|
646
|
+
.text("※ 金額は税抜きです")
|
|
647
|
+
.getNode();
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
</details>
|
|
651
|
+
|
|
652
|
+
<details>
|
|
653
|
+
<summary><strong>セル単位スタイル(_style)</strong></summary>
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
.table({
|
|
657
|
+
columns: [
|
|
658
|
+
{ key: "name", label: "名前" },
|
|
659
|
+
{ key: "price", label: "価格" },
|
|
660
|
+
{ key: "status", label: "ステータス" },
|
|
661
|
+
],
|
|
662
|
+
data: [
|
|
663
|
+
{ name: "通常商品", price: 100, status: "在庫あり" },
|
|
664
|
+
{
|
|
665
|
+
name: "特価商品",
|
|
666
|
+
price: 50,
|
|
667
|
+
status: "セール中",
|
|
668
|
+
_style: {
|
|
669
|
+
price: { bold: true, fill: "#FFFF00" }, // 価格列を強調
|
|
670
|
+
status: { color: "#FF0000" }, // ステータスを赤文字
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
name: "品切れ",
|
|
675
|
+
price: 200,
|
|
676
|
+
status: "在庫なし",
|
|
677
|
+
_style: {
|
|
678
|
+
status: { color: "#999999", italic: true },
|
|
679
|
+
},
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
})
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
</details>
|
|
686
|
+
|
|
687
|
+
<details>
|
|
688
|
+
<summary><strong>条件付きスタイル</strong></summary>
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
.table({
|
|
692
|
+
columns: [
|
|
693
|
+
{ key: "name", label: "商品名" },
|
|
694
|
+
{ key: "price", label: "価格" },
|
|
695
|
+
{ key: "profit", label: "利益率" },
|
|
696
|
+
],
|
|
697
|
+
data: [...],
|
|
698
|
+
conditionalStyle: (row, col) => {
|
|
699
|
+
// 利益率が負の場合は赤文字
|
|
700
|
+
if (col === "profit" && row.profit < 0) {
|
|
701
|
+
return { color: "#FF0000" };
|
|
702
|
+
}
|
|
703
|
+
// 価格が10000以上の場合は太字
|
|
704
|
+
if (col === "price" && row.price >= 10000) {
|
|
705
|
+
return { bold: true };
|
|
706
|
+
}
|
|
707
|
+
return {};
|
|
708
|
+
},
|
|
709
|
+
})
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
</details>
|
|
713
|
+
|
|
714
|
+
<details>
|
|
715
|
+
<summary><strong>列幅自動調整</strong></summary>
|
|
716
|
+
|
|
717
|
+
```typescript
|
|
718
|
+
// ヘッダーとボディ両方の最大幅で調整
|
|
719
|
+
.table({
|
|
720
|
+
autoWidth: "all",
|
|
721
|
+
columns: [...],
|
|
722
|
+
data: [...],
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
// ボディのみで調整(ヘッダーは無視)
|
|
726
|
+
.table({
|
|
727
|
+
autoWidth: "body",
|
|
728
|
+
columns: [...],
|
|
729
|
+
data: [...],
|
|
730
|
+
})
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
</details>
|
|
734
|
+
|
|
735
|
+
<details>
|
|
736
|
+
<summary><strong>数値書式</strong></summary>
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
.table({
|
|
740
|
+
columns: [
|
|
741
|
+
{ key: "name", label: "商品名" },
|
|
742
|
+
{
|
|
743
|
+
key: "price",
|
|
744
|
+
label: "価格",
|
|
745
|
+
style: {
|
|
746
|
+
format: "number",
|
|
747
|
+
thousandsSeparator: true, // 3桁区切り: 1,234,567
|
|
748
|
+
},
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
key: "rate",
|
|
752
|
+
label: "割合",
|
|
753
|
+
style: {
|
|
754
|
+
format: "number",
|
|
755
|
+
decimalPlaces: 2, // 小数点2桁: 12.34
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
],
|
|
759
|
+
data: [
|
|
760
|
+
{ name: "商品A", price: 1234567, rate: 12.3456 },
|
|
761
|
+
],
|
|
762
|
+
})
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
</details>
|
|
766
|
+
|
|
767
|
+
<details>
|
|
768
|
+
<summary><strong>読み取りAPI</strong></summary>
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
import { read } from "xlkit";
|
|
772
|
+
|
|
773
|
+
// Excelファイルを読み込み
|
|
774
|
+
const workbook = await read("./report.xlsx");
|
|
775
|
+
|
|
776
|
+
// シート一覧を取得
|
|
777
|
+
console.log(workbook.sheetNames); // ["売上", "在庫"]
|
|
778
|
+
|
|
779
|
+
// シートを取得
|
|
780
|
+
const sheet = workbook.sheet("売上");
|
|
781
|
+
|
|
782
|
+
// セルの値を取得
|
|
783
|
+
console.log(sheet.cell("A1").value); // "商品名"
|
|
784
|
+
console.log(sheet.cell("B2").value); // 100
|
|
785
|
+
|
|
786
|
+
// セルのスタイルを取得
|
|
787
|
+
const style = sheet.cell("A1").style;
|
|
788
|
+
console.log(style?.bold); // true
|
|
789
|
+
console.log(style?.fill); // "#4472C4"
|
|
790
|
+
|
|
791
|
+
// マージ情報を取得
|
|
792
|
+
console.log(sheet.mergedCells); // ["A1:B1", "C1:C2"]
|
|
793
|
+
|
|
794
|
+
// 行・列番号でアクセス(0-indexed)
|
|
795
|
+
console.log(sheet.cellAt(0, 0).value); // A1の値
|
|
796
|
+
console.log(sheet.cellAt(1, 1).value); // B2の値
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
</details>
|
|
800
|
+
|
|
801
|
+
---
|
|
802
|
+
|
|
803
|
+
## プリセット一覧
|
|
804
|
+
|
|
805
|
+
| プリセット | ヘッダー | ボディ | 罫線 |
|
|
806
|
+
|-----------|---------|--------|------|
|
|
807
|
+
| `basic` | 青背景(#4472C4)・白文字・太字 | - | 全罫線(thin) |
|
|
808
|
+
| `minimal` | 太字のみ | - | なし |
|
|
809
|
+
| `striped` | 青背景(#4472C4)・白文字・太字 | 奇数行グレー背景(#F2F2F2) | 全罫線(thin) |
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
## サポートしていない機能
|
|
814
|
+
|
|
815
|
+
| 機能 | 理由 |
|
|
816
|
+
|------|------|
|
|
817
|
+
| チャート | スコープ外 |
|
|
818
|
+
| 数式 | 計算はプログラム側で行う想定 |
|
|
819
|
+
| 既存Excelへの追記 | 常に新規作成のみ |
|
|
820
|
+
| 列幅・行高の読み取り | 読み取りAPIの対象外 |
|
|
821
|
+
| ピボットテーブル | スコープ外 |
|
|
822
|
+
| マクロ | スコープ外 |
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
## Excel制約
|
|
827
|
+
|
|
828
|
+
xlkitはExcelの制約を自動でチェックし、違反時はエラーをスローします。
|
|
829
|
+
|
|
830
|
+
| 項目 | 制約 |
|
|
831
|
+
|------|------|
|
|
832
|
+
| シート名 | 最大31文字 |
|
|
833
|
+
| シート名禁止文字 | `: \ / ? * [ ]` |
|
|
834
|
+
| 最大行数 | 1,048,576行 |
|
|
835
|
+
| 最大列数 | 16,384列 |
|
|
836
|
+
|
|
837
|
+
---
|
|
838
|
+
|
|
839
|
+
## ライセンス
|
|
840
|
+
|
|
841
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xlkit",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -13,13 +13,8 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
"demo",
|
|
18
|
-
"bin"
|
|
16
|
+
"dist"
|
|
19
17
|
],
|
|
20
|
-
"bin": {
|
|
21
|
-
"xlkit": "./bin/cli.js"
|
|
22
|
-
},
|
|
23
18
|
"scripts": {
|
|
24
19
|
"build": "tsup",
|
|
25
20
|
"demo": "vite demo",
|
|
@@ -58,6 +53,7 @@
|
|
|
58
53
|
"@types/react": "^19.1.0",
|
|
59
54
|
"@types/react-dom": "^19.1.0",
|
|
60
55
|
"@vitejs/plugin-react": "^4.4.1",
|
|
56
|
+
"esbuild-plugins-node-modules-polyfill": "^1.7.1",
|
|
61
57
|
"react": "^19.1.0",
|
|
62
58
|
"react-dom": "^19.1.0",
|
|
63
59
|
"tsup": "^8.5.0",
|
package/bin/cli.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const demoDir = join(__dirname, "..", "demo");
|
|
8
|
-
|
|
9
|
-
const command = process.argv[2];
|
|
10
|
-
|
|
11
|
-
if (command === "demo") {
|
|
12
|
-
spawn("npx", ["vite", demoDir], { stdio: "inherit", shell: true });
|
|
13
|
-
} else {
|
|
14
|
-
console.log("Usage: xlkit demo");
|
|
15
|
-
}
|
package/demo/index.html
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="ja">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>xlkit Demo</title>
|
|
7
|
-
</head>
|
|
8
|
-
<body>
|
|
9
|
-
<div id="root"></div>
|
|
10
|
-
<script type="module" src="./main.tsx"></script>
|
|
11
|
-
</body>
|
|
12
|
-
</html>
|
package/demo/main.tsx
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { StrictMode } from "react";
|
|
2
|
-
import { createRoot } from "react-dom/client";
|
|
3
|
-
|
|
4
|
-
function App() {
|
|
5
|
-
return (
|
|
6
|
-
<div style={{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
|
|
7
|
-
<h1>xlkit Demo</h1>
|
|
8
|
-
<p>Declarative Excel generation library</p>
|
|
9
|
-
<button type="button" onClick={() => alert("Coming soon!")}>
|
|
10
|
-
Download Sample Excel
|
|
11
|
-
</button>
|
|
12
|
-
</div>
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const root = document.getElementById("root");
|
|
17
|
-
if (root) {
|
|
18
|
-
createRoot(root).render(
|
|
19
|
-
<StrictMode>
|
|
20
|
-
<App />
|
|
21
|
-
</StrictMode>,
|
|
22
|
-
);
|
|
23
|
-
}
|