mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-24 11:24:07 +03:00
improve size observer code and tests
This commit is contained in:
@@ -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<boolean> | SizeObserverEntry[] | Event) => {
|
||||
const onSizeChangedCallbackProxy = (sizeChangedContext?: CacheValues<boolean> | ResizeObserverEntry[] | Event) => {
|
||||
const hasDirectionCache = sizeChangedContext && isBoolean((sizeChangedContext as CacheValues<boolean>)._value);
|
||||
|
||||
let skip = false;
|
||||
@@ -100,7 +96,7 @@ export const createSizeObserver = (
|
||||
}
|
||||
// else if its triggered with DirectionCache
|
||||
else if (hasDirectionCache) {
|
||||
doDirectionScroll = (sizeChangedContext as CacheValues<boolean>)._changed; // direction scroll when DirectionCache changed, false toherwise
|
||||
doDirectionScroll = (sizeChangedContext as CacheValues<boolean>)._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);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -2,6 +2,6 @@ import { jsAPI } from 'support/compatibility/vendors';
|
||||
|
||||
export const MutationObserverConstructor = jsAPI<typeof MutationObserver>('MutationObserver');
|
||||
export const IntersectionObserverConstructor = jsAPI<typeof IntersectionObserver>('IntersectionObserver');
|
||||
export const ResizeObserverConstructor: any | undefined = jsAPI('ResizeObserver');
|
||||
export const ResizeObserverConstructor = jsAPI<typeof ResizeObserver>('ResizeObserver');
|
||||
export const cAF = jsAPI<typeof cancelAnimationFrame>('cancelAnimationFrame');
|
||||
export const rAF = jsAPI<typeof requestAnimationFrame>('requestAnimationFrame');
|
||||
|
||||
@@ -87,10 +87,16 @@ export const on = <T extends Event = Event>(
|
||||
* 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);
|
||||
|
||||
+13
@@ -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());
|
||||
});
|
||||
}
|
||||
@@ -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 };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<div id="controls">
|
||||
<button id="resizeobserver-polyfill">ResizeObserver polyfill</button><br />
|
||||
<label for="height">height</label>
|
||||
<select name="height" id="height">
|
||||
<option value="heightAuto">auto</option>
|
||||
|
||||
@@ -7,7 +7,14 @@ describe('SizeObserver', () => {
|
||||
await page.goto(url);
|
||||
});
|
||||
|
||||
test('test', async () => {
|
||||
test('with ResizeOserver', async () => {
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
test('with ResizeOserver polyfill', async () => {
|
||||
await page.click('#resizeobserver-polyfill');
|
||||
await page.waitForTimeout(500);
|
||||
await page.click('#start');
|
||||
await expect(page).toHaveSelector('#testResult.passed');
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { off, preventDefault, stopPropagation, OnOptions } from 'support/dom/events';
|
||||
import { off, preventDefault, stopPropagation, stopAndPrevent, OnOptions } from 'support/dom/events';
|
||||
|
||||
const testElm = document.body;
|
||||
const mockEventListener = (passive?: boolean, add?: (...args: any) => any, remove?: (...args: any) => any) => {
|
||||
@@ -80,7 +80,7 @@ describe('dom events', () => {
|
||||
[true, false].forEach((passiveSupport) => {
|
||||
describe(`passive event listeners support: ${passiveSupport}`, () => {
|
||||
['testEventName', 'testEventName testEventName2 testEventName3'].forEach((eventNames) => {
|
||||
const title = eventNames.split(' ').length === 1 ? 'signle' : 'multiple';
|
||||
const title = eventNames.split(' ').length === 1 ? 'single' : 'multiple';
|
||||
test(title, () => {
|
||||
onOffTest(passiveSupport, eventNames);
|
||||
onOffTest(passiveSupport, eventNames, { _capture: true });
|
||||
@@ -113,7 +113,7 @@ describe('dom events', () => {
|
||||
};
|
||||
|
||||
['testEventName', 'testEventName testEventName2 testEventName3'].forEach((eventNames) => {
|
||||
const title = eventNames.split(' ').length === 1 ? 'signle' : 'multiple';
|
||||
const title = eventNames.split(' ').length === 1 ? 'single' : 'multiple';
|
||||
test(title, () => {
|
||||
offTest(eventNames, false);
|
||||
offTest(eventNames, true);
|
||||
@@ -138,4 +138,15 @@ describe('dom events', () => {
|
||||
stopPropagation(fakeEvent);
|
||||
expect(fakeEvent.stopPropagation).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('stopAndPrevent', () => {
|
||||
// @ts-ignore
|
||||
const fakeEvent: Event = {
|
||||
stopPropagation: jest.fn(),
|
||||
preventDefault: jest.fn(),
|
||||
};
|
||||
stopAndPrevent(fakeEvent);
|
||||
expect(fakeEvent.stopPropagation).toHaveBeenCalled();
|
||||
expect(fakeEvent.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user