feat(queue): use stencil's queue controller for dom read/writes

This commit is contained in:
Adam Bradley
2018-04-11 15:24:49 -05:00
parent 6a31f3960a
commit d623b3b71f
17 changed files with 75 additions and 145 deletions

View File

@ -1,5 +1,5 @@
import { Component, Element, Listen, Method, Prop } from '@stencil/core'; import { Component, Element, Listen, Method, Prop } from '@stencil/core';
import { Config, DomController } from '../../index'; import { Config, QueueController } from '../../index';
@Component({ @Component({
tag: 'ion-content', tag: 'ion-content',
@ -18,7 +18,7 @@ export class Content {
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
@Prop({ context: 'config' }) config: Config; @Prop({ context: 'config' }) config: Config;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
/** /**
* If true, the content will scroll behind the headers * If true, the content will scroll behind the headers
@ -100,13 +100,13 @@ export class Content {
return; return;
} }
if (this.fullscreen) { if (this.fullscreen) {
this.dom.raf(() => { this.queue.read(() => {
this.dom.read(this.readDimensions.bind(this)); this.queue.read(this.readDimensions.bind(this));
this.dom.write(this.writeDimensions.bind(this)); this.queue.write(this.writeDimensions.bind(this));
}); });
} else { } else {
this.cTop = this.cBottom = -1; this.cTop = this.cBottom = -1;
this.dom.write(() => this.scrollEl && this.scrollEl.removeAttribute('style')); this.queue.write(() => this.scrollEl && this.scrollEl.removeAttribute('style'));
} }
} }

View File

@ -1,6 +1,6 @@
import { Component, Event, EventEmitter, EventListenerEnable, Listen, Prop, Watch } from '@stencil/core'; import { Component, Event, EventEmitter, EventListenerEnable, Listen, Prop, Watch } from '@stencil/core';
import { assert, now } from '../../utils/helpers'; import { assert, now } from '../../utils/helpers';
import { BlockerConfig, BlockerDelegate, DomController, GestureDelegate } from '../../index'; import { BlockerConfig, BlockerDelegate, GestureDelegate, QueueController } from '../../index';
import { PanRecognizer } from './recognizers'; import { PanRecognizer } from './recognizers';
export const BLOCK_ALL: BlockerConfig = { export const BLOCK_ALL: BlockerConfig = {
@ -27,7 +27,7 @@ export class Gesture {
private blocker: BlockerDelegate|undefined; private blocker: BlockerDelegate|undefined;
@Prop({ connect: 'ion-gesture-controller' }) gestureCtrl: HTMLIonGestureControllerElement; @Prop({ connect: 'ion-gesture-controller' }) gestureCtrl: HTMLIonGestureControllerElement;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'enableListener' }) enableListener: EventListenerEnable; @Prop({ context: 'enableListener' }) enableListener: EventListenerEnable;
@Prop() disabled = false; @Prop() disabled = false;
@ -232,7 +232,7 @@ export class Gesture {
if (!this.isMoveQueued && this.hasFiredStart) { if (!this.isMoveQueued && this.hasFiredStart) {
this.isMoveQueued = true; this.isMoveQueued = true;
this.calcGestureData(ev); this.calcGestureData(ev);
this.dom.write(this.fireOnMove.bind(this)); this.queue.write(this.fireOnMove.bind(this));
} }
return; return;
} }

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 } from '../../index'; import { QueueController } from '../../index';
const enum Position { const enum Position {
Top = 'top', Top = 'top',
@ -22,7 +22,7 @@ export class InfiniteScroll {
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
@State() isLoading = false; @State() isLoading = false;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'enableListener' }) enableListener: EventListenerEnable; @Prop({ context: 'enableListener' }) enableListener: EventListenerEnable;
/** /**
@ -92,7 +92,7 @@ export class InfiniteScroll {
this.thresholdChanged(this.threshold); this.thresholdChanged(this.threshold);
this.enableScrollEvents(!this.disabled); this.enableScrollEvents(!this.disabled);
if (this.position === Position.Top) { if (this.position === Position.Top) {
this.dom.write(() => this.scrollEl && this.scrollEl.scrollToBottom(0)); this.queue.write(() => this.scrollEl && this.scrollEl.scrollToBottom(0));
} }
} }
@ -179,14 +179,14 @@ export class InfiniteScroll {
const prev = scrollEl.scrollHeight - scrollEl.scrollTop; const prev = scrollEl.scrollHeight - scrollEl.scrollTop;
// ******** DOM READ **************** // ******** DOM READ ****************
this.dom.read(() => { this.queue.read(() => {
// UI has updated, save the new content dimensions // UI has updated, save the new content dimensions
const scrollHeight = scrollEl.scrollHeight; const scrollHeight = scrollEl.scrollHeight;
// New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around // New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around
const newScrollTop = scrollHeight - prev; const newScrollTop = scrollHeight - prev;
// ******** DOM WRITE **************** // ******** DOM WRITE ****************
this.dom.write(() => { this.queue.write(() => {
scrollEl.scrollTop = newScrollTop; scrollEl.scrollTop = newScrollTop;
this.isBusy = false; this.isBusy = false;
}); });

View File

@ -11,7 +11,7 @@ import {
} from './nav-util'; } from './nav-util';
import { ViewController, matches } from './view-controller'; import { ViewController, matches } from './view-controller';
import { Animation, ComponentProps, Config, DomController, FrameworkDelegate, GestureDetail, NavOutlet } from '../..'; import { Animation, ComponentProps, Config, FrameworkDelegate, GestureDetail, NavOutlet, QueueController } from '../..';
import { RouteID, RouteWrite, RouterDirection } from '../router/utils/interfaces'; import { RouteID, RouteWrite, RouterDirection } from '../router/utils/interfaces';
import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition'; import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
import { assert } from '../../utils/helpers'; import { assert } from '../../utils/helpers';
@ -25,7 +25,7 @@ import mdTransitionAnimation from './animations/md.transition';
export class Nav implements NavOutlet { export class Nav implements NavOutlet {
private init = false; private init = false;
private queue: TransitionInstruction[] = []; private transInstr: TransitionInstruction[] = [];
private sbTrns: Animation|undefined; private sbTrns: Animation|undefined;
private useRouter = false; private useRouter = false;
private isTransitioning = false; private isTransitioning = false;
@ -36,7 +36,7 @@ export class Nav implements NavOutlet {
@Element() el: HTMLElement; @Element() el: HTMLElement;
@Prop({context: 'dom'}) dom: DomController; @Prop({context: 'queue'}) queue: QueueController;
@Prop({context: 'config'}) config: Config; @Prop({context: 'config'}) config: Config;
@Prop({context: 'window'}) win: Window; @Prop({context: 'window'}) win: Window;
@ -82,7 +82,7 @@ export class Nav implements NavOutlet {
// release swipe back gesture and transition // release swipe back gesture and transition
this.sbTrns && this.sbTrns.destroy(); this.sbTrns && this.sbTrns.destroy();
this.queue.length = this.views.length = 0; this.transInstr.length = this.views.length = 0;
this.sbTrns = undefined; this.sbTrns = undefined;
this.destroyed = true; this.destroyed = true;
} }
@ -283,7 +283,7 @@ export class Nav implements NavOutlet {
} }
// Enqueue transition instruction // Enqueue transition instruction
this.queue.push(ti); this.transInstr.push(ti);
// if there isn't a transition already happening // if there isn't a transition already happening
// then this will kick off this transition // then this will kick off this transition
@ -293,7 +293,7 @@ export class Nav implements NavOutlet {
} }
private success(result: NavResult, ti: TransitionInstruction) { private success(result: NavResult, ti: TransitionInstruction) {
if (this.queue === null) { if (this.transInstr === null) {
this.fireError('nav controller was destroyed', ti); this.fireError('nav controller was destroyed', ti);
return; return;
} }
@ -323,11 +323,11 @@ export class Nav implements NavOutlet {
} }
private failed(rejectReason: any, ti: TransitionInstruction) { private failed(rejectReason: any, ti: TransitionInstruction) {
if (this.queue === null) { if (this.transInstr === null) {
this.fireError('nav controller was destroyed', ti); this.fireError('nav controller was destroyed', ti);
return; return;
} }
this.queue.length = 0; this.transInstr.length = 0;
this.fireError(rejectReason, ti); this.fireError(rejectReason, ti);
} }
@ -351,7 +351,7 @@ export class Nav implements NavOutlet {
// there is no transition happening right now // there is no transition happening right now
// get the next instruction // get the next instruction
const ti = this.queue.shift(); const ti = this.transInstr.shift();
if (!ti) { if (!ti) {
return false; return false;
} }
@ -689,7 +689,7 @@ export class Nav implements NavOutlet {
} }
private swipeBackStart() { private swipeBackStart() {
if (this.isTransitioning || this.queue.length > 0) { if (this.isTransitioning || this.transInstr.length > 0) {
return; return;
} }

View File

@ -1,6 +1,6 @@
import { Component, Element, Prop } from '@stencil/core'; import { Component, Element, Prop } from '@stencil/core';
import { DomController, GestureDetail, PickerColumn, PickerColumnOption } from '../../index';
import { clamp } from '../../utils/helpers'; import { clamp } from '../../utils/helpers';
import { GestureDetail, PickerColumn, PickerColumnOption, QueueController } from '../../index';
import { hapticSelectionChanged } from '../../utils'; import { hapticSelectionChanged } from '../../utils';
@ -28,7 +28,7 @@ export class PickerColumnCmp {
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
@Prop() col: PickerColumn; @Prop() col: PickerColumn;
@ -210,7 +210,7 @@ export class PickerColumnCmp {
if (notLockedIn) { if (notLockedIn) {
// isn't locked in yet, keep decelerating until it is // isn't locked in yet, keep decelerating until it is
this.dom.raf(() => this.decelerate()); this.queue.read(() => this.decelerate());
} }
} else if (this.y % this.optHeight !== 0) { } else if (this.y % this.optHeight !== 0) {

View File

@ -1,5 +1,5 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Method, Prop, State } from '@stencil/core';
import { DomController, GestureDetail } from '../../index'; import { GestureDetail, QueueController } from '../../index';
export const enum RefresherState { export const enum RefresherState {
Inactive = 1 << 0, Inactive = 1 << 0,
@ -30,7 +30,7 @@ export class Refresher {
private progress = 0; private progress = 0;
private scrollEl: HTMLElement | null = null; private scrollEl: HTMLElement | null = null;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
/** /**
* The current state which the refresher is in. The refresher's states include: * The current state which the refresher is in. The refresher's states include:
@ -335,7 +335,7 @@ export class Refresher {
private setCss(y: number, duration: string, overflowVisible: boolean, delay: string) { private setCss(y: number, duration: string, overflowVisible: boolean, delay: string) {
this.appliedStyles = (y > 0); this.appliedStyles = (y > 0);
this.dom.write(() => { this.queue.write(() => {
if (this.scrollEl) { if (this.scrollEl) {
const style = this.scrollEl.style; const style = this.scrollEl.style;
style.transform = ((y > 0) ? 'translateY(' + y + 'px) translateZ(0px)' : 'translateZ(0px)'); style.transform = ((y > 0) ? 'translateY(' + y + 'px) translateZ(0px)' : 'translateZ(0px)');

View File

@ -1,5 +1,5 @@
import { Component, Element, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Prop, State, Watch } from '@stencil/core';
import { DomController, GestureDetail } from '../../index'; import { GestureDetail, QueueController } from '../../index';
import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart} from '../../utils/haptic'; import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart} from '../../utils/haptic';
const AUTO_SCROLL_MARGIN = 60; const AUTO_SCROLL_MARGIN = 60;
@ -44,7 +44,7 @@ export class ReorderGroup {
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
/** /**
* If true, the reorder will be hidden. Defaults to `true`. * If true, the reorder will be hidden. Defaults to `true`.
@ -55,7 +55,7 @@ export class ReorderGroup {
protected disabledChanged(disabled: boolean) { protected disabledChanged(disabled: boolean) {
if (!disabled) { if (!disabled) {
this.enabled = true; this.enabled = true;
this.dom.raf(() => { this.queue.read(() => {
this.iconVisible = true; this.iconVisible = true;
}); });
} else { } else {

View File

@ -1,6 +1,6 @@
import { Component, Element, EventListenerEnable, Listen, Method, Prop, Watch } from '@stencil/core'; import { Component, Element, EventListenerEnable, Listen, Method, Prop, Watch } from '@stencil/core';
import { now } from '../../utils/helpers'; import { now } from '../../utils/helpers';
import { DomController } from '../../global/dom-controller'; import { QueueController } from '../../index';
@Component({ @Component({
tag: 'ion-ripple-effect', tag: 'ion-ripple-effect',
@ -11,7 +11,7 @@ export class RippleEffect {
private lastClick = -10000; private lastClick = -10000;
@Element() el: HTMLElement; @Element() el: HTMLElement;
@Prop({context: 'dom'}) dom: DomController; @Prop({context: 'queue'}) queue: QueueController;
@Prop({context: 'enableListener'}) enableListener: EventListenerEnable; @Prop({context: 'enableListener'}) enableListener: EventListenerEnable;
@Prop() tapClick = false; @Prop() tapClick = false;
@ -50,7 +50,7 @@ export class RippleEffect {
addRipple(pageX: number, pageY: number) { addRipple(pageX: number, pageY: number) {
let x: number, y: number, size: number; let x: number, y: number, size: number;
this.dom.read(() => { this.queue.read(() => {
const rect = this.el.getBoundingClientRect(); const rect = this.el.getBoundingClientRect();
const width = rect.width; const width = rect.width;
const height = rect.height; const height = rect.height;
@ -58,7 +58,7 @@ export class RippleEffect {
x = pageX - rect.left - (size / 2); x = pageX - rect.left - (size / 2);
y = pageY - rect.top - (size / 2); y = pageY - rect.top - (size / 2);
}); });
this.dom.write(() => { this.queue.write(() => {
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('ripple-effect'); div.classList.add('ripple-effect');
const style = div.style; const style = div.style;

View File

@ -1,5 +1,5 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Config, DomController } from '../../index'; import { Config, QueueController } from '../../index';
import { flattenRouterTree, readRedirects, readRoutes } from './utils/parser'; import { flattenRouterTree, readRedirects, readRoutes } from './utils/parser';
import { readNavState, writeNavState } from './utils/dom'; import { readNavState, writeNavState } from './utils/dom';
import { chainToPath, generatePath, parsePath, readPath, writePath } from './utils/path'; import { chainToPath, generatePath, parsePath, readPath, writePath } from './utils/path';
@ -23,7 +23,7 @@ export class Router {
@Element() el: HTMLElement; @Element() el: HTMLElement;
@Prop({ context: 'config' }) config: Config; @Prop({ context: 'config' }) config: Config;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
@Prop() base = ''; @Prop() base = '';
@Prop() useHash = true; @Prop() useHash = true;

View File

@ -1,5 +1,5 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Config, DomController, GestureDetail } from '../../index'; import { Config, GestureDetail, QueueController } from '../../index';
@Component({ @Component({
tag: 'ion-scroll', tag: 'ion-scroll',
@ -22,7 +22,7 @@ export class Scroll {
@Element() private el: HTMLElement; @Element() private el: HTMLElement;
@Prop({ context: 'config'}) config: Config; @Prop({ context: 'config'}) config: Config;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
@Prop() mode: string; @Prop() mode: string;
@ -99,7 +99,7 @@ export class Scroll {
} }
if (!this.queued && this.scrollEvents) { if (!this.queued && this.scrollEvents) {
this.queued = true; this.queued = true;
this.dom.read(timeStamp => { this.queue.read(timeStamp => {
this.queued = false; this.queued = false;
this.detail.event = ev; this.detail.event = ev;
updateScrollDetail(this.detail, this.el, timeStamp, didStart); updateScrollDetail(this.detail, this.el, timeStamp, didStart);
@ -189,7 +189,7 @@ export class Scroll {
if (easedT < 1) { if (easedT < 1) {
// do not use DomController here // do not use DomController here
// must use nativeRaf in order to fire in the next frame // must use nativeRaf in order to fire in the next frame
self.dom.raf(step); self.queue.read(step);
} else { } else {
stopScroll = true; stopScroll = true;
@ -203,8 +203,8 @@ export class Scroll {
self.isScrolling = true; self.isScrolling = true;
// chill out for a frame first // chill out for a frame first
this.dom.write(() => { this.queue.write(() => {
this.dom.write(timeStamp => { this.queue.write(timeStamp => {
startTime = timeStamp; startTime = timeStamp;
step(timeStamp); step(timeStamp);
}); });

View File

@ -1,18 +1,18 @@
import { Component, Listen, Prop } from '@stencil/core'; import { Component, Listen, Prop } from '@stencil/core';
import { DomController } from '../..'; import { QueueController } from '../..';
@Component({ @Component({
tag: 'ion-status-tap' tag: 'ion-status-tap'
}) })
export class StatusTap { export class StatusTap {
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
@Prop() duration = 300; @Prop() duration = 300;
@Listen('window:statusTap') @Listen('window:statusTap')
onStatusTap() { onStatusTap() {
this.dom.read(() => { this.queue.read(() => {
const width = window.innerWidth; const width = window.innerWidth;
const height = window.innerWidth; const height = window.innerWidth;
const el = document.elementFromPoint(width / 2, height / 2); const el = document.elementFromPoint(width / 2, height / 2);
@ -22,7 +22,7 @@ export class StatusTap {
const scrollEl = el.closest('ion-scroll'); const scrollEl = el.closest('ion-scroll');
if (scrollEl) { if (scrollEl) {
scrollEl.componentOnReady().then(() => { scrollEl.componentOnReady().then(() => {
this.dom.write(() => { this.queue.write(() => {
scrollEl.scrollToTop(this.duration); scrollEl.scrollToTop(this.duration);
}); });
}); });

View File

@ -1,6 +1,6 @@
import { Component, Element, Listen, Prop, State, Watch } from '@stencil/core'; import { Component, Element, Listen, Prop, State, Watch } from '@stencil/core';
import { createThemedClasses } from '../../utils/theme'; import { createThemedClasses } from '../../utils/theme';
import { DomController } from '../../index'; import { QueueController } from '../../index';
@Component({ @Component({
tag: 'ion-tabbar', tag: 'ion-tabbar',
@ -23,7 +23,7 @@ export class Tabbar {
@State() hidden = false; @State() hidden = false;
@Prop({ context: 'dom' }) dom: DomController; @Prop({ context: 'queue' }) queue: QueueController;
@Prop() placement = 'bottom'; @Prop() placement = 'bottom';
@Prop() selectedTab: HTMLIonTabElement; @Prop() selectedTab: HTMLIonTabElement;
@Prop() scrollable: boolean; @Prop() scrollable: boolean;
@ -99,7 +99,7 @@ export class Tabbar {
} }
protected scrollToSelectedButton() { protected scrollToSelectedButton() {
this.dom.read(() => { this.queue.read(() => {
const activeTabButton = this.getSelectedButton(); const activeTabButton = this.getSelectedButton();
if (activeTabButton) { if (activeTabButton) {
@ -126,7 +126,7 @@ export class Tabbar {
} }
private scrollByTab(direction: 'left' | 'right') { private scrollByTab(direction: 'left' | 'right') {
this.dom.read(() => { this.queue.read(() => {
const {previous, next} = this.analyzeTabs(); const {previous, next} = this.analyzeTabs();
const info = direction === 'right' ? next : previous; const info = direction === 'right' ? next : previous;
const amount = info && info.amount; const amount = info && info.amount;
@ -148,7 +148,7 @@ export class Tabbar {
if (!this.highlight) { if (!this.highlight) {
return; return;
} }
this.dom.read(() => { this.queue.read(() => {
const btn = this.getSelectedButton(); const btn = this.getSelectedButton();
const highlight = this.el.querySelector('div.tabbar-highlight') as HTMLElement; const highlight = this.el.querySelector('div.tabbar-highlight') as HTMLElement;
if (btn && highlight) { if (btn && highlight) {

View File

@ -1,10 +1,10 @@
import { Component, Element, EventListenerEnable, Listen, Method, Prop, Watch } from '@stencil/core'; import { Component, Element, EventListenerEnable, Listen, Method, Prop, Watch } from '@stencil/core';
import { DomController } from '../../index';
import { Cell, DomRenderFn, HeaderFn, ItemHeightFn, import { Cell, DomRenderFn, HeaderFn, ItemHeightFn,
ItemRenderFn, NodeHeightFn, Range, Viewport, ItemRenderFn, NodeHeightFn, Range, Viewport,
VirtualNode, calcCells, calcHeightIndex, doRender, VirtualNode, calcCells, calcHeightIndex, doRender,
findCellIndex, getRange, getShouldUpdate, getViewport, findCellIndex, getRange, getShouldUpdate, getViewport,
inplaceUpdate, positionForIndex, resizeBuffer, updateVDom } from './virtual-scroll-utils'; inplaceUpdate, positionForIndex, resizeBuffer, updateVDom } from './virtual-scroll-utils';
import { QueueController } from '../../index';
@Component({ @Component({
@ -30,7 +30,7 @@ export class VirtualScroll {
@Element() el: HTMLStencilElement; @Element() el: HTMLStencilElement;
@Prop({context: 'dom'}) dom: DomController; @Prop({context: 'queue'}) queue: QueueController;
@Prop({context: 'enableListener'}) enableListener: EventListenerEnable; @Prop({context: 'enableListener'}) enableListener: EventListenerEnable;
@ -206,8 +206,8 @@ export class VirtualScroll {
this.timerUpdate = null; this.timerUpdate = null;
} }
this.dom.read(this.readVS.bind(this)); this.queue.read(this.readVS.bind(this));
this.dom.read(this.writeVS.bind(this)); this.queue.read(this.writeVS.bind(this));
} }
private readVS() { private readVS() {

View File

@ -1,79 +0,0 @@
/* tslint:disable */
export interface Now {
(): number;
}
export interface DomController {
read: DomControllerCallback;
write: DomControllerCallback;
raf: DomControllerCallback;
}
export interface RafCallback {
(timeStamp: number): void;
}
export interface DomControllerCallback {
(cb: RafCallback): void;
}
export function createDomControllerClient(win: Window, now: Now, rafPending?: boolean): DomController {
const readCBs: RafCallback[] = [];
const writeCBs: RafCallback[] = [];
const raf = (cb: FrameRequestCallback): number => win.requestAnimationFrame(cb);
function rafFlush(timeStamp: number, startTime?: number, cb?: RafCallback, err?: any) {
try {
startTime = now();
// ******** DOM READS ****************
while (cb = readCBs.shift()) {
cb(timeStamp);
}
// ******** DOM WRITES ****************
while (cb = writeCBs.shift()) {
cb(timeStamp);
if ((now() - startTime) > 8) {
break;
}
}
} catch (e) {
err = e;
}
if (rafPending = (readCBs.length > 0 || writeCBs.length > 0)) {
raf(rafFlush);
}
if (err) {
console.error(err);
}
}
return {
read: (cb: RafCallback) => {
readCBs.push(cb);
if (!rafPending) {
rafPending = true;
raf(rafFlush);
}
},
write: (cb: RafCallback) => {
writeCBs.push(cb);
if (!rafPending) {
rafPending = true;
raf(rafFlush);
}
},
raf: raf
};
}

View File

@ -1,6 +1,5 @@
import 'ionicons'; import 'ionicons';
import { createConfigController } from './config-controller'; import { createConfigController } from './config-controller';
import { createDomControllerClient } from './dom-controller';
import { PLATFORM_CONFIGS, detectPlatforms, readQueryParam } from './platform-configs'; import { PLATFORM_CONFIGS, detectPlatforms, readQueryParam } from './platform-configs';
@ -8,12 +7,9 @@ const Ionic = (window as any).Ionic = (window as any).Ionic || {};
declare const Context: any; declare const Context: any;
// add dom controller, used to coordinate DOM reads and write in order to avoid // queue used to coordinate DOM reads and
// layout thrashing // write in order to avoid layout thrashing
if (!Context.dom) { Ionic.queue = Context.queue;
const now = () => window.performance.now();
Context.dom = createDomControllerClient(window, now);
}
if (!Context.platforms) { if (!Context.platforms) {
Context.platforms = detectPlatforms(window.location.href, window.navigator.userAgent, PLATFORM_CONFIGS, 'core'); Context.platforms = detectPlatforms(window.location.href, window.navigator.userAgent, PLATFORM_CONFIGS, 'core');

View File

@ -0,0 +1,13 @@
export interface QueueController {
read: DomControllerCallback;
write: DomControllerCallback;
}
export interface RafCallback {
(timeStamp: number): void;
}
export interface DomControllerCallback {
(cb: RafCallback): void;
}

2
core/src/index.d.ts vendored
View File

@ -106,7 +106,7 @@ export { PlatformConfig } from './global/platform-configs';
// export all of the component declarations that are dynamically created // export all of the component declarations that are dynamically created
export * from './components'; export * from './components';
export { DomController, RafCallback } from './global/dom-controller'; export { QueueController, RafCallback } from './global/queue-controller';
export { FrameworkDelegate } from './utils/framework-delegate'; export { FrameworkDelegate } from './utils/framework-delegate';
export { OverlayEventDetail } from './utils/overlays'; export { OverlayEventDetail } from './utils/overlays';
export * from './utils/transition'; export * from './utils/transition';