import samples from "./gallery-samples.json"; import { resolveControlTemplate } from "@wrnexus/core"; const illustration = `data:image/svg+xml,${encodeURIComponent('')}`; const map = `data:image/svg+xml,${encodeURIComponent('')}`; function localAssets(value: unknown, key = ""): unknown { if (typeof value === "string") return /^(?:https?:\/\/|\/showcase-images\/)/.test(value) && /src|image|avatar|poster|logo/i.test(key) ? illustration : /^https?:\/\//.test(value) && /href|action/i.test(key) ? "#sample" : value; if (Array.isArray(value)) return value.map((entry) => localAssets(entry, key)); if (value && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, localAssets(v, k)])); return value; } /** Fixed, populated sample content; never contains draft-supplied executable markup. */ export function gallerySample(name: string) { const source = (samples as Record; content: string }>)[ name ]; if (!source) throw Error(`Missing gallery sample: ${name}`); const props = localAssets(structuredClone(source.props)) as Record; const field = [ "Input", "Textarea", "Select", "Checkbox", "Radio", "Switch", "AdvancedSelect", "ComboBox", "DatePicker", "TimePicker", "RangeSlider", "InputNumber", "PinInput", "FileInput", "StrongPassword", "TogglePassword", ].includes(name); if (Object.hasOwn(props, "id") || field) props.id = `sample-${name.toLowerCase()}`; if (field && !props.name) props.name = `sample-${name.toLowerCase()}`; props.class = ""; let content = source.content; let hint = "Sample data. Interact with the controls to try this component."; if (name === "Map") { Object.assign(props, { title: "Sample location map", description: "Schematic sample: select a marker or use the zoom controls.", imageSrc: map, imageAlt: "Schematic sample map", items: [ { label: "Office", x: 32, y: 42 }, { label: "Park", x: 68, y: 70 }, ], }); hint = "Local schematic sample; no map service or API key is needed."; } if (name === "DrawflowCanvas" || name === "FlowCanvas") Object.assign(props, { nodes: [ { id: "start", label: "Start", x: 30, y: 50, input: false, ports: [{ key: "next", label: "Next", target: "review" }], }, { id: "review", label: "Review", x: 260, y: 100, ports: [], }, ], palette: [{ kind: "task", label: "Add task" }], }); if (name === "Toaster") content = ''; if (name === "BackToTop") { props.threshold = 0; props.alwaysVisible = true; props.showLabel = true; hint = "Scroll this preview, then use the back-to-top control."; } if (name === "DeviceFrame") { props.items = []; content = "

Sample mobile page

This local content sits inside the device frame.

Account preferences

Rotate the preview to compare portrait and landscape layouts.

"; } if (name === "VisuallyHidden") hint = "This accessibility component intentionally hides its content visually."; if (name === "CustomScrollbar") content = Array.from( { length: 12 }, (_, i) => `

Sample row ${i + 1} — scroll to see more content.

`, ).join(""); if (["AdvancedSelect", "Checkbox", "Select"].includes(name)) delete props.values; if (["AuthForm", "AuthSplitLayout", "FileInput"].includes(name)) hint = "Preview only. Submissions and file uploads are not sent anywhere."; return { props, content, hint }; } export function previewSlots(html: string, content: string) { const named = new Map(); // Author-controlled sample slots are the only HTML accepted here. let defaultContent = ""; let cursor = 0; const slots = /
/g; for (let match = slots.exec(content); match; match = slots.exec(content)) { defaultContent += content.slice(cursor, match.index); const tags = /<\/?div\b[^>]*>/g; tags.lastIndex = slots.lastIndex; let depth = 1; let end = tags.lastIndex; for (let tag = tags.exec(content); tag; tag = tags.exec(content)) { depth += tag[0].startsWith("]*)>([\s\S]*?)<\/slot>/g, (all, attributes) => { const name = /\bname=["']([^"']+)["']/.exec(attributes)?.[1]; const replacement = name ? named.get(name) : defaultContent; return replacement ? `${replacement}` : all; }); } /** Conditional/loop bodies are request-owned templates, and need the same slots as SSR. */ export function previewSampleMarkup(html: string, content: string) { const seen = new Set(); const visit = (markup: string): string => { for (const match of markup.matchAll(/data-wrn-(?:if|each)="(@[^"]+)"/g)) { if (seen.has(match[1]!)) continue; seen.add(match[1]!); const definition = resolveControlTemplate(match[1]!); for (const entry of Array.isArray(definition) ? definition : [definition]) { if (!entry || typeof entry !== "object") continue; for (const key of ["body", "empty"]) { if (typeof entry[key] === "string") entry[key] = visit(entry[key]); } } } return previewSlots(markup, content); }; return visit(html); }