From 6b967a0a770d2222d3506197c5df6ca9f985bc8f Mon Sep 17 00:00:00 2001 From: Pascal LEPAISANT Date: Wed, 6 Apr 2016 14:50:22 +0200 Subject: [PATCH] merge after rebase --- src/application/popin/index.js | 162 +++++++++++++++++++++------------ 1 file changed, 103 insertions(+), 59 deletions(-) diff --git a/src/application/popin/index.js b/src/application/popin/index.js index 74ad52409..09e4c0350 100644 --- a/src/application/popin/index.js +++ b/src/application/popin/index.js @@ -7,10 +7,11 @@ import builder from 'focus-core/component/builder'; import types from 'focus-core/component/types'; import {ArgumentInvalidException} from 'focus-core/exception'; const includes = require('lodash').includes; +const {max} = require('lodash/math'); /** -* Small overlay component used to listen to scroll and prevent it to leave the Popin component -*/ + * Small overlay component used to listen to scroll and prevent it to leave the Popin component + */ const Overlay = React.createClass({ displayName: 'PopinOverlay', propTypes: { @@ -22,35 +23,36 @@ const Overlay = React.createClass({ return {show: false}; }, /** - * Store the body overgflow property, and set it to hidden - * @private - */ + * Store the body overgflow property, and set it to hidden + * @private + */ _hideBodyOverflow() { document.body.style['overflow-y'] = 'hidden'; }, /** - * Restore body overflow property - * @private - */ + * Restore body overflow property + * @private + */ _restoreBodyOverflow() { document.body.style['overflow-y'] = 'auto'; }, /** - * Component will unmount event handler. - * Remove the mouse wheel listener. - */ + * Component will unmount event handler. + * Remove the mouse wheel listener. + */ componentWillUnmount() { // ReactDOM.findDOMNode(this.refs.overlay).removeEventListener('mousewheel', this._onScroll); this._restoreBodyOverflow(); }, /** - * Render the component - * @return {XML} the rendered HTML - */ + * Render the component + * @return {XML} the rendered HTML + */ render() { const {children, clickHandler, show} = this.props; return ( -
+
{children}
); @@ -58,25 +60,25 @@ const Overlay = React.createClass({ }); /** -* The popin component configuration -* @type {Object} -*/ + * The popin component configuration + * @type {Object} + */ const popin = { /** - * Init the component. - * The popin is closed by default. - * @return {Object} the initial state - */ + * Init the component. + * The popin is closed by default. + * @return {Object} the initial state + */ getInitialState() { return ({ opened: this.props.open }); }, /** - * Init the props if not provided. - * By default, a popin is full, medium and modal. - * @return {Object} the default props - */ + * Init the props if not provided. + * By default, a popin is full, medium and modal. + * @return {Object} the default props + */ getDefaultProps() { return ({ modal: true, @@ -88,12 +90,12 @@ const popin = { }); }, /** - * Helper attribute, for React debugging - */ + * Helper attribute, for React debugging + */ displayName: 'Popin', /** - * Properties validation - */ + * Properties validation + */ propTypes: { modal: types('bool'), size: types('string'), @@ -103,15 +105,15 @@ const popin = { open: types('bool') }, /** - * Wheel event handler. - * @param {object} event wheel event - */ + * Wheel event handler. + * @param {object} event wheel event + */ _onWheel(event) { ReactDOM.findDOMNode(this.refs['popin-window']).scrollTop += 0 < event.deltaY ? 100 : -100; }, /** - * Toggle the popin's open state - */ + * Toggle the popin's open state + */ toggleOpen() { let timeout = 0; const {opened} = this.state; @@ -131,6 +133,11 @@ const popin = { this._openTimeoutID = setTimeout(() => { // Store the current popin state const wasOpened = this.state.opened; + if (wasOpened) { + window.removeEventListener('keydown', this.handleKeyDown, true); + } else { + window.addEventListener('keydown', this.handleKeyDown, true); + } // If it was opened, then we are closing it, so restore the body overflow before closing. if (wasOpened && this.refs['popin-overlay']) { this.refs['popin-overlay']._restoreBodyOverflow(); @@ -147,35 +154,72 @@ const popin = { }); }, timeout); }, + componentWillUnmount() { window.clearTimeout(this._openTimeoutID); }, /** - * Render the component - * @return {XML} the rendered HTML - */ + * set the z-order level of the popin + * @param level + */ + setLevel(level) { + this.setState({level : level}); + }, + /** + * keyboard event handler + * close the popin with esc + * @param e keyboard event + */ + handleKeyDown(e) { + e.preventDefault(); + if (e.code === "Escape") { + if (this.state.opened) { + const popins = [].slice.call(document.querySelectorAll("[data-focus='popin']")).reduce((prec, current) => { + if (current.hasChildNodes()) { + prec.push(Number(current.getAttribute('data-level'))); + } + return prec; + }, []); + + const level = this.state.level || this.props.level; + if (max(popins) === level) { + this.toggleOpen(); + e.stopPropagation(); + e.stopImmediatePropagation(); + } + } + } + }, + /** + * Render the component + * @return {XML} the rendered HTML + */ render() { // test for this.state.opened and return an Overlay component if true - const {type, level, modal, overlay, children} = this.props; + const {type, modal, overlay, children} = this.props; + const level = this.state.level || this.props.level; return ( -
+
{this.state.opened && - -
- close -
- {children} -
+ +
+ close +
+ {children}
- +
+
}
); }, /** - * Compute the animation classes - * @return {Object} the props to attach to the component - * @private - */ + * Compute the animation classes + * @return {Object} the props to attach to the component + * @private + */ _getAnimationProps() { let openingAnimation; let closingAnimation; @@ -200,10 +244,10 @@ const popin = { }); }, /** - * Validate the optional given size - * @return {string} the validated size - * @private - */ + * Validate the optional given size + * @return {string} the validated size + * @private + */ _validateSize() { if (!includes(['small', 'medium', 'large'], this.props.size)) { throw new ArgumentInvalidException('Please provide a valid popin size among small, medium and large. Provided ' + this.props.size); @@ -211,10 +255,10 @@ const popin = { return this.props.size; }, /** - * Prevent popin close when there's a click on the popin window - * @param {Object} event - raised by the click - * @private - */ + * Prevent popin close when there's a click on the popin window + * @param {Object} event - raised by the click + * @private + */ _preventPopinClose(event) { event.stopPropagation(); }