refactor(keyboard): move to utils

This commit is contained in:
Manu Mtz.-Almeida
2018-03-19 20:10:37 +01:00
parent 4d8a99f03b
commit c85f7483c9
23 changed files with 120 additions and 299 deletions

View File

@ -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;

View File

@ -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.

View File

@ -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

View File

@ -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<void> {
return present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation).then(() => {
autoFocus(this.el);
});
return present(this, 'alertEnter', iosEnterAnimation, mdEnterAnimation);
}
/**

View File

@ -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

View File

@ -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<any> {
return onCloseImpl(this, callback, pollingInterval, maxPollingChecks);
}
}
export function onCloseImpl(keyboardController: KeyboardController, callback: Function, pollingInterval: number, maxPollingChecks: number): Promise<any> {
let numChecks = 0;
const promise: Promise<any> = 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;

View File

@ -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;

View File

@ -1,33 +0,0 @@
# ion-keyboard-controller
<!-- Auto Generated Below -->
## 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/)*

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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<any>;
}

View File

@ -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<void> {
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);

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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.

2
core/src/index.d.ts vendored
View File

@ -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';

2
core/src/utils/index.ts Normal file
View File

@ -0,0 +1,2 @@
export * from './haptic';
export * from './keyboard';

View File

@ -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))));
}

View File

@ -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;