@magnet-js/list
v0.2.0
Published
Data-keyed identity-stable list rendering with selection markers for Magnet.
Readme
@magnet/list
Data-keyed identity-stable list rendering with selection markers for Magnet.
list renders item arrays as nodes created once per item and reused across
changes: swaps become moves, removals dispose only the removed nodes, and
appends create only the new ones. The cache lives in the package, so apps never
handle DOM nodes directly. Selection is a first-class concern: pass the selected
key and the marker (classes, attributes, callbacks) follows it in O(1) DOM
writes.
Install
JSR
deno add jsr:@magnet/listnpm via JSR
npx jsr add @magnet/listnpm
npm install @magnet-js/listAPI
list(signal)
Creates a List function bound to signal primitives. Accepts any object with
computed and isSignal — a Magnet context satisfies the interface directly.
List
list(m)(items, render, selected?, options?)items— the source array, or a signal of it. Plain arrays compute eagerly with no reactive machinery; signal arrays return aComputedthat re-evaluates on change, so components accept either shape without forking.render(item, index)— creates the node for an item, called once per item.selected?— the selected item's key, or a signal of it (nullclears the selection). A plain key applies the marker once, eagerly; a signal key is tracked reactively, even for plain arrays — in that case the driving effect's cleanup is merged into the first node'sCLEAN, whichrenderdisposes with the parent like any other element teardown.options?— see below.
Returns the identity-stable nodes, renderable as children: a plain array for
plain items, a Computed for signal items. On every change, the same items
yield the same nodes, so Magnet's keyed list update moves and reuses them: swaps
become moves, removals dispose only the removed nodes, and appends create only
the new ones.
import { list } from "@magnet/list";
const items = m.state([{ id: 1, label: "a" }, { id: 2, label: "b" }]);
m.html.tbody(
list(m)(
items,
(item) => m.html.tr([m.html.td([item.label])]),
undefined,
{ id: (item) => item.id },
),
);Options
id?(item)— key extractor; defaults to object identity. Use it with primitive keys (for examplerow.id) or when items are recreated with the same logical identity (immutable updates).class?— CSS class or classes added to the selected item's node.attributes?— attributes set on the selected item's node.
Marking is O(1): when the selected key changes, the previous node is unmarked
and the new one is marked, nothing else runs. The marker follows its key through
reorders and is dropped when the item is removed; a selection made before its
item exists is applied when the item arrives. For imperative behavior on
selection, run an effect on the signal backing selected.
const selected = m.state(null);
m.html.tbody(
list(m)(
items,
(item) =>
m.html.tr([
m.html.td([
m.html.a({ onclick: () => selected.set(item.id) }, [item.label]),
]),
]),
selected,
{ id: (item) => item.id, class: "danger" },
),
);Types
List— the bound function's interface (overloaded for plain and signal arrays).Options<T, N, K>— the options bag described above.Render<T, N>— the item render function's type.KeyOf<T, K>— the key extractor's type.
License
MIT © 2026 Fernando G. Vilar.
