perf(scroll): watchdog + simplification

This commit is contained in:
Manu Mtz.-Almeida
2018-03-02 16:04:27 +01:00
parent c1f1c28aef
commit 33a6274613
15 changed files with 183 additions and 265 deletions

View File

@ -42,20 +42,16 @@ import {
GestureCallback, GestureCallback,
GestureDetail, GestureDetail,
} from './components/gesture/gesture'; } from './components/gesture/gesture';
import {
App,
FrameworkDelegate as FrameworkDelegate2,
} from '.';
import { import {
PickerButton, PickerButton,
PickerColumn as PickerColumn2, PickerColumn as PickerColumn2,
} from './components/picker/picker'; } from './components/picker/picker';
import {
ScrollCallback,
} from './components/scroll/scroll';
import { import {
SelectPopoverOption, SelectPopoverOption,
} from './components/select-popover/select-popover'; } from './components/select-popover/select-popover';
import {
FrameworkDelegate as FrameworkDelegate2,
} from '.';
import { import {
DomRenderFn, DomRenderFn,
HeaderFn, HeaderFn,
@ -770,6 +766,7 @@ declare global {
export interface IonContentAttributes extends HTMLAttributes { export interface IonContentAttributes extends HTMLAttributes {
forceOverscroll?: boolean; forceOverscroll?: boolean;
fullscreen?: boolean; fullscreen?: boolean;
scrollEvents?: boolean;
} }
} }
} }
@ -1240,7 +1237,7 @@ declare global {
} }
namespace JSXElements { namespace JSXElements {
export interface IonInputShimsAttributes extends HTMLAttributes { export interface IonInputShimsAttributes extends HTMLAttributes {
app?: App;
} }
} }
} }
@ -2706,9 +2703,7 @@ declare global {
export interface IonScrollAttributes extends HTMLAttributes { export interface IonScrollAttributes extends HTMLAttributes {
forceOverscroll?: boolean; forceOverscroll?: boolean;
mode?: string; mode?: string;
onionScroll?: ScrollCallback; scrollEvents?: boolean;
onionScrollEnd?: ScrollCallback;
onionScrollStart?: ScrollCallback;
} }
} }
} }

View File

