diff --git a/core/src/components/popover/popover.scss b/core/src/components/popover/popover.scss index da67813ad7..9468bc1020 100644 --- a/core/src/components/popover/popover.scss +++ b/core/src/components/popover/popover.scss @@ -44,6 +44,25 @@ z-index: $z-index-overlay; } +/** + * With nested popovers, only the popover that + * was first presented has a popover. This is the + * parent popover and allows us to easily + * track all nested popovers from a single backdrop. + * As a result, nested popovers do not have backdrops + * and their container should not be clickable. + * This ensures that users can click the parent backdrop + * while still interacting with the nested popover inner + * content. + */ +:host(.popover-nested) { + pointer-events: none; +} + +:host(.popover-nested) .popover-wrapper { + pointer-events: auto; +} + :host(.overlay-hidden) { display: none; } diff --git a/core/src/components/popover/test/nested/e2e.ts b/core/src/components/popover/test/nested/e2e.ts new file mode 100644 index 0000000000..cf53d858f5 --- /dev/null +++ b/core/src/components/popover/test/nested/e2e.ts @@ -0,0 +1,180 @@ +import type { E2EPage } from '@stencil/core/testing'; +import { newE2EPage } from '@stencil/core/testing'; + +describe('nested popovers', () => { + let page: E2EPage; + + beforeEach(async () => { + page = await newE2EPage({ url: '/src/components/popover/test/nested?ionic:_testing=true' }); + }); + + test('nested popover should render correctly', async () => { + const screenshotCompares = []; + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + + await page.click('#first-trigger'); + + await ionPopoverDidPresent.next(); + + const parentPopover = await page.find('.parent-popover'); + expect(parentPopover).not.toHaveClass('overlay-hidden'); + + await page.click('#open-with-popover'); + + await ionPopoverDidPresent.next(); + + const nestedPopover = await page.find('.child-popover-one'); + expect(nestedPopover).not.toHaveClass('overlay-hidden'); + + screenshotCompares.push(await page.compareScreenshot()); + + for (const screenshotCompare of screenshotCompares) { + expect(screenshotCompare).toMatchScreenshot(); + } + }); + + test('nested popover should dismiss when clicking backdrop', async () => { + const screenshotCompares = []; + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + const ionPopoverDidDismiss = await page.spyOnEvent('ionPopoverDidDismiss'); + + await page.click('#first-trigger'); + + await ionPopoverDidPresent.next(); + + const parentPopover = await page.find('.parent-popover'); + expect(parentPopover).not.toHaveClass('overlay-hidden'); + + await page.click('#open-with-popover'); + + await ionPopoverDidPresent.next(); + + const nestedPopover = await page.find('.child-popover-one'); + expect(nestedPopover).not.toHaveClass('overlay-hidden'); + + screenshotCompares.push(await page.compareScreenshot()); + + const backdrop = await page.find('.parent-popover >>> ion-backdrop'); + await backdrop.click(); + + await ionPopoverDidDismiss.next(); + + const nestedPopoverAgain = await page.find('.child-popover-one'); + expect(nestedPopoverAgain).toHaveClass('overlay-hidden'); + + screenshotCompares.push(await page.compareScreenshot()); + + for (const screenshotCompare of screenshotCompares) { + expect(screenshotCompare).toMatchScreenshot(); + } + }); + + test('nested popover should be interactive', async () => { + const screenshotCompares = []; + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + + await page.click('#first-trigger'); + + await ionPopoverDidPresent.next(); + + await page.click('#share-popover'); + + await ionPopoverDidPresent.next(); + + await page.click('#share-other-popover'); + + await ionPopoverDidPresent.next(); + + const nestedPopover = await page.find('.child-popover-three'); + expect(nestedPopover).not.toHaveClass('overlay-hidden'); + + screenshotCompares.push(await page.compareScreenshot()); + + for (const screenshotCompare of screenshotCompares) { + expect(screenshotCompare).toMatchScreenshot(); + } + }); + + test('clicking backdrop should dismiss multiple levels of popover', async () => { + const screenshotCompares = []; + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + const ionPopoverDidDismiss = await page.spyOnEvent('ionPopoverDidDismiss'); + + await page.click('#first-trigger'); + + await ionPopoverDidPresent.next(); + + await page.click('#share-popover'); + + await ionPopoverDidPresent.next(); + + await page.click('#share-other-popover'); + + await ionPopoverDidPresent.next(); + + const backdrop = await page.find('.parent-popover >>> ion-backdrop'); + await backdrop.click(); + + await ionPopoverDidDismiss.next(); + + const nestedPopoverOne = await page.find('.child-popover-one'); + expect(nestedPopoverOne).toHaveClass('overlay-hidden'); + + const nestedPopoverTwo = await page.find('.child-popover-three'); + expect(nestedPopoverTwo).toHaveClass('overlay-hidden'); + + screenshotCompares.push(await page.compareScreenshot()); + + for (const screenshotCompare of screenshotCompares) { + expect(screenshotCompare).toMatchScreenshot(); + } + }); + + test('should dismiss sibling nested popover when another sibling popover is opened', async () => { + const screenshotCompares = []; + + const ionPopoverDidPresent = await page.spyOnEvent('ionPopoverDidPresent'); + const ionPopoverDidDismiss = await page.spyOnEvent('ionPopoverDidDismiss'); + + await page.click('#first-trigger'); + + await ionPopoverDidPresent.next(); + + await page.click('#open-with-popover'); + + await ionPopoverDidPresent.next(); + + const openWithPopover = await page.find('.child-popover-one'); + expect(openWithPopover).not.toHaveClass('overlay-hidden'); + + await page.click('#share-popover'); + + await ionPopoverDidPresent.next(); + await ionPopoverDidDismiss.next(); + + const openWithPopoverAgain = await page.find('.child-popover-one'); + const sharePopover = await page.find('.child-popover-two'); + expect(openWithPopoverAgain).toHaveClass('overlay-hidden'); + expect(sharePopover).not.toHaveClass('overlay-hidden'); + + screenshotCompares.push(await page.compareScreenshot()); + + const backdrop = await page.find('.parent-popover >>> ion-backdrop'); + await backdrop.click(); + + await ionPopoverDidDismiss.next(); + + const nestedPopoverAgain = await page.find('.child-popover-one'); + expect(nestedPopoverAgain).toHaveClass('overlay-hidden'); + + screenshotCompares.push(await page.compareScreenshot()); + + for (const screenshotCompare of screenshotCompares) { + expect(screenshotCompare).toMatchScreenshot(); + } + }) +}); diff --git a/core/src/components/popover/test/nested/index.html b/core/src/components/popover/test/nested/index.html index 0034322143..f11042fa7a 100644 --- a/core/src/components/popover/test/nested/index.html +++ b/core/src/components/popover/test/nested/index.html @@ -35,203 +35,6 @@ ion-item-divider { min-height: 1px; } - - - /* Ionic Variables and Theming. For more info, please see: - http://ionicframework.com/docs/theming/ */ - - /** Ionic CSS Variables **/ - :root { - /** primary **/ - --ion-color-primary: #3880ff; - --ion-color-primary-rgb: 56, 128, 255; - --ion-color-primary-contrast: #ffffff; - --ion-color-primary-contrast-rgb: 255, 255, 255; - --ion-color-primary-shade: #3171e0; - --ion-color-primary-tint: #4c8dff; - - /** secondary **/ - --ion-color-secondary: #3dc2ff; - --ion-color-secondary-rgb: 61, 194, 255; - --ion-color-secondary-contrast: #ffffff; - --ion-color-secondary-contrast-rgb: 255, 255, 255; - --ion-color-secondary-shade: #36abe0; - --ion-color-secondary-tint: #50c8ff; - - /** tertiary **/ - --ion-color-tertiary: #5260ff; - --ion-color-tertiary-rgb: 82, 96, 255; - --ion-color-tertiary-contrast: #ffffff; - --ion-color-tertiary-contrast-rgb: 255, 255, 255; - --ion-color-tertiary-shade: #4854e0; - --ion-color-tertiary-tint: #6370ff; - - /** success **/ - --ion-color-success: #2dd36f; - --ion-color-success-rgb: 45, 211, 111; - --ion-color-success-contrast: #ffffff; - --ion-color-success-contrast-rgb: 255, 255, 255; - --ion-color-success-shade: #28ba62; - --ion-color-success-tint: #42d77d; - - /** warning **/ - --ion-color-warning: #ffc409; - --ion-color-warning-rgb: 255, 196, 9; - --ion-color-warning-contrast: #000000; - --ion-color-warning-contrast-rgb: 0, 0, 0; - --ion-color-warning-shade: #e0ac08; - --ion-color-warning-tint: #ffca22; - - /** danger **/ - --ion-color-danger: #eb445a; - --ion-color-danger-rgb: 235, 68, 90; - --ion-color-danger-contrast: #ffffff; - --ion-color-danger-contrast-rgb: 255, 255, 255; - --ion-color-danger-shade: #cf3c4f; - --ion-color-danger-tint: #ed576b; - - /** dark **/ - --ion-color-dark: #222428; - --ion-color-dark-rgb: 34, 36, 40; - --ion-color-dark-contrast: #ffffff; - --ion-color-dark-contrast-rgb: 255, 255, 255; - --ion-color-dark-shade: #1e2023; - --ion-color-dark-tint: #383a3e; - - /** medium **/ - --ion-color-medium: #92949c; - --ion-color-medium-rgb: 146, 148, 156; - --ion-color-medium-contrast: #ffffff; - --ion-color-medium-contrast-rgb: 255, 255, 255; - --ion-color-medium-shade: #808289; - --ion-color-medium-tint: #9d9fa6; - - /** light **/ - --ion-color-light: #f4f5f8; - --ion-color-light-rgb: 244, 245, 248; - --ion-color-light-contrast: #000000; - --ion-color-light-contrast-rgb: 0, 0, 0; - --ion-color-light-shade: #d7d8da; - --ion-color-light-tint: #f5f6f9; - } - /* - @media (prefers-color-scheme: dark) { - body { - --ion-color-primary: #428cff; - --ion-color-primary-rgb: 66,140,255; - --ion-color-primary-contrast: #ffffff; - --ion-color-primary-contrast-rgb: 255,255,255; - --ion-color-primary-shade: #3a7be0; - --ion-color-primary-tint: #5598ff; - --ion-color-secondary: #50c8ff; - --ion-color-secondary-rgb: 80,200,255; - --ion-color-secondary-contrast: #ffffff; - --ion-color-secondary-contrast-rgb: 255,255,255; - --ion-color-secondary-shade: #46b0e0; - --ion-color-secondary-tint: #62ceff; - --ion-color-tertiary: #6a64ff; - --ion-color-tertiary-rgb: 106,100,255; - --ion-color-tertiary-contrast: #ffffff; - --ion-color-tertiary-contrast-rgb: 255,255,255; - --ion-color-tertiary-shade: #5d58e0; - --ion-color-tertiary-tint: #7974ff; - --ion-color-success: #2fdf75; - --ion-color-success-rgb: 47,223,117; - --ion-color-success-contrast: #000000; - --ion-color-success-contrast-rgb: 0,0,0; - --ion-color-success-shade: #29c467; - --ion-color-success-tint: #44e283; - --ion-color-warning: #ffd534; - --ion-color-warning-rgb: 255,213,52; - --ion-color-warning-contrast: #000000; - --ion-color-warning-contrast-rgb: 0,0,0; - --ion-color-warning-shade: #e0bb2e; - --ion-color-warning-tint: #ffd948; - --ion-color-danger: #ff4961; - --ion-color-danger-rgb: 255,73,97; - --ion-color-danger-contrast: #ffffff; - --ion-color-danger-contrast-rgb: 255,255,255; - --ion-color-danger-shade: #e04055; - --ion-color-danger-tint: #ff5b71; - --ion-color-dark: #f4f5f8; - --ion-color-dark-rgb: 244,245,248; - --ion-color-dark-contrast: #000000; - --ion-color-dark-contrast-rgb: 0,0,0; - --ion-color-dark-shade: #d7d8da; - --ion-color-dark-tint: #f5f6f9; - --ion-color-medium: #989aa2; - --ion-color-medium-rgb: 152,154,162; - --ion-color-medium-contrast: #000000; - --ion-color-medium-contrast-rgb: 0,0,0; - --ion-color-medium-shade: #86888f; - --ion-color-medium-tint: #a2a4ab; - --ion-color-light: #222428; - --ion-color-light-rgb: 34,36,40; - --ion-color-light-contrast: #ffffff; - --ion-color-light-contrast-rgb: 255,255,255; - --ion-color-light-shade: #1e2023; - --ion-color-light-tint: #383a3e; - } - .ios body { - --ion-background-color: #000000; - --ion-background-color-rgb: 0,0,0; - --ion-text-color: #ffffff; - --ion-text-color-rgb: 255,255,255; - --ion-color-step-50: #0d0d0d; - --ion-color-step-100: #1a1a1a; - --ion-color-step-150: #262626; - --ion-color-step-200: #333333; - --ion-color-step-250: #404040; - --ion-color-step-300: #4d4d4d; - --ion-color-step-350: #595959; - --ion-color-step-400: #666666; - --ion-color-step-450: #737373; - --ion-color-step-500: #808080; - --ion-color-step-550: #8c8c8c; - --ion-color-step-600: #999999; - --ion-color-step-650: #a6a6a6; - --ion-color-step-700: #b3b3b3; - --ion-color-step-750: #bfbfbf; - --ion-color-step-800: #cccccc; - --ion-color-step-850: #d9d9d9; - --ion-color-step-900: #e6e6e6; - --ion-color-step-950: #f2f2f2; - --ion-toolbar-background: #0d0d0d; - --ion-item-background: #000000; - --ion-card-background: #1c1c1d; - } - .md body { - --ion-background-color: #121212; - --ion-background-color-rgb: 18,18,18; - --ion-text-color: #ffffff; - --ion-text-color-rgb: 255,255,255; - --ion-border-color: #222222; - --ion-color-step-50: #1e1e1e; - --ion-color-step-100: #2a2a2a; - --ion-color-step-150: #363636; - --ion-color-step-200: #414141; - --ion-color-step-250: #4d4d4d; - --ion-color-step-300: #595959; - --ion-color-step-350: #656565; - --ion-color-step-400: #717171; - --ion-color-step-450: #7d7d7d; - --ion-color-step-500: #898989; - --ion-color-step-550: #949494; - --ion-color-step-600: #a0a0a0; - --ion-color-step-650: #acacac; - --ion-color-step-700: #b8b8b8; - --ion-color-step-750: #c4c4c4; - --ion-color-step-800: #d0d0d0; - --ion-color-step-850: #dbdbdb; - --ion-color-step-900: #e7e7e7; - --ion-color-step-950: #f3f3f3; - --ion-item-background: #1e1e1e; - --ion-toolbar-background: #1f1f1f; - --ion-tab-bar-background: #1f1f1f; - --ion-card-background: #1e1e1e; - } - } - */ @@ -243,10 +46,10 @@ @@ -262,6 +65,7 @@ trigger-action="click" trigger="open-with-popover" side="end" + class="child-popover-one" > @@ -318,6 +122,7 @@ trigger-action="click" trigger="share-popover" side="end" + class="child-popover-two" > @@ -337,6 +142,32 @@ AirDrop + + + Other + + + + + + Facebook + + + + Twitter + + + + LinkedIn + + +