react-focus-lock
Advanced tools
Comparing version 1.17.7 to 1.18.0
@@ -26,2 +26,8 @@ "use strict"; | ||
}); | ||
Object.defineProperty(exports, "InFocusGuard", { | ||
enumerable: true, | ||
get: function get() { | ||
return _FocusGuard.default; | ||
} | ||
}); | ||
exports.default = void 0; | ||
@@ -37,3 +43,5 @@ | ||
var _FocusGuard = _interopRequireDefault(require("./FocusGuard")); | ||
var _default = _Lock.default; | ||
exports.default = _default; |
@@ -38,2 +38,4 @@ "use strict"; | ||
var _FocusGuard = require("./FocusGuard"); | ||
var RenderChildren = function RenderChildren(_ref) { | ||
@@ -48,11 +50,3 @@ var children = _ref.children; | ||
var Fragment = _react.default.Fragment ? _react.default.Fragment : RenderChildren; | ||
var hidden = { | ||
width: '1px', | ||
height: '0px', | ||
padding: 0, | ||
overflow: 'hidden', | ||
position: 'fixed', | ||
top: '1px', | ||
left: '1px' | ||
}; | ||
var emptyArray = []; | ||
@@ -139,2 +133,4 @@ var FocusLock = | ||
whiteList = _this$props.whiteList, | ||
_this$props$shards = _this$props.shards, | ||
shards = _this$props$shards === void 0 ? emptyArray : _this$props$shards, | ||
_this$props$as = _this$props.as, | ||
@@ -158,3 +154,3 @@ Container = _this$props$as === void 0 ? 'div' : _this$props$as, | ||
tabIndex: disabled ? -1 : 0, | ||
style: hidden | ||
style: _FocusGuard.hiddenGuard | ||
}), // nearest focus guard | ||
@@ -165,3 +161,3 @@ _react.default.createElement("div", { | ||
tabIndex: disabled ? -1 : 1, | ||
style: hidden | ||
style: _FocusGuard.hiddenGuard | ||
})], _react.default.createElement(Container, (0, _extends2.default)({ | ||
@@ -179,2 +175,3 @@ ref: this.setObserveNode | ||
whiteList: whiteList, | ||
shards: shards, | ||
onActivation: this.onActivation, | ||
@@ -185,3 +182,3 @@ onDeactivation: this.onDeactivation | ||
tabIndex: disabled ? -1 : 0, | ||
style: hidden | ||
style: _FocusGuard.hiddenGuard | ||
})); | ||
@@ -204,2 +201,5 @@ } | ||
whiteList: _propTypes.default.func, | ||
shards: _propTypes.default.arrayOf(_propTypes.default.shape({ | ||
current: _propTypes.default.instanceOf(Element) | ||
})), | ||
as: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.func, _propTypes.default.object]), | ||
@@ -220,2 +220,3 @@ lockProps: _propTypes.default.object, | ||
whiteList: undefined, | ||
shards: undefined, | ||
as: 'div', | ||
@@ -222,0 +223,0 @@ lockProps: {}, |
@@ -12,2 +12,4 @@ "use strict"; | ||
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); | ||
var _react = _interopRequireDefault(require("react")); | ||
@@ -54,2 +56,23 @@ | ||
function autoGuard(startIndex, end, step, allNodes) { | ||
var lastGuard = null; | ||
var i = startIndex; | ||
do { | ||
var node = allNodes[i]; | ||
if (node.guard) { | ||
lastGuard = node; | ||
} else if (node.lockItem) { | ||
lastGuard = null; | ||
} else { | ||
break; | ||
} | ||
} while ((i += step) !== end); | ||
if (lastGuard) { | ||
lastGuard.node.tabIndex = 0; | ||
} | ||
} | ||
var activateTrap = function activateTrap() { | ||
@@ -62,19 +85,51 @@ var result = false; | ||
persistentFocus = _lastActiveTrap.persistentFocus, | ||
autoFocus = _lastActiveTrap.autoFocus; | ||
autoFocus = _lastActiveTrap.autoFocus, | ||
shards = _lastActiveTrap.shards; | ||
var workingNode = observed || lastPortaledElement && lastPortaledElement.portaledElement; | ||
var activeElement = document && document.activeElement; | ||
if (!activeElement || focusWhitelisted(activeElement)) { | ||
if (persistentFocus || !isFreeFocus() || !lastActiveFocus && autoFocus) { | ||
if (workingNode && !((0, _focusLock.focusInside)(workingNode) || focusIsPortaledPair(activeElement, workingNode))) { | ||
if (document && !lastActiveFocus && activeElement && !autoFocus) { | ||
activeElement.blur(); | ||
document.body.focus(); | ||
} else { | ||
result = (0, _focusLock.default)(workingNode, lastActiveFocus); | ||
lastPortaledElement = {}; | ||
if (workingNode) { | ||
var workingArea = [workingNode].concat((0, _toConsumableArray2.default)(shards.map(function (_ref) { | ||
var current = _ref.current; | ||
return current; | ||
}))); | ||
if (!activeElement || focusWhitelisted(activeElement)) { | ||
if (persistentFocus || !isFreeFocus() || !lastActiveFocus && autoFocus) { | ||
if (workingNode && !((0, _focusLock.focusInside)(workingArea) || focusIsPortaledPair(activeElement, workingNode))) { | ||
if (document && !lastActiveFocus && activeElement && !autoFocus) { | ||
activeElement.blur(); | ||
document.body.focus(); | ||
} else { | ||
result = (0, _focusLock.default)(workingArea, lastActiveFocus); | ||
lastPortaledElement = {}; | ||
} | ||
} | ||
lastActiveFocus = document && document.activeElement; | ||
} | ||
} | ||
lastActiveFocus = document && document.activeElement; | ||
if (document) { | ||
var newActiveElement = document && document.activeElement; | ||
var allNodes = (0, _focusLock.getFocusabledIn)(workingArea); | ||
var focusedItem = allNodes.find(function (_ref2) { | ||
var node = _ref2.node; | ||
return node === newActiveElement; | ||
}); | ||
if (focusedItem) { | ||
// remove old focus | ||
allNodes.filter(function (_ref3) { | ||
var guard = _ref3.guard, | ||
node = _ref3.node; | ||
return guard && node.dataset.focusAutoGuard; | ||
}).forEach(function (_ref4) { | ||
var node = _ref4.node; | ||
return node.removeAttribute('tabIndex'); | ||
}); | ||
var focusedIndex = allNodes.indexOf(focusedItem); | ||
autoGuard(focusedIndex, allNodes.length, +1, allNodes); | ||
autoGuard(focusedIndex, -1, -1, allNodes); | ||
} | ||
} | ||
@@ -117,4 +172,4 @@ } | ||
var FocusTrap = function FocusTrap(_ref) { | ||
var children = _ref.children; | ||
var FocusTrap = function FocusTrap(_ref5) { | ||
var children = _ref5.children; | ||
return _react.default.createElement("div", { | ||
@@ -141,4 +196,4 @@ onBlur: onBlur, | ||
function reducePropsToState(propsList) { | ||
return propsList.filter(function (_ref2) { | ||
var disabled = _ref2.disabled; | ||
return propsList.filter(function (_ref6) { | ||
var disabled = _ref6.disabled; | ||
return !disabled; | ||
@@ -145,0 +200,0 @@ }).slice(-1)[0]; |
@@ -5,3 +5,4 @@ import FocusLock from './Lock'; | ||
import FreeFocusInside from './FreeFocusInside'; | ||
export { AutoFocusInside, MoveFocusInside, FreeFocusInside }; | ||
import InFocusGuard from './FocusGuard'; | ||
export { AutoFocusInside, MoveFocusInside, FreeFocusInside, InFocusGuard }; | ||
export default FocusLock; |
@@ -9,2 +9,3 @@ import _extends from "@babel/runtime/helpers/extends"; | ||
import FocusTrap, { onBlur, onFocus } from './Trap'; | ||
import { hiddenGuard } from './FocusGuard'; | ||
@@ -20,11 +21,3 @@ var RenderChildren = function RenderChildren(_ref) { | ||
var Fragment = React.Fragment ? React.Fragment : RenderChildren; | ||
var hidden = { | ||
width: '1px', | ||
height: '0px', | ||
padding: 0, | ||
overflow: 'hidden', | ||
position: 'fixed', | ||
top: '1px', | ||
left: '1px' | ||
}; | ||
var emptyArray = []; | ||
@@ -117,2 +110,4 @@ var FocusLock = | ||
whiteList = _this$props.whiteList, | ||
_this$props$shards = _this$props.shards, | ||
shards = _this$props$shards === void 0 ? emptyArray : _this$props$shards, | ||
_this$props$as = _this$props.as, | ||
@@ -137,3 +132,3 @@ Container = _this$props$as === void 0 ? 'div' : _this$props$as, | ||
tabIndex: disabled ? -1 : 0, | ||
style: hidden | ||
style: hiddenGuard | ||
}), // nearest focus guard | ||
@@ -144,3 +139,3 @@ React.createElement("div", { | ||
tabIndex: disabled ? -1 : 1, | ||
style: hidden | ||
style: hiddenGuard | ||
})], React.createElement(Container, _extends({ | ||
@@ -158,2 +153,3 @@ ref: this.setObserveNode | ||
whiteList: whiteList, | ||
shards: shards, | ||
onActivation: this.onActivation, | ||
@@ -164,3 +160,3 @@ onDeactivation: this.onDeactivation | ||
tabIndex: disabled ? -1 : 0, | ||
style: hidden | ||
style: hiddenGuard | ||
})); | ||
@@ -183,2 +179,5 @@ }; | ||
whiteList: PropTypes.func, | ||
shards: PropTypes.arrayOf(PropTypes.shape({ | ||
current: PropTypes.instanceOf(Element) | ||
})), | ||
as: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object]), | ||
@@ -199,2 +198,3 @@ lockProps: PropTypes.object, | ||
whiteList: undefined, | ||
shards: undefined, | ||
as: 'div', | ||
@@ -201,0 +201,0 @@ lockProps: {}, |
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import withSideEffect from 'react-clientside-effect'; | ||
import moveFocusInside, { focusInside, focusIsHidden } from 'focus-lock'; | ||
import moveFocusInside, { focusInside, focusIsHidden, getFocusabledIn } from 'focus-lock'; | ||
import { deferAction } from './util'; | ||
@@ -38,2 +38,23 @@ | ||
function autoGuard(startIndex, end, step, allNodes) { | ||
var lastGuard = null; | ||
var i = startIndex; | ||
do { | ||
var node = allNodes[i]; | ||
if (node.guard) { | ||
lastGuard = node; | ||
} else if (node.lockItem) { | ||
lastGuard = null; | ||
} else { | ||
break; | ||
} | ||
} while ((i += step) !== end); | ||
if (lastGuard) { | ||
lastGuard.node.tabIndex = 0; | ||
} | ||
} | ||
var activateTrap = function activateTrap() { | ||
@@ -46,19 +67,51 @@ var result = false; | ||
persistentFocus = _lastActiveTrap.persistentFocus, | ||
autoFocus = _lastActiveTrap.autoFocus; | ||
autoFocus = _lastActiveTrap.autoFocus, | ||
shards = _lastActiveTrap.shards; | ||
var workingNode = observed || lastPortaledElement && lastPortaledElement.portaledElement; | ||
var activeElement = document && document.activeElement; | ||
if (!activeElement || focusWhitelisted(activeElement)) { | ||
if (persistentFocus || !isFreeFocus() || !lastActiveFocus && autoFocus) { | ||
if (workingNode && !(focusInside(workingNode) || focusIsPortaledPair(activeElement, workingNode))) { | ||
if (document && !lastActiveFocus && activeElement && !autoFocus) { | ||
activeElement.blur(); | ||
document.body.focus(); | ||
} else { | ||
result = moveFocusInside(workingNode, lastActiveFocus); | ||
lastPortaledElement = {}; | ||
if (workingNode) { | ||
var workingArea = [workingNode].concat(shards.map(function (_ref) { | ||
var current = _ref.current; | ||
return current; | ||
})); | ||
if (!activeElement || focusWhitelisted(activeElement)) { | ||
if (persistentFocus || !isFreeFocus() || !lastActiveFocus && autoFocus) { | ||
if (workingNode && !(focusInside(workingArea) || focusIsPortaledPair(activeElement, workingNode))) { | ||
if (document && !lastActiveFocus && activeElement && !autoFocus) { | ||
activeElement.blur(); | ||
document.body.focus(); | ||
} else { | ||
result = moveFocusInside(workingArea, lastActiveFocus); | ||
lastPortaledElement = {}; | ||
} | ||
} | ||
lastActiveFocus = document && document.activeElement; | ||
} | ||
} | ||
lastActiveFocus = document && document.activeElement; | ||
if (document) { | ||
var newActiveElement = document && document.activeElement; | ||
var allNodes = getFocusabledIn(workingArea); | ||
var focusedItem = allNodes.find(function (_ref2) { | ||
var node = _ref2.node; | ||
return node === newActiveElement; | ||
}); | ||
if (focusedItem) { | ||
// remove old focus | ||
allNodes.filter(function (_ref3) { | ||
var guard = _ref3.guard, | ||
node = _ref3.node; | ||
return guard && node.dataset.focusAutoGuard; | ||
}).forEach(function (_ref4) { | ||
var node = _ref4.node; | ||
return node.removeAttribute('tabIndex'); | ||
}); | ||
var focusedIndex = allNodes.indexOf(focusedItem); | ||
autoGuard(focusedIndex, allNodes.length, +1, allNodes); | ||
autoGuard(focusedIndex, -1, -1, allNodes); | ||
} | ||
} | ||
@@ -96,4 +149,4 @@ } | ||
var FocusTrap = function FocusTrap(_ref) { | ||
var children = _ref.children; | ||
var FocusTrap = function FocusTrap(_ref5) { | ||
var children = _ref5.children; | ||
return React.createElement("div", { | ||
@@ -120,4 +173,4 @@ onBlur: onBlur, | ||
function reducePropsToState(propsList) { | ||
return propsList.filter(function (_ref2) { | ||
var disabled = _ref2.disabled; | ||
return propsList.filter(function (_ref6) { | ||
var disabled = _ref6.disabled; | ||
return !disabled; | ||
@@ -124,0 +177,0 @@ }).slice(-1)[0]; |
{ | ||
"name": "react-focus-lock", | ||
"version": "1.17.7", | ||
"version": "1.18.0", | ||
"description": "It is a trap! (for a focus)", | ||
@@ -79,3 +79,3 @@ "main": "dist/cjs/index.js", | ||
"sinon": "3.2.1", | ||
"size-limit": "^0.21.0" | ||
"size-limit": "^0.21.1" | ||
}, | ||
@@ -85,3 +85,3 @@ "homepage": "https://github.com/theKashey/react-focus-lock#readme", | ||
"@babel/runtime": "^7.0.0", | ||
"focus-lock": "^0.5.2", | ||
"focus-lock": "^0.6.0", | ||
"prop-types": "^15.6.2", | ||
@@ -88,0 +88,0 @@ "react-clientside-effect": "^1.2.0" |
@@ -70,2 +70,7 @@ declare module 'react-focus-lock' { | ||
/** | ||
* Shards forms a scattered lock, same as `group` does, but in more "low" and controlled way | ||
*/ | ||
shards?: Array<React.RefObject<any>>; | ||
children: React.ReactNode; | ||
@@ -83,2 +88,6 @@ } | ||
interface InFocusGuardProps { | ||
children: React.ReactNode; | ||
} | ||
/** | ||
@@ -107,2 +116,8 @@ * Traps Focus inside a Lock | ||
} | ||
/** | ||
* Secures the focus around the node | ||
*/ | ||
export class InFocusGuard extends React.Component<InFocusGuardProps> { | ||
} | ||
} |
@@ -69,2 +69,3 @@ <div align="left"> | ||
- `group` named focus group for focus scattering aka [combined lock targets](https://github.com/theKashey/vue-focus-lock/issues/2) | ||
- `shards` an array of `ref` pointing to the nodes, which focus lock should consider and a part of it. This is another way focus scattering. | ||
- `whiteList` you could _whitelist_ locations FocusLock should carry about. Everything outside it will ignore. For example - any modals. | ||
@@ -128,2 +129,66 @@ - `as` if you need to change internal `div` element, to any other. Use ref forwarding to give FocusLock the node to work with. | ||
# Portals | ||
Use focus scattering to handle portals | ||
- using `groups`. Just create a few locks (only one could be active) with a same group name | ||
```js | ||
const PortaledElement = () => ( | ||
<FocusLock group="group42" disabled={true}> | ||
// "discoverable" portaled content | ||
</FocusLock> | ||
); | ||
<FocusLock group="group42"> | ||
// main content | ||
</FocusLock> | ||
``` | ||
- using `shards`. Just pass all the pieces to the "shards" prop. | ||
```js | ||
const PortaledElement = () => ( | ||
<div ref={ref}> | ||
// "discoverable" portaled content | ||
</div> | ||
); | ||
<FocusLock shards={[ref]}> | ||
// main content | ||
</FocusLock> | ||
``` | ||
- without anything. FocusLock will not prevent focusing portaled element, but will not include them in to tab order | ||
```js | ||
const PortaledElement = () => ( | ||
<div> | ||
// NON-"discoverable" portaled content | ||
</div> | ||
); | ||
<FocusLock shards={[ref]}> | ||
// main content | ||
<PortaledElement /> | ||
</FocusLock> | ||
``` | ||
### Guarding | ||
As you may know - FocusLock is adding `Focus Guards` before and after lock to remove some side effects, like page scrolling. | ||
But `shards` will not have such guards, and it might be not so cool to use them - for example if no `tabbable` would be | ||
defined after shard - you will tab to the browser chrome. | ||
You may wrap shard with `InFocusGuard` or just drop `InFocusGuard` here and there - that would solve the problem. | ||
```js | ||
import {InFocusGuard} from 'react-focus-lock'; | ||
<InFocusGuard> | ||
<button> | ||
</InFocusGuard> | ||
// | ||
<InFocusGuard /> | ||
<button> | ||
<InFocusGuard /> | ||
``` | ||
InFocusGuards would be active(tabbable) only when tabble, it protecting, is focused. | ||
### Automatic potral discovery | ||
# Unmounting and focus management | ||
@@ -130,0 +195,0 @@ - In case FocusLock has `returnFocus` enabled, and it's gonna to be unmounted - focus will be returned after zero-timeout. |
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
623515
23
1177
260
16
+ Addedfocus-lock@0.6.8(transitive)
- Removedfocus-lock@0.5.4(transitive)
Updatedfocus-lock@^0.6.0