@tangle-network/agent-app 0.24.0 → 0.26.0

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.
@@ -9,13 +9,13 @@ import {
9
9
  } from "./chunk-JZAJE3JL.js";
10
10
 
11
11
  // src/design-canvas-react/components/DesignCanvas.tsx
12
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo, useRef as useRef4, useState as useState4, useSyncExternalStore } from "react";
12
+ import { useCallback as useCallback2, useEffect as useEffect3, useMemo, useRef as useRef5, useState as useState5, useSyncExternalStore } from "react";
13
13
 
14
14
  // src/design-canvas-react/engine/command-stack.ts
15
15
  var SCENE_COMMAND_HISTORY_LIMIT = 200;
16
- function createSceneCommandStack(document, activePageId) {
16
+ function createSceneCommandStack(document2, activePageId) {
17
17
  let state = {
18
- document,
18
+ document: document2,
19
19
  activePageId,
20
20
  selectedElementIds: [],
21
21
  zoom: 1,
@@ -862,6 +862,16 @@ var DuplicateGlyph = glyph(
862
862
  var ZoomFitGlyph = glyph(
863
863
  /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2("path", { d: "M15 3h6v6M14 10l6.1-6.1M9 21H3v-6M10 14l-6.1 6.1" }) })
864
864
  );
865
+ var ExportGlyph = glyph(
866
+ /* @__PURE__ */ jsx2(Fragment2, { children: /* @__PURE__ */ jsx2("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3" }) })
867
+ );
868
+ var ShapesGlyph = glyph(
869
+ /* @__PURE__ */ jsxs2(Fragment2, { children: [
870
+ /* @__PURE__ */ jsx2("rect", { x: "3", y: "13", width: "8", height: "8", rx: "1" }),
871
+ /* @__PURE__ */ jsx2("circle", { cx: "17", cy: "17", r: "4" }),
872
+ /* @__PURE__ */ jsx2("path", { d: "M8.5 3 13 11H4z" })
873
+ ] })
874
+ );
865
875
 
866
876
  // src/design-canvas-react/components/PagesStrip.tsx
867
877
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -1263,11 +1273,54 @@ function VerticalRuler({ pageHeight, zoom, scrollTop, guides, onGuidesChange })
1263
1273
  }
1264
1274
 
1265
1275
  // src/design-canvas-react/components/Toolbar.tsx
1266
- import { useState as useState3 } from "react";
1276
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
1267
1277
  import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1278
+ var FONT_FAMILIES = [
1279
+ "Inter",
1280
+ "Arial",
1281
+ "Helvetica",
1282
+ "Verdana",
1283
+ "Tahoma",
1284
+ "Trebuchet MS",
1285
+ "Times New Roman",
1286
+ "Georgia",
1287
+ "Garamond",
1288
+ "Courier New",
1289
+ "Brush Script MT",
1290
+ "Impact"
1291
+ ];
1268
1292
  var BTN2 = "flex h-7 w-7 items-center justify-center rounded border border-[var(--border-default)] text-[var(--text-secondary)] transition hover:text-[var(--text-primary)] disabled:cursor-default disabled:opacity-40";
1269
1293
  var BTN_ACTIVE = `${BTN2} border-[var(--brand-primary)] text-[var(--brand-primary)] hover:text-[var(--brand-primary)]`;
1270
- var SEP = /* @__PURE__ */ jsx5("div", { className: "mx-1 h-5 w-px bg-[var(--border-default)]" });
1294
+ var SEP = /* @__PURE__ */ jsx5("div", { className: "mx-1 h-5 w-px shrink-0 bg-[var(--border-default)]" });
1295
+ var POPOVER_PANEL = "absolute top-full left-0 z-50 mt-1 flex flex-col rounded border border-[var(--border-default)] bg-[var(--bg-input)] shadow-lg";
1296
+ function Popover({
1297
+ open,
1298
+ onClose,
1299
+ trigger,
1300
+ children
1301
+ }) {
1302
+ const ref = useRef3(null);
1303
+ useEffect2(() => {
1304
+ if (!open) return;
1305
+ function onDocPointer(event) {
1306
+ if (ref.current && !ref.current.contains(event.target)) onClose();
1307
+ }
1308
+ function onKey(event) {
1309
+ if (event.key === "Escape") onClose();
1310
+ }
1311
+ document.addEventListener("pointerdown", onDocPointer);
1312
+ document.addEventListener("keydown", onKey);
1313
+ return () => {
1314
+ document.removeEventListener("pointerdown", onDocPointer);
1315
+ document.removeEventListener("keydown", onKey);
1316
+ };
1317
+ }, [open, onClose]);
1318
+ return /* @__PURE__ */ jsxs5("div", { ref, className: "relative", children: [
1319
+ trigger,
1320
+ open ? children : null
1321
+ ] });
1322
+ }
1323
+ var FIELD_LABEL = "text-[9px] uppercase tracking-wide text-[var(--text-muted)]";
1271
1324
  function NumberInput({
1272
1325
  label,
1273
1326
  value,
@@ -1283,7 +1336,7 @@ function NumberInput({
1283
1336
  setRaw(null);
1284
1337
  }
1285
1338
  return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1286
- /* @__PURE__ */ jsx5("span", { className: "text-[9px] uppercase tracking-wide text-[var(--text-muted)]", children: label }),
1339
+ /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: label }),
1287
1340
  /* @__PURE__ */ jsx5(
1288
1341
  "input",
1289
1342
  {
@@ -1302,16 +1355,170 @@ function NumberInput({
1302
1355
  )
1303
1356
  ] });
1304
1357
  }
