JSPM

  • Created
  • Published
  • Downloads 88
  • Score
    100M100P100Q83978F
  • License ISC

Type-safe, zero-dependency HTML builder for TypeScript with built-in XSS protection and first-class HTMX support. Build robust, maintainable web interfaces using functional composition.

Package Exports

  • lambda.html
  • lambda.html/dist/src/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (lambda.html) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

Lambda.html

A type-safe, zero-dependency HTML builder for TypeScript with built-in XSS protection and first-class HTMX support.

npm version License: ISC TypeScript Zero Dependencies

const page = Div([
  H1("Welcome").setClass("text-3xl font-bold"),
  Button("Load More")
    .setHtmx(hx("/api/items", {
      trigger: "click",           // ← IDE suggests: "click" | "load" | "revealed" | ...
      swap: "beforeend",          // ← IDE suggests: "innerHTML" | "outerHTML" | ...
      target: closest("ul"),      // ← Type-safe selector helper
    }))
]).setClass("container mx-auto p-8");

Why Lambda.html?

🎯 IDE-Powered Development

Lambda.html's type system provides intelligent autocomplete for everythingβ€”HTML attributes, HTMX configurations, CSS classes, and more. Your IDE becomes your documentation.

// Your IDE suggests all valid trigger options as you type:
Button("Save").setHtmx(hx("/api/save", {
  trigger: "cl"  // IDE shows: click | change | load | revealed | intersect | ...
  //        ↑ Autocomplete appears here!
}))

// Complex triggers with modifiers? Also fully typed:
Input().setHtmx(hx("/api/search", {
  trigger: "keyup changed delay:300ms"  // ← Valid typed trigger
  //              ↑ IDE validates modifier syntax
}))

πŸ”’ Compile-Time Safety

Catch errors before they reach production:

// βœ… Compiles - valid swap strategy
Div().setHtmx(hx("/api", { swap: "innerHTML" }))

// ❌ Type Error - "inner" is not a valid swap strategy
Div().setHtmx(hx("/api", { swap: "inner" }))
//                              ~~~~~~~ 
// Type '"inner"' is not assignable to type 'HxSwap'

// βœ… Compiles - valid input type
Input().setType("email").setPattern("[a-z]+@[a-z]+\\.[a-z]+")

// ❌ Type Error - setPattern doesn't exist on Div
Div().setPattern("[a-z]+")  
//    ~~~~~~~~~~ Property 'setPattern' does not exist on type 'Tag'

πŸ›‘οΈ Built-in XSS Protection

All content is automatically escapedβ€”no configuration needed:

const userInput = '<script>alert("xss")</script>';
render(Div(userInput));
// Output: <div>&lt;script&gt;alert("xss")&lt;/script&gt;</div>

⚑ Zero Dependencies, Tiny Footprint

Pure TypeScript. No runtime overhead. Perfect for server-side rendering.


Installation

npm install lambda.html

Quick Start

import { Div, H1, P, Button, render, hx } from 'lambda.html';

const page = Div([
  H1("Welcome to Lambda.html")
    .setClass("text-3xl font-bold"),
  P("Build type-safe HTML with confidence.")
    .setClass("text-gray-600 mt-2"),
  Button("Get Started")
    .setClass("bg-blue-500 text-white px-4 py-2 rounded mt-4")
    .setHtmx(hx("/api/start", { method: "post" }))
]).setClass("container mx-auto p-8");

console.log(render(page));

Output:

<div class="container mx-auto p-8">
  <h1 class="text-3xl font-bold">Welcome to Lambda.html</h1>
  <p class="text-gray-600 mt-2">Build type-safe HTML with confidence.</p>
  <button class="bg-blue-500 text-white px-4 py-2 rounded mt-4" 
          hx-post="/api/start">Get Started</button>
</div>

Table of Contents


IDE Autocomplete in Action

Lambda.html transforms your IDE into a powerful documentation tool. Here's what you get:

HTMX Triggers

