Client - improve keyboard navigation
This commit is contained in:
parent
6653648422
commit
3f672b5e90
@ -7,7 +7,7 @@
|
||||
<div class="admin-menu description-list">
|
||||
<dl>
|
||||
<dt>
|
||||
<router-link to="/admin/application">
|
||||
<router-link id="adminLink" to="/admin/application">
|
||||
{{ $t('admin.APPLICATION') }}
|
||||
</router-link>
|
||||
</dt>
|
||||
@ -54,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { capitalize, toRefs, withDefaults } from 'vue'
|
||||
import { capitalize, onMounted, toRefs, withDefaults } from 'vue'
|
||||
|
||||
import AppStatsCards from '@/components/Administration/AppStatsCards.vue'
|
||||
import Card from '@/components/Common/Card.vue'
|
||||
@ -69,6 +69,13 @@
|
||||
})
|
||||
|
||||
const { appConfig, appStatistics } = toRefs(props)
|
||||
|
||||
onMounted(() => {
|
||||
const applicationLink = document.getElementById('adminLink')
|
||||
if (applicationLink) {
|
||||
applicationLink.focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -22,8 +22,8 @@
|
||||
{{ $t('buttons.YES') }}
|
||||
</button>
|
||||
<button
|
||||
tabindex="0"
|
||||
id="cancel-button"
|
||||
:tabindex="0"
|
||||
:id="`${name}-cancel-button`"
|
||||
class="cancel"
|
||||
@click="emit('cancelAction')"
|
||||
>
|
||||
@ -46,9 +46,11 @@
|
||||
title: string
|
||||
message: string
|
||||
strongMessage?: string | null
|
||||
name?: string | null
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
strongMessage: () => null,
|
||||
name: 'modal',
|
||||
})
|
||||
|
||||
const emit = defineEmits(['cancelAction', 'confirmAction'])
|
||||
|
@ -138,7 +138,7 @@
|
||||
function updateDisplayModal(display: boolean) {
|
||||
displayModal.value = display
|
||||
if (display) {
|
||||
const button = document.getElementById('cancel-button')
|
||||
const button = document.getElementById('modal-cancel-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div class="chart-menu">
|
||||
<div class="chart-arrow">
|
||||
<i
|
||||
class="fa fa-chevron-left"
|
||||
aria-hidden="true"
|
||||
@click="emit('arrowClick', true)"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="chart-arrow transparent"
|
||||
@click="emit('arrowClick', true)"
|
||||
@keydown.enter="emit('arrowClick', true)"
|
||||
>
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</button>
|
||||
<div class="time-frames custom-checkboxes-group">
|
||||
<div class="time-frames-checkboxes custom-checkboxes">
|
||||
<div
|
||||
@ -22,23 +22,30 @@
|
||||
:checked="selectedTimeFrame === frame"
|
||||
@input="onUpdateTimeFrame(frame)"
|
||||
/>
|
||||
<span>{{ $t(`statistics.TIME_FRAMES.${frame}`) }}</span>
|
||||
<span
|
||||
:id="`frame-${frame}`"
|
||||
:tabindex="0"
|
||||
role="button"
|
||||
@keydown.enter="onUpdateTimeFrame(frame)"
|
||||
>
|
||||
{{ $t(`statistics.TIME_FRAMES.${frame}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart-arrow">
|
||||
<i
|
||||
class="fa fa-chevron-right"
|
||||
aria-hidden="true"
|
||||
@click="emit('arrowClick', false)"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="chart-arrow transparent"
|
||||
@click="emit('arrowClick', false)"
|
||||
@keydown.enter="emit('arrowClick', false)"
|
||||
>
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
|
||||
const emit = defineEmits(['arrowClick', 'timeFrameUpdate'])
|
||||
|
||||
@ -49,11 +56,19 @@
|
||||
selectedTimeFrame.value = timeFrame
|
||||
emit('timeFrameUpdate', timeFrame)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const input = document.getElementById('frame-month')
|
||||
if (input) {
|
||||
input.focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.chart-arrow,
|
||||
.time-frames {
|
||||
|
@ -11,7 +11,14 @@
|
||||
:disabled="disabled"
|
||||
@input="$router.push(getPath(tab))"
|
||||
/>
|
||||
<span>{{ $t(`user.PROFILE.TABS.${tab}`) }}</span>
|
||||
<span
|
||||
:id="`tab-${tab}`"
|
||||
:tabindex="0"
|
||||
role="button"
|
||||
@keydown.enter="$router.push(getPath(tab))"
|
||||
>
|
||||
{{ $t(`user.PROFILE.TABS.${tab}`) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -19,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { toRefs, withDefaults } from 'vue'
|
||||
import { onMounted, toRefs, withDefaults } from 'vue'
|
||||
|
||||
interface Props {
|
||||
tabs: string[]
|
||||
@ -33,6 +40,13 @@
|
||||
|
||||
const { tabs, selectedTab, disabled } = toRefs(props)
|
||||
|
||||
onMounted(() => {
|
||||
const input = document.getElementById(`tab-${tabs.value[0]}`)
|
||||
if (input) {
|
||||
input.focus()
|
||||
}
|
||||
})
|
||||
|
||||
function getPath(tab: string) {
|
||||
switch (tab) {
|
||||
case 'ACCOUNT':
|
||||
|
@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<div id="workout-card-title">
|
||||
<div
|
||||
class="workout-previous workout-arrow"
|
||||
<button
|
||||
class="workout-previous workout-arrow transparent"
|
||||
:class="{ inactive: !workoutObject.previousUrl }"
|
||||
:disabled="!workoutObject.previousUrl"
|
||||
:title="
|
||||
workoutObject.previousUrl
|
||||
? $t(`workouts.PREVIOUS_${workoutObject.type}`)
|
||||
@ -15,33 +16,37 @@
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-left" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
<div class="workout-card-title">
|
||||
<SportImage :sport-label="sport.label" :color="sport.color" />
|
||||
<div class="workout-title-date">
|
||||
<div class="workout-title" v-if="workoutObject.type === 'WORKOUT'">
|
||||
<span>{{ workoutObject.title }}</span>
|
||||
<i
|
||||
class="fa fa-edit"
|
||||
aria-hidden="true"
|
||||
<button
|
||||
class="transparent icon-button"
|
||||
@click="
|
||||
$router.push({
|
||||
name: 'EditWorkout',
|
||||
params: { workoutId: workoutObject.workoutId },
|
||||
})
|
||||
"
|
||||
/>
|
||||
<i
|
||||
>
|
||||
<i class="fa fa-edit" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
v-if="workoutObject.with_gpx"
|
||||
class="fa fa-download"
|
||||
aria-hidden="true"
|
||||
class="transparent icon-button"
|
||||
@click.prevent="downloadGpx(workoutObject.workoutId)"
|
||||
/>
|
||||
<i
|
||||
class="fa fa-trash"
|
||||
aria-hidden="true"
|
||||
@click="emit('displayModal', true)"
|
||||
/>
|
||||
>
|
||||
<i class="fa fa-download" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
id="delete-workout-button"
|
||||
class="transparent icon-button"
|
||||
@click="displayDeleteModal"
|
||||
>
|
||||
<i class="fa fa-trash" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="workout-title" v-else>
|
||||
{{ workoutObject.title }}
|
||||
@ -69,9 +74,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="workout-next workout-arrow"
|
||||
<button
|
||||
class="workout-next workout-arrow transparent"
|
||||
:class="{ inactive: !workoutObject.nextUrl }"
|
||||
:disabled="!workoutObject.nextUrl"
|
||||
:title="
|
||||
workoutObject.nextUrl
|
||||
? $t(`workouts.NEXT_${workoutObject.type}`)
|
||||
@ -82,7 +88,7 @@
|
||||
"
|
||||
>
|
||||
<i class="fa fa-chevron-right" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -119,6 +125,10 @@
|
||||
gpxLink.click()
|
||||
})
|
||||
}
|
||||
function displayDeleteModal(event: Event & { target: HTMLInputElement }) {
|
||||
event.target.blur()
|
||||
emit('displayModal', true)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -166,9 +176,13 @@
|
||||
}
|
||||
|
||||
.fa {
|
||||
cursor: pointer;
|
||||
padding: 0 $default-padding * 0.3;
|
||||
}
|
||||
.icon-button {
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
margin-left: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-limit) {
|
||||
|
@ -18,12 +18,20 @@
|
||||
@ready="fitBounds(bounds)"
|
||||
>
|
||||
<LControlLayers />
|
||||
<LControl position="topleft" class="map-control" @click="resetZoom">
|
||||
<LControl
|
||||
position="topleft"
|
||||
class="map-control"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
@click="resetZoom"
|
||||
>
|
||||
<i class="fa fa-refresh" aria-hidden="true" />
|
||||
</LControl>
|
||||
<LControl
|
||||
position="topleft"
|
||||
class="map-control"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
@click="toggleFullscreen"
|
||||
>
|
||||
<i
|
||||
|
@ -2,10 +2,12 @@
|
||||
<div class="workout-detail">
|
||||
<Modal
|
||||
v-if="displayModal"
|
||||
name="workout"
|
||||
:title="$t('common.CONFIRMATION')"
|
||||
:message="$t('workouts.WORKOUT_DELETION_CONFIRMATION')"
|
||||
@confirmAction="deleteWorkout(workoutObject.workoutId)"
|
||||
@cancelAction="updateDisplayModal(false)"
|
||||
@cancelAction="cancelDelete"
|
||||
@keydown.esc="cancelDelete"
|
||||
/>
|
||||
<Card>
|
||||
<template #title>
|
||||
@ -35,6 +37,7 @@
|
||||
ComputedRef,
|
||||
Ref,
|
||||
computed,
|
||||
nextTick,
|
||||
ref,
|
||||
toRefs,
|
||||
watch,
|
||||
@ -161,18 +164,50 @@
|
||||
}
|
||||
function updateDisplayModal(value: boolean) {
|
||||
displayModal.value = value
|
||||
if (displayModal.value) {
|
||||
nextTick(() => {
|
||||
const button = document.getElementById('workout-cancel-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
function cancelDelete() {
|
||||
updateDisplayModal(false)
|
||||
const button = document.getElementById('delete-workout-button')
|
||||
if (button) {
|
||||
button.focus()
|
||||
}
|
||||
}
|
||||
function deleteWorkout(workoutId: string) {
|
||||
updateDisplayModal(false)
|
||||
store.dispatch(WORKOUTS_STORE.ACTIONS.DELETE_WORKOUT, {
|
||||
workoutId: workoutId,
|
||||
})
|
||||
}
|
||||
function scrollToTop() {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.segmentId,
|
||||
async (newSegmentId) => {
|
||||
if (newSegmentId) {
|
||||
segmentId.value = +newSegmentId
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => route.params.workoutId,
|
||||
async (workoutId) => {
|
||||
if (workoutId) {
|
||||
displayModal.value = false
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -352,8 +352,15 @@
|
||||
const payloadErrorMessages: Ref<string[]> = ref([])
|
||||
|
||||
onMounted(() => {
|
||||
let element
|
||||
if (props.workout.id) {
|
||||
formatWorkoutForm(props.workout)
|
||||
element = document.getElementById('sport')
|
||||
} else {
|
||||
element = document.getElementById('withGpx')
|
||||
}
|
||||
if (element) {
|
||||
element.focus()
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
<div class="form-item">
|
||||
<label> {{ $t('workouts.FROM') }}: </label>
|
||||
<input
|
||||
id="from"
|
||||
name="from"
|
||||
type="date"
|
||||
:value="$route.query.from"
|
||||
@ -185,7 +186,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComputedRef, computed, toRefs, watch } from 'vue'
|
||||
import { ComputedRef, computed, toRefs, watch, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { LocationQuery, useRoute, useRouter } from 'vue-router'
|
||||
|
||||
@ -216,6 +217,13 @@
|
||||
)
|
||||
let params: LocationQuery = Object.assign({}, route.query)
|
||||
|
||||
onMounted(() => {
|
||||
const filter = document.getElementById('from')
|
||||
if (filter) {
|
||||
filter.focus()
|
||||
}
|
||||
})
|
||||
|
||||
function handleFilterChange(event: Event & { target: HTMLInputElement }) {
|
||||
if (event.target.value === '') {
|
||||
delete params[event.target.name]
|
||||
|
@ -85,14 +85,22 @@ button {
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:disabled {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--app-color);
|
||||
}
|
||||
|
||||
&:enabled:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:disabled, &.confirm:disabled {
|
||||
border-color: transparent;
|
||||
color: var(--disabled-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
Loading…
Reference in New Issue
Block a user