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 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.0",
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
- }
@@ -1,7 +0,0 @@
1
- import react from "@vitejs/plugin-react";
2
- import { defineConfig } from "vite";
3
-
4
- export default defineConfig({
5
- plugins: [react()],
6
- root: __dirname,
7
- });