@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.
- package/README.md +68 -0
- package/bundled-docs/api/README.md +94 -0
- package/bundled-docs/api/column-def.mdx +379 -0
- package/bundled-docs/api/components-column-chooser.mdx +310 -0
- package/bundled-docs/api/components-column-header-filter.mdx +363 -0
- package/bundled-docs/api/components-datagrid-table.mdx +316 -0
- package/bundled-docs/api/components-pagination-controls.mdx +344 -0
- package/bundled-docs/api/components-sidebar.mdx +427 -0
- package/bundled-docs/api/components-status-bar.mdx +309 -0
- package/bundled-docs/api/grid-api.mdx +299 -0
- package/bundled-docs/api/js-api.mdx +198 -0
- package/bundled-docs/api/ogrid-props.mdx +244 -0
- package/bundled-docs/api/types.mdx +640 -0
- package/bundled-docs/features/cell-references.mdx +225 -0
- package/bundled-docs/features/column-chooser.mdx +279 -0
- package/bundled-docs/features/column-groups.mdx +290 -0
- package/bundled-docs/features/column-pinning.mdx +282 -0
- package/bundled-docs/features/column-reordering.mdx +359 -0
- package/bundled-docs/features/column-types.mdx +181 -0
- package/bundled-docs/features/context-menu.mdx +216 -0
- package/bundled-docs/features/csv-export.mdx +227 -0
- package/bundled-docs/features/editing.mdx +377 -0
- package/bundled-docs/features/filtering.mdx +330 -0
- package/bundled-docs/features/formulas.mdx +381 -0
- package/bundled-docs/features/grid-api.mdx +311 -0
- package/bundled-docs/features/keyboard-navigation.mdx +236 -0
- package/bundled-docs/features/pagination.mdx +245 -0
- package/bundled-docs/features/performance.mdx +433 -0
- package/bundled-docs/features/row-selection.mdx +256 -0
- package/bundled-docs/features/server-side-data.mdx +291 -0
- package/bundled-docs/features/sidebar.mdx +234 -0
- package/bundled-docs/features/sorting.mdx +241 -0
- package/bundled-docs/features/spreadsheet-selection.mdx +201 -0
- package/bundled-docs/features/status-bar.mdx +205 -0
- package/bundled-docs/features/toolbar.mdx +284 -0
- package/bundled-docs/features/virtual-scrolling.mdx +624 -0
- package/bundled-docs/getting-started/installation.mdx +216 -0
- package/bundled-docs/getting-started/overview.mdx +151 -0
- package/bundled-docs/getting-started/quick-start.mdx +425 -0
- package/bundled-docs/getting-started/vanilla-js.mdx +191 -0
- package/bundled-docs/guides/accessibility.mdx +550 -0
- package/bundled-docs/guides/controlled-vs-uncontrolled.mdx +153 -0
- package/bundled-docs/guides/custom-cell-editors.mdx +201 -0
- package/bundled-docs/guides/framework-showcase.mdx +200 -0
- package/bundled-docs/guides/mcp-live-testing.mdx +291 -0
- package/bundled-docs/guides/mcp.mdx +172 -0
- package/bundled-docs/guides/migration-from-ag-grid.mdx +223 -0
- package/bundled-docs/guides/theming.mdx +211 -0
- package/dist/esm/bridge-client.d.ts +87 -0
- package/dist/esm/bridge-client.js +162 -0
- package/dist/esm/index.js +1060 -0
- package/package.json +43 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 8
|
|
3
|
+
title: Accessibility
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Accessibility
|
|
7
|
+
|
|
8
|
+
OGrid is built with accessibility as a core principle, meeting WCAG 2.1 AA standards. All components support keyboard navigation, screen readers, and high contrast modes.
|
|
9
|
+
|
|
10
|
+
## WCAG 2.1 AA Compliance
|
|
11
|
+
|
|
12
|
+
OGrid implements the following WCAG 2.1 AA guidelines:
|
|
13
|
+
|
|
14
|
+
### Perceivable
|
|
15
|
+
|
|
16
|
+
- **1.3.1 Info and Relationships** — Semantic HTML (`<table>`, `<thead>`, `<tbody>`, `<th>`, `<td>`) with proper ARIA roles
|
|
17
|
+
- **1.4.1 Use of Color** — Focus indicators use both color and visible outlines (2px solid)
|
|
18
|
+
- **1.4.3 Contrast (Minimum)** — All text and interactive elements meet 4.5:1 contrast ratio
|
|
19
|
+
- **1.4.11 Non-text Contrast** — Focus indicators and UI components meet 3:1 contrast
|
|
20
|
+
|
|
21
|
+
### Operable
|
|
22
|
+
|
|
23
|
+
- **2.1.1 Keyboard** — All functionality available via keyboard
|
|
24
|
+
- **2.1.2 No Keyboard Trap** — Focus can always escape modal dialogs and popovers
|
|
25
|
+
- **2.4.3 Focus Order** — Logical tab order through interactive elements
|
|
26
|
+
- **2.4.7 Focus Visible** — `:focus-visible` styles on all interactive elements
|
|
27
|
+
|
|
28
|
+
### Understandable
|
|
29
|
+
|
|
30
|
+
- **3.2.1 On Focus** — No unexpected context changes when elements receive focus
|
|
31
|
+
- **3.2.2 On Input** — No automatic context changes during user input
|
|
32
|
+
- **3.3.2 Labels or Instructions** — All form controls have associated `<label>` elements
|
|
33
|
+
|
|
34
|
+
### Robust
|
|
35
|
+
|
|
36
|
+
- **4.1.2 Name, Role, Value** — All UI components use proper ARIA attributes
|
|
37
|
+
- **4.1.3 Status Messages** — ARIA live regions announce dynamic content changes
|
|
38
|
+
|
|
39
|
+
## Keyboard Navigation
|
|
40
|
+
|
|
41
|
+
OGrid provides Excel-style keyboard navigation for all grid interactions.
|
|
42
|
+
|
|
43
|
+
### Arrow Key Navigation
|
|
44
|
+
|
|
45
|
+
| Key | Action |
|
|
46
|
+
|-----|--------|
|
|
47
|
+
| `↑` `↓` `←` `→` | Move active cell one cell in the direction pressed |
|
|
48
|
+
| `Ctrl+↑` / `Ctrl+↓` | Jump to first/last row in current column |
|
|
49
|
+
| `Ctrl+←` / `Ctrl+→` | Jump to first/last column in current row |
|
|
50
|
+
| `Home` | Jump to first column in current row |
|
|
51
|
+
| `End` | Jump to last column in current row |
|
|
52
|
+
| `Ctrl+Home` | Jump to first cell (top-left) |
|
|
53
|
+
| `Ctrl+End` | Jump to last cell (bottom-right) |
|
|
54
|
+
|
|
55
|
+
### Selection
|
|
56
|
+
|
|
57
|
+
| Key | Action |
|
|
58
|
+
|-----|--------|
|
|
59
|
+
| `Shift+↑` `↓` `←` `→` | Extend cell selection range |
|
|
60
|
+
| `Shift+Home` | Extend selection to first column |
|
|
61
|
+
| `Shift+End` | Extend selection to last column |
|
|
62
|
+
| `Shift+Ctrl+Home` | Extend selection to top-left corner |
|
|
63
|
+
| `Shift+Ctrl+End` | Extend selection to bottom-right corner |
|
|
64
|
+
| `Ctrl+A` | Select all cells |
|
|
65
|
+
| `Space` (on checkbox column) | Toggle row selection |
|
|
66
|
+
|
|
67
|
+
### Editing
|
|
68
|
+
|
|
69
|
+
| Key | Action |
|
|
70
|
+
|-----|--------|
|
|
71
|
+
| `Enter` | Start editing active cell (if editable) |
|
|
72
|
+
| `F2` | Start editing active cell (if editable) |
|
|
73
|
+
| `Escape` | Cancel editing and revert changes |
|
|
74
|
+
| `Enter` (while editing) | Commit changes and move down |
|
|
75
|
+
| `Tab` (while editing) | Commit changes and move right |
|
|
76
|
+
| `Shift+Tab` (while editing) | Commit changes and move left |
|
|
77
|
+
|
|
78
|
+
### Clipboard
|
|
79
|
+
|
|
80
|
+
| Key | Action |
|
|
81
|
+
|-----|--------|
|
|
82
|
+
| `Ctrl+C` / `Cmd+C` | Copy selected cells |
|
|
83
|
+
| `Ctrl+X` / `Cmd+X` | Cut selected cells |
|
|
84
|
+
| `Ctrl+V` / `Cmd+V` | Paste clipboard content |
|
|
85
|
+
| `Delete` | Clear selected cells |
|
|
86
|
+
|
|
87
|
+
### Undo/Redo
|
|
88
|
+
|
|
89
|
+
| Key | Action |
|
|
90
|
+
|-----|--------|
|
|
91
|
+
| `Ctrl+Z` / `Cmd+Z` | Undo last edit |
|
|
92
|
+
| `Ctrl+Y` / `Cmd+Shift+Z` | Redo last undone edit |
|
|
93
|
+
|
|
94
|
+
### Context Menu
|
|
95
|
+
|
|
96
|
+
| Key | Action |
|
|
97
|
+
|-----|--------|
|
|
98
|
+
| `Shift+F10` | Open context menu for active cell |
|
|
99
|
+
| `Escape` | Close context menu |
|
|
100
|
+
| `↑` `↓` | Navigate menu items |
|
|
101
|
+
| `Enter` | Execute selected menu item |
|
|
102
|
+
|
|
103
|
+
### Column Headers
|
|
104
|
+
|
|
105
|
+
| Key | Action |
|
|
106
|
+
|-----|--------|
|
|
107
|
+
| `Enter` / `Space` | Toggle sort on focused column header |
|
|
108
|
+
| `Tab` | Move focus to next interactive element (sort/filter buttons) |
|
|
109
|
+
| `Shift+Tab` | Move focus to previous interactive element |
|
|
110
|
+
|
|
111
|
+
### Pagination
|
|
112
|
+
|
|
113
|
+
| Key | Action |
|
|
114
|
+
|-----|--------|
|
|
115
|
+
| `Tab` | Navigate between pagination buttons and page size dropdown |
|
|
116
|
+
| `Enter` / `Space` | Activate focused button |
|
|
117
|
+
| `←` `→` | Navigate page number buttons |
|
|
118
|
+
|
|
119
|
+
### Column Chooser
|
|
120
|
+
|
|
121
|
+
| Key | Action |
|
|
122
|
+
|-----|--------|
|
|
123
|
+
| `Enter` / `Space` | Open/close column chooser dropdown |
|
|
124
|
+
| `↑` `↓` | Navigate column list |
|
|
125
|
+
| `Space` | Toggle column visibility |
|
|
126
|
+
| `Escape` | Close dropdown |
|
|
127
|
+
|
|
128
|
+
### Filters
|
|
129
|
+
|
|
130
|
+
| Key | Action |
|
|
131
|
+
|-----|--------|
|
|
132
|
+
| `Enter` / `Space` | Open filter popover |
|
|
133
|
+
| `Tab` | Navigate filter inputs |
|
|
134
|
+
| `Enter` | Apply filter |
|
|
135
|
+
| `Escape` | Close filter popover |
|
|
136
|
+
|
|
137
|
+
## ARIA Attributes
|
|
138
|
+
|
|
139
|
+
OGrid uses comprehensive ARIA attributes for screen reader support.
|
|
140
|
+
|
|
141
|
+
### DataGridTable
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<div role="grid" aria-label="Product catalog" aria-rowcount="1000">
|
|
145
|
+
<table>
|
|
146
|
+
<thead>
|
|
147
|
+
<tr role="row">
|
|
148
|
+
<th role="columnheader" aria-sort="ascending" scope="col">
|
|
149
|
+
Product Name
|
|
150
|
+
</th>
|
|
151
|
+
</tr>
|
|
152
|
+
</thead>
|
|
153
|
+
<tbody>
|
|
154
|
+
<tr role="row" aria-rowindex="1">
|
|
155
|
+
<td role="gridcell" tabindex="-1">Widget</td>
|
|
156
|
+
</tr>
|
|
157
|
+
</tbody>
|
|
158
|
+
</table>
|
|
159
|
+
</div>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Attribute | Element | Purpose |
|
|
163
|
+
|-----------|---------|---------|
|
|
164
|
+
| `role="grid"` | Grid wrapper | Identifies the grid container |
|
|
165
|
+
| `role="row"` | `<tr>` | Identifies table rows |
|
|
166
|
+
| `role="columnheader"` | `<th>` | Identifies column headers |
|
|
167
|
+
| `role="gridcell"` | `<td>` | Identifies data cells |
|
|
168
|
+
| `aria-label` | Grid wrapper | Provides accessible name for the grid |
|
|
169
|
+
| `aria-rowcount` | Grid wrapper | Total number of rows (server-side pagination) |
|
|
170
|
+
| `aria-rowindex` | `<tr>` | Row's position in the full dataset (1-indexed) |
|
|
171
|
+
| `aria-sort` | `<th>` | Current sort state (`"ascending"`, `"descending"`, `"none"`) |
|
|
172
|
+
| `aria-colindex` | `<th>`, `<td>` | Column's position (1-indexed) |
|
|
173
|
+
| `tabindex="-1"` | `<td>` | Enables programmatic focus for keyboard navigation |
|
|
174
|
+
|
|
175
|
+
### StatusBar
|
|
176
|
+
|
|
177
|
+
```html
|
|
178
|
+
<div role="status" aria-live="polite">
|
|
179
|
+
<span>Rows: 1,000</span>
|
|
180
|
+
<span>Selected: 5</span>
|
|
181
|
+
</div>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
| Attribute | Purpose |
|
|
185
|
+
|-----------|---------|
|
|
186
|
+
| `role="status"` | Identifies status information |
|
|
187
|
+
| `aria-live="polite"` | Announces changes when user is idle (non-intrusive) |
|
|
188
|
+
|
|
189
|
+
### PaginationControls
|
|
190
|
+
|
|
191
|
+
```html
|
|
192
|
+
<nav role="navigation" aria-label="Pagination">
|
|
193
|
+
<button aria-label="First page" aria-disabled="true">«</button>
|
|
194
|
+
<button aria-label="Previous page" aria-disabled="true">‹</button>
|
|
195
|
+
<button aria-label="Page 1" aria-current="page">1</button>
|
|
196
|
+
<button aria-label="Page 2">2</button>
|
|
197
|
+
<button aria-label="Next page">›</button>
|
|
198
|
+
<button aria-label="Last page">»</button>
|
|
199
|
+
</nav>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
| Attribute | Purpose |
|
|
203
|
+
|-----------|---------|
|
|
204
|
+
| `role="navigation"` | Identifies pagination controls |
|
|
205
|
+
| `aria-label="Pagination"` | Provides context for screen readers |
|
|
206
|
+
| `aria-label` (buttons) | Descriptive labels for icon-only buttons |
|
|
207
|
+
| `aria-current="page"` | Identifies the current page |
|
|
208
|
+
| `aria-disabled` | Indicates disabled state |
|
|
209
|
+
|
|
210
|
+
### ColumnChooser
|
|
211
|
+
|
|
212
|
+
```html
|
|
213
|
+
<button aria-expanded="false" aria-haspopup="listbox">
|
|
214
|
+
Column Visibility (3 of 5)
|
|
215
|
+
</button>
|
|
216
|
+
<div role="dialog" aria-label="Column visibility">
|
|
217
|
+
<input type="checkbox" id="col-name" />
|
|
218
|
+
<label for="col-name">Product Name</label>
|
|
219
|
+
</div>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
| Attribute | Purpose |
|
|
223
|
+
|-----------|---------|
|
|
224
|
+
| `aria-expanded` | Indicates dropdown open/closed state |
|
|
225
|
+
| `aria-haspopup="listbox"` | Indicates the button triggers a listbox |
|
|
226
|
+
| `role="dialog"` | Identifies the dropdown as a dialog |
|
|
227
|
+
| `aria-label="Column visibility"` | Provides context for the dialog |
|
|
228
|
+
|
|
229
|
+
### ColumnHeaderFilter
|
|
230
|
+
|
|
231
|
+
```html
|
|
232
|
+
<th scope="col">
|
|
233
|
+
<button aria-label="Sort by Product Name, currently unsorted">
|
|
234
|
+
Product Name
|
|
235
|
+
</button>
|
|
236
|
+
<button aria-label="Filter Product Name" aria-expanded="false">
|
|
237
|
+
▼
|
|
238
|
+
</button>
|
|
239
|
+
</th>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
| Attribute | Purpose |
|
|
243
|
+
|-----------|---------|
|
|
244
|
+
| `scope="col"` | Associates header with its column |
|
|
245
|
+
| `aria-label` (sort button) | Describes current sort state |
|
|
246
|
+
| `aria-label` (filter button) | Describes filter action |
|
|
247
|
+
| `aria-expanded` | Indicates filter popover open/closed state |
|
|
248
|
+
|
|
249
|
+
### Editable Cells
|
|
250
|
+
|
|
251
|
+
```html
|
|
252
|
+
<td role="gridcell" aria-readonly="false" tabindex="-1">
|
|
253
|
+
<input aria-label="Edit Product Name" />
|
|
254
|
+
</td>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
| Attribute | Purpose |
|
|
258
|
+
|-----------|---------|
|
|
259
|
+
| `aria-readonly="false"` | Indicates cell is editable |
|
|
260
|
+
| `aria-label` (input) | Provides context for screen readers |
|
|
261
|
+
|
|
262
|
+
## Screen Reader Support
|
|
263
|
+
|
|
264
|
+
OGrid is tested with the following screen readers:
|
|
265
|
+
|
|
266
|
+
- **NVDA** (Windows) — Latest version
|
|
267
|
+
- **JAWS** (Windows) — Latest version
|
|
268
|
+
- **VoiceOver** (macOS) — Built-in
|
|
269
|
+
- **Narrator** (Windows) — Built-in
|
|
270
|
+
- **TalkBack** (Android) — Built-in
|
|
271
|
+
|
|
272
|
+
### Announcements
|
|
273
|
+
|
|
274
|
+
Screen readers announce the following information:
|
|
275
|
+
|
|
276
|
+
#### Grid Navigation
|
|
277
|
+
|
|
278
|
+
- **Cell activation:** "Product Name, cell, row 1, column 2"
|
|
279
|
+
- **Sort change:** "Sorted by Product Name, ascending"
|
|
280
|
+
- **Filter applied:** "Filtered by Status, 3 results"
|
|
281
|
+
- **Selection:** "5 rows selected"
|
|
282
|
+
|
|
283
|
+
#### Editing
|
|
284
|
+
|
|
285
|
+
- **Edit mode:** "Editing Product Name, row 1, column 2"
|
|
286
|
+
- **Value changed:** "Product Name changed from Widget to Gadget"
|
|
287
|
+
- **Validation error:** "Invalid value: Price must be a positive number"
|
|
288
|
+
|
|
289
|
+
#### Pagination
|
|
290
|
+
|
|
291
|
+
- **Page change:** "Showing 21 to 40 of 250 rows, page 2 of 13"
|
|
292
|
+
- **Page size change:** "Page size changed to 50 rows per page"
|
|
293
|
+
|
|
294
|
+
#### Column Visibility
|
|
295
|
+
|
|
296
|
+
- **Column hidden:** "Product Name column hidden"
|
|
297
|
+
- **Column shown:** "Product Name column shown"
|
|
298
|
+
|
|
299
|
+
## Focus Management
|
|
300
|
+
|
|
301
|
+
OGrid implements advanced focus management for optimal keyboard experience.
|
|
302
|
+
|
|
303
|
+
### Focus Visible
|
|
304
|
+
|
|
305
|
+
All interactive elements use `:focus-visible` (not `:focus`) to show focus indicators only when navigating via keyboard, not when clicking with a mouse.
|
|
306
|
+
|
|
307
|
+
```scss
|
|
308
|
+
.dataTable tbody tr {
|
|
309
|
+
outline: none;
|
|
310
|
+
|
|
311
|
+
&:focus-visible {
|
|
312
|
+
outline: 2px solid var(--ogrid-accent, #0078d4);
|
|
313
|
+
outline-offset: -2px;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Focus Trap
|
|
319
|
+
|
|
320
|
+
Modal dialogs and popovers trap focus within their boundaries:
|
|
321
|
+
- `Tab` cycles through interactive elements inside the dialog
|
|
322
|
+
- `Shift+Tab` cycles backwards
|
|
323
|
+
- `Escape` closes the dialog and returns focus to the trigger element
|
|
324
|
+
|
|
325
|
+
### Focus Restoration
|
|
326
|
+
|
|
327
|
+
When closing dialogs, popovers, or exiting edit mode:
|
|
328
|
+
- Focus returns to the element that triggered the action
|
|
329
|
+
- Active cell focus is restored after pagination or sorting
|
|
330
|
+
|
|
331
|
+
## High Contrast Mode
|
|
332
|
+
|
|
333
|
+
OGrid supports Windows High Contrast Mode and other forced color schemes.
|
|
334
|
+
|
|
335
|
+
### Custom Properties
|
|
336
|
+
|
|
337
|
+
All colors use CSS custom properties with fallbacks:
|
|
338
|
+
|
|
339
|
+
```css
|
|
340
|
+
--ogrid-bg: #ffffff; /* Background */
|
|
341
|
+
--ogrid-fg: #242424; /* Foreground text */
|
|
342
|
+
--ogrid-border: #e0e0e0; /* Borders */
|
|
343
|
+
--ogrid-accent: #0078d4; /* Focus indicators, primary actions */
|
|
344
|
+
--ogrid-bg-subtle: #f3f2f1; /* Header background */
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### High Contrast Support
|
|
348
|
+
|
|
349
|
+
In high contrast mode:
|
|
350
|
+
- Focus indicators use system colors (`Highlight`, `HighlightText`)
|
|
351
|
+
- Borders use system colors (`ButtonText`, `GrayText`)
|
|
352
|
+
- Icons and text remain visible
|
|
353
|
+
|
|
354
|
+
## Accessible Usage Examples
|
|
355
|
+
|
|
356
|
+
### Basic Accessible Grid
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
|
|
360
|
+
function AccessibleGrid() {
|
|
361
|
+
return (
|
|
362
|
+
<OGrid
|
|
363
|
+
data={products}
|
|
364
|
+
columns={columns}
|
|
365
|
+
getRowId={(item) => item.id}
|
|
366
|
+
aria-label="Product catalog"
|
|
367
|
+
rowSelection="multiple"
|
|
368
|
+
editable={true}
|
|
369
|
+
/>
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### With Descriptive Labels
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
|
|
378
|
+
function GridWithLabels() {
|
|
379
|
+
return (
|
|
380
|
+
<div>
|
|
381
|
+
<h2 id="product-grid-heading">Product Catalog</h2>
|
|
382
|
+
<p id="product-grid-description">
|
|
383
|
+
Browse and manage your product inventory. Use arrow keys to navigate,
|
|
384
|
+
Enter to edit, and Space to select rows.
|
|
385
|
+
</p>
|
|
386
|
+
<OGrid
|
|
387
|
+
data={products}
|
|
388
|
+
columns={columns}
|
|
389
|
+
getRowId={(item) => item.id}
|
|
390
|
+
aria-labelledby="product-grid-heading"
|
|
391
|
+
aria-describedby="product-grid-description"
|
|
392
|
+
/>
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Announcing Status Changes
|
|
399
|
+
|
|
400
|
+
```tsx
|
|
401
|
+
|
|
402
|
+
function GridWithAnnouncements() {
|
|
403
|
+
const [announcement, setAnnouncement] = useState('');
|
|
404
|
+
|
|
405
|
+
const handleSelectionChange = (event) => {
|
|
406
|
+
const count = event.selectedRowIds.length;
|
|
407
|
+
setAnnouncement(`${count} ${count === 1 ? 'row' : 'rows'} selected`);
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
return (
|
|
411
|
+
<>
|
|
412
|
+
<div role="status" aria-live="polite" className="sr-only">
|
|
413
|
+
{announcement}
|
|
414
|
+
</div>
|
|
415
|
+
<OGrid
|
|
416
|
+
data={products}
|
|
417
|
+
columns={columns}
|
|
418
|
+
getRowId={(item) => item.id}
|
|
419
|
+
rowSelection="multiple"
|
|
420
|
+
onSelectionChange={handleSelectionChange}
|
|
421
|
+
/>
|
|
422
|
+
</>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Screen Reader Only Text
|
|
428
|
+
|
|
429
|
+
Use a `.sr-only` class for screen reader only content:
|
|
430
|
+
|
|
431
|
+
```css
|
|
432
|
+
.sr-only {
|
|
433
|
+
position: absolute;
|
|
434
|
+
width: 1px;
|
|
435
|
+
height: 1px;
|
|
436
|
+
padding: 0;
|
|
437
|
+
margin: -1px;
|
|
438
|
+
overflow: hidden;
|
|
439
|
+
clip: rect(0, 0, 0, 0);
|
|
440
|
+
white-space: nowrap;
|
|
441
|
+
border-width: 0;
|
|
442
|
+
}
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Testing Accessibility
|
|
446
|
+
|
|
447
|
+
### Automated Testing
|
|
448
|
+
|
|
449
|
+
Use automated accessibility testing tools:
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# Install axe-core for automated testing
|
|
453
|
+
npm install --save-dev @axe-core/react
|
|
454
|
+
|
|
455
|
+
# Install jest-axe for Jest integration
|
|
456
|
+
npm install --save-dev jest-axe
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
|
|
461
|
+
expect.extend(toHaveNoViolations);
|
|
462
|
+
|
|
463
|
+
test('OGrid has no accessibility violations', async () => {
|
|
464
|
+
const { container } = render(
|
|
465
|
+
<OGrid
|
|
466
|
+
data={products}
|
|
467
|
+
columns={columns}
|
|
468
|
+
getRowId={(item) => item.id}
|
|
469
|
+
aria-label="Product catalog"
|
|
470
|
+
/>
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const results = await axe(container);
|
|
474
|
+
expect(results).toHaveNoViolations();
|
|
475
|
+
});
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Manual Testing
|
|
479
|
+
|
|
480
|
+
1. **Keyboard Navigation**
|
|
481
|
+
- Disconnect your mouse
|
|
482
|
+
- Navigate the entire grid using only keyboard
|
|
483
|
+
- Verify all features are accessible
|
|
484
|
+
|
|
485
|
+
2. **Screen Reader Testing**
|
|
486
|
+
- Enable VoiceOver (macOS: `Cmd+F5`) or NVDA (Windows)
|
|
487
|
+
- Navigate the grid and verify announcements
|
|
488
|
+
- Test editing, filtering, and selection
|
|
489
|
+
|
|
490
|
+
3. **High Contrast Mode**
|
|
491
|
+
- Windows: `Alt+Left Shift+Print Screen`
|
|
492
|
+
- Verify all content is visible
|
|
493
|
+
- Check focus indicators are clear
|
|
494
|
+
|
|
495
|
+
4. **Zoom Testing**
|
|
496
|
+
- Zoom to 200% (`Ctrl++` or `Cmd++`)
|
|
497
|
+
- Verify layout remains usable
|
|
498
|
+
- No horizontal scrolling on text content
|
|
499
|
+
|
|
500
|
+
### Checklist
|
|
501
|
+
|
|
502
|
+
Use this checklist when building accessible grids:
|
|
503
|
+
|
|
504
|
+
- [ ] Grid has `aria-label` or `aria-labelledby`
|
|
505
|
+
- [ ] All interactive elements are keyboard accessible
|
|
506
|
+
- [ ] Focus indicators are visible (`:focus-visible`)
|
|
507
|
+
- [ ] Column headers have `scope="col"`
|
|
508
|
+
- [ ] Sorted columns have `aria-sort` attribute
|
|
509
|
+
- [ ] Edit mode inputs have descriptive `aria-label`
|
|
510
|
+
- [ ] Status changes are announced (ARIA live regions)
|
|
511
|
+
- [ ] High contrast mode is supported
|
|
512
|
+
- [ ] Tested with screen reader
|
|
513
|
+
- [ ] Automated accessibility tests pass
|
|
514
|
+
|
|
515
|
+
## Known Limitations
|
|
516
|
+
|
|
517
|
+
### Virtual Scrolling
|
|
518
|
+
|
|
519
|
+
When virtual scrolling is enabled (`virtualScroll.enabled: true`), screen readers may not announce the total row count accurately because only visible rows are rendered in the DOM. Use `aria-rowcount` on the grid wrapper to communicate the full dataset size.
|
|
520
|
+
|
|
521
|
+
### Large Datasets
|
|
522
|
+
|
|
523
|
+
For grids with 10,000+ rows, consider server-side pagination instead of client-side filtering to improve screen reader performance.
|
|
524
|
+
|
|
525
|
+
### Custom Cell Renderers
|
|
526
|
+
|
|
527
|
+
When using custom `renderCell` functions, ensure your custom markup:
|
|
528
|
+
- Includes appropriate ARIA attributes
|
|
529
|
+
- Maintains keyboard navigability
|
|
530
|
+
- Announces changes to screen readers
|
|
531
|
+
|
|
532
|
+
## Resources
|
|
533
|
+
|
|
534
|
+
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
535
|
+
- [ARIA Authoring Practices Guide - Grid Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/)
|
|
536
|
+
- [WebAIM Screen Reader Testing](https://webaim.org/articles/screenreader_testing/)
|
|
537
|
+
- [axe DevTools Browser Extension](https://www.deque.com/axe/devtools/)
|
|
538
|
+
|
|
539
|
+
## Support
|
|
540
|
+
|
|
541
|
+
If you encounter accessibility issues or have suggestions for improvements:
|
|
542
|
+
|
|
543
|
+
1. Check the [GitHub Issues](https://github.com/alaarab/ogrid/issues) for known issues
|
|
544
|
+
2. Report new issues with:
|
|
545
|
+
- Browser and screen reader versions
|
|
546
|
+
- Steps to reproduce
|
|
547
|
+
- Expected vs. actual behavior
|
|
548
|
+
3. Tag issues with `accessibility` label
|
|
549
|
+
|
|
550
|
+
We're committed to maintaining WCAG 2.1 AA compliance and improving accessibility based on user feedback.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 1
|
|
3
|
+
title: Controlled vs Uncontrolled
|
|
4
|
+
description: Choose between letting OGrid manage state internally or owning it yourself.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Controlled vs Uncontrolled
|
|
8
|
+
|
|
9
|
+
OGrid supports both **uncontrolled** and **controlled** state patterns for every stateful feature -- sorting, filtering, pagination, column visibility, row selection, and column order. You can also mix and match, controlling some features while leaving others to the grid.
|
|
10
|
+
|
|
11
|
+
## Uncontrolled (Zero Config)
|
|
12
|
+
|
|
13
|
+
In uncontrolled mode, OGrid manages all state internally. Just pass your columns and data:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
|
|
17
|
+
const columns = [
|
|
18
|
+
{ columnId: 'name', name: 'Name', sortable: true },
|
|
19
|
+
{ columnId: 'email', name: 'Email', filterable: { type: 'text' as const } },
|
|
20
|
+
{ columnId: 'role', name: 'Role' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
return (
|
|
25
|
+
<OGrid
|
|
26
|
+
columns={columns}
|
|
27
|
+
data={people}
|
|
28
|
+
getRowId={(item) => item.id}
|
|
29
|
+
defaultPageSize={25}
|
|
30
|
+
defaultSortBy="name"
|
|
31
|
+
defaultSortDirection="asc"
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The grid handles sorting, filtering, pagination, and everything else on its own. Use the `default*` props to set initial values.
|
|
38
|
+
|
|
39
|
+
## Controlled (You Own the State)
|
|
40
|
+
|
|
41
|
+
In controlled mode, you pass the current value and an `onChange` callback for each feature you want to control. This is useful when you need to sync grid state with a URL, persist it to storage, or coordinate with other components.
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
|
|
45
|
+
function App() {
|
|
46
|
+
const [page, setPage] = useState(1);
|
|
47
|
+
const [pageSize, setPageSize] = useState(25);
|
|
48
|
+
const [sort, setSort] = useState({ field: 'name', direction: 'asc' as const });
|
|
49
|
+
const [filters, setFilters] = useState<IFilters>({});
|
|
50
|
+
|
|
51
|
+
// Sync to URL, localStorage, or server on every change
|
|
52
|
+
const handleFiltersChange = (newFilters: IFilters) => {
|
|
53
|
+
setFilters(newFilters);
|
|
54
|
+
saveToUrl(newFilters);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<OGrid
|
|
59
|
+
columns={columns}
|
|
60
|
+
data={people}
|
|
61
|
+
getRowId={(item) => item.id}
|
|
62
|
+
page={page}
|
|
63
|
+
onPageChange={setPage}
|
|
64
|
+
pageSize={pageSize}
|
|
65
|
+
onPageSizeChange={setPageSize}
|
|
66
|
+
sort={sort}
|
|
67
|
+
onSortChange={setSort}
|
|
68
|
+
filters={filters}
|
|
69
|
+
onFiltersChange={handleFiltersChange}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Partial Control
|
|
76
|
+
|
|
77
|
+
You do not have to control everything. Control only the features you need -- the rest stays uncontrolled:
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
function App() {
|
|
81
|
+
// Only control sort; filters, pagination, etc. are managed by the grid
|
|
82
|
+
const [sort, setSort] = useState({ field: 'name', direction: 'asc' as const });
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<OGrid
|
|
86
|
+
columns={columns}
|
|
87
|
+
data={people}
|
|
88
|
+
getRowId={(item) => item.id}
|
|
89
|
+
sort={sort}
|
|
90
|
+
onSortChange={setSort}
|
|
91
|
+
defaultPageSize={50}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Controllable Prop Pairs
|
|
98
|
+
|
|
99
|
+
Every stateful feature follows the same pattern: a value prop and an `onChange` callback. When both are provided, the grid operates in controlled mode for that feature. When neither is provided, the grid manages it internally.
|
|
100
|
+
|
|
101
|
+
| Feature | Value Prop | Callback Prop | Default Prop |
|
|
102
|
+
|---------|-----------|---------------|-------------|
|
|
103
|
+
| Sort | `sort` | `onSortChange` | `defaultSortBy`, `defaultSortDirection` |
|
|
104
|
+
| Filters | `filters` | `onFiltersChange` | -- |
|
|
105
|
+
| Page | `page` | `onPageChange` | -- |
|
|
106
|
+
| Page size | `pageSize` | `onPageSizeChange` | `defaultPageSize` |
|
|
107
|
+
| Visible columns | `visibleColumns` | `onVisibleColumnsChange` | -- |
|
|
108
|
+
| Selected rows | `selectedRows` | `onSelectionChange` | -- |
|
|
109
|
+
| Column order | `columnOrder` | `onColumnOrderChange` | -- |
|
|
110
|
+
|
|
111
|
+
:::tip
|
|
112
|
+
When using controlled mode, always pass **both** the value and the callback. Passing only the value without a callback will lock the feature in its initial state, since OGrid will not be able to update it.
|
|
113
|
+
:::
|
|
114
|
+
|
|
115
|
+
## Imperative API
|
|
116
|
+
|
|
117
|
+
For cases where you need to read or set state programmatically (e.g., a "Reset Filters" button), use the grid API ref:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
|
|
121
|
+
function App() {
|
|
122
|
+
const gridRef = useRef<IOGridApi<Person>>(null);
|
|
123
|
+
|
|
124
|
+
const handleReset = () => {
|
|
125
|
+
gridRef.current?.setFilterModel({});
|
|
126
|
+
gridRef.current?.deselectAll();
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<>
|
|
131
|
+
<button onClick={handleReset}>Reset</button>
|
|
132
|
+
<OGrid
|
|
133
|
+
ref={gridRef}
|
|
134
|
+
columns={columns}
|
|
135
|
+
data={people}
|
|
136
|
+
getRowId={(item) => item.id}
|
|
137
|
+
/>
|
|
138
|
+
</>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The API ref works in both controlled and uncontrolled mode. See the [Grid API reference](../api/grid-api) for the full list of methods.
|
|
144
|
+
|
|
145
|
+
## When to Use Which
|
|
146
|
+
|
|
147
|
+
| Scenario | Recommendation |
|
|
148
|
+
|----------|---------------|
|
|
149
|
+
| Simple table, no external state needed | Uncontrolled |
|
|
150
|
+
| URL-driven filters or sort | Controlled (filters, sort) |
|
|
151
|
+
| Persist column layout to localStorage | Controlled (visibleColumns, columnOrder) + `getColumnState()` |
|
|
152
|
+
| Server-side data with `dataSource` | Controlled or uncontrolled -- both work with `dataSource` |
|
|
153
|
+
| Coordinate multiple grids | Controlled (shared state between grids) |
|