Skip to content

Commit 0ee812f

Browse files
committed
chore: Using skip-to as wrap to single and list anchors
1 parent 546db01 commit 0ee812f

File tree

6 files changed

+148
-124
lines changed

6 files changed

+148
-124
lines changed

demo/search.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8">
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<meta http-equiv="X-UA-Compatible" content="ie=edge">
7-
<title>Using vue-skip-to</title>
7+
<title>Using search vue-skip-to</title>
88

99
<style>
1010
a:focus, div:focus {
@@ -19,7 +19,7 @@
1919

2020
<div id="app">
2121
<!-- data-vst is used for internal testing, it is not required -->
22-
<vue-skip-to to="#search" text="Skip to search" data-vst="skip-to"></vue-skip-to>
22+
<vue-skip-to to="#search" label="Skip to search" data-vst="skip-to"></vue-skip-to>
2323
<header>
2424
<h1>Press tab</h1>
2525

demo/skip-to-list.html

+10-7
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,35 @@
1919

2020
<div id="app">
2121
<!-- data-vst used for internal testing, it is NOT required -->
22-
<vue-skip-to-list
22+
<vue-skip-to
23+
title-list="Skip to"
2324
:to="[
24-
{anchor: '#main', label: 'Main'},
25-
{anchor: '#footer', label: 'Footer', ariaLabel: 'Skip to about'},
25+
{ anchor: '#main', label: 'Main content' },
26+
{ anchor: '#footer', label: 'Footer' },
2627
]"
2728
data-vst="skip-to-list"
2829
>
2930
Skip links
30-
</vue-skip-to-list>
31+
</vue-skip-to>
3132

3233
<header>
3334
<h1>Press tab</h1>
3435
</header>
3536

3637
<main id="main">
3738
<p>
38-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent ornare elit vitae felis tincidunt commodo. Phasellus volutpat pharetra consectetur. Mauris eu orci fermentum, maximus odio ut, consequat dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec et tortor sagittis, euismod risus vel, pulvinar urna. Suspendisse non facilisis leo. Quisque rhoncus vel mauris sed dapibus. Nunc nibh mauris, fringilla ut nisi at, maximus varius magna. Proin hendrerit a orci quis ullamcorper. Curabitur molestie eros quis eros tempus auctor. Proin luctus nibh quis sem dictum tempus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed eleifend, tortor tristique efficitur egestas, nulla libero tincidunt quam, vel pulvinar velit nulla id libero. Maecenas nisl quam, dictum in tempus in, ultrices a felis.
39+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent ornare elit vitae felis tincidunt commodo. <a href="https://google.com">Google</a> Phasellus volutpat pharetra consectetur. Mauris eu orci fermentum, maximus odio ut, consequat dui. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec et tortor sagittis, euismod risus vel, pulvinar urna. Suspendisse non facilisis leo. Quisque rhoncus vel mauris sed dapibus. Nunc nibh mauris, fringilla ut nisi at, maximus varius magna. Proin hendrerit a orci quis ullamcorper. Curabitur molestie eros quis eros tempus auctor. Proin luctus nibh quis sem dictum tempus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Sed eleifend, tortor tristique efficitur egestas, nulla libero tincidunt quam, vel pulvinar velit nulla id libero. Maecenas nisl quam, dictum in tempus in, ultrices a felis.
3940
</p>
4041
<p>
41-
Aliquam mattis convallis est et cursus. Phasellus tincidunt efficitur dui, non fringilla ipsum dignissim nec. Nam consectetur ante vitae malesuada rutrum. Praesent nec varius magna. Sed sem nisi, tempor eu venenatis vitae, consectetur eu sapien. Suspendisse sit amet massa lacinia purus ultrices lacinia. Aliquam non cursus quam, ac elementum enim. Morbi dignissim lacus nulla, sit amet interdum diam eleifend in. In hac habitasse platea dictumst. Suspendisse eget erat eu eros placerat pharetra vel eget metus. Morbi porta ex sed erat vestibulum, ac varius ex euismod. Nunc iaculis ornare lacus a egestas. Cras cursus facilisis mi, nec vulputate leo. Fusce varius varius arcu sit amet mollis.
42+
Aliquam mattis convallis est et cursus. <a href="https://google.com">Google</a> Phasellus tincidunt efficitur dui, non fringilla ipsum dignissim nec. Nam consectetur ante vitae malesuada rutrum. Praesent nec varius magna. Sed sem nisi, tempor eu venenatis vitae, consectetur eu sapien. Suspendisse sit amet massa lacinia purus ultrices lacinia. Aliquam non cursus quam, ac elementum enim. Morbi dignissim lacus nulla, sit amet interdum diam eleifend in. In hac habitasse platea dictumst. Suspendisse eget erat eu eros placerat pharetra vel eget metus. Morbi porta ex sed erat vestibulum, ac varius ex euismod. Nunc iaculis ornare lacus a egestas. Cras cursus facilisis mi, nec vulputate leo. Fusce varius varius arcu sit amet mollis.
4243
</p>
4344
</main>
4445

