// src/ui/pieces/types.ts
var PieceCatalog = class {
  pieces = /* @__PURE__ */ new Map();
  constructor(initial = []) {
    for (const p of initial) this.register(p);
  }
  register(piece) {
    if (this.pieces.has(piece.kind)) {
      console.warn(`[Pieces] kind "${piece.kind}" already registered \u2014 replacing.`);
    }
    this.pieces.set(piece.kind, piece);
  }
  get(kind) {
    return this.pieces.get(kind);
  }
  list() {
    return Array.from(this.pieces.values());
  }
  /**
   * Merge another catalog into this one. Host's custom catalog wins on
   * conflict — lets enterprises extend / override built-ins.
   */
  extend(other) {
    const incoming = Array.isArray(other) ? other : other.list();
    for (const p of incoming) this.register(p);
  }
};

// src/ui/pieces/renderer.tsx
import { useCallback, useMemo, useState } from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function PieceRenderer({
  surface,
  catalog,
  onAction,
  onDataChange
}) {
  const [data, setData] = useState(surface.data ?? {});
  const setBinding = useCallback((path, value) => {
    setData((cur) => {
      const next = applyPointer(cur, path, value);
      onDataChange?.(next);
      return next;
    });
  }, [onDataChange]);
  const trigger = useCallback((name, payload) => {
    onAction?.(name, payload, data);
  }, [onAction, data]);
  const ctx = useMemo(() => {
    const c = {
      data,
      setBinding,
      trigger,
      render: (node) => renderNode(node, c, catalog)
    };
    return c;
  }, [data, setBinding, trigger, catalog]);
  return /* @__PURE__ */ jsx(Fragment, { children: renderNode(surface.root, ctx, catalog) });
}
function renderNode(node, ctx, catalog) {
  const def = catalog.get(node.kind);
  if (!def) {
    return /* @__PURE__ */ jsxs("div", { "data-dddk-ui": "piece-unknown", style: { color: "#cc3333", padding: 8 }, children: [
      "Unknown piece: ",
      node.kind
    ] });
  }
  return def.render(node, ctx);
}
function readPointer(data, path) {
  if (!path) return void 0;
  if (!path.startsWith("/")) return data[path];
  const parts = path.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
  let cur = data;
  for (const p of parts) {
    if (cur && typeof cur === "object" && p in cur) {
      cur = cur[p];
    } else {
      return void 0;
    }
  }
  return cur;
}
function applyPointer(data, path, value) {
  if (!path) return data;
  if (!path.startsWith("/")) {
    return { ...data, [path]: value };
  }
  const parts = path.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
  const last = parts.pop();
  const next = { ...data };
  if (!last) return next;
  let cur = next;
  for (const p of parts) {
    const existing = cur[p];
    if (existing && typeof existing === "object" && !Array.isArray(existing)) {
      const cloned = { ...existing };
      cur[p] = cloned;
      cur = cloned;
    } else {
      const fresh = {};
      cur[p] = fresh;
      cur = fresh;
    }
  }
  cur[last] = value;
  return next;
}
function resolveValue(node, ctx, literalKey, fallback) {
  if (node.bind) {
    const v = readPointer(ctx.data, node.bind);
    if (v !== void 0) return v;
  }
  const lit = node[literalKey];
  if (lit !== void 0) return lit;
  return fallback;
}