1305
- function ColorSwatch({ label, value, onCommit }) {
1306
- return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5 cursor-pointer", children: [
1307
- /* @__PURE__ */ jsx5("span", { className: "text-[9px] uppercase tracking-wide text-[var(--text-muted)]", children: label }),
1358
+ function SelectControl({
1359
+ label,
1360
+ value,
1361
+ options,
1362
+ disabled,
1363
+ onChange,
1364
+ buttonClassName = "w-24"
1365
+ }) {
1366
+ const [open, setOpen] = useState3(false);
1367
+ const current = options.find((o) => o.value === value);
1368
+ return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1369
+ /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: label }),
1308
1370
  /* @__PURE__ */ jsx5(
1309
- "input",
1371
+ Popover,
1310
1372
  {
1311
- type: "color",
1312
- value: value.startsWith("#") ? value : "#ffffff",
1313
- onChange: (event) => onCommit(event.target.value),
1314
- className: "h-6 w-10 cursor-pointer rounded border border-[var(--border-default)] p-0.5"
1373
+ open,
1374
+ onClose: () => setOpen(false),
1375
+ trigger: /* @__PURE__ */ jsxs5(
1376
+ "button",
1377
+ {
1378
+ type: "button",
1379
+ disabled,
1380
+ "aria-haspopup": "listbox",
1381
+ "aria-expanded": open,
1382
+ onClick: () => setOpen((v) => !v),
1383
+ className: `${buttonClassName} flex items-center justify-between gap-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-0.5 text-left text-[12px] text-[var(--text-primary)] outline-none hover:border-[var(--brand-primary)] disabled:cursor-default disabled:opacity-40`,
1384
+ children: [
1385
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: current?.label ?? value }),
1386
+ /* @__PURE__ */ jsx5("span", { className: "text-[8px] text-[var(--text-muted)]", children: "\u25BE" })
1387
+ ]
1388
+ }
1389
+ ),
1390
+ children: /* @__PURE__ */ jsx5("div", { role: "listbox", className: `${POPOVER_PANEL} max-h-64 w-44 overflow-y-auto py-1`, children: options.map((opt) => /* @__PURE__ */ jsx5(
1391
+ "button",
1392
+ {
1393
+ type: "button",
1394
+ role: "option",
1395
+ "aria-selected": opt.value === value,
1396
+ onClick: () => {
1397
+ onChange(opt.value);
1398
+ setOpen(false);
1399
+ },
1400
+ className: `px-3 py-1 text-left text-[12px] hover:bg-[var(--brand-primary)]/10 ${opt.value === value ? "text-[var(--brand-primary)]" : "text-[var(--text-primary)]"}`,
1401
+ children: opt.label
1402
+ },
1403
+ opt.value
1404
+ )) })
1405
+ }
1406
+ )
1407
+ ] });
1408
+ }
1409
+ function FontPicker({
1410
+ value,
1411
+ disabled,
1412
+ onChange
1413
+ }) {
1414
+ const [open, setOpen] = useState3(false);
1415
+ const [query, setQuery] = useState3("");
1416
+ const families = FONT_FAMILIES.includes(value) ? [...FONT_FAMILIES] : [value, ...FONT_FAMILIES];
1417
+ const filtered = families.filter((f) => f.toLowerCase().includes(query.trim().toLowerCase()));
1418
+ return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1419
+ /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: "Font" }),
1420
+ /* @__PURE__ */ jsx5(
1421
+ Popover,
1422
+ {
1423
+ open,
1424
+ onClose: () => {
1425
+ setOpen(false);
1426
+ setQuery("");
1427
+ },
1428
+ trigger: /* @__PURE__ */ jsxs5(
1429
+ "button",
1430
+ {
1431
+ type: "button",
1432
+ disabled,
1433
+ "aria-haspopup": "listbox",
1434
+ "aria-expanded": open,
1435
+ "aria-label": "Font family",
1436
+ onClick: () => setOpen((v) => !v),
1437
+ className: "flex w-28 items-center justify-between gap-1 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-0.5 text-left text-[12px] text-[var(--text-primary)] outline-none hover:border-[var(--brand-primary)] disabled:cursor-default disabled:opacity-40",
1438
+ style: { fontFamily: value },
1439
+ children: [
1440
+ /* @__PURE__ */ jsx5("span", { className: "truncate", children: value }),
1441
+ /* @__PURE__ */ jsx5("span", { className: "text-[8px] text-[var(--text-muted)]", children: "\u25BE" })
1442
+ ]
1443
+ }
1444
+ ),
1445
+ children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-52`, children: [
1446
+ /* @__PURE__ */ jsx5(
1447
+ "input",
1448
+ {
1449
+ autoFocus: true,
1450
+ value: query,
1451
+ onChange: (event) => setQuery(event.target.value),
1452
+ placeholder: "Search fonts",
1453
+ "aria-label": "Search fonts",
1454
+ className: "m-1 rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1455
+ }
1456
+ ),
1457
+ /* @__PURE__ */ jsx5("div", { role: "listbox", className: "max-h-60 overflow-y-auto py-1", children: filtered.length === 0 ? /* @__PURE__ */ jsx5("div", { className: "px-3 py-2 text-[11px] text-[var(--text-muted)]", children: "No matches" }) : filtered.map((family) => /* @__PURE__ */ jsx5(
1458
+ "button",
1459
+ {
1460
+ type: "button",
1461
+ role: "option",
1462
+ "aria-selected": family === value,
1463
+ onClick: () => {
1464
+ onChange(family);
1465
+ setOpen(false);
1466
+ setQuery("");
1467
+ },
1468
+ style: { fontFamily: family },
1469
+ className: `block w-full px-3 py-1 text-left text-[13px] hover:bg-[var(--brand-primary)]/10 ${family === value ? "text-[var(--brand-primary)]" : "text-[var(--text-primary)]"}`,
1470
+ children: family
1471
+ },
1472
+ family
1473
+ )) })
1474
+ ] })
1475
+ }
1476
+ )
1477
+ ] });
1478
+ }
1479
+ function ColorSwatch({ label, value, onCommit, disabled }) {
1480
+ const [open, setOpen] = useState3(false);
1481
+ const normalized = value.startsWith("#") ? value : "#ffffff";
1482
+ return /* @__PURE__ */ jsxs5("div", { className: "flex flex-col items-center gap-0.5", children: [
1483
+ /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: label }),
1484
+ /* @__PURE__ */ jsx5(
1485
+ Popover,
1486
+ {
1487
+ open,
1488
+ onClose: () => setOpen(false),
1489
+ trigger: /* @__PURE__ */ jsx5(
1490
+ "button",
1491
+ {
1492
+ type: "button",
1493
+ disabled,
1494
+ "aria-label": `${label} color`,
1495
+ onClick: () => setOpen((v) => !v),
1496
+ className: "h-6 w-10 rounded border border-[var(--border-default)] disabled:cursor-default disabled:opacity-40",
1497
+ style: { backgroundColor: normalized }
1498
+ }
1499
+ ),
1500
+ children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-40 gap-2 p-2`, children: [
1501
+ /* @__PURE__ */ jsx5(
1502
+ "input",
1503
+ {
1504
+ type: "color",
1505
+ "aria-label": `${label} color picker`,
1506
+ value: normalized,
1507
+ onChange: (event) => onCommit(event.target.value),
1508
+ className: "h-8 w-full cursor-pointer rounded border border-[var(--border-default)] p-0.5"
1509
+ }
1510
+ ),
1511
+ /* @__PURE__ */ jsx5(
1512
+ "input",
1513
+ {
1514
+ type: "text",
1515
+ "aria-label": `${label} hex value`,
1516
+ value,
1517
+ onChange: (event) => onCommit(event.target.value),
1518
+ className: "rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1519
+ }
1520
+ )
1521
+ ] })
1315
1522
  }