// As you type, your IDE suggests all valid triggers:
.setHtmx(hx("/api", { 
  trigger: "|"  // Cursor here shows:
}))
// Suggestions:
//   click      - Standard click event
//   change     - Input change event  
//   submit     - Form submission
//   load       - Page load
//   revealed   - Element scrolled into view
//   intersect  - Intersection observer
//   keyup      - Key release
//   mouseenter - Mouse enters element
//   every 1s   - Polling every second
//   sse:event  - Server-sent event
//   ...and 20+ more!

HTMX Swap Strategies

.setHtmx(hx("/api", { 
  swap: "|"  // Cursor here shows:
}))
// Suggestions:
//   innerHTML   - Replace inner content (default)
//   outerHTML   - Replace entire element
//   beforebegin - Insert before element
//   afterbegin  - Insert at start of children
//   beforeend   - Insert at end of children
//   afterend    - Insert after element
//   delete      - Remove element
//   none        - No swap
//
// With modifiers:
//   innerHTML scroll:top
//   outerHTML transition:true
//   beforeend showπŸͺŸtop

HTMX Sync Strategies

.setHtmx(hx("/api", { 
  sync: "|"  // Cursor here shows:
}))
// Suggestions:
//   drop        - Drop new request if one in flight
//   abort       - Abort current request
//   replace     - Same as abort
//   queue       - Queue requests
//   queue first - Queue, process first only
//   queue last  - Queue, process last only
//   queue all   - Queue all requests

Input Types & Attributes

Input()
  .setType("|")  // IDE suggests: text | email | password | number | tel | url | ...
  .setAutocomplete("|")  // IDE suggests: on | off | email | username | current-password | ...

Element-Specific Methods

// Only Th and Td have colspan/rowspan:
Th("Header").setColspan(2).setScope("col")
//           ~~~~~~~~~~~ βœ“ Available on ThTag
//                       ~~~~~~~~~ βœ“ Available on ThTag

Div("Content").setColspan(2)
//             ~~~~~~~~~~ βœ— Error: Property 'setColspan' does not exist

// Only Form has action/method:
Form().setAction("/submit").setMethod("post")
//     ~~~~~~~~~ βœ“ Available    ~~~~~~~~~ βœ“ Available

// Only Input has min/max/step:
Input().setType("number").setMin(0).setMax(100).setStep(5)
//                        ~~~~~~ βœ“  ~~~~~~ βœ“   ~~~~~~~ βœ“

Type-Safe HTMX

Lambda.html provides complete HTMX 2.0 support with full type safety.

Basic Requests

import { hx } from 'lambda.html';

// GET request (default)
Button("Load").setHtmx(hx("/api/items"))

// POST request
Button("Submit").setHtmx(hx("/api/submit", { method: "post" }))

// PUT request
Button("Update").setHtmx(hx("/api/update/123", { method: "put" }))

// PATCH request (new in HTMX 2.0)
Button("Patch").setHtmx(hx("/api/resource", { method: "patch" }))

// DELETE request
Button("Delete").setHtmx(hx("/api/delete/123", { method: "delete" }))

Type-Safe Target Selectors

import { id, clss, closest, find, next, previous } from 'lambda.html';

// ID selector β†’ "#content"
Button("Load").setHtmx(hx("/api", { target: id("content") }))

// Class selector β†’ ".items"
Button("Update").setHtmx(hx("/api", { target: clss("items") }))

// Closest ancestor β†’ "closest tr"
Button("Delete Row").setHtmx(hx("/api/delete", { 
  method: "delete",
  target: closest("tr") 
}))

// Find descendant β†’ "find .content"
Div().setHtmx(hx("/api", { target: find(".content") }))

// Next sibling β†’ "next div"
Button("Next").setHtmx(hx("/api", { target: next("div") }))

// Previous sibling β†’ "previous li"  
Button("Prev").setHtmx(hx("/api", { target: previous("li") }))

Triggers with Modifiers