4546
<footer id="footer">
4647
<p>
47-
Sed elit nunc, volutpat in urna vel, hendrerit vulputate justo. Etiam pulvinar id ligula in ultrices. Suspendisse nulla risus, accumsan quis sagittis ac, faucibus eget dolor. Curabitur ullamcorper magna eget consequat facilisis. Cras massa leo, tristique nec tempus ac, auctor in arcu. Quisque a faucibus ex, congue eleifend ante. Praesent ultrices arcu neque, eget lacinia erat ultrices eget.
48+
Sed elit nunc, volutpat in urna vel, hendrerit vulputate justo.
49+
<a href="https://google.com">Google</a>
50+
Etiam pulvinar id ligula in ultrices. Suspendisse nulla risus, accumsan quis sagittis ac, faucibus eget dolor. Curabitur ullamcorper magna eget consequat facilisis. Cras massa leo, tristique nec tempus ac, auctor in arcu. Quisque a faucibus ex, congue eleifend ante. Praesent ultrices arcu neque, eget lacinia erat ultrices eget.
4851
</p>
4952
</footer>
5053
</div>

src/VueSkipTo.vue

+58-24
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,55 @@
11
<template>
2-
<a
3-
class="vue-skip-to"
4-
:href="to"
5-
@click.prevent="handleFocusElement"
6-
@focus="$emit('focus')"
7-
@blur="$emit('blur')"
8-
>
9-
<slot>{{ text }}</slot>
10-
</a>
2+
<div class="vue-skip-to">
3+
<component :is="comp" v-bind="props" />
4+
</div>
115
</template>
126

