Private
Public Access
1
0

feat: Fluent UI Outlook Lite + connections mockup

This commit is contained in:
2026-04-14 18:52:25 +00:00
parent 1199eff6c3
commit dfa4010406
34820 changed files with 1003813 additions and 205 deletions

2347
node_modules/@fluentui/react-portal/CHANGELOG.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

15
node_modules/@fluentui/react-portal/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,15 @@
@fluentui/react-portal
Copyright (c) Microsoft Corporation
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Note: Usage of the fonts and icons referenced in Fluent UI React is subject to the terms listed at https://aka.ms/fluentui-assets-license

145
node_modules/@fluentui/react-portal/README.md generated vendored Normal file
View File

@@ -0,0 +1,145 @@
# @fluentui/react-portal
**React Portal components for [Fluent UI React](https://react.fluentui.dev)**
This package contains the `Portal` component, which allow consumers to render [React portals](https://reactjs.org/docs/portals.html) with Fluent styling and RTL awareness.
## Usage
### Portal
`Portal` can be used as standalone with any part of a Fluent app. The component should be under a `FluentProvider` in the tree to make sure that proper theming and RTL handling is available.
By default `Portal` will render content to `document body`
```tsx
<FluentProvider>
<Portal>Content rendered by default to Fluent's document.body</Portal>
</FluentProvider>
```
The mount location of the portal can be customized
```tsx
const node = document.getElementById('customNode');
<Portal mountNode={node}>Render to a custom node in DOM</Portal>;
```
### Styling
`Portal` renders React children directly to the default/configured DOM node. Therefore styling should be applied to the `children` by users directly.
### Virtual parents
Out of order DOM elements can be problematic when using 'click outside' event listeners since you cannot rely on `element.contains(event.target)` because the `Portal` elements are out of DOM order.
```tsx
const outerButtonRef = React.useRef();
const innerButtonRef = React.useRef();
<Portal>
<div>
<button ref={outerButtonRef}> Outer button </button>
<Portal>
<div>
<button ref={innerButtonRef}> Inner button </button>
</div>
</Portal>
</div>
</Portal>
// DOM output
<div>
<button>Outer button</button>
</div>
<div>
<button>Inner button</button>
</div>
// Let's add an event listener to 'dismss' the outer portal when clicked outside
// ⚠⚠⚠ This will always be called when clicking on the inner button
document.addEventListener((event) => {
if (outerButtonRef.current.contains(event.target)) {
dismissOuterPortal();
}
})
```
When the above case is not required, using `element.contains` is perfectly fine. But nested cases should still be handled appropriately. We do this using the concept of `virtual parents`
`Portal` will make public 2 utilities that will only be used in cases where the user needs to know if an out of order DOM element will need to be used or not.
- `setVirtualParent` - sets virtual parent. Portal uses this already internally.
- `elementContains` - similar to `element.contains` but uses the virtual hierarchy as reference
Below shows what a virtual parent is
```tsx
// Setting a virtual parent
const parent = document.getElementById('parent');
const child = document.getElementById('child');
child._virtual.parent = parent;
```
`Portals` will render a hidden span that will be the virtual parent, by nesting portals virtual parens will also be nested so that `elementContains` will work predictably.
```tsx
<FluentProvider>
<Portal id="portal-1" />
<Portal id="portal-2" />
</FluentProvider>
```
DOM output:
```tsx
<body>
<div>
{/* Virtual parent for portal*/}
<span aria-hidden />
{/* Virtual parent for portal*/}
<span aria-hidden />
</div>
<div id="portal-1" class="theme-provider-0">
{children}
</div>
<div id="portal-2" class="theme-provider-0">
{children}
</div>
</body>
```
```tsx
<FluentProvider>
<Portal id="portal-1">
<Portal id="portal-2" />
</Portal>
</FluentProvider>
```
DOM output:
```tsx
<body>
<div>
{/* Virtual parent for outer portal*/}
<span aria-hidden></span>
</div>
<div id="portal-1" class="theme-provider-0">
{/* Virtual parent for inner portal*/}
<span aria-hidden />
{children}
</div>
<div id="portal-2" class="theme-provider-0">
{children}
</div>
</body>
```

63
node_modules/@fluentui/react-portal/dist/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,63 @@
import { elementContains } from '@fluentui/react-utilities';
import * as React_2 from 'react';
import { setVirtualParent } from '@fluentui/react-utilities';
export { elementContains }
/**
* A portal provides a way to render children into a DOM node
* that exists outside the DOM hierarchy of the parent component.
*/
export declare const Portal: React_2.FC<PortalProps>;
export declare type PortalProps = {
/**
* React children
*/
children?: React_2.ReactNode;
/**
* Where the portal children are mounted on DOM
*
* @default a new element on document.body without any styling
*/
mountNode?: HTMLElement | null | {
element?: HTMLElement | null;
className?: string;
};
};
export declare type PortalState = Pick<PortalProps, 'children'> & {
mountNode: HTMLElement | null | undefined;
/**
* Ref to the root span element as virtual parent
*/
virtualParentRootRef: React_2.MutableRefObject<HTMLSpanElement | null>;
};
/**
* Render the final JSX of Portal
*/
export declare const renderPortal_unstable: (state: PortalState) => React_2.ReactElement;
export { setVirtualParent }
/**
* The function that normalizes the `mountNode` prop into an object with element and className props.
*
* @param mountNode - an HTML element or an object with props
*/
export declare function toMountNodeProps(mountNode: PortalProps['mountNode']): {
element?: HTMLElement | null;
className?: string;
};
/**
* Create the state required to render Portal.
*
* The returned state can be modified with hooks such as usePortalStyles, before being passed to renderPortal_unstable.
*
* @param props - props from this instance of Portal
*/
export declare const usePortal_unstable: (props: PortalProps) => PortalState;
export { }

View File

@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
Portal: function() {
return _index.Portal;
},
renderPortal_unstable: function() {
return _index.renderPortal_unstable;
},
usePortal_unstable: function() {
return _index.usePortal_unstable;
}
});
const _index = require("./components/Portal/index");

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/Portal.ts"],"sourcesContent":["export type { PortalProps, PortalState } from './components/Portal/index';\nexport { Portal, renderPortal_unstable, usePortal_unstable } from './components/Portal/index';\n"],"names":["Portal","renderPortal_unstable","usePortal_unstable"],"mappings":";;;;;;;;;;;;eACSA,aAAM;;;eAAEC,4BAAqB;;;eAAEC,yBAAkB;;;uBAAQ,4BAA4B"}

View File

@@ -0,0 +1,20 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "Portal", {
enumerable: true,
get: function() {
return Portal;
}
});
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
const _usePortal = require("./usePortal");
const _renderPortal = require("./renderPortal");
const Portal = (props)=>{
const state = (0, _usePortal.usePortal_unstable)(props);
return (0, _renderPortal.renderPortal_unstable)(state);
};
Portal.displayName = 'Portal';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/Portal.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { usePortal_unstable } from './usePortal';\nimport { renderPortal_unstable } from './renderPortal';\nimport type { PortalProps } from './Portal.types';\n\n/**\n * A portal provides a way to render children into a DOM node\n * that exists outside the DOM hierarchy of the parent component.\n */\nexport const Portal: React.FC<PortalProps> = props => {\n const state = usePortal_unstable(props);\n\n return renderPortal_unstable(state);\n};\n\nPortal.displayName = 'Portal';\n"],"names":["React","usePortal_unstable","renderPortal_unstable","Portal","props","state","displayName"],"mappings":"AAAA;;;;;;;;;;;;iEAEuB,QAAQ;2BAEI,cAAc;8BACX,iBAAiB;AAOhD,MAAMG,SAAgCC,CAAAA;IAC3C,MAAMC,YAAQJ,6BAAAA,EAAmBG;IAEjC,WAAOF,mCAAAA,EAAsBG;AAC/B,EAAE;AAEFF,OAAOG,WAAW,GAAG"}

