@derivesome/tree 0.1.1

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.
Files changed (113) hide show
  1. package/.package.json.~undo-tree~ +4 -0
  2. package/.tsconfig.json.~undo-tree~ +4 -0
  3. package/dist/cjs/brands.d.ts +3 -0
  4. package/dist/cjs/brands.d.ts.map +1 -0
  5. package/dist/cjs/brands.js +5 -0
  6. package/dist/cjs/brands.js.map +1 -0
  7. package/dist/cjs/context.d.ts +28 -0
  8. package/dist/cjs/context.d.ts.map +1 -0
  9. package/dist/cjs/context.js +48 -0
  10. package/dist/cjs/context.js.map +1 -0
  11. package/dist/cjs/index.d.ts +6 -0
  12. package/dist/cjs/index.d.ts.map +1 -0
  13. package/dist/cjs/index.js +22 -0
  14. package/dist/cjs/index.js.map +1 -0
  15. package/dist/cjs/mount.d.ts +6 -0
  16. package/dist/cjs/mount.d.ts.map +1 -0
  17. package/dist/cjs/mount.js +209 -0
  18. package/dist/cjs/mount.js.map +1 -0
  19. package/dist/cjs/props.d.ts +12 -0
  20. package/dist/cjs/props.d.ts.map +1 -0
  21. package/dist/cjs/props.js +80 -0
  22. package/dist/cjs/props.js.map +1 -0
  23. package/dist/cjs/renderer.d.ts +29 -0
  24. package/dist/cjs/renderer.d.ts.map +1 -0
  25. package/dist/cjs/renderer.js +3 -0
  26. package/dist/cjs/renderer.js.map +1 -0
  27. package/dist/cjs/tree-node-like.d.ts +8 -0
  28. package/dist/cjs/tree-node-like.d.ts.map +1 -0
  29. package/dist/cjs/tree-node-like.js +4 -0
  30. package/dist/cjs/tree-node-like.js.map +1 -0
  31. package/dist/cjs/tree.d.ts +46 -0
  32. package/dist/cjs/tree.d.ts.map +1 -0
  33. package/dist/cjs/tree.js +154 -0
  34. package/dist/cjs/tree.js.map +1 -0
  35. package/dist/cjs/velement.d.ts +185 -0
  36. package/dist/cjs/velement.d.ts.map +1 -0
  37. package/dist/cjs/velement.js +874 -0
  38. package/dist/cjs/velement.js.map +1 -0
  39. package/dist/esm/brands.d.ts +3 -0
  40. package/dist/esm/brands.d.ts.map +1 -0
  41. package/dist/esm/brands.js +5 -0
  42. package/dist/esm/brands.js.map +1 -0
  43. package/dist/esm/context.d.ts +28 -0
  44. package/dist/esm/context.d.ts.map +1 -0
  45. package/dist/esm/context.js +48 -0
  46. package/dist/esm/context.js.map +1 -0
  47. package/dist/esm/index.d.ts +6 -0
  48. package/dist/esm/index.d.ts.map +1 -0
  49. package/dist/esm/index.js +22 -0
  50. package/dist/esm/index.js.map +1 -0
  51. package/dist/esm/mount.d.ts +6 -0
  52. package/dist/esm/mount.d.ts.map +1 -0
  53. package/dist/esm/mount.js +209 -0
  54. package/dist/esm/mount.js.map +1 -0
  55. package/dist/esm/props.d.ts +12 -0
  56. package/dist/esm/props.d.ts.map +1 -0
  57. package/dist/esm/props.js +80 -0
  58. package/dist/esm/props.js.map +1 -0
  59. package/dist/esm/renderer.d.ts +29 -0
  60. package/dist/esm/renderer.d.ts.map +1 -0
  61. package/dist/esm/renderer.js +3 -0
  62. package/dist/esm/renderer.js.map +1 -0
  63. package/dist/esm/tree-node-like.d.ts +8 -0
  64. package/dist/esm/tree-node-like.d.ts.map +1 -0
  65. package/dist/esm/tree-node-like.js +4 -0
  66. package/dist/esm/tree-node-like.js.map +1 -0
  67. package/dist/esm/tree.d.ts +46 -0
  68. package/dist/esm/tree.d.ts.map +1 -0
  69. package/dist/esm/tree.js +154 -0
  70. package/dist/esm/tree.js.map +1 -0
  71. package/dist/esm/velement.d.ts +185 -0
  72. package/dist/esm/velement.d.ts.map +1 -0
  73. package/dist/esm/velement.js +874 -0
  74. package/dist/esm/velement.js.map +1 -0
  75. package/package.json +46 -0
  76. package/package.json~ +52 -0
  77. package/src/#mount.test.ts# +372 -0
  78. package/src/.brands.ts.~undo-tree~ +6 -0
  79. package/src/.context.ts.~undo-tree~ +6 -0
  80. package/src/.index.ts.~undo-tree~ +11 -0
  81. package/src/.mount.test.ts.~undo-tree~ +438 -0
  82. package/src/.mount.ts.~undo-tree~ +70 -0
  83. package/src/.node-like.ts.~undo-tree~ +8 -0
  84. package/src/.props.ts.~undo-tree~ +125 -0
  85. package/src/.renderer.ts.~undo-tree~ +18 -0
  86. package/src/.tree-node-like.ts.~undo-tree~ +12 -0
  87. package/src/.tree.ts.~undo-tree~ +46 -0
  88. package/src/.velement.ts.~undo-tree~ +1739 -0
  89. package/src/brands.ts +2 -0
  90. package/src/brands.ts~ +0 -0
  91. package/src/context.ts +61 -0
  92. package/src/context.ts~ +0 -0
  93. package/src/index.ts +5 -0
  94. package/src/index.ts~ +4 -0
  95. package/src/mount.test.ts +405 -0
  96. package/src/mount.test.ts~ +375 -0
  97. package/src/mount.ts +332 -0
  98. package/src/mount.ts~ +306 -0
  99. package/src/node-like.ts~ +0 -0
  100. package/src/props.ts +99 -0
  101. package/src/props.ts~ +86 -0
  102. package/src/renderer.ts +37 -0
  103. package/src/renderer.ts~ +37 -0
  104. package/src/tree-node-like.ts +8 -0
  105. package/src/tree-node-like.ts~ +6 -0
  106. package/src/tree.ts +226 -0
  107. package/src/tree.ts~ +227 -0
  108. package/src/velement.ts +990 -0
  109. package/src/velement.ts~ +966 -0
  110. package/tsconfig.cjs.json +10 -0
  111. package/tsconfig.esm.json +10 -0
  112. package/tsconfig.json +23 -0
  113. package/tsconfig.json~ +23 -0
