diff --git a/core/api.txt b/core/api.txt index 52cb7fc3f7..7f63bb580b 100644 --- a/core/api.txt +++ b/core/api.txt @@ -23,7 +23,7 @@ ion-accordion-group,event,ionChange,AccordionGroupChangeEventDetail,true ion-action-sheet,scoped ion-action-sheet,prop,animated,boolean,true,false,false ion-action-sheet,prop,backdropDismiss,boolean,true,false,false -ion-action-sheet,prop,buttons,(string | ActionSheetButton)[],[],false,false +ion-action-sheet,prop,buttons,(string | ActionSheetButton)[],[],false,false ion-action-sheet,prop,cssClass,string | string[] | undefined,undefined,false,false ion-action-sheet,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false ion-action-sheet,prop,header,string | undefined,undefined,false,false diff --git a/core/src/components/action-sheet/action-sheet-interface.ts b/core/src/components/action-sheet/action-sheet-interface.ts index d37aaa1f9a..a8a6577cd0 100644 --- a/core/src/components/action-sheet/action-sheet-interface.ts +++ b/core/src/components/action-sheet/action-sheet-interface.ts @@ -16,11 +16,12 @@ export interface ActionSheetOptions { leaveAnimation?: AnimationBuilder; } -export interface ActionSheetButton { +export interface ActionSheetButton { text?: string; role?: 'cancel' | 'destructive' | 'selected' | string; icon?: string; cssClass?: string | string[]; id?: string; handler?: () => boolean | void | Promise; + data?: T; } diff --git a/core/src/components/action-sheet/action-sheet.tsx b/core/src/components/action-sheet/action-sheet.tsx index 13879dc310..44b197be99 100644 --- a/core/src/components/action-sheet/action-sheet.tsx +++ b/core/src/components/action-sheet/action-sheet.tsx @@ -156,11 +156,11 @@ export class ActionSheet implements ComponentInterface, OverlayInterface { private async buttonClick(button: ActionSheetButton) { const role = button.role; if (isCancel(role)) { - return this.dismiss(undefined, role); + return this.dismiss(button.data, role); } const shouldDismiss = await this.callButtonHandler(button); if (shouldDismiss) { - return this.dismiss(undefined, button.role); + return this.dismiss(button.data, button.role); } return Promise.resolve(); } diff --git a/core/src/components/action-sheet/readme.md b/core/src/components/action-sheet/readme.md index 691e7b7133..2815e07312 100644 --- a/core/src/components/action-sheet/readme.md +++ b/core/src/components/action-sheet/readme.md @@ -6,6 +6,8 @@ An Action Sheet is a dialog that displays a set of options. It appears on top of A button's `role` property can either be `destructive` or `cancel`. Buttons without a role property will have the default look for the platform. Buttons with the `cancel` role will always load as the bottom button, no matter where they are in the array. All other buttons will be displayed in the order they have been added to the `buttons` array. Note: We recommend that `destructive` buttons are always the first button in the array, making them the top button. Additionally, if the action sheet is dismissed by tapping the backdrop, then it will fire the handler from the button with the cancel role. +A button can also be passed data via the `data` property on `ActionSheetButton`. This will populate the `data` field in the return value of the `onDidDismiss` method. + ## Customization Action Sheet uses scoped encapsulation, which means it will automatically scope its CSS by appending each of the styles with an additional class at runtime. Overriding scoped selectors in CSS requires a [higher specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) selector. @@ -39,12 +41,13 @@ Any of the defined [CSS Custom Properties](#css-custom-properties) can be used t ### ActionSheetButton ```typescript -interface ActionSheetButton { +interface ActionSheetButton { text?: string; role?: 'cancel' | 'destructive' | 'selected' | string; icon?: string; cssClass?: string | string[]; handler?: () => boolean | void | Promise; + data?: T; } ``` @@ -97,18 +100,23 @@ export class ActionSheetExample { role: 'destructive', icon: 'trash', id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: 'share', + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: 'caret-forward-circle', + data: 'Data value', handler: () => { console.log('Play clicked'); } @@ -129,8 +137,8 @@ export class ActionSheetExample { }); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); } } @@ -155,18 +163,23 @@ async function presentActionSheet() { role: 'destructive', icon: 'trash', id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: 'share', + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: 'caret-forward-circle', + data: 'Data value', handler: () => { console.log('Play clicked'); } @@ -187,8 +200,8 @@ async function presentActionSheet() { document.body.appendChild(actionSheet); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); } ``` @@ -270,18 +283,23 @@ export const ActionSheetExample: React.FC = () => { role: 'destructive', icon: trash, id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: share, + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: caretForwardCircle, + data: 'Data value', handler: () => { console.log('Play clicked'); } @@ -328,18 +346,23 @@ export class ActionSheetExample { role: 'destructive', icon: 'trash', id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: 'share', + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: 'caret-forward-circle', + data: 'Data value', handler: () => { console.log('Play clicked'); } @@ -360,8 +383,8 @@ export class ActionSheetExample { }); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); } render() { @@ -400,7 +423,10 @@ export default defineComponent({ text: 'Delete', role: 'destructive', icon: trash, - id: 'delete-button', + id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked') }, @@ -408,6 +434,7 @@ export default defineComponent({ { text: 'Share', icon: share, + data: 10, handler: () => { console.log('Share clicked') }, @@ -415,6 +442,7 @@ export default defineComponent({ { text: 'Play (open modal)', icon: caretForwardCircle, + data: 'Data value', handler: () => { console.log('Play clicked') }, @@ -438,8 +466,8 @@ export default defineComponent({ }); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); }, }, }); @@ -476,6 +504,9 @@ export default defineComponent({ text: 'Delete', role: 'destructive', icon: trash, + data: { + type: 'delete' + } handler: () => { console.log('Delete clicked') }, @@ -483,6 +514,7 @@ export default defineComponent({ { text: 'Share', icon: share, + data: 10, handler: () => { console.log('Share clicked') }, @@ -490,6 +522,7 @@ export default defineComponent({ { text: 'Play (open modal)', icon: caretForwardCircle, + data: 'Data value', handler: () => { console.log('Play clicked') }, @@ -525,7 +558,7 @@ export default defineComponent({ | ----------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------- | | `animated` | `animated` | If `true`, the action sheet will animate. | `boolean` | `true` | | `backdropDismiss` | `backdrop-dismiss` | If `true`, the action sheet will be dismissed when the backdrop is clicked. | `boolean` | `true` | -| `buttons` | -- | An array of buttons for the action sheet. | `(string \| ActionSheetButton)[]` | `[]` | +| `buttons` | -- | An array of buttons for the action sheet. | `(string \| ActionSheetButton)[]` | `[]` | | `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 action sheet is presented. | `((baseEl: any, opts?: any) => Animation) \| undefined` | `undefined` | | `header` | `header` | Title for the action sheet. | `string \| undefined` | `undefined` | diff --git a/core/src/components/action-sheet/test/basic/e2e.ts b/core/src/components/action-sheet/test/basic/e2e.ts index a98661d765..3cd02b9914 100644 --- a/core/src/components/action-sheet/test/basic/e2e.ts +++ b/core/src/components/action-sheet/test/basic/e2e.ts @@ -7,6 +7,38 @@ const getActiveElementText = async (page) => { return await page.evaluate(el => el && el.textContent, activeElement); } +test('action-sheet: data', async () => { + const page = await newE2EPage({ url: '/src/components/action-sheet/test/basic?ionic:_testing=true' }); + const didDismiss = await page.spyOnEvent('ionActionSheetDidDismiss'); + + await page.click('#buttonData'); + await page.waitForSelector('#buttonData'); + + const actionSheet = await page.find('ion-action-sheet'); + await actionSheet.waitForVisible(); + + const button = await actionSheet.find('button#option'); + await button.click(); + + expect(didDismiss).toHaveReceivedEventDetail({ data: { type: '1' } }); +}); + +test('action-sheet: data cancel', async () => { + const page = await newE2EPage({ url: '/src/components/action-sheet/test/basic?ionic:_testing=true' }); + const didDismiss = await page.spyOnEvent('ionActionSheetDidDismiss'); + + await page.click('#buttonData'); + await page.waitForSelector('#buttonData'); + + const actionSheet = await page.find('ion-action-sheet'); + await actionSheet.waitForVisible(); + + const button = await actionSheet.find('button.action-sheet-cancel'); + await button.click(); + + expect(didDismiss).toHaveReceivedEventDetail({ data: { type: 'cancel' }, role: 'cancel' }); +}) + test('action-sheet: focus trap', async () => { const page = await newE2EPage({ url: '/src/components/action-sheet/test/basic?ionic:_testing=true' }); diff --git a/core/src/components/action-sheet/test/basic/index.html b/core/src/components/action-sheet/test/basic/index.html index b5165a3a49..b2fb9922ad 100644 --- a/core/src/components/action-sheet/test/basic/index.html +++ b/core/src/components/action-sheet/test/basic/index.html @@ -33,6 +33,7 @@ Scrollable Options Scroll Without Cancel Custom Backdrop Opacity + Button data @@ -427,6 +428,28 @@ ] }); } + + async function presentWithButtonData() { + await openActionSheet({ + buttons: [ + { + text: 'Option 1', + id: 'option', + data: { + type: '1' + } + }, + { + text: 'Cancel', + role: 'cancel', + id: 'cancel', + data: { + type: 'cancel' + } + } + ] + }); + } diff --git a/core/src/components/action-sheet/usage/angular.md b/core/src/components/action-sheet/usage/angular.md index 04f7fa536c..5d549dd7a1 100644 --- a/core/src/components/action-sheet/usage/angular.md +++ b/core/src/components/action-sheet/usage/angular.md @@ -20,18 +20,23 @@ export class ActionSheetExample { role: 'destructive', icon: 'trash', id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: 'share', + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: 'caret-forward-circle', + data: 'Data value', handler: () => { console.log('Play clicked'); } @@ -52,8 +57,8 @@ export class ActionSheetExample { }); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); } } diff --git a/core/src/components/action-sheet/usage/javascript.md b/core/src/components/action-sheet/usage/javascript.md index 40501f10e2..57913c3ad6 100644 --- a/core/src/components/action-sheet/usage/javascript.md +++ b/core/src/components/action-sheet/usage/javascript.md @@ -9,18 +9,23 @@ async function presentActionSheet() { role: 'destructive', icon: 'trash', id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: 'share', + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: 'caret-forward-circle', + data: 'Data value', handler: () => { console.log('Play clicked'); } @@ -41,7 +46,7 @@ async function presentActionSheet() { document.body.appendChild(actionSheet); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); } ``` diff --git a/core/src/components/action-sheet/usage/react.md b/core/src/components/action-sheet/usage/react.md index 24db5d5d93..11ffb4c643 100644 --- a/core/src/components/action-sheet/usage/react.md +++ b/core/src/components/action-sheet/usage/react.md @@ -73,18 +73,23 @@ export const ActionSheetExample: React.FC = () => { role: 'destructive', icon: trash, id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: share, + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: caretForwardCircle, + data: 'Data value', handler: () => { console.log('Play clicked'); } diff --git a/core/src/components/action-sheet/usage/stencil.md b/core/src/components/action-sheet/usage/stencil.md index c35a6d61c2..92b76ab94f 100644 --- a/core/src/components/action-sheet/usage/stencil.md +++ b/core/src/components/action-sheet/usage/stencil.md @@ -17,18 +17,23 @@ export class ActionSheetExample { role: 'destructive', icon: 'trash', id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked'); } }, { text: 'Share', icon: 'share', + data: 10, handler: () => { console.log('Share clicked'); } }, { text: 'Play (open modal)', icon: 'caret-forward-circle', + data: 'Data value', handler: () => { console.log('Play clicked'); } @@ -49,8 +54,8 @@ export class ActionSheetExample { }); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); } render() { @@ -61,4 +66,4 @@ export class ActionSheetExample { ]; } } -``` \ No newline at end of file +``` diff --git a/core/src/components/action-sheet/usage/vue.md b/core/src/components/action-sheet/usage/vue.md index 95e35e3cdc..af67a46d36 100644 --- a/core/src/components/action-sheet/usage/vue.md +++ b/core/src/components/action-sheet/usage/vue.md @@ -21,7 +21,10 @@ export default defineComponent({ text: 'Delete', role: 'destructive', icon: trash, - id: 'delete-button', + id: 'delete-button', + data: { + type: 'delete' + }, handler: () => { console.log('Delete clicked') }, @@ -29,6 +32,7 @@ export default defineComponent({ { text: 'Share', icon: share, + data: 10, handler: () => { console.log('Share clicked') }, @@ -36,6 +40,7 @@ export default defineComponent({ { text: 'Play (open modal)', icon: caretForwardCircle, + data: 'Data value', handler: () => { console.log('Play clicked') }, @@ -59,8 +64,8 @@ export default defineComponent({ }); await actionSheet.present(); - const { role } = await actionSheet.onDidDismiss(); - console.log('onDidDismiss resolved with role', role); + const { role, data } = await actionSheet.onDidDismiss(); + console.log('onDidDismiss resolved with role and data', role, data); }, }, }); @@ -97,6 +102,9 @@ export default defineComponent({ text: 'Delete', role: 'destructive', icon: trash, + data: { + type: 'delete' + } handler: () => { console.log('Delete clicked') }, @@ -104,6 +112,7 @@ export default defineComponent({ { text: 'Share', icon: share, + data: 10, handler: () => { console.log('Share clicked') }, @@ -111,6 +120,7 @@ export default defineComponent({ { text: 'Play (open modal)', icon: caretForwardCircle, + data: 'Data value', handler: () => { console.log('Play clicked') },