mirror of
https://github.com/tenrok/OverlayScrollbars.git
synced 2026-05-20 16:37:59 +03:00
add ignore target change feature to domObserver
This commit is contained in:
+91
-73
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user