Skip to content
Snippets Groups Projects
Verified Commit ae55e648 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Fix #576: Improved keyboard accessibility on player, queue and various controls

parent a21db8d9
No related branches found
No related tags found
No related merge requests found
Showing with 185 additions and 82 deletions
Improved keyboard accessibility on player, queue and various controls (#576)
......@@ -359,6 +359,13 @@ html, body {
cursor: pointer;
}
.ui.really.basic.button {
&:not(:focus) {
box-shadow: none !important;
background-color: none !important;
}
}
.floated.buttons .button ~ .dropdown {
border-left: none;
}
......@@ -380,4 +387,27 @@ a {
display: none;
}
button.reset {
border: none;
margin: 0;
padding: 0;
width: auto;
overflow: visible;
background: transparent;
/* inherit font & color from ancestor */
color: inherit;
font: inherit;
/* Normalize `line-height`. Cannot be changed from `normal` in Firefox 4+. */
line-height: normal;
/* Corrects font smoothing for webkit */
-webkit-font-smoothing: inherit;
-moz-osx-font-smoothing: inherit;
/* Corrects inability to style clickable `input` types in iOS */
-webkit-appearance: none;
text-align: inherit;
}
</style>
<template>
<div class="ui pagination menu">
<div
<a href
:disabled="current - 1 < 1"
@click="selectPage(current - 1)"
:class="[{'disabled': current - 1 < 1}, 'item']"><i class="angle left icon"></i></div>
@click.prevent.stop="selectPage(current - 1)"
:class="[{'disabled': current - 1 < 1}, 'item']"><i class="angle left icon"></i></a>
<template v-if="!compact">
<div
<a href
v-if="page !== 'skip'"
v-for="page in pages"
@click="selectPage(page)"
@click.prevent.stop="selectPage(page)"
:class="[{'active': page === current}, 'item']">
{{ page }}
</div>
</a href>
<div v-else class="disabled item">
...
</div>
</template>
<div
<a href
:disabled="current + 1 > maxPage"
@click="selectPage(current + 1)"
:class="[{'disabled': current + 1 > maxPage}, 'item']"><i class="angle right icon"></i></div>
@click.prevent.stop="selectPage(current + 1)"
:class="[{'disabled': current + 1 > maxPage}, 'item']"><i class="angle right icon"></i></a>
</div>
</template>
......@@ -90,4 +90,3 @@ export default {
cursor: pointer;
}
</style>
......@@ -16,8 +16,8 @@
<div class="menu-area">
<div class="ui compact fluid two item inverted menu">
<a class="active item" @click="selectedTab = 'library'" data-tab="library"><translate>Browse</translate></a>
<a class="item" @click="selectedTab = 'queue'" data-tab="queue">
<a class="active item" href @click.prevent.stop="selectedTab = 'library'" data-tab="library"><translate>Browse</translate></a>
<a class="item" href @click.prevent.stop="selectedTab = 'queue'" data-tab="queue">
<translate>Queue</translate>&nbsp;
<template v-if="queue.tracks.length === 0">
<translate>(empty)</translate>
......@@ -128,8 +128,10 @@
<img class="ui mini image" v-else src="../assets/audio/default-cover.png">
</td>
<td colspan="4">
<strong>{{ track.title }}</strong><br />
{{ track.artist.name }}
<button class="title reset ellipsis">
<strong>{{ track.title }}</strong><br />
{{ track.artist.name }}
</button>
</td>
<td>
<template v-if="$store.getters['favorites/isFavorite'](track.id)">
......@@ -137,7 +139,9 @@
</template>
</td>
<td>
<i @click.stop="cleanTrack(index)" class="circular trash icon"></i>
<button @click.stop="cleanTrack(index)" :class="['ui', {'inverted': index != queue.currentIndex}, 'really', 'tiny', 'basic', 'circular', 'icon', 'button']">
<i class="trash icon"></i>
</button>
</td>
</tr>
</draggable>
......@@ -428,4 +432,8 @@ $sidebar-color: #3d3e3f;
top: -0.5em;
width: 3em;
}
:not(.active) button.title {
outline-color: white;
}
</style>
......@@ -12,9 +12,9 @@
<div v-if="!discrete && !iconOnly" :class="['ui', {disabled: !playable}, 'floating', 'dropdown', {'icon': !dropdownOnly}, {'button': !dropdownOnly}]">
<i :class="dropdownIconClasses.concat(['icon'])"></i>
<div class="menu">
<div class="item" :disabled="!playable" @click="add" :title="labels.addToQueue"><i class="plus icon"></i><translate>Add to queue</translate></div>
<div class="item" :disabled="!playable" @click="addNext()" :title="labels.playNext"><i class="step forward icon"></i><translate>Play next</translate></div>
<div class="item" :disabled="!playable" @click="addNext(true)" :title="labels.playNow"><i class="play icon"></i><translate>Play now</translate></div>
<button class="item basic" ref="add" data-ref="add" :disabled="!playable" @click.stop.prevent="add" :title="labels.addToQueue"><i class="plus icon"></i><translate>Add to queue</translate></button>
<button class="item basic" ref="addNext" data-ref="addNext" :disabled="!playable" @click.stop.prevent="addNext()" :title="labels.playNext"><i class="step forward icon"></i><translate>Play next</translate></button>
<button class="item basic" ref="playNow" data-ref="playNow" :disabled="!playable" @click.stop.prevent="addNext(true)" :title="labels.playNow"><i class="play icon"></i><translate>Play now</translate></button>
</div>
</div>
</span>
......@@ -46,7 +46,16 @@ export default {
}
},
mounted () {
jQuery(this.$el).find('.ui.dropdown').dropdown()
let self = this
jQuery(this.$el).find('.ui.dropdown').dropdown({
selectOnKeydown: false,
action: function (text, value, $el) {
// used ton ensure focusing the dropdown and clicking via keyboard
// works as expected
self.$refs[$el.data('ref')].click()
jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
}
})
},
computed: {
labels () {
......@@ -139,6 +148,7 @@ export default {
this.getPlayableTracks().then((tracks) => {
self.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => self.addMessage(tracks))
})
jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
},
addNext (next) {
let self = this
......@@ -150,6 +160,7 @@ export default {
self.$store.dispatch('queue/next')
}
})
jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
},
addMessage (tracks) {
if (tracks.length < 1) {
......@@ -170,4 +181,8 @@ export default {
i {
cursor: pointer;
}
button.item {
background-color: white;
width: 100%;
}
</style>
......@@ -31,9 +31,11 @@
<div class="description">
<track-favorite-icon
v-if="$store.state.auth.authenticated"
:class="{'inverted': !$store.getters['favorites/isFavorite'](currentTrack.id)}"
:track="currentTrack"></track-favorite-icon>
<track-playlist-icon
v-if="$store.state.auth.authenticated"
:class="['inverted']"
:track="currentTrack"></track-playlist-icon>
</div>
</div>
......@@ -55,44 +57,71 @@
</div>
<div class="two wide column controls ui grid">
<div
<a
href
:title="labels.previousTrack"
:aria-label="labels.previousTrack"
class="two wide column control"
@click.prevent.stop="previous"
:disabled="emptyQueue">
<i @click="previous" :class="['ui', 'backward', {'disabled': emptyQueue}, 'secondary', 'icon']"></i>
</div>
<div
<i :class="['ui', 'backward', {'disabled': emptyQueue}, 'secondary', 'icon']"></i>
</a>
<a
href
v-if="!playing"
:title="labels.play"
:aria-label="labels.play"
@click.prevent.stop="togglePlay"
class="two wide column control">
<i @click="togglePlay" :class="['ui', 'play', {'disabled': !currentTrack}, 'secondary', 'icon']"></i>
</div>
<div
<i :class="['ui', 'play', {'disabled': !currentTrack}, 'secondary', 'icon']"></i>
</a>
<a
href
v-else
:title="labels.pause"
:aria-label="labels.pause"
@click.prevent.stop="togglePlay"
class="two wide column control">
<i @click="togglePlay" :class="['ui', 'pause', {'disabled': !currentTrack}, 'secondary', 'icon']"></i>
</div>
<div
<i :class="['ui', 'pause', {'disabled': !currentTrack}, 'secondary', 'icon']"></i>
</a>
<a
href
:title="labels.next"
:aria-label="labels.next"
class="two wide column control"
@click.prevent.stop="next"
:disabled="!hasNext">
<i @click="next" :class="['ui', {'disabled': !hasNext}, 'step', 'forward', 'secondary', 'icon']" ></i>
</div>
<i :class="['ui', {'disabled': !hasNext}, 'step', 'forward', 'secondary', 'icon']" ></i>
</a>
<div
class="wide column control volume-control"
v-on:mouseover="showVolume = true"
v-on:mouseleave="showVolume = false"
v-bind:class="{ active : showVolume }">
<i
<a
href
v-if="volume === 0"
:title="labels.unmute"
@click="unmute" v-if="volume === 0" class="volume off secondary icon"></i>
<i
:aria-label="labels.unmute"
@click.prevent.stop="unmute">
<i class="volume off secondary icon"></i>
</a>
<a
href
v-else-if="volume < 0.5"
:title="labels.mute"
@click="mute" v-else-if="volume < 0.5" class="volume down secondary icon"></i>
<i
:aria-label="labels.mute"
@click.prevent.stop="mute">
<i class="volume down secondary icon"></i>
</a>
<a
href
v-else
:title="labels.mute"
@click="mute" v-else class="volume up secondary icon"></i>
:aria-label="labels.mute"
@click.prevent.stop="mute">
<i class="volume up secondary icon"></i>
</a>
<input
type="range"
step="0.05"
......@@ -102,44 +131,61 @@
v-if="showVolume" />
</div>
<div class="two wide column control looping" v-if="!showVolume">
<i
:title="labels.loopingDisabled"
<a
href
v-if="looping === 0"
@click="$store.commit('player/looping', 1)"
:disabled="!currentTrack"
:class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'secondary', 'icon']"></i>
<i
:title="labels.loopingDisabled"
:aria-label="labels.loopingDisabled"
@click.prevent.stop="$store.commit('player/looping', 1)"
:disabled="!currentTrack">
<i :class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'secondary', 'icon']"></i>
</a>
<a
href
@click.prevent.stop="$store.commit('player/looping', 2)"
:title="labels.loopingSingle"
:aria-label="labels.loopingSingle"
v-if="looping === 1"
@click="$store.commit('player/looping', 2)"
:disabled="!currentTrack"
class="repeat secondary icon">
<span class="ui circular tiny orange label">1</span>
</i>
<i
:disabled="!currentTrack">
<i
class="repeat secondary icon">
<span class="ui circular tiny orange label">1</span>
</i>
</a>
<a
href
:title="labels.loopingWhole"
:aria-label="labels.loopingWhole"
v-if="looping === 2"
@click="$store.commit('player/looping', 0)"
:disabled="!currentTrack"
class="repeat orange secondary icon">
</i>
@click.prevent.stop="$store.commit('player/looping', 0)">
<i
class="repeat orange secondary icon">
</i>
</a>
</div>
<div
<a
href
:disabled="queue.tracks.length === 0"
:title="labels.shuffle"
:aria-label="labels.shuffle"
v-if="!showVolume"
@click.prevent.stop="shuffle()"
class="two wide column control">
<div v-if="isShuffling" class="ui inline shuffling inverted tiny active loader"></div>
<i v-else @click="shuffle()" :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</div>
<i v-else :class="['ui', 'random', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</a>
<div class="one wide column" v-if="!showVolume"></div>
<div
<a
href
:disabled="queue.tracks.length === 0"
:title="labels.clear"
:aria-label="labels.clear"
v-if="!showVolume"
@click.prevent.stop="clean()"
class="two wide column control">
<i @click="clean()" :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</div>
<i :class="['ui', 'trash', 'secondary', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
</a>
</div>
<GlobalEvents
@keydown.space.prevent.exact="togglePlay"
......@@ -147,7 +193,6 @@
@keydown.ctrl.right.prevent.exact="next"
@keydown.ctrl.down.prevent.exact="$store.commit('player/incrementVolume', -0.1)"
@keydown.ctrl.up.prevent.exact="$store.commit('player/incrementVolume', 0.1)"
@keydown.f.prevent.exact="$store.dispatch('favorites/toggle', currentTrack.id)"
@keydown.l.prevent.exact="$store.commit('player/toggleLooping')"
@keydown.s.prevent.exact="shuffle"
/>
......@@ -370,6 +415,9 @@ export default {
color: white !important;
}
}
.controls a {
color: white;
}
.controls .icon.big {
cursor: pointer;
......
......@@ -3,10 +3,8 @@
<h3 class="ui header">
<slot name="title"></slot>
</h3>
<i @click="fetchData(previousPage)" :disabled="!previousPage" :class="['ui', {disabled: !previousPage}, 'circular', 'medium', 'angle left', 'icon']">
</i>
<i @click="fetchData(nextPage)" :disabled="!nextPage" :class="['ui', {disabled: !nextPage}, 'circular', 'medium', 'angle right', 'icon']">
</i>
<button :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle left', 'icon']"></i></button>
<button :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button>
<div class="ui hidden divider"></div>
<div class="ui five cards">
<div v-if="isLoading" class="ui inverted active dimmer">
......
......@@ -40,7 +40,7 @@
<td colspan="4" v-else>
<translate>N/A</translate>
</td>
<td>
<td colspan="2">
<track-favorite-icon class="favorite-icon" :track="track"></track-favorite-icon>
<track-playlist-icon
v-if="$store.state.auth.authenticated"
......@@ -89,7 +89,7 @@ export default {
tr:not(:hover) {
.favorite-icon:not(.favorited), .playlist-icon {
display: none;
visibility: hidden;
}
}
</style>
......@@ -8,7 +8,7 @@
<th colspan="4"><translate>Artist</translate></th>
<th colspan="4"><translate>Album</translate></th>
<th colspan="4"><translate>Duration</translate></th>
<th></th>
<th colspan="2"></th>
</tr>
</thead>
<tbody>
......
......@@ -3,12 +3,9 @@
<h3 class="ui header">
<slot name="title"></slot>
</h3>
<i @click="fetchData(previousPage)" :disabled="!previousPage" :class="['ui', {disabled: !previousPage}, 'circular', 'medium', 'angle up', 'icon']">
</i>
<i @click="fetchData(nextPage)" :disabled="!nextPage" :class="['ui', {disabled: !nextPage}, 'circular', 'medium', 'angle down', 'icon']">
</i>
<i @click="fetchData(url)" :class="['ui', 'circular', 'medium', 'refresh', 'icon']">
</i>
<button :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle up', 'icon']"></i></button>
<button :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle down', 'icon']"></i></button>
<button @click="fetchData(url)" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button>
<div class="ui divided unstackable items">
<div class="item" v-for="object in objects" :key="object.id">
<div class="ui tiny image">
......
......@@ -4,7 +4,14 @@
<translate v-if="isFavorite">In favorites</translate>
<translate v-else>Add to favorites</translate>
</button>
<i v-else @click="$store.dispatch('favorites/toggle', track.id)" :class="['favorite-icon', 'heart', {'pink': isFavorite}, {'favorited': isFavorite}, 'link', 'icon']" :title="title"></i>
<button
v-else
@click="$store.dispatch('favorites/toggle', track.id)"
:class="['ui', 'favorite-icon', {'pink': isFavorite}, {'favorited': isFavorite}, 'basic', 'circular', 'icon', 'really', 'button']"
:aria-label="title"
:title="title">
<i :class="['heart', {'pink': isFavorite}, 'basic', 'icon']"></i>
</button>
</template>
<script>
......
......@@ -6,12 +6,14 @@
<i class="list icon"></i>
<translate>Add to playlist...</translate>
</button>
<i
<button
v-else
@click="$store.commit('playlists/chooseTrack', track)"
:class="['playlist-icon', 'list', 'link', 'icon']"
:class="['ui', 'basic', 'circular', 'icon', 'really', 'button']"
:aria-label="labels.addToPlaylist"
:title="labels.addToPlaylist">
</i>
<i :class="['list', 'basic', 'icon']"></i>
</button>
</template>
<script>
......
......@@ -3,12 +3,10 @@
<h3 class="ui header">
<slot name="title"></slot>
</h3>
<i @click="fetchData(previousPage)" :disabled="!previousPage" :class="['ui', {disabled: !previousPage}, 'circular', 'medium', 'angle up', 'icon']">
</i>
<i @click="fetchData(nextPage)" :disabled="!nextPage" :class="['ui', {disabled: !nextPage}, 'circular', 'medium', 'angle down', 'icon']">
</i>
<i @click="fetchData(url)" :class="['ui', 'circular', 'medium', 'refresh', 'icon']">
</i>
<button :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle up', 'icon']"></i></button>
<button :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle down', 'icon']"></i></button>
<button @click="fetchData(url)" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button>
<div v-if="isLoading" class="ui inverted active dimmer">
<div class="ui loader"></div>
</div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment