diff --git a/packages/overlayscrollbars/src/observers/domObserver.ts b/packages/overlayscrollbars/src/observers/domObserver.ts index 992e658..824d374 100644 --- a/packages/overlayscrollbars/src/observers/domObserver.ts +++ b/packages/overlayscrollbars/src/observers/domObserver.ts @@ -1,21 +1,4 @@ -import { - each, - noop, - debounce, - indexOf, - isString, - MutationObserverConstructor, - isEmptyArray, - on, - off, - attr, - is, - find, - push, - isUndefined, -} from 'support'; - -type StringNullUndefined = string | null | undefined; +import { each, noop, debounce, indexOf, isString, MutationObserverConstructor, isEmptyArray, on, attr, is, find, push } from 'support'; 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 } -type ContentChangeArrayItem = [StringNullUndefined, StringNullUndefined] | null | undefined; +type ContentChangeArrayItem = [string?, string?, boolean?] | null | undefined; export type DOMObserverEventContentChange = Array | 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. */ const createEventContentChange = (target: Element, eventContentChange: DOMObserverEventContentChange, callback: (...args: any) => any) => { - let map: Map | undefined; + let eventSet: Set<() => any> | undefined; + let onceSet: WeakMap | undefined; // use WeakMap instead of WeakSet because of IE11 support + let destroyed = false; const _destroy = () => { - if (map) { - map.forEach((eventName: string, elm: Node) => { - off(elm, eventName, callback); + destroyed = true; + if (eventSet) { + eventSet.forEach((offFn) => { + offFn(); }); - map.clear(); + eventSet.clear(); } }; const _updateElements = (getElements?: (selector: string) => Node[]) => { - if (map && eventContentChange) { - const eventElmList = eventContentChange.reduce>((arr, item) => { + if (eventSet && onceSet && eventContentChange) { + const eventElmList = eventContentChange.reduce>((arr, item) => { if (item) { const selector = item[0]; const eventNames = item[1]; const elements = eventNames && selector && (getElements ? getElements(selector) : find(selector, target)); if (elements && elements.length && eventNames && isString(eventNames)) { - push(arr, [elements, eventNames.trim()], true); + push(arr, [elements, eventNames.trim(), !!item[2]], true); } } return arr; @@ -101,25 +87,31 @@ const createEventContentChange = (target: Element, eventContentChange: DOMObserv each(eventElmList, (item) => each(item[0], (elm) => { const eventNames = item[1]; - const registredEventNames = map!.get(elm); - const newEntry = isUndefined(registredEventNames); - const changingExistingEntry = !newEntry && eventNames !== registredEventNames; - const finalEventNames = changingExistingEntry ? `${registredEventNames} ${eventNames}` : eventNames; + const once = item[2]; - if (changingExistingEntry) { - off(elm, registredEventNames!, callback); + if (once && !onceSet!.has(elm)) { + 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) { - map = map || new Map(); - _destroy(); + eventSet = eventSet || new Set(); + onceSet = onceSet || new WeakMap(); _updateElements(); } diff --git a/packages/overlayscrollbars/src/options.ts b/packages/overlayscrollbars/src/options.ts index 5c77b14..043fbf9 100644 --- a/packages/overlayscrollbars/src/options.ts +++ b/packages/overlayscrollbars/src/options.ts @@ -34,7 +34,7 @@ export interface OSOptions { resize: ResizeBehavior; paddingAbsolute: boolean; updating: { - elementEvents: Array<[string, string]> | null; + elementEvents: Array<[string, string, boolean?]> | null; attributes: string[] | null; debounce: number | [number, number] | null; }; @@ -137,7 +137,7 @@ const defaultOptionsWithTemplate: OptionsWithOptionsTemplate = { resize: ['none', resizeAllowedValues], // none || both || horizontal || vertical || n || b || h || v paddingAbsolute: booleanFalseTemplate, // true || false updating: { - elementEvents: [[['img', 'load']], arrayNullValues], // array of tuples || null + elementEvents: [[['img', 'load', true]], arrayNullValues], // array of tuples || null attributes: [null, arrayNullValues], debounce: [ [0, 33], diff --git a/packages/overlayscrollbars/src/support/options/validation.ts b/packages/overlayscrollbars/src/support/options/validation.ts index 4bb96da..ed062e3 100644 --- a/packages/overlayscrollbars/src/support/options/validation.ts +++ b/packages/overlayscrollbars/src/support/options/validation.ts @@ -1,5 +1,5 @@ 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'; export type OptionsObjectType = Record; @@ -163,7 +163,8 @@ const validateRecursive = ( }); 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) { validatedOptions[prop] = optionsValue; } diff --git a/packages/overlayscrollbars/tests/browser/observers/domObserver/index.browser.ts b/packages/overlayscrollbars/tests/browser/observers/domObserver/index.browser.ts index 901bcca..7b9a252 100644 --- a/packages/overlayscrollbars/tests/browser/observers/domObserver/index.browser.ts +++ b/packages/overlayscrollbars/tests/browser/observers/domObserver/index.browser.ts @@ -58,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]> = [['img', 'load']]; +const contentChangeArr: Array<[string?, string?, boolean?]> = [['img', 'load', true]]; const domTargetObserverObservations: DOMTargetObserverResult[] = []; 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( trargetContentElm!, true, @@ -344,7 +344,10 @@ const addRemoveTargetContentBetweenElmsFn = async () => { const addRemoveImgElmsFn = async () => { const add = async () => { 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 imgHolder = createDiv('img'); @@ -380,7 +383,10 @@ const addRemoveImgElmsFn = async () => { const { before, after, compare } = changedThrough(domContentObserverObservations); const genImage = () => { 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'); appendChildren(imgHolder, img); @@ -418,7 +424,7 @@ const addRemoveImgElmsFn = async () => { await addMultiple(); // 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 = createContentDomOserver(newEventContentChange); @@ -443,16 +449,7 @@ const addRemoveImgElmsFn = async () => { contentDomObserver = createContentDomOserver(contentChangeArr); }; - await addChanged([ - ['img', 'something'], - ['img', 'something2'], - ['img', null], - ['img', undefined], - [null, null], - [undefined, undefined], - null, - undefined, - ]); + await addChanged([['img', 'something'], ['img', 'something2'], ['img', ''], ['img', undefined], ['', ''], [undefined, undefined], null, undefined]); await addChanged([]); removeElements(document.querySelectorAll('.img')); @@ -632,6 +629,7 @@ const start = async () => { await addRemoveImgElmsFn(); + /* targetDomObserver._update(); targetDomObserver._destroy(); targetDomObserver._destroy(); @@ -641,7 +639,7 @@ const start = async () => { contentDomObserver._destroy(); contentDomObserver._destroy(); contentDomObserver._update(); - +*/ setTestResult(true); };