Fixed #2920 - Improve ScrollPanel implementation for Accessibility
parent
e2a631657b
commit
1f65c0892f
|
@ -1,6 +1,16 @@
|
|||
const ScrollPanelProps = [
|
||||
{
|
||||
name: "step",
|
||||
type: "number",
|
||||
default: "5",
|
||||
description: "Step factor to scroll the content while pressing the arrow keys."
|
||||
}
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
scrollpanel: {
|
||||
name: "ScrollPanel",
|
||||
description: "ScrollPanel is a cross browser, lightweight and themable alternative to native browser scrollbar."
|
||||
description: "ScrollPanel is a cross browser, lightweight and themable alternative to native browser scrollbar.",
|
||||
props: ScrollPanelProps
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,6 +2,11 @@ import { VNode } from 'vue';
|
|||
import { ClassComponent, GlobalComponentConstructor } from '../ts-helpers';
|
||||
|
||||
export interface ScrollPanelProps {
|
||||
/**
|
||||
* Step factor to scroll the content while pressing the arrow keys.
|
||||
* Default value is 5.
|
||||
*/
|
||||
step?: number | undefined;
|
||||
}
|
||||
|
||||
export interface ScrollPanelSlots {
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
<template>
|
||||
<div class="p-scrollpanel p-component">
|
||||
<div class="p-scrollpanel-wrapper">
|
||||
<div ref="content" class="p-scrollpanel-content" @scroll="moveBar" @mouseenter="moveBar">
|
||||
<div class="p-scrollpanel p-component" role="scrollbar" :aria-orientation="orientation" :aria-valuenow="orientation === 'vertical' ? lastScrollTop : lastScrollLeft" :aria-controls="id + '_scrollpanel'">
|
||||
<div class="p-scrollpanel-wrapper" :id="id + '_scrollpanel'">
|
||||
<div ref="content" class="p-scrollpanel-content" @scroll="onScroll" @mouseenter="moveBar">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div ref="xBar" class="p-scrollpanel-bar p-scrollpanel-bar-x" @mousedown="onXBarMouseDown"></div>
|
||||
<div ref="yBar" class="p-scrollpanel-bar p-scrollpanel-bar-y" @mousedown="onYBarMouseDown"></div>
|
||||
<div ref="xBar" class="p-scrollpanel-bar p-scrollpanel-bar-x" tabindex="0" @mousedown="onXBarMouseDown" @keydown="onKeyDown($event)" @keyup="onKeyUp" @focus="onFocus" @blur="onBlur"></div>
|
||||
<div ref="yBar" class="p-scrollpanel-bar p-scrollpanel-bar-y" tabindex="0" @mousedown="onYBarMouseDown" @keydown="onKeyDown($event)" @keyup="onKeyUp" @focus="onFocus"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {DomHandler} from 'primevue/utils';
|
||||
import {DomHandler,UniqueComponentId} from 'primevue/utils';
|
||||
|
||||
export default {
|
||||
name: 'ScrollPanel',
|
||||
props: {
|
||||
step: {
|
||||
type: Number,
|
||||
default: 5
|
||||
}
|
||||
},
|
||||
initialized: false,
|
||||
documentResizeListener: null,
|
||||
documentMouseMoveListener: null,
|
||||
|
@ -26,6 +32,16 @@ export default {
|
|||
isYBarClicked: false,
|
||||
lastPageX: null,
|
||||
lastPageY: null,
|
||||
timer: null,
|
||||
outsideClickListener: null,
|
||||
data() {
|
||||
return {
|
||||
id: UniqueComponentId(),
|
||||
orientation: 'vertical',
|
||||
lastScrollTop: 0,
|
||||
lastScrollLeft: 0
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$el.offsetParent) {
|
||||
this.initialize();
|
||||
|
@ -98,6 +114,7 @@ export default {
|
|||
},
|
||||
onYBarMouseDown(e) {
|
||||
this.isYBarClicked = true;
|
||||
this.$refs.yBar.focus();
|
||||
this.lastPageY = e.pageY;
|
||||
DomHandler.addClass(this.$refs.yBar, 'p-scrollpanel-grabbed');
|
||||
DomHandler.addClass(document.body, 'p-scrollpanel-grabbed');
|
||||
|
@ -107,6 +124,7 @@ export default {
|
|||
},
|
||||
onXBarMouseDown(e) {
|
||||
this.isXBarClicked = true;
|
||||
this.$refs.xBar.focus();
|
||||
this.lastPageX = e.pageX;
|
||||
DomHandler.addClass(this.$refs.xBar, 'p-scrollpanel-grabbed');
|
||||
DomHandler.addClass(document.body, 'p-scrollpanel-grabbed');
|
||||
|
@ -114,6 +132,89 @@ export default {
|
|||
this.bindDocumentMouseListeners();
|
||||
e.preventDefault();
|
||||
},
|
||||
onScroll(event) {
|
||||
if (this.lastScrollLeft !== event.target.scrollLeft) {
|
||||
this.lastScrollLeft = event.target.scrollLeft;
|
||||
this.orientation = 'horizontal';
|
||||
}
|
||||
else if (this.lastScrollTop !== event.target.scrollTop) {
|
||||
this.lastScrollTop = event.target.scrollTop;
|
||||
this.orientation = 'vertical';
|
||||
}
|
||||
|
||||
this.moveBar();
|
||||
},
|
||||
onKeyDown(event) {
|
||||
if (this.orientation === 'vertical') {
|
||||
switch(event.code) {
|
||||
case 'ArrowDown': {
|
||||
this.setTimer('scrollTop', this.step);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowUp': {
|
||||
this.setTimer('scrollTop', this.step * -1);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowRight': {
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
//no op
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
else if (this.orientation === 'horizontal') {
|
||||
switch(event.code) {
|
||||
case 'ArrowRight': {
|
||||
this.setTimer('scrollLeft', this.step);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowLeft': {
|
||||
this.setTimer('scrollLeft', this.step * -1);
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ArrowDown':
|
||||
case 'ArrowUp': {
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
//no op
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
onKeyUp() {
|
||||
this.clearTimer();
|
||||
},
|
||||
repeat(bar, step) {
|
||||
this.$refs.content[bar] += step;
|
||||
this.moveBar();
|
||||
},
|
||||
setTimer(bar, step) {
|
||||
this.clearTimer();
|
||||
this.timer = setTimeout(() => {
|
||||
this.repeat(bar, step);
|
||||
}, 40);
|
||||
},
|
||||
clearTimer() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
},
|
||||
onDocumentMouseMove(e) {
|
||||
if (this.isXBarClicked) {
|
||||
this.onMouseMoveForXBar(e);
|
||||
|
@ -142,6 +243,19 @@ export default {
|
|||
this.$refs.content.scrollTop += deltaY / this.scrollYRatio;
|
||||
});
|
||||
},
|
||||
onFocus(event) {
|
||||
if (this.$refs.xBar.isSameNode(event.target)) {
|
||||
this.orientation = 'horizontal';
|
||||
}
|
||||
else if (this.$refs.yBar.isSameNode(event.target)) {
|
||||
this.orientation = 'vertical';
|
||||
}
|
||||
},
|
||||
onBlur() {
|
||||
if (this.orientation === 'horizontal') {
|
||||
this.orientation = 'vertical';
|
||||
}
|
||||
},
|
||||
onDocumentMouseUp() {
|
||||
DomHandler.removeClass(this.$refs.yBar, 'p-scrollpanel-grabbed');
|
||||
DomHandler.removeClass(this.$refs.xBar, 'p-scrollpanel-grabbed');
|
||||
|
@ -161,7 +275,10 @@ export default {
|
|||
scrollTop(scrollTop) {
|
||||
let scrollableHeight = this.$refs.content.scrollHeight - this.$refs.content.clientHeight;
|
||||
scrollTop = scrollTop > scrollableHeight ? scrollableHeight : scrollTop > 0 ? scrollTop : 0;
|
||||
this.$refs.contentscrollTop = scrollTop;
|
||||
this.$refs.content.scrollTop = scrollTop;
|
||||
},
|
||||
timeoutFrame(fn) {
|
||||
setTimeout(fn, 0);
|
||||
},
|
||||
bindDocumentMouseListeners() {
|
||||
if (!this.documentMouseMoveListener) {
|
||||
|
|
|
@ -46,10 +46,39 @@ import ScrollPanel from 'primevue/scrollpanel';
|
|||
background-color: #135ba1;
|
||||
}
|
||||
|
||||
</code></pre>
|
||||
|
||||
<h5>Steps</h5>
|
||||
<p>Step factor is 5px by default and can be customized with <i>step</i> option.</p>
|
||||
<pre v-code><code>
|
||||
<ScrollPanel style="width: 100%; height: 200px" :step="10"3>
|
||||
content
|
||||
</ScrollPanel>
|
||||
|
||||
</code></pre>
|
||||
|
||||
<h5>Properties</h5>
|
||||
<p>Any property such as style and class are passed to the main container element. There are no component specific properties.</p>
|
||||
<p>Any property such as style and class are passed to the main container element. Following are the additional properties to configure the component.</p>
|
||||
<div class="doc-tablewrapper">
|
||||
<table class="doc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Default</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>step</td>
|
||||
<td>number</td>
|
||||
<td>1</td>
|
||||
<td>Step factor to scroll the content while pressing the arrow keys.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h5>Styling</h5>
|
||||
<p>Following is the list of structural style classes, for theming classes visit <router-link to="/theming">theming</router-link> page.</p>
|
||||
|
@ -90,6 +119,42 @@ import ScrollPanel from 'primevue/scrollpanel';
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<h5>Accessibility</h5>
|
||||
<DevelopmentSection>
|
||||
<h6>Screen Reader</h6>
|
||||
<p>Scrollbars of the ScrollPanel has a <i>scrollbar</i> role along with the <i>aria-controls</i> attribute that refers to the id of the scrollable content container and the <i>aria-orientation</i> to indicate the orientation of scrolling.</p>
|
||||
|
||||
<h6>Header Keyboard Support</h6>
|
||||
<div class="doc-tablewrapper">
|
||||
<table class="doc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Function</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><i>down arrow</i></td>
|
||||
<td>Scrolls content down when vertical scrolling is available.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>up arrow</i></td>
|
||||
<td>Scrolls content up when vertical scrolling is available.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>left</i></td>
|
||||
<td>Scrolls content left when horizontal scrolling is available.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>right</i></td>
|
||||
<td>Scrolls content right when horizontal scrolling is available.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DevelopmentSection>
|
||||
|
||||
<h5>Dependencies</h5>
|
||||
<p>None.</p>
|
||||
</AppDoc>
|
||||
|
|
Loading…
Reference in New Issue