Last active
February 28, 2018 14:59
-
-
Save jmsfwk/ec74b4c3ff8c23ea5d0c78dfa378054f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<script src="vue.js"></script> | |
<script> | |
const data = [ | |
{ | |
"id": "739d8b36-e80f-4c04-9188-757a5bd64e70", | |
"headline": "Liverpool initiative for renewing housing stock to benefit local contractors" | |
}, | |
{ | |
"id": "739e5179-80ba-435a-9b92-bd3e77fc78a0", | |
"headline": "UK Budget: portfolio impact muted" | |
}, | |
{ | |
"id": "739f0851-416d-4fef-be25-953a1726aa0e", | |
"headline": "ProAxia Consulting Group | AeroOne®" | |
}, | |
{ | |
"id": "73a681c9-e032-4dca-8c20-96f7d538a8a5", | |
"headline": "World first for ACCA and University of London" | |
}, | |
{ | |
"id": "73a6b00d-1f48-4e6b-9bd2-c38a509c2975", | |
"headline": "Student IT experience at The Arthur Terry Learning Partnership is transformed by Hybrid Cloud " | |
}, | |
{ | |
"id": "739f0b60-7a0f-4d16-b288-f7647b4a8a6a", | |
"headline": "Latimer Hinks Warns Businesses Over Insurance Obligations" | |
}, | |
{ | |
"id": "739fb078-e840-4afe-b293-2af0e78370eb", | |
"headline": "Butchery acquisition leads to an additional £4m increase in turnover for Harlech Foodservice" | |
}, | |
{ | |
"id": "73a0f61d-3478-4e94-9c1d-f6bb7dec4543", | |
"headline": "Leeds’ Samuel Grant Group take home gold" | |
}, | |
{ | |
"id": "73a15ea7-8e56-4d39-a780-76cb6400fbda", | |
"headline": "Access Programme marks a year of exporting success for North East businesses" | |
}, | |
{ | |
"id": "73a265e3-5e7a-4512-9ada-02bb56a97a28", | |
"headline": "Balancing the benefits of BYOD" | |
} | |
]; | |
</script> | |
<style> | |
li.focused { | |
outline: 1px dotted black; | |
} | |
li[aria-selected=true] { | |
color: red; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="app"> | |
</div> | |
<script type="text/x-template" id="template"> | |
<ul role="listbox" tabindex="0" aria-multiselectable="true" | |
:aria-activedescendant="activedescendant" | |
@keydown.a="ctrlA" | |
@keydown.35="end" | |
@keydown.36="home" | |
@keydown.up="up" | |
@keydown.down="down" | |
@keydown.space="space"> | |
<li v-for="(option, i) in options" role="option" | |
:key="i" | |
:id="`${id}_${i}`" | |
:class="{focused: isFocused(i)}" | |
:aria-selected="isSelected(i)" | |
@click="click(i)">{{ option.headline }}</li> | |
</ul> | |
</script> | |
<script> | |
const app = new Vue({ | |
el: '#app', | |
template: '#template', | |
data: { | |
id: '', | |
checked: [], | |
focused: null, | |
selected: null, | |
lastSelected: null, | |
options: data, | |
}, | |
computed: { | |
activedescendant() { | |
return this.focused !== null ? `${this.id}_${this.focused}` : false; | |
}, | |
max() { | |
return this.options.length - 1; | |
} | |
}, | |
methods: { | |
isChecked(i) { | |
return this.checked.includes(i); | |
}, | |
isFocused(i) { | |
return i === this.focused; | |
}, | |
isSelected(i) { | |
return this.checked.includes(i); | |
}, | |
ctrlA(e){ | |
if (e.ctrlKey) { | |
for (const i of this.options.keys()) { | |
if (!this.isChecked(i)) { | |
this.check(i); | |
} | |
} | |
} | |
}, | |
home(e) { | |
console.log(e); | |
this.focus(0); | |
}, | |
end(e) { | |
console.log(e); | |
this.focus(this.max); | |
}, | |
up(e) { | |
console.log(e); | |
this.focus(this.focused - 1); | |
}, | |
down(e) { | |
console.log(e); | |
this.focus(this.focused + 1); | |
if (e.shiftKey) { | |
this.select(this.focused); | |
} | |
}, | |
selectContiguous(_start, _end) { | |
const start = Math.min(_start, _end); | |
const end = Math.max(_start, _end); | |
console.log(start, end); | |
for (let i = start; i <= end; i++) { | |
this.select(i); | |
} | |
}, | |
space(e) { | |
if (e.shiftKey) { | |
this.selectContiguous(this.focused, this.lastSelected); | |
return; | |
} | |
if (null !== this.focused) { | |
this.toggle(this.focused); | |
} | |
}, | |
click(i) { | |
this.focus(i); | |
this.toggle(i); | |
}, | |
focus(i) { | |
if(i <= this.max && i >= 0) { | |
this.focused = i; | |
} | |
}, | |
select(i) { | |
if(!this.isSelected(i)) { | |
const index = this.checked.indexOf(null); | |
Vue.set(this.checked, index === -1 ? this.checked.length : index, i); | |
this.lastSelected = i; | |
} | |
}, | |
unselect(i) { | |
const index = this.checked.indexOf(i); | |
Vue.set(this.checked, index, null); | |
}, | |
toggle(i) { | |
if(!this.isSelected(i)) { | |
this.select(i); | |
} else { | |
this.unselect(i); | |
} | |
}, | |
log(e) { | |
console.log(e); | |
}, | |
}, | |
created() { | |
console.log(this.$data); | |
this.id = Array.from(crypto.getRandomValues(new Uint8Array(8))) | |
.map(v => v.toString(16)) | |
.map(a => a.padStart(2, '0')) | |
.join(''); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://www.w3.org/TR/2017/NOTE-wai-aria-practices-1.1-20171214/#Listbox