SpreadsheetGrid props
SpreadsheetGridProps<T> の全 prop と既定値
SpreadsheetGrid props (SpreadsheetGridProps<T>)
| Name | Type | Default | Description |
|---|---|---|---|
rows | readonly T[] | — | clientSide モードの行データ。readonly 配列も受け付ける(グリッドは入力配列を破壊的に変更しない。編集結果は onRowsChange が新配列で返す)。dataSource を指定した場合は無視され serverSide モードになる(両者は排他)。 |
columns | readonly GridColumn<T>[] | (required) | 列定義の配列。readonly 配列も受け付ける。 |
onRowsChange | (nextRows: T[]) => void | — | 行が変化したとき呼ばれる(rows を controlled にする)。 |
dataSource | ServerSideDataSource<T> | — | serverSide(SSRM)モードのデータ供給口。指定すると可視窓近傍のブロックだけを getRows で都度取得し、rows 系の clientSide パイプラインをバイパスする。updateRows(任意)を持たせるとセル編集の書き戻し(楽観更新つき)が有効になる(「セル編集の書き戻し」節)。 |
serverSideRefreshToken | number | — | serverSide のソフトリフレッシュ用トークン。値を増やすと、クエリ(フィルター/ソート/グローバル)を変えずにキャッシュを破棄して現在の可視レンジをサーバから取り直す。スクロール位置は保持し、件数は到着ブロックの totalRowCount で追従する(clientSide では無視)。命令的に呼びたい場合は同挙動のハンドル refreshServerSide() を使う。 |
onServerSideLoadError | (error, params) => void | — | serverSide の getRows が reject したときの通知(abort は正常キャンセルのため通知しない)。params は失敗した要求の view 空間レンジ { startIndex, endIndex }。グリッド内蔵のエラーバー(再試行 UI)とは独立に呼ばれる(利用側トースト / ログ用)。インライン関数可(latest-ref 経由で読む)。 |
onServerSideWriteError | (error, params) => void | — | serverSide の dataSource.updateRows が reject したときの通知。グリッド側は楽観更新をロールバック済みで、params.updates に失敗した行更新(rowKey / changes / previousRow)が入る(利用側トースト / リトライ導線用)。グリッド内蔵の保存失敗バーとは独立に呼ばれる。インライン関数可(latest-ref 経由で読む)。詳細は「セル編集の書き戻し」節。 |
onColumnsChange | (nextColumns: GridColumn<T>[]) => void | — | 列が変化したとき呼ばれる。列メニューの固定切替はこれが指定されている場合のみ反映。列の並べ替え(ヘッダーのドラッグ / 列パネル)と applyState で渡る nextColumns は、配列順が固定列ごと(左固定 → 中央 → 右固定)に並べ直される(画面上の列順は変わらない。例: 右固定列の後ろに宣言した列があると、右固定列が配列の末尾へ移る)。列メニューの固定切替は配列順を変えず pinned だけを更新する。 |
rowKeyGetter | (row: T, index: number) => GridRowKey | index ベース | 安定した行キーを返す。 |
isRowExportable | (row: T, ctx: { viewRowIndex: number; rowKey: GridRowKey }) => boolean | 全行 true | コピー(Ctrl/Cmd+C の TSV)/ exportCsv / getExportData の対象行フィルタ。false の行は出力から行ごと除く(行単位のみ。全体選択かの判定と貼り付けには影響しない)。ctx.viewRowIndex はフィルター / ソート適用後のビュー行 index(scope 'raw' のみ rows 配列のソース index)、ctx.rowKey は rowKeyGetter の値。用途: プレースホルダ行など表示上の詰め物を出力から除く。 |
createRow | () => T | — | 行追加時に使う新規行ファクトリ。 |
createOverflowColumn | (columnIndex: number) => GridColumn<T> | — | 列追加時に使う列ファクトリ。 |
rowHeight | number | density 依存(standard: 36) | uniform 行の行高(px)。未指定時は density プリセット(compact: 28 / comfortable: 44)から解決。明示指定が常に優先。 |
autoHeight | boolean | false | auto-height 行(可変行高)を有効化する大本のスイッチ。これに加えて少なくとも1列に column.autoHeight: true が必要(その列が折り返して行高を駆動)。両方 true かつ行数 ≤ 50,000のとき有効(超過時は uniform rowHeight へフォールバック)。詳細は「auto-height 行」節。 |
estimateRowHeight | number | rowHeight | 未測定行の推定行高(px)。 |
headerHeight | number | density 依存(standard: 40) | ヘッダー行の高さ(px)。未指定時は density プリセット(compact: 32 / comfortable: 48)から解決。明示指定が常に優先。 |
density | 'compact' | 'standard' | 'comfortable' | 'standard' | 密度プリセット。rowHeight / headerHeight の既定値と寸法トークン(セル横 padding / バー padding / アイコンボタン寸法 / セル文字の相対拡縮)を一括切替。'standard' は従来と同値。個別調整はトークン(--ssg-cell-pad-x 等)の上書きで可能。popover / menu 等のポータルは対象外。 |
theme | 'light' | 'dark' | 'auto' | 'light' | カラーテーマ。'dark' でダークプリセット(.ssg-theme-dark のトークン一括上書き。Mantine dark 系パレット)をグリッド本体・全ポータル(popover / menu / panel)・ドラッグゴースト・ツールチップへ適用。'auto' は prefers-color-scheme へ追従(Mantine / HeroUI 等クラスベース dark 運用では、利用側カラースキームの解決値を 'light' | 'dark' で渡す使い方を推奨)。個別の色調整はトークン(--ssg-*)の上書きで可能。 |
motion | 'auto' | 'on' | 'off' | 'auto' | モーション(アニメーション / トランジション)の有効化。'auto' は OS / ブラウザの「視差効果を減らす」(prefers-reduced-motion: reduce)が有効なら 'off'、それ以外は 'on' として扱う。'off' では root と全ポータル(popover / menu / panel)・ドラッグゴースト・ツールチップへ .ssg-motion-off が付き、継続時間トークン(--ssg-motion-fast / --ssg-motion-base / --ssg-motion-slow)が 0 になって動きが止まる(表示結果は同じ)。'on' は OS 設定に関わらず動かす。速さの調整はトークンの上書き。詳細は「モーション」節。 |
animateRows | boolean | true | 行の並び替えアニメ。ソート / フィルター / グループ開閉 / rows の差し替えで、描画中の行が新しい位置へ滑り(--ssg-motion-base)、スクロールなしで現れた行は上から順にフェードインする。スクロール中の仮想化による出入りは対象外。auto-height 行と serverSide(SSRM)では自動で無効。motion の実効値が 'off' なら動かない。詳細は「モーション」節。 |
showCopyRange | boolean | true | Ctrl/Cmd+C のあと、コピー元の範囲に Excel と同じ「動く点線」を残す(.ssg-copy-range-overlay。motion が 'off' なら静的な点線)。Esc と編集開始で消え、ソート / フィルター / グループ開閉 / 行数 / 列の変化で描画しなくなる(貼り付けでは残る)。false で点線を出さない(コピー自体は従来どおり)。 |
hoverHighlight | 'row' | 'cross' | 'row' | ホバーの強調範囲。'row' はポインタの行だけ(従来)。'cross' はその行に加えて、ポインタの列(列ヘッダー + 同じ列の他セルに薄い帯 .ssg-col-hover-overlay)と行番号も染めるクロスヘア表示。色はトークン --ssg-col-hover-bg(帯)/ --ssg-select-bg(ヘッダー・行番号)。横に長い表で「この値はどの列か」を視線移動なしで答えるため。 |
highlightChanges | boolean | false | rows が変わったとき、値が変わったセルを一瞬アクセント色で光らせ(.ssg-body-cell--changed、--ssg-motion-slow)、数値の既定セルは旧値から新値へカウントアップする(--ssg-motion-base。表示は valueFormatter で整形)。セル編集 / 貼り付け / クリア / undo・redo / 外部からの rows 差し替えのすべてが対象。clientSide 専用(SSRM では無効)。描画中のセルだけが対象で、変わったセルが 200 を超えるとフラッシュのみ、2,000 を超える・行の挿入 / 削除 / 並べ替えを伴う・参照の変わった行が 5,000 を超える(データの読み直し)ときは何もしない。 |
showSaveStatus | boolean | true | serverSide の書き戻し(dataSource.updateRows)で、確定したセルに保存状態の印を出す。保存中は斜線 + 小さなスピナー([data-ssg-save="pending"])、成功は ✓ バッジを 1.3 秒("ok")、失敗は揺れ + 赤いフラッシュ + ✕ バッジ("failed")とセル下の「保存に失敗しました」チップ(4 秒 / スクロールで消える)。既存の保存失敗バー(onServerSideWriteError)とは独立に出る。描画中のセルだけが対象。clientSide では何も出ない。 |
rowDragMotion | 'ghost' | 'live' | 'ghost' | 行ドラッグ並べ替え(enableRowDrag)の表示方式。'ghost' は従来(ゴースト + ガイド線、ドロップ後に新しい位置へスライド)。'live' は掴んだ行がポインタに追従し、通る先の行がその場で上下へ退避する(ドロップ先が「隙間」として見える。枠外で離しても直前の位置へ確定、Esc で元へ戻る。ウィンドウからフォーカスが外れたときも元へ戻る)。motion が 'off' のときは 'ghost' と同じ。 |
columnDragMotion | 'ghost' | 'live' | 'ghost' | 列ヘッダーのドラッグ並べ替え(onColumnsChange 指定時)の表示方式。'ghost' は従来(ゴースト + 縦線、ドロップ後に新しい位置へスライド)。'live' は同じペイン内では掴んだ列(ヘッダー + 描画中のセル)がポインタに追従し、通る先の列がその場で左右へ退避する(ドロップ先が「隙間」として見える。枠外で離しても直前の位置へ確定、Esc で元へ戻る。ウィンドウからフォーカスが外れたときも元へ戻る)。固定列のペインをまたいで移す(固定の変更)ときは、掴んだ列の複製(浮かぶ列)がポインタに付いてきて元のペインは列が抜けた分だけ詰まり、移動先は縦線で示す(浮かぶ列の見出しに、固定ペインへはピン / 中央へは移動の矢印のアイコン。浮かぶ列は表の枠の内側に収まり、離すと新しい位置へ滑り込む)。motion が 'off' のときは 'ghost' と同じ。 |
find | boolean | FindOptions | false | セル内検索。true でグリッド右上の検索バー(.ssg-find-bar)と Ctrl/Cmd+F(グリッドにフォーカスがあるときだけ横取り)が有効になる。表示文字列(valueFormatter 適用後)への部分一致で、ヒットをすべて <mark class="ssg-find-mark"> で強調し、Enter / ↓ で次、Shift+Enter / ↑ で前へ(アクティブセルとスクロールが追従)、Esc で閉じる。行は消さない(絞り込みのフィルターとは別)。FindOptions = { shortcut?: boolean(既定 true。false でブラウザ標準の検索に譲る), caseSensitive?: boolean(既定 false) }。走査は時間分割(1 チャンク 10ms)で入力を止めない。SSRM はロード済み範囲だけが対象。ヒットは 10,000 件で打ち切り(件数に +)。詳細は「セル内検索」節。 |
onFindChange | (params: FindChangeParams) => void | — | セル内検索の状態({ query, matchCount, currentIndex, open })が変わったときの通知。currentIndex は 0 始まり(ヒットなしは null)。ツールバーに「3 / 12」を出す等に使う。 |
rowHeaderWidth | number | 56 | 行番号列の幅(px)。 |
height | number | string | — | グリッドの明示高さ。値の種類で何の高さかが変わる。① % を含む文字列('100%' / '50%' / 'calc(100% - 40px)'): トップバー・フィルターチップバー・ボトムバーを含むグリッド全体の高さ。'100%' でグリッドが親要素に収まり、バーを除いた残りがスクロール領域になる(ルートに ssg-root--fill-height が付く)。親要素が確定高さを持つ前提で、親が高さ auto だと全行分まで伸びて仮想化が効かない。② number(px)/ % を含まない文字列('400px' / '50vh' / 'calc(100vh - 120px)'): スクロール領域だけの高さ(グリッド全体はバーの分だけ高くなる)。③ 未指定: スクロール領域は内容の高さで maxHeight によりクリップ。ルートへの style={{ height }} だけではスクロール領域は決まらないため、高さはこの prop で指定する。 |
maxHeight | number | string | —(既定 480px) | スクロール領域の高さ上限(バーは含まない。height の種類に関わらず同じ)。height・maxHeight が共に未指定のときのみ既定の 480px が効く(従来挙動)。数値の height と併用するとスクロール領域 = min(height, maxHeight)、% の height と併用すると min(maxHeight, 親の高さ − バー)(親が大きければグリッド全体はバー + maxHeight に縮む)。 |
readOnly | boolean | false | グリッド全体の編集を無効化。編集中に true へ切り替わった場合、その編集は確定時に書き込まれず終了する(canEditCell も確定時に再評価)。renderCell の ctx.setValue は対象外で、readOnly 中も書き込める(ctx.readOnly を見て利用側で無効化する)。行ドラッグによる並べ替え(enableRowDrag)も対象外(セル値の編集ではないため)で、止める場合は enableRowDrag={!readOnly} を渡す。 |
dimReadOnlyCells | boolean | false | readonly セルの組み込み淡色表示(背景 + 文字色)を有効化。false でもセマンティッククラス .ssg-body-cell--readonly は常時付与され、利用側 CSS のフックに使える。 |
canEditCell | (rowIndex, colIndex, row, column, ctx) => boolean | — | セル単位の編集可否ゲート。rowIndex はビュー行 index(フィルター / ソート後の表示位置)で、表示(読み取り専用の見た目)・編集開始・確定・貼り付け・Delete クリア・IME 入力のどの経路でも同じ値が渡る。ctx(CanEditCellContext)は { sourceRowIndex, rowKey } = 元 rows の index と行キー(cellClassName の ctx と同基準。serverSide では sourceRowIndex は view index と同値)。ソート / フィルターで動かない判定は row か ctx.rowKey で行う。貼り付けで末尾に追加される行は、追加後の位置と元 rows の index で呼ばれる。※ v0.48 以前は clientSide の貼り付けと Delete クリアだけ元 rows の index が渡っていた。 |
enableUndoRedo | boolean | true | グリッド編集(セル編集 / ペースト / renderCell の setValue)の取り消し/やり直し。Ctrl/Cmd+Z = undo、Ctrl/Cmd+Shift+Z / Ctrl/Cmd+Y = redo(ハンドルの undo() / redo() でも可)。clientSide(rows + onRowsChange)専用で、serverSide(dataSource)/ readOnly / onRowsChange 未指定時は無効。履歴は「変更前 rows 配列」の参照スナップショット(未変更行は構造共有されるため低コスト)。onRowsChange で受け取った配列は参照そのまま rows へ戻すのが前提(map 等で作り直すと毎回「外部変更」と見なされ履歴が消える)。rows が grid 起点以外(親の直接 setState 等)で差し替わると履歴は自動破棄。エディタ内の文字入力の取り消しは input のネイティブ undo に委譲(グリッドの undo は確定済みの編集が対象)。 |
undoHistoryLimit | number | 100 | 保持する undo ステップ数の上限。超過分は古い順に破棄。 |
enableClearOnDelete | boolean | true | Delete / Backspace キーによる選択セル(なければアクティブセル)の値クリア。false でキーは何もしない(素通し)。ペースト・エディタでの上書き・undo/redo には影響しない(クリアのキーボード操作だけの opt-out)。 |
editorEnterMove | EditorEnterMove | 'down' | 組み込みエディタ(text / number / select / date)の Enter 確定後にアクティブセルをどこへ移すか('down' | 'up' | 'right' | 'left' | 'none'。Excel の「Enter キーを押したら、セルを移動する(方向)」相当)。'none' は移動せずその場に留まる。Tab / Shift+Tab(右 / 左)と Escape には影響しない。custom エディタはキーバインドが consumer 責務のため対象外(ctx.commit(value, direction) の direction で指定)。 |
imeDirectInput | boolean | false | IME オンのままの直接入力(Excel / SPREAD と同じ)。true で、編集していないあいだグリッドのフォーカスをアクティブセル上の透明な入力欄(入力受け)に置き、IME の変換をそこで始める。text エディタ(editor 未指定 / type: 'text')の編集可能セルでは変換中の文字をセルの上に表示し、変換を確定すると確定した文字列を初期値に編集を始める(もう一度 Enter でセルを確定して移動)。number エディタの列では確定した全角の数字・符号・小数点・3 桁区切りを半角に直して編集を始め(「12.5」→ 12.5)、数値として読めない変換は捨てる。それ以外の列(select / date / checkbox / custom)と読み取り専用セルでは変換を捨てる(従来どおり何も入らない)。キー操作 / コピー・貼り付け / Tab は従来どおり。既定 OFF の理由: 有効中はグリッドにフォーカスがあるあいだ document.activeElement が入力欄になり、入力欄では反応しないアプリのショートカット(例: Mantine の useHotkeys の既定)が効かなくなるため。詳細は「IME オンのままの直接入力」節。 |
onUndoRedoStateChange | (state: UndoRedoState) => void | — | undo / redo 可能状態が変化したときに呼ばれる(ツールバーの undo/redo ボタンの disabled 表示などリアクティブな UI 用)。初回マウントでは発火せず、同値では再発火しない。毎レンダーのインライン関数でも問題ない。引数の型 UndoRedoState({ canUndo, canRedo })はパッケージから import できる。 |
enableRangeSelection | boolean | true | 複数セル範囲選択。 |
enableRowSelection | boolean | false | チェックボックス行選択の有効化(マスタースイッチ)。true で行ヘッダ(行NO)ガターが行選択のヒット領域になり、Excel 風のガター起点セル範囲選択は off(ボディ側セルのドラッグ範囲選択は不変)。判定は O(1)・全選択は除外集合でキーを列挙しない(1M 行でも一定コスト)。 |
rowSelectionMode | 'single' | 'multiple' | 'multiple' | 単一/複数の選択モード。single は常に 1 行。multiple はクリックでトグル、shift+クリック/ガタードラッグで範囲選択。 |
enableSelectAllRows | boolean | enableRowSelection && multiple | ヘッダ左上コーナーの全選択チェック(tri-state: none/some/all)の有効化。 |
rowSelection | RowSelectionModel | — | controlled の行選択記述子。{ type:'include', rowKeys }=これらを選択 / { type:'exclude', rowKeys }=全選択のうち除外。全選択をキー列挙せず表現できる。指定時は controlled(内部 state を使わない)。 |
selectedRowKeys | GridRowKey[] | — | controlled 簡易版({ type:'include', rowKeys } の糖衣)。rowSelection と併用時は rowSelection を優先。全選択(exclude)は表現不可。 |
onRowSelectionChange | (model: RowSelectionModel) => void | — | 行選択変化の通知(controlled/uncontrolled いずれでも発火)。 |
enableGlobalFilter | boolean | true | グローバルフィルター機能の有効化。false で機能が無効になり、既定トップバーのフィルター入力欄も出ない(summary は showTopBarSummary に従う。トップバー自体を消すには showTopBar=false)。 |
enableColumnFilter | boolean | true | 列ごとのフィルター。 |
renderFilterDateInput | (ctx: FilterDateInputContext) => ReactNode | 内製の日付フィールド | dateSet フィルター条件の日付入力を利用側コンポーネント(Mantine DatePickerInput 等)へ差し替えるスロット。既定は内製フィールド(自由入力 + ドリルアップカレンダー)。値契約と外側クリック対策はソートとフィルターを参照。 |
getFilterOptions | (params: GetFilterOptionsParams<T>) => Promise<{ options: GridSelectFilterOption[]; truncated?: boolean }> | — | set / select / 複合(numberSet / textSet / dateSet)列の候補を非同期に供給する(DB の DISTINCT など)。popover を開くたびに { columnKey, column, columnFilters(自列を除く他列の有効フィルター), globalText, signal } で呼ばれ、閉じる / 列切替で signal が abort される(ライブラリはキャッシュしない)。読み込み中 / 失敗(再試行)/ 打ち切り(truncated)の表示は popover が持つ。優先順位は column.filterOptions(静的)> getFilterOptions > rows 自動収集。非同期候補の列は反転(exclude)可。clientSide / serverSide 両対応。詳細は「ソートとフィルター」ガイド。 |
enableSorting | boolean | true | ソート機能の有効化。ソートは列メニュー(⋮)の「昇順 / 降順で並び替え」と「並び替えを管理…」パネルから行う(ヘッダー本体のクリックは列範囲選択)。false でメニューのソート項目が消え、applyState 等で載った sort も適用されない。ソート順は値で決まり、数値として解釈できる値 → 文字列(日本語照合・数字は数値順)→ 空白セル(null / undefined / 空文字)の順。空白セルは昇順でも降順でも末尾(Excel と同じ)。 |
manualFiltering | boolean | false | 列 / グローバルフィルターの絞り込みをグリッドで行わない(手動フィルターモード)。フィルター UI(popover / チップバー / フィルター管理 / フィルター中の印)と状態(GridState.filters / onStateChange)は従来どおり動き、rows は渡した件数・順のまま表示される(絞り込みはサーバ側 WHERE 等の外部責務)。rows が 0 件でフィルターが載っているときは noMatchingRowsText を表示。serverSide(dataSource)では無視。詳細は「ソートとフィルター」ガイド。 |
manualSorting | boolean | false | ソートの並べ替えをグリッドで行わない(手動ソートモード)。ソート UI と状態(GridState.sort / onStateChange)は従来どおり動き、rows は渡した順のまま。再マウントなしで切り替え可(false へ戻すと即座にクライアントソートが適用)。手動ソート中はラベル行の sortMode 連動 / 行ドラッグの無効化は起きない(並べ替えていない扱い)。serverSide では無視。 |
enableColumnResize | boolean | true | 列幅の手動リサイズ可否のグリッド既定。各列 resizable 未指定時に継承(column.resizable ?? enableColumnResize)。 |
autoSizeColumns | 'onMount' | 'onDataChange' | false | false | データ投入時に全列幅を内容へ自動フィット。'onMount'=初回にデータが載った一度きり / 'onDataChange'=rows(参照)が変わるたび(= データ差し替えのたび。手動リサイズは上書き) / false=無効。計測は列メニュー「すべての列の幅を自動調整」と同一エンジン(suppressAutoSize / autoHeight 列は除外)。フィルター / ソート / 列並べ替えでは再フィットしません。serverSide(dataSource)では無効。詳細は「flex と autoSize」節。 |
showCellOverflowTooltip | boolean | false | セル内容が省略(…)される列で、ホバー時に全文ツールチップを表示。対象は既定テキストセルのみ(renderCell 列 / autoHeight 折り返し列は対象外)。表示はホバー時に scrollWidth > clientWidth を判定し、実際にクリップされているセルのみ。既存のカスタムツールチップ(data-ssg-tooltip)を共有。詳細は「ツールチップ」節。 |
showValidationMarks | boolean | true | invalid マーク(背景 + 右上マーカー + ホバーツールチップ)の表示可否。false で非表示 + 可視セルの validate 評価スキップ。getInvalidCells() と validationMode: 'reject' の書き込み拒否には影響しない(独立経路)。「送信時にだけマークを出す」UX は利用側 state で本 prop を切り替えて実現(「バリデーション」節のレシピ参照)。 |
enableColumnMenu | boolean | true | 列メニュー(⋮ + ヘッダー右クリック)。 |
enableRowHover | boolean | true | 行ホバー時に行全体を薄くハイライト。 |
hoveredRowIndex | number | null | — | 行ホバーの controlled 値(ビュー行 index / null = ホバーなし)。指定時は内部 state を使わずこの値でハイライトし、pointer 由来の変化は onHoveredRowChange で通知のみ(optionally controlled)。enableRowHover: false のときは無視(ハイライトも通知もしない)。一時的な UI 状態のため GridState / ハンドルには載らない。 |
onHoveredRowChange | (viewRowIndex: number | null, ctx: { source: 'pointer' }) => void | — | 行ホバーが変わったときの通知(uncontrolled でも呼ばれる)。viewRowIndex はフィルター / ソート適用後のビュー行 index。同値では発火しない(pointerenter は同一行内のセル跨ぎでも来るため)。用途: 複数グリッド間のホバー同期など。 |
onCellClick | (params: GridCellEventParams<T>) => void | — | データセル(データ行 × 利用側の列)のクリックで呼ばれる。左ボタンのみで、押した位置と離した位置が同じセルのときだけ呼ばれる(範囲選択のドラッグはクリック扱いにしない)。読み取り専用セルでも呼ばれ、セル内の要素(checkbox 等)のクリックもそのセルのクリックとして通知する。params は GridCellRef<T>({ row, rowKey, rowIndex, sourceRowIndex, column, columnKey, colIndex, value })+ event(DOM 標準の MouseEvent)。列ヘッダー / 行番号 / グループ行 / ラベル行 / 展開行 / SSRM の未ロード行 / 合成列(展開トグル・行ドラッグハンドル)では呼ばれない。インライン関数可。詳細は「セル操作の通知」節。 |
onCellDoubleClick | (params: GridCellDoubleClickParams<T>) => void | — | データセルのダブルクリック(タッチのダブルタップ含む)で、既定の動作(編集可能セルなら編集開始)の前に呼ばれる。読み取り専用セルでも呼ばれる。params.preventDefault() で既定の動作を止められる(params.event.preventDefault() では止まらない)。対象のセルは onCellClick と同じ。インライン関数可。 |
onActiveCellChange | (cell: GridCellRef<T> | null) => void | — | アクティブセルが変わったときに呼ばれる(クリック / キー操作 / setActiveCell など由来を問わない)。アクティブセルが無くなった / データセル以外(グループ行 / ラベル行 / 未ロード行 / 合成列)へ移ったときは null。行キー・列キー・位置(rowIndex / colIndex)がすべて同じなら呼ばれない(同じセルへの再設定)。座標が同じでも、ソートなどでその位置の行が変われば呼ばれる。初回マウントでは呼ばれない。通知はペイント後(クリックでは onCellClick より先)。インライン関数可。 |
enableColumnHeaderHover | boolean | true | 列ヘッダーのホバー時にヘッダーセルを薄くハイライト。 |
noMatchingRowsText | string | '一致する行がありません' | フィルター結果 0 行時のオーバーレイ文言。serverSide(dataSource)では列 / グローバルフィルターが掛かった状態で 0 件のときにこちら、掛かっていなければ noRowsText を出す。 |
noRowsText | string | '表示する行がありません' | rows が 0 件のときの文言。 |
ariaLabel | string | — | 表(role="grid" の要素)のアクセシブル名。スクリーンリーダーが表を読み上げるときの名前で、1 画面に複数のグリッドを置くときは付けておくと区別できる(「アクセシビリティ」節)。 |
showTopBar | boolean | true | 上部バー(ツールバー)の表示有無。false で renderTopBar / enableGlobalFilter に関わらず一切描画しない(表示のマスタースイッチ。矛盾指定時は renderTopBar より優先)。 |
showTopBarSummary | boolean | true | 既定トップバーの summary chips(件数/フィルター/ソート)の表示有無。renderTopBar 未指定時のみ有効。これと showTopBarFilter がともに非表示なら既定トップバーは描画されない(空バーを出さない)。 |
showTopBarCounts | boolean | true | 既定トップバーの Rows / Columns 件数 chips の表示有無。showTopBarSummary=true(かつ renderTopBar 未指定)のときのみ有効。Filter / Sort chips は対象外。 |
showTopBarFilter | boolean | true | 既定トップバーのグローバルフィルター入力欄の表示有無。renderTopBar 未指定時のみ有効。enableGlobalFilter=false のときは本値に関わらず非表示。 |
globalFilterPlaceholder | string | 'グローバルフィルター' | 既定トップバーのグローバルフィルター入力の placeholder。renderTopBar 未指定時のみ有効。 |
globalFilterIcon | ReactNode | 組み込み検索アイコン | 既定トップバーのグローバルフィルター入力の左アイコン。renderTopBar 未指定時のみ有効。undefined=組み込みの検索(虫眼鏡)アイコン / null(など falsy)=アイコン無し / 任意 ReactNode=差し替え。クリアボタンは入力枠の内側右に × で表示され、入力が空のときは出ない。 |
showBottomBar | boolean | true | 下部バー(ステータスバー)の表示有無。false で renderBottomBar に関わらず一切描画しない(表示のマスタースイッチ。矛盾指定時は renderBottomBar より優先)。 |
showBottomBarCounts | boolean | true | 既定ボトムバーの Rows / Columns 件数 chips(左側)の表示有無。renderBottomBar 未指定時のみ有効。右側の Active / Selection / 選択統計 / Cols は対象外。 |
showSelectionAggregates | boolean | true | 2 セル以上を選んだとき、選択範囲の集計(平均 / データの個数 / 合計)を既定ボトムバーの右側(Active / Selection の隣の空き)に出す。数えるのは表示中のデータ行 × データ列のセルで(フィルターで隠れた行・グループ行・ラベル行・合成列は対象外)、合計 / 平均は数値と数値の文字列だけ(グループ集計と同じ判定)、データの個数は空でないセル。大きな選択は時間分割で計算し、その間は「集計中…」。SSRM では読み込み済みの行だけで計算し「読み込み済みの行のみ」と添える。false では計算もせず slotContext.selectionAggregates は null(「選択範囲の集計」節)。 |
showFilterChipBar | boolean | false | フィルターチップバー(適用中の列フィルターをトップバー直下にチップで常時表示)の表示有無(opt-in)。有効フィルター 0 件時はバーごと非表示(空バーは出さない)。showTopBar とは独立。チップ本体クリックで対象列へジャンプしてフィルター popover を開き、× で個別クリア、「すべてクリア」は列フィルターのみ対象(グローバルフィルターは対象外)。 |
renderTopBar | (ctx: SpreadsheetGridSlotContext<T>) => ReactNode | 内蔵トップバー | 上部バーの差し替え。未指定時は内蔵トップバー(summary chips + フィルター入力。内訳は showTopBarSummary / showTopBarFilter で制御。フィルター入力は enableGlobalFilter=true が前提)。showTopBar=false 時は本指定に関わらず描画されない。 |
renderBottomBar | (ctx: SpreadsheetGridSlotContext<T>) => ReactNode | 内蔵ボトムバー | 下部バーの差し替え。未指定時は内蔵ステータスバー。showBottomBar=false 時は本指定に関わらず描画されない。 |
className | string | — | ルート要素の class。 |
style | CSSProperties | — | ルート要素(.ssg-root)のインライン style。classNames.root の style とマージされ、こちらが後勝ち。 |
classNames | GridClassNames | — | パーツ別の追加スロット。各値は GridSlotProps(string | { className?, style? })で、StyleX の stylex.props(...) の戻り値をそのまま渡せる。全 25 スロット配線済み(一覧と規則は「パーツ別スロット」節)。レンダー毎に新しいオブジェクトを渡してもよい(内容の署名で memo)。基底 class は未レイヤー・特異度 (0,1,0)のため同特異度のクラスは読み込み順で決まる(確実な上書きは連結セレクタか style.layer.css)。 |
getRowClassName | (row: T, rowIndex: number, ctx: RowStyleContext<T>) => GridSlotProps | undefined | — | 行ごとの追加 class(または { className, style })。行コンテナ + 行ヘッダー「#」セル + 各データセルに付与され、Tailwind / StyleX での行ハイライトに使える。style はインラインで付与され、座標 / 寸法はグリッドが後勝ち(返した style は内容比較で memo される)。第 3 引数 ctx は { row, rowIndex, sourceRowIndex, rowKey, isSelected }(「補助型」節参照)。既存の 2 引数関数もそのまま動く(後方互換)。グループ行は対象外。 |
onStateChange | (state: GridState) => void | — | 永続スライス(手動リサイズ幅 / フィルター / ソート)が実際に変化したときに最新 GridState を渡して呼ばれる。保存タイミングの signal(例: localStorage 自動保存)。発火規約は「状態の保存 / 復元」節を参照。 |
onFiltersChange | (filters: GridFilterState) => void | — | フィルター状態(globalText + columnFilters)が実際に変化したときだけ、そのスライスの複製を渡して呼ばれる。onStateChange は列幅 / 列メタでも呼ばれるため、記述子から WHERE を組み立てるなどフィルターだけを追いたい用途向け。規約は onStateChange と同じ(初回非発火 / 同値非発火 / applyState でも発火)。 |
onSortChange | (sort: GridSortState) => void | — | ソート状態が実際に変化したときだけ、その複製を渡して呼ばれる(ORDER BY の組み立てなど)。規約は onFiltersChange と同じ。 |
onScroll | (params: GridScrollEventParams) => void | — | スクロール位置の変化通知(rAF で 1 フレーム 1 回に間引き・縦横どちらの変化でも発火)。params は { top, left, source }(px)。source: 'api' は setScrollPosition / scrollTo* 系由来、'user' はそれ以外。2 グリッドの双方向スクロール同期は source === 'user' のときだけ相手へ反映することでループを止められる。インライン関数可(latest-ref 経由)。 |
enableContextMenu | boolean | false | コンテキストメニュー機能の有効化(マスタースイッチ)。他機能の enable* と同じく既定 OFF。false のあいだは getContextMenuItems を渡しても発火せず、右クリックはブラウザ標準メニューのまま。現状はまだ機能 / UI に改善余地があるため既定 OFF で提供する(利用側で明示 opt-in)。 |
getContextMenuItems | (params: GridContextMenuParams<T>) => GridContextMenuItem[] | — | セル/行の完全カスタムコンテキストメニュー。右クリック時のみ呼ばれ、返した項目でメニューを描画する(ライブラリは固定の既定項目を持たない)。opt-in は enableContextMenu={true} かつ本コールバックの指定の両方。未指定、または [] を返したときはブラウザ標準の右クリックメニューへフォールスルー(空パネルは出さない)。SSRM 未ロード行では開かない。ヘッダー右クリックは列メニュー(enableColumnMenu)が担当し、本メニューはボディ(セル / 行NO ガター)専用。詳細は「コンテキストメニュー」節を参照。 |
onContextMenuOpen | (params: GridContextMenuParams<T>) => void | — | コンテキストメニューが実際に開いた直後の通知(項目が 1 件以上あり表示された場合のみ)。 |
scrollHint | boolean | ScrollHintOptions<T> | —(無効) | スクロール位置インジケーター。スクロール中にスクロールバー脇へ行番号バブル(「行 N / 総行数」+ 任意の列値)と行目盛りルーラーを表示し、スクロールバー帯のホバーで「行 N へ」のジャンプ先プレビューを出す。true は全既定({ bubble: true, ruler: true, scrollbar: true, trigger: 'scroll', minRows: 0 })と同義。minRows で「表示行数がしきい値以上のときだけ有効」のデータ量ゲートも掛けられる。表示は総行数とスクロール位置のみで駆動されるため clientSide / SSRM の全構成で動作。オーバーレイは pointer-events: none で既存操作へ一切干渉しない。詳細は「スクロール位置インジケーター」節とガイドを参照。 |
detailRow | DetailRowOptions<T> | —(無効) | 展開行(Master/Detail)。マスター行の直下に、行順(view index)を変えずに全幅の帯を差し込み、その中(カード)へ render の返す任意の React 要素(自前のサブグリッド / フォーム / 集計パネル等)を描画する。指定時のみ有効で、未指定なら既存の描画・状態・イベント経路は一切変わらない。{ render, height?, isExpandable?, showToggleColumn?, className? }。clientSide / serverSide の両方で使える(serverSide の制約は節内)。詳細は「展開行(Master/Detail)」節とガイドを参照。 |
onExpandedDetailRowKeysChange | (keys: GridRowKey[]) => void | — | 展開中の展開行のマスター行キー集合が変化したときに呼ばれる(開閉の永続化・外部同期用)。初回マウントでは発火しない。インライン関数可(latest-ref 経由)。 |
labelRow | LabelRowOptions<T> | —(無効) | ラベル行(見出し / 区切り行)。rows の中で isLabelRow(row) が true の行を「行数に数えない見出し」として、3 ペインを跨ぐ全幅の帯で描画する(中身は render で任意の React 要素に差し替え可。横スクロールしても左端に留まる)。編集 / 選択 / コピー / エクスポートの既定対象外で、行番号も消費しない。ソート / フィルターは既定でラベル行から次のラベル行までの区間(セクション)に閉じる(sortMode)。sticky: true で現在セクションのラベルを列ヘッダー直下に固定。{ isLabelRow, getLabel, render?, height?, className?, sticky?, sortMode?, keepEmptySections?, exportText? }。行グルーピング(rowGroup)とは別機能で併用不可。詳細は「ラベル行(見出し / 区切り行)」節とガイド。 |
totalRow | boolean | TotalRowOptions<T> | —(無効) | 合計行。表の下端に合計行を固定表示する(縦スクロールしても動かず、横スクロールと左右の固定列には付いていく)。列ごとの集計は GridColumn.aggFunc(行グルーピングと共通)で決め、フィルター後のデータ行で計算し直す(ラベル行は数えず、グルーピング中は折りたたんだ行も数える)。合計以外の組み込み集計は値の前に種類(平均 / 最小 / 最大 / 個数)を出す。表示専用(選択 / 編集 / 貼り付け / コピーの対象外)。true で既定値、オブジェクトで { label?, values?, renderCell?, height?, className? }。serverSide は計算せず values の値だけを出す。エクスポートは includeTotalRow: true のときだけ末尾に含める。詳細は「合計行」節。 |
fillHandle | boolean | FillHandleOptions<T> | —(無効) | フィルハンドル。選択範囲(無ければアクティブセル)の右下に小さな四角を出し、引いた先のセルを連続データ(1, 2, 3 … / 日付 / 曜日・月の名前 / P-001 のような末尾の数)かセルのコピーで埋める(Excel のオートフィル)。ドラッグ中は埋める範囲を点線で、いちばん遠いセルへ入る値をポインタの近くに出し、Ctrl(Mac は ⌘)で埋め方を入れ替え、Escape でやめる。離したあとは右下のボタンで「連続データ / セルのコピー」を選び直せる(元に戻す 1 回で埋める前へ)。ハンドルのダブルクリックで隣の列に値が続くところまで下へ埋める。書き込みは貼り付けと同じ規則(編集できないセル・validate の reject は飛ばす / undo の対象)。readOnly や書き込み先が無い(onRowsChange も dataSource.updateRows も無い)ときは出さない。true で既定、オブジェクトで { getFillValue? }。詳細は「フィルハンドル」節。 |
enableRowDrag | boolean | false | 行ドラッグ並び替え。先頭にドラッグハンドル列(幅 28px・タイトル無しの合成列。左固定列があれば左固定側)を挿入し、ハンドル(⋮⋮)を掴んで行を上下へ動かせる。確定時は onRowsChange へ移動後の新配列を渡し(履歴ラッパ経由 = undo/redo 対象)、続けて onRowMove を呼ぶ。clientSide(rows + onRowsChange)専用で、dataSource(serverSide)/ 行グルーピング中 / onRowsChange 未指定ではハンドル列を出さない。ソート / フィルター適用中はハンドルを淡色 + 理由ツールチップにして操作を無効化する(列は残る)。詳細は「行ドラッグ並び替え」節とガイド。 |
isRowDraggable | (row: T, ctx: RowDragContext) => boolean | 全行可 | 行ごとのドラッグ可否。false の行にはハンドルを描画しない。ctx = { rowKey, sourceRowIndex }。 |
onRowMove | (params: RowMoveParams<T>) => void | — | 行移動の確定後(onRowsChange の直後)に呼ばれる。params = { rowKey, fromIndex, toIndex, rows }(index は元 rows 配列基準、rows は onRowsChange と同じ新配列参照)。ハンドルの moveRow() による移動でも呼ばれる。 |
バーの表示制御(top / bottom)
トップ / ボトムバーは次の優先順で解決される。
showTopBar/showBottomBar(マスタースイッチ):falseならそのバーは一切描画されない(render*/enable*より優先)。renderTopBar/renderBottomBar(カスタム): 指定時はそのまま描画。トップバーの内訳 props(showTopBarSummary/showTopBarFilter)はカスタム側が中身を決めるため関与しない。- 既定バー:
render*未指定時のフォールバック。
既定トップバーは summary chips(左) と グローバルフィルター入力(右) の 2 パートからなり、独立に出し分けできる。
| やりたいこと | 設定 |
|---|---|
| バーごと消す | showTopBar={false} |
| summary だけ(フィルター入力なし) | showTopBarFilter={false} |
| フィルター入力だけ(summary なし) | showTopBarSummary={false} |
| トップの Rows/Columns 件数だけ消す | showTopBarCounts={false} |
| ボトムの Rows/Columns 件数だけ消す | showBottomBarCounts={false} |
| 選択範囲の集計(平均 / データの個数 / 合計)を消す | showSelectionAggregates={false} |
| 適用中の列フィルターをチップで常時表示 | showFilterChipBar(既定 OFF) |
| フィルター機能ごと無効 + summary は残す | enableGlobalFilter={false} |
| 完全に自前のバー | renderTopBar={(ctx) => …} |
showTopBarSummary と showTopBarFilter(実効は showTopBarFilter && enableGlobalFilter)がともに false の場合、既定トップバーは描画されない(空バーを出さない)。
ボトムバーは Rows / Columns 件数を showBottomBarCounts、選択範囲の集計を showSelectionAggregates で出し分けできる(右側の Active / Selection / 選択統計 / Cols は対象外)。それ以外の内訳を変えたい場合は renderBottomBar を使う。
選択範囲の集計(showSelectionAggregates)
2 セル以上を選ぶと、既定ボトムバーの右側に Excel のステータスバーと同じ 平均 / データの個数 / 合計 が出る(既定 ON)。
- 数えるのは選択範囲のうち表示中のデータ行 × データ列のセル。フィルターで隠れた行・グループ行・ラベル行・合成列(行ドラッグ / 展開トグル / 自動グループ列)は対象外。
- 合計 / 平均は数値と数値の文字列だけ(グループ集計の
aggFuncと同じ判定。空白だけの文字列・boolean・Dateは数えない)。データの個数は空でない(null/undefined/ 空文字でない)セルの数。数値が 1 つも無いときはデータの個数だけ、空のセルだけのときは何も出さない。 - 値は
getValueがあればその値(表示用のvalueFormatterは通さない)。表示は小数 4 桁まで・桁区切りあり。 - 走査するセルが 50,000 を超える選択(全選択など)は主スレッドを塞がないよう時間分割で計算し、その間は「集計中… N%」。編集で値が変わると計算し直す(そのあいだは前の結果を出したまま。行が頻繁に入れ替わっても、計算が終わるたびに新しい値になる)。
- 既定ボトムバーでは右グループ(Active / Selection …)の隣の空きに置き、バーの高さや他の項目の位置は変えない(入りきらないときは端から切れ、ポインタを重ねると全文)。
- 出す先(ボトムバー、または自前の
renderTopBar)が無いときは計算しない。 - SSRM では読み込み済みの行だけで計算し、「読み込み済みの行のみ」と添える。
- 自前のバー(
renderTopBar/renderBottomBar)ではslotContext.selectionAggregates(GridSelectionAggregates。最小 / 最大も入る)で同じ値を使える。
renderBottomBar={(ctx) => {
const agg = ctx.selectionAggregates;
return (
<div className="my-statusbar">
{agg?.status === 'computing' && <span>集計中…</span>}
{agg?.status === 'ready' && agg.sum !== undefined && (
<span>合計 {agg.sum} / 最小 {agg.min} / 最大 {agg.max}</span>
)}
</div>
);
}}フィルハンドル(fillHandle)
fillHandle を指定すると、選択範囲(無ければアクティブセル)の右下に小さな四角(フィルハンドル)が出ます。引くと Excel のオートフィルと同じく、元の値の続き(連続データ)か元の値の繰り返し(セルのコピー)で埋めます。
<SpreadsheetGrid rows={rows} onRowsChange={setRows} columns={columns} fillHandle />
// 埋める値を差し替える(例: 「数量」列は 10 の倍数に丸める)
<SpreadsheetGrid
rows={rows}
onRowsChange={setRows}
columns={columns}
fillHandle={{
getFillValue: ({ column, defaultValue }) =>
column.key === 'qty' && typeof defaultValue === 'number' ? Math.round(defaultValue / 10) * 10 : defaultValue,
}}
/>FillHandleOptions<T>(fillHandle にオブジェクトで渡したとき):
| Name | Type | Default | Description |
|---|---|---|---|
getFillValue | (params: GridFillValueParams<T>) => unknown | — | 埋めるセルごとに書く値を決める。返した値をそのまま書く(既定のままにするなら params.defaultValue を返す)。編集できないセルでは呼ばれない。ドラッグ中のツールチップ(いちばん遠いセルの値)でも呼ぶため、副作用の無い関数にする。返した値も validate を通り、reject なら書かない。 |
GridFillValueParams<T>(getFillValue の引数):
| フィールド | 型 | 説明 |
|---|---|---|
column | GridColumn<T> | 埋めるセルの列。 |
row | T | 埋めるセルの行(書き込み前)。 |
rowIndex | number | 埋めるセルのビュー行 index。 |
sourceValues | readonly unknown[] | 元の値。埋める向きの順で、最後の要素が埋めるセルに一番近い(上 / 左へ埋めるときは下 / 右から並ぶ)。 |
index | number | 元の範囲の直後を 0 とする位置(ラベル行 / グループ行は数えない)。 |
direction | GridFillDirection | 埋める向き('down' | 'up' | 'right' | 'left')。 |
kind | GridFillKind | この列(上下へ埋めるとき)/ 行(左右へ埋めるとき)の埋め方('series' | 'copy')。 |
defaultValue | unknown | 既定で書く値。 |
- 操作: ハンドルを押して上下か左右へ引きます(元の範囲から遠く離れた向きを採る)。点線が埋める範囲、ポインタの近くの値がいちばん遠いセルへ入る値です。表の端へ寄せると自動でスクロールします。
Ctrl(Mac は⌘)を押しているあいだは埋め方が入れ替わり、Escapeでやめます(ドラッグ中のキーはグリッドへ流しません)。離すと元の範囲 + 埋めた範囲が選ばれます(アクティブセルはそのまま)。 - 埋め方の既定: 列ごと(上下へ埋めるとき)/ 行ごと(左右へ埋めるとき)に決めます。続き方の分かる値は連続データ、数値 1 つ・数字だけの文字列 1 つ・続き方の無い値はコピーです。
Ctrl/⌘はこれを入れ替えます(数値 1 つなら 1 ずつ増やす)。上 / 左へ引くと逆向きに続きます(1, 2, 3の上は0, -1 …。元の値が 1 つなら 1 ずつ戻り、日付は前の日)。 - 連続データの規則:
- 数値: 2 つ以上なら最小二乗の直線で続けます(等差ならその差。
1, 3→5, 7)。浮動小数の誤差は丸めます。数値の文字列('1.5')も同じ規則で文字列を返します。 - 日付:
Dateと'YYYY-MM-DD'/'YYYY/MM/DD'の文字列(形式を保つ)。日が同じで月の差が一定なら月単位(2026-01-15, 2026-02-15→2026-03-15。その月に無い日は月末に丸める)、それ以外は日単位です(1 つだけなら 1 日ずつ)。 - 曜日 / 月の名前: 日本語(
月/月曜/月曜日、1月)と英語の短縮・正式(Mon/Monday、Jan/January)。英語は大文字小文字を最後の値に合わせます。 - 末尾が数字の文字列(
P-001、No.9): 前置きが同じとき末尾の数を進めます。0 埋めは最後の値が 0 で始まるときだけ桁数を保ちます。 - 空のセルを含む元の値・型の混在・真偽値は常にコピーです(空のセルから引くと埋める範囲を空にする。Excel と同じ)。
- 数値: 2 つ以上なら最小二乗の直線で続けます(等差ならその差。
- 埋めたあとのボタン: 埋めた範囲の右下に出ます。押すとメニュー(連続データ / セルのコピー)を開き、選び直すと埋める前の値から作り直します(履歴は 1 つのまま = 元に戻す 1 回で埋める前へ)。列 / 行ごとに埋め方が混ざるときに選ぶと、すべての列 / 行をその埋め方にします(続き方の無い値はコピーのまま)。連続データにできる値が無いときは「連続データ」を選べません。右下に余地が無い(表の下端・合計行・見えている範囲の下端にかかる)ときは角の上に出します。選択を変える・セルの値が外から変わる・列が変わる・ソート / フィルター / グループの開閉が変わる・編集を始めると消えます。
- ダブルクリック: ハンドル(見えている四角)のダブルクリックで、左隣(値が無ければ右隣)の列に値が続く行まで下へ埋めます。埋める列にすでに値があるセル・ラベル行 / グループ行・未読み込み行の手前で止まり、既存の値は上書きしません。
- 書き込みの規則: 貼り付けと同じです。
readOnly/ 列のeditable: false/canEditCellで編集できないセルは飛ばし、validateで reject されたセルも書きません(markは書いてエラー表示)。値は型を保って書きます(貼り付けのような文字列からの変換はしない)。clientSide はonRowsChangeに 1 回で渡し、undo / redo の対象です。 - 飛ばすもの: ラベル行 / グループ行(位置にも数えない)/ 合成列(行ドラッグ / 展開トグル / 自動グループ列)/ 合計行。フィルター / ソート中は表示されている順に埋め、隠れた行には書きません。表の末尾より先へ行は増やしません。
- 出さないとき:
readOnly、書き込み先が無い(clientSide でonRowsChangeが無い / serverSide でdataSource.updateRowsが無い)、編集中、範囲を選んでいる最中、行 / 列の選択中、選択範囲が合成列だけ / ラベル行・グループ行だけ(埋める元の値が無い)のとき。 - serverSide(
dataSource):dataSource.updateRowsがあるときだけ使えます(楽観更新・失敗時のロールバックは編集と同じ)。読み込まれていない行には書かず、元の範囲に読み込まれていない行がある列 / 行は埋めません。undo / redo はありません(serverSide 共通)。ボタンでの選び直しは、行の並びが埋めたときと同じあいだだけできます。選び直しはそのときの行の上に書くため、validateの reject などで選び直しでは書かないセルには、最初に埋めた値が残ります。 - キーボード / 支援技術: ハンドルはポインタ操作の部品で、読み上げの対象外です(
aria-hidden)。キーボードではコピー / 貼り付けで同じことができます。埋めたあとのボタンはaria-haspopup="menu"、メニューはrole="menu"+role="menuitemradio"(↑ / ↓ で移動、Escape/Tabで閉じる)。 - 見た目: ハンドル
.ssg-fill-handle、点線.ssg-fill-preview、ドラッグ中のルート.ssg-root--filling、ボタン.ssg-fill-options、メニュー.ssg-menu-panel.ssg-fill-menu、値のツールチップ.ssg-tooltip.ssg-fill-tooltip。トークン--ssg-fill-line(点線の色。既定は文字色)/--ssg-fill-handle-ring(ハンドルの縁取り。既定はセルの地色)。ハンドルの色は--ssg-accent。
セル操作の通知(onCellClick / onCellDoubleClick / onActiveCellChange)
データセルのクリック / ダブルクリック / アクティブセルの変化を受け取る通知口。3 つとも省略可で、指定しなければ従来どおり(グリッドの既定の動作は変わらない)。
- 渡るもの:
GridCellRef<T> = { row, rowKey, rowIndex, sourceRowIndex, column, columnKey, colIndex, value }。行は index ではなく行データrowとrowKeyを主に使う(rowIndexはフィルター / ソート後の表示位置 =handle.selectCellと同じ空間、sourceRowIndexは元rowsの index、colIndexは論理列 index)。valueはセルの生値(getValue指定列はその戻り値。valueFormatter適用前)。クリック / ダブルクリックにはevent(DOM 標準のMouseEvent。React の合成イベントではない)が付く。修飾キー(ctrlKey/shiftKey/metaKey/altKey)はここから読む。 - 対象: データ行 × 利用側の列のセルだけ。列ヘッダー / 行番号「#」/ グループ行 / ラベル行 / 展開行の帯 / SSRM の未ロード行(スケルトン)/ 合成列(展開トグル列・行ドラッグハンドル列・自動グループ列)ではクリック / ダブルクリックを通知せず、アクティブセルがこれらへ移ったときの
onActiveCellChangeはnull。 - クリックの判定: ブラウザの
clickイベントに従う。左ボタンのみで、押したセルと離したセルが違う(範囲選択のドラッグ)ときは呼ばれない。セル内に置いた自前の要素のクリックもそのセルのクリックとして届くため、セルの通知に含めたくない要素は自分でevent.stopPropagation()する。 - 順番: マウスで別のセルをクリックすると
onActiveCellChange(pointerdown でアクティブセルが移った分)→onCellClickの順。ダブルクリックはブラウザの仕様どおりonCellClick× 2 →onCellDoubleClickの順。 - ダブルクリックの既定の動作: 編集可能セルなら編集開始、checkbox 列・読み取り専用セルは何もしない。
params.preventDefault()でこれを止められる(F2/Enter/ 印字キーでの編集開始には影響しない)。
// 読み取り専用の一覧: 行をクリックしたら上の入力フォームへ写す。
<SpreadsheetGrid<Row>
rows={rows}
columns={columns}
readOnly
rowKeyGetter={(row) => row.id}
onCellClick={({ row }) => form.setValues(row)}
/>
// 選択モーダル: ダブルクリックした行で確定して閉じる。
<SpreadsheetGrid<Row> rows={rows} columns={columns} readOnly onCellDoubleClick={({ row }) => onConfirm(row)} />
// 「件名」セルだけダブルクリックでモーダルを開き、他のセルは従来どおり編集を始める。
<SpreadsheetGrid<Row>
rows={rows}
columns={columns}
onRowsChange={setRows}
onCellDoubleClick={(params) => {
if (params.columnKey === 'subject') {
params.preventDefault();
openSubjectPicker(params.rowKey);
}
}}
/>
// キー操作での移動も含めて、アクティブセルの行を詳細パネルへ出す。
<SpreadsheetGrid<Row> rows={rows} columns={columns} onActiveCellChange={(cell) => setDetail(cell?.row ?? null)} />コンテキストメニュー(enableContextMenu / getContextMenuItems)
セル / 行の右クリックで開く完全カスタムメニュー。既定は OFF(enableContextMenu={false})で、他機能の enable* と同じくマスタースイッチで有効化する(現状はまだ機能 / UI に改善余地があるため既定 OFF)。ライブラリは固定項目を一切持たず、getContextMenuItems が返した項目配列だけを描画する。用意されるのは「窓」(パネル外装 + 右クリック座標配置 + 開閉 / Escape / 外側クリック / スクロール close)だけで、中身(ラベル / アイコン / onSelect)はすべて利用側が渡す。列メニューと同じ .ssg-menu-panel / .ssg-menu-item 外装を再利用する。
opt-in と標準メニューへのフォールスルー
enableContextMenu未設定 /false(既定)→ ブラウザ標準の右クリックメニュー(getContextMenuItemsを渡していても発火しない)。enableContextMenu={true}かつgetContextMenuItemsが項目を返した → その項目が並んだメニューを右クリック座標に表示。enableContextMenu={true}でもgetContextMenuItems未指定 /[]を返した(対象で項目なし)→ ブラウザ標準メニュー(空パネルは浮かせない)。
対象と挙動
- ヘッダー右クリックは対象外(列メニュー
enableColumnMenuが担当)。本メニューはボディの データセルと 行NO ガター専用。 - SSRM 未ロード行(まだ取得できていない行)の上では開かない(標準メニューになる)。
- uncontrolled のみ。右クリックしてもセル選択は変化しない(対象セル/行の情報は
paramsで受け取る)。 - close 契機: 項目選択 / 外側クリック / Escape / スクロール。項目間のキーボード移動(矢印キー)は持たない。
GridContextMenuParams<T>(コールバック引数)
| フィールド | 型 | 説明 |
|---|---|---|
target | GridContextMenuTarget<T> | 右クリック対象。{ type:'cell', rowIndex, colIndex, rowKey, row, column, value } か { type:'rowHeader', rowIndex, rowKey, row }(行NO ガター)。rowIndex はビュー行 index、colIndex は論理列 index(視覚順 左→中央→右 = handle.selectCell と同一空間)。 |
clientX / clientY | number | 右クリックのビューポート座標(メニュー配置に使用済み。分岐の判断材料にも)。 |
selection | GridSelection | 現在のセル範囲選択。チェックボックス行選択は handle.getRowSelection() で別途取得。 |
activeCell | CellCoord | null | 現在のアクティブセル。 |
isTargetSelected | boolean | 対象(cell はそのセル / rowHeader はその行)が selection に含まれるか。「選択範囲への操作」か「単一対象への操作」かを分岐する簡便値。 |
GridContextMenuItem(判別共用体)
- action(既定):
{ kind?: 'action'; id?; label: ReactNode; icon?: ReactNode; disabled?; danger?; onSelect: () => void }— クリックでonSelect実行後に自動で閉じる。icon省略時も左 14px 枠が空スペーサになりラベル左端が揃う。dangerは削除など危険操作の赤系強調(Mantine のcolor="red"相当)。 - label(見出し):
{ kind: 'label'; id?; label: ReactNode }— 非インタラクティブなセクション見出し(Mantine のMenu.Label相当)。項目群のグルーピング表示に使う。 - separator:
{ kind: 'separator'; id? }— 区切り線(Mantine のMenu.Divider相当)。 - custom(レンダラ / エスケープハッチ):
{ kind: 'custom'; id?; render: (ctx: { close: () => void }) => ReactNode }— パネル内に任意 JSX を差し込む。close()で任意タイミングに閉じられる。
id は React key 用(省略時は配列 index)。項目は非ジェネリック: 行データは getContextMenuItems 内で params を通じてクロージャに閉じ込める。
例
<SpreadsheetGrid
// …
enableContextMenu // 既定 false。機能を使うにはこのマスタースイッチが必要
getContextMenuItems={(params) => {
const items: GridContextMenuItem[] = [];
items.push({ kind: 'label', label: '操作' }); // セクション見出し
if (params.target.type === 'cell') {
const value = params.target.value; // narrowing はローカルへ退避してから onSelect で使う
items.push({
label: '値をコピー',
icon: '📋',
onSelect: () => navigator.clipboard?.writeText(String(value ?? '')),
});
}
items.push({ kind: 'separator' });
items.push({
label: 'この行を削除',
danger: true, // 赤系強調
onSelect: () => deleteRow(params.target.rowKey),
});
items.push({ kind: 'separator' });
items.push({
label: '選択範囲を CSV 出力',
disabled: !params.isTargetSelected,
onSelect: () => gridRef.current?.downloadCsv('selection.csv', { scope: 'selection' }),
});
items.push({
kind: 'custom',
render: ({ close }) => (
<div style={{ padding: '6px 8px' }}>
行 {params.target.rowIndex}
<button type="button" onClick={close}>閉じる</button>
</div>
),
});
return items; // [] を返すと標準メニューへフォールスルー
}}
onContextMenuOpen={(params) => console.log('opened at', params.target)}
/>レシピ: 右クリックからフィルター管理パネルを開く
適用中フィルターの一覧 / 編集 / クリアを行うフィルター管理パネル(列メニュー「フィルターを管理…」と同じもの)は、ハンドルの openFilterManager() で任意の場所から開ける。コンテキストメニューに載せる場合:
const gridRef = useRef<SpreadsheetGridHandle<Row>>(null);
<SpreadsheetGrid
ref={gridRef}
enableContextMenu
getContextMenuItems={() => [
{
label: 'フィルターを管理…',
onSelect: () => gridRef.current?.openFilterManager(),
},
]}
/>;スクロール位置インジケーター(scrollHint)
大量行(特に 100 万行級)ではスクロールバー 1px の移動が数百〜数千行に相当し、移動中に「今どの行にいるか」を見失う。scrollHint は次の 3 つのオーバーレイでこれに答える(既定は完全無効。opt-in)。
- 行番号バブル(
bubble): スクロール中、スクロールバー脇(サム位置)に「行 N / 総行数」を表示。hintColumn/renderHintで任意の列値を添えられる。 - 行目盛りルーラー(
ruler): ボディ右端に行数スケール(切りのよい刻み。刻みが 1 万の倍数のときは「10万」等の圧縮表記へ全目盛り統一)。スクロール中とスクロールバー帯ホバー中のみフェード表示。 - ジャンプ先プレビュー(
rulerに含む): スクロールバー帯(右端)にポインタを置くと、スクロールバーのクリック / ドラッグと同じサム中心写像で「行 N へ」の点線 + ラベルを表示。 - カスタムスクロールバー(
scrollbar): ヘッダー下から始まる専用ガターに常時表示のトラック + 最小 30px サムを自前描画(ドラッグ / クリックジャンプ / ホイール対応)。macOS のオーバーレイスクロールバーは自動で消え、大量行ではサムが極小になり「掴む場所」を見失うための置き換え。有効時はネイティブ縦スクロールバーを非表示化する(Chromium / WebKit。Firefox はネイティブ縦バーが残るがガター操作は有効)。
ScrollHintOptions<T>:
| 項目 | 型 | 既定 | 説明 |
|---|---|---|---|
bubble | boolean | true | 行番号バブルの表示。 |
ruler | boolean | true | ルーラー + ジャンプ先プレビューの表示。 |
scrollbar | boolean | true | カスタム縦スクロールバー(専用ガター・常時表示)。false でネイティブバーのまま(バブル等は疑似サム位置に表示)。 |
trigger | 'scroll' | 'hover' | 'always' | 'scroll' | バブル / ルーラーの表示トリガー(スクロールバー自体は常時表示)。'scroll' = スクロール中のみ(停止約 1 秒でフェードアウト)/ 'hover' = グリッドホバー中 + スクロール中 / 'always' = 常時。 |
minRows | number | 0 | データ量ゲート。表示行数(フィルター / グルーピング適用後のビュー行数。SSRM はサーバー総行数)がこの値未満のあいだ、scrollHint 全体(カスタムスクロールバー含む)を自動 OFF にしてネイティブスクロールバー表示のままにする。0 = 常時有効(従来挙動)。 |
hintColumn | string | — | 行番号に添えて表示する列 key(= 行オブジェクトのフィールド名)。 |
renderHint | ({ rowIndex, rowData }) => ReactNode | — | 表示内容の完全カスタム(hintColumn より優先)。null / undefined を返すと行番号のみの既定表示。 |
// 簡易: 行番号 + 品番
<SpreadsheetGrid scrollHint={{ hintColumn: 'partNo' }} />
// 完全カスタム: 品番 — 品名
<SpreadsheetGrid
scrollHint={{
trigger: 'scroll',
renderHint: ({ rowData }) =>
rowData ? `${rowData.partNo} — ${rowData.partName}` : null,
}}
/>データ量ゲート(minRows): 小規模データではバブル / ルーラー / カスタムスクロールバーがノイズになるため、minRows を指定すると「表示行数がしきい値以上のときだけ出る」挙動にできる(例: scrollHint={{ minRows: 100 }})。判定はバブルの「/ 総行数」と同じ行数(フィルター / グルーピング適用後のビュー行数。SSRM はサーバー総行数)で、フィルターで絞り込んでしきい値を割ればその間は自動 OFF になる。既定は 0(常時有効)。注意: scrollbar 有効時はしきい値またぎでガター余白が付け外しされるため、行数が変動する画面では僅かなレイアウトシフトが起きる(気になる場合は scrollbar: false と併用する)。
SSRM / グルーピングでの挙動: バブル / プレビューの対象行が SSRM の未ロード行(またはグルーピングのグループ行)の場合、rowData は undefined になり、hintColumn 指定時は自動的に行番号のみへフォールバックする(renderHint には undefined がそのまま渡るので利用側で分岐する)。総行数とスクロール位置だけで駆動されるため、インジケーター自体は SSRM を含む全構成で動作する。
実装ノート: 行番号は仮想化の縦ジオメトリ(1M 行の pixel scaling / auto-height の prefix-sum)と同一の写像で解決するため常に正確。バブル / ルーラー / プレビューのオーバーレイは pointer-events: none で、スクロール・セル操作へ一切干渉しない。カスタムスクロールバーのガターのみポインタ操作を受けるが、コンテンツのスクロール自体はネイティブのまま(ガターは scrollTop を書くだけの鏡映し)。全行が viewport に収まりスクロール不能のときは何も表示しない。配色はテーマトークン(--ssg-pill-* / --ssg-scrollhint-*)で light / dark 両対応。
展開行(Master/Detail)(detailRow)
detailRow prop を渡すと、各行を「展開」してマスター行の直下に消費側 UI(カード)を差し込めます(AG Grid の Master/Detail 相当)。行グルーピングが「ライブラリがグループ行を作る」機能なのに対し、展開行は「行の下に何を出すかを利用側が全面的に決める」動線です。両者は独立で併用もできます。
<SpreadsheetGrid
rows={rows}
columns={columns}
rowKeyGetter={(row) => row.id}
detailRow={{
height: 220,
isExpandable: (row) => row.lines.length > 0,
render: ({ row, rowKey, collapse }) => (
<OrderLinesPanel order={row} onClose={collapse} />
),
}}
onExpandedDetailRowKeysChange={(keys) => save(keys)}
/>DetailRowOptions<T>:
| Name | Type | Default | Description |
|---|---|---|---|
render | (ctx: DetailRowRenderContext<T>) => ReactNode | (required) | 展開行の中身。ctx = { row, rowKey, rowIndex, sourceRowIndex, collapse }(rowIndex はビュー行 index、collapse() はその展開行を閉じる)。帯の内側のカード要素(.ssg-detail-card、data-ssg-detail 属性つき)に描画される。 |
height | number | 200 | 帯の高さ(px)。固定高で、中身が超えるとカード内でスクロールする(auto 高は非対応)。 |
isExpandable | (row: T, ctx: { rowKey; sourceRowIndex }) => boolean | 全行展開可 | 行ごとの展開可否。false の行はトグルが描画されず、命令的 API / ctx.detail.toggle() からの展開も no-op。 |
showToggleColumn | boolean | true | 専用トグル列(幅 28px・タイトル無し、行ヘッダーの右隣 = 先頭列。左固定列があるときは左固定側)を自動挿入する。false にすると列は挿入されず、任意の列の renderCell から ctx.detail.toggle() でトグルを自前配置する(下記)。 |
className | GridSlotProps | — | カード要素へ追加する class(または { className, style }。classNames.detailCard に加えて付与)。 |
- 表示: 帯はマスター行の直下・グリッド全幅(3 ペインとも背景を描画)で、カードは中央ペインに
position: stickyで置かれ、横スクロールしてもビューポート左端(左固定ペインの右隣)に留まります。幅は中央ペインの可視幅です。展開しても行の順序・view index は変わらず(第 3 の行種は作らない)、後続行が帯の高さぶん下がります。展開時にスクロール位置は動かしません。 - 状態: 展開状態は
rowKeyGetterの行キーで保持されるため、ソート / フィルター / 行の追加削除を跨いで同じ行に追従します(フィルターで除外中の行は帯が出ず、復帰すると再表示)。UI 状態で undo/redo・getState()の対象外。永続化はonExpandedDetailRowKeysChange+setDetailRowExpanded()で行います。 - マウント: カードは仮想化の描画窓に載っている間だけマウントされます(スクロールアウトでアンマウント、戻ると再マウント)。カード内で保持したい状態は消費側で
rowKeyをキーに持ってください。 - イベント境界: カード内のキーボード / クリップボード / 右クリック / ダブルクリック / ドラッグ開始はグリッド本体へ伝播しません(カード内の input で矢印キーを押してもアクティブセルは動かない)。カード内にフォーカスがある間は、グリッド側のフォーカス復帰(編集確定・popover close 後)がフォーカスを奪いません。カード内に別の
SpreadsheetGridをネストしても、外側の自動高さ実測 / 列ヘッダー検索は内側のセルを対象にしません。 renderCellからの操作(ctx.detail):detailRow有効時、CellRenderContextにdetail: { expanded, expandable, toggle(), setExpanded(bool) }が入ります(無効時はundefined)。showToggleColumn: falseと組み合わせて、商品名セルの横などにトグルを自前配置できます。- 選択 / アクティブセル: 帯はセルではないため選択・アクティブセルの対象外です。帯を跨ぐ範囲選択のハイライトは帯を避けて分割描画されます。
- serverSide(
dataSource): 使えますが、(1) クエリ(フィルター / ソート / グローバル)が変わると展開状態はすべて閉じます(結果セットが総入れ替えされ、未ロード行のキーを走査できないため)。(2) 未ロード行の帯は表示されません。 - 上限: 帯は auto-height 行と同じ可変行高ジオメトリで描画するため、
rows × rowHeight + 展開中の帯の合計が 15,000,000px(36px 行で約 41 万行)を超える構成では帯を描画しません(開発時警告。展開状態は保持され、行数を絞ると表示されます)。 - 行グルーピング併用: 展開できるのは leaf 行のみ(グループ行は対象外)。
ラベル行(見出し / 区切り行)(labelRow)
labelRow prop を渡すと、rows の中の特定の行を「見出し(区切り)行」として扱えます。Excel のシートに置く節見出しの行と同じもので、データ行の間に置いたその位置に全幅の帯として描画され、行数には数えません。行の型 T も rows / onRowsChange の型も変わらないため、Excel / CSV 取り込みデータの形そのままで使えます(ガイド)。
行グルーピングとの違い
ラベル行(labelRow) | 行グルーピング(rowGroup) | |
|---|---|---|
| 何をするか | rows の中に置いた位置の見出し行を帯で描く | 列の値で行をまとめ直し、グループ行を作る |
| 並び順 | rows の並びそのまま(ソートはセクション内に閉じる) | グループ単位に並び替わる |
| 集計 / ツリー / 開閉 | 無し | あり(aggFunc / 自動グループ列 / 開閉) |
| データ源 | 行データそのもの(isLabelRow で識別) | 列定義(rowGroup: true) |
| 併用 | 不可(rowGroup 有効中はラベル行を表示しない。開発時警告) |
type Row = { id: string; kind?: 'label'; code: string; name: string; qty: number };
<SpreadsheetGrid
rows={rows} // ラベル行はデータ行と同じ配列に混在させる
columns={columns}
rowKeyGetter={(row) => row.id}
labelRow={{
isLabelRow: (row) => row.kind === 'label',
getLabel: (row) => row.name,
sticky: true, // 縦スクロール中も現在セクションの見出しをヘッダー直下に固定
render: ({ label, sectionRowCount }) => (
<>
<strong>{label}</strong>
<span className="text-gray-500">{sectionRowCount} 件</span>
</>
),
}}
/>LabelRowOptions<T>:
| Name | Type | Default | Description |
|---|---|---|---|
isLabelRow | (row: T, sourceIndex: number) => boolean | (required) | ラベル行の識別。rows / 本関数が変わったときに全行を 1 パス評価するため純粋・軽量であること。 |
getLabel | (row: T) => string | (required) | 表示文字列。既定描画(render 未指定時)/ エクスポート / aria-label に使う。 |
render | (ctx: LabelRowRenderContext<T>) => ReactNode | — | 中身の React 描画(装飾)。ctx = { row, rowKey, rowIndex, sourceRowIndex, label, sectionRowCount }(sectionRowCount はフィルター後のセクション内データ行数。serverSide では undefined)。 |
height | number | ((row: T) => number) | rowHeight | ラベル行の高さ(px)。行ごとに変えるときは関数。 |
className | GridSlotProps | ((row: T) => GridSlotProps | undefined) | — | 行要素へ追加する class(または { className, style })。classNames.labelRow に加えて付与。 |
sticky | boolean | false | 縦スクロール中、現在セクションのラベル行を列ヘッダー直下に固定する。次のラベル行が到達すると押し上げられて交代する(clientSide のみ)。 |
sortMode | 'section' | 'follow' | 'hide' | 'section' | ソート / フィルター適用時の扱い(下記)。 |
keepEmptySections | boolean | false | フィルターで中身が 0 件になったセクションのラベル行を残す。 |
exportText | (row: T) => string | Array<string | number | null | undefined> | getLabel を先頭列へ | エクスポート(includeLabelRows: true)時の出力値。文字列は先頭列(他列は空)、配列は列順にそのまま。 |
- セクション: ラベル行から次のラベル行の直前までを 1 区間とみなします。最初のラベル行より前の行は「ラベル無しの区間」です。
- ソート / フィルター(
sortMode):'section'(既定)= 並べ替えは各セクションの中だけで行い、ラベル行の位置は動かない。フィルターで 0 件になったセクションはラベルごと消える(keepEmptySectionsで残せる)。'follow'= 全体を並べ替え、ラベル行は「元のセクションの先頭に来るデータ行」の直前に付いて移動する(元から中身の無いセクションのラベルは、続くセクションのラベルの直前に並ぶ。消えるのはフィルターで 0 件になったときだけで、keepEmptySectionsで残せる)。'hide'= ソート / フィルター中はラベル行を出さない。ソートもフィルターも無いときは 3 モードともrowsの並びそのものです。 - 表示: 帯は 3 ペインとも描き、中身(
getLabelの文字列 /renderの要素)は中央ペインにposition: stickyで置かれ、横スクロールしてもビューポート左端(左固定ペインの右隣)に留まります(展開行カードと同じ機構)。行ヘッダー「#」は空欄で、データ行の行番号はラベル行を飛ばした通し番号になります。既定表示は左のアクセント線 + 太字(トークン--ssg-label-row-bg/--ssg-label-row-text/--ssg-label-row-accent)。 - 操作の対象外: ラベル行はセルを持たないため、セル選択 / 編集 / コピー / クリア / チェックボックス行選択 / ホバー / コンテキストメニューの対象になりません。矢印キー(↑ / ↓)はラベル行に止まらず読み飛ばし、貼り付けはラベル行を飛ばして次のデータ行へ続けます(行は落ちません)。件数表示(bar の
Rows: X / Y・行選択件数)はデータ行のみを数えます。範囲選択の塗りはラベル行の上も通過して描かれます(グループ行と同じ)。 - 行ドラッグ併用: データ行はセクションを跨いで移動できます(移動先のセクションに所属が変わる)。ラベル行自体は掴めません。ソート / フィルター中に操作不可になる規則は従来どおりです。
- 展開行併用: 可。ラベル行は展開できません(データ行のみ)。
- serverSide(
dataSource): サーバーがgetRowsの結果にラベル行を含めて返し(totalRowCountにも数える)、isLabelRowで識別します。セクションの並べ替え /sectionRowCount/ 縦固定(sticky)/ ラベル行のheightはサーバー行モデルでは適用されず、件数表示はサーバー総数のままです。 - エクスポート: 既定では出力に含みません(scope
'raw'でもデータ行として出ません)。exportCsv/getExportDataのincludeLabelRows: trueで 1 行として出力し、getExportDataはrowKindsで行種を返します(「Excel / スプレッドシート エクスポート」節のレシピ参照)。コピー(Ctrl/Cmd+C)には含まれません。 - 上限:
heightを指定した場合は可変行高ジオメトリで描画するため、論理全高が 15,000,000px を超える構成では上書きを諦めてrowHeightに戻ります。
ラベル行の記述子は GridLabelRow<T>({ kind: 'label', row, sourceIndex, label, sectionRowCount })としてバレルから公開されます(LabelRowOptions<T> / LabelRowRenderContext<T> / LabelRowSortMode も同様)。
合計行(totalRow)
totalRow prop で、表の下端に合計行を固定表示します。どの列を何で集計するかは列の aggFunc(行グルーピングと同じ組み込み集計 / カスタム関数)で決め、フィルターをかけると見えている行だけで計算し直します。
const columns: GridColumn<Order>[] = [
{ key: 'product', title: '商品', width: 160 },
{ key: 'qty', title: '数量', width: 90, align: 'right', aggFunc: 'sum' },
{ key: 'price', title: '単価', width: 110, align: 'right', aggFunc: 'avg', valueFormatter: yen },
{ key: 'amount', title: '金額', width: 120, align: 'right', aggFunc: 'sum', valueFormatter: yen },
];
<SpreadsheetGrid rows={rows} columns={columns} totalRow />
// 見出しを変える / 一部の列は値を渡す
<SpreadsheetGrid rows={rows} columns={columns} totalRow={{ label: '総計', values: { product: `${rows.length} 品目` } }} />TotalRowOptions<T>(totalRow にオブジェクトで渡したとき):
| Name | Type | Default | Description |
|---|---|---|---|
label | string | '合計' | 見出し。行ドラッグ / 展開トグルの合成列を除いた先頭列に出す(その列に値があるときは行番号の欄へ)。 |
values | Record<string, unknown> | — | 列 key → 表示する値。指定した列は計算せずこの値を出す(aggFunc より優先。serverSide はこれだけ)。 |
renderCell | (ctx: TotalRowCellContext<T>) => ReactNode | — | 値のあるセルの中身を差し替える。ctx = { column, value, kind, text, caption }(kind は 'sum' | 'avg' | 'min' | 'max' | 'count' | 'custom' | 'value'、text は既定の表示文字列、caption は種類の見出し)。 |
height | number | rowHeight | 合計行の高さ(px)。 |
className | GridSlotProps | — | 行要素へ追加する class(または { className, style })。classNames.totalRow に加えて付与。 |
- 集計の対象: フィルター / ソート後のデータ行です。ラベル行(
labelRow)は数えず、行グルーピング中は折りたたんだグループの行も数えます。数値の判定は行グルーピングの集計と同じで(数値と数値の文字列だけ)、countは対象の行数です。数値対象が 0 件の sum / avg / min / max は空セルになります。 - 表示: 合計以外の組み込み集計(平均 / 最小 / 最大 / 個数)は値の前に小さく種類を出します。値の整形は列の
valueFormatterを使います(グループ行と同じく合計行に元の行は無いためrowはundefined。rowを読む formatter の列はrenderCellかvaluesで整形してください)。文字列(カスタム集計 /valuesの文字列)はそのまま出します。countの件数とvalueFormatterの無い列の数値は、下部バーの選択範囲の集計と同じ書式(桁区切りあり・小数は 4 桁まで・浮動小数の誤差は丸める)です。列のrenderCell/cellClassNameは合計行には使いません(行データが無いため)。中身の差し替えはtotalRow.renderCellで行います。列幅の自動調整(列メニュー / 境界のダブルクリック / すべての列の自動調整)は合計行の表示(種類 + 値)も収まる幅にし、列幅で切れた値はポインタを乗せるとツールチップで全体を出します。 - 位置: 縦スクロール中は表の下端に留まり、行が少ないときは最後の行の直後に来ます。横スクロールと左右の固定列には通常の行と同じく付いていきます。最後までスクロールすると最終行は合計行のすぐ上に来て、キーボード操作 /
scrollToRowで行を見せるときも合計行の裏に隠しません(最後までスクロールした状態で合計行を出したときも最終行を見せたまま)。範囲選択 / 行ドラッグ / フィルハンドルのドラッグで合計行の上へポインタを動かすと、本体の見えている最後の行として扱い、自動スクロールは合計行の上端から始まります。 - 表示専用: セル選択 / 編集 / 貼り付け / コピー / クリア / 行選択 / 右クリックメニューの対象外です(押しても選択は動きません)。件数表示(bar の Rows)やビュー行 index には数えません。
- エクスポート: 既定では含みません。
exportCsv/getExportDataのincludeTotalRow: trueで、scope'view'のときだけ末尾に 1 行出します(値は生値。見出しは先頭列に値が無いときだけ先頭列へ)。getExportDataはrowKindsの'total'で判別できます。 - serverSide(
dataSource): 全件を持たないため計算しません。サーバーで集計した値をvaluesで渡してください。 - アクセシビリティ: 表の最後の行(
role="row")として読み上げます(aria-rowcountに 1 行足す)。 - 見た目: 行は
.ssg-total-row、セルは.ssg-body-cell.ssg-total-cell、種類の見出しは.ssg-total-caption。トークン--ssg-total-row-bg/--ssg-total-row-text/--ssg-total-row-border(上辺の区切り線)。
行ドラッグ並び替え(enableRowDrag)
enableRowDrag を付けると、先頭のハンドル列(⋮⋮)を掴んで行を上下へ動かせます(AG Grid の managed row dragging 相当)。並び替えの結果は通常の編集と同じく onRowsChange で新配列として返るため、消費側は rows を差し替えるだけです。
const [rows, setRows] = useState(initialRows);
<SpreadsheetGrid
rows={rows}
onRowsChange={setRows}
columns={columns}
rowKeyGetter={(row) => row.id}
enableRowDrag
isRowDraggable={(row) => !row.locked}
onRowMove={({ rowKey, fromIndex, toIndex, rows }) => saveOrder(rows)}
/>- 操作: ハンドルを押して上下へドラッグすると、挿入位置に水平のガイド線が出ます。ドロップで確定し、影響行が新しい位置へスライドします(
prefers-reduced-motionでは即時)。グリッドの枠外で離す /Escapeでキャンセルします。上下端に近づくと自動スクロールします(仮想化された画面外の行へも運べます)。 - ゴースト: ドラッグ中はポインタ追従のピルに、先頭の(合成列でない)表示列の表示値(
valueFormatter適用後)を出します。空なら「行 N」。 - データ契約: 確定時は「1 要素を移動した新配列」(未変更行は参照共有)を
onRowsChangeに渡し、その直後にonRowMoveを呼びます。掴んだ行の直上 / 直下(動かない位置)で離した場合はどちらも呼ばれません。履歴ラッパ経由のためCtrl/Cmd+Z/undo()で戻せます。 - 有効条件: clientSide(
rows+onRowsChange)専用です。dataSource(serverSide)/ 行グルーピング中 /onRowsChange未指定ではハンドル列を挿入しません。readOnlyは関係しません(並び替えはセル編集ではないため)。読み取り専用時に並べ替えも止めたい場合はenableRowDrag={!readOnly}を渡します。 - ソート / フィルター中: 表示順と
rowsの順が一致しないため、ハンドルは淡色(.ssg-row-drag-handle--disabled)+ 理由のツールチップになり操作できません(列はそのまま残るのでレイアウトは跳ねません)。解除すると復帰します。 - 展開行との併用: 展開中のマスター行は詳細パネルごと一緒に移動します。ドロップ位置の判定は詳細パネルの高さ込みで、パネルの上は「マスター行の下」として扱います。
- ハンドル列: 合成列のため、列メニュー / ソート / 列 DnD / autoSize / エクスポート / 並び替え管理パネルの対象外です。左固定列があるときは左固定側に、展開行トグル列よりさらに先頭に入ります。
- 命令的 API:
moveRow(rowKey, toIndex)は表示状態(ソート / フィルター)に関わらず元配列上で移動します(onRowsChange→onRowMoveの順)。 - 表示方式:
rowDragMotion: 'live'で、ドラッグ中に掴んだ行がポインタに追従し、周囲の行がリアルタイムに退避する見せ方になります(スロット解決は'ghost'と共通)。 - スタイル:
.ssg-row-drag-handle(+--disabled)/.ssg-body-cell--row-drag-handle/.ssg-row-drop-indicator/ ドラッグ中の行.ssg-body-row[data-ssg-row-dragging]。色はトークン(--ssg-drop-indicator/--ssg-glyph-*/--ssg-ghost-*)です。
IME オンのままの直接入力(imeDirectInput)
既定では、セルを選んだ状態で IME をオンにしたまま文字を打つと、変換が始まらず打った文字が失われます(編集していないあいだのフォーカスが入力欄ではない要素にあるため。F2 / Enter / ダブルクリックで編集を始めた後の IME は問題ありません)。imeDirectInput を true にすると、Excel / SPREAD と同じく IME オンのまま打ち始められます。
<SpreadsheetGrid rows={rows} columns={columns} onRowsChange={setRows} imeDirectInput />- 動き: 編集していないあいだ、グリッドのフォーカスはアクティブセルの上の透明な入力欄(入力受け)にあります。日本語を打ち始めると変換中の文字がセルの上にエディタと同じ見た目で表示され、変換を確定(
Enter/ 候補の確定)すると確定した文字列を初期値に編集が始まります。そのまま続けて入力でき、もう一度Enterでセルを確定して移動します(移動先はeditorEnterMove)。変換を取り消した(Escapeで空にした)ときは編集を始めません。 - 対象: text エディタ(
editor未指定 /type: 'text')と number エディタの編集可能なデータセル。select / date / checkbox / custom の列、読み取り専用セル、グループ行 / ラベル行、SSRM の未ロード行では変換を捨てます(従来どおり何も入りません)。 - number 列: 変換を確定すると、全角の数字・符号・小数点・3 桁区切りを半角に直した値で編集が始まります(「12.5」→
12.5、「ー5」→-5、「1,234」→1234。句読点の設定が「、。」の IME で入る「12。5」も12.5)。かなや漢字など数値として読めない変換は捨てます。IME そのものはオフにしません(ブラウザからは IME をオンへ戻せないため。Chrome / Edge は CSS のime-modeにも対応していません)。 - 変わらないもの: 矢印 /
Tab/Enter/F2/Delete/Ctrl+C/ 貼り付け / undo などのキー操作、IME を使わない文字キーでの編集開始(その 1 文字が初期値)、端の列のTabでグリッド外へ出る動き。変換中のキー(候補の選択など)はグリッドでは扱いません。 - 変換中にクリックしたとき: 変換中に別のセルやグリッド外をクリックすると、変換中の文字は入力されません(確定してから移動してください)。クリックした先のフォーカスは奪いません。
- フォーカス: 有効中は
document.activeElementが入力受け(input[data-ssg-ime-input])になります。グリッドにフォーカスがあるかをgridElement.contains(document.activeElement)で判定している場合は従来どおり動きますが、「入力欄にフォーカスがあるときは無効」になるショートカット(Mantine のuseHotkeysの既定など)はグリッド上で効かなくなります。これが既定 OFF の理由です。 - タッチが主のデバイス(スマホ / タブレット): 入力受けに
inputmode="none"を付け、セルのタップで仮想キーボードが開かないようにしています(従来どおり)。 - 確認状況: Chromium(Chrome / Edge / WebView2 と同じエンジン)で、DevTools Protocol の変換イベント(
Input.imeSetComposition/Input.insertText)を使って確認しています。OS の IME そのもの(Windows の MS-IME など)での最終確認は実機で行ってください。
モーション(motion)
グリッドの動き(アクティブセル枠の追従、ポップオーバーの出現、ソート時の行移動、読み込み中のシマー等)は motion prop で一括制御する(既定 'auto')。
| 値 | 挙動 |
|---|---|
'auto' | OS / ブラウザの「視差効果を減らす」(prefers-reduced-motion: reduce)が有効なら 'off'、それ以外は 'on'。設定の変化にも追従する。 |
'on' | OS 設定に関わらず動かす。 |
'off' | すべて止める(表示結果は同じで、瞬時に切り替わる)。 |
- 実装は CSS トークン:
--ssg-motion-fast(140ms)/--ssg-motion-base(260ms)/--ssg-motion-slow(600ms)/--ssg-motion-ease(イージング)。各効果はこのトークンだけを参照する。'off'では root と全ポータル root・ドラッグゴースト・ツールチップに.ssg-motion-offが付き、継続時間が 0 になる。 - 速さは利用側のトークン上書きで変えられる(
.ssg-root { --ssg-motion-base: 180ms; })。.ssg-motion-offの値は:where(特異度 0)で定義しているため、停止中でも特定の効果だけ戻すことができる。 - SSR では
'auto'はいったん'on'で描画し、ハイドレーション後にクライアント側で再解決する(theme='auto'と同じ)。 - 従来
prefers-reduced-motionを直接見ていた効果(ツールパネルのフラッシュ / フィルター候補のスピナー / スクロール位置インジケーターのフェード)もmotionの実効値で判定するようになった('auto'では従来と同じ)。
効果の一覧
| 効果 | 既定 | トークン | 備考 |
|---|---|---|---|
| アクティブセル枠 / 選択範囲の追従(M-1) | 有効 | --ssg-motion-fast | キーボード移動 / クリックで枠が新しい位置へ滑る。スクロール中とポインタでの範囲ドラッグ中は瞬時に追従(漂って見えるのを避ける)。 |
| ポップオーバー / メニューの出現(M-7) | 有効 | --ssg-motion-fast | 列メニュー / コンテキストメニュー / フィルター / ツールパネル / select 候補 / 日付パネルが 4px 上からフェードイン。出現のみ(閉じるのは即時)。 |
| ソート / フィルター / グループ開閉の行移動(M-4 / M-5) | 有効(animateRows) | --ssg-motion-base | 描画中の行が新しい位置へ滑る(FLIP 相当。行要素の transform の transition)。auto-height / SSRM では自動 OFF。 |
| スクロールなしで現れた行のフェードイン(M-4 / M-5) | 有効(animateRows) | --ssg-motion-base | ソートで窓に入ってきた行、展開したグループの子行、差し替えた rows の行が上から順に(約 23ms 刻み)3px 下からフェードイン。スクロールで出入りする行は対象外。 |
| コピー範囲の動く点線(M-3) | 有効(showCopyRange) | — | Ctrl/Cmd+C 後、コピー元の範囲に点線が流れる(0.6s 周期)。motion が 'off' では止まった点線。 |
| ホバーのクロスヘア(M-6) | opt-in(hoverHighlight: 'cross') | — | ポインタの行に加えて列ヘッダー・行番号・同じ列の他セルを薄く染める(動きはなく、静的な強調)。 |
| 変更セルのフラッシュ + 数値トゥイーン(M-2) | opt-in(highlightChanges) | --ssg-motion-slow / --ssg-motion-base | 値が変わったセルがアクセント色から 600ms でフェードし、数値は 260ms でカウントアップ。 |
| セル単位の保存状態(M-9) | 有効(showSaveStatus、SSRM の書き戻し時) | --ssg-motion-base / --ssg-motion-slow | 保存中の斜線 + スピナー → ✓ バッジ、失敗は揺れ + 赤いフラッシュ + ✕ とチップ。motion が 'off' では静的な印だけ。 |
| 行ドラッグの live 退避(M-11) | opt-in(rowDragMotion: 'live') | 160ms 固定 | 掴んだ行がポインタに追従し、通る先の行がその場で退避。ドロップ後は従来どおりスライドで収まる。 |
| 列ドラッグの live 退避(M-12) | opt-in(columnDragMotion: 'live') | 160ms 固定 | 掴んだ列がポインタに追従し、通る先の列がその場で左右へ退避。固定ペインをまたぐときは列の複製(浮かぶ列)が付いてきて元のペインは詰まり、移動先は縦線 + 見出しのアイコン(ピン / 移動の矢印)で示す。ドロップ後は従来どおりスライドで収まる(またいだときは浮かぶ列が新しい位置へ滑り込む)。 |
| 読み込み中のシマーと到着行のフェードイン(M-8) | 有効 | --ssg-motion-base | SSRM の取得中はスケルトンに光の帯が流れ、ブロックが到着して差し替わった行のセルが上から順に(約 23ms 刻み)3px 下からフェードイン。スクロールで出入りする行は対象外。clientSide では発生しない。 |
| 細かな遷移 | 有効 | --ssg-motion-fast | ヘッダー操作アイコンのフェード、チェックボックス、タブのインジケータ、ツールチップ、スクロール位置インジケーター。 |
セル内検索(find)
find を付けると、グリッドにフォーカスがある状態の Ctrl/Cmd+F(または handle.openFind())でグリッド右上に検索バーが出る(既定は無効)。「絞り込み(フィルター)」とは別に、行を消さずに場所を探す操作で、ブラウザの Ctrl+F では探せない仮想化された行も対象になる。
<SpreadsheetGrid find ... />
// find={{ shortcut: false, caseSensitive: true }}- 対象: ビュー行(フィルター / ソート適用後)× 可視列の表示文字列(
valueFormatter適用後 = 画面に見えている文字)。renderCellの列も表示文字列(valueFormatterまたはString(value))で検索されるが、<mark>の強調は既定セルだけ(カレントへの移動は同じ)。 - 走査: 時間分割(
logic/chunkedLoop。1 チャンク 10ms)で入力・描画を止めない。走査中は件数が「検索中…」になる。クエリ / 大文字小文字 /rows(編集・差し替え)/ ソート / フィルター / 列が変わると再走査し、同じヒットが残っていればカレントを保つ(移動はしない)。 - 順送り:
Enter/↓で次、Shift+Enter/↑で前(循環)。カレントのヒットへはクリック相当の単一選択 + 可視化スクロール(selectCell(row, col, { scrollIntoView: true })と同じ)。クエリを打ち直すと先頭のヒットへ移動する。 - 閉じる:
Esc/ × でクエリとヒットが消え、フォーカスはグリッド本体へ戻る。findをfalseにすると開いていても閉じる。 - ヒット数の上限: 10,000 件で打ち切り(件数表示に
+)。SSRM はロード済み範囲だけが対象(未ロード行は読み飛ばす)。 - スタイル: ヒットは
.ssg-find-mark、カレントは.ssg-find-mark--current(トークン--ssg-find-mark-bg/--ssg-find-mark-current-bg)。バーはclassNames.findBar。 - ショートカット:
Ctrl/Cmd+Fの横取りはグリッド本体にフォーカスがあるときだけ(フィルター入力 / エディタ内では効かない)。shortcut: falseでブラウザ標準の検索に譲り、handle.openFind()だけで開く運用もできる。
キーボード操作
グリッド本体フォーカス中(編集中でない)の操作一覧。フィルター入力等のフォーム要素にフォーカス中は無効。
| キー | 操作 |
|---|---|
矢印(+ Shift で範囲拡張)/ Tab / Shift+Tab | アクティブセル移動(labelRow 有効時、↑ / ↓ はラベル行に止まらず読み飛ばす)。最終列での Tab / 先頭列での Shift+Tab はグリッドで止めず、ブラウザ既定のフォーカス移動でグリッド外の次 / 前の要素へ移る(キーボードトラップ回避)。 |
Enter / F2 / 印字キー直打ち | 編集開始(印字キーはその 1 文字を初期値に。select エディタではその文字で始まる候補を頭出しする)。編集可否は readOnly / 列 / canEditCell に従う。 |
Escape | 選択解除。 |
Ctrl/Cmd+C / ペースト(Ctrl/Cmd+V) | 選択範囲の TSV コピー(isRowExportable 指定時は false の行を除く)/ アクティブセル起点の貼り付け(readOnly では no-op)。TSV は Excel / Google スプレッドシート互換(改行・タブ・" を含むセルは "…" で囲み " は ""。貼り付けはこの引用符を解釈し、途中の空行も行として保持する)。 |
Ctrl/Cmd+F | セル内検索バーを開く(find が有効なとき。無効ならブラウザ標準の検索)。 |
Ctrl/Cmd+A | 全体選択(2 回目で解除)。 |
Delete / Backspace | 選択セル(なければアクティブセル)の値クリア。編集不可セルは対象外。クリア値は「空文字のペースト」と同じ規則(parseClipboardValue('') 経由、未定義なら '')。変更が無ければ no-op(undo 履歴にも積まれない)。 |
Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z / Ctrl/Cmd+Y | undo / redo(詳細は命令的 API の「undo / redo」節)。 |
編集エディタ内: Enter = 確定して下へ(移動先は editorEnterMove で変更可。既定 'down')、Tab / Shift+Tab = 確定して右 / 左へ、Escape = キャンセル、フォーカスアウト = 確定。IME 変換中(isComposing)の Enter / Escape / Tab は IME の操作としてのみ扱われ、セル編集の確定 / キャンセルには使われない。
確定 / キャンセル後のフォーカス: Enter / Tab / Escape で確定・キャンセルしたときはグリッドへ戻る(imeDirectInput 有効時は入力受けへ)。フォーカスアウトで確定したときは、フォーカスの移り先がグリッド外の要素(グリッドの上のフォームの入力欄 / 上部バーのグローバルフィルター等)ならそこに残し、グリッドへ奪い返さない(クリックした入力欄へそのまま入力できる)。グリッド内の別のセルをクリックしたとき・移り先がないとき(フォーカスを取らない余白のクリック等)はグリッドへ戻る。展開行カード内のフォーカスは奪わない。
確定後のアクティブセル: 方向なしの確定(フォーカスアウト / select の候補クリック / custom エディタの commit(value) / editorEnterMove: 'none' の Enter)の後は、編集していたセルが単一選択になる。ただし確定のきっかけがグリッド内の別のセルや行・列ヘッダーの押下だった場合は、押した操作(そのセルのアクティブ化 / ドラッグでの範囲選択 / 行・列の選択)を優先し、編集していたセルへ戻さない。Enter / Tab など方向付きの確定は従来どおり隣のセルへ移る。
IME オンのまま(編集していないセルで)日本語を打ち始めるには imeDirectInput を有効にする(既定 OFF。無効時は変換が始まらず最初の入力が失われる。「IME オンのままの直接入力」節)。
確定後の移動先は「確定を反映した再レンダー後」の行数・列数でクランプされる。このため、onRowsChange で末尾に空行を追加する消費側(Excel 的な入力グリッドの定石パターン)では、最終行の Enter 確定で「増えた行」へそのまま移動できる。行が増えない場合は従来どおり最終行に留まる。
アクセシビリティ
WAI-ARIA の grid パターンに沿って、表・行・セルに役割と状態を付けている(支援技術向け。ariaLabel で表の名前を付けられる)。
| 要素 | 役割 / 属性 |
|---|---|
表本体(.ssg-scroll-container) | role="grid"。aria-label(ariaLabel)/ aria-rowcount(見出し行を含む表示中の行数。合計行(totalRow)があればそれも含む)/ aria-colcount(行番号列を含む列数)/ aria-multiselectable(enableRangeSelection 時)/ aria-readonly(readOnly 時)。 |
フォーカスを受ける要素(.ssg-shell) | role="group"。aria-activedescendant はアクティブセルが描画されているときだけ、そのセルの id(キーボードでセルを移動すると支援技術がそのセルを読み上げる)。 |
| 見出し行 / データ行 | role="row"。aria-rowindex は見出し行が 1、データ行は表示順 + 2。行選択のチェックボックスがあるときは aria-selected。未ロード行(SSRM のスケルトン)は aria-busy。 |
| 列見出し | role="columnheader"。aria-colindex(行番号列が 1)。ソートの第 1 キーの列だけ aria-sort(ascending / descending)。 |
| 行番号 / 左上の角 | role="rowheader" / role="columnheader"。 |
| セル | role="gridcell"。aria-colindex / aria-selected(選択範囲内)/ aria-readonly(編集できないセル)。 |
| 行選択のチェックボックス | role="checkbox"。aria-checked(一部選択は mixed)/ aria-label(「すべての行を選択」「行 N を選択」)。 |
editor: { type: 'checkbox' } のセル | role="checkbox"。aria-label は列の title(無ければ key)。 |
| 編集中のエディタ | role="group"、aria-label は「(列名)を編集」。行の外に重ねて描画するため、編集中のセルが aria-owns でエディタを子に持つ(支援技術ではエディタがセルの中に入る)。 |
合計行(totalRow) | role="row"(表の最後の行。aria-rowindex = 表示行数 + 2)。見出しの欄は role="rowheader"、セルは role="gridcell"。 |
フィルハンドル(fillHandle) | ハンドルは aria-hidden(ポインタ操作。キーボードではコピー / 貼り付け)。埋めたあとのボタンは aria-haspopup="menu" / aria-expanded / aria-label(「オートフィル オプション(連続データ)」等)、メニューは role="menu"、項目は role="menuitemradio" + aria-checked。 |
- 仮想化で描画していない行・列は DOM に無いため、表全体の中の位置は
aria-rowindex/aria-colindex/aria-rowcount/aria-colcountで伝える。 - 制約: 固定列(
pinned)があると、1 つの行が左 / 中央 / 右のペインごとのrole="row"に分かれる(どれも同じaria-rowindex。読み上げの順は DOM 順でペインごと)。展開行(detailRow)の帯はaria-rowindexを持たない(データ行の番号をずらさないため)。縦固定で複製したラベル行(labelRow.sticky)はaria-hiddenで、中のボタン等はtabindex="-1"(Tab で止まらない。マウスでは押せる。本物のラベル行はそのまま)。
パーツ別スロット(classNames / GridSlotProps)
className 系の受け口(classNames.* / cellClassName / getRowClassName / detailRow.className / labelRow.className / totalRow.className)はすべて GridSlotProps を受ける(バレルから GridSlotProps / GridClassNames を公開)。
type GridSlotProps = string | { className?: string; style?: CSSProperties };- 文字列は従来どおり class として付与される。オブジェクト形は StyleX の
stylex.props(...)の戻り値と同形で、そのまま渡せる(StyleX の動的スタイルはstyle側の CSS 変数で届くため、この形が必要)。 styleは当該パーツのルート要素へインラインで付与される。グリッドが位置決めに使う座標 / 寸法(left/top/width/height/transform等)はグリッド側が後勝ちで上書きするため、レイアウトは壊せない。- インライン style は状態クラス(選択 / ホバー等の背景)にも勝つ。状態で切り替えたい装飾は className 側で行う。
classNamesはレンダー毎に新しいオブジェクトを渡してもよい(内容の署名で memo され、行 / ヘッダーの再レンダーは増えない)。getRowClassNameが返す style も内容比較で memo される。tooltip/dragGhostは命令的 DOM(document.body直下)のため、style の数値は単位なしのまま設定される(px が必要な値は'12px'のように文字列で)。
| スロット | 付与先 |
|---|---|
root | ルート要素 .ssg-root(className / style prop と同じ要素) |
toolbar / statusBar | 既定トップバー .ssg-bar--top / 既定ボトムバー .ssg-bar--bottom(renderTopBar / renderBottomBar 指定時は対象外) |
headerRow / headerCell | ヘッダー行 / 列ヘッダーセル(コーナー・行ヘッダーセルは含まない) |
rowHeaderCell / cornerCell | 行ヘッダー「#」セルとコーナーセル(rowHeaderCell は両方、cornerCell はコーナーのみ) |
bodyRow / bodyCell | 本体行(データ / スケルトン / グループ行)/ データセル |
groupRow / groupCell | グループ行 / グループ行のセル(bodyRow / bodyCell に加えて付与) |
detailBand / detailCard | 展開行の帯 / カード(detailRow.className に加えて付与) |
labelRow / labelRowContent | ラベル行(見出し / 区切り行)の行要素 .ssg-body-row[data-ssg-label-row](bodyRow / labelRow.className に加えて付与。縦固定の複製にも付く)/ 中身の器 .ssg-label-row-content |
totalRow / totalCell | 合計行の行要素 .ssg-total-row(3 ペイン分。totalRow.className に加えて付与)/ 合計行のセル .ssg-total-cell(bodyCell に加えて付与) |
iconButton | ヘッダーのアイコンボタン .ssg-icon-btn |
popover | ポータル系パネルの root(列メニュー / フィルター / コンテキストメニュー / select エディタ候補 / ツールパネル。document.body 直下) |
menuItem | メニュー項目 .ssg-menu-item(列メニュー / コンテキストメニュー) |
tooltip | カスタムツールチップ .ssg-tooltip(body 直下のシングルトン。複数グリッド同居時は最後に更新したグリッドの値) |
dragGhost | 列 / 行ドラッグのゴースト [data-grid-drag-ghost] |
checkbox | 行選択 / checkbox 列のチェックボックス glyph .ssg-row-checkbox |
cellEditor | セルエディタの枠 .ssg-cell-editor |
emptyState | 0 行時の空状態 .ssg-empty-state |
filterChipBar | フィルターチップバー .ssg-filter-chip-bar |
errorBar | SSRM のエラーバー .ssg-ssrm-error-bar |
scrollHint | スクロール位置インジケーター .ssg-scroll-hint |
activeCellOverlay / selectionOverlay | アクティブセル枠 / 範囲選択の塗り |
copyRangeOverlay | コピー範囲の動く点線 .ssg-copy-range-overlay |
findBar | セル内検索バー .ssg-find-bar |
StyleX との併用
import * as stylex from '@stylexjs/stylex';
import { vars } from './tokens.stylex'; // stylex.defineVars({ accent: '#7c3aed', danger: '#dc2626' })
const s = stylex.create({
grid: { '--ssg-accent': vars.accent, borderRadius: 8 }, // グリッドのトークンへ橋渡し
cell: { fontVariantNumeric: 'tabular-nums' },
negative: { color: vars.danger },
width: (w: number) => ({ maxWidth: w }), // 動的スタイル(style 側で届く)
});
<SpreadsheetGrid
classNames={{ root: stylex.props(s.grid), bodyCell: stylex.props(s.cell) }}
columns={[{ key: 'amount', title: '金額', cellClassName: (ctx) => stylex.props(typeof ctx.value === 'number' && ctx.value < 0 && s.negative, s.width(120)) }]}
/>- StyleX は子孫セレクタを書けない(要素自身のクラスでしか装飾できない)ため、上記スロットに無い内部要素(ポップオーバー内のボタン / 入力欄等)はデザイントークン(
--ssg-*)で調整する。 - StyleX の atomic クラスとグリッドの基底クラスは同じ特異度 (0,1,0)。同じプロパティは後に読み込まれた方が勝つため、グリッド CSS を先に・StyleX の出力 CSS を後に読み込むか、
style.layer.css(@layer ssg-base)を使う。StyleX 側でuseLayersを有効にしている場合は、未レイヤーのグリッド CSS がレイヤー内に常に勝つためstyle.layer.cssが必須。
スタイリング用の状態クラス(公開契約)
cellClassName / getRowClassName の返すクラスは、下記の内部付与クラスと連結セレクタで組み合わせて使える(例: .ssg-body-cell.my-diff で基底に勝たせ、.ssg-body-cell.my-diff.ssg-body-cell--row-hovered でホバー時色を切替)。以下は公開契約とし、変更時は breaking 扱いにする。
| クラス | 付与先 / 条件 |
|---|---|
.ssg-root | グリッドのルート要素(className prop の付与先)。 |
.ssg-body-row | 行コンテナ(getRowClassName の付与先のひとつ)。 |
.ssg-body-cell | データセルの基底(未レイヤー・特異度 (0,1,0))。 |
.ssg-row-header-cell | 行ヘッダー「#」セル。 |
.ssg-body-cell--readonly | 読み取り専用セル(範囲選択に入っていないとき。dimReadOnlyCells と独立して常時付与)。 |
.ssg-body-cell--invalid | validation mark 表示中のセル。 |
.ssg-body-cell--has-note | セルのメモ(cellNote)があるセル(右上の印は ::before。色は --ssg-note-indicator)。 |
.ssg-body-cell--cf-bar | 条件付き書式のデータバーがあるセル(帯は子要素 .ssg-cf-bar。負の値は .ssg-cf-bar--negative)。 |
.ssg-body-cell--cf-scale | 条件付き書式のカラースケールで背景を染めたセル。 |
.ssg-chip | 条件付き書式の状態チップ(色味は .ssg-chip--info / --good / --warning / --critical / --neutral、任意色は .ssg-chip--custom。中身は .ssg-chip-icon / .ssg-chip-label)。 |
.ssg-body-cell--row-hovered | 行ホバー中のセル(enableRowHover 有効時)。 |
.ssg-body-cell--autoheight | auto-height 列のセル。 |
.ssg-body-cell--align-center / .ssg-body-cell--align-right | column.align の水平寄せ。 |
.ssg-total-row / .ssg-total-cell | 合計行(totalRow)の行 / セル(セルは .ssg-body-cell にも付く。種類の見出しは .ssg-total-caption)。 |
.ssg-fill-handle / .ssg-fill-preview / .ssg-fill-options / .ssg-fill-menu / .ssg-fill-tooltip / .ssg-root--filling | フィルハンドル(fillHandle)のハンドル / ドラッグ中の点線 / 埋めたあとのボタン / メニュー / 値のツールチップ / ドラッグ中のルート(カーソルを十字に固定)。 |
.ssg-theme-dark | theme="dark"(または 'auto' のダーク解決)時に root と各ポータル(popover / menu / panel / ツールチップ)へ。 |
※上記以外の ssg-* クラス(内部構造クラス)は非公開の実装詳細で、予告なく変わり得る。セレクタで依存しないこと。
補助型(props で参照される shape)
GridRowKey = string | numberGridColumnPinned = 'left' | 'right'GridSelectFilterOption = { label: string; value: string }CellRenderContext<T> = { row, rowIndex, sourceRowIndex, rowKey, colIndex, value, column, isActive, isSelected, isEditing, readOnly, setValue, detail? }setValueはreadOnly/canEditCellを見ずに書き込む(検証validateは通る)。読み取り専用時に無効化したい場合はreadOnlyを見て呼び分ける。readOnly中は undo が無効なため、この書き込みは取り消せない。detail?: CellDetailContext = { expanded, expandable, toggle, setExpanded }はdetailRowprop 有効時のみ定義(「展開行(Master/Detail)」節)。
DetailRowOptions<T>/DetailRowRenderContext<T> = { row, rowKey, rowIndex, sourceRowIndex, collapse }/CellDetailContext(展開行。バレルから公開)LabelRowOptions<T>/LabelRowRenderContext<T> = { row, rowKey, rowIndex, sourceRowIndex, label, sectionRowCount }/GridLabelRow<T> = { kind: 'label', row, sourceIndex, label, sectionRowCount }/LabelRowSortMode = 'section' | 'follow' | 'hide'(ラベル行。バレルから公開。「ラベル行(見出し / 区切り行)」節)FillHandleOptions<T> = { getFillValue? }/GridFillValueParams<T> = { column, row, rowIndex, sourceValues, index, direction, kind, defaultValue }/GridFillDirection = 'down' | 'up' | 'right' | 'left'/GridFillKind = 'series' | 'copy'(フィルハンドル。バレルから公開。「フィルハンドル」節)RowDragContext = { rowKey, sourceRowIndex }(isRowDraggableの第 2 引数)/RowMoveParams<T> = { rowKey, fromIndex, toIndex, rows }(onRowMoveの引数。いずれもバレルから公開)GridCellRef<T> = { row, rowKey, rowIndex, sourceRowIndex, column, columnKey, colIndex, value }(onActiveCellChangeの引数)/GridCellEventParams<T> = GridCellRef<T> & { event: MouseEvent }(onCellClickの引数。eventは DOM 標準のMouseEvent)/GridCellDoubleClickParams<T> = GridCellEventParams<T> & { preventDefault(): void }(onCellDoubleClickの引数。タッチのダブルタップではeventは離したときのPointerEvent)。いずれもバレルから公開。index の基準はCellStyleContextと同じ(「セル操作の通知」節)。CellStyleContext<T>= 上記からsetValueを除いた読み取り専用版(cellClassName関数へ渡る)。バレル(index.ts)から公開(import type { CellStyleContext } from '@ishibashi0112/spreadsheet-grid')rowIndexはビュー行 index(ソート / フィルター適用後の表示位置)、sourceRowIndexは元rowsの index、rowKeyは行キー(rowKeyGetter由来、既定は source index)。ソート / フィルター ON の画面で「エラー行 index の集合」など source 基準のデータと突き合わせるときはsourceRowIndex/rowKeyを使う(getInvalidCells()の返すsourceRowIndex/rowKeyと同一基準)。serverSide では view 順が正準のためsourceRowIndexは view index と同値。
CanEditCellContext = { sourceRowIndex, rowKey }(canEditCellの第 5 引数。基準はCellStyleContextと同一。バレルから公開)RowStyleContext<T> = { row, rowIndex, sourceRowIndex, rowKey, isSelected }(getRowClassNameの第 3 引数。バレルから公開)GridSlotProps = string | { className?: string; style?: CSSProperties }(classNames.*/cellClassName/getRowClassName/detailRow.classNameの値。GridClassNamesと共にバレルから公開)GridFrameworkTypes = { node: unknown; style: object }/ReactGridTypes = { node: ReactNode; style: CSSProperties }(バレルから公開)。公開型の本体は React 非依存の core(描画ノード / style の型を束ね型Fで受ける)で、利用者が普段使うGridColumn<T>等は core をReactGridTypesで固定したエイリアス。型引数の数・名前は従来どおりで、通常は意識不要。rowIndex/sourceRowIndex/rowKeyの基準はCellStyleContextと同一。isSelectedはチェックボックス行選択(enableRowSelection)の選択状態(範囲選択とは別)。グループ行(grouping 有効時)は専用描画のためgetRowClassNameの対象外。
GridScrollPosition = { top: number; left: number }(getScrollPositionの返り値 /setScrollPositionの基準)GridScrollEventParams = { top: number; left: number; source: 'user' | 'api' }(onScrollの引数。sourceの意味は props 表のonScroll行を参照)HeaderRenderContext<T> = { colIndex, width, column, filterValue?, isFiltered? }SpreadsheetGridSlotContext<T> = { rows, filteredRows, columns, visibleColumns, globalFilterText, columnFilterValues, sortState, setGlobalFilterText, activeCell, selection, derivedSummary, globalFilterStatus, globalFilterProgress, selectionAggregates }derivedSummaryはSpreadsheetGridDerivedSummary(行/列/フィルター/ソートの summary 文字列・選択統計などを内包)。helper を import せずトップ/ボトムバーで使える。- 開発ビルドでの注意:
ctxやrows/filteredRowsを子コンポーネントの props にそのまま渡す(<MyBar ctx={ctx} />/<MyBar {...ctx} />/<MyBar rows={ctx.filteredRows} />)と、React 19.2 以降の開発ビルドは Performance Track 向けに props の差分を取るため大きな配列を走査し、100 万行規模ではソート 1 回に数十秒かかることがある。子へは使う値だけを渡す(件数ならctx.filteredRows.lengthやderivedSummary)。本番ビルドには影響しない。内蔵のトップ / ボトムバーも必要な値だけを受け取る。 globalFilterStatus: GlobalFilterStatus('idle' | 'filtering' | 'ready')/globalFilterProgress: number(0..1)。グローバルテキストフィルタは行数が大きい(しきい値 50,000 行・条件はrows.length > 50000)とき、入力を主スレッドを塞がず時間分割で適用する。適用中はstatus='filtering'・progressが進捗(0..1)になる。空/無効は'idle'、確定は'ready'(progress=1)。50,000 行以下は同期適用のため即'ready'('filtering'を経由しない)。ローディング表示はグリッドが本体に重ねる組み込み overlay(autosize の計測中 overlay と同じ作法)で行うため、トップバーやカスタム UI 側で扱う必要は通常ない。 この 2 値は、入力の無効化や独自インジケータなどカスタム UI を出したい場合の参照用に公開している。serverSide では基本'idle'/'ready'(取得中表示は行スケルトンが担当)。selectionAggregates?: GridSelectionAggregates | null(グリッドは常に入れる)。2 セル以上を選んでいるときの選択範囲の集計{ status: 'computing' | 'ready'; progress; cellCount; count; numericCount; sum; average; min; max; partial }(count= 空でないセル数、numericCount= 数値として数えたセル数、数値が無ければsum/average/min/maxはundefined、partial= SSRM で未読み込みの行を飛ばした)。1 セル以下の選択 /showSelectionAggregates={false}/ 出す先が無い(ボトムバー非表示で、renderTopBarも無い)ときはnull。走査セル数が 50,000 を超える選択は時間分割で計算し、その間はstatus: 'computing'(値は未確定、progressは 0..1 を 5% 刻み)。同じ選択のまま値だけ変わったときは、計算し直すあいだも前の結果(status: 'ready')のままで、終われば差し替わる。既定ボトムバーは平均 / データの個数 / 合計を出す(最小 / 最大はこの値でだけ参照できる)。