import {Component, Renderer, ElementRef, HostListener, ChangeDetectionStrategy, ViewEncapsulation} from 'angular2/core'; import {NgFor, NgIf} from 'angular2/common'; import {Animation} from '../../animations/animation'; import {Transition, TransitionOptions} from '../../transitions/transition'; import {Config} from '../../config/config'; import {Icon} from '../icon/icon'; import {isPresent} from '../../util/util'; import {NavParams} from '../nav/nav-params'; import {ViewController} from '../nav/view-controller'; /** * @name ActionSheet * @description * An Action Sheet is a dialog that lets the user choose from a set of * options. It appears on top of the app's content, and must be manually * dismissed by the user before they can resume interaction with the app. * Dangerous (destructive) options are made obvious in `ios` mode. There are easy * ways to cancel out of the action sheet, such as tapping the backdrop or * hitting the escape key on desktop. * * An action sheet is created from an array of `buttons`, with each button * including properties for its `text`, and optionally a `handler` and `role`. * If a handler returns `false` then the action sheet will not be dismissed. An * action sheet can also optionally have a `title` and a `subTitle`. * * 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. * * You can pass all of the action sheet's options in the first argument of * the create method: `ActionSheet.create(opts)`. Otherwise the action sheet's * instance has methods to add options, like `setTitle()` or `addButton()`. * * @usage * ```ts * constructor(nav: NavController) { * this.nav = nav; * } * * presentActionSheet() { * let actionSheet = ActionSheet.create({ * title: 'Modify your album', * buttons: [ * { * text: 'Destructive', * role: 'destructive', * handler: () => { * console.log('Destructive clicked'); * } * }, * { * text: 'Archive', * handler: () => { * console.log('Archive clicked'); * } * }, * { * text: 'Cancel', * role: 'cancel', * handler: () => { * console.log('Cancel clicked'); * } * } * ] * }); * * this.nav.present(actionSheet); * } * ``` * * @demo /docs/v2/demos/action-sheet/ * @see {@link /docs/v2/components#action-sheets ActionSheet Component Docs} */ export class ActionSheet extends ViewController { constructor(opts: ActionSheetOptions = {}) { opts.buttons = opts.buttons || []; opts.enableBackdropDismiss = isPresent(opts.enableBackdropDismiss) ? !!opts.enableBackdropDismiss : true; super(ActionSheetCmp, opts); this.viewType = 'action-sheet'; this.isOverlay = true; // by default, actionsheets should not fire lifecycle events of other views // for example, when an actionsheets enters, the current active view should // not fire its lifecycle events because it's not conceptually leaving this.fireOtherLifecycles = false; } /** * @private */ getTransitionName(direction: string) { let key = 'actionSheet' + (direction === 'back' ? 'Leave' : 'Enter'); return this._nav && this._nav.config.get(key); } /** * @param {string} title Action sheet title */ setTitle(title: string) { this.data.title = title; } /** * @param {string} subTitle Action sheet subtitle */ setSubTitle(subTitle: string) { this.data.subTitle = subTitle; } /** * @param {object} button Action sheet button */ addButton(button: any) { this.data.buttons.push(button); } /** * Open an action sheet with the following options * * | Option | Type | Description | * |-----------------------|------------|-----------------------------------------------------------------| * | title |`string` | The title for the actionsheet | * | subTitle |`string` | The sub-title for the actionsheet | * | cssClass |`string` | An additional class for custom styles | * | enableBackdropDismiss |`boolean` | If the actionsheet should close when the user taps the backdrop | * | buttons |`array`| An array of buttons to display | * * For the buttons: * * | Option | Type | Description | * |----------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------| * | text | `string` | The buttons text | * | icon | `icon` | The buttons icons | * | handler | `any` | An express the button should evaluate | * | cssClass | `string` | An additional class for custom styles | * | role | `string` | How the button should be displayed, `destructive` or `cancel`. If not role is provided, it will display the button without any additional styles | * * * * @param {object} opts Action sheet options */ static create(opts: ActionSheetOptions = {}) { return new ActionSheet(opts); } } /** * @private */ @Component({ selector: 'ion-action-sheet', template: '' + '
' + '
' + '
' + '
{{d.title}}
' + '
{{d.subTitle}}
' + '' + '
' + '
' + '' + '
' + '
' + '
', host: { 'role': 'dialog', '[attr.aria-labelledby]': 'hdrId', '[attr.aria-describedby]': 'descId' }, directives: [NgFor, NgIf, Icon], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, }) class ActionSheetCmp { private d: any; private descId: string; private hdrId: string; private id: number; private created: number; constructor( private _viewCtrl: ViewController, private _config: Config, private _elementRef: ElementRef, params: NavParams, renderer: Renderer ) { this.d = params.data; this.created = Date.now(); if (this.d.cssClass) { renderer.setElementClass(_elementRef.nativeElement, this.d.cssClass, true); } this.id = (++actionSheetIds); if (this.d.title) { this.hdrId = 'acst-hdr-' + this.id; } if (this.d.subTitle) { this.descId = 'acst-subhdr-' + this.id; } } onPageLoaded() { // normalize the data let buttons = []; this.d.buttons.forEach(button => { if (typeof button === 'string') { button = { text: button }; } if (!button.cssClass) { button.cssClass = ''; } // deprecated warning if (button.style) { console.warn('Alert "style" property has been renamed to "role"'); button.role = button.style; } if (button.role === 'cancel') { this.d.cancelButton = button; } else { if (button.role === 'destructive') { button.cssClass = (button.cssClass + ' ' || '') + 'action-sheet-destructive'; } buttons.push(button); } }); this.d.buttons = buttons; } onPageDidEnter() { let activeElement: any = document.activeElement; if (document.activeElement) { activeElement.blur(); } let focusableEle = this._elementRef.nativeElement.querySelector('button'); if (focusableEle) { focusableEle.focus(); } } @HostListener('body:keyup', ['$event']) private _keyUp(ev: KeyboardEvent) { if (this.isEnabled() && this._viewCtrl.isLast()) { if (ev.keyCode === 27) { console.debug('actionsheet, escape button'); this.bdClick(); } } } click(button, dismissDelay?) { if (!this.isEnabled()) { return; } let shouldDismiss = true; if (button.handler) { // a handler has been provided, execute it if (button.handler() === false) { // if the return value of the handler is false then do not dismiss shouldDismiss = false; } } if (shouldDismiss) { setTimeout(() => { this.dismiss(button.role); }, dismissDelay || this._config.get('pageTransitionDelay')); } } bdClick() { if (this.isEnabled() && this.d.enableBackdropDismiss) { if (this.d.cancelButton) { this.click(this.d.cancelButton, 1); } else { this.dismiss('backdrop'); } } } dismiss(role): Promise { return this._viewCtrl.dismiss(null, role); } isEnabled() { let tm = this._config.getNumber('overlayCreatedDiff', 750); return (this.created + tm < Date.now()); } } export interface ActionSheetOptions { title?: string; subTitle?: string; cssClass?: string; buttons?: Array; enableBackdropDismiss?: boolean; } class ActionSheetSlideIn extends Transition { constructor(enteringView, leavingView, opts: TransitionOptions) { super(opts); let ele = enteringView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); let wrapper = new Animation(ele.querySelector('.action-sheet-wrapper')); backdrop.fromTo('opacity', 0.01, 0.4); wrapper.fromTo('translateY', '100%', '0%'); this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(backdrop).add(wrapper); } } Transition.register('action-sheet-slide-in', ActionSheetSlideIn); class ActionSheetSlideOut extends Transition { constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { super(opts); let ele = leavingView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); let wrapper = new Animation(ele.querySelector('.action-sheet-wrapper')); backdrop.fromTo('opacity', 0.4, 0); wrapper.fromTo('translateY', '0%', '100%'); this.easing('cubic-bezier(.36,.66,.04,1)').duration(300).add(backdrop).add(wrapper); } } Transition.register('action-sheet-slide-out', ActionSheetSlideOut); class ActionSheetMdSlideIn extends Transition { constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { super(opts); let ele = enteringView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); let wrapper = new Animation(ele.querySelector('.action-sheet-wrapper')); backdrop.fromTo('opacity', 0.01, 0.26); wrapper.fromTo('translateY', '100%', '0%'); this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(backdrop).add(wrapper); } } Transition.register('action-sheet-md-slide-in', ActionSheetMdSlideIn); class ActionSheetMdSlideOut extends Transition { constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { super(opts); let ele = leavingView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); let wrapper = new Animation(ele.querySelector('.action-sheet-wrapper')); backdrop.fromTo('opacity', 0.26, 0); wrapper.fromTo('translateY', '0%', '100%'); this.easing('cubic-bezier(.36,.66,.04,1)').duration(450).add(backdrop).add(wrapper); } } Transition.register('action-sheet-md-slide-out', ActionSheetMdSlideOut); class ActionSheetWpSlideIn extends Transition { constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { super(opts); let ele = enteringView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); let wrapper = new Animation(ele.querySelector('.action-sheet-wrapper')); backdrop.fromTo('opacity', 0.01, 0.16); wrapper.fromTo('translateY', '100%', '0%'); this.easing('cubic-bezier(.36,.66,.04,1)').duration(400).add(backdrop).add(wrapper); } } Transition.register('action-sheet-wp-slide-in', ActionSheetWpSlideIn); class ActionSheetWpSlideOut extends Transition { constructor(enteringView: ViewController, leavingView: ViewController, opts: TransitionOptions) { super(opts); let ele = leavingView.pageRef().nativeElement; let backdrop = new Animation(ele.querySelector('.backdrop')); let wrapper = new Animation(ele.querySelector('.action-sheet-wrapper')); backdrop.fromTo('opacity', 0.1, 0); wrapper.fromTo('translateY', '0%', '100%'); this.easing('cubic-bezier(.36,.66,.04,1)').duration(450).add(backdrop).add(wrapper); } } Transition.register('action-sheet-wp-slide-out', ActionSheetWpSlideOut); let actionSheetIds = -1;