@glissade/element 0.10.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 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: none; gap: 8px; align-items: center; padding: 6px 4px; font: 12px system-ui, sans-serif; }
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 = ["loop", "autoplay"];
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
- #button;
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
- const player = this.#mounted.player;
94
- this.#unsubscribe = scene.playhead.subscribe(() => {
144
+ this.#playhead = scene.playhead;
145
+ if (this.#controls) this.#unsubscribe = scene.playhead.subscribe(() => {
95
146
  requestAnimationFrame(() => {
96
- const t = scene.playhead.peek();
97
- this.#time.textContent = `${t.toFixed(2)} / ${player.duration.toFixed(2)}s`;
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.#time.textContent = `0.00 / ${player.duration.toFixed(2)}s`;
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
- this.#button.textContent = playing ? "Pause" : "Play";
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.10.1",
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.10.1",
19
- "@glissade/core": "0.10.1",
20
- "@glissade/player": "0.10.1",
21
- "@glissade/scene": "0.10.1"
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",