Keyboard and focus improvements

This commit is contained in:
Sam Potts
2018-07-15 19:23:28 +10:00
parent ead6601394
commit e63ad7c74b
20 changed files with 1326 additions and 646 deletions

View File

@ -116,7 +116,11 @@ export function emptyElement(element) {
// Replace element
export function replaceElement(newChild, oldChild) {
if (!is.element(oldChild) || !is.element(oldChild.parentNode) || !is.element(newChild)) {
if (
!is.element(oldChild) ||
!is.element(oldChild.parentNode) ||
!is.element(newChild)
) {
return null;
}
@ -203,6 +207,10 @@ export function toggleHidden(element, hidden) {
// Mirror Element.classList.toggle, with IE compatibility for "force" argument
export function toggleClass(element, className, force) {
if (is.nodeList(element)) {
return Array.from(element).map(e => toggleClass(e, className, force));
}
if (is.element(element)) {
let method = 'toggle';
if (typeof force !== 'undefined') {
@ -213,7 +221,7 @@ export function toggleClass(element, className, force) {
return element.classList.contains(className);
}
return null;
return false;
}
// Has class name
@ -249,26 +257,16 @@ export function getElement(selector) {
return this.elements.container.querySelector(selector);
}
// Get the focused element
export function getFocusElement() {
let focused = document.activeElement;
if (!focused || focused === document.body) {
focused = null;
} else {
focused = document.querySelector(':focus');
}
return focused;
}
// Trap focus inside container
export function trapFocus(element = null, toggle = false) {
if (!is.element(element)) {
return;
}
const focusable = getElements.call(this, 'button:not(:disabled), input:not(:disabled), [tabindex]');
const focusable = getElements.call(
this,
'button:not(:disabled), input:not(:disabled), [tabindex]',
);
const first = focusable[0];
const last = focusable[focusable.length - 1];
@ -279,7 +277,7 @@ export function trapFocus(element = null, toggle = false) {
}
// Get the current focused element
const focused = getFocusElement();
const focused = document.activeElement;
if (focused === last && !event.shiftKey) {
// Move focus to first element that can be tabbed if Shift isn't used
@ -292,7 +290,14 @@ export function trapFocus(element = null, toggle = false) {
}
};
toggleListener.call(this, this.elements.container, 'keydown', trap, toggle, false);
toggleListener.call(
this,
this.elements.container,
'keydown',
trap,
toggle,
false,
);
}
// Set focus and tab focus class

View File

@ -27,9 +27,21 @@ const supportsPassiveListeners = (() => {
})();
// Toggle event listener
export function toggleListener(element, event, callback, toggle = false, passive = true, capture = false) {
export function toggleListener(
element,
event,
callback,
toggle = false,
passive = true,
capture = false,
) {
// Bail if no element, event, or callback
if (!element || !('addEventListener' in element) || is.empty(event) || !is.function(callback)) {
if (
!element ||
!('addEventListener' in element) ||
is.empty(event) ||
!is.function(callback)
) {
return;
}
@ -57,28 +69,74 @@ export function toggleListener(element, event, callback, toggle = false, passive
this.eventListeners.push({ element, type, callback, options });
}
element[toggle ? 'addEventListener' : 'removeEventListener'](type, callback, options);
element[toggle ? 'addEventListener' : 'removeEventListener'](
type,
callback,
options,
);
});
}
// Bind event handler
export function on(element, events = '', callback, passive = true, capture = false) {
toggleListener.call(this, element, events, callback, true, passive, capture);
export function on(
element,
events = '',
callback,
passive = true,
capture = false,
) {
toggleListener.call(
this,
element,
events,
callback,
true,
passive,
capture,
);
}
// Unbind event handler
export function off(element, events = '', callback, passive = true, capture = false) {
toggleListener.call(this, element, events, callback, false, passive, capture);
export function off(
element,
events = '',
callback,
passive = true,
capture = false,
) {
toggleListener.call(
this,
element,
events,
callback,
false,
passive,
capture,
);
}
// Bind once-only event handler
export function once(element, events = '', callback, passive = true, capture = false) {
export function once(
element,
events = '',
callback,
passive = true,
capture = false,
) {
function onceCallback(...args) {
off(element, events, onceCallback, passive, capture);
callback.apply(this, args);
}
toggleListener.call(this, element, events, onceCallback, true, passive, capture);
toggleListener.call(
this,
element,
events,
onceCallback,
true,
passive,
capture,
);
}
// Trigger event
@ -115,6 +173,9 @@ export function unbindListeners() {
// Run method when / if player is ready
export function ready() {
return new Promise(
resolve => (this.ready ? setTimeout(resolve, 0) : on.call(this, this.elements.container, 'ready', resolve)),
resolve =>
this.ready
? setTimeout(resolve, 0)
: on.call(this, this.elements.container, 'ready', resolve),
).then(() => {});
}