feat(action-sheet): add data property to ActionSheetButton (#23744)

resolves #23700

Co-authored-by: Liam DeBeasi <liamdebeasi@icloud.com>
This commit is contained in:
Hans Krywalsky
2021-08-09 17:32:57 +02:00
committed by GitHub
parent fbd32ffb26
commit 30f8508296
11 changed files with 144 additions and 25 deletions

View File

@ -23,7 +23,7 @@ ion-accordion-group,event,ionChange,AccordionGroupChangeEventDetail<any>,true
ion-action-sheet,scoped ion-action-sheet,scoped
ion-action-sheet,prop,animated,boolean,true,false,false ion-action-sheet,prop,animated,boolean,true,false,false
ion-action-sheet,prop,backdropDismiss,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<any>)[],[],false,false
ion-action-sheet,prop,cssClass,string | string[] | undefined,undefined,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,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-action-sheet,prop,header,string | undefined,undefined,false,false ion-action-sheet,prop,header,string | undefined,undefined,false,false

View File

@ -16,11 +16,12 @@ export interface ActionSheetOptions {
leaveAnimation?: AnimationBuilder; leaveAnimation?: AnimationBuilder;
} }
export interface ActionSheetButton { export interface ActionSheetButton<T = any> {
text?: string; text?: string;
role?: 'cancel' | 'destructive' | 'selected' | string; role?: 'cancel' | 'destructive' | 'selected' | string;
icon?: string; icon?: string;
cssClass?: string | string[]; cssClass?: string | string[];
id?: string; id?: string;
handler?: () => boolean | void | Promise<boolean | void>; handler?: () => boolean | void | Promise<boolean | void>;
data?: T;
} }

View File

@ -156,11 +156,11 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
private async buttonClick(button: ActionSheetButton) { private async buttonClick(button: ActionSheetButton) {
const role = button.role; const role = button.role;
if (isCancel(role)) { if (isCancel(role)) {
return this.dismiss(undefined, role); return this.dismiss(button.data, role);
} }
const shouldDismiss = await this.callButtonHandler(button); const shouldDismiss = await this.callButtonHandler(button);
if (shouldDismiss) { if (shouldDismiss) {
return this.dismiss(undefined, button.role); return this.dismiss(button.data, button.role);
} }
return Promise.resolve(); return Promise.resolve();
} }

View File

@ -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'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 ## 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. 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 ### ActionSheetButton
```typescript ```typescript
interface ActionSheetButton { interface ActionSheetButton<T = any> {
text?: string; text?: string;
role?: 'cancel' | 'destructive' | 'selected' | string; role?: 'cancel' | 'destructive' | 'selected' | string;
icon?: string; icon?: string;
cssClass?: string | string[]; cssClass?: string | string[];
handler?: () => boolean | void | Promise<boolean | void>; handler?: () => boolean | void | Promise<boolean | void>;
data?: T;
} }
``` ```
@ -97,18 +100,23 @@ export class ActionSheetExample {
role: 'destructive', role: 'destructive',
icon: 'trash', icon: 'trash',
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: 'share', icon: 'share',
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: 'caret-forward-circle', icon: 'caret-forward-circle',
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }
@ -129,8 +137,8 @@ export class ActionSheetExample {
}); });
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
} }
} }
@ -155,18 +163,23 @@ async function presentActionSheet() {
role: 'destructive', role: 'destructive',
icon: 'trash', icon: 'trash',
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: 'share', icon: 'share',
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: 'caret-forward-circle', icon: 'caret-forward-circle',
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }
@ -187,8 +200,8 @@ async function presentActionSheet() {
document.body.appendChild(actionSheet); document.body.appendChild(actionSheet);
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
} }
``` ```
@ -270,18 +283,23 @@ export const ActionSheetExample: React.FC = () => {
role: 'destructive', role: 'destructive',
icon: trash, icon: trash,
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: share, icon: share,
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: caretForwardCircle, icon: caretForwardCircle,
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }
@ -328,18 +346,23 @@ export class ActionSheetExample {
role: 'destructive', role: 'destructive',
icon: 'trash', icon: 'trash',
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: 'share', icon: 'share',
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: 'caret-forward-circle', icon: 'caret-forward-circle',
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }
@ -360,8 +383,8 @@ export class ActionSheetExample {
}); });
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
} }
render() { render() {
@ -400,7 +423,10 @@ export default defineComponent({
text: 'Delete', text: 'Delete',
role: 'destructive', role: 'destructive',
icon: trash, icon: trash,
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked') console.log('Delete clicked')
}, },
@ -408,6 +434,7 @@ export default defineComponent({
{ {
text: 'Share', text: 'Share',
icon: share, icon: share,
data: 10,
handler: () => { handler: () => {
console.log('Share clicked') console.log('Share clicked')
}, },
@ -415,6 +442,7 @@ export default defineComponent({
{ {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: caretForwardCircle, icon: caretForwardCircle,
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked') console.log('Play clicked')
}, },
@ -438,8 +466,8 @@ export default defineComponent({
}); });
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
}, },
}, },
}); });
@ -476,6 +504,9 @@ export default defineComponent({
text: 'Delete', text: 'Delete',
role: 'destructive', role: 'destructive',
icon: trash, icon: trash,
data: {
type: 'delete'
}
handler: () => { handler: () => {
console.log('Delete clicked') console.log('Delete clicked')
}, },
@ -483,6 +514,7 @@ export default defineComponent({
{ {
text: 'Share', text: 'Share',
icon: share, icon: share,
data: 10,
handler: () => { handler: () => {
console.log('Share clicked') console.log('Share clicked')
}, },
@ -490,6 +522,7 @@ export default defineComponent({
{ {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: caretForwardCircle, icon: caretForwardCircle,
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked') console.log('Play clicked')
}, },
@ -525,7 +558,7 @@ export default defineComponent({
| ----------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------- | | ----------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | ----------- |
| `animated` | `animated` | If `true`, the action sheet will animate. | `boolean` | `true` | | `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` | | `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<any>)[]` | `[]` |
| `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` | | `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` | | `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` | | `header` | `header` | Title for the action sheet. | `string \| undefined` | `undefined` |

View File

@ -7,6 +7,38 @@ const getActiveElementText = async (page) => {
return await page.evaluate(el => el && el.textContent, activeElement); 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 () => { test('action-sheet: focus trap', async () => {
const page = await newE2EPage({ url: '/src/components/action-sheet/test/basic?ionic:_testing=true' }); const page = await newE2EPage({ url: '/src/components/action-sheet/test/basic?ionic:_testing=true' });

View File

@ -33,6 +33,7 @@
<ion-button expand="block" id="scrollableOptions" onclick="presentScroll()">Scrollable Options</ion-button> <ion-button expand="block" id="scrollableOptions" onclick="presentScroll()">Scrollable Options</ion-button>
<ion-button expand="block" id="scrollWithoutCancel" onclick="presentScrollNoCancel()">Scroll Without Cancel</ion-button> <ion-button expand="block" id="scrollWithoutCancel" onclick="presentScrollNoCancel()">Scroll Without Cancel</ion-button>
<ion-button expand="block" id="customBackdrop" onclick="presentWithCssClass('custom-backdrop')">Custom Backdrop Opacity</ion-button> <ion-button expand="block" id="customBackdrop" onclick="presentWithCssClass('custom-backdrop')">Custom Backdrop Opacity</ion-button>
<ion-button expand="block" id="buttonData" onclick="presentWithButtonData()">Button data</ion-button>
</ion-content> </ion-content>
</ion-app> </ion-app>
@ -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'
}
}
]
});
}
</script> </script>
</body> </body>

View File

@ -20,18 +20,23 @@ export class ActionSheetExample {
role: 'destructive', role: 'destructive',
icon: 'trash', icon: 'trash',
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: 'share', icon: 'share',
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: 'caret-forward-circle', icon: 'caret-forward-circle',
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }
@ -52,8 +57,8 @@ export class ActionSheetExample {
}); });
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
} }
} }

View File

@ -9,18 +9,23 @@ async function presentActionSheet() {
role: 'destructive', role: 'destructive',
icon: 'trash', icon: 'trash',
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: 'share', icon: 'share',
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: 'caret-forward-circle', icon: 'caret-forward-circle',
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }
@ -41,7 +46,7 @@ async function presentActionSheet() {
document.body.appendChild(actionSheet); document.body.appendChild(actionSheet);
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
} }
``` ```

