209 lines
10 KiB
JavaScript
209 lines
10 KiB
JavaScript
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(undefined);
|
|
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 @typescript-eslint/no-deprecated
|
|
_c = props.onLayerMounted,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
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
|