mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-12 12:02:29 +03:00
no class Environment
This commit is contained in:
@@ -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
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user