@gjsify/dom-elements 0.1.0

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 (84) hide show
  1. package/README.md +31 -0
  2. package/lib/esm/attr.js +31 -0
  3. package/lib/esm/character-data.js +56 -0
  4. package/lib/esm/comment.js +21 -0
  5. package/lib/esm/document-fragment.js +112 -0
  6. package/lib/esm/document.js +83 -0
  7. package/lib/esm/dom-token-list.js +109 -0
  8. package/lib/esm/element.js +237 -0
  9. package/lib/esm/html-canvas-element.js +65 -0
  10. package/lib/esm/html-element.js +346 -0
  11. package/lib/esm/html-image-element.js +184 -0
  12. package/lib/esm/image.js +23 -0
  13. package/lib/esm/index.js +112 -0
  14. package/lib/esm/intersection-observer.js +19 -0
  15. package/lib/esm/mutation-observer.js +14 -0
  16. package/lib/esm/named-node-map.js +124 -0
  17. package/lib/esm/namespace-uri.js +10 -0
  18. package/lib/esm/node-list.js +34 -0
  19. package/lib/esm/node-type.js +14 -0
  20. package/lib/esm/node.js +227 -0
  21. package/lib/esm/property-symbol.js +30 -0
  22. package/lib/esm/resize-observer.js +13 -0
  23. package/lib/esm/text.js +51 -0
  24. package/lib/esm/types/i-html-image-element.js +0 -0
  25. package/lib/esm/types/image-data.js +0 -0
  26. package/lib/esm/types/index.js +3 -0
  27. package/lib/esm/types/predefined-color-space.js +0 -0
  28. package/lib/types/attr.d.ts +22 -0
  29. package/lib/types/character-data.d.ts +24 -0
  30. package/lib/types/comment.d.ts +12 -0
  31. package/lib/types/document-fragment.d.ts +37 -0
  32. package/lib/types/document.d.ts +39 -0
  33. package/lib/types/dom-token-list.d.ts +30 -0
  34. package/lib/types/element.d.ts +58 -0
  35. package/lib/types/html-canvas-element.d.ts +40 -0
  36. package/lib/types/html-element.d.ts +119 -0
  37. package/lib/types/html-image-element.d.ts +65 -0
  38. package/lib/types/image.d.ts +17 -0
  39. package/lib/types/index.d.ts +21 -0
  40. package/lib/types/intersection-observer.d.ts +21 -0
  41. package/lib/types/mutation-observer.d.ts +24 -0
  42. package/lib/types/named-node-map.d.ts +31 -0
  43. package/lib/types/namespace-uri.d.ts +7 -0
  44. package/lib/types/node-list.d.ts +18 -0
  45. package/lib/types/node-type.d.ts +11 -0
  46. package/lib/types/node.d.ts +63 -0
  47. package/lib/types/property-symbol.d.ts +14 -0
  48. package/lib/types/resize-observer.d.ts +13 -0
  49. package/lib/types/text.d.ts +21 -0
  50. package/lib/types/types/i-html-image-element.d.ts +41 -0
  51. package/lib/types/types/image-data.d.ts +11 -0
  52. package/lib/types/types/index.d.ts +3 -0
  53. package/lib/types/types/predefined-color-space.d.ts +1 -0
  54. package/package.json +43 -0
  55. package/src/attr.ts +61 -0
  56. package/src/character-data.ts +79 -0
  57. package/src/comment.ts +31 -0
  58. package/src/document-fragment.ts +137 -0
  59. package/src/document.ts +93 -0
  60. package/src/dom-token-list.ts +140 -0
  61. package/src/element.ts +299 -0
  62. package/src/html-canvas-element.ts +81 -0
  63. package/src/html-element.ts +422 -0
  64. package/src/html-image-element.ts +242 -0
  65. package/src/image.ts +31 -0
  66. package/src/index.spec.ts +897 -0
  67. package/src/index.ts +95 -0
  68. package/src/intersection-observer.ts +42 -0
  69. package/src/mutation-observer.ts +39 -0
  70. package/src/named-node-map.ts +159 -0
  71. package/src/namespace-uri.ts +11 -0
  72. package/src/node-list.ts +52 -0
  73. package/src/node-type.ts +14 -0
  74. package/src/node.ts +250 -0
  75. package/src/property-symbol.ts +23 -0
  76. package/src/resize-observer.ts +28 -0
  77. package/src/test.mts +6 -0
  78. package/src/text.ts +67 -0
  79. package/src/types/i-html-image-element.ts +44 -0
  80. package/src/types/image-data.ts +12 -0
  81. package/src/types/index.ts +3 -0
  82. package/src/types/predefined-color-space.ts +1 -0
  83. package/tsconfig.json +37 -0
  84. package/tsconfig.tsbuildinfo +1 -0
