luci-base: ui.js: improve ui.Dropdown event handling

Improve overall event and focus handling, avoid registering a global
mouseover event listener, stop propagating escape keypress on closing
dropdown and avoid `Element.blur()` to prevent de-focusing open modals.

Signed-off-by: Jo-Philipp Wich <jo@mein.io>
This commit is contained in:
Jo-Philipp Wich 2023-02-05 19:39:13 +01:00
parent c13ef9406c
commit 8db3e0e70f

View file

@ -1050,7 +1050,8 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
'class': 'cbi-dropdown', 'class': 'cbi-dropdown',
'multiple': this.options.multiple ? '' : null, 'multiple': this.options.multiple ? '' : null,
'optional': this.options.optional ? '' : null, 'optional': this.options.optional ? '' : null,
'disabled': this.options.disabled ? '' : null 'disabled': this.options.disabled ? '' : null,
'tabindex': -1
}, E('ul')); }, E('ul'));
var keys = Object.keys(this.choices); var keys = Object.keys(this.choices);
@ -1186,11 +1187,11 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
} }
else { else {
sb.addEventListener('mouseover', this.handleMouseover.bind(this)); sb.addEventListener('mouseover', this.handleMouseover.bind(this));
sb.addEventListener('mouseout', this.handleMouseout.bind(this));
sb.addEventListener('focus', this.handleFocus.bind(this)); sb.addEventListener('focus', this.handleFocus.bind(this));
canary.addEventListener('focus', this.handleCanaryFocus.bind(this)); canary.addEventListener('focus', this.handleCanaryFocus.bind(this));
window.addEventListener('mouseover', this.setFocus);
window.addEventListener('click', this.closeAllDropdowns); window.addEventListener('click', this.closeAllDropdowns);
} }
@ -1343,7 +1344,12 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
sb.lastElementChild.setAttribute('tabindex', 0); sb.lastElementChild.setAttribute('tabindex', 0);
this.setFocus(sb, sel || li[0], true); var focusFn = L.bind(function(el) {
this.setFocus(sb, el, true);
ul.removeEventListener('transitionend', focusFn);
}, this, sel || li[0]);
ul.addEventListener('transitionend', focusFn);
}, },
/** @private */ /** @private */
@ -1559,26 +1565,33 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
/** @private */ /** @private */
setFocus: function(sb, elem, scroll) { setFocus: function(sb, elem, scroll) {
if (sb && sb.hasAttribute && sb.hasAttribute('locked-in')) if (sb.hasAttribute('locked-in'))
return; return;
if (sb.target && findParent(sb.target, 'ul.dropdown')) sb.querySelectorAll('.focus').forEach(function(e) {
return; e.classList.remove('focus');
document.querySelectorAll('.focus').forEach(function(e) {
if (!matchesElem(e, 'input')) {
e.classList.remove('focus');
e.blur();
}
}); });
if (elem) { elem.classList.add('focus');
elem.focus();
elem.classList.add('focus');
if (scroll) if (scroll)
elem.parentNode.scrollTop = elem.offsetTop - elem.parentNode.offsetTop; elem.parentNode.scrollTop = elem.offsetTop - elem.parentNode.offsetTop;
}
elem.focus();
},
/** @private */
handleMouseout: function(ev) {
var sb = ev.currentTarget;
if (!sb.hasAttribute('open'))
return;
sb.querySelectorAll('.focus').forEach(function(e) {
e.classList.remove('focus');
});
sb.querySelector('ul.dropdown').focus();
}, },
/** @private */ /** @private */
@ -1758,7 +1771,8 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
/** @private */ /** @private */
handleKeydown: function(ev) { handleKeydown: function(ev) {
var sb = ev.currentTarget; var sb = ev.currentTarget,
ul = sb.querySelector('ul.dropdown');
if (matchesElem(ev.target, 'input')) if (matchesElem(ev.target, 'input'))
return; return;
@ -1779,6 +1793,7 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
switch (ev.keyCode) { switch (ev.keyCode) {
case 27: case 27:
this.closeDropdown(sb); this.closeDropdown(sb);
ev.stopPropagation();
break; break;
case 13: case 13:
@ -1802,6 +1817,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
this.setFocus(sb, active.previousElementSibling); this.setFocus(sb, active.previousElementSibling);
ev.preventDefault(); ev.preventDefault();
} }
else if (document.activeElement === ul) {
this.setFocus(sb, ul.lastElementChild);
ev.preventDefault();
}
break; break;
case 40: case 40:
@ -1809,6 +1828,10 @@ var UIDropdown = UIElement.extend(/** @lends LuCI.ui.Dropdown.prototype */ {
this.setFocus(sb, active.nextElementSibling); this.setFocus(sb, active.nextElementSibling);
ev.preventDefault(); ev.preventDefault();
} }
else if (document.activeElement === ul) {
this.setFocus(sb, ul.firstElementChild);
ev.preventDefault();
}
break; break;
} }
} }