Something went wrong. Try again.
Monorepo for the nibling.run platform
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980import { adopt, define, sheet } from "@nibling/web/internals";import type { Files } from "../files.ts";import type { Unsubscribe } from "../shell/workbench.ts";
const previewSheet = sheet(` :host { display: flex; container: nib-preview / size; } iframe { flex: 1; min-width: 0; min-height: 0; border: 0; background: var(--nib-color-document); } iframe:focus-visible { outline: var(--nib-size-focus-ring) solid var(--nib-color-focus-ring); outline-offset: calc(-1 * var(--nib-size-focus-ring)); }`);
/** * Renders an HTML file in an opaque-origin iframe. The real preview is guest HTTP on the sandbox's * own origin (D3); `sandbox` without `allow-same-origin` is the in-page stand-in. An opaque origin * has nothing to fetch relative URLs from, so local stylesheets and scripts are inlined. */export class PreviewElement extends HTMLElement { readonly #iframe: HTMLIFrameElement;
constructor() { super(); const root = this.attachShadow({ mode: "open", delegatesFocus: true }); adopt(root, previewSheet); this.#iframe = document.createElement("iframe"); root.append(this.#iframe); }
connect(files: Files, path: string): Unsubscribe { this.#iframe.title = `Preview of ${path}`; this.#iframe.sandbox.add("allow-scripts"); let deps = new Set<string>(); const draw = () => { const bundle = inline(files, path); deps = bundle.deps; this.#iframe.srcdoc = bundle.html; }; draw(); return files.watch((changed) => { if (changed === path || deps.has(changed)) draw(); }); }}
/** Inlines the stylesheets and scripts an HTML file links from `files`, and lists the paths it read. */function inline(files: Files, path: string): { html: string; deps: Set<string> } { const deps = new Set<string>(); const doc = new DOMParser().parseFromString(files.read(path) ?? "", "text/html"); const local = (ref: string | null) => { if (!ref) return; const url = new URL(ref, `file:///${path}`); if (url.protocol !== "file:") return; const target = decodeURIComponent(url.pathname.slice(1)); const text = files.read(target); if (text === undefined) return; deps.add(target); return text; }; for (const link of doc.querySelectorAll<HTMLLinkElement>('link[rel~="stylesheet"][href]')) { const text = local(link.getAttribute("href")); if (text === undefined) continue; const style = doc.createElement("style"); style.textContent = text; link.replaceWith(style); } for (const script of doc.querySelectorAll<HTMLScriptElement>("script[src]")) { const text = local(script.getAttribute("src")); if (text === undefined) continue; script.removeAttribute("src"); script.textContent = text; } return { html: `<!doctype html>\n${doc.documentElement.outerHTML}`, deps };}
declare global { interface HTMLElementTagNameMap { "nib-preview": PreviewElement; }}
define("nib-preview", PreviewElement);