UNPKG

lightview

Version:

A reactive UI library with features of Bau, Juris, and HTMX plus safe LLM UI generation

1,153 lines (1,053 loc) 51.5 kB
<script src="/lightview-router.js?base=/index.html"></script> <style> /* Syntax Tabs */ .syntax-tabs { display: inline-flex; gap: 0.25rem; padding: 0.25rem; background: #f1f5f9; border-radius: 8px; border: 1px solid #e2e8f0; margin-bottom: 1rem; } [data-theme="dark"] .syntax-tabs { background: #1e293b; border-color: #334155; } .syntax-tab { padding: 0.5rem 1rem; font-size: 0.875rem; font-weight: 500; border: none; background: transparent; border-radius: 6px; cursor: pointer; color: #64748b; transition: all 0.2s; } .syntax-tab:hover { background: #ffffff; color: #1e293b; } [data-theme="dark"] .syntax-tab:hover { background: #334155; color: #f1f5f9; } .syntax-tab-active { background: #ffffff !important; color: #6366f1 !important; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); } [data-theme="dark"] .syntax-tab-active { background: #0f172a !important; color: #818cf8 !important; } </style> <script> globalThis.switchCDOMTab = (tabId) => { const tabs = ['jprx', 'cdomc', 'operators']; tabs.forEach(t => { const btn = document.getElementById(`tab-btn-${t}`); const pane = document.getElementById(`pane-${t}`); if (t === tabId) { btn.classList.add('syntax-tab-active'); pane.style.display = 'block'; } else { btn.classList.remove('syntax-tab-active'); pane.style.display = 'none'; } }); }; </script> <div class="docs-layout"> <aside class="docs-sidebar" src="/docs/cdom-nav.html"></aside> <main class="docs-content"> <h1>cDOM (Computational DOM)</h1> <p class="text-secondary" style="font-size: 1.125rem;"> A declarative, expression-based way to build reactive UIs. </p> <div class="experimental-notice" style="margin: 1.5rem 0; padding: 1.25rem; border-radius: var(--site-radius); background: var(--site-accent-light); border: 1px solid var(--site-warning); color: var(--site-text);"> <div style="display: flex; gap: 0.75rem; align-items: flex-start;"> <span style="font-size: 1.5rem;">⚠️</span> <div> <strong style="display: block; margin-bottom: 0.25rem; font-size: 1.1rem;">Experimental Feature</strong> <p style="margin: 0; font-size: 0.95rem; opacity: 0.9;"> cDOM and JPRX are currently in an <strong>experimental</strong> phase. The expression syntax, helper functions, and integration patterns are subject to change as we continue to evolve the library. Use with caution in production environments. </p> <p style="margin: 0.75rem 0 0 0; padding-top: 0.75rem; border-top: 1px solid var(--site-border); font-size: 0.95rem; opacity: 0.9;"> <strong>v2.5.0 Syntax Update:</strong> Starting with Lightview v2.5.0 and JPRX v1.4.0, expressions use a new wrapper syntax: <code style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=(expr)</code> for JPRX and <code style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">#(xpath)</code> for XPath. The legacy prefix-only syntax (e.g., <code style="background: var(--site-bg-secondary); padding: 0.125rem 0.375rem; border-radius: 3px;">=path</code>) will be <strong>fully deprecated after March 31, 2026</strong>. </p> </div> </div> </div> <!-- ===== OVERVIEW ===== --> <h2 id="overview">Overview</h2> <p> The <strong>Computational DOM (cDOM)</strong> is a way to describe user interfaces using reactive expressions that feel as natural as spreadsheet formulas. Instead of writing JavaScript logic to update your UI, you define the <em>relationships</em> between your data and your elements. </p> <p> cDOM was designed so that both developers and LLMs can generate safe user interfaces that humans can interact with across platforms. </p> <p> cDOM uses <strong>JPRX (JSON Pointer Reactive eXpressions)</strong> as its expression language. JPRX extends <a href="https://www.rfc-editor.org/rfc/rfc6901" target="_blank">JSON Pointer (RFC 6901)</a> with reactivity, relative paths, and helper functions. cDOM also supports <strong>standard XPath</strong> for powerful DOM navigation during element construction. Together with deep integration for <a href="https://json-schema.org/" target="_blank">JSON Schema</a> (Standard Draft 7+), cDOM provides industrial-strength data validation and automatic type coercion. </p> <!-- ===== SIMPLE EXAMPLE ===== --> <h2 id="simple-example">Simple Example</h2> <p>Here's a simple counter in cDOM. Notice how the UI is purely declarative — no JavaScript logic:</p> <div id="simple-counter-demo"> <pre><script> examplify(document.currentScript.nextElementSibling, { at: document.currentScript.parentElement, scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'], type: 'module', height: '200px', autoRun: true, controls: false }); </script><code>await import('/lightview-cdom.js'); const { parseJPRX, hydrate } = globalThis.LightviewCDOM; const { $ } = Lightview; const cdom = `{ "div": { "id": "Counter", "onmount": "=state({ count: 0 }, { name: 'local', schema: 'auto', scope: $this })", "children": [ { "h2": "#(../../@id)" }, // XPath: text node -> h2 -> div { "p": ["Count: ", "=(/local/count)"] }, { "button": { "onclick": "=(++/local/count)", "children": ["+"] } }, { "button": { "onclick": "=(--/local/count)", "children": ["-"] } } ] } }`; $('#example').content(hydrate(parseJPRX(cdom)));</code></pre> </div> <p> This defines a counter with: </p> <ul> <li><code>onmount</code> — A Lightview lifecycle hook where we initialize state.</li> <li><code>#../../@id</code> — An XPath expression that extracts the <code>id</code> from the grandparent element.</li> <li><code>=state(...)</code> — An initializer that creates local reactive state.</li> <li><code>scope: $this</code> — Explicitly attaches the state to the current element.</li> <li><code>=/local/count</code> — A path that reactively displays the count value.</li> </ul> <p style="font-size: 0.95rem; font-style: italic; color: var(--site-text-secondary);"> <strong>Note:</strong> In cDOM, expressions use a wrapper syntax for clarity: <code>#(xpath)</code> for <strong>XPath</strong> expressions that navigate and extract data from the DOM, and <code>=(expr)</code> for <strong>JPRX</strong> expressions that navigate or apply functions to reactive state. Legacy syntax without parentheses is still supported. </p> <p> The UI automatically updates whenever <code>count</code> changes — no manual DOM manipulation required. It also uses XPath to navigate the DOM structure during construction. </p> <!-- ===== ADVANTAGES ===== --> <h2 id="advantages">Advantages</h2> <div class="feature-grid" style="margin: 2rem 0;"> <div class="feature-card"> <h3 class="feature-title">🛡️ Enhanced Security</h3> <p> cDOM strictly avoids <code>eval()</code> and direct HTML injection. By using a custom high-performance parser and a registry of pre-defined helper functions, it provides a safe sandbox for dynamic content, making it highly resistant to XSS attacks. </p> </div> <div class="feature-card"> <h3 class="feature-title">🤖 LLM Friendly</h3> <p> Large Language Models excel at generating structured data and formulaic expressions. cDOM's declarative nature and concise syntax make it far easier for AI to generate correct, bug-free UI components compared to traditional JavaScript-heavy frameworks. </p> </div> </div> <!-- ===== XPATH NAVIGATION ===== --> <h2 id="xpath-navigation">Using XPath</h2> <p> cDOM allows elements to navigate and reference the DOM structure during construction using standard <strong>XPath</strong>. This is strictly a <strong>cDOM feature</strong> (not JPRX) used for structural navigation. </p> <p> XPath is incredibly useful for keeping your definitions <strong>DRY (Don't Repeat Yourself)</strong> by referencing existing attributes instead of repeating values. </p> <div id="xpath-demo"> <pre><script> examplify(document.currentScript.nextElementSibling, { at: document.currentScript.parentElement, scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'], type: 'module', height: '180px', autoRun: true, controls: false }); </script><code>await import('/lightview-cdom.js'); const { parseJPRX, hydrate } = globalThis.LightviewCDOM; const { $ } = Lightview; const cdom = `{ // The cdom below uses a compressed syntax. It does not require quotes // around properties or JPRX/XPath values and supports comments. // It is JSON-like, but not JSON. div: { id: "profile-container", class: "card", data-theme: "dark", children: [ { h3: "User Profile" }, { button: { id: "7", // XPath #(../@id) gets the "7" from this button's id // XPath #(../../@id) gets "profile-container" from the g-parent div children: ["Button ", #(../@id), " in section ", #(../../@id)] }} ] } }`; $('#example').content(hydrate(parseJPRX(cdom)));</code></pre> </div> <p> In the example above, the button's text is derived entirely from its own <code>id</code> and its parent's <code>id</code> using <code>#(../@id)</code> and <code>#(../../@id)</code>. </p> <p> For more details, see the <a href="/docs/cdom-xpath.html">Full XPath Documentation</a>. </p> <!-- ===== JPRX INTRODUCTION ===== --> <h2 id="JPRX">JPRX (JSON Pointer Reactive eXpressions)</h2> <p> <strong>JPRX</strong> is the expression language that powers cDOM. It extends <a href="https://www.rfc-editor.org/rfc/rfc6901" target="_blank">JSON Pointer (RFC 6901)</a> with reactivity, relative paths, and helper functions. </p> <h3 id="JPRX-delimiters">Expression Syntax</h3> <p> JPRX expressions use a <strong>wrapper syntax</strong> for unambiguous parsing. The recommended syntax is <code>=(expression)</code>: </p> <table class="api-table"> <thead> <tr> <th>Syntax</th> <th>Purpose</th> <th>Example</th> </tr> </thead> <tbody> <tr> <td><code>=(/path)</code></td> <td>Access a path in the global registry</td> <td><code>=(/users/0/name)</code></td> </tr> <tr> <td><code>=(function(...))</code></td> <td>Call a helper function</td> <td><code>=(sum(/items...price))</code></td> </tr> <tr> <td><code>#(xpath)</code></td> <td>XPath expression for DOM navigation</td> <td><code>#(../../@id)</code></td> </tr> </tbody> </table> <p> The wrapper syntax <code>=()</code> and <code>#()</code> makes expressions unambiguous and eliminates conflicts with literal strings. Legacy syntax without parentheses (e.g., <code>=/path</code>) is still supported for backward compatibility. </p> <h3 id="JPRX-literal-strings">Literal Strings</h3> <p> With the new wrapper syntax, you no longer need escape sequences. Any string that doesn't match the wrapper pattern is treated as a literal: </p> <div class="code-block" style="margin-bottom: 1rem;"> <pre><code>// JPRX expression (wrapped): { "p": "=(/user/name)" } // Resolves the path /user/name // Literal strings (no wrapper): { "p": "=E=mc²" } // Renders as "=E=mc²" { "p": "=42" } // Renders as "=42" { "p": "#ff0000" } // Renders as "#ff0000" (hex color)</code></pre> </div> <p> The wrapper syntax eliminates ambiguity: <code>=(expr)</code> is always an expression, while strings without the wrapper pattern are always literals. </p> <h3 id="JPRX-anatomy">Anatomy of a Path</h3> <p> Inside a JPRX expression, paths follow JSON Pointer with these extensions: </p> <table class="api-table"> <thead> <tr> <th>Path</th> <th>Description</th> <th>Origin</th> </tr> </thead> <tbody> <tr> <td><code>/users/0/name</code></td> <td>Absolute path (property access)</td> <td>JSON Pointer</td> </tr> <tr> <td><code>/items[2]</code></td> <td>Array index (bracket notation)</td> <td>JPRX extension</td> </tr> <tr> <td><code>./child</code></td> <td>Relative to current context</td> <td>JPRX extension</td> </tr> <tr> <td><code>../sibling</code></td> <td>Parent context access</td> <td>JPRX extension</td> </tr> <tr> <td><code>/items...price</code></td> <td>Extract <code>price</code> from all items (explosion)</td> <td>JPRX extension</td> </tr> </tbody> </table> <h4>Function Calls</h4> <p> Paths can contain function calls to transform data: </p> <div class="code-block"> <pre><code>=currency(sum(map(filter(/orders, eq(_/status, 'paid')), _/total)...))</code></pre> </div> <ul> <li><strong>/orders</strong>: Access the <code>orders</code> collection (JSON Pointer)</li> <li><strong>filter(..., eq(...))</strong>: Keep only "paid" orders</li> <li><strong>map(..., _/total)</strong>: Extract the <code>total</code> from each order</li> <li><strong>...</strong>: Explode the array into individual arguments</li> <li><strong>sum(...)</strong>: Add up all the totals</li> <li><strong>=currency(...)</strong>: Format as currency string</li> </ul> <h3 id="JPRX-placeholders">Placeholders</h3> <table class="api-table"> <thead> <tr> <th>Placeholder</th> <th>Description</th> <th>Example</th> </tr> </thead> <tbody> <tr> <td><code>_</code></td> <td>Current item during iteration</td> <td><code>=map(/items, _/name)</code></td> </tr> <tr> <td><code>$event</code></td> <td>Event object in handlers</td> <td><code>=set(=/selected, $event/target/value)</code></td> </tr> </tbody> </table> <h3 id="JPRX-explosion">Explosion & Mapping (<code>...</code>)</h3> <p> The <code>...</code> operator is a powerful JPRX extension used for mapping properties from arrays and spreading array elements as function arguments. </p> <h4>Mapping & Auto-Explosion: <code>path...property</code></h4> <p> When used between a path to an array and a property name, it acts as a shorthand for mapping. It extracts the specified property from every object in the array. <strong>Inside a function call, it automatically spreads (explodes) the resulting values as individual arguments.</strong> </p> <div class="code-block" style="margin-bottom: 1.5rem;"> <pre><code>// Correct: Maps 'price' and automatically spreads results as arguments to sum() =sum(/cart/items...price) // Mapping also works for direct display (returns an array) { p: ["Prices: ", =/cart/items...price] }</code></pre> </div> <h4>Argument Spreading: <code>path...</code></h4> <p> When an array path is followed by a trailing <code>...</code> at the end of the path expression (and no property name follows), the array elements are spread as individual arguments. Use this when you have a direct reference to an array. </p> <div class="code-block"> <pre><code>// Passes each number in the array as a separate argument =sum(/listOfNumbers...)</code></pre> </div> <div class="warning-notice" style="margin-top: 1.5rem; padding: 1rem; background: rgba(239, 68, 68, 0.1); border-left: 4px solid #ef4444; border-radius: 4px;"> <strong>Warning:</strong> Do not combine infix mapping with trailing dots. <code>/items...price...</code> is invalid because the trailing dots are interpreted as part of the property name (looking for a property literally named "price..."). </div> <!-- ===== COMPARISON TO EXCEL ===== --> <h2 id="comparison">Comparison to Excel</h2> <p> Think of your UI as a spreadsheet. In Excel, if Cell C1 has the formula <code>=A1+B1</code>, C1 updates automatically whenever A1 or B1 changes. </p> <p> cDOM brings this exact paradigm to the web. Every attribute and text node can be a "cell" that computes its value based on other "cells" (reactive signals). </p> <table class="api-table"> <thead> <tr> <th>Feature</th> <th>Excel</th> <th>cDOM / JPRX</th> </tr> </thead> <tbody> <tr> <td><strong>Reactive Unit</strong></td> <td>Cell</td> <td>Signal / State Proxy</td> </tr> <tr> <td><strong>Formulas</strong></td> <td><code>=SUM(A1:A10)</code></td> <td><code>=sum(/items...price)</code></td> </tr> <tr> <td><strong>Path Resolution</strong></td> <td>Cell References (A1, $B$2)</td> <td>JSON Pointer paths (./name, =/global)</td> </tr> <tr> <td><strong>Recalculation</strong></td> <td>Automatic on change</td> <td>Automatic on change</td> </tr> </tbody> </table> <!-- ===== LIGHTVIEW INTEGRATION ===== --> <h2 id="integration">Lightview Integration</h2> <p> cDOM integrates seamlessly with Lightview's existing DOM formats. You can use JPRX expressions in any of Lightview's hypermedia formats just by loading <code>lightview-cdom.js</code>: </p> <h3>vDOM (Virtual DOM)</h3> <p>JPRX expressions work directly in vDOM arrays or objects:</p> <div class="code-block"> <pre><code>// vDOM formal object { tag: "div", attributes: { class: "counter" }, children: [ { tag: "p", children: ["Count: ", "=/local/count"] }, { tag: "button", attributes: { onclick: "=++/local/count }, children: ["+"] } ] }</code></pre> </div> <h3>oDOM (Object DOM)</h3> <p>JPRX expressions work in oDOM objects:</p> <div class="code-block"> <pre><code>{ div: { class: "counter", children: [ { p: { children: ["Count: ", "=/local/count] } }, { button: { onclick: "=++/local/count", children: ["+"] } } ] } }</code></pre> </div> <h3>cDOM Shorthand with JPRX</h3> <p>cDOM's concise shorthand syntax. Note that cDOM does not require quoting attribute names or JPRX expressions like vDOM and oDOM do and you can include comments:</p> <div class="code-block"> <pre><code>{ div: { class: "counter", // This is a JPRX comment children: [ { p: ["Count: ", =/local/count] }, { button: { onclick: =++/local/count, children: ["+"] } } ] } }</code></pre> </div> <!-- ===== DOM PATCHES ===== --> <h2 id="dom-patches">DOM Patches & Decentralized Layouts</h2> <p> cDOM supports <strong>Decentralized Layouts</strong>, allowing components to "move themselves" to their rightful home in the DOM upon being created. This is especially powerful for LLM-driven streaming UIs. </p> <h3 id="helpers-move">=move(target, location?)</h3> <p> The <code>=move</code> helper (typically used in <code>onmount</code>) teleports the host element to a different part of the document. </p> <div class="code-block"> <pre><code>{ div: { id: "weather-widget", onmount: =move('#sidebar', 'afterbegin'), content: "Sunny, 75°F" } }</code></pre> </div> <h4>Identity & Patching</h4> <p> If the moving element has a unique <code>id</code> and an element with that same ID already exists at the destination, the existing element is <strong>replaced</strong>. This turns <code>=move</code> into an idempotent <strong>patch</strong> command — simply stream the new version of the component with the same ID, and it will update the UI automatically. </p> <h4>Placement Locations</h4> <table class="api-table"> <thead> <tr> <th>Location</th> <th>Result</th> </tr> </thead> <tbody> <tr> <td><code>inner</code> / <code>shadow</code></td> <td>Replaces all children of the target.</td> </tr> <tr> <td><code>afterbegin</code> / <code>prepend</code></td> <td>Inserts at the start of the target.</td> </tr> <tr> <td><code>beforeend</code> / <code>append</code></td> <td>Inserts at the end of the target (default).</td> </tr> <tr> <td><code>outer</code> / <code>replace</code></td> <td>Replaces the target node itself.</td> </tr> <tr> <td><code>beforebegin</code> / <code>afterend</code></td> <td>Inserts before or after the target node.</td> </tr> </tbody> </table> <h2 id="state-binding">State & Binding</h2> <p> cDOM does not use attribute directives for state it uses lifecycle events and helpers instead. </p> <h3 id="lifecycle-state">Lifecycle State</h3> <p> In Lightview, you initialize state within the <code>onmount</code> hook. The <code>=state</code> and <code>=signal</code> helpers allow you to create reactive data that is tied to the DOM structure. </p> <h4 id="scope-resolution">Scope & Name Resolution</h4> <p> When JPRX encounters a name (e.g., <code>=/profile/name</code>), it doesn't just look in a global registry. Instead, it performs an <strong>up-tree search</strong>: </p> <ol> <li>It starts at the element where the expression is defined.</li> <li>It looks for a registered signal or state with that name.</li> <li>If not found, it moves to the parent element and repeats the search.</li> <li>This continues all the way to the <code>document</code> root, finally checking the global registry if needed.</li> </ol> <h4>The <code>$this</code> Placeholder</h4> <p> The <code>$this</code> keyword is a special placeholder that represents the <strong>current element</strong>. When used in the <code>scope</code> option of <code>=state</code> or <code>=signal</code>, it instructs Lightview to register the name specifically at that element's level in the DOM. </p> <div class="code-block" style="margin-bottom: 1.5rem;"> <pre><code>// Scoping state to the current element creates a local namespace { "onmount": "=state({ count: 0 }, { name: 'local', scope: $this })" }</code></pre> </div> <p> This mechanism is what allows you to create <strong>reusable components</strong>. Each instance of a component can have its own "local" state because the search for <code>local</code> will stop at the first element it finds that has it registered—typically the root of the component instance. </p> <h4>Using a Registered Schema</h4> <div class="code-block" style="margin-bottom: 1rem;"> <pre><code>// 1. Register centrally in JS Lightview.registerSchema('User', { name: 'string', age: 'number' }); // 2. Use in cDOM { "onmount": "=state({}, { name: 'profile', schema: 'User', scope: $this })" }</code></pre> </div> <h4>Standard Schema Behaviors</h4> <p> When using the <code>schema</code> option, you can use these standard behaviors: </p> <ul style="margin-bottom: 2rem;"> <li><strong>"auto"</strong>: Infers a fixed schema from the initial value. Strict type checking (throws on mismatch).</li> <li><strong>"dynamic"</strong>: Like auto, but allows adding new properties to the state object.</li> <li><strong>"polymorphic"</strong>: The most powerful setting. It includes <strong>"dynamic"</strong> behavior and automatically <strong>coerces</strong> values to match the inferred type (e.g., "50" -> 50). </li> </ul> <h4>Example: Polymorphic Coercion</h4> <div class="code-block" style="margin-bottom: 2rem;"> <pre><code>{ div: { "onmount": "=state({ count: 0 }, { name: 'local', schema: 'polymorphic', scope: $this })", children: [ { p: ["Typing '10' into a bind will save it as the number 10."] } ] } }</code></pre> </div> <h3 id="bind-helper">Two-Way Binding ($bind)</h3> <p> Two-way data binding is achieved via the <code>=bind(path)</code> helper. </p> <div class="code-block"> <pre><code>{ input: { type: "text", value: "=bind(/profile/name)", placeholder: "Enter name" } } { input: { type: "checkbox", checked: "=bind(/settings/enabled)" } }</code></pre> </div> <h4 id="binding-transformations">Handling Transformations</h4> <p> Because <code>=bind</code> is strict (it only accepts direct paths), you cannot pass a computation like <code>=bind(upper(/name))</code>. To transform data during binding, you have two choices: </p> <ul> <li> <strong>Manual Transformation:</strong> Use an <code>oninput</code> handler: <code>{ oninput: "=set(/name, upper($event/target/value))" }</code> </li> <li> <strong>Schema Transformation:</strong> Define a <code>transform</code> in your schema: <div class="code-block" style="margin-top: 0.5rem;"> <pre><code>Lightview.registerSchema('Profile', { name: { type: 'string', transform: 'upper' } });</code></pre> </div> The state manager will automatically apply the <a href="/docs/api/state.html#transformations">transformation</a> during the write-back phase of <code>=bind</code>. </li> </ul> <!-- ===== SHOPPING CART EXAMPLE ===== --> <h2 id="shopping-cart">Shopping Cart Example</h2> <p>A more complete example showing reactive expressions with data transformations:</p> <div id="shopping-cart-example"> <pre><script> examplify(document.currentScript.nextElementSibling, { at: document.currentScript.parentElement, scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'], type: 'module', height: '250px', autoRun: true }); </script><code contenteditable="true">// Shopping Cart: Demonstrating $map and $currency helpers await import('/lightview-cdom.js'); const { parseJPRX, hydrate } = globalThis.LightviewCDOM; const { $ } = Lightview; const cdomString = `{ div: { "onmount": "=state({ cart: { items: [ { name: 'Apple', price: 1.00 }, { name: 'Orange', price: 2.00 } ] } }, 'store')", children: [ { h3: "Shopping Cart" }, { ul: { children: =(map(/store/cart/items, { li: { children: [_/name, " - ", currency(_/price)] } })) }}, { p: { style: "font-weight: bold; margin-top: 1rem;", children: ["Total: ", =(currency(sum(/store/cart/items...price)))] }} ] } }`; const hydrated = hydrate(parseJPRX(cdomString)); $('#example').content(hydrated); </code></pre></code></pre> </div> <!-- ===== INTERACTIVE EXAMPLE ===== --> <h2 id="interactive-example">Interactive Example</h2> <p> Choose a syntax to see how the same reactive counter can be defined using standard JSON, concise cDOMC, or operator syntax. </p> <div role="tablist" class="syntax-tabs"> <button id="tab-btn-jprx" class="syntax-tab syntax-tab-active" onclick="switchCDOMTab('jprx')">JPRX (Standard)</button> <button id="tab-btn-cdomc" class="syntax-tab" onclick="switchCDOMTab('cdomc')">cDOMC</button> <button id="tab-btn-operators" class="syntax-tab" onclick="switchCDOMTab('operators')">Operators</button> </div> <!-- JPRX Pane --> <div id="pane-jprx"> <pre><script> examplify(document.currentScript.nextElementSibling, { at: document.currentScript.parentElement, scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'], type: 'module', height: '250px', autoRun: true }); </script><code contenteditable="true">// JPRX: Standard JSON format (strict) await import('/lightview-cdom.js'); const { parseJPRX, hydrate } = globalThis.LightviewCDOM; const { $ } = Lightview; const cdomString = `{ "div": { onmount: "=signal(0, 'count')", "children": [ { "h3": ["Standard JPRX Counter"] }, { "p": { "children": ["Count: ", "=(/count)"] }}, { "div": { "children": [ { "button": { "onclick": "=(decrement(/count))", "children": ["-"] } }, { "button": { "onclick": "=(increment(/count))", "children": ["+"] } } ]}} ] } }`; const hydrated = hydrate(parseJPRX(cdomString)); $('#example').content(hydrated); </code></pre> </div> <!-- cDOMC Pane --> <div id="pane-cdomc" style="display: none;"> <pre><script> examplify(document.currentScript.nextElementSibling, { at: document.currentScript.parentElement, scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'], type: 'module', height: '250px' }); </script><code contenteditable="true">// cDOMC: Compressed format (unquoted keys, comments) await import('/lightview-cdom.js'); const { parseJPRX, hydrate } = globalThis.LightviewCDOM; const { $ } = ightview; const cdomString = `{ div: { onmount: =signal(0, 'count'), children: [ { h3: ["Compressed Counter"] }, { p: { children: ["Count: ", =(/count)] }}, { div: { children: [ { button: { onclick: =(decrement(/count)), children: ["-"] } }, { button: { onclick: =(increment(/count)), children: ["+"] } } ]}} ] } }`; const hydrated = hydrate(parseJPRX(cdomString)); $('#example').content(hydrated); globalThis.LightviewCDOM.activate(hydrated.domEl); </code></pre> </div> <!-- Operators Pane --> <div id="pane-operators" style="display: none;"> <pre><script> examplify(document.currentScript.nextElementSibling, { at: document.currentScript.parentElement, scripts: ['/lightview.js', '/lightview-x.js', '/lightview-cdom.js'], type: 'module', height: '250px' }); </script><code contenteditable="true">// Operators: Using =++/path and =--/path await import('/lightview-cdom.js'); const { parseJPRX, hydrate } = globalThis.LightviewCDOM; const { $ } = Lightview; const cdomString = `{ div: { onmount: =signal(0, 'count'), children: [ { h3: ["Operator Counter"] }, { p: { children: ["Count: ", =(/count)] }}, { div: { children: [ // Prefix operators: =(--/count) and =(++/count) { button: { onclick: =(--/count), children: ["-"] } }, { button: { onclick: =(++/count), children: ["+"] } } ]}} ] } }`; const hydrated = hydrate(parseJPRX(cdomString)); $('#example').content(hydrated); globalThis.LightviewCDOM.activate(hydrated.domEl); </code></pre> </div> <!-- ===== OPERATOR SYNTAX EXAMPLE ===== --> <h2 id="operator-syntax">Operator Syntax</h2> <p> JPRX supports <strong>prefix</strong> and <strong>postfix</strong> operator syntax as alternatives to function calls. This makes expressions more concise and familiar to JavaScript developers. </p> <h3>Equivalent Expressions</h3> <table class="api-table"> <thead> <tr> <th>Function Syntax</th> <th>Operator Syntax</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>=increment(/count)</code></td> <td><code>=++/count</code> or <code>=/count++</code></td> <td>Increment by 1</td> </tr> <tr> <td><code>=decrement(/count)</code></td> <td><code>=--/count</code> or <code>=/count--</code></td> <td>Decrement by 1</td> </tr> <tr> <td><code>=toggle(/enabled)</code></td> <td><code>=!!/enabled</code></td> <td>Toggle boolean (prefix only)</td> </tr> </tbody> </table> <!-- ===== EVENTS ===== --> <h2 id="events">Events and Interaction</h2> <p> cDOM handles user interactions gracefully, whether in standalone applications or LLM-driven environments. </p> <h3 id="events-manual">Manual Implementation</h3> <p> Use standard event attributes with JPRX expressions: </p> <div class="code-block"> <pre><code>{ button: { onclick: =++/count, children: ["Click Me"] } } { input: { oninput: =set(/name, $event/target/value) } }</code></pre> </div> <h3 id="events-llm">LLM-Generated Interaction</h3> <p> LLMs can generate event handlers to register interest in user actions or trigger UI updates: </p> <div class="code-block"> <pre><code>// 1. Notify LLM of an event { button: { onclick: =fetch('/api/notify', { method: 'POST', body: $event }), children: ["Notify"] } } // 2. Request a UI Patch from LLM { button: { onclick: =mount('/api/update', { method: 'POST', body: $event }), children: ["Update UI"] } }</code></pre> </div> <p> The <code>$event</code> placeholder gives the LLM full context of the interaction. When using <code>=mount</code>, the server should respond with cDOM, vDOM, or oDOM content (see the <a href="#helpers-network">Network section</a> for more details). </p> <p> By default, <code>=mount</code> will <strong>append</strong> the response to the <code>body</code> and then let it "rip itself out" and <strong>teleport</strong> to its final destination if the response contains a <code>=move</code> helper. This "Safe Landing" strategy ensures decentralized layouts work seamlessly without the patcher needing to know the exact destination. </p> <h3 id="events-lifecycle">The Interaction Lifecycle</h3> <div class="feature-grid" style="margin: 2rem 0;"> <div class="feature-card"> <h4 class="feature-title">🤖 LLM-Driven Flow</h4> <ol style="margin-top: 1rem; font-size: 0.9em; line-height: 1.6;"> <li><strong>LLM</strong> generates UI structure as JSON</li> <li><strong>Server</strong> serves the JSON to the client</li> <li><strong>Client</strong> renders cDOM and activates reactivity</li> <li><strong>User</strong> interacts (e.g., clicks a button)</li> <li><strong>Client</strong> sends interaction data to server</li> <li><strong>Server</strong> relays event to LLM</li> <li><strong>LLM</strong> sends UI patch back</li> <li><strong>Client</strong> merges update, UI refreshes</li> </ol> </div> <div class="feature-card"> <h4 class="feature-title">🏠 Standalone Flow</h4> <ol style="margin-top: 1rem; font-size: 0.9em; line-height: 1.6;"> <li><strong>cDOM</strong> defined in source code</li> <li><strong>Client</strong> activates UI on page load</li> <li><strong>User</strong> interacts (e.g., toggles a switch)</li> <li><strong>State</strong> updates immediately</li> <li><strong>UI</strong> recalculates reactively (0 latency)</li> <li><strong>Server</strong> only for persistence/APIs</li> </ol> </div> </div> <!-- ===== JAVASCRIPT API ===== --> <h2 id="js-api">JavaScript API</h2> <p>Interact with cDOM programmatically using the <code>LightviewCDOM</code> global object.</p> <h3 id="api-activate">activate(root)</h3> <p> Scans the DOM from <code>root</code> (defaults to <code>document.body</code>) and initializes all cDOM directives. </p> <div class="code-block"> <pre><code>LightviewCDOM.activate(); LightviewCDOM.activate(document.getElementById('myApp'));</code></pre> </div> <h3 id="api-hydrate">hydrate(object)</h3> <p> Converts <code>=/</code> prefixed strings into reactive computed signals. </p> <div class="code-block"> <pre><code>const config = { title: "Dashboard", total: "=/cart/items...price" }; const liveConfig = LightviewCDOM.hydrate(config);</code></pre> </div> <h3 id="api-parseJPRX">parseJPRX(string)</h3> <p> Parses cDOM's concise syntax (unquoted keys, JPRX expressions) into a JavaScript object. </p> <div class="code-block"> <pre><code>const obj = LightviewCDOM.parseJPRX(`{ div: { children: [=/name] } }`);</code></pre> </div> <h3 id="api-registerSchema">registerSchema(name, definition)</h3> <p> Registers a reusable schema for state validation and initialization. Supported behaviors include <code>"auto"</code> (strict/fixed), <code>"dynamic"</code> (strict/expandable), and <code>"polymorphic"</code> (coerce/expandable). </p> <div class="code-block"> <pre><code>Lightview.registerSchema('User', { name: 'string', age: 'number' }); // Usable in JPRX: =state({}, { name: 'profile', schema: 'User' })</code></pre> </div> <h3 id="api-registerHelper">registerHelper(name, fn, options?)</h3> <p> Registers a custom helper function for use in JPRX expressions. </p> <div class="code-block"> <pre><code>LightviewCDOM.registerHelper('double', (x) => x * 2); // Now usable: =double(/count)</code></pre> </div> <!-- ===== JPRX HELPERS ===== --> <h2 id="helpers">JPRX Helper Functions</h2> <p> cDOM includes a rich set of built-in helpers for common transformations. For security, only registered helpers are available — no access to <code>globalThis</code>. </p> <h3 id="helpers-math">Math</h3> <p>Basic arithmetic operations.</p> <div class="code-block"> <pre><code>+, add, -, sub, *, mul, /, div, round, ceil, floor, abs, mod, pow, sqrt</code></pre> </div> <h3 id="helpers-stats">Stats</h3> <p>Aggregate calculations.</p> <div class="code-block"> <pre><code>sum, avg, min, max, median, stdev, var</code></pre> </div> <h3 id="helpers-string">String</h3> <p>Text manipulation.</p> <div class="code-block"> <pre><code>upper, lower, trim, capitalize, titleCase, contains, startsWith, endsWith, replace, split, len, join, concat, default</code></pre> </div> <h3 id="helpers-array">Array</h3> <p>Collection processing.</p> <div class="code-block"> <pre><code>count, map, filter, find, unique, sort, reverse, first, last, slice, flatten, join, len, length</code></pre> </div> <h3 id="helpers-logic">Logic & Comparison</h3> <p>Boolean logic and comparisons.</p> <h4 class="text-xs font-bold opacity-60 uppercase mb-2">Named Operators</h4> <div class="code-block mb-4"> <pre><code>if, and, or, not, eq, neq, gt, lt, gte, lte, between, in</code></pre> </div> <h4 class="text-xs font-bold opacity-60 uppercase mb-2">Aliases</h4> <div class="code-block mb-4"> <pre><code>&&, ||, !, ==, ===, !=, >, <, >=, <=</code></pre> </div> <p class="text-sm italic opacity-80" style="margin-top: 1rem;"> Example: <code>=if(gt(=/count, 10), 'Large', 'Small')</code> </p> <h3 id="helpers-conditional">Conditional Aggregates</h3> <p>Statistical functions with predicates.</p> <div class="code-block"> <pre><code>sumIf, countIf, avgIf</code></pre> </div> <h3 id="helpers-formatting">Formatting</h3> <p>Display formatting.</p> <div class="code-block"> <pre><code>number, currency, percent, thousands</code></pre> </div> <p class="text-xs italic opacity-70" style="margin-top: 0.5rem;"> <strong>Explosion Operator:</strong> In JPRX, <code>...</code> is placed at the end of a path (e.g., <code>=/items...price</code>) to expand arrays into arguments. </p> <h3 id="helpers-datetime">DateTime</h3> <p>Date operations.</p> <div class="code-block"> <pre><code>now, today, date, formatDate, year, month, day, weekday, addDays, dateDiff</code></pre> </div> <h3 id="helpers-lookup">Lookup</h3> <p>Data retrieval from indexed structures.</p> <div class="code-block"> <pre><code>lookup, vlookup, index, match</code></pre> </div> <h3 id="helpers-mutation">State & Lifecycle</h3> <p>Initialize and modify reactive state.</p> <div class="code-block"> <pre><code>state, signal, bind, set, increment (++), decrement (--), toggle (!!), push, pop, assign, clear</code></pre> </div> <h3 id="helpers-network">Network</h3> <p>HTTP requests.</p> <div class="code-block" style="margin-bottom: 2rem;"> <pre><code>fetch(url, options?) <span id="helpers-mount"></span>mount(url, options?)</span></code></pre> </div> <h3 id="helpers-dom">DOM & XPath</h3> <p>Structural navigation and manipulation. For fetching and mounting remote content, see the <a href="#helpers-network">mount()</a> helper in the Network section. </p> <div class="code-block"> <pre><code>move(selector, location?), xpath(expression)</code></pre> </div> <p style="margin-top: 1rem;"> While <code>move</code> is for local placement, <code>mount</code> is used for remote Hypermedia updates. It fetches content (cDOM, vDOM, or oDOM) and injects it into the DOM. If the content contains a <code>=move</code> helper, it will automatically relocate itself upon mounting. </p> <table class="api-table"> <thead> <tr> <th>Option</th> <th>Default</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td><code>method</code></td> <td><code>"GET"</code></td> <td>HTTP method (GET, POST, etc.)</td> </tr> <tr> <td><code>body</code></td