<x-spoiler>
  Содержимое тега.
</x-spoiler>

<br><br>

<x-spoiler text-when-close="Узреть" text-when-open="Скрыть">
  Ещё один x-spoiler с настроенным текстом
</x-spoiler>

<br><br>

<x-spoiler opened>
  Изначально открытый элемент
</x-spoiler>
class XSpoiler extends HTMLElement {
  constructor() {
    super();

    this.text = {
      "when-close": "Развернуть",
      "when-open": "Свернуть"
    };
    
    this.events = {
      "close": new CustomEvent("x-spoiler.changed", {
        bubbles: true,
        detail: {opened: false},
      }),
      "open": new CustomEvent("x-spoiler.changed", {
        bubbles: true,
        detail: {opened: true},
      }),
    };

    this.attachShadow({mode: "open"});
    this.shadowRoot.innerHTML = `
      <style>
        :host > section {
          display: none;
        }
        :host([opened]) > section {
          display: block;
        }
      </style>
      <button type="button">${this.text["when-close"]}</button>
      <section><slot></slot></section>
    `;

    this.shadowRoot.querySelector("button").addEventListener("click", () => {
      this.opened = !this.opened;
    });
  }

  get opened() {
    return this.getAttribute("opened") !== null;
  }

  set opened(state) {
    if (!!state) {
      this.setAttribute("opened", "");
    } else {
      this.removeAttribute("opened");
    }
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    switch (attrName) {
      case "opened":
        const opened = newVal !== null;
        const button = this.shadowRoot.querySelector("button");
        const text = this.text[opened ? "when-open" : "when-close"];
        button.textContent = text;
        this.dispatchEvent(this.events[opened ? "open" : "close"]);
        break;

      case "text-when-open":
        this.text["when-open"] = newVal;
        if (this.opened) {
          this.shadowRoot.querySelector("button").textContent = newVal;
        }
        break;

      case "text-when-close":
        this.text["when-close"] = newVal;
        if (!this.opened) {
          this.shadowRoot.querySelector("button").textContent = newVal;
        }
        break;
    }
  }

  static get observedAttributes() {
    return [
      "opened",
      "text-when-open",
      "text-when-close",
    ];
  }
}

customElements.define("x-spoiler", XSpoiler);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.