{"slug":"utilities","title":"Utilities","headings":["ref","innerHTML","Conditional classes","portal","errorBoundary","resource","store","context","router"],"contentHtml":"<h1 id=\"utilities\">Utilities</h1><p>Batteries included alongside the core runtime - all tree-shakeable imports from <code>ruri</code>.</p><h2 id=\"ref\">ref</h2><p>Imperative access to a created element:</p><div class=\"playground\"><pre class=\"pg-code shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { tags } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"ruri\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> { </span><span style=\"color:#79B8FF\">input</span><span style=\"color:#E1E4E8\"> } </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> tags</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">let</span><span style=\"color:#E1E4E8\"> inputElement</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">output.</span><span style=\"color:#B392F0\">append</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">    input</span><span style=\"color:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">      ref</span><span style=\"color:#E1E4E8\">: (</span><span style=\"color:#FFAB70\">el</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=></span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        inputElement </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> el</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">        el.</span><span style=\"color:#B392F0\">focus</span><span style=\"color:#E1E4E8\">()</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      },</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">      placeholder: </span><span style=\"color:#9ECBFF\">\"focused on mount\"</span><span style=\"color:#E1E4E8\">,</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    }),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">)</span></span></code></pre><div class=\"pg-view\" id=\"pg-view-1\"><span class=\"pg-hint\">running...</span></div></div><p>Refs are replayed during hydration and mark their element as bound, so row updates swap rather than patch through them.</p><h2 id=\"innerhtml\">innerHTML</h2><p>Escape hatch for pre-rendered HTML:</p><pre class=\"shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#B392F0\">div</span><span style=\"color:#E1E4E8\">({ innerHTML: </span><span style=\"color:#9ECBFF\">\"&#x3C;b>trusted&#x3C;/b> markup\"</span><span style=\"color:#E1E4E8\"> })</span></span></code></pre><p>Server-side the string is emitted verbatim (<code>ServerRaw</code>); client-side it is assigned to <code>element.innerHTML</code>. Only use trusted content.</p><h2 id=\"conditional-classes\">Conditional classes</h2><pre class=\"shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#B392F0\">div</span><span style=\"color:#E1E4E8\">({ class: { active: isActive.value, muted: </span><span style=\"color:#F97583\">!</span><span style=\"color:#E1E4E8\">isActive.value } })</span></span></code></pre><p>Truthy keys become classes. Objects, arrays and strings can all be wrapped in a <code>Signal</code> for reactive class lists.</p><h2 id=\"portal\">portal</h2><p>Render children into another container (modals, toolbars). Content is removed when the surrounding tree unmounts:</p><div class=\"playground\"><pre class=\"pg-code shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { portal, tags } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"ruri\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> { </span><span style=\"color:#79B8FF\">div</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">p</span><span style=\"color:#E1E4E8\"> } </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> tags</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">output.</span><span style=\"color:#B392F0\">append</span><span style=\"color:#E1E4E8\">(</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">    div</span><span style=\"color:#E1E4E8\">({},</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">        p</span><span style=\"color:#E1E4E8\">({}, </span><span style=\"color:#9ECBFF\">\"stays here\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">        portal</span><span style=\"color:#E1E4E8\">(output, </span><span style=\"color:#B392F0\">p</span><span style=\"color:#E1E4E8\">({}, </span><span style=\"color:#9ECBFF\">\"also rendered into output\"</span><span style=\"color:#E1E4E8\">)),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    ),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">)</span></span></code></pre><div class=\"pg-view\" id=\"pg-view-2\"><span class=\"pg-hint\">running...</span></div></div><p>On the server portals render inline at their call site.</p><h2 id=\"errorboundary\">errorBoundary</h2><p>Catch failures while building a subtree:</p><pre class=\"shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { errorBoundary } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"ruri\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\">errorBoundary</span><span style=\"color:#E1E4E8\">(() </span><span style=\"color:#F97583\">=></span><span style=\"color:#B392F0\"> riskyRender</span><span style=\"color:#E1E4E8\">(), (</span><span style=\"color:#FFAB70\">error</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">=></span><span style=\"color:#B392F0\"> errorMessage</span><span style=\"color:#E1E4E8\">(error))</span></span></code></pre><h2 id=\"resource\">resource</h2><p>Wrap an async fetcher into reactive state:</p><div class=\"playground\"><pre class=\"pg-code shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { createResource, tags } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"ruri\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> { </span><span style=\"color:#79B8FF\">div</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#79B8FF\">span</span><span style=\"color:#E1E4E8\"> } </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> tags</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> todos</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> createResource</span><span style=\"color:#E1E4E8\">(() </span><span style=\"color:#F97583\">=></span><span style=\"color:#79B8FF\"> Promise</span><span style=\"color:#E1E4E8\">.</span><span style=\"color:#B392F0\">resolve</span><span style=\"color:#E1E4E8\">([</span><span style=\"color:#9ECBFF\">\"one\"</span><span style=\"color:#E1E4E8\">, </span><span style=\"color:#9ECBFF\">\"two\"</span><span style=\"color:#E1E4E8\">]))</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> view</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> div</span><span style=\"color:#E1E4E8\">({},</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">    todos.loading </span><span style=\"color:#F97583\">?</span><span style=\"color:#B392F0\"> span</span><span style=\"color:#E1E4E8\">({}, </span><span style=\"color:#9ECBFF\">\"loading...\"</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">:</span><span style=\"color:#B392F0\"> span</span><span style=\"color:#E1E4E8\">({}, todos.data.</span><span style=\"color:#B392F0\">join</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\", \"</span><span style=\"color:#E1E4E8\">)),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">output.</span><span style=\"color:#B392F0\">append</span><span style=\"color:#E1E4E8\">(view)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">todos.</span><span style=\"color:#B392F0\">refetch</span><span style=\"color:#E1E4E8\">()</span></span></code></pre><div class=\"pg-view\" id=\"pg-view-3\"><span class=\"pg-hint\">running...</span></div></div><p><code>loading</code>, <code>data</code> and <code>error</code> are plain signals - bind them anywhere.</p><h2 id=\"store\">store</h2><p>Shallow reactive objects with per-property tracking:</p><div class=\"playground\"><pre class=\"pg-code shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { createStore, effect, tags } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"ruri\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> { </span><span style=\"color:#79B8FF\">div</span><span style=\"color:#E1E4E8\"> } </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> tags</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> state</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> createStore</span><span style=\"color:#E1E4E8\">({ count: </span><span style=\"color:#79B8FF\">0</span><span style=\"color:#E1E4E8\"> })</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\">effect</span><span style=\"color:#E1E4E8\">(() </span><span style=\"color:#F97583\">=></span><span style=\"color:#E1E4E8\"> console.</span><span style=\"color:#B392F0\">log</span><span style=\"color:#E1E4E8\">(state.count))</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">state.count</span><span style=\"color:#F97583\">++</span><span style=\"color:#6A737D\"> // triggers exactly this effect</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">output.</span><span style=\"color:#B392F0\">append</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#B392F0\">div</span><span style=\"color:#E1E4E8\">({}, state.count))</span></span></code></pre><div class=\"pg-view\" id=\"pg-view-4\"><span class=\"pg-hint\">running...</span></div></div><p>Nested objects stay plain - replace arrays wholesale instead of mutating them.</p><h2 id=\"context\">context</h2><p>Build-time value injection without prop drilling:</p><pre class=\"shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { consume, createContext, provide } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"ruri\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> Theme</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> createContext</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"light\"</span><span style=\"color:#E1E4E8\">)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#B392F0\">provide</span><span style=\"color:#E1E4E8\">([[Theme, </span><span style=\"color:#9ECBFF\">\"dark\"</span><span style=\"color:#E1E4E8\">]], () </span><span style=\"color:#F97583\">=></span><span style=\"color:#E1E4E8\"> {</span></span>\n<span class=\"line\"><span style=\"color:#B392F0\">  panel</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#B392F0\">consume</span><span style=\"color:#E1E4E8\">(Theme)) </span><span style=\"color:#6A737D\">// \"dark\"</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">})</span></span></code></pre><p>Values are captured while the subtree is created.</p><h2 id=\"router\">router</h2><p>A minimal history-based client router:</p><div class=\"playground\"><pre class=\"pg-code shiki github-dark\" style=\"background-color:#17171d;color:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#F97583\">import</span><span style=\"color:#E1E4E8\"> { createRouter, tags } </span><span style=\"color:#F97583\">from</span><span style=\"color:#9ECBFF\"> \"ruri\"</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#E1E4E8\"> { </span><span style=\"color:#79B8FF\">div</span><span style=\"color:#E1E4E8\"> } </span><span style=\"color:#F97583\">=</span><span style=\"color:#E1E4E8\"> tags</span></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> app</span><span style=\"color:#F97583\"> =</span><span style=\"color:#E1E4E8\"> document.</span><span style=\"color:#B392F0\">getElementById</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"playground-root\"</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#F97583\">??</span><span style=\"color:#E1E4E8\"> output</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#F97583\">const</span><span style=\"color:#79B8FF\"> router</span><span style=\"color:#F97583\"> =</span><span style=\"color:#B392F0\"> createRouter</span><span style=\"color:#E1E4E8\">({</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">  \"/\"</span><span style=\"color:#E1E4E8\">: () </span><span style=\"color:#F97583\">=></span><span style=\"color:#B392F0\"> div</span><span style=\"color:#E1E4E8\">({}, </span><span style=\"color:#9ECBFF\">\"home\"</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#9ECBFF\">  \"/hello/:name\"</span><span style=\"color:#E1E4E8\">: ({ </span><span style=\"color:#FFAB70\">params</span><span style=\"color:#E1E4E8\"> }) </span><span style=\"color:#F97583\">=></span><span style=\"color:#B392F0\"> div</span><span style=\"color:#E1E4E8\">({}, </span><span style=\"color:#9ECBFF\">`hello ${</span><span style=\"color:#E1E4E8\">params</span><span style=\"color:#9ECBFF\">.</span><span style=\"color:#E1E4E8\">name</span><span style=\"color:#9ECBFF\">}`</span><span style=\"color:#E1E4E8\">),</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">}, { mode: </span><span style=\"color:#9ECBFF\">\"hash\"</span><span style=\"color:#E1E4E8\"> })</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">router.</span><span style=\"color:#B392F0\">mount</span><span style=\"color:#E1E4E8\">(app)</span></span>\n<span class=\"line\"><span style=\"color:#E1E4E8\">router.</span><span style=\"color:#B392F0\">navigate</span><span style=\"color:#E1E4E8\">(</span><span style=\"color:#9ECBFF\">\"/hello/ruri\"</span><span style=\"color:#E1E4E8\">) </span><span style=\"color:#6A737D\">// lives in #/hello/ruri - the page URL is untouched</span></span></code></pre><div class=\"pg-view\" id=\"pg-view-5\"><span class=\"pg-hint\">running...</span></div></div><p>Routes with <code>:param</code> segments receive typed params; navigation works through <code>router.navigate()</code> and browser back/forward.</p>"}