@rogieking/figui3 4.9.0 → 4.10.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/components.css +72 -58
- package/dist/components.css +1 -1
- package/dist/fig.css +1 -1
- package/dist/fig.js +46 -46
- package/fig.js +375 -45
- package/package.json +1 -1
package/fig.js
CHANGED
|
@@ -8380,7 +8380,6 @@ customElements.define("fig-swatch", FigSwatch);
|
|
|
8380
8380
|
*/
|
|
8381
8381
|
class FigMedia extends HTMLElement {
|
|
8382
8382
|
#src = null;
|
|
8383
|
-
#chit = null;
|
|
8384
8383
|
#mediaEl = null;
|
|
8385
8384
|
#fileInput = null;
|
|
8386
8385
|
#blobUrl = null;
|
|
@@ -8389,6 +8388,13 @@ class FigMedia extends HTMLElement {
|
|
|
8389
8388
|
#boundHandleMediaPlay = this.#handleMediaPlay.bind(this);
|
|
8390
8389
|
#boundHandleMediaPause = this.#handleMediaPause.bind(this);
|
|
8391
8390
|
#boundHandleMediaEnded = this.#handleMediaEnded.bind(this);
|
|
8391
|
+
#controlsEl = null;
|
|
8392
|
+
#controlsWiredFor = null;
|
|
8393
|
+
#controlsWiredControls = null;
|
|
8394
|
+
#controlsSync = null;
|
|
8395
|
+
#controlsOnPlay = null;
|
|
8396
|
+
#controlsOnPause = null;
|
|
8397
|
+
#controlsOnSeek = null;
|
|
8392
8398
|
|
|
8393
8399
|
static get observedAttributes() {
|
|
8394
8400
|
return [
|
|
@@ -8465,27 +8471,16 @@ class FigMedia extends HTMLElement {
|
|
|
8465
8471
|
|
|
8466
8472
|
const ar = this.getAttribute("aspect-ratio");
|
|
8467
8473
|
if (ar) {
|
|
8468
|
-
this.style.setProperty("--aspect-ratio", ar);
|
|
8474
|
+
this.style.setProperty("--fig-media-aspect-ratio", ar);
|
|
8475
|
+
} else {
|
|
8476
|
+
this.style.setProperty("--fig-media-aspect-ratio", "4/3");
|
|
8469
8477
|
}
|
|
8470
8478
|
const fit = this.getAttribute("fit");
|
|
8471
8479
|
if (fit) {
|
|
8472
|
-
this.style.setProperty("--fit", fit);
|
|
8480
|
+
this.style.setProperty("--fig-media-fit", fit);
|
|
8473
8481
|
}
|
|
8474
8482
|
|
|
8475
|
-
|
|
8476
|
-
const chit = document.createElement("fig-chit");
|
|
8477
|
-
chit.setAttribute("data-generated", "");
|
|
8478
|
-
chit.setAttribute("size", "large");
|
|
8479
|
-
chit.setAttribute("data-type", this.mediaKind);
|
|
8480
|
-
chit.setAttribute("disabled", "");
|
|
8481
|
-
this.#applyChitBackground(chit);
|
|
8482
|
-
if (this.hasAttribute("checkerboard") && this.getAttribute("checkerboard") !== "false") {
|
|
8483
|
-
chit.setAttribute("checkerboard", "");
|
|
8484
|
-
}
|
|
8485
|
-
this.prepend(chit);
|
|
8486
|
-
}
|
|
8487
|
-
this.#chit = this.querySelector("fig-chit");
|
|
8488
|
-
this.#syncChitType();
|
|
8483
|
+
this.querySelectorAll("fig-chit[data-generated]").forEach((el) => el.remove());
|
|
8489
8484
|
this.#ensureMediaElement();
|
|
8490
8485
|
this.#syncGeneratedMediaElement();
|
|
8491
8486
|
|
|
@@ -8498,22 +8493,13 @@ class FigMedia extends HTMLElement {
|
|
|
8498
8493
|
disconnectedCallback() {
|
|
8499
8494
|
this.#fileInput?.removeEventListener("change", this.#boundHandleFileInput);
|
|
8500
8495
|
this.#removeMediaElementListeners();
|
|
8496
|
+
this.#removeControls();
|
|
8501
8497
|
if (this.#blobUrl) {
|
|
8502
8498
|
URL.revokeObjectURL(this.#blobUrl);
|
|
8503
8499
|
this.#blobUrl = null;
|
|
8504
8500
|
}
|
|
8505
8501
|
}
|
|
8506
8502
|
|
|
8507
|
-
#applyChitBackground(chit) {
|
|
8508
|
-
const cb = this.hasAttribute("checkerboard") && this.getAttribute("checkerboard") !== "false";
|
|
8509
|
-
chit.setAttribute("background", cb ? "url()" : "var(--figma-color-bg-secondary)");
|
|
8510
|
-
}
|
|
8511
|
-
|
|
8512
|
-
#syncChitType() {
|
|
8513
|
-
if (!this.#chit) return;
|
|
8514
|
-
this.#chit.setAttribute("data-type", this.mediaKind);
|
|
8515
|
-
}
|
|
8516
|
-
|
|
8517
8503
|
#removeMediaElementListeners() {
|
|
8518
8504
|
if (!this.#mediaEl) return;
|
|
8519
8505
|
if (this.#mediaEl.tagName === "VIDEO") {
|
|
@@ -8556,12 +8542,19 @@ class FigMedia extends HTMLElement {
|
|
|
8556
8542
|
video.setAttribute("data-generated", "");
|
|
8557
8543
|
video.className = "fig-media-element";
|
|
8558
8544
|
video.setAttribute("playsinline", "");
|
|
8559
|
-
video.preload = "
|
|
8545
|
+
video.preload = "auto";
|
|
8560
8546
|
this.prepend(video);
|
|
8561
8547
|
this.#mediaEl = video;
|
|
8562
8548
|
this.#mediaEl.addEventListener("play", this.#boundHandleMediaPlay);
|
|
8563
8549
|
this.#mediaEl.addEventListener("pause", this.#boundHandleMediaPause);
|
|
8564
8550
|
this.#mediaEl.addEventListener("ended", this.#boundHandleMediaEnded);
|
|
8551
|
+
const seekToFirstFrame = () => {
|
|
8552
|
+
if (this.#mediaEl?.autoplay) return;
|
|
8553
|
+
try {
|
|
8554
|
+
this.#mediaEl.currentTime = 0.001;
|
|
8555
|
+
} catch {}
|
|
8556
|
+
};
|
|
8557
|
+
this.#mediaEl.addEventListener("loadedmetadata", seekToFirstFrame, { once: true });
|
|
8565
8558
|
} else {
|
|
8566
8559
|
const img = document.createElement("img");
|
|
8567
8560
|
img.setAttribute("data-generated", "");
|
|
@@ -8601,11 +8594,163 @@ class FigMedia extends HTMLElement {
|
|
|
8601
8594
|
} else {
|
|
8602
8595
|
this.#mediaEl.removeAttribute("poster");
|
|
8603
8596
|
}
|
|
8604
|
-
this.#mediaEl.controls =
|
|
8597
|
+
this.#mediaEl.controls = false;
|
|
8598
|
+
this.#mediaEl.removeAttribute("controls");
|
|
8605
8599
|
this.#mediaEl.autoplay = this.#isEnabledAttr("autoplay", false);
|
|
8606
8600
|
this.#mediaEl.loop = this.#isEnabledAttr("loop", false);
|
|
8607
8601
|
this.#mediaEl.muted = this.#isEnabledAttr("muted", false);
|
|
8608
8602
|
this.#mediaEl.playsInline = true;
|
|
8603
|
+
this.#syncControlsVisibility();
|
|
8604
|
+
}
|
|
8605
|
+
|
|
8606
|
+
get mediaEl() {
|
|
8607
|
+
return this.#mediaEl;
|
|
8608
|
+
}
|
|
8609
|
+
|
|
8610
|
+
#syncControlsVisibility() {
|
|
8611
|
+
if (this.mediaKind !== "video") {
|
|
8612
|
+
this.#removeControls();
|
|
8613
|
+
return;
|
|
8614
|
+
}
|
|
8615
|
+
const userControls = this.querySelector(
|
|
8616
|
+
":scope > fig-media-controls:not([data-generated])",
|
|
8617
|
+
);
|
|
8618
|
+
if (userControls) {
|
|
8619
|
+
if (this.#controlsEl !== userControls) {
|
|
8620
|
+
this.#removeControls();
|
|
8621
|
+
this.#controlsEl = userControls;
|
|
8622
|
+
}
|
|
8623
|
+
this.#wireControlsToMedia();
|
|
8624
|
+
return;
|
|
8625
|
+
}
|
|
8626
|
+
if (this.#isEnabledAttr("controls", false)) {
|
|
8627
|
+
this.#ensureControls();
|
|
8628
|
+
} else {
|
|
8629
|
+
this.#removeControls();
|
|
8630
|
+
}
|
|
8631
|
+
}
|
|
8632
|
+
|
|
8633
|
+
#ensureControls() {
|
|
8634
|
+
if (this.#controlsEl && this.#controlsEl.isConnected) {
|
|
8635
|
+
this.#wireControlsToMedia();
|
|
8636
|
+
return;
|
|
8637
|
+
}
|
|
8638
|
+
const controls = document.createElement("fig-media-controls");
|
|
8639
|
+
controls.setAttribute("data-generated", "");
|
|
8640
|
+
controls.setAttribute("overlay", "");
|
|
8641
|
+
this.append(controls);
|
|
8642
|
+
this.#controlsEl = controls;
|
|
8643
|
+
this.#wireControlsToMedia();
|
|
8644
|
+
}
|
|
8645
|
+
|
|
8646
|
+
#wireControlsToMedia() {
|
|
8647
|
+
if (!this.#controlsEl || !this.#mediaEl) return;
|
|
8648
|
+
if (
|
|
8649
|
+
this.#controlsWiredFor === this.#mediaEl &&
|
|
8650
|
+
this.#controlsWiredControls === this.#controlsEl
|
|
8651
|
+
) {
|
|
8652
|
+
return;
|
|
8653
|
+
}
|
|
8654
|
+
this.#unwireControls();
|
|
8655
|
+
|
|
8656
|
+
const controls = this.#controlsEl;
|
|
8657
|
+
const video = this.#mediaEl;
|
|
8658
|
+
this.#controlsWiredFor = video;
|
|
8659
|
+
this.#controlsWiredControls = controls;
|
|
8660
|
+
|
|
8661
|
+
let pendingSeekTime = null;
|
|
8662
|
+
const syncFromVideo = () => {
|
|
8663
|
+
controls.playing = !video.paused && !video.ended;
|
|
8664
|
+
if (Number.isFinite(video.duration)) controls.duration = video.duration;
|
|
8665
|
+
if (pendingSeekTime !== null) {
|
|
8666
|
+
if (Math.abs(video.currentTime - pendingSeekTime) < 0.25) {
|
|
8667
|
+
pendingSeekTime = null;
|
|
8668
|
+
} else {
|
|
8669
|
+
return;
|
|
8670
|
+
}
|
|
8671
|
+
}
|
|
8672
|
+
controls.time = video.currentTime || 0;
|
|
8673
|
+
};
|
|
8674
|
+
const onPlay = () => {
|
|
8675
|
+
const p = video.play?.();
|
|
8676
|
+
if (p && typeof p.catch === "function") p.catch(() => {});
|
|
8677
|
+
};
|
|
8678
|
+
const onPause = () => video.pause?.();
|
|
8679
|
+
const onSeek = (e) => {
|
|
8680
|
+
const next = Number(e?.detail?.time);
|
|
8681
|
+
if (!Number.isFinite(next)) return;
|
|
8682
|
+
pendingSeekTime = next;
|
|
8683
|
+
try { video.currentTime = next; } catch {}
|
|
8684
|
+
};
|
|
8685
|
+
|
|
8686
|
+
this.#controlsSync = syncFromVideo;
|
|
8687
|
+
this.#controlsOnPlay = onPlay;
|
|
8688
|
+
this.#controlsOnPause = onPause;
|
|
8689
|
+
this.#controlsOnSeek = onSeek;
|
|
8690
|
+
|
|
8691
|
+
video.addEventListener("play", syncFromVideo);
|
|
8692
|
+
video.addEventListener("pause", syncFromVideo);
|
|
8693
|
+
video.addEventListener("ended", syncFromVideo);
|
|
8694
|
+
video.addEventListener("timeupdate", syncFromVideo);
|
|
8695
|
+
video.addEventListener("loadedmetadata", syncFromVideo);
|
|
8696
|
+
video.addEventListener("durationchange", syncFromVideo);
|
|
8697
|
+
video.addEventListener("seeked", syncFromVideo);
|
|
8698
|
+
controls.addEventListener("play", onPlay);
|
|
8699
|
+
controls.addEventListener("pause", onPause);
|
|
8700
|
+
controls.addEventListener("seek", onSeek);
|
|
8701
|
+
|
|
8702
|
+
syncFromVideo();
|
|
8703
|
+
}
|
|
8704
|
+
|
|
8705
|
+
#unwireControls() {
|
|
8706
|
+
const video = this.#controlsWiredFor;
|
|
8707
|
+
const controls = this.#controlsWiredControls;
|
|
8708
|
+
if (video && this.#controlsSync) {
|
|
8709
|
+
video.removeEventListener("play", this.#controlsSync);
|
|
8710
|
+
video.removeEventListener("pause", this.#controlsSync);
|
|
8711
|
+
video.removeEventListener("ended", this.#controlsSync);
|
|
8712
|
+
video.removeEventListener("timeupdate", this.#controlsSync);
|
|
8713
|
+
video.removeEventListener("loadedmetadata", this.#controlsSync);
|
|
8714
|
+
video.removeEventListener("durationchange", this.#controlsSync);
|
|
8715
|
+
video.removeEventListener("seeked", this.#controlsSync);
|
|
8716
|
+
}
|
|
8717
|
+
if (controls) {
|
|
8718
|
+
if (this.#controlsOnPlay) controls.removeEventListener("play", this.#controlsOnPlay);
|
|
8719
|
+
if (this.#controlsOnPause) controls.removeEventListener("pause", this.#controlsOnPause);
|
|
8720
|
+
if (this.#controlsOnSeek) controls.removeEventListener("seek", this.#controlsOnSeek);
|
|
8721
|
+
}
|
|
8722
|
+
this.#controlsWiredFor = null;
|
|
8723
|
+
this.#controlsWiredControls = null;
|
|
8724
|
+
this.#controlsSync = null;
|
|
8725
|
+
this.#controlsOnPlay = null;
|
|
8726
|
+
this.#controlsOnPause = null;
|
|
8727
|
+
this.#controlsOnSeek = null;
|
|
8728
|
+
}
|
|
8729
|
+
|
|
8730
|
+
#removeControls() {
|
|
8731
|
+
this.#unwireControls();
|
|
8732
|
+
if (!this.#controlsEl) return;
|
|
8733
|
+
if (this.#controlsEl.hasAttribute("data-generated")) {
|
|
8734
|
+
this.#controlsEl.remove();
|
|
8735
|
+
}
|
|
8736
|
+
this.#controlsEl = null;
|
|
8737
|
+
}
|
|
8738
|
+
|
|
8739
|
+
toggle() {
|
|
8740
|
+
if (!this.#mediaEl || this.mediaKind !== "video") return;
|
|
8741
|
+
if (this.#mediaEl.paused || this.#mediaEl.ended) this.play();
|
|
8742
|
+
else this.pause();
|
|
8743
|
+
}
|
|
8744
|
+
|
|
8745
|
+
play() {
|
|
8746
|
+
if (this.mediaKind !== "video" || !this.#mediaEl) return;
|
|
8747
|
+
const p = this.#mediaEl.play();
|
|
8748
|
+
if (p && typeof p.catch === "function") p.catch(() => {});
|
|
8749
|
+
}
|
|
8750
|
+
|
|
8751
|
+
pause() {
|
|
8752
|
+
if (this.mediaKind !== "video" || !this.#mediaEl) return;
|
|
8753
|
+
this.#mediaEl.pause();
|
|
8609
8754
|
}
|
|
8610
8755
|
|
|
8611
8756
|
#createFileInput() {
|
|
@@ -8724,7 +8869,6 @@ class FigMedia extends HTMLElement {
|
|
|
8724
8869
|
}
|
|
8725
8870
|
|
|
8726
8871
|
if (name === "type") {
|
|
8727
|
-
this.#syncChitType();
|
|
8728
8872
|
this.#ensureMediaElement();
|
|
8729
8873
|
this.#syncGeneratedMediaElement();
|
|
8730
8874
|
if (this.#fileInput) {
|
|
@@ -8750,28 +8894,17 @@ class FigMedia extends HTMLElement {
|
|
|
8750
8894
|
|
|
8751
8895
|
if (name === "aspect-ratio") {
|
|
8752
8896
|
if (newValue) {
|
|
8753
|
-
this.style.setProperty("--aspect-ratio", newValue);
|
|
8897
|
+
this.style.setProperty("--fig-media-aspect-ratio", newValue);
|
|
8754
8898
|
} else {
|
|
8755
|
-
this.style.removeProperty("--aspect-ratio");
|
|
8899
|
+
this.style.removeProperty("--fig-media-aspect-ratio");
|
|
8756
8900
|
}
|
|
8757
8901
|
}
|
|
8758
8902
|
|
|
8759
8903
|
if (name === "fit") {
|
|
8760
8904
|
if (newValue) {
|
|
8761
|
-
this.style.setProperty("--fit", newValue);
|
|
8905
|
+
this.style.setProperty("--fig-media-fit", newValue);
|
|
8762
8906
|
} else {
|
|
8763
|
-
this.style.removeProperty("--fit");
|
|
8764
|
-
}
|
|
8765
|
-
}
|
|
8766
|
-
|
|
8767
|
-
if (name === "checkerboard") {
|
|
8768
|
-
if (this.#chit) {
|
|
8769
|
-
if (newValue !== null && newValue !== "false") {
|
|
8770
|
-
this.#chit.setAttribute("checkerboard", "");
|
|
8771
|
-
} else {
|
|
8772
|
-
this.#chit.removeAttribute("checkerboard");
|
|
8773
|
-
}
|
|
8774
|
-
this.#applyChitBackground(this.#chit);
|
|
8907
|
+
this.style.removeProperty("--fig-media-fit");
|
|
8775
8908
|
}
|
|
8776
8909
|
}
|
|
8777
8910
|
|
|
@@ -8802,6 +8935,203 @@ class FigVideo extends FigMedia {
|
|
|
8802
8935
|
}
|
|
8803
8936
|
customElements.define("fig-video", FigVideo);
|
|
8804
8937
|
|
|
8938
|
+
/**
|
|
8939
|
+
* <fig-media-controls> — Standalone playback controls UI.
|
|
8940
|
+
*
|
|
8941
|
+
* Renders a play/pause button, a scrubber slider, and a MM:SS time display.
|
|
8942
|
+
* Holds its own state via attributes — no media element required.
|
|
8943
|
+
*
|
|
8944
|
+
* Attributes:
|
|
8945
|
+
* - `playing` (boolean presence) — current play/pause state
|
|
8946
|
+
* - `duration` (number, seconds) — total track length
|
|
8947
|
+
* - `time` (number, seconds) — current playhead position
|
|
8948
|
+
*
|
|
8949
|
+
* Events:
|
|
8950
|
+
* - `play` — emitted when the user toggles playback on (detail: { playing: true })
|
|
8951
|
+
* - `pause` — emitted when the user toggles playback off (detail: { playing: false })
|
|
8952
|
+
* - `seek` — emitted when the user drags the scrubber (detail: { time })
|
|
8953
|
+
*
|
|
8954
|
+
* Properties: `playing`, `duration`, `time` mirror the attributes.
|
|
8955
|
+
*/
|
|
8956
|
+
class FigMediaControls extends HTMLElement {
|
|
8957
|
+
#playBtn = null;
|
|
8958
|
+
#playTooltip = null;
|
|
8959
|
+
#timeSlider = null;
|
|
8960
|
+
#timeEl = null;
|
|
8961
|
+
#userSeeking = false;
|
|
8962
|
+
#rendered = false;
|
|
8963
|
+
|
|
8964
|
+
static get observedAttributes() {
|
|
8965
|
+
return ["playing", "duration", "time"];
|
|
8966
|
+
}
|
|
8967
|
+
|
|
8968
|
+
connectedCallback() {
|
|
8969
|
+
this.#render();
|
|
8970
|
+
this.#syncPlayingUi();
|
|
8971
|
+
this.#syncTimeUi();
|
|
8972
|
+
}
|
|
8973
|
+
|
|
8974
|
+
get playing() {
|
|
8975
|
+
return this.hasAttribute("playing") && this.getAttribute("playing") !== "false";
|
|
8976
|
+
}
|
|
8977
|
+
set playing(value) {
|
|
8978
|
+
if (value) this.setAttribute("playing", "");
|
|
8979
|
+
else this.removeAttribute("playing");
|
|
8980
|
+
}
|
|
8981
|
+
|
|
8982
|
+
get duration() {
|
|
8983
|
+
const n = Number(this.getAttribute("duration"));
|
|
8984
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
8985
|
+
}
|
|
8986
|
+
set duration(value) {
|
|
8987
|
+
const n = Number(value);
|
|
8988
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
8989
|
+
this.removeAttribute("duration");
|
|
8990
|
+
return;
|
|
8991
|
+
}
|
|
8992
|
+
this.setAttribute("duration", String(n));
|
|
8993
|
+
}
|
|
8994
|
+
|
|
8995
|
+
get time() {
|
|
8996
|
+
const n = Number(this.getAttribute("time"));
|
|
8997
|
+
return Number.isFinite(n) && n >= 0 ? n : 0;
|
|
8998
|
+
}
|
|
8999
|
+
set time(value) {
|
|
9000
|
+
const n = Number(value);
|
|
9001
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
9002
|
+
this.removeAttribute("time");
|
|
9003
|
+
return;
|
|
9004
|
+
}
|
|
9005
|
+
this.setAttribute("time", String(n));
|
|
9006
|
+
}
|
|
9007
|
+
|
|
9008
|
+
attributeChangedCallback(name) {
|
|
9009
|
+
if (!this.#rendered) return;
|
|
9010
|
+
if (name === "playing") this.#syncPlayingUi();
|
|
9011
|
+
if (name === "duration" || name === "time") this.#syncTimeUi();
|
|
9012
|
+
}
|
|
9013
|
+
|
|
9014
|
+
#render() {
|
|
9015
|
+
if (this.#rendered) return;
|
|
9016
|
+
this.#rendered = true;
|
|
9017
|
+
|
|
9018
|
+
const tooltip = document.createElement("fig-tooltip");
|
|
9019
|
+
tooltip.setAttribute("text", "Play");
|
|
9020
|
+
const btn = document.createElement("fig-button");
|
|
9021
|
+
btn.setAttribute("variant", "ghost");
|
|
9022
|
+
btn.setAttribute("size", "small");
|
|
9023
|
+
btn.setAttribute("icon", "true");
|
|
9024
|
+
btn.setAttribute("aria-label", "Play");
|
|
9025
|
+
const icon = document.createElement("span");
|
|
9026
|
+
icon.className = "fig-mask-icon fig-media-controls-play-icon";
|
|
9027
|
+
icon.style.setProperty("--icon", "var(--icon-play)");
|
|
9028
|
+
icon.style.setProperty("--size", "1.5rem");
|
|
9029
|
+
btn.append(icon);
|
|
9030
|
+
tooltip.append(btn);
|
|
9031
|
+
btn.addEventListener("click", (e) => {
|
|
9032
|
+
e.preventDefault();
|
|
9033
|
+
e.stopPropagation();
|
|
9034
|
+
this.toggle();
|
|
9035
|
+
});
|
|
9036
|
+
|
|
9037
|
+
const slider = document.createElement("fig-slider");
|
|
9038
|
+
slider.setAttribute("variant", "neue");
|
|
9039
|
+
slider.setAttribute("min", "0");
|
|
9040
|
+
slider.setAttribute("max", String(this.duration));
|
|
9041
|
+
slider.setAttribute("step", "0.1");
|
|
9042
|
+
slider.setAttribute("value", String(this.time));
|
|
9043
|
+
slider.setAttribute("full", "");
|
|
9044
|
+
const timeEl = document.createElement("label");
|
|
9045
|
+
timeEl.className = "fig-media-controls-time";
|
|
9046
|
+
timeEl.textContent = this.#formatTime(this.time);
|
|
9047
|
+
|
|
9048
|
+
const handleSeek = (e) => {
|
|
9049
|
+
const host = e.currentTarget;
|
|
9050
|
+
const next = Number(host?.value);
|
|
9051
|
+
if (!Number.isFinite(next)) return;
|
|
9052
|
+
this.#userSeeking = true;
|
|
9053
|
+
this.setAttribute("time", String(next));
|
|
9054
|
+
this.dispatchEvent(
|
|
9055
|
+
new CustomEvent("seek", {
|
|
9056
|
+
bubbles: true,
|
|
9057
|
+
composed: true,
|
|
9058
|
+
detail: { time: next },
|
|
9059
|
+
}),
|
|
9060
|
+
);
|
|
9061
|
+
requestAnimationFrame(() => {
|
|
9062
|
+
this.#userSeeking = false;
|
|
9063
|
+
});
|
|
9064
|
+
};
|
|
9065
|
+
slider.addEventListener("input", handleSeek);
|
|
9066
|
+
slider.addEventListener("change", handleSeek);
|
|
9067
|
+
|
|
9068
|
+
this.append(tooltip, slider, timeEl);
|
|
9069
|
+
|
|
9070
|
+
this.#playBtn = btn;
|
|
9071
|
+
this.#playTooltip = tooltip;
|
|
9072
|
+
this.#timeSlider = slider;
|
|
9073
|
+
this.#timeEl = timeEl;
|
|
9074
|
+
}
|
|
9075
|
+
|
|
9076
|
+
#formatTime(seconds) {
|
|
9077
|
+
if (!Number.isFinite(seconds) || seconds < 0) seconds = 0;
|
|
9078
|
+
const total = Math.floor(seconds);
|
|
9079
|
+
const m = Math.floor(total / 60);
|
|
9080
|
+
const s = total % 60;
|
|
9081
|
+
return `${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
9082
|
+
}
|
|
9083
|
+
|
|
9084
|
+
#syncPlayingUi() {
|
|
9085
|
+
if (!this.#playBtn) return;
|
|
9086
|
+
const playing = this.playing;
|
|
9087
|
+
this.#playBtn.setAttribute("aria-label", playing ? "Pause" : "Play");
|
|
9088
|
+
this.#playTooltip?.setAttribute("text", playing ? "Pause" : "Play");
|
|
9089
|
+
const icon = this.#playBtn.querySelector(".fig-media-controls-play-icon");
|
|
9090
|
+
if (icon) {
|
|
9091
|
+
icon.style.setProperty(
|
|
9092
|
+
"--icon",
|
|
9093
|
+
playing ? "var(--icon-pause)" : "var(--icon-play)",
|
|
9094
|
+
);
|
|
9095
|
+
}
|
|
9096
|
+
}
|
|
9097
|
+
|
|
9098
|
+
#syncTimeUi() {
|
|
9099
|
+
if (!this.#timeSlider) return;
|
|
9100
|
+
const duration = this.duration;
|
|
9101
|
+
if (Number(this.#timeSlider.getAttribute("max")) !== duration) {
|
|
9102
|
+
this.#timeSlider.setAttribute("max", String(duration));
|
|
9103
|
+
}
|
|
9104
|
+
const t = this.time;
|
|
9105
|
+
if (!this.#userSeeking) {
|
|
9106
|
+
this.#timeSlider.setAttribute("value", String(t));
|
|
9107
|
+
}
|
|
9108
|
+
if (this.#timeEl) this.#timeEl.textContent = this.#formatTime(t);
|
|
9109
|
+
}
|
|
9110
|
+
|
|
9111
|
+
toggle() {
|
|
9112
|
+
const next = !this.playing;
|
|
9113
|
+
this.playing = next;
|
|
9114
|
+
this.dispatchEvent(
|
|
9115
|
+
new CustomEvent(next ? "play" : "pause", {
|
|
9116
|
+
bubbles: true,
|
|
9117
|
+
composed: true,
|
|
9118
|
+
detail: { playing: next },
|
|
9119
|
+
}),
|
|
9120
|
+
);
|
|
9121
|
+
}
|
|
9122
|
+
|
|
9123
|
+
play() {
|
|
9124
|
+
if (this.playing) return;
|
|
9125
|
+
this.toggle();
|
|
9126
|
+
}
|
|
9127
|
+
|
|
9128
|
+
pause() {
|
|
9129
|
+
if (!this.playing) return;
|
|
9130
|
+
this.toggle();
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
customElements.define("fig-media-controls", FigMediaControls);
|
|
9134
|
+
|
|
8805
9135
|
/* File Upload Input */
|
|
8806
9136
|
class FigInputFile extends HTMLElement {
|
|
8807
9137
|
static observedAttributes = ["accepts", "label", "disabled", "multiple", "variant", "url"];
|
package/package.json
CHANGED