feat(ui): build LayoutSplitter and CustomScrollbar for real
Quality / quality (ubuntu-latest) (push) Failing after 6m8s
Quality / quality (windows-latest) (push) Canceled after 0s

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>
This commit is contained in:
2026-08-08 11:22:45 +05:30
co-authored by Claude Opus 5
parent b3a4d80df3
commit b56cb8cba5
15 changed files with 794 additions and 184 deletions
@@ -10,7 +10,7 @@ page LayoutShowcase {
<span class="showcase-eyebrow">Component category</span>
<h1 class="showcase-title">Layout</h1>
<p class="showcase-description">15 unique, responsive, theme-aware components. Open a component to inspect multiple live configurations and its complete props API.</p>
<div class="category-meta"><span>15 components</span><span>Multiple use cases</span><span>45 live configurations</span></div>
<div class="category-meta"><span>15 components</span><span>Multiple use cases</span><span>43 live configurations</span></div>
</header>
<div class="catalog-grid">
<article class="catalog-card" data-interactive-preview="false">
@@ -36,11 +36,11 @@ page LayoutShowcase {
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><CustomScrollbar size="default" columns="2" class="showcase-instance showcase-instance--1" /></div>
<div class="catalog-card-stage"><CustomScrollbar size="default" axis="vertical" thickness="8" maxHeight="14rem" radius="999px" class="showcase-instance showcase-instance--1"><div class="showcase-scroll-demo"><p>Line 1. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 2. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 3. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 4. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 5. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 6. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 7. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 8. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 9. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 10. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 11. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p><p>Line 12. A scrolling region only shows its scrollbar when the content actually overflows, so this demo ships enough of it.</p></div></CustomScrollbar></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Layout</span><h2>Custom Scrollbar</h2><p>Theme-aware, responsive custom scrollbar component.</p></div>
<div class="catalog-card-footer"><span>6 props · 1 slots</span><a href="/components/custom-scrollbar">Explore component <span aria-hidden="true">→</span></a></div>
<div class="catalog-card-footer"><span>7 props · 1 slots</span><a href="/components/custom-scrollbar">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">
@@ -106,11 +106,11 @@ page LayoutShowcase {
<article class="catalog-card" data-interactive-preview="false">
<div class="catalog-card-preview">
<div class="catalog-card-glow" aria-hidden="true"></div>
<div class="catalog-card-stage"><LayoutSplitter size="default" columns="2" class="showcase-instance showcase-instance--1" /></div>
<div class="catalog-card-stage"><LayoutSplitter size="40" orientation="horizontal" minSize="20" step="5" label="Resize panels" class="showcase-instance showcase-instance--1" /></div>
</div>
<div class="catalog-card-body">
<div><span class="catalog-card-category">Layout</span><h2>Layout Splitter</h2><p>Theme-aware, responsive layout splitter component.</p></div>
<div class="catalog-card-footer"><span>6 props · 1 slots</span><a href="/components/layout-splitter">Explore component <span aria-hidden="true">→</span></a></div>
<div class="catalog-card-footer"><span>7 props · 3 slots</span><a href="/components/layout-splitter">Explore component <span aria-hidden="true">→</span></a></div>
</div>
</article>
<article class="catalog-card" data-interactive-preview="false">