Skip to content
/ cell Public

☯ A frontend tool for easily writing UI components. Cell encourages the use of event handlers and signals https://jsr.io/@kt3k/cell

License

Notifications You must be signed in to change notification settings

kt3k/cell

Repository files navigation

cell

Cell v0.7.6

A frontend UI tool, encourages local event handlers and signals

Features

  • Cell encourages local event handlers pattern
  • Cell encourages signals pattern for remote effects
  • Lightweight (< 1.5 kiB gzipped)
  • TypeScript friendly

Live examples

See the live demos.

TodoMVC

TodoMVC implementation is also available here.

Install

npx jsr add @kt3k/cell

Or, in Deno,

deno add @kt3k/cell

Hello world: How to use on helper

The below is an example of cell component. A cell component is a function of the type (ctx: Context) => string | undefined.

Context includes many handy helpers for implementing UI behavior easily and quickly.

The below example uses on helper, which registers the event handler to the mounted dom element, which is <button> element in this case.

import { type Context, register } from "@kt3k/cell"

function MyComponent({ on }: Context) {
  on("click", () => {
    alert("hello")
  })
}

register(MyComponent, "js-hello")
<button class="js-hello">Click</button>

When you click this button, it alerts "hello".

Mirroring the inputs: How to use query helper

The next component shows how you can copy the input text into other dom element.

query is helper to query by the selector inside your component. query(".dest") is equivalent of el.querySelector(".dest").

import { type Context, register } from "@kt3k/cell"

function Mirroring({ on, query }: Context) {
  on("input", () => {
    query(".dest").textContent = query(".src").value
  })
}

register(Mirroring, "js-mirroring")
<div class="js-mirroring">
  <input class="src" placeholder="type something" />
  <p class="dest"></p>
</div>

Event Delegation: The 2nd arg of on helper

If you pass a string (a selector) as the second argument of on function, the event handler is only invoked when the event comes from the element which matches the given selector.

import { type Context, register } from "@kt3k/cell"

function DelegateComponent({ on, query }: Context) {
  on("click", ".btn", () => {
    query(".result").textContext += " .btn clicked!"
  })
}

register(DelegateComponent, "js-delegate")

Outside events: onOutside helper

By calling onOutside(event, handler), you can handle the event outside of the component's DOM.

This is convenient, for example, when you like to close the modal dialog when the user clicking the outside of it.

import { type Context, register } from "@kt3k/cell"

function OutsideClickComponent({ onOutside }: Context) {
  onOutside("click", ({ e }) => {
    console.log("The outside of my-component has been clicked!")
  })
}

register(OutsideClickComponent, "js-outside-click")

Using Cell directly from the browser

The below example shows how you can use cell directly in the browsers.

<script type="module">
  import { register } from "https://kt3k.github.io/cell/dist.min.js"

  function Mirroring({ on, query }) {
    on("input", () => {
      query(".dest").textContent = query(".src").value
    })
  }

  register(Mirroring, "js-mirroring")
</script>
<div class="js-mirroring">
  <input class="src" placeholder="Type something" />
  <p class="dest"></p>
</div>

Use signals when making remote effect

cell recommends handling event locally, but in many cases you would also need to make effects to remote elements.

If you need to affects the components in remote places (i.e. components not an ancestor or decendant of the component), we commend using signals for communicating with them.

signals are event emitter with values, whose events are triggered only when the values are changed.

import { Context, Signal } from "@kt3k/cell"

const sig = new Signal(0)

function Component({ el, subscribe }: Context) {
  subscribe(sig, (v) => {
    el.textContent = `The value is ${v}`
  })
}

sig.update(1)
sig.update(2)

Write test of components

Use @b-fuse/deno-dom for polyfill document object. An example of basic test case of a component looks like the below:

import { DOMParser } from "@b-fuze/deno-dom"
import { assertEquals } from "@std/assert"
import { type Context, mount, register } from "@kt3k/cell"

Deno.test("A test case of Component", () => {
  function Component({ el }: Context) {
    el.textContent = "a"
  }

  register(Component, "js-component")

  globalThis.document = new DOMParser().parseFromString(
    `<body><div class="js-component"></div></body>`,
    "text/html",
    // deno-lint-ignore no-explicit-any
  ) as any
  mount()
  assertEquals(document.body.firstChild?.textContent, "a")
})

Prior art

Projects with similar concepts

  • Flight by twitter
    • Not under active development
  • eddy.js
    • Archived

History

  • 2024-06-18 Forked from capsule.

License

MIT

About

☯ A frontend tool for easily writing UI components. Cell encourages the use of event handlers and signals https://jsr.io/@kt3k/cell

Resources

License

Stars

Watchers

Forks