// @ts-check import { html } from 'dhtml' import { createRoot, invalidate } from 'dhtml/client' function transition(cb) { if ('startViewTransition' in document) { document.startViewTransition(cb) } else { cb() } } function classes(...args) { const classes = args.flatMap(a => (a ? a.split(' ') : [])) return node => { node.classList.add(...classes) return () => { node.classList.remove(...classes) } } } const autofocus = node => node.focus() const autoselect = node => node.setSelectionRange(0, node.value.length) class TodoItem { id = crypto.randomUUID() completed = false editing = false constructor(app, title) { this.app = app this.title = title } render() { return html`
  • { e.preventDefault() transition(async () => { this.completed = e.target.checked await invalidate(this, this.app) }) }} />
    ${this.editing ? html`
    { const value = e.target.value.trim() if (value) { transition(async () => { this.title = value this.editing = false await invalidate(this) }) } }} onkeydown=${e => { if (e.key === 'Enter') { const value = e.target.value.trim() if (value) { transition(async () => { this.title = value this.editing = false await invalidate(this) }) } } }} />
    ` : null}
  • ` } } class App { todos = [] get(id) { return this.todos.find(todo => todo.id === id) } remove(id) { this.todos = this.todos.filter(todo => todo.id !== id) } filter = 'All' render() { const completedCount = this.todos.filter(todo => todo.completed).length const activeCount = this.todos.length - completedCount return html`

    todos

    { if (event.key === 'Enter') { const value = event.target.value.trim() if (value) { transition(async () => { this.todos.push(new TodoItem(this, value)) event.target.value = '' await invalidate(this) }) } } }} />
    ${this.todos.length > 0 ? html`
    { transition(async () => { for (const todo of this.todos) todo.completed = e.target.checked await invalidate(this) }) }} />
    ` : null} ` } } const app = new App() globalThis.app = app document.body.addEventListener('keypress', e => { if (e.ctrlKey && e.key === 'i') invalidate(app) }) app.todos.push(new TodoItem(app, 'hello')) app.todos.push(new TodoItem(app, 'world')) const rootEl = document.getElementById('root') if (!rootEl) throw new Error('Root element not found') createRoot(rootEl).render(app)