diff --git a/src/js/modal/index.js b/src/js/modal/index.js index 0778cf28f..a0868eca2 100644 --- a/src/js/modal/index.js +++ b/src/js/modal/index.js @@ -2,35 +2,96 @@ import React from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' +import keycode from 'keycode' + +const scrollKeys = [ + keycode('left'), + keycode('right'), + keycode('up'), + keycode('down'), + keycode('space'), + keycode('pgup'), + keycode('pgdn'), + keycode('end'), + keycode('home') +].reduce((scrollKeys, key) => { + scrollKeys[key] = true + return scrollKeys +}, {}) + +export const preventDefault = (e) => { + e.preventDefault() + e.returnValue = false +} /** * Modal component */ -var Modal = ({header, body, footer, visible, toggle}) => ( -
toggle(false)} - onTouchEnd={() => toggle(false)} - > -
e.stopPropagation()} - onTouchEnd={(e) => e.stopPropagation()} - > -
-
- {header} -
-
- {body} +class Modal extends React.Component { + handleScrollAndEscKeys = (e) => { + if (scrollKeys[e.keyCode]) { + preventDefault(e) + return false + } else if (e.keyCode === keycode('esc')) { + this.props.toggle(false) + } + } + + disableScroll () { + window.addEventListener('DOMMouseScroll', preventDefault, false) + window.addEventListener('wheel', preventDefault, false) + window.addEventListener('touchmove', preventDefault, false) + window.addEventListener('keydown', this.handleScrollAndEscKeys, false) + } + + enableScroll () { + window.removeEventListener('DOMMouseScroll', preventDefault, false) + window.removeEventListener('wheel', preventDefault, false) + window.removeEventListener('touchmove', preventDefault, false) + window.removeEventListener('keydown', this.handleScrollAndEscKeys, false) + } + + componentWillReceiveProps (props) { + if (props.visible && !this.props.visible) { + document.activeElement.blur() + this.disableScroll() + } else if (!props.visible && this.props.visible) { + this.enableScroll() + } + } + + render () { + const {header, body, footer, visible, toggle} = this.props + return ( +
{ + e.preventDefault() + e.stopPropagation() + toggle(false) + }} + > +
e.stopPropagation()} + onTouchEnd={(e) => e.stopPropagation()} + > +
+
+ {header} +
+
+ {body} +
+
+
+ {footer} +
-
- {footer} -
-
-
-) + ) + } +} Modal.propTypes = { header: PropTypes.element, diff --git a/src/js/modal/test/test.js b/src/js/modal/test/test.js index 76f3d9a6c..c6a8bd3b6 100644 --- a/src/js/modal/test/test.js +++ b/src/js/modal/test/test.js @@ -2,7 +2,7 @@ import assert from 'assert' import React from 'react' -import Modal from '../' +import {default as Modal, preventDefault} from '../' import {mount} from 'enzyme' describe('Modal', () => { @@ -26,15 +26,6 @@ describe('Modal', () => { wrapper.find('.mdc-Modal-overlay').simulate('click') }) - it('should callback when touching dark background', (done) => { - const callback = (visible) => { - assert.equal(visible, false) - done() - } - const wrapper = mount() - wrapper.find('.mdc-Modal-overlay').simulate('touchend') - }) - it('should have a custom header', () => { const header =

my custom header

const wrapper = mount() @@ -52,4 +43,161 @@ describe('Modal', () => { const wrapper = mount() assert.equal(wrapper.find('.mdc-Modal-footer').text(), 'my custom footer') }) + + it('.disableScroll() should replace window event listeners with preventDefault', () => { + const wrapper = mount() + const calls = [] + const addEventListenerBak = window.addEventListener + window.addEventListener = (type, dispatch, flag) => { + calls.push({ + type, + dispatch, + flag + }) + } + wrapper.instance().disableScroll() + assert.deepEqual(calls[0], { + type: 'DOMMouseScroll', + dispatch: preventDefault, + flag: false + }) + assert.deepEqual(calls[1], { + type: 'wheel', + dispatch: preventDefault, + flag: false + }) + assert.deepEqual(calls[2], { + type: 'touchmove', + dispatch: preventDefault, + flag: false + }) + assert.deepEqual(calls[3], { + type: 'keydown', + dispatch: wrapper.instance().handleScrollAndEscKeys, + flag: false + }) + window.addEventListener = addEventListenerBak + }) + + it('.enableScroll() should restore event listeners', () => { + const wrapper = mount() + const addEventListenerBak = window.addEventListener + const removeEventListenerBak = window.removeEventListener + const calls = [] + window.addEventListener = () => {} + window.removeEventListener = (type, dispatch, flag) => { + calls.push({ + type, + dispatch, + flag + }) + } + wrapper.instance().disableScroll() + wrapper.instance().enableScroll() + assert.deepEqual(calls[0], { + type: 'DOMMouseScroll', + dispatch: preventDefault, + flag: false + }) + assert.deepEqual(calls[1], { + type: 'wheel', + dispatch: preventDefault, + flag: false + }) + assert.deepEqual(calls[2], { + type: 'touchmove', + dispatch: preventDefault, + flag: false + }) + assert.deepEqual(calls[3], { + type: 'keydown', + dispatch: wrapper.instance().handleScrollAndEscKeys, + flag: false + }) + window.addEventListener = addEventListenerBak + window.removeEventListener = removeEventListenerBak + }) + + it('helper preventDefault should preventDefault and set returnValue=false', () => { + let wasCalled + const event = { + preventDefault: () => { + wasCalled = true + } + } + preventDefault(event) + assert.equal(event.returnValue, false) + assert(wasCalled) + }) + + it('should disable scroll and blur activeElement when getting visible', () => { + const wrapper = mount() + let disableWasCalled + wrapper.instance().disableScroll = () => { + disableWasCalled = true + } + let blurWasCalled + document.activeElement.blur = () => { + blurWasCalled = true + } + wrapper.setProps({visible: true}) + assert(disableWasCalled) + assert(blurWasCalled) + }) + + it('should enable scroll when getting invisible', () => { + const wrapper = mount() + let enableWasCalled + wrapper.instance().enableScroll = () => { + enableWasCalled = true + } + wrapper.setProps({visible: false}) + assert(enableWasCalled) + }) + + it('click on modal should not propagate', () => { + const wrapper = mount() + let wasCalled + wrapper.find('.mdc-Modal').simulate('click', { + stopPropagation: () => { + wasCalled = true + } + }) + assert(wasCalled) + }) + + it('touchend on modal should not propagate', () => { + const wrapper = mount() + let wasCalled + wrapper.find('.mdc-Modal').simulate('touchend', { + stopPropagation: () => { + wasCalled = true + } + }) + assert(wasCalled) + }) + + it('handleScrollAndEscKeys should prevent default for scroll keys', () => { + const wrapper = mount() + let wasCalled + let result = wrapper.instance().handleScrollAndEscKeys({ + keyCode: 32, + preventDefault: () => { + wasCalled = true + } + }) + assert(wasCalled) + assert.equal(result, false) + }) + + it('handleScrollAndEscKeys should prevent default for scroll keys', () => { + let toggleArg + const wrapper = mount( { + toggleArg = arg + }} />) + wrapper.instance().handleScrollAndEscKeys({ + keyCode: 27 + }) + assert.equal(toggleArg, false) + }) })