size observer

This commit is contained in:
Rene
2020-10-18 23:32:21 +02:00
parent 845eac9640
commit 24eadd5f1d
38 changed files with 741 additions and 102 deletions
+2
View File
@@ -14,6 +14,8 @@ module.exports = function (api) {
[ [
'@babel/preset-env', '@babel/preset-env',
{ {
useBuiltIns: 'usage',
corejs: { version: 3, proposals: true },
targets: { targets: {
node: 'current', node: 'current',
}, },
+119 -20
View File
@@ -12,6 +12,45 @@ const rollupNodeEnv = 'build';
const cacheFilePrefix = 'jest-puppeteer-overlayscrollbars-cache-'; const cacheFilePrefix = 'jest-puppeteer-overlayscrollbars-cache-';
const cacheEncoding = 'utf8'; const cacheEncoding = 'utf8';
const cacheHash = 'md5'; const cacheHash = 'md5';
const legacyBabelConfigAssign = {
exclude: [/\/core-js\//],
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3, proposals: true },
},
],
],
};
const mergeBabelConfigs = (currentConfig, mergeConfig) => {
const { presets: assignPresets, exclude: assignExclude } = mergeConfig;
const { presets: configPresets, exclude: configExclude } = currentConfig;
assignPresets.forEach((assignPreset) => {
if (Array.isArray(assignPreset)) {
const [assignName, assignConfig] = assignPreset;
configPresets.forEach((configPreset) => {
if (Array.isArray(configPreset)) {
const [configName, configConfig] = configPreset;
if (configName === assignName && typeof configConfig === 'object' && typeof assignConfig === 'object') {
Object.assign(configConfig, {
...assignConfig,
});
}
}
});
}
});
const finalAssignExclude = Array.isArray(assignExclude) ? assignExclude : [assignExclude];
const finalConfigExclude = Array.isArray(configExclude) ? configExclude : [configExclude, ...finalAssignExclude];
currentConfig.exclude = finalConfigExclude.filter((exc) => !!exc);
};
const makeHtmlAttributes = (attributes) => { const makeHtmlAttributes = (attributes) => {
if (!attributes) { if (!attributes) {
@@ -40,11 +79,68 @@ const genHtmlTemplateFunc = (content) => ({ attributes, files, meta, publicPath,
<head> <head>
${metas} ${metas}
<title>${title}</title> <title>${title}</title>
<style>
html,
body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}
body {
padding: 10px;
}
*::before,
*::after {
box-sizing: border-box;
}
* {
box-sizing: inherit;
}
#testResult {
display: none;
position: fixed;
top: 0;
right: 0;
padding: 5px;
background: white;
}
#testResult.passed {
display: block;
background: lime;
}
#testResult.passed::before {
content: 'success';
}
#testResult.failed {
display: block;
background: red;
}
#testResult.failed::before {
content: 'failed';
}
</style>
${links} ${links}
</head> </head>
<body> <body>
${content || ''} ${content || ''}
${scripts} ${scripts}
<div id="testResult"></div>
<script>
var testResultElm = document.getElementById('testResult');
window.setTestResult = function(result) {
if (typeof result === 'boolean') {
testResultElm.setAttribute('class', result ? 'passed' : 'failed');
}
else {
testResultElm.removeAttribute('class');
}
};
window.testPassed = function() {
return testResultElm.getAttribute('class') === 'passed';
}
</script>
</body> </body>
</html>`; </html>`;
}; };
@@ -112,26 +208,29 @@ const setupRollupTest = async (rootDir, testPath, cacheDir) => {
let rollupConfigObj = rollupConfig(undefined, { let rollupConfigObj = rollupConfig(undefined, {
project: rootDir, project: rootDir,
overwrite: (rollupConfigDefaults) => ({ overwrite: (rollupConfigDefaults, legacyBabelConfig) => {
input: path.resolve(testDir, deploymentConfig.js.input), mergeBabelConfigs(legacyBabelConfig, legacyBabelConfigAssign);
dist: path.resolve(testDir, deploymentConfig.build), return {
file: deploymentConfig.js.output, input: path.resolve(testDir, deploymentConfig.js.input),
types: null, dist: path.resolve(testDir, deploymentConfig.build),
minVersions: false, file: deploymentConfig.js.output,
esmBuild: false, types: null,
sourcemap: true, minVersions: false,
name: testName, esmBuild: false,
pipeline: [ sourcemap: true,
rollupPluginStyles(), name: testName,
...rollupConfigDefaults.pipeline, pipeline: [
rollupPluginHtml({ rollupPluginStyles(),
title: `Jest-Puppeteer: ${testName}`, ...rollupConfigDefaults.pipeline,
fileName: deploymentConfig.html.output, rollupPluginHtml({
template: genHtmlTemplateFunc(htmlFileContent), title: `Jest-Puppeteer: ${testName}`,
meta: [{ charset: 'utf-8' }, { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }], fileName: deploymentConfig.html.output,
}), template: genHtmlTemplateFunc(htmlFileContent),
], meta: [{ charset: 'utf-8' }, { 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }],
}), }),
],
};
},
silent: true, silent: true,
fast: true, fast: true,
}); });
+3
View File
@@ -9,6 +9,9 @@ process.env.TEST_SERVER_PORT = port;
module.exports = { module.exports = {
browser: 'chromium', browser: 'chromium',
browserContext: 'incognito', browserContext: 'incognito',
launch: {
headless: false,
},
server: { server: {
command: `cross-env TEST_SERVER_PORT=${port} node ${testServerPath}`, command: `cross-env TEST_SERVER_PORT=${port} node ${testServerPath}`,
port, port,
+1
View File
@@ -22,6 +22,7 @@
"babel-jest": "^26.0.1", "babel-jest": "^26.0.1",
"bufferutil": "^4.0.1", "bufferutil": "^4.0.1",
"canvas": "^2.6.1", "canvas": "^2.6.1",
"core-js": "^3.6.5",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"del": "^5.1.0", "del": "^5.1.0",
"eslint": "^7.5.0", "eslint": "^7.5.0",
+12 -10
View File
@@ -335,6 +335,7 @@ const optionsTemplateTypes = ['boolean', 'number', 'string', 'array', 'object',
}, {}); }, {});
const { abs, round } = Math; const { abs, round } = Math;
const envornmentElmId = 'os-envornment';
const nativeScrollbarSize = (body, measureElm) => { const nativeScrollbarSize = (body, measureElm) => {
appendChildren(body, measureElm); appendChildren(body, measureElm);
@@ -363,6 +364,7 @@ const rtlScrollBehavior = (parentElm, childElm) => {
style(parentElm, { style(parentElm, {
overflowX: strHidden, overflowX: strHidden,
overflowY: strHidden, overflowY: strHidden,
direction: 'rtl',
}); });
scrollLeft(parentElm, 0); scrollLeft(parentElm, 0);
const parentOffset = absoluteCoordinates(parentElm); const parentOffset = absoluteCoordinates(parentElm);
@@ -419,7 +421,7 @@ class Environment {
const _self = this; const _self = this;
const { body } = document; const { body } = document;
const envDOM = createDOM('<div id="os-dummy-scrollbar-size"><div></div></div>'); const envDOM = createDOM(`<div id="${envornmentElmId}"><div></div></div>`);
const envElm = envDOM[0]; const envElm = envDOM[0];
const envChildElm = envElm.firstChild; const envChildElm = envElm.firstChild;
const nScrollBarSize = nativeScrollbarSize(body, envElm); const nScrollBarSize = nativeScrollbarSize(body, envElm);
@@ -427,13 +429,13 @@ class Environment {
x: nScrollBarSize.x === 0, x: nScrollBarSize.x === 0,
y: nScrollBarSize.y === 0, y: nScrollBarSize.y === 0,
}; };
_self.autoUpdateLoop = false; _self._autoUpdateLoop = false;
_self.nativeScrollbarSize = nScrollBarSize; _self._nativeScrollbarSize = nScrollBarSize;
_self.nativeScrollbarIsOverlaid = nativeScrollbarIsOverlaid; _self._nativeScrollbarIsOverlaid = nativeScrollbarIsOverlaid;
_self.nativeScrollbarStyling = nativeScrollbarStyling(envElm); _self._nativeScrollbarStyling = nativeScrollbarStyling(envElm);
_self.rtlScrollBehavior = rtlScrollBehavior(envElm, envChildElm); _self._rtlScrollBehavior = rtlScrollBehavior(envElm, envChildElm);
_self.supportPassiveEvents = passiveEvents(); _self._supportPassiveEvents = passiveEvents();
_self.supportResizeObserver = !!jsAPI('ResizeObserver'); _self._supportResizeObserver = !!jsAPI('ResizeObserver');
removeAttr(envElm, 'style'); removeAttr(envElm, 'style');
removeElements(envElm); removeElements(envElm);
@@ -464,11 +466,11 @@ class Environment {
const difference = !diffBiggerThanOne(deltaAbsRatio.w, deltaAbsRatio.h); const difference = !diffBiggerThanOne(deltaAbsRatio.w, deltaAbsRatio.h);
const dprChanged = dprNew !== dpr && dpr > 0; const dprChanged = dprNew !== dpr && dpr > 0;
const isZoom = deltaIsBigger && difference && dprChanged; const isZoom = deltaIsBigger && difference && dprChanged;
const oldScrollbarSize = _self.nativeScrollbarSize; const oldScrollbarSize = _self._nativeScrollbarSize;
let newScrollbarSize; let newScrollbarSize;
if (isZoom) { if (isZoom) {
newScrollbarSize = _self.nativeScrollbarSize = nativeScrollbarSize(body, envElm); newScrollbarSize = _self._nativeScrollbarSize = nativeScrollbarSize(body, envElm);
removeElements(envElm); removeElements(envElm);
if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) { if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+12 -10
View File
@@ -377,6 +377,7 @@
var abs = Math.abs, var abs = Math.abs,
round = Math.round; round = Math.round;
var envornmentElmId = 'os-envornment';
var nativeScrollbarSize = function nativeScrollbarSize(body, measureElm) { var nativeScrollbarSize = function nativeScrollbarSize(body, measureElm) {
appendChildren(body, measureElm); appendChildren(body, measureElm);
@@ -406,6 +407,7 @@
style(parentElm, { style(parentElm, {
overflowX: strHidden, overflowX: strHidden,
overflowY: strHidden, overflowY: strHidden,
direction: 'rtl',
}); });
scrollLeft(parentElm, 0); scrollLeft(parentElm, 0);
var parentOffset = absoluteCoordinates(parentElm); var parentOffset = absoluteCoordinates(parentElm);
@@ -463,7 +465,7 @@
var _document = document, var _document = document,
body = _document.body; body = _document.body;
var envDOM = createDOM('<div id="os-dummy-scrollbar-size"><div></div></div>'); var envDOM = createDOM('<div id="' + envornmentElmId + '"><div></div></div>');
var envElm = envDOM[0]; var envElm = envDOM[0];
var envChildElm = envElm.firstChild; var envChildElm = envElm.firstChild;
var nScrollBarSize = nativeScrollbarSize(body, envElm); var nScrollBarSize = nativeScrollbarSize(body, envElm);
@@ -471,13 +473,13 @@
x: nScrollBarSize.x === 0, x: nScrollBarSize.x === 0,
y: nScrollBarSize.y === 0, y: nScrollBarSize.y === 0,
}; };
_self.autoUpdateLoop = false; _self._autoUpdateLoop = false;
_self.nativeScrollbarSize = nScrollBarSize; _self._nativeScrollbarSize = nScrollBarSize;
_self.nativeScrollbarIsOverlaid = nativeScrollbarIsOverlaid; _self._nativeScrollbarIsOverlaid = nativeScrollbarIsOverlaid;
_self.nativeScrollbarStyling = nativeScrollbarStyling(envElm); _self._nativeScrollbarStyling = nativeScrollbarStyling(envElm);
_self.rtlScrollBehavior = rtlScrollBehavior(envElm, envChildElm); _self._rtlScrollBehavior = rtlScrollBehavior(envElm, envChildElm);
_self.supportPassiveEvents = passiveEvents(); _self._supportPassiveEvents = passiveEvents();
_self.supportResizeObserver = !!jsAPI('ResizeObserver'); _self._supportResizeObserver = !!jsAPI('ResizeObserver');
removeAttr(envElm, 'style'); removeAttr(envElm, 'style');
removeElements(envElm); removeElements(envElm);
@@ -508,11 +510,11 @@
var difference = !diffBiggerThanOne(deltaAbsRatio.w, deltaAbsRatio.h); var difference = !diffBiggerThanOne(deltaAbsRatio.w, deltaAbsRatio.h);
var dprChanged = dprNew !== dpr && dpr > 0; var dprChanged = dprNew !== dpr && dpr > 0;
var isZoom = deltaIsBigger && difference && dprChanged; var isZoom = deltaIsBigger && difference && dprChanged;
var oldScrollbarSize = _self.nativeScrollbarSize; var oldScrollbarSize = _self._nativeScrollbarSize;
var newScrollbarSize; var newScrollbarSize;
if (isZoom) { if (isZoom) {
newScrollbarSize = _self.nativeScrollbarSize = nativeScrollbarSize(body, envElm); newScrollbarSize = _self._nativeScrollbarSize = nativeScrollbarSize(body, envElm);
removeElements(envElm); removeElements(envElm);
if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) { if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -17,6 +17,7 @@ import {
type OnEnvironmentChanged = (env: Environment) => void; type OnEnvironmentChanged = (env: Environment) => void;
const { abs, round } = Math; const { abs, round } = Math;
const envornmentElmId = 'os-envornment';
const nativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => { const nativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => {
appendChildren(body, measureElm); appendChildren(body, measureElm);
@@ -42,7 +43,7 @@ const nativeScrollbarStyling = (testElm: HTMLElement): boolean => {
const rtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { i: boolean; n: boolean } => { const rtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): { i: boolean; n: boolean } => {
const strHidden = 'hidden'; const strHidden = 'hidden';
style(parentElm, { overflowX: strHidden, overflowY: strHidden }); style(parentElm, { overflowX: strHidden, overflowY: strHidden, direction: 'rtl' });
scrollLeft(parentElm, 0); scrollLeft(parentElm, 0);
const parentOffset = absoluteCoordinates(parentElm); const parentOffset = absoluteCoordinates(parentElm);
@@ -105,24 +106,24 @@ const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => {
export class Environment { export class Environment {
#onChangedListener: Set<OnEnvironmentChanged> = new Set(); #onChangedListener: Set<OnEnvironmentChanged> = new Set();
autoUpdateLoop!: boolean; _autoUpdateLoop!: boolean;
nativeScrollbarSize!: XY; _nativeScrollbarSize!: XY;
nativeScrollbarIsOverlaid!: XY<boolean>; _nativeScrollbarIsOverlaid!: XY<boolean>;
nativeScrollbarStyling!: boolean; _nativeScrollbarStyling!: boolean;
rtlScrollBehavior!: { n: boolean; i: boolean }; _rtlScrollBehavior!: { n: boolean; i: boolean };
supportPassiveEvents!: boolean; _supportPassiveEvents!: boolean;
supportResizeObserver!: boolean; _supportResizeObserver!: boolean;
constructor() { constructor() {
const _self = this; const _self = this;
const { body } = document; const { body } = document;
const envDOM = createDOM('<div id="os-dummy-scrollbar-size"><div></div></div>'); const envDOM = createDOM(`<div id="${envornmentElmId}"><div></div></div>`);
const envElm = envDOM[0] as HTMLElement; const envElm = envDOM[0] as HTMLElement;
const envChildElm = envElm.firstChild as HTMLElement; const envChildElm = envElm.firstChild as HTMLElement;
@@ -132,13 +133,13 @@ export class Environment {
y: nScrollBarSize.y === 0, y: nScrollBarSize.y === 0,
}; };
_self.autoUpdateLoop = false; _self._autoUpdateLoop = false;
_self.nativeScrollbarSize = nScrollBarSize; _self._nativeScrollbarSize = nScrollBarSize;
_self.nativeScrollbarIsOverlaid = nativeScrollbarIsOverlaid; _self._nativeScrollbarIsOverlaid = nativeScrollbarIsOverlaid;
_self.nativeScrollbarStyling = nativeScrollbarStyling(envElm); _self._nativeScrollbarStyling = nativeScrollbarStyling(envElm);
_self.rtlScrollBehavior = rtlScrollBehavior(envElm, envChildElm); _self._rtlScrollBehavior = rtlScrollBehavior(envElm, envChildElm);
_self.supportPassiveEvents = passiveEvents(); _self._supportPassiveEvents = passiveEvents();
_self.supportResizeObserver = !!jsAPI('ResizeObserver'); _self._supportResizeObserver = !!jsAPI('ResizeObserver');
removeAttr(envElm, 'style'); removeAttr(envElm, 'style');
removeElements(envElm); removeElements(envElm);
@@ -172,11 +173,11 @@ export class Environment {
const dprChanged = dprNew !== dpr && dpr > 0; const dprChanged = dprNew !== dpr && dpr > 0;
const isZoom = deltaIsBigger && difference && dprChanged; const isZoom = deltaIsBigger && difference && dprChanged;
const oldScrollbarSize = _self.nativeScrollbarSize; const oldScrollbarSize = _self._nativeScrollbarSize;
let newScrollbarSize; let newScrollbarSize;
if (isZoom) { if (isZoom) {
newScrollbarSize = _self.nativeScrollbarSize = nativeScrollbarSize(body, envElm); newScrollbarSize = _self._nativeScrollbarSize = nativeScrollbarSize(body, envElm);
removeElements(envElm); removeElements(envElm);
if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) { if (oldScrollbarSize.x !== newScrollbarSize.x || oldScrollbarSize.y !== newScrollbarSize.y) {
@@ -0,0 +1,48 @@
@import './sizeobserver.scss';
#os-envornment {
position: fixed;
opacity: 0;
visibility: hidden;
overflow: scroll;
height: 500px;
width: 500px;
}
#os-envornment > div {
width: 200%;
height: 200%;
margin: 10px 0;
}
/* fix restricted measuring */
#os-envornment:before,
#os-envornment:after,
.os-content:before,
.os-content:after {
content: '';
display: table;
width: 0.01px;
height: 0.01px;
line-height: 0;
font-size: 0;
flex-grow: 0;
flex-shrink: 0;
visibility: hidden;
}
#os-envornment,
.os-viewport {
-ms-overflow-style: scrollbar !important;
}
.os-viewport-native-scrollbars-invisible#os-envornment,
.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-viewport::-webkit-scrollbar,
.os-viewport-native-scrollbars-invisible#os-envornment::-webkit-scrollbar-corner,
.os-viewport-native-scrollbars-invisible.os-viewport::-webkit-scrollbar-corner {
display: none !important;
width: 0px !important;
height: 0px !important;
visibility: hidden !important;
background: transparent !important;
}
@@ -0,0 +1,54 @@
import { validate, assignDeep } from 'support';
import { Options, optionsTemplate } from 'options';
import { TargetElement } from 'overlayscrollbars';
import { Environment } from 'environment';
let ENVIRONMENT: Environment;
interface UpdateHints {
_changedOptions: Options;
}
interface OverlayScrollbarsInstanceVars {
_documentElm: Document;
_windowElm: Window;
_htmlElm: HTMLElement;
_bodyElm: HTMLElement;
_targetElm: TargetElement;
_isTextarea: boolean;
_isBody: boolean;
_currentOptions: Options;
_setOptions(newOptions: Options): Options;
_update(updateHints: UpdateHints): void;
}
/*
const initSingletons = () => {
if (!ENVIRONMENT) {
ENVIRONMENT = new Environment();
}
};
export class OverlayScrollbars {
#instanceVars: OverlayScrollbarsInstanceVars = {
_setOptions(newOptions: Options): Options {
const { _currentOptions } = this;
const { validated } = validate(newOptions, optionsTemplate, _currentOptions, true);
this._currentOptions = assignDeep({}, _currentOptions, validated);
return validated;
},
};
constructor(target: HTMLElement, options: Options) {
this.#instanceVars._documentElm = document;
this.#instanceVars._windowElm = window;
this.#instanceVars._htmlElm = document.body;
this.#instanceVars._bodyElm = document.body;
this.#instanceVars._targetElm = document.body;
this.#instanceVars._isTextarea = false;
this.#instanceVars._isBody = false;
initSingletons();
}
}
*/
@@ -0,0 +1,3 @@
export * from 'overlayscrollbars';
export type TargetElement = HTMLElement | HTMLTextAreaElement;
@@ -0,0 +1,18 @@
import { OverlayScrollbarsLifecycle } from 'overlayscrollbars/lifecycles';
export interface StructureLifecycleOptions {
_paddingAbsolute: boolean;
_autoSizeCapable: boolean;
_heightAuto: boolean;
_widthAuto: boolean;
_border: [number, number, number, number];
_padding: [number, number, number, number];
_margin: [number, number, number, number];
}
export class StructureLifecycle extends OverlayScrollbarsLifecycle<StructureLifecycleOptions> {
// eslint-disable-next-line
_update(options?: StructureLifecycleOptions): void {}
// eslint-disable-next-line
_destruct(): void {}
}
@@ -0,0 +1,14 @@
import { PlainObject } from 'typings';
import { Environment } from 'environment';
export abstract class OverlayScrollbarsLifecycle<T extends PlainObject> {
protected environment: Environment;
constructor(environment: Environment) {
this.environment = environment;
}
abstract _update(options?: T): void;
abstract _destruct(): void;
}
@@ -0,0 +1,92 @@
import { createDOM, style, appendChildren, offsetSize, scrollLeft, scrollTop, jsAPI, addClass, each } from 'support';
const animationStartEventName = 'animationstart mozAnimationStart webkitAnimationStart MSAnimationStart';
const scrollEventName = 'scroll';
const scrollAmount = 3333333;
const ResizeObserverConstructor = jsAPI('ResizeObserver');
const classNameSizeObserver = 'os-size-observer';
const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
const cAF = cancelAnimationFrame;
const rAF = requestAnimationFrame;
// TODO:
// 1. handling for event listeners (animationStartEventName.split(' '))
// 2. return not just element but also destruction function
// 3. shorthand handling for preventDefault & stopPropagation etc.
// 4. add test for appearance (display: none => display: block)
// 5. add functionality & tests for direction change
// 6. MAYBE add comparison function to offsetSize etc.
// 7. Create test utils (waitFor)
export const createSizeObserver = (onSizeChangedCallback: () => void) => {
const baseElements = createDOM(`<div class="${classNameSizeObserver}"><div class="${classNameSizeObserverListener}"></div></div>`);
const sizeObserver = baseElements[0] as HTMLElement;
const listenerElement = sizeObserver.firstChild as HTMLElement;
if (ResizeObserverConstructor) {
addClass(sizeObserver, 'resize-observer');
const resizeObserverInstance = new ResizeObserverConstructor(onSizeChangedCallback);
resizeObserverInstance.observe(listenerElement);
} else {
const observerElementChildren = createDOM(
`<div class="${classNameSizeObserverListenerItem}" dir="ltr"><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}"></div></div><div class="${classNameSizeObserverListenerItem}"><div class="${classNameSizeObserverListenerItemFinal}" style="width: 200%; height: 200%"></div></div></div>`
);
appendChildren(listenerElement, observerElementChildren);
const observerElementChildrenRoot = observerElementChildren[0] as HTMLElement;
const shrinkElement = observerElementChildrenRoot.lastChild as HTMLElement;
const expandElement = observerElementChildrenRoot.firstChild as HTMLElement;
const expandElementChild = expandElement?.firstChild as HTMLElement;
let cacheSize = offsetSize(listenerElement);
let currSize = cacheSize;
let isDirty = false;
let rAFId: number;
const reset = () => {
scrollLeft(expandElement, scrollAmount);
scrollTop(expandElement, scrollAmount);
scrollLeft(shrinkElement, scrollAmount);
scrollTop(shrinkElement, scrollAmount);
};
const onResized = function () {
rAFId = 0;
if (!isDirty) return;
cacheSize = currSize;
onSizeChangedCallback();
};
const onScroll = (scrollEvent?: Event) => {
currSize = offsetSize(listenerElement);
isDirty = currSize.w !== cacheSize.w || currSize.h !== cacheSize.h;
if (scrollEvent && isDirty && !rAFId) {
cAF(rAFId);
rAFId = rAF(onResized);
} else if (!scrollEvent) onResized();
reset();
if (scrollEvent) {
scrollEvent.preventDefault();
scrollEvent.stopPropagation();
}
return false;
};
expandElement.addEventListener(scrollEventName, onScroll);
shrinkElement.addEventListener(scrollEventName, onScroll);
each(animationStartEventName.split(' '), (eventName) => {
sizeObserver.addEventListener(eventName, () => {
onScroll();
});
});
// lets assume that the divs will never be that large and a constant value is enough
style(expandElementChild, {
width: scrollAmount,
height: scrollAmount,
});
reset();
}
return sizeObserver;
};
@@ -0,0 +1,70 @@
$scrollbar-cushion: 100px;
.os-size-observer,
.os-size-observer-listener {
padding: inherit;
margin: 0;
pointer-events: none;
overflow: hidden;
visibility: hidden;
}
.os-size-observer,
.os-size-observer-listener,
.os-size-observer-listener-item,
.os-size-observer-listener-item-final {
position: absolute;
left: 0;
top: 0;
}
.os-size-observer {
height: 100%;
width: 100%;
z-index: -1;
animation-duration: 0.001s;
animation-name: os-size-observer-appear-animation;
&.resize-observer {
.os-size-observer-listener {
position: absolute;
box-sizing: border-box;
}
}
}
.os-size-observer-listener {
display: block;
height: 200%;
width: 200%;
box-sizing: content-box;
// lets assume no scrollbar is 100px wide
& > .os-size-observer-listener-item {
top: -$scrollbar-cushion;
right: -$scrollbar-cushion;
bottom: -$scrollbar-cushion;
left: -$scrollbar-cushion;
}
}
.os-size-observer-listener-item {
right: 0;
bottom: 0;
overflow: hidden;
direction: ltr;
flex: none;
}
.os-size-observer-listener-item-final {
transition: none;
}
@keyframes os-size-observer-appear-animation {
from {
z-index: 0;
}
to {
z-index: -1;
}
}
-3
View File
@@ -1,3 +0,0 @@
body {
background: blue;
}
-5
View File
@@ -1,5 +0,0 @@
$c: red;
body {
background: $c;
}
@@ -19,12 +19,9 @@ export type OptionsTemplate<T extends Required<T>> = {
? OptionsTemplateValue<T[P]> ? OptionsTemplateValue<T[P]>
: never; : never;
}; };
export type OptionsValidated<T> = {
[P in keyof T]?: OptionsValidated<T[P]>;
};
export type OptionsValidatedResult<T> = { export type OptionsValidatedResult<T> = {
readonly foreign: PlainObject; readonly foreign: PlainObject;
readonly validated: OptionsValidated<T>; readonly validated: T;
}; };
// Options With Options Template Typings: // Options With Options Template Typings:
export type OptionsAndOptionsTemplateValue<T extends OptionsTemplateNativeTypes> = [T, OptionsTemplateValue<T>]; export type OptionsAndOptionsTemplateValue<T extends OptionsTemplateNativeTypes> = [T, OptionsTemplateValue<T>];
@@ -48,5 +45,5 @@ type OptionsTemplateValueNonEnum<T extends OptionsTemplateNativeTypes> =
| OptionsTemplateType<T> | OptionsTemplateType<T>
| [OptionsTemplateType<T>, ...Array<OptionsTemplateTypes>]; | [OptionsTemplateType<T>, ...Array<OptionsTemplateTypes>];
type ExtractPropsKey<T, TProps extends T[keyof T]> = { type ExtractPropsKey<T, TProps extends T[keyof T]> = {
[P in keyof T]: TProps extends T[P] ? P : never; [P in keyof T]: TProps extends T[P] ? P : never;
}[keyof T]; }[keyof T];
@@ -1,6 +1,6 @@
import { each, indexOf, hasOwnProperty, keys } from 'support/utils'; import { each, indexOf, hasOwnProperty, keys } from 'support/utils';
import { type, isArray, isUndefined, isEmptyObject, isPlainObject, isString } from 'support/utils/types'; import { type, isArray, isUndefined, isEmptyObject, isPlainObject, isString } from 'support/utils/types';
import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, OptionsValidated, Func, OptionsValidatedResult } from 'support/options'; import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, Func, OptionsValidatedResult } from 'support/options';
import { PlainObject } from 'typings'; import { PlainObject } from 'typings';
const { stringify } = JSON; const { stringify } = JSON;
@@ -43,11 +43,11 @@ const optionsTemplateTypes: OptionsTemplateTypesDictionary = ['boolean', 'number
const validateRecursive = <T extends PlainObject>( const validateRecursive = <T extends PlainObject>(
options: T, options: T,
template: OptionsTemplate<Required<T>>, template: OptionsTemplate<Required<T>>,
optionsDiff: OptionsValidated<T>, optionsDiff: T,
doWriteErrors?: boolean, doWriteErrors?: boolean,
propPath?: string propPath?: string
): OptionsValidatedResult<T> => { ): OptionsValidatedResult<T> => {
const validatedOptions: OptionsValidated<T> = {}; const validatedOptions: T = {} as T;
const optionsCopy: T = { ...options }; const optionsCopy: T = { ...options };
const props = keys(template).filter((prop) => hasOwnProperty(options, prop)); const props = keys(template).filter((prop) => hasOwnProperty(options, prop));
@@ -143,7 +143,7 @@ const validateRecursive = <T extends PlainObject>(
const validate = <T extends PlainObject>( const validate = <T extends PlainObject>(
options: T, options: T,
template: OptionsTemplate<Required<T>>, template: OptionsTemplate<Required<T>>,
optionsDiff?: OptionsValidated<T>, optionsDiff?: T,
doWriteErrors?: boolean doWriteErrors?: boolean
): OptionsValidatedResult<T> => { ): OptionsValidatedResult<T> => {
/* /*
@@ -155,7 +155,7 @@ const validate = <T extends PlainObject>(
Object.assign(result.validated, foreign); Object.assign(result.validated, foreign);
} }
*/ */
return validateRecursive(options, template, optionsDiff || {}, doWriteErrors || false); return validateRecursive<T>(options, template, optionsDiff || ({} as T), doWriteErrors || false);
}; };
export { validate, optionsTemplateTypes }; export { validate, optionsTemplateTypes };
@@ -1,3 +1,4 @@
import { Environment } from 'environment';
import url from './.build/build.html'; import url from './.build/build.html';
describe('Environment', () => { describe('Environment', () => {
@@ -6,10 +7,13 @@ describe('Environment', () => {
}); });
it('should be titled "Environment"', async () => { it('should be titled "Environment"', async () => {
await expect(page).toMatchElement('#a'); //await expect(page).toMatchElement('#a');
await expect(page).toMatchElement('#b'); //await expect(page).toMatchElement('#b');
await expect(page).toMatchElement('#c'); //await expect(page).toMatchElement('#c');
await expect(page).toMatchElement('#d'); //await expect(page).toMatchElement('#d');
const a: Environment = await page.evaluate(() => window.envInstance);
console.log(a);
await expect(page.title()).resolves.toMatch('Environment'); await expect(page.title()).resolves.toMatch('Environment');
}); });
}); });
@@ -1,7 +1,7 @@
import 'overlayscrollbars.scss';
import { Environment } from 'environment'; import { Environment } from 'environment';
import 'some.scss';
import 'some.css';
document.body.append(JSON.stringify(new Environment())); window.envInstance = new Environment();
document.body.textContent = JSON.stringify(window.envInstance);
export { Environment }; export { Environment };
@@ -0,0 +1,23 @@
<label for="height">height</label>
<select name="height" id="height">
<option value="heightAuto">auto</option>
<option value="heightHundred">100%</option>
<option value="height200">200px</option>
</select>
<label for="width">width</label>
<select name="width" id="width">
<option value="widthAuto">auto</option>
<option value="widthHundred">100%</option>
<option value="width200">200px</option>
</select>
<label for="padding">padding</label>
<select name="padding" id="padding">
<option value="padding0">0</option>
<option value="padding10">10px</option>
<option value="padding50">50px</option>
</select>
<button id="start">start</button>
<span>Detected resizes: <span id="resizes">0</span></span>
<br />
<div id="target"></div>
@@ -0,0 +1,37 @@
#target {
border: 2px solid red;
overflow: scroll;
resize: both;
position: relative;
}
.padding0 {
padding: 0;
}
.padding10 {
padding: 10px;
}
.padding50 {
padding: 50px;
}
.heightAuto {
height: auto;
}
.height200 {
height: 200px;
}
.heightHundred {
height: 100%;
}
.widthAuto {
width: auto;
float: left;
}
.width200 {
width: 200px;
}
.widthHundred {
width: 100%;
}
@@ -0,0 +1,15 @@
import expectPuppeteer from 'expect-puppeteer';
import url from './.build/build.html';
describe('Environment', () => {
beforeAll(async () => {
await page.goto(url);
});
it('test', async () => {
await expectPuppeteer(page).toClick('#start');
await expectPuppeteer(page).toMatchElement('#testResult.passed', {
timeout: 30000,
});
}, 30000);
});
@@ -0,0 +1,128 @@
import 'overlayscrollbars.scss';
import './index.scss';
import { createSizeObserver } from 'overlayscrollbars/observers/createSizeObserver';
import { from, removeClass, addClass } from 'support';
const targetElm = document.querySelector('#target');
const heightSelect: HTMLSelectElement | null = document.querySelector('#height');
const widthSelect: HTMLSelectElement | null = document.querySelector('#width');
const paddingSelect: HTMLSelectElement | null = document.querySelector('#padding');
const startBtn: HTMLButtonElement | null = document.querySelector('#start');
const resizesSlot: HTMLButtonElement | null = document.querySelector('#resizes');
const getSelectOptions = (selectElement: HTMLSelectElement) => {
const arr = from(selectElement.options).map((option) => option.value);
return arr;
};
const selectCallback = (event: Event) => {
const target = event.target as HTMLSelectElement;
const selectedOption = target.value;
const selectOptions = getSelectOptions(target);
removeClass(targetElm, selectOptions.join(' '));
addClass(targetElm, selectedOption);
};
heightSelect?.addEventListener('change', selectCallback);
widthSelect?.addEventListener('change', selectCallback);
paddingSelect?.addEventListener('change', selectCallback);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
selectCallback({ target: heightSelect });
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
selectCallback({ target: widthSelect });
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
selectCallback({ target: paddingSelect });
let iterations = 0;
const observerElm = createSizeObserver(() => {
iterations += 1;
requestAnimationFrame(() => {
if (resizesSlot) {
resizesSlot.textContent = iterations.toString();
}
});
});
targetElm?.appendChild(observerElm);
const waitFor = (func: () => any) => {
const start = Date.now();
return new Promise((resolve, reject) => {
const intervalId = setInterval(() => {
const now = Date.now();
if (func()) {
clearInterval(intervalId);
resolve();
}
if (now - start > 5000) {
clearInterval(intervalId);
window.setTestResult(false);
reject();
}
}, 30);
});
};
const iterateSelect = async (select: HTMLSelectElement | null, afterEach?: () => any) => {
if (select) {
const selectOptions = getSelectOptions(select);
const selectOptionsReversed = getSelectOptions(select).reverse();
const iterateOptions = [...selectOptions, ...selectOptionsReversed];
for (let i = 0; i < iterateOptions.length; i++) {
const option = iterateOptions[i];
const currValue = select.value;
if (option === currValue) {
continue;
}
select.value = option;
const currIterations = iterations;
let event;
if (typeof Event === 'function') {
event = new Event('change');
} else {
event = document.createEvent('Event');
event.initEvent('change', true, true);
}
select.dispatchEvent(event);
// eslint-disable-next-line
await waitFor(() => iterations === currIterations + 1);
if (typeof afterEach === 'function') {
// eslint-disable-next-line
await afterEach();
}
}
}
};
window.iteratePadding = async (afterEach?: () => any) => {
await iterateSelect(paddingSelect, afterEach);
};
window.iterateHeight = async (afterEach?: () => any) => {
await iterateSelect(heightSelect, afterEach);
};
window.iterateWidth = async (afterEach?: () => any) => {
await iterateSelect(widthSelect, afterEach);
};
const start = (window.iterate = async () => {
window.setTestResult(null);
targetElm?.removeAttribute('style');
await iterateHeight(async () => {
await iterateWidth(async () => {
await iteratePadding();
});
});
window.setTestResult(true);
});
startBtn?.addEventListener('click', start);
@@ -2,16 +2,16 @@ import { XY } from 'support';
declare type OnEnvironmentChanged = (env: Environment) => void; declare type OnEnvironmentChanged = (env: Environment) => void;
export declare class Environment { export declare class Environment {
#private; #private;
autoUpdateLoop: boolean; _autoUpdateLoop: boolean;
nativeScrollbarSize: XY; _nativeScrollbarSize: XY;
nativeScrollbarIsOverlaid: XY<boolean>; _nativeScrollbarIsOverlaid: XY<boolean>;
nativeScrollbarStyling: boolean; _nativeScrollbarStyling: boolean;
rtlScrollBehavior: { _rtlScrollBehavior: {
n: boolean; n: boolean;
i: boolean; i: boolean;
}; };
supportPassiveEvents: boolean; _supportPassiveEvents: boolean;
supportResizeObserver: boolean; _supportResizeObserver: boolean;
constructor(); constructor();
addListener(listener: OnEnvironmentChanged): void; addListener(listener: OnEnvironmentChanged): void;
removeListener(listener: OnEnvironmentChanged): void; removeListener(listener: OnEnvironmentChanged): void;
@@ -0,0 +1 @@
export {};
@@ -0,0 +1,2 @@
export * from 'overlayscrollbars';
export declare type TargetElement = HTMLElement | HTMLTextAreaElement;
@@ -0,0 +1,14 @@
import { OverlayScrollbarsLifecycle } from 'overlayscrollbars/lifecycles';
export interface StructureLifecycleOptions {
_paddingAbsolute: boolean;
_autoSizeCapable: boolean;
_heightAuto: boolean;
_widthAuto: boolean;
_border: [number, number, number, number];
_padding: [number, number, number, number];
_margin: [number, number, number, number];
}
export declare class StructureLifecycle extends OverlayScrollbarsLifecycle<StructureLifecycleOptions> {
_update(options?: StructureLifecycleOptions): void;
_destruct(): void;
}
@@ -0,0 +1,8 @@
import { PlainObject } from 'typings';
import { Environment } from 'environment';
export declare abstract class OverlayScrollbarsLifecycle<T extends PlainObject> {
protected environment: Environment;
constructor(environment: Environment);
abstract _update(options?: T): void;
abstract _destruct(): void;
}
@@ -0,0 +1 @@
export declare const createSizeObserver: (onSizeChangedCallback: () => void) => HTMLElement;
@@ -9,12 +9,9 @@ export declare type OptionsTemplateValue<T extends OptionsTemplateNativeTypes =
export declare type OptionsTemplate<T extends Required<T>> = { export declare type OptionsTemplate<T extends Required<T>> = {
[P in keyof T]: PlainObject extends T[P] ? OptionsTemplate<Required<T[P]>> : T[P] extends OptionsTemplateNativeTypes ? OptionsTemplateValue<T[P]> : never; [P in keyof T]: PlainObject extends T[P] ? OptionsTemplate<Required<T[P]>> : T[P] extends OptionsTemplateNativeTypes ? OptionsTemplateValue<T[P]> : never;
}; };
export declare type OptionsValidated<T> = {
[P in keyof T]?: OptionsValidated<T[P]>;
};
export declare type OptionsValidatedResult<T> = { export declare type OptionsValidatedResult<T> = {
readonly foreign: PlainObject; readonly foreign: PlainObject;
readonly validated: OptionsValidated<T>; readonly validated: T;
}; };
export declare type OptionsAndOptionsTemplateValue<T extends OptionsTemplateNativeTypes> = [T, OptionsTemplateValue<T>]; export declare type OptionsAndOptionsTemplateValue<T extends OptionsTemplateNativeTypes> = [T, OptionsTemplateValue<T>];
export declare type OptionsAndOptionsTemplate<T extends Required<T>> = { export declare type OptionsAndOptionsTemplate<T extends Required<T>> = {
@@ -1,7 +1,7 @@
import { OptionsTemplate, OptionsTemplateType, OptionsValidated, Func, OptionsValidatedResult } from 'support/options'; import { OptionsTemplate, OptionsTemplateType, Func, OptionsValidatedResult } from 'support/options';
import { PlainObject } from 'typings'; import { PlainObject } from 'typings';
declare const optionsTemplateTypes: OptionsTemplateTypesDictionary; declare const optionsTemplateTypes: OptionsTemplateTypesDictionary;
declare const validate: <T extends PlainObject<any>>(options: T, template: OptionsTemplate<Required<T>>, optionsDiff?: OptionsValidated<T> | undefined, doWriteErrors?: boolean | undefined) => OptionsValidatedResult<T>; declare const validate: <T extends PlainObject<any>>(options: T, template: OptionsTemplate<Required<T>>, optionsDiff?: T | undefined, doWriteErrors?: boolean | undefined) => OptionsValidatedResult<T>;
export { validate, optionsTemplateTypes }; export { validate, optionsTemplateTypes };
declare type OptionsTemplateTypesDictionary = { declare type OptionsTemplateTypesDictionary = {
readonly boolean: OptionsTemplateType<boolean>; readonly boolean: OptionsTemplateType<boolean>;
+7 -1
View File
@@ -66,7 +66,7 @@ const resolvePath = (basePath, pathToResolve, appendExt) => {
const resolveConfig = (config) => { const resolveConfig = (config) => {
if (typeof config === 'function') { if (typeof config === 'function') {
return config(rollupConfigDefaults) || {}; return config(rollupConfigDefaults, legacyBabelConfig, esmBabelConfig) || {};
} }
return config; return config;
}; };
@@ -164,6 +164,12 @@ const rollupConfig = (config = {}, { project = process.cwd(), overwrite = {}, si
rollupTerser({ rollupTerser({
ecma: 8, ecma: 8,
safari10: true, safari10: true,
mangle: {
safari10: true,
properties: {
regex: /^_/,
},
},
}), }),
], ],
} }
+5
View File
@@ -2543,6 +2543,11 @@ core-js@^2.6.5:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg== integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
core-js@^3.6.5:
version "3.6.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
core-util-is@1.0.2, core-util-is@~1.0.0: core-util-is@1.0.2, core-util-is@~1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"