Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@fluentui/react-portal

Package Overview
Dependencies
Maintainers
12
Versions
902
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluentui/react-portal - npm Package Compare versions

Comparing version 9.0.0-alpha.12 to 9.0.0-alpha.13

lib-amd/virtualParent/elementContains.d.ts

25

CHANGELOG.json

@@ -5,3 +5,26 @@ {

{
"date": "Tue, 18 May 2021 07:31:38 GMT",
"date": "Wed, 19 May 2021 07:30:20 GMT",
"tag": "@fluentui/react-portal_v9.0.0-alpha.13",
"version": "9.0.0-alpha.13",
"comments": {
"prerelease": [
{
"comment": "Render portals with virtual parent",
"author": "lingfan.gao@microsoft.com",
"commit": "90cd8ca21a6904680a1a06ba4800053dc69a9a48",
"package": "@fluentui/react-portal"
}
],
"patch": [
{
"comment": "Bump @fluentui/react-conformance to v0.3.1",
"author": "martinhochel@microsoft.com",
"commit": "616b4b7c381c757871e8a590564d8eff7337834c",
"package": "@fluentui/react-portal"
}
]
}
},
{
"date": "Tue, 18 May 2021 07:34:38 GMT",
"tag": "@fluentui/react-portal_v9.0.0-alpha.12",

@@ -8,0 +31,0 @@ "version": "9.0.0-alpha.12",

# Change Log - @fluentui/react-portal
This log was last generated on Tue, 18 May 2021 07:31:38 GMT and should not be manually modified.
This log was last generated on Wed, 19 May 2021 07:30:20 GMT and should not be manually modified.
<!-- Start content -->
## [9.0.0-alpha.13](https://github.com/microsoft/fluentui/tree/@fluentui/react-portal_v9.0.0-alpha.13)
Wed, 19 May 2021 07:30:20 GMT
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-portal_v9.0.0-alpha.12..@fluentui/react-portal_v9.0.0-alpha.13)
### Patches
- Bump @fluentui/react-conformance to v0.3.1 ([PR #18194](https://github.com/microsoft/fluentui/pull/18194) by martinhochel@microsoft.com)
### Changes
- Render portals with virtual parent ([PR #18148](https://github.com/microsoft/fluentui/pull/18148) by lingfan.gao@microsoft.com)
## [9.0.0-alpha.12](https://github.com/microsoft/fluentui/tree/@fluentui/react-portal_v9.0.0-alpha.12)
Tue, 18 May 2021 07:31:38 GMT
Tue, 18 May 2021 07:34:38 GMT
[Compare changes](https://github.com/microsoft/fluentui/compare/@fluentui/react-portal_v9.0.0-alpha.11..@fluentui/react-portal_v9.0.0-alpha.12)

@@ -11,0 +24,0 @@

import * as React_2 from 'react';
/**
* Similar functionality to `element.contains` DOM API for use with out of order DOM elements that
* checks the virtual parent hierarchy. If a virtual parents exists, it is chosen over the actual parent
*
* @returns true if the child can find the parent in its virtual hierarchy
*/
export declare function elementContains(parent: HTMLElement | null, child: HTMLElement | null): boolean;
/**
* Component that renders children in a React portal

@@ -23,2 +31,6 @@ */

shouldRender: boolean;
/**
* Ref to the root span element as virtual parent
*/
virtualParentRootRef: React_2.MutableRefObject<HTMLSpanElement | null>;
}

@@ -29,5 +41,13 @@

*/
export declare const renderPortal: (state: PortalState) => React_2.ReactPortal | null;
export declare const renderPortal: (state: PortalState) => React_2.ReactElement;
/**
* Sets the virtual parent of an element.
*
* @param child - Theme element to set the virtual parent
* @param parent - The virtual parent, use `undefined` to remove a virtual parent relationship
*/
export declare function setVirtualParent(child: HTMLElement, parent?: HTMLElement): void;
/**
* Create the state required to render Portal.

@@ -34,0 +54,0 @@ *

@@ -10,2 +10,5 @@ ## API Report File for "@fluentui/react-portal"

// @public
export function elementContains(parent: HTMLElement | null, child: HTMLElement | null): boolean;
// @public
export const Portal: React_2.FC<PortalProps>;

@@ -22,8 +25,12 @@

shouldRender: boolean;
virtualParentRootRef: React_2.MutableRefObject<HTMLSpanElement | null>;
}
// @public
export const renderPortal: (state: PortalState) => React_2.ReactPortal | null;
export const renderPortal: (state: PortalState) => React_2.ReactElement;
// @public
export function setVirtualParent(child: HTMLElement, parent?: HTMLElement): void;
// @public
export const usePortal: (props: PortalProps, defaultProps?: PortalProps | undefined) => PortalState;

@@ -30,0 +37,0 @@

@@ -16,2 +16,6 @@ import * as React from 'react';

shouldRender: boolean;
/**
* Ref to the root span element as virtual parent
*/
virtualParentRootRef: React.MutableRefObject<HTMLSpanElement | null>;
}

2

lib-amd/components/Portal/renderPortal.d.ts

@@ -6,2 +6,2 @@ import * as React from 'react';

*/
export declare const renderPortal: (state: PortalState) => React.ReactPortal | null;
export declare const renderPortal: (state: PortalState) => React.ReactElement;

@@ -1,2 +0,2 @@

define(["require", "exports", "react-dom"], function (require, exports, ReactDOM) {
define(["require", "exports", "react-dom", "react"], function (require, exports, ReactDOM, React) {
"use strict";

@@ -9,6 +9,3 @@ Object.defineProperty(exports, "__esModule", { value: true });

var renderPortal = function (state) {
if (state.shouldRender && state.mountNode) {
return ReactDOM.createPortal(state.children, state.mountNode);
}
return null;
return (React.createElement("span", { hidden: true, ref: state.virtualParentRootRef }, state.shouldRender && state.mountNode && ReactDOM.createPortal(state.children, state.mountNode)));
};

@@ -15,0 +12,0 @@ exports.renderPortal = renderPortal;

@@ -1,2 +0,2 @@

define(["require", "exports", "@fluentui/react-utilities", "./usePortalMountNode"], function (require, exports, react_utilities_1, usePortalMountNode_1) {
define(["require", "exports", "react", "@fluentui/react-utilities", "./usePortalMountNode", "../../virtualParent/index"], function (require, exports, React, react_utilities_1, usePortalMountNode_1, index_1) {
"use strict";

@@ -16,5 +16,11 @@ Object.defineProperty(exports, "__esModule", { value: true });

var _a;
var state = mergeProps({ shouldRender: !react_utilities_1.useIsSSR() }, defaultProps, props);
var virtualParentRootRef = React.useRef(null);
var state = mergeProps({ shouldRender: !react_utilities_1.useIsSSR(), virtualParentRootRef: virtualParentRootRef }, defaultProps, props);
var fallbackMountNode = usePortalMountNode_1.usePortalMountNode({ disabled: !!state.mountNode });
state.mountNode = (_a = state.mountNode) !== null && _a !== void 0 ? _a : fallbackMountNode;
React.useEffect(function () {
if (state.virtualParentRootRef.current && state.mountNode) {
index_1.setVirtualParent(state.mountNode, state.virtualParentRootRef.current);
}
}, [state.virtualParentRootRef, state.mountNode]);
return state;

@@ -21,0 +27,0 @@ };

export * from './components/Portal/index';
export { elementContains, setVirtualParent } from './virtualParent/index';

@@ -1,6 +0,9 @@

define(["require", "exports", "tslib", "./components/Portal/index"], function (require, exports, tslib_1, index_1) {
define(["require", "exports", "tslib", "./components/Portal/index", "./virtualParent/index"], function (require, exports, tslib_1, index_1, index_2) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setVirtualParent = exports.elementContains = void 0;
tslib_1.__exportStar(index_1, exports);
Object.defineProperty(exports, "elementContains", { enumerable: true, get: function () { return index_2.elementContains; } });
Object.defineProperty(exports, "setVirtualParent", { enumerable: true, get: function () { return index_2.setVirtualParent; } });
});
//# sourceMappingURL=index.js.map

@@ -16,2 +16,6 @@ import * as React from 'react';

shouldRender: boolean;
/**
* Ref to the root span element as virtual parent
*/
virtualParentRootRef: React.MutableRefObject<HTMLSpanElement | null>;
}

@@ -6,2 +6,2 @@ import * as React from 'react';

*/
export declare const renderPortal: (state: PortalState) => React.ReactPortal | null;
export declare const renderPortal: (state: PortalState) => React.ReactElement;

@@ -5,2 +5,3 @@ "use strict";

var ReactDOM = require("react-dom");
var React = require("react");
/**

@@ -10,8 +11,5 @@ * Render the final JSX of Portal

var renderPortal = function (state) {
if (state.shouldRender && state.mountNode) {
return ReactDOM.createPortal(state.children, state.mountNode);
}
return null;
return (React.createElement("span", { hidden: true, ref: state.virtualParentRootRef }, state.shouldRender && state.mountNode && ReactDOM.createPortal(state.children, state.mountNode)));
};
exports.renderPortal = renderPortal;
//# sourceMappingURL=renderPortal.js.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.usePortal = void 0;
var React = require("react");
var react_utilities_1 = require("@fluentui/react-utilities");
var usePortalMountNode_1 = require("./usePortalMountNode");
var index_1 = require("../../virtualParent/index");
var mergeProps = react_utilities_1.makeMergeProps();

@@ -17,5 +19,11 @@ /**

var _a;
var state = mergeProps({ shouldRender: !react_utilities_1.useIsSSR() }, defaultProps, props);
var virtualParentRootRef = React.useRef(null);
var state = mergeProps({ shouldRender: !react_utilities_1.useIsSSR(), virtualParentRootRef: virtualParentRootRef }, defaultProps, props);
var fallbackMountNode = usePortalMountNode_1.usePortalMountNode({ disabled: !!state.mountNode });
state.mountNode = (_a = state.mountNode) !== null && _a !== void 0 ? _a : fallbackMountNode;
React.useEffect(function () {
if (state.virtualParentRootRef.current && state.mountNode) {
index_1.setVirtualParent(state.mountNode, state.virtualParentRootRef.current);
}
}, [state.virtualParentRootRef, state.mountNode]);
return state;

@@ -22,0 +30,0 @@ };

export * from './components/Portal/index';
export { elementContains, setVirtualParent } from './virtualParent/index';
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setVirtualParent = exports.elementContains = void 0;
var tslib_1 = require("tslib");
tslib_1.__exportStar(require("./components/Portal/index"), exports);
var index_1 = require("./virtualParent/index");
Object.defineProperty(exports, "elementContains", { enumerable: true, get: function () { return index_1.elementContains; } });
Object.defineProperty(exports, "setVirtualParent", { enumerable: true, get: function () { return index_1.setVirtualParent; } });
//# sourceMappingURL=index.js.map

@@ -16,2 +16,6 @@ import * as React from 'react';

shouldRender: boolean;
/**
* Ref to the root span element as virtual parent
*/
virtualParentRootRef: React.MutableRefObject<HTMLSpanElement | null>;
}

@@ -6,2 +6,2 @@ import * as React from 'react';

*/
export declare const renderPortal: (state: PortalState) => React.ReactPortal | null;
export declare const renderPortal: (state: PortalState) => React.ReactElement;
import * as ReactDOM from 'react-dom';
import * as React from 'react';
/**

@@ -6,7 +7,4 @@ * Render the final JSX of Portal

export var renderPortal = function (state) {
if (state.shouldRender && state.mountNode) {
return ReactDOM.createPortal(state.children, state.mountNode);
}
return null;
return (React.createElement("span", { hidden: true, ref: state.virtualParentRootRef }, state.shouldRender && state.mountNode && ReactDOM.createPortal(state.children, state.mountNode)));
};
//# sourceMappingURL=renderPortal.js.map

@@ -0,3 +1,5 @@

import * as React from 'react';
import { makeMergeProps, useIsSSR } from '@fluentui/react-utilities';
import { usePortalMountNode } from './usePortalMountNode';
import { setVirtualParent } from '../../virtualParent/index';
var mergeProps = makeMergeProps();

@@ -14,7 +16,13 @@ /**

var _a;
var state = mergeProps({ shouldRender: !useIsSSR() }, defaultProps, props);
var virtualParentRootRef = React.useRef(null);
var state = mergeProps({ shouldRender: !useIsSSR(), virtualParentRootRef: virtualParentRootRef }, defaultProps, props);
var fallbackMountNode = usePortalMountNode({ disabled: !!state.mountNode });
state.mountNode = (_a = state.mountNode) !== null && _a !== void 0 ? _a : fallbackMountNode;
React.useEffect(function () {
if (state.virtualParentRootRef.current && state.mountNode) {
setVirtualParent(state.mountNode, state.virtualParentRootRef.current);
}
}, [state.virtualParentRootRef, state.mountNode]);
return state;
};
//# sourceMappingURL=usePortal.js.map
export * from './components/Portal/index';
export { elementContains, setVirtualParent } from './virtualParent/index';
export * from './components/Portal/index';
export { elementContains, setVirtualParent } from './virtualParent/index';
//# sourceMappingURL=index.js.map
{
"name": "@fluentui/react-portal",
"version": "9.0.0-alpha.12",
"version": "9.0.0-alpha.13",
"description": "A utility component that creates portals compatible with Fluent UI",

@@ -27,4 +27,4 @@ "main": "lib-commonjs/index.js",

"@fluentui/eslint-plugin": "^1.2.0",
"@fluentui/jest-serializer-make-styles": "^9.0.0-alpha.11",
"@fluentui/react-conformance": "^0.3.0",
"@fluentui/jest-serializer-make-styles": "^9.0.0-alpha.12",
"@fluentui/react-conformance": "^0.3.1",
"@fluentui/scripts": "^1.0.0",

@@ -43,5 +43,5 @@ "@types/enzyme": "3.10.3",

"dependencies": {
"@fluentui/react-make-styles": "^9.0.0-alpha.34",
"@fluentui/react-shared-contexts": "^9.0.0-alpha.10",
"@fluentui/react-utilities": "^9.0.0-alpha.21",
"@fluentui/react-make-styles": "^9.0.0-alpha.35",
"@fluentui/react-shared-contexts": "^9.0.0-alpha.11",
"@fluentui/react-utilities": "^9.0.0-alpha.22",
"tslib": "^2.1.0"

@@ -48,0 +48,0 @@ },

@@ -34,1 +34,115 @@ # @fluentui/react-portal

`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.getElement.ById('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>
```

@@ -111,9 +111,66 @@ # Portal Spec

### 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
- `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.getElement.ById('child');
child._virtual.parent = parent;
```
## Structure
```
<FluentProvider
```tsx
<FluentProvider>
<Portal id="portal-1" />
<Portal id="portal-2" />
</FluentProvider
</FluentProvider>
```

@@ -125,9 +182,46 @@

<body>
<div>Maintree</div>
<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>
<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>
```
## Migration

@@ -134,0 +228,0 @@

@@ -24,2 +24,7 @@ import * as React from 'react';

shouldRender: boolean;
/**
* Ref to the root span element as virtual parent
*/
virtualParentRootRef: React.MutableRefObject<HTMLSpanElement | null>;
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc