rvx
Version:
A signal based rendering library
387 lines (306 loc) • 9.25 kB
Markdown
# Elements
JSX expressions and the element builder API can be used to directly create DOM elements:
=== "JSX"
```jsx
<div class="example">Hello World!</div>
```
=== "No Build"
```jsx
import { e } from "./rvx.js";
e("div").class("example").append("Hello World!")
```
## Attributes
Attributes are set using `setAttribute` or `removeAttribute` by default.
+ Attributes set to `null`, `undefined` or `false` are removed.
+ Attributes set to `true` are set as an empty string.
+ All other values are set as strings.
=== "JSX"
+ 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 event listeners.
+ An array can be used to pass the event listener with additional options.
+ The current [context](./context.md) is available within the listener.
+ The [`class`](#classes) and [`style`](#styles) attributes are special cases described below.
```jsx
// 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 }]} />
```
=== "No Build"
```jsx
// 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:
// (The current context is available within the listener)
e("input").on("click", event => { ... })
e("input").on("click", event => { ... }, { capture: true, passive: true })
```
Attribute values can be [expressions](signals.md#expressions).
=== "JSX"
```jsx
// Static values:
<div title="Hello World!" />
<div title={"Hello World!"} />
// Signals:
<div title={someSignal} />
// Functions:
<div title={() => someSignal.value} />
```
=== "No Build"
```jsx
// 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](signals.md#expressions) using `string` or `optionalString`:
=== "JSX"
```jsx
import { string, optionalString } from "rvx/convert";
// 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)} />
```
=== "No Build"
```jsx
import { e } from "./rvx.js";
import { string, optionalString } from "./rvx.convert.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](signals.md#expressions) to determine which classes are added. `undefined`, `null` and `false` is ignored.
=== "JSX"
```jsx
<div class="example" />
<div class={[
"foo",
() => "bar",
{
baz: true,
boo: () => false,
},
]} />
```
To avoid this special behavior, you can use the `attr:` prefix:
```jsx
<div attr:class="example">
```
=== "No Build"
```jsx
e("div").class("example")
e("div").class([
"foo",
() => "bar",
{
baz: true,
boo: () => false,
},
])
```
To avoid this special behavior, you can use the `set` function:
```jsx
e("div").set("class", "example")
```
## Styles
The **style** attribute can be any combination of arrays, objects and [expressions](signals.md#expressions).
Properties use the same casing as in css. E.g. `font-family`, not `fontFamily`.
=== "JSX"
```jsx
<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:
```jsx
<div attr:style="color: red;">
```
=== "No Build"
```jsx
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:
```jsx
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
=== "JSX"
To get the reference to an element, you can either use the JSX expression directly:
```jsx
const input = <input /> as HTMLInputElement;
```
Or use the special `ref` attribute:
```jsx
<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:
```jsx
<input data-a ref={input => { ... }} data-b />;
```
=== "No Build"
To get references to an element, you can reference the builder's `elem` property.
```jsx
const input = e("input").elem;
```
## Content
Everything listed below can be used as element content or can be returned from [component](components.md) functions.
### Text
Expressions (static values, signals and functions) are rendered as escaped text content. `null` and `undefined` are rendered as an empty string:
=== "JSX"
```jsx
<div>
Static text
{"Static text"}
{someSignal}
{() => someSignal.value}
</div>
```
=== "No Build"
```jsx
e("div").append(
"Static text",
someSignal,
() => someSignal.value
)
```
### Nodes
Any DOM nodes are moved into the parent element.
=== "JSX"
```jsx
<div>
<input />
{document.createElement("div")}
</div>
```
=== "No Build"
```jsx
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](components.md), 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`](./views/movable.md) 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:
=== "JSX"
```jsx
import { NODE } from "rvx";
<div>
{{ [NODE]: document.createElement("div") }}
</div>
```
=== "No Build"
```jsx
import { NODE, e } from "./rvx.js";
e("div").append(
{ [NODE]: document.createElement("div") }
)
```
### Views
[Views](views/index.md) 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](lifecycle.md) during which the element was created is disposed.
=== "JSX"
```jsx
import { Show } from "rvx";
<div>
<Show when={someSignal}>
{() => <>Hello World!</>}
</Show>
</div>
```
=== "No Build"
```jsx
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`](./views/movable.md) for safely reusing & moving arbitrary content.
### Arrays & Fragments
Content can be wrapped in arbitrarily nested arrays and JSX fragments.
=== "JSX"
```jsx
<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.
```jsx
<></> // undefined
<>42</> // 42
<>foo{"bar"}</> // ["foo", "bar"]
```
=== "No Build"
```jsx
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](context.md).
=== "JSX"
```jsx
import { Inject, XMLNS, SVG } from "rvx";
<Inject context={XMLNS} value={SVG}>
{() => <svg viewBox="0 0 100 100">...</svg>}
</Inject>
```
=== "No Build"
```jsx
import { XMLNS, SVG } from "./rvx.js";
XMLNS.inject(SVG, () => {
return e("svg").set("viewBox", "0 0 100 100").append(...)
})
```