Accessibility for Knob

pull/2809/head
Tuğçe Küçükoğlu 2022-07-22 11:31:56 +03:00
parent af9fe6fbaa
commit f8725ae262
5 changed files with 197 additions and 5 deletions

View File

@ -76,6 +76,24 @@ const KnobProps = [
type: "string",
default: "{value}",
description: "Template string of the value."
},
{
name: "tabindex",
type: "number",
default: "null",
description: "Index of the element in tabbing order."
},
{
name: "aria-labelledby",
type: "string",
default: "null",
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: "Used to define a string that labels the element."
}
];

View File

@ -59,6 +59,18 @@ export interface KnobProps {
* Default value is '{value}'.
*/
valueTemplate?: string | undefined;
/**
* Index of the element in tabbing order.
*/
tabindex?: number | undefined;
/**
* Establishes relationships between the component and label(s) where its value should be one or more element IDs.
*/
ariaLabelledby?: string | undefined;
/**
* Used to define a string that labels the element.
*/
ariaLabel?: string | undefined;
}
export interface KnobSlots {

View File

@ -1,7 +1,7 @@
<template>
<div :class="containerClass">
<svg viewBox="0 0 100 100" :width="size" :height="size" @click="onClick" @mousedown="onMouseDown" @mouseup="onMouseUp"
@touchstart="onTouchStart" @touchend="onTouchEnd">
<svg viewBox="0 0 100 100" role="slider" :width="size" :height="size" :tabindex="tabindex" :aria-valuemin="min" :aria-valuemax="max" :aria-valuenow="modelValue" :aria-labelledby="ariaLabelledby" :aria-label="ariaLabel"
@click="onClick" @keydown="onKeyDown" @mousedown="onMouseDown" @mouseup="onMouseUp" @touchstart="onTouchStart" @touchend="onTouchEnd">
<path :d="rangePath" :stroke-width="strokeWidth" :stroke="rangeColor" class="p-knob-range"></path>
<path :d="valuePath" :stroke-width="strokeWidth" :stroke="valueColor" class="p-knob-value"></path>
<text v-if="showValue" :x="50" :y="57" text-anchor="middle" :fill="textColor" class="p-knob-text">{{valueToDisplay}}</text>
@ -74,6 +74,18 @@ export default {
valueTemplate: {
type: String,
default: "{value}"
},
tabindex: {
type: Number,
default: 0
},
'aria-labelledby': {
type: String,
default: null
},
'aria-label': {
type: String,
default: null
}
},
methods: {
@ -97,6 +109,11 @@ export default {
this.$emit('update:modelValue', newValue);
this.$emit('change', newValue);
},
updateModelValue(newValue) {
if (newValue > this.max) this.$emit('update:modelValue', this.max);
else if (newValue < this.min) this.$emit('update:modelValue', this.min);
else this.$emit('update:modelValue', newValue);
},
mapRange(x, inMin, inMax, outMin, outMax) {
return (x - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
},
@ -147,6 +164,49 @@ export default {
const offsetY = touch.clientY - rect.top;
this.updateValue(offsetX, offsetY);
}
},
onKeyDown(event) {
if (!this.disabled || !this.readonly) {
switch (event.code) {
case 'ArrowLeft':
case 'ArrowUp': {
event.preventDefault();
this.updateModelValue(this.modelValue + 1);
break;
}
case 'ArrowRight':
case 'ArrowDown': {
event.preventDefault();
this.updateModelValue(this.modelValue - 1);
break;
}
case 'Home': {
event.preventDefault();
this.$emit('update:modelValue', this.min);
break;
}
case 'End': {
event.preventDefault();
this.$emit('update:modelValue', this.max);
break;
}
case 'PageUp': {
event.preventDefault();
this.updateModelValue(this.modelValue + 10);
break;
}
case 'PageDown': {
event.preventDefault();
this.updateModelValue(this.modelValue - 10);
break;
}
}
}
}
},
computed: {

View File

@ -49,6 +49,12 @@
</div>
</div>
</div>
<div class="card text-center">
<h5>Reactive Knob</h5>
<Knob v-model="value10" :size="150" readonly />
<Button label="Increment" @click="value10++" class="mr-2" :disabled="value10 >= 100" />
<Button label="Decrement" @click="value10--" :disabled="value10 <= 0" />
</div>
</div>
<KnobDoc />
@ -70,6 +76,7 @@ export default {
value7: 40,
value8: 60,
value9: 50,
value10: 0
}
},
components: {

View File

@ -93,6 +93,12 @@ data() {
<td>boolean</td>
<td>false</td>
<td>When present, it specifies that the component value cannot be edited.</td>
</tr>
<tr>
<td>tabindex</td>
<td>number</td>
<td>null</td>
<td>Index of the element in tabbing order.</td>
</tr>
<tr>
<td>step</td>
@ -203,6 +209,73 @@ data() {
</table>
</div>
<h5>Accessibility</h5>
<DevelopmentSection>
<h6>Screen Reader</h6>
<p>Knob element component uses <i>slider</i> role in addition to the <i>aria-valuemin</i>, <i>aria-valuemax</i> and <i>aria-valuenow</i> attributes. Value to describe the component can be defined using
<i>aria-labelledby</i> and <i>aria-label</i> props.</p>
<pre v-code><code>
&lt;span id="label_number"&gt;Number&lt;/span&gt;
&lt;Knob aria-labelledby="label_number" /&gt;
&lt;Knob aria-label="Number" /&gt;
</code></pre>
<h6>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 slider.</td>
</tr>
<tr>
<td>
<span class="inline-flex flex-column">
<i class="mb-1">left arrow</i>
<i>up arrow</i>
</span>
</td>
<td>Decrements the value.</td>
</tr>
<tr>
<td>
<span class="inline-flex flex-column">
<i class="mb-1">right arrow</i>
<i>down arrow</i>
</span>
</td>
<td>Increments the value.</td>
</tr>
<tr>
<td><i>home</i></td>
<td>Set the minimum value.</td>
</tr>
<tr>
<td><i>end</i></td>
<td>Set the maximum value.</td>
</tr>
<tr>
<td><i>page up</i></td>
<td>Increments the value by 10 steps.</td>
</tr>
<tr>
<td><i>page down</i></td>
<td>Decrements the value by 10 steps.</td>
</tr>
</tbody>
</table>
</div>
</DevelopmentSection>
<h5>Dependencies</h5>
<p>None.</p>
@ -259,6 +332,12 @@ export default {
<h5 class="mt-3">Color</h5>
<Knob v-model="value9" valueColor="SlateGray" rangeColor="MediumTurquoise" />
</div>
<div class="field col-12 md:col-4">
<h5>Reactive Knob</h5>
<Knob v-model="value10" :size="150" readonly />
<Button label="Increment" @click="value10++" class="mr-2" :disabled="value10 >= 100" />
<Button label="Decrement" @click="value10--" :disabled="value10 <= 0" />
</div>
</div>
</div>
</template>
@ -276,6 +355,7 @@ export default {
value7: 40,
value8: 60,
value9: 50,
value10: 0
}
}
}
@ -324,6 +404,12 @@ export default {
<h5 class="mt-3">Color</h5>
<Knob v-model="value9" valueColor="SlateGray" rangeColor="MediumTurquoise" />
</div>
<div class="field col-12 md:col-4">
<h5>Reactive Knob</h5>
<Knob v-model="value10" :size="150" readonly />
<Button label="Increment" @click="value10++" class="mr-2" :disabled="value10 >= 100" />
<Button label="Decrement" @click="value10--" :disabled="value10 <= 0" />
</div>
</div>
</div>
</template>
@ -341,8 +427,9 @@ export default {
const value7 = ref(40);
const value8 = ref(60);
const value9 = ref(50);
const value10 = ref(0);
return { value1, value2, value3, value4, value5, value6, value7, value8, value9 }
return { value1, value2, value3, value4, value5, value6, value7, value8, value9, value10 }
}
}
<\\/script>
@ -389,6 +476,12 @@ export default {
<h5 class="mt-3">Color</h5>
<p-knob v-model="value9" value-color="SlateGray" range-color="MediumTurquoise"></p-knob>
</div>
<div class="field col-12 md:col-4">
<h5>Reactive Knob</h5>
<p-knob v-model="value10" :size="150" readonly></p-knob>
<p-button label="Increment" @click="value10++" class="mr-2" :disabled="value10 >= 100"></p-button>
<p-button label="Decrement" @click="value10--" :disabled="value10 <= 0"></p-button>
</div>
</div>
</div>
@ -406,11 +499,13 @@ export default {
const value7 = ref(40);
const value8 = ref(60);
const value9 = ref(50);
const value10 = ref(0);
return { value1, value2, value3, value4, value5, value6, value7, value8, value9 }
return { value1, value2, value3, value4, value5, value6, value7, value8, value9, value10 }
},
components: {
"p-knob": primevue.knob
"p-knob": primevue.knob,
"p-button": primevue.button
}
};