UNPKG

@zeix/ui-element

Version:

UIElement - minimal reactive framework based on Web Components

614 lines (567 loc) 37.8 kB
<!doctype 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&#39;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&#39;s it!</p> <ul> <li>✅ Whenever one of the <code>value</code> signals of a <code>&lt;spin-button&gt;</code> updates, the total in the badge of <code>&lt;input-button&gt;</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&#39;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&#39;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&#39;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&#39;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&#39;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>&#39;add-todo&#39;</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&#39;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>&#39;add-todo&#39;</code></strong> with the new task as event detail.</li> <li><strong><code>TodoApp</code> listens for <code>&#39;add-todo&#39;</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>