Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
Apr 25, 2021
Apr 24, 2021
src
Apr 25, 2021
Apr 25, 2021
Apr 25, 2021
Feb 3, 2020
Feb 3, 2020
Apr 25, 2021
Mar 31, 2020
Apr 25, 2021
Apr 25, 2021
Apr 25, 2021

spect

Micro DOM aspects toolkit.

npm bundle size npm

Run


Spect is minimalistic DOM toolkit, providing aspects, reactivity and observables with 3 essential functions − $, h and v, for better compact UI code and efficient manipulations.

💎 Separation of cross-cutting concerns with CSS-like aspects.

🌳 Native first − healthy semantic HTML tree, vanilla js friendly.

📲 Unblocked, facilitated progressive enhancement.

🐤 No entry barrier − already familiar functions.

💫 0 tooling, 0 boilerplate code, 0 environment setup needed.

:shipit: Low-profile − doesn't impose itself, can be used as side-utility; separate modules.

Good performance / size result.

Installation

A. Directly as a module:

<script type="module">
import { $, h, v } from 'https://unpkg.com/spect?module'
</script>

Available from CDN: unpkg, pika, jsdelivr.

B. As a dependency from npm:

npm i spect

import { $, h, v } from 'spect'

API

spect/$

$( container? , selector , handler? )

Assign an aspect handler function to a selector within the container (by default document). Handler is called for each element matching the selector, returned function acts as disconnect callback. Returns live collection of matched elements.

import $ from 'spect/$'

// assign an aspect to all .foo elements
let foos = $('.foo', el => {
  console.log('active')
  return () => console.log('inactive')
})

// append .foo element
let foo = document.createElement('div')
foo.className = 'foo'
document.body.append(foo)
// ... "active"

foo.remove()
// ... "inactive"

// destroy selector observer
foos[Symbol.dispose]()

spect/h

el = h`...content`

HTML renderer with HTM syntax and reactive values support: Promise, Async Iterable, any Observable, RXjs, any observ*.

import {h, v} from 'spect'

// create reactive value
const text = v('foo')

// create live node
const a = h`<a>${ text }</a>`
a // <a>foo</a>

// updating value updates node
text.value = 'bar'
a // <a>bar</a>


// HTM syntax is fully supported
const frag = h`<x ...${{x: 1}}>1</x><y>2</y>`

// mount content on another element
h`<${a}>${ frag }</a>`
a // <a><x x="1">1</x><y>2</y></a>


// dispose values
a[Symbol.dispose]()


/* jsx h */
const a2 = <a>{ rxSubject } or { asyncIterable } or { promise }</a>

// render/update/hydrate
h(a, a2)

spect/v

ref = v( init? )

Takes an init value and returns a reactive mutable ref object with a single .value property that points to the inner value. ref implements Observable/AsyncIterable, allowing subscription to changes (essentially vue3/ref with Observable).

import v from 'spect/v'

// create value ref
let count = v(0)
count.value // 0

// subscribe to value changes
count.subscribe(value => {
  console.log(value)
  return () => console.log('teardown', value)
})

count.value = 1
// ... "1"

count.value = 2
// ... "teardown 1"
// "2"


// create mapped value ref
let double = count.map(value => value * 2)
double.value // 4

count.value = 3
double.value // 6


// combined value
let sum = v(count.value + double.value)
count.subscribe(v => sum.value = v + double.value)
double.subscribe(v => sum.value = count.value + v)

// async iterable
for await (const value of sum) console.log(value)


// dispose reference, disconnect listeners
sum[Symbol.dispose]()

Examples

Hello World

<div class="user">Loading...</div>

<script type="module">
  import { $, h, v } from 'spect'

  $('.user', async el => {
    // create user state
    const user = v({ name: 'guest' })

    // render element content, map user state
    h`<${el}>Hello, ${ user.map(u => u.name) }!</>`

    // load data & set user
    user.value = (await fetch('/user')).json()
  })
</script>

Timer

<time id="timer"></time>

<script type="module">
  import { $, v, h } from 'spect'

  $('#timer', timer => {
    const count = v(0), id = setInterval(() => count.value++, 1000)
    h`<${timer}>${ count }</>`
    return () => clearInterval(id)
  })
</script>

Counter

<output id="count">0</output>
<button id="inc">+</button><button id="dec">-</button>

<script type="module">
  import { $, h, v } from 'spect'

  const count = v(0)
  $('#count', el => count.subscribe(c => el.value = c))
  $('#inc', el => el.onclick = e => count.value++)
  $('#dec', el => el.onclick = e => count.value--)
</script>

Todo list

<form class="todo">
  <label for="add-todo">
    <span>Add Todo</span>
    <input name="text" required>
  </label>
  <button type="submit">Add</button>
  <ul class="todo-list"><ul>
</form>

<script type="module">
  import { $, h, v } from 'spect'

  const todos = v([])
  $('.todo-list', el => h`<${el}>${
    todos.map(items =>
      items.map(item => h`<li>${ item.text }</li>`)
    )
  }</>`)
  $('.todo-form', form => form.addEventListener('submit', e => {
    e.preventDefault()
    if (!form.checkValidity()) return

    todos.value = [...todos.value, { text: form.text.value }]

    form.reset()
  }))
</script>

Form validator

<form></form>

<script type="module">
  import { $, h, v } from 'spect'

  const isValidEmail = s => /.+@.+\..+/i.test(s)

  $('form', form => {
    const valid = v(false)
    h`<${form}>
      <label for="email">Please enter an email address:</label>
      <input#email onchange=${ e => valid.value = isValidEmail(e.target.value) }/>
      The address is ${ v(valid, b => b ? "valid" : "invalid") }
    </>`
  })
</script>

Prompt

<script>
import {v,h} from 'spect'

const showPrompt = v(false), proceed = v(false)

document.body.appendChild(h`<dialog open=${showPrompt}>
  Proceed?
  <menu>
    <button onclick=${e => (showPrompt.value = false, proceed.value = false)}>Cancel</button>
    <button onclick=${e => (showPrompt.value = false, proceed.value = true)}>Confirm</button>
  </menu>
</>`)
</script>

See all examples.

R&D

Sources of inspiration / analysis:

Spect has long story of research, at v13.0 it had repository reset. See changelog.

Related

  • element-props − unified access to element props with observable support. Comes handy for organizing components.
  • strui − collection of UI streams, such as router, storage etc. Comes handy for building complex reactive web-apps (spect, rxjs etc).

License

MIT