@patternfly/react-core 4.219.1 → 4.219.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/esm/components/AboutModal/AboutModalContainer.js +1 -1
  3. package/dist/esm/components/AboutModal/AboutModalContainer.js.map +1 -1
  4. package/dist/esm/components/ContextSelector/ContextSelector.d.ts.map +1 -1
  5. package/dist/esm/components/ContextSelector/ContextSelector.js +1 -1
  6. package/dist/esm/components/ContextSelector/ContextSelector.js.map +1 -1
  7. package/dist/esm/components/Modal/ModalContent.js +1 -1
  8. package/dist/esm/components/Modal/ModalContent.js.map +1 -1
  9. package/dist/esm/components/Popover/Popover.d.ts.map +1 -1
  10. package/dist/esm/components/Popover/Popover.js +1 -0
  11. package/dist/esm/components/Popover/Popover.js.map +1 -1
  12. package/dist/js/components/AboutModal/AboutModalContainer.js +1 -1
  13. package/dist/js/components/AboutModal/AboutModalContainer.js.map +1 -1
  14. package/dist/js/components/ContextSelector/ContextSelector.d.ts.map +1 -1
  15. package/dist/js/components/ContextSelector/ContextSelector.js +1 -1
  16. package/dist/js/components/ContextSelector/ContextSelector.js.map +1 -1
  17. package/dist/js/components/Modal/ModalContent.js +1 -1
  18. package/dist/js/components/Modal/ModalContent.js.map +1 -1
  19. package/dist/js/components/Popover/Popover.d.ts.map +1 -1
  20. package/dist/js/components/Popover/Popover.js +1 -0
  21. package/dist/js/components/Popover/Popover.js.map +1 -1
  22. package/dist/umd/react-core.js +718 -158
  23. package/dist/umd/react-core.min.js +7 -7
  24. package/package.json +6 -6
  25. package/src/components/AboutModal/AboutModalContainer.tsx +1 -1
  26. package/src/components/ContextSelector/ContextSelector.tsx +4 -1
  27. package/src/components/Modal/ModalContent.tsx +1 -1
  28. package/src/components/Popover/Popover.tsx +1 -0
  29. package/src/demos/ComposableMenu/examples/ComposableActionsMenu.tsx +12 -1
  30. package/src/demos/ComposableMenu/examples/ComposableContextSelector.tsx +12 -1
  31. package/src/demos/ComposableMenu/examples/ComposableDateSelect.tsx +13 -2
  32. package/src/demos/ComposableMenu/examples/ComposableDrilldownMenu.tsx +12 -1
  33. package/src/demos/ComposableMenu/examples/ComposableDropdwnVariants.tsx +10 -6
  34. package/src/demos/ComposableMenu/examples/ComposableFlyout.tsx +12 -1
  35. package/src/demos/ComposableMenu/examples/ComposableOptionsMenuVariants.tsx +6 -1
  36. package/src/demos/ComposableMenu/examples/ComposableSimpleDropdown.tsx +6 -1
  37. package/src/demos/ComposableMenu/examples/ComposableSimpleSelect.tsx +6 -1
  38. package/src/demos/ComposableMenu/examples/ComposableTreeViewMenu.tsx +12 -12
  39. package/src/demos/SearchInput/SearchInput.md +47 -44
@@ -123,12 +123,24 @@
123
123
  };
124
124
 
125
125
  /*!
126
- * tabbable 5.1.4
126
+ * tabbable 5.3.2
127
127
  * @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
128
128
  */
129
- var candidateSelectors = ['input', 'select', 'textarea', 'a[href]', 'button', '[tabindex]', 'audio[controls]', 'video[controls]', '[contenteditable]:not([contenteditable="false"])', 'details>summary:first-of-type', 'details'];
129
+ 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'];
130
130
  var candidateSelector = /* #__PURE__ */candidateSelectors.join(',');
131
- var matches = typeof Element === 'undefined' ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
131
+ var NoElement = typeof Element === 'undefined';
132
+ var matches = NoElement ? function () {} : Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
133
+ var getRootNode = !NoElement && Element.prototype.getRootNode ? function (element) {
134
+ return element.getRootNode();
135
+ } : function (element) {
136
+ return element.ownerDocument;
137
+ };
138
+ /**
139
+ * @param {Element} el container to check in
140
+ * @param {boolean} includeContainer add container to check
141
+ * @param {(node: Element) => boolean} filter filter candidates
142
+ * @returns {Element[]}
143
+ */
132
144
 
133
145
  var getCandidates = function getCandidates(el, includeContainer, filter) {
134
146
  var candidates = Array.prototype.slice.apply(el.querySelectorAll(candidateSelector));
@@ -140,31 +152,112 @@
140
152
  candidates = candidates.filter(filter);
141
153
  return candidates;
142
154
  };
155
+ /**
156
+ * @callback GetShadowRoot
157
+ * @param {Element} element to check for shadow root
158
+ * @returns {ShadowRoot|boolean} ShadowRoot if available or boolean indicating if a shadowRoot is attached but not available.
159
+ */
160
+
161
+ /**
162
+ * @typedef {Object} CandidatesScope
163
+ * @property {Element} scope contains inner candidates
164
+ * @property {Element[]} candidates
165
+ */
166
+
167
+ /**
168
+ * @typedef {Object} IterativeOptions
169
+ * @property {GetShadowRoot|boolean} getShadowRoot true if shadow support is enabled; falsy if not;
170
+ * if a function, implies shadow support is enabled and either returns the shadow root of an element
171
+ * or a boolean stating if it has an undisclosed shadow root
172
+ * @property {(node: Element) => boolean} filter filter candidates
173
+ * @property {boolean} flatten if true then result will flatten any CandidatesScope into the returned list
174
+ */
175
+
176
+ /**
177
+ * @param {Element[]} elements list of element containers to match candidates from
178
+ * @param {boolean} includeContainer add container list to check
179
+ * @param {IterativeOptions} options
180
+ * @returns {Array.<Element|CandidatesScope>}
181
+ */
143
182
 
144
- var isContentEditable = function isContentEditable(node) {
145
- return node.contentEditable === 'true';
146
- };
147
183
 
