@gjsify/dom-elements 0.4.0 → 0.4.4

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 (53) hide show
  1. package/lib/types/location-stub.d.ts +1 -1
  2. package/package.json +78 -75
  3. package/src/attr.ts +0 -61
  4. package/src/character-data.ts +0 -79
  5. package/src/comment.ts +0 -31
  6. package/src/document-fragment.ts +0 -137
  7. package/src/document.ts +0 -103
  8. package/src/dom-matrix.ts +0 -109
  9. package/src/dom-token-list.ts +0 -140
  10. package/src/element.ts +0 -316
  11. package/src/font-face.ts +0 -97
  12. package/src/gst-time.ts +0 -26
  13. package/src/html-canvas-element.ts +0 -90
  14. package/src/html-element.ts +0 -502
  15. package/src/html-image-element.spec.ts +0 -285
  16. package/src/html-image-element.ts +0 -295
  17. package/src/html-media-element.ts +0 -123
  18. package/src/html-video-element.ts +0 -143
  19. package/src/image.ts +0 -31
  20. package/src/index.spec.ts +0 -914
  21. package/src/index.ts +0 -39
  22. package/src/intersection-observer.ts +0 -42
  23. package/src/location-stub.ts +0 -20
  24. package/src/match-media.ts +0 -32
  25. package/src/mutation-observer.ts +0 -39
  26. package/src/named-node-map.ts +0 -159
  27. package/src/namespace-uri.ts +0 -11
  28. package/src/node-list.ts +0 -52
  29. package/src/node-type.ts +0 -14
  30. package/src/node.ts +0 -280
  31. package/src/property-symbol.ts +0 -23
  32. package/src/register/canvas.ts +0 -23
  33. package/src/register/document.ts +0 -64
  34. package/src/register/font-face.ts +0 -18
  35. package/src/register/helpers.ts +0 -15
  36. package/src/register/image.ts +0 -8
  37. package/src/register/location.ts +0 -6
  38. package/src/register/match-media.ts +0 -6
  39. package/src/register/navigator.ts +0 -6
  40. package/src/register/observers.ts +0 -10
  41. package/src/register.spec.ts +0 -136
  42. package/src/register.ts +0 -13
  43. package/src/resize-observer.ts +0 -28
  44. package/src/stubs.spec.ts +0 -284
  45. package/src/test.browser.mts +0 -686
  46. package/src/test.mts +0 -9
  47. package/src/text.ts +0 -67
  48. package/src/types/i-html-image-element.ts +0 -44
  49. package/src/types/image-data.ts +0 -12
  50. package/src/types/index.ts +0 -3
  51. package/src/types/predefined-color-space.ts +0 -1
  52. package/tsconfig.json +0 -37
  53. package/tsconfig.tsbuildinfo +0 -1
