diff --git a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts index 4defdee..ab4b3fe 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/StructureLifecycle.ts @@ -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 = 'visible-hidden visible-scroll scroll hidden'; -const { _template: optionsTemplate, _options: defaultOptions } = transformOptions>({ - 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 => { - const options: Required = assignDeep( - {}, - defaultOptions, - validateOptions(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(`
`)[0]; const contentElm = createDOM(`
`)[0]; - const updateCache = createCache({ - padding: [() => topRightBottomLeft(target, 'padding'), equalTRBL], - }); + const { _options, _update, _cacheChange } = createLifecycleBase( + { + 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); diff --git a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts index 96eb28e..efde8ab 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars/lifecycles/index.ts @@ -1,7 +1,75 @@ +import { + CacheUpdateInfo, + CachePropsToUpdate, + CacheChanged, + OptionsWithOptionsTemplate, + OptionsValidated, + transformOptions, + validateOptions, + assignDeep, + createCache, +} from 'support'; import { PlainObject } from 'typings'; -export interface Lifecycle { - _options(options?: T): void; +interface LifecycleUpdateHints { + _force?: boolean; + _changedOptions?: OptionsValidated; + _changedCache?: CachePropsToUpdate; +} + +interface AbstractLifecycle { + _options(newOptions?: O): O; _update(force?: boolean): void; +} + +export interface Lifecycle extends AbstractLifecycle { _destruct(): void; } + +export interface LifecycleBase extends AbstractLifecycle { + _cacheChange(cachePropsToUpdate?: CachePropsToUpdate): 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 = ( + defaultOptionsWithTemplate: OptionsWithOptionsTemplate>, + cacheUpdateInfo: CacheUpdateInfo, + initialOptions: O | undefined, + updateFunction: (changedOptions: OptionsValidated, changedCache: CacheChanged) => any +): LifecycleBase => { + const { _template: optionsTemplate, _options: defaultOptions } = transformOptions>(defaultOptionsWithTemplate); + const options: Required = assignDeep({}, defaultOptions, validateOptions(initialOptions || ({} as O), optionsTemplate)._validated); + const cacheChange = createCache(cacheUpdateInfo); + + const update = (hints: LifecycleUpdateHints) => { + 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) => { + update({ _changedCache: cachePropsToUpdate }); + }, + }; +}; diff --git a/packages/overlayscrollbars/src/support/cache/cache.ts b/packages/overlayscrollbars/src/support/cache/cache.ts index 8930d5f..28ca662 100644 --- a/packages/overlayscrollbars/src/support/cache/cache.ts +++ b/packages/overlayscrollbars/src/support/cache/cache.ts @@ -12,12 +12,14 @@ type Cache = { type UpdateCacheProp =

(prop: P, value: T[P], compare: CacheEqualFunction | null) => void; -type PropsToUpdate = Array | keyof T; +export type CachePropsToUpdate = Array | keyof T; export type CacheUpdateFunction = (current?: T[P], previous?: T[P]) => T[P]; export type CacheEqualFunction = (a?: T[P], b?: T[P]) => boolean; +export type CacheChange = (propsToUpdate?: CachePropsToUpdate, force?: boolean) => CacheChanged; + export type CacheChanged = { [P in keyof T]?: T[P]; }; @@ -44,7 +46,7 @@ export type CacheUpdateInfo = { * @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 = (cacheUpdateInfo: CacheUpdateInfo): ((propsToUpdate?: PropsToUpdate, force?: boolean) => CacheChanged) => { +export const createCache = (cacheUpdateInfo: CacheUpdateInfo): CacheChange => { const cache: Cache = {} as T; const allProps: Array = keys(cacheUpdateInfo) as Array; @@ -73,7 +75,7 @@ export const createCache = (cacheUpdateInfo: CacheUpdateInfo): ((propsToUp return result; }; - return (propsToUpdate?: PropsToUpdate, force?: boolean) => { + return (propsToUpdate?: CachePropsToUpdate, force?: boolean) => { const finalPropsToUpdate: Array = (isString(propsToUpdate) ? ([propsToUpdate] as Array) : (propsToUpdate as Array)) || allProps; each(finalPropsToUpdate, (prop) => { diff --git a/packages/overlayscrollbars/src/support/options/index.ts b/packages/overlayscrollbars/src/support/options/index.ts index f43dcdc..646cd1a 100644 --- a/packages/overlayscrollbars/src/support/options/index.ts +++ b/packages/overlayscrollbars/src/support/options/index.ts @@ -19,9 +19,12 @@ export type OptionsTemplate> = { ? OptionsTemplateValue : never; }; -export type OptionsValidatedResult = { +export type OptionsValidated = { + [P in keyof T]?: T[P]; +}; +export type OptionsValidationResult = { readonly _foreign: PlainObject; - readonly _validated: T; + readonly _validated: OptionsValidated; }; // Options With Options Template Typings: export type OptionsWithOptionsTemplateValue = [T, OptionsTemplateValue]; diff --git a/packages/overlayscrollbars/src/support/options/validation.ts b/packages/overlayscrollbars/src/support/options/validation.ts index 86670bd..e4e5a84 100644 --- a/packages/overlayscrollbars/src/support/options/validation.ts +++ b/packages/overlayscrollbars/src/support/options/validation.ts @@ -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 = ( optionsDiff: T, doWriteErrors?: boolean, propPath?: string -): OptionsValidatedResult => { - const validatedOptions: T = {} as T; +): OptionsValidationResult => { + const validatedOptions: OptionsValidated = {}; const optionsCopy: T = { ...options }; const props = keys(template).filter((prop) => hasOwnProperty(options, prop)); @@ -61,7 +61,7 @@ const validateRecursive = ( // 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 = ( template: OptionsTemplate>, optionsDiff?: T, doWriteErrors?: boolean -): OptionsValidatedResult => { +): OptionsValidationResult => { /* if (!isEmptyObject(foreign) && doWriteErrors) console.warn(`The following options are discarded due to invalidity:\r\n ${window.JSON.stringify(foreign, null, 2)}`); diff --git a/packages/overlayscrollbars/types/support/options/validation.d.ts b/packages/overlayscrollbars/types/support/options/validation.d.ts index 90756c8..7b24af5 100644 --- a/packages/overlayscrollbars/types/support/options/validation.d.ts +++ b/packages/overlayscrollbars/types/support/options/validation.d.ts @@ -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: >(options: T, template: OptionsTemplate>, optionsDiff?: T | undefined, doWriteErrors?: boolean | undefined) => OptionsValidatedResult; +declare const validate: >(options: T, template: OptionsTemplate>, optionsDiff?: T | undefined, doWriteErrors?: boolean | undefined) => OptionsValidationResult; export { validate, optionsTemplateTypes }; declare type OptionsTemplateTypesDictionary = { readonly boolean: OptionsTemplateType;