veo-sdk 0.5.34 → 0.6.1
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-BKPMANUB.mjs +5 -0
- package/dist/{builder-ZKWVTSTP.mjs.map → builder-BKPMANUB.mjs.map} +1 -1
- package/dist/builder.cjs +2501 -570
- 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-HAAT6XVN.mjs → chunk-GVPAJFNW.mjs} +97 -10
- package/dist/chunk-GVPAJFNW.mjs.map +1 -0
- package/dist/{chunk-W4R4XCWL.mjs → chunk-NGP7IJVE.mjs} +1196 -105
- package/dist/chunk-NGP7IJVE.mjs.map +1 -0
- package/dist/{chunk-IZXJA55N.mjs → chunk-VWF4UHUU.mjs} +895 -41
- package/dist/chunk-VWF4UHUU.mjs.map +1 -0
- package/dist/{guide-preview-DbEZdGBV.d.cts → guide-preview-DylsEhrg.d.cts} +98 -5
- package/dist/{guide-preview-DbEZdGBV.d.ts → guide-preview-DylsEhrg.d.ts} +98 -5
- package/dist/index.cjs +2221 -151
- 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-ZKWVTSTP.mjs +0 -5
- package/dist/chunk-HAAT6XVN.mjs.map +0 -1
- package/dist/chunk-IZXJA55N.mjs.map +0 -1
- package/dist/chunk-W4R4XCWL.mjs.map +0 -1
package/dist/builder.cjs
CHANGED
|
@@ -417,6 +417,125 @@ function rebuildNode(node, doc, depth, budget) {
|
|
|
417
417
|
return rebuilt;
|
|
418
418
|
}
|
|
419
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
|
+
|
|
420
539
|
// src/plugins/guides/block-builder.ts
|
|
421
540
|
function buildStepContent(step, doc, callbacks) {
|
|
422
541
|
const container = doc.createElement("div");
|
|
@@ -541,8 +660,12 @@ function renderBlocks(container, blocks, doc, callbacks) {
|
|
|
541
660
|
i++;
|
|
542
661
|
}
|
|
543
662
|
}
|
|
544
|
-
function buildContentBlock(block, doc) {
|
|
663
|
+
function buildContentBlock(block, doc, opts = {}) {
|
|
545
664
|
switch (block.type) {
|
|
665
|
+
case "video":
|
|
666
|
+
return buildVideoBlock(block, doc, opts);
|
|
667
|
+
case "divider":
|
|
668
|
+
return buildDividerBlock(block, doc);
|
|
546
669
|
case "title": {
|
|
547
670
|
const el2 = doc.createElement("h2");
|
|
548
671
|
el2.className = "veo-guide-title";
|
|
@@ -566,7 +689,7 @@ function buildContentBlock(block, doc) {
|
|
|
566
689
|
const el2 = doc.createElement("img");
|
|
567
690
|
el2.className = "veo-guide-image";
|
|
568
691
|
el2.src = block.url;
|
|
569
|
-
el2.alt = "";
|
|
692
|
+
el2.alt = typeof block.alt === "string" ? block.alt : "";
|
|
570
693
|
applyBlockStyle(el2, block.style, "image");
|
|
571
694
|
return el2;
|
|
572
695
|
}
|
|
@@ -574,6 +697,32 @@ function buildContentBlock(block, doc) {
|
|
|
574
697
|
return null;
|
|
575
698
|
}
|
|
576
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
|
+
}
|
|
577
726
|
function buildButtonBlock(block, doc, callbacks) {
|
|
578
727
|
const btn = doc.createElement("button");
|
|
579
728
|
btn.type = "button";
|
|
@@ -600,6 +749,12 @@ var BLOCK_ALIGN_SELF = {
|
|
|
600
749
|
function clampNum(n, min, max) {
|
|
601
750
|
return Math.min(max, Math.max(min, n));
|
|
602
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
|
+
};
|
|
603
758
|
function applyBlockStyle(el2, style, kind) {
|
|
604
759
|
if (!style || typeof style !== "object") return;
|
|
605
760
|
const s = style;
|
|
@@ -609,14 +764,34 @@ function applyBlockStyle(el2, style, kind) {
|
|
|
609
764
|
}
|
|
610
765
|
const align = typeof s.align === "string" ? s.align : null;
|
|
611
766
|
if (align && (align === "left" || align === "center" || align === "right")) {
|
|
612
|
-
if (kind === "text")
|
|
613
|
-
|
|
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";
|
|
614
784
|
}
|
|
615
785
|
if (kind !== "image" && typeof s.color === "string") el2.style.color = s.color;
|
|
616
786
|
if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
|
|
617
787
|
el2.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
|
|
618
788
|
}
|
|
619
|
-
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)) {
|
|
620
795
|
el2.style.width = `${clampNum(s.width, 10, 100)}%`;
|
|
621
796
|
}
|
|
622
797
|
if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
|
|
@@ -696,67 +871,326 @@ function readCtaUrl(step) {
|
|
|
696
871
|
return isSafeUrl(candidate) ? candidate : void 0;
|
|
697
872
|
}
|
|
698
873
|
|
|
699
|
-
// src/
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
};
|
|
711
|
-
var ALIGN_SELF = {
|
|
712
|
-
left: "flex-start",
|
|
713
|
-
center: "center",
|
|
714
|
-
right: "flex-end"
|
|
715
|
-
};
|
|
716
|
-
var ELEMENT_ALIGN_MODE = {
|
|
717
|
-
title: "text",
|
|
718
|
-
text: "text",
|
|
719
|
-
image: "self"
|
|
720
|
-
};
|
|
721
|
-
var SHADOW = {
|
|
722
|
-
none: "none",
|
|
723
|
-
soft: "0 4px 14px rgba(0, 0, 0, 0.10)",
|
|
724
|
-
medium: "0 8px 30px rgba(0, 0, 0, 0.18)",
|
|
725
|
-
strong: "0 24px 60px rgba(0, 0, 0, 0.32)"
|
|
726
|
-
};
|
|
727
|
-
var PRESET_POS = {
|
|
728
|
-
center: [0.5, 0.5],
|
|
729
|
-
top: [0.5, 0],
|
|
730
|
-
bottom: [0.5, 1],
|
|
731
|
-
left: [0, 0.5],
|
|
732
|
-
right: [1, 0.5],
|
|
733
|
-
"top-left": [0, 0],
|
|
734
|
-
"top-right": [1, 0],
|
|
735
|
-
"bottom-left": [0, 1],
|
|
736
|
-
"bottom-right": [1, 1]
|
|
737
|
-
};
|
|
738
|
-
var COLOR_RE = /^#[0-9a-f]{3,8}$|^(rgb|rgba|hsl|hsla)\([\d.,%/\sdeg]+\)$|^[a-z]{3,20}$/i;
|
|
739
|
-
function clamp(n, min, max) {
|
|
740
|
-
return Math.min(max, Math.max(min, n));
|
|
741
|
-
}
|
|
742
|
-
function applyDesignVars(host, style) {
|
|
743
|
-
if (!style || typeof style !== "object") return;
|
|
744
|
-
const s = style;
|
|
745
|
-
if (typeof s.accentColor === "string" && COLOR_RE.test(s.accentColor.trim())) {
|
|
746
|
-
host.style.setProperty("--veo-primary", s.accentColor.trim());
|
|
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
|
+
}
|
|
747
885
|
}
|
|
748
|
-
|
|
749
|
-
|
|
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("-");
|
|
896
|
+
}
|
|
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;
|
|
750
983
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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}"`);
|
|
754
994
|
}
|
|
995
|
+
return attrs.join(" ");
|
|
996
|
+
} catch {
|
|
997
|
+
return "";
|
|
755
998
|
}
|
|
756
|
-
|
|
757
|
-
|
|
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;
|
|
1045
|
+
}
|
|
758
1046
|
}
|
|
759
|
-
|
|
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());
|
|
1192
|
+
}
|
|
1193
|
+
if (typeof s.textColor === "string" && COLOR_RE.test(s.textColor.trim())) {
|
|
760
1194
|
host.style.setProperty("--veo-text", s.textColor.trim());
|
|
761
1195
|
}
|
|
762
1196
|
if (typeof s.textSecondaryColor === "string" && COLOR_RE.test(s.textSecondaryColor.trim())) {
|
|
@@ -1037,7 +1471,161 @@ var GUIDE_STYLES = `
|
|
|
1037
1471
|
}
|
|
1038
1472
|
/* Card gen\xE9rica del modelo unificado. El dise\xF1o (bg/borde/padding/\u2026) lo aplica
|
|
1039
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; }
|
|
1040
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;
|
|
1041
1629
|
pointer-events: auto;
|
|
1042
1630
|
background: var(--veo-bg, #ffffff);
|
|
1043
1631
|
color: var(--veo-text, #1f2937);
|
|
@@ -1130,6 +1718,13 @@ var GUIDE_STYLES = `
|
|
|
1130
1718
|
transition: opacity 120ms ease;
|
|
1131
1719
|
}
|
|
1132
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; }
|
|
1133
1728
|
.veo-guide-cta:active { transform: translateY(1px); }
|
|
1134
1729
|
.veo-guide-close {
|
|
1135
1730
|
position: absolute; top: -6px; right: -6px;
|
|
@@ -1160,6 +1755,10 @@ var GUIDE_STYLES = `
|
|
|
1160
1755
|
|
|
1161
1756
|
.veo-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
|
|
1162
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; }
|
|
1163
1762
|
.veo-form-label { font-size: 13px; font-weight: 500; color: var(--veo-text); }
|
|
1164
1763
|
.veo-form-input {
|
|
1165
1764
|
font-size: 14px; color: var(--veo-text);
|
|
@@ -1168,26 +1767,71 @@ var GUIDE_STYLES = `
|
|
|
1168
1767
|
padding: 8px 10px; width: 100%;
|
|
1169
1768
|
}
|
|
1170
1769
|
.veo-form-input:focus { outline: 2px solid var(--veo-primary); outline-offset: -1px; border-color: transparent; }
|
|
1171
|
-
|
|
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; }
|
|
1172
1804
|
.veo-form-option {
|
|
1173
|
-
display: flex; align-items: center; gap:
|
|
1174
|
-
font-size:
|
|
1175
|
-
}
|
|
1176
|
-
.veo-form-option input {
|
|
1177
|
-
|
|
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; }
|
|
1178
1819
|
.veo-nps-btn {
|
|
1179
|
-
min-width:
|
|
1180
|
-
font-size:
|
|
1181
|
-
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;
|
|
1182
1823
|
cursor: pointer; transition: all 100ms ease;
|
|
1183
1824
|
}
|
|
1184
|
-
.veo-nps-btn:hover { border-color: var(--veo-primary); }
|
|
1825
|
+
.veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-primary)); }
|
|
1185
1826
|
.veo-rating-btn {
|
|
1186
1827
|
background: none; border: none; padding: 0 2px;
|
|
1187
1828
|
font-size: 26px; line-height: 1; color: #d1d5db;
|
|
1188
1829
|
cursor: pointer; transition: color 100ms ease;
|
|
1189
1830
|
}
|
|
1190
|
-
.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
|
+
}
|
|
1191
1835
|
.veo-scale-active.veo-rating-btn { color: #f59e0b; }
|
|
1192
1836
|
.veo-form-error { font-size: 13px; color: #dc2626; margin: 0; }
|
|
1193
1837
|
.veo-form-thanks {
|
|
@@ -1700,397 +2344,364 @@ function buildBadgeElement(config, doc) {
|
|
|
1700
2344
|
} else {
|
|
1701
2345
|
btn.className = "veo-badge veo-badge--icon";
|
|
1702
2346
|
btn.textContent = "?";
|
|
1703
|
-
btn.style.width = `${size}px`;
|
|
1704
|
-
btn.style.height = `${size}px`;
|
|
1705
|
-
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1706
|
-
}
|
|
1707
|
-
break;
|
|
1708
|
-
}
|
|
1709
|
-
default: {
|
|
1710
|
-
btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
|
|
1711
|
-
btn.style.width = `${size}px`;
|
|
1712
|
-
btn.style.height = `${size}px`;
|
|
1713
|
-
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1714
|
-
if (config.color) btn.style.color = config.color;
|
|
1715
|
-
break;
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
return btn;
|
|
1719
|
-
}
|
|
1720
|
-
|
|
1721
|
-
// src/plugins/guides/walkthrough-block-builder.ts
|
|
1722
|
-
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
1723
|
-
const container = doc.createElement("div");
|
|
1724
|
-
container.className = "veo-guide-content";
|
|
1725
|
-
const counter = doc.createElement("div");
|
|
1726
|
-
counter.className = "veo-walkthrough-counter";
|
|
1727
|
-
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
1728
|
-
container.appendChild(counter);
|
|
1729
|
-
const progress = doc.createElement("div");
|
|
1730
|
-
progress.className = "veo-walkthrough-progress";
|
|
1731
|
-
for (let i = 0; i < totalSteps; i++) {
|
|
1732
|
-
const dot = doc.createElement("span");
|
|
1733
|
-
dot.className = "veo-walkthrough-progress-dot";
|
|
1734
|
-
if (i < stepIndex) dot.classList.add("completed");
|
|
1735
|
-
if (i === stepIndex) dot.classList.add("active");
|
|
1736
|
-
progress.appendChild(dot);
|
|
1737
|
-
}
|
|
1738
|
-
container.appendChild(progress);
|
|
1739
|
-
for (const node of buildContentNodes(step, doc)) {
|
|
1740
|
-
container.appendChild(node);
|
|
1741
|
-
}
|
|
1742
|
-
const actions = doc.createElement("div");
|
|
1743
|
-
actions.className = "veo-walkthrough-actions";
|
|
1744
|
-
const skipBtn = doc.createElement("button");
|
|
1745
|
-
skipBtn.type = "button";
|
|
1746
|
-
skipBtn.className = "veo-walkthrough-skip";
|
|
1747
|
-
skipBtn.textContent = "Omitir";
|
|
1748
|
-
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
1749
|
-
actions.appendChild(skipBtn);
|
|
1750
|
-
const rightGroup = doc.createElement("div");
|
|
1751
|
-
rightGroup.className = "veo-walkthrough-actions-right";
|
|
1752
|
-
if (stepIndex > 0) {
|
|
1753
|
-
const backBtn = doc.createElement("button");
|
|
1754
|
-
backBtn.type = "button";
|
|
1755
|
-
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
1756
|
-
backBtn.textContent = "Atr\xE1s";
|
|
1757
|
-
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
1758
|
-
rightGroup.appendChild(backBtn);
|
|
1759
|
-
}
|
|
1760
|
-
const isLastStep = stepIndex === totalSteps - 1;
|
|
1761
|
-
const primaryBtn = doc.createElement("button");
|
|
1762
|
-
primaryBtn.type = "button";
|
|
1763
|
-
primaryBtn.className = "veo-guide-cta";
|
|
1764
|
-
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
1765
|
-
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
1766
|
-
primaryBtn.addEventListener("click", () => {
|
|
1767
|
-
if (isLastStep) callbacks.onComplete();
|
|
1768
|
-
else callbacks.onNext();
|
|
1769
|
-
});
|
|
1770
|
-
rightGroup.appendChild(primaryBtn);
|
|
1771
|
-
actions.appendChild(rightGroup);
|
|
1772
|
-
container.appendChild(actions);
|
|
1773
|
-
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
1774
|
-
return container;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
// src/plugins/guides/renderers/banner-renderer.ts
|
|
1778
|
-
var BannerRenderer = class extends BaseRenderer {
|
|
1779
|
-
async render(context) {
|
|
1780
|
-
const idx = context.nav?.stepIndex ?? 0;
|
|
1781
|
-
const step = context.guide.guideSteps[idx];
|
|
1782
|
-
if (!step) return;
|
|
1783
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1784
|
-
const selector = step.selector?.trim() || null;
|
|
1785
|
-
let anchor = null;
|
|
1786
|
-
if (selector) {
|
|
1787
|
-
anchor = await waitForElement(selector);
|
|
1788
|
-
if (!anchor) return;
|
|
1789
|
-
}
|
|
1790
|
-
const { host, root } = this.createHost();
|
|
1791
|
-
this.applyDesign(step.style);
|
|
1792
|
-
const ownerDocument = root.ownerDocument ?? document;
|
|
1793
|
-
const banner = ownerDocument.createElement("div");
|
|
1794
|
-
banner.className = bannerClassOf(position, Boolean(anchor));
|
|
1795
|
-
const dismiss = (action, ctaUrl, meta) => {
|
|
1796
|
-
context.onInteraction({
|
|
1797
|
-
guideId: context.guide.guideId,
|
|
1798
|
-
stepIndex: idx,
|
|
1799
|
-
action,
|
|
1800
|
-
...meta ? { metadata: meta } : {}
|
|
1801
|
-
});
|
|
1802
|
-
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1803
|
-
context.onClose();
|
|
1804
|
-
};
|
|
1805
|
-
const content = context.nav ? buildWalkthroughStepContent(
|
|
1806
|
-
step,
|
|
1807
|
-
context.nav.stepIndex,
|
|
1808
|
-
context.nav.totalSteps,
|
|
1809
|
-
ownerDocument,
|
|
1810
|
-
context.nav.callbacks
|
|
1811
|
-
) : buildStepContent(step, ownerDocument, {
|
|
1812
|
-
onCtaClick: (action, target, meta) => {
|
|
1813
|
-
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
1814
|
-
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
1815
|
-
},
|
|
1816
|
-
onDismiss: () => dismiss("dismissed")
|
|
1817
|
-
});
|
|
1818
|
-
banner.appendChild(content);
|
|
1819
|
-
root.appendChild(banner);
|
|
1820
|
-
if (selector && anchor) {
|
|
1821
|
-
host.style.display = "block";
|
|
1822
|
-
const insertAt = position === "top" ? "prepend" : "append";
|
|
1823
|
-
insertHost(anchor, host, insertAt);
|
|
1824
|
-
this.registerCleanup(keepHostAttached(host, selector, insertAt));
|
|
1825
|
-
}
|
|
1826
|
-
if (!context.nav) {
|
|
1827
|
-
this.liveContainer = banner;
|
|
1828
|
-
this.liveContent = content;
|
|
1829
|
-
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1830
|
-
onCtaClick: (action, target, meta) => {
|
|
1831
|
-
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
1832
|
-
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
1833
|
-
},
|
|
1834
|
-
onDismiss: () => dismiss("dismissed")
|
|
1835
|
-
});
|
|
1836
|
-
this.liveKey = this.liveKeyOf(step);
|
|
1837
|
-
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
/** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
|
|
1841
|
-
liveKeyOf(step) {
|
|
1842
|
-
const selector = step.selector?.trim() ?? "";
|
|
1843
|
-
if (!selector) return "";
|
|
1844
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1845
|
-
return `${selector}|${position}`;
|
|
1846
|
-
}
|
|
1847
|
-
onLiveUpdate(step) {
|
|
1848
|
-
if (this.liveContainer) {
|
|
1849
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1850
|
-
const embedded = Boolean(step.selector?.trim());
|
|
1851
|
-
this.liveContainer.className = bannerClassOf(position, embedded);
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
};
|
|
1855
|
-
function bannerClassOf(position, embedded) {
|
|
1856
|
-
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
// src/utils/uuid.ts
|
|
1860
|
-
function uuidv7() {
|
|
1861
|
-
const timestamp = Date.now();
|
|
1862
|
-
const timeHex = timestamp.toString(16).padStart(12, "0");
|
|
1863
|
-
const random = new Uint8Array(10);
|
|
1864
|
-
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
|
1865
|
-
crypto.getRandomValues(random);
|
|
1866
|
-
} else {
|
|
1867
|
-
for (let i = 0; i < random.length; i++) {
|
|
1868
|
-
random[i] = Math.floor(Math.random() * 256);
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
random[0] = (random[0] ?? 0) & 15 | 112;
|
|
1872
|
-
random[2] = (random[2] ?? 0) & 63 | 128;
|
|
1873
|
-
const hex = Array.from(random, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1874
|
-
return [
|
|
1875
|
-
timeHex.slice(0, 8),
|
|
1876
|
-
timeHex.slice(8, 12),
|
|
1877
|
-
hex.slice(0, 4),
|
|
1878
|
-
hex.slice(4, 8),
|
|
1879
|
-
hex.slice(8, 20)
|
|
1880
|
-
].join("-");
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
// src/plugins/guides/renderers/custom-frame.ts
|
|
1884
|
-
function mountCustomFrame(doc, step, context, opts) {
|
|
1885
|
-
const iframe = doc.createElement("iframe");
|
|
1886
|
-
iframe.className = "veo-custom-frame";
|
|
1887
|
-
iframe.setAttribute("sandbox", "allow-scripts");
|
|
1888
|
-
iframe.setAttribute("title", context.guide.guideName || "OnUser");
|
|
1889
|
-
iframe.style.width = opts.width;
|
|
1890
|
-
const hasFixedHeight = opts.fixedHeight !== void 0 && opts.fixedHeight !== null;
|
|
1891
|
-
if (hasFixedHeight) iframe.style.height = toCssSize(opts.fixedHeight);
|
|
1892
|
-
const token = uuidv7();
|
|
1893
|
-
const wantsTailwind = step.style?.tailwind === true;
|
|
1894
|
-
iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token, {
|
|
1895
|
-
hostCss: collectHostStyles(),
|
|
1896
|
-
htmlAttrs: hostHtmlAttrs(),
|
|
1897
|
-
tailwind: wantsTailwind
|
|
1898
|
-
});
|
|
1899
|
-
const close = () => context.onClose();
|
|
1900
|
-
const onMessage = (event) => {
|
|
1901
|
-
if (event.source !== iframe.contentWindow) return;
|
|
1902
|
-
const data = event.data;
|
|
1903
|
-
if (!data || typeof data !== "object" || data.__veo !== token) return;
|
|
1904
|
-
switch (data.type) {
|
|
1905
|
-
case "dismiss":
|
|
1906
|
-
context.onInteraction({
|
|
1907
|
-
guideId: context.guide.guideId,
|
|
1908
|
-
stepIndex: 0,
|
|
1909
|
-
action: "dismissed"
|
|
1910
|
-
});
|
|
1911
|
-
close();
|
|
1912
|
-
break;
|
|
1913
|
-
case "cta":
|
|
1914
|
-
context.onInteraction({
|
|
1915
|
-
guideId: context.guide.guideId,
|
|
1916
|
-
stepIndex: 0,
|
|
1917
|
-
action: "cta_clicked"
|
|
1918
|
-
});
|
|
1919
|
-
if (typeof data.url === "string" && isSafeUrl(data.url)) {
|
|
1920
|
-
window.open(data.url, "_blank", "noopener,noreferrer");
|
|
1921
|
-
}
|
|
1922
|
-
if (data.close !== false) close();
|
|
1923
|
-
break;
|
|
1924
|
-
case "navigate":
|
|
1925
|
-
if (typeof data.url === "string" && isSafeUrl(data.url)) {
|
|
1926
|
-
window.location.assign(data.url);
|
|
1927
|
-
}
|
|
1928
|
-
break;
|
|
1929
|
-
case "track":
|
|
1930
|
-
if (typeof data.name === "string" && context.onTrack) {
|
|
1931
|
-
context.onTrack(data.name, isRecord(data.props) ? data.props : void 0);
|
|
1932
|
-
}
|
|
1933
|
-
break;
|
|
1934
|
-
case "resize":
|
|
1935
|
-
if (!hasFixedHeight && typeof data.height === "number" && data.height > 0) {
|
|
1936
|
-
iframe.style.height = `${Math.ceil(data.height)}px`;
|
|
1937
|
-
}
|
|
1938
|
-
break;
|
|
1939
|
-
}
|
|
1940
|
-
};
|
|
1941
|
-
window.addEventListener("message", onMessage);
|
|
1942
|
-
return { iframe, cleanup: () => window.removeEventListener("message", onMessage) };
|
|
1943
|
-
}
|
|
1944
|
-
function toCssSize(value) {
|
|
1945
|
-
return typeof value === "number" ? `${value}px` : value;
|
|
1946
|
-
}
|
|
1947
|
-
var MAX_HOST_CSS = 800 * 1024;
|
|
1948
|
-
function collectHostStyles() {
|
|
1949
|
-
if (typeof document === "undefined") return "";
|
|
1950
|
-
let out = "";
|
|
1951
|
-
try {
|
|
1952
|
-
for (const sheet of Array.from(document.styleSheets)) {
|
|
1953
|
-
let rules = null;
|
|
1954
|
-
try {
|
|
1955
|
-
rules = sheet.cssRules;
|
|
1956
|
-
} catch {
|
|
1957
|
-
continue;
|
|
1958
|
-
}
|
|
1959
|
-
if (!rules) continue;
|
|
1960
|
-
for (const rule of Array.from(rules)) {
|
|
1961
|
-
out += `${rule.cssText}
|
|
1962
|
-
`;
|
|
1963
|
-
if (out.length > MAX_HOST_CSS) return out;
|
|
1964
|
-
}
|
|
1965
|
-
}
|
|
1966
|
-
} catch {
|
|
1967
|
-
return out;
|
|
1968
|
-
}
|
|
1969
|
-
return out;
|
|
1970
|
-
}
|
|
1971
|
-
function hostHtmlAttrs() {
|
|
1972
|
-
if (typeof document === "undefined") return "";
|
|
1973
|
-
try {
|
|
1974
|
-
const el2 = document.documentElement;
|
|
1975
|
-
const attrs = [];
|
|
1976
|
-
for (const name of ["class", "style", "data-theme", "data-mode", "data-color-mode"]) {
|
|
1977
|
-
const v = el2.getAttribute(name);
|
|
1978
|
-
if (v && v.length <= 1e3 && !/["<>]/.test(v)) attrs.push(`${name}="${v}"`);
|
|
1979
|
-
}
|
|
1980
|
-
return attrs.join(" ");
|
|
1981
|
-
} catch {
|
|
1982
|
-
return "";
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
function isRecord(value) {
|
|
1986
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1987
|
-
}
|
|
1988
|
-
function buildSrcdoc(html, css, js, token, opts) {
|
|
1989
|
-
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){}})();`;
|
|
1990
|
-
const hostStyle = opts.hostCss ? `<style>${escapeClosing(opts.hostCss, "style")}</style>` : "";
|
|
1991
|
-
const tailwind = opts.tailwind ? '<script src="https://cdn.tailwindcss.com"></script>' : "";
|
|
1992
|
-
return (
|
|
1993
|
-
// Orden: base → CSS del host (utilidades+clases propias+:root) → reset
|
|
1994
|
-
// flotante (fondo transparente, sin márgenes) → Tailwind → CSS del autor (gana).
|
|
1995
|
-
`<!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},
|
|
1996
|
-
// html{height:100%}, fondo del layout…) que en un iframe embebido lo estirarían a
|
|
1997
|
-
// toda la pantalla (caja negra) en vez de ajustarse al contenido. Neutralizamos
|
|
1998
|
-
// tamaño/fondo/overflow (mantenemos color y fuente heredados de la app).
|
|
1999
|
-
// `color-scheme:normal` es CLAVE para la transparencia: si el CSS de la app
|
|
2000
|
-
// declara `:root{color-scheme:dark}` y no coincide con el del elemento iframe
|
|
2001
|
-
// del host (normal), el navegador pinta el canvas OPACO (negro) en vez de
|
|
2002
|
-
// transparente — igualamos ambos lados para conservar el fondo transparente.
|
|
2003
|
-
"<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.
|
|
2004
|
-
// `.dark body{background:#000!important}`) porque el estilo inline tiene la
|
|
2005
|
-
// mayor especificidad dentro de la capa important-author.
|
|
2006
|
-
`<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>"
|
|
2007
|
-
);
|
|
2008
|
-
}
|
|
2009
|
-
function escapeClosing(code, tag) {
|
|
2010
|
-
const rx = tag === "script" ? /<\/script/gi : /<\/style/gi;
|
|
2011
|
-
return code.replace(rx, `<\\/${tag}`);
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
// src/plugins/guides/code-block.ts
|
|
2015
|
-
function insertRawNodes(anchor, nodes, pos) {
|
|
2016
|
-
const parent = anchor.parentNode;
|
|
2017
|
-
if (pos === "prepend") {
|
|
2018
|
-
const first = anchor.firstChild;
|
|
2019
|
-
for (const n of nodes) anchor.insertBefore(n, first);
|
|
2020
|
-
} else if (pos === "append") {
|
|
2021
|
-
for (const n of nodes) anchor.appendChild(n);
|
|
2022
|
-
} else if (pos === "before") {
|
|
2023
|
-
for (const n of nodes) parent?.insertBefore(n, anchor);
|
|
2024
|
-
} else {
|
|
2025
|
-
let ref = anchor;
|
|
2026
|
-
for (const n of nodes) {
|
|
2027
|
-
parent?.insertBefore(n, ref.nextSibling);
|
|
2028
|
-
ref = n;
|
|
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;
|
|
2029
2360
|
}
|
|
2030
2361
|
}
|
|
2362
|
+
return btn;
|
|
2031
2363
|
}
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
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);
|
|
2041
2381
|
}
|
|
2042
|
-
|
|
2043
|
-
const
|
|
2044
|
-
|
|
2045
|
-
const style = hostDoc.createElement("style");
|
|
2046
|
-
style.textContent = block.css;
|
|
2047
|
-
hostDoc.head.appendChild(style);
|
|
2048
|
-
injected.push(style);
|
|
2382
|
+
container.appendChild(progress);
|
|
2383
|
+
for (const node of buildContentNodes(step, doc)) {
|
|
2384
|
+
container.appendChild(node);
|
|
2049
2385
|
}
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
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;
|
|
2058
2433
|
}
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
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" });
|
|
2073
2482
|
}
|
|
2074
2483
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
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}`;
|
|
2080
2490
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
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);
|
|
2085
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)
|
|
2086
2534
|
};
|
|
2087
2535
|
}
|
|
2088
|
-
function
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
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;
|
|
2094
2705
|
}
|
|
2095
2706
|
|
|
2096
2707
|
// src/plugins/guides/form-content.ts
|
|
@@ -2319,8 +2930,14 @@ function buildField(field, doc) {
|
|
|
2319
2930
|
wrapper.appendChild(group);
|
|
2320
2931
|
return { wrapper, read: () => inputs.find((i) => i.input.checked)?.value };
|
|
2321
2932
|
}
|
|
2322
|
-
case "nps":
|
|
2323
|
-
|
|
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
|
+
}
|
|
2324
2941
|
case "rating":
|
|
2325
2942
|
return buildScale(wrapper, doc, 1, 5, "veo-rating-btn", "\u2605");
|
|
2326
2943
|
default: {
|
|
@@ -2333,17 +2950,21 @@ function buildField(field, doc) {
|
|
|
2333
2950
|
}
|
|
2334
2951
|
}
|
|
2335
2952
|
}
|
|
2336
|
-
function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
2953
|
+
function buildScale(wrapper, doc, min, max, btnClass, symbol, opts = { labels: [], showLabels: false }) {
|
|
2337
2954
|
const row = doc.createElement("div");
|
|
2338
2955
|
row.className = "veo-form-scale";
|
|
2339
2956
|
let selected;
|
|
2340
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));
|
|
2341
2960
|
for (let n = min; n <= max; n++) {
|
|
2342
2961
|
const btn = doc.createElement("button");
|
|
2343
2962
|
btn.type = "button";
|
|
2344
2963
|
btn.className = btnClass;
|
|
2345
2964
|
btn.textContent = symbol ?? String(n);
|
|
2346
|
-
|
|
2965
|
+
const lbl = labelOf(n);
|
|
2966
|
+
btn.setAttribute("aria-label", lbl ? `${n} \xB7 ${lbl}` : String(n));
|
|
2967
|
+
if (lbl) btn.title = lbl;
|
|
2347
2968
|
btn.addEventListener("click", () => {
|
|
2348
2969
|
selected = n;
|
|
2349
2970
|
buttons.forEach((b, i) => {
|
|
@@ -2352,11 +2973,113 @@ function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
|
2352
2973
|
});
|
|
2353
2974
|
});
|
|
2354
2975
|
buttons.push(btn);
|
|
2355
|
-
|
|
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);
|
|
2356
2987
|
}
|
|
2357
2988
|
wrapper.appendChild(row);
|
|
2358
2989
|
return { wrapper, read: () => selected };
|
|
2359
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
|
+
}
|
|
2360
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";
|
|
2361
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";
|
|
2362
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";
|
|
@@ -2515,13 +3238,83 @@ function markerOffset(anchor, marker, position, offset6) {
|
|
|
2515
3238
|
}
|
|
2516
3239
|
return { x: x + offset6.x, y: y + offset6.y };
|
|
2517
3240
|
}
|
|
3241
|
+
var NO_CHILDREN = /* @__PURE__ */ new Set([
|
|
3242
|
+
"img",
|
|
3243
|
+
"input",
|
|
3244
|
+
"textarea",
|
|
3245
|
+
"select",
|
|
3246
|
+
"iframe",
|
|
3247
|
+
"video",
|
|
3248
|
+
"audio",
|
|
3249
|
+
"canvas",
|
|
3250
|
+
"br",
|
|
3251
|
+
"hr",
|
|
3252
|
+
"svg",
|
|
3253
|
+
"object",
|
|
3254
|
+
"embed"
|
|
3255
|
+
]);
|
|
3256
|
+
function canNest(anchor) {
|
|
3257
|
+
const win = anchor.ownerDocument.defaultView;
|
|
3258
|
+
if (!win || !(anchor instanceof win.HTMLElement)) return false;
|
|
3259
|
+
if (NO_CHILDREN.has(anchor.tagName.toLowerCase())) return false;
|
|
3260
|
+
const cs = win.getComputedStyle(anchor);
|
|
3261
|
+
if (cs.display === "contents") return false;
|
|
3262
|
+
const tokens = `${cs.overflow} ${cs.overflowX} ${cs.overflowY}`.split(/\s+/).filter(Boolean);
|
|
3263
|
+
return tokens.every((t) => t === "visible");
|
|
3264
|
+
}
|
|
2518
3265
|
function attachMarker(anchor, host, marker) {
|
|
2519
3266
|
host.style.position = "absolute";
|
|
2520
3267
|
host.style.zIndex = String(num(marker.zIndex, DEFAULT_MARKER_Z));
|
|
2521
3268
|
host.style.margin = "0";
|
|
3269
|
+
const position = marker.position ?? "top-right";
|
|
3270
|
+
const offset6 = { x: num(marker.offsetX, 0), y: num(marker.offsetY, 0) };
|
|
3271
|
+
const markerSize = () => {
|
|
3272
|
+
const m = host.getBoundingClientRect();
|
|
3273
|
+
return { width: m.width, height: m.height };
|
|
3274
|
+
};
|
|
3275
|
+
if (canNest(anchor)) {
|
|
3276
|
+
const win = anchor.ownerDocument.defaultView;
|
|
3277
|
+
const prevPosition = anchor.style.position;
|
|
3278
|
+
const computed = win.getComputedStyle(anchor).position;
|
|
3279
|
+
if (!computed || computed === "static") anchor.style.position = "relative";
|
|
3280
|
+
anchor.appendChild(host);
|
|
3281
|
+
const swallowClick = (e) => {
|
|
3282
|
+
e.preventDefault();
|
|
3283
|
+
e.stopPropagation();
|
|
3284
|
+
};
|
|
3285
|
+
const swallow = (e) => e.stopPropagation();
|
|
3286
|
+
host.addEventListener("click", swallowClick);
|
|
3287
|
+
host.addEventListener("pointerdown", swallow);
|
|
3288
|
+
host.addEventListener("mousedown", swallow);
|
|
3289
|
+
const update2 = () => {
|
|
3290
|
+
const { x, y } = markerOffset(
|
|
3291
|
+
{
|
|
3292
|
+
left: -anchor.clientLeft,
|
|
3293
|
+
top: -anchor.clientTop,
|
|
3294
|
+
width: anchor.offsetWidth,
|
|
3295
|
+
height: anchor.offsetHeight
|
|
3296
|
+
},
|
|
3297
|
+
markerSize(),
|
|
3298
|
+
position,
|
|
3299
|
+
offset6
|
|
3300
|
+
);
|
|
3301
|
+
host.style.left = `${Math.round(x)}px`;
|
|
3302
|
+
host.style.top = `${Math.round(y)}px`;
|
|
3303
|
+
};
|
|
3304
|
+
update2();
|
|
3305
|
+
const stop2 = dom.autoUpdate(anchor, host, update2, { ancestorScroll: false, layoutShift: false });
|
|
3306
|
+
return () => {
|
|
3307
|
+
stop2();
|
|
3308
|
+
host.removeEventListener("click", swallowClick);
|
|
3309
|
+
host.removeEventListener("pointerdown", swallow);
|
|
3310
|
+
host.removeEventListener("mousedown", swallow);
|
|
3311
|
+
host.remove();
|
|
3312
|
+
anchor.style.position = prevPosition;
|
|
3313
|
+
};
|
|
3314
|
+
}
|
|
3315
|
+
anchor.ownerDocument.body.appendChild(host);
|
|
2522
3316
|
const update = () => {
|
|
2523
3317
|
const a = anchor.getBoundingClientRect();
|
|
2524
|
-
const m = host.getBoundingClientRect();
|
|
2525
3318
|
const { x, y } = markerOffset(
|
|
2526
3319
|
{
|
|
2527
3320
|
left: a.left + window.scrollX,
|
|
@@ -2529,15 +3322,19 @@ function attachMarker(anchor, host, marker) {
|
|
|
2529
3322
|
width: a.width,
|
|
2530
3323
|
height: a.height
|
|
2531
3324
|
},
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
3325
|
+
markerSize(),
|
|
3326
|
+
position,
|
|
3327
|
+
offset6
|
|
2535
3328
|
);
|
|
2536
3329
|
host.style.left = `${Math.round(x)}px`;
|
|
2537
3330
|
host.style.top = `${Math.round(y)}px`;
|
|
2538
3331
|
};
|
|
2539
3332
|
update();
|
|
2540
|
-
|
|
3333
|
+
const stop = dom.autoUpdate(anchor, host, update);
|
|
3334
|
+
return () => {
|
|
3335
|
+
stop();
|
|
3336
|
+
host.remove();
|
|
3337
|
+
};
|
|
2541
3338
|
}
|
|
2542
3339
|
|
|
2543
3340
|
// src/plugins/guides/renderers/composite-renderer.ts
|
|
@@ -2546,6 +3343,32 @@ var ROW_ALIGN = {
|
|
|
2546
3343
|
center: "center",
|
|
2547
3344
|
right: "flex-end"
|
|
2548
3345
|
};
|
|
3346
|
+
var ROW_JUSTIFY = { ...ROW_ALIGN, distribute: "space-between" };
|
|
3347
|
+
var ROW_VALIGN = {
|
|
3348
|
+
top: "flex-start",
|
|
3349
|
+
center: "center",
|
|
3350
|
+
bottom: "flex-end"
|
|
3351
|
+
};
|
|
3352
|
+
function applyRowStyle(rowEl, style) {
|
|
3353
|
+
const s = rowEl.style;
|
|
3354
|
+
if (typeof style.background === "string" && style.background) s.background = style.background;
|
|
3355
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3356
|
+
const p = style[`padding${side}`];
|
|
3357
|
+
if (typeof p === "number" && Number.isFinite(p))
|
|
3358
|
+
s.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3359
|
+
const m = style[`margin${side}`];
|
|
3360
|
+
if (typeof m === "number" && Number.isFinite(m))
|
|
3361
|
+
s.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3362
|
+
}
|
|
3363
|
+
if (typeof style.minHeight === "number" && Number.isFinite(style.minHeight) && style.minHeight > 0) {
|
|
3364
|
+
s.minHeight = `${style.minHeight}px`;
|
|
3365
|
+
}
|
|
3366
|
+
if (style.align && ROW_JUSTIFY[style.align]) {
|
|
3367
|
+
s.justifyContent = ROW_JUSTIFY[style.align];
|
|
3368
|
+
rowEl.classList.add("veo-row--aligned");
|
|
3369
|
+
}
|
|
3370
|
+
if (style.valign && ROW_VALIGN[style.valign]) s.alignItems = ROW_VALIGN[style.valign];
|
|
3371
|
+
}
|
|
2549
3372
|
var CompositeRenderer = class extends BaseRenderer {
|
|
2550
3373
|
async render(context) {
|
|
2551
3374
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -2564,7 +3387,8 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2564
3387
|
const rawDoc = anchor.ownerDocument ?? document;
|
|
2565
3388
|
const { cleanup } = mountCodeBlock(soleCode, rawDoc, context, void 0, {
|
|
2566
3389
|
anchor,
|
|
2567
|
-
pos: inlinePosOf(step.inlinePosition)
|
|
3390
|
+
pos: inlinePosOf(step.inlinePosition),
|
|
3391
|
+
...context.isPreview && soleCode.editorId ? { tagBlockId: soleCode.editorId } : {}
|
|
2568
3392
|
});
|
|
2569
3393
|
this.registerCleanup(cleanup);
|
|
2570
3394
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action: "shown" });
|
|
@@ -2577,6 +3401,10 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2577
3401
|
const card = doc.createElement("div");
|
|
2578
3402
|
card.className = "veo-card";
|
|
2579
3403
|
applyCompositeDesign(card, design);
|
|
3404
|
+
if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
|
|
3405
|
+
card.setAttribute("role", "dialog");
|
|
3406
|
+
card.setAttribute("aria-label", design.ariaLabel.trim());
|
|
3407
|
+
}
|
|
2580
3408
|
const dismiss = (action, meta) => {
|
|
2581
3409
|
context.onInteraction({
|
|
2582
3410
|
guideId: context.guide.guideId,
|
|
@@ -2587,6 +3415,28 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2587
3415
|
};
|
|
2588
3416
|
const readers = [];
|
|
2589
3417
|
this.buildContent(card, step.blocks ?? [], doc, context, idx, readers, host);
|
|
3418
|
+
if (blocks.length === 0) {
|
|
3419
|
+
card.classList.add("veo-card--empty");
|
|
3420
|
+
if (context.isPreview) {
|
|
3421
|
+
const zone = doc.createElement("div");
|
|
3422
|
+
zone.className = "veo-card-empty-zone";
|
|
3423
|
+
const plus = doc.createElement("b");
|
|
3424
|
+
plus.textContent = "+";
|
|
3425
|
+
const text = doc.createElement("span");
|
|
3426
|
+
text.textContent = "Clic para agregar contenido";
|
|
3427
|
+
zone.append(plus, text);
|
|
3428
|
+
card.appendChild(zone);
|
|
3429
|
+
const applyNarrow = () => {
|
|
3430
|
+
card.classList.toggle("veo-card--narrow", card.clientWidth < NARROW_CARD_WIDTH);
|
|
3431
|
+
};
|
|
3432
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
3433
|
+
const ro = new ResizeObserver(applyNarrow);
|
|
3434
|
+
ro.observe(card);
|
|
3435
|
+
this.registerCleanup(() => ro.disconnect());
|
|
3436
|
+
}
|
|
3437
|
+
applyNarrow();
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
2590
3440
|
const lastBlock = card.lastElementChild;
|
|
2591
3441
|
if (lastBlock && !lastBlock.style.marginBottom) lastBlock.style.marginBottom = "0";
|
|
2592
3442
|
let closeTooltip = null;
|
|
@@ -2604,6 +3454,14 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2604
3454
|
if (closeColor) closeBtn.style.color = closeColor;
|
|
2605
3455
|
const closePos = typeof design.closePosition === "string" ? design.closePosition : "top-right";
|
|
2606
3456
|
applyClosePosition(closeBtn, closePos);
|
|
3457
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3458
|
+
const p = design[`closePadding${side}`];
|
|
3459
|
+
if (typeof p === "number")
|
|
3460
|
+
closeBtn.style.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3461
|
+
const m = design[`closeMargin${side}`];
|
|
3462
|
+
if (typeof m === "number")
|
|
3463
|
+
closeBtn.style.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3464
|
+
}
|
|
2607
3465
|
if (hoverColor) {
|
|
2608
3466
|
closeBtn.addEventListener("mouseenter", () => {
|
|
2609
3467
|
closeBtn.style.color = hoverColor;
|
|
@@ -2613,6 +3471,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2613
3471
|
});
|
|
2614
3472
|
}
|
|
2615
3473
|
card.appendChild(closeBtn);
|
|
3474
|
+
reserveCloseSpace(card, blocks, closePos, design);
|
|
2616
3475
|
}
|
|
2617
3476
|
if (badgeMode && anchor) {
|
|
2618
3477
|
closeTooltip = this.renderBadgeMode(
|
|
@@ -2643,6 +3502,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2643
3502
|
const bz = typeof design.backdropZIndex === "number" ? design.backdropZIndex : 2e5;
|
|
2644
3503
|
bg.style.zIndex = String(bz);
|
|
2645
3504
|
root.appendChild(bg);
|
|
3505
|
+
this.registerCleanup(lockScroll(doc));
|
|
2646
3506
|
if (design.targetAbove === true) this.registerCleanup(liftTarget(anchor, bz + 1));
|
|
2647
3507
|
}
|
|
2648
3508
|
const wrapper = doc.createElement("div");
|
|
@@ -2669,6 +3529,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2669
3529
|
this.closeOrSkip(context);
|
|
2670
3530
|
}
|
|
2671
3531
|
});
|
|
3532
|
+
this.registerCleanup(lockScroll(doc));
|
|
2672
3533
|
}
|
|
2673
3534
|
applyViewportPosition(card, step.position, design);
|
|
2674
3535
|
overlay.appendChild(card);
|
|
@@ -2686,83 +3547,217 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2686
3547
|
...block.id ? { metadata: { buttonId: block.id } } : {}
|
|
2687
3548
|
});
|
|
2688
3549
|
};
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
3550
|
+
const tag = (node, block, index) => {
|
|
3551
|
+
node.dataset.veoBlock = String(index);
|
|
3552
|
+
if (block.editorId) node.dataset.veoBlockId = block.editorId;
|
|
3553
|
+
return node;
|
|
3554
|
+
};
|
|
3555
|
+
const placeholder = (block, label) => {
|
|
3556
|
+
if (!context.isPreview || !block.editorId) return null;
|
|
3557
|
+
const el2 = doc.createElement("div");
|
|
3558
|
+
el2.className = "veo-block-placeholder";
|
|
3559
|
+
el2.textContent = label;
|
|
3560
|
+
return el2;
|
|
3561
|
+
};
|
|
3562
|
+
const buildSequence = (container, cell) => {
|
|
3563
|
+
let i = 0;
|
|
3564
|
+
while (i < cell.length) {
|
|
3565
|
+
const { block: b, index } = cell[i];
|
|
3566
|
+
if (b.type === "button") {
|
|
3567
|
+
const row = doc.createElement("div");
|
|
3568
|
+
row.className = "veo-guide-actions";
|
|
3569
|
+
let rowAlign;
|
|
3570
|
+
while (i < cell.length && cell[i]?.block.type === "button") {
|
|
3571
|
+
const { block: bb, index: bi } = cell[i];
|
|
3572
|
+
if (!rowAlign) {
|
|
3573
|
+
const a = bb.style?.align;
|
|
3574
|
+
if (a === "left" || a === "center" || a === "right") rowAlign = a;
|
|
3575
|
+
}
|
|
3576
|
+
row.appendChild(tag(this.buildButton(bb, doc, context, idx, readers, emit), bb, bi));
|
|
3577
|
+
i++;
|
|
2705
3578
|
}
|
|
2706
|
-
row.
|
|
3579
|
+
if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
|
|
3580
|
+
container.appendChild(row);
|
|
3581
|
+
continue;
|
|
3582
|
+
}
|
|
3583
|
+
if (b.type === "code") {
|
|
3584
|
+
const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
|
|
3585
|
+
const el2 = node2 ?? placeholder(b, "Bloque de c\xF3digo");
|
|
3586
|
+
if (el2) container.appendChild(tag(el2, b, index));
|
|
3587
|
+
this.registerCleanup(cleanup);
|
|
2707
3588
|
i++;
|
|
3589
|
+
continue;
|
|
2708
3590
|
}
|
|
2709
|
-
if (
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
form.className = "veo-form";
|
|
2723
|
-
for (const field of b.fields ?? []) {
|
|
2724
|
-
const { wrapper, read } = buildField(field, doc);
|
|
2725
|
-
readers.push({ field, read });
|
|
2726
|
-
form.appendChild(wrapper);
|
|
3591
|
+
if (b.type === "form") {
|
|
3592
|
+
container.appendChild(tag(this.buildForm(b, doc, context, idx, readers, emit), b, index));
|
|
3593
|
+
i++;
|
|
3594
|
+
continue;
|
|
3595
|
+
}
|
|
3596
|
+
if (b.type === "checklist") {
|
|
3597
|
+
const el2 = buildChecklistBlock(b, doc, context);
|
|
3598
|
+
applyBlockStyle(el2, b.style, "text");
|
|
3599
|
+
el2.style.alignSelf = "stretch";
|
|
3600
|
+
el2.style.width = "100%";
|
|
3601
|
+
container.appendChild(tag(el2, b, index));
|
|
3602
|
+
i++;
|
|
3603
|
+
continue;
|
|
2727
3604
|
}
|
|
2728
|
-
|
|
3605
|
+
const node = buildContentBlock(b, doc, { editing: context.isPreview === true }) ?? placeholder(
|
|
3606
|
+
b,
|
|
3607
|
+
b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo"
|
|
3608
|
+
);
|
|
3609
|
+
if (node) container.appendChild(tag(node, b, index));
|
|
2729
3610
|
i++;
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
const rowStyles = context.guide.guideSteps[idx]?.rows;
|
|
3614
|
+
for (const row of groupLayout(blocks)) {
|
|
3615
|
+
const rowStyle = Number.isInteger(row.row) ? rowStyles?.[String(row.row)] : void 0;
|
|
3616
|
+
if (row.columns.length === 1 && !rowStyle) {
|
|
3617
|
+
buildSequence(card, row.columns[0].blocks);
|
|
2730
3618
|
continue;
|
|
2731
3619
|
}
|
|
2732
|
-
const
|
|
2733
|
-
|
|
2734
|
-
|
|
3620
|
+
const rowEl = doc.createElement("div");
|
|
3621
|
+
rowEl.className = "veo-row";
|
|
3622
|
+
if (Number.isInteger(row.row)) rowEl.dataset.veoRow = String(row.row);
|
|
3623
|
+
for (const column of row.columns) {
|
|
3624
|
+
const colEl = doc.createElement("div");
|
|
3625
|
+
colEl.className = "veo-col";
|
|
3626
|
+
buildSequence(colEl, column.blocks);
|
|
3627
|
+
rowEl.appendChild(colEl);
|
|
3628
|
+
}
|
|
3629
|
+
if (rowStyle) applyRowStyle(rowEl, rowStyle);
|
|
3630
|
+
card.appendChild(rowEl);
|
|
2735
3631
|
}
|
|
2736
3632
|
}
|
|
2737
3633
|
/** Botón con enrutado de acción (navegación / cierre / url / guía / envío de forms). */
|
|
2738
|
-
|
|
3634
|
+
/**
|
|
3635
|
+
* Bloque `form` (encuesta): pregunta (rich text del bloque) + campos + botón
|
|
3636
|
+
* de envío propio (`submit`). Con pregunta, el label del campo se omite (sería
|
|
3637
|
+
* la misma pregunta dos veces) y pasa a `aria-label` del input. El estilo del
|
|
3638
|
+
* bloque viste la pregunta (preset/alineación/color); los márgenes van al
|
|
3639
|
+
* contenedor del bloque entero.
|
|
3640
|
+
*/
|
|
3641
|
+
buildForm(b, doc, context, idx, readers, emit) {
|
|
3642
|
+
const form = doc.createElement("div");
|
|
3643
|
+
form.className = "veo-form";
|
|
3644
|
+
const style = b.style ?? {};
|
|
3645
|
+
const { marginTop, marginBottom, ...questionStyle } = style;
|
|
3646
|
+
for (const [side, v] of [
|
|
3647
|
+
["marginTop", marginTop],
|
|
3648
|
+
["marginBottom", marginBottom]
|
|
3649
|
+
]) {
|
|
3650
|
+
if (typeof v === "number" && Number.isFinite(v)) form.style[side] = `${v}px`;
|
|
3651
|
+
}
|
|
3652
|
+
let question = null;
|
|
3653
|
+
if (typeof b.html === "string" && b.html || typeof b.text === "string" && b.text) {
|
|
3654
|
+
question = doc.createElement("div");
|
|
3655
|
+
question.className = "veo-form-question";
|
|
3656
|
+
if (typeof b.html === "string" && b.html) question.appendChild(renderRichText(b.html, doc));
|
|
3657
|
+
else question.textContent = b.text;
|
|
3658
|
+
applyBlockStyle(question, questionStyle, "text");
|
|
3659
|
+
form.appendChild(question);
|
|
3660
|
+
}
|
|
3661
|
+
const questionText = question?.textContent?.trim() ?? "";
|
|
3662
|
+
const own = [];
|
|
3663
|
+
for (const field of b.fields ?? []) {
|
|
3664
|
+
const { wrapper, read } = buildField(field, doc);
|
|
3665
|
+
const st = b.style ?? {};
|
|
3666
|
+
if (typeof st.scaleColor === "string" && st.scaleColor) {
|
|
3667
|
+
wrapper.style.setProperty("--veo-scale-color", st.scaleColor);
|
|
3668
|
+
}
|
|
3669
|
+
if (typeof st.scaleSize === "number" && st.scaleSize >= 24 && st.scaleSize <= 96) {
|
|
3670
|
+
wrapper.style.setProperty("--veo-scale-size", `${st.scaleSize}px`);
|
|
3671
|
+
}
|
|
3672
|
+
if (question) {
|
|
3673
|
+
wrapper.querySelector(".veo-form-label")?.remove();
|
|
3674
|
+
if (questionText) {
|
|
3675
|
+
for (const inp of wrapper.querySelectorAll("input, textarea, select")) {
|
|
3676
|
+
inp.setAttribute("aria-label", questionText);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
const reader = { field, read, blockId: b.id ?? null };
|
|
3681
|
+
readers.push(reader);
|
|
3682
|
+
own.push(reader);
|
|
3683
|
+
form.appendChild(wrapper);
|
|
3684
|
+
}
|
|
3685
|
+
if (b.submit) {
|
|
3686
|
+
const actions = doc.createElement("div");
|
|
3687
|
+
actions.className = "veo-guide-actions";
|
|
3688
|
+
const submit = { ...b.submit, type: "button" };
|
|
3689
|
+
const sa = submit.style?.align;
|
|
3690
|
+
if (sa === "left" || sa === "center" || sa === "right") {
|
|
3691
|
+
actions.style.justifyContent = ROW_ALIGN[sa] ?? "flex-end";
|
|
3692
|
+
}
|
|
3693
|
+
actions.appendChild(this.buildButton(submit, doc, context, idx, readers, emit, own));
|
|
3694
|
+
form.appendChild(actions);
|
|
3695
|
+
}
|
|
3696
|
+
return form;
|
|
3697
|
+
}
|
|
3698
|
+
buildButton(block, doc, context, idx, readers, emit, submitFirst) {
|
|
2739
3699
|
const btn = doc.createElement("button");
|
|
2740
3700
|
btn.type = "button";
|
|
2741
3701
|
btn.className = "veo-guide-cta";
|
|
2742
3702
|
btn.textContent = block.text?.trim() || "OK";
|
|
3703
|
+
const st = block.style ?? {};
|
|
3704
|
+
if (st.preset === "secondary" || st.preset === "link") {
|
|
3705
|
+
btn.classList.add(`veo-guide-cta--${st.preset}`);
|
|
3706
|
+
}
|
|
3707
|
+
if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
|
|
3708
|
+
btn.setAttribute("aria-label", st.ariaLabel.trim());
|
|
3709
|
+
}
|
|
2743
3710
|
applyBlockStyle(btn, block.style, "button");
|
|
2744
3711
|
const nav = context.nav?.callbacks;
|
|
2745
|
-
|
|
2746
|
-
switch (
|
|
3712
|
+
const runSimple = (spec) => {
|
|
3713
|
+
switch (spec.action) {
|
|
2747
3714
|
case "next":
|
|
2748
|
-
emit(block);
|
|
2749
3715
|
if (nav) nav.onNext();
|
|
2750
3716
|
else context.onClose();
|
|
2751
3717
|
break;
|
|
2752
3718
|
case "prev":
|
|
2753
|
-
emit(block);
|
|
2754
3719
|
nav?.onBack();
|
|
2755
3720
|
break;
|
|
3721
|
+
case "goto":
|
|
3722
|
+
if (nav?.onGoto && typeof spec.targetStep === "number") nav.onGoto(spec.targetStep);
|
|
3723
|
+
else context.onClose();
|
|
3724
|
+
break;
|
|
3725
|
+
case "snooze":
|
|
3726
|
+
snoozeGuide(
|
|
3727
|
+
context.guide.guideId,
|
|
3728
|
+
typeof spec.snoozeEvery === "number" ? spec.snoozeEvery : 1,
|
|
3729
|
+
spec.snoozeUnit ?? "day"
|
|
3730
|
+
);
|
|
3731
|
+
context.onClose();
|
|
3732
|
+
break;
|
|
2756
3733
|
case "url":
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
window.open(
|
|
3734
|
+
if (typeof spec.url === "string" && isSafeUrl(spec.url)) {
|
|
3735
|
+
if (spec.newTab === false) window.location.assign(spec.url);
|
|
3736
|
+
else window.open(spec.url, "_blank", "noopener,noreferrer");
|
|
2760
3737
|
}
|
|
2761
3738
|
break;
|
|
2762
3739
|
case "guide":
|
|
2763
|
-
|
|
2764
|
-
|
|
3740
|
+
if (spec.targetGuideId) context.onOpenGuide?.(spec.targetGuideId);
|
|
3741
|
+
this.closeOrSkip(context);
|
|
3742
|
+
break;
|
|
3743
|
+
default:
|
|
2765
3744
|
this.closeOrSkip(context);
|
|
3745
|
+
}
|
|
3746
|
+
};
|
|
3747
|
+
const runSecond = () => {
|
|
3748
|
+
if (block.secondAction?.action) runSimple(block.secondAction);
|
|
3749
|
+
};
|
|
3750
|
+
const runActions = () => {
|
|
3751
|
+
switch (block.action) {
|
|
3752
|
+
case "next":
|
|
3753
|
+
case "prev":
|
|
3754
|
+
case "goto":
|
|
3755
|
+
case "snooze":
|
|
3756
|
+
case "url":
|
|
3757
|
+
case "guide":
|
|
3758
|
+
emit(block);
|
|
3759
|
+
runSimple(block);
|
|
3760
|
+
runSecond();
|
|
2766
3761
|
break;
|
|
2767
3762
|
case "submit-forms":
|
|
2768
3763
|
case "submit-forms-advance":
|
|
@@ -2781,7 +3776,12 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2781
3776
|
default:
|
|
2782
3777
|
emit(block);
|
|
2783
3778
|
this.closeOrSkip(context);
|
|
3779
|
+
runSecond();
|
|
2784
3780
|
}
|
|
3781
|
+
};
|
|
3782
|
+
btn.addEventListener("click", () => {
|
|
3783
|
+
if (submitFirst) this.submitForms(submitFirst, context, idx, btn, runActions);
|
|
3784
|
+
else runActions();
|
|
2785
3785
|
});
|
|
2786
3786
|
return btn;
|
|
2787
3787
|
}
|
|
@@ -2800,17 +3800,27 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2800
3800
|
}
|
|
2801
3801
|
answers[field.key] = value;
|
|
2802
3802
|
}
|
|
2803
|
-
if (
|
|
3803
|
+
if (Object.keys(answers).length === 0) {
|
|
3804
|
+
onDone();
|
|
3805
|
+
return;
|
|
3806
|
+
}
|
|
2804
3807
|
btn.disabled = true;
|
|
2805
3808
|
const submitFn = context.onFormSubmit ?? (() => Promise.resolve(true));
|
|
2806
|
-
|
|
3809
|
+
const blockIds = new Set(
|
|
3810
|
+
readers.filter((r) => answers[r.field.key] !== void 0).map((r) => r.blockId ?? "")
|
|
3811
|
+
);
|
|
3812
|
+
const pollId = blockIds.size === 1 ? [...blockIds][0] || void 0 : void 0;
|
|
3813
|
+
void submitFn(answers, { ...pollId ? { pollId } : {}, stepIndex: idx }).then((ok) => {
|
|
2807
3814
|
btn.disabled = false;
|
|
2808
3815
|
if (!ok) return;
|
|
2809
|
-
context.
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
3816
|
+
const lastStep = !context.nav || idx >= context.nav.totalSteps - 1;
|
|
3817
|
+
if (lastStep) {
|
|
3818
|
+
context.onInteraction({
|
|
3819
|
+
guideId: context.guide.guideId,
|
|
3820
|
+
stepIndex: idx,
|
|
3821
|
+
action: "completed"
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
2814
3824
|
onDone();
|
|
2815
3825
|
});
|
|
2816
3826
|
}
|
|
@@ -2853,9 +3863,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2853
3863
|
root.appendChild(wrapper);
|
|
2854
3864
|
if (marker) {
|
|
2855
3865
|
host.style.display = "inline-block";
|
|
2856
|
-
(anchor.ownerDocument ?? document).body.appendChild(host);
|
|
2857
3866
|
this.registerCleanup(attachMarker(anchor, host, marker));
|
|
2858
|
-
this.registerCleanup(() => host.remove());
|
|
2859
3867
|
} else {
|
|
2860
3868
|
host.style.display = "inline-block";
|
|
2861
3869
|
const pos = inlinePosOf(step.inlinePosition);
|
|
@@ -2924,21 +3932,38 @@ function inlinePosOf(p) {
|
|
|
2924
3932
|
function applyViewportPosition(card, position, design) {
|
|
2925
3933
|
const s = card.style;
|
|
2926
3934
|
s.position = "fixed";
|
|
2927
|
-
const
|
|
3935
|
+
const BASE = 16;
|
|
3936
|
+
const m = (k) => typeof design[k] === "number" && Number.isFinite(design[k]) ? design[k] : 0;
|
|
3937
|
+
s.margin = "0";
|
|
2928
3938
|
const hAlign = design.hAlign === "left" ? "left" : design.hAlign === "right" ? "right" : null;
|
|
2929
3939
|
const v = position.includes("top") ? "top" : position.includes("bottom") ? "bottom" : "middle";
|
|
2930
3940
|
const h = hAlign ?? (position.includes("left") ? "left" : position.includes("right") ? "right" : "middle");
|
|
2931
|
-
if (v === "top") s.top =
|
|
2932
|
-
else if (v === "bottom") s.bottom =
|
|
3941
|
+
if (v === "top") s.top = `${BASE + m("marginTop")}px`;
|
|
3942
|
+
else if (v === "bottom") s.bottom = `${BASE + m("marginBottom")}px`;
|
|
2933
3943
|
else s.top = "50%";
|
|
2934
|
-
if (h === "left") s.left =
|
|
2935
|
-
else if (h === "right") s.right =
|
|
3944
|
+
if (h === "left") s.left = `${BASE + m("marginLeft")}px`;
|
|
3945
|
+
else if (h === "right") s.right = `${BASE + m("marginRight")}px`;
|
|
2936
3946
|
else s.left = "50%";
|
|
2937
|
-
const
|
|
2938
|
-
const
|
|
3947
|
+
const dx = m("marginLeft") - m("marginRight");
|
|
3948
|
+
const dy = m("marginTop") - m("marginBottom");
|
|
3949
|
+
const tx = h === "middle" ? `calc(-50% + ${dx}px)` : "0";
|
|
3950
|
+
const ty = v === "middle" ? `calc(-50% + ${dy}px)` : "0";
|
|
2939
3951
|
s.transform = tx === "0" && ty === "0" ? "" : `translate(${tx}, ${ty})`;
|
|
2940
3952
|
}
|
|
2941
3953
|
var CLOSE_INSET = "10px";
|
|
3954
|
+
var CLOSE_RESERVE_PX = 42;
|
|
3955
|
+
var CARD_DEFAULT_PAD_PX = 20;
|
|
3956
|
+
function reserveCloseSpace(card, blocks, position, design) {
|
|
3957
|
+
if (blocks.length === 0) return;
|
|
3958
|
+
const side = position.includes("bottom") ? "Bottom" : "Top";
|
|
3959
|
+
const edgeBlock = side === "Top" ? blocks[0] : blocks[blocks.length - 1];
|
|
3960
|
+
if (edgeBlock?.type === "image") return;
|
|
3961
|
+
const margin = design[`closeMargin${side}`];
|
|
3962
|
+
const need = CLOSE_RESERVE_PX + (typeof margin === "number" ? margin : 0);
|
|
3963
|
+
const current = design[`padding${side}`];
|
|
3964
|
+
const have = typeof current === "number" ? current : CARD_DEFAULT_PAD_PX;
|
|
3965
|
+
if (have < need) card.style.setProperty(`padding-${side.toLowerCase()}`, `${need}px`);
|
|
3966
|
+
}
|
|
2942
3967
|
function applyClosePosition(btn, position) {
|
|
2943
3968
|
const s = btn.style;
|
|
2944
3969
|
s.top = "auto";
|
|
@@ -3003,10 +4028,17 @@ async function anchorPosition(anchor, wrapper, caret, preferred = "bottom") {
|
|
|
3003
4028
|
window.removeEventListener("resize", reposition);
|
|
3004
4029
|
};
|
|
3005
4030
|
}
|
|
4031
|
+
var MIN_CARD_WIDTH = 120;
|
|
4032
|
+
var NARROW_CARD_WIDTH = 260;
|
|
3006
4033
|
function applyCompositeDesign(card, design) {
|
|
3007
4034
|
const s = card.style;
|
|
3008
4035
|
const px = (v) => typeof v === "number" && Number.isFinite(v) ? `${v}px` : null;
|
|
3009
4036
|
const strv = (v) => typeof v === "string" && v ? v : null;
|
|
4037
|
+
if (design.colorMode === "dark") {
|
|
4038
|
+
s.background = "#1f1d1b";
|
|
4039
|
+
s.color = "#f5f0eb";
|
|
4040
|
+
card.classList.add("veo-card--dark");
|
|
4041
|
+
}
|
|
3010
4042
|
if (strv(design.background)) s.background = design.background;
|
|
3011
4043
|
const textColor = strv(design.color) ?? strv(design.textColor);
|
|
3012
4044
|
if (textColor) s.color = textColor;
|
|
@@ -3028,13 +4060,14 @@ function applyCompositeDesign(card, design) {
|
|
|
3028
4060
|
const n = (k) => typeof design[k] === "number" ? design[k] : 0;
|
|
3029
4061
|
s.boxShadow = `${n("shadowX")}px ${n("shadowY")}px ${n("shadowBlur")}px ${n("shadowSpread")}px ${strv(design.shadowColor) ?? "rgba(0,0,0,0.2)"}`;
|
|
3030
4062
|
}
|
|
4063
|
+
const width = typeof design.width === "number" && Number.isFinite(design.width) ? `${Math.max(MIN_CARD_WIDTH, design.width)}px` : null;
|
|
3031
4064
|
if (design.fullWidth === true) {
|
|
3032
4065
|
s.width = "100%";
|
|
3033
4066
|
} else if (design.widthType === "fluid") {
|
|
3034
|
-
s.width = "
|
|
3035
|
-
if (
|
|
3036
|
-
} else if (
|
|
3037
|
-
s.width =
|
|
4067
|
+
s.width = "100%";
|
|
4068
|
+
if (width) s.maxWidth = width;
|
|
4069
|
+
} else if (width) {
|
|
4070
|
+
s.width = width;
|
|
3038
4071
|
}
|
|
3039
4072
|
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3040
4073
|
const p = px(design[`padding${side}`]);
|
|
@@ -3138,6 +4171,7 @@ var FormRenderer = class extends BaseRenderer {
|
|
|
3138
4171
|
mountFormContent(card, context, doc);
|
|
3139
4172
|
overlay.appendChild(card);
|
|
3140
4173
|
root.appendChild(overlay);
|
|
4174
|
+
this.registerCleanup(lockScroll(doc));
|
|
3141
4175
|
const dismiss = () => {
|
|
3142
4176
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
|
|
3143
4177
|
context.onClose();
|
|
@@ -3265,6 +4299,18 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3265
4299
|
constructor() {
|
|
3266
4300
|
super(...arguments);
|
|
3267
4301
|
this.overlay = null;
|
|
4302
|
+
/** Suelta el bloqueo de scroll del backdrop (null = sin backdrop). */
|
|
4303
|
+
this.releaseScroll = null;
|
|
4304
|
+
}
|
|
4305
|
+
/** Con backdrop la app de atrás queda congelada; sin él, scrollea normal. */
|
|
4306
|
+
syncScrollLock(step) {
|
|
4307
|
+
const wantLock = step.style?.backdrop !== false;
|
|
4308
|
+
if (wantLock && !this.releaseScroll) {
|
|
4309
|
+
this.releaseScroll = lockScroll(this.host?.ownerDocument ?? document);
|
|
4310
|
+
} else if (!wantLock && this.releaseScroll) {
|
|
4311
|
+
this.releaseScroll();
|
|
4312
|
+
this.releaseScroll = null;
|
|
4313
|
+
}
|
|
3268
4314
|
}
|
|
3269
4315
|
render(context) {
|
|
3270
4316
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -3276,6 +4322,7 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3276
4322
|
const overlay = ownerDocument.createElement("div");
|
|
3277
4323
|
overlay.className = overlayClassOf(step);
|
|
3278
4324
|
this.overlay = overlay;
|
|
4325
|
+
this.syncScrollLock(step);
|
|
3279
4326
|
const card = ownerDocument.createElement("div");
|
|
3280
4327
|
card.className = "veo-modal-card";
|
|
3281
4328
|
const dismiss = (action, ctaUrl, meta) => {
|
|
@@ -3333,9 +4380,12 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3333
4380
|
}
|
|
3334
4381
|
onLiveUpdate(step) {
|
|
3335
4382
|
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
4383
|
+
this.syncScrollLock(step);
|
|
3336
4384
|
}
|
|
3337
4385
|
destroy() {
|
|
3338
4386
|
this.overlay = null;
|
|
4387
|
+
this.releaseScroll?.();
|
|
4388
|
+
this.releaseScroll = null;
|
|
3339
4389
|
super.destroy();
|
|
3340
4390
|
}
|
|
3341
4391
|
};
|
|
@@ -3561,6 +4611,7 @@ var GuidePreviewController = class {
|
|
|
3561
4611
|
// Preview de forms: por defecto simula el envío SIN escribir props;
|
|
3562
4612
|
// el caller puede pasar `formSubmit` real (link de preview).
|
|
3563
4613
|
onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
|
|
4614
|
+
...this.input?.fetchProgress ? { fetchProgress: this.input.fetchProgress } : {},
|
|
3564
4615
|
isPreview: true,
|
|
3565
4616
|
...this.input?.formSubmit && this.input.formSubmitNote !== void 0 ? { previewNote: this.input.formSubmitNote } : {}
|
|
3566
4617
|
});
|
|
@@ -3738,9 +4789,35 @@ function matchesUrl(matcher, pageUrl) {
|
|
|
3738
4789
|
}
|
|
3739
4790
|
}
|
|
3740
4791
|
function stepMatchesPage(loc, pageUrl) {
|
|
3741
|
-
if (!loc || loc.scope === "sitewide"
|
|
4792
|
+
if (!loc || loc.scope === "sitewide") return true;
|
|
4793
|
+
if (loc.scope === "pages") {
|
|
4794
|
+
const rules = loc.rules ?? [];
|
|
4795
|
+
return rules.some((r) => pageRuleMatches(r, pageUrl));
|
|
4796
|
+
}
|
|
4797
|
+
if (!loc.pattern) return true;
|
|
3742
4798
|
return matchesUrl({ type: loc.matchType ?? "prefix", pattern: loc.pattern }, pageUrl);
|
|
3743
4799
|
}
|
|
4800
|
+
function pageRuleMatches(rule, pageUrl) {
|
|
4801
|
+
const pattern = rule.pattern.toLowerCase();
|
|
4802
|
+
const candidates = urlCandidates(pageUrl).map((c) => c.toLowerCase());
|
|
4803
|
+
switch (rule.type) {
|
|
4804
|
+
case "exact":
|
|
4805
|
+
return candidates.some((c) => c === pattern);
|
|
4806
|
+
case "prefix":
|
|
4807
|
+
return candidates.some((c) => c.startsWith(pattern));
|
|
4808
|
+
case "contains":
|
|
4809
|
+
return candidates.some((c) => c.includes(pattern));
|
|
4810
|
+
case "regex":
|
|
4811
|
+
try {
|
|
4812
|
+
const re = new RegExp(rule.pattern, "i");
|
|
4813
|
+
return candidates.some((c) => re.test(c));
|
|
4814
|
+
} catch {
|
|
4815
|
+
return false;
|
|
4816
|
+
}
|
|
4817
|
+
default:
|
|
4818
|
+
return false;
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
3744
4821
|
|
|
3745
4822
|
// src/plugins/builder/bridge-protocol.ts
|
|
3746
4823
|
var VEO_BUILDER_SOURCE = "veo-builder";
|
|
@@ -3757,6 +4834,9 @@ var ICONS = {
|
|
|
3757
4834
|
chevronDown: '<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>',
|
|
3758
4835
|
chevronUp: '<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/>',
|
|
3759
4836
|
plus: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>',
|
|
4837
|
+
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"/>',
|
|
4838
|
+
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"/>',
|
|
4839
|
+
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"/>',
|
|
3760
4840
|
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"/>',
|
|
3761
4841
|
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"/>'
|
|
3762
4842
|
};
|
|
@@ -3942,8 +5022,14 @@ function injectBuilderChrome(opts) {
|
|
|
3942
5022
|
stepsList.append(activation);
|
|
3943
5023
|
s.steps.forEach((st, i) => {
|
|
3944
5024
|
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>';
|
|
3945
|
-
const
|
|
3946
|
-
card
|
|
5025
|
+
const isStepView = s.section === "step" || s.section === "block" || s.section === "add-block" || s.section === "row" || s.section === "structure";
|
|
5026
|
+
const card = stepCard(thumb, st.label, isStepView && s.activeStep === i, {
|
|
5027
|
+
// Se pueden borrar TODOS los pasos (el panel vuelve a Activación).
|
|
5028
|
+
canRemove: true,
|
|
5029
|
+
onEdit: () => act({ action: "select-step", index: i }),
|
|
5030
|
+
onDuplicate: () => act({ action: "duplicate-step", index: i }),
|
|
5031
|
+
onRemove: () => act({ action: "remove-step", index: i })
|
|
5032
|
+
});
|
|
3947
5033
|
stepsList.append(card);
|
|
3948
5034
|
});
|
|
3949
5035
|
const add = el("button", "veo-step-add", `${svg("plus", 18)}<span>Agregar paso</span>`);
|
|
@@ -3972,16 +5058,40 @@ function injectBuilderChrome(opts) {
|
|
|
3972
5058
|
}
|
|
3973
5059
|
};
|
|
3974
5060
|
}
|
|
3975
|
-
function stepCard(inner, label, active) {
|
|
3976
|
-
const
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
5061
|
+
function stepCard(inner, label, active, actions) {
|
|
5062
|
+
const cls = `veo-step${active ? " is-active" : ""}`;
|
|
5063
|
+
if (!actions) {
|
|
5064
|
+
const b = el(
|
|
5065
|
+
"button",
|
|
5066
|
+
cls,
|
|
5067
|
+
`<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
|
|
5068
|
+
);
|
|
5069
|
+
b.type = "button";
|
|
5070
|
+
b.querySelector(".veo-step-label").textContent = label;
|
|
5071
|
+
b.setAttribute("aria-pressed", active ? "true" : "false");
|
|
5072
|
+
return b;
|
|
5073
|
+
}
|
|
5074
|
+
const card = el(
|
|
5075
|
+
"div",
|
|
5076
|
+
cls,
|
|
5077
|
+
`<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>`
|
|
3980
5078
|
);
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
5079
|
+
card.querySelector(".veo-step-label").textContent = label;
|
|
5080
|
+
card.setAttribute("aria-current", active ? "step" : "false");
|
|
5081
|
+
const remove = card.querySelector(".veo-step-remove");
|
|
5082
|
+
remove.disabled = !actions.canRemove;
|
|
5083
|
+
if (!actions.canRemove) remove.title = "La gu\xEDa necesita al menos un paso";
|
|
5084
|
+
const on = (sel, fn) => {
|
|
5085
|
+
card.querySelector(sel)?.addEventListener("click", (e) => {
|
|
5086
|
+
e.stopPropagation();
|
|
5087
|
+
fn();
|
|
5088
|
+
});
|
|
5089
|
+
};
|
|
5090
|
+
on(".veo-step-edit", actions.onEdit);
|
|
5091
|
+
on(".veo-step-duplicate", actions.onDuplicate);
|
|
5092
|
+
on(".veo-step-remove", actions.onRemove);
|
|
5093
|
+
card.addEventListener("click", actions.onEdit);
|
|
5094
|
+
return card;
|
|
3985
5095
|
}
|
|
3986
5096
|
var CHROME_CSS = `
|
|
3987
5097
|
:host { all: initial; }
|
|
@@ -4066,10 +5176,31 @@ var CHROME_CSS = `
|
|
|
4066
5176
|
.veo-steps-list { display: flex; align-items: flex-start; gap: 20px; }
|
|
4067
5177
|
.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; }
|
|
4068
5178
|
.veo-step-box {
|
|
5179
|
+
position: relative; overflow: hidden;
|
|
4069
5180
|
width: 116px; height: 64px; border-radius: 10px; background: #fff; border: 2px solid transparent;
|
|
4070
5181
|
display: flex; align-items: center; justify-content: center; color: #2b2623;
|
|
4071
5182
|
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
4072
5183
|
}
|
|
5184
|
+
/* Capa de acciones del paso: aparece al pasar el mouse o al enfocar un bot\xF3n. */
|
|
5185
|
+
.veo-step-hover {
|
|
5186
|
+
position: absolute; inset: 0; background: #2b2623; border-radius: 8px;
|
|
5187
|
+
display: flex; flex-direction: column; padding: 3px 4px 6px; box-sizing: border-box;
|
|
5188
|
+
opacity: 0; transition: opacity 120ms ease-out;
|
|
5189
|
+
}
|
|
5190
|
+
.veo-step:hover .veo-step-hover, .veo-step:focus-within .veo-step-hover { opacity: 1; }
|
|
5191
|
+
.veo-step-hover-top { display: flex; justify-content: space-between; align-items: center; }
|
|
5192
|
+
.veo-step-hover-main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
|
5193
|
+
.veo-step-icon {
|
|
5194
|
+
border: 0; background: transparent; color: #fff; cursor: pointer; padding: 0;
|
|
5195
|
+
width: 20px; height: 20px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center;
|
|
5196
|
+
}
|
|
5197
|
+
.veo-step-icon:hover { background: rgba(255,255,255,0.14); }
|
|
5198
|
+
.veo-step-icon:disabled { opacity: 0.35; cursor: default; background: transparent; }
|
|
5199
|
+
.veo-step-edit {
|
|
5200
|
+
width: 100%; margin: 0 6px; border: 0; background: #dbeefc; color: #1d6fb8; font: inherit; font-size: 13px; font-weight: 600;
|
|
5201
|
+
border-radius: 6px; padding: 5px 10px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
5202
|
+
}
|
|
5203
|
+
.veo-step-edit:hover { background: #cbe4f9; }
|
|
4073
5204
|
.veo-step.is-active .veo-step-box { border-color: #b74d00; }
|
|
4074
5205
|
.veo-step-label { font-size: 13px; color: #6b635d; }
|
|
4075
5206
|
.veo-step.is-active .veo-step-label { color: #2b2623; font-weight: 600; }
|
|
@@ -4112,6 +5243,8 @@ function injectBuilderPanel(opts) {
|
|
|
4112
5243
|
},
|
|
4113
5244
|
isVisible: () => false,
|
|
4114
5245
|
setTopOffset: () => {
|
|
5246
|
+
},
|
|
5247
|
+
setSize: () => {
|
|
4115
5248
|
}
|
|
4116
5249
|
};
|
|
4117
5250
|
}
|
|
@@ -4164,6 +5297,21 @@ function injectBuilderPanel(opts) {
|
|
|
4164
5297
|
card.style.display = next && !(popped && !popped.closed) ? "" : "none";
|
|
4165
5298
|
};
|
|
4166
5299
|
minimize.addEventListener("click", () => setVisible(false));
|
|
5300
|
+
let compactRestore = null;
|
|
5301
|
+
const setSize = (size) => {
|
|
5302
|
+
if (maximized || popped && !popped.closed) return;
|
|
5303
|
+
if (size) {
|
|
5304
|
+
if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
|
|
5305
|
+
card.style.minHeight = "0";
|
|
5306
|
+
card.style.width = `${clamp3(Math.round(size.width), MIN_W, window.innerWidth - MARGIN * 2)}px`;
|
|
5307
|
+
card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top - MARGIN)}px`;
|
|
5308
|
+
} else if (compactRestore) {
|
|
5309
|
+
card.style.minHeight = "";
|
|
5310
|
+
card.style.width = `${compactRestore.w}px`;
|
|
5311
|
+
card.style.height = `${compactRestore.h}px`;
|
|
5312
|
+
compactRestore = null;
|
|
5313
|
+
}
|
|
5314
|
+
};
|
|
4167
5315
|
maximize.addEventListener("click", () => {
|
|
4168
5316
|
maximized = !maximized;
|
|
4169
5317
|
if (maximized) {
|
|
@@ -4263,6 +5411,7 @@ function injectBuilderPanel(opts) {
|
|
|
4263
5411
|
title.textContent = t;
|
|
4264
5412
|
},
|
|
4265
5413
|
setVisible,
|
|
5414
|
+
setSize,
|
|
4266
5415
|
isVisible: () => visible,
|
|
4267
5416
|
setTopOffset: (px) => {
|
|
4268
5417
|
topOffset = px;
|
|
@@ -4909,6 +6058,9 @@ function initBuilderMode() {
|
|
|
4909
6058
|
let staticTarget = null;
|
|
4910
6059
|
let manipulator = null;
|
|
4911
6060
|
let dropTarget = null;
|
|
6061
|
+
let cardClick = null;
|
|
6062
|
+
let rawBlock = null;
|
|
6063
|
+
let selectedBlockId = null;
|
|
4912
6064
|
let dragGhost = null;
|
|
4913
6065
|
let previewStepIndex = 0;
|
|
4914
6066
|
let pendingPreview = null;
|
|
@@ -4929,13 +6081,742 @@ function initBuilderMode() {
|
|
|
4929
6081
|
};
|
|
4930
6082
|
const previewMatchesPage = (cmd) => stepMatchesPage(previewStep(cmd)?.pageLocation, window.location.href);
|
|
4931
6083
|
const postPreviewStatus = (payload) => post({ type: "preview-status", payload });
|
|
4932
|
-
const
|
|
6084
|
+
const detachPreviewTools = () => {
|
|
4933
6085
|
manipulator?.detach();
|
|
4934
6086
|
manipulator = null;
|
|
4935
6087
|
dropTarget?.detach();
|
|
4936
6088
|
dropTarget = null;
|
|
6089
|
+
cardClick?.();
|
|
6090
|
+
cardClick = null;
|
|
6091
|
+
rowHover?.();
|
|
6092
|
+
rowHover = null;
|
|
6093
|
+
hoverRowId = null;
|
|
6094
|
+
hoverCard = false;
|
|
6095
|
+
rawBlock?.detach();
|
|
6096
|
+
rawBlock = null;
|
|
6097
|
+
shownPreview = null;
|
|
6098
|
+
};
|
|
6099
|
+
const withholdPreview = () => {
|
|
6100
|
+
detachPreviewTools();
|
|
6101
|
+
shownPreview = null;
|
|
4937
6102
|
closeGuidePreview();
|
|
4938
6103
|
};
|
|
6104
|
+
const FRAME_PAD = 4;
|
|
6105
|
+
const FRAME_MIN_H = 0;
|
|
6106
|
+
const TOOLBAR_H = 30;
|
|
6107
|
+
const ICON = {
|
|
6108
|
+
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>',
|
|
6109
|
+
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>',
|
|
6110
|
+
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>'
|
|
6111
|
+
};
|
|
6112
|
+
const ICON_MOVE = {
|
|
6113
|
+
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>',
|
|
6114
|
+
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>'
|
|
6115
|
+
};
|
|
6116
|
+
const moveAbility = (blocks, blockId) => {
|
|
6117
|
+
for (const row of groupLayout(blocks)) {
|
|
6118
|
+
for (let c = 0; c < row.columns.length; c++) {
|
|
6119
|
+
const col = row.columns[c];
|
|
6120
|
+
if (!col.blocks.some((b) => b.block.editorId === blockId)) continue;
|
|
6121
|
+
return { left: c > 0, right: c < row.columns.length - 1 };
|
|
6122
|
+
}
|
|
6123
|
+
}
|
|
6124
|
+
return { left: false, right: false };
|
|
6125
|
+
};
|
|
6126
|
+
let previewBlocks = [];
|
|
6127
|
+
let hoverRowId = null;
|
|
6128
|
+
let selectedRowId = null;
|
|
6129
|
+
let rowHover = null;
|
|
6130
|
+
let hoverCard = false;
|
|
6131
|
+
let panelSection = null;
|
|
6132
|
+
let shownPreview = null;
|
|
6133
|
+
const structureOf = (step) => {
|
|
6134
|
+
if (!step) return "";
|
|
6135
|
+
const { blocks, ...rest } = step;
|
|
6136
|
+
return JSON.stringify({
|
|
6137
|
+
rest,
|
|
6138
|
+
blocks: (blocks ?? []).map((b) => {
|
|
6139
|
+
const { text: _t, html: _h, style: _s, ...keep } = b;
|
|
6140
|
+
return keep;
|
|
6141
|
+
})
|
|
6142
|
+
});
|
|
6143
|
+
};
|
|
6144
|
+
const tryPatchPreview = (cmd) => {
|
|
6145
|
+
const prev = shownPreview;
|
|
6146
|
+
if (!prev || cmd.guide.guideType !== "composite" || prev.guide.guideType !== "composite") {
|
|
6147
|
+
return false;
|
|
6148
|
+
}
|
|
6149
|
+
const idx = cmd.guide.startStepIndex ?? 0;
|
|
6150
|
+
if ((prev.guide.startStepIndex ?? 0) !== idx) return false;
|
|
6151
|
+
const guideSig = (g) => JSON.stringify({ ...g, guideSteps: void 0 });
|
|
6152
|
+
if (guideSig(prev.guide) !== guideSig(cmd.guide) || prev.editable !== cmd.editable)
|
|
6153
|
+
return false;
|
|
6154
|
+
const host = document.querySelector("[data-veo-guide]");
|
|
6155
|
+
const card = host ? previewCard(host) : null;
|
|
6156
|
+
if (!host || !card) return false;
|
|
6157
|
+
const prevStep = prev.guide.guideSteps[idx];
|
|
6158
|
+
const nextStep = cmd.guide.guideSteps[idx];
|
|
6159
|
+
if (!prevStep || !nextStep) return false;
|
|
6160
|
+
if (structureOf(prevStep) !== structureOf(nextStep)) return false;
|
|
6161
|
+
const doc = card.ownerDocument;
|
|
6162
|
+
const prevBlocks = prevStep.blocks ?? [];
|
|
6163
|
+
const nextBlocks = nextStep.blocks ?? [];
|
|
6164
|
+
for (let i = 0; i < nextBlocks.length; i++) {
|
|
6165
|
+
const a = prevBlocks[i];
|
|
6166
|
+
const b = nextBlocks[i];
|
|
6167
|
+
if (a.text === b.text && a.html === b.html && JSON.stringify(a.style) === JSON.stringify(b.style)) {
|
|
6168
|
+
continue;
|
|
6169
|
+
}
|
|
6170
|
+
if (!b.editorId) return false;
|
|
6171
|
+
const node = card.querySelector(`[data-veo-block-id="${b.editorId}"]`);
|
|
6172
|
+
if (!node) return false;
|
|
6173
|
+
if (b.type === "button") {
|
|
6174
|
+
node.textContent = b.text?.trim() || "OK";
|
|
6175
|
+
node.removeAttribute("style");
|
|
6176
|
+
node.classList.remove("veo-guide-cta--secondary", "veo-guide-cta--link");
|
|
6177
|
+
const st = b.style ?? {};
|
|
6178
|
+
if (st.preset === "secondary" || st.preset === "link") {
|
|
6179
|
+
node.classList.add(`veo-guide-cta--${st.preset}`);
|
|
6180
|
+
}
|
|
6181
|
+
if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
|
|
6182
|
+
node.setAttribute("aria-label", st.ariaLabel.trim());
|
|
6183
|
+
} else node.removeAttribute("aria-label");
|
|
6184
|
+
applyBlockStyle(node, b.style, "button");
|
|
6185
|
+
continue;
|
|
6186
|
+
}
|
|
6187
|
+
if (b.type === "code" || b.type === "form") return false;
|
|
6188
|
+
const fresh = buildContentBlock(b, doc, { editing: true }) ?? (() => {
|
|
6189
|
+
const el2 = doc.createElement("div");
|
|
6190
|
+
el2.className = "veo-block-placeholder";
|
|
6191
|
+
el2.textContent = b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo";
|
|
6192
|
+
return el2;
|
|
6193
|
+
})();
|
|
6194
|
+
fresh.dataset.veoBlock = node.dataset.veoBlock ?? String(i);
|
|
6195
|
+
fresh.dataset.veoBlockId = b.editorId;
|
|
6196
|
+
node.replaceWith(fresh);
|
|
6197
|
+
}
|
|
6198
|
+
shownPreview = cmd;
|
|
6199
|
+
previewBlocks = nextBlocks;
|
|
6200
|
+
applyBlockHighlight(host, idx);
|
|
6201
|
+
renderGapZones(host, idx, nextBlocks);
|
|
6202
|
+
return true;
|
|
6203
|
+
};
|
|
6204
|
+
const previewCard = (host) => host.shadowRoot?.querySelector(".veo-card") ?? null;
|
|
6205
|
+
const clearBlockHighlight = (host) => {
|
|
6206
|
+
const card = previewCard(host);
|
|
6207
|
+
if (!card) return;
|
|
6208
|
+
for (const el2 of card.querySelectorAll(".veo-block--selected")) {
|
|
6209
|
+
el2.classList.remove("veo-block--selected");
|
|
6210
|
+
}
|
|
6211
|
+
card.querySelector(".veo-block-toolbar")?.remove();
|
|
6212
|
+
card.querySelector(".veo-block-frame")?.remove();
|
|
6213
|
+
};
|
|
6214
|
+
const applyBlockHighlight = (host, stepIndex) => {
|
|
6215
|
+
clearBlockHighlight(host);
|
|
6216
|
+
const card = previewCard(host);
|
|
6217
|
+
if (!card) return;
|
|
6218
|
+
const el2 = selectedBlockId ? card.querySelector(`[data-veo-block-id="${selectedBlockId}"]`) : null;
|
|
6219
|
+
if (!el2 || !selectedBlockId) {
|
|
6220
|
+
renderRowChrome(host, stepIndex);
|
|
6221
|
+
return;
|
|
6222
|
+
}
|
|
6223
|
+
el2.classList.add("veo-block--selected");
|
|
6224
|
+
const doc = card.ownerDocument;
|
|
6225
|
+
const cs = getComputedStyle(card);
|
|
6226
|
+
const padL = Number.parseFloat(cs.paddingLeft) || 0;
|
|
6227
|
+
const padT = Number.parseFloat(cs.paddingTop) || 0;
|
|
6228
|
+
const padR = Number.parseFloat(cs.paddingRight) || 0;
|
|
6229
|
+
const padB = Number.parseFloat(cs.paddingBottom) || 0;
|
|
6230
|
+
const parent = el2.parentElement;
|
|
6231
|
+
const inCol = parent?.classList.contains("veo-col") ? parent : null;
|
|
6232
|
+
const boxL = inCol ? inCol.offsetLeft : padL;
|
|
6233
|
+
const boxR = inCol ? inCol.offsetLeft + inCol.offsetWidth : card.clientWidth - padR;
|
|
6234
|
+
const boxT = padT;
|
|
6235
|
+
const boxB = card.clientHeight - padB;
|
|
6236
|
+
const fx = Math.max(boxL, el2.offsetLeft - FRAME_PAD);
|
|
6237
|
+
const fr = Math.min(boxR, el2.offsetLeft + el2.offsetWidth + FRAME_PAD);
|
|
6238
|
+
const fw = Math.max(0, fr - fx);
|
|
6239
|
+
const fy = Math.max(boxT, el2.offsetTop - FRAME_PAD);
|
|
6240
|
+
const fb = Math.min(boxB, el2.offsetTop + el2.offsetHeight + FRAME_PAD);
|
|
6241
|
+
const fh = Math.max(FRAME_MIN_H, fb - fy);
|
|
6242
|
+
const frame = doc.createElement("div");
|
|
6243
|
+
frame.className = "veo-block-frame";
|
|
6244
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6245
|
+
frame.style.left = `${fx}px`;
|
|
6246
|
+
frame.style.top = `${fy}px`;
|
|
6247
|
+
frame.style.width = `${fw}px`;
|
|
6248
|
+
frame.style.height = `${fh}px`;
|
|
6249
|
+
card.appendChild(frame);
|
|
6250
|
+
const bar = doc.createElement("div");
|
|
6251
|
+
bar.className = "veo-block-toolbar";
|
|
6252
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6253
|
+
const blockId = selectedBlockId;
|
|
6254
|
+
const mk = (action, label, icon, enabled = true) => {
|
|
6255
|
+
const b = doc.createElement("button");
|
|
6256
|
+
b.type = "button";
|
|
6257
|
+
b.title = label;
|
|
6258
|
+
b.setAttribute("aria-label", label);
|
|
6259
|
+
b.className = `veo-block-toolbar-${action}`;
|
|
6260
|
+
b.innerHTML = icon;
|
|
6261
|
+
b.disabled = !enabled;
|
|
6262
|
+
b.addEventListener("click", (e) => {
|
|
6263
|
+
e.preventDefault();
|
|
6264
|
+
e.stopPropagation();
|
|
6265
|
+
post({ type: "block-action", payload: { stepIndex, blockId, action } });
|
|
6266
|
+
});
|
|
6267
|
+
bar.appendChild(b);
|
|
6268
|
+
};
|
|
6269
|
+
const can = moveAbility(previewBlocks, blockId);
|
|
6270
|
+
mk("edit", "Editar", ICON.pencil);
|
|
6271
|
+
mk("duplicate", "Duplicar", ICON.copy);
|
|
6272
|
+
mk("remove", "Eliminar", ICON.trash);
|
|
6273
|
+
mk("move-left", "Mover a la izquierda", ICON_MOVE.left, can.left);
|
|
6274
|
+
mk("move-right", "Mover a la derecha", ICON_MOVE.right, can.right);
|
|
6275
|
+
bar.style.right = `${Math.max(0, card.clientWidth - fr)}px`;
|
|
6276
|
+
card.appendChild(bar);
|
|
6277
|
+
const barH = bar.offsetHeight || TOOLBAR_H;
|
|
6278
|
+
bar.dataset.veoTopAbove = String(fy - barH - 6);
|
|
6279
|
+
bar.dataset.veoTopBelow = String(fb + 6);
|
|
6280
|
+
bar.style.top = bar.dataset.veoTopAbove;
|
|
6281
|
+
renderRowChrome(host, stepIndex);
|
|
6282
|
+
};
|
|
6283
|
+
const chromeRect = (el2) => ({
|
|
6284
|
+
left: el2.offsetLeft,
|
|
6285
|
+
top: el2.offsetTop,
|
|
6286
|
+
right: el2.offsetLeft + el2.offsetWidth,
|
|
6287
|
+
bottom: el2.offsetTop + el2.offsetHeight
|
|
6288
|
+
});
|
|
6289
|
+
const intersects = (a, b) => a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
|
|
6290
|
+
const resolveToolbarOverlaps = (card) => {
|
|
6291
|
+
const cardBar = card.querySelector(".veo-card-toolbar");
|
|
6292
|
+
const blockBar = card.querySelector(".veo-block-toolbar");
|
|
6293
|
+
const rowBar = card.querySelector(".veo-row-toolbar");
|
|
6294
|
+
if (blockBar?.dataset.veoTopAbove) {
|
|
6295
|
+
blockBar.style.top = `${blockBar.dataset.veoTopAbove}px`;
|
|
6296
|
+
if (cardBar && intersects(chromeRect(blockBar), chromeRect(cardBar))) {
|
|
6297
|
+
blockBar.style.top = `${blockBar.dataset.veoTopBelow}px`;
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
if (rowBar?.dataset.veoRight) {
|
|
6301
|
+
rowBar.style.left = "";
|
|
6302
|
+
rowBar.style.right = `${rowBar.dataset.veoRight}px`;
|
|
6303
|
+
rowBar.style.top = `${rowBar.dataset.veoTopBelow}px`;
|
|
6304
|
+
const others = [cardBar, blockBar].filter((b) => !!b);
|
|
6305
|
+
const clashes = () => others.some((o) => intersects(chromeRect(rowBar), chromeRect(o)));
|
|
6306
|
+
if (clashes()) {
|
|
6307
|
+
rowBar.style.right = "";
|
|
6308
|
+
rowBar.style.left = `${rowBar.dataset.veoLeft}px`;
|
|
6309
|
+
}
|
|
6310
|
+
if (clashes() && blockBar) {
|
|
6311
|
+
rowBar.style.left = "";
|
|
6312
|
+
rowBar.style.right = `${rowBar.dataset.veoRight}px`;
|
|
6313
|
+
rowBar.style.top = `${chromeRect(blockBar).bottom + 6}px`;
|
|
6314
|
+
}
|
|
6315
|
+
}
|
|
6316
|
+
};
|
|
6317
|
+
const rectOf = (el2) => ({
|
|
6318
|
+
left: el2.offsetLeft,
|
|
6319
|
+
top: el2.offsetTop,
|
|
6320
|
+
right: el2.offsetLeft + el2.offsetWidth,
|
|
6321
|
+
bottom: el2.offsetTop + el2.offsetHeight
|
|
6322
|
+
});
|
|
6323
|
+
const union = (rs) => rs.length === 0 ? null : rs.reduce((acc, r) => ({
|
|
6324
|
+
left: Math.min(acc.left, r.left),
|
|
6325
|
+
top: Math.min(acc.top, r.top),
|
|
6326
|
+
right: Math.max(acc.right, r.right),
|
|
6327
|
+
bottom: Math.max(acc.bottom, r.bottom)
|
|
6328
|
+
}));
|
|
6329
|
+
const layoutRows = (card, blocks) => {
|
|
6330
|
+
const elOf = (b) => b.editorId ? card.querySelector(`[data-veo-block-id="${b.editorId}"]`) : null;
|
|
6331
|
+
const cs = getComputedStyle(card);
|
|
6332
|
+
const padL = Number.parseFloat(cs.paddingLeft) || 0;
|
|
6333
|
+
const padR = Number.parseFloat(cs.paddingRight) || 0;
|
|
6334
|
+
const contentLeft = padL;
|
|
6335
|
+
const contentRight = card.clientWidth - padR;
|
|
6336
|
+
const rows = groupLayout(blocks).map((row) => {
|
|
6337
|
+
const columns = row.columns.map((col) => {
|
|
6338
|
+
const items = col.blocks.map(({ block }) => ({ block, el: elOf(block) })).filter(
|
|
6339
|
+
(x) => !!x.el && !!x.block.editorId
|
|
6340
|
+
);
|
|
6341
|
+
const blocksRect2 = union(items.map((x) => rectOf(x.el)));
|
|
6342
|
+
if (!blocksRect2) return null;
|
|
6343
|
+
const colEl = items[0]?.el.parentElement;
|
|
6344
|
+
const rect2 = colEl?.classList.contains("veo-col") ? rectOf(colEl) : blocksRect2;
|
|
6345
|
+
return { items, rect: rect2 };
|
|
6346
|
+
}).filter((c) => c !== null);
|
|
6347
|
+
const blocksRect = union(columns.map((c) => c.rect));
|
|
6348
|
+
if (!blocksRect) return null;
|
|
6349
|
+
const rowEl = columns[0]?.items[0]?.el.parentElement?.parentElement;
|
|
6350
|
+
const rect = rowEl?.classList.contains("veo-row") ? rectOf(rowEl) : { ...blocksRect, left: contentLeft, right: contentRight };
|
|
6351
|
+
const id = columns[0]?.items[0]?.block.editorId;
|
|
6352
|
+
return { id, row: row.row, columns, rect };
|
|
6353
|
+
}).filter((r) => r !== null);
|
|
6354
|
+
return { rows, content: { left: contentLeft, right: contentRight } };
|
|
6355
|
+
};
|
|
6356
|
+
const clearRowChrome = (card) => {
|
|
6357
|
+
card.querySelector(".veo-row-frame")?.remove();
|
|
6358
|
+
card.querySelector(".veo-row-toolbar")?.remove();
|
|
6359
|
+
};
|
|
6360
|
+
const renderRowChrome = (host, stepIndex) => {
|
|
6361
|
+
const card = previewCard(host);
|
|
6362
|
+
if (!card) return;
|
|
6363
|
+
clearRowChrome(card);
|
|
6364
|
+
resolveToolbarOverlaps(card);
|
|
6365
|
+
const { rows } = layoutRows(card, previewBlocks);
|
|
6366
|
+
const rowOfSelected = selectedBlockId ? rows.find(
|
|
6367
|
+
(r) => r.columns.some((c) => c.items.some((i) => i.block.editorId === selectedBlockId))
|
|
6368
|
+
) : void 0;
|
|
6369
|
+
const target = (selectedRowId ? rows.find((r) => r.id === selectedRowId) : void 0) ?? rowOfSelected ?? (hoverRowId ? rows.find((r) => r.id === hoverRowId) : void 0);
|
|
6370
|
+
if (!target) return;
|
|
6371
|
+
const doc = card.ownerDocument;
|
|
6372
|
+
const cs = getComputedStyle(card);
|
|
6373
|
+
const padT = Number.parseFloat(cs.paddingTop) || 0;
|
|
6374
|
+
const padB = Number.parseFloat(cs.paddingBottom) || 0;
|
|
6375
|
+
const PAD = 6;
|
|
6376
|
+
const fx = Math.max(0, target.rect.left - PAD);
|
|
6377
|
+
const fr = Math.min(card.clientWidth, target.rect.right + PAD);
|
|
6378
|
+
const fy = Math.max(Math.min(padT, target.rect.top), target.rect.top - PAD);
|
|
6379
|
+
const fb = Math.min(
|
|
6380
|
+
Math.max(card.clientHeight - padB, target.rect.bottom),
|
|
6381
|
+
target.rect.bottom + PAD
|
|
6382
|
+
);
|
|
6383
|
+
const frame = doc.createElement("div");
|
|
6384
|
+
frame.className = "veo-row-frame";
|
|
6385
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6386
|
+
frame.style.left = `${fx}px`;
|
|
6387
|
+
frame.style.top = `${fy}px`;
|
|
6388
|
+
frame.style.width = `${Math.max(0, fr - fx)}px`;
|
|
6389
|
+
frame.style.height = `${Math.max(0, fb - fy)}px`;
|
|
6390
|
+
card.appendChild(frame);
|
|
6391
|
+
const bar = doc.createElement("div");
|
|
6392
|
+
bar.className = "veo-row-toolbar";
|
|
6393
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6394
|
+
const blockId = target.id;
|
|
6395
|
+
const mk = (action, label, icon, text) => {
|
|
6396
|
+
const b = doc.createElement("button");
|
|
6397
|
+
b.type = "button";
|
|
6398
|
+
b.title = label;
|
|
6399
|
+
b.setAttribute("aria-label", label);
|
|
6400
|
+
b.className = `veo-row-toolbar-${action}`;
|
|
6401
|
+
b.innerHTML = icon;
|
|
6402
|
+
if (text) b.append(text);
|
|
6403
|
+
b.addEventListener("click", (e) => {
|
|
6404
|
+
e.preventDefault();
|
|
6405
|
+
e.stopPropagation();
|
|
6406
|
+
post({ type: "row-action", payload: { stepIndex, blockId, action } });
|
|
6407
|
+
});
|
|
6408
|
+
bar.appendChild(b);
|
|
6409
|
+
};
|
|
6410
|
+
mk("edit", "Editar fila", ICON.pencil, "Editar fila");
|
|
6411
|
+
mk("duplicate", "Duplicar fila", ICON.copy);
|
|
6412
|
+
mk("remove", "Eliminar fila", ICON.trash);
|
|
6413
|
+
card.appendChild(bar);
|
|
6414
|
+
bar.dataset.veoRight = String(Math.max(0, card.clientWidth - fr));
|
|
6415
|
+
bar.dataset.veoLeft = String(fx);
|
|
6416
|
+
bar.dataset.veoTopBelow = String(fb + 6);
|
|
6417
|
+
resolveToolbarOverlaps(card);
|
|
6418
|
+
};
|
|
6419
|
+
const renderCardChrome = (host, stepIndex) => {
|
|
6420
|
+
const card = previewCard(host);
|
|
6421
|
+
if (!card) return;
|
|
6422
|
+
card.querySelector(".veo-card-frame")?.remove();
|
|
6423
|
+
card.querySelector(".veo-card-toolbar")?.remove();
|
|
6424
|
+
if (!hoverCard) {
|
|
6425
|
+
resolveToolbarOverlaps(card);
|
|
6426
|
+
return;
|
|
6427
|
+
}
|
|
6428
|
+
const doc = card.ownerDocument;
|
|
6429
|
+
const frame = doc.createElement("div");
|
|
6430
|
+
frame.className = "veo-card-frame";
|
|
6431
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6432
|
+
card.appendChild(frame);
|
|
6433
|
+
const bar = doc.createElement("div");
|
|
6434
|
+
bar.className = "veo-card-toolbar";
|
|
6435
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6436
|
+
const mk = (action, label, active) => {
|
|
6437
|
+
const b = doc.createElement("button");
|
|
6438
|
+
b.type = "button";
|
|
6439
|
+
b.className = `veo-card-toolbar-${action}${active ? " is-active" : ""}`;
|
|
6440
|
+
b.setAttribute("aria-pressed", active ? "true" : "false");
|
|
6441
|
+
b.textContent = label;
|
|
6442
|
+
b.addEventListener("click", (e) => {
|
|
6443
|
+
e.preventDefault();
|
|
6444
|
+
e.stopPropagation();
|
|
6445
|
+
post({ type: "card-action", payload: { stepIndex, action } });
|
|
6446
|
+
});
|
|
6447
|
+
bar.appendChild(b);
|
|
6448
|
+
};
|
|
6449
|
+
mk("edit-container", "Editar contenedor", panelSection === "step");
|
|
6450
|
+
mk("edit-rows", "Editar filas", panelSection === "structure");
|
|
6451
|
+
card.appendChild(bar);
|
|
6452
|
+
bar.style.top = `${-Math.round((bar.offsetHeight || 34) / 2)}px`;
|
|
6453
|
+
resolveToolbarOverlaps(card);
|
|
6454
|
+
};
|
|
6455
|
+
const attachRowHover = (host, stepIndex) => {
|
|
6456
|
+
const onMove = (e) => {
|
|
6457
|
+
const card = previewCard(host);
|
|
6458
|
+
if (!card) return;
|
|
6459
|
+
const overCard = e.composedPath().some(
|
|
6460
|
+
(n) => n instanceof Element && (n.classList.contains("veo-card") || n.classList.contains("veo-card-toolbar"))
|
|
6461
|
+
);
|
|
6462
|
+
if (overCard !== hoverCard) {
|
|
6463
|
+
hoverCard = overCard;
|
|
6464
|
+
renderCardChrome(host, stepIndex);
|
|
6465
|
+
}
|
|
6466
|
+
if (e.composedPath().some((n) => n instanceof Element && n.classList.contains("veo-row-toolbar"))) {
|
|
6467
|
+
return;
|
|
6468
|
+
}
|
|
6469
|
+
const rect = card.getBoundingClientRect();
|
|
6470
|
+
const x = e.clientX - rect.left - card.clientLeft;
|
|
6471
|
+
const y = e.clientY - rect.top - card.clientTop;
|
|
6472
|
+
const bar = card.querySelector(".veo-row-toolbar");
|
|
6473
|
+
if (bar) {
|
|
6474
|
+
const AIR = 10;
|
|
6475
|
+
if (x >= bar.offsetLeft - AIR && x <= bar.offsetLeft + bar.offsetWidth + AIR && y >= bar.offsetTop - 14 && y <= bar.offsetTop + bar.offsetHeight + AIR) {
|
|
6476
|
+
return;
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
const TOL = 6;
|
|
6480
|
+
const hit = layoutRows(card, previewBlocks).rows.find(
|
|
6481
|
+
(r) => x >= r.rect.left - TOL && x <= r.rect.right + TOL && y >= r.rect.top - TOL && y <= r.rect.bottom + TOL
|
|
6482
|
+
);
|
|
6483
|
+
const next = hit?.id ?? null;
|
|
6484
|
+
if (next === hoverRowId) return;
|
|
6485
|
+
hoverRowId = next;
|
|
6486
|
+
renderRowChrome(host, stepIndex);
|
|
6487
|
+
};
|
|
6488
|
+
const onLeave = () => {
|
|
6489
|
+
if (hoverCard) {
|
|
6490
|
+
hoverCard = false;
|
|
6491
|
+
renderCardChrome(host, stepIndex);
|
|
6492
|
+
}
|
|
6493
|
+
if (hoverRowId === null) return;
|
|
6494
|
+
hoverRowId = null;
|
|
6495
|
+
renderRowChrome(host, stepIndex);
|
|
6496
|
+
};
|
|
6497
|
+
host.addEventListener("mousemove", onMove);
|
|
6498
|
+
host.addEventListener("mouseleave", onLeave);
|
|
6499
|
+
return () => {
|
|
6500
|
+
host.removeEventListener("mousemove", onMove);
|
|
6501
|
+
host.removeEventListener("mouseleave", onLeave);
|
|
6502
|
+
};
|
|
6503
|
+
};
|
|
6504
|
+
const renderGapZones = (host, stepIndex, blocks) => {
|
|
6505
|
+
const card = previewCard(host);
|
|
6506
|
+
if (!card) return;
|
|
6507
|
+
for (const z of card.querySelectorAll(".veo-gap")) z.remove();
|
|
6508
|
+
const doc = card.ownerDocument;
|
|
6509
|
+
const GAP = 18;
|
|
6510
|
+
const EDGE2 = 4;
|
|
6511
|
+
const zone = (kind, x, y, w, h, label, blockId, action) => {
|
|
6512
|
+
const z = doc.createElement("div");
|
|
6513
|
+
z.className = `veo-gap veo-gap--${kind}`;
|
|
6514
|
+
z.setAttribute("data-veo-builder-chrome", "");
|
|
6515
|
+
z.dataset.veoGapAction = action;
|
|
6516
|
+
z.style.left = `${x}px`;
|
|
6517
|
+
z.style.top = `${y}px`;
|
|
6518
|
+
z.style.width = `${Math.max(w, GAP)}px`;
|
|
6519
|
+
z.style.height = `${Math.max(h, GAP)}px`;
|
|
6520
|
+
const line = doc.createElement("i");
|
|
6521
|
+
const btn = doc.createElement("button");
|
|
6522
|
+
btn.type = "button";
|
|
6523
|
+
btn.title = label;
|
|
6524
|
+
btn.setAttribute("aria-label", label);
|
|
6525
|
+
btn.textContent = "+";
|
|
6526
|
+
btn.addEventListener("click", (e) => {
|
|
6527
|
+
e.preventDefault();
|
|
6528
|
+
e.stopPropagation();
|
|
6529
|
+
post({ type: "block-layout", payload: { stepIndex, blockId, action } });
|
|
6530
|
+
});
|
|
6531
|
+
z.append(line, btn);
|
|
6532
|
+
card.appendChild(z);
|
|
6533
|
+
};
|
|
6534
|
+
const { rows, content } = layoutRows(card, blocks);
|
|
6535
|
+
if (rows.length === 0) return;
|
|
6536
|
+
const cw = content.right - content.left;
|
|
6537
|
+
rows.forEach((row, k) => {
|
|
6538
|
+
const first = row.columns[0]?.items[0]?.block.editorId;
|
|
6539
|
+
if (k === 0) {
|
|
6540
|
+
zone(
|
|
6541
|
+
"h",
|
|
6542
|
+
content.left,
|
|
6543
|
+
row.rect.top - EDGE2 - GAP,
|
|
6544
|
+
cw,
|
|
6545
|
+
GAP,
|
|
6546
|
+
"Agregar fila",
|
|
6547
|
+
first,
|
|
6548
|
+
"row-before"
|
|
6549
|
+
);
|
|
6550
|
+
} else {
|
|
6551
|
+
const prev = rows[k - 1];
|
|
6552
|
+
const y = (prev.rect.bottom + row.rect.top) / 2 - GAP / 2;
|
|
6553
|
+
zone("h", content.left, y, cw, GAP, "Agregar fila", first, "row-before");
|
|
6554
|
+
}
|
|
6555
|
+
if (k === rows.length - 1) {
|
|
6556
|
+
zone(
|
|
6557
|
+
"h",
|
|
6558
|
+
content.left,
|
|
6559
|
+
row.rect.bottom + EDGE2,
|
|
6560
|
+
cw,
|
|
6561
|
+
GAP,
|
|
6562
|
+
"Agregar fila",
|
|
6563
|
+
first,
|
|
6564
|
+
"row-after"
|
|
6565
|
+
);
|
|
6566
|
+
}
|
|
6567
|
+
});
|
|
6568
|
+
for (const row of rows) {
|
|
6569
|
+
const rh = row.rect.bottom - row.rect.top;
|
|
6570
|
+
row.columns.forEach((col, j) => {
|
|
6571
|
+
const id = col.items[0]?.block.editorId;
|
|
6572
|
+
if (j === 0) {
|
|
6573
|
+
zone(
|
|
6574
|
+
"v",
|
|
6575
|
+
row.rect.left - EDGE2 - GAP,
|
|
6576
|
+
row.rect.top,
|
|
6577
|
+
GAP,
|
|
6578
|
+
rh,
|
|
6579
|
+
"Agregar columna",
|
|
6580
|
+
id,
|
|
6581
|
+
"col-before"
|
|
6582
|
+
);
|
|
6583
|
+
} else {
|
|
6584
|
+
const prev = row.columns[j - 1];
|
|
6585
|
+
const x = (prev.rect.right + col.rect.left) / 2 - GAP / 2;
|
|
6586
|
+
zone("v", x, row.rect.top, GAP, rh, "Agregar columna", id, "col-before");
|
|
6587
|
+
}
|
|
6588
|
+
if (j === row.columns.length - 1) {
|
|
6589
|
+
zone(
|
|
6590
|
+
"v",
|
|
6591
|
+
row.rect.right + EDGE2,
|
|
6592
|
+
row.rect.top,
|
|
6593
|
+
GAP,
|
|
6594
|
+
rh,
|
|
6595
|
+
"Agregar columna",
|
|
6596
|
+
id,
|
|
6597
|
+
"col-after"
|
|
6598
|
+
);
|
|
6599
|
+
}
|
|
6600
|
+
});
|
|
6601
|
+
}
|
|
6602
|
+
for (const row of rows) {
|
|
6603
|
+
for (const col of row.columns) {
|
|
6604
|
+
const w = col.rect.right - col.rect.left;
|
|
6605
|
+
col.items.forEach((it, i) => {
|
|
6606
|
+
const next = col.items[i + 1];
|
|
6607
|
+
if (next) {
|
|
6608
|
+
const a = rectOf(it.el);
|
|
6609
|
+
const b = rectOf(next.el);
|
|
6610
|
+
const y = (a.bottom + b.top) / 2 - GAP / 2;
|
|
6611
|
+
zone(
|
|
6612
|
+
"h",
|
|
6613
|
+
col.rect.left,
|
|
6614
|
+
y,
|
|
6615
|
+
w,
|
|
6616
|
+
GAP,
|
|
6617
|
+
"Agregar bloque",
|
|
6618
|
+
it.block.editorId,
|
|
6619
|
+
"stack-after"
|
|
6620
|
+
);
|
|
6621
|
+
} else if (row.columns.length > 1 && col.rect.bottom < row.rect.bottom - GAP) {
|
|
6622
|
+
zone(
|
|
6623
|
+
"h",
|
|
6624
|
+
col.rect.left,
|
|
6625
|
+
col.rect.bottom + EDGE2,
|
|
6626
|
+
w,
|
|
6627
|
+
GAP,
|
|
6628
|
+
"Agregar bloque",
|
|
6629
|
+
it.block.editorId,
|
|
6630
|
+
"stack-after"
|
|
6631
|
+
);
|
|
6632
|
+
}
|
|
6633
|
+
});
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
};
|
|
6637
|
+
const attachCardClick = (host, stepIndex) => {
|
|
6638
|
+
let downAt = null;
|
|
6639
|
+
const onDown = (e) => {
|
|
6640
|
+
downAt = { x: e.clientX, y: e.clientY };
|
|
6641
|
+
};
|
|
6642
|
+
const onClick = (e) => {
|
|
6643
|
+
const path = e.composedPath();
|
|
6644
|
+
if (path.some(
|
|
6645
|
+
(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"))
|
|
6646
|
+
)) {
|
|
6647
|
+
return;
|
|
6648
|
+
}
|
|
6649
|
+
const inCard = path.some((n) => n instanceof Element && n.classList.contains("veo-card"));
|
|
6650
|
+
if (!inCard) return;
|
|
6651
|
+
if (downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 4) return;
|
|
6652
|
+
const blockEl = path.find(
|
|
6653
|
+
(n) => n instanceof HTMLElement && !!n.dataset.veoBlockId
|
|
6654
|
+
);
|
|
6655
|
+
if (blockEl) {
|
|
6656
|
+
if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
|
|
6657
|
+
e.preventDefault();
|
|
6658
|
+
e.stopPropagation();
|
|
6659
|
+
}
|
|
6660
|
+
selectedBlockId = blockEl.dataset.veoBlockId ?? null;
|
|
6661
|
+
applyBlockHighlight(host, stepIndex);
|
|
6662
|
+
if (selectedBlockId) {
|
|
6663
|
+
post({ type: "block-clicked", payload: { stepIndex, blockId: selectedBlockId } });
|
|
6664
|
+
}
|
|
6665
|
+
return;
|
|
6666
|
+
}
|
|
6667
|
+
const interactive = path.some(
|
|
6668
|
+
(n) => n instanceof Element && n.matches("button, a, input, textarea, select, label, [contenteditable]")
|
|
6669
|
+
);
|
|
6670
|
+
if (interactive) return;
|
|
6671
|
+
selectedBlockId = null;
|
|
6672
|
+
clearBlockHighlight(host);
|
|
6673
|
+
renderRowChrome(host, stepIndex);
|
|
6674
|
+
post({ type: "card-clicked", payload: { stepIndex } });
|
|
6675
|
+
};
|
|
6676
|
+
host.addEventListener("mousedown", onDown, true);
|
|
6677
|
+
host.addEventListener("click", onClick, true);
|
|
6678
|
+
return () => {
|
|
6679
|
+
host.removeEventListener("mousedown", onDown, true);
|
|
6680
|
+
host.removeEventListener("click", onClick, true);
|
|
6681
|
+
};
|
|
6682
|
+
};
|
|
6683
|
+
const attachRawBlock = (stepIndex, blockId) => {
|
|
6684
|
+
const nodes = Array.from(
|
|
6685
|
+
document.querySelectorAll(`[${RAW_BLOCK_ATTR}="${blockId}"]`)
|
|
6686
|
+
);
|
|
6687
|
+
if (nodes.length === 0) return null;
|
|
6688
|
+
const doc = document;
|
|
6689
|
+
const box = doc.createElement("div");
|
|
6690
|
+
box.setAttribute("data-veo-builder-chrome", "");
|
|
6691
|
+
box.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;z-index:2147483646;";
|
|
6692
|
+
box.style.display = "none";
|
|
6693
|
+
const shadow = box.attachShadow({ mode: "open" });
|
|
6694
|
+
const style = doc.createElement("style");
|
|
6695
|
+
style.textContent = GUIDE_STYLES;
|
|
6696
|
+
shadow.appendChild(style);
|
|
6697
|
+
const frame = doc.createElement("div");
|
|
6698
|
+
frame.className = "veo-block-frame";
|
|
6699
|
+
frame.style.position = "fixed";
|
|
6700
|
+
const bar = doc.createElement("div");
|
|
6701
|
+
bar.className = "veo-block-toolbar";
|
|
6702
|
+
bar.style.position = "fixed";
|
|
6703
|
+
const mk = (action, label, icon, enabled = true) => {
|
|
6704
|
+
const b = doc.createElement("button");
|
|
6705
|
+
b.type = "button";
|
|
6706
|
+
b.title = label;
|
|
6707
|
+
b.setAttribute("aria-label", label);
|
|
6708
|
+
b.className = `veo-block-toolbar-${action}`;
|
|
6709
|
+
b.innerHTML = icon;
|
|
6710
|
+
b.disabled = !enabled;
|
|
6711
|
+
b.addEventListener("click", (e) => {
|
|
6712
|
+
e.preventDefault();
|
|
6713
|
+
e.stopPropagation();
|
|
6714
|
+
post({ type: "block-action", payload: { stepIndex, blockId, action } });
|
|
6715
|
+
});
|
|
6716
|
+
bar.appendChild(b);
|
|
6717
|
+
};
|
|
6718
|
+
mk("edit", "Editar", ICON.pencil);
|
|
6719
|
+
mk("duplicate", "Duplicar", ICON.copy, false);
|
|
6720
|
+
mk("remove", "Eliminar", ICON.trash);
|
|
6721
|
+
mk("move-left", "Mover a la izquierda", ICON_MOVE.left, false);
|
|
6722
|
+
mk("move-right", "Mover a la derecha", ICON_MOVE.right, false);
|
|
6723
|
+
frame.style.display = "none";
|
|
6724
|
+
bar.style.display = "none";
|
|
6725
|
+
const GAP = 18;
|
|
6726
|
+
const EDGE2 = 4;
|
|
6727
|
+
const gap = (kind, label, action) => {
|
|
6728
|
+
const z = doc.createElement("div");
|
|
6729
|
+
z.className = `veo-gap veo-gap--${kind}`;
|
|
6730
|
+
z.style.position = "fixed";
|
|
6731
|
+
z.dataset.veoGapAction = action;
|
|
6732
|
+
const line = doc.createElement("i");
|
|
6733
|
+
const btn = doc.createElement("button");
|
|
6734
|
+
btn.type = "button";
|
|
6735
|
+
btn.title = label;
|
|
6736
|
+
btn.setAttribute("aria-label", label);
|
|
6737
|
+
btn.textContent = "+";
|
|
6738
|
+
btn.addEventListener("click", (e) => {
|
|
6739
|
+
e.preventDefault();
|
|
6740
|
+
e.stopPropagation();
|
|
6741
|
+
post({ type: "block-layout", payload: { stepIndex, blockId, action } });
|
|
6742
|
+
});
|
|
6743
|
+
z.append(line, btn);
|
|
6744
|
+
return z;
|
|
6745
|
+
};
|
|
6746
|
+
const gaps = {
|
|
6747
|
+
top: gap("h", "Agregar fila", "row-before"),
|
|
6748
|
+
bottom: gap("h", "Agregar fila", "row-after"),
|
|
6749
|
+
left: gap("v", "Agregar columna", "col-before"),
|
|
6750
|
+
right: gap("v", "Agregar columna", "col-after")
|
|
6751
|
+
};
|
|
6752
|
+
shadow.append(frame, bar, gaps.top, gaps.bottom, gaps.left, gaps.right);
|
|
6753
|
+
doc.body.appendChild(box);
|
|
6754
|
+
box.style.display = "";
|
|
6755
|
+
let visible = false;
|
|
6756
|
+
const place = (el2, x, y, w, h) => {
|
|
6757
|
+
el2.style.left = `${x}px`;
|
|
6758
|
+
el2.style.top = `${y}px`;
|
|
6759
|
+
el2.style.width = `${w}px`;
|
|
6760
|
+
el2.style.height = `${h}px`;
|
|
6761
|
+
};
|
|
6762
|
+
const reposition = () => {
|
|
6763
|
+
const live = nodes.filter((n) => n.isConnected);
|
|
6764
|
+
if (live.length === 0) return;
|
|
6765
|
+
let l = Number.POSITIVE_INFINITY;
|
|
6766
|
+
let t = Number.POSITIVE_INFINITY;
|
|
6767
|
+
let r = Number.NEGATIVE_INFINITY;
|
|
6768
|
+
let b = Number.NEGATIVE_INFINITY;
|
|
6769
|
+
for (const n of live) {
|
|
6770
|
+
const rc = n.getBoundingClientRect();
|
|
6771
|
+
if (rc.width === 0 && rc.height === 0) continue;
|
|
6772
|
+
l = Math.min(l, rc.left);
|
|
6773
|
+
t = Math.min(t, rc.top);
|
|
6774
|
+
r = Math.max(r, rc.right);
|
|
6775
|
+
b = Math.max(b, rc.bottom);
|
|
6776
|
+
}
|
|
6777
|
+
if (!Number.isFinite(l)) return;
|
|
6778
|
+
place(gaps.top, l, t - EDGE2 - GAP, r - l, GAP);
|
|
6779
|
+
place(gaps.bottom, l, b + EDGE2, r - l, GAP);
|
|
6780
|
+
place(gaps.left, l - EDGE2 - GAP, t, GAP, b - t);
|
|
6781
|
+
place(gaps.right, r + EDGE2, t, GAP, b - t);
|
|
6782
|
+
if (!visible) return;
|
|
6783
|
+
place(frame, l, t, r - l, b - t);
|
|
6784
|
+
const bh = bar.offsetHeight || TOOLBAR_H;
|
|
6785
|
+
const bw = bar.offsetWidth;
|
|
6786
|
+
bar.style.left = `${Math.max(0, r - bw)}px`;
|
|
6787
|
+
bar.style.top = `${Math.max(0, t - bh - 6)}px`;
|
|
6788
|
+
};
|
|
6789
|
+
const select = (id) => {
|
|
6790
|
+
visible = id === blockId;
|
|
6791
|
+
frame.style.display = visible ? "" : "none";
|
|
6792
|
+
bar.style.display = visible ? "" : "none";
|
|
6793
|
+
reposition();
|
|
6794
|
+
};
|
|
6795
|
+
reposition();
|
|
6796
|
+
const onClick = (e) => {
|
|
6797
|
+
const path = e.composedPath();
|
|
6798
|
+
if (!nodes.some((n) => path.includes(n))) return;
|
|
6799
|
+
if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
|
|
6800
|
+
e.preventDefault();
|
|
6801
|
+
e.stopPropagation();
|
|
6802
|
+
}
|
|
6803
|
+
selectedBlockId = blockId;
|
|
6804
|
+
select(blockId);
|
|
6805
|
+
post({ type: "block-clicked", payload: { stepIndex, blockId } });
|
|
6806
|
+
};
|
|
6807
|
+
doc.addEventListener("click", onClick, true);
|
|
6808
|
+
window.addEventListener("scroll", reposition, true);
|
|
6809
|
+
window.addEventListener("resize", reposition);
|
|
6810
|
+
return {
|
|
6811
|
+
select,
|
|
6812
|
+
detach: () => {
|
|
6813
|
+
doc.removeEventListener("click", onClick, true);
|
|
6814
|
+
window.removeEventListener("scroll", reposition, true);
|
|
6815
|
+
window.removeEventListener("resize", reposition);
|
|
6816
|
+
box.remove();
|
|
6817
|
+
}
|
|
6818
|
+
};
|
|
6819
|
+
};
|
|
4939
6820
|
const applyPreviewGated = (cmd) => {
|
|
4940
6821
|
const showNow = !cmd.honorTrigger;
|
|
4941
6822
|
if (showNow) {
|
|
@@ -4955,7 +6836,7 @@ function initBuilderMode() {
|
|
|
4955
6836
|
return;
|
|
4956
6837
|
}
|
|
4957
6838
|
const devices = cmd.guide.activationRules?.devices;
|
|
4958
|
-
if (!showNow &&
|
|
6839
|
+
if (!showNow && previewMode && devices && !isDeviceAllowed(devices)) {
|
|
4959
6840
|
cancelPreviewDelay();
|
|
4960
6841
|
withholdPreview();
|
|
4961
6842
|
postPreviewStatus({ state: "device-mismatch", devices });
|
|
@@ -5007,15 +6888,16 @@ function initBuilderMode() {
|
|
|
5007
6888
|
postPreviewStatus({ state: "waiting-delay", delayMs });
|
|
5008
6889
|
};
|
|
5009
6890
|
const applyPreview = (cmd) => {
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
6891
|
+
if (cmd.editable && !previewMode && tryPatchPreview(cmd)) {
|
|
6892
|
+
previewStepIndex = cmd.guide.startStepIndex ?? 0;
|
|
6893
|
+
return;
|
|
6894
|
+
}
|
|
6895
|
+
detachPreviewTools();
|
|
5014
6896
|
previewStepIndex = cmd.guide.startStepIndex ?? 0;
|
|
5015
6897
|
const rules = cmd.guide.activationRules;
|
|
5016
6898
|
const trig = rules?.trigger;
|
|
5017
6899
|
const sel = rules?.selector?.trim();
|
|
5018
|
-
const gate = cmd.honorTrigger && !previewFired &&
|
|
6900
|
+
const gate = cmd.honorTrigger && !previewFired && previewMode;
|
|
5019
6901
|
const waitsForTarget = (trig === "on_click" || trig === "on_hover") && (!isAutomaticActivation(rules) || previewDismissed);
|
|
5020
6902
|
if (gate && waitsForTarget && sel) {
|
|
5021
6903
|
cancelPreviewDelay();
|
|
@@ -5032,13 +6914,13 @@ function initBuilderMode() {
|
|
|
5032
6914
|
}
|
|
5033
6915
|
disarmPreviewTrigger();
|
|
5034
6916
|
cancelPreviewDelay();
|
|
6917
|
+
const fetchProgress = previewProgressFetcher();
|
|
5035
6918
|
const handle = previewGuide({
|
|
5036
6919
|
...cmd.guide,
|
|
6920
|
+
...fetchProgress ? { fetchProgress } : {},
|
|
5037
6921
|
onClosed: () => {
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
dropTarget?.detach();
|
|
5041
|
-
dropTarget = null;
|
|
6922
|
+
detachPreviewTools();
|
|
6923
|
+
shownPreview = null;
|
|
5042
6924
|
previewFired = false;
|
|
5043
6925
|
previewDismissed = true;
|
|
5044
6926
|
if (previewMode) {
|
|
@@ -5046,9 +6928,7 @@ function initBuilderMode() {
|
|
|
5046
6928
|
const t = r?.trigger;
|
|
5047
6929
|
const selector = r?.selector?.trim();
|
|
5048
6930
|
if ((t === "on_click" || t === "on_hover") && selector) armPreviewTrigger(selector, t);
|
|
5049
|
-
return;
|
|
5050
6931
|
}
|
|
5051
|
-
if (lastPreview) applyPreviewGated(lastPreview);
|
|
5052
6932
|
}
|
|
5053
6933
|
});
|
|
5054
6934
|
if (cmd.honorTrigger) postPreviewStatus({ state: "shown" });
|
|
@@ -5056,7 +6936,23 @@ function initBuilderMode() {
|
|
|
5056
6936
|
void handle.ready.then((result) => {
|
|
5057
6937
|
if (!result.rendered) return;
|
|
5058
6938
|
const host = handle.host();
|
|
5059
|
-
if (!host)
|
|
6939
|
+
if (!host) {
|
|
6940
|
+
const idx = cmd.guide.startStepIndex ?? 0;
|
|
6941
|
+
const only = cmd.guide.guideSteps[idx]?.blocks?.[0];
|
|
6942
|
+
if (only?.type === "code" && only.editorId) {
|
|
6943
|
+
rawBlock = attachRawBlock(idx, only.editorId);
|
|
6944
|
+
rawBlock?.select(selectedBlockId);
|
|
6945
|
+
}
|
|
6946
|
+
return;
|
|
6947
|
+
}
|
|
6948
|
+
host.classList.add("veo-no-anim");
|
|
6949
|
+
shownPreview = cmd;
|
|
6950
|
+
cardClick = attachCardClick(host, cmd.guide.startStepIndex ?? 0);
|
|
6951
|
+
const stepBlocks = cmd.guide.guideSteps[cmd.guide.startStepIndex ?? 0]?.blocks ?? [];
|
|
6952
|
+
previewBlocks = stepBlocks;
|
|
6953
|
+
applyBlockHighlight(host, cmd.guide.startStepIndex ?? 0);
|
|
6954
|
+
renderGapZones(host, cmd.guide.startStepIndex ?? 0, stepBlocks);
|
|
6955
|
+
rowHover = attachRowHover(host, cmd.guide.startStepIndex ?? 0);
|
|
5060
6956
|
manipulator = attachDesignManipulator({
|
|
5061
6957
|
host,
|
|
5062
6958
|
guideType: cmd.guide.guideType,
|
|
@@ -5124,13 +7020,21 @@ function initBuilderMode() {
|
|
|
5124
7020
|
break;
|
|
5125
7021
|
case "editor-state":
|
|
5126
7022
|
chrome?.setState(cmd.state);
|
|
7023
|
+
if (panelSection !== cmd.state.section) {
|
|
7024
|
+
panelSection = cmd.state.section;
|
|
7025
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7026
|
+
if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
|
|
7027
|
+
}
|
|
5127
7028
|
panel?.setTitle(
|
|
5128
|
-
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"
|
|
7029
|
+
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"
|
|
5129
7030
|
);
|
|
5130
7031
|
break;
|
|
5131
7032
|
case "panel-visibility":
|
|
5132
7033
|
panel?.setVisible(cmd.visible);
|
|
5133
7034
|
break;
|
|
7035
|
+
case "panel-size":
|
|
7036
|
+
panel?.setSize(cmd.size);
|
|
7037
|
+
break;
|
|
5134
7038
|
case "start-picker": {
|
|
5135
7039
|
picker?.stop();
|
|
5136
7040
|
const panelWasShown = panel?.isVisible() ?? false;
|
|
@@ -5138,10 +7042,7 @@ function initBuilderMode() {
|
|
|
5138
7042
|
chrome?.setHidden(true);
|
|
5139
7043
|
const previewToRestore = lastPreview;
|
|
5140
7044
|
if (previewToRestore) {
|
|
5141
|
-
|
|
5142
|
-
manipulator = null;
|
|
5143
|
-
dropTarget?.detach();
|
|
5144
|
-
dropTarget = null;
|
|
7045
|
+
detachPreviewTools();
|
|
5145
7046
|
closeGuidePreview();
|
|
5146
7047
|
}
|
|
5147
7048
|
const restorePanel = () => {
|
|
@@ -5180,11 +7081,21 @@ function initBuilderMode() {
|
|
|
5180
7081
|
case "block-drag":
|
|
5181
7082
|
handleBlockDrag(cmd);
|
|
5182
7083
|
break;
|
|
7084
|
+
case "select-block": {
|
|
7085
|
+
selectedBlockId = cmd.blockId;
|
|
7086
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7087
|
+
if (h && !previewMode) applyBlockHighlight(h, previewStepIndex);
|
|
7088
|
+
if (!previewMode) rawBlock?.select(cmd.blockId);
|
|
7089
|
+
break;
|
|
7090
|
+
}
|
|
7091
|
+
case "select-row": {
|
|
7092
|
+
selectedRowId = cmd.blockId;
|
|
7093
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7094
|
+
if (h && !previewMode) renderRowChrome(h, previewStepIndex);
|
|
7095
|
+
break;
|
|
7096
|
+
}
|
|
5183
7097
|
case "close-preview":
|
|
5184
|
-
|
|
5185
|
-
manipulator = null;
|
|
5186
|
-
dropTarget?.detach();
|
|
5187
|
-
dropTarget = null;
|
|
7098
|
+
detachPreviewTools();
|
|
5188
7099
|
hideDragGhost();
|
|
5189
7100
|
pendingPreview = null;
|
|
5190
7101
|
lastPreview = null;
|
|
@@ -5237,10 +7148,7 @@ function initBuilderMode() {
|
|
|
5237
7148
|
removeNavWatcher();
|
|
5238
7149
|
picker?.stop();
|
|
5239
7150
|
picker = null;
|
|
5240
|
-
|
|
5241
|
-
manipulator = null;
|
|
5242
|
-
dropTarget?.detach();
|
|
5243
|
-
dropTarget = null;
|
|
7151
|
+
detachPreviewTools();
|
|
5244
7152
|
hideDragGhost();
|
|
5245
7153
|
pendingPreview = null;
|
|
5246
7154
|
disarmPreviewTrigger();
|
|
@@ -5268,6 +7176,8 @@ function initBuilderMode() {
|
|
|
5268
7176
|
case "preview":
|
|
5269
7177
|
if (previewMode) return;
|
|
5270
7178
|
previewMode = true;
|
|
7179
|
+
previewFired = false;
|
|
7180
|
+
previewDismissed = false;
|
|
5271
7181
|
panelWasVisible = panel?.isVisible() ?? true;
|
|
5272
7182
|
panel?.setVisible(false);
|
|
5273
7183
|
chrome?.setPreview(true);
|
|
@@ -5276,6 +7186,8 @@ function initBuilderMode() {
|
|
|
5276
7186
|
case "exit-preview":
|
|
5277
7187
|
if (!previewMode) return;
|
|
5278
7188
|
previewMode = false;
|
|
7189
|
+
disarmPreviewTrigger();
|
|
7190
|
+
cancelPreviewDelay();
|
|
5279
7191
|
chrome?.setPreview(false);
|
|
5280
7192
|
panel?.setVisible(panelWasVisible);
|
|
5281
7193
|
if (lastPreview) applyPreviewGated(lastPreview);
|
|
@@ -5357,6 +7269,25 @@ function resolveDashboardOrigin(originHint) {
|
|
|
5357
7269
|
}
|
|
5358
7270
|
return null;
|
|
5359
7271
|
}
|
|
7272
|
+
function previewProgressFetcher() {
|
|
7273
|
+
const cfg = window.__veoBuilder;
|
|
7274
|
+
if (!cfg?.apiKey || !cfg?.apiUrl) return void 0;
|
|
7275
|
+
const apiUrl = cfg.apiUrl.replace(/\/+$/, "");
|
|
7276
|
+
const apiKey = cfg.apiKey;
|
|
7277
|
+
return async (guideIds) => {
|
|
7278
|
+
const params = new URLSearchParams({
|
|
7279
|
+
endUserId: "veo_preview",
|
|
7280
|
+
guideIds: guideIds.join(","),
|
|
7281
|
+
includeUnpublished: "1"
|
|
7282
|
+
});
|
|
7283
|
+
const res = await fetch(`${apiUrl}/v1/guides/progress?${params}`, {
|
|
7284
|
+
headers: { "X-Api-Key": apiKey }
|
|
7285
|
+
});
|
|
7286
|
+
if (!res.ok) return [];
|
|
7287
|
+
const data = await res.json();
|
|
7288
|
+
return data.items ?? [];
|
|
7289
|
+
};
|
|
7290
|
+
}
|
|
5360
7291
|
|
|
5361
7292
|
// src/plugins/builder/builder-session.ts
|
|
5362
7293
|
var DEFAULT_FEATURES = "popup,width=1280,height=860";
|