mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-23 15:40:36 +03:00
Create DOMObserver
This commit is contained in:
@@ -0,0 +1,85 @@
|
|||||||
|
import { rAF, cAF, isEmptyArray, indexOf, createCache, runEach } from 'support';
|
||||||
|
import { getEnvironment } from 'environment';
|
||||||
|
|
||||||
|
export interface AutoUpdateLoop {
|
||||||
|
_add(fn: (delta: number) => any): () => void;
|
||||||
|
_interval(newInterval: number): () => void;
|
||||||
|
_interval(): number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultLoopInterval = 33;
|
||||||
|
let autoUpdateLoopInstance: AutoUpdateLoop;
|
||||||
|
|
||||||
|
const createAutoUpdateLoop = (): AutoUpdateLoop => {
|
||||||
|
let loopIsRunning = false;
|
||||||
|
let loopInterval = defaultLoopInterval;
|
||||||
|
let loopId: number | undefined;
|
||||||
|
const intervals: number[] = [];
|
||||||
|
const loopFunctions: Array<(...args: any) => any> = [];
|
||||||
|
const updateLoopInterval = () => {
|
||||||
|
loopInterval = isEmptyArray(intervals) ? defaultLoopInterval : Math.min.apply(null, intervals);
|
||||||
|
};
|
||||||
|
const updateTimeCache = createCache<number, number>((ctx) => ctx || performance.now(), {
|
||||||
|
_initialValue: performance.now(),
|
||||||
|
_equal: (currTime, newTime) => {
|
||||||
|
const delta = newTime! - currTime!;
|
||||||
|
return delta < loopInterval;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const loop = (newTime?: number) => {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (!isEmptyArray(loopFunctions) && loopIsRunning) {
|
||||||
|
loopId = rAF!(loop);
|
||||||
|
const { _changed, _value, _previous } = updateTimeCache(0, newTime);
|
||||||
|
if (_changed) {
|
||||||
|
runEach(loopFunctions, _value! - _previous!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function interval(): number;
|
||||||
|
function interval(newInterval: number): () => void;
|
||||||
|
function interval(newInterval?: number): number | (() => void) {
|
||||||
|
if (newInterval) {
|
||||||
|
intervals.push(newInterval);
|
||||||
|
updateLoopInterval();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
intervals.splice(indexOf(intervals, newInterval), 1);
|
||||||
|
updateLoopInterval();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return loopInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
_add: (fn) => {
|
||||||
|
loopFunctions.push(fn);
|
||||||
|
|
||||||
|
if (!loopIsRunning && !isEmptyArray(loopFunctions)) {
|
||||||
|
getEnvironment()._autoUpdateLoop = loopIsRunning = true;
|
||||||
|
|
||||||
|
updateTimeCache(true);
|
||||||
|
loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
loopFunctions.splice(indexOf(loopFunctions, fn), 1);
|
||||||
|
|
||||||
|
if (isEmptyArray(loopFunctions) && loopIsRunning) {
|
||||||
|
getEnvironment()._autoUpdateLoop = loopIsRunning = false;
|
||||||
|
|
||||||
|
cAF!(loopId!);
|
||||||
|
loopId = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_interval: interval,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAutoUpdateLoop = (): AutoUpdateLoop => {
|
||||||
|
if (!autoUpdateLoopInstance) {
|
||||||
|
autoUpdateLoopInstance = createAutoUpdateLoop();
|
||||||
|
}
|
||||||
|
return autoUpdateLoopInstance;
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from 'autoUpdateLoop/autoUpdateLoop';
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
export const classNameEnvironment = 'os-environment';
|
||||||
|
export const classNameEnvironmentFlexboxGlue = `${classNameEnvironment}-flexbox-glue`;
|
||||||
|
export const classNameEnvironmentFlexboxGlueMax = `${classNameEnvironmentFlexboxGlue}-max`;
|
||||||
|
|
||||||
|
export const classNameHost = 'os-host';
|
||||||
|
export const classNamePadding = 'os-padding';
|
||||||
|
export const classNameViewport = 'os-viewport';
|
||||||
|
export const classNameContent = 'os-content';
|
||||||
|
export const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`;
|
||||||
|
|
||||||
|
export const classNameSizeObserver = 'os-size-observer';
|
||||||
|
export const classNameSizeObserverAppear = `${classNameSizeObserver}-appear`;
|
||||||
|
export const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
|
||||||
|
export const classNameSizeObserverListenerScroll = `${classNameSizeObserverListener}-scroll`;
|
||||||
|
export const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
|
||||||
|
export const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
||||||
|
|
||||||
|
export const classNameTrinsicObserver = 'os-trinsic-observer';
|
||||||
@@ -14,6 +14,12 @@ import {
|
|||||||
runEach,
|
runEach,
|
||||||
equalWH,
|
equalWH,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
|
import {
|
||||||
|
classNameEnvironment,
|
||||||
|
classNameEnvironmentFlexboxGlue,
|
||||||
|
classNameEnvironmentFlexboxGlueMax,
|
||||||
|
classNameViewportScrollbarStyling,
|
||||||
|
} from 'classnames';
|
||||||
|
|
||||||
export type OnEnvironmentChanged = (env: Environment) => void;
|
export type OnEnvironmentChanged = (env: Environment) => void;
|
||||||
export interface Environment {
|
export interface Environment {
|
||||||
@@ -29,9 +35,6 @@ export interface Environment {
|
|||||||
|
|
||||||
let environmentInstance: Environment;
|
let environmentInstance: Environment;
|
||||||
const { abs, round } = Math;
|
const { abs, round } = Math;
|
||||||
const environmentElmId = 'os-environment';
|
|
||||||
const classNameFlexboxGlue = 'flexbox-glue';
|
|
||||||
const classNameFlexboxGlueMax = `${classNameFlexboxGlue}-max`;
|
|
||||||
|
|
||||||
const getNativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => {
|
const getNativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY => {
|
||||||
appendChildren(body, measureElm);
|
appendChildren(body, measureElm);
|
||||||
@@ -46,7 +49,7 @@ const getNativeScrollbarSize = (body: HTMLElement, measureElm: HTMLElement): XY
|
|||||||
|
|
||||||
const getNativeScrollbarStyling = (testElm: HTMLElement): boolean => {
|
const getNativeScrollbarStyling = (testElm: HTMLElement): boolean => {
|
||||||
let result = false;
|
let result = false;
|
||||||
addClass(testElm, 'os-viewport-scrollbar-styled');
|
addClass(testElm, classNameViewportScrollbarStyling);
|
||||||
try {
|
try {
|
||||||
result =
|
result =
|
||||||
style(testElm, 'scrollbar-width') === 'none' || window.getComputedStyle(testElm, '::-webkit-scrollbar').getPropertyValue('display') === 'none';
|
style(testElm, 'scrollbar-width') === 'none' || window.getComputedStyle(testElm, '::-webkit-scrollbar').getPropertyValue('display') === 'none';
|
||||||
@@ -83,12 +86,12 @@ const getRtlScrollBehavior = (parentElm: HTMLElement, childElm: HTMLElement): {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getFlexboxGlue = (parentElm: HTMLElement, childElm: HTMLElement): boolean => {
|
const getFlexboxGlue = (parentElm: HTMLElement, childElm: HTMLElement): boolean => {
|
||||||
addClass(parentElm, classNameFlexboxGlue);
|
addClass(parentElm, classNameEnvironmentFlexboxGlue);
|
||||||
const minOffsetsizeParent = offsetSize(parentElm);
|
const minOffsetsizeParent = offsetSize(parentElm);
|
||||||
const minOffsetsize = offsetSize(childElm);
|
const minOffsetsize = offsetSize(childElm);
|
||||||
const supportsMin = equalWH(minOffsetsize, minOffsetsizeParent);
|
const supportsMin = equalWH(minOffsetsize, minOffsetsizeParent);
|
||||||
|
|
||||||
addClass(parentElm, classNameFlexboxGlueMax);
|
addClass(parentElm, classNameEnvironmentFlexboxGlueMax);
|
||||||
const maxOffsetsizeParent = offsetSize(parentElm);
|
const maxOffsetsizeParent = offsetSize(parentElm);
|
||||||
const maxOffsetsize = offsetSize(childElm);
|
const maxOffsetsize = offsetSize(childElm);
|
||||||
const supportsMax = equalWH(maxOffsetsize, maxOffsetsizeParent);
|
const supportsMax = equalWH(maxOffsetsize, maxOffsetsizeParent);
|
||||||
@@ -114,7 +117,7 @@ const diffBiggerThanOne = (valOne: number, valTwo: number): boolean => {
|
|||||||
|
|
||||||
const createEnvironment = (): Environment => {
|
const createEnvironment = (): Environment => {
|
||||||
const { body } = document;
|
const { body } = document;
|
||||||
const envDOM = createDOM(`<div id="${environmentElmId}"><div></div></div>`);
|
const envDOM = createDOM(`<div class="${classNameEnvironment}"><div></div></div>`);
|
||||||
const envElm = envDOM[0] as HTMLElement;
|
const envElm = envDOM[0] as HTMLElement;
|
||||||
const envChildElm = envElm.firstChild as HTMLElement;
|
const envChildElm = envElm.firstChild as HTMLElement;
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,16 @@ import {
|
|||||||
createCache,
|
createCache,
|
||||||
topRightBottomLeft,
|
topRightBottomLeft,
|
||||||
TRBL,
|
TRBL,
|
||||||
|
WH,
|
||||||
|
XY,
|
||||||
equalTRBL,
|
equalTRBL,
|
||||||
|
equalXY,
|
||||||
optionsTemplateTypes as oTypes,
|
optionsTemplateTypes as oTypes,
|
||||||
OptionsTemplateValue,
|
OptionsTemplateValue,
|
||||||
style,
|
style,
|
||||||
OptionsWithOptionsTemplate,
|
OptionsWithOptionsTemplate,
|
||||||
|
scrollSize,
|
||||||
|
offsetSize,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import { OSTargetObject } from 'typings';
|
import { OSTargetObject } from 'typings';
|
||||||
import { createLifecycleBase, Lifecycle } from 'lifecycles/lifecycleBase';
|
import { createLifecycleBase, Lifecycle } from 'lifecycles/lifecycleBase';
|
||||||
@@ -33,11 +38,6 @@ const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<Required<StructureL
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const classNameHost = 'os-host';
|
|
||||||
const classNameViewport = 'os-viewport';
|
|
||||||
const classNameContent = 'os-content';
|
|
||||||
const classNameViewportScrollbarStyling = `${classNameViewport}-scrollbar-styled`;
|
|
||||||
|
|
||||||
const cssMarginEnd = cssProperty('margin-inline-end');
|
const cssMarginEnd = cssProperty('margin-inline-end');
|
||||||
const cssBorderEnd = cssProperty('border-inline-end');
|
const cssBorderEnd = cssProperty('border-inline-end');
|
||||||
|
|
||||||
@@ -55,6 +55,13 @@ export const createStructureLifecycle = (
|
|||||||
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
const directionObserverObsolete = (cssMarginEnd && cssBorderEnd) || supportsScrollbarStyling || scrollbarsOverlaid.y;
|
||||||
|
|
||||||
const updatePaddingCache = createCache(() => topRightBottomLeft(host, 'padding'), { _equal: equalTRBL });
|
const updatePaddingCache = createCache(() => topRightBottomLeft(host, 'padding'), { _equal: equalTRBL });
|
||||||
|
const updateOverflowAmountCache = createCache<XY<number>, { _contentScrollSize: WH<number>; _viewportSize: WH<number> }>(
|
||||||
|
(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 { _options, _update } = createLifecycleBase<StructureLifecycleOptions>(defaultOptionsWithTemplate, initialOptions, (force, checkOption) => {
|
const { _options, _update } = createLifecycleBase<StructureLifecycleOptions>(defaultOptionsWithTemplate, initialOptions, (force, checkOption) => {
|
||||||
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
|
const { _value: paddingAbsolute, _changed: paddingAbsoluteChanged } = checkOption('paddingAbsolute');
|
||||||
@@ -75,11 +82,6 @@ export const createStructureLifecycle = (
|
|||||||
paddingStyle.l = -padding!.l;
|
paddingStyle.l = -padding!.l;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!supportsScrollbarStyling) {
|
|
||||||
paddingStyle.r -= env._nativeScrollbarSize.y;
|
|
||||||
paddingStyle.b -= env._nativeScrollbarSize.x;
|
|
||||||
}
|
|
||||||
|
|
||||||
style(paddingElm, {
|
style(paddingElm, {
|
||||||
top: paddingStyle.t,
|
top: paddingStyle.t,
|
||||||
left: paddingStyle.l,
|
left: paddingStyle.l,
|
||||||
@@ -88,6 +90,59 @@ export const createStructureLifecycle = (
|
|||||||
'max-width': `calc(100% + ${paddingStyle.r * -1}px)`,
|
'max-width': `calc(100% + ${paddingStyle.r * -1}px)`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewportOffsetSize = offsetSize(paddingElm);
|
||||||
|
const contentClientSize = offsetSize(content);
|
||||||
|
const contentScrollSize = scrollSize(content);
|
||||||
|
const overflowAmuntCache = updateOverflowAmountCache(force, {
|
||||||
|
_contentScrollSize: contentScrollSize,
|
||||||
|
_viewportSize: {
|
||||||
|
w: viewportOffsetSize.w + Math.max(0, contentClientSize.w - contentScrollSize.w),
|
||||||
|
h: viewportOffsetSize.h + Math.max(0, contentClientSize.h - contentScrollSize.h),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const { _value: overflowAmount, _changed: overflowAmountChanged } = overflowAmuntCache;
|
||||||
|
|
||||||
|
console.log('overflowAmount', overflowAmount);
|
||||||
|
console.log('overflowAmountChanged', overflowAmountChanged);
|
||||||
|
|
||||||
|
/*
|
||||||
|
var setOverflowVariables = function (horizontal) {
|
||||||
|
var scrollbarVars = getScrollbarVars(horizontal);
|
||||||
|
var scrollbarVarsInverted = getScrollbarVars(!horizontal);
|
||||||
|
var xyI = scrollbarVarsInverted._x_y;
|
||||||
|
var xy = scrollbarVars._x_y;
|
||||||
|
var wh = scrollbarVars._w_h;
|
||||||
|
var widthHeight = scrollbarVars._width_height;
|
||||||
|
var scrollMax = _strScroll + scrollbarVars._Left_Top + 'Max';
|
||||||
|
var fractionalOverflowAmount = viewportRect[widthHeight] ? MATH.abs(viewportRect[widthHeight] - _viewportSize[wh]) : 0;
|
||||||
|
var checkFractionalOverflowAmount = previousOverflowAmount && previousOverflowAmount[xy] > 0 && _viewportElementNative[scrollMax] === 0;
|
||||||
|
overflowBehaviorIsVS[xy] = overflowBehavior[xy] === 'v-s';
|
||||||
|
overflowBehaviorIsVH[xy] = overflowBehavior[xy] === 'v-h';
|
||||||
|
overflowBehaviorIsS[xy] = overflowBehavior[xy] === 's';
|
||||||
|
overflowAmount[xy] = MATH.max(0, MATH.round((contentScrollSize[wh] - _viewportSize[wh]) * 100) / 100);
|
||||||
|
overflowAmount[xy] *=
|
||||||
|
hideOverflowForceTextarea || (checkFractionalOverflowAmount && fractionalOverflowAmount > 0 && fractionalOverflowAmount < 1) ? 0 : 1;
|
||||||
|
hasOverflow[xy] = overflowAmount[xy] > 0;
|
||||||
|
|
||||||
|
//hideOverflow:
|
||||||
|
//x || y : true === overflow is hidden by "overflow: scroll" OR "overflow: hidden"
|
||||||
|
//xs || ys : true === overflow is hidden by "overflow: scroll"
|
||||||
|
hideOverflow[xy] =
|
||||||
|
overflowBehaviorIsVS[xy] || overflowBehaviorIsVH[xy]
|
||||||
|
? hasOverflow[xyI] && !overflowBehaviorIsVS[xyI] && !overflowBehaviorIsVH[xyI]
|
||||||
|
: hasOverflow[xy];
|
||||||
|
hideOverflow[xy + 's'] = hideOverflow[xy] ? overflowBehaviorIsS[xy] || overflowBehaviorIsVS[xy] : false;
|
||||||
|
|
||||||
|
canScroll[xy] = hasOverflow[xy] && hideOverflow[xy + 's'];
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
if (!supportsScrollbarStyling) {
|
||||||
|
paddingStyle.r -= env._nativeScrollbarSize.y;
|
||||||
|
paddingStyle.b -= env._nativeScrollbarSize.x;
|
||||||
|
}
|
||||||
|
*/
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSizeChanged = () => {
|
const onSizeChanged = () => {
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { each, indexOf, isString, MutationObserverConstructor, isEmptyArray, liesBetween } from 'support';
|
||||||
|
import { classNameHost, classNameContent } from 'classnames';
|
||||||
|
|
||||||
|
export interface DOMObserverOptions {
|
||||||
|
_observeContent?: boolean;
|
||||||
|
_attributes?: string[];
|
||||||
|
}
|
||||||
|
export interface DOMObserver {
|
||||||
|
_disconnect: () => void;
|
||||||
|
_update: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styleChangingAttributes = ['id', 'class', 'style', 'open'];
|
||||||
|
const mutationObserverAttrsTextarea = ['wrap', 'cols', 'rows'];
|
||||||
|
|
||||||
|
const isUnknownMutation = (
|
||||||
|
attributeName: string | null,
|
||||||
|
type: MutationRecordType,
|
||||||
|
observeContent?: boolean,
|
||||||
|
target?: Node,
|
||||||
|
mutationTarget?: Node
|
||||||
|
) => {
|
||||||
|
const isAttributesType = type === 'attributes';
|
||||||
|
const targetIsMutationTarget = target === mutationTarget;
|
||||||
|
const styleChangingAttrChanged = indexOf(styleChangingAttributes, attributeName) > -1;
|
||||||
|
const contentChanged = observeContent && !isAttributesType;
|
||||||
|
const contentAttrChanged =
|
||||||
|
observeContent &&
|
||||||
|
isAttributesType &&
|
||||||
|
styleChangingAttrChanged &&
|
||||||
|
!targetIsMutationTarget &&
|
||||||
|
!liesBetween(mutationTarget as Element | undefined, `.${classNameHost}`, `.${classNameContent}`);
|
||||||
|
const targetAttrChanged = isAttributesType && styleChangingAttrChanged && targetIsMutationTarget && !observeContent;
|
||||||
|
|
||||||
|
return contentChanged || contentAttrChanged || targetAttrChanged;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createDOMObserver = (
|
||||||
|
target: HTMLElement,
|
||||||
|
callback: (changedTargetAttrs: string[], styleChanged: boolean, contentChanged: boolean) => any,
|
||||||
|
options?: DOMObserverOptions
|
||||||
|
): DOMObserver => {
|
||||||
|
const { _observeContent, _attributes } = options || {};
|
||||||
|
|
||||||
|
// MutationObserver
|
||||||
|
const observedAttributes = (_attributes || []).concat(_observeContent ? styleChangingAttributes : mutationObserverAttrsTextarea);
|
||||||
|
const observerCallback = (mutations: MutationRecord[]) => {
|
||||||
|
let styleChanged = false;
|
||||||
|
let contentChanged = false;
|
||||||
|
const changedTargetAttrs: string[] = [];
|
||||||
|
each(mutations, (mutation) => {
|
||||||
|
const { attributeName, target: mutationTarget, type } = mutation;
|
||||||
|
|
||||||
|
styleChanged = styleChanged || isUnknownMutation(attributeName, type);
|
||||||
|
|
||||||
|
if (_observeContent) {
|
||||||
|
contentChanged = contentChanged || isUnknownMutation(attributeName, type, true, target, mutationTarget);
|
||||||
|
}
|
||||||
|
if (isString(attributeName) && target === mutationTarget) {
|
||||||
|
changedTargetAttrs.push(attributeName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isEmptyArray(changedTargetAttrs) || styleChanged || contentChanged) {
|
||||||
|
callback(changedTargetAttrs, styleChanged, contentChanged);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mutationObserver: MutationObserver = new MutationObserverConstructor!(observerCallback);
|
||||||
|
|
||||||
|
const connect = () => {
|
||||||
|
mutationObserver.observe(target, {
|
||||||
|
attributes: true,
|
||||||
|
attributeOldValue: true,
|
||||||
|
subtree: _observeContent,
|
||||||
|
childList: _observeContent,
|
||||||
|
characterData: _observeContent,
|
||||||
|
attributeFilter: observedAttributes,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
connect();
|
||||||
|
|
||||||
|
return {
|
||||||
|
_disconnect: mutationObserver.disconnect,
|
||||||
|
_update: () => {
|
||||||
|
observerCallback(mutationObserver.takeRecords());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -17,22 +17,24 @@ import {
|
|||||||
addClass,
|
addClass,
|
||||||
isString,
|
isString,
|
||||||
equalWH,
|
equalWH,
|
||||||
|
cAF,
|
||||||
|
rAF,
|
||||||
} from 'support';
|
} from 'support';
|
||||||
import { CSSDirection } from 'typings';
|
import { CSSDirection } from 'typings';
|
||||||
import { getEnvironment } from 'environment';
|
import { getEnvironment } from 'environment';
|
||||||
|
import {
|
||||||
|
classNameSizeObserver,
|
||||||
|
classNameSizeObserverAppear,
|
||||||
|
classNameSizeObserverListener,
|
||||||
|
classNameSizeObserverListenerScroll,
|
||||||
|
classNameSizeObserverListenerItem,
|
||||||
|
classNameSizeObserverListenerItemFinal,
|
||||||
|
} from 'classnames';
|
||||||
|
|
||||||
const animationStartEventName = 'animationstart';
|
const animationStartEventName = 'animationstart';
|
||||||
const scrollEventName = 'scroll';
|
const scrollEventName = 'scroll';
|
||||||
const scrollAmount = 3333333;
|
const scrollAmount = 3333333;
|
||||||
const ResizeObserverConstructor = jsAPI('ResizeObserver');
|
const ResizeObserverConstructor = jsAPI('ResizeObserver');
|
||||||
const classNameSizeObserver = 'os-size-observer';
|
|
||||||
const classNameSizeObserverAppear = `${classNameSizeObserver}-appear`;
|
|
||||||
const classNameSizeObserverListener = `${classNameSizeObserver}-listener`;
|
|
||||||
const classNameSizeObserverListenerScroll = `${classNameSizeObserverListener}-scroll`;
|
|
||||||
const classNameSizeObserverListenerItem = `${classNameSizeObserverListener}-item`;
|
|
||||||
const classNameSizeObserverListenerItemFinal = `${classNameSizeObserverListenerItem}-final`;
|
|
||||||
const cAF = cancelAnimationFrame;
|
|
||||||
const rAF = requestAnimationFrame;
|
|
||||||
const getDirection = (elm: HTMLElement): CSSDirection => style(elm, 'direction') as CSSDirection;
|
const getDirection = (elm: HTMLElement): CSSDirection => style(elm, 'direction') as CSSDirection;
|
||||||
|
|
||||||
export type SizeObserverOptions = { _direction?: boolean; _appear?: boolean };
|
export type SizeObserverOptions = { _direction?: boolean; _appear?: boolean };
|
||||||
@@ -95,8 +97,8 @@ export const createSizeObserver = (
|
|||||||
isDirty = !scrollEvent || !equalWH(currSize, cacheSize);
|
isDirty = !scrollEvent || !equalWH(currSize, cacheSize);
|
||||||
|
|
||||||
if (scrollEvent && isDirty && !rAFId) {
|
if (scrollEvent && isDirty && !rAFId) {
|
||||||
cAF(rAFId);
|
cAF!(rAFId);
|
||||||
rAFId = rAF(onResized);
|
rAFId = rAF!(onResized);
|
||||||
} else if (!scrollEvent) {
|
} else if (!scrollEvent) {
|
||||||
onResized();
|
onResized();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { WH, Cache, createDOM, offsetSize, jsAPI, runEach, prependChildren, removeElements, createCache } from 'support';
|
import { WH, Cache, createDOM, offsetSize, runEach, prependChildren, removeElements, createCache, IntersectionObserverConstructor } from 'support';
|
||||||
import { createSizeObserver } from 'observers/sizeObserver';
|
import { createSizeObserver } from 'observers/sizeObserver';
|
||||||
|
import { classNameTrinsicObserver } from 'classnames';
|
||||||
const classNameTrinsicObserver = 'os-trinsic-observer';
|
|
||||||
const IntersectionObserverConstructor = jsAPI('IntersectionObserver');
|
|
||||||
|
|
||||||
export const createTrinsicObserver = (
|
export const createTrinsicObserver = (
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
@import './trinsicobserver.scss';
|
@import './trinsicobserver.scss';
|
||||||
@import './structurelifecycle.scss';
|
@import './structurelifecycle.scss';
|
||||||
|
|
||||||
#os-environment {
|
.os-environment {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.flexbox-glue {
|
&.os-environment-flexbox-glue {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.flexbox-glue-max {
|
&.os-environment-flexbox-glue-max {
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import { Cache, appendChildren, addClass, contents, is, isHTMLElement, createDiv
|
|||||||
import { createSizeObserver } from 'observers/sizeObserver';
|
import { createSizeObserver } from 'observers/sizeObserver';
|
||||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||||
import { Lifecycle } from 'lifecycles/lifecycleBase';
|
import { Lifecycle } from 'lifecycles/lifecycleBase';
|
||||||
|
import { classNameHost, classNamePadding, classNameViewport, classNameContent } from 'classnames';
|
||||||
const classNameHost = 'os-host';
|
|
||||||
const classNamePadding = 'os-padding';
|
|
||||||
const classNameViewport = 'os-viewport';
|
|
||||||
const classNameContent = 'os-content';
|
|
||||||
|
|
||||||
const normalizeTarget = (target: OSTarget): OSTargetObject => {
|
const normalizeTarget = (target: OSTarget): OSTargetObject => {
|
||||||
if (isHTMLElement(target)) {
|
if (isHTMLElement(target)) {
|
||||||
|
|||||||
+4
-4
@@ -13,20 +13,20 @@ export type CacheUpdate<T, C> = (force?: boolean | 0, context?: C) => Cache<T>;
|
|||||||
|
|
||||||
export type UpdateCachePropFunction<T, C> = (context?: C, current?: T, previous?: T) => T;
|
export type UpdateCachePropFunction<T, C> = (context?: C, current?: T, previous?: T) => T;
|
||||||
|
|
||||||
export type EqualCachePropFunction<T> = (a?: T, b?: 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>): CacheUpdate<T, C> => {
|
||||||
const { _equal, _initialValue } = options || {};
|
const { _equal, _initialValue } = options || {};
|
||||||
let _value: T | undefined = _initialValue;
|
let _value: T | undefined = _initialValue;
|
||||||
let _previous: T | undefined;
|
let _previous: T | undefined;
|
||||||
return (force, context) => {
|
return (force, context) => {
|
||||||
const prev = _value;
|
const curr = _value;
|
||||||
const newVal = update(context, _value, _previous);
|
const newVal = update(context, _value, _previous);
|
||||||
const changed = force || (_equal ? !_equal(prev, newVal) : prev !== newVal);
|
const changed = force || (_equal ? !_equal(curr, newVal) : curr !== newVal);
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
_value = newVal;
|
_value = newVal;
|
||||||
_previous = prev;
|
_previous = curr;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
import { jsAPI } from 'support/compatibility/vendors';
|
import { jsAPI } from 'support/compatibility/vendors';
|
||||||
|
|
||||||
export const resizeObserver: any | undefined = jsAPI('ResizeObserver');
|
export const MutationObserverConstructor = jsAPI<typeof MutationObserver>('MutationObserver');
|
||||||
|
export const IntersectionObserverConstructor = jsAPI<typeof IntersectionObserver>('IntersectionObserver');
|
||||||
|
export const ResizeObserverConstructor: any | undefined = jsAPI('ResizeObserver');
|
||||||
|
export const cAF = jsAPI<typeof cancelAnimationFrame>('cancelAnimationFrame');
|
||||||
|
export const rAF = jsAPI<typeof requestAnimationFrame>('requestAnimationFrame');
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ export const windowSize = (): WH => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the offset- width and height of the passed element. If the element is null the width and height values are 0.
|
* Returns the scroll- width and height of the passed element. If the element is null the width and height values are 0.
|
||||||
* @param elm The element of which the offset- width and height shall be returned.
|
* @param elm The element of which the scroll- width and height shall be returned.
|
||||||
*/
|
*/
|
||||||
export const offsetSize = (elm: HTMLElement | null): WH =>
|
export const offsetSize = (elm: HTMLElement | null): WH =>
|
||||||
elm
|
elm
|
||||||
@@ -41,6 +41,18 @@ export const clientSize = (elm: HTMLElement | null): WH =>
|
|||||||
}
|
}
|
||||||
: zeroObj;
|
: zeroObj;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the client- width and height of the passed element. If the element is null the width and height values are 0.
|
||||||
|
* @param elm The element of which the client- width and height shall be returned.
|
||||||
|
*/
|
||||||
|
export const scrollSize = (elm: HTMLElement | null): WH =>
|
||||||
|
elm
|
||||||
|
? {
|
||||||
|
w: elm.scrollWidth,
|
||||||
|
h: elm.scrollHeight,
|
||||||
|
}
|
||||||
|
: zeroObj;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the BoundingClientRect of the passed element.
|
* Returns the BoundingClientRect of the passed element.
|
||||||
* @param elm The element of which the BoundingClientRect shall be returned.
|
* @param elm The element of which the BoundingClientRect shall be returned.
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export const prependChildren = (node: Node | null, children: NodeCollection): vo
|
|||||||
* @param insertedNodes The Nodes which shall be inserted.
|
* @param insertedNodes The Nodes which shall be inserted.
|
||||||
*/
|
*/
|
||||||
export const insertBefore = (node: Node | null, insertedNodes: NodeCollection): void => {
|
export const insertBefore = (node: Node | null, insertedNodes: NodeCollection): void => {
|
||||||
before(parent(node), node, insertedNodes);
|
before(parent(node as Element), node, insertedNodes);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,7 +78,7 @@ export const insertBefore = (node: Node | null, insertedNodes: NodeCollection):
|
|||||||
* @param insertedNodes The Nodes which shall be inserted.
|
* @param insertedNodes The Nodes which shall be inserted.
|
||||||
*/
|
*/
|
||||||
export const insertAfter = (node: Node | null, insertedNodes: NodeCollection): void => {
|
export const insertAfter = (node: Node | null, insertedNodes: NodeCollection): void => {
|
||||||
before(parent(node), node && node.nextSibling, insertedNodes);
|
before(parent(node as Element), node && node.nextSibling, insertedNodes);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,7 +89,7 @@ export const removeElements = (nodes: NodeCollection): void => {
|
|||||||
if (isArrayLike(nodes)) {
|
if (isArrayLike(nodes)) {
|
||||||
each(from(nodes), (e) => removeElements(e));
|
each(from(nodes), (e) => removeElements(e));
|
||||||
} else if (nodes) {
|
} else if (nodes) {
|
||||||
const parentElm = parent(nodes);
|
const parentElm = parent(nodes as Element);
|
||||||
if (parentElm) {
|
if (parentElm) {
|
||||||
parentElm.removeChild(nodes);
|
parentElm.removeChild(nodes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
import { each, from } from 'support/utils/array';
|
import { each, from } from 'support/utils/array';
|
||||||
|
|
||||||
const matches = (elm: Element | null, selector: string): boolean => {
|
type InputElementType = Element | null | undefined;
|
||||||
if (elm) {
|
type OutputElementType = Element | null;
|
||||||
/* istanbul ignore next */
|
|
||||||
// eslint-disable-next-line
|
const elmPrototype = Element.prototype;
|
||||||
// @ts-ignore
|
|
||||||
const fn = Element.prototype.matches || Element.prototype.msMatchesSelector;
|
|
||||||
return fn.call(elm, selector);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find all elements with the passed selector, outgoing (and including) the passed element or the document if no element was provided.
|
* Find all elements with the passed selector, outgoing (and including) the passed element or the document if no element was provided.
|
||||||
* @param selector The selector which has to be searched by.
|
* @param selector The selector which has to be searched by.
|
||||||
* @param elm The element from which the search shall be outgoing.
|
* @param elm The element from which the search shall be outgoing.
|
||||||
*/
|
*/
|
||||||
export const find = (selector: string, elm?: Element | null): ReadonlyArray<Element> => {
|
const find = (selector: string, elm?: InputElementType): ReadonlyArray<Element> => {
|
||||||
const arr: Array<Element> = [];
|
const arr: Array<Element> = [];
|
||||||
|
|
||||||
each((elm || document).querySelectorAll(selector), (e: Element) => {
|
each((elm || document).querySelectorAll(selector), (e: Element) => {
|
||||||
@@ -31,26 +25,35 @@ export const find = (selector: string, elm?: Element | null): ReadonlyArray<Elem
|
|||||||
* @param selector The selector which has to be searched by.
|
* @param selector The selector which has to be searched by.
|
||||||
* @param elm The element from which the search shall be outgoing.
|
* @param elm The element from which the search shall be outgoing.
|
||||||
*/
|
*/
|
||||||
export const findFirst = (selector: string, elm?: Element | null): Element | null => (elm || document).querySelector(selector);
|
const findFirst = (selector: string, elm?: InputElementType): OutputElementType => (elm || document).querySelector(selector);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the passed element is matching with the passed selector.
|
* Determines whether the passed element is matching with the passed selector.
|
||||||
* @param elm The element which has to be compared with the passed selector.
|
* @param elm The element which has to be compared with the passed selector.
|
||||||
* @param selector The selector which has to be compared with the passed element. Additional selectors: ':visible' and ':hidden'.
|
* @param selector The selector which has to be compared with the passed element. Additional selectors: ':visible' and ':hidden'.
|
||||||
*/
|
*/
|
||||||
export const is = (elm: Element | null, selector: string): boolean => matches(elm, selector);
|
const is = (elm: InputElementType, selector: string): boolean => {
|
||||||
|
if (elm) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
|
const fn = elmPrototype.matches || elmPrototype.msMatchesSelector;
|
||||||
|
return fn.call(elm, selector);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the children (no text-nodes or comments) of the passed element which are matching the passed selector. An empty array is returned if the passed element is null.
|
* Returns the children (no text-nodes or comments) of the passed element which are matching the passed selector. An empty array is returned if the passed element is null.
|
||||||
* @param elm The element of which the children shall be returned.
|
* @param elm The element of which the children shall be returned.
|
||||||
* @param selector The selector which must match with the children elements.
|
* @param selector The selector which must match with the children elements.
|
||||||
*/
|
*/
|
||||||
export const children = (elm: Element | null, selector?: string): ReadonlyArray<Element> => {
|
const children = (elm: InputElementType, selector?: string): ReadonlyArray<Element> => {
|
||||||
const childs: Array<Element> = [];
|
const childs: Array<Element> = [];
|
||||||
|
|
||||||
each(elm && elm.children, (child: Element) => {
|
each(elm && elm.children, (child: Element) => {
|
||||||
if (selector) {
|
if (selector) {
|
||||||
if (matches(child, selector)) {
|
if (is(child, selector)) {
|
||||||
childs.push(child);
|
childs.push(child);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -65,10 +68,47 @@ export const children = (elm: Element | null, selector?: string): ReadonlyArray<
|
|||||||
* Returns the childNodes (incl. text-nodes or comments etc.) of the passed element. An empty array is returned if the passed element is null.
|
* Returns the childNodes (incl. text-nodes or comments etc.) of the passed element. An empty array is returned if the passed element is null.
|
||||||
* @param elm The element of which the childNodes shall be returned.
|
* @param elm The element of which the childNodes shall be returned.
|
||||||
*/
|
*/
|
||||||
export const contents = (elm: Element | null): ReadonlyArray<ChildNode> => (elm ? from(elm.childNodes) : []);
|
const contents = (elm: InputElementType): ReadonlyArray<ChildNode> => (elm ? from(elm.childNodes) : []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parent element of the passed element, or null if the passed element is null.
|
* Returns the parent element of the passed element, or null if the passed element is null.
|
||||||
* @param elm The element of which the parent element shall be returned.
|
* @param elm The element of which the parent element shall be returned.
|
||||||
*/
|
*/
|
||||||
export const parent = (elm: Node | null): Node | null => (elm ? elm.parentElement : null);
|
const parent = (elm: InputElementType): OutputElementType => (elm ? elm.parentElement : null);
|
||||||
|
|
||||||
|
const closest = (elm: InputElementType, selector: string): OutputElementType => {
|
||||||
|
if (elm) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
// @ts-ignore
|
||||||
|
if (elmPrototype.closest) {
|
||||||
|
return elm.closest(selector);
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
if (is(elm, selector)) {
|
||||||
|
return elm;
|
||||||
|
}
|
||||||
|
elm = parent(elm);
|
||||||
|
} while (elm !== null && elm.nodeType === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the given element lies between two selectors in the DOM.
|
||||||
|
* @param elm The element.
|
||||||
|
* @param highBoundarySelector The high boundary selector.
|
||||||
|
* @param deepBoundarySelector The deep boundary selector.
|
||||||
|
*/
|
||||||
|
const liesBetween = (elm: InputElementType, highBoundarySelector: string, deepBoundarySelector: string): boolean => {
|
||||||
|
const closestHighBoundaryElm = closest(elm, highBoundarySelector);
|
||||||
|
const closestDeepBoundaryElm = findFirst(deepBoundarySelector, closestHighBoundaryElm);
|
||||||
|
|
||||||
|
return closestHighBoundaryElm && closestDeepBoundaryElm
|
||||||
|
? closestHighBoundaryElm === elm ||
|
||||||
|
closestDeepBoundaryElm === elm ||
|
||||||
|
closest(closest(elm, deepBoundarySelector), highBoundarySelector) !== closestHighBoundaryElm
|
||||||
|
: false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { find, findFirst, is, children, contents, parent, liesBetween };
|
||||||
|
|||||||
@@ -15,23 +15,26 @@ export function each<T>(
|
|||||||
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | void
|
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | void
|
||||||
): Array<T> | ReadonlyArray<T>;
|
): Array<T> | ReadonlyArray<T>;
|
||||||
export function each<T>(
|
export function each<T>(
|
||||||
array: Array<T> | ReadonlyArray<T> | null,
|
array: Array<T> | ReadonlyArray<T> | null | undefined,
|
||||||
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | void
|
callback: (value: T, indexOrKey: number, source: Array<T>) => boolean | void
|
||||||
): Array<T> | ReadonlyArray<T> | null;
|
): Array<T> | ReadonlyArray<T> | null | undefined;
|
||||||
export function each<T>(
|
export function each<T>(
|
||||||
arrayLikeObject: ArrayLike<T>,
|
arrayLikeObject: ArrayLike<T>,
|
||||||
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | void
|
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | void
|
||||||
): ArrayLike<T>;
|
): ArrayLike<T>;
|
||||||
export function each<T>(
|
export function each<T>(
|
||||||
arrayLikeObject: ArrayLike<T> | null,
|
arrayLikeObject: ArrayLike<T> | null | undefined,
|
||||||
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | void
|
callback: (value: T, indexOrKey: number, source: ArrayLike<T>) => boolean | void
|
||||||
): ArrayLike<T> | null;
|
): ArrayLike<T> | null | undefined;
|
||||||
export function each(obj: PlainObject, callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | void): PlainObject;
|
export function each(obj: PlainObject, callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | void): PlainObject;
|
||||||
export function each(obj: PlainObject | null, callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | void): PlainObject | null;
|
export function each(
|
||||||
|
obj: PlainObject | null | undefined,
|
||||||
|
callback: (value: any, indexOrKey: string, source: PlainObject) => boolean | void
|
||||||
|
): PlainObject | null | undefined;
|
||||||
export function each<T>(
|
export function each<T>(
|
||||||
source: ArrayLike<T> | PlainObject | null,
|
source: ArrayLike<T> | PlainObject | null | undefined,
|
||||||
callback: (value: T, indexOrKey: any, source: any) => boolean | void
|
callback: (value: T, indexOrKey: any, source: any) => boolean | void
|
||||||
): Array<T> | ReadonlyArray<T> | ArrayLike<T> | PlainObject | null {
|
): Array<T> | ReadonlyArray<T> | ArrayLike<T> | PlainObject | null | undefined {
|
||||||
if (isArrayLike(source)) {
|
if (isArrayLike(source)) {
|
||||||
for (let i = 0; i < source.length; i++) {
|
for (let i = 0; i < source.length; i++) {
|
||||||
if (callback(source[i], i, source) === false) {
|
if (callback(source[i], i, source) === false) {
|
||||||
@@ -67,14 +70,21 @@ export const from = <T = any>(arr: ArrayLike<T>) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the passed array is empty.
|
||||||
|
* @param array The array which shall be checked.
|
||||||
|
*/
|
||||||
|
export const isEmptyArray = (array: Array<any> | null | undefined) => array && array.length === 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls all functions in the passed array/set of functions.
|
* Calls all functions in the passed array/set of functions.
|
||||||
* @param arr The array filled with function which shall be called.
|
* @param arr The array filled with function which shall be called.
|
||||||
|
* @param p1 The first param.
|
||||||
*/
|
*/
|
||||||
export const runEach = (arr: ArrayLike<RunEachItem> | Set<RunEachItem>): void => {
|
export const runEach = (arr: ArrayLike<RunEachItem> | Set<RunEachItem>, p1?: unknown): void => {
|
||||||
if (arr instanceof Set) {
|
if (arr instanceof Set) {
|
||||||
arr.forEach((fn) => fn && fn());
|
arr.forEach((fn) => fn && fn(p1));
|
||||||
} else {
|
} else {
|
||||||
each(arr, (fn) => fn && fn());
|
each(arr, (fn) => fn && fn(p1));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from 'support/utils/array';
|
export * from 'support/utils/array';
|
||||||
export * from 'support/utils/equal';
|
export * from 'support/utils/equal';
|
||||||
|
export * from 'support/utils/lexicon';
|
||||||
export * from 'support/utils/object';
|
export * from 'support/utils/object';
|
||||||
export * from 'support/utils/types';
|
export * from 'support/utils/types';
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
interface GenericLexicon<T extends boolean> {
|
||||||
|
_widthHeight: T extends true ? 'width' : 'height';
|
||||||
|
_WidthHeight: T extends true ? 'Width' : 'Height';
|
||||||
|
_leftTop: T extends true ? 'left' : 'top';
|
||||||
|
_LeftTop: T extends true ? 'Left' : 'Top';
|
||||||
|
_xy: T extends true ? 'x' : 'y';
|
||||||
|
_XY: T extends true ? 'X' : 'Y';
|
||||||
|
_wh: T extends true ? 'w' : 'h';
|
||||||
|
_lt: T extends true ? 'l' : 't';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Lexicon<T extends boolean> extends GenericLexicon<T> {
|
||||||
|
_inverted: Lexicon<T extends true ? false : true>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLexicon = <T extends boolean = false>(horizontal?: T): Lexicon<T> => {
|
||||||
|
return {
|
||||||
|
_widthHeight: horizontal ? 'width' : 'height',
|
||||||
|
_WidthHeight: horizontal ? 'Width' : 'Height',
|
||||||
|
_leftTop: horizontal ? 'left' : 'top',
|
||||||
|
_LeftTop: horizontal ? 'Left' : 'Top',
|
||||||
|
_xy: horizontal ? 'x' : 'y',
|
||||||
|
_XY: horizontal ? 'X' : 'Y',
|
||||||
|
_wh: horizontal ? 'w' : 'h',
|
||||||
|
_lt: horizontal ? 'l' : 't',
|
||||||
|
_inverted: getLexicon(!horizontal),
|
||||||
|
} as Lexicon<T>;
|
||||||
|
};
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { getAutoUpdateLoop } from 'autoUpdateLoop';
|
||||||
|
import { getEnvironment } from 'environment';
|
||||||
|
|
||||||
|
describe('autoUpdateLoop', () => {
|
||||||
|
test('first creation', async () => {
|
||||||
|
const deltas: number[] = [];
|
||||||
|
const wait = 2700;
|
||||||
|
const loop = getAutoUpdateLoop();
|
||||||
|
const defaultInterval = loop._interval();
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
|
const added = Date.now();
|
||||||
|
const remove = loop._add((delta) => {
|
||||||
|
if (deltas.length === 0) {
|
||||||
|
expect(Date.now() - added >= defaultInterval).toBe(true);
|
||||||
|
}
|
||||||
|
expect(delta >= defaultInterval).toBe(true);
|
||||||
|
deltas.push(delta);
|
||||||
|
});
|
||||||
|
expect(getEnvironment()._autoUpdateLoop).toBe(true);
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, wait));
|
||||||
|
const elapsedDeltas = deltas.reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
|
expect(wait - elapsedDeltas < defaultInterval * 2).toBe(true);
|
||||||
|
|
||||||
|
remove();
|
||||||
|
expect(getEnvironment()._autoUpdateLoop).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add multiple', async () => {
|
||||||
|
const loop = getAutoUpdateLoop();
|
||||||
|
const fn1 = jest.fn();
|
||||||
|
const fn2 = jest.fn();
|
||||||
|
const fn3 = jest.fn();
|
||||||
|
|
||||||
|
const remove1 = loop._add(fn1);
|
||||||
|
const remove2 = loop._add(fn2);
|
||||||
|
const remove3 = loop._add(fn3);
|
||||||
|
|
||||||
|
expect(getEnvironment()._autoUpdateLoop).toBe(true);
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2500));
|
||||||
|
|
||||||
|
expect(fn1).toHaveBeenCalledTimes(fn1.mock.calls.length);
|
||||||
|
expect(fn2).toHaveBeenCalledTimes(fn1.mock.calls.length);
|
||||||
|
expect(fn3).toHaveBeenCalledTimes(fn1.mock.calls.length);
|
||||||
|
|
||||||
|
remove1();
|
||||||
|
remove2();
|
||||||
|
remove3();
|
||||||
|
|
||||||
|
expect(getEnvironment()._autoUpdateLoop).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('change interval', async () => {
|
||||||
|
const loop = getAutoUpdateLoop();
|
||||||
|
const defaultInterval = loop._interval();
|
||||||
|
|
||||||
|
const remove10 = loop._interval(10);
|
||||||
|
const remove5 = loop._interval(5);
|
||||||
|
const remove3 = loop._interval(3);
|
||||||
|
const remove8 = loop._interval(8);
|
||||||
|
const remove15 = loop._interval(15);
|
||||||
|
|
||||||
|
expect(loop._interval()).toBe(3);
|
||||||
|
remove3();
|
||||||
|
expect(loop._interval()).toBe(5);
|
||||||
|
remove10();
|
||||||
|
remove8();
|
||||||
|
expect(loop._interval()).toBe(5);
|
||||||
|
remove5();
|
||||||
|
expect(loop._interval()).toBe(15);
|
||||||
|
remove15();
|
||||||
|
|
||||||
|
expect(loop._interval()).toBe(defaultInterval);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { isNumber, isPlainObject } from 'support/utils/types';
|
import { isNumber, isPlainObject } from 'support/utils/types';
|
||||||
import { createDiv } from 'support/dom/create';
|
import { createDiv } from 'support/dom/create';
|
||||||
import { windowSize, offsetSize, clientSize, getBoundingClientRect, hasDimensions } from 'support/dom/dimensions';
|
import { windowSize, offsetSize, clientSize, scrollSize, getBoundingClientRect, hasDimensions } from 'support/dom/dimensions';
|
||||||
|
|
||||||
describe('dom dimensions', () => {
|
describe('dom dimensions', () => {
|
||||||
describe('offsetSize', () => {
|
describe('offsetSize', () => {
|
||||||
@@ -35,6 +35,22 @@ describe('dom dimensions', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('scrollSize', () => {
|
||||||
|
test('DOM element', () => {
|
||||||
|
const result = scrollSize(document.body);
|
||||||
|
expect(isPlainObject(result)).toBe(true);
|
||||||
|
expect(isNumber(result.w)).toBe(true);
|
||||||
|
expect(isNumber(result.h)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('null', () => {
|
||||||
|
const result = scrollSize(null);
|
||||||
|
expect(isPlainObject(result)).toBe(true);
|
||||||
|
expect(isNumber(result.w)).toBe(true);
|
||||||
|
expect(isNumber(result.h)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('windowSize', () => {
|
test('windowSize', () => {
|
||||||
const result = windowSize();
|
const result = windowSize();
|
||||||
expect(isPlainObject(result)).toBe(true);
|
expect(isPlainObject(result)).toBe(true);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { find, findFirst, is, children, contents, parent, createDiv } from 'support/dom';
|
import { find, findFirst, is, children, contents, parent, createDiv, liesBetween } from 'support/dom';
|
||||||
|
|
||||||
const slotElm = document.body;
|
const slotElm = document.body;
|
||||||
const testHTML = '<div id="parent" class="div-class"><div id="child" class="div-class"></div></div><p>2</p><input type="text" value="3"></input>abc';
|
const testHTML = '<div id="parent" class="div-class"><div id="child" class="div-class"></div></div><p>2</p><input type="text" value="3"></input>abc';
|
||||||
@@ -199,4 +199,92 @@ describe('dom traversal', () => {
|
|||||||
expect(p).toBeNull();
|
expect(p).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('liesBetween', () => {
|
||||||
|
const elmsBetween = ['.host', '.something', '.something-a', '.something-b', '.padding', '.viewport', '.content'];
|
||||||
|
const elmsOutside = ['.allowed-a', '.allowed-b', '.allowed-c', '.deeper-a', '.deeper-b'];
|
||||||
|
const elmsToTest = [...elmsBetween, ...elmsOutside];
|
||||||
|
const domPart = (id: string, content?: string) => `
|
||||||
|
<div id="${id}" class="host">
|
||||||
|
<div class="something">
|
||||||
|
<div class="something-a">
|
||||||
|
<div class="something-b">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="padding">
|
||||||
|
<div class="viewport">
|
||||||
|
<div class="content">
|
||||||
|
<div class="allowed-a"><div class="allowed-b"><div class="allowed-c"></div></div></div>
|
||||||
|
<div class="deeper-a"><div class="deeper-b">${content || ''}</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const createTestDOM = (nestings = 0) => {
|
||||||
|
let part = '';
|
||||||
|
for (let i = 0; i < nestings + 1; i++) {
|
||||||
|
part = domPart(`host-${nestings - i}`, part);
|
||||||
|
}
|
||||||
|
|
||||||
|
return part;
|
||||||
|
};
|
||||||
|
const genericTest = (nestings: number) => {
|
||||||
|
const allHostIds = Array(nestings)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => `#host-${index}`);
|
||||||
|
|
||||||
|
const test = (hostId: string, remainingIds: string[]) => {
|
||||||
|
const runExpectance = (id: string) => {
|
||||||
|
const isRemainingId = remainingIds.includes(id);
|
||||||
|
elmsToTest.forEach((elm) => {
|
||||||
|
const hostElm = findFirst(`${id}`);
|
||||||
|
const searchElm = hostElm?.classList.contains(elm.substring(1)) ? hostElm : findFirst(`${id} ${elm}`);
|
||||||
|
|
||||||
|
expect(liesBetween(searchElm, `${hostId}`, '.content')).toBe(
|
||||||
|
isRemainingId ? false : elmsBetween.includes(elm) ? true : elmsOutside.includes(elm) ? false : undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
runExpectance(hostId);
|
||||||
|
|
||||||
|
remainingIds.forEach((id) => {
|
||||||
|
runExpectance(id);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
elmsBetween.forEach((elm) => {
|
||||||
|
expect(liesBetween(findFirst(elm), '.host', '.content')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
elmsOutside.forEach((elm) => {
|
||||||
|
expect(liesBetween(findFirst(elm), '.host', '.content')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < nestings; i++) {
|
||||||
|
const currHostId = allHostIds[i];
|
||||||
|
const remainingIds = allHostIds.filter((id) => id !== currHostId);
|
||||||
|
|
||||||
|
test(currHostId, remainingIds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test('with native closest', () => {
|
||||||
|
slotElm.innerHTML = createTestDOM(3);
|
||||||
|
genericTest(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('with polyfill closest', () => {
|
||||||
|
const original = Element.prototype.closest;
|
||||||
|
// @ts-ignore
|
||||||
|
Element.prototype.closest = undefined;
|
||||||
|
|
||||||
|
slotElm.innerHTML = createTestDOM(3);
|
||||||
|
genericTest(3);
|
||||||
|
|
||||||
|
Element.prototype.closest = original;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user