Comparing version 5.2.1 to 5.3.0-beta.0
# Changelog | ||
## 5.3.0-beta.0 | ||
- Includes new Shadow DOM support for open shadows by default | ||
- Includes a new `getShadowRoot()` configuration option, enabling support for closed shadows | ||
## 5.2.1 | ||
@@ -4,0 +9,0 @@ |
/*! | ||
* tabbable 5.2.1 | ||
* tabbable 5.3.0-beta.0 | ||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE | ||
*/ | ||
var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details']; | ||
var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]:not(slot)', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details']; | ||
var candidateSelector = /* #__PURE__ */candidateSelectors.join(','); | ||
var matches = typeof Element === 'undefined' ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var NoElement = typeof Element === 'undefined'; | ||
var matches = NoElement ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var getRootNode = !NoElement && Element.prototype.getRootNode ? function (element) { | ||
return element.getRootNode(); | ||
} : function (element) { | ||
return element.ownerDocument; | ||
}; | ||
/** | ||
* @param {Element} el container to check in | ||
* @param {boolean} includeContainer add container to check | ||
* @param {(node: Element) => boolean} filter filter candidates | ||
* @returns {Element[]} | ||
*/ | ||
@@ -19,3 +31,83 @@ var getCandidates = function getCandidates(el, includeContainer, filter) { | ||
}; | ||
/** | ||
* @callback GetShadowRoot | ||
* @param {Element} element to check for shadow root | ||
* @returns {ShadowRoot|boolean} ShadowRoot if available or boolean indicating if a shadowRoot is attached but not available. | ||
*/ | ||
/** | ||
* @typedef {Object} CandidatesScope | ||
* @property {Element} scope contains inner candidates | ||
* @property {Element[]} candidates | ||
*/ | ||
/** | ||
* @typedef {Object} IterativeOptions | ||
* @property {GetShadowRoot} getShadowRoot returns the shadow root of an element or a boolean stating if it has a shadow root | ||
* @property {(node: Element) => boolean} filter filter candidates | ||
* @property {boolean} flatten if true then result will flatten any CandidatesScope into the returned list | ||
*/ | ||
/** | ||
* @param {Element[]} elements list of element containers to match candidates from | ||
* @param {boolean} includeContainer add container list to check | ||
* @param {IterativeOptions} options | ||
* @returns {Array.<Element|CandidatesScope>} | ||
*/ | ||
var getCandidatesIteratively = function getCandidatesIteratively(elements, includeContainer, options) { | ||
var candidates = []; | ||
var elementsToCheck = Array.from(elements); | ||
while (elementsToCheck.length) { | ||
var element = elementsToCheck.shift(); | ||
if (element.tagName === 'SLOT') { | ||
// add shadow dom slot scope (slot itself cannot be focusable) | ||
var assigned = element.assignedElements(); | ||
var content = assigned.length ? assigned : element.children; | ||
var nestedCandidates = getCandidatesIteratively(content, true, options); | ||
if (options.flatten) { | ||
candidates.push.apply(candidates, nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: nestedCandidates | ||
}); | ||
} | ||
} else { | ||
// check candidate element | ||
var validCandidate = matches.call(element, candidateSelector); | ||
if (validCandidate && options.filter(element) && (includeContainer || !elements.includes(element))) { | ||
candidates.push(element); | ||
} // iterate over content | ||
var shadowRoot = element.shadowRoot || options.getShadowRoot(element); | ||
if (shadowRoot) { | ||
// add shadow dom scope | ||
var _nestedCandidates = getCandidatesIteratively(shadowRoot === true ? element.children : shadowRoot.children, true, options); | ||
if (options.flatten) { | ||
candidates.push.apply(candidates, _nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: _nestedCandidates | ||
}); | ||
} | ||
} else { | ||
// add light dom scope | ||
elementsToCheck.unshift.apply(elementsToCheck, element.children); | ||
} | ||
} | ||
} | ||
return candidates; | ||
}; | ||
var isContentEditable = function isContentEditable(node) { | ||
@@ -25,3 +117,3 @@ return node.contentEditable === 'true'; | ||
var getTabindex = function getTabindex(node) { | ||
var getTabindex = function getTabindex(node, isScope) { | ||
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); | ||
@@ -42,5 +134,9 @@ | ||
// order, consider their tab index to be 0. | ||
// | ||
// isScope is positive for custom element with shadow root or slot that by default | ||
// have tabIndex -1, but need to be sorted by document order in order for their | ||
// content to be inserted in the correct position | ||
if ((node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { | ||
if ((isScope || node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { | ||
return 0; | ||
@@ -84,3 +180,3 @@ } | ||
var radioScope = node.form || node.ownerDocument; | ||
var radioScope = node.form || getRootNode(node); | ||
@@ -117,3 +213,17 @@ var queryRadios = function queryRadios(name) { | ||
var isHidden = function isHidden(node, displayCheck) { | ||
var noop = function noop() {}; | ||
var isZeroArea = function isZeroArea(node) { | ||
var _node$getBoundingClie = node.getBoundingClientRect(), | ||
width = _node$getBoundingClie.width, | ||
height = _node$getBoundingClie.height; | ||
return width === 0 && height === 0; | ||
}; | ||
var isHidden = function isHidden(node, _ref) { | ||
var displayCheck = _ref.displayCheck, | ||
_ref$getShadowRoot = _ref.getShadowRoot, | ||
getShadowRoot = _ref$getShadowRoot === void 0 ? noop : _ref$getShadowRoot; | ||
if (getComputedStyle(node).visibility === 'hidden') { | ||
@@ -136,10 +246,21 @@ return true; | ||
node = node.parentElement; | ||
var parentElement = node.parentElement; | ||
var rootNode = getRootNode(node); | ||
if (parentElement && !parentElement.shadowRoot && getShadowRoot(parentElement)) { | ||
// fallback to zero area size for unreachable shadow dom | ||
return isZeroArea(node); | ||
} else if (node.assignedSlot) { | ||
// iterate up slot | ||
node = node.assignedSlot; | ||
} else if (!parentElement && rootNode !== node.ownerDocument) { | ||
// cross shadow boundary | ||
node = rootNode.host; | ||
} else { | ||
// iterate up normal dom | ||
node = parentElement; | ||
} | ||
} | ||
} else if (displayCheck === 'non-zero-area') { | ||
var _node$getBoundingClie = node.getBoundingClientRect(), | ||
width = _node$getBoundingClie.width, | ||
height = _node$getBoundingClie.height; | ||
return width === 0 && height === 0; | ||
return isZeroArea(node); | ||
} | ||
@@ -192,3 +313,3 @@ | ||
var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(options, node) { | ||
if (node.disabled || isHiddenInput(node) || isHidden(node, options.displayCheck) || // For a details element with a summary, the summary element gets the focus | ||
if (node.disabled || isHiddenInput(node) || isHidden(node, options) || // For a details element with a summary, the summary element gets the focus | ||
isDetailsWithSummary(node) || isDisabledFromFieldset(node)) { | ||
@@ -208,13 +329,19 @@ return false; | ||
}; | ||
/** | ||
* @param {Array.<Element|CandidatesScope>} candidates | ||
* @returns Element[] | ||
*/ | ||
var tabbable = function tabbable(el, options) { | ||
options = options || {}; | ||
var sortByOrder = function sortByOrder(candidates) { | ||
var regularTabbables = []; | ||
var orderedTabbables = []; | ||
var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); | ||
candidates.forEach(function (candidate, i) { | ||
var candidateTabindex = getTabindex(candidate); | ||
candidates.forEach(function (item, i) { | ||
var isScope = !!item.scope; | ||
var element = isScope ? item.scope : item; | ||
var candidateTabindex = getTabindex(element, isScope); | ||
var elements = isScope ? sortByOrder(item.candidates) : element; | ||
if (candidateTabindex === 0) { | ||
regularTabbables.push(candidate); | ||
isScope ? regularTabbables.push.apply(regularTabbables, elements) : regularTabbables.push(element); | ||
} else { | ||
@@ -224,15 +351,45 @@ orderedTabbables.push({ | ||
tabIndex: candidateTabindex, | ||
node: candidate | ||
item: item, | ||
isScope: isScope, | ||
content: elements | ||
}); | ||
} | ||
}); | ||
var tabbableNodes = orderedTabbables.sort(sortOrderedTabbables).map(function (a) { | ||
return a.node; | ||
}).concat(regularTabbables); | ||
return tabbableNodes; | ||
return orderedTabbables.sort(sortOrderedTabbables).reduce(function (acc, sortable) { | ||
sortable.isScope ? acc.push.apply(acc, sortable.content) : acc.push(sortable.content); | ||
return acc; | ||
}, []).concat(regularTabbables); | ||
}; | ||
var tabbable = function tabbable(el, options) { | ||
options = options || {}; | ||
var candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorTabbable.bind(null, options), | ||
flatten: false, | ||
getShadowRoot: options.getShadowRoot | ||
}); | ||
} else { | ||
candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); | ||
} | ||
return sortByOrder(candidates); | ||
}; | ||
var focusable = function focusable(el, options) { | ||
options = options || {}; | ||
var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); | ||
var candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorFocusable.bind(null, options), | ||
flatten: true, | ||
getShadowRoot: options.getShadowRoot | ||
}); | ||
} else { | ||
candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); | ||
} | ||
return candidates; | ||
@@ -239,0 +396,0 @@ }; |
/*! | ||
* tabbable 5.2.1 | ||
* tabbable 5.3.0-beta.0 | ||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE | ||
*/ | ||
var e=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary:first-of-type","details"],t=e.join(","),n="undefined"==typeof Element?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,r=function(e,r,o){var i=Array.prototype.slice.apply(e.querySelectorAll(t));return r&&n.call(e,t)&&i.unshift(e),i=i.filter(o)},o=function(e){var t=parseInt(e.getAttribute("tabindex"),10);return isNaN(t)?function(e){return"true"===e.contentEditable}(e)?0:"AUDIO"!==e.nodeName&&"VIDEO"!==e.nodeName&&"DETAILS"!==e.nodeName||null!==e.getAttribute("tabindex")?e.tabIndex:0:t},i=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},a=function(e){return"INPUT"===e.tagName},u=function(e){return function(e){return a(e)&&"radio"===e.type}(e)&&!function(e){if(!e.name)return!0;var t,n=e.form||e.ownerDocument,r=function(e){return n.querySelectorAll('input[type="radio"][name="'+e+'"]')};if("undefined"!=typeof window&&void 0!==window.CSS&&"function"==typeof window.CSS.escape)t=r(window.CSS.escape(e.name));else try{t=r(e.name)}catch(e){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",e.message),!1}var o=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]}(t,e.form);return!o||o===e}(e)},c=function(e,t){return!(t.disabled||function(e){return a(e)&&"hidden"===e.type}(t)||function(e,t){if("hidden"===getComputedStyle(e).visibility)return!0;var r=n.call(e,"details>summary:first-of-type")?e.parentElement:e;if(n.call(r,"details:not([open]) *"))return!0;if(t&&"full"!==t){if("non-zero-area"===t){var o=e.getBoundingClientRect(),i=o.width,a=o.height;return 0===i&&0===a}}else for(;e;){if("none"===getComputedStyle(e).display)return!0;e=e.parentElement}return!1}(t,e.displayCheck)||function(e){return"DETAILS"===e.tagName&&Array.prototype.slice.apply(e.children).some((function(e){return"SUMMARY"===e.tagName}))}(t)||function(e){if(a(e)||"SELECT"===e.tagName||"TEXTAREA"===e.tagName||"BUTTON"===e.tagName)for(var t=e.parentElement;t;){if("FIELDSET"===t.tagName&&t.disabled){for(var n=0;n<t.children.length;n++){var r=t.children.item(n);if("LEGEND"===r.tagName)return!r.contains(e)}return!0}t=t.parentElement}return!1}(t))},l=function(e,t){return!(!c(e,t)||u(t)||o(t)<0)},d=function(e,t){var n=[],a=[];return r(e,(t=t||{}).includeContainer,l.bind(null,t)).forEach((function(e,t){var r=o(e);0===r?n.push(e):a.push({documentOrder:t,tabIndex:r,node:e})})),a.sort(i).map((function(e){return e.node})).concat(n)},f=function(e,t){return r(e,(t=t||{}).includeContainer,c.bind(null,t))},m=function(e,r){if(r=r||{},!e)throw new Error("No node provided");return!1!==n.call(e,t)&&l(r,e)},p=e.concat("iframe").join(","),s=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==n.call(e,p)&&c(t,e)};export{f as focusable,s as isFocusable,m as isTabbable,d as tabbable}; | ||
var e=["input","select","textarea","a[href]","button","[tabindex]:not(slot)","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary:first-of-type","details"],t=e.join(","),n="undefined"==typeof Element,r=n?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,o=!n&&Element.prototype.getRootNode?function(e){return e.getRootNode()}:function(e){return e.ownerDocument},a=function(e,n,o){var a=Array.prototype.slice.apply(e.querySelectorAll(t));return n&&r.call(e,t)&&a.unshift(e),a=a.filter(o)},i=function e(n,o,a){for(var i=[],u=Array.from(n);u.length;){var l=u.shift();if("SLOT"===l.tagName){var c=l.assignedElements(),d=e(c.length?c:l.children,!0,a);a.flatten?i.push.apply(i,d):i.push({scope:l,candidates:d})}else{r.call(l,t)&&a.filter(l)&&(o||!n.includes(l))&&i.push(l);var f=l.shadowRoot||a.getShadowRoot(l);if(f){var s=e(!0===f?l.children:f.children,!0,a);a.flatten?i.push.apply(i,s):i.push({scope:l,candidates:s})}else u.unshift.apply(u,l.children)}}return i},u=function(e,t){var n=parseInt(e.getAttribute("tabindex"),10);return isNaN(n)?function(e){return"true"===e.contentEditable}(e)?0:!t&&"AUDIO"!==e.nodeName&&"VIDEO"!==e.nodeName&&"DETAILS"!==e.nodeName||null!==e.getAttribute("tabindex")?e.tabIndex:0:n},l=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},c=function(e){return"INPUT"===e.tagName},d=function(e){return function(e){return c(e)&&"radio"===e.type}(e)&&!function(e){if(!e.name)return!0;var t,n=e.form||o(e),r=function(e){return n.querySelectorAll('input[type="radio"][name="'+e+'"]')};if("undefined"!=typeof window&&void 0!==window.CSS&&"function"==typeof window.CSS.escape)t=r(window.CSS.escape(e.name));else try{t=r(e.name)}catch(e){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",e.message),!1}var a=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]}(t,e.form);return!a||a===e}(e)},f=function(){},s=function(e){var t=e.getBoundingClientRect(),n=t.width,r=t.height;return 0===n&&0===r},p=function(e,t){return!(t.disabled||function(e){return c(e)&&"hidden"===e.type}(t)||function(e,t){var n=t.displayCheck,a=t.getShadowRoot,i=void 0===a?f:a;if("hidden"===getComputedStyle(e).visibility)return!0;var u=r.call(e,"details>summary:first-of-type")?e.parentElement:e;if(r.call(u,"details:not([open]) *"))return!0;if(n&&"full"!==n){if("non-zero-area"===n)return s(e)}else for(;e;){if("none"===getComputedStyle(e).display)return!0;var l=e.parentElement,c=o(e);if(l&&!l.shadowRoot&&i(l))return s(e);e=e.assignedSlot?e.assignedSlot:l||c===e.ownerDocument?l:c.host}return!1}(t,e)||function(e){return"DETAILS"===e.tagName&&Array.prototype.slice.apply(e.children).some((function(e){return"SUMMARY"===e.tagName}))}(t)||function(e){if(c(e)||"SELECT"===e.tagName||"TEXTAREA"===e.tagName||"BUTTON"===e.tagName)for(var t=e.parentElement;t;){if("FIELDSET"===t.tagName&&t.disabled){for(var n=0;n<t.children.length;n++){var r=t.children.item(n);if("LEGEND"===r.tagName)return!r.contains(e)}return!0}t=t.parentElement}return!1}(t))},h=function(e,t){return!(!p(e,t)||d(t)||u(t)<0)},m=function(e,t){return function e(t){var n=[],r=[];return t.forEach((function(t,o){var a=!!t.scope,i=a?t.scope:t,l=u(i,a),c=a?e(t.candidates):i;0===l?a?n.push.apply(n,c):n.push(i):r.push({documentOrder:o,tabIndex:l,item:t,isScope:a,content:c})})),r.sort(l).reduce((function(e,t){return t.isScope?e.push.apply(e,t.content):e.push(t.content),e}),[]).concat(n)}((t=t||{}).getShadowRoot?i([e],t.includeContainer,{filter:h.bind(null,t),flatten:!1,getShadowRoot:t.getShadowRoot}):a(e,t.includeContainer,h.bind(null,t)))},y=function(e,t){return(t=t||{}).getShadowRoot?i([e],t.includeContainer,{filter:p.bind(null,t),flatten:!0,getShadowRoot:t.getShadowRoot}):a(e,t.includeContainer,p.bind(null,t))},g=function(e,n){if(n=n||{},!e)throw new Error("No node provided");return!1!==r.call(e,t)&&h(n,e)},S=e.concat("iframe").join(","),v=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==r.call(e,S)&&p(t,e)};export{y as focusable,v as isFocusable,g as isTabbable,m as tabbable}; | ||
//# sourceMappingURL=index.esm.min.js.map |
/*! | ||
* tabbable 5.2.1 | ||
* tabbable 5.3.0-beta.0 | ||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE | ||
@@ -9,5 +9,17 @@ */ | ||
var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details']; | ||
var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]:not(slot)', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details']; | ||
var candidateSelector = /* #__PURE__ */candidateSelectors.join(','); | ||
var matches = typeof Element === 'undefined' ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var NoElement = typeof Element === 'undefined'; | ||
var matches = NoElement ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var getRootNode = !NoElement && Element.prototype.getRootNode ? function (element) { | ||
return element.getRootNode(); | ||
} : function (element) { | ||
return element.ownerDocument; | ||
}; | ||
/** | ||
* @param {Element} el container to check in | ||
* @param {boolean} includeContainer add container to check | ||
* @param {(node: Element) => boolean} filter filter candidates | ||
* @returns {Element[]} | ||
*/ | ||
@@ -24,3 +36,83 @@ var getCandidates = function getCandidates(el, includeContainer, filter) { | ||
}; | ||
/** | ||
* @callback GetShadowRoot | ||
* @param {Element} element to check for shadow root | ||
* @returns {ShadowRoot|boolean} ShadowRoot if available or boolean indicating if a shadowRoot is attached but not available. | ||
*/ | ||
/** | ||
* @typedef {Object} CandidatesScope | ||
* @property {Element} scope contains inner candidates | ||
* @property {Element[]} candidates | ||
*/ | ||
/** | ||
* @typedef {Object} IterativeOptions | ||
* @property {GetShadowRoot} getShadowRoot returns the shadow root of an element or a boolean stating if it has a shadow root | ||
* @property {(node: Element) => boolean} filter filter candidates | ||
* @property {boolean} flatten if true then result will flatten any CandidatesScope into the returned list | ||
*/ | ||
/** | ||
* @param {Element[]} elements list of element containers to match candidates from | ||
* @param {boolean} includeContainer add container list to check | ||
* @param {IterativeOptions} options | ||
* @returns {Array.<Element|CandidatesScope>} | ||
*/ | ||
var getCandidatesIteratively = function getCandidatesIteratively(elements, includeContainer, options) { | ||
var candidates = []; | ||
var elementsToCheck = Array.from(elements); | ||
while (elementsToCheck.length) { | ||
var element = elementsToCheck.shift(); | ||
if (element.tagName === 'SLOT') { | ||
// add shadow dom slot scope (slot itself cannot be focusable) | ||
var assigned = element.assignedElements(); | ||
var content = assigned.length ? assigned : element.children; | ||
var nestedCandidates = getCandidatesIteratively(content, true, options); | ||
if (options.flatten) { | ||
candidates.push.apply(candidates, nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: nestedCandidates | ||
}); | ||
} | ||
} else { | ||
// check candidate element | ||
var validCandidate = matches.call(element, candidateSelector); | ||
if (validCandidate && options.filter(element) && (includeContainer || !elements.includes(element))) { | ||
candidates.push(element); | ||
} // iterate over content | ||
var shadowRoot = element.shadowRoot || options.getShadowRoot(element); | ||
if (shadowRoot) { | ||
// add shadow dom scope | ||
var _nestedCandidates = getCandidatesIteratively(shadowRoot === true ? element.children : shadowRoot.children, true, options); | ||
if (options.flatten) { | ||
candidates.push.apply(candidates, _nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: _nestedCandidates | ||
}); | ||
} | ||
} else { | ||
// add light dom scope | ||
elementsToCheck.unshift.apply(elementsToCheck, element.children); | ||
} | ||
} | ||
} | ||
return candidates; | ||
}; | ||
var isContentEditable = function isContentEditable(node) { | ||
@@ -30,3 +122,3 @@ return node.contentEditable === 'true'; | ||
var getTabindex = function getTabindex(node) { | ||
var getTabindex = function getTabindex(node, isScope) { | ||
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); | ||
@@ -47,5 +139,9 @@ | ||
// order, consider their tab index to be 0. | ||
// | ||
// isScope is positive for custom element with shadow root or slot that by default | ||
// have tabIndex -1, but need to be sorted by document order in order for their | ||
// content to be inserted in the correct position | ||
if ((node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { | ||
if ((isScope || node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { | ||
return 0; | ||
@@ -89,3 +185,3 @@ } | ||
var radioScope = node.form || node.ownerDocument; | ||
var radioScope = node.form || getRootNode(node); | ||
@@ -122,3 +218,17 @@ var queryRadios = function queryRadios(name) { | ||
var isHidden = function isHidden(node, displayCheck) { | ||
var noop = function noop() {}; | ||
var isZeroArea = function isZeroArea(node) { | ||
var _node$getBoundingClie = node.getBoundingClientRect(), | ||
width = _node$getBoundingClie.width, | ||
height = _node$getBoundingClie.height; | ||
return width === 0 && height === 0; | ||
}; | ||
var isHidden = function isHidden(node, _ref) { | ||
var displayCheck = _ref.displayCheck, | ||
_ref$getShadowRoot = _ref.getShadowRoot, | ||
getShadowRoot = _ref$getShadowRoot === void 0 ? noop : _ref$getShadowRoot; | ||
if (getComputedStyle(node).visibility === 'hidden') { | ||
@@ -141,10 +251,21 @@ return true; | ||
node = node.parentElement; | ||
var parentElement = node.parentElement; | ||
var rootNode = getRootNode(node); | ||
if (parentElement && !parentElement.shadowRoot && getShadowRoot(parentElement)) { | ||
// fallback to zero area size for unreachable shadow dom | ||
return isZeroArea(node); | ||
} else if (node.assignedSlot) { | ||
// iterate up slot | ||
node = node.assignedSlot; | ||
} else if (!parentElement && rootNode !== node.ownerDocument) { | ||
// cross shadow boundary | ||
node = rootNode.host; | ||
} else { | ||
// iterate up normal dom | ||
node = parentElement; | ||
} | ||
} | ||
} else if (displayCheck === 'non-zero-area') { | ||
var _node$getBoundingClie = node.getBoundingClientRect(), | ||
width = _node$getBoundingClie.width, | ||
height = _node$getBoundingClie.height; | ||
return width === 0 && height === 0; | ||
return isZeroArea(node); | ||
} | ||
@@ -197,3 +318,3 @@ | ||
var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(options, node) { | ||
if (node.disabled || isHiddenInput(node) || isHidden(node, options.displayCheck) || // For a details element with a summary, the summary element gets the focus | ||
if (node.disabled || isHiddenInput(node) || isHidden(node, options) || // For a details element with a summary, the summary element gets the focus | ||
isDetailsWithSummary(node) || isDisabledFromFieldset(node)) { | ||
@@ -213,13 +334,19 @@ return false; | ||
}; | ||
/** | ||
* @param {Array.<Element|CandidatesScope>} candidates | ||
* @returns Element[] | ||
*/ | ||
var tabbable = function tabbable(el, options) { | ||
options = options || {}; | ||
var sortByOrder = function sortByOrder(candidates) { | ||
var regularTabbables = []; | ||
var orderedTabbables = []; | ||
var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); | ||
candidates.forEach(function (candidate, i) { | ||
var candidateTabindex = getTabindex(candidate); | ||
candidates.forEach(function (item, i) { | ||
var isScope = !!item.scope; | ||
var element = isScope ? item.scope : item; | ||
var candidateTabindex = getTabindex(element, isScope); | ||
var elements = isScope ? sortByOrder(item.candidates) : element; | ||
if (candidateTabindex === 0) { | ||
regularTabbables.push(candidate); | ||
isScope ? regularTabbables.push.apply(regularTabbables, elements) : regularTabbables.push(element); | ||
} else { | ||
@@ -229,15 +356,45 @@ orderedTabbables.push({ | ||
tabIndex: candidateTabindex, | ||
node: candidate | ||
item: item, | ||
isScope: isScope, | ||
content: elements | ||
}); | ||
} | ||
}); | ||
var tabbableNodes = orderedTabbables.sort(sortOrderedTabbables).map(function (a) { | ||
return a.node; | ||
}).concat(regularTabbables); | ||
return tabbableNodes; | ||
return orderedTabbables.sort(sortOrderedTabbables).reduce(function (acc, sortable) { | ||
sortable.isScope ? acc.push.apply(acc, sortable.content) : acc.push(sortable.content); | ||
return acc; | ||
}, []).concat(regularTabbables); | ||
}; | ||
var tabbable = function tabbable(el, options) { | ||
options = options || {}; | ||
var candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorTabbable.bind(null, options), | ||
flatten: false, | ||
getShadowRoot: options.getShadowRoot | ||
}); | ||
} else { | ||
candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); | ||
} | ||
return sortByOrder(candidates); | ||
}; | ||
var focusable = function focusable(el, options) { | ||
options = options || {}; | ||
var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); | ||
var candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorFocusable.bind(null, options), | ||
flatten: true, | ||
getShadowRoot: options.getShadowRoot | ||
}); | ||
} else { | ||
candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); | ||
} | ||
return candidates; | ||
@@ -244,0 +401,0 @@ }; |
/*! | ||
* tabbable 5.2.1 | ||
* tabbable 5.3.0-beta.0 | ||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE | ||
*/ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary:first-of-type","details"],t=e.join(","),n="undefined"==typeof Element?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,r=function(e,r,o){var a=Array.prototype.slice.apply(e.querySelectorAll(t));return r&&n.call(e,t)&&a.unshift(e),a=a.filter(o)},o=function(e){var t=parseInt(e.getAttribute("tabindex"),10);return isNaN(t)?function(e){return"true"===e.contentEditable}(e)?0:"AUDIO"!==e.nodeName&&"VIDEO"!==e.nodeName&&"DETAILS"!==e.nodeName||null!==e.getAttribute("tabindex")?e.tabIndex:0:t},a=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},i=function(e){return"INPUT"===e.tagName},u=function(e){return function(e){return i(e)&&"radio"===e.type}(e)&&!function(e){if(!e.name)return!0;var t,n=e.form||e.ownerDocument,r=function(e){return n.querySelectorAll('input[type="radio"][name="'+e+'"]')};if("undefined"!=typeof window&&void 0!==window.CSS&&"function"==typeof window.CSS.escape)t=r(window.CSS.escape(e.name));else try{t=r(e.name)}catch(e){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",e.message),!1}var o=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]}(t,e.form);return!o||o===e}(e)},l=function(e,t){return!(t.disabled||function(e){return i(e)&&"hidden"===e.type}(t)||function(e,t){if("hidden"===getComputedStyle(e).visibility)return!0;var r=n.call(e,"details>summary:first-of-type")?e.parentElement:e;if(n.call(r,"details:not([open]) *"))return!0;if(t&&"full"!==t){if("non-zero-area"===t){var o=e.getBoundingClientRect(),a=o.width,i=o.height;return 0===a&&0===i}}else for(;e;){if("none"===getComputedStyle(e).display)return!0;e=e.parentElement}return!1}(t,e.displayCheck)||function(e){return"DETAILS"===e.tagName&&Array.prototype.slice.apply(e.children).some((function(e){return"SUMMARY"===e.tagName}))}(t)||function(e){if(i(e)||"SELECT"===e.tagName||"TEXTAREA"===e.tagName||"BUTTON"===e.tagName)for(var t=e.parentElement;t;){if("FIELDSET"===t.tagName&&t.disabled){for(var n=0;n<t.children.length;n++){var r=t.children.item(n);if("LEGEND"===r.tagName)return!r.contains(e)}return!0}t=t.parentElement}return!1}(t))},c=function(e,t){return!(!l(e,t)||u(t)||o(t)<0)},d=e.concat("iframe").join(",");exports.focusable=function(e,t){return r(e,(t=t||{}).includeContainer,l.bind(null,t))},exports.isFocusable=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==n.call(e,d)&&l(t,e)},exports.isTabbable=function(e,r){if(r=r||{},!e)throw new Error("No node provided");return!1!==n.call(e,t)&&c(r,e)},exports.tabbable=function(e,t){var n=[],i=[];return r(e,(t=t||{}).includeContainer,c.bind(null,t)).forEach((function(e,t){var r=o(e);0===r?n.push(e):i.push({documentOrder:t,tabIndex:r,node:e})})),i.sort(a).map((function(e){return e.node})).concat(n)}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=["input","select","textarea","a[href]","button","[tabindex]:not(slot)","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary:first-of-type","details"],t=e.join(","),n="undefined"==typeof Element,r=n?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,o=!n&&Element.prototype.getRootNode?function(e){return e.getRootNode()}:function(e){return e.ownerDocument},a=function(e,n,o){var a=Array.prototype.slice.apply(e.querySelectorAll(t));return n&&r.call(e,t)&&a.unshift(e),a=a.filter(o)},i=function e(n,o,a){for(var i=[],u=Array.from(n);u.length;){var l=u.shift();if("SLOT"===l.tagName){var c=l.assignedElements(),d=e(c.length?c:l.children,!0,a);a.flatten?i.push.apply(i,d):i.push({scope:l,candidates:d})}else{r.call(l,t)&&a.filter(l)&&(o||!n.includes(l))&&i.push(l);var s=l.shadowRoot||a.getShadowRoot(l);if(s){var f=e(!0===s?l.children:s.children,!0,a);a.flatten?i.push.apply(i,f):i.push({scope:l,candidates:f})}else u.unshift.apply(u,l.children)}}return i},u=function(e,t){var n=parseInt(e.getAttribute("tabindex"),10);return isNaN(n)?function(e){return"true"===e.contentEditable}(e)?0:!t&&"AUDIO"!==e.nodeName&&"VIDEO"!==e.nodeName&&"DETAILS"!==e.nodeName||null!==e.getAttribute("tabindex")?e.tabIndex:0:n},l=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},c=function(e){return"INPUT"===e.tagName},d=function(e){return function(e){return c(e)&&"radio"===e.type}(e)&&!function(e){if(!e.name)return!0;var t,n=e.form||o(e),r=function(e){return n.querySelectorAll('input[type="radio"][name="'+e+'"]')};if("undefined"!=typeof window&&void 0!==window.CSS&&"function"==typeof window.CSS.escape)t=r(window.CSS.escape(e.name));else try{t=r(e.name)}catch(e){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",e.message),!1}var a=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]}(t,e.form);return!a||a===e}(e)},s=function(){},f=function(e){var t=e.getBoundingClientRect(),n=t.width,r=t.height;return 0===n&&0===r},p=function(e,t){return!(t.disabled||function(e){return c(e)&&"hidden"===e.type}(t)||function(e,t){var n=t.displayCheck,a=t.getShadowRoot,i=void 0===a?s:a;if("hidden"===getComputedStyle(e).visibility)return!0;var u=r.call(e,"details>summary:first-of-type")?e.parentElement:e;if(r.call(u,"details:not([open]) *"))return!0;if(n&&"full"!==n){if("non-zero-area"===n)return f(e)}else for(;e;){if("none"===getComputedStyle(e).display)return!0;var l=e.parentElement,c=o(e);if(l&&!l.shadowRoot&&i(l))return f(e);e=e.assignedSlot?e.assignedSlot:l||c===e.ownerDocument?l:c.host}return!1}(t,e)||function(e){return"DETAILS"===e.tagName&&Array.prototype.slice.apply(e.children).some((function(e){return"SUMMARY"===e.tagName}))}(t)||function(e){if(c(e)||"SELECT"===e.tagName||"TEXTAREA"===e.tagName||"BUTTON"===e.tagName)for(var t=e.parentElement;t;){if("FIELDSET"===t.tagName&&t.disabled){for(var n=0;n<t.children.length;n++){var r=t.children.item(n);if("LEGEND"===r.tagName)return!r.contains(e)}return!0}t=t.parentElement}return!1}(t))},h=function(e,t){return!(!p(e,t)||d(t)||u(t)<0)},m=e.concat("iframe").join(",");exports.focusable=function(e,t){return(t=t||{}).getShadowRoot?i([e],t.includeContainer,{filter:p.bind(null,t),flatten:!0,getShadowRoot:t.getShadowRoot}):a(e,t.includeContainer,p.bind(null,t))},exports.isFocusable=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==r.call(e,m)&&p(t,e)},exports.isTabbable=function(e,n){if(n=n||{},!e)throw new Error("No node provided");return!1!==r.call(e,t)&&h(n,e)},exports.tabbable=function(e,t){return function e(t){var n=[],r=[];return t.forEach((function(t,o){var a=!!t.scope,i=a?t.scope:t,l=u(i,a),c=a?e(t.candidates):i;0===l?a?n.push.apply(n,c):n.push(i):r.push({documentOrder:o,tabIndex:l,item:t,isScope:a,content:c})})),r.sort(l).reduce((function(e,t){return t.isScope?e.push.apply(e,t.content):e.push(t.content),e}),[]).concat(n)}((t=t||{}).getShadowRoot?i([e],t.includeContainer,{filter:h.bind(null,t),flatten:!1,getShadowRoot:t.getShadowRoot}):a(e,t.includeContainer,h.bind(null,t)))}; | ||
//# sourceMappingURL=index.min.js.map |
/*! | ||
* tabbable 5.2.1 | ||
* tabbable 5.3.0-beta.0 | ||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE | ||
@@ -13,8 +13,20 @@ */ | ||
exports.noConflict = function () { global.tabbable = current; return exports; }; | ||
}())); | ||
}(this, (function (exports) { 'use strict'; | ||
})()); | ||
})(this, (function (exports) { 'use strict'; | ||
var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details']; | ||
var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]:not(slot)', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details']; | ||
var candidateSelector = /* #__PURE__ */candidateSelectors.join(','); | ||
var matches = typeof Element === 'undefined' ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var NoElement = typeof Element === 'undefined'; | ||
var matches = NoElement ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
var getRootNode = !NoElement && Element.prototype.getRootNode ? function (element) { | ||
return element.getRootNode(); | ||
} : function (element) { | ||
return element.ownerDocument; | ||
}; | ||
/** | ||
* @param {Element} el container to check in | ||
* @param {boolean} includeContainer add container to check | ||
* @param {(node: Element) => boolean} filter filter candidates | ||
* @returns {Element[]} | ||
*/ | ||
@@ -31,3 +43,83 @@ var getCandidates = function getCandidates(el, includeContainer, filter) { | ||
}; | ||
/** | ||
* @callback GetShadowRoot | ||
* @param {Element} element to check for shadow root | ||
* @returns {ShadowRoot|boolean} ShadowRoot if available or boolean indicating if a shadowRoot is attached but not available. | ||
*/ | ||
/** | ||
* @typedef {Object} CandidatesScope | ||
* @property {Element} scope contains inner candidates | ||
* @property {Element[]} candidates | ||
*/ | ||
/** | ||
* @typedef {Object} IterativeOptions | ||
* @property {GetShadowRoot} getShadowRoot returns the shadow root of an element or a boolean stating if it has a shadow root | ||
* @property {(node: Element) => boolean} filter filter candidates | ||
* @property {boolean} flatten if true then result will flatten any CandidatesScope into the returned list | ||
*/ | ||
/** | ||
* @param {Element[]} elements list of element containers to match candidates from | ||
* @param {boolean} includeContainer add container list to check | ||
* @param {IterativeOptions} options | ||
* @returns {Array.<Element|CandidatesScope>} | ||
*/ | ||
var getCandidatesIteratively = function getCandidatesIteratively(elements, includeContainer, options) { | ||
var candidates = []; | ||
var elementsToCheck = Array.from(elements); | ||
while (elementsToCheck.length) { | ||
var element = elementsToCheck.shift(); | ||
if (element.tagName === 'SLOT') { | ||
// add shadow dom slot scope (slot itself cannot be focusable) | ||
var assigned = element.assignedElements(); | ||
var content = assigned.length ? assigned : element.children; | ||
var nestedCandidates = getCandidatesIteratively(content, true, options); | ||
if (options.flatten) { | ||
candidates.push.apply(candidates, nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: nestedCandidates | ||
}); | ||
} | ||
} else { | ||
// check candidate element | ||
var validCandidate = matches.call(element, candidateSelector); | ||
if (validCandidate && options.filter(element) && (includeContainer || !elements.includes(element))) { | ||
candidates.push(element); | ||
} // iterate over content | ||
var shadowRoot = element.shadowRoot || options.getShadowRoot(element); | ||
if (shadowRoot) { | ||
// add shadow dom scope | ||
var _nestedCandidates = getCandidatesIteratively(shadowRoot === true ? element.children : shadowRoot.children, true, options); | ||
if (options.flatten) { | ||
candidates.push.apply(candidates, _nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: _nestedCandidates | ||
}); | ||
} | ||
} else { | ||
// add light dom scope | ||
elementsToCheck.unshift.apply(elementsToCheck, element.children); | ||
} | ||
} | ||
} | ||
return candidates; | ||
}; | ||
var isContentEditable = function isContentEditable(node) { | ||
@@ -37,3 +129,3 @@ return node.contentEditable === 'true'; | ||
var getTabindex = function getTabindex(node) { | ||
var getTabindex = function getTabindex(node, isScope) { | ||
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); | ||
@@ -54,5 +146,9 @@ | ||
// order, consider their tab index to be 0. | ||
// | ||
// isScope is positive for custom element with shadow root or slot that by default | ||
// have tabIndex -1, but need to be sorted by document order in order for their | ||
// content to be inserted in the correct position | ||
if ((node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { | ||
if ((isScope || node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) { | ||
return 0; | ||
@@ -96,3 +192,3 @@ } | ||
var radioScope = node.form || node.ownerDocument; | ||
var radioScope = node.form || getRootNode(node); | ||
@@ -129,3 +225,17 @@ var queryRadios = function queryRadios(name) { | ||
var isHidden = function isHidden(node, displayCheck) { | ||
var noop = function noop() {}; | ||
var isZeroArea = function isZeroArea(node) { | ||
var _node$getBoundingClie = node.getBoundingClientRect(), | ||
width = _node$getBoundingClie.width, | ||
height = _node$getBoundingClie.height; | ||
return width === 0 && height === 0; | ||
}; | ||
var isHidden = function isHidden(node, _ref) { | ||
var displayCheck = _ref.displayCheck, | ||
_ref$getShadowRoot = _ref.getShadowRoot, | ||
getShadowRoot = _ref$getShadowRoot === void 0 ? noop : _ref$getShadowRoot; | ||
if (getComputedStyle(node).visibility === 'hidden') { | ||
@@ -148,10 +258,21 @@ return true; | ||
node = node.parentElement; | ||
var parentElement = node.parentElement; | ||
var rootNode = getRootNode(node); | ||
if (parentElement && !parentElement.shadowRoot && getShadowRoot(parentElement)) { | ||
// fallback to zero area size for unreachable shadow dom | ||
return isZeroArea(node); | ||
} else if (node.assignedSlot) { | ||
// iterate up slot | ||
node = node.assignedSlot; | ||
} else if (!parentElement && rootNode !== node.ownerDocument) { | ||
// cross shadow boundary | ||
node = rootNode.host; | ||
} else { | ||
// iterate up normal dom | ||
node = parentElement; | ||
} | ||
} | ||
} else if (displayCheck === 'non-zero-area') { | ||
var _node$getBoundingClie = node.getBoundingClientRect(), | ||
width = _node$getBoundingClie.width, | ||
height = _node$getBoundingClie.height; | ||
return width === 0 && height === 0; | ||
return isZeroArea(node); | ||
} | ||
@@ -204,3 +325,3 @@ | ||
var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(options, node) { | ||
if (node.disabled || isHiddenInput(node) || isHidden(node, options.displayCheck) || // For a details element with a summary, the summary element gets the focus | ||
if (node.disabled || isHiddenInput(node) || isHidden(node, options) || // For a details element with a summary, the summary element gets the focus | ||
isDetailsWithSummary(node) || isDisabledFromFieldset(node)) { | ||
@@ -220,13 +341,19 @@ return false; | ||
}; | ||
/** | ||
* @param {Array.<Element|CandidatesScope>} candidates | ||
* @returns Element[] | ||
*/ | ||
var tabbable = function tabbable(el, options) { | ||
options = options || {}; | ||
var sortByOrder = function sortByOrder(candidates) { | ||
var regularTabbables = []; | ||
var orderedTabbables = []; | ||
var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); | ||
candidates.forEach(function (candidate, i) { | ||
var candidateTabindex = getTabindex(candidate); | ||
candidates.forEach(function (item, i) { | ||
var isScope = !!item.scope; | ||
var element = isScope ? item.scope : item; | ||
var candidateTabindex = getTabindex(element, isScope); | ||
var elements = isScope ? sortByOrder(item.candidates) : element; | ||
if (candidateTabindex === 0) { | ||
regularTabbables.push(candidate); | ||
isScope ? regularTabbables.push.apply(regularTabbables, elements) : regularTabbables.push(element); | ||
} else { | ||
@@ -236,15 +363,45 @@ orderedTabbables.push({ | ||
tabIndex: candidateTabindex, | ||
node: candidate | ||
item: item, | ||
isScope: isScope, | ||
content: elements | ||
}); | ||
} | ||
}); | ||
var tabbableNodes = orderedTabbables.sort(sortOrderedTabbables).map(function (a) { | ||
return a.node; | ||
}).concat(regularTabbables); | ||
return tabbableNodes; | ||
return orderedTabbables.sort(sortOrderedTabbables).reduce(function (acc, sortable) { | ||
sortable.isScope ? acc.push.apply(acc, sortable.content) : acc.push(sortable.content); | ||
return acc; | ||
}, []).concat(regularTabbables); | ||
}; | ||
var tabbable = function tabbable(el, options) { | ||
options = options || {}; | ||
var candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorTabbable.bind(null, options), | ||
flatten: false, | ||
getShadowRoot: options.getShadowRoot | ||
}); | ||
} else { | ||
candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options)); | ||
} | ||
return sortByOrder(candidates); | ||
}; | ||
var focusable = function focusable(el, options) { | ||
options = options || {}; | ||
var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); | ||
var candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorFocusable.bind(null, options), | ||
flatten: true, | ||
getShadowRoot: options.getShadowRoot | ||
}); | ||
} else { | ||
candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options)); | ||
} | ||
return candidates; | ||
@@ -290,3 +447,3 @@ }; | ||
}))); | ||
})); | ||
//# sourceMappingURL=index.umd.js.map |
/*! | ||
* tabbable 5.2.1 | ||
* tabbable 5.3.0-beta.0 | ||
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE | ||
*/ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.tabbable,r=e.tabbable={};t(r),r.noConflict=function(){return e.tabbable=n,r}}())}(this,(function(e){"use strict";var t=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary:first-of-type","details"],n=t.join(","),r="undefined"==typeof Element?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,o=function(e,t,o){var i=Array.prototype.slice.apply(e.querySelectorAll(n));return t&&r.call(e,n)&&i.unshift(e),i=i.filter(o)},i=function(e){var t=parseInt(e.getAttribute("tabindex"),10);return isNaN(t)?function(e){return"true"===e.contentEditable}(e)?0:"AUDIO"!==e.nodeName&&"VIDEO"!==e.nodeName&&"DETAILS"!==e.nodeName||null!==e.getAttribute("tabindex")?e.tabIndex:0:t},a=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},u=function(e){return"INPUT"===e.tagName},l=function(e){return function(e){return u(e)&&"radio"===e.type}(e)&&!function(e){if(!e.name)return!0;var t,n=e.form||e.ownerDocument,r=function(e){return n.querySelectorAll('input[type="radio"][name="'+e+'"]')};if("undefined"!=typeof window&&void 0!==window.CSS&&"function"==typeof window.CSS.escape)t=r(window.CSS.escape(e.name));else try{t=r(e.name)}catch(e){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",e.message),!1}var o=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]}(t,e.form);return!o||o===e}(e)},c=function(e,t){return!(t.disabled||function(e){return u(e)&&"hidden"===e.type}(t)||function(e,t){if("hidden"===getComputedStyle(e).visibility)return!0;var n=r.call(e,"details>summary:first-of-type")?e.parentElement:e;if(r.call(n,"details:not([open]) *"))return!0;if(t&&"full"!==t){if("non-zero-area"===t){var o=e.getBoundingClientRect(),i=o.width,a=o.height;return 0===i&&0===a}}else for(;e;){if("none"===getComputedStyle(e).display)return!0;e=e.parentElement}return!1}(t,e.displayCheck)||function(e){return"DETAILS"===e.tagName&&Array.prototype.slice.apply(e.children).some((function(e){return"SUMMARY"===e.tagName}))}(t)||function(e){if(u(e)||"SELECT"===e.tagName||"TEXTAREA"===e.tagName||"BUTTON"===e.tagName)for(var t=e.parentElement;t;){if("FIELDSET"===t.tagName&&t.disabled){for(var n=0;n<t.children.length;n++){var r=t.children.item(n);if("LEGEND"===r.tagName)return!r.contains(e)}return!0}t=t.parentElement}return!1}(t))},d=function(e,t){return!(!c(e,t)||l(t)||i(t)<0)},f=t.concat("iframe").join(",");e.focusable=function(e,t){return o(e,(t=t||{}).includeContainer,c.bind(null,t))},e.isFocusable=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==r.call(e,f)&&c(t,e)},e.isTabbable=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==r.call(e,n)&&d(t,e)},e.tabbable=function(e,t){var n=[],r=[];return o(e,(t=t||{}).includeContainer,d.bind(null,t)).forEach((function(e,t){var o=i(e);0===o?n.push(e):r.push({documentOrder:t,tabIndex:o,node:e})})),r.sort(a).map((function(e){return e.node})).concat(n)},Object.defineProperty(e,"__esModule",{value:!0})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.tabbable,o=e.tabbable={};t(o),o.noConflict=function(){return e.tabbable=n,o}}())}(this,(function(e){"use strict";var t=["input","select","textarea","a[href]","button","[tabindex]:not(slot)","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary:first-of-type","details"],n=t.join(","),o="undefined"==typeof Element,r=o?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,a=!o&&Element.prototype.getRootNode?function(e){return e.getRootNode()}:function(e){return e.ownerDocument},i=function(e,t,o){var a=Array.prototype.slice.apply(e.querySelectorAll(n));return t&&r.call(e,n)&&a.unshift(e),a=a.filter(o)},u=function e(t,o,a){for(var i=[],u=Array.from(t);u.length;){var l=u.shift();if("SLOT"===l.tagName){var c=l.assignedElements(),d=e(c.length?c:l.children,!0,a);a.flatten?i.push.apply(i,d):i.push({scope:l,candidates:d})}else{r.call(l,n)&&a.filter(l)&&(o||!t.includes(l))&&i.push(l);var f=l.shadowRoot||a.getShadowRoot(l);if(f){var s=e(!0===f?l.children:f.children,!0,a);a.flatten?i.push.apply(i,s):i.push({scope:l,candidates:s})}else u.unshift.apply(u,l.children)}}return i},l=function(e,t){var n=parseInt(e.getAttribute("tabindex"),10);return isNaN(n)?function(e){return"true"===e.contentEditable}(e)?0:!t&&"AUDIO"!==e.nodeName&&"VIDEO"!==e.nodeName&&"DETAILS"!==e.nodeName||null!==e.getAttribute("tabindex")?e.tabIndex:0:n},c=function(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex},d=function(e){return"INPUT"===e.tagName},f=function(e){return function(e){return d(e)&&"radio"===e.type}(e)&&!function(e){if(!e.name)return!0;var t,n=e.form||a(e),o=function(e){return n.querySelectorAll('input[type="radio"][name="'+e+'"]')};if("undefined"!=typeof window&&void 0!==window.CSS&&"function"==typeof window.CSS.escape)t=o(window.CSS.escape(e.name));else try{t=o(e.name)}catch(e){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",e.message),!1}var r=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]}(t,e.form);return!r||r===e}(e)},s=function(){},p=function(e){var t=e.getBoundingClientRect(),n=t.width,o=t.height;return 0===n&&0===o},h=function(e,t){return!(t.disabled||function(e){return d(e)&&"hidden"===e.type}(t)||function(e,t){var n=t.displayCheck,o=t.getShadowRoot,i=void 0===o?s:o;if("hidden"===getComputedStyle(e).visibility)return!0;var u=r.call(e,"details>summary:first-of-type")?e.parentElement:e;if(r.call(u,"details:not([open]) *"))return!0;if(n&&"full"!==n){if("non-zero-area"===n)return p(e)}else for(;e;){if("none"===getComputedStyle(e).display)return!0;var l=e.parentElement,c=a(e);if(l&&!l.shadowRoot&&i(l))return p(e);e=e.assignedSlot?e.assignedSlot:l||c===e.ownerDocument?l:c.host}return!1}(t,e)||function(e){return"DETAILS"===e.tagName&&Array.prototype.slice.apply(e.children).some((function(e){return"SUMMARY"===e.tagName}))}(t)||function(e){if(d(e)||"SELECT"===e.tagName||"TEXTAREA"===e.tagName||"BUTTON"===e.tagName)for(var t=e.parentElement;t;){if("FIELDSET"===t.tagName&&t.disabled){for(var n=0;n<t.children.length;n++){var o=t.children.item(n);if("LEGEND"===o.tagName)return!o.contains(e)}return!0}t=t.parentElement}return!1}(t))},m=function(e,t){return!(!h(e,t)||f(t)||l(t)<0)},b=t.concat("iframe").join(",");e.focusable=function(e,t){return(t=t||{}).getShadowRoot?u([e],t.includeContainer,{filter:h.bind(null,t),flatten:!0,getShadowRoot:t.getShadowRoot}):i(e,t.includeContainer,h.bind(null,t))},e.isFocusable=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==r.call(e,b)&&h(t,e)},e.isTabbable=function(e,t){if(t=t||{},!e)throw new Error("No node provided");return!1!==r.call(e,n)&&m(t,e)},e.tabbable=function(e,t){return function e(t){var n=[],o=[];return t.forEach((function(t,r){var a=!!t.scope,i=a?t.scope:t,u=l(i,a),c=a?e(t.candidates):i;0===u?a?n.push.apply(n,c):n.push(i):o.push({documentOrder:r,tabIndex:u,item:t,isScope:a,content:c})})),o.sort(c).reduce((function(e,t){return t.isScope?e.push.apply(e,t.content):e.push(t.content),e}),[]).concat(n)}((t=t||{}).getShadowRoot?u([e],t.includeContainer,{filter:m.bind(null,t),flatten:!1,getShadowRoot:t.getShadowRoot}):i(e,t.includeContainer,m.bind(null,t)))},Object.defineProperty(e,"__esModule",{value:!0})})); | ||
//# sourceMappingURL=index.umd.min.js.map |
@@ -5,2 +5,3 @@ type FocusableElement = HTMLElement | SVGElement; | ||
displayCheck?: 'full' | 'non-zero-area' | 'none'; | ||
getShadowRoot?: (node: FocusableElement) => ShadowRoot | boolean | undefined; | ||
}; | ||
@@ -7,0 +8,0 @@ |
{ | ||
"name": "tabbable", | ||
"version": "5.2.1", | ||
"version": "5.3.0-beta.0", | ||
"description": "Returns an array of all tabbable DOM nodes within a containing node.", | ||
@@ -54,17 +54,17 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@babel/core": "^7.15.0", | ||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.2", | ||
"@babel/plugin-proposal-optional-chaining": "^7.14.2", | ||
"@babel/preset-env": "^7.15.0", | ||
"@changesets/cli": "^2.16.0", | ||
"@cypress/code-coverage": "^3.9.10", | ||
"@babel/core": "^7.16.12", | ||
"@babel/eslint-parser": "^7.16.5", | ||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", | ||
"@babel/plugin-proposal-optional-chaining": "^7.16.0", | ||
"@babel/preset-env": "^7.16.11", | ||
"@changesets/cli": "^2.20.0", | ||
"@cypress/code-coverage": "^3.9.12", | ||
"@rollup/plugin-babel": "^5.3.0", | ||
"@rollup/plugin-commonjs": "^20.0.0", | ||
"@rollup/plugin-node-resolve": "^13.0.4", | ||
"@testing-library/dom": "^8.1.0", | ||
"@testing-library/jest-dom": "^5.14.1", | ||
"@types/node": "^16.4.13", | ||
"@rollup/plugin-commonjs": "^21.0.1", | ||
"@rollup/plugin-node-resolve": "^13.1.3", | ||
"@testing-library/dom": "^8.11.3", | ||
"@testing-library/jest-dom": "^5.16.1", | ||
"@types/node": "^17.0.12", | ||
"all-contributors-cli": "^6.20.0", | ||
"babel-eslint": "^10.1.0", | ||
"babel-jest": "^27.0.6", | ||
"babel-jest": "^27.4.6", | ||
"brfs": "^2.0.2", | ||
@@ -74,17 +74,17 @@ "browserify": "^17.0.0", | ||
"cross-env": "^7.0.3", | ||
"cypress": "^8.2.0", | ||
"eslint": "^7.32.0", | ||
"cypress": "^9.3.1", | ||
"eslint": "^8.7.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint-plugin-cypress": "^2.11.3", | ||
"eslint-plugin-import": "^2.24.0", | ||
"jest": "^27.0.6", | ||
"jest-watch-typeahead": "^0.6.4", | ||
"eslint-plugin-cypress": "^2.12.1", | ||
"eslint-plugin-import": "^2.25.4", | ||
"jest": "^27.4.7", | ||
"jest-watch-typeahead": "^1.0.0", | ||
"onchange": "^7.1.0", | ||
"prettier": "^2.3.2", | ||
"rollup": "^2.56.2", | ||
"prettier": "^2.5.1", | ||
"rollup": "^2.66.1", | ||
"rollup-plugin-sourcemaps": "^0.6.3", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"typescript": "^4.3.5", | ||
"typescript": "^4.5.5", | ||
"watchify": "^4.0.0" | ||
} | ||
} |
@@ -94,2 +94,12 @@ # tabbable [![CI](https://github.com/focus-trap/tabbable/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/focus-trap/tabbable/actions?query=workflow:CI+branch:master) [![Codecov](https://img.shields.io/codecov/c/github/focus-trap/tabbable)](https://codecov.io/gh/focus-trap/tabbable) [![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE) | ||
##### getShadowRoot | ||
Type: `(node: FocusableElement) => ShadowRoot | boolean | undefined` | ||
Returns the node's `ShadowRoot` if available, or a `boolean` indicating if a `ShadowRoot` is attached but not available. `node` will be a descendent of the `rootNode` given to `tabbable()`. | ||
If `true` is returned, Tabbable assumes a closed `ShadowRoot` is attached and will iterate the `node`'s children for additional tabbable/focusable candidates. | ||
If a falsy value is returned, all children will be ignored as candidates. | ||
### isTabbable | ||
@@ -96,0 +106,0 @@ |
232
src/index.js
@@ -7,3 +7,3 @@ const candidateSelectors = [ | ||
'button', | ||
'[tabindex]', | ||
'[tabindex]:not(slot)', | ||
'audio[controls]', | ||
@@ -17,9 +17,21 @@ 'video[controls]', | ||
const matches = | ||
typeof Element === 'undefined' | ||
? function () {} | ||
: Element.prototype.matches || | ||
Element.prototype.msMatchesSelector || | ||
Element.prototype.webkitMatchesSelector; | ||
const NoElement = typeof Element === 'undefined'; | ||
const matches = NoElement | ||
? function () {} | ||
: Element.prototype.matches || | ||
Element.prototype.msMatchesSelector || | ||
Element.prototype.webkitMatchesSelector; | ||
const getRootNode = | ||
!NoElement && Element.prototype.getRootNode | ||
? (element) => element.getRootNode() | ||
: (element) => element.ownerDocument; | ||
/** | ||
* @param {Element} el container to check in | ||
* @param {boolean} includeContainer add container to check | ||
* @param {(node: Element) => boolean} filter filter candidates | ||
* @returns {Element[]} | ||
*/ | ||
const getCandidates = function (el, includeContainer, filter) { | ||
@@ -36,2 +48,85 @@ let candidates = Array.prototype.slice.apply( | ||
/** | ||
* @callback GetShadowRoot | ||
* @param {Element} element to check for shadow root | ||
* @returns {ShadowRoot|boolean} ShadowRoot if available or boolean indicating if a shadowRoot is attached but not available. | ||
*/ | ||
/** | ||
* @typedef {Object} CandidatesScope | ||
* @property {Element} scope contains inner candidates | ||
* @property {Element[]} candidates | ||
*/ | ||
/** | ||
* @typedef {Object} IterativeOptions | ||
* @property {GetShadowRoot} getShadowRoot returns the shadow root of an element or a boolean stating if it has a shadow root | ||
* @property {(node: Element) => boolean} filter filter candidates | ||
* @property {boolean} flatten if true then result will flatten any CandidatesScope into the returned list | ||
*/ | ||
/** | ||
* @param {Element[]} elements list of element containers to match candidates from | ||
* @param {boolean} includeContainer add container list to check | ||
* @param {IterativeOptions} options | ||
* @returns {Array.<Element|CandidatesScope>} | ||
*/ | ||
const getCandidatesIteratively = function ( | ||
elements, | ||
includeContainer, | ||
options | ||
) { | ||
const candidates = []; | ||
const elementsToCheck = Array.from(elements); | ||
while (elementsToCheck.length) { | ||
const element = elementsToCheck.shift(); | ||
if (element.tagName === 'SLOT') { | ||
// add shadow dom slot scope (slot itself cannot be focusable) | ||
const assigned = element.assignedElements(); | ||
const content = assigned.length ? assigned : element.children; | ||
const nestedCandidates = getCandidatesIteratively(content, true, options); | ||
if (options.flatten) { | ||
candidates.push(...nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: nestedCandidates, | ||
}); | ||
} | ||
} else { | ||
// check candidate element | ||
const validCandidate = matches.call(element, candidateSelector); | ||
if ( | ||
validCandidate && | ||
options.filter(element) && | ||
(includeContainer || !elements.includes(element)) | ||
) { | ||
candidates.push(element); | ||
} | ||
// iterate over content | ||
const shadowRoot = element.shadowRoot || options.getShadowRoot(element); | ||
if (shadowRoot) { | ||
// add shadow dom scope | ||
const nestedCandidates = getCandidatesIteratively( | ||
shadowRoot === true ? element.children : shadowRoot.children, | ||
true, | ||
options | ||
); | ||
if (options.flatten) { | ||
candidates.push(...nestedCandidates); | ||
} else { | ||
candidates.push({ | ||
scope: element, | ||
candidates: nestedCandidates, | ||
}); | ||
} | ||
} else { | ||
// add light dom scope | ||
elementsToCheck.unshift(...element.children); | ||
} | ||
} | ||
} | ||
return candidates; | ||
}; | ||
const isContentEditable = function (node) { | ||
@@ -41,3 +136,3 @@ return node.contentEditable === 'true'; | ||
const getTabindex = function (node) { | ||
const getTabindex = function (node, isScope) { | ||
const tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); | ||
@@ -60,4 +155,9 @@ | ||
// order, consider their tab index to be 0. | ||
// | ||
// isScope is positive for custom element with shadow root or slot that by default | ||
// have tabIndex -1, but need to be sorted by document order in order for their | ||
// content to be inserted in the correct position | ||
if ( | ||
(node.nodeName === 'AUDIO' || | ||
(isScope || | ||
node.nodeName === 'AUDIO' || | ||
node.nodeName === 'VIDEO' || | ||
@@ -108,4 +208,3 @@ node.nodeName === 'DETAILS') && | ||
} | ||
const radioScope = node.form || node.ownerDocument; | ||
const radioScope = node.form || getRootNode(node); | ||
const queryRadios = function (name) { | ||
@@ -149,3 +248,8 @@ return radioScope.querySelectorAll( | ||
const isHidden = function (node, displayCheck) { | ||
const noop = () => {}; | ||
const isZeroArea = function (node) { | ||
const { width, height } = node.getBoundingClientRect(); | ||
return width === 0 && height === 0; | ||
}; | ||
const isHidden = function (node, { displayCheck, getShadowRoot = noop }) { | ||
if (getComputedStyle(node).visibility === 'hidden') { | ||
@@ -160,2 +264,3 @@ return true; | ||
} | ||
if (!displayCheck || displayCheck === 'full') { | ||
@@ -166,7 +271,24 @@ while (node) { | ||
} | ||
node = node.parentElement; | ||
const parentElement = node.parentElement; | ||
const rootNode = getRootNode(node); | ||
if ( | ||
parentElement && | ||
!parentElement.shadowRoot && | ||
getShadowRoot(parentElement) | ||
) { | ||
// fallback to zero area size for unreachable shadow dom | ||
return isZeroArea(node); | ||
} else if (node.assignedSlot) { | ||
// iterate up slot | ||
node = node.assignedSlot; | ||
} else if (!parentElement && rootNode !== node.ownerDocument) { | ||
// cross shadow boundary | ||
node = rootNode.host; | ||
} else { | ||
// iterate up normal dom | ||
node = parentElement; | ||
} | ||
} | ||
} else if (displayCheck === 'non-zero-area') { | ||
const { width, height } = node.getBoundingClientRect(); | ||
return width === 0 && height === 0; | ||
return isZeroArea(node); | ||
} | ||
@@ -225,3 +347,3 @@ | ||
isHiddenInput(node) || | ||
isHidden(node, options.displayCheck) || | ||
isHidden(node, options) || | ||
// For a details element with a summary, the summary element gets the focus | ||
@@ -247,18 +369,18 @@ isDetailsWithSummary(node) || | ||
const tabbable = function (el, options) { | ||
options = options || {}; | ||
/** | ||
* @param {Array.<Element|CandidatesScope>} candidates | ||
* @returns Element[] | ||
*/ | ||
const sortByOrder = function (candidates) { | ||
const regularTabbables = []; | ||
const orderedTabbables = []; | ||
const candidates = getCandidates( | ||
el, | ||
options.includeContainer, | ||
isNodeMatchingSelectorTabbable.bind(null, options) | ||
); | ||
candidates.forEach(function (candidate, i) { | ||
const candidateTabindex = getTabindex(candidate); | ||
candidates.forEach(function (item, i) { | ||
const isScope = !!item.scope; | ||
const element = isScope ? item.scope : item; | ||
const candidateTabindex = getTabindex(element, isScope); | ||
const elements = isScope ? sortByOrder(item.candidates) : element; | ||
if (candidateTabindex === 0) { | ||
regularTabbables.push(candidate); | ||
isScope | ||
? regularTabbables.push(...elements) | ||
: regularTabbables.push(element); | ||
} else { | ||
@@ -268,3 +390,5 @@ orderedTabbables.push({ | ||
tabIndex: candidateTabindex, | ||
node: candidate, | ||
item: item, | ||
isScope: isScope, | ||
content: elements, | ||
}); | ||
@@ -274,8 +398,31 @@ } | ||
const tabbableNodes = orderedTabbables | ||
return orderedTabbables | ||
.sort(sortOrderedTabbables) | ||
.map((a) => a.node) | ||
.reduce((acc, sortable) => { | ||
sortable.isScope | ||
? acc.push(...sortable.content) | ||
: acc.push(sortable.content); | ||
return acc; | ||
}, []) | ||
.concat(regularTabbables); | ||
}; | ||
return tabbableNodes; | ||
const tabbable = function (el, options) { | ||
options = options || {}; | ||
let candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorTabbable.bind(null, options), | ||
flatten: false, | ||
getShadowRoot: options.getShadowRoot, | ||
}); | ||
} else { | ||
candidates = getCandidates( | ||
el, | ||
options.includeContainer, | ||
isNodeMatchingSelectorTabbable.bind(null, options) | ||
); | ||
} | ||
return sortByOrder(candidates); | ||
}; | ||
@@ -286,7 +433,16 @@ | ||
const candidates = getCandidates( | ||
el, | ||
options.includeContainer, | ||
isNodeMatchingSelectorFocusable.bind(null, options) | ||
); | ||
let candidates; | ||
if (options.getShadowRoot) { | ||
candidates = getCandidatesIteratively([el], options.includeContainer, { | ||
filter: isNodeMatchingSelectorFocusable.bind(null, options), | ||
flatten: true, | ||
getShadowRoot: options.getShadowRoot, | ||
}); | ||
} else { | ||
candidates = getCandidates( | ||
el, | ||
options.includeContainer, | ||
isNodeMatchingSelectorFocusable.bind(null, options) | ||
); | ||
} | ||
@@ -293,0 +449,0 @@ return candidates; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
241826
1540
223
1