View File

@@ -0,0 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/Portal.types.ts"],"sourcesContent":["import * as React from 'react';\n\nexport type PortalProps = {\n /**\n * React children\n */\n children?: React.ReactNode;\n\n /**\n * Where the portal children are mounted on DOM\n *\n * @default a new element on document.body without any styling\n */\n mountNode?: HTMLElement | null | { element?: HTMLElement | null; className?: string };\n};\n\nexport type PortalState = Pick<PortalProps, 'children'> & {\n mountNode: HTMLElement | null | undefined;\n\n /**\n * Ref to the root span element as virtual parent\n */\n virtualParentRootRef: // eslint-disable-next-line @typescript-eslint/no-deprecated\n React.MutableRefObject<HTMLSpanElement | null>;\n};\n"],"names":["React"],"mappings":";;;;;iEAAuB,QAAQ"}

View File

@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
Portal: function() {
return _Portal.Portal;
},
renderPortal_unstable: function() {
return _renderPortal.renderPortal_unstable;
},
usePortal_unstable: function() {
return _usePortal.usePortal_unstable;
}
});
const _Portal = require("./Portal");
const _renderPortal = require("./renderPortal");
const _usePortal = require("./usePortal");

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/index.ts"],"sourcesContent":["export { Portal } from './Portal';\nexport type { PortalProps, PortalState } from './Portal.types';\nexport { renderPortal_unstable } from './renderPortal';\nexport { usePortal_unstable } from './usePortal';\n"],"names":["Portal","renderPortal_unstable","usePortal_unstable"],"mappings":";;;;;;;;;;;;eAASA,cAAM;;;eAENC,mCAAqB;;;eACrBC,6BAAkB;;;wBAHJ,WAAW;8BAEI,iBAAiB;2BACpB,cAAc"}

