@ishibashi0112/spreadsheet-grid 0.7.0 → 0.9.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.
@@ -0,0 +1,4 @@
1
+ import type { GridColumn, GridSelection } from '../model/gridTypes';
2
+ export declare const resolveContextMenuColIndex: <T>(orderedColumns: GridColumn<T>[], columnKey: string) => number;
3
+ export declare const isContextMenuCellSelected: (selection: GridSelection, rowIndex: number, colIndex: number) => boolean;
4
+ export declare const isContextMenuRowSelected: (selection: GridSelection, rowIndex: number) => boolean;
@@ -0,0 +1,3 @@
1
+ import type { CsvExportScope } from '../model/gridTypes';
2
+ export type NormalizedCsvExportScope = 'view' | 'raw' | 'rendered' | 'selection';
3
+ export declare const normalizeExportScope: (scope: CsvExportScope) => NormalizedCsvExportScope;
@@ -8,5 +8,6 @@ export declare const isNumberColumnFilterValue: (value: ColumnFilterValue | unde
8
8
  export declare const buildNumberColumnFilterValue: (rawInput: string) => NumberColumnFilterValue | null;
9
9
  export declare const columnFilterValueToDraftText: (value: ColumnFilterValue | undefined) => string;
10
10
  export declare const applyNumberFilter: (cellValue: unknown, filterValue: unknown) => boolean;
11
+ export declare const rowMatchesGlobalText: <T>(row: T, columns: GridColumn<T>[], normalizedNeedle: string) => boolean;
11
12
  export declare const filterOrderByGlobalText: <T>(rows: T[], order: RowOrder, columns: GridColumn<T>[], globalText: string) => RowOrder;
12
13
  export declare const filterOrderByColumns: <T>(rows: T[], order: RowOrder, columns: GridColumn<T>[], columnFilters: Record<string, ColumnFilterValue>, numericKeys?: ReadonlyMap<string, Float64Array>) => RowOrder;
@@ -0,0 +1,16 @@
1
+ import type { GridRowKey, RowSelectionModel, RowSelectionState, SelectAllState } from '../model/gridTypes';
2
+ export type { RowSelectionModel, RowSelectionState, SelectAllState };
3
+ export declare const createEmptyRowSelection: () => RowSelectionState;
4
+ export declare const resolveIsRowSelected: (state: RowSelectionState, rowKey: GridRowKey) => boolean;
5
+ export declare const toggleRowKey: (state: RowSelectionState, rowKey: GridRowKey) => RowSelectionState;
6
+ export declare const selectSingleRow: (rowKey: GridRowKey) => RowSelectionState;
7
+ export declare const selectRowRange: (rangeKeys: GridRowKey[]) => RowSelectionState;
8
+ export declare const addRowRange: (base: RowSelectionState, rangeKeys: GridRowKey[]) => RowSelectionState;
9
+ export declare const selectAllRows: () => RowSelectionState;
10
+ export declare const clearRowSelection: () => RowSelectionState;
11
+ export declare const countSelectedRows: (state: RowSelectionState, totalRowCount: number) => number;
12
+ export declare const getSelectAllState: (state: RowSelectionState, totalRowCount: number) => SelectAllState;
13
+ export declare const rowSelectionToModel: (state: RowSelectionState) => RowSelectionModel;
14
+ export declare const rowSelectionFromModel: (model: RowSelectionModel) => RowSelectionState;
15
+ export declare const rowSelectionStateEquals: (a: RowSelectionState, b: RowSelectionState) => boolean;
16
+ export declare const rowSelectionModelEquals: (a: RowSelectionModel, b: RowSelectionModel) => boolean;
@@ -1,4 +1,4 @@
1
- import type { CellCoord, ColumnFilterValue, GridFilterState, GridSortEntry } from './gridTypes';
1
+ import type { CellCoord, ColumnFilterValue, GridFilterState, GridSortEntry, RowSelectionState } from './gridTypes';
2
2
  export type GridUiAction = {
3
3
  type: 'cell/activate';
4
4
  cell: CellCoord | null;
@@ -67,6 +67,9 @@ export type GridUiAction = {
67
67
  entries: GridSortEntry[];
68
68
  } | {
69
69
  type: 'sort/clear';
70
+ } | {
71
+ type: 'rowSelect/set';
72
+ state: RowSelectionState;
70
73
  };
71
74
  export declare const gridActions: {
72
75
  activateCell: (cell: CellCoord | null) => GridUiAction;
@@ -92,4 +95,5 @@ export declare const gridActions: {
92
95
  setAllFilters: (filters: GridFilterState) => GridUiAction;
93
96
  setSort: (entries: GridSortEntry[]) => GridUiAction;
94
97
  clearSort: () => GridUiAction;
98
+ setRowSelectionState: (state: RowSelectionState) => GridUiAction;
95
99
  };
@@ -47,6 +47,19 @@ export type GridSelection = {
47
47
  startCol: number;
48
48
  endCol: number;
49
49
  } | null;
50
+ export type RowSelectionState = {
51
+ mode: 'include' | 'exclude';
52
+ keys: ReadonlySet<GridRowKey>;
53
+ };
54
+ export type RowSelectionModel = {
55
+ type: 'include';
56
+ rowKeys: GridRowKey[];
57
+ } | {
58
+ type: 'exclude';
59
+ rowKeys: GridRowKey[];
60
+ };
61
+ export type RowSelectionMode = 'single' | 'multiple';
62
+ export type SelectAllState = 'none' | 'some' | 'all';
50
63
  export type GridFilterState = {
51
64
  globalText: string;
52
65
  columnFilters: Record<string, ColumnFilterValue>;
@@ -194,6 +207,7 @@ export type ColumnSelectionDragState = {
194
207
  export type GridUiState = {
195
208
  activeCell: CellCoord | null;
196
209
  selection: GridSelection;
210
+ rowSelection: RowSelectionState;
197
211
  editingCell: CellCoord | null;
198
212
  dragState: CellSelectionDragState | RowSelectionDragState | ColumnSelectionDragState | ColumnResizeDragState | null;
199
213
  columnWidths: Record<string, number>;
@@ -221,6 +235,7 @@ export type SpreadsheetGridDerivedSummary = {
221
235
  hasSorting: boolean;
222
236
  sortedColumnLabel: string | null;
223
237
  };
238
+ export type GlobalFilterStatus = 'idle' | 'filtering' | 'ready';
224
239
  export type SpreadsheetGridSlotContext<T> = {
225
240
  rows: T[];
226
241
  filteredRows: T[];
@@ -233,7 +248,57 @@ export type SpreadsheetGridSlotContext<T> = {
233
248
  activeCell: CellCoord | null;
234
249
  selection: GridSelection;
235
250
  derivedSummary: SpreadsheetGridDerivedSummary;
251
+ globalFilterStatus: GlobalFilterStatus;
252
+ globalFilterProgress: number;
253
+ };
254
+ export type GridContextMenuTarget<T> = {
255
+ type: 'cell';
256
+ rowIndex: number;
257
+ colIndex: number;
258
+ rowKey: GridRowKey;
259
+ row: T;
260
+ column: GridColumn<T>;
261
+ value: unknown;
262
+ } | {
263
+ type: 'rowHeader';
264
+ rowIndex: number;
265
+ rowKey: GridRowKey;
266
+ row: T;
267
+ };
268
+ export type GridContextMenuParams<T> = {
269
+ target: GridContextMenuTarget<T>;
270
+ clientX: number;
271
+ clientY: number;
272
+ selection: GridSelection;
273
+ activeCell: CellCoord | null;
274
+ isTargetSelected: boolean;
275
+ };
276
+ export type GridContextMenuActionItem = {
277
+ kind?: 'action';
278
+ id?: string;
279
+ label: ReactNode;
280
+ icon?: ReactNode;
281
+ disabled?: boolean;
282
+ danger?: boolean;
283
+ onSelect: () => void;
284
+ };
285
+ export type GridContextMenuLabelItem = {
286
+ kind: 'label';
287
+ id?: string;
288
+ label: ReactNode;
289
+ };
290
+ export type GridContextMenuSeparatorItem = {
291
+ kind: 'separator';
292
+ id?: string;
293
+ };
294
+ export type GridContextMenuCustomItem = {
295
+ kind: 'custom';
296
+ id?: string;
297
+ render: (ctx: {
298
+ close: () => void;
299
+ }) => ReactNode;
236
300
  };
301
+ export type GridContextMenuItem = GridContextMenuActionItem | GridContextMenuLabelItem | GridContextMenuSeparatorItem | GridContextMenuCustomItem;
237
302
  export type GridClassNames = {
238
303
  root?: string;
239
304
  toolbar?: string;
@@ -246,7 +311,21 @@ export type GridClassNames = {
246
311
  iconButton?: string;
247
312
  };
248
313
  export type ScrollAlign = 'auto' | 'start' | 'center' | 'end';
249
- export type CsvExportScope = 'all' | 'selection' | 'visible';
314
+ /**
315
+ * エクスポートの対象範囲です(exportCsv / downloadCsv / getExportData で共通)。
316
+ * - `'view'` : ビュー行全体(フィルター/ソート/列可視・固定順を反映)。**既定値**。スクロール位置に依存しません。
317
+ * - `'raw'` : 全ソース行(`rows` 配列順)。フィルターもソートも無視します(列は可視列・固定順に従います)。
318
+ * serverSide はソース行配列を持たないため `'view'` 相当へフォールバックします(console.warn を出力)。
319
+ * - `'rendered'` : 仮想化ウィンドウ(いま描画中の行のみ・オーバースキャン込み)。結果はスクロール位置に依存します。
320
+ * - `'selection'`: 現在の選択範囲(セル/行/列)。選択なしのときは空(CSV は空文字 / データは空配列)を返します。
321
+ */
322
+ export type CsvExportScope = 'view' | 'raw' | 'rendered' | 'selection' | DeprecatedCsvExportScope;
323
+ /**
324
+ * 後方互換エイリアスです(実行時挙動は新名称と完全同一): `'all'` → `'view'` / `'visible'` → `'rendered'`。
325
+ * @deprecated `'visible'` は「フィルターで見えている行」ではなく「描画中の行(仮想化ウィンドウ)」を
326
+ * 指すため誤読のもとになります。新規コードでは `'view'` / `'rendered'` を使用してください。
327
+ */
328
+ export type DeprecatedCsvExportScope = 'all' | 'visible';
250
329
  export type CsvExportOptions = {
251
330
  scope?: CsvExportScope;
252
331
  includeHeaders?: boolean;
@@ -310,6 +389,14 @@ export type SpreadsheetGridHandle<T> = {
310
389
  getExportData: (options?: GridExportOptions) => GridExportData;
311
390
  getState: () => GridState;
312
391
  applyState: (state: GridState) => void;
392
+ getRowSelection: () => RowSelectionModel;
393
+ setRowSelection: (model: RowSelectionModel) => void;
394
+ getSelectedRowKeys: () => GridRowKey[];
395
+ getSelectedRowData: () => T[];
396
+ getSelectedRowCount: () => number;
397
+ isRowSelected: (rowKey: GridRowKey) => boolean;
398
+ selectAllRows: () => void;
399
+ clearRowSelection: () => void;
313
400
  };
314
401
  export type SpreadsheetGridProps<T> = {
315
402
  ref?: Ref<SpreadsheetGridHandle<T>>;
@@ -333,6 +420,12 @@ export type SpreadsheetGridProps<T> = {
333
420
  readOnly?: boolean;
334
421
  canEditCell?: (rowIndex: number, colIndex: number, row: T, column: GridColumn<T>) => boolean;
335
422
  enableRangeSelection?: boolean;
423
+ enableRowSelection?: boolean;
424
+ rowSelectionMode?: RowSelectionMode;
425
+ enableSelectAllRows?: boolean;
426
+ rowSelection?: RowSelectionModel;
427
+ selectedRowKeys?: GridRowKey[];
428
+ onRowSelectionChange?: (model: RowSelectionModel) => void;
336
429
  enableGlobalFilter?: boolean;
337
430
  enableColumnFilter?: boolean;
338
431
  enableSorting?: boolean;
@@ -346,6 +439,8 @@ export type SpreadsheetGridProps<T> = {
346
439
  showTopBarSummary?: boolean;
347
440
  showTopBarCounts?: boolean;
348
441
  showTopBarFilter?: boolean;
442
+ globalFilterPlaceholder?: string;
443
+ globalFilterIcon?: ReactNode;
349
444
  showBottomBar?: boolean;
350
445
  showBottomBarCounts?: boolean;
351
446
  renderTopBar?: (context: SpreadsheetGridSlotContext<T>) => ReactNode;
@@ -353,4 +448,7 @@ export type SpreadsheetGridProps<T> = {
353
448
  className?: string;
354
449
  classNames?: GridClassNames;
355
450
  getRowClassName?: (row: T, rowIndex: number) => string | undefined;
451
+ enableContextMenu?: boolean;
452
+ getContextMenuItems?: (params: GridContextMenuParams<T>) => GridContextMenuItem[];
453
+ onContextMenuOpen?: (params: GridContextMenuParams<T>) => void;
356
454
  };
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
- @layer ssg-base{.ssg-root{--ssg-accent:#3461c9;--ssg-accent-strong:#1d4ed8;--ssg-line:#f1f5f9;--ssg-line-strong:#e5e7eb;--ssg-divider:#d7dce3;--ssg-header-bg:#f8fafc;--ssg-row-hover-bg:#f1f5f9;--ssg-glyph:#94a3b8;--ssg-glyph-hover:#475569;--ssg-icon-hover-bg:#d6dee8;--ssg-icon-active-bg:#e3e9f5;--ssg-icon-active-bg-hover:#d4ddf0;--ssg-icon-active-border:#c7d3ea;--ssg-cell-bg:#fff;--ssg-cell-text:#0f172a;--ssg-readonly-bg:#f8fafc;--ssg-readonly-text:#64748b;--ssg-readonly-hover-bg:#eef2f7;--ssg-header-text:#334155;--ssg-header-hover-bg:#e2e8f0;--ssg-select-bg:#e3e9f5;--ssg-select-bg-hover:#ccd7ee;--ssg-selection-fill:#3461c914;--ssg-radius:8px;--ssg-icon-btn-size:20px;border:1px solid var(--ssg-divider);border-radius:var(--ssg-radius);background-color:var(--ssg-cell-bg);overflow:hidden;box-shadow:0 4px 14px #0f172a0a}.ssg-icon-btn{width:var(--ssg-icon-btn-size);height:var(--ssg-icon-btn-size);color:var(--ssg-glyph);cursor:pointer;background-color:#0000;border:1px solid #0000;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;font-size:11px;line-height:1;transition:background-color .1s,color .1s;display:inline-flex}.ssg-icon-btn:hover{background-color:var(--ssg-icon-hover-bg);color:var(--ssg-glyph-hover)}.ssg-icon-btn--active{border-color:var(--ssg-icon-active-border);background-color:var(--ssg-icon-active-bg);color:var(--ssg-accent)}.ssg-icon-btn--active:hover{background-color:var(--ssg-icon-active-bg-hover);color:var(--ssg-accent)}.ssg-body-cell{box-sizing:border-box;border-right:1px solid var(--ssg-line);border-bottom:1px solid var(--ssg-line-strong);background-color:var(--ssg-cell-bg);color:var(--ssg-cell-text);white-space:nowrap;cursor:default;-webkit-user-select:none;user-select:none;outline:none;align-items:center;padding:0 10px;display:flex;position:absolute;top:0;overflow:hidden}.ssg-body-cell--autoheight{white-space:normal;word-break:break-word;align-items:flex-start;overflow:visible}.ssg-body-cell--align-center{text-align:center;justify-content:center}.ssg-body-cell--align-right{text-align:right;justify-content:flex-end}.ssg-body-cell--row-hovered{background-color:var(--ssg-row-hover-bg)}.ssg-body-cell--readonly{background-color:var(--ssg-readonly-bg);color:var(--ssg-readonly-text)}.ssg-body-cell--readonly.ssg-body-cell--row-hovered{background-color:var(--ssg-readonly-hover-bg)}.ssg-header-row{z-index:6;background-color:var(--ssg-header-bg);position:sticky;top:0}.ssg-header-cell{box-sizing:border-box;border-right:1px solid var(--ssg-line);border-bottom:1px solid var(--ssg-divider);background-color:var(--ssg-header-bg);color:var(--ssg-header-text);align-items:center;gap:4px;padding:0 6px;font-size:13px;font-weight:600;display:flex;position:absolute;top:0}.ssg-header-cell--hovered{background-color:var(--ssg-header-hover-bg)}.ssg-header-cell--selected{background-color:var(--ssg-select-bg)}.ssg-header-cell--selected.ssg-header-cell--hovered{background-color:var(--ssg-select-bg-hover)}.ssg-row-header-cell{z-index:5;justify-content:center;font-weight:500;left:0}.ssg-corner-cell{z-index:7;border-right:1px solid var(--ssg-line-strong);justify-content:center;padding:0;line-height:1;left:0}.ssg-active-cell-overlay{box-sizing:border-box;border:2px solid var(--ssg-accent);pointer-events:none;z-index:4}.ssg-selection-overlay{box-sizing:border-box;border:2px solid var(--ssg-accent);background-color:var(--ssg-selection-fill);pointer-events:none;z-index:2}.ssg-cell-editor{pointer-events:none;z-index:5}.ssg-cell-editor-input{box-sizing:border-box;border:2px solid var(--ssg-accent);width:100%;height:100%;color:var(--ssg-cell-text);background-color:var(--ssg-cell-bg);pointer-events:auto;outline:none;margin:0;padding:0 10px;font-size:16px}.ssg-bar--top{border-bottom:1px solid var(--ssg-divider)}.ssg-bar--bottom{border-top:1px solid var(--ssg-divider)}.ssg-bar-container{background-color:var(--ssg-header-bg);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:6px;padding:5px 10px;display:flex}.ssg-bar-group{flex-wrap:wrap;align-items:center;gap:4px;display:flex}.ssg-bar-leading{min-width:0;color:var(--ssg-header-text);flex:auto;font-size:12px}.ssg-bar-title{letter-spacing:.06em;text-transform:uppercase;color:var(--ssg-glyph);margin-right:2px;font-size:10px;font-weight:600}.ssg-bar-chip{color:var(--ssg-header-text);font-variant-numeric:tabular-nums;white-space:nowrap;background-color:#0000;border:none;border-radius:5px;align-items:center;padding:1px 6px;font-size:11.5px;display:inline-flex}.ssg-bar-chip--emphasis{background-color:var(--ssg-header-hover-bg);color:var(--ssg-header-text);font-weight:600}.ssg-bar-input{box-sizing:border-box;background-color:var(--ssg-cell-bg);width:100%;min-width:0;color:var(--ssg-cell-text);caret-color:var(--ssg-cell-text);-webkit-text-fill-color:var(--ssg-cell-text);border:1px solid #cbd5e1;border-radius:6px;outline:none;padding:6px 10px;font-size:13px;line-height:1.4}.ssg-bar-input-group{align-items:center;gap:6px;width:100%;max-width:380px;display:flex}.ssg-bar-container>.ssg-bar-input-group:only-child{flex:auto;max-width:480px}.ssg-bar-clear-btn{background-color:var(--ssg-cell-bg);color:#475569;cursor:pointer;white-space:nowrap;border:1px solid #cbd5e1;border-radius:6px;flex:none;padding:6px 10px;font-size:12px;line-height:1}.ssg-popover{box-sizing:border-box;z-index:1000;background-color:#fff;border:1px solid #cbd5e1;border-radius:12px;flex-direction:column;padding:12px;display:flex;position:fixed;box-shadow:0 12px 28px #0f172a29}.ssg-popover-header{border-bottom:1px solid #e2e8f0;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;padding-bottom:8px;display:flex}.ssg-popover-title{color:#1e293b;-webkit-user-select:none;user-select:none;font-size:13px;font-weight:700}.ssg-popover-close{color:#64748b;cursor:pointer;background-color:#0000;border:none;border-radius:6px;justify-content:center;align-items:center;width:24px;height:24px;font-size:16px;line-height:1;display:flex}.ssg-popover-close:hover{background-color:#f1f5f9}.ssg-popover-footer{border-top:1px solid #e2e8f0;gap:8px;margin-top:8px;padding-top:8px;display:flex}.ssg-sort-panel{max-height:420px}.ssg-sort-list{flex-direction:column;flex:1;gap:6px;min-height:0;margin:0 -4px;padding:0 4px;display:flex;overflow-y:auto}.ssg-sort-empty{color:#94a3b8;-webkit-user-select:none;user-select:none;padding:10px 4px;font-size:12px}.ssg-sort-level{opacity:1;border-top:2px solid #0000;align-items:center;gap:6px;display:flex}.ssg-sort-level--drop-before{border-top-color:#2563eb}.ssg-sort-level--dragging{opacity:.4}.ssg-sort-handle{cursor:grab;touch-action:none;border-radius:6px;flex:none;justify-content:center;align-items:center;width:16px;height:26px;display:flex}.ssg-sort-handle--dragging{cursor:grabbing}.ssg-sort-handle--disabled{cursor:default}.ssg-sort-handle:not(.ssg-sort-handle--disabled):hover{background-color:#f1f5f9}.ssg-sort-badge{color:#2563eb;-webkit-user-select:none;user-select:none;background-color:#eff6ff;border-radius:9px;flex:none;justify-content:center;align-items:center;width:18px;height:18px;font-size:11px;font-weight:700;display:flex}.ssg-sort-select{box-sizing:border-box;color:#334155;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;border-radius:6px;flex:1;min-width:0;padding:5px 6px;font-size:13px}.ssg-sort-select:disabled{cursor:default}.ssg-sort-dir-group{flex:none;display:flex}.ssg-sort-dir-btn{box-sizing:border-box;color:#475569;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;flex:none;padding:4px 9px;font-size:12px;line-height:1.4}.ssg-sort-dir-group>.ssg-sort-dir-btn:first-child{border-right:none;border-top-left-radius:6px;border-bottom-left-radius:6px}.ssg-sort-dir-group>.ssg-sort-dir-btn:last-child{border-top-right-radius:6px;border-bottom-right-radius:6px}.ssg-sort-dir-btn--active{color:#fff;background-color:#2563eb;border-color:#2563eb}.ssg-sort-dir-btn:disabled{color:#cbd5e1;cursor:default}.ssg-sort-delete{color:#64748b;cursor:pointer;background-color:#0000;border:none;border-radius:6px;flex:none;justify-content:center;align-items:center;width:26px;height:26px;font-size:15px;line-height:1;display:flex}.ssg-sort-delete:disabled{color:#cbd5e1;cursor:default}.ssg-sort-delete:not(:disabled):hover{color:#dc2626;background-color:#fef2f2}.ssg-sort-drop-end{border-top:2px solid #2563eb;height:0}.ssg-sort-footer-btn{box-sizing:border-box;color:#334155;cursor:pointer;text-align:center;-webkit-user-select:none;user-select:none;background-color:#0000;border:1px solid #e2e8f0;border-radius:8px;font-size:13px}.ssg-sort-footer-btn--add{flex:1;padding:7px 8px}.ssg-sort-footer-btn--clear{flex:none;padding:7px 12px}.ssg-sort-footer-btn:disabled{color:#cbd5e1;cursor:default}.ssg-sort-footer-btn:not(:disabled):hover{background-color:#f1f5f9}.ssg-sort-note{color:#94a3b8;-webkit-user-select:none;user-select:none;margin-top:8px;font-size:11px}.ssg-chooser-panel{max-height:420px}.ssg-chooser-search-row{align-items:center;gap:8px;margin-bottom:8px;display:flex}.ssg-chooser-master-btn{cursor:pointer;background:0 0;border:none;align-items:center;padding:0;display:flex}.ssg-chooser-master-btn:disabled{cursor:default}.ssg-chooser-search-field{flex:1;min-width:0;position:relative}.ssg-chooser-search-icon{color:#94a3b8;pointer-events:none;font-size:13px;line-height:1;position:absolute;top:50%;left:9px;transform:translateY(-50%)}.ssg-chooser-search-input{box-sizing:border-box;color:#334155;border:1px solid #cbd5e1;border-radius:8px;outline:none;width:100%;padding:7px 10px 7px 26px;font-size:13px}.ssg-chooser-list{flex:1;min-height:0;margin:0 -4px;padding:0 4px;overflow-y:auto}.ssg-chooser-empty{color:#94a3b8;-webkit-user-select:none;user-select:none;padding:10px 4px;font-size:12px}.ssg-chooser-section-heading{color:#94a3b8;letter-spacing:.4px;-webkit-user-select:none;user-select:none;padding:8px 4px 4px;font-size:11px;font-weight:700}.ssg-chooser-row{opacity:1;border-top:2px solid #0000;align-items:center;gap:4px;display:flex}.ssg-chooser-row--drop-before{border-top-color:#2563eb}.ssg-chooser-row--dragging{opacity:.4}.ssg-chooser-handle{cursor:grab;touch-action:none;border-radius:6px;flex:none;justify-content:center;align-items:center;width:18px;height:28px;display:flex}.ssg-chooser-handle--dragging{cursor:grabbing}.ssg-chooser-handle--disabled{cursor:default}.ssg-chooser-handle:not(.ssg-chooser-handle--disabled):hover{background-color:#f1f5f9}.ssg-chooser-toggle{box-sizing:border-box;cursor:pointer;text-align:left;background-color:#0000;border:none;border-radius:8px;flex:1;align-items:center;gap:10px;min-width:0;padding:7px 8px;display:flex}.ssg-chooser-toggle:disabled{cursor:default}.ssg-chooser-toggle:not(:disabled):hover{background-color:#f1f5f9}.ssg-chooser-label{color:#334155;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:13px;overflow:hidden}.ssg-chooser-label--muted{color:#94a3b8}.ssg-chooser-drop-end{border-top:2px solid #2563eb;height:0}.ssg-chooser-footer{border-top:1px solid #e2e8f0;margin-top:8px;padding-top:8px}.ssg-chooser-reset-btn{box-sizing:border-box;color:#334155;cursor:pointer;text-align:center;-webkit-user-select:none;user-select:none;background-color:#0000;border:1px solid #e2e8f0;border-radius:8px;width:100%;padding:7px 8px;font-size:13px;display:block}.ssg-chooser-reset-btn:disabled{color:#cbd5e1;cursor:default}.ssg-chooser-reset-btn:not(:disabled):hover{background-color:#f1f5f9}.ssg-chooser-note{color:#94a3b8;-webkit-user-select:none;user-select:none;margin-top:8px;font-size:11px}.ssg-checkbox{background-color:#fff;border:1.5px solid #94a3b8;border-radius:5px;flex:none;justify-content:center;align-items:center;width:18px;height:18px;transition:background-color 80ms,border-color 80ms;display:flex}.ssg-checkbox--filled{background-color:#2563eb;border-color:#2563eb}.ssg-checkbox--disabled{background-color:#f1f5f9;border-color:#cbd5e1}.ssg-checkbox-dash{background-color:#fff;border-radius:1px;width:9px;height:2px}.ssg-filter-popover{box-sizing:border-box;z-index:1000;background-color:#fff;border:1px solid #cbd5e1;border-radius:10px;padding:12px;position:fixed;box-shadow:0 10px 24px #0f172a1f}.ssg-filter-title{color:#334155;margin-bottom:8px;font-size:12px;font-weight:700}.ssg-filter-hint{color:#64748b;margin-bottom:8px;font-size:11px}.ssg-filter-meta{color:#64748b;margin-bottom:10px;font-size:11px}.ssg-filter-meta--ellipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.ssg-filter-collecting{text-align:center;color:#64748b;padding:24px;font-size:12px}.ssg-filter-input{box-sizing:border-box;border:1px solid #cbd5e1;border-radius:8px;outline:none;width:100%;margin-bottom:8px;padding:8px 10px}.ssg-filter-select{box-sizing:border-box;background-color:#fff;border:1px solid #cbd5e1;border-radius:8px;outline:none;width:100%;margin-bottom:8px;padding:8px 10px}.ssg-filter-selectall{cursor:pointer;color:#334155;-webkit-user-select:none;user-select:none;border-bottom:1px solid #e2e8f0;align-items:center;gap:8px;height:28px;padding:0 8px;font-size:12px;display:flex}.ssg-filter-selectall-label{font-weight:600}.ssg-filter-list{border:1px solid #e2e8f0;border-top:none;border-radius:0 0 8px 8px;height:208px;margin-bottom:8px;overflow-y:auto}.ssg-filter-empty{color:#94a3b8;text-align:center;padding:12px;font-size:12px}.ssg-filter-virt{width:100%;position:relative}.ssg-filter-option{box-sizing:border-box;cursor:pointer;color:#334155;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;padding:0 8px;font-size:12px;display:flex;position:absolute;top:0;left:0;right:0}.ssg-filter-option-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.ssg-filter-footer{justify-content:flex-end;gap:8px;display:flex}.ssg-filter-btn-secondary{color:#475569;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;border-radius:8px;padding:6px 10px;font-size:12px}.ssg-filter-btn-primary{color:#fff;cursor:pointer;background-color:#2563eb;border:1px solid #2563eb;border-radius:8px;padding:6px 10px;font-size:12px}.ssg-menu-panel{box-sizing:border-box;background-color:#fff;border:1px solid #cbd5e1;border-radius:10px;padding:8px;box-shadow:0 10px 24px #0f172a1f}.ssg-menu-title{color:#334155;text-overflow:ellipsis;white-space:nowrap;-webkit-user-select:none;user-select:none;border-bottom:1px solid #e2e8f0;margin-bottom:4px;padding:2px 8px 6px;font-size:12px;font-weight:700;overflow:hidden}.ssg-menu-item{box-sizing:border-box;color:#334155;text-align:left;cursor:pointer;background-color:#0000;border:none;border-radius:8px;align-items:center;gap:8px;width:100%;padding:7px 8px;font-size:13px;display:flex}.ssg-menu-item:disabled,.ssg-menu-item--disabled{color:#94a3b8;cursor:default}.ssg-menu-item--active,.ssg-menu-item:not(:disabled):not(.ssg-menu-item--disabled):hover{background-color:#f1f5f9}.ssg-menu-submenu-anchor{position:relative}.ssg-menu-icon{text-align:center;flex:none;width:14px}.ssg-menu-sort-icon{text-align:center;color:#94a3b8;flex:none;width:14px;font-weight:400}.ssg-menu-sort-icon--active{color:#2563eb;font-weight:700}.ssg-menu-check{text-align:center;color:#2563eb;flex:none;width:14px;font-weight:700}.ssg-menu-check--hidden{visibility:hidden}.ssg-menu-label{flex:1;min-width:0}.ssg-menu-label--sorted{color:#1d4ed8}.ssg-menu-caret{color:#94a3b8;flex:none;font-size:12px;line-height:1}.ssg-menu-separator{border-top:1px solid #e2e8f0;margin:4px 0}.ssg-menu-note{color:#94a3b8;-webkit-user-select:none;user-select:none;padding:4px 8px 2px;font-size:11px}.ssg-menu-footer{border-top:1px solid #e2e8f0;justify-content:flex-end;margin-top:4px;padding-top:6px;display:flex}.ssg-menu-close-btn{color:#475569;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;border-radius:8px;padding:5px 10px;font-size:12px}.ssg-body-row{contain:layout style paint;width:100%;position:absolute;top:0;left:0}.ssg-header-grip{color:#cbd5e1;cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;flex:none;justify-content:center;align-items:center;width:12px;height:20px;display:inline-flex}.ssg-header-status{flex:none;align-items:center;gap:3px;display:inline-flex}.ssg-header-actions{background-color:var(--ssg-header-bg);opacity:0;pointer-events:none;align-items:center;gap:2px;height:100%;padding:0 6px 0 14px;transition:opacity .1s;display:flex;position:absolute;top:0;right:0}.ssg-header-cell:hover .ssg-header-actions{opacity:1;pointer-events:auto;background-color:var(--ssg-header-hover-bg)}@media (hover:none){.ssg-header-actions{opacity:1;pointer-events:auto;background-color:#0000;height:auto;padding:0;position:static}}.ssg-header-actions .ssg-header-grip,.ssg-header-actions .ssg-icon-btn:not(.ssg-icon-btn--active){color:#475569}.ssg-header-label{min-width:0;color:var(--ssg-header-text);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.ssg-header-label--filtered{color:var(--ssg-accent-strong)}.ssg-header-sort{color:var(--ssg-accent);flex:none;align-items:center;gap:1px;font-size:12px;font-weight:700;line-height:1;display:inline-flex}.ssg-header-sort-priority{font-size:9px;font-weight:700;line-height:1}.ssg-header-filtered-mark{color:var(--ssg-accent-strong);flex:none;align-items:center;display:inline-flex}.ssg-header-resize{cursor:col-resize;z-index:3;width:6px;height:100%;position:absolute;top:0;right:-3px}.ssg-skeleton-rowheader{color:#94a3b8}.ssg-skeleton-bar{background-color:#e2e8f0;border-radius:4px;height:10px}.ssg-shell{background-color:var(--ssg-cell-bg);position:relative;overflow:hidden}.ssg-scroll-container{max-height:480px;position:relative;overflow:auto}.ssg-inner-row{display:flex}.ssg-center-pane{z-index:1;flex-shrink:0;position:relative}.ssg-empty-state{color:#94a3b8;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;height:160px;font-size:13px;display:flex;position:sticky;left:0}.ssg-autosize-overlay{pointer-events:none;z-index:5;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.ssg-autosize-pill{color:#f8fafc;-webkit-user-select:none;user-select:none;background-color:#0f172ad1;border-radius:8px;padding:8px 14px;font-size:13px;font-weight:600;box-shadow:0 6px 20px #0f172a40}}
1
+ @layer ssg-base{.ssg-root{--ssg-accent:#3461c9;--ssg-accent-strong:#1d4ed8;--ssg-line:#f1f5f9;--ssg-line-strong:#e5e7eb;--ssg-divider:#d7dce3;--ssg-header-bg:#f8fafc;--ssg-row-hover-bg:#f1f5f9;--ssg-glyph:#94a3b8;--ssg-glyph-hover:#475569;--ssg-icon-hover-bg:#d6dee8;--ssg-icon-active-bg:#e3e9f5;--ssg-icon-active-bg-hover:#d4ddf0;--ssg-icon-active-border:#c7d3ea;--ssg-cell-bg:#fff;--ssg-cell-text:#0f172a;--ssg-readonly-bg:#f8fafc;--ssg-readonly-text:#64748b;--ssg-readonly-hover-bg:#eef2f7;--ssg-header-text:#334155;--ssg-header-hover-bg:#e2e8f0;--ssg-select-bg:#e3e9f5;--ssg-select-bg-hover:#ccd7ee;--ssg-row-selected-bg:#eff4ff;--ssg-checkbox-border:#94a3b8;--ssg-checkbox-bg:#fff;--ssg-checkbox-checked-bg:var(--ssg-accent);--ssg-checkbox-checked-mark:#fff;--ssg-checkbox-hover-border:var(--ssg-accent);--ssg-selection-fill:#3461c914;--ssg-radius:8px;--ssg-icon-btn-size:20px;border:1px solid var(--ssg-divider);border-radius:var(--ssg-radius);background-color:var(--ssg-cell-bg);overflow:hidden;box-shadow:0 4px 14px #0f172a0a}.ssg-icon-btn{width:var(--ssg-icon-btn-size);height:var(--ssg-icon-btn-size);color:var(--ssg-glyph);cursor:pointer;background-color:#0000;border:1px solid #0000;border-radius:6px;flex:none;justify-content:center;align-items:center;padding:0;font-size:11px;line-height:1;transition:background-color .1s,color .1s;display:inline-flex}.ssg-icon-btn:hover{background-color:var(--ssg-icon-hover-bg);color:var(--ssg-glyph-hover)}.ssg-icon-btn--active{border-color:var(--ssg-icon-active-border);background-color:var(--ssg-icon-active-bg);color:var(--ssg-accent)}.ssg-icon-btn--active:hover{background-color:var(--ssg-icon-active-bg-hover);color:var(--ssg-accent)}.ssg-body-cell{box-sizing:border-box;border-right:1px solid var(--ssg-line);border-bottom:1px solid var(--ssg-line-strong);background-color:var(--ssg-cell-bg);color:var(--ssg-cell-text);white-space:nowrap;cursor:default;-webkit-user-select:none;user-select:none;outline:none;align-items:center;padding:0 10px;display:flex;position:absolute;top:0;overflow:hidden}.ssg-body-cell--autoheight{white-space:normal;word-break:break-word;align-items:flex-start;overflow:visible}.ssg-body-cell--align-center{text-align:center;justify-content:center}.ssg-body-cell--align-right{text-align:right;justify-content:flex-end}.ssg-body-cell--row-hovered{background-color:var(--ssg-row-hover-bg)}.ssg-body-cell--readonly{background-color:var(--ssg-readonly-bg);color:var(--ssg-readonly-text)}.ssg-body-cell--readonly.ssg-body-cell--row-hovered{background-color:var(--ssg-readonly-hover-bg)}.ssg-header-row{z-index:6;background-color:var(--ssg-header-bg);position:sticky;top:0}.ssg-header-cell{box-sizing:border-box;border-right:1px solid var(--ssg-line);border-bottom:1px solid var(--ssg-divider);background-color:var(--ssg-header-bg);color:var(--ssg-header-text);align-items:center;gap:4px;padding:0 6px;font-size:13px;font-weight:600;display:flex;position:absolute;top:0}.ssg-header-cell--hovered{background-color:var(--ssg-header-hover-bg)}.ssg-header-cell--selected{background-color:var(--ssg-select-bg)}.ssg-header-cell--selected.ssg-header-cell--hovered{background-color:var(--ssg-select-bg-hover)}.ssg-row-header-cell{z-index:5;justify-content:center;font-weight:500;left:0}.ssg-corner-cell{z-index:7;border-right:1px solid var(--ssg-line-strong);justify-content:center;padding:0;line-height:1;left:0}.ssg-row-checkbox{box-sizing:border-box;border:1.5px solid var(--ssg-checkbox-border);background-color:var(--ssg-checkbox-bg);border-radius:4px;width:16px;height:16px;transition:background-color .12s,border-color .12s;display:inline-block;position:relative}.ssg-row-checkbox[data-state=checked],.ssg-row-checkbox[data-state=indeterminate]{border-color:var(--ssg-checkbox-checked-bg);background-color:var(--ssg-checkbox-checked-bg)}.ssg-row-checkbox[data-state=checked]:after{content:"";border:solid var(--ssg-checkbox-checked-mark);border-width:0 2px 2px 0;width:4px;height:8px;position:absolute;top:1.5px;left:4.5px;transform:rotate(45deg)}.ssg-row-checkbox[data-state=indeterminate]:after{content:"";background-color:var(--ssg-checkbox-checked-mark);border-radius:1px;height:2px;position:absolute;top:6.5px;left:3px;right:3px}.ssg-row-checkbox[data-disabled=true]{opacity:.45}.ssg-row-header-cell--checkbox:hover .ssg-row-checkbox[data-state=unchecked],.ssg-corner-cell--checkbox:hover .ssg-row-checkbox[data-state=unchecked]{border-color:var(--ssg-checkbox-hover-border)}.ssg-active-cell-overlay{box-sizing:border-box;border:2px solid var(--ssg-accent);pointer-events:none;z-index:4}.ssg-selection-overlay{box-sizing:border-box;border:2px solid var(--ssg-accent);background-color:var(--ssg-selection-fill);pointer-events:none;z-index:2}.ssg-cell-editor{pointer-events:none;z-index:5}.ssg-cell-editor-input{box-sizing:border-box;border:2px solid var(--ssg-accent);width:100%;height:100%;color:var(--ssg-cell-text);background-color:var(--ssg-cell-bg);pointer-events:auto;outline:none;margin:0;padding:0 10px;font-size:16px}.ssg-bar--top{border-bottom:1px solid var(--ssg-divider)}.ssg-bar--bottom{border-top:1px solid var(--ssg-divider)}.ssg-bar-container{background-color:var(--ssg-header-bg);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:6px;padding:5px 10px;display:flex}.ssg-bar-group{flex-wrap:wrap;align-items:center;gap:4px;display:flex}.ssg-bar-leading{min-width:0;color:var(--ssg-header-text);flex:auto;font-size:12px}.ssg-bar-title{letter-spacing:.06em;text-transform:uppercase;color:var(--ssg-glyph);margin-right:2px;font-size:10px;font-weight:600}.ssg-bar-chip{color:var(--ssg-header-text);font-variant-numeric:tabular-nums;white-space:nowrap;background-color:#0000;border:none;border-radius:5px;align-items:center;padding:1px 6px;font-size:11.5px;display:inline-flex}.ssg-bar-chip--emphasis{background-color:var(--ssg-header-hover-bg);color:var(--ssg-header-text);font-weight:600}.ssg-bar-input{box-sizing:border-box;min-width:0;color:var(--ssg-cell-text);caret-color:var(--ssg-cell-text);-webkit-text-fill-color:var(--ssg-cell-text);background-color:#0000;border:none;outline:none;flex:auto;padding:6px 0;font-size:13px;line-height:1.4}.ssg-bar-input-group{box-sizing:border-box;background-color:var(--ssg-cell-bg);border:1px solid #cbd5e1;border-radius:6px;align-items:center;gap:6px;width:100%;max-width:380px;padding:0 8px;display:flex}.ssg-bar-input-group:focus-within{border-color:var(--ssg-accent)}.ssg-bar-container>.ssg-bar-input-group:only-child{flex:auto;max-width:480px}.ssg-bar-input-icon{color:var(--ssg-glyph);pointer-events:none;flex:none;justify-content:center;align-items:center;display:inline-flex}.ssg-bar-input-clear{width:18px;height:18px;color:var(--ssg-glyph);cursor:pointer;background-color:#0000;border:none;border-radius:4px;flex:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.ssg-bar-input-clear:hover{background-color:var(--ssg-row-hover-bg);color:var(--ssg-glyph-hover)}.ssg-popover{box-sizing:border-box;z-index:1000;background-color:#fff;border:1px solid #cbd5e1;border-radius:12px;flex-direction:column;padding:12px;display:flex;position:fixed;box-shadow:0 12px 28px #0f172a29}.ssg-popover-header{border-bottom:1px solid #e2e8f0;justify-content:space-between;align-items:center;gap:8px;margin-bottom:8px;padding-bottom:8px;display:flex}.ssg-popover-title{color:#1e293b;-webkit-user-select:none;user-select:none;font-size:13px;font-weight:700}.ssg-popover-close{color:#64748b;cursor:pointer;background-color:#0000;border:none;border-radius:6px;justify-content:center;align-items:center;width:24px;height:24px;font-size:16px;line-height:1;display:flex}.ssg-popover-close:hover{background-color:#f1f5f9}.ssg-popover-footer{border-top:1px solid #e2e8f0;gap:8px;margin-top:8px;padding-top:8px;display:flex}.ssg-sort-panel{max-height:420px}.ssg-sort-list{flex-direction:column;flex:1;gap:6px;min-height:0;margin:0 -4px;padding:0 4px;display:flex;overflow-y:auto}.ssg-sort-empty{color:#94a3b8;-webkit-user-select:none;user-select:none;padding:10px 4px;font-size:12px}.ssg-sort-level{opacity:1;border-top:2px solid #0000;align-items:center;gap:6px;display:flex}.ssg-sort-level--drop-before{border-top-color:#2563eb}.ssg-sort-level--dragging{opacity:.4}.ssg-sort-handle{cursor:grab;touch-action:none;border-radius:6px;flex:none;justify-content:center;align-items:center;width:16px;height:26px;display:flex}.ssg-sort-handle--dragging{cursor:grabbing}.ssg-sort-handle--disabled{cursor:default}.ssg-sort-handle:not(.ssg-sort-handle--disabled):hover{background-color:#f1f5f9}.ssg-sort-badge{color:#2563eb;-webkit-user-select:none;user-select:none;background-color:#eff6ff;border-radius:9px;flex:none;justify-content:center;align-items:center;width:18px;height:18px;font-size:11px;font-weight:700;display:flex}.ssg-sort-select{box-sizing:border-box;color:#334155;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;border-radius:6px;flex:1;min-width:0;padding:5px 6px;font-size:13px}.ssg-sort-select:disabled{cursor:default}.ssg-sort-dir-group{flex:none;display:flex}.ssg-sort-dir-btn{box-sizing:border-box;color:#475569;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;flex:none;padding:4px 9px;font-size:12px;line-height:1.4}.ssg-sort-dir-group>.ssg-sort-dir-btn:first-child{border-right:none;border-top-left-radius:6px;border-bottom-left-radius:6px}.ssg-sort-dir-group>.ssg-sort-dir-btn:last-child{border-top-right-radius:6px;border-bottom-right-radius:6px}.ssg-sort-dir-btn--active{color:#fff;background-color:#2563eb;border-color:#2563eb}.ssg-sort-dir-btn:disabled{color:#cbd5e1;cursor:default}.ssg-sort-delete{color:#64748b;cursor:pointer;background-color:#0000;border:none;border-radius:6px;flex:none;justify-content:center;align-items:center;width:26px;height:26px;font-size:15px;line-height:1;display:flex}.ssg-sort-delete:disabled{color:#cbd5e1;cursor:default}.ssg-sort-delete:not(:disabled):hover{color:#dc2626;background-color:#fef2f2}.ssg-sort-drop-end{border-top:2px solid #2563eb;height:0}.ssg-sort-footer-btn{box-sizing:border-box;color:#334155;cursor:pointer;text-align:center;-webkit-user-select:none;user-select:none;background-color:#0000;border:1px solid #e2e8f0;border-radius:8px;font-size:13px}.ssg-sort-footer-btn--add{flex:1;padding:7px 8px}.ssg-sort-footer-btn--clear{flex:none;padding:7px 12px}.ssg-sort-footer-btn:disabled{color:#cbd5e1;cursor:default}.ssg-sort-footer-btn:not(:disabled):hover{background-color:#f1f5f9}.ssg-sort-note{color:#94a3b8;-webkit-user-select:none;user-select:none;margin-top:8px;font-size:11px}.ssg-chooser-panel{max-height:420px}.ssg-chooser-search-row{align-items:center;gap:8px;margin-bottom:8px;display:flex}.ssg-chooser-master-btn{cursor:pointer;background:0 0;border:none;align-items:center;padding:0;display:flex}.ssg-chooser-master-btn:disabled{cursor:default}.ssg-chooser-search-field{flex:1;min-width:0;position:relative}.ssg-chooser-search-icon{color:#94a3b8;pointer-events:none;font-size:13px;line-height:1;position:absolute;top:50%;left:9px;transform:translateY(-50%)}.ssg-chooser-search-input{box-sizing:border-box;color:#334155;border:1px solid #cbd5e1;border-radius:8px;outline:none;width:100%;padding:7px 10px 7px 26px;font-size:13px}.ssg-chooser-list{flex:1;min-height:0;margin:0 -4px;padding:0 4px;overflow-y:auto}.ssg-chooser-empty{color:#94a3b8;-webkit-user-select:none;user-select:none;padding:10px 4px;font-size:12px}.ssg-chooser-section-heading{color:#94a3b8;letter-spacing:.4px;-webkit-user-select:none;user-select:none;padding:8px 4px 4px;font-size:11px;font-weight:700}.ssg-chooser-row{opacity:1;border-top:2px solid #0000;align-items:center;gap:4px;display:flex}.ssg-chooser-row--drop-before{border-top-color:#2563eb}.ssg-chooser-row--dragging{opacity:.4}.ssg-chooser-handle{cursor:grab;touch-action:none;border-radius:6px;flex:none;justify-content:center;align-items:center;width:18px;height:28px;display:flex}.ssg-chooser-handle--dragging{cursor:grabbing}.ssg-chooser-handle--disabled{cursor:default}.ssg-chooser-handle:not(.ssg-chooser-handle--disabled):hover{background-color:#f1f5f9}.ssg-chooser-toggle{box-sizing:border-box;cursor:pointer;text-align:left;background-color:#0000;border:none;border-radius:8px;flex:1;align-items:center;gap:10px;min-width:0;padding:7px 8px;display:flex}.ssg-chooser-toggle:disabled{cursor:default}.ssg-chooser-toggle:not(:disabled):hover{background-color:#f1f5f9}.ssg-chooser-label{color:#334155;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:13px;overflow:hidden}.ssg-chooser-label--muted{color:#94a3b8}.ssg-chooser-drop-end{border-top:2px solid #2563eb;height:0}.ssg-chooser-footer{border-top:1px solid #e2e8f0;margin-top:8px;padding-top:8px}.ssg-chooser-reset-btn{box-sizing:border-box;color:#334155;cursor:pointer;text-align:center;-webkit-user-select:none;user-select:none;background-color:#0000;border:1px solid #e2e8f0;border-radius:8px;width:100%;padding:7px 8px;font-size:13px;display:block}.ssg-chooser-reset-btn:disabled{color:#cbd5e1;cursor:default}.ssg-chooser-reset-btn:not(:disabled):hover{background-color:#f1f5f9}.ssg-chooser-note{color:#94a3b8;-webkit-user-select:none;user-select:none;margin-top:8px;font-size:11px}.ssg-checkbox{background-color:#fff;border:1.5px solid #94a3b8;border-radius:5px;flex:none;justify-content:center;align-items:center;width:18px;height:18px;transition:background-color 80ms,border-color 80ms;display:flex}.ssg-checkbox--filled{background-color:#2563eb;border-color:#2563eb}.ssg-checkbox--disabled{background-color:#f1f5f9;border-color:#cbd5e1}.ssg-checkbox-dash{background-color:#fff;border-radius:1px;width:9px;height:2px}.ssg-filter-popover{box-sizing:border-box;z-index:1000;background-color:#fff;border:1px solid #cbd5e1;border-radius:10px;padding:12px;position:fixed;box-shadow:0 10px 24px #0f172a1f}.ssg-filter-title{color:#334155;margin-bottom:8px;font-size:12px;font-weight:700}.ssg-filter-hint{color:#64748b;margin-bottom:8px;font-size:11px}.ssg-filter-meta{color:#64748b;margin-bottom:10px;font-size:11px}.ssg-filter-meta--ellipsis{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.ssg-filter-collecting{text-align:center;color:#64748b;padding:24px;font-size:12px}.ssg-filter-input{box-sizing:border-box;border:1px solid #cbd5e1;border-radius:8px;outline:none;width:100%;margin-bottom:8px;padding:8px 10px}.ssg-filter-select{box-sizing:border-box;background-color:#fff;border:1px solid #cbd5e1;border-radius:8px;outline:none;width:100%;margin-bottom:8px;padding:8px 10px}.ssg-filter-selectall{cursor:pointer;color:#334155;-webkit-user-select:none;user-select:none;border-bottom:1px solid #e2e8f0;align-items:center;gap:8px;height:28px;padding:0 8px;font-size:12px;display:flex}.ssg-filter-selectall-label{font-weight:600}.ssg-filter-list{border:1px solid #e2e8f0;border-top:none;border-radius:0 0 8px 8px;height:208px;margin-bottom:8px;overflow-y:auto}.ssg-filter-empty{color:#94a3b8;text-align:center;padding:12px;font-size:12px}.ssg-filter-virt{width:100%;position:relative}.ssg-filter-option{box-sizing:border-box;cursor:pointer;color:#334155;-webkit-user-select:none;user-select:none;align-items:center;gap:8px;padding:0 8px;font-size:12px;display:flex;position:absolute;top:0;left:0;right:0}.ssg-filter-option-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.ssg-filter-footer{justify-content:flex-end;gap:8px;display:flex}.ssg-filter-btn-secondary{color:#475569;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;border-radius:8px;padding:6px 10px;font-size:12px}.ssg-filter-btn-primary{color:#fff;cursor:pointer;background-color:#2563eb;border:1px solid #2563eb;border-radius:8px;padding:6px 10px;font-size:12px}.ssg-menu-panel{box-sizing:border-box;background-color:#fff;border:1px solid #cbd5e1;border-radius:10px;padding:8px;box-shadow:0 10px 24px #0f172a1f}.ssg-menu-title{color:#334155;text-overflow:ellipsis;white-space:nowrap;-webkit-user-select:none;user-select:none;border-bottom:1px solid #e2e8f0;margin-bottom:4px;padding:2px 8px 6px;font-size:12px;font-weight:700;overflow:hidden}.ssg-menu-item{box-sizing:border-box;color:#334155;text-align:left;cursor:pointer;background-color:#0000;border:none;border-radius:8px;align-items:center;gap:8px;width:100%;padding:7px 8px;font-size:13px;display:flex}.ssg-menu-item:disabled,.ssg-menu-item--disabled{color:#94a3b8;cursor:default}.ssg-menu-item--active,.ssg-menu-item:not(:disabled):not(.ssg-menu-item--disabled):hover{background-color:#f1f5f9}.ssg-menu-section-label{color:#94a3b8;-webkit-user-select:none;user-select:none;padding:6px 8px 2px;font-size:11px;font-weight:600}.ssg-menu-item--danger{color:#dc2626}.ssg-menu-item--danger:not(:disabled):not(.ssg-menu-item--disabled):hover{background-color:#fef2f2}.ssg-menu-submenu-anchor{position:relative}.ssg-menu-icon{text-align:center;flex:none;width:14px}.ssg-menu-sort-icon{text-align:center;color:#94a3b8;flex:none;width:14px;font-weight:400}.ssg-menu-sort-icon--active{color:#2563eb;font-weight:700}.ssg-menu-check{text-align:center;color:#2563eb;flex:none;width:14px;font-weight:700}.ssg-menu-check--hidden{visibility:hidden}.ssg-menu-label{flex:1;min-width:0}.ssg-menu-label--sorted{color:#1d4ed8}.ssg-menu-caret{color:#94a3b8;flex:none;font-size:12px;line-height:1}.ssg-menu-separator{border-top:1px solid #e2e8f0;margin:4px 0}.ssg-menu-note{color:#94a3b8;-webkit-user-select:none;user-select:none;padding:4px 8px 2px;font-size:11px}.ssg-menu-footer{border-top:1px solid #e2e8f0;justify-content:flex-end;margin-top:4px;padding-top:6px;display:flex}.ssg-menu-close-btn{color:#475569;cursor:pointer;background-color:#fff;border:1px solid #cbd5e1;border-radius:8px;padding:5px 10px;font-size:12px}.ssg-body-row{contain:layout style paint;width:100%;position:absolute;top:0;left:0}.ssg-body-row--checked{background-color:var(--ssg-row-selected-bg)}.ssg-body-row--checked .ssg-body-cell:not(.ssg-body-cell--row-hovered){background-color:#0000}.ssg-header-grip{color:#cbd5e1;cursor:grab;touch-action:none;-webkit-user-select:none;user-select:none;flex:none;justify-content:center;align-items:center;width:12px;height:20px;display:inline-flex}.ssg-header-status{flex:none;align-items:center;gap:3px;display:inline-flex}.ssg-header-actions{background-color:var(--ssg-header-bg);opacity:0;pointer-events:none;align-items:center;gap:2px;height:100%;padding:0 6px 0 14px;transition:opacity .1s;display:flex;position:absolute;top:0;right:0}.ssg-header-cell:hover .ssg-header-actions{opacity:1;pointer-events:auto;background-color:var(--ssg-header-hover-bg)}@media (hover:none){.ssg-header-actions{opacity:1;pointer-events:auto;background-color:#0000;height:auto;padding:0;position:static}}.ssg-header-actions .ssg-header-grip,.ssg-header-actions .ssg-icon-btn:not(.ssg-icon-btn--active){color:#475569}.ssg-header-label{min-width:0;color:var(--ssg-header-text);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.ssg-header-label--filtered{color:var(--ssg-accent-strong)}.ssg-header-sort{color:var(--ssg-accent);flex:none;align-items:center;gap:1px;font-size:12px;font-weight:700;line-height:1;display:inline-flex}.ssg-header-sort-priority{font-size:9px;font-weight:700;line-height:1}.ssg-header-filtered-mark{color:var(--ssg-accent-strong);flex:none;align-items:center;display:inline-flex}.ssg-header-resize{cursor:col-resize;z-index:3;width:6px;height:100%;position:absolute;top:0;right:-3px}.ssg-skeleton-rowheader{color:#94a3b8}.ssg-skeleton-bar{background-color:#e2e8f0;border-radius:4px;height:10px}.ssg-shell{background-color:var(--ssg-cell-bg);position:relative;overflow:hidden}.ssg-scroll-container{max-height:480px;position:relative;overflow:auto}.ssg-inner-row{display:flex}.ssg-center-pane{z-index:1;flex-shrink:0;position:relative}.ssg-empty-state{color:#94a3b8;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;height:160px;font-size:13px;display:flex;position:sticky;left:0}.ssg-autosize-overlay{pointer-events:none;z-index:5;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.ssg-autosize-pill{color:#f8fafc;-webkit-user-select:none;user-select:none;background-color:#0f172ad1;border-radius:8px;padding:8px 14px;font-size:13px;font-weight:600;box-shadow:0 6px 20px #0f172a40}.ssg-filter-overlay{pointer-events:none;z-index:5;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.ssg-filter-overlay-chip{background-color:var(--ssg-cell-bg);border:1px solid var(--ssg-line-strong);color:var(--ssg-readonly-text);font-variant-numeric:tabular-nums;white-space:nowrap;-webkit-user-select:none;user-select:none;border-radius:999px;align-items:center;gap:8px;padding:6px 12px;font-size:12px;display:inline-flex;box-shadow:0 6px 20px #0f172a2e}.ssg-filter-overlay-text{text-align:center;min-width:5.5em}.ssg-filter-spinner{border:2px solid var(--ssg-line-strong);border-top-color:var(--ssg-accent);border-radius:50%;width:14px;height:14px;animation:.7s linear infinite ssg-filter-spin}@keyframes ssg-filter-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.ssg-filter-spinner{animation:none}}.ssg-col-drop-indicator{background-color:#0f172a;width:2px}.ssg-col-drop-indicator:before,.ssg-col-drop-indicator:after{content:"";background-color:#0f172a;width:7px;height:7px;position:absolute;left:50%;transform:translate(-50%)rotate(45deg)}.ssg-col-drop-indicator:before{top:-3px}.ssg-col-drop-indicator:after{bottom:-3px}}
2
2
  /*$vite$:1*/
@@ -0,0 +1,12 @@
1
+ import type { RefObject } from 'react';
2
+ import type { GridContextMenuItem } from '../model/gridTypes';
3
+ import type { CellContextMenuLayout } from '../hooks/useCellContextMenuController';
4
+ type CellContextMenuPopoverProps = {
5
+ isOpen: boolean;
6
+ items: GridContextMenuItem[];
7
+ layout: CellContextMenuLayout | null;
8
+ popoverRef: RefObject<HTMLDivElement | null>;
9
+ onRequestClose: () => void;
10
+ };
11
+ export declare function CellContextMenuPopover({ isOpen, items, layout, popoverRef, onRequestClose, }: CellContextMenuPopoverProps): import("react").ReactPortal | null;
12
+ export default CellContextMenuPopover;
@@ -1,9 +1,12 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type { SpreadsheetGridSlotContext } from '../model/gridTypes';
2
3
  type DefaultGridTopBarProps<T> = {
3
4
  context: SpreadsheetGridSlotContext<T>;
4
5
  showSummary?: boolean;
5
6
  showFilter?: boolean;
6
7
  showCounts?: boolean;
8
+ globalFilterPlaceholder?: string;
9
+ globalFilterIcon?: ReactNode;
7
10
  };
8
- export declare function DefaultGridTopBar<T>({ context, showSummary, showFilter, showCounts, }: DefaultGridTopBarProps<T>): import("react").JSX.Element;
11
+ export declare function DefaultGridTopBar<T>({ context, showSummary, showFilter, showCounts, globalFilterPlaceholder, globalFilterIcon, }: DefaultGridTopBarProps<T>): import("react").JSX.Element;
9
12
  export default DefaultGridTopBar;
@@ -1,6 +1,6 @@
1
1
  import type { CSSProperties, PointerEvent, ReactNode } from 'react';
2
2
  import type { SelectionSnapshot } from '../model/gridSelectors';
3
- import type { CellCoord, CellRenderState, GridColumn, RowModel, SpreadsheetGridProps } from '../model/gridTypes';
3
+ import type { CellCoord, CellRenderState, GridColumn, RowModel, RowSelectionState, SpreadsheetGridProps } from '../model/gridTypes';
4
4
  import type { PaneColumnEntry } from '../logic/geometry';
5
5
  import type { GridPaneKind } from './GridHeaderRow';
6
6
  type VirtualRowLike = {
@@ -22,6 +22,8 @@ type GridBodyLayerProps<T> = {
22
22
  rowHeaderCellStyle: CSSProperties;
23
23
  hoveredRowIndex: number | null;
24
24
  isWholeGridSelected: boolean;
25
+ enableRowSelection: boolean;
26
+ rowSelectionState: RowSelectionState;
25
27
  activeCell: CellCoord | null;
26
28
  editingCell: CellCoord | null;
27
29
  selectionSnapshot: SelectionSnapshot;
@@ -39,5 +41,5 @@ type GridBodyLayerProps<T> = {
39
41
  bodyRowClassName?: string;
40
42
  rowHeaderCellClassName?: string;
41
43
  };
42
- export declare function GridBodyLayer<T>({ pane, ownsRowHeader, leadingWidth, rowModel, virtualRows, virtualRowIndexes, renderEntries, rowHeight, autoHeight, isServerSide, rowHeaderCellStyle, hoveredRowIndex, isWholeGridSelected, activeCell, editingCell, selectionSnapshot, readOnly, canEditCell, onRowHeaderPointerDown, onRowHeaderPointerEnter, onRowHeaderPointerLeave, onCellPointerDown, onCellPointerEnter, onCellDoubleClick, renderCellContent, getRowClassName, bodyCellClassName, bodyRowClassName, rowHeaderCellClassName, }: GridBodyLayerProps<T>): import("react").JSX.Element;
44
+ export declare function GridBodyLayer<T>({ pane, ownsRowHeader, leadingWidth, rowModel, virtualRows, virtualRowIndexes, renderEntries, rowHeight, autoHeight, isServerSide, rowHeaderCellStyle, hoveredRowIndex, isWholeGridSelected, enableRowSelection, rowSelectionState, activeCell, editingCell, selectionSnapshot, readOnly, canEditCell, onRowHeaderPointerDown, onRowHeaderPointerEnter, onRowHeaderPointerLeave, onCellPointerDown, onCellPointerEnter, onCellDoubleClick, renderCellContent, getRowClassName, bodyCellClassName, bodyRowClassName, rowHeaderCellClassName, }: GridBodyLayerProps<T>): import("react").JSX.Element;
43
45
  export default GridBodyLayer;
@@ -1,6 +1,6 @@
1
1
  import { type CSSProperties, type MouseEvent, type PointerEvent } from 'react';
2
2
  import type { SelectionSnapshot } from '../model/gridSelectors';
3
- import type { ColumnFilterValue, GridColumn, GridSortState } from '../model/gridTypes';
3
+ import type { ColumnFilterValue, GridColumn, GridSortState, SelectAllState } from '../model/gridTypes';
4
4
  import type { PaneColumnEntry } from '../logic/geometry';
5
5
  export type GridPaneKind = 'left' | 'center' | 'right';
6
6
  type GridHeaderRowProps<T> = {
@@ -14,6 +14,8 @@ type GridHeaderRowProps<T> = {
14
14
  rowHeaderCellClassName?: string;
15
15
  isCornerHovered: boolean;
16
16
  isWholeGridSelected: boolean;
17
+ showSelectAllCheckbox: boolean;
18
+ selectAllState: SelectAllState;
17
19
  filteredRowsLength: number;
18
20
  visibleColumnsLength: number;
19
21
  renderEntries: PaneColumnEntry<T>[];
@@ -36,6 +38,6 @@ type GridHeaderRowProps<T> = {
36
38
  onColumnHeaderContextMenu: (column: GridColumn<T>, event: MouseEvent<HTMLDivElement>) => void;
37
39
  onColumnDragHandlePointerDown?: (column: GridColumn<T>, event: PointerEvent<HTMLSpanElement>) => void;
38
40
  };
39
- declare function GridHeaderRowInner<T>({ pane, ownsRowHeader, leadingWidth, headerHeight, rowHeaderCellStyle, headerRowClassName, headerCellClassName, rowHeaderCellClassName, isCornerHovered, isWholeGridSelected, filteredRowsLength, visibleColumnsLength, renderEntries, hoveredColumnIndex, selectionSnapshot, columnFilterValues, sortState, iconButtonClassName, onCornerPointerDown, onCornerPointerEnter, onCornerPointerLeave, onColumnHeaderPointerDown, onColumnHeaderPointerEnter, onColumnHeaderPointerLeave, onColumnResizePointerDown, enableColumnResize, enableColumnMenu, openedMenuColumnKey, onColumnMenuButtonPointerDown, onColumnHeaderContextMenu, onColumnDragHandlePointerDown, }: GridHeaderRowProps<T>): import("react").JSX.Element;
41
+ declare function GridHeaderRowInner<T>({ pane, ownsRowHeader, leadingWidth, headerHeight, rowHeaderCellStyle, headerRowClassName, headerCellClassName, rowHeaderCellClassName, isCornerHovered, isWholeGridSelected, showSelectAllCheckbox, selectAllState, filteredRowsLength, visibleColumnsLength, renderEntries, hoveredColumnIndex, selectionSnapshot, columnFilterValues, sortState, iconButtonClassName, onCornerPointerDown, onCornerPointerEnter, onCornerPointerLeave, onColumnHeaderPointerDown, onColumnHeaderPointerEnter, onColumnHeaderPointerLeave, onColumnResizePointerDown, enableColumnResize, enableColumnMenu, openedMenuColumnKey, onColumnMenuButtonPointerDown, onColumnHeaderContextMenu, onColumnDragHandlePointerDown, }: GridHeaderRowProps<T>): import("react").JSX.Element;
40
42
  export declare const GridHeaderRow: typeof GridHeaderRowInner;
41
43
  export default GridHeaderRow;
@@ -0,0 +1,8 @@
1
+ export type RowCheckboxState = 'unchecked' | 'checked' | 'indeterminate';
2
+ type RowSelectionCheckboxProps = {
3
+ state: RowCheckboxState;
4
+ disabled?: boolean;
5
+ };
6
+ declare function RowSelectionCheckboxInner({ state, disabled, }: RowSelectionCheckboxProps): import("react").JSX.Element;
7
+ export declare const RowSelectionCheckbox: import("react").MemoExoticComponent<typeof RowSelectionCheckboxInner>;
8
+ export default RowSelectionCheckbox;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ishibashi0112/spreadsheet-grid",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "React 19 製のカスタム仮想化データグリッド。100 万行対応の仮想化・3 ペイン固定列・ソート/フィルター・クライアント/サーバーサイド行モデル対応。",
5
5
  "keywords": [
6
6
  "react",