diff --git a/CHANGELOG.md b/CHANGELOG.md index 452e41d7..a0573970 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +v3.7.1 - Mon, 10 Dec 2018 13:12:33 UTC +-------------------------------------- + +- [2ae092a](../../commit/2ae092a) [fixed] Allow empty classNames for body (#720) +- [8d8f476](../../commit/8d8f476) React-Modal: chromeHeadless use +- [fc53400](../../commit/fc53400) [fixed] Allow ReactDOM.createPortal to be mocked in tests +- [6a6bcf7](../../commit/6a6bcf7) [fixed] Render `testId` property +- [1b561fc](../../commit/1b561fc) [fixed] if tabbable element is undefined, focus head or tail based on shiftKey +- [86632aa](../../commit/86632aa) [fixed] check if element exists before focusing in scopeTab helper + + v3.6.1 - Tue, 25 Sep 2018 11:47:45 UTC -------------------------------------- diff --git a/bower.json b/bower.json index d56d77fd..60e9ab78 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "react-modal", - "version": "3.6.1", + "version": "3.7.1", "homepage": "https://github.com/reactjs/react-modal", "authors": [ "Ryan Florence", diff --git a/dist/react-modal.js b/dist/react-modal.js index c61a04ee..b20e0e0c 100644 --- a/dist/react-modal.js +++ b/dist/react-modal.js @@ -542,7 +542,10 @@ var portalClassName = exports.portalClassName = "ReactModalPortal"; var bodyOpenClassName = exports.bodyOpenClassName = "ReactModal__Body--open"; var isReact16 = _reactDom2.default.createPortal !== undefined; -var createPortal = isReact16 ? _reactDom2.default.createPortal : _reactDom2.default.unstable_renderSubtreeIntoContainer; + +var getCreatePortal = function getCreatePortal() { + return isReact16 ? _reactDom2.default.createPortal : _reactDom2.default.unstable_renderSubtreeIntoContainer; +}; function getParentElement(parentSelector) { return parentSelector(); @@ -569,6 +572,7 @@ var Modal = function (_Component) { }, _this.portalRef = function (ref) { _this.portal = ref; }, _this.renderPortal = function (props) { + var createPortal = getCreatePortal(); var portal = createPortal(_this, _react2.default.createElement(_ModalPortal2.default, _extends({ defaultStyles: Modal.defaultStyles }, props)), _this.node); _this.portalRef(portal); }, _temp), _possibleConstructorReturn(_this, _ret); @@ -652,6 +656,7 @@ var Modal = function (_Component) { this.node = document.createElement("div"); } + var createPortal = getCreatePortal(); return createPortal(_react2.default.createElement(_ModalPortal2.default, _extends({ ref: this.portalRef, defaultStyles: Modal.defaultStyles @@ -1629,7 +1634,7 @@ var ModalPortal = function (_Component) { // Remove classes. - classList.remove(document.body, bodyOpenClassName); + bodyOpenClassName && classList.remove(document.body, bodyOpenClassName); htmlOpenClassName && classList.remove(document.getElementsByTagName("html")[0], htmlOpenClassName); @@ -1843,7 +1848,7 @@ var ModalPortal = function (_Component) { // Add classes. - classList.add(document.body, bodyOpenClassName); + bodyOpenClassName && classList.add(document.body, bodyOpenClassName); htmlOpenClassName && classList.add(document.getElementsByTagName("html")[0], htmlOpenClassName); @@ -1888,7 +1893,9 @@ var ModalPortal = function (_Component) { onClick: this.handleContentOnClick, role: this.props.role, "aria-label": this.props.contentLabel - }, this.attributesFromObject("aria", this.props.aria || {}), this.attributesFromObject("data", this.props.data || {})), + }, this.attributesFromObject("aria", this.props.aria || {}), this.attributesFromObject("data", this.props.data || {}), { + "data-testid": this.props.testId + }), this.props.children ) ); @@ -2117,6 +2124,15 @@ function scopeTab(node, event) { x += shiftKey ? -1 : 1; } + // If the tabbable element does not exist, + // focus head/tail based on shiftKey + if (typeof tabbable[x] === "undefined") { + event.preventDefault(); + target = shiftKey ? tail : head; + target.focus(); + return; + } + event.preventDefault(); tabbable[x].focus(); diff --git a/dist/react-modal.min.js b/dist/react-modal.min.js index c92fb84d..8b6cf466 100644 --- a/dist/react-modal.min.js +++ b/dist/react-modal.min.js @@ -9,12 +9,12 @@ * * http://api.jqueryui.com/category/ui-core/ */ -var u=/input|select|textarea|button|object/;e.exports=t.default},function(e,t,n){"use strict";function o(e,t){if(!e||!e.length)throw new Error("react-modal: No elements were found for selector "+t+".")}function r(e){var t=e;if("string"==typeof t&&p.canUseDOM){var n=document.querySelectorAll(t);o(n,t),t="length"in n?n[0]:n}return d=t||d}function a(e){return!(!e&&!d)||((0,f.default)(!1,["react-modal: App element is not defined.","Please use `Modal.setAppElement(el)` or set `appElement={el}`.","This is needed so screen readers don't see main content","when modal is opened. It is not recommended, but you can opt-out","by setting `ariaHideApp={false}`."].join(" ")),!1)}function s(e){a(e)&&(e||d).setAttribute("aria-hidden","true")}function i(e){a(e)&&(e||d).removeAttribute("aria-hidden")}function u(){d=null}function l(){d=null}Object.defineProperty(t,"__esModule",{value:!0}),t.assertNodeList=o,t.setElement=r,t.validateElement=a,t.hide=s,t.show=i,t.documentNotReadyOrSSRTesting=u,t.resetForTesting=l;var c=n(19),f=function(e){return e&&e.__esModule?e:{default:e}}(c),p=n(3),d=null},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=n(10),r=function(e){return e&&e.__esModule?e:{default:e}}(o);t.default=r.default,e.exports=t.default},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function i(e){return e()}Object.defineProperty(t,"__esModule",{value:!0}),t.bodyOpenClassName=t.portalClassName=void 0;var u=Object.assign||function(e){for(var t=1;t0&&0===(R-=1)&&g.show(t),n.props.shouldFocusAfterRender&&(n.props.shouldReturnFocusAfterClose?(m.returnFocus(),m.teardownScopedFocus()):m.popWithoutFocus())},n.open=function(){n.beforeOpen(),n.state.afterOpen&&n.state.beforeClose?(clearTimeout(n.closeTimer),n.setState({beforeClose:!1})):(n.props.shouldFocusAfterRender&&(m.setupScopedFocus(n.node),m.markForFocusLater()),n.setState({isOpen:!0},function(){n.setState({afterOpen:!0}),n.props.isOpen&&n.props.onAfterOpen&&n.props.onAfterOpen()}))},n.close=function(){n.props.closeTimeoutMS>0?n.closeWithTimeout():n.closeWithoutTimeout()},n.focusContent=function(){return n.content&&!n.contentHasFocus()&&n.content.focus()},n.closeWithTimeout=function(){var e=Date.now()+n.props.closeTimeoutMS;n.setState({beforeClose:!0,closesAt:e},function(){n.closeTimer=setTimeout(n.closeWithoutTimeout,n.state.closesAt-Date.now())})},n.closeWithoutTimeout=function(){n.setState({beforeClose:!1,isOpen:!1,afterOpen:!1,closesAt:null},n.afterClose)},n.handleKeyDown=function(e){e.keyCode===E&&(0,b.default)(n.content,e),n.props.shouldCloseOnEsc&&e.keyCode===P&&(e.stopPropagation(),n.requestClose(e))},n.handleOverlayOnClick=function(e){null===n.shouldClose&&(n.shouldClose=!0),n.shouldClose&&n.props.shouldCloseOnOverlayClick&&(n.ownerHandlesClose()?n.requestClose(e):n.focusContent()),n.shouldClose=null},n.handleContentOnMouseUp=function(){n.shouldClose=!1},n.handleOverlayOnMouseDown=function(e){n.props.shouldCloseOnOverlayClick||e.target!=n.overlay||e.preventDefault()},n.handleContentOnClick=function(){n.shouldClose=!1},n.handleContentOnMouseDown=function(){n.shouldClose=!1},n.requestClose=function(e){return n.ownerHandlesClose()&&n.props.onRequestClose(e)},n.ownerHandlesClose=function(){return n.props.onRequestClose},n.shouldBeClosed=function(){return!n.state.isOpen&&!n.state.beforeClose},n.contentHasFocus=function(){return document.activeElement===n.content||n.content.contains(document.activeElement)},n.buildClassName=function(e,t){var o="object"===(void 0===t?"undefined":l(t))?t:{base:j[e],afterOpen:j[e]+"--after-open",beforeClose:j[e]+"--before-close"},r=o.base;return n.state.afterOpen&&(r=r+" "+o.afterOpen),n.state.beforeClose&&(r=r+" "+o.beforeClose),"string"==typeof t&&t?r+" "+t:r},n.attributesFromObject=function(e,t){return Object.keys(t).reduce(function(n,o){return n[e+"-"+o]=t[o],n},{})},n.state={afterOpen:!1,beforeClose:!1},n.shouldClose=null,n.moveFromContentToOverlay=null,n}return i(t,e),c(t,[{key:"componentDidMount",value:function(){this.props.isOpen&&this.open()}},{key:"componentDidUpdate",value:function(e,t){e.bodyOpenClassName!==this.props.bodyOpenClassName&&console.warn('React-Modal: "bodyOpenClassName" prop has been modified. This may cause unexpected behavior when multiple modals are open.'),e.htmlOpenClassName!==this.props.htmlOpenClassName&&console.warn('React-Modal: "htmlOpenClassName" prop has been modified. This may cause unexpected behavior when multiple modals are open.'),this.props.isOpen&&!e.isOpen?this.open():!this.props.isOpen&&e.isOpen&&this.close(),this.props.shouldFocusAfterRender&&this.state.isOpen&&!t.isOpen&&this.focusContent()}},{key:"componentWillUnmount",value:function(){this.afterClose(),clearTimeout(this.closeTimer)}},{key:"beforeOpen",value:function(){var e=this.props,t=e.appElement,n=e.ariaHideApp,o=e.htmlOpenClassName,r=e.bodyOpenClassName;C.add(document.body,r),o&&C.add(document.getElementsByTagName("html")[0],o),n&&(R+=1,g.hide(t))}},{key:"render",value:function(){var e=this.props,t=e.className,n=e.overlayClassName,o=e.defaultStyles,r=t?{}:o.content,a=n?{}:o.overlay;return this.shouldBeClosed()?null:p.default.createElement("div",{ref:this.setOverlayRef,className:this.buildClassName("overlay",n),style:u({},a,this.props.style.overlay),onClick:this.handleOverlayOnClick,onMouseDown:this.handleOverlayOnMouseDown},p.default.createElement("div",u({ref:this.setContentRef,style:u({},r,this.props.style.content),className:this.buildClassName("content",t),tabIndex:"-1",onKeyDown:this.handleKeyDown,onMouseDown:this.handleContentOnMouseDown,onMouseUp:this.handleContentOnMouseUp,onClick:this.handleContentOnClick,role:this.props.role,"aria-label":this.props.contentLabel},this.attributesFromObject("aria",this.props.aria||{}),this.attributesFromObject("data",this.props.data||{})),this.props.children))}}]),t}(f.Component);T.defaultProps={style:{overlay:{},content:{}},defaultStyles:{}},T.propTypes={isOpen:y.default.bool.isRequired,defaultStyles:y.default.shape({content:y.default.object,overlay:y.default.object}),style:y.default.shape({content:y.default.object,overlay:y.default.object}),className:y.default.oneOfType([y.default.string,y.default.object]),overlayClassName:y.default.oneOfType([y.default.string,y.default.object]),bodyOpenClassName:y.default.string,htmlOpenClassName:y.default.string,ariaHideApp:y.default.bool,appElement:y.default.instanceOf(S.default),onAfterOpen:y.default.func,onRequestClose:y.default.func,closeTimeoutMS:y.default.number,shouldFocusAfterRender:y.default.bool,shouldCloseOnOverlayClick:y.default.bool,shouldReturnFocusAfterClose:y.default.bool,role:y.default.string,contentLabel:y.default.string,aria:y.default.object,data:y.default.object,children:y.default.node,shouldCloseOnEsc:y.default.bool,overlayRef:y.default.func,contentRef:y.default.func,testId:y.default.string},t.default=T,e.exports=t.default},function(e,t,n){"use strict";function o(){y=!0}function r(){if(y){if(y=!1,!d)return;setTimeout(function(){if(!d.contains(document.activeElement)){((0,f.default)(d)[0]||d).focus()}},0)}}function a(){p.push(document.activeElement)}function s(){var e=null;try{return void(0!==p.length&&(e=p.pop(),e.focus()))}catch(t){console.warn(["You tried to return focus to",e,"but it is not in the DOM anymore"].join(" "))}}function i(){p.length>0&&p.pop()}function u(e){d=e,window.addEventListener?(window.addEventListener("blur",o,!1),document.addEventListener("focus",r,!0)):(window.attachEvent("onBlur",o),document.attachEvent("onFocus",r))}function l(){d=null,window.addEventListener?(window.removeEventListener("blur",o),document.removeEventListener("focus",r)):(window.detachEvent("onBlur",o),document.detachEvent("onFocus",r))}Object.defineProperty(t,"__esModule",{value:!0}),t.handleBlur=o,t.handleFocus=r,t.markForFocusLater=a,t.returnFocus=s,t.popWithoutFocus=i,t.setupScopedFocus=u,t.teardownScopedFocus=l;var c=n(7),f=function(e){return e&&e.__esModule?e:{default:e}}(c),p=[],d=null,y=!1},function(e,t,n){"use strict";function o(e,t){var n=(0,a.default)(e);if(!n.length)return void t.preventDefault();var o=t.shiftKey,r=n[0],s=n[n.length-1];if(e===document.activeElement){if(!o)return;i=s}var i;if(s!==document.activeElement||o||(i=r),r===document.activeElement&&o&&(i=s),i)return t.preventDefault(),void i.focus();var u=/(\bChrome\b|\bSafari\b)\//.exec(navigator.userAgent);if(null!=u&&"Chrome"!=u[1]&&null==/\biPod\b|\biPad\b/g.exec(navigator.userAgent)){var l=n.indexOf(document.activeElement);l>-1&&(l+=o?-1:1),t.preventDefault(),n[l].focus()}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var r=n(7),a=function(e){return e&&e.__esModule?e:{default:e}}(r);e.exports=t.default},function(e,t,n){"use strict";var o=function(){};o=function(e,t,n){var o=arguments.length;n=new Array(o>2?o-2:0);for(var r=2;r0&&0===(R-=1)&&g.show(t),n.props.shouldFocusAfterRender&&(n.props.shouldReturnFocusAfterClose?(m.returnFocus(),m.teardownScopedFocus()):m.popWithoutFocus())},n.open=function(){n.beforeOpen(),n.state.afterOpen&&n.state.beforeClose?(clearTimeout(n.closeTimer),n.setState({beforeClose:!1})):(n.props.shouldFocusAfterRender&&(m.setupScopedFocus(n.node),m.markForFocusLater()),n.setState({isOpen:!0},function(){n.setState({afterOpen:!0}),n.props.isOpen&&n.props.onAfterOpen&&n.props.onAfterOpen()}))},n.close=function(){n.props.closeTimeoutMS>0?n.closeWithTimeout():n.closeWithoutTimeout()},n.focusContent=function(){return n.content&&!n.contentHasFocus()&&n.content.focus()},n.closeWithTimeout=function(){var e=Date.now()+n.props.closeTimeoutMS;n.setState({beforeClose:!0,closesAt:e},function(){n.closeTimer=setTimeout(n.closeWithoutTimeout,n.state.closesAt-Date.now())})},n.closeWithoutTimeout=function(){n.setState({beforeClose:!1,isOpen:!1,afterOpen:!1,closesAt:null},n.afterClose)},n.handleKeyDown=function(e){e.keyCode===E&&(0,b.default)(n.content,e),n.props.shouldCloseOnEsc&&e.keyCode===P&&(e.stopPropagation(),n.requestClose(e))},n.handleOverlayOnClick=function(e){null===n.shouldClose&&(n.shouldClose=!0),n.shouldClose&&n.props.shouldCloseOnOverlayClick&&(n.ownerHandlesClose()?n.requestClose(e):n.focusContent()),n.shouldClose=null},n.handleContentOnMouseUp=function(){n.shouldClose=!1},n.handleOverlayOnMouseDown=function(e){n.props.shouldCloseOnOverlayClick||e.target!=n.overlay||e.preventDefault()},n.handleContentOnClick=function(){n.shouldClose=!1},n.handleContentOnMouseDown=function(){n.shouldClose=!1},n.requestClose=function(e){return n.ownerHandlesClose()&&n.props.onRequestClose(e)},n.ownerHandlesClose=function(){return n.props.onRequestClose},n.shouldBeClosed=function(){return!n.state.isOpen&&!n.state.beforeClose},n.contentHasFocus=function(){return document.activeElement===n.content||n.content.contains(document.activeElement)},n.buildClassName=function(e,t){var o="object"===(void 0===t?"undefined":l(t))?t:{base:j[e],afterOpen:j[e]+"--after-open",beforeClose:j[e]+"--before-close"},r=o.base;return n.state.afterOpen&&(r=r+" "+o.afterOpen),n.state.beforeClose&&(r=r+" "+o.beforeClose),"string"==typeof t&&t?r+" "+t:r},n.attributesFromObject=function(e,t){return Object.keys(t).reduce(function(n,o){return n[e+"-"+o]=t[o],n},{})},n.state={afterOpen:!1,beforeClose:!1},n.shouldClose=null,n.moveFromContentToOverlay=null,n}return i(t,e),c(t,[{key:"componentDidMount",value:function(){this.props.isOpen&&this.open()}},{key:"componentDidUpdate",value:function(e,t){e.bodyOpenClassName!==this.props.bodyOpenClassName&&console.warn('React-Modal: "bodyOpenClassName" prop has been modified. This may cause unexpected behavior when multiple modals are open.'),e.htmlOpenClassName!==this.props.htmlOpenClassName&&console.warn('React-Modal: "htmlOpenClassName" prop has been modified. This may cause unexpected behavior when multiple modals are open.'),this.props.isOpen&&!e.isOpen?this.open():!this.props.isOpen&&e.isOpen&&this.close(),this.props.shouldFocusAfterRender&&this.state.isOpen&&!t.isOpen&&this.focusContent()}},{key:"componentWillUnmount",value:function(){this.afterClose(),clearTimeout(this.closeTimer)}},{key:"beforeOpen",value:function(){var e=this.props,t=e.appElement,n=e.ariaHideApp,o=e.htmlOpenClassName,r=e.bodyOpenClassName;r&&C.add(document.body,r),o&&C.add(document.getElementsByTagName("html")[0],o),n&&(R+=1,g.hide(t))}},{key:"render",value:function(){var e=this.props,t=e.className,n=e.overlayClassName,o=e.defaultStyles,r=t?{}:o.content,a=n?{}:o.overlay;return this.shouldBeClosed()?null:p.default.createElement("div",{ref:this.setOverlayRef,className:this.buildClassName("overlay",n),style:u({},a,this.props.style.overlay),onClick:this.handleOverlayOnClick,onMouseDown:this.handleOverlayOnMouseDown},p.default.createElement("div",u({ref:this.setContentRef,style:u({},r,this.props.style.content),className:this.buildClassName("content",t),tabIndex:"-1",onKeyDown:this.handleKeyDown,onMouseDown:this.handleContentOnMouseDown,onMouseUp:this.handleContentOnMouseUp,onClick:this.handleContentOnClick,role:this.props.role,"aria-label":this.props.contentLabel},this.attributesFromObject("aria",this.props.aria||{}),this.attributesFromObject("data",this.props.data||{}),{"data-testid":this.props.testId}),this.props.children))}}]),t}(f.Component);T.defaultProps={style:{overlay:{},content:{}},defaultStyles:{}},T.propTypes={isOpen:y.default.bool.isRequired,defaultStyles:y.default.shape({content:y.default.object,overlay:y.default.object}),style:y.default.shape({content:y.default.object,overlay:y.default.object}),className:y.default.oneOfType([y.default.string,y.default.object]),overlayClassName:y.default.oneOfType([y.default.string,y.default.object]),bodyOpenClassName:y.default.string,htmlOpenClassName:y.default.string,ariaHideApp:y.default.bool,appElement:y.default.instanceOf(S.default),onAfterOpen:y.default.func,onRequestClose:y.default.func,closeTimeoutMS:y.default.number,shouldFocusAfterRender:y.default.bool,shouldCloseOnOverlayClick:y.default.bool,shouldReturnFocusAfterClose:y.default.bool,role:y.default.string,contentLabel:y.default.string,aria:y.default.object,data:y.default.object,children:y.default.node,shouldCloseOnEsc:y.default.bool,overlayRef:y.default.func,contentRef:y.default.func,testId:y.default.string},t.default=T,e.exports=t.default},function(e,t,n){"use strict";function o(){y=!0}function r(){if(y){if(y=!1,!d)return;setTimeout(function(){if(!d.contains(document.activeElement)){((0,f.default)(d)[0]||d).focus()}},0)}}function a(){p.push(document.activeElement)}function s(){var e=null;try{return void(0!==p.length&&(e=p.pop(),e.focus()))}catch(t){console.warn(["You tried to return focus to",e,"but it is not in the DOM anymore"].join(" "))}}function i(){p.length>0&&p.pop()}function u(e){d=e,window.addEventListener?(window.addEventListener("blur",o,!1),document.addEventListener("focus",r,!0)):(window.attachEvent("onBlur",o),document.attachEvent("onFocus",r))}function l(){d=null,window.addEventListener?(window.removeEventListener("blur",o),document.removeEventListener("focus",r)):(window.detachEvent("onBlur",o),document.detachEvent("onFocus",r))}Object.defineProperty(t,"__esModule",{value:!0}),t.handleBlur=o,t.handleFocus=r,t.markForFocusLater=a,t.returnFocus=s,t.popWithoutFocus=i,t.setupScopedFocus=u,t.teardownScopedFocus=l;var c=n(7),f=function(e){return e&&e.__esModule?e:{default:e}}(c),p=[],d=null,y=!1},function(e,t,n){"use strict";function o(e,t){var n=(0,a.default)(e);if(!n.length)return void t.preventDefault();var o=t.shiftKey,r=n[0],s=n[n.length-1];if(e===document.activeElement){if(!o)return;i=s}var i;if(s!==document.activeElement||o||(i=r),r===document.activeElement&&o&&(i=s),i)return t.preventDefault(),void i.focus();var u=/(\bChrome\b|\bSafari\b)\//.exec(navigator.userAgent);if(null!=u&&"Chrome"!=u[1]&&null==/\biPod\b|\biPad\b/g.exec(navigator.userAgent)){var l=n.indexOf(document.activeElement);if(l>-1&&(l+=o?-1:1),void 0===n[l])return t.preventDefault(),i=o?s:r,void i.focus();t.preventDefault(),n[l].focus()}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=o;var r=n(7),a=function(e){return e&&e.__esModule?e:{default:e}}(r);e.exports=t.default},function(e,t,n){"use strict";var o=function(){};o=function(e,t,n){var o=arguments.length;n=new Array(o>2?o-2:0);for(var r=2;r