From a3c23908b11a8cab1769e4e7c0b423cc8ffd2ee2 Mon Sep 17 00:00:00 2001 From: Adi Dahiya Date: Fri, 26 Jan 2024 12:07:35 -0500 Subject: [PATCH 1/4] WIP fix useOverlayStack --- .../core/src/components/overlay2/overlay2.tsx | 21 ++++++++++++------- .../core/src/components/popover/popover.md | 4 ++-- .../core/src/components/popover/popover.tsx | 2 +- packages/core/src/components/portal/portal.md | 14 ++++++------- .../src/components/toast/overlayToaster.tsx | 2 +- .../components/toast/overlayToasterProps.ts | 2 +- .../src/context/overlays/overlaysProvider.tsx | 12 +++++++---- packages/core/src/docs/variables.md | 2 +- .../hooks/overlays/useLegacyOverlayStack.ts | 3 +++ .../src/hooks/overlays/useOverlayStack.ts | 10 +++++++++ packages/core/test/dialog/dialogTests.tsx | 2 +- .../docs-app/src/components/blueprintDocs.tsx | 12 +++++------ .../docs-theme/src/components/scrollbar.ts | 2 +- packages/table-dev-app/src/index.tsx | 8 +++---- 14 files changed, 59 insertions(+), 37 deletions(-) diff --git a/packages/core/src/components/overlay2/overlay2.tsx b/packages/core/src/components/overlay2/overlay2.tsx index 4c45bb6a00..870a97da79 100644 --- a/packages/core/src/components/overlay2/overlay2.tsx +++ b/packages/core/src/components/overlay2/overlay2.tsx @@ -36,8 +36,7 @@ import { setRef, } from "../../common/utils"; import { hasDOMEnvironment } from "../../common/utils/domUtils"; -import { useOverlayStack } from "../../hooks/overlays/useOverlayStack"; -import { usePrevious } from "../../hooks/usePrevious"; +import { useOverlayStack, usePrevious } from "../../hooks"; import type { OverlayProps } from "../overlay/overlayProps"; import { getKeyboardFocusableElements } from "../overlay/overlayUtils"; import { Portal } from "../portal/portal"; @@ -116,7 +115,8 @@ export const Overlay2 = React.forwardRef((props, } = props; useOverlay2Validation(props); - const { closeOverlay, getLastOpened, getThisOverlayAndDescendants, openOverlay } = useOverlayStack(); + const { closeOverlay, getLastOpened, getThisOverlayAndDescendants, openOverlay, getStack } = useOverlayStack(); + const stack = getStack(); const [isAutoFocusing, setIsAutoFocusing] = React.useState(false); const [hasEverOpened, setHasEverOpened] = React.useState(false); @@ -210,7 +210,7 @@ export const Overlay2 = React.forwardRef((props, [autoFocus, bringFocusInsideOverlay, enforceFocus, handleDocumentFocus, hasBackdrop, id, usePortal], ); - const handleDocumentClick = React.useCallback( + const handleDocumentMousedown = React.useCallback( (e: MouseEvent) => { if (instance.current == null) { return; @@ -274,7 +274,8 @@ export const Overlay2 = React.forwardRef((props, } if (canOutsideClickClose && !hasBackdrop) { - document.addEventListener("mousedown", handleDocumentClick); + console.log("adding document mousedown listener, stack is: ", stack); + document.addEventListener("mousedown", handleDocumentMousedown); } setRef(lastActiveElementBeforeOpened, getActiveElement(getRef(containerElement))); @@ -284,10 +285,11 @@ export const Overlay2 = React.forwardRef((props, canOutsideClickClose, enforceFocus, getLastOpened, - handleDocumentClick, + handleDocumentMousedown, handleDocumentFocus, hasBackdrop, openOverlay, + stack, ]); const overlayWillClose = React.useCallback(() => { @@ -296,8 +298,9 @@ export const Overlay2 = React.forwardRef((props, } document.removeEventListener("focus", handleDocumentFocus, /* useCapture */ true); - document.removeEventListener("mousedown", handleDocumentClick); + document.removeEventListener("mousedown", handleDocumentMousedown); + console.log("closing overlay", instance.current); closeOverlay(instance.current); const lastOpenedOverlay = getLastOpened(); if (lastOpenedOverlay !== undefined) { @@ -309,7 +312,7 @@ export const Overlay2 = React.forwardRef((props, document.addEventListener("focus", lastOpenedOverlay.handleDocumentFocus, /* useCapture */ true); } } - }, [closeOverlay, getLastOpened, handleDocumentClick, handleDocumentFocus]); + }, [closeOverlay, getLastOpened, handleDocumentMousedown, handleDocumentFocus]); const prevIsOpen = usePrevious(isOpen) ?? false; React.useEffect(() => { @@ -332,6 +335,8 @@ export const Overlay2 = React.forwardRef((props, React.useEffect(() => { return () => { if (isOpen) { + // if the overlay is still open, we need to run cleanup code to remove some event handlers + console.log("----- unmounting, running overlayWillClose"); overlayWillClose(); } }; diff --git a/packages/core/src/components/popover/popover.md b/packages/core/src/components/popover/popover.md index 277a7d76d1..c983204b06 100644 --- a/packages/core/src/components/popover/popover.md +++ b/packages/core/src/components/popover/popover.md @@ -426,7 +426,7 @@ performance. If your components are not updating in a synchronous fashion as exp `setTimeout` to wait for asynchronous Popover rendering to catch up: ```tsx -import { Classes, Overlay, Popover } from "@blueprintjs/core"; +import { Classes, Overlay2, Popover } from "@blueprintjs/core"; import { assert } from "chai"; import { mount } from "enzyme"; import { Target } from "react-popper"; @@ -445,7 +445,7 @@ wrapper.find(`.${Classes.POPOVER}`).hostNodes().simulate("mouseleave"); setTimeout(() => { // Popover delays closing using setTimeout, so need to defer this check too. - const isOpen = wrapper.find(Overlay).prop("isOpen"); + const isOpen = wrapper.find(Overlay2).prop("isOpen"); assert.equal(isOpen, false); }); ``` diff --git a/packages/core/src/components/popover/popover.tsx b/packages/core/src/components/popover/popover.tsx index 7d5831b295..38782d736f 100644 --- a/packages/core/src/components/popover/popover.tsx +++ b/packages/core/src/components/popover/popover.tsx @@ -206,7 +206,7 @@ export class Popover< public targetRef = React.createRef(); /** - * Overlay transition container element ref. + * Overlay2 transition container element ref. */ private transitionContainerElement = React.createRef(); diff --git a/packages/core/src/components/portal/portal.md b/packages/core/src/components/portal/portal.md index 67052a6487..52363404be 100644 --- a/packages/core/src/components/portal/portal.md +++ b/packages/core/src/components/portal/portal.md @@ -1,19 +1,19 @@ @# Portal -The __Portal__ component renders its children into a new DOM "subtree" outside of the current component -hierarchy. It is an essential piece of the [Overlay](#core/components/overlay) component, responsible for +The **Portal** component renders its children into a new DOM "subtree" outside of the current component +hierarchy. It is an essential piece of the [Overlay2](#core/components/overlay2) component, responsible for ensuring that the overlay contents appear above the rest of the application. In most cases, you do not need to use a Portal directly; this documentation is provided only for reference. @## DOM Behavior -__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: +**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 +**Portal** is used inside [Overlay2](#core/components/overlay2) to actually overlay the content on the application.
@@ -31,7 +31,7 @@ apply `position: absolute` to the `` tag. @## React context options -__Portal__ supports some customization through [React context](https://react.dev/learn/passing-data-deeply-with-context). +**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-provider) in your React tree @@ -63,7 +63,7 @@ This feature uses React's legacy context API. Support for this API will be remov
-__Portal__ supports the following options via the [React legacy context API](https://reactjs.org/docs/legacy-context.html). +**Portal** supports the following options via the [React legacy context API](https://reactjs.org/docs/legacy-context.html). To use them, supply a child context to a subtree that contains the Portals you want to customize. @interface PortalLegacyContext diff --git a/packages/core/src/components/toast/overlayToaster.tsx b/packages/core/src/components/toast/overlayToaster.tsx index d2681f719b..42e046db25 100644 --- a/packages/core/src/components/toast/overlayToaster.tsx +++ b/packages/core/src/components/toast/overlayToaster.tsx @@ -235,7 +235,7 @@ export class OverlayToaster extends AbstractPureComponent]; -const initialOverlaysState: OverlaysContextState = { hasProvider: false, stack: [] }; +const initialStateWithoutProvider: OverlaysContextState = { hasProvider: false, stack: [] }; +const initialStateWithProvider: OverlaysContextState = { hasProvider: true, stack: [] }; const noOpDispatch: React.Dispatch = () => null; /** @@ -54,7 +55,10 @@ const noOpDispatch: React.Dispatch = () => null; * * For more information, see the [OverlaysProvider documentation](https://blueprintjs.com/docs/#core/context/overlays-provider). */ -export const OverlaysContext = React.createContext([initialOverlaysState, noOpDispatch]); +export const OverlaysContext = React.createContext([ + initialStateWithoutProvider, + noOpDispatch, +]); /** * N.B. this is exported in order for `useOverlayStack()` to implement backwards-compatibility for overlays @@ -73,7 +77,7 @@ export const overlaysReducer = (state: OverlaysContextState, action: OverlayStac newStack.splice(index, 1); return { ...state, stack: newStack }; case "RESET_STACK": - return initialOverlaysState; + return { ...state, stack: [] }; default: return state; } @@ -90,6 +94,6 @@ export interface OverlaysProviderProps { * @see https://blueprintjs.com/docs/#core/context/overlays-provider */ export const OverlaysProvider = ({ children }: OverlaysProviderProps) => { - const contextValue = React.useReducer(overlaysReducer, { ...initialOverlaysState, hasProvider: true }); + const contextValue = React.useReducer(overlaysReducer, initialStateWithProvider); return {children}; }; diff --git a/packages/core/src/docs/variables.md b/packages/core/src/docs/variables.md index 537aaaa6dd..0b1228623d 100644 --- a/packages/core/src/docs/variables.md +++ b/packages/core/src/docs/variables.md @@ -89,7 +89,7 @@ without writing much CSS. Here are some resources for learning flexbox: @## Layering Blueprint provides variables for three z-index layers. This should be enough for most use cases, -especially if you make correct use of [stacking context][MDN]. [Overlay](#core/components/overlay) +especially if you make correct use of [stacking context][MDN]. [Overlay2](#core/components/overlay2) components such as dialogs and popovers use these z-index values to configure their stacking contexts. diff --git a/packages/core/src/hooks/overlays/useLegacyOverlayStack.ts b/packages/core/src/hooks/overlays/useLegacyOverlayStack.ts index b9a6a0b9a4..0491acf890 100644 --- a/packages/core/src/hooks/overlays/useLegacyOverlayStack.ts +++ b/packages/core/src/hooks/overlays/useLegacyOverlayStack.ts @@ -99,9 +99,12 @@ export function useLegacyOverlayStack(): UseOverlayStackReturnValue { [stack], ); + const getStack = React.useCallback(() => stack, [stack]); + return { closeOverlay, getLastOpened, + getStack, getThisOverlayAndDescendants, openOverlay, resetStack, diff --git a/packages/core/src/hooks/overlays/useOverlayStack.ts b/packages/core/src/hooks/overlays/useOverlayStack.ts index 384926aedd..199e776dd8 100644 --- a/packages/core/src/hooks/overlays/useOverlayStack.ts +++ b/packages/core/src/hooks/overlays/useOverlayStack.ts @@ -35,6 +35,8 @@ export interface UseOverlayStackReturnValue { */ getLastOpened: () => OverlayInstance | undefined; + getStack: () => OverlayInstance[]; + /** * @param overlay current overlay * @returns a list of the current overlay and all overlays which are descendants of it. @@ -68,6 +70,7 @@ export function useOverlayStack(): UseOverlayStackReturnValue { const getThisOverlayAndDescendants = React.useCallback( (overlay: OverlayInstance) => { + console.log("looking for overlay and descendants in stack:", stack, overlay.containerElement.current); const stackIndex = stack.findIndex(o => o.id === overlay.id); return stack.slice(stackIndex); }, @@ -103,6 +106,12 @@ export function useOverlayStack(): UseOverlayStackReturnValue { [dispatch, stack], ); + const getStack = React.useCallback(() => stack, [stack]); + + React.useEffect(() => { + console.info("overlay stack", stack); + }, [stack]); + if (!state.hasProvider) { if (isNodeEnv("development")) { console.error(OVERLAY2_REQUIRES_OVERLAY_PROVDER); @@ -113,6 +122,7 @@ export function useOverlayStack(): UseOverlayStackReturnValue { return { closeOverlay, getLastOpened, + getStack, getThisOverlayAndDescendants, openOverlay, resetStack, diff --git a/packages/core/test/dialog/dialogTests.tsx b/packages/core/test/dialog/dialogTests.tsx index b18de8168b..42504170a9 100644 --- a/packages/core/test/dialog/dialogTests.tsx +++ b/packages/core/test/dialog/dialogTests.tsx @@ -197,7 +197,7 @@ describe("", () => { }); }); - // N.B. everything else about Dialog is tested by Overlay + // N.B. everything else about Dialog is tested by Overlay2 function renderDialogBodyAndFooter(): React.JSX.Element[] { return [ diff --git a/packages/docs-app/src/components/blueprintDocs.tsx b/packages/docs-app/src/components/blueprintDocs.tsx index bb82e5a580..901880557c 100644 --- a/packages/docs-app/src/components/blueprintDocs.tsx +++ b/packages/docs-app/src/components/blueprintDocs.tsx @@ -107,9 +107,9 @@ export class BlueprintDocs extends React.Component ); return ( - - - + + + - - - + + + ); } diff --git a/packages/docs-theme/src/components/scrollbar.ts b/packages/docs-theme/src/components/scrollbar.ts index b506411b15..48675da8b0 100644 --- a/packages/docs-theme/src/components/scrollbar.ts +++ b/packages/docs-theme/src/components/scrollbar.ts @@ -18,7 +18,7 @@ import { Classes } from "@blueprintjs/core"; /** * Inject some CSS style rules into a new `