feat: add stdlib and browser examples

- stdlib/html.lux: Type-safe HTML construction
- stdlib/browser.lux: Browser utilities
- examples/web/: Counter app with DOM manipulation
- examples/counter.lux: Simple counter example

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 03:54:17 -05:00
parent ccd335c80f
commit 634f665b1b
11 changed files with 1177 additions and 0 deletions

173
examples/counter.lux Normal file
View File

@@ -0,0 +1,173 @@
// Counter Example - A simple interactive counter using TEA pattern
//
// This example demonstrates:
// - Model-View-Update architecture (TEA)
// - Html DSL for describing UI (inline version)
// - Message-based state updates
// ============================================================================
// Html Types (subset of stdlib/html)
// ============================================================================
type Html<M> =
| Element(String, List<Attr<M>>, List<Html<M>>)
| Text(String)
| Empty
type Attr<M> =
| Class(String)
| Id(String)
| OnClick(M)
// Html builder helpers
fn div<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("div", attrs, children)
fn span<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("span", attrs, children)
fn h1<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h1", attrs, children)
fn button<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("button", attrs, children)
fn text<M>(content: String): Html<M> =
Text(content)
fn class<M>(name: String): Attr<M> =
Class(name)
fn onClick<M>(msg: M): Attr<M> =
OnClick(msg)
// ============================================================================
// Model - The application state (using ADT wrapper)
// ============================================================================
type Model =
| Counter(Int)
fn getCount(model: Model): Int =
match model {
Counter(n) => n
}
fn init(): Model = Counter(0)
// ============================================================================
// Messages - Events that can occur
// ============================================================================
type Msg =
| Increment
| Decrement
| Reset
// ============================================================================
// Update - State transitions
// ============================================================================
fn update(model: Model, msg: Msg): Model =
match msg {
Increment => Counter(getCount(model) + 1),
Decrement => Counter(getCount(model) - 1),
Reset => Counter(0)
}
// ============================================================================
// View - Render the UI
// ============================================================================
fn viewCounter(count: Int): Html<Msg> = {
let countText = text(toString(count))
let countSpan = span([class("count")], [countText])
let displayDiv = div([class("counter-display")], [countSpan])
let minusBtn = button([onClick(Decrement), class("btn")], [text("-")])
let resetBtn = button([onClick(Reset), class("btn btn-reset")], [text("Reset")])
let plusBtn = button([onClick(Increment), class("btn")], [text("+")])
let buttonsDiv = div([class("counter-buttons")], [minusBtn, resetBtn, plusBtn])
let title = h1([], [text("Counter")])
div([class("counter-app")], [title, displayDiv, buttonsDiv])
}
fn view(model: Model): Html<Msg> = viewCounter(getCount(model))
// ============================================================================
// Debug: Print Html structure
// ============================================================================
fn showAttr(attr: Attr<Msg>): String =
match attr {
Class(s) => "class=\"" + s + "\"",
Id(s) => "id=\"" + s + "\"",
OnClick(msg) => match msg {
Increment => "onclick=\"Increment\"",
Decrement => "onclick=\"Decrement\"",
Reset => "onclick=\"Reset\""
}
}
fn showAttrs(attrs: List<Attr<Msg>>): String =
match List.head(attrs) {
None => "",
Some(a) => match List.tail(attrs) {
None => showAttr(a),
Some(rest) => showAttr(a) + " " + showAttrs(rest)
}
}
fn showChildren(children: List<Html<Msg>>, indent: Int): String =
match List.head(children) {
None => "",
Some(c) => match List.tail(children) {
None => showHtml(c, indent),
Some(rest) => showHtml(c, indent) + showChildren(rest, indent)
}
}
fn showHtml(html: Html<Msg>, indent: Int): String =
match html {
Empty => "",
Text(s) => s,
Element(tag, attrs, children) => {
let attrStr = showAttrs(attrs)
let attrPart = if String.length(attrStr) > 0 then " " + attrStr else ""
let childStr = showChildren(children, indent + 2)
"<" + tag + attrPart + ">" + childStr + "</" + tag + ">"
}
}
// ============================================================================
// Entry point
// ============================================================================
fn main(): Unit with {Console} = {
let model = init()
Console.print("=== Counter App (TEA Pattern) ===")
Console.print("")
Console.print("Initial count: " + toString(getCount(model)))
Console.print("")
let m1 = update(model, Increment)
Console.print("After Increment: " + toString(getCount(m1)))
let m2 = update(m1, Increment)
Console.print("After Increment: " + toString(getCount(m2)))
let m3 = update(m2, Increment)
Console.print("After Increment: " + toString(getCount(m3)))
let m4 = update(m3, Decrement)
Console.print("After Decrement: " + toString(getCount(m4)))
let m5 = update(m4, Reset)
Console.print("After Reset: " + toString(getCount(m5)))
Console.print("")
Console.print("=== View (HTML Structure) ===")
Console.print(showHtml(view(m2), 0))
}
let output = run main() with {}

