@miaskiewicz/turbo-dom 0.1.8 → 0.1.9
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.9",
|
|
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
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -17,9 +17,10 @@ import { installGlobals } from './install.mjs';
|
|
|
17
17
|
|
|
18
18
|
const environment = {
|
|
19
19
|
name: 'turbo-dom',
|
|
20
|
-
// vitest 3/4 read `viteEnvironment
|
|
20
|
+
// vitest 3/4 read `viteEnvironment` (this). vitest <3 used `transformMode: 'web'`
|
|
21
|
+
// — omitted here to avoid the v4 deprecation warning; on vitest <3 set
|
|
22
|
+
// `test.transformMode` (or add transformMode:'web' to this object) if needed.
|
|
21
23
|
viteEnvironment: 'client',
|
|
22
|
-
transformMode: 'web',
|
|
23
24
|
|
|
24
25
|
setup(global, options) {
|
|
25
26
|
const opts = (options && options.turboDom) || {};
|
package/src/runtime/dom.mjs
CHANGED
|
@@ -573,14 +573,25 @@ export class Element extends Node {
|
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
focus() {
|
|
576
|
-
this.ownerDocument
|
|
577
|
-
|
|
578
|
-
|
|
576
|
+
const doc = this.ownerDocument;
|
|
577
|
+
const prev = doc.__active;
|
|
578
|
+
if (prev === this) return;
|
|
579
|
+
// moving focus blurs the previously-focused element first
|
|
580
|
+
if (prev && prev !== doc.body && typeof prev.dispatchEvent === 'function') {
|
|
581
|
+
doc.__active = null;
|
|
582
|
+
prev.dispatchEvent(new FocusEvent('blur', { relatedTarget: this }));
|
|
583
|
+
prev.dispatchEvent(new FocusEvent('focusout', { bubbles: true, relatedTarget: this }));
|
|
584
|
+
}
|
|
585
|
+
doc.__setActive(this);
|
|
586
|
+
this.dispatchEvent(new FocusEvent('focus', { relatedTarget: prev || null }));
|
|
587
|
+
this.dispatchEvent(new FocusEvent('focusin', { bubbles: true, relatedTarget: prev || null }));
|
|
579
588
|
}
|
|
580
589
|
blur() {
|
|
581
|
-
this.ownerDocument
|
|
582
|
-
|
|
583
|
-
|
|
590
|
+
const doc = this.ownerDocument;
|
|
591
|
+
if (doc.__active !== this) return;
|
|
592
|
+
doc.__setActive(doc.body);
|
|
593
|
+
this.dispatchEvent(new FocusEvent('blur'));
|
|
594
|
+
this.dispatchEvent(new FocusEvent('focusout', { bubbles: true }));
|
|
584
595
|
}
|
|
585
596
|
getBoundingClientRect() { return zeroRect(); }
|
|
586
597
|
getClientRects() { return []; }
|
package/src/runtime/window.mjs
CHANGED
|
@@ -31,6 +31,32 @@ class ClipboardEvent extends Event {
|
|
|
31
31
|
constructor(type, init = {}) { super(type, init); this.clipboardData = init.clipboardData ?? new DataTransfer(); }
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
// FormData that preserves File/Blob identity. Node's global FormData clones
|
|
35
|
+
// entries into anonymous Blobs (losing the File reference + filename); tests that
|
|
36
|
+
// assert `fd.get('file') === file` need the original kept.
|
|
37
|
+
class TurboFormData {
|
|
38
|
+
constructor() { this.__entries = []; }
|
|
39
|
+
append(name, value, filename) { this.__entries.push([String(name), this.__wrap(value, filename)]); }
|
|
40
|
+
set(name, value, filename) { this.delete(name); this.append(name, value, filename); }
|
|
41
|
+
__wrap(value, filename) {
|
|
42
|
+
// string values coerce; File/Blob are kept by reference (optionally re-named)
|
|
43
|
+
if (value == null || typeof value !== 'object') return String(value);
|
|
44
|
+
if (filename !== undefined && globalThis.Blob && value instanceof globalThis.Blob && value.name !== filename) {
|
|
45
|
+
const F = makeFile(); return new F([value], String(filename), { type: value.type });
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
get(name) { const e = this.__entries.find((x) => x[0] === String(name)); return e ? e[1] : null; }
|
|
50
|
+
getAll(name) { return this.__entries.filter((x) => x[0] === String(name)).map((x) => x[1]); }
|
|
51
|
+
has(name) { return this.__entries.some((x) => x[0] === String(name)); }
|
|
52
|
+
delete(name) { this.__entries = this.__entries.filter((x) => x[0] !== String(name)); }
|
|
53
|
+
forEach(cb, thisArg) { for (const [k, v] of this.__entries) cb.call(thisArg, v, k, this); }
|
|
54
|
+
*entries() { for (const e of this.__entries) yield [e[0], e[1]]; }
|
|
55
|
+
*keys() { for (const e of this.__entries) yield e[0]; }
|
|
56
|
+
*values() { for (const e of this.__entries) yield e[1]; }
|
|
57
|
+
[Symbol.iterator]() { return this.entries(); }
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
// Capture host functions at module load — BEFORE any installGlobals() can shadow
|
|
35
61
|
// the bare names on globalThis (which would make these delegates call themselves).
|
|
36
62
|
const hostSetTimeout = globalThis.setTimeout;
|
|
@@ -78,7 +104,7 @@ export function createWindow(document, { url = 'http://localhost/' } = {}) {
|
|
|
78
104
|
// web platform globals Node already provides
|
|
79
105
|
fetch: globalThis.fetch ? (...a) => globalThis.fetch(...a) : undefined,
|
|
80
106
|
Headers: globalThis.Headers, Request: globalThis.Request, Response: globalThis.Response,
|
|
81
|
-
FormData:
|
|
107
|
+
FormData: TurboFormData, ReadableStream: globalThis.ReadableStream,
|
|
82
108
|
crypto: globalThis.crypto, Crypto: globalThis.Crypto, SubtleCrypto: globalThis.SubtleCrypto,
|
|
83
109
|
btoa: (s) => Buffer.from(String(s), 'binary').toString('base64'),
|
|
84
110
|
atob: (s) => Buffer.from(String(s), 'base64').toString('binary'),
|
|
Binary file
|