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 __());
};
define(["require", "exports", 'react', './FocusZone.Props', '../../utilities/eventGroup/EventGroup', '../../utilities/KeyCodes', '../../utilities/rtl', '../../utilities/css', '../../utilities/focus'], function (require, exports, React, FocusZone_Props_1, EventGroup_1, KeyCodes_1, rtl_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 _instance = 0;
    var _allInstances = {};
    var FocusZone = (function (_super) {
        __extends(FocusZone, _super);
        function FocusZone(props) {
            _super.call(this, props);
            this._id = String(_instance++);
            _allInstances[this._id] = this;
            this._focusAlignment = {
                left: 0,
                top: 0
            };
            this._events = new EventGroup_1.EventGroup(this);
            this._onKeyDown = this._onKeyDown.bind(this);
            this._onFocus = this._onFocus.bind(this);
            this._onMouseDown = this._onMouseDown.bind(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, className = _a.className, ariaLabelledBy = _a.ariaLabelledBy;
            return (React.createElement("div", React.__spread({}, this.props, {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. */
        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));
            }
        };
        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;
                    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;
                    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._focusElement = function (element) {
            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._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
        };
        return FocusZone;
    }(React.Component));
    exports.FocusZone = FocusZone;
});
