improve initialization and types

This commit is contained in:
Rene
2022-07-11 11:20:58 +02:00
parent e38421d78a
commit 1d9a2cf817
31 changed files with 4502 additions and 4754 deletions
+26 -6
View File
@@ -27,14 +27,10 @@ const createOutputWithMinifiedVersion = (output, esm, buildMinifiedVersion) =>
rollupTerser({
ecma: esm ? 2015 : 5,
safari10: true,
mangle: {
safari10: true,
properties: {
regex: /^_/,
},
},
compress: {
evaluate: false,
module: !!esm,
passes: 3,
},
}),
],
@@ -63,6 +59,30 @@ module.exports = (esm, options, { declarationFiles = false, outputStyle = false
format: esm ? 'esm' : 'umd',
generatedCode: esm ? 'es2015' : 'es5',
file: path.resolve(distPath, `${file}${esm ? '.esm' : ''}.js`),
plugins: [
rollupTerser({
ecma: esm ? 2015 : 5,
safari10: true,
mangle: {
safari10: true,
keep_fnames: true, // eslint-disable-line camelcase
properties: {
regex: /^_/,
},
},
compress: {
defaults: false,
hoist_funs: true, // eslint-disable-line camelcase
},
format: {
beautify: true,
max_line_len: 80, // eslint-disable-line camelcase
braces: true,
indent_level: 2, // eslint-disable-line camelcase
},
}),
],
},
esm,
buildMinifiedVersion
+3 -3
View File
@@ -55,11 +55,11 @@
"rollup-plugin-scss": "^3.0.0",
"rollup-plugin-serve": "^1.1.0",
"rollup-plugin-styles": "^3.10.0",
"rollup-plugin-terser": "^6.1.0",
"rollup-plugin-ts": "^3.0.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-ts": "^3.0.2",
"should": "^13.2.3",
"tslib": "^2.4.0",
"typescript": "^4.7.3",
"typescript": "^4.7.4",
"utf-8-validate": "^5.0.2"
},
"scripts": {
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16 -71
View File
@@ -27,81 +27,31 @@ import {
classNameEnvironmentFlexboxGlueMax,
classNameViewportScrollbarStyling,
} from 'classnames';
import { OSOptions, defaultOptions } from 'options';
import { OSTargetElement, PartialOptions } from 'typings';
type StructureInitializationStrategyElementFn<T> =
| ((target: OSTargetElement) => HTMLElement | T)
| T;
type ScrollbarsInitializationStrategyElementFn<T> =
| ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => HTMLElement | T)
| T;
/**
* A Static element is an element which MUST be generated.
* If null or undefined (or the returned result is null or undefined), the initialization function is generatig the element, otherwise
* the element returned by the function acts as the generated element.
*/
export type StructureInitializationStrategyStaticElement = StructureInitializationStrategyElementFn<
null | undefined
>;
/**
* A Dynamic element is an element which CAN be generated.
* If boolean (or the returned result is boolean), the generation of the element is forced (or not).
* If the function returns and element, the element returned by the function acts as the generated element.
*/
export type StructureInitializationStrategyDynamicElement =
StructureInitializationStrategyElementFn<boolean>;
export interface StructureInitializationStrategy {
_host: StructureInitializationStrategyStaticElement;
_viewport: StructureInitializationStrategyStaticElement;
_padding: StructureInitializationStrategyDynamicElement;
_content: StructureInitializationStrategyDynamicElement;
}
export interface ScrollbarsInitializationStrategy {
/**
* The scrollbars slot. If null or undefined (or the returned result is null or undefined), the initialization function is deciding the element, otherwise
* the element returned by the function acts as the scrollbars slot.
*/
_scrollbarsSlot: ScrollbarsInitializationStrategyElementFn<null | undefined>;
}
export interface InitializationStrategy
extends StructureInitializationStrategy,
ScrollbarsInitializationStrategy {}
export type DefaultInitializationStrategy = {
[K in keyof InitializationStrategy]: Extract<
InitializationStrategy[K],
boolean | null | undefined
>;
};
import { Options, defaultOptions } from 'options';
import { PartialOptions } from 'typings';
import { InitializationStrategy } from 'initialization';
export interface EnvironmentListenersNameArgsMap {
_: undefined;
}
export interface Environment {
export interface InternalEnvironment {
readonly _nativeScrollbarSize: XY;
readonly _nativeScrollbarIsOverlaid: XY<boolean>;
readonly _nativeScrollbarStyling: boolean;
readonly _rtlScrollBehavior: { n: boolean; i: boolean };
readonly _flexboxGlue: boolean;
readonly _cssCustomProperties: boolean;
readonly _defaultInitializationStrategy: DefaultInitializationStrategy;
readonly _defaultDefaultOptions: OSOptions;
readonly _defaultInitializationStrategy: InitializationStrategy;
readonly _defaultDefaultOptions: Options;
_addListener(listener: EventListener<EnvironmentListenersNameArgsMap, '_'>): () => void;
_getInitializationStrategy(): InitializationStrategy;
_setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
_getDefaultOptions(): OSOptions;
_setDefaultOptions(newDefaultOptions: PartialOptions<OSOptions>): void;
_getDefaultOptions(): Options;
_setDefaultOptions(newDefaultOptions: PartialOptions<Options>): void;
}
let environmentInstance: Environment;
let environmentInstance: InternalEnvironment;
const { abs, round } = Math;
const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => {
@@ -199,15 +149,12 @@ const getWindowDPR = (): number => {
const getDefaultInitializationStrategy = (
nativeScrollbarStyling: boolean
): DefaultInitializationStrategy => ({
_host: null,
_viewport: null,
): InitializationStrategy => ({
_padding: !nativeScrollbarStyling,
_content: false,
_scrollbarsSlot: null,
});
const createEnvironment = (): Environment => {
const createEnvironment = (): InternalEnvironment => {
const { body } = document;
const envDOM = createDOM(`<div class="${classNameEnvironment}"><div></div></div>`);
const envElm = envDOM[0] as HTMLElement;
@@ -226,7 +173,7 @@ const createEnvironment = (): Environment => {
const initializationStrategy = getDefaultInitializationStrategy(nativeScrollbarStyling);
const defaultDefaultOptions = assignDeep({}, defaultOptions);
const env: Environment = {
const env: InternalEnvironment = {
_nativeScrollbarSize: nativeScrollbarSize,
_nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid,
_nativeScrollbarStyling: nativeScrollbarStyling,
@@ -242,11 +189,7 @@ const createEnvironment = (): Environment => {
_setInitializationStrategy(newInitializationStrategy) {
assignDeep(initializationStrategy, newInitializationStrategy);
},
_getDefaultOptions: assignDeep<OSOptions, OSOptions>.bind(
0,
{} as OSOptions,
defaultDefaultOptions
),
_getDefaultOptions: assignDeep<Options, Options>.bind(0, {} as Options, defaultDefaultOptions),
_setDefaultOptions(newDefaultOptions) {
assignDeep(defaultDefaultOptions, newDefaultOptions);
},
@@ -305,9 +248,11 @@ const createEnvironment = (): Environment => {
return env;
};
export const getEnvironment = (): Environment => {
const getEnvironment = (): InternalEnvironment => {
if (!environmentInstance) {
environmentInstance = createEnvironment();
}
return environmentInstance;
};
export { getEnvironment };
@@ -1,59 +0,0 @@
import { OSOptions } from 'options';
import { createEventListenerHub } from 'support';
import { PartialOptions } from 'typings';
import type {
InitialEventListeners,
AddEventListener,
RemoveEventListener,
TriggerEventListener,
EventListener,
} from 'support/eventListeners';
/*
onScrollStart : null,
onScroll : null,
onScrollStop : null,
onOverflowChanged : null,
onOverflowAmountChanged : null, // fusion with onOverflowChanged
onDirectionChanged : null, // gone
onContentSizeChanged : null, // gone
onHostSizeChanged : null, // gone
*/
export interface OnUpdatedEventListenerArgs {
updateHints: {
sizeChanged: boolean;
directionChanged: boolean;
heightIntrinsicChanged: boolean;
overflowAmountChanged: boolean;
overflowStyleChanged: boolean;
hostMutation: boolean;
contentMutation: boolean;
};
changedOptions: PartialOptions<OSOptions>;
force: boolean;
}
export interface OSEventListenersNameArgsMap {
initialized: undefined;
initializationWithdrawn: undefined;
updated: OnUpdatedEventListenerArgs;
destroyed: undefined;
}
export type OSEventListener<
N extends Extract<keyof OSEventListenersNameArgsMap, string> = Extract<
keyof OSEventListenersNameArgsMap,
string
>
> = EventListener<OSEventListenersNameArgsMap, N>;
export type AddOSEventListener = AddEventListener<OSEventListenersNameArgsMap>;
export type RemoveOSEventListener = RemoveEventListener<OSEventListenersNameArgsMap>;
export type TriggerOSEventListener = TriggerEventListener<OSEventListenersNameArgsMap>;
export type InitialOSEventListeners = InitialEventListeners<OSEventListenersNameArgsMap>;
export const createOSEventListenerHub = (initialEventListeners?: InitialOSEventListeners) =>
createEventListenerHub(initialEventListeners);
@@ -0,0 +1,87 @@
import { isFunction, isBoolean, isNull, isUndefined } from 'support';
import type {
StructureInitialization,
StructureInitializationStrategy,
} from 'setups/structureSetup';
import type {
ScrollbarsInitialization,
ScrollbarsInitializationStrategy,
} from 'setups/scrollbarsSetup';
type StaticInitialization = HTMLElement | null | undefined;
type DynamicInitialization = HTMLElement | boolean | null | undefined;
export type InitializationTargetElement = HTMLElement | HTMLTextAreaElement;
export type InitializationTargetObject = StructureInitialization & ScrollbarsInitialization;
export type InitializationTarget = InitializationTargetElement | InitializationTargetObject;
export type InitializationStrategy = StructureInitializationStrategy &
ScrollbarsInitializationStrategy;
/**
* Static elements MUST be present.
* Null or undefined behave like if this element wasn't specified during initialization.
*/
export type StaticInitializationElement<Args extends any[]> =
| ((...args: Args) => StaticInitialization)
| StaticInitialization;
/**
* Dynamic element CAN be present.
* If its a element the element will be handled as the repsective element.
* True means that the respective dynamic element is forced to be generated.
* False means that the respective dynamic element is forced NOT to be generated.
* Null or undefined behave like if this element wasn't specified during initialization.
*/
export type DynamicInitializationElement<Args extends any[]> =
| ((...args: Args) => DynamicInitialization)
| DynamicInitialization;
export type InitializtationElementStrategy<InitElm> = Exclude<InitElm, HTMLElement>;
export type DefaultInitializtationElementStrategy<
InitElm extends StaticInitializationElement<any> | DynamicInitializationElement<any>
> = Extract<InitElm, (...args: any[]) => any> extends (...args: infer P) => any
? (...args: P) => HTMLElement
: never;
const staticInitializationElement = <T extends StaticInitializationElement<any>>(
args: Parameters<Extract<T, (...args: any[]) => any>>,
defaultStaticInitializationElement: DefaultInitializtationElementStrategy<T>,
staticInitializationElementStrategy?: InitializtationElementStrategy<T>,
staticInitializationElementValue?: T | false
): HTMLElement => {
const result =
staticInitializationElementValue ||
(isFunction(staticInitializationElementStrategy)
? staticInitializationElementStrategy.apply(0, args)
: staticInitializationElementStrategy);
return (
(isFunction(result) ? result.apply(0, args) : result) ||
defaultStaticInitializationElement.apply(0, args)
);
};
const dynamicInitializationElement = <T extends DynamicInitializationElement<any>>(
args: Parameters<Extract<T, (...args: any[]) => any>>,
defaultDynamicInitializationElement: DefaultInitializtationElementStrategy<T>,
dynamicInitializationElementStrategy?: InitializtationElementStrategy<T>,
dynamicInitializationElementValue?: T | false
): HTMLElement | false => {
const takeInitializationValue =
isBoolean(dynamicInitializationElementValue) || !!dynamicInitializationElementValue;
const result = takeInitializationValue
? (dynamicInitializationElementValue as boolean | HTMLElement)
: isFunction(dynamicInitializationElementStrategy)
? dynamicInitializationElementStrategy.apply(0, args)
: dynamicInitializationElementStrategy;
return result === true || isNull(result) || isUndefined(result) || isFunction(result)
? defaultDynamicInitializationElement.apply(0, args)
: result;
};
export { staticInitializationElement, dynamicInitializationElement };
+5 -5
View File
@@ -1,7 +1,7 @@
import { assignDeep, each, isObject, keys, isArray, hasOwnProperty, isFunction } from 'support';
import { PartialOptions, ReadonlyOptions } from 'typings';
const stringify = (value: any) =>
const opsStringify = (value: any) =>
JSON.stringify(value, (_, val) => {
if (isFunction(val)) {
throw new Error();
@@ -38,7 +38,7 @@ export type SizeChangedCallback = (this: any, args?: SizeChangedArgs) => void;
export type UpdatedCallback = (this: any, args?: UpdatedArgs) => void;
export interface OSOptions {
export interface Options {
paddingAbsolute: boolean;
updating: {
elementEvents: Array<[elementSelector: string, eventNames: string]> | null;
@@ -64,7 +64,7 @@ export interface OSOptions {
};
}
export type ReadonlyOSOptions = ReadonlyOptions<OSOptions>;
export type ReadonlyOSOptions = ReadonlyOptions<Options>;
export interface OverflowChangedArgs {
x: boolean;
@@ -93,7 +93,7 @@ export interface UpdatedArgs {
forced: boolean;
}
export const defaultOptions: OSOptions = {
export const defaultOptions: Options = {
// resize: 'none', // none || both || horizontal || vertical || n || b || h || v
paddingAbsolute: false, // true || false
updating: {
@@ -145,7 +145,7 @@ export const getOptionsDiff = <T>(
if (isArray(currOptionValue) || isArray(newOptionValue)) {
try {
if (stringify(currOptionValue) === stringify(newOptionValue)) {
if (opsStringify(currOptionValue) === opsStringify(newOptionValue)) {
isDiff = false;
}
} catch {}
@@ -1,4 +1,3 @@
import { OSTarget, OSInitializationObject, PartialOptions, OverflowStyle } from 'typings';
import {
assignDeep,
isEmptyObject,
@@ -8,10 +7,11 @@ import {
isHTMLElement,
XY,
TRBL,
createEventListenerHub,
} from 'support';
import { createStructureSetup, createScrollbarsSetup } from 'setups';
import { getOptionsDiff, OSOptions, ReadonlyOSOptions } from 'options';
import { DefaultInitializationStrategy, getEnvironment, InitializationStrategy } from 'environment';
import { getOptionsDiff, Options, ReadonlyOSOptions } from 'options';
import { getEnvironment } from 'environment';
import {
getPlugins,
addPlugin,
@@ -20,41 +20,56 @@ import {
OptionsValidationPluginInstance,
} from 'plugins';
import { addInstance, getInstance, removeInstance } from 'instances';
import {
createOSEventListenerHub,
InitialOSEventListeners,
AddOSEventListener,
RemoveOSEventListener,
} from 'eventListeners';
import type { PartialOptions, OverflowStyle } from 'typings';
import type {
InitializationTarget,
InitializationTargetObject,
InitializationStrategy,
} from 'initialization';
import type {
InitialEventListeners as GeneralInitialEventListeners,
EventListener as GeneralEventListener,
} from 'support/eventListeners';
/*
onScrollStart : null,
onScroll : null,
onScrollStop : null,
onOverflowChanged : null,
onOverflowAmountChanged : null, // fusion with onOverflowChanged
onDirectionChanged : null, // gone
onContentSizeChanged : null, // gone
onHostSizeChanged : null, // gone
*/
export interface OverlayScrollbarsStatic {
(
target: OSTarget | OSInitializationObject,
options?: PartialOptions<OSOptions>,
eventListeners?: InitialOSEventListeners
target: InitializationTarget | InitializationTargetObject,
options?: PartialOptions<Options>,
eventListeners?: GeneralInitialEventListeners<EventListenerMap>
): OverlayScrollbars;
plugin(osPlugin: OSPlugin | OSPlugin[]): void;
env(): OverlayScrollbarsEnv;
env(): Environment;
}
export interface OverlayScrollbarsEnv {
export interface Environment {
scrollbarSize: XY<number>;
scrollbarIsOverlaid: XY<boolean>;
scrollbarStyling: boolean;
rtlScrollBehavior: { n: boolean; i: boolean };
flexboxGlue: boolean;
cssCustomProperties: boolean;
defaultInitializationStrategy: DefaultInitializationStrategy;
defaultDefaultOptions: OSOptions;
defaultInitializationStrategy: InitializationStrategy;
defaultDefaultOptions: Options;
getInitializationStrategy(): InitializationStrategy;
setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
getDefaultOptions(): OSOptions;
setDefaultOptions(newDefaultOptions: PartialOptions<OSOptions>): void;
getDefaultOptions(): Options;
setDefaultOptions(newDefaultOptions: PartialOptions<Options>): void;
}
export interface OverlayScrollbarsState {
export interface State {
padding: TRBL;
paddingAbsolute: boolean;
overflowAmount: XY<number>;
@@ -62,7 +77,7 @@ export interface OverlayScrollbarsState {
hasOverflow: XY<boolean>;
}
export interface OverlayScrollbarsElements {
export interface Elements {
target: HTMLElement;
host: HTMLElement;
padding: HTMLElement;
@@ -70,18 +85,51 @@ export interface OverlayScrollbarsElements {
content: HTMLElement;
}
export interface OnUpdatedEventListenerArgs {
updateHints: {
sizeChanged: boolean;
directionChanged: boolean;
heightIntrinsicChanged: boolean;
overflowAmountChanged: boolean;
overflowStyleChanged: boolean;
hostMutation: boolean;
contentMutation: boolean;
};
changedOptions: PartialOptions<Options>;
force: boolean;
}
export interface EventListenerMap {
initialized: undefined;
initializationWithdrawn: undefined;
updated: OnUpdatedEventListenerArgs;
destroyed: undefined;
}
export type InitialEventListeners = GeneralInitialEventListeners<EventListenerMap>;
export type EventListener<Name extends keyof EventListenerMap> = GeneralEventListener<
EventListenerMap,
Name
>;
export interface OverlayScrollbars {
options(): OSOptions;
options(newOptions?: PartialOptions<OSOptions>): OSOptions;
options(): Options;
options(newOptions?: PartialOptions<Options>): Options;
update(force?: boolean): void;
destroy(): void;
state(): OverlayScrollbarsState;
elements(): OverlayScrollbarsElements;
state(): State;
on: AddOSEventListener;
off: RemoveOSEventListener;
elements(): Elements;
on<Name extends keyof EventListenerMap>(name: Name, listener: EventListener<Name>): () => void;
on<Name extends keyof EventListenerMap>(name: Name, listener: EventListener<Name>[]): () => void;
off<Name extends keyof EventListenerMap>(name: Name, listener?: EventListener<Name>): void;
off<Name extends keyof EventListenerMap>(name: Name, listener?: EventListener<Name>[]): void;
}
/**
@@ -109,7 +157,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
const optionsValidationPlugin = plugins[
optionsValidationPluginName
] as OptionsValidationPluginInstance;
const validateOptions = (newOptions?: PartialOptions<OSOptions>) => {
const validateOptions = (newOptions?: PartialOptions<Options>) => {
const opts = newOptions || {};
const validate = optionsValidationPlugin && optionsValidationPlugin._;
return validate ? validate(opts, true) : opts;
@@ -119,7 +167,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
_getDefaultOptions(),
validateOptions(options)
);
const [addEvent, removeEvent, triggerEvent] = createOSEventListenerHub(eventListeners);
const [addEvent, removeEvent, triggerEvent] = createEventListenerHub(eventListeners);
if (
_nativeScrollbarIsOverlaid.x &&
@@ -139,7 +187,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
structureState._elements
);
const update = (changedOptions: PartialOptions<OSOptions>, force?: boolean) => {
const update = (changedOptions: PartialOptions<Options>, force?: boolean) => {
updateStructure(changedOptions, force);
updateScrollbars(changedOptions, force);
};
@@ -173,7 +221,7 @@ export const OverlayScrollbars: OverlayScrollbarsStatic = (
});
const instance: OverlayScrollbars = {
options(newOptions?: PartialOptions<OSOptions>) {
options(newOptions?: PartialOptions<Options>) {
if (newOptions) {
const changedOptions = getOptionsDiff(currentOptions, validateOptions(newOptions));
@@ -1,5 +1,5 @@
import { OSPlugin } from 'plugins';
import { OSOptions, OverflowBehavior, VisibilityBehavior, AutoHideBehavior } from 'options';
import { Options, OverflowBehavior, VisibilityBehavior, AutoHideBehavior } from 'options';
import {
validateOptions,
OptionsTemplate,
@@ -18,7 +18,7 @@ const scrollbarsVisibilityAllowedValues: OptionsTemplateValue<VisibilityBehavior
const scrollbarsAutoHideAllowedValues: OptionsTemplateValue<AutoHideBehavior> =
'never scroll leavemove';
const optionsTemplate: OptionsTemplate<OSOptions> = {
const optionsTemplate: OptionsTemplate<Options> = {
// resize: resizeAllowedValues, // none || both || horizontal || vertical || n || b ||
paddingAbsolute: booleanAllowedValues, // true || false
updating: {
@@ -53,7 +53,7 @@ const optionsTemplate: OptionsTemplate<OSOptions> = {
};
export type OptionsValidationPluginInstance = {
_: (options: PartialOptions<OSOptions>, doWriteErrors?: boolean) => PartialOptions<OSOptions>;
_: (options: PartialOptions<Options>, doWriteErrors?: boolean) => PartialOptions<Options>;
};
export const optionsValidationPluginName = '__osOptionsValidationPlugin';
@@ -61,7 +61,7 @@ export const optionsValidationPluginName = '__osOptionsValidationPlugin';
export const optionsValidationPlugin: OSPlugin<OptionsValidationPluginInstance> = [
optionsValidationPluginName,
{
_: (options: PartialOptions<OSOptions>, doWriteErrors?: boolean) => {
_: (options: PartialOptions<Options>, doWriteErrors?: boolean) => {
const [validated, foreign] = validateOptions(optionsTemplate, options, doWriteErrors);
return { ...foreign, ...validated };
},
@@ -1 +1,2 @@
export * from 'setups/scrollbarsSetup/scrollbarsSetup';
export * from 'setups/scrollbarsSetup/scrollbarsSetup.initialization';
@@ -1,4 +1,4 @@
import { appendChildren, createDiv, removeElements, isFunction } from 'support';
import { appendChildren, createDiv, removeElements } from 'support';
import {
classNameScrollbar,
classNameScrollbarHorizontal,
@@ -6,9 +6,15 @@ import {
classNameScrollbarTrack,
classNameScrollbarHandle,
} from 'classnames';
import { getEnvironment, ScrollbarsInitializationStrategy } from 'environment';
import { OSTarget, ScrollbarsInitialization } from 'typings';
import { getEnvironment } from 'environment';
import { dynamicInitializationElement as generalDynamicInitializationElement } from 'initialization';
import type { InitializationTarget } from 'initialization';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type {
ScrollbarsInitialization,
ScrollbarsInitializationStrategy,
ScrollbarsDynamicInitializationElement,
} from 'setups/scrollbarsSetup/scrollbarsSetup.initialization';
export interface ScrollbarStructure {
_scrollbar: HTMLElement;
@@ -39,7 +45,7 @@ const generateScrollbarDOM = (scrollbarClassName: string): ScrollbarStructure =>
};
export const createScrollbarsSetupElements = (
target: OSTarget,
target: InitializationTarget,
structureSetupElements: StructureSetupElementsObj
): ScrollbarsSetupElements => {
const { _getInitializationStrategy } = getEnvironment();
@@ -48,15 +54,13 @@ export const createScrollbarsSetupElements = (
const { _target, _host, _viewport, _targetIsElm } = structureSetupElements;
const initializationScrollbarSlot =
!_targetIsElm && (target as ScrollbarsInitialization).scrollbarsSlot;
const initializationScrollbarSlotResult = isFunction(initializationScrollbarSlot)
? initializationScrollbarSlot(_target, _host, _viewport)
: initializationScrollbarSlot;
const evaluatedScrollbarSlot =
initializationScrollbarSlotResult ||
(isFunction(environmentScrollbarSlot)
? environmentScrollbarSlot(_target, _host, _viewport)
: environmentScrollbarSlot) ||
_host;
generalDynamicInitializationElement<ScrollbarsDynamicInitializationElement>(
[_target, _host, _viewport],
() => _host,
environmentScrollbarSlot,
initializationScrollbarSlot
);
const horizontalScrollbarStructure = generateScrollbarDOM(classNameScrollbarHorizontal);
const verticalScrollbarStructure = generateScrollbarDOM(classNameScrollbarVertical);
@@ -0,0 +1,27 @@
import type {
InitializationTargetElement,
InitializtationElementStrategy,
DynamicInitializationElement,
} from 'initialization';
export type ScrollbarsDynamicInitializationElement = DynamicInitializationElement<
[target: InitializationTargetElement, host: HTMLElement, viewport: HTMLElement]
>;
/**
* Object for special initialization.
*
* 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.
*
* Null or Undefined means that the environment initialization strategy is used.
*/
export interface ScrollbarsInitialization {
scrollbarsSlot?: ScrollbarsDynamicInitializationElement;
}
export type ScrollbarsInitializationStrategy = {
[K in keyof ScrollbarsInitialization as `_${K}`]: InitializtationElementStrategy<
ScrollbarsInitialization[K]
>;
};
@@ -6,7 +6,7 @@ import {
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { ReadonlyOSOptions } from 'options';
import type { Setup } from 'setups';
import type { OSTarget } from 'typings';
import type { InitializationTarget } from 'initialization';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ScrollbarsSetupState {}
@@ -16,7 +16,7 @@ export interface ScrollbarsSetupStaticState {
}
export const createScrollbarsSetup = (
target: OSTarget,
target: InitializationTarget,
options: ReadonlyOSOptions,
structureSetupElements: StructureSetupElementsObj
): Setup<ScrollbarsSetupState, ScrollbarsSetupStaticState> => {
@@ -1,13 +1,10 @@
import { assignDeep, hasOwnProperty } from 'support';
import type { OSOptions, ReadonlyOSOptions } from 'options';
import type { Options, ReadonlyOSOptions } from 'options';
import type { PartialOptions } from 'typings';
export type SetupElements<T extends Record<string, any>> = [elements: T, destroy: () => void];
export type SetupUpdate<T = void> = (
changedOptions: PartialOptions<OSOptions>,
force?: boolean
) => T;
export type SetupUpdate<T = void> = (changedOptions: PartialOptions<Options>, force?: boolean) => T;
export type SetupUpdateCheckOption = <T>(path: string) => [value: T, changed: boolean];
@@ -36,7 +33,7 @@ const getPropByPath = <T>(obj: any, path: string): T =>
export const createOptionCheck =
(
options: ReadonlyOSOptions,
changedOptions: PartialOptions<OSOptions>,
changedOptions: PartialOptions<Options>,
force?: boolean
): SetupUpdateCheckOption =>
(path: string) =>
@@ -1 +1,2 @@
export * from 'setups/structureSetup/structureSetup';
export * from 'setups/structureSetup/structureSetup.initialization';
@@ -1,8 +1,8 @@
import {
isHTMLElement,
appendChildren,
is,
createDiv,
is,
contents,
insertAfter,
addClass,
@@ -15,8 +15,6 @@ import {
runEach,
insertBefore,
attr,
isBoolean,
isFunction,
keys,
removeAttr,
attrClass,
@@ -33,17 +31,22 @@ import {
classNameContent,
classNameViewportScrollbarStyling,
} from 'classnames';
import { getEnvironment } from 'environment';
import {
getEnvironment,
StructureInitializationStrategyStaticElement,
StructureInitializationStrategyDynamicElement,
} from 'environment';
import { OSTarget, OSTargetElement, StructureInitialization } from 'typings';
staticInitializationElement as generalStaticInitializationElement,
dynamicInitializationElement as generalDynamicInitializationElement,
} from 'initialization';
import type { InitializationTarget, InitializationTargetElement } from 'initialization';
import type {
StructureDynamicInitializationElement,
StructureInitialization,
StructureStaticInitializationElement,
} from 'setups/structureSetup/structureSetup.initialization';
export type StructureSetupElements = [targetObj: StructureSetupElementsObj, destroy: () => void];
export interface StructureSetupElementsObj {
_target: OSTargetElement;
_target: InitializationTargetElement;
_host: HTMLElement;
_viewport: HTMLElement;
_padding: HTMLElement | false;
@@ -64,6 +67,8 @@ export interface StructureSetupElementsObj {
let contentArrangeCounter = 0;
const createNewDiv = createDiv.bind(0, '');
const unwrap = (elm: HTMLElement | false | null | undefined) => {
appendChildren(parent(elm), contents(elm));
removeElements(elm);
@@ -87,38 +92,14 @@ const createUniqueViewportArrangeElement = (): HTMLStyleElement | false => {
return result;
};
const staticCreationFromStrategy = (
target: OSTargetElement,
initializationValue?: HTMLElement | undefined,
strategy?: StructureInitializationStrategyStaticElement
): HTMLElement => {
const result =
initializationValue ||
(isFunction(strategy) ? strategy(target) : (strategy as null | undefined));
return result || createDiv();
};
const dynamicCreationFromStrategy = (
target: OSTargetElement,
initializationValue: HTMLElement | boolean | undefined,
strategy: StructureInitializationStrategyDynamicElement
): HTMLElement | false => {
const takeInitializationValue = isBoolean(initializationValue) || initializationValue;
const result = takeInitializationValue
? (initializationValue as boolean | HTMLElement)
: isFunction(strategy)
? strategy(target)
: strategy;
return result === true ? createDiv() : result;
};
const addDataAttrHost = (elm: HTMLElement, value?: string | false | null | undefined) => {
attr(elm, dataAttributeHost, value || '');
return removeAttr.bind(0, elm, dataAttributeHost);
};
export const createStructureSetupElements = (target: OSTarget): StructureSetupElements => {
export const createStructureSetupElements = (
target: InitializationTarget
): StructureSetupElements => {
const { _getInitializationStrategy, _nativeScrollbarStyling } = getEnvironment();
const {
_host: hostInitializationStrategy,
@@ -129,7 +110,7 @@ export const createStructureSetupElements = (target: OSTarget): StructureSetupEl
const targetIsElm = isHTMLElement(target);
const targetStructureInitialization = target as StructureInitialization;
const targetElement = targetIsElm
? (target as OSTargetElement)
? (target as InitializationTargetElement)
: targetStructureInitialization.target;
const isTextarea = is(targetElement, 'textarea');
const isBody = !isTextarea && is(targetElement, 'body');
@@ -137,36 +118,44 @@ export const createStructureSetupElements = (target: OSTarget): StructureSetupEl
const bodyElm = ownerDocument.body as HTMLBodyElement;
const wnd = ownerDocument.defaultView as Window;
const singleElmSupport = !!ResizeObserverConstructor && _nativeScrollbarStyling;
const potentialViewportElement = staticCreationFromStrategy(
targetElement,
targetStructureInitialization.viewport,
viewportInitializationStrategy
const staticInitializationElement =
generalStaticInitializationElement<StructureStaticInitializationElement>.bind(0, [
targetElement,
]);
const dynamicInitializationElement =
generalDynamicInitializationElement<StructureDynamicInitializationElement>.bind(0, [
targetElement,
]);
const potentialViewportElement = staticInitializationElement(
createNewDiv,
viewportInitializationStrategy,
targetStructureInitialization.viewport
);
const potentiallySingleElm = potentialViewportElement === targetElement;
const viewportIsTarget = singleElmSupport && potentiallySingleElm;
const viewportElement =
potentiallySingleElm && !viewportIsTarget
? staticCreationFromStrategy(targetElement)
? staticInitializationElement(createNewDiv)
: potentialViewportElement;
const evaluatedTargetObj: StructureSetupElementsObj = {
_target: targetElement,
_host: isTextarea
? staticCreationFromStrategy(
targetElement,
targetStructureInitialization.host,
hostInitializationStrategy
? staticInitializationElement(
createNewDiv,
hostInitializationStrategy,
targetStructureInitialization.host
)
: (targetElement as HTMLElement),
_viewport: viewportElement,
_padding: dynamicCreationFromStrategy(
targetElement,
targetStructureInitialization.padding,
paddingInitializationStrategy
_padding: dynamicInitializationElement(
createNewDiv,
paddingInitializationStrategy,
targetStructureInitialization.padding
),
_content: dynamicCreationFromStrategy(
targetElement,
targetStructureInitialization.content,
contentInitializationStrategy
_content: dynamicInitializationElement(
createNewDiv,
contentInitializationStrategy,
targetStructureInitialization.content
),
_viewportArrange: !viewportIsTarget && createUniqueViewportArrangeElement(),
_windowElm: wnd,
@@ -0,0 +1,38 @@
import type {
InitializationTargetElement,
StaticInitializationElement,
DynamicInitializationElement,
InitializtationElementStrategy,
} from 'initialization';
export type StructureStaticInitializationElement = StaticInitializationElement<
[target: InitializationTargetElement]
>;
export type StructureDynamicInitializationElement = DynamicInitializationElement<
[target: InitializationTargetElement]
>;
/**
* Object for special initialization.
*
* Target is always required, if element is not provided or undefined it will be generated.
*
* 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.
*
* Null or Undefined means that the environment initialization strategy is used.
*/
export interface StructureInitialization {
target: InitializationTargetElement;
host?: StructureStaticInitializationElement; // only relevant for textarea
viewport?: StructureStaticInitializationElement;
padding?: StructureDynamicInitializationElement;
content?: StructureDynamicInitializationElement;
}
export type StructureInitializationStrategy = {
[K in keyof Omit<StructureInitialization, 'target'> as `_${K}`]: InitializtationElementStrategy<
StructureInitialization[K]
>;
};
@@ -6,9 +6,10 @@ import { createStructureSetupObservers } from 'setups/structureSetup/structureSe
import type { StructureSetupUpdateHints } from 'setups/structureSetup/structureSetup.update';
import type { StructureSetupElementsObj } from 'setups/structureSetup/structureSetup.elements';
import type { TRBL, XY } from 'support';
import type { OSOptions, ReadonlyOSOptions } from 'options';
import type { Options, ReadonlyOSOptions } from 'options';
import type { Setup } from 'setups';
import type { OSTarget, PartialOptions, StyleObject, OverflowStyle } from 'typings';
import type { InitializationTarget } from 'initialization';
import type { PartialOptions, StyleObject, OverflowStyle } from 'typings';
export interface StructureSetupState {
_padding: TRBL;
@@ -28,7 +29,7 @@ export interface StructureSetupStaticState {
export type OnUpdatedListener = (
updateHints: StructureSetupUpdateHints,
changedOptions: PartialOptions<OSOptions>,
changedOptions: PartialOptions<Options>,
force: boolean
) => void;
@@ -66,7 +67,7 @@ const initialStructureSetupUpdateState: StructureSetupState = {
};
export const createStructureSetup = (
target: OSTarget,
target: InitializationTarget,
options: ReadonlyOSOptions
): Setup<StructureSetupState, StructureSetupStaticState> => {
const checkOptionsFallback = createOptionCheck(options, {});
@@ -75,7 +76,7 @@ export const createStructureSetup = (
const [getState] = state;
const runOnUpdatedListeners = (
updateHints: StructureSetupUpdateHints,
changedOptions?: PartialOptions<OSOptions>,
changedOptions?: PartialOptions<Options>,
force?: boolean
) => {
runEach(onUpdatedListeners, [updateHints, changedOptions || {}, !!force]);
@@ -12,11 +12,11 @@ type NodeCollection = ArrayLike<Node> | Node | false | null | undefined;
*/
const before = (
parentElm: Node | false | null | undefined,
preferredAnchor: Node | null | undefined,
preferredAnchor: Node | false | null | undefined,
insertedElms: NodeCollection
): void => {
if (insertedElms) {
let anchor: Node | null | undefined = preferredAnchor;
let anchor: Node | false | null | undefined = preferredAnchor;
let fragment: DocumentFragment | Node | null | undefined;
// parent must be defined
@@ -54,7 +54,10 @@ const before = (
* @param node The Node to which the children shall be appended.
* @param children The Nodes which shall be appended.
*/
export const appendChildren = (node: Node | null | undefined, children: NodeCollection): void => {
export const appendChildren = (
node: Node | false | null | undefined,
children: NodeCollection
): void => {
before(node, null, children);
};
@@ -63,7 +66,10 @@ export const appendChildren = (node: Node | null | undefined, children: NodeColl
* @param node The Node to which the children shall be prepended.
* @param children The Nodes which shall be prepended.
*/
export const prependChildren = (node: Node | null | undefined, children: NodeCollection): void => {
export const prependChildren = (
node: Node | false | null | undefined,
children: NodeCollection
): void => {
before(node, node && node.firstChild, children);
};
@@ -73,7 +79,7 @@ export const prependChildren = (node: Node | null | undefined, children: NodeCol
* @param insertedNodes The Nodes which shall be inserted.
*/
export const insertBefore = (
node: Node | null | undefined,
node: Node | false | null | undefined,
insertedNodes: NodeCollection
): void => {
before(parent(node), node, insertedNodes);
@@ -84,7 +90,10 @@ export const insertBefore = (
* @param node The Node after which the given Nodes shall be inserted.
* @param insertedNodes The Nodes which shall be inserted.
*/
export const insertAfter = (node: Node | null | undefined, insertedNodes: NodeCollection): void => {
export const insertAfter = (
node: Node | false | null | undefined,
insertedNodes: NodeCollection
): void => {
before(parent(node), node && node.nextSibling, insertedNodes);
};
@@ -3,96 +3,102 @@ import { keys } from 'support/utils/object';
import { each, from } from 'support/utils/array';
export type EventListener<
NameArgsMap extends Record<string, any>,
Name extends Extract<keyof NameArgsMap, string> = Extract<keyof NameArgsMap, string>
> = (...args: NameArgsMap[Name] extends undefined ? [] : [args: NameArgsMap[Name]]) => void;
EventMap extends Record<string, any>,
Name extends keyof EventMap = keyof EventMap
> = (...args: EventMap[Name] extends undefined ? [] : [args: EventMap[Name]]) => void;
export type EventListenerGroup<
NameArgsMap extends Record<string, any>,
Name extends Extract<keyof NameArgsMap, string> = Extract<keyof NameArgsMap, string>
> = EventListener<NameArgsMap, Name> | EventListener<NameArgsMap, Name>[];
export type AddEventListener<NameArgsMap extends Record<string, any>> = <
Name extends Extract<keyof NameArgsMap, string>
>(
name: Name,
listener: EventListenerGroup<NameArgsMap, Name>
) => () => void;
export type RemoveEventListener<NameArgsMap extends Record<string, any>> = <
Name extends Extract<keyof NameArgsMap, string>
>(
name?: Name,
listener?: EventListenerGroup<NameArgsMap, Name>
) => void;
export type TriggerEventListener<NameArgsMap extends Record<string, any>> = <
Name extends Extract<keyof NameArgsMap, string>
>(
name: Name,
...args: NameArgsMap[Name] extends undefined ? [] : [args: NameArgsMap[Name]]
) => void;
export type InitialEventListeners<NameArgsMap extends Record<string, any>> = {
[K in Extract<keyof NameArgsMap, string>]?: EventListenerGroup<NameArgsMap, K>;
export type InitialEventListeners<EventMap extends Record<string, any>> = {
[K in keyof EventMap]?: EventListener<EventMap> | EventListener<EventMap>[];
};
const manageListener = <NameArgsMap extends Record<string, any>>(
callback: (listener?: EventListener<NameArgsMap>) => void,
listener?: EventListener<NameArgsMap> | EventListener<NameArgsMap>[]
const manageListener = <EventMap extends Record<string, any>>(
callback: (listener?: EventListener<EventMap>) => void,
listener?: EventListener<EventMap> | EventListener<EventMap>[]
) => {
each(isArray(listener) ? listener : [listener], callback);
};
export const createEventListenerHub = <NameArgsMap extends Record<string, any>>(
initialEventListeners?: InitialEventListeners<NameArgsMap>
): [
AddEventListener<NameArgsMap>,
RemoveEventListener<NameArgsMap>,
TriggerEventListener<NameArgsMap>
] => {
const events = new Map<Extract<keyof NameArgsMap, string>, Set<EventListener<NameArgsMap>>>();
const removeEvent: RemoveEventListener<NameArgsMap> = (name?, listener?) => {
export const createEventListenerHub = <EventMap extends Record<string, any>>(
initialEventListeners?: InitialEventListeners<EventMap>
) => {
type EventListener<Name extends keyof EventMap = keyof EventMap> = (
...args: EventMap[Name] extends undefined ? [] : [args: EventMap[Name]]
) => void;
const events = new Map<keyof EventMap, Set<EventListener>>();
function removeEvent<Name extends keyof EventMap>(
name?: Name,
listener?: EventListener<Name>
): void;
function removeEvent<Name extends keyof EventMap>(
name?: Name,
listener?: EventListener<Name>[]
): void;
function removeEvent<Name extends keyof EventMap>(
name?: Name,
listener?: EventListener<Name> | EventListener<Name>[]
): void {
if (name) {
const eventSet = events.get(name);
manageListener((currListener) => {
if (eventSet) {
eventSet[currListener ? 'delete' : 'clear'](currListener!);
}
}, listener as EventListenerGroup<NameArgsMap> | undefined);
}, listener as any);
} else {
events.forEach((eventSet) => {
eventSet.clear();
});
events.clear();
}
};
const addEvent: AddEventListener<NameArgsMap> = (name, listener) => {
}
function addEvent<Name extends keyof EventMap>(
name: Name,
listener: EventListener<Name>
): () => void;
function addEvent<Name extends keyof EventMap>(
name: Name,
listener: EventListener<Name>[]
): () => void;
function addEvent<Name extends keyof EventMap>(
name: Name,
listener: EventListener<Name> | EventListener<Name>[]
): () => void {
const eventSet = events.get(name) || new Set();
events.set(name, eventSet);
manageListener((currListener) => {
currListener && eventSet.add(currListener);
}, listener as EventListenerGroup<NameArgsMap>);
}, listener as any);
return removeEvent.bind(0, name as any, listener as EventListenerGroup<NameArgsMap>);
};
const triggerEvent: TriggerEventListener<NameArgsMap> = (name, args?) => {
return removeEvent.bind(0, name as any, listener as any);
}
function triggerEvent<Name extends keyof EventMap>(
name: Name,
...args: EventMap[Name] extends undefined ? [] : [args: EventMap[Name]]
): void {
const eventSet = events.get(name);
each(from(eventSet), (event) => {
if (args) {
(event as (args: NameArgsMap[Extract<keyof NameArgsMap, string>]) => void)(args as any);
(event as (args: EventMap[keyof EventMap]) => void)(args as any);
} else {
(event as () => void)();
}
});
};
}
const initialListenerKeys = keys(initialEventListeners) as Extract<keyof NameArgsMap, string>[];
const initialListenerKeys = keys(initialEventListeners) as Extract<keyof EventMap, string>[];
each(initialListenerKeys, (key) => {
addEvent(key, initialEventListeners![key] as any);
});
return [addEvent, removeEvent, triggerEvent];
return [addEvent, removeEvent, triggerEvent] as [
typeof addEvent,
typeof removeEvent,
typeof triggerEvent
];
};
-53
View File
@@ -14,59 +14,6 @@ export type StyleObject<CustomCssProps = ''> = {
| number;
};
export type InternalVersionOf<T> = {
[K in keyof T as `_${Uncapitalize<string & K>}`]: T[K];
};
export type OSTargetElement = HTMLElement | HTMLTextAreaElement;
/**
* Static elements MUST be present.
*/
type StructureInitializationStaticElement = HTMLElement;
/**
* Dynamic element CAN be present.
* If its a element the element will be handled as the repsective element.
* True means that the respective dynamic element is forced to be generated.
* False means that the respective dynamic element is forced NOT to be generated.
*/
type StructureInitializationDynamicElement = HTMLElement | boolean;
/**
* Object for special initialization.
*
* Target is always required, if element is not provided or undefined it will be generated.
*
* 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.
*
* Undefined means that the environment initialization strategy for the respective element is used.
*/
export interface StructureInitialization {
target: OSTargetElement;
host?: StructureInitializationStaticElement; // only relevant for textarea
viewport?: StructureInitializationStaticElement;
padding?: StructureInitializationDynamicElement;
content?: StructureInitializationDynamicElement;
}
/**
* Object for special initialization.
*
* scrollbarsSlot is the element to which the scrollbars are applied to. If null or undefined the plugin decides by itself whats the scrollbars slot.
*/
export interface ScrollbarsInitialization {
scrollbarsSlot?:
| null
| HTMLElement
| ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => null | HTMLElement);
}
export interface OSInitializationObject extends StructureInitialization, ScrollbarsInitialization {}
export type OSTarget = OSTargetElement | OSInitializationObject;
export type OverflowStyle = 'scroll' | 'hidden' | 'visible';
/*
@@ -1,13 +1,14 @@
import {
Environment,
StructureInitializationStrategyStaticElement,
StructureInitializationStrategyDynamicElement,
} from 'environment';
import { OSTarget, StructureInitialization } from 'typings';
import { InternalEnvironment } from 'environment';
import {
createStructureSetupElements,
StructureSetupElementsObj,
} from 'setups/structureSetup/structureSetup.elements';
import type { InitializationTarget, InitializtationElementStrategy } from 'initialization';
import type {
StructureInitialization,
StructureStaticInitializationElement,
StructureDynamicInitializationElement,
} from 'setups/structureSetup/structureSetup.initialization';
import { isHTMLElement } from 'support';
const mockGetEnvironment = jest.fn();
@@ -16,7 +17,7 @@ jest.mock('environment', () => ({
}));
interface StructureSetupElementsProxy {
input: OSTarget;
input: InitializationTarget;
elements: StructureSetupElementsObj;
destroy: () => void;
}
@@ -95,7 +96,7 @@ const assertCorrectDOMStructure = (textarea?: boolean) => {
};
const createStructureSetupProxy = (
target: OSTarget | StructureInitialization
target: InitializationTarget | StructureInitialization
): StructureSetupElementsProxy => {
const [elements, destroy] = createStructureSetupElements(target);
return {
@@ -108,7 +109,7 @@ const createStructureSetupProxy = (
const assertCorrectSetupElements = (
textarea: boolean,
setupElementsProxy: StructureSetupElementsProxy,
environment: Environment
environment: InternalEnvironment
): [StructureSetupElementsObj, () => void] => {
const { input, elements, destroy } = setupElementsProxy;
const { _target, _host, _padding, _viewport, _content } = elements;
@@ -165,11 +166,11 @@ const assertCorrectSetupElements = (
const styleElm = document.querySelector('style');
const checkStrategyDependendElements = (
elm: Element | null,
input: HTMLElement | boolean | undefined,
input: StructureStaticInitializationElement | StructureDynamicInitializationElement,
isStaticStrategy: boolean,
strategy:
| StructureInitializationStrategyStaticElement
| StructureInitializationStrategyDynamicElement
| InitializtationElementStrategy<StructureStaticInitializationElement>
| InitializtationElementStrategy<StructureDynamicInitializationElement>
) => {
if (input) {
expect(elm).toBeTruthy();
@@ -179,7 +180,8 @@ const assertCorrectSetupElements = (
}
if (input === undefined) {
if (isStaticStrategy) {
strategy = strategy as StructureInitializationStrategyStaticElement;
strategy =
strategy as InitializtationElementStrategy<StructureStaticInitializationElement>;
if (typeof strategy === 'function') {
const result = strategy(target);
if (result) {
@@ -191,14 +193,13 @@ const assertCorrectSetupElements = (
expect(elm).toBeTruthy();
}
} else {
strategy = strategy as StructureInitializationStrategyDynamicElement;
strategy =
strategy as InitializtationElementStrategy<StructureDynamicInitializationElement>;
expect(strategy).not.toBe(null);
expect(strategy).not.toBe(undefined);
if (typeof strategy === 'function') {
const result = strategy(target);
const resultIsBoolean = typeof result === 'boolean';
expect(result).not.toBe(null);
expect(result).not.toBe(undefined);
if (resultIsBoolean) {
if (result) {
expect(elm).toBeTruthy();
@@ -265,7 +266,7 @@ const assertCorrectDestroy = (snapshot: string, destroy: () => void) => {
expect(snapshot).toBe(getSnapshot());
};
const env: Environment = jest.requireActual('environment').getEnvironment();
const env: InternalEnvironment = jest.requireActual('environment').getEnvironment();
const envDefault = {
name: 'default',
env,
@@ -317,12 +318,8 @@ const envInitStrategyAssigned = {
_getInitializationStrategy: () => ({
_host: () => document.querySelector('#host1') as HTMLElement,
_viewport: (target: HTMLElement) => target.querySelector('#viewport') as HTMLElement,
_content: (target: HTMLElement) =>
target.querySelector<HTMLElement>('#content') ||
env._defaultInitializationStrategy._content,
_padding: (target: HTMLElement) =>
target.querySelector<HTMLElement>('#padding') ||
env._defaultInitializationStrategy._padding,
_content: (target: HTMLElement) => target.querySelector<HTMLElement>('#content'),
_padding: (target: HTMLElement) => target.querySelector<HTMLElement>('#padding'),
_scrollbarsSlot: null,
}),
},
@@ -1,14 +1,14 @@
// @ts-ignore
import { playwrightRollup } from '@/playwright/rollup';
import { test } from '@playwright/test';
import { Environment } from 'environment';
import { InternalEnvironment } from 'environment';
playwrightRollup();
test.describe('Environment', () => {
test('page should be titled "Environment"', async ({ page }) => {
// @ts-ignore
const a: Environment = await page.evaluate(() => window.environment.envInstance);
const a: InternalEnvironment = await page.evaluate(() => window.environment.envInstance);
console.log(a);
await expect(page.title()).resolves.toMatch('environment');
});
@@ -22,7 +22,7 @@ import { resize } from '@/testing-browser/Resize';
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
import { generateClassChangeSelectCallback, iterateSelect } from '@/testing-browser/Select';
import { timeout } from '@/testing-browser/timeout';
import { OSOptions } from 'options';
import { Options } from 'options';
import { PartialOptions } from 'typings';
interface Metrics {
@@ -572,7 +572,7 @@ const iterateMinMax = async (afterEach?: () => any) => {
await iterate(containerMinMaxSelect, afterEach);
};
const overflowTest = async (osOptions?: PartialOptions<OSOptions>) => {
const overflowTest = async (osOptions?: PartialOptions<Options>) => {
const additiveOverflow = () => {
if (isFractionalPixelRatio()) {
return 1;
+119 -127
View File
@@ -1,46 +1,6 @@
type PartialOptions<T> = {
[P in keyof T]?: T[P] extends Record<string, unknown> ? PartialOptions<T[P]> : T[P];
};
type OSTargetElement = HTMLElement | HTMLTextAreaElement;
/**
* Static elements MUST be present.
*/
type StructureInitializationStaticElement = HTMLElement;
/**
* Dynamic element CAN be present.
* If its a element the element will be handled as the repsective element.
* True means that the respective dynamic element is forced to be generated.
* False means that the respective dynamic element is forced NOT to be generated.
*/
type StructureInitializationDynamicElement = HTMLElement | boolean;
/**
* Object for special initialization.
*
* Target is always required, if element is not provided or undefined it will be generated.
*
* 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.
*
* Undefined means that the environment initialization strategy for the respective element is used.
*/
interface StructureInitialization {
target: OSTargetElement;
host?: StructureInitializationStaticElement; // only relevant for textarea
viewport?: StructureInitializationStaticElement;
padding?: StructureInitializationDynamicElement;
content?: StructureInitializationDynamicElement;
}
/**
* Object for special initialization.
*
* scrollbarsSlot is the element to which the scrollbars are applied to. If null or undefined the plugin decides by itself whats the scrollbars slot.
*/
interface ScrollbarsInitialization {
scrollbarsSlot?: null | HTMLElement | ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => null | HTMLElement);
}
interface OSInitializationObject extends StructureInitialization, ScrollbarsInitialization {
}
type OSTarget = OSTargetElement | OSInitializationObject;
type OverflowStyle = "scroll" | "hidden" | "visible";
interface TRBL {
t: number;
@@ -52,20 +12,17 @@ interface XY<T> {
x: T;
y: T;
}
type EventListener<NameArgsMap extends Record<string, any>, Name extends Extract<keyof NameArgsMap, string>> = (...args: NameArgsMap[Name] extends undefined ? [
type EventListener<EventMap extends Record<string, any>, Name extends keyof EventMap> = (...args: EventMap[Name] extends undefined ? [
] : [
args: NameArgsMap[Name]
args: EventMap[Name]
]) => void;
type EventListenerGroup<NameArgsMap extends Record<string, any>, Name extends Extract<keyof NameArgsMap, string>> = EventListener<NameArgsMap, Name> | EventListener<NameArgsMap, Name>[];
type AddEventListener<NameArgsMap extends Record<string, any>> = <Name extends Extract<keyof NameArgsMap, string>>(name: Name, listener: EventListenerGroup<NameArgsMap, Name>) => () => void;
type RemoveEventListener<NameArgsMap extends Record<string, any>> = <Name extends Extract<keyof NameArgsMap, string>>(name?: Name, listener?: EventListenerGroup<NameArgsMap, Name>) => void;
type InitialEventListeners<NameArgsMap extends Record<string, any>> = {
[K in Extract<keyof NameArgsMap, string>]?: EventListenerGroup<NameArgsMap, K>;
type InitialEventListeners<EventMap extends Record<string, any>> = {
[K in keyof EventMap]?: EventListener<EventMap> | EventListener<EventMap>[];
};
type OverflowBehavior = "hidden" | "scroll" | "visible" | "visible-hidden" | "visible-scroll";
type VisibilityBehavior = "visible" | "hidden" | "auto";
type AutoHideBehavior = "never" | "scroll" | "leave" | "move";
interface OSOptions {
interface Options {
paddingAbsolute: boolean;
updating: {
elementEvents: Array<[
@@ -96,43 +53,78 @@ interface OSOptions {
initialize: boolean;
};
}
type StructureInitializationStrategyElementFn<T> = ((target: OSTargetElement) => HTMLElement | T) | T;
type ScrollbarsInitializationStrategyElementFn<T> = ((target: OSTargetElement, host: HTMLElement, viewport: HTMLElement) => HTMLElement | T) | T;
/**
* A Static element is an element which MUST be generated.
* If null or undefined (or the returned result is null or undefined), the initialization function is generatig the element, otherwise
* the element returned by the function acts as the generated element.
*/
type StructureInitializationStrategyStaticElement = StructureInitializationStrategyElementFn<null | undefined>;
/**
* A Dynamic element is an element which CAN be generated.
* If boolean (or the returned result is boolean), the generation of the element is forced (or not).
* If the function returns and element, the element returned by the function acts as the generated element.
*/
type StructureInitializationStrategyDynamicElement = StructureInitializationStrategyElementFn<boolean>;
interface StructureInitializationStrategy {
_host: StructureInitializationStrategyStaticElement;
_viewport: StructureInitializationStrategyStaticElement;
_padding: StructureInitializationStrategyDynamicElement;
_content: StructureInitializationStrategyDynamicElement;
}
interface ScrollbarsInitializationStrategy {
/**
* The scrollbars slot. If null or undefined (or the returned result is null or undefined), the initialization function is deciding the element, otherwise
* the element returned by the function acts as the scrollbars slot.
*/
_scrollbarsSlot: ScrollbarsInitializationStrategyElementFn<null | undefined>;
}
interface InitializationStrategy extends StructureInitializationStrategy, ScrollbarsInitializationStrategy {
}
type DefaultInitializationStrategy = {
[K in keyof InitializationStrategy]: Extract<InitializationStrategy[K], boolean | null | undefined>;
};
type OSPluginInstance = Record<string, unknown> | ((staticObj: OverlayScrollbarsStatic, instanceObj: OverlayScrollbars) => void);
type OSPlugin<T extends OSPluginInstance> = [
string,
T
];
type ScrollbarsDynamicInitializationElement = DynamicInitializationElement<[
target: InitializationTargetElement,
host: HTMLElement,
viewport: HTMLElement
]>;
/**
* Object for special initialization.
*
* 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.
*
* Null or Undefined means that the environment initialization strategy is used.
*/
interface ScrollbarsInitialization {
scrollbarsSlot?: ScrollbarsDynamicInitializationElement;
}
type ScrollbarsInitializationStrategy = {
[K in keyof ScrollbarsInitialization as `_${K}`]: InitializtationElementStrategy<ScrollbarsInitialization[K]>;
};
type StructureStaticInitializationElement = StaticInitializationElement<[
target: InitializationTargetElement
]>;
type StructureDynamicInitializationElement = DynamicInitializationElement<[
target: InitializationTargetElement
]>;
/**
* Object for special initialization.
*
* Target is always required, if element is not provided or undefined it will be generated.
*
* 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.
*
* Null or Undefined means that the environment initialization strategy is used.
*/
interface StructureInitialization {
target: InitializationTargetElement;
host?: StructureStaticInitializationElement; // only relevant for textarea
viewport?: StructureStaticInitializationElement;
padding?: StructureDynamicInitializationElement;
content?: StructureDynamicInitializationElement;
}
type StructureInitializationStrategy = {
[K in keyof Omit<StructureInitialization, "target"> as `_${K}`]: InitializtationElementStrategy<StructureInitialization[K]>;
};
type StaticInitialization = HTMLElement | null | undefined;
type DynamicInitialization = HTMLElement | boolean | null | undefined;
type InitializationTargetElement = HTMLElement | HTMLTextAreaElement;
type InitializationTargetObject = StructureInitialization & ScrollbarsInitialization;
type InitializationTarget = InitializationTargetElement | InitializationTargetObject;
type InitializationStrategy = StructureInitializationStrategy & ScrollbarsInitializationStrategy;
/**
* Static elements MUST be present.
* Null or undefined behave like if this element wasn't specified during initialization.
*/
type StaticInitializationElement<Args extends any[]> = ((...args: Args) => StaticInitialization) | StaticInitialization;
/**
* Dynamic element CAN be present.
* If its a element the element will be handled as the repsective element.
* True means that the respective dynamic element is forced to be generated.
* False means that the respective dynamic element is forced NOT to be generated.
* Null or undefined behave like if this element wasn't specified during initialization.
*/
type DynamicInitializationElement<Args extends any[]> = ((...args: Args) => DynamicInitialization) | DynamicInitialization;
type InitializtationElementStrategy<InitElm> = Exclude<InitElm, HTMLElement>;
type GeneralInitialEventListeners = InitialEventListeners;
type GeneralEventListener = EventListener;
/*
onScrollStart : null,
onScroll : null,
@@ -143,6 +135,42 @@ onDirectionChanged : null, // gone
onContentSizeChanged : null, // gone
onHostSizeChanged : null, // gone
*/
interface OverlayScrollbarsStatic {
(target: InitializationTarget | InitializationTargetObject, options?: PartialOptions<Options>, eventListeners?: GeneralInitialEventListeners<EventListenerMap>): OverlayScrollbars;
plugin(osPlugin: OSPlugin | OSPlugin[]): void;
env(): Environment;
}
interface Environment {
scrollbarSize: XY<number>;
scrollbarIsOverlaid: XY<boolean>;
scrollbarStyling: boolean;
rtlScrollBehavior: {
n: boolean;
i: boolean;
};
flexboxGlue: boolean;
cssCustomProperties: boolean;
defaultInitializationStrategy: InitializationStrategy;
defaultDefaultOptions: Options;
getInitializationStrategy(): InitializationStrategy;
setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
getDefaultOptions(): Options;
setDefaultOptions(newDefaultOptions: PartialOptions<Options>): void;
}
interface State {
padding: TRBL;
paddingAbsolute: boolean;
overflowAmount: XY<number>;
overflowStyle: XY<OverflowStyle>;
hasOverflow: XY<boolean>;
}
interface Elements {
target: HTMLElement;
host: HTMLElement;
padding: HTMLElement;
viewport: HTMLElement;
content: HTMLElement;
}
interface OnUpdatedEventListenerArgs {
updateHints: {
sizeChanged: boolean;
@@ -153,63 +181,27 @@ interface OnUpdatedEventListenerArgs {
hostMutation: boolean;
contentMutation: boolean;
};
changedOptions: PartialOptions<OSOptions>;
changedOptions: PartialOptions<Options>;
force: boolean;
}
interface OSEventListenersNameArgsMap {
interface EventListenerMap {
initialized: undefined;
initializationWithdrawn: undefined;
updated: OnUpdatedEventListenerArgs;
destroyed: undefined;
}
type AddOSEventListener = AddEventListener<OSEventListenersNameArgsMap>;
type RemoveOSEventListener = RemoveEventListener<OSEventListenersNameArgsMap>;
type InitialOSEventListeners = InitialEventListeners<OSEventListenersNameArgsMap>;
interface OverlayScrollbarsStatic {
(target: OSTarget | OSInitializationObject, options?: PartialOptions<OSOptions>, eventListeners?: InitialOSEventListeners): OverlayScrollbars;
plugin(osPlugin: OSPlugin | OSPlugin[]): void;
env(): OverlayScrollbarsEnv;
}
interface OverlayScrollbarsEnv {
scrollbarSize: XY<number>;
scrollbarIsOverlaid: XY<boolean>;
scrollbarStyling: boolean;
rtlScrollBehavior: {
n: boolean;
i: boolean;
};
flexboxGlue: boolean;
cssCustomProperties: boolean;
defaultInitializationStrategy: DefaultInitializationStrategy;
defaultDefaultOptions: OSOptions;
getInitializationStrategy(): InitializationStrategy;
setInitializationStrategy(newInitializationStrategy: Partial<InitializationStrategy>): void;
getDefaultOptions(): OSOptions;
setDefaultOptions(newDefaultOptions: PartialOptions<OSOptions>): void;
}
interface OverlayScrollbarsState {
padding: TRBL;
paddingAbsolute: boolean;
overflowAmount: XY<number>;
overflowStyle: XY<OverflowStyle>;
hasOverflow: XY<boolean>;
}
interface OverlayScrollbarsElements {
target: HTMLElement;
host: HTMLElement;
padding: HTMLElement;
viewport: HTMLElement;
content: HTMLElement;
}
type EventListener$0<Name extends keyof EventListenerMap> = GeneralEventListener<EventListenerMap, Name>;
interface OverlayScrollbars {
options(): OSOptions;
options(newOptions?: PartialOptions<OSOptions>): OSOptions;
options(): Options;
options(newOptions?: PartialOptions<Options>): Options;
update(force?: boolean): void;
destroy(): void;
state(): OverlayScrollbarsState;
elements(): OverlayScrollbarsElements;
on: AddOSEventListener;
off: RemoveOSEventListener;
state(): State;
elements(): Elements;
on<Name extends keyof EventListenerMap>(name: Name, listener: EventListener$0<Name>): () => void;
on<Name extends keyof EventListenerMap>(name: Name, listener: EventListener$0<Name>[]): () => void;
off<Name extends keyof EventListenerMap>(name: Name, listener?: EventListener$0<Name>): void;
off<Name extends keyof EventListenerMap>(name: Name, listener?: EventListener$0<Name>[]): void;
}
/**
* Notes:
+32 -23
View File
@@ -17,7 +17,7 @@
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.8.3":
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
@@ -1248,6 +1248,14 @@
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.1.tgz#36a6acc93987adcf0ba50c66908bd0b70de8afea"
integrity sha512-Ct5MqZkLGEXTVmQYbGtx9SVqD2fqwvdubdps5D3djjAkgkKwT918VNOz65pEHFaYTeWcukmJmH5SwsA9Tn2ObQ==
"@jridgewell/source-map@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb"
integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==
dependencies:
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.13"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c"
@@ -4784,7 +4792,7 @@ jest-watcher@^28.1.1:
jest-util "^28.1.1"
string-length "^4.0.1"
jest-worker@^26.0.0:
jest-worker@^26.2.1:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
@@ -6574,17 +6582,17 @@ rollup-plugin-styles@^3.10.0:
source-map "^0.7.3"
tslib "^2.1.0"
rollup-plugin-terser@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-6.1.0.tgz#071866585aea104bfbb9dd1019ac523e63c81e45"
integrity sha512-4fB3M9nuoWxrwm39habpd4hvrbrde2W2GG4zEGPQg1YITNkM3Tqur5jSuXlWNzbv/2aMLJ+dZJaySc3GCD8oDw==
rollup-plugin-terser@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
dependencies:
"@babel/code-frame" "^7.8.3"
jest-worker "^26.0.0"
serialize-javascript "^3.0.0"
terser "^4.7.0"
"@babel/code-frame" "^7.10.4"
jest-worker "^26.2.1"
serialize-javascript "^4.0.0"
terser "^5.0.0"
rollup-plugin-ts@^3.0.1:
rollup-plugin-ts@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-ts/-/rollup-plugin-ts-3.0.2.tgz#ee1a3f9ffe202ceff0b4d2f725fa268fa0c921bf"
integrity sha512-67qi2QTHewhLyKDG6fX3jpohWpmUPPIT/xJ7rsYK46X6MqmoWy64Ti0y8ygPfLv8mXDCdRZUofM3mTxDfCswRA==
@@ -6688,10 +6696,10 @@ semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7:
dependencies:
lru-cache "^6.0.0"
serialize-javascript@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea"
integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==
serialize-javascript@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
dependencies:
randombytes "^2.1.0"
@@ -6845,7 +6853,7 @@ source-map-support@0.5.13:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map-support@~0.5.12:
source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
@@ -7145,14 +7153,15 @@ terminal-link@^2.0.0:
ansi-escapes "^4.2.1"
supports-hyperlinks "^2.0.0"
terser@^4.7.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
terser@^5.0.0:
version "5.14.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.1.tgz#7c95eec36436cb11cf1902cc79ac564741d19eca"
integrity sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ==
dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
commander "^2.20.0"
source-map "~0.6.1"
source-map-support "~0.5.12"
source-map-support "~0.5.20"
test-exclude@^6.0.0:
version "6.0.0"
@@ -7326,7 +7335,7 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
typescript@^4.5.4, typescript@^4.7.3:
typescript@^4.5.4, typescript@^4.7.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==