mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
215 lines
6.4 KiB
TypeScript
215 lines
6.4 KiB
TypeScript
import {Directive, Attribute, ElementRef, Renderer, Input, Output, EventEmitter, HostListener} from '@angular/core';
|
|
import {NgControl} from '@angular/common';
|
|
|
|
import {Config} from '../../config/config';
|
|
import {CSS, hasFocus} from '../../util/dom';
|
|
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
@Directive({
|
|
selector: '.text-input'
|
|
})
|
|
export class NativeInput {
|
|
private _relocated: boolean;
|
|
private _clone: boolean;
|
|
private _blurring: boolean;
|
|
private _unrefBlur: Function;
|
|
|
|
@Output() focusChange: EventEmitter<boolean> = new EventEmitter();
|
|
@Output() valueChange: EventEmitter<string> = new EventEmitter();
|
|
|
|
constructor(
|
|
private _elementRef: ElementRef,
|
|
private _renderer: Renderer,
|
|
config: Config,
|
|
public ngControl: NgControl
|
|
) {
|
|
this._clone = config.getBoolean('inputCloning', false);
|
|
this._blurring = config.getBoolean('inputBlurring', false);
|
|
}
|
|
|
|
@HostListener('input', ['$event'])
|
|
private _change(ev) {
|
|
this.valueChange.emit(ev.target.value);
|
|
}
|
|
|
|
@HostListener('focus')
|
|
private _focus() {
|
|
var self = this;
|
|
|
|
self.focusChange.emit(true);
|
|
|
|
function docTouchEnd(ev) {
|
|
var tapped: HTMLElement = ev.target;
|
|
if (tapped && self.element()) {
|
|
if (tapped.tagName !== 'INPUT' && tapped.tagName !== 'TEXTAREA' && !tapped.classList.contains('input-cover')) {
|
|
self.element().blur();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self._blurring) {
|
|
// automatically blur input if:
|
|
// 1) this input has focus
|
|
// 2) the newly tapped document element is not an input
|
|
console.debug('input blurring enabled');
|
|
|
|
document.addEventListener('touchend', docTouchEnd, true);
|
|
self._unrefBlur = function() {
|
|
console.debug('input blurring disabled');
|
|
document.removeEventListener('touchend', docTouchEnd, true);
|
|
};
|
|
}
|
|
}
|
|
|
|
@HostListener('blur')
|
|
private _blur() {
|
|
this.focusChange.emit(false);
|
|
this.hideFocus(false);
|
|
|
|
this._unrefBlur && this._unrefBlur();
|
|
this._unrefBlur = null;
|
|
}
|
|
|
|
labelledBy(val: string) {
|
|
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'aria-labelledby', val);
|
|
}
|
|
|
|
isDisabled(val: boolean) {
|
|
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'disabled', val ? '' : null);
|
|
}
|
|
|
|
setFocus() {
|
|
// let's set focus to the element
|
|
// but only if it does not already have focus
|
|
if (document.activeElement !== this.element()) {
|
|
this.element().focus();
|
|
}
|
|
}
|
|
|
|
beginFocus(shouldFocus: boolean, inputRelativeY: number) {
|
|
if (this._relocated !== shouldFocus) {
|
|
var focusedInputEle = this.element();
|
|
if (shouldFocus) {
|
|
// we should focus into this element
|
|
|
|
if (this._clone) {
|
|
// this platform needs the input to be cloned
|
|
// this allows for the actual input to receive the focus from
|
|
// the user's touch event, but before it receives focus, it
|
|
// moves the actual input to a location that will not screw
|
|
// up the app's layout, and does not allow the native browser
|
|
// to attempt to scroll the input into place (messing up headers/footers)
|
|
// the cloned input fills the area of where native input should be
|
|
// while the native input fakes out the browser by relocating itself
|
|
// before it receives the actual focus event
|
|
var clonedInputEle = cloneInput(focusedInputEle, 'cloned-focus');
|
|
focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle);
|
|
|
|
// move the native input to a location safe to receive focus
|
|
// according to the browser, the native input receives focus in an
|
|
// area which doesn't require the browser to scroll the input into place
|
|
focusedInputEle.style[CSS.transform] = `translate3d(-9999px,${inputRelativeY}px,0)`;
|
|
focusedInputEle.style.opacity = '0';
|
|
}
|
|
|
|
// let's now set focus to the actual native element
|
|
// at this point it is safe to assume the browser will not attempt
|
|
// to scroll the input into view itself (screwing up headers/footers)
|
|
this.setFocus();
|
|
|
|
if (this._clone) {
|
|
focusedInputEle.classList.add('cloned-active');
|
|
}
|
|
|
|
} else {
|
|
// should remove the focus
|
|
if (this._clone) {
|
|
// should remove the cloned node
|
|
focusedInputEle.classList.remove('cloned-active');
|
|
focusedInputEle.style[CSS.transform] = '';
|
|
focusedInputEle.style.opacity = '';
|
|
removeClone(focusedInputEle, 'cloned-focus');
|
|
}
|
|
}
|
|
|
|
this._relocated = shouldFocus;
|
|
}
|
|
}
|
|
|
|
hideFocus(shouldHideFocus: boolean) {
|
|
let focusedInputEle = this.element();
|
|
|
|
console.debug(`native input hideFocus, shouldHideFocus: ${shouldHideFocus}, input value: ${focusedInputEle.value}`);
|
|
|
|
if (shouldHideFocus) {
|
|
let clonedInputEle = cloneInput(focusedInputEle, 'cloned-move');
|
|
|
|
focusedInputEle.classList.add('cloned-active');
|
|
focusedInputEle.parentNode.insertBefore(clonedInputEle, focusedInputEle);
|
|
|
|
} else {
|
|
focusedInputEle.classList.remove('cloned-active');
|
|
removeClone(focusedInputEle, 'cloned-move');
|
|
}
|
|
}
|
|
|
|
hasFocus(): boolean {
|
|
return hasFocus(this.element());
|
|
}
|
|
|
|
getValue(): string {
|
|
return this.element().value;
|
|
}
|
|
|
|
element(): HTMLInputElement {
|
|
return this._elementRef.nativeElement;
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
this._unrefBlur && this._unrefBlur();
|
|
}
|
|
|
|
}
|
|
|
|
function cloneInput(focusedInputEle, addCssClass) {
|
|
let clonedInputEle = focusedInputEle.cloneNode(true);
|
|
clonedInputEle.classList.add('cloned-input');
|
|
clonedInputEle.classList.add(addCssClass);
|
|
clonedInputEle.setAttribute('aria-hidden', true);
|
|
clonedInputEle.removeAttribute('aria-labelledby');
|
|
clonedInputEle.tabIndex = -1;
|
|
clonedInputEle.style.width = (focusedInputEle.offsetWidth + 10) + 'px';
|
|
clonedInputEle.style.height = focusedInputEle.offsetHeight + 'px';
|
|
clonedInputEle.value = focusedInputEle.value;
|
|
return clonedInputEle;
|
|
}
|
|
|
|
function removeClone(focusedInputEle, queryCssClass) {
|
|
let clonedInputEle = focusedInputEle.parentElement.querySelector('.' + queryCssClass);
|
|
if (clonedInputEle) {
|
|
clonedInputEle.parentNode.removeChild(clonedInputEle);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
@Directive({
|
|
selector: '[next-input]'
|
|
})
|
|
export class NextInput {
|
|
@Output() focused: EventEmitter<boolean> = new EventEmitter();
|
|
|
|
@HostListener('focus')
|
|
receivedFocus() {
|
|
console.debug('native-input, next-input received focus');
|
|
this.focused.emit(true);
|
|
}
|
|
|
|
}
|