@alaarab/ogrid-mcp 2.4.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.
Files changed (52) hide show
  1. package/README.md +68 -0
  2. package/bundled-docs/api/README.md +94 -0
  3. package/bundled-docs/api/column-def.mdx +379 -0
  4. package/bundled-docs/api/components-column-chooser.mdx +310 -0
  5. package/bundled-docs/api/components-column-header-filter.mdx +363 -0
  6. package/bundled-docs/api/components-datagrid-table.mdx +316 -0
  7. package/bundled-docs/api/components-pagination-controls.mdx +344 -0
  8. package/bundled-docs/api/components-sidebar.mdx +427 -0
  9. package/bundled-docs/api/components-status-bar.mdx +309 -0
  10. package/bundled-docs/api/grid-api.mdx +299 -0
  11. package/bundled-docs/api/js-api.mdx +198 -0
  12. package/bundled-docs/api/ogrid-props.mdx +244 -0
  13. package/bundled-docs/api/types.mdx +640 -0
  14. package/bundled-docs/features/cell-references.mdx +225 -0
  15. package/bundled-docs/features/column-chooser.mdx +279 -0
  16. package/bundled-docs/features/column-groups.mdx +290 -0
  17. package/bundled-docs/features/column-pinning.mdx +282 -0
  18. package/bundled-docs/features/column-reordering.mdx +359 -0
  19. package/bundled-docs/features/column-types.mdx +181 -0
  20. package/bundled-docs/features/context-menu.mdx +216 -0
  21. package/bundled-docs/features/csv-export.mdx +227 -0
  22. package/bundled-docs/features/editing.mdx +377 -0
  23. package/bundled-docs/features/filtering.mdx +330 -0
  24. package/bundled-docs/features/formulas.mdx +381 -0
  25. package/bundled-docs/features/grid-api.mdx +311 -0
  26. package/bundled-docs/features/keyboard-navigation.mdx +236 -0
  27. package/bundled-docs/features/pagination.mdx +245 -0
  28. package/bundled-docs/features/performance.mdx +433 -0
  29. package/bundled-docs/features/row-selection.mdx +256 -0
  30. package/bundled-docs/features/server-side-data.mdx +291 -0
  31. package/bundled-docs/features/sidebar.mdx +234 -0
  32. package/bundled-docs/features/sorting.mdx +241 -0
  33. package/bundled-docs/features/spreadsheet-selection.mdx +201 -0
  34. package/bundled-docs/features/status-bar.mdx +205 -0
  35. package/bundled-docs/features/toolbar.mdx +284 -0
  36. package/bundled-docs/features/virtual-scrolling.mdx +624 -0
  37. package/bundled-docs/getting-started/installation.mdx +216 -0
  38. package/bundled-docs/getting-started/overview.mdx +151 -0
  39. package/bundled-docs/getting-started/quick-start.mdx +425 -0
  40. package/bundled-docs/getting-started/vanilla-js.mdx +191 -0
  41. package/bundled-docs/guides/accessibility.mdx +550 -0
  42. package/bundled-docs/guides/controlled-vs-uncontrolled.mdx +153 -0
  43. package/bundled-docs/guides/custom-cell-editors.mdx +201 -0
  44. package/bundled-docs/guides/framework-showcase.mdx +200 -0
  45. package/bundled-docs/guides/mcp-live-testing.mdx +291 -0
  46. package/bundled-docs/guides/mcp.mdx +172 -0
  47. package/bundled-docs/guides/migration-from-ag-grid.mdx +223 -0
  48. package/bundled-docs/guides/theming.mdx +211 -0
  49. package/dist/esm/bridge-client.d.ts +87 -0
  50. package/dist/esm/bridge-client.js +162 -0
  51. package/dist/esm/index.js +1060 -0
  52. package/package.json +43 -0
