diff --git a/BREAKING.md b/BREAKING.md index 5b2febefe0..282814ae96 100644 --- a/BREAKING.md +++ b/BREAKING.md @@ -101,12 +101,16 @@ Converted `ion-modal` to use [Shadow DOM](https://developer.mozilla.org/en-US/do If you were targeting the internals of `ion-modal` in your CSS, you will need to target the `backdrop` or `content` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables. +Developers dynamically creating modals using `document.createElement('ion-modal')` will now need to call `modal.remove()` after the modal has been dismissed if they want the modal to be removed from the DOM. + #### Popover Converted `ion-popover` to use [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM). If you were targeting the internals of `ion-popover` in your CSS, you will need to target the `backdrop`, `arrow`, or `content` [Shadow Parts](https://ionicframework.com/docs/theming/css-shadow-parts) instead, or use the provided CSS Variables. +Developers dynamically creating popovers using `document.createElement('ion-popover')` will now need to call `popover.remove()` after the popover has been dismissed if they want the popover to be removed from the DOM. + #### Radio The `RadioChangeEventDetail` interface has been removed. Instead, listen for the `ionChange` event on `ion-radio-group` and use the `RadioGroupChangeEventDetail` interface. diff --git a/core/src/components.d.ts b/core/src/components.d.ts index b7ad27a934..49cd4308bf 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -1532,6 +1532,7 @@ export namespace Components { * The horizontal line that displays at the top of a sheet modal. It is `true` by default when setting the `breakpoints` and `initialBreakpoint` properties. */ "handle"?: boolean; + "hasController": boolean; /** * Additional attributes to pass to the modal. */ @@ -1883,6 +1884,7 @@ export namespace Components { */ "event": any; "getParentPopover": () => Promise; + "hasController": boolean; /** * Additional attributes to pass to the popover. */ @@ -5220,6 +5222,7 @@ declare namespace LocalJSX { * The horizontal line that displays at the top of a sheet modal. It is `true` by default when setting the `breakpoints` and `initialBreakpoint` properties. */ "handle"?: boolean; + "hasController"?: boolean; /** * Additional attributes to pass to the modal. */ @@ -5509,6 +5512,7 @@ declare namespace LocalJSX { * The event to pass to the popover animation. */ "event"?: any; + "hasController"?: boolean; /** * Additional attributes to pass to the popover. */ diff --git a/core/src/components/modal/modal.ios.scss b/core/src/components/modal/modal.ios.scss index a5536aa1a6..d293f2500b 100644 --- a/core/src/components/modal/modal.ios.scss +++ b/core/src/components/modal/modal.ios.scss @@ -4,7 +4,7 @@ // iOS Modals // -------------------------------------------------- -:host(:first-of-type) { +:host { --backdrop-opacity: var(--ion-backdrop-opacity, 0.4); } diff --git a/core/src/components/modal/modal.md.scss b/core/src/components/modal/modal.md.scss index 3833e0fcf0..6a822e8918 100644 --- a/core/src/components/modal/modal.md.scss +++ b/core/src/components/modal/modal.md.scss @@ -5,16 +5,13 @@ // Material Design Modals // -------------------------------------------------- -:host(:first-of-type) { +:host { --backdrop-opacity: var(--ion-backdrop-opacity, 0.32); } @media only screen and (min-width: $modal-inset-min-width) and (min-height: $modal-inset-min-height-small) { :host { --border-radius: 2px; - } - - :host(:first-of-type) { --box-shadow: #{$modal-inset-box-shadow}; } } @@ -24,3 +21,4 @@ opacity: .01; } + diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 22fc85c8f1..37a08dec65 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -60,6 +60,9 @@ export class Modal implements ComponentInterface, OverlayInterface { @Element() el!: HTMLIonModalElement; + /** @internal */ + @Prop() hasController = false; + /** @internal */ @Prop() overlayIndex!: number; @@ -322,13 +325,13 @@ export class Modal implements ComponentInterface, OverlayInterface { * If using overlay inline * we potentially need to use the coreDelegate * so that this works in vanilla JS apps. - * If a user has already placed the overlay - * as a direct descendant of ion-app or - * the body, then we can assume that - * the overlay is already in the correct place. + * If a developer has presented this component + * via a controller, then we can assume + * the component is already in the + * correct place. */ const parentEl = this.el.parentNode as HTMLElement | null; - const inline = this.inline = parentEl !== null && parentEl.tagName !== 'ION-APP' && parentEl.tagName !== 'BODY'; + const inline = this.inline = parentEl !== null && !this.hasController; const delegate = this.workingDelegate = (inline) ? this.delegate || this.coreDelegate : this.delegate return { inline, delegate } diff --git a/core/src/components/modal/test/sheet/index.html b/core/src/components/modal/test/sheet/index.html index e841244bc0..7179436fa2 100644 --- a/core/src/components/modal/test/sheet/index.html +++ b/core/src/components/modal/test/sheet/index.html @@ -168,6 +168,9 @@ async function presentModal(options) { const modal = createModal(options); await modal.present(); + + await modal.onDidDismiss(); + modal.remove(); } async function presentCardModal() { diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index 1944265061..ec2c723ce4 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -56,6 +56,9 @@ export class Popover implements ComponentInterface, PopoverInterface { @Element() el!: HTMLIonPopoverElement; + /** @internal */ + @Prop() hasController = false; + /** @internal */ @Prop() delegate?: FrameworkDelegate; @@ -337,13 +340,13 @@ export class Popover implements ComponentInterface, PopoverInterface { * If using overlay inline * we potentially need to use the coreDelegate * so that this works in vanilla JS apps. - * If a user has already placed the overlay - * as a direct descendant of ion-app or - * the body, then we can assume that - * the overlay is already in the correct place. + * If a developer has presented this component + * via a controller, then we can assume + * the component is already in the + * correct place. */ const parentEl = this.el.parentNode as HTMLElement | null; - const inline = this.inline = parentEl !== null && parentEl.tagName !== 'ION-APP' && parentEl.tagName !== 'BODY'; + const inline = this.inline = parentEl !== null && !this.hasController; const delegate = this.workingDelegate = (inline) ? this.delegate || this.coreDelegate : this.delegate return { inline, delegate } diff --git a/core/src/css/core.scss b/core/src/css/core.scss index 7e33f1f4d6..d3440a3583 100644 --- a/core/src/css/core.scss +++ b/core/src/css/core.scss @@ -86,6 +86,30 @@ html.ios ion-modal .ion-page { } } +/** + * Subsequent modals should not have a backdrop/box shadow + * as it will cause the screen to appear to get progressively + * darker. With Ionic 6, declarative modals made it + * possible to have multiple non-presented modals in the DOM, + * so we could no longer rely on ion-modal:first-of-type. + * Here we disable the opacity/box-shadow for every modal + * that comes after the first presented modal. + * + * Note: ion-modal:not(.overlay-hidden):first-of-type + * does not match the first modal to not have + * the .overlay-hidden class, it will match the + * first modal in general only if it does not + * have the .overlay-hidden class. + * The :nth-child() pseudo-class has support + * for selectors which would help us here. At the + * time of writing it does not have great cross browser + * support. + */ +ion-modal:not(.overlay-hidden) ~ ion-modal { + --backdrop-opacity: 0; + --box-shadow: none; +} + // Ionic Colors // -------------------------------------------------- // Generates the color classes and variables based on the diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index cc21b3bb62..050a2ecc89 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -54,7 +54,7 @@ export const createOverlay = (tagName: string, * Convert the passed in overlay options into props * that get passed down into the new overlay. */ - Object.assign(element, { ...opts }); + Object.assign(element, { ...opts, hasController: true }); // append the overlay element to the document body getAppRoot(document).appendChild(element); diff --git a/core/src/utils/test/framework-delegate/framework-delegate.e2e.ts b/core/src/utils/test/framework-delegate/framework-delegate.e2e.ts new file mode 100644 index 0000000000..70a9993f92 --- /dev/null +++ b/core/src/utils/test/framework-delegate/framework-delegate.e2e.ts @@ -0,0 +1,35 @@ +import { newE2EPage } from '@stencil/core/testing'; + +test('framework-delegate: should present modal already at ion-app root', async () => { + const page = await newE2EPage({ url: '/src/utils/test/framework-delegate?ionic:_testing=true' }); + + const button = await page.find('#button-inline-root'); + await button.click(); + + const modal = await page.find('#inline-root'); + expect(modal).not.toBe(null); + await modal.waitForVisible(); + +}); + +test('framework-delegate: should present modal in content', async () => { + const page = await newE2EPage({ url: '/src/utils/test/framework-delegate?ionic:_testing=true' }); + + const button = await page.find('#button-inline-content'); + await button.click(); + + const modal = await page.find('#inline-content'); + expect(modal).not.toBe(null); + await modal.waitForVisible(); +}); + +test('framework-delegate: should present modal via controller', async () => { + const page = await newE2EPage({ url: '/src/utils/test/framework-delegate?ionic:_testing=true' }); + + const button = await page.find('#button-controller'); + await button.click(); + + const modal = await page.find('#controller'); + expect(modal).not.toBe(null); + await modal.waitForVisible(); +}); diff --git a/core/src/utils/test/framework-delegate/index.html b/core/src/utils/test/framework-delegate/index.html new file mode 100644 index 0000000000..43e51f0527 --- /dev/null +++ b/core/src/utils/test/framework-delegate/index.html @@ -0,0 +1,67 @@ + + + + + Framework Delegate + + + + + + + + + + + Hello World + + +
+ + + Modal - Inline + + + + + Open inline modal at ion-app root + Open inline modal inside of content + Open controller modal + + + + Hello World + + + +
+
+ + + + +