focus-trap
Advanced tools
Comparing version 5.1.0 to 6.0.0
# Changelog | ||
## 6.0.0 | ||
- Add boolean value support for `allowOutsideClick` option. | ||
- New `preventScroll` feature to *prevent* scrolling to the element getting focus if not in the viewport. | ||
- Changed code formatting to use dangling commas where ES5 supports them. | ||
- **BREAKING**: Updated [tabbable](https://github.com/focus-trap/tabbable/blob/master/CHANGELOG.md#500) dependency to the new 5.0.0 release which contains breaking changes to its `isTabbableRadio()` internal function. | ||
- Help with tree shaking by having `package.json` state `sideEffects: false` to mark this module as having no side effects as a result of merely importing it. | ||
- **BREAKING**: This `package.json`'s "main" no longer points to `./index.js` in the package (although it still points to a CJS module, so it's possible this actually doesn't break anything). It now has: | ||
- "main": `dist/focus-trap.min.js` (the CJS bundle) | ||
- "module": `dist/focus-trap.esm.min.js` (the **new ESM bundle**) | ||
- the UMD is `dist/focus-trap.umd.min.js` if needed (convenient for loading directly in an older browser that doesn't support ESM) | ||
- **NOTE:** The CJS build no longer provides a function as a default export. Use `const { createFocusTrap } = require('focus-trap');` to get the function from before. | ||
- **NOTE:** The ESM build does not provide a default export. Use `import { createFocusTrap } from 'focus-trap';` to import the module. | ||
- **New ESM Build**: Included in `dist/focus-trap.esm.*`. | ||
- New UMD Build: Included in `dist/focus-trap.umd.*`. | ||
## 5.1.0 | ||
@@ -4,0 +20,0 @@ |
@@ -1,11 +0,17 @@ | ||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||
/*! | ||
* focus-trap 6.0.0 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
*/ | ||
'use strict'; | ||
Object.defineProperty(exports, '__esModule', { value: true }); | ||
var tabbable = require('tabbable'); | ||
var xtend = require('xtend'); | ||
var activeFocusDelay; | ||
var activeFocusTraps = (function() { | ||
var activeFocusTraps = (function () { | ||
var trapQueue = []; | ||
return { | ||
activateTrap: function(trap) { | ||
activateTrap: function (trap) { | ||
if (trapQueue.length > 0) { | ||
@@ -28,3 +34,3 @@ var activeTrap = trapQueue[trapQueue.length - 1]; | ||
deactivateTrap: function(trap) { | ||
deactivateTrap: function (trap) { | ||
var trapIndex = trapQueue.indexOf(trap); | ||
@@ -38,7 +44,7 @@ if (trapIndex !== -1) { | ||
} | ||
} | ||
}, | ||
}; | ||
})(); | ||
function focusTrap(element, userOptions) { | ||
function createFocusTrap(element, userOptions) { | ||
var doc = document; | ||
@@ -48,9 +54,7 @@ var container = | ||
var config = xtend( | ||
{ | ||
returnFocusOnDeactivate: true, | ||
escapeDeactivates: true | ||
}, | ||
userOptions | ||
); | ||
var config = { | ||
returnFocusOnDeactivate: true, | ||
escapeDeactivates: true, | ||
...userOptions, | ||
}; | ||
@@ -63,3 +67,3 @@ var state = { | ||
active: false, | ||
paused: false | ||
paused: false, | ||
}; | ||
@@ -71,3 +75,3 @@ | ||
pause: pause, | ||
unpause: unpause | ||
unpause: unpause, | ||
}; | ||
@@ -122,3 +126,3 @@ | ||
if (returnFocus) { | ||
delay(function() { | ||
delay(function () { | ||
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)); | ||
@@ -152,3 +156,3 @@ }); | ||
// that caused the focus trap activation. | ||
activeFocusDelay = delay(function() { | ||
activeFocusDelay = delay(function () { | ||
tryFocus(getInitialFocusNode()); | ||
@@ -160,15 +164,15 @@ }); | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
doc.addEventListener('touchstart', checkPointerDown, { | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
doc.addEventListener('click', checkClick, { | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
doc.addEventListener('keydown', checkKey, { | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
@@ -242,3 +246,3 @@ | ||
deactivate({ | ||
returnFocus: !tabbable.isFocusable(e.target) | ||
returnFocus: !tabbable.isFocusable(e.target), | ||
}); | ||
@@ -250,3 +254,8 @@ return; | ||
// then on mobile they will be blocked anyways if `touchstart` is blocked.) | ||
if (config.allowOutsideClick && config.allowOutsideClick(e)) { | ||
if ( | ||
config.allowOutsideClick && | ||
(typeof config.allowOutsideClick === 'boolean' | ||
? config.allowOutsideClick | ||
: config.allowOutsideClick(e)) | ||
) { | ||
return; | ||
@@ -300,3 +309,8 @@ } | ||
if (container.contains(e.target)) return; | ||
if (config.allowOutsideClick && config.allowOutsideClick(e)) { | ||
if ( | ||
config.allowOutsideClick && | ||
(typeof config.allowOutsideClick === 'boolean' | ||
? config.allowOutsideClick | ||
: config.allowOutsideClick(e)) | ||
) { | ||
return; | ||
@@ -309,3 +323,3 @@ } | ||
function updateTabbableNodes() { | ||
var tabbableNodes = tabbable(container); | ||
var tabbableNodes = tabbable.tabbable(container); | ||
state.firstTabbableNode = tabbableNodes[0] || getInitialFocusNode(); | ||
@@ -322,3 +336,3 @@ state.lastTabbableNode = | ||
} | ||
node.focus(); | ||
node.focus({ preventScroll: !!config.preventScroll }); | ||
state.mostRecentlyFocusedNode = node; | ||
@@ -351,181 +365,3 @@ if (isSelectableInput(node)) { | ||
module.exports = focusTrap; | ||
},{"tabbable":2,"xtend":3}],2:[function(require,module,exports){ | ||
var candidateSelectors = [ | ||
'input', | ||
'select', | ||
'textarea', | ||
'a[href]', | ||
'button', | ||
'[tabindex]', | ||
'audio[controls]', | ||
'video[controls]', | ||
'[contenteditable]:not([contenteditable="false"])', | ||
]; | ||
var candidateSelector = candidateSelectors.join(','); | ||
var matches = typeof Element === 'undefined' | ||
? function () {} | ||
: Element.prototype.matches || Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; | ||
function tabbable(el, options) { | ||
options = options || {}; | ||
var regularTabbables = []; | ||
var orderedTabbables = []; | ||
var candidates = el.querySelectorAll(candidateSelector); | ||
if (options.includeContainer) { | ||
if (matches.call(el, candidateSelector)) { | ||
candidates = Array.prototype.slice.apply(candidates); | ||
candidates.unshift(el); | ||
} | ||
} | ||
var i, candidate, candidateTabindex; | ||
for (i = 0; i < candidates.length; i++) { | ||
candidate = candidates[i]; | ||
if (!isNodeMatchingSelectorTabbable(candidate)) continue; | ||
candidateTabindex = getTabindex(candidate); | ||
if (candidateTabindex === 0) { | ||
regularTabbables.push(candidate); | ||
} else { | ||
orderedTabbables.push({ | ||
documentOrder: i, | ||
tabIndex: candidateTabindex, | ||
node: candidate, | ||
}); | ||
} | ||
} | ||
var tabbableNodes = orderedTabbables | ||
.sort(sortOrderedTabbables) | ||
.map(function(a) { return a.node }) | ||
.concat(regularTabbables); | ||
return tabbableNodes; | ||
} | ||
tabbable.isTabbable = isTabbable; | ||
tabbable.isFocusable = isFocusable; | ||
function isNodeMatchingSelectorTabbable(node) { | ||
if ( | ||
!isNodeMatchingSelectorFocusable(node) | ||
|| isNonTabbableRadio(node) | ||
|| getTabindex(node) < 0 | ||
) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
function isTabbable(node) { | ||
if (!node) throw new Error('No node provided'); | ||
if (matches.call(node, candidateSelector) === false) return false; | ||
return isNodeMatchingSelectorTabbable(node); | ||
} | ||
function isNodeMatchingSelectorFocusable(node) { | ||
if ( | ||
node.disabled | ||
|| isHiddenInput(node) | ||
|| isHidden(node) | ||
) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
var focusableCandidateSelector = candidateSelectors.concat('iframe').join(','); | ||
function isFocusable(node) { | ||
if (!node) throw new Error('No node provided'); | ||
if (matches.call(node, focusableCandidateSelector) === false) return false; | ||
return isNodeMatchingSelectorFocusable(node); | ||
} | ||
function getTabindex(node) { | ||
var tabindexAttr = parseInt(node.getAttribute('tabindex'), 10); | ||
if (!isNaN(tabindexAttr)) return tabindexAttr; | ||
// Browsers do not return `tabIndex` correctly for contentEditable nodes; | ||
// so if they don't have a tabindex attribute specifically set, assume it's 0. | ||
if (isContentEditable(node)) return 0; | ||
return node.tabIndex; | ||
} | ||
function sortOrderedTabbables(a, b) { | ||
return a.tabIndex === b.tabIndex ? a.documentOrder - b.documentOrder : a.tabIndex - b.tabIndex; | ||
} | ||
function isContentEditable(node) { | ||
return node.contentEditable === 'true'; | ||
} | ||
function isInput(node) { | ||
return node.tagName === 'INPUT'; | ||
} | ||
function isHiddenInput(node) { | ||
return isInput(node) && node.type === 'hidden'; | ||
} | ||
function isRadio(node) { | ||
return isInput(node) && node.type === 'radio'; | ||
} | ||
function isNonTabbableRadio(node) { | ||
return isRadio(node) && !isTabbableRadio(node); | ||
} | ||
function getCheckedRadio(nodes) { | ||
for (var i = 0; i < nodes.length; i++) { | ||
if (nodes[i].checked) { | ||
return nodes[i]; | ||
} | ||
} | ||
} | ||
function isTabbableRadio(node) { | ||
if (!node.name) return true; | ||
// This won't account for the edge case where you have radio groups with the same | ||
// in separate forms on the same page. | ||
var radioSet = node.ownerDocument.querySelectorAll('input[type="radio"][name="' + node.name + '"]'); | ||
var checked = getCheckedRadio(radioSet); | ||
return !checked || checked === node; | ||
} | ||
function isHidden(node) { | ||
// offsetParent being null will allow detecting cases where an element is invisible or inside an invisible element, | ||
// as long as the element does not use position: fixed. For them, their visibility has to be checked directly as well. | ||
return node.offsetParent === null || getComputedStyle(node).visibility === 'hidden'; | ||
} | ||
module.exports = tabbable; | ||
},{}],3:[function(require,module,exports){ | ||
module.exports = extend | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
function extend() { | ||
var target = {} | ||
for (var i = 0; i < arguments.length; i++) { | ||
var source = arguments[i] | ||
for (var key in source) { | ||
if (hasOwnProperty.call(source, key)) { | ||
target[key] = source[key] | ||
} | ||
} | ||
} | ||
return target | ||
} | ||
},{}]},{},[1])(1) | ||
}); | ||
exports.createFocusTrap = createFocusTrap; | ||
//# sourceMappingURL=focus-trap.js.map |
@@ -1,1 +0,6 @@ | ||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.focusTrap=f()}})(function(){var define,module,exports;return function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r}()({1:[function(require,module,exports){var tabbable=require("tabbable");var xtend=require("xtend");var activeFocusDelay;var activeFocusTraps=function(){var trapQueue=[];return{activateTrap:function(trap){if(trapQueue.length>0){var activeTrap=trapQueue[trapQueue.length-1];if(activeTrap!==trap){activeTrap.pause()}}var trapIndex=trapQueue.indexOf(trap);if(trapIndex===-1){trapQueue.push(trap)}else{trapQueue.splice(trapIndex,1);trapQueue.push(trap)}},deactivateTrap:function(trap){var trapIndex=trapQueue.indexOf(trap);if(trapIndex!==-1){trapQueue.splice(trapIndex,1)}if(trapQueue.length>0){trapQueue[trapQueue.length-1].unpause()}}}}();function focusTrap(element,userOptions){var doc=document;var container=typeof element==="string"?doc.querySelector(element):element;var config=xtend({returnFocusOnDeactivate:true,escapeDeactivates:true},userOptions);var state={firstTabbableNode:null,lastTabbableNode:null,nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:false,paused:false};var trap={activate:activate,deactivate:deactivate,pause:pause,unpause:unpause};return trap;function activate(activateOptions){if(state.active)return;updateTabbableNodes();state.active=true;state.paused=false;state.nodeFocusedBeforeActivation=doc.activeElement;var onActivate=activateOptions&&activateOptions.onActivate?activateOptions.onActivate:config.onActivate;if(onActivate){onActivate()}addListeners();return trap}function deactivate(deactivateOptions){if(!state.active)return;clearTimeout(activeFocusDelay);removeListeners();state.active=false;state.paused=false;activeFocusTraps.deactivateTrap(trap);var onDeactivate=deactivateOptions&&deactivateOptions.onDeactivate!==undefined?deactivateOptions.onDeactivate:config.onDeactivate;if(onDeactivate){onDeactivate()}var returnFocus=deactivateOptions&&deactivateOptions.returnFocus!==undefined?deactivateOptions.returnFocus:config.returnFocusOnDeactivate;if(returnFocus){delay(function(){tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation))})}return trap}function pause(){if(state.paused||!state.active)return;state.paused=true;removeListeners()}function unpause(){if(!state.paused||!state.active)return;state.paused=false;updateTabbableNodes();addListeners()}function addListeners(){if(!state.active)return;activeFocusTraps.activateTrap(trap);activeFocusDelay=delay(function(){tryFocus(getInitialFocusNode())});doc.addEventListener("focusin",checkFocusIn,true);doc.addEventListener("mousedown",checkPointerDown,{capture:true,passive:false});doc.addEventListener("touchstart",checkPointerDown,{capture:true,passive:false});doc.addEventListener("click",checkClick,{capture:true,passive:false});doc.addEventListener("keydown",checkKey,{capture:true,passive:false});return trap}function removeListeners(){if(!state.active)return;doc.removeEventListener("focusin",checkFocusIn,true);doc.removeEventListener("mousedown",checkPointerDown,true);doc.removeEventListener("touchstart",checkPointerDown,true);doc.removeEventListener("click",checkClick,true);doc.removeEventListener("keydown",checkKey,true);return trap}function getNodeForOption(optionName){var optionValue=config[optionName];var node=optionValue;if(!optionValue){return null}if(typeof optionValue==="string"){node=doc.querySelector(optionValue);if(!node){throw new Error("`"+optionName+"` refers to no known node")}}if(typeof optionValue==="function"){node=optionValue();if(!node){throw new Error("`"+optionName+"` did not return a node")}}return node}function getInitialFocusNode(){var node;if(getNodeForOption("initialFocus")!==null){node=getNodeForOption("initialFocus")}else if(container.contains(doc.activeElement)){node=doc.activeElement}else{node=state.firstTabbableNode||getNodeForOption("fallbackFocus")}if(!node){throw new Error("Your focus-trap needs to have at least one focusable element")}return node}function getReturnFocusNode(previousActiveElement){var node=getNodeForOption("setReturnFocus");return node?node:previousActiveElement}function checkPointerDown(e){if(container.contains(e.target))return;if(config.clickOutsideDeactivates){deactivate({returnFocus:!tabbable.isFocusable(e.target)});return}if(config.allowOutsideClick&&config.allowOutsideClick(e)){return}e.preventDefault()}function checkFocusIn(e){if(container.contains(e.target)||e.target instanceof Document){return}e.stopImmediatePropagation();tryFocus(state.mostRecentlyFocusedNode||getInitialFocusNode())}function checkKey(e){if(config.escapeDeactivates!==false&&isEscapeEvent(e)){e.preventDefault();deactivate();return}if(isTabEvent(e)){checkTab(e);return}}function checkTab(e){updateTabbableNodes();if(e.shiftKey&&e.target===state.firstTabbableNode){e.preventDefault();tryFocus(state.lastTabbableNode);return}if(!e.shiftKey&&e.target===state.lastTabbableNode){e.preventDefault();tryFocus(state.firstTabbableNode);return}}function checkClick(e){if(config.clickOutsideDeactivates)return;if(container.contains(e.target))return;if(config.allowOutsideClick&&config.allowOutsideClick(e)){return}e.preventDefault();e.stopImmediatePropagation()}function updateTabbableNodes(){var tabbableNodes=tabbable(container);state.firstTabbableNode=tabbableNodes[0]||getInitialFocusNode();state.lastTabbableNode=tabbableNodes[tabbableNodes.length-1]||getInitialFocusNode()}function tryFocus(node){if(node===doc.activeElement)return;if(!node||!node.focus){tryFocus(getInitialFocusNode());return}node.focus();state.mostRecentlyFocusedNode=node;if(isSelectableInput(node)){node.select()}}}function isSelectableInput(node){return node.tagName&&node.tagName.toLowerCase()==="input"&&typeof node.select==="function"}function isEscapeEvent(e){return e.key==="Escape"||e.key==="Esc"||e.keyCode===27}function isTabEvent(e){return e.key==="Tab"||e.keyCode===9}function delay(fn){return setTimeout(fn,0)}module.exports=focusTrap},{tabbable:2,xtend:3}],2:[function(require,module,exports){var candidateSelectors=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])'];var candidateSelector=candidateSelectors.join(",");var matches=typeof Element==="undefined"?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector;function tabbable(el,options){options=options||{};var regularTabbables=[];var orderedTabbables=[];var candidates=el.querySelectorAll(candidateSelector);if(options.includeContainer){if(matches.call(el,candidateSelector)){candidates=Array.prototype.slice.apply(candidates);candidates.unshift(el)}}var i,candidate,candidateTabindex;for(i=0;i<candidates.length;i++){candidate=candidates[i];if(!isNodeMatchingSelectorTabbable(candidate))continue;candidateTabindex=getTabindex(candidate);if(candidateTabindex===0){regularTabbables.push(candidate)}else{orderedTabbables.push({documentOrder:i,tabIndex:candidateTabindex,node:candidate})}}var tabbableNodes=orderedTabbables.sort(sortOrderedTabbables).map(function(a){return a.node}).concat(regularTabbables);return tabbableNodes}tabbable.isTabbable=isTabbable;tabbable.isFocusable=isFocusable;function isNodeMatchingSelectorTabbable(node){if(!isNodeMatchingSelectorFocusable(node)||isNonTabbableRadio(node)||getTabindex(node)<0){return false}return true}function isTabbable(node){if(!node)throw new Error("No node provided");if(matches.call(node,candidateSelector)===false)return false;return isNodeMatchingSelectorTabbable(node)}function isNodeMatchingSelectorFocusable(node){if(node.disabled||isHiddenInput(node)||isHidden(node)){return false}return true}var focusableCandidateSelector=candidateSelectors.concat("iframe").join(",");function isFocusable(node){if(!node)throw new Error("No node provided");if(matches.call(node,focusableCandidateSelector)===false)return false;return isNodeMatchingSelectorFocusable(node)}function getTabindex(node){var tabindexAttr=parseInt(node.getAttribute("tabindex"),10);if(!isNaN(tabindexAttr))return tabindexAttr;if(isContentEditable(node))return 0;return node.tabIndex}function sortOrderedTabbables(a,b){return a.tabIndex===b.tabIndex?a.documentOrder-b.documentOrder:a.tabIndex-b.tabIndex}function isContentEditable(node){return node.contentEditable==="true"}function isInput(node){return node.tagName==="INPUT"}function isHiddenInput(node){return isInput(node)&&node.type==="hidden"}function isRadio(node){return isInput(node)&&node.type==="radio"}function isNonTabbableRadio(node){return isRadio(node)&&!isTabbableRadio(node)}function getCheckedRadio(nodes){for(var i=0;i<nodes.length;i++){if(nodes[i].checked){return nodes[i]}}}function isTabbableRadio(node){if(!node.name)return true;var radioSet=node.ownerDocument.querySelectorAll('input[type="radio"][name="'+node.name+'"]');var checked=getCheckedRadio(radioSet);return!checked||checked===node}function isHidden(node){return node.offsetParent===null||getComputedStyle(node).visibility==="hidden"}module.exports=tabbable},{}],3:[function(require,module,exports){module.exports=extend;var hasOwnProperty=Object.prototype.hasOwnProperty;function extend(){var target={};for(var i=0;i<arguments.length;i++){var source=arguments[i];for(var key in source){if(hasOwnProperty.call(source,key)){target[key]=source[key]}}}return target}},{}]},{},[1])(1)}); | ||
/*! | ||
* focus-trap 6.0.0 | ||
* @license MIT, https://github.com/focus-trap/focus-trap/blob/master/LICENSE | ||
*/ | ||
"use strict";function e(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function t(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}Object.defineProperty(exports,"__esModule",{value:!0});var n=["input","select","textarea","a[href]","button","[tabindex]","audio[controls]","video[controls]",'[contenteditable]:not([contenteditable="false"])',"details>summary"],r=n.join(","),o="undefined"==typeof Element?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector;function a(e){return!(!i(e)||function(e){return function(e){return d(e)&&"radio"===e.type}(e)&&!function(e){if(!e.name)return!0;var t=function(e,t){for(var n=0;n<e.length;n++)if(e[n].checked&&e[n].form===t)return e[n]}((e.form||e.ownerDocument).querySelectorAll('input[type="radio"][name="'+e.name+'"]'),e.form);return!t||t===e}(e)}(e)||l(e)<0)}function i(e){return!(e.disabled||function(e){return d(e)&&"hidden"===e.type}(e)||function(e){return null===e.offsetParent||"hidden"===getComputedStyle(e).visibility}(e))}var c,u=n.concat("iframe").join(",");function s(e){if(!e)throw new Error("No node provided");return!1!==o.call(e,u)&&i(e)}function l(e){var t=parseInt(e.getAttribute("tabindex"),10);return isNaN(t)?function(e){return"true"===e.contentEditable}(e)?0:"AUDIO"!==e.nodeName&&"VIDEO"!==e.nodeName||null!==e.getAttribute("tabindex")?e.tabIndex:0:t}function f(e,t){return e.tabIndex===t.tabIndex?e.documentOrder-t.documentOrder:e.tabIndex-t.tabIndex}function d(e){return"INPUT"===e.tagName}var v,p=(v=[],{activateTrap:function(e){if(v.length>0){var t=v[v.length-1];t!==e&&t.pause()}var n=v.indexOf(e);-1===n||v.splice(n,1),v.push(e)},deactivateTrap:function(e){var t=v.indexOf(e);-1!==t&&v.splice(t,1),v.length>0&&v[v.length-1].unpause()}});function b(e){return setTimeout(e,0)}exports.createFocusTrap=function(n,i){var u=document,d="string"==typeof n?u.querySelector(n):n,v=function(n){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?t(Object(o),!0).forEach((function(t){e(n,t,o[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(n,Object.getOwnPropertyDescriptors(o)):t(Object(o)).forEach((function(e){Object.defineProperty(n,e,Object.getOwnPropertyDescriptor(o,e))}))}return n}({returnFocusOnDeactivate:!0,escapeDeactivates:!0},i),m={firstTabbableNode:null,lastTabbableNode:null,nodeFocusedBeforeActivation:null,mostRecentlyFocusedNode:null,active:!1,paused:!1},y={activate:function(e){if(m.active)return;F(),m.active=!0,m.paused=!1,m.nodeFocusedBeforeActivation=u.activeElement;var t=e&&e.onActivate?e.onActivate:v.onActivate;t&&t();return g(),y},deactivate:O,pause:function(){if(m.paused||!m.active)return;m.paused=!0,h()},unpause:function(){if(!m.paused||!m.active)return;m.paused=!1,F(),g()}};return y;function O(e){if(m.active){clearTimeout(c),h(),m.active=!1,m.paused=!1,p.deactivateTrap(y);var t=e&&void 0!==e.onDeactivate?e.onDeactivate:v.onDeactivate;return t&&t(),(e&&void 0!==e.returnFocus?e.returnFocus:v.returnFocusOnDeactivate)&&b((function(){var e;j((e=m.nodeFocusedBeforeActivation,w("setReturnFocus")||e))})),y}}function g(){if(m.active)return p.activateTrap(y),c=b((function(){j(E())})),u.addEventListener("focusin",D,!0),u.addEventListener("mousedown",k,{capture:!0,passive:!1}),u.addEventListener("touchstart",k,{capture:!0,passive:!1}),u.addEventListener("click",T,{capture:!0,passive:!1}),u.addEventListener("keydown",N,{capture:!0,passive:!1}),y}function h(){if(m.active)return u.removeEventListener("focusin",D,!0),u.removeEventListener("mousedown",k,!0),u.removeEventListener("touchstart",k,!0),u.removeEventListener("click",T,!0),u.removeEventListener("keydown",N,!0),y}function w(e){var t=v[e],n=t;if(!t)return null;if("string"==typeof t&&!(n=u.querySelector(t)))throw new Error("`"+e+"` refers to no known node");if("function"==typeof t&&!(n=t()))throw new Error("`"+e+"` did not return a node");return n}function E(){var e;if(!(e=null!==w("initialFocus")?w("initialFocus"):d.contains(u.activeElement)?u.activeElement:m.firstTabbableNode||w("fallbackFocus")))throw new Error("Your focus-trap needs to have at least one focusable element");return e}function k(e){d.contains(e.target)||(v.clickOutsideDeactivates?O({returnFocus:!s(e.target)}):v.allowOutsideClick&&("boolean"==typeof v.allowOutsideClick?v.allowOutsideClick:v.allowOutsideClick(e))||e.preventDefault())}function D(e){d.contains(e.target)||e.target instanceof Document||(e.stopImmediatePropagation(),j(m.mostRecentlyFocusedNode||E()))}function N(e){if(!1!==v.escapeDeactivates&&function(e){return"Escape"===e.key||"Esc"===e.key||27===e.keyCode}(e))return e.preventDefault(),void O();(function(e){return"Tab"===e.key||9===e.keyCode})(e)&&function(e){if(F(),e.shiftKey&&e.target===m.firstTabbableNode)return e.preventDefault(),void j(m.lastTabbableNode);if(!e.shiftKey&&e.target===m.lastTabbableNode)e.preventDefault(),j(m.firstTabbableNode)}(e)}function T(e){v.clickOutsideDeactivates||d.contains(e.target)||v.allowOutsideClick&&("boolean"==typeof v.allowOutsideClick?v.allowOutsideClick:v.allowOutsideClick(e))||(e.preventDefault(),e.stopImmediatePropagation())}function F(){var e=function(e,t){t=t||{};var n,i,c=[],u=[],s=e.querySelectorAll(r);t.includeContainer&&o.call(e,r)&&(s=Array.prototype.slice.apply(s)).unshift(e);for(var d=0;d<s.length;d++)a(n=s[d])&&(0===(i=l(n))?c.push(n):u.push({documentOrder:d,tabIndex:i,node:n}));return u.sort(f).map((function(e){return e.node})).concat(c)}(d);m.firstTabbableNode=e[0]||E(),m.lastTabbableNode=e[e.length-1]||E()}function j(e){e!==u.activeElement&&(e&&e.focus?(e.focus({preventScroll:!!v.preventScroll}),m.mostRecentlyFocusedNode=e,function(e){return e.tagName&&"input"===e.tagName.toLowerCase()&&"function"==typeof e.select}(e)&&e.select()):j(E()))}}; | ||
//# sourceMappingURL=focus-trap.min.js.map |
@@ -1,2 +0,2 @@ | ||
declare module "focus-trap" { | ||
declare module 'focus-trap' { | ||
/** | ||
@@ -9,2 +9,4 @@ * A DOM node, a selector string (which will be passed to | ||
type MouseEventToBoolean = (event: MouseEvent) => boolean | ||
export interface Options { | ||
@@ -15,3 +17,2 @@ /** | ||
onActivate?: () => void; | ||
/** | ||
@@ -21,3 +22,2 @@ * A function that will be called when the focus trap deactivates. | ||
onDeactivate?: () => void; | ||
/** | ||
@@ -29,3 +29,2 @@ * By default, when a focus trap is activated the first element in the | ||
initialFocus?: FocusTarget; | ||
/** | ||
@@ -41,3 +40,2 @@ * By default, an error will be thrown if the focus trap contains no | ||
fallbackFocus?: FocusTarget; | ||
/** | ||
@@ -48,9 +46,7 @@ * Default: `true`. If `false`, when the trap is deactivated, | ||
returnFocusOnDeactivate?: boolean; | ||
/** | ||
* By default, focus trap on deactivation will return to the element | ||
* that was focused before activation. | ||
*/ | ||
*/ | ||
setReturnFocus?: FocusTarget; | ||
/** | ||
@@ -63,3 +59,2 @@ * Default: `true`. If `false`, the `Escape` key will not trigger | ||
escapeDeactivates?: boolean; | ||
/** | ||
@@ -70,9 +65,19 @@ * Default: `false`. If `true`, a click outside the focus trap will | ||
clickOutsideDeactivates?: boolean; | ||
allowOutsideClick?: (event: MouseEvent) => boolean; | ||
/** | ||
* If set and is or returns `true`, | ||
* a click outside the focus trap will not be prevented, | ||
* even when `clickOutsideDeactivates` is `false`. | ||
*/ | ||
allowOutsideClick?: boolean | MouseEventToBoolean; | ||
/** | ||
* 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. | ||
*/ | ||
preventScroll?: boolean; | ||
} | ||
type ActivateOptions = Pick<Options, "onActivate">; | ||
type ActivateOptions = Pick<Options, 'onActivate'>; | ||
interface DeactivateOptions extends Pick<Options, "onDeactivate"> { | ||
interface DeactivateOptions extends Pick<Options, 'onDeactivate'> { | ||
returnFocus?: boolean; | ||
@@ -95,3 +100,3 @@ } | ||
*/ | ||
export default function focusTrap( | ||
export function createFocusTrap( | ||
element: HTMLElement | string, | ||
@@ -98,0 +103,0 @@ userOptions?: Options |
61
index.js
@@ -1,10 +0,9 @@ | ||
var tabbable = require('tabbable'); | ||
var xtend = require('xtend'); | ||
import { tabbable, isFocusable } from 'tabbable'; | ||
var activeFocusDelay; | ||
var activeFocusTraps = (function() { | ||
var activeFocusTraps = (function () { | ||
var trapQueue = []; | ||
return { | ||
activateTrap: function(trap) { | ||
activateTrap: function (trap) { | ||
if (trapQueue.length > 0) { | ||
@@ -27,3 +26,3 @@ var activeTrap = trapQueue[trapQueue.length - 1]; | ||
deactivateTrap: function(trap) { | ||
deactivateTrap: function (trap) { | ||
var trapIndex = trapQueue.indexOf(trap); | ||
@@ -37,7 +36,7 @@ if (trapIndex !== -1) { | ||
} | ||
} | ||
}, | ||
}; | ||
})(); | ||
function focusTrap(element, userOptions) { | ||
function createFocusTrap(element, userOptions) { | ||
var doc = document; | ||
@@ -47,9 +46,7 @@ var container = | ||
var config = xtend( | ||
{ | ||
returnFocusOnDeactivate: true, | ||
escapeDeactivates: true | ||
}, | ||
userOptions | ||
); | ||
var config = { | ||
returnFocusOnDeactivate: true, | ||
escapeDeactivates: true, | ||
...userOptions, | ||
}; | ||
@@ -62,3 +59,3 @@ var state = { | ||
active: false, | ||
paused: false | ||
paused: false, | ||
}; | ||
@@ -70,3 +67,3 @@ | ||
pause: pause, | ||
unpause: unpause | ||
unpause: unpause, | ||
}; | ||
@@ -121,3 +118,3 @@ | ||
if (returnFocus) { | ||
delay(function() { | ||
delay(function () { | ||
tryFocus(getReturnFocusNode(state.nodeFocusedBeforeActivation)); | ||
@@ -151,3 +148,3 @@ }); | ||
// that caused the focus trap activation. | ||
activeFocusDelay = delay(function() { | ||
activeFocusDelay = delay(function () { | ||
tryFocus(getInitialFocusNode()); | ||
@@ -159,15 +156,15 @@ }); | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
doc.addEventListener('touchstart', checkPointerDown, { | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
doc.addEventListener('click', checkClick, { | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
doc.addEventListener('keydown', checkKey, { | ||
capture: true, | ||
passive: false | ||
passive: false, | ||
}); | ||
@@ -241,3 +238,3 @@ | ||
deactivate({ | ||
returnFocus: !tabbable.isFocusable(e.target) | ||
returnFocus: !isFocusable(e.target), | ||
}); | ||
@@ -249,3 +246,8 @@ return; | ||
// then on mobile they will be blocked anyways if `touchstart` is blocked.) | ||
if (config.allowOutsideClick && config.allowOutsideClick(e)) { | ||
if ( | ||
config.allowOutsideClick && | ||
(typeof config.allowOutsideClick === 'boolean' | ||
? config.allowOutsideClick | ||
: config.allowOutsideClick(e)) | ||
) { | ||
return; | ||
@@ -299,3 +301,8 @@ } | ||
if (container.contains(e.target)) return; | ||
if (config.allowOutsideClick && config.allowOutsideClick(e)) { | ||
if ( | ||
config.allowOutsideClick && | ||
(typeof config.allowOutsideClick === 'boolean' | ||
? config.allowOutsideClick | ||
: config.allowOutsideClick(e)) | ||
) { | ||
return; | ||
@@ -320,3 +327,3 @@ } | ||
} | ||
node.focus(); | ||
node.focus({ preventScroll: !!config.preventScroll }); | ||
state.mostRecentlyFocusedNode = node; | ||
@@ -349,2 +356,2 @@ if (isSelectableInput(node)) { | ||
module.exports = focusTrap; | ||
export { createFocusTrap }; |
{ | ||
"name": "focus-trap", | ||
"version": "5.1.0", | ||
"version": "6.0.0", | ||
"description": "Trap focus within a DOM node.", | ||
"main": "index.js", | ||
"main": "dist/focus-trap.min.js", | ||
"module": "dist/focus-trap.esm.min.js", | ||
"types": "index.d.ts", | ||
"sideEffects": false, | ||
"files": [ | ||
"package.json", | ||
"README.md", | ||
"CHANGELOG.md", | ||
"LICENSE", | ||
"index.js", | ||
"index.d.ts", | ||
"dist" | ||
], | ||
"scripts": { | ||
"precommit": "lint-staged", | ||
"format": "prettier --write '**/*.{js,json}'", | ||
"lint": "eslint .", | ||
"demo-bundle": "browserify demo/js/index.js -o demo/demo-bundle.js", | ||
"clean": "del-cli dist && make-dir dist", | ||
"build-dev": "npm run clean && browserify index.js -s focusTrap > dist/focus-trap.js", | ||
"minify": "uglifyjs dist/focus-trap.js > dist/focus-trap.min.js", | ||
"build": "npm run build-dev && npm run minify", | ||
"start": "budo demo/js/index.js:demo-bundle.js --dir demo --live", | ||
"test": "npm run lint", | ||
"prepublishOnly": "npm run build" | ||
"format": "prettier --write \"{*,src/**/*,test/**/*,demo/js/**/*,.github/workflows/*}.+(js|yml)\"", | ||
"format-check": "prettier --check \"{*,src/**/*,test/**/*,demo/js/**/*,.github/workflows/*}.+(js|yml)\"", | ||
"lint": "eslint \"*.js\" \"demo/**/*.js\"", | ||
"clean": "rm -rf ./dist", | ||
"compile-esm": "BUILD_ENV=esm rollup -c", | ||
"compile-cjs": "BUILD_ENV=cjs rollup -c", | ||
"compile-umd": "BUILD_ENV=umd rollup -c", | ||
"compile": "yarn compile-esm && yarn compile-cjs && yarn compile-umd", | ||
"build": "yarn clean && yarn compile", | ||
"start": "yarn build && budo demo/js/index.js:demo-bundle.js --dir demo --live -- -t babelify", | ||
"test-types": "tsc index.d.ts", | ||
"test-unit": "echo \"No unit tests to run!\"", | ||
"test": "yarn format-check && yarn lint && yarn test-unit && yarn test-types", | ||
"prepublishOnly": "yarn build", | ||
"release": "yarn build && changeset publish" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/davidtheclark/focus-trap.git" | ||
"url": "git+https://github.com/focus-trap/focus-trap.git" | ||
}, | ||
@@ -33,27 +48,40 @@ "keywords": [ | ||
"author": "David Clark", | ||
"contributors": [ | ||
{ | ||
"name": "David Clark", | ||
"url": "http://davidtheclark.com/" | ||
}, | ||
{ | ||
"name": "Stefan Cameron", | ||
"url": "https://stefancameron.com/" | ||
} | ||
], | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/davidtheclark/focus-trap/issues" | ||
"url": "https://github.com/focus-trap/focus-trap/issues" | ||
}, | ||
"homepage": "https://github.com/davidtheclark/focus-trap#readme", | ||
"homepage": "https://github.com/focus-trap/focus-trap#readme", | ||
"dependencies": { | ||
"tabbable": "^4.0.0", | ||
"xtend": "^4.0.1" | ||
"tabbable": "^5.0.0" | ||
}, | ||
"devDependencies": { | ||
"browserify": "^13.3.0", | ||
"budo": "^9.4.1", | ||
"del-cli": "^1.1.0", | ||
"eslint": "^3.13.1", | ||
"husky": "^0.14.3", | ||
"lint-staged": "^7.2.0", | ||
"make-dir-cli": "^1.0.0", | ||
"prettier": "^1.14.0", | ||
"uglify-js": "^3.3.22" | ||
}, | ||
"files": [ | ||
"dist", | ||
"index.js", | ||
"index.d.ts" | ||
] | ||
"@babel/cli": "^7.11.5", | ||
"@babel/core": "^7.11.5", | ||
"@babel/preset-env": "^7.11.5", | ||
"@changesets/cli": "^2.10.2", | ||
"@rollup/plugin-babel": "^5.2.0", | ||
"@rollup/plugin-commonjs": "^15.0.0", | ||
"@rollup/plugin-node-resolve": "^9.0.0", | ||
"babel-eslint": "^10.1.0", | ||
"babel-loader": "^8.1.0", | ||
"babelify": "^10.0.0", | ||
"budo": "^11.6.4", | ||
"eslint": "^7.8.1", | ||
"eslint-config-prettier": "^6.11.0", | ||
"prettier": "^2.1.1", | ||
"rollup": "^2.26.9", | ||
"rollup-plugin-sourcemaps": "^0.6.2", | ||
"rollup-plugin-terser": "^7.0.1", | ||
"typescript": "^4.0.2" | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
# focus-trap | ||
# 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) | ||
@@ -55,4 +55,11 @@ --- | ||
### focusTrap = createFocusTrap(element[, createOptions]); | ||
### createFocusTrap(element[, createOptions]) | ||
```javascript | ||
import { createFocusTrap } from 'focus-trap'; // ESM | ||
const { createFocusTrap } = require('focus-trap'); // CJS | ||
focusTrap = createFocusTrap(element[, createOptions]); | ||
``` | ||
Returns a new focus trap on `element`. | ||
@@ -74,3 +81,4 @@ | ||
- **setReturnFocus** {element|string|function}: By default, focus trap on deactivation will return to the element that was focused before activation. With this option you can specify another element to programmatically receive focus after deactivation. 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. | ||
- **allowOutsideClick** {function}: If set and returns `true`, a click outside the focus trap will not be prevented, even when `clickOutsideDeactivates` is `false`. | ||
- **allowOutsideClick** {boolean|function}: If set and is or returns `true`, a click outside the focus trap will not be prevented, even when `clickOutsideDeactivates` is `false`. | ||
- **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. | ||
@@ -136,22 +144,29 @@ ### focusTrap.activate([activateOptions]) | ||
Here's what happens in `demo-one.js`: | ||
Here's what happens in `default.js` (the "default behavior" demo): | ||
```js | ||
var createFocusTrap = require('../../'); | ||
var { createFocusTrap } = require('../../dist/focus-trap'); | ||
var containerOne = document.getElementById('demo-one'); | ||
var focusTrapOne = createFocusTrap('#demo-one', { | ||
var container = document.getElementById('default'); | ||
var focusTrap = createFocusTrap('#default', { | ||
onActivate: function () { | ||
container.className = 'trap is-active'; | ||
}, | ||
onDeactivate: function () { | ||
containerOne.className = 'trap'; | ||
container.className = 'trap'; | ||
}, | ||
}); | ||
document.getElementById('activate-one').addEventListener('click', function () { | ||
focusTrapOne.activate(); | ||
containerOne.className = 'trap is-active'; | ||
}); | ||
document | ||
.getElementById('activate-default') | ||
.addEventListener('click', function () { | ||
focusTrap.activate(); | ||
}); | ||
document.getElementById('deactivate-one').addEventListener('click', function () { | ||
focusTrapOne.deactivate(); | ||
}); | ||
document | ||
.getElementById('deactivate-default') | ||
.addEventListener('click', function () { | ||
focusTrap.deactivate(); | ||
}); | ||
``` | ||
@@ -158,0 +173,0 @@ |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
197349
1
18
1384
0
196
0
18
2
+ Addedtabbable@5.3.3(transitive)
- Removedxtend@^4.0.1
- Removedtabbable@4.0.0(transitive)
- Removedxtend@4.0.2(transitive)
Updatedtabbable@^5.0.0