@gjsify/dom-elements 0.1.8 → 0.1.9
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.
- package/lib/esm/dom-matrix.js +124 -0
- package/lib/esm/font-face.js +90 -0
- package/lib/esm/html-element.js +65 -1
- package/lib/esm/html-image-element.js +38 -1
- package/lib/esm/index.js +12 -1
- package/lib/esm/location-stub.js +25 -0
- package/lib/esm/match-media.js +22 -0
- package/lib/esm/register/canvas.js +16 -0
- package/lib/esm/register/document.js +36 -0
- package/lib/esm/register/font-face.js +14 -0
- package/lib/esm/register/helpers.js +16 -0
- package/lib/esm/register/image.js +5 -0
- package/lib/esm/register/location.js +3 -0
- package/lib/esm/register/match-media.js +3 -0
- package/lib/esm/register/navigator.js +3 -0
- package/lib/esm/register/observers.js +7 -0
- package/lib/esm/register.js +8 -47
- package/lib/types/dom-matrix.d.ts +64 -0
- package/lib/types/font-face.d.ts +45 -0
- package/lib/types/html-element.d.ts +10 -1
- package/lib/types/html-image-element.spec.d.ts +2 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/location-stub.d.ts +21 -0
- package/lib/types/match-media.d.ts +12 -0
- package/lib/types/register/canvas.d.ts +1 -0
- package/lib/types/register/document.d.ts +1 -0
- package/lib/types/register/font-face.d.ts +1 -0
- package/lib/types/register/helpers.d.ts +4 -0
- package/lib/types/register/image.d.ts +1 -0
- package/lib/types/register/location.d.ts +1 -0
- package/lib/types/register/match-media.d.ts +1 -0
- package/lib/types/register/navigator.d.ts +1 -0
- package/lib/types/register/observers.d.ts +1 -0
- package/lib/types/register.d.ts +8 -1
- package/lib/types/register.spec.d.ts +3 -0
- package/lib/types/stubs.spec.d.ts +2 -0
- package/package.json +37 -12
- package/src/dom-matrix.ts +109 -0
- package/src/font-face.ts +97 -0
- package/src/html-element.ts +64 -1
- package/src/html-image-element.spec.ts +285 -0
- package/src/html-image-element.ts +43 -2
- package/src/index.ts +4 -0
- package/src/location-stub.ts +20 -0
- package/src/match-media.ts +32 -0
- package/src/register/canvas.ts +23 -0
- package/src/register/document.ts +56 -0
- package/src/register/font-face.ts +18 -0
- package/src/register/helpers.ts +15 -0
- package/src/register/image.ts +8 -0
- package/src/register/location.ts +6 -0
- package/src/register/match-media.ts +6 -0
- package/src/register/navigator.ts +6 -0
- package/src/register/observers.ts +10 -0
- package/src/register.spec.ts +115 -0
- package/src/register.ts +13 -72
- package/src/stubs.spec.ts +284 -0
- package/src/test.mts +4 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal DOMMatrix implementation — supports 2D and 3D construction, plus
|
|
3
|
+
* 2D multiply/inverse/translate/scale operations used by Canvas 2D libraries
|
|
4
|
+
* like Excalibur. Full 3D math (4x4 multiply, inverse) is NOT implemented.
|
|
5
|
+
*/
|
|
6
|
+
export declare class DOMMatrix {
|
|
7
|
+
a: number;
|
|
8
|
+
b: number;
|
|
9
|
+
c: number;
|
|
10
|
+
d: number;
|
|
11
|
+
e: number;
|
|
12
|
+
f: number;
|
|
13
|
+
m11: number;
|
|
14
|
+
m12: number;
|
|
15
|
+
m13: number;
|
|
16
|
+
m14: number;
|
|
17
|
+
m21: number;
|
|
18
|
+
m22: number;
|
|
19
|
+
m23: number;
|
|
20
|
+
m24: number;
|
|
21
|
+
m31: number;
|
|
22
|
+
m32: number;
|
|
23
|
+
m33: number;
|
|
24
|
+
m34: number;
|
|
25
|
+
m41: number;
|
|
26
|
+
m42: number;
|
|
27
|
+
m43: number;
|
|
28
|
+
m44: number;
|
|
29
|
+
is2D: boolean;
|
|
30
|
+
isIdentity: boolean;
|
|
31
|
+
constructor(init?: number[] | string);
|
|
32
|
+
/**
|
|
33
|
+
* Multiply this 2D matrix by another 2D matrix and return a new matrix.
|
|
34
|
+
* [a c e] [a' c' e'] [a*a'+c*b' a*c'+c*d' a*e'+c*f'+e]
|
|
35
|
+
* [b d f] [b' d' f'] = [b*a'+d*b' b*c'+d*d' b*e'+d*f'+f]
|
|
36
|
+
* [0 0 1] [0 0 1 ] [0 0 1 ]
|
|
37
|
+
*/
|
|
38
|
+
multiply(other: {
|
|
39
|
+
a: number;
|
|
40
|
+
b: number;
|
|
41
|
+
c: number;
|
|
42
|
+
d: number;
|
|
43
|
+
e: number;
|
|
44
|
+
f: number;
|
|
45
|
+
}): DOMMatrix;
|
|
46
|
+
/** In-place multiply; returns this. */
|
|
47
|
+
multiplySelf(other: {
|
|
48
|
+
a: number;
|
|
49
|
+
b: number;
|
|
50
|
+
c: number;
|
|
51
|
+
d: number;
|
|
52
|
+
e: number;
|
|
53
|
+
f: number;
|
|
54
|
+
}): DOMMatrix;
|
|
55
|
+
/** 2D inverse. Throws if non-invertible (det === 0). */
|
|
56
|
+
inverse(): DOMMatrix;
|
|
57
|
+
translate(tx?: number, ty?: number): DOMMatrix;
|
|
58
|
+
scale(sx?: number, sy?: number): DOMMatrix;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* DOMMatrixReadOnly alias — MDN specifies this as the immutable base class.
|
|
62
|
+
* We expose the same impl since consumers (Excalibur, three.js) rarely care.
|
|
63
|
+
*/
|
|
64
|
+
export declare const DOMMatrixReadOnly: typeof DOMMatrix;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export declare class FontFace {
|
|
2
|
+
readonly family: string;
|
|
3
|
+
readonly source: string;
|
|
4
|
+
status: 'unloaded' | 'loading' | 'loaded' | 'error';
|
|
5
|
+
loaded: Promise<FontFace>;
|
|
6
|
+
display: string;
|
|
7
|
+
style: string;
|
|
8
|
+
weight: string;
|
|
9
|
+
stretch: string;
|
|
10
|
+
unicodeRange: string;
|
|
11
|
+
variant: string;
|
|
12
|
+
featureSettings: string;
|
|
13
|
+
constructor(family: string, source: string | ArrayBuffer | ArrayBufferView, _descriptors?: Record<string, string>);
|
|
14
|
+
private _extractFilePath;
|
|
15
|
+
load(): Promise<FontFace>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* FontFaceSet — tracks loaded FontFace objects and exposes them to consumers.
|
|
19
|
+
*
|
|
20
|
+
* Intentionally does NOT extend EventTarget. The dom-elements /register module
|
|
21
|
+
* runs before dom-events/register in the inject order, so EventTarget may not
|
|
22
|
+
* yet exist when this class is defined at module load time. All event methods
|
|
23
|
+
* are provided as no-ops; consumers that call addEventListener('loadingdone')
|
|
24
|
+
* etc. will silently receive nothing.
|
|
25
|
+
*/
|
|
26
|
+
export declare class FontFaceSet {
|
|
27
|
+
status: 'loading' | 'loaded';
|
|
28
|
+
ready: Promise<FontFaceSet>;
|
|
29
|
+
private _faces;
|
|
30
|
+
addEventListener(_type: string, _listener: unknown): void;
|
|
31
|
+
removeEventListener(_type: string, _listener: unknown): void;
|
|
32
|
+
dispatchEvent(_event: unknown): boolean;
|
|
33
|
+
add(face: FontFace): FontFaceSet;
|
|
34
|
+
delete(face: FontFace): boolean;
|
|
35
|
+
clear(): void;
|
|
36
|
+
has(face: FontFace): boolean;
|
|
37
|
+
check(_font: string, _text?: string): boolean;
|
|
38
|
+
load(_font: string, _text?: string): Promise<FontFace[]>;
|
|
39
|
+
forEach(callback: (value: FontFace, key: FontFace, parent: FontFaceSet) => void): void;
|
|
40
|
+
values(): IterableIterator<FontFace>;
|
|
41
|
+
keys(): IterableIterator<FontFace>;
|
|
42
|
+
entries(): IterableIterator<[FontFace, FontFace]>;
|
|
43
|
+
[Symbol.iterator](): Iterator<FontFace>;
|
|
44
|
+
get size(): number;
|
|
45
|
+
}
|
|
@@ -7,7 +7,15 @@ import { Element } from './element.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class CSSStyleDeclaration {
|
|
9
9
|
[key: string]: unknown;
|
|
10
|
-
|
|
10
|
+
private _cssText;
|
|
11
|
+
/** Setting cssText parses individual declarations and stores them as camelCase properties,
|
|
12
|
+
* matching browser behavior for feature-detection checks like Excalibur's rgbaSupport. */
|
|
13
|
+
get cssText(): string;
|
|
14
|
+
set cssText(value: string);
|
|
15
|
+
setProperty(property: string, value: string, _priority?: string): void;
|
|
16
|
+
getPropertyValue(property: string): string;
|
|
17
|
+
removeProperty(property: string): string;
|
|
18
|
+
getPropertyPriority(_property: string): string;
|
|
11
19
|
}
|
|
12
20
|
/**
|
|
13
21
|
* HTML Element base class.
|
|
@@ -16,6 +24,7 @@ export declare class CSSStyleDeclaration {
|
|
|
16
24
|
*/
|
|
17
25
|
export declare class HTMLElement extends Element {
|
|
18
26
|
readonly style: CSSStyleDeclaration;
|
|
27
|
+
get dataset(): Record<string, string>;
|
|
19
28
|
get title(): string;
|
|
20
29
|
set title(value: string);
|
|
21
30
|
get lang(): string;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -19,3 +19,7 @@ export { IntersectionObserver } from './intersection-observer.js';
|
|
|
19
19
|
export { NodeType } from './node-type.js';
|
|
20
20
|
export { NamespaceURI } from './namespace-uri.js';
|
|
21
21
|
export * as PropertySymbol from './property-symbol.js';
|
|
22
|
+
export { FontFace, FontFaceSet } from './font-face.js';
|
|
23
|
+
export { MediaQueryList, matchMedia } from './match-media.js';
|
|
24
|
+
export { location } from './location-stub.js';
|
|
25
|
+
export { DOMMatrix, DOMMatrixReadOnly } from './dom-matrix.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare const location: {
|
|
2
|
+
href: string;
|
|
3
|
+
origin: string;
|
|
4
|
+
protocol: string;
|
|
5
|
+
host: string;
|
|
6
|
+
hostname: string;
|
|
7
|
+
port: string;
|
|
8
|
+
pathname: string;
|
|
9
|
+
search: string;
|
|
10
|
+
hash: string;
|
|
11
|
+
assign(_url: string): void;
|
|
12
|
+
replace(_url: string): void;
|
|
13
|
+
reload(): void;
|
|
14
|
+
toString(): string;
|
|
15
|
+
ancestorOrigins: {
|
|
16
|
+
length: number;
|
|
17
|
+
item: () => null;
|
|
18
|
+
contains: () => boolean;
|
|
19
|
+
[Symbol.iterator]: () => Generator<string>;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EventTarget } from '@gjsify/dom-events';
|
|
2
|
+
export declare class MediaQueryList extends EventTarget {
|
|
3
|
+
readonly media: string;
|
|
4
|
+
readonly matches: boolean;
|
|
5
|
+
onchange: ((this: MediaQueryList, ev: unknown) => unknown) | null;
|
|
6
|
+
constructor(query: string);
|
|
7
|
+
/** @deprecated Use addEventListener('change', ...) */
|
|
8
|
+
addListener(_listener: unknown): void;
|
|
9
|
+
/** @deprecated Use removeEventListener('change', ...) */
|
|
10
|
+
removeListener(_listener: unknown): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function matchMedia(query: string): MediaQueryList;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Unconditionally expose a DOM class on `globalThis` (writable + configurable). */
|
|
2
|
+
export declare function defineGlobal(name: string, value: unknown): void;
|
|
3
|
+
/** Only set the global if it hasn't already been defined. */
|
|
4
|
+
export declare function defineGlobalIfMissing(name: string, value: unknown): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/types/register.d.ts
CHANGED
|
@@ -1 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import './register/document.js';
|
|
2
|
+
import './register/canvas.js';
|
|
3
|
+
import './register/image.js';
|
|
4
|
+
import './register/observers.js';
|
|
5
|
+
import './register/font-face.js';
|
|
6
|
+
import './register/match-media.js';
|
|
7
|
+
import './register/location.js';
|
|
8
|
+
import './register/navigator.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/dom-elements",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "DOM element hierarchy (Node, Element, HTMLElement, HTMLImageElement) for GJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "lib/esm/index.js",
|
|
@@ -13,10 +13,35 @@
|
|
|
13
13
|
"./register": {
|
|
14
14
|
"types": "./lib/types/register.d.ts",
|
|
15
15
|
"default": "./lib/esm/register.js"
|
|
16
|
+
},
|
|
17
|
+
"./register/document": {
|
|
18
|
+
"default": "./lib/esm/register/document.js"
|
|
19
|
+
},
|
|
20
|
+
"./register/canvas": {
|
|
21
|
+
"default": "./lib/esm/register/canvas.js"
|
|
22
|
+
},
|
|
23
|
+
"./register/image": {
|
|
24
|
+
"default": "./lib/esm/register/image.js"
|
|
25
|
+
},
|
|
26
|
+
"./register/observers": {
|
|
27
|
+
"default": "./lib/esm/register/observers.js"
|
|
28
|
+
},
|
|
29
|
+
"./register/font-face": {
|
|
30
|
+
"default": "./lib/esm/register/font-face.js"
|
|
31
|
+
},
|
|
32
|
+
"./register/match-media": {
|
|
33
|
+
"default": "./lib/esm/register/match-media.js"
|
|
34
|
+
},
|
|
35
|
+
"./register/location": {
|
|
36
|
+
"default": "./lib/esm/register/location.js"
|
|
37
|
+
},
|
|
38
|
+
"./register/navigator": {
|
|
39
|
+
"default": "./lib/esm/register/navigator.js"
|
|
16
40
|
}
|
|
17
41
|
},
|
|
18
42
|
"sideEffects": [
|
|
19
|
-
"./lib/esm/register.js"
|
|
43
|
+
"./lib/esm/register.js",
|
|
44
|
+
"./lib/esm/register/*.js"
|
|
20
45
|
],
|
|
21
46
|
"scripts": {
|
|
22
47
|
"clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
|
|
@@ -36,18 +61,18 @@
|
|
|
36
61
|
"node"
|
|
37
62
|
],
|
|
38
63
|
"dependencies": {
|
|
39
|
-
"@girs/gdkpixbuf-2.0": "^2.0.0-4.0.0-rc.
|
|
40
|
-
"@girs/gjs": "^4.0.0-rc.
|
|
41
|
-
"@girs/glib-2.0": "^2.88.0-4.0.0-rc.
|
|
42
|
-
"@gjsify/abort-controller": "^0.1.
|
|
43
|
-
"@gjsify/canvas2d-core": "^0.1.
|
|
44
|
-
"@gjsify/dom-events": "^0.1.
|
|
45
|
-
"@gjsify/fetch": "^0.1.
|
|
64
|
+
"@girs/gdkpixbuf-2.0": "^2.0.0-4.0.0-rc.2",
|
|
65
|
+
"@girs/gjs": "^4.0.0-rc.2",
|
|
66
|
+
"@girs/glib-2.0": "^2.88.0-4.0.0-rc.2",
|
|
67
|
+
"@gjsify/abort-controller": "^0.1.9",
|
|
68
|
+
"@gjsify/canvas2d-core": "^0.1.9",
|
|
69
|
+
"@gjsify/dom-events": "^0.1.9",
|
|
70
|
+
"@gjsify/fetch": "^0.1.9"
|
|
46
71
|
},
|
|
47
72
|
"devDependencies": {
|
|
48
|
-
"@gjsify/cli": "^0.1.
|
|
49
|
-
"@gjsify/unit": "^0.1.
|
|
50
|
-
"@types/node": "^25.
|
|
73
|
+
"@gjsify/cli": "^0.1.9",
|
|
74
|
+
"@gjsify/unit": "^0.1.9",
|
|
75
|
+
"@types/node": "^25.6.0",
|
|
51
76
|
"typescript": "^6.0.2"
|
|
52
77
|
}
|
|
53
78
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// DOMMatrix stub for GJS — minimal 2D/3D transformation matrix.
|
|
2
|
+
// Libraries like Excalibur construct `new DOMMatrix([a,b,c,d,e,f])` and pass
|
|
3
|
+
// the result to `ctx.setTransform(matrix)` — we store the 6 2D components
|
|
4
|
+
// plus 16 3D components. Our Canvas 2D context's setTransform() accepts
|
|
5
|
+
// either a DOMMatrix or six numbers, so this works transparently.
|
|
6
|
+
// Reference: https://developer.mozilla.org/en-US/docs/Web/API/DOMMatrix
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Minimal DOMMatrix implementation — supports 2D and 3D construction, plus
|
|
10
|
+
* 2D multiply/inverse/translate/scale operations used by Canvas 2D libraries
|
|
11
|
+
* like Excalibur. Full 3D math (4x4 multiply, inverse) is NOT implemented.
|
|
12
|
+
*/
|
|
13
|
+
export class DOMMatrix {
|
|
14
|
+
// 2D components
|
|
15
|
+
a = 1; b = 0; c = 0; d = 1; e = 0; f = 0;
|
|
16
|
+
// 3D components (column-major)
|
|
17
|
+
m11 = 1; m12 = 0; m13 = 0; m14 = 0;
|
|
18
|
+
m21 = 0; m22 = 1; m23 = 0; m24 = 0;
|
|
19
|
+
m31 = 0; m32 = 0; m33 = 1; m34 = 0;
|
|
20
|
+
m41 = 0; m42 = 0; m43 = 0; m44 = 1;
|
|
21
|
+
is2D = true;
|
|
22
|
+
isIdentity = true;
|
|
23
|
+
|
|
24
|
+
constructor(init?: number[] | string) {
|
|
25
|
+
if (Array.isArray(init)) {
|
|
26
|
+
if (init.length === 6) {
|
|
27
|
+
// 2D affine: [a, b, c, d, e, f]
|
|
28
|
+
this.a = this.m11 = init[0];
|
|
29
|
+
this.b = this.m12 = init[1];
|
|
30
|
+
this.c = this.m21 = init[2];
|
|
31
|
+
this.d = this.m22 = init[3];
|
|
32
|
+
this.e = this.m41 = init[4];
|
|
33
|
+
this.f = this.m42 = init[5];
|
|
34
|
+
this.is2D = true;
|
|
35
|
+
} else if (init.length === 16) {
|
|
36
|
+
// 3D: column-major 4x4
|
|
37
|
+
this.m11 = init[0]; this.m12 = init[1]; this.m13 = init[2]; this.m14 = init[3];
|
|
38
|
+
this.m21 = init[4]; this.m22 = init[5]; this.m23 = init[6]; this.m24 = init[7];
|
|
39
|
+
this.m31 = init[8]; this.m32 = init[9]; this.m33 = init[10]; this.m34 = init[11];
|
|
40
|
+
this.m41 = init[12]; this.m42 = init[13]; this.m43 = init[14]; this.m44 = init[15];
|
|
41
|
+
this.a = this.m11; this.b = this.m12;
|
|
42
|
+
this.c = this.m21; this.d = this.m22;
|
|
43
|
+
this.e = this.m41; this.f = this.m42;
|
|
44
|
+
this.is2D = false;
|
|
45
|
+
}
|
|
46
|
+
this.isIdentity =
|
|
47
|
+
this.a === 1 && this.b === 0 && this.c === 0 &&
|
|
48
|
+
this.d === 1 && this.e === 0 && this.f === 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Multiply this 2D matrix by another 2D matrix and return a new matrix.
|
|
54
|
+
* [a c e] [a' c' e'] [a*a'+c*b' a*c'+c*d' a*e'+c*f'+e]
|
|
55
|
+
* [b d f] [b' d' f'] = [b*a'+d*b' b*c'+d*d' b*e'+d*f'+f]
|
|
56
|
+
* [0 0 1] [0 0 1 ] [0 0 1 ]
|
|
57
|
+
*/
|
|
58
|
+
multiply(other: { a: number; b: number; c: number; d: number; e: number; f: number }): DOMMatrix {
|
|
59
|
+
const a = this.a * other.a + this.c * other.b;
|
|
60
|
+
const b = this.b * other.a + this.d * other.b;
|
|
61
|
+
const c = this.a * other.c + this.c * other.d;
|
|
62
|
+
const d = this.b * other.c + this.d * other.d;
|
|
63
|
+
const e = this.a * other.e + this.c * other.f + this.e;
|
|
64
|
+
const f = this.b * other.e + this.d * other.f + this.f;
|
|
65
|
+
return new DOMMatrix([a, b, c, d, e, f]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** In-place multiply; returns this. */
|
|
69
|
+
multiplySelf(other: { a: number; b: number; c: number; d: number; e: number; f: number }): DOMMatrix {
|
|
70
|
+
const result = this.multiply(other);
|
|
71
|
+
this.a = result.a; this.b = result.b;
|
|
72
|
+
this.c = result.c; this.d = result.d;
|
|
73
|
+
this.e = result.e; this.f = result.f;
|
|
74
|
+
this.m11 = this.a; this.m12 = this.b;
|
|
75
|
+
this.m21 = this.c; this.m22 = this.d;
|
|
76
|
+
this.m41 = this.e; this.m42 = this.f;
|
|
77
|
+
this.isIdentity = false;
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 2D inverse. Throws if non-invertible (det === 0). */
|
|
82
|
+
inverse(): DOMMatrix {
|
|
83
|
+
const det = this.a * this.d - this.b * this.c;
|
|
84
|
+
if (det === 0) return new DOMMatrix([1, 0, 0, 1, 0, 0]);
|
|
85
|
+
const invDet = 1 / det;
|
|
86
|
+
return new DOMMatrix([
|
|
87
|
+
this.d * invDet,
|
|
88
|
+
-this.b * invDet,
|
|
89
|
+
-this.c * invDet,
|
|
90
|
+
this.a * invDet,
|
|
91
|
+
(this.c * this.f - this.d * this.e) * invDet,
|
|
92
|
+
(this.b * this.e - this.a * this.f) * invDet,
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
translate(tx = 0, ty = 0): DOMMatrix {
|
|
97
|
+
return this.multiply({ a: 1, b: 0, c: 0, d: 1, e: tx, f: ty });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
scale(sx = 1, sy: number = sx): DOMMatrix {
|
|
101
|
+
return this.multiply({ a: sx, b: 0, c: 0, d: sy, e: 0, f: 0 });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* DOMMatrixReadOnly alias — MDN specifies this as the immutable base class.
|
|
107
|
+
* We expose the same impl since consumers (Excalibur, three.js) rarely care.
|
|
108
|
+
*/
|
|
109
|
+
export const DOMMatrixReadOnly = DOMMatrix;
|
package/src/font-face.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
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/html-element.ts
CHANGED
|
@@ -15,7 +15,37 @@ import * as PS from './property-symbol.js';
|
|
|
15
15
|
*/
|
|
16
16
|
export class CSSStyleDeclaration {
|
|
17
17
|
[key: string]: unknown;
|
|
18
|
-
|
|
18
|
+
private _cssText = '';
|
|
19
|
+
|
|
20
|
+
/** Setting cssText parses individual declarations and stores them as camelCase properties,
|
|
21
|
+
* matching browser behavior for feature-detection checks like Excalibur's rgbaSupport. */
|
|
22
|
+
get cssText(): string { return this._cssText; }
|
|
23
|
+
set cssText(value: string) {
|
|
24
|
+
this._cssText = value;
|
|
25
|
+
for (const decl of value.split(';')) {
|
|
26
|
+
const colon = decl.indexOf(':');
|
|
27
|
+
if (colon === -1) continue;
|
|
28
|
+
const prop = decl.slice(0, colon).trim();
|
|
29
|
+
const val = decl.slice(colon + 1).trim();
|
|
30
|
+
if (!prop) continue;
|
|
31
|
+
const camel = prop.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
32
|
+
(this as Record<string, unknown>)[camel] = val;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
setProperty(property: string, value: string, _priority?: string): void {
|
|
37
|
+
(this as Record<string, unknown>)[property] = value;
|
|
38
|
+
}
|
|
39
|
+
getPropertyValue(property: string): string {
|
|
40
|
+
const v = (this as Record<string, unknown>)[property];
|
|
41
|
+
return typeof v === 'string' ? v : '';
|
|
42
|
+
}
|
|
43
|
+
removeProperty(property: string): string {
|
|
44
|
+
const v = (this as Record<string, unknown>)[property];
|
|
45
|
+
delete (this as Record<string, unknown>)[property];
|
|
46
|
+
return typeof v === 'string' ? v : '';
|
|
47
|
+
}
|
|
48
|
+
getPropertyPriority(_property: string): string { return ''; }
|
|
19
49
|
}
|
|
20
50
|
|
|
21
51
|
/**
|
|
@@ -27,6 +57,39 @@ export class HTMLElement extends Element {
|
|
|
27
57
|
// -- Style stub (no layout engine — assignments are no-ops) --
|
|
28
58
|
readonly style: CSSStyleDeclaration = new CSSStyleDeclaration();
|
|
29
59
|
|
|
60
|
+
// -- dataset: Proxy-backed DOMStringMap over data-* attributes --
|
|
61
|
+
// Converts `data-original-src` ↔ `originalSrc` (camelCase). Used by
|
|
62
|
+
// Excalibur's ImageSource which sets data-original-src on images for
|
|
63
|
+
// debugging and TextureLoader.checkImageSizeSupportedAndLog.
|
|
64
|
+
get dataset(): Record<string, string> {
|
|
65
|
+
const el = this;
|
|
66
|
+
return new Proxy({} as Record<string, string>, {
|
|
67
|
+
get(_target, prop: string): string | undefined {
|
|
68
|
+
if (typeof prop !== 'string') return undefined;
|
|
69
|
+
const attr = 'data-' + prop.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
|
|
70
|
+
const value = el.getAttribute(attr);
|
|
71
|
+
return value ?? undefined;
|
|
72
|
+
},
|
|
73
|
+
set(_target, prop: string, value: string): boolean {
|
|
74
|
+
if (typeof prop !== 'string') return false;
|
|
75
|
+
const attr = 'data-' + prop.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
|
|
76
|
+
el.setAttribute(attr, String(value));
|
|
77
|
+
return true;
|
|
78
|
+
},
|
|
79
|
+
deleteProperty(_target, prop: string): boolean {
|
|
80
|
+
if (typeof prop !== 'string') return false;
|
|
81
|
+
const attr = 'data-' + prop.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
|
|
82
|
+
el.removeAttribute(attr);
|
|
83
|
+
return true;
|
|
84
|
+
},
|
|
85
|
+
has(_target, prop: string): boolean {
|
|
86
|
+
if (typeof prop !== 'string') return false;
|
|
87
|
+
const attr = 'data-' + prop.replace(/[A-Z]/g, c => '-' + c.toLowerCase());
|
|
88
|
+
return el.hasAttribute(attr);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
30
93
|
// -- Attribute-backed string properties --
|
|
31
94
|
|
|
32
95
|
get title(): string {
|