Accessibility for CascadeSelect
parent
638d132c21
commit
35556009ca
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div ref="container" :class="containerClass" @click="onClick($event)">
|
||||
<div class="p-hidden-accessible">
|
||||
<input ref="focusInput" type="text" :id="inputId" readonly :disabled="disabled" @focus="onFocus" @blur="onBlur" @keydown="onKeyDown" :tabindex="tabindex"
|
||||
aria-haspopup="listbox" :aria-expanded="overlayVisible" :aria-labelledby="ariaLabelledBy" />
|
||||
<input ref="focusInput" role="combobox" type="text" :id="inputId" readonly :disabled="disabled" @focus="onFocus" @blur="onBlur" @keydown="onKeyDown" :tabindex="tabindex"
|
||||
aria-haspopup="listbox" :aria-expanded="overlayVisible" :aria-labelledby="ariaLabelledBy" :aria-controls="listId" v-bind="inputProps" />
|
||||
</div>
|
||||
<span :class="labelClass">
|
||||
<slot name="value" :value="modelValue" :placeholder="placeholder">
|
||||
|
@ -16,9 +16,9 @@
|
|||
</div>
|
||||
<Portal :appendTo="appendTo">
|
||||
<transition name="p-connected-overlay" @enter="onOverlayEnter" @leave="onOverlayLeave" @after-leave="onOverlayAfterLeave">
|
||||
<div :ref="overlayRef" :class="panelStyleClass" v-if="overlayVisible" @click="onOverlayClick">
|
||||
<div :ref="overlayRef" :class="panelStyleClass" v-if="overlayVisible" @click="onOverlayClick" role="group">
|
||||
<div class="p-cascadeselect-items-wrapper">
|
||||
<CascadeSelectSub :options="options" :selectionPath="selectionPath"
|
||||
<CascadeSelectSub :id="listId" role="tree" :options="options" :selectionPath="selectionPath"
|
||||
:optionLabel="optionLabel" :optionValue="optionValue" :level="0" :templates="$slots"
|
||||
:optionGroupLabel="optionGroupLabel" :optionGroupChildren="optionGroupChildren"
|
||||
@option-select="onOptionSelect" @optiongroup-select="onOptionGroupSelect" :dirty="dirty" :root="true" />
|
||||
|
@ -30,7 +30,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {ConnectedOverlayScrollHandler,ObjectUtils,DomHandler,ZIndexUtils} from 'primevue/utils';
|
||||
import {ConnectedOverlayScrollHandler,ObjectUtils,DomHandler,ZIndexUtils,UniqueComponentId} from 'primevue/utils';
|
||||
import OverlayEventBus from 'primevue/overlayeventbus';
|
||||
import CascadeSelectSub from './CascadeSelectSub.vue';
|
||||
import Portal from 'primevue/portal';
|
||||
|
@ -71,7 +71,8 @@ export default {
|
|||
loadingIcon: {
|
||||
type: String,
|
||||
default: 'pi pi-spinner pi-spin'
|
||||
}
|
||||
},
|
||||
inputProps: null
|
||||
},
|
||||
outsideClickListener: null,
|
||||
scrollHandler: null,
|
||||
|
@ -260,7 +261,12 @@ export default {
|
|||
this.overlay = el;
|
||||
},
|
||||
onKeyDown(event) {
|
||||
switch(event.key) {
|
||||
if (this.disabled || this.loading) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
switch(event.code) {
|
||||
case 'Down':
|
||||
case 'ArrowDown':
|
||||
if (this.overlayVisible) {
|
||||
|
@ -272,11 +278,14 @@ export default {
|
|||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case 'Escape':
|
||||
case 'Space':
|
||||
if (this.overlayVisible) {
|
||||
this.hide();
|
||||
event.preventDefault();
|
||||
}
|
||||
else {
|
||||
this.show();
|
||||
}
|
||||
event.preventDefault();
|
||||
break;
|
||||
|
||||
case 'Tab':
|
||||
|
@ -326,6 +335,9 @@ export default {
|
|||
},
|
||||
dropdownIconClass() {
|
||||
return ['p-cascadeselect-trigger-icon', this.loading ? this.loadingIcon : 'pi pi-chevron-down'];
|
||||
},
|
||||
listId() {
|
||||
return UniqueComponentId() + '_list';
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<template>
|
||||
<ul class="p-cascadeselect-panel p-cascadeselect-items" role="listbox" aria-orientation="horizontal">
|
||||
<template v-for="(option,i) of options" :key="getOptionLabelToRender(option)">
|
||||
<li :class="getItemClass(option)" role="none">
|
||||
<div class="p-cascadeselect-item-content" @click="onOptionClick($event, option)" tabindex="0" @keydown="onKeyDown($event, option, i)" v-ripple>
|
||||
<ul class="p-cascadeselect-panel p-cascadeselect-items" aria-orientation="horizontal">
|
||||
<template v-for="(option,index) of options" :key="getOptionLabelToRender(option)">
|
||||
<li :class="getItemClass(option)" role="treeitem" :aria-label="getOptionLabelToRender(option)" :aria-selected="isOptionActive(option)" :aria-expanded="isOptionActive(option)"
|
||||
:aria-setsize="options.length" :aria-posinset="index + 1" :aria-level="level + 1">
|
||||
<div class="p-cascadeselect-item-content" @click="onOptionClick($event, option)" tabindex="0" @keydown="onKeyDown($event, option, index)" v-ripple>
|
||||
<component :is="templates['option']" :option="option" v-if="templates['option']"/>
|
||||
<template v-else>
|
||||
<span class="p-cascadeselect-item-text">{{getOptionLabelToRender(option)}}</span>
|
||||
|
@ -11,7 +12,7 @@
|
|||
</div>
|
||||
<CascadeSelectSub v-if="isOptionGroup(option) && isOptionActive(option)" class="p-cascadeselect-sublist" :selectionPath="selectionPath" :options="getOptionGroupChildren(option)"
|
||||
:optionLabel="optionLabel" :optionValue="optionValue" :level="level + 1" @option-select="onOptionSelect" @optiongroup-select="onOptionGroupSelect"
|
||||
:optionGroupLabel="optionGroupLabel" :optionGroupChildren="optionGroupChildren" :parentActive="isOptionActive(option)" :dirty="dirty" :templates="templates"/>
|
||||
:optionGroupLabel="optionGroupLabel" :optionGroupChildren="optionGroupChildren" :parentActive="isOptionActive(option)" :dirty="dirty" :templates="templates" :aria-level="level + 2"/>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
|
@ -117,7 +118,7 @@ export default {
|
|||
return this.activeOption === option;
|
||||
},
|
||||
onKeyDown(event, option, index) {
|
||||
switch (event.key) {
|
||||
switch (event.code) {
|
||||
case 'Down':
|
||||
case 'ArrowDown':
|
||||
var nextItem = this.$el.children[index + 1];
|
||||
|
@ -157,6 +158,7 @@ export default {
|
|||
break;
|
||||
|
||||
case 'Enter':
|
||||
case 'Space':
|
||||
this.onOptionClick(event, option);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="content-section introduction">
|
||||
<div class="feature-intro">
|
||||
<h1>CascadeSelect</h1>
|
||||
<p>CascadeSelect displays a nested structure of options.</p>
|
||||
<p>CascadeSelect is a form component to select a value from a nested structure of options.</p>
|
||||
</div>
|
||||
<AppDemoActions />
|
||||
</div>
|
||||
|
|
|
@ -25,87 +25,85 @@ import CascadeSelect from 'primevue/cascadeselect';
|
|||
</code></pre>
|
||||
|
||||
<pre v-code.script><code>
|
||||
data() {
|
||||
data() {
|
||||
return {
|
||||
selectedCity: null,
|
||||
countries: [
|
||||
{
|
||||
name: 'Australia',
|
||||
code: 'AU',
|
||||
states: [
|
||||
{
|
||||
name: 'New South Wales',
|
||||
cities: [
|
||||
{cname: 'Sydney', code: 'A-SY'},
|
||||
{cname: 'Newcastle', code: 'A-NE'},
|
||||
{cname: 'Wollongong', code: 'A-WO'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Queensland',
|
||||
cities: [
|
||||
{cname: 'Brisbane', code: 'A-BR'},
|
||||
{cname: 'Townsville', code: 'A-TO'}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Canada',
|
||||
code: 'CA',
|
||||
states: [
|
||||
{
|
||||
name: 'Quebec',
|
||||
cities: [
|
||||
{cname: 'Montreal', code: 'C-MO'},
|
||||
{cname: 'Quebec City', code: 'C-QU'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Ontario',
|
||||
cities: [
|
||||
{cname: 'Ottawa', code: 'C-OT'},
|
||||
{cname: 'Toronto', code: 'C-TO'}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'United States',
|
||||
code: 'US',
|
||||
states: [
|
||||
{
|
||||
name: 'California',
|
||||
cities: [
|
||||
{cname: 'Los Angeles', code: 'US-LA'},
|
||||
{cname: 'San Diego', code: 'US-SD'},
|
||||
{cname: 'San Francisco', code: 'US-SF'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Florida',
|
||||
cities: [
|
||||
{cname: 'Jacksonville', code: 'US-JA'},
|
||||
{cname: 'Miami', code: 'US-MI'},
|
||||
{cname: 'Tampa', code: 'US-TA'},
|
||||
{cname: 'Orlando', code: 'US-OR'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Texas',
|
||||
cities: [
|
||||
{cname: 'Austin', code: 'US-AU'},
|
||||
{cname: 'Dallas', code: 'US-DA'},
|
||||
{cname: 'Houston', code: 'US-HO'}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
data() {
|
||||
return {
|
||||
selectedCity: null,
|
||||
countries: [
|
||||
{
|
||||
name: 'Australia',
|
||||
code: 'AU',
|
||||
states: [
|
||||
{
|
||||
name: 'New South Wales',
|
||||
cities: [
|
||||
{cname: 'Sydney', code: 'A-SY'},
|
||||
{cname: 'Newcastle', code: 'A-NE'},
|
||||
{cname: 'Wollongong', code: 'A-WO'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Queensland',
|
||||
cities: [
|
||||
{cname: 'Brisbane', code: 'A-BR'},
|
||||
{cname: 'Townsville', code: 'A-TO'}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Canada',
|
||||
code: 'CA',
|
||||
states: [
|
||||
{
|
||||
name: 'Quebec',
|
||||
cities: [
|
||||
{cname: 'Montreal', code: 'C-MO'},
|
||||
{cname: 'Quebec City', code: 'C-QU'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Ontario',
|
||||
cities: [
|
||||
{cname: 'Ottawa', code: 'C-OT'},
|
||||
{cname: 'Toronto', code: 'C-TO'}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'United States',
|
||||
code: 'US',
|
||||
states: [
|
||||
{
|
||||
name: 'California',
|
||||
cities: [
|
||||
{cname: 'Los Angeles', code: 'US-LA'},
|
||||
{cname: 'San Diego', code: 'US-SD'},
|
||||
{cname: 'San Francisco', code: 'US-SF'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Florida',
|
||||
cities: [
|
||||
{cname: 'Jacksonville', code: 'US-JA'},
|
||||
{cname: 'Miami', code: 'US-MI'},
|
||||
{cname: 'Tampa', code: 'US-TA'},
|
||||
{cname: 'Orlando', code: 'US-OR'}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Texas',
|
||||
cities: [
|
||||
{cname: 'Austin', code: 'US-AU'},
|
||||
{cname: 'Dallas', code: 'US-DA'},
|
||||
{cname: 'Houston', code: 'US-HO'}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -360,6 +358,101 @@ data() {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<h5>Accessibility</h5>
|
||||
<DevelopmentSection>
|
||||
<h6>Screen Reader</h6>
|
||||
<p>Value to describe the component can either be provided with <i>aria-labelledby</i> or <i>aria-label</i> props. The cascadeselect element has a <i>combobox</i> role
|
||||
in addition to <i>aria-haspopup</i> and <i>aria-expanded</i> attributes. The relation between the combobox and the popup is created with <i>aria-controls</i> that refers to the id of the popup.</p>
|
||||
<p>The popup list has an id that refers to the <i>aria-controls</i> attribute of the <i>combobox</i> element and uses <i>tree</i> as the role. Each list item has a <i>treeitem</i> role along with <i>aria-label</i>, <i>aria-selected</i> and <i>aria-expanded</i> attributes. The container
|
||||
element of a treenode has the <i>group</i> role. The <i>aria-setsize</i>, <i>aria-posinset</i> and <i>aria-level</i> attributes are calculated implicitly and added to each treeitem.</p>
|
||||
|
||||
<p>If filtering is enabled, <i>filterInputProps</i> can be defined to give <i>aria-*</i> props to the filter input element.</p>
|
||||
|
||||
<pre v-code><code>
|
||||
<span id="dd1">Options</span>
|
||||
<CascadeSelect aria-labelledby="dd1" />
|
||||
|
||||
<CascadeSelect aria-label="Options" />
|
||||
|
||||
</code></pre>
|
||||
|
||||
<h6>Closed State 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>tab</i></td>
|
||||
<td>Moves focus to the cascadeselect element.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>space</i></td>
|
||||
<td>Opens the popup and moves visual focus to the selected option, if there is none then first option receives the focus.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>down arrow</i></td>
|
||||
<td>Opens the popup and moves visual focus to the selected option, if there is none then first option receives the focus.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h6>Popup 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>tab</i></td>
|
||||
<td>Hides the popup and moves focus to the next tabbable element.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>tab</i></td>
|
||||
<td>Hides the popup and moves focus to the previous tabbable element.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>enter</i></td>
|
||||
<td>Selects the focused option and closes the popup.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>space</i></td>
|
||||
<td>Selects the focused option and closes the popup.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>escape</i></td>
|
||||
<td>Closes the popup, moves focus to the cascadeselect element.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>down arrow</i></td>
|
||||
<td>Moves focus to the next option.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>up arrow</i></td>
|
||||
<td>Moves focus to the previous option.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>right arrow</i></td>
|
||||
<td>If option is closed, opens the option otherwise moves focus to the first child option.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>left arrow</i></td>
|
||||
<td>If option is open, closes the option otherwise moves focus to the parent option.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DevelopmentSection>
|
||||
|
||||
<h5>Dependencies</h5>
|
||||
<p>None.</p>
|
||||
</AppDoc>
|
||||
|
|
Loading…
Reference in New Issue