UNPKG

@fluentui/react

Version:

Reusable React components for building web experiences.

209 lines 10.2 kB
import { __assign } from "tslib"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore "react-portal-compat-context" uses v9 configs via path aliases import { usePortalCompat } from '@fluentui/react-portal-compat-context'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Fabric } from '../../Fabric'; import { classNamesFunction, css, getDocument, setPortalAttribute, setVirtualParent, FocusRectsProvider, FocusRectsContext, IsFocusVisibleClassName, } from '../../Utilities'; import { registerLayer, getDefaultTarget, unregisterLayer, getLayerHost, createDefaultLayerHost, } from './Layer.notification'; import { useIsomorphicLayoutEffect, useMergedRefs, useWarnings } from '@fluentui/react-hooks'; var getClassNames = classNamesFunction(); var getFocusVisibility = function (providerRef) { if (providerRef === null || providerRef === void 0 ? void 0 : providerRef.current) { return providerRef.current.classList.contains(IsFocusVisibleClassName); } return false; }; export var LayerBase = React.forwardRef(function (props, ref) { var registerPortalEl = usePortalCompat(); var rootRef = React.useRef(null); var mergedRef = useMergedRefs(rootRef, ref); var layerRef = React.useRef(); var fabricElementRef = React.useRef(null); var focusContext = React.useContext(FocusRectsContext); // Tracks if the layer mount events need to be raised. // Required to allow the DOM to render after the layer element is added. var _a = React.useState(false), needRaiseLayerMount = _a[0], setNeedRaiseLayerMount = _a[1]; // Sets the focus visible className when the FocusRectsProvider for the layer is rendered // This allows the current focus visibility style to be carried over to the layer content var focusRectsRef = React.useCallback(function (el) { var isFocusVisible = getFocusVisibility(focusContext === null || focusContext === void 0 ? void 0 : focusContext.providerRef); if (el && isFocusVisible) { el.classList.add(IsFocusVisibleClassName); } }, [focusContext]); var children = props.children, className = props.className, eventBubblingEnabled = props.eventBubblingEnabled, fabricProps = props.fabricProps, hostId = props.hostId, insertFirst = props.insertFirst, _b = props.onLayerDidMount, onLayerDidMount = _b === void 0 ? function () { return undefined; } : _b, // eslint-disable-next-line deprecation/deprecation _c = props.onLayerMounted, // eslint-disable-next-line deprecation/deprecation onLayerMounted = _c === void 0 ? function () { return undefined; } : _c, onLayerWillUnmount = props.onLayerWillUnmount, styles = props.styles, theme = props.theme; var fabricRef = useMergedRefs(fabricElementRef, fabricProps === null || fabricProps === void 0 ? void 0 : fabricProps.ref, focusRectsRef); var classNames = getClassNames(styles, { theme: theme, className: className, isNotHost: !hostId, }); // Returns the user provided hostId props element, the default target selector, // or undefined if document doesn't exist. var getHost = function (doc, shadowRoot) { var _a, _b; if (shadowRoot === void 0) { shadowRoot = null; } var root = shadowRoot !== null && shadowRoot !== void 0 ? shadowRoot : doc; if (hostId) { var layerHost = getLayerHost(hostId); if (layerHost) { return (_a = layerHost.rootRef.current) !== null && _a !== void 0 ? _a : null; } return (_b = root.getElementById(hostId)) !== null && _b !== void 0 ? _b : null; } else { var defaultHostSelector = getDefaultTarget(); // Find the host. var host = defaultHostSelector ? root.querySelector(defaultHostSelector) : null; // If no host is available, create a container for injecting layers in. // Having a container scopes layout computation. if (!host) { host = createDefaultLayerHost(doc, shadowRoot); } return host; } }; // Removes the current layer element's parentNode and runs onLayerWillUnmount prop if provided. var removeLayerElement = function () { onLayerWillUnmount === null || onLayerWillUnmount === void 0 ? void 0 : onLayerWillUnmount(); var elem = layerRef.current; // Clear ref before removing from the DOM layerRef.current = undefined; if (elem && elem.parentNode) { elem.parentNode.removeChild(elem); } }; // If a doc or host exists, it will remove and update layer parentNodes. var createLayerElement = function () { var _a, _b, _c, _d; var doc = getDocument(rootRef.current); var shadowRoot = ((_b = (_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.getRootNode()) === null || _b === void 0 ? void 0 : _b.host) ? (_c = rootRef === null || rootRef === void 0 ? void 0 : rootRef.current) === null || _c === void 0 ? void 0 : _c.getRootNode() : undefined; if (!doc || (!doc && !shadowRoot)) { return; } var host = getHost(doc, shadowRoot); if (!host) { return; } // Tabster in V9 sets aria-hidden on the elements outside of the modal dialog. And it doesn't set aria-hidden // on the virtual children of the dialog. But the host element itself is not a virtual child of a dialog, it // might contain virtual children. noDirectAriaHidden flag makes Tabster to poke inside the element and set // aria-hidden on the children (if they are not virtual children of the active V9 dialog) not on the host element. // To avoid importing Tabster as a dependency here, we just set a flag on the host element which is checked by // Tabster. if (!host.__tabsterElementFlags) { host.__tabsterElementFlags = {}; } host.__tabsterElementFlags.noDirectAriaHidden = true; // Remove and re-create any previous existing layer elements. removeLayerElement(); var el = ((_d = host.ownerDocument) !== null && _d !== void 0 ? _d : doc).createElement('div'); el.className = classNames.root; setPortalAttribute(el); setVirtualParent(el, rootRef.current); insertFirst ? host.insertBefore(el, host.firstChild) : host.appendChild(el); layerRef.current = el; setNeedRaiseLayerMount(true); }; useIsomorphicLayoutEffect(function () { createLayerElement(); // Check if the user provided a hostId prop and register the layer with the ID. if (hostId) { registerLayer(hostId, createLayerElement); } var unregisterPortalEl = layerRef.current ? registerPortalEl(layerRef.current) : undefined; return function () { if (unregisterPortalEl) { unregisterPortalEl(); } removeLayerElement(); if (hostId) { unregisterLayer(hostId, createLayerElement); } }; // eslint-disable-next-line react-hooks/exhaustive-deps -- should run if the hostId updates. }, [hostId]); React.useEffect(function () { if (layerRef.current && needRaiseLayerMount) { onLayerMounted === null || onLayerMounted === void 0 ? void 0 : onLayerMounted(); onLayerDidMount === null || onLayerDidMount === void 0 ? void 0 : onLayerDidMount(); setNeedRaiseLayerMount(false); } }, [needRaiseLayerMount, onLayerMounted, onLayerDidMount]); useDebugWarnings(props); return (React.createElement("span", { className: "ms-layer", ref: mergedRef }, layerRef.current && ReactDOM.createPortal(React.createElement(FocusRectsProvider, { layerRoot: true, providerRef: fabricRef }, React.createElement(Fabric, __assign({}, (!eventBubblingEnabled && getFilteredEvents()), fabricProps, { className: css(classNames.content, fabricProps === null || fabricProps === void 0 ? void 0 : fabricProps.className), ref: fabricRef }), children)), layerRef.current))); }); LayerBase.displayName = 'LayerBase'; var filteredEventProps; var onFilterEvent = function (ev) { // We should just be able to check ev.bubble here and only stop events that are bubbling up. However, even though // mouseenter and mouseleave do NOT bubble up, they are showing up as bubbling. Therefore we stop events based on // event name rather than ev.bubble. if (ev.eventPhase === Event.BUBBLING_PHASE && ev.type !== 'mouseenter' && ev.type !== 'mouseleave' && ev.type !== 'touchstart' && ev.type !== 'touchend') { ev.stopPropagation(); } }; function getFilteredEvents() { if (!filteredEventProps) { filteredEventProps = {}; [ 'onClick', 'onContextMenu', 'onDoubleClick', 'onDrag', 'onDragEnd', 'onDragEnter', 'onDragExit', 'onDragLeave', 'onDragOver', 'onDragStart', 'onDrop', 'onMouseDown', 'onMouseEnter', 'onMouseLeave', 'onMouseMove', 'onMouseOver', 'onMouseOut', 'onMouseUp', 'onTouchMove', 'onTouchStart', 'onTouchCancel', 'onTouchEnd', 'onKeyDown', 'onKeyPress', 'onKeyUp', 'onFocus', 'onBlur', 'onChange', 'onInput', 'onInvalid', 'onSubmit', ].forEach(function (name) { return (filteredEventProps[name] = onFilterEvent); }); } return filteredEventProps; } function useDebugWarnings(props) { if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line react-hooks/rules-of-hooks -- build-time conditional useWarnings({ name: 'Layer', props: props, deprecations: { onLayerMounted: 'onLayerDidMount' }, }); } } //# sourceMappingURL=Layer.base.js.map