2024-01-05 12:14:38 +00:00
/ * !
2026-02-11 16:20:26 +00:00
* tabbable 6.4 . 0
2024-01-05 12:14:38 +00:00
* @ license MIT , https : //github.com/focus-trap/tabbable/blob/master/LICENSE
* /
( function ( global , factory ) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory ( exports ) :
typeof define === 'function' && define . amd ? define ( [ 'exports' ] , factory ) :
( global = typeof globalThis !== 'undefined' ? globalThis : global || self , ( function ( ) {
var current = global . tabbable ;
var exports = global . tabbable = { } ;
factory ( exports ) ;
exports . noConflict = function ( ) { global . tabbable = current ; return exports ; } ;
} ) ( ) ) ;
} ) ( this , ( function ( exports ) { 'use strict' ;
// NOTE: separate `:not()` selectors has broader browser support than the newer
// `:not([inert], [inert] *)` (Feb 2023)
2026-02-11 16:20:26 +00:00
var candidateSelectors = [ 'input:not([inert]):not([inert] *)' , 'select:not([inert]):not([inert] *)' , 'textarea:not([inert]):not([inert] *)' , 'a[href]:not([inert]):not([inert] *)' , 'button:not([inert]):not([inert] *)' , '[tabindex]:not(slot):not([inert]):not([inert] *)' , 'audio[controls]:not([inert]):not([inert] *)' , 'video[controls]:not([inert]):not([inert] *)' , '[contenteditable]:not([contenteditable="false"]):not([inert]):not([inert] *)' , 'details>summary:first-of-type:not([inert]):not([inert] *)' , 'details:not([inert]):not([inert] *)' ] ;
2024-01-05 12:14:38 +00:00
var candidateSelector = /* #__PURE__ */ candidateSelectors . join ( ',' ) ;
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 ) {
var _element$getRootNode ;
return element === null || element === void 0 ? void 0 : ( _element$getRootNode = element . getRootNode ) === null || _element$getRootNode === void 0 ? void 0 : _element$getRootNode . call ( element ) ;
} : function ( element ) {
return element === null || element === void 0 ? void 0 : element . ownerDocument ;
} ;
/ * *
* Determines if a node is inert or in an inert ancestor .
2026-02-11 16:20:26 +00:00
* @ param { Node } [ node ]
2024-01-05 12:14:38 +00:00
* @ param { boolean } [ lookUp ] If true and ` node ` is not inert , looks up at ancestors to
* see if any of them are inert . If false , only ` node ` itself is considered .
* @ returns { boolean } True if inert itself or by way of being in an inert ancestor .
* False if ` node ` is falsy .
* /
2026-02-11 16:20:26 +00:00
var _isInert = function isInert ( node , lookUp ) {
2024-01-05 12:14:38 +00:00
var _node$getAttribute ;
if ( lookUp === void 0 ) {
lookUp = true ;
}
// CAREFUL: JSDom does not support inert at all, so we can't use the `HTMLElement.inert`
// JS API property; we have to check the attribute, which can either be empty or 'true';
// if it's `null` (not specified) or 'false', it's an active element
var inertAtt = node === null || node === void 0 ? void 0 : ( _node$getAttribute = node . getAttribute ) === null || _node$getAttribute === void 0 ? void 0 : _node$getAttribute . call ( node , 'inert' ) ;
var inert = inertAtt === '' || inertAtt === 'true' ;
// NOTE: this could also be handled with `node.matches('[inert], :is([inert] *)')`
// if it weren't for `matches()` not being a function on shadow roots; the following
// code works for any kind of node
2026-02-11 16:20:26 +00:00
var result = inert || lookUp && node && (
// closest does not exist on shadow roots, so we fall back to a manual
// lookup upward, in case it is not defined.
typeof node . closest === 'function' ? node . closest ( '[inert]' ) : _isInert ( node . parentNode ) ) ;
2024-01-05 12:14:38 +00:00
return result ;
} ;
/ * *
* Determines if a node ' s content is editable .
* @ param { Element } [ node ]
* @ returns True if it 's content-editable; false if it' s not or ` node ` is falsy .
* /
var isContentEditable = function isContentEditable ( node ) {
var _node$getAttribute2 ;
// CAREFUL: JSDom does not support the `HTMLElement.isContentEditable` API so we have
// to use the attribute directly to check for this, which can either be empty or 'true';
// if it's `null` (not specified) or 'false', it's a non-editable element
var attValue = node === null || node === void 0 ? void 0 : ( _node$getAttribute2 = node . getAttribute ) === null || _node$getAttribute2 === void 0 ? void 0 : _node$getAttribute2 . call ( node , 'contenteditable' ) ;
return attValue === '' || attValue === 'true' ;
} ;
/ * *
* @ param { Element } el container to check in
* @ param { boolean } includeContainer add container to check
* @ param { ( node : Element ) => boolean } filter filter candidates
* @ returns { Element [ ] }
* /
var getCandidates = function getCandidates ( el , includeContainer , filter ) {
// even if `includeContainer=false`, we still have to check it for inertness because
2026-02-11 16:20:26 +00:00
// if it's inert (either by itself or via its parent), then all its children are inert
if ( _isInert ( el ) ) {
2024-01-05 12:14:38 +00:00
return [ ] ;
}
var candidates = Array . prototype . slice . apply ( el . querySelectorAll ( candidateSelector ) ) ;
if ( includeContainer && matches . call ( el , candidateSelector ) ) {
candidates . unshift ( el ) ;
}
candidates = candidates . filter ( filter ) ;
return candidates ;
} ;
/ * *
* @ 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 .
* /
/ * *
* @ callback ShadowRootFilter
* @ param { Element } shadowHostNode the element which contains shadow content
* @ returns { boolean } true if a shadow root could potentially contain valid candidates .
* /
/ * *
* @ typedef { Object } CandidateScope
* @ property { Element } scopeParent contains inner candidates
* @ property { Element [ ] } candidates list of candidates found in the scope parent
* /
/ * *
* @ typedef { Object } IterativeOptions
* @ property { GetShadowRoot | boolean } getShadowRoot true if shadow support is enabled ; falsy if not ;
* if a function , implies shadow support is enabled and either returns the shadow root of an element
* or a boolean stating if it has an undisclosed shadow root
* @ property { ( node : Element ) => boolean } filter filter candidates
* @ property { boolean } flatten if true then result will flatten any CandidateScope into the returned list
* @ property { ShadowRootFilter } shadowRootFilter filter shadow roots ;
* /
/ * *
* @ 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 | CandidateScope > }
* /
2026-02-11 16:20:26 +00:00
var _getCandidatesIteratively = function getCandidatesIteratively ( elements , includeContainer , options ) {
2024-01-05 12:14:38 +00:00
var candidates = [ ] ;
var elementsToCheck = Array . from ( elements ) ;
while ( elementsToCheck . length ) {
var element = elementsToCheck . shift ( ) ;
2026-02-11 16:20:26 +00:00
if ( _isInert ( element , false ) ) {
2024-01-05 12:14:38 +00:00
// no need to look up since we're drilling down
// anything inside this container will also be inert
continue ;
}
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 ;
2026-02-11 16:20:26 +00:00
var nestedCandidates = _getCandidatesIteratively ( content , true , options ) ;
2024-01-05 12:14:38 +00:00
if ( options . flatten ) {
candidates . push . apply ( candidates , nestedCandidates ) ;
} else {
candidates . push ( {
scopeParent : 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 shadow content if possible
var shadowRoot = element . shadowRoot ||
// check for an undisclosed shadow
typeof options . getShadowRoot === 'function' && options . getShadowRoot ( element ) ;
// no inert look up because we're already drilling down and checking for inertness
// on the way down, so all containers to this root node should have already been
// vetted as non-inert
2026-02-11 16:20:26 +00:00
var validShadowRoot = ! _isInert ( shadowRoot , false ) && ( ! options . shadowRootFilter || options . shadowRootFilter ( element ) ) ;
2024-01-05 12:14:38 +00:00
if ( shadowRoot && validShadowRoot ) {
// add shadow dom scope IIF a shadow root node was given; otherwise, an undisclosed
// shadow exists, so look at light dom children as fallback BUT create a scope for any
// child candidates found because they're likely slotted elements (elements that are
// children of the web component element (which has the shadow), in the light dom, but
// slotted somewhere _inside_ the undisclosed shadow) -- the scope is created below,
// _after_ we return from this recursive call
2026-02-11 16:20:26 +00:00
var _nestedCandidates = _getCandidatesIteratively ( shadowRoot === true ? element . children : shadowRoot . children , true , options ) ;
2024-01-05 12:14:38 +00:00
if ( options . flatten ) {
candidates . push . apply ( candidates , _nestedCandidates ) ;
} else {
candidates . push ( {
scopeParent : element ,
candidates : _nestedCandidates
} ) ;
}
} else {
// there's not shadow so just dig into the element's (light dom) children
// __without__ giving the element special scope treatment
elementsToCheck . unshift . apply ( elementsToCheck , element . children ) ;
}
}
}
return candidates ;
} ;
/ * *
* @ private
* Determines if the node has an explicitly specified ` tabindex ` attribute .
* @ param { HTMLElement } node
* @ returns { boolean } True if so ; false if not .
* /
var hasTabIndex = function hasTabIndex ( node ) {
return ! isNaN ( parseInt ( node . getAttribute ( 'tabindex' ) , 10 ) ) ;
} ;
/ * *
* Determine the tab index of a given node .
* @ param { HTMLElement } node
* @ returns { number } Tab order ( negative , 0 , or positive number ) .
* @ throws { Error } If ` node ` is falsy .
* /
var getTabIndex = function getTabIndex ( node ) {
if ( ! node ) {
throw new Error ( 'No node provided' ) ;
}
if ( node . tabIndex < 0 ) {
// in Chrome, <details/>, <audio controls/> and <video controls/> elements get a default
// `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM,
// yet they are still part of the regular tab order; in FF, they get a default
// `tabIndex` of 0; since Chrome still puts those elements in the regular tab
// order, consider their tab index to be 0.
// Also browsers do not return `tabIndex` correctly for contentEditable nodes;
// so if they don't have a tabindex attribute specifically set, assume it's 0.
if ( ( /^(AUDIO|VIDEO|DETAILS)$/ . test ( node . tagName ) || isContentEditable ( node ) ) && ! hasTabIndex ( node ) ) {
return 0 ;
}
}
return node . tabIndex ;
} ;
/ * *
* Determine the tab index of a given node _ _for sort order purposes _ _ .
* @ param { HTMLElement } node
* @ param { boolean } [ isScope ] True for a custom element with shadow root or slot that , by default ,
* has tabIndex - 1 , but needs to be sorted by document order in order for its content to be
* inserted into the correct sort position .
* @ returns { number } Tab order ( negative , 0 , or positive number ) .
* /
var getSortOrderTabIndex = function getSortOrderTabIndex ( node , isScope ) {
var tabIndex = getTabIndex ( node ) ;
if ( tabIndex < 0 && isScope && ! hasTabIndex ( node ) ) {
return 0 ;
}
return tabIndex ;
} ;
var sortOrderedTabbables = function sortOrderedTabbables ( a , b ) {
return a . tabIndex === b . tabIndex ? a . documentOrder - b . documentOrder : a . tabIndex - b . tabIndex ;
} ;
var isInput = function isInput ( node ) {
return node . tagName === 'INPUT' ;
} ;
var isHiddenInput = function isHiddenInput ( node ) {
return isInput ( node ) && node . type === 'hidden' ;
} ;
var isDetailsWithSummary = function isDetailsWithSummary ( node ) {
var r = node . tagName === 'DETAILS' && Array . prototype . slice . apply ( node . children ) . some ( function ( child ) {
return child . tagName === 'SUMMARY' ;
} ) ;
return r ;
} ;
var getCheckedRadio = function getCheckedRadio ( nodes , form ) {
for ( var i = 0 ; i < nodes . length ; i ++ ) {
if ( nodes [ i ] . checked && nodes [ i ] . form === form ) {
return nodes [ i ] ;
}
}
} ;
var isTabbableRadio = function isTabbableRadio ( node ) {
if ( ! node . name ) {
return true ;
}
var radioScope = node . form || getRootNode ( node ) ;
var queryRadios = function queryRadios ( name ) {
return radioScope . querySelectorAll ( 'input[type="radio"][name="' + name + '"]' ) ;
} ;
var radioSet ;
if ( typeof window !== 'undefined' && typeof window . CSS !== 'undefined' && typeof window . CSS . escape === 'function' ) {
radioSet = queryRadios ( window . CSS . escape ( node . name ) ) ;
} else {
try {
radioSet = queryRadios ( node . name ) ;
} catch ( err ) {
// eslint-disable-next-line no-console
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' , err . message ) ;
return false ;
}
}
var checked = getCheckedRadio ( radioSet , node . form ) ;
return ! checked || checked === node ;
} ;
var isRadio = function isRadio ( node ) {
return isInput ( node ) && node . type === 'radio' ;
} ;
var isNonTabbableRadio = function isNonTabbableRadio ( node ) {
return isRadio ( node ) && ! isTabbableRadio ( node ) ;
} ;
// determines if a node is ultimately attached to the window's document
var isNodeAttached = function isNodeAttached ( node ) {
var _nodeRoot ;
// The root node is the shadow root if the node is in a shadow DOM; some document otherwise
// (but NOT _the_ document; see second 'If' comment below for more).
// If rootNode is shadow root, it'll have a host, which is the element to which the shadow
// is attached, and the one we need to check if it's in the document or not (because the
// shadow, and all nodes it contains, is never considered in the document since shadows
// behave like self-contained DOMs; but if the shadow's HOST, which is part of the document,
// is hidden, or is not in the document itself but is detached, it will affect the shadow's
// visibility, including all the nodes it contains). The host could be any normal node,
// or a custom element (i.e. web component). Either way, that's the one that is considered
// part of the document, not the shadow root, nor any of its children (i.e. the node being
// tested).
// To further complicate things, we have to look all the way up until we find a shadow HOST
// that is attached (or find none) because the node might be in nested shadows...
// If rootNode is not a shadow root, it won't have a host, and so rootNode should be the
// document (per the docs) and while it's a Document-type object, that document does not
// appear to be the same as the node's `ownerDocument` for some reason, so it's safer
// to ignore the rootNode at this point, and use `node.ownerDocument`. Otherwise,
// using `rootNode.contains(node)` will _always_ be true we'll get false-positives when
// node is actually detached.
// NOTE: If `nodeRootHost` or `node` happens to be the `document` itself (which is possible
// if a tabbable/focusable node was quickly added to the DOM, focused, and then removed
// from the DOM as in https://github.com/focus-trap/focus-trap-react/issues/905), then
// `ownerDocument` will be `null`, hence the optional chaining on it.
var nodeRoot = node && getRootNode ( node ) ;
var nodeRootHost = ( _nodeRoot = nodeRoot ) === null || _nodeRoot === void 0 ? void 0 : _nodeRoot . host ;
// in some cases, a detached node will return itself as the root instead of a document or
// shadow root object, in which case, we shouldn't try to look further up the host chain
var attached = false ;
if ( nodeRoot && nodeRoot !== node ) {
var _nodeRootHost , _nodeRootHost$ownerDo , _node$ownerDocument ;
attached = ! ! ( ( _nodeRootHost = nodeRootHost ) !== null && _nodeRootHost !== void 0 && ( _nodeRootHost$ownerDo = _nodeRootHost . ownerDocument ) !== null && _nodeRootHost$ownerDo !== void 0 && _nodeRootHost$ownerDo . contains ( nodeRootHost ) || node !== null && node !== void 0 && ( _node$ownerDocument = node . ownerDocument ) !== null && _node$ownerDocument !== void 0 && _node$ownerDocument . contains ( node ) ) ;
while ( ! attached && nodeRootHost ) {
var _nodeRoot2 , _nodeRootHost2 , _nodeRootHost2$ownerD ;
// since it's not attached and we have a root host, the node MUST be in a nested shadow DOM,
// which means we need to get the host's host and check if that parent host is contained
// in (i.e. attached to) the document
nodeRoot = getRootNode ( nodeRootHost ) ;
nodeRootHost = ( _nodeRoot2 = nodeRoot ) === null || _nodeRoot2 === void 0 ? void 0 : _nodeRoot2 . host ;
attached = ! ! ( ( _nodeRootHost2 = nodeRootHost ) !== null && _nodeRootHost2 !== void 0 && ( _nodeRootHost2$ownerD = _nodeRootHost2 . ownerDocument ) !== null && _nodeRootHost2$ownerD !== void 0 && _nodeRootHost2$ownerD . contains ( nodeRootHost ) ) ;
}
}
return attached ;
} ;
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 ,
getShadowRoot = _ref . getShadowRoot ;
2026-02-11 16:20:26 +00:00
if ( displayCheck === 'full-native' ) {
if ( 'checkVisibility' in node ) {
// Chrome >= 105, Edge >= 105, Firefox >= 106, Safari >= 17.4
// @see https://developer.mozilla.org/en-US/docs/Web/API/Element/checkVisibility#browser_compatibility
var visible = node . checkVisibility ( {
// Checking opacity might be desirable for some use cases, but natively,
// opacity zero elements _are_ focusable and tabbable.
checkOpacity : false ,
opacityProperty : false ,
contentVisibilityAuto : true ,
visibilityProperty : true ,
// This is an alias for `visibilityProperty`. Contemporary browsers
// support both. However, this alias has wider browser support (Chrome
// >= 105 and Firefox >= 106, vs. Chrome >= 121 and Firefox >= 122), so
// we include it anyway.
checkVisibilityCSS : true
} ) ;
return ! visible ;
}
// Fall through to manual visibility checks
}
2024-01-05 12:14:38 +00:00
// NOTE: visibility will be `undefined` if node is detached from the document
// (see notes about this further down), which means we will consider it visible
// (this is legacy behavior from a very long way back)
// NOTE: we check this regardless of `displayCheck="none"` because this is a
// _visibility_ check, not a _display_ check
if ( getComputedStyle ( node ) . visibility === 'hidden' ) {
return true ;
}
var isDirectSummary = matches . call ( node , 'details>summary:first-of-type' ) ;
var nodeUnderDetails = isDirectSummary ? node . parentElement : node ;
if ( matches . call ( nodeUnderDetails , 'details:not([open]) *' ) ) {
return true ;
}
2026-02-11 16:20:26 +00:00
if ( ! displayCheck || displayCheck === 'full' ||
// full-native can run this branch when it falls through in case
// Element#checkVisibility is unsupported
displayCheck === 'full-native' || displayCheck === 'legacy-full' ) {
2024-01-05 12:14:38 +00:00
if ( typeof getShadowRoot === 'function' ) {
// figure out if we should consider the node to be in an undisclosed shadow and use the
// 'non-zero-area' fallback
var originalNode = node ;
while ( node ) {
var parentElement = node . parentElement ;
var rootNode = getRootNode ( node ) ;
if ( parentElement && ! parentElement . shadowRoot && getShadowRoot ( parentElement ) === true // check if there's an undisclosed shadow
) {
// node has an undisclosed shadow which means we can only treat it as a black box, so we
// fall back to a non-zero-area test
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 ;
}
}
node = originalNode ;
}
// else, `getShadowRoot` might be true, but all that does is enable shadow DOM support
// (i.e. it does not also presume that all nodes might have undisclosed shadows); or
// it might be a falsy value, which means shadow DOM support is disabled
// Since we didn't find it sitting in an undisclosed shadow (or shadows are disabled)
// now we can just test to see if it would normally be visible or not, provided it's
// attached to the main document.
// NOTE: We must consider case where node is inside a shadow DOM and given directly to
// `isTabbable()` or `isFocusable()` -- regardless of `getShadowRoot` option setting.
if ( isNodeAttached ( node ) ) {
// this works wherever the node is: if there's at least one client rect, it's
// somehow displayed; it also covers the CSS 'display: contents' case where the
// node itself is hidden in place of its contents; and there's no need to search
// up the hierarchy either
return ! node . getClientRects ( ) . length ;
}
// Else, the node isn't attached to the document, which means the `getClientRects()`
// API will __always__ return zero rects (this can happen, for example, if React
// is used to render nodes onto a detached tree, as confirmed in this thread:
// https://github.com/facebook/react/issues/9117#issuecomment-284228870)
//
// It also means that even window.getComputedStyle(node).display will return `undefined`
// because styles are only computed for nodes that are in the document.
//
// NOTE: THIS HAS BEEN THE CASE FOR YEARS. It is not new, nor is it caused by tabbable
// somehow. Though it was never stated officially, anyone who has ever used tabbable
// APIs on nodes in detached containers has actually implicitly used tabbable in what
// was later (as of v5.2.0 on Apr 9, 2021) called `displayCheck="none"` mode -- essentially
// considering __everything__ to be visible because of the innability to determine styles.
//
// v6.0.0: As of this major release, the default 'full' option __no longer treats detached
// nodes as visible with the 'none' fallback.__
if ( displayCheck !== 'legacy-full' ) {
return true ; // hidden
}
// else, fallback to 'none' mode and consider the node visible
} else if ( displayCheck === 'non-zero-area' ) {
// NOTE: Even though this tests that the node's client rect is non-zero to determine
// whether it's displayed, and that a detached node will __always__ have a zero-area
// client rect, we don't special-case for whether the node is attached or not. In
// this mode, we do want to consider nodes that have a zero area to be hidden at all
// times, and that includes attached or not.
return isZeroArea ( node ) ;
}
// visible, as far as we can tell, or per current `displayCheck=none` mode, we assume
// it's visible
return false ;
} ;
// form fields (nested) inside a disabled fieldset are not focusable/tabbable
// unless they are in the _first_ <legend> element of the top-most disabled
// fieldset
var isDisabledFromFieldset = function isDisabledFromFieldset ( node ) {
if ( /^(INPUT|BUTTON|SELECT|TEXTAREA)$/ . test ( node . tagName ) ) {
var parentNode = node . parentElement ;
// check if `node` is contained in a disabled <fieldset>
while ( parentNode ) {
if ( parentNode . tagName === 'FIELDSET' && parentNode . disabled ) {
// look for the first <legend> among the children of the disabled <fieldset>
for ( var i = 0 ; i < parentNode . children . length ; i ++ ) {
var child = parentNode . children . item ( i ) ;
// when the first <legend> (in document order) is found
if ( child . tagName === 'LEGEND' ) {
// if its parent <fieldset> is not nested in another disabled <fieldset>,
// return whether `node` is a descendant of its first <legend>
return matches . call ( parentNode , 'fieldset[disabled] *' ) ? true : ! child . contains ( node ) ;
}
}
// the disabled <fieldset> containing `node` has no <legend>
return true ;
}
parentNode = parentNode . parentElement ;
}
}
// else, node's tabbable/focusable state should not be affected by a fieldset's
// enabled/disabled state
return false ;
} ;
var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable ( options , node ) {
2026-02-11 16:20:26 +00:00
if ( node . disabled || isHiddenInput ( node ) || isHidden ( node , options ) ||
2024-01-05 12:14:38 +00:00
// For a details element with a summary, the summary element gets the focus
isDetailsWithSummary ( node ) || isDisabledFromFieldset ( node ) ) {
return false ;
}
return true ;
} ;
var isNodeMatchingSelectorTabbable = function isNodeMatchingSelectorTabbable ( options , node ) {
if ( isNonTabbableRadio ( node ) || getTabIndex ( node ) < 0 || ! isNodeMatchingSelectorFocusable ( options , node ) ) {
return false ;
}
return true ;
} ;
2026-02-11 16:20:26 +00:00
var isShadowRootTabbable = function isShadowRootTabbable ( shadowHostNode ) {
2024-01-05 12:14:38 +00:00
var tabIndex = parseInt ( shadowHostNode . getAttribute ( 'tabindex' ) , 10 ) ;
if ( isNaN ( tabIndex ) || tabIndex >= 0 ) {
return true ;
}
// If a custom element has an explicit negative tabindex,
// browsers will not allow tab targeting said element's children.
return false ;
} ;
/ * *
* @ param { Array . < Element | CandidateScope > } candidates
* @ returns Element [ ]
* /
2026-02-11 16:20:26 +00:00
var _sortByOrder = function sortByOrder ( candidates ) {
2024-01-05 12:14:38 +00:00
var regularTabbables = [ ] ;
var orderedTabbables = [ ] ;
candidates . forEach ( function ( item , i ) {
var isScope = ! ! item . scopeParent ;
var element = isScope ? item . scopeParent : item ;
var candidateTabindex = getSortOrderTabIndex ( element , isScope ) ;
2026-02-11 16:20:26 +00:00
var elements = isScope ? _sortByOrder ( item . candidates ) : element ;
2024-01-05 12:14:38 +00:00
if ( candidateTabindex === 0 ) {
isScope ? regularTabbables . push . apply ( regularTabbables , elements ) : regularTabbables . push ( element ) ;
} else {
orderedTabbables . push ( {
documentOrder : i ,
tabIndex : candidateTabindex ,
item : item ,
isScope : isScope ,
content : elements
} ) ;
}
} ) ;
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 ( container , options ) {
options = options || { } ;
var candidates ;
if ( options . getShadowRoot ) {
2026-02-11 16:20:26 +00:00
candidates = _getCandidatesIteratively ( [ container ] , options . includeContainer , {
2024-01-05 12:14:38 +00:00
filter : isNodeMatchingSelectorTabbable . bind ( null , options ) ,
flatten : false ,
getShadowRoot : options . getShadowRoot ,
2026-02-11 16:20:26 +00:00
shadowRootFilter : isShadowRootTabbable
2024-01-05 12:14:38 +00:00
} ) ;
} else {
candidates = getCandidates ( container , options . includeContainer , isNodeMatchingSelectorTabbable . bind ( null , options ) ) ;
}
2026-02-11 16:20:26 +00:00
return _sortByOrder ( candidates ) ;
2024-01-05 12:14:38 +00:00
} ;
var focusable = function focusable ( container , options ) {
options = options || { } ;
var candidates ;
if ( options . getShadowRoot ) {
2026-02-11 16:20:26 +00:00
candidates = _getCandidatesIteratively ( [ container ] , options . includeContainer , {
2024-01-05 12:14:38 +00:00
filter : isNodeMatchingSelectorFocusable . bind ( null , options ) ,
flatten : true ,
getShadowRoot : options . getShadowRoot
} ) ;
} else {
candidates = getCandidates ( container , options . includeContainer , isNodeMatchingSelectorFocusable . bind ( null , options ) ) ;
}
return candidates ;
} ;
var isTabbable = function isTabbable ( node , options ) {
options = options || { } ;
if ( ! node ) {
throw new Error ( 'No node provided' ) ;
}
if ( matches . call ( node , candidateSelector ) === false ) {
return false ;
}
return isNodeMatchingSelectorTabbable ( options , node ) ;
} ;
2026-02-11 16:20:26 +00:00
var focusableCandidateSelector = /* #__PURE__ */ candidateSelectors . concat ( 'iframe:not([inert]):not([inert] *)' ) . join ( ',' ) ;
2024-01-05 12:14:38 +00:00
var isFocusable = function isFocusable ( node , options ) {
options = options || { } ;
if ( ! node ) {
throw new Error ( 'No node provided' ) ;
}
if ( matches . call ( node , focusableCandidateSelector ) === false ) {
return false ;
}
return isNodeMatchingSelectorFocusable ( options , node ) ;
} ;
exports . focusable = focusable ;
exports . getTabIndex = getTabIndex ;
exports . isFocusable = isFocusable ;
exports . isTabbable = isTabbable ;
exports . tabbable = tabbable ;
} ) ) ;
//# sourceMappingURL=index.umd.js.map