summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlmari Lauhakangas <ilmari.lauhakangas@libreoffice.org>2023-10-25 18:20:13 +0300
committerOlivier Hallot <olivier.hallot@libreoffice.org>2023-10-25 17:53:36 +0200
commit69f85cbf17c5acb8fb9b38772139c34eea96a772 (patch)
treed4e18ca537c65871497e4b5154e0152c45c052ee
parent3c0331071035ec78359ec8a4e5919164a31ec879 (diff)
Improve accessibility of dropdown menus
Now the opened menus can be closed with Esc key, so we conform to the dismissible success criterion in WCAG 2.1: https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus.html To simplify things, now the navigation lists are populated upon page load in all cases. Change-Id: I0a7daaea122d3e03de36c322ccb012c546b271e0 Reviewed-on: https://gerrit.libreoffice.org/c/help/+/158429 Tested-by: Jenkins Reviewed-by: Olivier Hallot <olivier.hallot@libreoffice.org>
-rw-r--r--help3xsl/a11y-toggle.js146
-rw-r--r--help3xsl/default.css6
-rw-r--r--help3xsl/help.js1
-rw-r--r--help3xsl/help2.js27
-rw-r--r--help3xsl/online_transform.xsl8
-rw-r--r--help3xsl/xap_templ_query.xsl4
6 files changed, 75 insertions, 117 deletions
diff --git a/help3xsl/a11y-toggle.js b/help3xsl/a11y-toggle.js
index 821a8e0272..62e1032b02 100644
--- a/help3xsl/a11y-toggle.js
+++ b/help3xsl/a11y-toggle.js
@@ -1,100 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
-MIT License
-
-Copyright (c) 2016 Edenspiekermann
-
-*/
-
-(function () {
- 'use strict';
-
- var internalId = 0;
- var togglesMap = {};
- var targetsMap = {};
-
- function $ (selector, context) {
- return Array.prototype.slice.call(
- (context || document).querySelectorAll(selector)
- );
- }
-
- function getClosestToggle (element) {
- if (element.closest) {
- return element.closest('[data-a11y-toggle]');
- }
-
- while (element) {
- if (element.nodeType === 1 && element.hasAttribute('data-a11y-toggle')) {
- return element;
- }
-
- element = element.parentNode;
- }
-
- return null;
- }
-
- function handleToggle (toggle) {
- var target = toggle && targetsMap[toggle.getAttribute('aria-controls')];
-
- if (!target) {
- return false;
- }
-
- var toggles = togglesMap['#' + target.id];
- var isExpanded = target.getAttribute('aria-hidden') === 'false';
-
- target.setAttribute('aria-hidden', isExpanded);
- toggles.forEach(function (toggle) {
- toggle.setAttribute('aria-expanded', !isExpanded);
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function hideNavs() {
+ let navs = document.querySelectorAll('[data-a11y-toggle] + nav');
+ navs.forEach((nav) => {
+ if (!nav.hasAttribute('hidden')) {
+ nav.previousElementSibling.setAttribute('aria-expanded', 'false');
+ nav.setAttribute('hidden', 'true');
+ }
});
- }
-
- var initA11yToggle = function (context) {
- togglesMap = $('[data-a11y-toggle]', context).reduce(function (acc, toggle) {
- var selector = '#' + toggle.getAttribute('data-a11y-toggle');
- acc[selector] = acc[selector] || [];
- acc[selector].push(toggle);
- return acc;
- }, togglesMap);
-
- var targets = Object.keys(togglesMap);
- targets.length && $(targets).forEach(function (target) {
- var toggles = togglesMap['#' + target.id];
- var isExpanded = target.hasAttribute('data-a11y-toggle-open');
- var labelledby = [];
-
- toggles.forEach(function (toggle) {
- toggle.id || toggle.setAttribute('id', 'a11y-toggle-' + internalId++);
- toggle.setAttribute('aria-controls', target.id);
- toggle.setAttribute('aria-expanded', isExpanded);
- labelledby.push(toggle.id);
- });
-
- target.setAttribute('aria-hidden', !isExpanded);
- target.hasAttribute('aria-labelledby') || target.setAttribute('aria-labelledby', labelledby.join(' '));
-
- targetsMap[target.id] = target;
- });
- };
-
- document.addEventListener('DOMContentLoaded', function () {
- initA11yToggle();
- });
-
- document.addEventListener('click', function (event) {
- var toggle = getClosestToggle(event.target);
- handleToggle(toggle);
- });
-
- document.addEventListener('keyup', function (event) {
- if (event.which === 13 || event.which === 32) {
- var toggle = getClosestToggle(event.target);
- if (toggle && toggle.getAttribute('role') === 'button') {
- handleToggle(toggle);
- }
+}
+const navToggle = document.querySelectorAll('[data-a11y-toggle]');
+navToggle.forEach((toggle) => {
+ let navList = toggle.nextElementSibling;
+ let navLinks = navList.querySelectorAll('a');
+ toggle.addEventListener('click', (event) => {
+ if (navList.hasAttribute('hidden')) {
+ toggle.setAttribute('aria-expanded', 'true');
+ navList.removeAttribute('hidden');
+ // Set focus on first link
+ // will be highlighted for keyboard users
+ navLinks[0].focus();
+ } else {
+ navList.setAttribute('hidden', 'true');
+ toggle.setAttribute('aria-expanded', 'false');
+ }
+ event.stopPropagation();
+ }, false);
+});
+document.addEventListener('keydown', (event) => {
+ // Ignore IME composition
+ if (event.isComposing || event.keyCode === 229) {
+ return;
}
- });
- window && (window.a11yToggle = initA11yToggle);
-})();
+ // Close menu with ESC key
+ if (event.keyCode === 27) {
+ hideNavs();
+ }
+}, false);
+document.addEventListener('click', (event) => {
+ // close navigation menus when clicking anywhere (except when on mobile)
+ if (event.target.closest('[data-a11y-toggle] + nav') || Math.max(document.documentElement.clientWidth, window.innerWidth || 0) < 960) return
+ hideNavs();
+})
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/help3xsl/default.css b/help3xsl/default.css
index 539a7da2fb..62f62d313c 100644
--- a/help3xsl/default.css
+++ b/help3xsl/default.css
@@ -451,7 +451,7 @@ h6 {
transition-duration: .35s;
}
-#langs-nav:not([aria-hidden='true']), #modules-nav:not([aria-hidden='true']) {
+#langs-nav, #modules-nav {
z-index: 100;
/* line them up horizontally */
display: flex;
@@ -528,7 +528,7 @@ aside input[type=checkbox]:checked ~ .contents-treeview {
font-size: 15px;
display: block;
}
-.index .hidden {
+.index .hidden, #langs-nav[hidden], #modules-nav[hidden] {
display: none;
}
#Bookmarks {
@@ -849,7 +849,7 @@ li.disabled a {
}
/* change the menu direction to stacked */
- #langs-nav:not([aria-hidden='true']), #modules-nav:not([aria-hidden='true']) {
+ #langs-nav, #modules-nav {
display: flex;
flex-direction: column;
overflow-y: auto;
diff --git a/help3xsl/help.js b/help3xsl/help.js
index 3e9c0fe110..8e0dc54206 100644
--- a/help3xsl/help.js
+++ b/help3xsl/help.js
@@ -258,4 +258,5 @@ function youtubeLoader(ytId, width, height) {
placeholder.innerHTML = iframeMarkup;
placeholder.removeAttribute("style");
}
+
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/help3xsl/help2.js b/help3xsl/help2.js
index df11cb5210..684136245d 100644
--- a/help3xsl/help2.js
+++ b/help3xsl/help2.js
@@ -175,7 +175,7 @@ function existingLang(lang) {
function setupModules(lang) {
var modulesNav = document.getElementById('modules-nav');
if (!modulesNav.classList.contains('loaded')) {
- var html =
+ let html =
'<a href="' + lang + '/text/shared/05/new_help.html?DbPAR=SHARED"><div class="office-icon"></div>%PRODUCTNAME</a>' +
'<a href="' + lang + '/text/swriter/main0000.html?DbPAR=WRITER"><div class="writer-icon"></div>Writer</a>' +
'<a href="' + lang + '/text/scalc/main0000.html?DbPAR=CALC"><div class="calc-icon"></div>Calc</a>' +
@@ -190,10 +190,12 @@ function setupModules(lang) {
}
}
-function setupLanguages(page) {
- var langNav = document.getElementById('langs-nav');
+function setupLanguages(url) {
+ let langNav = document.getElementById('langs-nav');
+ if (!langNav) return;
+ let page = url.substring(url.search('/text/'));
if (!langNav.classList.contains('loaded')) {
- var html = '';
+ let html = '';
languagesSet.forEach(function(lang) {
html += '<a href="' + lang + page + '">' + ((lang in languageNames)? languageNames[lang]: lang) + '</a>';
});
@@ -240,18 +242,19 @@ if(missingElement != null){missingElement.innerHTML = helpID;}
debugInfo(getParameterByName("Debug"));
-// Mobile devices need the modules and langs on page load
+// Mobile devices need the modules and langs displayed on page load
if (Math.max(document.documentElement.clientWidth, window.innerWidth || 0) < 960) {
- let e = new Event('click');
- let modulesBtn = document.getElementById('modules');
- let langsBtn = document.getElementById('langs');
let modules = document.getElementById('modules-nav');
let langs = document.getElementById('langs-nav');
- modules.setAttribute('data-a11y-toggle-open', '');
- modulesBtn.dispatchEvent(e);
+ modules.removeAttribute('hidden');
if (langs) {
- langs.setAttribute('data-a11y-toggle-open', '');
- langsBtn.dispatchEvent(e);
+ langs.removeAttribute('hidden');
}
}
+
+const href = window.location.href;
+const lang = existingLang(getParameterByName("Language", href) || navigator.language);
+setupModules(lang);
+setupLanguages(href);
+
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/help3xsl/online_transform.xsl b/help3xsl/online_transform.xsl
index aaa56175d8..51961c68b6 100644
--- a/help3xsl/online_transform.xsl
+++ b/help3xsl/online_transform.xsl
@@ -185,17 +185,17 @@
</a>
<div class="dropdowns">
<div class="modules">
- <button type="button" data-a11y-toggle="modules-nav" id="modules" onclick="setupModules('{$lang}');">
+ <button type="button" data-a11y-toggle="modules-nav" id="modules" aria-haspopup="true" aria-expanded="false" aria-controls="modules-nav">
<xsl:value-of select="$ui_module"/>
</button>
- <nav id="modules-nav"/><!-- is filled in via setupModules() on demand -->
+ <nav id="modules-nav" hidden=""/><!-- is filled in via setupModules() -->
</div>
<xsl:if test="$online">
<div class="lang">
- <button type="button" data-a11y-toggle="langs-nav" id="langs" onclick="setupLanguages('{$htmlpage}');">
+ <button type="button" data-a11y-toggle="langs-nav" id="langs" aria-haspopup="true" aria-expanded="false" aria-controls="modules-nav">
<xsl:value-of select="$ui_language"/>
</button>
- <nav id="langs-nav"/><!-- is filled in via setupLanguages() on demand -->
+ <nav id="langs-nav" hidden=""/><!-- is filled in via setupLanguages() -->
</div>
</xsl:if>
</div>
diff --git a/help3xsl/xap_templ_query.xsl b/help3xsl/xap_templ_query.xsl
index 51f2fea3cf..3aedf2e1b3 100644
--- a/help3xsl/xap_templ_query.xsl
+++ b/help3xsl/xap_templ_query.xsl
@@ -118,9 +118,9 @@ document.write("<span title=\""+D+" "+T+"\">]]><xsl:apply-templates select="//va
</header>
</div>
<div class="modules">
- <button type="button" data-a11y-toggle="modules-nav" id="modules" onclick="setupModules(']]><xsl:value-of select="$lang"/><![CDATA[');">]]><xsl:value-of select="$ui_module"/><![CDATA[
+ <button type="button" data-a11y-toggle="modules-nav" id="modules" onclick="setupModules(']]><xsl:value-of select="$lang"/><![CDATA[');" aria-haspopup="true" aria-expanded="false" aria-controls="modules-nav">]]><xsl:value-of select="$ui_module"/><![CDATA[
</button>
- <nav id="modules-nav"/><!-- is filled in via setupModules() on demand -->
+ <nav id="modules-nav" hidden=""/><!-- is filled in via setupModules() on demand -->
</div>
<aside class="rightside">
<input id="accordion-1" name="accordion-menu" type="checkbox"/>