API
createElement — a hyperscript DOM builder
Section titled “createElement — a hyperscript DOM builder”document.createElement-alike that sets attributes and appends children in
one call, similar to React’s
JSX-less React.createElement:
import createElement, { _ } from "@johnhenry/domable/create-element";
const list = createElement("ul", { id: "foo" }, createElement("li", {}, "bar"), document.createElement("li"),);list.outerHTML;// <ul id="foo"><li>bar</li><li></li></ul>tagis optional. Omit it (or pass aNode/string as the first argument instead) and you get aDocumentFragmentof children back, with no wrapping element._is shorthand for exactly that:_(...)==createElement(...).propsis optional. ANodeor string passed as the second argument is treated as the first child instead.props.classmay be a string (set as-is) or an array of strings (added individually viaclassList.add()).props.children, if present, is prepended to any positional children — for compatibility with JSX transforms that pass children this way.- String children become text nodes;
Nodechildren are appended as-is.
import { createSVGElement, SVG_NAMESPACE } from "@johnhenry/domable/create-element";createSVGElement("circle", { r: "5" }).namespaceURI === SVG_NAMESPACE; // truePer-tag shorthands
Section titled “Per-tag shorthands”Every HTML and SVG element has a shorthand function equivalent to
createElement("tagname", ...) / createSVGElement("tagname", ...). Kept
on separate subpaths, not the main barrel — both tag sets independently
define common names (a, audio, canvas, script, style, svg,
title, video, …), so flattening them into one namespace would silently
collide:
import { html, head, title, body, div, ul, li } from "@johnhenry/domable/html";import { circle, path } from "@johnhenry/domable/svg";
html({ lang: "en" }, head({}, title({}, "Hello")), body({}, ul({}, li({}, "item"))));var and switch are exported uppercased (Var, Switch) since both are
reserved words; hyphenated SVG tag names (color-profile, font-face, …)
are exported with underscores in place of hyphens (color_profile,
font_face).
domToText — serializing DOM back to HTML, including shadow DOM
Section titled “domToText — serializing DOM back to HTML, including shadow DOM”import domToText from "@johnhenry/domable/dom-to-text";domToText(document.querySelector("#list"));Writes its own serializer rather than just joining .outerHTML strings,
specifically so it can round-trip what simple-element (below) builds:
.outerHTML never descends into shadow trees, so a shadow-DOM custom
element serialized that way would silently lose its entire contents.
domToText/domToSource (see dom-to-hyperscript, below) walk
element.shadowRoot and emit
Declarative Shadow DOM
syntax (<template shadowrootmode="open">...</template>) instead. Uses the
native Element#getHTML({serializableShadowRoots}) where available (newer
browsers), falling back to a manual walker everywhere else.
Known limitation, not a bug: a mode: 'closed' shadow root is
invisible to element.shadowRoot by design — there is no way for any
serializer, native or not, to recover closed shadow content it was never
handed. A closed custom element serializes as just its host tag, with no
shadow content — correct, expected behavior.
simple-element — HTML text (or a Node) to a Custom Element class
Section titled “simple-element — HTML text (or a Node) to a Custom Element class”import { shadowOpen, shadowClosed, light, register } from "@johnhenry/domable/simple-element";
customElements.define("sample-element", shadowOpen`<div>I am HTML</div>`);// or, as a regular function call:customElements.define("sample-element", shadowOpen("<div>I am HTML</div>"));// or, in one step:register("sample-element", {})`<div>I am HTML</div>`;shadowOpen— accessible shadow root (element.shadowRootreturns the real root).shadowClosed— inaccessible shadow root (element.shadowRootreturnsnull).light— no shadow root at all; children go straight into light DOM.register(tagname, options, ...rest)— builds the class and callscustomElements.define()in one step.restis forwarded todefine()after the class.constructSuperclass({HTML, shadowHTML, shadowMode, baseElement})— lower-level: independently control light-DOM and shadow-DOM content on the same element (e.g. a<style>-bearing shadow tree and light-DOM fallback content).
Node input is accepted directly, not just an HTML string — since
create-element already builds DOM trees directly, requiring a
serialize-then-reparse round trip through text just to turn one into a
Custom Element would be a pointless step now that the two live in the same
package. Each instantiated element gets its own .cloneNode(true) of the
input, so one source Node can safely back any number of custom elements.
import createElement from "@johnhenry/domable/create-element";import { light } from "@johnhenry/domable/simple-element";
const built = createElement("div", { class: "card" }, "built with createElement, not a string");customElements.define("built-element", light(built));Composing / slots / styling
Section titled “Composing / slots / styling”Use <slot> to let other elements embed content, and a <style> tag
(scoped to the shadow root, so it only affects this element) to style it —
see simple-element.test.mjs in the repo for real, running examples of
both, including named slots and the ::part() pseudo-element for styling
parts of a shadow tree from outside it.
dom-to-hyperscript — DOM to reconstructable source
Section titled “dom-to-hyperscript — DOM to reconstructable source”create-element converts hyperscript calls into a DOM tree. Nothing in
the original six modules converted a DOM tree back out into that same
call shape — useful for devtools/codegen (“show me the createElement()
source that would rebuild this node”).
import domToHyperscript, { hyperscriptToSource, domToSource } from "@johnhenry/domable/dom-to-hyperscript";
const el = createElement("div", { id: "foo" }, createElement("span", {}, "hi"));
domToHyperscript(el);// { tag: "div", props: { id: "foo" }, children: [{ tag: "span", props: {}, children: ["hi"] }] }
domToSource(el);// 'createElement("div", {"id":"foo"}, createElement("span", {}, "hi"))'
domToSource(el, { indent: " " }); // pretty-printed, multi-linedomToSource(el, { fn: "h" }); // call a different identifier, e.g. for a different hyperscript libNote: domToHyperscript’s props keeps class as class (targets
create-element’s own attribute convention) — unlike domToReact, which
translates it to className (targets React’s convention). These are
deliberately different, matching what each conversion actually feeds into.
Exports
Section titled “Exports”| Export | Description |
|---|---|
@johnhenry/domable |
The conversion matrix: textToDom, domToText, domToReact, reactToDom, textToReact, reactToText |
@johnhenry/domable/create-element |
createElement, _, createSVGElement, SVG_NAMESPACE |
@johnhenry/domable/html |
Per-tag shorthand functions for every HTML element |
@johnhenry/domable/svg |
Per-tag shorthand functions for every SVG element |
@johnhenry/domable/text-to-dom |
textToDom |
@johnhenry/domable/dom-to-text |
domToText |
@johnhenry/domable/dom-to-react |
domToReact |
@johnhenry/domable/react-to-dom |
reactToDom |
@johnhenry/domable/text-to-react |
textToReact |
@johnhenry/domable/react-to-text |
reactToText |
@johnhenry/domable/simple-element |
shadowOpen, shadowClosed, light, register, constructSuperclass |
@johnhenry/domable/dom-to-hyperscript |
domToHyperscript, hyperscriptToSource, domToSource |