mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-25 07:34:07 +03:00
improve option types and add optional pure parameter to options function
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+3
-3
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user