Accessibility for Rating
parent
4b17571644
commit
d8ec9abd14
|
@ -66,7 +66,10 @@ const defaultOptions = {
|
||||||
aria: {
|
aria: {
|
||||||
trueLabel: 'True',
|
trueLabel: 'True',
|
||||||
falseLabel: 'False',
|
falseLabel: 'False',
|
||||||
nullLabel: 'Not Selected'
|
nullLabel: 'Not Selected',
|
||||||
|
star: 'star',
|
||||||
|
stars: 'stars',
|
||||||
|
noneStars: 'No Rating'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterMatchModeOptions: {
|
filterMatchModeOptions: {
|
||||||
|
|
|
@ -16,6 +16,10 @@ export interface RatingProps {
|
||||||
* Value of the rating.
|
* Value of the rating.
|
||||||
*/
|
*/
|
||||||
modelValue?: number | undefined;
|
modelValue?: number | undefined;
|
||||||
|
/**
|
||||||
|
* Name of the element.
|
||||||
|
*/
|
||||||
|
name?: string | undefined;
|
||||||
/**
|
/**
|
||||||
* When present, it specifies that the element should be disabled.
|
* When present, it specifies that the element should be disabled.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="containerClass">
|
<div :class="containerClass">
|
||||||
<span class="p-rating-icon p-rating-cancel pi pi-ban" :tabindex="focusIndex" v-if="cancel" @click="onCancelClick"></span>
|
<span class="p-hidden-accessible" v-if="cancel">
|
||||||
<span :key="i" v-for="i in stars" @click="onStarClick($event,i)" :tabindex="focusIndex" @keydown.enter.prevent="onStarClick($event,i)"
|
<label :for="labelFor(0)">{{labelText(0)}}</label>
|
||||||
:class="['p-rating-icon', {'pi pi-star': (i > modelValue), 'pi pi-star-fill': (i <= modelValue)}]"></span>
|
<input type="radio" :id="labelFor(0)" value="0" :name="name" :checked="modelValue === 0" :disabled="disabled" :readonly="readonly" @focus="onFocus($event, 0)" @blur="onBlur" @keydown="onKeyDown($event, 0)">
|
||||||
|
</span>
|
||||||
|
<span :class="['p-rating-icon p-rating-cancel pi pi-ban', {'p-focus': focusIndex === 0}]" v-if="cancel" @click="onCancelClick" @keydown="onKeyDown"></span>
|
||||||
|
|
||||||
|
<template :key="i" v-for="i in stars">
|
||||||
|
<span class="p-hidden-accessible">
|
||||||
|
<label :for="labelFor(i)">{{labelText(i)}}</label>
|
||||||
|
<input type="radio" :id="labelFor(i)" :value="i" :name="name" :checked="modelValue === i" :disabled="disabled" :readonly="readonly" @focus="onFocus($event, i)" @blur="onBlur" @keydown="onKeyDown($event,i)">
|
||||||
|
</span>
|
||||||
|
<span :class="['p-rating-icon', {'pi pi-star': (i > modelValue), 'pi pi-star-fill': (i <= modelValue), 'p-focus': i === focusIndex}]" @click="onStarClick($event,i)"></span>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Rating',
|
name: 'Rating',
|
||||||
emits: ['update:modelValue', 'change'],
|
emits: ['update:modelValue', 'change', 'focus', 'blur'],
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: null
|
default: null
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -32,12 +46,46 @@ export default {
|
||||||
default: true
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
focusIndex: null
|
||||||
|
};
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onStarClick(event, value) {
|
onStarClick(event, value) {
|
||||||
if (!this.readonly && !this.disabled) {
|
if (!this.readonly && !this.disabled) {
|
||||||
this.updateModel(event, value);
|
this.updateModel(event, value);
|
||||||
|
this.focusIndex = value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onKeyDown(event, value) {
|
||||||
|
if (event.code === 'Space') {
|
||||||
|
this.updateModel(event, value);
|
||||||
|
}
|
||||||
|
if (event.code === 'Tab') {
|
||||||
|
this.focusIndex = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFocus(event, index) {
|
||||||
|
if (!this.readonly) {
|
||||||
|
if (this.modelValue === null && this.focusIndex === null) {
|
||||||
|
this.cancel ? this.focusIndex = 0 : this.focusIndex = 1;
|
||||||
|
}
|
||||||
|
else if (this.modelValue !== null && this.focusIndex === null) {
|
||||||
|
this.focusIndex = this.modelValue;
|
||||||
|
this.updateModel(event, this.modelValue);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.focusIndex = index;
|
||||||
|
this.updateModel(event, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('focus', event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onBlur(event) {
|
||||||
|
this.$emit('blur', event);
|
||||||
|
},
|
||||||
onCancelClick(event) {
|
onCancelClick(event) {
|
||||||
if (!this.readonly && !this.disabled) {
|
if (!this.readonly && !this.disabled) {
|
||||||
this.updateModel(event, null);
|
this.updateModel(event, null);
|
||||||
|
@ -49,6 +97,13 @@ export default {
|
||||||
originalEvent: event,
|
originalEvent: event,
|
||||||
value: value
|
value: value
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
labelFor(index) {
|
||||||
|
return this.name + '_' + index;
|
||||||
|
},
|
||||||
|
labelText(index) {
|
||||||
|
return index === 0 ? this.$primevue.config.locale.aria.noneStars : index === 1
|
||||||
|
? index + ' ' + this.$primevue.config.locale.aria.star : index + ' ' + this.$primevue.config.locale.aria.stars;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -60,9 +115,6 @@ export default {
|
||||||
'p-disabled': this.disabled
|
'p-disabled': this.disabled
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
|
||||||
focusIndex() {
|
|
||||||
return (this.disabled || this.readonly) ? null : '0';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,4 +128,11 @@ export default {
|
||||||
.p-rating.p-rating-readonly .p-rating-icon {
|
.p-rating.p-rating-readonly .p-rating-icon {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-rating:not(.p-disabled) .p-rating-icon.p-focus {
|
||||||
|
outline: 0 none;
|
||||||
|
outline-offset: 0;
|
||||||
|
box-shadow: 0 0 0 0.2rem #BFDBFE;
|
||||||
|
border-color: #3B82F6;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -11,16 +11,16 @@
|
||||||
<div class="content-section implementation">
|
<div class="content-section implementation">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h5>Basic {{val1}}</h5>
|
<h5>Basic {{val1}}</h5>
|
||||||
<Rating v-model="val1" />
|
<Rating v-model="val1" name="basic" />
|
||||||
|
|
||||||
<h5>Without Cancel</h5>
|
<h5>Without Cancel</h5>
|
||||||
<Rating v-model="val2" :cancel="false" />
|
<Rating v-model="val2" :cancel="false" name="cancel" />
|
||||||
|
|
||||||
<h5>ReadOnly</h5>
|
<h5>ReadOnly</h5>
|
||||||
<Rating :modelValue="5" :readonly="true" :stars="10" :cancel="false" />
|
<Rating :modelValue="5" :readonly="true" :stars="10" :cancel="false" name="readonly" />
|
||||||
|
|
||||||
<h5>Disabled</h5>
|
<h5>Disabled</h5>
|
||||||
<Rating :modelValue="8" :disabled="true" :stars="10" />
|
<Rating :modelValue="8" :disabled="true" :stars="10" name="disabled" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,12 @@ import Rating from 'primevue/rating';
|
||||||
<td>null</td>
|
<td>null</td>
|
||||||
<td>Value of the rating.</td>
|
<td>Value of the rating.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>name</td>
|
||||||
|
<td>string</td>
|
||||||
|
<td>null</td>
|
||||||
|
<td>Name of the element.</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>disabled</td>
|
<td>disabled</td>
|
||||||
<td>boolean</td>
|
<td>boolean</td>
|
||||||
|
@ -133,6 +139,53 @@ import Rating from 'primevue/rating';
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h5>Accessibility</h5>
|
||||||
|
<DevelopmentSection>
|
||||||
|
<h6>Screen Reader</h6>
|
||||||
|
<p>Rating component internally uses radio buttons that are only visible to screen readers. The value to read for item is retrieved from the <Link href="/locale">locale</Link> API via <i>star</i> and <i>stars</i> of the <i>aria</i> property.</p>
|
||||||
|
|
||||||
|
<h6>Keyboard Support</h6>
|
||||||
|
<p>Keyboard interaction is derived from the native browser handling of radio buttons in a group.</p>
|
||||||
|
<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 star representing the value, if there is none then first star receives the focus.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="inline-flex flex-column">
|
||||||
|
<i class="mb-1">left arrow</i>
|
||||||
|
<i>up arrow</i>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>Moves focus to the previous star, if there is none then last radio button receives the focus.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span class="inline-flex flex-column">
|
||||||
|
<i class="mb-1">right arrow</i>
|
||||||
|
<i>down arrow</i>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>Moves focus to the next star, if there is none then first star receives the focus.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><i>space</i></td>
|
||||||
|
<td>If the focused star does not represent the value, changes the value to the star value.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</DevelopmentSection>
|
||||||
|
|
||||||
<h5>Dependencies</h5>
|
<h5>Dependencies</h5>
|
||||||
<p>None.</p>
|
<p>None.</p>
|
||||||
</AppDoc>
|
</AppDoc>
|
||||||
|
@ -149,16 +202,16 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h5>Basic {{val1}}</h5>
|
<h5>Basic {{val1}}</h5>
|
||||||
<Rating v-model="val1" />
|
<Rating v-model="val1" name="basic" />
|
||||||
|
|
||||||
<h5>Without Cancel</h5>
|
<h5>Without Cancel</h5>
|
||||||
<Rating v-model="val2" :cancel="false" />
|
<Rating v-model="val2" :cancel="false" name="cancel" />
|
||||||
|
|
||||||
<h5>ReadOnly</h5>
|
<h5>ReadOnly</h5>
|
||||||
<Rating :modelValue="5" :readonly="true" :stars="10" :cancel="false" />
|
<Rating :modelValue="5" :readonly="true" :stars="10" :cancel="false" name="readonly" />
|
||||||
|
|
||||||
<h5>Disabled</h5>
|
<h5>Disabled</h5>
|
||||||
<Rating :modelValue="8" :disabled="true" :stars="10" />
|
<Rating :modelValue="8" :disabled="true" :stars="10" name="disabled" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -180,16 +233,16 @@ export default {
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h5>Basic {{val1}}</h5>
|
<h5>Basic {{val1}}</h5>
|
||||||
<Rating v-model="val1" />
|
<Rating v-model="val1" name="basic" />
|
||||||
|
|
||||||
<h5>Without Cancel</h5>
|
<h5>Without Cancel</h5>
|
||||||
<Rating v-model="val2" :cancel="false" />
|
<Rating v-model="val2" :cancel="false" name="cancel" />
|
||||||
|
|
||||||
<h5>ReadOnly</h5>
|
<h5>ReadOnly</h5>
|
||||||
<Rating :modelValue="5" :readonly="true" :stars="10" :cancel="false" />
|
<Rating :modelValue="5" :readonly="true" :stars="10" :cancel="false" name="readonly" />
|
||||||
|
|
||||||
<h5>Disabled</h5>
|
<h5>Disabled</h5>
|
||||||
<Rating :modelValue="8" :disabled="true" :stars="10" />
|
<Rating :modelValue="8" :disabled="true" :stars="10" name="disabled" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -212,16 +265,16 @@ export default {
|
||||||
imports: `<script src="https://unpkg.com/primevue@^3/rating/rating.min.js"><\\/script>`,
|
imports: `<script src="https://unpkg.com/primevue@^3/rating/rating.min.js"><\\/script>`,
|
||||||
content: `<div id="app">
|
content: `<div id="app">
|
||||||
<h5>Basic {{val1}}</h5>
|
<h5>Basic {{val1}}</h5>
|
||||||
<p-rating v-model="val1"></p-rating>
|
<p-rating v-model="val1" name="basic"></p-rating>
|
||||||
|
|
||||||
<h5>Without Cancel</h5>
|
<h5>Without Cancel</h5>
|
||||||
<p-rating v-model="val2" :cancel="false"></p-rating>
|
<p-rating v-model="val2" :cancel="false" name="cancel"></p-rating>
|
||||||
|
|
||||||
<h5>ReadOnly</h5>
|
<h5>ReadOnly</h5>
|
||||||
<p-rating :model-value="5" :readonly="true" :stars="10" :cancel="false"></p-rating>
|
<p-rating :modelValue="5" :readonly="true" :stars="10" :cancel="false" name="readonly"></p-rating>
|
||||||
|
|
||||||
<h5>Disabled</h5>
|
<h5>Disabled</h5>
|
||||||
<p-rating :model-value="8" :disabled="true" :stars="10"></p-rating>
|
<p-rating :modelValue="8" :disabled="true" :stars="10" name="disabled"></p-rating>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
|
Loading…
Reference in New Issue