UNPKG

react-tray

Version:

An accessible tray component useful for navigation menus

249 lines (209 loc) 6.53 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _helpersFocusManager = require('../helpers/focusManager'); var _helpersFocusManager2 = _interopRequireDefault(_helpersFocusManager); var _helpersIsLeavingNode = require('../helpers/isLeavingNode'); var _helpersIsLeavingNode2 = _interopRequireDefault(_helpersIsLeavingNode); var _helpersTabbable = require('../helpers/tabbable'); var _helpersTabbable2 = _interopRequireDefault(_helpersTabbable); var styles = { overlay: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0 }, content: { position: 'absolute', background: '#fff' } }; var CLASS_NAMES = { overlay: { base: 'ReactTray__Overlay', afterOpen: 'ReactTray__Overlay--after-open', beforeClose: 'ReactTray__Overlay--before-close' }, content: { base: 'ReactTray__Content', afterOpen: 'ReactTray__Content--after-open', beforeClose: 'ReactTray__Content--before-close' } }; function isChild(parent, child) { if (parent === child) { return true; } var node = child; /* eslint no-cond-assign:0 */ while (node = node.parentNode) { if (node === parent) { return true; } } return false; } exports['default'] = _react2['default'].createClass({ displayName: 'TrayPortal', propTypes: { className: _react.PropTypes.string, overlayClassName: _react.PropTypes.string, isOpen: _react.PropTypes.bool, onBlur: _react.PropTypes.func, onOpen: _react.PropTypes.func, closeOnBlur: _react.PropTypes.bool, closeTimeoutMS: _react.PropTypes.number, children: _react.PropTypes.any, maintainFocus: _react.PropTypes.bool, getElementToFocus: _react.PropTypes.func, getAriaHideElement: _react.PropTypes.func }, getInitialState: function getInitialState() { return { afterOpen: false, beforeClose: false }; }, componentDidMount: function componentDidMount() { if (this.props.isOpen) { this.setFocusAfterRender(true); this.open(); } }, componentWillReceiveProps: function componentWillReceiveProps(props) { if (props.isOpen) { this.setFocusAfterRender(true); this.open(); } else if (this.props.isOpen && !props.isOpen) { this.close(); } }, componentDidUpdate: function componentDidUpdate() { if (this.focusAfterRender) { if (this.props.getElementToFocus) { this.props.getElementToFocus().focus(); } else { this.focusContent(); } this.setFocusAfterRender(false); } }, setFocusAfterRender: function setFocusAfterRender(focus) { this.focusAfterRender = focus; }, focusContent: function focusContent() { this.refs.content.focus(); }, applyAriaHidden: function applyAriaHidden(element) { element.setAttribute('aria-hidden', true); }, removeAriaHidden: function removeAriaHidden(element) { element.removeAttribute('aria-hidden'); }, handleOverlayClick: function handleOverlayClick(e) { if (!isChild(this.refs.content, e.target)) { this.props.onBlur(); } }, handleContentKeyDown: function handleContentKeyDown(e) { // Treat ESC as blur/close if (e.keyCode === 27) { this.props.onBlur(); } // Keep focus inside the tray if maintainFocus is true if (e.keyCode === 9 && this.props.maintainFocus && (0, _helpersIsLeavingNode2['default'])(this.refs.content, e)) { e.preventDefault(); var tabbable = (0, _helpersTabbable2['default'])(this.refs.content); var target = tabbable[e.shiftKey ? tabbable.length - 1 : 0]; target.focus(); return; } // Treat tabbing away from content as blur/close if closeOnBlur if (e.keyCode === 9 && this.props.closeOnBlur && (0, _helpersIsLeavingNode2['default'])(this.refs.content, e)) { e.preventDefault(); this.props.onBlur(); } }, open: function open() { var _this = this; _helpersFocusManager2['default'].markForFocusLater(); this.setState({ isOpen: true }, function () { if (_this.props.onOpen) { _this.props.onOpen(); } if (_this.props.getAriaHideElement) { _this.applyAriaHidden(_this.props.getAriaHideElement()); } _this.setState({ afterOpen: true }); }); }, close: function close() { if (this.props.closeTimeoutMS > 0) { this.closeWithTimeout(); } else { this.closeWithoutTimeout(); } if (this.props.getAriaHideElement) { this.removeAriaHidden(this.props.getAriaHideElement()); } }, closeWithTimeout: function closeWithTimeout() { var _this2 = this; this.setState({ beforeClose: true }, function () { setTimeout(_this2.closeWithoutTimeout, _this2.props.closeTimeoutMS); }); }, closeWithoutTimeout: function closeWithoutTimeout() { this.setState({ afterOpen: false, beforeClose: false }, this.afterClose); }, afterClose: function afterClose() { _helpersFocusManager2['default'].returnFocus(); }, shouldBeClosed: function shouldBeClosed() { return !this.props.isOpen && !this.state.beforeClose; }, buildClassName: function buildClassName(which) { var className = CLASS_NAMES[which].base; if (this.state.afterOpen) { className += ' ' + CLASS_NAMES[which].afterOpen; } if (this.state.beforeClose) { className += ' ' + CLASS_NAMES[which].beforeClose; } return className; }, render: function render() { return this.shouldBeClosed() ? _react2['default'].createElement('div', null) : _react2['default'].createElement( 'div', { ref: 'overlay', style: styles.overlay, className: (0, _classnames2['default'])(this.buildClassName('overlay'), this.props.overlayClassName), onClick: this.handleOverlayClick }, _react2['default'].createElement( 'div', { ref: 'content', style: styles.content, className: (0, _classnames2['default'])(this.buildClassName('content'), this.props.className), onKeyDown: this.handleContentKeyDown, tabIndex: '-1' }, this.props.children ) ); } }); module.exports = exports['default'];