@@ -1,140 +0,0 @@
1
- // Adapted from happy-dom (refs/happy-dom/packages/happy-dom/src/dom/DOMTokenList.ts)
2
- // Copyright (c) David Ortner (capricorn86). MIT license.
3
- // Modifications: Simplified for gjsify — no Proxy, plain array-based implementation
4
-
5
- import type { Element } from './element.js';
6
-
7
- /**
8
- * DOMTokenList — manages a set of space-separated tokens on an attribute.
9
- *
10
- * Reference: https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList
11
- */
12
- export class DOMTokenList {
13
- private _ownerElement: Element;
14
- private _attributeName: string;
15
-
16
- constructor(ownerElement: Element, attributeName: string) {
17
- this._ownerElement = ownerElement;
18
- this._attributeName = attributeName;
19
- }
20
-
21
- private _getTokens(): string[] {
22
- const value = this._ownerElement.getAttribute(this._attributeName);
23
- if (!value) return [];
24
- return value.split(/\s+/).filter(t => t.length > 0);
25
- }
26
-
27
- private _setTokens(tokens: string[]): void {
28
- const value = tokens.join(' ');
29
- if (value) {
30
- this._ownerElement.setAttribute(this._attributeName, value);
31
- } else {
32
- this._ownerElement.removeAttribute(this._attributeName);
33
- }
34
- }
35
-
36
- get length(): number {
37
- return this._getTokens().length;
38
- }
39
-
40
- get value(): string {
41
- return this._ownerElement.getAttribute(this._attributeName) ?? '';
42
- }
43
-
44
- set value(val: string) {
45
- if (val) {
46
- this._ownerElement.setAttribute(this._attributeName, val);
47
- } else {
48
- this._ownerElement.removeAttribute(this._attributeName);
49
- }
50
- }
51
-
52
- item(index: number): string | null {
53
- const tokens = this._getTokens();
54
- return index >= 0 && index < tokens.length ? tokens[index] : null;
55
- }
56
-
57
- contains(token: string): boolean {
58
- return this._getTokens().includes(token);
59
- }
60
-
61
- add(...tokens: string[]): void {
62
- const current = this._getTokens();
63
- for (const token of tokens) {
64
- if (token && !current.includes(token)) {
65
- current.push(token);
66
- }
67
- }
68
- this._setTokens(current);
69
- }
70
-
71
- remove(...tokens: string[]): void {
72
- const current = this._getTokens().filter(t => !tokens.includes(t));
73
- this._setTokens(current);
74
- }
75
-
76
- toggle(token: string, force?: boolean): boolean {
77
- const has = this.contains(token);
78
- if (force !== undefined) {
79
- if (force) {
80
- this.add(token);
81
- return true;
82
- } else {
83
- this.remove(token);
84
- return false;
85
- }
86
- }
87
- if (has) {
88
- this.remove(token);
89
- return false;
90
- } else {
91
- this.add(token);
92
- return true;
93
- }
94
- }
95
-
96
- replace(token: string, newToken: string): boolean {
97
- const tokens = this._getTokens();
98
- const idx = tokens.indexOf(token);
99
- if (idx === -1) return false;
100
- tokens[idx] = newToken;
101
- this._setTokens(tokens);
102
- return true;
103
- }
104
-
105
- supports(_token: string): boolean {
106
- // No validation — all tokens are supported
107
- return true;
108
- }
109
-
110
- forEach(callback: (value: string, index: number, list: DOMTokenList) => void): void {
111
- const tokens = this._getTokens();
112
- for (let i = 0; i < tokens.length; i++) {
113
- callback(tokens[i], i, this);
114
- }
115
- }
116
-
117
- keys(): IterableIterator<number> {
118
- return this._getTokens().keys();
119
- }
120
-
121
- values(): IterableIterator<string> {
122
- return this._getTokens().values();
123
- }
124
-
125
- entries(): IterableIterator<[number, string]> {
126
- return this._getTokens().entries();
127
- }
128
-
129
- [Symbol.iterator](): IterableIterator<string> {
130
- return this._getTokens().values();
131
- }
132
-
133
- toString(): string {
134
- return this.value;
135
- }
136
-
137
- get [Symbol.toStringTag](): string {
138
- return 'DOMTokenList';
139
- }
140
- }
package/src/element.ts DELETED
@@ -1,316 +0,0 @@
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
- // -- Pointer capture (no-op stubs, GTK tracks pointer implicitly) --
297
- // Reference: refs/happy-dom/packages/happy-dom/src/nodes/element/Element.ts
298
-
299
- private _pointerCaptures = new Set<number>();
300
-
301
- setPointerCapture(pointerId: number): void {
302
- this._pointerCaptures.add(pointerId);
303
- }
304
-
305
- releasePointerCapture(pointerId: number): void {
306
- this._pointerCaptures.delete(pointerId);
307
- }
308
-
309
- hasPointerCapture(pointerId: number): boolean {
310
- return this._pointerCaptures.has(pointerId);
311
- }
312
-
313
- get [Symbol.toStringTag](): string {
314
- return 'Element';
315
- }
316
- }
package/src/font-face.ts DELETED
@@ -1,97 +0,0 @@
1
- // FontFace implementation for GJS — registers custom TTF fonts in PangoCairo
2
- // so that Canvas2D fillText uses the correct font family.
3
- //
4
- // Flow (Excalibur): XHR(blob) → /tmp/gjsify-blob-N.ttf → createObjectURL →
5
- // new FontFace(family, 'url(file:///tmp/gjsify-blob-N.ttf)') → face.load()
6
- // The file is already on disk when load() is called; we just tell PangoCairo
7
- // about it via font_map_get_default().add_font_file(path).
8
- //
9
- // Node.js: dynamic gi:// import fails gracefully → status='loaded' still set.
10
- // Reference: https://developer.mozilla.org/en-US/docs/Web/API/FontFace
11
-
12
- export class FontFace {
13
- readonly family: string;
14
- readonly source: string;
15
- status: 'unloaded' | 'loading' | 'loaded' | 'error' = 'unloaded';
16
- loaded: Promise<FontFace>;
17
- display = 'auto';
18
- style = 'normal';
19
- weight = 'normal';
20
- stretch = 'normal';
21
- unicodeRange = 'U+0-10FFFF';
22
- variant = 'normal';
23
- featureSettings = 'normal';
24
-
25
- constructor(family: string, source: string | ArrayBuffer | ArrayBufferView, _descriptors?: Record<string, string>) {
26
- this.family = family;
27
- this.source = typeof source === 'string' ? source : '[binary]';
28
- this.loaded = Promise.resolve(this);
29
- }
30
-
31
- // Parses: url(file:///path), url("file:///path"), url('file:///path')
32
- private _extractFilePath(): string | null {
33
- const m = this.source.match(/url\s*\(\s*["']?(file:\/\/\/[^"')]+)["']?\s*\)/i);
34
- if (!m) return null;
35
- // Strip file:// prefix → /path/to/file.ttf
36
- return m[1].replace(/^file:\/\//, '');
37
- }
38
-
39
- async load(): Promise<FontFace> {
40
- this.status = 'loading';
41
- const filePath = this._extractFilePath();
42
- if (filePath) {
43
- try {
44
- // gi:// only available in GJS — fails gracefully on Node.js
45
- // @ts-ignore
46
- const { default: PangoCairo } = await import('gi://PangoCairo?version=1.0');
47
- PangoCairo.font_map_get_default().add_font_file(filePath);
48
- } catch {
49
- // Not in GJS, or font file not found — fall through to loaded
50
- // Canvas fillText will use system font fallback (acceptable)
51
- }
52
- }
53
- this.status = 'loaded';
54
- return this;
55
- }
56
- }
57
-
58
- /**
59
- * FontFaceSet — tracks loaded FontFace objects and exposes them to consumers.
60
- *
61
- * Intentionally does NOT extend EventTarget. The dom-elements /register module
62
- * runs before dom-events/register in the inject order, so EventTarget may not
63
- * yet exist when this class is defined at module load time. All event methods
64
- * are provided as no-ops; consumers that call addEventListener('loadingdone')
65
- * etc. will silently receive nothing.
66
- */
67
- export class FontFaceSet {
68
- status: 'loading' | 'loaded' = 'loaded';
69
- ready: Promise<FontFaceSet> = Promise.resolve(this);
70
-
71
- private _faces = new Set<FontFace>();
72
-
73
- addEventListener(_type: string, _listener: unknown): void {}
74
- removeEventListener(_type: string, _listener: unknown): void {}
75
- dispatchEvent(_event: unknown): boolean { return true; }
76
-
77
- add(face: FontFace): FontFaceSet {
78
- this._faces.add(face);
79
- return this;
80
- }
81
- delete(face: FontFace): boolean { return this._faces.delete(face); }
82
- clear(): void { this._faces.clear(); }
83
- has(face: FontFace): boolean { return this._faces.has(face); }
84
- check(_font: string, _text?: string): boolean { return false; }
85
- load(_font: string, _text?: string): Promise<FontFace[]> { return Promise.resolve([]); }
86
- forEach(callback: (value: FontFace, key: FontFace, parent: FontFaceSet) => void): void {
87
- this._faces.forEach(f => callback(f, f, this));
88
- }
89
- values(): IterableIterator<FontFace> { return this._faces.values(); }
90
- keys(): IterableIterator<FontFace> { return this._faces.values(); }
91
- entries(): IterableIterator<[FontFace, FontFace]> {
92
- const faces = Array.from(this._faces);
93
- return faces.map(f => [f, f] as [FontFace, FontFace])[Symbol.iterator]() as IterableIterator<[FontFace, FontFace]>;
94
- }
95
- [Symbol.iterator](): Iterator<FontFace> { return this._faces[Symbol.iterator](); }
96
- get size(): number { return this._faces.size; }
97
- }
package/src/gst-time.ts DELETED
@@ -1,26 +0,0 @@
1
- // Conversions between seconds (Web video API) and GStreamer's nanosecond
2
- // `BigInt` timebase (used by `Gst.Format.TIME` throughout GStreamer). Lives
3
- // in @gjsify/dom-elements so HTMLVideoElement/HTMLAudioElement can use it
4
- // directly; @gjsify/video re-exports these for consumers of the bridge
5
- // package. Kept as pure number math — no runtime Gst import required.
6
-
7
- const NS_PER_SECOND = 1_000_000_000;
8
-
9
- /**
10
- * Convert seconds (number) to GStreamer nanoseconds (bigint).
11
- * Rounds to the nearest nanosecond to avoid floating-point drift over
12
- * repeated back-and-forth conversions.
13
- */
14
- export function secondsToGstTime(seconds: number): bigint {
15
- return BigInt(Math.round(seconds * NS_PER_SECOND));
16
- }
17
-
18
- /**
19
- * Convert GStreamer nanoseconds to seconds (number).
20
- * Accepts both `bigint` (the runtime type from GStreamer queries) and `number`
21
- * (what the `@girs/gst-1.0` typings currently declare — a known GIR bug for
22
- * `gint64` return values in `query_position` / `query_duration`).
23
- */
24
- export function gstTimeToSeconds(nanoseconds: bigint | number): number {
25
- return Number(nanoseconds) / NS_PER_SECOND;
26
- }
@@ -1,90 +0,0 @@
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. Delegates to the active 2D context if available. */
68
- toDataURL(type?: string, quality?: any): string {
69
- const ctx = this.getContext('2d') as any;
70
- if (ctx && typeof ctx._toDataURL === 'function') return ctx._toDataURL(type, quality);
71
- return '';
72
- }
73
-
74
- /** Converts the canvas to a Blob and passes it to the callback. Delegates to the active 2D context if available. */
75
- toBlob(callback: ((blob: Blob | null) => void), type?: string, quality?: any): void {
76
- const dataUrl = this.toDataURL(type, quality);
77
- if (!dataUrl) { callback(null); return; }
78
- const [header, b64] = dataUrl.split(',');
79
- const mime = header.split(':')[1].split(';')[0];
80
- const bytes = atob(b64);
81
- const arr = new Uint8Array(bytes.length);
82
- for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
83
- callback(new Blob([arr], { type: mime }));
84
- }
85
-
86
- /** Returns a MediaStream capturing the canvas. Stub — returns empty object. */
87
- captureStream(_frameRequestRate?: number): any {
88
- return {};
89
- }
90
- }