mod data;
pub mod html;
mod icon;
pub(crate) mod node;
mod types;
use std::time::Instant;
pub use data::*;
pub use icon::Icon;
pub use node::{Node, View};
pub use types::*;
use self::node::{
actions_methods, base_methods, children_methods, event_methods, kind_type, max_method,
min_method, props, title_method,
};
pub type CardStatus = Status;
#[macro_export]
macro_rules! spans {
($($s:expr),* $(,)?) => {
vec![$($crate::ui::Span::from($s)),*]
};
}
#[macro_export]
macro_rules! nodes {
($($n:expr),* $(,)?) => {
vec![$($crate::ui::Node::from($n)),*]
};
}
pub fn span(t: impl Into<String>) -> Span {
Span::new(t)
}
pub fn node<M>(kind: impl Into<String>) -> Node<M> {
Node::new(kind)
}
macro_rules! common_kind {
($(#[$m:meta])* $name:ident) => {
kind_type!($(#[$m])* $name);
impl<M> $name<M> {
base_methods!();
min_method!();
title_method!();
max_method!();
actions_methods!();
event_methods!();
}
};
}
macro_rules! container_kind {
($(#[$m:meta])* $name:ident) => {
common_kind!($(#[$m])* $name);
impl<M> $name<M> {
children_methods!();
}
};
}
fn new<M, T: From<Node<M>>>(kind: &str) -> T {
T::from(Node::new(kind))
}
macro_rules! from_node {
($($name:ident),*) => {
$(impl<M> From<Node<M>> for $name<M> {
fn from(node: Node<M>) -> Self {
Self(node)
}
})*
};
}
from_node!(
Col,
Row,
Card,
Section,
Rule,
Spacer,
TextNode,
Md,
Code,
Diff,
Ansi,
Rows,
Math,
Kv,
Table,
Tree,
Json,
Badge,
Kbd,
IconNode,
Image,
Media,
List,
Item,
Tabs,
Picker,
Spinner,
Shimmer,
Elapsed,
Rate,
Progress,
Meter,
Chart,
Effort,
Editor,
Input,
StatusBar,
Seg,
Toast,
Overlay,
Tool,
Agent,
Checklist,
Prefs,
Button,
Stats,
Select,
Switch,
Slider,
Field,
Otp,
Buttons,
Avatar,
Avatars,
Breadcrumb,
Pager,
Empty,
Skeleton,
Calendar,
Date,
Menu,
Menubar,
Nav,
Split,
Scroll,
Carousel,
Message,
Attachment,
Trace,
Stacktrace,
Suggestions,
Questions
);
pub enum Slot<M = ()> {
Text(Text),
Node(Node<M>),
}
impl<M> From<&str> for Slot<M> {
fn from(t: &str) -> Self {
Self::Text(t.into())
}
}
impl<M> From<String> for Slot<M> {
fn from(t: String) -> Self {
Self::Text(t.into())
}
}
impl<M> From<&String> for Slot<M> {
fn from(t: &String) -> Self {
Self::Text(t.into())
}
}
impl<M, const N: usize> From<[Span; N]> for Slot<M> {
fn from(s: [Span; N]) -> Self {
Self::Text(s.into())
}
}
impl<M> From<Text> for Slot<M> {
fn from(t: Text) -> Self {
Self::Text(t)
}
}
impl<M> From<Span> for Slot<M> {
fn from(s: Span) -> Self {
Self::Text(s.into())
}
}
impl<M> From<Vec<Span>> for Slot<M> {
fn from(s: Vec<Span>) -> Self {
Self::Text(s.into())
}
}
impl<M> From<Node<M>> for Slot<M> {
fn from(n: Node<M>) -> Self {
Self::Node(n)
}
}
macro_rules! slot_from {
($($name:ident),*) => {
$(impl<M> From<$name<M>> for Slot<M> {
fn from(n: $name<M>) -> Self {
Self::Node(n.0)
}
})*
};
}
slot_from!(
Col,
Row,
Card,
Section,
Rule,
Spacer,
TextNode,
Md,
Code,
Diff,
Ansi,
Rows,
Math,
Kv,
Table,
Tree,
Json,
Badge,
Kbd,
IconNode,
Image,
Media,
List,
Item,
Tabs,
Picker,
Spinner,
Shimmer,
Elapsed,
Rate,
Progress,
Meter,
Chart,
Effort,
Editor,
Input,
StatusBar,
Seg,
Toast,
Overlay,
Tool,
Agent,
Checklist,
Prefs,
Button,
Stats,
Select,
Switch,
Slider,
Field,
Otp,
Buttons,
Avatar,
Avatars,
Breadcrumb,
Pager,
Empty,
Skeleton,
Calendar,
Date,
Menu,
Menubar,
Nav,
Split,
Scroll,
Carousel,
Message,
Attachment,
Trace,
Stacktrace,
Suggestions,
Questions
);
fn set_slot<M>(node: &mut Node<M>, slot: &'static str, value: Slot<M>) {
match value {
Slot::Text(text) => {
node.clear_slot(slot);
node.set(slot, &text);
},
Slot::Node(child) => node.set_slot(slot, child),
}
}
macro_rules! panel_head_methods {
() => {
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.0.set("icon", &icon.into());
self
}
pub fn meta(mut self, meta: impl Into<Text>) -> Self {
self.0.set("meta", &meta.into());
self
}
pub fn end(mut self, end: impl Into<Slot<M>>) -> Self {
set_slot(&mut self.0, "end", end.into());
self
}
};
}
container_kind!(
Col
);
impl<M> Col<M> {
props! {
gap(Gap) = "gap";
align(Align) = "align";
justify(Justify) = "justify";
wrap(bool) = "wrap";
}
}
pub fn col<M>() -> Col<M> {
new("col")
}
container_kind!(
Row
);
impl<M> Row<M> {
props! {
gap(Gap) = "gap";
align(Align) = "align";
justify(Justify) = "justify";
wrap(bool) = "wrap";
}
}
pub fn row<M>() -> Row<M> {
new("row")
}
container_kind!(
Card
);
impl<M> Card<M> {
panel_head_methods!();
props! {
status(Status) = "status";
collapsible(bool) = "collapsible";
collapsed(bool) = "collapsed";
preview(Preview) = "preview";
selected(bool) = "selected";
inset(bool) = "inset";
variant(CardVariant) = "variant";
}
pub fn head(mut self, head: impl Into<Slot<M>>) -> Self {
set_slot(&mut self.0, "head", head.into());
self
}
}
pub fn card<M>() -> Card<M> {
new("card")
}
container_kind!(
Section
);
impl<M> Section<M> {
props! {
collapsible(bool) = "collapsible";
collapsed(bool) = "collapsed";
}
pub fn head(mut self, head: impl Into<Slot<M>>) -> Self {
set_slot(&mut self.0, "head", head.into());
self
}
}
pub fn section<M>() -> Section<M> {
new("section")
}
common_kind!(
Rule
);
impl<M> Rule<M> {
props! {
label(Text) = "label";
variant(RuleVariant) = "variant";
icon(Icon) = "icon";
time(String) = "time";
}
pub fn end(mut self, end: impl Into<Slot<M>>) -> Self {
set_slot(&mut self.0, "end", end.into());
self
}
}
pub fn rule<M>() -> Rule<M> {
new("rule")
}
common_kind!(
Spacer
);
impl<M> Spacer<M> {
props! {
size(Gap) = "size";
}
}
pub fn spacer<M>() -> Spacer<M> {
new("spacer")
}
common_kind!(
TextNode
);
impl<M> TextNode<M> {
props! {
wrap(Wrap) = "wrap";
truncate(Truncate) = "truncate";
lines(u32) = "lines";
measure(Measure) = "measure";
cite(CiteStyle) = "cite";
sources(bool) = "sources";
}
pub fn text(mut self, text: impl Into<Text>) -> Self {
self.0.set_text(text.into());
self
}
}
pub fn text<M>(text: impl Into<Text>) -> TextNode<M> {
let mut node = Node::new("text");
node.set_text(text.into());
TextNode(node)
}
common_kind!(
Md
);
impl<M> Md<M> {
props! {
text(String) = "text";
stream(bool) = "stream";
marks(Vec<Span>) = "marks";
}
}
pub fn md<M>(source: impl Into<String>) -> Md<M> {
new::<M, Md<M>>("md").text(source)
}
common_kind!(
Code
);
impl<M> Code<M> {
props! {
text(String) = "text";
lang(String) = "lang";
path(String) = "path";
numbers(bool) = "numbers";
start(i64) = "start";
nums(Vec<u64>) = "nums";
marks(Vec<CodeMark>) = "marks";
wrap(bool) = "wrap";
folds(Vec<CodeFold>) = "folds";
overview(bool) = "overview";
}
}
pub fn code<M>(source: impl Into<String>) -> Code<M> {
new::<M, Code<M>>("code").text(source)
}
common_kind!(
Diff
);
impl<M> Diff<M> {
props! {
text(String) = "text";
hunks(Vec<Hunk>) = "hunks";
path(String) = "path";
lang(String) = "lang";
mode(DiffMode) = "mode";
}
}
pub fn diff<M>() -> Diff<M> {
new("diff")
}
common_kind!(
Ansi
);
impl<M> Ansi<M> {
props! {
text(String) = "text";
cols(u32) = "cols";
follow(bool) = "follow";
}
pub fn preview(mut self, lines: u32) -> Self {
self
.0
.set("preview", &serde_json::json!({ "lines": lines }));
self
}
}
pub fn ansi<M>(output: impl Into<String>) -> Ansi<M> {
new::<M, Ansi<M>>("ansi").text(output)
}
common_kind!(
Rows
);
impl<M> Rows<M> {
props! {
lines(Vec<String>) = "lines";
cols(u32) = "cols";
}
}
pub fn rows<M>(lines: impl Into<Vec<String>>) -> Rows<M> {
new::<M, Rows<M>>("rows").lines(lines)
}
common_kind!(
Math
);
impl<M> Math<M> {
props! {
text(String) = "text";
display(bool) = "display";
}
}
pub fn math<M>(tex: impl Into<String>) -> Math<M> {
new::<M, Math<M>>("math").text(tex)
}
common_kind!(
Kv
);
impl<M> Kv<M> {
props! {
items(Vec<KvItem>) = "items";
layout(KvLayout) = "layout";
}
pub fn item(mut self, k: impl Into<Text>, v: impl Into<Text>) -> Self {
push(&mut self.0, "items", &KvItem { k: k.into(), v: v.into() });
self
}
}
pub fn kv<M>() -> Kv<M> {
new("kv")
}
common_kind!(
Table
);
impl<M> Table<M> {
props! {
cols(Vec<Column>) = "cols";
rows(Vec<TableRow>) = "rows";
selectable(bool) = "selectable";
selected(String) = "selected";
empty(Text) = "empty";
sort(Sort) = "sort";
multi(bool) = "multi";
picked(Vec<String>) = "picked";
variant(TableVariant) = "variant";
bar(Vec<ActButton>) = "bar";
hint(Text) = "hint";
}
pub fn col(mut self, col: Column) -> Self {
push(&mut self.0, "cols", &col);
self
}
pub fn row(mut self, row: TableRow) -> Self {
push(&mut self.0, "rows", &row);
self
}
pub fn bar_button(mut self, button: ActButton) -> Self {
push(&mut self.0, "bar", &button);
self
}
}
pub fn table<M>() -> Table<M> {
new("table")
}
common_kind!(
Tree
);
impl<M> Tree<M> {
props! {
nodes(Vec<TreeItem>) = "nodes";
}
pub fn item(mut self, item: TreeItem) -> Self {
push(&mut self.0, "nodes", &item);
self
}
}
pub fn tree<M>() -> Tree<M> {
new("tree")
}
common_kind!(
Json
);
impl<M> Json<M> {
pub fn open(mut self, levels: u32) -> Self {
self.0.set("open", &levels);
self
}
}
pub fn json<M>(value: impl Into<serde_json::Value>) -> Json<M> {
let mut node: Json<M> = new("json");
node.0.set("value", &value.into());
node
}
common_kind!(
Badge
);
impl<M> Badge<M> {
props! {
text(String) = "text";
}
}
pub fn badge<M>(text: impl Into<String>) -> Badge<M> {
new::<M, Badge<M>>("badge").text(text)
}
common_kind!(
Kbd
);
impl<M> Kbd<M> {
props! {
keys(Vec<String>) = "keys";
}
}
pub fn kbd<M, K: Into<String>>(keys: impl IntoIterator<Item = K>) -> Kbd<M> {
new::<M, Kbd<M>>("kbd").keys(keys.into_iter().map(Into::into).collect::<Vec<_>>())
}
common_kind!(
IconNode
);
impl<M> IconNode<M> {
props! {
name(Icon) = "name";
}
}
pub fn icon<M>(name: impl Into<Icon>) -> IconNode<M> {
new::<M, IconNode<M>>("icon").name(name)
}
common_kind!(
Image
);
impl<M> Image<M> {
props! {
blob(String) = "blob";
builtin(String) = "builtin";
alt(String) = "alt";
w(f64) = "w";
h(f64) = "h";
path(String) = "path";
}
}
pub fn image<M>() -> Image<M> {
new("image")
}
common_kind!(
Media
);
impl<M> Media<M> {
props! {
rev(String) = "rev";
link(String) = "link";
}
}
pub fn media<M>(path: impl Into<String>) -> Media<M> {
let mut node = new::<M, Media<M>>("media");
node.0.set("path", &path.into());
node
}
kind_type!(
List
);
impl<M> List<M> {
base_methods!();
min_method!();
title_method!();
actions_methods!();
event_methods!();
children_methods!();
props! {
selected(String) = "selected";
filter(String) = "filter";
empty(Text) = "empty";
virtualize(bool) = "virtual";
}
pub fn max_lines(mut self, lines: u32) -> Self {
self.0.set("max", &serde_json::json!({ "lines": lines }));
self
}
pub fn max_fraction(mut self, fraction: f64) -> Self {
self.0.set("max", &fraction);
self
}
}
pub fn list<M>() -> List<M> {
new("list")
}
common_kind!(
Item
);
impl<M> Item<M> {
props! {
label(Text) = "label";
detail(Text) = "detail";
value(Text) = "value";
icon(Icon) = "icon";
hint(Vec<String>) = "hint";
disabled(bool) = "disabled";
}
}
pub fn item<M>(label: impl Into<Text>) -> Item<M> {
new::<M, Item<M>>("item").label(label)
}
common_kind!(
Tabs
);
impl<M> Tabs<M> {
props! {
items(Vec<Tab>) = "items";
active(String) = "active";
variant(TabsVariant) = "variant";
}
pub fn tab(mut self, id: impl Into<String>, label: impl Into<Text>) -> Self {
push(&mut self.0, "items", &Tab { id: id.into(), label: label.into() });
self
}
}
pub fn tabs<M>() -> Tabs<M> {
new("tabs")
}
kind_type!(
Picker
);
impl<M> Picker<M> {
base_methods!();
min_method!();
max_method!();
event_methods!();
children_methods!();
props! {
size(PickerSize) = "size";
layout(PickerLayout) = "layout";
preview(PickerPreview) = "preview";
title(Text) = "title";
subtitle(Text) = "subtitle";
icon(Icon) = "icon";
query(String) = "query";
cursor(usize) = "cursor";
placeholder(String) = "placeholder";
noun(String) = "noun";
focus(PickerFocus) = "focus";
state(PickerState) = "state";
message(Text) = "message";
empty(Text) = "empty";
total(u64) = "total";
items(Vec<PickerItem>) = "items";
items_add(Vec<PickerItem>) = "itemsAdd";
items_del(Vec<String>) = "itemsDel";
order(Vec<OrderEntry>) = "order";
selected(String) = "selected";
current(Vec<String>) = "current";
hits(std::collections::BTreeMap<String, Vec<[usize; 2]>>) = "hits";
columns(Vec<PickerColumn>) = "columns";
confirm(Confirm) = "confirm";
scopes(Vec<Scope>) = "scopes";
scope(String) = "scope";
tabs(Vec<PickerTab>) = "tabs";
tab(String) = "tab";
strip(Strip) = "strip";
actions(Vec<PickerAction>) = "actions";
}
}
pub fn picker<M>() -> Picker<M> {
new("picker")
}
common_kind!(
Spinner
);
impl<M> Spinner<M> {
props! {
style(SpinnerStyle) = "style";
label(Text) = "label";
}
}
pub fn spinner<M>() -> Spinner<M> {
new("spinner")
}
common_kind!(
Shimmer
);
impl<M> Shimmer<M> {
props! {
mode(ShimmerMode) = "mode";
palette(ShimmerPalette) = "palette";
}
pub fn text(mut self, text: impl Into<Text>) -> Self {
self.0.set_text(text.into());
self
}
}
pub fn shimmer<M>(text: impl Into<Text>) -> Shimmer<M> {
new::<M, Shimmer<M>>("shimmer").text(text)
}
common_kind!(
Elapsed
);
impl<M> Elapsed<M> {
props! {
age(i64) = "age";
stopped(u64) = "stopped";
format(ElapsedFormat) = "format";
}
pub fn since(self, start: Instant) -> Self {
let age = i64::try_from(start.elapsed().as_millis()).unwrap_or(i64::MAX);
self.age(age)
}
}
pub fn elapsed<M>() -> Elapsed<M> {
new("elapsed")
}
common_kind!(
Rate
);
impl<M> Rate<M> {
props! {
value(f64) = "value";
unit(String) = "unit";
}
}
pub fn rate<M>(value: f64) -> Rate<M> {
new::<M, Rate<M>>("rate").value(value)
}
common_kind!(
Progress
);
impl<M> Progress<M> {
props! {
value(f64) = "value";
label(Text) = "label";
}
}
pub fn progress<M>() -> Progress<M> {
new("progress")
}
common_kind!(
Meter
);
impl<M> Meter<M> {
props! {
value(f64) = "value";
parts(Vec<Part>) = "parts";
thresholds(Thresholds) = "thresholds";
style(MeterStyle) = "style";
size(Size) = "size";
steps(u32) = "steps";
marks(Vec<MeterMark>) = "marks";
label(Text) = "label";
total(Text) = "total";
}
}
pub fn meter<M>() -> Meter<M> {
new("meter")
}
kind_type!(
Chart
);
impl<M> Chart<M> {
base_methods!();
title_method!();
actions_methods!();
event_methods!();
props! {
kind(ChartKind) = "kind";
size(Size) = "size";
token(String) = "token";
summary(Text) = "summary";
series(Vec<Point>) = "series";
multi_series(Vec<Series>) = "series";
labels(Vec<String>) = "labels";
unit(String) = "unit";
last(TextFlag) = "last";
total(Text) = "total";
caption(Text) = "caption";
min(f64) = "min";
max(f64) = "max";
marks(Vec<ChartMark>) = "marks";
cells(Vec<Vec<Option<f64>>>) = "cells";
tips(Vec<Vec<Option<String>>>) = "tips";
rows(Vec<String>) = "rows";
cols(Vec<ChartCol>) = "cols";
}
}
pub fn chart<M>(kind: impl Into<ChartKind>) -> Chart<M> {
new::<M, Chart<M>>("chart").kind(kind)
}
common_kind!(
Effort
);
impl<M> Effort<M> {
props! {
level(EffortLevel) = "level";
}
}
pub fn effort<M>(level: impl Into<EffortLevel>) -> Effort<M> {
new::<M, Effort<M>>("effort").level(level)
}
common_kind!(
Editor
);
impl<M> Editor<M> {
props! {
text(String) = "text";
cursor(usize) = "cursor";
anchor(usize) = "anchor";
decor(Vec<Decor>) = "decor";
ghost(String) = "ghost";
placeholder(Text) = "placeholder";
prompt(Text) = "prompt";
mode(String) = "mode";
lang(String) = "lang";
readonly(bool) = "readonly";
sendable(bool) = "sendable";
typeahead(bool) = "typeahead";
max_lines(u32) = "maxLines";
variant(InputVariant) = "variant";
buttons(Vec<ActButton>) = "buttons";
}
}
pub fn editor<M>() -> Editor<M> {
new("editor")
}
common_kind!(
Input
);
impl<M> Input<M> {
props! {
text(String) = "text";
cursor(usize) = "cursor";
anchor(usize) = "anchor";
decor(Vec<Decor>) = "decor";
ghost(String) = "ghost";
placeholder(Text) = "placeholder";
prompt(Text) = "prompt";
mode(String) = "mode";
lang(String) = "lang";
readonly(bool) = "readonly";
sendable(bool) = "sendable";
typeahead(bool) = "typeahead";
variant(InputVariant) = "variant";
icon(Icon) = "icon";
mono(bool) = "mono";
clear(bool) = "clear";
prefix(String) = "prefix";
suffix(String) = "suffix";
unit(String) = "unit";
stepper(bool) = "stepper";
secret(bool) = "secret";
reveal(bool) = "reveal";
buttons(Vec<ActButton>) = "buttons";
}
pub fn keys<K: Into<String>>(mut self, keys: impl IntoIterator<Item = K>) -> Self {
self
.0
.set("keys", &keys.into_iter().map(Into::into).collect::<Vec<String>>());
self
}
}
pub fn input<M>() -> Input<M> {
new("input")
}
container_kind!(
StatusBar
);
impl<M> StatusBar<M> {
props! {
transparent(bool) = "transparent";
}
}
pub fn status<M>() -> StatusBar<M> {
new("status")
}
container_kind!(
Seg
);
impl<M> Seg<M> {
props! {
icon(Icon) = "icon";
side(Side) = "side";
priority(f64) = "priority";
}
pub fn text(mut self, text: impl Into<Text>) -> Self {
self.0.set_text(text.into());
self
}
}
pub fn seg<M>(text: impl Into<Text>) -> Seg<M> {
new::<M, Seg<M>>("seg").text(text)
}
common_kind!(
Toast
);
impl<M> Toast<M> {
props! {
text(String) = "text";
#[allow(clippy::should_implement_trait, reason = "the prop is named `sub`")]
sub(String) = "sub";
ttl(u64) = "ttl";
}
}
pub fn toast<M>(text: impl Into<String>) -> Toast<M> {
new::<M, Toast<M>>("toast").text(text)
}
container_kind!(
Overlay
);
impl<M> Overlay<M> {
props! {
anchor(Anchor) = "anchor";
size(OverlaySize) = "size";
modal(bool) = "modal";
head(Text) = "head";
side(OverlaySide) = "side";
variant(OverlayVariant) = "variant";
}
panel_head_methods!();
pub fn size_fraction(mut self, fraction: f64) -> Self {
self.0.set("size", &fraction);
self
}
pub fn foot(mut self, foot: impl Into<Slot<M>>) -> Self {
set_slot(&mut self.0, "foot", foot.into());
self
}
}
pub fn overlay<M>() -> Overlay<M> {
new("overlay")
}
common_kind!(
Button
);
impl<M> Button<M> {
props! {
text(String) = "text";
icon(Icon) = "icon";
variant(ButtonVariant) = "variant";
size(ButtonSize) = "size";
on(bool) = "on";
disabled(Disabled) = "disabled";
busy(bool) = "busy";
}
pub fn keys<K: Into<String>>(mut self, keys: impl IntoIterator<Item = K>) -> Self {
self
.0
.set("keys", &keys.into_iter().map(Into::into).collect::<Vec<String>>());
self
}
}
pub fn button<M>(text: impl Into<String>) -> Button<M> {
new::<M, Button<M>>("button").text(text)
}
common_kind!(
Stats
);
impl<M> Stats<M> {
props! {
items(Vec<StatItem>) = "items";
}
pub fn item(mut self, item: StatItem) -> Self {
push(&mut self.0, "items", &item);
self
}
}
pub fn stats<M>(items: impl IntoIterator<Item = StatItem>) -> Stats<M> {
new::<M, Stats<M>>("stats").items(items.into_iter().collect::<Vec<_>>())
}
common_kind!(
Select
);
impl<M> Select<M> {
props! {
value(String) = "value";
options(Vec<SelectOption>) = "options";
variant(SelectVariant) = "variant";
label(String) = "label";
placeholder(String) = "placeholder";
size(Size) = "size";
disabled(Disabled) = "disabled";
open(bool) = "open";
active(String) = "active";
search(bool) = "search";
query(String) = "query";
cursor(usize) = "cursor";
total(u64) = "total";
empty(String) = "empty";
}
pub fn option(mut self, option: impl Into<SelectOption>) -> Self {
push(&mut self.0, "options", &option.into());
self
}
}
pub fn select<M>() -> Select<M> {
new("select")
}
common_kind!(
Switch
);
impl<M> Switch<M> {
props! {
on(bool) = "on";
variant(SwitchVariant) = "variant";
label(String) = "label";
hint(String) = "hint";
disabled(Disabled) = "disabled";
}
pub fn labels(mut self, off: impl Into<String>, on: impl Into<String>) -> Self {
let labels: [String; 2] = [off.into(), on.into()];
self.0.set("labels", &labels);
self
}
}
pub fn switch<M>(label: impl Into<String>) -> Switch<M> {
new::<M, Switch<M>>("switch").label(label)
}
kind_type!(
Slider
);
impl<M> Slider<M> {
base_methods!();
title_method!();
actions_methods!();
event_methods!();
props! {
value(SliderValue) = "value";
min(f64) = "min";
max(f64) = "max";
step(f64) = "step";
label(String) = "label";
unit(String) = "unit";
format(SliderFormat) = "format";
ticks(Ticks) = "ticks";
variant(SliderVariant) = "variant";
disabled(Disabled) = "disabled";
}
}
pub fn slider<M>() -> Slider<M> {
new("slider")
}
container_kind!(
Field
);
impl<M> Field<M> {
props! {
label(Text) = "label";
hint(Text) = "hint";
error(TextFlag) = "error";
warning(TextFlag) = "warning";
required(bool) = "required";
label_width(Extent) = "labelWidth";
variant(FieldVariant) = "variant";
}
}
pub fn field<M>(label: impl Into<Text>) -> Field<M> {
new::<M, Field<M>>("field").label(label)
}
common_kind!(
Otp
);
impl<M> Otp<M> {
props! {
text(String) = "text";
length(u32) = "length";
group(u32) = "group";
variant(OtpVariant) = "variant";
mask(OtpMask) = "mask";
state(OtpState) = "state";
hint(String) = "hint";
disabled(Disabled) = "disabled";
}
pub fn cursor(mut self, cursor: Option<u32>) -> Self {
match cursor {
Some(cell) => self.0.set("cursor", &cell),
None => self.0.set("cursor", &false),
}
self
}
}
pub fn otp<M>(length: u32) -> Otp<M> {
new::<M, Otp<M>>("otp").length(length)
}
container_kind!(
Buttons
);
impl<M> Buttons<M> {
props! {
variant(ButtonsVariant) = "variant";
size(ButtonSize) = "size";
label(String) = "label";
}
}
pub fn buttons<M>() -> Buttons<M> {
new("buttons")
}
kind_type!(
Calendar
);
impl<M> Calendar<M> {
base_methods!();
title_method!();
actions_methods!();
event_methods!();
props! {
variant(CalendarVariant) = "variant";
value(DateValue) = "value";
range(bool) = "range";
month(String) = "month";
today(String) = "today";
min(String) = "min";
max(String) = "max";
week_start(WeekStart) = "weekStart";
marks(Vec<DayMark>) = "marks";
cursor(String) = "cursor";
presets(Vec<Preset>) = "presets";
preset(String) = "preset";
note(String) = "note";
}
}
pub fn calendar<M>() -> Calendar<M> {
new("calendar")
}
kind_type!(
Date
);
impl<M> Date<M> {
base_methods!();
title_method!();
actions_methods!();
event_methods!();
props! {
value(DateValue) = "value";
range(bool) = "range";
open(bool) = "open";
placeholder(String) = "placeholder";
month(String) = "month";
today(String) = "today";
min(String) = "min";
max(String) = "max";
week_start(WeekStart) = "weekStart";
marks(Vec<DayMark>) = "marks";
cursor(String) = "cursor";
note(String) = "note";
}
}
pub fn date<M>() -> Date<M> {
new("date")
}
common_kind!(
Menu
);
impl<M> Menu<M> {
props! {
items(Vec<MenuEntry>) = "items";
text(String) = "text";
icon(Icon) = "icon";
trigger(ButtonVariant) = "trigger";
caret(bool) = "caret";
size(ButtonSize) = "size";
variant(MenuVariant) = "variant";
open(bool) = "open";
active(String) = "active";
align(MenuAlign) = "align";
disabled(Disabled) = "disabled";
}
pub fn item(mut self, entry: impl Into<MenuEntry>) -> Self {
push(&mut self.0, "items", &entry.into());
self
}
}
pub fn menu<M>() -> Menu<M> {
new("menu")
}
common_kind!(
Menubar
);
impl<M> Menubar<M> {
props! {
menus(Vec<MenubarMenu>) = "menus";
variant(MenubarVariant) = "variant";
icon(Icon) = "icon";
open(String) = "open";
active(String) = "active";
}
pub fn menu(mut self, menu: MenubarMenu) -> Self {
push(&mut self.0, "menus", &menu);
self
}
}
pub fn menubar<M>() -> Menubar<M> {
new("menubar")
}
common_kind!(
Nav
);
impl<M> Nav<M> {
props! {
items(Vec<NavItem>) = "items";
active(String) = "active";
open(String) = "open";
variant(NavVariant) = "variant";
}
pub fn item(mut self, item: NavItem) -> Self {
push(&mut self.0, "items", &item);
self
}
}
pub fn nav<M>() -> Nav<M> {
new("nav")
}
kind_type!(
Breadcrumb
);
impl<M> Breadcrumb<M> {
base_methods!();
min_method!();
title_method!();
actions_methods!();
event_methods!();
props! {
items(Vec<Crumb>) = "items";
variant(BreadcrumbVariant) = "variant";
max(u32) = "max";
open(bool) = "open";
active(String) = "active";
}
pub fn crumb(mut self, crumb: Crumb) -> Self {
push(&mut self.0, "items", &crumb);
self
}
}
pub fn breadcrumb<M>() -> Breadcrumb<M> {
new("breadcrumb")
}
common_kind!(
Pager
);
impl<M> Pager<M> {
props! {
variant(PagerVariant) = "variant";
page(u64) = "page";
pages(u64) = "pages";
total(u64) = "total";
size(u64) = "size";
loading(bool) = "loading";
label(String) = "label";
}
}
pub fn pager<M>() -> Pager<M> {
new("pager")
}
kind_type!(
Split
);
impl<M> Split<M> {
base_methods!();
title_method!();
max_method!();
actions_methods!();
event_methods!();
children_methods!();
props! {
variant(SplitVariant) = "variant";
dir(SplitDir) = "dir";
sizes(Vec<f64>) = "sizes";
min(PaneMin) = "min";
h(Extent) = "h";
active(u32) = "active";
}
}
pub fn split<M>() -> Split<M> {
new("split")
}
container_kind!(
Scroll
);
impl<M> Scroll<M> {
props! {
variant(ScrollVariant) = "variant";
stick(Stick) = "stick";
pinned(bool) = "pinned";
bar(ScrollBar) = "bar";
gap(Gap) = "gap";
noun(Noun) = "noun";
}
}
pub fn scroll<M>() -> Scroll<M> {
new("scroll")
}
container_kind!(
Carousel
);
impl<M> Carousel<M> {
props! {
index(u32) = "index";
looping(bool) = "loop";
variant(CarouselVariant) = "variant";
h(Height) = "h";
}
}
pub fn carousel<M>() -> Carousel<M> {
new("carousel")
}
common_kind!(
Avatar
);
impl<M> Avatar<M> {
props! {
variant(AvatarVariant) = "variant";
text(String) = "text";
seed(String) = "seed";
icon(Icon) = "icon";
blob(String) = "blob";
size(AvatarSize) = "size";
status(Tone) = "status";
}
}
pub fn avatar<M>(text: impl Into<String>) -> Avatar<M> {
new::<M, Avatar<M>>("avatar").text(text)
}
kind_type!(
Avatars
);
impl<M> Avatars<M> {
base_methods!();
min_method!();
title_method!();
actions_methods!();
event_methods!();
props! {
items(Vec<AvatarItem>) = "items";
max(u32) = "max";
size(AvatarSize) = "size";
label(String) = "label";
}
}
pub fn avatars<M>(items: impl IntoIterator<Item = AvatarItem>) -> Avatars<M> {
new::<M, Avatars<M>>("avatars").items(items.into_iter().collect::<Vec<_>>())
}
kind_type!(
Empty
);
impl<M> Empty<M> {
base_methods!();
min_method!();
max_method!();
actions_methods!();
event_methods!();
children_methods!();
props! {
variant(EmptyVariant) = "variant";
icon(Icon) = "icon";
title(Text) = "title";
text(Text) = "text";
frame(bool) = "frame";
over(bool) = "over";
}
}
pub fn empty<M>(title: impl Into<Text>) -> Empty<M> {
new::<M, Empty<M>>("empty").title(title)
}
common_kind!(
Skeleton
);
impl<M> Skeleton<M> {
props! {
variant(SkeletonVariant) = "variant";
shape(SkeletonShape) = "shape";
lines(u32) = "lines";
rows(u32) = "rows";
tiles(u32) = "tiles";
}
}
pub fn skeleton<M>() -> Skeleton<M> {
new("skeleton")
}
kind_type!(
Tool
);
impl<M> Tool<M> {
base_methods!();
min_method!();
max_method!();
actions_methods!();
event_methods!();
children_methods!();
props! {
name(String) = "name";
title(Text) = "title";
target(Text) = "target";
target_kind(TargetKind) = "targetKind";
meta(Vec<Text>) = "meta";
badges(Vec<BadgeSpec>) = "badges";
note(Text) = "note";
exit(i64) = "exit";
status(Status) = "status";
confirm(ToolConfirm) = "confirm";
ask(Ask) = "ask";
approved(Approved) = "approved";
age(i64) = "age";
took(u64) = "took";
intent(String) = "intent";
frame(ToolFrame) = "frame";
collapsible(bool) = "collapsible";
collapsed(bool) = "collapsed";
preview(ToolPreview) = "preview";
tools(Vec<ToolButton>) = "tools";
}
}
pub fn tool<M>(name: impl Into<String>) -> Tool<M> {
new::<M, Tool<M>>("tool").name(name)
}
container_kind!(
Agent
);
impl<M> Agent<M> {
props! {
name(String) = "name";
agent(String) = "agent";
badges(Vec<BadgeSpec>) = "badges";
task(Text) = "task";
status(AgentStatus) = "status";
model(String) = "model";
thinking(String) = "thinking";
stats(AgentStats) = "stats";
tool(AgentTool) = "tool";
retry(Retry) = "retry";
depth(u32) = "depth";
collapsible(bool) = "collapsible";
collapsed(bool) = "collapsed";
}
}
pub fn agent<M>(name: impl Into<String>) -> Agent<M> {
new::<M, Agent<M>>("agent").name(name)
}
common_kind!(
Checklist
);
impl<M> Checklist<M> {
props! {
phases(Vec<Phase>) = "phases";
mode(ChecklistMode) = "mode";
note(Text) = "note";
}
}
pub fn checklist<M>() -> Checklist<M> {
new("checklist")
}
kind_type!(
Prefs
);
impl<M> Prefs<M> {
base_methods!();
min_method!();
max_method!();
actions_methods!();
event_methods!();
children_methods!();
props! {
title(String) = "title";
pages(Vec<PrefsPage>) = "pages";
page(String) = "page";
lead(String) = "lead";
sections(Vec<PrefsSection>) = "sections";
query(String) = "query";
cursor(usize) = "cursor";
focus(String) = "focus";
editing(Editing) = "editing";
}
}
pub fn prefs<M>() -> Prefs<M> {
new("prefs")
}
kind_type!(
Message
);
impl<M> Message<M> {
base_methods!();
min_method!();
title_method!();
max_method!();
event_methods!();
children_methods!();
props! {
variant(MessageVariant) = "variant";
side(MessageSide) = "side";
author(String) = "author";
time(String) = "time";
continued(bool) = "continued";
reactions(Vec<Reaction>) = "reactions";
actions(Vec<IconAction>) = "actions";
reveal(Reveal) = "reveal";
status(String) = "status";
pending(bool) = "pending";
}
pub fn avatar(mut self, avatar: impl Into<Slot<M>>) -> Self {
set_slot(&mut self.0, "avatar", avatar.into());
self
}
}
pub fn message<M>() -> Message<M> {
new("message")
}
kind_type!(
Attachment
);
impl<M> Attachment<M> {
base_methods!();
min_method!();
title_method!();
max_method!();
event_methods!();
props! {
name(String) = "name";
variant(AttachmentVariant) = "variant";
ext(String) = "ext";
mime(String) = "mime";
size(u64) = "size";
meta(String) = "meta";
blob(String) = "blob";
path(String) = "path";
state(AttachmentState) = "state";
progress(f64) = "progress";
error(String) = "error";
removable(bool) = "removable";
actions(Vec<IconAction>) = "actions";
}
}
pub fn attachment<M>(name: impl Into<String>) -> Attachment<M> {
new::<M, Attachment<M>>("attachment").name(name)
}
kind_type!(
Trace
);
impl<M> Trace<M> {
base_methods!();
min_method!();
max_method!();
actions_methods!();
event_methods!();
children_methods!();
props! {
variant(TraceVariant) = "variant";
lines(Vec<TraceLine>) = "lines";
status(Status) = "status";
title(Text) = "title";
summary(Text) = "summary";
icon(Icon) = "icon";
age(u64) = "age";
took(u64) = "took";
open(bool) = "open";
sources(Vec<Source>) = "sources";
}
pub fn line(mut self, line: TraceLine) -> Self {
push(&mut self.0, "lines", &line);
self
}
}
pub fn trace<M>() -> Trace<M> {
new("trace")
}
common_kind!(
Stacktrace
);
impl<M> Stacktrace<M> {
props! {
error(StackError) = "error";
frames(Vec<Frame>) = "frames";
selected(u32) = "selected";
lang(String) = "lang";
variant(StacktraceVariant) = "variant";
}
pub fn frame(mut self, frame: Frame) -> Self {
push(&mut self.0, "frames", &frame);
self
}
}
pub fn stacktrace<M>(error: StackError) -> Stacktrace<M> {
new::<M, Stacktrace<M>>("stacktrace").error(error)
}
common_kind!(
Suggestions
);
impl<M> Suggestions<M> {
props! {
variant(SuggestionsVariant) = "variant";
items(Vec<Suggestion>) = "items";
}
pub fn item(mut self, item: Suggestion) -> Self {
push(&mut self.0, "items", &item);
self
}
}
pub fn suggestions<M>() -> Suggestions<M> {
new("suggestions")
}
container_kind!(
Questions
);
impl<M> Questions<M> {
props! {
items(Vec<Question>) = "items";
step(u32) = "step";
answers(std::collections::BTreeMap<String, QuestionAnswer>) = "answers";
variant(QuestionsVariant) = "variant";
hint(String) = "hint";
submit(String) = "submit";
}
pub fn question(mut self, question: Question) -> Self {
push(&mut self.0, "items", &question);
self
}
}
pub fn questions<M>() -> Questions<M> {
new("questions")
}
fn push<M>(node: &mut Node<M>, key: &str, value: &impl serde::Serialize) {
let Ok(value) = serde_json::to_value(value) else {
return;
};
match node.props.get_mut(key) {
Some(serde_json::Value::Array(list)) => list.push(value),
_ => {
node
.props
.insert(key.to_owned(), serde_json::Value::Array(vec![value]));
},
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
fn wire<M>(node: impl Into<Node<M>>) -> serde_json::Value {
let node = node.into();
json!({ "k": node.kind, "p": node.props })
}
#[test]
fn text_takes_a_string_or_spans() {
assert_eq!(wire::<()>(text("hi")), json!({"k": "text", "p": {"text": "hi"}}));
let styled = text::<()>(spans!["a", span("b").style(Token::Muted).style("strong")]);
assert_eq!(
wire(styled.clone()),
json!({"k": "text", "p": {"spans": [{"t": "a"}, {"t": "b", "s": "muted strong"}]}})
);
assert_eq!(wire(styled.text("c")), json!({"k": "text", "p": {"text": "c"}}));
}
#[test]
fn json_carries_its_value_and_open_levels() {
assert_eq!(
wire::<()>(super::json(json!({"a": 1})).open(1)),
json!({"k": "json", "p": {"value": {"a": 1}, "open": 1}})
);
}
#[test]
fn typed_values_and_escape_hatches_serialize() {
let c = card::<()>()
.head("Deploy")
.status(Status::Running)
.tone("brand-new")
.min(Bound::w(Extent::Ch(10.0)))
.basis(Basis::Content)
.key(7)
.prop("future", json!([1]))
.prop("head", serde_json::Value::Null);
assert_eq!(
wire(c),
json!({"k": "card", "p": {
"status": "running", "tone": "brand-new", "min": {"w": "10ch"},
"basis": "content", "key": 7, "future": [1]
}})
);
}
#[test]
fn code_bars_folds_and_overview_serialize() {
let c = code::<()>("a\nb\nc")
.marks(vec![CodeMark { line: 1, wash: vec![0.9, 0.3], ..CodeMark::default() }])
.folds(vec![
CodeFold {
from: 2,
to: 3,
note: Some("cut".into()),
tone: Some(Tone::Error),
hovercard: Some(Node::<()>::from(text("why")).to_json()),
..CodeFold::default()
},
CodeFold { from: 5, to: 6, ..CodeFold::default() },
])
.overview(true)
.nums(vec![1, 2, 7]);
assert_eq!(
wire(c),
json!({"k": "code", "p": {
"text": "a\nb\nc", "nums": [1, 2, 7], "marks": [{"line": 1, "wash": [0.9, 0.3]}],
"folds": [
{"from": 2, "to": 3, "note": "cut", "tone": "error", "hovercard": {"k": "text", "p": {"text": "why"}}},
{"from": 5, "to": 6}
],
"overview": true
}})
);
}
#[test]
fn kind_specific_props_replace_common_meanings() {
let title = spans![span("Heading").style("strong")];
assert_eq!(
wire(tool::<()>("read").title(title.clone()))["p"]["title"],
json!([{"t": "Heading", "s": "strong"}])
);
assert_eq!(
wire(picker::<()>().title(title).actions(vec![PickerAction {
id: "open".into(),
primary: Some(true),
..PickerAction::default()
}]))["p"],
json!({
"title": [{"t": "Heading", "s": "strong"}],
"actions": [{"id": "open", "primary": true}]
})
);
assert_eq!(wire(prefs::<()>().title("Settings"))["p"]["title"], "Settings");
assert_eq!(
wire(list::<()>().max_fraction(0.5).max_lines(4))["p"]["max"],
json!({"lines": 4})
);
assert_eq!(wire(list::<()>().max_lines(4).max_fraction(0.5))["p"]["max"], 0.5);
}
#[test]
fn agent_statuses_have_their_own_vocabulary() {
for (status, expected) in [
(AgentStatus::Pending, "pending"),
(AgentStatus::Running, "running"),
(AgentStatus::Done, "done"),
(AgentStatus::Failed, "failed"),
(AgentStatus::Aborted, "aborted"),
(AgentStatus::Idle, "idle"),
(AgentStatus::Parked, "parked"),
(AgentStatus::Other("future".into()), "future"),
] {
assert_eq!(wire(agent::<()>("worker").status(status))["p"]["status"], expected);
}
}
#[test]
fn table_meter_cells_keep_the_wire_wrapper() {
let node = wire(table::<()>().row(TableRow::new("a").cell("usage", MeterCell {
value: Some(0.5),
title: Some("Half".into()),
tone: Some(Tone::Success),
..MeterCell::default()
})));
assert_eq!(
node["p"]["rows"][0]["cells"]["usage"],
json!({
"meter": {"value": 0.5, "title": "Half", "tone": "success"}
})
);
assert_eq!(
serde_json::to_value(Cell::Meter(MeterCell::default())).unwrap(),
json!({"meter": {}})
);
}
#[test]
fn form_controls_serialize_their_props() {
assert_eq!(
wire(
select::<()>()
.label("Model")
.value("haiku")
.option("opus")
.option(
SelectOption::new("haiku", "Haiku")
.detail("200k")
.group("Fast")
)
.search(true)
)["p"],
json!({
"label": "Model", "value": "haiku", "search": true, "options": [
{"value": "opus"},
{"value": "haiku", "label": "Haiku", "detail": "200k", "group": "Fast"}
]
})
);
assert_eq!(
wire(
switch::<()>("Wrap")
.on(true)
.variant(SwitchVariant::Segment)
.labels("Off", "On")
),
json!({"k": "switch", "p": {
"label": "Wrap", "on": true, "variant": "segment", "labels": ["Off", "On"]
}})
);
assert_eq!(
wire(
slider::<()>()
.value((200.0, 1200.0))
.min(0.0)
.max(2000.0)
.ticks(4u32)
.unit("ms")
)["p"],
json!({"value": [200.0, 1200.0], "min": 0.0, "max": 2000.0, "ticks": 4, "unit": "ms"})
);
assert_eq!(
wire(
field::<()>("Name")
.required(true)
.error(true)
.label_width(Extent::Ch(14.0))
.child(input())
)["p"],
json!({"label": "Name", "required": true, "error": true, "labelWidth": "14ch"})
);
assert_eq!(
wire(otp::<()>(6).text("12").mask("*").cursor(None))["p"],
json!({"length": 6, "text": "12", "mask": "*", "cursor": false})
);
assert_eq!(
wire(
calendar::<()>()
.range(true)
.value(DateValue::Range("2026-10-19".into(), None))
.week_start(WeekStart::Sun)
.marks(vec![DayMark::from("2026-10-20"), DayMark::new("2026-10-21", Tone::Error)])
)["p"],
json!({
"range": true, "value": ["2026-10-19", null], "weekStart": "sun",
"marks": [{"date": "2026-10-20"}, {"date": "2026-10-21", "tone": "error"}]
})
);
}
#[test]
fn menu_entries_serialize_as_strings_headers_and_items() {
let entries = vec![
MenuEntry::from("open"),
MenuEntry::header("Danger"),
MenuEntry::Separator,
MenuItem::new("trash", "Move to Trash")
.icon("trash")
.keys(["cmd", "backspace"])
.danger(true)
.into(),
MenuItem::new("export", "Export")
.item(MenuItem::new("csv", "CSV").checked(true))
.into(),
];
let expected = json!([
"open", {"header": "Danger"}, "-",
{"id": "trash", "label": "Move to Trash", "icon": "trash",
"keys": ["cmd", "backspace"], "danger": true},
{"id": "export", "label": "Export",
"items": [{"id": "csv", "label": "CSV", "checked": true}]}
]);
assert_eq!(wire(menu::<()>().text("More").items(entries.clone()))["p"]["items"], expected);
let actions = entries
.into_iter()
.fold(Actions::new(), |actions, entry| actions.menu(entry));
assert_eq!(wire(text::<()>("log").actions(actions))["p"]["actions"]["menu"], expected);
}
#[test]
fn kinds_with_their_own_min_max_title_and_actions_keep_them() {
assert_eq!(wire(avatars::<()>([AvatarItem::new("Cara")]).max(4u32))["p"]["max"], 4);
assert_eq!(wire(breadcrumb::<()>().crumb(Crumb::new("src", "src")).max(3u32))["p"]["max"], 3);
assert_eq!(
wire(
split::<()>()
.min(vec![Some(Extent::Ch(20.0)), None])
.sizes(vec![2.0, 1.0])
)["p"],
json!({"min": ["20ch", null], "sizes": [2.0, 1.0]})
);
assert_eq!(
wire(empty::<()>(spans![span("No runs").style("strong")]))["p"]["title"],
json!([{"t": "No runs", "s": "strong"}])
);
assert_eq!(
wire(attachment::<()>("a.png").actions(vec![IconAction::new("open").icon("eye")]))["p"],
json!({"name": "a.png", "actions": [{"act": "open", "icon": "eye"}]})
);
assert_eq!(
wire(carousel::<()>().looping(true).h(Height::Lines(10.0)))["p"],
json!({"loop": true, "h": "10lines"})
);
}
#[test]
fn work_kinds_serialize_lines_frames_and_questions() {
let t = trace::<()>().status(Status::Running).line(
TraceLine::new("s1", "Searched")
.target("tern tsp")
.status(Status::Done)
.took(1200)
.source(Source::new("https://x.dev").site("x")),
);
assert_eq!(
wire(t)["p"],
json!({"status": "running", "lines": [{
"id": "s1", "title": "Searched", "target": "tern tsp", "status": "done",
"took": 1200, "sources": [{"href": "https://x.dev", "site": "x"}]
}]})
);
let s = stacktrace::<()>(StackError::new("TypeError", "x is undefined"))
.frame(Frame::new("main", "src/a.ts").at(3, 7).app(false));
assert_eq!(
wire(s)["p"],
json!({
"error": {"type": "TypeError", "message": "x is undefined"},
"frames": [{"fn": "main", "file": "src/a.ts", "line": 3, "col": 7, "app": false}]
})
);
let q = questions::<()>()
.question(
Question::new("db", "Which database?")
.choice("postgres")
.choice(QuestionChoice::new("sqlite", "SQLite"))
.other("Something else"),
)
.answers(std::collections::BTreeMap::from([(
"db".to_owned(),
QuestionAnswer::from("postgres"),
)]));
assert_eq!(
wire(q)["p"],
json!({
"items": [{"id": "db", "prompt": "Which database?",
"choices": [{"value": "postgres"}, {"value": "sqlite", "label": "SQLite"}],
"other": {"placeholder": "Something else"}}],
"answers": {"db": "postgres"}
})
);
let m = message::<()>()
.author("Cara")
.side(MessageSide::End)
.reactions(vec![Reaction::new("👍", 2).on(true)])
.avatar(avatar("Cara"))
.child(md("hi"));
let node: Node<()> = m.into();
assert_eq!(node.props()["reactions"], json!([{"text": "👍", "count": 2, "on": true}]));
assert_eq!(node.child_nodes()[0].props()["key"], "avatar");
assert_eq!(node.child_nodes()[1].kind(), "md");
}
#[test]
fn extended_kinds_take_their_new_props() {
let t = table::<()>()
.col(Column::new("name", "Name").sortable(true))
.sort(Sort::desc("name"))
.multi(true)
.picked(vec!["a".to_owned()])
.bar_button(ActButton::new("restart", "Restart").icon("refresh"));
assert_eq!(
wire(t)["p"],
json!({
"cols": [{"id": "name", "head": "Name", "sortable": true}],
"sort": {"col": "name", "dir": "desc"}, "multi": true, "picked": ["a"],
"bar": [{"act": "restart", "text": "Restart", "icon": "refresh"}]
})
);
assert_eq!(
wire(
chart::<()>(ChartKind::Line)
.multi_series(vec![Series::new(vec![Some(1.0), None]).label("p50")])
.unit("ms")
.last(false)
)["p"],
json!({
"kind": "line", "series": [{"label": "p50", "values": [1.0, null]}],
"unit": "ms", "last": false
})
);
assert_eq!(
wire(
input::<()>()
.icon("search")
.keys(["cmd", "f"])
.clear(true)
.secret(true)
)["p"],
json!({"icon": "search", "keys": ["cmd", "f"], "clear": true, "secret": true})
);
assert_eq!(
wire(overlay::<()>().side(OverlaySide::Right).size_fraction(0.4))["p"],
json!({"side": "right", "size": 0.4})
);
assert_eq!(
wire(
tool::<()>("bash")
.status(Status::Ask)
.confirm(ToolConfirm::Scopes)
.ask(
Ask::new("Run it?")
.scope_option(AskScope::new("once", "Once"))
.always(AskAlways::Hidden)
)
)["p"],
json!({
"name": "bash", "status": "ask", "confirm": "scopes",
"ask": {"text": "Run it?", "always": false, "scopes": [{"id": "once", "label": "Once"}]}
})
);
assert_eq!(
wire(
rule::<()>()
.variant(RuleVariant::Note)
.label("Resumed")
.time("14:02")
)["p"],
json!({"variant": "note", "label": "Resumed", "time": "14:02"})
);
let cited = text::<()>(spans![
span("Rust 2024"),
span("").cite(Cite::new("https://blog.rust-lang.org").n(2))
])
.cite(CiteStyle::Note);
assert_eq!(
wire(cited)["p"],
json!({"cite": "note", "spans": [
{"t": "Rust 2024"}, {"t": "", "cite": [{"href": "https://blog.rust-lang.org", "n": 2}]}
]})
);
}
#[test]
fn list_props_append() {
let k = kv::<()>().item("a", "1").item("b", span("2").style("num"));
assert_eq!(
wire(k),
json!({"k": "kv", "p": {"items": [
{"k": "a", "v": "1"}, {"k": "b", "v": [{"t": "2", "s": "num"}]}
]}})
);
}
}