From 4c598d5a3437b0ab56e2917e79feca17cc89d702 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 25 Mar 2025 14:20:48 -0700 Subject: [PATCH] feat(alert): add slot for customization --- core/src/components.d.ts | 8 ++++++++ core/src/components/alert/alert-interface.ts | 5 +++-- core/src/components/alert/alert.tsx | 9 ++++++++- core/src/utils/overlays.ts | 20 +++++++++++++++++-- packages/angular/src/directives/proxies.ts | 4 ++-- .../standalone/src/directives/proxies.ts | 4 ++-- 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/core/src/components.d.ts b/core/src/components.d.ts index c458b85150..cd38426119 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -233,6 +233,10 @@ export namespace Components { * Array of buttons to be added to the alert. */ "buttons": (AlertButton | string)[]; + /** + * The component to display inside of the alert. + */ + "component"?: ComponentRef; /** * Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. */ @@ -5023,6 +5027,10 @@ declare namespace LocalJSX { * Array of buttons to be added to the alert. */ "buttons"?: (AlertButton | string)[]; + /** + * The component to display inside of the alert. + */ + "component"?: ComponentRef; /** * Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. */ diff --git a/core/src/components/alert/alert-interface.ts b/core/src/components/alert/alert-interface.ts index 326d74484d..f4ea72af21 100644 --- a/core/src/components/alert/alert-interface.ts +++ b/core/src/components/alert/alert-interface.ts @@ -1,7 +1,8 @@ -import type { AnimationBuilder, LiteralUnion, Mode, TextFieldTypes } from '../../interface'; +import type { AnimationBuilder, LiteralUnion, Mode, TextFieldTypes, ComponentRef } from '../../interface'; import type { IonicSafeString } from '../../utils/sanitization'; -export interface AlertOptions { +export interface AlertOptions { + component?: T; header?: string; subHeader?: string; message?: string | IonicSafeString; diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx index dc598fbdf0..6e052b5b55 100644 --- a/core/src/components/alert/alert.tsx +++ b/core/src/components/alert/alert.tsx @@ -22,7 +22,7 @@ import { getClassMap } from '@utils/theme'; import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; -import type { AnimationBuilder, CssClassMap, OverlayInterface, FrameworkDelegate } from '../../interface'; +import type { AnimationBuilder, CssClassMap, OverlayInterface, FrameworkDelegate, ComponentRef } from '../../interface'; import type { OverlayEventDetail } from '../../utils/overlays-interface'; import type { IonicSafeString } from '../../utils/sanitization'; @@ -150,6 +150,11 @@ export class Alert implements ComponentInterface, OverlayInterface { */ @Prop() htmlAttributes?: { [key: string]: any }; + /** + * The component to display inside of the alert. + */ + @Prop() component?: ComponentRef; + /** * If `true`, the alert will open. If `false`, the alert will close. * Use this if you need finer grained control over presentation, otherwise @@ -814,6 +819,8 @@ export class Alert implements ComponentInterface, OverlayInterface { {this.renderAlertInputs()} {this.renderAlertButtons()} + + diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 90a4062840..1b697c2bde 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -115,12 +115,28 @@ export const createOverlay = ( return window.customElements.whenDefined(tagName).then(() => { const element = document.createElement(tagName) as HTMLIonOverlayElement; element.classList.add('overlay-hidden'); + + const { component, ...restOpts } = (opts ?? {}) as { component?: HTMLElement } & Record; /** - * Convert the passed in overlay options into props + * If the overlay is a shadow component, then + * convert the passed in overlay options into props * that get passed down into the new overlay. + * + * If the overlay is a scoped component, the `component` option + * must be appended directly to the overlay element. Since `component` + * is not set via the `attachComponent` method (used for shadow components), + * it needs to be manually added to the overlay element. + */ - Object.assign(element, { ...opts, hasController: true }); + Object.assign(element, { + ...(element.shadowRoot ? opts : restOpts), + hasController: true + }); + + if (!element.shadowRoot && component) { + element.appendChild(component); + } // append the overlay element to the document body getAppRoot(document).appendChild(element); diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index cfa828e322..74e37aac83 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -125,7 +125,7 @@ Shorthand for ionActionSheetDidDismiss. @ProxyCmp({ - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], + inputs: ['animated', 'backdropDismiss', 'buttons', 'component', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'] }) @Component({ @@ -133,7 +133,7 @@ Shorthand for ionActionSheetDidDismiss. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], + inputs: ['animated', 'backdropDismiss', 'buttons', 'component', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], }) export class IonAlert { protected el: HTMLIonAlertElement; diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index c7d111f7be..59e5586926 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -205,7 +205,7 @@ Shorthand for ionActionSheetDidDismiss. @ProxyCmp({ defineCustomElementFn: defineIonAlert, - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], + inputs: ['animated', 'backdropDismiss', 'buttons', 'component', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss'] }) @Component({ @@ -213,7 +213,7 @@ Shorthand for ionActionSheetDidDismiss. changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], + inputs: ['animated', 'backdropDismiss', 'buttons', 'component', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'isOpen', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent', 'trigger'], standalone: true }) export class IonAlert {