0.3.1 • Published 2 years ago

mixter v0.3.1

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

A Web Components framework.

Examples

/** @jsxImportSource mixter/jsx */
import { create } from 'mixter'
import { jsx } from 'mixter/jsx'

export const App = create(
  class {
    who = 'world'
  },
  ({ $ }) => {
    const { render } = jsx($)
    render(({ who }) => <div>Hello, {who}!</div>)
  }
)
/** @jsxImportSource mixter/jsx */
import { attrs, css, event, events, mixter, props, shadow, state } from 'mixter'
import { jsx, refs } from 'mixter/jsx'

type Todo = {
  name: string
  done: boolean
}

export class TodoApp extends mixter(
  // Extend basic HTMLElement
  HTMLElement,
  // Attach ShadowRoot
  shadow(),
  // Declare the events we will be emitting, this allows for
  // `el.onsomeevent = fn` to be statically typed
  events<{
    done: CustomEvent<{ todo: Todo }>
  }>(),
  // Element attributes, can be String, Number, Boolean
  attrs(
    class {
      name = 'My todos!'
      background = '#446'
      todoColor = '#fff'
      doneColor = '#999'
    }
  ),
  // Declare properties, can be any type
  props(
    class {
      form?: HTMLFormElement
      textInput?: HTMLInputElement
      todos: Todo[] = [
        { name: 'create todo list', done: true },
        { name: 'wow todo list', done: false },
        { name: 'so much todo', done: false },
      ]
      onTodoCleanup?: () => void
      onTodoAdd?: () => void
      onTodoChange?: (todo: Todo) => (
        e: Event & { currentTarget: HTMLInputElement },
      ) => void
    }
  ),
  // Reactive state handler
  state<TodoApp>(({ $, effect, reduce }) => {
    // Use jsx, returns the render function which acts like a `reduce`
    // that instead renders on the root element.
    const { render } = jsx($)
    // Use refs, `ref.someElement` can now be passed to `ref=` attributes in JSX.
    // Refs are bidirectional, meaning if they already have a reference, passing them to
    // a JSX element will "give" that element by reference, instead of filling "from" it.
    const { ref } = refs($)

    $.onTodoAdd = reduce(({ textInput, todos }) => (event().prevent.stop(() => {
      $.todos = [mixter.todos, { name: textInput.value, done: false }]
      textInput.value = ''
      textInput.focus()
      // initialized with a noop function otherwise the render() below will never "fire"
      // because we are in its dependencies and our dependency `textInput` is
      // assigned inside it.
    })), () => {})

    $.onTodoChange = reduce(({ host, todos }) => (todo => (e => {
      todo.done = e.currentTarget.checked
      $.todos = [mixter.todos]
      if (todo.done) host.dispatch('done', { todo })
    })))

    $.onTodoCleanup = reduce(({ todos }) => (() => {
      $.todos = [mixter.todos.filter(todo => !todo.done)]
    }), () => {})

    effect(({ todos }) => {
      if (todos.length > 0 && todos.filter(todo => !todo.done).length === 0)
        alert('All done! Congrats!')
    })

    render((
      {
        name,
        background,
        doneColor,
        onTodoAdd,
        onTodoChange,
        onTodoCleanup,
        todoColor,
        todos,
      },
    ) => (
      <>
        <style>
          {css`
          width: 250px;
          padding: 10px;
          display: inline-flex;
          flex-flow: column nowrap;
          align-items: center;
          font-family: 'Ubuntu Mono', monospace;
          background: ${background};
          color: ${todoColor};
          .done {
            color: ${doneColor};
          }
          `()}
        </style>

        <h1>{name}</h1>

        <form
          onsubmit={onTodoAdd}
          style={{
            display: 'flex',
            flexFlow: 'column nowrap',
            alignItems: 'center',
          }}
        >
          <div>
            <input ref={ref.textInput} type="text" autofocus />
            <button type="submit">Add</button>
          </div>

          <ol>
            {todos.map(todo => (
              <li class={todo.done ? 'done' : ''}>
                <label>
                  <input
                    type="checkbox"
                    checked={todo.done}
                    onchange={onTodoChange(todo)}
                  />
                  {todo.done
                    ? <strike>{todo.name}</strike>
                    : todo.name}
                </label>
              </li>
            ))}
          </ol>

          {todos.some(todo => todo.done)
            && <button onclick={onTodoCleanup}>Cleanup</button>}
        </form>
      </>
    ))
  })
) {}

customElements.define('todo-app', TodoApp)

const todoApp = new TodoApp()

document.body.appendChild(todoApp)

todoApp.ondone = ({ detail: { todo } }) => {
  console.log('done', todo)
  // change background to a random hue every time we finish a todo
  todoApp.background = `hsl(${Math.random() * 360}, 30%, 20%)`
}
import { App } from './simple'

customElements.define('x-app', App)

const app = new App()
document.body.appendChild(app)

API