1316
1523
  )
1317
1524
  ] });
@@ -1355,16 +1562,18 @@ function Toolbar({
1355
1562
  const selectedIds = selectedElements.map((e) => e.id);
1356
1563
  const isGroup = single?.kind === "group";
1357
1564
  const groupable = selectedElements.length >= 2;
1358
- return /* @__PURE__ */ jsxs5("div", { className: "flex h-11 shrink-0 items-center gap-2 overflow-x-auto border-b border-[var(--border-default)] bg-[var(--bg-input)] px-3", children: [
1359
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Undo", disabled: !canUndo || !canWrite, onClick: onUndo, className: BTN2, children: /* @__PURE__ */ jsx5(UndoGlyph, { className: "h-3.5 w-3.5" }) }),
1360
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Redo", disabled: !canRedo || !canWrite, onClick: onRedo, className: BTN2, children: /* @__PURE__ */ jsx5(RedoGlyph, { className: "h-3.5 w-3.5" }) }),
1361
- SEP,
1362
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle rulers", "aria-pressed": showRulers, onClick: onToggleRulers, className: showRulers ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(RulerGlyph, { className: "h-3.5 w-3.5" }) }),
1363
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle grid", "aria-pressed": gridEnabled, onClick: onToggleGrid, className: gridEnabled ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(GridGlyph, { className: "h-3.5 w-3.5" }) }),
1364
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle snap", "aria-pressed": snapEnabled, onClick: onToggleSnap, className: snapEnabled ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(MagnetGlyph, { className: "h-3.5 w-3.5" }) }),
1365
- /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle bleed overlay", "aria-pressed": showBleed, onClick: onToggleBleed, className: showBleed ? BTN_ACTIVE : BTN2, disabled: !page.bleed, children: /* @__PURE__ */ jsx5(BleedGlyph, { className: "h-3.5 w-3.5" }) }),
1565
+ return /* @__PURE__ */ jsxs5("div", { className: "flex min-h-11 shrink-0 flex-wrap items-center gap-x-2 gap-y-1 border-b border-[var(--border-default)] bg-[var(--bg-input)] px-3 py-1", children: [
1566
+ /* @__PURE__ */ jsxs5("div", { className: "flex shrink-0 items-center gap-2", children: [
1567
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Undo", disabled: !canUndo || !canWrite, onClick: onUndo, className: BTN2, children: /* @__PURE__ */ jsx5(UndoGlyph, { className: "h-3.5 w-3.5" }) }),
1568
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Redo", disabled: !canRedo || !canWrite, onClick: onRedo, className: BTN2, children: /* @__PURE__ */ jsx5(RedoGlyph, { className: "h-3.5 w-3.5" }) }),
1569
+ SEP,
1570
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle rulers", "aria-pressed": showRulers, onClick: onToggleRulers, className: showRulers ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(RulerGlyph, { className: "h-3.5 w-3.5" }) }),
1571
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle grid", "aria-pressed": gridEnabled, onClick: onToggleGrid, className: gridEnabled ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(GridGlyph, { className: "h-3.5 w-3.5" }) }),
1572
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle snap", "aria-pressed": snapEnabled, onClick: onToggleSnap, className: snapEnabled ? BTN_ACTIVE : BTN2, children: /* @__PURE__ */ jsx5(MagnetGlyph, { className: "h-3.5 w-3.5" }) }),
1573
+ /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Toggle bleed overlay", "aria-pressed": showBleed, onClick: onToggleBleed, className: showBleed ? BTN_ACTIVE : BTN2, disabled: !page.bleed, children: /* @__PURE__ */ jsx5(BleedGlyph, { className: "h-3.5 w-3.5" }) })
1574
+ ] }),
1366
1575
  SEP,
1367
- hasSelection ? /* @__PURE__ */ jsx5(
1576
+ /* @__PURE__ */ jsx5("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1", children: hasSelection ? /* @__PURE__ */ jsx5(
1368
1577
  SelectionControls,
1369
1578
  {
1370
1579
  elements: selectedElements,
@@ -1392,7 +1601,7 @@ function Toolbar({
1392
1601
  onSetPageProps,
1393
1602
  onSetPageGuides
1394
1603
  }
1395
- )
1604
+ ) })
1396
1605
  ] });
1397
1606
  }
1398
1607
  function SelectionControls({
@@ -1458,75 +1667,69 @@ function SelectionControls({
1458
1667
  children: single.locked ? /* @__PURE__ */ jsx5(LockGlyph, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx5(UnlockGlyph, { className: "h-3.5 w-3.5" })
1459
1668
  }
1460
1669
  ) : null,
1461
- single && onBindSlot ? /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
1462
- /* @__PURE__ */ jsx5(
1463
- "button",
1464
- {
1465
- type: "button",
1466
- "aria-label": currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1467
- onClick: () => {
1468
- setSlotInput(currentSlot ?? "");
1469
- setSlotPopoverOpen((v) => !v);
1470
- },
1471
- className: currentSlot ? BTN_ACTIVE : BTN2,
1472
- title: currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1473
- children: /* @__PURE__ */ jsx5(SlotGlyph, { className: "h-3.5 w-3.5" })
1474
- }
1475
- ),
1476
- slotPopoverOpen ? /* @__PURE__ */ jsxs5("div", { className: "absolute top-full left-0 z-50 mt-1 flex w-48 flex-col gap-2 rounded border border-[var(--border-default)] bg-[var(--bg-input)] p-2 shadow-lg", children: [
1477
- /* @__PURE__ */ jsx5(
1478
- "input",
1670
+ single && onBindSlot ? /* @__PURE__ */ jsx5(
1671
+ Popover,
1672
+ {
1673
+ open: slotPopoverOpen,
1674
+ onClose: () => setSlotPopoverOpen(false),
1675
+ trigger: /* @__PURE__ */ jsx5(
1676
+ "button",
1479
1677
  {
1480
- autoFocus: true,
1481
- value: slotInput,
1482
- onChange: (event) => setSlotInput(event.target.value),
1483
- placeholder: "slot-name",
1484
- className: "rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1678
+ type: "button",
1679
+ "aria-label": currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1680
+ onClick: () => {
1681
+ setSlotInput(currentSlot ?? "");
1682
+ setSlotPopoverOpen((v) => !v);
1683
+ },
1684
+ className: currentSlot ? BTN_ACTIVE : BTN2,
1685
+ title: currentSlot ? `Slot: ${currentSlot}` : "Bind slot",
1686
+ children: /* @__PURE__ */ jsx5(SlotGlyph, { className: "h-3.5 w-3.5" })
1485
1687
  }
1486
1688
  ),
1487
- /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1689
+ children: /* @__PURE__ */ jsxs5("div", { className: `${POPOVER_PANEL} w-48 gap-2 p-2`, children: [
1488
1690
  /* @__PURE__ */ jsx5(
1489
- "button",
1691
+ "input",
1490
1692
  {
1491
- type: "button",
1492
- onClick: () => {
1493
- onBindSlot(slotInput.trim() || null);
1494
- setSlotPopoverOpen(false);
1495
- },
1496
- className: "flex-1 rounded border border-[var(--brand-primary)] px-2 py-0.5 text-[11px] text-[var(--brand-primary)] hover:bg-[var(--brand-primary)]/10",
1497
- children: slotInput.trim() ? "Bind" : "Unbind"
1693
+ autoFocus: true,
1694
+ value: slotInput,
1695
+ onChange: (event) => setSlotInput(event.target.value),
1696
+ placeholder: "slot-name",
1697
+ className: "rounded border border-[var(--border-default)] bg-transparent px-2 py-1 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]"
1498
1698
  }
1499
1699
  ),
1500
- /* @__PURE__ */ jsx5(
1501
- "button",
1502
- {
1503
- type: "button",
1504
- onClick: () => setSlotPopoverOpen(false),
1505
- className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-[11px] text-[var(--text-secondary)]",
1506
- children: "Cancel"
1507
- }
1508
- )
1700
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2", children: [
1701
+ /* @__PURE__ */ jsx5(
1702
+ "button",
1703
+ {
1704
+ type: "button",
1705
+ onClick: () => {
1706
+ onBindSlot(slotInput.trim() || null);
1707
+ setSlotPopoverOpen(false);
1708
+ },
1709
+ className: "flex-1 rounded border border-[var(--brand-primary)] px-2 py-0.5 text-[11px] text-[var(--brand-primary)] hover:bg-[var(--brand-primary)]/10",
1710
+ children: slotInput.trim() ? "Bind" : "Unbind"
1711
+ }
1712
+ ),
1713
+ /* @__PURE__ */ jsx5(
1714
+ "button",
1715
+ {
1716
+ type: "button",
1717
+ onClick: () => setSlotPopoverOpen(false),
1718
+ className: "rounded border border-[var(--border-default)] px-2 py-0.5 text-[11px] text-[var(--text-secondary)]",
1719
+ children: "Cancel"
1720
+ }
1721
+ )
1722
+ ] })
1509
1723
  ] })
1510
- ] }) : null
1511
- ] }) : null,
1724
+ }
1725
+ ) : null,
1512
1726
  SEP,
1513
1727
  /* @__PURE__ */ jsx5("button", { type: "button", "aria-label": "Delete selection", disabled: !canWrite, onClick: onDelete, className: BTN2, children: /* @__PURE__ */ jsx5(TrashGlyph, { className: "h-3.5 w-3.5 text-rose-400" }) })
1514
1728
  ] });
