Cell
☯ A frontend tool for easily writing UI components. Cell encourages the use of event handlers and signals (Demo: https://kt3k.github.io/cell )
Install / Use
/learn @kt3k/CellREADME
Cell v0.7.9
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 install jsr:@kt3k/cell
Hello world: How to use on
The below is an example of a cell component. A cell component is a function of
the type (ctx: Context) => string | undefined.
Context includes 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
The component below shows how to 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: Use the 2nd arg of on
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
You can import directly from https://kt3k.github.io/cell/dist.min.js to try
cell in the browser.
<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 encourages local event handling, 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 unit tests 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")
})
Notes
About local event handlers
We call DOM event handlers local when
- it only reads the data in the bound DOM element
- and it only manipulates DOM elements inside the bound DOM element
The Context helpers in cell like on, el, query, and queryAll have
focus on the retrieval and manipulation of local data and makes it easier to
write local event handlers
Earlier works
Previous projects which implemented the same idea in a different syntax.
Projects with similar concepts
History
- 2024-06-18 Forked from capsule.
License
MIT
Related Skills
qqbot-channel
347.6kQQ 频道管理技能。查询频道列表、子频道、成员、发帖、公告、日程等操作。使用 qqbot_channel_api 工具代理 QQ 开放平台 HTTP 接口,自动处理 Token 鉴权。当用户需要查看频道、管理子频道、查询成员、发布帖子/公告/日程时使用。
docs-writer
100.2k`docs-writer` skill instructions As an expert technical writer and editor for the Gemini CLI project, you produce accurate, clear, and consistent documentation. When asked to write, edit, or revie
model-usage
347.6kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
cursor-agent-tracking
134A repository that provides a structured system for maintaining context and tracking changes in Cursor's AGENT mode conversations through template files, enabling better continuity and organization of AI interactions.