@@ -0,0 +1,874 @@
1
+ "use strict";
2
+ // virtual-dom.ts
3
+ // A lightweight virtual DOM implementation for unit testing browser code without a real browser.
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.VEvent = exports.VDocument = exports.VDocumentFragment = exports.VElement = exports.VStyle = exports.VClassList = exports.VComment = exports.VText = exports.VNode = exports.NodeType = void 0;
6
+ exports.toHTML = toHTML;
7
+ exports.createDocument = createDocument;
8
+ // ---------------------------------------------------------------------------
9
+ // Node type constants (mirrors the real DOM)
10
+ // ---------------------------------------------------------------------------
11
+ var NodeType;
12
+ (function (NodeType) {
13
+ NodeType[NodeType["ELEMENT_NODE"] = 1] = "ELEMENT_NODE";
14
+ NodeType[NodeType["TEXT_NODE"] = 3] = "TEXT_NODE";
15
+ NodeType[NodeType["COMMENT_NODE"] = 8] = "COMMENT_NODE";
16
+ NodeType[NodeType["DOCUMENT_NODE"] = 9] = "DOCUMENT_NODE";
17
+ NodeType[NodeType["DOCUMENT_TYPE_NODE"] = 10] = "DOCUMENT_TYPE_NODE";
18
+ NodeType[NodeType["DOCUMENT_FRAGMENT_NODE"] = 11] = "DOCUMENT_FRAGMENT_NODE";
19
+ })(NodeType || (exports.NodeType = NodeType = {}));
20
+ // ---------------------------------------------------------------------------
21
+ // VNode — abstract base for every node
22
+ // ---------------------------------------------------------------------------
23
+ class VNode {
24
+ parentNode = null;
25
+ childNodes = [];
26
+ ownerDocument = null;
27
+ get firstChild() {
28
+ return this.childNodes[0] ?? null;
29
+ }
30
+ get lastChild() {
31
+ return this.childNodes[this.childNodes.length - 1] ?? null;
32
+ }
33
+ get nextSibling() {
34
+ if (!this.parentNode)
35
+ return null;
36
+ const siblings = this.parentNode.childNodes;
37
+ const idx = siblings.indexOf(this);
38
+ return siblings[idx + 1] ?? null;
39
+ }
40
+ get previousSibling() {
41
+ if (!this.parentNode)
42
+ return null;
43
+ const siblings = this.parentNode.childNodes;
44
+ const idx = siblings.indexOf(this);
45
+ return idx > 0 ? (siblings[idx - 1] ?? null) : null;
46
+ }
47
+ get parentElement() {
48
+ return this.parentNode instanceof VElement ? this.parentNode : null;
49
+ }
50
+ // ---- Mutation helpers ----
51
+ appendChild(child) {
52
+ this._removeFromParent(child);
53
+ child.parentNode = this;
54
+ child.ownerDocument = this.ownerDocument;
55
+ this.childNodes.push(child);
56
+ return child;
57
+ }
58
+ insertBefore(newChild, refChild) {
59
+ if (refChild === null)
60
+ return this.appendChild(newChild);
61
+ const idx = this.childNodes.indexOf(refChild);
62
+ if (idx === -1)
63
+ throw new Error("refChild is not a child of this node");
64
+ this._removeFromParent(newChild);
65
+ newChild.parentNode = this;
66
+ newChild.ownerDocument = this.ownerDocument;
67
+ this.childNodes.splice(idx, 0, newChild);
68
+ return newChild;
69
+ }
70
+ removeChild(child) {
71
+ const idx = this.childNodes.indexOf(child);
72
+ if (idx === -1)
73
+ throw new Error("Node is not a child of this node");
74
+ this.childNodes.splice(idx, 1);
75
+ child.parentNode = null;
76
+ return child;
77
+ }
78
+ replaceChild(newChild, oldChild) {
79
+ const idx = this.childNodes.indexOf(oldChild);
80
+ if (idx === -1)
81
+ throw new Error("oldChild is not a child of this node");
82
+ this._removeFromParent(newChild);
83
+ newChild.parentNode = this;
84
+ newChild.ownerDocument = this.ownerDocument;
85
+ this.childNodes.splice(idx, 1, newChild);
86
+ oldChild.parentNode = null;
87
+ return oldChild;
88
+ }
89
+ hasChildNodes() {
90
+ return this.childNodes.length > 0;
91
+ }
92
+ contains(other) {
93
+ if (!other)
94
+ return false;
95
+ let current = other;
96
+ while (current) {
97
+ if (current === this)
98
+ return true;
99
+ current = current.parentNode;
100
+ }
101
+ return false;
102
+ }
103
+ cloneNode(deep = false) {
104
+ return this._clone(deep);
105
+ }
106
+ /** Remove a node from its current parent before re-parenting. */
107
+ _removeFromParent(child) {
108
+ if (child.parentNode) {
109
+ child.parentNode.removeChild(child);
110
+ }
111
+ }
112
+ }
113
+ exports.VNode = VNode;
114
+ // ---------------------------------------------------------------------------
115
+ // VText
116
+ // ---------------------------------------------------------------------------
117
+ class VText extends VNode {
118
+ data;
119
+ nodeType = NodeType.TEXT_NODE;
120
+ nodeName = "#text";
121
+ constructor(data) {
122
+ super();
123
+ this.data = data;
124
+ }
125
+ get textContent() {
126
+ return this.data;
127
+ }
128
+ set textContent(value) {
129
+ this.data = value ?? "";
130
+ }
131
+ get nodeValue() {
132
+ return this.data;
133
+ }
134
+ set nodeValue(value) {
135
+ this.data = value ?? "";
136
+ }
137
+ _clone(_deep) {
138
+ return new VText(this.data);
139
+ }
140
+ }
141
+ exports.VText = VText;
142
+ // ---------------------------------------------------------------------------
143
+ // VComment
144
+ // ---------------------------------------------------------------------------
145
+ class VComment extends VNode {
146
+ data;
147
+ nodeType = NodeType.COMMENT_NODE;
148
+ nodeName = "#comment";
149
+ constructor(data) {
150
+ super();
151
+ this.data = data;
152
+ }
153
+ get textContent() {
154
+ return this.data;
155
+ }
156
+ set textContent(value) {
157
+ this.data = value ?? "";
158
+ }
159
+ get nodeValue() {
160
+ return this.data;
161
+ }
162
+ set nodeValue(value) {
163
+ this.data = value ?? "";
164
+ }
165
+ _clone(_deep) {
166
+ return new VComment(this.data);
167
+ }
168
+ }
169
+ exports.VComment = VComment;
170
+ // ---------------------------------------------------------------------------
171
+ // Attr map — case-insensitive for HTML-like behaviour
172
+ // ---------------------------------------------------------------------------
173
+ class AttributeMap {
174
+ _map = new Map();
175
+ get(name) {
176
+ return this._map.get(name.toLowerCase()) ?? null;
177
+ }
178
+ set(name, value) {
179
+ this._map.set(name.toLowerCase(), value);
180
+ }
181
+ has(name) {
182
+ return this._map.has(name.toLowerCase());
183
+ }
184
+ remove(name) {
185
+ return this._map.delete(name.toLowerCase());
186
+ }
187
+ entries() {
188
+ return this._map.entries();
189
+ }
190
+ get size() {
191
+ return this._map.size;
192
+ }
193
+ clone() {
194
+ const copy = new AttributeMap();
195
+ for (const [k, v] of this._map)
196
+ copy._map.set(k, v);
197
+ return copy;
198
+ }
199
+ }
200
+ // ---------------------------------------------------------------------------
201
+ // ClassList — minimal DOMTokenList stand-in
202
+ // ---------------------------------------------------------------------------
203
+ class VClassList {
204
+ _el;
205
+ constructor(_el) {
206
+ this._el = _el;
207
+ }
208
+ _tokens() {
209
+ const raw = this._el.getAttribute("class") ?? "";
210
+ return raw.split(/\s+/).filter(Boolean);
211
+ }
212
+ _write(tokens) {
213
+ this._el.setAttribute("class", tokens.join(" "));
214
+ }
215
+ add(...tokens) {
216
+ const set = new Set(this._tokens());
217
+ for (const t of tokens)
218
+ set.add(t);
219
+ this._write([...set]);
220
+ }
221
+ remove(...tokens) {
222
+ const set = new Set(this._tokens());
223
+ for (const t of tokens)
224
+ set.delete(t);
225
+ this._write([...set]);
226
+ }
227
+ toggle(token, force) {
228
+ const set = new Set(this._tokens());
229
+ const shouldAdd = force !== undefined ? force : !set.has(token);
230
+ if (shouldAdd) {
231
+ set.add(token);
232
+ }
233
+ else {
234
+ set.delete(token);
235
+ }
236
+ this._write([...set]);
237
+ return shouldAdd;
238
+ }
239
+ contains(token) {
240
+ return this._tokens().includes(token);
241
+ }
242
+ replace(oldToken, newToken) {
243
+ const tokens = this._tokens();
244
+ const idx = tokens.indexOf(oldToken);
245
+ if (idx === -1)
246
+ return false;
247
+ tokens.splice(idx, 1, newToken);
248
+ this._write(tokens);
249
+ return true;
250
+ }
251
+ get length() {
252
+ return this._tokens().length;
253
+ }
254
+ toString() {
255
+ return this._tokens().join(" ");
256
+ }
257
+ }
258
+ exports.VClassList = VClassList;
259
+ // ---------------------------------------------------------------------------
260
+ // Style — minimal CSSStyleDeclaration stand-in
261
+ // ---------------------------------------------------------------------------
262
+ class VStyle {
263
+ // @ts-ignore
264
+ _props = new Map();
265
+ getPropertyValue(name) {
266
+ return this._props.get(name) ?? "";
267
+ }
268
+ setProperty(name, value) {
269
+ if (value === null || value === "") {
270
+ this._props.delete(name);
271
+ }
272
+ else {
273
+ this._props.set(name, value);
274
+ }
275
+ }
276
+ removeProperty(name) {
277
+ const old = this._props.get(name) ?? "";
278
+ this._props.delete(name);
279
+ return old;
280
+ }
281
+ get cssText() {
282
+ return [...this._props.entries()].map(([k, v]) => `${k}: ${v};`).join(" ");
283
+ }
284
+ set cssText(value) {
285
+ this._props.clear();
286
+ if (!value)
287
+ return;
288
+ for (const decl of value.split(";")) {
289
+ const colon = decl.indexOf(":");
290
+ if (colon === -1)
291
+ continue;
292
+ const prop = decl.slice(0, colon).trim();
293
+ const val = decl.slice(colon + 1).trim();
294
+ if (prop)
295
+ this._props.set(prop, val);
296
+ }
297
+ }
298
+ }
299
+ exports.VStyle = VStyle;
300
+ // ---------------------------------------------------------------------------
301
+ // VElement
302
+ // ---------------------------------------------------------------------------
303
+ class VElement extends VNode {
304
+ nodeType = NodeType.ELEMENT_NODE;
305
+ tagName;
306
+ classList;
307
+ style;
308
+ _attrs = new AttributeMap();
309
+ _eventListeners = new Map();
310
+ _dataset = {};
311
+ constructor(tagName) {
312
+ super();
313
+ this.tagName = tagName.toUpperCase();
314
+ this.classList = new VClassList(this);
315
+ this.style = new VStyle();
316
+ // Return a proxy so element.id, element.className etc. work naturally
317
+ return new Proxy(this, {
318
+ get(target, prop, receiver) {
319
+ // Built-in properties first
320
+ if (prop in target || typeof prop === "symbol") {
321
+ return Reflect.get(target, prop, receiver);
322
+ }
323
+ // Fallback: treat as an attribute shorthand (e.g. el.id, el.href)
324
+ if (typeof prop === "string") {
325
+ return target.getAttribute(prop) ?? undefined;
326
+ }
327
+ return undefined;
328
+ },
329
+ set(target, prop, value, receiver) {
330
+ if (prop in target || typeof prop === "symbol") {
331
+ return Reflect.set(target, prop, value, receiver);
332
+ }
333
+ if (typeof prop === "string") {
334
+ target.setAttribute(prop, String(value));
335
+ return true;
336
+ }
337
+ return false;
338
+ },
339
+ });
340
+ }
341
+ get nodeName() {
342
+ return this.tagName;
343
+ }
344
+ // ---- id / className shortcuts ----
345
+ get id() {
346
+ return this.getAttribute("id") ?? "";
347
+ }
348
+ set id(value) {
349
+ this.setAttribute("id", value);
350
+ }
351
+ get className() {
352
+ return this.getAttribute("class") ?? "";
353
+ }
354
+ set className(value) {
355
+ this.setAttribute("class", value);
356
+ }
357
+ // ---- dataset ----
358
+ get dataset() {
359
+ return this._dataset;
360
+ }
361
+ // ---- textContent / innerHTML ----
362
+ get textContent() {
363
+ return this.childNodes.map((c) => c.textContent ?? "").join("");
364
+ }
365
+ set textContent(value) {
366
+ this.childNodes = [];
367
+ if (value) {
368
+ this.appendChild(new VText(value));
369
+ }
370
+ }
371
+ get innerHTML() {
372
+ return this.childNodes.map((c) => serializeNode(c)).join("");
373
+ }
374
+ set innerHTML(html) {
375
+ // Minimal: just set the raw text. A real implementation would parse HTML.
376
+ this.childNodes = [];
377
+ if (html) {
378
+ this.appendChild(new VText(html));
379
+ }
380
+ }
381
+ get outerHTML() {
382
+ return serializeNode(this);
383
+ }
384
+ // ---- Attributes ----
385
+ getAttribute(name) {
386
+ return this._attrs.get(name);
387
+ }
388
+ setAttribute(name, value) {
389
+ this._attrs.set(name, value);
390
+ // Keep dataset in sync
391
+ if (name.startsWith("data-")) {
392
+ this._dataset[camelCase(name.slice(5))] = value;
393
+ }
394
+ }
395
+ removeAttribute(name) {
396
+ this._attrs.remove(name);
397
+ if (name.startsWith("data-")) {
398
+ delete this._dataset[camelCase(name.slice(5))];
399
+ }
400
+ }
401
+ hasAttribute(name) {
402
+ return this._attrs.has(name);
403
+ }
404
+ getAttributeNames() {
405
+ return [...this._attrs.entries()].map(([k]) => k);
406
+ }
407
+ // ---- Query helpers ----
408
+ getElementById(id) {
409
+ return this._query((el) => el.getAttribute("id") === id);
410
+ }
411
+ getElementsByClassName(className) {
412
+ const target = className.trim();
413
+ return this._queryAll((el) => el.classList.contains(target));
414
+ }
415
+ getElementsByTagName(tag) {
416
+ const upper = tag.toUpperCase();
417
+ return this._queryAll((el) => upper === "*" || el.tagName === upper);
418
+ }
419
+ querySelector(selector) {
420
+ return this._query(makeSimpleMatcher(selector));
421
+ }
422
+ querySelectorAll(selector) {
423
+ return this._queryAll(makeSimpleMatcher(selector));
424
+ }
425
+ closest(selector) {
426
+ const matcher = makeSimpleMatcher(selector);
427
+ let current = this;
428
+ while (current) {
429
+ if (current instanceof VElement && matcher(current)) {
430
+ return current;
431
+ }
432
+ current = current.parentNode;
433
+ }
434
+ return null;
435
+ }
436
+ matches(selector) {
437
+ return makeSimpleMatcher(selector)(this);
438
+ }
439
+ // ---- Events (record & replay for testing) ----
440
+ addEventListener(type, listener) {
441
+ let list = this._eventListeners.get(type);
442
+ if (!list) {
443
+ list = [];
444
+ this._eventListeners.set(type, list);
445
+ }
446
+ list.push(listener);
447
+ }
448
+ removeEventListener(type, listener) {
449
+ const arr = this._eventListeners.get(type);
450
+ if (!arr)
451
+ return;
452
+ const idx = arr.indexOf(listener);
453
+ if (idx !== -1)
454
+ arr.splice(idx, 1);
455
+ }
456
+ dispatchEvent(event) {
457
+ const listeners = this._eventListeners.get(event.type) ?? [];
458
+ for (const l of listeners) {
459
+ if (typeof l === "function")
460
+ l(event);
461
+ else
462
+ l.handleEvent(event);
463
+ }
464
+ return true;
465
+ }
466
+ /** Click shorthand — dispatches a "click" VEvent. */
467
+ click() {
468
+ this.dispatchEvent(new VEvent("click"));
469
+ }
470
+ // ---- Children helpers ----
471
+ get children() {
472
+ return this.childNodes.filter((c) => c instanceof VElement);
473
+ }
474
+ get childElementCount() {
475
+ return this.children.length;
476
+ }
477
+ get firstElementChild() {
478
+ return this.children[0] ?? null;
479
+ }
480
+ get lastElementChild() {
481
+ const ch = this.children;
482
+ return ch[ch.length - 1] ?? null;
483
+ }
484
+ // ---- Clone ----
485
+ _clone(deep) {
486
+ const el = new VElement(this.tagName);
487
+ el._attrs = this._attrs.clone();
488
+ el._dataset = { ...this._dataset };
489
+ el.style.cssText = this.style.cssText;
490
+ if (deep) {
491
+ for (const child of this.childNodes) {
492
+ el.appendChild(child.cloneNode(true));
493
+ }
494
+ }
495
+ return el;
496
+ }
497
+ // ---- Internal query helpers ----
498
+ _query(pred) {
499
+ for (const child of this.childNodes) {
500
+ if (child instanceof VElement) {
501
+ if (pred(child))
502
+ return child;
503
+ const found = child._query(pred);
504
+ if (found)
505
+ return found;
506
+ }
507
+ }
508
+ return null;
509
+ }
510
+ _queryAll(pred) {
511
+ const results = [];
512
+ for (const child of this.childNodes) {
513
+ if (child instanceof VElement) {
514
+ if (pred(child))
515
+ results.push(child);
516
+ results.push(...child._queryAll(pred));
517
+ }
518
+ }
519
+ return results;
520
+ }
521
+ }
522
+ exports.VElement = VElement;
523
+ // ---------------------------------------------------------------------------
524
+ // VDocumentFragment
525
+ // ---------------------------------------------------------------------------
526
+ class VDocumentFragment extends VNode {
527
+ nodeType = NodeType.DOCUMENT_FRAGMENT_NODE;
528
+ nodeName = "#document-fragment";
529
+ get textContent() {
530
+ return this.childNodes.map((c) => c.textContent ?? "").join("");
531
+ }
532
+ set textContent(value) {
533
+ this.childNodes = [];
534
+ if (value)
535
+ this.appendChild(new VText(value));
536
+ }
537
+ _clone(deep) {
538
+ const frag = new VDocumentFragment();
539
+ if (deep) {
540
+ for (const child of this.childNodes) {
541
+ frag.appendChild(child.cloneNode(true));
542
+ }
543
+ }
544
+ return frag;
545
+ }
546
+ }
547
+ exports.VDocumentFragment = VDocumentFragment;
548
+ // ---------------------------------------------------------------------------
549
+ // VDocument
550
+ // ---------------------------------------------------------------------------
551
+ class VDocument extends VNode {
552
+ nodeType = NodeType.DOCUMENT_NODE;
553
+ nodeName = "#document";
554
+ body;
555
+ head;
556
+ documentElement;
557
+ constructor() {
558
+ super();
559
+ this.ownerDocument = this;
560
+ this.documentElement = this._make("html");
561
+ this.head = this._make("head");
562
+ this.body = this._make("body");
563
+ this.documentElement.appendChild(this.head);
564
+ this.documentElement.appendChild(this.body);
565
+ this.appendChild(this.documentElement);
566
+ }
567
+ get textContent() {
568
+ return null; // matches real DOM behaviour
569
+ }
570
+ set textContent(_value) {
571
+ // no-op for Document, matches real DOM
572
+ }
573
+ createElement(tagName) {
574
+ const el = new VElement(tagName);
575
+ el.ownerDocument = this;
576
+ return el;
577
+ }
578
+ createTextNode(data) {
579
+ const t = new VText(data);
580
+ t.ownerDocument = this;
581
+ return t;
582
+ }
583
+ createComment(data) {
584
+ const c = new VComment(data);
585
+ c.ownerDocument = this;
586
+ return c;
587
+ }
588
+ createDocumentFragment() {
589
+ const f = new VDocumentFragment();
590
+ f.ownerDocument = this;
591
+ return f;
592
+ }
593
+ getElementById(id) {
594
+ return this.documentElement.getElementById(id);
595
+ }
596
+ getElementsByClassName(className) {
597
+ return this.documentElement.getElementsByClassName(className);
598
+ }
599
+ getElementsByTagName(tag) {
600
+ return this.documentElement.getElementsByTagName(tag);
601
+ }
602
+ querySelector(selector) {
603
+ return this.documentElement.querySelector(selector);
604
+ }
605
+ querySelectorAll(selector) {
606
+ return this.documentElement.querySelectorAll(selector);
607
+ }
608
+ _clone(deep) {
609
+ const doc = new VDocument();
610
+ if (deep) {
611
+ // Already has html > head + body structure; skip re-creating
612
+ }
613
+ return doc;
614
+ }
615
+ _make(tag) {
616
+ const el = new VElement(tag);
617
+ el.ownerDocument = this;
618
+ return el;
619
+ }
620
+ }
621
+ exports.VDocument = VDocument;
622
+ // ---------------------------------------------------------------------------
623
+ // VEvent — lightweight Event stand-in
624
+ // ---------------------------------------------------------------------------
625
+ class VEvent {
626
+ type;
627
+ bubbles;
628
+ cancelable;
629
+ defaultPrevented = false;
630
+ target = null;
631
+ currentTarget = null;
632
+ constructor(type, opts = {}) {
633
+ this.type = type;
634
+ this.bubbles = opts.bubbles ?? false;
635
+ this.cancelable = opts.cancelable ?? false;
636
+ }
637
+ preventDefault() {
638
+ if (this.cancelable)
639
+ this.defaultPrevented = true;
640
+ }
641
+ stopPropagation() {
642
+ /* noop for now */
643
+ }
644
+ stopImmediatePropagation() {
645
+ /* noop for now */
646
+ }
647
+ }
648
+ exports.VEvent = VEvent;
649
+ // ---------------------------------------------------------------------------
650
+ // Serialization helper (outerHTML / innerHTML / toHTML)
651
+ // ---------------------------------------------------------------------------
652
+ const VOID_ELEMENTS = new Set([
653
+ "AREA",
654
+ "BASE",
655
+ "BR",
656
+ "COL",
657
+ "EMBED",
658
+ "HR",
659
+ "IMG",
660
+ "INPUT",
661
+ "LINK",
662
+ "META",
663
+ "PARAM",
664
+ "SOURCE",
665
+ "TRACK",
666
+ "WBR",
667
+ ]);
668
+ /** Flat serialization used by innerHTML / outerHTML (no indentation). */
669
+ function serializeNode(node) {
670
+ if (node instanceof VText)
671
+ return escapeHtml(node.data);
672
+ if (node instanceof VComment)
673
+ return `<!--${node.data}-->`;
674
+ if (node instanceof VElement) {
675
+ const tag = node.tagName.toLowerCase();
676
+ let attrs = "";
677
+ for (const name of node.getAttributeNames()) {
678
+ const val = node.getAttribute(name) ?? "";
679
+ attrs += ` ${name}="${escapeAttr(val)}"`;
680
+ }
681
+ if (VOID_ELEMENTS.has(node.tagName)) {
682
+ return `<${tag}${attrs} />`;
683
+ }
684
+ const inner = node.childNodes.map(serializeNode).join("");
685
+ return `<${tag}${attrs}>${inner}</${tag}>`;
686
+ }
687
+ if (node instanceof VDocumentFragment) {
688
+ return node.childNodes.map(serializeNode).join("");
689
+ }
690
+ if (node instanceof VDocument) {
691
+ return "<!DOCTYPE html>\n" + node.childNodes.map(serializeNode).join("");
692
+ }
693
+ return "";
694
+ }
695
+ // ---------------------------------------------------------------------------
696
+ // Pretty-printed serialization (used by toHTML)
697
+ // ---------------------------------------------------------------------------
698
+ /** Elements whose content is purely inline and should stay on one line. */
699
+ const INLINE_ELEMENTS = new Set([
700
+ "A",
701
+ "ABBR",
702
+ "B",
703
+ "BDO",
704
+ "BR",
705
+ "CITE",
706
+ "CODE",
707
+ "DFN",
708
+ "EM",
709
+ "I",
710
+ "KBD",
711
+ "LABEL",
712
+ "MAP",
713
+ "Q",
714
+ "S",
715
+ "SAMP",
716
+ "SMALL",
717
+ "SPAN",
718
+ "STRONG",
719
+ "SUB",
720
+ "SUP",
721
+ "TEXTAREA",
722
+ "TIME",
723
+ "U",
724
+ "VAR",
725
+ ]);
726
+ /**
727
+ * Returns `true` when every child is either a text node or an inline element
728
+ * that itself contains only text / inline children. In that case we render
729
+ * the element on a single line to avoid awkward whitespace inside e.g.
730
+ * `<p>Hello <b>world</b></p>`.
731
+ */
732
+ function isInlineContent(node) {
733
+ return node.childNodes.every((child) => {
734
+ if (child instanceof VText)
735
+ return true;
736
+ if (child instanceof VElement && INLINE_ELEMENTS.has(child.tagName)) {
737
+ return isInlineContent(child);
738
+ }
739
+ return false;
740
+ });
741
+ }
742
+ function prettyPrint(node, depth, indent) {
743
+ const pad = indent.repeat(depth);
744
+ if (node instanceof VText) {
745
+ const trimmed = node.data.trim();
746
+ return trimmed.length > 0 ? pad + escapeHtml(trimmed) : "";
747
+ }
748
+ if (node instanceof VComment) {
749
+ return `${pad}<!--${node.data}-->`;
750
+ }
751
+ if (node instanceof VElement) {
752
+ const tag = node.tagName.toLowerCase();
753
+ let attrs = "";
754
+ for (const name of node.getAttributeNames()) {
755
+ const val = node.getAttribute(name) ?? "";
756
+ attrs += ` ${name}="${escapeAttr(val)}"`;
757
+ }
758
+ // Self-closing / void
759
+ if (VOID_ELEMENTS.has(node.tagName)) {
760
+ return `${pad}<${tag}${attrs} />`;
761
+ }
762
+ // No children
763
+ if (node.childNodes.length === 0) {
764
+ return `${pad}<${tag}${attrs}></${tag}>`;
765
+ }
766
+ // Inline content — keep on one line
767
+ if (isInlineContent(node)) {
768
+ const inner = node.childNodes.map((c) => serializeNode(c)).join("");
769
+ return `${pad}<${tag}${attrs}>${inner}</${tag}>`;
770
+ }
771
+ // Block content — children on separate indented lines
772
+ const children = node.childNodes
773
+ .map((c) => prettyPrint(c, depth + 1, indent))
774
+ .filter(Boolean)
775
+ .join("\n");
776
+ return `${pad}<${tag}${attrs}>\n${children}\n${pad}</${tag}>`;
777
+ }
778
+ if (node instanceof VDocumentFragment) {
779
+ return node.childNodes
780
+ .map((c) => prettyPrint(c, depth, indent))
781
+ .filter(Boolean)
782
+ .join("\n");
783
+ }
784
+ if (node instanceof VDocument) {
785
+ const children = node.childNodes
786
+ .map((c) => prettyPrint(c, depth, indent))
787
+ .filter(Boolean)
788
+ .join("\n");
789
+ return `<!DOCTYPE html>\n${children}`;
790
+ }
791
+ return "";
792
+ }
793
+ /**
794
+ * Convert any virtual DOM node to an HTML string.
795
+ *
796
+ * @param node The node to serialise.
797
+ * @param indent Indentation per level (default: `" "` — two spaces).
798
+ * Pass `""` for compact / non-indented output.
799
+ *
800
+ * - `VDocument` → `<!DOCTYPE html>\n<html>…</html>`
801
+ * - `VElement` → `<tag attrs>\n …children…\n</tag>`
802
+ * - `VText` → escaped text content
803
+ * - `VComment` → `<!--…-->`
804
+ * - `VDocumentFragment` → concatenated children
805
+ */
806
+ function toHTML(node, indent = " ") {
807
+ if (indent === "")
808
+ return serializeNode(node);
809
+ return prettyPrint(node, 0, indent);
810
+ }
811
+ function escapeHtml(s) {
812
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
813
+ }
814
+ function escapeAttr(s) {
815
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
816
+ }
817
+ // ---------------------------------------------------------------------------
818
+ // Very small CSS-selector matcher (covers the common cases)
819
+ // Supports: tag, .class, #id, [attr], [attr=value], and combos thereof
820
+ // ---------------------------------------------------------------------------
821
+ function makeSimpleMatcher(selector) {
822
+ // Split on commas for ,‑separated selectors
823
+ const parts = selector.split(",").map((s) => s.trim());
824
+ const matchers = parts.map(parseSingleSelector);
825
+ return (el) => matchers.some((m) => m(el));
826
+ }
827
+ function parseSingleSelector(sel) {
828
+ const checks = [];
829
+ // Tag name (must be at the start, before any # . or [)
830
+ const tagMatch = sel.match(/^([a-zA-Z][a-zA-Z0-9-]*)/);
831
+ if (tagMatch) {
832
+ const tag = tagMatch[1].toUpperCase();
833
+ checks.push((el) => el.tagName === tag);
834
+ }
835
+ // #id
836
+ for (const m of sel.matchAll(/#([a-zA-Z0-9_-]+)/g)) {
837
+ const id = m[1];
838
+ checks.push((el) => el.getAttribute("id") === id);
839
+ }
840
+ // .class
841
+ for (const m of sel.matchAll(/\.([a-zA-Z0-9_-]+)/g)) {
842
+ const cls = m[1];
843
+ checks.push((el) => el.classList.contains(cls));
844
+ }
845
+ // [attr] and [attr=value] and [attr="value"]
846
+ for (const m of sel.matchAll(/\[([a-zA-Z0-9_-]+)(?:=["']?([^"'\]]*)["']?)?\]/g)) {
847
+ const attr = m[1];
848
+ const val = m[2];
849
+ if (val !== undefined) {
850
+ checks.push((el) => el.getAttribute(attr) === val);
851
+ }
852
+ else {
853
+ checks.push((el) => el.hasAttribute(attr));
854
+ }
855
+ }
856
+ if (checks.length === 0) {
857
+ return () => false;
858
+ }
859
+ return (el) => checks.every((fn) => fn(el));
860
+ }
861
+ // ---------------------------------------------------------------------------
862
+ // Utility
863
+ // ---------------------------------------------------------------------------
864
+ function camelCase(s) {
865
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
866
+ }
867
+ // ---------------------------------------------------------------------------
868
+ // Convenience factory (drop-in for tests)
869
+ // ---------------------------------------------------------------------------
870
+ /** Create a fresh virtual document, ready to use like `window.document`. */
871
+ function createDocument() {
872
+ return new VDocument();
873
+ }
874
+ //# sourceMappingURL=velement.js.map