1515
1729
  }
1516
1730
  function TextControls({ element, canWrite, onPatch }) {
1517
1731
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1518
- /* @__PURE__ */ jsx5(
1519
- "input",
1520
- {
1521
- type: "text",
1522
- "aria-label": "Font family",
1523
- value: element.fontFamily,
1524
- disabled: !canWrite,
1525
- onChange: (event) => onPatch({ fontFamily: event.target.value }),
1526
- className: "w-28 rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-2 py-0.5 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]",
1527
- placeholder: "Font"
1528
- }
1529
- ),
1732
+ /* @__PURE__ */ jsx5(FontPicker, { value: element.fontFamily, disabled: !canWrite, onChange: (fontFamily) => onPatch({ fontFamily }) }),
1530
1733
  /* @__PURE__ */ jsx5(NumberInput, { label: "Size", value: element.fontSize, min: 1, onCommit: (v) => onPatch({ fontSize: v }), className: "w-12" }),
1531
1734
  /* @__PURE__ */ jsx5(
1532
1735
  "button",
@@ -1564,35 +1767,33 @@ function TextControls({ element, canWrite, onPatch }) {
1564
1767
  )),
1565
1768
  /* @__PURE__ */ jsx5(NumberInput, { label: "Line H", value: element.lineHeight, step: 0.1, min: 0.5, onCommit: (v) => onPatch({ lineHeight: v }), className: "w-12" }),
1566
1769
  /* @__PURE__ */ jsx5(NumberInput, { label: "Spacing", value: element.letterSpacing, step: 0.5, onCommit: (v) => onPatch({ letterSpacing: v }), className: "w-14" }),
1567
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }) })
1770
+ /* @__PURE__ */ jsx5(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }), disabled: !canWrite })
1568
1771
  ] });
1569
1772
  }
1570
1773
  function ShapeControls({ element, canWrite, onPatch, showCornerRadius }) {
1571
1774
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1572
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }) }),
1573
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "Stroke", value: element.stroke ?? "#000000", onCommit: (v) => onPatch({ stroke: v }) }),
1775
+ /* @__PURE__ */ jsx5(ColorSwatch, { label: "Fill", value: element.fill, onCommit: (v) => onPatch({ fill: v }), disabled: !canWrite }),
1776
+ /* @__PURE__ */ jsx5(ColorSwatch, { label: "Stroke", value: element.stroke ?? "#000000", onCommit: (v) => onPatch({ stroke: v }), disabled: !canWrite }),
1574
1777
  /* @__PURE__ */ jsx5(NumberInput, { label: "Stroke W", value: element.strokeWidth ?? 0, min: 0, onCommit: (v) => onPatch({ strokeWidth: v }), className: "w-14" }),
