packing-bingo 0.2.0 → 0.3.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
@@ -1,24 +1,25 @@
1
1
  # 持ちものビンゴ(Packing Bingo)
2
2
 
3
- **修学旅行の持ち物チェックリストを、4×4のビンゴにするReactコンポーネントです。持ち物をかばんに入れるたびにマスをタップしてスタンプを押し、タテ・ヨコ・ナナメがそろうと、コンボ・ファンファーレ・花火で盛り上がります。進み具合は各自のブラウザに自動で保存されます。**
3
+ **修学旅行の持ち物チェックリストを、4×4(3×3や5×5などにも変えられます)のビンゴにするReactコンポーネントです。持ち物をかばんに入れるたびにマスをタップしてスタンプを押し、タテ・ヨコ・ナナメがそろうと、コンボ・ファンファーレ・花火で盛り上がります。進み具合は各自のブラウザに自動で保存されます。**
4
4
 
5
+ [![Demo on GitHub Pages](https://img.shields.io/badge/demo-GitHub%20Pages-222222?logo=githubpages&logoColor=white)](https://jin-hiroo.github.io/packing-bingo/)
5
6
  [![npm version](https://img.shields.io/npm/v/packing-bingo?logo=npm)](https://www.npmjs.com/package/packing-bingo)
6
7
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
8
  [![React 18 or later](https://img.shields.io/badge/React-%E2%89%A518-61DAFB?logo=react&logoColor=black)](#概要)
8
9
  [![TypeScript types included](https://img.shields.io/badge/types-TypeScript-3178C6?logo=typescript&logoColor=white)](#api)
9
10
  [![Standalone single HTML file](https://img.shields.io/badge/standalone-single%20HTML%20file-E34F26?logo=html5&logoColor=white)](#単体のページ)
10
11
 
11
- [クイックスタート](#クイックスタート) · [使い方の例](#使い方の例) · [API](#api) · [組み込むときの注意](#組み込むときの注意) · [Reactなしで使う](#reactなしで使う) · [English](#english) · [変更履歴](CHANGELOG.md)
12
+ [デモサイト](https://jin-hiroo.github.io/packing-bingo/) · [クイックスタート](#クイックスタート) · [デモコード](#デモ) · [使い方の例](#使い方の例) · [API](#api) · [組み込むときの注意](#組み込むときの注意) · [Reactなしで使う](#reactなしで使う) · [English](#english) · [変更履歴](CHANGELOG.md)
12
13
 
13
14
  <p align="center">
14
- <a href="docs/images/bingo-desktop.png"><img src="docs/images/bingo-desktop.png" alt="持ち物が並んだ4×4のビンゴカード。ナナメの列がそろった瞬間で、大きなBINGO!の文字、+100の得点、紙吹雪が出ている。上の欄には8/16、2ライン、2コンボと表示されている。" width="560"></a>
15
+ <a href="docs/images/bingo-desktop.png"><img src="docs/images/bingo-desktop.png" alt="持ち物が並んだ4×4のビンゴカード。一番上の列に続いて、左上からのナナメの列がそろった瞬間で、大きなBINGO!の文字、「2 ライン目!」、+100の得点、紙吹雪が出ている。上の欄には8/16、2ライン、290点、2 COMBOと表示されている。" width="560"></a>
15
16
  </p>
16
17
 
17
18
  _2コンボで2列目がそろったところ。列に線が引かれ、BINGO!と+100が出て、紙吹雪が舞います。_
18
19
 
19
20
  <p>
20
- <a href="docs/images/phone-reach.png"><img src="docs/images/phone-reach.png" alt="スマホの画面。1列そろっていて、次の列を完成させる最後のマスが点線で囲まれ、リーチの札が付いて点滅している。" height="320"></a>
21
- <a href="docs/images/phone-combo.png"><img src="docs/images/phone-combo.png" alt="スマホの画面。3回続けてタップしたところで、+20と+30、2 COMBO!と3 COMBO!の表示、減っていくコンボのゲージ、2つのリーチが見える。" height="320"></a>
21
+ <a href="docs/images/phone-reach.png"><img src="docs/images/phone-reach.png" alt="スマホの画面。一番上の列がそろっていて、ナナメの列を完成させる最後のマス(日焼け止め)が点線で囲まれ、リーチの札が付いて点滅している。下の1行には「リーチ! あと「日焼け止め」でビンゴ」と出ている。" height="320"></a>
22
+ <a href="docs/images/phone-combo.png"><img src="docs/images/phone-combo.png" alt="スマホの画面。3回続けてタップしたところで、+20と+30、2 COMBO!と3 COMBO!の表示、減っていくコンボのゲージ、新しく出た2つのリーチ!が見える。" height="320"></a>
22
23
  <a href="docs/images/phone-perfect.png"><img src="docs/images/phone-perfect.png" alt="スマホの画面。16個すべてにスタンプを押したあと。準備完了のスタンプと、得点3,410、最大コンボ×10、10ラインのパーフェクト画面に、紙吹雪と持ち物の絵文字が降っている。" height="320"></a>
23
24
  </p>
24
25
 
@@ -30,7 +31,8 @@ _スマホでの表示。左から、1列そろって次の列にリーチがか
30
31
 
31
32
  - **タップするたびに反応があります。** スタンプ、波紋、火花、少しずつ高くなる音。2.5秒以内に続けてタップすると、コンボになります。
32
33
  - **次の目標がいつも見えます。** あと1つで列がそろうマスは、リーチの札を付けて点滅し、下の1行にその持ち物の名前が出ます。
33
- - **列がそろうたびにお祝いします。** タテ・ヨコ・ナナメの10列のどれかがそろうと、BINGO!の文字と紙吹雪が出ます。16個すべてそろうと、花火とスコアカードのフィナーレになります。
34
+ - **列がそろうたびにお祝いします。** タテ・ヨコ・ナナメの列(4×4なら10列)のどれかがそろうと、BINGO!の文字と紙吹雪が出ます。すべてそろうと、花火とスコアカードのフィナーレになります。
35
+ - **マスの数を選べます。** 3×3や5×5など、2×2から10×10までの正方形にできます。奇数のときは、真ん中をはじめからスタンプ済みのFREEマスにできます。[マスの数を変える](#マスの数を変える)を見てください。
34
36
  - **得点は、そろえることへのごほうびです。** 付けたり外したりしても増えません。[得点のルール](#得点のルール)を見てください。
35
37
  - **進み具合は自動で保存されます。** 各自のブラウザ(localStorage)に保存されるので、ページを閉じても続きから始められます。サーバーには何も送りません。
36
38
  - **だれでも使えるように配慮しています。** マスは本物のチェックボックスなので、キーボードで操作できます。`prefers-reduced-motion`(動きを減らす設定)に従います。音は合成した音(音声ファイルなし)で、消すこともできます。
@@ -41,7 +43,7 @@ _スマホでの表示。左から、1列そろって次の列にリーチがか
41
43
  | --- | --- |
42
44
  | インストール | `npm install packing-bingo`。ESMとCommonJSの両方、TypeScriptの型付き |
43
45
  | 必要なもの | React 18以上と `react-dom`。ほかに依存するパッケージはありません。CSSの読み込みもProviderも不要です |
44
- | サイズ | 約13 kB(minify・gzip後、CSS込み、Reactは含まず) |
46
+ | サイズ | 約14 kB(minify・gzip後、CSS込み、Reactは含まず) |
45
47
  | サーバー側での表示 | `"use client"` 付きで配布しています。サーバー側では空の `<div>` を出し、ブラウザでカードを作ります |
46
48
  | 見た目 | `<head>` に `<style>` を1つ追加します。クラス名はすべて `pkb-` で始まります。CSS変数で配色を変えられ、置いた場所の幅に合わせて自動で大きさが変わります |
47
49
  | 通信 | Google Fontsからフォントを2つ読み込みます(`fonts={false}` で止められます) |
@@ -60,68 +62,66 @@ npm install packing-bingo
60
62
 
61
63
  pnpmなら `pnpm add packing-bingo`、yarnなら `yarn add packing-bingo` です。
62
64
 
63
- ### 2. 部品のファイルを作る
65
+ ### 2. 表示したい場所に置く
64
66
 
65
- `OkinawaPackingBingo.tsx` というファイルを作り、次をそのまま貼り付けます。置き場所はどこでも構いません(例:`components/OkinawaPackingBingo.tsx`)。
67
+ 表示したいページやコンポーネントで読み込み、置きたい場所に `<PackingBingo />` と書きます。一般的な修学旅行の持ち物16個が、4×4のカードに並びます。
66
68
 
67
69
  ```tsx
68
- import { PackingBingo, type BingoItem } from "packing-bingo";
69
-
70
- // 沖縄の修学旅行の持ち物。ちょうど16個、左上から横に4つずつ並びます。
71
- const items: BingoItem[] = [
72
- { emoji: "📖", label: "旅のしおり" },
73
- { emoji: "🪪", label: "生徒手帳" },
74
- { emoji: "🏥", label: "保険証コピー" },
75
- { emoji: "👛", label: "おこづかい" },
76
- { emoji: "👕", label: "着替え" },
77
- { emoji: "🩱", label: "水着" },
78
- { emoji: "🧴", label: "日焼け止め" },
79
- { emoji: "🧢", label: "帽子" },
80
- { emoji: "🕶️", label: "サングラス" },
81
- { emoji: "🩴", label: "ビーチサンダル" },
82
- { emoji: "🪥", label: "洗面用具" },
83
- { emoji: "🛁", label: "タオル" },
84
- { emoji: "💊", label: "常備薬・酔い止め" },
85
- { emoji: "☂️", label: "折りたたみ傘" },
86
- { emoji: "🛍️", label: "ビニール袋" },
87
- { emoji: "📱", label: "スマホ・充電器" },
88
- ];
89
-
90
- export function OkinawaPackingBingo() {
91
- return <PackingBingo items={items} tag="沖縄修学旅行" />;
92
- }
93
- ```
94
-
95
- ### 3. 表示したい場所に置く
96
-
97
- 表示したいページやコンポーネントで読み込み、置きたい場所に `<OkinawaPackingBingo />` と書きます。`import` のパスは、ファイルを置いた場所に合わせてください。
98
-
99
- ```tsx
100
- import { OkinawaPackingBingo } from "./components/OkinawaPackingBingo";
70
+ import { PackingBingo } from "packing-bingo";
101
71
 
102
72
  // JSXの中の、表示したい場所に
103
- <OkinawaPackingBingo />
73
+ <PackingBingo />
104
74
  ```
105
75
 
106
76
  Next.js(App Router)で専用のページを作るなら、`app/packing/page.tsx` を作って次を貼り付けます。`/packing` を開くと表示されます。
107
77
 
108
78
  ```tsx
109
- import { OkinawaPackingBingo } from "@/components/OkinawaPackingBingo";
79
+ import { PackingBingo } from "packing-bingo";
110
80
 
111
81
  export default function PackingPage() {
112
82
  return (
113
83
  <main style={{ padding: 16 }}>
114
- <OkinawaPackingBingo />
84
+ <PackingBingo />
115
85
  </main>
116
86
  );
117
87
  }
118
88
  ```
119
89
 
120
- `@/` は、`create-next-app` で作ったプロジェクトに最初から設定されている、プロジェクトの一番上を指す書き方です。部品のファイルは `components/OkinawaPackingBingo.tsx` に置いてください。`src/` フォルダを使うプロジェクトでは `src/components/OkinawaPackingBingo.tsx` です。`@/` が使えないプロジェクトでは、`../../components/OkinawaPackingBingo` のような相対パスに書き換えてください。
90
+ カードは置いた場所の幅いっぱい(最大720 px)に広がり、狭い場所ではスマホ向けの配置に自動で切り替わります。
91
+
92
+ Next.jsのServer Componentのページからでも、そのまま使えます。ただし、`onBingo` などの関数(コールバック)はServer Componentから渡せず、「Event handlers cannot be passed to Client Component props」というエラーでビルドが止まります。コールバックを使うときは、1行目に `"use client";` を書いたファイル(client component)の中に `<PackingBingo>` を置いてください。
121
93
 
122
- カードは置いた場所の幅いっぱい(最大720 px)に広がり、狭い場所ではスマホ向けの配置に自動で切り替わります。持ち物を変えたいときは `items` の中身を書き換えるだけです。`items` を省くと、一般的な修学旅行の持ち物リストになります。
94
+ ### 3. 持ち物を変える
123
95
 
124
- Next.jsのServer Componentのページからでも、そのまま使えます。ただし、`onBingo` などの関数(コールバック)はServer Componentから渡せず、「Event handlers cannot be passed to Client Component props」というエラーでビルドが止まります。コールバックを使うときは、部品のファイルの1行目に `"use client";` を書いてください。
96
+ `items` に、持ち物を左上から横に1行ずつ並べて渡します。16個なら4×4、9個なら3×3、25個なら5×5になります([マスの数を変える](#マスの数を変える))。次のリストを書き換えて使ってください。
97
+
98
+ ```tsx
99
+ import { PackingBingo, type BingoItem } from "packing-bingo";
100
+
101
+ // 持ち物。16個なら、左上から横に4つずつ並びます。
102
+ // \u200B は「狭い画面ではここで改行してよい」という目印です(画面には出ません)。
103
+ const items: BingoItem[] = [
104
+ { emoji: "👛", label: "財布" },
105
+ { emoji: "📱", label: "スマホ" },
106
+ { emoji: "🔋", label: "モバイル\u200B充電器" },
107
+ { emoji: "📋", label: "健康調査票" },
108
+ { emoji: "🪪", label: "マイナカード\u200Bまたは保険証" },
109
+ { emoji: "📖", label: "しおり" },
110
+ { emoji: "😷", label: "マスク" },
111
+ { emoji: "🤧", label: "ハンカチ" },
112
+ { emoji: "💊", label: "常備薬" },
113
+ { emoji: "🛍️", label: "ビニール袋" },
114
+ { emoji: "🧤", label: "軍手(学校支給)" },
115
+ { emoji: "🔦", label: "ヘッドライト" },
116
+ { emoji: "👕", label: "ジャージ" },
117
+ { emoji: "☔", label: "雨具" },
118
+ { emoji: "🛁", label: "タオル" },
119
+ { emoji: "🧴", label: "日焼け止め" },
120
+ ];
121
+
122
+ // JSXの中の、表示したい場所に
123
+ <PackingBingo items={items} />
124
+ ```
125
125
 
126
126
  ### Reactを使っていないページに置く場合
127
127
 
@@ -129,24 +129,35 @@ HTMLの好きな場所に、次をそのまま貼り付けます。インスト
129
129
 
130
130
  ```html
131
131
  <div id="packing-bingo"></div>
132
- <script src="https://cdn.jsdelivr.net/npm/packing-bingo@0.2/dist/packing-bingo.global.js"></script>
132
+ <script src="https://cdn.jsdelivr.net/npm/packing-bingo@0.3/dist/packing-bingo.global.js"></script>
133
133
  <script>
134
134
  PackingBingo.createPackingBingo(document.getElementById("packing-bingo"), {
135
- tag: "沖縄修学旅行",
136
135
  items: [
137
- { emoji: "📖", label: "旅のしおり" }, { emoji: "🪪", label: "生徒手帳" },
138
- { emoji: "🏥", label: "保険証コピー" }, { emoji: "👛", label: "おこづかい" },
139
- { emoji: "👕", label: "着替え" }, { emoji: "🩱", label: "水着" },
140
- { emoji: "🧴", label: "日焼け止め" }, { emoji: "🧢", label: "帽子" },
141
- { emoji: "🕶️", label: "サングラス" }, { emoji: "🩴", label: "ビーチサンダル" },
142
- { emoji: "🪥", label: "洗面用具" }, { emoji: "🛁", label: "タオル" },
143
- { emoji: "💊", label: "常備薬・酔い止め" }, { emoji: "☂️", label: "折りたたみ傘" },
144
- { emoji: "🛍️", label: "ビニール袋" }, { emoji: "📱", label: "スマホ・充電器" },
136
+ { emoji: "👛", label: "財布" }, { emoji: "📱", label: "スマホ" },
137
+ { emoji: "🔋", label: "モバイル\u200B充電器" }, { emoji: "📋", label: "健康調査票" },
138
+ { emoji: "🪪", label: "マイナカード\u200Bまたは保険証" }, { emoji: "📖", label: "しおり" },
139
+ { emoji: "😷", label: "マスク" }, { emoji: "🤧", label: "ハンカチ" },
140
+ { emoji: "💊", label: "常備薬" }, { emoji: "🛍️", label: "ビニール袋" },
141
+ { emoji: "🧤", label: "軍手(学校支給)" }, { emoji: "🔦", label: "ヘッドライト" },
142
+ { emoji: "👕", label: "ジャージ" }, { emoji: "☔", label: "雨具" },
143
+ { emoji: "🛁", label: "タオル" }, { emoji: "🧴", label: "日焼け止め" },
145
144
  ],
146
145
  });
147
146
  </script>
148
147
  ```
149
148
 
149
+ ## デモ
150
+
151
+ - **デモサイト:** https://jin-hiroo.github.io/packing-bingo/
152
+ その場でタップして試せます。演出だけを自動で再生するカードと、組み込み用のコード(このREADMEのクイックスタートと同じもの)も載っています。
153
+ - **デモコード:** [`examples/`](examples/) に、そのまま動かせるプロジェクトがあります。フォルダごとコピーして、自分のサイトの土台にもできます。
154
+
155
+ | フォルダ | 内容 | 動かし方 |
156
+ | --- | --- | --- |
157
+ | [`examples/nextjs`](examples/nextjs) | Next.js(App Router)。クイックスタートそのままのページと、ビンゴの瞬間を受け取るページ | `npm install` → `npm run dev` |
158
+ | [`examples/vite-react`](examples/vite-react) | Vite + React。ふつうのReactアプリのページに置く例 | `npm install` → `npm run dev` |
159
+ | [`examples/html`](examples/html) | HTMLだけ。Reactもインストールも使わない例 | `index.html` をブラウザで開く |
160
+
150
161
  ## 使い方の例
151
162
 
152
163
  ### 持ち物と文言を変える
@@ -155,17 +166,43 @@ HTMLの好きな場所に、次をそのまま貼り付けます。インスト
155
166
  import { PackingBingo, en, type BingoItem } from "packing-bingo";
156
167
 
157
168
  const items: BingoItem[] = [
158
- { emoji: "🩱", label: "Swimsuit" },
159
- { emoji: "🧴", label: "Sunscreen" },
160
- // … ちょうど16個、左上から横に1行ずつ
169
+ { emoji: "👛", label: "Wallet" },
170
+ { emoji: "📱", label: "Phone" },
171
+ // … 4×4なら16個、左上から横に1行ずつ
161
172
  ];
162
173
 
163
- <PackingBingo items={items} messages={en} title="Okinawa Trip" tag="Class 2-B" />;
174
+ <PackingBingo items={items} messages={en} title="School Trip" tag="Class 2-B" />;
164
175
  ```
165
176
 
166
- - `items` はちょうど16個にしてください。それ以外の数だと `[packing-bingo] "items" must have exactly 16 entries` というエラーになります。
177
+ - `items` の数で、カードの大きさが決まります。16個なら4×4、9個なら3×3、25個なら5×5です。正方形にならない数だと、`[packing-bingo] "items" must have a square number of entries` で始まるエラーになります。くわしくは[マスの数を変える](#マスの数を変える)を見てください。
167
178
  - `messages` は日本語の既定値の上に重なるので、`messages={{ reset: "やり直す" }}` のように一部の文言だけを変えられます。`en` は英語の文言一式です。キーの一覧は[文言のキー](#文言のキー)にあります。
168
- - 狭いカードでは、ラベルは `・` の後ろで折り返します。`常備薬・酔い止め` は「常備薬・」と「酔い止め」の2行になります。
179
+ - 狭いカードでは、ラベルは `・` の後ろで折り返します。`常備薬・酔い止め` は「常備薬・」と「酔い止め」の2行になります。`・` を入れたくない長いラベルは、`\u200B`(画面には出ない改行位置の目印)を入れた場所で折り返します。`"モバイル\u200B充電器"` は「モバイル」と「充電器」の2行になります。スマホの幅では、1行に入るのは6文字ほどです。
180
+
181
+ ### マスの数を変える
182
+
183
+ `size` で1辺のマスの数を、`free` で真ん中のFREEマスを決めます。
184
+
185
+ ```tsx
186
+ // 3×3。items を渡さなければ、既定の持ち物の最初の9個を使います
187
+ <PackingBingo size={3} />
188
+
189
+ // 5×5で、真ん中がFREEマス。items は 5×5 − 1 = 24個
190
+ <PackingBingo size={5} free items={items} />
191
+ ```
192
+
193
+ <p>
194
+ <a href="docs/images/phone-5x5-free.png"><img src="docs/images/phone-5x5-free.png" alt="スマホの画面。5×5のカードで、真ん中が赤い丸のFREEマス。左上からのナナメの列がFREEマスを通ってそろい、線が引かれている。真ん中のヨコの列は、残りの「雨具」にリーチの札が付いている。上の欄は9/24、1ライン。" height="320"></a>
195
+ </p>
196
+
197
+ _5×5で真ん中がFREEのカード。FREEマスを通るナナメの列は、残りの4個でそろいます。_
198
+
199
+ - `size` は2〜10です。省略すると、`items` の数から決まります(9個なら3×3、16個なら4×4、25個なら5×5。`free` のときは8個で3×3、24個で5×5)。
200
+ - `items` は `size` × `size` 個、`free` のときはそれより1個少なくします。左上から横に1行ずつ並び、FREEマスは飛ばします。数が合わないと、必要な数を書いたエラーになります。
201
+ - `items` を渡さないときに使う既定の持ち物は16個です。3×3までは最初の何個かを使いますが、5×5以上では `items` が必要です。
202
+ - `free` は `size` が奇数のときだけ使えます。FREEマスは最初からスタンプ済みで、タップできず、持ち物の数にも入りません。FREEマスを通る列(真ん中のタテとヨコ、2本のナナメ)は、残りの `size` − 1 個でそろいます。マスの文字は `messages` の `free` で変えられます(既定は `FREE`)。
203
+ - `checked`・`defaultChecked`・`onChange` の配列は、持ち物の数(FREEマスを除く)と同じ長さです。
204
+ - 文字やすき間は、マスの大きさに合わせて変わります。スマホの幅で読みやすいのは5×5くらいまでです。それより大きいと、ラベルが細かく折り返してマスが縦に伸びます。
205
+ - 持ち物リストから決まる保存のキーは、実際に並ぶ持ち物から決まります。`size={3}` だけのカードと、ふつうの4×4のカードは、別々に保存されます。
169
206
 
170
207
  ### 保存のしかたを変える
171
208
 
@@ -213,13 +250,13 @@ export function PackingCard({ studentId }: { studentId: string }) {
213
250
  }
214
251
  ```
215
252
 
216
- 生徒のタップは通信より速いので、この例では保存を順番に送り、`studentId` が変わったあとに届いた読み込みは捨てています。保存のたびに16個すべてを送るので、最後の保存がいつも最新の状態になります。
253
+ 生徒のタップは通信より速いので、この例では保存を順番に送り、`studentId` が変わったあとに届いた読み込みは捨てています。保存のたびにすべての持ち物の状態を送るので、最後の保存がいつも最新の状態になります。
217
254
 
218
255
  controlledのカードの動き:
219
256
 
220
- - タップしただけでは、カードは変わりません。ユーザーが求めた16個の状態で `onChange` が呼ばれ、`checked` が変わるとカードがそれに合わせます。`checked` を変えなければ、タップは取り消されます。
257
+ - タップしただけでは、カードは変わりません。ユーザーが求めた状態(持ち物の数だけの配列)で `onChange` が呼ばれ、`checked` が変わるとカードがそれに合わせます。`checked` を変えなければ、タップは取り消されます。
221
258
  - `checked` がちょうど1個だけ変わったときは、タップと同じように演出します(スタンプ、音、ビンゴやフィナーレ)。サーバーからデータが届いたときのように一度に何個も変わったときは、演出なしで静かに反映し、同時にそろった列のボーナスも付けません。
222
- - リセットボタンを押すと、16個すべて `false` で `onChange` が呼ばれます。
259
+ - リセットボタンを押すと、すべて `false` の配列で `onChange` が呼ばれます。
223
260
  - `storageKey` は使われません。
224
261
 
225
262
  ### ビンゴの瞬間を受け取る
@@ -281,28 +318,30 @@ function PackingPage() {
281
318
 
282
319
  | prop | 型 | 既定値 | 内容 |
283
320
  | --- | --- | --- | --- |
284
- | `items` | `readonly BingoItem[]`(ちょうど16個) | `DEFAULT_ITEMS`(一般的な修学旅行の持ち物) | マスの中身。左上から横に1行ずつ:`{ emoji, label }` |
321
+ | `items` | `readonly BingoItem[]`(`size` × `size` 個。`free` のときは1個少なく) | `DEFAULT_ITEMS`(一般的な修学旅行の持ち物16個) | マスの中身。左上から横に1行ずつ、FREEマスは飛ばす:`{ emoji, label }` |
322
+ | `size` | `number`(2〜10) | `items` の数から決まる(既定の持ち物なら4) | 1辺のマスの数。[マスの数を変える](#マスの数を変える)を見てください |
323
+ | `free` | `boolean` | `false` | 真ん中をFREEマスにする。`size` が奇数のときだけ |
285
324
  | `messages` | `Partial<Messages>` | `ja` | 画面の文言。日本語の既定値の上に重なります。英語の `en` も入っています |
286
- | `title`・`tag`・`subtitle` | `string` | `messages` の値 | 見出し、その上の小さなラベル、その下の1行 |
325
+ | `title`・`tag`・`subtitle` | `string` | `messages` の値 | 見出し、その上の小さなラベル、その下の1行。`subtitle` は既定では空で、表示しません |
287
326
  | `storageKey` | `string \| null` | 持ち物から自動で決まる | 進み具合を保存するlocalStorageのキー。`null` で保存しません。controlledのときは使われません |
288
- | `defaultChecked` | `readonly boolean[]`(16個) | すべて `false` | uncontrolledのカードの最初の状態。保存された進み具合があれば、そちらが優先です |
289
- | `checked` | `readonly boolean[]`(16個) | なし | 指定するとcontrolledになります。[自分のサーバーに保存する](#自分のサーバーに保存する)を見てください |
327
+ | `defaultChecked` | `readonly boolean[]`(持ち物と同じ数) | すべて `false` | uncontrolledのカードの最初の状態。保存された進み具合があれば、そちらが優先です |
328
+ | `checked` | `readonly boolean[]`(持ち物と同じ数) | なし | 指定するとcontrolledになります。[自分のサーバーに保存する](#自分のサーバーに保存する)を見てください |
290
329
  | `onChange` | `(checked: boolean[]) => void` | なし | ユーザーがタップかリセットをした。controlledのときは、求められた状態 |
291
330
  | `onBingo` | `(event: BingoEvent) => void` | なし | タップで1列以上そろった |
292
331
  | `onPerfect` | `(event: PerfectEvent) => void` | なし | タップでカード全体がそろった |
293
332
  | `sound` | `boolean` | `true` | 音の最初の設定。ユーザーが🔊ボタンで選ぶまで使われます |
294
333
  | `fonts` | `boolean` | `true` | Dela Gothic OneとM PLUS Rounded 1cをGoogle Fontsから読み込む |
295
- | `demo` | `boolean` | `false` | デモ用のボタンを出します。保存された進み具合も `defaultChecked` もなければ、6マス埋まった状態から始めます。`storageKey` を指定しない限り保存しません |
334
+ | `demo` | `boolean` | `false` | デモ用のボタンを出します。保存された進み具合も `defaultChecked` もなければ、一番上の列と、左上からのナナメの途中までが埋まった状態(4×4なら6マス)から始めます。`storageKey` を指定しない限り保存しません |
296
335
  | `className`・`style` | | なし | 外側の `<div>` に付きます |
297
336
  | `ref` | `Ref<PackingBingoHandle>` | なし | [ref](#ref) を見てください |
298
337
 
299
- propsは中身で比べるので、配列やオブジェクトをその場で書いても構いません。`items`・`title`・`tag`・`subtitle`・`storageKey`・`sound`・`fonts`・`demo`、または `messages` の文字列が変わると、カードを作り直します。保存先のキーが変わらなければ、チェックはそのまま残ります。キーが変わったとき(`items` を変えて自動のキーが変わったときも含む)は、新しいキーに保存された進み具合を読み込みます。得点は[得点のルール](#得点のルール)のとおり計算し直します。`messages` の関数はpropsやstateを使っても構いません。`messages` が変わると、作り直さずに下の1行だけを描き直します。
338
+ propsは中身で比べるので、配列やオブジェクトをその場で書いても構いません。`items`・`size`・`free`・`title`・`tag`・`subtitle`・`storageKey`・`sound`・`fonts`・`demo`、または `messages` の文字列が変わると、カードを作り直します。保存先のキーが変わらなければ、チェックはそのまま残ります。キーが変わったとき(`items` を変えて自動のキーが変わったときも含む)は、新しいキーに保存された進み具合を読み込みます。得点は[得点のルール](#得点のルール)のとおり計算し直します。`messages` の関数はpropsやstateを使っても構いません。`messages` が変わると、作り直さずに下の1行だけを描き直します。
300
339
 
301
340
  ### イベント
302
341
 
303
342
  ```ts
304
343
  interface BingoEvent {
305
- newLines: number[]; // このタップでそろった列(LINESの番号)
344
+ newLines: number[]; // このタップでそろった列(bingoLines(size, free) の番号。4×4ならLINESと同じ)
306
345
  totalLines: number; // これまでにそろった列の数
307
346
  score: number; // このタップのあとの得点
308
347
  }
@@ -326,15 +365,17 @@ interface PackingBingoHandle {
326
365
  }
327
366
 
328
367
  interface PackingBingoState {
329
- checked: boolean[]; // 16個、左上から横に1行ずつ
330
- lines: number[]; // そろった列(LINESの番号)
368
+ checked: boolean[]; // 持ち物ごと、左上から横に1行ずつ(FREEマスは含まない)
369
+ lines: number[]; // そろった列(bingoLines(size, free) の番号)
331
370
  score: number;
332
371
  maxCombo: number;
333
- complete: boolean; // 16個すべてにチェックが付いている
372
+ complete: boolean; // すべての持ち物にチェックが付いている
373
+ size: number; // 1辺のマスの数
374
+ free: boolean; // 真ん中がFREEマスか
334
375
  }
335
376
  ```
336
377
 
337
- `LINES` もexportしています。0〜3がヨコの列、4〜7がタテの列、8が左上からのナナメ、9が右上からのナナメです。
378
+ `LINES` もexportしています。4×4のカードの列で、0〜3がヨコの列、4〜7がタテの列、8が左上からのナナメ、9が右上からのナナメです。ほかの大きさの列は `bingoLines(size, free)` で取れます。並び方は同じで、ヨコの列、タテの列、左上からのナナメ、右上からのナナメの順です。中身は `checked` と同じ持ち物の番号なので、`free` のときは真ん中を通る列だけ `size` − 1 個になります。
338
379
 
339
380
  ### 得点のルール
340
381
 
@@ -348,12 +389,14 @@ interface PackingBingoState {
348
389
 
349
390
  `messages` には、次のキーを好きなだけ指定できます。日本語と英語の一式は、[`src/messages.ts`](src/messages.ts) の `ja` と `en` です。
350
391
 
351
- - **文字列:** `title`、`tag`、`subtitle`、`packed`、`bingo`、`score`、`reset`、`soundOn`、`soundOff`、`stamp`、`reachBadge`、`reachPop`、`bannerSingle`、`bannerDouble`、`bannerTriple`、`statusDone`、`perfectTag`、`perfectStamp`、`perfectTitle`、`perfectBody`、`statScore`、`statCombo`、`statLines`、`close`、`demoLabel`、`demoBingo`、`demoPerfect`、`demoRestore`。`perfectStamp` と `perfectBody` では、`\n` で改行します。
392
+ - **文字列:** `title`、`tag`、`subtitle`、`packed`、`bingo`、`score`、`reset`、`soundOn`、`soundOff`、`stamp`、`free`、`reachBadge`、`reachPop`、`bannerSingle`、`bannerDouble`、`bannerTriple`、`statusDone`、`perfectTag`、`perfectStamp`、`perfectTitle`、`perfectBody`、`statScore`、`statCombo`、`statLines`、`close`、`demoLabel`、`demoBingo`、`demoPerfect`、`demoRestore`。`perfectStamp` と `perfectBody` では、`\n` で改行します。
352
393
  - **関数(どれも文字列を返します):** `combo(n)`、`comboPop(n)`、`bannerNth(totalLines)`、`bannerMulti(newLines)`、`statusStart(left)`、`statusLines(lines, left)`、`statusReach(labels)`、`perfectBonus(points)`。
394
+ - `perfectBody` は、文字列のほかに、持ち物の数を受け取る関数 `perfectBody(total)` にもできます。`ja` と `en` では関数で、「16こ ぜんぶそろった!」の数がカードに合わせて変わります。
395
+ - `subtitle`(見出しの下の1行)と `statusStart(left)`(1列もそろう前の下の1行)は、`ja` と `en` のどちらでも空なので、表示しません。出したいときは `subtitle="タップでスタンプ。タテ・ヨコ・ナナメでビンゴ!"` や `` messages={{ statusStart: (left) => `のこり ${left} こ` }} `` のように指定します。
353
396
 
354
397
  ### export
355
398
 
356
- - `packing-bingo`:`PackingBingo`、`createPackingBingo`、`LINES`、`DEFAULT_ITEMS`、`ja`、`en`。型は `PackingBingoProps`、`PackingBingoHandle`、`PackingBingoState`、`BingoItem`、`BingoEvent`、`PerfectEvent`、`Messages`、`PackingBingoOptions`、`PackingBingoInstance`。
399
+ - `packing-bingo`:`PackingBingo`、`createPackingBingo`、`LINES`、`bingoLines`、`DEFAULT_ITEMS`、`ja`、`en`。型は `PackingBingoProps`、`PackingBingoHandle`、`PackingBingoState`、`BingoItem`、`BingoEvent`、`PerfectEvent`、`Messages`、`PackingBingoOptions`、`PackingBingoInstance`。
357
400
  - `packing-bingo/core`:Reactのコンポーネントとその型(`PackingBingoProps`、`PackingBingoHandle`)を除いた残り。Reactを読み込みません。
358
401
 
359
402
  ## 組み込むときの注意
@@ -378,7 +421,7 @@ Dela Gothic OneとM PLUS Rounded 1cを使います。`fonts={false}` にする
378
421
 
379
422
  ### 保存されるデータ
380
423
 
381
- チェックは、16個の真偽値のJSON配列としてlocalStorageに保存されます。キーは `storageKey`、指定しなければ `packing-bingo:` で始まる、持ち物リストから決まるキーです。得点は保存しません。保存された進み具合は `defaultChecked` より優先されます。プライベートブラウズなどで保存できない環境では、保存せずにそのまま動きます。
424
+ チェックは、持ち物の数(4×4なら16個。FREEマスは含みません)だけの真偽値のJSON配列として、localStorageに保存されます。キーは `storageKey`、指定しなければ `packing-bingo:` で始まる、持ち物リストから決まるキーです。得点は保存しません。保存された進み具合は `defaultChecked` より優先されます。プライベートブラウズなどで保存できない環境では、保存せずにそのまま動きます。
382
425
 
383
426
  ### アクセシビリティ
384
427
 
@@ -407,7 +450,7 @@ CSSのコンテナクエリ(Chrome・Edge 105以上、Safari 16以上、Firefo
407
450
  import { createPackingBingo } from "packing-bingo/core";
408
451
 
409
452
  const card = createPackingBingo(document.getElementById("bingo")!, {
410
- tag: "沖縄修学旅行",
453
+ tag: "2年B組",
411
454
  onBingo: (e) => console.log(e),
412
455
  });
413
456
  // card.reset()、card.getState()、card.setChecked(next, { animate: true })、card.destroy()
@@ -427,7 +470,7 @@ cd packing-bingo
427
470
  open index.html # macOSの場合。ほかのOSでは、ファイルをブラウザで開いてください
428
471
  ```
429
472
 
430
- URLの末尾に `?demo` を付けると、途中まで埋まった状態から始まり、デモ用のボタンが出ます。「▶ ビンゴを見る」でビンゴを、「▶ コンボ → パーフェクト」でコンボからパーフェクトまでを再生します。GitHub Pagesで公開するときは、**Settings → Pages → Deploy from a branch → `main` / root** を選びます。
473
+ URLの末尾に `?demo` を付けると、途中まで埋まった状態から始まり、デモ用のボタンが出ます。「▶ ビンゴを見る」でビンゴを、「▶ コンボ → パーフェクト」でコンボからパーフェクトまでを再生します。デモサイトの [standalone.html](https://jin-hiroo.github.io/packing-bingo/standalone.html) でも同じページを公開しています。
431
474
 
432
475
  ページの中身を変えるときは、最後の `<script>` にある `PackingBingo.createPackingBingo(…)` のオプションを書き換えます。どのビルドでも使われる既定値を変えるときは、`src/items.ts` か `src/messages.ts` を編集して `npm run build` を実行します。
433
476
 
@@ -441,9 +484,11 @@ npm run typecheck
441
484
 
442
485
  `index.html` は `scripts/build-standalone.mjs` が `src/` から作るので、直接編集しないでください。
443
486
 
487
+ デモサイトは `site/index.html` です。`node scripts/build-site.mjs` を実行すると、`_site/` にデモサイトができます。そのとき、組み込み用のコードはこのREADMEのクイックスタートから差し込まれるので、READMEとデモサイトのコードはいつも同じになります。`main` に変更が入ると、GitHub Actions(`.github/workflows/pages.yml`)がビルドしてGitHub Pagesに公開し直します。
488
+
444
489
  ## English
445
490
 
446
- Packing Bingo is a React component that turns a school-trip packing checklist into a 4×4 bingo card, with combos, fanfares and fireworks. Progress is saved in each student's browser automatically. It has no runtime dependencies and is safe to render on the server (it ships with `"use client"`).
491
+ Packing Bingo is a React component that turns a school-trip packing checklist into a bingo card, with combos, fanfares and fireworks. Cards are 4×4 by default; `size` makes them anything from 2×2 to 10×10, and `free` puts a FREE square in the center of an odd-sized card. Progress is saved in each student's browser automatically. It has no runtime dependencies and is safe to render on the server (it ships with `"use client"`).
447
492
 
448
493
  ```sh
449
494
  npm install packing-bingo
@@ -455,7 +500,7 @@ import { PackingBingo, en } from "packing-bingo";
455
500
  <PackingBingo messages={en} />;
456
501
  ```
457
502
 
458
- The default UI text is Japanese; `en` is a complete English set. Pass exactly 16 `items` (`{ emoji, label }`) to use your own list. The sections above cover the props, events and integration notes; the code snippets and type names can be read as they are.
503
+ Try it on the [demo site](https://jin-hiroo.github.io/packing-bingo/); runnable example projects (Next.js, Vite + React, plain HTML) are in [`examples/`](examples/). The default UI text is Japanese; `en` is a complete English set. Pass your own `items` (`{ emoji, label }`, row by row): 16 for 4×4, size² in general, or size² − 1 with `free` (the FREE square is skipped, also in `checked`). Without `size`, the number of items sets it. `bingoLines(size, free)` gives the winning lines as item indices. The sections above cover the props, events and integration notes; the code snippets and type names can be read as they are.
459
504
 
460
505
  ## ライセンス
461
506