improve adaptive update hints

This commit is contained in:
Rene
2022-06-24 12:01:32 +02:00
parent b2d93d07d5
commit 0413be4b4a
6 changed files with 98 additions and 134 deletions
@@ -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,
@@ -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,