@glissade/element 0.10.1-pre.1 → 0.11.0-pre.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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +98 -38
- package/package.json +8 -5
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ declare class GsPlayerElement extends HTMLElement {
|
|
|
14
14
|
get player(): _glissade_player0.Player | null;
|
|
15
15
|
connectedCallback(): void;
|
|
16
16
|
disconnectedCallback(): void;
|
|
17
|
-
attributeChangedCallback(): void;
|
|
17
|
+
attributeChangedCallback(name: string): void;
|
|
18
18
|
}
|
|
19
19
|
/** Define <gs-player> (idempotent). Importing this module defines it. */
|
|
20
20
|
declare function defineGsPlayer(tagName?: string): void;
|
package/dist/index.js
CHANGED
|
@@ -5,53 +5,33 @@ const TEMPLATE = `
|
|
|
5
5
|
<style>
|
|
6
6
|
:host { display: inline-block; }
|
|
7
7
|
canvas { display: block; width: 100%; height: auto; }
|
|
8
|
-
.controls { display:
|
|
9
|
-
:host([controls]) .controls { display: flex; }
|
|
8
|
+
.controls { display: flex; gap: 8px; align-items: center; padding: 6px 4px; font: 12px system-ui, sans-serif; }
|
|
10
9
|
button { font: inherit; cursor: pointer; }
|
|
11
10
|
input[type='range'] { flex: 1; }
|
|
12
11
|
.time { font-variant-numeric: tabular-nums; opacity: 0.75; min-width: 9ch; text-align: right; }
|
|
13
12
|
</style>
|
|
14
13
|
<canvas part="canvas"></canvas>
|
|
15
|
-
<div class="controls" part="controls">
|
|
16
|
-
<button part="button" aria-label="Play or pause">Play</button>
|
|
17
|
-
<input part="scrubber" type="range" min="0" max="1" step="0.0001" value="0" aria-label="Seek" />
|
|
18
|
-
<span class="time" part="time"></span>
|
|
19
|
-
</div>
|
|
20
14
|
`;
|
|
21
15
|
var GsPlayerElement = class extends HTMLElement {
|
|
22
|
-
static observedAttributes = [
|
|
16
|
+
static observedAttributes = [
|
|
17
|
+
"loop",
|
|
18
|
+
"autoplay",
|
|
19
|
+
"controls"
|
|
20
|
+
];
|
|
23
21
|
#mounted = null;
|
|
24
22
|
#scene = null;
|
|
25
23
|
#unsubscribe = null;
|
|
26
24
|
#scrubbing = false;
|
|
25
|
+
#playhead = null;
|
|
26
|
+
#shadow;
|
|
27
27
|
#canvas;
|
|
28
|
-
#
|
|
29
|
-
#scrubber;
|
|
30
|
-
#time;
|
|
28
|
+
#controls = null;
|
|
31
29
|
constructor() {
|
|
32
30
|
super();
|
|
33
31
|
const root = this.attachShadow({ mode: "open" });
|
|
34
32
|
root.innerHTML = TEMPLATE;
|
|
33
|
+
this.#shadow = root;
|
|
35
34
|
this.#canvas = root.querySelector("canvas");
|
|
36
|
-
this.#button = root.querySelector("button");
|
|
37
|
-
this.#scrubber = root.querySelector("input");
|
|
38
|
-
this.#time = root.querySelector(".time");
|
|
39
|
-
this.#button.addEventListener("click", () => {
|
|
40
|
-
const player = this.#mounted?.player;
|
|
41
|
-
if (!player) return;
|
|
42
|
-
if (player.playing) player.pause();
|
|
43
|
-
else player.play();
|
|
44
|
-
this.#syncButton();
|
|
45
|
-
});
|
|
46
|
-
this.#scrubber.addEventListener("input", () => {
|
|
47
|
-
const player = this.#mounted?.player;
|
|
48
|
-
if (!player) return;
|
|
49
|
-
this.#scrubbing = true;
|
|
50
|
-
player.pause();
|
|
51
|
-
player.seek(parseFloat(this.#scrubber.value) * player.duration);
|
|
52
|
-
this.#scrubbing = false;
|
|
53
|
-
this.#syncButton();
|
|
54
|
-
});
|
|
55
35
|
}
|
|
56
36
|
/** The scene module to play; assigning (re)mounts. */
|
|
57
37
|
get scene() {
|
|
@@ -66,19 +46,90 @@ var GsPlayerElement = class extends HTMLElement {
|
|
|
66
46
|
return this.#mounted?.player ?? null;
|
|
67
47
|
}
|
|
68
48
|
connectedCallback() {
|
|
49
|
+
this.#syncControls();
|
|
69
50
|
this.#remount();
|
|
70
51
|
}
|
|
71
52
|
disconnectedCallback() {
|
|
72
53
|
this.#teardown();
|
|
73
54
|
}
|
|
74
|
-
attributeChangedCallback() {
|
|
55
|
+
attributeChangedCallback(name) {
|
|
56
|
+
if (name === "controls") {
|
|
57
|
+
this.#syncControls();
|
|
58
|
+
if (this.isConnected && this.#scene) this.#remount();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
75
61
|
if (this.isConnected && this.#scene) this.#remount();
|
|
76
62
|
}
|
|
63
|
+
/** Build the controls subtree + listeners on demand; tear them down when gone. */
|
|
64
|
+
#syncControls() {
|
|
65
|
+
const want = this.hasAttribute("controls");
|
|
66
|
+
if (want && !this.#controls) this.#buildControls();
|
|
67
|
+
else if (!want && this.#controls) this.#destroyControls();
|
|
68
|
+
}
|
|
69
|
+
#buildControls() {
|
|
70
|
+
const div = document.createElement("div");
|
|
71
|
+
div.className = "controls";
|
|
72
|
+
div.setAttribute("part", "controls");
|
|
73
|
+
const button = document.createElement("button");
|
|
74
|
+
button.setAttribute("part", "button");
|
|
75
|
+
button.setAttribute("aria-label", "Play or pause");
|
|
76
|
+
button.textContent = "Play";
|
|
77
|
+
const scrubber = document.createElement("input");
|
|
78
|
+
scrubber.setAttribute("part", "scrubber");
|
|
79
|
+
scrubber.type = "range";
|
|
80
|
+
scrubber.min = "0";
|
|
81
|
+
scrubber.max = "1";
|
|
82
|
+
scrubber.step = "0.0001";
|
|
83
|
+
scrubber.value = "0";
|
|
84
|
+
scrubber.setAttribute("aria-label", "Seek");
|
|
85
|
+
const time = document.createElement("span");
|
|
86
|
+
time.className = "time";
|
|
87
|
+
time.setAttribute("part", "time");
|
|
88
|
+
const onClick = () => {
|
|
89
|
+
const player = this.#mounted?.player;
|
|
90
|
+
if (!player) return;
|
|
91
|
+
if (player.playing) player.pause();
|
|
92
|
+
else player.play();
|
|
93
|
+
this.#syncButton();
|
|
94
|
+
};
|
|
95
|
+
const onInput = () => {
|
|
96
|
+
const player = this.#mounted?.player;
|
|
97
|
+
if (!player) return;
|
|
98
|
+
this.#scrubbing = true;
|
|
99
|
+
player.pause();
|
|
100
|
+
player.seek(parseFloat(scrubber.value) * player.duration);
|
|
101
|
+
this.#scrubbing = false;
|
|
102
|
+
this.#syncButton();
|
|
103
|
+
};
|
|
104
|
+
button.addEventListener("click", onClick);
|
|
105
|
+
scrubber.addEventListener("input", onInput);
|
|
106
|
+
div.append(button, scrubber, time);
|
|
107
|
+
this.#shadow.append(div);
|
|
108
|
+
this.#controls = {
|
|
109
|
+
root: div,
|
|
110
|
+
button,
|
|
111
|
+
scrubber,
|
|
112
|
+
time,
|
|
113
|
+
onClick,
|
|
114
|
+
onInput
|
|
115
|
+
};
|
|
116
|
+
this.#syncReadout();
|
|
117
|
+
this.#syncButton();
|
|
118
|
+
}
|
|
119
|
+
#destroyControls() {
|
|
120
|
+
const c = this.#controls;
|
|
121
|
+
if (!c) return;
|
|
122
|
+
c.button.removeEventListener("click", c.onClick);
|
|
123
|
+
c.scrubber.removeEventListener("input", c.onInput);
|
|
124
|
+
c.root.remove();
|
|
125
|
+
this.#controls = null;
|
|
126
|
+
}
|
|
77
127
|
#teardown() {
|
|
78
128
|
this.#unsubscribe?.();
|
|
79
129
|
this.#unsubscribe = null;
|
|
80
130
|
this.#mounted?.dispose();
|
|
81
131
|
this.#mounted = null;
|
|
132
|
+
this.#playhead = null;
|
|
82
133
|
}
|
|
83
134
|
#remount() {
|
|
84
135
|
this.#teardown();
|
|
@@ -90,21 +141,30 @@ var GsPlayerElement = class extends HTMLElement {
|
|
|
90
141
|
loop: this.hasAttribute("loop"),
|
|
91
142
|
autoplay: this.hasAttribute("autoplay")
|
|
92
143
|
});
|
|
93
|
-
|
|
94
|
-
this.#unsubscribe = scene.playhead.subscribe(() => {
|
|
144
|
+
this.#playhead = scene.playhead;
|
|
145
|
+
if (this.#controls) this.#unsubscribe = scene.playhead.subscribe(() => {
|
|
95
146
|
requestAnimationFrame(() => {
|
|
96
|
-
|
|
97
|
-
this.#
|
|
98
|
-
if (!this.#scrubbing && player.duration > 0) this.#scrubber.value = String(t / player.duration);
|
|
147
|
+
if (!this.#controls) return;
|
|
148
|
+
this.#syncReadout();
|
|
99
149
|
this.#syncButton();
|
|
100
150
|
});
|
|
101
151
|
});
|
|
102
|
-
this.#
|
|
152
|
+
this.#syncReadout();
|
|
103
153
|
this.#syncButton();
|
|
104
154
|
}
|
|
155
|
+
#syncReadout() {
|
|
156
|
+
const c = this.#controls;
|
|
157
|
+
const player = this.#mounted?.player;
|
|
158
|
+
if (!c || !player) return;
|
|
159
|
+
const t = this.#playhead?.peek() ?? 0;
|
|
160
|
+
c.time.textContent = `${t.toFixed(2)} / ${player.duration.toFixed(2)}s`;
|
|
161
|
+
if (!this.#scrubbing && player.duration > 0) c.scrubber.value = String(t / player.duration);
|
|
162
|
+
}
|
|
105
163
|
#syncButton() {
|
|
164
|
+
const c = this.#controls;
|
|
165
|
+
if (!c) return;
|
|
106
166
|
const playing = this.#mounted?.player.playing ?? false;
|
|
107
|
-
|
|
167
|
+
c.button.textContent = playing ? "Pause" : "Play";
|
|
108
168
|
}
|
|
109
169
|
};
|
|
110
170
|
/** Define <gs-player> (idempotent). Importing this module defines it. */
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glissade/element",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0-pre.0",
|
|
4
4
|
"description": "glissade <gs-player> custom element: zero-framework embedding with minimal, CSS-part themable controls.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20.19"
|
|
8
|
+
},
|
|
6
9
|
"type": "module",
|
|
7
10
|
"sideEffects": true,
|
|
8
11
|
"exports": {
|
|
@@ -15,10 +18,10 @@
|
|
|
15
18
|
"dist"
|
|
16
19
|
],
|
|
17
20
|
"dependencies": {
|
|
18
|
-
"@glissade/backend-canvas2d": "0.
|
|
19
|
-
"@glissade/core": "0.
|
|
20
|
-
"@glissade/player": "0.
|
|
21
|
-
"@glissade/scene": "0.
|
|
21
|
+
"@glissade/backend-canvas2d": "0.11.0-pre.0",
|
|
22
|
+
"@glissade/core": "0.11.0-pre.0",
|
|
23
|
+
"@glissade/player": "0.11.0-pre.0",
|
|
24
|
+
"@glissade/scene": "0.11.0-pre.0"
|
|
22
25
|
},
|
|
23
26
|
"repository": {
|
|
24
27
|
"type": "git",
|