/* global AbortController, DOMParser, HTMLFormElement, HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, MutationObserver, Node, URL, URLSearchParams, clearTimeout, document, fetch, navigator, setTimeout, window */ (() => { const SELECTOR = "[data-playground]"; const DESIGN_DEFAULTS = { style: "default", palette: "violet", mode: "system", font: "jakarta", scale: "default", }; const htmlPolicy = window.trustedTypes?.createPolicy("wrnexus-playground", { createHTML: (value) => value, }); let request; let timer; function storedDesign() { const root = document.documentElement.dataset; 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, }; } function setCookie(name, value) { document.cookie = `${name}=${encodeURIComponent(value)};path=/;max-age=31536000;samesite=lax`; } function applyDesign(settings, persist = true) { const root = document.documentElement; root.dataset.uiStyle = settings.style; root.dataset.uiPalette = settings.palette; root.dataset.uiMode = settings.mode; root.dataset.uiFont = settings.font; root.dataset.uiScale = settings.scale; const dark = settings.mode === "dark" || (settings.mode === "system" && window.matchMedia("(prefers-color-scheme: dark)").matches); root.dataset.theme = dark ? "dark" : "light"; for (const control of document.querySelectorAll("[data-design-setting]")) { control.value = settings[control.dataset.designSetting] || ""; } if (persist) { setCookie("wrn-ui-style", settings.style); setCookie("wrn-ui-palette", settings.palette); setCookie("wrn-ui-mode", settings.mode); setCookie("wrn-ui-font", settings.font); setCookie("wrn-ui-scale", settings.scale); 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"); } window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => { if (design.mode === "system") applyDesign(design); }); function sourceAttribute(value) { return String(value) .replaceAll("&", "&") .replaceAll('"', """) .replaceAll("<", "<") .replaceAll(">", ">"); } function updateCode(playground) { const form = playground.querySelector("[data-playground-form]"); const code = playground.querySelector("[data-playground-code]"); if (!(form instanceof HTMLFormElement) || !code) return; const component = playground.getAttribute("data-playground-component") || "Component"; const publicTag = playground.getAttribute("data-playground-public-tag") === "true"; const hasSlot = playground.getAttribute("data-playground-has-slot") === "true"; const attributes = []; for (const field of form.elements) { if ( !( field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement || field instanceof HTMLSelectElement ) || !field.name.startsWith("pg_") ) continue; const name = field.name.slice(3); if (field instanceof HTMLInputElement && field.type === "checkbox") { attributes.push(`${name}="${field.checked}"`); } else if (field.value !== "") { const value = field.hasAttribute("data-playground-json") ? `'${field.value.replaceAll("'", "\\'")}'` : `"${field.value.replaceAll('"', '\\"')}"`; attributes.push(`${name}=${value}`); } } const tag = publicTag ? component : "div"; 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 ? `${opening}>\n \n` : `${opening}\n/>`; } function valuesFrom(form) { const params = new URLSearchParams(); let valid = true; for (const field of form.elements) { if (!( field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement || field instanceof HTMLSelectElement )) continue; if (!field.name) continue; const error = field.closest(".playground-field")?.querySelector("[data-playground-error]"); if (field.hasAttribute("data-playground-json")) { try { JSON.parse(field.value); field.removeAttribute("aria-invalid"); if (error) error.textContent = ""; } catch { field.setAttribute("aria-invalid", "true"); if (error) error.textContent = "Enter valid JSON"; valid = false; continue; } } params.set(field.name, field.type === "checkbox" ? String(field.checked) : field.value); } return valid ? params : null; } async function update(playground) { const form = playground.querySelector("[data-playground-form]"); const preview = playground.querySelector("[data-playground-preview]"); const status = playground.querySelector("[data-playground-status]"); if (!(form instanceof HTMLFormElement) || !preview || !status) return; const params = valuesFrom(form); if (!params) { status.textContent = "Fix the highlighted JSON value."; return; } request?.abort(); request = new AbortController(); const url = new URL(window.location.href); for (const key of [...url.searchParams.keys()]) { if (key.startsWith("pg_")) url.searchParams.delete(key); } for (const [key, value] of params) url.searchParams.set(key, value); playground.setAttribute("data-updating", "true"); status.textContent = "Updating preview…"; try { const response = await fetch(url, { headers: { "x-wrnexus-playground": "1" }, signal: request.signal, }); if (!response.ok) throw new Error(`Preview request failed (${response.status})`); const responseHtml = await response.text(); const parsed = new DOMParser().parseFromString( htmlPolicy ? htmlPolicy.createHTML(responseHtml) : responseHtml, "text/html", ); const next = parsed.querySelector("[data-playground-preview]"); if (!next) throw new Error("Preview markup was not returned"); window.__wrnexusDisposeBehaviors?.(preview); preview.replaceChildren( ...Array.from(next.childNodes, (node) => document.importNode(node, true)), ); window.__wrnexusHydrateScopes?.(preview); window.__wrnexusHydrateCsrFetches?.(preview); window.wrnTheme?.bind?.(document); bindPlaygroundEvents(document); status.textContent = "Preview updated"; } catch (error) { if (error?.name !== "AbortError") { status.textContent = error instanceof Error ? error.message : "Unable to update preview"; } } finally { playground.removeAttribute("data-updating"); } } const outputBindings = new WeakMap(); const legacyObservedOutputs = new Set(); function formatOutputPayload(payload) { if (payload === undefined) return "No output payload"; if (typeof payload === "string") return payload; try { return JSON.stringify(payload, null, 2); } catch { return String(payload); } } function renderOutput(owner, outputName, payload) { const log = owner.querySelector("[data-playground-event-log]"); if (!log) return; log.replaceChildren(); const heading = document.createElement("strong"); heading.textContent = `@${outputName}`; const output = document.createElement("pre"); output.textContent = formatOutputPayload(payload); log.append(heading, output); } function bindDirectOutput(host, owner, outputName) { let byName = outputBindings.get(host); if (!byName) { byName = new Map(); outputBindings.set(host, byName); } if (byName.has(outputName)) return; const registry = (host.__wrnexusOutputHandlers ??= new Map()); const handlers = registry.get(outputName) ?? new Set(); const handler = (payload) => renderOutput(owner, outputName, payload); handlers.add(handler); registry.set(outputName, handlers); byName.set(outputName, handler); } function bindPlaygroundEvents(root = document) { for (const playground of root.querySelectorAll?.(SELECTOR) ?? []) { const outputNames = (playground.getAttribute("data-playground-events") || "") .split(",") .map((name) => name.trim()) .filter(Boolean); const host = playground.querySelector( "[data-playground-preview] [data-wrn-scope], [data-playground-preview] [data-ui-component], [data-playground-preview] [data-component]", ); for (const outputName of outputNames) { if (host) bindDirectOutput(host, playground, outputName); // Legacy CustomEvent observation is retained only for compatibility-mode components. if (legacyObservedOutputs.has(outputName)) continue; legacyObservedOutputs.add(outputName); document.addEventListener(outputName, (legacyEvent) => { const owner = legacyEvent.target?.closest?.(SELECTOR); if (!owner) return; const declaredOutputs = (owner.getAttribute("data-playground-events") || "").split(","); if (!declaredOutputs.includes(outputName)) return; renderOutput(owner, outputName, legacyEvent.detail); }); } } } bindPlaygroundEvents(); new MutationObserver(() => bindPlaygroundEvents(document)).observe( document.getElementById("app") || document.body, { childList: true, subtree: true }, ); function schedule(playground) { updateCode(playground); clearTimeout(timer); timer = setTimeout(() => update(playground), 180); } document.addEventListener("input", (event) => { const playground = event.target.closest?.(SELECTOR); if (playground) schedule(playground); }); document.addEventListener("change", (event) => { const playground = event.target.closest?.(SELECTOR); if (playground) schedule(playground); }); document.addEventListener("reset", (event) => { const playground = event.target.closest?.(SELECTOR); if (playground) setTimeout(() => { updateCode(playground); update(playground); }, 0); }); document.addEventListener("click", async (event) => { const designToggle = event.target.closest?.("[data-design-panel-toggle]"); if (designToggle) { document.documentElement.toggleAttribute("data-design-panel-open"); return; } if (event.target.closest?.("[data-design-reset]")) { design = { ...DESIGN_DEFAULTS }; applyDesign(design); return; } const copyText = event.target.closest?.("[data-copy-text]"); if (copyText) { try { await navigator.clipboard.writeText(copyText.dataset.copyText || ""); copyText.setAttribute("data-copied", "true"); setTimeout(() => copyText.removeAttribute("data-copied"), 1200); } catch { // Clipboard access may be blocked in an insecure local context. } return; } const menuToggle = event.target.closest?.("[data-docs-menu-toggle]"); if (menuToggle) { document.documentElement.toggleAttribute("data-docs-menu-open"); return; } if (event.target.closest?.("[data-docs-component-link]")) { document.documentElement.removeAttribute("data-docs-menu-open"); } const copy = event.target.closest?.("[data-playground-copy]"); if (!copy) return; const playground = copy.closest(SELECTOR); const code = playground?.querySelector("[data-playground-code]"); const status = playground?.querySelector("[data-playground-status]"); if (!code || !status) return; try { await navigator.clipboard.writeText(code.textContent || ""); status.textContent = "Component code copied"; const label = copy.lastChild; if (label?.nodeType === Node.TEXT_NODE) label.textContent = "Copied"; setTimeout(() => { if (label?.nodeType === Node.TEXT_NODE) label.textContent = "Copy"; }, 1400); } catch { status.textContent = "Copy failed. Select the code and copy it manually."; } }); document.addEventListener("input", (event) => { const setting = event.target.closest?.("[data-design-setting]"); if (setting) { design = { ...design, [setting.dataset.designSetting]: setting.value }; applyDesign(design); return; } if (!event.target.matches?.("[data-docs-search]")) return; const query = event.target.value.trim().toLowerCase(); const links = [...document.querySelectorAll("[data-docs-component-link]")]; let visible = 0; for (const link of links) { const matches = !query || link.textContent.toLowerCase().includes(query); link.hidden = !matches; if (matches) visible++; } for (const group of document.querySelectorAll(".docs-directory-group")) { group.hidden = !group.querySelector("[data-docs-component-link]:not([hidden])"); } const empty = document.querySelector("[data-docs-empty]"); if (empty) empty.hidden = visible > 0; }); document.addEventListener("keydown", (event) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { event.preventDefault(); document.querySelector("[data-docs-search]")?.focus(); } }); })();