diff --git a/packages/overlayscrollbars/src/overlayscrollbars/observers/createSizeObserver.ts b/packages/overlayscrollbars/src/overlayscrollbars/observers/createSizeObserver.ts
index 05f3b7d..524670f 100644
--- a/packages/overlayscrollbars/src/overlayscrollbars/observers/createSizeObserver.ts
+++ b/packages/overlayscrollbars/src/overlayscrollbars/observers/createSizeObserver.ts
@@ -1,6 +1,6 @@
import { createDOM, style, appendChildren, offsetSize, scrollLeft, scrollTop, jsAPI, addClass, each } from 'support';
-const animationStartEventName = 'animationstart mozAnimationStart webkitAnimationStart MSAnimationStart';
+const animationStartEventName = 'animationstart';
const scrollEventName = 'scroll';
const scrollAmount = 3333333;
const ResizeObserverConstructor = jsAPI('ResizeObserver');
@@ -15,20 +15,20 @@ const rAF = requestAnimationFrame;
// 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)
+// 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) => {
const baseElements = createDOM(`
`);
const sizeObserver = baseElements[0] as HTMLElement;
const listenerElement = sizeObserver.firstChild as HTMLElement;
+ let appearCallback = onSizeChangedCallback;
if (ResizeObserverConstructor) {
- addClass(sizeObserver, 'resize-observer');
const resizeObserverInstance = new ResizeObserverConstructor(onSizeChangedCallback);
resizeObserverInstance.observe(listenerElement);
} else {
+ addClass(sizeObserver, 'scroll-observer');
const observerElementChildren = createDOM(
``
);
@@ -58,7 +58,7 @@ export const createSizeObserver = (onSizeChangedCallback: () => void) => {
};
const onScroll = (scrollEvent?: Event) => {
currSize = offsetSize(listenerElement);
- isDirty = currSize.w !== cacheSize.w || currSize.h !== cacheSize.h;
+ isDirty = !scrollEvent || currSize.w !== cacheSize.w || currSize.h !== cacheSize.h;
if (scrollEvent && isDirty && !rAFId) {
cAF(rAFId);
@@ -75,18 +75,21 @@ export const createSizeObserver = (onSizeChangedCallback: () => void) => {
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();
+ appearCallback = onScroll;
}
+ each(animationStartEventName.split(' '), (eventName) => {
+ sizeObserver.addEventListener(eventName, () => {
+ appearCallback();
+ });
+ });
+
return sizeObserver;
};
diff --git a/packages/overlayscrollbars/src/sizeobserver.scss b/packages/overlayscrollbars/src/sizeobserver.scss
index 27960a1..b5db5e1 100644
--- a/packages/overlayscrollbars/src/sizeobserver.scss
+++ b/packages/overlayscrollbars/src/sizeobserver.scss
@@ -7,6 +7,7 @@ $scrollbar-cushion: 100px;
pointer-events: none;
overflow: hidden;
visibility: hidden;
+ box-sizing: border-box;
}
.os-size-observer,
@@ -25,10 +26,9 @@ $scrollbar-cushion: 100px;
animation-duration: 0.001s;
animation-name: os-size-observer-appear-animation;
- &.resize-observer {
+ &.scroll-observer {
.os-size-observer-listener {
- position: absolute;
- box-sizing: border-box;
+ box-sizing: content-box;
}
}
}
@@ -37,7 +37,6 @@ $scrollbar-cushion: 100px;
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/src/support/dom/dimensions.ts b/packages/overlayscrollbars/src/support/dom/dimensions.ts
index a26fb1a..531937b 100644
--- a/packages/overlayscrollbars/src/support/dom/dimensions.ts
+++ b/packages/overlayscrollbars/src/support/dom/dimensions.ts
@@ -1,5 +1,6 @@
import { WH } from 'support/dom';
+const elementHasDimensions = (elm: HTMLElement): boolean => !!(elm.offsetWidth || elm.offsetHeight || elm.getClientRects().length);
const zeroObj: WH = {
w: 0,
h: 0,
@@ -42,3 +43,9 @@ export const clientSize = (elm: HTMLElement | null): WH =>
* @param elm The element of which the BoundingClientRect shall be returned.
*/
export const getBoundingClientRect = (elm: HTMLElement): DOMRect => elm.getBoundingClientRect();
+
+/**
+ * Determines whether the passed element has any dimensions.
+ * @param elm The element.
+ */
+export const hasDimensions = (elm: HTMLElement | null): boolean => (elm ? elementHasDimensions(elm as HTMLElement) : false);
diff --git a/packages/overlayscrollbars/src/support/dom/traversal.ts b/packages/overlayscrollbars/src/support/dom/traversal.ts
index df61979..c2c37a7 100644
--- a/packages/overlayscrollbars/src/support/dom/traversal.ts
+++ b/packages/overlayscrollbars/src/support/dom/traversal.ts
@@ -1,7 +1,5 @@
import { each, from } from 'support/utils/array';
-const elementIsVisible = (elm: HTMLElement): boolean => !!(elm.offsetWidth || elm.offsetHeight || elm.getClientRects().length);
-
/**
* Find all elements with the passed selector, outgoing (and including) the passed element or the document if no element was provided.
* @param selector The selector which has to be searched by.
@@ -29,20 +27,7 @@ export const findFirst = (selector: string, elm?: Element | null): Element | nul
* @param elm The element which has to be compared with the passed selector.
* @param selector The selector which has to be compared with the passed element. Additional selectors: ':visible' and ':hidden'.
*/
-export const is = (elm: Element | null, selector: string): boolean => {
- if (elm) {
- if (selector === ':visible') {
- return elementIsVisible(elm as HTMLElement);
- }
- if (selector === ':hidden') {
- return !elementIsVisible(elm as HTMLElement);
- }
- if (elm.matches(selector)) {
- return true;
- }
- }
- return false;
-};
+export const is = (elm: Element | null, selector: string): boolean => (elm ? elm.matches(selector) : false);
/**
* Returns the children (no text-nodes or comments) of the passed element which are matching the passed selector. An empty array is returned if the passed element is null.
diff --git a/packages/overlayscrollbars/tests/jsdom/support/dom/dimensions.test.ts b/packages/overlayscrollbars/tests/jsdom/support/dom/dimensions.test.ts
index b0078fc..7344eec 100644
--- a/packages/overlayscrollbars/tests/jsdom/support/dom/dimensions.test.ts
+++ b/packages/overlayscrollbars/tests/jsdom/support/dom/dimensions.test.ts
@@ -1,5 +1,6 @@
import { isNumber, isPlainObject } from 'support/utils/types';
-import { windowSize, offsetSize, clientSize, getBoundingClientRect } from 'support/dom/dimensions';
+import { createDiv } from 'support/dom/create';
+import { windowSize, offsetSize, clientSize, getBoundingClientRect, hasDimensions } from 'support/dom/dimensions';
describe('dom dimensions', () => {
describe('offsetSize', () => {
@@ -44,4 +45,22 @@ describe('dom dimensions', () => {
test('getBoundingClientRect', () => {
expect(getBoundingClientRect(document.body)).toEqual(document.body.getBoundingClientRect());
});
+
+ describe('hasDimensions', () => {
+ test('DOM element', () => {
+ const result = hasDimensions(document.body);
+ expect(result).toBe(true);
+ });
+
+ test('generated element', () => {
+ const div = createDiv();
+ const result = hasDimensions(div);
+ expect(result).toBe(false);
+ });
+
+ test('null', () => {
+ const result = hasDimensions(null);
+ expect(result).toBe(false);
+ });
+ });
});
diff --git a/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts b/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts
index f7b64ed..2062c33 100644
--- a/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts
+++ b/packages/overlayscrollbars/tests/jsdom/support/dom/traversal.test.ts
@@ -119,11 +119,6 @@ describe('dom traversal', () => {
expect(is(findFirst('.div-class'), '.other-class')).toBe(false);
});
- test('visibility', () => {
- expect(is(findFirst('.div-class'), ':visible')).toBe(false);
- expect(is(findFirst('.div-class'), ':hidden')).toBe(true);
- });
-
test('created', () => {
const div = createDiv();
expect(div.parentNode).toBeNull();
@@ -139,9 +134,6 @@ describe('dom traversal', () => {
expect(is(div, '.div-class')).toBe(false);
expect(is(div, '.other-class')).toBe(false);
-
- expect(is(div, ':visible')).toBe(false);
- expect(is(div, ':hidden')).toBe(true);
});
test('none', () => {
@@ -154,9 +146,6 @@ describe('dom traversal', () => {
expect(is(null, '.div-class')).toBe(false);
expect(is(null, '.other-class')).toBe(false);
-
- expect(is(null, ':visible')).toBe(false);
- expect(is(null, ':hidden')).toBe(false);
});
});
diff --git a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html
index f7b44ad..a97c43c 100644
--- a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html
+++ b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.html
@@ -16,6 +16,23 @@
+
+
+
+
+
+
+
Detected resizes: 0
diff --git a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss
index 7226546..7131e1b 100644
--- a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss
+++ b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.scss
@@ -1,8 +1,10 @@
#target {
- border: 2px solid red;
overflow: scroll;
resize: both;
position: relative;
+ // prevent container from reaching 0x0 dimensions for testing purposes
+ min-width: 50px;
+ min-height: 50px;
}
.padding0 {
@@ -15,6 +17,16 @@
padding: 50px;
}
+.border2 {
+ border: 2px solid red;
+}
+.border10 {
+ border: 10px solid red;
+}
+.border0 {
+ border: none;
+}
+
.heightAuto {
height: auto;
}
@@ -35,3 +47,10 @@
.widthHundred {
width: 100%;
}
+
+.displayNone {
+ display: none;
+}
+.displayBlock {
+ display: block;
+}
diff --git a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.ts b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.ts
index 2a0deb7..9046e77 100644
--- a/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.ts
+++ b/packages/overlayscrollbars/tests/puppeteer/SizeObserver/index.ts
@@ -1,15 +1,28 @@
import 'overlayscrollbars.scss';
import './index.scss';
import { createSizeObserver } from 'overlayscrollbars/observers/createSizeObserver';
-import { from, removeClass, addClass } from 'support';
+import { from, removeClass, addClass, hasDimensions, isString, isNumber, offsetSize } 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 borderSelect: HTMLSelectElement | null = document.querySelector('#border');
+const boxSizingSelect: HTMLSelectElement | null = document.querySelector('#boxSizing');
+const displaySelect: HTMLSelectElement | null = document.querySelector('#display');
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 getSelectOptions = (selectElement: HTMLSelectElement) => {
const arr = from(selectElement.options).map((option) => option.value);
return arr;
@@ -24,31 +37,35 @@ const selectCallback = (event: Event) => {
addClass(targetElm, selectedOption);
};
-heightSelect?.addEventListener('change', selectCallback);
-widthSelect?.addEventListener('change', selectCallback);
-paddingSelect?.addEventListener('change', selectCallback);
+const selectOption = (select: HTMLSelectElement | null, selectedOption: string | number): boolean => {
+ if (!select) {
+ return false;
+ }
-// 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 });
+ const options = getSelectOptions(select);
+ const currValue = select.value;
-let iterations = 0;
-const observerElm = createSizeObserver(() => {
- iterations += 1;
- requestAnimationFrame(() => {
- if (resizesSlot) {
- resizesSlot.textContent = iterations.toString();
- }
- });
-});
+ if (selectedOption === currValue) {
+ return false;
+ }
-targetElm?.appendChild(observerElm);
+ if (isString(selectedOption) && options.includes(selectedOption)) {
+ select.value = selectedOption;
+ } else if (isNumber(selectedOption) && options.length < selectedOption && selectedOption > -1) {
+ select.selectedIndex = selectedOption;
+ }
+
+ let event;
+ if (typeof Event === 'function') {
+ event = new Event('change');
+ } else {
+ event = document.createEvent('Event');
+ event.initEvent('change', true, true);
+ }
+ select.dispatchEvent(event);
+
+ return true;
+};
const waitFor = (func: () => any) => {
const start = Date.now();
@@ -77,52 +94,87 @@ const iterateSelect = async (select: HTMLSelectElement | null, afterEach?: () =>
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;
+ const currOffsetSize = offsetSize(targetElm as HTMLElement);
+ if (selectOption(select, option)) {
+ const newOffsetSize = offsetSize(targetElm as HTMLElement);
+ const offsetSizeChanged = currOffsetSize.w !== newOffsetSize.w || currOffsetSize.h !== newOffsetSize.h;
- let event;
- if (typeof Event === 'function') {
- event = new Event('change');
- } else {
- event = document.createEvent('Event');
- event.initEvent('change', true, true);
- }
- select.dispatchEvent(event);
+ if (hasDimensions(targetElm as HTMLElement) && offsetSizeChanged) {
+ // eslint-disable-next-line
+ await waitFor(() => iterations === currIterations + 1);
+ }
- // eslint-disable-next-line
- await waitFor(() => iterations === currIterations + 1);
-
- if (typeof afterEach === 'function') {
- // eslint-disable-next-line
- await afterEach();
+ if (typeof afterEach === 'function') {
+ // eslint-disable-next-line
+ await afterEach();
+ }
}
}
}
};
-window.iteratePadding = async (afterEach?: () => any) => {
+heightSelect?.addEventListener('change', selectCallback);
+widthSelect?.addEventListener('change', selectCallback);
+paddingSelect?.addEventListener('change', selectCallback);
+borderSelect?.addEventListener('change', selectCallback);
+boxSizingSelect?.addEventListener('change', selectCallback);
+displaySelect?.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 });
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+selectCallback({ target: borderSelect });
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+selectCallback({ target: boxSizingSelect });
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+selectCallback({ target: displaySelect });
+
+const iteratePadding = (window.iteratePadding = async (afterEach?: () => any) => {
await iterateSelect(paddingSelect, afterEach);
-};
-window.iterateHeight = async (afterEach?: () => any) => {
+});
+const iterateBorder = (window.iterateBorder = async (afterEach?: () => any) => {
+ await iterateSelect(borderSelect, afterEach);
+});
+const iterateHeight = (window.iterateHeight = async (afterEach?: () => any) => {
await iterateSelect(heightSelect, afterEach);
-};
-window.iterateWidth = async (afterEach?: () => any) => {
+});
+const iterateWidth = (window.iterateWidth = async (afterEach?: () => any) => {
await iterateSelect(widthSelect, afterEach);
-};
+});
+const iterateBoxSizing = (window.iterateBoxSizing = async (afterEach?: () => any) => {
+ await iterateSelect(boxSizingSelect, afterEach);
+});
+const iterateDisplay = (window.iterateDisplay = async (afterEach?: () => any) => {
+ await iterateSelect(displaySelect, afterEach);
+});
const start = (window.iterate = async () => {
window.setTestResult(null);
targetElm?.removeAttribute('style');
- await iterateHeight(async () => {
- await iterateWidth(async () => {
- await iteratePadding();
+ await iterateDisplay();
+ await iterateBoxSizing(async () => {
+ await iterateHeight(async () => {
+ await iterateWidth(async () => {
+ await iterateBorder(async () => {
+ await iteratePadding();
+ });
+ });
});
});
window.setTestResult(true);
});
startBtn?.addEventListener('click', start);
+
+targetElm?.appendChild(observerElm);