@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/element.ts ADDED
@@ -0,0 +1,299 @@
1
+ // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/nodes/element/Element.ts)
2
+ // Copyright (c) David Ortner (capricorn86). MIT license.
3
+ // Modifications: Simplified for gjsify — no innerHTML/outerHTML, no querySelector/CSS selectors,
4
+ // no Shadow DOM, no classList/DOMTokenList, no computed styles
5
+
6
+ import { Event } from '@gjsify/dom-events';
7
+
8
+ import { Node } from './node.js';
9
+ import { NodeType } from './node-type.js';
10
+ import { NamedNodeMap } from './named-node-map.js';
11
+ import { NamespaceURI } from './namespace-uri.js';
12
+ import * as PS from './property-symbol.js';
13
+
14
+ /**
15
+ * DOM Element class.
16
+ *
17
+ * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Element
18
+ */
19
+ export class Element extends Node {
20
+ public [PS.tagName]: string = '';
21
+ public [PS.localName]: string = '';
22
+ public [PS.namespaceURI]: string | null = NamespaceURI.html;
23
+ public [PS.prefix]: string | null = null;
24
+ public [PS.attributes]: NamedNodeMap = new NamedNodeMap(this);
25
+ public [PS.propertyEventListeners]: Map<string, ((event: Event) => void) | null> = new Map();
26
+
27
+ constructor() {
28
+ super();
29
+ this[PS.nodeType] = NodeType.ELEMENT_NODE;
30
+ }
31
+
32
+ get tagName(): string {
33
+ return this[PS.tagName];
34
+ }
35
+
36
+ get localName(): string {
37
+ return this[PS.localName];
38
+ }
39
+
40
+ get namespaceURI(): string | null {
41
+ return this[PS.namespaceURI];
42
+ }
43
+
44
+ get prefix(): string | null {
45
+ return this[PS.prefix];
46
+ }
47
+
48
+ get nodeName(): string {
49
+ return this[PS.tagName];
50
+ }
51
+
52
+ get attributes(): NamedNodeMap {
53
+ return this[PS.attributes];
54
+ }
55
+
56
+ get id(): string {
57
+ return this.getAttribute('id') ?? '';
58
+ }
59
+
60
+ set id(value: string) {
61
+ this.setAttribute('id', value);
62
+ }
63
+
64
+ get className(): string {
65
+ return this.getAttribute('class') ?? '';
66
+ }
67
+
68
+ set className(value: string) {
69
+ this.setAttribute('class', value);
70
+ }
71
+
72
+ get children(): Element[] {
73
+ return this[PS.elementChildren] as Element[];
74
+ }
75
+
76
+ get childElementCount(): number {
77
+ return this[PS.elementChildren].length;
78
+ }
79
+
80
+ get firstElementChild(): Element | null {
81
+ return (this[PS.elementChildren][0] as Element) ?? null;
82
+ }
83
+
84
+ get lastElementChild(): Element | null {
85
+ const children = this[PS.elementChildren];
86
+ return (children[children.length - 1] as Element) ?? null;
87
+ }
88
+
89
+ get previousElementSibling(): Element | null {
90
+ const parent = this[PS.parentNode];
91
+ if (!parent) return null;
92
+ const siblings = parent[PS.elementChildren];
93
+ const idx = siblings.indexOf(this);
94
+ return idx > 0 ? (siblings[idx - 1] as Element) : null;
95
+ }
96
+
97
+ get nextElementSibling(): Element | null {
98
+ const parent = this[PS.parentNode];
99
+ if (!parent) return null;
100
+ const siblings = parent[PS.elementChildren];
101
+ const idx = siblings.indexOf(this);
102
+ return idx !== -1 && idx < siblings.length - 1 ? (siblings[idx + 1] as Element) : null;
103
+ }
104
+
105
+ get textContent(): string {
106
+ let text = '';
107
+ for (const child of this[PS.childNodesList]) {
108
+ if (child.textContent !== null) {
109
+ text += child.textContent;
110
+ }
111
+ }
112
+ return text;
113
+ }
114
+
115
+ set textContent(_value: string | null) {
116
+ // Remove all children
117
+ const children = this[PS.childNodesList];
118
+ while (children.length > 0) {
119
+ this.removeChild(children[0]);
120
+ }
121
+ }
122
+
123
+ // -- Attribute methods --
124
+
125
+ getAttribute(qualifiedName: string): string | null {
126
+ const attr = this[PS.attributes].getNamedItem(qualifiedName);
127
+ return attr ? attr.value : null;
128
+ }
129
+
130
+ getAttributeNS(namespace: string | null, localName: string): string | null {
131
+ const attr = this[PS.attributes].getNamedItemNS(namespace, localName);
132
+ return attr ? attr.value : null;
133
+ }
134
+
135
+ setAttribute(qualifiedName: string, value: string): void {
136
+ this[PS.attributes]._setNamedItem(qualifiedName, String(value));
137
+ }
138
+
139
+ setAttributeNS(namespace: string | null, qualifiedName: string, value: string): void {
140
+ const ns = namespace === '' ? null : namespace;
141
+ const parts = qualifiedName.split(':');
142
+ const prefix = parts.length > 1 ? parts[0] : null;
143
+ this[PS.attributes]._setNamedItem(qualifiedName, String(value), ns, prefix);
144
+ }
145
+
146
+ removeAttribute(qualifiedName: string): void {
147
+ this[PS.attributes]._removeNamedItem(qualifiedName);
148
+ }
149
+
150
+ removeAttributeNS(namespace: string | null, localName: string): void {
151
+ const ns = namespace === '' ? null : namespace;
152
+ this[PS.attributes]._removeNamedItemNS(ns, localName);
153
+ }
154
+
155
+ hasAttribute(qualifiedName: string): boolean {
156
+ return this[PS.attributes].getNamedItem(qualifiedName) !== null;
157
+ }
158
+
159
+ hasAttributeNS(namespace: string | null, localName: string): boolean {
160
+ return this[PS.attributes].getNamedItemNS(namespace, localName) !== null;
161
+ }
162
+
163
+ getAttributeNode(qualifiedName: string): unknown {
164
+ return this[PS.attributes].getNamedItem(qualifiedName);
165
+ }
166
+
167
+ setAttributeNode(attr: unknown): unknown {
168
+ return this[PS.attributes].setNamedItem(attr as any);
169
+ }
170
+
171
+ removeAttributeNode(attr: unknown): unknown {
172
+ const existing = this[PS.attributes].getNamedItem((attr as any).name);
173
+ if (!existing) {
174
+ throw new DOMException(
175
+ "Failed to execute 'removeAttributeNode' on 'Element': The attribute is not owned by this element.",
176
+ 'NotFoundError',
177
+ );
178
+ }
179
+ this[PS.attributes].removeNamedItem(existing.name);
180
+ return existing;
181
+ }
182
+
183
+ toggleAttribute(qualifiedName: string, force?: boolean): boolean {
184
+ if (force !== undefined) {
185
+ if (force) {
186
+ this.setAttribute(qualifiedName, '');
187
+ return true;
188
+ }
189
+ this.removeAttribute(qualifiedName);
190
+ return false;
191
+ }
192
+ if (this.hasAttribute(qualifiedName)) {
193
+ this.removeAttribute(qualifiedName);
194
+ return false;
195
+ }
196
+ this.setAttribute(qualifiedName, '');
197
+ return true;
198
+ }
199
+
200
+ hasAttributes(): boolean {
201
+ return this[PS.attributes].length > 0;
202
+ }
203
+
204
+ // -- Override dispatchEvent to call on* property handlers --
205
+
206
+ dispatchEvent(event: Event): boolean {
207
+ const result = super.dispatchEvent(event);
208
+
209
+ // Call on<type> property handler if registered
210
+ const handler = this[PS.propertyEventListeners].get('on' + event.type);
211
+ if (typeof handler === 'function') {
212
+ handler.call(this, event);
213
+ }
214
+
215
+ return result;
216
+ }
217
+
218
+ // -- Stubs for commonly expected methods --
219
+
220
+ querySelector(_selectors: string): Element | null {
221
+ return null;
222
+ }
223
+
224
+ querySelectorAll(_selectors: string): Element[] {
225
+ return [];
226
+ }
227
+
228
+ matches(_selectors: string): boolean {
229
+ return false;
230
+ }
231
+
232
+ closest(_selectors: string): Element | null {
233
+ return null;
234
+ }
235
+
236
+ getElementsByTagName(tagName: string): Element[] {
237
+ const results: Element[] = [];
238
+ const upperTag = tagName.toUpperCase();
239
+ const walk = (node: Node): void => {
240
+ for (const child of node[PS.childNodesList]) {
241
+ if (child[PS.nodeType] === NodeType.ELEMENT_NODE) {
242
+ const el = child as Element;
243
+ if (tagName === '*' || el[PS.tagName] === upperTag) {
244
+ results.push(el);
245
+ }
246
+ walk(el);
247
+ }
248
+ }
249
+ };
250
+ walk(this);
251
+ return results;
252
+ }
253
+
254
+ getElementsByClassName(className: string): Element[] {
255
+ const results: Element[] = [];
256
+ const targetClasses = className.split(/\s+/).filter(Boolean);
257
+ const walk = (node: Node): void => {
258
+ for (const child of node[PS.childNodesList]) {
259
+ if (child[PS.nodeType] === NodeType.ELEMENT_NODE) {
260
+ const el = child as Element;
261
+ const elClasses = el.className.split(/\s+/);
262
+ if (targetClasses.every(c => elClasses.includes(c))) {
263
+ results.push(el);
264
+ }
265
+ walk(el);
266
+ }
267
+ }
268
+ };
269
+ walk(this);
270
+ return results;
271
+ }
272
+
273
+ // -- Clone --
274
+
275
+ cloneNode(deep = false): Element {
276
+ const clone = super.cloneNode(false) as Element;
277
+ clone[PS.tagName] = this[PS.tagName];
278
+ clone[PS.localName] = this[PS.localName];
279
+ clone[PS.namespaceURI] = this[PS.namespaceURI];
280
+ clone[PS.prefix] = this[PS.prefix];
281
+
282
+ // Clone attributes
283
+ for (const attr of this[PS.attributes]) {
284
+ clone.setAttributeNS(attr.namespaceURI, attr.name, attr.value);
285
+ }
286
+
287
+ if (deep) {
288
+ for (const child of this[PS.childNodesList]) {
289
+ clone.appendChild(child.cloneNode(true));
290
+ }
291
+ }
292
+
293
+ return clone;
294
+ }
295
+
296
+ get [Symbol.toStringTag](): string {
297
+ return 'Element';
298
+ }
299
+ }
@@ -0,0 +1,81 @@
1
+ // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/nodes/html-canvas-element/HTMLCanvasElement.ts)
2
+ // Copyright (c) David Ortner (capricorn86). MIT license.
3
+ // Modifications: Simplified for gjsify — stubs only, no window reference, no MediaStream/OffscreenCanvas deps.
4
+ // getContext() overridden in @gjsify/webgl with GTK.GLArea-backed implementation.
5
+
6
+ import { HTMLElement } from './html-element.js';
7
+
8
+ /**
9
+ * HTMLCanvasElement base class.
10
+ *
11
+ * This is a DOM-spec-compliant stub. The GTK-backed implementation lives in
12
+ * `@gjsify/webgl` and extends this class, overriding `getContext()`.
13
+ *
14
+ * Reference: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement
15
+ */
16
+ export class HTMLCanvasElement extends HTMLElement {
17
+ // Context factory registry — packages register their context types here.
18
+ // e.g. @gjsify/canvas2d registers '2d', @gjsify/webgl registers 'webgl'.
19
+ private static _contextFactories = new Map<string, (canvas: HTMLCanvasElement, options?: any) => any>();
20
+
21
+ /**
22
+ * Register a rendering context factory for a given context type.
23
+ * Called by packages like @gjsify/canvas2d and @gjsify/webgl to plug in their implementations.
24
+ */
25
+ static registerContextFactory(contextId: string, factory: (canvas: HTMLCanvasElement, options?: any) => any): void {
26
+ HTMLCanvasElement._contextFactories.set(contextId, factory);
27
+ }
28
+
29
+ // WebGL context event handlers
30
+ oncontextlost: ((ev: Event) => any) | null = null;
31
+ oncontextrestored: ((ev: Event) => any) | null = null;
32
+ onwebglcontextcreationerror: ((ev: Event) => any) | null = null;
33
+ onwebglcontextlost: ((ev: Event) => any) | null = null;
34
+ onwebglcontextrestored: ((ev: Event) => any) | null = null;
35
+
36
+ /** Returns the width of the canvas element. Default: 300. */
37
+ get width(): number {
38
+ const w = this.getAttribute('width');
39
+ return w !== null ? Number(w) : 300;
40
+ }
41
+
42
+ set width(value: number) {
43
+ this.setAttribute('width', String(value));
44
+ }
45
+
46
+ /** Returns the height of the canvas element. Default: 150. */
47
+ get height(): number {
48
+ const h = this.getAttribute('height');
49
+ return h !== null ? Number(h) : 150;
50
+ }
51
+
52
+ set height(value: number) {
53
+ this.setAttribute('height', String(value));
54
+ }
55
+
56
+ /**
57
+ * Returns a rendering context.
58
+ * Checks the static context factory registry for a matching factory.
59
+ * Subclasses (e.g. @gjsify/webgl) may override and fall through via super.getContext().
60
+ */
61
+ getContext(contextId: string, options?: any): any {
62
+ const factory = HTMLCanvasElement._contextFactories.get(contextId);
63
+ if (factory) return factory(this, options);
64
+ return null;
65
+ }
66
+
67
+ /** Returns a data URL representing the canvas image. Stub — returns empty string. */
68
+ toDataURL(_type?: string, _quality?: any): string {
69
+ return '';
70
+ }
71
+
72
+ /** Converts the canvas to a Blob and passes it to the callback. Stub — returns empty Blob. */
73
+ toBlob(callback: ((blob: Blob | null) => void), _type?: string, _quality?: any): void {
74
+ callback(new Blob([]));
75
+ }
76
+
77
+ /** Returns a MediaStream capturing the canvas. Stub — returns empty object. */
78
+ captureStream(_frameRequestRate?: number): any {
79
+ return {};
80
+ }
81
+ }