mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-06-22 07:00:35 +03:00
improve dom observer with once option
This commit is contained in:
@@ -1,21 +1,4 @@
|
|||||||
import {
|
import { each, noop, debounce, indexOf, isString, MutationObserverConstructor, isEmptyArray, on, attr, is, find, push } from 'support';
|
||||||
each,
|
|
||||||
noop,
|
|
||||||
debounce,
|
|
||||||
indexOf,
|
|
||||||
isString,
|
|
||||||
MutationObserverConstructor,
|
|
||||||
isEmptyArray,
|
|
||||||
on,
|
|
||||||
off,
|
|
||||||
attr,
|
|
||||||
is,
|
|
||||||
find,
|
|
||||||
push,
|
|
||||||
isUndefined,
|
|
||||||
} from 'support';
|
|
||||||
|
|
||||||
type StringNullUndefined = string | null | undefined;
|
|
||||||
|
|
||||||
type DOMContentObserverCallback = (contentChangedTroughEvent: boolean) => any;
|
type DOMContentObserverCallback = (contentChangedTroughEvent: boolean) => any;
|
||||||
|
|
||||||
@@ -37,7 +20,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
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContentChangeArrayItem = [StringNullUndefined, StringNullUndefined] | null | undefined;
|
type ContentChangeArrayItem = [string?, string?, boolean?] | null | undefined;
|
||||||
|
|
||||||
export type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
|
export type DOMObserverEventContentChange = Array<ContentChangeArrayItem> | false | null | undefined;
|
||||||
|
|
||||||
@@ -74,25 +57,28 @@ export interface DOMObserver {
|
|||||||
* @returns A object which contains a set of helper functions to destroy and update the observation of elements.
|
* @returns A object which contains a set of helper functions to destroy and update the observation of elements.
|
||||||
*/
|
*/
|
||||||
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 eventSet: Set<() => any> | undefined;
|
||||||
|
let onceSet: WeakMap<Node, 0> | undefined; // use WeakMap instead of WeakSet because of IE11 support
|
||||||
|
let destroyed = false;
|
||||||
const _destroy = () => {
|
const _destroy = () => {
|
||||||
if (map) {
|
destroyed = true;
|
||||||
map.forEach((eventName: string, elm: Node) => {
|
if (eventSet) {
|
||||||
off(elm, eventName, callback);
|
eventSet.forEach((offFn) => {
|
||||||
|
offFn();
|
||||||
});
|
});
|
||||||
map.clear();
|
eventSet.clear();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const _updateElements = (getElements?: (selector: string) => Node[]) => {
|
const _updateElements = (getElements?: (selector: string) => Node[]) => {
|
||||||
if (map && eventContentChange) {
|
if (eventSet && onceSet && eventContentChange) {
|
||||||
const eventElmList = eventContentChange.reduce<Array<[Node[], string]>>((arr, item) => {
|
const eventElmList = eventContentChange.reduce<Array<[Node[], string, boolean]>>((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));
|
||||||
|
|
||||||
if (elements && elements.length && eventNames && isString(eventNames)) {
|
if (elements && elements.length && eventNames && isString(eventNames)) {
|
||||||
push(arr, [elements, eventNames.trim()], true);
|
push(arr, [elements, eventNames.trim(), !!item[2]], true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return arr;
|
return arr;
|
||||||
@@ -101,25 +87,31 @@ const createEventContentChange = (target: Element, eventContentChange: DOMObserv
|
|||||||
each(eventElmList, (item) =>
|
each(eventElmList, (item) =>
|
||||||
each(item[0], (elm) => {
|
each(item[0], (elm) => {
|
||||||
const eventNames = item[1];
|
const eventNames = item[1];
|
||||||
const registredEventNames = map!.get(elm);
|
const once = item[2];
|
||||||
const newEntry = isUndefined(registredEventNames);
|
|
||||||
const changingExistingEntry = !newEntry && eventNames !== registredEventNames;
|
|
||||||
const finalEventNames = changingExistingEntry ? `${registredEventNames} ${eventNames}` : eventNames;
|
|
||||||
|
|
||||||
if (changingExistingEntry) {
|
if (once && !onceSet!.has(elm)) {
|
||||||
off(elm, registredEventNames!, callback);
|
onceSet!.set(elm, 0);
|
||||||
|
on(
|
||||||
|
elm,
|
||||||
|
eventNames,
|
||||||
|
(event) => {
|
||||||
|
if (!destroyed) {
|
||||||
|
callback(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ _once: once }
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
eventSet!.add(on(elm, eventNames, callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
map!.set(elm, finalEventNames);
|
|
||||||
on(elm, finalEventNames, callback);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (eventContentChange) {
|
if (eventContentChange) {
|
||||||
map = map || new Map<Node, string>();
|
eventSet = eventSet || new Set();
|
||||||
_destroy();
|
onceSet = onceSet || new WeakMap();
|
||||||
_updateElements();
|
_updateElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export interface OSOptions {
|
|||||||
resize: ResizeBehavior;
|
resize: ResizeBehavior;
|
||||||
paddingAbsolute: boolean;
|
paddingAbsolute: boolean;
|
||||||
updating: {
|
updating: {
|
||||||
elementEvents: Array<[string, string]> | null;
|
elementEvents: Array<[string, string, boolean?]> | null;
|
||||||
attributes: string[] | null;
|
attributes: string[] | null;
|
||||||
debounce: number | [number, number] | null;
|
debounce: number | [number, number] | null;
|
||||||
};
|
};
|
||||||
@@ -137,7 +137,7 @@ 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']], arrayNullValues], // array of tuples || null
|
elementEvents: [[['img', 'load', true]], arrayNullValues], // array of tuples || null
|
||||||
attributes: [null, arrayNullValues],
|
attributes: [null, arrayNullValues],
|
||||||
debounce: [
|
debounce: [
|
||||||
[0, 33],
|
[0, 33],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { each, hasOwnProperty, keys, push, isEmptyObject } from 'support/utils';
|
import { each, hasOwnProperty, keys, push, isEmptyObject } from 'support/utils';
|
||||||
import { type, isArray, isUndefined, isPlainObject, isString } from 'support/utils/types';
|
import { type, isArray, isUndefined, isPlainObject, isString, isNumber, isBoolean } from 'support/utils/types';
|
||||||
import { PlainObject } from 'typings';
|
import { PlainObject } from 'typings';
|
||||||
|
|
||||||
export type OptionsObjectType = Record<string, unknown>;
|
export type OptionsObjectType = Record<string, unknown>;
|
||||||
@@ -163,7 +163,8 @@ const validateRecursive = <T extends PlainObject>(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
const doStringifyComparison = isArray(optionsValue) || isPlainObject(optionsValue);
|
const isPrimitiveArr = isArray(optionsValue) && !optionsValue.some((val) => !isNumber(val) && !isString(val) && !isBoolean(val));
|
||||||
|
const doStringifyComparison = isPrimitiveArr || isPlainObject(optionsValue);
|
||||||
if (doStringifyComparison ? stringify(optionsValue) !== stringify(optionsDiffValue) : optionsValue !== optionsDiffValue) {
|
if (doStringifyComparison ? stringify(optionsValue) !== stringify(optionsDiffValue) : optionsValue !== optionsDiffValue) {
|
||||||
validatedOptions[prop] = optionsValue;
|
validatedOptions[prop] = optionsValue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,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]> = [['img', 'load']];
|
const contentChangeArr: Array<[string?, string?, boolean?]> = [['img', 'load', true]];
|
||||||
const domTargetObserverObservations: DOMTargetObserverResult[] = [];
|
const domTargetObserverObservations: DOMTargetObserverResult[] = [];
|
||||||
const domContentObserverObservations: DOMContentObserverResult[] = [];
|
const domContentObserverObservations: DOMContentObserverResult[] = [];
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ const targetDomObserver = createDOMObserver(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const createContentDomOserver = (eventContentChange: Array<[string | null | undefined, string | null | undefined] | null | undefined>) => {
|
const createContentDomOserver = (eventContentChange: Array<[string?, string?, boolean?] | null | undefined>) => {
|
||||||
return createDOMObserver(
|
return createDOMObserver(
|
||||||
trargetContentElm!,
|
trargetContentElm!,
|
||||||
true,
|
true,
|
||||||
@@ -344,7 +344,10 @@ const addRemoveTargetContentBetweenElmsFn = async () => {
|
|||||||
const addRemoveImgElmsFn = async () => {
|
const addRemoveImgElmsFn = async () => {
|
||||||
const add = async () => {
|
const add = async () => {
|
||||||
const img = new Image(1, 1);
|
const img = new Image(1, 1);
|
||||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
img.src = 'www.something.com/something/sometest';
|
||||||
|
setTimeout(() => {
|
||||||
|
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
||||||
|
}, 250);
|
||||||
|
|
||||||
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
||||||
const imgHolder = createDiv('img');
|
const imgHolder = createDiv('img');
|
||||||
@@ -380,7 +383,10 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
const { before, after, compare } = changedThrough(domContentObserverObservations);
|
||||||
const genImage = () => {
|
const genImage = () => {
|
||||||
const img = new Image(1, 1);
|
const img = new Image(1, 1);
|
||||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
img.src = 'www.something.com/something/sometest';
|
||||||
|
setTimeout(() => {
|
||||||
|
img.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
|
||||||
|
}, 250);
|
||||||
|
|
||||||
const imgHolder = createDiv('img');
|
const imgHolder = createDiv('img');
|
||||||
appendChildren(imgHolder, img);
|
appendChildren(imgHolder, img);
|
||||||
@@ -418,7 +424,7 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
await addMultiple();
|
await addMultiple();
|
||||||
|
|
||||||
// remove load event from image test
|
// remove load event from image test
|
||||||
const addChanged = async (newEventContentChange: Array<[string | null | undefined, string | null | undefined] | null | undefined>) => {
|
const addChanged = async (newEventContentChange: Array<[string?, string?, boolean?] | null | undefined>) => {
|
||||||
contentDomObserver._destroy();
|
contentDomObserver._destroy();
|
||||||
contentDomObserver = createContentDomOserver(newEventContentChange);
|
contentDomObserver = createContentDomOserver(newEventContentChange);
|
||||||
|
|
||||||
@@ -443,16 +449,7 @@ const addRemoveImgElmsFn = async () => {
|
|||||||
contentDomObserver = createContentDomOserver(contentChangeArr);
|
contentDomObserver = createContentDomOserver(contentChangeArr);
|
||||||
};
|
};
|
||||||
|
|
||||||
await addChanged([
|
await addChanged([['img', 'something'], ['img', 'something2'], ['img', ''], ['img', undefined], ['', ''], [undefined, undefined], null, undefined]);
|
||||||
['img', 'something'],
|
|
||||||
['img', 'something2'],
|
|
||||||
['img', null],
|
|
||||||
['img', undefined],
|
|
||||||
[null, null],
|
|
||||||
[undefined, undefined],
|
|
||||||
null,
|
|
||||||
undefined,
|
|
||||||
]);
|
|
||||||
await addChanged([]);
|
await addChanged([]);
|
||||||
|
|
||||||
removeElements(document.querySelectorAll('.img'));
|
removeElements(document.querySelectorAll('.img'));
|
||||||
@@ -632,6 +629,7 @@ const start = async () => {
|
|||||||
|
|
||||||
await addRemoveImgElmsFn();
|
await addRemoveImgElmsFn();
|
||||||
|
|
||||||
|
/*
|
||||||
targetDomObserver._update();
|
targetDomObserver._update();
|
||||||
targetDomObserver._destroy();
|
targetDomObserver._destroy();
|
||||||
targetDomObserver._destroy();
|
targetDomObserver._destroy();
|
||||||
@@ -641,7 +639,7 @@ const start = async () => {
|
|||||||
contentDomObserver._destroy();
|
contentDomObserver._destroy();
|
||||||
contentDomObserver._destroy();
|
contentDomObserver._destroy();
|
||||||
contentDomObserver._update();
|
contentDomObserver._update();
|
||||||
|
*/
|
||||||
setTestResult(true);
|
setTestResult(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user