account for elements with tabbable controls

pull/1755/head
konnorrogers 2023-12-01 18:09:11 -05:00
rodzic dd27db5196
commit 8d579c18cc
3 zmienionych plików z 33 dodań i 5 usunięć

Wyświetl plik

@ -300,9 +300,9 @@ export default class SlDialog extends ShoelaceElement {
`
: ''}
${
'' /* The tabindex="-1" is here because the body is technically scrollable if overflowing. However, if there's no focusable elements inside, you won't actually be able to scroll it via keyboard. */
'' /* The tabindex="-1" is here because the body is technically scrollable if overflowing. However, if there's no focusable elements inside, you won't actually be able to scroll it via keyboard. Previously this was just a <slot>, but tabindex="-1" on the slot causes children to not be focusable. https://github.com/shoelace-style/shoelace/issues/1753#issuecomment-1836803277 */
}
<slot part="body" class="dialog__body" tabindex="-1"></slot>
<div part="body" class="dialog__body" tabindex="-1"><slot></slot></div>
<footer part="footer" class="dialog__footer">
<slot name="footer"></slot>

Wyświetl plik

@ -71,14 +71,26 @@ export default class Modal {
if (event.key !== 'Tab' || this.isExternalActivated) return;
if (!this.isActive()) return;
const elementsWithTabbableControls = [
"audio",
"video",
"iframe"
]
const possiblyHasTabbableChildren = (element: HTMLElement) => {
return (
elementsWithTabbableControls.includes(element.tagName.toLowerCase())
|| element.hasAttribute("controls")
// Should we add a data-attribute for people to set just in case they have an element where we don't know if it has possibly tabbable elements?
)
}
if (event.shiftKey) {
this.tabDirection = 'backward';
} else {
this.tabDirection = 'forward';
}
event.preventDefault();
const tabbableElements = getTabbableElements(this.element);
// Because sometimes focus can actually be taken over from outside sources,
@ -89,6 +101,14 @@ export default class Modal {
if (currentFocusIndex === -1) {
this.currentFocus = tabbableElements[0];
// We don't call event.preventDefault() here because it messes with tabbing to the <iframe> controls.
// We just wait until the current focus is no longer an element with possible hidden controls.
if (possiblyHasTabbableChildren(this.currentFocus)) {
return
}
event.preventDefault();
this.currentFocus?.focus({ preventScroll: true });
return;
}
@ -104,6 +124,14 @@ export default class Modal {
}
this.currentFocus = tabbableElements[currentFocusIndex];
// We don't call event.preventDefault() here because it messes with tabbing to the <iframe> controls.
// We just wait until the current focus is no longer an element with possible hidden controls.
if (possiblyHasTabbableChildren(this.currentFocus)) {
return
}
event.preventDefault()
this.currentFocus?.focus({ preventScroll: true });
setTimeout(() => this.checkFocus());

Wyświetl plik

@ -58,7 +58,7 @@ function isTabbable(el: HTMLElement) {
}
// At this point, the following elements are considered tabbable
return ['button', 'input', 'select', 'textarea', 'a', 'audio', 'video', 'summary'].includes(tag);
return ['button', 'input', 'select', 'textarea', 'a', 'audio', 'video', 'summary', 'iframe'].includes(tag);
}
/**