fix(prerender): local references to window/document

This commit is contained in:
Manu Mtz.-Almeida
2018-04-19 13:26:49 +02:00
parent 86a6cde4a1
commit 78bd146ad2
42 changed files with 174 additions and 139 deletions

View File

@@ -1,4 +1,4 @@
import { Component, Listen, Method } from '@stencil/core';
import { Component, Listen, Method, Prop } from '@stencil/core';
import { ActionSheetOptions } from '../../index';
import { OverlayController, createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays';
@@ -9,6 +9,8 @@ export class ActionSheetController implements OverlayController {
private actionSheets = new Map<number, HTMLIonActionSheetElement>();
@Prop({ context: 'document' }) doc: Document;
@Listen('body:ionActionSheetWillPresent')
protected actionSheetWillPresent(ev: any) {
this.actionSheets.set(ev.target.overlayId, ev.target);
@@ -30,7 +32,7 @@ export class ActionSheetController implements OverlayController {
*/
@Method()
create(opts?: ActionSheetOptions): Promise<HTMLIonActionSheetElement> {
return createOverlay(document.createElement('ion-action-sheet'), opts);
return createOverlay(this.doc.createElement('ion-action-sheet'), opts);
}
/*

View File

@@ -1,4 +1,4 @@
import { Component, Listen, Method } from '@stencil/core';
import { Component, Listen, Method, Prop } from '@stencil/core';
import { AlertOptions } from '../../index';
import { OverlayController, createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays';
@@ -9,6 +9,8 @@ export class AlertController implements OverlayController {
private alerts = new Map<number, HTMLIonAlertElement>();
@Prop({ context: 'document' }) doc: Document;
@Listen('body:ionAlertWillPresent')
protected alertWillPresent(ev: any) {
this.alerts.set(ev.target.overlayId, ev.target);
@@ -30,7 +32,7 @@ export class AlertController implements OverlayController {
*/
@Method()
create(opts?: AlertOptions): Promise<HTMLIonAlertElement> {
return createOverlay(document.createElement('ion-alert'), opts);
return createOverlay(this.doc.createElement('ion-alert'), opts);
}
/*

View File

@@ -7,6 +7,8 @@ import { openURL } from '../../utils/theme';
})
export class Anchor {
@Prop({ context: 'window' }) win: Window;
/**
* Contains a URL or a URL fragment that the hyperlink points to.
* If this property is set, an anchor tag will be rendered.
@@ -22,7 +24,7 @@ export class Anchor {
render() {
return <a
href={this.href}
onClick={(ev) => openURL(this.href, ev, this.routerDirection)}>
onClick={(ev) => openURL(this.win, this.href, ev, this.routerDirection)}>
<slot/>
</a>;
}

View File

@@ -17,6 +17,7 @@ export class BackButton {
@Element() el: HTMLElement;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'window' }) win: Window;
/**
* The color to use from your Sass `$colors` map.
@@ -54,7 +55,7 @@ export class BackButton {
ev.preventDefault();
nav.pop();
} else if (this.defaultHref) {
openURL(this.defaultHref, ev, 'back');
openURL(this.win, this.defaultHref, ev, 'back');
}
}

View File

@@ -15,6 +15,8 @@ export class Backdrop {
private lastClick = -10000;
@Prop({ context: 'document' }) doc: Document;
/**
* If true, the backdrop will be visible. Defaults to `true`.
*/
@@ -36,11 +38,11 @@ export class Backdrop {
@Event() ionBackdropTap: EventEmitter;
componentDidLoad() {
registerBackdrop(this);
registerBackdrop(this.doc, this);
}
componentDidUnload() {
unregisterBackdrop(this);
unregisterBackdrop(this.doc, this);
}
@Listen('touchstart', {passive: false, capture: true})
@@ -80,14 +82,14 @@ export class Backdrop {
const BACKDROP_NO_SCROLL = 'backdrop-no-scroll';
const activeBackdrops = new Set();
function registerBackdrop(backdrop: any) {
function registerBackdrop(doc: Document, backdrop: any) {
activeBackdrops.add(backdrop);
document.body.classList.add(BACKDROP_NO_SCROLL);
doc.body.classList.add(BACKDROP_NO_SCROLL);
}
function unregisterBackdrop(backdrop: any) {
function unregisterBackdrop(doc: Document, backdrop: any) {
activeBackdrops.delete(backdrop);
if (activeBackdrops.size === 0) {
document.body.classList.remove(BACKDROP_NO_SCROLL);
doc.body.classList.remove(BACKDROP_NO_SCROLL);
}
}

View File

@@ -12,8 +12,11 @@ import { getButtonClassMap, getElementClassMap, openURL } from '../../utils/them
}
})
export class Button {
@Element() private el: HTMLElement;
@Prop({ context: 'window' }) win: Window;
@State() keyFocus: boolean;
/**
@@ -145,7 +148,7 @@ export class Button {
onFocus={this.onFocus.bind(this)}
onKeyUp={this.onKeyUp.bind(this)}
onBlur={this.onBlur.bind(this)}
onClick={(ev) => openURL(this.href, ev, this.routerDirection)}>
onClick={(ev) => openURL(this.win, this.href, ev, this.routerDirection)}>
<span class='button-inner'>
<slot name='icon-only'></slot>
<slot name='start'></slot>

View File

@@ -28,7 +28,7 @@ export class Fab {
*/
@Prop() edge = false;
@Prop({mutable: true}) activated = false;
@Prop({ mutable: true }) activated = false;
@Watch('activated')
activatedChanged() {
const activated = this.activated;

View File

@@ -15,7 +15,7 @@ export class HideWhen implements DisplayWhen {
@Element() element: HTMLElement;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'window'}) win: Window;
@Prop({ context: 'window' }) win: Window;
@Prop() orientation: string;
@Prop() mediaQuery: string;

View File

@@ -21,7 +21,8 @@ export function relocateInput(
// before it receives the actual focus event
// We hide the focused input (with the visible caret) invisiable by making it scale(0),
cloneInputComponent(componentEl, inputEl);
const tx = document.dir === 'rtl' ? 9999 : -9999;
const doc = componentEl.ownerDocument;
const tx = doc.dir === 'rtl' ? 9999 : -9999;
inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0)`;
// TODO
// inputEle.style.opacity = '0';
@@ -33,7 +34,7 @@ export function relocateInput(
export function isFocused(input: HTMLInputElement): boolean {
return input === document.activeElement;
return input === input.ownerDocument.activeElement;
}
function removeClone(componentEl: HTMLElement, inputEl: HTMLElement) {
@@ -56,6 +57,7 @@ function cloneInputComponent(componentEl: HTMLElement, inputEl: HTMLInputElement
// find its parent wrapping component like <ion-input> or <ion-textarea>
// then clone the entire component
const parentElement = componentEl.parentElement;
const doc = componentEl.ownerDocument;
if (componentEl && parentElement) {
// DOM READ
const srcTop = componentEl.offsetTop;
@@ -65,7 +67,7 @@ function cloneInputComponent(componentEl: HTMLElement, inputEl: HTMLInputElement
// DOM WRITE
// not using deep clone so we don't pull in unnecessary nodes
const clonedComponentEle = document.createElement('div');
const clonedComponentEle = doc.createElement('div');
const clonedStyle = clonedComponentEle.style;
clonedComponentEle.classList.add(...Array.from(componentEl.classList));
clonedComponentEle.classList.add('cloned-input');
@@ -77,7 +79,7 @@ function cloneInputComponent(componentEl: HTMLElement, inputEl: HTMLInputElement
clonedStyle.width = srcWidth + 'px';
clonedStyle.height = srcHeight + 'px';
const clonedInputEl = document.createElement('input');
const clonedInputEl = doc.createElement('input');
clonedInputEl.classList.add(...Array.from(inputEl.classList));
clonedInputEl.value = inputEl.value;
clonedInputEl.type = inputEl.type;

View File

@@ -1,7 +1,7 @@
const SKIP_BLURRING = ['INPUT', 'TEXTAREA', 'ION-INPUT', 'ION-TEXTAREA'];
export default function enableInputBlurring() {
export default function enableInputBlurring(doc: Document) {
console.debug('Input: enableInputBlurring');
let focused = true;
@@ -21,7 +21,7 @@ export default function enableInputBlurring() {
didScroll = false;
return;
}
const active = document.activeElement as HTMLElement;
const active = doc.activeElement as HTMLElement;
if (!active) {
return;
}
@@ -53,13 +53,13 @@ export default function enableInputBlurring() {
}, 50);
}
document.addEventListener('ionScrollStart', onScroll);
document.addEventListener('focusin', onFocusin, true);
document.addEventListener('touchend', onTouchend, false);
doc.addEventListener('ionScrollStart', onScroll);
doc.addEventListener('focusin', onFocusin, true);
doc.addEventListener('touchend', onTouchend, false);
return () => {
document.removeEventListener('ionScrollStart', onScroll, true);
document.removeEventListener('focusin', onFocusin, true);
document.removeEventListener('touchend', onTouchend, false);
doc.removeEventListener('ionScrollStart', onScroll, true);
doc.removeEventListener('focusin', onFocusin, true);
doc.removeEventListener('touchend', onTouchend, false);
};
}

View File

@@ -1,6 +1,6 @@
const PADDING_TIMER_KEY = '$ionPaddingTimer';
export default function enableScrollPadding(keyboardHeight: number) {
export default function enableScrollPadding(doc: Document, keyboardHeight: number) {
console.debug('Input: enableScrollPadding');
function onFocusin(ev: any) {
@@ -10,12 +10,12 @@ export default function enableScrollPadding(keyboardHeight: number) {
setScrollPadding(ev.target, 0);
}
document.addEventListener('focusin', onFocusin);
document.addEventListener('focusout', onFocusout);
doc.addEventListener('focusin', onFocusin);
doc.addEventListener('focusout', onFocusout);
return () => {
document.removeEventListener('focusin', onFocusin);
document.removeEventListener('focusout', onFocusout);
doc.removeEventListener('focusin', onFocusin);
doc.removeEventListener('focusout', onFocusout);
};
}

View File

@@ -23,7 +23,8 @@ export class InputShims {
private hideCaretMap = new WeakMap<HTMLElement, Function>();
private scrollAssistMap = new WeakMap<HTMLElement, Function>();
@Prop({context: 'config'}) config: Config;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'document' }) doc: Document;
componentDidLoad() {
this.keyboardHeight = this.config.getNumber('keyboardHeight', 290);
@@ -32,18 +33,18 @@ export class InputShims {
const inputBlurring = this.config.getBoolean('inputBlurring', true);
if (inputBlurring && INPUT_BLURRING) {
enableInputBlurring();
enableInputBlurring(this.doc);
}
const scrollPadding = this.config.getBoolean('scrollPadding', true);
if (scrollPadding && SCROLL_PADDING) {
enableScrollPadding(this.keyboardHeight);
enableScrollPadding(this.doc, this.keyboardHeight);
}
// Input might be already loaded in the DOM before ion-device-hacks did.
// At this point we need to look for all the ion-inputs not registered yet
// and register them.
const inputs = Array.from(document.querySelectorAll('ion-input'));
const inputs = Array.from(this.doc.querySelectorAll('ion-input'));
for (const input of inputs) {
this.registerInput(input);
}

View File

@@ -12,6 +12,8 @@ import { Side, isRightSide } from '../../utils/helpers';
export class ItemOptions {
@Element() private el: HTMLElement;
@Prop({ context: 'window' }) win: Window;
/**
* The side the option button should be on.
* Possible values: `"start"` and `"end"`.
@@ -27,7 +29,7 @@ export class ItemOptions {
@Method()
isRightSide() {
return isRightSide(this.side);
return isRightSide(this.win, this.side);
}
@Method()

View File

@@ -16,6 +16,8 @@ export class Item {
@Element() private el: HTMLStencilElement;
@Prop({ context: 'window' }) win: Window;
/**
* The color to use from your Sass `$colors` map.
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
@@ -125,7 +127,7 @@ export class Item {
<TagType
{...attrs}
class={themedClasses}
onClick={(ev) => openURL(this.href, ev, this.routerDirection)}>
onClick={(ev) => openURL(this.win, this.href, ev, this.routerDirection)}>
<slot name='start'></slot>
<div class='item-inner'>
<div class='input-wrapper'>

View File

@@ -1,4 +1,4 @@
import { Component, Listen, Method } from '@stencil/core';
import { Component, Listen, Method, Prop } from '@stencil/core';
import { LoadingOptions } from '../../index';
import { OverlayController, createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays';
@@ -10,6 +10,8 @@ export class LoadingController implements OverlayController {
private loadings = new Map<number, HTMLIonLoadingElement>();
@Prop({ context: 'document' }) doc: Document;
@Listen('body:ionLoadingWillPresent')
protected loadingWillPresent(ev: any) {
this.loadings.set(ev.target.overlayId, ev.target);
@@ -31,7 +33,7 @@ export class LoadingController implements OverlayController {
*/
@Method()
create(opts?: LoadingOptions): Promise<HTMLIonLoadingElement> {
return createOverlay(document.createElement('ion-loading'), opts);
return createOverlay(this.doc.createElement('ion-loading'), opts);
}
/*

View File

@@ -6,6 +6,8 @@ import { Component, Listen, Prop, State } from '@stencil/core';
})
export class MenuToggle {
@Prop({ context: 'document' }) doc: Document;
@State() visible = false;
/**
@@ -25,7 +27,7 @@ export class MenuToggle {
@Listen('child:click')
async onClick() {
const menuCtrl = await getMenuController();
const menuCtrl = await getMenuController(this.doc);
if (menuCtrl) {
const menu = menuCtrl.get(this.menu);
if (menu && menu.isActive()) {
@@ -38,7 +40,7 @@ export class MenuToggle {
@Listen('body:ionMenuChange')
@Listen('body:ionSplitPaneVisible')
async updateVisibility() {
const menuCtrl = await getMenuController();
const menuCtrl = await getMenuController(this.doc);
if (menuCtrl) {
const menu = menuCtrl.get(this.menu);
if (menu && menu.isActive()) {
@@ -60,8 +62,8 @@ export class MenuToggle {
}
function getMenuController(): Promise<HTMLIonMenuControllerElement|undefined> {
const menuControllerElement = document.querySelector('ion-menu-controller');
function getMenuController(doc: Document): Promise<HTMLIonMenuControllerElement|undefined> {
const menuControllerElement = doc.querySelector('ion-menu-controller');
if (!menuControllerElement) {
return Promise.resolve(undefined);
}

View File

@@ -37,7 +37,7 @@ export class Menu {
@Prop({ context: 'isServer' }) isServer: boolean;
@Prop({ connect: 'ion-menu-controller' }) lazyMenuCtrl: HTMLIonMenuControllerElement;
@Prop({ context: 'enableListener' }) enableListener: EventListenerEnable;
@Prop({ context: 'window' }) win: Window;
/**
* The content's id the menu should use.
*/
@@ -88,7 +88,7 @@ export class Menu {
@Watch('side')
protected sideChanged() {
this.isRightSide = isRightSide(this.side);
this.isRightSide = isRightSide(this.win, this.side);
}
/**
@@ -134,7 +134,7 @@ export class Menu {
}
const el = this.el;
const content = (this.contentId)
? document.getElementById(this.contentId)
? this.win.document.getElementById(this.contentId)
: el.parentElement && el.parentElement.querySelector('[main]');
if (!content || !content.tagName) {
@@ -275,7 +275,7 @@ export class Menu {
} else if (this.menuCtrl!.getOpen()) {
return false;
}
return checkEdgeSide(detail.currentX, this.isRightSide, this.maxEdgeStart);
return checkEdgeSide(this.win, detail.currentX, this.isRightSide, this.maxEdgeStart);
}
private onWillStart(): Promise<void> {
@@ -458,9 +458,9 @@ function computeDelta(deltaX: number, isOpen: boolean, isRightSide: boolean): nu
return Math.max(0, (isOpen !== isRightSide) ? -deltaX : deltaX);
}
function checkEdgeSide(posX: number, isRightSide: boolean, maxEdgeStart: number): boolean {
function checkEdgeSide(win: Window, posX: number, isRightSide: boolean, maxEdgeStart: number): boolean {
if (isRightSide) {
return posX >= window.innerWidth - maxEdgeStart;
return posX >= win.innerWidth - maxEdgeStart;
} else {
return posX <= maxEdgeStart;
}

View File

@@ -1,4 +1,4 @@
import { Component, Listen, Method } from '@stencil/core';
import { Component, Listen, Method, Prop } from '@stencil/core';
import { ModalOptions } from '../../index';
import { OverlayController, createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays';
@@ -10,6 +10,8 @@ export class ModalController implements OverlayController {
private modals = new Map<number, HTMLIonModalElement>();
@Prop({ context: 'document' }) doc: Document;
@Listen('body:ionModalWillPresent')
protected modalWillPresent(ev: any) {
this.modals.set(ev.target.overlayId, ev.target);
@@ -31,7 +33,7 @@ export class ModalController implements OverlayController {
*/
@Method()
create(opts?: ModalOptions): Promise<HTMLIonModalElement> {
return createOverlay(document.createElement('ion-modal'), opts);
return createOverlay(this.doc.createElement('ion-modal'), opts);
}
/*

View File

@@ -36,9 +36,9 @@ export class Nav implements NavOutlet {
@Element() el: HTMLElement;
@Prop({context: 'queue'}) queue: QueueController;
@Prop({context: 'config'}) config: Config;
@Prop({context: 'window'}) win: Window;
@Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'window' }) win: Window;
@Prop({ connect: 'ion-animation-controller' }) animationCtrl: HTMLIonAnimationControllerElement;
@Prop({ mutable: true }) swipeBackEnabled: boolean;
@@ -62,7 +62,7 @@ export class Nav implements NavOutlet {
@Event() ionNavDidChange: EventEmitter<void>;
componentWillLoad() {
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
this.useRouter = !!this.win.document.querySelector('ion-router') && !this.el.closest('[no-router]');
if (this.swipeBackEnabled === undefined) {
this.swipeBackEnabled = this.config.getBoolean('swipeBackEnabled', this.mode === 'ios');
}
@@ -312,7 +312,7 @@ export class Nav implements NavOutlet {
ti.resolve!(result.hasCompleted);
if (ti.opts!.updateURL !== false && this.useRouter) {
const router = document.querySelector('ion-router');
const router = this.win.document.querySelector('ion-router');
if (router) {
const direction = (result.direction === NavDirection.Back)
? RouterDirection.Back
@@ -714,7 +714,7 @@ export class Nav implements NavOutlet {
// set the transition animation's progress
const delta = detail.deltaX;
const stepValue = delta / window.innerWidth;
const stepValue = delta / this.win.innerWidth;
// set the transition animation's progress
this.sbTrns.progressStep(stepValue);
}
@@ -724,7 +724,7 @@ export class Nav implements NavOutlet {
if (this.sbTrns) {
// the swipe back gesture has ended
const delta = detail.deltaX;
const width = window.innerWidth;
const width = this.win.innerWidth;
const stepValue = delta / width;
const velocity = detail.velocityX;
const z = width / 2.0;

View File

@@ -1,4 +1,4 @@
import { Component, Listen, Method } from '@stencil/core';
import { Component, Listen, Method, Prop } from '@stencil/core';
import { PickerOptions } from '../../index';
import { OverlayController, createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays';
@@ -10,6 +10,8 @@ export class PickerController implements OverlayController {
private pickers = new Map<number, HTMLIonPickerElement>();
@Prop({ context: 'document' }) doc: Document;
@Listen('body:ionPickerWillPresent')
protected pickerWillPresent(ev: any) {
this.pickers.set(ev.target.overlayId, ev.target);
@@ -31,7 +33,7 @@ export class PickerController implements OverlayController {
*/
@Method()
create(opts?: PickerOptions): Promise<HTMLIonPickerElement> {
return createOverlay(document.createElement('ion-picker'), opts);
return createOverlay(this.doc.createElement('ion-picker'), opts);
}
/*

View File

@@ -1,4 +1,4 @@
import { Component, Listen, Method } from '@stencil/core';
import { Component, Listen, Method, Prop } from '@stencil/core';
import { PopoverOptions } from '../../index';
import { OverlayController, createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays';
@@ -9,6 +9,8 @@ export class PopoverController implements OverlayController {
private popovers = new Map<number, HTMLIonPopoverElement>();
@Prop({ context: 'document' }) doc: Document;
@Listen('body:ionPopoverWillPresent')
protected popoverWillPresent(ev: any) {
this.popovers.set(ev.target.overlayId, ev.target);
@@ -30,7 +32,7 @@ export class PopoverController implements OverlayController {
*/
@Method()
create(opts?: PopoverOptions): Promise<HTMLIonPopoverElement> {
return createOverlay(document.createElement('ion-popover'), opts);
return createOverlay(this.doc.createElement('ion-popover'), opts);
}
/*

View File

@@ -11,8 +11,9 @@ export class RippleEffect {
private lastClick = -10000;
@Element() el: HTMLElement;
@Prop({context: 'queue'}) queue: QueueController;
@Prop({context: 'enableListener'}) enableListener: EventListenerEnable;
@Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'enableListener' }) enableListener: EventListenerEnable;
@Prop({ context: 'document' }) doc: Document;
@Prop() tapClick = false;
@Watch('tapClick')
@@ -59,7 +60,7 @@ export class RippleEffect {
y = pageY - rect.top - (size / 2);
});
this.queue.write(() => {
const div = document.createElement('div');
const div = this.doc.createElement('div');
div.classList.add('ripple-effect');
const style = div.style;
const duration = Math.max(RIPPLE_FACTOR * Math.sqrt(size), MIN_RIPPLE_DURATION);

View File

@@ -21,9 +21,9 @@ export class RouterOutlet implements NavOutlet {
@Element() el: HTMLElement;
@Prop({context: 'config'}) config: Config;
@Prop({ context: 'config' }) config: Config;
@Prop({connect: 'ion-animation-controller'}) animationCtrl: HTMLIonAnimationControllerElement;
@Prop({context: 'window'}) window: Window;
@Prop({ context: 'window' }) win: Window;
@Prop({ mutable: true }) animated: boolean;
@Prop() animationBuilder: AnimationBuilder;
@@ -85,7 +85,7 @@ export class RouterOutlet implements NavOutlet {
animationCtrl: this.animationCtrl,
showGoBack: opts.showGoBack,
window: this.window,
window: this.win,
enteringEl: enteringEl,
leavingEl: leavingEl,
baseEl: this.el,

View File

@@ -24,6 +24,7 @@ export class Router {
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'window' }) win: Window;
@Prop() base = '';
@Prop() useHash = true;
@@ -84,12 +85,12 @@ export class Router {
}
private historyDirection() {
if (window.history.state === null) {
if (this.win.history.state === null) {
this.state++;
window.history.replaceState(this.state, document.title, document.location.href);
this.win.history.replaceState(this.state, this.win.document.title, this.win.document.location.href);
}
const state = window.history.state;
const state = this.win.history.state;
const lastState = this.lastState;
this.lastState = state;
@@ -107,7 +108,7 @@ export class Router {
if (this.busy) {
return false;
}
const { ids, outlet } = readNavState(document.body);
const { ids, outlet } = readNavState(this.win.document.body);
const chain = routerIDsToChain(ids, this.routes);
if (!chain) {
console.warn('[ion-router] no matching URL for ', ids.map(i => i.id));
@@ -155,7 +156,7 @@ export class Router {
path = redirect.to!;
}
const chain = routerPathToChain(path, this.routes);
const changed = await this.writeNavState(document.body, chain, direction);
const changed = await this.writeNavState(this.win.document.body, chain, direction);
if (changed) {
this.emitRouteChange(path, redirectFrom);
}
@@ -174,11 +175,11 @@ export class Router {
private setPath(path: string[], direction: RouterDirection) {
this.state++;
writePath(window.history, this.base, this.useHash, path, direction, this.state);
writePath(this.win.history, this.base, this.useHash, path, direction, this.state);
}
private getPath(): string[] | null {
return readPath(window.location, this.base, this.useHash);
return readPath(this.win.location, this.base, this.useHash);
}
private emitRouteChange(path: string[], redirectPath: string[]|null) {

View File

@@ -95,15 +95,15 @@ export class TestWindow2 {
export declare interface TestWindow2 extends Window {}
export function mockRouteElement(window: Window, path: string, component: string) {
const el = window.document.createElement('ion-route');
export function mockRouteElement(win: Window, path: string, component: string) {
const el = win.document.createElement('ion-route');
el.setAttribute('url', path);
(el as any).component = component;
return el;
}
export function mockRedirectElement(window: Window, from: string|undefined, to: string|undefined|null) {
const el = window.document.createElement('ion-route-redirect');
export function mockRedirectElement(win: Window, from: string|undefined, to: string|undefined|null) {
const el = win.document.createElement('ion-route-redirect');
if (from != null) {
el.setAttribute('from', from);
}

View File

@@ -21,8 +21,9 @@ export class Scroll {
@Element() private el: HTMLElement;
@Prop({ context: 'config'}) config: Config;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'window' }) win: Window;
@Prop() mode: string;
@@ -32,7 +33,7 @@ export class Scroll {
* If the content exceeds the bounds of ionScroll, nothing will change.
* Note, the does not disable the system bounce on iOS. That is an OS level setting.
*/
@Prop({mutable: true}) forceOverscroll: boolean;
@Prop({ mutable: true }) forceOverscroll: boolean;
@Prop() scrollEvents = false;
@@ -79,7 +80,7 @@ export class Scroll {
componentWillLoad() {
if (this.forceOverscroll === undefined) {
this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in window);
this.forceOverscroll = this.mode === 'ios' && ('ontouchstart' in this.win);
}
}

View File

@@ -21,6 +21,8 @@ export class Searchbar {
@Element() private el: HTMLElement;
@Prop({ context: 'document' }) doc: Document;
@State() activated = false;
@State() focused = false;
@@ -235,7 +237,7 @@ export class Searchbar {
* Positions the input placeholder
*/
positionPlaceholder() {
const isRTL = document.dir === 'rtl';
const isRTL = this.doc.dir === 'rtl';
const inputEl = this.el.querySelector('.searchbar-input') as HTMLInputElement;
const iconEl = this.el.querySelector('.searchbar-search-icon') as HTMLElement;
@@ -245,13 +247,14 @@ export class Searchbar {
} else {
// Create a dummy span to get the placeholder width
const tempSpan = document.createElement('span');
const doc = this.doc;
const tempSpan = doc.createElement('span');
tempSpan.innerHTML = this.placeholder;
document.body.appendChild(tempSpan);
doc.body.appendChild(tempSpan);
// Get the width of the span then remove it
const textWidth = tempSpan.offsetWidth;
document.body.removeChild(tempSpan);
tempSpan.remove();
// Calculate the input padding
const inputLeft = 'calc(50% - ' + (textWidth / 2) + 'px)';
@@ -274,7 +277,7 @@ export class Searchbar {
* Show the iOS Cancel button on focus, hide it offscreen otherwise
*/
positionCancelButton() {
const isRTL = document.dir === 'rtl';
const isRTL = this.doc.dir === 'rtl';
const cancelButton = this.el.querySelector('.searchbar-cancel-button-ios') as HTMLElement;
const shouldShowCancel = this.focused;

View File

@@ -15,7 +15,7 @@ export class ShowWhen implements DisplayWhen {
@Element() element: HTMLElement;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'window'}) win: Window;
@Prop({ context: 'window' }) win: Window;
@Prop() orientation: string;
@Prop() mediaQuery: string;

View File

@@ -29,7 +29,8 @@ export class SplitPane {
@Element() private el: HTMLElement;
@State() visible = false;
@Prop({context: 'isServer'}) isServer: boolean;
@Prop({ context: 'isServer'}) isServer: boolean;
@Prop({ context: 'window' }) win: Window;
/**
* If true, the split pane will be hidden. Defaults to `false`.
@@ -105,7 +106,7 @@ export class SplitPane {
// Listen on media query
const callback = (q: MediaQueryList) => this.visible = q.matches;
const mediaList = window.matchMedia(mediaQuery);
const mediaList = this.win.matchMedia(mediaQuery);
mediaList.addListener(callback);
this.rmL = () => mediaList.removeListener(callback);
this.visible = mediaList.matches;

View File

@@ -7,15 +7,16 @@ import { QueueController } from '../..';
export class StatusTap {
@Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'window' }) win: Window;
@Prop() duration = 300;
@Listen('window:statusTap')
onStatusTap() {
this.queue.read(() => {
const width = window.innerWidth;
const height = window.innerWidth;
const el = document.elementFromPoint(width / 2, height / 2);
const width = this.win.innerWidth;
const height = this.win.innerWidth;
const el = this.win.document.elementFromPoint(width / 2, height / 2);
if (!el) {
return;
}

View File

@@ -18,12 +18,15 @@ export class Tabbar {
@Element() el: HTMLElement;
@Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'document' }) doc: Document;
@State() canScrollLeft = false;
@State() canScrollRight = false;
@State() hidden = false;
@Prop({ context: 'queue' }) queue: QueueController;
@Prop() placement = 'bottom';
@Prop() selectedTab: HTMLIonTabElement;
@Prop() scrollable: boolean;
@@ -70,7 +73,7 @@ export class Tabbar {
}
protected analyzeTabs() {
const tabs: HTMLIonTabButtonElement[] = Array.from(document.querySelectorAll('ion-tab-button'));
const tabs: HTMLIonTabButtonElement[] = Array.from(this.doc.querySelectorAll('ion-tab-button'));
const scrollLeft = this.scrollEl.scrollLeft;
const tabsWidth = this.scrollEl.clientWidth;
let previous: {tab: HTMLIonTabButtonElement, amount: number}|undefined = undefined;

View File

@@ -20,6 +20,7 @@ export class Tabs implements NavOutlet {
@State() selectedTab: HTMLIonTabElement | undefined;
@Prop({ context: 'config' }) config: Config;
@Prop({ context: 'document' }) doc: Document;
/**
* The color to use from your Sass `$colors` map.
@@ -63,7 +64,7 @@ export class Tabs implements NavOutlet {
@Prop() scrollable = false;
@Prop({mutable: true}) useRouter: boolean;
@Prop({ mutable: true }) useRouter: boolean;
/**
* Emitted when the tab changes.
@@ -74,7 +75,7 @@ export class Tabs implements NavOutlet {
componentWillLoad() {
if (!this.useRouter) {
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
this.useRouter = !!this.doc.querySelector('ion-router') && !this.el.closest('[no-router]');
}
this.loadConfig('tabsPlacement', 'bottom');
@@ -96,7 +97,7 @@ export class Tabs implements NavOutlet {
protected tabChange(ev: CustomEvent<HTMLIonTabElement>) {
const selectedTab = ev.detail;
if (this.useRouter && selectedTab.href != null) {
const router = document.querySelector('ion-router');
const router = this.doc.querySelector('ion-router');
if (router) {
router.push(selectedTab.href);
}
@@ -256,7 +257,7 @@ export class Tabs implements NavOutlet {
private notifyRouter() {
if (this.useRouter) {
const router = document.querySelector('ion-router');
const router = this.doc.querySelector('ion-router');
if (router) {
return router.navChanged(RouterDirection.Forward);
}

View File

@@ -16,8 +16,8 @@ export class TapClick {
private clearDefers = new WeakMap<HTMLElement, any>();
@Prop({context: 'isServer'}) isServer: boolean;
@Prop({context: 'enableListener'}) enableListener: EventListenerEnable;
@Prop({ context: 'isServer'}) isServer: boolean;
@Prop({ context: 'enableListener'}) enableListener: EventListenerEnable;
@Element() el: HTMLElement;

View File

@@ -1,4 +1,4 @@
import { Component, Listen, Method } from '@stencil/core';
import { Component, Listen, Method, Prop } from '@stencil/core';
import { ToastOptions } from '../../index';
import { OverlayController, createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays';
@@ -10,6 +10,8 @@ export class ToastController implements OverlayController {
private toasts = new Map<number, HTMLIonToastElement>();
@Prop({ context: 'document' }) doc: Document;
@Listen('body:ionToastWillPresent')
protected toastWillPresent(ev: any) {
this.toasts.set(ev.target.overlayId, ev.target);
@@ -31,7 +33,7 @@ export class ToastController implements OverlayController {
*/
@Method()
create(opts?: ToastOptions): Promise<HTMLIonToastElement> {
return createOverlay(document.createElement('ion-toast'), opts);
return createOverlay(this.doc.createElement('ion-toast'), opts);
}
/*

View File

@@ -30,8 +30,9 @@ export class VirtualScroll {
@Element() el: HTMLStencilElement;
@Prop({context: 'queue'}) queue: QueueController;
@Prop({context: 'enableListener'}) enableListener: EventListenerEnable;
@Prop({ context: 'queue' }) queue: QueueController;
@Prop({ context: 'enableListener'}) enableListener: EventListenerEnable;
@Prop({ context: 'window' }) win: Window;
/**
@@ -268,7 +269,7 @@ export class VirtualScroll {
private updateCellHeight(cell: Cell, node: HTMLStencilElement | HTMLElement) {
const update = () => {
if ((node as any)['$ionCell'] === cell) {
const style = window.getComputedStyle(node);
const style = this.win.getComputedStyle(node);
const height = node.offsetHeight + parseFloat(style.getPropertyValue('margin-bottom'));
this.setCellHeight(cell, height);
}

View File

@@ -5,6 +5,7 @@ import { configFromURL, isIOS } from '../utils/platform';
const Ionic = (window as any).Ionic = (window as any).Ionic || {};
declare const Context: any;
debugger;
// queue used to coordinate DOM reads and
// write in order to avoid layout thrashing
Object.defineProperty(Ionic, 'queue', {

View File

@@ -43,8 +43,8 @@ export type Side = 'start' | 'end';
* @param side the side
* @param isRTL whether the application dir is rtl
*/
export function isRightSide(side: Side): boolean {
const isRTL = document.dir === 'rtl';
export function isRightSide(win: Window, side: Side): boolean {
const isRTL = win.document.dir === 'rtl';
switch (side) {
case 'start': return isRTL;
case 'end': return !isRTL;

View File

@@ -1,17 +1,12 @@
export function isTextInputFocused(): boolean {
const activeElement = document.activeElement;
export function isTextInputFocused(doc: Document): boolean {
const activeElement = doc.activeElement;
if (isTextInput(activeElement) && activeElement.parentElement) {
return activeElement.parentElement.querySelector(':focus') === activeElement;
}
return false;
}
export function closeKeyboard() {
const activeElement = document.activeElement as HTMLElement;
activeElement && activeElement.blur && activeElement.blur();
}
const NON_TEXT_INPUT_REGEX = /^(radio|checkbox|range|file|submit|reset|color|image|button)$/i;
function isTextInput(el: any) {

View File

@@ -1,7 +1,7 @@
export function waitUntilVisible(el: HTMLElement, callback?: Function) {
return new Promise((resolve) => {
if ('IntersectionObserver' in window) {
if (IntersectionObserver) {
const io = new IntersectionObserver(data => {
if (data[0].isIntersecting) {
resolve();

View File

@@ -19,7 +19,8 @@ export function createOverlay<T extends HTMLIonOverlayElement & Requires<keyof B
element.overlayId = lastId++;
// append the overlay element to the document body
const appRoot = document.querySelector('ion-app') || document.body;
const doc = element.ownerDocument;
const appRoot = doc.querySelector('ion-app') || doc.body;
appRoot.appendChild(element);
return element.componentOnReady();
@@ -110,7 +111,8 @@ async function overlayAnimation(
opts: any
): Promise<void> {
if (overlay.keyboardClose) {
closeKeyboard();
const activeElement = baseEl.ownerDocument.activeElement as HTMLElement;
activeElement && activeElement.blur && activeElement.blur();
}
if (overlay.animation) {
overlay.animation.destroy();
@@ -156,11 +158,6 @@ export function onceEvent(element: HTMLElement, eventName: string, callback: (ev
element.addEventListener(eventName, handler);
}
function closeKeyboard() {
const activeElement = document.activeElement as HTMLElement;
activeElement && activeElement.blur && activeElement.blur();
}
export function isCancel(role: string|undefined): boolean {
return role === 'cancel' || role === BACKDROP;
}

View File

@@ -24,15 +24,12 @@ export function isModeMatch(config: Config, multiModeString: string) {
return modes.indexOf(currentMode) >= 0;
}
export function isMediaQueryMatch(mediaQuery: string) {
return window.matchMedia(mediaQuery).matches;
}
export function isSizeMatch(multiSizeString: string) {
export function isSizeMatch(win: Window, multiSizeString: string) {
const sizes = multiSizeString.replace(/\s/g, '').split(',');
for (const size of sizes) {
const mediaQuery = SIZE_TO_MEDIA[size];
if (mediaQuery && window.matchMedia(mediaQuery).matches) {
if (mediaQuery && win.matchMedia(mediaQuery).matches) {
return true;
}
}
@@ -42,10 +39,10 @@ export function isSizeMatch(multiSizeString: string) {
export function getTestResult(displayWhen: DisplayWhen) {
const resultsToConsider: boolean[] = [];
if (displayWhen.mediaQuery) {
resultsToConsider.push(isMediaQueryMatch(displayWhen.mediaQuery));
resultsToConsider.push(displayWhen.win.matchMedia(displayWhen.mediaQuery).matches);
}
if (displayWhen.size) {
resultsToConsider.push(isSizeMatch(displayWhen.size));
resultsToConsider.push(isSizeMatch(displayWhen.win, displayWhen.size));
}
if (displayWhen.mode) {
resultsToConsider.push(isModeMatch(displayWhen.config, displayWhen.mode));
@@ -55,7 +52,7 @@ export function getTestResult(displayWhen: DisplayWhen) {
resultsToConsider.push(isPlatformMatch(platformNames, displayWhen.platform));
}
if (displayWhen.orientation) {
resultsToConsider.push(isOrientationMatch(displayWhen.orientation));
resultsToConsider.push(isOrientationMatch(displayWhen.win, displayWhen.orientation));
}
if (!resultsToConsider.length) {
@@ -72,18 +69,18 @@ export function getTestResult(displayWhen: DisplayWhen) {
});
}
export function isOrientationMatch(orientation: string) {
export function isOrientationMatch(win: Window, orientation: string) {
if (orientation === 'portrait') {
return isPortrait();
return isPortrait(win);
} else if (orientation === 'landscape') {
return !isPortrait();
return !isPortrait(win);
}
// it's an invalid orientation, so just return it
return false;
}
export function isPortrait(): boolean {
return window.matchMedia('(orientation: portrait)').matches;
export function isPortrait(win: Window): boolean {
return win.matchMedia('(orientation: portrait)').matches;
}
const SIZE_TO_MEDIA: any = {
@@ -145,6 +142,7 @@ export function detectPlatforms(win: Window, platforms: PlatformConfig[]) {
export interface DisplayWhen {
calculatedPlatforms: PlatformConfig[];
config: Config;
win: Window;
mediaQuery: string|undefined;
mode: string|undefined;
or: boolean;

View File

@@ -70,9 +70,9 @@ export function getClassMap(classes: string | string[] | undefined): CssClassMap
export type RouterDirection = 'forward' | 'back';
export async function openURL(url: string, ev: Event, direction: RouterDirection = 'forward') {
export async function openURL(win: Window, url: string, ev: Event, direction: RouterDirection = 'forward') {
if (url && url[0] !== '#' && url.indexOf('://') === -1) {
const router = document.querySelector('ion-router');
const router = win.document.querySelector('ion-router');
if (router) {
ev && ev.preventDefault();
await router.componentOnReady();