class OutfitViewer extends HTMLElement {
#internals;
constructor() {
super();
this.#internals = this.attachInternals();
this.#setIsPlaying(false);
}
connectedCallback() {
setTimeout(() => this.#connectToChildren(), 0);
}
#connectToChildren() {
const playPauseToggle = document.querySelector(".play-pause-toggle");
// Read our initial playing state from the toggle, and subscribe to changes.
this.#setIsPlaying(playPauseToggle.checked);
playPauseToggle.addEventListener("change", () => {
this.#setIsPlaying(playPauseToggle.checked);
});
}
#setIsPlaying(isPlaying) {
// TODO: Listen for changes to the child list, and add `playing` when new
// nodes arrive, if playing.
if (isPlaying) {
this.#internals.states.add("playing");
for (const child of this.children) {
child.setAttribute("playing", "");
}
} else {
this.#internals.states.delete("playing");
for (const child of this.children) {
child.removeAttribute("playing");
}
}
}
}
class OutfitLayer extends HTMLElement {
static observedAttributes = ["playing"];
#internals;
constructor() {
super();
this.#internals = this.attachInternals();
// An starts in the loading state, and then might very
// quickly decide it's not after `#connectToChildren`. This is to prevent a
// flash of *non*-loading state, when a new layer loads in. (e.g. In the
// time between our parent loading, which shows the loading
// spinner; and us being marked `:state(loading)`, which shows the loading
// spinner; we don't want the loading spinner to do its usual *immediate*
// total fade-out; then have to fade back in again, on the usual delay.)
this.#setStatus("loading");
}
connectedCallback() {
setTimeout(() => this.#connectToChildren(), 0);
}
disconnectedCallback() {
window.removeEventListener("message", this.#onMessage);
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "playing") {
const isPlaying = newValue != null;
this.#forwardIsPlaying(isPlaying);
}
}
#connectToChildren() {
const image = this.querySelector("img");
const iframe = this.querySelector("iframe");
if (image) {
image.addEventListener("load", () => this.#setStatus("loaded"));
image.addEventListener("error", () => this.#setStatus("error"));
this.#setStatus(image.complete ? "loaded" : "loading");
} else if (iframe) {
this.iframe = iframe;
window.addEventListener("message", (m) => this.#onMessage(m));
this.iframe.addEventListener("error", () => this.#setStatus("error"));
this.#setStatus("loading");
} else {
throw new Error(` must contain an or