Files
OverlayScrollbars/packages/overlayscrollbars/src/lifecycles/lifecycleHub.ts
T
2022-06-23 17:44:15 +02:00

258 lines
7.8 KiB
TypeScript

import {
XY,
WH,
TRBL,
CacheValues,
each,
hasOwnProperty,
isNumber,
scrollLeft,
scrollTop,
assignDeep,
} from 'support';
import { OSOptions } from 'options';
import { getEnvironment } from 'environment';
import { StructureSetup } from 'setups/structureSetup';
import { lifecycleHubOservers, UpdateObserverOptions } from 'lifecycles/lifecycleHubObservers';
import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle';
import { createPaddingLifecycle } from 'lifecycles/paddingLifecycle';
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>;
export type Lifecycle = (
updateHints: LifecycleUpdateHints,
checkOption: LifecycleCheckOption,
force: boolean
) => Partial<LifecycleAdaptiveUpdateHints> | void;
export type LifecycleOptionInfo<T> = [T, boolean];
export interface LifecycleCommunication {
_paddingInfo: {
_absolute: boolean;
_padding: TRBL;
};
_viewportPaddingStyle: StyleObject;
_viewportOverflowScroll: XY<boolean>;
_viewportOverflowAmount: WH<number>;
}
export interface LifecycleAdaptiveUpdateHints {
_sizeChanged: boolean;
_hostMutation: boolean;
_contentMutation: boolean;
_paddingStyleChanged: boolean;
}
export interface LifecycleUpdateHints extends LifecycleAdaptiveUpdateHints {
_directionIsRTL: CacheValues<boolean>;
_heightIntrinsic: CacheValues<boolean>;
}
export interface LifecycleHubState {
_overflowAmount: WH<number>;
}
export interface LifecycleHubInstance {
_update(changedOptions: PartialOptions<OSOptions>, force?: boolean): void;
_state(): LifecycleHubState;
_destroy(): void;
}
export interface LifecycleHub {
_options: OSOptions;
_structureSetup: StructureSetup;
// whether the "viewport arrange" strategy must be used (true if no native scrollbar hiding and scrollbars are overlaid)
_doViewportArrange: boolean;
_getLifecycleCommunication(): LifecycleCommunication;
_setLifecycleCommunication(newLifecycleCommunication?: Partial<LifecycleCommunication>): void;
}
const getPropByPath = <T>(obj: any, path: string): T =>
obj
? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj)
: undefined;
const booleanCacheValuesFallback: CacheValues<boolean> = [false, false, false];
const lifecycleCommunicationFallback: LifecycleCommunication = {
_paddingInfo: {
_absolute: false,
_padding: {
t: 0,
r: 0,
b: 0,
l: 0,
},
},
_viewportOverflowScroll: {
x: false,
y: false,
},
_viewportOverflowAmount: {
w: 0,
h: 0,
},
_viewportPaddingStyle: {
marginRight: 0,
marginBottom: 0,
marginLeft: 0,
paddingTop: 0,
paddingRight: 0,
paddingBottom: 0,
paddingLeft: 0,
},
};
export const createLifecycleHub = (
options: OSOptions,
triggerListener: TriggerEventListener,
structureSetup: StructureSetup,
scrollbarsSetup: ScrollbarsSetup
): LifecycleHubInstance => {
let lifecycleCommunication = lifecycleCommunicationFallback;
let sizeObserver: SizeObserver;
let trinsicObserver: false | TrinsicObserver;
let updateObserverOptions: UpdateObserverOptions;
let destroyObservers: () => void;
const { _viewport } = structureSetup._targetObj;
const {
_nativeScrollbarStyling,
_nativeScrollbarIsOverlaid,
_flexboxGlue,
_addListener: addEnvironmentListener,
_removeListener: removeEnvironmentListener,
} = getEnvironment();
const doViewportArrange =
!_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
const instance: LifecycleHub = {
_options: options,
_structureSetup: structureSetup,
_doViewportArrange: doViewportArrange,
_getLifecycleCommunication: () => lifecycleCommunication,
_setLifecycleCommunication(newLifecycleCommunication) {
lifecycleCommunication = assignDeep({}, lifecycleCommunication, newLifecycleCommunication);
},
};
const lifecycles: Lifecycle[] = [
createTrinsicLifecycle(instance),
createPaddingLifecycle(instance),
createOverflowLifecycle(instance),
];
const updateLifecycles = (
updateHints: Partial<LifecycleUpdateHints>,
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 checkOption: LifecycleCheckOption = (path) => [
getPropByPath(options, path),
force || getPropByPath(changedOptions, path) !== undefined,
];
const adjustScrollOffset = doViewportArrange || !_flexboxGlue;
const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport);
const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport);
// place before updating lifecycles because of possible flushing of debounce
if (updateObserverOptions) {
updateObserverOptions(checkOption);
}
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;
});
if (isNumber(scrollOffsetX)) {
scrollLeft(_viewport, scrollOffsetX);
}
if (isNumber(scrollOffsetY)) {
scrollTop(_viewport, scrollOffsetY);
}
triggerListener('updated', {
updateHints: {
sizeChanged: _sizeChanged,
contentMutation: _contentMutation,
hostMutation: _hostMutation,
directionChanged: finalDirectionIsRTL[1],
heightIntrinsicChanged: finalHeightIntrinsic[1],
},
changedOptions: changedOptions || {},
force: !!force,
});
};
// eslint-disable-next-line prefer-const
[sizeObserver, trinsicObserver, 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: () => ({
_overflowAmount: lifecycleCommunication._viewportOverflowAmount,
}),
_destroy() {
destroyObservers();
removeEnvironmentListener(envUpdateListener);
structureSetup._destroy();
scrollbarsSetup._destroy();
},
};
};