Tag Values
A tag value lets you pass a component around as a value, and render it later as the tag of an element. Use one when the caller of your component picks what it renders: an icon, a wrapper, a list item.
Making a tag value
Wrap a component constructor in TagValue, or use the !@ operator, which does the same:
let Icon = TagValue(Pill)
let Icon = !@Pill
!@ is rewritten by the plugin, so it only works inside a [<SolidComponent>] or [<SolidTypeComponent>]. See SolidComponent for what that means for code outside a component.
A prop that holds a tag value has the type TagValue.
Rendering a tag value
% renders a tag value. On the right, give it the props, either as an anonymous record or as an element:
Icon % {| class' = "large" |} // <Icon class="large" />
Icon % span (class' = "large") { "text" } // <Icon class="large">text</Icon>
With an element on the right, the plugin keeps its attributes and children and swaps its tag for the tag value; the span only carries them. Icon.render(...) is the long form of %, and Icon.render() renders it with no props.
Example
Labelled takes a wrapper prop, and uses Pill when the caller does not pass one:
[<Erase>]
type Pill() =
inherit span()
[<SolidTypeComponent>]
member props.View =
span(style = "padding: 0 .5em; border-radius: 1em; background: lavender").spread props {
props.children
}
[<Erase>]
type Shouty() =
inherit span()
[<SolidTypeComponent>]
member props.View =
strong () {
props.children
"!"
}
[<Erase>]
type Labelled() =
inherit div()
[<Erase>]
member val wrapper: TagValue = unbox null with get, set
[<Erase>]
member val label: string = unbox null with get, set
[<SolidTypeComponent>]
member props.View =
props.wrapper <- !@Pill
div () {
"Label: "
props.wrapper % span () { props.label }
}
export function Pill(props) {
const PARTAS_OTHERS = omit(props, "children");
return <span style="padding: 0 .5em; border-radius: 1em; background: lavender"
{...PARTAS_OTHERS} n$={false}>
{props.children}
</span>;
}
export function Shouty(props) {
const PARTAS_OTHERS = omit(props, "children");
return <strong>
{props.children}
!
</strong>;
}
export function Labelled(props) {
props = merge({
wrapper: Pill,
}, props);
const PARTAS_OTHERS = omit(props, "label", "wrapper");
return <div>
{"Label: "}
<props.wrapper>
{props.label}
</props.wrapper>
</div>;
}
div () {
Labelled(label = "default wrapper")
Labelled(label = "custom wrapper", wrapper = !@Shouty)
}
A tag value in a local binding works the same way, and renders as <Pill title=...>:
[<SolidComponent>]
let LocalTag () =
let Wrapper = !@Pill
Wrapper % span (title = "rendered through a local tag value") { "local" }
HTML tags
Use !@ with components only. !@button or !@div compiles to a bare button or div identifier, which does not exist at runtime.
To let the caller choose between HTML tags, or to pick a tag from a signal, use Dynamic from Partas.Solid.Web. It takes the tag name as a string:
open Partas.Solid.Web
[<SolidComponent>]
let HeadingLevels () =
let level, setLevel = createSignal 3
let headingProps = createObj [ "children", box "A heading of any level" ]
div () {
button (onClick = fun _ -> setLevel (if level () = 6 then 1 else level () + 1)) { "Next level" }
Dynamic<obj>(component' = unbox ("h" + string (level ()))).spread (headingProps)
}
Dynamic takes components too: Dynamic<obj>(component' = !@Pill).
Signatures
type TagValue(tag: FSharpFunc<_, #HtmlElement>) =
member render: PARTAS_CONSTRUCTOR: 'T -> 'T
member render: PARTAS_PROPERTIES: obj -> RegularNode
member render: unit -> RegularNode
static member (%): TagValue * 'T -> 'T
static member (%): TagValue * obj -> RegularNode
val (!@): FSharpFunc<_, #HtmlElement> -> TagValue
