new setup and update structure

This commit is contained in:
Rene
2022-06-28 02:07:45 +02:00
parent 43f86b74c8
commit 88eebe95ed
32 changed files with 2380 additions and 2224 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -72,8 +72,7 @@ export interface Environment {
_rtlScrollBehavior: { n: boolean; i: boolean };
_flexboxGlue: boolean;
_cssCustomProperties: boolean;
_addListener(listener: OnEnvironmentChanged): void;
_removeListener(listener: OnEnvironmentChanged): void;
_addListener(listener: OnEnvironmentChanged): () => void;
_getInitializationStrategy(): InitializationStrategy;
_setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
_getDefaultOptions(): OSOptions;
@@ -203,11 +202,9 @@ const createEnvironment = (): Environment => {
_cssCustomProperties: style(envElm, 'zIndex') === '-1',
_rtlScrollBehavior: getRtlScrollBehavior(envElm, envChildElm),
_flexboxGlue: getFlexboxGlue(envElm, envChildElm),
_addListener(listener: OnEnvironmentChanged): void {
_addListener(listener) {
onChangedListener.add(listener);
},
_removeListener(listener: OnEnvironmentChanged): void {
onChangedListener.delete(listener);
return () => onChangedListener.delete(listener);
},
_getInitializationStrategy: () => ({ ...initializationStrategy }),
_setInitializationStrategy(newInitializationStrategy) {
@@ -254,8 +251,11 @@ const createEnvironment = (): Environment => {
const isZoom = deltaIsBigger && difference && dprChanged;
if (isZoom) {
const newScrollbarSize = (environmentInstance._nativeScrollbarSize =
getNativeScrollbarSize(body, envElm));
const newScrollbarSize = getNativeScrollbarSize(body, envElm);
// keep the object same!
environmentInstance._nativeScrollbarSize.x = newScrollbarSize.x;
environmentInstance._nativeScrollbarSize.y = newScrollbarSize.y;
removeElements(envElm);
if (scrollbarSize.x !== newScrollbarSize.x || scrollbarSize.y !== newScrollbarSize.y) {
@@ -1,6 +1,13 @@
import { OSOptions } from 'options';
import { each, from, isArray, keys, XY } from 'support';
import { createEventListenerHub, XY } from 'support';
import { PartialOptions } from 'typings';
import type {
InitialEventListeners,
AddEventListener,
RemoveEventListener,
TriggerEventListener,
EventListener,
} from 'support/eventListeners';
/*
onScrollStart : null,
onScroll : null,
@@ -35,7 +42,7 @@ export interface OnOverflowChangedEventListenerArgs {
};
}
export interface EventListenerArgsMap {
export interface OSEventListenersNameArgsMap {
initialized: undefined;
initializationWithdrawn: undefined;
overflowChanged: OnOverflowChangedEventListenerArgs;
@@ -43,85 +50,20 @@ export interface EventListenerArgsMap {
destroyed: undefined;
}
export type OSEventListener<N extends keyof EventListenerArgsMap = keyof EventListenerArgsMap> =
undefined extends EventListenerArgsMap[N] ? () => void : (args: EventListenerArgsMap[N]) => void;
export type OSEventListener<
N extends Extract<keyof OSEventListenersNameArgsMap, string> = Extract<
keyof OSEventListenersNameArgsMap,
string
>
> = EventListener<OSEventListenersNameArgsMap, N>;
export type AddEventListener = <N extends keyof EventListenerArgsMap>(
name: N,
listener: OSEventListener<N> | OSEventListener<N>[]
) => () => void;
export type AddOSEventListener = AddEventListener<OSEventListenersNameArgsMap>;
export type RemoveEventListener = <N extends keyof EventListenerArgsMap>(
name?: N,
listener?: OSEventListener<N> | OSEventListener<N>[]
) => void;
export type RemoveOSEventListener = RemoveEventListener<OSEventListenersNameArgsMap>;
export type TriggerEventListener = <N extends keyof EventListenerArgsMap>(
name: N,
...args: undefined extends EventListenerArgsMap[N]
? [args?: never]
: [args: EventListenerArgsMap[N]]
) => void;
export type TriggerOSEventListener = TriggerEventListener<OSEventListenersNameArgsMap>;
export type EventListenersHub = [AddEventListener, RemoveEventListener, TriggerEventListener];
export type InitialOSEventListeners = InitialEventListeners<OSEventListenersNameArgsMap>;
export type EventListenersMap = {
[K in keyof EventListenerArgsMap]?: OSEventListener<K> | OSEventListener<K>[];
};
const manageListener = <N extends keyof EventListenerArgsMap>(
callback: (listener?: OSEventListener<any>) => void,
listener?: OSEventListener<N> | OSEventListener<N>[]
) => {
each(isArray(listener) ? listener : [listener], callback);
};
export const createEventListenerHub = (
initialEventListeners?: EventListenersMap
): EventListenersHub => {
const events = new Map<keyof EventListenerArgsMap, Set<OSEventListener>>();
const removeEvent: RemoveEventListener = (name?, listener?) => {
if (name) {
const eventSet = events.get(name);
manageListener((currListener) => {
if (eventSet) {
eventSet[currListener ? 'delete' : 'clear'](currListener!);
}
}, listener);
} else {
events.forEach((eventSet) => {
eventSet.clear();
});
events.clear();
}
};
const addEvent: AddEventListener = (name, listener) => {
const eventSet = events.get(name) || new Set();
events.set(name, eventSet);
manageListener((currListener) => {
eventSet.add(currListener!);
}, listener);
return removeEvent.bind(0, name, listener as any);
};
const triggerEvent: TriggerEventListener = (name, args?) => {
const eventSet = events.get(name);
each(from(eventSet), (event) => {
if (args) {
(event as (args: any) => void)(args);
} else {
(event as () => void)();
}
});
};
const initialListenerKeys = keys(initialEventListeners) as (keyof EventListenerArgsMap)[];
each(initialListenerKeys, (key) => {
addEvent(key, initialEventListeners![key] as any);
});
return [addEvent, removeEvent, triggerEvent];
};
export const createOSEventListenerHub = (initialEventListeners?: InitialOSEventListeners) =>
createEventListenerHub(initialEventListeners);
@@ -1,280 +0,0 @@
import {
XY,
WH,
TRBL,
CacheValues,
each,
hasOwnProperty,
isNumber,
scrollLeft,
scrollTop,
assignDeep,
keys,
isBoolean,
} 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';
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
export type Lifecycle = (
updateHints: LifecycleUpdateHints,
checkOption: LifecycleCheckOption,
force: boolean
) => Partial<LifecycleUpdateHints> | void;
export type LifecycleOptionInfo<T> = [T, boolean];
export interface LifecycleCommunication {
_padding: TRBL;
_paddingAbsolute: boolean;
_viewportPaddingStyle: StyleObject;
_viewportOverflowScrollCache: CacheValues<XY<boolean>>;
_viewportOverflowAmountCache: CacheValues<WH<number>>;
}
export interface LifecycleUpdateHints {
_sizeChanged: boolean;
_hostMutation: boolean;
_contentMutation: boolean;
_paddingStyleChanged: boolean;
_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 applyForceToCache = <T>(cacheValues: CacheValues<T>, force?: boolean): CacheValues<T> => [
cacheValues[0],
force || cacheValues[1],
cacheValues[2],
];
const booleanCacheValuesFallback: CacheValues<boolean> = [false, false, false];
const initialLifecycleCommunication: LifecycleCommunication = {
_padding: {
t: 0,
r: 0,
b: 0,
l: 0,
},
_paddingAbsolute: false,
_viewportOverflowScrollCache: [
{
x: false,
y: false,
},
false,
],
_viewportOverflowAmountCache: [
{
w: 0,
h: 0,
},
false,
],
_viewportPaddingStyle: {
marginRight: 0,
marginBottom: 0,
marginLeft: 0,
paddingTop: 0,
paddingRight: 0,
paddingBottom: 0,
paddingLeft: 0,
},
};
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>;
};
const createOverflowChangedArgs = (overflowAmount: WH<number>, overflowScroll: XY<boolean>) => ({
amount: {
x: overflowAmount.w,
y: overflowAmount.h,
},
overflow: {
x: overflowAmount.w > 0,
y: overflowAmount.h > 0,
},
scrollableOverflow: assignDeep({}, overflowScroll),
});
export const createLifecycleHub = (
options: OSOptions,
triggerListener: TriggerEventListener,
structureSetup: StructureSetup,
scrollbarsSetup: ScrollbarsSetup
): LifecycleHubInstance => {
let lifecycleCommunication = initialLifecycleCommunication;
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
) => {
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,
];
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);
}
let adaptivedUpdateHints: Required<LifecycleUpdateHints> = initialUpdateHints;
each(lifecycles, (lifecycle) => {
adaptivedUpdateHints = prepareUpdateHints<LifecycleUpdateHints>(
adaptivedUpdateHints,
lifecycle(adaptivedUpdateHints, checkOption, !!force) || {},
force
);
});
if (isNumber(scrollOffsetX)) {
scrollLeft(_viewport, scrollOffsetX);
}
if (isNumber(scrollOffsetY)) {
scrollTop(_viewport, scrollOffsetY);
}
const {
_viewportOverflowAmountCache: overflowAmountCache,
_viewportOverflowScrollCache: overflowScrollCache,
} = lifecycleCommunication;
const [overflowAmount, overflowAmountChanged, prevOverflowAmount] = overflowAmountCache;
const [overflowScroll, overflowScrollChanged, prevOverflowScroll] = overflowScrollCache;
if (overflowAmountChanged || overflowScrollChanged) {
triggerListener(
'overflowChanged',
assignDeep({}, createOverflowChangedArgs(overflowAmount, overflowScroll), {
previous: createOverflowChangedArgs(prevOverflowAmount!, prevOverflowScroll!),
})
);
}
triggerListener('updated', {
updateHints: {
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
[updateObserverOptions, destroyObservers] = lifecycleHubOservers(instance, updateLifecycles);
const update = (changedOptions: Partial<OSOptions>, force?: boolean) =>
updateLifecycles({}, changedOptions, force);
const envUpdateListener = update.bind(0, {}, true);
addEnvironmentListener(envUpdateListener);
return {
_update: update,
_state: () => ({
_overflowAmount: lifecycleCommunication._viewportOverflowAmountCache[0],
}),
_destroy() {
destroyObservers();
removeEnvironmentListener(envUpdateListener);
structureSetup._destroy();
scrollbarsSetup._destroy();
},
};
};
@@ -1,8 +1,6 @@
import { OSTarget, OSInitializationObject, PartialOptions } from 'typings';
import { assignDeep, isEmptyObject, each, isFunction, keys, isHTMLElement } from 'support';
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
import { createScrollbarsSetup, ScrollbarsSetup } from 'setups/scrollbarsSetup';
import { createLifecycleHub } from 'lifecycles/lifecycleHub';
import { assignDeep, isEmptyObject, each, isFunction, keys, isHTMLElement, WH, XY } from 'support';
import { createStructureSetup, createScrollbarsSetup } from 'setups';
import { getOptionsDiff, OSOptions } from 'options';
import { getEnvironment } from 'environment';
import {
@@ -14,17 +12,17 @@ import {
} from 'plugins';
import { addInstance, getInstance, removeInstance } from 'instances';
import {
createEventListenerHub,
EventListenersMap,
AddEventListener,
RemoveEventListener,
createOSEventListenerHub,
InitialOSEventListeners,
AddOSEventListener,
RemoveOSEventListener,
} from 'eventListeners';
export interface OverlayScrollbarsStatic {
(
target: OSTarget | OSInitializationObject,
options?: PartialOptions<OSOptions>,
eventListeners?: EventListenersMap
eventListeners?: InitialOSEventListeners
): OverlayScrollbars;
extend(osPlugin: OSPlugin | OSPlugin[]): void;
@@ -39,10 +37,22 @@ export interface OverlayScrollbars {
state(): any;
on: AddEventListener;
off: RemoveEventListener;
on: AddOSEventListener;
off: RemoveOSEventListener;
}
const createOverflowChangedArgs = (overflowAmount: WH<number>, overflowScroll: XY<boolean>) => ({
amount: {
x: overflowAmount.w,
y: overflowAmount.h,
},
overflow: {
x: overflowAmount.w > 0,
y: overflowAmount.h > 0,
},
scrollableOverflow: assignDeep({}, overflowScroll),
});
export const OverlayScrollbars: OverlayScrollbarsStatic = (
target,
options?,
@@ -65,7 +75,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
return validate ? validate(opts, true) : opts;
};
const currentOptions: OSOptions = assignDeep({}, _getDefaultOptions(), validateOptions(options));
const [addEvent, removeEvent, triggerEvent] = createEventListenerHub(eventListeners);
const [addEvent, removeEvent, triggerEvent] = createOSEventListenerHub(eventListeners);
if (
_nativeScrollbarIsOverlaid.x &&
@@ -75,14 +85,50 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
triggerEvent('initializationWithdrawn');
}
const structureSetup: StructureSetup = createStructureSetup(target);
const scrollbarsSetup: ScrollbarsSetup = createScrollbarsSetup(target, structureSetup);
const lifecycleHub = createLifecycleHub(
currentOptions,
triggerEvent,
structureSetup,
scrollbarsSetup
const [updateStructure, structureState, destroyStructure] = createStructureSetup(
target,
currentOptions
);
const [updateScrollbars, , destroyScrollbars] = createScrollbarsSetup(
target,
currentOptions,
structureState._elements
);
const update = (changedOptions: PartialOptions<OSOptions>, force?: boolean) => {
updateStructure(changedOptions, force);
updateScrollbars(changedOptions, force);
};
structureState._addOnUpdatedListener((updateHints, changedOptions, force) => {
const {
_viewportOverflowAmountCache: overflowAmountCache,
_viewportOverflowScrollCache: overflowScrollCache,
} = structureState();
const [overflowAmount, overflowAmountChanged, prevOverflowAmount] = overflowAmountCache;
const [overflowScroll, overflowScrollChanged, prevOverflowScroll] = overflowScrollCache;
if (overflowAmountChanged || overflowScrollChanged) {
triggerEvent(
'overflowChanged',
assignDeep({}, createOverflowChangedArgs(overflowAmount, overflowScroll), {
previous: createOverflowChangedArgs(prevOverflowAmount!, prevOverflowScroll!),
})
);
}
triggerEvent('updated', {
updateHints: {
sizeChanged: updateHints._sizeChanged,
contentMutation: updateHints._contentMutation,
hostMutation: updateHints._hostMutation,
directionChanged: updateHints._directionIsRTL[1],
heightIntrinsicChanged: updateHints._heightIntrinsic[1],
},
changedOptions,
force,
});
});
const instance: OverlayScrollbars = {
options(newOptions?: PartialOptions<OSOptions>) {
@@ -91,21 +137,26 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
if (!isEmptyObject(changedOptions)) {
assignDeep(currentOptions, changedOptions);
lifecycleHub._update(changedOptions);
update(changedOptions);
}
}
return currentOptions;
},
on: addEvent,
off: removeEvent,
state: () => lifecycleHub._state(),
state: () => ({
_overflowAmount: structureState()._viewportOverflowAmountCache[0],
}),
update(force?: boolean) {
lifecycleHub._update({}, force);
update({}, force);
},
destroy: () => {
lifecycleHub._destroy();
removeInstance(instanceTarget);
removeEvent();
destroyScrollbars();
destroyStructure();
triggerEvent('destroyed');
},
};
@@ -0,0 +1,3 @@
export * from 'setups/setups';
export * from 'setups/structureSetup';
export * from 'setups/scrollbarsSetup';
@@ -0,0 +1 @@
export * from 'setups/scrollbarsSetup/scrollbarsSetup';
@@ -8,7 +8,7 @@ import {
} from 'classnames';
import { getEnvironment, ScrollbarsInitializationStrategy } from 'environment';
import { OSTarget, ScrollbarsInitialization } from 'typings';
import { StructureSetup } from 'setups/structureSetup';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
export interface ScrollbarStructure {
_scrollbar: HTMLElement;
@@ -16,12 +16,13 @@ export interface ScrollbarStructure {
_handle: HTMLElement;
}
export interface ScrollbarsSetup {
export interface ScrollbarsSetupElementsObj {
_horizontalScrollbarStructure: ScrollbarStructure;
_verticalScrollbarStructure: ScrollbarStructure;
_destroy: () => void;
}
export type ScrollbarsSetupElements = [elements: ScrollbarsSetupElementsObj, destroy: () => void];
const generateScrollbarDOM = (scrollbarClassName: string): ScrollbarStructure => {
const scrollbar = createDiv(`${classNameScrollbar} ${scrollbarClassName}`);
const track = createDiv(classNameScrollbarTrack);
@@ -37,18 +38,24 @@ const generateScrollbarDOM = (scrollbarClassName: string): ScrollbarStructure =>
};
};
export const createScrollbarsSetup = (target: OSTarget | ScrollbarsInitialization, structureSetup: StructureSetup): ScrollbarsSetup => {
export const createScrollbarsSetupElements = (
target: OSTarget,
structureSetupElements: StructureSetupElementsObj
): ScrollbarsSetupElements => {
const { _getInitializationStrategy } = getEnvironment();
const { _scrollbarsSlot: environmentScrollbarSlot } = _getInitializationStrategy() as ScrollbarsInitializationStrategy;
const { _targetObj, _targetCtx } = structureSetup;
const { _target, _host, _viewport } = _targetObj;
const initializationScrollbarSlot = !_targetCtx._targetIsElm && (target as ScrollbarsInitialization).scrollbarsSlot;
const { _scrollbarsSlot: environmentScrollbarSlot } =
_getInitializationStrategy() as ScrollbarsInitializationStrategy;
const { _target, _host, _viewport, _targetIsElm } = structureSetupElements;
const initializationScrollbarSlot =
!_targetIsElm && (target as ScrollbarsInitialization).scrollbarsSlot;
const initializationScrollbarSlotResult = isFunction(initializationScrollbarSlot)
? initializationScrollbarSlot(_target, _host, _viewport)
: initializationScrollbarSlot;
const evaluatedScrollbarSlot =
initializationScrollbarSlotResult ||
(isFunction(environmentScrollbarSlot) ? environmentScrollbarSlot(_target, _host, _viewport) : environmentScrollbarSlot) ||
(isFunction(environmentScrollbarSlot)
? environmentScrollbarSlot(_target, _host, _viewport)
: environmentScrollbarSlot) ||
_host;
const horizontalScrollbarStructure = generateScrollbarDOM(classNameScrollbarHorizontal);
@@ -60,11 +67,11 @@ export const createScrollbarsSetup = (target: OSTarget | ScrollbarsInitializatio
appendChildren(evaluatedScrollbarSlot, horizontalScrollbar);
appendChildren(evaluatedScrollbarSlot, verticalScrollbar);
return {
_horizontalScrollbarStructure: horizontalScrollbarStructure,
_verticalScrollbarStructure: verticalScrollbarStructure,
_destroy() {
removeElements([horizontalScrollbar, verticalScrollbar]);
return [
{
_horizontalScrollbarStructure: horizontalScrollbarStructure,
_verticalScrollbarStructure: verticalScrollbarStructure,
},
};
removeElements.bind(0, [horizontalScrollbar, verticalScrollbar]),
];
};
@@ -0,0 +1,43 @@
import { createState, createOptionCheck } from 'setups/setups';
import {
createScrollbarsSetupElements,
ScrollbarsSetupElementsObj,
} from 'setups/scrollbarsSetup/scrollbarsSetup.elements';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { OSOptions } from 'options';
import type { Setup } from 'setups';
import type { OSTarget } from 'typings';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ScrollbarsSetupState {}
export interface ScrollbarsSetupStaticState {
_elements: ScrollbarsSetupElementsObj;
}
export const createScrollbarsSetup = (
target: OSTarget,
options: OSOptions,
structureSetupElements: StructureSetupElementsObj
): Setup<ScrollbarsSetupState, ScrollbarsSetupStaticState> => {
const state = createState({});
const [getState] = state;
const [elements, destroyElements] = createScrollbarsSetupElements(target, structureSetupElements);
const scrollbarsSetupState = getState.bind(0) as (() => ScrollbarsSetupState) &
ScrollbarsSetupStaticState;
scrollbarsSetupState._elements = elements;
return [
(changedOptions, force?) => {
const checkOption = createOptionCheck(options, changedOptions, force);
// eslint-disable-next-line no-console
console.log(checkOption);
},
scrollbarsSetupState,
() => {
destroyElements();
},
];
};
@@ -0,0 +1,53 @@
import { assignDeep, hasOwnProperty } from 'support';
import type { OSOptions } from 'options';
import type { PartialOptions } from 'typings';
export type SetupElements<T extends Record<string, any>> = [elements: T, destroy: () => void];
export type SetupUpdate<T = void> = (
changedOptions: PartialOptions<OSOptions>,
force?: boolean
) => T;
export type SetupUpdateCheckOption = <T>(path: string) => [value: T, changed: boolean];
export type SetupUpdateSegment<Hints extends Record<string, any>> = (
updateHints: Hints,
checkOption: SetupUpdateCheckOption,
force: boolean
) => Partial<Hints> | void;
export type SetupState<T extends Record<string, any>> = [
get: () => T,
set: (newState: Partial<T>) => void
];
export type Setup<DynamicState, StaticState extends Record<string, any> = Record<string, any>> = [
update: SetupUpdate,
state: (() => DynamicState) & StaticState,
destroy: () => void
];
const getPropByPath = <T>(obj: any, path: string): T =>
obj
? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj)
: undefined;
export const createOptionCheck =
(
options: OSOptions,
changedOptions: PartialOptions<OSOptions>,
force?: boolean
): SetupUpdateCheckOption =>
(path: string) =>
[getPropByPath(options, path), force || getPropByPath(changedOptions, path) !== undefined];
export const createState = <S>(initialState: S): SetupState<S> => {
let state: S = initialState;
return [
() => state,
(newState: Partial<S>) => {
state = assignDeep({}, state, newState);
},
];
};
@@ -0,0 +1 @@
export * from 'setups/structureSetup/structureSetup';
@@ -34,29 +34,23 @@ import {
} from 'environment';
import { OSTarget, OSTargetElement, StructureInitialization } from 'typings';
export interface OSTargetContext {
_isTextarea: boolean;
_isBody: boolean;
_htmlElm: HTMLHtmlElement;
_bodyElm: HTMLBodyElement;
_windowElm: Window;
_documentElm: HTMLDocument;
_targetIsElm: boolean;
}
export type StructureSetupElements = [targetObj: StructureSetupElementsObj, destroy: () => void];
export interface PreparedOSTargetObject {
export interface StructureSetupElementsObj {
_target: OSTargetElement;
_host: HTMLElement;
_viewport: HTMLElement;
_padding: HTMLElement | false;
_content: HTMLElement | false;
_viewportArrange: HTMLStyleElement | false;
}
export interface StructureSetup {
_targetObj: PreparedOSTargetObject;
_targetCtx: OSTargetContext;
_destroy: () => void;
// ctx ----
_isTextarea: boolean;
_isBody: boolean;
_htmlElm: HTMLHtmlElement;
_bodyElm: HTMLBodyElement;
_windowElm: Window;
_documentElm: Document;
_targetIsElm: boolean;
}
let contentArrangeCounter = 0;
@@ -116,9 +110,7 @@ const dynamicCreationFromStrategy = (
return result === true ? createDiv(elementClass) : result;
};
export const createStructureSetup = (
target: OSTarget | StructureInitialization
): StructureSetup => {
export const createStructureSetupElements = (target: OSTarget): StructureSetupElements => {
const { _getInitializationStrategy, _nativeScrollbarStyling } = getEnvironment();
const {
_host: hostInitializationStrategy,
@@ -136,7 +128,7 @@ export const createStructureSetup = (
const ownerDocument: HTMLDocument = targetElement!.ownerDocument;
const bodyElm = ownerDocument.body as HTMLBodyElement;
const wnd = ownerDocument.defaultView as Window;
const evaluatedTargetObj: PreparedOSTargetObject = {
const evaluatedTargetObj: StructureSetupElementsObj = {
_target: targetElement,
_host: isTextarea
? staticCreationFromStrategy(
@@ -167,8 +159,6 @@ export const createStructureSetup = (
false // default value for content
),
_viewportArrange: createUniqueViewportArrangeElement(),
};
const ctx: OSTargetContext = {
_windowElm: wnd,
_documentElm: ownerDocument,
_htmlElm: parent(bodyElm) as HTMLHtmlElement,
@@ -245,11 +235,5 @@ export const createStructureSetup = (
push(destroyFns, removeElements.bind(0, _viewportArrange));
}
return {
_targetObj: evaluatedTargetObj,
_targetCtx: ctx,
_destroy: () => {
runEach(destroyFns);
},
};
return [evaluatedTargetObj, runEach.bind(0, destroyFns)];
};
@@ -14,12 +14,17 @@ import { getEnvironment } from 'environment';
import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver';
import { createTrinsicObserver } from 'observers/trinsicObserver';
import { createDOMObserver, DOMObserver } from 'observers/domObserver';
import { LifecycleHub, LifecycleCheckOption, LifecycleUpdateHints } from 'lifecycles/lifecycleHub';
import type { SetupUpdateCheckOption } from 'setups';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type {
StructureSetupUpdate,
StructureSetupUpdateHints,
} from 'setups/structureSetup/structureSetup.update';
export type UpdateObserverOptions = (checkOption: LifecycleCheckOption) => void;
export type StructureSetupObserversUpdate = (checkOption: SetupUpdateCheckOption) => void;
export type LifecycleHubObservers = [
updateObserverOptions: UpdateObserverOptions,
export type StructureSetupObservers = [
updateObserverOptions: StructureSetupObserversUpdate,
destroy: () => void
];
@@ -49,22 +54,27 @@ const ignoreTargetChange = (
return false;
};
export const lifecycleHubOservers = (
instance: LifecycleHub,
updateLifecycles: (updateHints: Partial<LifecycleUpdateHints>) => unknown
): LifecycleHubObservers => {
type ExcludeFromTuple<T extends readonly any[], E> = T extends [infer F, ...infer R]
? [F] extends [E]
? ExcludeFromTuple<R, E>
: [F, ...ExcludeFromTuple<R, E>]
: [];
export const createStructureSetupObservers = (
structureSetupElements: StructureSetupElementsObj,
structureSetupUpdate: (
...args: ExcludeFromTuple<Parameters<StructureSetupUpdate>, Parameters<StructureSetupUpdate>[0]>
) => any
): StructureSetupObservers => {
let debounceTimeout: number | false | undefined;
let debounceMaxDelay: number | false | undefined;
let contentMutationObserver: DOMObserver | undefined;
const { _structureSetup } = instance;
const { _targetObj, _targetCtx } = _structureSetup;
const { _host, _viewport, _content } = _targetObj;
const { _isTextarea } = _targetCtx;
const { _host, _viewport, _content, _isTextarea } = structureSetupElements;
const { _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
const contentMutationObserverAttr = _isTextarea
? baseStyleChangingAttrsTextarea
: baseStyleChangingAttrs.concat(baseStyleChangingAttrsTextarea);
const updateLifecyclesWithDebouncedAdaptiveUpdateHints = debounce(updateLifecycles, {
const structureSetupUpdateWithDebouncedAdaptiveUpdateHints = debounce(structureSetupUpdate, {
_timeout: () => debounceTimeout,
_maxDelay: () => debounceMaxDelay,
_mergeParams(prev, curr) {
@@ -78,7 +88,7 @@ export const lifecycleHubOservers = (
_hostMutation: currvHostMutation,
_contentMutation: currContentMutation,
} = curr[0];
const merged: [Partial<LifecycleUpdateHints>] = [
const merged: [Partial<StructureSetupUpdateHints>] = [
{
_sizeChanged: prevSizeChanged || currSizeChanged,
_hostMutation: prevHostMutation || currvHostMutation,
@@ -103,7 +113,7 @@ export const lifecycleHubOservers = (
});
};
const onTrinsicChanged = (heightIntrinsic: CacheValues<boolean>) => {
updateLifecycles({
structureSetupUpdate({
_heightIntrinsic: heightIntrinsic,
});
};
@@ -114,8 +124,8 @@ export const lifecycleHubOservers = (
}: SizeObserverCallbackParams) => {
const updateFn =
!_sizeChanged || _appear
? updateLifecycles
: updateLifecyclesWithDebouncedAdaptiveUpdateHints;
? structureSetupUpdate
: structureSetupUpdateWithDebouncedAdaptiveUpdateHints;
updateFn({
_sizeChanged,
@@ -125,15 +135,15 @@ export const lifecycleHubOservers = (
const onContentMutation = (contentChangedTroughEvent: boolean) => {
// if contentChangedTroughEvent is true its already debounced
const updateFn = contentChangedTroughEvent
? updateLifecycles
: updateLifecyclesWithDebouncedAdaptiveUpdateHints;
? structureSetupUpdate
: structureSetupUpdateWithDebouncedAdaptiveUpdateHints;
updateFn({
_contentMutation: true,
});
};
const onHostMutation = (targetChangedAttrs: string[], targetStyleChanged: boolean) => {
if (targetStyleChanged) {
updateLifecyclesWithDebouncedAdaptiveUpdateHints({
structureSetupUpdateWithDebouncedAdaptiveUpdateHints({
_hostMutation: true,
});
} else {
@@ -153,61 +163,64 @@ export const lifecycleHubOservers = (
_ignoreTargetChange: ignoreTargetChange,
});
const updateOptions: UpdateObserverOptions = (checkOption) => {
const [elementEvents, elementEventsChanged] = checkOption<Array<[string, string]> | null>(
'updating.elementEvents'
);
const [attributes, attributesChanged] = checkOption<string[] | null>('updating.attributes');
const [debounceValue, debounceChanged] = checkOption<Array<number> | number | null>(
'updating.debounce'
);
const updateContentMutationObserver = elementEventsChanged || attributesChanged;
if (updateContentMutationObserver) {
if (contentMutationObserver) {
contentMutationObserver[1](); // update
contentMutationObserver[0](); // destroy
}
contentMutationObserver = createDOMObserver(_content || _viewport, true, onContentMutation, {
_styleChangingAttributes: contentMutationObserverAttr.concat(attributes || []),
_attributes: contentMutationObserverAttr.concat(attributes || []),
_eventContentChange: elementEvents,
_ignoreNestedTargetChange: ignoreTargetChange,
// _nestedTargetSelector: hostSelector,
/*
_ignoreContentChange: (mutation, isNestedTarget) => {
const { target, attributeName } = mutation;
return isNestedTarget
? false
: attributeName
? liesBetween(target as Element, hostSelector, viewportSelector) || liesBetween(target as Element, hostSelector, contentSelector)
: false;
},
*/
});
}
if (debounceChanged) {
updateLifecyclesWithDebouncedAdaptiveUpdateHints._flush();
if (isArray(debounceValue)) {
const timeout = debounceValue[0];
const maxWait = debounceValue[1];
debounceTimeout = isNumber(timeout) ? timeout : false;
debounceMaxDelay = isNumber(maxWait) ? maxWait : false;
} else if (isNumber(debounceValue)) {
debounceTimeout = debounceValue;
debounceMaxDelay = false;
} else {
debounceTimeout = false;
debounceMaxDelay = false;
}
}
};
updateViewportAttrsFromHost();
return [
updateOptions,
(checkOption) => {
const [elementEvents, elementEventsChanged] = checkOption<Array<[string, string]> | null>(
'updating.elementEvents'
);
const [attributes, attributesChanged] = checkOption<string[] | null>('updating.attributes');
const [debounceValue, debounceChanged] = checkOption<Array<number> | number | null>(
'updating.debounce'
);
const updateContentMutationObserver = elementEventsChanged || attributesChanged;
if (updateContentMutationObserver) {
if (contentMutationObserver) {
contentMutationObserver[1](); // update
contentMutationObserver[0](); // destroy
}
contentMutationObserver = createDOMObserver(
_content || _viewport,
true,
onContentMutation,
{
_styleChangingAttributes: contentMutationObserverAttr.concat(attributes || []),
_attributes: contentMutationObserverAttr.concat(attributes || []),
_eventContentChange: elementEvents,
_ignoreNestedTargetChange: ignoreTargetChange,
// _nestedTargetSelector: hostSelector,
/*
_ignoreContentChange: (mutation, isNestedTarget) => {
const { target, attributeName } = mutation;
return isNestedTarget
? false
: attributeName
? liesBetween(target as Element, hostSelector, viewportSelector) || liesBetween(target as Element, hostSelector, contentSelector)
: false;
},
*/
}
);
}
if (debounceChanged) {
structureSetupUpdateWithDebouncedAdaptiveUpdateHints._flush();
if (isArray(debounceValue)) {
const timeout = debounceValue[0];
const maxWait = debounceValue[1];
debounceTimeout = isNumber(timeout) ? timeout : false;
debounceMaxDelay = isNumber(maxWait) ? maxWait : false;
} else if (isNumber(debounceValue)) {
debounceTimeout = debounceValue;
debounceMaxDelay = false;
} else {
debounceTimeout = false;
debounceMaxDelay = false;
}
}
},
() => {
contentMutationObserver && contentMutationObserver[0](); // destroy
destroyTrinsicObserver && destroyTrinsicObserver();
@@ -0,0 +1,119 @@
import { runEach } from 'support';
import { getEnvironment } from 'environment';
import { createState, createOptionCheck } from 'setups/setups';
import { createStructureSetupElements } from 'setups/structureSetup/structureSetup.elements';
import {
createStructureSetupUpdate,
StructureSetupUpdateHints,
} from 'setups/structureSetup/structureSetup.update';
import { createStructureSetupObservers } from 'setups/structureSetup/structureSetup.observers';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { TRBL, CacheValues, XY, WH } from 'support';
import type { OSOptions } from 'options';
import type { Setup } from 'setups';
import type { OSTarget, PartialOptions, StyleObject } from 'typings';
export interface StructureSetupState {
_padding: TRBL;
_paddingAbsolute: boolean;
_viewportPaddingStyle: StyleObject;
_viewportOverflowScrollCache: CacheValues<XY<boolean>>;
_viewportOverflowAmountCache: CacheValues<WH<number>>;
}
export interface StructureSetupStaticState {
_elements: StructureSetupElementsObj;
_addOnUpdatedListener: (listener: OnUpdatedListener) => void;
}
export type OnUpdatedListener = (
updateHints: StructureSetupUpdateHints,
changedOptions: PartialOptions<OSOptions>,
force: boolean
) => void;
const initialStructureSetupUpdateState: StructureSetupState = {
_padding: {
t: 0,
r: 0,
b: 0,
l: 0,
},
_paddingAbsolute: false,
_viewportOverflowScrollCache: [
{
x: false,
y: false,
},
false,
],
_viewportOverflowAmountCache: [
{
w: 0,
h: 0,
},
false,
],
_viewportPaddingStyle: {
marginRight: 0,
marginBottom: 0,
marginLeft: 0,
paddingTop: 0,
paddingRight: 0,
paddingBottom: 0,
paddingLeft: 0,
},
};
export const createStructureSetup = (
target: OSTarget,
options: OSOptions
): Setup<StructureSetupState, StructureSetupStaticState> => {
const checkOptionsFallback = createOptionCheck(options, {});
const state = createState(initialStructureSetupUpdateState);
const onUpdatedListeners = new Set<OnUpdatedListener>();
const [getUpdateState] = state;
const runOnUpdatedListeners = (
updateHints: StructureSetupUpdateHints,
changedOptions?: PartialOptions<OSOptions>,
force?: boolean
) => {
runEach(onUpdatedListeners, [updateHints, changedOptions || {}, !!force]);
};
const [elements, destroyElements] = createStructureSetupElements(target);
const updateStructure = createStructureSetupUpdate(elements, state);
const [updateObservers, destroyObservers] = createStructureSetupObservers(
elements,
(updateHints) => {
runOnUpdatedListeners(updateStructure(checkOptionsFallback, updateHints));
}
);
const removeEnvListener = getEnvironment()._addListener(
updateStructure.bind(0, checkOptionsFallback, {}, true)
);
const structureSetupState = getUpdateState.bind(0) as (() => StructureSetupState) &
StructureSetupStaticState;
structureSetupState._addOnUpdatedListener = (listener) => {
onUpdatedListeners.add(listener);
};
structureSetupState._elements = elements;
return [
(changedOptions, force?) => {
const checkOption = createOptionCheck(options, changedOptions, force);
updateObservers(checkOption);
runOnUpdatedListeners(updateStructure(checkOption, {}, force));
},
structureSetupState,
() => {
onUpdatedListeners.clear();
removeEnvListener();
destroyObservers();
destroyElements();
},
];
};
@@ -0,0 +1,128 @@
import {
CacheValues,
each,
isNumber,
scrollLeft,
scrollTop,
assignDeep,
keys,
isBoolean,
} from 'support';
import { getEnvironment } from 'environment';
import {
createTrinsicUpdate,
createPaddingUpdate,
createOverflowUpdate,
} from 'setups/structureSetup/updateSegments';
import type { SetupState, SetupUpdateSegment, SetupUpdateCheckOption } from 'setups';
import type { StructureSetupState } from 'setups/structureSetup';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
export type CreateStructureUpdateSegment = (
structureSetupElements: StructureSetupElementsObj,
state: SetupState<StructureSetupState>
) => StructureSetupUpdateSegment;
export type StructureSetupUpdateSegment = SetupUpdateSegment<StructureSetupUpdateHints>;
export type StructureSetupUpdate = (
checkOption: SetupUpdateCheckOption,
updateHints: Partial<StructureSetupUpdateHints>,
force?: boolean
) => StructureSetupUpdateHints;
export interface StructureSetupUpdateHints {
_sizeChanged: boolean;
_hostMutation: boolean;
_contentMutation: boolean;
_paddingStyleChanged: boolean;
_directionIsRTL: CacheValues<boolean>;
_heightIntrinsic: CacheValues<boolean>;
}
const booleanCacheValuesFallback: CacheValues<boolean> = [false, false, false];
const applyForceToCache = <T>(cacheValues: CacheValues<T>, force?: boolean): CacheValues<T> => [
cacheValues[0],
force || cacheValues[1],
cacheValues[2],
];
const prepareUpdateHints = <T extends StructureSetupUpdateHints>(
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 createStructureSetupUpdate = (
structureSetupElements: StructureSetupElementsObj,
state: SetupState<StructureSetupState>
): StructureSetupUpdate => {
const { _viewport } = structureSetupElements;
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _flexboxGlue } = getEnvironment();
const doViewportArrange =
!_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
const updateSegments: StructureSetupUpdateSegment[] = [
createTrinsicUpdate(structureSetupElements, state),
createPaddingUpdate(structureSetupElements, state),
createOverflowUpdate(structureSetupElements, state),
];
return (
checkOption: SetupUpdateCheckOption,
updateHints: Partial<StructureSetupUpdateHints>,
force?: boolean
) => {
const initialUpdateHints = prepareUpdateHints(
assignDeep(
{
_sizeChanged: false,
_hostMutation: false,
_contentMutation: false,
_paddingStyleChanged: false,
_directionIsRTL: booleanCacheValuesFallback,
_heightIntrinsic: booleanCacheValuesFallback,
},
updateHints
),
{},
force
);
const adjustScrollOffset = doViewportArrange || !_flexboxGlue;
const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport);
const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport);
let adaptivedUpdateHints: Required<StructureSetupUpdateHints> = initialUpdateHints;
each(updateSegments, (updateSegment) => {
adaptivedUpdateHints = prepareUpdateHints<StructureSetupUpdateHints>(
adaptivedUpdateHints,
updateSegment(adaptivedUpdateHints, checkOption, !!force) || {},
force
);
});
if (isNumber(scrollOffsetX)) {
scrollLeft(_viewport, scrollOffsetX);
}
if (isNumber(scrollOffsetY)) {
scrollTop(_viewport, scrollOffsetY);
}
return adaptivedUpdateHints;
};
};
@@ -0,0 +1,3 @@
export * from 'setups/structureSetup/updateSegments/trinsicUpdateSegment';
export * from 'setups/structureSetup/updateSegments/paddingUpdateSegment';
export * from 'setups/structureSetup/updateSegments/overflowUpdateSegment';
@@ -17,11 +17,11 @@ import {
each,
equalXY,
} from 'support';
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
import { getEnvironment } from 'environment';
import { OverflowBehavior } from 'options';
import { StyleObject } from 'typings';
import { classNameViewportArrange, classNameViewportScrollbarStyling } from 'classnames';
import type { CreateStructureUpdateSegment } from 'setups/structureSetup/structureSetup.update';
interface ViewportOverflowState {
_scrollbarsHideOffset: XY<number>;
@@ -101,18 +101,23 @@ const getOverflowAmount = (
/**
* Lifecycle with the responsibility to set the correct overflow and scrollbar hiding styles of the viewport element.
* @param lifecycleHub
* @param structureUpdateHub
* @returns
*/
export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
export const createOverflowUpdate: CreateStructureUpdateSegment = (
structureSetupElements,
state
) => {
const [getState, setState] = state;
const { _host, _viewport, _viewportArrange } = structureSetupElements;
const {
_structureSetup,
_doViewportArrange,
_getLifecycleCommunication,
_setLifecycleCommunication,
} = lifecycleHub;
const { _host, _viewport, _viewportArrange } = _structureSetup._targetObj;
_nativeScrollbarSize,
_flexboxGlue,
_nativeScrollbarStyling,
_nativeScrollbarIsOverlaid,
} = getEnvironment();
const doViewportArrange =
!_nativeScrollbarStyling && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y);
const [updateViewportSizeFraction, getCurrentViewportSizeFraction] = createCache<WH<number>>(
whCacheOptions,
@@ -142,8 +147,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
});
if (heightIntrinsic) {
const { _nativeScrollbarIsOverlaid } = getEnvironment();
const { _paddingAbsolute, _padding } = _getLifecycleCommunication();
const { _paddingAbsolute, _padding } = getState();
const { _overflowScroll, _scrollbarsHideOffset } = viewportOverflowState;
const hostSizeFraction = sizeFraction(_host);
const hostClientSize = clientSize(_host);
@@ -173,8 +177,6 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
showNativeOverlaidScrollbars: boolean,
viewportStyleObj?: StyleObject
): ViewportOverflowState => {
const { _nativeScrollbarSize, _nativeScrollbarIsOverlaid, _nativeScrollbarStyling } =
getEnvironment();
const { x: overlaidX, y: overlaidY } = _nativeScrollbarIsOverlaid;
const determineOverflow = !viewportStyleObj;
const arrangeHideOffset =
@@ -255,8 +257,8 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
viewportSizeFraction: WH<number>,
directionIsRTL: boolean
) => {
if (_doViewportArrange) {
const { _viewportPaddingStyle } = _getLifecycleCommunication();
if (doViewportArrange) {
const { _viewportPaddingStyle } = getState();
const { _scrollbarsHideOffset, _scrollbarsHideOffsetArrange } = viewportOverflowState;
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset;
@@ -310,7 +312,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
}
}
return _doViewportArrange;
return doViewportArrange;
};
/**
@@ -329,7 +331,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
const { _scrollbarsHideOffset, _scrollbarsHideOffsetArrange } = viewportOverflowState;
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
const { x: hideOffsetX, y: hideOffsetY } = _scrollbarsHideOffset;
const { _viewportPaddingStyle: viewportPaddingStyle } = _getLifecycleCommunication();
const { _viewportPaddingStyle: viewportPaddingStyle } = getState();
const horizontalMarginKey: keyof StyleObject = directionIsRTL ? 'marginLeft' : 'marginRight';
const viewportHorizontalPaddingKey: keyof StyleObject = directionIsRTL
? 'paddingLeft'
@@ -366,11 +368,10 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
directionIsRTL: boolean,
viewportOverflowState?: ViewportOverflowState
): UndoViewportArrangeResult => {
if (_doViewportArrange) {
if (doViewportArrange) {
const finalViewportOverflowState =
viewportOverflowState || getViewportOverflowState(showNativeOverlaidScrollbars);
const { _viewportPaddingStyle: viewportPaddingStyle } = _getLifecycleCommunication();
const { _flexboxGlue } = getEnvironment();
const { _viewportPaddingStyle: viewportPaddingStyle } = getState();
const { _scrollbarsHideOffsetArrange } = finalViewportOverflowState;
const { x: arrangeX, y: arrangeY } = _scrollbarsHideOffsetArrange;
const finalPaddingStyle: StyleObject = {};
@@ -400,7 +401,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
hideNativeScrollbars(
finalViewportOverflowState,
directionIsRTL,
_doViewportArrange,
doViewportArrange,
prevStyle
);
style(_viewport, prevStyle);
@@ -421,7 +422,6 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
_contentMutation,
_paddingStyleChanged,
} = updateHints;
const { _flexboxGlue, _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment();
const [heightIntrinsic, heightIntrinsicChanged] = _heightIntrinsic;
const [directionIsRTL, directionChanged] = _directionIsRTL;
const [showNativeOverlaidScrollbarsOption, showNativeOverlaidScrollbarsChanged] =
@@ -561,7 +561,7 @@ export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =
style(_viewport, viewportStyle);
_setLifecycleCommunication({
setState({
_viewportOverflowScrollCache: updateOverflowScrollCache(
viewportOverflowState._overflowScroll
),
@@ -1,16 +1,19 @@
import { createCache, topRightBottomLeft, equalTRBL, style, assignDeep } from 'support';
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
import { StyleObject } from 'typings';
import { getEnvironment } from 'environment';
import type { CreateStructureUpdateSegment } from 'setups/structureSetup/structureSetup.update';
/**
* Lifecycle with the responsibility to adjust the padding styling of the padding and viewport element.
* @param lifecycleHub
* @param structureUpdateHub
* @returns
*/
export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
const { _structureSetup, _setLifecycleCommunication } = lifecycleHub;
const { _host, _padding, _viewport } = _structureSetup._targetObj;
export const createPaddingUpdate: CreateStructureUpdateSegment = (
structureSetupElements,
state
) => {
const [, setState] = state;
const { _host, _padding, _viewport } = structureSetupElements;
const [updatePaddingCache, currentPaddingCache] = createCache(
{
_equal: equalTRBL,
@@ -59,7 +62,7 @@ export const createPaddingLifecycle = (lifecycleHub: LifecycleHub): Lifecycle =>
style(_padding || _viewport, paddingStyle);
style(_viewport, viewportStyle);
_setLifecycleCommunication({
setState({
_padding: padding,
_paddingAbsolute: !paddingRelative,
_viewportPaddingStyle: _padding
@@ -1,14 +1,13 @@
import { style } from 'support';
import { LifecycleHub, Lifecycle } from 'lifecycles/lifecycleHub';
import type { CreateStructureUpdateSegment } from 'setups/structureSetup/structureSetup.update';
/**
* Lifecycle with the responsibility to adjust the trinsic behavior of the content element.
* @param lifecycleHub
* @param structureUpdateHub
* @returns
*/
export const createTrinsicLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
const { _structureSetup } = lifecycleHub;
const { _content } = _structureSetup._targetObj;
export const createTrinsicUpdate: CreateStructureUpdateSegment = (structureSetupElements) => {
const { _content } = structureSetupElements;
return (updateHints) => {
const { _heightIntrinsic } = updateHints;
-1
View File
@@ -1 +0,0 @@
export * from 'support/cache/cache';
@@ -0,0 +1,98 @@
import { isArray } from 'support/utils/types';
import { keys } from 'support/utils/object';
import { each, from } from 'support/utils/array';
export type EventListener<
NameArgsMap extends Record<string, any>,
Name extends Extract<keyof NameArgsMap, string> = Extract<keyof NameArgsMap, string>
> = (...args: NameArgsMap[Name] extends undefined ? [] : [args: NameArgsMap[Name]]) => void;
export type EventListenerGroup<
NameArgsMap extends Record<string, any>,
Name extends Extract<keyof NameArgsMap, string> = Extract<keyof NameArgsMap, string>
> = EventListener<NameArgsMap, Name> | EventListener<NameArgsMap, Name>[];
export type AddEventListener<NameArgsMap extends Record<string, any>> = <
Name extends Extract<keyof NameArgsMap, string>
>(
name: Name,
listener: EventListenerGroup<NameArgsMap, Name>
) => () => void;
export type RemoveEventListener<NameArgsMap extends Record<string, any>> = <
Name extends Extract<keyof NameArgsMap, string>
>(
name?: Name,
listener?: EventListenerGroup<NameArgsMap, Name>
) => void;
export type TriggerEventListener<NameArgsMap extends Record<string, any>> = <
Name extends Extract<keyof NameArgsMap, string>
>(
name: Name,
...args: NameArgsMap[Name] extends undefined ? [] : [args: NameArgsMap[Name]]
) => void;
export type InitialEventListeners<NameArgsMap extends Record<string, any>> = {
[K in Extract<keyof NameArgsMap, string>]?: EventListenerGroup<NameArgsMap, K>;
};
const manageListener = <NameArgsMap extends Record<string, any>>(
callback: (listener?: EventListener<NameArgsMap>) => void,
listener?: EventListener<NameArgsMap> | EventListener<NameArgsMap>[]
) => {
each(isArray(listener) ? listener : [listener], callback);
};
export const createEventListenerHub = <NameArgsMap extends Record<string, any>>(
initialEventListeners?: InitialEventListeners<NameArgsMap>
): [
AddEventListener<NameArgsMap>,
RemoveEventListener<NameArgsMap>,
TriggerEventListener<NameArgsMap>
] => {
const events = new Map<Extract<keyof NameArgsMap, string>, Set<EventListener<NameArgsMap>>>();
const removeEvent: RemoveEventListener<NameArgsMap> = (name?, listener?) => {
if (name) {
const eventSet = events.get(name);
manageListener((currListener) => {
if (eventSet) {
eventSet[currListener ? 'delete' : 'clear'](currListener!);
}
}, listener as EventListenerGroup<NameArgsMap> | undefined);
} else {
events.forEach((eventSet) => {
eventSet.clear();
});
events.clear();
}
};
const addEvent: AddEventListener<NameArgsMap> = (name, listener) => {
const eventSet = events.get(name) || new Set();
events.set(name, eventSet);
manageListener((currListener) => {
currListener && eventSet.add(currListener);
}, listener as EventListenerGroup<NameArgsMap>);
return removeEvent.bind(0, name as any, listener as EventListenerGroup<NameArgsMap>);
};
const triggerEvent: TriggerEventListener<NameArgsMap> = (name, args?) => {
const eventSet = events.get(name);
each(from(eventSet), (event) => {
if (args) {
(event as (args: NameArgsMap[Extract<keyof NameArgsMap, string>]) => void)(args as any);
} else {
(event as () => void)();
}
});
};
const initialListenerKeys = keys(initialEventListeners) as Extract<keyof NameArgsMap, string>[];
each(initialListenerKeys, (key) => {
addEvent(key, initialEventListeners![key] as any);
});
return [addEvent, removeEvent, triggerEvent];
};
@@ -2,3 +2,4 @@ export * from 'support/cache';
export * from 'support/compatibility';
export * from 'support/dom';
export * from 'support/utils';
export * from 'support/eventListeners';
@@ -106,8 +106,9 @@ export const isEmptyArray = (array: any[] | null | undefined): boolean =>
* @param arr The array filled with function which shall be called.
* @param p1 The first param.
*/
export const runEach = (arr: ArrayLike<RunEachItem> | Set<RunEachItem>, p1?: unknown): void => {
const runFn = (fn: RunEachItem) => fn && fn(p1);
export const runEach = (arr: ArrayLike<RunEachItem> | Set<RunEachItem>, args?: any[]): void => {
// eslint-disable-next-line prefer-spread
const runFn = (fn: RunEachItem) => fn && fn.apply(undefined, args || []);
if (arr instanceof Set) {
arr.forEach(runFn);
} else {
@@ -4,7 +4,10 @@ import {
StructureInitializationDynamicElement,
} from 'environment';
import { OSTarget, StructureInitialization } from 'typings';
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
import {
createStructureSetupElements,
StructureSetupElementsObj,
} from 'setups/structureSetup/structureSetup.elements';
import { isHTMLElement } from 'support';
const mockGetEnvironment = jest.fn();
@@ -12,9 +15,10 @@ jest.mock('environment', () => ({
getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()),
}));
interface StructureSetupProxy {
input: OSTarget | StructureInitialization;
setup: StructureSetup;
interface StructureSetupElementsProxy {
input: OSTarget;
elements: StructureSetupElementsObj;
destroy: () => void;
}
const textareaId = 'textarea';
@@ -92,19 +96,22 @@ const assertCorrectDOMStructure = (textarea?: boolean) => {
const createStructureSetupProxy = (
target: OSTarget | StructureInitialization
): StructureSetupProxy => ({
input: target,
setup: createStructureSetup(target),
});
): StructureSetupElementsProxy => {
const [elements, destroy] = createStructureSetupElements(target);
return {
input: target,
elements,
destroy,
};
};
const assertCorrectSetup = (
const assertCorrectSetupElements = (
textarea: boolean,
setupProxy: StructureSetupProxy,
setupElementsProxy: StructureSetupElementsProxy,
environment: Environment
): StructureSetup => {
const { input, setup } = setupProxy;
const { _targetObj, _targetCtx, _destroy } = setup;
const { _target, _host, _padding, _viewport, _content } = _targetObj;
): [StructureSetupElementsObj, () => void] => {
const { input, elements, destroy } = setupElementsProxy;
const { _target, _host, _padding, _viewport, _content } = elements;
const { target, host, padding, viewport, content } = getElements(textarea);
const isTextarea = target.matches('textarea');
const isBody = target.matches('body');
@@ -135,7 +142,7 @@ const assertCorrectSetup = (
expect(_content).toBeFalsy();
}
const { _isTextarea, _isBody, _bodyElm, _htmlElm, _documentElm, _windowElm } = _targetCtx;
const { _isTextarea, _isBody, _bodyElm, _htmlElm, _documentElm, _windowElm } = elements;
expect(_isTextarea).toBe(isTextarea);
expect(_isBody).toBe(isBody);
@@ -144,7 +151,7 @@ const assertCorrectSetup = (
expect(_htmlElm).toBe(document.body.parentElement);
expect(_bodyElm).toBe(document.body);
expect(typeof _destroy).toBe('function');
expect(typeof destroy).toBe('function');
const { _nativeScrollbarStyling, _cssCustomProperties, _getInitializationStrategy } = environment;
const {
@@ -250,13 +257,11 @@ const assertCorrectSetup = (
checkStrategyDependendElements(host, inputHost, true, hostInitStrategy, 'host');
}
return setup;
return [elements, destroy];
};
const assertCorrectDestroy = (snapshot: string, setup: StructureSetup) => {
const { _destroy } = setup;
_destroy();
const assertCorrectDestroy = (snapshot: string, destroy: () => void) => {
destroy();
// remove empty class attr
const elms = document.querySelectorAll('*');
@@ -351,24 +356,24 @@ describe('structureSetup', () => {
describe('basic', () => {
test('Element', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy(getTarget(isTextarea)),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('Object', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({ target: getTarget(isTextarea) }),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
@@ -380,7 +385,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -390,7 +395,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('viewport', () => {
@@ -399,7 +404,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -409,7 +414,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('content', () => {
@@ -418,7 +423,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="content">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -428,7 +433,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
@@ -439,7 +444,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -451,7 +456,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('padding viewport', () => {
@@ -460,7 +465,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -471,7 +476,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('padding content', () => {
@@ -480,7 +485,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -491,7 +496,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('viewport content', () => {
@@ -500,7 +505,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -511,14 +516,14 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('single false', () => {
test('padding', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
@@ -527,12 +532,12 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
@@ -541,14 +546,14 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('single true', () => {
test('padding', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
@@ -557,12 +562,12 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
@@ -571,14 +576,14 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('multiple false', () => {
test('padding & content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
@@ -588,14 +593,14 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
describe('multiple true', () => {
test('padding & content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
@@ -605,7 +610,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
@@ -616,7 +621,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -628,7 +633,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding & content | assigned: viewport', () => {
@@ -637,7 +642,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -649,7 +654,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | false: padding | assigned: viewport', () => {
@@ -658,7 +663,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -670,7 +675,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | false: content | assigned: viewport', () => {
@@ -679,7 +684,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -691,7 +696,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('false: padding | assigned: content', () => {
@@ -700,7 +705,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="content">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -711,7 +716,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | assigned: content', () => {
@@ -720,7 +725,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="content">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -731,7 +736,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('false: padding | assigned: viewport', () => {
@@ -740,7 +745,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -751,7 +756,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | assigned: viewport', () => {
@@ -760,7 +765,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -771,7 +776,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('false: padding | assigned: viewport & content', () => {
@@ -780,7 +785,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -792,7 +797,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: padding | assigned: viewport & content', () => {
@@ -801,7 +806,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -813,7 +818,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('false: content | assigned: padding', () => {
@@ -822,7 +827,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -833,7 +838,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | assigned: padding', () => {
@@ -842,7 +847,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -853,7 +858,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('false: content | assigned: viewport', () => {
@@ -862,7 +867,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -873,7 +878,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | assigned: viewport', () => {
@@ -882,7 +887,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="viewport">${content}</div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -893,7 +898,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('false: content | assigned: padding & viewport', () => {
@@ -902,7 +907,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -914,7 +919,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
test('true: content | assigned: padding & viewport', () => {
@@ -923,7 +928,7 @@ describe('structureSetup', () => {
(content, hostId) =>
`<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`
);
const setup = assertCorrectSetup(
const [, destroy] = assertCorrectSetupElements(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
@@ -935,7 +940,7 @@ describe('structureSetup', () => {
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
assertCorrectDestroy(snapshot, destroy);
});
});
});
+17 -10
View File
@@ -93,6 +93,16 @@ interface XY<T> {
x: T;
y: T;
}
type EventListener<NameArgsMap extends Record<string, any>, Name extends Extract<keyof NameArgsMap, string>> = (...args: NameArgsMap[Name] extends undefined ? [
] : [
args: NameArgsMap[Name]
]) => void;
type EventListenerGroup<NameArgsMap extends Record<string, any>, Name extends Extract<keyof NameArgsMap, string>> = EventListener<NameArgsMap, Name> | EventListener<NameArgsMap, Name>[];
type AddEventListener<NameArgsMap extends Record<string, any>> = <Name extends Extract<keyof NameArgsMap, string>>(name: Name, listener: EventListenerGroup<NameArgsMap, Name>) => () => void;
type RemoveEventListener<NameArgsMap extends Record<string, any>> = <Name extends Extract<keyof NameArgsMap, string>>(name?: Name, listener?: EventListenerGroup<NameArgsMap, Name>) => void;
type InitialEventListeners<NameArgsMap extends Record<string, any>> = {
[K in Extract<keyof NameArgsMap, string>]?: EventListenerGroup<NameArgsMap, K>;
};
/*
onScrollStart : null,
onScroll : null,
@@ -124,21 +134,18 @@ interface OnOverflowChangedEventListenerArgs {
amount: XY<number>;
};
}
interface EventListenerArgsMap {
interface OSEventListenersNameArgsMap {
initialized: undefined;
initializationWithdrawn: undefined;
overflowChanged: OnOverflowChangedEventListenerArgs;
updated: OnUpdatedEventListenerArgs;
destroyed: undefined;
}
type OSEventListener<N extends keyof EventListenerArgsMap> = undefined extends EventListenerArgsMap[N] ? () => void : (args: EventListenerArgsMap[N]) => void;
type AddEventListener = <N extends keyof EventListenerArgsMap>(name: N, listener: OSEventListener<N> | OSEventListener<N>[]) => () => void;
type RemoveEventListener = <N extends keyof EventListenerArgsMap>(name?: N, listener?: OSEventListener<N> | OSEventListener<N>[]) => void;
type EventListenersMap = {
[K in keyof EventListenerArgsMap]?: OSEventListener<K> | OSEventListener<K>[];
};
type AddOSEventListener = AddEventListener<OSEventListenersNameArgsMap>;
type RemoveOSEventListener = RemoveEventListener<OSEventListenersNameArgsMap>;
type InitialOSEventListeners = InitialEventListeners<OSEventListenersNameArgsMap>;
interface OverlayScrollbarsStatic {
(target: OSTarget | OSInitializationObject, options?: PartialOptions<OSOptions>, eventListeners?: EventListenersMap): OverlayScrollbars;
(target: OSTarget | OSInitializationObject, options?: PartialOptions<OSOptions>, eventListeners?: InitialOSEventListeners): OverlayScrollbars;
extend(osPlugin: OSPlugin | OSPlugin[]): void;
}
interface OverlayScrollbars {
@@ -147,8 +154,8 @@ interface OverlayScrollbars {
update(force?: boolean): void;
destroy(): void;
state(): any;
on: AddEventListener;
off: RemoveEventListener;
on: AddOSEventListener;
off: RemoveOSEventListener;
}
declare const OverlayScrollbars: OverlayScrollbarsStatic;
export { OverlayScrollbars as default };