@braincrew-lab/langchain-canvas 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -1
- package/dist/index.js +217 -18
- package/dist/styles.css +11 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -374,7 +374,7 @@ interface ChatMessage {
|
|
|
374
374
|
interface IframeCommand {
|
|
375
375
|
artifactId: string;
|
|
376
376
|
/** style · structure (duplicate/delete/move/insert/insert_html) · group/ungroup · set_src · set_slide_style · clear. */
|
|
377
|
-
type: "set_style" | "style_persist" | "commit" | "clear" | "set_src" | "set_slide_style" | "duplicate" | "delete" | "move_up" | "move_down" | "insert" | "insert_html" | "group" | "ungroup";
|
|
377
|
+
type: "set_style" | "style_persist" | "commit" | "clear" | "set_src" | "set_slide_style" | "scroll_to" | "duplicate" | "delete" | "move_up" | "move_down" | "insert" | "insert_html" | "group" | "ungroup";
|
|
378
378
|
/** Target element (omitted for document-level inserts with no selection). */
|
|
379
379
|
cid?: string;
|
|
380
380
|
/** Members to wrap for `group`. */
|
|
@@ -383,6 +383,8 @@ interface IframeCommand {
|
|
|
383
383
|
value?: string;
|
|
384
384
|
/** Style map to apply to the slide root for `set_slide_style` (e.g. background, color). */
|
|
385
385
|
style?: Record<string, string>;
|
|
386
|
+
/** Heading index to scroll into view for `scroll_to`. */
|
|
387
|
+
index?: number;
|
|
386
388
|
/** Tag/block to insert for `insert` (e.g. "h2", "p", "button", "img", "hr", "section"). */
|
|
387
389
|
block?: string;
|
|
388
390
|
/** HTML fragment to insert for `insert_html` (a built-in section template). */
|
package/dist/index.js
CHANGED
|
@@ -180,7 +180,7 @@ var INSPECTOR_SCRIPT = `
|
|
|
180
180
|
fmtBar.className = "lcx-fmt";
|
|
181
181
|
fmtBar.style.left = Math.max(4, r.left) + "px";
|
|
182
182
|
fmtBar.style.top = Math.max(4, r.top - 40) + "px";
|
|
183
|
-
var specs = [["<b>B</b>", "bold"], ["<i>I</i>", "italic"], ["<u>U</u>", "underline"], ["\\uD83D\\uDD17", "createLink"]];
|
|
183
|
+
var specs = [["<b>B</b>", "bold"], ["<i>I</i>", "italic"], ["<u>U</u>", "underline"], ["•", "insertUnorderedList"], ["1.", "insertOrderedList"], ["\\uD83D\\uDD17", "createLink"]];
|
|
184
184
|
for (var i = 0; i < specs.length; i++) {
|
|
185
185
|
(function (spec) {
|
|
186
186
|
var btn = document.createElement("button");
|
|
@@ -568,6 +568,11 @@ var INSPECTOR_SCRIPT = `
|
|
|
568
568
|
var d = e.data;
|
|
569
569
|
if (!d || d.source !== MARK) return;
|
|
570
570
|
if (d.type === "clear") { clearSelected(); return; }
|
|
571
|
+
if (d.type === "scroll_to") {
|
|
572
|
+
var hs = document.querySelectorAll("h1,h2,h3,h4,h5,h6");
|
|
573
|
+
if (hs[d.index]) hs[d.index].scrollIntoView({ behavior: "smooth", block: "start" });
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
571
576
|
if (d.type === "set_style") { var el = byCid(d.cid); if (el) el.style[d.prop] = d.value; return; }
|
|
572
577
|
if (d.type === "style_persist") { var elp = byCid(d.cid); if (elp) { elp.style[d.prop] = d.value; emitEdit(elp); } return; }
|
|
573
578
|
if (d.type === "set_slide_style") {
|
|
@@ -1527,8 +1532,97 @@ var TEMPLATES = {
|
|
|
1527
1532
|
<p style="margin:0 0 22px;opacity:.9">Join thousands of teams already building with us.</p>
|
|
1528
1533
|
<a href="#" style="display:inline-block;padding:12px 24px;background:#fff;color:#4338ca;border-radius:10px;text-decoration:none;font-weight:700">Sign up</a>
|
|
1529
1534
|
</section>`
|
|
1535
|
+
},
|
|
1536
|
+
navbar: {
|
|
1537
|
+
label: "Nav bar",
|
|
1538
|
+
html: `<nav style="display:flex;align-items:center;justify-content:space-between;padding:18px 32px;background:#0b1020;color:#e6e8ef;border-bottom:1px solid #232a44">
|
|
1539
|
+
<strong style="font-size:18px;letter-spacing:-.01em">Brand</strong>
|
|
1540
|
+
<div style="display:flex;gap:26px;align-items:center;font-size:15px">
|
|
1541
|
+
<a href="#" style="color:#c7cddb;text-decoration:none">Product</a>
|
|
1542
|
+
<a href="#" style="color:#c7cddb;text-decoration:none">Pricing</a>
|
|
1543
|
+
<a href="#" style="color:#c7cddb;text-decoration:none">Docs</a>
|
|
1544
|
+
<a href="#" style="padding:9px 18px;background:#6366f1;color:#fff;border-radius:9px;text-decoration:none;font-weight:600">Sign in</a>
|
|
1545
|
+
</div>
|
|
1546
|
+
</nav>`
|
|
1547
|
+
},
|
|
1548
|
+
pricing: {
|
|
1549
|
+
label: "Pricing",
|
|
1550
|
+
html: `<section style="padding:56px 24px;background:#0b1020;color:#e6e8ef">
|
|
1551
|
+
<div style="max-width:960px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px">
|
|
1552
|
+
<div style="background:#151a2e;border:1px solid #232a44;border-radius:16px;padding:28px"><h3 style="margin:0 0 6px">Starter</h3><p style="font-size:34px;font-weight:800;margin:0 0 4px">$0<span style="font-size:15px;font-weight:400;color:#9aa4b2">/mo</span></p><ul style="margin:16px 0 0;padding-left:1.1em;color:#9aa4b2;line-height:1.9"><li>1 project</li><li>Community support</li></ul></div>
|
|
1553
|
+
<div style="background:#1b2140;border:1px solid #3a44f1;border-radius:16px;padding:28px"><h3 style="margin:0 0 6px">Pro</h3><p style="font-size:34px;font-weight:800;margin:0 0 4px">$29<span style="font-size:15px;font-weight:400;color:#9aa4b2">/mo</span></p><ul style="margin:16px 0 0;padding-left:1.1em;color:#c7cddb;line-height:1.9"><li>Unlimited projects</li><li>Priority support</li></ul></div>
|
|
1554
|
+
<div style="background:#151a2e;border:1px solid #232a44;border-radius:16px;padding:28px"><h3 style="margin:0 0 6px">Team</h3><p style="font-size:34px;font-weight:800;margin:0 0 4px">$99<span style="font-size:15px;font-weight:400;color:#9aa4b2">/mo</span></p><ul style="margin:16px 0 0;padding-left:1.1em;color:#9aa4b2;line-height:1.9"><li>SSO & roles</li><li>SLA</li></ul></div>
|
|
1555
|
+
</div>
|
|
1556
|
+
</section>`
|
|
1557
|
+
},
|
|
1558
|
+
stats: {
|
|
1559
|
+
label: "Stats",
|
|
1560
|
+
html: `<section style="padding:56px 24px;background:#0b1020;color:#e6e8ef">
|
|
1561
|
+
<div style="max-width:900px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:24px;text-align:center">
|
|
1562
|
+
<div><div style="font-size:44px;font-weight:800">12k+</div><div style="color:#9aa4b2">Customers</div></div>
|
|
1563
|
+
<div><div style="font-size:44px;font-weight:800">99.9%</div><div style="color:#9aa4b2">Uptime</div></div>
|
|
1564
|
+
<div><div style="font-size:44px;font-weight:800">4.9/5</div><div style="color:#9aa4b2">Rating</div></div>
|
|
1565
|
+
</div>
|
|
1566
|
+
</section>`
|
|
1567
|
+
},
|
|
1568
|
+
testimonial: {
|
|
1569
|
+
label: "Testimonial",
|
|
1570
|
+
html: `<section style="padding:64px 24px;background:#151a2e;color:#e6e8ef;text-align:center">
|
|
1571
|
+
<p style="max-width:640px;margin:0 auto 20px;font-size:24px;line-height:1.5;font-weight:600">\u201CThis is the tool our team didn't know it was missing. Shipped a landing page in an afternoon.\u201D</p>
|
|
1572
|
+
<p style="margin:0;color:#9aa4b2">Jordan Lee \xB7 Head of Product, Acme</p>
|
|
1573
|
+
</section>`
|
|
1574
|
+
},
|
|
1575
|
+
faq: {
|
|
1576
|
+
label: "FAQ",
|
|
1577
|
+
html: `<section style="padding:56px 24px;background:#0b1020;color:#e6e8ef">
|
|
1578
|
+
<div style="max-width:720px;margin:0 auto">
|
|
1579
|
+
<h2 style="margin:0 0 24px;font-size:26px">Frequently asked</h2>
|
|
1580
|
+
<div style="border-top:1px solid #232a44;padding:18px 0"><h4 style="margin:0 0 6px">Is there a free plan?</h4><p style="margin:0;color:#9aa4b2">Yes \u2014 the Starter plan is free forever.</p></div>
|
|
1581
|
+
<div style="border-top:1px solid #232a44;padding:18px 0"><h4 style="margin:0 0 6px">Can I cancel anytime?</h4><p style="margin:0;color:#9aa4b2">Absolutely, no questions asked.</p></div>
|
|
1582
|
+
</div>
|
|
1583
|
+
</section>`
|
|
1584
|
+
},
|
|
1585
|
+
gallery: {
|
|
1586
|
+
label: "Gallery",
|
|
1587
|
+
html: `<section style="padding:40px 24px;background:#0b1020">
|
|
1588
|
+
<div style="max-width:960px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px">
|
|
1589
|
+
<img src="https://placehold.co/400x300/1b2140/9aa4b2?text=1" alt="" style="width:100%;border-radius:12px;display:block" />
|
|
1590
|
+
<img src="https://placehold.co/400x300/1b2140/9aa4b2?text=2" alt="" style="width:100%;border-radius:12px;display:block" />
|
|
1591
|
+
<img src="https://placehold.co/400x300/1b2140/9aa4b2?text=3" alt="" style="width:100%;border-radius:12px;display:block" />
|
|
1592
|
+
</div>
|
|
1593
|
+
</section>`
|
|
1594
|
+
},
|
|
1595
|
+
contact: {
|
|
1596
|
+
label: "Contact form",
|
|
1597
|
+
html: `<section style="padding:56px 24px;background:#151a2e;color:#e6e8ef">
|
|
1598
|
+
<form style="max-width:480px;margin:0 auto;display:flex;flex-direction:column;gap:12px">
|
|
1599
|
+
<h2 style="margin:0 0 6px;font-size:24px">Get in touch</h2>
|
|
1600
|
+
<input placeholder="Your name" style="padding:12px 14px;border-radius:10px;border:1px solid #232a44;background:#0b1020;color:#e6e8ef" />
|
|
1601
|
+
<input placeholder="Email" style="padding:12px 14px;border-radius:10px;border:1px solid #232a44;background:#0b1020;color:#e6e8ef" />
|
|
1602
|
+
<textarea placeholder="Message" rows="4" style="padding:12px 14px;border-radius:10px;border:1px solid #232a44;background:#0b1020;color:#e6e8ef"></textarea>
|
|
1603
|
+
<button style="padding:12px;background:#6366f1;color:#fff;border:0;border-radius:10px;font-weight:700;cursor:pointer">Send</button>
|
|
1604
|
+
</form>
|
|
1605
|
+
</section>`
|
|
1606
|
+
},
|
|
1607
|
+
footer: {
|
|
1608
|
+
label: "Footer",
|
|
1609
|
+
html: `<footer style="padding:40px 32px;background:#080b16;color:#9aa4b2;display:flex;flex-wrap:wrap;gap:24px;justify-content:space-between;border-top:1px solid #232a44">
|
|
1610
|
+
<div><strong style="color:#e6e8ef;font-size:16px">Brand</strong><p style="margin:8px 0 0;max-width:260px;font-size:14px">Build faster with confidence.</p></div>
|
|
1611
|
+
<div style="display:flex;gap:48px;font-size:14px">
|
|
1612
|
+
<div style="display:flex;flex-direction:column;gap:8px"><a href="#" style="color:#9aa4b2;text-decoration:none">Product</a><a href="#" style="color:#9aa4b2;text-decoration:none">Pricing</a></div>
|
|
1613
|
+
<div style="display:flex;flex-direction:column;gap:8px"><a href="#" style="color:#9aa4b2;text-decoration:none">About</a><a href="#" style="color:#9aa4b2;text-decoration:none">Contact</a></div>
|
|
1614
|
+
</div>
|
|
1615
|
+
</footer>`
|
|
1530
1616
|
}
|
|
1531
1617
|
};
|
|
1618
|
+
function pageDoc(title, sections) {
|
|
1619
|
+
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title></head><body style="margin:0;font-family:'Segoe UI',system-ui,'Apple SD Gothic Neo','Noto Sans KR',sans-serif;background:#0b1020;color:#e6e8ef">${sections.join("\n")}</body></html>`;
|
|
1620
|
+
}
|
|
1621
|
+
var STARTERS = {
|
|
1622
|
+
landing: { label: "Landing page", build: () => pageDoc("Landing", [TEMPLATES.navbar.html, TEMPLATES.hero.html, TEMPLATES.features.html, TEMPLATES.pricing.html, TEMPLATES.cta.html, TEMPLATES.footer.html]) },
|
|
1623
|
+
saas: { label: "SaaS", build: () => pageDoc("SaaS", [TEMPLATES.navbar.html, TEMPLATES.hero.html, TEMPLATES.stats.html, TEMPLATES.features.html, TEMPLATES.testimonial.html, TEMPLATES.faq.html, TEMPLATES.cta.html, TEMPLATES.footer.html]) },
|
|
1624
|
+
portfolio: { label: "Portfolio", build: () => pageDoc("Portfolio", [TEMPLATES.navbar.html, TEMPLATES.hero.html, TEMPLATES.gallery.html, TEMPLATES.contact.html, TEMPLATES.footer.html]) }
|
|
1625
|
+
};
|
|
1532
1626
|
var SLIDE_TEMPLATES = {
|
|
1533
1627
|
title: {
|
|
1534
1628
|
label: "Title",
|
|
@@ -1628,6 +1722,27 @@ var SLIDE_FONTS = {
|
|
|
1628
1722
|
rounded: { label: "Rounded", stack: "'Trebuchet MS', 'Segoe UI', 'Apple SD Gothic Neo', sans-serif" },
|
|
1629
1723
|
condensed: { label: "Condensed", stack: "'Arial Narrow', 'Helvetica Neue', 'Apple SD Gothic Neo', sans-serif" }
|
|
1630
1724
|
};
|
|
1725
|
+
function checkA11y(html) {
|
|
1726
|
+
if (typeof DOMParser === "undefined") return [];
|
|
1727
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
1728
|
+
const issues = [];
|
|
1729
|
+
const count = (sel, keep) => Array.from(doc.querySelectorAll(sel)).filter(keep).length;
|
|
1730
|
+
const imgs = count("img", (el) => !el.getAttribute("alt"));
|
|
1731
|
+
if (imgs) issues.push(`${imgs} image${imgs > 1 ? "s" : ""} missing alt text`);
|
|
1732
|
+
const links = count("a", (el) => !el.textContent?.trim() && !el.getAttribute("aria-label"));
|
|
1733
|
+
if (links) issues.push(`${links} link${links > 1 ? "s" : ""} with no text or aria-label`);
|
|
1734
|
+
const btns = count("button", (el) => !el.textContent?.trim() && !el.getAttribute("aria-label"));
|
|
1735
|
+
if (btns) issues.push(`${btns} button${btns > 1 ? "s" : ""} with no accessible label`);
|
|
1736
|
+
const fields = count("input, textarea, select", (el) => {
|
|
1737
|
+
const id = el.getAttribute("id");
|
|
1738
|
+
const labelled = id && doc.querySelector(`label[for="${CSS.escape(id)}"]`);
|
|
1739
|
+
return !labelled && !el.getAttribute("aria-label") && !el.getAttribute("placeholder");
|
|
1740
|
+
});
|
|
1741
|
+
if (fields) issues.push(`${fields} form field${fields > 1 ? "s" : ""} with no label`);
|
|
1742
|
+
if (!doc.documentElement.getAttribute("lang")) issues.push("Missing a <html lang> attribute");
|
|
1743
|
+
if (!doc.querySelector("h1")) issues.push("No <h1> \u2014 every page needs one top-level heading");
|
|
1744
|
+
return issues;
|
|
1745
|
+
}
|
|
1631
1746
|
var SLIDE_W = 1280;
|
|
1632
1747
|
var SCROLL_FIX = "<style>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
|
|
1633
1748
|
function withScrollableBody(html) {
|
|
@@ -1667,6 +1782,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1667
1782
|
const iframeCommand = useCanvasStore((s) => s.iframeCommand);
|
|
1668
1783
|
const [device, setDevice] = useState("desktop");
|
|
1669
1784
|
const [mode, setMode] = useState("design");
|
|
1785
|
+
const [a11y, setA11y] = useState(null);
|
|
1670
1786
|
const lastSelfHtml = useRef(null);
|
|
1671
1787
|
const srcDocRef = useRef("");
|
|
1672
1788
|
const isFixedSlide = Boolean(artifact2.meta?.ratio);
|
|
@@ -1679,6 +1795,14 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1679
1795
|
}, [artifact2.data.html, mode, isFixedSlide]);
|
|
1680
1796
|
const selected = selections.filter((s) => s.artifactId === artifact2.id);
|
|
1681
1797
|
const single = selected.length === 1 ? selected[0] : null;
|
|
1798
|
+
const outline = useMemo(() => {
|
|
1799
|
+
if (typeof DOMParser === "undefined") return [];
|
|
1800
|
+
const doc = new DOMParser().parseFromString(artifact2.data.html, "text/html");
|
|
1801
|
+
return Array.from(doc.querySelectorAll("h1,h2,h3,h4,h5,h6")).map((h) => ({
|
|
1802
|
+
level: Number(h.tagName[1]),
|
|
1803
|
+
text: (h.textContent ?? "").trim().slice(0, 48) || "(untitled)"
|
|
1804
|
+
}));
|
|
1805
|
+
}, [artifact2.data.html]);
|
|
1682
1806
|
useEffect(() => {
|
|
1683
1807
|
function onMessage(event) {
|
|
1684
1808
|
if (event.source !== iframeRef.current?.contentWindow) return;
|
|
@@ -1763,23 +1887,60 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1763
1887
|
] }),
|
|
1764
1888
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: "Add" }),
|
|
1765
1889
|
(ratio ? BLOCKS.filter((b) => SLIDE_BLOCK_TAGS.has(b.tag)) : BLOCKS).map((b) => /* @__PURE__ */ jsx("button", { className: "cv-html-add", onClick: () => command("insert", { block: b.tag }), children: b.label }, b.tag)),
|
|
1766
|
-
!ratio && /* @__PURE__ */ jsxs(
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1890
|
+
!ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1891
|
+
/* @__PURE__ */ jsxs(
|
|
1892
|
+
"select",
|
|
1893
|
+
{
|
|
1894
|
+
className: "cv-html-tpl",
|
|
1895
|
+
value: "",
|
|
1896
|
+
title: "Start from a full page template (replaces the page)",
|
|
1897
|
+
onChange: (e) => {
|
|
1898
|
+
const s = STARTERS[e.target.value];
|
|
1899
|
+
if (s) applyEvent({ type: "canvas.patch", id: artifact2.id, patch: { html: s.build() } });
|
|
1900
|
+
e.currentTarget.value = "";
|
|
1901
|
+
},
|
|
1902
|
+
children: [
|
|
1903
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Page\u2026" }),
|
|
1904
|
+
Object.entries(STARTERS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1905
|
+
]
|
|
1906
|
+
}
|
|
1907
|
+
),
|
|
1908
|
+
/* @__PURE__ */ jsxs(
|
|
1909
|
+
"select",
|
|
1910
|
+
{
|
|
1911
|
+
className: "cv-html-tpl",
|
|
1912
|
+
value: "",
|
|
1913
|
+
title: "Insert a section template",
|
|
1914
|
+
onChange: (e) => {
|
|
1915
|
+
const t = TEMPLATES[e.target.value];
|
|
1916
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
|
|
1917
|
+
e.currentTarget.value = "";
|
|
1918
|
+
},
|
|
1919
|
+
children: [
|
|
1920
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Section\u2026" }),
|
|
1921
|
+
Object.entries(TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1922
|
+
]
|
|
1923
|
+
}
|
|
1924
|
+
),
|
|
1925
|
+
outline.length > 0 && /* @__PURE__ */ jsxs(
|
|
1926
|
+
"select",
|
|
1927
|
+
{
|
|
1928
|
+
className: "cv-html-tpl",
|
|
1929
|
+
value: "",
|
|
1930
|
+
title: "Jump to a heading",
|
|
1931
|
+
onChange: (e) => {
|
|
1932
|
+
const idx = Number(e.target.value);
|
|
1933
|
+
if (!Number.isNaN(idx)) sendIframeCommand({ artifactId: artifact2.id, type: "scroll_to", index: idx });
|
|
1934
|
+
e.currentTarget.value = "";
|
|
1935
|
+
},
|
|
1936
|
+
children: [
|
|
1937
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Outline\u2026" }),
|
|
1938
|
+
outline.map((h, i) => /* @__PURE__ */ jsx("option", { value: i, children: "\xA0".repeat((h.level - 1) * 2) + h.text }, i))
|
|
1939
|
+
]
|
|
1940
|
+
}
|
|
1941
|
+
),
|
|
1942
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Accessibility check", onClick: () => setA11y(checkA11y(artifact2.data.html)), children: "\u267F Check" })
|
|
1943
|
+
] }),
|
|
1783
1944
|
ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1784
1945
|
/* @__PURE__ */ jsxs(
|
|
1785
1946
|
"select",
|
|
@@ -1898,6 +2059,18 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1898
2059
|
/* @__PURE__ */ jsx("button", { className: mode === "code" ? "is-on" : "", onClick: () => setMode("code"), children: "Code" })
|
|
1899
2060
|
] })
|
|
1900
2061
|
] }),
|
|
2062
|
+
a11y !== null && /* @__PURE__ */ jsxs("div", { className: "cv-a11y", role: "status", children: [
|
|
2063
|
+
/* @__PURE__ */ jsx("button", { className: "cv-a11y__close", onClick: () => setA11y(null), "aria-label": "Dismiss", children: "\xD7" }),
|
|
2064
|
+
a11y.length === 0 ? /* @__PURE__ */ jsx("span", { className: "cv-a11y__ok", children: "\u267F No accessibility issues found" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2065
|
+
/* @__PURE__ */ jsxs("b", { children: [
|
|
2066
|
+
"\u267F ",
|
|
2067
|
+
a11y.length,
|
|
2068
|
+
" accessibility issue",
|
|
2069
|
+
a11y.length > 1 ? "s" : ""
|
|
2070
|
+
] }),
|
|
2071
|
+
/* @__PURE__ */ jsx("ul", { children: a11y.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) })
|
|
2072
|
+
] })
|
|
2073
|
+
] }),
|
|
1901
2074
|
mode === "design" ? /* @__PURE__ */ jsx("div", { className: `cv-html-stage${ratio ? " cv-html-stage--slide" : ""}`, ref: stageRef, children: ratio ? (
|
|
1902
2075
|
// Scaled slide: the iframe lays out at its full design size, then a
|
|
1903
2076
|
// transform shrinks it to fit; a sized wrapper reserves the scaled box
|
|
@@ -2231,6 +2404,30 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2231
2404
|
}
|
|
2232
2405
|
setOpen(false);
|
|
2233
2406
|
};
|
|
2407
|
+
const openInTab = () => {
|
|
2408
|
+
const html = artifact2.type === "html" ? artifact2.data.html : artifact2.type === "slides" ? slidesToPrintHtml(artifact2.data, artifact2.title) : (() => {
|
|
2409
|
+
const h = getRenderedHtml();
|
|
2410
|
+
return h == null ? null : toStandaloneHtml(artifact2.title, h);
|
|
2411
|
+
})();
|
|
2412
|
+
if (html == null) return;
|
|
2413
|
+
const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
|
2414
|
+
window.open(url, "_blank", "noopener");
|
|
2415
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
2416
|
+
setOpen(false);
|
|
2417
|
+
};
|
|
2418
|
+
const [copied, setCopied] = useState(false);
|
|
2419
|
+
const copyHtml = async () => {
|
|
2420
|
+
const html = artifact2.type === "html" ? artifact2.data.html : getRenderedHtml();
|
|
2421
|
+
if (html == null) return;
|
|
2422
|
+
try {
|
|
2423
|
+
await navigator.clipboard.writeText(
|
|
2424
|
+
artifact2.type === "html" ? html : toStandaloneHtml(artifact2.title, html)
|
|
2425
|
+
);
|
|
2426
|
+
setCopied(true);
|
|
2427
|
+
setTimeout(() => setCopied(false), 1400);
|
|
2428
|
+
} catch {
|
|
2429
|
+
}
|
|
2430
|
+
};
|
|
2234
2431
|
return /* @__PURE__ */ jsxs("div", { className: "cv-export", children: [
|
|
2235
2432
|
/* @__PURE__ */ jsx(
|
|
2236
2433
|
"button",
|
|
@@ -2245,6 +2442,8 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2245
2442
|
open && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2246
2443
|
/* @__PURE__ */ jsx("div", { className: "cv-export__scrim", onClick: () => setOpen(false) }),
|
|
2247
2444
|
/* @__PURE__ */ jsxs("div", { className: "cv-export__menu", role: "menu", children: [
|
|
2445
|
+
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: openInTab, children: "Open in new tab \u2197" }),
|
|
2446
|
+
/* @__PURE__ */ jsx("button", { role: "menuitem", onClick: copyHtml, children: copied ? "Copied \u2713" : "Copy HTML" }),
|
|
2248
2447
|
/* @__PURE__ */ jsxs("button", { role: "menuitem", onClick: exportHtml, children: [
|
|
2249
2448
|
"HTML ",
|
|
2250
2449
|
/* @__PURE__ */ jsx("span", { className: "cv-export__ext", children: ".html" })
|
package/dist/styles.css
CHANGED
|
@@ -761,6 +761,17 @@
|
|
|
761
761
|
/* --- html renderer (base substrate) ------------------------------------------- */
|
|
762
762
|
|
|
763
763
|
.cv-html-wrap { display: flex; flex-direction: column; height: 100%; min-height: 0; gap: 10px; }
|
|
764
|
+
.cv-a11y {
|
|
765
|
+
position: relative; flex: none; padding: 12px 34px 12px 16px;
|
|
766
|
+
border: 1px solid var(--cv-border); border-radius: 10px; background: var(--cv-surface);
|
|
767
|
+
font-size: 13px; color: var(--cv-text);
|
|
768
|
+
}
|
|
769
|
+
.cv-a11y ul { margin: 8px 0 0; padding-left: 18px; color: var(--cv-muted); line-height: 1.7; }
|
|
770
|
+
.cv-a11y__ok { color: var(--cv-good, #1f9d6b); font-weight: 600; }
|
|
771
|
+
.cv-a11y__close {
|
|
772
|
+
position: absolute; top: 6px; right: 8px; border: 0; background: transparent;
|
|
773
|
+
color: var(--cv-muted); font-size: 18px; cursor: pointer; line-height: 1;
|
|
774
|
+
}
|
|
764
775
|
.cv-html-bar {
|
|
765
776
|
display: flex; align-items: center; flex-wrap: wrap; gap: 5px;
|
|
766
777
|
padding: 7px 9px; border: 1px solid var(--cv-border); border-radius: 9px; background: var(--cv-surface);
|
package/package.json
CHANGED