View File

@ -73,18 +73,23 @@ export const ActionSheetExample: React.FC = () => {
role: 'destructive', role: 'destructive',
icon: trash, icon: trash,
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: share, icon: share,
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: caretForwardCircle, icon: caretForwardCircle,
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }

View File

@ -17,18 +17,23 @@ export class ActionSheetExample {
role: 'destructive', role: 'destructive',
icon: 'trash', icon: 'trash',
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked'); console.log('Delete clicked');
} }
}, { }, {
text: 'Share', text: 'Share',
icon: 'share', icon: 'share',
data: 10,
handler: () => { handler: () => {
console.log('Share clicked'); console.log('Share clicked');
} }
}, { }, {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: 'caret-forward-circle', icon: 'caret-forward-circle',
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked'); console.log('Play clicked');
} }
@ -49,8 +54,8 @@ export class ActionSheetExample {
}); });
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
} }
render() { render() {
@ -61,4 +66,4 @@ export class ActionSheetExample {
]; ];
} }
} }
``` ```

View File

@ -21,7 +21,10 @@ export default defineComponent({
text: 'Delete', text: 'Delete',
role: 'destructive', role: 'destructive',
icon: trash, icon: trash,
id: 'delete-button', id: 'delete-button',
data: {
type: 'delete'
},
handler: () => { handler: () => {
console.log('Delete clicked') console.log('Delete clicked')
}, },
@ -29,6 +32,7 @@ export default defineComponent({
{ {
text: 'Share', text: 'Share',
icon: share, icon: share,
data: 10,
handler: () => { handler: () => {
console.log('Share clicked') console.log('Share clicked')
}, },
@ -36,6 +40,7 @@ export default defineComponent({
{ {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: caretForwardCircle, icon: caretForwardCircle,
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked') console.log('Play clicked')
}, },
@ -59,8 +64,8 @@ export default defineComponent({
}); });
await actionSheet.present(); await actionSheet.present();
const { role } = await actionSheet.onDidDismiss(); const { role, data } = await actionSheet.onDidDismiss();
console.log('onDidDismiss resolved with role', role); console.log('onDidDismiss resolved with role and data', role, data);
}, },
}, },
}); });
@ -97,6 +102,9 @@ export default defineComponent({
text: 'Delete', text: 'Delete',
role: 'destructive', role: 'destructive',
icon: trash, icon: trash,
data: {
type: 'delete'
}
handler: () => { handler: () => {
console.log('Delete clicked') console.log('Delete clicked')
}, },
@ -104,6 +112,7 @@ export default defineComponent({
{ {
text: 'Share', text: 'Share',
icon: share, icon: share,
data: 10,
handler: () => { handler: () => {
console.log('Share clicked') console.log('Share clicked')
}, },
@ -111,6 +120,7 @@ export default defineComponent({
{ {
text: 'Play (open modal)', text: 'Play (open modal)',
icon: caretForwardCircle, icon: caretForwardCircle,
data: 'Data value',
handler: () => { handler: () => {
console.log('Play clicked') console.log('Play clicked')
}, },