// Debounced search input
Input()
  .setType("search")
  .setName("q")
  .setHtmx(hx("/api/search", {
    trigger: "keyup changed delay:300ms",  // ← Fully typed!
    target: "#search-results"
  }))

// Throttled scroll
Div().setHtmx(hx("/api/more", {
  trigger: "scroll throttle:500ms",
  swap: "beforeend"
}))

// Load on reveal (lazy loading)
Div().setHtmx(hx("/api/content", { trigger: "revealed" }))

// Polling every 30 seconds
Div().setHtmx(hx("/api/notifications", { trigger: "every 30s" }))

// Multiple triggers
Button("Action").setHtmx(hx("/api/action", {
  trigger: "click, keyup[key=='Enter']"
}))

Swap Strategies with Modifiers

// Basic swaps
Div().setHtmx(hx("/api", { swap: "innerHTML" }))    // Replace inner content
Div().setHtmx(hx("/api", { swap: "outerHTML" }))    // Replace entire element
Div().setHtmx(hx("/api", { swap: "beforeend" }))    // Append to children

// With scroll modifier
Div().setHtmx(hx("/api", { swap: "innerHTML scroll:top" }))

// With show modifier (scroll into view)
Div().setHtmx(hx("/api", { swap: "innerHTML showπŸͺŸtop" }))

// With transition
Div().setHtmx(hx("/api", { swap: "outerHTML transition:true" }))

// With timing
Div().setHtmx(hx("/api", { swap: "innerHTML swap:500ms settle:100ms" }))

HTMX 2.0 Features

// Prompt dialog before request
Button("Rename").setHtmx(hx("/api/rename", {
  method: "post",
  prompt: "Enter new name:"
}))

// Disable elements during request
Button("Submit").setHtmx(hx("/api/submit", {
  method: "post",
  disabledElt: "this"  // or "#submit-btn" or "closest form"
}))

// Out-of-band swaps
Div().setHtmx(hx("/api/data", { swapOob: true }))
Div().setHtmx(hx("/api/data", { swapOob: "true:#sidebar" }))

// Control attribute inheritance
Div().setHtmx(hx("/api", { disinherit: "*" }))           // Disable all
Div().setHtmx(hx("/api", { inherit: "hx-target hx-swap" })) // Force specific

// Prevent history snapshot
Div().setHtmx(hx("/api/modal", { history: false }))

// Preserve element during swap (e.g., video player)
Video().setId("player").setHtmx(hx("/api/page", { preserve: true }))

// Request configuration
Div().setHtmx(hx("/api/slow", { request: "timeout:5000" }))

// Boost links/forms to use AJAX
A("Page").setHref("/page").setHtmx(hx("/page", { boost: true }))

// Sync strategies
Button("Save").setHtmx(hx("/api/save", { 
  method: "post",
  sync: "abort"        // Abort previous request
}))
Input().setHtmx(hx("/api/search", { 
  sync: "queue last"   // Queue, process last
}))

Complete Form Example

Form([
  Fieldset([
    Legend("User Registration"),
    
    Label("Email").setFor("email"),
    Input()
      .setType("email")
      .setId("email")
      .setName("email")
      .setPlaceholder("you@example.com")
      .setAutocomplete("email")
      .setToggles(["required"]),
    
    Label("Password").setFor("password"),
    Input()
      .setType("password")
      .setId("password")
      .setName("password")
      .setMinlength(8)
      .setAutocomplete("new-password")
      .setToggles(["required"]),
    
    Button("Register")
      .setType("submit")
      .setClass("bg-blue-500 text-white px-4 py-2 rounded")
  ]).setClass("space-y-4")
])
  .setHtmx(hx("/api/register", {
    method: "post",
    swap: "outerHTML",
    indicator: "#loading",
    disabledElt: "find button"
  }))

XSS Protection

Lambda.html automatically escapes all text content and attributes. No configuration needed.

Automatic Text Escaping

