improve code

This commit is contained in:
Rene
2022-06-29 02:42:00 +02:00
parent f5ccc02959
commit 948560a71f
9 changed files with 120 additions and 101 deletions
+65 -43
View File
@@ -16,6 +16,8 @@ import {
getBoundingClientRect,
assignDeep,
cssProperty,
createCache,
equalXY,
} from 'support';
import {
classNameEnvironment,
@@ -26,59 +28,72 @@ import {
import { OSOptions, defaultOptions } from 'options';
import { OSTargetElement, PartialOptions } from 'typings';
type StructureInitializationElementFn<T> = ((target: OSTargetElement) => HTMLElement | T) | T;
type StructureInitializationStrategyElementFn<T> =
| ((target: OSTargetElement) => HTMLElement | T)
| T;
type ScrollbarsInitializationElementFn<T> =
type ScrollbarsInitializationStrategyElementFn<T> =
| ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => HTMLElement | T)
| T;
/**
* A Static element is an element which MUST be generated.
* If null (or the returned result is null), the initialization function is generatig the element, otherwise
* If null or undefined (or the returned result is null or undefined), the initialization function is generatig the element, otherwise
* the element returned by the function acts as the generated element.
*/
export type StructureInitializationStaticElement = StructureInitializationElementFn<null>;
export type StructureInitializationStrategyStaticElement = StructureInitializationStrategyElementFn<
null | undefined
>;
/**
* A Dynamic element is an element which CAN be generated.
* If null (or the returned result is null), then the default behavior is used.
* If boolean (or the returned result is boolean), the generation of the element is forced (or not).
* If the function returns and element, the element returned by the function acts as the generated element.
*/
export type StructureInitializationDynamicElement = StructureInitializationElementFn<
boolean | null
>;
export type StructureInitializationStrategyDynamicElement =
StructureInitializationStrategyElementFn<boolean>;
export interface StructureInitializationStrategy {
_host: StructureInitializationStaticElement;
_viewport: StructureInitializationStaticElement;
_padding: StructureInitializationDynamicElement;
_content: StructureInitializationDynamicElement;
_host: StructureInitializationStrategyStaticElement;
_viewport: StructureInitializationStrategyStaticElement;
_padding: StructureInitializationStrategyDynamicElement;
_content: StructureInitializationStrategyDynamicElement;
}
export interface ScrollbarsInitializationStrategy {
_scrollbarsSlot: ScrollbarsInitializationElementFn<null | undefined>;
/**
* The scrollbars slot. If null or undefined (or the returned result is null or undefined), the initialization function is deciding the element, otherwise
* the element returned by the function acts as the scrollbars slot.
*/
_scrollbarsSlot: ScrollbarsInitializationStrategyElementFn<null | undefined>;
}
export interface InitializationStrategy
extends StructureInitializationStrategy,
ScrollbarsInitializationStrategy {}
export type DefaultInitializationStrategy = {
[K in keyof InitializationStrategy]: Extract<
InitializationStrategy[K],
boolean | null | undefined
>;
};
export type OnEnvironmentChanged = (env: Environment) => void;
export interface Environment {
_nativeScrollbarSize: XY;
_nativeScrollbarIsOverlaid: XY<boolean>;
_nativeScrollbarStyling: boolean;
_rtlScrollBehavior: { n: boolean; i: boolean };
_flexboxGlue: boolean;
_cssCustomProperties: boolean;
readonly _nativeScrollbarSize: XY;
readonly _nativeScrollbarIsOverlaid: XY<boolean>;
readonly _nativeScrollbarStyling: boolean;
readonly _rtlScrollBehavior: { n: boolean; i: boolean };
readonly _flexboxGlue: boolean;
readonly _cssCustomProperties: boolean;
readonly _defaultInitializationStrategy: DefaultInitializationStrategy;
readonly _defaultDefaultOptions: OSOptions;
_addListener(listener: OnEnvironmentChanged): () => void;
_getInitializationStrategy(): InitializationStrategy;
_setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
_getDefaultOptions(): OSOptions;
_setDefaultOptions(newDefaultOptions: PartialOptions<OSOptions>): void;
_defaultInitializationStrategy: InitializationStrategy;
_defaultDefaultOptions: OSOptions;
}
let environmentInstance: Environment;
@@ -168,14 +183,13 @@ const getWindowDPR = (): number => {
return window.devicePixelRatio || dDPI / sDPI;
};
// init function decides for all values
const getDefaultInitializationStrategy = (
nativeScrollbarStyling: boolean
): InitializationStrategy => ({
): DefaultInitializationStrategy => ({
_host: null,
_viewport: null,
_padding: null,
_content: null,
_padding: !nativeScrollbarStyling,
_content: false,
_scrollbarsSlot: null,
});
@@ -185,15 +199,18 @@ const createEnvironment = (): Environment => {
const envElm = envDOM[0] as HTMLElement;
const envChildElm = envElm.firstChild as HTMLElement;
const onChangedListener: Set<OnEnvironmentChanged> = new Set();
const nativeScrollbarSize = getNativeScrollbarSize(body, envElm);
const [updateNativeScrollbarSizeCache, getNativeScrollbarSizeCache] = createCache({
_initialValue: getNativeScrollbarSize(body, envElm),
_equal: equalXY,
});
const [nativeScrollbarSize] = getNativeScrollbarSizeCache();
const nativeScrollbarStyling = getNativeScrollbarStyling(envElm);
const nativeScrollbarIsOverlaid = {
x: nativeScrollbarSize.x === 0,
y: nativeScrollbarSize.y === 0,
};
const defaultInitializationStrategy = getDefaultInitializationStrategy(nativeScrollbarStyling);
let initializationStrategy = defaultInitializationStrategy;
let defaultDefaultOptions = defaultOptions;
const initializationStrategy = getDefaultInitializationStrategy(nativeScrollbarStyling);
const defaultDefaultOptions = assignDeep({}, defaultOptions);
const env: Environment = {
_nativeScrollbarSize: nativeScrollbarSize,
@@ -206,16 +223,24 @@ const createEnvironment = (): Environment => {
onChangedListener.add(listener);
return () => onChangedListener.delete(listener);
},
_getInitializationStrategy: () => ({ ...initializationStrategy }),
_getInitializationStrategy: assignDeep<InitializationStrategy, InitializationStrategy>.bind(
0,
{} as InitializationStrategy,
initializationStrategy
),
_setInitializationStrategy(newInitializationStrategy) {
initializationStrategy = assignDeep({}, initializationStrategy, newInitializationStrategy);
assignDeep(initializationStrategy, newInitializationStrategy);
},
_getDefaultOptions: () => ({ ...defaultDefaultOptions }),
_getDefaultOptions: assignDeep<OSOptions, OSOptions>.bind(
0,
{} as OSOptions,
defaultDefaultOptions
),
_setDefaultOptions(newDefaultOptions) {
defaultDefaultOptions = assignDeep({}, defaultDefaultOptions, newDefaultOptions);
assignDeep(defaultDefaultOptions, newDefaultOptions);
},
_defaultInitializationStrategy: defaultInitializationStrategy,
_defaultDefaultOptions: defaultDefaultOptions,
_defaultInitializationStrategy: assignDeep({}, initializationStrategy),
_defaultDefaultOptions: assignDeep({}, defaultDefaultOptions),
};
removeAttr(envElm, 'style');
@@ -224,7 +249,6 @@ const createEnvironment = (): Environment => {
if (!nativeScrollbarStyling && (!nativeScrollbarIsOverlaid.x || !nativeScrollbarIsOverlaid.y)) {
let size = windowSize();
let dpr = getWindowDPR();
let scrollbarSize = nativeScrollbarSize;
window.addEventListener('resize', () => {
if (onChangedListener.size) {
@@ -251,18 +275,16 @@ const createEnvironment = (): Environment => {
const isZoom = deltaIsBigger && difference && dprChanged;
if (isZoom) {
const newScrollbarSize = getNativeScrollbarSize(body, envElm);
// keep the object same!
environmentInstance._nativeScrollbarSize.x = newScrollbarSize.x;
environmentInstance._nativeScrollbarSize.y = newScrollbarSize.y;
const [scrollbarSize, scrollbarSizeChanged] = updateNativeScrollbarSizeCache(
getNativeScrollbarSize(body, envElm)
);
assignDeep(environmentInstance._nativeScrollbarSize, scrollbarSize); // keep the object same!
removeElements(envElm);
if (scrollbarSize.x !== newScrollbarSize.x || scrollbarSize.y !== newScrollbarSize.y) {
if (scrollbarSizeChanged) {
runEach(onChangedListener);
}
scrollbarSize = newScrollbarSize;
}
size = sizeNew;
+3 -1
View File
@@ -1,5 +1,5 @@
import { assignDeep, each, isObject, keys, isArray, hasOwnProperty, isFunction } from 'support';
import { PartialOptions } from 'typings';
import { PartialOptions, ReadonlyOptions } from 'typings';
const stringify = (value: any) =>
JSON.stringify(value, (_, val) => {
@@ -83,6 +83,8 @@ export interface OSOptions {
*/
}
export type ReadonlyOSOptions = ReadonlyOptions<OSOptions>;
export interface OverflowChangedArgs {
x: boolean;
y: boolean;
@@ -1,7 +1,7 @@
import { OSTarget, OSInitializationObject, PartialOptions } from 'typings';
import { assignDeep, isEmptyObject, each, isFunction, keys, isHTMLElement, WH, XY } from 'support';
import { createStructureSetup, createScrollbarsSetup } from 'setups';
import { getOptionsDiff, OSOptions } from 'options';
import { getOptionsDiff, OSOptions, ReadonlyOSOptions } from 'options';
import { getEnvironment } from 'environment';
import {
getPlugins,
@@ -78,7 +78,11 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
const validate = optionsValidationPlugin && optionsValidationPlugin._;
return validate ? validate(opts, true) : opts;
};
const currentOptions: OSOptions = assignDeep({}, _getDefaultOptions(), validateOptions(options));
const currentOptions: ReadonlyOSOptions = assignDeep(
{},
_getDefaultOptions(),
validateOptions(options)
);
const [addEvent, removeEvent, triggerEvent] = createOSEventListenerHub(eventListeners);
if (
@@ -4,7 +4,7 @@ import {
ScrollbarsSetupElementsObj,
} from 'setups/scrollbarsSetup/scrollbarsSetup.elements';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { OSOptions } from 'options';
import type { ReadonlyOSOptions } from 'options';
import type { Setup } from 'setups';
import type { OSTarget } from 'typings';
@@ -17,7 +17,7 @@ export interface ScrollbarsSetupStaticState {
export const createScrollbarsSetup = (
target: OSTarget,
options: OSOptions,
options: ReadonlyOSOptions,
structureSetupElements: StructureSetupElementsObj
): Setup<ScrollbarsSetupState, ScrollbarsSetupStaticState> => {
const state = createState({});
@@ -1,5 +1,5 @@
import { assignDeep, hasOwnProperty } from 'support';
import type { OSOptions } from 'options';
import type { OSOptions, ReadonlyOSOptions } from 'options';
import type { PartialOptions } from 'typings';
export type SetupElements<T extends Record<string, any>> = [elements: T, destroy: () => void];
@@ -35,7 +35,7 @@ const getPropByPath = <T>(obj: any, path: string): T =>
export const createOptionCheck =
(
options: OSOptions,
options: ReadonlyOSOptions,
changedOptions: PartialOptions<OSOptions>,
force?: boolean
): SetupUpdateCheckOption =>
@@ -28,9 +28,8 @@ import {
} from 'classnames';
import {
getEnvironment,
StructureInitializationStaticElement,
StructureInitializationDynamicElement,
StructureInitializationStrategy,
StructureInitializationStrategyStaticElement,
StructureInitializationStrategyDynamicElement,
} from 'environment';
import { OSTarget, OSTargetElement, StructureInitialization } from 'typings';
@@ -81,20 +80,20 @@ const createUniqueViewportArrangeElement = (): HTMLStyleElement | false => {
const staticCreationFromStrategy = (
target: OSTargetElement,
initializationValue: HTMLElement | undefined,
strategy: StructureInitializationStaticElement,
strategy: StructureInitializationStrategyStaticElement,
elementClass: string
): HTMLElement => {
const result =
initializationValue || (isFunction(strategy) ? strategy(target) : (strategy as null));
initializationValue ||
(isFunction(strategy) ? strategy(target) : (strategy as null | undefined));
return result || createDiv(elementClass);
};
const dynamicCreationFromStrategy = (
target: OSTargetElement,
initializationValue: HTMLElement | boolean | undefined,
strategy: StructureInitializationDynamicElement,
elementClass: string,
defaultValue: boolean
strategy: StructureInitializationStrategyDynamicElement,
elementClass: string
): HTMLElement | false => {
const takeInitializationValue = isBoolean(initializationValue) || initializationValue;
const result = takeInitializationValue
@@ -103,10 +102,6 @@ const dynamicCreationFromStrategy = (
? strategy(target)
: strategy;
if (result === null) {
return defaultValue ? createDiv(elementClass) : false;
}
return result === true ? createDiv(elementClass) : result;
};
@@ -117,7 +112,7 @@ export const createStructureSetupElements = (target: OSTarget): StructureSetupEl
_viewport: viewportInitializationStrategy,
_padding: paddingInitializationStrategy,
_content: contentInitializationStrategy,
} = _getInitializationStrategy() as StructureInitializationStrategy;
} = _getInitializationStrategy();
const targetIsElm = isHTMLElement(target);
const targetStructureInitialization = target as StructureInitialization;
const targetElement = targetIsElm
@@ -125,7 +120,7 @@ export const createStructureSetupElements = (target: OSTarget): StructureSetupEl
: targetStructureInitialization.target;
const isTextarea = is(targetElement, 'textarea');
const isBody = !isTextarea && is(targetElement, 'body');
const ownerDocument: HTMLDocument = targetElement!.ownerDocument;
const ownerDocument = targetElement!.ownerDocument;
const bodyElm = ownerDocument.body as HTMLBodyElement;
const wnd = ownerDocument.defaultView as Window;
const evaluatedTargetObj: StructureSetupElementsObj = {
@@ -148,15 +143,13 @@ export const createStructureSetupElements = (target: OSTarget): StructureSetupEl
targetElement,
targetStructureInitialization.padding,
paddingInitializationStrategy,
classNamePadding,
!_nativeScrollbarStyling // default value for padding
classNamePadding
),
_content: dynamicCreationFromStrategy(
targetElement,
targetStructureInitialization.content,
contentInitializationStrategy,
classNameContent,
false // default value for content
classNameContent
),
_viewportArrange: createUniqueViewportArrangeElement(),
_windowElm: wnd,
@@ -6,7 +6,7 @@ import { createStructureSetupObservers } from 'setups/structureSetup/structureSe
import type { StructureSetupUpdateHints } from 'setups/structureSetup/structureSetup.update';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { TRBL, CacheValues, XY, WH } from 'support';
import type { OSOptions } from 'options';
import type { OSOptions, ReadonlyOSOptions } from 'options';
import type { Setup } from 'setups';
import type { OSTarget, PartialOptions, StyleObject } from 'typings';
@@ -64,7 +64,7 @@ const initialStructureSetupUpdateState: StructureSetupState = {
export const createStructureSetup = (
target: OSTarget,
options: OSOptions
options: ReadonlyOSOptions
): Setup<StructureSetupState, StructureSetupStaticState> => {
const checkOptionsFallback = createOptionCheck(options, {});
const state = createState(initialStructureSetupUpdateState);
@@ -2,6 +2,10 @@ export type PartialOptions<T> = {
[P in keyof T]?: T[P] extends Record<string, unknown> ? PartialOptions<T[P]> : T[P];
};
export type ReadonlyOptions<T> = {
readonly [P in keyof T]: T[P] extends Record<string, unknown> ? ReadonlyOptions<T[P]> : T[P];
};
export type PlainObject<T = any> = { [name: string]: T };
export type StyleObject<CustomCssProps = ''> = {
@@ -1,7 +1,7 @@
import {
Environment,
StructureInitializationStaticElement,
StructureInitializationDynamicElement,
StructureInitializationStrategyStaticElement,
StructureInitializationStrategyDynamicElement,
} from 'environment';
import { OSTarget, StructureInitialization } from 'typings';
import {
@@ -167,8 +167,9 @@ const assertCorrectSetupElements = (
elm: Element | null,
input: HTMLElement | boolean | undefined,
isStaticStrategy: boolean,
strategy: StructureInitializationStaticElement | StructureInitializationDynamicElement,
id: string
strategy:
| StructureInitializationStrategyStaticElement
| StructureInitializationStrategyDynamicElement
) => {
if (input) {
expect(elm).toBeTruthy();
@@ -178,7 +179,7 @@ const assertCorrectSetupElements = (
}
if (input === undefined) {
if (isStaticStrategy) {
strategy = strategy as StructureInitializationStaticElement;
strategy = strategy as StructureInitializationStrategyStaticElement;
if (typeof strategy === 'function') {
const result = strategy(target);
if (result) {
@@ -190,21 +191,14 @@ const assertCorrectSetupElements = (
expect(elm).toBeTruthy();
}
} else {
strategy = strategy as StructureInitializationDynamicElement;
const expectDefaultValue = () => {
if (id === 'padding') {
if (_nativeScrollbarStyling) {
expect(elm).toBeFalsy();
} else {
expect(elm).toBeTruthy();
}
} else if (id === 'content') {
expect(elm).toBeFalsy();
}
};
strategy = strategy as StructureInitializationStrategyDynamicElement;
expect(strategy).not.toBe(null);
expect(strategy).not.toBe(undefined);
if (typeof strategy === 'function') {
const result = strategy(target);
const resultIsBoolean = typeof result === 'boolean';
expect(result).not.toBe(null);
expect(result).not.toBe(undefined);
if (resultIsBoolean) {
if (result) {
expect(elm).toBeTruthy();
@@ -213,8 +207,6 @@ const assertCorrectSetupElements = (
}
} else if (result) {
expect(elm).toBe(result);
} else {
expectDefaultValue();
}
} else {
const strategyIsBoolean = typeof strategy === 'boolean';
@@ -224,8 +216,6 @@ const assertCorrectSetupElements = (
} else {
expect(elm).toBeFalsy();
}
} else {
expectDefaultValue();
}
}
}
@@ -240,10 +230,10 @@ const assertCorrectSetupElements = (
}
if (inputIsElement) {
checkStrategyDependendElements(padding, undefined, false, paddingInitStrategy, 'padding');
checkStrategyDependendElements(content, undefined, false, contentInitStrategy, 'content');
checkStrategyDependendElements(viewport, undefined, true, viewportInitStrategy, 'viewport');
checkStrategyDependendElements(host, undefined, true, hostInitStrategy, 'host');
checkStrategyDependendElements(padding, undefined, false, paddingInitStrategy);
checkStrategyDependendElements(content, undefined, false, contentInitStrategy);
checkStrategyDependendElements(viewport, undefined, true, viewportInitStrategy);
checkStrategyDependendElements(host, undefined, true, hostInitStrategy);
} else {
const {
padding: inputPadding,
@@ -251,10 +241,10 @@ const assertCorrectSetupElements = (
viewport: inputViewport,
host: inputHost,
} = inputAsObj;
checkStrategyDependendElements(padding, inputPadding, false, paddingInitStrategy, 'padding');
checkStrategyDependendElements(content, inputContent, false, contentInitStrategy, 'content');
checkStrategyDependendElements(viewport, inputViewport, true, viewportInitStrategy, 'viewport');
checkStrategyDependendElements(host, inputHost, true, hostInitStrategy, 'host');
checkStrategyDependendElements(padding, inputPadding, false, paddingInitStrategy);
checkStrategyDependendElements(content, inputContent, false, contentInitStrategy);
checkStrategyDependendElements(viewport, inputViewport, true, viewportInitStrategy);
checkStrategyDependendElements(host, inputHost, true, hostInitStrategy);
}
return [elements, destroy];
@@ -327,8 +317,12 @@ const envInitStrategyAssigned = {
_getInitializationStrategy: () => ({
_host: () => document.querySelector('#host1') as HTMLElement,
_viewport: (target: HTMLElement) => target.querySelector('#viewport') as HTMLElement,
_content: (target: HTMLElement) => target.querySelector('#content') as HTMLElement,
_padding: (target: HTMLElement) => target.querySelector('#padding') as HTMLElement,
_content: (target: HTMLElement) =>
target.querySelector<HTMLElement>('#content') ||
env._defaultInitializationStrategy._content,
_padding: (target: HTMLElement) =>
target.querySelector<HTMLElement>('#padding') ||
env._defaultInitializationStrategy._padding,
_scrollbarsSlot: null,
}),
},