137
<script>
8+
import VueSkipToSingle from './VueSkipToSingle.vue'
9+
import VueSkipToList from './VueSkipToList.vue'
10+
1411
export default {
1512
name: 'VueSkipTo',
1613
1714
props: {
18-
text: {
15+
titleList: {
16+
type: String,
17+
default: 'Skip to'
18+
},
19+
label: {
1920
type: String,
2021
default: 'Skip to main content'
2122
},
2223
to: {
23-
type: String,
24+
type: [String, Array],
2425
default: '#main'
2526
}
2627
},
2728
28-
methods: {
29-
handleFocusElement ({ target }) {
30-
this.focusElement(target.getAttribute('href').substring(1))
29+
computed: {
30+
isList () {
31+
return Array.isArray(this.to)
32+
},
33+
34+
comp () {
35+
return this.isList ? VueSkipToList : VueSkipToSingle
3136
},
3237
33-
focusElement (id) {
34-
if (!id) return
35-
const element = window.document.getElementById(id)
36-
if (!element) return
37-
if (!/^(a|select|input|button|textarea)/i.test(element.tagName.toLowerCase())) {
38-
element.setAttribute('tabindex', -1)
39-
}
40-
element.focus()
38+
props () {
39+
if (this.isList) return { titleList: this.titleList, to: this.to }
40+
return { label: this.label, to: this.to }
4141
}
4242
}
4343
}
4444
</script>
4545

4646
<style>
47+
.vue-skip-to * {
48+
padding: 0;
49+
margin: 0;
50+
box-sizing: border-box;
51+
}
52+
4753
.vue-skip-to {
4854
position: fixed;
4955
width: 1px;
@@ -56,12 +62,40 @@ export default {
5662
border-width: 0;
5763
}
5864
59-
.vue-skip-to:focus, .vue-skip-to:hover {
65+
.vue-skip-to:focus-within, .vue-skip-to:focus, .vue-skip-to:hover {
6066
left: 0;
6167
top: 0;
6268
clip: auto;
6369
height: auto;
6470
width: auto;
65-
padding: 8px 10px;
71+
background-color: white;
72+
border: 2px solid #333;
73+
}
74+
75+
.vue-skip-to__nav-list {
76+
list-style-type: none;
77+
}
78+
79+
.vue-skip-to__nav > span, .vue-skip-to__link {
80+
display: block;
81+
padding: 8px 16px;
82+
color: #333;
83+
font-size: 18px;
84+
}
85+
86+
.vue-skip-to__nav > span {
87+
border-bottom: 2px solid #333;
88+
font-weight: bold;
6689
}
90+
91+
.vue-skip-to__link {
92+
text-decoration: none;
93+
}
94+
95+
.vue-skip-to__link:focus {
96+
outline: none;
97+
background-color: #333;
98+
color: #f2f2f2;
99+
}
100+
67101
</style>

src/VueSkipToList.vue

+36-89
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,36 @@
11
<template>
2-
<div :class="containerClassNames">
3-
<label>
4-
5-
<p class="vue-skip-to__label">
6-
<slot>Skip to</slot>
7-
</p>
8-
9-
<nav class="vue-skip-to__nav">
10-
<ul class="vue-skip-to__nav-list">
11-
<li
12-
v-for="el in to"
13-
:key="el.anchor"
14-
class="vue-skip-to__nav-list-item"
15-
>
16-
<vue-skip-to
17-
:to="el.anchor"
18-
:aria-label="el.ariaLabel"
19-
@focus="labelVisible = true"
20-
@blur="labelVisible = false"
21-
class="vue-skip-to vue-skip-to--relative"
22-
>
23-
{{ el.label }}
24-
</vue-skip-to>
25-
</li>
26-
</ul>
27-
</nav>
28-
29-
</label>
30-
</div>
2+
<nav class="vue-skip-to__nav">
3+
<span id="list-title">{{ titleList }}</span>
4+
<ul class="vue-skip-to__nav-list">
5+
<li
6+
v-for="el in to"
7+
:key="el.anchor"
8+
class="vue-skip-to__nav-list-item"
9+
@keydown.prevent.up="handleKeydown"
10+
@keydown.prevent.down="handleKeydown"
11+
>
12+
<VueSkipToSingle
13+
:to="el.anchor"
14+
:aria-label="el.ariaLabel || `${titleList} ${el.label}`"
15+
>
16+
{{ el.label }}
17+
</VueSkipToSingle>
18+
</li>
19+
</ul>
20+
</nav>
3121
</template>
3222

3323
<script>
24+
import VueSkipToSingle from './VueSkipToSingle.vue'
25+
3426
export default {
3527
name: 'VueSkipToList',
3628
3729
props: {
38-
// TODO: allow modifying `<skip-to>` props
30+
titleList: {
31+
type: String,
32+
default: 'Skip to'
33+
},
3934
to: {
4035
validator: function (val) {
4136
return Array.isArray(val) &&
@@ -48,68 +43,20 @@ export default {
4843
}
4944
},
5045
51-
data () {
52-
return {
53-
labelVisible: false
54-
}
46+
components: {
47+
VueSkipToSingle
5548
},
5649
57-
computed: {
58-
containerClassNames: function () {
59-
return {
60-
'vue-skip-to__list-container': true,
61-
'vue-skip-to__list-container--focus': this.labelVisible
62-
}
50+
methods: {
51+
handleKeydown ({ key, target }) {
52+
const parent = target.parentElement
53+
const itemList = key === 'ArrowUp' ? parent.previousElementSibling : parent.nextElementSibling
54+
55+
if (!itemList) return
56+
57+
const link = itemList.getElementsByTagName('a')
58+
if (link.length) link[0].focus()
6359
}
6460
}
65-
6661
}
6762
</script>
68-
69-
<style scoped>
70-
.vue-skip-to__list-container {
71-
position: absolute;
72-
left: -10000px;
73-
top: 0;
74-
border: 1px solid #000;
75-
}
76-
77-
.vue-skip-to__list-container--focus {
78-
background-color: #fff;
79-
left: 0;
80-
}
81-
82-
.vue-skip-to__label {
83-
font-weight: 800;
84-
margin: 8px 0 0 0;
85-
padding: 8px 24px 8px 10px;
86-
border-bottom: 1px solid #000;
87-
}
88-
89-
.vue-skip-to__nav-list {
90-
display: flex;
91-
flex-direction: column;
92-
padding: 0;
93-
margin: 0 0 10px 0;
94-
list-style-type: none;
95-
}
96-
97-
.vue-skip-to__nav-list-item {
98-
position: relative;
99-
}
100-
101-
/* override `<vue-skip-to>` styles */
102-
.vue-skip-to.vue-skip-to--relative {
103-
position: relative;
104-
left: unset;
105-
top: unset;
106-
display: block;
107-
padding: 8px 24px 8px 10px;
108-
color: #000;
109-
text-decoration: none;
110-
}
111-
112-
.vue-skip-to.vue-skip-to--relative:focus {
113-
color: #fff;
114-
}
115-
</style>

src/VueSkipToSingle.vue

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<template>
2+
<a
3+
:href="to"
4+
@click.prevent="handleFocusElement"
5+
class="vue-skip-to__link"
6+
>
7+
<slot>{{ label }}</slot>
8+
</a>
9+
</template>
10+
11+
<script>
12+
export default {
13+
name: 'VueSkipToSingle',
14+
15+
props: {
16+
label: {
17+
type: String,
18+
default: 'Skip to main content'
19+
},
20+
to: {
21+
type: String,
22+
default: '#main'
23+
}
24+
},
25+
26+
methods: {
27+
handleFocusElement ({ target }) {
28+
this.focusElement(target.getAttribute('href').substring(1))
29+
},
30+
31+
focusElement (id) {
32+
if (!id) return
33+
const element = window.document.getElementById(id)
34+
if (!element) return
35+
if (!/^(a|select|input|button|textarea)/i.test(element.tagName.toLowerCase())) {
36+
element.setAttribute('tabindex', -1)
37+
}
38+
element.focus()
39+
}
40+
}
41+
}
42+
</script>

src/index.js

-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import VueSkipTo from './VueSkipTo.vue'
2-
import VueSkipToList from './VueSkipToList.vue'
32

43
export default function install (Vue) {
54
if (install.installed) return
65
install.installed = true
76
Vue.component('VueSkipTo', VueSkipTo)
8-
Vue.component('VueSkipToList', VueSkipToList)
97
}
108

119
// auto install

0 commit comments

Comments
 (0)