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.
- package/dist/builder-3QPHB2VJ.mjs +5 -0
- package/dist/{builder-MUTQMYAH.mjs.map → builder-3QPHB2VJ.mjs.map} +1 -1
- package/dist/builder.cjs +2470 -577
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.d.cts +92 -4
- package/dist/builder.d.ts +92 -4
- package/dist/builder.mjs +4 -4
- package/dist/{chunk-FZ72JUAR.mjs → chunk-GMXQMZ4X.mjs} +123 -27
- package/dist/chunk-GMXQMZ4X.mjs.map +1 -0
- package/dist/{chunk-3ZOKCQ54.mjs → chunk-N4VFMZFA.mjs} +917 -44
- package/dist/chunk-N4VFMZFA.mjs.map +1 -0
- package/dist/{chunk-M6PB5UMW.mjs → chunk-O6USA4TU.mjs} +1133 -99
- package/dist/chunk-O6USA4TU.mjs.map +1 -0
- package/dist/{guide-preview-C73rKO0u.d.cts → guide-preview-DylsEhrg.d.cts} +109 -6
- package/dist/{guide-preview-C73rKO0u.d.ts → guide-preview-DylsEhrg.d.ts} +109 -6
- package/dist/index.cjs +2213 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +5 -5
- package/dist/veo-builder.js +261 -28
- package/dist/veo-builder.js.map +1 -1
- package/dist/veo-guides.js +228 -16
- package/dist/veo-guides.js.map +1 -1
- package/package.json +1 -1
- package/dist/builder-MUTQMYAH.mjs +0 -5
- package/dist/chunk-3ZOKCQ54.mjs.map +0 -1
- package/dist/chunk-FZ72JUAR.mjs.map +0 -1
- package/dist/chunk-M6PB5UMW.mjs.map +0 -1
package/dist/builder.cjs
CHANGED
|
@@ -291,6 +291,19 @@ function isDeviceAllowed(devices, viewportWidth = typeof window === "undefined"
|
|
|
291
291
|
return devices === "mobile" ? mobile : !mobile;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// src/plugins/guides/activation-rules.ts
|
|
295
|
+
function isAutomaticActivation(rules) {
|
|
296
|
+
const trigger = rules?.trigger ?? "immediate";
|
|
297
|
+
if (trigger === "immediate" || trigger === "delayed") return true;
|
|
298
|
+
if (trigger === "on_click" || trigger === "on_hover") return rules?.autoShow === true;
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
function activationDelayMs(rules) {
|
|
302
|
+
if (!isAutomaticActivation(rules)) return 0;
|
|
303
|
+
const ms = rules?.delayMs ?? 0;
|
|
304
|
+
return Number.isFinite(ms) && ms > 0 ? ms : 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
294
307
|
// src/plugins/guides/constants.ts
|
|
295
308
|
var GUIDE_Z_INDEX = 2147483640;
|
|
296
309
|
var DEFAULT_ANCHOR_WAIT_MS = 5e3;
|
|
@@ -404,6 +417,125 @@ function rebuildNode(node, doc, depth, budget) {
|
|
|
404
417
|
return rebuilt;
|
|
405
418
|
}
|
|
406
419
|
|
|
420
|
+
// src/plugins/guides/video-block.ts
|
|
421
|
+
function videoEmbedUrl(provider, url) {
|
|
422
|
+
let u;
|
|
423
|
+
try {
|
|
424
|
+
u = new URL(url);
|
|
425
|
+
} catch {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
429
|
+
const host = u.hostname.replace(/^www\./, "").replace(/^m\./, "");
|
|
430
|
+
const id = (re, from) => {
|
|
431
|
+
const m = from.match(re);
|
|
432
|
+
return m?.[1] ?? null;
|
|
433
|
+
};
|
|
434
|
+
switch (provider) {
|
|
435
|
+
case "youtube": {
|
|
436
|
+
let v = null;
|
|
437
|
+
if (host === "youtube.com" || host === "youtube-nocookie.com") {
|
|
438
|
+
v = u.searchParams.get("v") ?? id(/^\/(?:embed|shorts|live)\/([A-Za-z0-9_-]{6,})/, u.pathname);
|
|
439
|
+
} else if (host === "youtu.be") {
|
|
440
|
+
v = id(/^\/([A-Za-z0-9_-]{6,})/, u.pathname);
|
|
441
|
+
}
|
|
442
|
+
if (!v || !/^[A-Za-z0-9_-]{6,}$/.test(v)) return null;
|
|
443
|
+
return `https://www.youtube-nocookie.com/embed/${v}`;
|
|
444
|
+
}
|
|
445
|
+
case "vimeo": {
|
|
446
|
+
if (host !== "vimeo.com" && host !== "player.vimeo.com") return null;
|
|
447
|
+
const v = id(/(\d{6,})/, u.pathname);
|
|
448
|
+
return v ? `https://player.vimeo.com/video/${v}` : null;
|
|
449
|
+
}
|
|
450
|
+
case "loom": {
|
|
451
|
+
if (host !== "loom.com") return null;
|
|
452
|
+
const v = id(/^\/(?:share|embed)\/([A-Za-z0-9]{16,})/, u.pathname);
|
|
453
|
+
return v ? `https://www.loom.com/embed/${v}` : null;
|
|
454
|
+
}
|
|
455
|
+
case "wistia": {
|
|
456
|
+
if (!under(host, "wistia.com") && !under(host, "wistia.net")) return null;
|
|
457
|
+
const v = id(/\/(?:medias|embed\/iframe)\/([A-Za-z0-9]{6,})/, u.pathname);
|
|
458
|
+
return v ? `https://fast.wistia.net/embed/iframe/${v}` : null;
|
|
459
|
+
}
|
|
460
|
+
case "msstream": {
|
|
461
|
+
if (!under(host, "microsoftstream.com")) return null;
|
|
462
|
+
const v = id(/\/(?:embed\/)?video\/([0-9a-f-]{36})/i, u.pathname);
|
|
463
|
+
return v ? `https://web.microsoftstream.com/embed/video/${v}?autoplay=false&showinfo=true` : null;
|
|
464
|
+
}
|
|
465
|
+
case "vidyard": {
|
|
466
|
+
if (!under(host, "vidyard.com")) return null;
|
|
467
|
+
const v = id(/\/watch\/([A-Za-z0-9_-]{8,})/, u.pathname) ?? (host === "play.vidyard.com" ? id(/^\/([A-Za-z0-9_-]{8,})/, u.pathname) : null);
|
|
468
|
+
return v ? `https://play.vidyard.com/${v}` : null;
|
|
469
|
+
}
|
|
470
|
+
case "sharepoint":
|
|
471
|
+
return u.protocol === "https:" && under(host, "sharepoint.com") ? u.href : null;
|
|
472
|
+
case "brightcove":
|
|
473
|
+
return u.protocol === "https:" && host === "players.brightcove.net" && u.searchParams.get("videoId") ? u.href : null;
|
|
474
|
+
case "kaltura":
|
|
475
|
+
return u.protocol === "https:" && under(host, "kaltura.com") && u.pathname.startsWith("/p/") ? u.href : null;
|
|
476
|
+
default:
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function under(host, domain) {
|
|
481
|
+
return host === domain || host.endsWith(`.${domain}`);
|
|
482
|
+
}
|
|
483
|
+
function fileVideoUrl(url) {
|
|
484
|
+
try {
|
|
485
|
+
const u = new URL(url);
|
|
486
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
487
|
+
return /\.(mp4|webm|mov)$/i.test(u.pathname) ? u.href : null;
|
|
488
|
+
} catch {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function buildVideoBlock(block, doc, opts = {}) {
|
|
493
|
+
const isFile = block.provider === "file";
|
|
494
|
+
const src = typeof block.url === "string" ? isFile ? fileVideoUrl(block.url) : videoEmbedUrl(block.provider, block.url) : null;
|
|
495
|
+
if (!src) return null;
|
|
496
|
+
const s = block.style ?? {};
|
|
497
|
+
const wrap = doc.createElement("div");
|
|
498
|
+
wrap.className = "veo-guide-video";
|
|
499
|
+
const ratio = s.aspect === "4:3" ? "4 / 3" : "16 / 9";
|
|
500
|
+
wrap.style.aspectRatio = ratio;
|
|
501
|
+
if (s.widthMode === "fill") {
|
|
502
|
+
wrap.style.width = "100%";
|
|
503
|
+
} else {
|
|
504
|
+
const w = typeof s.width === "number" && Number.isFinite(s.width) ? s.width : 400;
|
|
505
|
+
wrap.style.width = `${Math.max(120, Math.min(1600, w))}px`;
|
|
506
|
+
wrap.style.maxWidth = "100%";
|
|
507
|
+
}
|
|
508
|
+
const align = s.align === "center" ? "center" : s.align === "right" ? "flex-end" : "flex-start";
|
|
509
|
+
wrap.style.alignSelf = align;
|
|
510
|
+
if (isFile) {
|
|
511
|
+
const video = doc.createElement("video");
|
|
512
|
+
video.src = src;
|
|
513
|
+
video.controls = true;
|
|
514
|
+
video.setAttribute("playsinline", "");
|
|
515
|
+
video.preload = "metadata";
|
|
516
|
+
video.setAttribute(
|
|
517
|
+
"aria-label",
|
|
518
|
+
typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video"
|
|
519
|
+
);
|
|
520
|
+
if (opts.editing) video.style.pointerEvents = "none";
|
|
521
|
+
wrap.appendChild(video);
|
|
522
|
+
return wrap;
|
|
523
|
+
}
|
|
524
|
+
const frame = doc.createElement("iframe");
|
|
525
|
+
frame.src = src;
|
|
526
|
+
frame.title = typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video";
|
|
527
|
+
frame.setAttribute("loading", "lazy");
|
|
528
|
+
frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
529
|
+
frame.setAttribute(
|
|
530
|
+
"allow",
|
|
531
|
+
`accelerometer; autoplay; clipboard-write; encrypted-media; picture-in-picture${opts.editing ? "" : "; fullscreen"}`
|
|
532
|
+
);
|
|
533
|
+
if (!opts.editing) frame.setAttribute("allowfullscreen", "");
|
|
534
|
+
if (opts.editing) frame.style.pointerEvents = "none";
|
|
535
|
+
wrap.appendChild(frame);
|
|
536
|
+
return wrap;
|
|
537
|
+
}
|
|
538
|
+
|
|
407
539
|
// src/plugins/guides/block-builder.ts
|
|
408
540
|
function buildStepContent(step, doc, callbacks) {
|
|
409
541
|
const container = doc.createElement("div");
|
|
@@ -528,8 +660,12 @@ function renderBlocks(container, blocks, doc, callbacks) {
|
|
|
528
660
|
i++;
|
|
529
661
|
}
|
|
530
662
|
}
|
|
531
|
-
function buildContentBlock(block, doc) {
|
|
663
|
+
function buildContentBlock(block, doc, opts = {}) {
|
|
532
664
|
switch (block.type) {
|
|
665
|
+
case "video":
|
|
666
|
+
return buildVideoBlock(block, doc, opts);
|
|
667
|
+
case "divider":
|
|
668
|
+
return buildDividerBlock(block, doc);
|
|
533
669
|
case "title": {
|
|
534
670
|
const el2 = doc.createElement("h2");
|
|
535
671
|
el2.className = "veo-guide-title";
|
|
@@ -553,7 +689,7 @@ function buildContentBlock(block, doc) {
|
|
|
553
689
|
const el2 = doc.createElement("img");
|
|
554
690
|
el2.className = "veo-guide-image";
|
|
555
691
|
el2.src = block.url;
|
|
556
|
-
el2.alt = "";
|
|
692
|
+
el2.alt = typeof block.alt === "string" ? block.alt : "";
|
|
557
693
|
applyBlockStyle(el2, block.style, "image");
|
|
558
694
|
return el2;
|
|
559
695
|
}
|
|
@@ -561,6 +697,32 @@ function buildContentBlock(block, doc) {
|
|
|
561
697
|
return null;
|
|
562
698
|
}
|
|
563
699
|
}
|
|
700
|
+
var DIVIDER_LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dotted", "dashed"]);
|
|
701
|
+
function buildDividerBlock(block, doc) {
|
|
702
|
+
const s = block.style ?? {};
|
|
703
|
+
const wrap = doc.createElement("div");
|
|
704
|
+
wrap.className = "veo-guide-divider";
|
|
705
|
+
wrap.setAttribute("role", "separator");
|
|
706
|
+
const hr = doc.createElement("hr");
|
|
707
|
+
const custom = s.preset === "custom";
|
|
708
|
+
const color = custom && typeof s.color === "string" && s.color ? s.color : s.colorMode === "dark" ? "rgba(255, 255, 255, 0.35)" : "#e5e7eb";
|
|
709
|
+
const thickness = custom && typeof s.thickness === "number" && Number.isFinite(s.thickness) ? clampNum(Math.round(s.thickness), 1, 12) : 1;
|
|
710
|
+
const lineStyle = typeof s.lineStyle === "string" && DIVIDER_LINE_STYLES.has(s.lineStyle) ? s.lineStyle : "solid";
|
|
711
|
+
hr.style.borderTop = `${thickness}px ${lineStyle} ${color}`;
|
|
712
|
+
wrap.appendChild(hr);
|
|
713
|
+
for (const [k, prop] of [
|
|
714
|
+
["marginTop", "marginTop"],
|
|
715
|
+
["marginRight", "marginRight"],
|
|
716
|
+
["marginBottom", "marginBottom"],
|
|
717
|
+
["marginLeft", "marginLeft"]
|
|
718
|
+
]) {
|
|
719
|
+
const v = s[k];
|
|
720
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
721
|
+
wrap.style[prop] = `${clampNum(v, 0, 64)}px`;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return wrap;
|
|
725
|
+
}
|
|
564
726
|
function buildButtonBlock(block, doc, callbacks) {
|
|
565
727
|
const btn = doc.createElement("button");
|
|
566
728
|
btn.type = "button";
|
|
@@ -587,6 +749,12 @@ var BLOCK_ALIGN_SELF = {
|
|
|
587
749
|
function clampNum(n, min, max) {
|
|
588
750
|
return Math.min(max, Math.max(min, n));
|
|
589
751
|
}
|
|
752
|
+
var TEXT_PRESETS = {
|
|
753
|
+
title: { size: 18, weight: 600, lineHeight: 1.3 },
|
|
754
|
+
subtitle: { size: 16, weight: 600, lineHeight: 1.35 },
|
|
755
|
+
paragraph: { size: 14, weight: 400, lineHeight: 1.5 },
|
|
756
|
+
small: { size: 12, weight: 400, lineHeight: 1.5 }
|
|
757
|
+
};
|
|
590
758
|
function applyBlockStyle(el2, style, kind) {
|
|
591
759
|
if (!style || typeof style !== "object") return;
|
|
592
760
|
const s = style;
|
|
@@ -596,14 +764,34 @@ function applyBlockStyle(el2, style, kind) {
|
|
|
596
764
|
}
|
|
597
765
|
const align = typeof s.align === "string" ? s.align : null;
|
|
598
766
|
if (align && (align === "left" || align === "center" || align === "right")) {
|
|
599
|
-
if (kind === "text")
|
|
600
|
-
|
|
767
|
+
if (kind === "text") {
|
|
768
|
+
el2.style.textAlign = align;
|
|
769
|
+
el2.style.alignSelf = BLOCK_ALIGN_SELF[align];
|
|
770
|
+
} else if (kind === "image") el2.style.alignSelf = BLOCK_ALIGN_SELF[align];
|
|
771
|
+
}
|
|
772
|
+
if (kind === "text" && typeof s.preset === "string" && s.preset in TEXT_PRESETS) {
|
|
773
|
+
const preset = TEXT_PRESETS[s.preset];
|
|
774
|
+
if (preset) {
|
|
775
|
+
el2.style.fontSize = `${preset.size}px`;
|
|
776
|
+
el2.style.fontWeight = String(preset.weight);
|
|
777
|
+
el2.style.lineHeight = String(preset.lineHeight);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (kind !== "image" && s.colorMode === "dark") {
|
|
781
|
+
el2.style.background = "#1f1d1b";
|
|
782
|
+
el2.style.color = "#f5f0eb";
|
|
783
|
+
el2.style.borderRadius = el2.style.borderRadius || "8px";
|
|
601
784
|
}
|
|
602
785
|
if (kind !== "image" && typeof s.color === "string") el2.style.color = s.color;
|
|
603
786
|
if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
|
|
604
787
|
el2.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
|
|
605
788
|
}
|
|
606
|
-
if (kind === "image" &&
|
|
789
|
+
if (kind === "image" && s.naturalSize === true) {
|
|
790
|
+
el2.style.width = "auto";
|
|
791
|
+
el2.style.maxWidth = "100%";
|
|
792
|
+
el2.style.maxHeight = "none";
|
|
793
|
+
el2.style.objectFit = "contain";
|
|
794
|
+
} else if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
|
|
607
795
|
el2.style.width = `${clampNum(s.width, 10, 100)}%`;
|
|
608
796
|
}
|
|
609
797
|
if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
|
|
@@ -683,65 +871,324 @@ function readCtaUrl(step) {
|
|
|
683
871
|
return isSafeUrl(candidate) ? candidate : void 0;
|
|
684
872
|
}
|
|
685
873
|
|
|
686
|
-
// src/
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
var SHADOW = {
|
|
709
|
-
none: "none",
|
|
710
|
-
soft: "0 4px 14px rgba(0, 0, 0, 0.10)",
|
|
711
|
-
medium: "0 8px 30px rgba(0, 0, 0, 0.18)",
|
|
712
|
-
strong: "0 24px 60px rgba(0, 0, 0, 0.32)"
|
|
713
|
-
};
|
|
714
|
-
var PRESET_POS = {
|
|
715
|
-
center: [0.5, 0.5],
|
|
716
|
-
top: [0.5, 0],
|
|
717
|
-
bottom: [0.5, 1],
|
|
718
|
-
left: [0, 0.5],
|
|
719
|
-
right: [1, 0.5],
|
|
720
|
-
"top-left": [0, 0],
|
|
721
|
-
"top-right": [1, 0],
|
|
722
|
-
"bottom-left": [0, 1],
|
|
723
|
-
"bottom-right": [1, 1]
|
|
724
|
-
};
|
|
725
|
-
var COLOR_RE = /^#[0-9a-f]{3,8}$|^(rgb|rgba|hsl|hsla)\([\d.,%/\sdeg]+\)$|^[a-z]{3,20}$/i;
|
|
726
|
-
function clamp(n, min, max) {
|
|
727
|
-
return Math.min(max, Math.max(min, n));
|
|
874
|
+
// src/utils/uuid.ts
|
|
875
|
+
function uuidv7() {
|
|
876
|
+
const timestamp = Date.now();
|
|
877
|
+
const timeHex = timestamp.toString(16).padStart(12, "0");
|
|
878
|
+
const random = new Uint8Array(10);
|
|
879
|
+
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
880
|
+
crypto.getRandomValues(random);
|
|
881
|
+
} else {
|
|
882
|
+
for (let i = 0; i < random.length; i++) {
|
|
883
|
+
random[i] = Math.floor(Math.random() * 256);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
random[0] = (random[0] ?? 0) & 15 | 112;
|
|
887
|
+
random[2] = (random[2] ?? 0) & 63 | 128;
|
|
888
|
+
const hex = Array.from(random, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
889
|
+
return [
|
|
890
|
+
timeHex.slice(0, 8),
|
|
891
|
+
timeHex.slice(8, 12),
|
|
892
|
+
hex.slice(0, 4),
|
|
893
|
+
hex.slice(4, 8),
|
|
894
|
+
hex.slice(8, 20)
|
|
895
|
+
].join("-");
|
|
728
896
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
897
|
+
|
|
898
|
+
// src/plugins/guides/renderers/custom-frame.ts
|
|
899
|
+
function mountCustomFrame(doc, step, context, opts) {
|
|
900
|
+
const iframe = doc.createElement("iframe");
|
|
901
|
+
iframe.className = "veo-custom-frame";
|
|
902
|
+
iframe.setAttribute("sandbox", "allow-scripts");
|
|
903
|
+
iframe.setAttribute("title", context.guide.guideName || "OnUser");
|
|
904
|
+
iframe.style.width = opts.width;
|
|
905
|
+
const hasFixedHeight = opts.fixedHeight !== void 0 && opts.fixedHeight !== null;
|
|
906
|
+
if (hasFixedHeight) iframe.style.height = toCssSize(opts.fixedHeight);
|
|
907
|
+
const token = uuidv7();
|
|
908
|
+
const wantsTailwind = step.style?.tailwind === true;
|
|
909
|
+
iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token, {
|
|
910
|
+
hostCss: collectHostStyles(),
|
|
911
|
+
htmlAttrs: hostHtmlAttrs(),
|
|
912
|
+
tailwind: wantsTailwind
|
|
913
|
+
});
|
|
914
|
+
const close = () => context.onClose();
|
|
915
|
+
const onMessage = (event) => {
|
|
916
|
+
if (event.source !== iframe.contentWindow) return;
|
|
917
|
+
const data = event.data;
|
|
918
|
+
if (!data || typeof data !== "object" || data.__veo !== token) return;
|
|
919
|
+
switch (data.type) {
|
|
920
|
+
case "dismiss":
|
|
921
|
+
context.onInteraction({
|
|
922
|
+
guideId: context.guide.guideId,
|
|
923
|
+
stepIndex: 0,
|
|
924
|
+
action: "dismissed"
|
|
925
|
+
});
|
|
926
|
+
close();
|
|
927
|
+
break;
|
|
928
|
+
case "cta":
|
|
929
|
+
context.onInteraction({
|
|
930
|
+
guideId: context.guide.guideId,
|
|
931
|
+
stepIndex: 0,
|
|
932
|
+
action: "cta_clicked"
|
|
933
|
+
});
|
|
934
|
+
if (typeof data.url === "string" && isSafeUrl(data.url)) {
|
|
935
|
+
window.open(data.url, "_blank", "noopener,noreferrer");
|
|
936
|
+
}
|
|
937
|
+
if (data.close !== false) close();
|
|
938
|
+
break;
|
|
939
|
+
case "navigate":
|
|
940
|
+
if (typeof data.url === "string" && isSafeUrl(data.url)) {
|
|
941
|
+
window.location.assign(data.url);
|
|
942
|
+
}
|
|
943
|
+
break;
|
|
944
|
+
case "track":
|
|
945
|
+
if (typeof data.name === "string" && context.onTrack) {
|
|
946
|
+
context.onTrack(data.name, isRecord(data.props) ? data.props : void 0);
|
|
947
|
+
}
|
|
948
|
+
break;
|
|
949
|
+
case "resize":
|
|
950
|
+
if (!hasFixedHeight && typeof data.height === "number" && data.height > 0) {
|
|
951
|
+
iframe.style.height = `${Math.ceil(data.height)}px`;
|
|
952
|
+
}
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
window.addEventListener("message", onMessage);
|
|
957
|
+
return { iframe, cleanup: () => window.removeEventListener("message", onMessage) };
|
|
958
|
+
}
|
|
959
|
+
function toCssSize(value) {
|
|
960
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
961
|
+
}
|
|
962
|
+
var MAX_HOST_CSS = 800 * 1024;
|
|
963
|
+
function collectHostStyles() {
|
|
964
|
+
if (typeof document === "undefined") return "";
|
|
965
|
+
let out = "";
|
|
966
|
+
try {
|
|
967
|
+
for (const sheet of Array.from(document.styleSheets)) {
|
|
968
|
+
let rules = null;
|
|
969
|
+
try {
|
|
970
|
+
rules = sheet.cssRules;
|
|
971
|
+
} catch {
|
|
972
|
+
continue;
|
|
973
|
+
}
|
|
974
|
+
if (!rules) continue;
|
|
975
|
+
for (const rule of Array.from(rules)) {
|
|
976
|
+
out += `${rule.cssText}
|
|
977
|
+
`;
|
|
978
|
+
if (out.length > MAX_HOST_CSS) return out;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
} catch {
|
|
982
|
+
return out;
|
|
734
983
|
}
|
|
735
|
-
|
|
736
|
-
|
|
984
|
+
return out;
|
|
985
|
+
}
|
|
986
|
+
function hostHtmlAttrs() {
|
|
987
|
+
if (typeof document === "undefined") return "";
|
|
988
|
+
try {
|
|
989
|
+
const el2 = document.documentElement;
|
|
990
|
+
const attrs = [];
|
|
991
|
+
for (const name of ["class", "style", "data-theme", "data-mode", "data-color-mode"]) {
|
|
992
|
+
const v = el2.getAttribute(name);
|
|
993
|
+
if (v && v.length <= 1e3 && !/["<>]/.test(v)) attrs.push(`${name}="${v}"`);
|
|
994
|
+
}
|
|
995
|
+
return attrs.join(" ");
|
|
996
|
+
} catch {
|
|
997
|
+
return "";
|
|
737
998
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
999
|
+
}
|
|
1000
|
+
function isRecord(value) {
|
|
1001
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1002
|
+
}
|
|
1003
|
+
function buildSrcdoc(html, css, js, token, opts) {
|
|
1004
|
+
const bridge = `(function(){var T=${JSON.stringify(token)};function P(m){m.__veo=T;parent.postMessage(m,'*');}window.veo={dismiss:function(){P({type:'dismiss'});},cta:function(u){P({type:'cta',url:u});},track:function(n,p){P({type:'track',name:n,props:p||{}});},navigate:function(u){P({type:'navigate',url:u});}};document.addEventListener('click',function(e){var a=e.target&&e.target.closest?e.target.closest('a[href]'):null;if(!a)return;var h=a.getAttribute('href');if(!h||h.charAt(0)==='#'||/^(javascript|mailto|tel):/i.test(h))return;e.preventDefault();if(a.target==='_blank'){P({type:'cta',url:a.href,close:false});}else{P({type:'navigate',url:h});}},true);function H(){var r=document.getElementById('__veo_root');var h=r?r.getBoundingClientRect().height:document.documentElement.scrollHeight;P({type:'resize',height:Math.ceil(h)});}window.addEventListener('load',H);try{if(typeof ResizeObserver!=='undefined'){var _r=document.getElementById('__veo_root');new ResizeObserver(H).observe(_r||document.documentElement);}}catch(e){}})();`;
|
|
1005
|
+
const hostStyle = opts.hostCss ? `<style>${escapeClosing(opts.hostCss, "style")}</style>` : "";
|
|
1006
|
+
const tailwind = opts.tailwind ? '<script src="https://cdn.tailwindcss.com"></script>' : "";
|
|
1007
|
+
return (
|
|
1008
|
+
// Orden: base → CSS del host (utilidades+clases propias+:root) → reset
|
|
1009
|
+
// flotante (fondo transparente, sin márgenes) → Tailwind → CSS del autor (gana).
|
|
1010
|
+
`<!DOCTYPE html><html ${opts.htmlAttrs}><head><meta charset="utf-8"><style>html,body{margin:0;padding:0;}body{font-family:system-ui,-apple-system,sans-serif;}</style>` + hostStyle + // Reset flotante: el CSS de la app trae reglas de PÁGINA (body{min-height:100vh},
|
|
1011
|
+
// html{height:100%}, fondo del layout…) que en un iframe embebido lo estirarían a
|
|
1012
|
+
// toda la pantalla (caja negra) en vez de ajustarse al contenido. Neutralizamos
|
|
1013
|
+
// tamaño/fondo/overflow (mantenemos color y fuente heredados de la app).
|
|
1014
|
+
// `color-scheme:normal` es CLAVE para la transparencia: si el CSS de la app
|
|
1015
|
+
// declara `:root{color-scheme:dark}` y no coincide con el del elemento iframe
|
|
1016
|
+
// del host (normal), el navegador pinta el canvas OPACO (negro) en vez de
|
|
1017
|
+
// transparente — igualamos ambos lados para conservar el fondo transparente.
|
|
1018
|
+
"<style>html,body{margin:0!important;padding:0!important;background:transparent!important;height:auto!important;min-height:0!important;max-height:none!important;width:auto!important;min-width:0!important;overflow:visible!important;color-scheme:normal!important;}</style>" + tailwind + // Inline en <body>: gana incluso sobre reglas `!important` de la app (p. ej.
|
|
1019
|
+
// `.dark body{background:#000!important}`) porque el estilo inline tiene la
|
|
1020
|
+
// mayor especificidad dentro de la capa important-author.
|
|
1021
|
+
`<style>${escapeClosing(css, "style")}</style></head><body style="margin:0!important;background:transparent!important;min-height:0!important;height:auto!important;"><div id="__veo_root">${html}</div><script>${bridge}</script>` + (js ? `<script>${escapeClosing(js, "script")}</script>` : "") + "</body></html>"
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
function escapeClosing(code, tag) {
|
|
1025
|
+
const rx = tag === "script" ? /<\/script/gi : /<\/style/gi;
|
|
1026
|
+
return code.replace(rx, `<\\/${tag}`);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/plugins/guides/code-block.ts
|
|
1030
|
+
var RAW_BLOCK_ATTR = "data-veo-raw-block";
|
|
1031
|
+
function insertRawNodes(anchor, nodes, pos) {
|
|
1032
|
+
const parent = anchor.parentNode;
|
|
1033
|
+
if (pos === "prepend") {
|
|
1034
|
+
const first = anchor.firstChild;
|
|
1035
|
+
for (const n of nodes) anchor.insertBefore(n, first);
|
|
1036
|
+
} else if (pos === "append") {
|
|
1037
|
+
for (const n of nodes) anchor.appendChild(n);
|
|
1038
|
+
} else if (pos === "before") {
|
|
1039
|
+
for (const n of nodes) parent?.insertBefore(n, anchor);
|
|
1040
|
+
} else {
|
|
1041
|
+
let ref = anchor;
|
|
1042
|
+
for (const n of nodes) {
|
|
1043
|
+
parent?.insertBefore(n, ref.nextSibling);
|
|
1044
|
+
ref = n;
|
|
741
1045
|
}
|
|
742
1046
|
}
|
|
743
|
-
|
|
744
|
-
|
|
1047
|
+
}
|
|
1048
|
+
var codeSlotSeq = 0;
|
|
1049
|
+
function mountCodeBlock(block, doc, context, host, rawInline) {
|
|
1050
|
+
if (block.sandboxed) {
|
|
1051
|
+
const step = { html: block.html ?? "", css: block.css, js: block.js };
|
|
1052
|
+
const { iframe, cleanup } = mountCustomFrame(doc, step, context, { width: "100%" });
|
|
1053
|
+
const wrapper = doc.createElement("div");
|
|
1054
|
+
wrapper.className = "veo-code-sandbox";
|
|
1055
|
+
wrapper.appendChild(iframe);
|
|
1056
|
+
return { node: wrapper, cleanup };
|
|
1057
|
+
}
|
|
1058
|
+
const hostDoc = doc;
|
|
1059
|
+
const injected = [];
|
|
1060
|
+
if (typeof block.css === "string" && block.css.trim()) {
|
|
1061
|
+
const style = hostDoc.createElement("style");
|
|
1062
|
+
style.textContent = block.css;
|
|
1063
|
+
hostDoc.head.appendChild(style);
|
|
1064
|
+
injected.push(style);
|
|
1065
|
+
}
|
|
1066
|
+
let node = null;
|
|
1067
|
+
if (typeof block.html === "string" && block.html.trim()) {
|
|
1068
|
+
const holder = doc.createElement("div");
|
|
1069
|
+
holder.className = "veo-code-html";
|
|
1070
|
+
holder.innerHTML = block.html;
|
|
1071
|
+
for (const old of Array.from(holder.querySelectorAll("script"))) {
|
|
1072
|
+
injected.push(runScript(old, hostDoc));
|
|
1073
|
+
old.remove();
|
|
1074
|
+
}
|
|
1075
|
+
if (rawInline) {
|
|
1076
|
+
const rawNodes = Array.from(holder.childNodes);
|
|
1077
|
+
if (rawInline.tagBlockId) {
|
|
1078
|
+
for (const n of rawNodes) {
|
|
1079
|
+
if (n instanceof Element) n.setAttribute(RAW_BLOCK_ATTR, rawInline.tagBlockId);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
insertRawNodes(rawInline.anchor, rawNodes, rawInline.pos);
|
|
1083
|
+
injected.push(...rawNodes);
|
|
1084
|
+
} else if (host) {
|
|
1085
|
+
const slotName = `veo-code-${++codeSlotSeq}`;
|
|
1086
|
+
holder.slot = slotName;
|
|
1087
|
+
host.appendChild(holder);
|
|
1088
|
+
injected.push(holder);
|
|
1089
|
+
const slot = doc.createElement("slot");
|
|
1090
|
+
slot.setAttribute("name", slotName);
|
|
1091
|
+
const wrapper = doc.createElement("div");
|
|
1092
|
+
wrapper.className = "veo-code-host";
|
|
1093
|
+
wrapper.appendChild(slot);
|
|
1094
|
+
node = wrapper;
|
|
1095
|
+
} else {
|
|
1096
|
+
node = holder;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
let cancelled = false;
|
|
1100
|
+
if (typeof block.js === "string" && block.js.trim()) {
|
|
1101
|
+
const js = block.js;
|
|
1102
|
+
const run = () => {
|
|
1103
|
+
if (cancelled) return;
|
|
1104
|
+
const s = hostDoc.createElement("script");
|
|
1105
|
+
s.textContent = wrapUserScript(js);
|
|
1106
|
+
hostDoc.body.appendChild(s);
|
|
1107
|
+
injected.push(s);
|
|
1108
|
+
};
|
|
1109
|
+
if (host && !host.isConnected) queueMicrotask(run);
|
|
1110
|
+
else run();
|
|
1111
|
+
}
|
|
1112
|
+
return {
|
|
1113
|
+
node,
|
|
1114
|
+
cleanup: () => {
|
|
1115
|
+
cancelled = true;
|
|
1116
|
+
for (const n of injected) n.parentNode?.removeChild(n);
|
|
1117
|
+
}
|
|
1118
|
+
};
|
|
1119
|
+
}
|
|
1120
|
+
function wrapUserScript(js) {
|
|
1121
|
+
return `(function(){try{
|
|
1122
|
+
${js}
|
|
1123
|
+
}catch(e){console.error("[OnUser] Error en el bloque de c\xF3digo:",e)}})();`;
|
|
1124
|
+
}
|
|
1125
|
+
function runScript(old, hostDoc) {
|
|
1126
|
+
const s = hostDoc.createElement("script");
|
|
1127
|
+
for (const attr of Array.from(old.attributes)) s.setAttribute(attr.name, attr.value);
|
|
1128
|
+
if (!old.src) s.textContent = old.textContent;
|
|
1129
|
+
hostDoc.body.appendChild(s);
|
|
1130
|
+
return s;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// src/plugins/guides/guide-design.ts
|
|
1134
|
+
var DARK_THEME = {
|
|
1135
|
+
"--veo-bg": "#1f2937",
|
|
1136
|
+
"--veo-text": "#f9fafb",
|
|
1137
|
+
"--veo-text-secondary": "#d1d5db",
|
|
1138
|
+
"--veo-shadow": "0 20px 60px rgba(0, 0, 0, 0.55)"
|
|
1139
|
+
};
|
|
1140
|
+
var ALIGN_JUSTIFY = {
|
|
1141
|
+
left: "flex-start",
|
|
1142
|
+
center: "center",
|
|
1143
|
+
right: "flex-end"
|
|
1144
|
+
};
|
|
1145
|
+
var ALIGN_SELF = {
|
|
1146
|
+
left: "flex-start",
|
|
1147
|
+
center: "center",
|
|
1148
|
+
right: "flex-end"
|
|
1149
|
+
};
|
|
1150
|
+
var ELEMENT_ALIGN_MODE = {
|
|
1151
|
+
title: "text",
|
|
1152
|
+
text: "text",
|
|
1153
|
+
image: "self"
|
|
1154
|
+
};
|
|
1155
|
+
var SHADOW = {
|
|
1156
|
+
none: "none",
|
|
1157
|
+
soft: "0 4px 14px rgba(0, 0, 0, 0.10)",
|
|
1158
|
+
medium: "0 8px 30px rgba(0, 0, 0, 0.18)",
|
|
1159
|
+
strong: "0 24px 60px rgba(0, 0, 0, 0.32)"
|
|
1160
|
+
};
|
|
1161
|
+
var PRESET_POS = {
|
|
1162
|
+
center: [0.5, 0.5],
|
|
1163
|
+
top: [0.5, 0],
|
|
1164
|
+
bottom: [0.5, 1],
|
|
1165
|
+
left: [0, 0.5],
|
|
1166
|
+
right: [1, 0.5],
|
|
1167
|
+
"top-left": [0, 0],
|
|
1168
|
+
"top-right": [1, 0],
|
|
1169
|
+
"bottom-left": [0, 1],
|
|
1170
|
+
"bottom-right": [1, 1]
|
|
1171
|
+
};
|
|
1172
|
+
var COLOR_RE = /^#[0-9a-f]{3,8}$|^(rgb|rgba|hsl|hsla)\([\d.,%/\sdeg]+\)$|^[a-z]{3,20}$/i;
|
|
1173
|
+
function clamp(n, min, max) {
|
|
1174
|
+
return Math.min(max, Math.max(min, n));
|
|
1175
|
+
}
|
|
1176
|
+
function applyDesignVars(host, style) {
|
|
1177
|
+
if (!style || typeof style !== "object") return;
|
|
1178
|
+
const s = style;
|
|
1179
|
+
if (typeof s.accentColor === "string" && COLOR_RE.test(s.accentColor.trim())) {
|
|
1180
|
+
host.style.setProperty("--veo-primary", s.accentColor.trim());
|
|
1181
|
+
}
|
|
1182
|
+
if (typeof s.fontFamily === "string" && s.fontFamily.length <= 200 && !/[<>{}]/.test(s.fontFamily)) {
|
|
1183
|
+
host.style.setProperty("--veo-font", s.fontFamily.trim());
|
|
1184
|
+
}
|
|
1185
|
+
if (s.theme === "dark") {
|
|
1186
|
+
for (const [key, value] of Object.entries(DARK_THEME)) {
|
|
1187
|
+
host.style.setProperty(key, value);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (typeof s.background === "string" && COLOR_RE.test(s.background.trim())) {
|
|
1191
|
+
host.style.setProperty("--veo-bg", s.background.trim());
|
|
745
1192
|
}
|
|
746
1193
|
if (typeof s.textColor === "string" && COLOR_RE.test(s.textColor.trim())) {
|
|
747
1194
|
host.style.setProperty("--veo-text", s.textColor.trim());
|
|
@@ -1024,7 +1471,161 @@ var GUIDE_STYLES = `
|
|
|
1024
1471
|
}
|
|
1025
1472
|
/* Card gen\xE9rica del modelo unificado. El dise\xF1o (bg/borde/padding/\u2026) lo aplica
|
|
1026
1473
|
el renderer como estilos inline desde step.design; ac\xE1 van los defaults. */
|
|
1474
|
+
/* Edici\xF3n (builder): sin animaci\xF3n de entrada al repintar la preview. */
|
|
1475
|
+
:host(.veo-no-anim) * { animation: none !important; }
|
|
1476
|
+
|
|
1477
|
+
/* Bloque video: caja con relaci\xF3n de aspecto; el iframe la llena. */
|
|
1478
|
+
.veo-guide-video { position: relative; overflow: hidden; border-radius: 8px; background: #000; }
|
|
1479
|
+
.veo-guide-video iframe, .veo-guide-video video {
|
|
1480
|
+
position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
/* \u2500\u2500 Layout por filas/columnas (composite) \u2500\u2500
|
|
1484
|
+
El espacio tiene UNA fuente por capa: el aire con el borde lo da el padding
|
|
1485
|
+
de la card; la separaci\xF3n entre filas/bloques la da el margen inferior
|
|
1486
|
+
(12px, ah\xED vive la l\xEDnea \xAB+\xBB del editor); los bloques NO llevan padding
|
|
1487
|
+
propio (solo el que el autor les ponga inline). El \xFAltimo elemento no deja
|
|
1488
|
+
margen colgando. */
|
|
1489
|
+
.veo-card > [data-veo-block], .veo-card > .veo-row, .veo-card > .veo-guide-actions,
|
|
1490
|
+
.veo-col > [data-veo-block], .veo-col > .veo-guide-actions { margin-bottom: 12px; }
|
|
1491
|
+
/* La card y cada columna son columnas flex: cada bloque mide SU contenido (como
|
|
1492
|
+
el bot\xF3n) y se ubica con su alineaci\xF3n (align-self); filas, columnas, formularios
|
|
1493
|
+
y filas de botones ocupan todo el ancho. */
|
|
1494
|
+
.veo-card:not(.veo-card--empty), .veo-col {
|
|
1495
|
+
display: flex; flex-direction: column; align-items: flex-start;
|
|
1496
|
+
}
|
|
1497
|
+
.veo-card > [data-veo-block], .veo-col > [data-veo-block] { max-width: 100%; }
|
|
1498
|
+
/* L\xEDnea horizontal: ocupa todo el ancho; el padding del bloque es solo vertical
|
|
1499
|
+
(zona clickeable en el editor) y la l\xEDnea es el borde superior del hr. */
|
|
1500
|
+
.veo-card > .veo-guide-divider, .veo-col > .veo-guide-divider { padding: 6px 0; align-self: stretch; }
|
|
1501
|
+
.veo-guide-divider { box-sizing: border-box; }
|
|
1502
|
+
/* Bloque de c\xF3digo (modo host): ocupa todo el ancho, como un formulario. */
|
|
1503
|
+
.veo-code-host { display: block; min-width: 0; }
|
|
1504
|
+
.veo-card > .veo-code-host, .veo-col > .veo-code-host { align-self: stretch; }
|
|
1505
|
+
.veo-guide-divider hr { margin: 0; border: 0; height: 0; }
|
|
1506
|
+
.veo-card > .veo-row, .veo-card > .veo-guide-actions, .veo-card > .veo-form,
|
|
1507
|
+
.veo-col > .veo-guide-actions, .veo-col > .veo-form { align-self: stretch; }
|
|
1508
|
+
.veo-row { display: flex; gap: 12px; align-items: flex-start; box-sizing: border-box; }
|
|
1509
|
+
.veo-col { flex: 1 1 0; min-width: 0; }
|
|
1510
|
+
.veo-col > :last-child { margin-bottom: 0; }
|
|
1511
|
+
/* Fila con alineaci\xF3n horizontal (editor de fila): las columnas miden su contenido. */
|
|
1512
|
+
.veo-row--aligned > .veo-col { flex: 0 1 auto; }
|
|
1513
|
+
|
|
1514
|
+
/* \u2500\u2500 Editor in-app: bloque seleccionado + barra de acciones + placeholders \u2500\u2500 */
|
|
1515
|
+
.veo-block-placeholder {
|
|
1516
|
+
border: 1px dashed #b0a9a5; border-radius: 6px; padding: 12px; margin-bottom: 12px;
|
|
1517
|
+
color: #8a8582; font-size: 13px; text-align: center;
|
|
1518
|
+
}
|
|
1519
|
+
/* El bloque seleccionado no lleva contorno propio: lo rodea .veo-block-frame
|
|
1520
|
+
(marco aparte, con aire y alto m\xEDnimo, que no altera el layout). */
|
|
1521
|
+
.veo-block--selected { position: relative; z-index: 1; }
|
|
1522
|
+
.veo-block-frame {
|
|
1523
|
+
position: absolute; z-index: 4; pointer-events: none; box-sizing: border-box;
|
|
1524
|
+
border: 2px solid #3b82f6; border-radius: 10px; background: rgba(59, 130, 246, 0.04);
|
|
1525
|
+
}
|
|
1526
|
+
/* Barra del bloque (mock): p\xEDldora blanca con sombra, solo \xEDconos; la papelera
|
|
1527
|
+
en rojo, separada por una l\xEDnea. */
|
|
1528
|
+
.veo-block-toolbar {
|
|
1529
|
+
position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
|
|
1530
|
+
background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
|
|
1531
|
+
box-shadow: 0 6px 20px rgba(0,0,0,0.16);
|
|
1532
|
+
}
|
|
1533
|
+
.veo-block-toolbar button {
|
|
1534
|
+
border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px; border-radius: 8px;
|
|
1535
|
+
display: inline-flex; align-items: center; justify-content: center; line-height: 1;
|
|
1536
|
+
}
|
|
1537
|
+
.veo-block-toolbar button:hover { background: #f5efeb; }
|
|
1538
|
+
.veo-block-toolbar button svg { width: 20px; height: 20px; }
|
|
1539
|
+
.veo-block-toolbar .veo-block-toolbar-remove { color: #e5484d; }
|
|
1540
|
+
/* Separadores: antes de la papelera y antes de las flechas de mover. */
|
|
1541
|
+
.veo-block-toolbar .veo-block-toolbar-remove, .veo-block-toolbar .veo-block-toolbar-move-left {
|
|
1542
|
+
margin-left: 6px; position: relative;
|
|
1543
|
+
}
|
|
1544
|
+
.veo-block-toolbar .veo-block-toolbar-remove::before, .veo-block-toolbar .veo-block-toolbar-move-left::before {
|
|
1545
|
+
content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
|
|
1546
|
+
}
|
|
1547
|
+
.veo-block-toolbar button:disabled { opacity: 0.3; cursor: default; background: transparent; }
|
|
1548
|
+
/* Contenedor (hover): contorno azul de la tarjeta y dos pesta\xF1as centradas
|
|
1549
|
+
sobre su borde superior: \xABEditar contenedor\xBB (outline) y \xABEditar filas\xBB. */
|
|
1550
|
+
.veo-card-frame {
|
|
1551
|
+
position: absolute; z-index: 3; inset: 0; pointer-events: none; box-sizing: border-box;
|
|
1552
|
+
border: 2px solid #3b82f6; border-radius: inherit;
|
|
1553
|
+
}
|
|
1554
|
+
.veo-card-toolbar {
|
|
1555
|
+
position: absolute; z-index: 6; left: 50%; transform: translateX(-50%);
|
|
1556
|
+
display: inline-flex; align-items: flex-end; gap: 8px; white-space: nowrap;
|
|
1557
|
+
}
|
|
1558
|
+
.veo-card-toolbar button {
|
|
1559
|
+
cursor: pointer; padding: 8px 14px; border-radius: 8px; border: 2px solid #3b82f6;
|
|
1560
|
+
font: 600 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
1561
|
+
background: #fff; color: #3b82f6;
|
|
1562
|
+
}
|
|
1563
|
+
.veo-card-toolbar button:hover { background: #eff6ff; }
|
|
1564
|
+
/* Con fondo = la vista que el panel muestra ahora (contenedor o estructura). */
|
|
1565
|
+
.veo-card-toolbar button.is-active { background: #3b82f6; color: #fff; }
|
|
1566
|
+
.veo-card-toolbar button.is-active:hover { background: #2563eb; border-color: #2563eb; }
|
|
1567
|
+
/* Editor de fila: marco al pasar el mouse por una fila (o la fila del bloque
|
|
1568
|
+
seleccionado) y barra \xABEditar fila \xB7 duplicar\xBB bajo su esquina derecha. */
|
|
1569
|
+
.veo-row-frame {
|
|
1570
|
+
position: absolute; z-index: 3; pointer-events: none; box-sizing: border-box;
|
|
1571
|
+
border: 2px solid #3b82f6; border-radius: 10px;
|
|
1572
|
+
}
|
|
1573
|
+
.veo-row-toolbar {
|
|
1574
|
+
position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
|
|
1575
|
+
background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
|
|
1576
|
+
box-shadow: 0 6px 20px rgba(0,0,0,0.16); white-space: nowrap;
|
|
1577
|
+
}
|
|
1578
|
+
.veo-row-toolbar button {
|
|
1579
|
+
border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px 8px; border-radius: 8px;
|
|
1580
|
+
display: inline-flex; align-items: center; gap: 8px; line-height: 1;
|
|
1581
|
+
font: 500 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
1582
|
+
}
|
|
1583
|
+
.veo-row-toolbar button:hover { background: #f5efeb; }
|
|
1584
|
+
.veo-row-toolbar button svg { width: 20px; height: 20px; }
|
|
1585
|
+
.veo-row-toolbar .veo-row-toolbar-duplicate { margin-left: 6px; position: relative; }
|
|
1586
|
+
.veo-row-toolbar .veo-row-toolbar-duplicate::before {
|
|
1587
|
+
content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
|
|
1588
|
+
}
|
|
1589
|
+
.veo-row-toolbar .veo-row-toolbar-remove { color: #e5484d; }
|
|
1590
|
+
/* Huecos \xAB+\xBB (mock): zona invisible entre elementos; al pasar el mouse aparece
|
|
1591
|
+
la l\xEDnea azul (horizontal = fila / bloque apilado, vertical = columna) con
|
|
1592
|
+
un \xAB+\xBB circular al centro. */
|
|
1593
|
+
.veo-gap { position: absolute; z-index: 4; }
|
|
1594
|
+
.veo-gap i { position: absolute; background: #3b82f6; opacity: 0; transition: opacity 100ms; pointer-events: none; }
|
|
1595
|
+
.veo-gap--h i { left: 0; right: 0; top: 50%; height: 2px; margin-top: -1px; }
|
|
1596
|
+
.veo-gap--v i { top: 0; bottom: 0; left: 50%; width: 2px; margin-left: -1px; }
|
|
1597
|
+
.veo-gap button {
|
|
1598
|
+
position: absolute; left: 50%; top: 50%; margin: -11px 0 0 -11px; box-sizing: border-box;
|
|
1599
|
+
opacity: 0; transition: opacity 100ms; width: 22px; height: 22px; border-radius: 50%; padding: 0;
|
|
1600
|
+
border: 2px solid #fff; background: #3b82f6; color: #fff; cursor: pointer;
|
|
1601
|
+
font: 600 15px/18px system-ui, -apple-system, "Segoe UI", sans-serif; text-align: center;
|
|
1602
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
|
1603
|
+
}
|
|
1604
|
+
.veo-gap:hover i, .veo-gap:hover button, .veo-gap:focus-within button { opacity: 1; }
|
|
1605
|
+
.veo-gap button:hover { background: #2563eb; }
|
|
1606
|
+
|
|
1607
|
+
/* Tarjeta sin bloques (paso reci\xE9n creado). La tarjeta NO impone tama\xF1o ni
|
|
1608
|
+
padding propios (as\xED el ancho/padding del dise\xF1o se ven tal cual); el tama\xF1o
|
|
1609
|
+
m\xEDnimo lo pone la zona punteada azul de la preview. */
|
|
1610
|
+
.veo-card--empty { display: flex; align-items: stretch; }
|
|
1611
|
+
.veo-card-empty-zone {
|
|
1612
|
+
flex: 1; min-width: 0; min-height: 160px; margin-top: 14px;
|
|
1613
|
+
display: flex; align-items: center; justify-content: center; gap: 6px; text-align: center;
|
|
1614
|
+
border: 2px dashed #3b82f6; border-radius: 8px; background: #eff6ff;
|
|
1615
|
+
color: #2563eb; font-size: 16px; font-weight: 500; user-select: none; pointer-events: none;
|
|
1616
|
+
padding: 0 12px; box-sizing: border-box; overflow: hidden;
|
|
1617
|
+
}
|
|
1618
|
+
.veo-card-empty-zone b { font-weight: 600; font-size: 22px; line-height: 1; }
|
|
1619
|
+
/* Sin ancho configurado, la zona da un tama\xF1o c\xF3modo por defecto. */
|
|
1620
|
+
.veo-card--empty:not([style*="width"]) .veo-card-empty-zone { min-width: 360px; }
|
|
1621
|
+
/* Tarjeta angosta (la mide el renderer): solo el \xAB+\xBB, sin texto, y m\xE1s baja. */
|
|
1622
|
+
.veo-card--narrow .veo-card-empty-zone span { display: none; }
|
|
1623
|
+
.veo-card--narrow .veo-card-empty-zone { min-height: 120px; }
|
|
1027
1624
|
.veo-card {
|
|
1625
|
+
/* Contenedor posicionado SIEMPRE: el \u2715 (absolute) se ubica respecto a la
|
|
1626
|
+
tarjeta tambi\xE9n en pasos embebidos (inline), no respecto a la app. Los
|
|
1627
|
+
flotantes lo pisan con position:fixed inline. */
|
|
1628
|
+
position: relative;
|
|
1028
1629
|
pointer-events: auto;
|
|
1029
1630
|
background: var(--veo-bg, #ffffff);
|
|
1030
1631
|
color: var(--veo-text, #1f2937);
|
|
@@ -1117,6 +1718,13 @@ var GUIDE_STYLES = `
|
|
|
1117
1718
|
transition: opacity 120ms ease;
|
|
1118
1719
|
}
|
|
1119
1720
|
.veo-guide-cta:hover { opacity: 0.9; }
|
|
1721
|
+
/* Presets del boton (editor): secundario = contorno; enlace = solo texto. */
|
|
1722
|
+
.veo-guide-cta--secondary {
|
|
1723
|
+
background: transparent; color: var(--veo-primary);
|
|
1724
|
+
border: 1px solid var(--veo-primary);
|
|
1725
|
+
}
|
|
1726
|
+
.veo-guide-cta--link { background: transparent; color: var(--veo-primary); padding: 0; }
|
|
1727
|
+
.veo-guide-cta--link:hover { opacity: 1; text-decoration: underline; }
|
|
1120
1728
|
.veo-guide-cta:active { transform: translateY(1px); }
|
|
1121
1729
|
.veo-guide-close {
|
|
1122
1730
|
position: absolute; top: -6px; right: -6px;
|
|
@@ -1147,6 +1755,10 @@ var GUIDE_STYLES = `
|
|
|
1147
1755
|
|
|
1148
1756
|
.veo-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
|
|
1149
1757
|
.veo-form-field { display: flex; flex-direction: column; gap: 6px; }
|
|
1758
|
+
/* Pregunta de la encuesta (rich text del bloque form). */
|
|
1759
|
+
.veo-form-question { font-size: 16px; font-weight: 600; line-height: 1.35; color: var(--veo-text); }
|
|
1760
|
+
.veo-form-question p { margin: 0; }
|
|
1761
|
+
.veo-form > .veo-guide-actions { margin: 0; }
|
|
1150
1762
|
.veo-form-label { font-size: 13px; font-weight: 500; color: var(--veo-text); }
|
|
1151
1763
|
.veo-form-input {
|
|
1152
1764
|
font-size: 14px; color: var(--veo-text);
|
|
@@ -1155,26 +1767,71 @@ var GUIDE_STYLES = `
|
|
|
1155
1767
|
padding: 8px 10px; width: 100%;
|
|
1156
1768
|
}
|
|
1157
1769
|
.veo-form-input:focus { outline: 2px solid var(--veo-primary); outline-offset: -1px; border-color: transparent; }
|
|
1158
|
-
|
|
1770
|
+
/* Lista de tareas (mock): \xEDtems con anillo de progreso, texto + subt\xEDtulo,
|
|
1771
|
+
y al pie \xABX de N completadas\xBB + porcentaje + barra. */
|
|
1772
|
+
.veo-checklist {
|
|
1773
|
+
display: flex; flex-direction: column; gap: 22px; align-self: stretch; width: 100%;
|
|
1774
|
+
min-width: 360px; box-sizing: border-box;
|
|
1775
|
+
}
|
|
1776
|
+
.veo-checklist-title { font-size: 24px; font-weight: 700; line-height: 1.25; color: var(--veo-cl-title); }
|
|
1777
|
+
.veo-checklist-title p { margin: 0; }
|
|
1778
|
+
.veo-checklist-items { display: flex; flex-direction: column; gap: 22px; }
|
|
1779
|
+
.veo-checklist-item {
|
|
1780
|
+
display: flex; align-items: center; gap: 18px; width: 100%; text-align: left;
|
|
1781
|
+
background: none; border: 0; padding: 0; cursor: pointer; font: inherit; color: inherit;
|
|
1782
|
+
}
|
|
1783
|
+
.veo-checklist-item:hover .veo-checklist-label { text-decoration: underline; }
|
|
1784
|
+
.veo-checklist-ring { width: 44px; height: 44px; flex: 0 0 auto; }
|
|
1785
|
+
.veo-checklist-texts { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
1786
|
+
.veo-checklist-label {
|
|
1787
|
+
font-size: calc(var(--veo-cl-font-size, 14px) + 4px); font-weight: var(--veo-cl-font-weight, 600);
|
|
1788
|
+
line-height: 1.3; color: var(--veo-cl-title);
|
|
1789
|
+
}
|
|
1790
|
+
.veo-checklist-sub { font-size: calc(var(--veo-cl-font-size, 14px) + 1px); color: var(--veo-cl-subtitle); }
|
|
1791
|
+
.veo-checklist-footer { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
|
|
1792
|
+
.veo-checklist-summary {
|
|
1793
|
+
display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
|
|
1794
|
+
font-size: calc(var(--veo-cl-font-size, 14px) + 1px);
|
|
1795
|
+
}
|
|
1796
|
+
.veo-checklist-count { color: var(--veo-cl-subtitle); }
|
|
1797
|
+
.veo-checklist-percent { font-weight: 700; font-size: calc(var(--veo-cl-font-size, 14px) + 3px); color: var(--veo-cl-bar); }
|
|
1798
|
+
.veo-checklist-bar { height: var(--veo-cl-bar-h, 8px); background: #e7e2de; border-radius: 999px; overflow: hidden; }
|
|
1799
|
+
.veo-checklist--dark .veo-checklist-bar { background: #3c3734; }
|
|
1800
|
+
.veo-checklist-bar-fill { height: 100%; background: var(--veo-cl-bar); border-radius: 999px; transition: width 300ms ease; }
|
|
1801
|
+
.veo-checklist-empty { margin: 0; font-size: 13px; color: var(--veo-cl-subtitle); }
|
|
1802
|
+
/* Opci\xF3n m\xFAltiple (mock): c\xEDrculos/casillas grandes con su texto al lado. */
|
|
1803
|
+
.veo-form-options { display: flex; flex-direction: column; gap: 14px; }
|
|
1159
1804
|
.veo-form-option {
|
|
1160
|
-
display: flex; align-items: center; gap:
|
|
1161
|
-
font-size:
|
|
1162
|
-
}
|
|
1163
|
-
.veo-form-option input {
|
|
1164
|
-
|
|
1805
|
+
display: flex; align-items: center; gap: 14px;
|
|
1806
|
+
font-size: 16px; color: var(--veo-text); cursor: pointer;
|
|
1807
|
+
}
|
|
1808
|
+
.veo-form-option input {
|
|
1809
|
+
accent-color: var(--veo-primary); margin: 0; cursor: pointer;
|
|
1810
|
+
width: 26px; height: 26px; flex: 0 0 auto;
|
|
1811
|
+
}
|
|
1812
|
+
/* Escala num\xE9rica (mock): casillas cuadradas; con etiquetas, cada casilla
|
|
1813
|
+
lleva la suya debajo (la primera cuelga a la izquierda, la \xFAltima a la derecha). */
|
|
1814
|
+
.veo-form-scale { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-start; }
|
|
1815
|
+
.veo-scale-item { display: inline-flex; flex-direction: column; align-items: center; gap: 8px; }
|
|
1816
|
+
.veo-scale-item--first { align-items: flex-start; }
|
|
1817
|
+
.veo-scale-item--last { align-items: flex-end; }
|
|
1818
|
+
.veo-scale-label { font-size: 12px; line-height: 1.2; color: #8a8582; white-space: nowrap; }
|
|
1165
1819
|
.veo-nps-btn {
|
|
1166
|
-
min-width:
|
|
1167
|
-
font-size:
|
|
1168
|
-
background: var(--veo-bg); border: 1px solid #
|
|
1820
|
+
min-width: var(--veo-scale-size, 44px); height: var(--veo-scale-size, 44px); padding: 0 6px;
|
|
1821
|
+
font-size: 16px; font-weight: 600; color: var(--veo-text);
|
|
1822
|
+
background: var(--veo-bg); border: 1px solid #d5cec9; border-radius: 8px;
|
|
1169
1823
|
cursor: pointer; transition: all 100ms ease;
|
|
1170
1824
|
}
|
|
1171
|
-
.veo-nps-btn:hover { border-color: var(--veo-primary); }
|
|
1825
|
+
.veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-primary)); }
|
|
1172
1826
|
.veo-rating-btn {
|
|
1173
1827
|
background: none; border: none; padding: 0 2px;
|
|
1174
1828
|
font-size: 26px; line-height: 1; color: #d1d5db;
|
|
1175
1829
|
cursor: pointer; transition: color 100ms ease;
|
|
1176
1830
|
}
|
|
1177
|
-
.veo-scale-active.veo-nps-btn {
|
|
1831
|
+
.veo-scale-active.veo-nps-btn {
|
|
1832
|
+
background: var(--veo-scale-color, var(--veo-primary)); color: #fff;
|
|
1833
|
+
border-color: var(--veo-scale-color, var(--veo-primary));
|
|
1834
|
+
}
|
|
1178
1835
|
.veo-scale-active.veo-rating-btn { color: #f59e0b; }
|
|
1179
1836
|
.veo-form-error { font-size: 13px; color: #dc2626; margin: 0; }
|
|
1180
1837
|
.veo-form-thanks {
|
|
@@ -1676,408 +2333,375 @@ function buildBadgeElement(config, doc) {
|
|
|
1676
2333
|
if (config.color) btn.style.color = config.color;
|
|
1677
2334
|
break;
|
|
1678
2335
|
}
|
|
1679
|
-
case "image": {
|
|
1680
|
-
if (config.imageUrl && isSafeUrl(config.imageUrl)) {
|
|
1681
|
-
const img = doc.createElement("img");
|
|
1682
|
-
img.src = config.imageUrl;
|
|
1683
|
-
img.alt = "";
|
|
1684
|
-
img.style.width = `${size}px`;
|
|
1685
|
-
img.style.height = `${size}px`;
|
|
1686
|
-
btn.appendChild(img);
|
|
1687
|
-
} else {
|
|
1688
|
-
btn.className = "veo-badge veo-badge--icon";
|
|
1689
|
-
btn.textContent = "?";
|
|
1690
|
-
btn.style.width = `${size}px`;
|
|
1691
|
-
btn.style.height = `${size}px`;
|
|
1692
|
-
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1693
|
-
}
|
|
1694
|
-
break;
|
|
1695
|
-
}
|
|
1696
|
-
default: {
|
|
1697
|
-
btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
|
|
1698
|
-
btn.style.width = `${size}px`;
|
|
1699
|
-
btn.style.height = `${size}px`;
|
|
1700
|
-
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1701
|
-
if (config.color) btn.style.color = config.color;
|
|
1702
|
-
break;
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
return btn;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
// src/plugins/guides/walkthrough-block-builder.ts
|
|
1709
|
-
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
1710
|
-
const container = doc.createElement("div");
|
|
1711
|
-
container.className = "veo-guide-content";
|
|
1712
|
-
const counter = doc.createElement("div");
|
|
1713
|
-
counter.className = "veo-walkthrough-counter";
|
|
1714
|
-
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
1715
|
-
container.appendChild(counter);
|
|
1716
|
-
const progress = doc.createElement("div");
|
|
1717
|
-
progress.className = "veo-walkthrough-progress";
|
|
1718
|
-
for (let i = 0; i < totalSteps; i++) {
|
|
1719
|
-
const dot = doc.createElement("span");
|
|
1720
|
-
dot.className = "veo-walkthrough-progress-dot";
|
|
1721
|
-
if (i < stepIndex) dot.classList.add("completed");
|
|
1722
|
-
if (i === stepIndex) dot.classList.add("active");
|
|
1723
|
-
progress.appendChild(dot);
|
|
1724
|
-
}
|
|
1725
|
-
container.appendChild(progress);
|
|
1726
|
-
for (const node of buildContentNodes(step, doc)) {
|
|
1727
|
-
container.appendChild(node);
|
|
1728
|
-
}
|
|
1729
|
-
const actions = doc.createElement("div");
|
|
1730
|
-
actions.className = "veo-walkthrough-actions";
|
|
1731
|
-
const skipBtn = doc.createElement("button");
|
|
1732
|
-
skipBtn.type = "button";
|
|
1733
|
-
skipBtn.className = "veo-walkthrough-skip";
|
|
1734
|
-
skipBtn.textContent = "Omitir";
|
|
1735
|
-
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
1736
|
-
actions.appendChild(skipBtn);
|
|
1737
|
-
const rightGroup = doc.createElement("div");
|
|
1738
|
-
rightGroup.className = "veo-walkthrough-actions-right";
|
|
1739
|
-
if (stepIndex > 0) {
|
|
1740
|
-
const backBtn = doc.createElement("button");
|
|
1741
|
-
backBtn.type = "button";
|
|
1742
|
-
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
1743
|
-
backBtn.textContent = "Atr\xE1s";
|
|
1744
|
-
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
1745
|
-
rightGroup.appendChild(backBtn);
|
|
1746
|
-
}
|
|
1747
|
-
const isLastStep = stepIndex === totalSteps - 1;
|
|
1748
|
-
const primaryBtn = doc.createElement("button");
|
|
1749
|
-
primaryBtn.type = "button";
|
|
1750
|
-
primaryBtn.className = "veo-guide-cta";
|
|
1751
|
-
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
1752
|
-
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
1753
|
-
primaryBtn.addEventListener("click", () => {
|
|
1754
|
-
if (isLastStep) callbacks.onComplete();
|
|
1755
|
-
else callbacks.onNext();
|
|
1756
|
-
});
|
|
1757
|
-
rightGroup.appendChild(primaryBtn);
|
|
1758
|
-
actions.appendChild(rightGroup);
|
|
1759
|
-
container.appendChild(actions);
|
|
1760
|
-
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
1761
|
-
return container;
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
|
-
// src/plugins/guides/renderers/banner-renderer.ts
|
|
1765
|
-
var BannerRenderer = class extends BaseRenderer {
|
|
1766
|
-
async render(context) {
|
|
1767
|
-
const idx = context.nav?.stepIndex ?? 0;
|
|
1768
|
-
const step = context.guide.guideSteps[idx];
|
|
1769
|
-
if (!step) return;
|
|
1770
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1771
|
-
const selector = step.selector?.trim() || null;
|
|
1772
|
-
let anchor = null;
|
|
1773
|
-
if (selector) {
|
|
1774
|
-
anchor = await waitForElement(selector);
|
|
1775
|
-
if (!anchor) return;
|
|
1776
|
-
}
|
|
1777
|
-
const { host, root } = this.createHost();
|
|
1778
|
-
this.applyDesign(step.style);
|
|
1779
|
-
const ownerDocument = root.ownerDocument ?? document;
|
|
1780
|
-
const banner = ownerDocument.createElement("div");
|
|
1781
|
-
banner.className = bannerClassOf(position, Boolean(anchor));
|
|
1782
|
-
const dismiss = (action, ctaUrl, meta) => {
|
|
1783
|
-
context.onInteraction({
|
|
1784
|
-
guideId: context.guide.guideId,
|
|
1785
|
-
stepIndex: idx,
|
|
1786
|
-
action,
|
|
1787
|
-
...meta ? { metadata: meta } : {}
|
|
1788
|
-
});
|
|
1789
|
-
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1790
|
-
context.onClose();
|
|
1791
|
-
};
|
|
1792
|
-
const content = context.nav ? buildWalkthroughStepContent(
|
|
1793
|
-
step,
|
|
1794
|
-
context.nav.stepIndex,
|
|
1795
|
-
context.nav.totalSteps,
|
|
1796
|
-
ownerDocument,
|
|
1797
|
-
context.nav.callbacks
|
|
1798
|
-
) : buildStepContent(step, ownerDocument, {
|
|
1799
|
-
onCtaClick: (action, target, meta) => {
|
|
1800
|
-
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
1801
|
-
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
1802
|
-
},
|
|
1803
|
-
onDismiss: () => dismiss("dismissed")
|
|
1804
|
-
});
|
|
1805
|
-
banner.appendChild(content);
|
|
1806
|
-
root.appendChild(banner);
|
|
1807
|
-
if (selector && anchor) {
|
|
1808
|
-
host.style.display = "block";
|
|
1809
|
-
const insertAt = position === "top" ? "prepend" : "append";
|
|
1810
|
-
insertHost(anchor, host, insertAt);
|
|
1811
|
-
this.registerCleanup(keepHostAttached(host, selector, insertAt));
|
|
1812
|
-
}
|
|
1813
|
-
if (!context.nav) {
|
|
1814
|
-
this.liveContainer = banner;
|
|
1815
|
-
this.liveContent = content;
|
|
1816
|
-
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1817
|
-
onCtaClick: (action, target, meta) => {
|
|
1818
|
-
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
1819
|
-
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
1820
|
-
},
|
|
1821
|
-
onDismiss: () => dismiss("dismissed")
|
|
1822
|
-
});
|
|
1823
|
-
this.liveKey = this.liveKeyOf(step);
|
|
1824
|
-
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
/** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
|
|
1828
|
-
liveKeyOf(step) {
|
|
1829
|
-
const selector = step.selector?.trim() ?? "";
|
|
1830
|
-
if (!selector) return "";
|
|
1831
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1832
|
-
return `${selector}|${position}`;
|
|
1833
|
-
}
|
|
1834
|
-
onLiveUpdate(step) {
|
|
1835
|
-
if (this.liveContainer) {
|
|
1836
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1837
|
-
const embedded = Boolean(step.selector?.trim());
|
|
1838
|
-
this.liveContainer.className = bannerClassOf(position, embedded);
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
};
|
|
1842
|
-
function bannerClassOf(position, embedded) {
|
|
1843
|
-
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
// src/utils/uuid.ts
|
|
1847
|
-
function uuidv7() {
|
|
1848
|
-
const timestamp = Date.now();
|
|
1849
|
-
const timeHex = timestamp.toString(16).padStart(12, "0");
|
|
1850
|
-
const random = new Uint8Array(10);
|
|
1851
|
-
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1852
|
-
crypto.getRandomValues(random);
|
|
1853
|
-
} else {
|
|
1854
|
-
for (let i = 0; i < random.length; i++) {
|
|
1855
|
-
random[i] = Math.floor(Math.random() * 256);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
random[0] = (random[0] ?? 0) & 15 | 112;
|
|
1859
|
-
random[2] = (random[2] ?? 0) & 63 | 128;
|
|
1860
|
-
const hex = Array.from(random, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1861
|
-
return [
|
|
1862
|
-
timeHex.slice(0, 8),
|
|
1863
|
-
timeHex.slice(8, 12),
|
|
1864
|
-
hex.slice(0, 4),
|
|
1865
|
-
hex.slice(4, 8),
|
|
1866
|
-
hex.slice(8, 20)
|
|
1867
|
-
].join("-");
|
|
1868
|
-
}
|
|
1869
|
-
|
|
1870
|
-
// src/plugins/guides/renderers/custom-frame.ts
|
|
1871
|
-
function mountCustomFrame(doc, step, context, opts) {
|
|
1872
|
-
const iframe = doc.createElement("iframe");
|
|
1873
|
-
iframe.className = "veo-custom-frame";
|
|
1874
|
-
iframe.setAttribute("sandbox", "allow-scripts");
|
|
1875
|
-
iframe.setAttribute("title", context.guide.guideName || "OnUser");
|
|
1876
|
-
iframe.style.width = opts.width;
|
|
1877
|
-
const hasFixedHeight = opts.fixedHeight !== void 0 && opts.fixedHeight !== null;
|
|
1878
|
-
if (hasFixedHeight) iframe.style.height = toCssSize(opts.fixedHeight);
|
|
1879
|
-
const token = uuidv7();
|
|
1880
|
-
const wantsTailwind = step.style?.tailwind === true;
|
|
1881
|
-
iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token, {
|
|
1882
|
-
hostCss: collectHostStyles(),
|
|
1883
|
-
htmlAttrs: hostHtmlAttrs(),
|
|
1884
|
-
tailwind: wantsTailwind
|
|
1885
|
-
});
|
|
1886
|
-
const close = () => context.onClose();
|
|
1887
|
-
const onMessage = (event) => {
|
|
1888
|
-
if (event.source !== iframe.contentWindow) return;
|
|
1889
|
-
const data = event.data;
|
|
1890
|
-
if (!data || typeof data !== "object" || data.__veo !== token) return;
|
|
1891
|
-
switch (data.type) {
|
|
1892
|
-
case "dismiss":
|
|
1893
|
-
context.onInteraction({
|
|
1894
|
-
guideId: context.guide.guideId,
|
|
1895
|
-
stepIndex: 0,
|
|
1896
|
-
action: "dismissed"
|
|
1897
|
-
});
|
|
1898
|
-
close();
|
|
1899
|
-
break;
|
|
1900
|
-
case "cta":
|
|
1901
|
-
context.onInteraction({
|
|
1902
|
-
guideId: context.guide.guideId,
|
|
1903
|
-
stepIndex: 0,
|
|
1904
|
-
action: "cta_clicked"
|
|
1905
|
-
});
|
|
1906
|
-
if (typeof data.url === "string" && isSafeUrl(data.url)) {
|
|
1907
|
-
window.open(data.url, "_blank", "noopener,noreferrer");
|
|
1908
|
-
}
|
|
1909
|
-
if (data.close !== false) close();
|
|
1910
|
-
break;
|
|
1911
|
-
case "navigate":
|
|
1912
|
-
if (typeof data.url === "string" && isSafeUrl(data.url)) {
|
|
1913
|
-
window.location.assign(data.url);
|
|
1914
|
-
}
|
|
1915
|
-
break;
|
|
1916
|
-
case "track":
|
|
1917
|
-
if (typeof data.name === "string" && context.onTrack) {
|
|
1918
|
-
context.onTrack(data.name, isRecord(data.props) ? data.props : void 0);
|
|
1919
|
-
}
|
|
1920
|
-
break;
|
|
1921
|
-
case "resize":
|
|
1922
|
-
if (!hasFixedHeight && typeof data.height === "number" && data.height > 0) {
|
|
1923
|
-
iframe.style.height = `${Math.ceil(data.height)}px`;
|
|
1924
|
-
}
|
|
1925
|
-
break;
|
|
1926
|
-
}
|
|
1927
|
-
};
|
|
1928
|
-
window.addEventListener("message", onMessage);
|
|
1929
|
-
return { iframe, cleanup: () => window.removeEventListener("message", onMessage) };
|
|
1930
|
-
}
|
|
1931
|
-
function toCssSize(value) {
|
|
1932
|
-
return typeof value === "number" ? `${value}px` : value;
|
|
1933
|
-
}
|
|
1934
|
-
var MAX_HOST_CSS = 800 * 1024;
|
|
1935
|
-
function collectHostStyles() {
|
|
1936
|
-
if (typeof document === "undefined") return "";
|
|
1937
|
-
let out = "";
|
|
1938
|
-
try {
|
|
1939
|
-
for (const sheet of Array.from(document.styleSheets)) {
|
|
1940
|
-
let rules = null;
|
|
1941
|
-
try {
|
|
1942
|
-
rules = sheet.cssRules;
|
|
1943
|
-
} catch {
|
|
1944
|
-
continue;
|
|
1945
|
-
}
|
|
1946
|
-
if (!rules) continue;
|
|
1947
|
-
for (const rule of Array.from(rules)) {
|
|
1948
|
-
out += `${rule.cssText}
|
|
1949
|
-
`;
|
|
1950
|
-
if (out.length > MAX_HOST_CSS) return out;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
} catch {
|
|
1954
|
-
return out;
|
|
1955
|
-
}
|
|
1956
|
-
return out;
|
|
1957
|
-
}
|
|
1958
|
-
function hostHtmlAttrs() {
|
|
1959
|
-
if (typeof document === "undefined") return "";
|
|
1960
|
-
try {
|
|
1961
|
-
const el2 = document.documentElement;
|
|
1962
|
-
const attrs = [];
|
|
1963
|
-
for (const name of ["class", "style", "data-theme", "data-mode", "data-color-mode"]) {
|
|
1964
|
-
const v = el2.getAttribute(name);
|
|
1965
|
-
if (v && v.length <= 1e3 && !/["<>]/.test(v)) attrs.push(`${name}="${v}"`);
|
|
1966
|
-
}
|
|
1967
|
-
return attrs.join(" ");
|
|
1968
|
-
} catch {
|
|
1969
|
-
return "";
|
|
1970
|
-
}
|
|
1971
|
-
}
|
|
1972
|
-
function isRecord(value) {
|
|
1973
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1974
|
-
}
|
|
1975
|
-
function buildSrcdoc(html, css, js, token, opts) {
|
|
1976
|
-
const bridge = `(function(){var T=${JSON.stringify(token)};function P(m){m.__veo=T;parent.postMessage(m,'*');}window.veo={dismiss:function(){P({type:'dismiss'});},cta:function(u){P({type:'cta',url:u});},track:function(n,p){P({type:'track',name:n,props:p||{}});},navigate:function(u){P({type:'navigate',url:u});}};document.addEventListener('click',function(e){var a=e.target&&e.target.closest?e.target.closest('a[href]'):null;if(!a)return;var h=a.getAttribute('href');if(!h||h.charAt(0)==='#'||/^(javascript|mailto|tel):/i.test(h))return;e.preventDefault();if(a.target==='_blank'){P({type:'cta',url:a.href,close:false});}else{P({type:'navigate',url:h});}},true);function H(){var r=document.getElementById('__veo_root');var h=r?r.getBoundingClientRect().height:document.documentElement.scrollHeight;P({type:'resize',height:Math.ceil(h)});}window.addEventListener('load',H);try{if(typeof ResizeObserver!=='undefined'){var _r=document.getElementById('__veo_root');new ResizeObserver(H).observe(_r||document.documentElement);}}catch(e){}})();`;
|
|
1977
|
-
const hostStyle = opts.hostCss ? `<style>${escapeClosing(opts.hostCss, "style")}</style>` : "";
|
|
1978
|
-
const tailwind = opts.tailwind ? '<script src="https://cdn.tailwindcss.com"></script>' : "";
|
|
1979
|
-
return (
|
|
1980
|
-
// Orden: base → CSS del host (utilidades+clases propias+:root) → reset
|
|
1981
|
-
// flotante (fondo transparente, sin márgenes) → Tailwind → CSS del autor (gana).
|
|
1982
|
-
`<!DOCTYPE html><html ${opts.htmlAttrs}><head><meta charset="utf-8"><style>html,body{margin:0;padding:0;}body{font-family:system-ui,-apple-system,sans-serif;}</style>` + hostStyle + // Reset flotante: el CSS de la app trae reglas de PÁGINA (body{min-height:100vh},
|
|
1983
|
-
// html{height:100%}, fondo del layout…) que en un iframe embebido lo estirarían a
|
|
1984
|
-
// toda la pantalla (caja negra) en vez de ajustarse al contenido. Neutralizamos
|
|
1985
|
-
// tamaño/fondo/overflow (mantenemos color y fuente heredados de la app).
|
|
1986
|
-
// `color-scheme:normal` es CLAVE para la transparencia: si el CSS de la app
|
|
1987
|
-
// declara `:root{color-scheme:dark}` y no coincide con el del elemento iframe
|
|
1988
|
-
// del host (normal), el navegador pinta el canvas OPACO (negro) en vez de
|
|
1989
|
-
// transparente — igualamos ambos lados para conservar el fondo transparente.
|
|
1990
|
-
"<style>html,body{margin:0!important;padding:0!important;background:transparent!important;height:auto!important;min-height:0!important;max-height:none!important;width:auto!important;min-width:0!important;overflow:visible!important;color-scheme:normal!important;}</style>" + tailwind + // Inline en <body>: gana incluso sobre reglas `!important` de la app (p. ej.
|
|
1991
|
-
// `.dark body{background:#000!important}`) porque el estilo inline tiene la
|
|
1992
|
-
// mayor especificidad dentro de la capa important-author.
|
|
1993
|
-
`<style>${escapeClosing(css, "style")}</style></head><body style="margin:0!important;background:transparent!important;min-height:0!important;height:auto!important;"><div id="__veo_root">${html}</div><script>${bridge}</script>` + (js ? `<script>${escapeClosing(js, "script")}</script>` : "") + "</body></html>"
|
|
1994
|
-
);
|
|
1995
|
-
}
|
|
1996
|
-
function escapeClosing(code, tag) {
|
|
1997
|
-
const rx = tag === "script" ? /<\/script/gi : /<\/style/gi;
|
|
1998
|
-
return code.replace(rx, `<\\/${tag}`);
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
// src/plugins/guides/code-block.ts
|
|
2002
|
-
function insertRawNodes(anchor, nodes, pos) {
|
|
2003
|
-
const parent = anchor.parentNode;
|
|
2004
|
-
if (pos === "prepend") {
|
|
2005
|
-
const first = anchor.firstChild;
|
|
2006
|
-
for (const n of nodes) anchor.insertBefore(n, first);
|
|
2007
|
-
} else if (pos === "append") {
|
|
2008
|
-
for (const n of nodes) anchor.appendChild(n);
|
|
2009
|
-
} else if (pos === "before") {
|
|
2010
|
-
for (const n of nodes) parent?.insertBefore(n, anchor);
|
|
2011
|
-
} else {
|
|
2012
|
-
let ref = anchor;
|
|
2013
|
-
for (const n of nodes) {
|
|
2014
|
-
parent?.insertBefore(n, ref.nextSibling);
|
|
2015
|
-
ref = n;
|
|
2016
|
-
}
|
|
2336
|
+
case "image": {
|
|
2337
|
+
if (config.imageUrl && isSafeUrl(config.imageUrl)) {
|
|
2338
|
+
const img = doc.createElement("img");
|
|
2339
|
+
img.src = config.imageUrl;
|
|
2340
|
+
img.alt = "";
|
|
2341
|
+
img.style.width = `${size}px`;
|
|
2342
|
+
img.style.height = `${size}px`;
|
|
2343
|
+
btn.appendChild(img);
|
|
2344
|
+
} else {
|
|
2345
|
+
btn.className = "veo-badge veo-badge--icon";
|
|
2346
|
+
btn.textContent = "?";
|
|
2347
|
+
btn.style.width = `${size}px`;
|
|
2348
|
+
btn.style.height = `${size}px`;
|
|
2349
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
2350
|
+
}
|
|
2351
|
+
break;
|
|
2352
|
+
}
|
|
2353
|
+
default: {
|
|
2354
|
+
btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
|
|
2355
|
+
btn.style.width = `${size}px`;
|
|
2356
|
+
btn.style.height = `${size}px`;
|
|
2357
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
2358
|
+
if (config.color) btn.style.color = config.color;
|
|
2359
|
+
break;
|
|
2360
|
+
}
|
|
2017
2361
|
}
|
|
2362
|
+
return btn;
|
|
2018
2363
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2364
|
+
|
|
2365
|
+
// src/plugins/guides/walkthrough-block-builder.ts
|
|
2366
|
+
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
2367
|
+
const container = doc.createElement("div");
|
|
2368
|
+
container.className = "veo-guide-content";
|
|
2369
|
+
const counter = doc.createElement("div");
|
|
2370
|
+
counter.className = "veo-walkthrough-counter";
|
|
2371
|
+
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
2372
|
+
container.appendChild(counter);
|
|
2373
|
+
const progress = doc.createElement("div");
|
|
2374
|
+
progress.className = "veo-walkthrough-progress";
|
|
2375
|
+
for (let i = 0; i < totalSteps; i++) {
|
|
2376
|
+
const dot = doc.createElement("span");
|
|
2377
|
+
dot.className = "veo-walkthrough-progress-dot";
|
|
2378
|
+
if (i < stepIndex) dot.classList.add("completed");
|
|
2379
|
+
if (i === stepIndex) dot.classList.add("active");
|
|
2380
|
+
progress.appendChild(dot);
|
|
2028
2381
|
}
|
|
2029
|
-
|
|
2030
|
-
const
|
|
2031
|
-
|
|
2032
|
-
const style = hostDoc.createElement("style");
|
|
2033
|
-
style.textContent = block.css;
|
|
2034
|
-
hostDoc.head.appendChild(style);
|
|
2035
|
-
injected.push(style);
|
|
2382
|
+
container.appendChild(progress);
|
|
2383
|
+
for (const node of buildContentNodes(step, doc)) {
|
|
2384
|
+
container.appendChild(node);
|
|
2036
2385
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2386
|
+
const actions = doc.createElement("div");
|
|
2387
|
+
actions.className = "veo-walkthrough-actions";
|
|
2388
|
+
const skipBtn = doc.createElement("button");
|
|
2389
|
+
skipBtn.type = "button";
|
|
2390
|
+
skipBtn.className = "veo-walkthrough-skip";
|
|
2391
|
+
skipBtn.textContent = "Omitir";
|
|
2392
|
+
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
2393
|
+
actions.appendChild(skipBtn);
|
|
2394
|
+
const rightGroup = doc.createElement("div");
|
|
2395
|
+
rightGroup.className = "veo-walkthrough-actions-right";
|
|
2396
|
+
if (stepIndex > 0) {
|
|
2397
|
+
const backBtn = doc.createElement("button");
|
|
2398
|
+
backBtn.type = "button";
|
|
2399
|
+
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
2400
|
+
backBtn.textContent = "Atr\xE1s";
|
|
2401
|
+
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
2402
|
+
rightGroup.appendChild(backBtn);
|
|
2403
|
+
}
|
|
2404
|
+
const isLastStep = stepIndex === totalSteps - 1;
|
|
2405
|
+
const primaryBtn = doc.createElement("button");
|
|
2406
|
+
primaryBtn.type = "button";
|
|
2407
|
+
primaryBtn.className = "veo-guide-cta";
|
|
2408
|
+
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
2409
|
+
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
2410
|
+
primaryBtn.addEventListener("click", () => {
|
|
2411
|
+
if (isLastStep) callbacks.onComplete();
|
|
2412
|
+
else callbacks.onNext();
|
|
2413
|
+
});
|
|
2414
|
+
rightGroup.appendChild(primaryBtn);
|
|
2415
|
+
actions.appendChild(rightGroup);
|
|
2416
|
+
container.appendChild(actions);
|
|
2417
|
+
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
2418
|
+
return container;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2421
|
+
// src/plugins/guides/renderers/banner-renderer.ts
|
|
2422
|
+
var BannerRenderer = class extends BaseRenderer {
|
|
2423
|
+
async render(context) {
|
|
2424
|
+
const idx = context.nav?.stepIndex ?? 0;
|
|
2425
|
+
const step = context.guide.guideSteps[idx];
|
|
2426
|
+
if (!step) return;
|
|
2427
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
2428
|
+
const selector = step.selector?.trim() || null;
|
|
2429
|
+
let anchor = null;
|
|
2430
|
+
if (selector) {
|
|
2431
|
+
anchor = await waitForElement(selector);
|
|
2432
|
+
if (!anchor) return;
|
|
2045
2433
|
}
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2434
|
+
const { host, root } = this.createHost();
|
|
2435
|
+
this.applyDesign(step.style);
|
|
2436
|
+
const ownerDocument = root.ownerDocument ?? document;
|
|
2437
|
+
const banner = ownerDocument.createElement("div");
|
|
2438
|
+
banner.className = bannerClassOf(position, Boolean(anchor));
|
|
2439
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2440
|
+
context.onInteraction({
|
|
2441
|
+
guideId: context.guide.guideId,
|
|
2442
|
+
stepIndex: idx,
|
|
2443
|
+
action,
|
|
2444
|
+
...meta ? { metadata: meta } : {}
|
|
2445
|
+
});
|
|
2446
|
+
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
2447
|
+
context.onClose();
|
|
2448
|
+
};
|
|
2449
|
+
const content = context.nav ? buildWalkthroughStepContent(
|
|
2450
|
+
step,
|
|
2451
|
+
context.nav.stepIndex,
|
|
2452
|
+
context.nav.totalSteps,
|
|
2453
|
+
ownerDocument,
|
|
2454
|
+
context.nav.callbacks
|
|
2455
|
+
) : buildStepContent(step, ownerDocument, {
|
|
2456
|
+
onCtaClick: (action, target, meta) => {
|
|
2457
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2458
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2459
|
+
},
|
|
2460
|
+
onDismiss: () => dismiss("dismissed")
|
|
2461
|
+
});
|
|
2462
|
+
banner.appendChild(content);
|
|
2463
|
+
root.appendChild(banner);
|
|
2464
|
+
if (selector && anchor) {
|
|
2465
|
+
host.style.display = "block";
|
|
2466
|
+
const insertAt = position === "top" ? "prepend" : "append";
|
|
2467
|
+
insertHost(anchor, host, insertAt);
|
|
2468
|
+
this.registerCleanup(keepHostAttached(host, selector, insertAt));
|
|
2469
|
+
}
|
|
2470
|
+
if (!context.nav) {
|
|
2471
|
+
this.liveContainer = banner;
|
|
2472
|
+
this.liveContent = content;
|
|
2473
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2474
|
+
onCtaClick: (action, target, meta) => {
|
|
2475
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2476
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2477
|
+
},
|
|
2478
|
+
onDismiss: () => dismiss("dismissed")
|
|
2479
|
+
});
|
|
2480
|
+
this.liveKey = this.liveKeyOf(step);
|
|
2481
|
+
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
2060
2482
|
}
|
|
2061
2483
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2484
|
+
/** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
|
|
2485
|
+
liveKeyOf(step) {
|
|
2486
|
+
const selector = step.selector?.trim() ?? "";
|
|
2487
|
+
if (!selector) return "";
|
|
2488
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
2489
|
+
return `${selector}|${position}`;
|
|
2067
2490
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2491
|
+
onLiveUpdate(step) {
|
|
2492
|
+
if (this.liveContainer) {
|
|
2493
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
2494
|
+
const embedded = Boolean(step.selector?.trim());
|
|
2495
|
+
this.liveContainer.className = bannerClassOf(position, embedded);
|
|
2072
2496
|
}
|
|
2497
|
+
}
|
|
2498
|
+
};
|
|
2499
|
+
function bannerClassOf(position, embedded) {
|
|
2500
|
+
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
// src/plugins/guides/checklist-block.ts
|
|
2504
|
+
var CHECKLIST_DEFAULTS = {
|
|
2505
|
+
iconEmptyColor: "#e7e2de",
|
|
2506
|
+
iconFillColor: "#406a31",
|
|
2507
|
+
barColor: "#406a31",
|
|
2508
|
+
barHeight: 8,
|
|
2509
|
+
fontSize: 14,
|
|
2510
|
+
fontWeight: 600,
|
|
2511
|
+
titleColor: "#2c2825",
|
|
2512
|
+
subtitleColor: "#8a8582",
|
|
2513
|
+
subtitleTemplate: "Completado <%= currentStep %> de <%= totalSteps %> pasos"
|
|
2514
|
+
};
|
|
2515
|
+
var WEIGHTS = { regular: 400, medium: 500, semibold: 600, bold: 700 };
|
|
2516
|
+
function checklistStyle(style) {
|
|
2517
|
+
const s = style ?? {};
|
|
2518
|
+
const str = (k, fb) => typeof s[k] === "string" && s[k].trim() ? s[k] : fb;
|
|
2519
|
+
const num2 = (k, fb, min, max) => typeof s[k] === "number" && Number.isFinite(s[k]) ? Math.min(max, Math.max(min, s[k])) : fb;
|
|
2520
|
+
const weightRaw = s.fontWeight;
|
|
2521
|
+
const fontWeight = typeof weightRaw === "number" ? Math.min(900, Math.max(100, weightRaw)) : typeof weightRaw === "string" && WEIGHTS[weightRaw.toLowerCase()] ? WEIGHTS[weightRaw.toLowerCase()] : CHECKLIST_DEFAULTS.fontWeight;
|
|
2522
|
+
const dark = s.colorMode === "dark";
|
|
2523
|
+
return {
|
|
2524
|
+
colorMode: dark ? "dark" : "light",
|
|
2525
|
+
iconEmptyColor: str("iconEmptyColor", dark ? "#3c3734" : CHECKLIST_DEFAULTS.iconEmptyColor),
|
|
2526
|
+
iconFillColor: str("iconFillColor", CHECKLIST_DEFAULTS.iconFillColor),
|
|
2527
|
+
barColor: str("barColor", CHECKLIST_DEFAULTS.barColor),
|
|
2528
|
+
barHeight: num2("barHeight", CHECKLIST_DEFAULTS.barHeight, 2, 40),
|
|
2529
|
+
fontSize: num2("fontSize", CHECKLIST_DEFAULTS.fontSize, 10, 40),
|
|
2530
|
+
fontWeight,
|
|
2531
|
+
titleColor: str("titleColor", dark ? "#f5efeb" : CHECKLIST_DEFAULTS.titleColor),
|
|
2532
|
+
subtitleColor: str("subtitleColor", dark ? "#b0a9a5" : CHECKLIST_DEFAULTS.subtitleColor),
|
|
2533
|
+
subtitleTemplate: str("subtitleTemplate", CHECKLIST_DEFAULTS.subtitleTemplate)
|
|
2073
2534
|
};
|
|
2074
2535
|
}
|
|
2075
|
-
function
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2536
|
+
function renderSubtitle(template, p) {
|
|
2537
|
+
return template.replace(/<%=\s*currentStep\s*%>/g, String(p.currentStep)).replace(/<%=\s*totalSteps\s*%>/g, String(p.totalSteps));
|
|
2538
|
+
}
|
|
2539
|
+
var RING_R = 10;
|
|
2540
|
+
var RING_C = 2 * Math.PI * RING_R;
|
|
2541
|
+
function buildRing(doc, fraction, completed, st) {
|
|
2542
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
2543
|
+
const svg2 = doc.createElementNS(ns, "svg");
|
|
2544
|
+
svg2.setAttribute("viewBox", "0 0 28 28");
|
|
2545
|
+
svg2.setAttribute("class", "veo-checklist-ring");
|
|
2546
|
+
svg2.setAttribute("aria-hidden", "true");
|
|
2547
|
+
const track = doc.createElementNS(ns, "circle");
|
|
2548
|
+
track.setAttribute("cx", "14");
|
|
2549
|
+
track.setAttribute("cy", "14");
|
|
2550
|
+
track.setAttribute("r", String(RING_R));
|
|
2551
|
+
track.setAttribute("fill", completed ? st.iconFillColor : "none");
|
|
2552
|
+
track.setAttribute("stroke", st.iconEmptyColor);
|
|
2553
|
+
track.setAttribute("stroke-width", "4.5");
|
|
2554
|
+
svg2.appendChild(track);
|
|
2555
|
+
if (completed) {
|
|
2556
|
+
const check = doc.createElementNS(ns, "path");
|
|
2557
|
+
check.setAttribute("d", "M9 14.5l3.2 3.2L19 11");
|
|
2558
|
+
check.setAttribute("fill", "none");
|
|
2559
|
+
check.setAttribute("stroke", "#fff");
|
|
2560
|
+
check.setAttribute("stroke-width", "2.4");
|
|
2561
|
+
check.setAttribute("stroke-linecap", "round");
|
|
2562
|
+
check.setAttribute("stroke-linejoin", "round");
|
|
2563
|
+
svg2.appendChild(check);
|
|
2564
|
+
return svg2;
|
|
2565
|
+
}
|
|
2566
|
+
const f = Math.max(0, Math.min(1, fraction));
|
|
2567
|
+
if (f > 0) {
|
|
2568
|
+
const arc = doc.createElementNS(ns, "circle");
|
|
2569
|
+
arc.setAttribute("cx", "14");
|
|
2570
|
+
arc.setAttribute("cy", "14");
|
|
2571
|
+
arc.setAttribute("r", String(RING_R));
|
|
2572
|
+
arc.setAttribute("fill", "none");
|
|
2573
|
+
arc.setAttribute("stroke", st.iconFillColor);
|
|
2574
|
+
arc.setAttribute("stroke-width", "4.5");
|
|
2575
|
+
arc.setAttribute("stroke-linecap", "butt");
|
|
2576
|
+
arc.setAttribute("stroke-dasharray", `${(RING_C * f).toFixed(2)} ${RING_C.toFixed(2)}`);
|
|
2577
|
+
arc.setAttribute("transform", "rotate(-90 14 14)");
|
|
2578
|
+
svg2.appendChild(arc);
|
|
2579
|
+
}
|
|
2580
|
+
return svg2;
|
|
2581
|
+
}
|
|
2582
|
+
function buildChecklistBlock(block, doc, context) {
|
|
2583
|
+
const st = checklistStyle(block.style);
|
|
2584
|
+
const items = (block.items ?? []).filter((i) => i && typeof i.guideId === "string" && i.guideId);
|
|
2585
|
+
const root = doc.createElement("div");
|
|
2586
|
+
root.className = `veo-checklist${st.colorMode === "dark" ? " veo-checklist--dark" : ""}`;
|
|
2587
|
+
root.style.setProperty("--veo-cl-font-size", `${st.fontSize}px`);
|
|
2588
|
+
root.style.setProperty("--veo-cl-font-weight", String(st.fontWeight));
|
|
2589
|
+
root.style.setProperty("--veo-cl-title", st.titleColor);
|
|
2590
|
+
root.style.setProperty("--veo-cl-subtitle", st.subtitleColor);
|
|
2591
|
+
root.style.setProperty("--veo-cl-bar", st.barColor);
|
|
2592
|
+
root.style.setProperty("--veo-cl-bar-h", `${st.barHeight}px`);
|
|
2593
|
+
if (typeof block.html === "string" && block.html || typeof block.text === "string" && block.text) {
|
|
2594
|
+
const title = doc.createElement("div");
|
|
2595
|
+
title.className = "veo-checklist-title";
|
|
2596
|
+
if (typeof block.html === "string" && block.html)
|
|
2597
|
+
title.appendChild(renderRichText(block.html, doc));
|
|
2598
|
+
else title.textContent = block.text;
|
|
2599
|
+
root.appendChild(title);
|
|
2600
|
+
}
|
|
2601
|
+
const list = doc.createElement("div");
|
|
2602
|
+
list.className = "veo-checklist-items";
|
|
2603
|
+
list.setAttribute("role", "list");
|
|
2604
|
+
root.appendChild(list);
|
|
2605
|
+
const footer = doc.createElement("div");
|
|
2606
|
+
footer.className = "veo-checklist-footer";
|
|
2607
|
+
const summary = doc.createElement("div");
|
|
2608
|
+
summary.className = "veo-checklist-summary";
|
|
2609
|
+
const count = doc.createElement("span");
|
|
2610
|
+
count.className = "veo-checklist-count";
|
|
2611
|
+
const percent = doc.createElement("span");
|
|
2612
|
+
percent.className = "veo-checklist-percent";
|
|
2613
|
+
summary.append(count, percent);
|
|
2614
|
+
const bar = doc.createElement("div");
|
|
2615
|
+
bar.className = "veo-checklist-bar";
|
|
2616
|
+
const fill = doc.createElement("div");
|
|
2617
|
+
fill.className = "veo-checklist-bar-fill";
|
|
2618
|
+
bar.appendChild(fill);
|
|
2619
|
+
footer.append(summary, bar);
|
|
2620
|
+
root.appendChild(footer);
|
|
2621
|
+
const paint = (progress) => {
|
|
2622
|
+
list.textContent = "";
|
|
2623
|
+
const visible = progress ? items.filter((i) => progress.has(i.guideId)) : items;
|
|
2624
|
+
let done = 0;
|
|
2625
|
+
for (const item of visible) {
|
|
2626
|
+
const p = progress?.get(item.guideId) ?? null;
|
|
2627
|
+
if (p?.completed) done++;
|
|
2628
|
+
const row = doc.createElement("button");
|
|
2629
|
+
row.type = "button";
|
|
2630
|
+
row.className = `veo-checklist-item${p?.completed ? " veo-checklist-item--done" : ""}`;
|
|
2631
|
+
row.setAttribute("role", "listitem");
|
|
2632
|
+
const fraction = p && p.totalSteps > 0 ? p.currentStep / p.totalSteps : 0;
|
|
2633
|
+
row.appendChild(buildRing(doc, fraction, p?.completed === true, st));
|
|
2634
|
+
const texts = doc.createElement("span");
|
|
2635
|
+
texts.className = "veo-checklist-texts";
|
|
2636
|
+
const label = doc.createElement("span");
|
|
2637
|
+
label.className = "veo-checklist-label";
|
|
2638
|
+
label.textContent = (item.text ?? "").trim() || p?.guideName || "Gu\xEDa";
|
|
2639
|
+
if (p && p.published === false) row.title = "Gu\xEDa no publicada: los usuarios no la ver\xE1n";
|
|
2640
|
+
texts.appendChild(label);
|
|
2641
|
+
if (p) {
|
|
2642
|
+
const sub = doc.createElement("span");
|
|
2643
|
+
sub.className = "veo-checklist-sub";
|
|
2644
|
+
sub.textContent = renderSubtitle(st.subtitleTemplate, p);
|
|
2645
|
+
texts.appendChild(sub);
|
|
2646
|
+
}
|
|
2647
|
+
row.appendChild(texts);
|
|
2648
|
+
row.addEventListener("click", (e) => {
|
|
2649
|
+
e.preventDefault();
|
|
2650
|
+
e.stopPropagation();
|
|
2651
|
+
if (context.isPreview) return;
|
|
2652
|
+
const start = p && !p.completed ? Math.min(p.currentStep, p.totalSteps - 1) : 0;
|
|
2653
|
+
if (context.onOpenGuideAt) context.onOpenGuideAt(item.guideId, start);
|
|
2654
|
+
else context.onOpenGuide?.(item.guideId);
|
|
2655
|
+
});
|
|
2656
|
+
list.appendChild(row);
|
|
2657
|
+
}
|
|
2658
|
+
const total = visible.length;
|
|
2659
|
+
const pct = total > 0 ? Math.round(done / total * 100) : 0;
|
|
2660
|
+
count.textContent = `${done} de ${total} ${total === 1 ? "completada" : "completadas"}`;
|
|
2661
|
+
percent.textContent = `${pct}%`;
|
|
2662
|
+
fill.style.width = `${pct}%`;
|
|
2663
|
+
footer.hidden = total === 0;
|
|
2664
|
+
};
|
|
2665
|
+
if (context.isPreview && items.length === 0) {
|
|
2666
|
+
const sample = /* @__PURE__ */ new Map([
|
|
2667
|
+
[
|
|
2668
|
+
"__sample_1",
|
|
2669
|
+
{
|
|
2670
|
+
guideId: "__sample_1",
|
|
2671
|
+
guideName: "Configura tu primer env\xEDo",
|
|
2672
|
+
totalSteps: 2,
|
|
2673
|
+
currentStep: 1,
|
|
2674
|
+
completed: false
|
|
2675
|
+
}
|
|
2676
|
+
],
|
|
2677
|
+
[
|
|
2678
|
+
"__sample_2",
|
|
2679
|
+
{
|
|
2680
|
+
guideId: "__sample_2",
|
|
2681
|
+
guideName: "Conecta tu tienda",
|
|
2682
|
+
totalSteps: 2,
|
|
2683
|
+
currentStep: 2,
|
|
2684
|
+
completed: true
|
|
2685
|
+
}
|
|
2686
|
+
]
|
|
2687
|
+
]);
|
|
2688
|
+
items.push(
|
|
2689
|
+
{ guideId: "__sample_1", text: "Configura tu primer env\xEDo" },
|
|
2690
|
+
{ guideId: "__sample_2", text: "Conecta tu tienda" }
|
|
2691
|
+
);
|
|
2692
|
+
paint(sample);
|
|
2693
|
+
const hint = doc.createElement("p");
|
|
2694
|
+
hint.className = "veo-checklist-empty";
|
|
2695
|
+
hint.textContent = "Ejemplo: agrega gu\xEDas a la lista desde Opciones.";
|
|
2696
|
+
root.appendChild(hint);
|
|
2697
|
+
return root;
|
|
2698
|
+
}
|
|
2699
|
+
paint(null);
|
|
2700
|
+
if (context.fetchProgress && items.length > 0) {
|
|
2701
|
+
void context.fetchProgress(items.map((i) => i.guideId)).then((rows) => paint(new Map(rows.map((r) => [r.guideId, r])))).catch(() => {
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
return root;
|
|
2081
2705
|
}
|
|
2082
2706
|
|
|
2083
2707
|
// src/plugins/guides/form-content.ts
|
|
@@ -2306,8 +2930,14 @@ function buildField(field, doc) {
|
|
|
2306
2930
|
wrapper.appendChild(group);
|
|
2307
2931
|
return { wrapper, read: () => inputs.find((i) => i.input.checked)?.value };
|
|
2308
2932
|
}
|
|
2309
|
-
case "nps":
|
|
2310
|
-
|
|
2933
|
+
case "nps": {
|
|
2934
|
+
const min = typeof field.min === "number" ? field.min : 0;
|
|
2935
|
+
const max = typeof field.max === "number" && field.max > min ? field.max : 10;
|
|
2936
|
+
return buildScale(wrapper, doc, min, max, "veo-nps-btn", void 0, {
|
|
2937
|
+
labels: field.scaleLabels ?? [],
|
|
2938
|
+
showLabels: field.showLabels !== false
|
|
2939
|
+
});
|
|
2940
|
+
}
|
|
2311
2941
|
case "rating":
|
|
2312
2942
|
return buildScale(wrapper, doc, 1, 5, "veo-rating-btn", "\u2605");
|
|
2313
2943
|
default: {
|
|
@@ -2320,17 +2950,21 @@ function buildField(field, doc) {
|
|
|
2320
2950
|
}
|
|
2321
2951
|
}
|
|
2322
2952
|
}
|
|
2323
|
-
function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
2953
|
+
function buildScale(wrapper, doc, min, max, btnClass, symbol, opts = { labels: [], showLabels: false }) {
|
|
2324
2954
|
const row = doc.createElement("div");
|
|
2325
2955
|
row.className = "veo-form-scale";
|
|
2326
2956
|
let selected;
|
|
2327
2957
|
const buttons = [];
|
|
2958
|
+
const labelOf = (n) => opts.labels[n - min]?.trim() ?? "";
|
|
2959
|
+
const anyLabel = opts.showLabels && Array.from({ length: max - min + 1 }, (_, i) => min + i).some((n) => labelOf(n));
|
|
2328
2960
|
for (let n = min; n <= max; n++) {
|
|
2329
2961
|
const btn = doc.createElement("button");
|
|
2330
2962
|
btn.type = "button";
|
|
2331
2963
|
btn.className = btnClass;
|
|
2332
2964
|
btn.textContent = symbol ?? String(n);
|
|
2333
|
-
|
|
2965
|
+
const lbl = labelOf(n);
|
|
2966
|
+
btn.setAttribute("aria-label", lbl ? `${n} \xB7 ${lbl}` : String(n));
|
|
2967
|
+
if (lbl) btn.title = lbl;
|
|
2334
2968
|
btn.addEventListener("click", () => {
|
|
2335
2969
|
selected = n;
|
|
2336
2970
|
buttons.forEach((b, i) => {
|
|
@@ -2339,11 +2973,113 @@ function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
|
2339
2973
|
});
|
|
2340
2974
|
});
|
|
2341
2975
|
buttons.push(btn);
|
|
2342
|
-
|
|
2976
|
+
if (!anyLabel) {
|
|
2977
|
+
row.appendChild(btn);
|
|
2978
|
+
continue;
|
|
2979
|
+
}
|
|
2980
|
+
const item = doc.createElement("div");
|
|
2981
|
+
item.className = `veo-scale-item${n === min ? " veo-scale-item--first" : n === max ? " veo-scale-item--last" : ""}`;
|
|
2982
|
+
const text = doc.createElement("span");
|
|
2983
|
+
text.className = "veo-scale-label";
|
|
2984
|
+
text.textContent = lbl;
|
|
2985
|
+
item.append(btn, text);
|
|
2986
|
+
row.appendChild(item);
|
|
2343
2987
|
}
|
|
2344
2988
|
wrapper.appendChild(row);
|
|
2345
2989
|
return { wrapper, read: () => selected };
|
|
2346
2990
|
}
|
|
2991
|
+
|
|
2992
|
+
// src/plugins/guides/layout.ts
|
|
2993
|
+
function groupLayout(blocks) {
|
|
2994
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2995
|
+
const order = [];
|
|
2996
|
+
let lastRow = -1;
|
|
2997
|
+
blocks.forEach((block, index) => {
|
|
2998
|
+
if (!block) return;
|
|
2999
|
+
let row;
|
|
3000
|
+
if (typeof block.row === "number" && Number.isFinite(block.row)) {
|
|
3001
|
+
row = block.row;
|
|
3002
|
+
lastRow = row;
|
|
3003
|
+
} else {
|
|
3004
|
+
row = lastRow + 0.5;
|
|
3005
|
+
lastRow = row;
|
|
3006
|
+
}
|
|
3007
|
+
const col = typeof block.col === "number" && Number.isFinite(block.col) ? block.col : 0;
|
|
3008
|
+
let r = rows.get(row);
|
|
3009
|
+
if (!r) {
|
|
3010
|
+
r = { row, columns: [] };
|
|
3011
|
+
rows.set(row, r);
|
|
3012
|
+
order.push(row);
|
|
3013
|
+
}
|
|
3014
|
+
let c = r.columns.find((x) => x.col === col);
|
|
3015
|
+
if (!c) {
|
|
3016
|
+
c = { col, blocks: [] };
|
|
3017
|
+
r.columns.push(c);
|
|
3018
|
+
}
|
|
3019
|
+
c.blocks.push({ block, index });
|
|
3020
|
+
});
|
|
3021
|
+
return order.sort((a, b) => a - b).map((k) => {
|
|
3022
|
+
const r = rows.get(k);
|
|
3023
|
+
r.columns.sort((a, b) => a.col - b.col);
|
|
3024
|
+
return r;
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
|
|
3028
|
+
// src/plugins/guides/scroll-lock.ts
|
|
3029
|
+
var states = /* @__PURE__ */ new WeakMap();
|
|
3030
|
+
function lockScroll(doc = document) {
|
|
3031
|
+
const html = doc.documentElement;
|
|
3032
|
+
const body = doc.body;
|
|
3033
|
+
if (!html || !body) return () => {
|
|
3034
|
+
};
|
|
3035
|
+
let state = states.get(doc);
|
|
3036
|
+
if (!state) {
|
|
3037
|
+
state = {
|
|
3038
|
+
count: 0,
|
|
3039
|
+
html: { overflow: html.style.overflow, paddingRight: html.style.paddingRight },
|
|
3040
|
+
body: { overflow: body.style.overflow, paddingRight: body.style.paddingRight }
|
|
3041
|
+
};
|
|
3042
|
+
states.set(doc, state);
|
|
3043
|
+
const win = doc.defaultView;
|
|
3044
|
+
const gap = win ? Math.max(0, win.innerWidth - html.clientWidth) : 0;
|
|
3045
|
+
if (gap > 0) body.style.paddingRight = `${gap}px`;
|
|
3046
|
+
html.style.overflow = "hidden";
|
|
3047
|
+
body.style.overflow = "hidden";
|
|
3048
|
+
}
|
|
3049
|
+
state.count += 1;
|
|
3050
|
+
let released = false;
|
|
3051
|
+
return () => {
|
|
3052
|
+
if (released) return;
|
|
3053
|
+
released = true;
|
|
3054
|
+
const s = states.get(doc);
|
|
3055
|
+
if (!s) return;
|
|
3056
|
+
s.count -= 1;
|
|
3057
|
+
if (s.count > 0) return;
|
|
3058
|
+
states.delete(doc);
|
|
3059
|
+
html.style.overflow = s.html.overflow;
|
|
3060
|
+
html.style.paddingRight = s.html.paddingRight;
|
|
3061
|
+
body.style.overflow = s.body.overflow;
|
|
3062
|
+
body.style.paddingRight = s.body.paddingRight;
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
// src/plugins/guides/snooze.ts
|
|
3067
|
+
var KEY = (guideId) => `veo:snooze:${guideId}`;
|
|
3068
|
+
var UNIT_MS = {
|
|
3069
|
+
hour: 36e5,
|
|
3070
|
+
day: 864e5,
|
|
3071
|
+
week: 6048e5
|
|
3072
|
+
};
|
|
3073
|
+
function snoozeDurationMs(every, unit) {
|
|
3074
|
+
const n = Number.isFinite(every) && every > 0 ? Math.floor(every) : 1;
|
|
3075
|
+
return n * (UNIT_MS[unit] ?? UNIT_MS.day);
|
|
3076
|
+
}
|
|
3077
|
+
function snoozeGuide(guideId, every, unit, now = Date.now()) {
|
|
3078
|
+
try {
|
|
3079
|
+
localStorage.setItem(KEY(guideId), String(now + snoozeDurationMs(every, unit)));
|
|
3080
|
+
} catch {
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
2347
3083
|
var INFO_GLYPH = "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021";
|
|
2348
3084
|
var QUESTION_GLYPH = "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75";
|
|
2349
3085
|
var STAR_GLYPH = "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z";
|
|
@@ -2533,6 +3269,32 @@ var ROW_ALIGN = {
|
|
|
2533
3269
|
center: "center",
|
|
2534
3270
|
right: "flex-end"
|
|
2535
3271
|
};
|
|
3272
|
+
var ROW_JUSTIFY = { ...ROW_ALIGN, distribute: "space-between" };
|
|
3273
|
+
var ROW_VALIGN = {
|
|
3274
|
+
top: "flex-start",
|
|
3275
|
+
center: "center",
|
|
3276
|
+
bottom: "flex-end"
|
|
3277
|
+
};
|
|
3278
|
+
function applyRowStyle(rowEl, style) {
|
|
3279
|
+
const s = rowEl.style;
|
|
3280
|
+
if (typeof style.background === "string" && style.background) s.background = style.background;
|
|
3281
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3282
|
+
const p = style[`padding${side}`];
|
|
3283
|
+
if (typeof p === "number" && Number.isFinite(p))
|
|
3284
|
+
s.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3285
|
+
const m = style[`margin${side}`];
|
|
3286
|
+
if (typeof m === "number" && Number.isFinite(m))
|
|
3287
|
+
s.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3288
|
+
}
|
|
3289
|
+
if (typeof style.minHeight === "number" && Number.isFinite(style.minHeight) && style.minHeight > 0) {
|
|
3290
|
+
s.minHeight = `${style.minHeight}px`;
|
|
3291
|
+
}
|
|
3292
|
+
if (style.align && ROW_JUSTIFY[style.align]) {
|
|
3293
|
+
s.justifyContent = ROW_JUSTIFY[style.align];
|
|
3294
|
+
rowEl.classList.add("veo-row--aligned");
|
|
3295
|
+
}
|
|
3296
|
+
if (style.valign && ROW_VALIGN[style.valign]) s.alignItems = ROW_VALIGN[style.valign];
|
|
3297
|
+
}
|
|
2536
3298
|
var CompositeRenderer = class extends BaseRenderer {
|
|
2537
3299
|
async render(context) {
|
|
2538
3300
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -2551,7 +3313,8 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2551
3313
|
const rawDoc = anchor.ownerDocument ?? document;
|
|
2552
3314
|
const { cleanup } = mountCodeBlock(soleCode, rawDoc, context, void 0, {
|
|
2553
3315
|
anchor,
|
|
2554
|
-
pos: inlinePosOf(step.inlinePosition)
|
|
3316
|
+
pos: inlinePosOf(step.inlinePosition),
|
|
3317
|
+
...context.isPreview && soleCode.editorId ? { tagBlockId: soleCode.editorId } : {}
|
|
2555
3318
|
});
|
|
2556
3319
|
this.registerCleanup(cleanup);
|
|
2557
3320
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action: "shown" });
|
|
@@ -2564,6 +3327,10 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2564
3327
|
const card = doc.createElement("div");
|
|
2565
3328
|
card.className = "veo-card";
|
|
2566
3329
|
applyCompositeDesign(card, design);
|
|
3330
|
+
if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
|
|
3331
|
+
card.setAttribute("role", "dialog");
|
|
3332
|
+
card.setAttribute("aria-label", design.ariaLabel.trim());
|
|
3333
|
+
}
|
|
2567
3334
|
const dismiss = (action, meta) => {
|
|
2568
3335
|
context.onInteraction({
|
|
2569
3336
|
guideId: context.guide.guideId,
|
|
@@ -2574,6 +3341,28 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2574
3341
|
};
|
|
2575
3342
|
const readers = [];
|
|
2576
3343
|
this.buildContent(card, step.blocks ?? [], doc, context, idx, readers, host);
|
|
3344
|
+
if (blocks.length === 0) {
|
|
3345
|
+
card.classList.add("veo-card--empty");
|
|
3346
|
+
if (context.isPreview) {
|
|
3347
|
+
const zone = doc.createElement("div");
|
|
3348
|
+
zone.className = "veo-card-empty-zone";
|
|
3349
|
+
const plus = doc.createElement("b");
|
|
3350
|
+
plus.textContent = "+";
|
|
3351
|
+
const text = doc.createElement("span");
|
|
3352
|
+
text.textContent = "Clic para agregar contenido";
|
|
3353
|
+
zone.append(plus, text);
|
|
3354
|
+
card.appendChild(zone);
|
|
3355
|
+
const applyNarrow = () => {
|
|
3356
|
+
card.classList.toggle("veo-card--narrow", card.clientWidth < NARROW_CARD_WIDTH);
|
|
3357
|
+
};
|
|
3358
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
3359
|
+
const ro = new ResizeObserver(applyNarrow);
|
|
3360
|
+
ro.observe(card);
|
|
3361
|
+
this.registerCleanup(() => ro.disconnect());
|
|
3362
|
+
}
|
|
3363
|
+
applyNarrow();
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
2577
3366
|
const lastBlock = card.lastElementChild;
|
|
2578
3367
|
if (lastBlock && !lastBlock.style.marginBottom) lastBlock.style.marginBottom = "0";
|
|
2579
3368
|
let closeTooltip = null;
|
|
@@ -2591,6 +3380,14 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2591
3380
|
if (closeColor) closeBtn.style.color = closeColor;
|
|
2592
3381
|
const closePos = typeof design.closePosition === "string" ? design.closePosition : "top-right";
|
|
2593
3382
|
applyClosePosition(closeBtn, closePos);
|
|
3383
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3384
|
+
const p = design[`closePadding${side}`];
|
|
3385
|
+
if (typeof p === "number")
|
|
3386
|
+
closeBtn.style.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3387
|
+
const m = design[`closeMargin${side}`];
|
|
3388
|
+
if (typeof m === "number")
|
|
3389
|
+
closeBtn.style.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3390
|
+
}
|
|
2594
3391
|
if (hoverColor) {
|
|
2595
3392
|
closeBtn.addEventListener("mouseenter", () => {
|
|
2596
3393
|
closeBtn.style.color = hoverColor;
|
|
@@ -2600,6 +3397,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2600
3397
|
});
|
|
2601
3398
|
}
|
|
2602
3399
|
card.appendChild(closeBtn);
|
|
3400
|
+
reserveCloseSpace(card, blocks, closePos, design);
|
|
2603
3401
|
}
|
|
2604
3402
|
if (badgeMode && anchor) {
|
|
2605
3403
|
closeTooltip = this.renderBadgeMode(
|
|
@@ -2630,6 +3428,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2630
3428
|
const bz = typeof design.backdropZIndex === "number" ? design.backdropZIndex : 2e5;
|
|
2631
3429
|
bg.style.zIndex = String(bz);
|
|
2632
3430
|
root.appendChild(bg);
|
|
3431
|
+
this.registerCleanup(lockScroll(doc));
|
|
2633
3432
|
if (design.targetAbove === true) this.registerCleanup(liftTarget(anchor, bz + 1));
|
|
2634
3433
|
}
|
|
2635
3434
|
const wrapper = doc.createElement("div");
|
|
@@ -2656,6 +3455,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2656
3455
|
this.closeOrSkip(context);
|
|
2657
3456
|
}
|
|
2658
3457
|
});
|
|
3458
|
+
this.registerCleanup(lockScroll(doc));
|
|
2659
3459
|
}
|
|
2660
3460
|
applyViewportPosition(card, step.position, design);
|
|
2661
3461
|
overlay.appendChild(card);
|
|
@@ -2673,83 +3473,217 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2673
3473
|
...block.id ? { metadata: { buttonId: block.id } } : {}
|
|
2674
3474
|
});
|
|
2675
3475
|
};
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
3476
|
+
const tag = (node, block, index) => {
|
|
3477
|
+
node.dataset.veoBlock = String(index);
|
|
3478
|
+
if (block.editorId) node.dataset.veoBlockId = block.editorId;
|
|
3479
|
+
return node;
|
|
3480
|
+
};
|
|
3481
|
+
const placeholder = (block, label) => {
|
|
3482
|
+
if (!context.isPreview || !block.editorId) return null;
|
|
3483
|
+
const el2 = doc.createElement("div");
|
|
3484
|
+
el2.className = "veo-block-placeholder";
|
|
3485
|
+
el2.textContent = label;
|
|
3486
|
+
return el2;
|
|
3487
|
+
};
|
|
3488
|
+
const buildSequence = (container, cell) => {
|
|
3489
|
+
let i = 0;
|
|
3490
|
+
while (i < cell.length) {
|
|
3491
|
+
const { block: b, index } = cell[i];
|
|
3492
|
+
if (b.type === "button") {
|
|
3493
|
+
const row = doc.createElement("div");
|
|
3494
|
+
row.className = "veo-guide-actions";
|
|
3495
|
+
let rowAlign;
|
|
3496
|
+
while (i < cell.length && cell[i]?.block.type === "button") {
|
|
3497
|
+
const { block: bb, index: bi } = cell[i];
|
|
3498
|
+
if (!rowAlign) {
|
|
3499
|
+
const a = bb.style?.align;
|
|
3500
|
+
if (a === "left" || a === "center" || a === "right") rowAlign = a;
|
|
3501
|
+
}
|
|
3502
|
+
row.appendChild(tag(this.buildButton(bb, doc, context, idx, readers, emit), bb, bi));
|
|
3503
|
+
i++;
|
|
2692
3504
|
}
|
|
2693
|
-
row.
|
|
3505
|
+
if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
|
|
3506
|
+
container.appendChild(row);
|
|
3507
|
+
continue;
|
|
3508
|
+
}
|
|
3509
|
+
if (b.type === "code") {
|
|
3510
|
+
const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
|
|
3511
|
+
const el2 = node2 ?? placeholder(b, "Bloque de c\xF3digo");
|
|
3512
|
+
if (el2) container.appendChild(tag(el2, b, index));
|
|
3513
|
+
this.registerCleanup(cleanup);
|
|
2694
3514
|
i++;
|
|
3515
|
+
continue;
|
|
2695
3516
|
}
|
|
2696
|
-
if (
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
form.className = "veo-form";
|
|
2710
|
-
for (const field of b.fields ?? []) {
|
|
2711
|
-
const { wrapper, read } = buildField(field, doc);
|
|
2712
|
-
readers.push({ field, read });
|
|
2713
|
-
form.appendChild(wrapper);
|
|
3517
|
+
if (b.type === "form") {
|
|
3518
|
+
container.appendChild(tag(this.buildForm(b, doc, context, idx, readers, emit), b, index));
|
|
3519
|
+
i++;
|
|
3520
|
+
continue;
|
|
3521
|
+
}
|
|
3522
|
+
if (b.type === "checklist") {
|
|
3523
|
+
const el2 = buildChecklistBlock(b, doc, context);
|
|
3524
|
+
applyBlockStyle(el2, b.style, "text");
|
|
3525
|
+
el2.style.alignSelf = "stretch";
|
|
3526
|
+
el2.style.width = "100%";
|
|
3527
|
+
container.appendChild(tag(el2, b, index));
|
|
3528
|
+
i++;
|
|
3529
|
+
continue;
|
|
2714
3530
|
}
|
|
2715
|
-
|
|
3531
|
+
const node = buildContentBlock(b, doc, { editing: context.isPreview === true }) ?? placeholder(
|
|
3532
|
+
b,
|
|
3533
|
+
b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo"
|
|
3534
|
+
);
|
|
3535
|
+
if (node) container.appendChild(tag(node, b, index));
|
|
2716
3536
|
i++;
|
|
3537
|
+
}
|
|
3538
|
+
};
|
|
3539
|
+
const rowStyles = context.guide.guideSteps[idx]?.rows;
|
|
3540
|
+
for (const row of groupLayout(blocks)) {
|
|
3541
|
+
const rowStyle = Number.isInteger(row.row) ? rowStyles?.[String(row.row)] : void 0;
|
|
3542
|
+
if (row.columns.length === 1 && !rowStyle) {
|
|
3543
|
+
buildSequence(card, row.columns[0].blocks);
|
|
2717
3544
|
continue;
|
|
2718
3545
|
}
|
|
2719
|
-
const
|
|
2720
|
-
|
|
2721
|
-
|
|
3546
|
+
const rowEl = doc.createElement("div");
|
|
3547
|
+
rowEl.className = "veo-row";
|
|
3548
|
+
if (Number.isInteger(row.row)) rowEl.dataset.veoRow = String(row.row);
|
|
3549
|
+
for (const column of row.columns) {
|
|
3550
|
+
const colEl = doc.createElement("div");
|
|
3551
|
+
colEl.className = "veo-col";
|
|
3552
|
+
buildSequence(colEl, column.blocks);
|
|
3553
|
+
rowEl.appendChild(colEl);
|
|
3554
|
+
}
|
|
3555
|
+
if (rowStyle) applyRowStyle(rowEl, rowStyle);
|
|
3556
|
+
card.appendChild(rowEl);
|
|
2722
3557
|
}
|
|
2723
3558
|
}
|
|
2724
3559
|
/** Botón con enrutado de acción (navegación / cierre / url / guía / envío de forms). */
|
|
2725
|
-
|
|
3560
|
+
/**
|
|
3561
|
+
* Bloque `form` (encuesta): pregunta (rich text del bloque) + campos + botón
|
|
3562
|
+
* de envío propio (`submit`). Con pregunta, el label del campo se omite (sería
|
|
3563
|
+
* la misma pregunta dos veces) y pasa a `aria-label` del input. El estilo del
|
|
3564
|
+
* bloque viste la pregunta (preset/alineación/color); los márgenes van al
|
|
3565
|
+
* contenedor del bloque entero.
|
|
3566
|
+
*/
|
|
3567
|
+
buildForm(b, doc, context, idx, readers, emit) {
|
|
3568
|
+
const form = doc.createElement("div");
|
|
3569
|
+
form.className = "veo-form";
|
|
3570
|
+
const style = b.style ?? {};
|
|
3571
|
+
const { marginTop, marginBottom, ...questionStyle } = style;
|
|
3572
|
+
for (const [side, v] of [
|
|
3573
|
+
["marginTop", marginTop],
|
|
3574
|
+
["marginBottom", marginBottom]
|
|
3575
|
+
]) {
|
|
3576
|
+
if (typeof v === "number" && Number.isFinite(v)) form.style[side] = `${v}px`;
|
|
3577
|
+
}
|
|
3578
|
+
let question = null;
|
|
3579
|
+
if (typeof b.html === "string" && b.html || typeof b.text === "string" && b.text) {
|
|
3580
|
+
question = doc.createElement("div");
|
|
3581
|
+
question.className = "veo-form-question";
|
|
3582
|
+
if (typeof b.html === "string" && b.html) question.appendChild(renderRichText(b.html, doc));
|
|
3583
|
+
else question.textContent = b.text;
|
|
3584
|
+
applyBlockStyle(question, questionStyle, "text");
|
|
3585
|
+
form.appendChild(question);
|
|
3586
|
+
}
|
|
3587
|
+
const questionText = question?.textContent?.trim() ?? "";
|
|
3588
|
+
const own = [];
|
|
3589
|
+
for (const field of b.fields ?? []) {
|
|
3590
|
+
const { wrapper, read } = buildField(field, doc);
|
|
3591
|
+
const st = b.style ?? {};
|
|
3592
|
+
if (typeof st.scaleColor === "string" && st.scaleColor) {
|
|
3593
|
+
wrapper.style.setProperty("--veo-scale-color", st.scaleColor);
|
|
3594
|
+
}
|
|
3595
|
+
if (typeof st.scaleSize === "number" && st.scaleSize >= 24 && st.scaleSize <= 96) {
|
|
3596
|
+
wrapper.style.setProperty("--veo-scale-size", `${st.scaleSize}px`);
|
|
3597
|
+
}
|
|
3598
|
+
if (question) {
|
|
3599
|
+
wrapper.querySelector(".veo-form-label")?.remove();
|
|
3600
|
+
if (questionText) {
|
|
3601
|
+
for (const inp of wrapper.querySelectorAll("input, textarea, select")) {
|
|
3602
|
+
inp.setAttribute("aria-label", questionText);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
const reader = { field, read, blockId: b.id ?? null };
|
|
3607
|
+
readers.push(reader);
|
|
3608
|
+
own.push(reader);
|
|
3609
|
+
form.appendChild(wrapper);
|
|
3610
|
+
}
|
|
3611
|
+
if (b.submit) {
|
|
3612
|
+
const actions = doc.createElement("div");
|
|
3613
|
+
actions.className = "veo-guide-actions";
|
|
3614
|
+
const submit = { ...b.submit, type: "button" };
|
|
3615
|
+
const sa = submit.style?.align;
|
|
3616
|
+
if (sa === "left" || sa === "center" || sa === "right") {
|
|
3617
|
+
actions.style.justifyContent = ROW_ALIGN[sa] ?? "flex-end";
|
|
3618
|
+
}
|
|
3619
|
+
actions.appendChild(this.buildButton(submit, doc, context, idx, readers, emit, own));
|
|
3620
|
+
form.appendChild(actions);
|
|
3621
|
+
}
|
|
3622
|
+
return form;
|
|
3623
|
+
}
|
|
3624
|
+
buildButton(block, doc, context, idx, readers, emit, submitFirst) {
|
|
2726
3625
|
const btn = doc.createElement("button");
|
|
2727
3626
|
btn.type = "button";
|
|
2728
3627
|
btn.className = "veo-guide-cta";
|
|
2729
3628
|
btn.textContent = block.text?.trim() || "OK";
|
|
3629
|
+
const st = block.style ?? {};
|
|
3630
|
+
if (st.preset === "secondary" || st.preset === "link") {
|
|
3631
|
+
btn.classList.add(`veo-guide-cta--${st.preset}`);
|
|
3632
|
+
}
|
|
3633
|
+
if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
|
|
3634
|
+
btn.setAttribute("aria-label", st.ariaLabel.trim());
|
|
3635
|
+
}
|
|
2730
3636
|
applyBlockStyle(btn, block.style, "button");
|
|
2731
3637
|
const nav = context.nav?.callbacks;
|
|
2732
|
-
|
|
2733
|
-
switch (
|
|
3638
|
+
const runSimple = (spec) => {
|
|
3639
|
+
switch (spec.action) {
|
|
2734
3640
|
case "next":
|
|
2735
|
-
emit(block);
|
|
2736
3641
|
if (nav) nav.onNext();
|
|
2737
3642
|
else context.onClose();
|
|
2738
3643
|
break;
|
|
2739
3644
|
case "prev":
|
|
2740
|
-
emit(block);
|
|
2741
3645
|
nav?.onBack();
|
|
2742
3646
|
break;
|
|
3647
|
+
case "goto":
|
|
3648
|
+
if (nav?.onGoto && typeof spec.targetStep === "number") nav.onGoto(spec.targetStep);
|
|
3649
|
+
else context.onClose();
|
|
3650
|
+
break;
|
|
3651
|
+
case "snooze":
|
|
3652
|
+
snoozeGuide(
|
|
3653
|
+
context.guide.guideId,
|
|
3654
|
+
typeof spec.snoozeEvery === "number" ? spec.snoozeEvery : 1,
|
|
3655
|
+
spec.snoozeUnit ?? "day"
|
|
3656
|
+
);
|
|
3657
|
+
context.onClose();
|
|
3658
|
+
break;
|
|
2743
3659
|
case "url":
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
window.open(
|
|
3660
|
+
if (typeof spec.url === "string" && isSafeUrl(spec.url)) {
|
|
3661
|
+
if (spec.newTab === false) window.location.assign(spec.url);
|
|
3662
|
+
else window.open(spec.url, "_blank", "noopener,noreferrer");
|
|
2747
3663
|
}
|
|
2748
3664
|
break;
|
|
2749
3665
|
case "guide":
|
|
2750
|
-
|
|
2751
|
-
|
|
3666
|
+
if (spec.targetGuideId) context.onOpenGuide?.(spec.targetGuideId);
|
|
3667
|
+
this.closeOrSkip(context);
|
|
3668
|
+
break;
|
|
3669
|
+
default:
|
|
2752
3670
|
this.closeOrSkip(context);
|
|
3671
|
+
}
|
|
3672
|
+
};
|
|
3673
|
+
const runSecond = () => {
|
|
3674
|
+
if (block.secondAction?.action) runSimple(block.secondAction);
|
|
3675
|
+
};
|
|
3676
|
+
const runActions = () => {
|
|
3677
|
+
switch (block.action) {
|
|
3678
|
+
case "next":
|
|
3679
|
+
case "prev":
|
|
3680
|
+
case "goto":
|
|
3681
|
+
case "snooze":
|
|
3682
|
+
case "url":
|
|
3683
|
+
case "guide":
|
|
3684
|
+
emit(block);
|
|
3685
|
+
runSimple(block);
|
|
3686
|
+
runSecond();
|
|
2753
3687
|
break;
|
|
2754
3688
|
case "submit-forms":
|
|
2755
3689
|
case "submit-forms-advance":
|
|
@@ -2768,7 +3702,12 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2768
3702
|
default:
|
|
2769
3703
|
emit(block);
|
|
2770
3704
|
this.closeOrSkip(context);
|
|
3705
|
+
runSecond();
|
|
2771
3706
|
}
|
|
3707
|
+
};
|
|
3708
|
+
btn.addEventListener("click", () => {
|
|
3709
|
+
if (submitFirst) this.submitForms(submitFirst, context, idx, btn, runActions);
|
|
3710
|
+
else runActions();
|
|
2772
3711
|
});
|
|
2773
3712
|
return btn;
|
|
2774
3713
|
}
|
|
@@ -2787,17 +3726,27 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2787
3726
|
}
|
|
2788
3727
|
answers[field.key] = value;
|
|
2789
3728
|
}
|
|
2790
|
-
if (
|
|
3729
|
+
if (Object.keys(answers).length === 0) {
|
|
3730
|
+
onDone();
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
2791
3733
|
btn.disabled = true;
|
|
2792
3734
|
const submitFn = context.onFormSubmit ?? (() => Promise.resolve(true));
|
|
2793
|
-
|
|
3735
|
+
const blockIds = new Set(
|
|
3736
|
+
readers.filter((r) => answers[r.field.key] !== void 0).map((r) => r.blockId ?? "")
|
|
3737
|
+
);
|
|
3738
|
+
const pollId = blockIds.size === 1 ? [...blockIds][0] || void 0 : void 0;
|
|
3739
|
+
void submitFn(answers, { ...pollId ? { pollId } : {}, stepIndex: idx }).then((ok) => {
|
|
2794
3740
|
btn.disabled = false;
|
|
2795
3741
|
if (!ok) return;
|
|
2796
|
-
context.
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
3742
|
+
const lastStep = !context.nav || idx >= context.nav.totalSteps - 1;
|
|
3743
|
+
if (lastStep) {
|
|
3744
|
+
context.onInteraction({
|
|
3745
|
+
guideId: context.guide.guideId,
|
|
3746
|
+
stepIndex: idx,
|
|
3747
|
+
action: "completed"
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
2801
3750
|
onDone();
|
|
2802
3751
|
});
|
|
2803
3752
|
}
|
|
@@ -2911,21 +3860,38 @@ function inlinePosOf(p) {
|
|
|
2911
3860
|
function applyViewportPosition(card, position, design) {
|
|
2912
3861
|
const s = card.style;
|
|
2913
3862
|
s.position = "fixed";
|
|
2914
|
-
const
|
|
3863
|
+
const BASE = 16;
|
|
3864
|
+
const m = (k) => typeof design[k] === "number" && Number.isFinite(design[k]) ? design[k] : 0;
|
|
3865
|
+
s.margin = "0";
|
|
2915
3866
|
const hAlign = design.hAlign === "left" ? "left" : design.hAlign === "right" ? "right" : null;
|
|
2916
3867
|
const v = position.includes("top") ? "top" : position.includes("bottom") ? "bottom" : "middle";
|
|
2917
3868
|
const h = hAlign ?? (position.includes("left") ? "left" : position.includes("right") ? "right" : "middle");
|
|
2918
|
-
if (v === "top") s.top =
|
|
2919
|
-
else if (v === "bottom") s.bottom =
|
|
3869
|
+
if (v === "top") s.top = `${BASE + m("marginTop")}px`;
|
|
3870
|
+
else if (v === "bottom") s.bottom = `${BASE + m("marginBottom")}px`;
|
|
2920
3871
|
else s.top = "50%";
|
|
2921
|
-
if (h === "left") s.left =
|
|
2922
|
-
else if (h === "right") s.right =
|
|
3872
|
+
if (h === "left") s.left = `${BASE + m("marginLeft")}px`;
|
|
3873
|
+
else if (h === "right") s.right = `${BASE + m("marginRight")}px`;
|
|
2923
3874
|
else s.left = "50%";
|
|
2924
|
-
const
|
|
2925
|
-
const
|
|
3875
|
+
const dx = m("marginLeft") - m("marginRight");
|
|
3876
|
+
const dy = m("marginTop") - m("marginBottom");
|
|
3877
|
+
const tx = h === "middle" ? `calc(-50% + ${dx}px)` : "0";
|
|
3878
|
+
const ty = v === "middle" ? `calc(-50% + ${dy}px)` : "0";
|
|
2926
3879
|
s.transform = tx === "0" && ty === "0" ? "" : `translate(${tx}, ${ty})`;
|
|
2927
3880
|
}
|
|
2928
3881
|
var CLOSE_INSET = "10px";
|
|
3882
|
+
var CLOSE_RESERVE_PX = 42;
|
|
3883
|
+
var CARD_DEFAULT_PAD_PX = 20;
|
|
3884
|
+
function reserveCloseSpace(card, blocks, position, design) {
|
|
3885
|
+
if (blocks.length === 0) return;
|
|
3886
|
+
const side = position.includes("bottom") ? "Bottom" : "Top";
|
|
3887
|
+
const edgeBlock = side === "Top" ? blocks[0] : blocks[blocks.length - 1];
|
|
3888
|
+
if (edgeBlock?.type === "image") return;
|
|
3889
|
+
const margin = design[`closeMargin${side}`];
|
|
3890
|
+
const need = CLOSE_RESERVE_PX + (typeof margin === "number" ? margin : 0);
|
|
3891
|
+
const current = design[`padding${side}`];
|
|
3892
|
+
const have = typeof current === "number" ? current : CARD_DEFAULT_PAD_PX;
|
|
3893
|
+
if (have < need) card.style.setProperty(`padding-${side.toLowerCase()}`, `${need}px`);
|
|
3894
|
+
}
|
|
2929
3895
|
function applyClosePosition(btn, position) {
|
|
2930
3896
|
const s = btn.style;
|
|
2931
3897
|
s.top = "auto";
|
|
@@ -2990,10 +3956,17 @@ async function anchorPosition(anchor, wrapper, caret, preferred = "bottom") {
|
|
|
2990
3956
|
window.removeEventListener("resize", reposition);
|
|
2991
3957
|
};
|
|
2992
3958
|
}
|
|
3959
|
+
var MIN_CARD_WIDTH = 120;
|
|
3960
|
+
var NARROW_CARD_WIDTH = 260;
|
|
2993
3961
|
function applyCompositeDesign(card, design) {
|
|
2994
3962
|
const s = card.style;
|
|
2995
3963
|
const px = (v) => typeof v === "number" && Number.isFinite(v) ? `${v}px` : null;
|
|
2996
3964
|
const strv = (v) => typeof v === "string" && v ? v : null;
|
|
3965
|
+
if (design.colorMode === "dark") {
|
|
3966
|
+
s.background = "#1f1d1b";
|
|
3967
|
+
s.color = "#f5f0eb";
|
|
3968
|
+
card.classList.add("veo-card--dark");
|
|
3969
|
+
}
|
|
2997
3970
|
if (strv(design.background)) s.background = design.background;
|
|
2998
3971
|
const textColor = strv(design.color) ?? strv(design.textColor);
|
|
2999
3972
|
if (textColor) s.color = textColor;
|
|
@@ -3015,13 +3988,14 @@ function applyCompositeDesign(card, design) {
|
|
|
3015
3988
|
const n = (k) => typeof design[k] === "number" ? design[k] : 0;
|
|
3016
3989
|
s.boxShadow = `${n("shadowX")}px ${n("shadowY")}px ${n("shadowBlur")}px ${n("shadowSpread")}px ${strv(design.shadowColor) ?? "rgba(0,0,0,0.2)"}`;
|
|
3017
3990
|
}
|
|
3991
|
+
const width = typeof design.width === "number" && Number.isFinite(design.width) ? `${Math.max(MIN_CARD_WIDTH, design.width)}px` : null;
|
|
3018
3992
|
if (design.fullWidth === true) {
|
|
3019
3993
|
s.width = "100%";
|
|
3020
3994
|
} else if (design.widthType === "fluid") {
|
|
3021
|
-
s.width = "
|
|
3022
|
-
if (
|
|
3023
|
-
} else if (
|
|
3024
|
-
s.width =
|
|
3995
|
+
s.width = "100%";
|
|
3996
|
+
if (width) s.maxWidth = width;
|
|
3997
|
+
} else if (width) {
|
|
3998
|
+
s.width = width;
|
|
3025
3999
|
}
|
|
3026
4000
|
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3027
4001
|
const p = px(design[`padding${side}`]);
|
|
@@ -3125,6 +4099,7 @@ var FormRenderer = class extends BaseRenderer {
|
|
|
3125
4099
|
mountFormContent(card, context, doc);
|
|
3126
4100
|
overlay.appendChild(card);
|
|
3127
4101
|
root.appendChild(overlay);
|
|
4102
|
+
this.registerCleanup(lockScroll(doc));
|
|
3128
4103
|
const dismiss = () => {
|
|
3129
4104
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
|
|
3130
4105
|
context.onClose();
|
|
@@ -3252,6 +4227,18 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3252
4227
|
constructor() {
|
|
3253
4228
|
super(...arguments);
|
|
3254
4229
|
this.overlay = null;
|
|
4230
|
+
/** Suelta el bloqueo de scroll del backdrop (null = sin backdrop). */
|
|
4231
|
+
this.releaseScroll = null;
|
|
4232
|
+
}
|
|
4233
|
+
/** Con backdrop la app de atrás queda congelada; sin él, scrollea normal. */
|
|
4234
|
+
syncScrollLock(step) {
|
|
4235
|
+
const wantLock = step.style?.backdrop !== false;
|
|
4236
|
+
if (wantLock && !this.releaseScroll) {
|
|
4237
|
+
this.releaseScroll = lockScroll(this.host?.ownerDocument ?? document);
|
|
4238
|
+
} else if (!wantLock && this.releaseScroll) {
|
|
4239
|
+
this.releaseScroll();
|
|
4240
|
+
this.releaseScroll = null;
|
|
4241
|
+
}
|
|
3255
4242
|
}
|
|
3256
4243
|
render(context) {
|
|
3257
4244
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -3263,6 +4250,7 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3263
4250
|
const overlay = ownerDocument.createElement("div");
|
|
3264
4251
|
overlay.className = overlayClassOf(step);
|
|
3265
4252
|
this.overlay = overlay;
|
|
4253
|
+
this.syncScrollLock(step);
|
|
3266
4254
|
const card = ownerDocument.createElement("div");
|
|
3267
4255
|
card.className = "veo-modal-card";
|
|
3268
4256
|
const dismiss = (action, ctaUrl, meta) => {
|
|
@@ -3320,9 +4308,12 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3320
4308
|
}
|
|
3321
4309
|
onLiveUpdate(step) {
|
|
3322
4310
|
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
4311
|
+
this.syncScrollLock(step);
|
|
3323
4312
|
}
|
|
3324
4313
|
destroy() {
|
|
3325
4314
|
this.overlay = null;
|
|
4315
|
+
this.releaseScroll?.();
|
|
4316
|
+
this.releaseScroll = null;
|
|
3326
4317
|
super.destroy();
|
|
3327
4318
|
}
|
|
3328
4319
|
};
|
|
@@ -3536,7 +4527,9 @@ var GuidePreviewController = class {
|
|
|
3536
4527
|
if (event.action === "shown") shown = true;
|
|
3537
4528
|
},
|
|
3538
4529
|
onClose: () => this.close(true),
|
|
3539
|
-
|
|
4530
|
+
// Sin indicación explícita, el badge se abre solo si la guía arranca
|
|
4531
|
+
// sola (como en el runtime); `manual` lo deja cerrado.
|
|
4532
|
+
badgeOpen: this.input?.badgeOpen ?? isAutomaticActivation(this.input?.activationRules),
|
|
3540
4533
|
onTrack: () => {
|
|
3541
4534
|
},
|
|
3542
4535
|
// Encadenado en preview: no-op — el editor no puede montar otra guía
|
|
@@ -3546,6 +4539,7 @@ var GuidePreviewController = class {
|
|
|
3546
4539
|
// Preview de forms: por defecto simula el envío SIN escribir props;
|
|
3547
4540
|
// el caller puede pasar `formSubmit` real (link de preview).
|
|
3548
4541
|
onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
|
|
4542
|
+
...this.input?.fetchProgress ? { fetchProgress: this.input.fetchProgress } : {},
|
|
3549
4543
|
isPreview: true,
|
|
3550
4544
|
...this.input?.formSubmit && this.input.formSubmitNote !== void 0 ? { previewNote: this.input.formSubmitNote } : {}
|
|
3551
4545
|
});
|
|
@@ -3723,9 +4717,35 @@ function matchesUrl(matcher, pageUrl) {
|
|
|
3723
4717
|
}
|
|
3724
4718
|
}
|
|
3725
4719
|
function stepMatchesPage(loc, pageUrl) {
|
|
3726
|
-
if (!loc || loc.scope === "sitewide"
|
|
4720
|
+
if (!loc || loc.scope === "sitewide") return true;
|
|
4721
|
+
if (loc.scope === "pages") {
|
|
4722
|
+
const rules = loc.rules ?? [];
|
|
4723
|
+
return rules.some((r) => pageRuleMatches(r, pageUrl));
|
|
4724
|
+
}
|
|
4725
|
+
if (!loc.pattern) return true;
|
|
3727
4726
|
return matchesUrl({ type: loc.matchType ?? "prefix", pattern: loc.pattern }, pageUrl);
|
|
3728
4727
|
}
|
|
4728
|
+
function pageRuleMatches(rule, pageUrl) {
|
|
4729
|
+
const pattern = rule.pattern.toLowerCase();
|
|
4730
|
+
const candidates = urlCandidates(pageUrl).map((c) => c.toLowerCase());
|
|
4731
|
+
switch (rule.type) {
|
|
4732
|
+
case "exact":
|
|
4733
|
+
return candidates.some((c) => c === pattern);
|
|
4734
|
+
case "prefix":
|
|
4735
|
+
return candidates.some((c) => c.startsWith(pattern));
|
|
4736
|
+
case "contains":
|
|
4737
|
+
return candidates.some((c) => c.includes(pattern));
|
|
4738
|
+
case "regex":
|
|
4739
|
+
try {
|
|
4740
|
+
const re = new RegExp(rule.pattern, "i");
|
|
4741
|
+
return candidates.some((c) => re.test(c));
|
|
4742
|
+
} catch {
|
|
4743
|
+
return false;
|
|
4744
|
+
}
|
|
4745
|
+
default:
|
|
4746
|
+
return false;
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
3729
4749
|
|
|
3730
4750
|
// src/plugins/builder/bridge-protocol.ts
|
|
3731
4751
|
var VEO_BUILDER_SOURCE = "veo-builder";
|
|
@@ -3742,6 +4762,9 @@ var ICONS = {
|
|
|
3742
4762
|
chevronDown: '<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>',
|
|
3743
4763
|
chevronUp: '<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/>',
|
|
3744
4764
|
plus: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>',
|
|
4765
|
+
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"/>',
|
|
4766
|
+
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"/>',
|
|
4767
|
+
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"/>',
|
|
3745
4768
|
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"/>',
|
|
3746
4769
|
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"/>'
|
|
3747
4770
|
};
|
|
@@ -3927,8 +4950,14 @@ function injectBuilderChrome(opts) {
|
|
|
3927
4950
|
stepsList.append(activation);
|
|
3928
4951
|
s.steps.forEach((st, i) => {
|
|
3929
4952
|
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>';
|
|
3930
|
-
const
|
|
3931
|
-
card
|
|
4953
|
+
const isStepView = s.section === "step" || s.section === "block" || s.section === "add-block" || s.section === "row" || s.section === "structure";
|
|
4954
|
+
const card = stepCard(thumb, st.label, isStepView && s.activeStep === i, {
|
|
4955
|
+
// Se pueden borrar TODOS los pasos (el panel vuelve a Activación).
|
|
4956
|
+
canRemove: true,
|
|
4957
|
+
onEdit: () => act({ action: "select-step", index: i }),
|
|
4958
|
+
onDuplicate: () => act({ action: "duplicate-step", index: i }),
|
|
4959
|
+
onRemove: () => act({ action: "remove-step", index: i })
|
|
4960
|
+
});
|
|
3932
4961
|
stepsList.append(card);
|
|
3933
4962
|
});
|
|
3934
4963
|
const add = el("button", "veo-step-add", `${svg("plus", 18)}<span>Agregar paso</span>`);
|
|
@@ -3957,16 +4986,40 @@ function injectBuilderChrome(opts) {
|
|
|
3957
4986
|
}
|
|
3958
4987
|
};
|
|
3959
4988
|
}
|
|
3960
|
-
function stepCard(inner, label, active) {
|
|
3961
|
-
const
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
4989
|
+
function stepCard(inner, label, active, actions) {
|
|
4990
|
+
const cls = `veo-step${active ? " is-active" : ""}`;
|
|
4991
|
+
if (!actions) {
|
|
4992
|
+
const b = el(
|
|
4993
|
+
"button",
|
|
4994
|
+
cls,
|
|
4995
|
+
`<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
|
|
4996
|
+
);
|
|
4997
|
+
b.type = "button";
|
|
4998
|
+
b.querySelector(".veo-step-label").textContent = label;
|
|
4999
|
+
b.setAttribute("aria-pressed", active ? "true" : "false");
|
|
5000
|
+
return b;
|
|
5001
|
+
}
|
|
5002
|
+
const card = el(
|
|
5003
|
+
"div",
|
|
5004
|
+
cls,
|
|
5005
|
+
`<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>`
|
|
3965
5006
|
);
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
5007
|
+
card.querySelector(".veo-step-label").textContent = label;
|
|
5008
|
+
card.setAttribute("aria-current", active ? "step" : "false");
|
|
5009
|
+
const remove = card.querySelector(".veo-step-remove");
|
|
5010
|
+
remove.disabled = !actions.canRemove;
|
|
5011
|
+
if (!actions.canRemove) remove.title = "La gu\xEDa necesita al menos un paso";
|
|
5012
|
+
const on = (sel, fn) => {
|
|
5013
|
+
card.querySelector(sel)?.addEventListener("click", (e) => {
|
|
5014
|
+
e.stopPropagation();
|
|
5015
|
+
fn();
|
|
5016
|
+
});
|
|
5017
|
+
};
|
|
5018
|
+
on(".veo-step-edit", actions.onEdit);
|
|
5019
|
+
on(".veo-step-duplicate", actions.onDuplicate);
|
|
5020
|
+
on(".veo-step-remove", actions.onRemove);
|
|
5021
|
+
card.addEventListener("click", actions.onEdit);
|
|
5022
|
+
return card;
|
|
3970
5023
|
}
|
|
3971
5024
|
var CHROME_CSS = `
|
|
3972
5025
|
:host { all: initial; }
|
|
@@ -4051,10 +5104,31 @@ var CHROME_CSS = `
|
|
|
4051
5104
|
.veo-steps-list { display: flex; align-items: flex-start; gap: 20px; }
|
|
4052
5105
|
.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; }
|
|
4053
5106
|
.veo-step-box {
|
|
5107
|
+
position: relative; overflow: hidden;
|
|
4054
5108
|
width: 116px; height: 64px; border-radius: 10px; background: #fff; border: 2px solid transparent;
|
|
4055
5109
|
display: flex; align-items: center; justify-content: center; color: #2b2623;
|
|
4056
5110
|
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
4057
5111
|
}
|
|
5112
|
+
/* Capa de acciones del paso: aparece al pasar el mouse o al enfocar un bot\xF3n. */
|
|
5113
|
+
.veo-step-hover {
|
|
5114
|
+
position: absolute; inset: 0; background: #2b2623; border-radius: 8px;
|
|
5115
|
+
display: flex; flex-direction: column; padding: 3px 4px 6px; box-sizing: border-box;
|
|
5116
|
+
opacity: 0; transition: opacity 120ms ease-out;
|
|
5117
|
+
}
|
|
5118
|
+
.veo-step:hover .veo-step-hover, .veo-step:focus-within .veo-step-hover { opacity: 1; }
|
|
5119
|
+
.veo-step-hover-top { display: flex; justify-content: space-between; align-items: center; }
|
|
5120
|
+
.veo-step-hover-main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
|
5121
|
+
.veo-step-icon {
|
|
5122
|
+
border: 0; background: transparent; color: #fff; cursor: pointer; padding: 0;
|
|
5123
|
+
width: 20px; height: 20px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center;
|
|
5124
|
+
}
|
|
5125
|
+
.veo-step-icon:hover { background: rgba(255,255,255,0.14); }
|
|
5126
|
+
.veo-step-icon:disabled { opacity: 0.35; cursor: default; background: transparent; }
|
|
5127
|
+
.veo-step-edit {
|
|
5128
|
+
width: 100%; margin: 0 6px; border: 0; background: #dbeefc; color: #1d6fb8; font: inherit; font-size: 13px; font-weight: 600;
|
|
5129
|
+
border-radius: 6px; padding: 5px 10px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
5130
|
+
}
|
|
5131
|
+
.veo-step-edit:hover { background: #cbe4f9; }
|
|
4058
5132
|
.veo-step.is-active .veo-step-box { border-color: #b74d00; }
|
|
4059
5133
|
.veo-step-label { font-size: 13px; color: #6b635d; }
|
|
4060
5134
|
.veo-step.is-active .veo-step-label { color: #2b2623; font-weight: 600; }
|
|
@@ -4097,6 +5171,8 @@ function injectBuilderPanel(opts) {
|
|
|
4097
5171
|
},
|
|
4098
5172
|
isVisible: () => false,
|
|
4099
5173
|
setTopOffset: () => {
|
|
5174
|
+
},
|
|
5175
|
+
setSize: () => {
|
|
4100
5176
|
}
|
|
4101
5177
|
};
|
|
4102
5178
|
}
|
|
@@ -4149,6 +5225,21 @@ function injectBuilderPanel(opts) {
|
|
|
4149
5225
|
card.style.display = next && !(popped && !popped.closed) ? "" : "none";
|
|
4150
5226
|
};
|
|
4151
5227
|
minimize.addEventListener("click", () => setVisible(false));
|
|
5228
|
+
let compactRestore = null;
|
|
5229
|
+
const setSize = (size) => {
|
|
5230
|
+
if (maximized || popped && !popped.closed) return;
|
|
5231
|
+
if (size) {
|
|
5232
|
+
if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
|
|
5233
|
+
card.style.minHeight = "0";
|
|
5234
|
+
card.style.width = `${clamp3(Math.round(size.width), MIN_W, window.innerWidth - MARGIN * 2)}px`;
|
|
5235
|
+
card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top - MARGIN)}px`;
|
|
5236
|
+
} else if (compactRestore) {
|
|
5237
|
+
card.style.minHeight = "";
|
|
5238
|
+
card.style.width = `${compactRestore.w}px`;
|
|
5239
|
+
card.style.height = `${compactRestore.h}px`;
|
|
5240
|
+
compactRestore = null;
|
|
5241
|
+
}
|
|
5242
|
+
};
|
|
4152
5243
|
maximize.addEventListener("click", () => {
|
|
4153
5244
|
maximized = !maximized;
|
|
4154
5245
|
if (maximized) {
|
|
@@ -4248,6 +5339,7 @@ function injectBuilderPanel(opts) {
|
|
|
4248
5339
|
title.textContent = t;
|
|
4249
5340
|
},
|
|
4250
5341
|
setVisible,
|
|
5342
|
+
setSize,
|
|
4251
5343
|
isVisible: () => visible,
|
|
4252
5344
|
setTopOffset: (px) => {
|
|
4253
5345
|
topOffset = px;
|
|
@@ -4894,6 +5986,9 @@ function initBuilderMode() {
|
|
|
4894
5986
|
let staticTarget = null;
|
|
4895
5987
|
let manipulator = null;
|
|
4896
5988
|
let dropTarget = null;
|
|
5989
|
+
let cardClick = null;
|
|
5990
|
+
let rawBlock = null;
|
|
5991
|
+
let selectedBlockId = null;
|
|
4897
5992
|
let dragGhost = null;
|
|
4898
5993
|
let previewStepIndex = 0;
|
|
4899
5994
|
let pendingPreview = null;
|
|
@@ -4902,6 +5997,7 @@ function initBuilderMode() {
|
|
|
4902
5997
|
let previewTrigger = null;
|
|
4903
5998
|
let previewFired = false;
|
|
4904
5999
|
let previewDelay = null;
|
|
6000
|
+
let previewDismissed = false;
|
|
4905
6001
|
let panelWasVisible = true;
|
|
4906
6002
|
const getTarget = () => panel ? panel.target() : staticTarget;
|
|
4907
6003
|
const post = (event) => {
|
|
@@ -4913,15 +6009,751 @@ function initBuilderMode() {
|
|
|
4913
6009
|
};
|
|
4914
6010
|
const previewMatchesPage = (cmd) => stepMatchesPage(previewStep(cmd)?.pageLocation, window.location.href);
|
|
4915
6011
|
const postPreviewStatus = (payload) => post({ type: "preview-status", payload });
|
|
4916
|
-
const
|
|
6012
|
+
const detachPreviewTools = () => {
|
|
4917
6013
|
manipulator?.detach();
|
|
4918
6014
|
manipulator = null;
|
|
4919
6015
|
dropTarget?.detach();
|
|
4920
6016
|
dropTarget = null;
|
|
6017
|
+
cardClick?.();
|
|
6018
|
+
cardClick = null;
|
|
6019
|
+
rowHover?.();
|
|
6020
|
+
rowHover = null;
|
|
6021
|
+
hoverRowId = null;
|
|
6022
|
+
hoverCard = false;
|
|
6023
|
+
rawBlock?.detach();
|
|
6024
|
+
rawBlock = null;
|
|
6025
|
+
shownPreview = null;
|
|
6026
|
+
};
|
|
6027
|
+
const withholdPreview = () => {
|
|
6028
|
+
detachPreviewTools();
|
|
6029
|
+
shownPreview = null;
|
|
4921
6030
|
closeGuidePreview();
|
|
4922
6031
|
};
|
|
6032
|
+
const FRAME_PAD = 4;
|
|
6033
|
+
const FRAME_MIN_H = 0;
|
|
6034
|
+
const TOOLBAR_H = 30;
|
|
6035
|
+
const ICON = {
|
|
6036
|
+
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>',
|
|
6037
|
+
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>',
|
|
6038
|
+
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>'
|
|
6039
|
+
};
|
|
6040
|
+
const ICON_MOVE = {
|
|
6041
|
+
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>',
|
|
6042
|
+
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>'
|
|
6043
|
+
};
|
|
6044
|
+
const moveAbility = (blocks, blockId) => {
|
|
6045
|
+
for (const row of groupLayout(blocks)) {
|
|
6046
|
+
for (let c = 0; c < row.columns.length; c++) {
|
|
6047
|
+
const col = row.columns[c];
|
|
6048
|
+
if (!col.blocks.some((b) => b.block.editorId === blockId)) continue;
|
|
6049
|
+
return { left: c > 0, right: c < row.columns.length - 1 };
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
return { left: false, right: false };
|
|
6053
|
+
};
|
|
6054
|
+
let previewBlocks = [];
|
|
6055
|
+
let hoverRowId = null;
|
|
6056
|
+
let selectedRowId = null;
|
|
6057
|
+
let rowHover = null;
|
|
6058
|
+
let hoverCard = false;
|
|
6059
|
+
let panelSection = null;
|
|
6060
|
+
let shownPreview = null;
|
|
6061
|
+
const structureOf = (step) => {
|
|
6062
|
+
if (!step) return "";
|
|
6063
|
+
const { blocks, ...rest } = step;
|
|
6064
|
+
return JSON.stringify({
|
|
6065
|
+
rest,
|
|
6066
|
+
blocks: (blocks ?? []).map((b) => {
|
|
6067
|
+
const { text: _t, html: _h, style: _s, ...keep } = b;
|
|
6068
|
+
return keep;
|
|
6069
|
+
})
|
|
6070
|
+
});
|
|
6071
|
+
};
|
|
6072
|
+
const tryPatchPreview = (cmd) => {
|
|
6073
|
+
const prev = shownPreview;
|
|
6074
|
+
if (!prev || cmd.guide.guideType !== "composite" || prev.guide.guideType !== "composite") {
|
|
6075
|
+
return false;
|
|
6076
|
+
}
|
|
6077
|
+
const idx = cmd.guide.startStepIndex ?? 0;
|
|
6078
|
+
if ((prev.guide.startStepIndex ?? 0) !== idx) return false;
|
|
6079
|
+
const guideSig = (g) => JSON.stringify({ ...g, guideSteps: void 0 });
|
|
6080
|
+
if (guideSig(prev.guide) !== guideSig(cmd.guide) || prev.editable !== cmd.editable)
|
|
6081
|
+
return false;
|
|
6082
|
+
const host = document.querySelector("[data-veo-guide]");
|
|
6083
|
+
const card = host ? previewCard(host) : null;
|
|
6084
|
+
if (!host || !card) return false;
|
|
6085
|
+
const prevStep = prev.guide.guideSteps[idx];
|
|
6086
|
+
const nextStep = cmd.guide.guideSteps[idx];
|
|
6087
|
+
if (!prevStep || !nextStep) return false;
|
|
6088
|
+
if (structureOf(prevStep) !== structureOf(nextStep)) return false;
|
|
6089
|
+
const doc = card.ownerDocument;
|
|
6090
|
+
const prevBlocks = prevStep.blocks ?? [];
|
|
6091
|
+
const nextBlocks = nextStep.blocks ?? [];
|
|
6092
|
+
for (let i = 0; i < nextBlocks.length; i++) {
|
|
6093
|
+
const a = prevBlocks[i];
|
|
6094
|
+
const b = nextBlocks[i];
|
|
6095
|
+
if (a.text === b.text && a.html === b.html && JSON.stringify(a.style) === JSON.stringify(b.style)) {
|
|
6096
|
+
continue;
|
|
6097
|
+
}
|
|
6098
|
+
if (!b.editorId) return false;
|
|
6099
|
+
const node = card.querySelector(`[data-veo-block-id="${b.editorId}"]`);
|
|
6100
|
+
if (!node) return false;
|
|
6101
|
+
if (b.type === "button") {
|
|
6102
|
+
node.textContent = b.text?.trim() || "OK";
|
|
6103
|
+
node.removeAttribute("style");
|
|
6104
|
+
node.classList.remove("veo-guide-cta--secondary", "veo-guide-cta--link");
|
|
6105
|
+
const st = b.style ?? {};
|
|
6106
|
+
if (st.preset === "secondary" || st.preset === "link") {
|
|
6107
|
+
node.classList.add(`veo-guide-cta--${st.preset}`);
|
|
6108
|
+
}
|
|
6109
|
+
if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
|
|
6110
|
+
node.setAttribute("aria-label", st.ariaLabel.trim());
|
|
6111
|
+
} else node.removeAttribute("aria-label");
|
|
6112
|
+
applyBlockStyle(node, b.style, "button");
|
|
6113
|
+
continue;
|
|
6114
|
+
}
|
|
6115
|
+
if (b.type === "code" || b.type === "form") return false;
|
|
6116
|
+
const fresh = buildContentBlock(b, doc, { editing: true }) ?? (() => {
|
|
6117
|
+
const el2 = doc.createElement("div");
|
|
6118
|
+
el2.className = "veo-block-placeholder";
|
|
6119
|
+
el2.textContent = b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo";
|
|
6120
|
+
return el2;
|
|
6121
|
+
})();
|
|
6122
|
+
fresh.dataset.veoBlock = node.dataset.veoBlock ?? String(i);
|
|
6123
|
+
fresh.dataset.veoBlockId = b.editorId;
|
|
6124
|
+
node.replaceWith(fresh);
|
|
6125
|
+
}
|
|
6126
|
+
shownPreview = cmd;
|
|
6127
|
+
previewBlocks = nextBlocks;
|
|
6128
|
+
applyBlockHighlight(host, idx);
|
|
6129
|
+
renderGapZones(host, idx, nextBlocks);
|
|
6130
|
+
return true;
|
|
6131
|
+
};
|
|
6132
|
+
const previewCard = (host) => host.shadowRoot?.querySelector(".veo-card") ?? null;
|
|
6133
|
+
const clearBlockHighlight = (host) => {
|
|
6134
|
+
const card = previewCard(host);
|
|
6135
|
+
if (!card) return;
|
|
6136
|
+
for (const el2 of card.querySelectorAll(".veo-block--selected")) {
|
|
6137
|
+
el2.classList.remove("veo-block--selected");
|
|
6138
|
+
}
|
|
6139
|
+
card.querySelector(".veo-block-toolbar")?.remove();
|
|
6140
|
+
card.querySelector(".veo-block-frame")?.remove();
|
|
6141
|
+
};
|
|
6142
|
+
const applyBlockHighlight = (host, stepIndex) => {
|
|
6143
|
+
clearBlockHighlight(host);
|
|
6144
|
+
const card = previewCard(host);
|
|
6145
|
+
if (!card) return;
|
|
6146
|
+
const el2 = selectedBlockId ? card.querySelector(`[data-veo-block-id="${selectedBlockId}"]`) : null;
|
|
6147
|
+
if (!el2 || !selectedBlockId) {
|
|
6148
|
+
renderRowChrome(host, stepIndex);
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
el2.classList.add("veo-block--selected");
|
|
6152
|
+
const doc = card.ownerDocument;
|
|
6153
|
+
const cs = getComputedStyle(card);
|
|
6154
|
+
const padL = Number.parseFloat(cs.paddingLeft) || 0;
|
|
6155
|
+
const padT = Number.parseFloat(cs.paddingTop) || 0;
|
|
6156
|
+
const padR = Number.parseFloat(cs.paddingRight) || 0;
|
|
6157
|
+
const padB = Number.parseFloat(cs.paddingBottom) || 0;
|
|
6158
|
+
const parent = el2.parentElement;
|
|
6159
|
+
const inCol = parent?.classList.contains("veo-col") ? parent : null;
|
|
6160
|
+
const boxL = inCol ? inCol.offsetLeft : padL;
|
|
6161
|
+
const boxR = inCol ? inCol.offsetLeft + inCol.offsetWidth : card.clientWidth - padR;
|
|
6162
|
+
const boxT = padT;
|
|
6163
|
+
const boxB = card.clientHeight - padB;
|
|
6164
|
+
const fx = Math.max(boxL, el2.offsetLeft - FRAME_PAD);
|
|
6165
|
+
const fr = Math.min(boxR, el2.offsetLeft + el2.offsetWidth + FRAME_PAD);
|
|
6166
|
+
const fw = Math.max(0, fr - fx);
|
|
6167
|
+
const fy = Math.max(boxT, el2.offsetTop - FRAME_PAD);
|
|
6168
|
+
const fb = Math.min(boxB, el2.offsetTop + el2.offsetHeight + FRAME_PAD);
|
|
6169
|
+
const fh = Math.max(FRAME_MIN_H, fb - fy);
|
|
6170
|
+
const frame = doc.createElement("div");
|
|
6171
|
+
frame.className = "veo-block-frame";
|
|
6172
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6173
|
+
frame.style.left = `${fx}px`;
|
|
6174
|
+
frame.style.top = `${fy}px`;
|
|
6175
|
+
frame.style.width = `${fw}px`;
|
|
6176
|
+
frame.style.height = `${fh}px`;
|
|
6177
|
+
card.appendChild(frame);
|
|
6178
|
+
const bar = doc.createElement("div");
|
|
6179
|
+
bar.className = "veo-block-toolbar";
|
|
6180
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6181
|
+
const blockId = selectedBlockId;
|
|
6182
|
+
const mk = (action, label, icon, enabled = true) => {
|
|
6183
|
+
const b = doc.createElement("button");
|
|
6184
|
+
b.type = "button";
|
|
6185
|
+
b.title = label;
|
|
6186
|
+
b.setAttribute("aria-label", label);
|
|
6187
|
+
b.className = `veo-block-toolbar-${action}`;
|
|
6188
|
+
b.innerHTML = icon;
|
|
6189
|
+
b.disabled = !enabled;
|
|
6190
|
+
b.addEventListener("click", (e) => {
|
|
6191
|
+
e.preventDefault();
|
|
6192
|
+
e.stopPropagation();
|
|
6193
|
+
post({ type: "block-action", payload: { stepIndex, blockId, action } });
|
|
6194
|
+
});
|
|
6195
|
+
bar.appendChild(b);
|
|
6196
|
+
};
|
|
6197
|
+
const can = moveAbility(previewBlocks, blockId);
|
|
6198
|
+
mk("edit", "Editar", ICON.pencil);
|
|
6199
|
+
mk("duplicate", "Duplicar", ICON.copy);
|
|
6200
|
+
mk("remove", "Eliminar", ICON.trash);
|
|
6201
|
+
mk("move-left", "Mover a la izquierda", ICON_MOVE.left, can.left);
|
|
6202
|
+
mk("move-right", "Mover a la derecha", ICON_MOVE.right, can.right);
|
|
6203
|
+
bar.style.right = `${Math.max(0, card.clientWidth - fr)}px`;
|
|
6204
|
+
card.appendChild(bar);
|
|
6205
|
+
const barH = bar.offsetHeight || TOOLBAR_H;
|
|
6206
|
+
bar.dataset.veoTopAbove = String(fy - barH - 6);
|
|
6207
|
+
bar.dataset.veoTopBelow = String(fb + 6);
|
|
6208
|
+
bar.style.top = bar.dataset.veoTopAbove;
|
|
6209
|
+
renderRowChrome(host, stepIndex);
|
|
6210
|
+
};
|
|
6211
|
+
const chromeRect = (el2) => ({
|
|
6212
|
+
left: el2.offsetLeft,
|
|
6213
|
+
top: el2.offsetTop,
|
|
6214
|
+
right: el2.offsetLeft + el2.offsetWidth,
|
|
6215
|
+
bottom: el2.offsetTop + el2.offsetHeight
|
|
6216
|
+
});
|
|
6217
|
+
const intersects = (a, b) => a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
|
|
6218
|
+
const resolveToolbarOverlaps = (card) => {
|
|
6219
|
+
const cardBar = card.querySelector(".veo-card-toolbar");
|
|
6220
|
+
const blockBar = card.querySelector(".veo-block-toolbar");
|
|
6221
|
+
const rowBar = card.querySelector(".veo-row-toolbar");
|
|
6222
|
+
if (blockBar?.dataset.veoTopAbove) {
|
|
6223
|
+
blockBar.style.top = `${blockBar.dataset.veoTopAbove}px`;
|
|
6224
|
+
if (cardBar && intersects(chromeRect(blockBar), chromeRect(cardBar))) {
|
|
6225
|
+
blockBar.style.top = `${blockBar.dataset.veoTopBelow}px`;
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
if (rowBar?.dataset.veoRight) {
|
|
6229
|
+
rowBar.style.left = "";
|
|
6230
|
+
rowBar.style.right = `${rowBar.dataset.veoRight}px`;
|
|
6231
|
+
rowBar.style.top = `${rowBar.dataset.veoTopBelow}px`;
|
|
6232
|
+
const others = [cardBar, blockBar].filter((b) => !!b);
|
|
6233
|
+
const clashes = () => others.some((o) => intersects(chromeRect(rowBar), chromeRect(o)));
|
|
6234
|
+
if (clashes()) {
|
|
6235
|
+
rowBar.style.right = "";
|
|
6236
|
+
rowBar.style.left = `${rowBar.dataset.veoLeft}px`;
|
|
6237
|
+
}
|
|
6238
|
+
if (clashes() && blockBar) {
|
|
6239
|
+
rowBar.style.left = "";
|
|
6240
|
+
rowBar.style.right = `${rowBar.dataset.veoRight}px`;
|
|
6241
|
+
rowBar.style.top = `${chromeRect(blockBar).bottom + 6}px`;
|
|
6242
|
+
}
|
|
6243
|
+
}
|
|
6244
|
+
};
|
|
6245
|
+
const rectOf = (el2) => ({
|
|
6246
|
+
left: el2.offsetLeft,
|
|
6247
|
+
top: el2.offsetTop,
|
|
6248
|
+
right: el2.offsetLeft + el2.offsetWidth,
|
|
6249
|
+
bottom: el2.offsetTop + el2.offsetHeight
|
|
6250
|
+
});
|
|
6251
|
+
const union = (rs) => rs.length === 0 ? null : rs.reduce((acc, r) => ({
|
|
6252
|
+
left: Math.min(acc.left, r.left),
|
|
6253
|
+
top: Math.min(acc.top, r.top),
|
|
6254
|
+
right: Math.max(acc.right, r.right),
|
|
6255
|
+
bottom: Math.max(acc.bottom, r.bottom)
|
|
6256
|
+
}));
|
|
6257
|
+
const layoutRows = (card, blocks) => {
|
|
6258
|
+
const elOf = (b) => b.editorId ? card.querySelector(`[data-veo-block-id="${b.editorId}"]`) : null;
|
|
6259
|
+
const cs = getComputedStyle(card);
|
|
6260
|
+
const padL = Number.parseFloat(cs.paddingLeft) || 0;
|
|
6261
|
+
const padR = Number.parseFloat(cs.paddingRight) || 0;
|
|
6262
|
+
const contentLeft = padL;
|
|
6263
|
+
const contentRight = card.clientWidth - padR;
|
|
6264
|
+
const rows = groupLayout(blocks).map((row) => {
|
|
6265
|
+
const columns = row.columns.map((col) => {
|
|
6266
|
+
const items = col.blocks.map(({ block }) => ({ block, el: elOf(block) })).filter(
|
|
6267
|
+
(x) => !!x.el && !!x.block.editorId
|
|
6268
|
+
);
|
|
6269
|
+
const blocksRect2 = union(items.map((x) => rectOf(x.el)));
|
|
6270
|
+
if (!blocksRect2) return null;
|
|
6271
|
+
const colEl = items[0]?.el.parentElement;
|
|
6272
|
+
const rect2 = colEl?.classList.contains("veo-col") ? rectOf(colEl) : blocksRect2;
|
|
6273
|
+
return { items, rect: rect2 };
|
|
6274
|
+
}).filter((c) => c !== null);
|
|
6275
|
+
const blocksRect = union(columns.map((c) => c.rect));
|
|
6276
|
+
if (!blocksRect) return null;
|
|
6277
|
+
const rowEl = columns[0]?.items[0]?.el.parentElement?.parentElement;
|
|
6278
|
+
const rect = rowEl?.classList.contains("veo-row") ? rectOf(rowEl) : { ...blocksRect, left: contentLeft, right: contentRight };
|
|
6279
|
+
const id = columns[0]?.items[0]?.block.editorId;
|
|
6280
|
+
return { id, row: row.row, columns, rect };
|
|
6281
|
+
}).filter((r) => r !== null);
|
|
6282
|
+
return { rows, content: { left: contentLeft, right: contentRight } };
|
|
6283
|
+
};
|
|
6284
|
+
const clearRowChrome = (card) => {
|
|
6285
|
+
card.querySelector(".veo-row-frame")?.remove();
|
|
6286
|
+
card.querySelector(".veo-row-toolbar")?.remove();
|
|
6287
|
+
};
|
|
6288
|
+
const renderRowChrome = (host, stepIndex) => {
|
|
6289
|
+
const card = previewCard(host);
|
|
6290
|
+
if (!card) return;
|
|
6291
|
+
clearRowChrome(card);
|
|
6292
|
+
resolveToolbarOverlaps(card);
|
|
6293
|
+
const { rows } = layoutRows(card, previewBlocks);
|
|
6294
|
+
const rowOfSelected = selectedBlockId ? rows.find(
|
|
6295
|
+
(r) => r.columns.some((c) => c.items.some((i) => i.block.editorId === selectedBlockId))
|
|
6296
|
+
) : void 0;
|
|
6297
|
+
const target = (selectedRowId ? rows.find((r) => r.id === selectedRowId) : void 0) ?? rowOfSelected ?? (hoverRowId ? rows.find((r) => r.id === hoverRowId) : void 0);
|
|
6298
|
+
if (!target) return;
|
|
6299
|
+
const doc = card.ownerDocument;
|
|
6300
|
+
const cs = getComputedStyle(card);
|
|
6301
|
+
const padT = Number.parseFloat(cs.paddingTop) || 0;
|
|
6302
|
+
const padB = Number.parseFloat(cs.paddingBottom) || 0;
|
|
6303
|
+
const PAD = 6;
|
|
6304
|
+
const fx = Math.max(0, target.rect.left - PAD);
|
|
6305
|
+
const fr = Math.min(card.clientWidth, target.rect.right + PAD);
|
|
6306
|
+
const fy = Math.max(Math.min(padT, target.rect.top), target.rect.top - PAD);
|
|
6307
|
+
const fb = Math.min(
|
|
6308
|
+
Math.max(card.clientHeight - padB, target.rect.bottom),
|
|
6309
|
+
target.rect.bottom + PAD
|
|
6310
|
+
);
|
|
6311
|
+
const frame = doc.createElement("div");
|
|
6312
|
+
frame.className = "veo-row-frame";
|
|
6313
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6314
|
+
frame.style.left = `${fx}px`;
|
|
6315
|
+
frame.style.top = `${fy}px`;
|
|
6316
|
+
frame.style.width = `${Math.max(0, fr - fx)}px`;
|
|
6317
|
+
frame.style.height = `${Math.max(0, fb - fy)}px`;
|
|
6318
|
+
card.appendChild(frame);
|
|
6319
|
+
const bar = doc.createElement("div");
|
|
6320
|
+
bar.className = "veo-row-toolbar";
|
|
6321
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6322
|
+
const blockId = target.id;
|
|
6323
|
+
const mk = (action, label, icon, text) => {
|
|
6324
|
+
const b = doc.createElement("button");
|
|
6325
|
+
b.type = "button";
|
|
6326
|
+
b.title = label;
|
|
6327
|
+
b.setAttribute("aria-label", label);
|
|
6328
|
+
b.className = `veo-row-toolbar-${action}`;
|
|
6329
|
+
b.innerHTML = icon;
|
|
6330
|
+
if (text) b.append(text);
|
|
6331
|
+
b.addEventListener("click", (e) => {
|
|
6332
|
+
e.preventDefault();
|
|
6333
|
+
e.stopPropagation();
|
|
6334
|
+
post({ type: "row-action", payload: { stepIndex, blockId, action } });
|
|
6335
|
+
});
|
|
6336
|
+
bar.appendChild(b);
|
|
6337
|
+
};
|
|
6338
|
+
mk("edit", "Editar fila", ICON.pencil, "Editar fila");
|
|
6339
|
+
mk("duplicate", "Duplicar fila", ICON.copy);
|
|
6340
|
+
mk("remove", "Eliminar fila", ICON.trash);
|
|
6341
|
+
card.appendChild(bar);
|
|
6342
|
+
bar.dataset.veoRight = String(Math.max(0, card.clientWidth - fr));
|
|
6343
|
+
bar.dataset.veoLeft = String(fx);
|
|
6344
|
+
bar.dataset.veoTopBelow = String(fb + 6);
|
|
6345
|
+
resolveToolbarOverlaps(card);
|
|
6346
|
+
};
|
|
6347
|
+
const renderCardChrome = (host, stepIndex) => {
|
|
6348
|
+
const card = previewCard(host);
|
|
6349
|
+
if (!card) return;
|
|
6350
|
+
card.querySelector(".veo-card-frame")?.remove();
|
|
6351
|
+
card.querySelector(".veo-card-toolbar")?.remove();
|
|
6352
|
+
if (!hoverCard) {
|
|
6353
|
+
resolveToolbarOverlaps(card);
|
|
6354
|
+
return;
|
|
6355
|
+
}
|
|
6356
|
+
const doc = card.ownerDocument;
|
|
6357
|
+
const frame = doc.createElement("div");
|
|
6358
|
+
frame.className = "veo-card-frame";
|
|
6359
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6360
|
+
card.appendChild(frame);
|
|
6361
|
+
const bar = doc.createElement("div");
|
|
6362
|
+
bar.className = "veo-card-toolbar";
|
|
6363
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6364
|
+
const mk = (action, label, active) => {
|
|
6365
|
+
const b = doc.createElement("button");
|
|
6366
|
+
b.type = "button";
|
|
6367
|
+
b.className = `veo-card-toolbar-${action}${active ? " is-active" : ""}`;
|
|
6368
|
+
b.setAttribute("aria-pressed", active ? "true" : "false");
|
|
6369
|
+
b.textContent = label;
|
|
6370
|
+
b.addEventListener("click", (e) => {
|
|
6371
|
+
e.preventDefault();
|
|
6372
|
+
e.stopPropagation();
|
|
6373
|
+
post({ type: "card-action", payload: { stepIndex, action } });
|
|
6374
|
+
});
|
|
6375
|
+
bar.appendChild(b);
|
|
6376
|
+
};
|
|
6377
|
+
mk("edit-container", "Editar contenedor", panelSection === "step");
|
|
6378
|
+
mk("edit-rows", "Editar filas", panelSection === "structure");
|
|
6379
|
+
card.appendChild(bar);
|
|
6380
|
+
bar.style.top = `${-Math.round((bar.offsetHeight || 34) / 2)}px`;
|
|
6381
|
+
resolveToolbarOverlaps(card);
|
|
6382
|
+
};
|
|
6383
|
+
const attachRowHover = (host, stepIndex) => {
|
|
6384
|
+
const onMove = (e) => {
|
|
6385
|
+
const card = previewCard(host);
|
|
6386
|
+
if (!card) return;
|
|
6387
|
+
const overCard = e.composedPath().some(
|
|
6388
|
+
(n) => n instanceof Element && (n.classList.contains("veo-card") || n.classList.contains("veo-card-toolbar"))
|
|
6389
|
+
);
|
|
6390
|
+
if (overCard !== hoverCard) {
|
|
6391
|
+
hoverCard = overCard;
|
|
6392
|
+
renderCardChrome(host, stepIndex);
|
|
6393
|
+
}
|
|
6394
|
+
if (e.composedPath().some((n) => n instanceof Element && n.classList.contains("veo-row-toolbar"))) {
|
|
6395
|
+
return;
|
|
6396
|
+
}
|
|
6397
|
+
const rect = card.getBoundingClientRect();
|
|
6398
|
+
const x = e.clientX - rect.left - card.clientLeft;
|
|
6399
|
+
const y = e.clientY - rect.top - card.clientTop;
|
|
6400
|
+
const bar = card.querySelector(".veo-row-toolbar");
|
|
6401
|
+
if (bar) {
|
|
6402
|
+
const AIR = 10;
|
|
6403
|
+
if (x >= bar.offsetLeft - AIR && x <= bar.offsetLeft + bar.offsetWidth + AIR && y >= bar.offsetTop - 14 && y <= bar.offsetTop + bar.offsetHeight + AIR) {
|
|
6404
|
+
return;
|
|
6405
|
+
}
|
|
6406
|
+
}
|
|
6407
|
+
const TOL = 6;
|
|
6408
|
+
const hit = layoutRows(card, previewBlocks).rows.find(
|
|
6409
|
+
(r) => x >= r.rect.left - TOL && x <= r.rect.right + TOL && y >= r.rect.top - TOL && y <= r.rect.bottom + TOL
|
|
6410
|
+
);
|
|
6411
|
+
const next = hit?.id ?? null;
|
|
6412
|
+
if (next === hoverRowId) return;
|
|
6413
|
+
hoverRowId = next;
|
|
6414
|
+
renderRowChrome(host, stepIndex);
|
|
6415
|
+
};
|
|
6416
|
+
const onLeave = () => {
|
|
6417
|
+
if (hoverCard) {
|
|
6418
|
+
hoverCard = false;
|
|
6419
|
+
renderCardChrome(host, stepIndex);
|
|
6420
|
+
}
|
|
6421
|
+
if (hoverRowId === null) return;
|
|
6422
|
+
hoverRowId = null;
|
|
6423
|
+
renderRowChrome(host, stepIndex);
|
|
6424
|
+
};
|
|
6425
|
+
host.addEventListener("mousemove", onMove);
|
|
6426
|
+
host.addEventListener("mouseleave", onLeave);
|
|
6427
|
+
return () => {
|
|
6428
|
+
host.removeEventListener("mousemove", onMove);
|
|
6429
|
+
host.removeEventListener("mouseleave", onLeave);
|
|
6430
|
+
};
|
|
6431
|
+
};
|
|
6432
|
+
const renderGapZones = (host, stepIndex, blocks) => {
|
|
6433
|
+
const card = previewCard(host);
|
|
6434
|
+
if (!card) return;
|
|
6435
|
+
for (const z of card.querySelectorAll(".veo-gap")) z.remove();
|
|
6436
|
+
const doc = card.ownerDocument;
|
|
6437
|
+
const GAP = 18;
|
|
6438
|
+
const EDGE2 = 4;
|
|
6439
|
+
const zone = (kind, x, y, w, h, label, blockId, action) => {
|
|
6440
|
+
const z = doc.createElement("div");
|
|
6441
|
+
z.className = `veo-gap veo-gap--${kind}`;
|
|
6442
|
+
z.setAttribute("data-veo-builder-chrome", "");
|
|
6443
|
+
z.dataset.veoGapAction = action;
|
|
6444
|
+
z.style.left = `${x}px`;
|
|
6445
|
+
z.style.top = `${y}px`;
|
|
6446
|
+
z.style.width = `${Math.max(w, GAP)}px`;
|
|
6447
|
+
z.style.height = `${Math.max(h, GAP)}px`;
|
|
6448
|
+
const line = doc.createElement("i");
|
|
6449
|
+
const btn = doc.createElement("button");
|
|
6450
|
+
btn.type = "button";
|
|
6451
|
+
btn.title = label;
|
|
6452
|
+
btn.setAttribute("aria-label", label);
|
|
6453
|
+
btn.textContent = "+";
|
|
6454
|
+
btn.addEventListener("click", (e) => {
|
|
6455
|
+
e.preventDefault();
|
|
6456
|
+
e.stopPropagation();
|
|
6457
|
+
post({ type: "block-layout", payload: { stepIndex, blockId, action } });
|
|
6458
|
+
});
|
|
6459
|
+
z.append(line, btn);
|
|
6460
|
+
card.appendChild(z);
|
|
6461
|
+
};
|
|
6462
|
+
const { rows, content } = layoutRows(card, blocks);
|
|
6463
|
+
if (rows.length === 0) return;
|
|
6464
|
+
const cw = content.right - content.left;
|
|
6465
|
+
rows.forEach((row, k) => {
|
|
6466
|
+
const first = row.columns[0]?.items[0]?.block.editorId;
|
|
6467
|
+
if (k === 0) {
|
|
6468
|
+
zone(
|
|
6469
|
+
"h",
|
|
6470
|
+
content.left,
|
|
6471
|
+
row.rect.top - EDGE2 - GAP,
|
|
6472
|
+
cw,
|
|
6473
|
+
GAP,
|
|
6474
|
+
"Agregar fila",
|
|
6475
|
+
first,
|
|
6476
|
+
"row-before"
|
|
6477
|
+
);
|
|
6478
|
+
} else {
|
|
6479
|
+
const prev = rows[k - 1];
|
|
6480
|
+
const y = (prev.rect.bottom + row.rect.top) / 2 - GAP / 2;
|
|
6481
|
+
zone("h", content.left, y, cw, GAP, "Agregar fila", first, "row-before");
|
|
6482
|
+
}
|
|
6483
|
+
if (k === rows.length - 1) {
|
|
6484
|
+
zone(
|
|
6485
|
+
"h",
|
|
6486
|
+
content.left,
|
|
6487
|
+
row.rect.bottom + EDGE2,
|
|
6488
|
+
cw,
|
|
6489
|
+
GAP,
|
|
6490
|
+
"Agregar fila",
|
|
6491
|
+
first,
|
|
6492
|
+
"row-after"
|
|
6493
|
+
);
|
|
6494
|
+
}
|
|
6495
|
+
});
|
|
6496
|
+
for (const row of rows) {
|
|
6497
|
+
const rh = row.rect.bottom - row.rect.top;
|
|
6498
|
+
row.columns.forEach((col, j) => {
|
|
6499
|
+
const id = col.items[0]?.block.editorId;
|
|
6500
|
+
if (j === 0) {
|
|
6501
|
+
zone(
|
|
6502
|
+
"v",
|
|
6503
|
+
row.rect.left - EDGE2 - GAP,
|
|
6504
|
+
row.rect.top,
|
|
6505
|
+
GAP,
|
|
6506
|
+
rh,
|
|
6507
|
+
"Agregar columna",
|
|
6508
|
+
id,
|
|
6509
|
+
"col-before"
|
|
6510
|
+
);
|
|
6511
|
+
} else {
|
|
6512
|
+
const prev = row.columns[j - 1];
|
|
6513
|
+
const x = (prev.rect.right + col.rect.left) / 2 - GAP / 2;
|
|
6514
|
+
zone("v", x, row.rect.top, GAP, rh, "Agregar columna", id, "col-before");
|
|
6515
|
+
}
|
|
6516
|
+
if (j === row.columns.length - 1) {
|
|
6517
|
+
zone(
|
|
6518
|
+
"v",
|
|
6519
|
+
row.rect.right + EDGE2,
|
|
6520
|
+
row.rect.top,
|
|
6521
|
+
GAP,
|
|
6522
|
+
rh,
|
|
6523
|
+
"Agregar columna",
|
|
6524
|
+
id,
|
|
6525
|
+
"col-after"
|
|
6526
|
+
);
|
|
6527
|
+
}
|
|
6528
|
+
});
|
|
6529
|
+
}
|
|
6530
|
+
for (const row of rows) {
|
|
6531
|
+
for (const col of row.columns) {
|
|
6532
|
+
const w = col.rect.right - col.rect.left;
|
|
6533
|
+
col.items.forEach((it, i) => {
|
|
6534
|
+
const next = col.items[i + 1];
|
|
6535
|
+
if (next) {
|
|
6536
|
+
const a = rectOf(it.el);
|
|
6537
|
+
const b = rectOf(next.el);
|
|
6538
|
+
const y = (a.bottom + b.top) / 2 - GAP / 2;
|
|
6539
|
+
zone(
|
|
6540
|
+
"h",
|
|
6541
|
+
col.rect.left,
|
|
6542
|
+
y,
|
|
6543
|
+
w,
|
|
6544
|
+
GAP,
|
|
6545
|
+
"Agregar bloque",
|
|
6546
|
+
it.block.editorId,
|
|
6547
|
+
"stack-after"
|
|
6548
|
+
);
|
|
6549
|
+
} else if (row.columns.length > 1 && col.rect.bottom < row.rect.bottom - GAP) {
|
|
6550
|
+
zone(
|
|
6551
|
+
"h",
|
|
6552
|
+
col.rect.left,
|
|
6553
|
+
col.rect.bottom + EDGE2,
|
|
6554
|
+
w,
|
|
6555
|
+
GAP,
|
|
6556
|
+
"Agregar bloque",
|
|
6557
|
+
it.block.editorId,
|
|
6558
|
+
"stack-after"
|
|
6559
|
+
);
|
|
6560
|
+
}
|
|
6561
|
+
});
|
|
6562
|
+
}
|
|
6563
|
+
}
|
|
6564
|
+
};
|
|
6565
|
+
const attachCardClick = (host, stepIndex) => {
|
|
6566
|
+
let downAt = null;
|
|
6567
|
+
const onDown = (e) => {
|
|
6568
|
+
downAt = { x: e.clientX, y: e.clientY };
|
|
6569
|
+
};
|
|
6570
|
+
const onClick = (e) => {
|
|
6571
|
+
const path = e.composedPath();
|
|
6572
|
+
if (path.some(
|
|
6573
|
+
(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"))
|
|
6574
|
+
)) {
|
|
6575
|
+
return;
|
|
6576
|
+
}
|
|
6577
|
+
const inCard = path.some((n) => n instanceof Element && n.classList.contains("veo-card"));
|
|
6578
|
+
if (!inCard) return;
|
|
6579
|
+
if (downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 4) return;
|
|
6580
|
+
const blockEl = path.find(
|
|
6581
|
+
(n) => n instanceof HTMLElement && !!n.dataset.veoBlockId
|
|
6582
|
+
);
|
|
6583
|
+
if (blockEl) {
|
|
6584
|
+
if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
|
|
6585
|
+
e.preventDefault();
|
|
6586
|
+
e.stopPropagation();
|
|
6587
|
+
}
|
|
6588
|
+
selectedBlockId = blockEl.dataset.veoBlockId ?? null;
|
|
6589
|
+
applyBlockHighlight(host, stepIndex);
|
|
6590
|
+
if (selectedBlockId) {
|
|
6591
|
+
post({ type: "block-clicked", payload: { stepIndex, blockId: selectedBlockId } });
|
|
6592
|
+
}
|
|
6593
|
+
return;
|
|
6594
|
+
}
|
|
6595
|
+
const interactive = path.some(
|
|
6596
|
+
(n) => n instanceof Element && n.matches("button, a, input, textarea, select, label, [contenteditable]")
|
|
6597
|
+
);
|
|
6598
|
+
if (interactive) return;
|
|
6599
|
+
selectedBlockId = null;
|
|
6600
|
+
clearBlockHighlight(host);
|
|
6601
|
+
renderRowChrome(host, stepIndex);
|
|
6602
|
+
post({ type: "card-clicked", payload: { stepIndex } });
|
|
6603
|
+
};
|
|
6604
|
+
host.addEventListener("mousedown", onDown, true);
|
|
6605
|
+
host.addEventListener("click", onClick, true);
|
|
6606
|
+
return () => {
|
|
6607
|
+
host.removeEventListener("mousedown", onDown, true);
|
|
6608
|
+
host.removeEventListener("click", onClick, true);
|
|
6609
|
+
};
|
|
6610
|
+
};
|
|
6611
|
+
const attachRawBlock = (stepIndex, blockId) => {
|
|
6612
|
+
const nodes = Array.from(
|
|
6613
|
+
document.querySelectorAll(`[${RAW_BLOCK_ATTR}="${blockId}"]`)
|
|
6614
|
+
);
|
|
6615
|
+
if (nodes.length === 0) return null;
|
|
6616
|
+
const doc = document;
|
|
6617
|
+
const box = doc.createElement("div");
|
|
6618
|
+
box.setAttribute("data-veo-builder-chrome", "");
|
|
6619
|
+
box.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;z-index:2147483646;";
|
|
6620
|
+
box.style.display = "none";
|
|
6621
|
+
const shadow = box.attachShadow({ mode: "open" });
|
|
6622
|
+
const style = doc.createElement("style");
|
|
6623
|
+
style.textContent = GUIDE_STYLES;
|
|
6624
|
+
shadow.appendChild(style);
|
|
6625
|
+
const frame = doc.createElement("div");
|
|
6626
|
+
frame.className = "veo-block-frame";
|
|
6627
|
+
frame.style.position = "fixed";
|
|
6628
|
+
const bar = doc.createElement("div");
|
|
6629
|
+
bar.className = "veo-block-toolbar";
|
|
6630
|
+
bar.style.position = "fixed";
|
|
6631
|
+
const mk = (action, label, icon, enabled = true) => {
|
|
6632
|
+
const b = doc.createElement("button");
|
|
6633
|
+
b.type = "button";
|
|
6634
|
+
b.title = label;
|
|
6635
|
+
b.setAttribute("aria-label", label);
|
|
6636
|
+
b.className = `veo-block-toolbar-${action}`;
|
|
6637
|
+
b.innerHTML = icon;
|
|
6638
|
+
b.disabled = !enabled;
|
|
6639
|
+
b.addEventListener("click", (e) => {
|
|
6640
|
+
e.preventDefault();
|
|
6641
|
+
e.stopPropagation();
|
|
6642
|
+
post({ type: "block-action", payload: { stepIndex, blockId, action } });
|
|
6643
|
+
});
|
|
6644
|
+
bar.appendChild(b);
|
|
6645
|
+
};
|
|
6646
|
+
mk("edit", "Editar", ICON.pencil);
|
|
6647
|
+
mk("duplicate", "Duplicar", ICON.copy, false);
|
|
6648
|
+
mk("remove", "Eliminar", ICON.trash);
|
|
6649
|
+
mk("move-left", "Mover a la izquierda", ICON_MOVE.left, false);
|
|
6650
|
+
mk("move-right", "Mover a la derecha", ICON_MOVE.right, false);
|
|
6651
|
+
frame.style.display = "none";
|
|
6652
|
+
bar.style.display = "none";
|
|
6653
|
+
const GAP = 18;
|
|
6654
|
+
const EDGE2 = 4;
|
|
6655
|
+
const gap = (kind, label, action) => {
|
|
6656
|
+
const z = doc.createElement("div");
|
|
6657
|
+
z.className = `veo-gap veo-gap--${kind}`;
|
|
6658
|
+
z.style.position = "fixed";
|
|
6659
|
+
z.dataset.veoGapAction = action;
|
|
6660
|
+
const line = doc.createElement("i");
|
|
6661
|
+
const btn = doc.createElement("button");
|
|
6662
|
+
btn.type = "button";
|
|
6663
|
+
btn.title = label;
|
|
6664
|
+
btn.setAttribute("aria-label", label);
|
|
6665
|
+
btn.textContent = "+";
|
|
6666
|
+
btn.addEventListener("click", (e) => {
|
|
6667
|
+
e.preventDefault();
|
|
6668
|
+
e.stopPropagation();
|
|
6669
|
+
post({ type: "block-layout", payload: { stepIndex, blockId, action } });
|
|
6670
|
+
});
|
|
6671
|
+
z.append(line, btn);
|
|
6672
|
+
return z;
|
|
6673
|
+
};
|
|
6674
|
+
const gaps = {
|
|
6675
|
+
top: gap("h", "Agregar fila", "row-before"),
|
|
6676
|
+
bottom: gap("h", "Agregar fila", "row-after"),
|
|
6677
|
+
left: gap("v", "Agregar columna", "col-before"),
|
|
6678
|
+
right: gap("v", "Agregar columna", "col-after")
|
|
6679
|
+
};
|
|
6680
|
+
shadow.append(frame, bar, gaps.top, gaps.bottom, gaps.left, gaps.right);
|
|
6681
|
+
doc.body.appendChild(box);
|
|
6682
|
+
box.style.display = "";
|
|
6683
|
+
let visible = false;
|
|
6684
|
+
const place = (el2, x, y, w, h) => {
|
|
6685
|
+
el2.style.left = `${x}px`;
|
|
6686
|
+
el2.style.top = `${y}px`;
|
|
6687
|
+
el2.style.width = `${w}px`;
|
|
6688
|
+
el2.style.height = `${h}px`;
|
|
6689
|
+
};
|
|
6690
|
+
const reposition = () => {
|
|
6691
|
+
const live = nodes.filter((n) => n.isConnected);
|
|
6692
|
+
if (live.length === 0) return;
|
|
6693
|
+
let l = Number.POSITIVE_INFINITY;
|
|
6694
|
+
let t = Number.POSITIVE_INFINITY;
|
|
6695
|
+
let r = Number.NEGATIVE_INFINITY;
|
|
6696
|
+
let b = Number.NEGATIVE_INFINITY;
|
|
6697
|
+
for (const n of live) {
|
|
6698
|
+
const rc = n.getBoundingClientRect();
|
|
6699
|
+
if (rc.width === 0 && rc.height === 0) continue;
|
|
6700
|
+
l = Math.min(l, rc.left);
|
|
6701
|
+
t = Math.min(t, rc.top);
|
|
6702
|
+
r = Math.max(r, rc.right);
|
|
6703
|
+
b = Math.max(b, rc.bottom);
|
|
6704
|
+
}
|
|
6705
|
+
if (!Number.isFinite(l)) return;
|
|
6706
|
+
place(gaps.top, l, t - EDGE2 - GAP, r - l, GAP);
|
|
6707
|
+
place(gaps.bottom, l, b + EDGE2, r - l, GAP);
|
|
6708
|
+
place(gaps.left, l - EDGE2 - GAP, t, GAP, b - t);
|
|
6709
|
+
place(gaps.right, r + EDGE2, t, GAP, b - t);
|
|
6710
|
+
if (!visible) return;
|
|
6711
|
+
place(frame, l, t, r - l, b - t);
|
|
6712
|
+
const bh = bar.offsetHeight || TOOLBAR_H;
|
|
6713
|
+
const bw = bar.offsetWidth;
|
|
6714
|
+
bar.style.left = `${Math.max(0, r - bw)}px`;
|
|
6715
|
+
bar.style.top = `${Math.max(0, t - bh - 6)}px`;
|
|
6716
|
+
};
|
|
6717
|
+
const select = (id) => {
|
|
6718
|
+
visible = id === blockId;
|
|
6719
|
+
frame.style.display = visible ? "" : "none";
|
|
6720
|
+
bar.style.display = visible ? "" : "none";
|
|
6721
|
+
reposition();
|
|
6722
|
+
};
|
|
6723
|
+
reposition();
|
|
6724
|
+
const onClick = (e) => {
|
|
6725
|
+
const path = e.composedPath();
|
|
6726
|
+
if (!nodes.some((n) => path.includes(n))) return;
|
|
6727
|
+
if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
|
|
6728
|
+
e.preventDefault();
|
|
6729
|
+
e.stopPropagation();
|
|
6730
|
+
}
|
|
6731
|
+
selectedBlockId = blockId;
|
|
6732
|
+
select(blockId);
|
|
6733
|
+
post({ type: "block-clicked", payload: { stepIndex, blockId } });
|
|
6734
|
+
};
|
|
6735
|
+
doc.addEventListener("click", onClick, true);
|
|
6736
|
+
window.addEventListener("scroll", reposition, true);
|
|
6737
|
+
window.addEventListener("resize", reposition);
|
|
6738
|
+
return {
|
|
6739
|
+
select,
|
|
6740
|
+
detach: () => {
|
|
6741
|
+
doc.removeEventListener("click", onClick, true);
|
|
6742
|
+
window.removeEventListener("scroll", reposition, true);
|
|
6743
|
+
window.removeEventListener("resize", reposition);
|
|
6744
|
+
box.remove();
|
|
6745
|
+
}
|
|
6746
|
+
};
|
|
6747
|
+
};
|
|
4923
6748
|
const applyPreviewGated = (cmd) => {
|
|
4924
|
-
|
|
6749
|
+
const showNow = !cmd.honorTrigger;
|
|
6750
|
+
if (showNow) {
|
|
6751
|
+
disarmPreviewTrigger();
|
|
6752
|
+
cancelPreviewDelay();
|
|
6753
|
+
previewFired = true;
|
|
6754
|
+
previewDismissed = false;
|
|
6755
|
+
}
|
|
6756
|
+
lastPreview = showNow ? { ...cmd, honorTrigger: true } : cmd;
|
|
4925
6757
|
if (!previewMatchesPage(cmd)) {
|
|
4926
6758
|
cancelPreviewDelay();
|
|
4927
6759
|
withholdPreview();
|
|
@@ -4932,13 +6764,13 @@ function initBuilderMode() {
|
|
|
4932
6764
|
return;
|
|
4933
6765
|
}
|
|
4934
6766
|
const devices = cmd.guide.activationRules?.devices;
|
|
4935
|
-
if (
|
|
6767
|
+
if (!showNow && previewMode && devices && !isDeviceAllowed(devices)) {
|
|
4936
6768
|
cancelPreviewDelay();
|
|
4937
6769
|
withholdPreview();
|
|
4938
6770
|
postPreviewStatus({ state: "device-mismatch", devices });
|
|
4939
6771
|
return;
|
|
4940
6772
|
}
|
|
4941
|
-
applyPreview(
|
|
6773
|
+
applyPreview(lastPreview);
|
|
4942
6774
|
};
|
|
4943
6775
|
const disarmPreviewTrigger = () => {
|
|
4944
6776
|
previewTrigger?.();
|
|
@@ -4963,6 +6795,7 @@ function initBuilderMode() {
|
|
|
4963
6795
|
}
|
|
4964
6796
|
disarmPreviewTrigger();
|
|
4965
6797
|
previewFired = true;
|
|
6798
|
+
previewDismissed = false;
|
|
4966
6799
|
if (lastPreview) applyPreviewGated(lastPreview);
|
|
4967
6800
|
};
|
|
4968
6801
|
document.addEventListener("click", onFire, true);
|
|
@@ -4983,22 +6816,24 @@ function initBuilderMode() {
|
|
|
4983
6816
|
postPreviewStatus({ state: "waiting-delay", delayMs });
|
|
4984
6817
|
};
|
|
4985
6818
|
const applyPreview = (cmd) => {
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
6819
|
+
if (cmd.editable && !previewMode && tryPatchPreview(cmd)) {
|
|
6820
|
+
previewStepIndex = cmd.guide.startStepIndex ?? 0;
|
|
6821
|
+
return;
|
|
6822
|
+
}
|
|
6823
|
+
detachPreviewTools();
|
|
4990
6824
|
previewStepIndex = cmd.guide.startStepIndex ?? 0;
|
|
4991
6825
|
const rules = cmd.guide.activationRules;
|
|
4992
6826
|
const trig = rules?.trigger;
|
|
4993
6827
|
const sel = rules?.selector?.trim();
|
|
4994
|
-
const gate = cmd.honorTrigger && !previewFired &&
|
|
4995
|
-
|
|
6828
|
+
const gate = cmd.honorTrigger && !previewFired && previewMode;
|
|
6829
|
+
const waitsForTarget = (trig === "on_click" || trig === "on_hover") && (!isAutomaticActivation(rules) || previewDismissed);
|
|
6830
|
+
if (gate && waitsForTarget && sel) {
|
|
4996
6831
|
cancelPreviewDelay();
|
|
4997
6832
|
closeGuidePreview();
|
|
4998
6833
|
armPreviewTrigger(sel, trig);
|
|
4999
6834
|
return;
|
|
5000
6835
|
}
|
|
5001
|
-
const delayMs =
|
|
6836
|
+
const delayMs = activationDelayMs(rules);
|
|
5002
6837
|
if (gate && delayMs > 0) {
|
|
5003
6838
|
disarmPreviewTrigger();
|
|
5004
6839
|
closeGuidePreview();
|
|
@@ -5007,15 +6842,21 @@ function initBuilderMode() {
|
|
|
5007
6842
|
}
|
|
5008
6843
|
disarmPreviewTrigger();
|
|
5009
6844
|
cancelPreviewDelay();
|
|
6845
|
+
const fetchProgress = previewProgressFetcher();
|
|
5010
6846
|
const handle = previewGuide({
|
|
5011
6847
|
...cmd.guide,
|
|
6848
|
+
...fetchProgress ? { fetchProgress } : {},
|
|
5012
6849
|
onClosed: () => {
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
dropTarget?.detach();
|
|
5016
|
-
dropTarget = null;
|
|
6850
|
+
detachPreviewTools();
|
|
6851
|
+
shownPreview = null;
|
|
5017
6852
|
previewFired = false;
|
|
5018
|
-
|
|
6853
|
+
previewDismissed = true;
|
|
6854
|
+
if (previewMode) {
|
|
6855
|
+
const r = lastPreview?.guide.activationRules;
|
|
6856
|
+
const t = r?.trigger;
|
|
6857
|
+
const selector = r?.selector?.trim();
|
|
6858
|
+
if ((t === "on_click" || t === "on_hover") && selector) armPreviewTrigger(selector, t);
|
|
6859
|
+
}
|
|
5019
6860
|
}
|
|
5020
6861
|
});
|
|
5021
6862
|
if (cmd.honorTrigger) postPreviewStatus({ state: "shown" });
|
|
@@ -5023,7 +6864,23 @@ function initBuilderMode() {
|
|
|
5023
6864
|
void handle.ready.then((result) => {
|
|
5024
6865
|
if (!result.rendered) return;
|
|
5025
6866
|
const host = handle.host();
|
|
5026
|
-
if (!host)
|
|
6867
|
+
if (!host) {
|
|
6868
|
+
const idx = cmd.guide.startStepIndex ?? 0;
|
|
6869
|
+
const only = cmd.guide.guideSteps[idx]?.blocks?.[0];
|
|
6870
|
+
if (only?.type === "code" && only.editorId) {
|
|
6871
|
+
rawBlock = attachRawBlock(idx, only.editorId);
|
|
6872
|
+
rawBlock?.select(selectedBlockId);
|
|
6873
|
+
}
|
|
6874
|
+
return;
|
|
6875
|
+
}
|
|
6876
|
+
host.classList.add("veo-no-anim");
|
|
6877
|
+
shownPreview = cmd;
|
|
6878
|
+
cardClick = attachCardClick(host, cmd.guide.startStepIndex ?? 0);
|
|
6879
|
+
const stepBlocks = cmd.guide.guideSteps[cmd.guide.startStepIndex ?? 0]?.blocks ?? [];
|
|
6880
|
+
previewBlocks = stepBlocks;
|
|
6881
|
+
applyBlockHighlight(host, cmd.guide.startStepIndex ?? 0);
|
|
6882
|
+
renderGapZones(host, cmd.guide.startStepIndex ?? 0, stepBlocks);
|
|
6883
|
+
rowHover = attachRowHover(host, cmd.guide.startStepIndex ?? 0);
|
|
5027
6884
|
manipulator = attachDesignManipulator({
|
|
5028
6885
|
host,
|
|
5029
6886
|
guideType: cmd.guide.guideType,
|
|
@@ -5091,13 +6948,21 @@ function initBuilderMode() {
|
|
|
5091
6948
|
break;
|
|
5092
6949
|
case "editor-state":
|
|
5093
6950
|
chrome?.setState(cmd.state);
|
|
6951
|
+
if (panelSection !== cmd.state.section) {
|
|
6952
|
+
panelSection = cmd.state.section;
|
|
6953
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
6954
|
+
if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
|
|
6955
|
+
}
|
|
5094
6956
|
panel?.setTitle(
|
|
5095
|
-
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"
|
|
6957
|
+
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"
|
|
5096
6958
|
);
|
|
5097
6959
|
break;
|
|
5098
6960
|
case "panel-visibility":
|
|
5099
6961
|
panel?.setVisible(cmd.visible);
|
|
5100
6962
|
break;
|
|
6963
|
+
case "panel-size":
|
|
6964
|
+
panel?.setSize(cmd.size);
|
|
6965
|
+
break;
|
|
5101
6966
|
case "start-picker": {
|
|
5102
6967
|
picker?.stop();
|
|
5103
6968
|
const panelWasShown = panel?.isVisible() ?? false;
|
|
@@ -5105,10 +6970,7 @@ function initBuilderMode() {
|
|
|
5105
6970
|
chrome?.setHidden(true);
|
|
5106
6971
|
const previewToRestore = lastPreview;
|
|
5107
6972
|
if (previewToRestore) {
|
|
5108
|
-
|
|
5109
|
-
manipulator = null;
|
|
5110
|
-
dropTarget?.detach();
|
|
5111
|
-
dropTarget = null;
|
|
6973
|
+
detachPreviewTools();
|
|
5112
6974
|
closeGuidePreview();
|
|
5113
6975
|
}
|
|
5114
6976
|
const restorePanel = () => {
|
|
@@ -5147,17 +7009,28 @@ function initBuilderMode() {
|
|
|
5147
7009
|
case "block-drag":
|
|
5148
7010
|
handleBlockDrag(cmd);
|
|
5149
7011
|
break;
|
|
7012
|
+
case "select-block": {
|
|
7013
|
+
selectedBlockId = cmd.blockId;
|
|
7014
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7015
|
+
if (h && !previewMode) applyBlockHighlight(h, previewStepIndex);
|
|
7016
|
+
if (!previewMode) rawBlock?.select(cmd.blockId);
|
|
7017
|
+
break;
|
|
7018
|
+
}
|
|
7019
|
+
case "select-row": {
|
|
7020
|
+
selectedRowId = cmd.blockId;
|
|
7021
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7022
|
+
if (h && !previewMode) renderRowChrome(h, previewStepIndex);
|
|
7023
|
+
break;
|
|
7024
|
+
}
|
|
5150
7025
|
case "close-preview":
|
|
5151
|
-
|
|
5152
|
-
manipulator = null;
|
|
5153
|
-
dropTarget?.detach();
|
|
5154
|
-
dropTarget = null;
|
|
7026
|
+
detachPreviewTools();
|
|
5155
7027
|
hideDragGhost();
|
|
5156
7028
|
pendingPreview = null;
|
|
5157
7029
|
lastPreview = null;
|
|
5158
7030
|
disarmPreviewTrigger();
|
|
5159
7031
|
cancelPreviewDelay();
|
|
5160
7032
|
previewFired = false;
|
|
7033
|
+
previewDismissed = false;
|
|
5161
7034
|
closeGuidePreview();
|
|
5162
7035
|
break;
|
|
5163
7036
|
case "teardown":
|
|
@@ -5203,10 +7076,7 @@ function initBuilderMode() {
|
|
|
5203
7076
|
removeNavWatcher();
|
|
5204
7077
|
picker?.stop();
|
|
5205
7078
|
picker = null;
|
|
5206
|
-
|
|
5207
|
-
manipulator = null;
|
|
5208
|
-
dropTarget?.detach();
|
|
5209
|
-
dropTarget = null;
|
|
7079
|
+
detachPreviewTools();
|
|
5210
7080
|
hideDragGhost();
|
|
5211
7081
|
pendingPreview = null;
|
|
5212
7082
|
disarmPreviewTrigger();
|
|
@@ -5234,6 +7104,8 @@ function initBuilderMode() {
|
|
|
5234
7104
|
case "preview":
|
|
5235
7105
|
if (previewMode) return;
|
|
5236
7106
|
previewMode = true;
|
|
7107
|
+
previewFired = false;
|
|
7108
|
+
previewDismissed = false;
|
|
5237
7109
|
panelWasVisible = panel?.isVisible() ?? true;
|
|
5238
7110
|
panel?.setVisible(false);
|
|
5239
7111
|
chrome?.setPreview(true);
|
|
@@ -5242,6 +7114,8 @@ function initBuilderMode() {
|
|
|
5242
7114
|
case "exit-preview":
|
|
5243
7115
|
if (!previewMode) return;
|
|
5244
7116
|
previewMode = false;
|
|
7117
|
+
disarmPreviewTrigger();
|
|
7118
|
+
cancelPreviewDelay();
|
|
5245
7119
|
chrome?.setPreview(false);
|
|
5246
7120
|
panel?.setVisible(panelWasVisible);
|
|
5247
7121
|
if (lastPreview) applyPreviewGated(lastPreview);
|
|
@@ -5323,6 +7197,25 @@ function resolveDashboardOrigin(originHint) {
|
|
|
5323
7197
|
}
|
|
5324
7198
|
return null;
|
|
5325
7199
|
}
|
|
7200
|
+
function previewProgressFetcher() {
|
|
7201
|
+
const cfg = window.__veoBuilder;
|
|
7202
|
+
if (!cfg?.apiKey || !cfg?.apiUrl) return void 0;
|
|
7203
|
+
const apiUrl = cfg.apiUrl.replace(/\/+$/, "");
|
|
7204
|
+
const apiKey = cfg.apiKey;
|
|
7205
|
+
return async (guideIds) => {
|
|
7206
|
+
const params = new URLSearchParams({
|
|
7207
|
+
endUserId: "veo_preview",
|
|
7208
|
+
guideIds: guideIds.join(","),
|
|
7209
|
+
includeUnpublished: "1"
|
|
7210
|
+
});
|
|
7211
|
+
const res = await fetch(`${apiUrl}/v1/guides/progress?${params}`, {
|
|
7212
|
+
headers: { "X-Api-Key": apiKey }
|
|
7213
|
+
});
|
|
7214
|
+
if (!res.ok) return [];
|
|
7215
|
+
const data = await res.json();
|
|
7216
|
+
return data.items ?? [];
|
|
7217
|
+
};
|
|
7218
|
+
}
|
|
5326
7219
|
|
|
5327
7220
|
// src/plugins/builder/builder-session.ts
|
|
5328
7221
|
var DEFAULT_FEATURES = "popup,width=1280,height=860";
|