veo-sdk 0.5.34 → 0.6.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.
@@ -1,4 +1,4 @@
1
- import { hasDocument, hasWindow, resolveBuilderContext, closeGuidePreview, clearBuilderToken, buildSelectorPath, filterHumanClasses, isDeviceAllowed, stepMatchesPage, isAutomaticActivation, activationDelayMs, previewGuide } from './chunk-W4R4XCWL.mjs';
1
+ import { hasDocument, hasWindow, resolveBuilderContext, closeGuidePreview, clearBuilderToken, buildSelectorPath, filterHumanClasses, groupLayout, stepMatchesPage, isAutomaticActivation, activationDelayMs, previewGuide, applyBlockStyle, buildContentBlock, RAW_BLOCK_ATTR, GUIDE_STYLES, isDeviceAllowed } from './chunk-O6USA4TU.mjs';
2
2
  import { BUILDER_TOKEN_PARAM, BUILDER_ORIGIN_PARAM } from './chunk-PTG3BTJE.mjs';
3
3
 
4
4
  // src/plugins/builder/block-drop-target.ts
@@ -122,6 +122,9 @@ var ICONS = {
122
122
  chevronDown: '<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>',
123
123
  chevronUp: '<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/>',
124
124
  plus: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>',
125
+ pencil: '<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/>',
126
+ copy: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v8.25A2.25 2.25 0 0 0 6 16.5h2.25m8.25-8.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-7.5A2.25 2.25 0 0 1 8.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 0 0-2.25 2.25v6"/>',
127
+ trash: '<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/>',
125
128
  cursor: '<path stroke-linecap="round" stroke-linejoin="round" d="M15.042 21.672 13.684 16.6m0 0-2.51 2.225.569-9.47 5.227 7.917-3.286-.672ZM12 2.25V4.5m5.834.166-1.591 1.591M20.25 10.5H18M7.757 14.743l-1.59 1.59M6 10.5H3.75m4.007-4.243-1.59-1.59"/>',
126
129
  spinner: '<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182"/>'
127
130
  };
@@ -307,8 +310,14 @@ function injectBuilderChrome(opts) {
307
310
  stepsList.append(activation);
308
311
  s.steps.forEach((st, i) => {
309
312
  const thumb = st.presentation === "inline" ? '<div class="veo-thumb veo-thumb-inline"><i></i><i></i><i></i></div>' : '<div class="veo-thumb"><i></i><i></i><b></b></div>';
310
- const card = stepCard(thumb, st.label, s.section === "step" && s.activeStep === i);
311
- card.addEventListener("click", () => act({ action: "select-step", index: i }));
313
+ const isStepView = s.section === "step" || s.section === "block" || s.section === "add-block" || s.section === "row" || s.section === "structure";
314
+ const card = stepCard(thumb, st.label, isStepView && s.activeStep === i, {
315
+ // Se pueden borrar TODOS los pasos (el panel vuelve a Activación).
316
+ canRemove: true,
317
+ onEdit: () => act({ action: "select-step", index: i }),
318
+ onDuplicate: () => act({ action: "duplicate-step", index: i }),
319
+ onRemove: () => act({ action: "remove-step", index: i })
320
+ });
312
321
  stepsList.append(card);
313
322
  });
314
323
  const add = el("button", "veo-step-add", `${svg("plus", 18)}<span>Agregar paso</span>`);
@@ -337,16 +346,40 @@ function injectBuilderChrome(opts) {
337
346
  }
338
347
  };
339
348
  }
340
- function stepCard(inner, label, active) {
341
- const b = el(
342
- "button",
343
- `veo-step${active ? " is-active" : ""}`,
344
- `<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
349
+ function stepCard(inner, label, active, actions) {
350
+ const cls = `veo-step${active ? " is-active" : ""}`;
351
+ if (!actions) {
352
+ const b = el(
353
+ "button",
354
+ cls,
355
+ `<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
356
+ );
357
+ b.type = "button";
358
+ b.querySelector(".veo-step-label").textContent = label;
359
+ b.setAttribute("aria-pressed", active ? "true" : "false");
360
+ return b;
361
+ }
362
+ const card = el(
363
+ "div",
364
+ cls,
365
+ `<div class="veo-step-box">${inner}<div class="veo-step-hover"><div class="veo-step-hover-top"><button type="button" class="veo-step-icon veo-step-remove" title="Eliminar paso" aria-label="Eliminar paso">${svg("trash", 14)}</button><button type="button" class="veo-step-icon veo-step-duplicate" title="Duplicar paso" aria-label="Duplicar paso">${svg("copy", 14)}</button></div><div class="veo-step-hover-main"><button type="button" class="veo-step-edit">${svg("pencil", 14)}<span>Editar</span></button></div></div></div><span class="veo-step-label"></span>`
345
366
  );
346
- b.type = "button";
347
- b.querySelector(".veo-step-label").textContent = label;
348
- b.setAttribute("aria-pressed", active ? "true" : "false");
349
- return b;
367
+ card.querySelector(".veo-step-label").textContent = label;
368
+ card.setAttribute("aria-current", active ? "step" : "false");
369
+ const remove = card.querySelector(".veo-step-remove");
370
+ remove.disabled = !actions.canRemove;
371
+ if (!actions.canRemove) remove.title = "La gu\xEDa necesita al menos un paso";
372
+ const on = (sel, fn) => {
373
+ card.querySelector(sel)?.addEventListener("click", (e) => {
374
+ e.stopPropagation();
375
+ fn();
376
+ });
377
+ };
378
+ on(".veo-step-edit", actions.onEdit);
379
+ on(".veo-step-duplicate", actions.onDuplicate);
380
+ on(".veo-step-remove", actions.onRemove);
381
+ card.addEventListener("click", actions.onEdit);
382
+ return card;
350
383
  }
351
384
  var CHROME_CSS = `
352
385
  :host { all: initial; }
@@ -431,10 +464,31 @@ var CHROME_CSS = `
431
464
  .veo-steps-list { display: flex; align-items: flex-start; gap: 20px; }
432
465
  .veo-step { border: 0; background: transparent; padding: 0; cursor: pointer; font: inherit; display: flex; flex-direction: column; align-items: center; gap: 8px; flex: 0 0 auto; }
433
466
  .veo-step-box {
467
+ position: relative; overflow: hidden;
434
468
  width: 116px; height: 64px; border-radius: 10px; background: #fff; border: 2px solid transparent;
435
469
  display: flex; align-items: center; justify-content: center; color: #2b2623;
436
470
  box-shadow: 0 1px 2px rgba(0,0,0,0.06);
437
471
  }