154
examples/web/README.md Normal file
View File

@@ -0,0 +1,154 @@
# Lux Web Examples
Interactive browser examples demonstrating Lux compiled to JavaScript.
## Quick Start
```bash
# 1. Compile the counter example
lux compile examples/web/counter.lux --target js -o examples/web/counter.js
# 2. Start a local server
cd examples/web
./serve.sh
# 3. Open in browser
# http://localhost:8080/index.html
```
## Examples
### Counter (String-based)
A simple counter demonstrating the TEA (The Elm Architecture) pattern using string concatenation for HTML:
```lux
fn view(model: Model): String = {
"<div>" + toString(getCount(model)) + "</div>"
}
```
### Counter with Html Module (Type-safe)
The same counter using the type-safe Html module:
```lux
fn view(model: Model): String = {
let count = getCount(model)
let display = Html.div([Html.class("display")], [Html.text(toString(count))])
Html.render(display)
}
```
Compile with:
```bash
lux compile examples/web/counter_html.lux --target js -o examples/web/counter_html.js
```
## The Elm Architecture (TEA)
Both examples follow the TEA pattern:
- **Model**: Application state (`Counter(Int)`)
- **Msg**: Events (`Increment`, `Decrement`, `Reset`)
- **Update**: State transitions `(Model, Msg) -> Model`
- **View**: HTML rendering `Model -> Html` or `Model -> String`
## Available Modules
### Html Module
Type-safe HTML construction:
```lux
// Elements
Html.div(attrs, children)
Html.span(attrs, children)
Html.button(attrs, children)
Html.input(attrs, children)
Html.h1(attrs, children)
// ... 30+ elements
// Attributes
Html.class("name")
Html.id("id")
Html.href("url")
Html.style("...")
Html.attr("name", "value")
// Events
Html.onClick(handler)
Html.onInput(handler)
Html.on("event", handler)
// Text
Html.text("content")
// Rendering
Html.render(node) // -> String (for innerHTML)
Html.renderToDom(node) // -> Element (for appendChild)
```
### Dom Effect
Direct DOM manipulation:
```lux
fn main(): Unit with {Dom} = {
match Dom.querySelector("#app") {
Some(el) => Dom.setTextContent(el, "Hello!"),
None => ()
}
}
```
Available operations:
- `querySelector`, `querySelectorAll`, `getElementById`
- `createElement`, `createTextNode`, `appendChild`
- `setTextContent`, `getTextContent`, `setInnerHtml`
- `setAttribute`, `getAttribute`, `addClass`, `removeClass`
- `addEventListener`, `focus`, `blur`
- `getValue`, `setValue` (for inputs)
- `scrollTo`, `getWindowSize`, `getBoundingClientRect`
## How It Works
1. Lux code is compiled to JavaScript using `lux compile --target js`
2. The HTML page loads the compiled JS
3. A minimal runtime handles the TEA loop:
- `luxInit_lux()` creates initial state
- `luxUpdate_lux(model, msg)` handles state updates
- `luxView_lux(model)` renders HTML
- `dispatch(msg)` triggers updates and re-renders
## TEA Runtime
For applications using the Html module with event handlers, you can use the built-in TEA runtime:
```javascript
Lux.app({
init: luxInit_lux(),
update: (model, msg) => luxUpdate_lux(model, msg),
view: (model, dispatch) => luxView_lux(model),
root: "#app"
});
```
Or for string-based views:
```javascript
Lux.simpleApp({
init: luxInit_lux(),
update: (model, msg) => luxUpdate_lux(model, msg),
view: (model) => luxView_lux(model),
root: "#app"
});
```
## Development
To modify an example:
1. Edit the `.lux` file
2. Recompile: `lux compile <file>.lux --target js -o <file>.js`
3. Refresh the browser

