@miaskiewicz/turbo-dom 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miaskiewicz/turbo-dom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "Faster, more spec-correct DOM for test runners — native html5ever (Rust/WASM) parser + lazy copy-on-write DOM. A drop-in-style alternative to jsdom/happy-dom for vitest & jest.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.js",
|
package/src/runtime/dom.mjs
CHANGED
|
@@ -158,7 +158,7 @@ export class Node extends EventTarget {
|
|
|
158
158
|
else if (kids[i].nodeType === ELEMENT_NODE) kids[i].normalize();
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
-
replaceChildren(...nodes) { this.__kids = []; for (const n of nodes) this.appendChild(typeof n === 'string' ? this.ownerDocument.createTextNode(n) : n); }
|
|
161
|
+
replaceChildren(...nodes) { this.__kids = []; this.__touch(); for (const n of nodes) this.appendChild(typeof n === 'string' ? this.ownerDocument.createTextNode(n) : n); }
|
|
162
162
|
// bitmask: 1 DISCONNECTED, 2 PRECEDING, 4 FOLLOWING, 8 CONTAINS, 16 CONTAINED_BY
|
|
163
163
|
compareDocumentPosition(other) {
|
|
164
164
|
if (other === this) return 0;
|
|
@@ -189,9 +189,15 @@ export class Node extends EventTarget {
|
|
|
189
189
|
}
|
|
190
190
|
set textContent(value) {
|
|
191
191
|
this.__kids = [];
|
|
192
|
+
this.__touch();
|
|
192
193
|
if (value !== '') this.appendChild(this.ownerDocument.createTextNode(String(value)));
|
|
193
194
|
}
|
|
194
195
|
|
|
196
|
+
// bump the document version so getElementsBy* caches invalidate after direct
|
|
197
|
+
// __kids reassignments (innerHTML/textContent/replaceChildren) that bypass
|
|
198
|
+
// insertBefore/removeChild.
|
|
199
|
+
__touch() { const d = this.ownerDocument; if (d) d.__version = (d.__version || 0) + 1; }
|
|
200
|
+
|
|
195
201
|
// window/document path for event propagation past the document
|
|
196
202
|
get __owner() { return null; }
|
|
197
203
|
}
|
|
@@ -505,7 +511,7 @@ export class Element extends Node {
|
|
|
505
511
|
before(...nodes) { const p = this.parentNode; if (!p) return; for (const n of nodes) p.insertBefore(toNode(this.ownerDocument, n), this); }
|
|
506
512
|
after(...nodes) { const p = this.parentNode; if (!p) return; const ref = this.nextSibling; for (const n of nodes) p.insertBefore(toNode(this.ownerDocument, n), ref); }
|
|
507
513
|
replaceWith(...nodes) { const p = this.parentNode; if (!p) return; const ref = this.nextSibling; this.remove(); for (const n of nodes) p.insertBefore(toNode(this.ownerDocument, n), ref); }
|
|
508
|
-
replaceChildren(...nodes) { this.__kids = []; for (const n of nodes) this.appendChild(toNode(this.ownerDocument, n)); }
|
|
514
|
+
replaceChildren(...nodes) { this.__kids = []; this.__touch(); for (const n of nodes) this.appendChild(toNode(this.ownerDocument, n)); }
|
|
509
515
|
|
|
510
516
|
// ---- queries ----
|
|
511
517
|
matches(sel) { return matchesSelector(this, sel); }
|
|
@@ -520,6 +526,7 @@ export class Element extends Node {
|
|
|
520
526
|
set innerHTML(html) {
|
|
521
527
|
const frag = native.parseFragment(String(html), this.__ns ? `${this.__ns} ${this.localName}` : this.localName);
|
|
522
528
|
this.__kids = [];
|
|
529
|
+
this.__touch();
|
|
523
530
|
for (const rawChild of frag.children) {
|
|
524
531
|
if (rawChild.nodeType === DOCUMENT_FRAGMENT_NODE && rawChild.name === 'content') continue;
|
|
525
532
|
const child = this.ownerDocument.__inflateNested(rawChild);
|
|
@@ -956,7 +963,11 @@ function isDescendant(node, ancestor) {
|
|
|
956
963
|
}
|
|
957
964
|
function notifyMutation(target, record) {
|
|
958
965
|
const doc = target.ownerDocument;
|
|
959
|
-
if (!doc
|
|
966
|
+
if (!doc) return;
|
|
967
|
+
// bump the DOM version on every structural/attribute mutation — invalidates
|
|
968
|
+
// the document's getElementsBy* caches. Unconditional (independent of observers).
|
|
969
|
+
doc.__version = (doc.__version || 0) + 1;
|
|
970
|
+
if (!doc.__mo || doc.__mo.length === 0) return;
|
|
960
971
|
for (const reg of doc.__mo) {
|
|
961
972
|
const { obs, target: obsTarget, options } = reg;
|
|
962
973
|
const onTarget = record.target === obsTarget;
|
|
@@ -1109,6 +1120,9 @@ export class Document extends Node {
|
|
|
1109
1120
|
this.__active = null;
|
|
1110
1121
|
this.__mo = []; // drop observers
|
|
1111
1122
|
this.__moPending = null;
|
|
1123
|
+
this.__version = (this.__version || 0) + 1; // invalidate getElementsBy* caches
|
|
1124
|
+
this.__tagCache = null;
|
|
1125
|
+
this.__classCache = null;
|
|
1112
1126
|
}
|
|
1113
1127
|
|
|
1114
1128
|
get documentElement() { return this.__children().find((n) => n.nodeType === ELEMENT_NODE && n.localName === 'html') ?? null; }
|
|
@@ -1191,8 +1205,28 @@ export class Document extends Node {
|
|
|
1191
1205
|
}
|
|
1192
1206
|
querySelector(sel) { return qsel(this, sel); }
|
|
1193
1207
|
querySelectorAll(sel) { return qselAll(this, sel); }
|
|
1194
|
-
|
|
1195
|
-
|
|
1208
|
+
// version-keyed cache: getElementsBy* is called repeatedly within a single
|
|
1209
|
+
// query (e.g. RTL getByLabelText calls element.labels per element, each doing
|
|
1210
|
+
// document.getElementsByTagName('label')). Without caching that's O(n²) tree
|
|
1211
|
+
// walks. The cache invalidates whenever the DOM version bumps (any mutation).
|
|
1212
|
+
__byTag(t) {
|
|
1213
|
+
const v = this.__version || 0;
|
|
1214
|
+
const c = this.__tagCache && this.__tagCache.get(t);
|
|
1215
|
+
if (c && c.v === v) return c.arr;
|
|
1216
|
+
const arr = collectByTag(this, t);
|
|
1217
|
+
(this.__tagCache || (this.__tagCache = new Map())).set(t, { v, arr });
|
|
1218
|
+
return arr;
|
|
1219
|
+
}
|
|
1220
|
+
__byClass(key, classes) {
|
|
1221
|
+
const v = this.__version || 0;
|
|
1222
|
+
const c = this.__classCache && this.__classCache.get(key);
|
|
1223
|
+
if (c && c.v === v) return c.arr;
|
|
1224
|
+
const arr = collectByClass(this, classes);
|
|
1225
|
+
(this.__classCache || (this.__classCache = new Map())).set(key, { v, arr });
|
|
1226
|
+
return arr;
|
|
1227
|
+
}
|
|
1228
|
+
getElementsByTagName(tag) { const self = this; const t = tag.toLowerCase(); return liveHTMLCollection(() => self.__byTag(t)); }
|
|
1229
|
+
getElementsByClassName(cls) { const self = this; const classes = cls.split(/\s+/).filter(Boolean); return liveHTMLCollection(() => self.__byClass(cls, classes)); }
|
|
1196
1230
|
contains(node) { return Node.prototype.contains.call(this, node); }
|
|
1197
1231
|
|
|
1198
1232
|
// cookie jar: store name=value, strip attributes (path/Secure/SameSite/…),
|
|
Binary file
|