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("") ? -1 : 1;
if (depth === 0) {
named.set(match[1]!, content.slice(slots.lastIndex, tag.index));
end = tags.lastIndex;
break;
}
}
if (depth !== 0) throw Error("Unbalanced sample slot markup.");
cursor = end;
slots.lastIndex = end;
}
defaultContent += content.slice(cursor);
return html.replace(/]*)>([\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);
}