office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
272 lines (270 loc) • 14.7 kB
JavaScript
define(["require", "exports", "tslib", "react", "../../common/DirectionalHint", "../../Utilities", "../../utilities/positioning", "../../Popup", "./Callout.scss", "../../Styling"], function (require, exports, tslib_1, React, DirectionalHint_1, Utilities_1, positioning_1, Popup_1, stylesImport, Styling_1) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var styles = stylesImport;
var BEAK_ORIGIN_POSITION = { top: 0, left: 0 };
var OFF_SCREEN_STYLE = { opacity: 0 };
var BORDER_WIDTH = 1;
var CalloutContent = (function (_super) {
tslib_1.__extends(CalloutContent, _super);
function CalloutContent(props) {
var _this = _super.call(this, props) || this;
_this._warnDeprecations({ 'beakStyle': 'beakWidth' });
_this._didSetInitialFocus = false;
_this.state = {
positions: null,
slideDirectionalClassName: null,
calloutElementRect: null,
heightOffset: 0
};
_this._positionAttempts = 0;
return _this;
}
CalloutContent.prototype.componentDidUpdate = function () {
this._setInitialFocus();
this._updatePosition();
};
CalloutContent.prototype.componentWillMount = function () {
var target = this.props.targetElement ? this.props.targetElement : this.props.target;
this._setTargetWindowAndElement(target);
};
CalloutContent.prototype.componentWillUpdate = function (newProps) {
if (newProps.targetElement !== this.props.targetElement || newProps.target !== this.props.target) {
var newTarget = newProps.targetElement ? newProps.targetElement : newProps.target;
this._maxHeight = undefined;
this._setTargetWindowAndElement(newTarget);
}
if (newProps.gapSpace !== this.props.gapSpace || this.props.beakWidth !== newProps.beakWidth) {
this._maxHeight = undefined;
}
};
CalloutContent.prototype.componentDidMount = function () {
this._onComponentDidMount();
};
CalloutContent.prototype.render = function () {
// If there is no target window then we are likely in server side rendering and we should not render anything.
if (!this._targetWindow) {
return null;
}
var _a = this.props, role = _a.role, ariaLabel = _a.ariaLabel, ariaDescribedBy = _a.ariaDescribedBy, ariaLabelledBy = _a.ariaLabelledBy, className = _a.className, target = _a.target, targetElement = _a.targetElement, isBeakVisible = _a.isBeakVisible, beakStyle = _a.beakStyle, children = _a.children, beakWidth = _a.beakWidth, finalHeight = _a.finalHeight, backgroundColor = _a.backgroundColor;
var positions = this.state.positions;
var beakStyleWidth = beakWidth;
// This is here to support the old way of setting the beak size until version 1.0.0.
// beakStyle is now deprecated and will be be removed at version 1.0.0
if (beakStyle === 'ms-Callout-smallbeak') {
beakStyleWidth = 16;
}
var beakReactStyle = {
top: positions && positions.beakPosition ? positions.beakPosition.top : BEAK_ORIGIN_POSITION.top,
left: positions && positions.beakPosition ? positions.beakPosition.left : BEAK_ORIGIN_POSITION.left,
height: beakStyleWidth,
width: beakStyleWidth,
backgroundColor: backgroundColor,
};
var directionalClassName = (positions && positions.directionalClassName)
? Styling_1.AnimationClassNames[positions.directionalClassName]
: '';
var contentMaxHeight = this._getMaxHeight() + this.state.heightOffset;
var beakVisible = isBeakVisible && (!!targetElement || !!target);
var content = (React.createElement("div", { ref: this._resolveRef('_hostElement'), className: Utilities_1.css('ms-Callout-container', styles.container) },
React.createElement("div", { className: Utilities_1.css('ms-Callout', styles.root, className, directionalClassName), style: positions ? positions.calloutPosition : OFF_SCREEN_STYLE, ref: this._resolveRef('_calloutElement') },
beakVisible && (React.createElement("div", { className: Utilities_1.css('ms-Callout-beak', styles.beak), style: beakReactStyle })),
beakVisible &&
(React.createElement("div", { className: Utilities_1.css('ms-Callout-beakCurtain', styles.beakCurtain) })),
React.createElement(Popup_1.Popup, { role: role, ariaLabel: ariaLabel, ariaDescribedBy: ariaDescribedBy, ariaLabelledBy: ariaLabelledBy, className: Utilities_1.css('ms-Callout-main', styles.main, (_b = {},
_b[styles.overFlowYHidden] = finalHeight,
_b)), onDismiss: this.dismiss, shouldRestoreFocus: true, style: { maxHeight: contentMaxHeight, backgroundColor: backgroundColor } }, children))));
return content;
var _b;
};
CalloutContent.prototype.dismiss = function (ev) {
var onDismiss = this.props.onDismiss;
if (onDismiss) {
onDismiss(ev);
}
};
CalloutContent.prototype._dismissOnScroll = function (ev) {
var preventDismissOnScroll = this.props.preventDismissOnScroll;
if (this.state.positions && !preventDismissOnScroll) {
this._dismissOnLostFocus(ev);
}
};
CalloutContent.prototype._dismissOnLostFocus = function (ev) {
var target = ev.target;
var clickedOutsideCallout = this._hostElement && !Utilities_1.elementContains(this._hostElement, target);
if ((!this._target && clickedOutsideCallout) ||
ev.target !== this._targetWindow &&
clickedOutsideCallout &&
(this._target.stopPropagation ||
(!this._target || (target !== this._target && !Utilities_1.elementContains(this._target, target))))) {
this.dismiss(ev);
}
};
CalloutContent.prototype._setInitialFocus = function () {
if (this.props.setInitialFocus && !this._didSetInitialFocus && this.state.positions) {
this._didSetInitialFocus = true;
Utilities_1.focusFirstChild(this._calloutElement);
}
};
CalloutContent.prototype._onComponentDidMount = function () {
var _this = this;
// This is added so the callout will dismiss when the window is scrolled
// but not when something inside the callout is scrolled. The delay seems
// to be required to avoid React firing an async focus event in IE from
// the target changing focus quickly prior to rendering the callout.
this._async.setTimeout(function () {
_this._events.on(_this._targetWindow, 'scroll', _this._dismissOnScroll, true);
_this._events.on(_this._targetWindow, 'resize', _this.dismiss, true);
_this._events.on(_this._targetWindow, 'focus', _this._dismissOnLostFocus, true);
_this._events.on(_this._targetWindow, 'click', _this._dismissOnLostFocus, true);
}, 0);
if (this.props.onLayerMounted) {
this.props.onLayerMounted();
}
this._updatePosition();
this._setHeightOffsetEveryFrame();
};
CalloutContent.prototype._updatePosition = function () {
var positions = this.state.positions;
var hostElement = this._hostElement;
var calloutElement = this._calloutElement;
if (hostElement && calloutElement) {
var currentProps = void 0;
currentProps = Utilities_1.assign(currentProps, this.props);
currentProps.bounds = this._getBounds();
// Temporary to be removed when targetElement is removed. Currently deprecated.
if (this.props.targetElement) {
currentProps.targetElement = this._target;
}
else {
currentProps.target = this._target;
}
var newPositions = positioning_1.getRelativePositions(currentProps, hostElement, calloutElement);
// Set the new position only when the positions are not exists or one of the new callout positions are different.
// The position should not change if the position is within 2 decimal places.
if ((!positions && newPositions) ||
(positions && newPositions && !this._arePositionsEqual(positions, newPositions)
&& this._positionAttempts < 5)) {
// We should not reposition the callout more than a few times, if it is then the content is likely resizing
// and we should stop trying to reposition to prevent a stack overflow.
this._positionAttempts++;
this.setState({
positions: newPositions
});
}
else {
this._positionAttempts = 0;
if (this.props.onPositioned) {
this.props.onPositioned();
}
}
}
};
CalloutContent.prototype._getBounds = function () {
if (!this._bounds) {
var currentBounds = this.props.bounds;
if (!currentBounds) {
currentBounds = {
top: 0 + this.props.minPagePadding,
left: 0 + this.props.minPagePadding,
right: this._targetWindow.innerWidth - this.props.minPagePadding,
bottom: this._targetWindow.innerHeight - this.props.minPagePadding,
width: this._targetWindow.innerWidth - this.props.minPagePadding * 2,
height: this._targetWindow.innerHeight - this.props.minPagePadding * 2
};
}
this._bounds = currentBounds;
}
return this._bounds;
};
CalloutContent.prototype._getMaxHeight = function () {
if (!this._maxHeight) {
if (this.props.directionalHintFixed && this._target) {
var beakWidth = this.props.isBeakVisible ? this.props.beakWidth : 0;
var gapSpace = this.props.gapSpace ? this.props.gapSpace : 0;
this._maxHeight = positioning_1.getMaxHeight(this._target, this.props.directionalHint, beakWidth + gapSpace, this._getBounds());
}
else {
this._maxHeight = this._getBounds().height - BORDER_WIDTH * 2;
}
}
return this._maxHeight;
};
CalloutContent.prototype._arePositionsEqual = function (positions, newPosition) {
if (positions.calloutPosition.top.toFixed(2) !== newPosition.calloutPosition.top.toFixed(2)) {
return false;
}
if (positions.calloutPosition.left.toFixed(2) !== newPosition.calloutPosition.left.toFixed(2)) {
return false;
}
if (positions.beakPosition.top.toFixed(2) !== newPosition.beakPosition.top.toFixed(2)) {
return false;
}
if (positions.beakPosition.top.toFixed(2) !== newPosition.beakPosition.top.toFixed(2)) {
return false;
}
return true;
};
CalloutContent.prototype._setTargetWindowAndElement = function (target) {
if (target) {
if (typeof target === 'string') {
var currentDoc = Utilities_1.getDocument();
this._target = currentDoc ? currentDoc.querySelector(target) : null;
this._targetWindow = Utilities_1.getWindow();
}
else if (target.stopPropagation) {
this._target = target;
this._targetWindow = Utilities_1.getWindow(target.toElement);
}
else {
var targetElement = target;
this._target = target;
this._targetWindow = Utilities_1.getWindow(targetElement);
}
}
else {
this._targetWindow = Utilities_1.getWindow();
}
};
CalloutContent.prototype._setHeightOffsetEveryFrame = function () {
var _this = this;
if (this._calloutElement && this.props.finalHeight) {
this._setHeightOffsetTimer = this._async.requestAnimationFrame(function () {
var calloutMainElem = _this._calloutElement.firstChild;
var cardScrollHeight = calloutMainElem.scrollHeight;
var cardCurrHeight = calloutMainElem.offsetHeight;
var scrollDiff = cardScrollHeight - cardCurrHeight;
_this.setState({
heightOffset: _this.state.heightOffset + scrollDiff
});
if (calloutMainElem.offsetHeight < _this.props.finalHeight) {
_this._setHeightOffsetEveryFrame();
}
else {
_this._async.cancelAnimationFrame(_this._setHeightOffsetTimer);
}
});
}
};
return CalloutContent;
}(Utilities_1.BaseComponent));
CalloutContent.defaultProps = {
preventDismissOnScroll: false,
isBeakVisible: true,
beakWidth: 16,
gapSpace: 0,
minPagePadding: 8,
directionalHint: DirectionalHint_1.DirectionalHint.bottomAutoEdge
};
tslib_1.__decorate([
Utilities_1.autobind
], CalloutContent.prototype, "dismiss", null);
tslib_1.__decorate([
Utilities_1.autobind
], CalloutContent.prototype, "_setInitialFocus", null);
tslib_1.__decorate([
Utilities_1.autobind
], CalloutContent.prototype, "_onComponentDidMount", null);
exports.CalloutContent = CalloutContent;
});
//# sourceMappingURL=CalloutContent.js.map