improve option types and add optional pure parameter to options function

This commit is contained in:
Rene Haas
2022-10-26 18:59:08 +02:00
parent 0c3d8a4b1b
commit 7c5271e1ed
10 changed files with 104 additions and 39 deletions
@@ -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>): 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>): Options;
setDefaultOptions(newDefaultOptions: PartialOptions): Options;
}
export interface InternalEnvironment {
@@ -89,9 +88,9 @@ export interface InternalEnvironment {
readonly _staticDefaultOptions: Options;
_addListener(listener: EventListener<EnvironmentEventMap, '_'>): () => void;
_getDefaultInitialization(): Initialization;
_setDefaultInitialization(newInitialization: DeepPartial<Initialization>): Initialization;
_setDefaultInitialization(newInitialization: PartialInitialization): Initialization;
_getDefaultOptions(): Options;
_setDefaultOptions(newDefaultOptions: DeepPartial<Options>): Options;
_setDefaultOptions(newDefaultOptions: PartialOptions): Options;
}
let environmentInstance: InternalEnvironment;
@@ -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<Options>;
changedOptions: PartialOptions;
/** Whether the update happened with and force invalidated cache. */
force: boolean;
}
+3
View File
@@ -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,
@@ -85,6 +85,8 @@ export type Initialization = {
};
};
export type PartialInitialization = DeepPartial<Initialization>;
/** 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<Initialization> & {
export type InitializationTargetObject = PartialInitialization & {
target: InitializationTargetElement;
};
@@ -134,6 +134,8 @@ export interface Options {
export type ReadonlyOptions = DeepReadonly<Options>;
export type PartialOptions = DeepPartial<Options>;
export const defaultOptions: Options = {
paddingAbsolute: false,
showNativeOverlaidScrollbars: false,
@@ -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>,
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;
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>,
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<Options>) => {
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<Options>, 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>) {
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);
@@ -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<number> = oTypes.number;
@@ -58,7 +58,7 @@ const optionsTemplate: OptionsTemplate<Options> = {
};
export type OptionsValidationPluginInstance = {
_: (options: DeepPartial<Options>, doWriteErrors?: boolean) => DeepPartial<Options>;
_: (options: PartialOptions, doWriteErrors?: boolean) => PartialOptions;
};
export const optionsValidationPluginName = '__osOptionsValidationPlugin';
@@ -66,7 +66,7 @@ export const optionsValidationPluginName = '__osOptionsValidationPlugin';
export const OptionsValidationPlugin: Plugin<OptionsValidationPluginInstance> =
/* @__PURE__ */ (() => ({
[optionsValidationPluginName]: {
_: (options: DeepPartial<Options>, doWriteErrors?: boolean) => {
_: (options: PartialOptions, doWriteErrors?: boolean) => {
const [validated, foreign] = validateOptions(optionsTemplate, options, doWriteErrors);
return { ...foreign, ...validated };
},
@@ -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<T extends Record<string, any>> = [elements: T, destroy: () => void];
export type SetupUpdate<Args extends any[], R> = (
changedOptions: DeepPartial<Options>,
changedOptions: PartialOptions,
force: boolean,
...args: Args
) => R;
@@ -38,7 +37,7 @@ const getPropByPath = <T>(obj: any, path: string): T =>
export const createOptionCheck =
(
options: ReadonlyOptions,
changedOptions: DeepPartial<Options>,
changedOptions: PartialOptions,
force?: boolean
): SetupUpdateCheckOption =>
(path: string) =>
@@ -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<Options>, force: boolean];
u: [updateHints: StructureSetupUpdateHints, changedOptions: PartialOptions, force: boolean];
};
const initialXYNumber = { x: 0, y: 0 };
@@ -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<Options> = {
const customOptions: PartialOptions = {
paddingAbsolute: false,
showNativeOverlaidScrollbars: true,
overflow: {
@@ -255,7 +254,7 @@ describe('overlayscrollbars', () => {
});
test('initial options', () => {
const customOptions: DeepPartial<Options> = {
const customOptions: PartialOptions = {
paddingAbsolute: !defaultOptions.paddingAbsolute,
overflow: { x: 'hidden' },
};
@@ -265,33 +264,90 @@ describe('overlayscrollbars', () => {
});
test('changed options', () => {
const customOptions: DeepPartial<Options> = {
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<Options> = {
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);
});
});
});