[A11y] Improve focus management for widget overlay

This commit is contained in:
Richard Schreiber
2025-03-27 13:18:59 +01:00
committed by GitHub
parent 6c7041c875
commit 1c8bcca846

View File

@@ -77,6 +77,13 @@ var strings = {
'FR': django.gettext('Fr'),
'SA': django.gettext('Sa'),
'SU': django.gettext('Su'),
'MONDAY': django.gettext('Monday'),
'TUESDAY': django.gettext('Tuesday'),
'WEDNESDAY': django.gettext('Wednesday'),
'THURSDAY': django.gettext('Thursday'),
'FRIDAY': django.gettext('Friday'),
'SATURDAY': django.gettext('Saturday'),
'SUNDAY': django.gettext('Sunday'),
},
'months': {
'01': django.gettext('January'),
@@ -964,10 +971,10 @@ Vue.component('pretix-widget-event-form', {
template: ('<div class="pretix-widget-event-form">'
// Back navigation
+ '<div class="pretix-widget-event-list-back" v-if="$root.events || $root.weeks || $root.days">'
+ '<a href="#" @click.prevent.stop="back_to_list" v-if="!$root.subevent">&lsaquo; '
+ '<a href="#" rel="back" @click.prevent.stop="back_to_list" v-if="!$root.subevent">&lsaquo; '
+ strings['back_to_list']
+ '</a>'
+ '<a href="#" @click.prevent.stop="back_to_list" v-if="$root.subevent">&lsaquo; '
+ '<a href="#" rel="back" @click.prevent.stop="back_to_list" v-if="$root.subevent">&lsaquo; '
+ strings['back_to_dates']
+ '</a>'
+ '</div>'
@@ -1119,6 +1126,11 @@ Vue.component('pretix-widget-event-form', {
back_to_list: function() {
this.$root.target_url = this.$root.parent_stack.pop();
this.$root.error = null;
if (!this.$root.subevent) {
// reset if we are not in a series
this.$root.name = null;
this.$root.frontpage_text = null;
}
this.$root.subevent = null;
this.$root.offset = 0;
this.$root.append_events = false;
@@ -1130,6 +1142,12 @@ Vue.component('pretix-widget-event-form', {
} else {
this.$root.view = "weeks";
}
var $el = this.$root.$el;
this.$root.$nextTick(function() {
// wait for redraw, then focus content element for better a11y
$el.focus();
});
},
calculate_buy_disabled: function() {
var i, j, k;
@@ -1197,7 +1215,7 @@ Vue.component('pretix-widget-event-list-filter-form', {
});
Vue.component('pretix-widget-event-list-entry', {
template: ('<a :class="classObject" @click.prevent.stop="select">'
template: ('<a href="#" :class="classObject" @click.prevent.stop="select">'
+ '<div class="pretix-widget-event-list-entry-name">{{ event.name }}</div>'
+ '<div class="pretix-widget-event-list-entry-date">{{ event.date_range }}</div>'
+ '<div class="pretix-widget-event-list-entry-location">{{ location }}</div>' // hidden by css for now, but
@@ -1237,7 +1255,7 @@ Vue.component('pretix-widget-event-list-entry', {
Vue.component('pretix-widget-event-list', {
template: ('<div class="pretix-widget-event-list">'
+ '<div class="pretix-widget-back" v-if="$root.weeks || $root.parent_stack.length > 0">'
+ '<a href="#" @click.prevent.stop="back_to_calendar" role="button">&lsaquo; '
+ '<a href="#" rel="prev" @click.prevent.stop="back_to_calendar">&lsaquo; '
+ strings['back']
+ '</a>'
+ '</div>'
@@ -1256,6 +1274,10 @@ Vue.component('pretix-widget-event-list', {
},
methods: {
back_to_calendar: function () {
// make sure to always focus content element
this.$nextTick(function () {
this.$root.$el.focus();
});
this.$root.offset = 0;
this.$root.append_events = false;
if (this.$root.weeks) {
@@ -1280,7 +1302,7 @@ Vue.component('pretix-widget-event-list', {
});
Vue.component('pretix-widget-event-calendar-event', {
template: ('<a :class="classObject" @click.prevent.stop="select">'
template: ('<a href="#" :class="classObject" @click.prevent.stop="select" v-bind:aria-describedby="describedby">'
+ '<strong class="pretix-widget-event-calendar-event-name">'
+ '{{ event.name }}'
+ '</strong>'
@@ -1288,7 +1310,8 @@ Vue.component('pretix-widget-event-calendar-event', {
+ '<div class="pretix-widget-event-calendar-event-availability" v-if="!event.continued && event.availability.text">{{ event.availability.text }}</div>'
+ '</a>'),
props: {
event: Object
event: Object,
describedby: String,
},
computed: {
classObject: function () {
@@ -1316,11 +1339,11 @@ Vue.component('pretix-widget-event-calendar-event', {
Vue.component('pretix-widget-event-week-cell', {
template: ('<div :class="classObject" @click.prevent.stop="selectDay">'
+ '<div class="pretix-widget-event-calendar-day" v-if="day">'
+ '<div class="pretix-widget-event-calendar-day" v-if="day" :id="id">'
+ '{{ dayhead }}'
+ '</div>'
+ '<div class="pretix-widget-event-calendar-events" v-if="day">'
+ '<pretix-widget-event-calendar-event v-for="e in day.events" :event="e"></pretix-widget-event-calendar-event>'
+ '<pretix-widget-event-calendar-event v-for="e in day.events" :event="e" :describedby="id"></pretix-widget-event-calendar-event>'
+ '</div>'
+ '</div>'),
props: {
@@ -1346,6 +1369,9 @@ Vue.component('pretix-widget-event-week-cell', {
}
},
computed: {
id: function () {
return this.day ? this.$root.html_id + '-' + this.day.date : '';
},
dayhead: function () {
if (!this.day) {
return;
@@ -1381,7 +1407,7 @@ Vue.component('pretix-widget-event-week-cell', {
Vue.component('pretix-widget-event-calendar-cell', {
template: ('<td :class="classObject" @click.prevent.stop="selectDay">'
+ '<div class="pretix-widget-event-calendar-day" v-if="day">'
+ '<div class="pretix-widget-event-calendar-day" v-if="day" v-bind:aria-label="date">'
+ '{{ daynum }}'
+ '</div>'
+ '<div class="pretix-widget-event-calendar-events" v-if="day">'
@@ -1417,6 +1443,9 @@ Vue.component('pretix-widget-event-calendar-cell', {
}
return this.day.date.substr(8);
},
date: function () {
return this.day ? (new Date(this.day.date)).toLocaleDateString() : '';
},
classObject: function () {
var o = {};
if (this.day && this.day.events.length > 0) {
@@ -1474,26 +1503,26 @@ Vue.component('pretix-widget-event-calendar', {
// Calendar navigation
+ '<div class="pretix-widget-event-calendar-head">'
+ '<a class="pretix-widget-event-calendar-previous-month" href="#" @click.prevent.stop="prevmonth" role="button">&laquo; '
+ '<a class="pretix-widget-event-calendar-previous-month" href="#" @click.prevent.stop="prevmonth">&laquo; '
+ strings['previous_month']
+ '</a> '
+ '<strong>{{ monthname }}</strong> '
+ '<a class="pretix-widget-event-calendar-next-month" href="#" @click.prevent.stop="nextmonth" role="button">'
+ '<a class="pretix-widget-event-calendar-next-month" href="#" @click.prevent.stop="nextmonth">'
+ strings['next_month']
+ ' &raquo;</a>'
+ '</div>'
// Calendar
+ '<table class="pretix-widget-event-calendar-table">'
+ '<table class="pretix-widget-event-calendar-table" :id="id" tabindex="0" v-bind:aria-label="monthname">'
+ '<thead>'
+ '<tr>'
+ '<th>' + strings['days']['MO'] + '</th>'
+ '<th>' + strings['days']['TU'] + '</th>'
+ '<th>' + strings['days']['WE'] + '</th>'
+ '<th>' + strings['days']['TH'] + '</th>'
+ '<th>' + strings['days']['FR'] + '</th>'
+ '<th>' + strings['days']['SA'] + '</th>'
+ '<th>' + strings['days']['SU'] + '</th>'
+ '<th aria-label="' + strings['days']['MONDAY'] + '">' + strings['days']['MO'] + '</th>'
+ '<th aria-label="' + strings['days']['TUESDAY'] + '">' + strings['days']['TU'] + '</th>'
+ '<th aria-label="' + strings['days']['WEDNESDAY'] + '">' + strings['days']['WE'] + '</th>'
+ '<th aria-label="' + strings['days']['THURSDAY'] + '">' + strings['days']['TH'] + '</th>'
+ '<th aria-label="' + strings['days']['FRIDAY'] + '">' + strings['days']['FR'] + '</th>'
+ '<th aria-label="' + strings['days']['SATURDAY'] + '">' + strings['days']['SA'] + '</th>'
+ '<th aria-label="' + strings['days']['SUNDAY'] + '">' + strings['days']['SU'] + '</th>'
+ '</tr>'
+ '</thead>'
+ '<tbody>'
@@ -1507,7 +1536,10 @@ Vue.component('pretix-widget-event-calendar', {
},
monthname: function () {
return strings['months'][this.$root.date.substr(5, 2)] + ' ' + this.$root.date.substr(0, 4);
}
},
id: function () {
return this.$root.html_id + "-event-calendar-table";
},
},
methods: {
back_to_list: function () {
@@ -1526,7 +1558,7 @@ Vue.component('pretix-widget-event-calendar', {
}
this.$root.date = String(curYear) + "-" + padNumber(curMonth, 2) + "-01";
this.$root.loading++;
this.$root.reload();
this.$root.reload({focus: '#'+this.id});
},
nextmonth: function () {
var curMonth = parseInt(this.$root.date.substr(5, 2));
@@ -1538,7 +1570,7 @@ Vue.component('pretix-widget-event-calendar', {
}
this.$root.date = String(curYear) + "-" + padNumber(curMonth, 2) + "-01";
this.$root.loading++;
this.$root.reload();
this.$root.reload({focus: '#'+this.id});
}
},
});
@@ -1573,7 +1605,7 @@ Vue.component('pretix-widget-event-week-calendar', {
+ '</div>'
// Actual calendar
+ '<div class="pretix-widget-event-week-table">'
+ '<div class="pretix-widget-event-week-table" :id="id" tabindex="0" v-bind:aria-label="weekname">'
+ '<div class="pretix-widget-event-week-col" v-for="d in $root.days">'
+ '<pretix-widget-event-week-cell :day="d">'
+ '</pretix-widget-event-week-cell>'
@@ -1591,6 +1623,9 @@ Vue.component('pretix-widget-event-week-calendar', {
var curYear = this.$root.week[0];
return curWeek + ' / ' + curYear;
},
id: function () {
return this.$root.html_id + "-event-week-table";
},
},
methods: {
back_to_list: function () {
@@ -1609,7 +1644,7 @@ Vue.component('pretix-widget-event-week-calendar', {
}
this.$root.week = [curYear, curWeek];
this.$root.loading++;
this.$root.reload();
this.$root.reload({focus: '#'+this.id});
},
nextweek: function () {
var curWeek = this.$root.week[1];
@@ -1621,13 +1656,13 @@ Vue.component('pretix-widget-event-week-calendar', {
}
this.$root.week = [curYear, curWeek];
this.$root.loading++;
this.$root.reload();
this.$root.reload({focus: '#'+this.id});
}
},
});
Vue.component('pretix-widget', {
template: ('<div class="pretix-widget-wrapper" ref="wrapper">'
template: ('<div class="pretix-widget-wrapper" ref="wrapper" tabindex="0" role="article" v-bind:aria-label="$root.name">'
+ '<div :class="classObject">'
+ shared_loading_fragment
+ '<div class="pretix-widget-error-message" v-if="$root.error && $root.view !== \'event\'">{{ $root.error }}</div>'
@@ -1737,7 +1772,7 @@ var shared_root_methods = {
}
});
},
reload: function () {
reload: function (opt = {}) {
var url;
if (this.$root.is_button) {
return;
@@ -1854,6 +1889,15 @@ var shared_root_methods = {
// If we're on desktop and someone selects a seating-only event in a calendar, let's open it right away,
// but only if the person didn't close it before.
root.startseating()
} else {
// make sure to only move focus to content element when it had focus before the reload/click
// this is needed because reload is also called on initial load and we do not want to move focus on initial load
if (root.$el.contains(document.activeElement)) {
root.$nextTick(function() {
// wait for redraw, then focus content element for better a11y
(opt.focus ? document.querySelector(opt.focus) : root.$el).focus();
});
}
}
}, function (error) {
root.categories = [];