add ignore target change feature to domObserver

This commit is contained in:
Rene
2021-01-23 21:22:48 +01:00
parent 323d05beec
commit 4eecc6fa7a
15 changed files with 344 additions and 178 deletions
+91 -73
View File
@@ -86,28 +86,6 @@ function scrollTop(elm, value) {
return getSetProp('scrollTop', 0, elm, value);
}
const rnothtmlwhite = /[^\x20\t\r\n\f]+/g;
const classListAction = (elm, className, action) => {
let clazz;
let i = 0;
let result = false;
if (elm && isString(className)) {
const classes = className.match(rnothtmlwhite) || [];
result = classes.length > 0;
while ((clazz = classes[i++])) {
result = !!action(elm.classList, clazz) && result;
}
}
return result;
};
const addClass = (elm, className) => {
classListAction(elm, className, (classList, clazz) => classList.add(clazz));
};
function each(source, callback) {
if (isArrayLike(source)) {
for (let i = 0; i < source.length; i++) {
@@ -148,6 +126,71 @@ const runEach = (arr, p1) => {
}
};
const hasOwnProperty$1 = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
const keys = (obj) => (obj ? Object.keys(obj) : []);
function assignDeep(target, object1, object2, object3, object4, object5, object6) {
const sources = [object1, object2, object3, object4, object5, object6];
if ((typeof target !== 'object' || isNull(target)) && !isFunction(target)) {
target = {};
}
each(sources, (source) => {
each(keys(source), (key) => {
const copy = source[key];
if (target === copy) {
return true;
}
const copyIsArray = isArray(copy);
if (copy && (isPlainObject(copy) || copyIsArray)) {
const src = target[key];
let clone = src;
if (copyIsArray && !isArray(src)) {
clone = [];
} else if (!copyIsArray && !isPlainObject(src)) {
clone = {};
}
target[key] = assignDeep(clone, copy);
} else {
target[key] = copy;
}
});
});
return target;
}
function isEmptyObject(obj) {
for (const name in obj) return false;
return true;
}
const rnothtmlwhite = /[^\x20\t\r\n\f]+/g;
const classListAction = (elm, className, action) => {
let clazz;
let i = 0;
let result = false;
if (elm && isString(className)) {
const classes = className.match(rnothtmlwhite) || [];
result = classes.length > 0;
while ((clazz = classes[i++])) {
result = !!action(elm.classList, clazz) && result;
}
}
return result;
};
const addClass = (elm, className) => {
classListAction(elm, className, (classList, clazz) => classList.add(clazz));
};
const elmPrototype = Element.prototype;
const find = (selector, elm) => {
@@ -338,49 +381,6 @@ const equalWH = (a, b) => equal(a, b, ['w', 'h']);
const equalXY = (a, b) => equal(a, b, ['x', 'y']);
const equalTRBL = (a, b) => equal(a, b, ['t', 'r', 'b', 'l']);
const hasOwnProperty$1 = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
const keys = (obj) => (obj ? Object.keys(obj) : []);
function assignDeep(target, object1, object2, object3, object4, object5, object6) {
const sources = [object1, object2, object3, object4, object5, object6];
if ((typeof target !== 'object' || isNull(target)) && !isFunction(target)) {
target = {};
}
each(sources, (source) => {
each(keys(source), (key) => {
const copy = source[key];
if (target === copy) {
return true;
}
const copyIsArray = isArray(copy);
if (copy && (isPlainObject(copy) || copyIsArray)) {
const src = target[key];
let clone = src;
if (copyIsArray && !isArray(src)) {
clone = [];
} else if (!copyIsArray && !isPlainObject(src)) {
clone = {};
}
target[key] = assignDeep(clone, copy);
} else {
target[key] = copy;
}
});
});
return target;
}
function isEmptyObject(obj) {
for (const name in obj) return false;
return true;
}
const firstLetterToUpper = (str) => str.charAt(0).toUpperCase() + str.slice(1);
const getDummyStyle = () => createDiv().style;
@@ -428,6 +428,7 @@ const ResizeObserverConstructor = jsAPI('ResizeObserver');
const cAF = jsAPI('cancelAnimationFrame');
const rAF = jsAPI('requestAnimationFrame');
const noop = () => {};
const debounce = (functionToDebounce, timeout, maxWait) => {
let timeoutId;
let lastCallTime;
@@ -1261,11 +1262,17 @@ const createEventContentChange = (target, eventContentChange, map, callback) =>
};
};
const getAttributeChanged = (mutationTarget, attributeName, oldValue) => oldValue !== attr(mutationTarget, attributeName);
const createDOMObserver = (target, callback, options) => {
let isConnected = false;
const { _observeContent, _attributes, _styleChangingAttributes, _ignoreContentChange, _eventContentChange } = options || {};
const {
_observeContent,
_attributes,
_styleChangingAttributes,
_eventContentChange,
_nestedTargetSelector,
_ignoreTargetAttrChange: _ignoreTargetChange,
_ignoreContentChange,
} = options || {};
const {
_updateElements: updateEventContentChangeElements,
_destroy: destroyEventContentChange,
@@ -1285,6 +1292,8 @@ const createDOMObserver = (target, callback, options) => {
const observedAttributes = finalAttributes.concat(finalStyleChangingAttributes);
const observerCallback = (mutations) => {
const ignoreTargetChange = _ignoreTargetChange || noop;
const ignoreContentChange = _ignoreContentChange || noop;
const targetChangedAttrs = [];
const totalAddedNodes = [];
let targetStyleChanged = false;
@@ -1295,10 +1304,14 @@ const createDOMObserver = (target, callback, options) => {
const isAttributesType = type === 'attributes';
const isChildListType = type === 'childList';
const targetIsMutationTarget = target === mutationTarget;
const attributeChanged = isAttributesType && isString(attributeName) && getAttributeChanged(mutationTarget, attributeName, oldValue);
const targetAttrChanged = attributeChanged && targetIsMutationTarget && !_observeContent;
const attributeValue = isAttributesType && isString(attributeName) ? attr(mutationTarget, attributeName) : 0;
const attributeChanged = attributeValue !== 0 && oldValue !== attributeValue;
const targetAttrChanged =
attributeChanged &&
targetIsMutationTarget &&
!_observeContent &&
!ignoreTargetChange(mutationTarget, attributeName, oldValue, attributeValue);
const styleChangingAttrChanged = indexOf(finalStyleChangingAttributes, attributeName) > -1 && attributeChanged;
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
if (targetAttrChanged) {
push(targetChangedAttrs, attributeName);
@@ -1307,12 +1320,17 @@ const createDOMObserver = (target, callback, options) => {
if (_observeContent) {
const notOnlyAttrChanged = !isAttributesType;
const contentAttrChanged = isAttributesType && styleChangingAttrChanged && !targetIsMutationTarget;
const contentFinalChanged =
(notOnlyAttrChanged || contentAttrChanged) && (_ignoreContentChange ? !_ignoreContentChange(mutation, target, options) : _observeContent);
const isNestedTarget = contentAttrChanged && _nestedTargetSelector && is(mutationTarget, _nestedTargetSelector);
const baseAssertion = isNestedTarget
? !ignoreTargetChange(mutationTarget, attributeName, oldValue, attributeValue)
: notOnlyAttrChanged || contentAttrChanged;
const contentFinalChanged = baseAssertion && !ignoreContentChange(mutation, isNestedTarget, target, options);
push(totalAddedNodes, addedNodes);
contentChanged = contentChanged || contentFinalChanged;
childListChanged = childListChanged || isChildListType;
}
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
});
if (childListChanged && !isEmptyArray(totalAddedNodes)) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+94 -84
View File
@@ -97,30 +97,6 @@
return getSetProp('scrollTop', 0, elm, value);
}
var rnothtmlwhite = /[^\x20\t\r\n\f]+/g;
var classListAction = function classListAction(elm, className, action) {
var clazz;
var i = 0;
var result = false;
if (elm && isString(className)) {
var classes = className.match(rnothtmlwhite) || [];
result = classes.length > 0;
while ((clazz = classes[i++])) {
result = !!action(elm.classList, clazz) && result;
}
}
return result;
};
var addClass = function addClass(elm, className) {
classListAction(elm, className, function (classList, clazz) {
return classList.add(clazz);
});
};
function each(source, callback) {
if (isArrayLike(source)) {
for (var i = 0; i < source.length; i++) {
@@ -169,6 +145,79 @@
}
};
var hasOwnProperty$1 = function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
};
var keys = function keys(obj) {
return obj ? Object.keys(obj) : [];
};
function assignDeep(target, object1, object2, object3, object4, object5, object6) {
var sources = [object1, object2, object3, object4, object5, object6];
if ((typeof target !== 'object' || isNull(target)) && !isFunction(target)) {
target = {};
}
each(sources, function (source) {
each(keys(source), function (key) {
var copy = source[key];
if (target === copy) {
return true;
}
var copyIsArray = isArray(copy);
if (copy && (isPlainObject(copy) || copyIsArray)) {
var src = target[key];
var clone = src;
if (copyIsArray && !isArray(src)) {
clone = [];
} else if (!copyIsArray && !isPlainObject(src)) {
clone = {};
}
target[key] = assignDeep(clone, copy);
} else {
target[key] = copy;
}
});
});
return target;
}
function isEmptyObject(obj) {
for (var name in obj) {
return false;
}
return true;
}
var rnothtmlwhite = /[^\x20\t\r\n\f]+/g;
var classListAction = function classListAction(elm, className, action) {
var clazz;
var i = 0;
var result = false;
if (elm && isString(className)) {
var classes = className.match(rnothtmlwhite) || [];
result = classes.length > 0;
while ((clazz = classes[i++])) {
result = !!action(elm.classList, clazz) && result;
}
}
return result;
};
var addClass = function addClass(elm, className) {
classListAction(elm, className, function (classList, clazz) {
return classList.add(clazz);
});
};
var elmPrototype = Element.prototype;
var find = function find(selector, elm) {
@@ -386,55 +435,6 @@
return equal(a, b, ['t', 'r', 'b', 'l']);
};
var hasOwnProperty$1 = function hasOwnProperty(obj, prop) {
return Object.prototype.hasOwnProperty.call(obj, prop);
};
var keys = function keys(obj) {
return obj ? Object.keys(obj) : [];
};
function assignDeep(target, object1, object2, object3, object4, object5, object6) {
var sources = [object1, object2, object3, object4, object5, object6];
if ((typeof target !== 'object' || isNull(target)) && !isFunction(target)) {
target = {};
}
each(sources, function (source) {
each(keys(source), function (key) {
var copy = source[key];
if (target === copy) {
return true;
}
var copyIsArray = isArray(copy);
if (copy && (isPlainObject(copy) || copyIsArray)) {
var src = target[key];
var clone = src;
if (copyIsArray && !isArray(src)) {
clone = [];
} else if (!copyIsArray && !isPlainObject(src)) {
clone = {};
}
target[key] = assignDeep(clone, copy);
} else {
target[key] = copy;
}
});
});
return target;
}
function isEmptyObject(obj) {
for (var name in obj) {
return false;
}
return true;
}
var firstLetterToUpper = function firstLetterToUpper(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
};
@@ -488,6 +488,7 @@
var cAF = jsAPI('cancelAnimationFrame');
var rAF = jsAPI('requestAnimationFrame');
var noop = function noop() {};
var debounce = function debounce(functionToDebounce, timeout, maxWait) {
var timeoutId;
var lastCallTime;
@@ -1403,10 +1404,6 @@
};
};
var getAttributeChanged = function getAttributeChanged(mutationTarget, attributeName, oldValue) {
return oldValue !== attr(mutationTarget, attributeName);
};
var createDOMObserver = function createDOMObserver(target, callback, options) {
var isConnected = false;
@@ -1414,8 +1411,10 @@
_observeContent = _ref._observeContent,
_attributes = _ref._attributes,
_styleChangingAttributes = _ref._styleChangingAttributes,
_ignoreContentChange = _ref._ignoreContentChange,
_eventContentChange = _ref._eventContentChange;
_eventContentChange = _ref._eventContentChange,
_nestedTargetSelector = _ref._nestedTargetSelector,
_ignoreTargetChange = _ref._ignoreTargetAttrChange,
_ignoreContentChange = _ref._ignoreContentChange;
var _createEventContentCh = createEventContentChange(
target,
@@ -1436,6 +1435,8 @@
var observedAttributes = finalAttributes.concat(finalStyleChangingAttributes);
var observerCallback = function observerCallback(mutations) {
var ignoreTargetChange = _ignoreTargetChange || noop;
var ignoreContentChange = _ignoreContentChange || noop;
var targetChangedAttrs = [];
var totalAddedNodes = [];
var targetStyleChanged = false;
@@ -1450,10 +1451,14 @@
var isAttributesType = type === 'attributes';
var isChildListType = type === 'childList';
var targetIsMutationTarget = target === mutationTarget;
var attributeChanged = isAttributesType && isString(attributeName) && getAttributeChanged(mutationTarget, attributeName, oldValue);
var targetAttrChanged = attributeChanged && targetIsMutationTarget && !_observeContent;
var attributeValue = isAttributesType && isString(attributeName) ? attr(mutationTarget, attributeName) : 0;
var attributeChanged = attributeValue !== 0 && oldValue !== attributeValue;
var targetAttrChanged =
attributeChanged &&
targetIsMutationTarget &&
!_observeContent &&
!ignoreTargetChange(mutationTarget, attributeName, oldValue, attributeValue);
var styleChangingAttrChanged = indexOf(finalStyleChangingAttributes, attributeName) > -1 && attributeChanged;
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
if (targetAttrChanged) {
push(targetChangedAttrs, attributeName);
@@ -1462,12 +1467,17 @@
if (_observeContent) {
var notOnlyAttrChanged = !isAttributesType;
var contentAttrChanged = isAttributesType && styleChangingAttrChanged && !targetIsMutationTarget;
var contentFinalChanged =
(notOnlyAttrChanged || contentAttrChanged) && (_ignoreContentChange ? !_ignoreContentChange(mutation, target, options) : _observeContent);
var isNestedTarget = contentAttrChanged && _nestedTargetSelector && is(mutationTarget, _nestedTargetSelector);
var baseAssertion = isNestedTarget
? !ignoreTargetChange(mutationTarget, attributeName, oldValue, attributeValue)
: notOnlyAttrChanged || contentAttrChanged;
var contentFinalChanged = baseAssertion && !ignoreContentChange(mutation, isNestedTarget, target, options);
push(totalAddedNodes, addedNodes);
contentChanged = contentChanged || contentFinalChanged;
childListChanged = childListChanged || isChildListType;
}
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
});
if (childListChanged && !isEmptyArray(totalAddedNodes)) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
import {
each,
noop,
debounce,
indexOf,
isString,
@@ -15,22 +16,33 @@ import {
isFunction,
} from 'support';
type TruthyOrFalsy = boolean | '' | 0 | null | undefined;
type StringNullUndefined = string | null | undefined;
export type DOMObserverEventContentChange =
| Array<[StringNullUndefined, ((elms: Node[]) => string) | StringNullUndefined] | null | undefined>
| false
| ''
| null
| undefined;
export type DOMObserverIgnoreContentChange = (
mutation: MutationRecord,
isNestedTarget: TruthyOrFalsy,
domObserverTarget: HTMLElement,
domObserverOptions: DOMObserverOptions | undefined
) => boolean | null | undefined;
) => TruthyOrFalsy;
export type DOMObserverIgnoreTargetAttrChange = (
target: Node,
attributeName: string,
oldAttributeValue: string | null,
newAttributeValue: string | null
) => TruthyOrFalsy;
export interface DOMObserverOptions {
_observeContent?: boolean; // do observe children and trigger content change
_attributes?: string[]; // observed attributes
_styleChangingAttributes?: string[]; // list of attributes that trigger a content change if changed
_styleChangingAttributes?: string[]; // list of attributes that trigger a contentChange or a targetStyleChange if changed
_eventContentChange?: DOMObserverEventContentChange; // [selector, eventname]
_nestedTargetSelector?: string;
_ignoreTargetAttrChange?: DOMObserverIgnoreTargetAttrChange;
_ignoreContentChange?: DOMObserverIgnoreContentChange;
}
export interface DOMObserver {
@@ -111,8 +123,6 @@ const createEventContentChange = (
_update,
};
};
const getAttributeChanged = (mutationTarget: Node, attributeName: string, oldValue: string | null): boolean =>
oldValue !== attr(mutationTarget as HTMLElement, attributeName);
export const createDOMObserver = (
target: HTMLElement,
@@ -120,7 +130,15 @@ export const createDOMObserver = (
options?: DOMObserverOptions
): DOMObserver => {
let isConnected = false;
const { _observeContent, _attributes, _styleChangingAttributes, _eventContentChange, _ignoreContentChange } = options || {};
const {
_observeContent,
_attributes,
_styleChangingAttributes,
_eventContentChange,
_nestedTargetSelector,
_ignoreTargetAttrChange: _ignoreTargetChange,
_ignoreContentChange,
} = options || {};
const {
_updateElements: updateEventContentChangeElements,
_destroy: destroyEventContentChange,
@@ -141,6 +159,8 @@ export const createDOMObserver = (
const finalStyleChangingAttributes = _styleChangingAttributes || [];
const observedAttributes = finalAttributes.concat(finalStyleChangingAttributes); // TODO: observer textarea attrs if textarea
const observerCallback = (mutations: MutationRecord[]) => {
const ignoreTargetChange = _ignoreTargetChange || noop;
const ignoreContentChange = _ignoreContentChange || noop;
const targetChangedAttrs: string[] = [];
const totalAddedNodes: Node[] = [];
let targetStyleChanged = false;
@@ -151,26 +171,33 @@ export const createDOMObserver = (
const isAttributesType = type === 'attributes';
const isChildListType = type === 'childList';
const targetIsMutationTarget = target === mutationTarget;
const attributeChanged = isAttributesType && isString(attributeName) && getAttributeChanged(mutationTarget, attributeName!, oldValue);
const targetAttrChanged = attributeChanged && targetIsMutationTarget && !_observeContent;
const attributeValue = isAttributesType && isString(attributeName) ? attr(mutationTarget as HTMLElement, attributeName!) : 0;
const attributeChanged = attributeValue !== 0 && oldValue !== attributeValue;
const targetAttrChanged =
attributeChanged &&
targetIsMutationTarget &&
!_observeContent &&
!ignoreTargetChange(mutationTarget, attributeName!, oldValue, attributeValue as string | null);
const styleChangingAttrChanged = indexOf(finalStyleChangingAttributes, attributeName) > -1 && attributeChanged;
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
if (targetAttrChanged) {
push(targetChangedAttrs, attributeName!);
}
if (_observeContent) {
const notOnlyAttrChanged = !isAttributesType;
const contentAttrChanged = isAttributesType && styleChangingAttrChanged && !targetIsMutationTarget;
const contentFinalChanged =
(notOnlyAttrChanged || contentAttrChanged) && (_ignoreContentChange ? !_ignoreContentChange(mutation, target, options) : _observeContent);
const isNestedTarget = contentAttrChanged && _nestedTargetSelector && is(mutationTarget, _nestedTargetSelector);
const baseAssertion = isNestedTarget
? !ignoreTargetChange(mutationTarget, attributeName!, oldValue, attributeValue as string | null)
: notOnlyAttrChanged || contentAttrChanged;
const contentFinalChanged = baseAssertion && !ignoreContentChange(mutation, isNestedTarget, target, options);
push(totalAddedNodes, addedNodes);
contentChanged = contentChanged || contentFinalChanged;
childListChanged = childListChanged || isChildListType;
}
targetStyleChanged = targetStyleChanged || (targetAttrChanged && styleChangingAttrChanged);
});
if (childListChanged && !isEmptyArray(totalAddedNodes)) {
@@ -1,4 +1,6 @@
import { isString } from 'support/utils/types';
import { each } from 'support/utils/array';
import { keys } from 'support/utils/object';
const rnothtmlwhite = /[^\x20\t\r\n\f]+/g;
const classListAction = (elm: Element | null, className: string, action: (elmClassList: DOMTokenList, clazz: string) => boolean | void): boolean => {
@@ -41,3 +43,27 @@ export const addClass = (elm: Element | null, className: string): void => {
export const removeClass = (elm: Element | null, className: string): void => {
classListAction(elm, className, (classList, clazz) => classList.remove(clazz));
};
/**
* Takes two className strings, compares them and returns the difference as array.
* @param classNameA ClassName A.
* @param classNameB ClassName B.
*/
export const diffClass = (classNameA: string | null | undefined, classNameB: string | null | undefined) => {
const classNameASplit = classNameA && classNameA.split(' ');
const classNameBSplit = classNameB && classNameB.split(' ');
const tempObj = {};
each(classNameASplit, (className) => {
tempObj[className] = 1;
});
each(classNameBSplit, (className) => {
if (tempObj[className]) {
delete tempObj[className];
} else {
tempObj[className] = 1;
}
});
return keys(tempObj);
};
@@ -1,6 +1,8 @@
import { isNumber } from 'support/utils/types';
import { cAF, rAF } from 'support/compatibility/apis';
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.
@@ -1,4 +1,4 @@
import { addClass, removeClass, hasClass } from 'support/dom/class';
import { addClass, removeClass, hasClass, diffClass } from 'support/dom/class';
const testElm = document.body;
const removeAllClassNames = () => {
@@ -92,4 +92,26 @@ describe('dom class names', () => {
expect(hasClass(null, 'abc')).toBe(false);
});
});
describe('diff', () => {
test('none', () => {
expect(diffClass('', '')).toEqual([]);
});
test('single', () => {
expect(diffClass('test', '')).toEqual(['test']);
expect(diffClass('', 'test')).toEqual(['test']);
});
test('multiple', () => {
expect(diffClass('a b c d', 'a c')).toEqual(['b', 'd']);
expect(diffClass('d b', 'a b c d')).toEqual(['a', 'c']);
});
test('null', () => {
expect(diffClass(null, null)).toEqual([]);
expect(diffClass('a c', null)).toEqual(['a', 'c']);
expect(diffClass('d b', null)).toEqual(['d', 'b']);
});
});
});
@@ -4,7 +4,20 @@ 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, on } from 'support';
import {
appendChildren,
createDiv,
removeElements,
children,
isArray,
isNumber,
liesBetween,
hasClass,
addClass,
removeClass,
diffClass,
on,
} from 'support';
import { createDOMObserver } from 'observers/domObserver';
@@ -21,6 +34,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 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');
const contentHostElmAttrChange: HTMLElement | null = document.querySelector('#content-nest-item-host');
@@ -36,6 +50,7 @@ const addRemoveTargetContentElms: HTMLButtonElement | null = document.querySelec
const addRemoveTargetContentBetweenElms: HTMLButtonElement | null = document.querySelector('#addRemoveTargetContentBetweenElms');
const addRemoveImgElms: HTMLButtonElement | null = document.querySelector('#addRemoveImgElms');
const addRemoveTransitionElms: HTMLButtonElement | null = document.querySelector('#addRemoveTransitionElms');
const ignoreTargetChange: HTMLButtonElement | null = document.querySelector('#ignoreTargetChange');
const setTargetAttr: HTMLSelectElement | null = document.querySelector('#setTargetAttr');
const setFilteredTargetAttr: HTMLSelectElement | null = document.querySelector('#setFilteredTargetAttr');
const setContentAttr: HTMLSelectElement | null = document.querySelector('#setContentAttr');
@@ -49,6 +64,8 @@ const summaryBetween: HTMLElement | null = document.querySelector('#summary-betw
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 targetElmObservations: DOMObserverResult[] = [];
@@ -67,6 +84,14 @@ createDOMObserver(
{
_styleChangingAttributes: attrs,
_attributes: attrs.concat(['data-target']),
_ignoreTargetAttrChange: (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;
},
}
);
const { _updateEventContentChange } = createDOMObserver(
@@ -84,9 +109,18 @@ const { _updateEventContentChange } = createDOMObserver(
_styleChangingAttributes: attrs,
_attributes: attrs,
_eventContentChange: contentChangeArr,
_ignoreContentChange: (mutation) => {
_nestedTargetSelector: hostSelector,
_ignoreContentChange: (mutation, isNestedTarget) => {
const { target, attributeName } = mutation;
return attributeName ? !hasClass(target as Element, 'host') && liesBetween(target as Element, '.host', '.content') : false;
return isNestedTarget ? false : attributeName ? liesBetween(target as Element, hostSelector, '.content') : false;
},
_ignoreTargetAttrChange: (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;
},
}
);
@@ -423,6 +457,25 @@ const addRemoveTransitionElmsFn = async () => {
await add(true);
};
const ignoreTargetChangeFn = async () => {
const check = async (target: Element | null, changeThrough: DOMObserverResult[]) => {
const { before, after, compare } = changedThrough(changeThrough);
before();
target?.classList.add(`${ignorePrefix}-something`);
await timeout(250);
target?.classList.remove(`${ignorePrefix}-something`);
await timeout(250);
await waitForOrFailTest(() => {
after();
compare(0);
});
};
await check(targetElm, targetElmObservations);
await check(targetElmContentElm, targetElmContentElmObservations);
};
const iterateTargetAttrChange = async () => {
await iterateAttrChange(setTargetAttr, targetElmObservations, (observation, selected) => {
const { changedTargetAttrs, styleChanged, contentChanged } = observation;
@@ -466,6 +519,7 @@ addRemoveTargetContentElms?.addEventListener('click', addRemoveTargetContentElms
addRemoveTargetContentBetweenElms?.addEventListener('click', addRemoveTargetContentBetweenElmsFn);
addRemoveImgElms?.addEventListener('click', addRemoveImgElmsFn);
addRemoveTransitionElms?.addEventListener('click', addRemoveTransitionElmsFn);
ignoreTargetChange?.addEventListener('click', ignoreTargetChangeFn);
setTargetAttr?.addEventListener('change', attrChangeListener(targetElm));
setFilteredTargetAttr?.addEventListener('change', attrChangeListener(targetElm));
setContentAttr?.addEventListener('change', attrChangeListener(contentElmAttrChange));
@@ -4,6 +4,7 @@
<button id="addRemoveTargetContentBetweenElms">Content Between Elements</button>
<button id="addRemoveImgElms">Image Elements</button>
<button id="addRemoveTransitionElms">Transition Elements</button>
<button id="ignoreTargetChange">Change Ignored Attribute</button>
<label for="setTargetAttr">setTargetAttr</label>
<select name="setTargetAttr" id="setTargetAttr">
@@ -1,11 +1,15 @@
declare type TruthyOrFalsy = boolean | '' | 0 | null | undefined;
declare type StringNullUndefined = string | null | undefined;
export declare type DOMObserverEventContentChange = Array<[StringNullUndefined, ((elms: Node[]) => string) | StringNullUndefined] | null | undefined> | false | null | undefined;
export declare type DOMObserverIgnoreContentChange = (mutation: MutationRecord, domObserverTarget: HTMLElement, domObserverOptions: DOMObserverOptions | undefined) => boolean | null | undefined;
export declare type DOMObserverEventContentChange = Array<[StringNullUndefined, ((elms: Node[]) => string) | StringNullUndefined] | null | undefined> | false | '' | null | undefined;
export declare type DOMObserverIgnoreContentChange = (mutation: MutationRecord, isNestedTarget: TruthyOrFalsy, domObserverTarget: HTMLElement, domObserverOptions: DOMObserverOptions | undefined) => TruthyOrFalsy;
export declare type DOMObserverIgnoreTargetAttrChange = (target: Node, attributeName: string, oldAttributeValue: string | null, newAttributeValue: string | null) => TruthyOrFalsy;
export interface DOMObserverOptions {
_observeContent?: boolean;
_attributes?: string[];
_styleChangingAttributes?: string[];
_eventContentChange?: DOMObserverEventContentChange;
_nestedTargetSelector?: string;
_ignoreTargetAttrChange?: DOMObserverIgnoreTargetAttrChange;
_ignoreContentChange?: DOMObserverIgnoreContentChange;
}
export interface DOMObserver {
@@ -1,3 +1,4 @@
export declare const hasClass: (elm: Element | null, className: string) => boolean;
export declare const addClass: (elm: Element | null, className: string) => void;
export declare const removeClass: (elm: Element | null, className: string) => void;
export declare const diffClass: (classNameA: string | null | undefined, classNameB: string | null | undefined) => string[];
@@ -1 +1,2 @@
export declare const noop: () => void;
export declare const debounce: (functionToDebounce: (...args: any) => any, timeout?: number | undefined, maxWait?: number | undefined) => () => void;