* @typedef {Object} Converter
* @property {(markdown: string) => string} makeHtml
* @typedef {new () => Converter} ConverterConstructor
* @typedef {Object} Showdown
* @property {ConverterConstructor} Converter
const showdown = window["showdown"]
class MarkdownPreviewElement extends HTMLElement {
static selector = "app-contenteditable"
#converter = new showdown.Converter()
return this.getAttribute("label") || ""
return this.getAttribute("value") || ""
/** @type {HTMLDivElement} */
const el = /** @type {HTMLTextAreaElement} */ (
this.shadowRoot?.getElementById("input")
const el = /** @type {HTMLDivElement} */ (
this.shadowRoot?.getElementById("output")
const markdown = this.#input.value
const html = this.#converter.makeHtml(markdown)
this.#output.innerHTML = html
const event = new CustomEvent("change", {
<textarea id="input" value="${this.#value}"></textarea>
this.#wrapper = document.createElement("div")
this.#wrapper.className = MarkdownPreviewElement.selector
this.#wrapper.innerHTML = this.#content
const shadow = this.attachShadow({ mode: "open" })
shadow.appendChild(this.#wrapper)
this.#input.addEventListener("input", () => this.#onchange())
this.#output.innerHTML = this.#converter.makeHtml(this.#value)
customElements.define(MarkdownPreviewElement.selector, MarkdownPreviewElement)