SpreadsheetGrid
ガイド

ソートとフィルター

列フィルター(text / number / date / select / set / custom)とグローバルフィルター

ヘッダーの列メニューからソートとフィルターを操作できます。フィルター UI の種別は column.filterType で列ごとに指定します。

デモ

ToolbarRows: 40 / 40Columns: 6 / 6Sort: なし
#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Rows: 40 / 40Columns: 6 / 6
Active: なしSelection: なしCells: 0 / Rows: 0Cols: 0
  • 商品名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 }onChangeDate / 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 リファレンス を参照してください。

On this page