Skip to content

Commit

Permalink
[added] enforceFocus prop to Modal
Browse files Browse the repository at this point in the history
Allows you to configure whether the modal should enforce focus when
open. Ideally this doesn't need to be a public API, but in the case of
static models (like in the docs) you need to turn it off, because you
can't assume its the only one open on the page.
  • Loading branch information
jquense committed Jun 13, 2015
1 parent 3869ca2 commit 66f0f92
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 6 deletions.
1 change: 1 addition & 0 deletions docs/examples/ModalStatic.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const modalInstance = (
<div className='static-modal'>
<Modal title='Modal title'
enforceFocus={false}
backdrop={false}
animation={false}
container={mountNode}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"test": "npm run lint && npm run build && karma start --single-run && _mocha --compilers js:babel-core/register test/server/*Spec.js",
"lint": "eslint ./",
"docs-build": "babel-node tools/build-cli.js --docs-only",
"docs": "docs/dev-run",
"docs": "babel-node docs/dev-run",
"docs-prod": "npm run docs-build && NODE_ENV=production babel-node docs/server.js",
"docs-prod-unoptimized": "npm run docs-build -- --dev && NODE_ENV=production babel-node docs/server.js"
},
Expand Down
59 changes: 54 additions & 5 deletions src/Modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ function getContainer(context){
domUtils.ownerDocument(context).body;
}

/**
* Firefox doesn't have a focusin event so using capture is easiest way to get bubbling
* IE8 can't do addEventListener, but does have onfocus in, so we use that in ie8
* @param {ReactElement|HTMLElement} context
* @param {Function} handler
*/
function onFocus(context, handler) {
let doc = domUtils.ownerDocument(context);
let useFocusin = !doc.addEventListener
, remove;

if (useFocusin) {
document.attachEvent('onfocusin', handler);
remove = () => document.detachEvent('onfocusin', handler);
} else {
document.addEventListener('focus', handler, true);
remove = () => document.removeEventListener('focus', handler, true);
}
return { remove };
}

let scrollbarSize;

if ( domUtils.canUseDom) {
let scrollDiv = document.createElement('div');
Expand Down Expand Up @@ -60,7 +82,8 @@ const Modal = React.createClass({
closeButton: React.PropTypes.bool,
animation: React.PropTypes.bool,
onRequestHide: React.PropTypes.func.isRequired,
dialogClassName: React.PropTypes.string
dialogClassName: React.PropTypes.string,
enforceFocus: React.PropTypes.bool
},

getDefaultProps() {
Expand All @@ -69,10 +92,15 @@ const Modal = React.createClass({
backdrop: true,
keyboard: true,
animation: true,
closeButton: true
closeButton: true,
enforceFocus: true
};
},

getInitialState(){
return { };
},

render() {
let state = this.state;
let modalStyle = { ...state.dialogStyles, display: 'block'};
Expand Down Expand Up @@ -107,7 +135,7 @@ const Modal = React.createClass({
);

return this.props.backdrop ?
this.renderBackdrop(modal) : modal;
this.renderBackdrop(modal, state.backdropStyles) : modal;
},

renderBackdrop(modal) {
Expand All @@ -132,8 +160,8 @@ const Modal = React.createClass({
let closeButton;
if (this.props.closeButton) {
closeButton = (
<button type="button" className="close" aria-hidden="true" onClick={this.props.onRequestHide}>&times;</button>
);
<button type="button" className="close" aria-hidden="true" onClick={this.props.onRequestHide}>&times;</button>
);
}

return (
Expand Down Expand Up @@ -169,6 +197,10 @@ const Modal = React.createClass({
this._onWindowResizeListener =
EventListener.listen(win, 'resize', this.handleWindowResize);

if (this.props.enforceFocus) {
this._onFocusinListener = onFocus(this, this.enforceFocus);
}

let container = getContainer(this);

container.className += container.className.length ? ' modal-open' : 'modal-open';
Expand Down Expand Up @@ -199,6 +231,10 @@ const Modal = React.createClass({
this._onDocumentKeyupListener.remove();
this._onWindowResizeListener.remove();

if (this._onFocusinListener) {
this._onFocusinListener.remove();
}

let container = getContainer(this);

container.className = container.className.replace(/ ?modal-open/, '');
Expand Down Expand Up @@ -237,6 +273,19 @@ const Modal = React.createClass({
}
},

enforceFocus() {
if ( !this.isMounted() ) {
return;
}

let active = domUtils.activeElement(this)
, modal = React.findDOMNode(this.refs.modal);

if (modal !== active && !domUtils.contains(modal, active)){
modal.focus();
}
},

_getStyles() {
if ( !domUtils.canUseDom ) { return {}; }

Expand Down
15 changes: 15 additions & 0 deletions src/utils/domUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,20 @@ function ownerWindow(componentOrElement) {
: doc.parentWindow;
}

/**
* get the active element, safe in IE
* @return {HTMLElement}
*/
function getActiveElement(componentOrElement){
let doc = ownerDocument(componentOrElement);

try {
return doc.activeElement || doc.body;
} catch (e) {
return doc.body;
}
}

/**
* Shortcut to compute element style
*
Expand Down Expand Up @@ -160,5 +174,6 @@ export default {
getComputedStyles,
getOffset,
getPosition,
activeElement: getActiveElement,
offsetParent: offsetParentFunc
};

0 comments on commit 66f0f92

Please sign in to comment.