@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.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +49 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +615 -559
- package/dist/index.mjs.map +1 -1
- package/dist/virtual-scroll.css +1 -1
- package/package.json +2 -4
- package/src/components/VirtualScroll.vue +133 -0
- package/src/composables/useVirtualScrollbar.ts +3 -0
- package/src/types.ts +37 -0
- package/src/components/VirtualScroll.test.ts +0 -2368
- package/src/components/VirtualScrollbar.test.ts +0 -174
- package/src/composables/useVirtualScroll.test.ts +0 -1627
- package/src/composables/useVirtualScrollbar.test.ts +0 -526
- package/src/utils/fenwick-tree.test.ts +0 -134
- package/src/utils/scroll.test.ts +0 -228
- package/src/utils/virtual-scroll-logic.test.ts +0 -2867
package/dist/virtual-scroll.css
CHANGED
|
@@ -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-
|
|
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.
|
|
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.
|
|
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. */
|