148
- var getTabindex = function getTabindex(node) {
149
- var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10);
184
+ var getCandidatesIteratively = function getCandidatesIteratively(elements, includeContainer, options) {
185
+ var candidates = [];
186
+ var elementsToCheck = Array.from(elements);
150
187
 
151
- if (!isNaN(tabindexAttr)) {
152
- return tabindexAttr;
153
- } // Browsers do not return `tabIndex` correctly for contentEditable nodes;
154
- // so if they don't have a tabindex attribute specifically set, assume it's 0.
188
+ while (elementsToCheck.length) {
189
+ var element = elementsToCheck.shift();
155
190
 
191
+ if (element.tagName === 'SLOT') {
192
+ // add shadow dom slot scope (slot itself cannot be focusable)
193
+ var assigned = element.assignedElements();
194
+ var content = assigned.length ? assigned : element.children;
195
+ var nestedCandidates = getCandidatesIteratively(content, true, options);
156
196
 
157
- if (isContentEditable(node)) {
158
- return 0;
159
- } // in Chrome, <details/>, <audio controls/> and <video controls/> elements get a default
160
- // `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM,
161
- // yet they are still part of the regular tab order; in FF, they get a default
162
- // `tabIndex` of 0; since Chrome still puts those elements in the regular tab
163
- // order, consider their tab index to be 0.
197
+ if (options.flatten) {
198
+ candidates.push.apply(candidates, nestedCandidates);
199
+ } else {
200
+ candidates.push({
201
+ scope: element,
202
+ candidates: nestedCandidates
203
+ });
204
+ }
205
+ } else {
206
+ // check candidate element
207
+ var validCandidate = matches.call(element, candidateSelector);
208
+
209
+ if (validCandidate && options.filter(element) && (includeContainer || !elements.includes(element))) {
210
+ candidates.push(element);
211
+ } // iterate over shadow content if possible
212
+
213
+
214
+ var shadowRoot = element.shadowRoot || // check for an undisclosed shadow
215
+ typeof options.getShadowRoot === 'function' && options.getShadowRoot(element);
216
+
217
+ if (shadowRoot) {
218
+ // add shadow dom scope IIF a shadow root node was given; otherwise, an undisclosed
219
+ // shadow exists, so look at light dom children as fallback BUT create a scope for any
220
+ // child candidates found because they're likely slotted elements (elements that are
221
+ // children of the web component element (which has the shadow), in the light dom, but
222
+ // slotted somewhere _inside_ the undisclosed shadow) -- the scope is created below,
223
+ // _after_ we return from this recursive call
224
+ var _nestedCandidates = getCandidatesIteratively(shadowRoot === true ? element.children : shadowRoot.children, true, options);
225
+
226
+ if (options.flatten) {
227
+ candidates.push.apply(candidates, _nestedCandidates);
228
+ } else {
229
+ candidates.push({
230
+ scope: element,
231
+ candidates: _nestedCandidates
232
+ });
233
+ }
234
+ } else {
235
+ // there's not shadow so just dig into the element's (light dom) children
236
+ // __without__ giving the element special scope treatment
237
+ elementsToCheck.unshift.apply(elementsToCheck, element.children);
238
+ }
239
+ }
240
+ }
164
241
 
242
+ return candidates;
243
+ };
165
244
 
166
- if ((node.nodeName === 'AUDIO' || node.nodeName === 'VIDEO' || node.nodeName === 'DETAILS') && node.getAttribute('tabindex') === null) {
167
- return 0;
245
+ var getTabindex = function getTabindex(node, isScope) {
246
+ if (node.tabIndex < 0) {
247
+ // in Chrome, <details/>, <audio controls/> and <video controls/> elements get a default
248
+ // `tabIndex` of -1 when the 'tabindex' attribute isn't specified in the DOM,
249
+ // yet they are still part of the regular tab order; in FF, they get a default
250
+ // `tabIndex` of 0; since Chrome still puts those elements in the regular tab
251
+ // order, consider their tab index to be 0.
252
+ // Also browsers do not return `tabIndex` correctly for contentEditable nodes;
253
+ // so if they don't have a tabindex attribute specifically set, assume it's 0.
254
+ //
255
+ // isScope is positive for custom element with shadow root or slot that by default
256
+ // have tabIndex -1, but need to be sorted by document order in order for their
257
+ // content to be inserted in the correct position
258
+ if ((isScope || /^(AUDIO|VIDEO|DETAILS)$/.test(node.tagName) || node.isContentEditable) && isNaN(parseInt(node.getAttribute('tabindex'), 10))) {
259
+ return 0;
260
+ }
168
261
  }
169
262
 
170
263
  return node.tabIndex;
@@ -202,8 +295,26 @@
202
295
  return true;
203
296
  }
204
297
 
205
- var radioScope = node.form || node.ownerDocument;
206
- var radioSet = radioScope.querySelectorAll('input[type="radio"][name="' + node.name + '"]');
298
+ var radioScope = node.form || getRootNode(node);
299
+
300
+ var queryRadios = function queryRadios(name) {
301
+ return radioScope.querySelectorAll('input[type="radio"][name="' + name + '"]');
302
+ };
303
+
304
+ var radioSet;
305
+
306
+ if (typeof window !== 'undefined' && typeof window.CSS !== 'undefined' && typeof window.CSS.escape === 'function') {
307
+ radioSet = queryRadios(window.CSS.escape(node.name));
308
+ } else {
309
+ try {
310
+ radioSet = queryRadios(node.name);
311
+ } catch (err) {
312
+ // eslint-disable-next-line no-console
313
+ 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);
314
+ return false;
315
+ }
316
+ }
317
+
207
318
  var checked = getCheckedRadio(radioSet, node.form);
208
319
  return !checked || checked === node;
209
320
  };
@@ -216,7 +327,23 @@
216
327
  return isRadio(node) && !isTabbableRadio(node);
217
328
  };
218
329
 