// src/ui/pieces/builtin.tsx
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
var UI = "data-dddk-piece";
function renderChildren(node, ctx) {
  const children = node.children;
  if (!children) return null;
  return children.map((c, i) => /* @__PURE__ */ jsx2(Frag, { children: ctx.render(c) }, c.id ?? i));
}
function Frag({ children }) {
  return /* @__PURE__ */ jsx2(Fragment2, { children });
}
var Stack = {
  kind: "Stack",
  meta: { category: "layout", description: "\u5782\u76F4 / \u6C34\u5E73\u5806\u758A" },
  render: (node, ctx) => /* @__PURE__ */ jsx2(
    "div",
    {
      ...{ [UI]: "stack" },
      "data-direction": node.direction ?? "vertical",
      style: {
        display: "flex",
        flexDirection: node.direction === "horizontal" ? "row" : "column",
        gap: node.gap ?? 8,
        alignItems: node.align ?? "stretch"
      },
      children: renderChildren(node, ctx)
    }
  )
};
var Grid = {
  kind: "Grid",
  meta: { category: "layout", description: "\u7DB2\u683C\u4F48\u5C40" },
  render: (node, ctx) => /* @__PURE__ */ jsx2(
    "div",
    {
      ...{ [UI]: "grid" },
      style: {
        display: "grid",
        gridTemplateColumns: `repeat(${node.columns ?? 2}, minmax(0, 1fr))`,
        gap: node.gap ?? 8
      },
      children: renderChildren(node, ctx)
    }
  )
};
var Split = {
  kind: "Split",
  meta: { category: "layout", description: "\u96D9\u6B04\u4F48\u5C40" },
  render: (node, ctx) => /* @__PURE__ */ jsx2("div", { ...{ [UI]: "split" }, style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }, children: renderChildren(node, ctx) })
};
var Card = {
  kind: "Card",
  meta: { category: "layout", description: "\u5361\u7247\u5BB9\u5668" },
  render: (node, ctx) => /* @__PURE__ */ jsx2(
    "div",
    {
      ...{ [UI]: "card" },
      style: {
        background: "var(--dddk-bg-elevated, #fff)",
        borderRadius: "var(--dddk-radius, 12px)",
        padding: 16,
        boxShadow: "var(--dddk-shadow-md, 0 1px 3px rgba(0,0,0,0.05))",
        display: "flex",
        flexDirection: "column",
        gap: 12
      },
      children: renderChildren(node, ctx)
    }
  )
};
var Tabs = {
  kind: "Tabs",
  meta: { category: "layout", description: "\u9801\u7C64" },
  render: (node, ctx) => {
    const tabs = node.tabs ?? [];
    const activeBind = node.bind ?? (node.id ? `__activeTab_${node.id}` : "__activeTab");
    const active = ctx.data[activeBind] ?? tabs[0]?.id;
    const activeTab = tabs.find((t) => t.id === active) ?? tabs[0];
    return /* @__PURE__ */ jsxs2("div", { ...{ [UI]: "tabs" }, children: [
      /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 8, borderBottom: "1px solid var(--dddk-border)" }, children: tabs.map((t) => /* @__PURE__ */ jsx2(
        "button",
        {
          type: "button",
          onClick: () => ctx.setBinding(activeBind, t.id),
          style: {
            padding: "6px 12px",
            background: "transparent",
            border: 0,
            borderBottom: t.id === active ? "2px solid var(--dddk-accent)" : "2px solid transparent",
            cursor: "pointer",
            font: "inherit"
          },
          children: t.label
        },
        t.id
      )) }),
      /* @__PURE__ */ jsx2("div", { style: { padding: 12 }, children: activeTab && ctx.render(activeTab.content) })
    ] });
  }
};
var Heading = {
  kind: "Heading",
  meta: { category: "content" },
  render: (node, ctx) => {
    const level = node.level ?? 3;
    const Tag2 = `h${level}`;
    const text = resolveValue(node, ctx, "text", "");
    return /* @__PURE__ */ jsx2(Tag2, { ...{ [UI]: "heading" }, style: { margin: "0 0 8px 0" }, children: text });
  }
};
var Text = {
  kind: "Text",
  meta: { category: "content" },
  render: (node, ctx) => {
    const text = resolveValue(node, ctx, "text", "");
    return /* @__PURE__ */ jsx2("span", { ...{ [UI]: "text" }, children: text });
  }
};
var Code = {
  kind: "Code",
  meta: { category: "content" },
  render: (node, ctx) => {
    const code = resolveValue(node, ctx, "code", "");
    return /* @__PURE__ */ jsx2("pre", { ...{ [UI]: "code" }, style: {
      background: "var(--dddk-bg, #fafafa)",
      padding: 8,
      borderRadius: 6,
      fontFamily: "var(--dddk-font-mono, ui-monospace, monospace)",
      fontSize: 12,
      margin: 0,
      overflowX: "auto"
    }, children: code });
  }
};
var Image = {
  kind: "Image",
  meta: { category: "content" },
  render: (node) => /* @__PURE__ */ jsx2(
    "img",
    {
      ...{ [UI]: "image" },
      src: node.src,
      alt: node.alt ?? "",
      style: { maxWidth: "100%", borderRadius: 6 }
    }
  )
};
var Tag = {
  kind: "Tag",
  meta: { category: "content" },
  render: (node) => {
    const tone = node.tone ?? "neutral";
    const colors = {
      neutral: { bg: "rgba(0,0,0,0.06)", fg: "inherit" },
      success: { bg: "rgba(16,185,129,0.12)", fg: "#10b981" },
      warning: { bg: "rgba(245,158,11,0.12)", fg: "#f59e0b" },
      error: { bg: "rgba(239,68,68,0.12)", fg: "#ef4444" },
      info: { bg: "rgba(59,130,246,0.12)", fg: "#3b82f6" }
    };
    const c = colors[tone] ?? colors.neutral;
    return /* @__PURE__ */ jsx2("span", { ...{ [UI]: "tag" }, style: {
      background: c.bg,
      color: c.fg,
      padding: "2px 8px",
      borderRadius: 12,
      fontSize: 11,
      fontWeight: 500,
      display: "inline-block"
    }, children: node.text });
  }
};
var Divider = {
  kind: "Divider",
  meta: { category: "content" },
  render: () => /* @__PURE__ */ jsx2("hr", { ...{ [UI]: "divider" }, style: { border: 0, borderTop: "1px solid var(--dddk-border, rgba(0,0,0,0.1))", margin: "8px 0" } })
};
var Markdown = {
  kind: "Markdown",
  meta: { category: "content" },
  // Minimal — host should plug a real md renderer if they want rich rendering.
  render: (node, ctx) => {
    const text = resolveValue(node, ctx, "text", "");
    return /* @__PURE__ */ jsx2("div", { ...{ [UI]: "markdown" }, style: { whiteSpace: "pre-wrap" }, children: text });
  }
};
var Listing = {
  kind: "Listing",
  meta: { category: "data", description: "\u641C\u5C0B\u5F0F\u6E05\u55AE" },
  render: (node, ctx) => {
    const items = resolveValue(node, ctx, "items", []);
    return /* @__PURE__ */ jsx2("ul", { ...{ [UI]: "listing" }, style: { listStyle: "none", margin: 0, padding: 0 }, children: items.map((it) => /* @__PURE__ */ jsxs2("li", { style: {
      display: "flex",
      gap: 8,
      padding: "6px 8px",
      borderBottom: "1px solid var(--dddk-border, rgba(0,0,0,0.05))"
    }, children: [
      it.icon && /* @__PURE__ */ jsx2("span", { children: it.icon }),
      /* @__PURE__ */ jsxs2("div", { style: { flex: 1, minWidth: 0 }, children: [
        /* @__PURE__ */ jsx2("div", { children: it.title }),
        it.subtitle && /* @__PURE__ */ jsx2("small", { style: { color: "var(--dddk-text-muted, #6b6b6b)" }, children: it.subtitle })
      ] }),
      it.accessory && /* @__PURE__ */ jsx2("span", { style: { color: "var(--dddk-text-muted, #6b6b6b)" }, children: it.accessory })
    ] }, it.id)) });
  }
};
var Table = {
  kind: "Table",
  meta: { category: "data" },
  render: (node, ctx) => {
    const columns = node.columns ?? [];
    const rows = resolveValue(node, ctx, "rows", []);
    return /* @__PURE__ */ jsxs2("table", { ...{ [UI]: "table" }, style: { width: "100%", borderCollapse: "collapse", fontSize: 13 }, children: [
      /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsx2("tr", { children: columns.map((c) => /* @__PURE__ */ jsx2("th", { style: { textAlign: "left", padding: 6, borderBottom: "1px solid var(--dddk-border)" }, children: c.label }, c.key)) }) }),
      /* @__PURE__ */ jsx2("tbody", { children: rows.map((r, i) => /* @__PURE__ */ jsx2("tr", { children: columns.map((c) => /* @__PURE__ */ jsx2("td", { style: { padding: 6, borderBottom: "1px solid var(--dddk-border, rgba(0,0,0,0.05))" }, children: String(r[c.key] ?? "") }, c.key)) }, i)) })
    ] });
  }
};
var Metric = {
  kind: "Metric",
  meta: { category: "data", description: "\u55AE\u4E00\u6578\u5B57 + \u8B8A\u52D5" },
  render: (node) => {
    const trend = node.trend ?? "flat";
    const trendColor = trend === "up" ? "#10b981" : trend === "down" ? "#ef4444" : "#6b6b6b";
    const arrow = trend === "up" ? "\u25B2" : trend === "down" ? "\u25BC" : "\u2013";
    return /* @__PURE__ */ jsxs2("div", { ...{ [UI]: "metric" }, style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
      Boolean(node.label) && /* @__PURE__ */ jsx2("span", { style: { fontSize: 11, color: "var(--dddk-text-muted, #6b6b6b)" }, children: node.label }),
      /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "baseline", gap: 8 }, children: [
        /* @__PURE__ */ jsx2("strong", { style: { fontSize: 22 }, children: node.value }),
        node.delta != null && /* @__PURE__ */ jsxs2("span", { style: { color: trendColor, fontSize: 12 }, children: [
          arrow,
          " ",
          node.delta
        ] })
      ] })
    ] });
  }
};
var Timeline = {
  kind: "Timeline",
  meta: { category: "data" },
  render: (node, ctx) => {
    const events = resolveValue(node, ctx, "events", []);
    return /* @__PURE__ */ jsx2("ol", { ...{ [UI]: "timeline" }, style: { listStyle: "none", margin: 0, padding: 0, borderLeft: "2px solid var(--dddk-border)" }, children: events.map((e) => /* @__PURE__ */ jsxs2("li", { style: { marginLeft: 12, paddingLeft: 12, paddingBottom: 12, position: "relative" }, children: [
      /* @__PURE__ */ jsx2("span", { style: {
        position: "absolute",
        left: -19,
        top: 6,
        width: 8,
        height: 8,
        borderRadius: "50%",
        background: "var(--dddk-accent, #3b82f6)"
      } }),
      /* @__PURE__ */ jsx2("small", { style: { color: "var(--dddk-text-muted)" }, children: e.time }),
      /* @__PURE__ */ jsx2("div", { children: e.title }),
      e.note && /* @__PURE__ */ jsx2("small", { style: { color: "var(--dddk-text-muted)" }, children: e.note })
    ] }, e.id)) });
  }
};
function field(node, child) {
  return /* @__PURE__ */ jsxs2("label", { style: { display: "flex", flexDirection: "column", gap: 4, fontSize: 13 }, children: [
    node.label != null && /* @__PURE__ */ jsx2("span", { children: node.label }),
    child
  ] });
}
var baseInputStyle = {
  width: "100%",
  padding: "6px 8px",
  border: "1px solid var(--dddk-border, rgba(0,0,0,0.15))",
  borderRadius: 6,
  font: "inherit",
  background: "var(--dddk-bg, #fff)",
  color: "inherit"
};
var TextField = {
  kind: "TextField",
  meta: { category: "input" },
  render: (node, ctx) => field(node, /* @__PURE__ */ jsx2(
    "input",
    {
      type: "text",
      ...{ [UI]: "textfield" },
      style: baseInputStyle,
      value: String(resolveValue(node, ctx, "value", "") ?? ""),
      placeholder: node.placeholder,
      onChange: (e) => node.bind && ctx.setBinding(node.bind, e.target.value)
    }
  ))
};
var TextArea = {
  kind: "TextArea",
  meta: { category: "input" },
  render: (node, ctx) => field(node, /* @__PURE__ */ jsx2(
    "textarea",
    {
      ...{ [UI]: "textarea" },
      style: baseInputStyle,
      rows: node.rows ?? 3,
      value: String(resolveValue(node, ctx, "value", "") ?? ""),
      placeholder: node.placeholder,
      onChange: (e) => node.bind && ctx.setBinding(node.bind, e.target.value)
    }
  ))
};
var NumberField = {
  kind: "NumberField",
  meta: { category: "input" },
  render: (node, ctx) => field(node, /* @__PURE__ */ jsx2(
    "input",
    {
      type: "number",
      ...{ [UI]: "numberfield" },
      style: baseInputStyle,
      value: Number(resolveValue(node, ctx, "value", 0)) || 0,
      min: node.min,
      max: node.max,
      step: node.step,
      onChange: (e) => node.bind && ctx.setBinding(node.bind, Number(e.target.value))
    }
  ))
};
var PasswordField = {
  kind: "PasswordField",
  meta: { category: "input" },
  render: (node, ctx) => field(node, /* @__PURE__ */ jsx2(
    "input",
    {
      type: "password",
      ...{ [UI]: "password" },
      style: baseInputStyle,
      value: String(resolveValue(node, ctx, "value", "") ?? ""),
      onChange: (e) => node.bind && ctx.setBinding(node.bind, e.target.value)
    }
  ))
};
var Checkbox = {
  kind: "Checkbox",
  meta: { category: "input" },
  render: (node, ctx) => /* @__PURE__ */ jsxs2("label", { style: { display: "flex", gap: 6, alignItems: "center", fontSize: 13 }, children: [
    /* @__PURE__ */ jsx2(
      "input",
      {
        type: "checkbox",
        checked: Boolean(resolveValue(node, ctx, "value", false)),
        onChange: (e) => node.bind && ctx.setBinding(node.bind, e.target.checked)
      }
    ),
    /* @__PURE__ */ jsx2("span", { children: node.label })
  ] })
};
var Switch = {
  kind: "Switch",
  meta: { category: "input" },
  // Visually distinct from checkbox; semantically same.
  render: (node, ctx) => {
    const checked = Boolean(resolveValue(node, ctx, "value", false));
    return /* @__PURE__ */ jsxs2("label", { style: { display: "flex", gap: 8, alignItems: "center", fontSize: 13, cursor: "pointer" }, children: [
      /* @__PURE__ */ jsx2(
        "span",
        {
          onClick: () => node.bind && ctx.setBinding(node.bind, !checked),
          style: {
            width: 32,
            height: 18,
            borderRadius: 9,
            position: "relative",
            background: checked ? "var(--dddk-accent)" : "rgba(0,0,0,0.2)",
            transition: "background 120ms"
          },
          children: /* @__PURE__ */ jsx2("span", { style: {
            position: "absolute",
            top: 2,
            left: checked ? 16 : 2,
            width: 14,
            height: 14,
            borderRadius: "50%",
            background: "#fff",
            transition: "left 120ms"
          } })
        }
      ),
      Boolean(node.label) && /* @__PURE__ */ jsx2("span", { children: node.label })
    ] });
  }
};
var Picker = {
  kind: "Picker",
  meta: { category: "input" },
  render: (node, ctx) => {
    const options = node.options ?? [];
    return field(node, /* @__PURE__ */ jsx2(
      "select",
      {
        style: baseInputStyle,
        value: String(resolveValue(node, ctx, "value", "") ?? ""),
        onChange: (e) => node.bind && ctx.setBinding(node.bind, e.target.value),
        children: options.map((o) => {
          const v = typeof o === "string" ? o : o.value;
          const l = typeof o === "string" ? o : o.label;
          return /* @__PURE__ */ jsx2("option", { value: v, children: l }, v);
        })
      }
    ));
  }
};
var DatePicker = {
  kind: "DatePicker",
  meta: { category: "input" },
  render: (node, ctx) => field(node, /* @__PURE__ */ jsx2(
    "input",
    {
      type: "datetime-local",
      style: baseInputStyle,
      value: String(resolveValue(node, ctx, "value", "") ?? ""),
      onChange: (e) => node.bind && ctx.setBinding(node.bind, e.target.value)
    }
  ))
};
var Slider = {
  kind: "Slider",
  meta: { category: "input" },
  render: (node, ctx) => field(node, /* @__PURE__ */ jsx2(
    "input",
    {
      type: "range",
      min: node.min ?? 0,
      max: node.max ?? 100,
      step: node.step ?? 1,
      value: Number(resolveValue(node, ctx, "value", 0)) || 0,
      onChange: (e) => node.bind && ctx.setBinding(node.bind, Number(e.target.value))
    }
  ))
};
var FilePicker = {
  kind: "FilePicker",
  meta: { category: "input" },
  render: (node, ctx) => field(node, /* @__PURE__ */ jsx2(
    "input",
    {
      type: "file",
      accept: node.accept,
      multiple: Boolean(node.multiple),
      onChange: (e) => {
        if (!node.bind) return;
        const files = Array.from(e.target.files ?? []);
        ctx.setBinding(node.bind, files);
      }
    }
  ))
};
var Button = {
  kind: "Button",
  meta: { category: "action" },
  render: (node, ctx) => {
    const variant = node.variant ?? "primary";
    const styles = {
      primary: { background: "var(--dddk-accent, #3b82f6)", color: "#fff" },
      secondary: { background: "transparent", color: "inherit", border: "1px solid var(--dddk-border)" },
      destructive: { background: "#ef4444", color: "#fff" }
    };
    return /* @__PURE__ */ jsx2(
      "button",
      {
        type: "button",
        ...{ [UI]: "button" },
        onClick: () => ctx.trigger(node.action ?? "submit", node.payload),
        style: {
          ...styles[variant],
          padding: "6px 14px",
          borderRadius: 6,
          border: 0,
          font: "inherit",
          cursor: "pointer",
          fontWeight: 500
        },
        children: node.text
      }
    );
  }
};
var Link = {
  kind: "Link",
  meta: { category: "action" },
  render: (node) => /* @__PURE__ */ jsx2(
    "a",
    {
      ...{ [UI]: "link" },
      href: node.href,
      target: node.target,
      style: { color: "var(--dddk-accent)", textDecoration: "underline", cursor: "pointer" },
      children: node.text
    }
  )
};
var IconButton = {
  kind: "IconButton",
  meta: { category: "action" },
  render: (node, ctx) => /* @__PURE__ */ jsx2(
    "button",
    {
      type: "button",
      ...{ [UI]: "icon-button" },
      title: node.title,
      onClick: () => ctx.trigger(node.action ?? "click", node.payload),
      style: { background: "transparent", border: 0, cursor: "pointer", padding: 4, fontSize: 16 },
      children: node.icon
    }
  )
};
var Spinner = {
  kind: "Spinner",
  meta: { category: "feedback" },
  render: () => /* @__PURE__ */ jsx2("span", { ...{ [UI]: "spinner" }, children: "\u22EF" })
};
var ProgressBar = {
  kind: "ProgressBar",
  meta: { category: "feedback" },
  render: (node, ctx) => {
    const v = Number(resolveValue(node, ctx, "value", 0)) || 0;
    return /* @__PURE__ */ jsx2("div", { style: { background: "rgba(0,0,0,0.08)", height: 6, borderRadius: 3, overflow: "hidden" }, children: /* @__PURE__ */ jsx2("div", { style: {
      width: `${Math.min(100, Math.max(0, v))}%`,
      height: "100%",
      background: "var(--dddk-accent, #3b82f6)",
      transition: "width 200ms"
    } }) });
  }
};
var EmptyState = {
  kind: "EmptyState",
  meta: { category: "feedback" },
  render: (node) => /* @__PURE__ */ jsxs2("div", { ...{ [UI]: "empty-state" }, style: {
    padding: 24,
    textAlign: "center",
    color: "var(--dddk-text-muted, #6b6b6b)"
  }, children: [
    /* @__PURE__ */ jsx2("div", { style: { fontSize: 28, marginBottom: 8 }, children: node.icon ?? "\u2205" }),
    /* @__PURE__ */ jsx2("div", { children: node.title ?? "No data" }),
    Boolean(node.description) && /* @__PURE__ */ jsx2("small", { children: node.description })
  ] })
};
var ErrorState = {
  kind: "ErrorState",
  meta: { category: "feedback" },
  render: (node) => /* @__PURE__ */ jsxs2("div", { ...{ [UI]: "error-state" }, style: {
    padding: 16,
    background: "rgba(239,68,68,0.08)",
    border: "1px solid #ef4444",
    borderRadius: 6,
    color: "#991b1b"
  }, children: [
    /* @__PURE__ */ jsx2("strong", { children: node.title ?? "Error" }),
    Boolean(node.message) && /* @__PURE__ */ jsx2("div", { style: { marginTop: 4, fontSize: 13 }, children: node.message })
  ] })
};
var Slot = {
  kind: "Slot",
  meta: { category: "slot", description: "\u6E32\u67D3 host \u63D0\u4F9B\u7684 React/Web component" },
  render: (node, ctx) => {
    if (typeof node.render === "function") {
      return node.render(ctx.data);
    }
    if (typeof node.slotName === "string") {
      const map = typeof window !== "undefined" && window.__dddkSlots || {};
      return map[node.slotName] ?? /* @__PURE__ */ jsxs2("div", { ...{ [UI]: "slot-empty" }, children: [
        "(slot:",
        node.slotName,
        ")"
      ] });
    }
    return null;
  }
};
var Group = {
  kind: "Group",
  meta: { category: "layout", description: "Transparent visual grouping container \u2014 flex column with gap, NO border / shadow / background. Use inside a Card or surface envelope when you want to cluster related children without adding a second framed container (which would stack borders and look heavy). Children render edge-to-edge against the surrounding envelope." },
  render: (node, ctx) => /* @__PURE__ */ jsx2(
    "div",
    {
      ...{ [UI]: "group" },
      style: {
        display: "flex",
        flexDirection: node.direction === "horizontal" ? "row" : "column",
        gap: node.gap ?? 8,
        alignItems: node.align ?? "stretch"
      },
      children: renderChildren(node, ctx)
    }
  )
};
var MediaCard = {
  kind: "MediaCard",
  meta: { category: "layout", description: 'Image + text composition for one item (a recommendation tile, a product summary, a destination preview). Transparent by default \u2014 does not draw its own border or shadow. Place inside an OptionGroup for "pick one of these" UX, or use standalone as the body of a Card. Props: `image: { src, alt?, aspectRatio? }`, `title`, `description?`, `meta?: string[]`, `orientation?: "top" | "left"` (image position).' },
  render: (node) => {
    const orientation = node.orientation ?? "top";
    const image = node.image;
    const title = node.title ?? "";
    const description = node.description;
    const meta = node.meta ?? [];
    const isLeft = orientation === "left";
    return /* @__PURE__ */ jsxs2(
      "div",
      {
        ...{ [UI]: "media-card" },
        "data-orientation": orientation,
        style: {
          display: "flex",
          flexDirection: isLeft ? "row" : "column",
          gap: isLeft ? 12 : 8,
          alignItems: isLeft ? "flex-start" : "stretch"
        },
        children: [
          image?.src && /* @__PURE__ */ jsx2("div", { style: {
            flex: isLeft ? "0 0 96px" : "0 0 auto",
            width: isLeft ? 96 : "100%",
            aspectRatio: image.aspectRatio ?? (isLeft ? "1" : "16/9"),
            overflow: "hidden",
            borderRadius: "var(--dddk-radius-sm, 6px)",
            background: "var(--dddk-bg-subtle, rgba(0,0,0,0.04))"
          }, children: /* @__PURE__ */ jsx2(
            "img",
            {
              src: image.src,
              alt: image.alt ?? "",
              style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
            }
          ) }),
          /* @__PURE__ */ jsxs2("div", { style: { flex: 1, display: "flex", flexDirection: "column", gap: 4, minWidth: 0 }, children: [
            title && /* @__PURE__ */ jsx2("div", { style: { fontWeight: 600, fontSize: 14, lineHeight: 1.3 }, children: title }),
            description && /* @__PURE__ */ jsx2("div", { style: { fontSize: 13, color: "var(--dddk-text-muted, #64748b)", lineHeight: 1.45 }, children: description }),
            meta.length > 0 && /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 8, fontSize: 11.5, color: "var(--dddk-text-muted, #64748b)", marginTop: 2 }, children: meta.map((m, i) => /* @__PURE__ */ jsx2("span", { children: m }, i)) })
          ] })
        ]
      }
    );
  }
};
var OptionGroup = {
  kind: "OptionGroup",
  meta: { category: "input", description: 'Keyboard-navigable, click-selectable list of options \u2014 each option is a MediaCard-shaped tile (title + description + optional image + meta). Arrow keys move focus (left/right in `layout: "row"`, up/down in `"column"`); Enter or Space confirms; click also works. Bound value is the chosen option\'s `value`. Fires `trigger("choose", { value, index })` on selection. Props: `options: OptionSpec[]`, `layout: "row" | "column"`, `columns?: number` (row mode), `bind: string`.' },
  render: (node, ctx) => {
    const options = node.options ?? [];
    const bind = node.bind ?? "";
    const layout = node.layout ?? "column";
    const columns = node.columns;
    const selected = bind ? readPointer(ctx.data, bind) : void 0;
    const isRow = layout === "row";
    const gridCols = isRow ? columns ?? options.length : 1;
    const handleKey = (e, idx) => {
      const key = e.key;
      let next = idx;
      if (isRow) {
        if (key === "ArrowLeft" || key === "ArrowUp") next = (idx - 1 + options.length) % options.length;
        else if (key === "ArrowRight" || key === "ArrowDown") next = (idx + 1) % options.length;
      } else {
        if (key === "ArrowUp" || key === "ArrowLeft") next = (idx - 1 + options.length) % options.length;
        else if (key === "ArrowDown" || key === "ArrowRight") next = (idx + 1) % options.length;
      }
      if (key === "Enter" || key === " ") {
        e.preventDefault();
        if (bind) ctx.setBinding(bind, options[idx].value);
        ctx.trigger("choose", { value: options[idx].value, index: idx });
        return;
      }
      if (next !== idx) {
        e.preventDefault();
        const parent = e.currentTarget.parentElement;
        const nextEl = parent?.children[next];
        nextEl?.focus();
      }
    };
    return /* @__PURE__ */ jsx2(
      "div",
      {
        ...{ [UI]: "option-group" },
        role: "radiogroup",
        "data-layout": layout,
        style: {
          display: "grid",
          gridTemplateColumns: isRow ? `repeat(${gridCols}, minmax(0, 1fr))` : "1fr",
          gap: 8
        },
        children: options.map((opt, i) => {
          const isSelected = opt.value === selected || selected === void 0 && i === 0;
          return /* @__PURE__ */ jsxs2(
            "div",
            {
              role: "radio",
              "aria-checked": isSelected,
              tabIndex: isSelected ? 0 : -1,
              onClick: () => {
                if (bind) ctx.setBinding(bind, opt.value);
                ctx.trigger("choose", { value: opt.value, index: i });
              },
              onKeyDown: (e) => handleKey(e, i),
              style: {
                display: "flex",
                flexDirection: "column",
                gap: 8,
                padding: 12,
                cursor: "pointer",
                outline: "none",
                borderRadius: "var(--dddk-radius, 10px)",
                border: "1px solid",
                borderColor: isSelected ? "var(--dddk-accent, #6366f1)" : "var(--dddk-border, rgba(0,0,0,0.08))",
                background: isSelected ? "var(--dddk-accent-soft, rgba(99,102,241,0.08))" : "transparent",
                transition: "border-color 120ms, background 120ms"
              },
              onFocus: (e) => {
                e.currentTarget.style.boxShadow = "0 0 0 2px var(--dddk-accent-soft, rgba(99,102,241,0.2))";
              },
              onBlur: (e) => {
                e.currentTarget.style.boxShadow = "none";
              },
              children: [
                opt.image?.src && /* @__PURE__ */ jsx2("div", { style: {
                  width: "100%",
                  aspectRatio: opt.image.aspectRatio ?? "16/9",
                  overflow: "hidden",
                  borderRadius: "var(--dddk-radius-sm, 6px)",
                  background: "var(--dddk-bg-subtle, rgba(0,0,0,0.04))"
                }, children: /* @__PURE__ */ jsx2("img", { src: opt.image.src, alt: opt.image.alt ?? "", style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }),
                /* @__PURE__ */ jsx2("div", { style: { fontWeight: 600, fontSize: 14 }, children: opt.title }),
                opt.description && /* @__PURE__ */ jsx2("div", { style: { fontSize: 13, color: "var(--dddk-text-muted, #64748b)", lineHeight: 1.45 }, children: opt.description }),
                opt.meta && opt.meta.length > 0 && /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 8, fontSize: 11.5, color: "var(--dddk-text-muted, #64748b)" }, children: opt.meta.map((m, mi) => /* @__PURE__ */ jsx2("span", { children: m }, mi)) })
              ]
            },
            opt.value
          );
        })
      }
    );
  }
};
var ChoiceList = {
  kind: "ChoiceList",
  meta: { category: "input", description: 'Text-only siblings of OptionGroup \u2014 same arrow-key navigation, Enter/Space confirm, click works. No image slot, denser by default. Use for "pick one of these" UX where each option fits in one line + optional one-line description. Props: `options: { value, label, description? }[]`, `orientation: "row" | "column"`, `bind: string`. Fires `trigger("choose", { value, index })`.' },
  render: (node, ctx) => {
    const options = node.options ?? [];
    const bind = node.bind ?? "";
    const orientation = node.orientation ?? "column";
    const selected = bind ? readPointer(ctx.data, bind) : void 0;
    const isRow = orientation === "row";
    const handleKey = (e, idx) => {
      const key = e.key;
      let next = idx;
      if (isRow) {
        if (key === "ArrowLeft" || key === "ArrowUp") next = (idx - 1 + options.length) % options.length;
        else if (key === "ArrowRight" || key === "ArrowDown") next = (idx + 1) % options.length;
      } else {
        if (key === "ArrowUp" || key === "ArrowLeft") next = (idx - 1 + options.length) % options.length;
        else if (key === "ArrowDown" || key === "ArrowRight") next = (idx + 1) % options.length;
      }
      if (key === "Enter" || key === " ") {
        e.preventDefault();
        if (bind) ctx.setBinding(bind, options[idx].value);
        ctx.trigger("choose", { value: options[idx].value, index: idx });
        return;
      }
      if (next !== idx) {
        e.preventDefault();
        const parent = e.currentTarget.parentElement;
        const nextEl = parent?.children[next];
        nextEl?.focus();
      }
    };
    return /* @__PURE__ */ jsx2(
      "div",
      {
        ...{ [UI]: "choice-list" },
        role: "radiogroup",
        "data-orientation": orientation,
        style: {
          display: "flex",
          flexDirection: isRow ? "row" : "column",
          gap: 6
        },
        children: options.map((opt, i) => {
          const isSelected = opt.value === selected || selected === void 0 && i === 0;
          return /* @__PURE__ */ jsxs2(
            "div",
            {
              role: "radio",
              "aria-checked": isSelected,
              tabIndex: isSelected ? 0 : -1,
              onClick: () => {
                if (bind) ctx.setBinding(bind, opt.value);
                ctx.trigger("choose", { value: opt.value, index: i });
              },
              onKeyDown: (e) => handleKey(e, i),
              style: {
                flex: isRow ? "1 1 0" : "0 0 auto",
                padding: "10px 14px",
                cursor: "pointer",
                outline: "none",
                borderRadius: "var(--dddk-radius-sm, 6px)",
                border: "1px solid",
                borderColor: isSelected ? "var(--dddk-accent, #6366f1)" : "var(--dddk-border, rgba(0,0,0,0.08))",
                background: isSelected ? "var(--dddk-accent-soft, rgba(99,102,241,0.08))" : "transparent",
                transition: "border-color 120ms, background 120ms"
              },
              onFocus: (e) => {
                e.currentTarget.style.boxShadow = "0 0 0 2px var(--dddk-accent-soft, rgba(99,102,241,0.2))";
              },
              onBlur: (e) => {
                e.currentTarget.style.boxShadow = "none";
              },
              children: [
                /* @__PURE__ */ jsx2("div", { style: { fontWeight: 500, fontSize: 14 }, children: opt.label }),
                opt.description && /* @__PURE__ */ jsx2("div", { style: { fontSize: 12.5, color: "var(--dddk-text-muted, #64748b)", marginTop: 2 }, children: opt.description })
              ]
            },
            opt.value
          );
        })
      }
    );
  }
};
var builtinPieces = [
  // layout
  Stack,
  Grid,
  Split,
  Card,
  Tabs,
  Group,
  // content
  Heading,
  Text,
  Code,
  Image,
  Tag,
  Divider,
  Markdown,
  // data
  Listing,
  Table,
  Metric,
  Timeline,
  // input
  TextField,
  TextArea,
  NumberField,
  PasswordField,
  Checkbox,
  Switch,
  Picker,
  DatePicker,
  Slider,
  FilePicker,
  // rich layout + selection
  MediaCard,
  OptionGroup,
  ChoiceList,
  // action
  Button,
  Link,
  IconButton,
  // feedback
  Spinner,
  ProgressBar,
  EmptyState,
  ErrorState,
  // slot
  Slot
];
function createBuiltinCatalog() {
  return new PieceCatalog(builtinPieces);
}

