Files
WRNexusJS/examples/component-showcase/app/pages/components/layout-splitter.wrn
T
ClintchizandClaude Opus 5 b56cb8cba5
Quality / quality (ubuntu-latest) (push) Failing after 6m8s
Quality / quality (windows-latest) (push) Canceled after 0s
feat(ui): build LayoutSplitter and CustomScrollbar for real
Both advertised behaviour they did not have. LayoutSplitter declared
resizeStart, resize and resizeEnd with no pointer handling whatsoever, so a
caller wired up @resize and received nothing, for ever, with no error, and its
props were columns, gap and maxWidth copied from a grid scaffold.
CustomScrollbar was the same shape with a scroll output.

The splitter now resizes. Dragging lives in the reactive runtime behind
data-wrn-splitter, because a pointermove fires far too often to route through
a client function and a state write made in that callback is dropped; the
resolved size is held on the container as a --wrn-split custom property and
the component grids from it. The handle is a real separator: arrow keys step
it, Home and End go to the bounds rather than to nothing, and it carries
aria-valuenow, aria-valuemin and aria-valuemax. minSize fixes both bounds so
neither pane can be dragged away and left unrecoverable.

CustomScrollbar is CSS rather than script -- scrollbar-width and
scrollbar-color with webkit rules for the engines that still need them -- and
its fake scroll output is removed rather than left unimplemented, since a
caller can listen for a plain scroll event.

The test harness needed a fix too: mount did not bind the window CustomEvent,
so the runtime built events from the host global and happy-dom listeners never
matched them, which made anything dispatched look silently lost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:22:45 +05:30

134 lines
12 KiB
Plaintext

// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page LayoutSplitterDetail {
layout = "showcase"
state playground_color = ctx.url.searchParams.get("pg_color") ?? "primary"
state playground_size = Number(ctx.url.searchParams.get("pg_size") ?? "50")
state playground_orientation = ctx.url.searchParams.get("pg_orientation") ?? "horizontal"
state playground_minSize = Number(ctx.url.searchParams.get("pg_minSize") ?? "15")
state playground_step = Number(ctx.url.searchParams.get("pg_step") ?? "5")
state playground_label = ctx.url.searchParams.get("pg_label") ?? "Layout Splitter"
state playground_class = ctx.url.searchParams.get("pg_class") ?? "showcase-instance showcase-instance--1"
seo {
title = "Layout Splitter"
description = "Theme-aware, responsive layout splitter component."
}
view {
<nav class="docs-breadcrumbs" aria-label="Breadcrumb">
<a href="/">Components</a><span aria-hidden="true">/</span><a href="/layout">Layout</a><span aria-hidden="true">/</span><span>Layout Splitter</span>
</nav>
<header class="detail-hero">
<div class="detail-hero-copy">
<span class="showcase-eyebrow">Layout component</span>
<h1>Layout Splitter</h1>
<p>Theme-aware, responsive layout splitter component.</p>
<div class="detail-badges"><span>7 props</span><span>3 slots</span><span>1 outputs</span><span>Theme ready</span><span>Responsive</span></div>
</div>
<div class="detail-hero-icon"><span class="icon-[lucide--component] size-10" aria-hidden="true"></span></div>
</header>
<section id="playground" class="detail-section playground-section" data-playground data-playground-component="LayoutSplitter" data-playground-public-tag="true" data-playground-has-slot="true" data-playground-events="resize">
<div class="detail-section-heading"><span>Interactive playground</span><h2>Configure Layout Splitter</h2><p>Change any prop and inspect the server-rendered component immediately.</p></div>
<div class="playground-workbench">
<div class="playground-preview">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="playground-preview-source" data-playground-preview><LayoutSplitter color='{playground_color}' size='{playground_size}' orientation='{playground_orientation}' minSize='{playground_minSize}' step='{playground_step}' label='{playground_label}' class='{playground_class}' /></div>
<section class="playground-code" aria-label="Current component code">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component code</span><button type="button" data-playground-copy><span class="icon-[lucide--copy] size-4" aria-hidden="true"></span>Copy</button></header>
<pre><code data-playground-code>&lt;LayoutSplitter
label="Layout Splitter"&gt;
&lt;div data-slot="start"&gt;
&lt;div class="showcase-slot"&gt;start slot&lt;/div&gt;
&lt;/div&gt;
&lt;div data-slot="end"&gt;
&lt;div class="showcase-slot"&gt;end slot&lt;/div&gt;
&lt;/div&gt;
&lt;/LayoutSplitter&gt;</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect the typed <code>payload</code>.</span></div>
</div>
<form class="playground-controls" data-playground-form>
<header><div><span>Component props</span><strong>7 controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
<div class="playground-fields"><label class="playground-field" for="playground-layout-splitter-color"><span><strong>color</strong><small>string</small></span><select id="playground-layout-splitter-color" name="pg_color"><option value="primary" selected>primary</option><option value="secondary">secondary</option><option value="success">success</option><option value="warning">warning</option><option value="danger">danger</option><option value="info">info</option></select></label><label class="playground-field" for="playground-layout-splitter-size"><span><strong>size</strong><small>number</small></span><input id="playground-layout-splitter-size" name="pg_size" type="number" value="50" /></label><label class="playground-field" for="playground-layout-splitter-orientation"><span><strong>orientation</strong><small>string</small></span><select id="playground-layout-splitter-orientation" name="pg_orientation"><option value="horizontal" selected>horizontal</option><option value="vertical">vertical</option></select></label><label class="playground-field" for="playground-layout-splitter-minSize"><span><strong>min Size</strong><small>number</small></span><input id="playground-layout-splitter-minSize" name="pg_minSize" type="number" value="15" /></label><label class="playground-field" for="playground-layout-splitter-step"><span><strong>step</strong><small>number</small></span><input id="playground-layout-splitter-step" name="pg_step" type="number" value="5" /></label><label class="playground-field" for="playground-layout-splitter-label"><span><strong>label</strong><small>string</small></span><input id="playground-layout-splitter-label" name="pg_label" type="text" value="Layout Splitter" /></label><label class="playground-field" for="playground-layout-splitter-class"><span><strong>class</strong><small>string</small></span><input id="playground-layout-splitter-class" name="pg_class" type="text" value="showcase-instance showcase-instance--1" /></label></div>
</form>
</div>
</section>
<div class="detail-layout">
<main class="detail-content">
<section class="detail-section"><div class="detail-section-heading"><span>Live examples</span><h2>Designed for real product surfaces</h2><p>Compare configurations and resize the browser to check responsive behavior.</p></div><div class="demo-list">
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Recommended</span><h2>Drag or arrow the divider</h2><p>Two panes with a movable divider. Drag it, or focus it and use the arrow keys; Home and End go to the bounds. The size is resolved in the runtime and written onto the container as a custom property, because a pointermove fires far too often to route through a client function.</p></div>
<span class="demo-case-number">01</span>
</header>
<div class="demo-workbench">
<div id="layout-splitter-demo-1" class="demo-canvas demo-canvas--1">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><LayoutSplitter size="40" orientation="horizontal" minSize="20" step="5" label="Resize panels" class="showcase-instance showcase-instance--1" /></div>
</div>
<section class="demo-code" aria-label="Drag or arrow the divider usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;LayoutSplitter
size="40"
minSize="20"&gt;
&lt;div data-slot="start"&gt;
&lt;div class="showcase-slot"&gt;start slot&lt;/div&gt;
&lt;/div&gt;
&lt;div data-slot="end"&gt;
&lt;div class="showcase-slot"&gt;end slot&lt;/div&gt;
&lt;/div&gt;
&lt;/LayoutSplitter&gt;</code></pre>
</section>
</div>
</article>
<article class="demo-case">
<header class="demo-case-header">
<div><span class="demo-case-eyebrow">Advanced</span><h2>Stacked panes</h2><p>Vertical orientation splits top from bottom and switches the arrow keys to up and down. Below the small breakpoint both orientations stack, because two panes side by side stop making sense on a phone.</p></div>
<span class="demo-case-number">02</span>
</header>
<div class="demo-workbench">
<div id="layout-splitter-demo-2" class="demo-canvas demo-canvas--2">
<div class="demo-browser-chrome"><span></span><span></span><span></span><small>Live preview</small></div>
<div class="demo-render"><LayoutSplitter size="60" orientation="vertical" minSize="25" step="10" label="Resize rows" class="showcase-instance showcase-instance--2" /></div>
</div>
<section class="demo-code" aria-label="Stacked panes usage">
<header><span><span class="icon-[lucide--code-2] size-4" aria-hidden="true"></span>Component usage</span><small>.wrn</small></header>
<pre><code>&lt;LayoutSplitter
size="60"
orientation="vertical"
minSize="25"
step="10"
label="Resize rows"&gt;
&lt;div data-slot="start"&gt;
&lt;div class="showcase-slot"&gt;start slot&lt;/div&gt;
&lt;/div&gt;
&lt;div data-slot="end"&gt;
&lt;div class="showcase-slot"&gt;end slot&lt;/div&gt;
&lt;/div&gt;
&lt;/LayoutSplitter&gt;</code></pre>
</section>
</div>
</article></div></section>
<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component outputs</span><h2>Receive every typed component output</h2><p>Use declarative output handlers in <code>.wrn</code> files or register a direct output handler from JavaScript. The canonical API exposes <code>payload</code> and does not require <code>event.detail</code>.</p></div><div class="detail-events-grid"><div class="detail-events-list"><div><code>@resize</code><span>Fires when the component emits the resize event.</span></div></div><div class="detail-event-examples"><section class="demo-code"><header><span><span class="icon-[lucide--braces] size-4" aria-hidden="true"></span>Declarative handlers</span><small>.wrn</small></header><pre><code>&lt;LayoutSplitter
@resize='console.log(payload)'
/&gt;</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Direct output handlers</span><small>.js</small></header><pre><code>import <span>&#123;</span> registerOutputHandler <span>&#125;</span> from "@wrnexus/csr/outputs"
const component = document.querySelector("[data-ui-component=\"LayoutSplitter\"], [data-component=\"LayoutSplitter\"]")
if (component) registerOutputHandler(component, "resize", (payload) =&gt; <span>&#123;</span>
console.log("resize", payload)
<span>&#125;</span>)</code></pre></section></div></div></section>
<section id="api" class="detail-section"><div class="detail-section-heading"><span>Component API</span><h2>Props and configuration</h2><p>All content and behavior shown above is supplied through these props and slots.</p></div><div class="docs-table-wrap"><table class="docs-table"><thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Required</th></tr></thead><tbody><tr><td><code>color</code></td><td>string</td><td><code>"primary"</code></td><td>No</td></tr><tr><td><code>size</code></td><td>number</td><td><code>50</code></td><td>No</td></tr><tr><td><code>orientation</code></td><td>string</td><td><code>"horizontal"</code></td><td>No</td></tr><tr><td><code>minSize</code></td><td>number</td><td><code>15</code></td><td>No</td></tr><tr><td><code>step</code></td><td>number</td><td><code>5</code></td><td>No</td></tr><tr><td><code>label</code></td><td>string</td><td><code>"Resize panels"</code></td><td>No</td></tr><tr><td><code>class</code></td><td>string</td><td><code>""</code></td><td>No</td></tr></tbody></table></div></section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a><a href="#layout-splitter-demo-1">Drag or arrow the divider</a><a href="#layout-splitter-demo-2">Stacked panes</a><a href="#events">Outputs</a><a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
<a href="/components/kbd"><small>Previous</small><strong>← Kbd</strong></a>
<a href="/components/link"><small>Next</small><strong>Link →</strong></a>
</nav>
}
}