fix(ssr): fix global window and document references (#17590)

This commit is contained in:
Adam Bradley
2019-02-22 20:13:09 -06:00
committed by GitHub
parent 6bea9d3248
commit 4646f53ec7
18 changed files with 76 additions and 70 deletions

View File

@ -120,7 +120,7 @@ export class Button implements ComponentInterface {
if (form) { if (form) {
ev.preventDefault(); ev.preventDefault();
const fakeButton = document.createElement('button'); const fakeButton = this.win.document.createElement('button');
fakeButton.type = this.type; fakeButton.type = this.type;
fakeButton.style.display = 'none'; fakeButton.style.display = 'none';
form.appendChild(fakeButton); form.appendChild(fakeButton);

View File

@ -232,30 +232,25 @@ export class Col implements ComponentInterface {
}; };
} }
private calculateOffset() { private calculateOffset(isRTL: boolean) {
const isRTL = document.dir === 'rtl';
return this.calculatePosition('offset', isRTL ? 'margin-right' : 'margin-left'); return this.calculatePosition('offset', isRTL ? 'margin-right' : 'margin-left');
} }
private calculatePull() { private calculatePull(isRTL: boolean) {
const isRTL = document.dir === 'rtl';
return this.calculatePosition('pull', isRTL ? 'left' : 'right'); return this.calculatePosition('pull', isRTL ? 'left' : 'right');
} }
private calculatePush() { private calculatePush(isRTL: boolean) {
const isRTL = document.dir === 'rtl';
return this.calculatePosition('push', isRTL ? 'right' : 'left'); return this.calculatePosition('push', isRTL ? 'right' : 'left');
} }
hostData() { hostData() {
const isRTL = this.win.document.dir === 'rtl';
return { return {
style: { style: {
...this.calculateOffset(), ...this.calculateOffset(isRTL),
...this.calculatePull(), ...this.calculatePull(isRTL),
...this.calculatePush(), ...this.calculatePush(isRTL),
...this.calculateSize(), ...this.calculateSize(),
} }
}; };

View File

@ -274,10 +274,10 @@ export class ItemSliding implements ComponentInterface {
? SlidingState.Start | SlidingState.SwipeStart ? SlidingState.Start | SlidingState.SwipeStart
: SlidingState.Start; : SlidingState.Start;
} else { } else {
this.tmr = window.setTimeout(() => { this.tmr = setTimeout(() => {
this.state = SlidingState.Disabled; this.state = SlidingState.Disabled;
this.tmr = undefined; this.tmr = undefined;
}, 600); }, 600) as any;
openSlidingItem = undefined; openSlidingItem = undefined;
style.transform = ''; style.transform = '';

View File

@ -155,7 +155,7 @@ export class Menu implements ComponentInterface, MenuI {
const el = this.el; const el = this.el;
const parent = el.parentNode as any; const parent = el.parentNode as any;
const content = this.contentId !== undefined const content = this.contentId !== undefined
? document.getElementById(this.contentId) ? this.doc.getElementById(this.contentId)
: parent && parent.querySelector && parent.querySelector('[main]'); : parent && parent.querySelector && parent.querySelector('[main]');
if (!content || !content.tagName) { if (!content || !content.tagName) {

View File

@ -15,7 +15,7 @@ export function iosLeaveAnimation(AnimationC: Animation, baseEl: HTMLElement): P
const wrapperElRect = wrapperEl!.getBoundingClientRect(); const wrapperElRect = wrapperEl!.getBoundingClientRect();
wrapperAnimation.beforeStyles({ 'opacity': 1 }) wrapperAnimation.beforeStyles({ 'opacity': 1 })
.fromTo('translateY', '0%', `${window.innerHeight - wrapperElRect.top}px`); .fromTo('translateY', '0%', `${(baseEl.ownerDocument as any).defaultView.innerHeight - wrapperElRect.top}px`);
backdropAnimation.fromTo('opacity', 0.4, 0.0); backdropAnimation.fromTo('opacity', 0.4, 0.0);

View File

@ -12,8 +12,8 @@ export function iosEnterAnimation(AnimationC: Animation, baseEl: HTMLElement, ev
const contentWidth = contentDimentions.width; const contentWidth = contentDimentions.width;
const contentHeight = contentDimentions.height; const contentHeight = contentDimentions.height;
const bodyWidth = window.innerWidth; const bodyWidth = (baseEl.ownerDocument as any).defaultView.innerWidth;
const bodyHeight = window.innerHeight; const bodyHeight = (baseEl.ownerDocument as any).defaultView.innerHeight;
// If ev was passed, use that for target element // If ev was passed, use that for target element
const targetDim = ev && ev.target && (ev.target as HTMLElement).getBoundingClientRect(); const targetDim = ev && ev.target && (ev.target as HTMLElement).getBoundingClientRect();

View File

@ -4,7 +4,8 @@ import { Animation } from '../../../interface';
* Md Popover Enter Animation * Md Popover Enter Animation
*/ */
export function mdEnterAnimation(AnimationC: Animation, baseEl: HTMLElement, ev?: Event): Promise<Animation> { export function mdEnterAnimation(AnimationC: Animation, baseEl: HTMLElement, ev?: Event): Promise<Animation> {
const isRTL = document.dir === 'rtl'; const doc = (baseEl.ownerDocument as any);
const isRTL = doc.dir === 'rtl';
let originY = 'top'; let originY = 'top';
let originX = isRTL ? 'right' : 'left'; let originX = isRTL ? 'right' : 'left';
@ -14,8 +15,8 @@ export function mdEnterAnimation(AnimationC: Animation, baseEl: HTMLElement, ev?
const contentWidth = contentDimentions.width; const contentWidth = contentDimentions.width;
const contentHeight = contentDimentions.height; const contentHeight = contentDimentions.height;
const bodyWidth = window.innerWidth; const bodyWidth = doc.defaultView.innerWidth;
const bodyHeight = window.innerHeight; const bodyHeight = doc.defaultView.innerHeight;
// If ev was passed, use that for target element // If ev was passed, use that for target element
const targetDim = const targetDim =

View File

@ -27,6 +27,7 @@ export class Range implements ComponentInterface {
@Element() el!: HTMLStencilElement; @Element() el!: HTMLStencilElement;
@Prop({ context: 'queue' }) queue!: QueueApi; @Prop({ context: 'queue' }) queue!: QueueApi;
@Prop({ context: 'document' }) doc!: Document;
@State() private ratioA = 0; @State() private ratioA = 0;
@State() private ratioB = 0; @State() private ratioB = 0;
@ -240,7 +241,7 @@ export class Range implements ComponentInterface {
// figure out which knob they started closer to // figure out which knob they started closer to
let ratio = clamp(0, (currentX - rect.left) / rect.width, 1); let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
if (document.dir === 'rtl') { if (this.doc.dir === 'rtl') {
ratio = 1 - ratio; ratio = 1 - ratio;
} }
@ -270,7 +271,7 @@ export class Range implements ComponentInterface {
// update the knob being interacted with // update the knob being interacted with
const rect = this.rect; const rect = this.rect;
let ratio = clamp(0, (currentX - rect.left) / rect.width, 1); let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
if (document.dir === 'rtl') { if (this.doc.dir === 'rtl') {
ratio = 1 - ratio; ratio = 1 - ratio;
} }
@ -368,7 +369,8 @@ export class Range implements ComponentInterface {
const barStart = `${ratioLower * 100}%`; const barStart = `${ratioLower * 100}%`;
const barEnd = `${100 - ratioUpper * 100}%`; const barEnd = `${100 - ratioUpper * 100}%`;
const isRTL = document.dir === 'rtl'; const doc = this.doc;
const isRTL = doc.dir === 'rtl';
const start = isRTL ? 'right' : 'left'; const start = isRTL ? 'right' : 'left';
const end = isRTL ? 'left' : 'right'; const end = isRTL ? 'left' : 'right';
@ -426,7 +428,7 @@ export class Range implements ComponentInterface {
style={barStyle()} style={barStyle()}
/> />
{ renderKnob({ { renderKnob(isRTL, {
knob: 'A', knob: 'A',
pressed: this.pressedKnob === 'A', pressed: this.pressedKnob === 'A',
value: this.valA, value: this.valA,
@ -438,7 +440,7 @@ export class Range implements ComponentInterface {
max max
})} })}
{ this.dualKnobs && renderKnob({ { this.dualKnobs && renderKnob(isRTL, {
knob: 'B', knob: 'B',
pressed: this.pressedKnob === 'B', pressed: this.pressedKnob === 'B',
value: this.valB, value: this.valB,
@ -468,8 +470,7 @@ interface RangeKnob {
handleKeyboard: (name: KnobName, isIncrease: boolean) => void; handleKeyboard: (name: KnobName, isIncrease: boolean) => void;
} }
function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }: RangeKnob) { function renderKnob(isRTL: boolean, { knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }: RangeKnob) {
const isRTL = document.dir === 'rtl';
const start = isRTL ? 'right' : 'left'; const start = isRTL ? 'right' : 'left';
const knobStyle = () => { const knobStyle = () => {

View File

@ -92,7 +92,7 @@ export class Router implements ComponentInterface {
@Method() @Method()
push(url: string, direction: RouterDirection = 'forward') { push(url: string, direction: RouterDirection = 'forward') {
if (url.startsWith('.')) { if (url.startsWith('.')) {
url = (new URL(url, window.location.href)).pathname; url = (new URL(url, this.win.location.href)).pathname;
} }
console.debug('[ion-router] URL pushed -> updating nav', url, direction); console.debug('[ion-router] URL pushed -> updating nav', url, direction);

View File

@ -116,15 +116,18 @@ export class SplitPane implements ComponentInterface {
return; return;
} }
if ((this.win as any).matchMedia) {
// Listen on media query // Listen on media query
const callback = (q: MediaQueryList) => { const callback = (q: MediaQueryList) => {
this.visible = q.matches; this.visible = q.matches;
}; };
const mediaList = this.win.matchMedia(mediaQuery); const mediaList = this.win.matchMedia(mediaQuery);
mediaList.addListener(callback as any); (mediaList as any).addListener(callback as any);
this.rmL = () => mediaList.removeListener(callback as any); this.rmL = () => (mediaList as any).removeListener(callback as any);
this.visible = mediaList.matches; this.visible = mediaList.matches;
} }
}
private isPane(element: HTMLElement): boolean { private isPane(element: HTMLElement): boolean {
if (!this.visible) { if (!this.visible) {

View File

@ -24,6 +24,8 @@ export class Toggle implements ComponentInterface {
@Prop({ context: 'queue' }) queue!: QueueApi; @Prop({ context: 'queue' }) queue!: QueueApi;
@Prop({ context: 'document' }) doc!: Document;
@State() activated = false; @State() activated = false;
/** /**
@ -146,7 +148,7 @@ export class Toggle implements ComponentInterface {
} }
private onMove(detail: GestureDetail) { private onMove(detail: GestureDetail) {
if (shouldToggle(this.checked, detail.deltaX, -10)) { if (shouldToggle(this.doc, this.checked, detail.deltaX, -10)) {
this.checked = !this.checked; this.checked = !this.checked;
hapticSelection(); hapticSelection();
} }
@ -222,8 +224,8 @@ export class Toggle implements ComponentInterface {
} }
} }
function shouldToggle(checked: boolean, deltaX: number, margin: number): boolean { function shouldToggle(doc: HTMLDocument, checked: boolean, deltaX: number, margin: number): boolean {
const isRTL = document.dir === 'rtl'; const isRTL = doc.dir === 'rtl';
if (checked) { if (checked) {
return (!isRTL && (margin > deltaX)) || return (!isRTL && (margin > deltaX)) ||

View File

@ -34,26 +34,25 @@ export class Config {
} }
} }
export function configFromSession(): any { export function configFromSession(win: Window): any {
try { try {
const configStr = window.sessionStorage.getItem(IONIC_SESSION_KEY); const configStr = win.sessionStorage.getItem(IONIC_SESSION_KEY);
return configStr !== null ? JSON.parse(configStr) : {}; return configStr !== null ? JSON.parse(configStr) : {};
} catch (e) { } catch (e) {
return {}; return {};
} }
} }
export function saveConfig(config: any) { export function saveConfig(win: Window, config: any) {
try { try {
window.sessionStorage.setItem(IONIC_SESSION_KEY, JSON.stringify(config)); win.sessionStorage.setItem(IONIC_SESSION_KEY, JSON.stringify(config));
} catch (e) { } catch (e) {
return; return;
} }
} }
export function configFromURL() { export function configFromURL(win: Window) {
const config: any = {}; const config: any = {};
const win = window;
win.location.search.slice(1) win.location.search.slice(1)
.split('&') .split('&')
.map(entry => entry.split('=')) .map(entry => entry.split('='))

View File

@ -4,10 +4,12 @@ import { isPlatform, setupPlatforms } from '../utils/platform';
import { Config, configFromSession, configFromURL, saveConfig } from './config'; import { Config, configFromSession, configFromURL, saveConfig } from './config';
const win = window;
const Ionic = (win as any)['Ionic'] = (win as any)['Ionic'] || {};
declare const Context: any; declare const Context: any;
const win = Context['window'] ? Context['window'] : typeof (window as any) !== 'undefined' ? window : {} as Window;
const Ionic = (win as any)['Ionic'] = (win as any)['Ionic'] || {};
// queue used to coordinate DOM reads and // queue used to coordinate DOM reads and
// write in order to avoid layout thrashing // write in order to avoid layout thrashing
Object.defineProperty(Ionic, 'queue', { Object.defineProperty(Ionic, 'queue', {
@ -21,25 +23,27 @@ Context.isPlatform = isPlatform;
// create the Ionic.config from raw config object (if it exists) // create the Ionic.config from raw config object (if it exists)
// and convert Ionic.config into a ConfigApi that has a get() fn // and convert Ionic.config into a ConfigApi that has a get() fn
const configObj = { const configObj = {
...configFromSession(), ...configFromSession(win),
persistConfig: false, persistConfig: false,
...Ionic['config'], ...Ionic['config'],
...configFromURL() ...configFromURL(win)
}; };
const config = Ionic['config'] = Context['config'] = new Config(configObj); const config = Ionic['config'] = Context['config'] = new Config(configObj);
if (config.getBoolean('persistConfig')) { if (config.getBoolean('persistConfig')) {
saveConfig(configObj); saveConfig(win, configObj);
} }
// first see if the mode was set as an attribute on <html> // first see if the mode was set as an attribute on <html>
// which could have been set by the user, or by prerendering // which could have been set by the user, or by prerendering
// otherwise get the mode via config settings, and fallback to md // otherwise get the mode via config settings, and fallback to md
const documentElement = document.documentElement!; const documentElement = win.document ? win.document.documentElement : null;
const mode = config.get('mode', documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md')); const mode = config.get('mode', (documentElement && documentElement.getAttribute('mode')) || (isPlatform(win, 'ios') ? 'ios' : 'md'));
Ionic.mode = Context.mode = mode; Ionic.mode = Context.mode = mode;
config.set('mode', mode); config.set('mode', mode);
documentElement.setAttribute('mode', mode); if (documentElement) {
documentElement.classList.add(mode); documentElement.setAttribute('mode', mode);
documentElement.classList.add(mode);
}
if (config.getBoolean('_testing')) { if (config.getBoolean('_testing')) {
config.set('animated', false); config.set('animated', false);
} }

View File

@ -25,8 +25,9 @@ export const TRANSFORM_PROPS: {[key: string]: number} = {
'perspective': 1 'perspective': 1
}; };
const raf = (window as any).requestAnimationFrame const win = typeof (window as any) !== 'undefined' ? window : {};
? window.requestAnimationFrame.bind(window) const raf = (win as any).requestAnimationFrame
? (win as Window).requestAnimationFrame.bind(win)
: (f: FrameRequestCallback) => f(Date.now()); : (f: FrameRequestCallback) => f(Date.now());
export class Animator { export class Animator {

View File

@ -14,7 +14,7 @@ export function getScrollData(componentEl: HTMLElement, contentEl: HTMLElement,
itemEl.getBoundingClientRect(), itemEl.getBoundingClientRect(),
contentEl.getBoundingClientRect(), contentEl.getBoundingClientRect(),
keyboardHeight, keyboardHeight,
window.innerHeight (componentEl as any).ownerDocument.defaultView.innerHeight
); );
} }

View File

@ -14,6 +14,9 @@ export function matchBreakpoint(win: Window, breakpoint: string | undefined) {
if (breakpoint === undefined || breakpoint === '') { if (breakpoint === undefined || breakpoint === '') {
return true; return true;
} }
if ((win as any).matchMedia) {
const mediaQuery = SIZE_TO_MEDIA[breakpoint]; const mediaQuery = SIZE_TO_MEDIA[breakpoint];
return win.matchMedia(mediaQuery).matches; return win.matchMedia(mediaQuery).matches;
}
return false;
} }

View File

@ -32,8 +32,7 @@ export function setupPlatforms(win: any) {
let platforms: string[] | undefined | null = win.Ionic.platforms; let platforms: string[] | undefined | null = win.Ionic.platforms;
if (platforms == null) { if (platforms == null) {
platforms = win.Ionic.platforms = detectPlatforms(win); platforms = win.Ionic.platforms = detectPlatforms(win);
const classList = win.document.documentElement.classList; platforms.forEach(p => win.document.documentElement.classList.add(`plt-${p}`));
platforms.forEach(p => classList.add(`plt-${p}`));
} }
return platforms; return platforms;
} }
@ -93,13 +92,11 @@ function isHybrid(win: Window) {
return isCordova(win) || isCapacitorNative(win); return isCordova(win) || isCapacitorNative(win);
} }
function isCordova(window: Window): boolean { function isCordova(win: any): boolean {
const win = window as any;
return !!(win['cordova'] || win['phonegap'] || win['PhoneGap']); return !!(win['cordova'] || win['phonegap'] || win['PhoneGap']);
} }
function isCapacitorNative(window: Window): boolean { function isCapacitorNative(win: any): boolean {
const win = window as any;
const capacitor = win['Capacitor']; const capacitor = win['Capacitor'];
return !!(capacitor && capacitor.isNative); return !!(capacitor && capacitor.isNative);
} }
@ -116,6 +113,6 @@ export function testUserAgent(win: Window, expr: RegExp) {
return expr.test(win.navigator.userAgent); return expr.test(win.navigator.userAgent);
} }
function matchMedia(win: Window, query: string): boolean { function matchMedia(win: any, query: string): boolean {
return win.matchMedia(query).matches; return win.matchMedia ? win.matchMedia(query).matches : false;
} }

View File

@ -15,7 +15,7 @@ export function shadow<T extends Element>(el: T): ShadowRoot | T {
export function iosTransitionAnimation(AnimationC: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise<Animation> { export function iosTransitionAnimation(AnimationC: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise<Animation> {
const isRTL = document.dir === 'rtl'; const isRTL = (navEl.ownerDocument as any).dir === 'rtl';
const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%'; const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%';
const OFF_LEFT = isRTL ? '33%' : '-33%'; const OFF_LEFT = isRTL ? '33%' : '-33%';