react-focus-lock
Advanced tools
Comparing version
@@ -147,68 +147,127 @@ import React, {Component} from 'react'; | ||
if (1) | ||
it('Should be enabled only on last node', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action1">action1</button> | ||
text | ||
</div> | ||
<FocusLock> | ||
describe('order', () => { | ||
if (1) | ||
it('Should be enabled only on last node', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action2">3-action2</button> | ||
<button className="action1">action1</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
<FocusLock> | ||
<FocusLock> | ||
<div> | ||
text | ||
<button className="action2">3-action2</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
<FocusLock> | ||
<div> | ||
text | ||
<button className="action3">action3</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('action3'); | ||
done(); | ||
}, 1); | ||
}); | ||
/**/ | ||
if (1) | ||
it('Should handle disabled state', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action3">action3</button> | ||
<button className="action1">action1</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('action3'); | ||
done(); | ||
}, 1); | ||
}); | ||
/**/ | ||
<FocusLock> | ||
<div> | ||
text | ||
<button className="action2">4-action2</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
<FocusLock disabled> | ||
<div> | ||
text | ||
<button className="action3">action3</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('4-action2'); | ||
done(); | ||
}, 1); | ||
}); | ||
/**/ | ||
if (1) | ||
it('Should handle disabled state', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action1">action1</button> | ||
text | ||
</div> | ||
<FocusLock> | ||
if (1) | ||
it('Should not pick hidden input', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action2">4-action2</button> | ||
<button className="action1">action1</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
<FocusLock disabled> | ||
<FocusLock> | ||
<input type="hidden" className="action2"/> | ||
<button style={{visibility: 'hidden'}}>hidden</button> | ||
<div style={{display: 'none'}}> | ||
<button className="action2">5-action3</button> | ||
</div> | ||
<button className="action2">5-action4</button> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('5-action4'); | ||
done(); | ||
}, 1); | ||
}); | ||
/**/ | ||
}); | ||
describe('move', () => { | ||
if (1) | ||
it('Should return focus on escape', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action3">action3</button> | ||
<button className="action1">action1</button> | ||
<button className="action1-1">action1-skip</button> | ||
<button className="action1-1">action1-skip-</button> | ||
text | ||
</div> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('4-action2'); | ||
done(); | ||
}, 1); | ||
}); | ||
/**/ | ||
<FocusLock> | ||
<button className="action2">button-action</button> | ||
<button>6-action3</button> | ||
<button>6-action4</button> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
expect(document.activeElement.innerHTML).to.be.equal('button-action'); | ||
setTimeout(() => { | ||
wrapper.find('.action1').simulate('focus'); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
wrapper.find('.action2').simulate('blur'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('button-action'); | ||
done(); | ||
}, 10); | ||
}, 1); | ||
}); | ||
if (1) | ||
it('Should not pick hidden input', (done) => { | ||
it('Should roll focus on escape', (done) => { | ||
const wrapper = mount(<div> | ||
@@ -221,30 +280,2 @@ <div> | ||
<FocusLock> | ||
<input type="hidden" className="action2" /> | ||
<button style={{visibility:'hidden'}}>hidden</button> | ||
<div style={{display: 'none'}}> | ||
<button className="action2">5-action3</button> | ||
</div> | ||
<button className="action2">5-action4</button> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('5-action4'); | ||
done(); | ||
}, 1); | ||
}); | ||
/**/ | ||
if (1) | ||
it('Should return focus on escape', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action1">action1</button> | ||
<button className="action1-1">action1-skip</button> | ||
<button className="action1-1">action1-skip-</button> | ||
text | ||
</div> | ||
<FocusLock> | ||
<button className="action2">button-action</button> | ||
@@ -256,3 +287,3 @@ <button>6-action3</button> | ||
expect(document.activeElement.innerHTML).to.be.equal('button-action'); | ||
setTimeout(()=> { | ||
setTimeout(() => { | ||
wrapper.find('.action1').simulate('focus'); | ||
@@ -263,35 +294,10 @@ wrapper.find('.action1').getDOMNode().focus(); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('button-action'); | ||
expect(document.activeElement.innerHTML).to.be.equal('6-action4'); | ||
done(); | ||
}, 10); | ||
},1); | ||
}, 1); | ||
}); | ||
/**/ | ||
it('Should roll focus on escape', (done) => { | ||
const wrapper = mount(<div> | ||
<div> | ||
text | ||
<button className="action1">action1</button> | ||
text | ||
</div> | ||
<FocusLock> | ||
<button className="action2">button-action</button> | ||
<button>6-action3</button> | ||
<button>6-action4</button> | ||
</FocusLock> | ||
</div>, mountPoint); | ||
expect(document.activeElement.innerHTML).to.be.equal('button-action'); | ||
setTimeout(()=> { | ||
wrapper.find('.action1').simulate('focus'); | ||
wrapper.find('.action1').getDOMNode().focus(); | ||
expect(document.activeElement.innerHTML).to.be.equal('action1'); | ||
wrapper.find('.action2').simulate('blur'); | ||
setTimeout(() => { | ||
expect(document.activeElement.innerHTML).to.be.equal('6-action4'); | ||
done(); | ||
}, 10); | ||
},1); | ||
}); | ||
/**/ | ||
}); | ||
@@ -298,0 +304,0 @@ |
@@ -40,8 +40,3 @@ 'use strict'; | ||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = FocusLock.__proto__ || Object.getPrototypeOf(FocusLock)).call.apply(_ref, [this].concat(args))), _this), _this.state = { | ||
escapeAttempts: 0, | ||
observed: undefined | ||
}, _this.onTrapBlur = function () { | ||
return setImmediate(_this.update); | ||
}, _this.onTrapFocus = function () { | ||
return _this.update(); | ||
}, _this.onActivation = function () { | ||
@@ -81,7 +76,4 @@ _this.originalFocusedElement = _this.originalFocusedElement || document.activeElement; | ||
children = _props.children, | ||
disabled = _props.disabled, | ||
sandboxed = _props.sandboxed; | ||
var _state = this.state, | ||
observed = _state.observed, | ||
escapeAttempts = _state.escapeAttempts; | ||
disabled = _props.disabled; | ||
var observed = this.state.observed; | ||
@@ -98,7 +90,3 @@ return _react2.default.createElement( | ||
observed: observed, | ||
escapeAttempts: escapeAttempts, | ||
disabled: disabled, | ||
sandboxed: sandboxed, | ||
onBlur: this.onTrapBlur, | ||
onFocus: this.onTrapFocus, | ||
onActivation: this.onActivation | ||
@@ -118,4 +106,3 @@ }, | ||
disabled: _react.PropTypes.bool, | ||
returnFocus: _react.PropTypes.bool, | ||
sandboxed: _react.PropTypes.bool | ||
returnFocus: _react.PropTypes.bool | ||
}; | ||
@@ -122,0 +109,0 @@ |
@@ -25,22 +25,6 @@ 'use strict'; | ||
var FocusTrap = function FocusTrap(_ref) { | ||
var children = _ref.children, | ||
onBlur = _ref.onBlur, | ||
onFocus = _ref.onFocus; | ||
return _react2.default.createElement( | ||
'div', | ||
{ onBlur: onBlur, onFocus: onFocus }, | ||
children | ||
); | ||
}; | ||
FocusTrap.propTypes = { | ||
onBlur: _propTypes2.default.func.isRequired, | ||
onFocus: _propTypes2.default.func.isRequired, | ||
children: _propTypes2.default.node.isRequired | ||
}; | ||
var lastActiveTrap = 0; | ||
var lastActiveFocus = null; | ||
var activateTrap = function activateTrap() { | ||
var result = false; | ||
if (lastActiveTrap) { | ||
@@ -53,8 +37,43 @@ var _lastActiveTrap = lastActiveTrap, | ||
onActivation(); | ||
(0, _focusLock2.default)(observed, lastActiveFocus); | ||
result = (0, _focusLock2.default)(observed, lastActiveFocus); | ||
} | ||
lastActiveFocus = document.activeElement; | ||
} | ||
return result; | ||
}; | ||
var onTrap = function onTrap(event) { | ||
if (activateTrap() && event) { | ||
// prevent scroll jump | ||
event.preventDefault(); | ||
} | ||
}; | ||
var onBlur = function onBlur() { | ||
setImmediate(activateTrap); | ||
}; | ||
var FocusTrap = function FocusTrap(_ref) { | ||
var children = _ref.children; | ||
return _react2.default.createElement( | ||
'div', | ||
{ onBlur: onBlur }, | ||
children | ||
); | ||
}; | ||
FocusTrap.propTypes = { | ||
children: _propTypes2.default.node.isRequired | ||
}; | ||
var attachHandler = function attachHandler() { | ||
document.addEventListener('focusin', onTrap, true); | ||
document.addEventListener('focusout', onBlur); | ||
}; | ||
var detachHandler = function detachHandler() { | ||
document.removeEventListener('focusin', onTrap, true); | ||
document.removeEventListener('focusout', onBlur); | ||
}; | ||
function reducePropsToState(propsList) { | ||
@@ -68,9 +87,12 @@ return propsList.filter(function (_ref2) { | ||
function handleStateChangeOnClient(trap) { | ||
if (trap && !lastActiveTrap) { | ||
attachHandler(); | ||
} | ||
lastActiveTrap = trap; | ||
if (trap) { | ||
_focusLock.tabHook.attach(trap.observed, trap.sandboxed); | ||
activateTrap(); | ||
setImmediate(activateTrap); | ||
} else { | ||
_focusLock.tabHook.detach(); | ||
detachHandler(); | ||
lastActiveFocus = null; | ||
@@ -77,0 +99,0 @@ } |
{ | ||
"name": "react-focus-lock", | ||
"version": "1.3.6", | ||
"version": "1.4.6", | ||
"description": "It is a trap! (for a focus)", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -50,5 +50,3 @@ # react-focus-lock | ||
- `returnFocus`, to return focus into initial position on unmount(not disable). | ||
I strongly recommend you NOT to use this feature. But sometimes it might be usable. | ||
- `sandboxed`, to override default tab behavior. Very usable if you have elements with non-zero tabindex inside. | ||
As long it will alter default(and preferred) behavior - __NEVER__ use it unless you have to. | ||
This is expected behavior for Modals, but it is better to implement it by your self. | ||
@@ -55,0 +53,0 @@ |
@@ -6,3 +6,2 @@ import React, { PropTypes, Component } from 'react'; | ||
state = { | ||
escapeAttempts: 0, | ||
observed: undefined, | ||
@@ -27,7 +26,2 @@ }; | ||
onTrapBlur = () => | ||
setImmediate(this.update); | ||
onTrapFocus = () => this.update(); | ||
onActivation = () => { | ||
@@ -50,4 +44,4 @@ this.originalFocusedElement = this.originalFocusedElement || document.activeElement; | ||
render() { | ||
const { children, disabled, sandboxed } = this.props; | ||
const { observed, escapeAttempts } = this.state; | ||
const { children, disabled } = this.props; | ||
const { observed } = this.state; | ||
return ( | ||
@@ -60,7 +54,3 @@ <div | ||
observed={observed} | ||
escapeAttempts={escapeAttempts} | ||
disabled={disabled} | ||
sandboxed={sandboxed} | ||
onBlur={this.onTrapBlur} | ||
onFocus={this.onTrapFocus} | ||
onActivation={this.onActivation} | ||
@@ -79,3 +69,2 @@ > | ||
returnFocus: PropTypes.bool, | ||
sandboxed: PropTypes.bool, | ||
}; | ||
@@ -82,0 +71,0 @@ |
import React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import withSideEffect from 'react-side-effect'; | ||
import moveFocusInside, { focusInside, tabHook } from 'focus-lock'; | ||
import moveFocusInside, { focusInside } from 'focus-lock'; | ||
const FocusTrap = ({ children, onBlur, onFocus }) => ( | ||
<div onBlur={onBlur} onFocus={onFocus}> | ||
{children} | ||
</div> | ||
); | ||
FocusTrap.propTypes = { | ||
onBlur: PropTypes.func.isRequired, | ||
onFocus: PropTypes.func.isRequired, | ||
children: PropTypes.node.isRequired, | ||
}; | ||
let lastActiveTrap = 0; | ||
let lastActiveFocus = null; | ||
const activateTrap = () => { | ||
let result = false; | ||
if (lastActiveTrap) { | ||
@@ -25,8 +14,41 @@ const { observed, onActivation } = lastActiveTrap; | ||
onActivation(); | ||
moveFocusInside(observed, lastActiveFocus); | ||
result = moveFocusInside(observed, lastActiveFocus); | ||
} | ||
lastActiveFocus = document.activeElement; | ||
} | ||
return result; | ||
}; | ||
const onTrap = (event) => { | ||
if (activateTrap() && event) { | ||
// prevent scroll jump | ||
event.preventDefault(); | ||
} | ||
}; | ||
const onBlur = () => { | ||
setImmediate(activateTrap); | ||
}; | ||
const FocusTrap = ({ children }) => ( | ||
<div onBlur={onBlur}> | ||
{children} | ||
</div> | ||
); | ||
FocusTrap.propTypes = { | ||
children: PropTypes.node.isRequired, | ||
}; | ||
const attachHandler = () => { | ||
document.addEventListener('focusin', onTrap, true); | ||
document.addEventListener('focusout', onBlur); | ||
}; | ||
const detachHandler = () => { | ||
document.removeEventListener('focusin', onTrap, true); | ||
document.removeEventListener('focusout', onBlur); | ||
}; | ||
function reducePropsToState(propsList) { | ||
@@ -39,9 +61,12 @@ return propsList | ||
function handleStateChangeOnClient(trap) { | ||
if (trap && !lastActiveTrap) { | ||
attachHandler(); | ||
} | ||
lastActiveTrap = trap; | ||
if (trap) { | ||
tabHook.attach(trap.observed, trap.sandboxed); | ||
activateTrap(); | ||
setImmediate(activateTrap); | ||
} else { | ||
tabHook.detach(); | ||
detachHandler(); | ||
lastActiveFocus = null; | ||
@@ -48,0 +73,0 @@ } |
160585
0.08%892
2.06%65
-2.99%