144
examples/web/counter.js Normal file
View File

@@ -0,0 +1,144 @@
// Lux Runtime
const Lux = {
Some: (value) => ({ tag: "Some", value }),
None: () => ({ tag: "None" }),
Ok: (value) => ({ tag: "Ok", value }),
Err: (error) => ({ tag: "Err", error }),
Cons: (head, tail) => [head, ...tail],
Nil: () => [],
defaultHandlers: {
Console: {
print: (msg) => console.log(msg),
readLine: () => {
if (typeof require !== 'undefined') {
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
return new Promise(resolve => rl.question('', answer => { rl.close(); resolve(answer); }));
}
return prompt('') || '';
},
readInt: () => parseInt(Lux.defaultHandlers.Console.readLine(), 10)
},
Random: {
int: (min, max) => Math.floor(Math.random() * (max - min + 1)) + min,
bool: () => Math.random() < 0.5,
float: () => Math.random()
},
Time: {
now: () => Date.now(),
sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms))
},
Http: {
get: async (url) => {
try {
const response = await fetch(url);
const body = await response.text();
const headers = [];
response.headers.forEach((v, k) => headers.push([k, v]));
return Lux.Ok({ status: response.status, body, headers });
} catch (e) {
return Lux.Err(e.message);
}
},
post: async (url, body) => {
try {
const response = await fetch(url, { method: 'POST', body });
const respBody = await response.text();
const headers = [];
response.headers.forEach((v, k) => headers.push([k, v]));
return Lux.Ok({ status: response.status, body: respBody, headers });
} catch (e) {
return Lux.Err(e.message);
}
},
postJson: async (url, json) => {
try {
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(json)
});
const body = await response.text();
const headers = [];
response.headers.forEach((v, k) => headers.push([k, v]));
return Lux.Ok({ status: response.status, body, headers });
} catch (e) {
return Lux.Err(e.message);
}
}
}
}
};
// Model constructors
function Counter_lux(field0) { return { tag: "Counter", field0: field0 }; }
// Msg constructors
const Increment_lux = { tag: "Increment" };
const Decrement_lux = { tag: "Decrement" };
const Reset_lux = { tag: "Reset" };
function getCount_lux(m) {
const _match_0 = m;
let _result_1;
if (_match_0.tag === "Counter") {
const n = _match_0.field0;
_result_1 = n;
} else {
throw new Error('Non-exhaustive match on ' + JSON.stringify(_match_0));
}
return _result_1;
}
function init_lux() {
return Counter_lux(0);
}
function update_lux(model, msg) {
const _match_2 = msg;
let _result_3;
if (_match_2.tag === "Increment") {
_result_3 = Counter_lux((getCount_lux(model) + 1));
} else if (_match_2.tag === "Decrement") {
_result_3 = Counter_lux((getCount_lux(model) - 1));
} else if (_match_2.tag === "Reset") {
_result_3 = Counter_lux(0);
} else {
throw new Error('Non-exhaustive match on ' + JSON.stringify(_match_2));
}
return _result_3;
}
function view_lux(model) {
const count_4 = getCount_lux(model);
return (((((((((("<div class=\"counter\">" + "<h1>Lux Counter</h1>") + "<div class=\"display\">") + String(count_4)) + "</div>") + "<div class=\"buttons\">") + "<button onclick=\"dispatch('Decrement')\">-</button>") + "<button onclick=\"dispatch('Reset')\">Reset</button>") + "<button onclick=\"dispatch('Increment')\">+</button>") + "</div>") + "</div>");
}
function luxInit_lux() {
return init_lux();
}
function luxUpdate_lux(model, msgName) {
const _match_5 = msgName;
let _result_6;
if (_match_5 === "Increment") {
_result_6 = update_lux(model, Increment_lux);
} else if (_match_5 === "Decrement") {
_result_6 = update_lux(model, Decrement_lux);
} else if (_match_5 === "Reset") {
_result_6 = update_lux(model, Reset_lux);
} else if (true) {
_result_6 = model;
} else {
throw new Error('Non-exhaustive match on ' + JSON.stringify(_match_5));
}
return _result_6;
}
function luxView_lux(model) {
return view_lux(model);
}