1575
1778
  showCornerRadius && "cornerRadius" in element ? /* @__PURE__ */ jsx5(NumberInput, { label: "Corner R", value: element.cornerRadius ?? 0, min: 0, onCommit: (v) => onPatch({ cornerRadius: v }), className: "w-14" }) : null
1576
1779
  ] });
1577
1780
  }
1578
1781
  function ImageControls({ element, canWrite, onPatch }) {
1579
- return /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1580
- /* @__PURE__ */ jsx5("span", { className: "text-[9px] uppercase tracking-wide text-[var(--text-muted)]", children: "Fit" }),
1581
- /* @__PURE__ */ jsxs5(
1582
- "select",
1583
- {
1584
- value: element.fit,
1585
- disabled: !canWrite,
1586
- onChange: (event) => onPatch({ fit: event.target.value }),
1587
- className: "rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-1 py-0.5 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]",
1588
- children: [
1589
- /* @__PURE__ */ jsx5("option", { value: "fill", children: "Fill" }),
1590
- /* @__PURE__ */ jsx5("option", { value: "cover", children: "Cover" }),
1591
- /* @__PURE__ */ jsx5("option", { value: "contain", children: "Contain" })
1592
- ]
1593
- }
1594
- )
1595
- ] });
1782
+ return /* @__PURE__ */ jsx5(
1783
+ SelectControl,
1784
+ {
1785
+ label: "Fit",
1786
+ value: element.fit,
1787
+ disabled: !canWrite,
1788
+ onChange: (fit) => onPatch({ fit }),
1789
+ buttonClassName: "w-24",
1790
+ options: [
1791
+ { value: "fill", label: "Fill" },
1792
+ { value: "cover", label: "Cover" },
1793
+ { value: "contain", label: "Contain" }
1794
+ ]
1795
+ }
1796
+ );
1596
1797
  }
1597
1798
  function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides }) {
1598
1799
  const matchedPreset = matchPreset(page.width, page.height);
@@ -1604,6 +1805,10 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1604
1805
  if (dim === "width") setCustomW(null);
1605
1806
  else setCustomH(null);
1606
1807
  }
1808
+ const presetOptions = [
1809
+ { value: "custom", label: "Custom" },
1810
+ ...SIZE_PRESETS.map((p) => ({ value: p.id, label: p.label }))
1811
+ ];
1607
1812
  return /* @__PURE__ */ jsxs5(Fragment4, { children: [
1608
1813
  /* @__PURE__ */ jsx5(
1609
1814
  "input",
@@ -1617,27 +1822,21 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1617
1822
  }
1618
1823
  ),
1619
1824
  SEP,
1825
+ /* @__PURE__ */ jsx5(
1826
+ SelectControl,
1827
+ {
1828
+ label: "Preset",
1829
+ value: matchedPreset?.id ?? "custom",
1830
+ disabled: !canWrite,
1831
+ onChange: (id) => {
1832
+ const preset = SIZE_PRESETS.find((p) => p.id === id);
1833
+ if (preset) onSetPageProps({ width: preset.width, height: preset.height });
1834
+ },
1835
+ options: presetOptions
1836
+ }
1837
+ ),
1620
1838
  /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1621
- /* @__PURE__ */ jsx5("span", { className: "text-[9px] uppercase tracking-wide text-[var(--text-muted)]", children: "Preset" }),
1622
- /* @__PURE__ */ jsxs5(
1623
- "select",
1624
- {
1625
- value: matchedPreset?.id ?? "custom",
1626
- disabled: !canWrite,
1627
- onChange: (event) => {
1628
- const preset = SIZE_PRESETS.find((p) => p.id === event.target.value);
1629
- if (preset) onSetPageProps({ width: preset.width, height: preset.height });
1630
- },
1631
- className: "rounded border border-[var(--border-default)] bg-[var(--bg-input)] px-1 py-0.5 text-[12px] text-[var(--text-primary)] outline-none focus:border-[var(--brand-primary)]",
1632
- children: [
1633
- /* @__PURE__ */ jsx5("option", { value: "custom", children: "Custom" }),
1634
- SIZE_PRESETS.map((p) => /* @__PURE__ */ jsx5("option", { value: p.id, children: p.label }, p.id))
1635
- ]
1636
- }
1637
- )
1638
- ] }),
1639
- /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1640
- /* @__PURE__ */ jsx5("span", { className: "text-[9px] uppercase tracking-wide text-[var(--text-muted)]", children: "W" }),
1839
+ /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: "W" }),
1641
1840
  /* @__PURE__ */ jsx5(
1642
1841
  "input",
1643
1842
  {
@@ -1657,7 +1856,7 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1657
1856
  ] }),
1658
1857
  /* @__PURE__ */ jsx5("span", { className: "text-[var(--text-muted)]", children: "\xD7" }),
1659
1858
  /* @__PURE__ */ jsxs5("label", { className: "flex flex-col items-center gap-0.5", children: [
1660
- /* @__PURE__ */ jsx5("span", { className: "text-[9px] uppercase tracking-wide text-[var(--text-muted)]", children: "H" }),
1859
+ /* @__PURE__ */ jsx5("span", { className: FIELD_LABEL, children: "H" }),
1661
1860
  /* @__PURE__ */ jsx5(
1662
1861
  "input",
1663
1862
  {
@@ -1676,7 +1875,7 @@ function PagePropsControls({ page, canWrite, onSetPageProps, onSetPageGuides })
1676
1875
  )
1677
1876
  ] }),
1678
1877
  SEP,
1679
- /* @__PURE__ */ jsx5(ColorSwatch, { label: "BG", value: page.background, onCommit: (v) => onSetPageProps({ background: v }) }),
1878
+ /* @__PURE__ */ jsx5(ColorSwatch, { label: "BG", value: page.background, onCommit: (v) => onSetPageProps({ background: v }), disabled: !canWrite }),
1680
1879
  SEP,
1681
1880
  /* @__PURE__ */ jsx5(BleedControls, { page, canWrite, onSetPageProps })
1682
1881
  ] });
@@ -1793,8 +1992,106 @@ function ZoomControls({ zoom, onZoom, onFit }) {
1793
1992
  ] });
1794
1993
  }
1795
1994
 
1796
- // src/design-canvas-react/components/DesignCanvas.tsx
1995
+ // src/design-canvas-react/components/ExportControl.tsx
1996
+ import { useRef as useRef4, useState as useState4 } from "react";
1797
1997
  import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1998
