diff --git a/packages/overlayscrollbars/src/overlayscrollbars/observers/createSizeObserver.ts b/packages/overlayscrollbars/src/overlayscrollbars/observers/SizeObserver.ts
similarity index 68%
rename from packages/overlayscrollbars/src/overlayscrollbars/observers/createSizeObserver.ts
rename to packages/overlayscrollbars/src/overlayscrollbars/observers/SizeObserver.ts
index 524670f..888cf17 100644
--- a/packages/overlayscrollbars/src/overlayscrollbars/observers/createSizeObserver.ts
+++ b/packages/overlayscrollbars/src/overlayscrollbars/observers/SizeObserver.ts
@@ -1,4 +1,5 @@
-import { createDOM, style, appendChildren, offsetSize, scrollLeft, scrollTop, jsAPI, addClass, each } from 'support';
+import { createDOM, style, appendChildren, offsetSize, scrollLeft, scrollTop, jsAPI, each, prependChildren, removeElements } from 'support';
+import { Environment } from 'environment';
const animationStartEventName = 'animationstart';
const scrollEventName = 'scroll';
@@ -10,25 +11,38 @@ const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
const cAF = cancelAnimationFrame;
const rAF = requestAnimationFrame;
+const getDirection = (elm: HTMLElement) => style(elm, 'direction');
// 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 functionality & tests for direction change
// 5. MAYBE add comparison function to offsetSize etc.
-// 6. Create test utils (waitFor)
-export const createSizeObserver = (onSizeChangedCallback: () => void) => {
+export const createSizeObserver = (
+ target: HTMLElement,
+ onSizeChangedCallback: (direction?: boolean) => any,
+ environment: Environment,
+ direction?: boolean
+) => {
+ const rtlScrollBehavior = environment._rtlScrollBehavior;
const baseElements = createDOM(`
`);
const sizeObserver = baseElements[0] as HTMLElement;
const listenerElement = sizeObserver.firstChild as HTMLElement;
- let appearCallback = onSizeChangedCallback;
+ const onSizeChangedCallbackProxy = (dir?: boolean) => {
+ if (direction) {
+ const rtl = getDirection(sizeObserver) === 'rtl';
+ scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
+ scrollTop(sizeObserver, scrollAmount);
+ }
+ onSizeChangedCallback(dir === true);
+ };
+ let appearCallback: (...args: any) => any = onSizeChangedCallbackProxy;
+
if (ResizeObserverConstructor) {
- const resizeObserverInstance = new ResizeObserverConstructor(onSizeChangedCallback);
+ const resizeObserverInstance = new ResizeObserverConstructor(onSizeChangedCallbackProxy);
resizeObserverInstance.observe(listenerElement);
} else {
- addClass(sizeObserver, 'scroll-observer');
const observerElementChildren = createDOM(
``
);
@@ -54,7 +68,7 @@ export const createSizeObserver = (onSizeChangedCallback: () => void) => {
if (!isDirty) return;
cacheSize = currSize;
- onSizeChangedCallback();
+ onSizeChangedCallbackProxy();
};
const onScroll = (scrollEvent?: Event) => {
currSize = offsetSize(listenerElement);
@@ -91,5 +105,30 @@ export const createSizeObserver = (onSizeChangedCallback: () => void) => {
});
});
- return sizeObserver;
+ if (direction) {
+ let dirCache: string | undefined;
+ sizeObserver.addEventListener('scroll', (event: Event) => {
+ const dir = getDirection(sizeObserver);
+ const changed = dir !== dirCache;
+ if (changed) {
+ if (dir === 'rtl') {
+ style(listenerElement, { left: 'auto', right: 0 });
+ } else {
+ style(listenerElement, { left: 0, right: 'auto' });
+ }
+ dirCache = dir;
+ onSizeChangedCallbackProxy(true);
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+ return false;
+ });
+ }
+
+ prependChildren(target, sizeObserver);
+
+ return () => {
+ removeElements(sizeObserver);
+ };
};
diff --git a/packages/overlayscrollbars/src/sizeobserver.scss b/packages/overlayscrollbars/src/sizeobserver.scss
index b5db5e1..dc159bb 100644
--- a/packages/overlayscrollbars/src/sizeobserver.scss
+++ b/packages/overlayscrollbars/src/sizeobserver.scss
@@ -2,7 +2,9 @@ $scrollbar-cushion: 100px;
.os-size-observer,
.os-size-observer-listener {
+ direction: inherit;
padding: inherit;
+ border: inherit;
margin: 0;
pointer-events: none;
overflow: hidden;
@@ -25,18 +27,13 @@ $scrollbar-cushion: 100px;
z-index: -1;
animation-duration: 0.001s;
animation-name: os-size-observer-appear-animation;
-
- &.scroll-observer {
- .os-size-observer-listener {
- box-sizing: content-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 {
diff --git a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.browser.ts b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.browser.ts
index 07f4126..651b18a 100644
--- a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.browser.ts
+++ b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.browser.ts
@@ -4,9 +4,24 @@ import should from 'should';
import { waitFor } from '@testing-library/dom';
import { generateSelectCallback, iterateSelect } from '@/testing-browser/Select';
import { setTestResult } from '@/testing-browser/TestResult';
-import { hasDimensions, offsetSize, WH } from 'support';
+import { hasDimensions, offsetSize, WH, style } from 'support';
-import { createSizeObserver } from 'overlayscrollbars/observers/createSizeObserver';
+import { Environment } from 'environment';
+import { createSizeObserver } from 'overlayscrollbars/observers/SizeObserver';
+
+let sizeIterations = 0;
+let directionIterations = 0;
+const contentBox = (elm: HTMLElement | null) => {
+ if (elm) {
+ const computedStyle = window.getComputedStyle(elm);
+ return {
+ width: elm.clientWidth - (parseFloat(computedStyle.paddingLeft) + parseFloat(computedStyle.paddingRight)),
+ height: elm.clientHeight - (parseFloat(computedStyle.paddingTop) + parseFloat(computedStyle.paddingBottom)),
+ };
+ }
+
+ return { width: 0, height: 0 };
+};
const targetElm = document.querySelector('#target');
const heightSelect: HTMLSelectElement | null = document.querySelector('#height');
@@ -15,44 +30,67 @@ const paddingSelect: HTMLSelectElement | null = document.querySelector('#padding
const borderSelect: HTMLSelectElement | null = document.querySelector('#border');
const boxSizingSelect: HTMLSelectElement | null = document.querySelector('#boxSizing');
const displaySelect: HTMLSelectElement | null = document.querySelector('#display');
+const directionSelect: HTMLSelectElement | null = document.querySelector('#direction');
const startBtn: HTMLButtonElement | null = document.querySelector('#start');
const resizesSlot: HTMLButtonElement | null = document.querySelector('#resizes');
-let iterations = 0;
-const observerElm = createSizeObserver(() => {
- iterations += 1;
- requestAnimationFrame(() => {
- if (resizesSlot) {
- resizesSlot.textContent = iterations.toString();
- }
- });
-});
-
const selectCallback = generateSelectCallback(targetElm as HTMLElement);
-
const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any) => {
- await iterateSelect<{ currIterations: number; currOffsetSize: WH }>(select, {
+ interface IterateSelect {
+ currSizeIterations: number;
+ currDirectionIterations: number;
+ currOffsetSize: WH;
+ currDir: string;
+ }
+
+ await iterateSelect(select, {
beforeEach() {
- const currIterations = iterations;
+ const currSizeIterations = sizeIterations;
+ const currDirectionIterations = directionIterations;
const currOffsetSize = offsetSize(targetElm as HTMLElement);
+ const currDir = style(targetElm as HTMLElement, 'direction');
return {
- currIterations,
+ currSizeIterations,
+ currDirectionIterations,
currOffsetSize,
+ currDir,
};
},
- async check({ currIterations, currOffsetSize }) {
+ async check({ currSizeIterations, currDirectionIterations, currOffsetSize, currDir }) {
const newOffsetSize = offsetSize(targetElm as HTMLElement);
+ const newDir = style(targetElm as HTMLElement, 'direction');
const offsetSizeChanged = currOffsetSize.w !== newOffsetSize.w || currOffsetSize.h !== newOffsetSize.h;
+ const dirChanged = currDir !== newDir;
+ const dimensions = hasDimensions(targetElm as HTMLElement);
+ const contentSize = contentBox(targetElm as HTMLElement);
+ const observerElm = targetElm?.firstElementChild as HTMLElement;
- if (hasDimensions(targetElm as HTMLElement) && offsetSizeChanged) {
- // eslint-disable-next-line
- await waitFor(() => should.equal(iterations, currIterations + 1), {
- onTimeout(error): Error {
- setTestResult(false);
- return error;
+ // no overflow if not needed
+ if (targetElm && contentSize.width > 0) {
+ should.ok(observerElm.getBoundingClientRect().right <= targetElm.getBoundingClientRect().right);
+ }
+ if (targetElm && contentSize.height > 0) {
+ should.ok(observerElm.getBoundingClientRect().bottom <= targetElm.getBoundingClientRect().bottom);
+ }
+
+ if (dimensions && (offsetSizeChanged || dirChanged)) {
+ await waitFor(
+ async () => {
+ if (offsetSizeChanged) {
+ should.equal(sizeIterations, currSizeIterations + 1);
+ }
+ if (dirChanged) {
+ should.equal(directionIterations, currDirectionIterations + 1);
+ }
},
- });
+ {
+ onTimeout(error): Error {
+ setTestResult(false);
+ return error;
+ },
+ }
+ );
}
},
afterEach,
@@ -65,6 +103,7 @@ paddingSelect?.addEventListener('change', selectCallback);
borderSelect?.addEventListener('change', selectCallback);
boxSizingSelect?.addEventListener('change', selectCallback);
displaySelect?.addEventListener('change', selectCallback);
+directionSelect?.addEventListener('change', selectCallback);
selectCallback(heightSelect);
selectCallback(widthSelect);
@@ -72,6 +111,7 @@ selectCallback(paddingSelect);
selectCallback(borderSelect);
selectCallback(boxSizingSelect);
selectCallback(displaySelect);
+selectCallback(directionSelect);
const iteratePadding = async (afterEach?: () => any) => {
await iterate(paddingSelect, afterEach);
@@ -91,16 +131,21 @@ const iterateBoxSizing = async (afterEach?: () => any) => {
const iterateDisplay = async (afterEach?: () => any) => {
await iterate(displaySelect, afterEach);
};
+const iterateDirection = async (afterEach?: () => any) => {
+ await iterate(directionSelect, afterEach);
+};
const start = async () => {
setTestResult(null);
targetElm?.removeAttribute('style');
await iterateDisplay();
+ await iterateDirection();
await iterateBoxSizing(async () => {
await iterateHeight(async () => {
await iterateWidth(async () => {
await iterateBorder(async () => {
+ await iterateDirection();
await iteratePadding();
});
});
@@ -112,6 +157,22 @@ const start = async () => {
startBtn?.addEventListener('click', start);
-targetElm?.appendChild(observerElm);
+createSizeObserver(
+ targetElm as HTMLElement,
+ (direction?: boolean) => {
+ if (direction) {
+ directionIterations += 1;
+ } else {
+ sizeIterations += 1;
+ }
+ requestAnimationFrame(() => {
+ if (resizesSlot) {
+ resizesSlot.textContent = (directionIterations + sizeIterations).toString();
+ }
+ });
+ },
+ new Environment(),
+ true
+);
export { start };
diff --git a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html
index a97c43c..05167a7 100644
--- a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html
+++ b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html
@@ -1,40 +1,49 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
Detected resizes: 0
-
-
-
+
+
Detected resizes: 0
+
+
diff --git a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss
index 7131e1b..8e9c890 100644
--- a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss
+++ b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss
@@ -1,5 +1,34 @@
+body {
+ display: flex;
+ flex-direction: column;
+}
+#controls {
+ flex: none;
+}
+#stage {
+ flex: auto;
+ position: relative;
+
+ & > div {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ background: lightgoldenrodyellow;
+ }
+}
+
+#canvas > div {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
#target {
- overflow: scroll;
+ overflow: hidden;
resize: both;
position: relative;
// prevent container from reaching 0x0 dimensions for testing purposes
@@ -48,9 +77,23 @@
width: 100%;
}
+.boxSizingBorderBox {
+ box-sizing: border-box;
+}
+.boxSizingContentBox {
+ box-sizing: content-box;
+}
+
.displayNone {
display: none;
}
.displayBlock {
display: block;
}
+
+.directionltr {
+ direction: ltr;
+}
+.directionRTL {
+ direction: rtl;
+}
diff --git a/packages/testing-browser/src/Select.ts b/packages/testing-browser/src/Select.ts
index 154b93c..076007b 100644
--- a/packages/testing-browser/src/Select.ts
+++ b/packages/testing-browser/src/Select.ts
@@ -16,7 +16,7 @@ export const generateSelectCallback = (targetElm: HTMLElement | null) => (event:
const selectOptions = getSelectOptions(target);
if (targetElm) {
- targetElm.classList.remove(...selectOptions);
+ selectOptions.forEach((clazz) => targetElm.classList.remove(clazz));
targetElm.classList.add(selectedOption);
}
}
diff --git a/packages/testing-browser/src/Timeout.ts b/packages/testing-browser/src/Timeout.ts
new file mode 100644
index 0000000..367d172
--- /dev/null
+++ b/packages/testing-browser/src/Timeout.ts
@@ -0,0 +1 @@
+export const timeout = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
diff --git a/rollup.config.base.js b/rollup.config.base.js
index 7f03a2e..37d2a65 100644
--- a/rollup.config.base.js
+++ b/rollup.config.base.js
@@ -256,12 +256,6 @@ const rollupConfig = (config = {}, { project = process.cwd(), overwrite = {}, si
if (isLast) {
build.plugins.push({
name: 'deleteCacheDirs',
- moduleParsed(moduleInfo) {
- //if (filename.includes('index.browser')) {
- console.log('moduleInfo', moduleInfo);
- //console.log('importer', filename);
- //}
- },
writeBundle() {
const cacheDirs = cache.map((dir) => path.resolve(projectPath, dir));
const deletedDirs = del.sync(cacheDirs);