mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 13:32:54 +08:00
feature(keyboard-controller): add keyboard-controller element
This commit is contained in:
@ -0,0 +1,173 @@
|
||||
import { Component, Prop, Event, EventEmitter} from '@stencil/core';
|
||||
import { KeyboardController } from './keyboard-interfaces';
|
||||
import { Config } from '../..';
|
||||
import { focusOutActiveElement, getDocument, getWindow, hasFocusedTextInput } from '../../utils/helpers';
|
||||
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 IonKeyboardController implements KeyboardController {
|
||||
|
||||
@Prop({context: 'config'}) config: Config;
|
||||
@Prop({context: 'dom'}) domController: any;
|
||||
@Event() keyboardWillShow: EventEmitter;
|
||||
@Event() keyboardDidShow: EventEmitter;
|
||||
@Event() keyboardWillHide: EventEmitter;
|
||||
@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(getDocument(), keyboardController.config.get('focusOutline'), keyboardController);
|
||||
if (keyboardController.config.getBoolean('keyboardResizes', false)) {
|
||||
listenV2(getWindow(), keyboardController);
|
||||
} else {
|
||||
listenV1(getWindow(), 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, keyboardController: KeyboardController) {
|
||||
/* 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 = () => {
|
||||
keyboardController.dom.write(() => {
|
||||
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);
|
||||
}
|
||||
|
||||
const KEYBOARD_CLOSE_POLLING = 150;
|
||||
const KEYBOARD_POLLING_CHECKS_MAX = 100;
|
14
packages/core/src/components/keyboard-controller/keyboard-interfaces.d.ts
vendored
Normal file
14
packages/core/src/components/keyboard-controller/keyboard-interfaces.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { EventEmitter } from '@stencil/core';
|
||||
import { Config } from '../..';
|
||||
|
||||
export interface KeyboardController {
|
||||
config?: Config;
|
||||
dom?: any; // TODO, make dom controller
|
||||
keyboardWillShow?: EventEmitter;
|
||||
keyboardDidShow?: EventEmitter;
|
||||
keyboardWillHide?: EventEmitter;
|
||||
keyboardDidHide?: EventEmitter;
|
||||
|
||||
isOpen?(): boolean;
|
||||
onClose?(callback: Function, pollingInterval: number, maxPollingchecks: number): Promise<any>;
|
||||
}
|
9
packages/core/src/components/keyboard-controller/keys.ts
Normal file
9
packages/core/src/components/keyboard-controller/keys.ts
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
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;
|
@ -30,7 +30,7 @@ import {
|
||||
|
||||
import { ViewControllerImpl } from './view-controller-impl';
|
||||
|
||||
import { assert, isDef, isNumber } from '../utils/helpers';
|
||||
import { assert, focusOutActiveElement, isDef, isNumber } from '../utils/helpers';
|
||||
|
||||
import { buildIOSTransition } from './transitions/transition.ios';
|
||||
import { buildMdTransition } from './transitions/transition.md';
|
||||
@ -425,8 +425,8 @@ export function transitionFinish(nav: Nav, transition: Transition, delegate: Fra
|
||||
|
||||
// TODO - navChange on the deep linker used to be called here
|
||||
|
||||
if (opts.keyboardClose) {
|
||||
// TODO - close the keyboard
|
||||
if (opts.keyboardClose !== false) {
|
||||
focusOutActiveElement();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,7 +168,50 @@ export function isReady(element: Element): Promise<any> {
|
||||
});
|
||||
}
|
||||
|
||||
export function getOrAppendElement(tagName: string): Element {
|
||||
const element = document.querySelector(tagName);
|
||||
if (element) {
|
||||
return element;
|
||||
}
|
||||
const tmp = document.createElement(tagName);
|
||||
document.body.appendChild(tmp);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
export function deepCopy(obj: any) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
export function getWindow() {
|
||||
return window;
|
||||
}
|
||||
|
||||
export function getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
export function getActiveElement(): HTMLElement {
|
||||
return getDocument()['activeElement'] as HTMLElement;
|
||||
}
|
||||
|
||||
export function focusOutActiveElement() {
|
||||
const activeElement = getActiveElement();
|
||||
activeElement && activeElement.blur && activeElement.blur();
|
||||
}
|
||||
|
||||
export function isTextInput(ele: any) {
|
||||
return !!ele &&
|
||||
(ele.tagName === 'TEXTAREA'
|
||||
|| ele.contentEditable === 'true'
|
||||
|| (ele.tagName === 'INPUT' && !(NON_TEXT_INPUT_REGEX.test(ele.type))));
|
||||
}
|
||||
export const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i;
|
||||
|
||||
export function hasFocusedTextInput() {
|
||||
const activeElement = getActiveElement();
|
||||
if (isTextInput(activeElement)) {
|
||||
return activeElement.parentElement.querySelector(':focus') === activeElement;
|
||||
}
|
||||
return false;
|
||||
}
|
Reference in New Issue
Block a user