release: WRNexusJS 0.6.0

This commit is contained in:
2026-08-01 01:09:58 +05:30
parent 3e565e8d03
commit 687d345882
502 changed files with 33038 additions and 11358 deletions
@@ -7,7 +7,6 @@ import { eventDescriptionFor, profileFor } from "./showcase-profiles.mjs";
const exampleRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const workspaceRoot = join(exampleRoot, "..", "..");
const referencePath = join(workspaceRoot, "packages", "ui", "component-reference.json");
const uiComponentsPath = join(workspaceRoot, "packages", "ui", "components");
const pagesDir = join(exampleRoot, "app", "pages");
const detailPagesDir = join(pagesDir, "components");
const layoutsDir = join(exampleRoot, "app", "layouts");
@@ -74,7 +73,6 @@ const escapeText = (value) =>
const escapeCode = (value) => escapeText(value).replaceAll("{", "{").replaceAll("}", "}");
const escapeAttribute = (value) =>
escapeText(value).replaceAll('"', """).replaceAll("'", "'");
const jsonAttribute = (value) => escapeAttribute(JSON.stringify(value));
function sampleItem(component, index, variation) {
const number = index + 1;
@@ -532,9 +530,7 @@ function sourceSnippet(component, variation, overrides = {}) {
const content = custom ?? fallback;
if (!content) return [];
if (slot === "default") return [indentSource(content, 2)];
return [
` <div data-slot="${slot}">\n${indentSource(content, 4)}\n </div>`,
];
return [` <div data-slot="${slot}">\n${indentSource(content, 4)}\n </div>`];
});
const slotMarkup = slotParts.length ? `\n${slotParts.join("\n")}\n` : "";
@@ -614,9 +610,7 @@ function playgroundStates(component) {
}
function playgroundMount(component) {
const attributes = component.props.map(
(prop) => `${prop.name}='{${playgroundStateName(prop)}}'`,
);
const attributes = component.props.map((prop) => `${prop.name}='{${playgroundStateName(prop)}}'`);
const profileSlots = profileFor(component.name)?.demos?.[0]?.slots ?? {};
const slotMarkup = configuredSlotMarkup(component, 0, { __slots: profileSlots });
const publicTag = /^[A-Z][A-Za-z0-9_]*$/.test(component.mount);
@@ -682,7 +676,20 @@ const commonPlaygroundOptions = {
maxwidth: ["compact", "lg", "xl", "wide", "2xl", "full"],
method: ["get", "post"],
orientation: ["horizontal", "vertical"],
placement: ["top", "top-start", "top-end", "right", "right-start", "right-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end"],
placement: [
"top",
"top-start",
"top-end",
"right",
"right-start",
"right-end",
"bottom",
"bottom-start",
"bottom-end",
"left",
"left-start",
"left-end",
],
position: ["top", "right", "bottom", "left", "start", "end", "center"],
shape: ["round", "rounded", "pill"],
size: ["default", "xs", "sm", "md", "lg", "xl", "icon", "icon-xs", "icon-sm", "icon-lg"],
@@ -698,9 +705,7 @@ function playgroundOptions(prop, component, currentValue) {
const profileOptions = profileFor(component.name)?.options?.[prop.name] ?? [];
const common = commonPlaygroundOptions[prop.name.toLowerCase()] ?? [];
const componentSpecific =
component.name === "Button" && prop.name === "type"
? ["button", "submit", "reset"]
: [];
component.name === "Button" && prop.name === "type" ? ["button", "submit", "reset"] : [];
const declared = [...profileOptions, ...componentSpecific, ...common];
// Free-form strings such as icon names, labels, URLs, IDs, and descriptions
@@ -733,7 +738,7 @@ function playgroundSection(component) {
<pre><code data-playground-code>${sourceSnippet(component, 0)}</code></pre>
</section>
<div class="playground-status" data-playground-status aria-live="polite"></div>
${component.events.length ? `<div class="playground-event-log" data-playground-event-log aria-live="polite"><strong>Event output</strong><span>Interact with the preview to inspect <code>event.detail</code>.</span></div>` : ""}
${component.events.length ? `<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>${component.props.length} controls</strong></div><button type="reset"><span class="icon-[lucide--rotate-ccw] size-4" aria-hidden="true"></span>Reset</button></header>
@@ -1431,18 +1436,18 @@ function eventDocumentation(component) {
)
.join("");
const declarativeAttributes = component.events
.map((eventName) => ` @${eventName}='console.log(event.detail)'`)
.map((eventName) => ` @${eventName}='console.log(payload)'`)
.join("\n");
const declarativeCode = `<${component.mount}\n${declarativeAttributes}\n/>`;
const selector = `[data-ui-component="${component.name}"], [data-component="${component.mount}"]`;
const listenerCode = `const component = document.querySelector(${JSON.stringify(selector)})\n\n${component.events
const listenerCode = `import { registerOutputHandler } from "@wrnexus/csr/outputs"\n\nconst component = document.querySelector(${JSON.stringify(selector)})\n\n${component.events
.map(
(eventName) =>
`component?.addEventListener(${JSON.stringify(eventName)}, (event) => {\n console.log(${JSON.stringify(eventName)}, event.detail)\n})`,
`if (component) registerOutputHandler(component, ${JSON.stringify(eventName)}, (payload) => {\n console.log(${JSON.stringify(eventName)}, payload)\n})`,
)
.join("\n\n")}`;
return `<section id="events" class="detail-section"><div class="detail-section-heading"><span>Component events</span><h2>Respond to every public interaction</h2><p>Use declarative <code>@event</code> handlers in <code>.wrn</code> files or subscribe to the bubbling browser events from JavaScript.</p></div><div class="detail-events-grid"><div class="detail-events-list">${eventRows}</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>${escapeCode(declarativeCode)}</code></pre></section><section class="demo-code"><header><span><span class="icon-[lucide--radio] size-4" aria-hidden="true"></span>Browser listeners</span><small>.js</small></header><pre><code>${escapeCode(listenerCode)}</code></pre></section></div></div></section>`;
return `<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">${eventRows}</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>${escapeCode(declarativeCode)}</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>${escapeCode(listenerCode)}</code></pre></section></div></div></section>`;
}
function detailPage(component, categoryComponents) {
@@ -1532,7 +1537,7 @@ ${playgroundStates(component)}
<span class="showcase-eyebrow">${titleCase(component.category)} component</span>
<h1>${escapeText(displayName(component.name))}</h1>
<p>${escapeText(component.purpose)}</p>
<div class="detail-badges"><span>${component.props.length} props</span><span>${component.slots.length} slots</span><span>${component.events.length} events</span><span>Theme ready</span><span>Responsive</span></div>
<div class="detail-badges"><span>${component.props.length} props</span><span>${component.slots.length} slots</span><span>${component.events.length} 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>
@@ -1544,7 +1549,7 @@ ${playgroundStates(component)}
<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>${propsTable(component)}</section>
</main>
<aside class="detail-aside">
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a>${uses.map((useCase, variation) => `<a href="#${slugOf(component.name)}-demo-${variation + 1}">${escapeText(useCase.title)}</a>`).join("")}${component.events.length ? '<a href="#events">Events</a>' : ""}<a href="#api">Props API</a></div>
<div class="detail-toc"><strong>On this page</strong><a href="#playground">Playground</a>${uses.map((useCase, variation) => `<a href="#${slugOf(component.name)}-demo-${variation + 1}">${escapeText(useCase.title)}</a>`).join("")}${component.events.length ? '<a href="#events">Outputs</a>' : ""}<a href="#api">Props API</a></div>
</aside>
</div>
<nav class="detail-pagination">
@@ -1649,6 +1654,7 @@ layout Showcase {
<aside class="docs-directory" data-docs-directory>
<nav aria-label="Documentation directory">
<section class="docs-directory-group docs-directory-guides"><strong>Getting Started</strong><div><a href="/docs">Introduction</a><a href="/installation">Installation</a><a href="/framework-guides">Framework guides</a><a href="/accessibility">Accessibility</a><a href="/resources">Resources</a></div></section>
<section class="docs-directory-group docs-directory-guides"><strong>WRNexusJS v0.6</strong><div><a href="/v06-types">Types</a><a href="/v06-functions">Functions</a><a href="/v06-outputs">Outputs</a><a href="/v06-stores">Stores</a><a href="/v06-imports">Imports</a><a href="/v06-state">State</a><a href="/v06-runtime">Runtime</a><a href="/v06-migration">Migration</a></div></section>
<section class="docs-directory-group docs-directory-guides"><strong>Customization</strong><div><a href="/dark-mode">Dark Mode</a><a href="/themes">Themes <small>New</small></a><a href="/colors">Colors <small>New</small></a><a href="/fonts">Fonts</a><a href="/sizing">Sizing</a></div></section>
${directory}
</nav>
@@ -1839,6 +1845,148 @@ writeFileSync(
"utf8",
);
const v06Guides = [
[
"types",
"Types",
"TypeScript contracts for props, state, outputs, imported application types, and generated declarations.",
[
[
"Application types",
"Load ambient app/types/global.d.ts and explicitly import types from app/types/*.ts.",
],
[
"Typed contracts",
"Validate component props, state assignments, function parameters, returns, and output payloads.",
],
[
"Generated declarations",
"Generate component, output, store, layout, and server-call declarations under dist/types.",
],
],
],
[
"functions",
"Functions",
"Classify behavior for the browser, server, or both runtimes from one functions block.",
[
["Client", "Compile browser-only functions into CSP-safe client modules."],
[
"Server",
"Keep server-only functions out of browser artifacts and expose typed RPC calls when referenced.",
],
["Shared", "Emit deterministic shared helpers to both compilation targets."],
],
],
[
"outputs",
"Outputs",
"Use typed callable outputs for child-to-parent communication without application-level $emit or event.detail.",
[
["Declare", "Define zero- or one-payload output signatures in an outputs block."],
["Invoke", "Call output.name(payload) from client functions."],
["Consume", "Receive the typed payload local in parent component listeners."],
],
],
[
"stores",
"Stores",
"Create request-safe global stores and route-scoped page stores with typed actions and computed state.",
[
["Global stores", "Survive CSR navigation while remaining isolated for every SSR request."],
[
"Page stores",
"Share state with route descendants and dispose it when navigation leaves the route.",
],
["Persistence", "Persist include-only safe fields to memory, session, or local storage."],
],
],
[
"imports",
"Imports",
"Declare component, layout, store, package, and type dependencies explicitly while retaining compatibility mode.",
[
["Application imports", "Resolve @/ aliases to the active application directory."],
["Package imports", "Use named imports for components exported by @wrnexus/ui."],
[
"Compatibility",
"Run legacy, compatible, or explicit import modes during project migration.",
],
],
],
[
"state",
"State",
"Use individual or grouped typed state declarations with shared, client-only, and server-only scopes.",
[
[
"Shared state",
"Render during SSR and safely hydrate serializable values into the browser.",
],
["Client state", "Keep browser-only interaction state out of server artifacts."],
["Server state", "Keep sensitive request-only state out of hydration data."],
],
],
[
"runtime",
"Runtime",
"Split server and browser artifacts, hydrate only interactive components, and preserve compatible state during HMR.",
[
[
"Partial hydration",
"Load client code using load, idle, visible, interaction, media, or none strategies.",
],
[
"RPC",
"Validate browser-to-server inputs and outputs with same-origin and CSRF-aware defaults.",
],
["HMR", "Replace functions and preserve compatible component and store state."],
],
],
[
"migration",
"Migration",
"Upgrade older projects through idempotent v0.6 migrations with backups, dry runs, and review reports.",
[
[
"Legacy outputs",
"Convert @event declarations, $emit calls, and event.detail listeners to outputs and payload.",
],
[
"Explicit dependencies",
"Add component and layout imports only when resolution is unambiguous.",
],
[
"Safety",
"Parse and type-check changed files and roll back when migration validation fails.",
],
],
],
];
for (const [slug, title, description, sections] of v06Guides) {
writeFileSync(
join(pagesDir, `v06-${slug}.wrn`),
`// Generated by scripts/generate-showcase.mjs. Do not edit directly.
page V06${identifier(title)}Guide {
layout = "showcase"
seo {
title = "WRNexusJS v0.6 ${escapeAttribute(title)}"
description = "${escapeAttribute(description)}"
}
view {
<article class="page-docs guide-page">
<nav class="docs-breadcrumbs" aria-label="Breadcrumb"><a href="/docs">Docs</a><span aria-hidden="true">/</span><span>WRNexusJS v0.6</span><span aria-hidden="true">/</span><span>${escapeText(title)}</span></nav>
<header class="docs-intro"><span class="showcase-eyebrow">WRNexusJS v0.6</span><h1>${escapeText(title)}</h1><p>${escapeText(description)}</p></header>
${sections.map(([heading, copy]) => `<section class="guide-section"><h2>${escapeText(heading)}</h2><p>${escapeText(copy)}</p></section>`).join("")}
<nav class="guide-next"><a href="/framework-guides">Framework guides <span aria-hidden="true">→</span></a><a href="/v06-migration">Migration guide <span aria-hidden="true">→</span></a></nav>
</article>
}
}
`,
"utf8",
);
}
const customizationGuides = new Set(["dark-mode", "themes", "colors", "fonts", "sizing"]);
for (const [slug, title, description, code] of docsSections) {
const section = customizationGuides.has(slug) ? "Customization" : "Getting Started";