@ -4,7 +4,6 @@ import { Config, NavEvent, OverlayController, PublicNav, PublicViewController }
import { getOrAppendElement } from '../../utils/helpers'; import { getOrAppendElement } from '../../utils/helpers';
const rootNavs = new Map<number, HTMLIonNavElement>(); const rootNavs = new Map<number, HTMLIonNavElement>();
const ACTIVE_SCROLLING_TIME = 100;
let backButtonActions: BackButtonAction[] = []; let backButtonActions: BackButtonAction[] = [];
@Component({ @Component({
@ -21,11 +20,9 @@ export class App {
private isDevice = false; private isDevice = false;
private deviceHacks = false; private deviceHacks = false;
private scrollTime = 0;
externalNavPromise: void | Promise<any> = null; externalNavPromise: void | Promise<any> = null;
externalNavOccuring = false; externalNavOccuring = false;
didScroll = false;
@Element() element: HTMLElement; @Element() element: HTMLElement;
@Event() exitApp: EventEmitter<ExitAppEventDetail>; @Event() exitApp: EventEmitter<ExitAppEventDetail>;
@ -102,29 +99,6 @@ export class App {
return true; return true;
} }
/**
* Boolean if the app is actively scrolling or not.
* @return {boolean} returns true or false
*/
@Method()
isScrolling(): boolean {
const scrollTime = this.scrollTime;
if (scrollTime === 0) {
return false;
}
if (scrollTime < Date.now()) {
this.scrollTime = 0;
return false;
}
return true;
}
@Method()
setScrolling() {
this.scrollTime = Date.now() + ACTIVE_SCROLLING_TIME;
this.didScroll = true;
}
@Method() @Method()
getTopNavs(rootNavId = -1): PublicNav[] { getTopNavs(rootNavId = -1): PublicNav[] {
return getTopNavsImpl(rootNavId); return getTopNavsImpl(rootNavId);
@ -142,7 +116,6 @@ export class App {
return null; return null;
} }
/** /**
* The back button event is triggered when the user presses the native * The back button event is triggered when the user presses the native
* platform's back button, also referred to as the "hardware" back button. * platform's back button, also referred to as the "hardware" back button.
@ -244,7 +217,7 @@ export class App {
render() { render() {
return [ return [
<ion-platform />, <ion-platform />,
this.deviceHacks && <ion-input-shims app={this} />, this.deviceHacks && <ion-input-shims />,
this.isDevice && <ion-tap-click />, this.isDevice && <ion-tap-click />,
this.isDevice && <ion-status-tap />, this.isDevice && <ion-status-tap />,
<slot></slot> <slot></slot>

View File

@ -42,11 +42,6 @@ Returns an array of top level Navs
Returns whether the application is enabled or not Returns whether the application is enabled or not
#### isScrolling()
Boolean if the app is actively scrolling or not.
#### registerBackButtonAction() #### registerBackButtonAction()
The back button event is triggered when the user presses the native The back button event is triggered when the user presses the native
@ -69,9 +64,6 @@ This API is not meant for public usage and could
change at any time change at any time
#### setScrolling()
#### updateExternalNavOccuring() #### updateExternalNavOccuring()
Updates whether an external navigation event is occuring Updates whether an external navigation event is occuring

View File

@ -35,6 +35,9 @@ export class Content {
*/ */
@Prop() forceOverscroll: boolean; @Prop() forceOverscroll: boolean;
@Prop() scrollEvents = false;
@Listen('body:ionNavChanged') @Listen('body:ionNavChanged')
onNavChanged() { onNavChanged() {
this.resize(); this.resize();
@ -128,7 +131,10 @@ export class Content {
this.resize(); this.resize();
return [ return [
<ion-scroll mode={this.mode} forceOverscroll={this.forceOverscroll}> <ion-scroll
mode={this.mode}
scrollEvents={this.scrollEvents}
forceOverscroll={this.forceOverscroll}>
<slot></slot> <slot></slot>
</ion-scroll>, </ion-scroll>,
<slot name='fixed'></slot> <slot name='fixed'></slot>

View File

@ -34,6 +34,11 @@ and footers. This effect can easily be seen by setting the toolbar
to transparent. to transparent.
#### scrollEvents
boolean
## Attributes ## Attributes
#### force-overscroll #### force-overscroll
@ -54,6 +59,11 @@ and footers. This effect can easily be seen by setting the toolbar
to transparent. to transparent.
#### scroll-events
boolean
## Methods ## Methods
#### scrollByPoint() #### scrollByPoint()

View File

@ -69,6 +69,13 @@
</ion-footer> </ion-footer>
<script> <script>
const content = document.getElementById('content');
content.scrollEvents = true;
content.addEventListener('ionScrollStart', () => console.log('scroll start'));
content.addEventListener('ionScroll', (ev) => console.log('scroll', ev.detail));
content.addEventListener('ionScrollEnd', () => console.log('scroll end'));
function toggleFullscreen() { function toggleFullscreen() {
const content = document.getElementById('content'); const content = document.getElementById('content');
content.fullscreen = !content.fullscreen; content.fullscreen = !content.fullscreen;

View File

@ -1,5 +1,5 @@
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop, State, Watch } from '@stencil/core';
import { DomController, ScrollDetail } from '../../index'; import { DomController } from '../../index';
const enum Position { const enum Position {
Top = 'top', Top = 'top',
@ -106,10 +106,9 @@ export class InfiniteScroll {
this.scrollEl = null; this.scrollEl = null;
} }
@Listen('ionScroll', {enabled: false}) @Listen('scroll', {enabled: false})
protected onScroll(ev: CustomEvent) { protected onScroll() {
const scrollEl = this.scrollEl; const scrollEl = this.scrollEl;
const detail = ev.detail as ScrollDetail;
if (!scrollEl || !this.canStart()) { if (!scrollEl || !this.canStart()) {
return 1; return 1;
} }
@ -119,7 +118,7 @@ export class InfiniteScroll {
// if there is no height of this element then do nothing // if there is no height of this element then do nothing
return 2; return 2;
} }
const scrollTop = detail.scrollTop; const scrollTop = scrollEl.scrollTop;
const scrollHeight = scrollEl.scrollHeight; const scrollHeight = scrollEl.scrollHeight;
const height = scrollEl.offsetHeight; const height = scrollEl.offsetHeight;
const threshold = this.thrPc ? (height * this.thrPc) : this.thrPx; const threshold = this.thrPc ? (height * this.thrPc) : this.thrPx;
@ -128,7 +127,7 @@ export class InfiniteScroll {
? scrollHeight - infiniteHeight - scrollTop - threshold - height ? scrollHeight - infiniteHeight - scrollTop - threshold - height
: scrollTop - infiniteHeight - threshold; : scrollTop - infiniteHeight - threshold;
if (distanceFromInfinite < 0) { if (distanceFromInfinite < 0) {
if (!this.didFire) { if (!this.didFire) {
this.isLoading = true; this.isLoading = true;
this.didFire = true; this.didFire = true;
@ -221,7 +220,7 @@ export class InfiniteScroll {
private enableScrollEvents(shouldListen: boolean) { private enableScrollEvents(shouldListen: boolean) {
if (this.scrollEl) { if (this.scrollEl) {
this.enableListener(this, 'ionScroll', shouldListen, this.scrollEl); this.enableListener(this, 'scroll', shouldListen, this.scrollEl);
} }
} }

View File

@ -1,11 +1,15 @@
import { App } from '../../..';
const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA']; const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
export default function enableInputBlurring(app: App) { export default function enableInputBlurring() {
console.debug('Input: enableInputBlurring'); console.debug('Input: enableInputBlurring');
let focused = true; let focused = true;
let didScroll = false;
function onScroll() {
didScroll = true;
}
function onFocusin() { function onFocusin() {
focused = true; focused = true;
@ -13,8 +17,8 @@ export default function enableInputBlurring(app: App) {
function onTouchend(ev: any) { function onTouchend(ev: any) {
// if app did scroll return early // if app did scroll return early
if (app.didScroll) { if (didScroll) {
app.didScroll = false; didScroll = false;
return; return;
} }
const active = document.activeElement as HTMLElement; const active = document.activeElement as HTMLElement;
@ -49,10 +53,12 @@ export default function enableInputBlurring(app: App) {
}, 50); }, 50);
} }
document.addEventListener('ionScrollStart', onScroll);
document.addEventListener('focusin', onFocusin, true); document.addEventListener('focusin', onFocusin, true);
document.addEventListener('touchend', onTouchend, false); document.addEventListener('touchend', onTouchend, false);
return () => { return () => {
document.removeEventListener('ionScrollStart', onScroll, true);
document.removeEventListener('focusin', onFocusin, true); document.removeEventListener('focusin', onFocusin, true);
document.removeEventListener('touchend', onTouchend, false); document.removeEventListener('touchend', onTouchend, false);
}; };

View File

@ -1,5 +1,5 @@
import { Component, Listen, Prop } from '@stencil/core'; import { Component, Listen, Prop } from '@stencil/core';
import { App, Config } from '../..'; import { Config } from '../..';
import enableHideCaretOnScroll from './hacks/hide-caret'; import enableHideCaretOnScroll from './hacks/hide-caret';
import enableInputBlurring from './hacks/input-blurring'; import enableInputBlurring from './hacks/input-blurring';
@ -24,7 +24,6 @@ export class InputShims {
private scrollAssistMap = new WeakMap<HTMLElement, Function>(); private scrollAssistMap = new WeakMap<HTMLElement, Function>();
@Prop({context: 'config'}) config: Config; @Prop({context: 'config'}) config: Config;
@Prop() app: App;
componentDidLoad() { componentDidLoad() {
this.keyboardHeight = this.config.getNumber('keyboardHeight', 290); this.keyboardHeight = this.config.getNumber('keyboardHeight', 290);
@ -33,7 +32,7 @@ export class InputShims {
const inputBlurring = this.config.getBoolean('inputBlurring', true); const inputBlurring = this.config.getBoolean('inputBlurring', true);
if (inputBlurring && INPUT_BLURRING) { if (inputBlurring && INPUT_BLURRING) {
enableInputBlurring(this.app); enableInputBlurring();
} }
const scrollPadding = this.config.getBoolean('scrollPadding', true); const scrollPadding = this.config.getBoolean('scrollPadding', true);

View File

@ -5,20 +5,6 @@
<!-- Auto Generated Below --> <!-- Auto Generated Below -->
## Properties
#### app
## Attributes
#### app
---------------------------------------------- ----------------------------------------------

View File

@ -21,19 +21,9 @@ Note, the does not disable the system bounce on iOS. That is an OS level setting
string string
#### onionScroll #### scrollEvents
#### onionScrollEnd
#### onionScrollStart
boolean
## Attributes ## Attributes
@ -52,26 +42,17 @@ Note, the does not disable the system bounce on iOS. That is an OS level setting
string string
#### onion-scroll #### scroll-events
#### onion-scroll-end
#### onion-scroll-start
boolean
## Events ## Events
#### ionScroll #### ionScroll
Emitted while scrolling. Emitted while scrolling. This event is disabled by default.
Look at the property: `scrollEvents`
#### ionScrollEnd #### ionScrollEnd

View File

@ -14,9 +14,6 @@ ion-scroll {
contain: size style layout; contain: size style layout;
margin: 0 !important; // scss-lint:disable all
padding: 0 !important; // scss-lint:disable all
.focus-input { .focus-input {
@include margin(0); @include margin(0);
@include padding(0); @include padding(0);

View File

@ -1,6 +1,5 @@
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Method, Prop } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Config, DomController, GestureDelegate, GestureDetail } from '../../index'; import { Config, DomController, GestureDetail } from '../../index';
@Component({ @Component({
tag: 'ion-scroll', tag: 'ion-scroll',
@ -14,19 +13,14 @@ import { Config, DomController, GestureDelegate, GestureDetail } from '../../ind
}) })
export class Scroll { export class Scroll {
private gesture: GestureDelegate; private watchDog: any;
private positions: number[] = [];
private tmr: any;
private queued = false;
private app: HTMLIonAppElement;
private isScrolling = false; private isScrolling = false;
private detail: ScrollDetail = {}; private lastScroll = 0;
private detail: ScrollDetail;
private queued = false;
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
@Prop({ connect: 'ion-gesture-controller'}) gestureCtrl: HTMLIonGestureControllerElement;
@Prop({ context: 'config'}) config: Config; @Prop({ context: 'config'}) config: Config;
@Prop({ context: 'enableListener'}) enableListener: EventListenerEnable;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'dom' }) dom: DomController;
@Prop({ context: 'isServer' }) isServer: boolean; @Prop({ context: 'isServer' }) isServer: boolean;
@ -40,69 +34,76 @@ export class Scroll {
*/ */
@Prop({mutable: true}) forceOverscroll: boolean; @Prop({mutable: true}) forceOverscroll: boolean;
@Prop() onionScrollStart: ScrollCallback; @Prop() scrollEvents = false;
@Prop() onionScroll: ScrollCallback;
@Prop() onionScrollEnd: ScrollCallback;
/** /**
* Emitted when the scroll has started. * Emitted when the scroll has started.
*/ */
@Event() ionScrollStart: EventEmitter; @Event() ionScrollStart: EventEmitter<ScrollBaseDetail>;
/** /**
* Emitted while scrolling. * Emitted while scrolling. This event is disabled by default.
* Look at the property: `scrollEvents`
*/ */
@Event({bubbles: false}) ionScroll: EventEmitter; @Event({bubbles: false}) ionScroll: EventEmitter<ScrollDetail>;
/** /**
* Emitted when the scroll has ended. * Emitted when the scroll has ended.
*/ */
@Event() ionScrollEnd: EventEmitter; @Event() ionScrollEnd: EventEmitter<ScrollBaseDetail>;
componentWillLoad() { constructor() {
return this.gestureCtrl.create({ // Detail is used in a hot loop in the scroll event, by allocating it here
name: 'scroll', // V8 will be able to inline any read/write to it since it's a monomorphic class.
priority: 100, // https://mrale.ph/blog/2015/01/11/whats-up-with-monomorphism.html
disableScroll: false, this.detail = {
}).then((gesture) => { positions: [],
this.gesture = gesture; scrollTop: 0,
}); scrollLeft: 0,
type: 'scroll',
event: undefined,
startX: 0,
startY: 0,
startTimeStamp: 0,
currentX: 0,
currentY: 0,
velocityX: 0,
velocityY: 0,
deltaX: 0,
deltaY: 0,
timeStamp: 0,
data: undefined
};
} }
componentDidLoad() { componentWillLoad() {
if (this.isServer) { if (this.isServer) {
return; return;
} }
if (this.forceOverscroll === undefined) { if (this.forceOverscroll === undefined) {
this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in window); this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in window);
} }
this.app = this.el.closest('ion-app');
} }
componentDidUnload() {
this.gesture && this.gesture.destroy();
this.gesture = this.detail = this.detail.event = null;
}
// Native Scroll *************************
@Listen('scroll', { passive: true }) @Listen('scroll', { passive: true })
onNativeScroll() { onScroll(ev: UIEvent) {
if (!this.queued) { const timeStamp = Date.now();
const didStart = !this.isScrolling;
this.lastScroll = timeStamp;
if (didStart) {
this.onScrollStart();
}
if (!this.queued && this.scrollEvents) {
this.queued = true; this.queued = true;
this.dom.read(timeStamp => { this.dom.read(timeStamp => {
this.queued = false; this.queued = false;
this.onScroll(timeStamp); this.detail.event = ev;
updateScrollDetail(this.detail, this.el, timeStamp, didStart);
this.ionScroll.emit(this.detail);
}); });
} }
} }
@Listen('touchend', {passive: true, capture: true })
onTouchEnd(_ev: TouchEvent) {
this.refocus();
}
@Method() @Method()
scrollToTop(duration: number): Promise<void> { scrollToTop(duration: number): Promise<void> {
return this.scrollToPoint(0, 0, duration); return this.scrollToPoint(0, 0, duration);
@ -212,115 +213,29 @@ export class Scroll {
return promise; return promise;
} }
private onScroll(timeStamp: number) {
const detail = this.detail;
const positions = this.positions;
const el = this.el;
if (this.app) {
this.app.setScrolling();
}
detail.timeStamp = timeStamp; private onScrollStart() {
this.isScrolling = true;
this.ionScrollStart.emit({
isScrolling: true
});
// get the current scrollTop // watchdog
// ******** DOM READ **************** this.watchDog = setInterval(() => {
detail.scrollTop = el.scrollTop; if (this.lastScroll < Date.now() - 120) {
this.onScrollEnd();
// get the current scrollLeft
// ******** DOM READ ****************
detail.scrollLeft = el.scrollLeft;
if (!this.isScrolling) {
// currently not scrolling, so this is a scroll start
this.isScrolling = true;
// remember the start positions
detail.startY = detail.scrollTop;
detail.startX = detail.scrollLeft;
// new scroll, so do some resets
detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0;
// emit only on the first scroll event
if (this.onionScrollStart) {
this.onionScrollStart(detail);
} }
this.gesture.capture(); }, 100);
this.ionScrollStart.emit(detail);
}
detail.deltaY = (detail.scrollTop - detail.startY);
detail.deltaX = (detail.scrollLeft - detail.startX);
// actively scrolling
positions.push(detail.scrollTop, detail.scrollLeft, detail.timeStamp);
// move pointer to position measured 100ms ago
const timeRange = timeStamp - 100;
let startPos = positions.length - 1;
while (startPos > 0 && positions[startPos] > timeRange) {
startPos -= 3;
}
if (startPos > 3) {
// compute relative movement between these two points
const frequency = 1 / (positions[startPos] - timeStamp);
const movedY = positions[startPos - 1] - detail.scrollLeft;
const movedX = positions[startPos - 2] - detail.scrollTop;
// based on XXms compute the movement to apply for each render step
// velocity = space/time = s*(1/t) = s*frequency
detail.velocityX = movedX * frequency;
detail.velocityY = movedY * frequency;
} else {
detail.velocityX = 0;
detail.velocityY = 0;
}
clearTimeout(this.tmr);
this.tmr = setTimeout(() => {
// haven't scrolled in a while, so it's a scrollend
this.isScrolling = false;
this.dom.read(timeStamp => {
if (!this.isScrolling) {
this.onEnd(timeStamp);
}
});
}, 80);
// emit on each scroll event
if (this.onionScroll) {
this.onionScroll(detail);
} else {
this.ionScroll.emit(detail);
}
} }
private onEnd(timeStamp: number) { private onScrollEnd() {
const detail = this.detail;
detail.timeStamp = timeStamp; clearInterval(this.watchDog);
this.watchDog = null;
// emit that the scroll has ended this.isScrolling = false;
this.gesture.release(); this.ionScrollEnd.emit({
isScrolling: false
if (this.onionScrollEnd) { });
this.onionScrollEnd(detail);
}
this.ionScrollEnd.emit(detail);
this.refocus();
}
private refocus() {
// TODO: renable when desktop testing is done
// setTimeout(() => {
// if (document.activeElement === document.body) {
// (this.el.querySelector('.focus-input') as HTMLElement).focus();
// }
// });
} }
hostData() { hostData() {
@ -339,18 +254,67 @@ export class Scroll {
</div> </div>
]; ];
} }
} }
export interface ScrollDetail extends GestureDetail { // ******** DOM READ ****************
function updateScrollDetail(
detail: ScrollDetail,
el: HTMLElement,
timeStamp: number,
didStart: boolean
) {
const scrollTop = el.scrollTop;
const scrollLeft = el.scrollLeft;
const positions = detail.positions;
if (didStart) {
// remember the start positions
detail.startTimeStamp = timeStamp;
detail.startY = scrollTop;
detail.startX = scrollLeft;
positions.length = 0;
}
detail.timeStamp = timeStamp;
detail.currentY = detail.scrollTop = scrollTop;
detail.currentX = detail.scrollLeft = scrollLeft;
detail.deltaY = scrollTop - detail.startY;
detail.deltaX = scrollLeft - detail.startX;
// actively scrolling
positions.push(scrollTop, scrollLeft, timeStamp);
// move pointer to position measured 100ms ago
const timeRange = timeStamp - 100;
let startPos = positions.length - 1;
while (startPos > 0 && positions[startPos] > timeRange) {
startPos -= 3;
}
if (startPos > 3) {
// compute relative movement between these two points
const frequency = 1 / (positions[startPos] - timeStamp);
const movedX = positions[startPos - 1] - scrollLeft;
const movedY = positions[startPos - 2] - scrollTop;
// based on XXms compute the movement to apply for each render step
// velocity = space/time = s*(1/t) = s*frequency
detail.velocityX = movedX * frequency;
detail.velocityY = movedY * frequency;
} else {
detail.velocityX = 0;
detail.velocityY = 0;
}
}
export interface ScrollDetail extends GestureDetail, ScrollBaseDetail {
positions?: number[];
scrollTop?: number; scrollTop?: number;
scrollLeft?: number; scrollLeft?: number;
scrollHeight?: number; }
scrollWidth?: number;
contentHeight?: number; export interface ScrollBaseDetail {
contentWidth?: number; isScrolling?: boolean;
contentTop?: number;
contentBottom?: number;
} }
export interface ScrollCallback { export interface ScrollCallback {

View File

@ -80,7 +80,7 @@ export {
} from './components/router/router-utils'; } from './components/router/router-utils';
export { Row } from './components/row/row'; export { Row } from './components/row/row';
export { Reorder } from './components/reorder/reorder'; export { Reorder } from './components/reorder/reorder';
export { Scroll, ScrollCallback, ScrollDetail } from './components/scroll/scroll'; export { Scroll } from './components/scroll/scroll';
export { Searchbar } from './components/searchbar/searchbar'; export { Searchbar } from './components/searchbar/searchbar';
export { Segment } from './components/segment/segment'; export { Segment } from './components/segment/segment';
export { SegmentButton } from './components/segment-button/segment-button'; export { SegmentButton } from './components/segment-button/segment-button';

View File

@ -44,9 +44,12 @@ export function isCheckedProperty(a: any, b: any): boolean {
return (a == b); // tslint:disable-line return (a == b); // tslint:disable-line
} }
export function assert(bool: boolean, msg: string) { export function assert(actual: any, reason: string) {
if (!bool) { if (!actual) {
console.error(msg); const message = 'ASSERT: ' + reason;
console.error(message);
debugger; // tslint:disable-line
throw new Error(message);
} }
} }
@ -143,7 +146,7 @@ export function getPageElement(el: HTMLElement) {
if (tabs) { if (tabs) {
return tabs; return tabs;
} }
const page = el.closest('ion-page,.ion-page,page-inner'); const page = el.closest('ion-app,ion-page,.ion-page,page-inner');
if (page) { if (page) {
return page; return page;
} }