Accessibility for Chips
parent
091d26e7e3
commit
081b9414f9
|
@ -30,16 +30,28 @@ const ChipsProps = [
|
|||
description: "Whether to allow duplicate values or not."
|
||||
},
|
||||
{
|
||||
name: "class",
|
||||
name: "inputId",
|
||||
type: "string",
|
||||
default: "null",
|
||||
description: "Style class of the component."
|
||||
description: "Identifier of the focus input to match a label defined for the chips."
|
||||
},
|
||||
{
|
||||
name: "style",
|
||||
type: "any",
|
||||
name: "disabled",
|
||||
type: "boolean",
|
||||
default: "false",
|
||||
description: "When present, it specifies that the element should be disabled."
|
||||
},
|
||||
{
|
||||
name: "'aria-labelledby'",
|
||||
type: "string",
|
||||
default: "null",
|
||||
description: "Inline of the component."
|
||||
description: "Establishes relationships between the component and label(s) where its value should be one or more element IDs."
|
||||
},
|
||||
{
|
||||
name: "'aria-label'",
|
||||
type: "string",
|
||||
default: "null",
|
||||
description: "Establishes a string value that labels the component."
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -40,13 +40,25 @@ export interface ChipsProps {
|
|||
*/
|
||||
separator?: string | undefined;
|
||||
/**
|
||||
* Style class of the component input field.
|
||||
* Identifier of the focus input to match a label defined for the chips.
|
||||
*/
|
||||
class?: any;
|
||||
inputId?: string | undefined;
|
||||
/**
|
||||
* Inline style of the component.
|
||||
*
|
||||
*/
|
||||
style?: any;
|
||||
inputProps?: object | undefined;
|
||||
/**
|
||||
* When present, it specifies that the element should be disabled.
|
||||
*/
|
||||
disabled?: boolean | undefined;
|
||||
/**
|
||||
* Establishes relationships between the component and label(s) where its value should be one or more element IDs.
|
||||
*/
|
||||
'aria-labelledby'?: string | undefined;
|
||||
/**
|
||||
* Establishes a string value that labels the component.
|
||||
*/
|
||||
'aria-label'?: string | undefined;
|
||||
}
|
||||
|
||||
export interface ChipsSlots {
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<div :class="containerClass" :style="style">
|
||||
<ul :class="['p-inputtext p-chips-multiple-container', {'p-disabled': $attrs.disabled, 'p-focus': focused}]" @click="onWrapperClick()">
|
||||
<li v-for="(val,i) of modelValue" :key="`${i}_${val}`" class="p-chips-token">
|
||||
<slot name="chip" :value="val">
|
||||
<div :class="containerClass">
|
||||
<ul :class="['p-inputtext p-chips-multiple-container', {'p-disabled': disabled, 'p-focus': focused}]" role="listbox" aria-orientation="horizontal" @click="onWrapperClick()">
|
||||
<li v-for="(val,i) of modelValue" :key="`${i}_${val}`" role="option" :class="['p-chips-token', {'p-focus': focusedIndex === i}]">
|
||||
<slot name="chip" :value="val" :aria-label="val">
|
||||
<span class="p-chips-token-label">{{val}}</span>
|
||||
</slot>
|
||||
<span class="p-chips-token-icon pi pi-times-circle" @click="removeItem($event, i)"></span>
|
||||
</li>
|
||||
<li class="p-chips-input-token">
|
||||
<input ref="input" type="text" v-bind="$attrs" @focus="onFocus" @blur="onBlur($event)" @input="onInput" @keydown="onKeyDown($event)" @paste="onPaste($event)"
|
||||
:disabled="$attrs.disabled || maxedOut">
|
||||
<input ref="input" type="text" :id="inputId" :disabled="disabled || maxedOut" :aria-labelledby="ariaLabelledby" :aria-label="ariaLabel"
|
||||
@focus="onFocus($event)" @blur="onBlur($event)" @input="onInput" @keydown="onKeyDown($event)" @paste="onPaste($event)" v-bind="inputProps">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -18,8 +18,7 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'Chips',
|
||||
inheritAttrs: false,
|
||||
emits: ['update:modelValue', 'add', 'remove'],
|
||||
emits: ['update:modelValue', 'add', 'remove', 'focus', 'blur'],
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array,
|
||||
|
@ -41,13 +40,26 @@ export default {
|
|||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
class: null,
|
||||
style: null
|
||||
inputId: null,
|
||||
inputProps: null,
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
'aria-labelledby': {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
'aria-label': {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: null,
|
||||
focused: false
|
||||
focused: false,
|
||||
focusedIndex: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -57,36 +69,55 @@ export default {
|
|||
onInput(event) {
|
||||
this.inputValue = event.target.value;
|
||||
},
|
||||
onFocus() {
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
this.focused = false;
|
||||
this.focusedIndex = null;
|
||||
if (this.addOnBlur) {
|
||||
this.addItem(event, event.target.value, false);
|
||||
}
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onKeyDown(event) {
|
||||
const inputValue = event.target.value;
|
||||
|
||||
switch(event.which) {
|
||||
//backspace
|
||||
case 8:
|
||||
switch(event.code) {
|
||||
case 'Backspace':
|
||||
if (inputValue.length === 0 && this.modelValue && this.modelValue.length > 0) {
|
||||
this.removeItem(event, this.modelValue.length - 1);
|
||||
if (this.focusedIndex !== null) {
|
||||
this.removeItem(event, this.focusedIndex);
|
||||
}
|
||||
else this.removeItem(event, this.modelValue.length - 1);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
//enter
|
||||
case 13:
|
||||
case 'Enter':
|
||||
if (inputValue && inputValue.trim().length && !this.maxedOut) {
|
||||
this.addItem(event, inputValue, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ArrowLeft':
|
||||
if (inputValue.length === 0 && this.modelValue && this.modelValue.length > 0) {
|
||||
if (this.focusedIndex === 0 || this.focusedIndex === null) this.focusedIndex = this.modelValue.length-1;
|
||||
else this.focusedIndex--;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ArrowRight':
|
||||
if (inputValue.length === 0 && this.modelValue && this.modelValue.length > 0) {
|
||||
if (this.focusedIndex === null || this.focusedIndex === this.modelValue.length-1) this.focusedIndex = 0;
|
||||
else this.focusedIndex++;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (this.separator) {
|
||||
if (this.separator === ',' && (event.which === 188 || event.which === 110)) {
|
||||
if (this.separator === ',' && event.key === ',') {
|
||||
this.addItem(event, inputValue, true);
|
||||
}
|
||||
}
|
||||
|
@ -128,12 +159,13 @@ export default {
|
|||
}
|
||||
},
|
||||
removeItem(event, index) {
|
||||
if (this.$attrs.disabled) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
let values = [...this.modelValue];
|
||||
const removedItem = values.splice(index, 1);
|
||||
if (values.length === 0) this.focusedIndex = null;
|
||||
this.$emit('update:modelValue', values);
|
||||
this.$emit('remove', {
|
||||
originalEvent: event,
|
||||
|
@ -146,7 +178,7 @@ export default {
|
|||
return this.max && this.modelValue && this.max === this.modelValue.length;
|
||||
},
|
||||
containerClass() {
|
||||
return ['p-chips p-component p-inputwrapper', this.class, {
|
||||
return ['p-chips p-component p-inputwrapper', {
|
||||
'p-inputwrapper-filled': ((this.modelValue && this.modelValue.length) || (this.inputValue && this.inputValue.length)),
|
||||
'p-inputwrapper-focus': this.focused
|
||||
}];
|
||||
|
@ -201,4 +233,8 @@ export default {
|
|||
.p-fluid .p-chips {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.p-chips .p-chips-multiple-container .p-chips-token.p-focus {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -78,16 +78,16 @@ import Chips from 'primevue/chips';
|
|||
<td>Whether to allow duplicate values or not.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>style</td>
|
||||
<td>any</td>
|
||||
<td>inputId</td>
|
||||
<td>string</td>
|
||||
<td>null</td>
|
||||
<td>Style class of the component input field.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>class</td>
|
||||
<td>string</td>
|
||||
<td>null</td>
|
||||
<td>Inline style of the component.</td>
|
||||
<td>disabled</td>
|
||||
<td>boolean</td>
|
||||
<td>false</td>
|
||||
<td>When present, it specifies that the element should be disabled.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -174,6 +174,80 @@ import Chips from 'primevue/chips';
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<h5>Accessibility</h5>
|
||||
<DevelopmentSection>
|
||||
<h6>Screen Reader</h6>
|
||||
<p>Value to describe the component can either be provided via <i>label</i> tag combined with <i>inputId</i> prop or using <i>aria-labelledby</i>, <i>aria-label</i> props.
|
||||
Chip list uses <i>listbox</i> role with <i>aria-orientation</i> set to horizontal whereas each chip has the <i>option</i> role with <i>aria-label</i> set to the label of the chip.</p>
|
||||
|
||||
<pre v-code><code>
|
||||
<label for="chips1">Tags</label>
|
||||
<Chips inputId="chips1" />
|
||||
|
||||
<span id="chips2">Tags</span>
|
||||
<Chips aria-labelledby="chips2" />
|
||||
|
||||
<Chips aria-label="Tags" />
|
||||
|
||||
</code></pre>
|
||||
|
||||
<h6>Input Field 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 input element</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>enter</i></td>
|
||||
<td>Adds a new chips using the input field value.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>backspace</i></td>
|
||||
<td>Deletes the previous chip if the input field is empty.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>left arrow</i></td>
|
||||
<td>Moves focus to the previous chip if available and input field is empty.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h6>Chip 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>left arrow</i></td>
|
||||
<td>Moves focus to the previous chip if available.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>right arrow</i></td>
|
||||
<td>Moves focus to the next chip, if there is none then input field receives the focus.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><i>backspace</i></td>
|
||||
<td>Deletes the chips and adds focus to the input field.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</DevelopmentSection>
|
||||
|
||||
<h5>Dependencies</h5>
|
||||
<p>None.</p>
|
||||
</AppDoc>
|
||||
|
|
Loading…
Reference in New Issue