From be9a399eeed37ae4a67add78ac1283ba0c5e4b14 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 21 Nov 2022 22:32:46 -0500 Subject: [PATCH] fix(popover): popover positions correctly on all frameworks (#26306) Resolves #25337 --- core/src/components/popover/popover.tsx | 77 ++++++++++--------- core/src/components/popover/utils.ts | 4 - .../createInlineOverlayComponent.tsx | 11 +++ .../overlay-components/PopoverComponent.tsx | 2 +- .../vue/src/vue-component-lib/overlays.ts | 1 + 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index a6f0e431f8..5bc652d20b 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -27,12 +27,7 @@ import { iosEnterAnimation } from './animations/ios.enter'; import { iosLeaveAnimation } from './animations/ios.leave'; import { mdEnterAnimation } from './animations/md.enter'; import { mdLeaveAnimation } from './animations/md.leave'; -import { - configureDismissInteraction, - configureKeyboardInteraction, - configureTriggerInteraction, - waitOneFrame, -} from './utils'; +import { configureDismissInteraction, configureKeyboardInteraction, configureTriggerInteraction } from './utils'; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. @@ -464,40 +459,48 @@ export class Popover implements ComponentInterface, PopoverInterface { } this.configureDismissInteraction(); - // TODO: FW-2773: Apply this to only the lazy build. - /** - * ionMount only needs to be emitted if the popover is inline. - */ this.ionMount.emit(); - /** - * Wait one raf before presenting the popover. - * This allows the lazy build enough time to - * calculate the popover dimensions for the animation. - */ - await waitOneFrame(); - this.currentTransition = present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, { - event: event || this.event, - size: this.size, - trigger: this.triggerEl, - reference: this.reference, - side: this.side, - align: this.alignment, + return new Promise((resolve) => { + /** + * Wait two request animation frame loops before presenting the popover. + * This allows the framework implementations enough time to mount + * the popover contents, so the bounding box is set when the popover + * transition starts. + * + * On Angular and React, a single raf is enough time, but for Vue + * we need to wait two rafs. As a result we are using two rafs for + * all frameworks to ensure the popover is presented correctly. + */ + raf(() => { + raf(async () => { + this.currentTransition = present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, { + event: event || this.event, + size: this.size, + trigger: this.triggerEl, + reference: this.reference, + side: this.side, + align: this.alignment, + }); + + await this.currentTransition; + + this.currentTransition = undefined; + + /** + * If popover is nested and was + * presented using the "Right" arrow key, + * we need to move focus to the first + * descendant inside of the popover. + */ + if (this.focusDescendantOnPresent) { + focusFirstDescendant(this.el, this.el); + } + + resolve(); + }); + }); }); - - await this.currentTransition; - - this.currentTransition = undefined; - - /** - * If popover is nested and was - * presented using the "Right" arrow key, - * we need to move focus to the first - * descendant inside of the popover. - */ - if (this.focusDescendantOnPresent) { - focusFirstDescendant(this.el, this.el); - } } /** diff --git a/core/src/components/popover/utils.ts b/core/src/components/popover/utils.ts index 6dfad7c08e..363db25c52 100644 --- a/core/src/components/popover/utils.ts +++ b/core/src/components/popover/utils.ts @@ -928,7 +928,3 @@ export const shouldShowArrow = (side: PositionSide, didAdjustBounds = false, ev? return true; }; - -export const waitOneFrame = () => { - return new Promise((resolve) => raf(() => resolve())); -}; diff --git a/packages/react/src/components/createInlineOverlayComponent.tsx b/packages/react/src/components/createInlineOverlayComponent.tsx index fac30f8a2e..41d04a0d90 100644 --- a/packages/react/src/components/createInlineOverlayComponent.tsx +++ b/packages/react/src/components/createInlineOverlayComponent.tsx @@ -55,6 +55,17 @@ export const createInlineOverlayComponent = ( componentDidMount() { this.componentDidUpdate(this.props); + /** + * Mount the inner component when the + * overlay is about to open. + * + * For ion-popover, this is when `ionMount` is emitted. + * For other overlays, this is when `willPresent` is emitted. + */ + this.ref.current?.addEventListener('ionMount', () => { + this.setState({ isOpen: true }); + }); + /** * Mount the inner component * when overlay is about to open. diff --git a/packages/react/test-app/src/pages/overlay-components/PopoverComponent.tsx b/packages/react/test-app/src/pages/overlay-components/PopoverComponent.tsx index 0d02dc53cd..c1355a9abc 100644 --- a/packages/react/test-app/src/pages/overlay-components/PopoverComponent.tsx +++ b/packages/react/test-app/src/pages/overlay-components/PopoverComponent.tsx @@ -83,4 +83,4 @@ const PopoverComponent: React.FC = () => { ); }; -export default PopoverComponent; +export default PopoverComponent; \ No newline at end of file diff --git a/packages/vue/src/vue-component-lib/overlays.ts b/packages/vue/src/vue-component-lib/overlays.ts index ede3ac4a37..b886c1d493 100644 --- a/packages/vue/src/vue-component-lib/overlays.ts +++ b/packages/vue/src/vue-component-lib/overlays.ts @@ -139,6 +139,7 @@ export const defineOverlayContainer = (name: string, defin const elementRef = ref(); onMounted(() => { + elementRef.value.addEventListener('ion-mount', () => isOpen.value = true); elementRef.value.addEventListener('will-present', () => isOpen.value = true); elementRef.value.addEventListener('did-dismiss', () => isOpen.value = false); });