improve options, environment and structure setup

This commit is contained in:
Rene
2021-04-05 19:06:55 +02:00
parent 6b835c5283
commit 62e4388bc9
19 changed files with 807 additions and 404 deletions
@@ -14,7 +14,10 @@ import {
runEach,
equalBCRWH,
getBoundingClientRect,
assignDeep,
PartialOptions,
} from 'support';
import { OverlayScrollbarsOptions, defaultOptions } from 'options';
import {
classNameEnvironment,
classNameEnvironmentFlexboxGlue,
@@ -22,6 +25,11 @@ import {
classNameViewportScrollbarStyling,
} from 'classnames';
export interface InitializationStrategy {
_padding: boolean;
_content: boolean;
}
export type OnEnvironmentChanged = (env: Environment) => void;
export interface Environment {
_autoUpdateLoop: boolean;
@@ -33,11 +41,23 @@ export interface Environment {
_cssCustomProperties: boolean;
_addListener(listener: OnEnvironmentChanged): void;
_removeListener(listener: OnEnvironmentChanged): void;
_getInitializationStrategy(): InitializationStrategy;
_setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
_getDefaultOptions(): OverlayScrollbarsOptions;
_setDefaultOptions(newDefaultOptions: PartialOptions<OverlayScrollbarsOptions>): void;
_defaultInitializationStrategy: InitializationStrategy;
_defaultDefaultOptions: OverlayScrollbarsOptions;
}
let environmentInstance: Environment;
const { abs, round } = Math;
const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => {
const absValOne = abs(valOne);
const absValTwo = abs(valTwo);
return !(absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo);
};
const getNativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => {
appendChildren(body, measureElm);
const cSize = clientSize(measureElm);
@@ -111,18 +131,16 @@ const getWindowDPR = (): number => {
return window.devicePixelRatio || dDPI / sDPI;
};
const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => {
const absValOne = abs(valOne);
const absValTwo = abs(valTwo);
return !(absValOne === absValTwo || absValOne + 1 === absValTwo || absValOne - 1 === absValTwo);
};
const getDefaultInitializationStrategy = (nativeScrollbarStyling: boolean): InitializationStrategy => ({
_padding: !nativeScrollbarStyling,
_content: false,
});
const createEnvironment = (): Environment => {
const { body } = document;
const envDOM = createDOM(`<div class="${classNameEnvironment}"><div></div></div>`);
const envElm = envDOM[0] as HTMLElement;
const envChildElm = envElm.firstChild as HTMLElement;
const onChangedListener: Set<OnEnvironmentChanged> = new Set();
const nativeScrollbarSize = getNativeScrollbarSize(body, envElm);
const nativeScrollbarStyling = false; //getNativeScrollbarStyling(envElm); //TODO: Re - enable;
@@ -130,6 +148,9 @@ const createEnvironment = (): Environment => {
x: nativeScrollbarSize.x === 0,
y: nativeScrollbarSize.y === 0,
};
const defaultInitializationStrategy = getDefaultInitializationStrategy(nativeScrollbarStyling);
let initializationStrategy = defaultInitializationStrategy;
let defaultDefaultOptions = defaultOptions;
const env: Environment = {
_autoUpdateLoop: false,
@@ -145,6 +166,16 @@ const createEnvironment = (): Environment => {
_removeListener(listener: OnEnvironmentChanged): void {
onChangedListener.delete(listener);
},
_getInitializationStrategy: () => ({ ...initializationStrategy }),
_setInitializationStrategy(newInitializationStrategy) {
initializationStrategy = assignDeep({}, initializationStrategy, newInitializationStrategy);
},
_getDefaultOptions: () => ({ ...defaultDefaultOptions }),
_setDefaultOptions(newDefaultOptions) {
defaultDefaultOptions = assignDeep({}, defaultDefaultOptions, newDefaultOptions);
},
_defaultInitializationStrategy: defaultInitializationStrategy,
_defaultDefaultOptions: defaultDefaultOptions,
};
removeAttr(envElm, 'style');
@@ -1,5 +1,5 @@
import { XY, TRBL, CacheValues, each, push, keys, OptionsValidated, hasOwnProperty, isNumber, scrollLeft, scrollTop } from 'support';
import { Options } from 'options';
import { XY, TRBL, CacheValues, PartialOptions, each, push, keys, hasOwnProperty, isNumber, scrollLeft, scrollTop } from 'support';
import { OverlayScrollbarsOptions } from 'options';
import { getEnvironment } from 'environment';
import { StructureSetup } from 'setups/structureSetup';
import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle';
@@ -41,12 +41,12 @@ export type Lifecycle = (
) => Partial<LifecycleAdaptiveUpdateHints> | void;
export interface LifecycleHubInstance {
_update(changedOptions?: OptionsValidated<Options> | null, force?: boolean): void;
_update(changedOptions?: PartialOptions<OverlayScrollbarsOptions> | null, force?: boolean): void;
_destroy(): void;
}
export interface LifecycleHub {
_options: Options;
_options: OverlayScrollbarsOptions;
_structureSetup: StructureSetup;
// whether the "viewport arrange" strategy must be used (true if no native scrollbar hiding and scrollbars are overlaid)
_doViewportArrange: boolean;
@@ -108,7 +108,7 @@ const heightIntrinsicCacheValuesFallback: CacheValues<boolean> = {
_changed: false,
};
export const createLifecycleHub = (options: Options, structureSetup: StructureSetup): LifecycleHubInstance => {
export const createLifecycleHub = (options: OverlayScrollbarsOptions, structureSetup: StructureSetup): LifecycleHubInstance => {
let paddingInfo = paddingInfoFallback;
let viewportPaddingStyle = viewportPaddingStyleFallback;
let viewportOverflowScroll = viewportOverflowScrollFallback;
@@ -146,7 +146,7 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe
const updateLifecycles = (
updateHints?: Partial<LifecycleUpdateHints> | null,
changedOptions?: OptionsValidated<Options> | null,
changedOptions?: Partial<OverlayScrollbarsOptions> | null,
force?: boolean
) => {
let {
@@ -260,7 +260,7 @@ export const createLifecycleHub = (options: Options, structureSetup: StructureSe
*/
});
const update = (changedOptions?: OptionsValidated<Options> | null, force?: boolean) => {
const update = (changedOptions?: Partial<OverlayScrollbarsOptions> | null, force?: boolean) => {
updateLifecycles(null, changedOptions, force);
};
const envUpdateListener = update.bind(null, null, true);
+25 -25
View File
@@ -24,35 +24,35 @@ export type SizeChangedCallback = (this: any, args?: SizeChangedArgs) => void;
export type UpdatedCallback = (this: any, args?: UpdatedArgs) => void;
export interface Options {
resize?: ResizeBehavior;
paddingAbsolute?: boolean;
updating?: {
elementEvents?: ReadonlyArray<[string, string]> | null;
contentMutationDebounce?: number;
hostMutationDebounce?: number;
resizeDebounce?: number;
export interface OverlayScrollbarsOptions {
resize: ResizeBehavior;
paddingAbsolute: boolean;
updating: {
elementEvents: ReadonlyArray<[string, string]> | null;
contentMutationDebounce: number;
hostMutationDebounce: number;
resizeDebounce: number;
};
overflow?: {
x?: OverflowBehavior;
y?: OverflowBehavior;
overflow: {
x: OverflowBehavior;
y: OverflowBehavior;
};
scrollbars?: {
visibility?: VisibilityBehavior;
autoHide?: AutoHideBehavior;
autoHideDelay?: number;
dragScroll?: boolean;
clickScroll?: boolean;
touch?: boolean;
scrollbars: {
visibility: VisibilityBehavior;
autoHide: AutoHideBehavior;
autoHideDelay: number;
dragScroll: boolean;
clickScroll: boolean;
touch: boolean;
};
textarea?: {
dynWidth?: boolean;
dynHeight?: boolean;
inheritedAttrs?: string | ReadonlyArray<string> | null;
textarea: {
dynWidth: boolean;
dynHeight: boolean;
inheritedAttrs: string | ReadonlyArray<string> | null;
};
nativeScrollbarsOverlaid?: {
show?: boolean;
initialize?: boolean;
nativeScrollbarsOverlaid: {
show: boolean;
initialize: boolean;
};
/*
callbacks?: {
@@ -6,7 +6,7 @@ import {
OptionsWithOptionsTemplate,
Func,
} from 'support/options';
import { ResizeBehavior, OverflowBehavior, VisibilityBehavior, AutoHideBehavior, Options } from 'options';
import { ResizeBehavior, OverflowBehavior, VisibilityBehavior, AutoHideBehavior, OverlayScrollbarsOptions } from 'options';
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
const stringArrayNullAllowedValues: OptionsTemplateValue<string | ReadonlyArray<string> | null> = [oTypes.string, oTypes.array, oTypes.null];
@@ -33,7 +33,8 @@ const scrollbarsAutoHideAllowedValues: OptionsTemplateValue<AutoHideBehavior> =
* Property "a" has a default value of 'default' and it can be a string or null
* Property "b" has a default value of 250 and it can be number
*/
const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<Options>> = {
const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<OverlayScrollbarsOptions> = {
resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v
paddingAbsolute: booleanFalseTemplate, // true || false
updating: {
@@ -1,20 +1,22 @@
import { OSTarget, OSTargetObject } from 'typings';
import { validateOptions, assignDeep, isEmptyObject } from 'support';
import { PartialOptions, validateOptions, assignDeep, isEmptyObject } from 'support';
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
import { createLifecycleHub } from 'lifecycles/lifecycleHub';
import { Options, defaultOptions, optionsTemplate } from 'options';
import { OverlayScrollbarsOptions, optionsTemplate } from 'options';
import { getEnvironment } from 'environment';
const OverlayScrollbars = (target: OSTarget | OSTargetObject, options?: Options, extensions?: any): any => {
const currentOptions: Required<Options> = assignDeep(
const OverlayScrollbars = (target: OSTarget | OSTargetObject, options?: PartialOptions<OverlayScrollbarsOptions>, extensions?: any): any => {
const { _getDefaultOptions } = getEnvironment();
const currentOptions: OverlayScrollbarsOptions = assignDeep(
{},
defaultOptions,
validateOptions<Options>(options || ({} as Options), optionsTemplate, null, true)._validated
_getDefaultOptions(),
validateOptions(options || ({} as PartialOptions<OverlayScrollbarsOptions>), optionsTemplate, null, true)._validated
);
const structureSetup: StructureSetup = createStructureSetup(target);
const lifecycleHub = createLifecycleHub(currentOptions, structureSetup);
const instance = {
options(newOptions?: Options) {
options(newOptions?: PartialOptions<OverlayScrollbarsOptions>) {
if (newOptions) {
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, currentOptions, true);
@@ -14,6 +14,7 @@ import {
runEach,
insertBefore,
attr,
isBoolean,
} from 'support';
import {
classNameHost,
@@ -24,7 +25,7 @@ import {
classNameViewportScrollbarStyling,
} from 'classnames';
import { getEnvironment } from 'environment';
import { OSTarget, OSTargetObject, InternalVersionOf, OSTargetElement } from 'typings';
import { OSTarget, OSTargetObject, OSTargetElement } from 'typings';
export interface OSTargetContext {
_isTextarea: boolean;
@@ -35,9 +36,13 @@ export interface OSTargetContext {
_documentElm: HTMLDocument;
}
export interface PreparedOSTargetObject extends Required<InternalVersionOf<OSTargetObject>> {
export interface PreparedOSTargetObject {
_target: OSTargetElement;
_host: HTMLElement;
_viewportArrange: HTMLStyleElement | null;
_viewport: HTMLElement;
_padding: HTMLElement | false | null;
_content: HTMLElement | false | null;
_viewportArrange: HTMLStyleElement | false | null;
}
export interface StructureSetup {
@@ -46,13 +51,13 @@ export interface StructureSetup {
_destroy: () => void;
}
const unwrap = (elm: HTMLElement | null | undefined) => {
const unwrap = (elm: HTMLElement | false | null | undefined) => {
appendChildren(parent(elm), contents(elm));
removeElements(elm);
};
let contentArrangeCounter = 0;
const createUniqueViewportArrangeElement = () => {
const createUniqueViewportArrangeElement = (): HTMLStyleElement => {
const elm = document.createElement('style');
attr(elm, 'id', `${classNameViewportArrange}-${contentArrangeCounter}`);
@@ -60,26 +65,32 @@ const createUniqueViewportArrangeElement = () => {
return elm;
};
const evaluateCreationFromStrategy = (initializationValue: HTMLElement | boolean | undefined, strategy: boolean): HTMLElement | false | undefined => {
const isBooleanValue = isBoolean(initializationValue);
if (isBooleanValue || isUndefined(initializationValue)) {
return (isBooleanValue ? initializationValue : strategy) && undefined;
}
return initializationValue as HTMLElement;
};
export const createStructureSetup = (target: OSTarget | OSTargetObject): StructureSetup => {
const { _getInitializationStrategy, _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment();
const { _padding: paddingNeeded, _content: contentNeeded } = _getInitializationStrategy();
const targetIsElm = isHTMLElement(target);
const osTargetObj: InternalVersionOf<OSTargetObject> = targetIsElm
? ({} as InternalVersionOf<OSTargetObject>)
const osTargetObj: Partial<PreparedOSTargetObject> = targetIsElm
? ({} as Partial<PreparedOSTargetObject>)
: {
_host: (target as OSTargetObject).host,
_target: (target as OSTargetObject).target,
_padding: (target as OSTargetObject).padding,
_viewport: (target as OSTargetObject).viewport,
_content: (target as OSTargetObject).content,
_padding: evaluateCreationFromStrategy((target as OSTargetObject).padding, paddingNeeded),
_content: evaluateCreationFromStrategy((target as OSTargetObject).content, contentNeeded),
};
if (targetIsElm) {
const padding = createDiv(classNamePadding);
const viewport = createDiv(classNameViewport);
const content = createDiv(classNameContent);
appendChildren(padding, viewport);
appendChildren(viewport, content);
const padding = paddingNeeded && createDiv(classNamePadding);
const content = contentNeeded && createDiv(classNameContent);
osTargetObj._target = target as OSTargetElement;
osTargetObj._padding = padding;
@@ -106,12 +117,15 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu
}
if (targetIsElm) {
appendChildren(_content!, getTargetContents(_target));
const contentSlot = _content || _viewport;
appendChildren(contentSlot, getTargetContents(_target!));
appendChildren(_host, _padding);
appendChildren(_padding || _host, _viewport);
appendChildren(_viewport, _content);
push(destroyFns, () => {
appendChildren(_host, contents(_content));
removeElements(_padding);
appendChildren(_host, contents(contentSlot));
removeElements(_padding || _viewport);
removeClass(_host, classNameHost);
});
} else {
@@ -154,7 +168,7 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu
addClass(_viewport, classNameViewport);
addClass(_content, classNameContent);
const ownerDocument: HTMLDocument = _target.ownerDocument;
const ownerDocument: HTMLDocument = _target!.ownerDocument;
const bodyElm = ownerDocument.body as HTMLBodyElement;
const wnd = ownerDocument.defaultView as Window;
const ctx: OSTargetContext = {
@@ -171,7 +185,6 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu
_host,
};
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid, _cssCustomProperties } = getEnvironment();
if (_nativeScrollbarStyling) {
push(destroyFns, removeClass.bind(0, _viewport, classNameViewportScrollbarStyling));
} else if (!_cssCustomProperties && (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y)) {
@@ -4,7 +4,7 @@ import { keys } from 'support/utils/object';
const rnothtmlwhite = /[^\x20\t\r\n\f]+/g;
const classListAction = (
elm: Element | null | undefined,
elm: Element | false | null | undefined,
className: string,
action: (elmClassList: DOMTokenList, clazz: string) => boolean | void
): boolean => {
@@ -27,7 +27,7 @@ const classListAction = (
* @param elm The element.
* @param className The class name(s).
*/
export const hasClass = (elm: Element | null | undefined, className: string): boolean =>
export const hasClass = (elm: Element | false | null | undefined, className: string): boolean =>
classListAction(elm, className, (classList, clazz) => classList.contains(clazz));
/**
@@ -35,7 +35,7 @@ export const hasClass = (elm: Element | null | undefined, className: string): bo
* @param elm The element.
* @param className The class name(s) which shall be added. (separated by spaces)
*/
export const addClass = (elm: Element | null | undefined, className: string): void => {
export const addClass = (elm: Element | false | null | undefined, className: string): void => {
classListAction(elm, className, (classList, clazz) => classList.add(clazz));
};
@@ -44,7 +44,7 @@ export const addClass = (elm: Element | null | undefined, className: string): vo
* @param elm The element.
* @param className The class name(s) which shall be removed. (separated by spaces)
*/
export const removeClass = (elm: Element | null | undefined, className: string): void => {
export const removeClass = (elm: Element | false | null | undefined, className: string): void => {
classListAction(elm, className, (classList, clazz) => classList.remove(clazz));
};
@@ -2,7 +2,7 @@ import { isArrayLike } from 'support/utils/types';
import { each, from } from 'support/utils/array';
import { parent } from 'support/dom/traversal';
type NodeCollection = ArrayLike<Node> | Node | null | undefined;
type NodeCollection = ArrayLike<Node> | Node | false | null | undefined;
/**
* Inserts Nodes before the given preferredAnchor element.
@@ -10,7 +10,7 @@ type NodeCollection = ArrayLike<Node> | Node | null | undefined;
* @param preferredAnchor The element before which the Nodes shall be inserted or null if the elements shall be appended at the end.
* @param insertedElms The Nodes which shall be inserted.
*/
const before = (parentElm: Node | null | undefined, preferredAnchor: Node | null | undefined, insertedElms: NodeCollection): void => {
const before = (parentElm: Node | false | null | undefined, preferredAnchor: Node | null | undefined, insertedElms: NodeCollection): void => {
if (insertedElms) {
let anchor: Node | null | undefined = preferredAnchor;
let fragment: DocumentFragment | Node | null | undefined;
@@ -1,7 +1,7 @@
import { isElement } from 'support/utils/types';
import { push, from } from 'support/utils/array';
type InputElementType = Element | Node | null | undefined;
type InputElementType = Element | Node | false | null | undefined;
type OutputElementType = Element | null;
const elmPrototype = Element.prototype;
@@ -3,6 +3,8 @@ import { PlainObject } from 'typings';
export * from 'support/options/validation';
export * from 'support/options/transformation';
type ObjectType = Record<string, unknown>;
export type Func = (this: any, ...args: any[]) => any;
export type OptionsTemplateType<T extends OptionsTemplateNativeTypes> = ExtractPropsKey<OptionsTemplateTypeMap, T>;
export type OptionsTemplateTypes = keyof OptionsTemplateTypeMap;
@@ -12,29 +14,26 @@ export type OptionsTemplateValue<T extends OptionsTemplateNativeTypes = string>
? OptionsTemplateValueNonEnum<T>
: string
: OptionsTemplateValueNonEnum<T>;
export type OptionsTemplate<T extends Required<T>> = {
[P in keyof T]: PlainObject extends T[P]
? OptionsTemplate<Required<T[P]>>
: T[P] extends OptionsTemplateNativeTypes
? OptionsTemplateValue<T[P]>
: never;
};
export type OptionsValidated<T> = {
[P in keyof T]?: T[P];
export type OptionsTemplate<T> = {
[P in keyof T]: T[P] extends ObjectType ? OptionsTemplate<T[P]> : T[P] extends OptionsTemplateNativeTypes ? OptionsTemplateValue<T[P]> : never;
};
export type OptionsValidationResult<T> = {
readonly _foreign: PlainObject;
readonly _validated: OptionsValidated<T>;
readonly _validated: PartialOptions<T>;
};
// Options With Options Template Typings:
export type OptionsWithOptionsTemplateValue<T extends OptionsTemplateNativeTypes> = [T, OptionsTemplateValue<T>];
export type OptionsWithOptionsTemplate<T extends Required<T>> = {
[P in keyof T]: PlainObject extends T[P]
[P in keyof T]: T[P] extends ObjectType
? OptionsWithOptionsTemplate<Required<T[P]>>
: T[P] extends OptionsTemplateNativeTypes
? OptionsWithOptionsTemplateValue<T[P]>
: never;
};
export type PartialOptions<T> = {
[P in keyof T]?: T[P] extends ObjectType ? PartialOptions<T[P]> : T[P];
};
type OptionsTemplateTypeMap = {
__TPL_boolean_TYPE__: boolean;
__TPL_number_TYPE__: number;
@@ -1,6 +1,6 @@
import { each, hasOwnProperty, keys, push, isEmptyObject } from 'support/utils';
import { type, isArray, isUndefined, isPlainObject, isString } from 'support/utils/types';
import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, Func, OptionsValidationResult, OptionsValidated } from 'support/options';
import { OptionsTemplate, OptionsTemplateTypes, OptionsTemplateType, PartialOptions, Func, OptionsValidationResult } from 'support/options';
import { PlainObject } from 'typings';
const { stringify } = JSON;
@@ -41,14 +41,14 @@ const optionsTemplateTypes: OptionsTemplateTypesDictionary = ['boolean', 'number
* @param propPath The propertyPath which lead to this object. (used for error logging)
*/
const validateRecursive = <T extends PlainObject>(
options: T,
template: OptionsTemplate<Required<T>>,
options: PartialOptions<T>,
template: OptionsTemplate<T>,
optionsDiff: T,
doWriteErrors?: boolean,
propPath?: string
): OptionsValidationResult<T> => {
const validatedOptions: OptionsValidated<T> = {};
const optionsCopy: T = { ...options };
const validatedOptions: PartialOptions<T> = {};
const optionsCopy: PartialOptions<T> = { ...options };
const props = keys(template).filter((prop) => hasOwnProperty(options, prop));
each(props, (prop: Extract<keyof T, string>) => {
@@ -60,7 +60,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);
const validatedResult = validateRecursive(optionsValue, templateValue as T, optionsDiffValue, doWriteErrors, propPrefix + prop);
validatedOptions[prop] = validatedResult._validated as any;
optionsCopy[prop] = validatedResult._foreign as any;
@@ -147,8 +147,8 @@ const validateRecursive = <T extends PlainObject>(
* @param doWriteErrors True if errors shall be logged into the console, false otherwise.
*/
const validateOptions = <T extends PlainObject>(
options: T,
template: OptionsTemplate<Required<T>>,
options: PartialOptions<T>,
template: OptionsTemplate<T>,
optionsDiff?: T | null,
doWriteErrors?: boolean
): OptionsValidationResult<T> => {
+5 -3
View File
@@ -18,14 +18,16 @@ export type OSTargetElement = HTMLElement | HTMLTextAreaElement;
* If element is provided, the provided element takes all its responsibilities.
* DOM hierarchy isn't checked in this case, its assumed that hieararchy is correct in such a case.
*
* If element is null it won't be generated, and the responsibilities (feautes) of this element are lost.
* Undefined means that the plugin decides whether the respective element needs to be added or can be savely omitted.
* True means that even if the plugin would decide to not generate the element, the element is still generated.
* False means that event if the plugin would decide to generate the element, the element won't be generated.
*/
export interface OSTargetObject {
target: OSTargetElement;
host?: HTMLElement;
padding?: HTMLElement | null;
padding?: HTMLElement | boolean;
viewport?: HTMLElement;
content?: HTMLElement | null;
content?: HTMLElement | boolean;
}
export type OSTarget = OSTargetElement | OSTargetObject;
@@ -4,7 +4,7 @@ import { createDiv, appendChildren, parent, style, on, off, addClass, WH, XY, cl
import { OverlayScrollbars } from 'overlayscrollbars/OverlayScrollbars';
const targetElm = document.querySelector('#target') as HTMLElement;
window.os = OverlayScrollbars({ target: targetElm });
window.os = OverlayScrollbars({ target: targetElm, content: null });
export const resize = (element: HTMLElement) => {
const strMouseTouchDownEvent = 'mousedown touchstart';
@@ -3,6 +3,23 @@ import 'expect-playwright';
import { Environment } from 'environment';
import url from './.build/build.html';
/**
* env test cases:
* 1. overlaid scrollbars
* - with scrollbar styling
* - without scrollbar styling
* - with css custom properties
* - without css custom properties
* 2. partially overlaid, partially normal scrollbars
* - with scrollbar styling
* - without scrollbar styling
* - with css custom properties
* - without css custom properties
* 3. normal scrollbars
* - with scrollbar styling
* - without scrollbar styling
*/
describe('StructureLifecycle', () => {
beforeAll(async () => {
await page.goto(url);
@@ -1,4 +1,19 @@
import { Environment } from 'environment';
import { OSTarget, OSTargetObject } from 'typings';
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
import { isHTMLElement } from 'support';
const mockGetEnvironment = jest.fn();
jest.mock('environment', () => {
return {
getEnvironment: jest.fn().mockImplementation(() => mockGetEnvironment()),
};
});
interface StructureSetupProxy {
input: OSTarget | OSTargetObject;
setup: StructureSetup;
}
const textareaId = 'textarea';
const textareaHostId = 'host';
@@ -69,7 +84,13 @@ const assertCorrectDOMStructure = (textarea?: boolean) => {
}
};
const assertCorrectSetup = (textarea: boolean, setup: StructureSetup) => {
const createStructureSetupProxy = (target: OSTarget | OSTargetObject): StructureSetupProxy => ({
input: target,
setup: createStructureSetup(target),
});
const assertCorrectSetup = (textarea: boolean, setupProxy: StructureSetupProxy, environment: Environment): StructureSetup => {
const { input, setup } = setupProxy;
const { _targetObj, _targetCtx, _destroy } = setup;
const { _target, _host, _padding, _viewport, _content } = _targetObj;
const { target, host, padding, viewport, content } = getElements(textarea);
@@ -113,6 +134,43 @@ const assertCorrectSetup = (textarea: boolean, setup: StructureSetup) => {
expect(typeof _destroy).toBe('function');
const { _nativeScrollbarStyling, _cssCustomProperties, _getInitializationStrategy } = environment;
const { _padding: paddingNeeded, _content: contentNeeded } = _getInitializationStrategy();
const inputIsElement = isHTMLElement(input);
const inputAsObj = input as OSTargetObject;
const styleElm = document.querySelector('style');
const checkStrategyDependendElements = (elm: Element | null, input: HTMLElement | boolean | undefined, strategy: boolean) => {
if (input) {
expect(elm).toBeTruthy();
} else {
if (input === false) {
expect(elm).toBeFalsy();
}
if (input === undefined) {
if (strategy) {
expect(elm).toBeTruthy();
} else {
expect(elm).toBeFalsy();
}
}
}
};
if (_nativeScrollbarStyling || _cssCustomProperties) {
expect(styleElm).toBeFalsy();
} else {
expect(styleElm).toBeTruthy();
}
if (inputIsElement) {
checkStrategyDependendElements(padding, undefined, paddingNeeded);
checkStrategyDependendElements(content, undefined, contentNeeded);
} else {
const { padding: inputPadding, content: inputContent } = inputAsObj;
checkStrategyDependendElements(padding, inputPadding, paddingNeeded);
checkStrategyDependendElements(content, inputContent, contentNeeded);
}
return setup;
};
@@ -133,314 +191,594 @@ const assertCorrectDestroy = (snapshot: string, setup: StructureSetup) => {
expect(snapshot).toBe(getSnapshot());
};
const env: Environment = jest.requireActual('environment').getEnvironment();
const envDefault = {
name: 'default',
env: env,
};
const envNativeScrollbarStyling = {
name: 'native scrollbar styling',
env: {
...env,
_nativeScrollbarStyling: true,
},
};
const envCssCustomProperties = {
name: 'custom css properties',
env: {
...env,
_cssCustomProperties: true,
},
};
const envInitStrategyMin = {
name: 'initialization strategy min',
env: {
...env,
_getInitializationStrategy: () => ({
_content: false,
_padding: false,
}),
},
};
const envInitStrategyMax = {
name: 'initialization strategy max',
env: {
...env,
_getInitializationStrategy: () => ({
_content: true,
_padding: true,
}),
},
};
describe('structureSetup', () => {
afterEach(() => clearBody());
[false, true].forEach((isTextarea) => {
describe(isTextarea ? 'textarea' : 'element', () => {
describe('basic', () => {
test('Element', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(isTextarea, createStructureSetup(getTarget(isTextarea)));
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('Object', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(isTextarea, createStructureSetup({ target: getTarget(isTextarea) }));
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
[envDefault, envNativeScrollbarStyling, envCssCustomProperties, envInitStrategyMin, envInitStrategyMax].forEach((envWithName) => {
const { env: currEnv, name } = envWithName;
describe(`Environment: ${name}`, () => {
beforeAll(() => {
mockGetEnvironment.mockImplementation(() => currEnv);
});
describe('complex', () => {
describe('single assigned', () => {
test('padding', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
[false, true].forEach((isTextarea) => {
describe(isTextarea ? 'textarea' : 'element', () => {
describe('basic', () => {
test('Element', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(isTextarea, createStructureSetupProxy(getTarget(isTextarea)), currEnv);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
test('Object', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(isTextarea, createStructureSetupProxy({ target: getTarget(isTextarea) }), currEnv);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
describe('complex', () => {
describe('single assigned', () => {
test('padding', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
content: document.querySelector<HTMLElement>('#content')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
describe('multiple assigned', () => {
test('padding viewport content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`;
describe('multiple assigned', () => {
test('padding viewport content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="viewport"><div id="content">${content}</div></div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('padding viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
viewport: document.querySelector<HTMLElement>('#viewport')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('padding content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('viewport content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: document.querySelector<HTMLElement>('#content')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('padding viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
describe('single false', () => {
test('padding', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
padding: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
content: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
viewport: document.querySelector<HTMLElement>('#viewport')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('padding content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="content">${content}</div></div></div>`;
describe('single true', () => {
test('padding', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
padding: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
content: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
content: document.querySelector<HTMLElement>('#content')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('viewport content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
describe('multiple false', () => {
test('padding & content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
padding: false,
content: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: document.querySelector<HTMLElement>('#content')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
describe('single null', () => {
test('padding', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
target: getTarget(isTextarea),
padding: null,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
target: getTarget(isTextarea),
content: null,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
describe('multiple null', () => {
test('padding & content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
target: getTarget(isTextarea),
padding: null,
content: null,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
describe('mixed', () => {
test('null: padding & content | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
describe('multiple true', () => {
test('padding & content', () => {
const snapshot = fillBody(isTextarea);
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
target: getTarget(isTextarea),
padding: true,
content: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: null,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: null,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('null: padding | assigned: content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: null,
content: document.querySelector<HTMLElement>('#content')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
describe('mixed', () => {
test('false: padding & content | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: false,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('null: padding | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: null,
viewport: document.querySelector<HTMLElement>('#viewport')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: padding & content | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: true,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('null: padding | assigned: viewport & content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
padding: null,
content: document.querySelector<HTMLElement>('#content')!,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: content | false: padding | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: false,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('null: content | assigned: padding', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
content: null,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: padding | false: content | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: true,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('null: content | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: null,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('false: padding | assigned: content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: false,
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('null: content | assigned: padding & viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
test('true: padding | assigned: content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="content">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: true,
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('false: padding | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: false,
viewport: document.querySelector<HTMLElement>('#viewport')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: padding | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: true,
viewport: document.querySelector<HTMLElement>('#viewport')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('false: padding | assigned: viewport & content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
padding: false,
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: padding | assigned: viewport & content', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport"><div id="content">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
padding: true,
content: document.querySelector<HTMLElement>('#content')!,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('false: content | assigned: padding', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
content: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: content | assigned: padding', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
content: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('false: content | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: content | assigned: viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="viewport">${content}</div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('false: content | assigned: padding & viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: false,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
test('true: content | assigned: padding & viewport', () => {
const snapshot = fillBody(isTextarea, (content, hostId) => {
return `<div id="${hostId}"><div id="padding"><div id="viewport">${content}</div></div></div>`;
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetupProxy({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: true,
}),
currEnv
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
const setup = assertCorrectSetup(
isTextarea,
createStructureSetup({
host: document.querySelector<HTMLElement>('#host')!,
target: getTarget(isTextarea),
padding: document.querySelector<HTMLElement>('#padding')!,
viewport: document.querySelector<HTMLElement>('#viewport')!,
content: null,
})
);
assertCorrectDOMStructure(isTextarea);
assertCorrectDestroy(snapshot, setup);
});
});
});
@@ -4,18 +4,18 @@ import { assignDeep, isEmptyObject } from 'support/utils';
type TestOptionsObj = { propA: 'propA'; null: null };
type TestOptionsEnum = 'A' | 'B' | 'C';
type TestOptions = {
str?: string;
strArrNull?: string | Array<string> | null;
nullbool?: boolean | null;
nested?: {
num?: number;
switch?: boolean;
abc?: TestOptionsEnum;
str: string;
strArrNull: string | Array<string> | null;
nullbool: boolean | null;
nested: {
num: number;
switch: boolean;
abc: TestOptionsEnum;
};
obj?: TestOptionsObj | null;
abc?: TestOptionsEnum;
arr?: Array<any>;
func?: () => void;
obj: TestOptionsObj | null;
abc: TestOptionsEnum;
arr: Array<any>;
func: () => void;
};
const options: TestOptions = {
@@ -33,7 +33,7 @@ const options: TestOptions = {
func: () => {},
};
const template: OptionsTemplate<Required<TestOptions>> = {
const template: OptionsTemplate<TestOptions> = {
str: oTypes.string,
strArrNull: [oTypes.string, oTypes.array, oTypes.null],
nullbool: [oTypes.boolean, oTypes.null],
@@ -385,7 +385,7 @@ describe('options validation', () => {
});
test('nested object is undefined', () => {
const modifiedOptions = assignDeep({}, options);
const modifiedOptions: Partial<TestOptions> = assignDeep({}, options);
modifiedOptions.nested = undefined;
const result = validateOptions(modifiedOptions, template);
const { _validated } = result;
@@ -399,7 +399,7 @@ describe('options validation', () => {
const { warn } = console;
console.warn = jest.fn();
validateOptions(options, template, {}, true);
validateOptions(options, template, null, true);
expect(console.warn).not.toBeCalled();
console.warn = warn;
@@ -410,7 +410,7 @@ describe('options validation', () => {
console.warn = jest.fn();
const modifiedOptions = assignDeep({}, options, { str: 1 });
validateOptions(modifiedOptions, template, {}, false);
validateOptions(modifiedOptions, template, null, false);
expect(console.warn).not.toBeCalled();
console.warn = warn;
@@ -421,15 +421,15 @@ describe('options validation', () => {
console.warn = jest.fn();
// str must be string
validateOptions(assignDeep({}, options, { str: 1 }), template, {}, true);
validateOptions(assignDeep({}, options, { str: 1 }), template, null, true);
expect(console.warn).toBeCalledTimes(1);
// abc must be A | B | C
validateOptions(assignDeep({}, options, { abc: 'some string' }), template, {}, true);
validateOptions(assignDeep({}, options, { abc: 'some string' }), template, null, true);
expect(console.warn).toBeCalledTimes(2);
// everthing OK
validateOptions(assignDeep({}, options, { abc: 'C' }), template, {}, true);
validateOptions(assignDeep({}, options, { abc: 'C' }), template, null, true);
expect(console.warn).toBeCalledTimes(2);
console.warn = warn;
@@ -1,5 +1,5 @@
import { XY, TRBL, CacheValues, OptionsValidated } from 'support';
import { Options } from 'options';
import { XY, TRBL, CacheValues } from 'support';
import { OverlayScrollbarsOptions } from 'options';
import { StructureSetup } from 'setups/structureSetup';
import { StyleObject } from 'typings';
export declare type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
@@ -23,11 +23,11 @@ export interface LifecycleUpdateHints extends LifecycleAdaptiveUpdateHints {
}
export declare type Lifecycle = (updateHints: LifecycleUpdateHints, checkOption: LifecycleCheckOption, force: boolean) => Partial<LifecycleAdaptiveUpdateHints> | void;
export interface LifecycleHubInstance {
_update(changedOptions?: OptionsValidated<Options> | null, force?: boolean): void;
_update(changedOptions?: Partial<OverlayScrollbarsOptions> | null, force?: boolean): void;
_destroy(): void;
}
export interface LifecycleHub {
_options: Options;
_options: OverlayScrollbarsOptions;
_structureSetup: StructureSetup;
_doViewportArrange: boolean;
_getPaddingInfo(): PaddingInfo;
@@ -37,4 +37,4 @@ export interface LifecycleHub {
_getViewportOverflowScroll(): XY<boolean>;
_setViewportOverflowScroll(newViewportOverflowScroll: XY<boolean>): void;
}
export declare const createLifecycleHub: (options: Options, structureSetup: StructureSetup) => LifecycleHubInstance;
export declare const createLifecycleHub: (options: OverlayScrollbarsOptions, structureSetup: StructureSetup) => LifecycleHubInstance;
+2 -2
View File
@@ -1,2 +1,2 @@
import { Options } from 'options';
export declare const optionsTemplate: import("../support/options").OptionsTemplate<Required<Options>>, defaultOptions: Required<Options>;
import { OverlayScrollbarsOptions } from 'options';
export declare const optionsTemplate: import("../support/options").OptionsTemplate<Required<OverlayScrollbarsOptions>>, defaultOptions: Required<OverlayScrollbarsOptions>;
@@ -1,4 +1,4 @@
import { OSTarget, OSTargetObject } from 'typings';
import { Options } from 'options';
declare const OverlayScrollbars: (target: OSTarget | OSTargetObject, options?: Options | undefined, extensions?: any) => any;
import { OverlayScrollbarsOptions } from 'options';
declare const OverlayScrollbars: (target: OSTarget | OSTargetObject, options?: OverlayScrollbarsOptions | undefined, extensions?: any) => any;
export { OverlayScrollbars };