package/src/index.ts ADDED
@@ -0,0 +1,95 @@
1
+ // DOM element hierarchy for GJS — original implementation adapted from happy-dom
2
+ // Copyright (c) David Ortner (capricorn86). MIT license.
3
+
4
+ export { Attr } from './attr.js';
5
+ export { NamedNodeMap } from './named-node-map.js';
6
+ export { NodeList } from './node-list.js';
7
+ export { Node } from './node.js';
8
+ export { CharacterData } from './character-data.js';
9
+ export { Text } from './text.js';
10
+ export { Comment } from './comment.js';
11
+ export { DocumentFragment } from './document-fragment.js';
12
+ export { DOMTokenList } from './dom-token-list.js';
13
+ export { Element } from './element.js';
14
+ export { HTMLElement, CSSStyleDeclaration } from './html-element.js';
15
+ export { HTMLCanvasElement } from './html-canvas-element.js';
16
+ export { HTMLImageElement } from './html-image-element.js';
17
+ export { Image } from './image.js';
18
+ export { Document, document } from './document.js';
19
+ export { MutationObserver } from './mutation-observer.js';
20
+ export { ResizeObserver } from './resize-observer.js';
21
+ export { IntersectionObserver } from './intersection-observer.js';
22
+ export { NodeType } from './node-type.js';
23
+ export { NamespaceURI } from './namespace-uri.js';
24
+ export * as PropertySymbol from './property-symbol.js';
25
+
26
+ // Side-effect: register DOM globals on import.
27
+ // Same pattern as @gjsify/node-globals (packages/node/globals/src/index.ts)
28
+ // and @gjsify/web-globals (packages/web/web-globals/src/index.ts).
29
+ import { Text } from './text.js';
30
+ import { Comment } from './comment.js';
31
+ import { DocumentFragment } from './document-fragment.js';
32
+ import { DOMTokenList } from './dom-token-list.js';
33
+ import { HTMLCanvasElement } from './html-canvas-element.js';
34
+ import { HTMLImageElement } from './html-image-element.js';
35
+ import { Image } from './image.js';
36
+ import { document } from './document.js';
37
+ import { MutationObserver } from './mutation-observer.js';
38
+ import { ResizeObserver } from './resize-observer.js';
39
+ import { IntersectionObserver } from './intersection-observer.js';
40
+
41
+ Object.defineProperty(globalThis, 'Text', {
42
+ value: Text,
43
+ writable: true,
44
+ configurable: true,
45
+ });
46
+ Object.defineProperty(globalThis, 'Comment', {
47
+ value: Comment,
48
+ writable: true,
49
+ configurable: true,
50
+ });
51
+ Object.defineProperty(globalThis, 'DocumentFragment', {
52
+ value: DocumentFragment,
53
+ writable: true,
54
+ configurable: true,
55
+ });
56
+ Object.defineProperty(globalThis, 'DOMTokenList', {
57
+ value: DOMTokenList,
58
+ writable: true,
59
+ configurable: true,
60
+ });
61
+ Object.defineProperty(globalThis, 'HTMLCanvasElement', {
62
+ value: HTMLCanvasElement,
63
+ writable: true,
64
+ configurable: true,
65
+ });
66
+ Object.defineProperty(globalThis, 'HTMLImageElement', {
67
+ value: HTMLImageElement,
68
+ writable: true,
69
+ configurable: true,
70
+ });
71
+ Object.defineProperty(globalThis, 'Image', {
72
+ value: Image,
73
+ writable: true,
74
+ configurable: true,
75
+ });
76
+ Object.defineProperty(globalThis, 'document', {
77
+ value: document,
78
+ writable: true,
79
+ configurable: true,
80
+ });
81
+ Object.defineProperty(globalThis, 'MutationObserver', {
82
+ value: MutationObserver,
83
+ writable: true,
84
+ configurable: true,
85
+ });
86
+ Object.defineProperty(globalThis, 'ResizeObserver', {
87
+ value: ResizeObserver,
88
+ writable: true,
89
+ configurable: true,
90
+ });
91
+ Object.defineProperty(globalThis, 'IntersectionObserver', {
92
+ value: IntersectionObserver,
93
+ writable: true,
94
+ configurable: true,
95
+ });
@@ -0,0 +1,42 @@
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
+ }
@@ -0,0 +1,39 @@
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
+ }
@@ -0,0 +1,159 @@
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
+ }
@@ -0,0 +1,11 @@
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;
@@ -0,0 +1,52 @@
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
+ }
@@ -0,0 +1,14 @@
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 ADDED
@@ -0,0 +1,250 @@
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 } 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(): null {
118
+ return null;
119
+ }
120
+
121
+ get isConnected(): boolean {
122
+ return this[PS.isConnected];
123
+ }
124
+
125
+ hasChildNodes(): boolean {
126
+ return this[PS.childNodesList].length > 0;
127
+ }
128
+
129
+ contains(other: Node | null): boolean {
130
+ if (other === null) return false;
131
+ if (other === this) return true;
132
+ let node: Node | null = other;
133
+ while (node) {
134
+ if (node === this) return true;
135
+ node = node[PS.parentNode];
136
+ }
137
+ return false;
138
+ }
139
+
140
+ getRootNode(): Node {
141
+ let node: Node = this;
142
+ while (node[PS.parentNode]) {
143
+ node = node[PS.parentNode]!;
144
+ }
145
+ return node;
146
+ }
147
+
148
+ appendChild(node: Node): Node {
149
+ // Remove from previous parent
150
+ if (node[PS.parentNode]) {
151
+ node[PS.parentNode]!.removeChild(node);
152
+ }
153
+
154
+ node[PS.parentNode] = this;
155
+ this[PS.childNodesList].push(node);
156
+
157
+ // Track element children
158
+ if (node[PS.nodeType] === NodeType.ELEMENT_NODE) {
159
+ this[PS.elementChildren].push(node);
160
+ }
161
+
162
+ return node;
163
+ }
164
+
165
+ removeChild(node: Node): Node {
166
+ const children = this[PS.childNodesList];
167
+ const idx = children.indexOf(node);
168
+ if (idx === -1) {
169
+ throw new DOMException(
170
+ "Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.",
171
+ 'NotFoundError',
172
+ );
173
+ }
174
+
175
+ children.splice(idx, 1);
176
+ node[PS.parentNode] = null;
177
+
178
+ // Remove from element children
179
+ if (node[PS.nodeType] === NodeType.ELEMENT_NODE) {
180
+ const elemIdx = this[PS.elementChildren].indexOf(node);
181
+ if (elemIdx !== -1) {
182
+ this[PS.elementChildren].splice(elemIdx, 1);
183
+ }
184
+ }
185
+
186
+ return node;
187
+ }
188
+
189
+ insertBefore(newNode: Node, referenceNode: Node | null): Node {
190
+ if (referenceNode === null) {
191
+ return this.appendChild(newNode);
192
+ }
193
+
194
+ const children = this[PS.childNodesList];
195
+ const refIdx = children.indexOf(referenceNode);
196
+ if (refIdx === -1) {
197
+ throw new DOMException(
198
+ "Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.",
199
+ 'NotFoundError',
200
+ );
201
+ }
202
+
203
+ // Remove from previous parent
204
+ if (newNode[PS.parentNode]) {
205
+ newNode[PS.parentNode]!.removeChild(newNode);
206
+ }
207
+
208
+ newNode[PS.parentNode] = this;
209
+ children.splice(refIdx, 0, newNode);
210
+
211
+ // Track element children
212
+ if (newNode[PS.nodeType] === NodeType.ELEMENT_NODE) {
213
+ // Find correct position in element children
214
+ const elemChildren = this[PS.elementChildren];
215
+ let elemIdx = elemChildren.length;
216
+ for (let i = refIdx; i < children.length; i++) {
217
+ if (children[i][PS.nodeType] === NodeType.ELEMENT_NODE && children[i] !== newNode) {
218
+ elemIdx = elemChildren.indexOf(children[i]);
219
+ break;
220
+ }
221
+ }
222
+ elemChildren.splice(elemIdx, 0, newNode);
223
+ }
224
+
225
+ return newNode;
226
+ }
227
+
228
+ replaceChild(newChild: Node, oldChild: Node): Node {
229
+ this.insertBefore(newChild, oldChild);
230
+ this.removeChild(oldChild);
231
+ return oldChild;
232
+ }
233
+
234
+ cloneNode(deep = false): Node {
235
+ const clone = new (this.constructor as new () => Node)();
236
+ clone[PS.nodeType] = this[PS.nodeType];
237
+
238
+ if (deep) {
239
+ for (const child of this[PS.childNodesList]) {
240
+ clone.appendChild(child.cloneNode(true));
241
+ }
242
+ }
243
+
244
+ return clone;
245
+ }
246
+
247
+ get [Symbol.toStringTag](): string {
248
+ return 'Node';
249
+ }
250
+ }
@@ -0,0 +1,23 @@
1
+ // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/PropertySymbol.ts)
2
+ // Copyright (c) David Ortner (capricorn86). MIT license.
3
+ // Modifications: Minimal subset for gjsify — only symbols needed by Node/Element/HTMLElement
4
+
5
+ // Node
6
+ export const nodeType = Symbol('nodeType');
7
+ export const parentNode = Symbol('parentNode');
8
+ export const childNodesList = Symbol('childNodesList');
9
+ export const elementChildren = Symbol('elementChildren');
10
+ export const isConnected = Symbol('isConnected');
11
+
12
+ // Element
13
+ export const tagName = Symbol('tagName');
14
+ export const localName = Symbol('localName');
15
+ export const namespaceURI = Symbol('namespaceURI');
16
+ export const prefix = Symbol('prefix');
17
+ export const attributes = Symbol('attributes');
18
+ export const propertyEventListeners = Symbol('propertyEventListeners');
19
+
20
+ // Attr
21
+ export const name = Symbol('name');
22
+ export const value = Symbol('value');
23
+ export const ownerElement = Symbol('ownerElement');