veo-sdk 0.5.33 → 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, previewGuide } from './chunk-M6PB5UMW.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;
@@ -1196,6 +1271,7 @@ function initBuilderMode() {
1196
1271
  let previewTrigger = null;
1197
1272
  let previewFired = false;
1198
1273
  let previewDelay = null;
1274
+ let previewDismissed = false;
1199
1275
  let panelWasVisible = true;
1200
1276
  const getTarget = () => panel ? panel.target() : staticTarget;
1201
1277
  const post = (event) => {
@@ -1207,15 +1283,751 @@ function initBuilderMode() {
1207
1283
  };
1208
1284
  const previewMatchesPage = (cmd) => stepMatchesPage(previewStep(cmd)?.pageLocation, window.location.href);
1209
1285
  const postPreviewStatus = (payload) => post({ type: "preview-status", payload });
1210
- const withholdPreview = () => {
1286
+ const detachPreviewTools = () => {
1211
1287
  manipulator?.detach();
1212
1288
  manipulator = null;
1213
1289
  dropTarget?.detach();
1214
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;
1215
1304
  closeGuidePreview();
1216
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
+ };
1217
2022
  const applyPreviewGated = (cmd) => {
1218
- lastPreview = cmd;
2023
+ const showNow = !cmd.honorTrigger;
2024
+ if (showNow) {
2025
+ disarmPreviewTrigger();
2026
+ cancelPreviewDelay();
2027
+ previewFired = true;
2028
+ previewDismissed = false;
2029
+ }
2030
+ lastPreview = showNow ? { ...cmd, honorTrigger: true } : cmd;
1219
2031
  if (!previewMatchesPage(cmd)) {
1220
2032
  cancelPreviewDelay();
1221
2033
  withholdPreview();
@@ -1226,13 +2038,13 @@ function initBuilderMode() {
1226
2038
  return;
1227
2039
  }
1228
2040
  const devices = cmd.guide.activationRules?.devices;
1229
- if (cmd.honorTrigger && !previewMode && devices && !isDeviceAllowed(devices)) {
2041
+ if (!showNow && previewMode && devices && !isDeviceAllowed(devices)) {
1230
2042
  cancelPreviewDelay();
1231
2043
  withholdPreview();
1232
2044
  postPreviewStatus({ state: "device-mismatch", devices });
1233
2045
  return;
1234
2046
  }
1235
- applyPreview(cmd);
2047
+ applyPreview(lastPreview);
1236
2048
  };
1237
2049
  const disarmPreviewTrigger = () => {
1238
2050
  previewTrigger?.();
@@ -1257,6 +2069,7 @@ function initBuilderMode() {
1257
2069
  }
1258
2070
  disarmPreviewTrigger();
1259
2071
  previewFired = true;
2072
+ previewDismissed = false;
1260
2073
  if (lastPreview) applyPreviewGated(lastPreview);
1261
2074
  };
1262
2075
  document.addEventListener("click", onFire, true);
@@ -1277,22 +2090,24 @@ function initBuilderMode() {
1277
2090
  postPreviewStatus({ state: "waiting-delay", delayMs });
1278
2091
  };
1279
2092
  const applyPreview = (cmd) => {
1280
- manipulator?.detach();
1281
- manipulator = null;
1282
- dropTarget?.detach();
1283
- dropTarget = null;
2093
+ if (cmd.editable && !previewMode && tryPatchPreview(cmd)) {
2094
+ previewStepIndex = cmd.guide.startStepIndex ?? 0;
2095
+ return;
2096
+ }
2097
+ detachPreviewTools();
1284
2098
  previewStepIndex = cmd.guide.startStepIndex ?? 0;
1285
2099
  const rules = cmd.guide.activationRules;
1286
2100
  const trig = rules?.trigger;
1287
2101
  const sel = rules?.selector?.trim();
1288
- const gate = cmd.honorTrigger && !previewFired && !previewMode;
1289
- if (gate && (trig === "on_click" || trig === "on_hover") && sel) {
2102
+ const gate = cmd.honorTrigger && !previewFired && previewMode;
2103
+ const waitsForTarget = (trig === "on_click" || trig === "on_hover") && (!isAutomaticActivation(rules) || previewDismissed);
2104
+ if (gate && waitsForTarget && sel) {
1290
2105
  cancelPreviewDelay();
1291
2106
  closeGuidePreview();
1292
2107
  armPreviewTrigger(sel, trig);
1293
2108
  return;
1294
2109
  }
1295
- const delayMs = trig === "delayed" ? rules?.delayMs ?? 0 : 0;
2110
+ const delayMs = activationDelayMs(rules);
1296
2111
  if (gate && delayMs > 0) {
1297
2112
  disarmPreviewTrigger();
1298
2113
  closeGuidePreview();
@@ -1301,15 +2116,21 @@ function initBuilderMode() {
1301
2116
  }
1302
2117
  disarmPreviewTrigger();
1303
2118
  cancelPreviewDelay();
2119
+ const fetchProgress = previewProgressFetcher();
1304
2120
  const handle = previewGuide({
1305
2121
  ...cmd.guide,
2122
+ ...fetchProgress ? { fetchProgress } : {},
1306
2123
  onClosed: () => {
1307
- manipulator?.detach();
1308
- manipulator = null;
1309
- dropTarget?.detach();
1310
- dropTarget = null;
2124
+ detachPreviewTools();
2125
+ shownPreview = null;
1311
2126
  previewFired = false;
1312
- if (lastPreview) applyPreviewGated(lastPreview);
2127
+ previewDismissed = true;
2128
+ if (previewMode) {
2129
+ const r = lastPreview?.guide.activationRules;
2130
+ const t = r?.trigger;
2131
+ const selector = r?.selector?.trim();
2132
+ if ((t === "on_click" || t === "on_hover") && selector) armPreviewTrigger(selector, t);
2133
+ }
1313
2134
  }
1314
2135
  });
1315
2136
  if (cmd.honorTrigger) postPreviewStatus({ state: "shown" });
@@ -1317,7 +2138,23 @@ function initBuilderMode() {
1317
2138
  void handle.ready.then((result) => {
1318
2139
  if (!result.rendered) return;
1319
2140
  const host = handle.host();
1320
- 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);
1321
2158
  manipulator = attachDesignManipulator({
1322
2159
  host,
1323
2160
  guideType: cmd.guide.guideType,
@@ -1385,13 +2222,21 @@ function initBuilderMode() {
1385
2222
  break;
1386
2223
  case "editor-state":
1387
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
+ }
1388
2230
  panel?.setTitle(
1389
- 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"
1390
2232
  );
1391
2233
  break;
1392
2234
  case "panel-visibility":
1393
2235
  panel?.setVisible(cmd.visible);
1394
2236
  break;
2237
+ case "panel-size":
2238
+ panel?.setSize(cmd.size);
2239
+ break;
1395
2240
  case "start-picker": {
1396
2241
  picker?.stop();
1397
2242
  const panelWasShown = panel?.isVisible() ?? false;
@@ -1399,10 +2244,7 @@ function initBuilderMode() {
1399
2244
  chrome?.setHidden(true);
1400
2245
  const previewToRestore = lastPreview;
1401
2246
  if (previewToRestore) {
1402
- manipulator?.detach();
1403
- manipulator = null;
1404
- dropTarget?.detach();
1405
- dropTarget = null;
2247
+ detachPreviewTools();
1406
2248
  closeGuidePreview();
1407
2249
  }
1408
2250
  const restorePanel = () => {
@@ -1441,17 +2283,28 @@ function initBuilderMode() {
1441
2283
  case "block-drag":
1442
2284
  handleBlockDrag(cmd);
1443
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
+ }
1444
2299
  case "close-preview":
1445
- manipulator?.detach();
1446
- manipulator = null;
1447
- dropTarget?.detach();
1448
- dropTarget = null;
2300
+ detachPreviewTools();
1449
2301
  hideDragGhost();
1450
2302
  pendingPreview = null;
1451
2303
  lastPreview = null;
1452
2304
  disarmPreviewTrigger();
1453
2305
  cancelPreviewDelay();
1454
2306
  previewFired = false;
2307
+ previewDismissed = false;
1455
2308
  closeGuidePreview();
1456
2309
  break;
1457
2310
  case "teardown":
@@ -1497,10 +2350,7 @@ function initBuilderMode() {
1497
2350
  removeNavWatcher();
1498
2351
  picker?.stop();
1499
2352
  picker = null;
1500
- manipulator?.detach();
1501
- manipulator = null;
1502
- dropTarget?.detach();
1503
- dropTarget = null;
2353
+ detachPreviewTools();
1504
2354
  hideDragGhost();
1505
2355
  pendingPreview = null;
1506
2356
  disarmPreviewTrigger();
@@ -1528,6 +2378,8 @@ function initBuilderMode() {
1528
2378
  case "preview":
1529
2379
  if (previewMode) return;
1530
2380
  previewMode = true;
2381
+ previewFired = false;
2382
+ previewDismissed = false;
1531
2383
  panelWasVisible = panel?.isVisible() ?? true;
1532
2384
  panel?.setVisible(false);
1533
2385
  chrome?.setPreview(true);
@@ -1536,6 +2388,8 @@ function initBuilderMode() {
1536
2388
  case "exit-preview":
1537
2389
  if (!previewMode) return;
1538
2390
  previewMode = false;
2391
+ disarmPreviewTrigger();
2392
+ cancelPreviewDelay();
1539
2393
  chrome?.setPreview(false);
1540
2394
  panel?.setVisible(panelWasVisible);
1541
2395
  if (lastPreview) applyPreviewGated(lastPreview);
@@ -1617,6 +2471,25 @@ function resolveDashboardOrigin(originHint) {
1617
2471
  }
1618
2472
  return null;
1619
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
+ }
1620
2493
 
1621
2494
  // src/plugins/builder/builder-session.ts
1622
2495
  var DEFAULT_FEATURES = "popup,width=1280,height=860";
@@ -1738,5 +2611,5 @@ function noopSession() {
1738
2611
  }
1739
2612
 
1740
2613
  export { VEO_BLOCK_MIME, attachBlockDropTarget, attachDesignManipulator, createBuilderSession, initBuilderMode, startElementPicker, stopElementPicker };
1741
- //# sourceMappingURL=chunk-3ZOKCQ54.mjs.map
1742
- //# sourceMappingURL=chunk-3ZOKCQ54.mjs.map
2614
+ //# sourceMappingURL=chunk-N4VFMZFA.mjs.map
2615
+ //# sourceMappingURL=chunk-N4VFMZFA.mjs.map