@glissade/element 0.10.1 → 0.11.0-pre.1
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 +114 -43
- 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,109 @@ 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
|
+
this.#syncControlsSubscription();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
75
61
|
if (this.isConnected && this.#scene) this.#remount();
|
|
76
62
|
}
|
|
63
|
+
/** Wire the scrubber/time playhead subscription when controls exist, unwire
|
|
64
|
+
* when they don't — against the existing playhead, so toggling controls never
|
|
65
|
+
* disturbs playback position. */
|
|
66
|
+
#syncControlsSubscription() {
|
|
67
|
+
if (this.#controls && !this.#unsubscribe && this.#playhead) {
|
|
68
|
+
this.#unsubscribe = this.#playhead.subscribe(() => {
|
|
69
|
+
requestAnimationFrame(() => {
|
|
70
|
+
if (!this.#controls) return;
|
|
71
|
+
this.#syncReadout();
|
|
72
|
+
this.#syncButton();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
this.#syncReadout();
|
|
76
|
+
this.#syncButton();
|
|
77
|
+
} else if (!this.#controls && this.#unsubscribe) {
|
|
78
|
+
this.#unsubscribe();
|
|
79
|
+
this.#unsubscribe = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Build the controls subtree + listeners on demand; tear them down when gone. */
|
|
83
|
+
#syncControls() {
|
|
84
|
+
const want = this.hasAttribute("controls");
|
|
85
|
+
if (want && !this.#controls) this.#buildControls();
|
|
86
|
+
else if (!want && this.#controls) this.#destroyControls();
|
|
87
|
+
}
|
|
88
|
+
#buildControls() {
|
|
89
|
+
const div = document.createElement("div");
|
|
90
|
+
div.className = "controls";
|
|
91
|
+
div.setAttribute("part", "controls");
|
|
92
|
+
const button = document.createElement("button");
|
|
93
|
+
button.setAttribute("part", "button");
|
|
94
|
+
button.setAttribute("aria-label", "Play or pause");
|
|
95
|
+
button.textContent = "Play";
|
|
96
|
+
const scrubber = document.createElement("input");
|
|
97
|
+
scrubber.setAttribute("part", "scrubber");
|
|
98
|
+
scrubber.type = "range";
|
|
99
|
+
scrubber.min = "0";
|
|
100
|
+
scrubber.max = "1";
|
|
101
|
+
scrubber.step = "0.0001";
|
|
102
|
+
scrubber.value = "0";
|
|
103
|
+
scrubber.setAttribute("aria-label", "Seek");
|
|
104
|
+
const time = document.createElement("span");
|
|
105
|
+
time.className = "time";
|
|
106
|
+
time.setAttribute("part", "time");
|
|
107
|
+
const onClick = () => {
|
|
108
|
+
const player = this.#mounted?.player;
|
|
109
|
+
if (!player) return;
|
|
110
|
+
if (player.playing) player.pause();
|
|
111
|
+
else player.play();
|
|
112
|
+
this.#syncButton();
|
|
113
|
+
};
|
|
114
|
+
const onInput = () => {
|
|
115
|
+
const player = this.#mounted?.player;
|
|
116
|
+
if (!player) return;
|
|
117
|
+
this.#scrubbing = true;
|
|
118
|
+
player.pause();
|
|
119
|
+
player.seek(parseFloat(scrubber.value) * player.duration);
|
|
120
|
+
this.#scrubbing = false;
|
|
121
|
+
this.#syncButton();
|
|
122
|
+
};
|
|
123
|
+
button.addEventListener("click", onClick);
|
|
124
|
+
scrubber.addEventListener("input", onInput);
|
|
125
|
+
div.append(button, scrubber, time);
|
|
126
|
+
this.#shadow.append(div);
|
|
127
|
+
this.#controls = {
|
|
128
|
+
root: div,
|
|
129
|
+
button,
|
|
130
|
+
scrubber,
|
|
131
|
+
time,
|
|
132
|
+
onClick,
|
|
133
|
+
onInput
|
|
134
|
+
};
|
|
135
|
+
this.#syncReadout();
|
|
136
|
+
this.#syncButton();
|
|
137
|
+
}
|
|
138
|
+
#destroyControls() {
|
|
139
|
+
const c = this.#controls;
|
|
140
|
+
if (!c) return;
|
|
141
|
+
c.button.removeEventListener("click", c.onClick);
|
|
142
|
+
c.scrubber.removeEventListener("input", c.onInput);
|
|
143
|
+
c.root.remove();
|
|
144
|
+
this.#controls = null;
|
|
145
|
+
}
|
|
77
146
|
#teardown() {
|
|
78
147
|
this.#unsubscribe?.();
|
|
79
148
|
this.#unsubscribe = null;
|
|
80
149
|
this.#mounted?.dispose();
|
|
81
150
|
this.#mounted = null;
|
|
151
|
+
this.#playhead = null;
|
|
82
152
|
}
|
|
83
153
|
#remount() {
|
|
84
154
|
this.#teardown();
|
|
@@ -90,21 +160,22 @@ var GsPlayerElement = class extends HTMLElement {
|
|
|
90
160
|
loop: this.hasAttribute("loop"),
|
|
91
161
|
autoplay: this.hasAttribute("autoplay")
|
|
92
162
|
});
|
|
93
|
-
|
|
94
|
-
this.#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
})
|
|
102
|
-
this.#
|
|
103
|
-
this.#syncButton();
|
|
163
|
+
this.#playhead = scene.playhead;
|
|
164
|
+
this.#syncControlsSubscription();
|
|
165
|
+
}
|
|
166
|
+
#syncReadout() {
|
|
167
|
+
const c = this.#controls;
|
|
168
|
+
const player = this.#mounted?.player;
|
|
169
|
+
if (!c || !player) return;
|
|
170
|
+
const t = this.#playhead?.peek() ?? 0;
|
|
171
|
+
c.time.textContent = `${t.toFixed(2)} / ${player.duration.toFixed(2)}s`;
|
|
172
|
+
if (!this.#scrubbing && player.duration > 0) c.scrubber.value = String(t / player.duration);
|
|
104
173
|
}
|
|
105
174
|
#syncButton() {
|
|
175
|
+
const c = this.#controls;
|
|
176
|
+
if (!c) return;
|
|
106
177
|
const playing = this.#mounted?.player.playing ?? false;
|
|
107
|
-
|
|
178
|
+
c.button.textContent = playing ? "Pause" : "Play";
|
|
108
179
|
}
|
|
109
180
|
};
|
|
110
181
|
/** 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.1",
|
|
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.1",
|
|
22
|
+
"@glissade/core": "0.11.0-pre.1",
|
|
23
|
+
"@glissade/player": "0.11.0-pre.1",
|
|
24
|
+
"@glissade/scene": "0.11.0-pre.1"
|
|
22
25
|
},
|
|
23
26
|
"repository": {
|
|
24
27
|
"type": "git",
|