Dropdown accessibility: handle submenus and other fixes

This commit is contained in:
yflory 2024-03-07 18:39:10 +01:00
parent c6fce9f662
commit 67cdadca1f
11 changed files with 155 additions and 176 deletions

View file

@ -1038,6 +1038,13 @@
align-items: center;
}
}
.cp-dropdown-container {
button {
.cp-icon {
margin-left: 5px;
}
}
}
}
.cp-app-drive-button {

View file

@ -48,6 +48,14 @@
position: relative;
display: inline-block;
&.cp-dropdown-recursive {
position: unset;
.cp-dropdown-content {
top: 0;
left: 100%;
}
}
.fa {
font-family: FontAwesome;
}
@ -76,6 +84,10 @@
font: @dropdown_font;
line-height: 1em;
&.cp-dropdown-has-submenu {
overflow: visible;
}
&.cp-dropdown-left {
right: 0;
margin-right: 5px;

View file

@ -523,11 +523,6 @@
&::before {
padding-top: 1px;
}
// .cp-dropdown-button-title {
// transform: scale(0.5);
// bottom: -5px;
// right: -5px;
// }
}
}
.cp-toolbar-user-dropdown {
@ -1053,7 +1048,7 @@
&.cp-toolbar-small {
button {
.cp-toolbar-name, .cp-button-name {
.cp-toolbar-name, .cp-button-name, .cp-dropdown-button-title {
display: none;
}
i, span {
@ -1127,27 +1122,29 @@
order: 8;
}
.cp-toolbar-file {
button {
&.fa-plus { order: 0; }
&.fa-history { order: 5; }
&.fa-hashtag { order: 10; }
&.fa-bookmark { order: 15; }
&.fa-upload { order: 20; }
&.fa-files-o { order: 25; }
&.fa-download { order: 30; }
&.fa-print { order: 35; }
&.fa-trash { order: 40; }
&.cp-toolbar-icon-pad-settings { order: 75; }
&.fa-info-circle { order: 100; }
li, button {
.fa-plus { order: 0; }
.fa-history { order: 5; }
.fa-hashtag { order: 10; }
.fa-bookmark { order: 15; }
.fa-upload { order: 20; }
.fa-files-o { order: 25; }
.fa-download { order: 30; }
.fa-print { order: 35; }
.fa-trash { order: 40; }
.cp-toolbar-icon-pad-settings { order: 75; }
.fa-info-circle { order: 100; }
&.cp-toolbar-icon-help { order: 150; }
.fa-question { order: 150; }
span { order: 1000; } // text always after icon
}
}
.cp-toolbar-drawer-content:empty ~ .cp-toolbar-drawer-button {
.cp-toolbar-drawer-contentsss:empty ~ .cp-toolbar-drawer-button {
display: none;
}
.cp-toolbar-drawer-content {
.cp-toolbar-drawer-contentsss {
box-shadow: 0px 1px 5px 0px @cp_shadow-color;
border-radius: @variables_radius;
overflow-y: auto;

View file

@ -89,11 +89,11 @@ define([
text: Messages.toolbar_theme,
options: [],
common: Common,
buttonCls: 'cptools cptools-palette'
iconCls: 'cptools cptools-palette'
});
framework._.toolbar.$theme = $drawer.find('ul.cp-dropdown-content');
framework._.toolbar.$bottomL.append($drawer);
$drawer.find('span').addClass('cp-button-name');
$drawer.addClass('cp-toolbar-appmenu');
};
var mkCbaButton = function (framework, markers) {
@ -105,15 +105,12 @@ define([
var $showAuthorColors = UIElements.createDropdownEntry({
tag: 'a',
attributes: {'class': $showAuthorColorsButton.attr('class')},
content: [
h('i', { 'class': $showAuthorColorsButton.children('i').attr('class') }),
h('span', $showAuthorColorsButton.text())
],
attributes: {'class': 'fa fa-paint-brush ' + $showAuthorColorsButton.attr('class')},
content: h('span', $showAuthorColorsButton.text()),
action: function () {
$showAuthorColorsButton.click();
},
});
}).hide();
$showAuthorColors.find('span').addClass('cp-toolbar-name cp-toolbar-drawer-element');
framework._.toolbar.$theme.append($showAuthorColors);
markers.setButton($showAuthorColors);
@ -425,16 +422,13 @@ define([
});
var $cba = UIElements.createDropdownEntry({
tag: 'a',
attributes: {'class': $cbaButton.attr('class')},
content: [
h('i', { 'class': $cbaButton.children('i').attr('class') }),
h('span', $cbaButton.text())
],
attributes: {'class': 'fa fa-paint-brush ' + $cbaButton.attr('class')},
content: h('span', $cbaButton.text()),
action: function () {
$cbaButton.click();
},
});
framework._.toolbar.$theme.append($cba);
framework._.toolbar.$theme.prepend($cba); // Put at the top
};
var mkFilePicker = function (framework, editor, evModeChange) {

View file

@ -624,11 +624,11 @@ define([
if (Env.opacity) {
Env.opacity = 0;
$button.find('.cp-toolbar-drawer-element').text(Messages.cba_show);
$button.removeClass("cp-toolbar-button-active");
//$button.removeClass("cp-toolbar-button-active");
} else {
Env.opacity = MARK_OPACITY;
$button.find('.cp-toolbar-drawer-element').text(Messages.cba_hide);
$button.addClass("cp-toolbar-button-active");
//$button.addClass("cp-toolbar-button-active");
}
};
toggle();

View file

@ -859,10 +859,9 @@ define([
])).click(common.prepareFeedback(type));
break;
case 'storeindrive':
button = $(h('button.cp-toolbar-storeindrive', {
button = $(h('button.cp-toolbar-storeindrive.fa.fa-hdd-o', {
style: 'display:none;'
}, [
h('i.fa.fa-hdd-o'),
h('span.cp-toolbar-name.cp-toolbar-drawer-element', Messages.toolbar_storeInDrive)
])).click(common.prepareFeedback(type)).click(function () {
$(button).hide();
@ -1454,7 +1453,12 @@ define([
};
UIElements.createDropdownEntry = function (config) {
var hide = function () {};
var allowedTags = ['a', 'li', 'p', 'hr', 'div'];
var isElement = function (o) {
return /HTML/.test(Object.prototype.toString.call(o)) &&
typeof(o.tagName) === 'string';
};
var isValidOption = function (o) {
if (typeof o !== "object") { return false; }
if (isElement(o)) { return true; }
@ -1462,15 +1466,8 @@ define([
return true;
};
var isElement = function (o) {
return /HTML/.test(Object.prototype.toString.call(o)) &&
typeof(o.tagName) === 'string';
};
var entry;
if (!isValidOption(config)) {
return null;
}
if (!isValidOption(config)) { return; }
if (isElement(config)) {
entry = $(config);
@ -1512,18 +1509,14 @@ define([
entry.append($el);
// Action can be triggered with a click or keyboard event
if (config.tag !== 'a' && config.tag !== 'li') {
return null;
}
if (config.tag === 'a' || config.tag === 'li') {
entry.on('mouseenter', (e) => {
e.stopPropagation();
entry.focus();
});
entry.on('mouseenter', (e) => {
e.stopPropagation();
entry.focus();
});
entry.on('click keydown', function(e) {
if (config.isSelect) { return; }
if (e.type === 'click' || (e.type === 'keydown' && e.keyCode === 13) || (e.type === 'keydown' && e.keyCode === 32)) {
Util.onClickEnter(entry, function(e) {
if (config.isSelect) { return; }
e.stopPropagation();
if (typeof(config.action) === "function") {
var close = config.action(e);
@ -1532,10 +1525,17 @@ define([
// Click on <a> with an href
if (e.type === 'keydown'){ $el.get(0).click(); }
}
}
});
}, {space: true});
}
}
hide = function () {
window.setTimeout(function () {
entry.closest('ul.cp-dropdown-content').hide();
entry.parents('.cp-dropdown-content').removeClass('cp-dropdown-has-submenu').hide();
}, 0);
};
return entry;
};
@ -1576,37 +1576,23 @@ define([
}
// Button
var $button;
if (config.buttonContent) {
$button = $(h('button', {
class: config.buttonCls || '',
'aria-haspopup': 'menu',
'aria-expanded': 'false',
'title': config.buttonTitle || '',
'aria-label': config.buttonTitle || '',
}, [
h('span.cp-dropdown-button-title', config.buttonContent),
]));
} else {
$button = $('<button>', {
'class': config.buttonCls || '',
'aria-haspopup': 'menu',
'aria-expanded': 'false',
'title': config.buttonTitle || '',
'aria-label': config.buttonTitle || '',
}).append($('<span>', {'class': 'cp-dropdown-button-title'}).text(config.text || ""));
}
let icon = config.iconCls ? h('i', {class:config.iconCls}) : undefined;
var $button = $(h('button', {
class: config.buttonCls || '',
'aria-haspopup': 'menu',
'aria-expanded': 'false',
'title': config.buttonTitle || '',
'aria-label': config.buttonTitle || '',
}, config.buttonContent || [
icon,
h('span.cp-dropdown-button-title', config.text),
]));
if (config.caretDown) {
$('<span>', {
'class': 'fa fa-caret-down',
}).prependTo($button);
$button.append(h('i.fa.fa-caret-down'));
}
if (config.angleDown) {
$('<span>', {
'class': 'fa fa-angle-down',
}).prependTo($button);
$button.append(h('i.fa.fa-angle-down'));
}
// Menu
@ -1614,7 +1600,11 @@ define([
if (config.left) { $innerblock.addClass('cp-dropdown-left'); }
var hide = function () {
window.setTimeout(function () { $innerblock.hide(); }, 0);
window.setTimeout(function () {
$innerblock.hide();
$innerblock.parents('.cp-dropdown-content').removeClass('cp-dropdown-has-submenu')
.hide();
}, 0);
};
// When the menu is collapsed, update aria-expanded
@ -1629,72 +1619,20 @@ define([
observer.observe($innerblock[0], { attributes: true });
// Add the dropdown content
var setOptions = function (options) {
var setOptions = $container.setOptions = function (options) {
$innerblock.empty();
options.forEach(function (o) {
if (!isValidOption(o)) { return; }
if (isElement(o)) { return void $innerblock.append(o); }
var $el = $(h(o.tag, (o.attributes || {})));
if (typeof(o.content) === 'string' || (o.content instanceof Element)) {
o.content = [o.content];
}
if (Array.isArray(o.content)) {
o.content.forEach(function (item) {
if (item instanceof Element) {
return void $el.append(item);
}
if (typeof(item) === 'string') {
$el[0].appendChild(document.createTextNode(item));
}
});
}
// Everything is added as an "li" tag
// Links and items with action are focusable
// Add correct "role" attribute
var $li = $(h('li'));
if (o.tag === 'a') {
$el.attr('tabindex', '-1');
$li.attr('role', 'menuitem');
$li.attr('tabindex', '0');
} else if (o.tag === 'li') {
$li = $el;
$li.attr('role', 'menuitem');
$li.attr('tabindex', '0');
} else if (o.tag === 'hr') {
$li.attr('role', 'separator');
} else {
$li.attr('role', 'none');
}
$li.append($el);
$li.appendTo($innerblock);
// Action can be triggered with a click or keyboard event
if (o.tag !== 'a' && o.tag !== 'li') { return; }
$li.on('mouseenter', (e) => {
e.stopPropagation();
$li.focus();
});
var onAction = function (e) {
if (config.isSelect) { return; }
if (e.type === 'click' || (e.type === 'keydown' && e.keyCode === 13) || (e.type === 'keydown' && e.keyCode === 32)) {
e.stopPropagation();
if (typeof(o.action) === "function") {
var close = o.action(e);
if (close) { hide(); }
} else {
// Click on <a> with an href
if (e.type === 'keydown'){ $el.get(0).click(); }
}
}
};
$li.on('click keydown', onAction);
o.isSelect = config.isSelect;
let $entry = UIElements.createDropdownEntry(o);
if (!$entry) { return void console.error('Error adding dropdown entry', o); }
$entry.appendTo($innerblock);
});
};
setOptions(config.options);
$container.setOptions = function (options) {
$innerblock.empty();
setOptions(options);
$container.addOption = function (config) {
let $entry = UIElements.createDropdownEntry(config);
$entry.appendTo($innerblock);
};
$container.append($button).append($innerblock);
@ -1729,10 +1667,16 @@ define([
if ((topPos + h) > wh) {
$innerblock.css('bottom', button.height+'px');
}
} else if ($innerblock.parents('.cp-dropdown-content').length) {
let $p = $innerblock.parents('.cp-dropdown-content');
let max = $p.css('max-height');
$innerblock.css('max-height', max);
} else {
$innerblock.css('max-height', Math.floor(wh - topPos - 1)+'px');
}
$innerblock.show();
$innerblock.parents('.cp-dropdown-content').addClass('cp-dropdown-has-submenu')
.show(); // keep parent open when recursive
$innerblock.find('.cp-dropdown-element-active').removeClass('cp-dropdown-element-active');
setTimeout(() => {
if (config.isSelect && value) {
@ -1755,11 +1699,11 @@ define([
var state = $innerblock.is(':visible');
$('.cp-dropdown-content').hide();
var $c = $container.closest('.cp-toolbar-drawer-content');
/*var $c = $container.closest('.cp-toolbar-drawer-content');
$c.removeClass('cp-dropdown-visible');
if (!state) {
$c.addClass('cp-dropdown-visible');
}
}*/
try {
$('iframe').each(function (idx, ifrw) {
@ -1924,6 +1868,8 @@ define([
}, 1000);
});
$container.close = hide;
return $container;
};
@ -2309,7 +2255,7 @@ define([
};
});
var dropdownConfigUser = {
buttonContent: $userButton[0],
text: $userButton[0],
options: options, // Entries displayed in the menu
left: true, // Open to the left of the button
container: config.$initBlock, // optional

View file

@ -117,7 +117,7 @@
// "enter" on a button triggers a click, disable it
if (e.type === 'keydown') { e.preventDefault(); }
handler();
handler(e);
});
};

View file

@ -1052,7 +1052,15 @@ define([
toolbar.$drawer.append($copy);
var $store = common.createButton('storeindrive', true);
toolbar.$drawer.append($store);
var $storeEntry = UIElements.createDropdownEntry({
tag: 'a',
attributes: { 'class': $store.attr('class') },
content: h('span', $store.text()),
action: function () {
$store.click();
}
});
toolbar.$drawer.append($storeEntry);
if (!cpNfInner.metadataMgr.getPrivateData().isTemplate) {
var templateObj = {

View file

@ -297,6 +297,7 @@ define([
var name = exp.$language.find('a[data-value="' + mode + '"]').text() || undefined;
name = name ? Messages.languageButton + ' ('+name+')' : Messages.languageButton;
exp.$language.setValue(mode, name);
exp.$language.find('span.cp-language-text').text(name);
}
if (mode === "orgmode") {
@ -341,6 +342,7 @@ define([
var name = theme || undefined;
name = name ? Messages.themeButton + ' ('+theme+')' : Messages.themeButton;
$select.setValue(theme, name);
$select.find('span.cp-theme-text').text(name);
}
};
}());
@ -369,7 +371,9 @@ define([
common: Common
};
var $block = exp.$language = UIElements.createDropdown(dropdownConfig);
$block.find('button').attr('title', Messages.languageButtonTitle);
$block.find('button').attr('title', Messages.languageButtonTitle).hide();
$block.prepend(h('span.cp-language-text', Messages.languageButton));
$block.addClass('cp-dropdown-recursive');
var isHovering = false;
var $aLanguages = $block.find('li');
@ -382,20 +386,21 @@ define([
setMode($block.find(".cp-dropdown-element-active").attr('data-value'));
}
});
$aLanguages.click(function () {
//$aLanguages.click(function () {
$block.onChange.reg(() => {
isHovering = false;
var mode = $(this).find('a').attr('data-value');
var mode = $block.getValue();
setMode(mode, onModeChanged);
$block.close();
onLocal();
});
if ($drawer) {
var $blockButton = UIElements.createDropdownEntry({
tag: 'a',
attributes: {'class': $block.find('button').attr('class')},
content: h('span', $block.find('button').text()),
content: $block[0],
action: function () {
$block.click();
$block.find('button').click();
},
});
$drawer.append($blockButton);
@ -432,14 +437,17 @@ define([
common: Common
};
var $block = exp.$theme = UIElements.createDropdown(dropdownConfig);
$block.find('button').attr('title', Messages.themeButtonTitle).click(function () {
/*$block.find('button').attr('title', Messages.themeButtonTitle).click(function () {
var state = $block.find('.cp-dropdown-content').is(':visible');
var $c = $block.closest('.cp-toolbar-drawer-content');
$c.removeClass('cp-dropdown-visible');
if (!state) {
$c.addClass('cp-dropdown-visible');
}
});
});*/
$block.find('button').hide();
$block.prepend(h('span.cp-theme-text', Messages.languageButton));
$block.addClass('cp-dropdown-recursive');
setTheme(lastTheme, $block);
@ -456,9 +464,9 @@ define([
Common.setAttribute(themeKey, lastTheme);
}
});
$aThemes.click(function () {
$block.onChange.reg(() => {
isHovering = false;
var theme = $(this).find('a').attr('data-value');
var theme = $block.getValue();
setTheme(theme, $block);
Common.setAttribute(themeKey, theme);
});
@ -466,10 +474,9 @@ define([
if ($drawer) {
const $blockButton = UIElements.createDropdownEntry({
tag: 'a',
attributes: {'class': $block.find('button').attr('class')},
content: h('span', $block.find('button').text()),
content: $block[0],
action: function () {
$block.click();
$block.find('button').click();
},
});
// $blockButton.append($block.find('ul'));

View file

@ -63,7 +63,7 @@ MessengerUI, Messages, Pages) {
return 'cp-toolbar-uid-' + String(Math.random()).substring(2);
};
var observeChildren = function ($content) {
var observeChildren = function ($content, isDrawer) {
var reorderDOM = Util.throttle(function ($content, observer) {
if (!$content.length) { return; }
@ -72,7 +72,15 @@ MessengerUI, Messages, Pages) {
$content[0].childNodes.forEach((node) => {
try {
if (!node.attributes) { return; }
var order = getComputedStyle(node).getPropertyValue("order");
let nodeWithOrder;
if (isDrawer) { // HACK: the order is set on their inner "a" tag
let $n = $(node);
if (!$n.attr('class') &&
($n.find('.fa').length || $n.find('.cptools').length)) {
nodeWithOrder = $n.find('.fa')[0] || $n.find('.cptools')[0];
}
}
var order = getComputedStyle(nodeWithOrder || node).getPropertyValue("order");
var a = map[order] = map[order] || [];
a.push(node);
} catch (e) { console.error(e, node); }
@ -150,7 +158,7 @@ MessengerUI, Messages, Pages) {
text: Messages.toolbar_file,
options: [],
common: Common,
buttonCls: 'fa fa-file-o'
iconCls: 'fa fa-file-o'
});
$drawer.addClass(FILE_CLS).appendTo($file);
$drawer.find('.cp-dropdown-content').addClass(DRAWER_CLS);
@ -285,7 +293,7 @@ MessengerUI, Messages, Pages) {
var fa_editusers = '<span class="fa fa-users"></span>';
var fa_viewusers = numberOfViewUsers === '' ? '' : '<span class="fa fa-eye"></span>';
var $spansmall = $('<span>').html(fa_editusers + ' ' + numberOfEditUsers + '&nbsp;&nbsp; ' + fa_viewusers + ' ' + numberOfViewUsers);
$userButtons.find('.cp-dropdown-button-title').html('').append($spansmall);
$userButtons.find('.cp-toolbar-userlist-button').html('').append($spansmall);
if (!online || toolbar.isDeleted) { return; }
@ -464,7 +472,7 @@ MessengerUI, Messages, Pages) {
var $container = $('<span>', {id: 'cp-toolbar-userlist-drawer-open', title: Messages.userListButton});
var $button = $('<button>').appendTo($container);
$('<span>',{'class': 'cp-dropdown-button-title'}).appendTo($button);
$('<span>',{'class': 'cp-toolbar-userlist-button'}).appendTo($button);
toolbar.$bottomR.prepend($container);
@ -1376,7 +1384,7 @@ MessengerUI, Messages, Pages) {
toolbar.$history = $toolbar.find('.'+Bar.constants.history);
toolbar.$user = $toolbar.find('.'+Bar.constants.userAdmin);
observeChildren(toolbar.$drawer);
observeChildren(toolbar.$drawer, true);
observeChildren(toolbar.$bottomL);
observeChildren(toolbar.$bottomM);
observeChildren(toolbar.$bottomR);

View file

@ -113,11 +113,11 @@ define([
text: Messages.toolbar_theme,
options: [],
common: Common,
buttonCls: 'cptools cptools-palette'
iconCls: 'cptools cptools-palette'
});
framework._.toolbar.$theme = $drawer.find('ul.cp-dropdown-content');
framework._.toolbar.$bottomL.append($drawer);
$drawer.find('span').addClass('cp-button-name');
$drawer.addClass('cp-toolbar-appmenu');
};
var mkPrintButton = function (framework, editor, $content, $print) {