-
Notifications
You must be signed in to change notification settings - Fork 82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: move focus utils to component-base #3141
Conversation
dd21829
to
478c22b
Compare
478c22b
to
3635f21
Compare
* @param {!HTMLElement} element | ||
* @return {boolean} | ||
*/ | ||
function isElementHidden(element) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Renamed isVisible
to isElementHidden
to invert the condition here.
web-components/packages/vaadin-overlay/src/vaadin-focusables-helper.js
Lines 137 to 146 in 699547e
static _isVisible(element) { | |
// Check inline style first to save a re-flow. If looks good, check also | |
// computed style. | |
let style = element.style; | |
if (style.visibility !== 'hidden' && style.display !== 'none') { | |
style = window.getComputedStyle(element); | |
return style.visibility !== 'hidden' && style.display !== 'none'; | |
} | |
return false; | |
} |
ccdfe18
to
211f52e
Compare
* @param {HTMLElement} element | ||
* @return {boolean} | ||
*/ | ||
export function isElementFocused(element) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: A new utility function.
* @param {HTMLElement} element | ||
* @return {HTMLElement[]} | ||
*/ | ||
export function getFocusableElements(element) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Renamed getTabbableNodes
to getFocusableElements
.
static getTabbableNodes(node) { | |
const result = []; | |
// If there is at least one element with tabindex > 0, we need to sort | |
// the final array by tabindex. | |
const needsSortByTabIndex = this._collectTabbableNodes(node, result); | |
if (needsSortByTabIndex) { | |
return this._sortByTabIndex(result); | |
} | |
return result; | |
} |
function collectFocusableNodes(node, result) { | ||
// If the node is not an element or hidden, no need to traverse children. | ||
if (node.nodeType !== Node.ELEMENT_NODE || isElementHidden(node)) { | ||
return false; | ||
} | ||
const element = /** @type {HTMLElement} */ (node); | ||
const tabIndex = normalizeTabIndex(element); | ||
let needsSort = tabIndex > 0; | ||
if (tabIndex >= 0) { | ||
result.push(element); | ||
} | ||
|
||
// In ShadowDOM v1, tab order is affected by the order of distribution. | ||
// E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B]; | ||
// in ShadowDOM v0 tab order is not affected by the distribution order, | ||
// in fact getTabbableNodes(#root) returns [#B, #A]. | ||
// <div id="root"> | ||
// <!-- shadow --> | ||
// <slot name="a"> | ||
// <slot name="b"> | ||
// <!-- /shadow --> | ||
// <input id="A" slot="a"> | ||
// <input id="B" slot="b" tabindex="1"> | ||
// </div> | ||
let children; | ||
if (element.localName === 'slot') { | ||
children = element.assignedNodes({ flatten: true }); | ||
} else { | ||
// Use shadow root if possible, will check for distributed nodes. | ||
children = (element.shadowRoot || element).children; | ||
} | ||
if (children) { | ||
for (let i = 0; i < children.length; i++) { | ||
// Ensure method is always invoked to collect tabbable children. | ||
needsSort = collectFocusableNodes(children[i], result) || needsSort; | ||
} | ||
} | ||
return needsSort; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Renamed _collectTabbableNodes
to collectFocusableNodes
.
web-components/packages/vaadin-overlay/src/vaadin-focusables-helper.js
Lines 91 to 129 in 699547e
static _collectTabbableNodes(node, result) { | |
// If not an element or not visible, no need to explore children. | |
if (node.nodeType !== Node.ELEMENT_NODE || !this._isVisible(node)) { | |
return false; | |
} | |
const element = /** @type {!HTMLElement} */ (node); | |
const tabIndex = this._normalizedTabIndex(element); | |
let needsSort = tabIndex > 0; | |
if (tabIndex >= 0) { | |
result.push(element); | |
} | |
// In ShadowDOM v1, tab order is affected by the order of distribution. | |
// E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B]; | |
// in ShadowDOM v0 tab order is not affected by the distribution order, | |
// in fact getTabbableNodes(#root) returns [#B, #A]. | |
// <div id="root"> | |
// <!-- shadow --> | |
// <slot name="a"> | |
// <slot name="b"> | |
// <!-- /shadow --> | |
// <input id="A" slot="a"> | |
// <input id="B" slot="b" tabindex="1"> | |
// </div> | |
let children; | |
if (element.localName === 'slot') { | |
children = element.assignedNodes({ flatten: true }); | |
} else { | |
// Use shadow root if possible, will check for distributed nodes. | |
children = (element.shadowRoot || element).children; | |
} | |
if (children) { | |
for (let i = 0; i < children.length; i++) { | |
// Ensure method is always invoked to collect tabbable children. | |
needsSort = this._collectTabbableNodes(children[i], result) || needsSort; | |
} | |
} | |
return needsSort; | |
} |
* @param {HTMLElement} element | ||
* @return {boolean} | ||
*/ | ||
export function isElementFocusable(element) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: Renamed isFocusable
to isElementFocusable
.
static isFocusable(element) { | |
// From http://stackoverflow.com/a/1600194/4228703: | |
// There isn't a definite list, it's up to the browser. The only | |
// standard we have is DOM Level 2 HTML | |
// https://www.w3.org/TR/DOM-Level-2-HTML/html.html, according to which the | |
// only elements that have a focus() method are HTMLInputElement, | |
// HTMLSelectElement, HTMLTextAreaElement and HTMLAnchorElement. This | |
// notably omits HTMLButtonElement and HTMLAreaElement. Referring to these | |
// tests with tabbables in different browsers | |
// http://allyjs.io/data-tables/focusable.html | |
// Elements that cannot be focused if they have [disabled] attribute. | |
if (element.matches('input, select, textarea, button, object')) { | |
return element.matches(':not([disabled])'); | |
} | |
// Elements that can be focused even if they have [disabled] attribute. | |
return element.matches('a[href], area[href], iframe, [tabindex], [contentEditable]'); | |
} |
/** | ||
* @param {Element[]} elements | ||
* @return {number} | ||
* @protected | ||
*/ | ||
_focusedIndex(elements) { | ||
elements = elements || this._getFocusableElements(); | ||
return elements.indexOf(elements.filter(this._isFocused).pop()); | ||
return elements.indexOf(elements.filter((element) => element && isElementFocused(element)).pop()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: It is the caller's responsibility to make sure that the element is not undefined
or null
before it is passed to the isElementFocused
function.
4b9850e
to
45cb73c
Compare
5778897
to
c060b97
Compare
c060b97
to
bcd47cb
Compare
Please retry analysis of this Pull-Request directly on SonarCloud. |
This ticket/PR has been released with platform 23.0.0.alpha1 and is also targeting the upcoming stable 23.0.0 version. |
Description
FocusablesHelper
class into plain functions.@vaadin/component-base/src/focus-utils.js
.Part of #3140.
Type of change
Checklist