@gjsify/dom-elements 0.3.21 → 0.4.3

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 (82) hide show
  1. package/lib/esm/_virtual/_rolldown/runtime.js +1 -1
  2. package/lib/esm/attr.js +1 -1
  3. package/lib/esm/character-data.js +1 -1
  4. package/lib/esm/comment.js +1 -1
  5. package/lib/esm/document-fragment.js +1 -1
  6. package/lib/esm/document.js +1 -1
  7. package/lib/esm/dom-matrix.js +1 -1
  8. package/lib/esm/dom-token-list.js +1 -1
  9. package/lib/esm/element.js +1 -1
  10. package/lib/esm/font-face.js +1 -1
  11. package/lib/esm/gst-time.js +1 -1
  12. package/lib/esm/html-canvas-element.js +1 -1
  13. package/lib/esm/html-element.js +1 -1
  14. package/lib/esm/html-image-element.js +1 -1
  15. package/lib/esm/html-media-element.js +1 -1
  16. package/lib/esm/html-video-element.js +1 -1
  17. package/lib/esm/image.js +1 -1
  18. package/lib/esm/intersection-observer.js +1 -1
  19. package/lib/esm/location-stub.js +1 -1
  20. package/lib/esm/match-media.js +1 -1
  21. package/lib/esm/mutation-observer.js +1 -1
  22. package/lib/esm/named-node-map.js +1 -1
  23. package/lib/esm/node-list.js +1 -1
  24. package/lib/esm/node.js +1 -1
  25. package/lib/esm/register/canvas.js +1 -1
  26. package/lib/esm/register/document.js +1 -1
  27. package/lib/esm/register/helpers.js +1 -1
  28. package/lib/esm/resize-observer.js +1 -1
  29. package/lib/esm/text.js +1 -1
  30. package/lib/types/location-stub.d.ts +1 -1
  31. package/package.json +78 -75
  32. package/src/attr.ts +0 -61
  33. package/src/character-data.ts +0 -79
  34. package/src/comment.ts +0 -31
  35. package/src/document-fragment.ts +0 -137
  36. package/src/document.ts +0 -103
  37. package/src/dom-matrix.ts +0 -109
  38. package/src/dom-token-list.ts +0 -140
  39. package/src/element.ts +0 -316
  40. package/src/font-face.ts +0 -97
  41. package/src/gst-time.ts +0 -26
  42. package/src/html-canvas-element.ts +0 -90
  43. package/src/html-element.ts +0 -502
  44. package/src/html-image-element.spec.ts +0 -285
  45. package/src/html-image-element.ts +0 -295
  46. package/src/html-media-element.ts +0 -123
  47. package/src/html-video-element.ts +0 -143
  48. package/src/image.ts +0 -31
  49. package/src/index.spec.ts +0 -914
  50. package/src/index.ts +0 -39
  51. package/src/intersection-observer.ts +0 -42
  52. package/src/location-stub.ts +0 -20
  53. package/src/match-media.ts +0 -32
  54. package/src/mutation-observer.ts +0 -39
  55. package/src/named-node-map.ts +0 -159
  56. package/src/namespace-uri.ts +0 -11
  57. package/src/node-list.ts +0 -52
  58. package/src/node-type.ts +0 -14
  59. package/src/node.ts +0 -280
  60. package/src/property-symbol.ts +0 -23
  61. package/src/register/canvas.ts +0 -23
  62. package/src/register/document.ts +0 -64
  63. package/src/register/font-face.ts +0 -18
  64. package/src/register/helpers.ts +0 -15
  65. package/src/register/image.ts +0 -8
  66. package/src/register/location.ts +0 -6
  67. package/src/register/match-media.ts +0 -6
  68. package/src/register/navigator.ts +0 -6
  69. package/src/register/observers.ts +0 -10
  70. package/src/register.spec.ts +0 -136
  71. package/src/register.ts +0 -13
  72. package/src/resize-observer.ts +0 -28
  73. package/src/stubs.spec.ts +0 -284
  74. package/src/test.browser.mts +0 -686
  75. package/src/test.mts +0 -9
  76. package/src/text.ts +0 -67
  77. package/src/types/i-html-image-element.ts +0 -44
  78. package/src/types/image-data.ts +0 -12
  79. package/src/types/index.ts +0 -3
  80. package/src/types/predefined-color-space.ts +0 -1
  81. package/tsconfig.json +0 -37
  82. package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts DELETED
