From 26abbae1f68dd72e384d6c6d30a0deed75bfefb8 Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 17:57:44 -0400 Subject: [PATCH 01/11] Add portalContainer to PortalContext --- .../core/src/components/portal/portal.tsx | 34 ++++++++----------- .../src/context/portal/portalProvider.tsx | 10 ++++-- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index fe29b265af..36f9adc4f7 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -78,33 +78,35 @@ const PORTAL_LEGACY_CONTEXT_TYPES: ValidationMap = { export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = {}) { const context = React.useContext(PortalContext); + const container = props.container ?? context.portalContainer ?? document?.body + const [hasMounted, setHasMounted] = React.useState(false); const [portalElement, setPortalElement] = React.useState(); const createContainerElement = React.useCallback(() => { - const container = document.createElement("div"); - container.classList.add(Classes.PORTAL); - maybeAddClass(container.classList, props.className); // directly added to this portal element - maybeAddClass(container.classList, context.portalClassName); // added via PortalProvider context - addStopPropagationListeners(container, props.stopPropagationEvents); + const newContainer = document.createElement("div"); + newContainer.classList.add(Classes.PORTAL); + maybeAddClass(newContainer.classList, props.className); // directly added to this portal element + maybeAddClass(newContainer.classList, context.portalClassName); // added via PortalProvider context + addStopPropagationListeners(newContainer, props.stopPropagationEvents); // TODO: remove legacy context support in Blueprint v6.0 const { blueprintPortalClassName } = legacyContext; if (blueprintPortalClassName != null && blueprintPortalClassName !== "") { console.error(Errors.PORTAL_LEGACY_CONTEXT_API); - maybeAddClass(container.classList, blueprintPortalClassName); // added via legacy context + maybeAddClass(newContainer.classList, blueprintPortalClassName); // added via legacy context } - return container; + return newContainer; }, [props.className, context.portalClassName]); // create the container element & attach it to the DOM React.useEffect(() => { - if (props.container == null) { + if (container == null) { return; } const newPortalElement = createContainerElement(); - props.container.appendChild(newPortalElement); + container.appendChild(newPortalElement); setPortalElement(newPortalElement); setHasMounted(true); @@ -114,7 +116,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = setHasMounted(false); setPortalElement(undefined); }; - }, [props.container, createContainerElement]); + }, [container, createContainerElement]); // wait until next successful render to invoke onChildrenMount callback React.useEffect(() => { @@ -123,21 +125,17 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = } }, [hasMounted, props.onChildrenMount]); - // update className prop on portal DOM element when props change - const prevClassName = usePrevious(props.className); React.useEffect(() => { if (portalElement != null) { - maybeRemoveClass(portalElement.classList, prevClassName); maybeAddClass(portalElement.classList, props.className); + return () => maybeRemoveClass(portalElement.classList, props.className); } }, [props.className]); - // update stopPropagation listeners when props change - const prevStopPropagationEvents = usePrevious(props.stopPropagationEvents); React.useEffect(() => { if (portalElement != null) { - removeStopPropagationListeners(portalElement, prevStopPropagationEvents); addStopPropagationListeners(portalElement, props.stopPropagationEvents); + return () => removeStopPropagationListeners(portalElement, props.stopPropagationEvents); } }, [props.stopPropagationEvents]); @@ -150,9 +148,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = return ReactDOM.createPortal(props.children, portalElement); } } -Portal.defaultProps = { - container: typeof document !== "undefined" ? document.body : undefined, -}; + Portal.displayName = `${DISPLAYNAME_PREFIX}.Portal`; // eslint-disable-next-line deprecation/deprecation Portal.contextTypes = PORTAL_LEGACY_CONTEXT_TYPES; diff --git a/packages/core/src/context/portal/portalProvider.tsx b/packages/core/src/context/portal/portalProvider.tsx index 69d3dfbbc8..135f6a7a4f 100644 --- a/packages/core/src/context/portal/portalProvider.tsx +++ b/packages/core/src/context/portal/portalProvider.tsx @@ -19,6 +19,8 @@ import * as React from "react"; export interface PortalContextOptions { /** Additional CSS classes to add to all `Portal` elements in this React context. */ portalClassName?: string; + /** Portal container element which the default parent for all `Portal` elements in this React context */ + portalContainer?: HTMLElement; } /** @@ -32,6 +34,10 @@ export const PortalContext = React.createContext({}); * * @see https://blueprintjs.com/docs/#core/context/portal-provider */ -export const PortalProvider = ({ children, ...options }: React.PropsWithChildren) => { - return {children}; +export const PortalProvider = ({ children, portalClassName, portalContainer }: React.PropsWithChildren) => { + const contextOptions = React.useMemo(() => ({ + portalClassName, + portalContainer + }), [portalClassName, portalContainer]) + return {children}; }; From 2b1d5ee97dde6e0a7f3252bdad609f16d18a81a1 Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 18:05:33 -0400 Subject: [PATCH 02/11] Remove setHasMounted state --- packages/core/src/components/portal/portal.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index 36f9adc4f7..a9449c8895 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -80,8 +80,8 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = const container = props.container ?? context.portalContainer ?? document?.body - const [hasMounted, setHasMounted] = React.useState(false); const [portalElement, setPortalElement] = React.useState(); + const hasMounted = portalElement != null const createContainerElement = React.useCallback(() => { const newContainer = document.createElement("div"); @@ -108,12 +108,10 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = const newPortalElement = createContainerElement(); container.appendChild(newPortalElement); setPortalElement(newPortalElement); - setHasMounted(true); return () => { removeStopPropagationListeners(newPortalElement, props.stopPropagationEvents); newPortalElement.remove(); - setHasMounted(false); setPortalElement(undefined); }; }, [container, createContainerElement]); From b32e66f4dc7a25c9299ec30009b8ff886f69369f Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 18:08:07 -0400 Subject: [PATCH 03/11] Remove hasMounted entirely --- packages/core/src/components/portal/portal.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index a9449c8895..bb27b4ac67 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -81,7 +81,6 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = const container = props.container ?? context.portalContainer ?? document?.body const [portalElement, setPortalElement] = React.useState(); - const hasMounted = portalElement != null const createContainerElement = React.useCallback(() => { const newContainer = document.createElement("div"); @@ -118,10 +117,10 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = // wait until next successful render to invoke onChildrenMount callback React.useEffect(() => { - if (hasMounted) { + if (portalElement != null) { props.onChildrenMount?.(); } - }, [hasMounted, props.onChildrenMount]); + }, [props.onChildrenMount]); React.useEffect(() => { if (portalElement != null) { @@ -140,7 +139,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = // Only render `children` once this component has mounted in a browser environment, so they are // immediately attached to the DOM tree and can do DOM things like measuring or `autoFocus`. // See long comment on componentDidMount in https://reactjs.org/docs/portals.html#event-bubbling-through-portals - if (typeof document === "undefined" || !hasMounted || portalElement == null) { + if (typeof document === "undefined" || portalElement == null) { return null; } else { return ReactDOM.createPortal(props.children, portalElement); From 981c4dcb04788d4270122cd9eec6ec503c3937e6 Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 18:11:45 -0400 Subject: [PATCH 04/11] Rename createContainer element with createPortalElement --- .../core/src/components/portal/portal.tsx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index bb27b4ac67..e21a8a98b2 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -82,21 +82,21 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = const [portalElement, setPortalElement] = React.useState(); - const createContainerElement = React.useCallback(() => { - const newContainer = document.createElement("div"); - newContainer.classList.add(Classes.PORTAL); - maybeAddClass(newContainer.classList, props.className); // directly added to this portal element - maybeAddClass(newContainer.classList, context.portalClassName); // added via PortalProvider context - addStopPropagationListeners(newContainer, props.stopPropagationEvents); + const createPortalElement = React.useCallback(() => { + const portalElement = document.createElement("div"); + portalElement.classList.add(Classes.PORTAL); + maybeAddClass(portalElement.classList, props.className); // directly added to this portal element + maybeAddClass(portalElement.classList, context.portalClassName); // added via PortalProvider context + addStopPropagationListeners(portalElement, props.stopPropagationEvents); // TODO: remove legacy context support in Blueprint v6.0 const { blueprintPortalClassName } = legacyContext; if (blueprintPortalClassName != null && blueprintPortalClassName !== "") { console.error(Errors.PORTAL_LEGACY_CONTEXT_API); - maybeAddClass(newContainer.classList, blueprintPortalClassName); // added via legacy context + maybeAddClass(portalElement.classList, blueprintPortalClassName); // added via legacy context } - return newContainer; + return portalElement; }, [props.className, context.portalClassName]); // create the container element & attach it to the DOM @@ -104,7 +104,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = if (container == null) { return; } - const newPortalElement = createContainerElement(); + const newPortalElement = createPortalElement(); container.appendChild(newPortalElement); setPortalElement(newPortalElement); @@ -113,7 +113,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = newPortalElement.remove(); setPortalElement(undefined); }; - }, [container, createContainerElement]); + }, [container, createPortalElement]); // wait until next successful render to invoke onChildrenMount callback React.useEffect(() => { From ada80745684b3ffb6961c6536fb01fca300d7b2e Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 18:20:45 -0400 Subject: [PATCH 05/11] Misc docs/renames --- packages/core/src/components/portal/portal.tsx | 10 +++++----- packages/core/src/context/portal/portalProvider.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index e21a8a98b2..5fc0de7b9c 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -35,7 +35,7 @@ export interface PortalProps extends Props { /** * The HTML element that children will be mounted to. * - * @default document.body + * @default PortalContext.portalContainer ?? document.body */ container?: HTMLElement; @@ -78,7 +78,7 @@ const PORTAL_LEGACY_CONTEXT_TYPES: ValidationMap = { export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = {}) { const context = React.useContext(PortalContext); - const container = props.container ?? context.portalContainer ?? document?.body + const portalContainer = props.container ?? context.portalContainer ?? document?.body const [portalElement, setPortalElement] = React.useState(); @@ -101,11 +101,11 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = // create the container element & attach it to the DOM React.useEffect(() => { - if (container == null) { + if (portalContainer == null) { return; } const newPortalElement = createPortalElement(); - container.appendChild(newPortalElement); + portalContainer.appendChild(newPortalElement); setPortalElement(newPortalElement); return () => { @@ -113,7 +113,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = newPortalElement.remove(); setPortalElement(undefined); }; - }, [container, createPortalElement]); + }, [portalContainer, createPortalElement]); // wait until next successful render to invoke onChildrenMount callback React.useEffect(() => { diff --git a/packages/core/src/context/portal/portalProvider.tsx b/packages/core/src/context/portal/portalProvider.tsx index 135f6a7a4f..4e63cfb0e1 100644 --- a/packages/core/src/context/portal/portalProvider.tsx +++ b/packages/core/src/context/portal/portalProvider.tsx @@ -19,7 +19,7 @@ import * as React from "react"; export interface PortalContextOptions { /** Additional CSS classes to add to all `Portal` elements in this React context. */ portalClassName?: string; - /** Portal container element which the default parent for all `Portal` elements in this React context */ + /** The HTML element that all `Portal` elements in this React context will be mounted to. */ portalContainer?: HTMLElement; } From 497478b77b73e206b16ceb92c6edeba9fd32c769 Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 18:31:11 -0400 Subject: [PATCH 06/11] Compile --- packages/core/src/components/portal/portal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index 5fc0de7b9c..4af39678d1 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -21,7 +21,6 @@ import { Classes, DISPLAYNAME_PREFIX, Props } from "../../common"; import { ValidationMap } from "../../common/context"; import * as Errors from "../../common/errors"; import { PortalContext } from "../../context/portal/portalProvider"; -import { usePrevious } from "../../hooks/usePrevious"; export interface PortalProps extends Props { /** Contents to send through the portal. */ @@ -127,6 +126,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = maybeAddClass(portalElement.classList, props.className); return () => maybeRemoveClass(portalElement.classList, props.className); } + return undefined }, [props.className]); React.useEffect(() => { @@ -134,6 +134,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = addStopPropagationListeners(portalElement, props.stopPropagationEvents); return () => removeStopPropagationListeners(portalElement, props.stopPropagationEvents); } + return undefined }, [props.stopPropagationEvents]); // Only render `children` once this component has mounted in a browser environment, so they are From e405b7ffb50ada82c5e41693e587ed59a8d0c107 Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 18:35:37 -0400 Subject: [PATCH 07/11] Prettier --- packages/core/src/components/portal/portal.tsx | 6 +++--- .../core/src/context/portal/portalProvider.tsx | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index 4af39678d1..29014767a2 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -77,7 +77,7 @@ const PORTAL_LEGACY_CONTEXT_TYPES: ValidationMap = { export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = {}) { const context = React.useContext(PortalContext); - const portalContainer = props.container ?? context.portalContainer ?? document?.body + const portalContainer = props.container ?? context.portalContainer ?? document?.body; const [portalElement, setPortalElement] = React.useState(); @@ -126,7 +126,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = maybeAddClass(portalElement.classList, props.className); return () => maybeRemoveClass(portalElement.classList, props.className); } - return undefined + return undefined; }, [props.className]); React.useEffect(() => { @@ -134,7 +134,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = addStopPropagationListeners(portalElement, props.stopPropagationEvents); return () => removeStopPropagationListeners(portalElement, props.stopPropagationEvents); } - return undefined + return undefined; }, [props.stopPropagationEvents]); // Only render `children` once this component has mounted in a browser environment, so they are diff --git a/packages/core/src/context/portal/portalProvider.tsx b/packages/core/src/context/portal/portalProvider.tsx index 4e63cfb0e1..d9449c4c98 100644 --- a/packages/core/src/context/portal/portalProvider.tsx +++ b/packages/core/src/context/portal/portalProvider.tsx @@ -34,10 +34,17 @@ export const PortalContext = React.createContext({}); * * @see https://blueprintjs.com/docs/#core/context/portal-provider */ -export const PortalProvider = ({ children, portalClassName, portalContainer }: React.PropsWithChildren) => { - const contextOptions = React.useMemo(() => ({ - portalClassName, - portalContainer - }), [portalClassName, portalContainer]) +export const PortalProvider = ({ + children, + portalClassName, + portalContainer, +}: React.PropsWithChildren) => { + const contextOptions = React.useMemo( + () => ({ + portalClassName, + portalContainer, + }), + [portalClassName, portalContainer], + ); return {children}; }; From 2cb57b32e9ed322d13c8a49b8d86c4eff1aba16c Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 23:47:25 -0400 Subject: [PATCH 08/11] Exhaustive deps & shadowing --- .../core/src/components/portal/portal.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index 29014767a2..554e009570 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -82,21 +82,21 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = const [portalElement, setPortalElement] = React.useState(); const createPortalElement = React.useCallback(() => { - const portalElement = document.createElement("div"); - portalElement.classList.add(Classes.PORTAL); - maybeAddClass(portalElement.classList, props.className); // directly added to this portal element - maybeAddClass(portalElement.classList, context.portalClassName); // added via PortalProvider context - addStopPropagationListeners(portalElement, props.stopPropagationEvents); + const newPortalElement = document.createElement("div"); + newPortalElement.classList.add(Classes.PORTAL); + maybeAddClass(newPortalElement.classList, props.className); // directly added to this portal element + maybeAddClass(newPortalElement.classList, context.portalClassName); // added via PortalProvider context + addStopPropagationListeners(newPortalElement, props.stopPropagationEvents); // TODO: remove legacy context support in Blueprint v6.0 const { blueprintPortalClassName } = legacyContext; if (blueprintPortalClassName != null && blueprintPortalClassName !== "") { console.error(Errors.PORTAL_LEGACY_CONTEXT_API); - maybeAddClass(portalElement.classList, blueprintPortalClassName); // added via legacy context + maybeAddClass(newPortalElement.classList, blueprintPortalClassName); // added via legacy context } - return portalElement; - }, [props.className, context.portalClassName]); + return newPortalElement; + }, [props.className, props.stopPropagationEvents, context.portalClassName, legacyContext]); // create the container element & attach it to the DOM React.useEffect(() => { @@ -112,14 +112,14 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = newPortalElement.remove(); setPortalElement(undefined); }; - }, [portalContainer, createPortalElement]); + }, [portalContainer, createPortalElement, props.stopPropagationEvents]); // wait until next successful render to invoke onChildrenMount callback React.useEffect(() => { if (portalElement != null) { props.onChildrenMount?.(); } - }, [props.onChildrenMount]); + }, [portalElement, props, props.onChildrenMount]); React.useEffect(() => { if (portalElement != null) { @@ -127,7 +127,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = return () => maybeRemoveClass(portalElement.classList, props.className); } return undefined; - }, [props.className]); + }, [portalElement, props.className]); React.useEffect(() => { if (portalElement != null) { @@ -135,7 +135,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = return () => removeStopPropagationListeners(portalElement, props.stopPropagationEvents); } return undefined; - }, [props.stopPropagationEvents]); + }, [portalElement, props.stopPropagationEvents]); // Only render `children` once this component has mounted in a browser environment, so they are // immediately attached to the DOM tree and can do DOM things like measuring or `autoFocus`. From 26555c7dadbb14123e4b9b615d7fcd91cb12c90a Mon Sep 17 00:00:00 2001 From: braeden Date: Mon, 3 Jul 2023 23:59:32 -0400 Subject: [PATCH 09/11] Destructure --- .../core/src/components/portal/portal.tsx | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index 554e009570..addd412f64 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -74,19 +74,22 @@ const PORTAL_LEGACY_CONTEXT_TYPES: ValidationMap = { * * @see https://blueprintjs.com/docs/#core/components/portal */ -export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = {}) { +export function Portal( + { className, stopPropagationEvents, container, onChildrenMount, children }: PortalProps, + legacyContext: PortalLegacyContext = {}, +) { const context = React.useContext(PortalContext); - const portalContainer = props.container ?? context.portalContainer ?? document?.body; + const portalContainer = container ?? context.portalContainer ?? document?.body; const [portalElement, setPortalElement] = React.useState(); const createPortalElement = React.useCallback(() => { const newPortalElement = document.createElement("div"); newPortalElement.classList.add(Classes.PORTAL); - maybeAddClass(newPortalElement.classList, props.className); // directly added to this portal element + maybeAddClass(newPortalElement.classList, className); // directly added to this portal element maybeAddClass(newPortalElement.classList, context.portalClassName); // added via PortalProvider context - addStopPropagationListeners(newPortalElement, props.stopPropagationEvents); + addStopPropagationListeners(newPortalElement, stopPropagationEvents); // TODO: remove legacy context support in Blueprint v6.0 const { blueprintPortalClassName } = legacyContext; @@ -96,7 +99,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = } return newPortalElement; - }, [props.className, props.stopPropagationEvents, context.portalClassName, legacyContext]); + }, [className, context.portalClassName, stopPropagationEvents, legacyContext]); // create the container element & attach it to the DOM React.useEffect(() => { @@ -108,34 +111,34 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = setPortalElement(newPortalElement); return () => { - removeStopPropagationListeners(newPortalElement, props.stopPropagationEvents); + removeStopPropagationListeners(newPortalElement, stopPropagationEvents); newPortalElement.remove(); setPortalElement(undefined); }; - }, [portalContainer, createPortalElement, props.stopPropagationEvents]); + }, [portalContainer, createPortalElement, stopPropagationEvents]); // wait until next successful render to invoke onChildrenMount callback React.useEffect(() => { if (portalElement != null) { - props.onChildrenMount?.(); + onChildrenMount?.(); } - }, [portalElement, props, props.onChildrenMount]); + }, [portalElement, onChildrenMount]); React.useEffect(() => { if (portalElement != null) { - maybeAddClass(portalElement.classList, props.className); - return () => maybeRemoveClass(portalElement.classList, props.className); + maybeAddClass(portalElement.classList, className); + return () => maybeRemoveClass(portalElement.classList, className); } return undefined; - }, [portalElement, props.className]); + }, [className, portalElement]); React.useEffect(() => { if (portalElement != null) { - addStopPropagationListeners(portalElement, props.stopPropagationEvents); - return () => removeStopPropagationListeners(portalElement, props.stopPropagationEvents); + addStopPropagationListeners(portalElement, stopPropagationEvents); + return () => removeStopPropagationListeners(portalElement, stopPropagationEvents); } return undefined; - }, [portalElement, props.stopPropagationEvents]); + }, [portalElement, stopPropagationEvents]); // Only render `children` once this component has mounted in a browser environment, so they are // immediately attached to the DOM tree and can do DOM things like measuring or `autoFocus`. @@ -143,7 +146,7 @@ export function Portal(props: PortalProps, legacyContext: PortalLegacyContext = if (typeof document === "undefined" || portalElement == null) { return null; } else { - return ReactDOM.createPortal(props.children, portalElement); + return ReactDOM.createPortal(children, portalElement); } } From 3ad274fa4a01ffc7667c3476f12acdf279420c5a Mon Sep 17 00:00:00 2001 From: braeden Date: Tue, 4 Jul 2023 00:13:56 -0400 Subject: [PATCH 10/11] Stabalize legacyContext ref --- packages/core/src/components/portal/portal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index addd412f64..6233df4126 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -92,14 +92,14 @@ export function Portal( addStopPropagationListeners(newPortalElement, stopPropagationEvents); // TODO: remove legacy context support in Blueprint v6.0 - const { blueprintPortalClassName } = legacyContext; + const blueprintPortalClassName = legacyContext.blueprintPortalClassName; if (blueprintPortalClassName != null && blueprintPortalClassName !== "") { console.error(Errors.PORTAL_LEGACY_CONTEXT_API); maybeAddClass(newPortalElement.classList, blueprintPortalClassName); // added via legacy context } return newPortalElement; - }, [className, context.portalClassName, stopPropagationEvents, legacyContext]); + }, [className, context.portalClassName, legacyContext.blueprintPortalClassName, stopPropagationEvents]); // create the container element & attach it to the DOM React.useEffect(() => { From 4d0454fdcb0185b961c3d80848670aae3e43bdd0 Mon Sep 17 00:00:00 2001 From: Braeden Smith Date: Fri, 7 Jul 2023 10:57:05 -0400 Subject: [PATCH 11/11] Update docs --- packages/core/src/components/portal/portal.md | 11 +++++++---- packages/core/src/components/portal/portal.tsx | 2 +- packages/core/src/context/portal/portalProvider.tsx | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/core/src/components/portal/portal.md b/packages/core/src/components/portal/portal.md index 677d021bba..1fcda414e1 100644 --- a/packages/core/src/components/portal/portal.md +++ b/packages/core/src/components/portal/portal.md @@ -7,8 +7,11 @@ need to use a Portal directly; this documentation is provided simply for referen @## DOM Behavior -__Portal__ component functions like a declarative `appendChild()`, or jQuery's -`$.fn.appendTo()`. The children of a __Portal__ are inserted into a new child of the document ``. +__Portal__ component functions like a declarative `appendChild()`. The children of a __Portal__ are inserted into a *new child* of the target element. This target element is determined in the following order: +1. The `container` prop, if specified +2. The `portalContainer` from the closest [PortalProvider](#core/context/portal-provider), if specified +3. Otherwise `document.body` + __Portal__ is used inside [Overlay](#core/components/overlay) to actually overlay the content on the application. @@ -28,10 +31,10 @@ apply `position: absolute` to the `` tag. @## React context options -__Portal__ supports some customization through [React context](https://reactjs.org/docs/context.html). +__Portal__ supports some customization through [React context](https://react.dev/learn/passing-data-deeply-with-context). Using this API can be helpful if you need to apply some custom styling or logic to _all_ Blueprint components which use portals (popovers, tooltips, dialogs, etc.). You can do so by rendering a -[PortalProvider](#core/context/portal/portal-provider) in your React tree +[PortalProvider](#core/context/portal-provider) in your React tree (usually, this should be done near the root of your application). ```tsx diff --git a/packages/core/src/components/portal/portal.tsx b/packages/core/src/components/portal/portal.tsx index 6233df4126..2ee0bb0fe9 100644 --- a/packages/core/src/components/portal/portal.tsx +++ b/packages/core/src/components/portal/portal.tsx @@ -34,7 +34,7 @@ export interface PortalProps extends Props { /** * The HTML element that children will be mounted to. * - * @default PortalContext.portalContainer ?? document.body + * @default PortalProvider#portalContainer ?? document.body */ container?: HTMLElement; diff --git a/packages/core/src/context/portal/portalProvider.tsx b/packages/core/src/context/portal/portalProvider.tsx index d9449c4c98..cf292db214 100644 --- a/packages/core/src/context/portal/portalProvider.tsx +++ b/packages/core/src/context/portal/portalProvider.tsx @@ -19,7 +19,7 @@ import * as React from "react"; export interface PortalContextOptions { /** Additional CSS classes to add to all `Portal` elements in this React context. */ portalClassName?: string; - /** The HTML element that all `Portal` elements in this React context will be mounted to. */ + /** The HTML element that all `Portal` elements in this React context will be added as children to */ portalContainer?: HTMLElement; }