Create LifecyclesBase function

This commit is contained in:
Rene
2020-12-07 18:39:44 +01:00
parent bdb2babc83
commit e91bdde504
6 changed files with 111 additions and 46 deletions
@@ -9,21 +9,17 @@ import {
topRightBottomLeft,
TRBL,
equalTRBL,
createCache,
optionsTemplateTypes as oTypes,
transformOptions,
validateOptions,
OptionsTemplateValue,
assignDeep,
} from 'support';
import { Lifecycle } from 'overlayscrollbars/lifecycles';
import { createLifecycleBase, Lifecycle } from 'overlayscrollbars/lifecycles';
import { getEnvironment, Environment } from 'environment';
import { createSizeObserver } from 'overlayscrollbars/observers/SizeObserver';
import { createTrinsicObserver } from 'overlayscrollbars/observers/TrinsicObserver';
export type OverflowBehavior = 'hidden' | 'scroll' | 'visible-hidden' | 'visible-scroll';
export interface StructureLifecycleOptions {
paddingAbsolute?: boolean;
paddingAbsolute: boolean;
overflowBehavior?: {
x?: OverflowBehavior;
y?: OverflowBehavior;
@@ -34,13 +30,6 @@ interface StructureLifecycleCache {
}
const overflowBehaviorAllowedValues: OptionsTemplateValue<OverflowBehavior> = 'visible-hidden visible-scroll scroll hidden';
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions<Required<StructureLifecycleOptions>>({
paddingAbsolute: [false, oTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
});
const classNameHost = 'os-host';
const classNameViewport = 'os-viewport';
@@ -51,12 +40,6 @@ const cssMarginEnd = cssProperty('margin-inline-end');
const cssBorderEnd = cssProperty('border-inline-end');
export const createStructureLifecycle = (target: HTMLElement, initialOptions?: StructureLifecycleOptions): Lifecycle<StructureLifecycleOptions> => {
const options: Required<StructureLifecycleOptions> = assignDeep(
{},
defaultOptions,
validateOptions<StructureLifecycleOptions>(initialOptions || {}, optionsTemplate)._validated
);
const destructFns: (() => any)[] = [];
const env: Environment = getEnvironment();
const scrollbarsOverlaid = env._nativeScrollbarIsOverlaid;
@@ -68,15 +51,30 @@ export const createStructureLifecycle = (target: HTMLElement, initialOptions?: S
const viewportElm = createDOM(`<div class="${classNameViewport} ${classNameViewportScrollbarStyling}"></div>`)[0];
const contentElm = createDOM(`<div class="${classNameContent}"></div>`)[0];
const updateCache = createCache<StructureLifecycleCache>({
padding: [() => topRightBottomLeft(target, 'padding'), equalTRBL],
});
const { _options, _update, _cacheChange } = createLifecycleBase<StructureLifecycleOptions, StructureLifecycleCache>(
{
paddingAbsolute: [false, oTypes.boolean],
overflowBehavior: {
x: ['scroll', overflowBehaviorAllowedValues],
y: ['scroll', overflowBehaviorAllowedValues],
},
},
{
padding: [() => topRightBottomLeft(target, 'padding'), equalTRBL],
},
initialOptions,
(changedOptions, changedCache) => {
console.log(changedOptions); // eslint-disable-line
console.log(changedCache); // eslint-disable-line
}
);
// eslint-disable-next-line
const onSizeChanged = (direction?: 'ltr' | 'rtl') => {
updateCache('padding');
_cacheChange('padding');
};
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsic: boolean) => {
console.log('heightAuot', heightIntrinsic);
console.log('heightAuot', heightIntrinsic); // eslint-disable-line
};
appendChildren(viewportElm, contentElm);
@@ -88,14 +86,8 @@ export const createStructureLifecycle = (target: HTMLElement, initialOptions?: S
destructFns.push(createTrinsicObserver(target, onTrinsicChanged));
return {
_options(newOptions?: StructureLifecycleOptions) {
// eslint-disable-next-line
console.log('_options', newOptions);
},
_update(force?: boolean) {
// eslint-disable-next-line
console.log('_options', force);
},
_options,
_update,
_destruct() {
runEach(destructFns);
removeElements(viewportElm);
@@ -1,7 +1,75 @@
import {
CacheUpdateInfo,
CachePropsToUpdate,
CacheChanged,
OptionsWithOptionsTemplate,
OptionsValidated,
transformOptions,
validateOptions,
assignDeep,
createCache,
} from 'support';
import { PlainObject } from 'typings';
export interface Lifecycle<T = PlainObject> {
_options(options?: T): void;
interface LifecycleUpdateHints<O, C> {
_force?: boolean;
_changedOptions?: OptionsValidated<O>;
_changedCache?: CachePropsToUpdate<C>;
}
interface AbstractLifecycle<O extends PlainObject> {
_options(newOptions?: O): O;
_update(force?: boolean): void;
}
export interface Lifecycle<T extends PlainObject> extends AbstractLifecycle<T> {
_destruct(): void;
}
export interface LifecycleBase<O extends PlainObject, C extends PlainObject> extends AbstractLifecycle<O> {
_cacheChange(cachePropsToUpdate?: CachePropsToUpdate<C>): void;
}
/**
* Creates a object which can be seen as the base of a lifecycle because it provides all the tools to manage a lifecycle and its options, cache and base functions.
* @param defaultOptionsWithTemplate A object which describes the options and the default options of the lifecycle.
* @param cacheUpdateInfo A object which describes how cache updates shall behave.
* @param initialOptions The initialOptions for the lifecylce. (Can be undefined)
* @param updateFunction The update function where cache and options updates are handled. Has two arguments which are the changedOptions and the changedCache objects.
*/
export const createLifecycleBase = <O, C>(
defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<O>>,
cacheUpdateInfo: CacheUpdateInfo<C>,
initialOptions: O | undefined,
updateFunction: (changedOptions: OptionsValidated<O>, changedCache: CacheChanged<C>) => any
): LifecycleBase<O, C> => {
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions<Required<O>>(defaultOptionsWithTemplate);
const options: Required<O> = assignDeep({}, defaultOptions, validateOptions<O>(initialOptions || ({} as O), optionsTemplate)._validated);
const cacheChange = createCache<C>(cacheUpdateInfo);
const update = (hints: LifecycleUpdateHints<O, C>) => {
const force = hints._force === true;
const changedCache = cacheChange(force ? undefined : hints._changedCache, force);
const changedOptions = force ? ({} as O) : hints._changedOptions || ({} as O);
updateFunction(changedOptions, changedCache);
};
return {
_options(newOptions?: O) {
if (newOptions) {
const { _validated: changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
assignDeep(options, changedOptions);
update({ _changedOptions: changedOptions });
}
return options;
},
_update: (force?: boolean) => {
update({ _force: force });
},
_cacheChange: (cachePropsToUpdate?: CachePropsToUpdate<C>) => {
update({ _changedCache: cachePropsToUpdate });
},
};
};
+5 -3
View File
@@ -12,12 +12,14 @@ type Cache<T> = {
type UpdateCacheProp<T> = <P extends keyof T>(prop: P, value: T[P], compare: CacheEqualFunction<T, P> | null) => void;
type PropsToUpdate<T> = Array<keyof T> | keyof T;
export type CachePropsToUpdate<T> = Array<keyof T> | keyof T;
export type CacheUpdateFunction<T, P extends keyof T> = (current?: T[P], previous?: T[P]) => T[P];
export type CacheEqualFunction<T, P extends keyof T> = (a?: T[P], b?: T[P]) => boolean;
export type CacheChange<T> = (propsToUpdate?: CachePropsToUpdate<T>, force?: boolean) => CacheChanged<T>;
export type CacheChanged<T> = {
[P in keyof T]?: T[P];
};
@@ -44,7 +46,7 @@ export type CacheUpdateInfo<T> = {
* @returns A function which can be called with wither one ar an array of properties which shall be updated. Optionally it can be called with the force param.
* This function returns a object which contains all changed cache properties, if a property isn't in this object it means that it didn't change.
*/
export const createCache = <T>(cacheUpdateInfo: CacheUpdateInfo<T>): ((propsToUpdate?: PropsToUpdate<T>, force?: boolean) => CacheChanged<T>) => {
export const createCache = <T>(cacheUpdateInfo: CacheUpdateInfo<T>): CacheChange<T> => {
const cache: Cache<T> = {} as T;
const allProps: Array<keyof T> = keys(cacheUpdateInfo) as Array<keyof T>;
@@ -73,7 +75,7 @@ export const createCache = <T>(cacheUpdateInfo: CacheUpdateInfo<T>): ((propsToUp
return result;
};
return (propsToUpdate?: PropsToUpdate<T>, force?: boolean) => {
return (propsToUpdate?: CachePropsToUpdate<T>, force?: boolean) => {
const finalPropsToUpdate: Array<keyof T> =
(isString(propsToUpdate) ? ([propsToUpdate] as Array<keyof T>) : (propsToUpdate as Array<keyof T>)) || allProps;
each(finalPropsToUpdate, (prop) => {
@@ -19,9 +19,12 @@ export type OptionsTemplate<T extends Required<T>> = {
? OptionsTemplateValue<T[P]>
: never;
};
export type OptionsValidatedResult<T> = {
export type OptionsValidated<T> = {
[P in keyof T]?: T[P];
};
export type OptionsValidationResult<T> = {
readonly _foreign: PlainObject;
readonly _validated: T;
readonly _validated: OptionsValidated<T>;
};
// Options With Options Template Typings:
export type OptionsWithOptionsTemplateValue<T extends OptionsTemplateNativeTypes> = [T, OptionsTemplateValue<T>];
@@ -1,6 +1,6 @@
import { each, indexOf, hasOwnProperty, keys } from 'support/utils';
import { type, isArray, isUndefined, isEmptyObject, isPlainObject, isString } from 'support/utils/types';
import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, Func, OptionsValidatedResult } from 'support/options';
import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, Func, OptionsValidationResult, OptionsValidated } from 'support/options';
import { PlainObject } from 'typings';
const { stringify } = JSON;
@@ -46,8 +46,8 @@ const validateRecursive = <T extends PlainObject>(
optionsDiff: T,
doWriteErrors?: boolean,
propPath?: string
): OptionsValidatedResult<T> => {
const validatedOptions: T = {} as T;
): OptionsValidationResult<T> => {
const validatedOptions: OptionsValidated<T> = {};
const optionsCopy: T = { ...options };
const props = keys(template).filter((prop) => hasOwnProperty(options, prop));
@@ -61,7 +61,7 @@ const validateRecursive = <T extends PlainObject>(
// if the template has a object as value, it means that the options are complex (verschachtelt)
if (templateIsComplex && isPlainObject(optionsValue)) {
const validatedResult = validateRecursive(optionsValue, templateValue as PlainObject, optionsDiffValue, doWriteErrors, propPrefix + prop);
validatedOptions[prop] = validatedResult._validated;
validatedOptions[prop] = validatedResult._validated as any;
optionsCopy[prop] = validatedResult._foreign as any;
each([optionsCopy, validatedOptions], (value) => {
@@ -145,7 +145,7 @@ const validateOptions = <T extends PlainObject>(
template: OptionsTemplate<Required<T>>,
optionsDiff?: T,
doWriteErrors?: boolean
): OptionsValidatedResult<T> => {
): OptionsValidationResult<T> => {
/*
if (!isEmptyObject(foreign) && doWriteErrors)
console.warn(`The following options are discarded due to invalidity:\r\n ${window.JSON.stringify(foreign, null, 2)}`);
@@ -1,7 +1,7 @@
import { OptionsTemplate, OptionsTemplateType, Func, OptionsValidatedResult } from 'support/options';
import { OptionsTemplate, OptionsTemplateType, Func, OptionsValidationResult } from 'support/options';
import { PlainObject } from 'typings';
declare const optionsTemplateTypes: OptionsTemplateTypesDictionary;
declare const validate: <T extends PlainObject<any>>(options: T, template: OptionsTemplate<Required<T>>, optionsDiff?: T | undefined, doWriteErrors?: boolean | undefined) => OptionsValidatedResult<T>;
declare const validate: <T extends PlainObject<any>>(options: T, template: OptionsTemplate<Required<T>>, optionsDiff?: T | undefined, doWriteErrors?: boolean | undefined) => OptionsValidationResult<T>;
export { validate, optionsTemplateTypes };
declare type OptionsTemplateTypesDictionary = {
readonly boolean: OptionsTemplateType<boolean>;