@ishibashi0112/spreadsheet-grid 0.13.0 → 0.14.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,468 +1,470 @@
1
- # @ishibashi0112/spreadsheet-grid
2
-
3
- [![npm version](https://img.shields.io/npm/v/@ishibashi0112/spreadsheet-grid.svg)](https://www.npmjs.com/package/@ishibashi0112/spreadsheet-grid)
4
- [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
5
-
6
- A high-performance, virtualized spreadsheet / data grid for **React 19**, written in TypeScript.
7
-
8
- 高性能な仮想化スプレッドシート/データグリッド(**React 19**・TypeScript 製)。
9
-
10
- **English** | [日本語](#日本語)
11
-
12
- ---
13
-
14
- ## Features
15
-
16
- - Scroll-space virtualization that handles up to ~1,000,000 rows.
17
- - Three-pane pinned columns (left / center / right) via sticky positioning.
18
- - Sorting, per-column filters (`text` / `number` / `date` / `select` / `set` / `custom`), and a global filter.
19
- - In-cell editing and clipboard copy / paste, with range selection and keyboard navigation.
20
- - Optional auto-height rows for wrapped, variable-height content.
21
- - Auto-fit column widths to content on data load — `autoSizeColumns="onMount"` (once, on first data) or `"onDataChange"` (every time `rows` changes, e.g. after a form submit). Same engine as the column menu's "Autosize All Columns"; opt individual columns out with `suppressAutoSize`.
22
- - Full-text tooltip on truncated cells — `showCellOverflowTooltip` shows the full value on hover, but only when the cell is actually clipped (…).
23
- - Japanese-aware line wrapping — per-column `wordBreak` / `lineBreak`, including `wordBreak: 'auto-phrase'` for phrase-based breaks on Chromium (BudouX). Cross-browser BudouX recipe in the API reference.
24
- - External height control via `height` / `maxHeight` (e.g. `height="100%"` to follow the parent's height).
25
- - Both **client-side** (`rows`) and **server-side** (`dataSource`, SSRM) row models.
26
- - Themeable with CSS custom properties (`--ssg-*`, defined at zero specificity so your overrides always win). Base styles are plain unlayered CSS with single-class specificity, so they survive CSS resets such as Tailwind Preflight; a cascade-layers variant (`style.layer.css`) is also shipped. `className` / `classNames` slots are provided.
27
- - Styled tooltips out of the box — action hints and truncated-text previews use a custom dark-chip tooltip (no browser-default `title` look). Add `data-ssg-tooltip="text"` to your own elements (custom cells, headers) to get the same tooltip; colors are themeable via `--ssg-tooltip-*` tokens.
28
- - Built-in dark theme — `theme="light" | "dark" | "auto"` switches the grid, every popover / panel / menu, the drag ghost and tooltips through a single token preset. `"auto"` follows `prefers-color-scheme`; with class-based dark frameworks (Mantine / HeroUI / Tailwind) pass your resolved color scheme instead.
29
- - Toggle the top / bottom bars and their parts via props — whole bars (`showTopBar` / `showBottomBar`), the default top bar's summary chips and global-filter input, and the Rows/Columns counts in each bar.
30
- - Filter management panel — review every active column filter in one place (jump to the column & edit, clear one / all, add new), opened from the column menu, the default top bar's clickable Filters chip, or `openFilterManager()` on the imperative handle. An optional filter chip bar (`showFilterChipBar`) keeps active filters visible right below the top bar.
31
- - Built-in CSV export (`downloadCsv` / `exportCsv`), plus a library-agnostic `getExportData()` for Excel / XLSX / ODS — feed the shaped data (filter/sort/visible-order aware) to your own writer such as [hucre](https://github.com/productdevbook/hucre), ExcelJS, or SheetJS. No spreadsheet library is bundled; multi-sheet is composed on your side. See [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md).
32
- - Export scopes: `'view'` (default — every filtered/sorted view row, scroll-independent), `'raw'` (every source row, ignoring filter & sort), `'rendered'` (only the rows currently rendered by virtualization — scroll-dependent), `'selection'`. Legacy `'all'` / `'visible'` keep working as deprecated aliases of `'view'` / `'rendered'`.
33
- - TypeScript-first, fully controlled API.
34
-
35
- ## Installation
36
-
37
- ```sh
38
- npm install @ishibashi0112/spreadsheet-grid
39
- # pnpm add @ishibashi0112/spreadsheet-grid
40
- # yarn add @ishibashi0112/spreadsheet-grid
41
- ```
42
-
43
- Requires **react** and **react-dom** `>= 19` as peer dependencies (install them in your app if you have not already). `@tanstack/react-virtual` is a regular dependency and is installed automatically.
44
-
45
- ## Styles
46
-
47
- The grid ships its CSS as a separate file. Import it once (for example, in your app entry):
48
-
49
- ```ts
50
- import '@ishibashi0112/spreadsheet-grid/style.css'
51
- ```
52
-
53
- The base styles are plain (unlayered) CSS scoped to `.ssg-*` classes, and all design tokens are defined at zero specificity (`:where(.ssg-root)`), so your token overrides always win regardless of import order.
54
-
55
- ### Using with Tailwind CSS / HeroUI / Mantine
56
-
57
- - **Tailwind CSS v3 (and HeroUI on v3)** — works out of the box. Preflight cannot break the grid: its element/universal resets lose to the grid's class selectors by specificity.
58
- - **Tailwind CSS v4 (and HeroUI on v4)** — works out of the box. If you additionally want Tailwind utilities to override grid defaults without the `!` modifier, put the grid CSS into a cascade layer below `utilities`:
59
-
60
- ```css
61
- @import 'tailwindcss';
62
- @import '@ishibashi0112/spreadsheet-grid/style.css' layer(components);
63
- ```
64
-
65
- Alternatively, use the pre-layered variant `style.layer.css` (everything wrapped in `@layer ssg-base`) and declare the layer order yourself:
66
-
67
- ```css
68
- @layer theme, base, ssg-base, components, utilities;
69
- @import 'tailwindcss';
70
- @import '@ishibashi0112/spreadsheet-grid/style.layer.css';
71
- ```
72
-
73
- - **Mantine** — works out of the box (no class-name or reset conflicts; grid popovers use `z-index: 1000`, above Mantine's default modal z-index).
74
-
75
- To override a grid default reliably in plain CSS, chain your class with the grid's base class so it wins by specificity, independent of import order:
76
-
77
- ```css
78
- .ssg-body-cell.my-warn-cell {
79
- background-color: #fff7ed;
80
- }
81
- ```
82
-
83
- ## Quick start
84
-
85
- ```tsx
86
- import { useState } from 'react'
87
- import { SpreadsheetGrid, type GridColumn } from '@ishibashi0112/spreadsheet-grid'
88
- import '@ishibashi0112/spreadsheet-grid/style.css'
89
-
90
- type Row = { id: number; name: string; qty: number }
91
-
92
- const columns: GridColumn<Row>[] = [
93
- { key: 'name', title: 'Name', width: 200, editable: true, filterType: 'text' },
94
- { key: 'qty', title: 'Qty', width: 120, editable: true, filterType: 'number' },
95
- ]
96
-
97
- export function Example() {
98
- const [rows, setRows] = useState<Row[]>([
99
- { id: 1, name: 'Apple', qty: 3 },
100
- { id: 2, name: 'Banana', qty: 5 },
101
- ])
102
-
103
- return (
104
- <SpreadsheetGrid
105
- rows={rows}
106
- columns={columns}
107
- onRowsChange={setRows}
108
- rowKeyGetter={(row) => row.id}
109
- />
110
- )
111
- }
112
- ```
113
-
114
- `rows` and `onRowsChange` make the grid a controlled component. A column needs at least `key` and `width`.
115
-
116
- ## Sizing
117
-
118
- By default the grid caps its height at `480px` (`max-height`) and scrolls when the content is taller. Pass `height` to take explicit control — use `height="100%"` to follow the parent's height, or a pixel value:
119
-
120
- ```tsx
121
- <div style={{ height: 600, minHeight: 0 }}>
122
- <SpreadsheetGrid rows={rows} columns={columns} height="100%" />
123
- </div>
124
- ```
125
-
126
- For `height="100%"` to work, the parent must have a resolved height (its ancestors are sized, and a flex child needs `min-height: 0`). This is standard CSS the library can't resolve for you. `maxHeight` sets an upper bound and can be combined with `height` (explicit height, capped at `maxHeight`).
127
-
128
- ### Auto-height rows
129
-
130
- Variable row height needs **two switches, both required**: the grid prop `autoHeight` (the master switch, default `false`) **and** at least one column with `autoHeight: true` (that column wraps and drives the row height). A cell grows only when `grid autoHeight && column.autoHeight` are both true. Auto-height is active only up to **50,000 rows**; beyond that it falls back to uniform `rowHeight`. `estimateRowHeight` is the placeholder for off-screen (not-yet-measured) rows — not a cap. See [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md) for details.
131
-
132
- ```tsx
133
- <SpreadsheetGrid
134
- rows={rows}
135
- columns={[{ key: 'note', title: 'Note', width: 320, autoHeight: true }]}
136
- autoHeight // master switch — without it, the column's autoHeight is ignored
137
- />
138
- ```
139
-
140
- ### Auto-sizing columns
141
-
142
- Set `autoSizeColumns` to fit column widths to their content when data arrives — no imperative calls or effects needed on your side:
143
-
144
- ```tsx
145
- // Refit every time a new result set replaces `rows` (e.g. after a form submit).
146
- <SpreadsheetGrid rows={rows} columns={columns} autoSizeColumns="onDataChange" />
147
- ```
148
-
149
- `'onMount'` fits once on first data; `'onDataChange'` refits whenever the `rows` reference changes; `false` (default) does nothing. It reuses the same measurement as the column menu's "Autosize All Columns", so per-column opt-outs apply: columns with `suppressAutoSize: true` (and `autoHeight: true` columns) keep their `width`. The trigger only reacts to `rows` — filtering, sorting and column reordering do **not** refit — and it writes to internal widths without calling `onColumnsChange`, so it coexists with controlled `columns`. Server-side (`dataSource`) is not supported. See [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md) for details.
150
-
151
- ### Density
152
-
153
- Set `density` to switch the overall sizing with one prop — `'compact' | 'standard' | 'comfortable'` (default `'standard'`, identical to previous versions):
154
-
155
- ```tsx
156
- <SpreadsheetGrid rows={rows} columns={columns} density="compact" />
157
- ```
158
-
159
- The preset drives the default `rowHeight` / `headerHeight` (compact: 28/32, standard: 36/40, comfortable: 44/48 — explicit props always win) and switches sizing tokens (cell horizontal padding, bar padding, icon-button size, relative cell font scale) via a root modifier class. Individual tokens (e.g. `--ssg-cell-pad-x`) can still be overridden for fine-tuning. Popovers/menus are not affected.
160
-
161
- ## Server-side mode (SSRM)
162
-
163
- Pass a `dataSource` instead of `rows` to switch to server-side mode. The grid keeps the full scroll height for the total row count and fetches only the blocks near the viewport:
164
-
165
- ```tsx
166
- <SpreadsheetGrid
167
- columns={columns}
168
- dataSource={{
169
- async getRows({ startIndex, endIndex, query, signal }) {
170
- // Apply `query` (filters / sort) on the server and return only [startIndex, endIndex).
171
- const { rows, totalRowCount } = await fetchPage({ startIndex, endIndex, query, signal })
172
- return { rows, totalRowCount }
173
- },
174
- }}
175
- />
176
- ```
177
-
178
- Sorting, column filters, and the global filter stay enabled and are forwarded to the server through `query`. See the [API reference](./src/components/spreadsheet-grid/API_REFERENCE.md) for the full `getRows` contract, the filter wire format, and `serverSideRefreshToken`.
179
-
180
- ## Styling & theming
181
-
182
- - Override the CSS variables on `.ssg-root` (or scope them via the `className` prop):
183
-
184
- ```css
185
- .ssg-root {
186
- --ssg-accent: #16a34a;
187
- --ssg-radius: 4px;
188
- }
189
- ```
190
-
191
- - Use the `classNames` prop for per-part class slots, `cellClassName` per column, and `getRowClassName` per row. Token overrides always apply (tokens are defined at zero specificity). For property overrides, chain with the base class (e.g. `.ssg-body-cell.my-class`) to win regardless of import order — see the Styles section above.
192
-
193
- ### Dark theme
194
-
195
- Pass `theme` to switch the whole surface — the grid itself, every popover / panel / menu (they are portalled to `document.body` and carry the theme class themselves), the column drag ghost and tooltips:
196
-
197
- ```tsx
198
- <SpreadsheetGrid theme="dark" columns={columns} rows={rows} />
199
- ```
200
-
201
- - `"light"` (default) / `"dark"` — explicit. `"auto"` follows the OS / browser `prefers-color-scheme` and updates live.
202
- - `color-scheme` is set accordingly, so native scrollbars and `<select>` controls follow the theme too.
203
- - The dark preset only redefines color tokens (`.ssg-theme-dark`); sizing tokens (radius, paddings) are theme-independent.
204
-
205
- **With Mantine / HeroUI / Tailwind (class-based dark):** the page's actual theme may not match `prefers-color-scheme`, so pass your resolved color scheme instead of `"auto"`:
206
-
207
- ```tsx
208
- // Mantine
209
- import { useComputedColorScheme } from '@mantine/core';
210
- const colorScheme = useComputedColorScheme('light'); // 'light' | 'dark'
211
- <SpreadsheetGrid theme={colorScheme} ... />
212
-
213
- // HeroUI / Tailwind (next-themes)
214
- import { useTheme } from 'next-themes';
215
- const { resolvedTheme } = useTheme();
216
- <SpreadsheetGrid theme={resolvedTheme === 'dark' ? 'dark' : 'light'} ... />
217
- ```
218
-
219
- **Customizing dark colors:** override tokens under `.ssg-theme-dark` — the class is present on the grid root and on every portal root, so one rule covers all surfaces:
220
-
221
- ```css
222
- .ssg-theme-dark {
223
- --ssg-cell-bg: #0d0d0f;
224
- --ssg-panel-bg: #1b1c20;
225
- }
226
- ```
227
-
228
- Note: a plain `.ssg-root { --ssg-* }` override wins over **both** themes (theme presets are defined at zero specificity). To target light only, scope it with `.ssg-root:not(.ssg-theme-dark)`.
229
-
230
- ## API reference
231
-
232
- The full prop and type reference lives in [`src/components/spreadsheet-grid/API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md).
233
-
234
- ## License
235
-
236
- [MIT](./LICENSE) © 2026 Yuki Sakakibara
237
-
238
- ---
239
-
240
- ## 日本語
241
-
242
- [English](#ishibashi0112spreadsheet-grid)
243
-
244
- **React 19** 製の高性能な仮想化スプレッドシート/データグリッドです。TypeScript で書かれています。
245
-
246
- ### 特徴
247
-
248
- - スクロール空間の仮想化により最大 100 万行規模に対応。
249
- - `position: sticky` による 3 ペイン固定列(左 / 中央 / 右)。
250
- - ソート、列ごとのフィルター(`text` / `number` / `date` / `select` / `set` / `custom`)、グローバルフィルター。
251
- - セル内編集とクリップボードのコピー/貼り付け、範囲選択、キーボード操作。
252
- - 折り返し・可変行高に対応する auto-height 行(任意)。
253
- - データ投入時に列幅を内容へ自動フィット — `autoSizeColumns="onMount"`(初回にデータが載った一度きり)/ `"onDataChange"`(`rows` が変わるたび。フォーム送信結果の差し替え等)。列メニュー「すべての列の幅を自動調整」と同一エンジンで、列個別の除外は `suppressAutoSize`。
254
- - 省略(…)セルの全文ツールチップ — `showCellOverflowTooltip` でホバー時に全文表示(実際にクリップされているセルのみ)。
255
- - 日本語対応の折り返し — 列ごとの `wordBreak` / `lineBreak`。`wordBreak: 'auto-phrase'` で Chromium(Chrome / Edge)の文節折り返し(BudouX)。クロスブラウザの BudouX レシピは API リファレンス参照。
256
- - `height` / `maxHeight` によるスクロールコンテナ高さの外部制御(`height="100%"` で親要素の高さに追従)。
257
- - **クライアントサイド**(`rows`)と**サーバーサイド**(`dataSource`、SSRM)の両行モデル。
258
- - CSS カスタムプロパティ(`--ssg-*`。特異度 0 で定義され、利用側の上書きが常に勝ちます)によるテーマ設定。基底スタイルは未レイヤーの単一クラス特異度で、Tailwind Preflight などの CSS リセットに壊されません。カスケードレイヤー版(`style.layer.css`)も同梱。`className` / `classNames` スロットも用意。
259
- - スタイル付きツールチップを標準装備 — 操作ヒントや切り詰めテキストの全文表示は、ブラウザ標準の `title` ではなくダークチップのカスタムツールチップで表示。利用側の要素(カスタムセルやヘッダー)にも `data-ssg-tooltip="文言"` を付けるだけで同じ見た目になります。配色は `--ssg-tooltip-*` トークンで調整可。
260
- - ダークテーマを標準装備 — `theme="light" | "dark" | "auto"` で、グリッド本体・全ポップオーバー / パネル / メニュー・ドラッグゴースト・ツールチップをトークンプリセット 1 つで一括切替。`"auto"` は `prefers-color-scheme` に追従(Mantine / HeroUI / Tailwind のクラスベース dark 運用では、解決済みのカラースキームを渡す使い方を推奨)。
261
- - トップ / ボトムバーとその構成要素(バー全体〔`showTopBar` / `showBottomBar`〕、既定トップバーの summary chips・グローバルフィルター入力、各バーの Rows/Columns 件数)を props で表示制御。
262
- - フィルター管理パネル — 適用中の列フィルターを 1 箇所で確認・操作(該当列へジャンプして編集 / 個別・全クリア / 追加)。列メニュー、既定トップバーの Filters chip クリック、ハンドルの `openFilterManager()` から開けます。トップバー直下に常時表示するフィルターチップバー(`showFilterChipBar`)もオプションで利用可。
263
- - CSV エクスポート(`downloadCsv` / `exportCsv`)を内蔵。Excel / XLSX / ODS はライブラリ非依存の `getExportData()` で、整形済みデータ(フィルター/ソート/可視列順を反映)を [hucre](https://github.com/productdevbook/hucre) / ExcelJS / SheetJS など任意の writer へ流す方式。xlsx ライブラリは同梱せず、マルチシートは利用側で合成。詳細は [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md)。
264
- - エクスポート scope: `'view'`(既定=フィルター/ソート後の全ビュー行。スクロール位置に非依存)/ `'raw'`(フィルター/ソート無視の全ソース行)/ `'rendered'`(描画中の行のみ=スクロール位置に依存)/ `'selection'`(選択範囲)。旧 `'all'` / `'visible'` は `'view'` / `'rendered'` の deprecated エイリアスとして従来どおり動作。
265
- - TypeScript ファースト、完全 controlled な API。
266
-
267
- ### インストール
268
-
269
- ```sh
270
- npm install @ishibashi0112/spreadsheet-grid
271
- # pnpm add @ishibashi0112/spreadsheet-grid
272
- # yarn add @ishibashi0112/spreadsheet-grid
273
- ```
274
-
275
- peer 依存として **react** / **react-dom** `>= 19` が必要です(未導入なら利用側で入れてください)。`@tanstack/react-virtual` は通常依存として自動的に入ります。
276
-
277
- ### スタイル
278
-
279
- CSS は別ファイルとして同梱されます。アプリのエントリ等で 1 度だけ import してください:
280
-
281
- ```ts
282
- import '@ishibashi0112/spreadsheet-grid/style.css'
283
- ```
284
-
285
- 基底スタイルは `.ssg-*` クラスにスコープした未レイヤーの素の CSS で、デザイントークンはすべて特異度 0(`:where(.ssg-root)`)で定義されています。トークンの上書きは読み込み順に依らず必ず勝ちます。
286
-
287
- #### Tailwind CSS / HeroUI / Mantine との共存
288
-
289
- - **Tailwind CSS v3(HeroUI の v3 世代)** — そのままで動作します。preflight(要素 / `*` セレクタのリセット)は本グリッドのクラスセレクタに特異度で負けるため、グリッドを壊せません。
290
- - **Tailwind CSS v4(HeroUI の v4 世代)** — そのままで動作します。さらに Tailwind ユーティリティで `!` 修飾子なしにグリッド既定を上書きしたい場合は、グリッド CSS を `utilities` より下のレイヤーへ入れてください:
291
-
292
- ```css
293
- @import 'tailwindcss';
294
- @import '@ishibashi0112/spreadsheet-grid/style.css' layer(components);
295
- ```
296
-
297
- もしくは全体を `@layer ssg-base` に包んだ `style.layer.css` を使い、レイヤー順を自分で宣言します:
298
-
299
- ```css
300
- @layer theme, base, ssg-base, components, utilities;
301
- @import 'tailwindcss';
302
- @import '@ishibashi0112/spreadsheet-grid/style.layer.css';
303
- ```
304
-
305
- - **Mantine** — そのままで動作します(クラス名・リセットの衝突なし。グリッドの popover は `z-index: 1000` で Mantine の既定モーダルより前面)。
306
-
307
- 素の CSS でグリッド既定を確実に上書きするには、基底クラスと連結して特異度で勝たせてください(読み込み順に依存しません):
308
-
309
- ```css
310
- .ssg-body-cell.my-warn-cell {
311
- background-color: #fff7ed;
312
- }
313
- ```
314
-
315
- ### クイックスタート
316
-
317
- ```tsx
318
- import { useState } from 'react'
319
- import { SpreadsheetGrid, type GridColumn } from '@ishibashi0112/spreadsheet-grid'
320
- import '@ishibashi0112/spreadsheet-grid/style.css'
321
-
322
- type Row = { id: number; name: string; qty: number }
323
-
324
- const columns: GridColumn<Row>[] = [
325
- { key: 'name', title: '名前', width: 200, editable: true, filterType: 'text' },
326
- { key: 'qty', title: '数量', width: 120, editable: true, filterType: 'number' },
327
- ]
328
-
329
- export function Example() {
330
- const [rows, setRows] = useState<Row[]>([
331
- { id: 1, name: 'りんご', qty: 3 },
332
- { id: 2, name: 'バナナ', qty: 5 },
333
- ])
334
-
335
- return (
336
- <SpreadsheetGrid
337
- rows={rows}
338
- columns={columns}
339
- onRowsChange={setRows}
340
- rowKeyGetter={(row) => row.id}
341
- />
342
- )
343
- }
344
- ```
345
-
346
- `rows` と `onRowsChange` でグリッドは controlled になります。列には最低限 `key` と `width` が必要です。
347
-
348
- ### サイズ(高さ)
349
-
350
- 既定ではグリッドの高さは `480px`(`max-height`)で頭打ちになり、中身がそれより高いとスクロールします。`height` を渡すと高さを明示制御できます。`height="100%"` で親要素の高さに追従、`number` で px 指定です:
351
-
352
- ```tsx
353
- <div style={{ height: 600, minHeight: 0 }}>
354
- <SpreadsheetGrid rows={rows} columns={columns} height="100%" />
355
- </div>
356
- ```
357
-
358
- `height="100%"` を効かせるには、**親要素が確定高さを持つ**必要があります(祖先まで高さが確定している/flex 子なら `min-height: 0` が必要)。これは CSS の一般則のため本ライブラリ側では解決できません。`maxHeight` は高さの上限で、`height` と併用できます(明示高さ+上限)。
359
-
360
- #### 可変行高(auto-height)
361
-
362
- 行高を可変にするには**2つのスイッチが両方必要**です。グリッド props の `autoHeight`(大本のスイッチ・既定 `false`)と、**少なくとも1列に `column.autoHeight: true`**(その列が折り返して行高を駆動)。セルが可変になるのは「グリッド `autoHeight` && 列 `autoHeight`」が両方 true のときだけです。有効なのは **50,000 行以内**で、超えると uniform 行高(`rowHeight`)へフォールバックします。`estimateRowHeight` は画面外(未測定)行の推定値で、上限ではありません。詳細は [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md)。
363
-
364
- ```tsx
365
- <SpreadsheetGrid
366
- rows={rows}
367
- columns={[{ key: 'note', title: '備考', width: 320, autoHeight: true }]}
368
- autoHeight // 大本のスイッチ(これが無いと列側 autoHeight は無視される)
369
- />
370
- ```
371
-
372
- #### 列幅の自動調整(autoSizeColumns)
373
-
374
- `autoSizeColumns` を渡すと、データ投入時に列幅を内容へ自動フィットします(利用側でトークンや effect は不要):
375
-
376
- ```tsx
377
- // フォーム送信結果などで rows を丸ごと差し替えるたびに合わせ直す。
378
- <SpreadsheetGrid rows={rows} columns={columns} autoSizeColumns="onDataChange" />
379
- ```
380
-
381
- `'onMount'` は初回にデータが載った一度きり、`'onDataChange'` は `rows`(参照)が変わるたび、`false`(既定)は無効です。計測は列メニュー「すべての列の幅を自動調整」と同一エンジンのため、列個別の除外がそのまま効きます — `suppressAutoSize: true` の列(および `autoHeight: true` の列)は `width` を維持します。発火 signal は `rows` のみで、フィルター / ソート / 列並べ替えでは**再フィットしません**。フィット幅は内部の列幅 state に反映され `onColumnsChange` を呼ばないため、controlled な `columns` とも競合しません。serverSide(`dataSource`)では無効です。詳細は [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md)。
382
-
383
- #### 密度(density)
384
-
385
- `density` プロップ 1 つで全体のサイズ感を切り替えられます — `'compact' | 'standard' | 'comfortable'`(既定 `'standard'` = 従来と同値):
386
-
387
- ```tsx
388
- <SpreadsheetGrid rows={rows} columns={columns} density="compact" />
389
- ```
390
-
391
- プリセットは `rowHeight` / `headerHeight` の既定値(compact: 28/32・standard: 36/40・comfortable: 44/48。明示 prop が常に優先)と、寸法トークン(セル横 padding・バー padding・アイコンボタン寸法・セル文字の相対拡縮)を root 修飾子経由で一括切替します。個別の微調整はトークン(例: `--ssg-cell-pad-x`)の上書きで可能です。popover / メニューは対象外です。
392
-
393
- ### サーバーサイドモード(SSRM)
394
-
395
- `rows` の代わりに `dataSource` を渡すとサーバーサイドモードになります。総行数ぶんのスクロール高さを保ったまま、可視窓に近いブロックだけを取得します:
396
-
397
- ```tsx
398
- <SpreadsheetGrid
399
- columns={columns}
400
- dataSource={{
401
- async getRows({ startIndex, endIndex, query, signal }) {
402
- // query(フィルター/ソート)をサーバで適用し、[startIndex, endIndex) のみ返す。
403
- const { rows, totalRowCount } = await fetchPage({ startIndex, endIndex, query, signal })
404
- return { rows, totalRowCount }
405
- },
406
- }}
407
- />
408
- ```
409
-
410
- ソート・列フィルター・グローバルフィルターは有効なまま `query` 経由でサーバへ送られます。`getRows` の契約、フィルターの wire format、`serverSideRefreshToken` の詳細は [API リファレンス](./src/components/spreadsheet-grid/API_REFERENCE.md) を参照してください。
411
-
412
- ### スタイリング / テーマ
413
-
414
- - `.ssg-root` 上で CSS 変数を上書きします(`className` prop でスコープも可能):
415
-
416
- ```css
417
- .ssg-root {
418
- --ssg-accent: #16a34a;
419
- --ssg-radius: 4px;
420
- }
421
- ```
422
-
423
- - パーツ別の class は `classNames` prop、列単位は `cellClassName`、行単位は `getRowClassName` で付与できます。トークン上書きは常に効きます(特異度 0 で定義)。プロパティ上書きは基底クラスとの連結(例: `.ssg-body-cell.my-class`)で読み込み順に依らず確実になります — 上記「スタイル」参照。
424
-
425
- #### ダークテーマ
426
-
427
- `theme` を渡すだけで全サーフェス — グリッド本体・全ポップオーバー / パネル / メニュー(`document.body` 直下のポータルですが、自身がテーマクラスを保持します)・列ドラッグゴースト・ツールチップ — が一括で切り替わります:
428
-
429
- ```tsx
430
- <SpreadsheetGrid theme="dark" columns={columns} rows={rows} />
431
- ```
432
-
433
- - `"light"`(既定)/ `"dark"` は明示指定。`"auto"` は OS / ブラウザの `prefers-color-scheme` に追従し、設定変更にもライブで反応します。
434
- - `color-scheme` も併せて切り替わるため、ネイティブのスクロールバーや `<select>` もテーマに揃います。
435
- - ダークプリセットが上書きするのは色トークンのみ(`.ssg-theme-dark`)。寸法トークン(radius / padding 等)はテーマ非依存です。
436
-
437
- **Mantine / HeroUI / Tailwind(クラスベース dark)との連動:** ページの実テーマと `prefers-color-scheme` は一致しないことがあるため、`"auto"` ではなく利用側カラースキームの解決値を渡してください:
438
-
439
- ```tsx
440
- // Mantine
441
- import { useComputedColorScheme } from '@mantine/core';
442
- const colorScheme = useComputedColorScheme('light'); // 'light' | 'dark'
443
- <SpreadsheetGrid theme={colorScheme} ... />
444
-
445
- // HeroUI / Tailwind(next-themes)
446
- import { useTheme } from 'next-themes';
447
- const { resolvedTheme } = useTheme();
448
- <SpreadsheetGrid theme={resolvedTheme === 'dark' ? 'dark' : 'light'} ... />
449
- ```
450
-
451
- **ダーク時の色調整:** `.ssg-theme-dark` 配下でトークンを上書きします。このクラスはグリッド root と全ポータル root に付与されるため、1 ルールで全サーフェスに効きます:
452
-
453
- ```css
454
- .ssg-theme-dark {
455
- --ssg-cell-bg: #0d0d0f;
456
- --ssg-panel-bg: #1b1c20;
457
- }
458
- ```
459
-
460
- 注意: 素の `.ssg-root { --ssg-* }` 上書きは**両テーマ**に勝ちます(テーマプリセットは特異度 0 で定義)。ライトのみを対象にしたい場合は `.ssg-root:not(.ssg-theme-dark)` でスコープしてください。
461
-
462
- ### API リファレンス
463
-
464
- prop と型の完全なリファレンスは [`src/components/spreadsheet-grid/API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md) にあります。
465
-
466
- ### ライセンス
467
-
1
+ # @ishibashi0112/spreadsheet-grid
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@ishibashi0112/spreadsheet-grid.svg)](https://www.npmjs.com/package/@ishibashi0112/spreadsheet-grid)
4
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
5
+
6
+ A high-performance, virtualized spreadsheet / data grid for **React 19**, written in TypeScript.
7
+
8
+ 高性能な仮想化スプレッドシート/データグリッド(**React 19**・TypeScript 製)。
9
+
10
+ **English** | [日本語](#日本語)
11
+
12
+ ---
13
+
14
+ ## Features
15
+
16
+ - Scroll-space virtualization that handles up to ~1,000,000 rows.
17
+ - Three-pane pinned columns (left / center / right) via sticky positioning.
18
+ - Sorting, per-column filters (`text` / `number` / `date` / `select` / `set` / `custom`), and a global filter.
19
+ - In-cell editing and clipboard copy / paste, with range selection, keyboard navigation, and Delete / Backspace to clear selected cells. Cell editing is IME-aware (composing Enter never commits the cell).
20
+ - Undo / redo for grid edits (cell edits, paste, clear) — `Ctrl/Cmd+Z`, `Ctrl/Cmd+Shift+Z` / `Ctrl/Cmd+Y`, plus `undo()` / `redo()` / `canUndo()` / `canRedo()` on the imperative handle and an `onUndoRedoStateChange` callback for toolbars. Restores the edited cell's active cell & selection and scrolls it back into view. History is snapshot-based with structural sharing, capped by `undoHistoryLimit` (default 100), and clears automatically when `rows` is replaced externally (client-side row model only).
21
+ - Optional auto-height rows for wrapped, variable-height content.
22
+ - Auto-fit column widths to content on data load — `autoSizeColumns="onMount"` (once, on first data) or `"onDataChange"` (every time `rows` changes, e.g. after a form submit). Same engine as the column menu's "Autosize All Columns"; opt individual columns out with `suppressAutoSize`.
23
+ - Full-text tooltip on truncated cells — `showCellOverflowTooltip` shows the full value on hover, but only when the cell is actually clipped (…).
24
+ - Japanese-aware line wrapping — per-column `wordBreak` / `lineBreak`, including `wordBreak: 'auto-phrase'` for phrase-based breaks on Chromium (BudouX). Cross-browser BudouX recipe in the API reference.
25
+ - External height control via `height` / `maxHeight` (e.g. `height="100%"` to follow the parent's height).
26
+ - Both **client-side** (`rows`) and **server-side** (`dataSource`, SSRM) row models.
27
+ - Themeable with CSS custom properties (`--ssg-*`, defined at zero specificity so your overrides always win). Base styles are plain unlayered CSS with single-class specificity, so they survive CSS resets such as Tailwind Preflight; a cascade-layers variant (`style.layer.css`) is also shipped. `className` / `classNames` slots are provided.
28
+ - Styled tooltips out of the box — action hints and truncated-text previews use a custom dark-chip tooltip (no browser-default `title` look). Add `data-ssg-tooltip="text"` to your own elements (custom cells, headers) to get the same tooltip; colors are themeable via `--ssg-tooltip-*` tokens.
29
+ - Built-in dark theme — `theme="light" | "dark" | "auto"` switches the grid, every popover / panel / menu, the drag ghost and tooltips through a single token preset. `"auto"` follows `prefers-color-scheme`; with class-based dark frameworks (Mantine / HeroUI / Tailwind) pass your resolved color scheme instead.
30
+ - Toggle the top / bottom bars and their parts via props — whole bars (`showTopBar` / `showBottomBar`), the default top bar's summary chips and global-filter input, and the Rows/Columns counts in each bar.
31
+ - Filter management panel — review every active column filter in one place (jump to the column & edit, clear one / all, add new), opened from the column menu, the default top bar's clickable Filters chip, or `openFilterManager()` on the imperative handle. An optional filter chip bar (`showFilterChipBar`) keeps active filters visible right below the top bar.
32
+ - Built-in CSV export (`downloadCsv` / `exportCsv`), plus a library-agnostic `getExportData()` for Excel / XLSX / ODS — feed the shaped data (filter/sort/visible-order aware) to your own writer such as [hucre](https://github.com/productdevbook/hucre), ExcelJS, or SheetJS. No spreadsheet library is bundled; multi-sheet is composed on your side. See [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md).
33
+ - Export scopes: `'view'` (default — every filtered/sorted view row, scroll-independent), `'raw'` (every source row, ignoring filter & sort), `'rendered'` (only the rows currently rendered by virtualization — scroll-dependent), `'selection'`. Legacy `'all'` / `'visible'` keep working as deprecated aliases of `'view'` / `'rendered'`.
34
+ - TypeScript-first, fully controlled API.
35
+
36
+ ## Installation
37
+
38
+ ```sh
39
+ npm install @ishibashi0112/spreadsheet-grid
40
+ # pnpm add @ishibashi0112/spreadsheet-grid
41
+ # yarn add @ishibashi0112/spreadsheet-grid
42
+ ```
43
+
44
+ Requires **react** and **react-dom** `>= 19` as peer dependencies (install them in your app if you have not already). `@tanstack/react-virtual` is a regular dependency and is installed automatically.
45
+
46
+ ## Styles
47
+
48
+ The grid ships its CSS as a separate file. Import it once (for example, in your app entry):
49
+
50
+ ```ts
51
+ import '@ishibashi0112/spreadsheet-grid/style.css'
52
+ ```
53
+
54
+ The base styles are plain (unlayered) CSS scoped to `.ssg-*` classes, and all design tokens are defined at zero specificity (`:where(.ssg-root)`), so your token overrides always win regardless of import order.
55
+
56
+ ### Using with Tailwind CSS / HeroUI / Mantine
57
+
58
+ - **Tailwind CSS v3 (and HeroUI on v3)** — works out of the box. Preflight cannot break the grid: its element/universal resets lose to the grid's class selectors by specificity.
59
+ - **Tailwind CSS v4 (and HeroUI on v4)** — works out of the box. If you additionally want Tailwind utilities to override grid defaults without the `!` modifier, put the grid CSS into a cascade layer below `utilities`:
60
+
61
+ ```css
62
+ @import 'tailwindcss';
63
+ @import '@ishibashi0112/spreadsheet-grid/style.css' layer(components);
64
+ ```
65
+
66
+ Alternatively, use the pre-layered variant `style.layer.css` (everything wrapped in `@layer ssg-base`) and declare the layer order yourself:
67
+
68
+ ```css
69
+ @layer theme, base, ssg-base, components, utilities;
70
+ @import 'tailwindcss';
71
+ @import '@ishibashi0112/spreadsheet-grid/style.layer.css';
72
+ ```
73
+
74
+ - **Mantine** — works out of the box (no class-name or reset conflicts; grid popovers use `z-index: 1000`, above Mantine's default modal z-index).
75
+
76
+ To override a grid default reliably in plain CSS, chain your class with the grid's base class so it wins by specificity, independent of import order:
77
+
78
+ ```css
79
+ .ssg-body-cell.my-warn-cell {
80
+ background-color: #fff7ed;
81
+ }
82
+ ```
83
+
84
+ ## Quick start
85
+
86
+ ```tsx
87
+ import { useState } from 'react'
88
+ import { SpreadsheetGrid, type GridColumn } from '@ishibashi0112/spreadsheet-grid'
89
+ import '@ishibashi0112/spreadsheet-grid/style.css'
90
+
91
+ type Row = { id: number; name: string; qty: number }
92
+
93
+ const columns: GridColumn<Row>[] = [
94
+ { key: 'name', title: 'Name', width: 200, editable: true, filterType: 'text' },
95
+ { key: 'qty', title: 'Qty', width: 120, editable: true, filterType: 'number' },
96
+ ]
97
+
98
+ export function Example() {
99
+ const [rows, setRows] = useState<Row[]>([
100
+ { id: 1, name: 'Apple', qty: 3 },
101
+ { id: 2, name: 'Banana', qty: 5 },
102
+ ])
103
+
104
+ return (
105
+ <SpreadsheetGrid
106
+ rows={rows}
107
+ columns={columns}
108
+ onRowsChange={setRows}
109
+ rowKeyGetter={(row) => row.id}
110
+ />
111
+ )
112
+ }
113
+ ```
114
+
115
+ `rows` and `onRowsChange` make the grid a controlled component. A column needs at least `key` and `width`.
116
+
117
+ ## Sizing
118
+
119
+ By default the grid caps its height at `480px` (`max-height`) and scrolls when the content is taller. Pass `height` to take explicit control — use `height="100%"` to follow the parent's height, or a pixel value:
120
+
121
+ ```tsx
122
+ <div style={{ height: 600, minHeight: 0 }}>
123
+ <SpreadsheetGrid rows={rows} columns={columns} height="100%" />
124
+ </div>
125
+ ```
126
+
127
+ For `height="100%"` to work, the parent must have a resolved height (its ancestors are sized, and a flex child needs `min-height: 0`). This is standard CSS the library can't resolve for you. `maxHeight` sets an upper bound and can be combined with `height` (explicit height, capped at `maxHeight`).
128
+
129
+ ### Auto-height rows
130
+
131
+ Variable row height needs **two switches, both required**: the grid prop `autoHeight` (the master switch, default `false`) **and** at least one column with `autoHeight: true` (that column wraps and drives the row height). A cell grows only when `grid autoHeight && column.autoHeight` are both true. Auto-height is active only up to **50,000 rows**; beyond that it falls back to uniform `rowHeight`. `estimateRowHeight` is the placeholder for off-screen (not-yet-measured) rows — not a cap. See [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md) for details.
132
+
133
+ ```tsx
134
+ <SpreadsheetGrid
135
+ rows={rows}
136
+ columns={[{ key: 'note', title: 'Note', width: 320, autoHeight: true }]}
137
+ autoHeight // master switch — without it, the column's autoHeight is ignored
138
+ />
139
+ ```
140
+
141
+ ### Auto-sizing columns
142
+
143
+ Set `autoSizeColumns` to fit column widths to their content when data arrives — no imperative calls or effects needed on your side:
144
+
145
+ ```tsx
146
+ // Refit every time a new result set replaces `rows` (e.g. after a form submit).
147
+ <SpreadsheetGrid rows={rows} columns={columns} autoSizeColumns="onDataChange" />
148
+ ```
149
+
150
+ `'onMount'` fits once on first data; `'onDataChange'` refits whenever the `rows` reference changes; `false` (default) does nothing. It reuses the same measurement as the column menu's "Autosize All Columns", so per-column opt-outs apply: columns with `suppressAutoSize: true` (and `autoHeight: true` columns) keep their `width`. The trigger only reacts to `rows` — filtering, sorting and column reordering do **not** refit — and it writes to internal widths without calling `onColumnsChange`, so it coexists with controlled `columns`. Server-side (`dataSource`) is not supported. See [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md) for details.
151
+
152
+ ### Density
153
+
154
+ Set `density` to switch the overall sizing with one prop — `'compact' | 'standard' | 'comfortable'` (default `'standard'`, identical to previous versions):
155
+
156
+ ```tsx
157
+ <SpreadsheetGrid rows={rows} columns={columns} density="compact" />
158
+ ```
159
+
160
+ The preset drives the default `rowHeight` / `headerHeight` (compact: 28/32, standard: 36/40, comfortable: 44/48 — explicit props always win) and switches sizing tokens (cell horizontal padding, bar padding, icon-button size, relative cell font scale) via a root modifier class. Individual tokens (e.g. `--ssg-cell-pad-x`) can still be overridden for fine-tuning. Popovers/menus are not affected.
161
+
162
+ ## Server-side mode (SSRM)
163
+
164
+ Pass a `dataSource` instead of `rows` to switch to server-side mode. The grid keeps the full scroll height for the total row count and fetches only the blocks near the viewport:
165
+
166
+ ```tsx
167
+ <SpreadsheetGrid
168
+ columns={columns}
169
+ dataSource={{
170
+ async getRows({ startIndex, endIndex, query, signal }) {
171
+ // Apply `query` (filters / sort) on the server and return only [startIndex, endIndex).
172
+ const { rows, totalRowCount } = await fetchPage({ startIndex, endIndex, query, signal })
173
+ return { rows, totalRowCount }
174
+ },
175
+ }}
176
+ />
177
+ ```
178
+
179
+ Sorting, column filters, and the global filter stay enabled and are forwarded to the server through `query`. See the [API reference](./src/components/spreadsheet-grid/API_REFERENCE.md) for the full `getRows` contract, the filter wire format, and `serverSideRefreshToken`.
180
+
181
+ ## Styling & theming
182
+
183
+ - Override the CSS variables on `.ssg-root` (or scope them via the `className` prop):
184
+
185
+ ```css
186
+ .ssg-root {
187
+ --ssg-accent: #16a34a;
188
+ --ssg-radius: 4px;
189
+ }
190
+ ```
191
+
192
+ - Use the `classNames` prop for per-part class slots, `cellClassName` per column, and `getRowClassName` per row. Token overrides always apply (tokens are defined at zero specificity). For property overrides, chain with the base class (e.g. `.ssg-body-cell.my-class`) to win regardless of import order — see the Styles section above.
193
+
194
+ ### Dark theme
195
+
196
+ Pass `theme` to switch the whole surface — the grid itself, every popover / panel / menu (they are portalled to `document.body` and carry the theme class themselves), the column drag ghost and tooltips:
197
+
198
+ ```tsx
199
+ <SpreadsheetGrid theme="dark" columns={columns} rows={rows} />
200
+ ```
201
+
202
+ - `"light"` (default) / `"dark"` — explicit. `"auto"` follows the OS / browser `prefers-color-scheme` and updates live.
203
+ - `color-scheme` is set accordingly, so native scrollbars and `<select>` controls follow the theme too.
204
+ - The dark preset only redefines color tokens (`.ssg-theme-dark`); sizing tokens (radius, paddings) are theme-independent.
205
+
206
+ **With Mantine / HeroUI / Tailwind (class-based dark):** the page's actual theme may not match `prefers-color-scheme`, so pass your resolved color scheme instead of `"auto"`:
207
+
208
+ ```tsx
209
+ // Mantine
210
+ import { useComputedColorScheme } from '@mantine/core';
211
+ const colorScheme = useComputedColorScheme('light'); // 'light' | 'dark'
212
+ <SpreadsheetGrid theme={colorScheme} ... />
213
+
214
+ // HeroUI / Tailwind (next-themes)
215
+ import { useTheme } from 'next-themes';
216
+ const { resolvedTheme } = useTheme();
217
+ <SpreadsheetGrid theme={resolvedTheme === 'dark' ? 'dark' : 'light'} ... />
218
+ ```
219
+
220
+ **Customizing dark colors:** override tokens under `.ssg-theme-dark` — the class is present on the grid root and on every portal root, so one rule covers all surfaces:
221
+
222
+ ```css
223
+ .ssg-theme-dark {
224
+ --ssg-cell-bg: #0d0d0f;
225
+ --ssg-panel-bg: #1b1c20;
226
+ }
227
+ ```
228
+
229
+ Note: a plain `.ssg-root { --ssg-* }` override wins over **both** themes (theme presets are defined at zero specificity). To target light only, scope it with `.ssg-root:not(.ssg-theme-dark)`.
230
+
231
+ ## API reference
232
+
233
+ The full prop and type reference lives in [`src/components/spreadsheet-grid/API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md).
234
+
235
+ ## License
236
+
237
+ [MIT](./LICENSE) © 2026 Yuki Sakakibara
238
+
239
+ ---
240
+
241
+ ## 日本語
242
+
243
+ [English](#ishibashi0112spreadsheet-grid)
244
+
245
+ **React 19** 製の高性能な仮想化スプレッドシート/データグリッドです。TypeScript で書かれています。
246
+
247
+ ### 特徴
248
+
249
+ - スクロール空間の仮想化により最大 100 万行規模に対応。
250
+ - `position: sticky` による 3 ペイン固定列(左 / 中央 / 右)。
251
+ - ソート、列ごとのフィルター(`text` / `number` / `date` / `select` / `set` / `custom`)、グローバルフィルター。
252
+ - セル内編集とクリップボードのコピー/貼り付け、範囲選択、キーボード操作、Delete / Backspace による選択セルのクリア。セル編集は IME 対応(変換確定の Enter でセルが確定されない)。
253
+ - グリッド編集の undo / redo(セル編集・貼り付け・クリア)— `Ctrl/Cmd+Z`、`Ctrl/Cmd+Shift+Z` / `Ctrl/Cmd+Y` に加え、ハンドルの `undo()` / `redo()` / `canUndo()` / `canRedo()` とツールバー向けの `onUndoRedoStateChange` コールバック。編集時のアクティブセル・選択範囲まで復元し、画面外なら可視位置へスクロールで追従。履歴は構造共有のスナップショット方式で `undoHistoryLimit`(既定 100)まで保持し、`rows` が外部から差し替えられたときは自動破棄(クライアントサイド行モデル専用)。
254
+ - 折り返し・可変行高に対応する auto-height 行(任意)。
255
+ - データ投入時に列幅を内容へ自動フィット — `autoSizeColumns="onMount"`(初回にデータが載った一度きり)/ `"onDataChange"`(`rows` が変わるたび。フォーム送信結果の差し替え等)。列メニュー「すべての列の幅を自動調整」と同一エンジンで、列個別の除外は `suppressAutoSize`。
256
+ - 省略(…)セルの全文ツールチップ — `showCellOverflowTooltip` でホバー時に全文表示(実際にクリップされているセルのみ)。
257
+ - 日本語対応の折り返し — 列ごとの `wordBreak` / `lineBreak`。`wordBreak: 'auto-phrase'` で Chromium(Chrome / Edge)の文節折り返し(BudouX)。クロスブラウザの BudouX レシピは API リファレンス参照。
258
+ - `height` / `maxHeight` によるスクロールコンテナ高さの外部制御(`height="100%"` で親要素の高さに追従)。
259
+ - **クライアントサイド**(`rows`)と**サーバーサイド**(`dataSource`、SSRM)の両行モデル。
260
+ - CSS カスタムプロパティ(`--ssg-*`。特異度 0 で定義され、利用側の上書きが常に勝ちます)によるテーマ設定。基底スタイルは未レイヤーの単一クラス特異度で、Tailwind Preflight などの CSS リセットに壊されません。カスケードレイヤー版(`style.layer.css`)も同梱。`className` / `classNames` スロットも用意。
261
+ - スタイル付きツールチップを標準装備 — 操作ヒントや切り詰めテキストの全文表示は、ブラウザ標準の `title` ではなくダークチップのカスタムツールチップで表示。利用側の要素(カスタムセルやヘッダー)にも `data-ssg-tooltip="文言"` を付けるだけで同じ見た目になります。配色は `--ssg-tooltip-*` トークンで調整可。
262
+ - ダークテーマを標準装備 — `theme="light" | "dark" | "auto"` で、グリッド本体・全ポップオーバー / パネル / メニュー・ドラッグゴースト・ツールチップをトークンプリセット 1 つで一括切替。`"auto"` は `prefers-color-scheme` に追従(Mantine / HeroUI / Tailwind のクラスベース dark 運用では、解決済みのカラースキームを渡す使い方を推奨)。
263
+ - トップ / ボトムバーとその構成要素(バー全体〔`showTopBar` / `showBottomBar`〕、既定トップバーの summary chips・グローバルフィルター入力、各バーの Rows/Columns 件数)を props で表示制御。
264
+ - フィルター管理パネル — 適用中の列フィルターを 1 箇所で確認・操作(該当列へジャンプして編集 / 個別・全クリア / 追加)。列メニュー、既定トップバーの Filters chip クリック、ハンドルの `openFilterManager()` から開けます。トップバー直下に常時表示するフィルターチップバー(`showFilterChipBar`)もオプションで利用可。
265
+ - CSV エクスポート(`downloadCsv` / `exportCsv`)を内蔵。Excel / XLSX / ODS はライブラリ非依存の `getExportData()` で、整形済みデータ(フィルター/ソート/可視列順を反映)を [hucre](https://github.com/productdevbook/hucre) / ExcelJS / SheetJS など任意の writer へ流す方式。xlsx ライブラリは同梱せず、マルチシートは利用側で合成。詳細は [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md)。
266
+ - エクスポート scope: `'view'`(既定=フィルター/ソート後の全ビュー行。スクロール位置に非依存)/ `'raw'`(フィルター/ソート無視の全ソース行)/ `'rendered'`(描画中の行のみ=スクロール位置に依存)/ `'selection'`(選択範囲)。旧 `'all'` / `'visible'` は `'view'` / `'rendered'` の deprecated エイリアスとして従来どおり動作。
267
+ - TypeScript ファースト、完全 controlled な API。
268
+
269
+ ### インストール
270
+
271
+ ```sh
272
+ npm install @ishibashi0112/spreadsheet-grid
273
+ # pnpm add @ishibashi0112/spreadsheet-grid
274
+ # yarn add @ishibashi0112/spreadsheet-grid
275
+ ```
276
+
277
+ peer 依存として **react** / **react-dom** `>= 19` が必要です(未導入なら利用側で入れてください)。`@tanstack/react-virtual` は通常依存として自動的に入ります。
278
+
279
+ ### スタイル
280
+
281
+ CSS は別ファイルとして同梱されます。アプリのエントリ等で 1 度だけ import してください:
282
+
283
+ ```ts
284
+ import '@ishibashi0112/spreadsheet-grid/style.css'
285
+ ```
286
+
287
+ 基底スタイルは `.ssg-*` クラスにスコープした未レイヤーの素の CSS で、デザイントークンはすべて特異度 0(`:where(.ssg-root)`)で定義されています。トークンの上書きは読み込み順に依らず必ず勝ちます。
288
+
289
+ #### Tailwind CSS / HeroUI / Mantine との共存
290
+
291
+ - **Tailwind CSS v3(HeroUI の v3 世代)** — そのままで動作します。preflight(要素 / `*` セレクタのリセット)は本グリッドのクラスセレクタに特異度で負けるため、グリッドを壊せません。
292
+ - **Tailwind CSS v4(HeroUI の v4 世代)** — そのままで動作します。さらに Tailwind ユーティリティで `!` 修飾子なしにグリッド既定を上書きしたい場合は、グリッド CSS を `utilities` より下のレイヤーへ入れてください:
293
+
294
+ ```css
295
+ @import 'tailwindcss';
296
+ @import '@ishibashi0112/spreadsheet-grid/style.css' layer(components);
297
+ ```
298
+
299
+ もしくは全体を `@layer ssg-base` に包んだ `style.layer.css` を使い、レイヤー順を自分で宣言します:
300
+
301
+ ```css
302
+ @layer theme, base, ssg-base, components, utilities;
303
+ @import 'tailwindcss';
304
+ @import '@ishibashi0112/spreadsheet-grid/style.layer.css';
305
+ ```
306
+
307
+ - **Mantine** — そのままで動作します(クラス名・リセットの衝突なし。グリッドの popover は `z-index: 1000` で Mantine の既定モーダルより前面)。
308
+
309
+ 素の CSS でグリッド既定を確実に上書きするには、基底クラスと連結して特異度で勝たせてください(読み込み順に依存しません):
310
+
311
+ ```css
312
+ .ssg-body-cell.my-warn-cell {
313
+ background-color: #fff7ed;
314
+ }
315
+ ```
316
+
317
+ ### クイックスタート
318
+
319
+ ```tsx
320
+ import { useState } from 'react'
321
+ import { SpreadsheetGrid, type GridColumn } from '@ishibashi0112/spreadsheet-grid'
322
+ import '@ishibashi0112/spreadsheet-grid/style.css'
323
+
324
+ type Row = { id: number; name: string; qty: number }
325
+
326
+ const columns: GridColumn<Row>[] = [
327
+ { key: 'name', title: '名前', width: 200, editable: true, filterType: 'text' },
328
+ { key: 'qty', title: '数量', width: 120, editable: true, filterType: 'number' },
329
+ ]
330
+
331
+ export function Example() {
332
+ const [rows, setRows] = useState<Row[]>([
333
+ { id: 1, name: 'りんご', qty: 3 },
334
+ { id: 2, name: 'バナナ', qty: 5 },
335
+ ])
336
+
337
+ return (
338
+ <SpreadsheetGrid
339
+ rows={rows}
340
+ columns={columns}
341
+ onRowsChange={setRows}
342
+ rowKeyGetter={(row) => row.id}
343
+ />
344
+ )
345
+ }
346
+ ```
347
+
348
+ `rows` と `onRowsChange` でグリッドは controlled になります。列には最低限 `key` と `width` が必要です。
349
+
350
+ ### サイズ(高さ)
351
+
352
+ 既定ではグリッドの高さは `480px`(`max-height`)で頭打ちになり、中身がそれより高いとスクロールします。`height` を渡すと高さを明示制御できます。`height="100%"` で親要素の高さに追従、`number` で px 指定です:
353
+
354
+ ```tsx
355
+ <div style={{ height: 600, minHeight: 0 }}>
356
+ <SpreadsheetGrid rows={rows} columns={columns} height="100%" />
357
+ </div>
358
+ ```
359
+
360
+ `height="100%"` を効かせるには、**親要素が確定高さを持つ**必要があります(祖先まで高さが確定している/flex 子なら `min-height: 0` が必要)。これは CSS の一般則のため本ライブラリ側では解決できません。`maxHeight` は高さの上限で、`height` と併用できます(明示高さ+上限)。
361
+
362
+ #### 可変行高(auto-height)
363
+
364
+ 行高を可変にするには**2つのスイッチが両方必要**です。グリッド props の `autoHeight`(大本のスイッチ・既定 `false`)と、**少なくとも1列に `column.autoHeight: true`**(その列が折り返して行高を駆動)。セルが可変になるのは「グリッド `autoHeight` && 列 `autoHeight`」が両方 true のときだけです。有効なのは **50,000 行以内**で、超えると uniform 行高(`rowHeight`)へフォールバックします。`estimateRowHeight` は画面外(未測定)行の推定値で、上限ではありません。詳細は [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md)。
365
+
366
+ ```tsx
367
+ <SpreadsheetGrid
368
+ rows={rows}
369
+ columns={[{ key: 'note', title: '備考', width: 320, autoHeight: true }]}
370
+ autoHeight // 大本のスイッチ(これが無いと列側 autoHeight は無視される)
371
+ />
372
+ ```
373
+
374
+ #### 列幅の自動調整(autoSizeColumns)
375
+
376
+ `autoSizeColumns` を渡すと、データ投入時に列幅を内容へ自動フィットします(利用側でトークンや effect は不要):
377
+
378
+ ```tsx
379
+ // フォーム送信結果などで rows を丸ごと差し替えるたびに合わせ直す。
380
+ <SpreadsheetGrid rows={rows} columns={columns} autoSizeColumns="onDataChange" />
381
+ ```
382
+
383
+ `'onMount'` は初回にデータが載った一度きり、`'onDataChange'` は `rows`(参照)が変わるたび、`false`(既定)は無効です。計測は列メニュー「すべての列の幅を自動調整」と同一エンジンのため、列個別の除外がそのまま効きます — `suppressAutoSize: true` の列(および `autoHeight: true` の列)は `width` を維持します。発火 signal は `rows` のみで、フィルター / ソート / 列並べ替えでは**再フィットしません**。フィット幅は内部の列幅 state に反映され `onColumnsChange` を呼ばないため、controlled な `columns` とも競合しません。serverSide(`dataSource`)では無効です。詳細は [`API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md)。
384
+
385
+ #### 密度(density)
386
+
387
+ `density` プロップ 1 つで全体のサイズ感を切り替えられます — `'compact' | 'standard' | 'comfortable'`(既定 `'standard'` = 従来と同値):
388
+
389
+ ```tsx
390
+ <SpreadsheetGrid rows={rows} columns={columns} density="compact" />
391
+ ```
392
+
393
+ プリセットは `rowHeight` / `headerHeight` の既定値(compact: 28/32・standard: 36/40・comfortable: 44/48。明示 prop が常に優先)と、寸法トークン(セル横 padding・バー padding・アイコンボタン寸法・セル文字の相対拡縮)を root 修飾子経由で一括切替します。個別の微調整はトークン(例: `--ssg-cell-pad-x`)の上書きで可能です。popover / メニューは対象外です。
394
+
395
+ ### サーバーサイドモード(SSRM)
396
+
397
+ `rows` の代わりに `dataSource` を渡すとサーバーサイドモードになります。総行数ぶんのスクロール高さを保ったまま、可視窓に近いブロックだけを取得します:
398
+
399
+ ```tsx
400
+ <SpreadsheetGrid
401
+ columns={columns}
402
+ dataSource={{
403
+ async getRows({ startIndex, endIndex, query, signal }) {
404
+ // query(フィルター/ソート)をサーバで適用し、[startIndex, endIndex) のみ返す。
405
+ const { rows, totalRowCount } = await fetchPage({ startIndex, endIndex, query, signal })
406
+ return { rows, totalRowCount }
407
+ },
408
+ }}
409
+ />
410
+ ```
411
+
412
+ ソート・列フィルター・グローバルフィルターは有効なまま `query` 経由でサーバへ送られます。`getRows` の契約、フィルターの wire format、`serverSideRefreshToken` の詳細は [API リファレンス](./src/components/spreadsheet-grid/API_REFERENCE.md) を参照してください。
413
+
414
+ ### スタイリング / テーマ
415
+
416
+ - `.ssg-root` 上で CSS 変数を上書きします(`className` prop でスコープも可能):
417
+
418
+ ```css
419
+ .ssg-root {
420
+ --ssg-accent: #16a34a;
421
+ --ssg-radius: 4px;
422
+ }
423
+ ```
424
+
425
+ - パーツ別の class は `classNames` prop、列単位は `cellClassName`、行単位は `getRowClassName` で付与できます。トークン上書きは常に効きます(特異度 0 で定義)。プロパティ上書きは基底クラスとの連結(例: `.ssg-body-cell.my-class`)で読み込み順に依らず確実になります — 上記「スタイル」参照。
426
+
427
+ #### ダークテーマ
428
+
429
+ `theme` を渡すだけで全サーフェス — グリッド本体・全ポップオーバー / パネル / メニュー(`document.body` 直下のポータルですが、自身がテーマクラスを保持します)・列ドラッグゴースト・ツールチップ — が一括で切り替わります:
430
+
431
+ ```tsx
432
+ <SpreadsheetGrid theme="dark" columns={columns} rows={rows} />
433
+ ```
434
+
435
+ - `"light"`(既定)/ `"dark"` は明示指定。`"auto"` は OS / ブラウザの `prefers-color-scheme` に追従し、設定変更にもライブで反応します。
436
+ - `color-scheme` も併せて切り替わるため、ネイティブのスクロールバーや `<select>` もテーマに揃います。
437
+ - ダークプリセットが上書きするのは色トークンのみ(`.ssg-theme-dark`)。寸法トークン(radius / padding 等)はテーマ非依存です。
438
+
439
+ **Mantine / HeroUI / Tailwind(クラスベース dark)との連動:** ページの実テーマと `prefers-color-scheme` は一致しないことがあるため、`"auto"` ではなく利用側カラースキームの解決値を渡してください:
440
+
441
+ ```tsx
442
+ // Mantine
443
+ import { useComputedColorScheme } from '@mantine/core';
444
+ const colorScheme = useComputedColorScheme('light'); // 'light' | 'dark'
445
+ <SpreadsheetGrid theme={colorScheme} ... />
446
+
447
+ // HeroUI / Tailwind(next-themes)
448
+ import { useTheme } from 'next-themes';
449
+ const { resolvedTheme } = useTheme();
450
+ <SpreadsheetGrid theme={resolvedTheme === 'dark' ? 'dark' : 'light'} ... />
451
+ ```
452
+
453
+ **ダーク時の色調整:** `.ssg-theme-dark` 配下でトークンを上書きします。このクラスはグリッド root と全ポータル root に付与されるため、1 ルールで全サーフェスに効きます:
454
+
455
+ ```css
456
+ .ssg-theme-dark {
457
+ --ssg-cell-bg: #0d0d0f;
458
+ --ssg-panel-bg: #1b1c20;
459
+ }
460
+ ```
461
+
462
+ 注意: 素の `.ssg-root { --ssg-* }` 上書きは**両テーマ**に勝ちます(テーマプリセットは特異度 0 で定義)。ライトのみを対象にしたい場合は `.ssg-root:not(.ssg-theme-dark)` でスコープしてください。
463
+
464
+ ### API リファレンス
465
+
466
+ prop と型の完全なリファレンスは [`src/components/spreadsheet-grid/API_REFERENCE.md`](./src/components/spreadsheet-grid/API_REFERENCE.md) にあります。
467
+
468
+ ### ライセンス
469
+
468
470
  [MIT](./LICENSE) © 2026 Yuki Sakakibara