View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "renderPortal_unstable", {
enumerable: true,
get: function() {
return renderPortal_unstable;
}
});
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
const _reactdom = /*#__PURE__*/ _interop_require_wildcard._(require("react-dom"));
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
const renderPortal_unstable = (state)=>{
return /*#__PURE__*/ _react.createElement("span", {
hidden: true,
ref: state.virtualParentRootRef
}, state.mountNode && /*#__PURE__*/ _reactdom.createPortal(/*#__PURE__*/ _react.createElement(_react.Fragment, null, state.children, /*#__PURE__*/ _react.createElement("span", {
hidden: true
})), state.mountNode));
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/renderPortal.tsx"],"sourcesContent":["import * as ReactDOM from 'react-dom';\nimport * as React from 'react';\nimport type { PortalState } from './Portal.types';\n\n/**\n * Render the final JSX of Portal\n */\nexport const renderPortal_unstable = (state: PortalState): React.ReactElement => {\n return (\n <span hidden ref={state.virtualParentRootRef}>\n {state.mountNode &&\n ReactDOM.createPortal(\n <>\n {state.children}\n {/* Heads up!\n * This node exists only to ensure that the portal is not empty as we rely on that in `usePortalMountNode`\n * hook for React 18+.\n */}\n <span hidden />\n </>,\n state.mountNode,\n )}\n </span>\n );\n};\n"],"names":["ReactDOM","React","renderPortal_unstable","state","span","hidden","ref","virtualParentRootRef","mountNode","createPortal","children"],"mappings":";;;;+BAOaE;;;;;;;oEAPa,YAAY;iEACf,QAAQ;AAMxB,8BAA8B,CAACC;IACpC,OAAA,WAAA,GACE,OAAA,aAAA,CAACC,QAAAA;QAAKC,QAAAA;QAAOC,KAAKH,MAAMI,oBAAoB;OACzCJ,MAAMK,SAAS,IAAA,WAAA,GACdR,UAASS,YAAY,CAAA,WAAA,GACnB,OAAA,aAAA,CAAA,OAAA,QAAA,EAAA,MACGN,MAAMO,QAAQ,EAAA,WAAA,GAKf,OAAA,aAAA,CAACN,QAAAA;QAAKC,QAAAA;SAERF,MAAMK,SAAS;AAIzB,EAAE"}

View File

@@ -0,0 +1,86 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "usePortal_unstable", {
enumerable: true,
get: function() {
return usePortal_unstable;
}
});
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
const _reactutilities = require("@fluentui/react-utilities");
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
const _toMountNodeProps = require("../../utils/toMountNodeProps");
const _usePortalMountNode = require("./usePortalMountNode");
const usePortal_unstable = (props)=>{
const { element, className } = (0, _toMountNodeProps.toMountNodeProps)(props.mountNode);
const virtualParentRootRef = _react.useRef(null);
const fallbackElement = (0, _usePortalMountNode.usePortalMountNode)({
disabled: !!element,
className
});
const mountNode = element !== null && element !== void 0 ? element : fallbackElement;
const state = {
children: props.children,
mountNode,
virtualParentRootRef
};
_react.useEffect(()=>{
if (!mountNode) {
return;
}
const virtualParent = virtualParentRootRef.current;
// By default, we create a mount node for portal on `document.body` (see usePortalMountNode()) and have following structure:
//
// <body>
// <!-- ⚛️ application root -->
// <div id="root">
// <!-- ⬇️ portal node rendered in a tree to anchor (virtual parent node) -->
// <span aria-hidden="true"></span>
// </div>
// <div id="portal-mount-node">
// <!-- 🧩portal content -->
// </div>
// </body>
//
// To make sure that `.elementContains()` works correctly, we link a virtual parent to a portal node (a virtual parent node becomes a parent of mount node):
// virtual.contains(mountNode) === false
// (while we need ⬇️⬇️⬇️)
// elementsContains(virtualParent, mountNode) === true
// elementsContains(mountNode, virtualParent) === false
//
// For more details, check docs for virtual parent utils.
//
// However, if a user provides a custom mount node (via `props`) the structure could be different:
//
// <body>
// <!-- application root -->
// <div id="root">
// <div id="portal-mount-node">
// <!-- 🧩portal content -->
//
// <span aria-hidden="true"></span>
// </div>
// </div>
// </body>
//
// A mount node in this case contains portal's content and a virtual parent node. In this case nodes linking is redundant and the check below avoids it.
//
// Otherwise, there is a circular reference - both elements are parents of each other:
// elementsContains(mountNode, virtualParent) === true
// elementsContains(virtualParent, mountNode) === true
const isVirtualParentInsideChild = mountNode.contains(virtualParent);
if (virtualParent && !isVirtualParentInsideChild) {
(0, _reactutilities.setVirtualParent)(mountNode, virtualParent);
return ()=>{
(0, _reactutilities.setVirtualParent)(mountNode, undefined);
};
}
}, [
virtualParentRootRef,
mountNode
]);
return state;
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/usePortal.ts"],"sourcesContent":["'use client';\n\nimport { setVirtualParent } from '@fluentui/react-utilities';\nimport * as React from 'react';\n\nimport { toMountNodeProps } from '../../utils/toMountNodeProps';\nimport { usePortalMountNode } from './usePortalMountNode';\nimport type { PortalProps, PortalState } from './Portal.types';\n\n/**\n * Create the state required to render Portal.\n *\n * The returned state can be modified with hooks such as usePortalStyles, before being passed to renderPortal_unstable.\n *\n * @param props - props from this instance of Portal\n */\nexport const usePortal_unstable = (props: PortalProps): PortalState => {\n const { element, className } = toMountNodeProps(props.mountNode);\n\n const virtualParentRootRef = React.useRef<HTMLSpanElement>(null);\n const fallbackElement = usePortalMountNode({ disabled: !!element, className });\n\n const mountNode = element ?? fallbackElement;\n const state: PortalState = {\n children: props.children,\n mountNode,\n virtualParentRootRef,\n };\n\n React.useEffect(() => {\n if (!mountNode) {\n return;\n }\n\n const virtualParent = virtualParentRootRef.current;\n\n // By default, we create a mount node for portal on `document.body` (see usePortalMountNode()) and have following structure:\n //\n // <body>\n // <!-- ⚛️ application root -->\n // <div id=\"root\">\n // <!-- ⬇️ portal node rendered in a tree to anchor (virtual parent node) -->\n // <span aria-hidden=\"true\"></span>\n // </div>\n // <div id=\"portal-mount-node\">\n // <!-- 🧩portal content -->\n // </div>\n // </body>\n //\n // To make sure that `.elementContains()` works correctly, we link a virtual parent to a portal node (a virtual parent node becomes a parent of mount node):\n // virtual.contains(mountNode) === false\n // (while we need ⬇️⬇️⬇️)\n // elementsContains(virtualParent, mountNode) === true\n // elementsContains(mountNode, virtualParent) === false\n //\n // For more details, check docs for virtual parent utils.\n //\n // However, if a user provides a custom mount node (via `props`) the structure could be different:\n //\n // <body>\n // <!-- application root -->\n // <div id=\"root\">\n // <div id=\"portal-mount-node\">\n // <!-- 🧩portal content -->\n //\n // <span aria-hidden=\"true\"></span>\n // </div>\n // </div>\n // </body>\n //\n // A mount node in this case contains portal's content and a virtual parent node. In this case nodes linking is redundant and the check below avoids it.\n //\n // Otherwise, there is a circular reference - both elements are parents of each other:\n // elementsContains(mountNode, virtualParent) === true\n // elementsContains(virtualParent, mountNode) === true\n const isVirtualParentInsideChild = mountNode.contains(virtualParent);\n\n if (virtualParent && !isVirtualParentInsideChild) {\n setVirtualParent(mountNode, virtualParent);\n\n return () => {\n setVirtualParent(mountNode, undefined);\n };\n }\n }, [virtualParentRootRef, mountNode]);\n\n return state;\n};\n"],"names":["setVirtualParent","React","toMountNodeProps","usePortalMountNode","usePortal_unstable","props","element","className","mountNode","virtualParentRootRef","useRef","fallbackElement","disabled","state","children","useEffect","virtualParent","current","isVirtualParentInsideChild","contains","undefined"],"mappings":"AAAA;;;;;+BAgBaI;;;;;;;gCAdoB,4BAA4B;iEACtC,QAAQ;kCAEE,+BAA+B;oCAC7B,uBAAuB;AAUnD,2BAA2B,CAACC;IACjC,MAAM,EAAEC,OAAO,EAAEC,SAAS,EAAE,OAAGL,kCAAAA,EAAiBG,MAAMG,SAAS;IAE/D,MAAMC,uBAAuBR,OAAMS,MAAM,CAAkB;IAC3D,MAAMC,sBAAkBR,sCAAAA,EAAmB;QAAES,UAAU,CAAC,CAACN;QAASC;IAAU;IAE5E,MAAMC,YAAYF,YAAAA,QAAAA,YAAAA,KAAAA,IAAAA,UAAWK;IAC7B,MAAME,QAAqB;QACzBC,UAAUT,MAAMS,QAAQ;QACxBN;QACAC;IACF;IAEAR,OAAMc,SAAS,CAAC;QACd,IAAI,CAACP,WAAW;YACd;QACF;QAEA,MAAMQ,gBAAgBP,qBAAqBQ,OAAO;QAElD,4HAA4H;QAC5H,EAAE;QACF,SAAS;QACT,iCAAiC;QACjC,oBAAoB;QACpB,iFAAiF;QACjF,uCAAuC;QACvC,WAAW;QACX,iCAAiC;QACjC,gCAAgC;QAChC,WAAW;QACX,UAAU;QACV,EAAE;QACF,4JAA4J;QAC5J,0CAA0C;QAC1C,2BAA2B;QAC3B,wDAAwD;QACxD,yDAAyD;QACzD,EAAE;QACF,yDAAyD;QACzD,EAAE;QACF,kGAAkG;QAClG,EAAE;QACF,SAAS;QACT,8BAA8B;QAC9B,oBAAoB;QACpB,mCAAmC;QACnC,kCAAkC;QAClC,EAAE;QACF,yCAAyC;QACzC,aAAa;QACb,WAAW;QACX,UAAU;QACV,EAAE;QACF,wJAAwJ;QACxJ,EAAE;QACF,sFAAsF;QACtF,wDAAwD;QACxD,wDAAwD;QACxD,MAAMC,6BAA6BV,UAAUW,QAAQ,CAACH;QAEtD,IAAIA,iBAAiB,CAACE,4BAA4B;YAChDlB,oCAAAA,EAAiBQ,WAAWQ;YAE5B,OAAO;oBACLhB,gCAAAA,EAAiBQ,WAAWY;YAC9B;QACF;IACF,GAAG;QAACX;QAAsBD;KAAU;IAEpC,OAAOK;AACT,EAAE"}

View File

@@ -0,0 +1,212 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "usePortalMountNode", {
enumerable: true,
get: function() {
return usePortalMountNode;
}
});
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
const _reactsharedcontexts = require("@fluentui/react-shared-contexts");
const _react1 = require("@griffel/react");
const _reacttabster = require("@fluentui/react-tabster");
const _usePortalMountNodeStylesstyles = require("./usePortalMountNodeStyles.styles");
const useInsertionEffect = _react['useInsertion' + 'Effect'];
/**
* Legacy element factory for React 17 and below. It's not safe for concurrent rendering.
*
* Creates a new element on a "document.body" to mount portals.
*/ const useLegacyElementFactory = (options)=>{
'use no memo';
const { className, dir, focusVisibleRef, targetNode } = options;
const targetElement = _react.useMemo(()=>{
if (targetNode === undefined || options.disabled) {
return null;
}
const element = targetNode.ownerDocument.createElement('div');
targetNode.appendChild(element);
return element;
}, [
targetNode,
options.disabled
]);
// Heads up!
// This useMemo() call is intentional for React 17 & below.
//
// We don't want to re-create the portal element when its attributes change. This also cannot not be done in an effect
// because, changing the value of CSS variables after an initial mount will trigger interesting CSS side effects like
// transitions.
_react.useMemo(()=>{
if (!targetElement) {
return;
}
targetElement.className = className;
targetElement.setAttribute('dir', dir);
targetElement.setAttribute('data-portal-node', 'true');
focusVisibleRef.current = targetElement;
}, [
className,
dir,
targetElement,
focusVisibleRef
]);
_react.useEffect(()=>{
return ()=>{
targetElement === null || targetElement === void 0 ? void 0 : targetElement.remove();
};
}, [
targetElement
]);
return targetElement;
};
const initializeElementFactory = ()=>{
let currentElement = undefined;
function get(targetRoot, forceCreation) {
if (currentElement) {
return currentElement;
}
if (forceCreation) {
currentElement = targetRoot.ownerDocument.createElement('div');
targetRoot.appendChild(currentElement);
}
return currentElement;
}
function dispose() {
if (currentElement) {
currentElement.remove();
currentElement = undefined;
}
}
return {
get,
dispose
};
};
/**
* This is a modern element factory for React 18 and above. It is safe for concurrent rendering.
*
* It abuses the fact that React will mount DOM once (unlike hooks), so by using a proxy we can intercept:
* - the `remove()` method (we call it in `useEffect()`) and remove the element only when the portal is unmounted
* - all other methods (and properties) will be called by React once a portal is mounted
*/ const useModernElementFactory = (options)=>{
'use no memo';
const { className, dir, focusVisibleRef, targetNode } = options;
const [elementFactory] = _react.useState(initializeElementFactory);
const elementProxy = _react.useMemo(()=>{
if (targetNode === undefined || options.disabled) {
return null;
}
return new Proxy({}, {
get (_, property) {
// Heads up!
// `createPortal()` performs a check for `nodeType` property to determine if the mount node is a valid DOM node
// before mounting the portal. We hardcode the value to `Node.ELEMENT_NODE` to pass this check and avoid
// premature node creation
if (property === 'nodeType') {
// Can't use the `Node.ELEMENT_NODE` as it's a browser API and not available in all environments, e.g SSR
return 1; // `Node.ELEMENT_NODE`
}
// Heads up!
// We intercept the `remove()` method to remove the mount node only when portal has been unmounted already.
if (property === 'remove') {
const targetElement = elementFactory.get(targetNode, false);
if (targetElement) {
// If the mountElement has children, the portal is still mounted, otherwise we can dispose of it
const portalHasNoChildren = targetElement.childNodes.length === 0;
if (portalHasNoChildren) {
elementFactory.dispose();
}
}
return ()=>{
// Always return a no-op function to avoid errors in the code
};
}
const targetElement = elementFactory.get(targetNode, true);
const targetProperty = targetElement ? targetElement[property] : undefined;
if (typeof targetProperty === 'function') {
return targetProperty.bind(targetElement);
}
return targetProperty;
},
set (_, property, value) {
const ignoredProperty = property === '_virtual' || property === 'focusVisible';
// We should use the `elementFactory.get(targetNode, !ignoredProperty)`,
// but TypeScript requires a literal `true` or `false` for the overload signature.
// This workaround ensures the correct overload is called and avoids TypeScript errors.
const targetElement = ignoredProperty ? elementFactory.get(targetNode, false) : elementFactory.get(targetNode, true);
if (ignoredProperty && !targetElement) {
// We ignore the `_virtual` and `focusVisible` properties to avoid conflicts with the proxy
return true;
}
if (targetElement) {
Object.assign(targetElement, {
[property]: value
});
return true;
}
return false;
}
});
}, [
elementFactory,
targetNode,
options.disabled
]);
useInsertionEffect(()=>{
if (!elementProxy) {
return;
}
const classesToApply = className.split(' ').filter(Boolean);
elementProxy.classList.add(...classesToApply);
elementProxy.setAttribute('dir', dir);
elementProxy.setAttribute('data-portal-node', 'true');
focusVisibleRef.current = elementProxy;
return ()=>{
elementProxy.classList.remove(...classesToApply);
elementProxy.removeAttribute('dir');
};
}, [
className,
dir,
elementProxy,
focusVisibleRef
]);
_react.useEffect(()=>{
return ()=>{
elementProxy === null || elementProxy === void 0 ? void 0 : elementProxy.remove();
};
}, [
elementProxy
]);
return elementProxy;
};
/**
* Element factory based on the React version.
*
* React 17 and below:
* - useLegacyElementFactory
*
* React 18 and above:
* - useModernElementFactory
*/ const useElementFactory = useInsertionEffect ? useModernElementFactory : useLegacyElementFactory;
const usePortalMountNode = (options)=>{
'use no memo';
const { targetDocument, dir } = (0, _reactsharedcontexts.useFluent_unstable)();
const mountNode = (0, _reactsharedcontexts.usePortalMountNode)();
// eslint-disable-next-line @typescript-eslint/no-deprecated
const focusVisibleRef = (0, _reacttabster.useFocusVisible)();
const classes = (0, _usePortalMountNodeStylesstyles.usePortalMountNodeStylesStyles)();
const themeClassName = (0, _reactsharedcontexts.useThemeClassName_unstable)();
const factoryOptions = {
dir,
disabled: options.disabled,
focusVisibleRef,
className: (0, _react1.mergeClasses)(themeClassName, classes.root, options.className),
targetNode: mountNode !== null && mountNode !== void 0 ? mountNode : targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.body
};
return useElementFactory(factoryOptions);
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,35 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "usePortalMountNodeStylesStyles", {
enumerable: true,
get: function() {
return usePortalMountNodeStylesStyles;
}
});
const _react = require("@griffel/react");
const usePortalMountNodeStylesStyles = /*#__PURE__*/ (0, _react.__styles)({
root: {
qhf8xq: "f1euv43f",
Bhzewxz: "f15twtuk",
oyh7mz: [
"f1vgc2s3",
"f1e31b4d"
],
j35jbq: [
"f1e31b4d",
"f1vgc2s3"
],
Bj3rh1h: "f494woh"
}
}, {
d: [
".f1euv43f{position:absolute;}",
".f15twtuk{top:0;}",
".f1vgc2s3{left:0;}",
".f1e31b4d{right:0;}",
".f494woh{z-index:1000000;}"
]
});

View File

@@ -0,0 +1 @@
{"version":3,"sources":["usePortalMountNodeStyles.styles.js"],"sourcesContent":["'use client';\nimport { makeStyles } from '@griffel/react';\nexport const usePortalMountNodeStylesStyles = makeStyles({\n root: {\n // Creates new stacking context to prevent z-index issues\n // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context\n //\n // Also keeps a portal on top of a page to prevent scrollbars from appearing\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n zIndex: 1000000\n }\n});\n"],"names":["__styles","usePortalMountNodeStylesStyles","root","qhf8xq","Bhzewxz","oyh7mz","j35jbq","Bj3rh1h","d"],"mappings":"AAAA,YAAY;;;;;;;;;;;uBACe,gBAAgB;AACpC,MAAMC,8BAA8B,GAAA,WAAA,OAAGD,eAAA,EAAA;IAAAE,IAAA,EAAA;QAAAC,MAAA,EAAA;QAAAC,OAAA,EAAA;QAAAC,MAAA,EAAA;YAAA;YAAA;SAAA;QAAAC,MAAA,EAAA;YAAA;YAAA;SAAA;QAAAC,OAAA,EAAA;IAAA;AAAA,GAAA;IAAAC,CAAA,EAAA;QAAA;QAAA;QAAA;QAAA;QAAA;KAAA;AAAA,CAY7C,CAAC"}

View File

@@ -0,0 +1,25 @@
'use client';
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "usePortalMountNodeStylesStyles", {
enumerable: true,
get: function() {
return usePortalMountNodeStylesStyles;
}
});
const _react = require("@griffel/react");
const usePortalMountNodeStylesStyles = (0, _react.makeStyles)({
root: {
// Creates new stacking context to prevent z-index issues
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context
//
// Also keeps a portal on top of a page to prevent scrollbars from appearing
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 1000000
}
});

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/usePortalMountNodeStyles.styles.ts"],"sourcesContent":["'use client';\n\nimport { makeStyles } from '@griffel/react';\n\nexport const usePortalMountNodeStylesStyles = makeStyles({\n root: {\n // Creates new stacking context to prevent z-index issues\n // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context\n //\n // Also keeps a portal on top of a page to prevent scrollbars from appearing\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n\n zIndex: 1000000,\n },\n});\n"],"names":["makeStyles","usePortalMountNodeStylesStyles","root","position","top","left","right","zIndex"],"mappings":"AAAA;;;;;;;;;;;uBAE2B,iBAAiB;AAErC,MAAMC,qCAAiCD,iBAAAA,EAAW;IACvDE,MAAM;QACJ,yDAAyD;QACzD,gHAAgH;QAChH,EAAE;QACF,4EAA4E;QAC5EC,UAAU;QACVC,KAAK;QACLC,MAAM;QACNC,OAAO;QAEPC,QAAQ;IACV;AACF,GAAG"}

View File

@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
Portal: function() {
return _index.Portal;
},
elementContains: function() {
return _reactutilities.elementContains;
},
renderPortal_unstable: function() {
return _index.renderPortal_unstable;
},
setVirtualParent: function() {
return _reactutilities.setVirtualParent;
},
toMountNodeProps: function() {
return _toMountNodeProps.toMountNodeProps;
},
usePortal_unstable: function() {
return _index.usePortal_unstable;
}
});
const _reactutilities = require("@fluentui/react-utilities");
const _index = require("./components/Portal/index");
const _toMountNodeProps = require("./utils/toMountNodeProps");

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { elementContains, setVirtualParent } from '@fluentui/react-utilities';\nexport { Portal, renderPortal_unstable, usePortal_unstable } from './components/Portal/index';\nexport type { PortalProps, PortalState } from './components/Portal/index';\n\nexport { toMountNodeProps } from './utils/toMountNodeProps';\n"],"names":["elementContains","setVirtualParent","Portal","renderPortal_unstable","usePortal_unstable","toMountNodeProps"],"mappings":";;;;;;;;;;;;eACSE,aAAM;;;eADNF,+BAAe;;;eACPG,4BAAqB;;;eADZF,gCAAgB;;;eAIjCI,kCAAgB;;;eAHeD,yBAAkB;;;gCADR,4BAA4B;uBACZ,4BAA4B;kCAG7D,2BAA2B"}

View File

@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "toMountNodeProps", {
enumerable: true,
get: function() {
return toMountNodeProps;
}
});
const _reactutilities = require("@fluentui/react-utilities");
function toMountNodeProps(mountNode) {
if ((0, _reactutilities.isHTMLElement)(mountNode)) {
return {
element: mountNode
};
}
if (typeof mountNode === 'object') {
if (mountNode === null) {
return {
element: null
};
}
return mountNode;
}
return {};
}

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/utils/toMountNodeProps.ts"],"sourcesContent":["import { isHTMLElement } from '@fluentui/react-utilities';\nimport type { PortalProps } from '../Portal';\n\n/**\n * The function that normalizes the `mountNode` prop into an object with element and className props.\n *\n * @param mountNode - an HTML element or an object with props\n */\nexport function toMountNodeProps(mountNode: PortalProps['mountNode']): {\n element?: HTMLElement | null;\n className?: string;\n} {\n if (isHTMLElement(mountNode)) {\n return { element: mountNode };\n }\n\n if (typeof mountNode === 'object') {\n if (mountNode === null) {\n return { element: null };\n }\n\n return mountNode;\n }\n\n return {};\n}\n"],"names":["isHTMLElement","toMountNodeProps","mountNode","element"],"mappings":";;;;+BAQgBC;;;;;;gCARc,4BAA4B;AAQnD,0BAA0BC,SAAmC;IAIlE,QAAIF,6BAAAA,EAAcE,YAAY;QAC5B,OAAO;YAAEC,SAASD;QAAU;IAC9B;IAEA,IAAI,OAAOA,cAAc,UAAU;QACjC,IAAIA,cAAc,MAAM;YACtB,OAAO;gBAAEC,SAAS;YAAK;QACzB;QAEA,OAAOD;IACT;IAEA,OAAO,CAAC;AACV"}

1
node_modules/@fluentui/react-portal/lib/Portal.js generated vendored Normal file
View File

@@ -0,0 +1 @@
export { Portal, renderPortal_unstable, usePortal_unstable } from './components/Portal/index';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/Portal.ts"],"sourcesContent":["export type { PortalProps, PortalState } from './components/Portal/index';\nexport { Portal, renderPortal_unstable, usePortal_unstable } from './components/Portal/index';\n"],"names":["Portal","renderPortal_unstable","usePortal_unstable"],"mappings":"AACA,SAASA,MAAM,EAAEC,qBAAqB,EAAEC,kBAAkB,QAAQ,4BAA4B"}

View File

@@ -0,0 +1,12 @@
'use client';
import * as React from 'react';
import { usePortal_unstable } from './usePortal';
import { renderPortal_unstable } from './renderPortal';
/**
* A portal provides a way to render children into a DOM node
* that exists outside the DOM hierarchy of the parent component.
*/ export const Portal = (props)=>{
const state = usePortal_unstable(props);
return renderPortal_unstable(state);
};
Portal.displayName = 'Portal';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/Portal.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\n\nimport { usePortal_unstable } from './usePortal';\nimport { renderPortal_unstable } from './renderPortal';\nimport type { PortalProps } from './Portal.types';\n\n/**\n * A portal provides a way to render children into a DOM node\n * that exists outside the DOM hierarchy of the parent component.\n */\nexport const Portal: React.FC<PortalProps> = props => {\n const state = usePortal_unstable(props);\n\n return renderPortal_unstable(state);\n};\n\nPortal.displayName = 'Portal';\n"],"names":["React","usePortal_unstable","renderPortal_unstable","Portal","props","state","displayName"],"mappings":"AAAA;AAEA,YAAYA,WAAW,QAAQ;AAE/B,SAASC,kBAAkB,QAAQ,cAAc;AACjD,SAASC,qBAAqB,QAAQ,iBAAiB;AAGvD;;;CAGC,GACD,OAAO,MAAMC,SAAgCC,CAAAA;IAC3C,MAAMC,QAAQJ,mBAAmBG;IAEjC,OAAOF,sBAAsBG;AAC/B,EAAE;AAEFF,OAAOG,WAAW,GAAG"}

View File

@@ -0,0 +1 @@
import * as React from 'react';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/Portal.types.ts"],"sourcesContent":["import * as React from 'react';\n\nexport type PortalProps = {\n /**\n * React children\n */\n children?: React.ReactNode;\n\n /**\n * Where the portal children are mounted on DOM\n *\n * @default a new element on document.body without any styling\n */\n mountNode?: HTMLElement | null | { element?: HTMLElement | null; className?: string };\n};\n\nexport type PortalState = Pick<PortalProps, 'children'> & {\n mountNode: HTMLElement | null | undefined;\n\n /**\n * Ref to the root span element as virtual parent\n */\n virtualParentRootRef: // eslint-disable-next-line @typescript-eslint/no-deprecated\n React.MutableRefObject<HTMLSpanElement | null>;\n};\n"],"names":["React"],"mappings":"AAAA,YAAYA,WAAW,QAAQ"}

View File

@@ -0,0 +1,3 @@
export { Portal } from './Portal';
export { renderPortal_unstable } from './renderPortal';
export { usePortal_unstable } from './usePortal';

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/index.ts"],"sourcesContent":["export { Portal } from './Portal';\nexport type { PortalProps, PortalState } from './Portal.types';\nexport { renderPortal_unstable } from './renderPortal';\nexport { usePortal_unstable } from './usePortal';\n"],"names":["Portal","renderPortal_unstable","usePortal_unstable"],"mappings":"AAAA,SAASA,MAAM,QAAQ,WAAW;AAElC,SAASC,qBAAqB,QAAQ,iBAAiB;AACvD,SAASC,kBAAkB,QAAQ,cAAc"}

View File

@@ -0,0 +1,12 @@
import * as ReactDOM from 'react-dom';
import * as React from 'react';
/**
* Render the final JSX of Portal
*/ export const renderPortal_unstable = (state)=>{
return /*#__PURE__*/ React.createElement("span", {
hidden: true,
ref: state.virtualParentRootRef
}, state.mountNode && /*#__PURE__*/ ReactDOM.createPortal(/*#__PURE__*/ React.createElement(React.Fragment, null, state.children, /*#__PURE__*/ React.createElement("span", {
hidden: true
})), state.mountNode));
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/renderPortal.tsx"],"sourcesContent":["import * as ReactDOM from 'react-dom';\nimport * as React from 'react';\nimport type { PortalState } from './Portal.types';\n\n/**\n * Render the final JSX of Portal\n */\nexport const renderPortal_unstable = (state: PortalState): React.ReactElement => {\n return (\n <span hidden ref={state.virtualParentRootRef}>\n {state.mountNode &&\n ReactDOM.createPortal(\n <>\n {state.children}\n {/* Heads up!\n * This node exists only to ensure that the portal is not empty as we rely on that in `usePortalMountNode`\n * hook for React 18+.\n */}\n <span hidden />\n </>,\n state.mountNode,\n )}\n </span>\n );\n};\n"],"names":["ReactDOM","React","renderPortal_unstable","state","span","hidden","ref","virtualParentRootRef","mountNode","createPortal","children"],"mappings":"AAAA,YAAYA,cAAc,YAAY;AACtC,YAAYC,WAAW,QAAQ;AAG/B;;CAEC,GACD,OAAO,MAAMC,wBAAwB,CAACC;IACpC,qBACE,oBAACC;QAAKC,QAAAA;QAAOC,KAAKH,MAAMI,oBAAoB;OACzCJ,MAAMK,SAAS,kBACdR,SAASS,YAAY,eACnB,0CACGN,MAAMO,QAAQ,gBAKf,oBAACN;QAAKC,QAAAA;SAERF,MAAMK,SAAS;AAIzB,EAAE"}

View File

@@ -0,0 +1,81 @@
'use client';
import { setVirtualParent } from '@fluentui/react-utilities';
import * as React from 'react';
import { toMountNodeProps } from '../../utils/toMountNodeProps';
import { usePortalMountNode } from './usePortalMountNode';
/**
* Create the state required to render Portal.
*
* The returned state can be modified with hooks such as usePortalStyles, before being passed to renderPortal_unstable.
*
* @param props - props from this instance of Portal
*/ export const usePortal_unstable = (props)=>{
const { element, className } = toMountNodeProps(props.mountNode);
const virtualParentRootRef = React.useRef(null);
const fallbackElement = usePortalMountNode({
disabled: !!element,
className
});
const mountNode = element !== null && element !== void 0 ? element : fallbackElement;
const state = {
children: props.children,
mountNode,
virtualParentRootRef
};
React.useEffect(()=>{
if (!mountNode) {
return;
}
const virtualParent = virtualParentRootRef.current;
// By default, we create a mount node for portal on `document.body` (see usePortalMountNode()) and have following structure:
//
// <body>
// <!-- ⚛️ application root -->
// <div id="root">
// <!-- ⬇️ portal node rendered in a tree to anchor (virtual parent node) -->
// <span aria-hidden="true"></span>
// </div>
// <div id="portal-mount-node">
// <!-- 🧩portal content -->
// </div>
// </body>
//
// To make sure that `.elementContains()` works correctly, we link a virtual parent to a portal node (a virtual parent node becomes a parent of mount node):
// virtual.contains(mountNode) === false
// (while we need ⬇️⬇️⬇️)
// elementsContains(virtualParent, mountNode) === true
// elementsContains(mountNode, virtualParent) === false
//
// For more details, check docs for virtual parent utils.
//
// However, if a user provides a custom mount node (via `props`) the structure could be different:
//
// <body>
// <!-- application root -->
// <div id="root">
// <div id="portal-mount-node">
// <!-- 🧩portal content -->
//
// <span aria-hidden="true"></span>
// </div>
// </div>
// </body>
//
// A mount node in this case contains portal's content and a virtual parent node. In this case nodes linking is redundant and the check below avoids it.
//
// Otherwise, there is a circular reference - both elements are parents of each other:
// elementsContains(mountNode, virtualParent) === true
// elementsContains(virtualParent, mountNode) === true
const isVirtualParentInsideChild = mountNode.contains(virtualParent);
if (virtualParent && !isVirtualParentInsideChild) {
setVirtualParent(mountNode, virtualParent);
return ()=>{
setVirtualParent(mountNode, undefined);
};
}
}, [
virtualParentRootRef,
mountNode
]);
return state;
};

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/usePortal.ts"],"sourcesContent":["'use client';\n\nimport { setVirtualParent } from '@fluentui/react-utilities';\nimport * as React from 'react';\n\nimport { toMountNodeProps } from '../../utils/toMountNodeProps';\nimport { usePortalMountNode } from './usePortalMountNode';\nimport type { PortalProps, PortalState } from './Portal.types';\n\n/**\n * Create the state required to render Portal.\n *\n * The returned state can be modified with hooks such as usePortalStyles, before being passed to renderPortal_unstable.\n *\n * @param props - props from this instance of Portal\n */\nexport const usePortal_unstable = (props: PortalProps): PortalState => {\n const { element, className } = toMountNodeProps(props.mountNode);\n\n const virtualParentRootRef = React.useRef<HTMLSpanElement>(null);\n const fallbackElement = usePortalMountNode({ disabled: !!element, className });\n\n const mountNode = element ?? fallbackElement;\n const state: PortalState = {\n children: props.children,\n mountNode,\n virtualParentRootRef,\n };\n\n React.useEffect(() => {\n if (!mountNode) {\n return;\n }\n\n const virtualParent = virtualParentRootRef.current;\n\n // By default, we create a mount node for portal on `document.body` (see usePortalMountNode()) and have following structure:\n //\n // <body>\n // <!-- ⚛️ application root -->\n // <div id=\"root\">\n // <!-- ⬇️ portal node rendered in a tree to anchor (virtual parent node) -->\n // <span aria-hidden=\"true\"></span>\n // </div>\n // <div id=\"portal-mount-node\">\n // <!-- 🧩portal content -->\n // </div>\n // </body>\n //\n // To make sure that `.elementContains()` works correctly, we link a virtual parent to a portal node (a virtual parent node becomes a parent of mount node):\n // virtual.contains(mountNode) === false\n // (while we need ⬇️⬇️⬇️)\n // elementsContains(virtualParent, mountNode) === true\n // elementsContains(mountNode, virtualParent) === false\n //\n // For more details, check docs for virtual parent utils.\n //\n // However, if a user provides a custom mount node (via `props`) the structure could be different:\n //\n // <body>\n // <!-- application root -->\n // <div id=\"root\">\n // <div id=\"portal-mount-node\">\n // <!-- 🧩portal content -->\n //\n // <span aria-hidden=\"true\"></span>\n // </div>\n // </div>\n // </body>\n //\n // A mount node in this case contains portal's content and a virtual parent node. In this case nodes linking is redundant and the check below avoids it.\n //\n // Otherwise, there is a circular reference - both elements are parents of each other:\n // elementsContains(mountNode, virtualParent) === true\n // elementsContains(virtualParent, mountNode) === true\n const isVirtualParentInsideChild = mountNode.contains(virtualParent);\n\n if (virtualParent && !isVirtualParentInsideChild) {\n setVirtualParent(mountNode, virtualParent);\n\n return () => {\n setVirtualParent(mountNode, undefined);\n };\n }\n }, [virtualParentRootRef, mountNode]);\n\n return state;\n};\n"],"names":["setVirtualParent","React","toMountNodeProps","usePortalMountNode","usePortal_unstable","props","element","className","mountNode","virtualParentRootRef","useRef","fallbackElement","disabled","state","children","useEffect","virtualParent","current","isVirtualParentInsideChild","contains","undefined"],"mappings":"AAAA;AAEA,SAASA,gBAAgB,QAAQ,4BAA4B;AAC7D,YAAYC,WAAW,QAAQ;AAE/B,SAASC,gBAAgB,QAAQ,+BAA+B;AAChE,SAASC,kBAAkB,QAAQ,uBAAuB;AAG1D;;;;;;CAMC,GACD,OAAO,MAAMC,qBAAqB,CAACC;IACjC,MAAM,EAAEC,OAAO,EAAEC,SAAS,EAAE,GAAGL,iBAAiBG,MAAMG,SAAS;IAE/D,MAAMC,uBAAuBR,MAAMS,MAAM,CAAkB;IAC3D,MAAMC,kBAAkBR,mBAAmB;QAAES,UAAU,CAAC,CAACN;QAASC;IAAU;IAE5E,MAAMC,YAAYF,oBAAAA,qBAAAA,UAAWK;IAC7B,MAAME,QAAqB;QACzBC,UAAUT,MAAMS,QAAQ;QACxBN;QACAC;IACF;IAEAR,MAAMc,SAAS,CAAC;QACd,IAAI,CAACP,WAAW;YACd;QACF;QAEA,MAAMQ,gBAAgBP,qBAAqBQ,OAAO;QAElD,4HAA4H;QAC5H,EAAE;QACF,SAAS;QACT,iCAAiC;QACjC,oBAAoB;QACpB,iFAAiF;QACjF,uCAAuC;QACvC,WAAW;QACX,iCAAiC;QACjC,gCAAgC;QAChC,WAAW;QACX,UAAU;QACV,EAAE;QACF,4JAA4J;QAC5J,0CAA0C;QAC1C,2BAA2B;QAC3B,wDAAwD;QACxD,yDAAyD;QACzD,EAAE;QACF,yDAAyD;QACzD,EAAE;QACF,kGAAkG;QAClG,EAAE;QACF,SAAS;QACT,8BAA8B;QAC9B,oBAAoB;QACpB,mCAAmC;QACnC,kCAAkC;QAClC,EAAE;QACF,yCAAyC;QACzC,aAAa;QACb,WAAW;QACX,UAAU;QACV,EAAE;QACF,wJAAwJ;QACxJ,EAAE;QACF,sFAAsF;QACtF,wDAAwD;QACxD,wDAAwD;QACxD,MAAMC,6BAA6BV,UAAUW,QAAQ,CAACH;QAEtD,IAAIA,iBAAiB,CAACE,4BAA4B;YAChDlB,iBAAiBQ,WAAWQ;YAE5B,OAAO;gBACLhB,iBAAiBQ,WAAWY;YAC9B;QACF;IACF,GAAG;QAACX;QAAsBD;KAAU;IAEpC,OAAOK;AACT,EAAE"}

View File

@@ -0,0 +1,203 @@
'use client';
import * as React from 'react';
import { useThemeClassName_unstable as useThemeClassName, useFluent_unstable as useFluent, usePortalMountNode as usePortalMountNodeContext } from '@fluentui/react-shared-contexts';
import { mergeClasses } from '@griffel/react';
import { useFocusVisible } from '@fluentui/react-tabster';
import { usePortalMountNodeStylesStyles } from './usePortalMountNodeStyles.styles';
const useInsertionEffect = React['useInsertion' + 'Effect'];
/**
* Legacy element factory for React 17 and below. It's not safe for concurrent rendering.
*
* Creates a new element on a "document.body" to mount portals.
*/ const useLegacyElementFactory = (options)=>{
'use no memo';
const { className, dir, focusVisibleRef, targetNode } = options;
const targetElement = React.useMemo(()=>{
if (targetNode === undefined || options.disabled) {
return null;
}
const element = targetNode.ownerDocument.createElement('div');
targetNode.appendChild(element);
return element;
}, [
targetNode,
options.disabled
]);
// Heads up!
// This useMemo() call is intentional for React 17 & below.
//
// We don't want to re-create the portal element when its attributes change. This also cannot not be done in an effect
// because, changing the value of CSS variables after an initial mount will trigger interesting CSS side effects like
// transitions.
React.useMemo(()=>{
if (!targetElement) {
return;
}
targetElement.className = className;
targetElement.setAttribute('dir', dir);
targetElement.setAttribute('data-portal-node', 'true');
focusVisibleRef.current = targetElement;
}, [
className,
dir,
targetElement,
focusVisibleRef
]);
React.useEffect(()=>{
return ()=>{
targetElement === null || targetElement === void 0 ? void 0 : targetElement.remove();
};
}, [
targetElement
]);
return targetElement;
};
const initializeElementFactory = ()=>{
let currentElement = undefined;
function get(targetRoot, forceCreation) {
if (currentElement) {
return currentElement;
}
if (forceCreation) {
currentElement = targetRoot.ownerDocument.createElement('div');
targetRoot.appendChild(currentElement);
}
return currentElement;
}
function dispose() {
if (currentElement) {
currentElement.remove();
currentElement = undefined;
}
}
return {
get,
dispose
};
};
/**
* This is a modern element factory for React 18 and above. It is safe for concurrent rendering.
*
* It abuses the fact that React will mount DOM once (unlike hooks), so by using a proxy we can intercept:
* - the `remove()` method (we call it in `useEffect()`) and remove the element only when the portal is unmounted
* - all other methods (and properties) will be called by React once a portal is mounted
*/ const useModernElementFactory = (options)=>{
'use no memo';
const { className, dir, focusVisibleRef, targetNode } = options;
const [elementFactory] = React.useState(initializeElementFactory);
const elementProxy = React.useMemo(()=>{
if (targetNode === undefined || options.disabled) {
return null;
}
return new Proxy({}, {
get (_, property) {
// Heads up!
// `createPortal()` performs a check for `nodeType` property to determine if the mount node is a valid DOM node
// before mounting the portal. We hardcode the value to `Node.ELEMENT_NODE` to pass this check and avoid
// premature node creation
if (property === 'nodeType') {
// Can't use the `Node.ELEMENT_NODE` as it's a browser API and not available in all environments, e.g SSR
return 1; // `Node.ELEMENT_NODE`
}
// Heads up!
// We intercept the `remove()` method to remove the mount node only when portal has been unmounted already.
if (property === 'remove') {
const targetElement = elementFactory.get(targetNode, false);
if (targetElement) {
// If the mountElement has children, the portal is still mounted, otherwise we can dispose of it
const portalHasNoChildren = targetElement.childNodes.length === 0;
if (portalHasNoChildren) {
elementFactory.dispose();
}
}
return ()=>{
// Always return a no-op function to avoid errors in the code
};
}
const targetElement = elementFactory.get(targetNode, true);
const targetProperty = targetElement ? targetElement[property] : undefined;
if (typeof targetProperty === 'function') {
return targetProperty.bind(targetElement);
}
return targetProperty;
},
set (_, property, value) {
const ignoredProperty = property === '_virtual' || property === 'focusVisible';
// We should use the `elementFactory.get(targetNode, !ignoredProperty)`,
// but TypeScript requires a literal `true` or `false` for the overload signature.
// This workaround ensures the correct overload is called and avoids TypeScript errors.
const targetElement = ignoredProperty ? elementFactory.get(targetNode, false) : elementFactory.get(targetNode, true);
if (ignoredProperty && !targetElement) {
// We ignore the `_virtual` and `focusVisible` properties to avoid conflicts with the proxy
return true;
}
if (targetElement) {
Object.assign(targetElement, {
[property]: value
});
return true;
}
return false;
}
});
}, [
elementFactory,
targetNode,
options.disabled
]);
useInsertionEffect(()=>{
if (!elementProxy) {
return;
}
const classesToApply = className.split(' ').filter(Boolean);
elementProxy.classList.add(...classesToApply);
elementProxy.setAttribute('dir', dir);
elementProxy.setAttribute('data-portal-node', 'true');
focusVisibleRef.current = elementProxy;
return ()=>{
elementProxy.classList.remove(...classesToApply);
elementProxy.removeAttribute('dir');
};
}, [
className,
dir,
elementProxy,
focusVisibleRef
]);
React.useEffect(()=>{
return ()=>{
elementProxy === null || elementProxy === void 0 ? void 0 : elementProxy.remove();
};
}, [
elementProxy
]);
return elementProxy;
};
/**
* Element factory based on the React version.
*
* React 17 and below:
* - useLegacyElementFactory
*
* React 18 and above:
* - useModernElementFactory
*/ const useElementFactory = useInsertionEffect ? useModernElementFactory : useLegacyElementFactory;
/**
* Creates a new element on a "document.body" to mount portals.
*/ export const usePortalMountNode = (options)=>{
'use no memo';
const { targetDocument, dir } = useFluent();
const mountNode = usePortalMountNodeContext();
// eslint-disable-next-line @typescript-eslint/no-deprecated
const focusVisibleRef = useFocusVisible();
const classes = usePortalMountNodeStylesStyles();
const themeClassName = useThemeClassName();
const factoryOptions = {
dir,
disabled: options.disabled,
focusVisibleRef,
className: mergeClasses(themeClassName, classes.root, options.className),
targetNode: mountNode !== null && mountNode !== void 0 ? mountNode : targetDocument === null || targetDocument === void 0 ? void 0 : targetDocument.body
};
return useElementFactory(factoryOptions);
};

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,14 @@
'use client';
import { __styles } from '@griffel/react';
export const usePortalMountNodeStylesStyles = /*#__PURE__*/__styles({
root: {
qhf8xq: "f1euv43f",
Bhzewxz: "f15twtuk",
oyh7mz: ["f1vgc2s3", "f1e31b4d"],
j35jbq: ["f1e31b4d", "f1vgc2s3"],
Bj3rh1h: "f494woh"
}
}, {
d: [".f1euv43f{position:absolute;}", ".f15twtuk{top:0;}", ".f1vgc2s3{left:0;}", ".f1e31b4d{right:0;}", ".f494woh{z-index:1000000;}"]
});

View File

@@ -0,0 +1 @@
{"version":3,"names":["__styles","usePortalMountNodeStylesStyles","root","qhf8xq","Bhzewxz","oyh7mz","j35jbq","Bj3rh1h","d"],"sources":["usePortalMountNodeStyles.styles.js"],"sourcesContent":["'use client';\nimport { makeStyles } from '@griffel/react';\nexport const usePortalMountNodeStylesStyles = makeStyles({\n root: {\n // Creates new stacking context to prevent z-index issues\n // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context\n //\n // Also keeps a portal on top of a page to prevent scrollbars from appearing\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n zIndex: 1000000\n }\n});\n"],"mappings":"AAAA,YAAY;;AACZ,SAAAA,QAAA,QAA2B,gBAAgB;AAC3C,OAAO,MAAMC,8BAA8B,gBAAGD,QAAA;EAAAE,IAAA;IAAAC,MAAA;IAAAC,OAAA;IAAAC,MAAA;IAAAC,MAAA;IAAAC,OAAA;EAAA;AAAA;EAAAC,CAAA;AAAA,CAY7C,CAAC","ignoreList":[]}

View File

@@ -0,0 +1,15 @@
'use client';
import { makeStyles } from '@griffel/react';
export const usePortalMountNodeStylesStyles = makeStyles({
root: {
// Creates new stacking context to prevent z-index issues
// https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context
//
// Also keeps a portal on top of a page to prevent scrollbars from appearing
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 1000000
}
});

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/components/Portal/usePortalMountNodeStyles.styles.ts"],"sourcesContent":["'use client';\n\nimport { makeStyles } from '@griffel/react';\n\nexport const usePortalMountNodeStylesStyles = makeStyles({\n root: {\n // Creates new stacking context to prevent z-index issues\n // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context\n //\n // Also keeps a portal on top of a page to prevent scrollbars from appearing\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n\n zIndex: 1000000,\n },\n});\n"],"names":["makeStyles","usePortalMountNodeStylesStyles","root","position","top","left","right","zIndex"],"mappings":"AAAA;AAEA,SAASA,UAAU,QAAQ,iBAAiB;AAE5C,OAAO,MAAMC,iCAAiCD,WAAW;IACvDE,MAAM;QACJ,yDAAyD;QACzD,gHAAgH;QAChH,EAAE;QACF,4EAA4E;QAC5EC,UAAU;QACVC,KAAK;QACLC,MAAM;QACNC,OAAO;QAEPC,QAAQ;IACV;AACF,GAAG"}

3
node_modules/@fluentui/react-portal/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,3 @@
export { elementContains, setVirtualParent } from '@fluentui/react-utilities';
export { Portal, renderPortal_unstable, usePortal_unstable } from './components/Portal/index';
export { toMountNodeProps } from './utils/toMountNodeProps';

1
node_modules/@fluentui/react-portal/lib/index.js.map generated vendored Normal file
View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { elementContains, setVirtualParent } from '@fluentui/react-utilities';\nexport { Portal, renderPortal_unstable, usePortal_unstable } from './components/Portal/index';\nexport type { PortalProps, PortalState } from './components/Portal/index';\n\nexport { toMountNodeProps } from './utils/toMountNodeProps';\n"],"names":["elementContains","setVirtualParent","Portal","renderPortal_unstable","usePortal_unstable","toMountNodeProps"],"mappings":"AAAA,SAASA,eAAe,EAAEC,gBAAgB,QAAQ,4BAA4B;AAC9E,SAASC,MAAM,EAAEC,qBAAqB,EAAEC,kBAAkB,QAAQ,4BAA4B;AAG9F,SAASC,gBAAgB,QAAQ,2BAA2B"}

View File

@@ -0,0 +1,21 @@
import { isHTMLElement } from '@fluentui/react-utilities';
/**
* The function that normalizes the `mountNode` prop into an object with element and className props.
*
* @param mountNode - an HTML element or an object with props
*/ export function toMountNodeProps(mountNode) {
if (isHTMLElement(mountNode)) {
return {
element: mountNode
};
}
if (typeof mountNode === 'object') {
if (mountNode === null) {
return {
element: null
};
}
return mountNode;
}
return {};
}

View File

@@ -0,0 +1 @@
{"version":3,"sources":["../src/utils/toMountNodeProps.ts"],"sourcesContent":["import { isHTMLElement } from '@fluentui/react-utilities';\nimport type { PortalProps } from '../Portal';\n\n/**\n * The function that normalizes the `mountNode` prop into an object with element and className props.\n *\n * @param mountNode - an HTML element or an object with props\n */\nexport function toMountNodeProps(mountNode: PortalProps['mountNode']): {\n element?: HTMLElement | null;\n className?: string;\n} {\n if (isHTMLElement(mountNode)) {\n return { element: mountNode };\n }\n\n if (typeof mountNode === 'object') {\n if (mountNode === null) {\n return { element: null };\n }\n\n return mountNode;\n }\n\n return {};\n}\n"],"names":["isHTMLElement","toMountNodeProps","mountNode","element"],"mappings":"AAAA,SAASA,aAAa,QAAQ,4BAA4B;AAG1D;;;;CAIC,GACD,OAAO,SAASC,iBAAiBC,SAAmC;IAIlE,IAAIF,cAAcE,YAAY;QAC5B,OAAO;YAAEC,SAASD;QAAU;IAC9B;IAEA,IAAI,OAAOA,cAAc,UAAU;QACjC,IAAIA,cAAc,MAAM;YACtB,OAAO;gBAAEC,SAAS;YAAK;QACzB;QAEA,OAAOD;IACT;IAEA,OAAO,CAAC;AACV"}

48
node_modules/@fluentui/react-portal/package.json generated vendored Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "@fluentui/react-portal",
"version": "9.8.11",
"description": "A utility component that creates portals compatible with Fluent UI",
"main": "lib-commonjs/index.js",
"module": "lib/index.js",
"typings": "./dist/index.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/microsoft/fluentui"
},
"license": "MIT",
"dependencies": {
"@fluentui/react-shared-contexts": "^9.26.2",
"@fluentui/react-tabster": "^9.26.13",
"@fluentui/react-utilities": "^9.26.2",
"@griffel/react": "^1.5.32",
"@swc/helpers": "^0.5.1"
},
"peerDependencies": {
"@types/react": ">=16.14.0 <20.0.0",
"@types/react-dom": ">=16.9.0 <20.0.0",
"react": ">=16.14.0 <20.0.0",
"react-dom": ">=16.14.0 <20.0.0"
},
"beachball": {
"disallowedChangeTypes": [
"major",
"prerelease"
]
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"node": "./lib-commonjs/index.js",
"import": "./lib/index.js",
"require": "./lib-commonjs/index.js"
},
"./package.json": "./package.json"
},
"files": [
"*.md",
"dist/*.d.ts",
"lib",
"lib-commonjs"
]
}