mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-21 11:30:36 +03:00
overflow lifecycle
This commit is contained in:
@@ -19,7 +19,7 @@ const createAutoUpdateLoop = (): AutoUpdateLoop => {
|
|||||||
const updateLoopInterval = () => {
|
const updateLoopInterval = () => {
|
||||||
loopInterval = isEmptyArray(intervals) ? defaultLoopInterval : Math.min.apply(null, intervals);
|
loopInterval = isEmptyArray(intervals) ? defaultLoopInterval : Math.min.apply(null, intervals);
|
||||||
};
|
};
|
||||||
const updateTimeCache = createCache<number, number>((ctx) => ctx || performance.now(), {
|
const { _update: updateTimeCache } = createCache<number, number | undefined>((ctx) => ctx || performance.now(), {
|
||||||
_initialValue: performance.now(),
|
_initialValue: performance.now(),
|
||||||
_equal: (currTime, newTime) => {
|
_equal: (currTime, newTime) => {
|
||||||
const delta = newTime! - currTime!;
|
const delta = newTime! - currTime!;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const classNameHost = 'os-host';
|
|||||||
export const classNamePadding = 'os-padding';
|
export const classNamePadding = 'os-padding';
|
||||||
export const classNameViewport = 'os-viewport';
|
export const classNameViewport = 'os-viewport';
|
||||||
export const classNameContent = 'os-content';
|
export const classNameContent = 'os-content';
|
||||||
|
export const classNameContentArrange = `${classNameContent}-arrange`;
|
||||||
export const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`;
|
export const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`;
|
||||||
|
|
||||||
export const classNameSizeObserver = 'os-size-observer';
|
export const classNameSizeObserver = 'os-size-observer';
|
||||||
|
|||||||
@@ -122,17 +122,18 @@ const createEnvironment = (): Environment => {
|
|||||||
const envChildElm = envElm.firstChild as HTMLElement;
|
const envChildElm = envElm.firstChild as HTMLElement;
|
||||||
|
|
||||||
const onChangedListener: Set<OnEnvironmentChanged> = new Set();
|
const onChangedListener: Set<OnEnvironmentChanged> = new Set();
|
||||||
const nativeScrollBarSize = getNativeScrollbarSize(body, envElm);
|
const nativeScrollbarSize = getNativeScrollbarSize(body, envElm);
|
||||||
|
const nativeScrollbarStyling = false; //getNativeScrollbarStyling(envElm); TODO: Re-enable
|
||||||
const nativeScrollbarIsOverlaid = {
|
const nativeScrollbarIsOverlaid = {
|
||||||
x: nativeScrollBarSize.x === 0,
|
x: nativeScrollbarSize.x === 0,
|
||||||
y: nativeScrollBarSize.y === 0,
|
y: nativeScrollbarSize.y === 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const env: Environment = {
|
const env: Environment = {
|
||||||
_autoUpdateLoop: false,
|
_autoUpdateLoop: false,
|
||||||
_nativeScrollbarSize: nativeScrollBarSize,
|
_nativeScrollbarSize: nativeScrollbarSize,
|
||||||
_nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid,
|
_nativeScrollbarIsOverlaid: nativeScrollbarIsOverlaid,
|
||||||
_nativeScrollbarStyling: getNativeScrollbarStyling(envElm),
|
_nativeScrollbarStyling: nativeScrollbarStyling,
|
||||||
_rtlScrollBehavior: getRtlScrollBehavior(envElm, envChildElm),
|
_rtlScrollBehavior: getRtlScrollBehavior(envElm, envChildElm),
|
||||||
_flexboxGlue: getFlexboxGlue(envElm, envChildElm),
|
_flexboxGlue: getFlexboxGlue(envElm, envChildElm),
|
||||||
_addListener(listener: OnEnvironmentChanged): void {
|
_addListener(listener: OnEnvironmentChanged): void {
|
||||||
@@ -144,13 +145,12 @@ const createEnvironment = (): Environment => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
removeAttr(envElm, 'style');
|
removeAttr(envElm, 'style');
|
||||||
removeAttr(envElm, 'class');
|
|
||||||
removeElements(envElm);
|
removeElements(envElm);
|
||||||
|
|
||||||
if (!nativeScrollbarIsOverlaid.x || !nativeScrollbarIsOverlaid.y) {
|
if (!nativeScrollbarStyling && (!nativeScrollbarIsOverlaid.x || !nativeScrollbarIsOverlaid.y)) {
|
||||||
let size = windowSize();
|
let size = windowSize();
|
||||||
let dpr = getWindowDPR();
|
let dpr = getWindowDPR();
|
||||||
let scrollbarSize = nativeScrollBarSize;
|
let scrollbarSize = nativeScrollbarSize;
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
if (onChangedListener.size) {
|
if (onChangedListener.size) {
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import {
|
|
||||||
Cache,
|
|
||||||
OptionsValidated,
|
|
||||||
OptionsWithOptionsTemplate,
|
|
||||||
transformOptions,
|
|
||||||
validateOptions,
|
|
||||||
assignDeep,
|
|
||||||
hasOwnProperty,
|
|
||||||
isEmptyObject,
|
|
||||||
} from 'support';
|
|
||||||
import { PlainObject } from 'typings';
|
|
||||||
|
|
||||||
interface LifecycleBaseUpdateHints<O> {
|
|
||||||
_force?: boolean;
|
|
||||||
_changedOptions?: OptionsValidated<O>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LifecycleBase<O extends PlainObject> {
|
|
||||||
_options(newOptions?: O): O;
|
|
||||||
_update(force?: boolean): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Lifecycle<T extends PlainObject> extends LifecycleBase<T> {
|
|
||||||
_destruct(): void;
|
|
||||||
_onSizeChanged?(): void;
|
|
||||||
_onDirectionChanged?(directionCache: Cache<boolean>): void;
|
|
||||||
_onTrinsicChanged?(widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LifecycleOptionInfo<T> {
|
|
||||||
_value: T;
|
|
||||||
_changed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
|
||||||
|
|
||||||
const getPropByPath = <T>(obj: any, path: string): T =>
|
|
||||||
obj && path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a object which can be seen as the base of a lifecycle because it provides all the tools to manage a lifecycle and its options, cache and base functions.
|
|
||||||
* @param defaultOptionsWithTemplate A object which describes the options and the default options of the lifecycle.
|
|
||||||
* @param initialOptions The initialOptions for the lifecylce. (Can be undefined)
|
|
||||||
* @param updateFunction The update function where cache and options updates are handled. Has two arguments which are the changedOptions and the changedCache objects.
|
|
||||||
*/
|
|
||||||
export const createLifecycleBase = <O>(
|
|
||||||
defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<O>>,
|
|
||||||
initialOptions: O | undefined,
|
|
||||||
updateFunction: (force: boolean, checkOption: LifecycleCheckOption) => any
|
|
||||||
): LifecycleBase<O> => {
|
|
||||||
const { _template: optionsTemplate, _options: defaultOptions } = transformOptions<Required<O>>(defaultOptionsWithTemplate);
|
|
||||||
const options: Required<O> = assignDeep(
|
|
||||||
{},
|
|
||||||
defaultOptions,
|
|
||||||
validateOptions<O>(initialOptions || ({} as O), optionsTemplate, null, true)._validated
|
|
||||||
);
|
|
||||||
|
|
||||||
const update = (hints: LifecycleBaseUpdateHints<O>) => {
|
|
||||||
const { _force, _changedOptions } = hints;
|
|
||||||
const checkOption: LifecycleCheckOption = (path) => ({
|
|
||||||
_value: getPropByPath(options, path),
|
|
||||||
_changed: _force || getPropByPath(_changedOptions, path) !== undefined,
|
|
||||||
});
|
|
||||||
updateFunction(!!_force, checkOption);
|
|
||||||
};
|
|
||||||
|
|
||||||
update({ _force: true });
|
|
||||||
|
|
||||||
return {
|
|
||||||
_options(newOptions?: O) {
|
|
||||||
if (newOptions) {
|
|
||||||
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, options, true);
|
|
||||||
|
|
||||||
if (!isEmptyObject(_changedOptions)) {
|
|
||||||
assignDeep(options, _changedOptions);
|
|
||||||
update({ _changedOptions });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
},
|
|
||||||
_update: (_force?: boolean) => {
|
|
||||||
update({ _force });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { CacheValues, each, push, validateOptions, assignDeep, isEmptyObject, OptionsValidated } from 'support';
|
||||||
|
import { Options } from 'options';
|
||||||
|
import { getEnvironment, Environment } from 'environment';
|
||||||
|
import { StructureSetup } from 'setups/structureSetup';
|
||||||
|
import { createStructureLifecycle } from 'lifecycles/structureLifecycle';
|
||||||
|
import { createOverflowLifecycle } from 'lifecycles/overflowLifecycle';
|
||||||
|
import { LifecycleUpdateFunction, LifecycleUpdateHints } from 'lifecycles/lifecycleUpdateFunction';
|
||||||
|
import { createSizeObserver } from 'observers/sizeObserver';
|
||||||
|
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||||
|
import { createDOMObserver } from 'observers/domObserver';
|
||||||
|
|
||||||
|
export interface LifecycleHubInstance {
|
||||||
|
_update(changedOptions?: OptionsValidated<Options> | null, force?: boolean): void;
|
||||||
|
_destroy(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LifecycleHub {
|
||||||
|
_options: Options;
|
||||||
|
_structureSetup: StructureSetup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrs = ['id', 'class', 'style', 'open'];
|
||||||
|
const directionIsRTLCacheValuesFallback: CacheValues<boolean> = {
|
||||||
|
_value: false,
|
||||||
|
_previous: false,
|
||||||
|
_changed: false,
|
||||||
|
};
|
||||||
|
const heightIntrinsicCacheValuesFallback: CacheValues<boolean> = {
|
||||||
|
_value: false,
|
||||||
|
_previous: false,
|
||||||
|
_changed: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createLifecycleHub = (options: Options, structureSetup: StructureSetup): LifecycleHubInstance => {
|
||||||
|
const { _host, _viewport, _content } = structureSetup._targetObj;
|
||||||
|
const environment: Environment = getEnvironment();
|
||||||
|
const lifecycles: LifecycleUpdateFunction[] = [];
|
||||||
|
const instance: LifecycleHub = {
|
||||||
|
_options: options,
|
||||||
|
_structureSetup: structureSetup,
|
||||||
|
};
|
||||||
|
|
||||||
|
// push(lifecycles, createStructureLifecycle(instance));
|
||||||
|
push(lifecycles, createOverflowLifecycle(instance));
|
||||||
|
|
||||||
|
const runLifecycles = (updateHints?: Partial<LifecycleUpdateHints> | null, changedOptions?: OptionsValidated<Options> | null, force?: boolean) => {
|
||||||
|
let { _directionIsRTL, _heightIntrinsic, _sizeChanged = force || false, _hostMutation = force || false, _contentMutation = force || false } =
|
||||||
|
updateHints || {};
|
||||||
|
const finalDirectionIsRTL =
|
||||||
|
_directionIsRTL || (sizeObserver ? sizeObserver._getCurrentCacheValues(force)._directionIsRTL : directionIsRTLCacheValuesFallback);
|
||||||
|
const finalHeightIntrinsic =
|
||||||
|
_heightIntrinsic || (trinsicObserver ? trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : heightIntrinsicCacheValuesFallback);
|
||||||
|
|
||||||
|
each(lifecycles, (lifecycle) => {
|
||||||
|
const { _sizeChanged: adaptiveSizeChanged, _hostMutation: adaptiveHostMutation, _contentMutation: adaptiveContentMutation } = lifecycle(
|
||||||
|
{
|
||||||
|
_directionIsRTL: finalDirectionIsRTL,
|
||||||
|
_heightIntrinsic: finalHeightIntrinsic,
|
||||||
|
_sizeChanged,
|
||||||
|
_hostMutation,
|
||||||
|
_contentMutation,
|
||||||
|
},
|
||||||
|
changedOptions,
|
||||||
|
force
|
||||||
|
);
|
||||||
|
|
||||||
|
_sizeChanged = adaptiveSizeChanged || _sizeChanged;
|
||||||
|
_hostMutation = adaptiveHostMutation || _hostMutation;
|
||||||
|
_contentMutation = adaptiveContentMutation || _contentMutation;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSizeChanged = (directionIsRTL?: CacheValues<boolean>) => {
|
||||||
|
const sizeChanged = !directionIsRTL;
|
||||||
|
runLifecycles({
|
||||||
|
_directionIsRTL: directionIsRTL,
|
||||||
|
_sizeChanged: sizeChanged,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onTrinsicChanged = (heightIntrinsic: CacheValues<boolean>) => {
|
||||||
|
runLifecycles({
|
||||||
|
_heightIntrinsic: heightIntrinsic,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onHostMutation = () => {
|
||||||
|
// TODO: rAF only here because IE
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
runLifecycles({
|
||||||
|
_hostMutation: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onContentMutation = () => {
|
||||||
|
// TODO: rAF only here because IE
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
runLifecycles({
|
||||||
|
_contentMutation: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const sizeObserver = createSizeObserver(_host, onSizeChanged, { _appear: true, _direction: true });
|
||||||
|
const trinsicObserver = createTrinsicObserver(_host, onTrinsicChanged);
|
||||||
|
const hostMutationObserver = createDOMObserver(_host, onHostMutation, {
|
||||||
|
_styleChangingAttributes: attrs,
|
||||||
|
_attributes: attrs,
|
||||||
|
});
|
||||||
|
const contentMutationObserver = createDOMObserver(_content || _viewport, onContentMutation, {
|
||||||
|
_observeContent: true,
|
||||||
|
_styleChangingAttributes: attrs,
|
||||||
|
_attributes: attrs,
|
||||||
|
_eventContentChange: options!.updating!.elementEvents as [string, string][],
|
||||||
|
/*
|
||||||
|
_nestedTargetSelector: hostSelector,
|
||||||
|
_ignoreContentChange: (mutation, isNestedTarget) => {
|
||||||
|
const { target, attributeName } = mutation;
|
||||||
|
return isNestedTarget ? false : attributeName ? liesBetween(target as Element, hostSelector, '.content') : false;
|
||||||
|
},
|
||||||
|
_ignoreTargetAttrChange: (target, attrName, oldValue, newValue) => {
|
||||||
|
if (attrName === 'class' && oldValue && newValue) {
|
||||||
|
const diff = diffClass(oldValue, newValue);
|
||||||
|
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
|
||||||
|
return ignore;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateAll = (changedOptions?: OptionsValidated<Options> | null, force?: boolean) => {
|
||||||
|
runLifecycles(null, changedOptions, force);
|
||||||
|
};
|
||||||
|
const envUpdateListener = updateAll.bind(null, null, true);
|
||||||
|
environment._addListener(envUpdateListener);
|
||||||
|
|
||||||
|
console.log('flexboxglue', environment._flexboxGlue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
_update: updateAll,
|
||||||
|
_destroy() {
|
||||||
|
environment._removeListener(envUpdateListener);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { CacheValues, OptionsValidated, hasOwnProperty } from 'support';
|
||||||
|
import { Options } from 'options';
|
||||||
|
import { LifecycleHub } from 'lifecycles/lifecycleHub';
|
||||||
|
|
||||||
|
export interface LifecycleAdaptiveUpdateHints {
|
||||||
|
_sizeChanged: boolean;
|
||||||
|
_hostMutation: boolean;
|
||||||
|
_contentMutation: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LifecycleUpdateHints extends LifecycleAdaptiveUpdateHints {
|
||||||
|
_directionIsRTL: CacheValues<boolean>;
|
||||||
|
_heightIntrinsic: CacheValues<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LifecycleUpdateFunction = (
|
||||||
|
updateHints: LifecycleUpdateHints,
|
||||||
|
changedOptions?: OptionsValidated<Options> | null,
|
||||||
|
force?: boolean
|
||||||
|
) => Partial<LifecycleAdaptiveUpdateHints>;
|
||||||
|
|
||||||
|
export interface LifecycleOptionInfo<T> {
|
||||||
|
readonly _value: T;
|
||||||
|
_changed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
||||||
|
|
||||||
|
const getPropByPath = <T>(obj: any, path: string): T =>
|
||||||
|
obj && path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a update function for a lifecycle.
|
||||||
|
* @param lifecycleHub The LifecycleHub which is managing this lifecylce.
|
||||||
|
* @param updateFunction The update function where cache and options updates are handled. Has two arguments which are the changedOptions and the changedCache objects.
|
||||||
|
*/
|
||||||
|
export const createLifecycleUpdateFunction = (
|
||||||
|
lifecycleHub: LifecycleHub,
|
||||||
|
updateFunction: (
|
||||||
|
force: boolean,
|
||||||
|
updateHints: LifecycleUpdateHints,
|
||||||
|
checkOption: LifecycleCheckOption
|
||||||
|
) => Partial<LifecycleAdaptiveUpdateHints> | void
|
||||||
|
): LifecycleUpdateFunction => {
|
||||||
|
return (updateHints: LifecycleUpdateHints, changedOptions?: OptionsValidated<Options> | null, force?: boolean) => {
|
||||||
|
const checkOption: LifecycleCheckOption = (path) => ({
|
||||||
|
_value: getPropByPath(lifecycleHub._options, path),
|
||||||
|
_changed: force || getPropByPath(changedOptions, path) !== undefined,
|
||||||
|
});
|
||||||
|
return updateFunction(!!force, updateHints, checkOption) || {};
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import { createCache, WH, XY, equalXY, style, scrollSize, offsetSize, CacheValues, equalWH, scrollLeft, scrollTop } from 'support';
|
||||||
|
import { createLifecycleUpdateFunction, LifecycleUpdateFunction } from 'lifecycles/lifecycleUpdateFunction';
|
||||||
|
import { LifecycleHub } from 'lifecycles/lifecycleHub';
|
||||||
|
import { getEnvironment } from 'environment';
|
||||||
|
import { OverflowBehavior } from 'options';
|
||||||
|
import { PlainObject } from 'typings';
|
||||||
|
|
||||||
|
const overlaidScrollbarsHideOffset = 42;
|
||||||
|
const overlaidScrollbarsHideBorderStyle = `${overlaidScrollbarsHideOffset}px solid transparent`;
|
||||||
|
interface OverflowAmountCacheContext {
|
||||||
|
_contentScrollSize: WH<number>;
|
||||||
|
_viewportSize: WH<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createOverflowLifecycle = (lifecycleHub: LifecycleHub): LifecycleUpdateFunction => {
|
||||||
|
const { _host, _padding, _viewport, _content, _contentArrange } = lifecycleHub._structureSetup._targetObj;
|
||||||
|
const { _update: updateContentScrollSizeCache, _current: getCurrentContentScrollSizeCache } = createCache<WH<number>>(
|
||||||
|
() => scrollSize(_content || _viewport),
|
||||||
|
{ _equal: equalWH }
|
||||||
|
);
|
||||||
|
const { _update: updateOverflowAmountCache, _current: getCurrentOverflowAmountCache } = createCache<XY<number>, OverflowAmountCacheContext>(
|
||||||
|
(ctx) => ({
|
||||||
|
x: Math.max(0, Math.round((ctx._contentScrollSize.w - ctx._viewportSize.w) * 100) / 100),
|
||||||
|
y: Math.max(0, Math.round((ctx._contentScrollSize.h - ctx._viewportSize.h) * 100) / 100),
|
||||||
|
}),
|
||||||
|
{ _equal: equalXY }
|
||||||
|
);
|
||||||
|
|
||||||
|
const setViewportOverflowStyle = (horizontal: boolean, amount: number, behavior: OverflowBehavior, styleObj: PlainObject) => {
|
||||||
|
const overflowKey = horizontal ? 'overflowX' : 'overflowY';
|
||||||
|
//const scrollMaxKey = horizontal ? 'scrollLeftMax' : 'scrollTopMax';
|
||||||
|
const behaviorIsScroll = behavior === 'scroll';
|
||||||
|
const behaviorIsVisibleScroll = behavior === 'visible-scroll';
|
||||||
|
const hideOverflow = behaviorIsScroll || behavior === 'hidden';
|
||||||
|
//const scrollMax = _viewport[scrollMaxKey];
|
||||||
|
//const scrollMaxOverflow = isNumber(scrollMax) ? scrollMax > 0 : true;
|
||||||
|
const applyStyle = amount > 0 && hideOverflow;
|
||||||
|
|
||||||
|
if (applyStyle) {
|
||||||
|
styleObj[overflowKey] = behavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_visible: !applyStyle,
|
||||||
|
_behavior: behaviorIsVisibleScroll ? 'scroll' : 'hidden',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideNativeScrollbars = (
|
||||||
|
contentScrollSize: WH<number>,
|
||||||
|
adjustFlexboxGlue: boolean,
|
||||||
|
directionIsRTL: boolean,
|
||||||
|
heightIntrinsic: boolean,
|
||||||
|
viewportStyleObj: PlainObject,
|
||||||
|
contentStyleObj: PlainObject
|
||||||
|
) => {
|
||||||
|
const { _nativeScrollbarSize, _nativeScrollbarIsOverlaid, _nativeScrollbarStyling } = getEnvironment();
|
||||||
|
const scrollX = viewportStyleObj.overflowX === 'scroll';
|
||||||
|
const scrollY = viewportStyleObj.overflowY === 'scroll';
|
||||||
|
const horizontalMarginKey = directionIsRTL ? 'marginLeft' : 'marginRight';
|
||||||
|
const horizontalBorderKey = directionIsRTL ? 'borderLeft' : 'borderRight';
|
||||||
|
const scrollXY = scrollY && scrollX;
|
||||||
|
const hideOffset = _content ? overlaidScrollbarsHideOffset : 0;
|
||||||
|
const offset = {
|
||||||
|
x: _nativeScrollbarIsOverlaid.x ? hideOffset : _nativeScrollbarSize.x,
|
||||||
|
y: _nativeScrollbarIsOverlaid.y ? hideOffset : _nativeScrollbarSize.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!_nativeScrollbarStyling) {
|
||||||
|
if (scrollX) {
|
||||||
|
viewportStyleObj.marginBottom = `-${offset.x}px`;
|
||||||
|
|
||||||
|
if (_nativeScrollbarIsOverlaid.x && hideOffset) {
|
||||||
|
contentStyleObj.borderBottom = overlaidScrollbarsHideBorderStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (scrollY) {
|
||||||
|
viewportStyleObj.maxWidth = `calc(100% + ${offset.y}px)`;
|
||||||
|
viewportStyleObj[horizontalMarginKey] = `-${offset.y}px`;
|
||||||
|
|
||||||
|
if (_nativeScrollbarIsOverlaid.y && hideOffset) {
|
||||||
|
contentStyleObj[horizontalBorderKey] = overlaidScrollbarsHideBorderStyle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hideOffset && (offset.x || offset.y)) {
|
||||||
|
style(_contentArrange, {
|
||||||
|
width: scrollXY ? `${hideOffset + contentScrollSize.w}px` : '',
|
||||||
|
height: scrollXY ? `${hideOffset + contentScrollSize.h}px` : '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (adjustFlexboxGlue) {
|
||||||
|
const offsetLeft = scrollLeft(_viewport);
|
||||||
|
const offsetTop = scrollTop(_viewport);
|
||||||
|
|
||||||
|
style(_viewport, {
|
||||||
|
maxHeight: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (heightIntrinsic) {
|
||||||
|
style(_viewport, {
|
||||||
|
maxHeight: `${_host.clientHeight + (scrollX ? offset.x : 0)}px`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollLeft(_viewport, offsetLeft);
|
||||||
|
scrollTop(_viewport, offsetTop);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return createLifecycleUpdateFunction(lifecycleHub, (force, updateHints, checkOption) => {
|
||||||
|
const { _directionIsRTL, _heightIntrinsic, _sizeChanged, _hostMutation, _contentMutation } = updateHints;
|
||||||
|
const { _flexboxGlue, _nativeScrollbarStyling } = getEnvironment();
|
||||||
|
const adjustFlexboxGlue = !_flexboxGlue && (_sizeChanged || _contentMutation || _hostMutation);
|
||||||
|
let overflowAmuntCache: CacheValues<XY<number>> = getCurrentOverflowAmountCache();
|
||||||
|
let contentScrollSizeCache: CacheValues<WH<number>> = getCurrentContentScrollSizeCache();
|
||||||
|
|
||||||
|
if (_sizeChanged || _contentMutation) {
|
||||||
|
const viewportOffsetSize = offsetSize(_padding);
|
||||||
|
const contentClientSize = offsetSize(_content || _viewport);
|
||||||
|
const contentArrangeOffsetSize = offsetSize(_contentArrange);
|
||||||
|
|
||||||
|
contentScrollSizeCache = updateContentScrollSizeCache(force);
|
||||||
|
const { _value: contentScrollSize } = contentScrollSizeCache;
|
||||||
|
overflowAmuntCache = updateOverflowAmountCache(force, {
|
||||||
|
_contentScrollSize: {
|
||||||
|
w: Math.max(contentScrollSize!.w, contentArrangeOffsetSize.w),
|
||||||
|
h: Math.max(contentScrollSize!.h, contentArrangeOffsetSize.h),
|
||||||
|
},
|
||||||
|
_viewportSize: {
|
||||||
|
w: viewportOffsetSize.w + Math.max(0, contentClientSize.w - contentScrollSize!.w),
|
||||||
|
h: viewportOffsetSize.h + Math.max(0, contentClientSize.h - contentScrollSize!.h),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { _value: directionIsRTL, _changed: directionChanged } = _directionIsRTL;
|
||||||
|
const { _value: contentScrollSize, _changed: contentScrollSizeChanged } = contentScrollSizeCache;
|
||||||
|
const { _value: overflowAmount, _changed: overflowAmountChanged } = overflowAmuntCache;
|
||||||
|
const { _value: overflow, _changed: overflowChanged } = checkOption<{
|
||||||
|
x: OverflowBehavior;
|
||||||
|
y: OverflowBehavior;
|
||||||
|
}>('overflow');
|
||||||
|
const adjustDirection = directionChanged && !_nativeScrollbarStyling;
|
||||||
|
|
||||||
|
if (contentScrollSizeChanged || overflowAmountChanged || overflowChanged || adjustDirection || adjustFlexboxGlue) {
|
||||||
|
const viewportStyle: PlainObject = {
|
||||||
|
overflowY: '',
|
||||||
|
overflowX: '',
|
||||||
|
marginTop: '',
|
||||||
|
marginRight: '',
|
||||||
|
marginBottom: '',
|
||||||
|
marginLeft: '',
|
||||||
|
maxWidth: '',
|
||||||
|
};
|
||||||
|
const contentStyle: PlainObject = {
|
||||||
|
borderTop: '',
|
||||||
|
borderRight: '',
|
||||||
|
borderBottom: '',
|
||||||
|
borderLeft: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const { _visible: xVisible, _behavior: xVisibleBehavior } = setViewportOverflowStyle(true, overflowAmount!.x, overflow.x, viewportStyle);
|
||||||
|
const { _visible: yVisible, _behavior: yVisibleBehavior } = setViewportOverflowStyle(false, overflowAmount!.y, overflow.y, viewportStyle);
|
||||||
|
|
||||||
|
if (xVisible && !yVisible) {
|
||||||
|
viewportStyle.overflowX = xVisibleBehavior;
|
||||||
|
}
|
||||||
|
if (yVisible && !xVisible) {
|
||||||
|
viewportStyle.overflowY = yVisibleBehavior;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideNativeScrollbars(contentScrollSize!, adjustFlexboxGlue, directionIsRTL!, !!_heightIntrinsic._value, viewportStyle, contentStyle);
|
||||||
|
|
||||||
|
// TODO: enlargen viewport if div too small for firefox scrollbar hiding behavior
|
||||||
|
// TODO: Test without content
|
||||||
|
// TODO: Test without padding
|
||||||
|
|
||||||
|
style(_viewport, viewportStyle);
|
||||||
|
style(_content, contentStyle);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Cache,
|
CacheValues,
|
||||||
cssProperty,
|
cssProperty,
|
||||||
runEach,
|
runEach,
|
||||||
createCache,
|
createCache,
|
||||||
@@ -9,43 +9,19 @@ import {
|
|||||||
XY,
|
XY,
|
||||||
equalTRBL,
|
equalTRBL,
|
||||||
equalXY,
|
equalXY,
|
||||||
optionsTemplateTypes as oTypes,
|
|
||||||
OptionsTemplateValue,
|
|
||||||
style,
|
style,
|
||||||
OptionsWithOptionsTemplate,
|
|
||||||
scrollSize,
|
scrollSize,
|
||||||
offsetSize,
|
offsetSize,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import { PreparedOSTargetObject } from 'setups/structureSetup';
|
import { createLifecycleUpdateFunction, Lifecycle } from 'lifecycles/lifecycleUpdateFunction';
|
||||||
import { createLifecycleBase, Lifecycle } from 'lifecycles/lifecycleBase';
|
import { LifecycleHub } from 'lifecycles/lifecycleHub';
|
||||||
import { getEnvironment, Environment } from 'environment';
|
import { getEnvironment, Environment } from 'environment';
|
||||||
|
|
||||||
export type OverflowBehavior = 'hidden' | 'scroll' | 'visible-hidden' | 'visible-scroll';
|
|
||||||
export interface StructureLifecycleOptions {
|
|
||||||
paddingAbsolute: boolean;
|
|
||||||
overflowBehavior?: {
|
|
||||||
x?: OverflowBehavior;
|
|
||||||
y?: OverflowBehavior;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const overflowBehaviorAllowedValues: OptionsTemplateValue<OverflowBehavior> = 'visible-hidden visible-scroll scroll hidden';
|
|
||||||
const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<StructureLifecycleOptions>> = {
|
|
||||||
paddingAbsolute: [false, oTypes.boolean],
|
|
||||||
overflowBehavior: {
|
|
||||||
x: ['scroll', overflowBehaviorAllowedValues],
|
|
||||||
y: ['scroll', overflowBehaviorAllowedValues],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const cssMarginEnd = cssProperty('margin-inline-end');
|
const cssMarginEnd = cssProperty('margin-inline-end');
|
||||||
const cssBorderEnd = cssProperty('border-inline-end');
|
const cssBorderEnd = cssProperty('border-inline-end');
|
||||||
|
|
||||||
export const createStructureLifecycle = (
|
export const createStructureLifecycle = (lifecycleHub: LifecycleHub): Lifecycle => {
|
||||||
target: PreparedOSTargetObject,
|
const { _host, _padding, _viewport, _content } = lifecycleHub._structureSetup._targetObj;
|
||||||
initialOptions?: StructureLifecycleOptions
|
|
||||||
): Lifecycle<StructureLifecycleOptions> => {
|
|
||||||
const { _host, _padding, _viewport, _content } = target;
|
|
||||||
const destructFns: (() => any)[] = [];
|
const destructFns: (() => any)[] = [];
|
||||||
const env: Environment = getEnvironment();
|
const env: Environment = getEnvironment();
|
||||||
const scrollbarsOverlaid = env._nativeScrollbarIsOverlaid;
|
const scrollbarsOverlaid = env._nativeScrollbarIsOverlaid;
|
||||||
@@ -54,8 +30,8 @@ export const createStructureLifecycle = (
|
|||||||
// direction change is only needed to update scrollbar hiding, therefore its not needed if css can do it, scrollbars are invisible or overlaid on y axis
|
// direction change is only needed to update scrollbar hiding, therefore its not needed if css can do it, scrollbars are invisible or overlaid on y axis
|
||||||
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
||||||
|
|
||||||
const updatePaddingCache = createCache(() => topRightBottomLeft(_host, 'padding'), { _equal: equalTRBL });
|
const { _update: updatePaddingCache } = createCache(() => topRightBottomLeft(_host, 'padding'), { _equal: equalTRBL });
|
||||||
const updateOverflowAmountCache = createCache<XY<number>, { _contentScrollSize: WH<number>; _viewportSize: WH<number> }>(
|
const { _update: updateOverflowAmountCache } = createCache<XY<number>, { _contentScrollSize: WH<number>; _viewportSize: WH<number> }>(
|
||||||
(ctx) => ({
|
(ctx) => ({
|
||||||
x: Math.max(0, Math.round((ctx!._contentScrollSize.w - ctx!._viewportSize.w) * 100) / 100),
|
x: Math.max(0, Math.round((ctx!._contentScrollSize.w - ctx!._viewportSize.w) * 100) / 100),
|
||||||
y: Math.max(0, Math.round((ctx!._contentScrollSize.h - ctx!._viewportSize.h) * 100) / 100),
|
y: Math.max(0, Math.round((ctx!._contentScrollSize.h - ctx!._viewportSize.h) * 100) / 100),
|
||||||
@@ -63,7 +39,7 @@ export const createStructureLifecycle = (
|
|||||||
{ _equal: equalXY }
|
{ _equal: equalXY }
|
||||||
);
|
);
|
||||||
|
|
||||||
const { _options, _update } = createLifecycleBase<StructureLifecycleOptions>(defaultOptionsWithTemplate, initialOptions, (force, checkOption) => {
|
const _update = createLifecycleUpdateFunction(lifecycleHub, (force, checkOption) => {
|
||||||
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
|
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
|
||||||
const { _value: padding, _changed: paddingChanged } = updatePaddingCache(force);
|
const { _value: padding, _changed: paddingChanged } = updatePaddingCache(force);
|
||||||
|
|
||||||
@@ -148,15 +124,14 @@ export const createStructureLifecycle = (
|
|||||||
const onSizeChanged = () => {
|
const onSizeChanged = () => {
|
||||||
_update();
|
_update();
|
||||||
};
|
};
|
||||||
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => {
|
const onTrinsicChanged = (heightIntrinsic: CacheValues<boolean>) => {
|
||||||
const { _changed, _value } = heightIntrinsicCache;
|
const { _changed, _value } = heightIntrinsic;
|
||||||
if (_changed) {
|
if (_changed) {
|
||||||
style(_content, { height: _value ? 'auto' : '100%' });
|
style(_content, { height: _value ? 'auto' : '100%' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_options,
|
|
||||||
_update,
|
_update,
|
||||||
_onSizeChanged: onSizeChanged,
|
_onSizeChanged: onSizeChanged,
|
||||||
_onTrinsicChanged: onTrinsicChanged,
|
_onTrinsicChanged: onTrinsicChanged,
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export const createDOMObserver = (
|
|||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
callback([], false, true);
|
callback([], false, true);
|
||||||
}
|
}
|
||||||
}, 80)
|
}, 84)
|
||||||
);
|
);
|
||||||
|
|
||||||
// MutationObserver
|
// MutationObserver
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Cache,
|
Cache,
|
||||||
|
CacheValues,
|
||||||
createCache,
|
createCache,
|
||||||
createDOM,
|
createDOM,
|
||||||
style,
|
style,
|
||||||
@@ -34,6 +35,21 @@ import {
|
|||||||
classNameSizeObserverListenerItemFinal,
|
classNameSizeObserverListenerItemFinal,
|
||||||
} from 'classnames';
|
} from 'classnames';
|
||||||
|
|
||||||
|
interface SizeObserverEntry {
|
||||||
|
contentRect: DOMRectReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SizeObserverOptions = { _direction?: boolean; _appear?: boolean };
|
||||||
|
|
||||||
|
export interface SizeObserver {
|
||||||
|
_destroy(): void;
|
||||||
|
_getCurrentCacheValues(
|
||||||
|
force?: boolean
|
||||||
|
): {
|
||||||
|
_directionIsRTL: CacheValues<boolean>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const animationStartEventName = 'animationstart';
|
const animationStartEventName = 'animationstart';
|
||||||
const scrollEventName = 'scroll';
|
const scrollEventName = 'scroll';
|
||||||
const scrollAmount = 3333333;
|
const scrollAmount = 3333333;
|
||||||
@@ -51,21 +67,17 @@ const directionIsRTL = (elm: HTMLElement): boolean => {
|
|||||||
};
|
};
|
||||||
const domRectHasDimensions = (rect?: DOMRectReadOnly) => rect && (rect.height || rect.width);
|
const domRectHasDimensions = (rect?: DOMRectReadOnly) => rect && (rect.height || rect.width);
|
||||||
|
|
||||||
interface SizeObserverEntry {
|
|
||||||
contentRect: DOMRectReadOnly;
|
|
||||||
}
|
|
||||||
export type SizeObserverOptions = { _direction?: boolean; _appear?: boolean };
|
|
||||||
export const createSizeObserver = (
|
export const createSizeObserver = (
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
onSizeChangedCallback: (directionIsRTLCache?: Cache<boolean>) => any,
|
onSizeChangedCallback: (directionIsRTLCache?: CacheValues<boolean>) => any,
|
||||||
options?: SizeObserverOptions
|
options?: SizeObserverOptions
|
||||||
): (() => void) => {
|
): SizeObserver => {
|
||||||
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } = options || {};
|
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } = options || {};
|
||||||
const rtlScrollBehavior = getEnvironment()._rtlScrollBehavior;
|
const { _rtlScrollBehavior: rtlScrollBehavior } = getEnvironment();
|
||||||
const baseElements = createDOM(`<div class="${classNameSizeObserver}"><div class="${classNameSizeObserverListener}"></div></div>`);
|
const baseElements = createDOM(`<div class="${classNameSizeObserver}"><div class="${classNameSizeObserverListener}"></div></div>`);
|
||||||
const sizeObserver = baseElements[0] as HTMLElement;
|
const sizeObserver = baseElements[0] as HTMLElement;
|
||||||
const listenerElement = sizeObserver.firstChild as HTMLElement;
|
const listenerElement = sizeObserver.firstChild as HTMLElement;
|
||||||
const updateResizeObserverContentRectCache = createCache<DOMRectReadOnly, DOMRectReadOnly>(0, {
|
const { _update: updateResizeObserverContentRectCache } = createCache<DOMRectReadOnly, DOMRectReadOnly>(0, {
|
||||||
_alwaysUpdateValues: true,
|
_alwaysUpdateValues: true,
|
||||||
_equal: (currVal, newVal) =>
|
_equal: (currVal, newVal) =>
|
||||||
!(
|
!(
|
||||||
@@ -74,8 +86,8 @@ export const createSizeObserver = (
|
|||||||
(!domRectHasDimensions(currVal) && domRectHasDimensions(newVal))
|
(!domRectHasDimensions(currVal) && domRectHasDimensions(newVal))
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const onSizeChangedCallbackProxy = (sizeChangedContext?: Cache<boolean> | SizeObserverEntry[] | Event) => {
|
const onSizeChangedCallbackProxy = (sizeChangedContext?: CacheValues<boolean> | SizeObserverEntry[] | Event) => {
|
||||||
const hasDirectionCache = sizeChangedContext && isBoolean((sizeChangedContext as Cache<boolean>)._value);
|
const hasDirectionCache = sizeChangedContext && isBoolean((sizeChangedContext as CacheValues<boolean>)._value);
|
||||||
|
|
||||||
let skip = false;
|
let skip = false;
|
||||||
let doDirectionScroll = true; // always true if sizeChangedContext is Event (appear callback or RO. Polyfill)
|
let doDirectionScroll = true; // always true if sizeChangedContext is Event (appear callback or RO. Polyfill)
|
||||||
@@ -88,21 +100,22 @@ export const createSizeObserver = (
|
|||||||
}
|
}
|
||||||
// else if its triggered with DirectionCache
|
// else if its triggered with DirectionCache
|
||||||
else if (hasDirectionCache) {
|
else if (hasDirectionCache) {
|
||||||
doDirectionScroll = (sizeChangedContext as Cache<boolean>)._changed; // direction scroll when DirectionCache changed, false toherwise
|
doDirectionScroll = (sizeChangedContext as CacheValues<boolean>)._changed; // direction scroll when DirectionCache changed, false toherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
if (observeDirectionChange) {
|
if (observeDirectionChange) {
|
||||||
const rtl = hasDirectionCache ? (sizeChangedContext as Cache<boolean>)._value : directionIsRTL(sizeObserver);
|
const rtl = hasDirectionCache ? (sizeChangedContext as CacheValues<boolean>)._value : directionIsRTL(sizeObserver);
|
||||||
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
|
||||||
scrollTop(sizeObserver, scrollAmount);
|
scrollTop(sizeObserver, scrollAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skip) {
|
if (!skip) {
|
||||||
onSizeChangedCallback(hasDirectionCache ? (sizeChangedContext as Cache<boolean>) : undefined);
|
onSizeChangedCallback(hasDirectionCache ? (sizeChangedContext as CacheValues<boolean>) : undefined);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const offListeners: (() => void)[] = [];
|
const offListeners: (() => void)[] = [];
|
||||||
let appearCallback: ((...args: any) => any) | false = observeAppearChange ? onSizeChangedCallbackProxy : false;
|
let appearCallback: ((...args: any) => any) | false = observeAppearChange ? onSizeChangedCallbackProxy : false;
|
||||||
|
let directionIsRTLCache: Cache<boolean> | undefined;
|
||||||
|
|
||||||
if (ResizeObserverConstructor) {
|
if (ResizeObserverConstructor) {
|
||||||
const resizeObserverInstance = new ResizeObserverConstructor(onSizeChangedCallbackProxy);
|
const resizeObserverInstance = new ResizeObserverConstructor(onSizeChangedCallbackProxy);
|
||||||
@@ -169,19 +182,20 @@ export const createSizeObserver = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (observeDirectionChange) {
|
if (observeDirectionChange) {
|
||||||
const updateDirectionIsRTLCache = createCache(() => directionIsRTL(sizeObserver));
|
directionIsRTLCache = createCache(() => directionIsRTL(sizeObserver));
|
||||||
|
const { _update: updateDirectionIsRTLCache } = directionIsRTLCache;
|
||||||
push(
|
push(
|
||||||
offListeners,
|
offListeners,
|
||||||
on(sizeObserver, scrollEventName, (event: Event) => {
|
on(sizeObserver, scrollEventName, (event: Event) => {
|
||||||
const directionIsRTLCache = updateDirectionIsRTLCache();
|
const directionIsRTLCacheValues = updateDirectionIsRTLCache();
|
||||||
const { _value, _changed } = directionIsRTLCache;
|
const { _value, _changed } = directionIsRTLCacheValues;
|
||||||
if (_changed) {
|
if (_changed) {
|
||||||
if (_value) {
|
if (_value) {
|
||||||
style(listenerElement, { left: 'auto', right: 0 });
|
style(listenerElement, { left: 'auto', right: 0 });
|
||||||
} else {
|
} else {
|
||||||
style(listenerElement, { left: 0, right: 'auto' });
|
style(listenerElement, { left: 0, right: 'auto' });
|
||||||
}
|
}
|
||||||
onSizeChangedCallbackProxy(directionIsRTLCache);
|
onSizeChangedCallbackProxy(directionIsRTLCacheValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
preventDefault(event);
|
preventDefault(event);
|
||||||
@@ -205,8 +219,21 @@ export const createSizeObserver = (
|
|||||||
|
|
||||||
prependChildren(target, sizeObserver);
|
prependChildren(target, sizeObserver);
|
||||||
|
|
||||||
return () => {
|
return {
|
||||||
runEach(offListeners);
|
_destroy() {
|
||||||
removeElements(sizeObserver);
|
runEach(offListeners);
|
||||||
|
removeElements(sizeObserver);
|
||||||
|
},
|
||||||
|
_getCurrentCacheValues(force?: boolean) {
|
||||||
|
return {
|
||||||
|
_directionIsRTL: directionIsRTLCache
|
||||||
|
? directionIsRTLCache._current(force)
|
||||||
|
: {
|
||||||
|
_value: false,
|
||||||
|
_previous: false,
|
||||||
|
_changed: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
WH,
|
WH,
|
||||||
Cache,
|
CacheValues,
|
||||||
createDOM,
|
createDOM,
|
||||||
offsetSize,
|
offsetSize,
|
||||||
runEach,
|
runEach,
|
||||||
@@ -13,13 +13,25 @@ import {
|
|||||||
import { createSizeObserver } from 'observers/sizeObserver';
|
import { createSizeObserver } from 'observers/sizeObserver';
|
||||||
import { classNameTrinsicObserver } from 'classnames';
|
import { classNameTrinsicObserver } from 'classnames';
|
||||||
|
|
||||||
|
export interface TrinsicObserver {
|
||||||
|
_destroy(): void;
|
||||||
|
_getCurrentCacheValues(
|
||||||
|
force?: boolean
|
||||||
|
): {
|
||||||
|
_heightIntrinsic: CacheValues<boolean>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const createTrinsicObserver = (
|
export const createTrinsicObserver = (
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
onTrinsicChangedCallback: (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => any
|
onTrinsicChangedCallback: (heightIntrinsic: CacheValues<boolean>) => any
|
||||||
): (() => void) => {
|
): TrinsicObserver => {
|
||||||
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0] as HTMLElement;
|
const trinsicObserver = createDOM(`<div class="${classNameTrinsicObserver}"></div>`)[0] as HTMLElement;
|
||||||
const offListeners: (() => void)[] = [];
|
const offListeners: (() => void)[] = [];
|
||||||
const updateHeightIntrinsicCache = createCache<boolean, IntersectionObserverEntry | WH<number>>(
|
const { _update: updateHeightIntrinsicCache, _current: getCurrentHeightIntrinsicCache } = createCache<
|
||||||
|
boolean,
|
||||||
|
IntersectionObserverEntry | WH<number>
|
||||||
|
>(
|
||||||
(ioEntryOrSize: IntersectionObserverEntry | WH<number>) =>
|
(ioEntryOrSize: IntersectionObserverEntry | WH<number>) =>
|
||||||
(ioEntryOrSize! as WH<number>).h === 0 ||
|
(ioEntryOrSize! as WH<number>).h === 0 ||
|
||||||
(ioEntryOrSize! as IntersectionObserverEntry).isIntersecting ||
|
(ioEntryOrSize! as IntersectionObserverEntry).isIntersecting ||
|
||||||
@@ -35,10 +47,10 @@ export const createTrinsicObserver = (
|
|||||||
if (entries && entries.length > 0) {
|
if (entries && entries.length > 0) {
|
||||||
const last = entries.pop();
|
const last = entries.pop();
|
||||||
if (last) {
|
if (last) {
|
||||||
const heightIntrinsicCache = updateHeightIntrinsicCache(0, last);
|
const heightIntrinsic = updateHeightIntrinsicCache(0, last);
|
||||||
|
|
||||||
if (heightIntrinsicCache._changed) {
|
if (heightIntrinsic._changed) {
|
||||||
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
onTrinsicChangedCallback(heightIntrinsic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,16 +67,23 @@ export const createTrinsicObserver = (
|
|||||||
const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
|
const heightIntrinsicCache = updateHeightIntrinsicCache(0, newSize);
|
||||||
|
|
||||||
if (heightIntrinsicCache._changed) {
|
if (heightIntrinsicCache._changed) {
|
||||||
onTrinsicChangedCallback(false, heightIntrinsicCache);
|
onTrinsicChangedCallback(heightIntrinsicCache);
|
||||||
}
|
}
|
||||||
})
|
})._destroy
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prependChildren(target, trinsicObserver);
|
prependChildren(target, trinsicObserver);
|
||||||
|
|
||||||
return () => {
|
return {
|
||||||
runEach(offListeners);
|
_destroy() {
|
||||||
removeElements(trinsicObserver);
|
runEach(offListeners);
|
||||||
|
removeElements(trinsicObserver);
|
||||||
|
},
|
||||||
|
_getCurrentCacheValues(force?: boolean) {
|
||||||
|
return {
|
||||||
|
_heightIntrinsic: getCurrentHeightIntrinsicCache(force),
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,20 +25,15 @@ export type SizeChangedCallback = (this: any, args?: SizeChangedArgs) => void;
|
|||||||
export type UpdatedCallback = (this: any, args?: UpdatedArgs) => void;
|
export type UpdatedCallback = (this: any, args?: UpdatedArgs) => void;
|
||||||
|
|
||||||
export interface Options {
|
export interface Options {
|
||||||
className?: string | null;
|
|
||||||
resize?: ResizeBehavior;
|
resize?: ResizeBehavior;
|
||||||
sizeAutoCapable?: boolean;
|
|
||||||
clipAlways?: boolean;
|
|
||||||
normalizeRTL?: boolean;
|
|
||||||
paddingAbsolute?: boolean;
|
paddingAbsolute?: boolean;
|
||||||
autoUpdate?: boolean | null;
|
updating?: {
|
||||||
autoUpdateInterval?: number;
|
elementEvents?: ReadonlyArray<[string, string]> | null;
|
||||||
updateOnLoad?: string | ReadonlyArray<string> | null;
|
contentMutationDebounce?: number;
|
||||||
nativeScrollbarsOverlaid?: {
|
hostMutationDebounce?: number;
|
||||||
showNativeScrollbars?: boolean;
|
resizeDebounce?: number;
|
||||||
initialize?: boolean;
|
|
||||||
};
|
};
|
||||||
overflowBehavior?: {
|
overflow?: {
|
||||||
x?: OverflowBehavior;
|
x?: OverflowBehavior;
|
||||||
y?: OverflowBehavior;
|
y?: OverflowBehavior;
|
||||||
};
|
};
|
||||||
@@ -46,16 +41,20 @@ export interface Options {
|
|||||||
visibility?: VisibilityBehavior;
|
visibility?: VisibilityBehavior;
|
||||||
autoHide?: AutoHideBehavior;
|
autoHide?: AutoHideBehavior;
|
||||||
autoHideDelay?: number;
|
autoHideDelay?: number;
|
||||||
dragScrolling?: boolean;
|
dragScroll?: boolean;
|
||||||
clickScrolling?: boolean;
|
clickScroll?: boolean;
|
||||||
touchSupport?: boolean;
|
touch?: boolean;
|
||||||
snapHandle?: boolean;
|
|
||||||
};
|
};
|
||||||
textarea?: {
|
textarea?: {
|
||||||
dynWidth?: boolean;
|
dynWidth?: boolean;
|
||||||
dynHeight?: boolean;
|
dynHeight?: boolean;
|
||||||
inheritedAttrs?: string | ReadonlyArray<string> | null;
|
inheritedAttrs?: string | ReadonlyArray<string> | null;
|
||||||
};
|
};
|
||||||
|
nativeScrollbarsOverlaid?: {
|
||||||
|
show?: boolean;
|
||||||
|
initialize?: boolean;
|
||||||
|
};
|
||||||
|
/*
|
||||||
callbacks?: {
|
callbacks?: {
|
||||||
onInitialized?: BasicEventCallback | null;
|
onInitialized?: BasicEventCallback | null;
|
||||||
onInitializationWithdrawn?: BasicEventCallback | null;
|
onInitializationWithdrawn?: BasicEventCallback | null;
|
||||||
@@ -70,6 +69,7 @@ export interface Options {
|
|||||||
onHostSizeChanged?: SizeChangedCallback | null;
|
onHostSizeChanged?: SizeChangedCallback | null;
|
||||||
onUpdated?: UpdatedCallback | null;
|
onUpdated?: UpdatedCallback | null;
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OverflowChangedArgs {
|
export interface OverflowChangedArgs {
|
||||||
|
|||||||
@@ -8,15 +8,13 @@ import {
|
|||||||
} from 'support/options';
|
} from 'support/options';
|
||||||
import { ResizeBehavior, OverflowBehavior, VisibilityBehavior, AutoHideBehavior, Options } from 'options';
|
import { ResizeBehavior, OverflowBehavior, VisibilityBehavior, AutoHideBehavior, Options } from 'options';
|
||||||
|
|
||||||
const classNameAllowedValues: OptionsTemplateValue<string | null> = [oTypes.string, oTypes.null];
|
|
||||||
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
|
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
|
||||||
const booleanNullAllowedValues: OptionsTemplateValue<boolean | null> = [oTypes.boolean, oTypes.null];
|
const stringArrayNullAllowedValues: OptionsTemplateValue<string | ReadonlyArray<string> | null> = [oTypes.string, oTypes.array, oTypes.null];
|
||||||
const stringArrayNullAllowedValues: OptionsTemplateValue<string | Array<string> | null> = [oTypes.string, oTypes.array, oTypes.null];
|
|
||||||
const booleanTrueTemplate: OptionsWithOptionsTemplateValue<boolean> = [true, oTypes.boolean];
|
const booleanTrueTemplate: OptionsWithOptionsTemplateValue<boolean> = [true, oTypes.boolean];
|
||||||
const booleanFalseTemplate: OptionsWithOptionsTemplateValue<boolean> = [false, oTypes.boolean];
|
const booleanFalseTemplate: OptionsWithOptionsTemplateValue<boolean> = [false, oTypes.boolean];
|
||||||
const callbackTemplate: OptionsWithOptionsTemplateValue<Func | null> = [null, [oTypes.function, oTypes.null]];
|
// const callbackTemplate: OptionsWithOptionsTemplateValue<Func | null> = [null, [oTypes.function, oTypes.null]];
|
||||||
const resizeAllowedValues: OptionsTemplateValue<ResizeBehavior> = 'none both horizontal vertical';
|
const resizeAllowedValues: OptionsTemplateValue<ResizeBehavior> = 'none both horizontal vertical';
|
||||||
const overflowBehaviorAllowedValues: OptionsTemplateValue<OverflowBehavior> = 'visible-hidden visible-scroll scroll hidden';
|
const overflowAllowedValues: OptionsTemplateValue<OverflowBehavior> = 'visible-hidden visible-scroll scroll hidden';
|
||||||
const scrollbarsVisibilityAllowedValues: OptionsTemplateValue<VisibilityBehavior> = 'visible hidden auto';
|
const scrollbarsVisibilityAllowedValues: OptionsTemplateValue<VisibilityBehavior> = 'visible hidden auto';
|
||||||
const scrollbarsAutoHideAllowedValues: OptionsTemplateValue<AutoHideBehavior> = 'never scroll leavemove';
|
const scrollbarsAutoHideAllowedValues: OptionsTemplateValue<AutoHideBehavior> = 'never scroll leavemove';
|
||||||
|
|
||||||
@@ -36,37 +34,36 @@ const scrollbarsAutoHideAllowedValues: OptionsTemplateValue<AutoHideBehavior> =
|
|||||||
* Property "b" has a default value of 250 and it can be number
|
* Property "b" has a default value of 250 and it can be number
|
||||||
*/
|
*/
|
||||||
const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<Options>> = {
|
const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<Options>> = {
|
||||||
className: ['os-theme-dark', classNameAllowedValues], // null || string
|
|
||||||
resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v
|
resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v
|
||||||
sizeAutoCapable: booleanTrueTemplate, // true || false
|
|
||||||
clipAlways: booleanTrueTemplate, // true || false
|
|
||||||
normalizeRTL: booleanTrueTemplate, // true || false
|
|
||||||
paddingAbsolute: booleanFalseTemplate, // true || false
|
paddingAbsolute: booleanFalseTemplate, // true || false
|
||||||
autoUpdate: [null, booleanNullAllowedValues], // true || false || null
|
updating: {
|
||||||
autoUpdateInterval: [33, numberAllowedValues], // number
|
elementEvents: [[['img', 'load']], [oTypes.array, oTypes.null]], // array of tuples || null
|
||||||
updateOnLoad: [['img'], stringArrayNullAllowedValues], // string || array || null
|
contentMutationDebounce: [80, numberAllowedValues], // number
|
||||||
nativeScrollbarsOverlaid: {
|
hostMutationDebounce: [0, numberAllowedValues], // number
|
||||||
showNativeScrollbars: booleanFalseTemplate, // true || false
|
resizeDebounce: [0, numberAllowedValues], // number
|
||||||
initialize: booleanFalseTemplate, // true || false
|
|
||||||
},
|
},
|
||||||
overflowBehavior: {
|
overflow: {
|
||||||
x: ['scroll', overflowBehaviorAllowedValues], // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
x: ['scroll', overflowAllowedValues], // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
||||||
y: ['scroll', overflowBehaviorAllowedValues], // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
y: ['scroll', overflowAllowedValues], // visible-hidden || visible-scroll || hidden || scroll || v-h || v-s || h || s
|
||||||
},
|
},
|
||||||
scrollbars: {
|
scrollbars: {
|
||||||
visibility: ['auto', scrollbarsVisibilityAllowedValues], // visible || hidden || auto || v || h || a
|
visibility: ['auto', scrollbarsVisibilityAllowedValues], // visible || hidden || auto || v || h || a
|
||||||
autoHide: ['never', scrollbarsAutoHideAllowedValues], // never || scroll || leave || move || n || s || l || m
|
autoHide: ['never', scrollbarsAutoHideAllowedValues], // never || scroll || leave || move || n || s || l || m
|
||||||
autoHideDelay: [800, numberAllowedValues], // number
|
autoHideDelay: [800, numberAllowedValues], // number
|
||||||
dragScrolling: booleanTrueTemplate, // true || false
|
dragScroll: booleanTrueTemplate, // true || false
|
||||||
clickScrolling: booleanFalseTemplate, // true || false
|
clickScroll: booleanFalseTemplate, // true || false
|
||||||
touchSupport: booleanTrueTemplate, // true || false
|
touch: booleanTrueTemplate, // true || false
|
||||||
snapHandle: booleanFalseTemplate, // true || false
|
|
||||||
},
|
},
|
||||||
textarea: {
|
textarea: {
|
||||||
dynWidth: booleanFalseTemplate, // true || false
|
dynWidth: booleanFalseTemplate, // true || false
|
||||||
dynHeight: booleanFalseTemplate, // true || false
|
dynHeight: booleanFalseTemplate, // true || false
|
||||||
inheritedAttrs: [['style', 'class'], stringArrayNullAllowedValues], // string || array || null
|
inheritedAttrs: [['style', 'class'], stringArrayNullAllowedValues], // string || array || null
|
||||||
},
|
},
|
||||||
|
nativeScrollbarsOverlaid: {
|
||||||
|
show: booleanFalseTemplate, // true || false
|
||||||
|
initialize: booleanFalseTemplate, // true || false
|
||||||
|
},
|
||||||
|
/*
|
||||||
callbacks: {
|
callbacks: {
|
||||||
onInitialized: callbackTemplate, // null || function
|
onInitialized: callbackTemplate, // null || function
|
||||||
onInitializationWithdrawn: callbackTemplate, // null || function
|
onInitializationWithdrawn: callbackTemplate, // null || function
|
||||||
@@ -81,6 +78,7 @@ const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<Options>>
|
|||||||
onHostSizeChanged: callbackTemplate, // null || function
|
onHostSizeChanged: callbackTemplate, // null || function
|
||||||
onUpdated: callbackTemplate, // null || function
|
onUpdated: callbackTemplate, // null || function
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
||||||
export const { _template: optionsTemplate, _options: defaultOptions } = transformOptions(defaultOptionsWithTemplate);
|
export const { _template: optionsTemplate, _options: defaultOptions } = transformOptions(defaultOptionsWithTemplate);
|
||||||
|
|||||||
@@ -51,10 +51,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#os-environment
|
|
||||||
/* fix restricted measuring */
|
/* fix restricted measuring */
|
||||||
#os-environment:before,
|
.os-environment:before,
|
||||||
#os-environment:after,
|
.os-environment:after,
|
||||||
.os-content:before,
|
.os-content:before,
|
||||||
.os-content:after {
|
.os-content:after {
|
||||||
content: '';
|
content: '';
|
||||||
@@ -67,17 +66,17 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
#os-environment,
|
.os-environment,
|
||||||
.os-viewport {
|
.os-viewport {
|
||||||
-ms-overflow-style: scrollbar !important;
|
-ms-overflow-style: scrollbar !important;
|
||||||
}
|
}
|
||||||
.os-viewport-scrollbar-styled#os-environment,
|
.os-viewport-scrollbar-styled.os-environment,
|
||||||
.os-viewport-scrollbar-styled.os-viewport {
|
.os-viewport-scrollbar-styled.os-viewport {
|
||||||
scrollbar-width: none !important;
|
scrollbar-width: none !important;
|
||||||
}
|
}
|
||||||
.os-viewport-scrollbar-styled#os-environment::-webkit-scrollbar,
|
.os-viewport-scrollbar-styled.os-environment::-webkit-scrollbar,
|
||||||
.os-viewport-scrollbar-styled.os-viewport::-webkit-scrollbar,
|
.os-viewport-scrollbar-styled.os-viewport::-webkit-scrollbar,
|
||||||
.os-viewport-scrollbar-styled#os-environment::-webkit-scrollbar-corner,
|
.os-viewport-scrollbar-styled.os-environment::-webkit-scrollbar-corner,
|
||||||
.os-viewport-scrollbar-styled.os-viewport::-webkit-scrollbar-corner {
|
.os-viewport-scrollbar-styled.os-viewport::-webkit-scrollbar-corner {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
@@ -85,3 +84,9 @@
|
|||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.os-content-arrange {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +1,38 @@
|
|||||||
import { OSTarget, OSTargetObject } from 'typings';
|
import { OSTarget, OSTargetObject } from 'typings';
|
||||||
import { createStructureLifecycle } from 'lifecycles/structureLifecycle';
|
|
||||||
import { Cache, each, push } from 'support';
|
import { validateOptions, assignDeep, isEmptyObject } from 'support';
|
||||||
import { createSizeObserver } from 'observers/sizeObserver';
|
|
||||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
|
||||||
import { createDOMObserver } from 'observers/domObserver';
|
|
||||||
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
|
import { createStructureSetup, StructureSetup } from 'setups/structureSetup';
|
||||||
import { Lifecycle } from 'lifecycles/lifecycleBase';
|
import { createLifecycleHub } from 'lifecycles/lifecycleHub';
|
||||||
|
import { Options, defaultOptions, optionsTemplate } from 'options';
|
||||||
|
|
||||||
const OverlayScrollbars = (target: OSTarget | OSTargetObject, options?: any, extensions?: any): void => {
|
const OverlayScrollbars = (target: OSTarget | OSTargetObject, options?: Options, extensions?: any): any => {
|
||||||
const structureSetup: StructureSetup = createStructureSetup(target);
|
const currentOptions: Required<Options> = assignDeep(
|
||||||
const lifecycles: Lifecycle<any>[] = [];
|
{},
|
||||||
const { _host, _viewport, _content } = structureSetup._targetObj;
|
defaultOptions,
|
||||||
|
validateOptions<Options>(options || ({} as Options), optionsTemplate, null, true)._validated
|
||||||
push(lifecycles, createStructureLifecycle(structureSetup._targetObj));
|
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const onSizeChanged = (directionCache?: Cache<boolean>) => {
|
|
||||||
if (directionCache) {
|
|
||||||
each(lifecycles, (lifecycle) => {
|
|
||||||
lifecycle._onDirectionChanged && lifecycle._onDirectionChanged(directionCache);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
each(lifecycles, (lifecycle) => {
|
|
||||||
lifecycle._onSizeChanged && lifecycle._onSizeChanged();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const onTrinsicChanged = (widthIntrinsic: boolean, heightIntrinsicCache: Cache<boolean>) => {
|
|
||||||
each(lifecycles, (lifecycle) => {
|
|
||||||
lifecycle._onTrinsicChanged && lifecycle._onTrinsicChanged(widthIntrinsic, heightIntrinsicCache);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
createSizeObserver(_host, onSizeChanged, { _appear: true, _direction: true });
|
|
||||||
createTrinsicObserver(_host, onTrinsicChanged);
|
|
||||||
createDOMObserver(_host, () => {
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
createDOMObserver(
|
|
||||||
_content || _viewport,
|
|
||||||
() => {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
{ _observeContent: true }
|
|
||||||
);
|
);
|
||||||
|
const structureSetup: StructureSetup = createStructureSetup(target);
|
||||||
|
const lifecycleHub = createLifecycleHub(currentOptions, structureSetup);
|
||||||
|
const instance = {
|
||||||
|
options(newOptions?: Options) {
|
||||||
|
if (newOptions) {
|
||||||
|
const { _validated: _changedOptions } = validateOptions(newOptions, optionsTemplate, currentOptions, true);
|
||||||
|
|
||||||
|
if (!isEmptyObject(_changedOptions)) {
|
||||||
|
assignDeep(currentOptions, _changedOptions);
|
||||||
|
lifecycleHub._update(_changedOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentOptions;
|
||||||
|
},
|
||||||
|
update(force?: boolean) {
|
||||||
|
lifecycleHub._update(null, force);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.update(true);
|
||||||
|
|
||||||
|
return instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { OverlayScrollbars };
|
export { OverlayScrollbars };
|
||||||
|
|||||||
@@ -12,8 +12,17 @@ import {
|
|||||||
removeClass,
|
removeClass,
|
||||||
push,
|
push,
|
||||||
runEach,
|
runEach,
|
||||||
|
prependChildren,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import { classNameHost, classNamePadding, classNameViewport, classNameContent } from 'classnames';
|
import {
|
||||||
|
classNameHost,
|
||||||
|
classNamePadding,
|
||||||
|
classNameViewport,
|
||||||
|
classNameContent,
|
||||||
|
classNameContentArrange,
|
||||||
|
classNameViewportScrollbarStyling,
|
||||||
|
} from 'classnames';
|
||||||
|
import { getEnvironment } from 'environment';
|
||||||
import { OSTarget, OSTargetObject, InternalVersionOf, OSTargetElement } from 'typings';
|
import { OSTarget, OSTargetObject, InternalVersionOf, OSTargetElement } from 'typings';
|
||||||
|
|
||||||
export interface OSTargetContext {
|
export interface OSTargetContext {
|
||||||
@@ -27,6 +36,7 @@ export interface OSTargetContext {
|
|||||||
|
|
||||||
export interface PreparedOSTargetObject extends Required<InternalVersionOf<OSTargetObject>> {
|
export interface PreparedOSTargetObject extends Required<InternalVersionOf<OSTargetObject>> {
|
||||||
_host: HTMLElement;
|
_host: HTMLElement;
|
||||||
|
_contentArrange: HTMLElement | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StructureSetup {
|
export interface StructureSetup {
|
||||||
@@ -150,6 +160,25 @@ export const createStructureSetup = (target: OSTarget | OSTargetObject): Structu
|
|||||||
_host,
|
_host,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { _nativeScrollbarStyling, _nativeScrollbarIsOverlaid } = getEnvironment();
|
||||||
|
if (_nativeScrollbarStyling) {
|
||||||
|
addClass(_viewport, classNameViewportScrollbarStyling);
|
||||||
|
push(destroyFns, () => {
|
||||||
|
removeClass(_viewport, classNameViewportScrollbarStyling);
|
||||||
|
});
|
||||||
|
} else if (_nativeScrollbarIsOverlaid.x || _nativeScrollbarIsOverlaid.y) {
|
||||||
|
if (obj._content) {
|
||||||
|
const contentArrangeElm = createDiv(classNameContentArrange);
|
||||||
|
|
||||||
|
prependChildren(_viewport, contentArrangeElm);
|
||||||
|
push(destroyFns, () => {
|
||||||
|
removeElements(contentArrangeElm);
|
||||||
|
});
|
||||||
|
|
||||||
|
obj._contentArrange = contentArrangeElm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_targetObj: obj,
|
_targetObj: obj,
|
||||||
_targetCtx: ctx,
|
_targetCtx: ctx,
|
||||||
|
|||||||
+19
-5
@@ -1,7 +1,7 @@
|
|||||||
export interface Cache<T> {
|
export interface CacheValues<T> {
|
||||||
readonly _value?: T;
|
readonly _value?: T;
|
||||||
readonly _previous?: T;
|
readonly _previous?: T;
|
||||||
readonly _changed: boolean;
|
_changed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CacheOptions<T> {
|
export interface CacheOptions<T> {
|
||||||
@@ -13,7 +13,14 @@ export interface CacheOptions<T> {
|
|||||||
_alwaysUpdateValues?: boolean;
|
_alwaysUpdateValues?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CacheUpdate<T, C> = undefined extends C ? (force?: boolean | 0, context?: C) => Cache<T> : (force: boolean | 0, context: C) => Cache<T>;
|
export interface Cache<T, C = undefined> {
|
||||||
|
_current: (force?: boolean) => CacheValues<T>;
|
||||||
|
_update: CacheUpdate<T, C>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CacheUpdate<T, C> = undefined extends C
|
||||||
|
? (force?: boolean | 0, context?: C) => CacheValues<T>
|
||||||
|
: (force: boolean | 0, context: C) => CacheValues<T>;
|
||||||
|
|
||||||
export type UpdateCachePropFunction<T, C> = undefined extends C
|
export type UpdateCachePropFunction<T, C> = undefined extends C
|
||||||
? (context?: C, current?: T, previous?: T) => T
|
? (context?: C, current?: T, previous?: T) => T
|
||||||
@@ -23,7 +30,7 @@ export type UpdateCachePropFunction<T, C> = undefined extends C
|
|||||||
|
|
||||||
export type EqualCachePropFunction<T> = (currentVal?: T, newVal?: T) => boolean;
|
export type EqualCachePropFunction<T> = (currentVal?: T, newVal?: T) => boolean;
|
||||||
|
|
||||||
export const createCache = <T, C = undefined>(update: UpdateCachePropFunction<T, C>, options?: CacheOptions<T>): CacheUpdate<T, C> => {
|
export const createCache = <T, C = undefined>(update: UpdateCachePropFunction<T, C>, options?: CacheOptions<T>): Cache<T, C> => {
|
||||||
const { _equal, _initialValue, _alwaysUpdateValues } = options || {};
|
const { _equal, _initialValue, _alwaysUpdateValues } = options || {};
|
||||||
let _value: T | undefined = _initialValue;
|
let _value: T | undefined = _initialValue;
|
||||||
let _previous: T | undefined;
|
let _previous: T | undefined;
|
||||||
@@ -48,5 +55,12 @@ export const createCache = <T, C = undefined>(update: UpdateCachePropFunction<T,
|
|||||||
};
|
};
|
||||||
}) as CacheUpdate<T, C>;
|
}) as CacheUpdate<T, C>;
|
||||||
|
|
||||||
return cacheUpdate;
|
return {
|
||||||
|
_update: cacheUpdate,
|
||||||
|
_current: (force?: boolean) => ({
|
||||||
|
_value,
|
||||||
|
_previous,
|
||||||
|
_changed: !!force,
|
||||||
|
}),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ export interface OnOptions {
|
|||||||
* @param listener The listener which shall be removed.
|
* @param listener The listener which shall be removed.
|
||||||
* @param capture The options of the removed listener.
|
* @param capture The options of the removed listener.
|
||||||
*/
|
*/
|
||||||
export const off = (target: EventTarget, eventNames: string, listener: EventListener, capture?: boolean): void => {
|
export const off = <T extends Event = Event>(target: EventTarget, eventNames: string, listener: (event: T) => any, capture?: boolean): void => {
|
||||||
each(splitEventNames(eventNames), (eventName) => {
|
each(splitEventNames(eventNames), (eventName) => {
|
||||||
target.removeEventListener(eventName, listener, capture);
|
target.removeEventListener(eventName, listener as EventListener, capture);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -50,7 +50,12 @@ export const off = (target: EventTarget, eventNames: string, listener: EventList
|
|||||||
* @param listener The listener which is called on the eventnames.
|
* @param listener The listener which is called on the eventnames.
|
||||||
* @param options The options of the added listener.
|
* @param options The options of the added listener.
|
||||||
*/
|
*/
|
||||||
export const on = (target: EventTarget, eventNames: string, listener: EventListener, options?: OnOptions): (() => void) => {
|
export const on = <T extends Event = Event>(
|
||||||
|
target: EventTarget,
|
||||||
|
eventNames: string,
|
||||||
|
listener: (event: T) => any,
|
||||||
|
options?: OnOptions
|
||||||
|
): (() => void) => {
|
||||||
const doSupportPassiveEvents = supportPassiveEvents();
|
const doSupportPassiveEvents = supportPassiveEvents();
|
||||||
const passive = (doSupportPassiveEvents && options && options._passive) || false;
|
const passive = (doSupportPassiveEvents && options && options._passive) || false;
|
||||||
const capture = (options && options._capture) || false;
|
const capture = (options && options._capture) || false;
|
||||||
@@ -64,12 +69,12 @@ export const on = (target: EventTarget, eventNames: string, listener: EventListe
|
|||||||
: capture;
|
: capture;
|
||||||
|
|
||||||
each(splitEventNames(eventNames), (eventName) => {
|
each(splitEventNames(eventNames), (eventName) => {
|
||||||
const finalListener = once
|
const finalListener = (once
|
||||||
? (evt: Event) => {
|
? (evt: T) => {
|
||||||
target.removeEventListener(eventName, finalListener, capture);
|
target.removeEventListener(eventName, finalListener, capture);
|
||||||
listener && listener(evt);
|
listener && listener(evt);
|
||||||
}
|
}
|
||||||
: listener;
|
: listener) as EventListener;
|
||||||
|
|
||||||
push(offListeners, off.bind(null, target, eventName, finalListener, capture));
|
push(offListeners, off.bind(null, target, eventName, finalListener, capture));
|
||||||
target.addEventListener(eventName, finalListener, nativeOptions);
|
target.addEventListener(eventName, finalListener, nativeOptions);
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type OptionsTemplateTypeMap = {
|
|||||||
__TPL_boolean_TYPE__: boolean;
|
__TPL_boolean_TYPE__: boolean;
|
||||||
__TPL_number_TYPE__: number;
|
__TPL_number_TYPE__: number;
|
||||||
__TPL_string_TYPE__: string;
|
__TPL_string_TYPE__: string;
|
||||||
__TPL_array_TYPE__: Array<any>;
|
__TPL_array_TYPE__: Array<any> | ReadonlyArray<any>;
|
||||||
__TPL_function_TYPE__: Func;
|
__TPL_function_TYPE__: Func;
|
||||||
__TPL_null_TYPE__: null;
|
__TPL_null_TYPE__: null;
|
||||||
__TPL_object_TYPE__: Record<string, unknown>;
|
__TPL_object_TYPE__: Record<string, unknown>;
|
||||||
|
|||||||
+62
-1
@@ -1,6 +1,67 @@
|
|||||||
import 'overlayscrollbars.scss';
|
import 'overlayscrollbars.scss';
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
import { createDiv, appendChildren, parent, style, on, off, addClass, WH, XY, clientSize } from 'support';
|
||||||
import { OverlayScrollbars } from 'overlayscrollbars/OverlayScrollbars';
|
import { OverlayScrollbars } from 'overlayscrollbars/OverlayScrollbars';
|
||||||
|
|
||||||
const targetElm = document.querySelector('#target') as HTMLElement;
|
const targetElm = document.querySelector('#target') as HTMLElement;
|
||||||
OverlayScrollbars(targetElm);
|
window.os = OverlayScrollbars(targetElm);
|
||||||
|
|
||||||
|
export const resize = (element: HTMLElement) => {
|
||||||
|
const dragStartSize: WH<number> = { w: 0, h: 0 };
|
||||||
|
const dragStartPosition: XY<number> = { x: 0, y: 0 };
|
||||||
|
const resizeBtn = createDiv('resizeBtn');
|
||||||
|
appendChildren(element, resizeBtn);
|
||||||
|
addClass(element, 'resizer');
|
||||||
|
|
||||||
|
let dragResizeBtn: HTMLElement | undefined;
|
||||||
|
let dragResizer: HTMLElement | undefined;
|
||||||
|
|
||||||
|
const onSelectStart = (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizerResize = (event: MouseEvent) => {
|
||||||
|
const sizeStyle = {
|
||||||
|
width: dragStartSize.w + event.pageX - dragStartPosition.x,
|
||||||
|
height: dragStartSize.h + event.pageY - dragStartPosition.y,
|
||||||
|
};
|
||||||
|
|
||||||
|
style(dragResizer, sizeStyle);
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resizerResized = (event: MouseEvent) => {
|
||||||
|
off(document, 'selectstart', onSelectStart);
|
||||||
|
off(document, 'mousemove', resizerResize);
|
||||||
|
off(document, 'mouseup', resizerResized);
|
||||||
|
|
||||||
|
dragResizer = undefined;
|
||||||
|
dragResizeBtn = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
on(resizeBtn, 'mousedown', (event: MouseEvent) => {
|
||||||
|
const { currentTarget } = event;
|
||||||
|
if (event.buttons === 1 || event.which === 1) {
|
||||||
|
dragStartPosition.x = event.pageX;
|
||||||
|
dragStartPosition.y = event.pageY;
|
||||||
|
|
||||||
|
dragResizeBtn = currentTarget as HTMLElement;
|
||||||
|
dragResizer = parent(currentTarget as HTMLElement) as HTMLElement;
|
||||||
|
|
||||||
|
const cSize = clientSize(element);
|
||||||
|
dragStartSize.w = cSize.w;
|
||||||
|
dragStartSize.h = cSize.h;
|
||||||
|
|
||||||
|
on(document, 'selectstart', onSelectStart);
|
||||||
|
on(document, 'mousemove', resizerResize);
|
||||||
|
on(document, 'mouseup', resizerResized);
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
resize(document.querySelector('#resize')!);
|
||||||
|
resize(document.querySelector('#target')!);
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ body {
|
|||||||
|
|
||||||
#target {
|
#target {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
resize: both;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 2px solid red;
|
border: 2px solid red;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
@@ -40,7 +39,6 @@ body {
|
|||||||
|
|
||||||
#resize {
|
#resize {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
resize: both;
|
|
||||||
background: blue;
|
background: blue;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -135,3 +133,18 @@ body {
|
|||||||
.directionRTL {
|
.directionRTL {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resizer {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizeBtn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
background: red;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,15 +15,17 @@ const createUpdater = <T, C = unknown>(updaterReturn: (i: number) => T) => {
|
|||||||
describe('cache', () => {
|
describe('cache', () => {
|
||||||
test('creates and updates cache', () => {
|
test('creates and updates cache', () => {
|
||||||
const [fn, updater] = createUpdater((i) => `${i}`);
|
const [fn, updater] = createUpdater((i) => `${i}`);
|
||||||
const update = createCache<string>(updater);
|
const { _update, _current } = createCache<string>(updater);
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toBe('1');
|
expect(_value).toBe('1');
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, '1', undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, '1', undefined);
|
||||||
expect(_value).toBe('2');
|
expect(_value).toBe('2');
|
||||||
expect(_previous).toBe('1');
|
expect(_previous).toBe('1');
|
||||||
@@ -41,16 +43,19 @@ describe('cache', () => {
|
|||||||
updateFn(context, current, previous);
|
updateFn(context, current, previous);
|
||||||
return context!.test === 'test' || context!.even % 2 === 0;
|
return context!.test === 'test' || context!.even % 2 === 0;
|
||||||
};
|
};
|
||||||
const update = createCache(updater);
|
const { _update, _current } = createCache(updater);
|
||||||
const firstCtx = { test: 'test', even: 2 };
|
const firstCtx = { test: 'test', even: 2 };
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update(0, firstCtx);
|
let { _value, _previous, _changed } = _update(0, firstCtx);
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, undefined, undefined);
|
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, undefined, undefined);
|
||||||
expect(_value).toBe(true);
|
expect(_value).toBe(true);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, firstCtx));
|
({ _value, _previous, _changed } = _update(0, firstCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, true, undefined);
|
expect(updateFn).toHaveBeenLastCalledWith(firstCtx, true, undefined);
|
||||||
expect(_value).toBe(true);
|
expect(_value).toBe(true);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
@@ -58,19 +63,22 @@ describe('cache', () => {
|
|||||||
|
|
||||||
const scndCtx = { test: 'nah', even: 1 };
|
const scndCtx = { test: 'nah', even: 1 };
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, scndCtx));
|
({ _value, _previous, _changed } = _update(0, scndCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, true, undefined);
|
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, true, undefined);
|
||||||
expect(_value).toBe(false);
|
expect(_value).toBe(false);
|
||||||
expect(_previous).toBe(true);
|
expect(_previous).toBe(true);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, scndCtx));
|
({ _value, _previous, _changed } = _update(0, scndCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
|
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
|
||||||
expect(_value).toBe(false);
|
expect(_value).toBe(false);
|
||||||
expect(_previous).toBe(true);
|
expect(_previous).toBe(true);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(true, scndCtx));
|
({ _value, _previous, _changed } = _update(true, scndCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
|
expect(updateFn).toHaveBeenLastCalledWith(scndCtx, false, true);
|
||||||
expect(_value).toBe(false);
|
expect(_value).toBe(false);
|
||||||
expect(_previous).toBe(false);
|
expect(_previous).toBe(false);
|
||||||
@@ -82,32 +90,32 @@ describe('cache', () => {
|
|||||||
test: string;
|
test: string;
|
||||||
even: number;
|
even: number;
|
||||||
}
|
}
|
||||||
const update = createCache<ContextObj, ContextObj>(0);
|
const { _update } = createCache<ContextObj, ContextObj>(0);
|
||||||
const firstCtx = { test: 'test', even: 2 };
|
const firstCtx = { test: 'test', even: 2 };
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update(0, firstCtx);
|
let { _value, _previous, _changed } = _update(0, firstCtx);
|
||||||
expect(_value).toBe(firstCtx);
|
expect(_value).toBe(firstCtx);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, firstCtx));
|
({ _value, _previous, _changed } = _update(0, firstCtx));
|
||||||
expect(_value).toBe(firstCtx);
|
expect(_value).toBe(firstCtx);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
const scndCtx = { test: 'nah', even: 1 };
|
const scndCtx = { test: 'nah', even: 1 };
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, scndCtx));
|
({ _value, _previous, _changed } = _update(0, scndCtx));
|
||||||
expect(_value).toBe(scndCtx);
|
expect(_value).toBe(scndCtx);
|
||||||
expect(_previous).toBe(firstCtx);
|
expect(_previous).toBe(firstCtx);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, scndCtx));
|
({ _value, _previous, _changed } = _update(0, scndCtx));
|
||||||
expect(_value).toBe(scndCtx);
|
expect(_value).toBe(scndCtx);
|
||||||
expect(_previous).toBe(firstCtx);
|
expect(_previous).toBe(firstCtx);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(true, scndCtx));
|
({ _value, _previous, _changed } = _update(true, scndCtx));
|
||||||
expect(_value).toBe(scndCtx);
|
expect(_value).toBe(scndCtx);
|
||||||
expect(_previous).toBe(scndCtx);
|
expect(_previous).toBe(scndCtx);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
@@ -117,15 +125,17 @@ describe('cache', () => {
|
|||||||
describe('equal', () => {
|
describe('equal', () => {
|
||||||
test('with equal always true', () => {
|
test('with equal always true', () => {
|
||||||
const [fn, updater] = createUpdater((i) => i);
|
const [fn, updater] = createUpdater((i) => i);
|
||||||
const update = createCache<number>(updater, { _equal: () => true });
|
const { _update, _current } = createCache<number>(updater, { _equal: () => true });
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toBe(undefined);
|
expect(_value).toBe(undefined);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toBe(undefined);
|
expect(_value).toBe(undefined);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
@@ -134,15 +144,17 @@ describe('cache', () => {
|
|||||||
|
|
||||||
test('with equal always false', () => {
|
test('with equal always false', () => {
|
||||||
const [fn, updater] = createUpdater(() => 1);
|
const [fn, updater] = createUpdater(() => 1);
|
||||||
const update = createCache<number>(updater, { _equal: () => false });
|
const { _update, _current } = createCache<number>(updater, { _equal: () => false });
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toBe(1);
|
expect(_value).toBe(1);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, 1, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, 1, undefined);
|
||||||
expect(_value).toBe(1);
|
expect(_value).toBe(1);
|
||||||
expect(_previous).toBe(1);
|
expect(_previous).toBe(1);
|
||||||
@@ -152,15 +164,15 @@ describe('cache', () => {
|
|||||||
test('with object equal', () => {
|
test('with object equal', () => {
|
||||||
const obj = { a: -1, b: -1 };
|
const obj = { a: -1, b: -1 };
|
||||||
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
|
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
|
||||||
const update = createCache<typeof obj>(updater, { _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
|
const { _update } = createCache<typeof obj>(updater, { _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toEqual({ a: 1, b: 2 });
|
expect(_value).toEqual({ a: 1, b: 2 });
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, undefined);
|
||||||
expect(_value).toEqual({ a: 2, b: 3 });
|
expect(_value).toEqual({ a: 2, b: 3 });
|
||||||
expect(_previous).toEqual({ a: 1, b: 2 });
|
expect(_previous).toEqual({ a: 1, b: 2 });
|
||||||
@@ -171,15 +183,17 @@ describe('cache', () => {
|
|||||||
describe('inital value', () => {
|
describe('inital value', () => {
|
||||||
test('creates and updates cache with initialValue', () => {
|
test('creates and updates cache with initialValue', () => {
|
||||||
const [fn, updater] = createUpdater((i) => i);
|
const [fn, updater] = createUpdater((i) => i);
|
||||||
const update = createCache<number>(updater, { _initialValue: 0 });
|
const { _update, _current } = createCache<number>(updater, { _initialValue: 0 });
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, 0, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, 0, undefined);
|
||||||
expect(_value).toBe(1);
|
expect(_value).toBe(1);
|
||||||
expect(_previous).toBe(0);
|
expect(_previous).toBe(0);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, 1, 0);
|
expect(fn).toHaveBeenLastCalledWith(undefined, 1, 0);
|
||||||
expect(_value).toBe(2);
|
expect(_value).toBe(2);
|
||||||
expect(_previous).toBe(1);
|
expect(_previous).toBe(1);
|
||||||
@@ -189,15 +203,15 @@ describe('cache', () => {
|
|||||||
test('creates and updates cache with initialValue and equal', () => {
|
test('creates and updates cache with initialValue and equal', () => {
|
||||||
const obj = { a: -1, b: -1 };
|
const obj = { a: -1, b: -1 };
|
||||||
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
|
const [fn, updater] = createUpdater((i) => ({ a: i, b: i + 1 }));
|
||||||
const update = createCache<typeof obj>(updater, { _initialValue: obj, _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
|
const { _update } = createCache<typeof obj>(updater, { _initialValue: obj, _equal: (a, b) => a?.a === b?.a && a?.b === b?.b });
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
|
||||||
expect(_value).toEqual({ a: 1, b: 2 });
|
expect(_value).toEqual({ a: 1, b: 2 });
|
||||||
expect(_previous).toBe(obj);
|
expect(_previous).toBe(obj);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, obj);
|
expect(fn).toHaveBeenLastCalledWith(undefined, { a: 1, b: 2 }, obj);
|
||||||
expect(_value).toEqual({ a: 2, b: 3 });
|
expect(_value).toEqual({ a: 2, b: 3 });
|
||||||
expect(_previous).toEqual({ a: 1, b: 2 });
|
expect(_previous).toEqual({ a: 1, b: 2 });
|
||||||
@@ -208,15 +222,15 @@ describe('cache', () => {
|
|||||||
describe('always update values', () => {
|
describe('always update values', () => {
|
||||||
test('creates and updates cache with alwaysUpdateValues and equal always true', () => {
|
test('creates and updates cache with alwaysUpdateValues and equal always true', () => {
|
||||||
const [fn, updater] = createUpdater((i) => i);
|
const [fn, updater] = createUpdater((i) => i);
|
||||||
const update = createCache<number>(updater, { _alwaysUpdateValues: true, _equal: () => true });
|
const { _update } = createCache<number>(updater, { _alwaysUpdateValues: true, _equal: () => true });
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toBe(1);
|
expect(_value).toBe(1);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, 1, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, 1, undefined);
|
||||||
expect(_value).toBe(2);
|
expect(_value).toBe(2);
|
||||||
expect(_previous).toBe(1);
|
expect(_previous).toBe(1);
|
||||||
@@ -228,32 +242,37 @@ describe('cache', () => {
|
|||||||
test: string;
|
test: string;
|
||||||
even: number;
|
even: number;
|
||||||
}
|
}
|
||||||
const update = createCache<ContextObj, ContextObj>(0, { _alwaysUpdateValues: true });
|
const { _update, _current } = createCache<ContextObj, ContextObj>(0, { _alwaysUpdateValues: true });
|
||||||
const firstCtx = { test: 'test', even: 2 };
|
const firstCtx = { test: 'test', even: 2 };
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update(0, firstCtx);
|
let { _value, _previous, _changed } = _update(0, firstCtx);
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(_value).toBe(firstCtx);
|
expect(_value).toBe(firstCtx);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, firstCtx));
|
({ _value, _previous, _changed } = _update(0, firstCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(_value).toBe(firstCtx);
|
expect(_value).toBe(firstCtx);
|
||||||
expect(_previous).toBe(firstCtx);
|
expect(_previous).toBe(firstCtx);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
const scndCtx = { test: 'nah', even: 1 };
|
const scndCtx = { test: 'nah', even: 1 };
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, scndCtx));
|
({ _value, _previous, _changed } = _update(0, scndCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(_value).toBe(scndCtx);
|
expect(_value).toBe(scndCtx);
|
||||||
expect(_previous).toBe(firstCtx);
|
expect(_previous).toBe(firstCtx);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(0, scndCtx));
|
({ _value, _previous, _changed } = _update(0, scndCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(_value).toBe(scndCtx);
|
expect(_value).toBe(scndCtx);
|
||||||
expect(_previous).toBe(scndCtx);
|
expect(_previous).toBe(scndCtx);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(true, scndCtx));
|
({ _value, _previous, _changed } = _update(true, scndCtx));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(_value).toBe(scndCtx);
|
expect(_value).toBe(scndCtx);
|
||||||
expect(_previous).toBe(scndCtx);
|
expect(_previous).toBe(scndCtx);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
@@ -263,15 +282,17 @@ describe('cache', () => {
|
|||||||
describe('constant', () => {
|
describe('constant', () => {
|
||||||
test('updates constant initially without intial value', () => {
|
test('updates constant initially without intial value', () => {
|
||||||
const [fn, updater] = createUpdater(() => true);
|
const [fn, updater] = createUpdater(() => true);
|
||||||
const update = createCache<boolean>(updater);
|
const { _update, _current } = createCache<boolean>(updater);
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toBe(true);
|
expect(_value).toBe(true);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, true, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, true, undefined);
|
||||||
expect(_value).toBe(true);
|
expect(_value).toBe(true);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
@@ -281,15 +302,15 @@ describe('cache', () => {
|
|||||||
test('doesnt update constant with initial value', () => {
|
test('doesnt update constant with initial value', () => {
|
||||||
const obj = { constant: true };
|
const obj = { constant: true };
|
||||||
const [fn, updater] = createUpdater(() => obj);
|
const [fn, updater] = createUpdater(() => obj);
|
||||||
const update = createCache<typeof obj>(updater, { _initialValue: obj });
|
const { _update } = createCache<typeof obj>(updater, { _initialValue: obj });
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
|
||||||
expect(_value).toBe(obj);
|
expect(_value).toBe(obj);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, obj, undefined);
|
||||||
expect(_value).toBe(obj);
|
expect(_value).toBe(obj);
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
@@ -298,27 +319,31 @@ describe('cache', () => {
|
|||||||
|
|
||||||
test('updates constant with force', () => {
|
test('updates constant with force', () => {
|
||||||
const [fn, updater] = createUpdater(() => 'constant');
|
const [fn, updater] = createUpdater(() => 'constant');
|
||||||
const update = createCache<string>(updater);
|
const { _update, _current } = createCache<string>(updater);
|
||||||
|
|
||||||
let { _value, _previous, _changed } = update();
|
let { _value, _previous, _changed } = _update();
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, undefined, undefined);
|
||||||
expect(_value).toBe('constant');
|
expect(_value).toBe('constant');
|
||||||
expect(_previous).toBe(undefined);
|
expect(_previous).toBe(undefined);
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(true));
|
({ _value, _previous, _changed } = _update(true));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', undefined);
|
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', undefined);
|
||||||
expect(_value).toBe('constant');
|
expect(_value).toBe('constant');
|
||||||
expect(_previous).toBe('constant');
|
expect(_previous).toBe('constant');
|
||||||
expect(_changed).toBe(true);
|
expect(_changed).toBe(true);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update(false));
|
({ _value, _previous, _changed } = _update(false));
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', 'constant');
|
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', 'constant');
|
||||||
expect(_value).toBe('constant');
|
expect(_value).toBe('constant');
|
||||||
expect(_previous).toBe('constant');
|
expect(_previous).toBe('constant');
|
||||||
expect(_changed).toBe(false);
|
expect(_changed).toBe(false);
|
||||||
|
|
||||||
({ _value, _previous, _changed } = update());
|
({ _value, _previous, _changed } = _update());
|
||||||
|
expect({ _value, _previous, _changed: false }).toEqual(_current());
|
||||||
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', 'constant');
|
expect(fn).toHaveBeenLastCalledWith(undefined, 'constant', 'constant');
|
||||||
expect(_value).toBe('constant');
|
expect(_value).toBe('constant');
|
||||||
expect(_previous).toBe('constant');
|
expect(_previous).toBe('constant');
|
||||||
|
|||||||
Reference in New Issue
Block a user