@zeix/ui-element
Version:
UIElement - minimal reactive framework based on Web Components
614 lines (567 loc) • 37.8 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Data Flow – UIElement Docs</title>
<meta name="description" content="Passing state, events, context" />
<base href="./data-flow.html" />
<link rel="stylesheet" href="assets/main.css" />
<script type="module" src="assets/main.js"></script>
</head>
<body>
<header class="content-grid">
<h1 class="content">
UIElement Docs <small>Version 0.12.2</small>
</h1>
<nav class="breakout">
<ol>
<li>
<a href="index.html">
<span class="icon">📖</span>
<strong>Introduction</strong>
<small>Overview and key benefits of UIElement</small>
</a>
</li>
<li>
<a href="getting-started.html">
<span class="icon">🚀</span>
<strong>Getting Started</strong>
<small>Installation, setup, and first steps</small>
</a>
</li>
<li>
<a href="building-components.html">
<span class="icon">🏗️</span>
<strong>Building Components</strong>
<small>Anatomy, lifecycle, signals, effects</small>
</a>
</li>
<li>
<a href="styling-components.html">
<span class="icon">🎨</span>
<strong>Styling Components</strong>
<small>Scoped styles, CSS custom properties</small>
</a>
</li>
<li>
<a href="data-flow.html" class="active">
<span class="icon">🔄</span>
<strong>Data Flow</strong>
<small>Passing state, events, context</small>
</a>
</li>
<li>
<a href="patterns-techniques.html">
<span class="icon">💡</span>
<strong>Patterns & Techniques</strong>
<small>Composition, scheduling, best practices</small>
</a>
</li>
<li>
<a href="examples-recipes.html">
<span class="icon">🍽️</span>
<strong>Examples & Recipes</strong>
<small>Common use cases and demos</small>
</a>
</li>
<li>
<a href="api-reference.html">
<span class="icon">📚</span>
<strong>API Reference</strong>
<small>Detailed documentation of classes and functions</small>
</a>
</li>
<li>
<a href="about-community.html">
<span class="icon">🤝</span>
<strong>About & Community</strong>
<small>License, versioning, getting involved</small>
</a>
</li>
</ol>
</nav>
</header>
<main><section class="hero">
<h1 id="data-flow">
<a name="data-flow" class="anchor" href="#data-flow">
<span class="permalink">🔗</span>
</a>
🔄 Data Flow
</h1><p class="lead"><strong>UIElement enables smooth data flow between components using signals, events, and context.</strong> State can be <strong>passed down</strong> to child components, events can <strong>bubble up</strong> to notify parents of changes, and context can propagate across the component tree to <strong>share global state</strong> efficiently. This page explores different patterns for structuring data flow, helping you create modular, loosely coupled components that work seamlessly together.</p>
</section>
<section>
<h2 id="component-coordination">
<a name="component-coordination" class="anchor" href="#component-coordination">
<span class="permalink">🔗</span>
</a>
Component Coordination
</h2><p>Let's consider a <strong>product catalog</strong> where users can add items to a shopping cart. We have <strong>three independent components</strong> that work together:</p>
<ul>
<li><code>ProductCatalog</code> <strong>(Parent)</strong>:<ul>
<li><strong>Tracks all <code>SpinButton</code> components</strong> in its subtree and calculates the total count of items in the shopping cart.</li>
<li><strong>Passes that total</strong> to a <code>InputButton</code>.</li>
</ul>
</li>
<li><code>InputButton</code> <strong>(Child)</strong>:<ul>
<li>Displays a <strong>badge</strong> in the top-right corner when the <code>badge</code> property is set.</li>
<li><strong>Does not track any state</strong> – it simply renders whatever value is passed to it.</li>
</ul>
</li>
<li><code>SpinButton</code> <strong>(Child)</strong>:<ul>
<li>Displays an <strong>Add to Cart</strong> button initially.</li>
<li>When an item is added, it transforms into a <strong>stepper</strong> (increment/decrement buttons).</li>
</ul>
</li>
</ul>
<p>Although <code>InputButton</code> and <code>SpinButton</code> are completely independent, they need to work together.<br>So <code>ProductCatalog</code> <strong>coordinates the data flow between them</strong>.</p>
<h3 id="parent-component-productcatalog">
<a name="parent-component-productcatalog" class="anchor" href="#parent-component-productcatalog">
<span class="permalink">🔗</span>
</a>
Parent Component: ProductCatalog
</h3><p>The <strong>parent component (<code>ProductCatalog</code>) knows about its children</strong>, meaning it can <strong>retrieve state from and pass state to</strong> them.</p>
<p>First, we need to observe the quantities of all <code>SpinButton</code> components. For this, we create a signal of all children matching the <code>spin-button</code> selector:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#A6E22E">component</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"product-catalog"</span><span style="color:#F8F8F2">, {</span></span>
<span class="line"><span style="color:#A6E22E"> total</span><span style="color:#F8F8F2">: (</span><span style="color:#FD971F;font-style:italic">el</span><span style="color:#F8F8F2">) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> () </span><span style="color:#66D9EF;font-style:italic">=></span></span>
<span class="line"><span style="color:#A6E22E"> selection</span><span style="color:#F8F8F2">(el, </span><span style="color:#E6DB74">"spin-button"</span><span style="color:#F8F8F2">)</span></span>
<span class="line"><span style="color:#F8F8F2"> .</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">()</span></span>
<span class="line"><span style="color:#F8F8F2"> .</span><span style="color:#A6E22E">reduce</span><span style="color:#F8F8F2">((</span><span style="color:#FD971F;font-style:italic">sum</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">item</span><span style="color:#F8F8F2">) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> sum </span><span style="color:#F92672">+</span><span style="color:#F8F8F2"> item.value, </span><span style="color:#AE81FF">0</span><span style="color:#F8F8F2">),</span></span>
<span class="line"><span style="color:#F8F8F2">}, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> []);</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<p>The <code>selection()</code> function returns a signal that emits an array of all matching elements. In contrast to a static <code>querySelectorAll()</code> call, the <code>selection()</code> function is reactive and updates whenever new elements are added or removed from the DOM.</p>
<p>Then, we need to calculate the total of all product quantities and pass it on to the <code>InputButton</code> component. In UIElement we use the <code>pass()</code> function to share state across components:</p>
<code-block collapsed language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#A6E22E">component</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"product-catalog"</span><span style="color:#F8F8F2">, {</span></span>
<span class="line"><span style="color:#A6E22E"> total</span><span style="color:#F8F8F2">: (</span><span style="color:#FD971F;font-style:italic">el</span><span style="color:#F8F8F2">) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> () </span><span style="color:#66D9EF;font-style:italic">=></span></span>
<span class="line"><span style="color:#A6E22E"> selection</span><span style="color:#F8F8F2">(el, </span><span style="color:#E6DB74">"spin-button"</span><span style="color:#F8F8F2">)</span></span>
<span class="line"><span style="color:#F8F8F2"> .</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">()</span></span>
<span class="line"><span style="color:#F8F8F2"> .</span><span style="color:#A6E22E">reduce</span><span style="color:#F8F8F2">((</span><span style="color:#FD971F;font-style:italic">sum</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">item</span><span style="color:#F8F8F2">) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> sum </span><span style="color:#F92672">+</span><span style="color:#F8F8F2"> item.value, </span><span style="color:#AE81FF">0</span><span style="color:#F8F8F2">),</span></span>
<span class="line"><span style="color:#F8F8F2">}, (</span><span style="color:#FD971F;font-style:italic">el</span><span style="color:#F8F8F2">) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> [</span></span>
<span class="line"><span style="color:#A6E22E"> first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"input-button"</span><span style="color:#F8F8F2">,</span></span>
<span class="line"><span style="color:#A6E22E"> pass</span><span style="color:#F8F8F2">({</span></span>
<span class="line"><span style="color:#A6E22E"> badge</span><span style="color:#F8F8F2">: () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> (el.total </span><span style="color:#F92672">></span><span style="color:#AE81FF"> 0</span><span style="color:#F92672"> ?</span><span style="color:#A6E22E"> String</span><span style="color:#F8F8F2">(el.total) </span><span style="color:#F92672">:</span><span style="color:#E6DB74"> ""</span><span style="color:#F8F8F2">),</span></span>
<span class="line"><span style="color:#A6E22E"> disabled</span><span style="color:#F8F8F2">: () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F92672"> !</span><span style="color:#F8F8F2">el.total,</span></span>
<span class="line"><span style="color:#F8F8F2"> }),</span></span>
<span class="line"><span style="color:#F8F8F2"> ),</span></span>
<span class="line"><span style="color:#F8F8F2">]);</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
<button type="button" class="overlay">Expand</button>
</code-block>
<p>Allright, that's it!</p>
<ul>
<li>✅ Whenever one of the <code>value</code> signals of a <code><spin-button></code> updates, the total in the badge of <code><input-button></code> automatically updates.</li>
<li>✅ No need for event listeners or manual updates!</li>
</ul>
<h3 id="child-component-inputbutton">
<a name="child-component-inputbutton" class="anchor" href="#child-component-inputbutton">
<span class="permalink">🔗</span>
</a>
Child Component: InputButton
</h3><p>The <code>InputButton</code> component <strong>displays a badge when needed</strong> – it does not know about any other component nor track state itself. It just exposes a reactive property <code>badge</code> of type <code>string</code> and has an effect to react to state changes that updates the DOM subtree.</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#A6E22E">component</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"input-button"</span><span style="color:#F8F8F2">, {</span></span>
<span class="line"><span style="color:#F8F8F2"> badge: </span><span style="color:#A6E22E">asString</span><span style="color:#F8F8F2">(RESET),</span></span>
<span class="line"><span style="color:#F8F8F2">}, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> [</span></span>
<span class="line"><span style="color:#A6E22E"> first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">".badge"</span><span style="color:#F8F8F2">, </span><span style="color:#A6E22E">setText</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"badge"</span><span style="color:#F8F8F2">)),</span></span>
<span class="line"><span style="color:#F8F8F2">])</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<ul>
<li>✅ Whenever the <code>badge</code> property is updated by a parent component, the badge text updates.</li>
<li>✅ If <code>badge</code> is an empty string, the badge indicator is hidden (via CSS).</li>
</ul>
<h3 id="childcomponent-spinbutton">
<a name="childcomponent-spinbutton" class="anchor" href="#childcomponent-spinbutton">
<span class="permalink">🔗</span>
</a>
ChildComponent: SpinButton
</h3><p>The <code>SpinButton</code> component reacts to user interactions and exposes a reactive property <code>value</code> of type <code>number</code>. It updates its own internal DOM subtree, but doesn't know about any other component nor where the value is used.</p>
<code-block collapsed language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#A6E22E">component</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"spin-button"</span><span style="color:#F8F8F2">, {</span></span>
<span class="line"><span style="color:#F8F8F2"> value: </span><span style="color:#A6E22E">asInteger</span><span style="color:#F8F8F2">(),</span></span>
<span class="line"><span style="color:#F8F8F2">}, (</span><span style="color:#FD971F;font-style:italic">el</span><span style="color:#F8F8F2">) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#F8F8F2"> max </span><span style="color:#F92672">=</span><span style="color:#A6E22E"> asInteger</span><span style="color:#F8F8F2">(</span><span style="color:#AE81FF">9</span><span style="color:#F8F8F2">)(el, el.</span><span style="color:#A6E22E">getAttribute</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"max"</span><span style="color:#F8F8F2">));</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#A6E22E"> isZero</span><span style="color:#F92672"> =</span><span style="color:#F8F8F2"> () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> el.value </span><span style="color:#F92672">===</span><span style="color:#AE81FF"> 0</span><span style="color:#F8F8F2">;</span></span>
<span class="line"><span style="color:#F92672"> return</span><span style="color:#F8F8F2"> [</span></span>
<span class="line"><span style="color:#A6E22E"> first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">".value"</span><span style="color:#F8F8F2">,</span></span>
<span class="line"><span style="color:#A6E22E"> setText</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"value"</span><span style="color:#F8F8F2">),</span></span>
<span class="line"><span style="color:#A6E22E"> setProperty</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"hidden"</span><span style="color:#F8F8F2">, isZero),</span></span>
<span class="line"><span style="color:#F8F8F2"> ),</span></span>
<span class="line"><span style="color:#A6E22E"> first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">".decrement"</span><span style="color:#F8F8F2">,</span></span>
<span class="line"><span style="color:#A6E22E"> setProperty</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"hidden"</span><span style="color:#F8F8F2">, isZero),</span></span>
<span class="line"><span style="color:#A6E22E"> on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"click"</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#F8F8F2"> el.value</span><span style="color:#F92672">--</span><span style="color:#F8F8F2">;</span></span>
<span class="line"><span style="color:#F8F8F2"> }),</span></span>
<span class="line"><span style="color:#F8F8F2"> ),</span></span>
<span class="line"><span style="color:#A6E22E"> first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">".increment"</span><span style="color:#F8F8F2">,</span></span>
<span class="line"><span style="color:#A6E22E"> setProperty</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"disabled"</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> el.value </span><span style="color:#F92672">>=</span><span style="color:#F8F8F2"> max),</span></span>
<span class="line"><span style="color:#A6E22E"> on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"click"</span><span style="color:#F8F8F2">, () </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#F8F8F2"> el.value</span><span style="color:#F92672">++</span><span style="color:#F8F8F2">;</span></span>
<span class="line"><span style="color:#F8F8F2"> }),</span></span>
<span class="line"><span style="color:#F8F8F2"> ),</span></span>
<span class="line"><span style="color:#A6E22E"> all</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"button"</span><span style="color:#F8F8F2">,</span></span>
<span class="line"><span style="color:#A6E22E"> on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">"keydown"</span><span style="color:#F8F8F2">, (</span><span style="color:#FD971F;font-style:italic">e</span><span style="color:#F8F8F2">) </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#F8F8F2"> { key } </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> e;</span></span>
<span class="line"><span style="color:#F92672"> if</span><span style="color:#F8F8F2"> ([</span><span style="color:#E6DB74">"ArrowUp"</span><span style="color:#F8F8F2">, </span><span style="color:#E6DB74">"ArrowDown"</span><span style="color:#F8F8F2">, </span><span style="color:#E6DB74">"-"</span><span style="color:#F8F8F2">, </span><span style="color:#E6DB74">"+"</span><span style="color:#F8F8F2">].</span><span style="color:#A6E22E">includes</span><span style="color:#F8F8F2">(key)) {</span></span>
<span class="line"><span style="color:#F8F8F2"> e.</span><span style="color:#A6E22E">stopPropagation</span><span style="color:#F8F8F2">();</span></span>
<span class="line"><span style="color:#F8F8F2"> e.</span><span style="color:#A6E22E">preventDefault</span><span style="color:#F8F8F2">();</span></span>
<span class="line"><span style="color:#F92672"> if</span><span style="color:#F8F8F2"> (key </span><span style="color:#F92672">===</span><span style="color:#E6DB74"> "ArrowDown"</span><span style="color:#F92672"> ||</span><span style="color:#F8F8F2"> key </span><span style="color:#F92672">===</span><span style="color:#E6DB74"> "-"</span><span style="color:#F8F8F2">) el.value</span><span style="color:#F92672">--</span><span style="color:#F8F8F2">;</span></span>
<span class="line"><span style="color:#F92672"> if</span><span style="color:#F8F8F2"> (key </span><span style="color:#F92672">===</span><span style="color:#E6DB74"> "ArrowUp"</span><span style="color:#F92672"> ||</span><span style="color:#F8F8F2"> key </span><span style="color:#F92672">===</span><span style="color:#E6DB74"> "+"</span><span style="color:#F8F8F2">) el.value</span><span style="color:#F92672">++</span><span style="color:#F8F8F2">;</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2"> }),</span></span>
<span class="line"><span style="color:#F8F8F2"> ),</span></span>
<span class="line"><span style="color:#F8F8F2"> ];</span></span>
<span class="line"><span style="color:#F8F8F2">});</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
<button type="button" class="overlay">Expand</button>
</code-block>
<ul>
<li>✅ Whenever the user clicks a button or presses a handled key, the value property is updated.</li>
<li>✅ The component sets hidden and disabled states of buttons and updates the text of the <code>.value</code> element.</li>
</ul>
<h3 id="full-example">
<a name="full-example" class="anchor" href="#full-example">
<span class="permalink">🔗</span>
</a>
Full Example
</h3><p>Here's how everything comes together:</p>
<ul>
<li>Each <code>SpinButton</code> tracks its own value.</li>
<li>The <code>ProductCatalog</code> sums all quantities and passes the total to <code>InputButton</code>.</li>
<li>The <code>InputButton</code> displays the total if it's greater than zero.</li>
</ul>
<p><strong>No custom events are needed – state flows naturally!</strong></p>
<component-demo>
<div class="preview">
<product-catalog>
<header>
<p>Shop</p>
<input-button disabled>
<button type="button" disabled>
<span class="label">🛒 Shopping Cart</span>
<span class="badge"></span>
</button>
</input-button>
</header>
<ul>
<li>
<p>Product 1</p>
<spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
<button type="button" class="decrement" aria-label="Decrement" hidden>−</button>
<p class="value" hidden>0</p>
<button type="button" class="increment">Add to Cart</button>
</spin-button>
</li>
<li>
<p>Product 2</p>
<spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
<button type="button" class="decrement" aria-label="Decrement" hidden>−</button>
<p class="value" hidden>0</p>
<button type="button" class="increment">Add to Cart</button>
</spin-button>
</li>
<li>
<p>Product 3</p>
<spin-button value="0" zero-label="Add to Cart" increment-label="Increment">
<button type="button" class="decrement" aria-label="Decrement" hidden>−</button>
<p class="value" hidden>0</p>
<button type="button" class="increment">Add to Cart</button>
</spin-button>
</li>
</ul>
</product-catalog>
</div>
<details>
<summary>ProductCatalog Source Code</summary>
<lazy-load src="./examples/product-catalog.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
<details>
<summary>InputButton Source Code</summary>
<lazy-load src="./examples/input-button.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
<details>
<summary>SpinButton Source Code</summary>
<lazy-load src="./examples/spin-button.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
</component-demo>
</section>
<section>
<h2 id="custom-events">
<a name="custom-events" class="anchor" href="#custom-events">
<span class="permalink">🔗</span>
</a>
Custom Events
</h2><p>Passing state down works well when a <strong>parent component can directly observe child state</strong>, but sometimes a <strong>child needs to notify its parent</strong> about an action <strong>without managing shared state itself</strong>.</p>
<p>Let's consider a Todo App, where users can add tasks:</p>
<ul>
<li><code>TodoApp</code> <strong>(Parent)</strong>:<ul>
<li>Holds the list of todos as a state signal.</li>
<li>Listens for an <code>add-todo</code> event from the child (<code>TodoForm</code>).</li>
<li>Updates the state when a new todo is submitted.</li>
</ul>
</li>
<li><code>TodoForm</code> <strong>(Child)</strong>:<ul>
<li>Handles <strong>user input</strong> but does <strong>not</strong> store todos.</li>
<li>Emits an <code>add-todo</code> event when the user submits the form.</li>
<li>Lets the parent decide <strong>what to do with the data</strong>.</li>
</ul>
</li>
</ul>
<h3 id="why-use-events-here">
<a name="why-use-events-here" class="anchor" href="#why-use-events-here">
<span class="permalink">🔗</span>
</a>
Why use events here?
</h3><ul>
<li>The child <strong>doesn't need to know where the data goes</strong> – it just <strong>emits an event</strong>.</li>
<li>The parent <strong>decides what to do</strong> with the new todo (e.g., adding it to a list).</li>
<li>This keeps <code>TodoForm</code> <strong>reusable</strong> – it could work in different apps without modification.</li>
</ul>
<h3 id="parent-component-todoapp">
<a name="parent-component-todoapp" class="anchor" href="#parent-component-todoapp">
<span class="permalink">🔗</span>
</a>
Parent Component: TodoApp
</h3><p>The parent (<code>TodoApp</code>) <strong>listens for events</strong> and calls the <code>.addItem()</code> method on <code>TodoList</code> when a new todo is added:</p>
<code-block language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.self.</span><span style="color:#A6E22E">on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'add-todo'</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">e</span><span style="color:#66D9EF;font-style:italic"> =></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">querySelector</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'todo-list'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">addItem</span><span style="color:#F8F8F2">(e.detail)</span></span>
<span class="line"><span style="color:#F8F8F2">})</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
</code-block>
<ul>
<li>✅ <strong>Whenever <code>TodoForm</code> emits an <code>'add-todo'</code> event</strong>, a new task is appended to the todo list.</li>
<li>✅ The <strong>event carries data</strong> (<code>e.detail</code>), so the parent knows what was submitted.</li>
</ul>
<h3 id="child-component-todoform">
<a name="child-component-todoform" class="anchor" href="#child-component-todoform">
<span class="permalink">🔗</span>
</a>
Child Component: TodoForm
</h3><p>The child (<code>TodoForm</code>) <strong>collects user input</strong> and emits an event when the form is submitted:</p>
<code-block collapsed language="js" copy-success="Copied!" copy-error="Error trying to copy to clipboard!">
<p class="meta">
<span class="language">js</span>
</p>
<pre class="shiki monokai" style="background-color:#272822;color:#F8F8F2" tabindex="0"><code><span class="line"><span style="color:#66D9EF;font-style:italic">const</span><span style="color:#F8F8F2"> input </span><span style="color:#F92672">=</span><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">querySelector</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'input-field'</span><span style="color:#F8F8F2">)</span></span>
<span class="line"><span style="color:#FD971F">this</span><span style="color:#F8F8F2">.</span><span style="color:#A6E22E">first</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'form'</span><span style="color:#F8F8F2">).</span><span style="color:#A6E22E">on</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'submit'</span><span style="color:#F8F8F2">, </span><span style="color:#FD971F;font-style:italic">e</span><span style="color:#66D9EF;font-style:italic"> =></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#F8F8F2"> e.</span><span style="color:#A6E22E">preventDefault</span><span style="color:#F8F8F2">()</span></span>
<span class="line"></span>
<span class="line"><span style="color:#88846F"> // Wait for microtask to ensure the input field value is updated before dispatching the event</span></span>
<span class="line"><span style="color:#A6E22E"> queueMicrotask</span><span style="color:#F8F8F2">(() </span><span style="color:#66D9EF;font-style:italic">=></span><span style="color:#F8F8F2"> {</span></span>
<span class="line"><span style="color:#66D9EF;font-style:italic"> const</span><span style="color:#F8F8F2"> value </span><span style="color:#F92672">=</span><span style="color:#F8F8F2"> input?.</span><span style="color:#A6E22E">get</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'value'</span><span style="color:#F8F8F2">)?.</span><span style="color:#A6E22E">trim</span><span style="color:#F8F8F2">()</span></span>
<span class="line"><span style="color:#F92672"> if</span><span style="color:#F8F8F2"> (value) {</span></span>
<span class="line"><span style="color:#FD971F"> this</span><span style="color:#F8F8F2">.self.</span><span style="color:#A6E22E">emit</span><span style="color:#F8F8F2">(</span><span style="color:#E6DB74">'add-todo'</span><span style="color:#F8F8F2">, value)</span></span>
<span class="line"><span style="color:#F8F8F2"> input?.</span><span style="color:#A6E22E">clear</span><span style="color:#F8F8F2">()</span></span>
<span class="line"><span style="color:#F8F8F2"> }</span></span>
<span class="line"><span style="color:#F8F8F2"> })</span></span>
<span class="line"><span style="color:#F8F8F2">})</span></span>
<span class="line"></span></code></pre>
<input-button class="copy">
<button type="button" class="secondary small">
<span class="label">Copy</span>
</button>
</input-button>
<button type="button" class="overlay">Expand</button>
</code-block>
<ul>
<li>✅ The form does <strong>NOT store the todo</strong> – it just emits an event.</li>
<li>✅ The parent (<code>TodoApp</code>) <strong>decides what happens next</strong>.</li>
<li>✅ The <strong>event includes data</strong> (value), making it easy to handle.</li>
</ul>
<h3 id="full-example">
<a name="full-example" class="anchor" href="#full-example">
<span class="permalink">🔗</span>
</a>
Full Example
</h3><p>Here's how everything comes together:</p>
<ul>
<li><strong>User types a task</strong> into input field in <code>TodoForm</code>.</li>
<li><strong>On submit, <code>TodoForm</code> emits <code>'add-todo'</code></strong> with the new task as event detail.</li>
<li><strong><code>TodoApp</code> listens for <code>'add-todo'</code></strong> and updates the todo list.</li>
</ul>
<component-demo>
<div class="preview">
<todo-app>
<form action="#">
<input-field>
<label for="add-todo">What needs to be done?</label>
<div class="row">
<div class="group auto">
<input id="add-todo" type="text" value="" required>
</div>
</div>
</input-field>
<input-button class="submit">
<button type="submit" class="constructive" disabled>Add Todo</button>
</input-button>
</form>
<ol filter="all"></ol>
<template>
<li>
<input-checkbox class="todo">
<label>
<input type="checkbox" class="visually-hidden" />
<span class="label"><slot></slot></span>
</label>
</input-checkbox>
<input-button class="delete">
<button type="button" class="destructive small">Delete</button>
</input-button>
</li>
</template>
<footer>
<div class="todo-count">
<p class="all-done">Well done, all done!</p>
<p class="remaining">
<span class="count"></span>
<span class="singular">task</span>
<span class="plural">tasks</span>
remaining
</p>
</div>
<input-radiogroup value="all" class="split-button">
<fieldset>
<legend class="visually-hidden">Filter</legend>
<label class="selected">
<input type="radio" class="visually-hidden" name="filter" value="all" checked>
<span>All</span>
</label>
<label>
<input type="radio" class="visually-hidden" name="filter" value="active">
<span>Active</span>
</label>
<label>
<input type="radio" class="visually-hidden" name="filter" value="completed">
<span>Completed</span>
</label>
</fieldset>
</input-radiogroup>
<input-button class="clear-completed">
<button type="button" class="destructive">
<span class="label">Clear Completed</span>
<span class="badge"></span>
</button>
</input-button>
</footer>
</todo-app>
</div>
<details>
<summary>TodoApp Source Code</summary>
<lazy-load src="./examples/todo-app.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
<details>
<summary>InputField Source Code</summary>
<lazy-load src="./examples/input-field.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
<details>
<summary>InputButton Source Code</summary>
<lazy-load src="./examples/input-button.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
<details>
<summary>InputCheckbox Source Code</summary>
<lazy-load src="./examples/input-checkbox.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
<details>
<summary>InputRadiogroup Source Code</summary>
<lazy-load src="./examples/input-radiogroup.html">
<p class="loading" role="status">Loading...</p>
<p class="error" role="alert" aria-live="polite" hidden></p>
</lazy-load>
</details>
</component-demo>
</section>
<section>
<h2 id="providing-context">
<a name="providing-context" class="anchor" href="#providing-context">
<span class="permalink">🔗</span>
</a>
Providing Context
</h2></section>
<section>
<h2 id="consuming-context">
<a name="consuming-context" class="anchor" href="#consuming-context">
<span class="permalink">🔗</span>
</a>
Consuming Context
</h2></section>
<section>
<h2 id="next-steps">
<a name="next-steps" class="anchor" href="#next-steps">
<span class="permalink">🔗</span>
</a>
Next Steps
</h2></section>
</main>
<footer class="content-grid">
<div class="content">
<h2 class="visually-hidden">Footer</h2>
<p>© 2024 – 2025 Zeix AG</p>
</div>
</footer>
</body>
</html>