import { IconCode, IconEdit, IconX } from "@tabler/icons-react";
import { useEffect, useId, useState } from "react";
import { AiEditableFieldLabel } from "../AiEditableField.js";
import { defineBlock } from "../types.js";
import type { BlockReadProps, BlockEditProps } from "../types.js";
import { htmlSchema, htmlMdx, type HtmlBlockData } from "./html.config.js";
import { useIsDark } from "./wireframe-kit.js";
/**
* Standard library HTML / Tailwind block. The registry form of the plan
* `custom-html` block: an author-supplied HTML (+ optional CSS) fragment
* rendered inside a sandboxed iframe, with an inline source editor.
*
* Security: the fragment is rendered in a `sandbox="allow-same-origin"` iframe
* with `referrerPolicy="no-referrer"` — no scripts execute — and the schema's
* `noFullHtmlDocument` refine rejects document/script/handler markup before it
* is ever stored. When the app injects `ctx.sanitizeHtml`, the fragment + CSS
* are additionally sanitized before being placed in the iframe `srcDoc`.
*
* Styling uses app-agnostic shadcn utility classes (`border`, `bg-muted`,
* `text-muted-foreground`) so the block renders cleanly in any template, not
* just the plan app.
*/
/** Build the iframe document for a fragment, applying app sanitization if given. */
function buildSrcDoc(
data: HtmlBlockData,
theme: "light" | "dark",
sanitize?: (html: string, css?: string) => string,
): string {
const css = data.css ?? "";
const body = sanitize ? sanitize(data.html, data.css) : data.html;
// The iframe is isolated from the host's `.dark` class and CSS variables, so
// bridge the current theme explicitly and expose the same semantic tokens that
// generated wireframe/diagram HTML already uses.
return `
);
}
/**
* The standard HTML / Tailwind block spec. Both apps register this; the plan app
* registers the matching React-free `{ schema, mdx }` server-side via
* `html.config.ts`. `empty()` seeds a friendly starter fragment for slash
* insertion.
*/
export const htmlBlock = defineBlock({
type: "custom-html",
schema: htmlSchema,
mdx: htmlMdx,
Read: HtmlReadBlock,
Edit: HtmlEditBlock,
placement: ["block"],
// Config-driven: the render (a sandboxed card) differs from its source, so edit
// the html/css/caption from a corner button + panel rather than always-inline.
editSurface: "panel",
label: "HTML / Tailwind",
icon: IconCode,
description:
"An author-supplied HTML (with optional CSS) fragment rendered in a sandboxed iframe, with inline source editing.",
empty: () => ({ html: '