62
examples/web/counter.lux Normal file
View File

@@ -0,0 +1,62 @@
// Simple Counter for Browser
// Compile with: lux compile examples/web/counter.lux --target js -o examples/web/counter.js
// ============================================================================
// Model
// ============================================================================
type Model = | Counter(Int)
fn getCount(m: Model): Int = match m { Counter(n) => n }
fn init(): Model = Counter(0)
// ============================================================================
// Messages
// ============================================================================
type Msg = | Increment | Decrement | Reset
// ============================================================================
// Update
// ============================================================================
fn update(model: Model, msg: Msg): Model =
match msg {
Increment => Counter(getCount(model) + 1),
Decrement => Counter(getCount(model) - 1),
Reset => Counter(0)
}
// ============================================================================
// View - Returns HTML string for simplicity
// ============================================================================
fn view(model: Model): String = {
let count = getCount(model)
"<div class=\"counter\">" +
"<h1>Lux Counter</h1>" +
"<div class=\"display\">" + toString(count) + "</div>" +
"<div class=\"buttons\">" +
"<button onclick=\"dispatch('Decrement')\">-</button>" +
"<button onclick=\"dispatch('Reset')\">Reset</button>" +
"<button onclick=\"dispatch('Increment')\">+</button>" +
"</div>" +
"</div>"
}
// ============================================================================
// Export for browser runtime
// ============================================================================
fn luxInit(): Model = init()
fn luxUpdate(model: Model, msgName: String): Model =
match msgName {
"Increment" => update(model, Increment),
"Decrement" => update(model, Decrement),
"Reset" => update(model, Reset),
_ => model
}
fn luxView(model: Model): String = view(model)

View File

@@ -0,0 +1,82 @@
// Counter with Html Module (Type-safe HTML)
// Compile with: lux compile examples/web/counter_html.lux --target js -o examples/web/counter_html.js
//
// This version uses the Html module for type-safe HTML construction
// instead of string concatenation. The Html module provides:
// - Type-safe element constructors (div, button, etc.)
// - Type-safe attribute constructors (class, onClick, etc.)
// - Automatic HTML escaping
// ============================================================================
// Model
// ============================================================================
type Model = | Counter(Int)
fn getCount(m: Model): Int = match m { Counter(n) => n }
fn init(): Model = Counter(0)
// ============================================================================
// Messages
// ============================================================================
type Msg = | Increment | Decrement | Reset
// ============================================================================
// Update
// ============================================================================
fn update(model: Model, msg: Msg): Model =
match msg {
Increment => Counter(getCount(model) + 1),
Decrement => Counter(getCount(model) - 1),
Reset => Counter(0)
}
// ============================================================================
// View - Type-safe HTML using Html module
// ============================================================================
fn view(model: Model): String = {
let count = getCount(model)
// Build HTML tree using Html module
let title = Html.h1([], [Html.text("Lux Counter")])
let display = Html.div([Html.class("display")], [Html.text(toString(count))])
let decBtn = Html.button(
[Html.attr("onclick", "dispatch('Decrement')")],
[Html.text("-")]
)
let resetBtn = Html.button(
[Html.attr("onclick", "dispatch('Reset')")],
[Html.text("Reset")]
)
let incBtn = Html.button(
[Html.attr("onclick", "dispatch('Increment')")],
[Html.text("+")]
)
let buttons = Html.div([Html.class("buttons")], [decBtn, resetBtn, incBtn])
let container = Html.div([Html.class("counter")], [title, display, buttons])
// Render to HTML string
Html.render(container)
}
// ============================================================================
// Export for browser runtime
// ============================================================================
fn luxInit(): Model = init()
fn luxUpdate(model: Model, msgName: String): Model =
match msgName {
"Increment" => update(model, Increment),
"Decrement" => update(model, Decrement),
"Reset" => update(model, Reset),
_ => model
}
fn luxView(model: Model): String = view(model)

