mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-08 11:42:26 +03:00
improve adaptive update hints
This commit is contained in:
@@ -9,6 +9,8 @@ import {
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
assignDeep,
|
||||
keys,
|
||||
isBoolean,
|
||||
} from 'support';
|
||||
import { OSOptions } from 'options';
|
||||
import { getEnvironment } from 'environment';
|
||||
@@ -20,8 +22,6 @@ import { createOverflowLifecycle } from 'lifecycles/overflowLifecycle';
|
||||
import { StyleObject, PartialOptions } from 'typings';
|
||||
import { ScrollbarsSetup } from 'setups/scrollbarsSetup';
|
||||
import { TriggerEventListener } from 'eventListeners';
|
||||
import { SizeObserver } from 'observers/sizeObserver';
|
||||
import { TrinsicObserver } from 'observers/trinsicObserver';
|
||||
|
||||
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
||||
|
||||
@@ -29,7 +29,7 @@ export type Lifecycle = (
|
||||
updateHints: LifecycleUpdateHints,
|
||||
checkOption: LifecycleCheckOption,
|
||||
force: boolean
|
||||
) => Partial<LifecycleAdaptiveUpdateHints> | void;
|
||||
) => Partial<LifecycleUpdateHints> | void;
|
||||
|
||||
export type LifecycleOptionInfo<T> = [T, boolean];
|
||||
|
||||
@@ -43,14 +43,11 @@ export interface LifecycleCommunication {
|
||||
_viewportOverflowAmount: WH<number>;
|
||||
}
|
||||
|
||||
export interface LifecycleAdaptiveUpdateHints {
|
||||
export interface LifecycleUpdateHints {
|
||||
_sizeChanged: boolean;
|
||||
_hostMutation: boolean;
|
||||
_contentMutation: boolean;
|
||||
_paddingStyleChanged: boolean;
|
||||
}
|
||||
|
||||
export interface LifecycleUpdateHints extends LifecycleAdaptiveUpdateHints {
|
||||
_directionIsRTL: CacheValues<boolean>;
|
||||
_heightIntrinsic: CacheValues<boolean>;
|
||||
}
|
||||
@@ -79,6 +76,11 @@ const getPropByPath = <T>(obj: any, path: string): T =>
|
||||
? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj)
|
||||
: undefined;
|
||||
|
||||
const applyForceToCache = <T>(cacheValues: CacheValues<T>, force?: boolean): CacheValues<T> => [
|
||||
cacheValues[0],
|
||||
force || cacheValues[1],
|
||||
cacheValues[2],
|
||||
];
|
||||
const booleanCacheValuesFallback: CacheValues<boolean> = [false, false, false];
|
||||
const lifecycleCommunicationFallback: LifecycleCommunication = {
|
||||
_paddingInfo: {
|
||||
@@ -109,6 +111,26 @@ const lifecycleCommunicationFallback: LifecycleCommunication = {
|
||||
},
|
||||
};
|
||||
|
||||
const prepareUpdateHints = <T extends LifecycleUpdateHints>(
|
||||
leading: Required<T>,
|
||||
adaptive?: Partial<T>,
|
||||
force?: boolean
|
||||
): Required<T> => {
|
||||
const result = {};
|
||||
const finalAdaptive = adaptive || {};
|
||||
const objKeys = keys(leading).concat(keys(finalAdaptive));
|
||||
|
||||
each(objKeys, (key) => {
|
||||
const leadingValue = leading[key];
|
||||
const adaptiveValue = finalAdaptive[key];
|
||||
result[key] = isBoolean(leadingValue)
|
||||
? !!force || !!leadingValue || !!adaptiveValue
|
||||
: applyForceToCache(leadingValue || booleanCacheValuesFallback, force);
|
||||
});
|
||||
|
||||
return result as Required<T>;
|
||||
};
|
||||
|
||||
export const createLifecycleHub = (
|
||||
options: OSOptions,
|
||||
triggerListener: TriggerEventListener,
|
||||
@@ -116,8 +138,6 @@ export const createLifecycleHub = (
|
||||
scrollbarsSetup: ScrollbarsSetup
|
||||
): LifecycleHubInstance => {
|
||||
let lifecycleCommunication = lifecycleCommunicationFallback;
|
||||
let sizeObserver: SizeObserver;
|
||||
let trinsicObserver: false | TrinsicObserver;
|
||||
let updateObserverOptions: UpdateObserverOptions;
|
||||
let destroyObservers: () => void;
|
||||
const { _viewport } = structureSetup._targetObj;
|
||||
@@ -150,27 +170,21 @@ export const createLifecycleHub = (
|
||||
changedOptions?: Partial<OSOptions>,
|
||||
force?: boolean
|
||||
) => {
|
||||
let {
|
||||
// eslint-disable-next-line prefer-const
|
||||
_directionIsRTL,
|
||||
// eslint-disable-next-line prefer-const
|
||||
_heightIntrinsic,
|
||||
_sizeChanged = force || false,
|
||||
_hostMutation = force || false,
|
||||
_contentMutation = force || false,
|
||||
_paddingStyleChanged = force || false,
|
||||
} = updateHints || {};
|
||||
|
||||
const finalDirectionIsRTL =
|
||||
_directionIsRTL ||
|
||||
(sizeObserver
|
||||
? sizeObserver._getCurrentCacheValues(force)._directionIsRTL
|
||||
: booleanCacheValuesFallback);
|
||||
const finalHeightIntrinsic =
|
||||
_heightIntrinsic ||
|
||||
(trinsicObserver
|
||||
? trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic
|
||||
: booleanCacheValuesFallback);
|
||||
const initialUpdateHints = prepareUpdateHints(
|
||||
assignDeep(
|
||||
{
|
||||
_sizeChanged: false,
|
||||
_hostMutation: false,
|
||||
_contentMutation: false,
|
||||
_paddingStyleChanged: false,
|
||||
_directionIsRTL: booleanCacheValuesFallback,
|
||||
_heightIntrinsic: booleanCacheValuesFallback,
|
||||
},
|
||||
updateHints
|
||||
),
|
||||
{},
|
||||
force
|
||||
);
|
||||
const checkOption: LifecycleCheckOption = (path) => [
|
||||
getPropByPath(options, path),
|
||||
force || getPropByPath(changedOptions, path) !== undefined,
|
||||
@@ -184,29 +198,13 @@ export const createLifecycleHub = (
|
||||
updateObserverOptions(checkOption);
|
||||
}
|
||||
|
||||
let adaptivedUpdateHints: Required<LifecycleUpdateHints> = initialUpdateHints;
|
||||
each(lifecycles, (lifecycle) => {
|
||||
const {
|
||||
_sizeChanged: adaptiveSizeChanged,
|
||||
_hostMutation: adaptiveHostMutation,
|
||||
_contentMutation: adaptiveContentMutation,
|
||||
_paddingStyleChanged: adaptivePaddingStyleChanged,
|
||||
} = lifecycle(
|
||||
{
|
||||
_directionIsRTL: finalDirectionIsRTL,
|
||||
_heightIntrinsic: finalHeightIntrinsic,
|
||||
_sizeChanged,
|
||||
_hostMutation,
|
||||
_contentMutation,
|
||||
_paddingStyleChanged,
|
||||
},
|
||||
checkOption,
|
||||
!!force
|
||||
) || {};
|
||||
|
||||
_sizeChanged = adaptiveSizeChanged || _sizeChanged;
|
||||
_hostMutation = adaptiveHostMutation || _hostMutation;
|
||||
_contentMutation = adaptiveContentMutation || _contentMutation;
|
||||
_paddingStyleChanged = adaptivePaddingStyleChanged || _paddingStyleChanged;
|
||||
adaptivedUpdateHints = prepareUpdateHints<LifecycleUpdateHints>(
|
||||
adaptivedUpdateHints,
|
||||
lifecycle(adaptivedUpdateHints, checkOption, !!force) || {},
|
||||
force
|
||||
);
|
||||
});
|
||||
|
||||
if (isNumber(scrollOffsetX)) {
|
||||
@@ -218,29 +216,24 @@ export const createLifecycleHub = (
|
||||
|
||||
triggerListener('updated', {
|
||||
updateHints: {
|
||||
sizeChanged: _sizeChanged,
|
||||
contentMutation: _contentMutation,
|
||||
hostMutation: _hostMutation,
|
||||
directionChanged: finalDirectionIsRTL[1],
|
||||
heightIntrinsicChanged: finalHeightIntrinsic[1],
|
||||
sizeChanged: adaptivedUpdateHints._sizeChanged,
|
||||
contentMutation: adaptivedUpdateHints._contentMutation,
|
||||
hostMutation: adaptivedUpdateHints._hostMutation,
|
||||
directionChanged: adaptivedUpdateHints._directionIsRTL[1],
|
||||
heightIntrinsicChanged: adaptivedUpdateHints._heightIntrinsic[1],
|
||||
},
|
||||
changedOptions: changedOptions || {},
|
||||
force: !!force,
|
||||
});
|
||||
};
|
||||
// eslint-disable-next-line prefer-const
|
||||
[sizeObserver, trinsicObserver, updateObserverOptions, destroyObservers] = lifecycleHubOservers(
|
||||
instance,
|
||||
updateLifecycles
|
||||
);
|
||||
[updateObserverOptions, destroyObservers] = lifecycleHubOservers(instance, updateLifecycles);
|
||||
|
||||
const update = (changedOptions: Partial<OSOptions>, force?: boolean) =>
|
||||
updateLifecycles({}, changedOptions, force);
|
||||
const envUpdateListener = update.bind(0, {}, true);
|
||||
addEnvironmentListener(envUpdateListener);
|
||||
|
||||
console.log(getEnvironment());
|
||||
|
||||
return {
|
||||
_update: update,
|
||||
_state: () => ({
|
||||
|
||||
@@ -11,23 +11,14 @@ import {
|
||||
CacheValues,
|
||||
} from 'support';
|
||||
import { getEnvironment } from 'environment';
|
||||
import {
|
||||
createSizeObserver,
|
||||
SizeObserver,
|
||||
SizeObserverCallbackParams,
|
||||
} from 'observers/sizeObserver';
|
||||
import { createTrinsicObserver, TrinsicObserver } from 'observers/trinsicObserver';
|
||||
import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver';
|
||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||
import { createDOMObserver, DOMObserver } from 'observers/domObserver';
|
||||
import { LifecycleHub, LifecycleCheckOption, LifecycleUpdateHints } from 'lifecycles/lifecycleHub';
|
||||
|
||||
export type UpdateObserverOptions = (checkOption: LifecycleCheckOption) => void;
|
||||
|
||||
export type LifecycleHubObservers = [
|
||||
SizeObserver,
|
||||
TrinsicObserver | false,
|
||||
UpdateObserverOptions,
|
||||
() => void
|
||||
];
|
||||
export type LifecycleHubObservers = [UpdateObserverOptions, () => void];
|
||||
|
||||
// const hostSelector = `.${classNameHost}`;
|
||||
|
||||
@@ -122,6 +113,7 @@ export const lifecycleHubOservers = (
|
||||
!_sizeChanged || _appear
|
||||
? updateLifecycles
|
||||
: updateLifecyclesWithDebouncedAdaptiveUpdateHints;
|
||||
|
||||
updateFn({
|
||||
_sizeChanged,
|
||||
_directionIsRTL: _directionIsRTLCache,
|
||||
@@ -146,9 +138,9 @@ export const lifecycleHubOservers = (
|
||||
}
|
||||
};
|
||||
|
||||
const trinsicObserver =
|
||||
const destroyTrinsicObserver =
|
||||
(_content || !_flexboxGlue) && createTrinsicObserver(_host, onTrinsicChanged);
|
||||
const sizeObserver = createSizeObserver(_host, onSizeChanged, {
|
||||
const destroySizeObserver = createSizeObserver(_host, onSizeChanged, {
|
||||
_appear: true,
|
||||
_direction: !_nativeScrollbarStyling,
|
||||
});
|
||||
@@ -212,13 +204,11 @@ export const lifecycleHubOservers = (
|
||||
updateViewportAttrsFromHost();
|
||||
|
||||
return [
|
||||
sizeObserver,
|
||||
trinsicObserver,
|
||||
updateOptions,
|
||||
() => {
|
||||
contentMutationObserver && contentMutationObserver._destroy();
|
||||
trinsicObserver && trinsicObserver._destroy();
|
||||
sizeObserver._destroy();
|
||||
destroyTrinsicObserver && destroyTrinsicObserver();
|
||||
destroySizeObserver();
|
||||
hostMutationObserver._destroy();
|
||||
},
|
||||
];
|
||||
|
||||
@@ -45,12 +45,7 @@ export interface SizeObserverCallbackParams {
|
||||
_appear?: boolean;
|
||||
}
|
||||
|
||||
export interface SizeObserver {
|
||||
_destroy(): void;
|
||||
_getCurrentCacheValues(force?: boolean): {
|
||||
_directionIsRTL: CacheValues<boolean>;
|
||||
};
|
||||
}
|
||||
export type DestroySizeObserver = () => void;
|
||||
|
||||
const animationStartEventName = 'animationstart';
|
||||
const scrollEventName = 'scroll';
|
||||
@@ -69,7 +64,7 @@ export const createSizeObserver = (
|
||||
target: HTMLElement,
|
||||
onSizeChangedCallback: (params: SizeObserverCallbackParams) => any,
|
||||
options?: SizeObserverOptions
|
||||
): SizeObserver => {
|
||||
): DestroySizeObserver => {
|
||||
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } =
|
||||
options || {};
|
||||
const { _rtlScrollBehavior: rtlScrollBehavior } = getEnvironment();
|
||||
@@ -271,17 +266,8 @@ export const createSizeObserver = (
|
||||
|
||||
prependChildren(target, sizeObserver);
|
||||
|
||||
return {
|
||||
_destroy() {
|
||||
runEach(offListeners);
|
||||
removeElements(sizeObserver);
|
||||
},
|
||||
_getCurrentCacheValues(force?: boolean) {
|
||||
return {
|
||||
_directionIsRTL: directionIsRTLCache
|
||||
? directionIsRTLCache[1](force) // get current cache values
|
||||
: [false, false, false],
|
||||
};
|
||||
},
|
||||
return () => {
|
||||
runEach(offListeners);
|
||||
removeElements(sizeObserver);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,12 +13,7 @@ import {
|
||||
import { createSizeObserver } from 'observers/sizeObserver';
|
||||
import { classNameTrinsicObserver } from 'classnames';
|
||||
|
||||
export interface TrinsicObserver {
|
||||
_destroy(): void;
|
||||
_getCurrentCacheValues(force?: boolean): {
|
||||
_heightIntrinsic: CacheValues<boolean>;
|
||||
};
|
||||
}
|
||||
export type DestroyTrinsicObserver = () => void;
|
||||
|
||||
const isHeightIntrinsic = (ioEntryOrSize: IntersectionObserverEntry | WH<number>): boolean =>
|
||||
(ioEntryOrSize as WH<number>).h === 0 ||
|
||||
@@ -34,10 +29,10 @@ const isHeightIntrinsic = (ioEntryOrSize: IntersectionObserverEntry | WH<number>
|
||||
export const createTrinsicObserver = (
|
||||
target: HTMLElement,
|
||||
onTrinsicChangedCallback: (heightIntrinsic: CacheValues<boolean>) => any
|
||||
): TrinsicObserver => {
|
||||
): DestroyTrinsicObserver => {
|
||||
const trinsicObserver = createDiv(classNameTrinsicObserver);
|
||||
const offListeners: (() => void)[] = [];
|
||||
const [updateHeightIntrinsicCache, getCurrentHeightIntrinsicCache] = createCache({
|
||||
const [updateHeightIntrinsicCache] = createCache({
|
||||
_initialValue: false,
|
||||
});
|
||||
|
||||
@@ -72,21 +67,14 @@ export const createTrinsicObserver = (
|
||||
const newSize = offsetSize(trinsicObserver);
|
||||
triggerOnTrinsicChangedCallback(newSize);
|
||||
};
|
||||
push(offListeners, createSizeObserver(trinsicObserver, onSizeChanged)._destroy);
|
||||
push(offListeners, createSizeObserver(trinsicObserver, onSizeChanged));
|
||||
onSizeChanged();
|
||||
}
|
||||
|
||||
prependChildren(target, trinsicObserver);
|
||||
|
||||
return {
|
||||
_destroy() {
|
||||
runEach(offListeners);
|
||||
removeElements(trinsicObserver);
|
||||
},
|
||||
_getCurrentCacheValues(force?: boolean) {
|
||||
return {
|
||||
_heightIntrinsic: getCurrentHeightIntrinsicCache(force),
|
||||
};
|
||||
},
|
||||
return () => {
|
||||
runEach(offListeners);
|
||||
removeElements(trinsicObserver);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
||||
const resizesSlot: HTMLButtonElement | null = document.querySelector('#resizes');
|
||||
const preInitChildren = targetElm?.children.length;
|
||||
|
||||
const sizeObserver = createSizeObserver(
|
||||
const destroySizeObserver = createSizeObserver(
|
||||
targetElm as HTMLElement,
|
||||
({ _directionIsRTLCache, _sizeChanged }) => {
|
||||
if (_sizeChanged) {
|
||||
@@ -129,17 +129,19 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
|
||||
if (dirChanged) {
|
||||
await waitForOrFailTest(() => {
|
||||
const expectedCacheValue = newDir === 'rtl';
|
||||
// const expectedCacheValue = newDir === 'rtl';
|
||||
should.equal(
|
||||
directionIterations,
|
||||
currDirectionIterations + 1,
|
||||
'Direction change was detected correctly.'
|
||||
);
|
||||
/*
|
||||
should.equal(
|
||||
sizeObserver._getCurrentCacheValues()._directionIsRTL[0],
|
||||
expectedCacheValue,
|
||||
'Direction cache value is correct.'
|
||||
);
|
||||
*/
|
||||
});
|
||||
}
|
||||
|
||||
@@ -261,7 +263,7 @@ const start = async () => {
|
||||
});
|
||||
await cleanBoxSizingChange();
|
||||
|
||||
sizeObserver._destroy();
|
||||
destroySizeObserver();
|
||||
should.equal(
|
||||
targetElm?.children.length,
|
||||
preInitChildren,
|
||||
|
||||
+17
-12
@@ -24,18 +24,21 @@ const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
||||
const changesSlot: HTMLButtonElement | null = document.querySelector('#changes');
|
||||
const preInitChildren = targetElm?.children.length;
|
||||
|
||||
const trinsicObserver = createTrinsicObserver(targetElm as HTMLElement, (heightIntrinsicCache) => {
|
||||
const [currentHeightIntrinsic, currentHeightIntrinsicChanged] = heightIntrinsicCache;
|
||||
if (currentHeightIntrinsicChanged) {
|
||||
heightIterations += 1;
|
||||
heightIntrinsic = currentHeightIntrinsic;
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
if (changesSlot) {
|
||||
changesSlot.textContent = heightIterations.toString();
|
||||
const destroyTrinsicObserver = createTrinsicObserver(
|
||||
targetElm as HTMLElement,
|
||||
(heightIntrinsicCache) => {
|
||||
const [currentHeightIntrinsic, currentHeightIntrinsicChanged] = heightIntrinsicCache;
|
||||
if (currentHeightIntrinsicChanged) {
|
||||
heightIterations += 1;
|
||||
heightIntrinsic = currentHeightIntrinsic;
|
||||
}
|
||||
});
|
||||
});
|
||||
requestAnimationFrame(() => {
|
||||
if (changesSlot) {
|
||||
changesSlot.textContent = heightIterations.toString();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const envElmSelectCallback = generateClassChangeSelectCallback(envElm as HTMLElement);
|
||||
const targetElmSelectCallback = generateClassChangeSelectCallback(targetElm as HTMLElement);
|
||||
@@ -75,11 +78,13 @@ const iterate = async (select: HTMLSelectElement | null, afterEach?: () => any)
|
||||
'Height intrinsic change has been detected correctly.'
|
||||
);
|
||||
}
|
||||
/*
|
||||
should.equal(
|
||||
trinsicObserver._getCurrentCacheValues()._heightIntrinsic[0],
|
||||
newHeightIntrinsic,
|
||||
'Height intrinsic cache value is correct.'
|
||||
);
|
||||
*/
|
||||
});
|
||||
},
|
||||
afterEach,
|
||||
@@ -148,7 +153,7 @@ const start = async () => {
|
||||
});
|
||||
await changeWhileHidden();
|
||||
|
||||
trinsicObserver._destroy();
|
||||
destroyTrinsicObserver();
|
||||
should.equal(
|
||||
targetElm?.children.length,
|
||||
preInitChildren,
|
||||
|
||||
Reference in New Issue
Block a user