refactor: migrate legacy wire namespace to wrn
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-12 18:51:15 +05:30
parent ec23dd4c93
commit 2c960fc1dc
488 changed files with 27306 additions and 19063 deletions
@@ -4,7 +4,7 @@
**Goal:** Ship runtime-owned roving arrow-key focus, then build Nav, Pagination and Stepper as real components on that foundation.
**Architecture:** One declarative attribute pair (`data-wrn-roving` on a container, `data-wrn-roving-item` on children) implements ARIA roving focus once in the reactive runtime, the same way modal dialog focus already lives there. The three components then declare intent with a single attribute instead of carrying five near-identical `@keydown` handlers. Each component uses a local `style {}` block with `wire-*` classes, declares its `outputs`, and takes props in / reports outputs out with no global state.
**Architecture:** One declarative attribute pair (`data-wrn-roving` on a container, `data-wrn-roving-item` on children) implements ARIA roving focus once in the reactive runtime, the same way modal dialog focus already lives there. The three components then declare intent with a single attribute instead of carrying five near-identical `@keydown` handlers. Each component uses a local `style {}` block with `wrn-*` classes, declares its `outputs`, and takes props in / reports outputs out with no global state.
**Tech Stack:** Bun, TypeScript, `.wrn` components, happy-dom for runtime tests, `@wrnexus/test` (`renderComponent`, `mountHtml`) for component tests.
@@ -456,10 +456,10 @@ test("pagination renders windowed page numbers and emits change", async () => {
siblingCount: 1,
});
const dom = mountHtml(html);
const root = dom.querySelector(".wire-pagination") as HTMLElement;
const root = dom.querySelector(".wrn-pagination") as HTMLElement;
expect(root.getAttribute("role")).toBe("navigation");
const current = dom.querySelector('.wire-pagination__page[aria-current="page"]');
const current = dom.querySelector('.wrn-pagination__page[aria-current="page"]');
expect(current!.textContent!.trim()).toBe("5");
expect(root.textContent).toContain("41");
expect(root.textContent).toContain("50");
@@ -478,7 +478,7 @@ test("pagination survives a non-array or empty dataset", async () => {
const source = readFileSync(uiComponentPath("Pagination"), "utf8");
const html = await renderComponent(source, { page: 1, pageSize: 10, total: 0 });
const dom = mountHtml(html);
expect(dom.querySelector(".wire-pagination")).not.toBeNull();
expect(dom.querySelector(".wrn-pagination")).not.toBeNull();
});
```
@@ -488,7 +488,7 @@ test("pagination survives a non-array or empty dataset", async () => {
bun test packages/ui -t "pagination"
```
Expected: FAIL — the scaffold has no `.wire-pagination` class and no page numbers.
Expected: FAIL — the scaffold has no `.wrn-pagination` class and no page numbers.
- [ ] **Step 3: Replace `packages/ui/components/Pagination.wrn`**
@@ -594,33 +594,33 @@ component Pagination {
<nav
{...attrs}
data-ui-component="Pagination"
class='wire-pagination {class}'
class='wrn-pagination {class}'
data-variant='{variant}'
role="navigation"
aria-label='{label}'
>
<p class="wire-pagination__summary" data-show="showSummary">
<p class="wrn-pagination__summary" data-show="showSummary">
{firstShown()} to {lastShown()} of {total}
</p>
<div class="wire-pagination__controls">
<div class="wrn-pagination__controls">
<button
type="button"
class="wire-pagination__step"
class="wrn-pagination__step"
aria-label='{previousLabel}'
@click='goPrevious()'
>
<span class="wire-pagination__step-icon" aria-hidden="true">&#8249;</span>
<span class="wire-pagination__step-label">{previousLabel}</span>
<span class="wrn-pagination__step-icon" aria-hidden="true">&#8249;</span>
<span class="wrn-pagination__step-label">{previousLabel}</span>
</button>
<ol class="wire-pagination__pages" data-show="variant === 'numbered'">
<ol class="wrn-pagination__pages" data-show="variant === 'numbered'">
{#each pageNumbers() as entry}
<li>
<span class="wire-pagination__gap" data-show="entry.gap">{entry.label}</span>
<span class="wrn-pagination__gap" data-show="entry.gap">{entry.label}</span>
<button
type="button"
class="wire-pagination__page"
class="wrn-pagination__page"
data-show="!entry.gap"
data-active='{entry.value === currentPage()}'
aria-current='{entry.value === currentPage() ? "page" : "false"}'
@@ -632,18 +632,18 @@ component Pagination {
{/each}
</ol>
<p class="wire-pagination__compact" data-show="variant !== 'numbered'">
<p class="wrn-pagination__compact" data-show="variant !== 'numbered'">
{currentPage()} / {lastPage()}
</p>
<button
type="button"
class="wire-pagination__step"
class="wrn-pagination__step"
aria-label='{nextLabel}'
@click='goNext()'
>
<span class="wire-pagination__step-label">{nextLabel}</span>
<span class="wire-pagination__step-icon" aria-hidden="true">&#8250;</span>
<span class="wrn-pagination__step-label">{nextLabel}</span>
<span class="wrn-pagination__step-icon" aria-hidden="true">&#8250;</span>
</button>
</div>
@@ -652,7 +652,7 @@ component Pagination {
}
style {
.wire-pagination {
.wrn-pagination {
display: flex;
flex-wrap: wrap;
align-items: center;
@@ -661,20 +661,20 @@ component Pagination {
max-width: 100%;
}
.wire-pagination__summary {
.wrn-pagination__summary {
margin: 0;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
font-size: 0.82rem;
}
.wire-pagination__controls {
.wrn-pagination__controls {
display: flex;
align-items: center;
gap: 0.35rem;
flex-wrap: wrap;
}
.wire-pagination__pages {
.wrn-pagination__pages {
display: flex;
align-items: center;
gap: 0.25rem;
@@ -683,8 +683,8 @@ component Pagination {
list-style: none;
}
.wire-pagination__page,
.wire-pagination__step {
.wrn-pagination__page,
.wrn-pagination__step {
appearance: none;
display: inline-flex;
align-items: center;
@@ -692,50 +692,50 @@ component Pagination {
min-width: 2.25rem;
justify-content: center;
padding: 0.4rem 0.6rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface);
color: var(--wire-color-text);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
background: var(--wrn-color-surface);
color: var(--wrn-color-text);
font: inherit;
font-size: 0.85rem;
cursor: pointer;
}
.wire-pagination__page:hover,
.wire-pagination__step:hover {
background: var(--wire-color-surface-soft);
.wrn-pagination__page:hover,
.wrn-pagination__step:hover {
background: var(--wrn-color-surface-soft);
}
.wire-pagination__page[data-active="true"] {
border-color: var(--wire-color-primary);
background: var(--wire-color-primary);
color: var(--wire-color-primary-contrast);
.wrn-pagination__page[data-active="true"] {
border-color: var(--wrn-color-primary);
background: var(--wrn-color-primary);
color: var(--wrn-color-primary-contrast);
font-weight: 700;
}
.wire-pagination__gap {
.wrn-pagination__gap {
padding: 0 0.35rem;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
}
.wire-pagination__compact {
.wrn-pagination__compact {
margin: 0;
padding: 0 0.5rem;
color: var(--wire-color-text-muted);
color: var(--wrn-color-text-muted);
font-size: 0.85rem;
}
/* Below the small breakpoint the word labels crowd the arrows out. */
@media (max-width: 639px) {
.wire-pagination {
.wrn-pagination {
justify-content: center;
}
.wire-pagination__step-label {
.wrn-pagination__step-label {
display: none;
}
.wire-pagination__summary {
.wrn-pagination__summary {
width: 100%;
text-align: center;
}
@@ -816,14 +816,14 @@ test("stepper marks complete, current and upcoming steps", async () => {
active: 1,
});
const dom = mountHtml(html);
const items = [...dom.querySelectorAll(".wire-stepper__step")];
const items = [...dom.querySelectorAll(".wrn-stepper__step")];
expect(items).toHaveLength(3);
expect(items[0].getAttribute("data-status")).toBe("complete");
expect(items[1].getAttribute("data-status")).toBe("current");
expect(items[2].getAttribute("data-status")).toBe("upcoming");
expect(items[1].getAttribute("aria-current")).toBe("step");
expect(dom.querySelector(".wire-stepper")!.tagName.toLowerCase()).toBe("ol");
expect(dom.querySelector(".wrn-stepper")!.tagName.toLowerCase()).toBe("ol");
});
test("stepper renders vertically and clamps an out-of-range active index", async () => {
@@ -834,9 +834,9 @@ test("stepper renders vertically and clamps an out-of-range active index", async
orientation: "vertical",
});
const dom = mountHtml(html);
const root = dom.querySelector(".wire-stepper") as HTMLElement;
const root = dom.querySelector(".wrn-stepper") as HTMLElement;
expect(root.getAttribute("data-orientation")).toBe("vertical");
const items = [...dom.querySelectorAll(".wire-stepper__step")];
const items = [...dom.querySelectorAll(".wrn-stepper__step")];
expect(items[1].getAttribute("data-status")).toBe("current");
});
@@ -859,7 +859,7 @@ test("clickable stepper opts into roving focus", async () => {
bun test packages/ui -t "stepper"
```
Expected: FAIL — the scaffold renders a `<nav>` of anchors with no `.wire-stepper__step`.
Expected: FAIL — the scaffold renders a `<nav>` of anchors with no `.wrn-stepper__step`.
- [ ] **Step 3: Replace `packages/ui/components/Stepper.wrn`**
@@ -927,7 +927,7 @@ component Stepper {
<ol
{...attrs}
data-ui-component="Stepper"
class='wire-stepper {class}'
class='wrn-stepper {class}'
data-orientation='{orientation}'
data-clickable='{clickable}'
data-wrn-roving='{clickable ? (orientation === "vertical" ? "vertical" : "horizontal") : ""}'
@@ -935,28 +935,28 @@ component Stepper {
>
{#each stepList() as step, index}
<li
class="wire-stepper__step"
class="wrn-stepper__step"
data-status='{statusFor(index)}'
aria-current='{statusFor(index) === "current" ? "step" : "false"}'
>
<button
type="button"
class="wire-stepper__button"
class="wrn-stepper__button"
data-wrn-roving-item='{clickable}'
@click='selectStep(index, step)'
>
<span class="wire-stepper__marker" aria-hidden="true">
<span class='wire-stepper__icon {step.icon}' data-show="step.icon"></span>
<span class="wire-stepper__number" data-show="!step.icon">{index + 1}</span>
<span class="wrn-stepper__marker" aria-hidden="true">
<span class='wrn-stepper__icon {step.icon}' data-show="step.icon"></span>
<span class="wrn-stepper__number" data-show="!step.icon">{index + 1}</span>
</span>
<span class="wire-stepper__body">
<span class="wire-stepper__label">{step.label}</span>
<span class="wire-stepper__description" data-show="step.description">
<span class="wrn-stepper__body">
<span class="wrn-stepper__label">{step.label}</span>
<span class="wrn-stepper__description" data-show="step.description">
{step.description}
</span>
</span>
</button>
<span class="wire-stepper__custom">
<span class="wrn-stepper__custom">
<slot name="step-{index}"></slot>
</span>
</li>
@@ -966,7 +966,7 @@ component Stepper {
}
style {
.wire-stepper {
.wrn-stepper {
display: flex;
gap: 0.5rem;
margin: 0;
@@ -975,11 +975,11 @@ component Stepper {
max-width: 100%;
}
.wire-stepper[data-orientation="vertical"] {
.wrn-stepper[data-orientation="vertical"] {
flex-direction: column;
}
.wire-stepper__step {
.wrn-stepper__step {
display: flex;
flex-direction: column;
flex: 1 1 0;
@@ -987,7 +987,7 @@ component Stepper {
gap: 0.35rem;
}
.wire-stepper__button {
.wrn-stepper__button {
appearance: none;
display: flex;
align-items: center;
@@ -995,7 +995,7 @@ component Stepper {
width: 100%;
padding: 0.5rem;
border: 0;
border-radius: var(--wire-radius-sm);
border-radius: var(--wrn-radius-sm);
background: transparent;
color: inherit;
font: inherit;
@@ -1003,67 +1003,67 @@ component Stepper {
cursor: default;
}
.wire-stepper[data-clickable="true"] .wire-stepper__button {
.wrn-stepper[data-clickable="true"] .wrn-stepper__button {
cursor: pointer;
}
.wire-stepper[data-clickable="true"] .wire-stepper__button:hover {
background: var(--wire-color-surface-soft);
.wrn-stepper[data-clickable="true"] .wrn-stepper__button:hover {
background: var(--wrn-color-surface-soft);
}
.wire-stepper__marker {
.wrn-stepper__marker {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 2rem;
height: 2rem;
border: 1px solid var(--wire-color-border);
border: 1px solid var(--wrn-color-border);
border-radius: 999px;
background: var(--wire-color-surface);
background: var(--wrn-color-surface);
font-size: 0.85rem;
font-weight: 700;
}
.wire-stepper__step[data-status="complete"] .wire-stepper__marker {
border-color: var(--wire-color-primary);
background: var(--wire-color-primary);
color: var(--wire-color-primary-contrast);
.wrn-stepper__step[data-status="complete"] .wrn-stepper__marker {
border-color: var(--wrn-color-primary);
background: var(--wrn-color-primary);
color: var(--wrn-color-primary-contrast);
}
.wire-stepper__step[data-status="current"] .wire-stepper__marker {
border-color: var(--wire-color-primary);
color: var(--wire-color-primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--wire-color-primary) 22%, transparent);
.wrn-stepper__step[data-status="current"] .wrn-stepper__marker {
border-color: var(--wrn-color-primary);
color: var(--wrn-color-primary);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--wrn-color-primary) 22%, transparent);
}
.wire-stepper__step[data-status="upcoming"] .wire-stepper__marker {
color: var(--wire-color-text-muted);
.wrn-stepper__step[data-status="upcoming"] .wrn-stepper__marker {
color: var(--wrn-color-text-muted);
}
.wire-stepper__body {
.wrn-stepper__body {
display: flex;
flex-direction: column;
min-width: 0;
}
.wire-stepper__label {
.wrn-stepper__label {
font-size: 0.9rem;
font-weight: 600;
}
.wire-stepper__description {
color: var(--wire-color-text-muted);
.wrn-stepper__description {
color: var(--wrn-color-text-muted);
font-size: 0.78rem;
}
.wire-stepper__step[data-status="upcoming"] .wire-stepper__label {
color: var(--wire-color-text-muted);
.wrn-stepper__step[data-status="upcoming"] .wrn-stepper__label {
color: var(--wrn-color-text-muted);
}
/* A horizontal stepper cannot stay side by side on a phone. */
@media (max-width: 639px) {
.wire-stepper {
.wrn-stepper {
flex-direction: column;
}
}
@@ -1163,11 +1163,11 @@ test("nav renders links, marks the active one, and shows icons and badges", asyn
});
const dom = mountHtml(html);
expect(dom.querySelector(".wire-nav")!.getAttribute("role")).toBe("navigation");
expect(dom.querySelector(".wrn-nav")!.getAttribute("role")).toBe("navigation");
const current = dom.querySelector('[aria-current="page"]');
expect(current!.textContent).toContain("Inbox");
expect(dom.querySelector(".wire-nav__badge")!.textContent!.trim()).toBe("9");
expect(dom.querySelector(".wire-nav__icon")).not.toBeNull();
expect(dom.querySelector(".wrn-nav__badge")!.textContent!.trim()).toBe("9");
expect(dom.querySelector(".wrn-nav__icon")).not.toBeNull();
expect(dom.querySelector('[aria-disabled="true"]')).not.toBeNull();
});
@@ -1194,10 +1194,10 @@ test("nav renders a submenu with a disclosure arrow and opts into roving focus",
expect(dom.querySelector("[data-wrn-roving]")).not.toBeNull();
expect(dom.querySelectorAll("[data-wrn-roving-item]").length).toBeGreaterThan(0);
expect(dom.querySelector(".wire-nav__arrow")).not.toBeNull();
expect(dom.querySelector(".wire-nav__submenu")).not.toBeNull();
expect(dom.querySelector(".wrn-nav__arrow")).not.toBeNull();
expect(dom.querySelector(".wrn-nav__submenu")).not.toBeNull();
// Third level is rendered, and nothing deeper is attempted.
expect(dom.querySelector(".wire-nav__submenu--level3")).not.toBeNull();
expect(dom.querySelector(".wrn-nav__submenu--level3")).not.toBeNull();
expect(dom.body.innerHTML).toContain("Deep");
});
@@ -1205,8 +1205,8 @@ test("nav renders empty rather than throwing when items is not an array", async
const source = readFileSync(uiComponentPath("Nav"), "utf8");
const html = await renderComponent(source, { items: "not-an-array", active: "" });
const dom = mountHtml(html);
expect(dom.querySelector(".wire-nav")).not.toBeNull();
expect(dom.querySelectorAll(".wire-nav__link").length).toBe(0);
expect(dom.querySelector(".wrn-nav")).not.toBeNull();
expect(dom.querySelectorAll(".wrn-nav__link").length).toBe(0);
});
```
@@ -1216,7 +1216,7 @@ test("nav renders empty rather than throwing when items is not an array", async
bun test packages/ui -t "nav "
```
Expected: FAIL — the scaffold renders bare anchors with no `.wire-nav__link`, no submenu and no roving attributes.
Expected: FAIL — the scaffold renders bare anchors with no `.wrn-nav__link`, no submenu and no roving attributes.
- [ ] **Step 3: Replace `packages/ui/components/Nav.wrn`**
@@ -1274,7 +1274,7 @@ component Nav {
<nav
{...attrs}
data-ui-component="Nav"
class='wire-nav {class}'
class='wrn-nav {class}'
data-orientation='{orientation}'
data-expanded='{expanded}'
role="navigation"
@@ -1282,24 +1282,24 @@ component Nav {
>
<button
type="button"
class="wire-nav__toggle"
class="wrn-nav__toggle"
data-show="collapsible"
aria-expanded='{expanded}'
aria-label='{toggleLabel}'
@click='toggleMenu()'
>
<span class="wire-nav__toggle-bar" aria-hidden="true"></span>
<span class="wire-nav__toggle-text">{toggleLabel}</span>
<span class="wrn-nav__toggle-bar" aria-hidden="true"></span>
<span class="wrn-nav__toggle-text">{toggleLabel}</span>
</button>
<ul
class="wire-nav__list"
class="wrn-nav__list"
data-wrn-roving='{orientation === "vertical" ? "vertical" : "horizontal"}'
>
{#each itemList() as item}
<li class="wire-nav__item" data-has-children='{childrenOf(item).length > 0}'>
<li class="wrn-nav__item" data-has-children='{childrenOf(item).length > 0}'>
<a
class="wire-nav__link"
class="wrn-nav__link"
href='{item.href || "#"}'
data-wrn-roving-item="true"
data-active='{isActive(item)}'
@@ -1307,53 +1307,53 @@ component Nav {
aria-disabled='{item.disabled ? "true" : "false"}'
@click='choose(item)'
>
<span class='wire-nav__icon {item.icon}' data-show="item.icon" aria-hidden="true"></span>
<span class="wire-nav__label">{item.label}</span>
<span class="wire-nav__badge" data-show="item.badge">{item.badge}</span>
<span class='wrn-nav__icon {item.icon}' data-show="item.icon" aria-hidden="true"></span>
<span class="wrn-nav__label">{item.label}</span>
<span class="wrn-nav__badge" data-show="item.badge">{item.badge}</span>
<span
class="wire-nav__arrow"
class="wrn-nav__arrow"
data-show="childrenOf(item).length > 0"
aria-hidden="true"
>&#8250;</span>
</a>
<ul class="wire-nav__submenu" data-show="childrenOf(item).length > 0">
<ul class="wrn-nav__submenu" data-show="childrenOf(item).length > 0">
{#each childrenOf(item) as child}
<li class="wire-nav__item" data-has-children='{childrenOf(child).length > 0}'>
<li class="wrn-nav__item" data-has-children='{childrenOf(child).length > 0}'>
<a
class="wire-nav__link"
class="wrn-nav__link"
href='{child.href || "#"}'
data-active='{isActive(child)}'
aria-current='{isActive(child) ? "page" : "false"}'
aria-disabled='{child.disabled ? "true" : "false"}'
@click='choose(child)'
>
<span class='wire-nav__icon {child.icon}' data-show="child.icon" aria-hidden="true"></span>
<span class="wire-nav__label">{child.label}</span>
<span class="wire-nav__badge" data-show="child.badge">{child.badge}</span>
<span class='wrn-nav__icon {child.icon}' data-show="child.icon" aria-hidden="true"></span>
<span class="wrn-nav__label">{child.label}</span>
<span class="wrn-nav__badge" data-show="child.badge">{child.badge}</span>
<span
class="wire-nav__arrow"
class="wrn-nav__arrow"
data-show="childrenOf(child).length > 0"
aria-hidden="true"
>&#8250;</span>
</a>
<ul
class="wire-nav__submenu wire-nav__submenu--level3"
class="wrn-nav__submenu wrn-nav__submenu--level3"
data-show="childrenOf(child).length > 0"
>
{#each childrenOf(child) as leaf}
<li class="wire-nav__item">
<li class="wrn-nav__item">
<a
class="wire-nav__link"
class="wrn-nav__link"
href='{leaf.href || "#"}'
data-active='{isActive(leaf)}'
aria-current='{isActive(leaf) ? "page" : "false"}'
aria-disabled='{leaf.disabled ? "true" : "false"}'
@click='choose(leaf)'
>
<span class='wire-nav__icon {leaf.icon}' data-show="leaf.icon" aria-hidden="true"></span>
<span class="wire-nav__label">{leaf.label}</span>
<span class='wrn-nav__icon {leaf.icon}' data-show="leaf.icon" aria-hidden="true"></span>
<span class="wrn-nav__label">{leaf.label}</span>
</a>
</li>
{/each}
@@ -1370,11 +1370,11 @@ component Nav {
}
style {
.wire-nav {
.wrn-nav {
max-width: 100%;
}
.wire-nav__list {
.wrn-nav__list {
display: flex;
align-items: center;
gap: 0.25rem;
@@ -1383,61 +1383,61 @@ component Nav {
list-style: none;
}
.wire-nav[data-orientation="vertical"] .wire-nav__list {
.wrn-nav[data-orientation="vertical"] .wrn-nav__list {
flex-direction: column;
align-items: stretch;
}
.wire-nav__item {
.wrn-nav__item {
position: relative;
}
.wire-nav__link {
.wrn-nav__link {
display: flex;
align-items: center;
gap: 0.45rem;
padding: 0.45rem 0.7rem;
border-radius: var(--wire-radius-sm);
color: var(--wire-color-text-muted);
border-radius: var(--wrn-radius-sm);
color: var(--wrn-color-text-muted);
font-size: 0.9rem;
font-weight: 600;
text-decoration: none;
}
.wire-nav__link:hover {
background: var(--wire-color-surface-soft);
color: var(--wire-color-text);
.wrn-nav__link:hover {
background: var(--wrn-color-surface-soft);
color: var(--wrn-color-text);
}
.wire-nav__link[data-active="true"] {
background: var(--wire-color-primary-soft);
color: var(--wire-color-primary);
.wrn-nav__link[data-active="true"] {
background: var(--wrn-color-primary-soft);
color: var(--wrn-color-primary);
}
.wire-nav__link[aria-disabled="true"] {
.wrn-nav__link[aria-disabled="true"] {
opacity: 0.5;
pointer-events: none;
}
.wire-nav__badge {
.wrn-nav__badge {
padding: 0.05rem 0.4rem;
border-radius: 999px;
background: var(--wire-color-primary-soft);
color: var(--wire-color-primary);
background: var(--wrn-color-primary-soft);
color: var(--wrn-color-primary);
font-size: 0.72rem;
}
.wire-nav__arrow {
.wrn-nav__arrow {
display: inline-block;
transition: transform 160ms ease;
}
.wire-nav__item:hover > .wire-nav__link > .wire-nav__arrow,
.wire-nav__item:focus-within > .wire-nav__link > .wire-nav__arrow {
.wrn-nav__item:hover > .wrn-nav__link > .wrn-nav__arrow,
.wrn-nav__item:focus-within > .wrn-nav__link > .wrn-nav__arrow {
transform: rotate(90deg);
}
.wire-nav__submenu {
.wrn-nav__submenu {
position: absolute;
z-index: 30;
top: 100%;
@@ -1445,42 +1445,42 @@ component Nav {
min-width: 12rem;
margin: 0;
padding: 0.35rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-md);
background: var(--wire-color-surface);
box-shadow: var(--wire-shadow-1);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-md);
background: var(--wrn-color-surface);
box-shadow: var(--wrn-shadow-1);
list-style: none;
opacity: 0;
visibility: hidden;
transition: opacity 160ms ease;
}
.wire-nav__item:hover > .wire-nav__submenu,
.wire-nav__item:focus-within > .wire-nav__submenu {
.wrn-nav__item:hover > .wrn-nav__submenu,
.wrn-nav__item:focus-within > .wrn-nav__submenu {
opacity: 1;
visibility: visible;
}
.wire-nav__submenu--level3 {
.wrn-nav__submenu--level3 {
top: 0;
left: 100%;
}
.wire-nav__toggle {
.wrn-nav__toggle {
display: none;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.7rem;
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius-sm);
background: var(--wire-color-surface);
color: var(--wire-color-text);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius-sm);
background: var(--wrn-color-surface);
color: var(--wrn-color-text);
font: inherit;
font-size: 0.9rem;
cursor: pointer;
}
.wire-nav__toggle-bar {
.wrn-nav__toggle-bar {
width: 1rem;
height: 2px;
background: currentColor;
@@ -1493,21 +1493,21 @@ component Nav {
* unreachable on touch.
*/
@media (max-width: 767px) {
.wire-nav__toggle {
.wrn-nav__toggle {
display: inline-flex;
}
.wire-nav__list {
.wrn-nav__list {
flex-direction: column;
align-items: stretch;
}
.wire-nav[data-expanded="false"] .wire-nav__list {
.wrn-nav[data-expanded="false"] .wrn-nav__list {
display: none;
}
.wire-nav__submenu,
.wire-nav__submenu--level3 {
.wrn-nav__submenu,
.wrn-nav__submenu--level3 {
position: static;
opacity: 1;
visibility: visible;
@@ -1656,7 +1656,7 @@ git push
## Self-Review
**Spec coverage for phase 1.** Roving focus in the runtime — Task 1. Nav multi-level to depth 3, icons, badges, animated arrows, responsive collapse, `aria-current` — Task 5. Pagination standalone with compact and numbered variants and windowed numbers — Task 3. Stepper horizontal/vertical with indexed named slots and `aria-current="step"` — Task 4. Local `style {}` blocks with `wire-*` classes — Tasks 3, 4, 5. Error handling for non-array `items` and out-of-range `active`/`page` — covered by a test in each of Tasks 3, 4, 5. Showcase profiles and regeneration — Tasks 36. Runtime budget check — Tasks 1 and 6.
**Spec coverage for phase 1.** Roving focus in the runtime — Task 1. Nav multi-level to depth 3, icons, badges, animated arrows, responsive collapse, `aria-current` — Task 5. Pagination standalone with compact and numbered variants and windowed numbers — Task 3. Stepper horizontal/vertical with indexed named slots and `aria-current="step"` — Task 4. Local `style {}` blocks with `wrn-*` classes — Tasks 3, 4, 5. Error handling for non-array `items` and out-of-range `active`/`page` — covered by a test in each of Tasks 3, 4, 5. Showcase profiles and regeneration — Tasks 36. Runtime budget check — Tasks 1 and 6.
Out of phase 1 by design, carried to later plans: Tabs, Sidebar, MegaMenu (phase 2); Navbar, Breadcrumb, Scrollspy, responsive sweep (phase 3).