// User input is automatically escaped
const userInput = '<script>alert("xss")</script>';
const element = Div(userInput);

console.log(render(element));
// Output: <div>&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;</div>

Automatic Attribute Escaping

// Malicious class names are escaped
const malicious = '"><script>alert(1)</script>';
const element = Div().setClass(malicious);

console.log(render(element));
// Output: <div class="&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;"></div>

Raw Content for Scripts & Styles

Script and Style elements are intentionally not escaped (they contain code, not user content):

// Script content preserved for valid JavaScript
Script(`
  if (count < 10 && count > 0) {
    console.log('valid');
  }
`)
// Output: <script>if (count < 10 && count > 0) { console.log('valid'); }</script>

// Style content preserved for valid CSS
Style(`
  .card > .title { content: "a & b"; }
`)
// Output: <style>.card > .title { content: "a & b"; }</style>

Safe Dynamic Content

// Building HTML from user data is always safe
function UserCard(user: { name: string; bio: string }): View {
  return Div([
    H2(user.name),      // ← Escaped automatically
    P(user.bio),        // ← Escaped automatically
  ]).setClass("user-card");
}

// Even malicious data is safely rendered
const user = {
  name: '<script>steal(cookies)</script>',
  bio: '"><img src=x onerror=alert(1)>'
};

render(UserCard(user));
// All content properly escaped - XSS prevented!

HTML Elements

Lambda.html provides 60+ HTML elements with typed attribute methods.

Element Chaining

All elements support fluent method chaining:

const card = Div("Content")
  .setId("my-card")
  .setClass("card shadow-lg")
  .addClass("hover:shadow-xl")
  .setStyle("max-width: 400px")
  .addAttribute("data-testid", "card-component")
  .setHtmx(hx("/api/card", { trigger: "click" }));

Nested Elements

// Single child
Div(P("Paragraph inside div"))

// Multiple children as array
Div([
  H1("Title"),
  P("First paragraph"),
  P("Second paragraph")
])

// Mixed content
Div([
  "Text node",
  Strong("Bold text"),
  " more text"
])

Form Elements

// Text input with validation
Input()
  .setType("email")
  .setName("email")
  .setPlaceholder("Enter your email")
  .setPattern("[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$")
  .setAutocomplete("email")
  .setToggles(["required"])

// Number input with constraints
Input()
  .setType("number")
  .setName("quantity")
  .setMin(1)
  .setMax(100)
  .setStep(5)

// Textarea
Textarea()
  .setName("message")
  .setPlaceholder("Enter your message")
  .setRows(5)
  .setMaxlength(500)

// Select with optgroups
Select([
  Option("Select a country...").setValue(""),
  Optgroup([
    Option("United States").setValue("us"),
    Option("Canada").setValue("ca"),
  ]).setLabel("North America"),
  Optgroup([
    Option("United Kingdom").setValue("uk"),
    Option("Germany").setValue("de"),
  ]).setLabel("Europe"),
]).setName("country").setToggles(["required"])

Table Elements

Table([
  Caption("Monthly Sales Report"),
  Thead(
    Tr([
      Th("Product").setScope("col"),
      Th("Q1").setScope("col"),
      Th("Q2").setScope("col"),
      Th("Total").setScope("col").setColspan(2),
    ])
  ),
  Tbody([
    Tr([
      Th("Widget A").setScope("row"),
      Td("$1,000"),
      Td("$1,500"),
      Td("$2,500").setColspan(2),
    ]),
  ]),
  Tfoot(
    Tr([
      Th("Total").setScope("row"),
      Td("$3,000").setColspan(3),
    ])
  ),
]).setClass("w-full border-collapse")

Media Elements

// Responsive image with lazy loading
Img()
  .setSrc("hero.jpg")
  .setAlt("Hero image")
  .setSrcset("hero-400.jpg 400w, hero-800.jpg 800w")
  .setSizes("(max-width: 600px) 400px, 800px")
  .setLoading("lazy")
  .setDecoding("async")

