UNPKG

office-ui-fabric-react

Version:

Reusable React components for building experiences for Office 365.

450 lines (449 loc) • 21 kB
var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; define(["require", "exports", 'react', './FocusZone.Props', '../../utilities/eventGroup/EventGroup', '../../utilities/KeyCodes', '../../utilities/rtl', '../../utilities/object', '../../utilities/autobind', '../../utilities/css', '../../utilities/focus'], function (require, exports, React, FocusZone_Props_1, EventGroup_1, KeyCodes_1, rtl_1, object_1, autobind_1, css_1, focus_1) { "use strict"; 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 _allInstances = {}; var FocusZone = (function (_super) { __extends(FocusZone, _super); function FocusZone(props) { _super.call(this, props); this._id = object_1.getId('FocusZone'); _allInstances[this._id] = this; this._focusAlignment = { left: 0, top: 0 }; this._events = new EventGroup_1.EventGroup(this); } FocusZone.prototype.componentDidMount = function () { var windowElement = this.refs.root.ownerDocument.defaultView; var parentElement = this.refs.root.parentElement; while (parentElement && parentElement !== document.body) { if (focus_1.isElementFocusZone(parentElement)) { this._isInnerZone = true; break; } parentElement = parentElement.parentElement; } this._events.on(windowElement, 'keydown', this._onKeyDownCapture, true); }; FocusZone.prototype.componentWillUnmount = function () { this._events.dispose(); delete _allInstances[this._id]; }; FocusZone.prototype.render = function () { var _a = this.props, rootProps = _a.rootProps, ariaLabelledBy = _a.ariaLabelledBy, className = _a.className; return (React.createElement("div", React.__spread({}, rootProps, {className: css_1.css('ms-FocusZone', className), ref: 'root', "data-focuszone-id": this._id, "aria-labelledby": ariaLabelledBy, onMouseDownCapture: this._onMouseDown, onKeyDown: this._onKeyDown, onFocus: this._onFocus}), this.props.children)); }; /** * Sets focus to the first tabbable item in the zone. * @returns True if focus could be set to an active element, false if no operation was taken. */ FocusZone.prototype.focus = function () { if (this._activeElement && this.refs.root.contains(this._activeElement)) { this._activeElement.focus(); return true; } else { var firstChild = this.refs.root.firstChild; return this.focusElement(focus_1.getNextElement(this.refs.root, firstChild, true)); } }; /** * 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 {HTMLElement} 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) { if (this._activeElement) { this._activeElement.tabIndex = -1; } this._activeElement = element; if (element) { if (!this._focusAlignment) { this._setFocusAlignment(element, true, true); } this._activeElement.tabIndex = 0; element.focus(); return true; } } return false; }; FocusZone.prototype._onFocus = function (ev) { var onActiveElementChanged = this.props.onActiveElementChanged; if (this._isImmediateDescendantOfZone(ev.target)) { this._activeElement = ev.target; this._setFocusAlignment(this._activeElement); } else { var parentElement = ev.target; while (parentElement && parentElement !== this.refs.root) { if (focus_1.isElementTabbable(parentElement) && this._isImmediateDescendantOfZone(parentElement)) { this._activeElement = parentElement; break; } parentElement = parentElement.parentElement; } } if (onActiveElementChanged) { onActiveElementChanged(this._activeElement, ev); } }; /** * Handle global tab presses so that we can patch tabindexes on the fly. */ FocusZone.prototype._onKeyDownCapture = function (ev) { if (ev.which === KeyCodes_1.KeyCodes.tab) { this._updateTabIndexes(); } }; FocusZone.prototype._onMouseDown = function (ev) { var disabled = this.props.disabled; if (disabled) { return; } var target = ev.target; var path = []; while (target && target !== this.refs.root) { path.push(target); target = target.parentElement; } while (path.length) { target = path.pop(); if (focus_1.isElementFocusZone(target)) { break; } else if (target && focus_1.isElementTabbable(target)) { target.tabIndex = 0; this._setFocusAlignment(target, true, true); } } }; /** * Handle the keystrokes. */ FocusZone.prototype._onKeyDown = function (ev) { var _a = this.props, direction = _a.direction, disabled = _a.disabled, isInnerZoneKeystroke = _a.isInnerZoneKeystroke; if (disabled) { return; } if (isInnerZoneKeystroke && this._isImmediateDescendantOfZone(ev.target) && isInnerZoneKeystroke(ev)) { // Try to focus var innerZone = this._getFirstInnerZone(); if (!innerZone || !innerZone.focus()) { return; } } else { switch (ev.which) { case KeyCodes_1.KeyCodes.left: if (direction !== FocusZone_Props_1.FocusZoneDirection.vertical && this._moveFocusLeft()) { break; } return; case KeyCodes_1.KeyCodes.right: if (direction !== FocusZone_Props_1.FocusZoneDirection.vertical && this._moveFocusRight()) { break; } return; case KeyCodes_1.KeyCodes.up: if (direction !== FocusZone_Props_1.FocusZoneDirection.horizontal && this._moveFocusUp()) { break; } return; case KeyCodes_1.KeyCodes.down: if (direction !== FocusZone_Props_1.FocusZoneDirection.horizontal && this._moveFocusDown()) { break; } return; case KeyCodes_1.KeyCodes.home: var firstChild = this.refs.root.firstChild; if (this.focusElement(focus_1.getNextElement(this.refs.root, firstChild, true))) { break; } return; case KeyCodes_1.KeyCodes.end: var lastChild = this.refs.root.lastChild; if (this.focusElement(focus_1.getPreviousElement(this.refs.root, lastChild, true, true, true))) { break; } return; case KeyCodes_1.KeyCodes.enter: if (this._tryInvokeClickForFocusable(ev.target)) { break; } return; default: return; } } ev.preventDefault(); ev.stopPropagation(); }; /** * Walk up the dom try to find a focusable element. */ FocusZone.prototype._tryInvokeClickForFocusable = function (target) { do { if (target.tagName === 'BUTTON' || target.tagName === 'A') { return false; } if (this._isImmediateDescendantOfZone(target) && target.getAttribute(IS_FOCUSABLE_ATTRIBUTE) === 'true' && target.getAttribute(IS_ENTER_DISABLED_ATTRIBUTE) !== 'true') { EventGroup_1.EventGroup.raise(target, 'click', null, true); return true; } target = target.parentElement; } while (target !== this.refs.root); return false; }; /** * Traverse to find first child zone. */ FocusZone.prototype._getFirstInnerZone = function (rootElement) { rootElement = rootElement || this._activeElement || this.refs.root; var child = rootElement.firstElementChild; while (child) { if (focus_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) { var element = this._activeElement; var candidateDistance = -1; var candidateElement; var changedFocus = false; var isBidirectional = this.props.direction === FocusZone_Props_1.FocusZoneDirection.bidirectional; if (!this._activeElement) { return; } var activeRect = isBidirectional ? this._activeElement.getBoundingClientRect() : null; do { element = isForward ? focus_1.getNextElement(this.refs.root, element) : focus_1.getPreviousElement(this.refs.root, element); if (isBidirectional) { if (element) { var targetRect = element.getBoundingClientRect(); var elementDistance = getDistanceFromCenter(activeRect, targetRect); 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) { if (isForward) { return this.focusElement(focus_1.getNextElement(this.refs.root, this.refs.root.firstElementChild, true)); } else { return this.focusElement(focus_1.getPreviousElement(this.refs.root, this.refs.root.lastElementChild, true, true, true)); } } return changedFocus; }; FocusZone.prototype._moveFocusDown = function () { var targetTop = -1; var leftAlignment = this._focusAlignment.left; if (this._moveFocus(true, function (activeRect, targetRect) { var distance = -1; if ((targetTop === -1 && targetRect.top >= activeRect.bottom) || (targetRect.top === targetTop)) { targetTop = targetRect.top; 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 targetTop = -1; var leftAlignment = this._focusAlignment.left; if (this._moveFocus(false, function (activeRect, targetRect) { var distance = -1; if ((targetTop === -1 && targetRect.bottom <= activeRect.top) || (targetRect.top === targetTop)) { targetTop = targetRect.top; 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 targetTop = -1; var topAlignment = this._focusAlignment.top; if (this._moveFocus(rtl_1.getRTL(), function (activeRect, targetRect) { var distance = -1; if ((targetTop === -1 && targetRect.right <= activeRect.right && (_this.props.direction === FocusZone_Props_1.FocusZoneDirection.horizontal || targetRect.top === activeRect.top)) || (targetRect.top === targetTop)) { targetTop = targetRect.top; distance = Math.abs((targetRect.top + (targetRect.height / 2)) - topAlignment); } return distance; })) { this._setFocusAlignment(this._activeElement, true, false); return true; } return false; }; FocusZone.prototype._moveFocusRight = function () { var _this = this; var targetTop = -1; var topAlignment = this._focusAlignment.top; if (this._moveFocus(!rtl_1.getRTL(), function (activeRect, targetRect) { var distance = -1; if ((targetTop === -1 && targetRect.left >= activeRect.left && (_this.props.direction === FocusZone_Props_1.FocusZoneDirection.horizontal || targetRect.top === activeRect.top)) || (targetRect.top === targetTop)) { targetTop = targetRect.top; distance = Math.abs((targetRect.top + (targetRect.height / 2)) - topAlignment); } return distance; })) { this._setFocusAlignment(this._activeElement, true, false); return true; } return false; }; FocusZone.prototype._setFocusAlignment = function (element, isHorizontal, isVertical) { if (this.props.direction === FocusZone_Props_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 = { left: left, top: top_1 }; } if (isHorizontal) { this._focusAlignment.left = left; } if (isVertical) { this._focusAlignment.top = top_1; } } }; FocusZone.prototype._isImmediateDescendantOfZone = function (element) { var parentElement = element.parentElement; while (parentElement && parentElement !== this.refs.root && parentElement !== document.body) { if (focus_1.isElementFocusZone(parentElement)) { return false; } parentElement = parentElement.parentElement; } return true; }; FocusZone.prototype._updateTabIndexes = function (element) { if (!element) { element = this.refs.root; if (this._activeElement && !element.contains(this._activeElement)) { this._activeElement = null; } } var childNodes = element.children; for (var childIndex = 0; childNodes && childIndex < childNodes.length; childIndex++) { var child = childNodes[childIndex]; if (!focus_1.isElementFocusZone(child)) { if (focus_1.isElementTabbable(child)) { if (this.props.disabled) { child.setAttribute(TABINDEX, '-1'); } else if (!this._isInnerZone && (!this._activeElement || this._activeElement === child)) { this._activeElement = 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'); } this._updateTabIndexes(child); } } }; FocusZone.defaultProps = { isCircularNavigation: false, direction: FocusZone_Props_1.FocusZoneDirection.bidirectional }; __decorate([ autobind_1.autobind ], FocusZone.prototype, "_onFocus", null); __decorate([ autobind_1.autobind ], FocusZone.prototype, "_onMouseDown", null); __decorate([ autobind_1.autobind ], FocusZone.prototype, "_onKeyDown", null); return FocusZone; }(React.Component)); exports.FocusZone = FocusZone; });