[A11y] Fix sneak-peek for cart (#5076)

This commit is contained in:
Richard Schreiber
2025-05-09 08:38:34 +02:00
committed by GitHub
parent 2b735bec0b
commit 7472564c26
3 changed files with 65 additions and 40 deletions

View File

@@ -13,7 +13,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<aside aria-label="{% trans "Your cart" %}"> <aside aria-label="{% trans "Your cart" %}">
<details class="panel panel-default cart{% if "open_cart" not in request.GET %} sneak-peek{% endif %}" {% if "open_cart" in request.GET %}open{% endif %}> <details class="panel panel-default cart sneak-peek-container" open>
<summary class="panel-heading"> <summary class="panel-heading">
<h2 class="panel-title"> <h2 class="panel-title">
<span> <span>
@@ -33,11 +33,15 @@
</summary> </summary>
{% if "open_cart" not in request.GET %} {% if "open_cart" not in request.GET %}
<p class="sneak-peek-trigger"> <p class="sneak-peek-trigger">
<button type="button" class="btn btn-default">{% trans "Show full cart" %}</button> <button type="button" class="btn btn-default" aria-controls="cart-foldable-container">{% trans "Show full cart" %}</button>
</p> </p>
{% endif %} {% endif %}
<div> <div>
{% if "open_cart" not in request.GET %}
<div class="panel-body sneak-peek-content" id="cart-foldable-container">
{% else %}
<div class="panel-body"> <div class="panel-body">
{% endif %}
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event %} {% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event %}
</div> </div>
</div> </div>

View File

@@ -2,39 +2,56 @@
setup_collapsible_details = function (el) { setup_collapsible_details = function (el) {
el.find('details.sneak-peek:not([open])').each(function() { el.find('.sneak-peek-trigger').each(function() {
this.open = true; var trigger = this;
var $elements = $("> :not(summary)", this).show().filter(':not(.sneak-peek-trigger)'); var button = this.querySelector('button');
var container = this; var content = document.getElementById(button.getAttribute('aria-controls'));
if (content.scrollHeight < 200) {
if (Array.prototype.reduce.call($elements, function (h, e) { trigger.remove();
return h + $(e).outerHeight(); content.classList.remove('sneak-peek-content');
}, 0) < 200) {
$(".sneak-peek-trigger", this).remove();
$(container).removeClass('sneak-peek');
container.style.removeProperty('height');
return; return;
} }
content.setAttribute('aria-hidden', 'true');
button.setAttribute('aria-expanded', 'false');
button.addEventListener('click', function (e) {
button.setAttribute('aria-expanded', 'true');
content.setAttribute('aria-hidden', 'false');
$elements.attr('aria-hidden', 'true'); content.addEventListener('transitionend', function() {
content.classList.remove('sneak-peek-content');
var trigger = $('summary, .sneak-peek-trigger button', container); content.style.removeProperty('height');
function onclick(e) { // we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
e.preventDefault(); trigger.classList.add('sr-only');
container.addEventListener('transitionend', function() {
$(container).removeClass('sneak-peek');
container.style.removeProperty('height');
}, {once: true}); }, {once: true});
container.style.height = container.scrollHeight + 'px'; content.style.height = content.scrollHeight + 'px';
$('.sneak-peek-trigger', container).fadeOut(function() {
$(this).remove();
});
$elements.removeAttr('aria-hidden');
trigger.off('click', onclick); button.addEventListener('click', function (e) {
// this will be called by screenreader users if they kept focus on the button after expanding
// we need to keep the trigger/button in the DOM to not irritate screenreaders toggling visibility
var expanded = button.getAttribute('aria-expanded') == 'true';
button.setAttribute('aria-expanded', !expanded);
content.setAttribute('aria-hidden', expanded);
});
button.addEventListener('blur', function (e) {
// if content is visible and the user leaves the button, we can safely remove the trigger/button
if (button.getAttribute('aria-expanded') == 'true') {
trigger.remove();
}
});
}, { once: true });
var container = this.closest('details.sneak-peek-container');
if (container) {
function removeSneekPeakWhenClosed(e) {
if (e.newState == "closed") {
container.removeEventListener("toggle", removeSneekPeakWhenClosed);
trigger.remove();
content.removeAttribute('aria-hidden');
content.classList.remove('sneak-peek-content');
}
}
container.addEventListener("toggle", removeSneekPeakWhenClosed);
} }
trigger.on('click', onclick);
}); });
var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
@@ -43,10 +60,6 @@ setup_collapsible_details = function (el) {
return true; return true;
} }
var $details = $(this).closest("details"); var $details = $(this).closest("details");
if ($details.hasClass('sneak-peek')) {
// if sneak-peek is active, needs to be handled differently
return true;
}
var isOpen = $details.prop("open"); var isOpen = $details.prop("open");
var $detailsNotSummary = $details.children(':not(summary)'); var $detailsNotSummary = $details.children(':not(summary)');
if ($detailsNotSummary.is(':animated')) { if ($detailsNotSummary.is(':animated')) {
@@ -70,11 +83,6 @@ setup_collapsible_details = function (el) {
if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) { if (32 == event.keyCode || (13 == event.keyCode && !isOpera)) {
// Space or Enter is pressed — trigger the `click` event on the `summary` element // Space or Enter is pressed — trigger the `click` event on the `summary` element
// Opera already seems to trigger the `click` event when Enter is pressed // Opera already seems to trigger the `click` event when Enter is pressed
var $details = $(this).closest("details");
if ($details.hasClass('sneak-peek')) {
// if sneak-peek is active, needs to be handled differently
return true;
}
event.preventDefault(); event.preventDefault();
$(this).click(); $(this).click();
} }

View File

@@ -521,12 +521,17 @@ details summary {
transform: rotate(-90deg); transform: rotate(-90deg);
} }
details.sneak-peek { .sneak-peek-container {
position: relative; position: relative;
height: 11em; }
.sneak-peek-content {
height: 8em;
overflow: hidden; overflow: hidden;
transition: height .5s; transition: height .5s;
} }
.nojs .sneak-peek-content {
height: auto;
}
.sneak-peek-trigger { .sneak-peek-trigger {
display: grid; display: grid;
justify-content: center; justify-content: center;
@@ -540,6 +545,14 @@ details.sneak-peek {
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 10; z-index: 10;
opacity: 1;
transition: opacity .5s;
}
.nojs .sneak-peek-trigger {
display: none;
}
.sneak-peek-trigger:has(button[aria-expanded="true"]) {
opacity: 0;
} }
form.download-btn-form { form.download-btn-form {