diff --git a/packages/overlayscrollbars/src/environment.ts b/packages/overlayscrollbars/src/environment.ts index f66090a..540a42a 100644 --- a/packages/overlayscrollbars/src/environment.ts +++ b/packages/overlayscrollbars/src/environment.ts @@ -27,10 +27,9 @@ import { import { defaultOptions } from '~/options'; import { getPlugins, scrollbarsHidingPluginName } from '~/plugins'; import type { XY, EventListener } from '~/support'; -import type { Options } from '~/options'; -import type { DeepPartial } from '~/typings'; +import type { Options, PartialOptions } from '~/options'; import type { ScrollbarsHidingPluginInstance } from '~/plugins'; -import type { Initialization } from '~/initialization'; +import type { Initialization, PartialInitialization } from '~/initialization'; type EnvironmentEventMap = { _: []; @@ -68,14 +67,14 @@ export interface Environment { * @param newDefaultInitialization The new default Initialization. * @returns The current default Initialization. */ - setDefaultInitialization(newDefaultInitialization: DeepPartial): Initialization; + setDefaultInitialization(newDefaultInitialization: PartialInitialization): Initialization; /** * Sets new default Options. * If the new default Options are partially filled, they're deeply merged with the current default Options. * @param newDefaultOptions The new default Options. * @returns The current default options. */ - setDefaultOptions(newDefaultOptions: DeepPartial): Options; + setDefaultOptions(newDefaultOptions: PartialOptions): Options; } export interface InternalEnvironment { @@ -89,9 +88,9 @@ export interface InternalEnvironment { readonly _staticDefaultOptions: Options; _addListener(listener: EventListener): () => void; _getDefaultInitialization(): Initialization; - _setDefaultInitialization(newInitialization: DeepPartial): Initialization; + _setDefaultInitialization(newInitialization: PartialInitialization): Initialization; _getDefaultOptions(): Options; - _setDefaultOptions(newDefaultOptions: DeepPartial): Options; + _setDefaultOptions(newDefaultOptions: PartialOptions): Options; } let environmentInstance: InternalEnvironment; diff --git a/packages/overlayscrollbars/src/eventListeners.ts b/packages/overlayscrollbars/src/eventListeners.ts index ef253f7..8ff2bc6 100644 --- a/packages/overlayscrollbars/src/eventListeners.ts +++ b/packages/overlayscrollbars/src/eventListeners.ts @@ -1,6 +1,5 @@ import type { OverlayScrollbars } from '~/overlayscrollbars'; -import type { DeepPartial } from '~/typings'; -import type { Options } from '~/options'; +import type { PartialOptions } from '~/options'; import type { EventListeners as GeneralEventListeners, EventListener as GeneralEventListener, @@ -33,7 +32,7 @@ export interface OnUpdatedEventListenerArgs { contentMutation: boolean; }; /** The changed options. */ - changedOptions: DeepPartial; + changedOptions: PartialOptions; /** Whether the update happened with and force invalidated cache. */ force: boolean; } diff --git a/packages/overlayscrollbars/src/index.ts b/packages/overlayscrollbars/src/index.ts index 37b055c..c6a723e 100644 --- a/packages/overlayscrollbars/src/index.ts +++ b/packages/overlayscrollbars/src/index.ts @@ -5,6 +5,8 @@ export { ScrollbarsHidingPlugin, SizeObserverPlugin, ClickScrollPlugin } from '~ export type { Options, + PartialOptions, + ReadonlyOptions, OverflowBehavior, ScrollbarsVisibilityBehavior as ScrollbarVisibilityBehavior, ScrollbarsAutoHideBehavior as ScrollbarAutoHideBehavior, @@ -17,6 +19,7 @@ export type { } from '~/eventListeners'; export type { Initialization, + PartialInitialization, InitializationTarget, InitializationTargetElement, InitializationTargetObject, diff --git a/packages/overlayscrollbars/src/initialization.ts b/packages/overlayscrollbars/src/initialization.ts index 88271fb..5a30fda 100644 --- a/packages/overlayscrollbars/src/initialization.ts +++ b/packages/overlayscrollbars/src/initialization.ts @@ -85,6 +85,8 @@ export type Initialization = { }; }; +export type PartialInitialization = DeepPartial; + /** The initialization target element. */ export type InitializationTargetElement = HTMLElement; // | HTMLTextAreaElement; @@ -92,7 +94,7 @@ export type InitializationTargetElement = HTMLElement; // | HTMLTextAreaElement; * The initialization target object. * OverlayScrollbars({ target: myElement }) is equivalent to OverlayScrollbars(myElement). */ -export type InitializationTargetObject = DeepPartial & { +export type InitializationTargetObject = PartialInitialization & { target: InitializationTargetElement; }; diff --git a/packages/overlayscrollbars/src/options.ts b/packages/overlayscrollbars/src/options.ts index e01be48..1187bfa 100644 --- a/packages/overlayscrollbars/src/options.ts +++ b/packages/overlayscrollbars/src/options.ts @@ -134,6 +134,8 @@ export interface Options { export type ReadonlyOptions = DeepReadonly; +export type PartialOptions = DeepPartial; + export const defaultOptions: Options = { paddingAbsolute: false, showNativeOverlaidScrollbars: false, diff --git a/packages/overlayscrollbars/src/overlayscrollbars.ts b/packages/overlayscrollbars/src/overlayscrollbars.ts index c765b9b..efea5f9 100644 --- a/packages/overlayscrollbars/src/overlayscrollbars.ts +++ b/packages/overlayscrollbars/src/overlayscrollbars.ts @@ -16,10 +16,10 @@ import { createStructureSetup, createScrollbarsSetup } from '~/setups'; import { getPlugins, addPlugin, optionsValidationPluginName } from '~/plugins'; import type { Environment } from '~/environment'; import type { XY, TRBL } from '~/support'; -import type { Options, ReadonlyOptions } from '~/options'; +import type { Options, PartialOptions, ReadonlyOptions } from '~/options'; import type { Plugin, OptionsValidationPluginInstance, PluginInstance } from '~/plugins'; import type { InitializationTarget } from '~/initialization'; -import type { DeepPartial, OverflowStyle } from '~/typings'; +import type { OverflowStyle } from '~/typings'; import type { EventListenerMap, EventListener, EventListeners } from '~/eventListeners'; import type { ScrollbarsSetupElement, @@ -47,7 +47,7 @@ export interface OverlayScrollbarsStatic { */ ( target: InitializationTarget, - options: DeepPartial, + options: PartialOptions, eventListeners?: EventListeners ): OverlayScrollbars; @@ -164,11 +164,12 @@ export interface OverlayScrollbars { options(): Options; /** * Sets the options of the instance. - * If the new options are partially filled, they're deeply merged with the current options. + * If the new options are partially filled, they're deeply merged with either the current options or the current default options. * @param newOptions The new options. + * @param pure If true the new options will be merged with the current default options instead of the current options. * @returns Returns the current options of the instance. */ - options(newOptions: DeepPartial): Options; + options(newOptions: PartialOptions, pure?: boolean): Options; /** * Adds event listeners to the instance. @@ -232,7 +233,7 @@ const invokePluginInstance = ( // eslint-disable-next-line @typescript-eslint/no-redeclare export const OverlayScrollbars: OverlayScrollbarsStatic = ( target: InitializationTarget, - options?: DeepPartial, + options?: PartialOptions, eventListeners?: EventListeners ) => { const { _getDefaultOptions, _getDefaultInitialization, _addListener } = getEnvironment(); @@ -242,7 +243,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = ( const potentialInstance = getInstance(instanceTarget); if (options && !potentialInstance) { let destroyed = false; - const validateOptions = (newOptions: DeepPartial) => { + const validateOptions = (newOptions: PartialOptions) => { const optionsValidationPlugin = getPlugins()[ optionsValidationPluginName ] as OptionsValidationPluginInstance; @@ -266,7 +267,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = ( // eslint-disable-next-line @typescript-eslint/no-use-before-define (scrollEvent) => triggerEvent('scroll', [instance, scrollEvent]) ); - const update = (changedOptions: DeepPartial, force?: boolean): boolean => + const update = (changedOptions: PartialOptions, force?: boolean): boolean => updateStructure(changedOptions, !!force); const removeEnvListener = _addListener(update.bind(0, {}, true)); const destroy = (canceled?: boolean) => { @@ -284,9 +285,13 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = ( }; const instance: OverlayScrollbars = { - options(newOptions?: DeepPartial) { + options(newOptions?: PartialOptions, pure?: boolean) { if (newOptions) { - const changedOptions = getOptionsDiff(currentOptions, validateOptions(newOptions)); + const base = pure ? _getDefaultOptions() : {}; + const changedOptions = getOptionsDiff( + currentOptions, + assignDeep(base, validateOptions(newOptions)) + ); if (!isEmptyObject(changedOptions)) { assignDeep(currentOptions, changedOptions); update(changedOptions); diff --git a/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts b/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts index 062421e..a355905 100644 --- a/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts +++ b/packages/overlayscrollbars/src/plugins/optionsValidationPlugin/optionsValidationPlugin.ts @@ -4,6 +4,7 @@ import { } from '~/plugins/optionsValidationPlugin/validation'; import type { Options, + PartialOptions, OverflowBehavior, ScrollbarsVisibilityBehavior, ScrollbarsAutoHideBehavior, @@ -12,7 +13,6 @@ import type { OptionsTemplate, OptionsTemplateValue, } from '~/plugins/optionsValidationPlugin/validation'; -import type { DeepPartial } from '~/typings'; import type { Plugin } from '~/plugins'; const numberAllowedValues: OptionsTemplateValue = oTypes.number; @@ -58,7 +58,7 @@ const optionsTemplate: OptionsTemplate = { }; export type OptionsValidationPluginInstance = { - _: (options: DeepPartial, doWriteErrors?: boolean) => DeepPartial; + _: (options: PartialOptions, doWriteErrors?: boolean) => PartialOptions; }; export const optionsValidationPluginName = '__osOptionsValidationPlugin'; @@ -66,7 +66,7 @@ export const optionsValidationPluginName = '__osOptionsValidationPlugin'; export const OptionsValidationPlugin: Plugin = /* @__PURE__ */ (() => ({ [optionsValidationPluginName]: { - _: (options: DeepPartial, doWriteErrors?: boolean) => { + _: (options: PartialOptions, doWriteErrors?: boolean) => { const [validated, foreign] = validateOptions(optionsTemplate, options, doWriteErrors); return { ...foreign, ...validated }; }, diff --git a/packages/overlayscrollbars/src/setups/setups.ts b/packages/overlayscrollbars/src/setups/setups.ts index 055ee57..c43a2e5 100644 --- a/packages/overlayscrollbars/src/setups/setups.ts +++ b/packages/overlayscrollbars/src/setups/setups.ts @@ -1,11 +1,10 @@ import { assignDeep, hasOwnProperty } from '~/support'; -import type { Options, ReadonlyOptions } from '~/options'; -import type { DeepPartial } from '~/typings'; +import type { PartialOptions, ReadonlyOptions } from '~/options'; export type SetupElements> = [elements: T, destroy: () => void]; export type SetupUpdate = ( - changedOptions: DeepPartial, + changedOptions: PartialOptions, force: boolean, ...args: Args ) => R; @@ -38,7 +37,7 @@ const getPropByPath = (obj: any, path: string): T => export const createOptionCheck = ( options: ReadonlyOptions, - changedOptions: DeepPartial, + changedOptions: PartialOptions, force?: boolean ): SetupUpdateCheckOption => (path: string) => diff --git a/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts b/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts index 1ce15d0..fb56640 100644 --- a/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts +++ b/packages/overlayscrollbars/src/setups/structureSetup/structureSetup.ts @@ -6,10 +6,10 @@ import { createStructureSetupObservers } from '~/setups/structureSetup/structure import type { StructureSetupUpdateHints } from '~/setups/structureSetup/structureSetup.update'; import type { StructureSetupElementsObj } from '~/setups/structureSetup/structureSetup.elements'; import type { TRBL, XY, EventListener } from '~/support'; -import type { Options, ReadonlyOptions } from '~/options'; +import type { PartialOptions, ReadonlyOptions } from '~/options'; import type { Setup } from '~/setups'; import type { InitializationTarget } from '~/initialization'; -import type { DeepPartial, StyleObject, OverflowStyle } from '~/typings'; +import type { StyleObject, OverflowStyle } from '~/typings'; export interface StructureSetupState { _padding: TRBL; @@ -30,7 +30,7 @@ export interface StructureSetupStaticState { } type StructureSetupEventMap = { - u: [updateHints: StructureSetupUpdateHints, changedOptions: DeepPartial, force: boolean]; + u: [updateHints: StructureSetupUpdateHints, changedOptions: PartialOptions, force: boolean]; }; const initialXYNumber = { x: 0, y: 0 }; diff --git a/packages/overlayscrollbars/test/jest-jsdom/overlayscrollbars.test.ts b/packages/overlayscrollbars/test/jest-jsdom/overlayscrollbars.test.ts index ef6c932..2cdee74 100644 --- a/packages/overlayscrollbars/test/jest-jsdom/overlayscrollbars.test.ts +++ b/packages/overlayscrollbars/test/jest-jsdom/overlayscrollbars.test.ts @@ -2,8 +2,7 @@ import { defaultOptions } from '~/options'; import { assignDeep } from '~/support'; import { OptionsValidationPlugin } from '~/plugins'; import { OverlayScrollbars as originalOverlayScrollbars } from '~/overlayscrollbars'; -import type { Options } from '~/options'; -import type { DeepPartial } from '~/typings'; +import type { PartialOptions } from '~/options'; const bodyElm = document.body; const div = document.createElement('div'); @@ -48,7 +47,7 @@ describe('overlayscrollbars', () => { }); test('with custom options', () => { - const customOptions: DeepPartial = { + const customOptions: PartialOptions = { paddingAbsolute: false, showNativeOverlaidScrollbars: true, overflow: { @@ -255,7 +254,7 @@ describe('overlayscrollbars', () => { }); test('initial options', () => { - const customOptions: DeepPartial = { + const customOptions: PartialOptions = { paddingAbsolute: !defaultOptions.paddingAbsolute, overflow: { x: 'hidden' }, }; @@ -265,33 +264,90 @@ describe('overlayscrollbars', () => { }); test('changed options', () => { - const customOptions: DeepPartial = { + const customOptions: PartialOptions = { paddingAbsolute: !defaultOptions.paddingAbsolute, overflow: { x: 'hidden' }, }; + const customOptions2: PartialOptions = { + overflow: { y: 'hidden' }, + }; const osInstance = OverlayScrollbars(div, {}); expect(osInstance.options(customOptions)).toEqual( assignDeep({}, defaultOptions, customOptions) ); expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions)); + expect(osInstance.options(customOptions2)).toEqual( + assignDeep({}, defaultOptions, customOptions, customOptions2) + ); + expect(osInstance.options()).toEqual( + assignDeep({}, defaultOptions, customOptions, customOptions2) + ); + osInstance.destroy(); + }); + + test('changed options pure', () => { + const customOptions: PartialOptions = { + paddingAbsolute: !defaultOptions.paddingAbsolute, + overflow: { x: 'hidden' }, + }; + const customOptions2: PartialOptions = { + overflow: { y: 'hidden' }, + }; + + const osInstance = OverlayScrollbars(div, {}); + expect(osInstance.options(customOptions, true)).toEqual( + assignDeep({}, defaultOptions, customOptions) + ); + expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions)); + expect(osInstance.options(customOptions2, true)).toEqual( + assignDeep({}, defaultOptions, customOptions2) + ); + expect(osInstance.options()).toEqual(assignDeep({}, defaultOptions, customOptions2)); osInstance.destroy(); }); test('unchanged wont trigger update', () => { const updated = jest.fn(); - const initialOpts: DeepPartial = { + const initialOpts: PartialOptions = { paddingAbsolute: true, overflow: { y: 'hidden', }, }; - const osInstance4 = OverlayScrollbars(div, initialOpts, { + const osInstance = OverlayScrollbars(div, initialOpts, { updated, }); expect(updated).toHaveBeenCalledTimes(1); - osInstance4.options(initialOpts); + osInstance.options(initialOpts); + osInstance.options(initialOpts); + osInstance.options({}); + osInstance.options({}); expect(updated).toHaveBeenCalledTimes(1); + osInstance.options(initialOpts, true); + osInstance.options(initialOpts, true); + expect(updated).toHaveBeenCalledTimes(1); + + osInstance.options({}, true); + osInstance.options({}, true); + expect(updated).toHaveBeenCalledTimes(2); + osInstance.options(defaultOptions, true); + osInstance.options(defaultOptions, true); + expect(updated).toHaveBeenCalledTimes(2); + + osInstance.options(initialOpts); + osInstance.options(initialOpts); + expect(updated).toHaveBeenCalledTimes(3); + osInstance.options(defaultOptions); + osInstance.options(defaultOptions); + expect(updated).toHaveBeenCalledTimes(4); + + osInstance.options(initialOpts, true); + osInstance.options(initialOpts, true); + expect(updated).toHaveBeenCalledTimes(5); + osInstance.options(defaultOptions, true); + osInstance.options(defaultOptions, true); + expect(updated).toHaveBeenCalledTimes(6); }); }); });