472
+ /* Capa de acciones del paso: aparece al pasar el mouse o al enfocar un bot\xF3n. */
473
+ .veo-step-hover {
474
+ position: absolute; inset: 0; background: #2b2623; border-radius: 8px;
475
+ display: flex; flex-direction: column; padding: 3px 4px 6px; box-sizing: border-box;
476
+ opacity: 0; transition: opacity 120ms ease-out;
477
+ }
478
+ .veo-step:hover .veo-step-hover, .veo-step:focus-within .veo-step-hover { opacity: 1; }
479
+ .veo-step-hover-top { display: flex; justify-content: space-between; align-items: center; }
480
+ .veo-step-hover-main { flex: 1; display: flex; align-items: center; justify-content: center; }
481
+ .veo-step-icon {
482
+ border: 0; background: transparent; color: #fff; cursor: pointer; padding: 0;
483
+ width: 20px; height: 20px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center;
484
+ }
485
+ .veo-step-icon:hover { background: rgba(255,255,255,0.14); }
486
+ .veo-step-icon:disabled { opacity: 0.35; cursor: default; background: transparent; }
487
+ .veo-step-edit {
488
+ width: 100%; margin: 0 6px; border: 0; background: #dbeefc; color: #1d6fb8; font: inherit; font-size: 13px; font-weight: 600;
489
+ border-radius: 6px; padding: 5px 10px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
490
+ }
491
+ .veo-step-edit:hover { background: #cbe4f9; }
438
492
  .veo-step.is-active .veo-step-box { border-color: #b74d00; }
439
493
  .veo-step-label { font-size: 13px; color: #6b635d; }
440
494
  .veo-step.is-active .veo-step-label { color: #2b2623; font-weight: 600; }
@@ -477,6 +531,8 @@ function injectBuilderPanel(opts) {
477
531
  },
478
532
  isVisible: () => false,
479
533
  setTopOffset: () => {
534
+ },
535
+ setSize: () => {
480
536
  }
481
537
  };
482
538
  }
@@ -529,6 +585,21 @@ function injectBuilderPanel(opts) {
529
585
  card.style.display = next && !(popped && !popped.closed) ? "" : "none";
530
586
  };
531
587
  minimize.addEventListener("click", () => setVisible(false));
588
+ let compactRestore = null;
589
+ const setSize = (size) => {
590
+ if (maximized || popped && !popped.closed) return;
591
+ if (size) {
592
+ if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
593
+ card.style.minHeight = "0";
594
+ card.style.width = `${clamp(Math.round(size.width), MIN_W, window.innerWidth - MARGIN * 2)}px`;
595
+ card.style.height = `${clamp(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top - MARGIN)}px`;
596
+ } else if (compactRestore) {
597
+ card.style.minHeight = "";
598
+ card.style.width = `${compactRestore.w}px`;
599
+ card.style.height = `${compactRestore.h}px`;
600
+ compactRestore = null;
601
+ }
602
+ };
532
603
  maximize.addEventListener("click", () => {
533
604
  maximized = !maximized;
534
605
  if (maximized) {
@@ -628,6 +699,7 @@ function injectBuilderPanel(opts) {
628
699
  title.textContent = t;
629
700
  },
630
701
  setVisible,
702
+ setSize,
631
703
  isVisible: () => visible,
632
704
  setTopOffset: (px) => {
633
705
  topOffset = px;
@@ -1188,6 +1260,9 @@ function initBuilderMode() {
1188
1260
  let staticTarget = null;
1189
1261
  let manipulator = null;
1190
1262
  let dropTarget = null;
1263
+ let cardClick = null;
1264
+ let rawBlock = null;
1265
+ let selectedBlockId = null;
1191
1266
  let dragGhost = null;
1192
1267
  let previewStepIndex = 0;
1193
1268
  let pendingPreview = null;
@@ -1208,13 +1283,742 @@ function initBuilderMode() {
1208
1283
  };
1209
1284
  const previewMatchesPage = (cmd) => stepMatchesPage(previewStep(cmd)?.pageLocation, window.location.href);
1210
1285
  const postPreviewStatus = (payload) => post({ type: "preview-status", payload });
1211
- const withholdPreview = () => {
1286
+ const detachPreviewTools = () => {
1212
1287
  manipulator?.detach();
1213
1288
  manipulator = null;
1214
1289
  dropTarget?.detach();
1215
1290
  dropTarget = null;
1291
+ cardClick?.();
1292
+ cardClick = null;
1293
+ rowHover?.();
1294
+ rowHover = null;
1295
+ hoverRowId = null;
1296
+ hoverCard = false;
1297
+ rawBlock?.detach();
1298
+ rawBlock = null;
1299
+ shownPreview = null;
1300
+ };
1301
+ const withholdPreview = () => {
1302
+ detachPreviewTools();
1303
+ shownPreview = null;
1216
1304
  closeGuidePreview();
1217
1305
  };
1306
+ const FRAME_PAD = 4;
1307
+ const FRAME_MIN_H = 0;
1308
+ const TOOLBAR_H = 30;
1309
+ const ICON = {
1310
+ pencil: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"/></svg>',
1311
+ copy: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M16.5 8.25V6a2.25 2.25 0 0 0-2.25-2.25H6A2.25 2.25 0 0 0 3.75 6v8.25A2.25 2.25 0 0 0 6 16.5h2.25m8.25-8.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-7.5A2.25 2.25 0 0 1 8.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 0 0-2.25 2.25v6"/></svg>',
1312
+ trash: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"/></svg>'
1313
+ };
1314
+ const ICON_MOVE = {
1315
+ left: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M19.5 12h-15m0 0 6.75 6.75M4.5 12l6.75-6.75"/></svg>',
1316
+ right: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 12h15m0 0-6.75-6.75M19.5 12l-6.75 6.75"/></svg>'
1317
+ };
1318
+ const moveAbility = (blocks, blockId) => {
1319
+ for (const row of groupLayout(blocks)) {
1320
+ for (let c = 0; c < row.columns.length; c++) {
1321
+ const col = row.columns[c];
1322
+ if (!col.blocks.some((b) => b.block.editorId === blockId)) continue;
1323
+ return { left: c > 0, right: c < row.columns.length - 1 };
1324
+ }
1325
+ }
1326
+ return { left: false, right: false };
1327
+ };
1328
+ let previewBlocks = [];
1329
+ let hoverRowId = null;
1330
+ let selectedRowId = null;
1331
+ let rowHover = null;
1332
+ let hoverCard = false;
1333
+ let panelSection = null;
1334
+ let shownPreview = null;
1335
+ const structureOf = (step) => {
1336
+ if (!step) return "";
1337
+ const { blocks, ...rest } = step;
1338
+ return JSON.stringify({
1339
+ rest,
1340
+ blocks: (blocks ?? []).map((b) => {
1341
+ const { text: _t, html: _h, style: _s, ...keep } = b;
1342
+ return keep;
1343
+ })
1344
+ });
1345
+ };
1346
+ const tryPatchPreview = (cmd) => {
1347
+ const prev = shownPreview;
1348
+ if (!prev || cmd.guide.guideType !== "composite" || prev.guide.guideType !== "composite") {
1349
+ return false;
1350
+ }
1351
+ const idx = cmd.guide.startStepIndex ?? 0;
1352
+ if ((prev.guide.startStepIndex ?? 0) !== idx) return false;
1353
+ const guideSig = (g) => JSON.stringify({ ...g, guideSteps: void 0 });
1354
+ if (guideSig(prev.guide) !== guideSig(cmd.guide) || prev.editable !== cmd.editable)
1355
+ return false;
1356
+ const host = document.querySelector("[data-veo-guide]");
1357
+ const card = host ? previewCard(host) : null;
1358
+ if (!host || !card) return false;
1359
+ const prevStep = prev.guide.guideSteps[idx];
1360
+ const nextStep = cmd.guide.guideSteps[idx];
1361
+ if (!prevStep || !nextStep) return false;
1362
+ if (structureOf(prevStep) !== structureOf(nextStep)) return false;
1363
+ const doc = card.ownerDocument;
1364
+ const prevBlocks = prevStep.blocks ?? [];
1365
+ const nextBlocks = nextStep.blocks ?? [];
1366
+ for (let i = 0; i < nextBlocks.length; i++) {
1367
+ const a = prevBlocks[i];
1368
+ const b = nextBlocks[i];
1369
+ if (a.text === b.text && a.html === b.html && JSON.stringify(a.style) === JSON.stringify(b.style)) {
1370
+ continue;
1371
+ }
1372
+ if (!b.editorId) return false;
1373
+ const node = card.querySelector(`[data-veo-block-id="${b.editorId}"]`);
1374
+ if (!node) return false;
1375
+ if (b.type === "button") {
1376
+ node.textContent = b.text?.trim() || "OK";
1377
+ node.removeAttribute("style");
1378
+ node.classList.remove("veo-guide-cta--secondary", "veo-guide-cta--link");
1379
+ const st = b.style ?? {};
1380
+ if (st.preset === "secondary" || st.preset === "link") {
1381
+ node.classList.add(`veo-guide-cta--${st.preset}`);
1382
+ }
1383
+ if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
1384
+ node.setAttribute("aria-label", st.ariaLabel.trim());
1385
+ } else node.removeAttribute("aria-label");
1386
+ applyBlockStyle(node, b.style, "button");
1387
+ continue;
1388
+ }
1389
+ if (b.type === "code" || b.type === "form") return false;
1390
+ const fresh = buildContentBlock(b, doc, { editing: true }) ?? (() => {
1391
+ const el2 = doc.createElement("div");
1392
+ el2.className = "veo-block-placeholder";
1393
+ el2.textContent = b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo";
1394
+ return el2;
1395
+ })();
1396
+ fresh.dataset.veoBlock = node.dataset.veoBlock ?? String(i);
1397
+ fresh.dataset.veoBlockId = b.editorId;
1398
+ node.replaceWith(fresh);
1399
+ }
1400
+ shownPreview = cmd;
1401
+ previewBlocks = nextBlocks;
1402
+ applyBlockHighlight(host, idx);
1403
+ renderGapZones(host, idx, nextBlocks);
1404
+ return true;
1405
+ };
1406
+ const previewCard = (host) => host.shadowRoot?.querySelector(".veo-card") ?? null;
1407
+ const clearBlockHighlight = (host) => {
1408
+ const card = previewCard(host);
1409
+ if (!card) return;
1410
+ for (const el2 of card.querySelectorAll(".veo-block--selected")) {
1411
+ el2.classList.remove("veo-block--selected");
1412
+ }
1413
+ card.querySelector(".veo-block-toolbar")?.remove();
1414
+ card.querySelector(".veo-block-frame")?.remove();
1415
+ };
1416
+ const applyBlockHighlight = (host, stepIndex) => {
1417
+ clearBlockHighlight(host);
1418
+ const card = previewCard(host);
1419
+ if (!card) return;
1420
+ const el2 = selectedBlockId ? card.querySelector(`[data-veo-block-id="${selectedBlockId}"]`) : null;
1421
+ if (!el2 || !selectedBlockId) {
1422
+ renderRowChrome(host, stepIndex);
1423
+ return;
1424
+ }
1425
+ el2.classList.add("veo-block--selected");
1426
+ const doc = card.ownerDocument;
1427
+ const cs = getComputedStyle(card);
1428
+ const padL = Number.parseFloat(cs.paddingLeft) || 0;
1429
+ const padT = Number.parseFloat(cs.paddingTop) || 0;
1430
+ const padR = Number.parseFloat(cs.paddingRight) || 0;
1431
+ const padB = Number.parseFloat(cs.paddingBottom) || 0;
1432
+ const parent = el2.parentElement;
1433
+ const inCol = parent?.classList.contains("veo-col") ? parent : null;
1434
+ const boxL = inCol ? inCol.offsetLeft : padL;
1435
+ const boxR = inCol ? inCol.offsetLeft + inCol.offsetWidth : card.clientWidth - padR;
1436
+ const boxT = padT;
1437
+ const boxB = card.clientHeight - padB;
1438
+ const fx = Math.max(boxL, el2.offsetLeft - FRAME_PAD);
1439
+ const fr = Math.min(boxR, el2.offsetLeft + el2.offsetWidth + FRAME_PAD);
1440
+ const fw = Math.max(0, fr - fx);
1441
+ const fy = Math.max(boxT, el2.offsetTop - FRAME_PAD);
1442
+ const fb = Math.min(boxB, el2.offsetTop + el2.offsetHeight + FRAME_PAD);
1443
+ const fh = Math.max(FRAME_MIN_H, fb - fy);
1444
+ const frame = doc.createElement("div");
1445
+ frame.className = "veo-block-frame";
1446
+ frame.setAttribute("data-veo-builder-chrome", "");
1447
+ frame.style.left = `${fx}px`;
1448
+ frame.style.top = `${fy}px`;
1449
+ frame.style.width = `${fw}px`;
1450
+ frame.style.height = `${fh}px`;
1451
+ card.appendChild(frame);
1452
+ const bar = doc.createElement("div");
1453
+ bar.className = "veo-block-toolbar";
1454
+ bar.setAttribute("data-veo-builder-chrome", "");
1455
+ const blockId = selectedBlockId;
1456
+ const mk = (action, label, icon, enabled = true) => {
1457
+ const b = doc.createElement("button");
1458
+ b.type = "button";
1459
+ b.title = label;
1460
+ b.setAttribute("aria-label", label);
1461
+ b.className = `veo-block-toolbar-${action}`;
1462
+ b.innerHTML = icon;
1463
+ b.disabled = !enabled;
1464
+ b.addEventListener("click", (e) => {
1465
+ e.preventDefault();
1466
+ e.stopPropagation();
1467
+ post({ type: "block-action", payload: { stepIndex, blockId, action } });
1468
+ });
1469
+ bar.appendChild(b);
1470
+ };
1471
+ const can = moveAbility(previewBlocks, blockId);
1472
+ mk("edit", "Editar", ICON.pencil);
1473
+ mk("duplicate", "Duplicar", ICON.copy);
1474
+ mk("remove", "Eliminar", ICON.trash);
1475
+ mk("move-left", "Mover a la izquierda", ICON_MOVE.left, can.left);
1476
+ mk("move-right", "Mover a la derecha", ICON_MOVE.right, can.right);
1477
+ bar.style.right = `${Math.max(0, card.clientWidth - fr)}px`;
1478
+ card.appendChild(bar);
1479
+ const barH = bar.offsetHeight || TOOLBAR_H;
1480
+ bar.dataset.veoTopAbove = String(fy - barH - 6);
1481
+ bar.dataset.veoTopBelow = String(fb + 6);
1482
+ bar.style.top = bar.dataset.veoTopAbove;
1483
+ renderRowChrome(host, stepIndex);
1484
+ };
1485
+ const chromeRect = (el2) => ({
1486
+ left: el2.offsetLeft,
1487
+ top: el2.offsetTop,
1488
+ right: el2.offsetLeft + el2.offsetWidth,
1489
+ bottom: el2.offsetTop + el2.offsetHeight
1490
+ });
1491
+ const intersects = (a, b) => a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
1492
+ const resolveToolbarOverlaps = (card) => {
1493
+ const cardBar = card.querySelector(".veo-card-toolbar");
1494
+ const blockBar = card.querySelector(".veo-block-toolbar");
1495
+ const rowBar = card.querySelector(".veo-row-toolbar");
1496
+ if (blockBar?.dataset.veoTopAbove) {
1497
+ blockBar.style.top = `${blockBar.dataset.veoTopAbove}px`;
1498
+ if (cardBar && intersects(chromeRect(blockBar), chromeRect(cardBar))) {
1499
+ blockBar.style.top = `${blockBar.dataset.veoTopBelow}px`;
1500
+ }
1501
+ }
1502
+ if (rowBar?.dataset.veoRight) {
1503
+ rowBar.style.left = "";
1504
+ rowBar.style.right = `${rowBar.dataset.veoRight}px`;
1505
+ rowBar.style.top = `${rowBar.dataset.veoTopBelow}px`;
1506
+ const others = [cardBar, blockBar].filter((b) => !!b);
1507
+ const clashes = () => others.some((o) => intersects(chromeRect(rowBar), chromeRect(o)));
1508
+ if (clashes()) {
1509
+ rowBar.style.right = "";
1510
+ rowBar.style.left = `${rowBar.dataset.veoLeft}px`;
1511
+ }
1512
+ if (clashes() && blockBar) {
1513
+ rowBar.style.left = "";
1514
+ rowBar.style.right = `${rowBar.dataset.veoRight}px`;
1515
+ rowBar.style.top = `${chromeRect(blockBar).bottom + 6}px`;
1516
+ }
1517
+ }
1518
+ };
1519
+ const rectOf = (el2) => ({
1520
+ left: el2.offsetLeft,
1521
+ top: el2.offsetTop,
1522
+ right: el2.offsetLeft + el2.offsetWidth,
1523
+ bottom: el2.offsetTop + el2.offsetHeight
1524
+ });
1525
+ const union = (rs) => rs.length === 0 ? null : rs.reduce((acc, r) => ({
1526
+ left: Math.min(acc.left, r.left),
1527
+ top: Math.min(acc.top, r.top),
1528
+ right: Math.max(acc.right, r.right),
1529
+ bottom: Math.max(acc.bottom, r.bottom)
1530
+ }));
1531
+ const layoutRows = (card, blocks) => {
1532
+ const elOf = (b) => b.editorId ? card.querySelector(`[data-veo-block-id="${b.editorId}"]`) : null;
1533
+ const cs = getComputedStyle(card);
1534
+ const padL = Number.parseFloat(cs.paddingLeft) || 0;
1535
+ const padR = Number.parseFloat(cs.paddingRight) || 0;
1536
+ const contentLeft = padL;
1537
+ const contentRight = card.clientWidth - padR;
1538
+ const rows = groupLayout(blocks).map((row) => {
1539
+ const columns = row.columns.map((col) => {
1540
+ const items = col.blocks.map(({ block }) => ({ block, el: elOf(block) })).filter(
1541
+ (x) => !!x.el && !!x.block.editorId
1542
+ );
1543
+ const blocksRect2 = union(items.map((x) => rectOf(x.el)));
1544
+ if (!blocksRect2) return null;
1545
+ const colEl = items[0]?.el.parentElement;
1546
+ const rect2 = colEl?.classList.contains("veo-col") ? rectOf(colEl) : blocksRect2;
1547
+ return { items, rect: rect2 };
1548
+ }).filter((c) => c !== null);
1549
+ const blocksRect = union(columns.map((c) => c.rect));
1550
+ if (!blocksRect) return null;
1551
+ const rowEl = columns[0]?.items[0]?.el.parentElement?.parentElement;
1552
+ const rect = rowEl?.classList.contains("veo-row") ? rectOf(rowEl) : { ...blocksRect, left: contentLeft, right: contentRight };
1553
+ const id = columns[0]?.items[0]?.block.editorId;
1554
+ return { id, row: row.row, columns, rect };
1555
+ }).filter((r) => r !== null);
1556
+ return { rows, content: { left: contentLeft, right: contentRight } };
1557
+ };
1558
+ const clearRowChrome = (card) => {
1559
+ card.querySelector(".veo-row-frame")?.remove();
1560
+ card.querySelector(".veo-row-toolbar")?.remove();
1561
+ };
1562
+ const renderRowChrome = (host, stepIndex) => {
1563
+ const card = previewCard(host);
1564
+ if (!card) return;
1565
+ clearRowChrome(card);
1566
+ resolveToolbarOverlaps(card);
1567
+ const { rows } = layoutRows(card, previewBlocks);
1568
+ const rowOfSelected = selectedBlockId ? rows.find(
1569
+ (r) => r.columns.some((c) => c.items.some((i) => i.block.editorId === selectedBlockId))
1570
+ ) : void 0;
1571
+ const target = (selectedRowId ? rows.find((r) => r.id === selectedRowId) : void 0) ?? rowOfSelected ?? (hoverRowId ? rows.find((r) => r.id === hoverRowId) : void 0);
1572
+ if (!target) return;
1573
+ const doc = card.ownerDocument;
1574
+ const cs = getComputedStyle(card);
1575
+ const padT = Number.parseFloat(cs.paddingTop) || 0;
1576
+ const padB = Number.parseFloat(cs.paddingBottom) || 0;
1577
+ const PAD = 6;
1578
+ const fx = Math.max(0, target.rect.left - PAD);
1579
+ const fr = Math.min(card.clientWidth, target.rect.right + PAD);
1580
+ const fy = Math.max(Math.min(padT, target.rect.top), target.rect.top - PAD);
1581
+ const fb = Math.min(
1582
+ Math.max(card.clientHeight - padB, target.rect.bottom),
1583
+ target.rect.bottom + PAD
1584
+ );
1585
+ const frame = doc.createElement("div");
1586
+ frame.className = "veo-row-frame";
1587
+ frame.setAttribute("data-veo-builder-chrome", "");
1588
+ frame.style.left = `${fx}px`;
1589
+ frame.style.top = `${fy}px`;
1590
+ frame.style.width = `${Math.max(0, fr - fx)}px`;
1591
+ frame.style.height = `${Math.max(0, fb - fy)}px`;
1592
+ card.appendChild(frame);
1593
+ const bar = doc.createElement("div");
1594
+ bar.className = "veo-row-toolbar";
1595
+ bar.setAttribute("data-veo-builder-chrome", "");
1596
+ const blockId = target.id;
1597
+ const mk = (action, label, icon, text) => {
1598
+ const b = doc.createElement("button");
1599
+ b.type = "button";
1600
+ b.title = label;
1601
+ b.setAttribute("aria-label", label);
1602
+ b.className = `veo-row-toolbar-${action}`;
1603
+ b.innerHTML = icon;
1604
+ if (text) b.append(text);
1605
+ b.addEventListener("click", (e) => {
1606
+ e.preventDefault();
1607
+ e.stopPropagation();
1608
+ post({ type: "row-action", payload: { stepIndex, blockId, action } });
1609
+ });
1610
+ bar.appendChild(b);
1611
+ };
1612
+ mk("edit", "Editar fila", ICON.pencil, "Editar fila");
1613
+ mk("duplicate", "Duplicar fila", ICON.copy);
1614
+ mk("remove", "Eliminar fila", ICON.trash);
1615
+ card.appendChild(bar);
1616
+ bar.dataset.veoRight = String(Math.max(0, card.clientWidth - fr));
1617
+ bar.dataset.veoLeft = String(fx);
1618
+ bar.dataset.veoTopBelow = String(fb + 6);
1619
+ resolveToolbarOverlaps(card);
1620
+ };
1621
+ const renderCardChrome = (host, stepIndex) => {
1622
+ const card = previewCard(host);
1623
+ if (!card) return;
1624
+ card.querySelector(".veo-card-frame")?.remove();
1625
+ card.querySelector(".veo-card-toolbar")?.remove();
1626
+ if (!hoverCard) {
1627
+ resolveToolbarOverlaps(card);
1628
+ return;
1629
+ }
1630
+ const doc = card.ownerDocument;
1631
+ const frame = doc.createElement("div");
1632
+ frame.className = "veo-card-frame";
1633
+ frame.setAttribute("data-veo-builder-chrome", "");
1634
+ card.appendChild(frame);
1635
+ const bar = doc.createElement("div");
1636
+ bar.className = "veo-card-toolbar";
1637
+ bar.setAttribute("data-veo-builder-chrome", "");
1638
+ const mk = (action, label, active) => {
1639
+ const b = doc.createElement("button");
1640
+ b.type = "button";
1641
+ b.className = `veo-card-toolbar-${action}${active ? " is-active" : ""}`;
1642
+ b.setAttribute("aria-pressed", active ? "true" : "false");
1643
+ b.textContent = label;
1644
+ b.addEventListener("click", (e) => {
1645
+ e.preventDefault();
1646
+ e.stopPropagation();
1647
+ post({ type: "card-action", payload: { stepIndex, action } });
1648
+ });
1649
+ bar.appendChild(b);
1650
+ };
1651
+ mk("edit-container", "Editar contenedor", panelSection === "step");
1652
+ mk("edit-rows", "Editar filas", panelSection === "structure");
1653
+ card.appendChild(bar);
1654
+ bar.style.top = `${-Math.round((bar.offsetHeight || 34) / 2)}px`;
1655
+ resolveToolbarOverlaps(card);
1656
+ };
1657
+ const attachRowHover = (host, stepIndex) => {
1658
+ const onMove = (e) => {
1659
+ const card = previewCard(host);
1660
+ if (!card) return;
1661
+ const overCard = e.composedPath().some(
1662
+ (n) => n instanceof Element && (n.classList.contains("veo-card") || n.classList.contains("veo-card-toolbar"))
1663
+ );
1664
+ if (overCard !== hoverCard) {
1665
+ hoverCard = overCard;
1666
+ renderCardChrome(host, stepIndex);
1667
+ }
1668
+ if (e.composedPath().some((n) => n instanceof Element && n.classList.contains("veo-row-toolbar"))) {
1669
+ return;
1670
+ }
1671
+ const rect = card.getBoundingClientRect();
1672
+ const x = e.clientX - rect.left - card.clientLeft;
1673
+ const y = e.clientY - rect.top - card.clientTop;
1674
+ const bar = card.querySelector(".veo-row-toolbar");
1675
+ if (bar) {
1676
+ const AIR = 10;
1677
+ if (x >= bar.offsetLeft - AIR && x <= bar.offsetLeft + bar.offsetWidth + AIR && y >= bar.offsetTop - 14 && y <= bar.offsetTop + bar.offsetHeight + AIR) {
1678
+ return;
1679
+ }
1680
+ }
1681
+ const TOL = 6;
1682
+ const hit = layoutRows(card, previewBlocks).rows.find(
1683
+ (r) => x >= r.rect.left - TOL && x <= r.rect.right + TOL && y >= r.rect.top - TOL && y <= r.rect.bottom + TOL
1684
+ );
1685
+ const next = hit?.id ?? null;
1686
+ if (next === hoverRowId) return;
1687
+ hoverRowId = next;
1688
+ renderRowChrome(host, stepIndex);
1689
+ };
1690
+ const onLeave = () => {
1691
+ if (hoverCard) {
1692
+ hoverCard = false;
1693
+ renderCardChrome(host, stepIndex);
1694
+ }
1695
+ if (hoverRowId === null) return;
1696
+ hoverRowId = null;
1697
+ renderRowChrome(host, stepIndex);
1698
+ };
1699
+ host.addEventListener("mousemove", onMove);
1700
+ host.addEventListener("mouseleave", onLeave);
1701
+ return () => {
1702
+ host.removeEventListener("mousemove", onMove);
1703
+ host.removeEventListener("mouseleave", onLeave);
1704
+ };
1705
+ };
1706
+ const renderGapZones = (host, stepIndex, blocks) => {
1707
+ const card = previewCard(host);
1708
+ if (!card) return;
1709
+ for (const z of card.querySelectorAll(".veo-gap")) z.remove();
1710
+ const doc = card.ownerDocument;
1711
+ const GAP = 18;
1712
+ const EDGE2 = 4;
1713
+ const zone = (kind, x, y, w, h, label, blockId, action) => {
1714
+ const z = doc.createElement("div");
1715
+ z.className = `veo-gap veo-gap--${kind}`;
1716
+ z.setAttribute("data-veo-builder-chrome", "");
1717
+ z.dataset.veoGapAction = action;
1718
+ z.style.left = `${x}px`;
1719
+ z.style.top = `${y}px`;
1720
+ z.style.width = `${Math.max(w, GAP)}px`;
1721
+ z.style.height = `${Math.max(h, GAP)}px`;
1722
+ const line = doc.createElement("i");
1723
+ const btn = doc.createElement("button");
1724
+ btn.type = "button";
1725
+ btn.title = label;
1726
+ btn.setAttribute("aria-label", label);
1727
+ btn.textContent = "+";
1728
+ btn.addEventListener("click", (e) => {
1729
+ e.preventDefault();
1730
+ e.stopPropagation();
1731
+ post({ type: "block-layout", payload: { stepIndex, blockId, action } });
1732
+ });
1733
+ z.append(line, btn);
1734
+ card.appendChild(z);
1735
+ };
1736
+ const { rows, content } = layoutRows(card, blocks);
1737
+ if (rows.length === 0) return;
1738
+ const cw = content.right - content.left;
1739
+ rows.forEach((row, k) => {
1740
+ const first = row.columns[0]?.items[0]?.block.editorId;
1741
+ if (k === 0) {
1742
+ zone(
1743
+ "h",
1744
+ content.left,
1745
+ row.rect.top - EDGE2 - GAP,
1746
+ cw,
1747
+ GAP,
1748
+ "Agregar fila",
1749
+ first,
1750
+ "row-before"
1751
+ );
1752
+ } else {
1753
+ const prev = rows[k - 1];
1754
+ const y = (prev.rect.bottom + row.rect.top) / 2 - GAP / 2;
1755
+ zone("h", content.left, y, cw, GAP, "Agregar fila", first, "row-before");
1756
+ }
1757
+ if (k === rows.length - 1) {
1758
+ zone(
1759
+ "h",
1760
+ content.left,
1761
+ row.rect.bottom + EDGE2,
1762
+ cw,
1763
+ GAP,
1764
+ "Agregar fila",
1765
+ first,
1766
+ "row-after"
1767
+ );
1768
+ }
1769
+ });
1770
+ for (const row of rows) {
1771
+ const rh = row.rect.bottom - row.rect.top;
1772
+ row.columns.forEach((col, j) => {
1773
+ const id = col.items[0]?.block.editorId;
1774
+ if (j === 0) {
1775
+ zone(
1776
+ "v",
1777
+ row.rect.left - EDGE2 - GAP,
1778
+ row.rect.top,
1779
+ GAP,
1780
+ rh,
1781
+ "Agregar columna",
1782
+ id,
1783
+ "col-before"
1784
+ );
1785
+ } else {
1786
+ const prev = row.columns[j - 1];
1787
+ const x = (prev.rect.right + col.rect.left) / 2 - GAP / 2;
1788
+ zone("v", x, row.rect.top, GAP, rh, "Agregar columna", id, "col-before");
1789
+ }
1790
+ if (j === row.columns.length - 1) {
1791
+ zone(
1792
+ "v",
1793
+ row.rect.right + EDGE2,
1794
+ row.rect.top,
1795
+ GAP,
1796
+ rh,
1797
+ "Agregar columna",
1798
+ id,
1799
+ "col-after"
1800
+ );
1801
+ }
1802
+ });
1803
+ }
1804
+ for (const row of rows) {
1805
+ for (const col of row.columns) {
1806
+ const w = col.rect.right - col.rect.left;
1807
+ col.items.forEach((it, i) => {
1808
+ const next = col.items[i + 1];
1809
+ if (next) {
1810
+ const a = rectOf(it.el);
1811
+ const b = rectOf(next.el);
1812
+ const y = (a.bottom + b.top) / 2 - GAP / 2;
1813
+ zone(
1814
+ "h",
1815
+ col.rect.left,
1816
+ y,
1817
+ w,
1818
+ GAP,
1819
+ "Agregar bloque",
1820
+ it.block.editorId,
1821
+ "stack-after"
1822
+ );
1823
+ } else if (row.columns.length > 1 && col.rect.bottom < row.rect.bottom - GAP) {
1824
+ zone(
1825
+ "h",
1826
+ col.rect.left,
1827
+ col.rect.bottom + EDGE2,
1828
+ w,
1829
+ GAP,
1830
+ "Agregar bloque",
1831
+ it.block.editorId,
1832
+ "stack-after"
1833
+ );
1834
+ }
1835
+ });
1836
+ }
1837
+ }
1838
+ };
1839
+ const attachCardClick = (host, stepIndex) => {
1840
+ let downAt = null;
1841
+ const onDown = (e) => {
1842
+ downAt = { x: e.clientX, y: e.clientY };
1843
+ };
1844
+ const onClick = (e) => {
1845
+ const path = e.composedPath();
1846
+ if (path.some(
1847
+ (n) => n instanceof Element && (n.classList.contains("veo-block-toolbar") || n.classList.contains("veo-row-toolbar") || n.classList.contains("veo-card-toolbar") || n.classList.contains("veo-gap"))
1848
+ )) {
1849
+ return;
1850
+ }
1851
+ const inCard = path.some((n) => n instanceof Element && n.classList.contains("veo-card"));
1852
+ if (!inCard) return;
1853
+ if (downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 4) return;
1854
+ const blockEl = path.find(
1855
+ (n) => n instanceof HTMLElement && !!n.dataset.veoBlockId
1856
+ );
1857
+ if (blockEl) {
1858
+ if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
1859
+ e.preventDefault();
1860
+ e.stopPropagation();
1861
+ }
1862
+ selectedBlockId = blockEl.dataset.veoBlockId ?? null;
1863
+ applyBlockHighlight(host, stepIndex);
1864
+ if (selectedBlockId) {
1865
+ post({ type: "block-clicked", payload: { stepIndex, blockId: selectedBlockId } });
1866
+ }
1867
+ return;
1868
+ }
1869
+ const interactive = path.some(
1870
+ (n) => n instanceof Element && n.matches("button, a, input, textarea, select, label, [contenteditable]")
1871
+ );
1872
+ if (interactive) return;
1873
+ selectedBlockId = null;
1874
+ clearBlockHighlight(host);
1875
+ renderRowChrome(host, stepIndex);
1876
+ post({ type: "card-clicked", payload: { stepIndex } });
1877
+ };
1878
+ host.addEventListener("mousedown", onDown, true);
1879
+ host.addEventListener("click", onClick, true);
1880
+ return () => {
1881
+ host.removeEventListener("mousedown", onDown, true);
1882
+ host.removeEventListener("click", onClick, true);
1883
+ };
1884
+ };
1885
+ const attachRawBlock = (stepIndex, blockId) => {
1886
+ const nodes = Array.from(
1887
+ document.querySelectorAll(`[${RAW_BLOCK_ATTR}="${blockId}"]`)
1888
+ );
1889
+ if (nodes.length === 0) return null;
1890
+ const doc = document;
1891
+ const box = doc.createElement("div");
1892
+ box.setAttribute("data-veo-builder-chrome", "");
1893
+ box.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;z-index:2147483646;";
1894
+ box.style.display = "none";
1895
+ const shadow = box.attachShadow({ mode: "open" });
1896
+ const style = doc.createElement("style");
1897
+ style.textContent = GUIDE_STYLES;
1898
+ shadow.appendChild(style);
1899
+ const frame = doc.createElement("div");
1900
+ frame.className = "veo-block-frame";
1901
+ frame.style.position = "fixed";
1902
+ const bar = doc.createElement("div");
1903
+ bar.className = "veo-block-toolbar";
1904
+ bar.style.position = "fixed";
1905
+ const mk = (action, label, icon, enabled = true) => {
1906
+ const b = doc.createElement("button");
1907
+ b.type = "button";
1908
+ b.title = label;
1909
+ b.setAttribute("aria-label", label);
1910
+ b.className = `veo-block-toolbar-${action}`;
1911
+ b.innerHTML = icon;
1912
+ b.disabled = !enabled;
1913
+ b.addEventListener("click", (e) => {
1914
+ e.preventDefault();
1915
+ e.stopPropagation();
1916
+ post({ type: "block-action", payload: { stepIndex, blockId, action } });
1917
+ });
1918
+ bar.appendChild(b);
1919
+ };
1920
+ mk("edit", "Editar", ICON.pencil);
1921
+ mk("duplicate", "Duplicar", ICON.copy, false);
1922
+ mk("remove", "Eliminar", ICON.trash);
1923
+ mk("move-left", "Mover a la izquierda", ICON_MOVE.left, false);
1924
+ mk("move-right", "Mover a la derecha", ICON_MOVE.right, false);
1925
+ frame.style.display = "none";
1926
+ bar.style.display = "none";
1927
+ const GAP = 18;
1928
+ const EDGE2 = 4;
1929
+ const gap = (kind, label, action) => {
1930
+ const z = doc.createElement("div");
1931
+ z.className = `veo-gap veo-gap--${kind}`;
1932
+ z.style.position = "fixed";
1933
+ z.dataset.veoGapAction = action;
1934
+ const line = doc.createElement("i");
1935
+ const btn = doc.createElement("button");
1936
+ btn.type = "button";
1937
+ btn.title = label;
1938
+ btn.setAttribute("aria-label", label);
1939
+ btn.textContent = "+";
1940
+ btn.addEventListener("click", (e) => {
1941
+ e.preventDefault();
1942
+ e.stopPropagation();
1943
+ post({ type: "block-layout", payload: { stepIndex, blockId, action } });
1944
+ });
1945
+ z.append(line, btn);
1946
+ return z;
1947
+ };
1948
+ const gaps = {
1949
+ top: gap("h", "Agregar fila", "row-before"),
1950
+ bottom: gap("h", "Agregar fila", "row-after"),
1951
+ left: gap("v", "Agregar columna", "col-before"),
1952
+ right: gap("v", "Agregar columna", "col-after")
1953
+ };
1954
+ shadow.append(frame, bar, gaps.top, gaps.bottom, gaps.left, gaps.right);
1955
+ doc.body.appendChild(box);
1956
+ box.style.display = "";
1957
+ let visible = false;
1958
+ const place = (el2, x, y, w, h) => {
1959
+ el2.style.left = `${x}px`;
1960
+ el2.style.top = `${y}px`;
1961
+ el2.style.width = `${w}px`;
1962
+ el2.style.height = `${h}px`;
1963
+ };
1964
+ const reposition = () => {
1965
+ const live = nodes.filter((n) => n.isConnected);
1966
+ if (live.length === 0) return;
1967
+ let l = Number.POSITIVE_INFINITY;
1968
+ let t = Number.POSITIVE_INFINITY;
1969
+ let r = Number.NEGATIVE_INFINITY;
1970
+ let b = Number.NEGATIVE_INFINITY;
1971
+ for (const n of live) {
1972
+ const rc = n.getBoundingClientRect();
1973
+ if (rc.width === 0 && rc.height === 0) continue;
1974
+ l = Math.min(l, rc.left);
1975
+ t = Math.min(t, rc.top);
1976
+ r = Math.max(r, rc.right);
1977
+ b = Math.max(b, rc.bottom);
1978
+ }
1979
+ if (!Number.isFinite(l)) return;
1980
+ place(gaps.top, l, t - EDGE2 - GAP, r - l, GAP);
1981
+ place(gaps.bottom, l, b + EDGE2, r - l, GAP);
1982
+ place(gaps.left, l - EDGE2 - GAP, t, GAP, b - t);
1983
+ place(gaps.right, r + EDGE2, t, GAP, b - t);
1984
+ if (!visible) return;
1985
+ place(frame, l, t, r - l, b - t);
1986
+ const bh = bar.offsetHeight || TOOLBAR_H;
1987
+ const bw = bar.offsetWidth;
1988
+ bar.style.left = `${Math.max(0, r - bw)}px`;
1989
+ bar.style.top = `${Math.max(0, t - bh - 6)}px`;
1990
+ };
1991
+ const select = (id) => {
1992
+ visible = id === blockId;
1993
+ frame.style.display = visible ? "" : "none";
1994
+ bar.style.display = visible ? "" : "none";
1995
+ reposition();
1996
+ };
1997
+ reposition();
1998
+ const onClick = (e) => {
1999
+ const path = e.composedPath();
2000
+ if (!nodes.some((n) => path.includes(n))) return;
2001
+ if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
2002
+ e.preventDefault();
2003
+ e.stopPropagation();
2004
+ }
2005
+ selectedBlockId = blockId;
2006
+ select(blockId);
2007
+ post({ type: "block-clicked", payload: { stepIndex, blockId } });
2008
+ };
2009
+ doc.addEventListener("click", onClick, true);
2010
+ window.addEventListener("scroll", reposition, true);
2011
+ window.addEventListener("resize", reposition);
2012
+ return {
2013
+ select,
2014
+ detach: () => {
2015
+ doc.removeEventListener("click", onClick, true);
2016
+ window.removeEventListener("scroll", reposition, true);
2017
+ window.removeEventListener("resize", reposition);
2018
+ box.remove();
2019
+ }
2020
+ };
2021
+ };
1218
2022
  const applyPreviewGated = (cmd) => {
1219
2023
  const showNow = !cmd.honorTrigger;
1220
2024
  if (showNow) {
@@ -1234,7 +2038,7 @@ function initBuilderMode() {
1234
2038
  return;
1235
2039
  }
1236
2040
  const devices = cmd.guide.activationRules?.devices;
1237
- if (!showNow && !previewMode && devices && !isDeviceAllowed(devices)) {
2041
+ if (!showNow && previewMode && devices && !isDeviceAllowed(devices)) {
1238
2042
  cancelPreviewDelay();
1239
2043
  withholdPreview();
1240
2044
  postPreviewStatus({ state: "device-mismatch", devices });
@@ -1286,15 +2090,16 @@ function initBuilderMode() {
1286
2090
  postPreviewStatus({ state: "waiting-delay", delayMs });
1287
2091
  };
1288
2092
  const applyPreview = (cmd) => {
1289
- manipulator?.detach();
1290
- manipulator = null;
1291
- dropTarget?.detach();
1292
- dropTarget = null;
2093
+ if (cmd.editable && !previewMode && tryPatchPreview(cmd)) {
2094
+ previewStepIndex = cmd.guide.startStepIndex ?? 0;
2095
+ return;
2096
+ }
2097
+ detachPreviewTools();
1293
2098
  previewStepIndex = cmd.guide.startStepIndex ?? 0;
1294
2099
  const rules = cmd.guide.activationRules;
1295
2100
  const trig = rules?.trigger;
1296
2101
  const sel = rules?.selector?.trim();
1297
- const gate = cmd.honorTrigger && !previewFired && !previewMode;
2102
+ const gate = cmd.honorTrigger && !previewFired && previewMode;
1298
2103
  const waitsForTarget = (trig === "on_click" || trig === "on_hover") && (!isAutomaticActivation(rules) || previewDismissed);
1299
2104
  if (gate && waitsForTarget && sel) {
1300
2105
  cancelPreviewDelay();
@@ -1311,13 +2116,13 @@ function initBuilderMode() {
1311
2116
  }
1312
2117
  disarmPreviewTrigger();
1313
2118
  cancelPreviewDelay();
2119
+ const fetchProgress = previewProgressFetcher();
1314
2120
  const handle = previewGuide({
1315
2121
  ...cmd.guide,
2122
+ ...fetchProgress ? { fetchProgress } : {},
1316
2123
  onClosed: () => {
1317
- manipulator?.detach();
1318
- manipulator = null;
1319
- dropTarget?.detach();
1320
- dropTarget = null;
2124
+ detachPreviewTools();
2125
+ shownPreview = null;
1321
2126
  previewFired = false;
1322
2127
  previewDismissed = true;
1323
2128
  if (previewMode) {
@@ -1325,9 +2130,7 @@ function initBuilderMode() {
1325
2130
  const t = r?.trigger;
1326
2131
  const selector = r?.selector?.trim();
1327
2132
  if ((t === "on_click" || t === "on_hover") && selector) armPreviewTrigger(selector, t);
1328
- return;
1329
2133
  }
1330
- if (lastPreview) applyPreviewGated(lastPreview);
1331
2134
  }
1332
2135
  });
1333
2136
  if (cmd.honorTrigger) postPreviewStatus({ state: "shown" });
@@ -1335,7 +2138,23 @@ function initBuilderMode() {
1335
2138
  void handle.ready.then((result) => {
1336
2139
  if (!result.rendered) return;
1337
2140
  const host = handle.host();
1338
- if (!host) return;
2141
+ if (!host) {
2142
+ const idx = cmd.guide.startStepIndex ?? 0;
2143
+ const only = cmd.guide.guideSteps[idx]?.blocks?.[0];
2144
+ if (only?.type === "code" && only.editorId) {
2145
+ rawBlock = attachRawBlock(idx, only.editorId);
2146
+ rawBlock?.select(selectedBlockId);
2147
+ }
2148
+ return;
2149
+ }
2150
+ host.classList.add("veo-no-anim");
2151
+ shownPreview = cmd;
2152
+ cardClick = attachCardClick(host, cmd.guide.startStepIndex ?? 0);
2153
+ const stepBlocks = cmd.guide.guideSteps[cmd.guide.startStepIndex ?? 0]?.blocks ?? [];
2154
+ previewBlocks = stepBlocks;
2155
+ applyBlockHighlight(host, cmd.guide.startStepIndex ?? 0);
2156
+ renderGapZones(host, cmd.guide.startStepIndex ?? 0, stepBlocks);
2157
+ rowHover = attachRowHover(host, cmd.guide.startStepIndex ?? 0);
1339
2158
  manipulator = attachDesignManipulator({
1340
2159
  host,
1341
2160
  guideType: cmd.guide.guideType,
@@ -1403,13 +2222,21 @@ function initBuilderMode() {
1403
2222
  break;
1404
2223
  case "editor-state":
1405
2224
  chrome?.setState(cmd.state);
2225
+ if (panelSection !== cmd.state.section) {
2226
+ panelSection = cmd.state.section;
2227
+ const h = document.querySelector("[data-veo-guide]");
2228
+ if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
2229
+ }
1406
2230
  panel?.setTitle(
1407
- cmd.state.section === "activation" ? "Ajustes de activaci\xF3n" : cmd.state.section === "badge" ? "Ajustes de badge" : cmd.state.section === "target" ? "Ajustes del elemento objetivo" : cmd.state.steps[cmd.state.activeStep]?.label ?? "Paso"
2231
+ cmd.state.section === "activation" ? "Ajustes de activaci\xF3n" : cmd.state.section === "badge" ? "Ajustes de badge" : cmd.state.section === "target" ? "Ajustes del elemento objetivo" : cmd.state.section === "row" ? "Editar fila" : cmd.state.blockLabel ? `Editor \xB7 ${cmd.state.blockLabel}` : cmd.state.steps[cmd.state.activeStep]?.label ?? "Paso"
1408
2232
  );
1409
2233
  break;
1410
2234
  case "panel-visibility":
1411
2235
  panel?.setVisible(cmd.visible);
1412
2236
  break;
2237
+ case "panel-size":
2238
+ panel?.setSize(cmd.size);
2239
+ break;
1413
2240
  case "start-picker": {
1414
2241
  picker?.stop();
1415
2242
  const panelWasShown = panel?.isVisible() ?? false;
@@ -1417,10 +2244,7 @@ function initBuilderMode() {
1417
2244
  chrome?.setHidden(true);
1418
2245
  const previewToRestore = lastPreview;
1419
2246
  if (previewToRestore) {
1420
- manipulator?.detach();
1421
- manipulator = null;
1422
- dropTarget?.detach();
1423
- dropTarget = null;
2247
+ detachPreviewTools();
1424
2248
  closeGuidePreview();
1425
2249
  }
1426
2250
  const restorePanel = () => {
@@ -1459,11 +2283,21 @@ function initBuilderMode() {
1459
2283
  case "block-drag":
1460
2284
  handleBlockDrag(cmd);
1461
2285
  break;
2286
+ case "select-block": {
2287
+ selectedBlockId = cmd.blockId;
2288
+ const h = document.querySelector("[data-veo-guide]");
2289
+ if (h && !previewMode) applyBlockHighlight(h, previewStepIndex);
2290
+ if (!previewMode) rawBlock?.select(cmd.blockId);
2291
+ break;
2292
+ }
2293
+ case "select-row": {
2294
+ selectedRowId = cmd.blockId;
2295
+ const h = document.querySelector("[data-veo-guide]");
2296
+ if (h && !previewMode) renderRowChrome(h, previewStepIndex);
2297
+ break;
2298
+ }
1462
2299
  case "close-preview":
1463
- manipulator?.detach();
1464
- manipulator = null;
1465
- dropTarget?.detach();
1466
- dropTarget = null;
2300
+ detachPreviewTools();
1467
2301
  hideDragGhost();
1468
2302
  pendingPreview = null;
1469
2303
  lastPreview = null;
@@ -1516,10 +2350,7 @@ function initBuilderMode() {
1516
2350
  removeNavWatcher();
1517
2351
  picker?.stop();
1518
2352
  picker = null;
1519
- manipulator?.detach();
1520
- manipulator = null;
1521
- dropTarget?.detach();
1522
- dropTarget = null;
2353
+ detachPreviewTools();
1523
2354
  hideDragGhost();
1524
2355
  pendingPreview = null;
1525
2356
  disarmPreviewTrigger();
@@ -1547,6 +2378,8 @@ function initBuilderMode() {
1547
2378
  case "preview":
1548
2379
  if (previewMode) return;
1549
2380
  previewMode = true;
2381
+ previewFired = false;
2382
+ previewDismissed = false;
1550
2383
  panelWasVisible = panel?.isVisible() ?? true;
1551
2384
  panel?.setVisible(false);
1552
2385
  chrome?.setPreview(true);
@@ -1555,6 +2388,8 @@ function initBuilderMode() {
1555
2388
  case "exit-preview":
1556
2389
  if (!previewMode) return;
1557
2390
  previewMode = false;
2391
+ disarmPreviewTrigger();
2392
+ cancelPreviewDelay();
1558
2393
  chrome?.setPreview(false);
1559
2394
  panel?.setVisible(panelWasVisible);
1560
2395
  if (lastPreview) applyPreviewGated(lastPreview);
@@ -1636,6 +2471,25 @@ function resolveDashboardOrigin(originHint) {
1636
2471
  }
1637
2472
  return null;
1638
2473
  }
2474
+ function previewProgressFetcher() {
2475
+ const cfg = window.__veoBuilder;
2476
+ if (!cfg?.apiKey || !cfg?.apiUrl) return void 0;
2477
+ const apiUrl = cfg.apiUrl.replace(/\/+$/, "");
2478
+ const apiKey = cfg.apiKey;
2479
+ return async (guideIds) => {
2480
+ const params = new URLSearchParams({
2481
+ endUserId: "veo_preview",
2482
+ guideIds: guideIds.join(","),
2483
+ includeUnpublished: "1"
2484
+ });
2485
+ const res = await fetch(`${apiUrl}/v1/guides/progress?${params}`, {
2486
+ headers: { "X-Api-Key": apiKey }
2487
+ });
2488
+ if (!res.ok) return [];
2489
+ const data = await res.json();
2490
+ return data.items ?? [];
2491
+ };
2492
+ }
1639
2493
 
1640
2494
  // src/plugins/builder/builder-session.ts
1641
2495
  var DEFAULT_FEATURES = "popup,width=1280,height=860";
@@ -1757,5 +2611,5 @@ function noopSession() {
1757
2611
  }
1758
2612
 
1759
2613
  export { VEO_BLOCK_MIME, attachBlockDropTarget, attachDesignManipulator, createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker };
1760
- //# sourceMappingURL=chunk-IZXJA55N.mjs.map
1761
- //# sourceMappingURL=chunk-IZXJA55N.mjs.map
2614
+ //# sourceMappingURL=chunk-N4VFMZFA.mjs.map
2615
+ //# sourceMappingURL=chunk-N4VFMZFA.mjs.map