mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-21 04:53:58 +08:00
@ -340,19 +340,17 @@ export class InputBase {
|
|||||||
*/
|
*/
|
||||||
initFocus() {
|
initFocus() {
|
||||||
// begin the process of setting focus to the inner input element
|
// begin the process of setting focus to the inner input element
|
||||||
let scrollView = this._scrollView;
|
var scrollView = this._scrollView;
|
||||||
|
|
||||||
if (scrollView) {
|
if (scrollView) {
|
||||||
// this input is inside of a scroll view
|
// this input is inside of a scroll view
|
||||||
|
|
||||||
// find out if text input should be manually scrolled into view
|
// find out if text input should be manually scrolled into view
|
||||||
let ele = this._elementRef.nativeElement;
|
|
||||||
let itemEle = closest(ele, 'ion-item');
|
|
||||||
if (itemEle) {
|
|
||||||
ele = itemEle;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scrollData = InputBase.getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getContentDimensions(), this._keyboardHeight, this._platform.height());
|
// get container of this input, probably an ion-item a few nodes up
|
||||||
|
var ele = this._elementRef.nativeElement;
|
||||||
|
ele = closest(ele, 'ion-item,[ion-item]') || ele;
|
||||||
|
|
||||||
|
var scrollData = InputBase.getScrollData(ele.offsetTop, ele.offsetHeight, scrollView.getContentDimensions(), this._keyboardHeight, this._platform.height());
|
||||||
if (scrollData.scrollAmount > -3 && scrollData.scrollAmount < 3) {
|
if (scrollData.scrollAmount > -3 && scrollData.scrollAmount < 3) {
|
||||||
// the text input is in a safe position that doesn't
|
// the text input is in a safe position that doesn't
|
||||||
// require it to be scrolled into view, just set focus now
|
// require it to be scrolled into view, just set focus now
|
||||||
@ -368,21 +366,22 @@ export class InputBase {
|
|||||||
|
|
||||||
// manually scroll the text input to the top
|
// manually scroll the text input to the top
|
||||||
// do not allow any clicks while it's scrolling
|
// do not allow any clicks while it's scrolling
|
||||||
let scrollDuration = getScrollAssistDuration(scrollData.scrollAmount);
|
var scrollDuration = getScrollAssistDuration(scrollData.scrollAmount);
|
||||||
this._app.setEnabled(false, scrollDuration);
|
this._app.setEnabled(false, scrollDuration);
|
||||||
this._nav && this._nav.setTransitioning(true, scrollDuration);
|
this._nav && this._nav.setTransitioning(true, scrollDuration);
|
||||||
|
|
||||||
// temporarily move the focus to the focus holder so the browser
|
// temporarily move the focus to the focus holder so the browser
|
||||||
// doesn't freak out while it's trying to get the input in place
|
// doesn't freak out while it's trying to get the input in place
|
||||||
// at this point the native text input still does not have focus
|
// at this point the native text input still does not have focus
|
||||||
this._native.relocate(true, scrollData.inputSafeY);
|
this._native.beginFocus(true, scrollData.inputSafeY);
|
||||||
|
|
||||||
// scroll the input into place
|
// scroll the input into place
|
||||||
scrollView.scrollTo(0, scrollData.scrollTo, scrollDuration).then(() => {
|
scrollView.scrollTo(0, scrollData.scrollTo, scrollDuration).then(() => {
|
||||||
// the scroll view is in the correct position now
|
// the scroll view is in the correct position now
|
||||||
// give the native text input focus
|
// give the native text input focus
|
||||||
this._native.relocate(false, 0);
|
this._native.beginFocus(false, 0);
|
||||||
|
|
||||||
|
// ensure this is the focused input
|
||||||
this.setFocus();
|
this.setFocus();
|
||||||
|
|
||||||
// all good, allow clicks again
|
// all good, allow clicks again
|
||||||
@ -417,6 +416,7 @@ export class InputBase {
|
|||||||
this._form.setAsFocused(this);
|
this._form.setAsFocused(this);
|
||||||
|
|
||||||
// set focus on the actual input element
|
// set focus on the actual input element
|
||||||
|
console.debug(`input-base, setFocus ${this._native.element().value}`);
|
||||||
this._native.setFocus();
|
this._native.setFocus();
|
||||||
|
|
||||||
// ensure the body hasn't scrolled down
|
// ensure the body hasn't scrolled down
|
||||||
|
@ -91,6 +91,15 @@ input.text-input:-webkit-autofill {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-has-focus {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-has-focus input,
|
||||||
|
.input-has-focus textarea {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Scroll Assist Input
|
// Scroll Assist Input
|
||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
@ -131,7 +140,7 @@ input.text-input:-webkit-autofill {
|
|||||||
// --------------------------------------------------
|
// --------------------------------------------------
|
||||||
|
|
||||||
.text-input.cloned-input {
|
.text-input.cloned-input {
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Directive, Attribute, ElementRef, Renderer, Input, Output, EventEmitter, HostListener} from 'angular2/core';
|
import {Directive, Attribute, ElementRef, Renderer, Input, Output, EventEmitter, HostListener} from 'angular2/core';
|
||||||
import {NgControl} from 'angular2/common';
|
import {NgControl} from 'angular2/common';
|
||||||
|
|
||||||
|
import {Config} from '../../config/config';
|
||||||
import {CSS, hasFocus, raf} from '../../util/dom';
|
import {CSS, hasFocus, raf} from '../../util/dom';
|
||||||
|
|
||||||
|
|
||||||
@ -12,6 +13,7 @@ import {CSS, hasFocus, raf} from '../../util/dom';
|
|||||||
})
|
})
|
||||||
export class NativeInput {
|
export class NativeInput {
|
||||||
private _relocated: boolean;
|
private _relocated: boolean;
|
||||||
|
private _clone: boolean;
|
||||||
|
|
||||||
@Output() focusChange: EventEmitter<boolean> = new EventEmitter();
|
@Output() focusChange: EventEmitter<boolean> = new EventEmitter();
|
||||||
@Output() valueChange: EventEmitter<string> = new EventEmitter();
|
@Output() valueChange: EventEmitter<string> = new EventEmitter();
|
||||||
@ -19,28 +21,22 @@ export class NativeInput {
|
|||||||
constructor(
|
constructor(
|
||||||
private _elementRef: ElementRef,
|
private _elementRef: ElementRef,
|
||||||
private _renderer: Renderer,
|
private _renderer: Renderer,
|
||||||
|
config: Config,
|
||||||
public ngControl: NgControl
|
public ngControl: NgControl
|
||||||
) {}
|
) {
|
||||||
|
this._clone = config.getBoolean('inputCloning', false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@HostListener('input', ['$event'])
|
@HostListener('input', ['$event'])
|
||||||
private _change(ev) {
|
private _change(ev) {
|
||||||
this.valueChange.emit(ev.target.value);
|
this.valueChange.emit(ev.target.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@HostListener('focus')
|
@HostListener('focus')
|
||||||
private _focus() {
|
private _focus() {
|
||||||
this.focusChange.emit(true);
|
this.focusChange.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
@HostListener('blur')
|
@HostListener('blur')
|
||||||
private _blur() {
|
private _blur() {
|
||||||
this.focusChange.emit(false);
|
this.focusChange.emit(false);
|
||||||
@ -55,55 +51,69 @@ export class NativeInput {
|
|||||||
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'disabled', val ? '' : null);
|
this._renderer.setElementAttribute(this._elementRef.nativeElement, 'disabled', val ? '' : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
setFocus() {
|
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();
|
this.element().focus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
beginFocus(shouldFocus: boolean, inputRelativeY: number) {
|
||||||
* @private
|
if (this._relocated !== shouldFocus) {
|
||||||
*/
|
var focusedInputEle = this.element();
|
||||||
relocate(shouldRelocate: boolean, inputRelativeY: number) {
|
if (shouldFocus) {
|
||||||
console.debug('native input relocate', shouldRelocate, inputRelativeY);
|
// we should focus into this element
|
||||||
|
|
||||||
if (this._relocated !== shouldRelocate) {
|
|
||||||
|
|
||||||
let focusedInputEle = this.element();
|
|
||||||
if (shouldRelocate) {
|
|
||||||
let clonedInputEle = cloneInput(focusedInputEle, 'cloned-focus');
|
|
||||||
|
|
||||||
|
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);
|
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[CSS.transform] = `translate3d(-9999px,${inputRelativeY}px,0)`;
|
||||||
focusedInputEle.style.opacity = '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();
|
this.setFocus();
|
||||||
|
|
||||||
raf(() => {
|
if (this._clone) {
|
||||||
focusedInputEle.classList.add('cloned-active');
|
focusedInputEle.classList.add('cloned-active');
|
||||||
});
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
// should remove the focus
|
||||||
|
if (this._clone) {
|
||||||
|
// should remove the cloned node
|
||||||
focusedInputEle.classList.remove('cloned-active');
|
focusedInputEle.classList.remove('cloned-active');
|
||||||
focusedInputEle.style[CSS.transform] = '';
|
focusedInputEle.style[CSS.transform] = '';
|
||||||
focusedInputEle.style.opacity = '';
|
focusedInputEle.style.opacity = '';
|
||||||
|
|
||||||
removeClone(focusedInputEle, 'cloned-focus');
|
removeClone(focusedInputEle, 'cloned-focus');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._relocated = shouldRelocate;
|
this._relocated = shouldFocus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
hideFocus(shouldHideFocus: boolean) {
|
hideFocus(shouldHideFocus: boolean) {
|
||||||
console.debug('native input hideFocus', shouldHideFocus);
|
|
||||||
|
|
||||||
let focusedInputEle = this.element();
|
let focusedInputEle = this.element();
|
||||||
|
|
||||||
|
console.debug(`native input hideFocus, shouldHideFocus: ${shouldHideFocus}, input value: ${focusedInputEle.value}`);
|
||||||
|
|
||||||
if (shouldHideFocus) {
|
if (shouldHideFocus) {
|
||||||
let clonedInputEle = cloneInput(focusedInputEle, 'cloned-move');
|
let clonedInputEle = cloneInput(focusedInputEle, 'cloned-move');
|
||||||
|
|
||||||
@ -124,9 +134,6 @@ export class NativeInput {
|
|||||||
return this.element().value;
|
return this.element().value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
element(): HTMLInputElement {
|
element(): HTMLInputElement {
|
||||||
return this._elementRef.nativeElement;
|
return this._elementRef.nativeElement;
|
||||||
}
|
}
|
||||||
@ -141,6 +148,8 @@ function cloneInput(focusedInputEle, addCssClass) {
|
|||||||
clonedInputEle.removeAttribute('aria-labelledby');
|
clonedInputEle.removeAttribute('aria-labelledby');
|
||||||
clonedInputEle.tabIndex = -1;
|
clonedInputEle.tabIndex = -1;
|
||||||
clonedInputEle.style.width = (focusedInputEle.offsetWidth + 10) + 'px';
|
clonedInputEle.style.width = (focusedInputEle.offsetWidth + 10) + 'px';
|
||||||
|
clonedInputEle.style.height = focusedInputEle.offsetHeight + 'px';
|
||||||
|
clonedInputEle.value = focusedInputEle.value;
|
||||||
return clonedInputEle;
|
return clonedInputEle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,6 +173,7 @@ export class NextInput {
|
|||||||
|
|
||||||
@HostListener('focus')
|
@HostListener('focus')
|
||||||
receivedFocus() {
|
receivedFocus() {
|
||||||
|
console.debug('native-input, next-input received focus');
|
||||||
this.focused.emit(true);
|
this.focused.emit(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import {App} from 'ionic-angular';
|
|||||||
@App({
|
@App({
|
||||||
templateUrl: 'main.html',
|
templateUrl: 'main.html',
|
||||||
config: {
|
config: {
|
||||||
scrollAssist: true
|
//scrollAssist: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
class E2EApp {
|
class E2EApp {
|
||||||
@ -12,3 +12,23 @@ class E2EApp {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('click', function(ev) {
|
||||||
|
console.log(`CLICK, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('touchstart', function(ev) {
|
||||||
|
console.log(`TOUCH START, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('touchend', function(ev) {
|
||||||
|
console.log(`TOUCH END, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('focusin', function(ev) {console.log(`CLICK, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
|
||||||
|
console.log(`FOCUS IN, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('focusout', function(ev) {console.log(`CLICK, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
|
||||||
|
console.log(`FOCUS OUT, ${ev.target.localName}.${ev.target.className}, time: ${Date.now()}`);
|
||||||
|
});
|
||||||
|
@ -128,6 +128,11 @@
|
|||||||
<ion-radio value="2"></ion-radio>
|
<ion-radio value="2"></ion-radio>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>Bottom input:</ion-label>
|
||||||
|
<ion-input value="bottom input"></ion-input>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
@ -75,7 +75,6 @@ Platform.register({
|
|||||||
hoverCSS: false,
|
hoverCSS: false,
|
||||||
keyboardHeight: 300,
|
keyboardHeight: 300,
|
||||||
mode: 'md',
|
mode: 'md',
|
||||||
scrollAssist: true,
|
|
||||||
},
|
},
|
||||||
isMatch(p: Platform): boolean {
|
isMatch(p: Platform): boolean {
|
||||||
return p.isPlatformMatch('android', ['android', 'silk'], ['windows phone']);
|
return p.isPlatformMatch('android', ['android', 'silk'], ['windows phone']);
|
||||||
@ -98,6 +97,7 @@ Platform.register({
|
|||||||
autoFocusAssist: 'delay',
|
autoFocusAssist: 'delay',
|
||||||
clickBlock: true,
|
clickBlock: true,
|
||||||
hoverCSS: false,
|
hoverCSS: false,
|
||||||
|
inputCloning: isIOSDevice,
|
||||||
keyboardHeight: 300,
|
keyboardHeight: 300,
|
||||||
mode: 'ios',
|
mode: 'ios',
|
||||||
scrollAssist: isIOSDevice,
|
scrollAssist: isIOSDevice,
|
||||||
|
@ -83,7 +83,7 @@ export class Keyboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkKeyboard() {
|
function checkKeyboard() {
|
||||||
console.debug('keyboard isOpen', self.isOpen(), checks);
|
console.debug('keyboard isOpen', self.isOpen());
|
||||||
if (!self.isOpen() || checks > pollingChecksMax) {
|
if (!self.isOpen() || checks > pollingChecksMax) {
|
||||||
rafFrames(30, () => {
|
rafFrames(30, () => {
|
||||||
self._zone.run(() => {
|
self._zone.run(() => {
|
||||||
|
Reference in New Issue
Block a user