// Video with multiple sources
Video([
  Source().setSrc("video.webm").setType("video/webm"),
  Source().setSrc("video.mp4").setType("video/mp4"),
  Track()
    .setSrc("captions.vtt")
    .setKind("subtitles")
    .setSrclang("en")
    .setLabel("English")
    .setDefault(),
  "Your browser does not support video."
])
  .setControls()
  .setPoster("poster.jpg")
  .setPreload("metadata")

// Picture element for art direction
Picture([
  Source()
    .setSrcset("hero-mobile.jpg")
    .setMedia("(max-width: 600px)"),
  Source()
    .setSrcset("hero-desktop.jpg")
    .setMedia("(min-width: 601px)"),
  Img().setSrc("hero-fallback.jpg").setAlt("Hero"),
])

Interactive Elements

// Details/Summary (accordion)
Details([
  Summary("Click to expand"),
  P("Hidden content revealed when opened."),
]).setOpen()

// Dialog (modal)
Dialog([
  H2("Confirm Action"),
  P("Are you sure you want to proceed?"),
  Button("Cancel").addAttribute("onclick", "this.closest('dialog').close()"),
  Button("Confirm").setClass("bg-blue-500 text-white"),
]).setId("confirm-modal")

// Progress and Meter
Progress().setValue(70).setMax(100)
Meter().setValue(0.7).setMin(0).setMax(1).setLow(0.3).setHigh(0.8).setOptimum(0.5)

Control Flow

Lambda.html provides functional control flow for conditional and iterative rendering.

IfThen / IfThenElse

import { IfThen, IfThenElse } from 'lambda.html';

// Conditional rendering
function UserBadge(user: { isAdmin: boolean; isPremium: boolean }): View {
  return Div([
    IfThen(user.isAdmin, () => 
      Span("Admin").setClass("badge badge-red")
    ),
    IfThen(user.isPremium, () => 
      Span("Premium").setClass("badge badge-gold")
    ),
  ]);
}

// If-else rendering
function LoginStatus(user: User | null): View {
  return IfThenElse(
    user !== null,
    () => Span(`Welcome, ${user!.name}`),
    () => A("Login").setHref("/login")
  );
}

SwitchCase

import { SwitchCase } from 'lambda.html';

type Status = 'pending' | 'approved' | 'rejected';

function StatusBadge(status: Status): View {
  return SwitchCase([
    { condition: status === 'pending',  component: () => Span("⏳ Pending").setClass("text-yellow-600") },
    { condition: status === 'approved', component: () => Span("βœ… Approved").setClass("text-green-600") },
    { condition: status === 'rejected', component: () => Span("❌ Rejected").setClass("text-red-600") },
  ], () => Span("Unknown").setClass("text-gray-600"));
}

ForEach Variants

import { ForEach, ForEach1, ForEach2, ForEach3, Repeat } from 'lambda.html';

const items = ["Apple", "Banana", "Cherry"];

// Basic iteration
Ul(ForEach(items, item => Li(item)))

// With index
Ol(ForEach1(items, (item, index) => 
  Li(`${index + 1}. ${item}`)
))

// Range iteration (0 to n-1)
Div(ForEach2(5, i => 
  Span(`Item ${i}`).setClass("inline-block p-2")
))

// Range iteration (start to end-1)
Div(ForEach3(1, 6, i => 
  Button(`Page ${i}`).setClass("px-3 py-1")
))

// Repeat n times
Div(Repeat(5, () => Span("⭐")))

Composable Components

Build reusable, type-safe components as pure functions.

Button Component

interface ButtonProps {
  text: string;
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  htmx?: HTMX;
}

