diff --git a/core/api.txt b/core/api.txt index 09817a5b16..a964a58fad 100644 --- a/core/api.txt +++ b/core/api.txt @@ -50,6 +50,7 @@ ion-alert,prop,buttons,(string | AlertButton)[],[],false,false ion-alert,prop,cssClass,string | string[] | undefined,undefined,false,false ion-alert,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-alert,prop,header,string | undefined,undefined,false,false +ion-alert,prop,htmlAttributes,AlertAttributes | undefined,undefined,false,false ion-alert,prop,inputs,AlertInput[],[],false,false ion-alert,prop,keyboardClose,boolean,true,false,false ion-alert,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false @@ -1268,6 +1269,7 @@ ion-toast,prop,cssClass,string | string[] | undefined,undefined,false,false ion-toast,prop,duration,number,0,false,false ion-toast,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-toast,prop,header,string | undefined,undefined,false,false +ion-toast,prop,htmlAttributes,ToastAttributes | undefined,undefined,false,false ion-toast,prop,keyboardClose,boolean,false,false,false ion-toast,prop,leaveAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-toast,prop,message,IonicSafeString | string | undefined,undefined,false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index f9f06e39f4..9c7aad7ded 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -7,8 +7,10 @@ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; import { ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimeOptions, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, MenuChangeEventDetail, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerButton, PickerColumn, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, ViewController } from "./interface"; import { IonicSafeString } from "./utils/sanitization"; +import { AlertAttributes } from "./components/alert/alert-interface"; import { NavigationHookCallback } from "./components/route/route-interface"; import { SelectCompareFn } from "./components/select/select-interface"; +import { ToastAttributes } from "./components/toast/toast-interface"; export namespace Components { interface IonActionSheet { /** @@ -106,6 +108,10 @@ export namespace Components { * The main title in the heading of the alert. */ "header"?: string; + /** + * Additional attributes to pass to the alert. + */ + "htmlAttributes"?: AlertAttributes; /** * Array of input to show in the alert. */ @@ -2565,6 +2571,10 @@ export namespace Components { * Header to be shown in the toast. */ "header"?: string; + /** + * Additional attributes to pass to the toast. + */ + "htmlAttributes"?: ToastAttributes; /** * If `true`, the keyboard will be automatically dismissed when the overlay is presented. */ @@ -3409,6 +3419,10 @@ declare namespace LocalJSX { * The main title in the heading of the alert. */ "header"?: string; + /** + * Additional attributes to pass to the alert. + */ + "htmlAttributes"?: AlertAttributes; /** * Array of input to show in the alert. */ @@ -5894,6 +5908,10 @@ declare namespace LocalJSX { * Header to be shown in the toast. */ "header"?: string; + /** + * Additional attributes to pass to the toast. + */ + "htmlAttributes"?: ToastAttributes; /** * If `true`, the keyboard will be automatically dismissed when the overlay is presented. */ diff --git a/core/src/components/alert/alert-interface.ts b/core/src/components/alert/alert-interface.ts index 370943d0a7..a807a11eaf 100644 --- a/core/src/components/alert/alert-interface.ts +++ b/core/src/components/alert/alert-interface.ts @@ -13,6 +13,7 @@ export interface AlertOptions { backdropDismiss?: boolean; translucent?: boolean; animated?: boolean; + htmlAttributes?: AlertAttributes; mode?: Mode; keyboardClose?: boolean; @@ -22,6 +23,8 @@ export interface AlertOptions { leaveAnimation?: AnimationBuilder; } +export interface AlertAttributes extends JSXBase.HTMLAttributes {} + export interface AlertInput { type?: TextFieldTypes | 'checkbox' | 'radio' | 'textarea'; name?: string; diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx index 3aac3a0069..88ff5077ee 100644 --- a/core/src/components/alert/alert.tsx +++ b/core/src/components/alert/alert.tsx @@ -8,6 +8,7 @@ import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safe import { IonicSafeString, sanitizeDOMString } from '../../utils/sanitization'; import { getClassMap } from '../../utils/theme'; +import { AlertAttributes } from './alert-interface'; import { iosEnterAnimation } from './animations/ios.enter'; import { iosLeaveAnimation } from './animations/ios.leave'; import { mdEnterAnimation } from './animations/md.enter'; @@ -110,6 +111,11 @@ export class Alert implements ComponentInterface, OverlayInterface { */ @Prop() animated = true; + /** + * Additional attributes to pass to the alert. + */ + @Prop() htmlAttributes?: AlertAttributes; + /** * Emitted after the alert has presented. */ @@ -550,15 +556,16 @@ export class Alert implements ComponentInterface, OverlayInterface { } render() { - const { overlayIndex, header, subHeader } = this; + const { overlayIndex, header, subHeader, htmlAttributes } = this; const mode = getIonMode(this); const hdrId = `alert-${overlayIndex}-hdr`; const subHdrId = `alert-${overlayIndex}-sub-hdr`; const msgId = `alert-${overlayIndex}-msg`; + const role = htmlAttributes?.role || (this.inputs.length > 0 || this.buttons.length > 0 ? 'alertdialog' : 'alert'); return ( diff --git a/core/src/components/alert/readme.md b/core/src/components/alert/readme.md index a7e30e6f61..879d3f4d91 100644 --- a/core/src/components/alert/readme.md +++ b/core/src/components/alert/readme.md @@ -95,16 +95,22 @@ interface AlertOptions { backdropDismiss?: boolean; translucent?: boolean; animated?: boolean; - + htmlAttributes?: AlertAttributes; + mode?: Mode; keyboardClose?: boolean; id?: string; - + enterAnimation?: AnimationBuilder; leaveAnimation?: AnimationBuilder; } ``` +### AlertAttributes +```typescript +interface AlertAttributes extends JSXBase.HTMLAttributes {} +``` + ### AlertTextareaAttributes ```typescript interface AlertTextareaAttributes extends JSXBase.TextareaHTMLAttributes {} @@ -1768,6 +1774,7 @@ export default defineComponent({ | `cssClass` | `css-class` | Additional classes to apply for custom CSS. If multiple classes are provided they should be separated by spaces. | `string \| string[] \| undefined` | `undefined` | | `enterAnimation` | -- | Animation to use when the alert is presented. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` | | `header` | `header` | The main title in the heading of the alert. | `string \| undefined` | `undefined` | +| `htmlAttributes` | -- | Additional attributes to pass to the alert. | `AlertAttributes \| undefined` | `undefined` | | `inputs` | -- | Array of input to show in the alert. | `AlertInput[]` | `[]` | | `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `true` | | `leaveAnimation` | -- | Animation to use when the alert is dismissed. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` | diff --git a/core/src/components/alert/test/basic/e2e.ts b/core/src/components/alert/test/basic/e2e.ts index ad2511d2b2..0bb2db8a23 100644 --- a/core/src/components/alert/test/basic/e2e.ts +++ b/core/src/components/alert/test/basic/e2e.ts @@ -102,3 +102,21 @@ test(`alert:rtl: basic, radio`, async () => { test(`alert:rtl: basic, checkbox`, async () => { await testAlert(DIRECTORY, '#checkbox', true); }); + +// Attributes + +test('alert: htmlAttributes', async () => { + const page = await newE2EPage({ url: '/src/components/alert/test/basic?ionic:_testing=true' }); + + await page.click('#basic'); + await page.waitForSelector('#basic'); + + let alert = await page.find('ion-alert'); + + expect(alert).not.toBe(null); + await alert.waitForVisible(); + + const attribute = await page.evaluate((el) => document.querySelector('ion-alert').getAttribute('data-testid')); + + expect(attribute).toEqual('basic-alert'); +}); diff --git a/core/src/components/alert/test/basic/index.html b/core/src/components/alert/test/basic/index.html index c8e3a62777..96bf13d31b 100644 --- a/core/src/components/alert/test/basic/index.html +++ b/core/src/components/alert/test/basic/index.html @@ -58,7 +58,10 @@ header: 'Alert', subHeader: 'Subtitle', message: 'This is an alert message.', - buttons: ['OK'] + buttons: ['OK'], + htmlAttributes: { + 'data-testid': 'basic-alert' + } }); } diff --git a/core/src/components/toast/readme.md b/core/src/components/toast/readme.md index ea6324d5b5..98bd7ef4d0 100644 --- a/core/src/components/toast/readme.md +++ b/core/src/components/toast/readme.md @@ -37,6 +37,7 @@ interface ToastOptions { position?: 'top' | 'bottom' | 'middle'; translucent?: boolean; animated?: boolean; + htmlAttributes?: ToastAttributes; color?: Color; mode?: Mode; @@ -48,6 +49,11 @@ interface ToastOptions { } ``` +### ToastAttributes +```typescript +interface ToastAttributes extends JSXBase.HTMLAttributes {} +``` + @@ -405,6 +411,7 @@ export default defineComponent({ | `duration` | `duration` | How many milliseconds to wait before hiding the toast. By default, it will show until `dismiss()` is called. | `number` | `0` | | `enterAnimation` | -- | Animation to use when the toast is presented. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` | | `header` | `header` | Header to be shown in the toast. | `string \| undefined` | `undefined` | +| `htmlAttributes` | -- | Additional attributes to pass to the toast. | `ToastAttributes \| undefined` | `undefined` | | `keyboardClose` | `keyboard-close` | If `true`, the keyboard will be automatically dismissed when the overlay is presented. | `boolean` | `false` | | `leaveAnimation` | -- | Animation to use when the toast is dismissed. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` | | `message` | `message` | Message to be shown in the toast. | `IonicSafeString \| string \| undefined` | `undefined` | diff --git a/core/src/components/toast/test/basic/e2e.ts b/core/src/components/toast/test/basic/e2e.ts index 8044981dd0..3f96b7c955 100644 --- a/core/src/components/toast/test/basic/e2e.ts +++ b/core/src/components/toast/test/basic/e2e.ts @@ -1,3 +1,4 @@ +import { newE2EPage } from '@stencil/core/testing'; import { testToast } from '../test.utils'; const DIRECTORY = 'basic'; @@ -101,3 +102,21 @@ test('toast:rtl: start end position', async () => { test('toast:rtl: html', async () => { await testToast(DIRECTORY, '#toast-html', true); }); + +// Attributes + +test('toast: htmlAttributes', async () => { + const page = await newE2EPage({ url: '/src/components/toast/test/basic?ionic:_testing=true' }); + + await page.click('#show-bottom-toast'); + await page.waitForSelector('#show-bottom-toast'); + + let toast = await page.find('ion-toast'); + + expect(toast).not.toBe(null); + await toast.waitForVisible(); + + const attribute = await page.evaluate((el) => document.querySelector('ion-toast').getAttribute('data-testid')); + + expect(attribute).toEqual('basic-toast'); +}); diff --git a/core/src/components/toast/test/basic/index.html b/core/src/components/toast/test/basic/index.html index 569f302eb5..a3a68ca462 100644 --- a/core/src/components/toast/test/basic/index.html +++ b/core/src/components/toast/test/basic/index.html @@ -23,7 +23,7 @@ - + Position Bottom @@ -211,4 +211,4 @@ - \ No newline at end of file + diff --git a/core/src/components/toast/toast-interface.ts b/core/src/components/toast/toast-interface.ts index b4e0e4d134..641ade8f94 100644 --- a/core/src/components/toast/toast-interface.ts +++ b/core/src/components/toast/toast-interface.ts @@ -1,3 +1,5 @@ +import { JSXBase } from '@stencil/core/internal'; + import { AnimationBuilder, Color, Mode } from '../../interface'; import { IonicSafeString } from '../../utils/sanitization'; @@ -10,6 +12,7 @@ export interface ToastOptions { position?: 'top' | 'bottom' | 'middle'; translucent?: boolean; animated?: boolean; + htmlAttributes?: ToastAttributes; color?: Color; mode?: Mode; @@ -20,6 +23,8 @@ export interface ToastOptions { leaveAnimation?: AnimationBuilder; } +export interface ToastAttributes extends JSXBase.HTMLAttributes {} + export interface ToastButton { text?: string; icon?: string; diff --git a/core/src/components/toast/toast.tsx b/core/src/components/toast/toast.tsx index 58a23e3416..eef8678163 100644 --- a/core/src/components/toast/toast.tsx +++ b/core/src/components/toast/toast.tsx @@ -10,6 +10,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 { ToastAttributes } from './toast-interface'; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. @@ -106,6 +107,11 @@ export class Toast implements ComponentInterface, OverlayInterface { */ @Prop() animated = true; + /** + * Additional attributes to pass to the toast. + */ + @Prop() htmlAttributes?: ToastAttributes; + /** * Emitted after the toast has presented. */ @@ -263,6 +269,7 @@ export class Toast implements ComponentInterface, OverlayInterface { 'toast-wrapper': true, [`toast-${this.position}`]: true }; + const role = this.htmlAttributes?.role || (allButtons.length > 0 ? 'dialog' : 'status'); return (
diff --git a/packages/vue/src/components/Overlays.ts b/packages/vue/src/components/Overlays.ts index d41c258657..f5625b24bb 100644 --- a/packages/vue/src/components/Overlays.ts +++ b/packages/vue/src/components/Overlays.ts @@ -15,7 +15,7 @@ import { defineOverlayContainer } from '../vue-component-lib/overlays'; export const IonActionSheet = /*@__PURE__*/defineOverlayContainer('ion-action-sheet', ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'keyboardClose', 'leaveAnimation', 'mode', 'subHeader', 'translucent'], actionSheetController); -export const IonAlert = /*@__PURE__*/defineOverlayContainer('ion-alert', ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'inputs', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent'], alertController); +export const IonAlert = /*@__PURE__*/defineOverlayContainer('ion-alert', ['animated', 'backdropDismiss', 'buttons', 'cssClass', 'enterAnimation', 'header', 'htmlAttributes', 'inputs', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'subHeader', 'translucent'], alertController); export const IonLoading = /*@__PURE__*/defineOverlayContainer('ion-loading', ['animated', 'backdropDismiss', 'cssClass', 'duration', 'enterAnimation', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'showBackdrop', 'spinner', 'translucent'], loadingController); @@ -25,5 +25,5 @@ export const IonPicker = /*@__PURE__*/defineOverlayContainer('ion export const IonPopover = /*@__PURE__*/defineOverlayContainer('ion-popover', ['animated', 'backdropDismiss', 'component', 'componentProps', 'cssClass', 'enterAnimation', 'event', 'keyboardClose', 'leaveAnimation', 'mode', 'showBackdrop', 'translucent'], popoverController); -export const IonToast = /*@__PURE__*/defineOverlayContainer('ion-toast', ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController); +export const IonToast = /*@__PURE__*/defineOverlayContainer('ion-toast', ['animated', 'buttons', 'color', 'cssClass', 'duration', 'enterAnimation', 'header', 'htmlAttributes', 'keyboardClose', 'leaveAnimation', 'message', 'mode', 'position', 'translucent'], toastController);