UNPKG

@fluentui/react

Version:

Reusable React components for building web experiences.

133 lines 8.15 kB
define(["require", "exports", "tslib", "react", "../../Utilities", "@fluentui/react-hooks", "@fluentui/react-window-provider"], function (require, exports, tslib_1, React, Utilities_1, react_hooks_1, react_window_provider_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Popup = void 0; function useScrollbarAsync(props, root) { var async = (0, react_hooks_1.useAsync)(); var _a = React.useState(false), needsVerticalScrollBarState = _a[0], setNeedsVerticalScrollBar = _a[1]; React.useEffect(function () { async.requestAnimationFrame(function () { var _a; // If overflowY is overridden, don't waste time calculating whether the scrollbar is necessary. if (props.style && props.style.overflowY) { return; } var needsVerticalScrollBar = false; if (root && root.current && ((_a = root.current) === null || _a === void 0 ? void 0 : _a.firstElementChild)) { // ClientHeight returns the client height of an element rounded to an // integer. On some browsers at different zoom levels this rounding // can generate different results for the root container and child even // though they are the same height. This causes us to show a scroll bar // when not needed. Ideally we would use BoundingClientRect().height // instead however seems that the API is 90% slower than using ClientHeight. // Therefore instead we will calculate the difference between heights and // allow for a 1px difference to still be considered ok and not show the // scroll bar. var rootHeight = root.current.clientHeight; var firstChildHeight = root.current.firstElementChild.clientHeight; if (rootHeight > 0 && firstChildHeight > rootHeight) { needsVerticalScrollBar = firstChildHeight - rootHeight > 1; } } if (needsVerticalScrollBarState !== needsVerticalScrollBar) { setNeedsVerticalScrollBar(needsVerticalScrollBar); } }); return function () { return async.dispose(); }; }); return needsVerticalScrollBarState; } function defaultFocusRestorer(options) { var originalElement = options.originalElement, containsFocus = options.containsFocus; if (originalElement && containsFocus && originalElement !== (0, Utilities_1.getWindow)()) { // Make sure that the focus method actually exists // In some cases the object might exist but not be a real element. // This is primarily for IE 11 and should be removed once IE 11 is no longer in use. // This is wrapped in a setTimeout because of a React 16 bug that is resolved in 17. // Once we move to 17, the setTimeout should be removed (ref: https://github.com/facebook/react/issues/17894#issuecomment-656094405) setTimeout(function () { var _a; (_a = originalElement.focus) === null || _a === void 0 ? void 0 : _a.call(originalElement); }, 0); } } function useRestoreFocus(props, root) { var _a = props.onRestoreFocus, onRestoreFocus = _a === void 0 ? defaultFocusRestorer : _a; var originalFocusedElement = React.useRef(undefined); var containsFocus = React.useRef(false); React.useEffect(function () { originalFocusedElement.current = (0, Utilities_1.getDocument)().activeElement; if ((0, Utilities_1.doesElementContainFocus)(root.current)) { containsFocus.current = true; } return function () { var _a; onRestoreFocus === null || onRestoreFocus === void 0 ? void 0 : onRestoreFocus({ originalElement: originalFocusedElement.current, containsFocus: containsFocus.current, documentContainsFocus: ((_a = (0, Utilities_1.getDocument)()) === null || _a === void 0 ? void 0 : _a.hasFocus()) || false, }); // De-reference DOM Node to avoid retainment via transpiled closure of _onKeyDown originalFocusedElement.current = undefined; }; // eslint-disable-next-line react-hooks/exhaustive-deps -- should only run on first render }, []); (0, react_hooks_1.useOnEvent)(root, 'focus', React.useCallback(function () { containsFocus.current = true; }, []), true); (0, react_hooks_1.useOnEvent)(root, 'blur', React.useCallback(function (ev) { /** The popup should update this._containsFocus when: * relatedTarget exists AND * the relatedTarget is not contained within the popup. * If the relatedTarget is within the popup, that means the popup still has focus * and focused moved from one element to another within the popup. * If relatedTarget is undefined or null that usually means that a * keyboard event occurred and focus didn't change */ if (root.current && ev.relatedTarget && !root.current.contains(ev.relatedTarget)) { containsFocus.current = false; } // eslint-disable-next-line react-hooks/exhaustive-deps -- should only run on first render }, []), true); } function useHideSiblingNodes(props, root) { // eslint-disable-next-line @typescript-eslint/no-deprecated var shouldHideSiblings = String(props['aria-modal']).toLowerCase() === 'true' && props.enableAriaHiddenSiblings; React.useEffect(function () { if (!(shouldHideSiblings && root.current)) { return; } var unmodalize = (0, Utilities_1.modalize)(root.current); return unmodalize; }, [root, shouldHideSiblings]); } /** * This adds accessibility to Dialog and Panel controls */ exports.Popup = React.forwardRef(function (propsWithoutDefaults, forwardedRef) { var props = (0, Utilities_1.getPropsWithDefaults)({ shouldRestoreFocus: true, enableAriaHiddenSiblings: true }, propsWithoutDefaults); var root = React.useRef(undefined); var mergedRootRef = (0, react_hooks_1.useMergedRefs)(root, forwardedRef); useHideSiblingNodes(props, root); useRestoreFocus(props, root); var role = props.role, className = props.className, ariaLabel = props.ariaLabel, ariaLabelledBy = props.ariaLabelledBy, ariaDescribedBy = props.ariaDescribedBy, style = props.style, children = props.children, onDismiss = props.onDismiss; var needsVerticalScrollBar = useScrollbarAsync(props, root); var onKeyDown = React.useCallback(function (ev) { // eslint-disable-next-line @typescript-eslint/no-deprecated switch (ev.which) { case Utilities_1.KeyCodes.escape: if (onDismiss) { onDismiss(ev); ev.preventDefault(); ev.stopPropagation(); } break; } }, [onDismiss]); var win = (0, react_window_provider_1.useWindow)(); (0, react_hooks_1.useOnEvent)(win, 'keydown', onKeyDown); return (React.createElement("div", tslib_1.__assign({ ref: mergedRootRef }, (0, Utilities_1.getNativeProps)(props, Utilities_1.divProperties), { className: className, role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, onKeyDown: onKeyDown, style: tslib_1.__assign({ overflowY: needsVerticalScrollBar ? 'scroll' : undefined, outline: 'none' }, style) }), children)); }); exports.Popup.displayName = 'Popup'; }); //# sourceMappingURL=Popup.js.map