ソートとフィルター
列フィルター(text / number / date / select / set / custom)とグローバルフィルター
ヘッダーの列メニューからソートとフィルターを操作できます。フィルター UI の種別は column.filterType で列ごとに指定します。
デモ
- 商品名 —
textSet:条件 AND 選択の複合フィルター(テキスト版)。「を含む / に等しい / で始まる / で終わる / 空白 / 空白でない」の条件と Set 一覧を組み合わせます - カテゴリ —
select:単一選択(候補は rows から自動収集) - ステータス —
set:チェックボックスの複数選択(Excel 風) - 数量 —
numberSet:条件 AND 選択の複合フィルター(数値版)。数値条件(以上 / 範囲 など)と Set 一覧を 1 つの popover で組み合わせます(例:「10 以上のうち、12 だけ除外」) - 単価 —
auto:種別を指定せず、列の値から自動判定します(値が全て数値なのでnumberSetとして開きます) - 登録日 —
dateSet:条件 AND 選択の複合フィルター(日付版)。日付条件(範囲 / 以降 / 以前 など)と相対プリセット(今日 / 今月 / 過去 30 日)、そして年 / 月 / 日の 3 階層ツリーの Set 一覧を組み合わせます
上部のグローバルフィルター入力は全列を横断して部分一致します。フィルター適用中はチップバー(showFilterChipBar)に条件が並び、ここから個別解除もできます。列メニューまたはチップバーの「フィルター管理」から、全フィルターを一覧で編集できるパネルも開けます。
列定義
const columns: GridColumn<Row>[] = [
{ key: 'name', title: '商品名', width: 180, filterType: 'textSet' },
{ key: 'category', title: 'カテゴリ', width: 130, filterType: 'select' },
{ key: 'status', title: 'ステータス', width: 130, filterType: 'set' },
{ key: 'qty', title: '数量', width: 110, filterType: 'numberSet' },
{ key: 'price', title: '単価', width: 130, filterType: 'auto' },
{ key: 'registered', title: '登録日', width: 140, filterType: 'dateSet' },
]select/set/numberSet/textSet/dateSetの候補は rows から自動収集されます。明示したい場合はfilterOptions: [{ label, value }]を指定します。number/numberSetの比較 / 範囲では空白セル(null / 空文字)は常に不一致です(0 とは区別されます)。空白行の抽出・除外は演算子「空白 / 空白でない」か、Set 一覧の「(空白)」項目を使います(dateSetの日付条件でも同じ規則です)。numberSet/textSet/dateSetでは条件を適用すると Set 候補が条件を満たす値だけに連動して絞られます。候補から外れた値のチェック状態は破棄されず、条件を戻せば復活します(条件の試行錯誤が可逆)。クリアは「条件のみ」「値のみ」「全部」の 3 粒度です。textSetの条件(Set の検索欄との違い): 検索欄は「候補を選ぶための絞り込み」で、述語としては残りません。「◯◯ を含む」を述語として保持したい(後から増えた該当データも自動で拾いたい)場合は条件欄を使います。dateSetの相対プリセット(今日 / 今月 / 過去 30 日)は相対のまま保存され、フィルター評価のたびに「今日」を基準へ解決されます(getState()で保存した状態を翌日復元すると範囲が追従します)。日付を固定したい場合は「範囲」等の絶対条件を使います。チップの非表示・カスタマイズは下記「相対プリセットの構成(dateFilterPresets)」を参照してください。- 独自の絞り込みロジックは
filterType: 'custom'+filterFn: (row, filterValue) => booleanで書けます。 - ソート / フィルター / グローバルフィルターは
enableSorting/enableColumnFilter/enableGlobalFilter(いずれも既定true)で無効化できます。
相対プリセットの構成(dateFilterPresets)
dateSet popover の相対プリセットチップは、列オプション dateFilterPresets で列ごとに構成できます。未指定はビルトイン 3 種(今日 / 今月 / 過去 30 日)です。
// ① 非表示(オプトアウト): チップ行そのものを出さない
{ key: 'updatedAt', title: '更新日時', filterType: 'dateSet', dateFilterPresets: false }
// ② カスタム構成: ビルトイン ID の再利用とカスタム定義を混在できる(表示順のまま描画)
{
key: 'updatedAt',
title: '更新日時',
filterType: 'dateSet',
dateFilterPresets: [
'today', // ビルトイン ID(ラベルは既定の「今日」)
{
id: 'thisWeek', // フィルター値に保存されるのはこの id(相対のまま)
label: '今週',
resolve: (now) => { // フィルター評価のたびに呼ばれ、絶対範囲へ解決される
const day = now.getDay();
const monday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - ((day + 6) % 7));
return { from: monday, to: now }; // 'YYYY-MM-DD' 文字列 or Date(両端含む)
},
},
],
}- カスタムプリセットも**ビルトインと同じ「相対保存」**です。保存されるのは
idだけで、評価のたびにresolve(now)が絶対範囲へ解決します。 resolveの返り値は{ from?, to? }(両端含む)。片側だけ返すと 以降 / 以前 として評価され、from > toは自動で入れ替わります。- 列定義からカスタム ID を外すと、保存済みのそのプリセット条件は「条件なし」として評価されます(全行が消える事故を避ける安全側の挙動)。
- serverSide(
dataSource)ではカスタム ID も{ mode: 'preset', preset: id }のまま送出されます。id の解釈はサーバ側の責務です(サーバーサイド行モデル)。
dateSet の日付入力(既定 UI)
dateSet 条件の日付入力は、v0.28 からネイティブ <input type="date"> に代わり内製の日付フィールドになりました(ブラウザ依存の見た目・操作性の問題を解消)。
- 自由入力 —
2026/7/1/2026-07-01など表記ゆれを受け付け、Enter / blur で正規化して確定します。解釈できない入力は赤枠のまま確定しません(適用済みの条件は保持)。 - カレンダー — フィールド右のボタンで開閉。タイトルクリックで 日 → 月一覧 → 年一覧 とドリルアップでき、遠い年代へも素早く移動できます。フッターに「今日」「クリア」(月・年ビューでは選択せずに戻れる「← 戻る」も表示)。
- ダークテーマ / 密度トークンへ追従します。追加の依存はありません。
日付入力の差し替え(renderFilterDateInput)
内製フィールドではなくアプリの UI ライブラリで統一したい場合は、グリッドの prop renderFilterDateInput でピッカーごと差し替えられます(対象はフィルター popover の条件欄のみ)。
import { DatePickerInput } from '@mantine/dates';
<SpreadsheetGrid
renderFilterDateInput={({ value, onChange, ariaLabel }) => (
<DatePickerInput
aria-label={ariaLabel}
value={value || null} // value は 'YYYY-MM-DD' か ''
onChange={onChange} // string | Date | null をそのまま渡せる(内部で正規化)
valueFormat="YYYY/MM/DD"
size="xs"
clearable
popoverProps={{ withinPortal: false }} // ★ 外側クリック対策(下記)
style={{ flex: 1, minWidth: 0 }}
/>
)}
/>- コンテキストは
{ value, onChange, ariaLabel, slot: 'single' | 'from' | 'to', columnKey }。onChangeはDate/null(クリア)/'2026/7/1'のような表記ゆれ文字列も受け付け、内部で'YYYY-MM-DD'へ正規化されます(解釈できない値はクリア扱い)。値を変更するとプリセットチップの選択は解除されます。 - 外側クリック対策: フィルター popover は外側 pointerdown で閉じるため、ピッカーのカレンダーが body 直下ポータルに出ると操作で閉じてしまいます。①ポータル無効化で popover 内に描画(Mantine の
popoverProps={{ withinPortal: false }}等。推奨)、または ②ポップアップ要素へdata-ssg-filter-keep-open属性を付与、のどちらかで回避してください。 - HeroUI の例や詳細な契約は API リファレンス(パッケージ同梱の
API_REFERENCE.md「日付入力の差し替え」節)を参照してください。
種別の自動判定(filterType: 'auto')
列ごとに種別を書き分けるのが面倒なときは filterType: 'auto' を指定すると、列の値から numberSet / textSet / dateSet を自動判定します。明示的な opt-in のみで、filterType 未指定の列は従来どおりフィルターなしのままです。
{ key: 'price', title: '単価', width: 130, filterType: 'auto' }- 判定は popover を初回に開いた時点で 1 回だけ行い、以後その列では固定されます(行を編集・追加するたびに種別が変わると、適用中のフィルターと UI が食い違うためです)。
- 優先順位: 適用済みフィルター → 前回の判定結果 →
editor種別のヒント({ type: 'number' }/{ type: 'date' })→ 値のサンプリング(空白を除く最大 1,000 件)。 - 厳格判定です。サンプルが全て日付なら
dateSet、全て数値ならnumberSet、1 件でも混ざればtextSet(安全側)になります。 - 判定は**値の型ではなく「数値 / 日付として解釈できるか」**です。DB の型が文字列でも
'1234'は数値とみなします。ただし'0001'のような先頭ゼロの値は数値とみなしません(品番コード・郵便番号を数値と誤認しないため。Excel と同じ扱いです)。 - serverSide(
dataSource)では値からの推定を行いません(クライアントが全行を持たないため)。editor種別があればそれで確定し、無ければtext(部分一致)になります。auto を使う列にはeditorの指定を推奨します。
状態を外から扱う
ソート / フィルター状態はグリッド内部の reducer が持ちますが、getState() / applyState()(命令的ハンドル)で保存・復元、onStateChange で変更検知ができます。詳細は API リファレンス を参照してください。