function StyledButton(props: ButtonProps): View {
  const variants = {
    primary: 'bg-blue-500 hover:bg-blue-600 text-white',
    secondary: 'bg-gray-200 hover:bg-gray-300 text-gray-800',
    danger: 'bg-red-500 hover:bg-red-600 text-white',
  };
  
  const sizes = {
    sm: 'px-2 py-1 text-sm',
    md: 'px-4 py-2',
    lg: 'px-6 py-3 text-lg',
  };
  
  const button = Button(props.text)
    .setClass(`rounded font-medium transition-colors
      ${variants[props.variant ?? 'primary']}
      ${sizes[props.size ?? 'md']}
      ${props.disabled ? 'opacity-50 cursor-not-allowed' : ''}
    `.replace(/\s+/g, ' ').trim());
  
  if (props.disabled) button.setDisabled();
  if (props.htmx) button.setHtmx(props.htmx);
  
  return button;
}

// Usage
StyledButton({ text: "Save", variant: "primary", htmx: hx("/api/save", { method: "post" }) })
StyledButton({ text: "Cancel", variant: "secondary" })
StyledButton({ text: "Delete", variant: "danger", disabled: true })

Card Component

interface CardProps {
  title: string;
  content: View;
  footer?: View;
  image?: string;
}

function Card(props: CardProps): View {
  return Div([
    IfThen(!!props.image, () =>
      Img()
        .setSrc(props.image!)
        .setAlt(props.title)
        .setClass("w-full h-48 object-cover")
        .setLoading("lazy")
    ),
    Div([
      H3(props.title).setClass("text-xl font-semibold mb-2"),
      Div(props.content).setClass("text-gray-600"),
    ]).setClass("p-4"),
    IfThen(!!props.footer, () =>
      Div(props.footer!).setClass("px-4 py-3 bg-gray-50 border-t")
    ),
  ]).setClass("bg-white rounded-lg shadow-md overflow-hidden");
}

Data Table Component

interface Column<T> {
  key: keyof T;
  header: string;
  render?: (value: T[keyof T], row: T) => View;
}

function DataTable<T extends Record<string, any>>(
  columns: Column<T>[],
  data: T[],
  options?: { striped?: boolean; hoverable?: boolean }
): View {
  return Table([
    Thead(
      Tr(ForEach(columns, col =>
        Th(col.header).setScope("col").setClass("px-4 py-3 text-left font-semibold")
      )).setClass("bg-gray-100")
    ),
    Tbody(
      ForEach1(data, (row, index) =>
        Tr(ForEach(columns, col => {
          const value = row[col.key];
          const content = col.render ? col.render(value, row) : String(value);
          return Td(content).setClass("px-4 py-3");
        }))
          .addClass(options?.hoverable ? 'hover:bg-gray-50' : '')
          .addClass(options?.striped && index % 2 ? 'bg-gray-50' : '')
      )
    ),
  ]).setClass("w-full border-collapse");
}

// Usage
interface User { id: number; name: string; email: string; role: string; }

DataTable<User>(
  [
    { key: "name", header: "Name" },
    { key: "email", header: "Email" },
    { 
      key: "role", 
      header: "Role",
      render: (value) => Span(String(value))
        .setClass(value === "Admin" ? "text-red-600 font-bold" : "text-gray-600")
    },
  ],
  users,
  { striped: true, hoverable: true }
)

Infinite Scroll Component

function InfiniteScrollList(items: Item[], page: number): View {
  return Div([
    Ul(
      ForEach(items, item => 
        Li([
          H3(item.title).setClass("font-medium"),
          P(item.description).setClass("text-gray-600 text-sm"),
        ]).setClass("p-4 border-b")
      )
    ).setId("item-list"),
    
    // Load more trigger - fires when scrolled into view
    Div("Loading...")
      .setId("load-more")
      .setClass("p-4 text-center text-gray-500")
      .setHtmx(hx(`/api/items?page=${page + 1}`, {
        trigger: "revealed",
        swap: "outerHTML",
      }))
  ]);
}

API Reference

Element Functions

