UNPKG

office-ui-fabric-react

Version:

Reusable React components for building experiences for Office 365.

398 lines • 20.1 kB
import * as tslib_1 from "tslib"; // Utilities import * as React from 'react'; import { BaseComponent, classNamesFunction, createRef, elementContains, focusFirstChild, getDocument, shallowCompare } from '../../Utilities'; import { DefaultPalette } from '../../Styling'; import { RectangleEdge, getOppositeEdge } from '../../utilities/positioning'; // Component Dependencies import { PositioningContainer } from './PositioningContainer/index'; import { Beak, BEAK_HEIGHT, BEAK_WIDTH } from './Beak/Beak'; import { COACHMARK_HEIGHT, COACHMARK_WIDTH, getStyles } from './Coachmark.styles'; import { FocusTrapZone } from '../../FocusTrapZone'; var getClassNames = classNamesFunction(); export var COACHMARK_ATTRIBUTE_NAME = 'data-coachmarkid'; var Coachmark = /** @class */ (function (_super) { tslib_1.__extends(Coachmark, _super); function Coachmark(props) { var _this = _super.call(this, props) || this; /** * The cached HTMLElement reference to the Entity Inner Host * element. */ _this._entityHost = createRef(); _this._entityInnerHostElement = createRef(); _this._translateAnimationContainer = createRef(); _this._ariaAlertContainer = createRef(); _this._childrenContainer = createRef(); _this._positioningContainer = createRef(); _this.dismiss = function (ev) { var onDismiss = _this.props.onDismiss; if (onDismiss) { onDismiss(ev); } }; _this._onKeyDown = function (e) { // Open coachmark if user presses ALT + C (arbitrary keypress for now) if ((e.altKey && e.which === 67 /* c */) || (e.which === 13 /* enter */ && _this._translateAnimationContainer.current && _this._translateAnimationContainer.current.contains(e.target))) { _this._onFocusHandler(); } }; _this._onFocusHandler = function () { if (_this.state.isCollapsed) { _this._openCoachmark(); } }; _this._onPositioned = function (positionData) { _this._async.requestAnimationFrame(function () { _this.setState({ targetAlignment: positionData.alignmentEdge, targetPosition: positionData.targetEdge }); }); }; _this._setBeakPosition = function () { var beakLeft; var beakTop; var beakRight; var beakBottom; var transformOriginX; var transformOriginY; var targetAlignment = _this.state.targetAlignment; var distanceAdjustment = '3px'; // Adjustment distance for Beak to shift towards Coachmark bubble. switch (_this._beakDirection) { // If Beak is pointing Up or Down case RectangleEdge.top: case RectangleEdge.bottom: // If there is no target alignment, then beak is X-axis centered in callout if (!targetAlignment) { beakLeft = "calc(50% - " + BEAK_WIDTH / 2 + "px)"; transformOriginX = 'center'; } else { // Beak is aligned to the left of target if (targetAlignment === RectangleEdge.left) { beakLeft = COACHMARK_WIDTH / 2 - BEAK_WIDTH / 2 + "px"; transformOriginX = 'left'; } else { // Beak is aligned to the right of target beakRight = COACHMARK_WIDTH / 2 - BEAK_WIDTH / 2 + "px"; transformOriginX = 'right'; } } if (_this._beakDirection === RectangleEdge.top) { beakTop = distanceAdjustment; transformOriginY = 'top'; } else { beakBottom = distanceAdjustment; transformOriginY = 'bottom'; } break; // If Beak is pointing Left or Right case RectangleEdge.left: case RectangleEdge.right: // If there is no target alignment, then beak is Y-axis centered in callout if (!targetAlignment) { beakTop = "calc(50% - " + BEAK_WIDTH / 2 + "px)"; transformOriginY = "center"; } else { // Beak is aligned to the top of target if (targetAlignment === RectangleEdge.top) { beakTop = COACHMARK_WIDTH / 2 - BEAK_WIDTH / 2 + "px"; transformOriginY = "top"; } else { // Beak is aligned to the bottom of target beakBottom = COACHMARK_WIDTH / 2 - BEAK_WIDTH / 2 + "px"; transformOriginY = "bottom"; } } if (_this._beakDirection === RectangleEdge.left) { beakLeft = distanceAdjustment; transformOriginX = 'left'; } else { beakRight = distanceAdjustment; transformOriginX = 'right'; } break; } _this.setState({ beakLeft: beakLeft, beakRight: beakRight, beakBottom: beakBottom, beakTop: beakTop, transformOrigin: transformOriginX + " " + transformOriginY }); }; _this._openCoachmark = function () { _this.setState({ isCollapsed: false }); if (_this.props.onAnimationOpenStart) { _this.props.onAnimationOpenStart(); } _this._entityInnerHostElement.current && _this._entityInnerHostElement.current.addEventListener('transitionend', function () { // Need setTimeout to trigger narrator _this._async.setTimeout(function () { if (_this._entityInnerHostElement.current) { focusFirstChild(_this._entityInnerHostElement.current); } }, 1000); if (_this.props.onAnimationOpenEnd) { _this.props.onAnimationOpenEnd(); } }); }; _this._warnDeprecations({ teachingBubbleRef: undefined, collapsed: 'isCollapsed', beakWidth: undefined, beakHeight: undefined, width: undefined, height: undefined }); // Set defaults for state _this.state = { isCollapsed: props.isCollapsed, isBeaconAnimating: true, isMeasuring: true, entityInnerHostRect: { width: 0, height: 0 }, isMouseInProximity: false, isMeasured: false }; return _this; } Object.defineProperty(Coachmark.prototype, "_beakDirection", { get: function () { var targetPosition = this.state.targetPosition; if (targetPosition === undefined) { return RectangleEdge.bottom; } return getOppositeEdge(targetPosition); }, enumerable: true, configurable: true }); Coachmark.prototype.render = function () { var _a = this.props, children = _a.children, target = _a.target, color = _a.color, positioningContainerProps = _a.positioningContainerProps, ariaDescribedBy = _a.ariaDescribedBy, ariaDescribedByText = _a.ariaDescribedByText, ariaLabelledBy = _a.ariaLabelledBy, ariaLabelledByText = _a.ariaLabelledByText, ariaAlertText = _a.ariaAlertText; var _b = this.state, beakLeft = _b.beakLeft, beakTop = _b.beakTop, beakRight = _b.beakRight, beakBottom = _b.beakBottom, isCollapsed = _b.isCollapsed, isBeaconAnimating = _b.isBeaconAnimating, isMeasuring = _b.isMeasuring, entityInnerHostRect = _b.entityInnerHostRect, transformOrigin = _b.transformOrigin, alertText = _b.alertText, isMeasured = _b.isMeasured; var classNames = getClassNames(getStyles, { isCollapsed: isCollapsed, isBeaconAnimating: isBeaconAnimating, isMeasuring: isMeasuring, entityHostHeight: entityInnerHostRect.height + "px", entityHostWidth: entityInnerHostRect.width + "px", width: COACHMARK_WIDTH + "px", height: COACHMARK_HEIGHT + "px", color: color, transformOrigin: transformOrigin, isMeasured: isMeasured }); var finalHeight = isCollapsed ? COACHMARK_HEIGHT : entityInnerHostRect.height; return (React.createElement(PositioningContainer, tslib_1.__assign({ target: target, offsetFromTarget: BEAK_HEIGHT, componentRef: this._positioningContainer, finalHeight: finalHeight, onPositioned: this._onPositioned, bounds: this._getBounds() }, positioningContainerProps), React.createElement("div", { className: classNames.root }, ariaAlertText && (React.createElement("div", { className: classNames.ariaContainer, role: "alert", ref: this._ariaAlertContainer, "aria-hidden": !isCollapsed }, alertText)), React.createElement("div", { className: classNames.pulsingBeacon }), React.createElement("div", { className: classNames.translateAnimationContainer, ref: this._translateAnimationContainer }, React.createElement("div", { className: classNames.scaleAnimationLayer }, React.createElement("div", { className: classNames.rotateAnimationLayer }, this._positioningContainer.current && isCollapsed && (React.createElement(Beak, { left: beakLeft, top: beakTop, right: beakRight, bottom: beakBottom, direction: this._beakDirection, color: color })), React.createElement("div", { className: classNames.entityHost, ref: this._entityHost, tabIndex: -1, "data-is-focusable": true, role: "dialog", "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy }, isCollapsed && [ ariaLabelledBy && (React.createElement("p", { id: ariaLabelledBy, key: 0, className: classNames.ariaContainer }, ariaLabelledByText)), ariaDescribedBy && (React.createElement("p", { id: ariaDescribedBy, key: 1, className: classNames.ariaContainer }, ariaDescribedByText)) ], React.createElement(FocusTrapZone, { isClickableOutsideFocusTrap: true, forceFocusInsideTrap: false }, React.createElement("div", { className: classNames.entityInnerHost, ref: this._entityInnerHostElement }, React.createElement("div", { className: classNames.childrenContainer, ref: this._childrenContainer, "aria-hidden": isCollapsed }, children)))))))))); }; Coachmark.prototype.componentWillReceiveProps = function (newProps) { if (this.props.isCollapsed && !newProps.isCollapsed) { // The coachmark is about to open this._openCoachmark(); } }; Coachmark.prototype.shouldComponentUpdate = function (newProps, newState) { return !shallowCompare(newProps, this.props) || !shallowCompare(newState, this.state); }; Coachmark.prototype.componentDidUpdate = function (prevProps, prevState) { if (prevState.targetAlignment !== this.state.targetAlignment || prevState.targetPosition !== this.state.targetPosition) { this._setBeakPosition(); } if (prevProps.preventDismissOnLostFocus !== this.props.preventDismissOnLostFocus) { this._addListeners(); } }; Coachmark.prototype.componentDidMount = function () { var _this = this; this._async.requestAnimationFrame(function () { if (_this._entityInnerHostElement.current && _this.state.entityInnerHostRect.width + _this.state.entityInnerHostRect.width === 0) { _this.setState({ isMeasuring: false, entityInnerHostRect: { width: _this._entityInnerHostElement.current.offsetWidth, height: _this._entityInnerHostElement.current.offsetHeight }, isMeasured: true }); _this._setBeakPosition(); _this.forceUpdate(); } _this._addListeners(); // We don't want to the user to immediately trigger the Coachmark when it's opened _this._async.setTimeout(function () { _this._addProximityHandler(_this.props.mouseProximityOffset); }, _this.props.delayBeforeMouseOpen); // Need to add setTimeout to have narrator read change in alert container if (_this.props.ariaAlertText) { _this._async.setTimeout(function () { if (_this.props.ariaAlertText && _this._ariaAlertContainer.current) { _this.setState({ alertText: _this.props.ariaAlertText }); } }, 0); } _this._async.setTimeout(function () { if (_this._entityHost.current) { _this._entityHost.current.focus(); } }, 1000); }); }; Coachmark.prototype._addListeners = function () { var preventDismissOnLostFocus = this.props.preventDismissOnLostFocus; var currentDoc = getDocument(); this._events.off(); if (currentDoc) { this._events.on(currentDoc, 'keydown', this._onKeyDown, true); if (!preventDismissOnLostFocus) { this._events.on(currentDoc, 'click', this._dismissOnLostFocus, true); this._events.on(currentDoc, 'focus', this._dismissOnLostFocus, true); } } }; Coachmark.prototype._dismissOnLostFocus = function (ev) { var clickTarget = ev.target; var clickedOutsideCallout = this._translateAnimationContainer.current && !elementContains(this._translateAnimationContainer.current, clickTarget); var target = this.props.target; if (clickedOutsideCallout && clickTarget !== target && !elementContains(target, clickTarget)) { this.dismiss(ev); } }; Coachmark.prototype._getBounds = function () { var _a = this.props, isPositionForced = _a.isPositionForced, positioningContainerProps = _a.positioningContainerProps; if (isPositionForced) { // If directionalHint direction is the top or bottom auto edge, then we want to set the left/right bounds // to the window x-axis to have auto positioning work correctly. if (positioningContainerProps && (positioningContainerProps.directionalHint === 3 /* topAutoEdge */ || positioningContainerProps.directionalHint === 7 /* bottomAutoEdge */)) { return { left: 0, top: -Infinity, bottom: Infinity, right: window.innerWidth, width: window.innerWidth, height: Infinity }; } else { return { left: -Infinity, top: -Infinity, bottom: Infinity, right: Infinity, width: Infinity, height: Infinity }; } } else { return undefined; } }; Coachmark.prototype._addProximityHandler = function (mouseProximityOffset) { var _this = this; if (mouseProximityOffset === void 0) { mouseProximityOffset = 0; } /** * An array of cached ids returned when setTimeout runs during * the window resize event trigger. */ var timeoutIds = []; // Take the initial measure out of the initial render to prevent // an unnecessary render. this._async.setTimeout(function () { _this._setTargetElementRect(); // When the window resizes we want to async // get the bounding client rectangle. // Every time the event is triggered we want to // setTimeout and then clear any previous instances // of setTimeout. _this._events.on(window, 'resize', function () { timeoutIds.forEach(function (value) { clearInterval(value); }); timeoutIds.push(_this._async.setTimeout(function () { _this._setTargetElementRect(); }, 100)); }); }, 10); // Every time the document's mouse move is triggered // we want to check if inside of an element and // set the state with the result. this._events.on(document, 'mousemove', function (e) { if (_this.state.isCollapsed) { var mouseY = e.pageY; var mouseX = e.pageX; _this._setTargetElementRect(); var isMouseInProximity = _this._isInsideElement(mouseX, mouseY, mouseProximityOffset); if (isMouseInProximity !== _this.state.isMouseInProximity) { _this._openCoachmark(); } } if (_this.props.onMouseMove) { _this.props.onMouseMove(e); } }); }; Coachmark.prototype._setTargetElementRect = function () { if (this._translateAnimationContainer && this._translateAnimationContainer.current) { this._targetElementRect = this._translateAnimationContainer.current.getBoundingClientRect(); } }; Coachmark.prototype._isInsideElement = function (mouseX, mouseY, mouseProximityOffset) { if (mouseProximityOffset === void 0) { mouseProximityOffset = 0; } return (mouseX > this._targetElementRect.left - mouseProximityOffset && mouseX < this._targetElementRect.left + this._targetElementRect.width + mouseProximityOffset && mouseY > this._targetElementRect.top - mouseProximityOffset && mouseY < this._targetElementRect.top + this._targetElementRect.height + mouseProximityOffset); }; Coachmark.defaultProps = { isCollapsed: true, mouseProximityOffset: 10, delayBeforeMouseOpen: 3600, color: DefaultPalette.themePrimary, isPositionForced: true, positioningContainerProps: { directionalHint: 7 /* bottomAutoEdge */ } }; return Coachmark; }(BaseComponent)); export { Coachmark }; //# sourceMappingURL=Coachmark.js.map