@excom/neutron
v0.2.0
Published
Element factory of the Nucleus Stack — typed custom elements with effect-based lifecycles
Maintainers
Readme
neutron
Define typed custom elements with effect-based lifecycles — props, events, and compose without rewriting the Custom Elements boilerplate.
Neutron is the element factory of the Nucleus Stack: Neutron({ tag, props }) returns a builder you chain lifecycles onto, then define(). Every Nucleus Kit element is a Neutron element, and so is every element you write yourself.
Features
- Declarative factory
Neutron({ tag, props }).… .define() - Typed props Primitives and
TokenListreflect to dashed attributes; objects / arrays / elements / promises stay on the instance - Effect returns Lifecycles / methods return a POJO (or an array of them) that sets props, emits, listens, calls methods, and styles
- Fine-grained reactions
onPropSet/Unset/Changed/onEffect - Events & broadcasts Tag-prefixed custom events, cancelable default actions, channel broadcasts
- Commands
onCommand("--verb")handles the HTML Command API —<button command commandfor>needs no custom element - Listener cleanup Listeners added through effects are removed on disconnect and restored on reconnect
- Compose Combine builders (
Neutron.compose) for mixin-style packages - Recompose Import a package's raw builder, add / remove lifecycles and methods, then
define()it yourself - DevTools
Neutron.attachDevtools()hooks the Nucleus DevTools extension
Installation
Usage
Beta disclaimer: Neutron automatically defines your element's Typescript types based upon your element config. This is done via complicated internal typing that has a few known issues. These issues will be resolved in the first stable release.
An element owns its own state (attributes) and announces changes (events). It never renders children or reaches into siblings — coordination belongs to Quark. The rules these examples follow are collected in Best Practices and Creating Elements.
import { Neutron } from "@excom/neutron";
export const PressTracker = Neutron({
tag: "press-tracker",
props: {
pressCount: { type: Number, defaultValue: () => 0 }, // reflects ↔ `press-count`
},
})
.onEvent("click", ({ pressCount }) => ({
// effects are declarative instructions, not imperative mutations
pressCount: pressCount + 1,
emit: ["press-tracker-press", { detail: { pressCount: pressCount + 1 } }],
}));
PressTracker.define();<press-tracker press-count="0">
<button>Press</button>
</press-tracker>
<!-- `press-tracker[press-count="3"]` is now a CSS / Quark selector -->Documentation
Defining elements
- Props — typed props, reflection,
TokenList, naming rules - Provision — the one property for published rich data
- TypeScript — global element types,
ConstructorType
Behavior
- Lifecycles —
onConnected& co., destructuring, async pitfalls - Effects — the object a handler returns
- Prop reactions —
onPropSet/Unset/Changed/onEffect - Methods — methods as effectors
- Events — emits, default actions, broadcasts, listener cleanup
- Commands —
onCommandfor--verbcommands, thecommandeffect - Promise props —
onPromiseResolved/Rejected
Composition
- Compose — stack builders into mixin-style packages
- Recompose — edit a packaged element before defining it
Runtime
Examples
State on connect
Neutron({ tag: "ready-flag", props: { isReady: Boolean } })
.onConnected(() => ({ isReady: true, emit: ["ready-flag-ready"] }))
.define();Child element effect across handlers
Assign an element prop, then react to it with a nested effect. Listener callbacks that return effects must be defineMethods methods:
Neutron({
tag: "focus-host",
props: {
inputEl: { type: HTMLInputElement, store: "weak" },
isFocused: Boolean,
},
})
.defineMethods({
handleFocus: () => ({ isFocused: true, emit: ["focus-host-focus"] }),
handleBlur: () => ({ isFocused: false }),
})
.onConnected((el) => ({
inputEl: el.querySelector("input"),
}))
.onPropSet("inputEl", ({ handleFocus, handleBlur }) => ({
inputEl: {
addListeners: [
["focus", handleFocus],
["blur", handleBlur],
],
},
}));Full documentation: https://nucleus.excom.dev/packages/neutron
