office-ui-fabric-react
Version:
Reusable React components for building experiences for Microsoft 365.
371 lines • 20.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var Utilities_1 = require("../../Utilities");
var index_1 = require("../FocusTrapZone/index");
var Modal_styles_1 = require("./Modal.styles");
var Overlay_1 = require("../../Overlay");
var Layer_1 = require("../../Layer");
var index_2 = require("../Popup/index");
var withResponsiveMode_1 = require("../../utilities/decorators/withResponsiveMode");
var index_3 = require("../Callout/index");
var index_4 = require("../Icon/index");
var index_5 = require("../../utilities/DraggableZone/index");
var utilities_1 = require("@uifabric/utilities");
// @TODO - need to change this to a panel whenever the breakpoint is under medium (verify the spec)
var DefaultLayerProps = {
eventBubblingEnabled: false,
};
var getClassNames = Utilities_1.classNamesFunction();
var COMPONENT_NAME = 'Modal';
var ModalBase = /** @class */ (function (_super) {
tslib_1.__extends(ModalBase, _super);
function ModalBase(props) {
var _this = _super.call(this, props) || this;
_this._focusTrapZone = React.createRef();
_this._focusTrapZoneMergedRef = Utilities_1.createMergedRef();
_this._registerInitialModalPosition = function () {
var _a;
var dialogMain = document.querySelector("[data-id=" + _this.state.id + "]");
if (dialogMain) {
var modalRectangle = dialogMain.getBoundingClientRect();
if (((_a = _this.props.dragOptions) === null || _a === void 0 ? void 0 : _a.keepInBounds) && !_this._minClampedPosition && !_this._maxClampedPosition) {
_this._minClampedPosition = { x: -modalRectangle.x, y: -modalRectangle.y };
_this._maxClampedPosition = { x: modalRectangle.x, y: modalRectangle.y };
}
_this.setState({
modalRectangleTop: modalRectangle.top,
});
}
};
// Allow the user to scroll within the modal but not on the body
_this._allowScrollOnModal = function (elt) {
if (elt) {
if (_this._allowTouchBodyScroll) {
Utilities_1.allowOverscrollOnElement(elt, _this._events);
}
else {
Utilities_1.allowScrollOnElement(elt, _this._events);
}
}
else {
_this._events.off(_this._scrollableContent);
}
_this._scrollableContent = elt;
};
_this._onModalContextMenuClose = function () {
_this.setState({ isModalMenuOpen: false });
};
_this._onModalClose = function () {
_this._lastSetX = 0;
_this._lastSetY = 0;
_this.setState({
isModalMenuOpen: false,
isInKeyboardMoveMode: false,
isOpen: false,
x: 0,
y: 0,
});
if (_this.props.dragOptions && _this._hasRegisteredKeyUp) {
_this._events.off(window, 'keyup', _this._onKeyUp, true /* useCapture */);
}
// Call the onDismiss callback
if (_this.props.onDismissed) {
_this.props.onDismissed();
}
};
_this._onDragStart = function () {
_this.setState({ isModalMenuOpen: false, isInKeyboardMoveMode: false });
};
_this._onDrag = function (_, ui) {
var _a = _this.state, x = _a.x, y = _a.y;
_this.setState(_this._getClampedPosition({ x: x + ui.delta.x, y: y + ui.delta.y }));
};
_this._onDragStop = function () {
_this.focus();
};
_this._onKeyUp = function (event) {
// Need to handle the CTRL + ALT + SPACE key during keyup due to FireFox bug:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1220143
// Otherwise it would continue to fire a click even if the event was cancelled
// during mouseDown.
if (event.altKey && event.ctrlKey && event.keyCode === Utilities_1.KeyCodes.space) {
// Since this is a global handler, we should make sure the target is within the dialog
// before opening the dropdown
if (Utilities_1.elementContains(_this._scrollableContent, event.target)) {
_this.setState({ isModalMenuOpen: !_this.state.isModalMenuOpen });
event.preventDefault();
event.stopPropagation();
}
}
};
// We need a global onKeyDown event when we are in the move mode so that we can
// handle the key presses and the components inside the modal do not get the events
_this._onKeyDown = function (event) {
if (event.altKey && event.ctrlKey && event.keyCode === Utilities_1.KeyCodes.space) {
// CTRL + ALT + SPACE is handled during keyUp
event.preventDefault();
event.stopPropagation();
return;
}
if (_this.state.isModalMenuOpen && (event.altKey || event.keyCode === Utilities_1.KeyCodes.escape)) {
_this.setState({ isModalMenuOpen: false });
}
if (_this.state.isInKeyboardMoveMode && (event.keyCode === Utilities_1.KeyCodes.escape || event.keyCode === Utilities_1.KeyCodes.enter)) {
_this.setState({ isInKeyboardMoveMode: false });
event.preventDefault();
event.stopPropagation();
}
if (_this.state.isInKeyboardMoveMode) {
var handledEvent = true;
var delta = _this._getMoveDelta(event);
switch (event.keyCode) {
/* eslint-disable no-fallthrough */
case Utilities_1.KeyCodes.escape:
_this.setState({ x: _this._lastSetX, y: _this._lastSetY });
case Utilities_1.KeyCodes.enter: {
// TODO: determine if fallthrough was intentional
/* eslint-enable no-fallthrough */
_this._lastSetX = 0;
_this._lastSetY = 0;
_this.setState({ isInKeyboardMoveMode: false });
break;
}
case Utilities_1.KeyCodes.up: {
_this.setState({
y: _this._getClampedPositionY(_this.state.y - delta),
});
break;
}
case Utilities_1.KeyCodes.down: {
_this.setState({
y: _this._getClampedPositionY(_this.state.y + delta),
});
break;
}
case Utilities_1.KeyCodes.left: {
_this.setState({
x: _this._getClampedPositionX(_this.state.x - delta),
});
break;
}
case Utilities_1.KeyCodes.right: {
_this.setState({
x: _this._getClampedPositionX(_this.state.x + delta),
});
break;
}
default: {
handledEvent = false;
}
}
if (handledEvent) {
event.preventDefault();
event.stopPropagation();
}
}
};
_this._onEnterKeyboardMoveMode = function () {
_this._lastSetX = _this.state.x;
_this._lastSetY = _this.state.y;
_this.setState({ isInKeyboardMoveMode: true, isModalMenuOpen: false });
_this._events.on(window, 'keydown', _this._onKeyDown, true /* useCapture */);
};
_this._onExitKeyboardMoveMode = function (ev) {
var _a, _b, _c;
(_c = (_a = _this.props.focusTrapZoneProps) === null || _a === void 0 ? void 0 : (_b = _a).onBlur) === null || _c === void 0 ? void 0 : _c.call(_b, ev);
_this._lastSetX = 0;
_this._lastSetY = 0;
_this.setState({ isInKeyboardMoveMode: false });
_this._events.off(window, 'keydown', _this._onKeyDown, true /* useCapture */);
};
_this._registerForKeyUp = function () {
if (!_this._hasRegisteredKeyUp) {
_this._events.on(window, 'keyup', _this._onKeyUp, true /* useCapture */);
_this._hasRegisteredKeyUp = true;
}
};
_this._async = new Utilities_1.Async(_this);
_this._events = new Utilities_1.EventGroup(_this);
utilities_1.initializeComponentRef(_this);
Utilities_1.warnDeprecations(COMPONENT_NAME, props, {
onLayerDidMount: 'layerProps.onLayerDidMount',
});
_this.state = {
id: Utilities_1.getId('Modal'),
isOpen: props.isOpen,
isVisible: props.isOpen,
hasBeenOpened: props.isOpen,
x: 0,
y: 0,
};
_this._lastSetX = 0;
_this._lastSetY = 0;
var _a = _this.props.allowTouchBodyScroll, allowTouchBodyScroll = _a === void 0 ? false : _a;
_this._allowTouchBodyScroll = allowTouchBodyScroll;
return _this;
}
ModalBase.prototype.UNSAFE_componentWillReceiveProps = function (newProps) {
clearTimeout(this._onModalCloseTimer);
// Opening the dialog
if (newProps.isOpen) {
if (!this.state.isOpen) {
// First Open
this.setState({
isOpen: true,
});
// Add a keyUp handler for all key up events when the dialog is open
if (newProps.dragOptions) {
this._registerForKeyUp();
}
}
else {
// Modal has been opened
// Reopen during closing
this.setState({
hasBeenOpened: true,
isVisible: true,
});
}
}
// Closing the dialog
if (!newProps.isOpen && this.state.isOpen) {
this._onModalCloseTimer = this._async.setTimeout(this._onModalClose, parseFloat(Modal_styles_1.animationDuration) * 1000);
this.setState({
isVisible: false,
});
}
};
ModalBase.prototype.componentDidMount = function () {
var _this = this;
// Not all modals show just by updating their props. Some only render when they are mounted and pass in
// isOpen as true. We need to add the keyUp handler in componentDidMount if we are in that case.
if (this.state.isOpen && this.state.isVisible) {
this._registerForKeyUp();
requestAnimationFrame(function () { return setTimeout(_this._registerInitialModalPosition, 0); });
}
};
ModalBase.prototype.componentDidUpdate = function (prevProps, prevState) {
var _this = this;
if (!prevProps.isOpen && !prevState.isVisible) {
this.setState({
isVisible: true,
});
}
if (!prevProps.isOpen && this.props.isOpen) {
requestAnimationFrame(function () { return setTimeout(_this._registerInitialModalPosition, 0); });
}
};
ModalBase.prototype.componentWillUnmount = function () {
this._async.dispose();
this._events.dispose();
};
ModalBase.prototype.render = function () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
var _r = this.props, className = _r.className, containerClassName = _r.containerClassName, scrollableContentClassName = _r.scrollableContentClassName, elementToFocusOnDismiss = _r.elementToFocusOnDismiss, firstFocusableSelector = _r.firstFocusableSelector, focusTrapZoneProps = _r.focusTrapZoneProps, forceFocusInsideTrap = _r.forceFocusInsideTrap, ignoreExternalFocusing = _r.ignoreExternalFocusing, isBlocking = _r.isBlocking, isClickableOutsideFocusTrap = _r.isClickableOutsideFocusTrap, isDarkOverlay = _r.isDarkOverlay, onDismiss = _r.onDismiss, layerProps = _r.layerProps, overlay = _r.overlay, responsiveMode = _r.responsiveMode, titleAriaId = _r.titleAriaId, styles = _r.styles, subtitleAriaId = _r.subtitleAriaId, theme = _r.theme, topOffsetFixed = _r.topOffsetFixed,
// eslint-disable-next-line deprecation/deprecation
onLayerDidMount = _r.onLayerDidMount, isModeless = _r.isModeless, isAlert = _r.isAlert, dragOptions = _r.dragOptions, enableAriaHiddenSiblings = _r.enableAriaHiddenSiblings;
var _s = this.state, isOpen = _s.isOpen, isVisible = _s.isVisible, hasBeenOpened = _s.hasBeenOpened, modalRectangleTop = _s.modalRectangleTop, x = _s.x, y = _s.y, isInKeyboardMoveMode = _s.isInKeyboardMoveMode;
if (!isOpen) {
return null;
}
var layerClassName = layerProps === undefined ? '' : layerProps.className;
var isAlertRole = (isAlert !== null && isAlert !== void 0 ? isAlert : (isBlocking && !isModeless));
var classNames = getClassNames(styles, {
theme: theme,
className: className,
containerClassName: containerClassName,
scrollableContentClassName: scrollableContentClassName,
isOpen: isOpen,
isVisible: isVisible,
hasBeenOpened: hasBeenOpened,
modalRectangleTop: modalRectangleTop,
topOffsetFixed: topOffsetFixed,
isModeless: isModeless,
layerClassName: layerClassName,
isDefaultDragHandle: dragOptions && !dragOptions.dragHandleSelector,
});
var mergedLayerProps = tslib_1.__assign(tslib_1.__assign(tslib_1.__assign({}, DefaultLayerProps), this.props.layerProps), { onLayerDidMount: layerProps && layerProps.onLayerDidMount ? layerProps.onLayerDidMount : onLayerDidMount, insertFirst: isModeless, className: classNames.layer });
var modalContent = (React.createElement(index_1.FocusTrapZone, tslib_1.__assign({}, focusTrapZoneProps, { "data-id": this.state.id, componentRef: this._focusTrapZoneMergedRef(this._focusTrapZone, (_a = focusTrapZoneProps) === null || _a === void 0 ? void 0 : _a.componentRef), className: Utilities_1.css(classNames.main, (_b = focusTrapZoneProps) === null || _b === void 0 ? void 0 : _b.className), elementToFocusOnDismiss: (_d = (_c = focusTrapZoneProps) === null || _c === void 0 ? void 0 : _c.elementToFocusOnDismiss, (_d !== null && _d !== void 0 ? _d : elementToFocusOnDismiss)), isClickableOutsideFocusTrap: (_f = (_e = focusTrapZoneProps) === null || _e === void 0 ? void 0 : _e.isClickableOutsideFocusTrap, (_f !== null && _f !== void 0 ? _f : (isModeless || isClickableOutsideFocusTrap || !isBlocking))), ignoreExternalFocusing: (_h = (_g = focusTrapZoneProps) === null || _g === void 0 ? void 0 : _g.ignoreExternalFocusing, (_h !== null && _h !== void 0 ? _h : ignoreExternalFocusing)), forceFocusInsideTrap: (_k = (_j = focusTrapZoneProps) === null || _j === void 0 ? void 0 : _j.forceFocusInsideTrap, (_k !== null && _k !== void 0 ? _k : forceFocusInsideTrap)) && !isModeless,
// eslint-disable-next-line deprecation/deprecation
firstFocusableSelector: ((_l = focusTrapZoneProps) === null || _l === void 0 ? void 0 : _l.firstFocusableSelector) || firstFocusableSelector, focusPreviouslyFocusedInnerElement: (_o = (_m = focusTrapZoneProps) === null || _m === void 0 ? void 0 : _m.focusPreviouslyFocusedInnerElement, (_o !== null && _o !== void 0 ? _o : true)), onBlur: isInKeyboardMoveMode ? this._onExitKeyboardMoveMode : undefined, enableAriaHiddenSiblings: (_q = (_p = focusTrapZoneProps) === null || _p === void 0 ? void 0 : _p.enableAriaHiddenSiblings, (_q !== null && _q !== void 0 ? _q : enableAriaHiddenSiblings)) }),
dragOptions && isInKeyboardMoveMode && (React.createElement("div", { className: classNames.keyboardMoveIconContainer }, dragOptions.keyboardMoveIconProps ? (React.createElement(index_4.Icon, tslib_1.__assign({}, dragOptions.keyboardMoveIconProps))) : (React.createElement(index_4.Icon, { iconName: "move", className: classNames.keyboardMoveIcon })))),
React.createElement("div", { ref: this._allowScrollOnModal, className: classNames.scrollableContent, "data-is-scrollable": true },
dragOptions && this.state.isModalMenuOpen && (React.createElement(dragOptions.menu, { items: [
{ key: 'move', text: dragOptions.moveMenuItemText, onClick: this._onEnterKeyboardMoveMode },
{ key: 'close', text: dragOptions.closeMenuItemText, onClick: this._onModalClose },
], onDismiss: this._onModalContextMenuClose, alignTargetEdge: true, coverTarget: true, directionalHint: index_3.DirectionalHint.topLeftEdge, directionalHintFixed: true, shouldFocusOnMount: true, target: this._scrollableContent })),
this.props.children)));
// @temp tuatology - Will adjust this to be a panel at certain breakpoints
if (responsiveMode >= withResponsiveMode_1.ResponsiveMode.small) {
return (React.createElement(Layer_1.Layer, tslib_1.__assign({}, mergedLayerProps),
React.createElement(index_2.Popup, { role: isAlertRole ? 'alertdialog' : 'dialog', "aria-modal": !isModeless, ariaLabelledBy: titleAriaId, ariaDescribedBy: subtitleAriaId, onDismiss: onDismiss, shouldRestoreFocus: !ignoreExternalFocusing },
React.createElement("div", { className: classNames.root, role: !isModeless ? 'document' : undefined },
!isModeless && (React.createElement(Overlay_1.Overlay, tslib_1.__assign({ isDarkThemed: isDarkOverlay, onClick: isBlocking ? undefined : onDismiss, allowTouchBodyScroll: this._allowTouchBodyScroll }, overlay))),
dragOptions ? (React.createElement(index_5.DraggableZone, { handleSelector: dragOptions.dragHandleSelector || "." + classNames.main.split(' ')[0], preventDragSelector: "button", onStart: this._onDragStart, onDragChange: this._onDrag, onStop: this._onDragStop, position: { x: x, y: y } }, modalContent)) : (modalContent)))));
}
return null;
};
ModalBase.prototype.focus = function () {
if (this._focusTrapZone.current) {
this._focusTrapZone.current.focus();
}
};
/**
* Clamps the position coordinates to the maximum/minimum value specified in props
*/
ModalBase.prototype._getClampedPosition = function (position) {
if (!this.props.dragOptions || !this.props.dragOptions.keepInBounds) {
return position;
}
return { x: this._getClampedPositionX(position.x), y: this._getClampedPositionY(position.y) };
};
ModalBase.prototype._getClampedPositionY = function (y) {
var minPosition = this._minClampedPosition;
var maxPosition = this._maxClampedPosition;
if (minPosition) {
y = Math.max(minPosition.y, y);
}
if (maxPosition) {
y = Math.min(maxPosition.y, y);
}
return y;
};
ModalBase.prototype._getClampedPositionX = function (x) {
var minPosition = this._minClampedPosition;
var maxPosition = this._maxClampedPosition;
if (minPosition) {
x = Math.max(minPosition.x, x);
}
if (maxPosition) {
x = Math.min(maxPosition.x, x);
}
return x;
};
ModalBase.prototype._getMoveDelta = function (event) {
var delta = 10;
if (event.shiftKey) {
if (!event.ctrlKey) {
delta = 50;
}
}
else if (event.ctrlKey) {
delta = 1;
}
return delta;
};
ModalBase.defaultProps = {
isOpen: false,
isDarkOverlay: true,
isBlocking: false,
className: '',
containerClassName: '',
};
ModalBase = tslib_1.__decorate([
withResponsiveMode_1.withResponsiveMode
], ModalBase);
return ModalBase;
}(React.Component));
exports.ModalBase = ModalBase;
//# sourceMappingURL=Modal.base.js.map