From a3573f87a1314a445a6b8af9cb57d06c6a1da1ba Mon Sep 17 00:00:00 2001 From: Rene Date: Wed, 14 Apr 2021 14:09:45 +0200 Subject: [PATCH] improve size observer code and tests --- .../src/observers/sizeObserver.ts | 54 +++++++++---------- .../src/styles/sizeobserver.scss | 4 +- .../src/support/compatibility/apis.ts | 2 +- .../src/support/dom/events.ts | 10 +++- .../sizeObserver/handleResizeObserver.ts | 13 +++++ .../observers/sizeObserver/index.browser.ts | 41 +++++++------- .../browser/observers/sizeObserver/index.html | 1 + .../observers/sizeObserver/index.test.ts | 9 +++- .../tests/jsdom/support/dom/events.test.ts | 17 ++++-- 9 files changed, 93 insertions(+), 58 deletions(-) create mode 100644 packages/overlayscrollbars/tests/browser/observers/sizeObserver/handleResizeObserver.ts diff --git a/packages/overlayscrollbars/src/observers/sizeObserver.ts b/packages/overlayscrollbars/src/observers/sizeObserver.ts index 0421d95..d94c701 100644 --- a/packages/overlayscrollbars/src/observers/sizeObserver.ts +++ b/packages/overlayscrollbars/src/observers/sizeObserver.ts @@ -12,8 +12,7 @@ import { prependChildren, removeElements, on, - preventDefault, - stopPropagation, + stopAndPrevent, addClass, equalWH, push, @@ -21,8 +20,6 @@ import { rAF, ResizeObserverConstructor, isArray, - indexOf, - each, isBoolean, } from 'support'; import { getEnvironment } from 'environment'; @@ -35,10 +32,6 @@ import { classNameSizeObserverListenerItemFinal, } from 'classnames'; -interface SizeObserverEntry { - contentRect: DOMRectReadOnly; -} - export type SizeObserverOptions = { _direction?: boolean; _appear?: boolean }; export interface SizeObserver { @@ -50,21 +43,24 @@ export interface SizeObserver { }; } -const animationStartEventName = 'animationstart'; -const scrollEventName = 'scroll'; -const scrollAmount = 3333333; +/* const directionIsRTLMap = { direction: ['rtl'], - // 'writing-mode': ['sideways-rl', 'tb', 'tb-rl', 'vertical-rl'], + 'writing-mode': ['sideways-rl', 'tb', 'tb-rl', 'vertical-rl'], }; const directionIsRTL = (elm: HTMLElement): boolean => { let isRTL = false; - const styles = style(elm, ['direction' /* , 'writing-mode' */]); + const styles = style(elm, ['direction', 'writing-mode']); each(styles, (value, key) => { isRTL = isRTL || indexOf(directionIsRTLMap[key], value) > -1; }); return isRTL; }; +*/ +const animationStartEventName = 'animationstart'; +const scrollEventName = 'scroll'; +const scrollAmount = 3333333; +const directionIsRTL = (elm: HTMLElement): boolean => style(elm, 'direction') === 'rtl'; const domRectHasDimensions = (rect?: DOMRectReadOnly) => rect && (rect.height || rect.width); export const createSizeObserver = ( @@ -86,7 +82,7 @@ export const createSizeObserver = ( (!domRectHasDimensions(currVal) && domRectHasDimensions(newVal)) ), }); - const onSizeChangedCallbackProxy = (sizeChangedContext?: CacheValues | SizeObserverEntry[] | Event) => { + const onSizeChangedCallbackProxy = (sizeChangedContext?: CacheValues | ResizeObserverEntry[] | Event) => { const hasDirectionCache = sizeChangedContext && isBoolean((sizeChangedContext as CacheValues)._value); let skip = false; @@ -100,7 +96,7 @@ export const createSizeObserver = ( } // else if its triggered with DirectionCache else if (hasDirectionCache) { - doDirectionScroll = (sizeChangedContext as CacheValues)._changed; // direction scroll when DirectionCache changed, false toherwise + doDirectionScroll = (sizeChangedContext as CacheValues)._changed; // direction scroll when DirectionCache changed, false otherwise } if (observeDirectionChange) { @@ -150,24 +146,22 @@ export const createSizeObserver = ( onSizeChangedCallbackProxy(); } }; - const onScroll = (scrollEvent?: Event) => { + const onScroll = (scrollEvent?: Event | false) => { currSize = offsetSize(listenerElement); isDirty = !scrollEvent || !equalWH(currSize, cacheSize); - if (scrollEvent && isDirty && !rAFId) { - cAF!(rAFId); - rAFId = rAF!(onResized); - } else if (!scrollEvent) { + if (scrollEvent) { + stopAndPrevent(scrollEvent); + + if (isDirty && !rAFId) { + cAF!(rAFId); + rAFId = rAF!(onResized); + } + } else { onResized(); } reset(); - - if (scrollEvent) { - preventDefault(scrollEvent); - stopPropagation(scrollEvent); - } - return false; }; push(offListeners, [on(expandElement, scrollEventName, onScroll), on(shrinkElement, scrollEventName, onScroll)]); @@ -177,8 +171,10 @@ export const createSizeObserver = ( width: scrollAmount, height: scrollAmount, }); + reset(); - appearCallback = observeAppearChange ? () => onScroll() : reset; + + appearCallback = observeAppearChange ? onScroll.bind(0, false) : reset; } if (observeDirectionChange) { @@ -198,9 +194,7 @@ export const createSizeObserver = ( onSizeChangedCallbackProxy(directionIsRTLCacheValues); } - preventDefault(event); - stopPropagation(event); - return false; + stopAndPrevent(event); }) ); } diff --git a/packages/overlayscrollbars/src/styles/sizeobserver.scss b/packages/overlayscrollbars/src/styles/sizeobserver.scss index 8c92b5e..52731eb 100644 --- a/packages/overlayscrollbars/src/styles/sizeobserver.scss +++ b/packages/overlayscrollbars/src/styles/sizeobserver.scss @@ -29,8 +29,8 @@ $scrollbar-cushion: 100px; } .os-size-observer-appear { - animation-duration: 0.001s; - animation-name: os-size-observer-appear-animation; + // "forwards" is important for older browsers + animation: os-size-observer-appear-animation 1ms forwards; } .os-size-observer-listener { diff --git a/packages/overlayscrollbars/src/support/compatibility/apis.ts b/packages/overlayscrollbars/src/support/compatibility/apis.ts index d080c88..d9c7ee0 100644 --- a/packages/overlayscrollbars/src/support/compatibility/apis.ts +++ b/packages/overlayscrollbars/src/support/compatibility/apis.ts @@ -2,6 +2,6 @@ import { jsAPI } from 'support/compatibility/vendors'; export const MutationObserverConstructor = jsAPI('MutationObserver'); export const IntersectionObserverConstructor = jsAPI('IntersectionObserver'); -export const ResizeObserverConstructor: any | undefined = jsAPI('ResizeObserver'); +export const ResizeObserverConstructor = jsAPI('ResizeObserver'); export const cAF = jsAPI('cancelAnimationFrame'); export const rAF = jsAPI('requestAnimationFrame'); diff --git a/packages/overlayscrollbars/src/support/dom/events.ts b/packages/overlayscrollbars/src/support/dom/events.ts index 4fab1d8..622dcfc 100644 --- a/packages/overlayscrollbars/src/support/dom/events.ts +++ b/packages/overlayscrollbars/src/support/dom/events.ts @@ -87,10 +87,16 @@ export const on = ( * Shorthand for the stopPropagation event Method. * @param evt The event of which the stopPropagation method shall be called. */ -export const stopPropagation = (evt: Event) => evt.stopPropagation(); +export const stopPropagation = (evt: Event): void => evt.stopPropagation(); /** * Shorthand for the preventDefault event Method. * @param evt The event of which the preventDefault method shall be called. */ -export const preventDefault = (evt: Event) => evt.preventDefault(); +export const preventDefault = (evt: Event): void => evt.preventDefault(); + +/** + * Shorthand for the stopPropagation and preventDefault event Method. + * @param evt The event of which the stopPropagation and preventDefault methods shall be called. + */ +export const stopAndPrevent = (evt: Event): void => (stopPropagation(evt) as undefined) || (preventDefault(evt) as undefined); diff --git a/packages/overlayscrollbars/tests/browser/observers/sizeObserver/handleResizeObserver.ts b/packages/overlayscrollbars/tests/browser/observers/sizeObserver/handleResizeObserver.ts new file mode 100644 index 0000000..183faa3 --- /dev/null +++ b/packages/overlayscrollbars/tests/browser/observers/sizeObserver/handleResizeObserver.ts @@ -0,0 +1,13 @@ +const url = new URL(window.location.toString()); +const params = url.searchParams; +const useResizeObserverPolyfill = Boolean(params.get('resizeobserverpolyfill')); + +if (useResizeObserverPolyfill) { + // @ts-ignore + window.ResizeObserver = undefined; +} else { + document.getElementById('resizeobserver-polyfill')?.addEventListener('click', () => { + params.set('resizeobserverpolyfill', 'true'); + window.location.assign(url.toString()); + }); +} diff --git a/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.browser.ts b/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.browser.ts index 2eab64d..b7dcb61 100644 --- a/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.browser.ts @@ -1,7 +1,7 @@ import 'styles/overlayscrollbars.scss'; import './index.scss'; +import './handleResizeObserver'; import should from 'should'; -// import { generateClassChangeSelectCallback, iterateSelect, setTestResult, waitForOrFailTest, timeout } from '@/testing-browser'; import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select'; import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult'; import { timeout } from '@/testing-browser/timeout'; @@ -34,6 +34,23 @@ const directionSelect: HTMLSelectElement | null = document.querySelector('#direc const startBtn: HTMLButtonElement | null = document.querySelector('#start'); const resizesSlot: HTMLButtonElement | null = document.querySelector('#resizes'); +const sizeObserver = createSizeObserver( + targetElm as HTMLElement, + (directionIsRTLCache?: any) => { + if (directionIsRTLCache) { + directionIterations += 1; + } else { + sizeIterations += 1; + } + requestAnimationFrame(() => { + if (resizesSlot) { + resizesSlot.textContent = (directionIterations + sizeIterations).toString(); + } + }); + }, + { _direction: true, _appear: true } +); + const selectCallback = generateClassChangeSelectCallback(targetElm as HTMLElement); const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any) => { interface IterateSelect { @@ -84,7 +101,9 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any) should.equal(sizeIterations, currSizeIterations + 1); } if (dirChanged) { + const expectedCacheValue = newDir === 'rtl'; should.equal(directionIterations, currDirectionIterations + 1); + should.equal(sizeObserver._getCurrentCacheValues()._directionIsRTL._value, expectedCacheValue); } }); } @@ -133,7 +152,6 @@ const iterateDisplay = async (afterEach?: () => any) => { const iterateDirection = async (afterEach?: () => any) => { await iterate(directionSelect, afterEach); }; - const start = async () => { setTestResult(null); @@ -156,26 +174,11 @@ const start = async () => { }); }); + sizeObserver._destroy(); + should.ok(targetElm?.children.length === 0); setTestResult(true); }; startBtn?.addEventListener('click', start); -createSizeObserver( - targetElm as HTMLElement, - (directionIsRTLCache?: any) => { - if (directionIsRTLCache) { - directionIterations += 1; - } else { - sizeIterations += 1; - } - requestAnimationFrame(() => { - if (resizesSlot) { - resizesSlot.textContent = (directionIterations + sizeIterations).toString(); - } - }); - }, - { _direction: true, _appear: true } -); - export { start }; diff --git a/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.html b/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.html index 223daad..b1bca45 100644 --- a/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.html +++ b/packages/overlayscrollbars/tests/browser/observers/sizeObserver/index.html @@ -1,4 +1,5 @@
+