office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
215 lines • 11.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 FocusTrapZone = /** @class */ (function (_super) {
tslib_1.__extends(FocusTrapZone, _super);
function FocusTrapZone() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this._root = React.createRef();
_this._firstBumper = React.createRef();
_this._lastBumper = React.createRef();
_this._hasFocus = false;
_this._onRootFocus = function (ev) {
if (_this.props.onFocus) {
_this.props.onFocus(ev);
}
_this._hasFocus = true;
};
_this._onRootBlur = function (ev) {
if (_this.props.onBlur) {
_this.props.onBlur(ev);
}
var relatedTarget = ev.relatedTarget;
if (ev.relatedTarget === null) {
// In IE11, due to lack of support, event.relatedTarget is always
// null making every onBlur call to be "outside" of the ComboBox
// even when it's not. Using document.activeElement is another way
// for us to be able to get what the relatedTarget without relying
// on the event
relatedTarget = document.activeElement;
}
if (!Utilities_1.elementContains(_this._root.current, relatedTarget)) {
_this._hasFocus = false;
}
};
_this._onFirstBumperFocus = function () {
_this._onBumperFocus(true);
};
_this._onLastBumperFocus = function () {
_this._onBumperFocus(false);
};
_this._onBumperFocus = function (isFirstBumper) {
var currentBumper = (isFirstBumper === _this._hasFocus ? _this._lastBumper.current : _this._firstBumper.current);
if (_this._root.current) {
var nextFocusable = isFirstBumper === _this._hasFocus
? Utilities_1.getLastTabbable(_this._root.current, currentBumper, true, false)
: Utilities_1.getFirstTabbable(_this._root.current, currentBumper, true, false);
if (nextFocusable) {
if (_this._isBumper(nextFocusable)) {
// This can happen when FTZ contains no tabbable elements. focus will take care of finding a focusable element in FTZ.
_this.focus();
}
else {
nextFocusable.focus();
}
}
}
};
_this._onFocusCapture = function (ev) {
if (_this.props.onFocusCapture) {
_this.props.onFocusCapture(ev);
}
if (ev.target !== ev.currentTarget && !_this._isBumper(ev.target)) {
// every time focus changes within the trap zone, remember the focused element so that
// it can be restored if focus leaves the pane and returns via keystroke (i.e. via a call to this.focus(true))
_this._previouslyFocusedElementInTrapZone = ev.target;
}
};
return _this;
}
FocusTrapZone.prototype.componentDidMount = function () {
this._bringFocusIntoZone();
this._updateEventHandlers(this.props);
};
FocusTrapZone.prototype.componentWillReceiveProps = function (nextProps) {
var elementToFocusOnDismiss = nextProps.elementToFocusOnDismiss;
if (elementToFocusOnDismiss && this._previouslyFocusedElementOutsideTrapZone !== elementToFocusOnDismiss) {
this._previouslyFocusedElementOutsideTrapZone = elementToFocusOnDismiss;
}
this._updateEventHandlers(nextProps);
};
FocusTrapZone.prototype.componentDidUpdate = function (prevProps) {
var prevForceFocusInsideTrap = prevProps.forceFocusInsideTrap !== undefined ? prevProps.forceFocusInsideTrap : true;
var newForceFocusInsideTrap = this.props.forceFocusInsideTrap !== undefined ? this.props.forceFocusInsideTrap : true;
if (!prevForceFocusInsideTrap && newForceFocusInsideTrap) {
// Transition from forceFocusInsideTrap disabled to enabled. Emulate what happens when a FocusTrapZone gets mounted
this._bringFocusIntoZone();
}
else if (prevForceFocusInsideTrap && !newForceFocusInsideTrap) {
// Transition from forceFocusInsideTrap enabled to disabled. Emulate what happens when a FocusTrapZone gets unmounted
this._returnFocusToInitiator();
}
};
FocusTrapZone.prototype.componentWillUnmount = function () {
this._events.dispose();
this._returnFocusToInitiator();
};
FocusTrapZone.prototype.render = function () {
var _a = this.props, className = _a.className, ariaLabelledBy = _a.ariaLabelledBy;
var divProps = Utilities_1.getNativeProps(this.props, Utilities_1.divProperties);
var bumperProps = {
style: {
pointerEvents: 'none',
position: 'fixed' // 'fixed' prevents browsers from scrolling to bumpers when viewport does not contain them
},
tabIndex: 0,
'aria-hidden': true,
'data-is-visible': true
};
return (React.createElement("div", tslib_1.__assign({}, divProps, { className: className, ref: this._root, "aria-labelledby": ariaLabelledBy, onFocusCapture: this._onFocusCapture, onFocus: this._onRootFocus, onBlur: this._onRootBlur }),
React.createElement("div", tslib_1.__assign({}, bumperProps, { ref: this._firstBumper, onFocus: this._onFirstBumperFocus })),
this.props.children,
React.createElement("div", tslib_1.__assign({}, bumperProps, { ref: this._lastBumper, onFocus: this._onLastBumperFocus }))));
};
FocusTrapZone.prototype.focus = function () {
var _a = this.props, focusPreviouslyFocusedInnerElement = _a.focusPreviouslyFocusedInnerElement, firstFocusableSelector = _a.firstFocusableSelector;
if (focusPreviouslyFocusedInnerElement &&
this._previouslyFocusedElementInTrapZone &&
Utilities_1.elementContains(this._root.current, this._previouslyFocusedElementInTrapZone)) {
// focus on the last item that had focus in the zone before we left the zone
this._focusAsync(this._previouslyFocusedElementInTrapZone);
return;
}
var focusSelector = typeof firstFocusableSelector === 'string' ? firstFocusableSelector : firstFocusableSelector && firstFocusableSelector();
var _firstFocusableChild;
if (this._root.current) {
if (focusSelector) {
_firstFocusableChild = this._root.current.querySelector('.' + focusSelector);
}
// Fall back to first element if query selector did not match any elements.
if (!_firstFocusableChild) {
_firstFocusableChild = Utilities_1.getNextElement(this._root.current, this._root.current.firstChild, false, false, false, true);
}
}
if (_firstFocusableChild) {
this._focusAsync(_firstFocusableChild);
}
};
FocusTrapZone.prototype._focusAsync = function (element) {
if (!this._isBumper(element)) {
Utilities_1.focusAsync(element);
}
};
FocusTrapZone.prototype._bringFocusIntoZone = function () {
var _a = this.props, elementToFocusOnDismiss = _a.elementToFocusOnDismiss, _b = _a.disableFirstFocus, disableFirstFocus = _b === void 0 ? false : _b;
FocusTrapZone._focusStack.push(this);
this._previouslyFocusedElementOutsideTrapZone = elementToFocusOnDismiss
? elementToFocusOnDismiss
: document.activeElement;
if (!disableFirstFocus && !Utilities_1.elementContains(this._root.current, this._previouslyFocusedElementOutsideTrapZone)) {
this.focus();
}
};
FocusTrapZone.prototype._returnFocusToInitiator = function () {
var _this = this;
var ignoreExternalFocusing = this.props.ignoreExternalFocusing;
FocusTrapZone._focusStack = FocusTrapZone._focusStack.filter(function (value) {
return _this !== value;
});
var activeElement = document.activeElement;
if (!ignoreExternalFocusing &&
this._previouslyFocusedElementOutsideTrapZone &&
typeof this._previouslyFocusedElementOutsideTrapZone.focus === 'function' &&
(Utilities_1.elementContains(this._root.current, activeElement) || activeElement === document.body)) {
this._focusAsync(this._previouslyFocusedElementOutsideTrapZone);
}
};
FocusTrapZone.prototype._updateEventHandlers = function (newProps) {
var _a = newProps.isClickableOutsideFocusTrap, isClickableOutsideFocusTrap = _a === void 0 ? false : _a, _b = newProps.forceFocusInsideTrap, forceFocusInsideTrap = _b === void 0 ? true : _b;
if (forceFocusInsideTrap && !this._hasFocusHandler) {
this._events.on(window, 'focus', this._forceFocusInTrap, true);
}
else if (!forceFocusInsideTrap && this._hasFocusHandler) {
this._events.off(window, 'focus', this._forceFocusInTrap, true);
}
this._hasFocusHandler = forceFocusInsideTrap;
if (!isClickableOutsideFocusTrap && !this._hasClickHandler) {
this._events.on(window, 'click', this._forceClickInTrap, true);
}
else if (isClickableOutsideFocusTrap && this._hasClickHandler) {
this._events.off(window, 'click', this._forceClickInTrap, true);
}
this._hasClickHandler = !isClickableOutsideFocusTrap;
};
FocusTrapZone.prototype._isBumper = function (element) {
return element === this._firstBumper.current || element === this._lastBumper.current;
};
FocusTrapZone.prototype._forceFocusInTrap = function (ev) {
if (FocusTrapZone._focusStack.length && this === FocusTrapZone._focusStack[FocusTrapZone._focusStack.length - 1]) {
var focusedElement = document.activeElement;
if (!Utilities_1.elementContains(this._root.current, focusedElement)) {
this.focus();
this._hasFocus = true; // set focus here since we stop event propagation
ev.preventDefault();
ev.stopPropagation();
}
}
};
FocusTrapZone.prototype._forceClickInTrap = function (ev) {
if (FocusTrapZone._focusStack.length && this === FocusTrapZone._focusStack[FocusTrapZone._focusStack.length - 1]) {
var clickedElement = ev.target;
if (clickedElement && !Utilities_1.elementContains(this._root.current, clickedElement)) {
this.focus();
this._hasFocus = true; // set focus here since we stop event propagation
ev.preventDefault();
ev.stopPropagation();
}
}
};
FocusTrapZone._focusStack = [];
return FocusTrapZone;
}(Utilities_1.BaseComponent));
exports.FocusTrapZone = FocusTrapZone;
//# sourceMappingURL=FocusTrapZone.js.map