Socket
Socket
Sign inDemoInstall

dom-mutator

Package Overview
Dependencies
0
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.4 to 0.3.0

dist/types.d.ts

464

dist/dom-mutator.cjs.development.js

@@ -5,8 +5,9 @@ 'use strict';

var mutations = {};
var validAttributeName = /^[a-zA-Z:_][a-zA-Z0-9:_.-]*$/;
var nullController = {
revert: function revert() {}
};
var elements = /*#__PURE__*/new Map();
var mutationIdCounter = 1; // attr="value" format
var mutations = /*#__PURE__*/new Set();
var setAttributeRegex = /^([a-zA-Z:_][a-zA-Z0-9:_.-]*)\s*=\s*"([^"]*)"/;
function getObserverInit(attr) {

@@ -30,135 +31,188 @@ if (attr === 'html') {

function getElementAttributeRecord(el, attr) {
var element = elements.get(el);
function getElementRecord(el) {
var ret = elements.get(el);
if (!element) {
element = {};
elements.set(el, element);
if (!ret) {
ret = {
el: el,
attributes: {}
};
elements.set(el, ret);
}
if (!element[attr]) {
var currentValue = getCurrentValue(el, attr);
var elAttr = {
originalValue: currentValue,
virtualValue: currentValue,
observer: new MutationObserver(function () {
var currentValue = getCurrentValue(el, attr);
if (currentValue === elAttr.virtualValue) return;
elAttr.originalValue = currentValue;
applyMutations(el, attr);
}),
mutations: []
};
element[attr] = elAttr;
elAttr.observer.observe(el, getObserverInit(attr));
return ret;
}
function newMutatedElementAttribute(el, attr, getCurrentValue, setValue, runMutations) {
var currentValue = getCurrentValue(el);
var ret = {
isDirty: false,
originalValue: currentValue,
virtualValue: currentValue,
mutations: [],
el: el,
observer: new MutationObserver(function () {
var currentValue = getCurrentValue(el);
if (currentValue === ret.virtualValue) return;
ret.originalValue = currentValue;
runMutations(ret);
}),
runMutations: runMutations,
setValue: setValue,
getCurrentValue: getCurrentValue
};
ret.observer.observe(el, getObserverInit(attr));
return ret;
}
function stringRunner(record) {
var val = record.originalValue;
record.mutations.forEach(function (m) {
return val = m.mutate(val);
});
return val;
}
function setRunner(val, record) {
record.mutations.forEach(function (m) {
return m.mutate(val);
});
return val;
}
function queueIfNeeded(val, record) {
var currentVal = record.getCurrentValue(record.el);
record.virtualValue = val;
if (val !== currentVal) {
record.isDirty = true;
queueDOMUpdates();
}
}
return element[attr];
function HTMLMutationRunner(record) {
queueIfNeeded(getTransformedHTML(stringRunner(record)), record);
}
function deleteElementAttributeRecord(el, attr) {
var element = elements.get(el);
/* istanbul ignore next */
function ClassMutationRunner(record) {
var val = setRunner(new Set(record.originalValue.split(/\s+/).filter(Boolean)), record);
queueIfNeeded(Array.from(val).filter(Boolean).join(' '), record);
}
if (!element) return;
element[attr] && element[attr].observer.disconnect();
delete element[attr];
function AttributeMutationRunner(record) {
queueIfNeeded(stringRunner(record), record);
}
var transformContainer;
var getHTMLValue = function getHTMLValue(el) {
return el.innerHTML;
};
function getTransformedHTML(html) {
if (!transformContainer) {
transformContainer = document.createElement('div');
var setHTMLValue = function setHTMLValue(el, value) {
return el.innerHTML = value;
};
function getElementHTMLRecord(el) {
var elementRecord = getElementRecord(el);
if (!elementRecord.html) {
elementRecord.html = newMutatedElementAttribute(el, 'html', getHTMLValue, setHTMLValue, HTMLMutationRunner);
}
transformContainer.innerHTML = html;
return transformContainer.innerHTML;
return elementRecord.html;
}
function applyMutation(mutation, value) {
if (mutation.type === 'addClass') {
var existing = value.split(' ');
var classes = mutation.value.split(' ');
classes.forEach(function (c) {
if (!existing.includes(c)) {
existing.push(c);
}
});
return existing.filter(Boolean).join(' ');
} else if (mutation.type === 'removeClass') {
var _existing = value.split(' ');
var setClassValue = function setClassValue(el, val) {
return val ? el.className = val : el.removeAttribute('class');
};
var _classes = mutation.value.split(' ');
var getClassValue = function getClassValue(el) {
return el.className;
};
return _existing.filter(function (c) {
return !_classes.includes(c);
}).join(' ');
} else if (mutation.type === 'setHTML') {
return mutation.value;
} else if (mutation.type === 'appendHTML') {
return value + mutation.value;
} else if (mutation.type === 'setAttribute') {
/* istanbul ignore next */
var match = setAttributeRegex.exec(mutation.value);
/* istanbul ignore next */
function getElementClassRecord(el) {
var elementRecord = getElementRecord(el);
return (match == null ? void 0 : match[2]) || '';
if (!elementRecord.classes) {
elementRecord.classes = newMutatedElementAttribute(el, 'class', getClassValue, setClassValue, ClassMutationRunner);
}
/* istanbul ignore next */
return elementRecord.classes;
}
return value;
function getElementAttributeRecord(el, attr) {
var elementRecord = getElementRecord(el);
if (!elementRecord.attributes[attr]) {
elementRecord.attributes[attr] = newMutatedElementAttribute(el, attr, function (el) {
return el.getAttribute(attr) || '';
}, function (el, val) {
return val ? el.setAttribute(attr, val) : el.removeAttribute(attr);
}, AttributeMutationRunner);
}
return elementRecord.attributes[attr];
}
function getCurrentValue(el, attr) {
function deleteElementAttributeRecord(el, attr) {
var element = elements.get(el);
/* istanbul ignore next */
if (!element) return;
if (attr === 'html') {
return el.innerHTML;
} else if (attr === 'className') {
return el.className;
var _element$html, _element$html$observe;
(_element$html = element.html) == null ? void 0 : (_element$html$observe = _element$html.observer) == null ? void 0 : _element$html$observe.disconnect();
delete element.html;
} else if (attr === 'class') {
var _element$classes, _element$classes$obse;
(_element$classes = element.classes) == null ? void 0 : (_element$classes$obse = _element$classes.observer) == null ? void 0 : _element$classes$obse.disconnect();
delete element.classes;
} else {
return el.getAttribute(attr) || '';
var _element$attributes, _element$attributes$a, _element$attributes$a2;
(_element$attributes = element.attributes) == null ? void 0 : (_element$attributes$a = _element$attributes[attr]) == null ? void 0 : (_element$attributes$a2 = _element$attributes$a.observer) == null ? void 0 : _element$attributes$a2.disconnect();
delete element.attributes[attr];
}
}
function setValue(el, attr, value) {
if (attr === 'html') {
el.innerHTML = value;
} else if (attr === 'className') {
if (value) {
el.className = value;
} else {
el.removeAttribute('class');
}
} else {
if (value) {
el.setAttribute(attr, value);
} else {
el.removeAttribute(attr);
}
var transformContainer;
function getTransformedHTML(html) {
if (!transformContainer) {
transformContainer = document.createElement('div');
}
transformContainer.innerHTML = html;
return transformContainer.innerHTML;
}
var raf = false;
function setAttributeValue(el, attr, m) {
if (!m.isDirty) return;
m.isDirty = false;
var val = m.virtualValue;
function setValues() {
raf = false;
elements.forEach(function (attrs, el) {
Object.keys(attrs).forEach(function (attr) {
var elAttr = attrs[attr];
if (!m.mutations.length) {
deleteElementAttributeRecord(el, attr);
}
if (elAttr.dirty) {
elAttr.dirty = false;
var value = elAttr.virtualValue; // No more mutations for the element, remove the observer
m.setValue(el, val);
}
if (!elAttr.mutations.length) {
deleteElementAttributeRecord(el, attr);
}
var raf = false;
setValue(el, attr, value);
}
});
function setValue(m, el) {
m.html && setAttributeValue(el, 'html', m.html);
m.classes && setAttributeValue(el, 'class', m.classes);
Object.keys(m.attributes).forEach(function (attr) {
setAttributeValue(el, attr, m.attributes[attr]);
});
}
function setValues() {
raf = false;
elements.forEach(setValue);
}
function queueDOMUpdates() {

@@ -171,78 +225,60 @@ if (!raf) {

function applyMutations(el, attr) {
var elAttr = getElementAttributeRecord(el, attr);
var val = elAttr.originalValue;
elAttr.mutations.forEach(function (id) {
var mutation = mutations[id];
/* istanbul ignore next */
function startMutating(mutation, el) {
mutation.elements.add(el);
if (!mutation) return;
val = applyMutation(mutation, val);
});
elAttr.virtualValue = val;
var currentVal = getCurrentValue(el, attr);
if (mutation.kind === 'html') {
var record = getElementHTMLRecord(el);
record.mutations.push(mutation);
record.runMutations(record);
} else if (mutation.kind === 'class') {
var _record = getElementClassRecord(el);
if (elAttr.virtualValue !== currentVal) {
elAttr.dirty = true;
queueDOMUpdates();
_record.mutations.push(mutation);
_record.runMutations(_record);
} else if (mutation.kind === 'attribute') {
var _record2 = getElementAttributeRecord(el, mutation.attribute);
_record2.mutations.push(mutation);
_record2.runMutations(_record2);
}
}
function getAttribute(type, value) {
if (['addClass', 'removeClass'].includes(type)) {
return 'className';
} else if (['appendHTML', 'setHTML'].includes(type)) {
return 'html';
} else if (type === 'setAttribute') {
var match = setAttributeRegex.exec(value);
function stopMutating(mutation, el) {
mutation.elements["delete"](el);
if (match != null && match[1]) {
var attr = match[1];
if (mutation.kind === 'html') {
var record = getElementHTMLRecord(el);
var index = record.mutations.indexOf(mutation);
if (attr === 'class' || attr === 'classname') {
return 'className';
}
return attr;
if (index !== -1) {
record.mutations.splice(index, 1);
}
}
return '';
}
record.runMutations(record);
} else if (mutation.kind === 'class') {
var _record3 = getElementClassRecord(el);
function startMutating(id, el) {
var mutation = mutations[id];
/* istanbul ignore next */
var _index = _record3.mutations.indexOf(mutation);
if (!mutation) return;
mutation.elements.add(el);
var attr = getAttribute(mutation.type, mutation.value);
var elAttr = getElementAttributeRecord(el, attr);
elAttr.mutations.push(id);
applyMutations(el, attr);
}
if (_index !== -1) {
_record3.mutations.splice(_index, 1);
}
function stopMutating(id, el) {
var mutation = mutations[id];
/* istanbul ignore next */
_record3.runMutations(_record3);
} else if (mutation.kind === 'attribute') {
var _record4 = getElementAttributeRecord(el, mutation.attribute);
if (!mutation) return;
mutation.elements["delete"](el);
var attr = getAttribute(mutation.type, mutation.value);
var elAttr = getElementAttributeRecord(el, attr);
var index = elAttr.mutations.indexOf(id);
/* istanbul ignore next */
var _index2 = _record4.mutations.indexOf(mutation);
if (index !== -1) {
elAttr.mutations.splice(index, 1);
if (_index2 !== -1) {
_record4.mutations.splice(_index2, 1);
}
_record4.runMutations(_record4);
}
applyMutations(el, attr);
}
function refreshElementsSet(id) {
var mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
function refreshElementsSet(mutation) {
var existingEls = new Set(mutation.elements);

@@ -255,3 +291,3 @@ var newElements = new Set();

if (!existingEls.has(el)) {
startMutating(id, el);
startMutating(mutation, el);
}

@@ -261,3 +297,3 @@ });

if (!newElements.has(el)) {
stopMutating(id, el);
stopMutating(mutation, el);
}

@@ -267,36 +303,13 @@ });

function newMutation(selector, type, value) {
// Fix invalid HTML values
if (type === 'appendHTML' || type === 'setHTML') {
value = getTransformedHTML(value);
}
var id = '' + mutationIdCounter++;
mutations[id] = {
selector: selector,
type: type,
value: value,
elements: new Set()
};
refreshElementsSet(id);
return id;
}
function revertMutation(id) {
var mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
function revertMutation(mutation) {
var els = new Set(mutation.elements);
els.forEach(function (el) {
stopMutating(id, el);
stopMutating(mutation, el);
});
mutations[id].elements.clear();
delete mutations[id];
mutation.elements.clear();
mutations["delete"](mutation);
}
function refreshAllElementSets() {
Object.keys(mutations).forEach(function (key) {
refreshElementsSet(key);
});
mutations.forEach(refreshElementsSet);
} // Observer for elements that don't exist in the DOM yet

@@ -328,27 +341,76 @@

connectGlobalObserver();
function mutate(selector, type, value) {
function newMutationRecord(m) {
/* istanbul ignore next */
if (typeof document === 'undefined') {
// Not in a browser
return function () {// do nothing
};
} // Invalid mutation
return nullController;
}
mutations.add(m);
refreshElementsSet(m);
return {
revert: function revert() {
revertMutation(m);
}
};
}
var attr = getAttribute(type, value);
function html(selector, mutate) {
return newMutationRecord({
kind: 'html',
elements: new Set(),
mutate: mutate,
selector: selector
});
}
if (!attr) {
return function () {// do nothing
};
function classes(selector, mutate) {
return newMutationRecord({
kind: 'class',
elements: new Set(),
mutate: mutate,
selector: selector
});
}
function attribute(selector, attribute, _mutate) {
if (!validAttributeName.test(attribute)) {
return nullController;
}
var id = newMutation(selector, type, value);
return function () {
revertMutation(id);
};
if (attribute === 'class' || attribute === 'className') {
return newMutationRecord({
kind: 'class',
elements: new Set(),
mutate: function mutate(classes) {
var val = _mutate(Array.from(classes).join(' '));
classes.clear();
val.split(/\s+/g).filter(Boolean).forEach(function (c) {
classes.add(c);
});
},
selector: selector
});
}
return newMutationRecord({
kind: 'attribute',
attribute: attribute,
elements: new Set(),
mutate: _mutate,
selector: selector
});
}
var index = {
html: html,
classes: classes,
attribute: attribute
};
exports.connectGlobalObserver = connectGlobalObserver;
exports.default = mutate;
exports.default = index;
exports.disconnectGlobalObserver = disconnectGlobalObserver;
//# sourceMappingURL=dom-mutator.cjs.development.js.map

@@ -1,2 +0,2 @@

"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e,t={},n=new Map,r=1,a=/^([a-zA-Z:_][a-zA-Z0-9:_.-]*)\s*=\s*"([^"]*)"/;function i(e,t){var r=n.get(e);if(r||n.set(e,r={}),!r[t]){var a=u(e,t),i={originalValue:a,virtualValue:a,observer:new MutationObserver((function(){var n=u(e,t);n!==i.virtualValue&&(i.originalValue=n,l(e,t))})),mutations:[]};r[t]=i,i.observer.observe(e,function(e){return"html"===e?{childList:!0,subtree:!0,attributes:!0,characterData:!0}:{childList:!1,subtree:!1,attributes:!0,attributeFilter:[e]}}(t))}return r[t]}function u(e,t){return"html"===t?e.innerHTML:"className"===t?e.className:e.getAttribute(t)||""}var s,o=!1;function c(){o=!1,n.forEach((function(e,t){Object.keys(e).forEach((function(r){var a=e[r];if(a.dirty){a.dirty=!1;var i=a.virtualValue;a.mutations.length||function(e,t){var r=n.get(e);r&&(r[t]&&r[t].observer.disconnect(),delete r[t])}(t,r),function(e,t,n){"html"===t?e.innerHTML=n:"className"===t?n?e.className=n:e.removeAttribute("class"):n?e.setAttribute(t,n):e.removeAttribute(t)}(t,r,i)}}))}))}function l(e,n){var r=i(e,n),s=r.originalValue;r.mutations.forEach((function(e){var n=t[e];n&&(s=function(e,t){if("addClass"===e.type){var n=t.split(" ");return e.value.split(" ").forEach((function(e){n.includes(e)||n.push(e)})),n.filter(Boolean).join(" ")}if("removeClass"===e.type){var r=t.split(" "),i=e.value.split(" ");return r.filter((function(e){return!i.includes(e)})).join(" ")}if("setHTML"===e.type)return e.value;if("appendHTML"===e.type)return t+e.value;if("setAttribute"===e.type){var u=a.exec(e.value);return(null==u?void 0:u[2])||""}return t}(n,s))})),r.virtualValue=s;var l=u(e,n);r.virtualValue!==l&&(r.dirty=!0,o||(o=!0,requestAnimationFrame(c)))}function f(e,t){if(["addClass","removeClass"].includes(e))return"className";if(["appendHTML","setHTML"].includes(e))return"html";if("setAttribute"===e){var n=a.exec(t);if(null!=n&&n[1]){var r=n[1];return"class"===r||"classname"===r?"className":r}}return""}function v(e,n){var r=t[e];if(r){r.elements.delete(n);var a=f(r.type,r.value),u=i(n,a),s=u.mutations.indexOf(e);-1!==s&&u.mutations.splice(s,1),l(n,a)}}function d(e){var n=t[e];if(n){var r=new Set(n.elements),a=new Set;document.body.querySelectorAll(n.selector).forEach((function(n){a.add(n),r.has(n)||function(e,n){var r=t[e];if(r){r.elements.add(n);var a=f(r.type,r.value);i(n,a).mutations.push(e),l(n,a)}}(e,n)})),r.forEach((function(t){a.has(t)||v(e,t)}))}}function m(){Object.keys(t).forEach((function(e){d(e)}))}function b(){"undefined"!=typeof document&&(s||(s=new MutationObserver((function(){m()}))),m(),s.observe(document.body,{childList:!0,subtree:!0,attributes:!1,characterData:!1}))}b(),exports.connectGlobalObserver=b,exports.default=function(n,a,i){if("undefined"==typeof document)return function(){};if(!f(a,i))return function(){};var u=function(n,a,i){var u;"appendHTML"!==a&&"setHTML"!==a||(u=i,e||(e=document.createElement("div")),e.innerHTML=u,i=e.innerHTML);var s=""+r++;return t[s]={selector:n,type:a,value:i,elements:new Set},d(s),s}(n,a,i);return function(){!function(e){var n=t[e];n&&(new Set(n.elements).forEach((function(t){v(e,t)})),t[e].elements.clear(),delete t[e])}(u)}},exports.disconnectGlobalObserver=function(){s&&s.disconnect()};
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=/^[a-zA-Z:_][a-zA-Z0-9:_.-]*$/,e={revert:function(){}},n=new Map,r=new Set;function u(t){var e=n.get(t);return e||n.set(t,e={el:t,attributes:{}}),e}function i(t,e,n,r,u){var i=n(t),a={isDirty:!1,originalValue:i,virtualValue:i,mutations:[],el:t,observer:new MutationObserver((function(){var e=n(t);e!==a.virtualValue&&(a.originalValue=e,u(a))})),runMutations:u,setValue:r,getCurrentValue:n};return a.observer.observe(t,function(t){return"html"===t?{childList:!0,subtree:!0,attributes:!0,characterData:!0}:{childList:!1,subtree:!1,attributes:!0,attributeFilter:[t]}}(e)),a}function a(t){var e=t.originalValue;return t.mutations.forEach((function(t){return e=t.mutate(e)})),e}function s(t,e){var n=e.getCurrentValue(e.el);e.virtualValue=t,t!==n&&(e.isDirty=!0,V||(V=!0,requestAnimationFrame(k)))}function o(t){var e;s((e=a(t),v||(v=document.createElement("div")),v.innerHTML=e,v.innerHTML),t)}function l(t){var e=function(t,e){return e.mutations.forEach((function(e){return e.mutate(t)})),t}(new Set(t.originalValue.split(/\s+/).filter(Boolean)),t);s(Array.from(e).filter(Boolean).join(" "),t)}function c(t){s(a(t),t)}var f=function(t){return t.innerHTML},m=function(t,e){return t.innerHTML=e};function d(t){var e=u(t);return e.html||(e.html=i(t,"html",f,m,o)),e.html}var v,b=function(t,e){return e?t.className=e:t.removeAttribute("class")},h=function(t){return t.className};function p(t){var e=u(t);return e.classes||(e.classes=i(t,"class",h,b,l)),e.classes}function M(t,e){var n=u(t);return n.attributes[e]||(n.attributes[e]=i(t,e,(function(t){return t.getAttribute(e)||""}),(function(t,n){return n?t.setAttribute(e,n):t.removeAttribute(e)}),c)),n.attributes[e]}function y(t,e,r){if(r.isDirty){r.isDirty=!1;var u=r.virtualValue;r.mutations.length||function(t,e){var r,u,i=n.get(t);if(i)if("html"===e)null==(r=i.html)||null==(u=r.observer)||u.disconnect(),delete i.html;else if("class"===e){var a,s;null==(a=i.classes)||null==(s=a.observer)||s.disconnect(),delete i.classes}else{var o,l,c;null==(o=i.attributes)||null==(l=o[e])||null==(c=l.observer)||c.disconnect(),delete i.attributes[e]}}(t,e),r.setValue(t,u)}}var w,V=!1;function g(t,e){t.html&&y(e,"html",t.html),t.classes&&y(e,"class",t.classes),Object.keys(t.attributes).forEach((function(n){y(e,n,t.attributes[n])}))}function k(){V=!1,n.forEach(g)}function A(t,e){if(t.elements.delete(e),"html"===t.kind){var n=d(e),r=n.mutations.indexOf(t);-1!==r&&n.mutations.splice(r,1),n.runMutations(n)}else if("class"===t.kind){var u=p(e),i=u.mutations.indexOf(t);-1!==i&&u.mutations.splice(i,1),u.runMutations(u)}else if("attribute"===t.kind){var a=M(e,t.attribute),s=a.mutations.indexOf(t);-1!==s&&a.mutations.splice(s,1),a.runMutations(a)}}function E(t){var e=new Set(t.elements),n=new Set;document.body.querySelectorAll(t.selector).forEach((function(r){n.add(r),e.has(r)||function(t,e){if(t.elements.add(e),"html"===t.kind){var n=d(e);n.mutations.push(t),n.runMutations(n)}else if("class"===t.kind){var r=p(e);r.mutations.push(t),r.runMutations(r)}else if("attribute"===t.kind){var u=M(e,t.attribute);u.mutations.push(t),u.runMutations(u)}}(t,r)})),e.forEach((function(e){n.has(e)||A(t,e)}))}function S(){r.forEach(E)}function O(){"undefined"!=typeof document&&(w||(w=new MutationObserver((function(){S()}))),S(),w.observe(document.body,{childList:!0,subtree:!0,attributes:!1,characterData:!1}))}function x(t){return"undefined"==typeof document?e:(r.add(t),E(t),{revert:function(){var e;e=t,new Set(e.elements).forEach((function(t){A(e,t)})),e.elements.clear(),r.delete(e)}})}O();var L={html:function(t,e){return x({kind:"html",elements:new Set,mutate:e,selector:t})},classes:function(t,e){return x({kind:"class",elements:new Set,mutate:e,selector:t})},attribute:function(n,r,u){return t.test(r)?x("class"===r||"className"===r?{kind:"class",elements:new Set,mutate:function(t){var e=u(Array.from(t).join(" "));t.clear(),e.split(/\s+/g).filter(Boolean).forEach((function(e){t.add(e)}))},selector:n}:{kind:"attribute",attribute:r,elements:new Set,mutate:u,selector:n}):e}};exports.connectGlobalObserver=O,exports.default=L,exports.disconnectGlobalObserver=function(){w&&w.disconnect()};
//# sourceMappingURL=dom-mutator.cjs.production.min.js.map

@@ -1,7 +0,8 @@

var mutations = {};
var validAttributeName = /^[a-zA-Z:_][a-zA-Z0-9:_.-]*$/;
var nullController = {
revert: function revert() {}
};
var elements = /*#__PURE__*/new Map();
var mutationIdCounter = 1; // attr="value" format
var mutations = /*#__PURE__*/new Set();
var setAttributeRegex = /^([a-zA-Z:_][a-zA-Z0-9:_.-]*)\s*=\s*"([^"]*)"/;
function getObserverInit(attr) {

@@ -25,135 +26,188 @@ if (attr === 'html') {

function getElementAttributeRecord(el, attr) {
var element = elements.get(el);
function getElementRecord(el) {
var ret = elements.get(el);
if (!element) {
element = {};
elements.set(el, element);
if (!ret) {
ret = {
el: el,
attributes: {}
};
elements.set(el, ret);
}
if (!element[attr]) {
var currentValue = getCurrentValue(el, attr);
var elAttr = {
originalValue: currentValue,
virtualValue: currentValue,
observer: new MutationObserver(function () {
var currentValue = getCurrentValue(el, attr);
if (currentValue === elAttr.virtualValue) return;
elAttr.originalValue = currentValue;
applyMutations(el, attr);
}),
mutations: []
};
element[attr] = elAttr;
elAttr.observer.observe(el, getObserverInit(attr));
return ret;
}
function newMutatedElementAttribute(el, attr, getCurrentValue, setValue, runMutations) {
var currentValue = getCurrentValue(el);
var ret = {
isDirty: false,
originalValue: currentValue,
virtualValue: currentValue,
mutations: [],
el: el,
observer: new MutationObserver(function () {
var currentValue = getCurrentValue(el);
if (currentValue === ret.virtualValue) return;
ret.originalValue = currentValue;
runMutations(ret);
}),
runMutations: runMutations,
setValue: setValue,
getCurrentValue: getCurrentValue
};
ret.observer.observe(el, getObserverInit(attr));
return ret;
}
function stringRunner(record) {
var val = record.originalValue;
record.mutations.forEach(function (m) {
return val = m.mutate(val);
});
return val;
}
function setRunner(val, record) {
record.mutations.forEach(function (m) {
return m.mutate(val);
});
return val;
}
function queueIfNeeded(val, record) {
var currentVal = record.getCurrentValue(record.el);
record.virtualValue = val;
if (val !== currentVal) {
record.isDirty = true;
queueDOMUpdates();
}
}
return element[attr];
function HTMLMutationRunner(record) {
queueIfNeeded(getTransformedHTML(stringRunner(record)), record);
}
function deleteElementAttributeRecord(el, attr) {
var element = elements.get(el);
/* istanbul ignore next */
function ClassMutationRunner(record) {
var val = setRunner(new Set(record.originalValue.split(/\s+/).filter(Boolean)), record);
queueIfNeeded(Array.from(val).filter(Boolean).join(' '), record);
}
if (!element) return;
element[attr] && element[attr].observer.disconnect();
delete element[attr];
function AttributeMutationRunner(record) {
queueIfNeeded(stringRunner(record), record);
}
var transformContainer;
var getHTMLValue = function getHTMLValue(el) {
return el.innerHTML;
};
function getTransformedHTML(html) {
if (!transformContainer) {
transformContainer = document.createElement('div');
var setHTMLValue = function setHTMLValue(el, value) {
return el.innerHTML = value;
};
function getElementHTMLRecord(el) {
var elementRecord = getElementRecord(el);
if (!elementRecord.html) {
elementRecord.html = newMutatedElementAttribute(el, 'html', getHTMLValue, setHTMLValue, HTMLMutationRunner);
}
transformContainer.innerHTML = html;
return transformContainer.innerHTML;
return elementRecord.html;
}
function applyMutation(mutation, value) {
if (mutation.type === 'addClass') {
var existing = value.split(' ');
var classes = mutation.value.split(' ');
classes.forEach(function (c) {
if (!existing.includes(c)) {
existing.push(c);
}
});
return existing.filter(Boolean).join(' ');
} else if (mutation.type === 'removeClass') {
var _existing = value.split(' ');
var setClassValue = function setClassValue(el, val) {
return val ? el.className = val : el.removeAttribute('class');
};
var _classes = mutation.value.split(' ');
var getClassValue = function getClassValue(el) {
return el.className;
};
return _existing.filter(function (c) {
return !_classes.includes(c);
}).join(' ');
} else if (mutation.type === 'setHTML') {
return mutation.value;
} else if (mutation.type === 'appendHTML') {
return value + mutation.value;
} else if (mutation.type === 'setAttribute') {
/* istanbul ignore next */
var match = setAttributeRegex.exec(mutation.value);
/* istanbul ignore next */
function getElementClassRecord(el) {
var elementRecord = getElementRecord(el);
return (match == null ? void 0 : match[2]) || '';
if (!elementRecord.classes) {
elementRecord.classes = newMutatedElementAttribute(el, 'class', getClassValue, setClassValue, ClassMutationRunner);
}
/* istanbul ignore next */
return elementRecord.classes;
}
return value;
function getElementAttributeRecord(el, attr) {
var elementRecord = getElementRecord(el);
if (!elementRecord.attributes[attr]) {
elementRecord.attributes[attr] = newMutatedElementAttribute(el, attr, function (el) {
return el.getAttribute(attr) || '';
}, function (el, val) {
return val ? el.setAttribute(attr, val) : el.removeAttribute(attr);
}, AttributeMutationRunner);
}
return elementRecord.attributes[attr];
}
function getCurrentValue(el, attr) {
function deleteElementAttributeRecord(el, attr) {
var element = elements.get(el);
/* istanbul ignore next */
if (!element) return;
if (attr === 'html') {
return el.innerHTML;
} else if (attr === 'className') {
return el.className;
var _element$html, _element$html$observe;
(_element$html = element.html) == null ? void 0 : (_element$html$observe = _element$html.observer) == null ? void 0 : _element$html$observe.disconnect();
delete element.html;
} else if (attr === 'class') {
var _element$classes, _element$classes$obse;
(_element$classes = element.classes) == null ? void 0 : (_element$classes$obse = _element$classes.observer) == null ? void 0 : _element$classes$obse.disconnect();
delete element.classes;
} else {
return el.getAttribute(attr) || '';
var _element$attributes, _element$attributes$a, _element$attributes$a2;
(_element$attributes = element.attributes) == null ? void 0 : (_element$attributes$a = _element$attributes[attr]) == null ? void 0 : (_element$attributes$a2 = _element$attributes$a.observer) == null ? void 0 : _element$attributes$a2.disconnect();
delete element.attributes[attr];
}
}
function setValue(el, attr, value) {
if (attr === 'html') {
el.innerHTML = value;
} else if (attr === 'className') {
if (value) {
el.className = value;
} else {
el.removeAttribute('class');
}
} else {
if (value) {
el.setAttribute(attr, value);
} else {
el.removeAttribute(attr);
}
var transformContainer;
function getTransformedHTML(html) {
if (!transformContainer) {
transformContainer = document.createElement('div');
}
transformContainer.innerHTML = html;
return transformContainer.innerHTML;
}
var raf = false;
function setAttributeValue(el, attr, m) {
if (!m.isDirty) return;
m.isDirty = false;
var val = m.virtualValue;
function setValues() {
raf = false;
elements.forEach(function (attrs, el) {
Object.keys(attrs).forEach(function (attr) {
var elAttr = attrs[attr];
if (!m.mutations.length) {
deleteElementAttributeRecord(el, attr);
}
if (elAttr.dirty) {
elAttr.dirty = false;
var value = elAttr.virtualValue; // No more mutations for the element, remove the observer
m.setValue(el, val);
}
if (!elAttr.mutations.length) {
deleteElementAttributeRecord(el, attr);
}
var raf = false;
setValue(el, attr, value);
}
});
function setValue(m, el) {
m.html && setAttributeValue(el, 'html', m.html);
m.classes && setAttributeValue(el, 'class', m.classes);
Object.keys(m.attributes).forEach(function (attr) {
setAttributeValue(el, attr, m.attributes[attr]);
});
}
function setValues() {
raf = false;
elements.forEach(setValue);
}
function queueDOMUpdates() {

@@ -166,78 +220,60 @@ if (!raf) {

function applyMutations(el, attr) {
var elAttr = getElementAttributeRecord(el, attr);
var val = elAttr.originalValue;
elAttr.mutations.forEach(function (id) {
var mutation = mutations[id];
/* istanbul ignore next */
function startMutating(mutation, el) {
mutation.elements.add(el);
if (!mutation) return;
val = applyMutation(mutation, val);
});
elAttr.virtualValue = val;
var currentVal = getCurrentValue(el, attr);
if (mutation.kind === 'html') {
var record = getElementHTMLRecord(el);
record.mutations.push(mutation);
record.runMutations(record);
} else if (mutation.kind === 'class') {
var _record = getElementClassRecord(el);
if (elAttr.virtualValue !== currentVal) {
elAttr.dirty = true;
queueDOMUpdates();
_record.mutations.push(mutation);
_record.runMutations(_record);
} else if (mutation.kind === 'attribute') {
var _record2 = getElementAttributeRecord(el, mutation.attribute);
_record2.mutations.push(mutation);
_record2.runMutations(_record2);
}
}
function getAttribute(type, value) {
if (['addClass', 'removeClass'].includes(type)) {
return 'className';
} else if (['appendHTML', 'setHTML'].includes(type)) {
return 'html';
} else if (type === 'setAttribute') {
var match = setAttributeRegex.exec(value);
function stopMutating(mutation, el) {
mutation.elements["delete"](el);
if (match != null && match[1]) {
var attr = match[1];
if (mutation.kind === 'html') {
var record = getElementHTMLRecord(el);
var index = record.mutations.indexOf(mutation);
if (attr === 'class' || attr === 'classname') {
return 'className';
}
return attr;
if (index !== -1) {
record.mutations.splice(index, 1);
}
}
return '';
}
record.runMutations(record);
} else if (mutation.kind === 'class') {
var _record3 = getElementClassRecord(el);
function startMutating(id, el) {
var mutation = mutations[id];
/* istanbul ignore next */
var _index = _record3.mutations.indexOf(mutation);
if (!mutation) return;
mutation.elements.add(el);
var attr = getAttribute(mutation.type, mutation.value);
var elAttr = getElementAttributeRecord(el, attr);
elAttr.mutations.push(id);
applyMutations(el, attr);
}
if (_index !== -1) {
_record3.mutations.splice(_index, 1);
}
function stopMutating(id, el) {
var mutation = mutations[id];
/* istanbul ignore next */
_record3.runMutations(_record3);
} else if (mutation.kind === 'attribute') {
var _record4 = getElementAttributeRecord(el, mutation.attribute);
if (!mutation) return;
mutation.elements["delete"](el);
var attr = getAttribute(mutation.type, mutation.value);
var elAttr = getElementAttributeRecord(el, attr);
var index = elAttr.mutations.indexOf(id);
/* istanbul ignore next */
var _index2 = _record4.mutations.indexOf(mutation);
if (index !== -1) {
elAttr.mutations.splice(index, 1);
if (_index2 !== -1) {
_record4.mutations.splice(_index2, 1);
}
_record4.runMutations(_record4);
}
applyMutations(el, attr);
}
function refreshElementsSet(id) {
var mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
function refreshElementsSet(mutation) {
var existingEls = new Set(mutation.elements);

@@ -250,3 +286,3 @@ var newElements = new Set();

if (!existingEls.has(el)) {
startMutating(id, el);
startMutating(mutation, el);
}

@@ -256,3 +292,3 @@ });

if (!newElements.has(el)) {
stopMutating(id, el);
stopMutating(mutation, el);
}

@@ -262,36 +298,13 @@ });

function newMutation(selector, type, value) {
// Fix invalid HTML values
if (type === 'appendHTML' || type === 'setHTML') {
value = getTransformedHTML(value);
}
var id = '' + mutationIdCounter++;
mutations[id] = {
selector: selector,
type: type,
value: value,
elements: new Set()
};
refreshElementsSet(id);
return id;
}
function revertMutation(id) {
var mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
function revertMutation(mutation) {
var els = new Set(mutation.elements);
els.forEach(function (el) {
stopMutating(id, el);
stopMutating(mutation, el);
});
mutations[id].elements.clear();
delete mutations[id];
mutation.elements.clear();
mutations["delete"](mutation);
}
function refreshAllElementSets() {
Object.keys(mutations).forEach(function (key) {
refreshElementsSet(key);
});
mutations.forEach(refreshElementsSet);
} // Observer for elements that don't exist in the DOM yet

@@ -323,26 +336,75 @@

connectGlobalObserver();
function mutate(selector, type, value) {
function newMutationRecord(m) {
/* istanbul ignore next */
if (typeof document === 'undefined') {
// Not in a browser
return function () {// do nothing
};
} // Invalid mutation
return nullController;
}
mutations.add(m);
refreshElementsSet(m);
return {
revert: function revert() {
revertMutation(m);
}
};
}
var attr = getAttribute(type, value);
function html(selector, mutate) {
return newMutationRecord({
kind: 'html',
elements: new Set(),
mutate: mutate,
selector: selector
});
}
if (!attr) {
return function () {// do nothing
};
function classes(selector, mutate) {
return newMutationRecord({
kind: 'class',
elements: new Set(),
mutate: mutate,
selector: selector
});
}
function attribute(selector, attribute, _mutate) {
if (!validAttributeName.test(attribute)) {
return nullController;
}
var id = newMutation(selector, type, value);
return function () {
revertMutation(id);
};
if (attribute === 'class' || attribute === 'className') {
return newMutationRecord({
kind: 'class',
elements: new Set(),
mutate: function mutate(classes) {
var val = _mutate(Array.from(classes).join(' '));
classes.clear();
val.split(/\s+/g).filter(Boolean).forEach(function (c) {
classes.add(c);
});
},
selector: selector
});
}
return newMutationRecord({
kind: 'attribute',
attribute: attribute,
elements: new Set(),
mutate: _mutate,
selector: selector
});
}
export default mutate;
var index = {
html: html,
classes: classes,
attribute: attribute
};
export default index;
export { connectGlobalObserver, disconnectGlobalObserver };
//# sourceMappingURL=dom-mutator.esm.js.map

@@ -1,4 +0,11 @@

export declare type MutationType = 'addClass' | 'removeClass' | 'appendHTML' | 'setHTML' | 'setAttribute';
export declare function disconnectGlobalObserver(): void;
export declare function connectGlobalObserver(): void;
export default function mutate(selector: string, type: MutationType, value: string): () => void;
declare function html(selector: string, mutate: (value: string) => string): MutationController;
declare function classes(selector: string, mutate: (classes: Set<string>) => void): MutationController;
declare function attribute(selector: string, attribute: string, mutate: (value: string) => string): MutationController;
declare const _default: {
html: typeof html;
classes: typeof classes;
attribute: typeof attribute;
};
export default _default;
{
"version": "0.2.4",
"version": "0.3.0",
"license": "MIT",

@@ -4,0 +4,0 @@ "main": "dist/index.js",

# DOM Mutator
![Build Status](https://github.com/growthbook/dom-mutator/workflows/CI/badge.svg)
Apply persistent DOM mutations on top of anything (static HTML, React, Vue, etc.)
It's like using jQuery `element.innerHTML = "My New Title"`, but it persists your change even if something external resets the HTML. Plus, it will automatically apply to any new matching elements added to the page later.
```ts
import mutate from "dom-mutator";
mutate.html("h1", html => html.toUpperCase());
mutate.classes("div.greeting", classes => classes.add("new-class"));
mutate.attr(".get-started", "title", (oldVal) => "This is my new title attribute");
```
Features:

@@ -13,25 +19,29 @@

* Super fast and light-weight (1Kb gzipped)
* If an element doesn't exist yet, wait for it to appear
* If an element is updated externally (e.g. a React render), re-apply the mutation immediately
* Ability to remove a mutation at any time and go back to the original value
* Mutations will apply to elements that match the selector (even ones that don't exist yet)
* Mutations persist even if the underlying element is updated externally (e.g. by a React render)
* Easily remove a mutation at any time
`yarn add dom-mutator` OR `npm install --save dom-mutator`.
![Build Status](https://github.com/growthbook/dom-mutator/workflows/CI/badge.svg)
## Basic Usage
innerHTML example:
```ts
import mutate from "dom-mutator";
// mutate(css selector, mutation type, value)
const stop = mutate("#greeting", "setHTML", "hello");
// Mutate the innerHTML of an element
const stop = mutate.html("#greeting", html => html + ' world');
// works even if the selector doesn't exist yet
document.body.innerHTML += "<div id='greeting'></div>";
document.body.innerHTML += "<div id='greeting'>hello</div>";
//**** div innerHTML = "hello" at this point!
//**** div innerHTML = "hello world" at this point!
// external changes are ignored and the mutation persists
document.getElementById('greeting').innerHTML = 'something new';
// mutation persists even if there is an external change
document.getElementById('greeting').innerHTML = 'hola';
//**** div innerHTML = "hello" still!
//**** div innerHTML = "hola world"

@@ -41,3 +51,3 @@ // Stop mutating the element

//**** div innerHTML = "something new" (the last external value)
//**** div innerHTML = "hola" (the last externally set value)
```

@@ -47,10 +57,17 @@

- addClass
- removeClass
- setHTML
- appendHTML
- setAttribute
The `mutate` object has a few different methods you can call:
For `setAttribute`, the "value" is in the format `{attribute}="{value}"` (e.g. `href="/about"`).
- html
- classList
- attr
```ts
mutate.html("h1", html => html.toUpperCase());
mutate.classList("div.greeting", classes => classes.add("new-class"));
mutate.attr(".get-started", "title", oldVal => "This is my new title attribute");
```
## How it Works

@@ -66,5 +83,5 @@

While the library is waiting for elements to appear, it runs `document.querySelectorAll` every time a batch of elements is added to the DOM.
While the library is waiting for elements to appear, it runs `document.querySelectorAll` every time a batch of elements is added or removed from the DOM.
This is fast enough for most cases, but if you want more control, you can pause and resume the global MutationObserver.
This is performant enough in most cases, but if you want more control, you can pause and resume the global MutationObserver on demand.

@@ -95,2 +112,2 @@ One example use case is if you are making a ton of DOM changes that you know have nothing to do with the elements you are watching. You would pause right before making the changes and resume after.

`npm run lint --fix` or `yarn lint --fix` to lint your code and autofix problems when possible.
`npm run lint --fix` or `yarn lint --fix` to lint your code and autofix problems when possible.

@@ -1,34 +0,9 @@

export type MutationType =
| 'addClass'
| 'removeClass'
| 'appendHTML'
| 'setHTML'
| 'setAttribute';
type MutationRecord = {
selector: string;
type: MutationType;
value: string;
elements: Set<Element>;
const validAttributeName = /^[a-zA-Z:_][a-zA-Z0-9:_.-]*$/;
const nullController: MutationController = {
revert: () => {},
};
type Mutations = { [key: string]: MutationRecord };
type ElementAttributeRecord = {
originalValue: string;
virtualValue: string;
dirty?: boolean;
observer: MutationObserver;
mutations: string[];
};
type ElementAttributes = {
[key: string]: ElementAttributeRecord;
};
type Elements = Map<Element, ElementAttributes>;
const mutations: Mutations = {};
const elements: Elements = new Map();
let mutationIdCounter = 1;
const elements: Map<Element, ElementRecord> = new Map();
const mutations: Set<AnyMutationRecord> = new Set();
// attr="value" format
const setAttributeRegex = /^([a-zA-Z:_][a-zA-Z0-9:_.-]*)\s*=\s*"([^"]*)"/;
function getObserverInit(attr: string): MutationObserverInit {

@@ -51,31 +26,153 @@ if (attr === 'html') {

function getElementAttributeRecord(
function getElementRecord(el: Element): ElementRecord {
let ret = elements.get(el);
if (!ret) {
ret = { el, attributes: {} };
elements.set(el, ret);
}
return ret;
}
function newMutatedElementAttribute<T>(
el: Element,
attr: string
): ElementAttributeRecord {
let element = elements.get(el);
if (!element) {
element = {};
elements.set(el, element);
attr: string,
getCurrentValue: (el: Element) => string,
setValue: (el: Element, val: string) => void,
runMutations: (record: ElementAttributeRecord<T>) => void
): ElementAttributeRecord<T> {
const currentValue = getCurrentValue(el);
const ret: ElementAttributeRecord<T> = {
isDirty: false,
originalValue: currentValue,
virtualValue: currentValue,
mutations: [],
el,
observer: new MutationObserver(() => {
const currentValue = getCurrentValue(el);
if (currentValue === ret.virtualValue) return;
ret.originalValue = currentValue;
runMutations(ret);
}),
runMutations,
setValue,
getCurrentValue,
};
ret.observer.observe(el, getObserverInit(attr));
return ret;
}
function stringRunner(record: {
originalValue: string;
mutations: { mutate: (v: string) => string }[];
}) {
let val = record.originalValue;
record.mutations.forEach(m => (val = m.mutate(val)));
return val;
}
function setRunner(
val: Set<string>,
record: {
mutations: { mutate: (v: Set<string>) => void }[];
}
) {
record.mutations.forEach(m => m.mutate(val));
return val;
}
function queueIfNeeded(
val: string,
record: {
el: Element;
getCurrentValue: (el: Element) => string;
virtualValue: string;
isDirty: boolean;
}
) {
const currentVal = record.getCurrentValue(record.el);
record.virtualValue = val;
if (val !== currentVal) {
record.isDirty = true;
queueDOMUpdates();
}
}
if (!element[attr]) {
const currentValue = getCurrentValue(el, attr);
const elAttr: ElementAttributeRecord = {
originalValue: currentValue,
virtualValue: currentValue,
observer: new MutationObserver(() => {
const currentValue = getCurrentValue(el, attr);
if (currentValue === elAttr.virtualValue) return;
elAttr.originalValue = currentValue;
applyMutations(el, attr);
}),
mutations: [],
};
element[attr] = elAttr;
elAttr.observer.observe(el, getObserverInit(attr));
function HTMLMutationRunner(
record: ElementAttributeRecord<HTMLMutationRecord>
) {
queueIfNeeded(getTransformedHTML(stringRunner(record)), record);
}
function ClassMutationRunner(
record: ElementAttributeRecord<ClassMutationRecord>
) {
const val = setRunner(
new Set(record.originalValue.split(/\s+/).filter(Boolean)),
record
);
queueIfNeeded(
Array.from(val)
.filter(Boolean)
.join(' '),
record
);
}
function AttributeMutationRunner(
record: ElementAttributeRecord<AttributeMutationRecord>
) {
queueIfNeeded(stringRunner(record), record);
}
const getHTMLValue = (el: Element) => el.innerHTML;
const setHTMLValue = (el: Element, value: string) => (el.innerHTML = value);
function getElementHTMLRecord(
el: Element
): ElementAttributeRecord<HTMLMutationRecord> {
const elementRecord = getElementRecord(el);
if (!elementRecord.html) {
elementRecord.html = newMutatedElementAttribute(
el,
'html',
getHTMLValue,
setHTMLValue,
HTMLMutationRunner
);
}
return elementRecord.html;
}
return element[attr];
const setClassValue = (el: Element, val: string) =>
val ? (el.className = val) : el.removeAttribute('class');
const getClassValue = (el: Element) => el.className;
function getElementClassRecord(
el: Element
): ElementAttributeRecord<ClassMutationRecord> {
const elementRecord = getElementRecord(el);
if (!elementRecord.classes) {
elementRecord.classes = newMutatedElementAttribute(
el,
'class',
getClassValue,
setClassValue,
ClassMutationRunner
);
}
return elementRecord.classes;
}
function getElementAttributeRecord(
el: Element,
attr: string
): ElementAttributeRecord<AttributeMutationRecord> {
const elementRecord = getElementRecord(el);
if (!elementRecord.attributes[attr]) {
elementRecord.attributes[attr] = newMutatedElementAttribute(
el,
attr,
el => el.getAttribute(attr) || '',
(el, val) =>
val ? el.setAttribute(attr, val) : el.removeAttribute(attr),
AttributeMutationRunner
);
}
return elementRecord.attributes[attr];
}
function deleteElementAttributeRecord(el: Element, attr: string) {

@@ -85,4 +182,12 @@ const element = elements.get(el);

if (!element) return;
element[attr] && element[attr].observer.disconnect();
delete element[attr];
if (attr === 'html') {
element.html?.observer?.disconnect();
delete element.html;
} else if (attr === 'class') {
element.classes?.observer?.disconnect();
delete element.classes;
} else {
element.attributes?.[attr]?.observer?.disconnect();
delete element.attributes[attr];
}
}

@@ -99,75 +204,27 @@

function applyMutation(mutation: MutationRecord, value: string): string {
if (mutation.type === 'addClass') {
const existing = value.split(' ');
const classes = mutation.value.split(' ');
classes.forEach(c => {
if (!existing.includes(c)) {
existing.push(c);
}
});
return existing.filter(Boolean).join(' ');
} else if (mutation.type === 'removeClass') {
const existing = value.split(' ');
const classes = mutation.value.split(' ');
return existing.filter(c => !classes.includes(c)).join(' ');
} else if (mutation.type === 'setHTML') {
return mutation.value;
} else if (mutation.type === 'appendHTML') {
return value + mutation.value;
} else if (mutation.type === 'setAttribute') {
/* istanbul ignore next */
const match = setAttributeRegex.exec(mutation.value);
/* istanbul ignore next */
return match?.[2] || '';
function setAttributeValue<T>(
el: Element,
attr: string,
m: ElementAttributeRecord<T>
) {
if (!m.isDirty) return;
m.isDirty = false;
const val = m.virtualValue;
if (!m.mutations.length) {
deleteElementAttributeRecord(el, attr);
}
/* istanbul ignore next */
return value;
m.setValue(el, val);
}
function getCurrentValue(el: Element, attr: string) {
if (attr === 'html') {
return el.innerHTML;
} else if (attr === 'className') {
return el.className;
} else {
return el.getAttribute(attr) || '';
}
let raf = false;
function setValue(m: ElementRecord, el: Element) {
m.html && setAttributeValue(el, 'html', m.html);
m.classes && setAttributeValue(el, 'class', m.classes);
Object.keys(m.attributes).forEach(attr => {
setAttributeValue(el, attr, m.attributes[attr]);
});
}
function setValue(el: Element, attr: string, value: string) {
if (attr === 'html') {
el.innerHTML = value;
} else if (attr === 'className') {
if (value) {
el.className = value;
} else {
el.removeAttribute('class');
}
} else {
if (value) {
el.setAttribute(attr, value);
} else {
el.removeAttribute(attr);
}
}
}
let raf = false;
function setValues() {
raf = false;
elements.forEach((attrs, el) => {
Object.keys(attrs).forEach(attr => {
const elAttr = attrs[attr];
if (elAttr.dirty) {
elAttr.dirty = false;
const value = elAttr.virtualValue;
// No more mutations for the element, remove the observer
if (!elAttr.mutations.length) {
deleteElementAttributeRecord(el, attr);
}
setValue(el, attr, value);
}
});
});
elements.forEach(setValue);
}

@@ -181,70 +238,48 @@ function queueDOMUpdates() {

function applyMutations(el: Element, attr: string) {
const elAttr = getElementAttributeRecord(el, attr);
let val = elAttr.originalValue;
elAttr.mutations.forEach(id => {
const mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
val = applyMutation(mutation, val);
});
elAttr.virtualValue = val;
const currentVal = getCurrentValue(el, attr);
if (elAttr.virtualValue !== currentVal) {
elAttr.dirty = true;
queueDOMUpdates();
}
}
function startMutating(mutation: AnyMutationRecord, el: Element) {
mutation.elements.add(el);
function getAttribute(type: MutationType, value: string): string {
if (['addClass', 'removeClass'].includes(type)) {
return 'className';
} else if (['appendHTML', 'setHTML'].includes(type)) {
return 'html';
} else if (type === 'setAttribute') {
const match = setAttributeRegex.exec(value);
if (match?.[1]) {
const attr = match[1];
if (attr === 'class' || attr === 'classname') {
return 'className';
}
return attr;
}
if (mutation.kind === 'html') {
const record = getElementHTMLRecord(el);
record.mutations.push(mutation);
record.runMutations(record);
} else if (mutation.kind === 'class') {
const record = getElementClassRecord(el);
record.mutations.push(mutation);
record.runMutations(record);
} else if (mutation.kind === 'attribute') {
const record = getElementAttributeRecord(el, mutation.attribute);
record.mutations.push(mutation);
record.runMutations(record);
}
return '';
}
function startMutating(id: string, el: Element) {
const mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
function stopMutating(mutation: AnyMutationRecord, el: Element) {
mutation.elements.delete(el);
mutation.elements.add(el);
const attr = getAttribute(mutation.type, mutation.value);
const elAttr = getElementAttributeRecord(el, attr);
elAttr.mutations.push(id);
applyMutations(el, attr);
}
function stopMutating(id: string, el: Element) {
const mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
mutation.elements.delete(el);
const attr = getAttribute(mutation.type, mutation.value);
const elAttr = getElementAttributeRecord(el, attr);
const index = elAttr.mutations.indexOf(id);
/* istanbul ignore next */
if (index !== -1) {
elAttr.mutations.splice(index, 1);
if (mutation.kind === 'html') {
const record = getElementHTMLRecord(el);
const index = record.mutations.indexOf(mutation);
if (index !== -1) {
record.mutations.splice(index, 1);
}
record.runMutations(record);
} else if (mutation.kind === 'class') {
const record = getElementClassRecord(el);
const index = record.mutations.indexOf(mutation);
if (index !== -1) {
record.mutations.splice(index, 1);
}
record.runMutations(record);
} else if (mutation.kind === 'attribute') {
const record = getElementAttributeRecord(el, mutation.attribute);
const index = record.mutations.indexOf(mutation);
if (index !== -1) {
record.mutations.splice(index, 1);
}
record.runMutations(record);
}
applyMutations(el, attr);
}
function refreshElementsSet(id: string) {
const mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
function refreshElementsSet(mutation: AnyMutationRecord) {
const existingEls = new Set(mutation.elements);

@@ -257,3 +292,3 @@

if (!existingEls.has(el)) {
startMutating(id, el);
startMutating(mutation, el);
}

@@ -264,3 +299,3 @@ });

if (!newElements.has(el)) {
stopMutating(id, el);
stopMutating(mutation, el);
}

@@ -270,40 +305,13 @@ });

function newMutation(
selector: string,
type: MutationType,
value: string
): string {
// Fix invalid HTML values
if (type === 'appendHTML' || type === 'setHTML') {
value = getTransformedHTML(value);
}
const id = '' + mutationIdCounter++;
mutations[id] = {
selector,
type,
value,
elements: new Set(),
};
refreshElementsSet(id);
return id;
}
function revertMutation(id: string) {
const mutation = mutations[id];
/* istanbul ignore next */
if (!mutation) return;
function revertMutation(mutation: AnyMutationRecord) {
const els = new Set(mutation.elements);
els.forEach(el => {
stopMutating(id, el);
stopMutating(mutation, el);
});
mutations[id].elements.clear();
delete mutations[id];
mutation.elements.clear();
mutations.delete(mutation);
}
function refreshAllElementSets() {
Object.keys(mutations).forEach(key => {
refreshElementsSet(key);
});
mutations.forEach(refreshElementsSet);
}

@@ -336,28 +344,75 @@

export default function mutate(
selector: string,
type: MutationType,
value: string
): () => void {
function newMutationRecord(m: AnyMutationRecord): MutationController {
/* istanbul ignore next */
if (typeof document === 'undefined') {
// Not in a browser
return () => {
// do nothing
};
return nullController;
}
// Invalid mutation
const attr = getAttribute(type, value);
if (!attr) {
return () => {
// do nothing
};
mutations.add(m);
refreshElementsSet(m);
return {
revert: () => {
revertMutation(m);
},
};
}
function html(selector: string, mutate: (value: string) => string) {
return newMutationRecord({
kind: 'html',
elements: new Set(),
mutate,
selector,
});
}
function classes(selector: string, mutate: (classes: Set<string>) => void) {
return newMutationRecord({
kind: 'class',
elements: new Set(),
mutate,
selector,
});
}
function attribute(
selector: string,
attribute: string,
mutate: (value: string) => string
) {
if (!validAttributeName.test(attribute)) {
return nullController;
}
if (attribute === 'class' || attribute === 'className') {
return newMutationRecord({
kind: 'class',
elements: new Set(),
mutate: classes => {
const val = mutate(Array.from(classes).join(' '));
classes.clear();
val
.split(/\s+/g)
.filter(Boolean)
.forEach(c => {
classes.add(c);
});
},
selector,
});
}
const id = newMutation(selector, type, value);
return newMutationRecord({
kind: 'attribute',
attribute,
elements: new Set(),
mutate,
selector,
});
}
return () => {
revertMutation(id);
};
}
export default {
html,
classes,
attribute,
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc