Source: Toast.js

import { BaseCustomElement, RichString, Message } from "brut-js"

/**
 * Support for a toast component, which is a momentary message shown to the user, for example
 * if an aynchronous update has occured.
 *
 * To use this element, you should set up your CSS so that the element is hidden if there is no `key`
 * attribute set.  When the `key` attribute *is* set, the element should be shown.  You can use CSS animations
 * for this as needed, but the main thing to remember is that, without a `key` attribute, this element
 * should not be visible.
 *
 * The `key` attribute is expected to be an i18n key that references a `<brut-i18n-message>` on
 * the page, which contains the actual message to show the visitor.  When the `key` attribute is
 * set, this component will find an `<output>` inside itself, and replace the entire contents
 * with a `<brut-message>` component, using the same `key`.  This will cause the `<brut-message>`
 * to look up the key and put that text into the element.
 *
 * In addition to this lookup, this element will set appropriate ARIA attributes on the
 * created `<brut-message>` element.
 *
 * Further, if there is a `<button>` inside this element, it will be used to close the toast by removing the
 * `key` attribute (which, assuming your CSS is correct, will hide the element).
 *
 * @property {string} key - an I18n key of the message to show in the toast.  When you generate
 *                          the toast's HTML on the server, do not set key.  Then, when you need
 *                          to display the toast, use JavaScript to set the key. This will
 *                          trigger its behavior as described above.
 *
 * @example
 * <style>
 *   brut-toast {
 *     display: none;
 *   }
 *   brut-toast[key] {
 *     display: block;
 *   }
 * </style>
 * <brut-i18n-translation key="toast.saved">Save successful</brut-i18n-translation>
 * <brut-toast>
 *   <div>
 *   <output></output>
 *   <button>Close</button>
 *   </div>
 * </brut-toast>
 * <!-- now, if you set the key to "toast.saved", the HTML will be changed as follows: -->
 * <brut-toast key="toast.saved">
 *   <div>
 *   <output>
 *     <brut-message key="toast.saved" role="status" aria-live="polite" aria-atomic="true">
 *       Save successful
 *     </brut-message>
 *   </output>
 *   <button>Close</button>
 *   </div>
 * </brut-toast>
 */
class Toast extends BaseCustomElement {
  static tagName = "brut-toast"

  static observedAttributes = [
    "show-warnings",
    "key",
  ]

  #key = null
  #closeListener = (event) => {
    event.preventDefault()
    this.removeAttribute("key")
  }

  keyChangedCallback({newValue}) {
    this.#key = RichString.fromString(newValue)
  }

  update() {
    const closeButton = this.querySelector("button")

    if (closeButton) {
      closeButton.addEventListener("click", this.#closeListener)
    }

    if (!this.#key) {
      return
    }
    const output = this.querySelector("output")
    if (!output) {
      this.logger.warn("No <output> element found, so toast will not be displayed")
      return
    }
    const messageNode = Message.createElement(document,{
      "key": this.#key,
      "role": "status",
      "aria-live": "polite",
      "aria-atomic": "true"
    })
    output.replaceChildren(messageNode)
    this.style.animation = "none"
    this.offsetWidth // Trigger reflow to restart the animation
    this.style.animation = ""
  }
}
export default Toast