diff --git a/CHANGELOG.md b/CHANGELOG.md index 9129b4a9..ef2488d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +v0.0.5 - Thu, 13 Nov 2014 18:55:47 GMT +-------------------------------------- + +- [b15aa82](../../commit/b15aa82) [added] Supporting custom className +- [b7a38de](../../commit/b7a38de) [fixed] Warning caused by trying to focus null element closes #11 + + v0.0.4 - Tue, 11 Nov 2014 16:08:14 GMT -------------------------------------- diff --git a/bower.json b/bower.json index d988dae0..be0efee2 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "react-modal", - "version": "0.0.4", + "version": "0.0.5", "homepage": "https://github.com/rackt/react-modal", "authors": [ "Ryan Florence", diff --git a/dist/react-modal.js b/dist/react-modal.js index 37decfad..00b42fcf 100644 --- a/dist/react-modal.js +++ b/dist/react-modal.js @@ -71,6 +71,7 @@ var React = (typeof window !== "undefined" ? window.React : typeof global !== "u var div = React.DOM.div; var focusManager = _dereq_('../helpers/focusManager'); var scopeTab = _dereq_('../helpers/scopeTab'); +var cx = _dereq_('react/lib/cx'); // so that our CSS is statically analyzable var CLASS_NAMES = { @@ -102,21 +103,36 @@ var ModalPortal = module.exports = React.createClass({ }, componentDidMount: function() { - this.handleProps(this.props); - this.maybeFocus(); + // Focus needs to be set when mounting and already open + if (this.props.isOpen) { + this.setFocusAfterRender(true); + this.open(); + } }, componentWillReceiveProps: function(newProps) { - this.handleProps(newProps); - }, + // Focus only needs to be set once when the modal is being opened + if (!this.props.isOpen && newProps.isOpen) { + this.setFocusAfterRender(true); + } - handleProps: function(props) { - if (props.isOpen === true) + if (newProps.isOpen === true) this.open(); - else if (props.isOpen === false) + else if (newProps.isOpen === false) this.close(); }, + componentDidUpdate: function () { + if (this.focusAfterRender) { + this.focusContent(); + this.setFocusAfterRender(false); + } + }, + + setFocusAfterRender: function (focus) { + this.focusAfterRender = focus; + }, + open: function() { focusManager.setupScopedFocus(this.getDOMNode()); focusManager.markForFocusLater(); @@ -134,17 +150,6 @@ var ModalPortal = module.exports = React.createClass({ this.closeWithoutTimeout(); }, - componentDidUpdate: function() { - this.maybeFocus(); - }, - - maybeFocus: function() { - if (this.props.isOpen && - !this.refs.content.getDOMNode().contains(document.activeElement)) { - this.focusContent(); - } - }, - focusContent: function() { this.refs.content.getDOMNode().focus(); }, @@ -212,7 +217,7 @@ var ModalPortal = module.exports = React.createClass({ }, div({ ref: "content", - className: this.buildClassName('content'), + className: cx(this.buildClassName('content'), this.props.className), tabIndex: "-1", onClick: stopPropagation, onKeyDown: this.handleKeyDown @@ -224,7 +229,7 @@ var ModalPortal = module.exports = React.createClass({ } }); -},{"../helpers/focusManager":4,"../helpers/scopeTab":6}],3:[function(_dereq_,module,exports){ +},{"../helpers/focusManager":4,"../helpers/scopeTab":6,"react/lib/cx":9}],3:[function(_dereq_,module,exports){ var _element = null; function setElement(element) { @@ -435,6 +440,52 @@ module.exports = findTabbableDescendants; module.exports = _dereq_('./components/Modal'); -},{"./components/Modal":1}]},{},[8]) +},{"./components/Modal":1}],9:[function(_dereq_,module,exports){ +/** + * Copyright 2013-2014 Facebook, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * @providesModule cx + */ + +/** + * This function is used to mark string literals representing CSS class names + * so that they can be transformed statically. This allows for modularization + * and minification of CSS class names. + * + * In static_upstream, this function is actually implemented, but it should + * eventually be replaced with something more descriptive, and the transform + * that is used in the main stack should be ported for use elsewhere. + * + * @param string|object className to modularize, or an object of key/values. + * In the object case, the values are conditions that + * determine if the className keys should be included. + * @param [string ...] Variable list of classNames in the string case. + * @return string Renderable space-separated CSS className. + */ +function cx(classNames) { + if (typeof classNames == 'object') { + return Object.keys(classNames).filter(function(className) { + return classNames[className]; + }).join(' '); + } else { + return Array.prototype.join.call(arguments, ' '); + } +} + +module.exports = cx; + +},{}]},{},[8]) (8) }); \ No newline at end of file diff --git a/dist/react-modal.min.js b/dist/react-modal.min.js index 2af2a5d9..2b85d5f3 100644 --- a/dist/react-modal.min.js +++ b/dist/react-modal.min.js @@ -1 +1 @@ -!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.ReactModal=e()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o0?this.closeWithTimeout():this.closeWithoutTimeout())},componentDidUpdate:function(){this.maybeFocus()},maybeFocus:function(){this.props.isOpen&&!this.refs.content.getDOMNode().contains(document.activeElement)&&this.focusContent()},focusContent:function(){this.refs.content.getDOMNode().focus()},closeWithTimeout:function(){this.setState({beforeClose:!0},function(){setTimeout(this.closeWithoutTimeout,this.props.closeTimeoutMS)}.bind(this))},closeWithoutTimeout:function(){this.setState({afterOpen:!1,beforeClose:!1},this.afterClose)},afterClose:function(){focusManager.returnFocus(),focusManager.teardownScopedFocus()},handleKeyDown:function(event){9==event.keyCode&&scopeTab(this.getDOMNode(),event),27==event.keyCode&&this.requestClose()},handleOverlayClick:function(){this.ownerHandlesClose()?this.requestClose():this.focusContent()},requestClose:function(){this.ownerHandlesClose()&&this.props.onRequestClose()},ownerHandlesClose:function(){return this.props.onRequestClose},shouldBeClosed:function(){return!this.props.isOpen&&!this.state.beforeClose},overlayStyles:{position:"fixed",left:0,right:0,top:0,bottom:0},buildClassName:function(which){var className=CLASS_NAMES[which].base;return this.state.afterOpen&&(className+=" "+CLASS_NAMES[which].afterOpen),this.state.beforeClose&&(className+=" "+CLASS_NAMES[which].beforeClose),className},render:function(){return this.shouldBeClosed()?div():div({className:this.buildClassName("overlay"),style:this.overlayStyles,onClick:this.handleOverlayClick},div({ref:"content",className:this.buildClassName("content"),tabIndex:"-1",onClick:stopPropagation,onKeyDown:this.handleKeyDown},this.props.children))}})}},{"../helpers/focusManager":4,"../helpers/scopeTab":6}],3:[function(_dereq_,module,exports){function setElement(element){_element=element}function hide(appElement){validateElement(appElement),(appElement||_element).setAttribute("aria-hidden","true")}function show(appElement){validateElement(appElement),(appElement||_element).removeAttribute("aria-hidden")}function toggle(shouldHide,appElement){shouldHide?hide(appElement):show(appElement)}function validateElement(appElement){if(!appElement&&!_element)throw new Error("react-modal: You must set an element with `Modal.setAppElement(el)` to make this accessible")}function resetForTesting(){_element=null}var _element=null;exports.toggle=toggle,exports.setElement=setElement,exports.show=show,exports.hide=hide,exports.resetForTesting=resetForTesting},{}],4:[function(_dereq_,module,exports){function handleBlur(){needToFocus=!0}function handleFocus(){needToFocus&&(needToFocus=!1,setTimeout(function(){if(!modalElement.contains(document.activeElement)){var el=findTabbable(modalElement)[0]||modalElement;el.focus()}},0))}var findTabbable=_dereq_("../helpers/tabbable"),modalElement=null,focusLaterElement=null,needToFocus=!1;exports.markForFocusLater=function(){focusLaterElement=document.activeElement},exports.returnFocus=function(){try{focusLaterElement.focus()}catch(e){console.warn("You tried to return focus to "+focusLaterElement+" but it is not in the DOM anymore")}focusLaterElement=null},exports.setupScopedFocus=function(element){modalElement=element,window.addEventListener("blur",handleBlur,!1),document.addEventListener("focus",handleFocus,!0)},exports.teardownScopedFocus=function(){modalElement=null,window.removeEventListener("blur",handleBlur),document.removeEventListener("focus",handleFocus)}},{"../helpers/tabbable":7}],5:[function(_dereq_,module){function injectStyle(css){var style=document.getElementById("rackt-style");if(!style){style=document.createElement("style"),style.setAttribute("id","rackt-style");var head=document.getElementsByTagName("head")[0];head.insertBefore(style,head.firstChild)}style.innerHTML=style.innerHTML+"\n"+css}module.exports=function(){injectStyle([".ReactModal__Overlay {"," background-color: rgba(255, 255, 255, 0.75);","}",".ReactModal__Content {"," position: absolute;"," top: 40px;"," left: 40px;"," right: 40px;"," bottom: 40px;"," border: 1px solid #ccc;"," background: #fff;"," overflow: auto;"," -webkit-overflow-scrolling: touch;"," border-radius: 4px;"," outline: none;"," padding: 20px;","}","@media (max-width: 768px) {"," .ReactModal__Content {"," top: 10px;"," left: 10px;"," right: 10px;"," bottom: 10px;"," padding: 10px;"," }","}"].join("\n"))}},{}],6:[function(_dereq_,module){var findTabbable=_dereq_("../helpers/tabbable");module.exports=function(node,event){var tabbable=findTabbable(node),finalTabbable=tabbable[event.shiftKey?0:tabbable.length-1],leavingFinalTabbable=finalTabbable===document.activeElement||node===document.activeElement;if(leavingFinalTabbable){event.preventDefault();var target=tabbable[event.shiftKey?tabbable.length-1:0];target.focus()}}},{"../helpers/tabbable":7}],7:[function(_dereq_,module){function focusable(element,isTabIndexNotNaN){var nodeName=element.nodeName.toLowerCase();return(/input|select|textarea|button|object/.test(nodeName)?!element.disabled:"a"===nodeName?element.href||isTabIndexNotNaN:isTabIndexNotNaN)&&visible(element)}function hidden(el){return el.offsetWidth<=0&&el.offsetHeight<=0||"none"===el.style.display}function visible(element){for(;element&&element!==document.body;){if(hidden(element))return!1;element=element.parentNode}return!0}function tabbable(element){var tabIndex=element.getAttribute("tabindex");null===tabIndex&&(tabIndex=void 0);var isTabIndexNaN=isNaN(tabIndex);return(isTabIndexNaN||tabIndex>=0)&&focusable(element,!isTabIndexNaN)}function findTabbableDescendants(element){return[].slice.call(element.querySelectorAll("*"),0).filter(function(el){return tabbable(el)})}module.exports=findTabbableDescendants},{}],8:[function(_dereq_,module){module.exports=_dereq_("./components/Modal")},{"./components/Modal":1}]},{},[8])(8)}); \ No newline at end of file +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.ReactModal=e()}}(function(){return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;o0?this.closeWithTimeout():this.closeWithoutTimeout())},focusContent:function(){this.refs.content.getDOMNode().focus()},closeWithTimeout:function(){this.setState({beforeClose:!0},function(){setTimeout(this.closeWithoutTimeout,this.props.closeTimeoutMS)}.bind(this))},closeWithoutTimeout:function(){this.setState({afterOpen:!1,beforeClose:!1},this.afterClose)},afterClose:function(){focusManager.returnFocus(),focusManager.teardownScopedFocus()},handleKeyDown:function(event){9==event.keyCode&&scopeTab(this.getDOMNode(),event),27==event.keyCode&&this.requestClose()},handleOverlayClick:function(){this.ownerHandlesClose()?this.requestClose():this.focusContent()},requestClose:function(){this.ownerHandlesClose()&&this.props.onRequestClose()},ownerHandlesClose:function(){return this.props.onRequestClose},shouldBeClosed:function(){return!this.props.isOpen&&!this.state.beforeClose},overlayStyles:{position:"fixed",left:0,right:0,top:0,bottom:0},buildClassName:function(which){var className=CLASS_NAMES[which].base;return this.state.afterOpen&&(className+=" "+CLASS_NAMES[which].afterOpen),this.state.beforeClose&&(className+=" "+CLASS_NAMES[which].beforeClose),className},render:function(){return this.shouldBeClosed()?div():div({className:this.buildClassName("overlay"),style:this.overlayStyles,onClick:this.handleOverlayClick},div({ref:"content",className:cx(this.buildClassName("content"),this.props.className),tabIndex:"-1",onClick:stopPropagation,onKeyDown:this.handleKeyDown},this.props.children))}})}},{"../helpers/focusManager":4,"../helpers/scopeTab":6,"react/lib/cx":9}],3:[function(_dereq_,module,exports){function setElement(element){_element=element}function hide(appElement){validateElement(appElement),(appElement||_element).setAttribute("aria-hidden","true")}function show(appElement){validateElement(appElement),(appElement||_element).removeAttribute("aria-hidden")}function toggle(shouldHide,appElement){shouldHide?hide(appElement):show(appElement)}function validateElement(appElement){if(!appElement&&!_element)throw new Error("react-modal: You must set an element with `Modal.setAppElement(el)` to make this accessible")}function resetForTesting(){_element=null}var _element=null;exports.toggle=toggle,exports.setElement=setElement,exports.show=show,exports.hide=hide,exports.resetForTesting=resetForTesting},{}],4:[function(_dereq_,module,exports){function handleBlur(){needToFocus=!0}function handleFocus(){needToFocus&&(needToFocus=!1,setTimeout(function(){if(!modalElement.contains(document.activeElement)){var el=findTabbable(modalElement)[0]||modalElement;el.focus()}},0))}var findTabbable=_dereq_("../helpers/tabbable"),modalElement=null,focusLaterElement=null,needToFocus=!1;exports.markForFocusLater=function(){focusLaterElement=document.activeElement},exports.returnFocus=function(){try{focusLaterElement.focus()}catch(e){console.warn("You tried to return focus to "+focusLaterElement+" but it is not in the DOM anymore")}focusLaterElement=null},exports.setupScopedFocus=function(element){modalElement=element,window.addEventListener("blur",handleBlur,!1),document.addEventListener("focus",handleFocus,!0)},exports.teardownScopedFocus=function(){modalElement=null,window.removeEventListener("blur",handleBlur),document.removeEventListener("focus",handleFocus)}},{"../helpers/tabbable":7}],5:[function(_dereq_,module){function injectStyle(css){var style=document.getElementById("rackt-style");if(!style){style=document.createElement("style"),style.setAttribute("id","rackt-style");var head=document.getElementsByTagName("head")[0];head.insertBefore(style,head.firstChild)}style.innerHTML=style.innerHTML+"\n"+css}module.exports=function(){injectStyle([".ReactModal__Overlay {"," background-color: rgba(255, 255, 255, 0.75);","}",".ReactModal__Content {"," position: absolute;"," top: 40px;"," left: 40px;"," right: 40px;"," bottom: 40px;"," border: 1px solid #ccc;"," background: #fff;"," overflow: auto;"," -webkit-overflow-scrolling: touch;"," border-radius: 4px;"," outline: none;"," padding: 20px;","}","@media (max-width: 768px) {"," .ReactModal__Content {"," top: 10px;"," left: 10px;"," right: 10px;"," bottom: 10px;"," padding: 10px;"," }","}"].join("\n"))}},{}],6:[function(_dereq_,module){var findTabbable=_dereq_("../helpers/tabbable");module.exports=function(node,event){var tabbable=findTabbable(node),finalTabbable=tabbable[event.shiftKey?0:tabbable.length-1],leavingFinalTabbable=finalTabbable===document.activeElement||node===document.activeElement;if(leavingFinalTabbable){event.preventDefault();var target=tabbable[event.shiftKey?tabbable.length-1:0];target.focus()}}},{"../helpers/tabbable":7}],7:[function(_dereq_,module){function focusable(element,isTabIndexNotNaN){var nodeName=element.nodeName.toLowerCase();return(/input|select|textarea|button|object/.test(nodeName)?!element.disabled:"a"===nodeName?element.href||isTabIndexNotNaN:isTabIndexNotNaN)&&visible(element)}function hidden(el){return el.offsetWidth<=0&&el.offsetHeight<=0||"none"===el.style.display}function visible(element){for(;element&&element!==document.body;){if(hidden(element))return!1;element=element.parentNode}return!0}function tabbable(element){var tabIndex=element.getAttribute("tabindex");null===tabIndex&&(tabIndex=void 0);var isTabIndexNaN=isNaN(tabIndex);return(isTabIndexNaN||tabIndex>=0)&&focusable(element,!isTabIndexNaN)}function findTabbableDescendants(element){return[].slice.call(element.querySelectorAll("*"),0).filter(function(el){return tabbable(el)})}module.exports=findTabbableDescendants},{}],8:[function(_dereq_,module){module.exports=_dereq_("./components/Modal")},{"./components/Modal":1}],9:[function(_dereq_,module){function cx(classNames){return"object"==typeof classNames?Object.keys(classNames).filter(function(className){return classNames[className]}).join(" "):Array.prototype.join.call(arguments," ")}module.exports=cx},{}]},{},[8])(8)}); \ No newline at end of file diff --git a/package.json b/package.json index 93081cfd..a23a6ac9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-modal", - "version": "0.0.4", + "version": "0.0.5", "description": "Accessible modal dialog component for React.JS", "main": "./lib/index", "repository": {