@alaarab/ogrid-mcp 2.5.5 → 2.5.8
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 +6 -51
- package/bundled-docs/api/README.md +11 -11
- package/bundled-docs/api/components-column-chooser.mdx +21 -21
- package/bundled-docs/api/components-column-header-filter.mdx +23 -23
- package/bundled-docs/api/components-datagrid-table.mdx +27 -27
- package/bundled-docs/api/components-pagination-controls.mdx +21 -21
- package/bundled-docs/api/components-sidebar.mdx +27 -27
- package/bundled-docs/api/components-status-bar.mdx +12 -12
- package/bundled-docs/api/js-api.mdx +13 -13
- package/bundled-docs/api/types.mdx +4 -4
- package/bundled-docs/features/column-chooser.mdx +4 -4
- package/bundled-docs/features/column-groups.mdx +4 -4
- package/bundled-docs/features/column-pinning.mdx +5 -5
- package/bundled-docs/features/column-reordering.mdx +5 -5
- package/bundled-docs/features/column-types.mdx +7 -7
- package/bundled-docs/features/context-menu.mdx +4 -4
- package/bundled-docs/features/csv-export.mdx +4 -4
- package/bundled-docs/features/editing.mdx +78 -68
- package/bundled-docs/features/filtering.mdx +83 -67
- package/bundled-docs/features/formulas.mdx +135 -139
- package/bundled-docs/features/grid-api.mdx +4 -4
- package/bundled-docs/features/keyboard-navigation.mdx +6 -6
- package/bundled-docs/features/mobile-touch.mdx +9 -9
- package/bundled-docs/features/pagination.mdx +4 -4
- package/bundled-docs/features/performance.mdx +97 -100
- package/bundled-docs/features/premium-inputs.mdx +579 -0
- package/bundled-docs/features/responsive-columns.mdx +1 -1
- package/bundled-docs/features/row-selection.mdx +8 -8
- package/bundled-docs/features/server-side-data.mdx +4 -4
- package/bundled-docs/features/sidebar.mdx +6 -6
- package/bundled-docs/features/sorting.mdx +87 -44
- package/bundled-docs/features/spreadsheet-selection.mdx +9 -9
- package/bundled-docs/features/status-bar.mdx +4 -4
- package/bundled-docs/features/toolbar.mdx +9 -9
- package/bundled-docs/features/virtual-scrolling.mdx +13 -13
- package/bundled-docs/getting-started/overview.mdx +9 -9
- package/bundled-docs/getting-started/quick-start.mdx +47 -47
- package/bundled-docs/getting-started/vanilla-js.mdx +98 -74
- package/bundled-docs/guides/accessibility.mdx +113 -421
- package/bundled-docs/guides/framework-showcase.mdx +98 -52
- package/bundled-docs/guides/mcp-live-testing.mdx +12 -12
- package/bundled-docs/guides/mcp.mdx +47 -46
- package/dist/esm/bridge-client.d.ts +3 -3
- package/dist/esm/index.js +9 -9
- package/package.json +3 -2
|
@@ -7,65 +7,63 @@ description: Automatic CSS containment, opt-in Web Worker sort/filter, and colum
|
|
|
7
7
|
|
|
8
8
|
# Performance
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
A grid that handles 100 rows fine but chokes at 50,000 isn't production-ready. OGrid ships with three complementary features to handle large datasets without framework-level heroics:
|
|
11
11
|
|
|
12
|
-
| Feature | Opt-in? |
|
|
12
|
+
| Feature | Opt-in? | What it does |
|
|
13
13
|
|---------|---------|---------|
|
|
14
|
-
| **CSS Containment** | No (automatic) |
|
|
14
|
+
| **CSS Containment** | No (automatic) | Browser skips layout and paint for off-screen cells |
|
|
15
15
|
| **Column Virtualization** | Yes | Renders only visible columns — scales to hundreds of columns |
|
|
16
|
-
| **Web Worker Sort/Filter** | Yes |
|
|
16
|
+
| **Web Worker Sort/Filter** | Yes | Sort and filter run in a background thread — UI stays responsive |
|
|
17
17
|
|
|
18
|
-
For
|
|
18
|
+
For rendering only visible rows (usually the most impactful optimization for tall datasets), see [Virtual Scrolling](./virtual-scrolling).
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## CSS Containment
|
|
22
|
+
## CSS Containment — you get this for free
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Every grid cell already has `contain: content` applied — you don't configure anything. This tells the browser that what happens inside a cell doesn't affect anything outside it, so it can skip layout and paint work for cells not visible in the viewport.
|
|
25
25
|
|
|
26
26
|
```css
|
|
27
27
|
/* Applied automatically to every body cell */
|
|
28
28
|
td.ogrid-cell {
|
|
29
|
-
contain: content;
|
|
29
|
+
contain: content; /* isolate layout + paint + style */
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/* Pinned columns use position: sticky — containment
|
|
32
|
+
/* Pinned columns use position: sticky — containment relaxed to avoid conflicts */
|
|
33
33
|
td.ogrid-cell[data-pinned] {
|
|
34
34
|
contain: none;
|
|
35
35
|
}
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
Without row virtualization, OGrid also sets `content-visibility: auto` on rows, letting the browser skip off-screen row layout entirely:
|
|
39
39
|
|
|
40
40
|
```css
|
|
41
|
-
/*
|
|
41
|
+
/* Browser skips layout for rows not in the viewport */
|
|
42
42
|
tr[data-row]:not([data-virtual-scroll]) {
|
|
43
43
|
content-visibility: auto;
|
|
44
44
|
}
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
When row virtualization is active, spacer rows handle positioning instead — so `content-visibility` is not applied there. The `data-virtual-scroll` attribute on `<table>` controls this automatically.
|
|
48
48
|
|
|
49
|
-
**
|
|
49
|
+
**Net result**: even without opting into anything, a 500-row grid renders measurably faster than without these rules.
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
|
-
## Web Worker Sort/Filter
|
|
53
|
+
## Web Worker Sort/Filter — keeps the UI responsive
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Sorting 50,000 rows on the main thread can freeze the UI for a visible moment. With `workerSort: true`, the work happens in a background thread — the grid stays interactive while it processes.
|
|
56
56
|
|
|
57
57
|
```tsx
|
|
58
58
|
<OGrid
|
|
59
59
|
columns={columns}
|
|
60
|
-
data={
|
|
60
|
+
data={largeDataset} // 50,000 rows
|
|
61
61
|
getRowId={(r) => r.id}
|
|
62
62
|
workerSort={true}
|
|
63
63
|
/>
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
### Quick Example
|
|
66
|
+
### Full example
|
|
69
67
|
|
|
70
68
|
<Tabs groupId="framework">
|
|
71
69
|
<TabItem value="react" label="React" default>
|
|
@@ -90,7 +88,7 @@ function App() {
|
|
|
90
88
|
return (
|
|
91
89
|
<OGrid
|
|
92
90
|
columns={columns}
|
|
93
|
-
data={largeDataset}
|
|
91
|
+
data={largeDataset} // e.g. 50,000 rows
|
|
94
92
|
getRowId={(r) => r.id}
|
|
95
93
|
workerSort={true}
|
|
96
94
|
statusBar
|
|
@@ -100,11 +98,11 @@ function App() {
|
|
|
100
98
|
```
|
|
101
99
|
|
|
102
100
|
:::tip Switching UI libraries
|
|
103
|
-
|
|
101
|
+
Same props across all React packages — just change the import:
|
|
104
102
|
|
|
105
103
|
- **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
|
|
106
|
-
- **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'`
|
|
107
|
-
- **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'`
|
|
104
|
+
- **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` - wrap in `<FluentProvider>`
|
|
105
|
+
- **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` - wrap in `<ThemeProvider>`
|
|
108
106
|
:::
|
|
109
107
|
|
|
110
108
|
</TabItem>
|
|
@@ -141,7 +139,7 @@ export class GridComponent {
|
|
|
141
139
|
```
|
|
142
140
|
|
|
143
141
|
:::tip Switching UI libraries
|
|
144
|
-
Same
|
|
142
|
+
Same API across Angular packages. Change the import:
|
|
145
143
|
|
|
146
144
|
- **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
|
|
147
145
|
- **Angular Material**: `from '@alaarab/ogrid-angular-material'`
|
|
@@ -185,10 +183,10 @@ const gridProps = {
|
|
|
185
183
|
```
|
|
186
184
|
|
|
187
185
|
:::tip Switching UI libraries
|
|
188
|
-
Same
|
|
186
|
+
Same API across Vue packages. Change the import:
|
|
189
187
|
|
|
190
188
|
- **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
|
|
191
|
-
- **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'`
|
|
189
|
+
- **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` - wrap in `<v-app>` for theming
|
|
192
190
|
- **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
|
|
193
191
|
:::
|
|
194
192
|
|
|
@@ -214,59 +212,52 @@ const grid = new OGrid(document.getElementById('grid'), {
|
|
|
214
212
|
</TabItem>
|
|
215
213
|
</Tabs>
|
|
216
214
|
|
|
217
|
-
### `'auto'`
|
|
215
|
+
### Not sure about your data size? Use `'auto'`
|
|
218
216
|
|
|
219
|
-
|
|
217
|
+
`workerSort: 'auto'` uses the worker only when your dataset exceeds 5,000 rows. For smaller datasets it runs synchronously — no overhead, no complexity:
|
|
220
218
|
|
|
221
219
|
```tsx
|
|
222
|
-
<OGrid
|
|
223
|
-
workerSort="auto"
|
|
224
|
-
// ...
|
|
225
|
-
/>
|
|
220
|
+
<OGrid workerSort="auto" ... />
|
|
226
221
|
```
|
|
227
222
|
|
|
228
|
-
This is the
|
|
223
|
+
This is the right default if you're not sure how large your data will get.
|
|
229
224
|
|
|
230
|
-
### How
|
|
225
|
+
### How it works under the hood
|
|
231
226
|
|
|
232
|
-
1. Before
|
|
233
|
-
2. The matrix is
|
|
234
|
-
3. The worker
|
|
235
|
-
4. The grid
|
|
227
|
+
1. Before sort/filter, the grid extracts a flat value matrix from your row data.
|
|
228
|
+
2. The matrix is sent to an inline Blob Web Worker via `postMessage`.
|
|
229
|
+
3. The worker sorts/filters and returns the resulting row indices.
|
|
230
|
+
4. The grid re-renders using those indices — no row data is ever copied back, only the index array.
|
|
236
231
|
|
|
237
|
-
The worker is created once and reused for subsequent sort/filter operations
|
|
232
|
+
The worker is created once per grid instance and reused for all subsequent sort/filter operations.
|
|
238
233
|
|
|
239
|
-
###
|
|
234
|
+
### When the worker falls back to sync
|
|
240
235
|
|
|
241
|
-
The worker
|
|
236
|
+
The worker can't run in every situation. It falls back silently in these cases:
|
|
242
237
|
|
|
243
|
-
| Situation |
|
|
244
|
-
|
|
245
|
-
|
|
|
246
|
-
| `people` filter type |
|
|
247
|
-
| `Worker` API unavailable (
|
|
238
|
+
| Situation | Why |
|
|
239
|
+
|-----------|-----|
|
|
240
|
+
| Column has a custom `compare` function | Functions can't cross the worker boundary |
|
|
241
|
+
| `people` filter type | Requires rich object comparison |
|
|
242
|
+
| `Worker` API unavailable (old browser, sandboxed iframe) | No Worker API to use |
|
|
248
243
|
|
|
249
|
-
|
|
244
|
+
In all cases, the grid sorts and filters correctly — just on the main thread.
|
|
250
245
|
|
|
251
|
-
###
|
|
246
|
+
### CSP gotcha
|
|
252
247
|
|
|
253
|
-
The worker
|
|
248
|
+
The worker uses an inline `Blob` URL. If your app has a `Content-Security-Policy` header, add `blob:` to `worker-src`:
|
|
254
249
|
|
|
255
250
|
```
|
|
256
251
|
Content-Security-Policy: worker-src 'self' blob:;
|
|
257
252
|
```
|
|
258
253
|
|
|
259
|
-
Without
|
|
260
|
-
|
|
261
|
-
:::caution
|
|
262
|
-
If your CSP does not permit `blob:` and you require worker-based processing, contact your security team about the CSP configuration before enabling `workerSort`.
|
|
263
|
-
:::
|
|
254
|
+
Without it, the browser blocks the worker and the grid falls back to synchronous processing. If you can't change the CSP, `workerSort` still works — it just runs on the main thread.
|
|
264
255
|
|
|
265
256
|
---
|
|
266
257
|
|
|
267
|
-
##
|
|
258
|
+
## Putting it all together
|
|
268
259
|
|
|
269
|
-
Row virtualization, column virtualization, and worker sort/filter
|
|
260
|
+
Row virtualization, column virtualization, and worker sort/filter stack together cleanly. For a 100-column, 100,000-row dataset, enable all three:
|
|
270
261
|
|
|
271
262
|
<Tabs groupId="framework">
|
|
272
263
|
<TabItem value="react" label="React" default>
|
|
@@ -276,17 +267,17 @@ Row virtualization, column virtualization, and worker sort/filter are fully comp
|
|
|
276
267
|
function App() {
|
|
277
268
|
return (
|
|
278
269
|
<OGrid
|
|
279
|
-
columns={columns}
|
|
280
|
-
data={data}
|
|
270
|
+
columns={columns} // 100 columns
|
|
271
|
+
data={data} // 100,000 rows
|
|
281
272
|
getRowId={(r) => r.id}
|
|
282
273
|
virtualScroll={{
|
|
283
|
-
enabled: true,
|
|
274
|
+
enabled: true, // renders ~30 rows instead of 100,000
|
|
284
275
|
rowHeight: 36,
|
|
285
276
|
overscan: 5,
|
|
286
|
-
columns: true,
|
|
277
|
+
columns: true, // renders only visible columns
|
|
287
278
|
columnOverscan: 2,
|
|
288
279
|
}}
|
|
289
|
-
workerSort={true}
|
|
280
|
+
workerSort={true} // sort/filter off the main thread
|
|
290
281
|
statusBar
|
|
291
282
|
/>
|
|
292
283
|
);
|
|
@@ -294,11 +285,11 @@ function App() {
|
|
|
294
285
|
```
|
|
295
286
|
|
|
296
287
|
:::tip Switching UI libraries
|
|
297
|
-
|
|
288
|
+
Same props across all React packages — just change the import:
|
|
298
289
|
|
|
299
290
|
- **Radix** (lightweight, default): `from '@alaarab/ogrid-react-radix'`
|
|
300
|
-
- **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'`
|
|
301
|
-
- **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'`
|
|
291
|
+
- **Fluent UI** (Microsoft 365 / SPFx): `from '@alaarab/ogrid-react-fluent'` - wrap in `<FluentProvider>`
|
|
292
|
+
- **Material UI** (MUI v7): `from '@alaarab/ogrid-react-material'` - wrap in `<ThemeProvider>`
|
|
302
293
|
:::
|
|
303
294
|
|
|
304
295
|
</TabItem>
|
|
@@ -306,8 +297,8 @@ The `OGrid` component has the same props across all React UI packages. To switch
|
|
|
306
297
|
|
|
307
298
|
```typescript
|
|
308
299
|
gridProps = {
|
|
309
|
-
columns, //
|
|
310
|
-
data: largeData, //
|
|
300
|
+
columns, // 100 columns
|
|
301
|
+
data: largeData, // 100,000 rows
|
|
311
302
|
getRowId: (item: Row) => item.id,
|
|
312
303
|
virtualScroll: {
|
|
313
304
|
enabled: true,
|
|
@@ -322,7 +313,7 @@ gridProps = {
|
|
|
322
313
|
```
|
|
323
314
|
|
|
324
315
|
:::tip Switching UI libraries
|
|
325
|
-
Same
|
|
316
|
+
Same API across Angular packages. Change the import:
|
|
326
317
|
|
|
327
318
|
- **Radix (CDK)**: `from '@alaarab/ogrid-angular-radix'` *(default, lightweight)*
|
|
328
319
|
- **Angular Material**: `from '@alaarab/ogrid-angular-material'`
|
|
@@ -337,8 +328,8 @@ All components are standalone — no NgModule required.
|
|
|
337
328
|
```vue
|
|
338
329
|
<script setup lang="ts">
|
|
339
330
|
const gridProps = {
|
|
340
|
-
columns, //
|
|
341
|
-
data: largeData, //
|
|
331
|
+
columns, // 100 columns
|
|
332
|
+
data: largeData, // 100,000 rows
|
|
342
333
|
getRowId: (item: Row) => item.id,
|
|
343
334
|
virtualScroll: {
|
|
344
335
|
enabled: true,
|
|
@@ -358,10 +349,10 @@ const gridProps = {
|
|
|
358
349
|
```
|
|
359
350
|
|
|
360
351
|
:::tip Switching UI libraries
|
|
361
|
-
Same
|
|
352
|
+
Same API across Vue packages. Change the import:
|
|
362
353
|
|
|
363
354
|
- **Radix (Headless UI)**: `from '@alaarab/ogrid-vue-radix'` *(default, lightweight)*
|
|
364
|
-
- **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'`
|
|
355
|
+
- **Vuetify**: `from '@alaarab/ogrid-vue-vuetify'` - wrap in `<v-app>` for theming
|
|
365
356
|
- **PrimeVue**: `from '@alaarab/ogrid-vue-primevue'`
|
|
366
357
|
:::
|
|
367
358
|
|
|
@@ -371,8 +362,8 @@ Same component API across Vue packages. To switch, just change the import:
|
|
|
371
362
|
```js
|
|
372
363
|
|
|
373
364
|
const grid = new OGrid(document.getElementById('grid'), {
|
|
374
|
-
columns, //
|
|
375
|
-
data: largeData, //
|
|
365
|
+
columns, // 100 columns
|
|
366
|
+
data: largeData, // 100,000 rows
|
|
376
367
|
getRowId: (r) => r.id,
|
|
377
368
|
virtualScroll: {
|
|
378
369
|
enabled: true,
|
|
@@ -391,43 +382,49 @@ const grid = new OGrid(document.getElementById('grid'), {
|
|
|
391
382
|
|
|
392
383
|
---
|
|
393
384
|
|
|
394
|
-
##
|
|
385
|
+
## Which features do I actually need?
|
|
386
|
+
|
|
387
|
+
Start here, add as needed:
|
|
388
|
+
|
|
389
|
+
| Dataset | Recommended setup |
|
|
390
|
+
|---|---|
|
|
391
|
+
| < 1,000 rows, < 20 columns | Nothing — CSS containment handles it automatically. |
|
|
392
|
+
| 1,000 – 10,000 rows | Add row virtualization: `virtualScroll: { enabled: true, rowHeight: 36 }`. |
|
|
393
|
+
| > 10,000 rows | Row virtualization + `workerSort: 'auto'`. |
|
|
394
|
+
| > 30 columns with horizontal scroll | Add `columns: true` to the `virtualScroll` config. |
|
|
395
|
+
| Very large + very wide | All three: row virtualization, column virtualization, and `workerSort: true`. |
|
|
396
|
+
|
|
397
|
+
:::tip Pro tip
|
|
398
|
+
If you're unsure whether you need column virtualization, open DevTools and look at how many `<td>` elements render in a single row. If it's more than you can count comfortably, add `columns: true`.
|
|
399
|
+
:::
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Props reference
|
|
395
404
|
|
|
396
405
|
### `workerSort`
|
|
397
406
|
|
|
398
|
-
| Value |
|
|
407
|
+
| Value | When to use it |
|
|
399
408
|
|-------|----------|
|
|
400
|
-
| `false` (default) |
|
|
401
|
-
| `true` |
|
|
402
|
-
| `'auto'` |
|
|
409
|
+
| `false` (default) | Small datasets, or when you have custom `compare` functions |
|
|
410
|
+
| `true` | Large datasets where you want the worker always active |
|
|
411
|
+
| `'auto'` | You don't know the data size ahead of time — use this as the safe default |
|
|
403
412
|
|
|
404
|
-
### `virtualScroll`
|
|
413
|
+
### Column virtualization fields (inside `virtualScroll`)
|
|
405
414
|
|
|
406
415
|
| Field | Type | Default | Description |
|
|
407
416
|
|-------|------|---------|-------------|
|
|
408
|
-
| `columns` | `boolean` | `false` | Enable column virtualization
|
|
409
|
-
| `columnOverscan` | `number` | `2` | Extra columns rendered beyond the visible
|
|
410
|
-
|
|
411
|
-
For the full `virtualScroll` props table, see [Virtual Scrolling — Props Reference](./virtual-scrolling#props-reference).
|
|
417
|
+
| `columns` | `boolean` | `false` | Enable column virtualization. Requires row virtualization to also be enabled. |
|
|
418
|
+
| `columnOverscan` | `number` | `2` | Extra columns rendered beyond the visible range on each side (prevents blank flash on fast scroll). |
|
|
412
419
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
## When to Use Each Feature
|
|
416
|
-
|
|
417
|
-
| Dataset Size | Recommendation |
|
|
418
|
-
|---|---|
|
|
419
|
-
| < 1,000 rows, < 20 columns | No performance features needed. CSS containment applies automatically. |
|
|
420
|
-
| 1,000 – 10,000 rows | Enable row virtualization (`virtualScroll: { enabled: true, rowHeight: 36 }`). |
|
|
421
|
-
| > 10,000 rows | Row virtualization + `workerSort: 'auto'` or `workerSort: true`. |
|
|
422
|
-
| > 30 columns with horizontal scroll | Add `columns: true` to the `virtualScroll` config. |
|
|
423
|
-
| All of the above | Combine all three: row + column virtualization and `workerSort: true`. |
|
|
420
|
+
For the full `virtualScroll` API, see [Virtual Scrolling — Props Reference](./virtual-scrolling#props-reference).
|
|
424
421
|
|
|
425
422
|
---
|
|
426
423
|
|
|
427
|
-
##
|
|
424
|
+
## Next steps
|
|
428
425
|
|
|
429
|
-
- [Virtual Scrolling](./virtual-scrolling)
|
|
430
|
-
- [Sorting](./sorting)
|
|
431
|
-
- [Filtering](./filtering)
|
|
432
|
-
- [Server-Side Data](./server-side-data)
|
|
433
|
-
- [Column Pinning](./column-pinning)
|
|
426
|
+
- [Virtual Scrolling](./virtual-scrolling) — row virtualization, scroll-to-row, and the full virtualScroll API
|
|
427
|
+
- [Sorting](./sorting) — configure sortable columns and custom comparators
|
|
428
|
+
- [Filtering](./filtering) — filter types and server-side filtering
|
|
429
|
+
- [Server-Side Data](./server-side-data) — skip client-side processing entirely for the largest datasets
|
|
430
|
+
- [Column Pinning](./column-pinning) — pinned columns always render regardless of column virtualization
|