mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-06 10:12:27 +03:00
update will sync observers, updated only called if something changed
This commit is contained in:
@@ -66,7 +66,10 @@ export type DOMObserverOptions<ContentObserver extends boolean> = ContentObserve
|
||||
? DOMContentObserverOptions
|
||||
: DOMTargetObserverOptions;
|
||||
|
||||
export type DOMObserver = [destroy: () => void, update: () => void];
|
||||
export type DOMObserver<ContentObserver extends boolean> = [
|
||||
destroy: () => void,
|
||||
update: () => void | false | Parameters<DOMObserverCallback<ContentObserver>>
|
||||
];
|
||||
|
||||
type EventContentChangeUpdateElement = (getElements?: (selector: string) => Node[]) => void;
|
||||
type EventContentChange = [destroy: () => void, updateElements: EventContentChangeUpdateElement];
|
||||
@@ -156,7 +159,7 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
||||
isContentObserver: ContentObserver,
|
||||
callback: DOMObserverCallback<ContentObserver>,
|
||||
options?: DOMObserverOptions<ContentObserver>
|
||||
): DOMObserver => {
|
||||
): DOMObserver<ContentObserver> => {
|
||||
let isConnected = false;
|
||||
const {
|
||||
_attributes,
|
||||
@@ -166,16 +169,17 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
||||
_ignoreTargetChange,
|
||||
_ignoreContentChange,
|
||||
} = (options as DOMContentObserverOptions & DOMTargetObserverOptions) || {};
|
||||
const debouncedEventContentChange = debounce(
|
||||
() => {
|
||||
if (isConnected) {
|
||||
(callback as DOMContentObserverCallback)(true);
|
||||
}
|
||||
},
|
||||
{ _timeout: 33, _maxDelay: 99 }
|
||||
);
|
||||
const [destroyEventContentChange, updateEventContentChangeElements] = createEventContentChange(
|
||||
target,
|
||||
debounce(
|
||||
() => {
|
||||
if (isConnected) {
|
||||
(callback as DOMContentObserverCallback)(true);
|
||||
}
|
||||
},
|
||||
{ _timeout: 33, _maxDelay: 99 }
|
||||
),
|
||||
debouncedEventContentChange,
|
||||
_eventContentChange
|
||||
);
|
||||
|
||||
@@ -183,7 +187,10 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
||||
const finalAttributes = _attributes || [];
|
||||
const finalStyleChangingAttributes = _styleChangingAttributes || [];
|
||||
const observedAttributes = finalAttributes.concat(finalStyleChangingAttributes);
|
||||
const observerCallback = (mutations: MutationRecord[]) => {
|
||||
const observerCallback = (
|
||||
mutations: MutationRecord[],
|
||||
fromRecords?: true
|
||||
): void | Parameters<DOMObserverCallback<ContentObserver>> => {
|
||||
const ignoreTargetChange = _ignoreTargetChange || noop;
|
||||
const ignoreContentChange = _ignoreContentChange || noop;
|
||||
const targetChangedAttrs: string[] = [];
|
||||
@@ -244,12 +251,20 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
||||
}
|
||||
|
||||
if (isContentObserver) {
|
||||
contentChanged && (callback as DOMContentObserverCallback)(false);
|
||||
} else if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged) {
|
||||
(callback as DOMTargetObserverCallback)(targetChangedAttrs, targetStyleChanged);
|
||||
!fromRecords && contentChanged && (callback as DOMContentObserverCallback)(false);
|
||||
return [false] as Parameters<DOMObserverCallback<ContentObserver>>;
|
||||
}
|
||||
if (!isEmptyArray(targetChangedAttrs) || targetStyleChanged) {
|
||||
!fromRecords &&
|
||||
(callback as DOMTargetObserverCallback)(targetChangedAttrs, targetStyleChanged);
|
||||
return [targetChangedAttrs, targetStyleChanged] as Parameters<
|
||||
DOMObserverCallback<ContentObserver>
|
||||
>;
|
||||
}
|
||||
};
|
||||
const mutationObserver: MutationObserver = new MutationObserverConstructor!(observerCallback);
|
||||
const mutationObserver: MutationObserver = new MutationObserverConstructor!((mutations) =>
|
||||
observerCallback(mutations)
|
||||
);
|
||||
|
||||
// Connect
|
||||
mutationObserver.observe(target, {
|
||||
@@ -272,7 +287,10 @@ export const createDOMObserver = <ContentObserver extends boolean>(
|
||||
},
|
||||
() => {
|
||||
if (isConnected) {
|
||||
observerCallback(mutationObserver.takeRecords());
|
||||
debouncedEventContentChange._flush();
|
||||
|
||||
const records = mutationObserver.takeRecords();
|
||||
return !isEmptyArray(records) && observerCallback(records, true);
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
@@ -13,7 +13,11 @@ import {
|
||||
import { createSizeObserver } from 'observers/sizeObserver';
|
||||
import { classNameTrinsicObserver } from 'classnames';
|
||||
|
||||
export type DestroyTrinsicObserver = () => void;
|
||||
export type TrinsicObserverCallback = (heightIntrinsic: CacheValues<boolean>) => any;
|
||||
export type TrinsicObserver = [
|
||||
destroy: () => void,
|
||||
update: () => void | Parameters<TrinsicObserverCallback>
|
||||
];
|
||||
|
||||
const isHeightIntrinsic = (ioEntryOrSize: IntersectionObserverEntry | WH<number>): boolean =>
|
||||
(ioEntryOrSize as WH<number>).h === 0 ||
|
||||
@@ -28,39 +32,45 @@ const isHeightIntrinsic = (ioEntryOrSize: IntersectionObserverEntry | WH<number>
|
||||
*/
|
||||
export const createTrinsicObserver = (
|
||||
target: HTMLElement,
|
||||
onTrinsicChangedCallback: (heightIntrinsic: CacheValues<boolean>) => any
|
||||
): DestroyTrinsicObserver => {
|
||||
onTrinsicChangedCallback: TrinsicObserverCallback
|
||||
): TrinsicObserver => {
|
||||
let intersectionObserverInstance: undefined | IntersectionObserver;
|
||||
const trinsicObserver = createDiv(classNameTrinsicObserver);
|
||||
const offListeners: (() => void)[] = [];
|
||||
const [updateHeightIntrinsicCache] = createCache({
|
||||
_initialValue: false,
|
||||
});
|
||||
|
||||
const triggerOnTrinsicChangedCallback = (
|
||||
updateValue?: IntersectionObserverEntry | WH<number>
|
||||
) => {
|
||||
updateValue?: IntersectionObserverEntry | WH<number>,
|
||||
fromRecords?: true
|
||||
): void | Parameters<TrinsicObserverCallback> => {
|
||||
if (updateValue) {
|
||||
const heightIntrinsic = updateHeightIntrinsicCache(isHeightIntrinsic(updateValue));
|
||||
const [, heightIntrinsicChanged] = heightIntrinsic;
|
||||
|
||||
if (heightIntrinsicChanged) {
|
||||
onTrinsicChangedCallback(heightIntrinsic);
|
||||
!fromRecords && onTrinsicChangedCallback(heightIntrinsic);
|
||||
return [heightIntrinsic];
|
||||
}
|
||||
}
|
||||
};
|
||||
const intersectionObserverCallback = (
|
||||
entries: IntersectionObserverEntry[],
|
||||
fromRecords?: true
|
||||
) => {
|
||||
if (entries && entries.length > 0) {
|
||||
return triggerOnTrinsicChangedCallback(entries.pop(), fromRecords);
|
||||
}
|
||||
};
|
||||
|
||||
if (IntersectionObserverConstructor) {
|
||||
const intersectionObserverInstance: IntersectionObserver = new IntersectionObserverConstructor(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
if (entries && entries.length > 0) {
|
||||
triggerOnTrinsicChangedCallback(entries.pop());
|
||||
}
|
||||
},
|
||||
intersectionObserverInstance = new IntersectionObserverConstructor(
|
||||
(entries) => intersectionObserverCallback(entries),
|
||||
{ root: target }
|
||||
);
|
||||
intersectionObserverInstance.observe(trinsicObserver);
|
||||
push(offListeners, () => {
|
||||
intersectionObserverInstance.disconnect();
|
||||
intersectionObserverInstance!.disconnect();
|
||||
});
|
||||
} else {
|
||||
const onSizeChanged = () => {
|
||||
@@ -73,8 +83,15 @@ export const createTrinsicObserver = (
|
||||
|
||||
prependChildren(target, trinsicObserver);
|
||||
|
||||
return () => {
|
||||
runEachAndClear(offListeners);
|
||||
removeElements(trinsicObserver);
|
||||
};
|
||||
return [
|
||||
() => {
|
||||
runEachAndClear(offListeners);
|
||||
removeElements(trinsicObserver);
|
||||
},
|
||||
() => {
|
||||
if (intersectionObserverInstance) {
|
||||
return intersectionObserverCallback(intersectionObserverInstance.takeRecords(), true);
|
||||
}
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
isFunction,
|
||||
ResizeObserverConstructor,
|
||||
closest,
|
||||
assignDeep,
|
||||
push,
|
||||
} from 'support';
|
||||
import { getEnvironment } from 'environment';
|
||||
import {
|
||||
@@ -41,8 +43,9 @@ import type {
|
||||
export type StructureSetupObserversUpdate = (checkOption: SetupUpdateCheckOption) => void;
|
||||
|
||||
export type StructureSetupObservers = [
|
||||
updateObserverOptions: StructureSetupObserversUpdate,
|
||||
destroy: () => void
|
||||
destroy: () => void,
|
||||
updateObservers: () => Partial<StructureSetupUpdateHints>,
|
||||
updateObserversOptions: StructureSetupObserversUpdate
|
||||
];
|
||||
|
||||
type ExcludeFromTuple<T extends readonly any[], E> = T extends [infer F, ...infer R]
|
||||
@@ -69,7 +72,7 @@ export const createStructureSetupObservers = (
|
||||
): StructureSetupObservers => {
|
||||
let debounceTimeout: number | false | undefined;
|
||||
let debounceMaxDelay: number | false | undefined;
|
||||
let contentMutationObserver: DOMObserver | undefined;
|
||||
let contentMutationObserver: DOMObserver<true> | undefined;
|
||||
const [, setState] = state;
|
||||
const {
|
||||
_host,
|
||||
@@ -134,10 +137,14 @@ export const createStructureSetupObservers = (
|
||||
}
|
||||
});
|
||||
};
|
||||
const onTrinsicChanged = (heightIntrinsicCache: CacheValues<boolean>) => {
|
||||
const onTrinsicChanged = (heightIntrinsicCache: CacheValues<boolean>, fromRecords?: true) => {
|
||||
const [heightIntrinsic, heightIntrinsicChanged] = heightIntrinsicCache;
|
||||
const updateHints: Partial<StructureSetupUpdateHints> = {
|
||||
_heightIntrinsicChanged: heightIntrinsicChanged,
|
||||
};
|
||||
setState({ _heightIntrinsic: heightIntrinsic });
|
||||
structureSetupUpdate({ _heightIntrinsicChanged: heightIntrinsicChanged });
|
||||
!fromRecords && structureSetupUpdate(updateHints);
|
||||
return updateHints;
|
||||
};
|
||||
const onSizeChanged = ({
|
||||
_sizeChanged,
|
||||
@@ -158,30 +165,36 @@ export const createStructureSetupObservers = (
|
||||
|
||||
updateFn({ _sizeChanged, _directionChanged: directionChanged });
|
||||
};
|
||||
const onContentMutation = (contentChangedTroughEvent: boolean) => {
|
||||
const onContentMutation = (contentChangedTroughEvent: boolean, fromRecords?: true) => {
|
||||
const [, contentSizeChanged] = updateContentSizeCache();
|
||||
const updateHints: Partial<StructureSetupUpdateHints> = {
|
||||
_contentMutation: contentSizeChanged,
|
||||
};
|
||||
// if contentChangedTroughEvent is true its already debounced
|
||||
const updateFn = contentChangedTroughEvent
|
||||
? structureSetupUpdate
|
||||
: structureSetupUpdateWithDebouncedAdaptiveUpdateHints;
|
||||
|
||||
if (contentSizeChanged) {
|
||||
updateFn({
|
||||
_contentMutation: true,
|
||||
});
|
||||
!fromRecords && updateFn(updateHints);
|
||||
}
|
||||
return updateHints;
|
||||
};
|
||||
const onHostMutation = (targetChangedAttrs: string[], targetStyleChanged: boolean) => {
|
||||
const onHostMutation = (
|
||||
targetChangedAttrs: string[],
|
||||
targetStyleChanged: boolean,
|
||||
fromRecords?: true
|
||||
) => {
|
||||
const updateHints: Partial<StructureSetupUpdateHints> = { _hostMutation: targetStyleChanged };
|
||||
if (targetStyleChanged) {
|
||||
structureSetupUpdateWithDebouncedAdaptiveUpdateHints({
|
||||
_hostMutation: true,
|
||||
});
|
||||
!fromRecords && structureSetupUpdateWithDebouncedAdaptiveUpdateHints(updateHints);
|
||||
} else if (!_viewportIsTarget) {
|
||||
updateViewportAttrsFromHost(targetChangedAttrs);
|
||||
}
|
||||
return updateHints;
|
||||
};
|
||||
|
||||
const destroyTrinsicObserver =
|
||||
const trinsicObserver =
|
||||
(_content || !_flexboxGlue) && createTrinsicObserver(_host, onTrinsicChanged);
|
||||
const destroySizeObserver =
|
||||
!_viewportIsTarget &&
|
||||
@@ -189,10 +202,15 @@ export const createStructureSetupObservers = (
|
||||
_appear: true,
|
||||
_direction: !_nativeScrollbarStyling,
|
||||
});
|
||||
const [destroyHostMutationObserver] = createDOMObserver(_host, false, onHostMutation, {
|
||||
_styleChangingAttributes: baseStyleChangingAttrs,
|
||||
_attributes: baseStyleChangingAttrs.concat(viewportAttrsFromTarget),
|
||||
});
|
||||
const [destroyHostMutationObserver, updateHostMutationObserver] = createDOMObserver(
|
||||
_host,
|
||||
false,
|
||||
onHostMutation,
|
||||
{
|
||||
_styleChangingAttributes: baseStyleChangingAttrs,
|
||||
_attributes: baseStyleChangingAttrs.concat(viewportAttrsFromTarget),
|
||||
}
|
||||
);
|
||||
|
||||
const viewportIsTargetResizeObserver =
|
||||
_viewportIsTarget &&
|
||||
@@ -202,6 +220,58 @@ export const createStructureSetupObservers = (
|
||||
updateViewportAttrsFromHost();
|
||||
|
||||
return [
|
||||
() => {
|
||||
contentMutationObserver && contentMutationObserver[0](); // destroy
|
||||
trinsicObserver && trinsicObserver[0](); // destroy
|
||||
destroySizeObserver && destroySizeObserver();
|
||||
viewportIsTargetResizeObserver && viewportIsTargetResizeObserver.disconnect();
|
||||
destroyHostMutationObserver();
|
||||
},
|
||||
() => {
|
||||
const updateHints: Partial<StructureSetupUpdateHints> = {};
|
||||
const hostUpdateResult = updateHostMutationObserver();
|
||||
const contentUpdateResult = contentMutationObserver && contentMutationObserver[1](); // update
|
||||
const trinsicUpdateResult = trinsicObserver && trinsicObserver[1](); // update
|
||||
|
||||
if (hostUpdateResult) {
|
||||
assignDeep(
|
||||
updateHints,
|
||||
onHostMutation.apply(
|
||||
0,
|
||||
push(hostUpdateResult, true) as [
|
||||
...updateResult: typeof hostUpdateResult,
|
||||
fromRecords: true
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
if (contentUpdateResult) {
|
||||
assignDeep(
|
||||
updateHints,
|
||||
onContentMutation.apply(
|
||||
0,
|
||||
push(contentUpdateResult, true) as [
|
||||
...updateResult: typeof contentUpdateResult,
|
||||
fromRecords: true
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
if (trinsicUpdateResult) {
|
||||
assignDeep(
|
||||
updateHints,
|
||||
onTrinsicChanged.apply(
|
||||
0,
|
||||
push(trinsicUpdateResult as any[], true) as [
|
||||
...updateResult: typeof trinsicUpdateResult,
|
||||
fromRecords: true
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return updateHints;
|
||||
},
|
||||
(checkOption) => {
|
||||
const [ignoreMutation] = checkOption<string[] | null>('updating.ignoreMutation');
|
||||
const [attributes, attributesChanged] = checkOption<string[] | null>('updating.attributes');
|
||||
@@ -261,12 +331,5 @@ export const createStructureSetupObservers = (
|
||||
}
|
||||
}
|
||||
},
|
||||
() => {
|
||||
contentMutationObserver && contentMutationObserver[0](); // destroy
|
||||
destroyTrinsicObserver && destroyTrinsicObserver();
|
||||
destroySizeObserver && destroySizeObserver();
|
||||
viewportIsTargetResizeObserver && viewportIsTargetResizeObserver.disconnect();
|
||||
destroyHostMutationObserver();
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createEventListenerHub } from 'support';
|
||||
import { createEventListenerHub, isEmptyObject, keys } from 'support';
|
||||
import { createState, createOptionCheck } from 'setups/setups';
|
||||
import { createStructureSetupElements } from 'setups/structureSetup/structureSetup.elements';
|
||||
import { createStructureSetupUpdate } from 'setups/structureSetup/structureSetup.update';
|
||||
@@ -79,11 +79,22 @@ export const createStructureSetup = (
|
||||
const [getState] = state;
|
||||
const [elements, appendElements, destroyElements] = createStructureSetupElements(target);
|
||||
const updateStructure = createStructureSetupUpdate(elements, state);
|
||||
const [updateObservers, destroyObservers] = createStructureSetupObservers(
|
||||
const triggerUpdateEvent: (...args: StructureSetupEventMap['u']) => void = (
|
||||
updateHints,
|
||||
changedOptions,
|
||||
force
|
||||
) => {
|
||||
const truthyUpdateHints = keys(updateHints).some((key) => updateHints[key]);
|
||||
|
||||
if (truthyUpdateHints || !isEmptyObject(changedOptions) || force) {
|
||||
triggerEvent('u', [updateHints, changedOptions, force]);
|
||||
}
|
||||
};
|
||||
const [destroyObservers, updateObservers, updateObserversOptions] = createStructureSetupObservers(
|
||||
elements,
|
||||
state,
|
||||
(updateHints) => {
|
||||
triggerEvent('u', [updateStructure(checkOptionsFallback, updateHints), {}, false]);
|
||||
triggerUpdateEvent(updateStructure(checkOptionsFallback, updateHints), {}, false);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -98,8 +109,12 @@ export const createStructureSetup = (
|
||||
return [
|
||||
(changedOptions, force?) => {
|
||||
const checkOption = createOptionCheck(options, changedOptions, force);
|
||||
updateObservers(checkOption);
|
||||
triggerEvent('u', [updateStructure(checkOption, {}, force), changedOptions, !!force]);
|
||||
updateObserversOptions(checkOption);
|
||||
triggerUpdateEvent(
|
||||
updateStructure(checkOption, updateObservers(), force),
|
||||
changedOptions,
|
||||
!!force
|
||||
);
|
||||
},
|
||||
structureSetupState,
|
||||
() => {
|
||||
|
||||
+4
-1
@@ -29,7 +29,7 @@ const startBtn: HTMLButtonElement | null = document.querySelector('#start');
|
||||
const changesSlot: HTMLButtonElement | null = document.querySelector('#changes');
|
||||
const preInitChildren = targetElm?.children.length;
|
||||
|
||||
const destroyTrinsicObserver = createTrinsicObserver(
|
||||
const [destroyTrinsicObserver, updateTrinsicObserver] = createTrinsicObserver(
|
||||
targetElm as HTMLElement,
|
||||
(heightIntrinsicCache) => {
|
||||
const [currentHeightIntrinsic, currentHeightIntrinsicChanged] = heightIntrinsicCache;
|
||||
@@ -158,6 +158,9 @@ const start = async () => {
|
||||
});
|
||||
await changeWhileHidden();
|
||||
|
||||
updateTrinsicObserver();
|
||||
destroyTrinsicObserver();
|
||||
updateTrinsicObserver();
|
||||
destroyTrinsicObserver();
|
||||
should.equal(
|
||||
targetElm?.children.length,
|
||||
|
||||
Reference in New Issue
Block a user