This commit is contained in:
Rene Haas
2021-05-14 10:27:01 +02:00
22 changed files with 1763 additions and 1435 deletions
+1
View File
@@ -67,6 +67,7 @@
"test": "yarn workspaces run test",
"test:jsdom": "yarn workspaces run test:jsdom",
"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",
"build": "yarn workspaces run build",
"lint": "npx eslint --fix ."
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -7,6 +7,7 @@
"test": "jest --coverage --runInBand --detectOpenHandles",
"test:jsdom": "jest --coverage --runInBand --detectOpenHandles --selectProjects jsdom --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",
"build": "rollup -c"
}
@@ -1,28 +1,11 @@
import {
XY,
WH,
TRBL,
CacheValues,
PartialOptions,
each,
hasOwnProperty,
isNumber,
scrollLeft,
scrollTop,
assignDeep,
liesBetween,
diffClass,
} from 'support';
import { XY, WH, TRBL, CacheValues, PartialOptions, each, hasOwnProperty, isNumber, scrollLeft, scrollTop, assignDeep } from 'support';
import { OSOptions } from 'options';
import { classNameHost, classNameViewport, classNameContent } from 'classnames';
import { getEnvironment } from 'environment';
import { StructureSetup } from 'setups/structureSetup';
import { lifecycleHubOservers } from 'lifecycles/lifecycleHubObservers';
import { createTrinsicLifecycle } from 'lifecycles/trinsicLifecycle';
import { createPaddingLifecycle } from 'lifecycles/paddingLifecycle';
import { createOverflowLifecycle } from 'lifecycles/overflowLifecycle';
import { createSizeObserver } from 'observers/sizeObserver';
import { createTrinsicObserver } from 'observers/trinsicObserver';
import { createDOMObserver } from 'observers/domObserver';
import { StyleObject } from 'typings';
export type LifecycleCheckOption = <T>(path: string) => LifecycleOptionInfo<T>;
@@ -82,28 +65,7 @@ export interface LifecycleHub {
const getPropByPath = <T>(obj: any, path: string): T =>
obj ? path.split('.').reduce((o, prop) => (o && hasOwnProperty(o, prop) ? o[prop] : undefined), obj) : undefined;
// TODO: observer textarea attrs if textarea
// 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> = {
const booleanCacheValuesFallback: CacheValues<boolean> = {
_value: false,
_previous: false,
_changed: false,
@@ -139,7 +101,7 @@ const lifecycleCommunicationFallback: LifecycleCommunication = {
export const createLifecycleHub = (options: OSOptions, structureSetup: StructureSetup): LifecycleHubInstance => {
let lifecycleCommunication = lifecycleCommunicationFallback;
const { _host, _viewport, _content } = structureSetup._targetObj;
const { _viewport } = structureSetup._targetObj;
const {
_nativeScrollbarStyling,
_nativeScrollbarIsOverlaid,
@@ -168,10 +130,11 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
_contentMutation = force || false,
_paddingStyleChanged = force || false,
} = updateHints || {};
const finalDirectionIsRTL =
_directionIsRTL || (sizeObserver ? sizeObserver._getCurrentCacheValues(force)._directionIsRTL : directionIsRTLCacheValuesFallback);
_directionIsRTL || (_sizeObserver ? _sizeObserver._getCurrentCacheValues(force)._directionIsRTL : booleanCacheValuesFallback);
const finalHeightIntrinsic =
_heightIntrinsic || (trinsicObserver ? trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : heightIntrinsicCacheValuesFallback);
_heightIntrinsic || (_trinsicObserver ? _trinsicObserver._getCurrentCacheValues(force)._heightIntrinsic : booleanCacheValuesFallback);
const checkOption: LifecycleCheckOption = (path) => ({
_value: getPropByPath(options, path),
_changed: force || getPropByPath(changedOptions, path) !== undefined,
@@ -180,6 +143,11 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
const scrollOffsetX = adjustScrollOffset && scrollLeft(_viewport);
const scrollOffsetY = adjustScrollOffset && scrollTop(_viewport);
// place before updating lifecycles because of possible flushing of debounce
if (_updateObserverOptions) {
_updateObserverOptions(checkOption);
}
each(lifecycles, (lifecycle) => {
const {
_sizeChanged: adaptiveSizeChanged,
@@ -217,58 +185,7 @@ export const createLifecycleHub = (options: OSOptions, structureSetup: Structure
options.callbacks.onUpdated();
}
};
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 { _sizeObserver, _trinsicObserver, _updateObserverOptions } = lifecycleHubOservers(instance, updateLifecycles);
const update = (changedOptions?: Partial<OSOptions> | null, force?: boolean) => {
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,
push,
isUndefined,
isFunction,
} from 'support';
type StringNullUndefined = string | null | undefined;
type DOMContentObserverCallback = (contentChanged: boolean) => any;
type DOMContentObserverCallback = (contentChangedTroughEvent: 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
}
interface DOMObserverBase {
_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;
type ContentChangeArrayItem = [StringNullUndefined, StringNullUndefined] | null | undefined;
export type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
@@ -57,7 +45,7 @@ export type DOMObserverIgnoreContentChange = (
mutation: MutationRecord,
isNestedTarget: boolean,
domObserverTarget: HTMLElement,
domObserverOptions: DOMContentObserverOptions | undefined
domObserverOptions?: DOMContentObserverOptions
) => boolean;
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 DOMObserver<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserver : DOMTargetObserver;
// const styleChangingAttributes = ['id', 'class', 'style', 'open'];
// const mutationObserverAttrsTextarea = ['wrap', 'cols', 'rows'];
export interface DOMObserver {
_destroy: () => void;
_update: () => void;
}
/**
* 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) => {
let map: Map<Node, string> | undefined;
let eventContentChangeRef: DOMObserverEventContentChange;
const _destroy = () => {
if (map) {
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[]) => {
if (map && eventContentChangeRef) {
const eventElmList = eventContentChangeRef.reduce<Array<[Node[], string]>>((arr, item) => {
if (map && eventContentChange) {
const eventElmList = eventContentChange.reduce<Array<[Node[], string]>>((arr, item) => {
if (item) {
const selector = item[0];
const eventNames = item[1];
const elements = eventNames && selector && (getElements ? getElements(selector) : find(selector, target));
const parsedEventNames = isFunction(eventNames) ? eventNames(elements) : eventNames;
if (elements && elements.length && parsedEventNames && isString(parsedEventNames)) {
push(arr, [elements, parsedEventNames.trim()], true);
if (elements && elements.length && eventNames && isString(eventNames)) {
push(arr, [elements, eventNames.trim()], true);
}
}
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) {
_updateEventContentChange(eventContentChange);
map = map || new Map<Node, string>();
_destroy();
_updateElements();
}
return {
_destroy,
_updateElements,
_updateEventContentChange,
};
};
@@ -159,7 +140,7 @@ export const createDOMObserver = <ContentObserver extends boolean>(
isContentObserver: ContentObserver,
callback: DOMObserverCallback<ContentObserver>,
options?: DOMObserverOptions<ContentObserver>
): DOMObserver<ContentObserver> => {
): DOMObserver => {
let isConnected = false;
const {
_attributes,
@@ -170,18 +151,17 @@ export const createDOMObserver = <ContentObserver extends boolean>(
_ignoreNestedTargetChange,
_ignoreContentChange,
} = (options as DOMContentObserverOptions & DOMTargetObserverOptions) || {};
const {
_destroy: destroyEventContentChange,
_updateElements: updateEventContentChangeElements,
_updateEventContentChange: updateEventContentChange,
} = createEventContentChange(
const { _destroy: destroyEventContentChange, _updateElements: updateEventContentChangeElements } = createEventContentChange(
target,
isContentObserver && _eventContentChange,
debounce(() => {
if (isConnected) {
(callback as DOMContentObserverCallback)(true);
}
}, 84)
debounce(
() => {
if (isConnected) {
(callback as DOMContentObserverCallback)(true);
}
},
{ _timeout: 33, _maxDelay: 99 }
)
);
// MutationObserver
@@ -243,7 +223,7 @@ export const createDOMObserver = <ContentObserver extends boolean>(
}
if (isContentObserver) {
contentChanged && (callback as DOMContentObserverCallback)(contentChanged);
contentChanged && (callback as DOMContentObserverCallback)(false);
} else if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged) {
(callback as DOMTargetObserverCallback)(targetChangedAttrs, targetStyleChanged);
}
@@ -269,13 +249,10 @@ export const createDOMObserver = <ContentObserver extends boolean>(
isConnected = false;
}
},
_updateEventContentChange: (newEventContentChange?: DOMObserverEventContentChange) => {
updateEventContentChange(isConnected && isContentObserver && newEventContentChange);
},
_update: () => {
if (isConnected) {
observerCallback(mutationObserver.takeRecords());
}
},
} as DOMObserver<ContentObserver>;
};
};
@@ -33,7 +33,16 @@ import {
classNameSizeObserverListenerItemFinal,
} 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 {
_destroy(): void;
@@ -73,7 +82,7 @@ const domRectHasDimensions = (rect?: DOMRectReadOnly) => rect && (rect.height ||
*/
export const createSizeObserver = (
target: HTMLElement,
onSizeChangedCallback: (directionIsRTLCache?: CacheValues<boolean>) => any,
onSizeChangedCallback: (params: SizeObserverCallbackParams) => any,
options?: SizeObserverOptions
): SizeObserver => {
const { _direction: observeDirectionChange = false, _appear: observeAppearChange = false } = options || {};
@@ -90,31 +99,44 @@ export const createSizeObserver = (
(!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);
let skip = false;
let appear: boolean | number | undefined = false;
let doDirectionScroll = true; // always true if sizeChangedContext is Event (appear callback or RO. Polyfill)
// if triggered from RO.
if (isArray(sizeChangedContext) && sizeChangedContext.length > 0) {
const { _previous, _value, _changed } = updateResizeObserverContentRectCache(0, sizeChangedContext.pop()!.contentRect);
skip = !_previous || !domRectHasDimensions(_value); // skip on initial RO. call or if display is none
doDirectionScroll = !skip && _changed; // direction scroll when not skipping and changing from display: none to block, false otherwise
const { _previous, _value } = updateResizeObserverContentRectCache(0, sizeChangedContext.pop()!.contentRect);
const hasDimensions = domRectHasDimensions(_value);
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 (hasDirectionCache) {
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);
scrollLeft(sizeObserver, rtl ? (rtlScrollBehavior.n ? -scrollAmount : rtlScrollBehavior.i ? 0 : scrollAmount) : scrollAmount);
scrollTop(sizeObserver, scrollAmount);
}
if (!skip) {
onSizeChangedCallback(hasDirectionCache ? (sizeChangedContext as CacheValues<boolean>) : undefined);
onSizeChangedCallback({
_sizeChanged: !hasDirectionCache,
_directionIsRTLCache: hasDirectionCache ? (sizeChangedContext as CacheValues<boolean>) : undefined,
_appear: !!appear,
});
}
};
const offListeners: (() => void)[] = [];
@@ -147,11 +169,11 @@ export const createSizeObserver = (
scrollLeft(shrinkElement, scrollAmount);
scrollTop(shrinkElement, scrollAmount);
};
const onResized = () => {
const onResized = (appear?: unknown) => {
rAFId = 0;
if (isDirty) {
cacheSize = currSize;
onSizeChangedCallbackProxy();
onSizeChangedCallbackProxy(appear === true);
}
};
const onScroll = (scrollEvent?: Event | false) => {
@@ -166,7 +188,7 @@ export const createSizeObserver = (
rAFId = rAF!(onResized);
}
} else {
onResized();
onResized(scrollEvent === false);
}
reset();
+9 -7
View File
@@ -35,9 +35,8 @@ export interface OSOptions {
paddingAbsolute: boolean;
updating: {
elementEvents: Array<[string, string]> | null;
contentMutationDebounce: number;
hostMutationDebounce: number;
resizeDebounce: number;
attributes: string[] | null;
debounce: number | [number, number] | null;
};
overflow: {
x: OverflowBehavior;
@@ -109,6 +108,7 @@ export interface UpdatedArgs {
}
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 booleanTrueTemplate: OptionsWithOptionsTemplateValue<boolean> = [true, 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
paddingAbsolute: booleanFalseTemplate, // true || false
updating: {
elementEvents: [[['img', 'load']], [oTypes.array, oTypes.null]], // array of tuples || null
contentMutationDebounce: [80, numberAllowedValues], // number
hostMutationDebounce: [0, numberAllowedValues], // number
resizeDebounce: [0, numberAllowedValues], // number
elementEvents: [[['img', 'load']], arrayNullValues], // array of tuples || null
attributes: [null, arrayNullValues],
debounce: [
[0, 33],
[oTypes.number, oTypes.array, oTypes.null],
], // number || number array || null
},
overflow: {
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 { cAF, rAF } from 'support/compatibility/apis';
import { isNumber, isFunction } from 'support/utils/types';
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
/**
* Debounces the given function either with a timeout or a animation frame.
* @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 maxWait A maximum amount of ms. before the function will be called even with debounce.
* @param options Options for debouncing.
*/
export const debounce = (functionToDebounce: (...args: any) => any, timeout?: number, maxWait?: number) => {
let timeoutId: number | void;
let lastCallTime: number;
const hasTimeout = isNumber(timeout) && timeout > 0;
const hasMaxWait = isNumber(maxWait) && maxWait > 0;
const cancel = hasTimeout ? window.clearTimeout : cAF!;
const set = hasTimeout ? window.setTimeout : rAF!;
const setFn = function (args: IArguments) {
lastCallTime = hasMaxWait ? performance.now() : 0;
timeoutId && cancel(timeoutId);
export const debounce = <FunctionToDebounce extends (...args: any) => any>(
functionToDebounce: FunctionToDebounce,
options: DebounceOptions<FunctionToDebounce>
): Debounced<FunctionToDebounce> => {
let timeoutId: number | undefined;
let maxTimeoutId: number | undefined;
let prevArguments: Parameters<FunctionToDebounce> | null | undefined;
let latestArguments: Parameters<FunctionToDebounce> | null | undefined;
const { _timeout, _maxDelay, _mergeParams } = options;
const invokeFunctionToDebounce = function (args: IArguments) {
clearTimeouts(timeoutId);
clearTimeouts(maxTimeoutId);
maxTimeoutId = timeoutId = prevArguments = undefined;
// eslint-disable-next-line
// @ts-ignore
functionToDebounce.apply(this, args);
};
return function () {
// eslint-disable-next-line
// @ts-ignore
const boundSetFn = setFn.bind(this, arguments); // eslint-disable-line
const forceCall = hasMaxWait ? performance.now() - lastCallTime >= maxWait! : false;
const mergeParms = (curr: Parameters<FunctionToDebounce>): Parameters<FunctionToDebounce> | false | null | undefined =>
_mergeParams && prevArguments ? _mergeParams(prevArguments, curr) : curr;
timeoutId && cancel(timeoutId);
timeoutId = forceCall ? boundSetFn() : (set(boundSetFn, timeout!) as number);
const flush = () => {
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 { timeout } from '@/testing-browser/timeout';
import { setTestResult, waitForOrFailTest } from '@/testing-browser/TestResult';
import {
appendChildren,
createDiv,
removeElements,
children,
isArray,
isNumber,
liesBetween,
hasClass,
addClass,
removeClass,
diffClass,
on,
} from 'support';
import { appendChildren, createDiv, removeElements, children, isArray, isNumber, liesBetween, addClass, removeClass, diffClass, on } from 'support';
import { createDOMObserver } from 'observers/domObserver';
type DOMContentObserverResult = boolean;
type DOMContentObserverResult = {
contentChange: boolean;
troughEvent: boolean;
};
type DOMTargetObserverResult = {
changedTargetAttrs: string[];
styleChanged: boolean;
@@ -34,6 +24,7 @@ interface SeparateChangeThrough {
const targetChangesCountSlot: HTMLElement | null = document.querySelector('#targetChanges');
const contentChangesCountSlot: HTMLElement | null = document.querySelector('#contentChanges');
const targetElm: HTMLElement | null = document.querySelector('#target');
const trargetContentElm: HTMLElement | null = document.querySelector('#target .content');
const targetElmContentElm: HTMLElement | null = document.querySelector('#content-host');
const contentElmAttrChange: HTMLElement | null = document.querySelector('#target .content-nest');
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 ignorePrefix = 'ignore';
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 domContentObserverObservations: DOMContentObserverResult[] = [];
@@ -115,44 +106,48 @@ const targetDomObserver = createDOMObserver(
}
);
const contentDomObserver = createDOMObserver(
document.querySelector('#target .content')!,
true,
(contentChanged: boolean) => {
should.equal(typeof contentChanged, 'boolean', 'The contentChanged parameter in a content dom observer must be a boolean.');
const createContentDomOserver = (eventContentChange: Array<[string | null | undefined, string | null | undefined] | null | undefined>) => {
return createDOMObserver(
trargetContentElm!,
true,
(contentChangedTroughEvent: boolean) => {
should.equal(typeof contentChangedTroughEvent, 'boolean', 'The contentChanged parameter in a content dom observer must be a boolean.');
domContentObserverObservations.push(contentChanged);
requestAnimationFrame(() => {
if (contentChangesCountSlot) {
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;
domContentObserverObservations.push({ contentChange: true, troughEvent: contentChangedTroughEvent });
requestAnimationFrame(() => {
if (contentChangesCountSlot) {
contentChangesCountSlot.textContent = `${domContentObserverObservations.length}`;
}
});
},
_ignoreNestedTargetChange: (target, attrName, oldValue, newValue) => {
if (attrName === 'class' && oldValue && newValue) {
const diff = diffClass(oldValue, newValue);
const ignore = diff.length === 1 && diff[0].startsWith(ignorePrefix);
return ignore;
}
return false;
},
// @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;
},
}
);
{
_styleChangingAttributes: attrs,
_attributes: attrs,
_eventContentChange: eventContentChange,
_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) {
const diff = diffClass(oldValue, newValue);
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 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) {
const contentChanged = getLast(addChangeThrough);
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) {
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);
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);
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
const addMultiple = async () => {
const { before, after, compare } = changedThrough(domContentObserverObservations);
const addMultipleItem = () => {
const genImage = () => {
const img = new Image(1, 1);
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
const imgHolder = createDiv('img');
appendChildren(imgHolder, img);
appendChildren(imgElmsSlot, imgHolder);
return imgHolder;
};
before();
await timeout(250);
addMultipleItem();
addMultipleItem();
addMultipleItem();
before();
appendChildren(imgElmsSlot, [genImage(), genImage(), genImage()]);
await timeout(250);
@@ -398,20 +400,27 @@ const addRemoveImgElmsFn = async () => {
compare(2);
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);
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();
// remove load event from image test
const addChanged = async (
newEventContentChange: Array<[string | null | undefined, (() => string | null | undefined) | string | null | undefined] | null | undefined>
) => {
contentDomObserver._updateEventContentChange(newEventContentChange);
const addChanged = async (newEventContentChange: Array<[string | null | undefined, string | null | undefined] | null | undefined>) => {
contentDomObserver._destroy();
contentDomObserver = createContentDomOserver(newEventContentChange);
const img = new Image(1, 1);
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
@@ -430,7 +439,8 @@ const addRemoveImgElmsFn = async () => {
compare(1);
});
contentDomObserver._updateEventContentChange(contentChangeArr);
contentDomObserver._destroy();
contentDomObserver = createContentDomOserver(contentChangeArr);
};
await addChanged([
@@ -440,9 +450,6 @@ const addRemoveImgElmsFn = async () => {
['img', undefined],
[null, null],
[undefined, undefined],
['img', () => 'hi'],
['img', () => null],
['img', () => undefined],
null,
undefined,
]);
@@ -471,7 +478,11 @@ const addRemoveTransitionElmsFn = async () => {
compareTransition(expectTransitionEndContentChange ? 2 : 1); // 2 because 1: added class mutation and 2: transition end event
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);
});
},
@@ -495,11 +506,16 @@ const addRemoveTransitionElmsFn = async () => {
compare(1);
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);
contentDomObserver._updateEventContentChange(contentChangeArr);
contentDomObserver._destroy();
contentDomObserver = createContentDomOserver(contentChangeArr);
await startTransition(elm, expectTransitionEndContentChange && false);
removeElements(elm);
@@ -509,19 +525,8 @@ const addRemoveTransitionElmsFn = async () => {
await add(false);
contentDomObserver._updateEventContentChange(
contentChangeArr.concat([
[
'.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';
},
],
])
);
contentDomObserver._destroy();
contentDomObserver = createContentDomOserver(contentChangeArr.concat([['.transition', 'transitionend']]));
await add(true);
};
@@ -562,7 +567,11 @@ const iterateTargetAttrChange = async () => {
const iterateContentAttrChange = async () => {
await iterateAttrChange(setContentAttr, domContentObserverObservations, (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);
};
@@ -573,7 +582,11 @@ const iterateContentBetweenAttrChange = async () => {
const iterateContentHostElmAttrChange = async () => {
await iterateAttrChange(setContentHostElmAttr, domContentObserverObservations, (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);
};
@@ -626,11 +639,9 @@ const start = async () => {
targetDomObserver._destroy();
targetDomObserver._update();
contentDomObserver._updateEventContentChange([]);
contentDomObserver._update();
contentDomObserver._destroy();
contentDomObserver._destroy();
contentDomObserver._updateEventContentChange([]);
contentDomObserver._update();
};
@@ -37,12 +37,14 @@ const preInitChildren = targetElm?.children.length;
const sizeObserver = createSizeObserver(
targetElm as HTMLElement,
(directionIsRTLCache?: any) => {
if (directionIsRTLCache) {
directionIterations += 1;
} else {
({ _directionIsRTLCache, _sizeChanged, _appear }) => {
if (_sizeChanged) {
sizeIterations += 1;
}
if (_directionIsRTLCache) {
directionIterations += 1;
}
requestAnimationFrame(() => {
if (resizesSlot) {
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
View File
@@ -1,5 +1,5 @@
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;
interface DOMObserverOptionsBase {
_attributes?: string[];
@@ -14,22 +14,16 @@ interface DOMContentObserverOptions extends DOMObserverOptionsBase {
interface DOMTargetObserverOptions extends DOMObserverOptionsBase {
_ignoreTargetChange?: DOMObserverIgnoreTargetChange;
}
interface DOMObserverBase {
_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;
declare type ContentChangeArrayItem = [StringNullUndefined, StringNullUndefined] | 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 DOMObserverCallback<ContentObserver extends boolean> = ContentObserver extends true ? DOMContentObserverCallback : DOMTargetObserverCallback;
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.
* @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.
* @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 {};
@@ -1,8 +1,13 @@
import { CacheValues } from 'support';
export declare type SizeObserverOptions = {
export interface SizeObserverOptions {
_direction?: boolean;
_appear?: boolean;
};
}
export interface SizeObserverCallbackParams {
_sizeChanged: boolean;
_directionIsRTLCache?: CacheValues<boolean>;
_appear?: boolean;
}
export interface SizeObserver {
_destroy(): void;
_getCurrentCacheValues(force?: boolean): {
@@ -16,4 +21,4 @@ export interface SizeObserver {
* @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.
*/
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
View File
@@ -15,9 +15,8 @@ export interface OSOptions {
paddingAbsolute: boolean;
updating: {
elementEvents: Array<[string, string]> | null;
contentMutationDebounce: number;
hostMutationDebounce: number;
resizeDebounce: number;
attributes: string[] | null;
debounce: number | [number, number] | null;
};
overflow: {
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;
/**
* Debounces the given function either with a timeout or a animation frame.
* @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 maxWait A maximum amount of ms. before the function will be called even with debounce.
* @param options Options for debouncing.
*/
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 {};
+1 -1
View File
@@ -268,7 +268,7 @@ const rollupConfig = (config = {}, { project = process.cwd(), overwrite = {}, si
plugins: [
...(output.plugins || []),
rollupTerser({
ecma: 8,
ecma: esm ? 2015 : 5,
safari10: true,
mangle: {
safari10: true,