no class Environment

This commit is contained in:
Rene
2020-11-21 19:18:31 +01:00
parent 4987652d9d
commit 946fa55a1b
8 changed files with 131 additions and 142 deletions
@@ -7,19 +7,29 @@ import {
absoluteCoordinates,
offsetSize,
scrollLeft,
jsAPI,
XY,
removeAttr,
removeElements,
windowSize,
runEach,
} from 'support';
type OnEnvironmentChanged = (env: Environment) => void;
export type OnEnvironmentChanged = (env: Environment) => void;
export interface Environment {
_autoUpdateLoop: boolean;
_nativeScrollbarSize: XY;
_nativeScrollbarIsOverlaid: XY<boolean>;
_nativeScrollbarStyling: boolean;
_rtlScrollBehavior: { n: boolean; i: boolean };
_addListener(listener: OnEnvironmentChanged): void;
_removeListener(listener: OnEnvironmentChanged): void;
}
let environmentInstance: Environment;
const { abs, round } = Math;
const envornmentElmId = 'os-envornment';
const environmentElmId = 'os-environment';
const nativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => {
const getNativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => {
appendChildren(body, measureElm);
const cSize = clientSize(measureElm);
const oSize = offsetSize(measureElm);
@@ -30,7 +40,7 @@ const nativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY =>
};
};
const nativeScrollbarStyling = (testElm: HTMLElement): boolean => {
const getNativeScrollbarStyling = (testElm: HTMLElement): boolean => {
let result = false;
addClass(testElm, 'os-viewport-native-scrollbars-invisible');
try {
@@ -41,7 +51,7 @@ const nativeScrollbarStyling = (testElm: HTMLElement): boolean => {
return result;
};
const rtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { i: boolean; n: boolean } => {
const getRtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { i: boolean; n: boolean } => {
const strHidden = 'hidden';
style(parentElm, { overflowX: strHidden, overflowY: strHidden, direction: 'rtl' });
scrollLeft(parentElm, 0);
@@ -68,26 +78,7 @@ const rtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { i:
};
};
const passiveEvents = (): boolean => {
let supportsPassive = false;
try {
/* eslint-disable */
// @ts-ignore
window.addEventListener(
'test',
null,
Object.defineProperty({}, 'passive', {
get: function () {
supportsPassive = true;
},
})
);
/* eslint-enable */
} catch (e) {}
return supportsPassive;
};
const windowDPR = (): number => {
const getWindowDPR = (): number => {
// eslint-disable-next-line
// @ts-ignore
const dDPI = window.screen.deviceXDPI || 0;
@@ -103,100 +94,88 @@ const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => {
return !(absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo);
};
export class Environment {
#onChangedListener: Set<OnEnvironmentChanged> = new Set();
const createEnvironment = (): Environment => {
const { body } = document;
const envDOM = createDOM(`<div id="${environmentElmId}"><div></div></div>`);
const envElm = envDOM[0] as HTMLElement;
const envChildElm = envElm.firstChild as HTMLElement;
_autoUpdateLoop!: boolean;
const onChangedListener: Set<OnEnvironmentChanged> = new Set();
const nativeScrollBarSize = getNativeScrollbarSize(body, envElm);
const nativeScrollbarIsOverlaid = {
x: nativeScrollBarSize.x === 0,
y: nativeScrollBarSize.y === 0,
};
_nativeScrollbarSize!: XY;
const env: Environment = {
_autoUpdateLoop: false,
_nativeScrollbarSize: nativeScrollBarSize,
_nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid,
_nativeScrollbarStyling: getNativeScrollbarStyling(envElm),
_rtlScrollBehavior: getRtlScrollBehavior(envElm, envChildElm),
_addListener(listener: OnEnvironmentChanged): void {
onChangedListener.add(listener);
},
_removeListener(listener: OnEnvironmentChanged): void {
onChangedListener.delete(listener);
},
};
_nativeScrollbarIsOverlaid!: XY<boolean>;
removeAttr(envElm, 'style');
removeElements(envElm);
_nativeScrollbarStyling!: boolean;
if (!nativeScrollbarIsOverlaid.x || !nativeScrollbarIsOverlaid.y) {
let size = windowSize();
let dpr = getWindowDPR();
let scrollbarSize = nativeScrollBarSize;
_rtlScrollBehavior!: { n: boolean; i: boolean };
window.addEventListener('resize', () => {
if (onChangedListener.size) {
const sizeNew = windowSize();
const deltaSize = {
w: sizeNew.w - size.w,
h: sizeNew.h - size.h,
};
_supportPassiveEvents!: boolean;
if (deltaSize.w === 0 && deltaSize.h === 0) return;
_supportResizeObserver!: boolean;
const deltaAbsSize = {
w: abs(deltaSize.w),
h: abs(deltaSize.h),
};
const deltaAbsRatio = {
w: abs(round(sizeNew.w / (size.w / 100.0))),
h: abs(round(sizeNew.h / (size.h / 100.0))),
};
const dprNew = getWindowDPR();
const deltaIsBigger = deltaAbsSize.w > 2 && deltaAbsSize.h > 2;
const difference = !diffBiggerThanOne(deltaAbsRatio.w, deltaAbsRatio.h);
const dprChanged = dprNew !== dpr && dpr > 0;
const isZoom = deltaIsBigger && difference && dprChanged;
constructor() {
const _self = this;
const { body } = document;
const envDOM = createDOM(`<div id="${envornmentElmId}"><div></div></div>`);
const envElm = envDOM[0] as HTMLElement;
const envChildElm = envElm.firstChild as HTMLElement;
if (isZoom) {
const newScrollbarSize = (environmentInstance._nativeScrollbarSize = getNativeScrollbarSize(body, envElm));
removeElements(envElm);
const nScrollBarSize = nativeScrollbarSize(body, envElm);
const nativeScrollbarIsOverlaid = {
x: nScrollBarSize.x === 0,
y: nScrollBarSize.y === 0,
};
_self._autoUpdateLoop = false;
_self._nativeScrollbarSize = nScrollBarSize;
_self._nativeScrollbarIsOverlaid = nativeScrollbarIsOverlaid;
_self._nativeScrollbarStyling = nativeScrollbarStyling(envElm);
_self._rtlScrollBehavior = rtlScrollBehavior(envElm, envChildElm);
_self._supportPassiveEvents = passiveEvents();
_self._supportResizeObserver = !!jsAPI('ResizeObserver');
removeAttr(envElm, 'style');
removeElements(envElm);
if (!nativeScrollbarIsOverlaid.x || !nativeScrollbarIsOverlaid.y) {
let size = windowSize();
let dpr = windowDPR();
const onChangedListener = this.#onChangedListener;
window.addEventListener('resize', () => {
if (onChangedListener.size) {
const sizeNew = windowSize();
const deltaSize = {
w: sizeNew.w - size.w,
h: sizeNew.h - size.h,
};
if (deltaSize.w === 0 && deltaSize.h === 0) return;
const deltaAbsSize = {
w: abs(deltaSize.w),
h: abs(deltaSize.h),
};
const deltaAbsRatio = {
w: abs(round(sizeNew.w / (size.w / 100.0))),
h: abs(round(sizeNew.h / (size.h / 100.0))),
};
const dprNew = windowDPR();
const deltaIsBigger = deltaAbsSize.w > 2 && deltaAbsSize.h > 2;
const difference = !diffBiggerThanOne(deltaAbsRatio.w, deltaAbsRatio.h);
const dprChanged = dprNew !== dpr && dpr > 0;
const isZoom = deltaIsBigger && difference && dprChanged;
const oldScrollbarSize = _self._nativeScrollbarSize;
let newScrollbarSize;
if (isZoom) {
newScrollbarSize = _self._nativeScrollbarSize = nativeScrollbarSize(body, envElm);
removeElements(envElm);
if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) {
onChangedListener.forEach((listener) => listener && listener(_self));
}
if (scrollbarSize.x !== newScrollbarSize.x || scrollbarSize.y !== newScrollbarSize.y) {
runEach(onChangedListener);
}
size = sizeNew;
dpr = dprNew;
scrollbarSize = newScrollbarSize;
}
});
}
size = sizeNew;
dpr = dprNew;
}
});
}
addListener(listener: OnEnvironmentChanged): void {
this.#onChangedListener.add(listener);
}
return env;
};
removeListener(listener: OnEnvironmentChanged): void {
this.#onChangedListener.delete(listener);
export const getEnvironment = (): Environment => {
if (!environmentInstance) {
environmentInstance = createEnvironment();
}
}
return environmentInstance;
};
@@ -1,6 +1,6 @@
@import './sizeobserver.scss';
#os-envornment {
#os-environment {
position: fixed;
opacity: 0;
visibility: hidden;
@@ -8,14 +8,14 @@
height: 500px;
width: 500px;
}
#os-envornment > div {
#os-environment > div {
width: 200%;
height: 200%;
margin: 10px 0;
}
/* fix restricted measuring */
#os-envornment:before,
#os-envornment:after,
#os-environment:before,
#os-environment:after,
.os-content:before,
.os-content:after {
content: '';
@@ -28,17 +28,17 @@
flex-shrink: 0;
visibility: hidden;
}
#os-envornment,
#os-environment,
.os-viewport {
-ms-overflow-style: scrollbar !important;
}
.os-viewport-native-scrollbars-invisible#os-envornment,
.os-viewport-native-scrollbars-invisible#os-environment,
.os-viewport-native-scrollbars-invisible.os-viewport {
scrollbar-width: none !important;
}
.os-viewport-native-scrollbars-invisible#os-envornment::-webkit-scrollbar,
.os-viewport-native-scrollbars-invisible#os-environment::-webkit-scrollbar,
.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar,
.os-viewport-native-scrollbars-invisible#os-envornment::-webkit-scrollbar-corner,
.os-viewport-native-scrollbars-invisible#os-environment::-webkit-scrollbar-corner,
.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar-corner {
display: none !important;
width: 0px !important;
@@ -13,7 +13,7 @@ import {
preventDefault,
stopPropagation,
} from 'support';
import { Environment } from 'environment';
import { getEnvironment } from 'environment';
const animationStartEventName = 'animationstart';
const scrollEventName = 'scroll';
@@ -29,15 +29,9 @@ const getDirection = (elm: HTMLElement) => style(elm, 'direction');
// TODO:
// 1. MAYBE add comparison function to offsetSize etc.
// 2. remove supportPassiveListeners & resizeobserver from environment
export const createSizeObserver = (
target: HTMLElement,
onSizeChangedCallback: (direction?: boolean) => any,
environment: Environment,
direction?: boolean
) => {
const rtlScrollBehavior = environment._rtlScrollBehavior;
export const createSizeObserver = (target: HTMLElement, onSizeChangedCallback: (direction?: boolean) => any, direction?: boolean) => {
const rtlScrollBehavior = getEnvironment()._rtlScrollBehavior;
const baseElements = createDOM(`<div class="${classNameSizeObserver}"><div class="${classNameSizeObserverListener}"></div></div>`);
const sizeObserver = baseElements[0] as HTMLElement;
const listenerElement = sizeObserver.firstChild as HTMLElement;
@@ -1,6 +1,8 @@
import { isArrayLike } from 'support/utils/types';
import { PlainObject } from 'typings';
type RunEachItem = ((...args: any) => any | any[]) | null | undefined;
/**
* Iterates through a array or object
* @param arrayLikeOrObject The array or object through which shall be iterated.
@@ -28,7 +30,7 @@ export function each(obj: PlainObject, callback: (value: any, indexOrKey: string
export function each(obj: PlainObject | null, callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | void): PlainObject | null;
export function each<T>(
source: ArrayLike<T> | PlainObject | null,
callback: (value: T | any, indexOrKey: any, source: any) => boolean | void
callback: (value: T, indexOrKey: any, source: any) => boolean | void
): Array<T> | ReadonlyArray<T> | ArrayLike<T> | PlainObject | null {
if (isArrayLike(source)) {
for (let i = 0; i < source.length; i++) {
@@ -66,9 +68,13 @@ export const from = <T = any>(arr: ArrayLike<T>) => {
};
/**
* Calls all functions in the passed array of functions.
* Calls all functions in the passed array/set of functions.
* @param arr The array filled with function which shall be called.
*/
export const runEach = (arr: Array<((...args: any) => any | any[]) | null | undefined>): void => {
each(arr, (fn) => fn && fn());
export const runEach = (arr: ArrayLike<RunEachItem> | Set<RunEachItem>): void => {
if (arr instanceof Set) {
arr.forEach((fn) => fn && fn());
} else {
each(arr, (fn) => fn && fn());
}
};
@@ -192,18 +192,30 @@ describe('array utilities', () => {
});
});
describe('runEach', () => {
test('array', () => {
const arr = [jest.fn(), null, jest.fn(), undefined, jest.fn()];
runEach(arr);
arr.forEach((fn) => {
if (fn) {
expect(fn).toHaveBeenCalled();
}
});
});
test('set', () => {
const set = new Set([jest.fn(), null, jest.fn(), undefined, jest.fn()]);
runEach(set);
set.forEach((fn) => {
if (fn) {
expect(fn).toHaveBeenCalled();
}
});
});
});
test('indexOf', () => {
const idx = indexOf([1, 2, 3], 2);
expect(idx).toBe(1);
});
test('runEach', () => {
const arr = [jest.fn(), null, jest.fn(), undefined, jest.fn()];
runEach(arr);
arr.forEach((fn) => {
if (fn) {
expect(fn).toHaveBeenCalled();
}
});
});
});
@@ -1,7 +1,7 @@
import 'overlayscrollbars.scss';
import { Environment } from 'environment';
import { getEnvironment } from 'environment';
const envInstance = new Environment();
const envInstance = getEnvironment();
document.body.textContent = JSON.stringify(envInstance);
export { envInstance };
@@ -6,7 +6,6 @@ import { generateSelectCallback, iterateSelect } from '@/testing-browser/Select'
import { setTestResult } from '@/testing-browser/TestResult';
import { hasDimensions, offsetSize, WH, style } from 'support';
import { Environment } from 'environment';
import { createSizeObserver } from 'overlayscrollbars/observers/SizeObserver';
let sizeIterations = 0;
@@ -171,7 +170,6 @@ createSizeObserver(
}
});
},
new Environment(),
true
);