Elements
JSX expressions and the element builder API can be used to directly create DOM elements:
<div class="example">Hello World!</div>
import { e } from "./rvx.js"; // or "rvx"
e("div").class("example").append("Hello World!")
Attributes
Attributes are set using setAttribute
or removeAttribute
by default.
- Attributes set to
null
,undefined
orfalse
are removed. - Attributes set to
true
are set as an empty string. - All other values are set as strings.
- Attributes prefixed with
prop:
are always set using the respective JavaScript properties. - Attributes prefixed with
attr:
are always set using the default behavior. - Attributes prefixed with
on:
are added as regular event listeners. An array can be used to pass the event listener with additional options. - The
class
andstyle
attributes are special cases described below.
// Using setAttribute:
<input value="Example" />
// Using the "value" property:
<input prop:value="Example" />
// Setting a boolean attribute:
<input disabled />;
<input disabled={true} />
// Removing a boolean attribute:
<input disabled={false} />
// Adding event listeners:
<button on:click={event => { ... }} />
<button on:click={[event => { ... }, { capture: true, passive: true }]} />
// Using setAttribute:
e("input").set("value", "Example")
// Using the "value" property:
e("input").prop("value", "Example")
// Setting a boolean attribute:
e("input").set("disabled", true)
// Removing a boolean attribute:
e("input").set("disabled", false)
// Adding event listeners:
e("input").on("click", event => { ... })
e("input").on("click", event => { ... }, { capture: true, passive: true })
Attribute values can be expressions.
// Static values:
<div title="Hello World!" />
<div title={"Hello World!"} />
// Signals:
<div title={someSignal} />
// Functions:
<div title={() => someSignal.value} />
// Static values:
e("div").set("title", "Hello World!")
// Signals:
e("div").set("title", someSignal)
// Functions:
e("div").set("title", () => someSignal.value)
Note, that the rules specified above apply to all attributes including aria attributes. To set an attribute to the literal "true"
and "false"
strings, you can convert an arbitrary expression using string
or optionalString
:
import { string, optionalString } from "rvx";
// Convert all values to strings including "null" and "undefined":
<div aria-disabled={string(someBooleanExpression)} />
// Convert values to strings excluding "null" or "undefined":
<div aria-disabled={optionalString(someBooleanExpression)} />
import { string, optionalString, e } from "./rvx.js";
// Convert all values to strings including "null" and "undefined":
e("div").set("aria-disabled", string(someBooleanExpression))
// Convert values to strings excluding "null" or "undefined":
e("div").set("aria-disabled", optionalString(someBooleanExpression))
Classes
The class
attribute can be any combination of class tokens, arrays and objects with boolean expressions to determine which classes are added. undefined
, null
and false
is ignored.
<div class="example" />
<div class={[
"foo",
() => "bar",
{
baz: true,
boo: () => false,
},
]} />
To avoid this special behavior, you can use the attr:
prefix:
<div attr:class="example">
e("div").class("example")
e("div").class([
"foo",
() => "bar",
{
baz: true,
boo: () => false,
},
])
To avoid this special behavior, you can use the set
function:
e("div").set("class", "example")
Styles
The style attribute can be any combination of arrays, objects and expressions.
Properties use the same casing as in css.
<div style={{ color: "red" }} />
<div style={[
{
"color": "red",
"font-size": "1rem",
},
() => ({ "color": () => "blue" }),
{ "color": someSignal },
[
{ "width": "42px" },
],
]} />
To avoid this special behavior, you can use the attr:
prefix:
<div attr:style="color: red;">
e("div").style({ color: "red" })
e("div").style([
{
"color": "red",
"font-size": "1rem",
},
() => ({ "color": () => "blue" }),
{ "color": someSignal },
[
{ "width": "42px" },
],
])
To avoid this special behavior, you can use the set
function:
e("div").set("style", "color: red;");
Note, that properties that are no longer specified after a signal update are not reset automatically to keep the current implementation simple. When properties are specified multiple times, the last one has priority used.
References
To get the reference to an element, you can either use the JSX expression directly:
const input = <input /> as HTMLInputElement;
Or use the special ref
attribute:
<input ref={input => { ... }} />;
All attributes (except key
) are processed in the specified order. In the example below, the ref
function is called after data-a
is set, but before data-b
is set:
<input data-a ref={input => { ... }} data-b />;
To get references to an element, you can reference the builder's elem
property.
const input = e("input").elem;
Content
Everything listed below can be used as element content or can be returned from component functions.
Text
Expressions (static values, signals and functions) are rendered as escaped text content. null
and undefined
are rendered as an empty string:
<div>
Static text
{"Static text"}
{someSignal}
{() => someSignal.value}
</div>
e("div").append(
"Static text",
someSignal,
() => someSignal.value
)
Nodes
Any DOM nodes are moved into the parent element.
<div>
<input />
{document.createElement("div")}
</div>
e("div").append(
e("input"),
document.createElement("div")
)
Note, that nodes are removed from their parent depending on when the content is actually used in an element. E.g. when returning a document fragment from a component, it's children are removed from the fragment as soon as the components return value is used in an element expression.
Reusing DOM nodes may result in undefined behavior. Consider using movable
for safely reusing & moving arbitrary content.
If objects have a NODE
symbol property, this node is used instead. This is internally used by the builder API, but you can also implement your own:
import { NODE } from "rvx";
<div>
{{ [NODE]: document.createElement("div") }}
</div>
import { NODE, e } from "./rvx.js";
e("div").append(
{ [NODE]: document.createElement("div") }
)
Views
Views are an abstraction for sequences of DOM nodes that may change over time. When views are used as content, they are owned by the element expression until the lifecycle during which the element was created is disposed.
import { Show } from "rvx";
<div>
<Show when={someSignal}>
{() => <>Hello World!</>}
</Show>
</div>
import { Show, e } from "./rvx.js";
e("div").append(
Show({
when: someSignal,
children: () => "Hello World!",
})
)
Reusing view instances may result in undefined behavior. Consider using movable
for safely reusing & moving arbitrary content.
Arrays & Fragments
Content can be wrapped in arbitrarily nested arrays and JSX fragments.
<div>
<>
{[
"Hello World!",
<div />,
]}
</>
</div>
Note, that JSX fragments in rvx return their children as is. The return type of single-child or empty fragments may depend on your JSX transpiler.
<></> // undefined
<>42</> // 42
<>foo{"bar"}</> // ["foo", "bar"]
e("div").append([
[
["Hello World!"],
e("div"),
],
])
Namespaces
By default, elements are created as HTML elements. This works fine for most cases, but requires some extra work to create SVG or MathML elements.
The namespace URI for new elements can be injected.
import { Inject, XMLNS, SVG } from "rvx";
<Inject context={XMLNS} value={SVG}>
{() => <svg viewBox="0 0 100 100">...</svg>}
</Inject>
import { XMLNS, SVG } from "./rvx.js";
XMLNS.inject(SVG, () => {
return e("svg").set("viewBox", "0 0 100 100").append(...)
})