Category Elements
Structure Div, Main, Header, Footer, Section, Article, Nav, Aside, Figure, Figcaption, Address, Hgroup, Search
Headings H1, H2, H3, H4, H5, H6
Text P, Span, Strong, Em, B, I, U, S, Mark, Small, Sub, Sup, Blockquote, Pre, Code, Abbr, Cite, Q, Dfn, Kbd, Samp, Var
Breaks Br, Hr, Wbr
Lists Ul, Ol, Li, Dl, Dt, Dd, Menu
Tables Table, Thead, Tbody, Tfoot, Tr, Th, Td, Caption, Colgroup, Col
Forms Form, Input, Textarea, Button, Label, Select, Option, Optgroup, Datalist, Fieldset, Legend, Output
Interactive Details, Summary, Dialog
Media Img, Picture, Source, Video, Audio, Track, Canvas
SVG Svg, Path, Circle, Rect, Line, Polygon, Polyline, Ellipse, G, Defs, Use, Text, Tspan
Embedded Iframe, ObjectEl, Embed, MapEl, Area
Links A
Document HTML, Head, Body, Title, Meta, Link, Style, Script, Base, Noscript, Template
Data Time, Data, Progress, Meter, Slot
Utility El (custom), Empty, Overlay

Common Methods (All Tags)

Method Description
.setId(id) Set element ID
.setClass(classes) Set CSS classes
.addClass(class) Append CSS class
.setStyle(css) Set inline styles
.addAttribute(key, value) Add custom attribute
.setHtmx(hx(...)) Add HTMX behavior
.setToggles([...]) Add boolean attributes (required, disabled, etc.)

Control Flow

Function Description
IfThen(condition, thenFn) Render if condition is true
IfThenElse(condition, thenFn, elseFn) Conditional rendering
SwitchCase(cases, defaultFn) Multi-branch conditional
ForEach(items, renderFn) Iterate over items
ForEach1(items, renderFn) Iterate with index
ForEach2(n, renderFn) Range 0 to n-1
ForEach3(start, end, renderFn) Range start to end-1
Repeat(n, renderFn) Repeat n times

HTMX Helper

hx(endpoint: string, options?: {
  // HTTP Method
  method?: 'get' | 'post' | 'put' | 'patch' | 'delete';
  
  // Targeting
  target?: string;              // CSS selector or extended selector
  swap?: HxSwap;                // Swap strategy
  swapOob?: boolean | string;   // Out-of-band swap
  select?: string;              // Select from response
  selectOob?: string;           // Select OOB content
  
  // Triggering  
  trigger?: HxTrigger;          // Event trigger
  
  // URL
  pushUrl?: boolean | string;   // Push to history
  replaceUrl?: boolean | string; // Replace in history
  
  // Data
  vals?: object | string;       // Additional values
  headers?: object;             // Custom headers
  include?: string;             // Include elements
  params?: string;              // Filter params
  encoding?: 'multipart/form-data';
  
  // Validation
  validate?: boolean;
  confirm?: string;             // Confirmation dialog
  prompt?: string;              // Prompt dialog
  
  // Loading
  indicator?: string;           // Loading indicator
  disabledElt?: string;         // Disable during request
  
  // Sync
  sync?: HxSync;                // Request synchronization
  
  // Other
  ext?: string;                 // Extensions
  disinherit?: string;          // Disable inheritance
  inherit?: string;             // Force inheritance
  history?: boolean;            // History snapshot
  historyElt?: boolean;
  preserve?: boolean;           // Preserve element
  request?: string;             // Request config
  boost?: boolean;              // Boost links/forms
  disable?: boolean;            // Disable htmx
})

Selector Helpers

import { id, clss, closest, find, next, previous } from 'lambda.html';

id("content")        // β†’ "#content"
clss("items")        // β†’ ".items"
closest("tr")        // β†’ "closest tr"
find(".content")     // β†’ "find .content"
next("div")          // β†’ "next div"
next()               // β†’ "next"
previous("li")       // β†’ "previous li"
previous()           // β†’ "previous"

License

ISC Β© Toni K. Turk