Compare commits

...

9 Commits

Author SHA1 Message Date
Job
54ac2e393f fix(input): slightly longer delay for autofocus (#12037) 2017-06-14 15:09:23 +02:00
Manuel Mtz-Almeida
dc958c3e2c fix(textarea): apply classes properly 2017-06-13 17:26:04 +02:00
Manuel Mtz-Almeida
9f86e10f46 fix(input): better handling of attributes 2017-06-13 14:18:03 +02:00
Job
8041eedf22 fix(input): use all supported attributes on both textareas and inputs (#12028) 2017-06-13 12:15:51 +02:00
Manuel Mtz-Almeida
ef85ba6c1f fix(input): add correct translate3d for rtl
thanks @Khalid-Nowaf

fixes #11745
fixes #11211
2017-06-12 23:17:48 +02:00
Manuel Mtz-Almeida
6dee17b89b Merge branch 'keyboard-fixes' 2017-06-12 22:49:13 +02:00
Manuel Mtz-Almeida
c10f72b1e2 fix(keyboard): big keyboard/input refactor
fixes #9699
fixes #11484
fixes #11389
fixes #11325
fixes #11291
fixes #10828
fixes #11291
fixes #10393
fixes #10257
fixes #9434
fixes #8933
fixes #7178
fixes #7047
fixes #10552
fixes #10393
fixes #10183
fixes #10187
fixes #10852
fixes #11578
2017-06-12 22:31:22 +02:00
Job
47e3c70bf3 fix(refresher): border should only show when pulled (#12015)
fixes #10994
2017-06-12 21:05:57 +02:00
Brandy Carney
a91a68e198 style(util): remove commented out test css 2017-06-12 13:51:26 -04:00
40 changed files with 1056 additions and 1019 deletions

View File

@@ -11,14 +11,14 @@ task('demos.watch', ['demos.prepare'], (done: Function) => {
done(new Error(`Usage: gulp e2e.watch --folder modal`));
}
serveDemo(folderInfo.componentName).then(() => {
serveDemo(folderInfo.componentName, folderInfo.devApp).then(() => {
done();
}).catch((err: Error) => {
done(err);
});
});
function serveDemo(folderName: any) {
function serveDemo(folderName: any, devApp: boolean) {
const ionicAngularDir = join(PROJECT_ROOT, 'src');
const srcTestRoot = join(DEMOS_ROOT, 'src', folderName);
@@ -40,5 +40,5 @@ function serveDemo(folderName: any) {
const appNgModulePath = join(srcTestRoot, 'app', 'app.module.ts');
const distDir = join(distDemoRoot, 'www');
return runAppScriptsServe(folderName, appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, watchConfigPath);
return runAppScriptsServe(folderName, appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, watchConfigPath, devApp);
}

View File

@@ -13,14 +13,14 @@ task('e2e.watch', ['e2e.prepare'], (done: Function) => {
return;
}
serveTest(folderInfo).then(() => {
serveTest(folderInfo, folderInfo.devApp).then(() => {
done();
}).catch((err: Error) => {
done(err);
});
});
function serveTest(folderInfo: any) {
function serveTest(folderInfo: any, devApp: boolean) {
const ionicAngularDir = join(PROJECT_ROOT, 'src');
const srcTestRoot = join(PROJECT_ROOT, 'src', 'components', folderInfo.componentName, 'test', folderInfo.componentTest);
@@ -47,5 +47,5 @@ function serveTest(folderInfo: any) {
const appNgModulePath = join(dirname(appEntryPoint), 'app.module.ts');
const distDir = join(distTestRoot, 'www');
return runAppScriptsServe(join(folderInfo.componentName, folderInfo.componentTest), appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, null);
return runAppScriptsServe(join(folderInfo.componentName, folderInfo.componentTest), appEntryPoint, appNgModulePath, ionicAngularDir, distDir, pathToWriteFile, ionicAngularDir, sassConfigPath, copyConfigPath, null, devApp);
}

View File

@@ -190,7 +190,7 @@ export function runWebpack(pathToWebpackConfig: string, done: Function) {
});
}
export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string, watchConfigPath: string) {
export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string, appNgModulePath: string, srcDir: string, distDir: string, tsConfig: string, ionicAngularDir: string, sassConfigPath: string, copyConfigPath: string, watchConfigPath: string, devApp: boolean) {
console.log('Running ionic-app-scripts serve with', testOrDemoName);
const deepLinksDir = dirname(dirname(appNgModulePath));
let scriptArgs = [
@@ -207,6 +207,9 @@ export function runAppScriptsServe(testOrDemoName: string, appEntryPoint: string
'--copy', copyConfigPath,
'--enableLint', 'false'
];
if (devApp) {
scriptArgs.push('--bonjour');
}
if (watchConfigPath) {
scriptArgs.push('--watch');
@@ -349,9 +352,11 @@ export function getFolderInfo() {
componentName = folderSplit[0];
componentTest = (folderSplit.length > 1 ? folderSplit[1] : 'basic');
}
const devApp = argv.devapp !== undefined;
return {
componentName: componentName,
componentTest: componentTest
componentTest: componentTest,
devApp: devApp
};
}

View File

@@ -84,6 +84,7 @@ export class AlertCmp {
msgId: string;
subHdrId: string;
mode: string;
keyboardResizes: boolean;
gestureBlocker: BlockerDelegate;
constructor(
@@ -99,6 +100,7 @@ export class AlertCmp {
this.gestureBlocker = gestureCtrl.createBlocker(BLOCK_ALL);
this.d = params.data;
this.mode = this.d.mode || config.get('mode');
this.keyboardResizes = config.getBoolean('keyboardResizes', false);
_renderer.setElementClass(_elementRef.nativeElement, `alert-${this.mode}`, true);
if (this.d.cssClass) {
@@ -178,7 +180,7 @@ export class AlertCmp {
}
const hasTextInput = (this.d.inputs.length && this.d.inputs.some(i => !(NON_TEXT_INPUT_REGEX.test(i.type))));
if (hasTextInput && this._plt.is('mobile')) {
if (!this.keyboardResizes && hasTextInput && this._plt.is('mobile')) {
// this alert has a text input and it's on a mobile device so we should align
// the alert up high because we need to leave space for the virtual keboard
// this also helps prevent the layout getting all messed up from

View File

@@ -30,6 +30,7 @@ export class App {
private _titleSrv: Title = new Title(DOCUMENT);
private _rootNav: NavController = null;
private _disableScrollAssist: boolean;
private _didScroll = false;
/**
* @hidden
@@ -87,6 +88,11 @@ export class App {
_plt.registerBackButtonAction(this.goBack.bind(this));
this._disableScrollAssist = _config.getBoolean('disableScrollAssist', false);
const blurring = _config.getBoolean('inputBlurring', false);
if (blurring) {
this._enableInputBlurring();
}
runInDev(() => {
// During developement, navPop can be triggered by calling
const win = <any>_plt.win();
@@ -179,6 +185,7 @@ export class App {
*/
setScrolling() {
this._scrollTime = Date.now() + ACTIVE_SCROLLING_TIME;
this._didScroll = true;
}
/**
@@ -289,6 +296,60 @@ export class App {
return recursivePop(this.getActiveNav());
}
/**
* @hidden
*/
_enableInputBlurring() {
console.debug('App: _enableInputBlurring');
let focused = true;
const self = this;
const platform = this._plt;
platform.registerListener(platform.doc(), 'focusin', onFocusin, { capture: true, zone: false, passive: true });
platform.registerListener(platform.doc(), 'touchend', onTouchend, { capture: false, zone: false, passive: true });
function onFocusin(ev: any) {
focused = true;
}
function onTouchend(ev: any) {
// if app did scroll return early
if (self._didScroll) {
self._didScroll = false;
return;
}
const active = <HTMLElement> self._plt.getActiveElement();
if (!active) {
return;
}
// only blur if the active element is a text-input or a textarea
if (SKIP_BLURRING.indexOf(active.tagName) === -1) {
return;
}
// if the selected target is the active element, do not blur
const tapped = ev.target;
if (tapped === active) {
return;
}
if (SKIP_BLURRING.indexOf(tapped.tagName) >= 0) {
return;
}
// skip if div is a cover
if (tapped.classList.contains('input-cover')) {
return;
}
focused = false;
// TODO: find a better way, why 50ms?
platform.timeout(() => {
if (!focused) {
active.blur();
}
}, 50);
}
}
}
function recursivePop(nav: any): Promise<any> {
@@ -322,5 +383,6 @@ function findTopNav(nav: NavController) {
return nav;
}
const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
const ACTIVE_SCROLLING_TIME = 100;
const CLICK_BLOCK_BUFFER_IN_MILLIS = 64;

View File

@@ -118,13 +118,6 @@ export class Checkbox extends BaseInput<boolean> implements IonicTapInput, OnDes
super(config, elementRef, renderer, 'checkbox', false, form, item, null);
}
/**
* @hidden
*/
initFocus() {
this._elementRef.nativeElement.querySelector('button').focus();
}
/**
* @hidden
*/
@@ -145,8 +138,8 @@ export class Checkbox extends BaseInput<boolean> implements IonicTapInput, OnDes
/**
* @hidden
*/
_inputCheckHasValue(val: boolean) {
this._item && this._item.setElementClass('item-checkbox-checked', val);
_inputUpdated() {
this._item && this._item.setElementClass('item-checkbox-checked', this._value);
}
}

View File

@@ -167,7 +167,8 @@ export class EventEmitterProxy<T> extends EventEmitter<T> {
'</div>' +
'<ng-content select="ion-refresher"></ng-content>',
host: {
'[class.statusbar-padding]': 'statusbarPadding'
'[class.statusbar-padding]': 'statusbarPadding',
'[class.has-refresher]': '_hasRefresher'
},
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
@@ -212,6 +213,8 @@ export class Content extends Ion implements OnDestroy, AfterViewInit, IContent {
/** @internal */
_fullscreen: boolean;
/** @internal */
_hasRefresher: boolean = false;
/** @internal */
_footerEle: HTMLElement;
/** @internal */
_dirty: boolean;
@@ -782,6 +785,11 @@ export class Content extends Ion implements OnDestroy, AfterViewInit, IContent {
this._cBottom += this._tabbarHeight;
}
// Refresher uses a border which should be hidden unless pulled
if (this._hasRefresher) {
this._cTop -= 1;
}
// Fixed content shouldn't include content padding
this._fTop = this._cTop;
this._fBottom = this._cBottom;

View File

@@ -448,6 +448,7 @@ export class DateTime extends BaseInput<DateTimeData> implements AfterContentIni
* @hidden
*/
_inputUpdated() {
super._inputUpdated();
this.updateText();
}
@@ -475,10 +476,6 @@ export class DateTime extends BaseInput<DateTimeData> implements AfterContentIni
@HostListener('click', ['$event'])
_click(ev: UIEvent) {
// do not continue if the click event came from a form submit
if (ev.detail === 0) {
return;
}
ev.preventDefault();
ev.stopPropagation();
this.open();

View File

@@ -93,6 +93,8 @@ input.text-input:-webkit-autofill {
width: 100%;
height: 100%;
touch-action: manipulation;
}
.input[disabled] .input-cover {
@@ -127,27 +129,6 @@ input.text-input:-webkit-autofill {
}
// Scroll Assist Input
// --------------------------------------------------
// This input is used to help the app handle
// Next and Previous input tabbing
[next-input] {
@include padding(0);
position: absolute;
bottom: 20px;
width: 1px;
height: 1px;
border: 0;
background: transparent;
pointer-events: none;
}
// Clear Input Icon
// --------------------------------------------------

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,242 +0,0 @@
import { Directive, ElementRef, EventEmitter, HostListener, Output, Renderer } from '@angular/core';
import { NgControl } from '@angular/forms';
import { Config } from '../../config/config';
import { Platform } from '../../platform/platform';
/**
* @hidden
*/
@Directive({
selector: '.text-input'
})
export class NativeInput {
_relocated: boolean;
_clone: boolean;
_blurring: boolean;
_unrefBlur: Function;
@Output() focusChange: EventEmitter<boolean> = new EventEmitter<boolean>();
@Output() valueChange: EventEmitter<string> = new EventEmitter<string>();
@Output() keydown: EventEmitter<string> = new EventEmitter<string>();
constructor(
public _elementRef: ElementRef,
public _renderer: Renderer,
config: Config,
private _plt: Platform,
public ngControl: NgControl
) {
this._clone = config.getBoolean('inputCloning', false);
this._blurring = config.getBoolean('inputBlurring', false);
}
@HostListener('input', ['$event'])
_change(ev: any) {
this.valueChange.emit(ev.target.value);
}
@HostListener('keydown', ['$event'])
_keyDown(ev: any) {
if (ev) {
ev.target && this.keydown.emit(ev.target.value);
}
}
@HostListener('focus')
_focus() {
var self = this;
self.focusChange.emit(true);
if (self._blurring) {
// automatically blur input if:
// 1) this input has focus
// 2) the newly tapped document element is not an input
console.debug(`native-input, blurring enabled`);
var unregTouchEnd = this._plt.registerListener(this._plt.doc(), 'touchend', (ev: TouchEvent) => {
var tapped = <HTMLElement>ev.target;
if (tapped && self.element()) {
if (tapped.tagName !== 'INPUT' && tapped.tagName !== 'TEXTAREA' && !tapped.classList.contains('input-cover')) {
self.element().blur();
}
}
}, {
capture: true,
zone: false
});
self._unrefBlur = function() {
console.debug(`native-input, blurring disabled`);
unregTouchEnd();
};
}
}
@HostListener('blur')
_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 (this._plt.getActiveElement() !== this.element()) {
this.element().focus();
}
}
beginFocus(shouldFocus: boolean, inputRelativeY: number) {
if (this._relocated !== shouldFocus) {
const 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
cloneInputComponent(this._plt, 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
(<any>focusedInputEle.style)[this._plt.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();
} else {
// should remove the focus
if (this._clone) {
// should remove the cloned node
removeClone(this._plt, focusedInputEle);
}
}
this._relocated = shouldFocus;
}
}
hideFocus(shouldHideFocus: boolean) {
let focusedInputEle = this.element();
console.debug(`native-input, hideFocus, shouldHideFocus: ${shouldHideFocus}, input value: ${focusedInputEle.value}`);
if (shouldHideFocus) {
cloneInputComponent(this._plt, focusedInputEle);
(<any>focusedInputEle.style)[this._plt.Css.transform] = 'scale(0)';
} else {
removeClone(this._plt, focusedInputEle);
}
}
setValue(val: any) {
this._elementRef.nativeElement['value'] = val;
}
getValue(): string {
return this.element().value;
}
setMin(val: any) {
this._elementRef.nativeElement['min'] = val;
}
setMax(val: any) {
this._elementRef.nativeElement['max'] = val;
}
setStep(val: any) {
this._elementRef.nativeElement['step'] = val;
}
setElementClass(cssClass: string, shouldAdd: boolean) {
this._renderer.setElementClass(this._elementRef.nativeElement, cssClass, shouldAdd);
}
element(): HTMLInputElement {
return this._elementRef.nativeElement;
}
ngOnDestroy() {
this._unrefBlur && this._unrefBlur();
}
}
function cloneInputComponent(plt: Platform, srcNativeInputEle: HTMLInputElement) {
// given a native <input> or <textarea> element
// find its parent wrapping component like <ion-input> or <ion-textarea>
// then clone the entire component
const srcComponentEle = <HTMLElement>srcNativeInputEle.closest('ion-input,ion-textarea');
if (srcComponentEle) {
// DOM READ
const srcTop = srcComponentEle.offsetTop;
const srcLeft = srcComponentEle.offsetLeft;
const srcWidth = srcComponentEle.offsetWidth;
const srcHeight = srcComponentEle.offsetHeight;
// DOM WRITE
// not using deep clone so we don't pull in unnecessary nodes
const clonedComponentEle = <HTMLElement>srcComponentEle.cloneNode(false);
clonedComponentEle.classList.add('cloned-input');
clonedComponentEle.setAttribute('aria-hidden', 'true');
clonedComponentEle.style.pointerEvents = 'none';
clonedComponentEle.style.position = 'absolute';
clonedComponentEle.style.top = srcTop + 'px';
clonedComponentEle.style.left = srcLeft + 'px';
clonedComponentEle.style.width = srcWidth + 'px';
clonedComponentEle.style.height = srcHeight + 'px';
const clonedNativeInputEle = <HTMLInputElement>srcNativeInputEle.cloneNode(false);
clonedNativeInputEle.value = srcNativeInputEle.value;
clonedNativeInputEle.tabIndex = -1;
clonedComponentEle.appendChild(clonedNativeInputEle);
srcComponentEle.parentNode.appendChild(clonedComponentEle);
srcComponentEle.style.pointerEvents = 'none';
}
(<any>srcNativeInputEle.style)[plt.Css.transform] = 'scale(0)';
}
function removeClone(plt: Platform, srcNativeInputEle: HTMLElement) {
const srcComponentEle = <HTMLElement>srcNativeInputEle.closest('ion-input,ion-textarea');
if (srcComponentEle && srcComponentEle.parentElement) {
const clonedInputEles = srcComponentEle.parentElement.querySelectorAll('.cloned-input');
for (var i = 0; i < clonedInputEles.length; i++) {
clonedInputEles[i].parentNode.removeChild(clonedInputEles[i]);
}
srcComponentEle.style.pointerEvents = '';
}
(<any>srcNativeInputEle.style)[plt.Css.transform] = '';
srcNativeInputEle.style.opacity = '';
}

View File

@@ -1,18 +0,0 @@
import { Directive, EventEmitter, HostListener, Output } from '@angular/core';
/**
* @hidden
*/
@Directive({
selector: '[next-input]'
})
export class NextInput {
@Output() focused: EventEmitter<boolean> = new EventEmitter<boolean>();
@HostListener('focus')
receivedFocus() {
console.debug('native-input, next-input received focus');
this.focused.emit(true);
}
}

View File

@@ -0,0 +1,10 @@
import { Component } from '@angular/core';
import { RootPage } from '../pages/root-page/root-page';
@Component({
template: '<ion-nav [root]="root"></ion-nav>'
})
export class AppComponent {
root = RootPage;
}

View File

@@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { IonicApp, IonicModule } from '../../../../..';
import { AppComponent } from './app.component';
import { RootPageModule } from '../pages/root-page/root-page.module';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
IonicModule.forRoot(AppComponent),
RootPageModule
],
bootstrap: [IonicApp]
})
export class AppModule {}

View File

@@ -0,0 +1,5 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);

View File

View File

@@ -0,0 +1,39 @@
<ion-header>
<ion-toolbar>
<ion-title>Input attributes</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-item>
<ion-label stacked>Stacked</ion-label>
<ion-input #input1
type="number"
placeholder="Placeholder"
value="1234"
id="mystackinput"
name="holaa"
min="0"
max="10000"
step="2"
autocomplete="on"
autocorrect="on"
autocapitalize="on"
spellcheck="true"
maxlength="4"
disabled
readonly
></ion-input>
</ion-item>
<ion-list>
<ion-item *ngIf="input1Valid" color="secondary">Test passed</ion-item>
<ion-item *ngIf="!input1Valid" color="danger">Test FAILED</ion-item>
</ion-list>
</ion-list>
</ion-content>

View File

@@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { IonicPageModule } from '../../../../../..';
import { RootPage } from './root-page';
@NgModule({
declarations: [
RootPage,
],
imports: [
IonicPageModule.forChild(RootPage)
]
})
export class RootPageModule {}

View File

@@ -0,0 +1,59 @@
import { Component, ViewChild } from '@angular/core';
import { TextInput } from '../../../../../../';
@Component({
templateUrl: 'root-page.html'
})
export class RootPage {
input1Valid: boolean;
input2Valid: boolean;
@ViewChild('input1') input1: TextInput;
ionViewDidEnter() {
this.input1Valid = this.checkInput1();
}
checkInput1(): boolean {
const nativeEle = <HTMLElement>this.input1._native.nativeElement;
return testAttributes(nativeEle, {
id: null,
type: 'number',
placeholder: 'Placeholder',
name: 'holaa',
min: '0',
max: '10000',
step: '2',
autocomplete: 'on',
autocorrect: 'on',
autocapitalize: 'on',
spellcheck: 'true',
maxLength: '4',
'aria-labelledby': 'lbl-0',
readOnly: true,
disabled: true
});
}
}
function testAttributes(ele: HTMLElement, attributes: any): boolean {
for (let attr in attributes) {
const expected = attributes[attr];
const value = (<any>ele)[attr];
if (expected === null) {
if (ele.hasAttribute(attr) || value !== '') {
console.error(`Element should NOT have "${attr}"`);
return false;
}
} else {
if (expected !== value && expected !== ele.getAttribute(attr)) {
console.error(`Value "${attr}" does not match: ${expected} != ${value}`);
return false;
}
}
}
return true;
}

View File

@@ -102,4 +102,16 @@
</ion-list>
</ion-card>
</form>
</ion-content>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-input style="background: white;
margin: 3px;
padding: 0px 8px;
border: 1px solid gray;
border-radius: 5px;
width: auto;
transform: translateZ(0);" placeholder="chat here"></ion-input>
</ion-toolbar>
</ion-footer>

View File

@@ -1,9 +1,38 @@
import { getScrollData } from '../input';
import { getScrollData, TextInput } from '../input';
import { ContentDimensions } from '../../content/content';
import { mockConfig, mockApp, mockPlatform, mockDomController, mockElementRef, mockElementRefEle, mockRenderer, mockItem, mockForm } from '../../../util/mock-providers';
import { commonInputTest, TEXT_CORPUS } from '../../../util/input-tester';
function newInput(): TextInput {
const platform = mockPlatform();
const config = mockConfig();
const app = mockApp(config, platform);
const elementRef = mockElementRef();
const renderer = mockRenderer();
const item: any = mockItem();
const form = mockForm();
const dom = mockDomController(platform);
const input = new TextInput(config, platform, form, app, elementRef, renderer, null, item, null, dom);
input._native = mockElementRefEle(document.createElement('input'));
return input;
}
describe('text input', () => {
it('should pass common test', () => {
const textInput = newInput();
const ele = textInput._native.nativeElement;
textInput._item._elementRef = mockElementRefEle(document.createElement('div'));
commonInputTest(textInput, {
defaultValue: '',
corpus: TEXT_CORPUS,
onValueChange: (value) => ele.value === value,
});
});
describe('getScrollData', () => {
it('should scroll, top and bottom below safe area, no room to scroll', () => {

View File

@@ -323,6 +323,7 @@ export class Item extends Ion {
this._setName(elementRef);
this._hasReorder = !!reorder;
this.id = form.nextId().toString();
this.labelId = 'lbl-' + this.id;
// auto add "tappable" attribute to ion-item components that have a click listener
if (!(<any>renderer).orgListen) {
@@ -391,7 +392,7 @@ export class Item extends Ion {
set contentLabel(label: Label) {
if (label) {
this._label = label;
this.labelId = label.id = ('lbl-' + this.id);
label.id = this.labelId;
if (label.type) {
this.setElementClass('item-label-' + label.type, true);
}

View File

@@ -202,7 +202,7 @@ export class Refresher {
constructor(private _plt: Platform, @Host() private _content: Content, private _zone: NgZone, gestureCtrl: GestureController) {
this._events = new UIEventManager(_plt);
_content.setElementClass('has-refresher', true);
_content._hasRefresher = true;
this._gesture = gestureCtrl.createGesture({
name: GESTURE_REFRESHER,
priority: GESTURE_PRIORITY_REFRESHER

View File

@@ -224,6 +224,9 @@ describe('Refresher', () => {
});
it('should set hasRefresher on content', () => {
expect(content._hasRefresher).toBeTruthy();
});
let contentElementRef: any;
let refresher: Refresher;

View File

@@ -188,10 +188,11 @@ export class Searchbar extends BaseInput<string> {
*/
_inputUpdated() {
const ele = this._searchbarInput.nativeElement;
const value = this._value;
// It is important not to re-assign the value if it is the same, because,
// otherwise, the caret is moved to the end of the input
if (ele && ele.value !== this.value) {
ele.value = this.value;
if (ele.value !== value) {
ele.value = value;
}
this.positionElements();
}

View File

@@ -206,10 +206,6 @@ export class Select extends BaseInput<any> implements OnDestroy {
@HostListener('click', ['$event'])
_click(ev: UIEvent) {
if (ev.detail === 0) {
// do not continue if the click event came from a form submit
return;
}
ev.preventDefault();
ev.stopPropagation();
this.open(ev);

View File

@@ -17,6 +17,10 @@
opacity: 0;
}
.tabbar-hidden .tabbar {
display: none;
}
.tabbar.show-tabbar {
opacity: 1;
}

View File

@@ -1,10 +1,14 @@
import { AfterViewInit, Component, ElementRef, EventEmitter, forwardRef, Input, Output, Optional, Renderer, ViewChild, ViewContainerRef, ViewEncapsulation } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/takeUntil';
import { App } from '../app/app';
import { Config } from '../../config/config';
import { DeepLinker } from '../../navigation/deep-linker';
import { Ion } from '../ion';
import { isBlank, assert } from '../../util/util';
import { Keyboard } from '../../platform/keyboard';
import { Tabs as ITabs } from '../../navigation/nav-interfaces';
import { NavController } from '../../navigation/nav-controller';
import { NavControllerBase } from '../../navigation/nav-controller-base';
@@ -176,7 +180,7 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs {
/** @internal */
_selectHistory: string[] = [];
/** @internal */
_resizeObs: any;
_onDestroy = new Subject<void>();
/**
* @input {number} The default selected tab index when first loaded. If a selected index isn't provided then it will use `0`, the first tab.
@@ -231,7 +235,8 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs {
elementRef: ElementRef,
private _plt: Platform,
renderer: Renderer,
private _linker: DeepLinker
private _linker: DeepLinker,
keyboard?: Keyboard
) {
super(config, elementRef, renderer, 'tabs');
@@ -261,10 +266,33 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs {
viewCtrl._setContent(this);
viewCtrl._setContentRef(elementRef);
}
const keyboardResizes = config.getBoolean('keyboardResizes', false);
if (keyboard && keyboardResizes) {
keyboard.willHide
.takeUntil(this._onDestroy)
.subscribe(() => {
this._plt.timeout(() => this.setTabbarHidden(false), 50);
});
keyboard.willShow
.takeUntil(this._onDestroy)
.subscribe(() => this.setTabbarHidden(true));
}
}
/**
* @internal
*/
setTabbarHidden(tabbarHidden: boolean) {
this.setElementClass('tabbar-hidden', tabbarHidden);
this.resize();
}
/**
* @internal
*/
ngOnDestroy() {
this._resizeObs && this._resizeObs.unsubscribe();
this._onDestroy.next();
this.parent.unregisterChildNav(this);
}
@@ -277,9 +305,9 @@ export class Tabs extends Ion implements AfterViewInit, RootNode, ITabs {
this._setConfig('tabsHighlight', this.tabsHighlight);
if (this.tabsHighlight) {
this._resizeObs = this._plt.resize.subscribe(() => {
this._highlight.select(this.getSelected());
});
this._plt.resize
.takeUntil(this._onDestroy)
.subscribe(() => this._highlight.select(this.getSelected()));
}
this.initTabs();

View File

@@ -16,6 +16,7 @@
<p><button ion-button (click)="color = !color" [color]="color ? 'primary':'secondary'">Change color</button></p>
<p>"Change color" should continue working after clicking Logout in the tabbar and cancelling (No in the alert)</p>
<p>UserId: {{userId}}</p>
<p><ion-input></ion-input></p>
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>
<div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div><div f></div>

View File

@@ -118,7 +118,7 @@ export class Toggle extends BaseInput<boolean> implements IonicTapInput, AfterCo
/**
* @hidden
*/
_inputCheckHasValue() {}
_inputUpdated() {}
/**
* @hidden
@@ -210,13 +210,6 @@ export class Toggle extends BaseInput<boolean> implements IonicTapInput, AfterCo
}
}
/**
* @hidden
*/
initFocus() {
this._elementRef.nativeElement.querySelector('button').focus();
}
/**
* @hidden
*/

View File

@@ -62,8 +62,6 @@ export { NavPop } from './components/nav/nav-pop';
export { NavPopAnchor } from './components/nav/nav-pop-anchor';
export { NavPush } from './components/nav/nav-push';
export { NavPushAnchor } from './components/nav/nav-push-anchor';
export { NativeInput } from './components/input/native-input';
export { NextInput } from './components/input/next-input';
export { Note } from './components/note/note';
export { Option } from './components/option/option';
export { Picker } from './components/picker/picker';

View File

@@ -69,8 +69,6 @@ import { Icon } from './components/icon/icon';
import { Img } from './components/img/img';
import { InfiniteScroll } from './components/infinite-scroll/infinite-scroll';
import { InfiniteScrollContent } from './components/infinite-scroll/infinite-scroll-content';
import { NativeInput } from './components/input/native-input';
import { NextInput } from './components/input/next-input';
import { TextInput } from './components/input/input';
import { Item } from './components/item/item';
import { ItemContent } from './components/item/item-content';
@@ -221,8 +219,6 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
ListHeader,
Reorder,
LoadingCmp,
NativeInput,
NextInput,
Menu,
MenuClose,
MenuToggle,
@@ -324,8 +320,6 @@ import { VirtualScroll } from './components/virtual-scroll/virtual-scroll';
ListHeader,
Reorder,
LoadingCmp,
NativeInput,
NextInput,
Menu,
MenuClose,
MenuToggle,

View File

@@ -1,4 +1,4 @@
import { Injectable, NgZone } from '@angular/core';
import { Injectable, NgZone, EventEmitter } from '@angular/core';
import { Config } from '../config/config';
import { DomController } from './dom-controller';
@@ -24,29 +24,85 @@ import { Platform } from './platform';
*/
@Injectable()
export class Keyboard {
private _tmr: any;
constructor(config: Config, private _plt: Platform, private _zone: NgZone, private _dom: DomController) {
_tmr: number;
willShow = new EventEmitter<void>();
willHide = new EventEmitter<void>();
didShow = new EventEmitter<void>();
didHide = new EventEmitter<void>();
eventsAvailable = false;
constructor(
config: Config,
private _plt: Platform,
private _zone: NgZone,
private _dom: DomController
) {
this.focusOutline(config.get('focusOutline'));
const win = _plt.win();
const win = <any>_plt.win();
if (config.getBoolean('keyboardResizes', false)) {
this.listenV2(win);
} else {
this.listenV1(win);
}
}
_plt.registerListener(win, 'native.keyboardhide', () => {
_plt.cancelTimeout(this._tmr);
this._tmr = _plt.timeout(() => {
private listenV2(win: any) {
const platform = this._plt;
platform.registerListener(win, 'keyboardWillShow', () => {
this._zone.run(() => {
this.willShow.emit();
});
}, { zone: false, passive: true });
platform.registerListener(win, 'keyboardWillHide', () => {
this._zone.run(() => {
this.willHide.emit();
});
}, { zone: false, passive: true });
platform.registerListener(win, 'keyboardDidShow', () => {
this._zone.run(() => {
this.didShow.emit();
});
}, { zone: false, passive: true });
platform.registerListener(win, 'keyboardDidHide', () => {
this._zone.run(() => {
this.didHide.emit();
});
}, { zone: false, passive: true });
this.eventsAvailable = true;
}
private listenV1(win: any) {
const platform = this._plt;
platform.registerListener(win, 'native.keyboardhide', () => {
this.blurActiveInput(true);
}, { zone: false, passive: true });
platform.registerListener(win, 'native.keyboardshow', () => {
this.blurActiveInput(false);
}, { zone: false, passive: true });
}
private blurActiveInput(shouldBlur: boolean) {
const platform = this._plt;
platform.cancelTimeout(this._tmr);
if (shouldBlur) {
this._tmr = platform.timeout(() => {
// this custom cordova plugin event fires when the keyboard will hide
// useful when the virtual keyboard is closed natively
// https://github.com/ionic-team/ionic-plugin-keyboard
if (this.isOpen()) {
this._plt.focusOutActiveElement();
platform.focusOutActiveElement();
}
}, 80);
}, { zone: false, passive: true });
_plt.registerListener(win, 'native.keyboardshow', () => {
_plt.cancelTimeout(this._tmr);
}, { zone: false, passive: true });
}
}
/**

View File

@@ -105,6 +105,7 @@ export const PLATFORM_CONFIGS: { [key: string]: PlatformConfig } = {
],
settings: {
autoFocusAssist: 'delay',
hideCaretOnScroll: true,
hoverCSS: false,
inputBlurring: isIos,
inputCloning: isIos,
@@ -116,6 +117,8 @@ export const PLATFORM_CONFIGS: { [key: string]: PlatformConfig } = {
tapPolyfill: isIosUIWebView,
virtualScrollEventAssist: isIosUIWebView,
disableScrollAssist: isIos,
keyboardResizes: keyboardResizes,
resizeAssist: keyboardResizes,
},
isMatch(plt: Platform) {
return plt.isPlatformMatch('ios', ['iphone', 'ipad', 'ipod'], ['windows phone']);
@@ -240,6 +243,13 @@ export const PLATFORM_CONFIGS: { [key: string]: PlatformConfig } = {
}
};
function keyboardResizes(plt: Platform): boolean {
const win = <any>plt.win();
if (win.Ionic && win.Ionic.keyboardResizes === true) {
return true;
}
return false;
}
export const PlatformConfigToken = new OpaqueToken('PLTCONFIG');

View File

@@ -62,8 +62,6 @@ ion-input :focus {
opacity: 0;
// background: red;
// opacity: .3;
contain: strict;
}

View File

@@ -2,7 +2,8 @@ import { AfterContentInit, ElementRef, EventEmitter, Input, NgZone, Output, Rend
import { ControlValueAccessor } from '@angular/forms';
import { NgControl } from '@angular/forms';
import { isPresent, isUndefined, isArray, isTrueProperty, deepCopy, assert } from './util';
import { isPresent, isString, isUndefined, isArray, isTrueProperty, deepCopy, assert } from './util';
import { IonicFormInput } from './form';
import { Ion } from '../components/ion';
import { Config } from '../config/config';
import { Item } from '../components/item/item';
@@ -10,7 +11,7 @@ import { Form } from './form';
import { TimeoutDebouncer } from './debouncer';
export interface CommonInput<T> extends ControlValueAccessor, AfterContentInit {
export interface CommonInput<T> extends ControlValueAccessor, AfterContentInit, IonicFormInput {
id: string;
disabled: boolean;
@@ -76,21 +77,22 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
private _defaultValue: T,
public _form: Form,
public _item: Item,
ngControl: NgControl
public _ngControl: NgControl
) {
super(config, elementRef, renderer, name);
_form && _form.register(this);
this._value = deepCopy(this._defaultValue);
if (_item) {
assert('lbl-' + _item.id === _item.labelId, 'labelId was not calculated correctly');
this.id = name + '-' + _item.registerInput(name);
this._labelId = 'lbl-' + _item.id;
this._labelId = _item.labelId;
this._item.setElementClass('item-' + name, true);
}
// If the user passed a ngControl we need to set the valueAccessor
if (ngControl) {
ngControl.valueAccessor = this;
if (_ngControl) {
_ngControl.valueAccessor = this;
}
}
@@ -143,12 +145,9 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
if (isUndefined(val)) {
return false;
}
let normalized;
if (val === null) {
normalized = deepCopy(this._defaultValue);
} else {
normalized = this._inputNormalize(val);
}
const normalized = (val === null)
? deepCopy(this._defaultValue)
: this._inputNormalize(val);
const notUpdate = isUndefined(normalized) || !this._inputShouldChange(normalized);
if (notUpdate) {
@@ -157,7 +156,6 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
console.debug('BaseInput: value changed:', normalized, this);
this._value = normalized;
this._inputCheckHasValue(normalized);
if (this._init) {
this._inputUpdated();
}
@@ -212,12 +210,11 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
if (this._isFocus) {
return;
}
assert(this._init, 'component was not initialized');
assert(NgZone.isInAngularZone(), '_fireFocus: should be zoned');
console.debug('BaseInput: focused:', this);
this._isFocus = true;
this._form && this._form.setAsFocused(this);
this._setFocus(true);
this.ionFocus.emit(this);
this._inputUpdated();
}
/**
@@ -227,11 +224,27 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
if (!this._isFocus) {
return;
}
assert(this._init, 'component was not initialized');
assert(NgZone.isInAngularZone(), '_fireBlur: should be zoned');
console.debug('BaseInput: blurred:', this);
this._isFocus = false;
this._form && this._form.unsetAsFocused(this);
this._setFocus(false);
this.ionBlur.emit(this);
}
/**
* @hidden
*/
private _setFocus(isFocused: boolean) {
assert(this._init, 'component was not initialized');
assert(NgZone.isInAngularZone(), '_fireFocus: should be zoned');
assert(isFocused !== this._isFocus, 'bad internal state');
this._isFocus = isFocused;
const item = this._item;
if (item) {
item.setElementClass('input-has-focus', isFocused);
item.setElementClass('item-input-has-focus', isFocused);
}
this._inputUpdated();
}
@@ -255,16 +268,30 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
*/
hasValue(): boolean {
const val = this._value;
return isArray(val)
? val.length > 0
: isPresent(val);
if (!isPresent(val)) {
return false;
}
if (isArray(val) || isString(val)) {
return val.length > 0;
}
return true;
}
/**
* @hidden
*/
focusNext() {
this._form && this._form.tabFocus(this);
}
/**
* @hidden
*/
ngOnDestroy() {
this._form && this._form.deregister(this);
assert(this._init, 'input was destroed without being initialized');
const form = this._form;
form && form.deregister(this);
this._init = false;
}
@@ -278,20 +305,11 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
/**
* @hidden
*/
_inputCheckHasValue(val: T) {
if (!this._item) {
return;
}
// TODO remove all uses of input-has-value in v4
this._item.setElementClass('input-has-value', this.hasValue());
this._item.setElementClass('item-input-has-value', this.hasValue());
initFocus() {
const ele = this._elementRef.nativeElement.querySelector('button');
ele && ele.focus();
}
/**
* @hidden
*/
initFocus() { }
/**
* @hidden
*/
@@ -326,5 +344,25 @@ export class BaseInput<T> extends Ion implements CommonInput<T> {
*/
_inputUpdated() {
assert(this._init, 'component should be initialized');
const item = this._item;
if (item) {
setControlCss(item, this._ngControl);
// TODO remove all uses of input-has-value in v4
let hasValue = this.hasValue();
item.setElementClass('input-has-value', hasValue);
item.setElementClass('item-input-has-value', hasValue);
}
}
}
function setControlCss(element: Ion, control: NgControl) {
if (!control) {
return;
}
element.setElementClass('ng-untouched', control.untouched);
element.setElementClass('ng-touched', control.touched);
element.setElementClass('ng-pristine', control.pristine);
element.setElementClass('ng-dirty', control.dirty);
element.setElementClass('ng-valid', control.valid);
element.setElementClass('ng-invalid', !control.valid);
}

View File

@@ -94,15 +94,15 @@ export function isTextInput(ele: any) {
export const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i;
const skipInputAttrsReg = /^(value|checked|disabled|type|class|style|id|autofocus|autocomplete|autocorrect)$/i;
const SKIP_INPUT_ATTR = ['value', 'checked', 'disabled', 'readonly', 'placeholder', 'type', 'class', 'style', 'id', 'autofocus', 'autocomplete', 'autocorrect'];
export function copyInputAttributes(srcElement: HTMLElement, destElement: HTMLElement) {
// copy attributes from one element to another
// however, skip over a few of them as they're already
// handled in the angular world
var attrs = srcElement.attributes;
const attrs = srcElement.attributes;
for (var i = 0; i < attrs.length; i++) {
var attr = attrs[i];
if (!skipInputAttrsReg.test(attr.name)) {
if (SKIP_INPUT_ATTR.indexOf(attr.name) === -1) {
destElement.setAttribute(attr.name, attr.value);
}
}

View File

@@ -7,41 +7,47 @@ import { removeArrayItem } from './util';
*/
@Injectable()
export class Form {
private _focused: any = null;
private _ids: number = -1;
private _inputs: any[] = [];
register(input: any) {
private _focused: IonicFormInput = null;
private _ids: number = -1;
private _inputs: IonicFormInput[] = [];
register(input: IonicFormInput) {
this._inputs.push(input);
}
deregister(input: any) {
deregister(input: IonicFormInput) {
removeArrayItem(this._inputs, input);
this.unsetAsFocused(input);
}
setAsFocused(input: IonicFormInput) {
this._focused = input;
}
unsetAsFocused(input: IonicFormInput) {
if (input === this._focused) {
this._focused = null;
}
}
setAsFocused(input: any) {
this._focused = input;
}
/**
* Focuses the next input element, if it exists.
*/
tabFocus(currentInput: any) {
let index = this._inputs.indexOf(currentInput);
if (index > -1 && (index + 1) < this._inputs.length) {
let nextInput = this._inputs[index + 1];
tabFocus(currentInput: IonicFormInput) {
const inputs = this._inputs;
let index = inputs.indexOf(currentInput) + 1;
if (index > 0 && index < inputs.length) {
var nextInput = inputs[index];
if (nextInput !== this._focused) {
console.debug('tabFocus, next');
return nextInput.initFocus();
}
}
index = this._inputs.indexOf(this._focused);
index = inputs.indexOf(this._focused);
if (index > 0) {
let previousInput = this._inputs[index - 1];
var previousInput = inputs[index - 1];
if (previousInput) {
console.debug('tabFocus, previous');
previousInput.initFocus();

View File

@@ -45,6 +45,10 @@ export const ANY_CORPUS: any[] = [
export interface TestConfig {
defaultValue: any;
corpus: any;
testItem?: boolean;
testForm?: boolean;
onValueChange?: (value: any) => boolean;
onFocusChange?: (isFocused: boolean) => boolean;
}
@@ -54,8 +58,34 @@ export function commonInputTest<T>(input: BaseInput<T>, config: TestConfig) {
// TODO test disable
const zone = new NgZone(true);
zone.run(() => {
if (config.testItem === true && !input._item) {
assert(false, 'input is invalid');
}
if (config.testForm === true && !input._form) {
assert(false, 'form is invalid');
}
// Run tests before initialization
testInput(input, config, false);
input.ngAfterContentInit();
(<any>input).ngAfterViewInit && (<any>input).ngAfterViewInit();
// Run tests after initialization
testInput(input, config, true);
// Run tests without item
if (config.testItem === true && !input._item) {
input._item = undefined;
testInput(input, config, true);
}
// Run tests without item
if (config.testForm === true && !input._form) {
input._form = undefined;
testInput(input, config, true);
}
testInput(input, config, true);
input.ngOnDestroy();
assert(!input._init, 'input was not destroyed correctly');
@@ -80,10 +110,16 @@ function testState<T>(input: BaseInput<T>, config: TestConfig, isInit: boolean)
const subBlur = input.ionBlur.subscribe((ev: any) => {
assertEqual(ev, input, 'ionBlur argument is wrong');
blurCount++;
if (config.onFocusChange && config.onFocusChange(false) !== true) {
assert(false, 'onFocusChange test failed');
}
});
const subFocus = input.ionFocus.subscribe((ev: any) => {
assertEqual(ev, input, 'ionFocus argument is wrong');
focusCount++;
if (config.onFocusChange && config.onFocusChange(true) !== true) {
assert(false, 'onFocusChange test failed');
}
});
input._fireFocus();
assertEqual(input._isFocus, true, 'should be focus');
@@ -144,6 +180,9 @@ function testWriteValue<T>(input: BaseInput<T>, config: TestConfig, isInit: bool
assertEqual(input.value, test[1], 'loop: input/output does not match');
if (isInit) {
assertEqual(ionChangeCalled, 1, 'loop: ionChange error');
if (config.onValueChange && config.onValueChange(test[1]) !== true) {
assert(false, 'onValueChange() test failed');
}
} else {
assertEqual(ionChangeCalled, 0, 'loop: ionChange error');
}
@@ -215,6 +254,9 @@ function testNgModelChange<T>(input: BaseInput<T>, config: TestConfig, isInit: b
assertEqual(input.value, test[1], 'input/output does not match');
if (isInit) {
assertEqual(ionChangeCalled, 1, 'ionChange error');
if (config.onValueChange && config.onValueChange(test[1]) !== true) {
assert(false, 'onValueChange() test failed');
}
} else {
assertEqual(ionChangeCalled, 0, 'ionChange error');
}

View File

@@ -68,6 +68,20 @@ export class ScrollView {
this._eventsEnabled = true;
}
setScrolling(isScrolling: boolean, ev: ScrollEvent) {
if (this.isScrolling) {
if (isScrolling) {
this.onScroll && this.onScroll(ev);
} else {
this.isScrolling = false;
this.onScrollEnd && this.onScrollEnd(ev);
}
} else if (isScrolling) {
this.isScrolling = true;
this.onScrollStart && this.onScrollStart(ev);
}
}
private enableNativeScrolling() {
assert(this.onScrollStart, 'onScrollStart is not defined');
assert(this.onScroll, 'onScroll is not defined');
@@ -94,6 +108,7 @@ export class ScrollView {
}
ev.timeStamp = scrollEvent.timeStamp;
// Event.timeStamp is 0 in firefox
if (!ev.timeStamp) {
ev.timeStamp = Date.now();
@@ -108,9 +123,6 @@ export class ScrollView {
ev.scrollLeft = self.getLeft();
if (!self.isScrolling) {
// currently not scrolling, so this is a scroll start
self.isScrolling = true;
// remember the start positions
ev.startY = ev.scrollTop;
ev.startX = ev.scrollLeft;
@@ -119,9 +131,6 @@ export class ScrollView {
ev.velocityY = ev.velocityX = 0;
ev.deltaY = ev.deltaX = 0;
positions.length = 0;
// emit only on the first scroll event
self.onScrollStart(ev);
}
// actively scrolling
@@ -157,20 +166,17 @@ export class ScrollView {
}
function scrollEnd() {
// haven't scrolled in a while, so it's a scrollend
self.isScrolling = false;
// reset velocity, do not reset the directions or deltas
ev.velocityY = ev.velocityX = 0;
// emit that the scroll has ended
self.onScrollEnd(ev);
self.setScrolling(false, ev);
self._endTmr = null;
}
// emit on each scroll event
self.onScroll(ev);
self.setScrolling(true, ev);
// debounce for a moment after the last scroll event
self._dom.cancel(self._endTmr);
@@ -467,7 +473,7 @@ export class ScrollView {
attempts++;
if (!self._el || stopScroll || attempts > maxAttempts) {
self.isScrolling = false;
self.setScrolling(false, null);
(<any>el.style)[transform] = '';
done();
return;
@@ -494,13 +500,14 @@ export class ScrollView {
} else {
stopScroll = true;
self.isScrolling = false;
self.setScrolling(false, null);
(<any>el.style)[transform] = '';
done();
}
}
// start scroll loop
self.setScrolling(true, null);
self.isScrolling = true;
// chill out for a frame first
@@ -525,7 +532,7 @@ export class ScrollView {
}
stop() {
this.isScrolling = false;
this.setScrolling(false, null);
}
/**