focus-trap
Advanced tools
Comparing version 6.7.1 to 6.9.4
# Changelog | ||
## 6.9.4 | ||
### Patch Changes | ||
- f68882e: Fix docs and typings to clarify that initialFocus, fallbackFocus, and setReturnFocus options can be functions that also return selector strings. | ||
## 6.9.3 | ||
### Patch Changes | ||
- 8a8b1f1: Bump tabbable to v5.3.3 to pick up a small bug fix to web component (shadow DOM) support. | ||
## 6.9.2 | ||
### Patch Changes | ||
- ef0ce48: Handle unexpected param (true) passed as the value for the `initialFocus`, `fallbackFocus`, and `setReturnFocus` options: Ignore and perform default behavior. | ||
## 6.9.1 | ||
### Patch Changes | ||
- 83262a7: Bumps tabbable to v5.3.2 to pick-up a fix to `displayCheck=full` (default) option behavior that caused issues with detached nodes. | ||
## 6.9.0 | ||
### Minor Changes | ||
- 2a57e4b: Add new `trap.active` and `trap.paused` readonly state properties on the trap so that the trap's active/paused state can be queried. | ||
### Patch Changes | ||
- 8fd49df: Fixed bug where `clickOutsideDeactivate` handler would get called on the 'click' event even if the node clicked was in the trap. As with 'mousedown' and 'touchstart' events where this option is also used, the handler should only get called if the target node is _outside_ the trap. | ||
- c32c60a: Fixed: onDeactivate, onPostDeactivate, and checkCanReturnFocus options originally given to createFocusTrap() were not being used by default when calling `trap.deactivate({...})` with an option set even if that option set didn't specify any overrides of these options. | ||
## 6.8.1 | ||
### Patch Changes | ||
- 7c86111: | ||
- Bump tabbable to `^5.3.1` (fixing previous update which was incorrectly set to `5.3.0`). | ||
- Fix `tabbableOptions` not being used in all internal uses of tabbable APIs. | ||
- Expose `displayCheck` option in `tabbableOptions` typings and pass it through to tabbable APIs. | ||
- Add info to README about testing traps in JSDom (which is not officially supported). | ||
## 6.8.0 | ||
### Minor Changes | ||
- 21458c9: Bumps tabbable to v5.3.0 and includes all changes from the past v6.8.0 beta releases. The big new feature is opt-in Shadow DOM support in tabbable, and a new `getShadowRoot` tabbable option exposed in a new `tabbableOptions` focus-trap config option. | ||
- ⚠️ This will likely break your tests **if you're using JSDom** (e.g. with Jest). See [testing in JSDom](./README.md#testing-in-jsdom) for more info. | ||
## 6.8.0-beta.2 | ||
- When updating tabbable nodes, make sure that `getShadowRoot` tabbable option is also passed to `focusable()`. | ||
- Fix bug where having a tabbable node inside a web component in the middle of a tab sequence would cause the tab key to seemingly stop working just before focus should move to it ((#643)[https://github.com/focus-trap/focus-trap/issues/643]). | ||
- Bumps tabbable to `v5.3.0-beta.1` | ||
## 6.8.0-beta.1 | ||
- Previous beta didn't include new source. This one does. | ||
## 6.8.0-beta.0 | ||
- Adds new `tabbableOptions` configuration option, which allows specifically for the new `getShadowRoot` Tabbable configuration option: `focusTrap.createFocusTrap(rootElement, { tabbableOptions: { getShadowRoot: (node) => closedShadowRoot } })`, for example (where your code has the reference to `closedShadowRoot` previously created on `node` which Tabbable cannot find on its own). | ||
- Bumps tabbable to `v5.3.0-beta.0` | ||
## 6.7.3 | ||
### Patch Changes | ||
- ab20d3d: Fix issue with focusing negative tabindex node and then tabbing away when this node is _not_ the last node in the trap's container ((#611)[https://github.com/focus-trap/focus-trap/issues/611]) | ||
## 6.7.2 | ||
### Patch Changes | ||
- c932330: Fixed bug where tabbing forward from an element with negative tabindex that is last in the trap would result in focus remaining on that element ([565](https://github.com/focus-trap/focus-trap/issues/565)) | ||
## 6.7.1 | ||
@@ -4,0 +83,0 @@ |
/*! | ||
* focus-trap 6.7.1 | ||
* focus-trap 6.9.4 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
*/ | ||
import { tabbable, isFocusable } from 'tabbable'; | ||
import { tabbable, focusable, isTabbable, isFocusable } from 'tabbable'; | ||
@@ -12,10 +12,5 @@ function ownKeys(object, enumerableOnly) { | ||
var symbols = Object.getOwnPropertySymbols(object); | ||
if (enumerableOnly) { | ||
symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
}); | ||
} | ||
keys.push.apply(keys, symbols); | ||
enumerableOnly && (symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
})), keys.push.apply(keys, symbols); | ||
} | ||
@@ -28,15 +23,8 @@ | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i] != null ? arguments[i] : {}; | ||
if (i % 2) { | ||
ownKeys(Object(source), true).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}); | ||
} else if (Object.getOwnPropertyDescriptors) { | ||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); | ||
} else { | ||
ownKeys(Object(source)).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
var source = null != arguments[i] ? arguments[i] : {}; | ||
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
@@ -157,2 +145,4 @@ | ||
var createFocusTrap = function createFocusTrap(elements, userOptions) { | ||
// SSR: a live trap shouldn't be created in this type of environment so this | ||
// should be safe code to execute if the `document` option isn't specified | ||
var doc = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.document) || document; | ||
@@ -167,6 +157,6 @@ | ||
var state = { | ||
// containers given to createFocusTrap() | ||
// @type {Array<HTMLElement>} | ||
containers: [], | ||
// list of objects identifying the first and last tabbable nodes in all containers/groups in | ||
// the trap | ||
// list of objects identifying tabbable nodes in `containers` in the trap | ||
// NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap | ||
@@ -176,3 +166,16 @@ // is active, but the trap should never get to a state where there isn't at least one group | ||
// result in an error being thrown) | ||
// @type {Array<{ container: HTMLElement, firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>} | ||
// @type {Array<{ | ||
// container: HTMLElement, | ||
// tabbableNodes: Array<HTMLElement>, // empty if none | ||
// focusableNodes: Array<HTMLElement>, // empty if none | ||
// firstTabbableNode: HTMLElement|null, | ||
// lastTabbableNode: HTMLElement|null, | ||
// nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined | ||
// }>} | ||
containerGroups: [], | ||
// same order/length as `containers` list | ||
// references to objects in `containerGroups`, but only those that actually have | ||
// tabbable nodes in them | ||
// NOTE: same order as `containers` and `containerGroups`, but __not necessarily__ | ||
// the same length | ||
tabbableGroups: [], | ||
@@ -189,10 +192,38 @@ nodeFocusedBeforeActivation: null, | ||
/** | ||
* Gets a configuration option value. | ||
* @param {Object|undefined} configOverrideOptions If true, and option is defined in this set, | ||
* value will be taken from this object. Otherwise, value will be taken from base configuration. | ||
* @param {string} optionName Name of the option whose value is sought. | ||
* @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName` | ||
* IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used. | ||
*/ | ||
var getOption = function getOption(configOverrideOptions, optionName, configOptionName) { | ||
return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName]; | ||
}; | ||
/** | ||
* Finds the index of the container that contains the element. | ||
* @param {HTMLElement} element | ||
* @returns {number} Index of the container in either `state.containers` or | ||
* `state.containerGroups` (the order/length of these lists are the same); -1 | ||
* if the element isn't found. | ||
*/ | ||
var containersContain = function containersContain(element) { | ||
return !!(element && state.containers.some(function (container) { | ||
return container.contains(element); | ||
})); | ||
var findContainerIndex = function findContainerIndex(element) { | ||
// NOTE: search `containerGroups` because it's possible a group contains no tabbable | ||
// nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`) | ||
// and we still need to find the element in there | ||
return state.containerGroups.findIndex(function (_ref) { | ||
var container = _ref.container, | ||
tabbableNodes = _ref.tabbableNodes; | ||
return container.contains(element) || // fall back to explicit tabbable search which will take into consideration any | ||
// web components if the `tabbableOptions.getShadowRoot` option was used for | ||
// the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't | ||
// look inside web components even if open) | ||
tabbableNodes.find(function (node) { | ||
return node === element; | ||
}); | ||
}); | ||
}; | ||
@@ -225,2 +256,6 @@ /** | ||
if (optionValue === true) { | ||
optionValue = undefined; // use default value | ||
} | ||
if (!optionValue) { | ||
@@ -257,3 +292,3 @@ if (optionValue === undefined || optionValue === false) { | ||
// option not specified: use fallback options | ||
if (containersContain(doc.activeElement)) { | ||
if (findContainerIndex(doc.activeElement) >= 0) { | ||
node = doc.activeElement; | ||
@@ -276,19 +311,58 @@ } else { | ||
var updateTabbableNodes = function updateTabbableNodes() { | ||
state.tabbableGroups = state.containers.map(function (container) { | ||
var tabbableNodes = tabbable(container); | ||
state.containerGroups = state.containers.map(function (container) { | ||
var tabbableNodes = tabbable(container, config.tabbableOptions); // NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes | ||
// are a superset of tabbable nodes | ||
if (tabbableNodes.length > 0) { | ||
return { | ||
container: container, | ||
firstTabbableNode: tabbableNodes[0], | ||
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1] | ||
}; | ||
} | ||
var focusableNodes = focusable(container, config.tabbableOptions); | ||
return { | ||
container: container, | ||
tabbableNodes: tabbableNodes, | ||
focusableNodes: focusableNodes, | ||
firstTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[0] : null, | ||
lastTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[tabbableNodes.length - 1] : null, | ||
return undefined; | ||
}).filter(function (group) { | ||
return !!group; | ||
}); // remove groups with no tabbable nodes | ||
// throw if no groups have tabbable nodes and we don't have a fallback focus node either | ||
/** | ||
* Finds the __tabbable__ node that follows the given node in the specified direction, | ||
* in this container, if any. | ||
* @param {HTMLElement} node | ||
* @param {boolean} [forward] True if going in forward tab order; false if going | ||
* in reverse. | ||
* @returns {HTMLElement|undefined} The next tabbable node, if any. | ||
*/ | ||
nextTabbableNode: function nextTabbableNode(node) { | ||
var forward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
// NOTE: If tabindex is positive (in order to manipulate the tab order separate | ||
// from the DOM order), this __will not work__ because the list of focusableNodes, | ||
// while it contains tabbable nodes, does not sort its nodes in any order other | ||
// than DOM order, because it can't: Where would you place focusable (but not | ||
// tabbable) nodes in that order? They have no order, because they aren't tabbale... | ||
// Support for positive tabindex is already broken and hard to manage (possibly | ||
// not supportable, TBD), so this isn't going to make things worse than they | ||
// already are, and at least makes things better for the majority of cases where | ||
// tabindex is either 0/unset or negative. | ||
// FYI, positive tabindex issue: https://github.com/focus-trap/focus-trap/issues/375 | ||
var nodeIdx = focusableNodes.findIndex(function (n) { | ||
return n === node; | ||
}); | ||
if (nodeIdx < 0) { | ||
return undefined; | ||
} | ||
if (forward) { | ||
return focusableNodes.slice(nodeIdx + 1).find(function (n) { | ||
return isTabbable(n, config.tabbableOptions); | ||
}); | ||
} | ||
return focusableNodes.slice(0, nodeIdx).reverse().find(function (n) { | ||
return isTabbable(n, config.tabbableOptions); | ||
}); | ||
} | ||
}; | ||
}); | ||
state.tabbableGroups = state.containerGroups.filter(function (group) { | ||
return group.tabbableNodes.length > 0; | ||
}); // throw if no groups have tabbable nodes and we don't have a fallback focus node either | ||
if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus') // returning false not supported for this option | ||
@@ -334,3 +408,3 @@ ) { | ||
if (containersContain(target)) { | ||
if (findContainerIndex(target) >= 0) { | ||
// allow the click since it ocurred inside the trap | ||
@@ -354,3 +428,3 @@ return; | ||
// on activation (or the configured `setReturnFocus` node) | ||
returnFocus: config.returnFocusOnDeactivate && !isFocusable(target) | ||
returnFocus: config.returnFocusOnDeactivate && !isFocusable(target, config.tabbableOptions) | ||
}); | ||
@@ -375,3 +449,3 @@ return; | ||
var target = getActualTarget(e); | ||
var targetContained = containersContain(target); // In Firefox when you Tab out of an iframe the Document is briefly focused. | ||
var targetContained = findContainerIndex(target) >= 0; // In Firefox when you Tab out of an iframe the Document is briefly focused. | ||
@@ -400,8 +474,6 @@ if (targetContained || target instanceof Document) { | ||
// make sure the target is actually contained in a group | ||
// NOTE: the target may also be the container itself if it's tabbable | ||
// NOTE: the target may also be the container itself if it's focusable | ||
// with tabIndex='-1' and was given initial focus | ||
var containerIndex = findIndex(state.tabbableGroups, function (_ref) { | ||
var container = _ref.container; | ||
return container.contains(target); | ||
}); | ||
var containerIndex = findContainerIndex(target); | ||
var containerGroup = containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined; | ||
@@ -426,4 +498,7 @@ if (containerIndex < 0) { | ||
if (startOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === target) { | ||
// an exception case where the target is the container itself, in which | ||
if (startOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) { | ||
// an exception case where the target is either the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle shift+tab as if focus were on the container's | ||
@@ -450,4 +525,7 @@ // first tabbable node, and go to the last tabbable node of the LAST group | ||
if (lastOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === target) { | ||
// an exception case where the target is the container itself, in which | ||
if (lastOfGroupIndex < 0 && (containerGroup.container === target || isFocusable(target, config.tabbableOptions) && !isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) { | ||
// an exception case where the target is the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle tab as if focus were on the container's | ||
@@ -494,9 +572,9 @@ // last tabbable node, and go to the first tabbable node of the FIRST group | ||
var checkClick = function checkClick(e) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
var target = getActualTarget(e); | ||
if (findContainerIndex(target) >= 0) { | ||
return; | ||
} | ||
var target = getActualTarget(e); | ||
if (containersContain(target)) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
return; | ||
@@ -565,2 +643,10 @@ } | ||
trap = { | ||
get active() { | ||
return state.active; | ||
}, | ||
get paused() { | ||
return state.paused; | ||
}, | ||
activate: function activate(activateOptions) { | ||
@@ -612,2 +698,8 @@ if (state.active) { | ||
var options = _objectSpread2({ | ||
onDeactivate: config.onDeactivate, | ||
onPostDeactivate: config.onPostDeactivate, | ||
checkCanReturnFocus: config.checkCanReturnFocus | ||
}, deactivateOptions); | ||
clearTimeout(state.delayInitialFocusTimer); // noop if undefined | ||
@@ -620,5 +712,6 @@ | ||
activeFocusTraps.deactivateTrap(trap); | ||
var onDeactivate = getOption(deactivateOptions, 'onDeactivate'); | ||
var onPostDeactivate = getOption(deactivateOptions, 'onPostDeactivate'); | ||
var checkCanReturnFocus = getOption(deactivateOptions, 'checkCanReturnFocus'); | ||
var onDeactivate = getOption(options, 'onDeactivate'); | ||
var onPostDeactivate = getOption(options, 'onPostDeactivate'); | ||
var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus'); | ||
var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate'); | ||
@@ -629,4 +722,2 @@ if (onDeactivate) { | ||
var returnFocus = getOption(deactivateOptions, 'returnFocus', 'returnFocusOnDeactivate'); | ||
var finishDeactivation = function finishDeactivation() { | ||
@@ -633,0 +724,0 @@ delay(function () { |
/*! | ||
* focus-trap 6.7.1 | ||
* focus-trap 6.9.4 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
*/ | ||
import{tabbable as e,isFocusable as t}from"tabbable";function n(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var r,o=(r=[],{activateTrap:function(e){if(r.length>0){var t=r[r.length-1];t!==e&&t.pause()}var n=r.indexOf(e);-1===n||r.splice(n,1),r.push(e)},deactivateTrap:function(e){var t=r.indexOf(e);-1!==t&&r.splice(t,1),r.length>0&&r[r.length-1].unpause()}}),i=function(e){return setTimeout(e,0)},c=function(e,t){var n=-1;return e.every((function(e,a){return!t(e)||(n=a,!1)})),n},u=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a<t;a++)n[a-1]=arguments[a];return"function"==typeof e?e.apply(void 0,n):e},s=function(e){return e.target.shadowRoot&&"function"==typeof e.composedPath?e.composedPath()[0]:e.target},l=function(r,l){var f,v=(null==l?void 0:l.document)||document,b=function(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?n(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):n(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}({returnFocusOnDeactivate:!0,escapeDeactivates:!0,delayInitialFocus:!0},l),p={containers:[],tabbableGroups:[],nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1,delayInitialFocusTimer:void 0},d=function(e,t,n){return e&&void 0!==e[t]?e[t]:b[n||t]},h=function(e){return!(!e||!p.containers.some((function(t){return t.contains(e)})))},m=function(e){var t=b[e];if("function"==typeof t){for(var n=arguments.length,a=new Array(n>1?n-1:0),r=1;r<n;r++)a[r-1]=arguments[r];t=t.apply(void 0,a)}if(!t){if(void 0===t||!1===t)return t;throw new Error("`".concat(e,"` was specified but was not a node, or did not return a node"))}var o=t;if("string"==typeof t&&!(o=v.querySelector(t)))throw new Error("`".concat(e,"` as selector refers to no known node"));return o},y=function(){var e=m("initialFocus");if(!1===e)return!1;if(void 0===e)if(h(v.activeElement))e=v.activeElement;else{var t=p.tabbableGroups[0];e=t&&t.firstTabbableNode||m("fallbackFocus")}if(!e)throw new Error("Your focus-trap needs to have at least one focusable element");return e},g=function(){if(p.tabbableGroups=p.containers.map((function(t){var n=e(t);if(n.length>0)return{container:t,firstTabbableNode:n[0],lastTabbableNode:n[n.length-1]}})).filter((function(e){return!!e})),p.tabbableGroups.length<=0&&!m("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times")},w=function e(t){!1!==t&&t!==v.activeElement&&(t&&t.focus?(t.focus({preventScroll:!!b.preventScroll}),p.mostRecentlyFocusedNode=t,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(t)&&t.select()):e(y()))},O=function(e){var t=m("setReturnFocus",e);return t||!1!==t&&e},F=function(e){var n=s(e);h(n)||(u(b.clickOutsideDeactivates,e)?f.deactivate({returnFocus:b.returnFocusOnDeactivate&&!t(n)}):u(b.allowOutsideClick,e)||e.preventDefault())},E=function(e){var t=s(e),n=h(t);n||t instanceof Document?n&&(p.mostRecentlyFocusedNode=t):(e.stopImmediatePropagation(),w(p.mostRecentlyFocusedNode||y()))},T=function(e){if(function(e){return"Escape"===e.key||"Esc"===e.key||27===e.keyCode}(e)&&!1!==u(b.escapeDeactivates,e))return e.preventDefault(),void f.deactivate();(function(e){return"Tab"===e.key||9===e.keyCode})(e)&&function(e){var t=s(e);g();var n=null;if(p.tabbableGroups.length>0){var a=c(p.tabbableGroups,(function(e){return e.container.contains(t)}));if(a<0)n=e.shiftKey?p.tabbableGroups[p.tabbableGroups.length-1].lastTabbableNode:p.tabbableGroups[0].firstTabbableNode;else if(e.shiftKey){var r=c(p.tabbableGroups,(function(e){var n=e.firstTabbableNode;return t===n}));if(r<0&&p.tabbableGroups[a].container===t&&(r=a),r>=0){var o=0===r?p.tabbableGroups.length-1:r-1;n=p.tabbableGroups[o].lastTabbableNode}}else{var i=c(p.tabbableGroups,(function(e){var n=e.lastTabbableNode;return t===n}));if(i<0&&p.tabbableGroups[a].container===t&&(i=a),i>=0){var u=i===p.tabbableGroups.length-1?0:i+1;n=p.tabbableGroups[u].firstTabbableNode}}}else n=m("fallbackFocus");n&&(e.preventDefault(),w(n))}(e)},k=function(e){if(!u(b.clickOutsideDeactivates,e)){var t=s(e);h(t)||u(b.allowOutsideClick,e)||(e.preventDefault(),e.stopImmediatePropagation())}},D=function(){if(p.active)return o.activateTrap(f),p.delayInitialFocusTimer=b.delayInitialFocus?i((function(){w(y())})):w(y()),v.addEventListener("focusin",E,!0),v.addEventListener("mousedown",F,{capture:!0,passive:!1}),v.addEventListener("touchstart",F,{capture:!0,passive:!1}),v.addEventListener("click",k,{capture:!0,passive:!1}),v.addEventListener("keydown",T,{capture:!0,passive:!1}),f},G=function(){if(p.active)return v.removeEventListener("focusin",E,!0),v.removeEventListener("mousedown",F,!0),v.removeEventListener("touchstart",F,!0),v.removeEventListener("click",k,!0),v.removeEventListener("keydown",T,!0),f};return(f={activate:function(e){if(p.active)return this;var t=d(e,"onActivate"),n=d(e,"onPostActivate"),a=d(e,"checkCanFocusTrap");a||g(),p.active=!0,p.paused=!1,p.nodeFocusedBeforeActivation=v.activeElement,t&&t();var r=function(){a&&g(),D(),n&&n()};return a?(a(p.containers.concat()).then(r,r),this):(r(),this)},deactivate:function(e){if(!p.active)return this;clearTimeout(p.delayInitialFocusTimer),p.delayInitialFocusTimer=void 0,G(),p.active=!1,p.paused=!1,o.deactivateTrap(f);var t=d(e,"onDeactivate"),n=d(e,"onPostDeactivate"),a=d(e,"checkCanReturnFocus");t&&t();var r=d(e,"returnFocus","returnFocusOnDeactivate"),c=function(){i((function(){r&&w(O(p.nodeFocusedBeforeActivation)),n&&n()}))};return r&&a?(a(O(p.nodeFocusedBeforeActivation)).then(c,c),this):(c(),this)},pause:function(){return p.paused||!p.active||(p.paused=!0,G()),this},unpause:function(){return p.paused&&p.active?(p.paused=!1,g(),D(),this):this},updateContainerElements:function(e){var t=[].concat(e).filter(Boolean);return p.containers=t.map((function(e){return"string"==typeof e?v.querySelector(e):e})),p.active&&g(),this}}).updateContainerElements(r),f};export{l as createFocusTrap}; | ||
import{tabbable as e,focusable as t,isTabbable as n,isFocusable as a}from"tabbable";function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var c,u=(c=[],{activateTrap:function(e){if(c.length>0){var t=c[c.length-1];t!==e&&t.pause()}var n=c.indexOf(e);-1===n||c.splice(n,1),c.push(e)},deactivateTrap:function(e){var t=c.indexOf(e);-1!==t&&c.splice(t,1),c.length>0&&c[c.length-1].unpause()}}),s=function(e){return setTimeout(e,0)},l=function(e,t){var n=-1;return e.every((function(e,a){return!t(e)||(n=a,!1)})),n},b=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a<t;a++)n[a-1]=arguments[a];return"function"==typeof e?e.apply(void 0,n):e},f=function(e){return e.target.shadowRoot&&"function"==typeof e.composedPath?e.composedPath()[0]:e.target},v=function(r,i){var c,v=(null==i?void 0:i.document)||document,d=o({returnFocusOnDeactivate:!0,escapeDeactivates:!0,delayInitialFocus:!0},i),p={containers:[],containerGroups:[],tabbableGroups:[],nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1,delayInitialFocusTimer:void 0},h=function(e,t,n){return e&&void 0!==e[t]?e[t]:d[n||t]},m=function(e){return p.containerGroups.findIndex((function(t){var n=t.container,a=t.tabbableNodes;return n.contains(e)||a.find((function(t){return t===e}))}))},y=function(e){var t=d[e];if("function"==typeof t){for(var n=arguments.length,a=new Array(n>1?n-1:0),r=1;r<n;r++)a[r-1]=arguments[r];t=t.apply(void 0,a)}if(!0===t&&(t=void 0),!t){if(void 0===t||!1===t)return t;throw new Error("`".concat(e,"` was specified but was not a node, or did not return a node"))}var o=t;if("string"==typeof t&&!(o=v.querySelector(t)))throw new Error("`".concat(e,"` as selector refers to no known node"));return o},O=function(){var e=y("initialFocus");if(!1===e)return!1;if(void 0===e)if(m(v.activeElement)>=0)e=v.activeElement;else{var t=p.tabbableGroups[0];e=t&&t.firstTabbableNode||y("fallbackFocus")}if(!e)throw new Error("Your focus-trap needs to have at least one focusable element");return e},g=function(){if(p.containerGroups=p.containers.map((function(a){var r=e(a,d.tabbableOptions),o=t(a,d.tabbableOptions);return{container:a,tabbableNodes:r,focusableNodes:o,firstTabbableNode:r.length>0?r[0]:null,lastTabbableNode:r.length>0?r[r.length-1]:null,nextTabbableNode:function(e){var t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],a=o.findIndex((function(t){return t===e}));if(!(a<0))return t?o.slice(a+1).find((function(e){return n(e,d.tabbableOptions)})):o.slice(0,a).reverse().find((function(e){return n(e,d.tabbableOptions)}))}}})),p.tabbableGroups=p.containerGroups.filter((function(e){return e.tabbableNodes.length>0})),p.tabbableGroups.length<=0&&!y("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times")},w=function e(t){!1!==t&&t!==v.activeElement&&(t&&t.focus?(t.focus({preventScroll:!!d.preventScroll}),p.mostRecentlyFocusedNode=t,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(t)&&t.select()):e(O()))},F=function(e){var t=y("setReturnFocus",e);return t||!1!==t&&e},E=function(e){var t=f(e);m(t)>=0||(b(d.clickOutsideDeactivates,e)?c.deactivate({returnFocus:d.returnFocusOnDeactivate&&!a(t,d.tabbableOptions)}):b(d.allowOutsideClick,e)||e.preventDefault())},T=function(e){var t=f(e),n=m(t)>=0;n||t instanceof Document?n&&(p.mostRecentlyFocusedNode=t):(e.stopImmediatePropagation(),w(p.mostRecentlyFocusedNode||O()))},k=function(e){if(function(e){return"Escape"===e.key||"Esc"===e.key||27===e.keyCode}(e)&&!1!==b(d.escapeDeactivates,e))return e.preventDefault(),void c.deactivate();(function(e){return"Tab"===e.key||9===e.keyCode})(e)&&function(e){var t=f(e);g();var r=null;if(p.tabbableGroups.length>0){var o=m(t),i=o>=0?p.containerGroups[o]:void 0;if(o<0)r=e.shiftKey?p.tabbableGroups[p.tabbableGroups.length-1].lastTabbableNode:p.tabbableGroups[0].firstTabbableNode;else if(e.shiftKey){var c=l(p.tabbableGroups,(function(e){var n=e.firstTabbableNode;return t===n}));if(c<0&&(i.container===t||a(t,d.tabbableOptions)&&!n(t,d.tabbableOptions)&&!i.nextTabbableNode(t,!1))&&(c=o),c>=0){var u=0===c?p.tabbableGroups.length-1:c-1;r=p.tabbableGroups[u].lastTabbableNode}}else{var s=l(p.tabbableGroups,(function(e){var n=e.lastTabbableNode;return t===n}));if(s<0&&(i.container===t||a(t,d.tabbableOptions)&&!n(t,d.tabbableOptions)&&!i.nextTabbableNode(t))&&(s=o),s>=0){var b=s===p.tabbableGroups.length-1?0:s+1;r=p.tabbableGroups[b].firstTabbableNode}}}else r=y("fallbackFocus");r&&(e.preventDefault(),w(r))}(e)},D=function(e){var t=f(e);m(t)>=0||b(d.clickOutsideDeactivates,e)||b(d.allowOutsideClick,e)||(e.preventDefault(),e.stopImmediatePropagation())},N=function(){if(p.active)return u.activateTrap(c),p.delayInitialFocusTimer=d.delayInitialFocus?s((function(){w(O())})):w(O()),v.addEventListener("focusin",T,!0),v.addEventListener("mousedown",E,{capture:!0,passive:!1}),v.addEventListener("touchstart",E,{capture:!0,passive:!1}),v.addEventListener("click",D,{capture:!0,passive:!1}),v.addEventListener("keydown",k,{capture:!0,passive:!1}),c},G=function(){if(p.active)return v.removeEventListener("focusin",T,!0),v.removeEventListener("mousedown",E,!0),v.removeEventListener("touchstart",E,!0),v.removeEventListener("click",D,!0),v.removeEventListener("keydown",k,!0),c};return(c={get active(){return p.active},get paused(){return p.paused},activate:function(e){if(p.active)return this;var t=h(e,"onActivate"),n=h(e,"onPostActivate"),a=h(e,"checkCanFocusTrap");a||g(),p.active=!0,p.paused=!1,p.nodeFocusedBeforeActivation=v.activeElement,t&&t();var r=function(){a&&g(),N(),n&&n()};return a?(a(p.containers.concat()).then(r,r),this):(r(),this)},deactivate:function(e){if(!p.active)return this;var t=o({onDeactivate:d.onDeactivate,onPostDeactivate:d.onPostDeactivate,checkCanReturnFocus:d.checkCanReturnFocus},e);clearTimeout(p.delayInitialFocusTimer),p.delayInitialFocusTimer=void 0,G(),p.active=!1,p.paused=!1,u.deactivateTrap(c);var n=h(t,"onDeactivate"),a=h(t,"onPostDeactivate"),r=h(t,"checkCanReturnFocus"),i=h(t,"returnFocus","returnFocusOnDeactivate");n&&n();var l=function(){s((function(){i&&w(F(p.nodeFocusedBeforeActivation)),a&&a()}))};return i&&r?(r(F(p.nodeFocusedBeforeActivation)).then(l,l),this):(l(),this)},pause:function(){return p.paused||!p.active||(p.paused=!0,G()),this},unpause:function(){return p.paused&&p.active?(p.paused=!1,g(),N(),this):this},updateContainerElements:function(e){var t=[].concat(e).filter(Boolean);return p.containers=t.map((function(e){return"string"==typeof e?v.querySelector(e):e})),p.active&&g(),this}}).updateContainerElements(r),c};export{v as createFocusTrap}; | ||
//# sourceMappingURL=focus-trap.esm.min.js.map |
/*! | ||
* focus-trap 6.7.1 | ||
* focus-trap 6.9.4 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
@@ -16,10 +16,5 @@ */ | ||
var symbols = Object.getOwnPropertySymbols(object); | ||
if (enumerableOnly) { | ||
symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
}); | ||
} | ||
keys.push.apply(keys, symbols); | ||
enumerableOnly && (symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
})), keys.push.apply(keys, symbols); | ||
} | ||
@@ -32,15 +27,8 @@ | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i] != null ? arguments[i] : {}; | ||
if (i % 2) { | ||
ownKeys(Object(source), true).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}); | ||
} else if (Object.getOwnPropertyDescriptors) { | ||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); | ||
} else { | ||
ownKeys(Object(source)).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
var source = null != arguments[i] ? arguments[i] : {}; | ||
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
@@ -161,2 +149,4 @@ | ||
var createFocusTrap = function createFocusTrap(elements, userOptions) { | ||
// SSR: a live trap shouldn't be created in this type of environment so this | ||
// should be safe code to execute if the `document` option isn't specified | ||
var doc = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.document) || document; | ||
@@ -171,6 +161,6 @@ | ||
var state = { | ||
// containers given to createFocusTrap() | ||
// @type {Array<HTMLElement>} | ||
containers: [], | ||
// list of objects identifying the first and last tabbable nodes in all containers/groups in | ||
// the trap | ||
// list of objects identifying tabbable nodes in `containers` in the trap | ||
// NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap | ||
@@ -180,3 +170,16 @@ // is active, but the trap should never get to a state where there isn't at least one group | ||
// result in an error being thrown) | ||
// @type {Array<{ container: HTMLElement, firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>} | ||
// @type {Array<{ | ||
// container: HTMLElement, | ||
// tabbableNodes: Array<HTMLElement>, // empty if none | ||
// focusableNodes: Array<HTMLElement>, // empty if none | ||
// firstTabbableNode: HTMLElement|null, | ||
// lastTabbableNode: HTMLElement|null, | ||
// nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined | ||
// }>} | ||
containerGroups: [], | ||
// same order/length as `containers` list | ||
// references to objects in `containerGroups`, but only those that actually have | ||
// tabbable nodes in them | ||
// NOTE: same order as `containers` and `containerGroups`, but __not necessarily__ | ||
// the same length | ||
tabbableGroups: [], | ||
@@ -193,10 +196,38 @@ nodeFocusedBeforeActivation: null, | ||
/** | ||
* Gets a configuration option value. | ||
* @param {Object|undefined} configOverrideOptions If true, and option is defined in this set, | ||
* value will be taken from this object. Otherwise, value will be taken from base configuration. | ||
* @param {string} optionName Name of the option whose value is sought. | ||
* @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName` | ||
* IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used. | ||
*/ | ||
var getOption = function getOption(configOverrideOptions, optionName, configOptionName) { | ||
return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName]; | ||
}; | ||
/** | ||
* Finds the index of the container that contains the element. | ||
* @param {HTMLElement} element | ||
* @returns {number} Index of the container in either `state.containers` or | ||
* `state.containerGroups` (the order/length of these lists are the same); -1 | ||
* if the element isn't found. | ||
*/ | ||
var containersContain = function containersContain(element) { | ||
return !!(element && state.containers.some(function (container) { | ||
return container.contains(element); | ||
})); | ||
var findContainerIndex = function findContainerIndex(element) { | ||
// NOTE: search `containerGroups` because it's possible a group contains no tabbable | ||
// nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`) | ||
// and we still need to find the element in there | ||
return state.containerGroups.findIndex(function (_ref) { | ||
var container = _ref.container, | ||
tabbableNodes = _ref.tabbableNodes; | ||
return container.contains(element) || // fall back to explicit tabbable search which will take into consideration any | ||
// web components if the `tabbableOptions.getShadowRoot` option was used for | ||
// the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't | ||
// look inside web components even if open) | ||
tabbableNodes.find(function (node) { | ||
return node === element; | ||
}); | ||
}); | ||
}; | ||
@@ -229,2 +260,6 @@ /** | ||
if (optionValue === true) { | ||
optionValue = undefined; // use default value | ||
} | ||
if (!optionValue) { | ||
@@ -261,3 +296,3 @@ if (optionValue === undefined || optionValue === false) { | ||
// option not specified: use fallback options | ||
if (containersContain(doc.activeElement)) { | ||
if (findContainerIndex(doc.activeElement) >= 0) { | ||
node = doc.activeElement; | ||
@@ -280,19 +315,58 @@ } else { | ||
var updateTabbableNodes = function updateTabbableNodes() { | ||
state.tabbableGroups = state.containers.map(function (container) { | ||
var tabbableNodes = tabbable.tabbable(container); | ||
state.containerGroups = state.containers.map(function (container) { | ||
var tabbableNodes = tabbable.tabbable(container, config.tabbableOptions); // NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes | ||
// are a superset of tabbable nodes | ||
if (tabbableNodes.length > 0) { | ||
return { | ||
container: container, | ||
firstTabbableNode: tabbableNodes[0], | ||
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1] | ||
}; | ||
} | ||
var focusableNodes = tabbable.focusable(container, config.tabbableOptions); | ||
return { | ||
container: container, | ||
tabbableNodes: tabbableNodes, | ||
focusableNodes: focusableNodes, | ||
firstTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[0] : null, | ||
lastTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[tabbableNodes.length - 1] : null, | ||
return undefined; | ||
}).filter(function (group) { | ||
return !!group; | ||
}); // remove groups with no tabbable nodes | ||
// throw if no groups have tabbable nodes and we don't have a fallback focus node either | ||
/** | ||
* Finds the __tabbable__ node that follows the given node in the specified direction, | ||
* in this container, if any. | ||
* @param {HTMLElement} node | ||
* @param {boolean} [forward] True if going in forward tab order; false if going | ||
* in reverse. | ||
* @returns {HTMLElement|undefined} The next tabbable node, if any. | ||
*/ | ||
nextTabbableNode: function nextTabbableNode(node) { | ||
var forward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
// NOTE: If tabindex is positive (in order to manipulate the tab order separate | ||
// from the DOM order), this __will not work__ because the list of focusableNodes, | ||
// while it contains tabbable nodes, does not sort its nodes in any order other | ||
// than DOM order, because it can't: Where would you place focusable (but not | ||
// tabbable) nodes in that order? They have no order, because they aren't tabbale... | ||
// Support for positive tabindex is already broken and hard to manage (possibly | ||
// not supportable, TBD), so this isn't going to make things worse than they | ||
// already are, and at least makes things better for the majority of cases where | ||
// tabindex is either 0/unset or negative. | ||
// FYI, positive tabindex issue: https://github.com/focus-trap/focus-trap/issues/375 | ||
var nodeIdx = focusableNodes.findIndex(function (n) { | ||
return n === node; | ||
}); | ||
if (nodeIdx < 0) { | ||
return undefined; | ||
} | ||
if (forward) { | ||
return focusableNodes.slice(nodeIdx + 1).find(function (n) { | ||
return tabbable.isTabbable(n, config.tabbableOptions); | ||
}); | ||
} | ||
return focusableNodes.slice(0, nodeIdx).reverse().find(function (n) { | ||
return tabbable.isTabbable(n, config.tabbableOptions); | ||
}); | ||
} | ||
}; | ||
}); | ||
state.tabbableGroups = state.containerGroups.filter(function (group) { | ||
return group.tabbableNodes.length > 0; | ||
}); // throw if no groups have tabbable nodes and we don't have a fallback focus node either | ||
if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus') // returning false not supported for this option | ||
@@ -338,3 +412,3 @@ ) { | ||
if (containersContain(target)) { | ||
if (findContainerIndex(target) >= 0) { | ||
// allow the click since it ocurred inside the trap | ||
@@ -358,3 +432,3 @@ return; | ||
// on activation (or the configured `setReturnFocus` node) | ||
returnFocus: config.returnFocusOnDeactivate && !tabbable.isFocusable(target) | ||
returnFocus: config.returnFocusOnDeactivate && !tabbable.isFocusable(target, config.tabbableOptions) | ||
}); | ||
@@ -379,3 +453,3 @@ return; | ||
var target = getActualTarget(e); | ||
var targetContained = containersContain(target); // In Firefox when you Tab out of an iframe the Document is briefly focused. | ||
var targetContained = findContainerIndex(target) >= 0; // In Firefox when you Tab out of an iframe the Document is briefly focused. | ||
@@ -404,8 +478,6 @@ if (targetContained || target instanceof Document) { | ||
// make sure the target is actually contained in a group | ||
// NOTE: the target may also be the container itself if it's tabbable | ||
// NOTE: the target may also be the container itself if it's focusable | ||
// with tabIndex='-1' and was given initial focus | ||
var containerIndex = findIndex(state.tabbableGroups, function (_ref) { | ||
var container = _ref.container; | ||
return container.contains(target); | ||
}); | ||
var containerIndex = findContainerIndex(target); | ||
var containerGroup = containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined; | ||
@@ -430,4 +502,7 @@ if (containerIndex < 0) { | ||
if (startOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === target) { | ||
// an exception case where the target is the container itself, in which | ||
if (startOfGroupIndex < 0 && (containerGroup.container === target || tabbable.isFocusable(target, config.tabbableOptions) && !tabbable.isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) { | ||
// an exception case where the target is either the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle shift+tab as if focus were on the container's | ||
@@ -454,4 +529,7 @@ // first tabbable node, and go to the last tabbable node of the LAST group | ||
if (lastOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === target) { | ||
// an exception case where the target is the container itself, in which | ||
if (lastOfGroupIndex < 0 && (containerGroup.container === target || tabbable.isFocusable(target, config.tabbableOptions) && !tabbable.isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) { | ||
// an exception case where the target is the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle tab as if focus were on the container's | ||
@@ -498,9 +576,9 @@ // last tabbable node, and go to the first tabbable node of the FIRST group | ||
var checkClick = function checkClick(e) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
var target = getActualTarget(e); | ||
if (findContainerIndex(target) >= 0) { | ||
return; | ||
} | ||
var target = getActualTarget(e); | ||
if (containersContain(target)) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
return; | ||
@@ -569,2 +647,10 @@ } | ||
trap = { | ||
get active() { | ||
return state.active; | ||
}, | ||
get paused() { | ||
return state.paused; | ||
}, | ||
activate: function activate(activateOptions) { | ||
@@ -616,2 +702,8 @@ if (state.active) { | ||
var options = _objectSpread2({ | ||
onDeactivate: config.onDeactivate, | ||
onPostDeactivate: config.onPostDeactivate, | ||
checkCanReturnFocus: config.checkCanReturnFocus | ||
}, deactivateOptions); | ||
clearTimeout(state.delayInitialFocusTimer); // noop if undefined | ||
@@ -624,5 +716,6 @@ | ||
activeFocusTraps.deactivateTrap(trap); | ||
var onDeactivate = getOption(deactivateOptions, 'onDeactivate'); | ||
var onPostDeactivate = getOption(deactivateOptions, 'onPostDeactivate'); | ||
var checkCanReturnFocus = getOption(deactivateOptions, 'checkCanReturnFocus'); | ||
var onDeactivate = getOption(options, 'onDeactivate'); | ||
var onPostDeactivate = getOption(options, 'onPostDeactivate'); | ||
var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus'); | ||
var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate'); | ||
@@ -633,4 +726,2 @@ if (onDeactivate) { | ||
var returnFocus = getOption(deactivateOptions, 'returnFocus', 'returnFocusOnDeactivate'); | ||
var finishDeactivation = function finishDeactivation() { | ||
@@ -637,0 +728,0 @@ delay(function () { |
/*! | ||
* focus-trap 6.7.1 | ||
* focus-trap 6.9.4 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
*/ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("tabbable");function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function n(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var a,r=(a=[],{activateTrap:function(e){if(a.length>0){var t=a[a.length-1];t!==e&&t.pause()}var n=a.indexOf(e);-1===n||a.splice(n,1),a.push(e)},deactivateTrap:function(e){var t=a.indexOf(e);-1!==t&&a.splice(t,1),a.length>0&&a[a.length-1].unpause()}}),o=function(e){return setTimeout(e,0)},i=function(e,t){var n=-1;return e.every((function(e,a){return!t(e)||(n=a,!1)})),n},c=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a<t;a++)n[a-1]=arguments[a];return"function"==typeof e?e.apply(void 0,n):e},u=function(e){return e.target.shadowRoot&&"function"==typeof e.composedPath?e.composedPath()[0]:e.target};exports.createFocusTrap=function(a,s){var l,f=(null==s?void 0:s.document)||document,b=function(e){for(var a=1;a<arguments.length;a++){var r=null!=arguments[a]?arguments[a]:{};a%2?t(Object(r),!0).forEach((function(t){n(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):t(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}({returnFocusOnDeactivate:!0,escapeDeactivates:!0,delayInitialFocus:!0},s),v={containers:[],tabbableGroups:[],nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1,delayInitialFocusTimer:void 0},p=function(e,t,n){return e&&void 0!==e[t]?e[t]:b[n||t]},d=function(e){return!(!e||!v.containers.some((function(t){return t.contains(e)})))},h=function(e){var t=b[e];if("function"==typeof t){for(var n=arguments.length,a=new Array(n>1?n-1:0),r=1;r<n;r++)a[r-1]=arguments[r];t=t.apply(void 0,a)}if(!t){if(void 0===t||!1===t)return t;throw new Error("`".concat(e,"` was specified but was not a node, or did not return a node"))}var o=t;if("string"==typeof t&&!(o=f.querySelector(t)))throw new Error("`".concat(e,"` as selector refers to no known node"));return o},y=function(){var e=h("initialFocus");if(!1===e)return!1;if(void 0===e)if(d(f.activeElement))e=f.activeElement;else{var t=v.tabbableGroups[0];e=t&&t.firstTabbableNode||h("fallbackFocus")}if(!e)throw new Error("Your focus-trap needs to have at least one focusable element");return e},m=function(){if(v.tabbableGroups=v.containers.map((function(t){var n=e.tabbable(t);if(n.length>0)return{container:t,firstTabbableNode:n[0],lastTabbableNode:n[n.length-1]}})).filter((function(e){return!!e})),v.tabbableGroups.length<=0&&!h("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times")},g=function e(t){!1!==t&&t!==f.activeElement&&(t&&t.focus?(t.focus({preventScroll:!!b.preventScroll}),v.mostRecentlyFocusedNode=t,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(t)&&t.select()):e(y()))},w=function(e){var t=h("setReturnFocus",e);return t||!1!==t&&e},F=function(t){var n=u(t);d(n)||(c(b.clickOutsideDeactivates,t)?l.deactivate({returnFocus:b.returnFocusOnDeactivate&&!e.isFocusable(n)}):c(b.allowOutsideClick,t)||t.preventDefault())},O=function(e){var t=u(e),n=d(t);n||t instanceof Document?n&&(v.mostRecentlyFocusedNode=t):(e.stopImmediatePropagation(),g(v.mostRecentlyFocusedNode||y()))},E=function(e){if(function(e){return"Escape"===e.key||"Esc"===e.key||27===e.keyCode}(e)&&!1!==c(b.escapeDeactivates,e))return e.preventDefault(),void l.deactivate();(function(e){return"Tab"===e.key||9===e.keyCode})(e)&&function(e){var t=u(e);m();var n=null;if(v.tabbableGroups.length>0){var a=i(v.tabbableGroups,(function(e){return e.container.contains(t)}));if(a<0)n=e.shiftKey?v.tabbableGroups[v.tabbableGroups.length-1].lastTabbableNode:v.tabbableGroups[0].firstTabbableNode;else if(e.shiftKey){var r=i(v.tabbableGroups,(function(e){var n=e.firstTabbableNode;return t===n}));if(r<0&&v.tabbableGroups[a].container===t&&(r=a),r>=0){var o=0===r?v.tabbableGroups.length-1:r-1;n=v.tabbableGroups[o].lastTabbableNode}}else{var c=i(v.tabbableGroups,(function(e){var n=e.lastTabbableNode;return t===n}));if(c<0&&v.tabbableGroups[a].container===t&&(c=a),c>=0){var s=c===v.tabbableGroups.length-1?0:c+1;n=v.tabbableGroups[s].firstTabbableNode}}}else n=h("fallbackFocus");n&&(e.preventDefault(),g(n))}(e)},T=function(e){if(!c(b.clickOutsideDeactivates,e)){var t=u(e);d(t)||c(b.allowOutsideClick,e)||(e.preventDefault(),e.stopImmediatePropagation())}},k=function(){if(v.active)return r.activateTrap(l),v.delayInitialFocusTimer=b.delayInitialFocus?o((function(){g(y())})):g(y()),f.addEventListener("focusin",O,!0),f.addEventListener("mousedown",F,{capture:!0,passive:!1}),f.addEventListener("touchstart",F,{capture:!0,passive:!1}),f.addEventListener("click",T,{capture:!0,passive:!1}),f.addEventListener("keydown",E,{capture:!0,passive:!1}),l},D=function(){if(v.active)return f.removeEventListener("focusin",O,!0),f.removeEventListener("mousedown",F,!0),f.removeEventListener("touchstart",F,!0),f.removeEventListener("click",T,!0),f.removeEventListener("keydown",E,!0),l};return(l={activate:function(e){if(v.active)return this;var t=p(e,"onActivate"),n=p(e,"onPostActivate"),a=p(e,"checkCanFocusTrap");a||m(),v.active=!0,v.paused=!1,v.nodeFocusedBeforeActivation=f.activeElement,t&&t();var r=function(){a&&m(),k(),n&&n()};return a?(a(v.containers.concat()).then(r,r),this):(r(),this)},deactivate:function(e){if(!v.active)return this;clearTimeout(v.delayInitialFocusTimer),v.delayInitialFocusTimer=void 0,D(),v.active=!1,v.paused=!1,r.deactivateTrap(l);var t=p(e,"onDeactivate"),n=p(e,"onPostDeactivate"),a=p(e,"checkCanReturnFocus");t&&t();var i=p(e,"returnFocus","returnFocusOnDeactivate"),c=function(){o((function(){i&&g(w(v.nodeFocusedBeforeActivation)),n&&n()}))};return i&&a?(a(w(v.nodeFocusedBeforeActivation)).then(c,c),this):(c(),this)},pause:function(){return v.paused||!v.active||(v.paused=!0,D()),this},unpause:function(){return v.paused&&v.active?(v.paused=!1,m(),k(),this):this},updateContainerElements:function(e){var t=[].concat(e).filter(Boolean);return v.containers=t.map((function(e){return"string"==typeof e?f.querySelector(e):e})),v.active&&m(),this}}).updateContainerElements(a),l}; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("tabbable");function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function n(e){for(var n=1;n<arguments.length;n++){var r=null!=arguments[n]?arguments[n]:{};n%2?t(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):t(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var r,o=(r=[],{activateTrap:function(e){if(r.length>0){var t=r[r.length-1];t!==e&&t.pause()}var n=r.indexOf(e);-1===n||r.splice(n,1),r.push(e)},deactivateTrap:function(e){var t=r.indexOf(e);-1!==t&&r.splice(t,1),r.length>0&&r[r.length-1].unpause()}}),i=function(e){return setTimeout(e,0)},c=function(e,t){var n=-1;return e.every((function(e,a){return!t(e)||(n=a,!1)})),n},u=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a<t;a++)n[a-1]=arguments[a];return"function"==typeof e?e.apply(void 0,n):e},s=function(e){return e.target.shadowRoot&&"function"==typeof e.composedPath?e.composedPath()[0]:e.target};exports.createFocusTrap=function(t,a){var r,l=(null==a?void 0:a.document)||document,b=n({returnFocusOnDeactivate:!0,escapeDeactivates:!0,delayInitialFocus:!0},a),f={containers:[],containerGroups:[],tabbableGroups:[],nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1,delayInitialFocusTimer:void 0},v=function(e,t,n){return e&&void 0!==e[t]?e[t]:b[n||t]},d=function(e){return f.containerGroups.findIndex((function(t){var n=t.container,a=t.tabbableNodes;return n.contains(e)||a.find((function(t){return t===e}))}))},p=function(e){var t=b[e];if("function"==typeof t){for(var n=arguments.length,a=new Array(n>1?n-1:0),r=1;r<n;r++)a[r-1]=arguments[r];t=t.apply(void 0,a)}if(!0===t&&(t=void 0),!t){if(void 0===t||!1===t)return t;throw new Error("`".concat(e,"` was specified but was not a node, or did not return a node"))}var o=t;if("string"==typeof t&&!(o=l.querySelector(t)))throw new Error("`".concat(e,"` as selector refers to no known node"));return o},h=function(){var e=p("initialFocus");if(!1===e)return!1;if(void 0===e)if(d(l.activeElement)>=0)e=l.activeElement;else{var t=f.tabbableGroups[0];e=t&&t.firstTabbableNode||p("fallbackFocus")}if(!e)throw new Error("Your focus-trap needs to have at least one focusable element");return e},y=function(){if(f.containerGroups=f.containers.map((function(t){var n=e.tabbable(t,b.tabbableOptions),a=e.focusable(t,b.tabbableOptions);return{container:t,tabbableNodes:n,focusableNodes:a,firstTabbableNode:n.length>0?n[0]:null,lastTabbableNode:n.length>0?n[n.length-1]:null,nextTabbableNode:function(t){var n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],r=a.findIndex((function(e){return e===t}));if(!(r<0))return n?a.slice(r+1).find((function(t){return e.isTabbable(t,b.tabbableOptions)})):a.slice(0,r).reverse().find((function(t){return e.isTabbable(t,b.tabbableOptions)}))}}})),f.tabbableGroups=f.containerGroups.filter((function(e){return e.tabbableNodes.length>0})),f.tabbableGroups.length<=0&&!p("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times")},m=function e(t){!1!==t&&t!==l.activeElement&&(t&&t.focus?(t.focus({preventScroll:!!b.preventScroll}),f.mostRecentlyFocusedNode=t,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(t)&&t.select()):e(h()))},O=function(e){var t=p("setReturnFocus",e);return t||!1!==t&&e},g=function(t){var n=s(t);d(n)>=0||(u(b.clickOutsideDeactivates,t)?r.deactivate({returnFocus:b.returnFocusOnDeactivate&&!e.isFocusable(n,b.tabbableOptions)}):u(b.allowOutsideClick,t)||t.preventDefault())},F=function(e){var t=s(e),n=d(t)>=0;n||t instanceof Document?n&&(f.mostRecentlyFocusedNode=t):(e.stopImmediatePropagation(),m(f.mostRecentlyFocusedNode||h()))},w=function(t){if(function(e){return"Escape"===e.key||"Esc"===e.key||27===e.keyCode}(t)&&!1!==u(b.escapeDeactivates,t))return t.preventDefault(),void r.deactivate();(function(e){return"Tab"===e.key||9===e.keyCode})(t)&&function(t){var n=s(t);y();var a=null;if(f.tabbableGroups.length>0){var r=d(n),o=r>=0?f.containerGroups[r]:void 0;if(r<0)a=t.shiftKey?f.tabbableGroups[f.tabbableGroups.length-1].lastTabbableNode:f.tabbableGroups[0].firstTabbableNode;else if(t.shiftKey){var i=c(f.tabbableGroups,(function(e){var t=e.firstTabbableNode;return n===t}));if(i<0&&(o.container===n||e.isFocusable(n,b.tabbableOptions)&&!e.isTabbable(n,b.tabbableOptions)&&!o.nextTabbableNode(n,!1))&&(i=r),i>=0){var u=0===i?f.tabbableGroups.length-1:i-1;a=f.tabbableGroups[u].lastTabbableNode}}else{var l=c(f.tabbableGroups,(function(e){var t=e.lastTabbableNode;return n===t}));if(l<0&&(o.container===n||e.isFocusable(n,b.tabbableOptions)&&!e.isTabbable(n,b.tabbableOptions)&&!o.nextTabbableNode(n))&&(l=r),l>=0){var v=l===f.tabbableGroups.length-1?0:l+1;a=f.tabbableGroups[v].firstTabbableNode}}}else a=p("fallbackFocus");a&&(t.preventDefault(),m(a))}(t)},T=function(e){var t=s(e);d(t)>=0||u(b.clickOutsideDeactivates,e)||u(b.allowOutsideClick,e)||(e.preventDefault(),e.stopImmediatePropagation())},E=function(){if(f.active)return o.activateTrap(r),f.delayInitialFocusTimer=b.delayInitialFocus?i((function(){m(h())})):m(h()),l.addEventListener("focusin",F,!0),l.addEventListener("mousedown",g,{capture:!0,passive:!1}),l.addEventListener("touchstart",g,{capture:!0,passive:!1}),l.addEventListener("click",T,{capture:!0,passive:!1}),l.addEventListener("keydown",w,{capture:!0,passive:!1}),r},k=function(){if(f.active)return l.removeEventListener("focusin",F,!0),l.removeEventListener("mousedown",g,!0),l.removeEventListener("touchstart",g,!0),l.removeEventListener("click",T,!0),l.removeEventListener("keydown",w,!0),r};return(r={get active(){return f.active},get paused(){return f.paused},activate:function(e){if(f.active)return this;var t=v(e,"onActivate"),n=v(e,"onPostActivate"),a=v(e,"checkCanFocusTrap");a||y(),f.active=!0,f.paused=!1,f.nodeFocusedBeforeActivation=l.activeElement,t&&t();var r=function(){a&&y(),E(),n&&n()};return a?(a(f.containers.concat()).then(r,r),this):(r(),this)},deactivate:function(e){if(!f.active)return this;var t=n({onDeactivate:b.onDeactivate,onPostDeactivate:b.onPostDeactivate,checkCanReturnFocus:b.checkCanReturnFocus},e);clearTimeout(f.delayInitialFocusTimer),f.delayInitialFocusTimer=void 0,k(),f.active=!1,f.paused=!1,o.deactivateTrap(r);var a=v(t,"onDeactivate"),c=v(t,"onPostDeactivate"),u=v(t,"checkCanReturnFocus"),s=v(t,"returnFocus","returnFocusOnDeactivate");a&&a();var l=function(){i((function(){s&&m(O(f.nodeFocusedBeforeActivation)),c&&c()}))};return s&&u?(u(O(f.nodeFocusedBeforeActivation)).then(l,l),this):(l(),this)},pause:function(){return f.paused||!f.active||(f.paused=!0,k()),this},unpause:function(){return f.paused&&f.active?(f.paused=!1,y(),E(),this):this},updateContainerElements:function(e){var t=[].concat(e).filter(Boolean);return f.containers=t.map((function(e){return"string"==typeof e?l.querySelector(e):e})),f.active&&y(),this}}).updateContainerElements(t),r}; | ||
//# sourceMappingURL=focus-trap.min.js.map |
/*! | ||
* focus-trap 6.7.1 | ||
* focus-trap 6.9.4 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
@@ -21,10 +21,5 @@ */ | ||
var symbols = Object.getOwnPropertySymbols(object); | ||
if (enumerableOnly) { | ||
symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
}); | ||
} | ||
keys.push.apply(keys, symbols); | ||
enumerableOnly && (symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
})), keys.push.apply(keys, symbols); | ||
} | ||
@@ -37,15 +32,8 @@ | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i] != null ? arguments[i] : {}; | ||
if (i % 2) { | ||
ownKeys(Object(source), true).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}); | ||
} else if (Object.getOwnPropertyDescriptors) { | ||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); | ||
} else { | ||
ownKeys(Object(source)).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
var source = null != arguments[i] ? arguments[i] : {}; | ||
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
@@ -166,2 +154,4 @@ | ||
var createFocusTrap = function createFocusTrap(elements, userOptions) { | ||
// SSR: a live trap shouldn't be created in this type of environment so this | ||
// should be safe code to execute if the `document` option isn't specified | ||
var doc = (userOptions === null || userOptions === void 0 ? void 0 : userOptions.document) || document; | ||
@@ -176,6 +166,6 @@ | ||
var state = { | ||
// containers given to createFocusTrap() | ||
// @type {Array<HTMLElement>} | ||
containers: [], | ||
// list of objects identifying the first and last tabbable nodes in all containers/groups in | ||
// the trap | ||
// list of objects identifying tabbable nodes in `containers` in the trap | ||
// NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap | ||
@@ -185,3 +175,16 @@ // is active, but the trap should never get to a state where there isn't at least one group | ||
// result in an error being thrown) | ||
// @type {Array<{ container: HTMLElement, firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>} | ||
// @type {Array<{ | ||
// container: HTMLElement, | ||
// tabbableNodes: Array<HTMLElement>, // empty if none | ||
// focusableNodes: Array<HTMLElement>, // empty if none | ||
// firstTabbableNode: HTMLElement|null, | ||
// lastTabbableNode: HTMLElement|null, | ||
// nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined | ||
// }>} | ||
containerGroups: [], | ||
// same order/length as `containers` list | ||
// references to objects in `containerGroups`, but only those that actually have | ||
// tabbable nodes in them | ||
// NOTE: same order as `containers` and `containerGroups`, but __not necessarily__ | ||
// the same length | ||
tabbableGroups: [], | ||
@@ -198,10 +201,38 @@ nodeFocusedBeforeActivation: null, | ||
/** | ||
* Gets a configuration option value. | ||
* @param {Object|undefined} configOverrideOptions If true, and option is defined in this set, | ||
* value will be taken from this object. Otherwise, value will be taken from base configuration. | ||
* @param {string} optionName Name of the option whose value is sought. | ||
* @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName` | ||
* IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used. | ||
*/ | ||
var getOption = function getOption(configOverrideOptions, optionName, configOptionName) { | ||
return configOverrideOptions && configOverrideOptions[optionName] !== undefined ? configOverrideOptions[optionName] : config[configOptionName || optionName]; | ||
}; | ||
/** | ||
* Finds the index of the container that contains the element. | ||
* @param {HTMLElement} element | ||
* @returns {number} Index of the container in either `state.containers` or | ||
* `state.containerGroups` (the order/length of these lists are the same); -1 | ||
* if the element isn't found. | ||
*/ | ||
var containersContain = function containersContain(element) { | ||
return !!(element && state.containers.some(function (container) { | ||
return container.contains(element); | ||
})); | ||
var findContainerIndex = function findContainerIndex(element) { | ||
// NOTE: search `containerGroups` because it's possible a group contains no tabbable | ||
// nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`) | ||
// and we still need to find the element in there | ||
return state.containerGroups.findIndex(function (_ref) { | ||
var container = _ref.container, | ||
tabbableNodes = _ref.tabbableNodes; | ||
return container.contains(element) || // fall back to explicit tabbable search which will take into consideration any | ||
// web components if the `tabbableOptions.getShadowRoot` option was used for | ||
// the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't | ||
// look inside web components even if open) | ||
tabbableNodes.find(function (node) { | ||
return node === element; | ||
}); | ||
}); | ||
}; | ||
@@ -234,2 +265,6 @@ /** | ||
if (optionValue === true) { | ||
optionValue = undefined; // use default value | ||
} | ||
if (!optionValue) { | ||
@@ -266,3 +301,3 @@ if (optionValue === undefined || optionValue === false) { | ||
// option not specified: use fallback options | ||
if (containersContain(doc.activeElement)) { | ||
if (findContainerIndex(doc.activeElement) >= 0) { | ||
node = doc.activeElement; | ||
@@ -285,19 +320,58 @@ } else { | ||
var updateTabbableNodes = function updateTabbableNodes() { | ||
state.tabbableGroups = state.containers.map(function (container) { | ||
var tabbableNodes = tabbable.tabbable(container); | ||
state.containerGroups = state.containers.map(function (container) { | ||
var tabbableNodes = tabbable.tabbable(container, config.tabbableOptions); // NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes | ||
// are a superset of tabbable nodes | ||
if (tabbableNodes.length > 0) { | ||
return { | ||
container: container, | ||
firstTabbableNode: tabbableNodes[0], | ||
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1] | ||
}; | ||
} | ||
var focusableNodes = tabbable.focusable(container, config.tabbableOptions); | ||
return { | ||
container: container, | ||
tabbableNodes: tabbableNodes, | ||
focusableNodes: focusableNodes, | ||
firstTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[0] : null, | ||
lastTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[tabbableNodes.length - 1] : null, | ||
return undefined; | ||
}).filter(function (group) { | ||
return !!group; | ||
}); // remove groups with no tabbable nodes | ||
// throw if no groups have tabbable nodes and we don't have a fallback focus node either | ||
/** | ||
* Finds the __tabbable__ node that follows the given node in the specified direction, | ||
* in this container, if any. | ||
* @param {HTMLElement} node | ||
* @param {boolean} [forward] True if going in forward tab order; false if going | ||
* in reverse. | ||
* @returns {HTMLElement|undefined} The next tabbable node, if any. | ||
*/ | ||
nextTabbableNode: function nextTabbableNode(node) { | ||
var forward = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; | ||
// NOTE: If tabindex is positive (in order to manipulate the tab order separate | ||
// from the DOM order), this __will not work__ because the list of focusableNodes, | ||
// while it contains tabbable nodes, does not sort its nodes in any order other | ||
// than DOM order, because it can't: Where would you place focusable (but not | ||
// tabbable) nodes in that order? They have no order, because they aren't tabbale... | ||
// Support for positive tabindex is already broken and hard to manage (possibly | ||
// not supportable, TBD), so this isn't going to make things worse than they | ||
// already are, and at least makes things better for the majority of cases where | ||
// tabindex is either 0/unset or negative. | ||
// FYI, positive tabindex issue: https://github.com/focus-trap/focus-trap/issues/375 | ||
var nodeIdx = focusableNodes.findIndex(function (n) { | ||
return n === node; | ||
}); | ||
if (nodeIdx < 0) { | ||
return undefined; | ||
} | ||
if (forward) { | ||
return focusableNodes.slice(nodeIdx + 1).find(function (n) { | ||
return tabbable.isTabbable(n, config.tabbableOptions); | ||
}); | ||
} | ||
return focusableNodes.slice(0, nodeIdx).reverse().find(function (n) { | ||
return tabbable.isTabbable(n, config.tabbableOptions); | ||
}); | ||
} | ||
}; | ||
}); | ||
state.tabbableGroups = state.containerGroups.filter(function (group) { | ||
return group.tabbableNodes.length > 0; | ||
}); // throw if no groups have tabbable nodes and we don't have a fallback focus node either | ||
if (state.tabbableGroups.length <= 0 && !getNodeForOption('fallbackFocus') // returning false not supported for this option | ||
@@ -343,3 +417,3 @@ ) { | ||
if (containersContain(target)) { | ||
if (findContainerIndex(target) >= 0) { | ||
// allow the click since it ocurred inside the trap | ||
@@ -363,3 +437,3 @@ return; | ||
// on activation (or the configured `setReturnFocus` node) | ||
returnFocus: config.returnFocusOnDeactivate && !tabbable.isFocusable(target) | ||
returnFocus: config.returnFocusOnDeactivate && !tabbable.isFocusable(target, config.tabbableOptions) | ||
}); | ||
@@ -384,3 +458,3 @@ return; | ||
var target = getActualTarget(e); | ||
var targetContained = containersContain(target); // In Firefox when you Tab out of an iframe the Document is briefly focused. | ||
var targetContained = findContainerIndex(target) >= 0; // In Firefox when you Tab out of an iframe the Document is briefly focused. | ||
@@ -409,8 +483,6 @@ if (targetContained || target instanceof Document) { | ||
// make sure the target is actually contained in a group | ||
// NOTE: the target may also be the container itself if it's tabbable | ||
// NOTE: the target may also be the container itself if it's focusable | ||
// with tabIndex='-1' and was given initial focus | ||
var containerIndex = findIndex(state.tabbableGroups, function (_ref) { | ||
var container = _ref.container; | ||
return container.contains(target); | ||
}); | ||
var containerIndex = findContainerIndex(target); | ||
var containerGroup = containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined; | ||
@@ -435,4 +507,7 @@ if (containerIndex < 0) { | ||
if (startOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === target) { | ||
// an exception case where the target is the container itself, in which | ||
if (startOfGroupIndex < 0 && (containerGroup.container === target || tabbable.isFocusable(target, config.tabbableOptions) && !tabbable.isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target, false))) { | ||
// an exception case where the target is either the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle shift+tab as if focus were on the container's | ||
@@ -459,4 +534,7 @@ // first tabbable node, and go to the last tabbable node of the LAST group | ||
if (lastOfGroupIndex < 0 && state.tabbableGroups[containerIndex].container === target) { | ||
// an exception case where the target is the container itself, in which | ||
if (lastOfGroupIndex < 0 && (containerGroup.container === target || tabbable.isFocusable(target, config.tabbableOptions) && !tabbable.isTabbable(target, config.tabbableOptions) && !containerGroup.nextTabbableNode(target))) { | ||
// an exception case where the target is the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle tab as if focus were on the container's | ||
@@ -503,9 +581,9 @@ // last tabbable node, and go to the first tabbable node of the FIRST group | ||
var checkClick = function checkClick(e) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
var target = getActualTarget(e); | ||
if (findContainerIndex(target) >= 0) { | ||
return; | ||
} | ||
var target = getActualTarget(e); | ||
if (containersContain(target)) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
return; | ||
@@ -574,2 +652,10 @@ } | ||
trap = { | ||
get active() { | ||
return state.active; | ||
}, | ||
get paused() { | ||
return state.paused; | ||
}, | ||
activate: function activate(activateOptions) { | ||
@@ -621,2 +707,8 @@ if (state.active) { | ||
var options = _objectSpread2({ | ||
onDeactivate: config.onDeactivate, | ||
onPostDeactivate: config.onPostDeactivate, | ||
checkCanReturnFocus: config.checkCanReturnFocus | ||
}, deactivateOptions); | ||
clearTimeout(state.delayInitialFocusTimer); // noop if undefined | ||
@@ -629,5 +721,6 @@ | ||
activeFocusTraps.deactivateTrap(trap); | ||
var onDeactivate = getOption(deactivateOptions, 'onDeactivate'); | ||
var onPostDeactivate = getOption(deactivateOptions, 'onPostDeactivate'); | ||
var checkCanReturnFocus = getOption(deactivateOptions, 'checkCanReturnFocus'); | ||
var onDeactivate = getOption(options, 'onDeactivate'); | ||
var onPostDeactivate = getOption(options, 'onPostDeactivate'); | ||
var checkCanReturnFocus = getOption(options, 'checkCanReturnFocus'); | ||
var returnFocus = getOption(options, 'returnFocus', 'returnFocusOnDeactivate'); | ||
@@ -638,4 +731,2 @@ if (onDeactivate) { | ||
var returnFocus = getOption(deactivateOptions, 'returnFocus', 'returnFocusOnDeactivate'); | ||
var finishDeactivation = function finishDeactivation() { | ||
@@ -642,0 +733,0 @@ delay(function () { |
/*! | ||
* focus-trap 6.7.1 | ||
* focus-trap 6.9.4 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
*/ | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("tabbable")):"function"==typeof define&&define.amd?define(["exports","tabbable"],t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.focusTrap,a=e.focusTrap={};t(a,e.tabbable),a.noConflict=function(){return e.focusTrap=n,a}}())}(this,(function(e,t){"use strict";function n(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function a(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var r,o=(r=[],{activateTrap:function(e){if(r.length>0){var t=r[r.length-1];t!==e&&t.pause()}var n=r.indexOf(e);-1===n||r.splice(n,1),r.push(e)},deactivateTrap:function(e){var t=r.indexOf(e);-1!==t&&r.splice(t,1),r.length>0&&r[r.length-1].unpause()}}),i=function(e){return setTimeout(e,0)},c=function(e,t){var n=-1;return e.every((function(e,a){return!t(e)||(n=a,!1)})),n},u=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a<t;a++)n[a-1]=arguments[a];return"function"==typeof e?e.apply(void 0,n):e},s=function(e){return e.target.shadowRoot&&"function"==typeof e.composedPath?e.composedPath()[0]:e.target};e.createFocusTrap=function(e,r){var l,f=(null==r?void 0:r.document)||document,b=function(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?n(Object(r),!0).forEach((function(t){a(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):n(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}({returnFocusOnDeactivate:!0,escapeDeactivates:!0,delayInitialFocus:!0},r),v={containers:[],tabbableGroups:[],nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1,delayInitialFocusTimer:void 0},d=function(e,t,n){return e&&void 0!==e[t]?e[t]:b[n||t]},p=function(e){return!(!e||!v.containers.some((function(t){return t.contains(e)})))},h=function(e){var t=b[e];if("function"==typeof t){for(var n=arguments.length,a=new Array(n>1?n-1:0),r=1;r<n;r++)a[r-1]=arguments[r];t=t.apply(void 0,a)}if(!t){if(void 0===t||!1===t)return t;throw new Error("`".concat(e,"` was specified but was not a node, or did not return a node"))}var o=t;if("string"==typeof t&&!(o=f.querySelector(t)))throw new Error("`".concat(e,"` as selector refers to no known node"));return o},y=function(){var e=h("initialFocus");if(!1===e)return!1;if(void 0===e)if(p(f.activeElement))e=f.activeElement;else{var t=v.tabbableGroups[0];e=t&&t.firstTabbableNode||h("fallbackFocus")}if(!e)throw new Error("Your focus-trap needs to have at least one focusable element");return e},m=function(){if(v.tabbableGroups=v.containers.map((function(e){var n=t.tabbable(e);if(n.length>0)return{container:e,firstTabbableNode:n[0],lastTabbableNode:n[n.length-1]}})).filter((function(e){return!!e})),v.tabbableGroups.length<=0&&!h("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times")},g=function e(t){!1!==t&&t!==f.activeElement&&(t&&t.focus?(t.focus({preventScroll:!!b.preventScroll}),v.mostRecentlyFocusedNode=t,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(t)&&t.select()):e(y()))},w=function(e){var t=h("setReturnFocus",e);return t||!1!==t&&e},F=function(e){var n=s(e);p(n)||(u(b.clickOutsideDeactivates,e)?l.deactivate({returnFocus:b.returnFocusOnDeactivate&&!t.isFocusable(n)}):u(b.allowOutsideClick,e)||e.preventDefault())},O=function(e){var t=s(e),n=p(t);n||t instanceof Document?n&&(v.mostRecentlyFocusedNode=t):(e.stopImmediatePropagation(),g(v.mostRecentlyFocusedNode||y()))},T=function(e){if(function(e){return"Escape"===e.key||"Esc"===e.key||27===e.keyCode}(e)&&!1!==u(b.escapeDeactivates,e))return e.preventDefault(),void l.deactivate();(function(e){return"Tab"===e.key||9===e.keyCode})(e)&&function(e){var t=s(e);m();var n=null;if(v.tabbableGroups.length>0){var a=c(v.tabbableGroups,(function(e){return e.container.contains(t)}));if(a<0)n=e.shiftKey?v.tabbableGroups[v.tabbableGroups.length-1].lastTabbableNode:v.tabbableGroups[0].firstTabbableNode;else if(e.shiftKey){var r=c(v.tabbableGroups,(function(e){var n=e.firstTabbableNode;return t===n}));if(r<0&&v.tabbableGroups[a].container===t&&(r=a),r>=0){var o=0===r?v.tabbableGroups.length-1:r-1;n=v.tabbableGroups[o].lastTabbableNode}}else{var i=c(v.tabbableGroups,(function(e){var n=e.lastTabbableNode;return t===n}));if(i<0&&v.tabbableGroups[a].container===t&&(i=a),i>=0){var u=i===v.tabbableGroups.length-1?0:i+1;n=v.tabbableGroups[u].firstTabbableNode}}}else n=h("fallbackFocus");n&&(e.preventDefault(),g(n))}(e)},E=function(e){if(!u(b.clickOutsideDeactivates,e)){var t=s(e);p(t)||u(b.allowOutsideClick,e)||(e.preventDefault(),e.stopImmediatePropagation())}},k=function(){if(v.active)return o.activateTrap(l),v.delayInitialFocusTimer=b.delayInitialFocus?i((function(){g(y())})):g(y()),f.addEventListener("focusin",O,!0),f.addEventListener("mousedown",F,{capture:!0,passive:!1}),f.addEventListener("touchstart",F,{capture:!0,passive:!1}),f.addEventListener("click",E,{capture:!0,passive:!1}),f.addEventListener("keydown",T,{capture:!0,passive:!1}),l},D=function(){if(v.active)return f.removeEventListener("focusin",O,!0),f.removeEventListener("mousedown",F,!0),f.removeEventListener("touchstart",F,!0),f.removeEventListener("click",E,!0),f.removeEventListener("keydown",T,!0),l};return(l={activate:function(e){if(v.active)return this;var t=d(e,"onActivate"),n=d(e,"onPostActivate"),a=d(e,"checkCanFocusTrap");a||m(),v.active=!0,v.paused=!1,v.nodeFocusedBeforeActivation=f.activeElement,t&&t();var r=function(){a&&m(),k(),n&&n()};return a?(a(v.containers.concat()).then(r,r),this):(r(),this)},deactivate:function(e){if(!v.active)return this;clearTimeout(v.delayInitialFocusTimer),v.delayInitialFocusTimer=void 0,D(),v.active=!1,v.paused=!1,o.deactivateTrap(l);var t=d(e,"onDeactivate"),n=d(e,"onPostDeactivate"),a=d(e,"checkCanReturnFocus");t&&t();var r=d(e,"returnFocus","returnFocusOnDeactivate"),c=function(){i((function(){r&&g(w(v.nodeFocusedBeforeActivation)),n&&n()}))};return r&&a?(a(w(v.nodeFocusedBeforeActivation)).then(c,c),this):(c(),this)},pause:function(){return v.paused||!v.active||(v.paused=!0,D()),this},unpause:function(){return v.paused&&v.active?(v.paused=!1,m(),k(),this):this},updateContainerElements:function(e){var t=[].concat(e).filter(Boolean);return v.containers=t.map((function(e){return"string"==typeof e?f.querySelector(e):e})),v.active&&m(),this}}).updateContainerElements(e),l},Object.defineProperty(e,"__esModule",{value:!0})})); | ||
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("tabbable")):"function"==typeof define&&define.amd?define(["exports","tabbable"],t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.focusTrap,a=e.focusTrap={};t(a,e.tabbable),a.noConflict=function(){return e.focusTrap=n,a}}())}(this,(function(e,t){"use strict";function n(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function a(e){for(var t=1;t<arguments.length;t++){var a=null!=arguments[t]?arguments[t]:{};t%2?n(Object(a),!0).forEach((function(t){o(e,t,a[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(a)):n(Object(a)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(a,t))}))}return e}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var r,i=(r=[],{activateTrap:function(e){if(r.length>0){var t=r[r.length-1];t!==e&&t.pause()}var n=r.indexOf(e);-1===n||r.splice(n,1),r.push(e)},deactivateTrap:function(e){var t=r.indexOf(e);-1!==t&&r.splice(t,1),r.length>0&&r[r.length-1].unpause()}}),c=function(e){return setTimeout(e,0)},u=function(e,t){var n=-1;return e.every((function(e,a){return!t(e)||(n=a,!1)})),n},s=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a<t;a++)n[a-1]=arguments[a];return"function"==typeof e?e.apply(void 0,n):e},l=function(e){return e.target.shadowRoot&&"function"==typeof e.composedPath?e.composedPath()[0]:e.target};e.createFocusTrap=function(e,n){var o,r=(null==n?void 0:n.document)||document,b=a({returnFocusOnDeactivate:!0,escapeDeactivates:!0,delayInitialFocus:!0},n),f={containers:[],containerGroups:[],tabbableGroups:[],nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1,delayInitialFocusTimer:void 0},v=function(e,t,n){return e&&void 0!==e[t]?e[t]:b[n||t]},d=function(e){return f.containerGroups.findIndex((function(t){var n=t.container,a=t.tabbableNodes;return n.contains(e)||a.find((function(t){return t===e}))}))},p=function(e){var t=b[e];if("function"==typeof t){for(var n=arguments.length,a=new Array(n>1?n-1:0),o=1;o<n;o++)a[o-1]=arguments[o];t=t.apply(void 0,a)}if(!0===t&&(t=void 0),!t){if(void 0===t||!1===t)return t;throw new Error("`".concat(e,"` was specified but was not a node, or did not return a node"))}var i=t;if("string"==typeof t&&!(i=r.querySelector(t)))throw new Error("`".concat(e,"` as selector refers to no known node"));return i},h=function(){var e=p("initialFocus");if(!1===e)return!1;if(void 0===e)if(d(r.activeElement)>=0)e=r.activeElement;else{var t=f.tabbableGroups[0];e=t&&t.firstTabbableNode||p("fallbackFocus")}if(!e)throw new Error("Your focus-trap needs to have at least one focusable element");return e},y=function(){if(f.containerGroups=f.containers.map((function(e){var n=t.tabbable(e,b.tabbableOptions),a=t.focusable(e,b.tabbableOptions);return{container:e,tabbableNodes:n,focusableNodes:a,firstTabbableNode:n.length>0?n[0]:null,lastTabbableNode:n.length>0?n[n.length-1]:null,nextTabbableNode:function(e){var n=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],o=a.findIndex((function(t){return t===e}));if(!(o<0))return n?a.slice(o+1).find((function(e){return t.isTabbable(e,b.tabbableOptions)})):a.slice(0,o).reverse().find((function(e){return t.isTabbable(e,b.tabbableOptions)}))}}})),f.tabbableGroups=f.containerGroups.filter((function(e){return e.tabbableNodes.length>0})),f.tabbableGroups.length<=0&&!p("fallbackFocus"))throw new Error("Your focus-trap must have at least one container with at least one tabbable node in it at all times")},m=function e(t){!1!==t&&t!==r.activeElement&&(t&&t.focus?(t.focus({preventScroll:!!b.preventScroll}),f.mostRecentlyFocusedNode=t,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(t)&&t.select()):e(h()))},O=function(e){var t=p("setReturnFocus",e);return t||!1!==t&&e},g=function(e){var n=l(e);d(n)>=0||(s(b.clickOutsideDeactivates,e)?o.deactivate({returnFocus:b.returnFocusOnDeactivate&&!t.isFocusable(n,b.tabbableOptions)}):s(b.allowOutsideClick,e)||e.preventDefault())},T=function(e){var t=l(e),n=d(t)>=0;n||t instanceof Document?n&&(f.mostRecentlyFocusedNode=t):(e.stopImmediatePropagation(),m(f.mostRecentlyFocusedNode||h()))},F=function(e){if(function(e){return"Escape"===e.key||"Esc"===e.key||27===e.keyCode}(e)&&!1!==s(b.escapeDeactivates,e))return e.preventDefault(),void o.deactivate();(function(e){return"Tab"===e.key||9===e.keyCode})(e)&&function(e){var n=l(e);y();var a=null;if(f.tabbableGroups.length>0){var o=d(n),r=o>=0?f.containerGroups[o]:void 0;if(o<0)a=e.shiftKey?f.tabbableGroups[f.tabbableGroups.length-1].lastTabbableNode:f.tabbableGroups[0].firstTabbableNode;else if(e.shiftKey){var i=u(f.tabbableGroups,(function(e){var t=e.firstTabbableNode;return n===t}));if(i<0&&(r.container===n||t.isFocusable(n,b.tabbableOptions)&&!t.isTabbable(n,b.tabbableOptions)&&!r.nextTabbableNode(n,!1))&&(i=o),i>=0){var c=0===i?f.tabbableGroups.length-1:i-1;a=f.tabbableGroups[c].lastTabbableNode}}else{var s=u(f.tabbableGroups,(function(e){var t=e.lastTabbableNode;return n===t}));if(s<0&&(r.container===n||t.isFocusable(n,b.tabbableOptions)&&!t.isTabbable(n,b.tabbableOptions)&&!r.nextTabbableNode(n))&&(s=o),s>=0){var v=s===f.tabbableGroups.length-1?0:s+1;a=f.tabbableGroups[v].firstTabbableNode}}}else a=p("fallbackFocus");a&&(e.preventDefault(),m(a))}(e)},w=function(e){var t=l(e);d(t)>=0||s(b.clickOutsideDeactivates,e)||s(b.allowOutsideClick,e)||(e.preventDefault(),e.stopImmediatePropagation())},E=function(){if(f.active)return i.activateTrap(o),f.delayInitialFocusTimer=b.delayInitialFocus?c((function(){m(h())})):m(h()),r.addEventListener("focusin",T,!0),r.addEventListener("mousedown",g,{capture:!0,passive:!1}),r.addEventListener("touchstart",g,{capture:!0,passive:!1}),r.addEventListener("click",w,{capture:!0,passive:!1}),r.addEventListener("keydown",F,{capture:!0,passive:!1}),o},k=function(){if(f.active)return r.removeEventListener("focusin",T,!0),r.removeEventListener("mousedown",g,!0),r.removeEventListener("touchstart",g,!0),r.removeEventListener("click",w,!0),r.removeEventListener("keydown",F,!0),o};return(o={get active(){return f.active},get paused(){return f.paused},activate:function(e){if(f.active)return this;var t=v(e,"onActivate"),n=v(e,"onPostActivate"),a=v(e,"checkCanFocusTrap");a||y(),f.active=!0,f.paused=!1,f.nodeFocusedBeforeActivation=r.activeElement,t&&t();var o=function(){a&&y(),E(),n&&n()};return a?(a(f.containers.concat()).then(o,o),this):(o(),this)},deactivate:function(e){if(!f.active)return this;var t=a({onDeactivate:b.onDeactivate,onPostDeactivate:b.onPostDeactivate,checkCanReturnFocus:b.checkCanReturnFocus},e);clearTimeout(f.delayInitialFocusTimer),f.delayInitialFocusTimer=void 0,k(),f.active=!1,f.paused=!1,i.deactivateTrap(o);var n=v(t,"onDeactivate"),r=v(t,"onPostDeactivate"),u=v(t,"checkCanReturnFocus"),s=v(t,"returnFocus","returnFocusOnDeactivate");n&&n();var l=function(){c((function(){s&&m(O(f.nodeFocusedBeforeActivation)),r&&r()}))};return s&&u?(u(O(f.nodeFocusedBeforeActivation)).then(l,l),this):(l(),this)},pause:function(){return f.paused||!f.active||(f.paused=!0,k()),this},unpause:function(){return f.paused&&f.active?(f.paused=!1,y(),E(),this):this},updateContainerElements:function(e){var t=[].concat(e).filter(Boolean);return f.containers=t.map((function(e){return"string"==typeof e?r.querySelector(e):e})),f.active&&y(),this}}).updateContainerElements(e),o},Object.defineProperty(e,"__esModule",{value:!0})})); | ||
//# sourceMappingURL=focus-trap.umd.min.js.map |
@@ -0,2 +1,7 @@ | ||
import { CheckOptions as TabbableCheckOptions } from 'tabbable'; | ||
declare module 'focus-trap' { | ||
export type FocusTargetValue = HTMLElement | SVGElement | string; | ||
export type FocusTargetValueOrFalse = FocusTargetValue | false; | ||
/** | ||
@@ -7,3 +12,3 @@ * A DOM node, a selector string (which will be passed to | ||
*/ | ||
export type FocusTarget = HTMLElement | SVGElement | string | (() => HTMLElement | SVGElement); | ||
export type FocusTarget = FocusTargetValue | (() => FocusTargetValue); | ||
@@ -15,3 +20,3 @@ /** | ||
*/ | ||
export type FocusTargetOrFalse = HTMLElement | SVGElement | string | false | (() => HTMLElement | SVGElement | false); | ||
export type FocusTargetOrFalse = FocusTargetValueOrFalse | (() => FocusTargetValueOrFalse); | ||
@@ -21,2 +26,6 @@ type MouseEventToBoolean = (event: MouseEvent | TouchEvent) => boolean; | ||
/** tabbable options supported by focus-trap. */ | ||
export interface FocusTrapTabbableOptions extends TabbableCheckOptions { | ||
} | ||
export interface Options { | ||
@@ -112,3 +121,3 @@ /** | ||
*/ | ||
setReturnFocus?: HTMLElement | SVGElement | string | false | ((nodeFocusedBeforeActivation: HTMLElement | SVGElement) => HTMLElement | SVGElement | false); | ||
setReturnFocus?: FocusTargetValueOrFalse | ((nodeFocusedBeforeActivation: HTMLElement | SVGElement) => FocusTargetValueOrFalse); | ||
/** | ||
@@ -155,3 +164,8 @@ * Default: `true`. If `false` or returns `false`, the `Escape` key will not trigger | ||
*/ | ||
document?: Document; | ||
document?: Document; | ||
/** | ||
* Specific tabbable options configurable on focus-trap. | ||
*/ | ||
tabbableOptions?: FocusTrapTabbableOptions; | ||
} | ||
@@ -166,2 +180,4 @@ | ||
export interface FocusTrap { | ||
active: boolean, | ||
paused: boolean, | ||
activate(activateOptions?: ActivateOptions): FocusTrap; | ||
@@ -168,0 +184,0 @@ deactivate(deactivateOptions?: DeactivateOptions): FocusTrap; |
206
index.js
@@ -1,2 +0,2 @@ | ||
import { tabbable, isFocusable } from 'tabbable'; | ||
import { tabbable, focusable, isFocusable, isTabbable } from 'tabbable'; | ||
@@ -99,2 +99,4 @@ const activeFocusTraps = (function () { | ||
const createFocusTrap = function (elements, userOptions) { | ||
// SSR: a live trap shouldn't be created in this type of environment so this | ||
// should be safe code to execute if the `document` option isn't specified | ||
const doc = userOptions?.document || document; | ||
@@ -110,7 +112,7 @@ | ||
const state = { | ||
// containers given to createFocusTrap() | ||
// @type {Array<HTMLElement>} | ||
containers: [], | ||
// list of objects identifying the first and last tabbable nodes in all containers/groups in | ||
// the trap | ||
// list of objects identifying tabbable nodes in `containers` in the trap | ||
// NOTE: it's possible that a group has no tabbable nodes if nodes get removed while the trap | ||
@@ -120,3 +122,16 @@ // is active, but the trap should never get to a state where there isn't at least one group | ||
// result in an error being thrown) | ||
// @type {Array<{ container: HTMLElement, firstTabbableNode: HTMLElement|null, lastTabbableNode: HTMLElement|null }>} | ||
// @type {Array<{ | ||
// container: HTMLElement, | ||
// tabbableNodes: Array<HTMLElement>, // empty if none | ||
// focusableNodes: Array<HTMLElement>, // empty if none | ||
// firstTabbableNode: HTMLElement|null, | ||
// lastTabbableNode: HTMLElement|null, | ||
// nextTabbableNode: (node: HTMLElement, forward: boolean) => HTMLElement|undefined | ||
// }>} | ||
containerGroups: [], // same order/length as `containers` list | ||
// references to objects in `containerGroups`, but only those that actually have | ||
// tabbable nodes in them | ||
// NOTE: same order as `containers` and `containerGroups`, but __not necessarily__ | ||
// the same length | ||
tabbableGroups: [], | ||
@@ -136,2 +151,10 @@ | ||
/** | ||
* Gets a configuration option value. | ||
* @param {Object|undefined} configOverrideOptions If true, and option is defined in this set, | ||
* value will be taken from this object. Otherwise, value will be taken from base configuration. | ||
* @param {string} optionName Name of the option whose value is sought. | ||
* @param {string|undefined} [configOptionName] Name of option to use __instead of__ `optionName` | ||
* IIF `configOverrideOptions` is not defined. Otherwise, `optionName` is used. | ||
*/ | ||
const getOption = (configOverrideOptions, optionName, configOptionName) => { | ||
@@ -144,6 +167,21 @@ return configOverrideOptions && | ||
const containersContain = function (element) { | ||
return !!( | ||
element && | ||
state.containers.some((container) => container.contains(element)) | ||
/** | ||
* Finds the index of the container that contains the element. | ||
* @param {HTMLElement} element | ||
* @returns {number} Index of the container in either `state.containers` or | ||
* `state.containerGroups` (the order/length of these lists are the same); -1 | ||
* if the element isn't found. | ||
*/ | ||
const findContainerIndex = function (element) { | ||
// NOTE: search `containerGroups` because it's possible a group contains no tabbable | ||
// nodes, but still contains focusable nodes (e.g. if they all have `tabindex=-1`) | ||
// and we still need to find the element in there | ||
return state.containerGroups.findIndex( | ||
({ container, tabbableNodes }) => | ||
container.contains(element) || | ||
// fall back to explicit tabbable search which will take into consideration any | ||
// web components if the `tabbableOptions.getShadowRoot` option was used for | ||
// the trap, enabling shadow DOM support in tabbable (`Node.contains()` doesn't | ||
// look inside web components even if open) | ||
tabbableNodes.find((node) => node === element) | ||
); | ||
@@ -172,2 +210,6 @@ }; | ||
if (optionValue === true) { | ||
optionValue = undefined; // use default value | ||
} | ||
if (!optionValue) { | ||
@@ -208,3 +250,3 @@ if (optionValue === undefined || optionValue === false) { | ||
// option not specified: use fallback options | ||
if (containersContain(doc.activeElement)) { | ||
if (findContainerIndex(doc.activeElement) >= 0) { | ||
node = doc.activeElement; | ||
@@ -231,18 +273,61 @@ } else { | ||
const updateTabbableNodes = function () { | ||
state.tabbableGroups = state.containers | ||
.map((container) => { | ||
const tabbableNodes = tabbable(container); | ||
state.containerGroups = state.containers.map((container) => { | ||
const tabbableNodes = tabbable(container, config.tabbableOptions); | ||
if (tabbableNodes.length > 0) { | ||
return { | ||
container, | ||
firstTabbableNode: tabbableNodes[0], | ||
lastTabbableNode: tabbableNodes[tabbableNodes.length - 1], | ||
}; | ||
} | ||
// NOTE: if we have tabbable nodes, we must have focusable nodes; focusable nodes | ||
// are a superset of tabbable nodes | ||
const focusableNodes = focusable(container, config.tabbableOptions); | ||
return undefined; | ||
}) | ||
.filter((group) => !!group); // remove groups with no tabbable nodes | ||
return { | ||
container, | ||
tabbableNodes, | ||
focusableNodes, | ||
firstTabbableNode: tabbableNodes.length > 0 ? tabbableNodes[0] : null, | ||
lastTabbableNode: | ||
tabbableNodes.length > 0 | ||
? tabbableNodes[tabbableNodes.length - 1] | ||
: null, | ||
/** | ||
* Finds the __tabbable__ node that follows the given node in the specified direction, | ||
* in this container, if any. | ||
* @param {HTMLElement} node | ||
* @param {boolean} [forward] True if going in forward tab order; false if going | ||
* in reverse. | ||
* @returns {HTMLElement|undefined} The next tabbable node, if any. | ||
*/ | ||
nextTabbableNode(node, forward = true) { | ||
// NOTE: If tabindex is positive (in order to manipulate the tab order separate | ||
// from the DOM order), this __will not work__ because the list of focusableNodes, | ||
// while it contains tabbable nodes, does not sort its nodes in any order other | ||
// than DOM order, because it can't: Where would you place focusable (but not | ||
// tabbable) nodes in that order? They have no order, because they aren't tabbale... | ||
// Support for positive tabindex is already broken and hard to manage (possibly | ||
// not supportable, TBD), so this isn't going to make things worse than they | ||
// already are, and at least makes things better for the majority of cases where | ||
// tabindex is either 0/unset or negative. | ||
// FYI, positive tabindex issue: https://github.com/focus-trap/focus-trap/issues/375 | ||
const nodeIdx = focusableNodes.findIndex((n) => n === node); | ||
if (nodeIdx < 0) { | ||
return undefined; | ||
} | ||
if (forward) { | ||
return focusableNodes | ||
.slice(nodeIdx + 1) | ||
.find((n) => isTabbable(n, config.tabbableOptions)); | ||
} | ||
return focusableNodes | ||
.slice(0, nodeIdx) | ||
.reverse() | ||
.find((n) => isTabbable(n, config.tabbableOptions)); | ||
}, | ||
}; | ||
}); | ||
state.tabbableGroups = state.containerGroups.filter( | ||
(group) => group.tabbableNodes.length > 0 | ||
); | ||
// throw if no groups have tabbable nodes and we don't have a fallback focus node either | ||
@@ -291,3 +376,3 @@ if ( | ||
if (containersContain(target)) { | ||
if (findContainerIndex(target) >= 0) { | ||
// allow the click since it ocurred inside the trap | ||
@@ -311,3 +396,5 @@ return; | ||
// on activation (or the configured `setReturnFocus` node) | ||
returnFocus: config.returnFocusOnDeactivate && !isFocusable(target), | ||
returnFocus: | ||
config.returnFocusOnDeactivate && | ||
!isFocusable(target, config.tabbableOptions), | ||
}); | ||
@@ -332,3 +419,3 @@ return; | ||
const target = getActualTarget(e); | ||
const targetContained = containersContain(target); | ||
const targetContained = findContainerIndex(target) >= 0; | ||
@@ -359,7 +446,7 @@ // In Firefox when you Tab out of an iframe the Document is briefly focused. | ||
// make sure the target is actually contained in a group | ||
// NOTE: the target may also be the container itself if it's tabbable | ||
// NOTE: the target may also be the container itself if it's focusable | ||
// with tabIndex='-1' and was given initial focus | ||
const containerIndex = findIndex(state.tabbableGroups, ({ container }) => | ||
container.contains(target) | ||
); | ||
const containerIndex = findContainerIndex(target); | ||
const containerGroup = | ||
containerIndex >= 0 ? state.containerGroups[containerIndex] : undefined; | ||
@@ -389,5 +476,11 @@ if (containerIndex < 0) { | ||
startOfGroupIndex < 0 && | ||
state.tabbableGroups[containerIndex].container === target | ||
(containerGroup.container === target || | ||
(isFocusable(target, config.tabbableOptions) && | ||
!isTabbable(target, config.tabbableOptions) && | ||
!containerGroup.nextTabbableNode(target, false))) | ||
) { | ||
// an exception case where the target is the container itself, in which | ||
// an exception case where the target is either the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle shift+tab as if focus were on the container's | ||
@@ -421,5 +514,11 @@ // first tabbable node, and go to the last tabbable node of the LAST group | ||
lastOfGroupIndex < 0 && | ||
state.tabbableGroups[containerIndex].container === target | ||
(containerGroup.container === target || | ||
(isFocusable(target, config.tabbableOptions) && | ||
!isTabbable(target, config.tabbableOptions) && | ||
!containerGroup.nextTabbableNode(target))) | ||
) { | ||
// an exception case where the target is the container itself, in which | ||
// an exception case where the target is the container itself, or | ||
// a non-tabbable node that was given focus (i.e. tabindex is negative | ||
// and user clicked on it or node was programmatically given focus) | ||
// and is not followed by any other tabbable node, in which | ||
// case, we should handle tab as if focus were on the container's | ||
@@ -472,9 +571,9 @@ // last tabbable node, and go to the first tabbable node of the FIRST group | ||
const checkClick = function (e) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
const target = getActualTarget(e); | ||
if (findContainerIndex(target) >= 0) { | ||
return; | ||
} | ||
const target = getActualTarget(e); | ||
if (containersContain(target)) { | ||
if (valueOrHandler(config.clickOutsideDeactivates, e)) { | ||
return; | ||
@@ -551,2 +650,10 @@ } | ||
trap = { | ||
get active() { | ||
return state.active; | ||
}, | ||
get paused() { | ||
return state.paused; | ||
}, | ||
activate(activateOptions) { | ||
@@ -600,2 +707,9 @@ if (state.active) { | ||
const options = { | ||
onDeactivate: config.onDeactivate, | ||
onPostDeactivate: config.onPostDeactivate, | ||
checkCanReturnFocus: config.checkCanReturnFocus, | ||
...deactivateOptions, | ||
}; | ||
clearTimeout(state.delayInitialFocusTimer); // noop if undefined | ||
@@ -610,7 +724,9 @@ state.delayInitialFocusTimer = undefined; | ||
const onDeactivate = getOption(deactivateOptions, 'onDeactivate'); | ||
const onPostDeactivate = getOption(deactivateOptions, 'onPostDeactivate'); | ||
const checkCanReturnFocus = getOption( | ||
deactivateOptions, | ||
'checkCanReturnFocus' | ||
const onDeactivate = getOption(options, 'onDeactivate'); | ||
const onPostDeactivate = getOption(options, 'onPostDeactivate'); | ||
const checkCanReturnFocus = getOption(options, 'checkCanReturnFocus'); | ||
const returnFocus = getOption( | ||
options, | ||
'returnFocus', | ||
'returnFocusOnDeactivate' | ||
); | ||
@@ -622,8 +738,2 @@ | ||
const returnFocus = getOption( | ||
deactivateOptions, | ||
'returnFocus', | ||
'returnFocusOnDeactivate' | ||
); | ||
const finishDeactivation = () => { | ||
@@ -630,0 +740,0 @@ delay(() => { |
{ | ||
"name": "focus-trap", | ||
"version": "6.7.1", | ||
"version": "6.9.4", | ||
"description": "Trap focus within a DOM node.", | ||
@@ -41,2 +41,3 @@ "main": "dist/focus-trap.js", | ||
"prepare": "yarn build", | ||
"prepublishOnly": "yarn test && yarn build", | ||
"release": "yarn build && changeset publish" | ||
@@ -66,26 +67,27 @@ }, | ||
"dependencies": { | ||
"tabbable": "^5.2.1" | ||
"tabbable": "^5.3.3" | ||
}, | ||
"devDependencies": { | ||
"@babel/cli": "^7.15.7", | ||
"@babel/core": "^7.15.5", | ||
"@babel/preset-env": "^7.15.6", | ||
"@changesets/cli": "^2.17.0", | ||
"@rollup/plugin-babel": "^5.3.0", | ||
"@rollup/plugin-commonjs": "^20.0.0", | ||
"@rollup/plugin-node-resolve": "^13.0.5", | ||
"@testing-library/cypress": "^8.0.1", | ||
"@types/jquery": "^3.5.6", | ||
"@babel/cli": "^7.17.10", | ||
"@babel/core": "^7.18.2", | ||
"@babel/eslint-parser": "^7.18.2", | ||
"@babel/preset-env": "^7.18.2", | ||
"@changesets/cli": "^2.22.0", | ||
"@rollup/plugin-babel": "^5.3.1", | ||
"@rollup/plugin-commonjs": "^22.0.0", | ||
"@rollup/plugin-node-resolve": "^13.3.0", | ||
"@testing-library/cypress": "^8.0.3", | ||
"@types/jquery": "^3.5.14", | ||
"all-contributors-cli": "^6.20.0", | ||
"babel-eslint": "^10.1.0", | ||
"babel-loader": "^8.2.2", | ||
"babel-loader": "^8.2.5", | ||
"cross-env": "^7.0.3", | ||
"cypress": "^8.4.1", | ||
"cypress": "^9.7.0", | ||
"cypress-plugin-tab": "^1.0.5", | ||
"eslint": "^7.32.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
"eslint": "^8.17.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"eslint-plugin-cypress": "^2.12.1", | ||
"eslint-plugin-jest": "^26.5.3", | ||
"onchange": "^7.1.0", | ||
"prettier": "^2.4.1", | ||
"rollup": "^2.57.0", | ||
"prettier": "^2.6.2", | ||
"rollup": "^2.75.6", | ||
"rollup-plugin-inject-process-env": "^1.3.1", | ||
@@ -97,4 +99,4 @@ "rollup-plugin-livereload": "^2.0.5", | ||
"start-server-and-test": "^1.14.0", | ||
"typescript": "^4.4.3" | ||
"typescript": "^4.7.3" | ||
} | ||
} |
123
README.md
# focus-trap [![CI](https://github.com/focus-trap/focus-trap/workflows/CI/badge.svg?branch=master&event=push)](https://github.com/focus-trap/focus-trap/actions?query=workflow:CI+branch:master) [![license](https://badgen.now.sh/badge/license/MIT)](./LICENSE) | ||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> | ||
[![All Contributors](https://img.shields.io/badge/all_contributors-19-orange.svg?style=flat-square)](#contributors) | ||
[![All Contributors](https://img.shields.io/badge/all_contributors-22-orange.svg?style=flat-square)](#contributors) | ||
<!-- ALL-CONTRIBUTORS-BADGE:END --> | ||
@@ -37,3 +37,3 @@ | ||
``` | ||
```bash | ||
npm install focus-trap | ||
@@ -46,3 +46,3 @@ ``` | ||
> NOTE: The UMD build does not bundle the `tabbable` dependency. Therefore you will have to also include that one, and include it _before_ `focus-trap`. | ||
> NOTE: The UMD build does not bundle the `tabbable` dependency. Therefore you will have to also include that one, and include it *before* `focus-trap`. | ||
@@ -78,3 +78,4 @@ ```html | ||
`element` can be | ||
`element` can be: | ||
- a DOM node (the focus trap itself); | ||
@@ -84,3 +85,3 @@ - a selector string (which will be passed to `document.querySelector()` to find the DOM node); or | ||
> A focus trap must have at least one container with at least one tabbable/focusable node in it to be considered valid. While nodes can be added/removed at runtime, with the trap adjusting to added/removed tabbable nodes, __an error will be thrown__ if the trap ever gets into a state where it determines none of its containers have any tabbable nodes in them _and_ the `fallbackFocus` option does not resolve to an alternate node where focus can go. | ||
> A focus trap must have at least one container with at least one tabbable/focusable node in it to be considered valid. While nodes can be added/removed at runtime, with the trap adjusting to added/removed tabbable nodes, **an error will be thrown** if the trap ever gets into a state where it determines none of its containers have any tabbable nodes in them *and* the `fallbackFocus` option does not resolve to an alternate node where focus can go. | ||
@@ -95,30 +96,61 @@ #### createOptions | ||
- **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: An animated trigger button will have a small delay between when `onDeactivate` is called and when the focus is able to be sent back to the trigger. `checkCanReturnFocus` expects a promise to be returned. When that promise settles (resolves or rejects), focus will be sent to to the node that had focus prior to the activation of the trap (or the node configured in the `setReturnFocus` option). Due to the lack of Promise support, `checkCanReturnFocus` is not supported in IE unless you provide a Promise polyfill. | ||
- **initialFocus** `{HTMLElement | SVGElement | string | false | (() => HTMLElement | SVGElement | false)}`: By default, when a focus trap is activated the first element in the focus trap's tab order will receive focus. With this option you can specify a different element to receive that initial focus. Can be a DOM node, or a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns a DOM node. You can also set this option to `false` (or to a function that returns `false`) to prevent any initial focus at all when the trap activates. | ||
- 💬 Setting this option to `false` (or a function that returns `false`) will prevent the `fallbackFocus` option from being used. | ||
- ⚠️ See warning below about **Shadow DOM** and selector strings. | ||
- **fallbackFocus** `{HTMLElement | SVGElement | string | () => HTMLElement | SVGElement}`: By default, an error will be thrown if the focus trap contains no elements in its tab order. With this option you can specify a fallback element to programmatically receive focus if no other tabbable elements are found. For example, you may want a popover's `<div>` to receive focus if the popover's content includes no tabbable elements. *Make sure the fallback element has a negative `tabindex` so it can be programmatically focused.* The option value can be a DOM node, a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns a DOM node. | ||
- 💬 If `initialFocus` is `false` (or a function that returns `false`), this function will not be called when the trap is activated, and no element will be initially focused. This function may still be called while the trap is active if things change such that there are no longer any tabbable nodes in the trap. | ||
- ⚠️ See warning below about **Shadow DOM** and selector strings. | ||
- **initialFocus** `{HTMLElement | SVGElement | string | false | (() => HTMLElement | SVGElement | string | false)}`: By default, when a focus trap is activated the first element in the focus trap's tab order will receive focus. With this option you can specify a different element to receive that initial focus. Can be a DOM node, or a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns any of these. You can also set this option to `false` (or to a function that returns `false`) to prevent any initial focus at all when the trap activates. | ||
- 💬 Setting this option to `false` (or a function that returns `false`) will prevent the `fallbackFocus` option from being used. | ||
- ⚠️ See warning below about **Shadow DOM** and selector strings. | ||
- **fallbackFocus** `{HTMLElement | SVGElement | string | () => HTMLElement | SVGElement | string}`: By default, an error will be thrown if the focus trap contains no elements in its tab order. With this option you can specify a fallback element to programmatically receive focus if no other tabbable elements are found. For example, you may want a popover's `<div>` to receive focus if the popover's content includes no tabbable elements. *Make sure the fallback element has a negative `tabindex` so it can be programmatically focused.* The option value can be a DOM node, a selector string (which will be passed to `document.querySelector()` to find the DOM node), or a function that returns any of these. | ||
- 💬 If `initialFocus` is `false` (or a function that returns `false`), this function will not be called when the trap is activated, and no element will be initially focused. This function may still be called while the trap is active if things change such that there are no longer any tabbable nodes in the trap. | ||
- ⚠️ See warning below about **Shadow DOM** and selector strings. | ||
- **escapeDeactivates** `{boolean} | (e: KeyboardEvent) => boolean)`: Default: `true`. If `false` or returns `false`, the `Escape` key will not trigger deactivation of the focus trap. This can be useful if you want to force the user to make a decision instead of allowing an easy way out. Note that if a function is given, it's only called if the ESC key was pressed. | ||
- **clickOutsideDeactivates** `{boolean | (e: MouseEvent | TouchEvent) => boolean}`: If `true` or returns `true`, a click outside the focus trap will deactivate the focus trap and allow the click event to do its thing (i.e. to pass-through to the element that was clicked). This option **takes precedence** over `allowOutsideClick` when it's set to `true`. Default: `false`. | ||
- ⚠️ If you're using a password manager such as 1Password, where the app adds a clickable icon to all fillable fields, you should avoid using this option, and instead use the `allowOutsideClick` option to better control exactly when the focus trap can be deactivated. The clickable icons are usually positioned absolutely, floating on top of the fields, and therefore _not_ part of the container the trap is managing. When using the `clickOutsideDeactivates` option, clicking on a field's 1Password icon will likely cause the trap to be unintentionally deactivated. | ||
- **allowOutsideClick** `{boolean | (e: MouseEvent | TouchEvent) => boolean}`: If set and is or returns `true`, a click outside the focus trap will not be prevented, even when `clickOutsideDeactivates` is `false`. When `clickOutsideDeactivates` is `true`, this option is **ignored** (i.e. if it's a function, it will not be called). Use this option to control if (and even which) clicks are allowed outside the trap in conjunction with `clickOutsideDeactivates: false`. Default: `false`. | ||
- ⚠️ If this is a function, it will be called **twice** on every click: First on `mousedown` (or `touchstart` on mobile), and then on the actual `click` if the function returned `true` on the first event. Be sure to check the event type if the double call is an issue in your code. | ||
- **clickOutsideDeactivates** `{boolean | (e: MouseEvent | TouchEvent) => boolean}`: If `true` or returns `true`, a click outside the focus trap will immediately deactivate the focus trap and allow the click event to do its thing (i.e. to pass-through to the element that was clicked). This option **takes precedence** over `allowOutsideClick` when it's set to `true`. Default: `false`. | ||
- 💬 If a function is provided, it will be called up to **twice** (but only if the click occurs *outside* the trap's containers): First on the `mousedown` (or `touchstart` on mobile) event and, if `true` was returned, again on the `click` event. It will get the same node each time, and it's recommended that the returned value is also the same each time. Be sure to check the event type if the double call is an issue in your code. | ||
- ⚠️ If you're using a password manager such as 1Password, where the app adds a clickable icon to all fillable fields, you should avoid using this option, and instead use the `allowOutsideClick` option to better control exactly when the focus trap can be deactivated. The clickable icons are usually positioned absolutely, floating on top of the fields, and therefore *not* part of the container the trap is managing. When using the `clickOutsideDeactivates` option, clicking on a field's 1Password icon will likely cause the trap to be unintentionally deactivated. | ||
- **allowOutsideClick** `{boolean | (e: MouseEvent | TouchEvent) => boolean}`: If set and is or returns `true`, a click outside the focus trap will not be prevented (letting focus temporarily escape the trap, without deactivating it), even if `clickOutsideDeactivates=false`. Default: `false`. | ||
- 💬 If this is a function, it will be called up to **twice** on every click (but only if the click occurs *outside* the trap's containers): First on `mousedown` (or `touchstart` on mobile), and then on the actual `click` if the function returned `true` on the first event. Be sure to check the event type if the double call is an issue in your code. | ||
- 💡 When `clickOutsideDeactivates=true`, this option is **ignored** (i.e. if it's a function, it will not be called). | ||
- Use this option to control if (and even which) clicks are allowed outside the trap in conjunction with `clickOutsideDeactivates=false`. | ||
- **returnFocusOnDeactivate** `{boolean}`: Default: `true`. If `false`, when the trap is deactivated, focus will *not* return to the element that had focus before activation. | ||
- **setReturnFocus** `{HTMLElement | SVGElement | string | (previousActiveElement: HTMLElement | SVGElement) => HTMLElement | SVGElement | false}`: By default, on **deactivation**, if `returnFocusOnDeactivate=true` (or if `returnFocus=true` in the [deactivation options](#trapdeactivatedeactivateoptions)), focus will be returned to the element that was focused just before activation. With this option, you can specify another element to programmatically receive focus after deactivation. It can be a DOM node, a selector string (which will be passed to `document.querySelector()` to find the DOM node **upon deactivation**), or a function that returns a DOM node to call **upon deactivation** (i.e. the selector and function options are only executed at the time the trap is deactivated), or `false` to leave focus where it is at the time of deactivation. | ||
- 💬 Using the selector or function options is a good way to return focus to a DOM node that may not even exist at the time the trap is activated. | ||
- ⚠️ See warning below about **Shadow DOM** and selector strings. | ||
- **setReturnFocus** `{HTMLElement | SVGElement | string | (previousActiveElement: HTMLElement | SVGElement) => HTMLElement | SVGElement | string | false}`: By default, on **deactivation**, if `returnFocusOnDeactivate=true` (or if `returnFocus=true` in the [deactivation options](#trapdeactivatedeactivateoptions)), focus will be returned to the element that was focused just before activation. With this option, you can specify another element to programmatically receive focus after deactivation. It can be a DOM node, a selector string (which will be passed to `document.querySelector()` to find the DOM node **upon deactivation**), or a function that returns any of these to call **upon deactivation** (i.e. the selector and function options are only executed at the time the trap is deactivated). Can also be `false` (or return `false`) to leave focus where it is at the time of deactivation. | ||
- 💬 Using the selector or function options is a good way to return focus to a DOM node that may not exist at the time the trap is activated. | ||
- ⚠️ See warning below about **Shadow DOM** and selector strings. | ||
- **preventScroll** `{boolean}`: By default, focus() will scroll to the element if not in viewport. It can produce unintended effects like scrolling back to the top of a modal. If set to `true`, no scroll will happen. | ||
- **delayInitialFocus** `{boolean}`: Default: `true`. Delays the autofocus to the next execution frame when the focus trap is activated. This prevents elements within the focusable element from capturing the event that triggered the focus trap activation. | ||
- **document** {Document}: Default: `window.document`. Document where the focus trap will be active. This allows to use FocusTrap in an iFrame context. | ||
- **tabbableOptions**: (optional) [tabbable options](https://github.com/focus-trap/tabbable#common-options) configurable on FocusTrap (all the *common options*). | ||
- ⚠️ See notes about **[testing in JSDom](#testing-in-jsdom)** (e.g. using Jest). | ||
#### Shadow DOM and selector strings | ||
#### Shadow DOM | ||
##### Selector strings | ||
⚠️ Beware that putting a focus-trap **inside** an open Shadow DOM means you must either: | ||
- **Not use selector strings** for options that support these (because nodes inside Shadow DOMs, even open shadows, are not visible via `document.querySelector()`); OR | ||
- You must **use the `document` option** to configure the focus trap to use your *shadow host* element as its document. The downside of this option is that, while selector queries on nodes inside your trap will now work, the trap will not prevent focus from being set on nodes outside your Shadow DOM, which is the same drawback as putting a focus trap <a href="https://focus-trap.github.io/focus-trap/#demo-in-iframe">inside an iframe</a>. | ||
- You must **use the `document` option** to configure the focus trap to use your *shadow host* element as its document. The downside of this option is that, while selector queries on nodes inside your trap will now work, the trap will not prevent focus from being set on nodes outside your Shadow DOM, which is the same drawback as putting a focus trap [inside an iframe](https://focus-trap.github.io/focus-trap/#demo-in-iframe). | ||
### trap.activate([activateOptions]) | ||
##### Closed shadows | ||
If you have closed shadow roots that you would like considered for tabbable/focusable nodes, use the `tabbableOptions.getShadowRoot` option to provide Tabbable (used internally) with a reference to a given node's shadow root so that it can be searched for candidates. | ||
### trap.active | ||
```typescript | ||
trap.active: boolean | ||
``` | ||
True if the trap is currently active. | ||
### trap.paused | ||
```typescript | ||
trap.paused: boolean | ||
``` | ||
True if the trap is currently paused. | ||
### trap.activate() | ||
```typescript | ||
trap.activate([activateOptions]) => FocusTrap | ||
``` | ||
Activates the focus trap, adding various event listeners to the document. | ||
@@ -144,4 +176,8 @@ | ||
### trap.deactivate([deactivateOptions]) | ||
### trap.deactivate() | ||
```typescript | ||
trap.deactivate([deactivateOptions]) => FocusTrap | ||
``` | ||
Deactivates the focus trap. | ||
@@ -155,9 +191,13 @@ | ||
- **returnFocus** `{boolean}`: Default: whatever you chose for `createOptions.returnFocusOnDeactivate`. If `true`, then the `setReturnFocus` option (specified when the trap was created) is used to determine where focus will be returned. | ||
- **onDeactivate** `{() => void}`: Default: whatever you chose for `createOptions.onDeactivate`. `null` or `false` are the equivalent of a `noop`. | ||
- **onPostDeactivate** `{() => void}`: Default: whatever you chose for `createOptions.onPostDeactivate`. `null` or `false` are the equivalent of a `noop`. | ||
- **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: Default: whatever you chose for `createOptions.checkCanReturnFocus`. Not called if the `returnFocus` option is falsy. `trigger` is either the originally focused node prior to activation, or the result of the `setReturnFocus` configuration option. | ||
- **returnFocus** `{boolean}`: Default: whatever you set for `createOptions.returnFocusOnDeactivate`. If `true`, then the `setReturnFocus` option (specified when the trap was created) is used to determine where focus will be returned. | ||
- **onDeactivate** `{() => void}`: Default: whatever you set for `createOptions.onDeactivate`. `null` or `false` are the equivalent of a `noop`. | ||
- **onPostDeactivate** `{() => void}`: Default: whatever you set for `createOptions.onPostDeactivate`. `null` or `false` are the equivalent of a `noop`. | ||
- **checkCanReturnFocus** `{(trigger: HTMLElement | SVGElement) => Promise<void>}`: Default: whatever you set for `createOptions.checkCanReturnFocus`. Not called if the `returnFocus` option is falsy. `trigger` is either the originally focused node prior to activation, or the result of the `setReturnFocus` configuration option. | ||
### trap.pause() | ||
```typescript | ||
trap.pause() => FocusTrap | ||
``` | ||
Pause an active focus trap's event listening without deactivating the trap. | ||
@@ -175,2 +215,6 @@ | ||
```typescript | ||
trap.unpause() => FocusTrap | ||
``` | ||
Unpause an active focus trap. (See `pause()`, above.) | ||
@@ -186,2 +230,6 @@ | ||
```typescript | ||
trap.updateContainerElements() => FocusTrap | ||
``` | ||
Update the element(s) that are used as containers for the focus trap. | ||
@@ -245,2 +293,16 @@ | ||
## Help | ||
### Testing in JSDom | ||
> ⚠️ JSDom is not officially supported. Your mileage may vary, and tests may break from one release to the next (even a patch or minor release). | ||
> | ||
> This topic is just here to help with what we know may affect your tests. | ||
In general, a focus trap is best tested in a full browser environment such as Cypress, Playwright, or Nightwatch where a full DOM is available. | ||
Sometimes, that's not entirely desirable, and depending on what you're testing, you may be able to get away with using JSDom (e.g. via Jest), but you'll have to configure your traps using the `tabbableOptions.displayCheck: 'none'` option. | ||
See [Testing tabbable in JSDom](https://github.com/focus-trap/tabbable#testing-in-jsdom) for more details. | ||
# Contributing | ||
@@ -263,7 +325,8 @@ | ||
<td align="center"><a href="https://github.com/Dan503"><img src="https://avatars.githubusercontent.com/u/10610368?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Daniel Tonon</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=Dan503" title="Documentation">📖</a> <a href="#tool-Dan503" title="Tools">🔧</a> <a href="#a11y-Dan503" title="Accessibility">️️️️♿️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=Dan503" title="Code">💻</a></td> | ||
<td align="center"><a href="https://github.com/DaviDevMod"><img src="https://avatars.githubusercontent.com/u/98312056?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DaviDevMod</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=DaviDevMod" title="Documentation">📖</a></td> | ||
<td align="center"><a href="http://davidtheclark.com/"><img src="https://avatars2.githubusercontent.com/u/628431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>David Clark</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Adavidtheclark" title="Bug reports">🐛</a> <a href="#infra-davidtheclark" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=davidtheclark" title="Documentation">📖</a> <a href="#maintenance-davidtheclark" title="Maintenance">🚧</a></td> | ||
<td align="center"><a href="https://github.com/features/security"><img src="https://avatars1.githubusercontent.com/u/27347476?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dependabot</b></sub></a><br /><a href="#maintenance-dependabot" title="Maintenance">🚧</a></td> | ||
<td align="center"><a href="https://github.com/michael-ar"><img src="https://avatars3.githubusercontent.com/u/18557997?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Reynolds</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Amichael-ar" title="Bug reports">🐛</a></td> | ||
</tr> | ||
<tr> | ||
<td align="center"><a href="https://github.com/michael-ar"><img src="https://avatars3.githubusercontent.com/u/18557997?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Reynolds</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Amichael-ar" title="Bug reports">🐛</a></td> | ||
<td align="center"><a href="https://github.com/liunate"><img src="https://avatars2.githubusercontent.com/u/38996291?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nate Liu</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=liunate" title="Tests">⚠️</a></td> | ||
@@ -275,5 +338,6 @@ <td align="center"><a href="https://github.com/far-fetched"><img src="https://avatars.githubusercontent.com/u/11621383?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Piotr Panek</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Afar-fetched" title="Bug reports">🐛</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=far-fetched" title="Documentation">📖</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=far-fetched" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=far-fetched" title="Tests">⚠️</a></td> | ||
<td align="center"><a href="https://seanmcp.com/"><img src="https://avatars1.githubusercontent.com/u/6360367?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sean McPherson</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=SeanMcP" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=SeanMcP" title="Documentation">📖</a></td> | ||
<td align="center"><a href="https://recollectr.io"><img src="https://avatars2.githubusercontent.com/u/6835891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Slapbox</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3ASlapbox" title="Bug reports">🐛</a></td> | ||
</tr> | ||
<tr> | ||
<td align="center"><a href="https://github.com/skriems"><img src="https://avatars.githubusercontent.com/u/15573317?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sebastian Kriems</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Askriems" title="Bug reports">🐛</a></td> | ||
<td align="center"><a href="https://recollectr.io"><img src="https://avatars2.githubusercontent.com/u/6835891?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Slapbox</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3ASlapbox" title="Bug reports">🐛</a></td> | ||
<td align="center"><a href="https://stefancameron.com/"><img src="https://avatars3.githubusercontent.com/u/2855350?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Cameron</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Code">💻</a> <a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Astefcameron" title="Bug reports">🐛</a> <a href="#infra-stefcameron" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=stefcameron" title="Documentation">📖</a> <a href="#maintenance-stefcameron" title="Maintenance">🚧</a></td> | ||
@@ -285,2 +349,5 @@ <td align="center"><a href="http://tylerhawkins.info/201R/"><img src="https://avatars0.githubusercontent.com/u/13806458?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tyler Hawkins</b></sub></a><br /><a href="#tool-thawkin3" title="Tools">🔧</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=thawkin3" title="Tests">⚠️</a> <a href="https://github.com/focus-trap/focus-trap/commits?author=thawkin3" title="Documentation">📖</a></td> | ||
</tr> | ||
<tr> | ||
<td align="center"><a href="https://github.com/jpveooys"><img src="https://avatars.githubusercontent.com/u/66470099?v=4?s=100" width="100px;" alt=""/><br /><sub><b>jpveooys</b></sub></a><br /><a href="https://github.com/focus-trap/focus-trap/issues?q=author%3Ajpveooys" title="Bug reports">🐛</a></td> | ||
</tr> | ||
</table> | ||
@@ -287,0 +354,0 @@ |
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
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
447877
2866
346
29
Updatedtabbable@^5.3.3