494 lines
14 KiB
JavaScript
494 lines
14 KiB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define(['old_unsupported_dont_use_react'],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.ReactModal=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&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}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
|
|
/** @jsx React.DOM */
|
|
var React = _dereq_('old_unsupported_dont_use_react');
|
|
var ModalPortal = _dereq_('./ModalPortal');
|
|
var ariaAppHider = _dereq_('../helpers/ariaAppHider');
|
|
var injectCSS = _dereq_('../helpers/injectCSS');
|
|
|
|
var Modal = module.exports = React.createClass({
|
|
|
|
displayName: 'Modal',
|
|
|
|
statics: {
|
|
setAppElement: ariaAppHider.setElement,
|
|
injectCSS: injectCSS
|
|
},
|
|
|
|
propTypes: {
|
|
isOpen: React.PropTypes.bool.isRequired,
|
|
onRequestClose: React.PropTypes.func,
|
|
appElement: React.PropTypes.instanceOf(HTMLElement),
|
|
closeTimeoutMS: React.PropTypes.number,
|
|
ariaHideApp: React.PropTypes.bool
|
|
},
|
|
|
|
getDefaultProps: function () {
|
|
return {
|
|
isOpen: false,
|
|
ariaHideApp: true,
|
|
closeTimeoutMS: 0
|
|
};
|
|
},
|
|
|
|
componentDidMount: function() {
|
|
this.node = document.createElement('div');
|
|
this.node.className = 'ReactModalPortal';
|
|
document.body.appendChild(this.node);
|
|
this.renderPortal(this.props);
|
|
},
|
|
|
|
componentWillReceiveProps: function(newProps) {
|
|
this.renderPortal(newProps);
|
|
},
|
|
|
|
componentWillUnmount: function() {
|
|
React.unmountComponentAtNode(this.node);
|
|
document.body.removeChild(this.node);
|
|
},
|
|
|
|
renderPortal: function(props) {
|
|
if (props.ariaHideApp) {
|
|
ariaAppHider.toggle(props.isOpen, props.appElement);
|
|
}
|
|
sanitizeProps(props);
|
|
if (this.portal)
|
|
this.portal.setProps(props);
|
|
else
|
|
this.portal = React.renderComponent(ModalPortal(props), this.node);
|
|
},
|
|
|
|
render: function () {
|
|
return null;
|
|
}
|
|
});
|
|
|
|
var appElement = document.getElementById('application');
|
|
|
|
// In general this will be present, but in the case that it's not present,
|
|
// you'll need to set your own which most likely occurs during tests.
|
|
|
|
if (appElement) {
|
|
Modal.setAppElement(document.getElementById('application'));
|
|
}
|
|
|
|
function sanitizeProps(props) {
|
|
delete props.ref;
|
|
}
|
|
|
|
},{"../helpers/ariaAppHider":3,"../helpers/injectCSS":5,"./ModalPortal":2}],2:[function(_dereq_,module,exports){
|
|
var React = _dereq_('old_unsupported_dont_use_react');
|
|
var div = React.DOM.div;
|
|
var focusManager = _dereq_('../helpers/focusManager');
|
|
var scopeTab = _dereq_('../helpers/scopeTab');
|
|
var cx = _dereq_('old_unsupported_dont_use_react/lib/cx');
|
|
|
|
// so that our CSS is statically analyzable
|
|
var CLASS_NAMES = {
|
|
overlay: {
|
|
base: 'ReactModal__Overlay',
|
|
afterOpen: 'ReactModal__Overlay--after-open',
|
|
beforeClose: 'ReactModal__Overlay--before-close',
|
|
},
|
|
content: {
|
|
base: 'ReactModal__Content',
|
|
afterOpen: 'ReactModal__Content--after-open',
|
|
beforeClose: 'ReactModal__Content--before-close',
|
|
}
|
|
};
|
|
|
|
function stopPropagation(event) {
|
|
event.stopPropagation();
|
|
}
|
|
|
|
var ModalPortal = module.exports = React.createClass({
|
|
|
|
displayName: 'ModalPortal',
|
|
|
|
getInitialState: function() {
|
|
return {
|
|
afterOpen: false,
|
|
beforeClose: false
|
|
};
|
|
},
|
|
|
|
componentDidMount: function() {
|
|
// Focus needs to be set when mounting and already open
|
|
if (this.props.isOpen) {
|
|
this.setFocusAfterRender(true);
|
|
this.open();
|
|
}
|
|
},
|
|
|
|
componentWillReceiveProps: function(newProps) {
|
|
// Focus only needs to be set once when the modal is being opened
|
|
if (!this.props.isOpen && newProps.isOpen) {
|
|
this.setFocusAfterRender(true);
|
|
}
|
|
|
|
if (newProps.isOpen === true)
|
|
this.open();
|
|
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();
|
|
this.setState({isOpen: true}, function() {
|
|
this.setState({afterOpen: true});
|
|
}.bind(this));
|
|
},
|
|
|
|
close: function() {
|
|
if (!this.ownerHandlesClose())
|
|
return;
|
|
if (this.props.closeTimeoutMS > 0)
|
|
this.closeWithTimeout();
|
|
else
|
|
this.closeWithoutTimeout();
|
|
},
|
|
|
|
focusContent: function() {
|
|
this.refs.content.getDOMNode().focus();
|
|
},
|
|
|
|
closeWithTimeout: function() {
|
|
this.setState({beforeClose: true}, function() {
|
|
setTimeout(this.closeWithoutTimeout, this.props.closeTimeoutMS);
|
|
}.bind(this));
|
|
},
|
|
|
|
closeWithoutTimeout: function() {
|
|
this.setState({
|
|
afterOpen: false,
|
|
beforeClose: false
|
|
}, this.afterClose);
|
|
},
|
|
|
|
afterClose: function() {
|
|
focusManager.returnFocus();
|
|
focusManager.teardownScopedFocus();
|
|
},
|
|
|
|
handleKeyDown: function(event) {
|
|
if (event.keyCode == 9 /*tab*/) scopeTab(this.getDOMNode(), event);
|
|
if (event.keyCode == 27 /*esc*/) this.requestClose();
|
|
},
|
|
|
|
handleOverlayClick: function() {
|
|
if (this.ownerHandlesClose())
|
|
this.requestClose();
|
|
else
|
|
this.focusContent();
|
|
},
|
|
|
|
requestClose: function() {
|
|
if (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;
|
|
if (this.state.afterOpen)
|
|
className += ' '+CLASS_NAMES[which].afterOpen;
|
|
if (this.state.beforeClose)
|
|
className += ' '+CLASS_NAMES[which].beforeClose;
|
|
return className;
|
|
},
|
|
|
|
render: function() {
|
|
return this.shouldBeClosed() ? div() : (
|
|
div({
|
|
ref: "overlay",
|
|
className: cx(this.buildClassName('overlay'), this.props.overlayClassName),
|
|
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,"old_unsupported_dont_use_react/lib/cx":9}],3:[function(_dereq_,module,exports){
|
|
var _element = null;
|
|
|
|
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) {
|
|
if (shouldHide)
|
|
hide(appElement);
|
|
else
|
|
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;
|
|
}
|
|
|
|
exports.toggle = toggle;
|
|
exports.setElement = setElement;
|
|
exports.show = show;
|
|
exports.hide = hide;
|
|
exports.resetForTesting = resetForTesting;
|
|
|
|
|
|
},{}],4:[function(_dereq_,module,exports){
|
|
var findTabbable = _dereq_('../helpers/tabbable');
|
|
var modalElement = null;
|
|
var focusLaterElement = null;
|
|
var needToFocus = false;
|
|
|
|
function handleBlur(event) {
|
|
needToFocus = true;
|
|
}
|
|
|
|
function handleFocus(event) {
|
|
if (needToFocus) {
|
|
needToFocus = false;
|
|
// need to see how jQuery shims document.on('focusin') so we don't need the
|
|
// setTimeout, firefox doesn't support focusin, if it did, we could focus
|
|
// the the element outisde of a setTimeout. Side-effect of this
|
|
// implementation is that the document.body gets focus, and then we focus
|
|
// our element right after, seems fine.
|
|
setTimeout(function() {
|
|
if (modalElement.contains(document.activeElement))
|
|
return;
|
|
var el = (findTabbable(modalElement)[0] || modalElement);
|
|
el.focus();
|
|
}, 0);
|
|
}
|
|
}
|
|
|
|
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, false);
|
|
document.addEventListener('focus', handleFocus, true);
|
|
};
|
|
|
|
exports.teardownScopedFocus = function() {
|
|
modalElement = null;
|
|
window.removeEventListener('blur', handleBlur);
|
|
document.removeEventListener('focus', handleFocus);
|
|
};
|
|
|
|
|
|
},{"../helpers/tabbable":7}],5:[function(_dereq_,module,exports){
|
|
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'));
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
},{}],6:[function(_dereq_,module,exports){
|
|
var findTabbable = _dereq_('../helpers/tabbable');
|
|
|
|
module.exports = function(node, event) {
|
|
var tabbable = findTabbable(node);
|
|
var finalTabbable = tabbable[event.shiftKey ? 0 : tabbable.length - 1];
|
|
var leavingFinalTabbable = (
|
|
finalTabbable === document.activeElement ||
|
|
// handle immediate shift+tab after opening with mouse
|
|
node === document.activeElement
|
|
);
|
|
if (!leavingFinalTabbable) return;
|
|
event.preventDefault();
|
|
var target = tabbable[event.shiftKey ? tabbable.length - 1 : 0];
|
|
target.focus();
|
|
};
|
|
|
|
},{"../helpers/tabbable":7}],7:[function(_dereq_,module,exports){
|
|
/*!
|
|
* Adapted from jQuery UI core
|
|
*
|
|
* http://jqueryui.com
|
|
*
|
|
* Copyright 2014 jQuery Foundation and other contributors
|
|
* Released under the MIT license.
|
|
* http://jquery.org/license
|
|
*
|
|
* http://api.jqueryui.com/category/ui-core/
|
|
*/
|
|
|
|
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) ||
|
|
el.style.display === 'none';
|
|
}
|
|
|
|
function visible(element) {
|
|
while (element) {
|
|
if (element === document.body) break;
|
|
if (hidden(element)) return false;
|
|
element = element.parentNode;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function tabbable(element) {
|
|
var tabIndex = element.getAttribute('tabindex');
|
|
if (tabIndex === null) tabIndex = undefined;
|
|
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,exports){
|
|
module.exports = _dereq_('./components/Modal');
|
|
|
|
|
|
},{"./components/Modal":1}],9:[function(_dereq_,module,exports){
|
|
/**
|
|
* Copyright 2013-2014, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*
|
|
* @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)
|
|
}); |