ally.js
ally.js copied to clipboard
add component to safely restore focus
This topic came up while talking jQueryUI integration with @jzaefferer:
The dialog tutorial features a naive method of storing and restoring focus. The following scenarios (upon restore) are not unheard of:
- the element is not focusable anymore
- the element does not exist anymore
safe focus()
Calling element.focus() on something that is not focusable or attached to the DOM will cause <body> to become the activeElement. In case we're trying to focus a non-focusable element, we could however traverse the parents to find a focusable element and pass focus to that element instead. ally.get.focusTarget should help with that. We would wrap this in ally.element.focus and could provide the following options:
- "do not shift focus away from activeElement unless there is a valid target"
- "predefined fallback element to focus if target cannot be resolved" (also using
ally.get.focusTargetfor convenience) - "try focusing next element in DOM hierarchy" (before engaging 1 or 2)
- "wait until element is focusable" (using
ally.when.focusablebut not returning the service handle)
restore current activeElement service
If we simply store the activeElement and it is detached from the DOM before we restore focus, we can't get its ancestry anymore. For that reason it makes sense to provide a service that upon engaging stores the current element hierarchy. The service handle would expose a function .restore() that tries to shift focus to an element in the previously saved hierarchy. At engage() and restore() a "fallback" callback can be passed that is invoked should the service not be able to restore focus.
Should this be made available at ally.maintain.restorableFocus?
Relevant snippet for restoring activeElement from jQuery UI dialog: https://github.com/jquery/jquery-ui/blob/ffcfb85c9818954adda69e73cf9ba76ea07b554c/ui/widgets/dialog.js#L224-L230 - has some inline comments for WebKit specific issues, might be useful.
That uses our safeBlur and safeActiveElement helpers - both have a bunch of support comments as well.
I haven't looked through ally.js enough to see if any of those aren't yet covered.
thx for the pointers!
You're mostly dealing with browsers "misbehaving" when hiding or even removing the activeElement.
document.activeElementmay be set tonullin fordocument.activeElement.parentNode.removeChild(document.activeElement)in Internet Explorer. The "same" is true in Firefox if the active element was in a Shadow DOM (likely a subsequent fault because of Shadow DOM leaking the active element in the first place)- I was not aware of IE's
document.body.blur()behavior described in 9420 - I assume the iframe try-catch is there to avoid cross-origin warnings?!
I haven't looked through ally.js enough to see if any of those aren't yet covered.
nothing so far. I guess I'll want to test these things before proceeding with implementing this issue.
I think the try/catch addresses: https://bugs.jqueryui.com/ticket/8443
indeed. looks like we should copy the safeActiveElement() and safeBlur() to ally, then…
Seems good to me. CCing @scottgonzalez and @tjvantoll on this, they might have some more useful insight as well.