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