@pdanpdan/virtual-scroll 0.6.0 → 0.7.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.
@@ -1,2 +1,2 @@
1
- @layer components{.virtual-scrollbar-track{--vsi-scrollbar-bg:var(--vs-scrollbar-bg,var(--lightningcss-light,#e6e6e6e6)var(--lightningcss-dark,#1e1e1ee6));--vsi-scrollbar-thumb-bg:var(--vs-scrollbar-thumb-bg,var(--lightningcss-light,#0000004d)var(--lightningcss-dark,#ffffff4d));--vsi-scrollbar-thumb-hover-bg:var(--vs-scrollbar-thumb-hover-bg,var(--lightningcss-light,#0009)var(--lightningcss-dark,#fff9));--vsi-scrollbar-radius:var(--vs-scrollbar-radius,4px);--vsi-scrollbar-size:var(--vs-scrollbar-size,8px);contain:layout;background-color:var(--vsi-scrollbar-bg);border-radius:var(--vsi-scrollbar-radius);z-index:30;-webkit-user-select:none;user-select:none;pointer-events:auto;transition:opacity .2s;position:absolute}.virtual-scrollbar-track.virtual-scrollbar-track--vertical{inline-size:var(--vsi-scrollbar-size);inset-block-start:2px;inset-inline-end:2px}.virtual-scrollbar-track.virtual-scrollbar-track--horizontal{block-size:var(--vsi-scrollbar-size);inset-block-end:2px;inset-inline-start:2px}.virtual-scrollbar-thumb{background-color:var(--vsi-scrollbar-thumb-bg);border-radius:var(--vsi-scrollbar-radius);touch-action:none;pointer-events:auto;cursor:pointer;position:absolute}.virtual-scrollbar-thumb:hover,.virtual-scrollbar-thumb:active,.virtual-scrollbar-thumb.virtual-scrollbar-thumb--active{background-color:var(--vsi-scrollbar-thumb-hover-bg)}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--vertical{inline-size:100%}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--horizontal{block-size:100%}.virtual-scroll-container[data-v-408dd72c]{outline-offset:1px;block-size:100%;inline-size:100%;position:relative}.virtual-scroll-container[data-v-408dd72c]:not(.virtual-scroll--window){overscroll-behavior:contain;overflow:auto}.virtual-scroll-container.virtual-scroll--table[data-v-408dd72c]{display:block}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-408dd72c]{scrollbar-width:none;-ms-overflow-style:none}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-408dd72c]::-webkit-scrollbar{display:none}.virtual-scroll-container.virtual-scroll--horizontal[data-v-408dd72c],.virtual-scroll-container.virtual-scroll--both[data-v-408dd72c]{white-space:nowrap}.virtual-scroll-scrollbar-container[data-v-408dd72c]{z-index:30;pointer-events:none;block-size:0;inline-size:100%;position:sticky;inset-block-start:0;inset-inline-start:0;overflow:visible}.virtual-scroll-scrollbar-viewport[data-v-408dd72c]{pointer-events:none;position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-wrapper[data-v-408dd72c]{contain:layout;position:relative}:where(.virtual-scroll--hydrated>.virtual-scroll-wrapper>.virtual-scroll-item[data-v-408dd72c]){position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-item[data-v-408dd72c]{box-sizing:border-box;will-change:transform;display:grid}.virtual-scroll-item:where(.virtual-scroll--debug)[data-v-408dd72c]{background-color:#ff00000d;outline:1px dashed #ff000080}.virtual-scroll-item:where(.virtual-scroll--debug)[data-v-408dd72c]:where(:hover){z-index:100;background-color:#ff00001a}.virtual-scroll-debug-info[data-v-408dd72c]{color:#fff;pointer-events:none;z-index:100;background:#000000b3;border-radius:4px;padding:2px 4px;font-family:monospace;font-size:10px;position:absolute;inset-block-start:2px;inset-inline-end:2px}.virtual-scroll-spacer[data-v-408dd72c]{pointer-events:none}.virtual-scroll-header[data-v-408dd72c],.virtual-scroll-footer[data-v-408dd72c]{z-index:20;position:relative}.virtual-scroll--sticky[data-v-408dd72c]{position:sticky}.virtual-scroll--sticky[data-v-408dd72c]:where(.virtual-scroll-header){box-sizing:border-box;min-inline-size:100%;inset-block-start:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-408dd72c]:where(.virtual-scroll-footer){box-sizing:border-box;min-inline-size:100%;inset-block-end:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-408dd72c]:where(.virtual-scroll-item){z-index:10}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-408dd72c]{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-408dd72c]>tr{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-408dd72c]>tr>:is(td,th){align-items:center;display:inline-block}}
1
+ @layer components{.virtual-scrollbar-track{--vsi-scrollbar-bg:var(--vs-scrollbar-bg,var(--lightningcss-light,#e6e6e6e6)var(--lightningcss-dark,#1e1e1ee6));--vsi-scrollbar-thumb-bg:var(--vs-scrollbar-thumb-bg,var(--lightningcss-light,#0000004d)var(--lightningcss-dark,#ffffff4d));--vsi-scrollbar-thumb-hover-bg:var(--vs-scrollbar-thumb-hover-bg,var(--lightningcss-light,#0009)var(--lightningcss-dark,#fff9));--vsi-scrollbar-radius:var(--vs-scrollbar-radius,4px);--vsi-scrollbar-size:var(--vs-scrollbar-size,8px);contain:layout;background-color:var(--vsi-scrollbar-bg);border-radius:var(--vsi-scrollbar-radius);z-index:30;-webkit-user-select:none;user-select:none;pointer-events:auto;transition:opacity .2s;position:absolute}.virtual-scrollbar-track.virtual-scrollbar-track--vertical{inline-size:var(--vsi-scrollbar-size);inset-block-start:2px;inset-inline-end:2px}.virtual-scrollbar-track.virtual-scrollbar-track--horizontal{block-size:var(--vsi-scrollbar-size);inset-block-end:2px;inset-inline-start:2px}.virtual-scrollbar-thumb{background-color:var(--vsi-scrollbar-thumb-bg);border-radius:var(--vsi-scrollbar-radius);touch-action:none;pointer-events:auto;cursor:pointer;position:absolute}.virtual-scrollbar-thumb:hover,.virtual-scrollbar-thumb:active,.virtual-scrollbar-thumb.virtual-scrollbar-thumb--active{background-color:var(--vsi-scrollbar-thumb-hover-bg)}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--vertical{inline-size:100%}.virtual-scrollbar-thumb.virtual-scrollbar-thumb--horizontal{block-size:100%}.virtual-scroll-container[data-v-184bc0a9]{outline-offset:1px;block-size:100%;inline-size:100%;position:relative}.virtual-scroll-container[data-v-184bc0a9]:not(.virtual-scroll--window){overscroll-behavior:contain;overflow:auto}.virtual-scroll-container.virtual-scroll--table[data-v-184bc0a9]{display:block}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-184bc0a9]{scrollbar-width:none;-ms-overflow-style:none}.virtual-scroll-container.virtual-scroll--hide-scrollbar[data-v-184bc0a9]::-webkit-scrollbar{display:none}.virtual-scroll-container.virtual-scroll--horizontal[data-v-184bc0a9],.virtual-scroll-container.virtual-scroll--both[data-v-184bc0a9]{white-space:nowrap}.virtual-scroll-scrollbar-container[data-v-184bc0a9]{z-index:30;pointer-events:none;block-size:0;inline-size:100%;position:sticky;inset-block-start:0;inset-inline-start:0;overflow:visible}.virtual-scroll-scrollbar-viewport[data-v-184bc0a9]{pointer-events:none;position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-wrapper[data-v-184bc0a9]{contain:layout;position:relative}:where(.virtual-scroll--hydrated>.virtual-scroll-wrapper>.virtual-scroll-item[data-v-184bc0a9]){position:absolute;inset-block-start:0;inset-inline-start:0}.virtual-scroll-item[data-v-184bc0a9]{box-sizing:border-box;will-change:transform;display:grid}.virtual-scroll-item:where(.virtual-scroll--debug)[data-v-184bc0a9]{background-color:#ff00000d;outline:1px dashed #ff000080}.virtual-scroll-item:where(.virtual-scroll--debug)[data-v-184bc0a9]:where(:hover){z-index:100;background-color:#ff00001a}.virtual-scroll-debug-info[data-v-184bc0a9]{color:#fff;pointer-events:none;z-index:100;background:#000000b3;border-radius:4px;padding:2px 4px;font-family:monospace;font-size:10px;position:absolute;inset-block-start:2px;inset-inline-end:2px}.virtual-scroll-spacer[data-v-184bc0a9]{pointer-events:none}.virtual-scroll-header[data-v-184bc0a9],.virtual-scroll-footer[data-v-184bc0a9]{z-index:20;position:relative}.virtual-scroll--sticky[data-v-184bc0a9]{position:sticky}.virtual-scroll--sticky[data-v-184bc0a9]:where(.virtual-scroll-header){box-sizing:border-box;min-inline-size:100%;inset-block-start:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-184bc0a9]:where(.virtual-scroll-footer){box-sizing:border-box;min-inline-size:100%;inset-block-end:0;inset-inline-start:0}.virtual-scroll--sticky[data-v-184bc0a9]:where(.virtual-scroll-item){z-index:10}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-184bc0a9]{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-184bc0a9]>tr{min-inline-size:100%;display:inline-flex}:is(tbody.virtual-scroll-wrapper,thead.virtual-scroll-header,tfoot.virtual-scroll-footer)[data-v-184bc0a9]>tr>:is(td,th){align-items:center;display:inline-block}}
2
2
  /*$vite$:1*/
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@pdanpdan/virtual-scroll",
3
3
  "type": "module",
4
- "version": "0.6.0",
4
+ "version": "0.7.0",
5
5
  "description": "A high-performance virtual scroll component for Vue 3",
6
6
  "author": "",
7
7
  "license": "MIT",
8
- "homepage": "https://github.com/pdanpdan/virtual-scroll#readme",
8
+ "homepage": "https://pdanpdan.github.io/virtual-scroll",
9
9
  "repository": {
10
10
  "type": "git",
11
11
  "url": "git+https://github.com/pdanpdan/virtual-scroll.git",
@@ -38,8 +38,6 @@
38
38
  "jsdelivr": "./dist/index.js",
39
39
  "types": "./dist/index.d.ts",
40
40
  "files": [
41
- "!src/**/*.test.ts",
42
- "!src/**/__tests__",
43
41
  "dist",
44
42
  "src"
45
43
  ],
@@ -49,6 +49,7 @@ const props = withDefaults(defineProps<Props<T>>(), {
49
49
  restoreScrollOnPrepend: false,
50
50
  debug: false,
51
51
  virtualScrollbar: false,
52
+ itemRole: undefined,
52
53
  });
53
54
 
54
55
  const emit = defineEmits<{
@@ -871,6 +872,7 @@ const verticalScrollbarProps = computed<ScrollbarSlotProps | null>(() => {
871
872
  scrollToOffset: handleVerticalScrollbarScrollToOffset,
872
873
  containerId: containerId.value,
873
874
  isRtl: isRtl.value,
875
+ ariaLabel: 'Vertical scroll',
874
876
  };
875
877
 
876
878
  return {
@@ -903,6 +905,7 @@ const horizontalScrollbarProps = computed<ScrollbarSlotProps | null>(() => {
903
905
  scrollToOffset: handleHorizontalScrollbarScrollToOffset,
904
906
  containerId: containerId.value,
905
907
  isRtl: isRtl.value,
908
+ ariaLabel: 'Horizontal scroll',
906
909
  };
907
910
 
908
911
  return {
@@ -989,6 +992,112 @@ const isTable = computed(() => props.containerTag === 'table');
989
992
  const headerTag = computed(() => isTable.value ? 'thead' : 'div');
990
993
  const footerTag = computed(() => isTable.value ? 'tfoot' : 'div');
991
994
 
995
+ const effectiveRole = computed(() => {
996
+ if (props.role) {
997
+ return props.role;
998
+ }
999
+ return isTable.value ? null : (props.direction === 'both' ? 'grid' : 'list');
1000
+ });
1001
+
1002
+ const isGrid = computed(() => effectiveRole.value === 'grid' || isTable.value);
1003
+
1004
+ const containerRole = computed(() => isTable.value
1005
+ ? null
1006
+ : ((props.ariaLabel || props.ariaLabelledby) ? 'region' : 'none'));
1007
+ const wrapperRole = computed(() => isTable.value ? null : effectiveRole.value);
1008
+ const internalItemRole = computed(() => {
1009
+ if (isGrid.value) {
1010
+ return 'row';
1011
+ }
1012
+
1013
+ const role = effectiveRole.value;
1014
+ if (role === 'tree') {
1015
+ return 'treeitem';
1016
+ }
1017
+ if (role === 'listbox') {
1018
+ return 'option';
1019
+ }
1020
+ if (role === 'menu') {
1021
+ return 'menuitem';
1022
+ }
1023
+ return 'listitem';
1024
+ });
1025
+ const itemRole = computed(() => props.itemRole != null ? props.itemRole : internalItemRole.value);
1026
+ const cellRole = computed(() => {
1027
+ if (props.role === 'grid' || (!props.role && props.direction === 'both')) {
1028
+ return 'gridcell';
1029
+ }
1030
+ return isTable.value ? 'cell' : null;
1031
+ });
1032
+
1033
+ const shouldBindItemAria = computed(() => {
1034
+ const role = itemRole.value;
1035
+ return role == null || (role !== 'none' && role !== 'presentation');
1036
+ });
1037
+
1038
+ const rootAriaProps = computed(() => ({
1039
+ 'aria-label': props.ariaLabel,
1040
+ 'aria-labelledby': props.ariaLabelledby,
1041
+ 'aria-busy': props.loading ? 'true' : undefined,
1042
+ }));
1043
+
1044
+ const wrapperAriaProps = computed(() => {
1045
+ const aria: Record<string, string | number | undefined> = {};
1046
+
1047
+ const role = effectiveRole.value;
1048
+ const supportsOrientation = role && [ 'grid', 'tree', 'listbox', 'menu', 'tablist' ].includes(role);
1049
+
1050
+ if (supportsOrientation) {
1051
+ aria[ 'aria-orientation' ] = props.direction === 'both' ? undefined : props.direction;
1052
+ }
1053
+
1054
+ if (isGrid.value) {
1055
+ aria[ 'aria-rowcount' ] = props.items.length;
1056
+ if (props.columnCount > 0) {
1057
+ aria[ 'aria-colcount' ] = props.columnCount;
1058
+ }
1059
+ }
1060
+
1061
+ return aria;
1062
+ });
1063
+
1064
+ function getItemAriaProps(index: number) {
1065
+ const aria: Record<string, string | number | undefined> = {};
1066
+
1067
+ if (isGrid.value) {
1068
+ aria[ 'aria-rowindex' ] = index + 1;
1069
+ } else {
1070
+ aria[ 'aria-setsize' ] = props.items.length;
1071
+ aria[ 'aria-posinset' ] = index + 1;
1072
+ }
1073
+
1074
+ const role = itemRole.value;
1075
+ if (role !== null) {
1076
+ aria.role = (role === 'none' || role === 'presentation')
1077
+ ? internalItemRole.value
1078
+ : role;
1079
+ }
1080
+
1081
+ return aria;
1082
+ }
1083
+
1084
+ function getCellAriaProps(colIndex: number) {
1085
+ const role = cellRole.value;
1086
+ if (!role) {
1087
+ return {};
1088
+ }
1089
+
1090
+ const aria: Record<string, string | number | undefined> = {
1091
+ role,
1092
+ };
1093
+
1094
+ if (isGrid.value) {
1095
+ aria[ 'aria-colindex' ] = colIndex + 1;
1096
+ }
1097
+
1098
+ return aria;
1099
+ }
1100
+
992
1101
  defineExpose({
993
1102
  ...toRefs(props),
994
1103
 
@@ -1020,6 +1129,18 @@ defineExpose({
1020
1129
  */
1021
1130
  getRowHeight,
1022
1131
 
1132
+ /**
1133
+ * Helper to get ARIA attributes for a cell.
1134
+ * @param colIndex - The column index.
1135
+ */
1136
+ getCellAriaProps,
1137
+
1138
+ /**
1139
+ * Helper to get ARIA attributes for an item.
1140
+ * @param index - The item index.
1141
+ */
1142
+ getItemAriaProps,
1143
+
1023
1144
  /**
1024
1145
  * Helper to get the virtual offset of a specific row.
1025
1146
  * @param index - The row index.
@@ -1156,6 +1277,8 @@ defineExpose({
1156
1277
  ]"
1157
1278
  :style="containerStyle"
1158
1279
  tabindex="0"
1280
+ :role="isTable ? undefined : containerRole"
1281
+ v-bind="isTable ? { ...rootAriaProps, ...wrapperAriaProps } : rootAriaProps"
1159
1282
  @keydown="handleKeyDown"
1160
1283
  @pointerdown="handlePointerDown"
1161
1284
  @pointermove="handlePointerMove"
@@ -1165,6 +1288,7 @@ defineExpose({
1165
1288
  <div
1166
1289
  v-if="showVirtualScrollbars"
1167
1290
  class="virtual-scroll-scrollbar-container"
1291
+ aria-hidden="true"
1168
1292
  >
1169
1293
  <div
1170
1294
  class="virtual-scroll-scrollbar-viewport"
@@ -1188,6 +1312,7 @@ defineExpose({
1188
1312
  ref="headerRef"
1189
1313
  class="virtual-scroll-header"
1190
1314
  :class="{ 'virtual-scroll--sticky': stickyHeader }"
1315
+ :role="isTable ? undefined : 'none'"
1191
1316
  >
1192
1317
  <slot name="header" />
1193
1318
  </component>
@@ -1197,6 +1322,8 @@ defineExpose({
1197
1322
  ref="wrapperRef"
1198
1323
  class="virtual-scroll-wrapper"
1199
1324
  :style="wrapperStyle"
1325
+ :role="isTable ? undefined : wrapperRole"
1326
+ v-bind="isTable ? {} : wrapperAriaProps"
1200
1327
  >
1201
1328
  <!-- Phantom element to push scroll height -->
1202
1329
  <component
@@ -1220,13 +1347,16 @@ defineExpose({
1220
1347
  'virtual-scroll--debug': isDebug,
1221
1348
  }"
1222
1349
  :style="getItemStyle(renderedItem)"
1350
+ v-bind="shouldBindItemAria ? getItemAriaProps(renderedItem.index) : { role: 'none' }"
1223
1351
  >
1224
1352
  <slot
1225
1353
  name="item"
1226
1354
  :item="renderedItem.item"
1227
1355
  :index="renderedItem.index"
1356
+ :get-item-aria-props="getItemAriaProps"
1228
1357
  :column-range="slotColumnRange"
1229
1358
  :get-column-width="getColumnWidth"
1359
+ :get-cell-aria-props="getCellAriaProps"
1230
1360
  :gap="props.gap"
1231
1361
  :column-gap="props.columnGap"
1232
1362
  :is-sticky="renderedItem.isSticky"
@@ -1246,6 +1376,8 @@ defineExpose({
1246
1376
  v-if="loading && slots.loading"
1247
1377
  class="virtual-scroll-loading"
1248
1378
  :style="loadingStyle"
1379
+ aria-live="polite"
1380
+ aria-atomic="true"
1249
1381
  >
1250
1382
  <slot name="loading" />
1251
1383
  </div>
@@ -1256,6 +1388,7 @@ defineExpose({
1256
1388
  ref="footerRef"
1257
1389
  class="virtual-scroll-footer"
1258
1390
  :class="{ 'virtual-scroll--sticky': stickyFooter }"
1391
+ :role="isTable ? undefined : 'none'"
1259
1392
  >
1260
1393
  <slot name="footer" />
1261
1394
  </component>
@@ -27,6 +27,8 @@ export interface UseVirtualScrollbarProps {
27
27
  containerId?: MaybeRefOrGetter<string | undefined>;
28
28
  /** Whether the scrollbar is in Right-to-Left (RTL) mode. */
29
29
  isRtl?: MaybeRefOrGetter<boolean>;
30
+ /** Accessible label for the scrollbar. */
31
+ ariaLabel?: MaybeRefOrGetter<string | undefined>;
30
32
  }
31
33
 
32
34
  /**
@@ -197,6 +199,7 @@ export function useVirtualScrollbar(props: UseVirtualScrollbarProps) {
197
199
  ],
198
200
  style: trackStyle.value,
199
201
  role: 'scrollbar',
202
+ 'aria-label': toValue(props.ariaLabel),
200
203
  'aria-orientation': axis.value,
201
204
  'aria-valuenow': Math.round(position.value),
202
205
  'aria-valuemin': 0,
package/src/types.ts CHANGED
@@ -164,6 +164,9 @@ export interface ScrollDetails<T = unknown> {
164
164
  columnRange: ColumnRange;
165
165
  }
166
166
 
167
+ /** Helper to get ARIA attributes for an item. */
168
+ export type GetItemAriaProps = (index: number) => Record<string, string | number | undefined>;
169
+
167
170
  /**
168
171
  * Configuration for Server-Side Rendering.
169
172
  * Defines which items are rendered statically on the server.
@@ -302,6 +305,29 @@ export interface VirtualScrollBaseProps<T = unknown> {
302
305
  * Enable debug visualization of buffers and indices.
303
306
  */
304
307
  debug?: boolean | undefined;
308
+
309
+ /**
310
+ * ARIA role for the scroll container.
311
+ * Defaults to 'list' for vertical/horizontal and 'grid' for both.
312
+ */
313
+ role?: string | undefined;
314
+
315
+ /**
316
+ * ARIA label for the scroll container.
317
+ */
318
+ ariaLabel?: string | undefined;
319
+
320
+ /**
321
+ * ID of the element that labels the scroll container.
322
+ */
323
+ ariaLabelledby?: string | undefined;
324
+
325
+ /**
326
+ * ARIA role for each rendered item.
327
+ * Defaults to 'listitem' for list roles and 'row' for grid roles.
328
+ * Set to 'none' or 'presentation' to disable automatic role assignment on the wrapper.
329
+ */
330
+ itemRole?: string | undefined;
305
331
  }
306
332
 
307
333
  /** Configuration properties for the `useVirtualScroll` composable. */
@@ -385,6 +411,11 @@ export interface VirtualScrollbarProps {
385
411
  * @default false
386
412
  */
387
413
  isRtl?: boolean;
414
+
415
+ /**
416
+ * Accessible label for the scrollbar.
417
+ */
418
+ ariaLabel?: string;
388
419
  }
389
420
 
390
421
  /** Properties passed to the 'scrollbar' scoped slot. */
@@ -428,10 +459,14 @@ export interface ItemSlotProps<T = unknown> {
428
459
  item: T;
429
460
  /** The 0-based index of the item. */
430
461
  index: number;
462
+ /** Helper to get ARIA attributes for the item. */
463
+ getItemAriaProps: GetItemAriaProps;
431
464
  /** Information about the currently visible range of columns. */
432
465
  columnRange: ColumnRange;
433
466
  /** Helper to get the current calculated width of any column index. */
434
467
  getColumnWidth: (index: number) => number;
468
+ /** Helper to get ARIA attributes for a cell. */
469
+ getCellAriaProps: (colIndex: number) => Record<string, string | number | undefined>;
435
470
  /** Vertical gap between items. */
436
471
  gap: number;
437
472
  /** Horizontal gap between columns. */
@@ -479,6 +514,8 @@ export interface VirtualScrollInstance<T = unknown> extends VirtualScrollCompone
479
514
  getColumnWidth: (index: number) => number;
480
515
  /** Helper to get the height of a specific row. */
481
516
  getRowHeight: (index: number) => number;
517
+ /** Helper to get ARIA attributes for a cell. */
518
+ getCellAriaProps: (colIndex: number) => Record<string, string | number | undefined>;
482
519
  /** Helper to get the virtual offset of a specific row. */
483
520
  getRowOffset: (index: number) => number;
484
521
  /** Helper to get the virtual offset of a specific column. */