mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-23 08:00:37 +03:00
Merge branch 'v2.0.0' of https://github.com/KingSora/OverlayScrollbars into v2.0.0
This commit is contained in:
@@ -67,6 +67,7 @@
|
|||||||
"test": "yarn workspaces run test",
|
"test": "yarn workspaces run test",
|
||||||
"test:jsdom": "yarn workspaces run test:jsdom",
|
"test:jsdom": "yarn workspaces run test:jsdom",
|
||||||
"test:browser": "yarn workspaces run test:browser",
|
"test:browser": "yarn workspaces run test:browser",
|
||||||
|
"test:browser:quick": "yarn workspaces run test:browser:quick",
|
||||||
"test:browser-dev": "yarn workspaces run test:browser-dev",
|
"test:browser-dev": "yarn workspaces run test:browser-dev",
|
||||||
"build": "yarn workspaces run build",
|
"build": "yarn workspaces run build",
|
||||||
"lint": "npx eslint --fix ."
|
"lint": "npx eslint --fix ."
|
||||||
|
|||||||
+603
-538
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+687
-589
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@
|
|||||||
"test": "jest --coverage --runInBand --detectOpenHandles",
|
"test": "jest --coverage --runInBand --detectOpenHandles",
|
||||||
"test:jsdom": "jest --coverage --runInBand --detectOpenHandles --selectProjects jsdom --testPathPattern",
|
"test:jsdom": "jest --coverage --runInBand --detectOpenHandles --selectProjects jsdom --testPathPattern",
|
||||||
"test:browser": "jest --runInBand --detectOpenHandles --selectProjects browser --testPathPattern",
|
"test:browser": "jest --runInBand --detectOpenHandles --selectProjects browser --testPathPattern",
|
||||||
|
"test:browser:quick": "jest --runInBand --detectOpenHandles --selectProjects browser --testPathIgnorePatterns=\"/node_modules/|/structureLifecycle/\"",
|
||||||
"test:browser-dev": "jest --runInBand --detectOpenHandles --selectProjects browser-dev --testPathPattern",
|
"test:browser-dev": "jest --runInBand --detectOpenHandles --selectProjects browser-dev --testPathPattern",
|
||||||
"build": "rollup -c"
|
"build": "rollup -c"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,11 @@
|
|||||||
import {
|
import { XY, WH, TRBL, CacheValues, PartialOptions, each, hasOwnProperty, isNumber, scrollLeft, scrollTop, assignDeep } from 'support';
|
||||||
XY,
|
|
||||||
WH,
|
|
||||||
TRBL,
|
|
||||||
CacheValues,
|
|
||||||
PartialOptions,
|
|
||||||
each,
|
|
||||||
hasOwnProperty,
|
|
||||||
isNumber,
|
|
||||||
scrollLeft,
|
|
||||||
scrollTop,
|
|
||||||
assignDeep,
|
|
||||||
liesBetween,
|
|
||||||
diffClass,
|
|
||||||
} from 'support';
|
|
||||||
import { OSOptions } from 'options';
|
import { OSOptions } from 'options';
|
||||||
import { classNameHost, classNameViewport, classNameContent } from 'classnames';
|
|
||||||
import { getEnvironment } from 'environment';
|
import { getEnvironment } from 'environment';
|
||||||
import { StructureSetup } from 'setups/structureSetup';
|
import { StructureSetup } from 'setups/structureSetup';
|
||||||
|
import { lifecycleHubOservers } from 'lifecycles/lifecycleHubObservers';
|
||||||
import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle';
|
import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle';
|
||||||
import { createPaddingLifecycle } from 'lifecycles/paddingLifecycle';
|
import { createPaddingLifecycle } from 'lifecycles/paddingLifecycle';
|
||||||
import { createOverflowLifecycle } from 'lifecycles/overflowLifecycle';
|
import { createOverflowLifecycle } from 'lifecycles/overflowLifecycle';
|
||||||
import { createSizeObserver } from 'observers/sizeObserver';
|
|
||||||
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
|
||||||
import { createDOMObserver } from 'observers/domObserver';
|
|
||||||
import { StyleObject } from 'typings';
|
import { StyleObject } from 'typings';
|
||||||
|
|
||||||
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
|
||||||
@@ -82,28 +65,7 @@ export interface LifecycleHub {
|
|||||||
const getPropByPath = <T>(obj: any, path: string): T =>
|
const getPropByPath = <T>(obj: any, path: string): T =>
|
||||||
obj ? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj) : undefined;
|
obj ? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj) : undefined;
|
||||||
|
|
||||||
// TODO: observer textarea attrs if textarea
|
const booleanCacheValuesFallback: CacheValues<boolean> = {
|
||||||
// TODO: tabindex, open etc.
|
|
||||||
// TODO: test _ignoreContentChange & _ignoreNestedTargetChange for content dom observer
|
|
||||||
// TODO: test _ignoreTargetChange for target dom observer
|
|
||||||
const ignorePrefix = 'os-';
|
|
||||||
const hostSelector = `.${classNameHost}`;
|
|
||||||
const viewportSelector = `.${classNameViewport}`;
|
|
||||||
const contentSelector = `.${classNameContent}`;
|
|
||||||
const attrs = ['id', 'class', 'style', 'open'];
|
|
||||||
const ignoreTargetChange = (target: Node, attrName: string, oldValue: string | null, newValue: string | null) => {
|
|
||||||
if (attrName === 'class' && oldValue && newValue) {
|
|
||||||
const diff = diffClass(oldValue, newValue);
|
|
||||||
return !!diff.find((addedOrRemovedClass) => addedOrRemovedClass.indexOf(ignorePrefix) !== 0);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
const directionIsRTLCacheValuesFallback: CacheValues<boolean> = {
|
|
||||||
_value: false,
|
|
||||||
_previous: false,
|
|
||||||
_changed: false,
|
|
||||||
};
|
|
||||||
const heightIntrinsicCacheValuesFallback: CacheValues<boolean> = {
|
|
||||||
_value: false,
|
_value: false,
|
||||||
_previous: false,
|
_previous: false,
|
||||||
_changed: false,
|
_changed: false,
|
||||||
@@ -139,7 +101,7 @@ const lifecycleCommunicationFallback: LifecycleCommunication = {
|
|||||||
|
|
||||||
export const createLifecycleHub = (options: OSOptions, structureSetup: StructureSetup): LifecycleHubInstance => {
|
export const createLifecycleHub = (options: OSOptions, structureSetup: StructureSetup): LifecycleHubInstance => {
|
||||||
let lifecycleCommunication = lifecycleCommunicationFallback;
|
let lifecycleCommunication = lifecycleCommunicationFallback;
|
||||||
const { _host, _viewport, _content } = structureSetup._targetObj;
|
const { _viewport } = structureSetup._targetObj;
|
||||||
const {
|
const {
|
||||||
_nativeScrollbarStyling,
|
_nativeScrollbarStyling,
|
||||||
_nativeScrollbarIsOverlaid,
|
_nativeScrollbarIsOverlaid,
|
||||||
@@ -168,10 +130,11 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
|||||||
_contentMutation = force || false,
|
_contentMutation = force || false,
|
||||||
_paddingStyleChanged = force || false,
|
_paddingStyleChanged = force || false,
|
||||||
} = updateHints || {};
|
} = updateHints || {};
|
||||||
|
|
||||||
const finalDirectionIsRTL =
|
const finalDirectionIsRTL =
|
||||||
_directionIsRTL || (sizeObserver ? sizeObserver._getCurrentCacheValues(force)._directionIsRTL : directionIsRTLCacheValuesFallback);
|
_directionIsRTL || (_sizeObserver ? _sizeObserver._getCurrentCacheValues(force)._directionIsRTL : booleanCacheValuesFallback);
|
||||||
const finalHeightIntrinsic =
|
const finalHeightIntrinsic =
|
||||||
_heightIntrinsic || (trinsicObserver ? trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : heightIntrinsicCacheValuesFallback);
|
_heightIntrinsic || (_trinsicObserver ? _trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : booleanCacheValuesFallback);
|
||||||
const checkOption: LifecycleCheckOption = (path) => ({
|
const checkOption: LifecycleCheckOption = (path) => ({
|
||||||
_value: getPropByPath(options, path),
|
_value: getPropByPath(options, path),
|
||||||
_changed: force || getPropByPath(changedOptions, path) !== undefined,
|
_changed: force || getPropByPath(changedOptions, path) !== undefined,
|
||||||
@@ -180,6 +143,11 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
|||||||
const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport);
|
const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport);
|
||||||
const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport);
|
const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport);
|
||||||
|
|
||||||
|
// place before updating lifecycles because of possible flushing of debounce
|
||||||
|
if (_updateObserverOptions) {
|
||||||
|
_updateObserverOptions(checkOption);
|
||||||
|
}
|
||||||
|
|
||||||
each(lifecycles, (lifecycle) => {
|
each(lifecycles, (lifecycle) => {
|
||||||
const {
|
const {
|
||||||
_sizeChanged: adaptiveSizeChanged,
|
_sizeChanged: adaptiveSizeChanged,
|
||||||
@@ -217,58 +185,7 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
|
|||||||
options.callbacks.onUpdated();
|
options.callbacks.onUpdated();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const { _sizeObserver, _trinsicObserver, _updateObserverOptions } = lifecycleHubOservers(instance, updateLifecycles);
|
||||||
const onSizeChanged = (directionIsRTL?: CacheValues<boolean>) => {
|
|
||||||
const sizeChanged = !directionIsRTL;
|
|
||||||
updateLifecycles({
|
|
||||||
_directionIsRTL: directionIsRTL,
|
|
||||||
_sizeChanged: sizeChanged,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onTrinsicChanged = (heightIntrinsic: CacheValues<boolean>) => {
|
|
||||||
updateLifecycles({
|
|
||||||
_heightIntrinsic: heightIntrinsic,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onHostMutation = () => {
|
|
||||||
// TODO: rAF only here because IE
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
updateLifecycles({
|
|
||||||
_hostMutation: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const onContentMutation = () => {
|
|
||||||
// TODO: rAF only here because IE
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
updateLifecycles({
|
|
||||||
_contentMutation: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const trinsicObserver = (_content || !_flexboxGlue) && createTrinsicObserver(_host, onTrinsicChanged);
|
|
||||||
const sizeObserver = createSizeObserver(_host, onSizeChanged, { _appear: true, _direction: !_nativeScrollbarStyling });
|
|
||||||
const hostMutationObserver = createDOMObserver(_host, false, onHostMutation, {
|
|
||||||
_styleChangingAttributes: attrs,
|
|
||||||
_attributes: attrs,
|
|
||||||
_ignoreTargetChange: ignoreTargetChange,
|
|
||||||
});
|
|
||||||
const contentMutationObserver = createDOMObserver(_content || _viewport, true, onContentMutation, {
|
|
||||||
_styleChangingAttributes: attrs,
|
|
||||||
_attributes: attrs,
|
|
||||||
_eventContentChange: options!.updating!.elementEvents,
|
|
||||||
_nestedTargetSelector: hostSelector,
|
|
||||||
_ignoreContentChange: (mutation, isNestedTarget) => {
|
|
||||||
const { target, attributeName } = mutation;
|
|
||||||
return isNestedTarget
|
|
||||||
? false
|
|
||||||
: attributeName
|
|
||||||
? liesBetween(target as Element, hostSelector, viewportSelector) || liesBetween(target as Element, hostSelector, contentSelector)
|
|
||||||
: false;
|
|
||||||
},
|
|
||||||
_ignoreNestedTargetChange: ignoreTargetChange,
|
|
||||||
});
|
|
||||||
|
|
||||||
const update = (changedOptions?: Partial<OSOptions> | null, force?: boolean) => {
|
const update = (changedOptions?: Partial<OSOptions> | null, force?: boolean) => {
|
||||||
updateLifecycles(null, changedOptions, force);
|
updateLifecycles(null, changedOptions, force);
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { CacheValues, diffClass, debounce, isArray, isNumber } from 'support';
|
||||||
|
import { getEnvironment } from 'environment';
|
||||||
|
import { createSizeObserver, SizeObserverCallbackParams } from 'observers/sizeObserver';
|
||||||
|
import { createTrinsicObserver } from 'observers/trinsicObserver';
|
||||||
|
import { createDOMObserver, DOMObserver } from 'observers/domObserver';
|
||||||
|
import { LifecycleHub, LifecycleCheckOption, LifecycleUpdateHints } from 'lifecycles/lifecycleHub';
|
||||||
|
|
||||||
|
//const hostSelector = `.${classNameHost}`;
|
||||||
|
|
||||||
|
// TODO: observer textarea attrs if textarea
|
||||||
|
// TODO: tabindex, etc. attributes for viewport
|
||||||
|
// TODO: test _ignoreContentChange & _ignoreNestedTargetChange for content dom observer
|
||||||
|
// TODO: test _ignoreTargetChange for target dom observer
|
||||||
|
|
||||||
|
//const viewportSelector = `.${classNameViewport}`;
|
||||||
|
//const contentSelector = `.${classNameContent}`;
|
||||||
|
const ignorePrefix = 'os-';
|
||||||
|
const viewportAttrsFromTarget = ['tabindex'];
|
||||||
|
const baseStyleChangingAttrsTextarea = ['wrap', 'cols', 'rows'];
|
||||||
|
const baseStyleChangingAttrs = ['id', 'class', 'style', 'open'];
|
||||||
|
|
||||||
|
const ignoreTargetChange = (target: Node, attrName: string, oldValue: string | null, newValue: string | null) => {
|
||||||
|
if (attrName === 'class' && oldValue && newValue) {
|
||||||
|
const diff = diffClass(oldValue, newValue);
|
||||||
|
return !!diff.find((addedOrRemovedClass) => addedOrRemovedClass.indexOf(ignorePrefix) !== 0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const lifecycleHubOservers = (instance: LifecycleHub, updateLifecycles: (updateHints?: Partial<LifecycleUpdateHints> | null) => unknown) => {
|
||||||
|
let debounceTimeout: number | false | undefined;
|
||||||
|
let debounceMaxDelay: number | false | undefined;
|
||||||
|
const { _structureSetup } = instance;
|
||||||
|
const { _targetObj, _targetCtx } = _structureSetup;
|
||||||
|
const { _host, _viewport, _content } = _targetObj;
|
||||||
|
const { _isTextarea } = _targetCtx;
|
||||||
|
const { _nativeScrollbarStyling, _flexboxGlue } = getEnvironment();
|
||||||
|
const contentMutationObserverAttr = _isTextarea ? baseStyleChangingAttrsTextarea : baseStyleChangingAttrs.concat(baseStyleChangingAttrsTextarea);
|
||||||
|
const updateLifecyclesWithDebouncedAdaptiveUpdateHints = debounce(updateLifecycles as (updateHints: Partial<LifecycleUpdateHints>) => any, {
|
||||||
|
_timeout: () => debounceTimeout,
|
||||||
|
_maxDelay: () => debounceMaxDelay,
|
||||||
|
_mergeParams(prev, curr) {
|
||||||
|
const { _sizeChanged: prevSizeChanged, _hostMutation: prevHostMutation, _contentMutation: prevContentMutation } = prev[0];
|
||||||
|
const { _sizeChanged: currSizeChanged, _hostMutation: currvHostMutation, _contentMutation: currContentMutation } = curr[0];
|
||||||
|
const merged: [Partial<LifecycleUpdateHints>] = [
|
||||||
|
{
|
||||||
|
_sizeChanged: prevSizeChanged || currSizeChanged,
|
||||||
|
_hostMutation: prevHostMutation || currvHostMutation,
|
||||||
|
_contentMutation: prevContentMutation || currContentMutation,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onTrinsicChanged = (heightIntrinsic: CacheValues<boolean>) => {
|
||||||
|
updateLifecycles({
|
||||||
|
_heightIntrinsic: heightIntrinsic,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onSizeChanged = ({ _sizeChanged, _directionIsRTLCache, _appear }: SizeObserverCallbackParams) => {
|
||||||
|
const updateFn = !_sizeChanged || _appear ? updateLifecycles : updateLifecyclesWithDebouncedAdaptiveUpdateHints;
|
||||||
|
updateFn({
|
||||||
|
_sizeChanged,
|
||||||
|
_directionIsRTL: _directionIsRTLCache,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onContentMutation = (contentChangedTroughEvent: boolean) => {
|
||||||
|
// if contentChangedTroughEvent is true its already debounced
|
||||||
|
const updateFn = contentChangedTroughEvent ? updateLifecycles : updateLifecyclesWithDebouncedAdaptiveUpdateHints;
|
||||||
|
updateFn({
|
||||||
|
_contentMutation: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const onHostMutation = updateLifecyclesWithDebouncedAdaptiveUpdateHints.bind(0, {
|
||||||
|
_hostMutation: true,
|
||||||
|
}) as () => any;
|
||||||
|
|
||||||
|
const trinsicObserver = (_content || !_flexboxGlue) && createTrinsicObserver(_host, onTrinsicChanged);
|
||||||
|
const sizeObserver = createSizeObserver(_host, onSizeChanged, { _appear: true, _direction: !_nativeScrollbarStyling });
|
||||||
|
const hostMutationObserver = createDOMObserver(_host, false, onHostMutation, {
|
||||||
|
_styleChangingAttributes: baseStyleChangingAttrs,
|
||||||
|
_attributes: baseStyleChangingAttrs,
|
||||||
|
_ignoreTargetChange: ignoreTargetChange,
|
||||||
|
});
|
||||||
|
let contentMutationObserver: DOMObserver | undefined;
|
||||||
|
|
||||||
|
const updateOptions = (checkOption: LifecycleCheckOption) => {
|
||||||
|
const { _value: elementEvents, _changed: elementEventsChanged } = checkOption<Array<[string, string]> | null>('updating.elementEvents');
|
||||||
|
const { _value: attributes, _changed: attributesChanged } = checkOption<string[] | null>('updating.attributes');
|
||||||
|
const { _value: debounce, _changed: debounceChanged } = checkOption<Array<number> | number | null>('updating.debounce');
|
||||||
|
const updateContentMutationObserver = elementEventsChanged || attributesChanged;
|
||||||
|
|
||||||
|
if (updateContentMutationObserver) {
|
||||||
|
if (contentMutationObserver) {
|
||||||
|
contentMutationObserver._update();
|
||||||
|
contentMutationObserver._destroy();
|
||||||
|
}
|
||||||
|
contentMutationObserver = createDOMObserver(_content || _viewport, true, onContentMutation, {
|
||||||
|
_styleChangingAttributes: contentMutationObserverAttr.concat(attributes || []),
|
||||||
|
_attributes: contentMutationObserverAttr.concat(attributes || []),
|
||||||
|
_eventContentChange: elementEvents,
|
||||||
|
_ignoreNestedTargetChange: ignoreTargetChange,
|
||||||
|
//_nestedTargetSelector: hostSelector,
|
||||||
|
/*
|
||||||
|
_ignoreContentChange: (mutation, isNestedTarget) => {
|
||||||
|
const { target, attributeName } = mutation;
|
||||||
|
return isNestedTarget
|
||||||
|
? false
|
||||||
|
: attributeName
|
||||||
|
? liesBetween(target as Element, hostSelector, viewportSelector) || liesBetween(target as Element, hostSelector, contentSelector)
|
||||||
|
: false;
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debounceChanged) {
|
||||||
|
updateLifecyclesWithDebouncedAdaptiveUpdateHints._flush();
|
||||||
|
if (isArray(debounce)) {
|
||||||
|
const timeout = debounce[0];
|
||||||
|
const maxWait = debounce[1];
|
||||||
|
debounceTimeout = isNumber(timeout) ? timeout : false;
|
||||||
|
debounceMaxDelay = isNumber(maxWait) ? maxWait : false;
|
||||||
|
} else if (isNumber(debounce)) {
|
||||||
|
debounceTimeout = debounce;
|
||||||
|
debounceMaxDelay = false;
|
||||||
|
} else {
|
||||||
|
debounceTimeout = false;
|
||||||
|
debounceMaxDelay = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
_trinsicObserver: trinsicObserver,
|
||||||
|
_sizeObserver: sizeObserver,
|
||||||
|
_updateObserverOptions: updateOptions,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -13,12 +13,11 @@ import {
|
|||||||
find,
|
find,
|
||||||
push,
|
push,
|
||||||
isUndefined,
|
isUndefined,
|
||||||
isFunction,
|
|
||||||
} from 'support';
|
} from 'support';
|
||||||
|
|
||||||
type StringNullUndefined = string | null | undefined;
|
type StringNullUndefined = string | null | undefined;
|
||||||
|
|
||||||
type DOMContentObserverCallback = (contentChanged: boolean) => any;
|
type DOMContentObserverCallback = (contentChangedTroughEvent: boolean) => any;
|
||||||
|
|
||||||
type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChanged: boolean) => any;
|
type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChanged: boolean) => any;
|
||||||
|
|
||||||
@@ -38,18 +37,7 @@ interface DOMTargetObserverOptions extends DOMObserverOptionsBase {
|
|||||||
_ignoreTargetChange?: DOMObserverIgnoreTargetChange; // a function which will prevent marking certain attributes as changed if it returns true
|
_ignoreTargetChange?: DOMObserverIgnoreTargetChange; // a function which will prevent marking certain attributes as changed if it returns true
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DOMObserverBase {
|
type ContentChangeArrayItem = [StringNullUndefined, StringNullUndefined] | null | undefined;
|
||||||
_destroy: () => void;
|
|
||||||
_update: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DOMContentObserver extends DOMObserverBase {
|
|
||||||
_updateEventContentChange: (newEventContentChange?: DOMObserverEventContentChange) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DOMTargetObserver extends DOMObserverBase {}
|
|
||||||
|
|
||||||
type ContentChangeArrayItem = [StringNullUndefined, ((elms: Node[]) => StringNullUndefined) | StringNullUndefined] | null | undefined;
|
|
||||||
|
|
||||||
export type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
|
export type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
|
||||||
|
|
||||||
@@ -57,7 +45,7 @@ export type DOMObserverIgnoreContentChange = (
|
|||||||
mutation: MutationRecord,
|
mutation: MutationRecord,
|
||||||
isNestedTarget: boolean,
|
isNestedTarget: boolean,
|
||||||
domObserverTarget: HTMLElement,
|
domObserverTarget: HTMLElement,
|
||||||
domObserverOptions: DOMContentObserverOptions | undefined
|
domObserverOptions?: DOMContentObserverOptions
|
||||||
) => boolean;
|
) => boolean;
|
||||||
|
|
||||||
export type DOMObserverIgnoreTargetChange = (
|
export type DOMObserverIgnoreTargetChange = (
|
||||||
@@ -73,10 +61,10 @@ export type DOMObserverCallback<ContentObserver extends boolean> = ContentObserv
|
|||||||
|
|
||||||
export type DOMObserverOptions<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverOptions : DOMTargetObserverOptions;
|
export type DOMObserverOptions<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverOptions : DOMTargetObserverOptions;
|
||||||
|
|
||||||
export type DOMObserver<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserver : DOMTargetObserver;
|
export interface DOMObserver {
|
||||||
|
_destroy: () => void;
|
||||||
// const styleChangingAttributes = ['id', 'class', 'style', 'open'];
|
_update: () => void;
|
||||||
// const mutationObserverAttrsTextarea = ['wrap', 'cols', 'rows'];
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a set of helper functions to observe events of elements inside the target element.
|
* Creates a set of helper functions to observe events of elements inside the target element.
|
||||||
@@ -87,7 +75,6 @@ export type DOMObserver<ContentObserver extends boolean> = ContentObserver exten
|
|||||||
*/
|
*/
|
||||||
const createEventContentChange = (target: Element, eventContentChange: DOMObserverEventContentChange, callback: (...args: any) => any) => {
|
const createEventContentChange = (target: Element, eventContentChange: DOMObserverEventContentChange, callback: (...args: any) => any) => {
|
||||||
let map: Map<Node, string> | undefined;
|
let map: Map<Node, string> | undefined;
|
||||||
let eventContentChangeRef: DOMObserverEventContentChange;
|
|
||||||
const _destroy = () => {
|
const _destroy = () => {
|
||||||
if (map) {
|
if (map) {
|
||||||
map.forEach((eventName: string, elm: Node) => off(elm, eventName, callback));
|
map.forEach((eventName: string, elm: Node) => off(elm, eventName, callback));
|
||||||
@@ -95,16 +82,15 @@ const createEventContentChange = (target: Element, eventContentChange: DOMObserv
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
const _updateElements = (getElements?: (selector: string) => Node[]) => {
|
const _updateElements = (getElements?: (selector: string) => Node[]) => {
|
||||||
if (map && eventContentChangeRef) {
|
if (map && eventContentChange) {
|
||||||
const eventElmList = eventContentChangeRef.reduce<Array<[Node[], string]>>((arr, item) => {
|
const eventElmList = eventContentChange.reduce<Array<[Node[], string]>>((arr, item) => {
|
||||||
if (item) {
|
if (item) {
|
||||||
const selector = item[0];
|
const selector = item[0];
|
||||||
const eventNames = item[1];
|
const eventNames = item[1];
|
||||||
const elements = eventNames && selector && (getElements ? getElements(selector) : find(selector, target));
|
const elements = eventNames && selector && (getElements ? getElements(selector) : find(selector, target));
|
||||||
const parsedEventNames = isFunction(eventNames) ? eventNames(elements) : eventNames;
|
|
||||||
|
|
||||||
if (elements && elements.length && parsedEventNames && isString(parsedEventNames)) {
|
if (elements && elements.length && eventNames && isString(eventNames)) {
|
||||||
push(arr, [elements, parsedEventNames.trim()], true);
|
push(arr, [elements, eventNames.trim()], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
@@ -128,21 +114,16 @@ const createEventContentChange = (target: Element, eventContentChange: DOMObserv
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const _updateEventContentChange = (newEventContentChange: DOMObserverEventContentChange) => {
|
|
||||||
map = map || new Map<Node, string>();
|
|
||||||
eventContentChangeRef = newEventContentChange;
|
|
||||||
_destroy();
|
|
||||||
_updateElements();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (eventContentChange) {
|
if (eventContentChange) {
|
||||||
_updateEventContentChange(eventContentChange);
|
map = map || new Map<Node, string>();
|
||||||
|
_destroy();
|
||||||
|
_updateElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_destroy,
|
_destroy,
|
||||||
_updateElements,
|
_updateElements,
|
||||||
_updateEventContentChange,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,7 +140,7 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
|||||||
isContentObserver: ContentObserver,
|
isContentObserver: ContentObserver,
|
||||||
callback: DOMObserverCallback<ContentObserver>,
|
callback: DOMObserverCallback<ContentObserver>,
|
||||||
options?: DOMObserverOptions<ContentObserver>
|
options?: DOMObserverOptions<ContentObserver>
|
||||||
): DOMObserver<ContentObserver> => {
|
): DOMObserver => {
|
||||||
let isConnected = false;
|
let isConnected = false;
|
||||||
const {
|
const {
|
||||||
_attributes,
|
_attributes,
|
||||||
@@ -170,18 +151,17 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
|||||||
_ignoreNestedTargetChange,
|
_ignoreNestedTargetChange,
|
||||||
_ignoreContentChange,
|
_ignoreContentChange,
|
||||||
} = (options as DOMContentObserverOptions & DOMTargetObserverOptions) || {};
|
} = (options as DOMContentObserverOptions & DOMTargetObserverOptions) || {};
|
||||||
const {
|
const { _destroy: destroyEventContentChange, _updateElements: updateEventContentChangeElements } = createEventContentChange(
|
||||||
_destroy: destroyEventContentChange,
|
|
||||||
_updateElements: updateEventContentChangeElements,
|
|
||||||
_updateEventContentChange: updateEventContentChange,
|
|
||||||
} = createEventContentChange(
|
|
||||||
target,
|
target,
|
||||||
isContentObserver && _eventContentChange,
|
isContentObserver && _eventContentChange,
|
||||||
debounce(() => {
|
debounce(
|
||||||
if (isConnected) {
|
() => {
|
||||||
(callback as DOMContentObserverCallback)(true);
|
if (isConnected) {
|
||||||
}
|
(callback as DOMContentObserverCallback)(true);
|
||||||
}, 84)
|
}
|
||||||
|
},
|
||||||
|
{ _timeout: 33, _maxDelay: 99 }
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// MutationObserver
|
// MutationObserver
|
||||||
@@ -243,7 +223,7 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isContentObserver) {
|
if (isContentObserver) {
|
||||||
contentChanged && (callback as DOMContentObserverCallback)(contentChanged);
|
contentChanged && (callback as DOMContentObserverCallback)(false);
|
||||||
} else if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged) {
|
} else if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged) {
|
||||||
(callback as DOMTargetObserverCallback)(targetChangedAttrs, targetStyleChanged);
|
(callback as DOMTargetObserverCallback)(targetChangedAttrs, targetStyleChanged);
|
||||||
}
|
}
|
||||||
@@ -269,13 +249,10 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
|||||||
isConnected = false;
|
isConnected = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_updateEventContentChange: (newEventContentChange?: DOMObserverEventContentChange) => {
|
|
||||||
updateEventContentChange(isConnected && isContentObserver && newEventContentChange);
|
|
||||||
},
|
|
||||||
_update: () => {
|
_update: () => {
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
observerCallback(mutationObserver.takeRecords());
|
observerCallback(mutationObserver.takeRecords());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
} as DOMObserver<ContentObserver>;
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -33,7 +33,16 @@ import {
|
|||||||
classNameSizeObserverListenerItemFinal,
|
classNameSizeObserverListenerItemFinal,
|
||||||
} from 'classnames';
|
} from 'classnames';
|
||||||
|
|
||||||
export type SizeObserverOptions = { _direction?: boolean; _appear?: boolean };
|
export interface SizeObserverOptions {
|
||||||
|
_direction?: boolean;
|
||||||
|
_appear?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SizeObserverCallbackParams {
|
||||||
|
_sizeChanged: boolean;
|
||||||
|
_directionIsRTLCache?: CacheValues<boolean>;
|
||||||
|
_appear?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SizeObserver {
|
export interface SizeObserver {
|
||||||
_destroy(): void;
|
_destroy(): void;
|
||||||
@@ -73,7 +82,7 @@ const domRectHasDimensions = (rect?: DOMRectReadOnly) => rect && (rect.height ||
|
|||||||
*/
|
*/
|
||||||
export const createSizeObserver = (
|
export const createSizeObserver = (
|
||||||
target: HTMLElement,
|
target: HTMLElement,
|
||||||
onSizeChangedCallback: (directionIsRTLCache?: CacheValues<boolean>) => any,
|
onSizeChangedCallback: (params: SizeObserverCallbackParams) => any,
|
||||||
options?: SizeObserverOptions
|
options?: SizeObserverOptions
|
||||||
): SizeObserver => {
|
): SizeObserver => {
|
||||||
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } = options || {};
|
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } = options || {};
|
||||||
@@ -90,31 +99,44 @@ export const createSizeObserver = (
|
|||||||
(!domRectHasDimensions(currVal) && domRectHasDimensions(newVal))
|
(!domRectHasDimensions(currVal) && domRectHasDimensions(newVal))
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const onSizeChangedCallbackProxy = (sizeChangedContext?: CacheValues<boolean> | ResizeObserverEntry[] | Event) => {
|
const onSizeChangedCallbackProxy = (sizeChangedContext?: CacheValues<boolean> | ResizeObserverEntry[] | Event | boolean) => {
|
||||||
const hasDirectionCache = sizeChangedContext && isBoolean((sizeChangedContext as CacheValues<boolean>)._value);
|
const hasDirectionCache = sizeChangedContext && isBoolean((sizeChangedContext as CacheValues<boolean>)._value);
|
||||||
|
|
||||||
let skip = false;
|
let skip = false;
|
||||||
|
let appear: boolean | number | undefined = 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)
|
||||||
|
|
||||||
// if triggered from RO.
|
// if triggered from RO.
|
||||||
if (isArray(sizeChangedContext) && sizeChangedContext.length > 0) {
|
if (isArray(sizeChangedContext) && sizeChangedContext.length > 0) {
|
||||||
const { _previous, _value, _changed } = updateResizeObserverContentRectCache(0, sizeChangedContext.pop()!.contentRect);
|
const { _previous, _value } = updateResizeObserverContentRectCache(0, sizeChangedContext.pop()!.contentRect);
|
||||||
skip = !_previous || !domRectHasDimensions(_value); // skip on initial RO. call or if display is none
|
const hasDimensions = domRectHasDimensions(_value);
|
||||||
doDirectionScroll = !skip && _changed; // direction scroll when not skipping and changing from display: none to block, false otherwise
|
const hadDimensions = domRectHasDimensions(_previous);
|
||||||
|
skip = !_previous || !hasDimensions; // skip on initial RO. call or if display is none
|
||||||
|
appear = !hadDimensions && hasDimensions;
|
||||||
|
|
||||||
|
doDirectionScroll = !skip; // direction scroll when not skipping
|
||||||
}
|
}
|
||||||
// else if its triggered with DirectionCache
|
// else if its triggered with DirectionCache
|
||||||
else if (hasDirectionCache) {
|
else if (hasDirectionCache) {
|
||||||
doDirectionScroll = (sizeChangedContext as CacheValues<boolean>)._changed; // direction scroll when DirectionCache changed, false otherwise
|
doDirectionScroll = (sizeChangedContext as CacheValues<boolean>)._changed; // direction scroll when DirectionCache changed, false otherwise
|
||||||
}
|
}
|
||||||
|
// else if it triggered with appear from polyfill
|
||||||
|
else {
|
||||||
|
appear = sizeChangedContext === true;
|
||||||
|
}
|
||||||
|
|
||||||
if (observeDirectionChange) {
|
if (observeDirectionChange && doDirectionScroll) {
|
||||||
const rtl = hasDirectionCache ? (sizeChangedContext as CacheValues<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 CacheValues<boolean>) : undefined);
|
onSizeChangedCallback({
|
||||||
|
_sizeChanged: !hasDirectionCache,
|
||||||
|
_directionIsRTLCache: hasDirectionCache ? (sizeChangedContext as CacheValues<boolean>) : undefined,
|
||||||
|
_appear: !!appear,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const offListeners: (() => void)[] = [];
|
const offListeners: (() => void)[] = [];
|
||||||
@@ -147,11 +169,11 @@ export const createSizeObserver = (
|
|||||||
scrollLeft(shrinkElement, scrollAmount);
|
scrollLeft(shrinkElement, scrollAmount);
|
||||||
scrollTop(shrinkElement, scrollAmount);
|
scrollTop(shrinkElement, scrollAmount);
|
||||||
};
|
};
|
||||||
const onResized = () => {
|
const onResized = (appear?: unknown) => {
|
||||||
rAFId = 0;
|
rAFId = 0;
|
||||||
if (isDirty) {
|
if (isDirty) {
|
||||||
cacheSize = currSize;
|
cacheSize = currSize;
|
||||||
onSizeChangedCallbackProxy();
|
onSizeChangedCallbackProxy(appear === true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const onScroll = (scrollEvent?: Event | false) => {
|
const onScroll = (scrollEvent?: Event | false) => {
|
||||||
@@ -166,7 +188,7 @@ export const createSizeObserver = (
|
|||||||
rAFId = rAF!(onResized);
|
rAFId = rAF!(onResized);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
onResized();
|
onResized(scrollEvent === false);
|
||||||
}
|
}
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
|
|||||||
@@ -35,9 +35,8 @@ export interface OSOptions {
|
|||||||
paddingAbsolute: boolean;
|
paddingAbsolute: boolean;
|
||||||
updating: {
|
updating: {
|
||||||
elementEvents: Array<[string, string]> | null;
|
elementEvents: Array<[string, string]> | null;
|
||||||
contentMutationDebounce: number;
|
attributes: string[] | null;
|
||||||
hostMutationDebounce: number;
|
debounce: number | [number, number] | null;
|
||||||
resizeDebounce: number;
|
|
||||||
};
|
};
|
||||||
overflow: {
|
overflow: {
|
||||||
x: OverflowBehavior;
|
x: OverflowBehavior;
|
||||||
@@ -109,6 +108,7 @@ export interface UpdatedArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
|
const numberAllowedValues: OptionsTemplateValue<number> = oTypes.number;
|
||||||
|
const arrayNullValues: OptionsTemplateValue<Array<unknown> | null> = [oTypes.array, oTypes.null];
|
||||||
const stringArrayNullAllowedValues: OptionsTemplateValue<string | ReadonlyArray<string> | null> = [oTypes.string, oTypes.array, oTypes.null];
|
const stringArrayNullAllowedValues: OptionsTemplateValue<string | ReadonlyArray<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];
|
||||||
@@ -137,10 +137,12 @@ const defaultOptionsWithTemplate: OptionsWithOptionsTemplate<OSOptions> = {
|
|||||||
resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v
|
resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v
|
||||||
paddingAbsolute: booleanFalseTemplate, // true || false
|
paddingAbsolute: booleanFalseTemplate, // true || false
|
||||||
updating: {
|
updating: {
|
||||||
elementEvents: [[['img', 'load']], [oTypes.array, oTypes.null]], // array of tuples || null
|
elementEvents: [[['img', 'load']], arrayNullValues], // array of tuples || null
|
||||||
contentMutationDebounce: [80, numberAllowedValues], // number
|
attributes: [null, arrayNullValues],
|
||||||
hostMutationDebounce: [0, numberAllowedValues], // number
|
debounce: [
|
||||||
resizeDebounce: [0, numberAllowedValues], // number
|
[0, 33],
|
||||||
|
[oTypes.number, oTypes.array, oTypes.null],
|
||||||
|
], // number || number array || null
|
||||||
},
|
},
|
||||||
overflow: {
|
overflow: {
|
||||||
x: ['scroll', overflowAllowedValues], // 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
|
||||||
|
|||||||
@@ -1,36 +1,103 @@
|
|||||||
import { isNumber } from 'support/utils/types';
|
import { isNumber, isFunction } from 'support/utils/types';
|
||||||
import { cAF, rAF } from 'support/compatibility/apis';
|
import { rAF, cAF } from 'support/compatibility/apis';
|
||||||
|
|
||||||
|
const setT = window.setTimeout;
|
||||||
|
const clearTimeouts = (id: number | undefined) => {
|
||||||
|
id && window.clearTimeout(id);
|
||||||
|
id && cAF!(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
type DebounceTiming = number | false | null | undefined;
|
||||||
|
|
||||||
|
export interface DebounceOptions<FunctionToDebounce extends (...args: any) => any> {
|
||||||
|
/**
|
||||||
|
* The timeout for debouncing. If null, no debounce is applied.
|
||||||
|
*/
|
||||||
|
_timeout?: DebounceTiming | (() => DebounceTiming);
|
||||||
|
/**
|
||||||
|
* A maximum amount of ms. before the function will be called even with debounce.
|
||||||
|
*/
|
||||||
|
_maxDelay?: DebounceTiming | (() => DebounceTiming);
|
||||||
|
/**
|
||||||
|
* Function which merges parameters for each canceled debounce.
|
||||||
|
* If parameters can't be merged the function will return null, otherwise it returns the merged parameters.
|
||||||
|
*/
|
||||||
|
_mergeParams?: (
|
||||||
|
prev: Parameters<FunctionToDebounce>,
|
||||||
|
curr: Parameters<FunctionToDebounce>
|
||||||
|
) => Parameters<FunctionToDebounce> | false | null | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Debounced<FunctionToDebounce extends (...args: any) => any> {
|
||||||
|
(...args: Parameters<FunctionToDebounce>): ReturnType<FunctionToDebounce>;
|
||||||
|
_flush(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export const noop = () => {}; // eslint-disable-line
|
export const noop = () => {}; // eslint-disable-line
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debounces the given function either with a timeout or a animation frame.
|
* Debounces the given function either with a timeout or a animation frame.
|
||||||
* @param functionToDebounce The function which shall be debounced.
|
* @param functionToDebounce The function which shall be debounced.
|
||||||
* @param timeout The timeout for debouncing. If 0 or lower animation frame is used for debouncing, a timeout otherwise.
|
* @param options Options for debouncing.
|
||||||
* @param maxWait A maximum amount of ms. before the function will be called even with debounce.
|
|
||||||
*/
|
*/
|
||||||
export const debounce = (functionToDebounce: (...args: any) => any, timeout?: number, maxWait?: number) => {
|
export const debounce = <FunctionToDebounce extends (...args: any) => any>(
|
||||||
let timeoutId: number | void;
|
functionToDebounce: FunctionToDebounce,
|
||||||
let lastCallTime: number;
|
options: DebounceOptions<FunctionToDebounce>
|
||||||
const hasTimeout = isNumber(timeout) && timeout > 0;
|
): Debounced<FunctionToDebounce> => {
|
||||||
const hasMaxWait = isNumber(maxWait) && maxWait > 0;
|
let timeoutId: number | undefined;
|
||||||
const cancel = hasTimeout ? window.clearTimeout : cAF!;
|
let maxTimeoutId: number | undefined;
|
||||||
const set = hasTimeout ? window.setTimeout : rAF!;
|
let prevArguments: Parameters<FunctionToDebounce> | null | undefined;
|
||||||
const setFn = function (args: IArguments) {
|
let latestArguments: Parameters<FunctionToDebounce> | null | undefined;
|
||||||
lastCallTime = hasMaxWait ? performance.now() : 0;
|
const { _timeout, _maxDelay, _mergeParams } = options;
|
||||||
timeoutId && cancel(timeoutId);
|
|
||||||
|
const invokeFunctionToDebounce = function (args: IArguments) {
|
||||||
|
clearTimeouts(timeoutId);
|
||||||
|
clearTimeouts(maxTimeoutId);
|
||||||
|
maxTimeoutId = timeoutId = prevArguments = undefined;
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
functionToDebounce.apply(this, args);
|
functionToDebounce.apply(this, args);
|
||||||
};
|
};
|
||||||
|
|
||||||
return function () {
|
const mergeParms = (curr: Parameters<FunctionToDebounce>): Parameters<FunctionToDebounce> | false | null | undefined =>
|
||||||
// eslint-disable-next-line
|
_mergeParams && prevArguments ? _mergeParams(prevArguments, curr) : curr;
|
||||||
// @ts-ignore
|
|
||||||
const boundSetFn = setFn.bind(this, arguments); // eslint-disable-line
|
|
||||||
const forceCall = hasMaxWait ? performance.now() - lastCallTime >= maxWait! : false;
|
|
||||||
|
|
||||||
timeoutId && cancel(timeoutId);
|
const flush = () => {
|
||||||
timeoutId = forceCall ? boundSetFn() : (set(boundSetFn, timeout!) as number);
|
if (timeoutId) {
|
||||||
|
invokeFunctionToDebounce(mergeParms(latestArguments!) || latestArguments!);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const debouncedFn = function () {
|
||||||
|
const args: Parameters<FunctionToDebounce> = arguments as Parameters<FunctionToDebounce>;
|
||||||
|
const finalTimeout = isFunction(_timeout) ? _timeout() : _timeout;
|
||||||
|
const hasTimeout = isNumber(finalTimeout) && finalTimeout >= 0;
|
||||||
|
|
||||||
|
if (hasTimeout) {
|
||||||
|
const finalMaxWait = isFunction(_maxDelay) ? _maxDelay() : _maxDelay;
|
||||||
|
const hasMaxWait = isNumber(finalMaxWait) && finalMaxWait >= 0;
|
||||||
|
const setTimeoutFn = finalTimeout! > 0 ? setT : rAF!;
|
||||||
|
const mergeParamsResult = mergeParms(args);
|
||||||
|
const invokedArgs = mergeParamsResult || args;
|
||||||
|
const boundInvoke = invokeFunctionToDebounce.bind(0, invokedArgs);
|
||||||
|
|
||||||
|
if (!mergeParamsResult) {
|
||||||
|
invokeFunctionToDebounce(prevArguments || args);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeouts(timeoutId);
|
||||||
|
timeoutId = setTimeoutFn(boundInvoke, finalTimeout as number) as number;
|
||||||
|
|
||||||
|
if (hasMaxWait && !maxTimeoutId) {
|
||||||
|
maxTimeoutId = setT(flush, finalMaxWait as number);
|
||||||
|
}
|
||||||
|
|
||||||
|
prevArguments = latestArguments = invokedArgs;
|
||||||
|
} else {
|
||||||
|
invokeFunctionToDebounce(args);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debouncedFn._flush = flush;
|
||||||
|
|
||||||
|
return debouncedFn as Debounced<FunctionToDebounce>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,24 +4,14 @@ import should from 'should';
|
|||||||
import { generateSelectCallback, iterateSelect } from '@/testing-browser/Select';
|
import { generateSelectCallback, iterateSelect } from '@/testing-browser/Select';
|
||||||
import { timeout } from '@/testing-browser/timeout';
|
import { timeout } from '@/testing-browser/timeout';
|
||||||
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
|
||||||
import {
|
import { appendChildren, createDiv, removeElements, children, isArray, isNumber, liesBetween, addClass, removeClass, diffClass, on } from 'support';
|
||||||
appendChildren,
|
|
||||||
createDiv,
|
|
||||||
removeElements,
|
|
||||||
children,
|
|
||||||
isArray,
|
|
||||||
isNumber,
|
|
||||||
liesBetween,
|
|
||||||
hasClass,
|
|
||||||
addClass,
|
|
||||||
removeClass,
|
|
||||||
diffClass,
|
|
||||||
on,
|
|
||||||
} from 'support';
|
|
||||||
|
|
||||||
import { createDOMObserver } from 'observers/domObserver';
|
import { createDOMObserver } from 'observers/domObserver';
|
||||||
|
|
||||||
type DOMContentObserverResult = boolean;
|
type DOMContentObserverResult = {
|
||||||
|
contentChange: boolean;
|
||||||
|
troughEvent: boolean;
|
||||||
|
};
|
||||||
type DOMTargetObserverResult = {
|
type DOMTargetObserverResult = {
|
||||||
changedTargetAttrs: string[];
|
changedTargetAttrs: string[];
|
||||||
styleChanged: boolean;
|
styleChanged: boolean;
|
||||||
@@ -34,6 +24,7 @@ interface SeparateChangeThrough {
|
|||||||
const targetChangesCountSlot: HTMLElement | null = document.querySelector('#targetChanges');
|
const targetChangesCountSlot: HTMLElement | null = document.querySelector('#targetChanges');
|
||||||
const contentChangesCountSlot: HTMLElement | null = document.querySelector('#contentChanges');
|
const contentChangesCountSlot: HTMLElement | null = document.querySelector('#contentChanges');
|
||||||
const targetElm: HTMLElement | null = document.querySelector('#target');
|
const targetElm: HTMLElement | null = document.querySelector('#target');
|
||||||
|
const trargetContentElm: HTMLElement | null = document.querySelector('#target .content');
|
||||||
const targetElmContentElm: HTMLElement | null = document.querySelector('#content-host');
|
const targetElmContentElm: HTMLElement | null = document.querySelector('#content-host');
|
||||||
const contentElmAttrChange: HTMLElement | null = document.querySelector('#target .content-nest');
|
const contentElmAttrChange: HTMLElement | null = document.querySelector('#target .content-nest');
|
||||||
const contentBetweenElmAttrChange: HTMLElement | null = document.querySelector('#content-host .padding-nest-item');
|
const contentBetweenElmAttrChange: HTMLElement | null = document.querySelector('#content-host .padding-nest-item');
|
||||||
@@ -67,7 +58,7 @@ const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
|||||||
const hostSelector = '.host';
|
const hostSelector = '.host';
|
||||||
const ignorePrefix = 'ignore';
|
const ignorePrefix = 'ignore';
|
||||||
const attrs = ['id', 'class', 'style', 'open'];
|
const attrs = ['id', 'class', 'style', 'open'];
|
||||||
const contentChangeArr: Array<[string, string | ((elms: Node[]) => string)]> = [['img', 'load']];
|
const contentChangeArr: Array<[string, string]> = [['img', 'load']];
|
||||||
const domTargetObserverObservations: DOMTargetObserverResult[] = [];
|
const domTargetObserverObservations: DOMTargetObserverResult[] = [];
|
||||||
const domContentObserverObservations: DOMContentObserverResult[] = [];
|
const domContentObserverObservations: DOMContentObserverResult[] = [];
|
||||||
|
|
||||||
@@ -115,44 +106,48 @@ const targetDomObserver = createDOMObserver(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentDomObserver = createDOMObserver(
|
const createContentDomOserver = (eventContentChange: Array<[string | null | undefined, string | null | undefined] | null | undefined>) => {
|
||||||
document.querySelector('#target .content')!,
|
return createDOMObserver(
|
||||||
true,
|
trargetContentElm!,
|
||||||
(contentChanged: boolean) => {
|
true,
|
||||||
should.equal(typeof contentChanged, 'boolean', 'The contentChanged parameter in a content dom observer must be a boolean.');
|
(contentChangedTroughEvent: boolean) => {
|
||||||
|
should.equal(typeof contentChangedTroughEvent, 'boolean', 'The contentChanged parameter in a content dom observer must be a boolean.');
|
||||||
|
|
||||||
domContentObserverObservations.push(contentChanged);
|
domContentObserverObservations.push({ contentChange: true, troughEvent: contentChangedTroughEvent });
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (contentChangesCountSlot) {
|
if (contentChangesCountSlot) {
|
||||||
contentChangesCountSlot.textContent = `${domContentObserverObservations.length}`;
|
contentChangesCountSlot.textContent = `${domContentObserverObservations.length}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
_styleChangingAttributes: attrs,
|
|
||||||
_attributes: attrs,
|
|
||||||
_eventContentChange: contentChangeArr,
|
|
||||||
_nestedTargetSelector: hostSelector,
|
|
||||||
_ignoreContentChange: (mutation, isNestedTarget) => {
|
|
||||||
const { target, attributeName } = mutation;
|
|
||||||
return isNestedTarget ? false : attributeName ? liesBetween(target as Element, hostSelector, '.content') : false;
|
|
||||||
},
|
},
|
||||||
_ignoreNestedTargetChange: (target, attrName, oldValue, newValue) => {
|
{
|
||||||
if (attrName === 'class' && oldValue && newValue) {
|
_styleChangingAttributes: attrs,
|
||||||
const diff = diffClass(oldValue, newValue);
|
_attributes: attrs,
|
||||||
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
|
_eventContentChange: eventContentChange,
|
||||||
return ignore;
|
_nestedTargetSelector: hostSelector,
|
||||||
}
|
_ignoreContentChange: (mutation, isNestedTarget) => {
|
||||||
return false;
|
const { target, attributeName } = mutation;
|
||||||
},
|
return isNestedTarget ? false : attributeName ? liesBetween(target as Element, hostSelector, '.content') : false;
|
||||||
// @ts-ignore
|
},
|
||||||
_ignoreTargetChange: () => {
|
_ignoreNestedTargetChange: (target, attrName, oldValue, newValue) => {
|
||||||
// if param: isContentObserver = true, this function should never be called.
|
if (attrName === 'class' && oldValue && newValue) {
|
||||||
should.ok(false, 'A content dom observer must not call the _ignoreTargetChange method.');
|
const diff = diffClass(oldValue, newValue);
|
||||||
return true;
|
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
|
||||||
},
|
return ignore;
|
||||||
}
|
}
|
||||||
);
|
return false;
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
_ignoreTargetChange: () => {
|
||||||
|
// if param: isContentObserver = true, this function should never be called.
|
||||||
|
should.ok(false, 'A content dom observer must not call the _ignoreTargetChange method.');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let contentDomObserver = createContentDomOserver(contentChangeArr);
|
||||||
|
|
||||||
const getTotalObservations = () => domTargetObserverObservations.length + domContentObserverObservations.length;
|
const getTotalObservations = () => domTargetObserverObservations.length + domContentObserverObservations.length;
|
||||||
const getLast = <T>(arr: T[], indexFromLast = 0): T => arr[arr.length - 1 - indexFromLast] || ({} as T);
|
const getLast = <T>(arr: T[], indexFromLast = 0): T => arr[arr.length - 1 - indexFromLast] || ({} as T);
|
||||||
@@ -277,7 +272,7 @@ const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMCo
|
|||||||
if (addChangeThrough) {
|
if (addChangeThrough) {
|
||||||
const contentChanged = getLast(addChangeThrough);
|
const contentChanged = getLast(addChangeThrough);
|
||||||
await waitForOrFailTest(() => {
|
await waitForOrFailTest(() => {
|
||||||
should.equal(contentChanged, true, 'Adding an content element must result in a content change.');
|
should.deepEqual(contentChanged, { contentChange: true, troughEvent: false }, 'Adding an content element must result in a content change.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -297,7 +292,11 @@ const addRemoveElementsTest = async (slot: Element | null, changeThrough?: DOMCo
|
|||||||
|
|
||||||
if (removeChangeThrough) {
|
if (removeChangeThrough) {
|
||||||
const contentChanged = getLast(removeChangeThrough);
|
const contentChanged = getLast(removeChangeThrough);
|
||||||
should.equal(contentChanged, true, 'Removing an content element must result in a content change.');
|
should.deepEqual(
|
||||||
|
contentChanged,
|
||||||
|
{ contentChange: true, troughEvent: false },
|
||||||
|
'Removing an content element must result in a content change.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -361,10 +360,14 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
compare(2);
|
compare(2);
|
||||||
|
|
||||||
const previousContentChanged = getLast(domContentObserverObservations, 1);
|
const previousContentChanged = getLast(domContentObserverObservations, 1);
|
||||||
should.equal(previousContentChanged, true, 'Adding an content image must result in a content change.');
|
should.deepEqual(
|
||||||
|
previousContentChanged,
|
||||||
|
{ contentChange: true, troughEvent: false },
|
||||||
|
'Adding an content image must result in a content change.'
|
||||||
|
);
|
||||||
|
|
||||||
const lastContentChanged = getLast(domContentObserverObservations);
|
const lastContentChanged = getLast(domContentObserverObservations);
|
||||||
should.equal(lastContentChanged, true, 'The images load event must result in a content change.');
|
should.deepEqual(lastContentChanged, { contentChange: true, troughEvent: true }, 'The images load event must result in a content change.');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -375,21 +378,20 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
// test event content change debounce
|
// test event content change debounce
|
||||||
const addMultiple = async () => {
|
const addMultiple = async () => {
|
||||||
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
||||||
const addMultipleItem = () => {
|
const genImage = () => {
|
||||||
const img = new Image(1, 1);
|
const img = new Image(1, 1);
|
||||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
||||||
|
|
||||||
const imgHolder = createDiv('img');
|
const imgHolder = createDiv('img');
|
||||||
appendChildren(imgHolder, img);
|
appendChildren(imgHolder, img);
|
||||||
|
|
||||||
appendChildren(imgElmsSlot, imgHolder);
|
return imgHolder;
|
||||||
};
|
};
|
||||||
|
|
||||||
before();
|
await timeout(250);
|
||||||
|
|
||||||
addMultipleItem();
|
before();
|
||||||
addMultipleItem();
|
appendChildren(imgElmsSlot, [genImage(), genImage(), genImage()]);
|
||||||
addMultipleItem();
|
|
||||||
|
|
||||||
await timeout(250);
|
await timeout(250);
|
||||||
|
|
||||||
@@ -398,20 +400,27 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
compare(2);
|
compare(2);
|
||||||
|
|
||||||
const previousContentChanged = getLast(domContentObserverObservations, 1);
|
const previousContentChanged = getLast(domContentObserverObservations, 1);
|
||||||
should.equal(previousContentChanged, true, 'Adding mutliple content images must result in a single content change. (debounced)');
|
should.deepEqual(
|
||||||
|
previousContentChanged,
|
||||||
|
{ contentChange: true, troughEvent: false },
|
||||||
|
'Adding mutliple content images must result in a single content change. (debounced)'
|
||||||
|
);
|
||||||
|
|
||||||
const lastContentChanged = getLast(domContentObserverObservations);
|
const lastContentChanged = getLast(domContentObserverObservations);
|
||||||
should.equal(lastContentChanged, true, 'Multiple images load events must result in a single cintent change. (debounced)');
|
should.deepEqual(
|
||||||
|
lastContentChanged,
|
||||||
|
{ contentChange: true, troughEvent: true },
|
||||||
|
'Multiple images load events must result in a single cintent change. (debounced)'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
await addMultiple();
|
await addMultiple();
|
||||||
|
|
||||||
// remove load event from image test
|
// remove load event from image test
|
||||||
const addChanged = async (
|
const addChanged = async (newEventContentChange: Array<[string | null | undefined, string | null | undefined] | null | undefined>) => {
|
||||||
newEventContentChange: Array<[string | null | undefined, (() => string | null | undefined) | string | null | undefined] | null | undefined>
|
contentDomObserver._destroy();
|
||||||
) => {
|
contentDomObserver = createContentDomOserver(newEventContentChange);
|
||||||
contentDomObserver._updateEventContentChange(newEventContentChange);
|
|
||||||
|
|
||||||
const img = new Image(1, 1);
|
const img = new Image(1, 1);
|
||||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
||||||
@@ -430,7 +439,8 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
compare(1);
|
compare(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
contentDomObserver._updateEventContentChange(contentChangeArr);
|
contentDomObserver._destroy();
|
||||||
|
contentDomObserver = createContentDomOserver(contentChangeArr);
|
||||||
};
|
};
|
||||||
|
|
||||||
await addChanged([
|
await addChanged([
|
||||||
@@ -440,9 +450,6 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
['img', undefined],
|
['img', undefined],
|
||||||
[null, null],
|
[null, null],
|
||||||
[undefined, undefined],
|
[undefined, undefined],
|
||||||
['img', () => 'hi'],
|
|
||||||
['img', () => null],
|
|
||||||
['img', () => undefined],
|
|
||||||
null,
|
null,
|
||||||
undefined,
|
undefined,
|
||||||
]);
|
]);
|
||||||
@@ -471,7 +478,11 @@ const addRemoveTransitionElmsFn = async () => {
|
|||||||
compareTransition(expectTransitionEndContentChange ? 2 : 1); // 2 because 1: added class mutation and 2: transition end event
|
compareTransition(expectTransitionEndContentChange ? 2 : 1); // 2 because 1: added class mutation and 2: transition end event
|
||||||
|
|
||||||
const contentChanged = getLast(domContentObserverObservations);
|
const contentChanged = getLast(domContentObserverObservations);
|
||||||
should.equal(contentChanged, true, 'The transitionend event must trigger a event content change.');
|
should.deepEqual(
|
||||||
|
contentChanged,
|
||||||
|
{ contentChange: true, troughEvent: expectTransitionEndContentChange },
|
||||||
|
'The transitionend event must trigger a event content change.'
|
||||||
|
);
|
||||||
resolve(1);
|
resolve(1);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -495,11 +506,16 @@ const addRemoveTransitionElmsFn = async () => {
|
|||||||
compare(1);
|
compare(1);
|
||||||
|
|
||||||
const contentChanged = getLast(domContentObserverObservations);
|
const contentChanged = getLast(domContentObserverObservations);
|
||||||
should.equal(contentChanged, true, 'Adding an content element (transition) must result in a content change.');
|
should.deepEqual(
|
||||||
|
contentChanged,
|
||||||
|
{ contentChange: true, troughEvent: false },
|
||||||
|
'Adding an content element (transition) must result in a content change.'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await startTransition(elm, expectTransitionEndContentChange && true);
|
await startTransition(elm, expectTransitionEndContentChange && true);
|
||||||
contentDomObserver._updateEventContentChange(contentChangeArr);
|
contentDomObserver._destroy();
|
||||||
|
contentDomObserver = createContentDomOserver(contentChangeArr);
|
||||||
await startTransition(elm, expectTransitionEndContentChange && false);
|
await startTransition(elm, expectTransitionEndContentChange && false);
|
||||||
|
|
||||||
removeElements(elm);
|
removeElements(elm);
|
||||||
@@ -509,19 +525,8 @@ const addRemoveTransitionElmsFn = async () => {
|
|||||||
|
|
||||||
await add(false);
|
await add(false);
|
||||||
|
|
||||||
contentDomObserver._updateEventContentChange(
|
contentDomObserver._destroy();
|
||||||
contentChangeArr.concat([
|
contentDomObserver = createContentDomOserver(contentChangeArr.concat([['.transition', 'transitionend']]));
|
||||||
[
|
|
||||||
'.transition',
|
|
||||||
(elms) => {
|
|
||||||
elms.forEach((elm) => {
|
|
||||||
should.equal(hasClass(elm as Element, 'transition'), true, 'Every checked element must match the correpsonding selector.'); // in this case "".transition"
|
|
||||||
});
|
|
||||||
return 'transitionend';
|
|
||||||
},
|
|
||||||
],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
await add(true);
|
await add(true);
|
||||||
};
|
};
|
||||||
@@ -562,7 +567,11 @@ const iterateTargetAttrChange = async () => {
|
|||||||
const iterateContentAttrChange = async () => {
|
const iterateContentAttrChange = async () => {
|
||||||
await iterateAttrChange(setContentAttr, domContentObserverObservations, (observation) => {
|
await iterateAttrChange(setContentAttr, domContentObserverObservations, (observation) => {
|
||||||
const contentChanged = observation;
|
const contentChanged = observation;
|
||||||
should.equal(contentChanged, true, 'A attribute change inside the content must trigger a content change for a DOMContentObserver.');
|
should.deepEqual(
|
||||||
|
contentChanged,
|
||||||
|
{ contentChange: true, troughEvent: false },
|
||||||
|
'A attribute change inside the content must trigger a content change for a DOMContentObserver.'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await iterateAttrChange(setFilteredContentAttr);
|
await iterateAttrChange(setFilteredContentAttr);
|
||||||
};
|
};
|
||||||
@@ -573,7 +582,11 @@ const iterateContentBetweenAttrChange = async () => {
|
|||||||
const iterateContentHostElmAttrChange = async () => {
|
const iterateContentHostElmAttrChange = async () => {
|
||||||
await iterateAttrChange(setContentHostElmAttr, domContentObserverObservations, (observation) => {
|
await iterateAttrChange(setContentHostElmAttr, domContentObserverObservations, (observation) => {
|
||||||
const contentChanged = observation;
|
const contentChanged = observation;
|
||||||
should.equal(contentChanged, true, 'A attribute change for a nested target must trigger a content change for a DOMContentObserver.');
|
should.deepEqual(
|
||||||
|
contentChanged,
|
||||||
|
{ contentChange: true, troughEvent: false },
|
||||||
|
'A attribute change for a nested target must trigger a content change for a DOMContentObserver.'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
await iterateAttrChange(setFilteredContentHostElmAttr);
|
await iterateAttrChange(setFilteredContentHostElmAttr);
|
||||||
};
|
};
|
||||||
@@ -626,11 +639,9 @@ const start = async () => {
|
|||||||
targetDomObserver._destroy();
|
targetDomObserver._destroy();
|
||||||
targetDomObserver._update();
|
targetDomObserver._update();
|
||||||
|
|
||||||
contentDomObserver._updateEventContentChange([]);
|
|
||||||
contentDomObserver._update();
|
contentDomObserver._update();
|
||||||
contentDomObserver._destroy();
|
contentDomObserver._destroy();
|
||||||
contentDomObserver._destroy();
|
contentDomObserver._destroy();
|
||||||
contentDomObserver._updateEventContentChange([]);
|
|
||||||
contentDomObserver._update();
|
contentDomObserver._update();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,14 @@ const preInitChildren = targetElm?.children.length;
|
|||||||
|
|
||||||
const sizeObserver = createSizeObserver(
|
const sizeObserver = createSizeObserver(
|
||||||
targetElm as HTMLElement,
|
targetElm as HTMLElement,
|
||||||
(directionIsRTLCache?: any) => {
|
({ _directionIsRTLCache, _sizeChanged, _appear }) => {
|
||||||
if (directionIsRTLCache) {
|
if (_sizeChanged) {
|
||||||
directionIterations += 1;
|
|
||||||
} else {
|
|
||||||
sizeIterations += 1;
|
sizeIterations += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_directionIsRTLCache) {
|
||||||
|
directionIterations += 1;
|
||||||
|
}
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (resizesSlot) {
|
if (resizesSlot) {
|
||||||
resizesSlot.textContent = (directionIterations + sizeIterations).toString();
|
resizesSlot.textContent = (directionIterations + sizeIterations).toString();
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { LifecycleHub, LifecycleCheckOption, LifecycleUpdateHints } from 'lifecycles/lifecycleHub';
|
||||||
|
export declare const lifecycleHubOservers: (instance: LifecycleHub, updateLifecycles: (updateHints?: Partial<LifecycleUpdateHints> | null | undefined) => unknown) => {
|
||||||
|
_trinsicObserver: false | import("observers/trinsicObserver").TrinsicObserver;
|
||||||
|
_sizeObserver: import("observers/sizeObserver").SizeObserver;
|
||||||
|
_updateObserverOptions: (checkOption: LifecycleCheckOption) => void;
|
||||||
|
};
|
||||||
+8
-14
@@ -1,5 +1,5 @@
|
|||||||
declare type StringNullUndefined = string | null | undefined;
|
declare type StringNullUndefined = string | null | undefined;
|
||||||
declare type DOMContentObserverCallback = (contentChanged: boolean) => any;
|
declare type DOMContentObserverCallback = (contentChangedTroughEvent: boolean) => any;
|
||||||
declare type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChanged: boolean) => any;
|
declare type DOMTargetObserverCallback = (targetChangedAttrs: string[], targetStyleChanged: boolean) => any;
|
||||||
interface DOMObserverOptionsBase {
|
interface DOMObserverOptionsBase {
|
||||||
_attributes?: string[];
|
_attributes?: string[];
|
||||||
@@ -14,22 +14,16 @@ interface DOMContentObserverOptions extends DOMObserverOptionsBase {
|
|||||||
interface DOMTargetObserverOptions extends DOMObserverOptionsBase {
|
interface DOMTargetObserverOptions extends DOMObserverOptionsBase {
|
||||||
_ignoreTargetChange?: DOMObserverIgnoreTargetChange;
|
_ignoreTargetChange?: DOMObserverIgnoreTargetChange;
|
||||||
}
|
}
|
||||||
interface DOMObserverBase {
|
declare type ContentChangeArrayItem = [StringNullUndefined, StringNullUndefined] | null | undefined;
|
||||||
_destroy: () => void;
|
|
||||||
_update: () => void;
|
|
||||||
}
|
|
||||||
interface DOMContentObserver extends DOMObserverBase {
|
|
||||||
_updateEventContentChange: (newEventContentChange?: DOMObserverEventContentChange) => void;
|
|
||||||
}
|
|
||||||
interface DOMTargetObserver extends DOMObserverBase {
|
|
||||||
}
|
|
||||||
declare type ContentChangeArrayItem = [StringNullUndefined, ((elms: Node[]) => StringNullUndefined) | StringNullUndefined] | null | undefined;
|
|
||||||
export declare type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
|
export declare type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
|
||||||
export declare type DOMObserverIgnoreContentChange = (mutation: MutationRecord, isNestedTarget: boolean, domObserverTarget: HTMLElement, domObserverOptions: DOMContentObserverOptions | undefined) => boolean;
|
export declare type DOMObserverIgnoreContentChange = (mutation: MutationRecord, isNestedTarget: boolean, domObserverTarget: HTMLElement, domObserverOptions?: DOMContentObserverOptions) => boolean;
|
||||||
export declare type DOMObserverIgnoreTargetChange = (target: Node, attributeName: string, oldAttributeValue: string | null, newAttributeValue: string | null) => boolean;
|
export declare type DOMObserverIgnoreTargetChange = (target: Node, attributeName: string, oldAttributeValue: string | null, newAttributeValue: string | null) => boolean;
|
||||||
export declare type DOMObserverCallback<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverCallback : DOMTargetObserverCallback;
|
export declare type DOMObserverCallback<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverCallback : DOMTargetObserverCallback;
|
||||||
export declare type DOMObserverOptions<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverOptions : DOMTargetObserverOptions;
|
export declare type DOMObserverOptions<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverOptions : DOMTargetObserverOptions;
|
||||||
export declare type DOMObserver<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserver : DOMTargetObserver;
|
export interface DOMObserver {
|
||||||
|
_destroy: () => void;
|
||||||
|
_update: () => void;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Creates a DOM observer which observes DOM changes to either the target element or its children.
|
* Creates a DOM observer which observes DOM changes to either the target element or its children.
|
||||||
* @param target The element which shall be observed.
|
* @param target The element which shall be observed.
|
||||||
@@ -38,5 +32,5 @@ export declare type DOMObserver<ContentObserver extends boolean> = ContentObserv
|
|||||||
* @param options The options for DOM change detection.
|
* @param options The options for DOM change detection.
|
||||||
* @returns A object which represents the instance of the DOM observer.
|
* @returns A object which represents the instance of the DOM observer.
|
||||||
*/
|
*/
|
||||||
export declare const createDOMObserver: <ContentObserver extends boolean>(target: HTMLElement, isContentObserver: ContentObserver, callback: DOMObserverCallback<ContentObserver>, options?: DOMObserverOptions<ContentObserver> | undefined) => DOMObserver<ContentObserver>;
|
export declare const createDOMObserver: <ContentObserver extends boolean>(target: HTMLElement, isContentObserver: ContentObserver, callback: DOMObserverCallback<ContentObserver>, options?: DOMObserverOptions<ContentObserver> | undefined) => DOMObserver;
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
import { CacheValues } from 'support';
|
import { CacheValues } from 'support';
|
||||||
export declare type SizeObserverOptions = {
|
export interface SizeObserverOptions {
|
||||||
_direction?: boolean;
|
_direction?: boolean;
|
||||||
_appear?: boolean;
|
_appear?: boolean;
|
||||||
};
|
}
|
||||||
|
export interface SizeObserverCallbackParams {
|
||||||
|
_sizeChanged: boolean;
|
||||||
|
_directionIsRTLCache?: CacheValues<boolean>;
|
||||||
|
_appear?: boolean;
|
||||||
|
}
|
||||||
export interface SizeObserver {
|
export interface SizeObserver {
|
||||||
_destroy(): void;
|
_destroy(): void;
|
||||||
_getCurrentCacheValues(force?: boolean): {
|
_getCurrentCacheValues(force?: boolean): {
|
||||||
@@ -16,4 +21,4 @@ export interface SizeObserver {
|
|||||||
* @param options The options for size detection, whether to observe also direction and appear.
|
* @param options The options for size detection, whether to observe also direction and appear.
|
||||||
* @returns A object which represents the instance of the size observer.
|
* @returns A object which represents the instance of the size observer.
|
||||||
*/
|
*/
|
||||||
export declare const createSizeObserver: (target: HTMLElement, onSizeChangedCallback: (directionIsRTLCache?: CacheValues<boolean> | undefined) => any, options?: SizeObserverOptions | undefined) => SizeObserver;
|
export declare const createSizeObserver: (target: HTMLElement, onSizeChangedCallback: (params: SizeObserverCallbackParams) => any, options?: SizeObserverOptions | undefined) => SizeObserver;
|
||||||
|
|||||||
+2
-3
@@ -15,9 +15,8 @@ export interface OSOptions {
|
|||||||
paddingAbsolute: boolean;
|
paddingAbsolute: boolean;
|
||||||
updating: {
|
updating: {
|
||||||
elementEvents: Array<[string, string]> | null;
|
elementEvents: Array<[string, string]> | null;
|
||||||
contentMutationDebounce: number;
|
attributes: string[] | null;
|
||||||
hostMutationDebounce: number;
|
debounce: number | [number, number] | null;
|
||||||
resizeDebounce: number;
|
|
||||||
};
|
};
|
||||||
overflow: {
|
overflow: {
|
||||||
x: OverflowBehavior;
|
x: OverflowBehavior;
|
||||||
|
|||||||
@@ -1,8 +1,28 @@
|
|||||||
|
declare type DebounceTiming = number | false | null | undefined;
|
||||||
|
export interface DebounceOptions<FunctionToDebounce extends (...args: any) => any> {
|
||||||
|
/**
|
||||||
|
* The timeout for debouncing. If null, no debounce is applied.
|
||||||
|
*/
|
||||||
|
_timeout?: DebounceTiming | (() => DebounceTiming);
|
||||||
|
/**
|
||||||
|
* A maximum amount of ms. before the function will be called even with debounce.
|
||||||
|
*/
|
||||||
|
_maxDelay?: DebounceTiming | (() => DebounceTiming);
|
||||||
|
/**
|
||||||
|
* Function which merges parameters for each canceled debounce.
|
||||||
|
* If parameters can't be merged the function will return null, otherwise it returns the merged parameters.
|
||||||
|
*/
|
||||||
|
_mergeParams?: (prev: Parameters<FunctionToDebounce>, curr: Parameters<FunctionToDebounce>) => Parameters<FunctionToDebounce> | false | null | undefined;
|
||||||
|
}
|
||||||
|
export interface Debounced<FunctionToDebounce extends (...args: any) => any> {
|
||||||
|
(...args: Parameters<FunctionToDebounce>): ReturnType<FunctionToDebounce>;
|
||||||
|
_flush(): void;
|
||||||
|
}
|
||||||
export declare const noop: () => void;
|
export declare const noop: () => void;
|
||||||
/**
|
/**
|
||||||
* Debounces the given function either with a timeout or a animation frame.
|
* Debounces the given function either with a timeout or a animation frame.
|
||||||
* @param functionToDebounce The function which shall be debounced.
|
* @param functionToDebounce The function which shall be debounced.
|
||||||
* @param timeout The timeout for debouncing. If 0 or lower animation frame is used for debouncing, a timeout otherwise.
|
* @param options Options for debouncing.
|
||||||
* @param maxWait A maximum amount of ms. before the function will be called even with debounce.
|
|
||||||
*/
|
*/
|
||||||
export declare const debounce: (functionToDebounce: (...args: any) => any, timeout?: number | undefined, maxWait?: number | undefined) => () => void;
|
export declare const debounce: <FunctionToDebounce extends (...args: any) => any>(functionToDebounce: FunctionToDebounce, options: DebounceOptions<FunctionToDebounce>) => Debounced<FunctionToDebounce>;
|
||||||
|
export {};
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ const rollupConfig = (config = {}, { project = process.cwd(), overwrite = {}, si
|
|||||||
plugins: [
|
plugins: [
|
||||||
...(output.plugins || []),
|
...(output.plugins || []),
|
||||||
rollupTerser({
|
rollupTerser({
|
||||||
ecma: 8,
|
ecma: esm ? 2015 : 5,
|
||||||
safari10: true,
|
safari10: true,
|
||||||
mangle: {
|
mangle: {
|
||||||
safari10: true,
|
safari10: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user