From 4646f53ec7ab39a2e89f0c59a427b6b61ea7788e Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Fri, 22 Feb 2019 20:13:09 -0600 Subject: [PATCH] fix(ssr): fix global window and document references (#17590) --- core/src/components/button/button.tsx | 2 +- core/src/components/col/col.tsx | 19 ++++++---------- .../components/item-sliding/item-sliding.tsx | 4 ++-- core/src/components/menu/menu.tsx | 2 +- .../components/modal/animations/ios.leave.ts | 2 +- .../popover/animations/ios.enter.ts | 4 ++-- .../components/popover/animations/md.enter.ts | 7 +++--- core/src/components/range/range.tsx | 15 +++++++------ core/src/components/router/router.tsx | 2 +- core/src/components/split-pane/split-pane.tsx | 19 +++++++++------- core/src/components/toggle/toggle.tsx | 8 ++++--- core/src/global/config.ts | 11 +++++----- core/src/global/ionic-global.ts | 22 +++++++++++-------- core/src/utils/animation/animator.ts | 5 +++-- .../utils/input-shims/hacks/scroll-data.ts | 2 +- core/src/utils/media.ts | 7 ++++-- core/src/utils/platform.ts | 13 +++++------ core/src/utils/transition/ios.transition.ts | 2 +- 18 files changed, 76 insertions(+), 70 deletions(-) diff --git a/core/src/components/button/button.tsx b/core/src/components/button/button.tsx index 0d550c19eb..b4eed33c31 100644 --- a/core/src/components/button/button.tsx +++ b/core/src/components/button/button.tsx @@ -120,7 +120,7 @@ export class Button implements ComponentInterface { if (form) { ev.preventDefault(); - const fakeButton = document.createElement('button'); + const fakeButton = this.win.document.createElement('button'); fakeButton.type = this.type; fakeButton.style.display = 'none'; form.appendChild(fakeButton); diff --git a/core/src/components/col/col.tsx b/core/src/components/col/col.tsx index 0cb8a62890..d3bd78cea7 100644 --- a/core/src/components/col/col.tsx +++ b/core/src/components/col/col.tsx @@ -232,30 +232,25 @@ export class Col implements ComponentInterface { }; } - private calculateOffset() { - const isRTL = document.dir === 'rtl'; - + private calculateOffset(isRTL: boolean) { return this.calculatePosition('offset', isRTL ? 'margin-right' : 'margin-left'); } - private calculatePull() { - const isRTL = document.dir === 'rtl'; - + private calculatePull(isRTL: boolean) { return this.calculatePosition('pull', isRTL ? 'left' : 'right'); } - private calculatePush() { - const isRTL = document.dir === 'rtl'; - + private calculatePush(isRTL: boolean) { return this.calculatePosition('push', isRTL ? 'right' : 'left'); } hostData() { + const isRTL = this.win.document.dir === 'rtl'; return { style: { - ...this.calculateOffset(), - ...this.calculatePull(), - ...this.calculatePush(), + ...this.calculateOffset(isRTL), + ...this.calculatePull(isRTL), + ...this.calculatePush(isRTL), ...this.calculateSize(), } }; diff --git a/core/src/components/item-sliding/item-sliding.tsx b/core/src/components/item-sliding/item-sliding.tsx index f7fac0c67d..ab1d0772d5 100644 --- a/core/src/components/item-sliding/item-sliding.tsx +++ b/core/src/components/item-sliding/item-sliding.tsx @@ -274,10 +274,10 @@ export class ItemSliding implements ComponentInterface { ? SlidingState.Start | SlidingState.SwipeStart : SlidingState.Start; } else { - this.tmr = window.setTimeout(() => { + this.tmr = setTimeout(() => { this.state = SlidingState.Disabled; this.tmr = undefined; - }, 600); + }, 600) as any; openSlidingItem = undefined; style.transform = ''; diff --git a/core/src/components/menu/menu.tsx b/core/src/components/menu/menu.tsx index 725a85e54d..8496c10e72 100644 --- a/core/src/components/menu/menu.tsx +++ b/core/src/components/menu/menu.tsx @@ -155,7 +155,7 @@ export class Menu implements ComponentInterface, MenuI { const el = this.el; const parent = el.parentNode as any; const content = this.contentId !== undefined - ? document.getElementById(this.contentId) + ? this.doc.getElementById(this.contentId) : parent && parent.querySelector && parent.querySelector('[main]'); if (!content || !content.tagName) { diff --git a/core/src/components/modal/animations/ios.leave.ts b/core/src/components/modal/animations/ios.leave.ts index 3fedaa1571..9712046694 100644 --- a/core/src/components/modal/animations/ios.leave.ts +++ b/core/src/components/modal/animations/ios.leave.ts @@ -15,7 +15,7 @@ export function iosLeaveAnimation(AnimationC: Animation, baseEl: HTMLElement): P const wrapperElRect = wrapperEl!.getBoundingClientRect(); 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); diff --git a/core/src/components/popover/animations/ios.enter.ts b/core/src/components/popover/animations/ios.enter.ts index a8142eee48..3f29ca6303 100644 --- a/core/src/components/popover/animations/ios.enter.ts +++ b/core/src/components/popover/animations/ios.enter.ts @@ -12,8 +12,8 @@ export function iosEnterAnimation(AnimationC: Animation, baseEl: HTMLElement, ev const contentWidth = contentDimentions.width; const contentHeight = contentDimentions.height; - const bodyWidth = window.innerWidth; - const bodyHeight = window.innerHeight; + const bodyWidth = (baseEl.ownerDocument as any).defaultView.innerWidth; + const bodyHeight = (baseEl.ownerDocument as any).defaultView.innerHeight; // If ev was passed, use that for target element const targetDim = ev && ev.target && (ev.target as HTMLElement).getBoundingClientRect(); diff --git a/core/src/components/popover/animations/md.enter.ts b/core/src/components/popover/animations/md.enter.ts index 554ad308e4..39c420faf6 100644 --- a/core/src/components/popover/animations/md.enter.ts +++ b/core/src/components/popover/animations/md.enter.ts @@ -4,7 +4,8 @@ import { Animation } from '../../../interface'; * Md Popover Enter Animation */ export function mdEnterAnimation(AnimationC: Animation, baseEl: HTMLElement, ev?: Event): Promise { - const isRTL = document.dir === 'rtl'; + const doc = (baseEl.ownerDocument as any); + const isRTL = doc.dir === 'rtl'; let originY = 'top'; let originX = isRTL ? 'right' : 'left'; @@ -14,8 +15,8 @@ export function mdEnterAnimation(AnimationC: Animation, baseEl: HTMLElement, ev? const contentWidth = contentDimentions.width; const contentHeight = contentDimentions.height; - const bodyWidth = window.innerWidth; - const bodyHeight = window.innerHeight; + const bodyWidth = doc.defaultView.innerWidth; + const bodyHeight = doc.defaultView.innerHeight; // If ev was passed, use that for target element const targetDim = diff --git a/core/src/components/range/range.tsx b/core/src/components/range/range.tsx index d84a8e095a..20d8c4c20e 100644 --- a/core/src/components/range/range.tsx +++ b/core/src/components/range/range.tsx @@ -27,6 +27,7 @@ export class Range implements ComponentInterface { @Element() el!: HTMLStencilElement; @Prop({ context: 'queue' }) queue!: QueueApi; + @Prop({ context: 'document' }) doc!: Document; @State() private ratioA = 0; @State() private ratioB = 0; @@ -240,7 +241,7 @@ export class Range implements ComponentInterface { // figure out which knob they started closer to let ratio = clamp(0, (currentX - rect.left) / rect.width, 1); - if (document.dir === 'rtl') { + if (this.doc.dir === 'rtl') { ratio = 1 - ratio; } @@ -270,7 +271,7 @@ export class Range implements ComponentInterface { // update the knob being interacted with const rect = this.rect; let ratio = clamp(0, (currentX - rect.left) / rect.width, 1); - if (document.dir === 'rtl') { + if (this.doc.dir === 'rtl') { ratio = 1 - ratio; } @@ -368,7 +369,8 @@ export class Range implements ComponentInterface { const barStart = `${ratioLower * 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 end = isRTL ? 'left' : 'right'; @@ -426,7 +428,7 @@ export class Range implements ComponentInterface { style={barStyle()} /> - { renderKnob({ + { renderKnob(isRTL, { knob: 'A', pressed: this.pressedKnob === 'A', value: this.valA, @@ -438,7 +440,7 @@ export class Range implements ComponentInterface { max })} - { this.dualKnobs && renderKnob({ + { this.dualKnobs && renderKnob(isRTL, { knob: 'B', pressed: this.pressedKnob === 'B', value: this.valB, @@ -468,8 +470,7 @@ interface RangeKnob { handleKeyboard: (name: KnobName, isIncrease: boolean) => void; } -function renderKnob({ knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }: RangeKnob) { - const isRTL = document.dir === 'rtl'; +function renderKnob(isRTL: boolean, { knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }: RangeKnob) { const start = isRTL ? 'right' : 'left'; const knobStyle = () => { diff --git a/core/src/components/router/router.tsx b/core/src/components/router/router.tsx index c5a32bf4ec..c28fada1fd 100644 --- a/core/src/components/router/router.tsx +++ b/core/src/components/router/router.tsx @@ -92,7 +92,7 @@ export class Router implements ComponentInterface { @Method() push(url: string, direction: RouterDirection = 'forward') { 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); diff --git a/core/src/components/split-pane/split-pane.tsx b/core/src/components/split-pane/split-pane.tsx index db9d112f5f..7d702e6ede 100644 --- a/core/src/components/split-pane/split-pane.tsx +++ b/core/src/components/split-pane/split-pane.tsx @@ -116,14 +116,17 @@ export class SplitPane implements ComponentInterface { return; } - // Listen on media query - const callback = (q: MediaQueryList) => { - this.visible = q.matches; - }; - const mediaList = this.win.matchMedia(mediaQuery); - mediaList.addListener(callback as any); - this.rmL = () => mediaList.removeListener(callback as any); - this.visible = mediaList.matches; + if ((this.win as any).matchMedia) { + // Listen on media query + const callback = (q: MediaQueryList) => { + this.visible = q.matches; + }; + + const mediaList = this.win.matchMedia(mediaQuery); + (mediaList as any).addListener(callback as any); + this.rmL = () => (mediaList as any).removeListener(callback as any); + this.visible = mediaList.matches; + } } private isPane(element: HTMLElement): boolean { diff --git a/core/src/components/toggle/toggle.tsx b/core/src/components/toggle/toggle.tsx index 96ad07a898..dd5989e440 100644 --- a/core/src/components/toggle/toggle.tsx +++ b/core/src/components/toggle/toggle.tsx @@ -24,6 +24,8 @@ export class Toggle implements ComponentInterface { @Prop({ context: 'queue' }) queue!: QueueApi; + @Prop({ context: 'document' }) doc!: Document; + @State() activated = false; /** @@ -146,7 +148,7 @@ export class Toggle implements ComponentInterface { } private onMove(detail: GestureDetail) { - if (shouldToggle(this.checked, detail.deltaX, -10)) { + if (shouldToggle(this.doc, this.checked, detail.deltaX, -10)) { this.checked = !this.checked; hapticSelection(); } @@ -222,8 +224,8 @@ export class Toggle implements ComponentInterface { } } -function shouldToggle(checked: boolean, deltaX: number, margin: number): boolean { - const isRTL = document.dir === 'rtl'; +function shouldToggle(doc: HTMLDocument, checked: boolean, deltaX: number, margin: number): boolean { + const isRTL = doc.dir === 'rtl'; if (checked) { return (!isRTL && (margin > deltaX)) || diff --git a/core/src/global/config.ts b/core/src/global/config.ts index 6b16b0a900..f5b4522d21 100644 --- a/core/src/global/config.ts +++ b/core/src/global/config.ts @@ -34,26 +34,25 @@ export class Config { } } -export function configFromSession(): any { +export function configFromSession(win: Window): any { try { - const configStr = window.sessionStorage.getItem(IONIC_SESSION_KEY); + const configStr = win.sessionStorage.getItem(IONIC_SESSION_KEY); return configStr !== null ? JSON.parse(configStr) : {}; } catch (e) { return {}; } } -export function saveConfig(config: any) { +export function saveConfig(win: Window, config: any) { try { - window.sessionStorage.setItem(IONIC_SESSION_KEY, JSON.stringify(config)); + win.sessionStorage.setItem(IONIC_SESSION_KEY, JSON.stringify(config)); } catch (e) { return; } } -export function configFromURL() { +export function configFromURL(win: Window) { const config: any = {}; - const win = window; win.location.search.slice(1) .split('&') .map(entry => entry.split('=')) diff --git a/core/src/global/ionic-global.ts b/core/src/global/ionic-global.ts index 9d2ad38b01..0ef1b44b74 100644 --- a/core/src/global/ionic-global.ts +++ b/core/src/global/ionic-global.ts @@ -4,10 +4,12 @@ import { isPlatform, setupPlatforms } from '../utils/platform'; import { Config, configFromSession, configFromURL, saveConfig } from './config'; -const win = window; -const Ionic = (win as any)['Ionic'] = (win as any)['Ionic'] || {}; 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 // write in order to avoid layout thrashing Object.defineProperty(Ionic, 'queue', { @@ -21,25 +23,27 @@ Context.isPlatform = isPlatform; // create the Ionic.config from raw config object (if it exists) // and convert Ionic.config into a ConfigApi that has a get() fn const configObj = { - ...configFromSession(), + ...configFromSession(win), persistConfig: false, ...Ionic['config'], - ...configFromURL() + ...configFromURL(win) }; const config = Ionic['config'] = Context['config'] = new Config(configObj); if (config.getBoolean('persistConfig')) { - saveConfig(configObj); + saveConfig(win, configObj); } // first see if the mode was set as an attribute on // which could have been set by the user, or by prerendering // otherwise get the mode via config settings, and fallback to md -const documentElement = document.documentElement!; -const mode = config.get('mode', documentElement.getAttribute('mode') || (isPlatform(win, 'ios') ? 'ios' : 'md')); +const documentElement = win.document ? win.document.documentElement : null; +const mode = config.get('mode', (documentElement && documentElement.getAttribute('mode')) || (isPlatform(win, 'ios') ? 'ios' : 'md')); Ionic.mode = Context.mode = mode; config.set('mode', mode); -documentElement.setAttribute('mode', mode); -documentElement.classList.add(mode); +if (documentElement) { + documentElement.setAttribute('mode', mode); + documentElement.classList.add(mode); +} if (config.getBoolean('_testing')) { config.set('animated', false); } diff --git a/core/src/utils/animation/animator.ts b/core/src/utils/animation/animator.ts index 5601e85faf..26b2601ff6 100644 --- a/core/src/utils/animation/animator.ts +++ b/core/src/utils/animation/animator.ts @@ -25,8 +25,9 @@ export const TRANSFORM_PROPS: {[key: string]: number} = { 'perspective': 1 }; -const raf = (window as any).requestAnimationFrame - ? window.requestAnimationFrame.bind(window) +const win = typeof (window as any) !== 'undefined' ? window : {}; +const raf = (win as any).requestAnimationFrame + ? (win as Window).requestAnimationFrame.bind(win) : (f: FrameRequestCallback) => f(Date.now()); export class Animator { diff --git a/core/src/utils/input-shims/hacks/scroll-data.ts b/core/src/utils/input-shims/hacks/scroll-data.ts index 0ed021a187..dd97ac7971 100644 --- a/core/src/utils/input-shims/hacks/scroll-data.ts +++ b/core/src/utils/input-shims/hacks/scroll-data.ts @@ -14,7 +14,7 @@ export function getScrollData(componentEl: HTMLElement, contentEl: HTMLElement, itemEl.getBoundingClientRect(), contentEl.getBoundingClientRect(), keyboardHeight, - window.innerHeight + (componentEl as any).ownerDocument.defaultView.innerHeight ); } diff --git a/core/src/utils/media.ts b/core/src/utils/media.ts index f3348fbe11..6665589c0d 100644 --- a/core/src/utils/media.ts +++ b/core/src/utils/media.ts @@ -14,6 +14,9 @@ export function matchBreakpoint(win: Window, breakpoint: string | undefined) { if (breakpoint === undefined || breakpoint === '') { return true; } - const mediaQuery = SIZE_TO_MEDIA[breakpoint]; - return win.matchMedia(mediaQuery).matches; + if ((win as any).matchMedia) { + const mediaQuery = SIZE_TO_MEDIA[breakpoint]; + return win.matchMedia(mediaQuery).matches; + } + return false; } diff --git a/core/src/utils/platform.ts b/core/src/utils/platform.ts index 73db61d130..fdc61fc36b 100644 --- a/core/src/utils/platform.ts +++ b/core/src/utils/platform.ts @@ -32,8 +32,7 @@ export function setupPlatforms(win: any) { let platforms: string[] | undefined | null = win.Ionic.platforms; if (platforms == null) { platforms = win.Ionic.platforms = detectPlatforms(win); - const classList = win.document.documentElement.classList; - platforms.forEach(p => classList.add(`plt-${p}`)); + platforms.forEach(p => win.document.documentElement.classList.add(`plt-${p}`)); } return platforms; } @@ -93,13 +92,11 @@ function isHybrid(win: Window) { return isCordova(win) || isCapacitorNative(win); } -function isCordova(window: Window): boolean { - const win = window as any; +function isCordova(win: any): boolean { return !!(win['cordova'] || win['phonegap'] || win['PhoneGap']); } -function isCapacitorNative(window: Window): boolean { - const win = window as any; +function isCapacitorNative(win: any): boolean { const capacitor = win['Capacitor']; return !!(capacitor && capacitor.isNative); } @@ -116,6 +113,6 @@ export function testUserAgent(win: Window, expr: RegExp) { return expr.test(win.navigator.userAgent); } -function matchMedia(win: Window, query: string): boolean { - return win.matchMedia(query).matches; +function matchMedia(win: any, query: string): boolean { + return win.matchMedia ? win.matchMedia(query).matches : false; } diff --git a/core/src/utils/transition/ios.transition.ts b/core/src/utils/transition/ios.transition.ts index 92c95c228b..645c0064ec 100644 --- a/core/src/utils/transition/ios.transition.ts +++ b/core/src/utils/transition/ios.transition.ts @@ -15,7 +15,7 @@ export function shadow(el: T): ShadowRoot | T { export function iosTransitionAnimation(AnimationC: Animation, navEl: HTMLElement, opts: TransitionOptions): Promise { - const isRTL = document.dir === 'rtl'; + const isRTL = (navEl.ownerDocument as any).dir === 'rtl'; const OFF_RIGHT = isRTL ? '-99.5%' : '99.5%'; const OFF_LEFT = isRTL ? '33%' : '-33%';