133
examples/web/index.html Normal file
View File

@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lux Counter Example</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.counter {
background: white;
padding: 2rem 3rem;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
text-align: center;
}
h1 {
color: #333;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.display {
font-size: 4rem;
font-weight: bold;
color: #667eea;
padding: 1rem;
min-width: 150px;
}
.buttons {
display: flex;
gap: 0.5rem;
justify-content: center;
margin-top: 1rem;
}
button {
font-size: 1.5rem;
padding: 0.5rem 1.5rem;
border: none;
border-radius: 8px;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.1s;
font-weight: bold;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
button:active {
transform: translateY(0);
}
button:nth-child(1) {
background: #ff6b6b;
color: white;
}
button:nth-child(2) {
background: #868e96;
color: white;
}
button:nth-child(3) {
background: #51cf66;
color: white;
}
.footer {
position: fixed;
bottom: 1rem;
color: white;
opacity: 0.8;
font-size: 0.9rem;
}
.footer a {
color: white;
}
</style>
</head>
<body>
<div id="app">Loading...</div>
<div class="footer">
Built with <strong>Lux</strong> - A functional language with first-class effects
</div>
<!-- Load compiled Lux code -->
<script src="counter.js"></script>
<!-- Minimal TEA runtime -->
<script>
// Global state
let model = luxInit_lux();
// Dispatch function called by button onclick handlers
function dispatch(msgName) {
model = luxUpdate_lux(model, msgName);
render();
}
// Render the view
function render() {
const html = luxView_lux(model);
document.getElementById('app').innerHTML = html;
}
// Initial render
render();
console.log('Lux Counter loaded!');
console.log('Initial model:', model);
</script>
</body>
</html>

26
examples/web/serve.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
# Simple HTTP server for testing Lux web examples
# Usage: ./serve.sh [port]
PORT=${1:-8080}
DIR="$(dirname "$0")"
echo "Serving Lux web examples at http://localhost:$PORT"
echo "Open http://localhost:$PORT/index.html in your browser"
echo "Press Ctrl+C to stop"
echo ""
cd "$DIR"
# Try python3 first, then python, then node
if command -v python3 &> /dev/null; then
python3 -m http.server $PORT
elif command -v python &> /dev/null; then
python -m SimpleHTTPServer $PORT
elif command -v npx &> /dev/null; then
npx serve -p $PORT .
else
echo "Error: No suitable HTTP server found."
echo "Install Python or Node.js to serve files."
exit 1
fi

89
stdlib/browser.lux Normal file
View File

@@ -0,0 +1,89 @@
// Browser Module - Effects for browser/DOM interaction
//
// This module provides effects for interacting with the browser DOM,
// local storage, and other browser APIs.
// Opaque Element type (represents a DOM element)
type Element =
| DomElement(Int)
// DOM manipulation effect
effect Dom {
fn getElementById(id: String): Option<Element>
fn querySelector(selector: String): Option<Element>
fn querySelectorAll(selector: String): List<Element>
fn createElement(tag: String): Element
fn createTextNode(text: String): Element
fn appendChild(parent: Element, child: Element): Unit
fn removeChild(parent: Element, child: Element): Unit
fn setAttribute(element: Element, name: String, value: String): Unit
fn removeAttribute(element: Element, name: String): Unit
fn setProperty(element: Element, name: String, value: String): Unit
fn setTextContent(element: Element, text: String): Unit
fn getBody(): Element
fn focus(element: Element): Unit
fn blur(element: Element): Unit
}
// Browser storage effect
effect Storage {
fn getItem(key: String): Option<String>
fn setItem(key: String, value: String): Unit
fn removeItem(key: String): Unit
fn clear(): Unit
}
// Browser navigation effect
effect Navigation {
fn pushState(url: String): Unit
fn replaceState(url: String): Unit
fn back(): Unit
fn forward(): Unit
fn getLocation(): String
fn getPathname(): String
}
// Browser window effect
effect Window {
fn alert(message: String): Unit
fn confirm(message: String): Bool
fn scrollTo(x: Int, y: Int): Unit
fn getInnerWidth(): Int
fn getInnerHeight(): Int
}
// ============================================================================
// Subscription types for listening to external events
// ============================================================================
type Sub<M> =
| OnAnimationFrame(fn(Float): M)
| OnResize(fn(Int, Int): M)
| OnKeyPress(fn(String): M)
| OnKeyDown(fn(String): M)
| OnKeyUp(fn(String): M)
| OnMouseMove(fn(Int, Int): M)
| OnClick(fn(Int, Int): M)
| OnUrlChange(fn(String): M)
| Every(Int, fn(Float): M)
| NoSub
// Subscription constructors
fn onAnimationFrame<M>(toMsg: fn(Float): M): Sub<M> =
OnAnimationFrame(toMsg)
fn onResize<M>(toMsg: fn(Int, Int): M): Sub<M> =
OnResize(toMsg)
fn onKeyPress<M>(toMsg: fn(String): M): Sub<M> =
OnKeyPress(toMsg)
fn every<M>(ms: Int, toMsg: fn(Float): M): Sub<M> =
Every(ms, toMsg)
fn noSub<M>(): Sub<M> =
NoSub
// Combine multiple subscriptions
fn batch<M>(subs: List<Sub<M>>): List<Sub<M>> =
subs

298
stdlib/html.lux Normal file
View File

@@ -0,0 +1,298 @@
// Html Module - DSL for building HTML/DOM structures
//
// This module provides a type-safe way to describe HTML elements that can be
// compiled to efficient JavaScript for browser rendering.
//
// Example usage:
// let myView = div([class("container")], [
// h1([id("title")], [text("Hello!")]),
// button([onClick(Increment)], [text("+")])
// ])
// Html type represents a DOM structure
// Parameterized by Msg - the type of messages emitted by event handlers
type Html<M> =
| Element(String, List<Attr<M>>, List<Html<M>>)
| Text(String)
| Empty
// Attributes that can be applied to elements
type Attr<M> =
| Class(String)
| Id(String)
| Style(String, String)
| Href(String)
| Src(String)
| Alt(String)
| Type(String)
| Value(String)
| Placeholder(String)
| Disabled(Bool)
| Checked(Bool)
| Name(String)
| OnClick(M)
| OnInput(fn(String): M)
| OnSubmit(M)
| OnChange(fn(String): M)
| OnMouseEnter(M)
| OnMouseLeave(M)
| OnFocus(M)
| OnBlur(M)
| OnKeyDown(fn(String): M)
| OnKeyUp(fn(String): M)
| DataAttr(String, String)
// ============================================================================
// Element builders - Container elements
// ============================================================================
fn div<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("div", attrs, children)
fn span<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("span", attrs, children)
fn section<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("section", attrs, children)
fn article<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("article", attrs, children)
fn header<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("header", attrs, children)
fn footer<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("footer", attrs, children)
fn nav<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("nav", attrs, children)
fn main<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("main", attrs, children)
fn aside<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("aside", attrs, children)
// ============================================================================
// Element builders - Text elements
// ============================================================================
fn h1<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h1", attrs, children)
fn h2<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h2", attrs, children)
fn h3<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h3", attrs, children)
fn h4<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h4", attrs, children)
fn h5<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h5", attrs, children)
fn h6<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("h6", attrs, children)
fn p<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("p", attrs, children)
fn pre<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("pre", attrs, children)
fn code<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("code", attrs, children)
fn blockquote<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("blockquote", attrs, children)
// ============================================================================
// Element builders - Inline elements
// ============================================================================
fn a<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("a", attrs, children)
fn strong<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("strong", attrs, children)
fn em<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("em", attrs, children)
fn small<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("small", attrs, children)
fn br<M>(): Html<M> =
Element("br", [], [])
fn hr<M>(): Html<M> =
Element("hr", [], [])
// ============================================================================
// Element builders - Lists
// ============================================================================
fn ul<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("ul", attrs, children)
fn ol<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("ol", attrs, children)
fn li<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("li", attrs, children)
// ============================================================================
// Element builders - Forms
// ============================================================================
fn form<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("form", attrs, children)
fn input<M>(attrs: List<Attr<M>>): Html<M> =
Element("input", attrs, [])
fn textarea<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("textarea", attrs, children)
fn button<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("button", attrs, children)
fn label<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("label", attrs, children)
fn select<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("select", attrs, children)
fn option<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("option", attrs, children)
// ============================================================================
// Element builders - Media
// ============================================================================
fn img<M>(attrs: List<Attr<M>>): Html<M> =
Element("img", attrs, [])
fn video<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("video", attrs, children)
fn audio<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("audio", attrs, children)
// ============================================================================
// Element builders - Tables
// ============================================================================
fn table<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("table", attrs, children)
fn thead<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("thead", attrs, children)
fn tbody<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("tbody", attrs, children)
fn tr<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("tr", attrs, children)
fn th<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("th", attrs, children)
fn td<M>(attrs: List<Attr<M>>, children: List<Html<M>>): Html<M> =
Element("td", attrs, children)
// ============================================================================
// Text and empty nodes
// ============================================================================
fn text<M>(content: String): Html<M> =
Text(content)
fn empty<M>(): Html<M> =
Empty
// ============================================================================
// Attribute helpers
// ============================================================================
fn class<M>(name: String): Attr<M> =
Class(name)
fn id<M>(name: String): Attr<M> =
Id(name)
fn style<M>(property: String, value: String): Attr<M> =
Style(property, value)
fn href<M>(url: String): Attr<M> =
Href(url)
fn src<M>(url: String): Attr<M> =
Src(url)
fn alt<M>(description: String): Attr<M> =
Alt(description)
fn inputType<M>(t: String): Attr<M> =
Type(t)
fn value<M>(v: String): Attr<M> =
Value(v)
fn placeholder<M>(p: String): Attr<M> =
Placeholder(p)
fn disabled<M>(d: Bool): Attr<M> =
Disabled(d)
fn checked<M>(c: Bool): Attr<M> =
Checked(c)
fn name<M>(n: String): Attr<M> =
Name(n)
fn onClick<M>(msg: M): Attr<M> =
OnClick(msg)
fn onInput<M>(h: fn(String): M): Attr<M> =
OnInput(h)
fn onSubmit<M>(msg: M): Attr<M> =
OnSubmit(msg)
fn onChange<M>(h: fn(String): M): Attr<M> =
OnChange(h)
fn onMouseEnter<M>(msg: M): Attr<M> =
OnMouseEnter(msg)
fn onMouseLeave<M>(msg: M): Attr<M> =
OnMouseLeave(msg)
fn onFocus<M>(msg: M): Attr<M> =
OnFocus(msg)
fn onBlur<M>(msg: M): Attr<M> =
OnBlur(msg)
fn onKeyDown<M>(h: fn(String): M): Attr<M> =
OnKeyDown(h)
fn onKeyUp<M>(h: fn(String): M): Attr<M> =
OnKeyUp(h)
fn data<M>(name: String, value: String): Attr<M> =
DataAttr(name, value)
// ============================================================================
// Utility functions
// ============================================================================
// Conditionally include an element
fn when<M>(condition: Bool, element: Html<M>): Html<M> =
if condition then element else Empty
// Conditionally apply attributes
fn attrIf<M>(condition: Bool, attr: Attr<M>): List<Attr<M>> =
if condition then [attr] else []

8
stdlib/lib.lux Normal file
View File

@@ -0,0 +1,8 @@
// Lux Standard Library
//
// This module re-exports the core standard library modules.
// Import with: import stdlib
// Re-export Html module
pub import html
pub import browser

8
stdlib/lux.toml Normal file
View File

@@ -0,0 +1,8 @@
[project]
name = "stdlib"
version = "0.1.0"
description = "Lux Standard Library - Core types and effects for browser development"
license = "MIT"
[dependencies]
# No dependencies - this is the core library