<to-dos>

by WebComponents

HTML

<to-dos>
  <to-do>Buy milk</to-do>
  <to-do>Buy eggs</to-do>
</to-dos>
<script>
  const createElement = (tag, props = {}) => Object.assign(document.createElement(tag), props)
  customElements.define("to-dos", class extends HTMLElement {
      constructor() {
        super().attachShadow({ mode: "open" }).append(
            createElement("style", {
              textContent: `:host{font:20px arial}` +
              						 `*:not(input){;padding:1em 0}` + 
              						 `::slotted(to-do) {display:block}` +
                					 `::slotted(to-do)::before {content: "• "}`,
            }),
            createElement("b", { textContent: "Todo App" }),
            createElement("br"),
            (this.input = createElement("input", {
              placeholder: "Add new todo",
              onkeypress: (evt) => this[evt.key] && this[evt.key](), // execute this["Enter"]()
            })),
            (this.count = createElement("div")),
            createElement("slot"), // display all <to-do> from lightDOM
          ) // append
        let todosElement = this; // so everything in scope can directly call this Element
        customElements.define("to-do", class extends HTMLElement {
            connectedCallback() {
              this.todoText = this.innerHTML;
              todosElement.count.textContent = `Number of Todo items: ${todosElement.length}`;
              this.append(" ", createElement("button", {
                  							 textContent: "remove",
                  							 onclick: _ => this.remove(),
                							 }),
              ) // append
            } // connectedCallback
          }) // defined to-do
      } // <to-dos> constructor
      Enter() {
        this.addTodo(this.input.value)
        this.input.value = ""
        console.log(this.length, this.todos);
      }
      addTodo(innerHTML) {
        this.append(createElement("to-do", { innerHTML }))
      }
      get todos() {
        return...