// wdoc standard library — common document elements and template functions
//
// Usage:
// import <wdoc.wcl>
// use wdoc::{doc, page, section, style, layout, heading, paragraph, image, code, data_table, callout, bold, italic, link, icon, icon_styled}
// use wdoc::draw::{diagram, rect, circle, ellipse, line, path, text, image, connection, ...}
// Helper used by shape template functions to read attributes with defaults.
// Registered globally (outside the wdoc namespace) so widget lambda bodies can
// reference it as bare `attr_or` without qualification.
declare attr_or(block: any, key: string, default: any) -> any
declare measure_text(text: any) -> map(string, any)
// Core helper for recursive templates. Returns direct child BlockRefs, optionally
// filtered by exact block kind, e.g. children(item, "UiMenuItem").
declare children(block: any) -> list(any)
declare children(block: any, kind: string) -> list(any)
declare block_children(block: any) -> list(any)
namespace wdoc {
// --- Structural schemas ---
@open
schema "doc" {
title: string
template: string @optional
version: string @optional
author: string @optional
}
@open
schema "page" {
section: string
title: string
template: string @optional
path: string @optional
date: string @optional
draft: bool @optional
weight: i64 @optional
summary: string @optional
tags: list(string) @optional
categories: list(string) @optional
params: map(string, string) @optional
}
@template("html", "wdoc::render_site_header")
@open
schema "site_header" {
title: string @optional
logo: string @optional
}
@template("html", "wdoc::render_site_nav")
@open
schema "site_nav" { }
@template("html", "wdoc::render_site_footer")
@open
schema "site_footer" {
text: string @optional
}
@template("html", "wdoc::render_site_main")
@open
schema "site_main" { }
@template("html", "wdoc::render_site_section")
@auto_id @open
schema "site_section" {
title: string @optional
}
@template("html", "wdoc::render_site_hero")
@auto_id @open
schema "site_hero" {
title: string
subtitle: string @optional
image: string @optional
}
@template("html", "wdoc::render_site_card_grid")
@auto_id @open
schema "site_card_grid" { }
@template("html", "wdoc::render_site_card")
@auto_id @open
schema "site_card" {
title: string
href: string @optional
summary: string @optional
}
@parent(["wdoc::page"]) @open
schema "signal" {
initial: any @optional
type: string @optional
}
@parent(["wdoc::page", "wdoc::layout"]) @open
schema "binding" {
signal: string
target: string
property: string
path: string @optional
format: string @optional
}
@open
schema "section" { }
@open
schema "style" { }
// Style child schemas — same names as content schemas but scoped to style blocks.
// Inside a style block, these are CSS property containers, not content elements.
@parent(["wdoc::style"]) @open schema "heading" { }
@parent(["wdoc::style"]) @open schema "paragraph" { }
@parent(["wdoc::style"]) @open schema "image" { }
@parent(["wdoc::style"]) @open schema "code" { }
@parent(["wdoc::style"]) @open schema "equation" { }
@parent(["wdoc::style"]) @open schema "data_table" { }
@parent(["wdoc::style"]) @open schema "callout" { }
@open
schema "layout" { }
// --- Content element schemas ---
@template("html", "wdoc::render_heading")
@auto_id @open
schema "heading" {
level: i32
content: string
}
// Per-level heading shorthands. Schemas use `@text` so users can write
// `h1 "Title"` (and `h1 myh1 "Title"` to also assign an anchor id).
// `@auto_id` prevents sibling h1/h2/... blocks from colliding when no
// explicit id is given.
@template("html", "wdoc::render_h1") @auto_id @open schema "h1" { content: string @text }
@template("html", "wdoc::render_h2") @auto_id @open schema "h2" { content: string @text }
@template("html", "wdoc::render_h3") @auto_id @open schema "h3" { content: string @text }
@template("html", "wdoc::render_h4") @auto_id @open schema "h4" { content: string @text }
@template("html", "wdoc::render_h5") @auto_id @open schema "h5" { content: string @text }
@template("html", "wdoc::render_h6") @auto_id @open schema "h6" { content: string @text }
@template("html", "wdoc::render_paragraph")
@auto_id @open
schema "paragraph" {
content: string
}
// Paragraph shorthand: `p "body text"`.
@template("html", "wdoc::render_paragraph") @auto_id @open schema "p" { content: string @text }
@template("html", "wdoc::render_image")
@auto_id @open
schema "image" {
src: string
alt: string @optional
width: string @optional
height: string @optional
}
@template("html", "wdoc::render_code")
@auto_id @open
schema "code" {
language: string @optional
content: string
}
@template("html", "wdoc::render_equation")
@auto_id @open
schema "equation" {
content: string @text
}
@template("html", "wdoc::render_table")
@auto_id @open
schema "data_table" {
caption: string @optional
}
@template("html", "wdoc::render_callout")
@auto_id @open
schema "callout" {
icon: string @optional
header: string @optional
color: string @optional
}
// Reusable scoped CSS emitted once into the shared WDoc stylesheet.
schema "css_fragment" {
scope: string
css: string
}
// Raw global CSS emitted once into the shared WDoc stylesheet.
schema "global_css" {
css: string
}
// First-class font asset declaration. The referenced font file is copied
// to the build output when the generated @font-face rule is emitted.
schema "font_asset" {
family: string
src: string
weight: string @optional
style: string @optional
display: string @optional
}
schema "icon_set" {
path: string
default: bool @optional
normalize_width: f64 @optional
normalize_height: f64 @optional
normalize_mode: string @optional
}
@parent(["wdoc::icon_set"]) @open schema "icon_part" {
selector: string
property: string
default: string @optional
}
// --- Diagram and shape schemas ---
namespace draw {
// diagram has no @template — it's special-cased by the wdoc renderer so
// shape template dispatch can run with the full context.
@open
schema "diagram" {
width: i32 @optional
height: i32 @optional
}
// Primitive shapes — handled directly by the renderer, no template fn.
@open schema "rect" { }
@open schema "circle" { }
@open schema "ellipse" { }
@open schema "line" { }
@open schema "path" { }
@open schema "text" { }
@open schema "text_block" { }
@open
schema "inline_svg" {
src: string @optional
content: string @optional
stroke: string @optional
fill: string @optional
stroke_width: f64 @optional
class: string @optional
opacity: f64 @optional
}
@open
schema "image" {
src: string
fit: string @optional @default("contain")
alt: string @optional
opacity: f64 @optional
rx: f64 @optional
ry: f64 @optional
tile_width: f64 @optional
tile_height: f64 @optional
}
@open
schema "icon" {
name: string
icon_set: string @optional
fill: string @optional
stroke: string @optional
props: map(string, any) @optional
normalize_width: f64 @optional
normalize_height: f64 @optional
normalize_mode: string @optional
class: string @optional
opacity: f64 @optional
}
@open
schema "map" {
src: string
content_width: f64 @optional
content_height: f64 @optional
view_x: f64 @optional
view_y: f64 @optional
view_width: f64 @optional
view_height: f64 @optional
min_zoom: f64 @optional
max_zoom: f64 @optional
alt: string @optional
background_fill: string @optional
}
@parent(["wdoc::draw::diagram"]) @open schema "dopesheet" {
src: string
columns: i32
frame_width: f64
frame_height: f64
frame_count: i32 @optional
offset_x: f64 @optional
offset_y: f64 @optional
gap_x: f64 @optional
gap_y: f64 @optional
sheet_width: f64 @optional
sheet_height: f64 @optional
}
@open schema "sprite" {
sheet: string
frame: i32 @optional
fit: string @optional
alt: string @optional
transparent_color: string @optional
transparent_tolerance: i32 @optional
}
@open schema "dopesheet_view" {
sheet: string
grid_stroke: string @optional
grid_stroke_width: f64 @optional
background_fill: string @optional
}
@open schema "tilemap" {
sheet: string
tile_width: f64
tile_height: f64
tile_render_height: f64 @optional
rows: list(any)
orientation: string @optional
background_fill: string @optional
grid_stroke: string @optional
grid_stroke_width: f64 @optional
transparent_color: string @optional
transparent_tolerance: i32 @optional
}
@open schema "game_layer" {
parallax: f64 @optional
locked: bool @optional
clip: bool @optional
opacity: f64 @optional
class: string @optional
}
@open schema "group" { }
@open
schema "terminal" {
rows: i32 @optional
cols: i32 @optional
content: string @optional
chrome: string @optional
title: string @optional
chrome_height: f64 @optional
chrome_fill: string @optional
chrome_foreground_fill: string @optional
chrome_border_fill: string @optional
background_fill: string @optional
foreground_fill: string @optional
}
@parent(["wdoc::draw::terminal"]) @open schema "terminal_text" {
row: i32
col: i32
content: string
}
@parent(["wdoc::draw::terminal"]) @open schema "terminal_box" {
row: i32
col: i32
rows: i32
cols: i32
title: string @optional
}
@parent(["wdoc::draw::terminal"]) @open schema "terminal_rule" {
row: i32
col: i32
rows: i32 @optional
cols: i32 @optional
direction: string @optional
}
@template("shape", "wdoc::terminal_widget_menubar")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_menubar" {
row: i32
col: i32
cols: i32 @optional
close_targets: string @optional
}
@template("shape", "wdoc::terminal_widget_menu")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_menu" {
row: i32
col: i32
rows: i32 @optional
cols: i32 @optional
close_targets: string @optional
leave_close_targets: string @optional
leave_guard_targets: string @optional
}
@template("shape", "wdoc::terminal_widget_context_menu")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_context_menu" {
row: i32
col: i32
rows: i32 @optional
cols: i32 @optional
close_targets: string @optional
leave_close_targets: string @optional
leave_guard_targets: string @optional
}
@parent(["wdoc::draw::terminal"]) @open schema "terminal_cursor" {
row: i32
col: i32
mode: string @optional
}
@parent(["wdoc::draw::terminal"]) @open schema "terminal_surface" {
row: i32
col: i32
rows: i32 @optional
cols: i32
}
@template("shape", "wdoc::terminal_widget_button")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_button" {
row: i32
col: i32
cols: i32 @optional
}
@template("shape", "wdoc::terminal_widget_textbox")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_textbox" {
row: i32
col: i32
rows: i32 @optional
cols: i32 @optional
}
@template("shape", "wdoc::terminal_widget_checkbox")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_checkbox" {
row: i32
col: i32
cols: i32 @optional
}
@template("shape", "wdoc::terminal_widget_radio")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_radio" {
row: i32
col: i32
cols: i32 @optional
}
@template("shape", "wdoc::terminal_widget_dropdown")
@parent(["wdoc::draw::terminal"]) @open schema "terminal_dropdown" {
row: i32
col: i32
cols: i32 @optional
}
@open schema "connection" { }
@open schema "class" { }
@parent(["wdoc::draw::class"]) @open schema "state" { }
@parent(["wdoc::draw::class"]) @open schema "animation" { }
@parent(["wdoc::draw::animation"]) @open schema "keyframe" { }
@parent([
"wdoc::draw::rect",
"wdoc::draw::circle",
"wdoc::draw::ellipse",
"wdoc::draw::line",
"wdoc::draw::path",
"wdoc::draw::text",
"wdoc::draw::text_block",
"wdoc::draw::inline_svg",
"wdoc::draw::image",
"wdoc::draw::icon",
"wdoc::draw::map",
"wdoc::draw::sprite",
"wdoc::draw::dopesheet_view",
"wdoc::draw::tilemap",
"wdoc::draw::game_layer",
"wdoc::draw::group",
"wdoc::draw::terminal",
"wdoc::draw::terminal_text",
"wdoc::draw::terminal_box",
"wdoc::draw::terminal_rule",
"wdoc::draw::terminal_menubar",
"wdoc::draw::terminal_menu",
"wdoc::draw::terminal_context_menu",
"wdoc::draw::terminal_cursor",
"wdoc::draw::terminal_surface",
"wdoc::draw::terminal_button",
"wdoc::draw::terminal_textbox",
"wdoc::draw::terminal_checkbox",
"wdoc::draw::terminal_radio",
"wdoc::draw::terminal_dropdown",
"wdoc::draw::phone",
"wdoc::draw::phone_landscape",
"wdoc::draw::browser",
"wdoc::draw::window",
"wdoc::draw::tablet",
"wdoc::draw::tablet_landscape",
"wdoc::draw::button",
"wdoc::draw::slider",
"wdoc::draw::input",
"wdoc::draw::card",
"wdoc::draw::collapsible_panel",
"wdoc::draw::avatar",
"wdoc::draw::toggle",
"wdoc::draw::checkbox",
"wdoc::draw::radio",
"wdoc::draw::button_group",
"wdoc::draw::textbox",
"wdoc::draw::dropdown",
"wdoc::draw::inline_image",
"wdoc::draw::menubar",
"wdoc::draw::context_menu",
"wdoc::draw::badge",
"wdoc::draw::navbar",
"wdoc::draw::stat_card",
"wdoc::draw::profile_card",
"wdoc::draw::action_panel",
"wdoc::draw::list_item",
"wdoc::draw::datatable",
"wdoc::draw::graph_node",
"wdoc::draw::pie_chart",
"wdoc::draw::bar_chart",
"wdoc::draw::line_chart",
"wdoc::draw::flowchart",
"wdoc::draw::flow_process",
"wdoc::draw::flow_decision",
"wdoc::draw::flow_terminal",
"wdoc::draw::flow_io",
"wdoc::draw::flow_subprocess",
"wdoc::draw::c4_person",
"wdoc::draw::c4_system",
"wdoc::draw::c4_container",
"wdoc::draw::c4_component",
"wdoc::draw::c4_boundary",
"wdoc::draw::uml_class",
"wdoc::draw::uml_actor",
"wdoc::draw::uml_package",
"wdoc::draw::uml_note",
"wdoc::draw::server",
"wdoc::draw::database",
"wdoc::draw::cloud",
"wdoc::draw::user"
])
@open schema "event" {
trigger: string
state: string @optional
target: string @optional
button: string @optional
mode: string @optional
duration_ms: i32 @optional
prevent_default: bool @optional
guard_targets: string @optional
}
@parent(["wdoc::draw::event"]) @open schema "set_signal" {
signal: string
value: any @optional
path: string @optional
}
// --- Composite widgets ---
// Each widget is bound to a `@template("shape", "fn")` function defined
// in WCL further down this file. The dispatcher calls the function with
// the widget's BlockRef and converts the returned list of shape
// descriptors into the widget's child shapes.
@template("shape", "wdoc::widget_phone") @open schema "phone" { }
@template("shape", "wdoc::widget_phone_landscape") @open schema "phone_landscape" { }
@template("shape", "wdoc::widget_browser") @open schema "browser" { }
@template("shape", "wdoc::widget_window") @open schema "window" { }
@template("shape", "wdoc::widget_tablet") @open schema "tablet" { }
@template("shape", "wdoc::widget_tablet_landscape") @open schema "tablet_landscape" { }
@template("shape", "wdoc::widget_button") @open schema "button" { }
@template("shape", "wdoc::widget_slider") @open schema "slider" { }
@template("shape", "wdoc::widget_input") @open schema "input" { }
@template("shape", "wdoc::widget_card") @open schema "card" { }
@template("shape", "wdoc::widget_collapsible_panel") @open schema "collapsible_panel" { }
@template("shape", "wdoc::widget_avatar") @open schema "avatar" { }
@template("shape", "wdoc::widget_toggle") @open schema "toggle" { }
@template("shape", "wdoc::widget_checkbox") @open schema "checkbox" { }
@template("shape", "wdoc::widget_radio") @open schema "radio" { }
@template("shape", "wdoc::widget_button_group") @open schema "button_group" { }
@template("shape", "wdoc::widget_textbox") @open schema "textbox" { }
@template("shape", "wdoc::widget_dropdown") @open schema "dropdown" { }
@template("shape", "wdoc::widget_inline_image") @open schema "inline_image" { }
@template("shape", "wdoc::widget_menubar") @open schema "menubar" { }
@template("shape", "wdoc::widget_context_menu") @open schema "context_menu" { }
@parent([
"wdoc::draw::menubar",
"wdoc::draw::context_menu",
"wdoc::draw::terminal_menubar",
"wdoc::draw::terminal_menu",
"wdoc::draw::terminal_context_menu",
"wdoc::draw::terminal_dropdown"
])
@open schema "menu_item" { }
@template("shape", "wdoc::widget_badge") @open schema "badge" { }
@template("shape", "wdoc::widget_navbar") @open schema "navbar" { }
@template("shape", "wdoc::widget_stat_card") @open schema "stat_card" { }
@template("shape", "wdoc::widget_profile_card") @open schema "profile_card" { }
@template("shape", "wdoc::widget_action_panel") @open schema "action_panel" { }
@template("shape", "wdoc::widget_list_item") @open schema "list_item" { }
@template("shape", "wdoc::widget_datatable") @open schema "datatable" { }
@structural @parent(["wdoc::draw::datatable"]) @open schema "datatable_column" { }
@structural @parent(["wdoc::draw::datatable"]) @open schema "datatable_row" { }
@structural @parent(["wdoc::draw::datatable_row"]) @open schema "datatable_cell" { }
@template("shape", "wdoc::widget_graph_node") @open schema "graph_node" { }
@structural @parent(["wdoc::draw::graph_node"]) @open schema "graph_row" { }
@structural @parent(["wdoc::draw::graph_node"]) @open schema "graph_divider" { }
@template("shape", "wdoc::widget_pie_chart") @open schema "pie_chart" { }
@template("shape", "wdoc::widget_bar_chart") @open schema "bar_chart" { }
@template("shape", "wdoc::widget_line_chart") @open schema "line_chart" { }
@parent([
"wdoc::draw::pie_chart",
"wdoc::draw::bar_chart",
"wdoc::draw::line_chart"
])
@structural @open schema "chart_point" { }
@structural @open schema "tooltip" { }
@parent(["wdoc::draw::text_block"]) @open schema "paragraph" { content: string }
@parent(["wdoc::draw::text_block"]) @open schema "p" { content: string @text }
@parent(["wdoc::draw::text_block"]) @open schema "code" {
language: string @optional
content: string
}
@template("shape", "wdoc::widget_flowchart") @open schema "flowchart" { }
@template("shape", "wdoc::widget_flow_process") @open schema "flow_process" { }
@template("shape", "wdoc::widget_flow_decision") @open schema "flow_decision" { }
@template("shape", "wdoc::widget_flow_terminal") @open schema "flow_terminal" { }
@template("shape", "wdoc::widget_flow_io") @open schema "flow_io" { }
@template("shape", "wdoc::widget_flow_subprocess") @open schema "flow_subprocess" { }
@template("shape", "wdoc::widget_c4_person") @open schema "c4_person" { }
@template("shape", "wdoc::widget_c4_system") @open schema "c4_system" { }
@template("shape", "wdoc::widget_c4_container") @open schema "c4_container" { }
@template("shape", "wdoc::widget_c4_component") @open schema "c4_component" { }
@template("shape", "wdoc::widget_c4_boundary") @open schema "c4_boundary" { }
@template("shape", "wdoc::widget_uml_class") @open schema "uml_class" { }
@template("shape", "wdoc::widget_uml_actor") @open schema "uml_actor" { }
@template("shape", "wdoc::widget_uml_package") @open schema "uml_package" { }
@template("shape", "wdoc::widget_uml_note") @open schema "uml_note" { }
@template("shape", "wdoc::widget_server") @open schema "server" { }
@template("shape", "wdoc::widget_database") @open schema "database" { }
@template("shape", "wdoc::widget_cloud") @open schema "cloud" { }
@template("shape", "wdoc::widget_user") @open schema "user" { }
}
// --- Decorator schemas ---
decorator_schema "style" {
target = [block]
name: string
}
// --- Inline formatting functions ---
export let shape_default_attrs = (b) => {
let kind = b.kind
kind == "wdoc::draw::flowchart" ? {
align = "layered",
gap = 48,
content_left = 16,
content_top = 36,
content_right = 16,
content_bottom = 16
} : (
kind == "wdoc::draw::card" && attr_or(b, "title", "") != "" ? {
content_top = 36
} : (
kind == "wdoc::draw::collapsible_panel" ? {
content_top = attr_or(b, "header_height", 36),
data_wdoc_collapsible_panel = "true",
data_wdoc_collapse_group = attr_or(b, "collapse_group", ""),
data_wdoc_collapse_header_height = attr_or(b, "header_height", 36),
data_wdoc_collapse_collapsed = attr_or(b, "collapsed", false),
_wdoc_runtime = "true"
} : (
kind == "wdoc::draw::phone" ? {
content_top = 74,
content_bottom = 50
} : (
kind == "wdoc::draw::phone_landscape" ? {
content_left = 18,
content_top = 68,
content_right = 28,
content_bottom = 14
} : (
kind == "wdoc::draw::browser" ? {
content_top = 72
} : (
kind == "wdoc::draw::window" ? {
content_top = 36
} : (
kind == "wdoc::draw::tablet" ? {
content_left = 18,
content_top = 42,
content_right = 18,
content_bottom = 24
} : (
kind == "wdoc::draw::tablet_landscape" ? {
content_left = 18,
content_top = 42,
content_right = 24,
content_bottom = 18
} : null
)
)
)
)
)
)
)
)
}
export let html_escape = value => replace(replace(replace(replace(to_string(value), "&", "&"), "<", "<"), ">", ">"), "\"", """)
@markup("**{text}**")
export let bold = text => html::render({ tag = "strong", content = text })
@markup("_{text}_")
export let italic = text => html::render({ tag = "em", content = text })
@markup("`{text}`")
export let code_span = text => html::render({ tag = "code", content = text })
@markup("[{text}]({url})")
export let link = (text, url) => html::render({ tag = "a", href = url, content = text })
@markup(":{name}:")
export let icon_markup = name => wdoc::icon(name)
declare icon(name: string) -> string
declare icon_styled(name: string, size: string, color: string) -> string
declare icon_props(name: string, size: string, props: map(string, any)) -> string
declare measure_text(text: any) -> map(string, any)
declare render_markup(text: string) -> string
declare render_children(block: any) -> string
declare block_children(block: any) -> list(any)
declare table_rows(block: any) -> map(string, any)
// --- Template rendering functions ---
export let slugify = value => regex_replace_all(regex_replace_all(regex_replace_all(lower(to_string(value)), "[^\\p{L}\\p{N}]", "-"), "^-+", ""), "-+$", "")
export let render_heading_at = (b, level) => {
let content = attr_or(b, "content", "")
let id = wdoc::slugify(content)
html::render({ tag = "h" + to_string(level), id = id, class_name = "wdoc-heading", html = wdoc::render_markup(content) })
}
export let render_heading = (b) => {
let level = attr_or(b, "level", 1)
let safe_level = level < 1 ? 1 : (level > 6 ? 6 : level)
wdoc::render_heading_at(b, safe_level)
}
export let render_h1 = (b) => wdoc::render_heading_at(b, 1)
export let render_h2 = (b) => wdoc::render_heading_at(b, 2)
export let render_h3 = (b) => wdoc::render_heading_at(b, 3)
export let render_h4 = (b) => wdoc::render_heading_at(b, 4)
export let render_h5 = (b) => wdoc::render_heading_at(b, 5)
export let render_h6 = (b) => wdoc::render_heading_at(b, 6)
export let render_paragraph = (b) => {
let content = attr_or(b, "content", "")
let rendered = wdoc::render_markup(content)
let has_block = contains(rendered, "<ul") || contains(rendered, "<ol") || contains(rendered, "<table") || contains(rendered, "<div")
html::render({ tag = has_block ? "div" : "p", class_name = "wdoc-paragraph", html = rendered })
}
export let render_image = (b) => {
let src = attr_or(b, "src", "")
let alt = attr_or(b, "alt", "")
let width = attr_or(b, "width", "")
let height = attr_or(b, "height", "")
html::render({ tag = "img", src = src, alt = alt != "" ? alt : null, width = width != "" ? width : null, height = height != "" ? height : null, class_name = "wdoc-image" })
}
export let render_code = (b) => {
let content = attr_or(b, "content", "")
let language = attr_or(b, "language", "")
let code_class = language != "" ? "language-" + language : null
html::render({ tag = "pre", class_name = "wdoc-code", children = [{ tag = "code", class_name = code_class, content = content }] })
}
export let render_equation = (b) => {
let content = attr_or(b, "content", "")
html::render({ tag = "div", class_name = "wdoc-equation", data_wdoc_equation = "display", content = "\\[" + content + "\\]" })
}
export let render_table = (b) => {
let tbl = wdoc::table_rows(b)
tbl.empty ? html::render({ tag = "p", class_name = "wdoc-paragraph", children = [{ tag = "em", content = "(empty table)" }] }) : html::render({
tag = "table",
class_name = "wdoc-table",
children = concat(
tbl.caption != "" ? [{ tag = "caption", html = wdoc::render_markup(tbl.caption) }] : [],
concat(
len(tbl.headers) > 0 ? [{ tag = "thead", children = [{ tag = "tr", children = map(tbl.headers, header => { tag = "th", html = wdoc::render_markup(header) }) }] }] : [],
[{ tag = "tbody", children = map(tbl.rows, row => { tag = "tr", children = map(row, cell => { tag = "td", html = wdoc::render_markup(cell) }) }) }]
)
)
})
}
export let render_callout = (b) => {
let color = attr_or(b, "color", "var(--color-nav-border)")
let header = attr_or(b, "header", "")
let icon = attr_or(b, "icon", "")
let icon_html = icon != "" ? wdoc::icon_styled(icon, "1em", color) + " " : ""
let header_html = (header != "" || icon != "") ? html::render({ tag = "div", class_name = "wdoc-callout-header", style = { color = color }, children = [{ tag = "raw", html = icon_html + wdoc::render_markup(header) }] }) : ""
html::render({
tag = "div",
class_name = "wdoc-callout",
style = { border_left_color = color },
children = [
{ tag = "raw", html = header_html },
{ tag = "div", class_name = "wdoc-callout-body", html = wdoc::render_children(b) }
]
})
}
export let render_site_header = (b) => {
let title = attr_or(b, "title", "")
let logo = attr_or(b, "logo", "")
let children_html = wdoc::render_children(b)
html::render({ tag = "header", class_name = "wdoc-site-header", children = [{ tag = "div", class_name = "wdoc-site-header-inner", children = concat(
logo != "" ? [{ tag = "img", class_name = "wdoc-site-logo", src = logo, alt = "" }] : [],
concat(title != "" ? [{ tag = "div", class_name = "wdoc-site-title", html = wdoc::render_markup(title) }] : [], [{ tag = "raw", html = children_html }])
) }] })
}
export let render_site_nav = (b) => {
let children_html = wdoc::render_children(b)
html::render({ tag = "nav", class_name = "wdoc-site-nav", html = children_html })
}
export let render_site_footer = (b) => {
let text = attr_or(b, "text", "")
let children_html = wdoc::render_children(b)
html::render({ tag = "footer", class_name = "wdoc-site-footer", children = concat(text != "" ? [{ tag = "p", html = wdoc::render_markup(text) }] : [], [{ tag = "raw", html = children_html }]) })
}
export let render_site_main = (b) => {
let children_html = wdoc::render_children(b)
html::render({ tag = "main", class_name = "wdoc-site-main", html = children_html })
}
export let render_site_section = (b) => {
let title = attr_or(b, "title", "")
let id = attr_or(b, "id", "")
let children_html = wdoc::render_children(b)
html::render({ tag = "section", id = id != "" ? id : null, class_name = "wdoc-site-section", children = concat(title != "" ? [{ tag = "h2", class_name = "wdoc-site-section-title", html = wdoc::render_markup(title) }] : [], [{ tag = "raw", html = children_html }]) })
}
export let render_site_hero = (b) => {
let title = attr_or(b, "title", "")
let subtitle = attr_or(b, "subtitle", "")
let image = attr_or(b, "image", "")
let children_html = wdoc::render_children(b)
html::render({ tag = "section", class_name = "wdoc-site-hero", style = image != "" ? { background_image = "linear-gradient(rgba(0,0,0,.40),rgba(0,0,0,.40)),url('" + image + "')" } : null, children = [{ tag = "div", class_name = "wdoc-site-hero-inner", children = concat([{ tag = "h1", html = wdoc::render_markup(title) }], concat(subtitle != "" ? [{ tag = "p", html = wdoc::render_markup(subtitle) }] : [], [{ tag = "raw", html = children_html }])) }] })
}
export let render_site_card_grid = (b) => {
let children_html = wdoc::render_children(b)
html::render({ tag = "div", class_name = "wdoc-site-card-grid", html = children_html })
}
export let render_site_card = (b) => {
let title = attr_or(b, "title", "")
let href = attr_or(b, "href", "")
let summary = attr_or(b, "summary", "")
let children_html = wdoc::render_children(b)
let body = concat([{ tag = "h3", html = wdoc::render_markup(title) }], concat(summary != "" ? [{ tag = "p", html = wdoc::render_markup(summary) }] : [], [{ tag = "raw", html = children_html }]))
html::render({ tag = href != "" ? "a" : "div", class_name = "wdoc-site-card", href = href != "" ? href : null, children = body })
}
export let render_content_block = (b) => {
let id = attr_or(b, "id", "")
let style = attr_or(b, "style", "")
let kind = attr_or(b, "css_kind", "")
let html = attr_or(b, "html", "")
let wrapped = (id != "" || style != "") ? html::render({ tag = "div", id = id != "" ? id : null, data_wdoc_content_id = id != "" ? id : null, class_name = style != "" ? "wdoc-style-" + style + "--" + kind : null, html = html }) : html
wrapped + "\n"
}
export let render_split_group = (item) => {
let dir_class = item.direction == "vertical" ? "wdoc-vsplit" : "wdoc-hsplit"
let children_html = wdoc::render_layout_items(item.splits)
html::render({ tag = "div", class_name = dir_class, html = "\n" + children_html }) + "\n"
}
export let render_split = (item) => {
let children_html = wdoc::render_layout_items(item.children)
html::render({ tag = "div", class_name = "wdoc-split", style = "flex: 0 0 " + to_string(item.size_percent) + "%;", html = "\n" + children_html }) + "\n"
}
export let render_layout_item = (item) => {
let item_kind = attr_or(item, "item_kind", "")
item_kind == "content" ? wdoc::render_content_block(item) : (
item_kind == "split_group" ? wdoc::render_split_group(item) : (
item_kind == "split" ? wdoc::render_split(item) : ""
)
)
}
export let render_layout_items = (items) => join("", map(items, (item) => wdoc::render_layout_item(item)))
// ----------------------------------------------------------------------
// Widget shape templates
// ----------------------------------------------------------------------
//
// Each widget below is a function that takes a block (the WCL block the
// user wrote in their diagram) and returns a list of "shape descriptors":
// maps with a `kind` field plus positioning and visual attributes. The
// wdoc renderer converts these descriptors into the widget's child shapes
// before resolving layout and emitting SVG.
//
// Recognized descriptor fields:
// kind — "rect" | "circle" | "ellipse" | "line" | "path" | "text" |
// "inline_svg" | "icon" | "image" | "map" | "group" | "connection"
// id — shape ID for layout and connection endpoints
// x, y, width, height — absolute placement (relative to widget)
// top, bottom, left, right — anchored placement
// map children use the map's content_width/content_height coordinate space
// align, gap, padding — layout for nested children
// layout_role — "node" opts template children into parent layout
// z_index — sibling paint order, higher renders later
// rx, ry, r — corner / radius
// fill, stroke, stroke_width, stroke_dasharray, opacity
// class, style, cursor, pointer_events
// x1, y1, x2, y2 (line) | d (path)
// from, to, direction, label, curve, from_anchor, to_anchor (connection)
// content, font_size, anchor, font_family, font_weight, font_style,
// line_height, text_decoration, letter_spacing (text)
// children — list of more descriptors (nested)
//
// Use `attr_or(block, "key", default)` to read user attributes safely.
// ----- UI widgets -----