diff --git a/core/src/components.d.ts b/core/src/components.d.ts index d08bf90865..26583fc8d5 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -113,6 +113,7 @@ declare global { cssClass?: string; enableBackdropDismiss?: boolean; enterAnimation?: AnimationBuilder; + keyboardClose?: boolean; leaveAnimation?: AnimationBuilder; overlayId?: number; subTitle?: string; @@ -183,6 +184,7 @@ declare global { enableBackdropDismiss?: boolean; enterAnimation?: AnimationBuilder; inputs?: AlertInput[]; + keyboardClose?: boolean; leaveAnimation?: AnimationBuilder; message?: string; overlayId?: number; @@ -1519,36 +1521,6 @@ declare global { } -import { - KeyboardController as IonKeyboardController -} from './components/keyboard-controller/keyboard-controller'; - -declare global { - interface HTMLIonKeyboardControllerElement extends IonKeyboardController, HTMLStencilElement { - } - var HTMLIonKeyboardControllerElement: { - prototype: HTMLIonKeyboardControllerElement; - new (): HTMLIonKeyboardControllerElement; - }; - interface HTMLElementTagNameMap { - "ion-keyboard-controller": HTMLIonKeyboardControllerElement; - } - interface ElementTagNameMap { - "ion-keyboard-controller": HTMLIonKeyboardControllerElement; - } - namespace JSX { - interface IntrinsicElements { - "ion-keyboard-controller": JSXElements.IonKeyboardControllerAttributes; - } - } - namespace JSXElements { - export interface IonKeyboardControllerAttributes extends HTMLAttributes { - - } - } -} - - import { Label as IonLabel } from './components/label/label'; @@ -1704,6 +1676,7 @@ declare global { duration?: number; enableBackdropDismiss?: boolean; enterAnimation?: AnimationBuilder; + keyboardClose?: boolean; leaveAnimation?: AnimationBuilder; overlayId?: number; showBackdrop?: boolean; @@ -1905,6 +1878,7 @@ declare global { delegate?: FrameworkDelegate; enableBackdropDismiss?: boolean; enterAnimation?: AnimationBuilder; + keyboardClose?: boolean; leaveAnimation?: AnimationBuilder; mode?: 'ios' | 'md'; overlayId?: number; @@ -2162,6 +2136,7 @@ declare global { duration?: number; enableBackdropDismiss?: boolean; enterAnimation?: AnimationBuilder; + keyboardClose?: boolean; leaveAnimation?: AnimationBuilder; overlayId?: number; showBackdrop?: boolean; @@ -2263,6 +2238,7 @@ declare global { enableBackdropDismiss?: boolean; enterAnimation?: AnimationBuilder; ev?: any; + keyboardClose?: boolean; leaveAnimation?: AnimationBuilder; mode?: 'ios' | 'md'; overlayId?: number; @@ -3540,6 +3516,7 @@ declare global { dismissOnPageChange?: boolean; duration?: number; enterAnimation?: AnimationBuilder; + keyboardClose?: boolean; leaveAnimation?: AnimationBuilder; message?: string; overlayId?: number; diff --git a/core/src/components/action-sheet/action-sheet.tsx b/core/src/components/action-sheet/action-sheet.tsx index a8901b5ebe..b691cab313 100644 --- a/core/src/components/action-sheet/action-sheet.tsx +++ b/core/src/components/action-sheet/action-sheet.tsx @@ -32,6 +32,7 @@ export class ActionSheet implements OverlayInterface { @Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement; @Prop({ context: 'config' }) config: Config; @Prop() overlayId: number; + @Prop() keyboardClose = true; /** * Animation to use when the action sheet is presented. diff --git a/core/src/components/action-sheet/readme.md b/core/src/components/action-sheet/readme.md index 608707957d..4740ecf8ef 100644 --- a/core/src/components/action-sheet/readme.md +++ b/core/src/components/action-sheet/readme.md @@ -89,6 +89,11 @@ If true, the action sheet will be dismissed when the backdrop is clicked. Defaul Animation to use when the action sheet is presented. +#### keyboardClose + +boolean + + #### leaveAnimation @@ -160,6 +165,11 @@ If true, the action sheet will be dismissed when the backdrop is clicked. Defaul Animation to use when the action sheet is presented. +#### keyboard-close + +boolean + + #### leave-animation diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx index 6e4ac57237..ca367a50a9 100644 --- a/core/src/components/alert/alert.tsx +++ b/core/src/components/alert/alert.tsx @@ -1,7 +1,7 @@ import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core'; import { Animation, AnimationBuilder, Config, CssClassMap } from '../../index'; import { createThemedClasses, getClassMap } from '../../utils/theme'; -import { BACKDROP, OverlayEventDetail, OverlayInterface, autoFocus, dismiss, eventMethod, isCancel, present } from '../../utils/overlays'; +import { BACKDROP, OverlayEventDetail, OverlayInterface, dismiss, eventMethod, isCancel, present } from '../../utils/overlays'; import iosEnterAnimation from './animations/ios.enter'; import iosLeaveAnimation from './animations/ios.leave'; @@ -35,6 +35,7 @@ export class Alert implements OverlayInterface { @Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement; @Prop({ context: 'config' }) config: Config; @Prop() overlayId: number; + @Prop() keyboardClose = true; /** * Animation to use when the alert is presented. @@ -150,9 +151,7 @@ export class Alert implements OverlayInterface { */ @Method() present(): Promise { - return present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation).then(() => { - autoFocus(this.el); - }); + return present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation); } /** diff --git a/core/src/components/alert/readme.md b/core/src/components/alert/readme.md index b2e3eec152..878cec5669 100644 --- a/core/src/components/alert/readme.md +++ b/core/src/components/alert/readme.md @@ -76,6 +76,11 @@ Animation to use when the alert is presented. Array of input to show in the alert. +#### keyboardClose + +boolean + + #### leaveAnimation @@ -161,6 +166,11 @@ Animation to use when the alert is presented. Array of input to show in the alert. +#### keyboard-close + +boolean + + #### leave-animation diff --git a/core/src/components/keyboard-controller/keyboard-controller.tsx b/core/src/components/keyboard-controller/keyboard-controller.tsx deleted file mode 100644 index 8f332f4503..0000000000 --- a/core/src/components/keyboard-controller/keyboard-controller.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import { Component, Event, EventEmitter, Prop} from '@stencil/core'; -import { Config } from '../..'; -import { KEY_TAB } from './keys'; - -let v2KeyboardWillShowHandler: () => void = null; -let v2KeyboardWillHideHandler: () => void = null; -let v2KeyboardDidShowHandler: () => void = null; -let v2KeyboardDidHideHandler: () => void = null; -let v1keyboardHide: () => void = null; -let v1keyboardShow: () => void = null; -let timeoutValue: number = null; - -@Component({ - tag: 'ion-keyboard-controller' -}) -export class KeyboardController { - - @Prop({context: 'config'}) config: Config; - - /** - * Emitted before the keyboard has shown. - */ - @Event() keyboardWillShow: EventEmitter; - - /** - * Emitted after the keyboard has shown. - */ - @Event() keyboardDidShow: EventEmitter; - - /** - * Emitted before the keyboard has hidden. - */ - @Event() keyboardWillHide: EventEmitter; - - /** - * Emitted after the keyboard has hidden. - */ - @Event() keyboardDidHide: EventEmitter; - - componentDidLoad() { - componentDidLoadImpl(this); - } - - isOpen(): boolean { - return hasFocusedTextInput(); - } - - onClose(callback: Function, pollingInterval: number = KEYBOARD_CLOSE_POLLING, maxPollingChecks: number = KEYBOARD_POLLING_CHECKS_MAX): Promise { - - return onCloseImpl(this, callback, pollingInterval, maxPollingChecks); - } -} - -export function onCloseImpl(keyboardController: KeyboardController, callback: Function, pollingInterval: number, maxPollingChecks: number): Promise { - let numChecks = 0; - - const promise: Promise = callback ? null : new Promise((resolve) => { - callback = resolve; - }); - - const checkKeyBoard = () => { - if (!keyboardController.isOpen() || numChecks > maxPollingChecks) { - setTimeout(() => { - callback(); - }, 400); - } else { - setTimeout(checkKeyBoard, pollingInterval); - } - numChecks++; - }; - - setTimeout(checkKeyBoard, pollingInterval); - return promise; -} - -export function componentDidLoadImpl(keyboardController: KeyboardController) { - focusOutline(document, keyboardController.config.get('focusOutline')); - if (keyboardController.config.getBoolean('keyboardResizes', false)) { - listenV2(window, keyboardController); - } else { - listenV1(window, keyboardController); - } -} - -export function listenV2(win: Window, keyboardController: KeyboardController) { - v2KeyboardWillShowHandler = () => { - keyboardController.keyboardWillShow.emit(); - }; - win.addEventListener('keyboardWillShow', v2KeyboardWillShowHandler); - - v2KeyboardWillHideHandler = () => { - keyboardController.keyboardWillHide.emit(); - }; - win.addEventListener('keyboardWillHide', v2KeyboardWillHideHandler); - - v2KeyboardDidShowHandler = () => { - keyboardController.keyboardDidShow.emit(); - }; - win.addEventListener('keyboardDidShow', v2KeyboardDidShowHandler); - - v2KeyboardDidHideHandler = () => { - keyboardController.keyboardDidHide.emit(); - }; - win.addEventListener('keyboardDidHide', v2KeyboardDidHideHandler); -} - -export function listenV1(win: Window, keyboardController: KeyboardController) { - v1keyboardHide = () => { - blurActiveInput(true, keyboardController); - }; - win.addEventListener('native.keyboardhide', v1keyboardHide); - - v1keyboardShow = () => { - blurActiveInput(false, keyboardController); - }; - win.addEventListener('native.keyboardshow', v1keyboardShow); -} - -export function blurActiveInput(shouldBlur: boolean, keyboardController: KeyboardController) { - clearTimeout(timeoutValue); - if (shouldBlur) { - timeoutValue = setTimeout(() => { - if (keyboardController.isOpen()) { - focusOutActiveElement(); - } - }, 80) as any as number; - } -} - -export function focusOutline(doc: Document, value: boolean) { - /* Focus Outline - * -------------------------------------------------- - * By default, when a keydown event happens from a tab key, then - * the 'focus-outline' css class is added to the body element - * so focusable elements have an outline. On a mousedown or - * touchstart event, then the 'focus-outline' css class is removed. - * - * Config default overrides: - * focusOutline: true - Always add the focus-outline - * focusOutline: false - Do not add the focus-outline - */ - - let isKeyInputEnabled = false; - - const cssClass = () => { - window.requestAnimationFrame(() => { - doc.body.classList[isKeyInputEnabled ? 'add' : 'remove']('focus-outline'); - }); - }; - - if (value === true) { - isKeyInputEnabled = true; - return cssClass(); - } else if (value === false) { - return; - } - - const keyDownHandler = (event: KeyboardEvent) => { - if (!isKeyInputEnabled && event.keyCode === KEY_TAB) { - isKeyInputEnabled = true; - enableKeyInput(); - } - }; - - const pointerDown = () => { - isKeyInputEnabled = false; - enableKeyInput(); - }; - - const enableKeyInput = () => { - cssClass(); - - doc.removeEventListener('mousedown', pointerDown); - doc.removeEventListener('touchstart', pointerDown); - - if (isKeyInputEnabled) { - doc.addEventListener('mousedown', pointerDown); - doc.addEventListener('touchstart', pointerDown); - } - }; - - doc.addEventListener('keydown', keyDownHandler); -} - - - -function hasFocusedTextInput() { - const activeElement = document.activeElement; - if (isTextInput(activeElement) && activeElement.parentElement) { - return activeElement.parentElement.querySelector(':focus') === activeElement; - } - return false; -} - -const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i; - -function isTextInput(el: any) { - return !!el && - (el.tagName === 'TEXTAREA' - || el.contentEditable === 'true' - || (el.tagName === 'INPUT' && !(NON_TEXT_INPUT_REGEX.test(el.type)))); -} - - -function focusOutActiveElement() { - const activeElement = document.activeElement as HTMLElement; - activeElement && activeElement.blur && activeElement.blur(); -} - - -const KEYBOARD_CLOSE_POLLING = 150; -const KEYBOARD_POLLING_CHECKS_MAX = 100; diff --git a/core/src/components/keyboard-controller/keys.ts b/core/src/components/keyboard-controller/keys.ts deleted file mode 100644 index eb797bac0e..0000000000 --- a/core/src/components/keyboard-controller/keys.ts +++ /dev/null @@ -1,9 +0,0 @@ - -export const KEY_LEFT = 37; -export const KEY_UP = 38; -export const KEY_RIGHT = 39; -export const KEY_DOWN = 40; -export const KEY_ENTER = 13; -export const KEY_ESCAPE = 27; -export const KEY_SPACE = 32; -export const KEY_TAB = 9; diff --git a/core/src/components/keyboard-controller/readme.md b/core/src/components/keyboard-controller/readme.md deleted file mode 100644 index 649b7d8167..0000000000 --- a/core/src/components/keyboard-controller/readme.md +++ /dev/null @@ -1,33 +0,0 @@ -# ion-keyboard-controller - - - - - - -## Events - -#### keyboardDidHide - -Emitted after the keyboard has hidden. - - -#### keyboardDidShow - -Emitted after the keyboard has shown. - - -#### keyboardWillHide - -Emitted before the keyboard has hidden. - - -#### keyboardWillShow - -Emitted before the keyboard has shown. - - - ----------------------------------------------- - -*Built with [StencilJS](https://stenciljs.com/)* diff --git a/core/src/components/loading/loading.tsx b/core/src/components/loading/loading.tsx index 0d8c129a56..2e506b6d14 100644 --- a/core/src/components/loading/loading.tsx +++ b/core/src/components/loading/loading.tsx @@ -33,6 +33,7 @@ export class Loading implements OverlayInterface { @Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement; @Prop({ context: 'config' }) config: Config; @Prop() overlayId: number; + @Prop() keyboardClose = true; /** * Animation to use when the loading indicator is presented. diff --git a/core/src/components/loading/readme.md b/core/src/components/loading/readme.md index cd394c75d2..04878374b3 100644 --- a/core/src/components/loading/readme.md +++ b/core/src/components/loading/readme.md @@ -76,6 +76,11 @@ If true, the loading indicator will be dismissed when the backdrop is clicked. D Animation to use when the loading indicator is presented. +#### keyboardClose + +boolean + + #### leaveAnimation @@ -162,6 +167,11 @@ If true, the loading indicator will be dismissed when the backdrop is clicked. D Animation to use when the loading indicator is presented. +#### keyboard-close + +boolean + + #### leave-animation diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 04680a962a..06bbab64fe 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -34,6 +34,7 @@ export class Modal implements OverlayInterface { @Prop() overlayId: number; @Prop() delegate: FrameworkDelegate; + @Prop() keyboardClose = true; /** * The color to use from your Sass `$colors` map. diff --git a/core/src/components/modal/readme.md b/core/src/components/modal/readme.md index bd58c5d631..1a65b5575e 100644 --- a/core/src/components/modal/readme.md +++ b/core/src/components/modal/readme.md @@ -98,6 +98,11 @@ If true, the modal will be dismissed when the backdrop is clicked. Defaults to ` Animation to use when the modal is presented. +#### keyboardClose + +boolean + + #### leaveAnimation @@ -185,6 +190,11 @@ If true, the modal will be dismissed when the backdrop is clicked. Defaults to ` Animation to use when the modal is presented. +#### keyboard-close + +boolean + + #### leave-animation diff --git a/core/src/components/nav/nav-util.ts b/core/src/components/nav/nav-util.ts index cb491d3ab6..17458bd836 100644 --- a/core/src/components/nav/nav-util.ts +++ b/core/src/components/nav/nav-util.ts @@ -1,6 +1,7 @@ import { ViewController, isViewController } from './view-controller'; import { NavControllerBase } from './nav'; import { Transition } from './transition'; +import { FrameworkDelegate } from '../..'; export function convertToView(page: any, params: any): ViewController { if (!page) { @@ -81,11 +82,8 @@ export interface NavOptions { id?: string; keyboardClose?: boolean; progressAnimation?: boolean; - disableApp?: boolean; - minClickBlockDuration?: number; ev?: any; - updateUrl?: boolean; - isNavRoot?: boolean; + delegate?: FrameworkDelegate; viewIsReady?: () => Promise; } diff --git a/core/src/components/picker/picker.tsx b/core/src/components/picker/picker.tsx index 353e19838d..6036cf9f29 100644 --- a/core/src/components/picker/picker.tsx +++ b/core/src/components/picker/picker.tsx @@ -33,6 +33,7 @@ export class Picker implements OverlayInterface { @Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement; @Prop({ context: 'config' }) config: Config; @Prop() overlayId: number; + @Prop() keyboardClose = true; /** * Animation to use when the picker is presented. @@ -145,10 +146,6 @@ export class Picker implements OverlayInterface { @Method() present(): Promise { return present(this, 'pickerEnter', iosEnterAnimation, iosEnterAnimation, undefined).then(() => { - // blur the currently active element - const activeElement: any = document.activeElement; - activeElement && activeElement.blur && activeElement.blur(); - // If there is a duration, dismiss after that amount of time if (this.duration > 10) { this.durationTimeout = setTimeout(() => this.dismiss(), this.duration); diff --git a/core/src/components/picker/readme.md b/core/src/components/picker/readme.md index d277812ad2..d134d38305 100644 --- a/core/src/components/picker/readme.md +++ b/core/src/components/picker/readme.md @@ -52,6 +52,11 @@ If true, the picker will be dismissed when the backdrop is clicked. Defaults to Animation to use when the picker is presented. +#### keyboardClose + +boolean + + #### leaveAnimation @@ -123,6 +128,11 @@ If true, the picker will be dismissed when the backdrop is clicked. Defaults to Animation to use when the picker is presented. +#### keyboard-close + +boolean + + #### leave-animation diff --git a/core/src/components/popover/popover.tsx b/core/src/components/popover/popover.tsx index 41ee509fcd..c82b7ece07 100644 --- a/core/src/components/popover/popover.tsx +++ b/core/src/components/popover/popover.tsx @@ -33,6 +33,7 @@ export class Popover implements OverlayInterface { @Prop({ context: 'config' }) config: Config; @Prop() delegate: FrameworkDelegate; @Prop() overlayId: number; + @Prop() keyboardClose = true; /** * The color to use from your Sass `$colors` map. diff --git a/core/src/components/popover/readme.md b/core/src/components/popover/readme.md index b42c3fc9e6..27a934ab64 100644 --- a/core/src/components/popover/readme.md +++ b/core/src/components/popover/readme.md @@ -85,6 +85,11 @@ any The event to pass to the popover animation. +#### keyboardClose + +boolean + + #### leaveAnimation @@ -186,6 +191,11 @@ any The event to pass to the popover animation. +#### keyboard-close + +boolean + + #### leave-animation diff --git a/core/src/components/toast/readme.md b/core/src/components/toast/readme.md index 723f2ea1af..9c5efc6082 100644 --- a/core/src/components/toast/readme.md +++ b/core/src/components/toast/readme.md @@ -80,6 +80,11 @@ until `dismiss()` is called. Animation to use when the toast is presented. +#### keyboardClose + +boolean + + #### leaveAnimation @@ -166,6 +171,11 @@ until `dismiss()` is called. Animation to use when the toast is presented. +#### keyboard-close + +boolean + + #### leave-animation diff --git a/core/src/components/toast/toast.tsx b/core/src/components/toast/toast.tsx index 38818e053a..774de33fee 100644 --- a/core/src/components/toast/toast.tsx +++ b/core/src/components/toast/toast.tsx @@ -33,6 +33,7 @@ export class Toast implements OverlayInterface { @Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement; @Prop({ context: 'config' }) config: Config; @Prop() overlayId: number; + @Prop() keyboardClose = false; /** * Animation to use when the toast is presented. diff --git a/core/src/index.d.ts b/core/src/index.d.ts index 3a2b5fa096..f7cc5ce36d 100644 --- a/core/src/index.d.ts +++ b/core/src/index.d.ts @@ -44,8 +44,6 @@ export { Item } from './components/item/item'; export { ItemDivider } from './components/item-divider/item-divider'; export { ItemOption } from './components/item-option/item-option'; export { ItemSliding } from './components/item-sliding/item-sliding'; -export { KeyboardController } from './components/keyboard-controller/keyboard-controller'; -export * from './components/keyboard-controller/keys'; export { Label } from './components/label/label'; export { List } from './components/list/list'; export { ListHeader } from './components/list-header/list-header'; diff --git a/core/src/utils/index.ts b/core/src/utils/index.ts new file mode 100644 index 0000000000..8da1bcb91c --- /dev/null +++ b/core/src/utils/index.ts @@ -0,0 +1,2 @@ +export * from './haptic'; +export * from './keyboard'; diff --git a/core/src/utils/keyboard.ts b/core/src/utils/keyboard.ts new file mode 100644 index 0000000000..d6089836a5 --- /dev/null +++ b/core/src/utils/keyboard.ts @@ -0,0 +1,22 @@ + +export function isTextInputFocused(): boolean { + const activeElement = document.activeElement; + if (isTextInput(activeElement) && activeElement.parentElement) { + return activeElement.parentElement.querySelector(':focus') === activeElement; + } + return false; +} + +export function closeKeyboard() { + const activeElement = document.activeElement as HTMLElement; + activeElement && activeElement.blur && activeElement.blur(); +} + +const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i; + +function isTextInput(el: any) { + return !!el && + (el.tagName === 'TEXTAREA' + || el.contentEditable === 'true' + || (el.tagName === 'INPUT' && !(NON_TEXT_INPUT_REGEX.test(el.type)))); +} diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index e42a65c723..a8c3b7138e 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -115,6 +115,9 @@ function overlayAnimation( overlay.animation.destroy(); overlay.animation = undefined; } + if (overlay.keyboardClose) { + closeKeyboard(); + } return overlay.animationCtrl.create(animationBuilder, baseEl, opts).then(animation => { overlay.animation = animation; if (!overlay.willAnimate) { @@ -170,12 +173,15 @@ export function onceEvent(element: HTMLElement, eventName: string, callback: (ev element.addEventListener(eventName, handler); } +function closeKeyboard() { + const activeElement = document.activeElement as HTMLElement; + activeElement && activeElement.blur && activeElement.blur(); +} export function isCancel(role: string): boolean { return role === 'cancel' || role === BACKDROP; } - export interface OverlayEventDetail { data?: any; role?: string; @@ -185,6 +191,7 @@ export interface OverlayInterface { mode: string; el: HTMLElement; willAnimate: boolean; + keyboardClose: boolean; config: Config; overlayId: number; presented: boolean;