@@ -0,0 +1,377 @@
1
+ ---
2
+ sidebar_position: 4
3
+ title: Editing & Clipboard
4
+ description: Inline editing, clipboard operations, fill handle, and undo/redo
5
+ ---
6
+
7
+
8
+ # Editing & Clipboard
9
+
10
+ OGrid supports inline cell editing, clipboard copy/paste, drag-to-fill, and undo/redo -- all working together through `onCellValueChanged` and `valueParser`.
11
+
12
+ ## Live Demo
13
+
14
+ <CellEditingDemo />
15
+
16
+ :::tip Try it in your framework
17
+ The demo above uses Radix UI for styling. To see this feature with your framework's design system (Fluent UI, Material UI, Vuetify, PrimeNG, etc.), click **"Open in online demo"** below the demo.
18
+ :::
19
+
20
+ ## Quick Example
21
+
22
+ <Tabs groupId="framework">
23
+ <TabItem value="react" label="React" default>
24
+
25
+ ```tsx
26
+
27
+ const DEPTS = ['Engineering', 'Marketing', 'Sales', 'Finance', 'Operations'];
28
+ const STATUSES = ['Active', 'Draft', 'Archived'];
29
+
30
+ const columns = [
31
+ { columnId: 'name', name: 'Name', editable: true },
32
+ {
33
+ columnId: 'department',
34
+ name: 'Department',
35
+ editable: true,
36
+ cellEditor: 'richSelect',
37
+ cellEditorParams: { values: DEPTS },
38
+ },
39
+ {
40
+ columnId: 'status',
41
+ name: 'Status',
42
+ editable: true,
43
+ cellEditor: 'select',
44
+ cellEditorParams: { values: STATUSES },
45
+ },
46
+ {
47
+ columnId: 'salary',
48
+ name: 'Salary',
49
+ editable: true,
50
+ type: 'numeric',
51
+ valueParser: ({ newValue }) => {
52
+ const num = Number(newValue);
53
+ return isNaN(num) || num < 0 ? undefined : num; // reject invalid
54
+ },
55
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
56
+ },
57
+ ];
58
+
59
+ function App() {
60
+ const [data, setData] = useState(initialData);
61
+ const { handleCellValueChanged, undo, redo, canUndo, canRedo } = useUndoRedo({
62
+ data, setData, getRowId: (item) => item.id,
63
+ });
64
+
65
+ return (
66
+ <OGrid
67
+ columns={columns}
68
+ data={data}
69
+ getRowId={(item) => item.id}
70
+ editable
71
+ onCellValueChanged={handleCellValueChanged}
72
+ onUndo={undo}
73
+ onRedo={redo}
74
+ canUndo={canUndo}
75
+ canRedo={canRedo}
76
+ />
77
+ );
78
+ }
79
+ ```
80
+
81
+ :::tip Switching UI libraries
82
+ The `OGrid` component has the same props across all React UI packages. To switch, just change the import:
83
+
84
+ - **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
85
+ - **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` — wrap in `<FluentProvider>`
86
+ - **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` — wrap in `<ThemeProvider>`
87
+ :::
88
+
89
+ </TabItem>
90
+ <TabItem value="angular" label="Angular">
91
+
92
+ ```typescript
93
+
94
+ const DEPTS = ['Engineering', 'Marketing', 'Sales', 'Finance', 'Operations'];
95
+ const STATUSES = ['Active', 'Draft', 'Archived'];
96
+
97
+ @Component({
98
+ standalone: true,
99
+ imports: [OGridComponent],
100
+ template: `<ogrid [props]="gridProps" />`
101
+ })
102
+ export class GridComponent {
103
+ gridProps = {
104
+ columns: [
105
+ { columnId: 'name', name: 'Name', editable: true },
106
+ {
107
+ columnId: 'department', name: 'Department', editable: true,
108
+ cellEditor: 'richSelect', cellEditorParams: { values: DEPTS },
109
+ },
110
+ {
111
+ columnId: 'status', name: 'Status', editable: true,
112
+ cellEditor: 'select', cellEditorParams: { values: STATUSES },
113
+ },
114
+ {
115
+ columnId: 'salary', name: 'Salary', editable: true, type: 'numeric',
116
+ valueParser: ({ newValue }: any) => {
117
+ const num = Number(newValue);
118
+ return isNaN(num) || num < 0 ? undefined : num;
119
+ },
120
+ valueFormatter: (v: unknown) => `$${Number(v).toLocaleString()}`,
121
+ },
122
+ ] as IColumnDef<Person>[],
123
+ data: initialData,
124
+ getRowId: (item: Person) => item.id,
125
+ editable: true,
126
+ };
127
+ }
128
+ ```
129
+
130
+ :::tip Switching UI libraries
131
+ Same component API across Angular packages. To switch, just change the import:
132
+
133
+ - **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
134
+ - **Angular Material**: `from '@alaarab/ogrid-angular-material'`
135
+ - **PrimeNG**: `from '@alaarab/ogrid-angular-primeng'`
136
+
137
+ All components are standalone — no NgModule required.
138
+ :::
139
+
140
+ </TabItem>
141
+ <TabItem value="vue" label="Vue">
142
+
143
+ ```vue
144
+ <script setup lang="ts">
145
+
146
+ const DEPTS = ['Engineering', 'Marketing', 'Sales', 'Finance', 'Operations'];
147
+ const STATUSES = ['Active', 'Draft', 'Archived'];
148
+
149
+ const columns: IColumnDef<Person>[] = [
150
+ { columnId: 'name', name: 'Name', editable: true },
151
+ {
152
+ columnId: 'department', name: 'Department', editable: true,
153
+ cellEditor: 'richSelect', cellEditorParams: { values: DEPTS },
154
+ },
155
+ {
156
+ columnId: 'status', name: 'Status', editable: true,
157
+ cellEditor: 'select', cellEditorParams: { values: STATUSES },
158
+ },
159
+ {
160
+ columnId: 'salary', name: 'Salary', editable: true, type: 'numeric',
161
+ valueParser: ({ newValue }) => {
162
+ const num = Number(newValue);
163
+ return isNaN(num) || num < 0 ? undefined : num;
164
+ },
165
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
166
+ },
167
+ ];
168
+
169
+ const gridProps = {
170
+ columns,
171
+ data: initialData,
172
+ getRowId: (item) => item.id,
173
+ editable: true,
174
+ };
175
+ </script>
176
+
177
+ <template>
178
+ <OGrid :gridProps="gridProps" />
179
+ </template>
180
+ ```
181
+
182
+ :::tip Switching UI libraries
183
+ Same component API across Vue packages. To switch, just change the import:
184
+
185
+ - **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
186
+ - **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` — wrap in `<v-app>` for theming
187
+ - **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
188
+ :::
189
+
190
+ </TabItem>
191
+ <TabItem value="js" label="Vanilla JS">
192
+
193
+ ```js
194
+
195
+ const DEPTS = ['Engineering', 'Marketing', 'Sales', 'Finance', 'Operations'];
196
+ const STATUSES = ['Active', 'Draft', 'Archived'];
197
+
198
+ const grid = new OGrid(document.getElementById('grid'), {
199
+ columns: [
200
+ { columnId: 'name', name: 'Name', editable: true },
201
+ {
202
+ columnId: 'department',
203
+ name: 'Department',
204
+ editable: true,
205
+ cellEditor: 'richSelect',
206
+ cellEditorParams: { values: DEPTS },
207
+ },
208
+ {
209
+ columnId: 'status',
210
+ name: 'Status',
211
+ editable: true,
212
+ cellEditor: 'select',
213
+ cellEditorParams: { values: STATUSES },
214
+ },
215
+ {
216
+ columnId: 'salary',
217
+ name: 'Salary',
218
+ editable: true,
219
+ type: 'numeric',
220
+ valueParser: ({ newValue }) => {
221
+ const num = Number(newValue);
222
+ return isNaN(num) || num < 0 ? undefined : num;
223
+ },
224
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
225
+ },
226
+ ],
227
+ data: initialData,
228
+ getRowId: (item) => item.id,
229
+ editable: true,
230
+ });
231
+
232
+ // Listen for cell edits
233
+ grid.on('cellValueChanged', (event) => {
234
+ console.log(`${event.columnId} changed to ${event.newValue}`);
235
+ });
236
+ ```
237
+
238
+ </TabItem>
239
+ </Tabs>
240
+
241
+ This single setup gives you inline editing, clipboard paste, fill handle, and full undo/redo.
242
+
243
+ ## Inline Editing
244
+
245
+ Double-click or press **F2** to edit. **Enter** commits and moves down, **Tab** commits and moves right, **Escape** cancels.
246
+
247
+ ### Built-in Editors
248
+
249
+ | `cellEditor` | Description |
250
+ |--------------|-------------|
251
+ | `'text'` (default) | Standard text input |
252
+ | `'select'` | Dropdown from `cellEditorParams.values` |
253
+ | `'checkbox'` | Toggle boolean |
254
+ | `'richSelect'` | Searchable dropdown with keyboard nav |
255
+
256
+ ### Rich Select
257
+
258
+ ```tsx
259
+ {
260
+ columnId: 'category',
261
+ editable: true,
262
+ cellEditor: 'richSelect',
263
+ cellEditorParams: {
264
+ values: ['Engineering', 'Design', 'Marketing', 'Sales'],
265
+ formatValue: (v) => `Dept: ${v}`,
266
+ },
267
+ }
268
+ ```
269
+
270
+ ### Per-Row Editability
271
+
272
+ ```tsx
273
+ { columnId: 'name', editable: (item) => item.status !== 'locked' }
274
+ ```
275
+
276
+ ### Custom Editor
277
+
278
+ Pass a component to `cellEditor`. Use `cellEditorPopup: true` for a popover:
279
+
280
+ ```tsx
281
+ function DateEditor({ value, onValueChange, onCommit, onCancel }: ICellEditorProps<Task>) {
282
+ return (
283
+ <input
284
+ type="date"
285
+ value={value as string}
286
+ onChange={(e) => onValueChange(e.target.value)}
287
+ onKeyDown={(e) => {
288
+ if (e.key === 'Enter') onCommit();
289
+ if (e.key === 'Escape') onCancel();
290
+ }}
291
+ autoFocus
292
+ />
293
+ );
294
+ }
295
+
296
+ // In column def:
297
+ { columnId: 'dueDate', editable: true, cellEditor: DateEditor, cellEditorPopup: true }
298
+ ```
299
+
300
+ ### Value Parsing
301
+
302
+ `valueParser` validates input for editing, paste, fill, and delete. Return `undefined` to reject:
303
+
304
+ ```tsx
305
+ { columnId: 'age', editable: true, type: 'numeric',
306
+ valueParser: ({ newValue }) => {
307
+ const num = Number(newValue);
308
+ return isNaN(num) || num < 0 || num > 150 ? undefined : num;
309
+ },
310
+ }
311
+ ```
312
+
313
+ ## Clipboard
314
+
315
+ <ClipboardDemo />
316
+
317
+ Standard keyboard shortcuts on selected cells:
318
+
319
+ | Key | Action | Requires `editable` |
320
+ |-----|--------|-------------------|
321
+ | Ctrl+C (Cmd+C) | Copy as tab-delimited text | No |
322
+ | Ctrl+V (Cmd+V) | Paste into cells | Yes |
323
+ | Ctrl+X (Cmd+X) | Cut (copy + clear) | Yes |
324
+ | Delete / Backspace | Clear selected cells | Yes |
325
+
326
+ - **Copy** uses `valueFormatter` so copied values match the display.
327
+ - **Paste** calls `valueParser` on each value and fires `onCellValueChanged` per cell.
328
+
329
+ ## Fill Handle
330
+
331
+ <FillHandleDemo />
332
+
333
+ A small square at the bottom-right of the selection. Drag it down to fill cells with the source value (like Excel).
334
+
335
+ - Requires `editable={true}` and `cellSelection={true}` (default).
336
+ - Calls `valueParser` per cell -- return `undefined` to skip.
337
+ - Each filled cell fires `onCellValueChanged`.
338
+ - Uses `requestAnimationFrame` for smooth drag performance.
339
+
340
+ ## Undo / Redo
341
+
342
+ The `useUndoRedo` hook tracks edit history (including paste, fill, and delete). See the quick example above.
343
+
344
+ | Hook Input | Type | Description |
345
+ |------------|------|-------------|
346
+ | `data` | `T[]` | Current data array |
347
+ | `setData` | `(data: T[]) => void` | State setter |
348
+ | `getRowId` | `(item: T) => RowId` | Row identifier |
349
+
350
+ | Hook Output | Type | Description |
351
+ |-------------|------|-------------|
352
+ | `handleCellValueChanged` | `(event) => void` | Wraps edits onto the undo stack |
353
+ | `undo` / `redo` | `() => void` | Revert / re-apply |
354
+ | `canUndo` / `canRedo` | `boolean` | Stack status |
355
+
356
+ **Shortcuts:** Ctrl+Z (undo), Ctrl+Y (redo). Also available in the right-click [context menu](./context-menu).
357
+
358
+ ## Props Summary
359
+
360
+ | Prop | Type | Description |
361
+ |------|------|-------------|
362
+ | `editable` | `boolean` | Enable editing grid-wide (on `OGrid`) |
363
+ | `editable` | `boolean \| (item: T) => boolean` | Per-column/row (on `IColumnDef`) |
364
+ | `cellEditor` | `'text' \| 'select' \| 'checkbox' \| 'richSelect' \| ComponentType` | Editor type |
365
+ | `cellEditorPopup` | `boolean` | Render custom editor in a popover |
366
+ | `cellEditorParams` | `CellEditorParams` | Editor parameters (e.g., `{ values: [...] }`) |
367
+ | `valueParser` | `(params) => unknown` | Validate on edit/paste/fill/delete. Return `undefined` to reject. |
368
+ | `valueFormatter` | `(value, item) => string` | Format display & copy output |
369
+ | `onCellValueChanged` | `(event) => void` | Fired after any cell value change |
370
+ | `onUndo` / `onRedo` | `() => void` | Undo/redo callbacks |
371
+ | `canUndo` / `canRedo` | `boolean` | Enable/disable undo/redo in context menu |
372
+
373
+ ## Related
374
+
375
+ - [Spreadsheet Selection](./spreadsheet-selection) -- select cells for editing
376
+ - [Context Menu](./context-menu) -- right-click menu with undo/redo
377
+ - [Custom Cell Editors](../guides/custom-cell-editors) -- advanced editor guide
@@ -0,0 +1,330 @@
1
+ ---
2
+ sidebar_position: 2
3
+ title: Filtering
4
+ description: Column-level filtering with text, multi-select, and people filter types.
5
+ ---
6
+
7
+
8
+ # Filtering
9
+
10
+ OGrid supports four built-in filter types -- **text**, **multiSelect**, **people**, and **date** -- configured per column. Filters appear as popovers triggered from column headers.
11
+
12
+ ## Live Demo
13
+
14
+ <FilteringDemo />
15
+
16
+ :::tip Framework-Specific Styling
17
+ The live demo above shows **React Radix UI** styling (lightweight default). To see how filter popovers look in your framework's design system, click the framework buttons above the demo to open online demo. Each framework renders filter UI with its native components (dropdowns, inputs, date pickers, etc.), so the styling matches your design system.
18
+ :::
19
+
20
+ ## Quick Example
21
+
22
+ <Tabs groupId="framework">
23
+ <TabItem value="react" label="React" default>
24
+
25
+ ```tsx
26
+
27
+ const columns = [
28
+ {
29
+ columnId: 'name',
30
+ name: 'Name',
31
+ filterable: { type: 'text' },
32
+ },
33
+ {
34
+ columnId: 'department',
35
+ name: 'Department',
36
+ filterable: { type: 'multiSelect', filterField: 'department' },
37
+ },
38
+ {
39
+ columnId: 'status',
40
+ name: 'Status',
41
+ filterable: { type: 'multiSelect', filterField: 'status' },
42
+ },
43
+ {
44
+ columnId: 'salary',
45
+ name: 'Salary',
46
+ type: 'numeric' as const,
47
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
48
+ },
49
+ ];
50
+
51
+ function App() {
52
+ return (
53
+ <OGrid
54
+ columns={columns}
55
+ data={people}
56
+ getRowId={(item) => item.id}
57
+ defaultPageSize={10}
58
+ />
59
+ );
60
+ }
61
+ ```
62
+
63
+ :::tip Switching UI libraries
64
+ The `OGrid` component has the same props across all React UI packages. To switch, just change the import:
65
+
66
+ - **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
67
+ - **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` — wrap in `<FluentProvider>`
68
+ - **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` — wrap in `<ThemeProvider>`
69
+ :::
70
+
71
+ </TabItem>
72
+ <TabItem value="angular" label="Angular">
73
+
74
+ ```typescript
75
+
76
+ @Component({
77
+ standalone: true,
78
+ imports: [OGridComponent],
79
+ template: `<ogrid [props]="gridProps" />`
80
+ })
81
+ export class GridComponent {
82
+ gridProps = {
83
+ columns: [
84
+ { columnId: 'name', name: 'Name', filterable: { type: 'text' } },
85
+ {
86
+ columnId: 'department', name: 'Department',
87
+ filterable: { type: 'multiSelect', filterField: 'department' },
88
+ },
89
+ {
90
+ columnId: 'status', name: 'Status',
91
+ filterable: { type: 'multiSelect', filterField: 'status' },
92
+ },
93
+ {
94
+ columnId: 'salary', name: 'Salary', type: 'numeric',
95
+ valueFormatter: (v: unknown) => `$${Number(v).toLocaleString()}`,
96
+ },
97
+ ] as IColumnDef<Person>[],
98
+ data: people,
99
+ getRowId: (item: Person) => item.id,
100
+ defaultPageSize: 10,
101
+ };
102
+ }
103
+ ```
104
+
105
+ :::tip Switching UI libraries
106
+ Same component API across Angular packages. To switch, just change the import:
107
+
108
+ - **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
109
+ - **Angular Material**: `from '@alaarab/ogrid-angular-material'`
110
+ - **PrimeNG**: `from '@alaarab/ogrid-angular-primeng'`
111
+
112
+ All components are standalone — no NgModule required.
113
+ :::
114
+
115
+ </TabItem>
116
+ <TabItem value="vue" label="Vue">
117
+
118
+ ```vue
119
+ <script setup lang="ts">
120
+
121
+ const columns: IColumnDef<Person>[] = [
122
+ { columnId: 'name', name: 'Name', filterable: { type: 'text' } },
123
+ {
124
+ columnId: 'department', name: 'Department',
125
+ filterable: { type: 'multiSelect', filterField: 'department' },
126
+ },
127
+ {
128
+ columnId: 'status', name: 'Status',
129
+ filterable: { type: 'multiSelect', filterField: 'status' },
130
+ },
131
+ {
132
+ columnId: 'salary', name: 'Salary', type: 'numeric',
133
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
134
+ },
135
+ ];
136
+
137
+ const gridProps = {
138
+ columns,
139
+ data: people,
140
+ getRowId: (item) => item.id,
141
+ defaultPageSize: 10,
142
+ };
143
+ </script>
144
+
145
+ <template>
146
+ <OGrid :gridProps="gridProps" />
147
+ </template>
148
+ ```
149
+
150
+ :::tip Switching UI libraries
151
+ Same component API across Vue packages. To switch, just change the import:
152
+
153
+ - **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
154
+ - **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` — wrap in `<v-app>` for theming
155
+ - **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
156
+ :::
157
+
158
+ </TabItem>
159
+ <TabItem value="js" label="Vanilla JS">
160
+
161
+ ```js
162
+
163
+ const grid = new OGrid(document.getElementById('grid'), {
164
+ columns: [
165
+ {
166
+ columnId: 'name',
167
+ name: 'Name',
168
+ filterable: { type: 'text' },
169
+ },
170
+ {
171
+ columnId: 'department',
172
+ name: 'Department',
173
+ filterable: { type: 'multiSelect', filterField: 'department' },
174
+ },
175
+ {
176
+ columnId: 'status',
177
+ name: 'Status',
178
+ filterable: { type: 'multiSelect', filterField: 'status' },
179
+ },
180
+ {
181
+ columnId: 'salary',
182
+ name: 'Salary',
183
+ type: 'numeric',
184
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}`,
185
+ },
186
+ ],
187
+ data: people,
188
+ getRowId: (item) => item.id,
189
+ pageSize: 10,
190
+ });
191
+ ```
192
+
193
+ </TabItem>
194
+ </Tabs>
195
+
196
+ ## How It Works
197
+
198
+ Set the `filterable` property on a column definition to an `IColumnFilterDef` object. The `type` field determines the filter UI:
199
+
200
+ ### Text Filter
201
+
202
+ A simple text input. Filters rows where the column value contains the search string (case-insensitive).
203
+
204
+ ```tsx
205
+ { columnId: 'name', name: 'Name', filterable: { type: 'text' } }
206
+ ```
207
+
208
+ ### Multi-Select Filter
209
+
210
+ A checkbox list with **Select All** and **Clear All** buttons. Users pick one or more values to include.
211
+
212
+ ```tsx
213
+ {
214
+ columnId: 'status',
215
+ name: 'Status',
216
+ filterable: {
217
+ type: 'multiSelect',
218
+ filterField: 'status', // field to filter on (defaults to columnId)
219
+ optionsSource: 'static', // 'static' | 'api' | 'years'
220
+ options: ['Active', 'Draft', 'Archived'],
221
+ },
222
+ }
223
+ ```
224
+
225
+ **Options sources:**
226
+
227
+ | `optionsSource` | Behavior |
228
+ |-----------------|----------|
229
+ | `'static'` | Uses the `options` array you provide. |
230
+ | `'api'` | Calls `dataSource.fetchFilterOptions(field)` to load options dynamically. |
231
+ | `'years'` | Auto-generates a year list (current year down, configurable via `yearsCount`). |
232
+ | _(omitted)_ | Client-side: unique values extracted from the data array. Server-side: uses `'api'`. |
233
+
234
+ ### People Filter
235
+
236
+ A search-as-you-type input that finds users via `dataSource.searchPeople(query)`. Displays `UserLike` objects with name, email, and photo.
237
+
238
+ ```tsx
239
+ {
240
+ columnId: 'assignee',
241
+ name: 'Assignee',
242
+ filterable: { type: 'people' },
243
+ }
244
+ ```
245
+
246
+ :::info
247
+ The people filter requires a `dataSource` with a `searchPeople` method. It is designed for directory lookups (e.g., Microsoft Graph, LDAP).
248
+ :::
249
+
250
+ ### Date Filter
251
+
252
+ A date range picker with **From** and **To** date inputs. Filters rows where the column value falls within the specified range.
253
+
254
+ ```tsx
255
+ {
256
+ columnId: 'createdDate',
257
+ name: 'Created',
258
+ type: 'date',
259
+ filterable: { type: 'date' },
260
+ }
261
+ ```
262
+
263
+ :::tip
264
+ Columns with `type: 'date'` automatically get date formatting, chronological sorting, and a native date input editor — no additional configuration needed.
265
+ :::
266
+
267
+ ### Controlled vs. Uncontrolled
268
+
269
+ - **Uncontrolled** -- the grid manages filter state internally. Filters reset when the component unmounts.
270
+ - **Controlled** -- pass `filters` and `onFiltersChange` to own the filter state externally (e.g., for URL sync or persistence).
271
+
272
+ ```tsx
273
+ function App() {
274
+ const [filters, setFilters] = useState<IFilters>({
275
+ status: { type: 'multiSelect', value: ['Active'] },
276
+ name: { type: 'text', value: 'John' },
277
+ });
278
+
279
+ return (
280
+ <OGrid
281
+ columns={columns}
282
+ data={tasks}
283
+ getRowId={(item) => item.id}
284
+ filters={filters}
285
+ onFiltersChange={setFilters}
286
+ />
287
+ );
288
+ }
289
+ ```
290
+
291
+ ### Server-Side Filtering
292
+
293
+ When using a `dataSource`, filter values are passed to `fetchPage()` in the `filters` param. The grid does not filter client-side.
294
+
295
+ ```tsx
296
+ const dataSource: IDataSource<Task> = {
297
+ fetchPage: async ({ filters, page, pageSize, sort }) => {
298
+ const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize) });
299
+ if (filters.status?.type === 'multiSelect') params.set('status', filters.status.value.join(','));
300
+ const res = await fetch(`/api/tasks?${params}`);
301
+ return res.json();
302
+ },
303
+ fetchFilterOptions: async (field) => {
304
+ const res = await fetch(`/api/tasks/filter-options/${field}`);
305
+ return res.json();
306
+ },
307
+ searchPeople: async (query) => {
308
+ const res = await fetch(`/api/users/search?q=${query}`);
309
+ return res.json();
310
+ },
311
+ };
312
+ ```
313
+
314
+ ## Props
315
+
316
+ | Prop | Type | Default | Description |
317
+ |------|------|---------|-------------|
318
+ | `filterable` | `IColumnFilterDef` | -- | Set on `IColumnDef`. Defines filter type and options. |
319
+ | `filterable.type` | `'text' \| 'multiSelect' \| 'people' \| 'date'` | -- | Filter UI type. |
320
+ | `filterable.filterField` | `string` | `columnId` | Field name used in the filter model. |
321
+ | `filterable.optionsSource` | `'api' \| 'static' \| 'years'` | auto | Where multi-select options come from. |
322
+ | `filterable.options` | `string[]` | -- | Static options for multi-select. |
323
+ | `filterable.yearsCount` | `number` | -- | Number of years to generate for `'years'` source. |
324
+ | `filters` | `IFilters` | -- | Controlled filter state on `OGrid`. |
325
+ | `onFiltersChange` | `(filters: IFilters) => void` | -- | Called when filters change (controlled mode). |
326
+
327
+ ## Related
328
+
329
+ - [Sorting](./sorting) -- sort filtered results
330
+ - [Pagination](./pagination) -- paginate filtered results