// src/ui/pieces/surface-bridge.ts
function envelopeToSurface(envelope) {
  const components = envelope.updateComponents?.components ?? [];
  if (components.length === 0) return null;
  const byId = /* @__PURE__ */ new Map();
  for (const c of components) byId.set(c.id, c);
  const rootComp = byId.get("root") ?? components[0];
  if (!rootComp) return null;
  const root = expand(rootComp, byId, /* @__PURE__ */ new Set());
  return {
    root,
    data: envelope.updateDataModel?.data ?? {}
  };
}
function expand(comp, byId, visited) {
  const { id, component, children, ...rest } = comp;
  const node = {
    kind: component,
    id,
    ...rest
  };
  if (visited.has(id)) return node;
  visited.add(id);
  if (Array.isArray(children) && children.length > 0) {
    const childNodes = [];
    for (const childId of children) {
      if (visited.has(childId)) continue;
      const child = byId.get(childId);
      if (child) childNodes.push(expand(child, byId, visited));
    }
    node.children = childNodes;
  }
  visited.delete(id);
  return node;
}
function surfaceToEnvelope(surface, surfaceId = "default") {
  const components = [];
  let nextAuto = 1;
  function visit(node, suggestedId) {
    const id = node.id ?? suggestedId ?? `auto_${nextAuto++}`;
    const childIds = [];
    if (Array.isArray(node.children)) {
      for (const child of node.children) {
        childIds.push(visit(child));
      }
    }
    const { kind, id: _drop, children: _drop2, ...rest } = node;
    const comp = {
      id,
      component: kind,
      ...childIds.length > 0 ? { children: childIds } : {},
      ...rest
    };
    components.push(comp);
    return id;
  }
  visit(surface.root, "root");
  return {
    version: "v0.10",
    updateComponents: { surfaceId, components },
    ...surface.data ? { updateDataModel: { surfaceId, data: surface.data } } : {}
  };
}

export {
  PieceCatalog,
  PieceRenderer,
  readPointer,
  resolveValue,
  builtinPieces,
  createBuiltinCatalog,
  envelopeToSurface,
  surfaceToEnvelope
};
//# sourceMappingURL=chunk-2E4ZNDF4.js.map