@@ -1,39 +0,0 @@
1
- // DOM element hierarchy for GJS — original implementation adapted from happy-dom
2
- // Copyright (c) David Ortner (capricorn86). MIT license.
3
- //
4
- // Note: This module has no side effects. Importing it gives you the DOM
5
- // classes as named exports but does NOT register `document`, `Image`,
6
- // `HTMLCanvasElement` etc. on globalThis, and does NOT wire up the 2d
7
- // canvas context factory.
8
- //
9
- // If you need the globals (or `canvas.getContext('2d')` to work), import
10
- // `@gjsify/dom-elements/register` explicitly — or let the gjsify esbuild
11
- // plugin auto-inject it based on references in your code.
12
-
13
- export { Attr } from './attr.js';
14
- export { NamedNodeMap } from './named-node-map.js';
15
- export { NodeList } from './node-list.js';
16
- export { Node } from './node.js';
17
- export { CharacterData } from './character-data.js';
18
- export { Text } from './text.js';
19
- export { Comment } from './comment.js';
20
- export { DocumentFragment } from './document-fragment.js';
21
- export { DOMTokenList } from './dom-token-list.js';
22
- export { Element } from './element.js';
23
- export { HTMLElement, CSSStyleDeclaration } from './html-element.js';
24
- export { HTMLCanvasElement } from './html-canvas-element.js';
25
- export { HTMLImageElement } from './html-image-element.js';
26
- export { HTMLMediaElement } from './html-media-element.js';
27
- export { HTMLVideoElement } from './html-video-element.js';
28
- export { Image } from './image.js';
29
- export { Document, document } from './document.js';
30
- export { MutationObserver } from './mutation-observer.js';
31
- export { ResizeObserver } from './resize-observer.js';
32
- export { IntersectionObserver } from './intersection-observer.js';
33
- export { NodeType } from './node-type.js';
34
- export { NamespaceURI } from './namespace-uri.js';
35
- export * as PropertySymbol from './property-symbol.js';
36
- export { FontFace, FontFaceSet } from './font-face.js';
37
- export { MediaQueryList, matchMedia } from './match-media.js';
38
- export { location } from './location-stub.js';
39
- export { DOMMatrix, DOMMatrixReadOnly } from './dom-matrix.js';
@@ -1,42 +0,0 @@
1
- // IntersectionObserver stub for GJS — original implementation
2
- // Reference: refs/happy-dom/packages/happy-dom/src/intersection-observer/IntersectionObserver.ts
3
- // Copyright (c) David Ortner (capricorn86). MIT license.
4
- // Modifications: Stub implementation — no actual intersection tracking
5
-
6
- import type { Element } from './element.js';
7
-
8
- /**
9
- * IntersectionObserver stub.
10
- * Many libraries check for IntersectionObserver existence; this prevents crashes.
11
- *
12
- * Reference: https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver
13
- */
14
- export class IntersectionObserver {
15
- readonly root: Element | null;
16
- readonly rootMargin: string;
17
- readonly thresholds: readonly number[];
18
-
19
- constructor(_callback: (...args: unknown[]) => void, options?: { root?: Element | null; rootMargin?: string; threshold?: number | number[] }) {
20
- this.root = options?.root ?? null;
21
- this.rootMargin = options?.rootMargin ?? '0px';
22
- this.thresholds = Array.isArray(options?.threshold)
23
- ? options.threshold
24
- : [options?.threshold ?? 0];
25
- }
26
-
27
- observe(_target: Element): void {
28
- // Stub
29
- }
30
-
31
- unobserve(_target: Element): void {
32
- // Stub
33
- }
34
-
35
- disconnect(): void {
36
- // Stub
37
- }
38
-
39
- takeRecords(): unknown[] {
40
- return [];
41
- }
42
- }
@@ -1,20 +0,0 @@
1
- // window.location stub for GJS — provides minimal Location-compatible object.
2
- // In GJS apps, there is no browser URL — we use file:// as a reasonable default.
3
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/Location
4
-
5
- export const location = {
6
- href: 'file://',
7
- origin: 'file://',
8
- protocol: 'file:',
9
- host: '',
10
- hostname: '',
11
- port: '',
12
- pathname: '/',
13
- search: '',
14
- hash: '',
15
- assign(_url: string): void {},
16
- replace(_url: string): void {},
17
- reload(): void {},
18
- toString(): string { return this.href; },
19
- ancestorOrigins: { length: 0, item: () => null, contains: () => false, [Symbol.iterator]: function*(): Generator<string> {} },
20
- };
@@ -1,32 +0,0 @@
1
- // matchMedia stub for GJS — used by Excalibur and other libraries to monitor
2
- // devicePixelRatio changes. Returns a minimal MediaQueryList-compatible object.
3
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia
4
- //
5
- // NOTE: imports EventTarget directly from @gjsify/dom-events rather than
6
- // using the global, because dom-elements/register runs BEFORE
7
- // dom-events/register in the inject order — so `globalThis.EventTarget` may
8
- // not yet exist when this class is defined at module load time.
9
-
10
- import { EventTarget } from '@gjsify/dom-events';
11
-
12
- export class MediaQueryList extends EventTarget {
13
- readonly media: string;
14
- readonly matches: boolean;
15
- onchange: ((this: MediaQueryList, ev: unknown) => unknown) | null = null;
16
-
17
- constructor(query: string) {
18
- super();
19
- this.media = query;
20
- this.matches = false;
21
- }
22
-
23
- /** @deprecated Use addEventListener('change', ...) */
24
- addListener(_listener: unknown): void {}
25
-
26
- /** @deprecated Use removeEventListener('change', ...) */
27
- removeListener(_listener: unknown): void {}
28
- }
29
-
30
- export function matchMedia(query: string): MediaQueryList {
31
- return new MediaQueryList(query);
32
- }
@@ -1,39 +0,0 @@
1
- // MutationObserver stub for GJS — original implementation
2
- // Reference: refs/happy-dom/packages/happy-dom/src/mutation-observer/MutationObserver.ts
3
- // Copyright (c) David Ortner (capricorn86). MIT license.
4
- // Modifications: Stub implementation — no actual mutation tracking
5
-
6
- import type { Node } from './node.js';
7
-
8
- interface MutationObserverOptions {
9
- childList?: boolean;
10
- attributes?: boolean;
11
- characterData?: boolean;
12
- subtree?: boolean;
13
- attributeOldValue?: boolean;
14
- characterDataOldValue?: boolean;
15
- attributeFilter?: string[];
16
- }
17
-
18
- /**
19
- * MutationObserver stub.
20
- * Many libraries check for MutationObserver existence; this prevents crashes.
21
- * Does not actually observe DOM mutations (no layout engine).
22
- *
23
- * Reference: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
24
- */
25
- export class MutationObserver {
26
- constructor(_callback: (...args: unknown[]) => void) {}
27
-
28
- observe(_target: Node, _options?: MutationObserverOptions): void {
29
- // Stub — no actual mutation tracking
30
- }
31
-
32
- disconnect(): void {
33
- // Stub
34
- }
35
-
36
- takeRecords(): unknown[] {
37
- return [];
38
- }
39
- }
@@ -1,159 +0,0 @@
1
- // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/named-node-map/NamedNodeMap.ts)
2
- // Copyright (c) David Ortner (capricorn86). MIT license.
3
- // Modifications: Simplified for gjsify — single Map storage, no proxy factory
4
-
5
- import { Attr } from './attr.js';
6
- import { NamespaceURI } from './namespace-uri.js';
7
-
8
- import type { Element } from './element.js';
9
-
10
- /**
11
- * Simplified NamedNodeMap for attribute storage.
12
- *
13
- * Reference: https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap
14
- */
15
- export class NamedNodeMap {
16
- private _items: Attr[] = [];
17
- private _ownerElement: Element;
18
-
19
- constructor(ownerElement: Element) {
20
- this._ownerElement = ownerElement;
21
- }
22
-
23
- get length(): number {
24
- return this._items.length;
25
- }
26
-
27
- item(index: number): Attr | null {
28
- return this._items[index] ?? null;
29
- }
30
-
31
- getNamedItem(qualifiedName: string): Attr | null {
32
- return this._findByName(qualifiedName);
33
- }
34
-
35
- getNamedItemNS(namespace: string | null, localName: string): Attr | null {
36
- const ns = namespace === '' ? null : namespace;
37
- for (const attr of this._items) {
38
- if (attr.namespaceURI === ns && attr.localName === localName) {
39
- return attr;
40
- }
41
- }
42
- return null;
43
- }
44
-
45
- setNamedItem(attr: Attr): Attr | null {
46
- return this._setAttr(attr);
47
- }
48
-
49
- setNamedItemNS(attr: Attr): Attr | null {
50
- return this._setAttr(attr);
51
- }
52
-
53
- removeNamedItem(qualifiedName: string): Attr {
54
- const existing = this._findByName(qualifiedName);
55
- if (!existing) {
56
- throw new DOMException(
57
- `Failed to execute 'removeNamedItem' on 'NamedNodeMap': No item with name '${qualifiedName}' was found.`,
58
- 'NotFoundError',
59
- );
60
- }
61
- this._removeAttr(existing);
62
- return existing;
63
- }
64
-
65
- removeNamedItemNS(namespace: string | null, localName: string): Attr {
66
- const existing = this.getNamedItemNS(namespace, localName);
67
- if (!existing) {
68
- throw new DOMException(
69
- `Failed to execute 'removeNamedItemNS' on 'NamedNodeMap': No item with namespace '${namespace}' and localName '${localName}' was found.`,
70
- 'NotFoundError',
71
- );
72
- }
73
- this._removeAttr(existing);
74
- return existing;
75
- }
76
-
77
- [Symbol.iterator](): IterableIterator<Attr> {
78
- return this._items[Symbol.iterator]();
79
- }
80
-
81
- get [Symbol.toStringTag](): string {
82
- return 'NamedNodeMap';
83
- }
84
-
85
- // -- Internal helpers --
86
-
87
- /** @internal Add or replace an attribute by name. */
88
- _setNamedItem(name: string, value: string, namespaceURI: string | null = null, prefix: string | null = null): void {
89
- const existing = namespaceURI !== null
90
- ? this.getNamedItemNS(namespaceURI, name.includes(':') ? name.split(':')[1] : name)
91
- : this._findByName(name);
92
-
93
- if (existing) {
94
- existing.value = value;
95
- } else {
96
- const attr = new Attr(name, value, namespaceURI, prefix, this._ownerElement);
97
- this._items.push(attr);
98
- }
99
- }
100
-
101
- /** @internal Remove an attribute by name. Returns true if removed. */
102
- _removeNamedItem(name: string): boolean {
103
- const existing = this._findByName(name);
104
- if (existing) {
105
- this._removeAttr(existing);
106
- return true;
107
- }
108
- return false;
109
- }
110
-
111
- /** @internal Remove an attribute by namespace + localName. Returns true if removed. */
112
- _removeNamedItemNS(namespace: string | null, localName: string): boolean {
113
- const existing = this.getNamedItemNS(namespace, localName);
114
- if (existing) {
115
- this._removeAttr(existing);
116
- return true;
117
- }
118
- return false;
119
- }
120
-
121
- private _findByName(qualifiedName: string): Attr | null {
122
- // For HTML elements, attribute names are case-insensitive
123
- const isHTML = this._ownerElement.namespaceURI === NamespaceURI.html;
124
- const searchName = isHTML ? qualifiedName.toLowerCase() : qualifiedName;
125
- for (const attr of this._items) {
126
- const attrName = isHTML ? attr.name.toLowerCase() : attr.name;
127
- if (attrName === searchName) {
128
- return attr;
129
- }
130
- }
131
- return null;
132
- }
133
-
134
- private _setAttr(attr: Attr): Attr | null {
135
- // Find existing by namespace match or by name
136
- let existing: Attr | null = null;
137
- if (attr.namespaceURI !== null) {
138
- existing = this.getNamedItemNS(attr.namespaceURI, attr.localName);
139
- } else {
140
- existing = this._findByName(attr.name);
141
- }
142
-
143
- if (existing) {
144
- const oldAttr = new Attr(existing.name, existing.value, existing.namespaceURI, existing.prefix, existing.ownerElement);
145
- existing.value = attr.value;
146
- return oldAttr;
147
- }
148
-
149
- this._items.push(attr);
150
- return null;
151
- }
152
-
153
- private _removeAttr(attr: Attr): void {
154
- const idx = this._items.indexOf(attr);
155
- if (idx !== -1) {
156
- this._items.splice(idx, 1);
157
- }
158
- }
159
- }
@@ -1,11 +0,0 @@
1
- // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/config/NamespaceURI.ts)
2
- // Copyright (c) David Ortner (capricorn86). MIT license.
3
- // Modifications: Simplified for gjsify — only commonly used namespaces
4
-
5
- export const NamespaceURI = {
6
- html: 'http://www.w3.org/1999/xhtml',
7
- svg: 'http://www.w3.org/2000/svg',
8
- mathML: 'http://www.w3.org/1998/Math/MathML',
9
- xml: 'http://www.w3.org/XML/1998/namespace',
10
- xmlns: 'http://www.w3.org/2000/xmlns/',
11
- } as const;
package/src/node-list.ts DELETED
@@ -1,52 +0,0 @@
1
- // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/nodes/node/NodeList.ts)
2
- // Copyright (c) David Ortner (capricorn86). MIT license.
3
- // Modifications: Simplified for gjsify — minimal live array wrapper
4
-
5
- import type { Node } from './node.js';
6
-
7
- /**
8
- * Minimal NodeList backed by an external array.
9
- *
10
- * Reference: https://developer.mozilla.org/en-US/docs/Web/API/NodeList
11
- */
12
- export class NodeList {
13
- private _items: Node[];
14
-
15
- constructor(items: Node[]) {
16
- this._items = items;
17
- }
18
-
19
- get length(): number {
20
- return this._items.length;
21
- }
22
-
23
- item(index: number): Node | null {
24
- return this._items[index] ?? null;
25
- }
26
-
27
- forEach(callback: (node: Node, index: number, list: NodeList) => void, thisArg?: unknown): void {
28
- for (let i = 0; i < this._items.length; i++) {
29
- callback.call(thisArg, this._items[i], i, this);
30
- }
31
- }
32
-
33
- entries(): IterableIterator<[number, Node]> {
34
- return this._items.entries();
35
- }
36
-
37
- keys(): IterableIterator<number> {
38
- return this._items.keys();
39
- }
40
-
41
- values(): IterableIterator<Node> {
42
- return this._items.values();
43
- }
44
-
45
- [Symbol.iterator](): IterableIterator<Node> {
46
- return this._items[Symbol.iterator]();
47
- }
48
-
49
- get [Symbol.toStringTag](): string {
50
- return 'NodeList';
51
- }
52
- }
package/src/node-type.ts DELETED
@@ -1,14 +0,0 @@
1
- // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/nodes/node/NodeTypeEnum.ts)
2
- // Copyright (c) David Ortner (capricorn86). MIT license.
3
-
4
- export const NodeType = {
5
- ELEMENT_NODE: 1,
6
- ATTRIBUTE_NODE: 2,
7
- TEXT_NODE: 3,
8
- CDATA_SECTION_NODE: 4,
9
- PROCESSING_INSTRUCTION_NODE: 7,
10
- COMMENT_NODE: 8,
11
- DOCUMENT_NODE: 9,
12
- DOCUMENT_TYPE_NODE: 10,
13
- DOCUMENT_FRAGMENT_NODE: 11,
14
- } as const;
package/src/node.ts DELETED
@@ -1,280 +0,0 @@
1
- // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/nodes/node/Node.ts)
2
- // Copyright (c) David Ortner (capricorn86). MIT license.
3
- // Modifications: Simplified for gjsify — no Document, no MutationObserver, no cache system,
4
- // extends @gjsify/dom-events EventTarget
5
-
6
- import { EventTarget, Event as DOMEvent } from '@gjsify/dom-events';
7
-
8
- import { NodeType } from './node-type.js';
9
- import { NodeList } from './node-list.js';
10
- import * as PS from './property-symbol.js';
11
-
12
- /**
13
- * DOM Node base class.
14
- *
15
- * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Node
16
- */
17
- export class Node extends EventTarget {
18
- // Static node type constants
19
- static readonly ELEMENT_NODE = NodeType.ELEMENT_NODE;
20
- static readonly ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE;
21
- static readonly TEXT_NODE = NodeType.TEXT_NODE;
22
- static readonly CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE;
23
- static readonly PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE;
24
- static readonly COMMENT_NODE = NodeType.COMMENT_NODE;
25
- static readonly DOCUMENT_NODE = NodeType.DOCUMENT_NODE;
26
- static readonly DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE;
27
- static readonly DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE;
28
-
29
- // Instance node type constants (mirror static for spec compliance)
30
- readonly ELEMENT_NODE = NodeType.ELEMENT_NODE;
31
- readonly ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE;
32
- readonly TEXT_NODE = NodeType.TEXT_NODE;
33
- readonly CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE;
34
- readonly PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE;
35
- readonly COMMENT_NODE = NodeType.COMMENT_NODE;
36
- readonly DOCUMENT_NODE = NodeType.DOCUMENT_NODE;
37
- readonly DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE;
38
- readonly DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE;
39
-
40
- // DOCUMENT_POSITION constants
41
- static readonly DOCUMENT_POSITION_DISCONNECTED = 0x01;
42
- static readonly DOCUMENT_POSITION_PRECEDING = 0x02;
43
- static readonly DOCUMENT_POSITION_FOLLOWING = 0x04;
44
- static readonly DOCUMENT_POSITION_CONTAINS = 0x08;
45
- static readonly DOCUMENT_POSITION_CONTAINED_BY = 0x10;
46
- static readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 0x20;
47
-
48
- // Internal state
49
- public [PS.nodeType]: number = NodeType.ELEMENT_NODE;
50
- public [PS.parentNode]: Node | null = null;
51
- public [PS.childNodesList]: Node[] = [];
52
- public [PS.elementChildren]: Node[] = [];
53
- public [PS.isConnected] = false;
54
-
55
- get nodeType(): number {
56
- return this[PS.nodeType];
57
- }
58
-
59
- get nodeName(): string {
60
- return '';
61
- }
62
-
63
- get parentNode(): Node | null {
64
- return this[PS.parentNode];
65
- }
66
-
67
- get parentElement(): Node | null {
68
- const parent = this[PS.parentNode];
69
- return parent && parent[PS.nodeType] === NodeType.ELEMENT_NODE ? parent : null;
70
- }
71
-
72
- get childNodes(): NodeList {
73
- return new NodeList(this[PS.childNodesList]);
74
- }
75
-
76
- get firstChild(): Node | null {
77
- return this[PS.childNodesList][0] ?? null;
78
- }
79
-
80
- get lastChild(): Node | null {
81
- const children = this[PS.childNodesList];
82
- return children[children.length - 1] ?? null;
83
- }
84
-
85
- get previousSibling(): Node | null {
86
- const parent = this[PS.parentNode];
87
- if (!parent) return null;
88
- const siblings = parent[PS.childNodesList];
89
- const idx = siblings.indexOf(this);
90
- return idx > 0 ? siblings[idx - 1] : null;
91
- }
92
-
93
- get nextSibling(): Node | null {
94
- const parent = this[PS.parentNode];
95
- if (!parent) return null;
96
- const siblings = parent[PS.childNodesList];
97
- const idx = siblings.indexOf(this);
98
- return idx !== -1 && idx < siblings.length - 1 ? siblings[idx + 1] : null;
99
- }
100
-
101
- get textContent(): string | null {
102
- return null;
103
- }
104
-
105
- set textContent(_value: string | null) {
106
- // Override in subclasses
107
- }
108
-
109
- get nodeValue(): string | null {
110
- return null;
111
- }
112
-
113
- set nodeValue(_value: string | null) {
114
- // Override in subclasses
115
- }
116
-
117
- get ownerDocument(): any {
118
- // Walk up the tree to find the root. If it's the global document, return it.
119
- // Standalone nodes (not in a document tree) return null per W3C spec.
120
- let root: Node = this;
121
- while (root[PS.parentNode]) {
122
- root = root[PS.parentNode]!;
123
- }
124
- const doc = (globalThis as any).document;
125
- return root === doc ? doc : null;
126
- }
127
-
128
- get isConnected(): boolean {
129
- return this[PS.isConnected];
130
- }
131
-
132
- hasChildNodes(): boolean {
133
- return this[PS.childNodesList].length > 0;
134
- }
135
-
136
- contains(other: Node | null): boolean {
137
- if (other === null) return false;
138
- if (other === this) return true;
139
- let node: Node | null = other;
140
- while (node) {
141
- if (node === this) return true;
142
- node = node[PS.parentNode];
143
- }
144
- return false;
145
- }
146
-
147
- getRootNode(): Node {
148
- let node: Node = this;
149
- while (node[PS.parentNode]) {
150
- node = node[PS.parentNode]!;
151
- }
152
- return node;
153
- }
154
-
155
- appendChild(node: Node): Node {
156
- // Remove from previous parent
157
- if (node[PS.parentNode]) {
158
- node[PS.parentNode]!.removeChild(node);
159
- }
160
-
161
- node[PS.parentNode] = this;
162
- this[PS.childNodesList].push(node);
163
-
164
- // Track element children
165
- if (node[PS.nodeType] === NodeType.ELEMENT_NODE) {
166
- this[PS.elementChildren].push(node);
167
- }
168
-
169
- return node;
170
- }
171
-
172
- removeChild(node: Node): Node {
173
- const children = this[PS.childNodesList];
174
- const idx = children.indexOf(node);
175
- if (idx === -1) {
176
- throw new DOMException(
177
- "Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.",
178
- 'NotFoundError',
179
- );
180
- }
181
-
182
- children.splice(idx, 1);
183
- node[PS.parentNode] = null;
184
-
185
- // Remove from element children
186
- if (node[PS.nodeType] === NodeType.ELEMENT_NODE) {
187
- const elemIdx = this[PS.elementChildren].indexOf(node);
188
- if (elemIdx !== -1) {
189
- this[PS.elementChildren].splice(elemIdx, 1);
190
- }
191
- }
192
-
193
- return node;
194
- }
195
-
196
- insertBefore(newNode: Node, referenceNode: Node | null): Node {
197
- if (referenceNode === null) {
198
- return this.appendChild(newNode);
199
- }
200
-
201
- const children = this[PS.childNodesList];
202
- const refIdx = children.indexOf(referenceNode);
203
- if (refIdx === -1) {
204
- throw new DOMException(
205
- "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.",
206
- 'NotFoundError',
207
- );
208
- }
209
-
210
- // Remove from previous parent
211
- if (newNode[PS.parentNode]) {
212
- newNode[PS.parentNode]!.removeChild(newNode);
213
- }
214
-
215
- newNode[PS.parentNode] = this;
216
- children.splice(refIdx, 0, newNode);
217
-
218
- // Track element children
219
- if (newNode[PS.nodeType] === NodeType.ELEMENT_NODE) {
220
- // Find correct position in element children
221
- const elemChildren = this[PS.elementChildren];
222
- let elemIdx = elemChildren.length;
223
- for (let i = refIdx; i < children.length; i++) {
224
- if (children[i][PS.nodeType] === NodeType.ELEMENT_NODE && children[i] !== newNode) {
225
- elemIdx = elemChildren.indexOf(children[i]);
226
- break;
227
- }
228
- }
229
- elemChildren.splice(elemIdx, 0, newNode);
230
- }
231
-
232
- return newNode;
233
- }
234
-
235
- replaceChild(newChild: Node, oldChild: Node): Node {
236
- this.insertBefore(newChild, oldChild);
237
- this.removeChild(oldChild);
238
- return oldChild;
239
- }
240
-
241
- cloneNode(deep = false): Node {
242
- const clone = new (this.constructor as new () => Node)();
243
- clone[PS.nodeType] = this[PS.nodeType];
244
-
245
- if (deep) {
246
- for (const child of this[PS.childNodesList]) {
247
- clone.appendChild(child.cloneNode(true));
248
- }
249
- }
250
-
251
- return clone;
252
- }
253
-
254
- /**
255
- * Override dispatchEvent to support event bubbling through the DOM tree.
256
- * After AT_TARGET phase, if event.bubbles is true, walk up the parentNode
257
- * chain and dispatch on each ancestor (BUBBLING_PHASE).
258
- */
259
- override dispatchEvent(event: DOMEvent): boolean {
260
- const result = super.dispatchEvent(event);
261
-
262
- // Bubble up the DOM tree
263
- if (event.bubbles && !event.cancelBubble) {
264
- let parent = this[PS.parentNode];
265
- while (parent) {
266
- // Dispatch on parent using EventTarget.dispatchEvent (no recursion)
267
- // We call the base class method to avoid infinite bubbling loops
268
- Object.getPrototypeOf(Node.prototype).dispatchEvent.call(parent, event);
269
- if (event.cancelBubble) break;
270
- parent = parent[PS.parentNode];
271
- }
272
- }
273
-
274
- return result;
275
- }
276
-
277
- get [Symbol.toStringTag](): string {
278
- return 'Node';
279
- }
280
- }