+ var FORMATS = [
1999
+ { id: "png", label: "PNG" },
2000
+ { id: "jpeg", label: "JPEG" }
2001
+ ];
2002
+ var SCALES = [
2003
+ { value: 1, label: "1x" },
2004
+ { value: 2, label: "2x" }
2005
+ ];
2006
+ function ExportControl({ defaults, onExport, className }) {
2007
+ const [open, setOpen] = useState4(false);
2008
+ const [format, setFormat] = useState4(defaults?.format ?? "png");
2009
+ const [pixelRatio, setPixelRatio] = useState4(defaults?.pixelRatio ?? 1);
2010
+ const containerRef = useRef4(null);
2011
+ function confirm() {
2012
+ onExport({ format, pixelRatio });
2013
+ setOpen(false);
2014
+ }
2015
+ return /* @__PURE__ */ jsxs7("div", { ref: containerRef, className: `relative ${className ?? ""}`, children: [
2016
+ /* @__PURE__ */ jsxs7(
2017
+ "button",
2018
+ {
2019
+ type: "button",
2020
+ "aria-label": "Export",
2021
+ "aria-haspopup": "dialog",
2022
+ "aria-expanded": open,
2023
+ onClick: () => setOpen((v) => !v),
2024
+ onBlur: (event) => {
2025
+ if (!containerRef.current?.contains(event.relatedTarget)) {
2026
+ setOpen(false);
2027
+ }
2028
+ },
2029
+ className: "flex h-8 items-center gap-1.5 rounded-md border border-[var(--border-default)] px-2.5 text-xs font-medium text-[var(--text-primary)] transition-colors hover:border-[var(--brand-primary)]/40",
2030
+ children: [
2031
+ /* @__PURE__ */ jsx7(ExportGlyph, { className: "h-3.5 w-3.5" }),
2032
+ "Export",
2033
+ /* @__PURE__ */ jsx7(ChevronDownGlyph, { className: "h-3 w-3 text-[var(--text-muted)]" })
2034
+ ]
2035
+ }
2036
+ ),
2037
+ open ? /* @__PURE__ */ jsxs7(
2038
+ "div",
2039
+ {
2040
+ role: "dialog",
2041
+ "aria-label": "Export options",
2042
+ onBlur: (event) => {
2043
+ if (!containerRef.current?.contains(event.relatedTarget)) {
2044
+ setOpen(false);
2045
+ }
2046
+ },
2047
+ className: "absolute right-0 top-full z-50 mt-1 flex w-52 flex-col gap-3 rounded-md border border-[var(--border-default)] bg-[var(--bg-input)] p-3 shadow-lg",
2048
+ children: [
2049
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5", children: [
2050
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] font-medium text-[var(--text-muted)]", children: "Format" }),
2051
+ /* @__PURE__ */ jsx7("div", { className: "flex gap-1.5", children: FORMATS.map((f) => /* @__PURE__ */ jsx7(
2052
+ "button",
2053
+ {
2054
+ type: "button",
2055
+ "aria-pressed": format === f.id,
2056
+ onClick: () => setFormat(f.id),
2057
+ className: `flex-1 rounded border px-2 py-1 text-[11px] transition-colors ${format === f.id ? "border-[var(--brand-primary)] text-[var(--brand-primary)]" : "border-[var(--border-default)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,
2058
+ children: f.label
2059
+ },
2060
+ f.id
2061
+ )) })
2062
+ ] }),
2063
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1.5", children: [
2064
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] font-medium text-[var(--text-muted)]", children: "Scale" }),
2065
+ /* @__PURE__ */ jsx7("div", { className: "flex gap-1.5", children: SCALES.map((s) => /* @__PURE__ */ jsx7(
2066
+ "button",
2067
+ {
2068
+ type: "button",
2069
+ "aria-pressed": pixelRatio === s.value,
2070
+ onClick: () => setPixelRatio(s.value),
2071
+ className: `flex-1 rounded border px-2 py-1 text-[11px] transition-colors ${pixelRatio === s.value ? "border-[var(--brand-primary)] text-[var(--brand-primary)]" : "border-[var(--border-default)] text-[var(--text-secondary)] hover:text-[var(--text-primary)]"}`,
2072
+ children: s.label
2073
+ },
2074
+ s.value
2075
+ )) })
2076
+ ] }),
2077
+ /* @__PURE__ */ jsx7(
2078
+ "button",
2079
+ {
2080
+ type: "button",
2081
+ "aria-label": "Export image",
2082
+ onClick: confirm,
2083
+ className: "rounded border border-[var(--brand-primary)] px-2 py-1 text-[11px] font-medium text-[var(--brand-primary)] transition-colors hover:bg-[var(--brand-primary)]/10",
2084
+ children: "Export"
2085
+ }
2086
+ )
2087
+ ]
2088
+ }
2089
+ ) : null
2090
+ ] });
2091
+ }
2092
+
2093
+ // src/design-canvas-react/components/DesignCanvas.tsx
2094
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1798
2095
  function mintId() {
1799
2096
  const uuid = globalThis.crypto && "randomUUID" in globalThis.crypto ? globalThis.crypto.randomUUID() : null;
1800
2097
  return `local-${uuid ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`}`;
@@ -1834,6 +2131,7 @@ function DesignCanvas({
1834
2131
  renderAgentPanel,
1835
2132
  renderSidePanel,
1836
2133
  onExport,
2134
+ exportDefaults,
1837
2135
  className,
1838
2136
  fitOnMount,
1839
2137
  onReady,
@@ -1846,23 +2144,24 @@ function DesignCanvas({
1846
2144
  []
1847
2145
  );
1848
2146
  const editorState = useSyncExternalStore(stack.subscribe, stack.getState, stack.getState);
1849
- const appliedDocumentRef = useRef4(initialDocument);
1850
- useEffect2(() => {
2147
+ const appliedDocumentRef = useRef5(initialDocument);
2148
+ useEffect3(() => {
1851
2149
  if (appliedDocumentRef.current === initialDocument) return;
1852
2150
  appliedDocumentRef.current = initialDocument;
1853
2151
  stack.reset(initialDocument);
1854
2152
  }, [initialDocument, stack]);
1855
- const [commitError, setCommitError] = useState4(null);
2153
+ const [commitError, setCommitError] = useState5(null);
1856
2154
  const commit = useCommitCommand(stack, onApplyOperations, canWrite, setCommitError);
1857
- const selectionChangeRef = useRef4(onSelectionChange);
2155
+ const selectionChangeRef = useRef5(onSelectionChange);
1858
2156
  selectionChangeRef.current = onSelectionChange;
1859
- useEffect2(() => {
2157
+ useEffect3(() => {
1860
2158
  const page = editorState.document.pages.find((p) => p.id === editorState.activePageId);
1861
2159
  if (!page) return;
1862
2160
  const selected = editorState.selectedElementIds.map((id) => page.elements.find((el) => el.id === id)).filter((el) => el !== void 0);
1863
2161
  selectionChangeRef.current?.(selected);
1864
2162
  }, [editorState.selectedElementIds, editorState.activePageId, editorState.document]);
1865
- const fitRef = useRef4(null);
2163
+ const fitRef = useRef5(null);
2164
+ const exportRef = useRef5(null);
1866
2165
  const setZoom = useCallback2((zoom) => stack.setView({ zoom }), [stack]);
1867
2166
  const setPan = useCallback2((panX, panY) => stack.setView({ panX, panY }), [stack]);
1868
2167
  const setActivePage = useCallback2((activePageId) => stack.setView({ activePageId, selectedElementIds: [] }), [stack]);
@@ -2048,7 +2347,7 @@ function DesignCanvas({
2048
2347
  setCommitError(error instanceof Error ? error.message : String(error));
2049
2348
  });
2050
2349
  }, [stack, canWrite, onApplyOperations]);
2051
- useEffect2(() => {
2350
+ useEffect3(() => {
2052
2351
  function onKeyDown(event) {
2053
2352
  const mod = event.metaKey || event.ctrlKey;
2054
2353
  if (mod && !isTypingTarget(event.target)) {
@@ -2087,7 +2386,7 @@ function DesignCanvas({
2087
2386
  return editorState.selectedElementIds.map((id) => activePage.elements.find((el) => el.id === id)).filter((el) => el !== void 0);
2088
2387
  }, [activePage, editorState.selectedElementIds]);
2089
2388
  if (!activePage) {
2090
- return /* @__PURE__ */ jsx7("div", { className: `flex h-full items-center justify-center bg-[var(--bg-input)] text-[var(--text-muted)] ${className ?? ""}`, children: "No pages in document" });
2389
+ return /* @__PURE__ */ jsx8("div", { className: `flex h-full items-center justify-center bg-[var(--bg-input)] text-[var(--text-muted)] ${className ?? ""}`, children: "No pages in document" });
2091
2390
  }
2092
2391
  const bleedScreen = editorState.showBleed && activePage.bleed ? {
2093
2392
  top: activePage.bleed.top * editorState.zoom,
@@ -2095,45 +2394,54 @@ function DesignCanvas({
2095
2394
  bottom: activePage.bleed.bottom * editorState.zoom,
2096
2395
  left: activePage.bleed.left * editorState.zoom
2097
2396
  } : null;
2098
- return /* @__PURE__ */ jsxs7("div", { className: `flex h-full min-h-0 bg-[var(--bg-input)] text-[var(--text-primary)] ${className ?? ""}`, children: [
2099
- renderSidePanel ? /* @__PURE__ */ jsx7("aside", { className: "flex w-64 shrink-0 flex-col overflow-hidden border-r border-[var(--border-default)]", children: renderSidePanel() }) : null,
2100
- /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 flex-col", children: [
2101
- /* @__PURE__ */ jsx7(
2102
- Toolbar,
2103
- {
2104
- page: activePage,
2105
- selectedElements,
2106
- canWrite,
2107
- canUndo: stack.canUndo(),
2108
- canRedo: stack.canRedo(),
2109
- gridEnabled: editorState.gridEnabled,
2110
- snapEnabled: editorState.snapEnabled,
2111
- showRulers: editorState.showRulers,
2112
- showBleed: editorState.showBleed,
2113
- onUndo: handleUndo,
2114
- onRedo: handleRedo,
2115
- onToggleGrid: () => stack.setView({ gridEnabled: !editorState.gridEnabled }),
2116
- onToggleSnap: () => stack.setView({ snapEnabled: !editorState.snapEnabled }),
2117
- onToggleRulers: () => stack.setView({ showRulers: !editorState.showRulers }),
2118
- onToggleBleed: () => stack.setView({ showBleed: !editorState.showBleed }),
2119
- onSetAttrs: handleSetAttrs,
2120
- onSetPageProps: handleSetPageProps,
2121
- onSetPageGuides: handleSetPageGuides,
2122
- onReorder: handleReorder,
2123
- onGroup: handleGroup,
2124
- onUngroup: handleUngroup,
2125
- onDelete: handleDelete,
2126
- onBindSlot: handleBindSlot
2127
- }
2128
- ),
2129
- commitError ? /* @__PURE__ */ jsxs7(
2397
+ return /* @__PURE__ */ jsxs8("div", { className: `flex h-full min-h-0 bg-[var(--bg-input)] text-[var(--text-primary)] ${className ?? ""}`, children: [
2398
+ renderSidePanel ? /* @__PURE__ */ jsx8("aside", { className: "flex w-64 shrink-0 flex-col overflow-hidden border-r border-[var(--border-default)]", children: renderSidePanel() }) : null,
2399
+ /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 flex-col", children: [
2400
+ /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-stretch", children: [
2401
+ /* @__PURE__ */ jsx8("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx8(
2402
+ Toolbar,
2403
+ {
2404
+ page: activePage,
2405
+ selectedElements,
2406
+ canWrite,
2407
+ canUndo: stack.canUndo(),
2408
+ canRedo: stack.canRedo(),
2409
+ gridEnabled: editorState.gridEnabled,
2410
+ snapEnabled: editorState.snapEnabled,
2411
+ showRulers: editorState.showRulers,
2412
+ showBleed: editorState.showBleed,
2413
+ onUndo: handleUndo,
2414
+ onRedo: handleRedo,
2415
+ onToggleGrid: () => stack.setView({ gridEnabled: !editorState.gridEnabled }),
2416
+ onToggleSnap: () => stack.setView({ snapEnabled: !editorState.snapEnabled }),
2417
+ onToggleRulers: () => stack.setView({ showRulers: !editorState.showRulers }),
2418
+ onToggleBleed: () => stack.setView({ showBleed: !editorState.showBleed }),
2419
+ onSetAttrs: handleSetAttrs,
2420
+ onSetPageProps: handleSetPageProps,
2421
+ onSetPageGuides: handleSetPageGuides,
2422
+ onReorder: handleReorder,
2423
+ onGroup: handleGroup,
2424
+ onUngroup: handleUngroup,
2425
+ onDelete: handleDelete,
2426
+ onBindSlot: handleBindSlot
2427
+ }
2428
+ ) }),
2429
+ onExport ? /* @__PURE__ */ jsx8("div", { className: "flex shrink-0 items-center border-b border-l border-[var(--border-default)] bg-[var(--bg-input)] px-2", children: /* @__PURE__ */ jsx8(
2430
+ ExportControl,
2431
+ {
2432
+ defaults: exportDefaults,
2433
+ onExport: (opts) => exportRef.current?.(opts)
2434
+ }
2435
+ ) }) : null
2436
+ ] }),
2437
+ commitError ? /* @__PURE__ */ jsxs8(
2130
2438
  "div",
2131
2439
  {
2132
2440
  className: "flex shrink-0 items-center justify-between gap-3 border-b border-rose-500/30 bg-rose-500/10 px-3 py-1.5 text-xs text-rose-300",
2133
2441
  role: "alert",
2134
2442
  children: [
2135
- /* @__PURE__ */ jsx7("span", { className: "min-w-0 truncate", children: commitError }),
2136
- /* @__PURE__ */ jsx7(
2443
+ /* @__PURE__ */ jsx8("span", { className: "min-w-0 truncate", children: commitError }),
2444
+ /* @__PURE__ */ jsx8(
2137
2445
  "button",
2138
2446
  {
2139
2447
  type: "button",
@@ -2145,8 +2453,8 @@ function DesignCanvas({
2145
2453
  ]
2146
2454
  }
2147
2455
  ) : null,
2148
- /* @__PURE__ */ jsxs7("div", { className: "relative min-h-0 flex-1", children: [
2149
- /* @__PURE__ */ jsx7(
2456
+ /* @__PURE__ */ jsxs8("div", { className: "relative min-h-0 flex-1", children: [
2457
+ /* @__PURE__ */ jsx8(
2150
2458
  Rulers,
2151
2459
  {
2152
2460
  pageWidth: activePage.width,
@@ -2159,12 +2467,12 @@ function DesignCanvas({
2159
2467
  onGuidesChange: handleSetPageGuides
2160
2468
  }
2161
2469
  ),
2162
- bleedScreen && activePage.bleed ? /* @__PURE__ */ jsx7(
2470
+ bleedScreen && activePage.bleed ? /* @__PURE__ */ jsx8(
2163
2471
  "div",
2164
2472
  {
2165
2473
  className: "pointer-events-none absolute inset-0 z-10 overflow-hidden",
2166
2474
  "aria-hidden": true,
2167
- children: /* @__PURE__ */ jsx7(
2475
+ children: /* @__PURE__ */ jsx8(
2168
2476
  "div",
2169
2477
  {
2170
2478
  style: {
@@ -2172,7 +2480,7 @@ function DesignCanvas({
2172
2480
  left: editorState.panX,
2173
2481
  top: editorState.panY
2174
2482
  },
2175
- children: /* @__PURE__ */ jsx7(
2483
+ children: /* @__PURE__ */ jsx8(
2176
2484
  BleedTrimOverlay,
2177
2485
  {
2178
2486
  pageWidthPx: activePage.width * editorState.zoom,
@@ -2199,6 +2507,7 @@ function DesignCanvas({
2199
2507
  stack,
2200
2508
  activePage,
2201
2509
  onFitRef: fitRef,
2510
+ onExportRef: exportRef,
2202
2511
  fitOnMount,
2203
2512
  onReady,
2204
2513
  onZoomChange: setZoom,
@@ -2206,8 +2515,8 @@ function DesignCanvas({
2206
2515
  onSelectElements: setSelectedElements
2207
2516
  })
2208
2517
  ] }),
2209
- /* @__PURE__ */ jsxs7("div", { className: "flex shrink-0 items-stretch border-t border-[var(--border-default)]", children: [
2210
- /* @__PURE__ */ jsx7("div", { className: "min-w-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx7(
2518
+ /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-stretch border-t border-[var(--border-default)]", children: [
2519
+ /* @__PURE__ */ jsx8("div", { className: "min-w-0 flex-1 overflow-hidden", children: /* @__PURE__ */ jsx8(
2211
2520
  PagesStrip,
2212
2521
  {
2213
2522
  pages: editorState.document.pages,
@@ -2221,7 +2530,7 @@ function DesignCanvas({
2221
2530
  onReorderPage: handleReorderPage
2222
2531
  }
2223
2532
  ) }),
2224
- /* @__PURE__ */ jsx7("div", { className: "flex shrink-0 items-center border-l border-[var(--border-default)]", children: /* @__PURE__ */ jsx7(
2533
+ /* @__PURE__ */ jsx8("div", { className: "flex shrink-0 items-center border-l border-[var(--border-default)]", children: /* @__PURE__ */ jsx8(
2225
2534
  ZoomControls,
2226
2535
  {
2227
2536
  zoom: editorState.zoom,
@@ -2231,7 +2540,7 @@ function DesignCanvas({
2231
2540
  ) })
2232
2541
  ] })
2233
2542
  ] }),
2234
- renderAgentPanel ? /* @__PURE__ */ jsx7("aside", { className: "flex w-80 shrink-0 flex-col overflow-hidden border-l border-[var(--border-default)]", children: renderAgentPanel({ selectedElements, activePageId: editorState.activePageId }) }) : null
2543
+ renderAgentPanel ? /* @__PURE__ */ jsx8("aside", { className: "flex w-80 shrink-0 flex-col overflow-hidden border-l border-[var(--border-default)]", children: renderAgentPanel({ selectedElements, activePageId: editorState.activePageId }) }) : null
2235
2544
  ] });
2236
2545
  }
2237
2546
  var DesignCanvas_default = DesignCanvas;
@@ -2270,11 +2579,13 @@ export {
2270
2579
  ImageGlyph,
2271
2580
  VideoGlyph,
2272
2581
  SlotGlyph,
2582
+ ShapesGlyph,
2273
2583
  PagesStrip,
2274
2584
  Rulers,
2275
2585
  Toolbar,
2276
2586
  ZoomControls,
2587
+ ExportControl,
2277
2588
  DesignCanvas,
2278
2589
  DesignCanvas_default
2279
2590
  };
2280
- //# sourceMappingURL=chunk-2W4YCAFH.js.map
2591
+ //# sourceMappingURL=chunk-LPAVBGVY.js.map