feat(alert): add slot for customization

This commit is contained in:
Maria Hutt
2025-03-25 14:20:48 -07:00
parent 1cfa915e8f
commit 4c598d5a34
6 changed files with 41 additions and 9 deletions

View File

@ -233,6 +233,10 @@ export namespace Components {
* Array of buttons to be added to the alert. * Array of buttons to be added to the alert.
*/ */
"buttons": (AlertButton | string)[]; "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. * 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. * Array of buttons to be added to the alert.
*/ */
"buttons"?: (AlertButton | string)[]; "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. * Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces.
*/ */

View File

@ -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'; import type { IonicSafeString } from '../../utils/sanitization';
export interface AlertOptions { export interface AlertOptions<T extends ComponentRef = ComponentRef> {
component?: T;
header?: string; header?: string;
subHeader?: string; subHeader?: string;
message?: string | IonicSafeString; message?: string | IonicSafeString;

View File

@ -22,7 +22,7 @@ import { getClassMap } from '@utils/theme';
import { config } from '../../global/config'; import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global'; 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 { OverlayEventDetail } from '../../utils/overlays-interface';
import type { IonicSafeString } from '../../utils/sanitization'; import type { IonicSafeString } from '../../utils/sanitization';
@ -150,6 +150,11 @@ export class Alert implements ComponentInterface, OverlayInterface {
*/ */
@Prop() htmlAttributes?: { [key: string]: any }; @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. * If `true`, the alert will open. If `false`, the alert will close.
* Use this if you need finer grained control over presentation, otherwise * Use this if you need finer grained control over presentation, otherwise
@ -814,6 +819,8 @@ export class Alert implements ComponentInterface, OverlayInterface {
{this.renderAlertInputs()} {this.renderAlertInputs()}
{this.renderAlertButtons()} {this.renderAlertButtons()}
<slot></slot>
</div> </div>
<div tabindex="0" aria-hidden="true"></div> <div tabindex="0" aria-hidden="true"></div>

View File

@ -115,12 +115,28 @@ export const createOverlay = <T extends HTMLIonOverlayElement>(
return window.customElements.whenDefined(tagName).then(() => { return window.customElements.whenDefined(tagName).then(() => {
const element = document.createElement(tagName) as HTMLIonOverlayElement; const element = document.createElement(tagName) as HTMLIonOverlayElement;
element.classList.add('overlay-hidden'); element.classList.add('overlay-hidden');
const { component, ...restOpts } = (opts ?? {}) as { component?: HTMLElement } & Record<string, any>;
/** /**
* 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. * 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 // append the overlay element to the document body
getAppRoot(document).appendChild(element); getAppRoot(document).appendChild(element);

View File

@ -125,7 +125,7 @@ Shorthand for ionActionSheetDidDismiss.
@ProxyCmp({ @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'] methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss']
}) })
@Component({ @Component({
@ -133,7 +133,7 @@ Shorthand for ionActionSheetDidDismiss.
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>', template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property // 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 { export class IonAlert {
protected el: HTMLIonAlertElement; protected el: HTMLIonAlertElement;

View File

@ -205,7 +205,7 @@ Shorthand for ionActionSheetDidDismiss.
@ProxyCmp({ @ProxyCmp({
defineCustomElementFn: defineIonAlert, 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'] methods: ['present', 'dismiss', 'onDidDismiss', 'onWillDismiss']
}) })
@Component({ @Component({
@ -213,7 +213,7 @@ Shorthand for ionActionSheetDidDismiss.
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>', template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property // 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 standalone: true
}) })
export class IonAlert { export class IonAlert {