define(["require", "exports", "tslib", "react", "../../Utilities", "@fluentui/react-hooks", "../../WindowProvider"], function (require, exports, tslib_1, React, Utilities_1, react_hooks_1, WindowProvider_1) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    exports.FocusTrapZone = void 0;
    var COMPONENT_NAME = 'FocusTrapZone';
    var DEFAULT_PROPS = {
        disabled: false,
        disableFirstFocus: false,
        forceFocusInsideTrap: true,
        isClickableOutsideFocusTrap: false,
    };
    var useComponentRef = function (componentRef, previouslyFocusedElement, focusFTZ) {
        React.useImperativeHandle(componentRef, function () { return ({
            get previouslyFocusedElement() {
                return previouslyFocusedElement;
            },
            focus: focusFTZ,
        }); }, [focusFTZ, previouslyFocusedElement]);
    };
    exports.FocusTrapZone = React.forwardRef(function (propsWithoutDefaults, ref) {
        var _a;
        var root = React.useRef(null);
        var firstBumper = React.useRef(null);
        var lastBumper = React.useRef(null);
        var mergedRootRef = react_hooks_1.useMergedRefs(root, ref);
        var doc = WindowProvider_1.useDocument();
        var isFirstRender = (_a = react_hooks_1.usePrevious(false)) !== null && _a !== void 0 ? _a : true;
        var props = Utilities_1.getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults);
        var internalState = react_hooks_1.useConst({
            hasFocus: false,
            focusStackId: react_hooks_1.useId('ftz-', props.id),
        });
        var children = props.children, componentRef = props.componentRef, disabled = props.disabled, disableFirstFocus = props.disableFirstFocus, forceFocusInsideTrap = props.forceFocusInsideTrap, focusPreviouslyFocusedInnerElement = props.focusPreviouslyFocusedInnerElement, 
        // eslint-disable-next-line deprecation/deprecation
        firstFocusableSelector = props.firstFocusableSelector, firstFocusableTarget = props.firstFocusableTarget, 
        // eslint-disable-next-line deprecation/deprecation
        _b = props.disableRestoreFocus, 
        // eslint-disable-next-line deprecation/deprecation
        disableRestoreFocus = _b === void 0 ? props.ignoreExternalFocusing : _b, isClickableOutsideFocusTrap = props.isClickableOutsideFocusTrap, enableAriaHiddenSiblings = props.enableAriaHiddenSiblings;
        var bumperProps = {
            'aria-hidden': true,
            style: {
                pointerEvents: 'none',
                position: 'fixed', // 'fixed' prevents browsers from scrolling to bumpers when viewport does not contain them
            },
            tabIndex: disabled ? -1 : 0,
            'data-is-visible': true,
            'data-is-focus-trap-zone-bumper': true,
        };
        var focusElementAsync = React.useCallback(function (element) {
            if (element !== firstBumper.current && element !== lastBumper.current) {
                Utilities_1.focusAsync(element);
            }
        }, []);
        /**
         * Callback to force focus into FTZ (named to avoid overlap with global focus() callback).
         * useEventCallback always returns the same callback reference but updates the implementation
         * every render to avoid stale captured values.
         */
        var focusFTZ = react_hooks_1.useEventCallback(function () {
            if (!root.current) {
                return; // not done mounting
            }
            var previouslyFocusedElementInTrapZone = internalState.previouslyFocusedElementInTrapZone;
            if (focusPreviouslyFocusedInnerElement &&
                previouslyFocusedElementInTrapZone &&
                Utilities_1.elementContains(root.current, previouslyFocusedElementInTrapZone)) {
                // focus on the last item that had focus in the zone before we left the zone
                focusElementAsync(previouslyFocusedElementInTrapZone);
                return;
            }
            var firstFocusableChild = null;
            if (typeof firstFocusableTarget === 'string') {
                firstFocusableChild = root.current.querySelector(firstFocusableTarget);
            }
            else if (firstFocusableTarget) {
                firstFocusableChild = firstFocusableTarget(root.current);
            }
            else if (firstFocusableSelector) {
                var focusSelector = typeof firstFocusableSelector === 'string' ? firstFocusableSelector : firstFocusableSelector();
                firstFocusableChild = root.current.querySelector('.' + focusSelector);
            }
            // Fall back to first element if query selector did not match any elements.
            if (!firstFocusableChild) {
                firstFocusableChild = Utilities_1.getNextElement(root.current, root.current.firstChild, false, false, false, true);
            }
            if (firstFocusableChild) {
                focusElementAsync(firstFocusableChild);
            }
        });
        /** Used in root div focus/blur handlers */
        var focusBumper = function (isFirstBumper) {
            if (disabled || !root.current) {
                return;
            }
            var nextFocusable = isFirstBumper === internalState.hasFocus
                ? Utilities_1.getLastTabbable(root.current, lastBumper.current, true, false)
                : Utilities_1.getFirstTabbable(root.current, firstBumper.current, true, false);
            if (nextFocusable) {
                if (nextFocusable === firstBumper.current || nextFocusable === lastBumper.current) {
                    // This can happen when FTZ contains no tabbable elements.
                    // focusFTZ() will take care of finding a focusable element in FTZ.
                    focusFTZ();
                }
                else {
                    nextFocusable.focus();
                }
            }
        };
        /** Root div blur handler (doesn't need useCallback since it's for a native element) */
        var onRootBlurCapture = function (ev) {
            var _a;
            (_a = props.onBlurCapture) === null || _a === void 0 ? void 0 : _a.call(props, 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 root
                // 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 = Utilities_1.getActiveElement(doc);
            }
            if (!Utilities_1.elementContains(root.current, relatedTarget)) {
                internalState.hasFocus = false;
            }
        };
        /** Root div focus handler (doesn't need useCallback since it's for a native element) */
        var onRootFocusCapture = function (ev) {
            var _a;
            (_a = props.onFocusCapture) === null || _a === void 0 ? void 0 : _a.call(props, ev);
            if (ev.target === firstBumper.current) {
                focusBumper(true);
            }
            else if (ev.target === lastBumper.current) {
                focusBumper(false);
            }
            internalState.hasFocus = true;
            if (ev.target !== ev.currentTarget && !(ev.target === firstBumper.current || ev.target === lastBumper.current)) {
                // 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))
                internalState.previouslyFocusedElementInTrapZone = ev.target;
            }
        };
        /** Called to restore focus on unmount or props change. (useEventCallback ensures latest prop values are used.) */
        var returnFocusToInitiator = react_hooks_1.useEventCallback(function (elementToFocusOnDismiss) {
            exports.FocusTrapZone.focusStack = exports.FocusTrapZone.focusStack.filter(function (value) { return internalState.focusStackId !== value; });
            if (!doc) {
                return;
            }
            var activeElement = Utilities_1.getActiveElement(doc);
            if (!disableRestoreFocus &&
                typeof (elementToFocusOnDismiss === null || elementToFocusOnDismiss === void 0 ? void 0 : elementToFocusOnDismiss.focus) === 'function' &&
                // only restore focus if the current focused element is within the FTZ, or if nothing is focused
                (Utilities_1.elementContains(root.current, activeElement) || activeElement === doc.body)) {
                focusElementAsync(elementToFocusOnDismiss);
            }
        });
        /** Called in window event handlers. (useEventCallback ensures latest prop values are used.) */
        var forceFocusOrClickInTrap = react_hooks_1.useEventCallback(function (ev) {
            // be sure to use the latest values here
            if (disabled) {
                return;
            }
            if (internalState.focusStackId === exports.FocusTrapZone.focusStack.slice(-1)[0]) {
                var targetElement = ev.target;
                if (targetElement && !Utilities_1.elementContains(root.current, targetElement)) {
                    if (doc && Utilities_1.getActiveElement(doc) === doc.body) {
                        setTimeout(function () {
                            if (doc && Utilities_1.getActiveElement(doc) === doc.body) {
                                focusFTZ();
                                internalState.hasFocus = true; // set focus here since we stop event propagation
                            }
                        }, 0);
                    }
                    else {
                        focusFTZ();
                        internalState.hasFocus = true; // set focus here since we stop event propagation
                    }
                    ev.preventDefault();
                    ev.stopPropagation();
                }
            }
        });
        // Update window event handlers when relevant props change
        React.useEffect(function () {
            var disposables = [];
            if (forceFocusInsideTrap) {
                disposables.push(Utilities_1.on(window, 'focus', forceFocusOrClickInTrap, true));
            }
            if (!isClickableOutsideFocusTrap) {
                disposables.push(Utilities_1.on(window, 'click', forceFocusOrClickInTrap, true));
            }
            return function () {
                disposables.forEach(function (dispose) { return dispose(); });
            };
            // eslint-disable-next-line react-hooks/exhaustive-deps -- should only run when these two props change
        }, [forceFocusInsideTrap, isClickableOutsideFocusTrap]);
        // On prop change or first render, focus the FTZ and update focusStack if appropriate
        React.useEffect(function () {
            // Do nothing if disabled, or if it's a re-render and forceFocusInsideTrap is false
            // (to match existing behavior, the FTZ handles first focus even if forceFocusInsideTrap
            // is false, though it's debatable whether it should do this)
            if (disabled || (!isFirstRender && !forceFocusInsideTrap) || !root.current) {
                return;
            }
            // Transition from forceFocusInsideTrap / FTZ disabled to enabled (or initial mount)
            exports.FocusTrapZone.focusStack.push(internalState.focusStackId);
            var elementToFocusOnDismiss = props.elementToFocusOnDismiss || Utilities_1.getActiveElement(doc);
            if (!disableFirstFocus && !Utilities_1.elementContains(root.current, elementToFocusOnDismiss)) {
                focusFTZ();
            }
            // To match existing behavior, always return focus on cleanup (even if we didn't handle
            // initial focus), but it's debatable whether that's correct
            return function () { return returnFocusToInitiator(elementToFocusOnDismiss); };
            // eslint-disable-next-line react-hooks/exhaustive-deps -- should only run when these two props change
        }, [forceFocusInsideTrap, disabled]);
        // Handle modalization separately from first focus
        React.useEffect(function () {
            if (!disabled && enableAriaHiddenSiblings) {
                var unmodalize = Utilities_1.modalize(root.current);
                return unmodalize;
            }
        }, [disabled, enableAriaHiddenSiblings, root]);
        // Cleanup lifecyle method for internalState.
        react_hooks_1.useUnmount(function () {
            // Dispose of element references so the DOM Nodes can be garbage-collected
            delete internalState.previouslyFocusedElementInTrapZone;
        });
        useComponentRef(componentRef, internalState.previouslyFocusedElementInTrapZone, focusFTZ);
        return (React.createElement("div", tslib_1.__assign({ "aria-labelledby": props.ariaLabelledBy }, Utilities_1.getNativeProps(props, Utilities_1.divProperties), { ref: mergedRootRef, onFocusCapture: onRootFocusCapture, onBlurCapture: onRootBlurCapture }),
            React.createElement("div", tslib_1.__assign({}, bumperProps, { ref: firstBumper })),
            children,
            React.createElement("div", tslib_1.__assign({}, bumperProps, { ref: lastBumper }))));
    });
    exports.FocusTrapZone.displayName = COMPONENT_NAME;
    exports.FocusTrapZone.focusStack = [];
});
//# sourceMappingURL=FocusTrapZone.js.map