feat: sync showcase with WRNexusJS 0.8.72 and @wrnexus/ui 0.8.88

- Sync app, public, scripts, test and examples from the framework's
  component-showcase: redesigned component demos, marketing blocks under
  /blocks, template and preview galleries, distinct Timeline/Section
  variants, Chat Bubble receipts, a top-placement Drawer demo, and the
  playground code panel fix (objects in array props no longer blank out).
- Standalone adaptations kept: the generator and tests read
  component-reference.json from the published @wrnexus/ui, and tests use
  the published @wrnexus/compiler and @wrnexus/syntax.
- Dependencies: every @wrnexus/* package to its latest release (ui 0.8.88,
  styles 0.8.26, core 0.8.16, cli 0.8.72, ...), happy-dom for the
  playground tests, Tailwind 4.3; bun.lock and package-lock.json refreshed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClintchizandClaude Opus 5.5 committed 2026-09-30 16:50:22 +05:30
1 parent bec9ef2889
commit c0ac38c572
171 files changed
+21983 -35658

No files matched your search

+122 -17
View File
@@ -15,14 +15,29 @@
let request;
let timer;
// Preview props are refreshed by the server when a control changes. Avoid a
// second parent-to-child prop write during hydration, which reconstructs
// nested composite mounts without their server-rendered children.
function preservePreviewProps(preview) {
const root = Array.from(preview.children).find((node) => node.tagName !== "SCRIPT");
for (const attribute of Array.from(root?.attributes ?? [])) {
if (attribute.name.startsWith("data-wrn-prop-bind-")) root.removeAttribute(attribute.name);
}
}
document.querySelectorAll("[data-playground-preview]").forEach(preservePreviewProps);
function storedDesign() {
const root = document.documentElement.dataset;
const preference = (name) => {
const entry = document.cookie.split("; ").find((part) => part.startsWith(`wrn-ui-${name}=`));
return entry ? decodeURIComponent(entry.slice(entry.indexOf("=") + 1)) : "";
};
return {
style: root.uiStyle || DESIGN_DEFAULTS.style,
palette: root.uiPalette || DESIGN_DEFAULTS.palette,
mode: root.uiMode || DESIGN_DEFAULTS.mode,
font: root.uiFont || DESIGN_DEFAULTS.font,
scale: root.uiScale || DESIGN_DEFAULTS.scale,
style: preference("style") || root.uiStyle || DESIGN_DEFAULTS.style,
palette: preference("palette") || root.uiPalette || DESIGN_DEFAULTS.palette,
mode: preference("mode") || root.uiMode || DESIGN_DEFAULTS.mode,
font: preference("font") || root.uiFont || DESIGN_DEFAULTS.font,
scale: preference("scale") || root.uiScale || DESIGN_DEFAULTS.scale,
};
}
@@ -41,8 +56,24 @@
const dark =
settings.mode === "dark" ||
(settings.mode === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches);
root.dataset.theme = dark ? "dark" : "light";
if (window.wrnTheme) window.wrnTheme.set(dark ? "dark" : "light");
else {
const theme = dark ? "dark" : "light";
root.dataset.theme = theme;
const stylesheet = document.querySelector("link[data-wrnexus-theme]");
if (stylesheet) {
const href = stylesheet.getAttribute("href");
if (href)
stylesheet.setAttribute("href", href.replace(/(\/theme\/)[^/]+\//, `$1${theme}/`));
}
}
for (const button of document.querySelectorAll("[data-design-mode]")) {
button.setAttribute(
"aria-pressed",
String(button.dataset.designMode === (dark ? "dark" : "light")),
);
}
for (const control of document.querySelectorAll("[data-design-setting]")) {
control.value = settings[control.dataset.designSetting] || "";
}
@@ -52,16 +83,35 @@
setCookie("wrn-ui-mode", settings.mode);
setCookie("wrn-ui-font", settings.font);
setCookie("wrn-ui-scale", settings.scale);
setCookie("wire-theme", dark ? "dark" : "light");
setCookie("wrn-theme", dark ? "dark" : "light");
}
}
let design = storedDesign();
applyDesign(design, false);
for (const link of document.querySelectorAll(".docs-directory a[href]")) {
const target = new URL(link.href, window.location.origin);
if (target.pathname === window.location.pathname) link.setAttribute("aria-current", "page");
function syncDirectory() {
const directory = document.querySelector(".docs-directory");
if (!directory) return;
let active;
for (const link of directory.querySelectorAll("a[href]")) {
const matches =
new URL(link.href, window.location.origin).pathname.replace(/\/$/, "") ===
window.location.pathname.replace(/\/$/, "");
if (matches) {
link.setAttribute("aria-current", "page");
active = link;
} else link.removeAttribute("aria-current");
}
if (active) {
const bounds = directory.getBoundingClientRect();
const item = active.getBoundingClientRect();
if (item.top < bounds.top || item.bottom > bounds.bottom)
directory.scrollTop += item.top - bounds.top - directory.clientHeight / 2;
}
}
syncDirectory();
window.addEventListener("wrnexus:navigated", syncDirectory);
window.addEventListener("popstate", syncDirectory);
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
if (design.mode === "system") applyDesign(design);
});
@@ -83,6 +133,7 @@
const publicTag = playground.getAttribute("data-playground-public-tag") === "true";
const hasSlot = playground.getAttribute("data-playground-has-slot") === "true";
const attributes = [];
const declarations = [];
for (const field of form.elements) {
if (
@@ -96,6 +147,14 @@
continue;
const name = field.name.slice(3);
if (field.hasAttribute("data-playground-json")) {
try {
const data = JSON.parse(field.value);
declarations.push(` state ${name} = ${JSON.stringify(data, null, 2)}`);
attributes.push(`${name}="{${name}}"`);
continue;
} catch { /* Keep invalid JSON visible for correction. */ }
}
if (field instanceof HTMLInputElement && field.type === "checkbox") {
attributes.push(`${name}="${field.checked}"`);
} else if (field.value !== "") {
@@ -110,9 +169,33 @@
const mount = publicTag ? [] : [`data-component="${sourceAttribute(component)}"`];
const lines = [...mount, ...attributes].map((attribute) => ` ${attribute}`).join("\n");
const opening = lines ? `<${tag}\n${lines}` : `<${tag}`;
code.textContent = hasSlot
let source = hasSlot
? `${opening}>\n <!-- Add slot content here -->\n</${tag}>`
: `${opening}\n/>`;
if (declarations.length) source = `page ${component.replace(/[^A-Za-z0-9]/g, "")}Example {\n${declarations.join("\n")}\n view {\n${source}\n }\n}`;
writeCode(code, source);
}
/*
* The reactive runtime treats "{expr}" inside a text node as an
* interpolation, so a sample like { "label": "Mon" } written with
* textContent was evaluated and rendered as an empty string. Putting each
* brace in its own <span> means no single text node ever holds a {...}
* pair -- the same markup the generator uses for the server-rendered code.
*/
function writeCode(code, source) {
const fragment = document.createDocumentFragment();
for (const part of source.split(/([{}])/)) {
if (!part) continue;
if (part === "{" || part === "}") {
const brace = document.createElement("span");
brace.textContent = part;
fragment.append(brace);
} else {
fragment.append(document.createTextNode(part));
}
}
code.replaceChildren(fragment);
}
function valuesFrom(form) {
@@ -187,11 +270,16 @@
window.__wrnexusDisposeBehaviors?.(preview);
preview.replaceChildren(
...Array.from(
parsed.querySelectorAll('script[type="application/json"][data-wrn-controls]'),
(node) => document.importNode(node, true),
),
...Array.from(next.childNodes, (node) => document.importNode(node, true)),
);
preservePreviewProps(preview);
window.__wrnexusHydrateScopes?.(preview);
window.__wrnexusHydrateCsrFetches?.(preview);
window.wireTheme?.bind?.(document);
window.wrnTheme?.bind?.(document);
bindPlaygroundEvents(document);
status.textContent = "Preview updated";
} catch (error) {
@@ -235,15 +323,19 @@
}
if (byName.has(outputName)) return;
const registry = (host.__wrnexusOutputHandlers ??= new Map());
const handlers = registry.get(outputName) ?? new Set();
const handlers =
(registry instanceof Map ? registry.get(outputName) : registry[outputName]) ?? new Set();
const handler = (payload) => renderOutput(owner, outputName, payload);
handlers.add(handler);
registry.set(outputName, handlers);
if (registry instanceof Map) registry.set(outputName, handlers);
else registry[outputName] = handlers;
byName.set(outputName, handler);
}
const initializedCode = new WeakSet();
function bindPlaygroundEvents(root = document) {
for (const playground of root.querySelectorAll?.(SELECTOR) ?? []) {
if (!initializedCode.has(playground)) { initializedCode.add(playground); updateCode(playground); }
const outputNames = (playground.getAttribute("data-playground-events") || "")
.split(",")
.map((name) => name.trim())
@@ -280,17 +372,19 @@
}
document.addEventListener("input", (event) => {
const playground = event.target.closest?.(SELECTOR);
const form = event.target.closest?.("[data-playground-form]");
const playground = form?.closest(SELECTOR);
if (playground) schedule(playground);
});
document.addEventListener("change", (event) => {
const playground = event.target.closest?.(SELECTOR);
const form = event.target.closest?.("[data-playground-form]");
const playground = form?.closest(SELECTOR);
if (playground) schedule(playground);
});
document.addEventListener("reset", (event) => {
const playground = event.target.closest?.(SELECTOR);
const playground = event.target.closest?.("[data-playground-form]")?.closest(SELECTOR);
if (playground)
setTimeout(() => {
updateCode(playground);
@@ -299,6 +393,11 @@
});
document.addEventListener("click", async (event) => {
if (event.target.closest?.("[data-design-mode]")) {
design.mode = event.target.closest("[data-design-mode]").dataset.designMode;
applyDesign(design);
return;
}
const designToggle = event.target.closest?.("[data-design-panel-toggle]");
if (designToggle) {
document.documentElement.toggleAttribute("data-design-panel-open");
@@ -311,6 +410,12 @@
return;
}
const copyTarget = event.target.closest?.("[data-copy-target]");
if (copyTarget) {
const target = document.querySelector(copyTarget.dataset.copyTarget);
if (target) { try { await navigator.clipboard.writeText(target.textContent || ""); copyTarget.textContent = "Copied"; } catch { copyTarget.textContent = "Select code to copy"; } }
return;
}
const copyText = event.target.closest?.("[data-copy-text]");
if (copyText) {
try {
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 440"><rect width="800" height="440" fill="#e8ece8"/><g fill="#fafbf9" stroke="#a5b5ae" stroke-width="4"><rect x="50" y="40" width="220" height="150" rx="12"/><rect x="330" y="40" width="180" height="150" rx="12"/><rect x="570" y="40" width="180" height="360" rx="12"/><rect x="50" y="250" width="220" height="150" rx="12"/><rect x="330" y="250" width="180" height="150" rx="12"/></g><path d="M25 220H550V420M300 20V420" stroke="#bccdc4" stroke-width="22" fill="none"/><g fill="#496258" font-family="sans-serif" font-size="16" text-anchor="middle"><text x="160" y="165">Design studio</text><text x="420" y="165">Meeting room</text><text x="660" y="365">Shared workspace</text><text x="160" y="370">Reception</text><text x="420" y="370">Kitchen</text></g></svg>

After

Width:  |  Height:  |  Size: 816 B