UNPKG

@ionic/core

Version:
842 lines (841 loc) • 36 kB
/*! * (C) Ionic http://ionicframework.com - MIT License */ import { Host, h, readTask } from "@stencil/core"; import { createButtonActiveGesture } from "../../utils/gesture/button-active"; import { raf } from "../../utils/helpers"; import { createLockController } from "../../utils/lock-controller"; import { BACKDROP, createDelegateController, createTriggerController, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall, setOverlayId, } from "../../utils/overlays"; import { getClassMap } from "../../utils/theme"; import { getIonMode } from "../../global/ionic-global"; import { iosEnterAnimation } from "./animations/ios.enter"; import { iosLeaveAnimation } from "./animations/ios.leave"; import { mdEnterAnimation } from "./animations/md.enter"; import { mdLeaveAnimation } from "./animations/md.leave"; /** * @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. */ export class ActionSheet { constructor() { this.delegateController = createDelegateController(this); this.lockController = createLockController(); this.triggerController = createTriggerController(); this.presented = false; /** @internal */ this.hasController = false; /** * If `true`, the keyboard will be automatically dismissed when the overlay is presented. */ this.keyboardClose = true; /** * An array of buttons for the action sheet. */ this.buttons = []; /** * If `true`, the action sheet will be dismissed when the backdrop is clicked. */ this.backdropDismiss = true; /** * If `true`, the action sheet will be translucent. * Only applies when the mode is `"ios"` and the device supports * [`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility). */ this.translucent = false; /** * If `true`, the action sheet will animate. */ this.animated = true; /** * If `true`, the action sheet will open. If `false`, the action sheet will close. * Use this if you need finer grained control over presentation, otherwise * just use the actionSheetController or the `trigger` property. * Note: `isOpen` will not automatically be set back to `false` when * the action sheet dismisses. You will need to do that in your code. */ this.isOpen = false; this.onBackdropTap = () => { this.dismiss(undefined, BACKDROP); }; this.dispatchCancelHandler = (ev) => { const role = ev.detail.role; if (isCancel(role)) { const cancelButton = this.getButtons().find((b) => b.role === 'cancel'); this.callButtonHandler(cancelButton); } }; } onIsOpenChange(newValue, oldValue) { if (newValue === true && oldValue === false) { this.present(); } else if (newValue === false && oldValue === true) { this.dismiss(); } } triggerChanged() { const { trigger, el, triggerController } = this; if (trigger) { triggerController.addClickListener(el, trigger); } } /** * Present the action sheet overlay after it has been created. */ async present() { const unlock = await this.lockController.lock(); await this.delegateController.attachViewToDom(); await present(this, 'actionSheetEnter', iosEnterAnimation, mdEnterAnimation); unlock(); } /** * Dismiss the action sheet overlay after it has been presented. * * @param data Any data to emit in the dismiss events. * @param role The role of the element that is dismissing the action sheet. * This can be useful in a button handler for determining which button was * clicked to dismiss the action sheet. * Some examples include: ``"cancel"`, `"destructive"`, "selected"`, and `"backdrop"`. * * This is a no-op if the overlay has not been presented yet. If you want * to remove an overlay from the DOM that was never presented, use the * [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method. */ async dismiss(data, role) { const unlock = await this.lockController.lock(); const dismissed = await dismiss(this, data, role, 'actionSheetLeave', iosLeaveAnimation, mdLeaveAnimation); if (dismissed) { this.delegateController.removeViewFromDom(); } unlock(); return dismissed; } /** * Returns a promise that resolves when the action sheet did dismiss. */ onDidDismiss() { return eventMethod(this.el, 'ionActionSheetDidDismiss'); } /** * Returns a promise that resolves when the action sheet will dismiss. * */ onWillDismiss() { return eventMethod(this.el, 'ionActionSheetWillDismiss'); } async buttonClick(button) { const role = button.role; if (isCancel(role)) { return this.dismiss(button.data, role); } const shouldDismiss = await this.callButtonHandler(button); if (shouldDismiss) { return this.dismiss(button.data, button.role); } return Promise.resolve(); } async callButtonHandler(button) { if (button) { // a handler has been provided, execute it // pass the handler the values from the inputs const rtn = await safeCall(button.handler); if (rtn === false) { // if the return value of the handler is false then do not dismiss return false; } } return true; } getButtons() { return this.buttons.map((b) => { return typeof b === 'string' ? { text: b } : b; }); } connectedCallback() { prepareOverlay(this.el); this.triggerChanged(); } disconnectedCallback() { if (this.gesture) { this.gesture.destroy(); this.gesture = undefined; } this.triggerController.removeClickListener(); } componentWillLoad() { var _a; if (!((_a = this.htmlAttributes) === null || _a === void 0 ? void 0 : _a.id)) { setOverlayId(this.el); } } componentDidLoad() { /** * Only create gesture if: * 1. A gesture does not already exist * 2. App is running in iOS mode * 3. A wrapper ref exists * 4. A group ref exists */ const { groupEl, wrapperEl } = this; if (!this.gesture && getIonMode(this) === 'ios' && wrapperEl && groupEl) { readTask(() => { const isScrollable = groupEl.scrollHeight > groupEl.clientHeight; if (!isScrollable) { this.gesture = createButtonActiveGesture(wrapperEl, (refEl) => refEl.classList.contains('action-sheet-button')); this.gesture.enable(true); } }); } /** * If action sheet was rendered with isOpen="true" * then we should open action sheet immediately. */ if (this.isOpen === true) { raf(() => this.present()); } /** * When binding values in frameworks such as Angular * it is possible for the value to be set after the Web Component * initializes but before the value watcher is set up in Stencil. * As a result, the watcher callback may not be fired. * We work around this by manually calling the watcher * callback when the component has loaded and the watcher * is configured. */ this.triggerChanged(); } render() { const { header, htmlAttributes, overlayIndex } = this; const mode = getIonMode(this); const allButtons = this.getButtons(); const cancelButton = allButtons.find((b) => b.role === 'cancel'); const buttons = allButtons.filter((b) => b.role !== 'cancel'); const headerID = `action-sheet-${overlayIndex}-header`; return (h(Host, Object.assign({ key: '7bbd202ca9e19727e7514abbe073687d982f80c3', role: "dialog", "aria-modal": "true", "aria-labelledby": header !== undefined ? headerID : null, tabindex: "-1" }, htmlAttributes, { style: { zIndex: `${20000 + this.overlayIndex}`, }, class: Object.assign(Object.assign({ [mode]: true }, getClassMap(this.cssClass)), { 'overlay-hidden': true, 'action-sheet-translucent': this.translucent }), onIonActionSheetWillDismiss: this.dispatchCancelHandler, onIonBackdropTap: this.onBackdropTap }), h("ion-backdrop", { key: '23344a9221a2e6720d7b9de5249dc37256cafa7b', tappable: this.backdropDismiss }), h("div", { key: 'fbc2ba15549c2ab04e759e82df6e177fd80cc0a6', tabindex: "0", "aria-hidden": "true" }), h("div", { key: '748ee5235d0b4cb26d6f1b7589f77af2e37ad28a', class: "action-sheet-wrapper ion-overlay-wrapper", ref: (el) => (this.wrapperEl = el) }, h("div", { key: '7ce5fa236cf75e9b1e49c4725c9a811078706554', class: "action-sheet-container" }, h("div", { key: 'dc2251f3bcee4a93e3449f09621cbd2b65d329e9', class: "action-sheet-group", ref: (el) => (this.groupEl = el) }, header !== undefined && (h("div", { key: '48d325c8a852f56ed57a9ada1a6709d05ba32ee2', id: headerID, class: { 'action-sheet-title': true, 'action-sheet-has-sub-title': this.subHeader !== undefined, } }, header, this.subHeader && h("div", { key: '66093728052eb67f37a35f3232761ce4a08896f3', class: "action-sheet-sub-title" }, this.subHeader))), buttons.map((b) => (h("button", Object.assign({}, b.htmlAttributes, { type: "button", id: b.id, class: buttonClass(b), onClick: () => this.buttonClick(b), disabled: b.disabled }), h("span", { class: "action-sheet-button-inner" }, b.icon && h("ion-icon", { icon: b.icon, "aria-hidden": "true", lazy: false, class: "action-sheet-icon" }), b.text), mode === 'md' && h("ion-ripple-effect", null))))), cancelButton && (h("div", { key: 'f4eb8e3e2885b85af5080df18d0de0bdd1d719de', class: "action-sheet-group action-sheet-group-cancel" }, h("button", Object.assign({ key: '169f4eb09255aba85062baad49ceb151239fbfb7' }, cancelButton.htmlAttributes, { type: "button", class: buttonClass(cancelButton), onClick: () => this.buttonClick(cancelButton) }), h("span", { key: '25fb8a466dd67ea94c79cfb4f9965527e1ce6d42', class: "action-sheet-button-inner" }, cancelButton.icon && (h("ion-icon", { key: 'eb5b071e120a2c86afdf967af6a763a43044d1ca', icon: cancelButton.icon, "aria-hidden": "true", lazy: false, class: "action-sheet-icon" })), cancelButton.text), mode === 'md' && h("ion-ripple-effect", { key: '452ad7e1052b2c681e2d98de8193949755ad4d54' })))))), h("div", { key: 'e1cecf280c987c050d9445e2c458b903f153089b', tabindex: "0", "aria-hidden": "true" }))); } static get is() { return "ion-action-sheet"; } static get encapsulation() { return "scoped"; } static get originalStyleUrls() { return { "ios": ["action-sheet.ios.scss"], "md": ["action-sheet.md.scss"] }; } static get styleUrls() { return { "ios": ["action-sheet.ios.css"], "md": ["action-sheet.md.css"] }; } static get properties() { return { "overlayIndex": { "type": "number", "attribute": "overlay-index", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": true, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "" }, "getter": false, "setter": false, "reflect": false }, "delegate": { "type": "unknown", "attribute": "delegate", "mutable": false, "complexType": { "original": "FrameworkDelegate", "resolved": "FrameworkDelegate | undefined", "references": { "FrameworkDelegate": { "location": "import", "path": "../../interface", "id": "src/interface.d.ts::FrameworkDelegate" } } }, "required": false, "optional": true, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "" }, "getter": false, "setter": false }, "hasController": { "type": "boolean", "attribute": "has-controller", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [{ "name": "internal", "text": undefined }], "text": "" }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "keyboardClose": { "type": "boolean", "attribute": "keyboard-close", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the keyboard will be automatically dismissed when the overlay is presented." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "true" }, "enterAnimation": { "type": "unknown", "attribute": "enter-animation", "mutable": false, "complexType": { "original": "AnimationBuilder", "resolved": "((baseEl: any, opts?: any) => Animation) | undefined", "references": { "AnimationBuilder": { "location": "import", "path": "../../interface", "id": "src/interface.d.ts::AnimationBuilder" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "Animation to use when the action sheet is presented." }, "getter": false, "setter": false }, "leaveAnimation": { "type": "unknown", "attribute": "leave-animation", "mutable": false, "complexType": { "original": "AnimationBuilder", "resolved": "((baseEl: any, opts?: any) => Animation) | undefined", "references": { "AnimationBuilder": { "location": "import", "path": "../../interface", "id": "src/interface.d.ts::AnimationBuilder" } } }, "required": false, "optional": true, "docs": { "tags": [], "text": "Animation to use when the action sheet is dismissed." }, "getter": false, "setter": false }, "buttons": { "type": "unknown", "attribute": "buttons", "mutable": false, "complexType": { "original": "(ActionSheetButton | string)[]", "resolved": "(string | ActionSheetButton<any>)[]", "references": { "ActionSheetButton": { "location": "import", "path": "./action-sheet-interface", "id": "src/components/action-sheet/action-sheet-interface.ts::ActionSheetButton" } } }, "required": false, "optional": false, "docs": { "tags": [], "text": "An array of buttons for the action sheet." }, "getter": false, "setter": false, "defaultValue": "[]" }, "cssClass": { "type": "string", "attribute": "css-class", "mutable": false, "complexType": { "original": "string | string[]", "resolved": "string | string[] | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Additional classes to apply for custom CSS. If multiple classes are\nprovided they should be separated by spaces." }, "getter": false, "setter": false, "reflect": false }, "backdropDismiss": { "type": "boolean", "attribute": "backdrop-dismiss", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the action sheet will be dismissed when the backdrop is clicked." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "true" }, "header": { "type": "string", "attribute": "header", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Title for the action sheet." }, "getter": false, "setter": false, "reflect": false }, "subHeader": { "type": "string", "attribute": "sub-header", "mutable": false, "complexType": { "original": "string", "resolved": "string | undefined", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Subtitle for the action sheet." }, "getter": false, "setter": false, "reflect": false }, "translucent": { "type": "boolean", "attribute": "translucent", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the action sheet will be translucent.\nOnly applies when the mode is `\"ios\"` and the device supports\n[`backdrop-filter`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter#Browser_compatibility)." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "animated": { "type": "boolean", "attribute": "animated", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the action sheet will animate." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "true" }, "htmlAttributes": { "type": "unknown", "attribute": "html-attributes", "mutable": false, "complexType": { "original": "{ [key: string]: any }", "resolved": "undefined | { [key: string]: any; }", "references": {} }, "required": false, "optional": true, "docs": { "tags": [], "text": "Additional attributes to pass to the action sheet." }, "getter": false, "setter": false }, "isOpen": { "type": "boolean", "attribute": "is-open", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "If `true`, the action sheet will open. If `false`, the action sheet will close.\nUse this if you need finer grained control over presentation, otherwise\njust use the actionSheetController or the `trigger` property.\nNote: `isOpen` will not automatically be set back to `false` when\nthe action sheet dismisses. You will need to do that in your code." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "trigger": { "type": "string", "attribute": "trigger", "mutable": false, "complexType": { "original": "string | undefined", "resolved": "string | undefined", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "An ID corresponding to the trigger element that\ncauses the action sheet to open when clicked." }, "getter": false, "setter": false, "reflect": false } }; } static get events() { return [{ "method": "didPresent", "name": "ionActionSheetDidPresent", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted after the action sheet has presented." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "willPresent", "name": "ionActionSheetWillPresent", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted before the action sheet has presented." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "willDismiss", "name": "ionActionSheetWillDismiss", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted before the action sheet has dismissed." }, "complexType": { "original": "OverlayEventDetail", "resolved": "OverlayEventDetail<any>", "references": { "OverlayEventDetail": { "location": "import", "path": "../../utils/overlays-interface", "id": "src/utils/overlays-interface.ts::OverlayEventDetail" } } } }, { "method": "didDismiss", "name": "ionActionSheetDidDismiss", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted after the action sheet has dismissed." }, "complexType": { "original": "OverlayEventDetail", "resolved": "OverlayEventDetail<any>", "references": { "OverlayEventDetail": { "location": "import", "path": "../../utils/overlays-interface", "id": "src/utils/overlays-interface.ts::OverlayEventDetail" } } } }, { "method": "didPresentShorthand", "name": "didPresent", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted after the action sheet has presented.\nShorthand for ionActionSheetWillDismiss." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "willPresentShorthand", "name": "willPresent", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted before the action sheet has presented.\nShorthand for ionActionSheetWillPresent." }, "complexType": { "original": "void", "resolved": "void", "references": {} } }, { "method": "willDismissShorthand", "name": "willDismiss", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted before the action sheet has dismissed.\nShorthand for ionActionSheetWillDismiss." }, "complexType": { "original": "OverlayEventDetail", "resolved": "OverlayEventDetail<any>", "references": { "OverlayEventDetail": { "location": "import", "path": "../../utils/overlays-interface", "id": "src/utils/overlays-interface.ts::OverlayEventDetail" } } } }, { "method": "didDismissShorthand", "name": "didDismiss", "bubbles": true, "cancelable": true, "composed": true, "docs": { "tags": [], "text": "Emitted after the action sheet has dismissed.\nShorthand for ionActionSheetDidDismiss." }, "complexType": { "original": "OverlayEventDetail", "resolved": "OverlayEventDetail<any>", "references": { "OverlayEventDetail": { "location": "import", "path": "../../utils/overlays-interface", "id": "src/utils/overlays-interface.ts::OverlayEventDetail" } } } }]; } static get methods() { return { "present": { "complexType": { "signature": "() => Promise<void>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<void>" }, "docs": { "text": "Present the action sheet overlay after it has been created.", "tags": [] } }, "dismiss": { "complexType": { "signature": "(data?: any, role?: string) => Promise<boolean>", "parameters": [{ "name": "data", "type": "any", "docs": "Any data to emit in the dismiss events." }, { "name": "role", "type": "string | undefined", "docs": "The role of the element that is dismissing the action sheet.\nThis can be useful in a button handler for determining which button was\nclicked to dismiss the action sheet.\nSome examples include: ``\"cancel\"`, `\"destructive\"`, \"selected\"`, and `\"backdrop\"`.\n\nThis is a no-op if the overlay has not been presented yet. If you want\nto remove an overlay from the DOM that was never presented, use the\n[remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method." }], "references": { "Promise": { "location": "global", "id": "global::Promise" } }, "return": "Promise<boolean>" }, "docs": { "text": "Dismiss the action sheet overlay after it has been presented.", "tags": [{ "name": "param", "text": "data Any data to emit in the dismiss events." }, { "name": "param", "text": "role The role of the element that is dismissing the action sheet.\nThis can be useful in a button handler for determining which button was\nclicked to dismiss the action sheet.\nSome examples include: ``\"cancel\"`, `\"destructive\"`, \"selected\"`, and `\"backdrop\"`.\n\nThis is a no-op if the overlay has not been presented yet. If you want\nto remove an overlay from the DOM that was never presented, use the\n[remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method." }] } }, "onDidDismiss": { "complexType": { "signature": "<T = any>() => Promise<OverlayEventDetail<T>>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" }, "OverlayEventDetail": { "location": "import", "path": "../../utils/overlays-interface", "id": "src/utils/overlays-interface.ts::OverlayEventDetail" }, "T": { "location": "global", "id": "global::T" } }, "return": "Promise<OverlayEventDetail<T>>" }, "docs": { "text": "Returns a promise that resolves when the action sheet did dismiss.", "tags": [] } }, "onWillDismiss": { "complexType": { "signature": "<T = any>() => Promise<OverlayEventDetail<T>>", "parameters": [], "references": { "Promise": { "location": "global", "id": "global::Promise" }, "OverlayEventDetail": { "location": "import", "path": "../../utils/overlays-interface", "id": "src/utils/overlays-interface.ts::OverlayEventDetail" }, "T": { "location": "global", "id": "global::T" } }, "return": "Promise<OverlayEventDetail<T>>" }, "docs": { "text": "Returns a promise that resolves when the action sheet will dismiss.", "tags": [] } } }; } static get elementRef() { return "el"; } static get watchers() { return [{ "propName": "isOpen", "methodName": "onIsOpenChange" }, { "propName": "trigger", "methodName": "triggerChanged" }]; } } const buttonClass = (button) => { return Object.assign({ 'action-sheet-button': true, 'ion-activatable': !button.disabled, 'ion-focusable': !button.disabled, [`action-sheet-${button.role}`]: button.role !== undefined }, getClassMap(button.cssClass)); };