From f02b1be659ee3b4c1130ac09d8c13162b0c67328 Mon Sep 17 00:00:00 2001 From: Richard Schreiber Date: Wed, 18 Dec 2024 15:24:28 +0100 Subject: [PATCH] =?UTF-8?q?[A11y]=20Improve=20grouping=20and=20labels=20in?= =?UTF-8?q?=20widget=E2=80=99s=20product=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../static/pretixpresale/js/widget/widget.js | 85 +++++++++++++++---- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index 46ea57ae2..9693652d9 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -17,6 +17,8 @@ var strings = { 'quantity_dec': django.pgettext('widget', 'Decrease quantity'), 'quantity_inc': django.pgettext('widget', 'Increase quantity'), 'price': django.pgettext('widget', 'Price'), + 'original_price': django.pgettext('widget', 'Original price: %s'), + 'new_price': django.pgettext('widget', 'New price: %s'), 'select': django.pgettext('widget', 'Select'), 'select_item': django.pgettext('widget', 'Select %s'), 'select_variant': django.pgettext('widget', 'Select variant %s'), @@ -26,6 +28,7 @@ var strings = { 'reserved': django.pgettext('widget', 'Reserved'), 'free': django.pgettext('widget', 'FREE'), 'price_from': django.pgettext('widget', 'from %(currency)s %(price)s'), + 'image_of': django.pgettext('widget', 'Image of %s'), 'tax_incl': django.pgettext('widget', 'incl. %(rate)s% %(taxname)s'), 'tax_plus': django.pgettext('widget', 'plus %(rate)s% %(taxname)s'), 'tax_incl_mixed': django.pgettext('widget', 'incl. taxes'), @@ -335,13 +338,13 @@ Vue.component('pricebox', { template: ('
' + '' + '' - + '{{ original_line }} ' - + '' + + ' ' + + '' + '
' + '{{ $root.currency }} ' + '' + + ' step="any" v-bind:aria-label="free_price_label">' + '
' + '' + '{{ taxline }}' @@ -355,6 +358,13 @@ Vue.component('pricebox', { original_price: String, mandatory_priced_addons: Boolean, }, + methods: { + stripHTML: function (s) { + var div = document.createElement('div'); + div.innerHTML = s; + return div.textContent || div.innerText || ''; + }, + }, computed: { display_price: function () { if (this.$root.display_net_prices) { @@ -381,8 +391,14 @@ Vue.component('pricebox', { return parseFloat(price.gross).toFixed(2); } }, + original_price_aria_label: function () { + return django.interpolate(strings.original_price, [this.stripHTML(this.original_line)]); + }, + new_price_aria_label: function () { + return django.interpolate(strings.new_price, [this.stripHTML(this.priceline)]); + }, original_line: function () { - return this.$root.currency + " " + floatformat(parseFloat(this.original_price), 2); + return '' + this.$root.currency + " " + floatformat(parseFloat(this.original_price), 2); }, priceline: function () { if (this.price.gross === "0.00") { @@ -394,6 +410,9 @@ Vue.component('pricebox', { return '' + this.$root.currency + " " + this.display_price; } }, + free_price_label () { + return [strings.price, this.$root.currency].join(", ") + }, taxline: function () { if (this.$root.display_net_prices) { if (this.price.includes_mixed_tax_rate) { @@ -418,14 +437,14 @@ Vue.component('pricebox', { } }); Vue.component('variation', { - template: ('
' + template: ('
' + '
' // Variation description + '
' + '
' - + '{{ variation.value }}' - + '
' + + '{{ variation.value }}' + + '
' + '

' + '{{ quota_left_str }}' @@ -434,7 +453,7 @@ Vue.component('variation', { + '

' // Price - + '
' + + '
' + '' @@ -464,23 +483,35 @@ Vue.component('variation', { quota_left_str: function () { return django.interpolate(strings["quota_left"], [this.variation.avail[1]]); }, + variation_label_id: function () { + return this.$root.html_id + '-variation-label-' + this.item.id + '-' + this.variation.id; + }, + variation_desc_id: function () { + return this.$root.html_id + '-variation-desc-' + this.item.id + '-' + this.variation.id; + }, + variation_price_id: function () { + return this.$root.html_id + '-variation-price-' + this.item.id + '-' + this.variation.id; + }, + aria_labelledby: function () { + return [this.variation_label_id, this.variation_price_id].join(" "); + }, } }); Vue.component('item', { - template: ('
' + template: ('
' + '
' // Product description + '
' - + '' + + '' + '
' - + '' + + '' + '{{ item.name }}' + '' - + '{{ item.name }}' - + '
' + + '{{ item.name }}' + + '
' + '

' + '{{ min_order_str }}' + '

' @@ -492,7 +523,7 @@ Vue.component('item', { + '
' // Price - + '
' + + '
' + '' @@ -574,6 +605,21 @@ Vue.component('item', { 'pretix-widget-item-variations-expanded': this.expanded, } }, + picture_alt_text: function () { + return django.interpolate(strings["image_of"], [this.item.name]); + }, + item_label_id: function () { + return this.$root.html_id + '-item-label-' + this.item.id; + }, + item_desc_id: function () { + return this.$root.html_id + '-item-desc-' + this.item.id; + }, + item_price_id: function () { + return this.$root.html_id + '-item-price-' + this.item.id; + }, + aria_labelledby: function () { + return [this.item_label_id, this.item_price_id].join(" "); + }, min_order_str: function () { return django.interpolate(strings["order_min"], [this.item.order_min]); }, @@ -978,10 +1024,10 @@ Vue.component('pretix-widget-event-form', { // Resume cart + '
' - + '' - + strings['cart_exists'] + + '' + strings['cart_exists'] + '' + '
' + '
' @@ -1051,6 +1097,9 @@ Vue.component('pretix-widget-event-form', { this.$root.$off('focus_voucher_field', this.focus_voucher_field) }, computed: { + id_cart_exists_msg: function () { + return this.$root.html_id + '-cart-exists'; + }, buy_label: function () { var i, j, k, all_free = true; for (i = 0; i < this.$root.categories.length; i++) {