219
- var isHidden = function isHidden(node) {
330
+ var isZeroArea = function isZeroArea(node) {
331
+ var _node$getBoundingClie = node.getBoundingClientRect(),
332
+ width = _node$getBoundingClie.width,
333
+ height = _node$getBoundingClie.height;
334
+
335
+ return width === 0 && height === 0;
336
+ };
337
+
338
+ var isHidden = function isHidden(node, _ref) {
339
+ var displayCheck = _ref.displayCheck,
340
+ getShadowRoot = _ref.getShadowRoot;
341
+
342
+ // NOTE: visibility will be `undefined` if node is detached from the document
343
+ // (see notes about this further down), which means we will consider it visible
344
+ // (this is legacy behavior from a very long way back)
345
+ // NOTE: we check this regardless of `displayCheck="none"` because this is a
346
+ // _visibility_ check, not a _display_ check
220
347
  if (getComputedStyle(node).visibility === 'hidden') {
221
348
  return true;
222
349
  }
@@ -226,64 +353,234 @@
226
353
 
227
354
  if (matches.call(nodeUnderDetails, 'details:not([open]) *')) {
228
355
  return true;
229
- }
356
+ } // The root node is the shadow root if the node is in a shadow DOM; some document otherwise
357
+ // (but NOT _the_ document; see second 'If' comment below for more).
358
+ // If rootNode is shadow root, it'll have a host, which is the element to which the shadow
359
+ // is attached, and the one we need to check if it's in the document or not (because the
360
+ // shadow, and all nodes it contains, is never considered in the document since shadows
361
+ // behave like self-contained DOMs; but if the shadow's HOST, which is part of the document,
362
+ // is hidden, or is not in the document itself but is detached, it will affect the shadow's
363
+ // visibility, including all the nodes it contains). The host could be any normal node,
364
+ // or a custom element (i.e. web component). Either way, that's the one that is considered
365
+ // part of the document, not the shadow root, nor any of its children (i.e. the node being
366
+ // tested).
367
+ // If rootNode is not a shadow root, it won't have a host, and so rootNode should be the
368
+ // document (per the docs) and while it's a Document-type object, that document does not
369
+ // appear to be the same as the node's `ownerDocument` for some reason, so it's safer
370
+ // to ignore the rootNode at this point, and use `node.ownerDocument`. Otherwise,
371
+ // using `rootNode.contains(node)` will _always_ be true we'll get false-positives when
372
+ // node is actually detached.
373
+
374
+
375
+ var nodeRootHost = getRootNode(node).host;
376
+ var nodeIsAttached = (nodeRootHost === null || nodeRootHost === void 0 ? void 0 : nodeRootHost.ownerDocument.contains(nodeRootHost)) || node.ownerDocument.contains(node);
377
+
378
+ if (!displayCheck || displayCheck === 'full') {
379
+ if (typeof getShadowRoot === 'function') {
380
+ // figure out if we should consider the node to be in an undisclosed shadow and use the
381
+ // 'non-zero-area' fallback
382
+ var originalNode = node;
383
+
384
+ while (node) {
385
+ var parentElement = node.parentElement;
386
+ var rootNode = getRootNode(node);
387
+
388
+ if (parentElement && !parentElement.shadowRoot && getShadowRoot(parentElement) === true // check if there's an undisclosed shadow
389
+ ) {
390
+ // node has an undisclosed shadow which means we can only treat it as a black box, so we
391
+ // fall back to a non-zero-area test
392
+ return isZeroArea(node);
393
+ } else if (node.assignedSlot) {
394
+ // iterate up slot
395
+ node = node.assignedSlot;
396
+ } else if (!parentElement && rootNode !== node.ownerDocument) {
397
+ // cross shadow boundary
398
+ node = rootNode.host;
399
+ } else {
400
+ // iterate up normal dom
401
+ node = parentElement;
402
+ }
403
+ }
230
404
 
231
- while (node) {
232
- if (getComputedStyle(node).display === 'none') {
233
- return true;
405
+ node = originalNode;
406
+ } // else, `getShadowRoot` might be true, but all that does is enable shadow DOM support
407
+ // (i.e. it does not also presume that all nodes might have undisclosed shadows); or
408
+ // it might be a falsy value, which means shadow DOM support is disabled
409
+ // Since we didn't find it sitting in an undisclosed shadow (or shadows are disabled)
410
+ // now we can just test to see if it would normally be visible or not, provided it's
411
+ // attached to the main document.
412
+ // NOTE: We must consider case where node is inside a shadow DOM and given directly to
413
+ // `isTabbable()` or `isFocusable()` -- regardless of `getShadowRoot` option setting.
414
+
415
+
416
+ if (nodeIsAttached) {
417
+ // this works wherever the node is: if there's at least one client rect, it's
418
+ // somehow displayed; it also covers the CSS 'display: contents' case where the
419
+ // node itself is hidden in place of its contents; and there's no need to search
420
+ // up the hierarchy either
421
+ return !node.getClientRects().length;
422
+ } // Else, the node isn't attached to the document, which means the `getClientRects()`
423
+ // API will __always__ return zero rects (this can happen, for example, if React
424
+ // is used to render nodes onto a detached tree, as confirmed in this thread:
425
+ // https://github.com/facebook/react/issues/9117#issuecomment-284228870)
426
+ //
427
+ // It also means that even window.getComputedStyle(node).display will return `undefined`
428
+ // because styles are only computed for nodes that are in the document.
429
+ //
430
+ // NOTE: THIS HAS BEEN THE CASE FOR YEARS. It is not new, nor is it caused by tabbable
431
+ // somehow. Though it was never stated officially, anyone who has ever used tabbable
432
+ // APIs on nodes in detached containers has actually implicitly used tabbable in what
433
+ // was later (as of v5.2.0 on Apr 9, 2021) called `displayCheck="none"` mode -- essentially
434
+ // considering __everything__ to be visible because of the innability to determine styles.
435
+
436
+ } else if (displayCheck === 'non-zero-area') {
437
+ // NOTE: Even though this tests that the node's client rect is non-zero to determine
438
+ // whether it's displayed, and that a detached node will __always__ have a zero-area
439
+ // client rect, we don't special-case for whether the node is attached or not. In
440
+ // this mode, we do want to consider nodes that have a zero area to be hidden at all
441
+ // times, and that includes attached or not.
442
+ return isZeroArea(node);
443
+ } // visible, as far as we can tell, or per current `displayCheck` mode
444
+
445
+
446
+ return false;
447
+ }; // form fields (nested) inside a disabled fieldset are not focusable/tabbable
448
+ // unless they are in the _first_ <legend> element of the top-most disabled
449
+ // fieldset
450
+
451
+
452
+ var isDisabledFromFieldset = function isDisabledFromFieldset(node) {
453
+ if (/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(node.tagName)) {
454
+ var parentNode = node.parentElement; // check if `node` is contained in a disabled <fieldset>
455
+
456
+ while (parentNode) {
457
+ if (parentNode.tagName === 'FIELDSET' && parentNode.disabled) {
458
+ // look for the first <legend> among the children of the disabled <fieldset>
459
+ for (var i = 0; i < parentNode.children.length; i++) {
460
+ var child = parentNode.children.item(i); // when the first <legend> (in document order) is found
461
+
462
+ if (child.tagName === 'LEGEND') {
463
+ // if its parent <fieldset> is not nested in another disabled <fieldset>,
464
+ // return whether `node` is a descendant of its first <legend>
465
+ return matches.call(parentNode, 'fieldset[disabled] *') ? true : !child.contains(node);
466
+ }
467
+ } // the disabled <fieldset> containing `node` has no <legend>
468
+
469
+
470
+ return true;
471
+ }
472
+
473
+ parentNode = parentNode.parentElement;
234
474
  }
475
+ } // else, node's tabbable/focusable state should not be affected by a fieldset's
476
+ // enabled/disabled state
235
477
 
236
- node = node.parentElement;
237
- }
238
478
 
239
479
  return false;
240
480
  };
241
481
 
242
- var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(node) {
243
- if (node.disabled || isHiddenInput(node) || isHidden(node) ||
244
- /* For a details element with a summary, the summary element gets the focused */
245
- isDetailsWithSummary(node)) {
482
+ var isNodeMatchingSelectorFocusable = function isNodeMatchingSelectorFocusable(options, node) {
483
+ if (node.disabled || isHiddenInput(node) || isHidden(node, options) || // For a details element with a summary, the summary element gets the focus
484
+ isDetailsWithSummary(node) || isDisabledFromFieldset(node)) {
246
485
  return false;
247
486
  }
248
487
 
249
488
  return true;
250
489
  };
251
490
 
252
- var isNodeMatchingSelectorTabbable = function isNodeMatchingSelectorTabbable(node) {
253
- if (!isNodeMatchingSelectorFocusable(node) || isNonTabbableRadio(node) || getTabindex(node) < 0) {
491
+ var isNodeMatchingSelectorTabbable = function isNodeMatchingSelectorTabbable(options, node) {
492
+ if (isNonTabbableRadio(node) || getTabindex(node) < 0 || !isNodeMatchingSelectorFocusable(options, node)) {
254
493
  return false;
255
494
  }
256
495
 
257
496
  return true;
258
497
  };
498
+ /**
499
+ * @param {Array.<Element|CandidatesScope>} candidates
500
+ * @returns Element[]
501
+ */
259
502
 
260
- var tabbable = function tabbable(el, options) {
261
- options = options || {};
503
+
504
+ var sortByOrder = function sortByOrder(candidates) {
262
505
  var regularTabbables = [];
263
506
  var orderedTabbables = [];
264
- var candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable);
265
- candidates.forEach(function (candidate, i) {
266
- var candidateTabindex = getTabindex(candidate);
507
+ candidates.forEach(function (item, i) {
508
+ var isScope = !!item.scope;
509
+ var element = isScope ? item.scope : item;
510
+ var candidateTabindex = getTabindex(element, isScope);
511
+ var elements = isScope ? sortByOrder(item.candidates) : element;
267
512
 
268
513
  if (candidateTabindex === 0) {
269
- regularTabbables.push(candidate);
514
+ isScope ? regularTabbables.push.apply(regularTabbables, elements) : regularTabbables.push(element);
270
515
  } else {
271
516
  orderedTabbables.push({
272
517
  documentOrder: i,
273
518
  tabIndex: candidateTabindex,
274
- node: candidate
519
+ item: item,
520
+ isScope: isScope,
521
+ content: elements
275
522
  });
276
523
  }
277
524
  });
278
- var tabbableNodes = orderedTabbables.sort(sortOrderedTabbables).map(function (a) {
279
- return a.node;
280
- }).concat(regularTabbables);
281
- return tabbableNodes;
525
+ return orderedTabbables.sort(sortOrderedTabbables).reduce(function (acc, sortable) {
526
+ sortable.isScope ? acc.push.apply(acc, sortable.content) : acc.push(sortable.content);
527
+ return acc;
528
+ }, []).concat(regularTabbables);
529
+ };
530
+
531
+ var tabbable = function tabbable(el, options) {
532
+ options = options || {};
533
+ var candidates;
534
+
535
+ if (options.getShadowRoot) {
536
+ candidates = getCandidatesIteratively([el], options.includeContainer, {
537
+ filter: isNodeMatchingSelectorTabbable.bind(null, options),
538
+ flatten: false,
539
+ getShadowRoot: options.getShadowRoot
540
+ });
541
+ } else {
542
+ candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorTabbable.bind(null, options));
543
+ }
544
+
545
+ return sortByOrder(candidates);
546
+ };
547
+
548
+ var focusable = function focusable(el, options) {
549
+ options = options || {};
550
+ var candidates;
551
+
552
+ if (options.getShadowRoot) {
553
+ candidates = getCandidatesIteratively([el], options.includeContainer, {
554
+ filter: isNodeMatchingSelectorFocusable.bind(null, options),
555
+ flatten: true,
556
+ getShadowRoot: options.getShadowRoot
557
+ });
558
+ } else {
559
+ candidates = getCandidates(el, options.includeContainer, isNodeMatchingSelectorFocusable.bind(null, options));
560
+ }
561
+
562
+ return candidates;
563
+ };
564
+
565
+ var isTabbable = function isTabbable(node, options) {
566
+ options = options || {};
567
+
568
+ if (!node) {
569
+ throw new Error('No node provided');
570
+ }
571
+
572
+ if (matches.call(node, candidateSelector) === false) {
573
+ return false;
574
+ }
575
+
576
+ return isNodeMatchingSelectorTabbable(options, node);
282
577
  };
283
578
 
284
579
  var focusableCandidateSelector = /* #__PURE__ */candidateSelectors.concat('iframe').join(',');
285
580
 
286
- var isFocusable = function isFocusable(node) {
581
+ var isFocusable = function isFocusable(node, options) {
582
+ options = options || {};
583
+
287
584
  if (!node) {
288
585
  throw new Error('No node provided');
289
586
  }
@@ -292,38 +589,22 @@
292
589
  return false;
293
590
  }
294
591
 
295
- return isNodeMatchingSelectorFocusable(node);
592
+ return isNodeMatchingSelectorFocusable(options, node);
296
593
  };
297
594
 
298
595
  /*!
299
- * focus-trap 6.2.2
596
+ * focus-trap 6.9.2
300
597
  * @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE
301
598
  */
302
599
 
303
- function _defineProperty$1(obj, key, value) {
304
- if (key in obj) {
305
- Object.defineProperty(obj, key, {
306
- value: value,
307
- enumerable: true,
308
- configurable: true,
309
- writable: true
310
- });
311
- } else {
312
- obj[key] = value;
313
- }
314
-
315
- return obj;
316
- }
317
-
318
600
  function ownKeys(object, enumerableOnly) {
319
601
  var keys = Object.keys(object);
320
602
 
321
603
  if (Object.getOwnPropertySymbols) {
322
604
  var symbols = Object.getOwnPropertySymbols(object);
323
- if (enumerableOnly) symbols = symbols.filter(function (sym) {
605
+ enumerableOnly && (symbols = symbols.filter(function (sym) {
324
606
  return Object.getOwnPropertyDescriptor(object, sym).enumerable;
325
- });
326
- keys.push.apply(keys, symbols);
607
+ })), keys.push.apply(keys, symbols);
327
608
  }
328
609
 
329
610
  return keys;
@@ -331,25 +612,31 @@
331
612
 
332
613
  function _objectSpread2(target) {
333
614
  for (var i = 1; i < arguments.length; i++) {
334
- var source = arguments[i] != null ? arguments[i] : {};
335
-
336
- if (i % 2) {
337
- ownKeys(Object(source), true).forEach(function (key) {
338
- _defineProperty$1(target, key, source[key]);
339
- });
340
- } else if (Object.getOwnPropertyDescriptors) {
341
- Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
342
- } else {
343
- ownKeys(Object(source)).forEach(function (key) {
344
- Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
345
- });
346
- }
615
+ var source = null != arguments[i] ? arguments[i] : {};
616
+ i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
617
+ _defineProperty$1(target, key, source[key]);
618
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
619
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
620
+ });
347
621
  }
348
622
 
349
623
  return target;
350
624
  }
351
625
 
352
- var activeFocusDelay;
626
+ function _defineProperty$1(obj, key, value) {
627
+ if (key in obj) {
628
+ Object.defineProperty(obj, key, {
629
+ value: value,
630
+ enumerable: true,
631
+ configurable: true,
632
+ writable: true
633
+ });
634
+ } else {
635
+ obj[key] = value;
636
+ }
637
+
638
+ return obj;
639
+ }
353
640
 
354
641
  var activeFocusTraps = function () {
355
642
  var trapQueue = [];
@@ -401,10 +688,54 @@
401
688
 
402
689
  var delay = function delay(fn) {
403
690
  return setTimeout(fn, 0);
691
+ }; // Array.find/findIndex() are not supported on IE; this replicates enough
692
+ // of Array.findIndex() for our needs
693
+
694
+
695
+ var findIndex = function findIndex(arr, fn) {
696
+ var idx = -1;
697
+ arr.every(function (value, i) {
698
+ if (fn(value)) {
699
+ idx = i;
700
+ return false; // break
701
+ }
702
+
703
+ return true; // next
704
+ });
705
+ return idx;
706
+ };
707
+ /**
708
+ * Get an option's value when it could be a plain value, or a handler that provides
709
+ * the value.
710
+ * @param {*} value Option's value to check.
711
+ * @param {...*} [params] Any parameters to pass to the handler, if `value` is a function.
712
+ * @returns {*} The `value`, or the handler's returned value.
713
+ */
714
+
715
+
716
+ var valueOrHandler = function valueOrHandler(value) {
717
+ for (var _len = arguments.length, params = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
718
+ params[_key - 1] = arguments[_key];
719
+ }
720
+
721
+ return typeof value === 'function' ? value.apply(void 0, params) : value;
722
+ };
723
+
724
+ var getActualTarget = function getActualTarget(event) {
725
+ // NOTE: If the trap is _inside_ a shadow DOM, event.target will always be the
726
+ // shadow host. However, event.target.composedPath() will be an array of
727
+ // nodes "clicked" from inner-most (the actual element inside the shadow) to
728
+ // outer-most (the host HTML document). If we have access to composedPath(),
729
+ // then use its first element; otherwise, fall back to event.target (and
730
+ // this only works for an _open_ shadow DOM; otherwise,
731
+ // composedPath()[0] === event.target always).
732
+ return event.target.shadowRoot && typeof event.composedPath === 'function' ? event.composedPath()[0] : event.target;
404
733
  };
405
734
 
406
735
  var createFocusTrap = function createFocusTrap(elements, userOptions) {
407
- var doc = document;
736
+ // SSR: a live trap shouldn't be created in this type of environment so this
737
+ // should be safe code to execute if the `document` option isn't specified
738
+ var doc = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.document) || document;
408
739
 
409
740
  var config = _objectSpread2({
410
741
  returnFocusOnDeactivate: true,
@@ -413,51 +744,122 @@
413
744
  }, userOptions);
414
745
 
415
746
  var state = {
747
+ // containers given to createFocusTrap()
416
748
  // @type {Array<HTMLElement>}
417
749
  containers: [],
418
- // list of objects identifying the first and last tabbable nodes in all containers/groups in
419
- // the trap
750
+ // list of objects identifying tabbable nodes in `containers` in the trap
420
751
  // NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap
421
752
  // is active, but the trap should never get to a state where there isn't at least one group
422
753
  // with at least one tabbable node in it (that would lead to an error condition that would
423
754
  // result in an error being thrown)
424
- // @type {Array<{ firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>}
755
+ // @type {Array<{
756
+ // container: HTMLElement,
757
+ // tabbableNodes: Array<HTMLElement>, // empty if none
758
+ // focusableNodes: Array<HTMLElement>, // empty if none
759
+ // firstTabbableNode: HTMLElement|null,
760
+ // lastTabbableNode: HTMLElement|null,
761
+ // nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined
762
+ // }>}
763
+ containerGroups: [],
764
+ // same order/length as `containers` list
765
+ // references to objects in `containerGroups`, but only those that actually have
766
+ // tabbable nodes in them
767
+ // NOTE: same order as `containers` and `containerGroups`, but __not necessarily__
768
+ // the same length
425
769
  tabbableGroups: [],
426
770
  nodeFocusedBeforeActivation: null,
427
771
  mostRecentlyFocusedNode: null,
428
772
  active: false,
429
- paused: false
773
+ paused: false,
774
+ // timer ID for when delayInitialFocus is true and initial focus in this trap
775
+ // has been delayed during activation
776
+ delayInitialFocusTimer: undefined
430
777
  };
431
778
  var trap; // eslint-disable-line prefer-const -- some private functions reference it, and its methods reference private functions, so we must declare here and define later
432
779
 
433
- var containersContain = function containersContain(element) {
434
- return state.containers.some(function (container) {
435
- return container.contains(element);
780
+ /**
781
+ * Gets a configuration option value.
782
+ * @param {Object|undefined} configOverrideOptions If true, and option is defined in this set,
783
+ * value will be taken from this object. Otherwise, value will be taken from base configuration.
784
+ * @param {string} optionName Name of the option whose value is sought.
785
+ * @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName`
786
+ * IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used.
787
+ */
788
+
789
+ var getOption = function getOption(configOverrideOptions, optionName, configOptionName) {
790
+ return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName];
791
+ };
792
+ /**
793
+ * Finds the index of the container that contains the element.
794
+ * @param {HTMLElement} element
795
+ * @returns {number} Index of the container in either `state.containers` or
796
+ * `state.containerGroups` (the order/length of these lists are the same); -1
797
+ * if the element isn't found.
798
+ */
799
+
800
+
801
+ var findContainerIndex = function findContainerIndex(element) {
802
+ // NOTE: search `containerGroups` because it's possible a group contains no tabbable
803
+ // nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`)
804
+ // and we still need to find the element in there
805
+ return state.containerGroups.findIndex(function (_ref) {
806
+ var container = _ref.container,
807
+ tabbableNodes = _ref.tabbableNodes;
808
+ return container.contains(element) || // fall back to explicit tabbable search which will take into consideration any
809
+ // web components if the `tabbableOptions.getShadowRoot` option was used for
810
+ // the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't
811
+ // look inside web components even if open)
812
+ tabbableNodes.find(function (node) {
813
+ return node === element;
814
+ });
436
815
  });
437
816
  };
817
+ /**
818
+ * Gets the node for the given option, which is expected to be an option that
819
+ * can be either a DOM node, a string that is a selector to get a node, `false`
820
+ * (if a node is explicitly NOT given), or a function that returns any of these
821
+ * values.
822
+ * @param {string} optionName
823
+ * @returns {undefined | false | HTMLElement | SVGElement} Returns
824
+ * `undefined` if the option is not specified; `false` if the option
825
+ * resolved to `false` (node explicitly not given); otherwise, the resolved
826
+ * DOM node.
827
+ * @throws {Error} If the option is set, not `false`, and is not, or does not
828
+ * resolve to a node.
829
+ */
830
+
438
831
 
439
832
  var getNodeForOption = function getNodeForOption(optionName) {
440
833
  var optionValue = config[optionName];
441
834
 
442
- if (!optionValue) {
443
- return null;
835
+ if (typeof optionValue === 'function') {
836
+ for (var _len2 = arguments.length, params = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
837
+ params[_key2 - 1] = arguments[_key2];
838
+ }
839
+
840
+ optionValue = optionValue.apply(void 0, params);
444
841
  }
445
842
 
446
- var node = optionValue;
843
+ if (optionValue === true) {
844
+ optionValue = undefined; // use default value
845
+ }
447
846
 
448
- if (typeof optionValue === 'string') {
449
- node = doc.querySelector(optionValue);
847
+ if (!optionValue) {
848
+ if (optionValue === undefined || optionValue === false) {
849
+ return optionValue;
850
+ } // else, empty string (invalid), null (invalid), 0 (invalid)
450
851
 
451
- if (!node) {
452
- throw new Error("`".concat(optionName, "` refers to no known node"));
453
- }
852
+
853
+ throw new Error("`".concat(optionName, "` was specified but was not a node, or did not return a node"));
454
854
  }
455
855
 
456
- if (typeof optionValue === 'function') {
457
- node = optionValue();
856
+ var node = optionValue; // could be HTMLElement, SVGElement, or non-empty string at this point
857
+
858
+ if (typeof optionValue === 'string') {
859
+ node = doc.querySelector(optionValue); // resolve to node, or null if fails
458
860
 
459
861
  if (!node) {
460
- throw new Error("`".concat(optionName, "` did not return a node"));
862
+ throw new Error("`".concat(optionName, "` as selector refers to no known node"));
461
863
  }
462
864
  }
463
865
 
@@ -465,16 +867,22 @@
465
867
  };
466
868
 
467
869
  var getInitialFocusNode = function getInitialFocusNode() {
468
- var node;
870
+ var node = getNodeForOption('initialFocus'); // false explicitly indicates we want no initialFocus at all
469
871
 
470
- if (getNodeForOption('initialFocus') !== null) {
471
- node = getNodeForOption('initialFocus');
472
- } else if (containersContain(doc.activeElement)) {
473
- node = doc.activeElement;
474
- } else {
475
- var firstTabbableGroup = state.tabbableGroups[0];
476
- var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode;
477
- node = firstTabbableNode || getNodeForOption('fallbackFocus');
872
+ if (node === false) {
873
+ return false;
874
+ }
875
+
876
+ if (node === undefined) {
877
+ // option not specified: use fallback options
878
+ if (findContainerIndex(doc.activeElement) >= 0) {
879
+ node = doc.activeElement;
880
+ } else {
881
+ var firstTabbableGroup = state.tabbableGroups[0];
882
+ var firstTabbableNode = firstTabbableGroup && firstTabbableGroup.firstTabbableNode; // NOTE: `fallbackFocus` option function cannot return `false` (not supported)
883
+
884
+ node = firstTabbableNode || getNodeForOption('fallbackFocus');
885
+ }
478
886
  }
479
887
 
480
888
  if (!node) {
@@ -485,28 +893,73 @@
485
893
  };
486
894
 
487
895
  var updateTabbableNodes = function updateTabbableNodes() {
488
- state.tabbableGroups = state.containers.map(function (container) {
489
- var tabbableNodes = tabbable(container);
896
+ state.containerGroups = state.containers.map(function (container) {
897
+ var tabbableNodes = tabbable(container, config.tabbableOptions); // NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes
898
+ // are a superset of tabbable nodes
899
+
900
+ var focusableNodes = focusable(container, config.tabbableOptions);
901
+ return {
902
+ container: container,
903
+ tabbableNodes: tabbableNodes,
904
+ focusableNodes: focusableNodes,
905
+ firstTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[0] : null,
906
+ lastTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[tabbableNodes.length - 1] : null,
490
907
 
491
- if (tabbableNodes.length > 0) {
492
- return {
493
- firstTabbableNode: tabbableNodes[0],
494
- lastTabbableNode: tabbableNodes[tabbableNodes.length - 1]
495
- };
496
- }
908
+ /**
909
+ * Finds the __tabbable__ node that follows the given node in the specified direction,
910
+ * in this container, if any.
911
+ * @param {HTMLElement} node
912
+ * @param {boolean} [forward] True if going in forward tab order; false if going
913
+ * in reverse.
914
+ * @returns {HTMLElement|undefined} The next tabbable node, if any.
915
+ */
916
+ nextTabbableNode: function nextTabbableNode(node) {
917
+ var forward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
918
+ // NOTE: If tabindex is positive (in order to manipulate the tab order separate
919
+ // from the DOM order), this __will not work__ because the list of focusableNodes,
920
+ // while it contains tabbable nodes, does not sort its nodes in any order other
921
+ // than DOM order, because it can't: Where would you place focusable (but not
922
+ // tabbable) nodes in that order? They have no order, because they aren't tabbale...
923
+ // Support for positive tabindex is already broken and hard to manage (possibly
924
+ // not supportable, TBD), so this isn't going to make things worse than they
925
+ // already are, and at least makes things better for the majority of cases where
926
+ // tabindex is either 0/unset or negative.
927
+ // FYI, positive tabindex issue: https://github.com/focus-trap/focus-trap/issues/375
928
+ var nodeIdx = focusableNodes.findIndex(function (n) {
929
+ return n === node;
930
+ });
931
+
932
+ if (nodeIdx < 0) {
933
+ return undefined;
934
+ }
497
935
 
498
- return undefined;
499
- }).filter(function (group) {
500
- return !!group;
501
- }); // remove groups with no tabbable nodes
502
- // throw if no groups have tabbable nodes and we don't have a fallback focus node either
936
+ if (forward) {
937
+ return focusableNodes.slice(nodeIdx + 1).find(function (n) {
938
+ return isTabbable(n, config.tabbableOptions);
939
+ });
940
+ }
941
+
942
+ return focusableNodes.slice(0, nodeIdx).reverse().find(function (n) {
943
+ return isTabbable(n, config.tabbableOptions);
944
+ });
945
+ }
946
+ };
947
+ });
948
+ state.tabbableGroups = state.containerGroups.filter(function (group) {
949
+ return group.tabbableNodes.length > 0;
950
+ }); // throw if no groups have tabbable nodes and we don't have a fallback focus node either
503
951
 
504
- if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus')) {
952
+ if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus') // returning false not supported for this option
953
+ ) {
505
954
  throw new Error('Your focus-trap must have at least one container with at least one tabbable node in it at all times');
506
955
  }
507
956
  };
508
957
 
509
958
  var tryFocus = function tryFocus(node) {
959
+ if (node === false) {
960
+ return;
961
+ }
962
+
510
963
  if (node === doc.activeElement) {
511
964
  return;
512
965
  }
@@ -527,19 +980,21 @@
527
980
  };
528
981
 
529
982
  var getReturnFocusNode = function getReturnFocusNode(previousActiveElement) {
530
- var node = getNodeForOption('setReturnFocus');
531
- return node ? node : previousActiveElement;
983
+ var node = getNodeForOption('setReturnFocus', previousActiveElement);
984
+ return node ? node : node === false ? false : previousActiveElement;
532
985
  }; // This needs to be done on mousedown and touchstart instead of click
533
986
  // so that it precedes the focus event.
534
987
 
535
988
 
536
989
  var checkPointerDown = function checkPointerDown(e) {
537
- if (containersContain(e.target)) {
990
+ var target = getActualTarget(e);
991
+
992
+ if (findContainerIndex(target) >= 0) {
538
993
  // allow the click since it ocurred inside the trap
539
994
  return;
540
995
  }
541
996
 
542
- if (config.clickOutsideDeactivates) {
997
+ if (valueOrHandler(config.clickOutsideDeactivates, e)) {
543
998
  // immediately deactivate the trap
544
999
  trap.deactivate({
545
1000
  // if, on deactivation, we should return focus to the node originally-focused
@@ -553,7 +1008,7 @@
553
1008
  // that was clicked, whether it's focusable or not; by setting
554
1009
  // `returnFocus: true`, we'll attempt to re-focus the node originally-focused
555
1010
  // on activation (or the configured `setReturnFocus` node)
556
- returnFocus: config.returnFocusOnDeactivate && !isFocusable(e.target)
1011
+ returnFocus: config.returnFocusOnDeactivate && !isFocusable(target, config.tabbableOptions)
557
1012
  });
558
1013
  return;
559
1014
  } // This is needed for mobile devices.
@@ -561,7 +1016,7 @@
561
1016
  // then on mobile they will be blocked anyways if `touchstart` is blocked.)
562
1017
 
563
1018
 
564
- if (config.allowOutsideClick && (typeof config.allowOutsideClick === 'boolean' ? config.allowOutsideClick : config.allowOutsideClick(e))) {
1019
+ if (valueOrHandler(config.allowOutsideClick, e)) {
565
1020
  // allow the click outside the trap to take place
566
1021
  return;
567
1022
  } // otherwise, prevent the click
@@ -572,11 +1027,12 @@
572
1027
 
573
1028
 
574
1029
  var checkFocusIn = function checkFocusIn(e) {
575
- var targetContained = containersContain(e.target); // In Firefox when you Tab out of an iframe the Document is briefly focused.
1030
+ var target = getActualTarget(e);
1031
+ var targetContained = findContainerIndex(target) >= 0; // In Firefox when you Tab out of an iframe the Document is briefly focused.
576
1032
 
577
- if (targetContained || e.target instanceof Document) {
1033
+ if (targetContained || target instanceof Document) {
578
1034
  if (targetContained) {
579
- state.mostRecentlyFocusedNode = e.target;
1035
+ state.mostRecentlyFocusedNode = target;
580
1036
  }
581
1037
  } else {
582
1038
  // escaped! pull it back in to where it just left
@@ -590,28 +1046,75 @@
590
1046
 
591
1047
 
592
1048
  var checkTab = function checkTab(e) {
1049
+ var target = getActualTarget(e);
593
1050
  updateTabbableNodes();
594
1051
  var destinationNode = null;
595
1052
 
596
1053
  if (state.tabbableGroups.length > 0) {
597
- if (e.shiftKey) {
598
- var startOfGroupIndex = state.tabbableGroups.findIndex(function (_ref) {
599
- var firstTabbableNode = _ref.firstTabbableNode;
600
- return e.target === firstTabbableNode;
1054
+ // make sure the target is actually contained in a group
1055
+ // NOTE: the target may also be the container itself if it's focusable
1056
+ // with tabIndex='-1' and was given initial focus
1057
+ var containerIndex = findContainerIndex(target);
1058
+ var containerGroup = containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined;
1059
+
1060
+ if (containerIndex < 0) {
1061
+ // target not found in any group: quite possible focus has escaped the trap,
1062
+ // so bring it back in to...
1063
+ if (e.shiftKey) {
1064
+ // ...the last node in the last group
1065
+ destinationNode = state.tabbableGroups[state.tabbableGroups.length - 1].lastTabbableNode;
1066
+ } else {
1067
+ // ...the first node in the first group
1068
+ destinationNode = state.tabbableGroups[0].firstTabbableNode;
1069
+ }
1070
+ } else if (e.shiftKey) {
1071
+ // REVERSE
1072
+ // is the target the first tabbable node in a group?
1073
+ var startOfGroupIndex = findIndex(state.tabbableGroups, function (_ref2) {
1074
+ var firstTabbableNode = _ref2.firstTabbableNode;
1075
+ return target === firstTabbableNode;
601
1076
  });
602
1077
 
1078
+ if (startOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) {
1079
+ // an exception case where the target is either the container itself, or
1080
+ // a non-tabbable node that was given focus (i.e. tabindex is negative
1081
+ // and user clicked on it or node was programmatically given focus)
1082
+ // and is not followed by any other tabbable node, in which
1083
+ // case, we should handle shift+tab as if focus were on the container's
1084
+ // first tabbable node, and go to the last tabbable node of the LAST group
1085
+ startOfGroupIndex = containerIndex;
1086
+ }
1087
+
603
1088
  if (startOfGroupIndex >= 0) {
1089
+ // YES: then shift+tab should go to the last tabbable node in the
1090
+ // previous group (and wrap around to the last tabbable node of
1091
+ // the LAST group if it's the first tabbable node of the FIRST group)
604
1092
  var destinationGroupIndex = startOfGroupIndex === 0 ? state.tabbableGroups.length - 1 : startOfGroupIndex - 1;
605
1093
  var destinationGroup = state.tabbableGroups[destinationGroupIndex];
606
1094
  destinationNode = destinationGroup.lastTabbableNode;
607
1095
  }
608
1096
  } else {
609
- var lastOfGroupIndex = state.tabbableGroups.findIndex(function (_ref2) {
610
- var lastTabbableNode = _ref2.lastTabbableNode;
611
- return e.target === lastTabbableNode;
1097
+ // FORWARD
1098
+ // is the target the last tabbable node in a group?
1099
+ var lastOfGroupIndex = findIndex(state.tabbableGroups, function (_ref3) {
1100
+ var lastTabbableNode = _ref3.lastTabbableNode;
1101
+ return target === lastTabbableNode;
612
1102
  });
613
1103
 
1104
+ if (lastOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) {
1105
+ // an exception case where the target is the container itself, or
1106
+ // a non-tabbable node that was given focus (i.e. tabindex is negative
1107
+ // and user clicked on it or node was programmatically given focus)
1108
+ // and is not followed by any other tabbable node, in which
1109
+ // case, we should handle tab as if focus were on the container's
1110
+ // last tabbable node, and go to the first tabbable node of the FIRST group
1111
+ lastOfGroupIndex = containerIndex;
1112
+ }
1113
+
614
1114
  if (lastOfGroupIndex >= 0) {
1115
+ // YES: then tab should go to the first tabbable node in the next
1116
+ // group (and wrap around to the first tabbable node of the FIRST
1117
+ // group if it's the last tabbable node of the LAST group)
615
1118
  var _destinationGroupIndex = lastOfGroupIndex === state.tabbableGroups.length - 1 ? 0 : lastOfGroupIndex + 1;
616
1119
 
617
1120
  var _destinationGroup = state.tabbableGroups[_destinationGroupIndex];
@@ -619,17 +1122,19 @@
619
1122
  }
620
1123
  }
621
1124
  } else {
1125
+ // NOTE: the fallbackFocus option does not support returning false to opt-out
622
1126
  destinationNode = getNodeForOption('fallbackFocus');
623
1127
  }
624
1128
 
625
1129
  if (destinationNode) {
626
1130
  e.preventDefault();
627
1131
  tryFocus(destinationNode);
628
- }
1132
+ } // else, let the browser take care of [shift+]tab and move the focus
1133
+
629
1134
  };
630
1135
 
631
1136
  var checkKey = function checkKey(e) {
632
- if (config.escapeDeactivates !== false && isEscapeEvent(e)) {
1137
+ if (isEscapeEvent(e) && valueOrHandler(config.escapeDeactivates, e) !== false) {
633
1138
  e.preventDefault();
634
1139
  trap.deactivate();
635
1140
  return;
@@ -642,15 +1147,17 @@
642
1147
  };
643
1148
 
644
1149
  var checkClick = function checkClick(e) {
645
- if (config.clickOutsideDeactivates) {
1150
+ var target = getActualTarget(e);
1151
+
1152
+ if (findContainerIndex(target) >= 0) {
646
1153
  return;
647
1154
  }
648
1155
 
649
- if (containersContain(e.target)) {
1156
+ if (valueOrHandler(config.clickOutsideDeactivates, e)) {
650
1157
  return;
651
1158
  }
652
1159
 
653
- if (config.allowOutsideClick && (typeof config.allowOutsideClick === 'boolean' ? config.allowOutsideClick : config.allowOutsideClick(e))) {
1160
+ if (valueOrHandler(config.allowOutsideClick, e)) {
654
1161
  return;
655
1162
  }
656
1163
 
@@ -670,7 +1177,7 @@
670
1177
  activeFocusTraps.activateTrap(trap); // Delay ensures that the focused element doesn't capture the event
671
1178
  // that caused the focus trap activation.
672
1179
 
673
- activeFocusDelay = config.delayInitialFocus ? delay(function () {
1180
+ state.delayInitialFocusTimer = config.delayInitialFocus ? delay(function () {
674
1181
  tryFocus(getInitialFocusNode());
675
1182
  }) : tryFocus(getInitialFocusNode());
676
1183
  doc.addEventListener('focusin', checkFocusIn, true);
@@ -710,22 +1217,53 @@
710
1217
 
711
1218
 
712
1219
  trap = {
1220
+ get active() {
1221
+ return state.active;
1222
+ },
1223
+
1224
+ get paused() {
1225
+ return state.paused;
1226
+ },
1227
+
713
1228
  activate: function activate(activateOptions) {
714
1229
  if (state.active) {
715
1230
  return this;
716
1231
  }
717
1232
 
718
- updateTabbableNodes();
1233
+ var onActivate = getOption(activateOptions, 'onActivate');
1234
+ var onPostActivate = getOption(activateOptions, 'onPostActivate');
1235
+ var checkCanFocusTrap = getOption(activateOptions, 'checkCanFocusTrap');
1236
+
1237
+ if (!checkCanFocusTrap) {
1238
+ updateTabbableNodes();
1239
+ }
1240
+
719
1241
  state.active = true;
720
1242
  state.paused = false;
721
1243
  state.nodeFocusedBeforeActivation = doc.activeElement;
722
- var onActivate = activateOptions && activateOptions.onActivate ? activateOptions.onActivate : config.onActivate;
723
1244
 
724
1245
  if (onActivate) {
725
1246
  onActivate();
726
1247
  }
727
1248
 
728
- addListeners();
1249
+ var finishActivation = function finishActivation() {
1250
+ if (checkCanFocusTrap) {
1251
+ updateTabbableNodes();
1252
+ }
1253
+
1254
+ addListeners();
1255
+
1256
+ if (onPostActivate) {
1257
+ onPostActivate();
1258
+ }
1259
+ };
1260
+
1261
+ if (checkCanFocusTrap) {
1262
+ checkCanFocusTrap(state.containers.concat()).then(finishActivation, finishActivation);
1263
+ return this;
1264
+ }
1265
+
1266
+ finishActivation();
729
1267
  return this;
730
1268
  },
731
1269
  deactivate: function deactivate(deactivateOptions) {
@@ -733,25 +1271,46 @@
733
1271
  return this;
734
1272
  }
735
1273
 
736
- clearTimeout(activeFocusDelay);
1274
+ var options = _objectSpread2({
1275
+ onDeactivate: config.onDeactivate,
1276
+ onPostDeactivate: config.onPostDeactivate,
1277
+ checkCanReturnFocus: config.checkCanReturnFocus
1278
+ }, deactivateOptions);
1279
+
1280
+ clearTimeout(state.delayInitialFocusTimer); // noop if undefined
1281
+
1282
+ state.delayInitialFocusTimer = undefined;
737
1283
  removeListeners();
738
1284
  state.active = false;
739
1285
  state.paused = false;
740
1286
  activeFocusTraps.deactivateTrap(trap);
741
- var onDeactivate = deactivateOptions && deactivateOptions.onDeactivate !== undefined ? deactivateOptions.onDeactivate : config.onDeactivate;
1287
+ var onDeactivate = getOption(options, 'onDeactivate');
1288
+ var onPostDeactivate = getOption(options, 'onPostDeactivate');
1289
+ var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus');
1290
+ var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate');
742
1291
 
743
1292
  if (onDeactivate) {
744
1293
  onDeactivate();
745
1294
  }
746
1295
 
747
- var returnFocus = deactivateOptions && deactivateOptions.returnFocus !== undefined ? deactivateOptions.returnFocus : config.returnFocusOnDeactivate;
748
-
749
- if (returnFocus) {
1296
+ var finishDeactivation = function finishDeactivation() {
750
1297
  delay(function () {
751
- tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
1298
+ if (returnFocus) {
1299
+ tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation));
1300
+ }
1301
+
1302
+ if (onPostDeactivate) {
1303
+ onPostDeactivate();
1304
+ }
752
1305
  });
1306
+ };
1307
+
1308
+ if (returnFocus && checkCanReturnFocus) {
1309
+ checkCanReturnFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)).then(finishDeactivation, finishDeactivation);
1310
+ return this;
753
1311
  }
754
1312
 
1313
+ finishDeactivation();
755
1314
  return this;
756
1315
  },
757
1316
  pause: function pause() {
@@ -3852,7 +4411,7 @@
3852
4411
  return null;
3853
4412
  }
3854
4413
  return (React__namespace.createElement(Backdrop, null,
3855
- React__namespace.createElement(FocusTrap, { active: !disableFocusTrap, focusTrapOptions: { clickOutsideDeactivates: true }, className: css(styles$1h.bullseye) },
4414
+ React__namespace.createElement(FocusTrap, { active: !disableFocusTrap, focusTrapOptions: { clickOutsideDeactivates: true, tabbableOptions: { displayCheck: 'none' } }, className: css(styles$1h.bullseye) },
3856
4415
  React__namespace.createElement(AboutModalBox, { className: className, "aria-labelledby": aboutModalBoxHeaderId, "aria-describedby": aboutModalBoxContentId },
3857
4416
  React__namespace.createElement(AboutModalBoxBrand, { src: brandImageSrc, alt: brandImageAlt }),
3858
4417
  React__namespace.createElement(AboutModalBoxCloseButton, { "aria-label": closeButtonAriaLabel, onClose: onClose }),
@@ -8522,6 +9081,7 @@
8522
9081
  const content = (React__namespace.createElement(FocusTrap, Object.assign({ active: focusTrapActive, focusTrapOptions: {
8523
9082
  returnFocusOnDeactivate: true,
8524
9083
  clickOutsideDeactivates: true,
9084
+ tabbableOptions: { displayCheck: 'none' },
8525
9085
  fallbackFocus: () => {
8526
9086
  // If the popover's trigger is focused but scrolled out of view,
8527
9087
  // FocusTrap will throw an error when the Enter button is used on the trigger.
@@ -9068,7 +9628,7 @@
9068
9628
  const screenReaderLabelId = `pf-context-selector-label-id-${newId}`;
9069
9629
  const searchButtonId = `pf-context-selector-search-button-id-${newId}`;
9070
9630
  const _a = this.props, { children, className, isOpen, isFullHeight, onToggle, onSelect, screenReaderLabel, toggleText, searchButtonAriaLabel, searchInputValue, onSearchInputChange, searchInputPlaceholder, onSearchButtonClick, menuAppendTo, ouiaId, ouiaSafe, isPlain, isText, footer, disableFocusTrap, isFlipEnabled } = _a, props = __rest$1(_a, ["children", "className", "isOpen", "isFullHeight", "onToggle", "onSelect", "screenReaderLabel", "toggleText", "searchButtonAriaLabel", "searchInputValue", "onSearchInputChange", "searchInputPlaceholder", "onSearchButtonClick", "menuAppendTo", "ouiaId", "ouiaSafe", "isPlain", "isText", "footer", "disableFocusTrap", "isFlipEnabled"]);
9071
- const menuContainer = (React__namespace.createElement("div", Object.assign({ className: css(styles$Q.contextSelectorMenu) }, (isFlipEnabled && { style: { position: 'revert' } })), isOpen && (React__namespace.createElement(FocusTrap, { active: !disableFocusTrap, focusTrapOptions: { clickOutsideDeactivates: true } },
9631
+ const menuContainer = (React__namespace.createElement("div", Object.assign({ className: css(styles$Q.contextSelectorMenu) }, (isFlipEnabled && { style: { position: 'revert' } })), isOpen && (React__namespace.createElement(FocusTrap, { active: !disableFocusTrap, focusTrapOptions: { clickOutsideDeactivates: true, tabbableOptions: { displayCheck: 'none' } } },
9072
9632
  React__namespace.createElement("div", { className: css(styles$Q.contextSelectorMenuSearch) },
9073
9633
  React__namespace.createElement(InputGroup, null,
9074
9634
  React__namespace.createElement(TextInput, { value: searchInputValue, type: "search", placeholder: searchInputPlaceholder, onChange: onSearchInputChange, onKeyPress: this.onEnterPressed, "aria-labelledby": searchButtonId }),
@@ -15433,7 +15993,7 @@
15433
15993
  modalBody,
15434
15994
  modalBoxFooter));
15435
15995
  return (React__namespace.createElement(Backdrop, null,
15436
- React__namespace.createElement(FocusTrap, { active: !disableFocusTrap, focusTrapOptions: { clickOutsideDeactivates: true }, className: css(styles$1h.bullseye) }, modalBox)));
15996
+ React__namespace.createElement(FocusTrap, { active: !disableFocusTrap, focusTrapOptions: { clickOutsideDeactivates: true, tabbableOptions: { displayCheck: 'none' } }, className: css(styles$1h.bullseye) }, modalBox)));
15437
15997
  };
15438
15998
  ModalContent.displayName = 'ModalContent';
15439
15999