office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
817 lines • 39.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var FocusZone_types_1 = require("./FocusZone.types");
var Utilities_1 = require("../../Utilities");
var merge_styles_1 = require("@uifabric/merge-styles");
var IS_FOCUSABLE_ATTRIBUTE = 'data-is-focusable';
var IS_ENTER_DISABLED_ATTRIBUTE = 'data-disable-click-on-enter';
var FOCUSZONE_ID_ATTRIBUTE = 'data-focuszone-id';
var TABINDEX = 'tabindex';
var NO_VERTICAL_WRAP = 'data-no-vertical-wrap';
var NO_HORIZONTAL_WRAP = 'data-no-horizontal-wrap';
var LARGE_DISTANCE_FROM_CENTER = 999999999;
var LARGE_NEGATIVE_DISTANCE_FROM_CENTER = -999999999;
var focusZoneStyles;
var focusZoneClass = 'ms-FocusZone';
// Helper function that will return a class for when the root is focused
function getRootClass() {
if (!focusZoneStyles) {
focusZoneStyles = merge_styles_1.mergeStyles({
selectors: {
':focus': {
outline: 'none'
}
}
}, focusZoneClass);
}
return focusZoneStyles;
}
var _allInstances = {};
var _outerZones = new Set();
var ALLOWED_INPUT_TYPES = ['text', 'number', 'password', 'email', 'tel', 'url', 'search'];
var ALLOW_VIRTUAL_ELEMENTS = false;
var FocusZone = /** @class */ (function (_super) {
tslib_1.__extends(FocusZone, _super);
function FocusZone(props) {
var _this = _super.call(this, props) || this;
_this._disposables = [];
_this._root = React.createRef();
_this._onFocus = function (ev) {
if (_this._portalContainsElement(ev.target)) {
// If the event target is inside a portal do not process the event.
return;
}
var _a = _this.props, onActiveElementChanged = _a.onActiveElementChanged, doNotAllowFocusEventToPropagate = _a.doNotAllowFocusEventToPropagate, onFocusNotification = _a.onFocusNotification;
var isImmediateDescendant = _this._isImmediateDescendantOfZone(ev.target);
var newActiveElement;
if (onFocusNotification) {
onFocusNotification();
}
if (isImmediateDescendant) {
newActiveElement = ev.target;
}
else {
var parentElement = ev.target;
while (parentElement && parentElement !== _this._root.current) {
if (Utilities_1.isElementTabbable(parentElement) && _this._isImmediateDescendantOfZone(parentElement)) {
newActiveElement = parentElement;
break;
}
parentElement = Utilities_1.getParent(parentElement, ALLOW_VIRTUAL_ELEMENTS);
}
}
var initialElementFocused = !_this._activeElement;
// If the new active element is a child of this zone and received focus,
// update alignment an immediate descendant
if (newActiveElement && newActiveElement !== _this._activeElement) {
if (isImmediateDescendant || initialElementFocused) {
_this._setFocusAlignment(newActiveElement, initialElementFocused);
}
_this._activeElement = newActiveElement;
if (initialElementFocused) {
_this._updateTabIndexes();
}
}
if (onActiveElementChanged) {
onActiveElementChanged(_this._activeElement, ev);
}
if (doNotAllowFocusEventToPropagate) {
ev.stopPropagation();
}
};
_this._onBlur = function () {
_this._setParkedFocus(false);
};
/**
* Handle global tab presses so that we can patch tabindexes on the fly.
*/
_this._onKeyDownCapture = function (ev) {
if (ev.which === Utilities_1.KeyCodes.tab) {
_outerZones.forEach(function (zone) { return zone._updateTabIndexes(); });
}
};
_this._onMouseDown = function (ev) {
if (_this._portalContainsElement(ev.target)) {
// If the event target is inside a portal do not process the event.
return;
}
var disabled = _this.props.disabled;
if (disabled) {
return;
}
var target = ev.target;
var path = [];
while (target && target !== _this._root.current) {
path.push(target);
target = Utilities_1.getParent(target, ALLOW_VIRTUAL_ELEMENTS);
}
while (path.length) {
target = path.pop();
if (target && Utilities_1.isElementTabbable(target)) {
_this._setActiveElement(target, true);
}
if (Utilities_1.isElementFocusZone(target)) {
// Stop here since the focus zone will take care of its own children.
break;
}
}
};
/**
* Handle the keystrokes.
*/
_this._onKeyDown = function (ev) {
if (_this._portalContainsElement(ev.target)) {
// If the event target is inside a portal do not process the event.
return;
}
var _a = _this.props, direction = _a.direction, disabled = _a.disabled, isInnerZoneKeystroke = _a.isInnerZoneKeystroke;
if (disabled) {
return;
}
if (_this.props.onKeyDown) {
_this.props.onKeyDown(ev);
}
// If the default has been prevented, do not process keyboard events.
if (ev.isDefaultPrevented()) {
return;
}
if (_this._getDocument().activeElement === _this._root.current && _this._isInnerZone) {
// If this element has focus, it is being controlled by a parent.
// Ignore the keystroke.
return;
}
if (isInnerZoneKeystroke && isInnerZoneKeystroke(ev) && _this._isImmediateDescendantOfZone(ev.target)) {
// Try to focus
var innerZone = _this._getFirstInnerZone();
if (innerZone) {
if (!innerZone.focus(true)) {
return;
}
}
else if (Utilities_1.isElementFocusSubZone(ev.target)) {
if (!_this.focusElement(Utilities_1.getNextElement(ev.target, ev.target.firstChild, true))) {
return;
}
}
else {
return;
}
}
else if (ev.altKey) {
return;
}
else {
switch (ev.which) {
case Utilities_1.KeyCodes.space:
if (_this._tryInvokeClickForFocusable(ev.target)) {
break;
}
return;
case Utilities_1.KeyCodes.left:
if (direction !== FocusZone_types_1.FocusZoneDirection.vertical && _this._moveFocusLeft()) {
break;
}
return;
case Utilities_1.KeyCodes.right:
if (direction !== FocusZone_types_1.FocusZoneDirection.vertical && _this._moveFocusRight()) {
break;
}
return;
case Utilities_1.KeyCodes.up:
if (direction !== FocusZone_types_1.FocusZoneDirection.horizontal && _this._moveFocusUp()) {
break;
}
return;
case Utilities_1.KeyCodes.down:
if (direction !== FocusZone_types_1.FocusZoneDirection.horizontal && _this._moveFocusDown()) {
break;
}
return;
case Utilities_1.KeyCodes.tab:
if (_this.props.allowTabKey ||
_this.props.handleTabKey === FocusZone_types_1.FocusZoneTabbableElements.all ||
(_this.props.handleTabKey === FocusZone_types_1.FocusZoneTabbableElements.inputOnly && _this._isElementInput(ev.target))) {
var focusChanged = false;
_this._processingTabKey = true;
if (direction === FocusZone_types_1.FocusZoneDirection.vertical ||
!_this._shouldWrapFocus(_this._activeElement, NO_HORIZONTAL_WRAP)) {
focusChanged = ev.shiftKey ? _this._moveFocusUp() : _this._moveFocusDown();
}
else if (direction === FocusZone_types_1.FocusZoneDirection.horizontal || direction === FocusZone_types_1.FocusZoneDirection.bidirectional) {
var tabWithDirection = Utilities_1.getRTL() ? !ev.shiftKey : ev.shiftKey;
focusChanged = tabWithDirection ? _this._moveFocusLeft() : _this._moveFocusRight();
}
_this._processingTabKey = false;
if (focusChanged) {
break;
}
}
return;
case Utilities_1.KeyCodes.home:
if (_this._isElementInput(ev.target) && !_this._shouldInputLoseFocus(ev.target, false)) {
return false;
}
var firstChild = _this._root.current && _this._root.current.firstChild;
if (_this._root.current && firstChild && _this.focusElement(Utilities_1.getNextElement(_this._root.current, firstChild, true))) {
break;
}
return;
case Utilities_1.KeyCodes.end:
if (_this._isElementInput(ev.target) && !_this._shouldInputLoseFocus(ev.target, true)) {
return false;
}
var lastChild = _this._root.current && _this._root.current.lastChild;
if (_this._root.current && _this.focusElement(Utilities_1.getPreviousElement(_this._root.current, lastChild, true, true, true))) {
break;
}
return;
case Utilities_1.KeyCodes.enter:
if (_this._tryInvokeClickForFocusable(ev.target)) {
break;
}
return;
default:
return;
}
}
ev.preventDefault();
ev.stopPropagation();
};
// Manage componentRef resolution.
Utilities_1.initializeComponentRef(_this);
if (process.env.NODE_ENV !== 'production') {
Utilities_1.warnDeprecations('FocusZone', props, {
rootProps: undefined,
allowTabKey: 'handleTabKey',
elementType: 'as',
ariaDescribedBy: 'aria-describedby',
ariaLabelledBy: 'aria-labelledby'
});
}
_this._id = Utilities_1.getId('FocusZone');
_this._focusAlignment = {
x: 0,
y: 0
};
_this._processingTabKey = false;
return _this;
}
/** Used for testing purposes only. */
FocusZone.getOuterZones = function () {
return _outerZones.size;
};
FocusZone.prototype.componentDidMount = function () {
var root = this._root.current;
_allInstances[this._id] = this;
if (root) {
var windowElement = root.ownerDocument.defaultView;
var parentElement = Utilities_1.getParent(root, ALLOW_VIRTUAL_ELEMENTS);
while (parentElement && parentElement !== this._getDocument().body && parentElement.nodeType === 1) {
if (Utilities_1.isElementFocusZone(parentElement)) {
this._isInnerZone = true;
break;
}
parentElement = Utilities_1.getParent(parentElement, ALLOW_VIRTUAL_ELEMENTS);
}
if (!this._isInnerZone) {
_outerZones.add(this);
}
if (windowElement && _outerZones.size === 1) {
this._disposables.push(Utilities_1.on(windowElement, 'keydown', this._onKeyDownCapture, true));
}
this._disposables.push(Utilities_1.on(root, 'blur', this._onBlur, true));
// Assign initial tab indexes so that we can set initial focus as appropriate.
this._updateTabIndexes();
if (this.props.defaultActiveElement) {
this._activeElement = this._getDocument().querySelector(this.props.defaultActiveElement);
this.focus();
}
}
};
FocusZone.prototype.componentDidUpdate = function () {
var root = this._root.current;
var doc = this._getDocument();
if (doc && this._lastIndexPath && (doc.activeElement === doc.body || doc.activeElement === root)) {
// The element has been removed after the render, attempt to restore focus.
var elementToFocus = Utilities_1.getFocusableByIndexPath(root, this._lastIndexPath);
if (elementToFocus) {
this._setActiveElement(elementToFocus, true);
elementToFocus.focus();
this._setParkedFocus(false);
}
else {
// We had a focus path to restore, but now that path is unresolvable. Park focus
// on the container until we can try again.
this._setParkedFocus(true);
}
}
};
FocusZone.prototype.componentWillUnmount = function () {
delete _allInstances[this._id];
if (!this._isInnerZone) {
_outerZones.delete(this);
}
// Dispose all events.
this._disposables.forEach(function (d) { return d(); });
};
FocusZone.prototype.render = function () {
var _a = this.props, rootProps = _a.rootProps, ariaDescribedBy = _a.ariaDescribedBy, ariaLabelledBy = _a.ariaLabelledBy, className = _a.className;
var divProps = Utilities_1.getNativeProps(this.props, Utilities_1.htmlElementProperties);
var Tag = this.props.as || this.props.elementType || 'div';
// Note, right before rendering/reconciling proceeds, we need to record if focus
// was in the zone before the update. This helper will track this and, if focus
// was actually in the zone, what the index path to the element is at this time.
// Then, later in componentDidUpdate, we can evaluate if we need to restore it in
// the case the element was removed.
this._evaluateFocusBeforeRender();
return (React.createElement(Tag, tslib_1.__assign({ "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy }, divProps, rootProps, {
// Once the getClassName correctly memoizes inputs this should
// be replaced so that className is passed to getRootClass and is included there so
// the class names will always be in the same order.
className: Utilities_1.css(getRootClass(), className), ref: this._root, "data-focuszone-id": this._id, onKeyDown: this._onKeyDown, onFocus: this._onFocus, onMouseDownCapture: this._onMouseDown }), this.props.children));
};
/**
* Sets focus to the first tabbable item in the zone.
* @param forceIntoFirstElement - If true, focus will be forced into the first element, even
* if focus is already in the focus zone.
* @returns True if focus could be set to an active element, false if no operation was taken.
*/
FocusZone.prototype.focus = function (forceIntoFirstElement) {
if (forceIntoFirstElement === void 0) { forceIntoFirstElement = false; }
if (this._root.current) {
if (!forceIntoFirstElement && this._root.current.getAttribute(IS_FOCUSABLE_ATTRIBUTE) === 'true' && this._isInnerZone) {
var ownerZoneElement = this._getOwnerZone(this._root.current);
if (ownerZoneElement !== this._root.current) {
var ownerZone = _allInstances[ownerZoneElement.getAttribute(FOCUSZONE_ID_ATTRIBUTE)];
return !!ownerZone && ownerZone.focusElement(this._root.current);
}
return false;
}
else if (!forceIntoFirstElement &&
this._activeElement &&
Utilities_1.elementContains(this._root.current, this._activeElement) &&
Utilities_1.isElementTabbable(this._activeElement)) {
this._activeElement.focus();
return true;
}
else {
var firstChild = this._root.current.firstChild;
return this.focusElement(Utilities_1.getNextElement(this._root.current, firstChild, true));
}
}
return false;
};
/**
* Sets focus to a specific child element within the zone. This can be used in conjunction with
* onBeforeFocus to created delayed focus scenarios (like animate the scroll position to the correct
* location and then focus.)
* @param element - The child element within the zone to focus.
* @returns True if focus could be set to an active element, false if no operation was taken.
*/
FocusZone.prototype.focusElement = function (element) {
var onBeforeFocus = this.props.onBeforeFocus;
if (onBeforeFocus && !onBeforeFocus(element)) {
return false;
}
if (element) {
// when we Set focus to a specific child, we should recalculate the alignment depend on its position
this._setActiveElement(element);
if (this._activeElement) {
this._activeElement.focus();
}
return true;
}
return false;
};
/**
* Forces horizontal alignment in the context of vertical arrowing to use specific point as the reference, rather than a center based on
* the last horizontal motion.
* @param point - the new reference point.
*/
FocusZone.prototype.setFocusAlignment = function (point) {
this._focusAlignment = point;
};
FocusZone.prototype._evaluateFocusBeforeRender = function () {
var root = this._root.current;
var doc = this._getDocument();
if (doc) {
var focusedElement = doc.activeElement;
// Only update the index path if we are not parked on the root.
if (focusedElement !== root) {
var shouldRestoreFocus = Utilities_1.elementContains(root, focusedElement, false);
this._lastIndexPath = shouldRestoreFocus ? Utilities_1.getElementIndexPath(root, focusedElement) : undefined;
}
}
};
/**
* When focus is in the zone at render time but then all focusable elements are removed,
* we "park" focus temporarily on the root. Once we update with focusable children, we restore
* focus to the closest path from previous. If the user tabs away from the parked container,
* we restore focusability to the pre-parked state.
*/
FocusZone.prototype._setParkedFocus = function (isParked) {
var root = this._root.current;
if (root && this._isParked !== isParked) {
this._isParked = isParked;
if (isParked) {
if (!this.props.allowFocusRoot) {
this._parkedTabIndex = root.getAttribute('tabindex');
root.setAttribute('tabindex', '-1');
}
root.focus();
}
else {
if (!this.props.allowFocusRoot) {
if (this._parkedTabIndex) {
root.setAttribute('tabindex', this._parkedTabIndex);
this._parkedTabIndex = undefined;
}
else {
root.removeAttribute('tabindex');
}
}
}
}
};
FocusZone.prototype._setActiveElement = function (element, forceAlignment) {
var previousActiveElement = this._activeElement;
this._activeElement = element;
if (previousActiveElement) {
if (Utilities_1.isElementFocusZone(previousActiveElement)) {
this._updateTabIndexes(previousActiveElement);
}
previousActiveElement.tabIndex = -1;
}
if (this._activeElement) {
if (!this._focusAlignment || forceAlignment) {
this._setFocusAlignment(element, true, true);
}
this._activeElement.tabIndex = 0;
}
};
/**
* Walk up the dom try to find a focusable element.
*/
FocusZone.prototype._tryInvokeClickForFocusable = function (target) {
if (target === this._root.current) {
return false;
}
do {
if (target.tagName === 'BUTTON' || target.tagName === 'A' || target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
return false;
}
if (this._isImmediateDescendantOfZone(target) &&
target.getAttribute(IS_FOCUSABLE_ATTRIBUTE) === 'true' &&
target.getAttribute(IS_ENTER_DISABLED_ATTRIBUTE) !== 'true') {
Utilities_1.raiseClick(target);
return true;
}
target = Utilities_1.getParent(target, ALLOW_VIRTUAL_ELEMENTS);
} while (target !== this._root.current);
return false;
};
/**
* Traverse to find first child zone.
*/
FocusZone.prototype._getFirstInnerZone = function (rootElement) {
rootElement = rootElement || this._activeElement || this._root.current;
if (!rootElement) {
return null;
}
if (Utilities_1.isElementFocusZone(rootElement)) {
return _allInstances[rootElement.getAttribute(FOCUSZONE_ID_ATTRIBUTE)];
}
var child = rootElement.firstElementChild;
while (child) {
if (Utilities_1.isElementFocusZone(child)) {
return _allInstances[child.getAttribute(FOCUSZONE_ID_ATTRIBUTE)];
}
var match = this._getFirstInnerZone(child);
if (match) {
return match;
}
child = child.nextElementSibling;
}
return null;
};
FocusZone.prototype._moveFocus = function (isForward, getDistanceFromCenter, ev, useDefaultWrap) {
if (useDefaultWrap === void 0) { useDefaultWrap = true; }
var element = this._activeElement;
var candidateDistance = -1;
var candidateElement = undefined;
var changedFocus = false;
var isBidirectional = this.props.direction === FocusZone_types_1.FocusZoneDirection.bidirectional;
if (!element || !this._root.current) {
return false;
}
if (this._isElementInput(element)) {
if (!this._shouldInputLoseFocus(element, isForward)) {
return false;
}
}
var activeRect = isBidirectional ? element.getBoundingClientRect() : null;
do {
element = (isForward ? Utilities_1.getNextElement(this._root.current, element) : Utilities_1.getPreviousElement(this._root.current, element));
if (isBidirectional) {
if (element) {
var targetRect = element.getBoundingClientRect();
var elementDistance = getDistanceFromCenter(activeRect, targetRect);
if (elementDistance === -1 && candidateDistance === -1) {
candidateElement = element;
break;
}
if (elementDistance > -1 && (candidateDistance === -1 || elementDistance < candidateDistance)) {
candidateDistance = elementDistance;
candidateElement = element;
}
if (candidateDistance >= 0 && elementDistance < 0) {
break;
}
}
}
else {
candidateElement = element;
break;
}
} while (element);
// Focus the closest candidate
if (candidateElement && candidateElement !== this._activeElement) {
changedFocus = true;
this.focusElement(candidateElement);
}
else if (this.props.isCircularNavigation && useDefaultWrap) {
if (isForward) {
return this.focusElement(Utilities_1.getNextElement(this._root.current, this._root.current.firstElementChild, true));
}
else {
return this.focusElement(Utilities_1.getPreviousElement(this._root.current, this._root.current.lastElementChild, true, true, true));
}
}
return changedFocus;
};
FocusZone.prototype._moveFocusDown = function () {
var _this = this;
var targetTop = -1;
var leftAlignment = this._focusAlignment.x;
if (this._moveFocus(true, function (activeRect, targetRect) {
var distance = -1;
// ClientRect values can be floats that differ by very small fractions of a decimal.
// If the difference between top and bottom are within a pixel then we should treat
// them as equivalent by using Math.floor. For instance 5.2222 and 5.222221 should be equivalent,
// but without Math.Floor they will be handled incorrectly.
var targetRectTop = Math.floor(targetRect.top);
var activeRectBottom = Math.floor(activeRect.bottom);
if (targetRectTop < activeRectBottom) {
if (!_this._shouldWrapFocus(_this._activeElement, NO_VERTICAL_WRAP)) {
return LARGE_NEGATIVE_DISTANCE_FROM_CENTER;
}
return LARGE_DISTANCE_FROM_CENTER;
}
if ((targetTop === -1 && targetRectTop >= activeRectBottom) || targetRectTop === targetTop) {
targetTop = targetRectTop;
if (leftAlignment >= targetRect.left && leftAlignment <= targetRect.left + targetRect.width) {
distance = 0;
}
else {
distance = Math.abs(targetRect.left + targetRect.width / 2 - leftAlignment);
}
}
return distance;
})) {
this._setFocusAlignment(this._activeElement, false, true);
return true;
}
return false;
};
FocusZone.prototype._moveFocusUp = function () {
var _this = this;
var targetTop = -1;
var leftAlignment = this._focusAlignment.x;
if (this._moveFocus(false, function (activeRect, targetRect) {
var distance = -1;
// ClientRect values can be floats that differ by very small fractions of a decimal.
// If the difference between top and bottom are within a pixel then we should treat
// them as equivalent by using Math.floor. For instance 5.2222 and 5.222221 should be equivalent,
// but without Math.Floor they will be handled incorrectly.
var targetRectBottom = Math.floor(targetRect.bottom);
var targetRectTop = Math.floor(targetRect.top);
var activeRectTop = Math.floor(activeRect.top);
if (targetRectBottom > activeRectTop) {
if (!_this._shouldWrapFocus(_this._activeElement, NO_VERTICAL_WRAP)) {
return LARGE_NEGATIVE_DISTANCE_FROM_CENTER;
}
return LARGE_DISTANCE_FROM_CENTER;
}
if ((targetTop === -1 && targetRectBottom <= activeRectTop) || targetRectTop === targetTop) {
targetTop = targetRectTop;
if (leftAlignment >= targetRect.left && leftAlignment <= targetRect.left + targetRect.width) {
distance = 0;
}
else {
distance = Math.abs(targetRect.left + targetRect.width / 2 - leftAlignment);
}
}
return distance;
})) {
this._setFocusAlignment(this._activeElement, false, true);
return true;
}
return false;
};
FocusZone.prototype._moveFocusLeft = function () {
var _this = this;
var shouldWrap = this._shouldWrapFocus(this._activeElement, NO_HORIZONTAL_WRAP);
if (this._moveFocus(Utilities_1.getRTL(), function (activeRect, targetRect) {
var distance = -1;
var topBottomComparison;
if (Utilities_1.getRTL()) {
// When in RTL, this comparison should be the same as the one in _moveFocusRight for LTR.
// Going left at a leftmost rectangle will go down a line instead of up a line like in LTR.
// This is important, because we want to be comparing the top of the target rect
// with the bottom of the active rect.
topBottomComparison = parseFloat(targetRect.top.toFixed(3)) < parseFloat(activeRect.bottom.toFixed(3));
}
else {
topBottomComparison = parseFloat(targetRect.bottom.toFixed(3)) > parseFloat(activeRect.top.toFixed(3));
}
if (topBottomComparison && targetRect.right <= activeRect.right && _this.props.direction !== FocusZone_types_1.FocusZoneDirection.vertical) {
distance = activeRect.right - targetRect.right;
}
else {
if (!shouldWrap) {
distance = LARGE_NEGATIVE_DISTANCE_FROM_CENTER;
}
}
return distance;
}, undefined /*ev*/, shouldWrap)) {
this._setFocusAlignment(this._activeElement, true, false);
return true;
}
return false;
};
FocusZone.prototype._moveFocusRight = function () {
var _this = this;
var shouldWrap = this._shouldWrapFocus(this._activeElement, NO_HORIZONTAL_WRAP);
if (this._moveFocus(!Utilities_1.getRTL(), function (activeRect, targetRect) {
var distance = -1;
var topBottomComparison;
if (Utilities_1.getRTL()) {
// When in RTL, this comparison should be the same as the one in _moveFocusLeft for LTR.
// Going right at a rightmost rectangle will go up a line instead of down a line like in LTR.
// This is important, because we want to be comparing the bottom of the target rect
// with the top of the active rect.
topBottomComparison = parseFloat(targetRect.bottom.toFixed(3)) > parseFloat(activeRect.top.toFixed(3));
}
else {
topBottomComparison = parseFloat(targetRect.top.toFixed(3)) < parseFloat(activeRect.bottom.toFixed(3));
}
if (topBottomComparison && targetRect.left >= activeRect.left && _this.props.direction !== FocusZone_types_1.FocusZoneDirection.vertical) {
distance = targetRect.left - activeRect.left;
}
else if (!shouldWrap) {
distance = LARGE_NEGATIVE_DISTANCE_FROM_CENTER;
}
return distance;
}, undefined /*ev*/, shouldWrap)) {
this._setFocusAlignment(this._activeElement, true, false);
return true;
}
return false;
};
FocusZone.prototype._setFocusAlignment = function (element, isHorizontal, isVertical) {
if (this.props.direction === FocusZone_types_1.FocusZoneDirection.bidirectional && (!this._focusAlignment || isHorizontal || isVertical)) {
var rect = element.getBoundingClientRect();
var left = rect.left + rect.width / 2;
var top_1 = rect.top + rect.height / 2;
if (!this._focusAlignment) {
this._focusAlignment = {
x: left,
y: top_1
};
}
if (isHorizontal) {
this._focusAlignment.x = left;
}
if (isVertical) {
this._focusAlignment.y = top_1;
}
}
};
FocusZone.prototype._isImmediateDescendantOfZone = function (element) {
return this._getOwnerZone(element) === this._root.current;
};
FocusZone.prototype._getOwnerZone = function (element) {
var parentElement = Utilities_1.getParent(element, ALLOW_VIRTUAL_ELEMENTS);
while (parentElement && parentElement !== this._root.current && parentElement !== this._getDocument().body) {
if (Utilities_1.isElementFocusZone(parentElement)) {
return parentElement;
}
parentElement = Utilities_1.getParent(parentElement, ALLOW_VIRTUAL_ELEMENTS);
}
return parentElement;
};
FocusZone.prototype._updateTabIndexes = function (element) {
if (!element && this._root.current) {
this._defaultFocusElement = null;
element = this._root.current;
if (this._activeElement && !Utilities_1.elementContains(element, this._activeElement)) {
this._activeElement = null;
}
}
// If active element changes state to disabled, set it to null.
// Otherwise, we lose keyboard accessibility to other elements in focus zone.
if (this._activeElement && !Utilities_1.isElementTabbable(this._activeElement)) {
this._activeElement = null;
}
var childNodes = element && element.children;
for (var childIndex = 0; childNodes && childIndex < childNodes.length; childIndex++) {
var child = childNodes[childIndex];
if (!Utilities_1.isElementFocusZone(child)) {
// If the item is explicitly set to not be focusable then TABINDEX needs to be set to -1.
if (child.getAttribute && child.getAttribute(IS_FOCUSABLE_ATTRIBUTE) === 'false') {
child.setAttribute(TABINDEX, '-1');
}
if (Utilities_1.isElementTabbable(child)) {
if (this.props.disabled) {
child.setAttribute(TABINDEX, '-1');
}
else if (!this._isInnerZone && ((!this._activeElement && !this._defaultFocusElement) || this._activeElement === child)) {
this._defaultFocusElement = child;
if (child.getAttribute(TABINDEX) !== '0') {
child.setAttribute(TABINDEX, '0');
}
}
else if (child.getAttribute(TABINDEX) !== '-1') {
child.setAttribute(TABINDEX, '-1');
}
}
else if (child.tagName === 'svg' && child.getAttribute('focusable') !== 'false') {
// Disgusting IE hack. Sad face.
child.setAttribute('focusable', 'false');
}
}
else if (child.getAttribute(IS_FOCUSABLE_ATTRIBUTE) === 'true') {
if (!this._isInnerZone && ((!this._activeElement && !this._defaultFocusElement) || this._activeElement === child)) {
this._defaultFocusElement = child;
if (child.getAttribute(TABINDEX) !== '0') {
child.setAttribute(TABINDEX, '0');
}
}
else if (child.getAttribute(TABINDEX) !== '-1') {
child.setAttribute(TABINDEX, '-1');
}
}
this._updateTabIndexes(child);
}
};
FocusZone.prototype._isElementInput = function (element) {
if (element && element.tagName && (element.tagName.toLowerCase() === 'input' || element.tagName.toLowerCase() === 'textarea')) {
return true;
}
return false;
};
FocusZone.prototype._shouldInputLoseFocus = function (element, isForward) {
// If a tab was used, we want to focus on the next element.
if (!this._processingTabKey && element && element.type && ALLOWED_INPUT_TYPES.indexOf(element.type.toLowerCase()) > -1) {
var selectionStart = element.selectionStart;
var selectionEnd = element.selectionEnd;
var isRangeSelected = selectionStart !== selectionEnd;
var inputValue = element.value;
// We shouldn't lose focus in the following cases:
// 1. There is range selected.
// 2. When selection start is larger than 0 and it is backward.
// 3. when selection start is not the end of length and it is forward.
// 4. We press any of the arrow keys when our handleTabKey isn't none or undefined (only losing focus if we hit tab)
// and if shouldInputLoseFocusOnArrowKey is defined, if scenario prefers to not loose the focus which is determined by calling the
// callback shouldInputLoseFocusOnArrowKey
if (isRangeSelected ||
(selectionStart > 0 && !isForward) ||
(selectionStart !== inputValue.length && isForward) ||
(!!this.props.handleTabKey && !(this.props.shouldInputLoseFocusOnArrowKey && this.props.shouldInputLoseFocusOnArrowKey(element)))) {
return false;
}
}
return true;
};
FocusZone.prototype._shouldWrapFocus = function (element, noWrapDataAttribute) {
return !!this.props.checkForNoWrap ? Utilities_1.shouldWrapFocus(element, noWrapDataAttribute) : true;
};
/**
* Returns true if the element is a descendant of the FocusZone through a React portal.
*/
FocusZone.prototype._portalContainsElement = function (element) {
return element && !!this._root.current && Utilities_1.portalContainsElement(element, this._root.current);
};
FocusZone.prototype._getDocument = function () {
return Utilities_1.getDocument(this._root.current);
};
FocusZone.defaultProps = {
isCircularNavigation: false,
direction: FocusZone_types_1.FocusZoneDirection.bidirectional
};
return FocusZone;
}(React.Component));
exports.FocusZone = FocusZone;
//# sourceMappingURL=FocusZone.js.map