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/index.cjs
CHANGED
|
@@ -571,6 +571,129 @@ var init_rich_text = __esm({
|
|
|
571
571
|
}
|
|
572
572
|
});
|
|
573
573
|
|
|
574
|
+
// src/plugins/guides/video-block.ts
|
|
575
|
+
function videoEmbedUrl(provider, url) {
|
|
576
|
+
let u;
|
|
577
|
+
try {
|
|
578
|
+
u = new URL(url);
|
|
579
|
+
} catch {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
583
|
+
const host = u.hostname.replace(/^www\./, "").replace(/^m\./, "");
|
|
584
|
+
const id = (re, from) => {
|
|
585
|
+
const m = from.match(re);
|
|
586
|
+
return m?.[1] ?? null;
|
|
587
|
+
};
|
|
588
|
+
switch (provider) {
|
|
589
|
+
case "youtube": {
|
|
590
|
+
let v = null;
|
|
591
|
+
if (host === "youtube.com" || host === "youtube-nocookie.com") {
|
|
592
|
+
v = u.searchParams.get("v") ?? id(/^\/(?:embed|shorts|live)\/([A-Za-z0-9_-]{6,})/, u.pathname);
|
|
593
|
+
} else if (host === "youtu.be") {
|
|
594
|
+
v = id(/^\/([A-Za-z0-9_-]{6,})/, u.pathname);
|
|
595
|
+
}
|
|
596
|
+
if (!v || !/^[A-Za-z0-9_-]{6,}$/.test(v)) return null;
|
|
597
|
+
return `https://www.youtube-nocookie.com/embed/${v}`;
|
|
598
|
+
}
|
|
599
|
+
case "vimeo": {
|
|
600
|
+
if (host !== "vimeo.com" && host !== "player.vimeo.com") return null;
|
|
601
|
+
const v = id(/(\d{6,})/, u.pathname);
|
|
602
|
+
return v ? `https://player.vimeo.com/video/${v}` : null;
|
|
603
|
+
}
|
|
604
|
+
case "loom": {
|
|
605
|
+
if (host !== "loom.com") return null;
|
|
606
|
+
const v = id(/^\/(?:share|embed)\/([A-Za-z0-9]{16,})/, u.pathname);
|
|
607
|
+
return v ? `https://www.loom.com/embed/${v}` : null;
|
|
608
|
+
}
|
|
609
|
+
case "wistia": {
|
|
610
|
+
if (!under(host, "wistia.com") && !under(host, "wistia.net")) return null;
|
|
611
|
+
const v = id(/\/(?:medias|embed\/iframe)\/([A-Za-z0-9]{6,})/, u.pathname);
|
|
612
|
+
return v ? `https://fast.wistia.net/embed/iframe/${v}` : null;
|
|
613
|
+
}
|
|
614
|
+
case "msstream": {
|
|
615
|
+
if (!under(host, "microsoftstream.com")) return null;
|
|
616
|
+
const v = id(/\/(?:embed\/)?video\/([0-9a-f-]{36})/i, u.pathname);
|
|
617
|
+
return v ? `https://web.microsoftstream.com/embed/video/${v}?autoplay=false&showinfo=true` : null;
|
|
618
|
+
}
|
|
619
|
+
case "vidyard": {
|
|
620
|
+
if (!under(host, "vidyard.com")) return null;
|
|
621
|
+
const v = id(/\/watch\/([A-Za-z0-9_-]{8,})/, u.pathname) ?? (host === "play.vidyard.com" ? id(/^\/([A-Za-z0-9_-]{8,})/, u.pathname) : null);
|
|
622
|
+
return v ? `https://play.vidyard.com/${v}` : null;
|
|
623
|
+
}
|
|
624
|
+
case "sharepoint":
|
|
625
|
+
return u.protocol === "https:" && under(host, "sharepoint.com") ? u.href : null;
|
|
626
|
+
case "brightcove":
|
|
627
|
+
return u.protocol === "https:" && host === "players.brightcove.net" && u.searchParams.get("videoId") ? u.href : null;
|
|
628
|
+
case "kaltura":
|
|
629
|
+
return u.protocol === "https:" && under(host, "kaltura.com") && u.pathname.startsWith("/p/") ? u.href : null;
|
|
630
|
+
default:
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
function under(host, domain) {
|
|
635
|
+
return host === domain || host.endsWith(`.${domain}`);
|
|
636
|
+
}
|
|
637
|
+
function fileVideoUrl(url) {
|
|
638
|
+
try {
|
|
639
|
+
const u = new URL(url);
|
|
640
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
641
|
+
return /\.(mp4|webm|mov)$/i.test(u.pathname) ? u.href : null;
|
|
642
|
+
} catch {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function buildVideoBlock(block, doc, opts = {}) {
|
|
647
|
+
const isFile = block.provider === "file";
|
|
648
|
+
const src = typeof block.url === "string" ? isFile ? fileVideoUrl(block.url) : videoEmbedUrl(block.provider, block.url) : null;
|
|
649
|
+
if (!src) return null;
|
|
650
|
+
const s = block.style ?? {};
|
|
651
|
+
const wrap = doc.createElement("div");
|
|
652
|
+
wrap.className = "veo-guide-video";
|
|
653
|
+
const ratio = s.aspect === "4:3" ? "4 / 3" : "16 / 9";
|
|
654
|
+
wrap.style.aspectRatio = ratio;
|
|
655
|
+
if (s.widthMode === "fill") {
|
|
656
|
+
wrap.style.width = "100%";
|
|
657
|
+
} else {
|
|
658
|
+
const w = typeof s.width === "number" && Number.isFinite(s.width) ? s.width : 400;
|
|
659
|
+
wrap.style.width = `${Math.max(120, Math.min(1600, w))}px`;
|
|
660
|
+
wrap.style.maxWidth = "100%";
|
|
661
|
+
}
|
|
662
|
+
const align = s.align === "center" ? "center" : s.align === "right" ? "flex-end" : "flex-start";
|
|
663
|
+
wrap.style.alignSelf = align;
|
|
664
|
+
if (isFile) {
|
|
665
|
+
const video = doc.createElement("video");
|
|
666
|
+
video.src = src;
|
|
667
|
+
video.controls = true;
|
|
668
|
+
video.setAttribute("playsinline", "");
|
|
669
|
+
video.preload = "metadata";
|
|
670
|
+
video.setAttribute(
|
|
671
|
+
"aria-label",
|
|
672
|
+
typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video"
|
|
673
|
+
);
|
|
674
|
+
if (opts.editing) video.style.pointerEvents = "none";
|
|
675
|
+
wrap.appendChild(video);
|
|
676
|
+
return wrap;
|
|
677
|
+
}
|
|
678
|
+
const frame = doc.createElement("iframe");
|
|
679
|
+
frame.src = src;
|
|
680
|
+
frame.title = typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video";
|
|
681
|
+
frame.setAttribute("loading", "lazy");
|
|
682
|
+
frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
683
|
+
frame.setAttribute(
|
|
684
|
+
"allow",
|
|
685
|
+
`accelerometer; autoplay; clipboard-write; encrypted-media; picture-in-picture${opts.editing ? "" : "; fullscreen"}`
|
|
686
|
+
);
|
|
687
|
+
if (!opts.editing) frame.setAttribute("allowfullscreen", "");
|
|
688
|
+
if (opts.editing) frame.style.pointerEvents = "none";
|
|
689
|
+
wrap.appendChild(frame);
|
|
690
|
+
return wrap;
|
|
691
|
+
}
|
|
692
|
+
var init_video_block = __esm({
|
|
693
|
+
"src/plugins/guides/video-block.ts"() {
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
|
|
574
697
|
// src/plugins/guides/block-builder.ts
|
|
575
698
|
function buildStepContent(step, doc, callbacks) {
|
|
576
699
|
const container = doc.createElement("div");
|
|
@@ -695,8 +818,12 @@ function renderBlocks(container, blocks, doc, callbacks) {
|
|
|
695
818
|
i++;
|
|
696
819
|
}
|
|
697
820
|
}
|
|
698
|
-
function buildContentBlock(block, doc) {
|
|
821
|
+
function buildContentBlock(block, doc, opts = {}) {
|
|
699
822
|
switch (block.type) {
|
|
823
|
+
case "video":
|
|
824
|
+
return buildVideoBlock(block, doc, opts);
|
|
825
|
+
case "divider":
|
|
826
|
+
return buildDividerBlock(block, doc);
|
|
700
827
|
case "title": {
|
|
701
828
|
const el2 = doc.createElement("h2");
|
|
702
829
|
el2.className = "veo-guide-title";
|
|
@@ -720,7 +847,7 @@ function buildContentBlock(block, doc) {
|
|
|
720
847
|
const el2 = doc.createElement("img");
|
|
721
848
|
el2.className = "veo-guide-image";
|
|
722
849
|
el2.src = block.url;
|
|
723
|
-
el2.alt = "";
|
|
850
|
+
el2.alt = typeof block.alt === "string" ? block.alt : "";
|
|
724
851
|
applyBlockStyle(el2, block.style, "image");
|
|
725
852
|
return el2;
|
|
726
853
|
}
|
|
@@ -728,6 +855,31 @@ function buildContentBlock(block, doc) {
|
|
|
728
855
|
return null;
|
|
729
856
|
}
|
|
730
857
|
}
|
|
858
|
+
function buildDividerBlock(block, doc) {
|
|
859
|
+
const s = block.style ?? {};
|
|
860
|
+
const wrap = doc.createElement("div");
|
|
861
|
+
wrap.className = "veo-guide-divider";
|
|
862
|
+
wrap.setAttribute("role", "separator");
|
|
863
|
+
const hr = doc.createElement("hr");
|
|
864
|
+
const custom = s.preset === "custom";
|
|
865
|
+
const color = custom && typeof s.color === "string" && s.color ? s.color : s.colorMode === "dark" ? "rgba(255, 255, 255, 0.35)" : "#e5e7eb";
|
|
866
|
+
const thickness = custom && typeof s.thickness === "number" && Number.isFinite(s.thickness) ? clampNum(Math.round(s.thickness), 1, 12) : 1;
|
|
867
|
+
const lineStyle = typeof s.lineStyle === "string" && DIVIDER_LINE_STYLES.has(s.lineStyle) ? s.lineStyle : "solid";
|
|
868
|
+
hr.style.borderTop = `${thickness}px ${lineStyle} ${color}`;
|
|
869
|
+
wrap.appendChild(hr);
|
|
870
|
+
for (const [k, prop] of [
|
|
871
|
+
["marginTop", "marginTop"],
|
|
872
|
+
["marginRight", "marginRight"],
|
|
873
|
+
["marginBottom", "marginBottom"],
|
|
874
|
+
["marginLeft", "marginLeft"]
|
|
875
|
+
]) {
|
|
876
|
+
const v = s[k];
|
|
877
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
878
|
+
wrap.style[prop] = `${clampNum(v, 0, 64)}px`;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return wrap;
|
|
882
|
+
}
|
|
731
883
|
function buildButtonBlock(block, doc, callbacks) {
|
|
732
884
|
const btn = doc.createElement("button");
|
|
733
885
|
btn.type = "button";
|
|
@@ -758,14 +910,34 @@ function applyBlockStyle(el2, style, kind) {
|
|
|
758
910
|
}
|
|
759
911
|
const align = typeof s.align === "string" ? s.align : null;
|
|
760
912
|
if (align && (align === "left" || align === "center" || align === "right")) {
|
|
761
|
-
if (kind === "text")
|
|
762
|
-
|
|
913
|
+
if (kind === "text") {
|
|
914
|
+
el2.style.textAlign = align;
|
|
915
|
+
el2.style.alignSelf = BLOCK_ALIGN_SELF[align];
|
|
916
|
+
} else if (kind === "image") el2.style.alignSelf = BLOCK_ALIGN_SELF[align];
|
|
917
|
+
}
|
|
918
|
+
if (kind === "text" && typeof s.preset === "string" && s.preset in TEXT_PRESETS) {
|
|
919
|
+
const preset = TEXT_PRESETS[s.preset];
|
|
920
|
+
if (preset) {
|
|
921
|
+
el2.style.fontSize = `${preset.size}px`;
|
|
922
|
+
el2.style.fontWeight = String(preset.weight);
|
|
923
|
+
el2.style.lineHeight = String(preset.lineHeight);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (kind !== "image" && s.colorMode === "dark") {
|
|
927
|
+
el2.style.background = "#1f1d1b";
|
|
928
|
+
el2.style.color = "#f5f0eb";
|
|
929
|
+
el2.style.borderRadius = el2.style.borderRadius || "8px";
|
|
763
930
|
}
|
|
764
931
|
if (kind !== "image" && typeof s.color === "string") el2.style.color = s.color;
|
|
765
932
|
if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
|
|
766
933
|
el2.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
|
|
767
934
|
}
|
|
768
|
-
if (kind === "image" &&
|
|
935
|
+
if (kind === "image" && s.naturalSize === true) {
|
|
936
|
+
el2.style.width = "auto";
|
|
937
|
+
el2.style.maxWidth = "100%";
|
|
938
|
+
el2.style.maxHeight = "none";
|
|
939
|
+
el2.style.objectFit = "contain";
|
|
940
|
+
} else if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
|
|
769
941
|
el2.style.width = `${clampNum(s.width, 10, 100)}%`;
|
|
770
942
|
}
|
|
771
943
|
if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
|
|
@@ -844,16 +1016,24 @@ function readCtaUrl(step) {
|
|
|
844
1016
|
if (typeof candidate !== "string") return void 0;
|
|
845
1017
|
return isSafeUrl(candidate) ? candidate : void 0;
|
|
846
1018
|
}
|
|
847
|
-
var BLOCK_ALIGN_SELF;
|
|
1019
|
+
var DIVIDER_LINE_STYLES, BLOCK_ALIGN_SELF, TEXT_PRESETS;
|
|
848
1020
|
var init_block_builder = __esm({
|
|
849
1021
|
"src/plugins/guides/block-builder.ts"() {
|
|
850
1022
|
init_constants2();
|
|
851
1023
|
init_rich_text();
|
|
1024
|
+
init_video_block();
|
|
1025
|
+
DIVIDER_LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dotted", "dashed"]);
|
|
852
1026
|
BLOCK_ALIGN_SELF = {
|
|
853
1027
|
left: "flex-start",
|
|
854
1028
|
center: "center",
|
|
855
1029
|
right: "flex-end"
|
|
856
1030
|
};
|
|
1031
|
+
TEXT_PRESETS = {
|
|
1032
|
+
title: { size: 18, weight: 600, lineHeight: 1.3 },
|
|
1033
|
+
subtitle: { size: 16, weight: 600, lineHeight: 1.35 },
|
|
1034
|
+
paragraph: { size: 14, weight: 400, lineHeight: 1.5 },
|
|
1035
|
+
small: { size: 12, weight: 400, lineHeight: 1.5 }
|
|
1036
|
+
};
|
|
857
1037
|
}
|
|
858
1038
|
});
|
|
859
1039
|
|
|
@@ -1216,7 +1396,161 @@ var init_styles = __esm({
|
|
|
1216
1396
|
}
|
|
1217
1397
|
/* Card gen\xE9rica del modelo unificado. El dise\xF1o (bg/borde/padding/\u2026) lo aplica
|
|
1218
1398
|
el renderer como estilos inline desde step.design; ac\xE1 van los defaults. */
|
|
1399
|
+
/* Edici\xF3n (builder): sin animaci\xF3n de entrada al repintar la preview. */
|
|
1400
|
+
:host(.veo-no-anim) * { animation: none !important; }
|
|
1401
|
+
|
|
1402
|
+
/* Bloque video: caja con relaci\xF3n de aspecto; el iframe la llena. */
|
|
1403
|
+
.veo-guide-video { position: relative; overflow: hidden; border-radius: 8px; background: #000; }
|
|
1404
|
+
.veo-guide-video iframe, .veo-guide-video video {
|
|
1405
|
+
position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/* \u2500\u2500 Layout por filas/columnas (composite) \u2500\u2500
|
|
1409
|
+
El espacio tiene UNA fuente por capa: el aire con el borde lo da el padding
|
|
1410
|
+
de la card; la separaci\xF3n entre filas/bloques la da el margen inferior
|
|
1411
|
+
(12px, ah\xED vive la l\xEDnea \xAB+\xBB del editor); los bloques NO llevan padding
|
|
1412
|
+
propio (solo el que el autor les ponga inline). El \xFAltimo elemento no deja
|
|
1413
|
+
margen colgando. */
|
|
1414
|
+
.veo-card > [data-veo-block], .veo-card > .veo-row, .veo-card > .veo-guide-actions,
|
|
1415
|
+
.veo-col > [data-veo-block], .veo-col > .veo-guide-actions { margin-bottom: 12px; }
|
|
1416
|
+
/* La card y cada columna son columnas flex: cada bloque mide SU contenido (como
|
|
1417
|
+
el bot\xF3n) y se ubica con su alineaci\xF3n (align-self); filas, columnas, formularios
|
|
1418
|
+
y filas de botones ocupan todo el ancho. */
|
|
1419
|
+
.veo-card:not(.veo-card--empty), .veo-col {
|
|
1420
|
+
display: flex; flex-direction: column; align-items: flex-start;
|
|
1421
|
+
}
|
|
1422
|
+
.veo-card > [data-veo-block], .veo-col > [data-veo-block] { max-width: 100%; }
|
|
1423
|
+
/* L\xEDnea horizontal: ocupa todo el ancho; el padding del bloque es solo vertical
|
|
1424
|
+
(zona clickeable en el editor) y la l\xEDnea es el borde superior del hr. */
|
|
1425
|
+
.veo-card > .veo-guide-divider, .veo-col > .veo-guide-divider { padding: 6px 0; align-self: stretch; }
|
|
1426
|
+
.veo-guide-divider { box-sizing: border-box; }
|
|
1427
|
+
/* Bloque de c\xF3digo (modo host): ocupa todo el ancho, como un formulario. */
|
|
1428
|
+
.veo-code-host { display: block; min-width: 0; }
|
|
1429
|
+
.veo-card > .veo-code-host, .veo-col > .veo-code-host { align-self: stretch; }
|
|
1430
|
+
.veo-guide-divider hr { margin: 0; border: 0; height: 0; }
|
|
1431
|
+
.veo-card > .veo-row, .veo-card > .veo-guide-actions, .veo-card > .veo-form,
|
|
1432
|
+
.veo-col > .veo-guide-actions, .veo-col > .veo-form { align-self: stretch; }
|
|
1433
|
+
.veo-row { display: flex; gap: 12px; align-items: flex-start; box-sizing: border-box; }
|
|
1434
|
+
.veo-col { flex: 1 1 0; min-width: 0; }
|
|
1435
|
+
.veo-col > :last-child { margin-bottom: 0; }
|
|
1436
|
+
/* Fila con alineaci\xF3n horizontal (editor de fila): las columnas miden su contenido. */
|
|
1437
|
+
.veo-row--aligned > .veo-col { flex: 0 1 auto; }
|
|
1438
|
+
|
|
1439
|
+
/* \u2500\u2500 Editor in-app: bloque seleccionado + barra de acciones + placeholders \u2500\u2500 */
|
|
1440
|
+
.veo-block-placeholder {
|
|
1441
|
+
border: 1px dashed #b0a9a5; border-radius: 6px; padding: 12px; margin-bottom: 12px;
|
|
1442
|
+
color: #8a8582; font-size: 13px; text-align: center;
|
|
1443
|
+
}
|
|
1444
|
+
/* El bloque seleccionado no lleva contorno propio: lo rodea .veo-block-frame
|
|
1445
|
+
(marco aparte, con aire y alto m\xEDnimo, que no altera el layout). */
|
|
1446
|
+
.veo-block--selected { position: relative; z-index: 1; }
|
|
1447
|
+
.veo-block-frame {
|
|
1448
|
+
position: absolute; z-index: 4; pointer-events: none; box-sizing: border-box;
|
|
1449
|
+
border: 2px solid #3b82f6; border-radius: 10px; background: rgba(59, 130, 246, 0.04);
|
|
1450
|
+
}
|
|
1451
|
+
/* Barra del bloque (mock): p\xEDldora blanca con sombra, solo \xEDconos; la papelera
|
|
1452
|
+
en rojo, separada por una l\xEDnea. */
|
|
1453
|
+
.veo-block-toolbar {
|
|
1454
|
+
position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
|
|
1455
|
+
background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
|
|
1456
|
+
box-shadow: 0 6px 20px rgba(0,0,0,0.16);
|
|
1457
|
+
}
|
|
1458
|
+
.veo-block-toolbar button {
|
|
1459
|
+
border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px; border-radius: 8px;
|
|
1460
|
+
display: inline-flex; align-items: center; justify-content: center; line-height: 1;
|
|
1461
|
+
}
|
|
1462
|
+
.veo-block-toolbar button:hover { background: #f5efeb; }
|
|
1463
|
+
.veo-block-toolbar button svg { width: 20px; height: 20px; }
|
|
1464
|
+
.veo-block-toolbar .veo-block-toolbar-remove { color: #e5484d; }
|
|
1465
|
+
/* Separadores: antes de la papelera y antes de las flechas de mover. */
|
|
1466
|
+
.veo-block-toolbar .veo-block-toolbar-remove, .veo-block-toolbar .veo-block-toolbar-move-left {
|
|
1467
|
+
margin-left: 6px; position: relative;
|
|
1468
|
+
}
|
|
1469
|
+
.veo-block-toolbar .veo-block-toolbar-remove::before, .veo-block-toolbar .veo-block-toolbar-move-left::before {
|
|
1470
|
+
content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
|
|
1471
|
+
}
|
|
1472
|
+
.veo-block-toolbar button:disabled { opacity: 0.3; cursor: default; background: transparent; }
|
|
1473
|
+
/* Contenedor (hover): contorno azul de la tarjeta y dos pesta\xF1as centradas
|
|
1474
|
+
sobre su borde superior: \xABEditar contenedor\xBB (outline) y \xABEditar filas\xBB. */
|
|
1475
|
+
.veo-card-frame {
|
|
1476
|
+
position: absolute; z-index: 3; inset: 0; pointer-events: none; box-sizing: border-box;
|
|
1477
|
+
border: 2px solid #3b82f6; border-radius: inherit;
|
|
1478
|
+
}
|
|
1479
|
+
.veo-card-toolbar {
|
|
1480
|
+
position: absolute; z-index: 6; left: 50%; transform: translateX(-50%);
|
|
1481
|
+
display: inline-flex; align-items: flex-end; gap: 8px; white-space: nowrap;
|
|
1482
|
+
}
|
|
1483
|
+
.veo-card-toolbar button {
|
|
1484
|
+
cursor: pointer; padding: 8px 14px; border-radius: 8px; border: 2px solid #3b82f6;
|
|
1485
|
+
font: 600 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
1486
|
+
background: #fff; color: #3b82f6;
|
|
1487
|
+
}
|
|
1488
|
+
.veo-card-toolbar button:hover { background: #eff6ff; }
|
|
1489
|
+
/* Con fondo = la vista que el panel muestra ahora (contenedor o estructura). */
|
|
1490
|
+
.veo-card-toolbar button.is-active { background: #3b82f6; color: #fff; }
|
|
1491
|
+
.veo-card-toolbar button.is-active:hover { background: #2563eb; border-color: #2563eb; }
|
|
1492
|
+
/* Editor de fila: marco al pasar el mouse por una fila (o la fila del bloque
|
|
1493
|
+
seleccionado) y barra \xABEditar fila \xB7 duplicar\xBB bajo su esquina derecha. */
|
|
1494
|
+
.veo-row-frame {
|
|
1495
|
+
position: absolute; z-index: 3; pointer-events: none; box-sizing: border-box;
|
|
1496
|
+
border: 2px solid #3b82f6; border-radius: 10px;
|
|
1497
|
+
}
|
|
1498
|
+
.veo-row-toolbar {
|
|
1499
|
+
position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
|
|
1500
|
+
background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
|
|
1501
|
+
box-shadow: 0 6px 20px rgba(0,0,0,0.16); white-space: nowrap;
|
|
1502
|
+
}
|
|
1503
|
+
.veo-row-toolbar button {
|
|
1504
|
+
border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px 8px; border-radius: 8px;
|
|
1505
|
+
display: inline-flex; align-items: center; gap: 8px; line-height: 1;
|
|
1506
|
+
font: 500 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
1507
|
+
}
|
|
1508
|
+
.veo-row-toolbar button:hover { background: #f5efeb; }
|
|
1509
|
+
.veo-row-toolbar button svg { width: 20px; height: 20px; }
|
|
1510
|
+
.veo-row-toolbar .veo-row-toolbar-duplicate { margin-left: 6px; position: relative; }
|
|
1511
|
+
.veo-row-toolbar .veo-row-toolbar-duplicate::before {
|
|
1512
|
+
content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
|
|
1513
|
+
}
|
|
1514
|
+
.veo-row-toolbar .veo-row-toolbar-remove { color: #e5484d; }
|
|
1515
|
+
/* Huecos \xAB+\xBB (mock): zona invisible entre elementos; al pasar el mouse aparece
|
|
1516
|
+
la l\xEDnea azul (horizontal = fila / bloque apilado, vertical = columna) con
|
|
1517
|
+
un \xAB+\xBB circular al centro. */
|
|
1518
|
+
.veo-gap { position: absolute; z-index: 4; }
|
|
1519
|
+
.veo-gap i { position: absolute; background: #3b82f6; opacity: 0; transition: opacity 100ms; pointer-events: none; }
|
|
1520
|
+
.veo-gap--h i { left: 0; right: 0; top: 50%; height: 2px; margin-top: -1px; }
|
|
1521
|
+
.veo-gap--v i { top: 0; bottom: 0; left: 50%; width: 2px; margin-left: -1px; }
|
|
1522
|
+
.veo-gap button {
|
|
1523
|
+
position: absolute; left: 50%; top: 50%; margin: -11px 0 0 -11px; box-sizing: border-box;
|
|
1524
|
+
opacity: 0; transition: opacity 100ms; width: 22px; height: 22px; border-radius: 50%; padding: 0;
|
|
1525
|
+
border: 2px solid #fff; background: #3b82f6; color: #fff; cursor: pointer;
|
|
1526
|
+
font: 600 15px/18px system-ui, -apple-system, "Segoe UI", sans-serif; text-align: center;
|
|
1527
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
|
1528
|
+
}
|
|
1529
|
+
.veo-gap:hover i, .veo-gap:hover button, .veo-gap:focus-within button { opacity: 1; }
|
|
1530
|
+
.veo-gap button:hover { background: #2563eb; }
|
|
1531
|
+
|
|
1532
|
+
/* Tarjeta sin bloques (paso reci\xE9n creado). La tarjeta NO impone tama\xF1o ni
|
|
1533
|
+
padding propios (as\xED el ancho/padding del dise\xF1o se ven tal cual); el tama\xF1o
|
|
1534
|
+
m\xEDnimo lo pone la zona punteada azul de la preview. */
|
|
1535
|
+
.veo-card--empty { display: flex; align-items: stretch; }
|
|
1536
|
+
.veo-card-empty-zone {
|
|
1537
|
+
flex: 1; min-width: 0; min-height: 160px; margin-top: 14px;
|
|
1538
|
+
display: flex; align-items: center; justify-content: center; gap: 6px; text-align: center;
|
|
1539
|
+
border: 2px dashed #3b82f6; border-radius: 8px; background: #eff6ff;
|
|
1540
|
+
color: #2563eb; font-size: 16px; font-weight: 500; user-select: none; pointer-events: none;
|
|
1541
|
+
padding: 0 12px; box-sizing: border-box; overflow: hidden;
|
|
1542
|
+
}
|
|
1543
|
+
.veo-card-empty-zone b { font-weight: 600; font-size: 22px; line-height: 1; }
|
|
1544
|
+
/* Sin ancho configurado, la zona da un tama\xF1o c\xF3modo por defecto. */
|
|
1545
|
+
.veo-card--empty:not([style*="width"]) .veo-card-empty-zone { min-width: 360px; }
|
|
1546
|
+
/* Tarjeta angosta (la mide el renderer): solo el \xAB+\xBB, sin texto, y m\xE1s baja. */
|
|
1547
|
+
.veo-card--narrow .veo-card-empty-zone span { display: none; }
|
|
1548
|
+
.veo-card--narrow .veo-card-empty-zone { min-height: 120px; }
|
|
1219
1549
|
.veo-card {
|
|
1550
|
+
/* Contenedor posicionado SIEMPRE: el \u2715 (absolute) se ubica respecto a la
|
|
1551
|
+
tarjeta tambi\xE9n en pasos embebidos (inline), no respecto a la app. Los
|
|
1552
|
+
flotantes lo pisan con position:fixed inline. */
|
|
1553
|
+
position: relative;
|
|
1220
1554
|
pointer-events: auto;
|
|
1221
1555
|
background: var(--veo-bg, #ffffff);
|
|
1222
1556
|
color: var(--veo-text, #1f2937);
|
|
@@ -1309,6 +1643,13 @@ var init_styles = __esm({
|
|
|
1309
1643
|
transition: opacity 120ms ease;
|
|
1310
1644
|
}
|
|
1311
1645
|
.veo-guide-cta:hover { opacity: 0.9; }
|
|
1646
|
+
/* Presets del boton (editor): secundario = contorno; enlace = solo texto. */
|
|
1647
|
+
.veo-guide-cta--secondary {
|
|
1648
|
+
background: transparent; color: var(--veo-primary);
|
|
1649
|
+
border: 1px solid var(--veo-primary);
|
|
1650
|
+
}
|
|
1651
|
+
.veo-guide-cta--link { background: transparent; color: var(--veo-primary); padding: 0; }
|
|
1652
|
+
.veo-guide-cta--link:hover { opacity: 1; text-decoration: underline; }
|
|
1312
1653
|
.veo-guide-cta:active { transform: translateY(1px); }
|
|
1313
1654
|
.veo-guide-close {
|
|
1314
1655
|
position: absolute; top: -6px; right: -6px;
|
|
@@ -1339,6 +1680,10 @@ var init_styles = __esm({
|
|
|
1339
1680
|
|
|
1340
1681
|
.veo-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
|
|
1341
1682
|
.veo-form-field { display: flex; flex-direction: column; gap: 6px; }
|
|
1683
|
+
/* Pregunta de la encuesta (rich text del bloque form). */
|
|
1684
|
+
.veo-form-question { font-size: 16px; font-weight: 600; line-height: 1.35; color: var(--veo-text); }
|
|
1685
|
+
.veo-form-question p { margin: 0; }
|
|
1686
|
+
.veo-form > .veo-guide-actions { margin: 0; }
|
|
1342
1687
|
.veo-form-label { font-size: 13px; font-weight: 500; color: var(--veo-text); }
|
|
1343
1688
|
.veo-form-input {
|
|
1344
1689
|
font-size: 14px; color: var(--veo-text);
|
|
@@ -1347,26 +1692,71 @@ var init_styles = __esm({
|
|
|
1347
1692
|
padding: 8px 10px; width: 100%;
|
|
1348
1693
|
}
|
|
1349
1694
|
.veo-form-input:focus { outline: 2px solid var(--veo-primary); outline-offset: -1px; border-color: transparent; }
|
|
1350
|
-
|
|
1695
|
+
/* Lista de tareas (mock): \xEDtems con anillo de progreso, texto + subt\xEDtulo,
|
|
1696
|
+
y al pie \xABX de N completadas\xBB + porcentaje + barra. */
|
|
1697
|
+
.veo-checklist {
|
|
1698
|
+
display: flex; flex-direction: column; gap: 22px; align-self: stretch; width: 100%;
|
|
1699
|
+
min-width: 360px; box-sizing: border-box;
|
|
1700
|
+
}
|
|
1701
|
+
.veo-checklist-title { font-size: 24px; font-weight: 700; line-height: 1.25; color: var(--veo-cl-title); }
|
|
1702
|
+
.veo-checklist-title p { margin: 0; }
|
|
1703
|
+
.veo-checklist-items { display: flex; flex-direction: column; gap: 22px; }
|
|
1704
|
+
.veo-checklist-item {
|
|
1705
|
+
display: flex; align-items: center; gap: 18px; width: 100%; text-align: left;
|
|
1706
|
+
background: none; border: 0; padding: 0; cursor: pointer; font: inherit; color: inherit;
|
|
1707
|
+
}
|
|
1708
|
+
.veo-checklist-item:hover .veo-checklist-label { text-decoration: underline; }
|
|
1709
|
+
.veo-checklist-ring { width: 44px; height: 44px; flex: 0 0 auto; }
|
|
1710
|
+
.veo-checklist-texts { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
1711
|
+
.veo-checklist-label {
|
|
1712
|
+
font-size: calc(var(--veo-cl-font-size, 14px) + 4px); font-weight: var(--veo-cl-font-weight, 600);
|
|
1713
|
+
line-height: 1.3; color: var(--veo-cl-title);
|
|
1714
|
+
}
|
|
1715
|
+
.veo-checklist-sub { font-size: calc(var(--veo-cl-font-size, 14px) + 1px); color: var(--veo-cl-subtitle); }
|
|
1716
|
+
.veo-checklist-footer { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
|
|
1717
|
+
.veo-checklist-summary {
|
|
1718
|
+
display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
|
|
1719
|
+
font-size: calc(var(--veo-cl-font-size, 14px) + 1px);
|
|
1720
|
+
}
|
|
1721
|
+
.veo-checklist-count { color: var(--veo-cl-subtitle); }
|
|
1722
|
+
.veo-checklist-percent { font-weight: 700; font-size: calc(var(--veo-cl-font-size, 14px) + 3px); color: var(--veo-cl-bar); }
|
|
1723
|
+
.veo-checklist-bar { height: var(--veo-cl-bar-h, 8px); background: #e7e2de; border-radius: 999px; overflow: hidden; }
|
|
1724
|
+
.veo-checklist--dark .veo-checklist-bar { background: #3c3734; }
|
|
1725
|
+
.veo-checklist-bar-fill { height: 100%; background: var(--veo-cl-bar); border-radius: 999px; transition: width 300ms ease; }
|
|
1726
|
+
.veo-checklist-empty { margin: 0; font-size: 13px; color: var(--veo-cl-subtitle); }
|
|
1727
|
+
/* Opci\xF3n m\xFAltiple (mock): c\xEDrculos/casillas grandes con su texto al lado. */
|
|
1728
|
+
.veo-form-options { display: flex; flex-direction: column; gap: 14px; }
|
|
1351
1729
|
.veo-form-option {
|
|
1352
|
-
display: flex; align-items: center; gap:
|
|
1353
|
-
font-size:
|
|
1354
|
-
}
|
|
1355
|
-
.veo-form-option input {
|
|
1356
|
-
|
|
1730
|
+
display: flex; align-items: center; gap: 14px;
|
|
1731
|
+
font-size: 16px; color: var(--veo-text); cursor: pointer;
|
|
1732
|
+
}
|
|
1733
|
+
.veo-form-option input {
|
|
1734
|
+
accent-color: var(--veo-primary); margin: 0; cursor: pointer;
|
|
1735
|
+
width: 26px; height: 26px; flex: 0 0 auto;
|
|
1736
|
+
}
|
|
1737
|
+
/* Escala num\xE9rica (mock): casillas cuadradas; con etiquetas, cada casilla
|
|
1738
|
+
lleva la suya debajo (la primera cuelga a la izquierda, la \xFAltima a la derecha). */
|
|
1739
|
+
.veo-form-scale { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-start; }
|
|
1740
|
+
.veo-scale-item { display: inline-flex; flex-direction: column; align-items: center; gap: 8px; }
|
|
1741
|
+
.veo-scale-item--first { align-items: flex-start; }
|
|
1742
|
+
.veo-scale-item--last { align-items: flex-end; }
|
|
1743
|
+
.veo-scale-label { font-size: 12px; line-height: 1.2; color: #8a8582; white-space: nowrap; }
|
|
1357
1744
|
.veo-nps-btn {
|
|
1358
|
-
min-width:
|
|
1359
|
-
font-size:
|
|
1360
|
-
background: var(--veo-bg); border: 1px solid #
|
|
1745
|
+
min-width: var(--veo-scale-size, 44px); height: var(--veo-scale-size, 44px); padding: 0 6px;
|
|
1746
|
+
font-size: 16px; font-weight: 600; color: var(--veo-text);
|
|
1747
|
+
background: var(--veo-bg); border: 1px solid #d5cec9; border-radius: 8px;
|
|
1361
1748
|
cursor: pointer; transition: all 100ms ease;
|
|
1362
1749
|
}
|
|
1363
|
-
.veo-nps-btn:hover { border-color: var(--veo-primary); }
|
|
1750
|
+
.veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-primary)); }
|
|
1364
1751
|
.veo-rating-btn {
|
|
1365
1752
|
background: none; border: none; padding: 0 2px;
|
|
1366
1753
|
font-size: 26px; line-height: 1; color: #d1d5db;
|
|
1367
1754
|
cursor: pointer; transition: color 100ms ease;
|
|
1368
1755
|
}
|
|
1369
|
-
.veo-scale-active.veo-nps-btn {
|
|
1756
|
+
.veo-scale-active.veo-nps-btn {
|
|
1757
|
+
background: var(--veo-scale-color, var(--veo-primary)); color: #fff;
|
|
1758
|
+
border-color: var(--veo-scale-color, var(--veo-primary));
|
|
1759
|
+
}
|
|
1370
1760
|
.veo-scale-active.veo-rating-btn { color: #f59e0b; }
|
|
1371
1761
|
.veo-form-error { font-size: 13px; color: #dc2626; margin: 0; }
|
|
1372
1762
|
.veo-form-thanks {
|
|
@@ -2074,6 +2464,217 @@ var init_banner_renderer = __esm({
|
|
|
2074
2464
|
}
|
|
2075
2465
|
});
|
|
2076
2466
|
|
|
2467
|
+
// src/plugins/guides/checklist-block.ts
|
|
2468
|
+
function checklistStyle(style) {
|
|
2469
|
+
const s = style ?? {};
|
|
2470
|
+
const str = (k, fb) => typeof s[k] === "string" && s[k].trim() ? s[k] : fb;
|
|
2471
|
+
const num2 = (k, fb, min, max) => typeof s[k] === "number" && Number.isFinite(s[k]) ? Math.min(max, Math.max(min, s[k])) : fb;
|
|
2472
|
+
const weightRaw = s.fontWeight;
|
|
2473
|
+
const fontWeight = typeof weightRaw === "number" ? Math.min(900, Math.max(100, weightRaw)) : typeof weightRaw === "string" && WEIGHTS[weightRaw.toLowerCase()] ? WEIGHTS[weightRaw.toLowerCase()] : CHECKLIST_DEFAULTS.fontWeight;
|
|
2474
|
+
const dark = s.colorMode === "dark";
|
|
2475
|
+
return {
|
|
2476
|
+
colorMode: dark ? "dark" : "light",
|
|
2477
|
+
iconEmptyColor: str("iconEmptyColor", dark ? "#3c3734" : CHECKLIST_DEFAULTS.iconEmptyColor),
|
|
2478
|
+
iconFillColor: str("iconFillColor", CHECKLIST_DEFAULTS.iconFillColor),
|
|
2479
|
+
barColor: str("barColor", CHECKLIST_DEFAULTS.barColor),
|
|
2480
|
+
barHeight: num2("barHeight", CHECKLIST_DEFAULTS.barHeight, 2, 40),
|
|
2481
|
+
fontSize: num2("fontSize", CHECKLIST_DEFAULTS.fontSize, 10, 40),
|
|
2482
|
+
fontWeight,
|
|
2483
|
+
titleColor: str("titleColor", dark ? "#f5efeb" : CHECKLIST_DEFAULTS.titleColor),
|
|
2484
|
+
subtitleColor: str("subtitleColor", dark ? "#b0a9a5" : CHECKLIST_DEFAULTS.subtitleColor),
|
|
2485
|
+
subtitleTemplate: str("subtitleTemplate", CHECKLIST_DEFAULTS.subtitleTemplate)
|
|
2486
|
+
};
|
|
2487
|
+
}
|
|
2488
|
+
function renderSubtitle(template, p) {
|
|
2489
|
+
return template.replace(/<%=\s*currentStep\s*%>/g, String(p.currentStep)).replace(/<%=\s*totalSteps\s*%>/g, String(p.totalSteps));
|
|
2490
|
+
}
|
|
2491
|
+
function buildRing(doc, fraction, completed, st) {
|
|
2492
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
2493
|
+
const svg2 = doc.createElementNS(ns, "svg");
|
|
2494
|
+
svg2.setAttribute("viewBox", "0 0 28 28");
|
|
2495
|
+
svg2.setAttribute("class", "veo-checklist-ring");
|
|
2496
|
+
svg2.setAttribute("aria-hidden", "true");
|
|
2497
|
+
const track = doc.createElementNS(ns, "circle");
|
|
2498
|
+
track.setAttribute("cx", "14");
|
|
2499
|
+
track.setAttribute("cy", "14");
|
|
2500
|
+
track.setAttribute("r", String(RING_R));
|
|
2501
|
+
track.setAttribute("fill", completed ? st.iconFillColor : "none");
|
|
2502
|
+
track.setAttribute("stroke", st.iconEmptyColor);
|
|
2503
|
+
track.setAttribute("stroke-width", "4.5");
|
|
2504
|
+
svg2.appendChild(track);
|
|
2505
|
+
if (completed) {
|
|
2506
|
+
const check = doc.createElementNS(ns, "path");
|
|
2507
|
+
check.setAttribute("d", "M9 14.5l3.2 3.2L19 11");
|
|
2508
|
+
check.setAttribute("fill", "none");
|
|
2509
|
+
check.setAttribute("stroke", "#fff");
|
|
2510
|
+
check.setAttribute("stroke-width", "2.4");
|
|
2511
|
+
check.setAttribute("stroke-linecap", "round");
|
|
2512
|
+
check.setAttribute("stroke-linejoin", "round");
|
|
2513
|
+
svg2.appendChild(check);
|
|
2514
|
+
return svg2;
|
|
2515
|
+
}
|
|
2516
|
+
const f = Math.max(0, Math.min(1, fraction));
|
|
2517
|
+
if (f > 0) {
|
|
2518
|
+
const arc = doc.createElementNS(ns, "circle");
|
|
2519
|
+
arc.setAttribute("cx", "14");
|
|
2520
|
+
arc.setAttribute("cy", "14");
|
|
2521
|
+
arc.setAttribute("r", String(RING_R));
|
|
2522
|
+
arc.setAttribute("fill", "none");
|
|
2523
|
+
arc.setAttribute("stroke", st.iconFillColor);
|
|
2524
|
+
arc.setAttribute("stroke-width", "4.5");
|
|
2525
|
+
arc.setAttribute("stroke-linecap", "butt");
|
|
2526
|
+
arc.setAttribute("stroke-dasharray", `${(RING_C * f).toFixed(2)} ${RING_C.toFixed(2)}`);
|
|
2527
|
+
arc.setAttribute("transform", "rotate(-90 14 14)");
|
|
2528
|
+
svg2.appendChild(arc);
|
|
2529
|
+
}
|
|
2530
|
+
return svg2;
|
|
2531
|
+
}
|
|
2532
|
+
function buildChecklistBlock(block, doc, context) {
|
|
2533
|
+
const st = checklistStyle(block.style);
|
|
2534
|
+
const items = (block.items ?? []).filter((i) => i && typeof i.guideId === "string" && i.guideId);
|
|
2535
|
+
const root = doc.createElement("div");
|
|
2536
|
+
root.className = `veo-checklist${st.colorMode === "dark" ? " veo-checklist--dark" : ""}`;
|
|
2537
|
+
root.style.setProperty("--veo-cl-font-size", `${st.fontSize}px`);
|
|
2538
|
+
root.style.setProperty("--veo-cl-font-weight", String(st.fontWeight));
|
|
2539
|
+
root.style.setProperty("--veo-cl-title", st.titleColor);
|
|
2540
|
+
root.style.setProperty("--veo-cl-subtitle", st.subtitleColor);
|
|
2541
|
+
root.style.setProperty("--veo-cl-bar", st.barColor);
|
|
2542
|
+
root.style.setProperty("--veo-cl-bar-h", `${st.barHeight}px`);
|
|
2543
|
+
if (typeof block.html === "string" && block.html || typeof block.text === "string" && block.text) {
|
|
2544
|
+
const title = doc.createElement("div");
|
|
2545
|
+
title.className = "veo-checklist-title";
|
|
2546
|
+
if (typeof block.html === "string" && block.html)
|
|
2547
|
+
title.appendChild(renderRichText(block.html, doc));
|
|
2548
|
+
else title.textContent = block.text;
|
|
2549
|
+
root.appendChild(title);
|
|
2550
|
+
}
|
|
2551
|
+
const list = doc.createElement("div");
|
|
2552
|
+
list.className = "veo-checklist-items";
|
|
2553
|
+
list.setAttribute("role", "list");
|
|
2554
|
+
root.appendChild(list);
|
|
2555
|
+
const footer = doc.createElement("div");
|
|
2556
|
+
footer.className = "veo-checklist-footer";
|
|
2557
|
+
const summary = doc.createElement("div");
|
|
2558
|
+
summary.className = "veo-checklist-summary";
|
|
2559
|
+
const count = doc.createElement("span");
|
|
2560
|
+
count.className = "veo-checklist-count";
|
|
2561
|
+
const percent = doc.createElement("span");
|
|
2562
|
+
percent.className = "veo-checklist-percent";
|
|
2563
|
+
summary.append(count, percent);
|
|
2564
|
+
const bar = doc.createElement("div");
|
|
2565
|
+
bar.className = "veo-checklist-bar";
|
|
2566
|
+
const fill = doc.createElement("div");
|
|
2567
|
+
fill.className = "veo-checklist-bar-fill";
|
|
2568
|
+
bar.appendChild(fill);
|
|
2569
|
+
footer.append(summary, bar);
|
|
2570
|
+
root.appendChild(footer);
|
|
2571
|
+
const paint = (progress) => {
|
|
2572
|
+
list.textContent = "";
|
|
2573
|
+
const visible = progress ? items.filter((i) => progress.has(i.guideId)) : items;
|
|
2574
|
+
let done = 0;
|
|
2575
|
+
for (const item of visible) {
|
|
2576
|
+
const p = progress?.get(item.guideId) ?? null;
|
|
2577
|
+
if (p?.completed) done++;
|
|
2578
|
+
const row = doc.createElement("button");
|
|
2579
|
+
row.type = "button";
|
|
2580
|
+
row.className = `veo-checklist-item${p?.completed ? " veo-checklist-item--done" : ""}`;
|
|
2581
|
+
row.setAttribute("role", "listitem");
|
|
2582
|
+
const fraction = p && p.totalSteps > 0 ? p.currentStep / p.totalSteps : 0;
|
|
2583
|
+
row.appendChild(buildRing(doc, fraction, p?.completed === true, st));
|
|
2584
|
+
const texts = doc.createElement("span");
|
|
2585
|
+
texts.className = "veo-checklist-texts";
|
|
2586
|
+
const label = doc.createElement("span");
|
|
2587
|
+
label.className = "veo-checklist-label";
|
|
2588
|
+
label.textContent = (item.text ?? "").trim() || p?.guideName || "Gu\xEDa";
|
|
2589
|
+
if (p && p.published === false) row.title = "Gu\xEDa no publicada: los usuarios no la ver\xE1n";
|
|
2590
|
+
texts.appendChild(label);
|
|
2591
|
+
if (p) {
|
|
2592
|
+
const sub = doc.createElement("span");
|
|
2593
|
+
sub.className = "veo-checklist-sub";
|
|
2594
|
+
sub.textContent = renderSubtitle(st.subtitleTemplate, p);
|
|
2595
|
+
texts.appendChild(sub);
|
|
2596
|
+
}
|
|
2597
|
+
row.appendChild(texts);
|
|
2598
|
+
row.addEventListener("click", (e) => {
|
|
2599
|
+
e.preventDefault();
|
|
2600
|
+
e.stopPropagation();
|
|
2601
|
+
if (context.isPreview) return;
|
|
2602
|
+
const start = p && !p.completed ? Math.min(p.currentStep, p.totalSteps - 1) : 0;
|
|
2603
|
+
if (context.onOpenGuideAt) context.onOpenGuideAt(item.guideId, start);
|
|
2604
|
+
else context.onOpenGuide?.(item.guideId);
|
|
2605
|
+
});
|
|
2606
|
+
list.appendChild(row);
|
|
2607
|
+
}
|
|
2608
|
+
const total = visible.length;
|
|
2609
|
+
const pct = total > 0 ? Math.round(done / total * 100) : 0;
|
|
2610
|
+
count.textContent = `${done} de ${total} ${total === 1 ? "completada" : "completadas"}`;
|
|
2611
|
+
percent.textContent = `${pct}%`;
|
|
2612
|
+
fill.style.width = `${pct}%`;
|
|
2613
|
+
footer.hidden = total === 0;
|
|
2614
|
+
};
|
|
2615
|
+
if (context.isPreview && items.length === 0) {
|
|
2616
|
+
const sample = /* @__PURE__ */ new Map([
|
|
2617
|
+
[
|
|
2618
|
+
"__sample_1",
|
|
2619
|
+
{
|
|
2620
|
+
guideId: "__sample_1",
|
|
2621
|
+
guideName: "Configura tu primer env\xEDo",
|
|
2622
|
+
totalSteps: 2,
|
|
2623
|
+
currentStep: 1,
|
|
2624
|
+
completed: false
|
|
2625
|
+
}
|
|
2626
|
+
],
|
|
2627
|
+
[
|
|
2628
|
+
"__sample_2",
|
|
2629
|
+
{
|
|
2630
|
+
guideId: "__sample_2",
|
|
2631
|
+
guideName: "Conecta tu tienda",
|
|
2632
|
+
totalSteps: 2,
|
|
2633
|
+
currentStep: 2,
|
|
2634
|
+
completed: true
|
|
2635
|
+
}
|
|
2636
|
+
]
|
|
2637
|
+
]);
|
|
2638
|
+
items.push(
|
|
2639
|
+
{ guideId: "__sample_1", text: "Configura tu primer env\xEDo" },
|
|
2640
|
+
{ guideId: "__sample_2", text: "Conecta tu tienda" }
|
|
2641
|
+
);
|
|
2642
|
+
paint(sample);
|
|
2643
|
+
const hint = doc.createElement("p");
|
|
2644
|
+
hint.className = "veo-checklist-empty";
|
|
2645
|
+
hint.textContent = "Ejemplo: agrega gu\xEDas a la lista desde Opciones.";
|
|
2646
|
+
root.appendChild(hint);
|
|
2647
|
+
return root;
|
|
2648
|
+
}
|
|
2649
|
+
paint(null);
|
|
2650
|
+
if (context.fetchProgress && items.length > 0) {
|
|
2651
|
+
void context.fetchProgress(items.map((i) => i.guideId)).then((rows) => paint(new Map(rows.map((r) => [r.guideId, r])))).catch(() => {
|
|
2652
|
+
});
|
|
2653
|
+
}
|
|
2654
|
+
return root;
|
|
2655
|
+
}
|
|
2656
|
+
var CHECKLIST_DEFAULTS, WEIGHTS, RING_R, RING_C;
|
|
2657
|
+
var init_checklist_block = __esm({
|
|
2658
|
+
"src/plugins/guides/checklist-block.ts"() {
|
|
2659
|
+
init_rich_text();
|
|
2660
|
+
CHECKLIST_DEFAULTS = {
|
|
2661
|
+
colorMode: "light",
|
|
2662
|
+
iconEmptyColor: "#e7e2de",
|
|
2663
|
+
iconFillColor: "#406a31",
|
|
2664
|
+
barColor: "#406a31",
|
|
2665
|
+
barHeight: 8,
|
|
2666
|
+
fontSize: 14,
|
|
2667
|
+
fontWeight: 600,
|
|
2668
|
+
titleColor: "#2c2825",
|
|
2669
|
+
subtitleColor: "#8a8582",
|
|
2670
|
+
subtitleTemplate: "Completado <%= currentStep %> de <%= totalSteps %> pasos"
|
|
2671
|
+
};
|
|
2672
|
+
WEIGHTS = { regular: 400, medium: 500, semibold: 600, bold: 700 };
|
|
2673
|
+
RING_R = 10;
|
|
2674
|
+
RING_C = 2 * Math.PI * RING_R;
|
|
2675
|
+
}
|
|
2676
|
+
});
|
|
2677
|
+
|
|
2077
2678
|
// src/plugins/guides/renderers/custom-frame.ts
|
|
2078
2679
|
function mountCustomFrame(doc, step, context, opts) {
|
|
2079
2680
|
const iframe = doc.createElement("iframe");
|
|
@@ -2258,6 +2859,11 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
|
|
|
2258
2859
|
}
|
|
2259
2860
|
if (rawInline) {
|
|
2260
2861
|
const rawNodes = Array.from(holder.childNodes);
|
|
2862
|
+
if (rawInline.tagBlockId) {
|
|
2863
|
+
for (const n of rawNodes) {
|
|
2864
|
+
if (n instanceof Element) n.setAttribute(RAW_BLOCK_ATTR, rawInline.tagBlockId);
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2261
2867
|
insertRawNodes(rawInline.anchor, rawNodes, rawInline.pos);
|
|
2262
2868
|
injected.push(...rawNodes);
|
|
2263
2869
|
} else if (host) {
|
|
@@ -2267,24 +2873,40 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
|
|
|
2267
2873
|
injected.push(holder);
|
|
2268
2874
|
const slot = doc.createElement("slot");
|
|
2269
2875
|
slot.setAttribute("name", slotName);
|
|
2270
|
-
|
|
2876
|
+
const wrapper = doc.createElement("div");
|
|
2877
|
+
wrapper.className = "veo-code-host";
|
|
2878
|
+
wrapper.appendChild(slot);
|
|
2879
|
+
node = wrapper;
|
|
2271
2880
|
} else {
|
|
2272
2881
|
node = holder;
|
|
2273
2882
|
}
|
|
2274
2883
|
}
|
|
2884
|
+
let cancelled = false;
|
|
2275
2885
|
if (typeof block.js === "string" && block.js.trim()) {
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2886
|
+
const js = block.js;
|
|
2887
|
+
const run = () => {
|
|
2888
|
+
if (cancelled) return;
|
|
2889
|
+
const s = hostDoc.createElement("script");
|
|
2890
|
+
s.textContent = wrapUserScript(js);
|
|
2891
|
+
hostDoc.body.appendChild(s);
|
|
2892
|
+
injected.push(s);
|
|
2893
|
+
};
|
|
2894
|
+
if (host && !host.isConnected) queueMicrotask(run);
|
|
2895
|
+
else run();
|
|
2280
2896
|
}
|
|
2281
2897
|
return {
|
|
2282
2898
|
node,
|
|
2283
2899
|
cleanup: () => {
|
|
2900
|
+
cancelled = true;
|
|
2284
2901
|
for (const n of injected) n.parentNode?.removeChild(n);
|
|
2285
2902
|
}
|
|
2286
2903
|
};
|
|
2287
2904
|
}
|
|
2905
|
+
function wrapUserScript(js) {
|
|
2906
|
+
return `(function(){try{
|
|
2907
|
+
${js}
|
|
2908
|
+
}catch(e){console.error("[OnUser] Error en el bloque de c\xF3digo:",e)}})();`;
|
|
2909
|
+
}
|
|
2288
2910
|
function runScript(old, hostDoc) {
|
|
2289
2911
|
const s = hostDoc.createElement("script");
|
|
2290
2912
|
for (const attr of Array.from(old.attributes)) s.setAttribute(attr.name, attr.value);
|
|
@@ -2292,10 +2914,11 @@ function runScript(old, hostDoc) {
|
|
|
2292
2914
|
hostDoc.body.appendChild(s);
|
|
2293
2915
|
return s;
|
|
2294
2916
|
}
|
|
2295
|
-
var codeSlotSeq;
|
|
2917
|
+
var RAW_BLOCK_ATTR, codeSlotSeq;
|
|
2296
2918
|
var init_code_block = __esm({
|
|
2297
2919
|
"src/plugins/guides/code-block.ts"() {
|
|
2298
2920
|
init_custom_frame();
|
|
2921
|
+
RAW_BLOCK_ATTR = "data-veo-raw-block";
|
|
2299
2922
|
codeSlotSeq = 0;
|
|
2300
2923
|
}
|
|
2301
2924
|
});
|
|
@@ -2526,8 +3149,14 @@ function buildField(field, doc) {
|
|
|
2526
3149
|
wrapper.appendChild(group);
|
|
2527
3150
|
return { wrapper, read: () => inputs.find((i) => i.input.checked)?.value };
|
|
2528
3151
|
}
|
|
2529
|
-
case "nps":
|
|
2530
|
-
|
|
3152
|
+
case "nps": {
|
|
3153
|
+
const min = typeof field.min === "number" ? field.min : 0;
|
|
3154
|
+
const max = typeof field.max === "number" && field.max > min ? field.max : 10;
|
|
3155
|
+
return buildScale(wrapper, doc, min, max, "veo-nps-btn", void 0, {
|
|
3156
|
+
labels: field.scaleLabels ?? [],
|
|
3157
|
+
showLabels: field.showLabels !== false
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
2531
3160
|
case "rating":
|
|
2532
3161
|
return buildScale(wrapper, doc, 1, 5, "veo-rating-btn", "\u2605");
|
|
2533
3162
|
default: {
|
|
@@ -2540,17 +3169,21 @@ function buildField(field, doc) {
|
|
|
2540
3169
|
}
|
|
2541
3170
|
}
|
|
2542
3171
|
}
|
|
2543
|
-
function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
3172
|
+
function buildScale(wrapper, doc, min, max, btnClass, symbol, opts = { labels: [], showLabels: false }) {
|
|
2544
3173
|
const row = doc.createElement("div");
|
|
2545
3174
|
row.className = "veo-form-scale";
|
|
2546
3175
|
let selected;
|
|
2547
3176
|
const buttons = [];
|
|
3177
|
+
const labelOf = (n) => opts.labels[n - min]?.trim() ?? "";
|
|
3178
|
+
const anyLabel = opts.showLabels && Array.from({ length: max - min + 1 }, (_, i) => min + i).some((n) => labelOf(n));
|
|
2548
3179
|
for (let n = min; n <= max; n++) {
|
|
2549
3180
|
const btn = doc.createElement("button");
|
|
2550
3181
|
btn.type = "button";
|
|
2551
3182
|
btn.className = btnClass;
|
|
2552
3183
|
btn.textContent = symbol ?? String(n);
|
|
2553
|
-
|
|
3184
|
+
const lbl = labelOf(n);
|
|
3185
|
+
btn.setAttribute("aria-label", lbl ? `${n} \xB7 ${lbl}` : String(n));
|
|
3186
|
+
if (lbl) btn.title = lbl;
|
|
2554
3187
|
btn.addEventListener("click", () => {
|
|
2555
3188
|
selected = n;
|
|
2556
3189
|
buttons.forEach((b, i) => {
|
|
@@ -2559,7 +3192,17 @@ function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
|
2559
3192
|
});
|
|
2560
3193
|
});
|
|
2561
3194
|
buttons.push(btn);
|
|
2562
|
-
|
|
3195
|
+
if (!anyLabel) {
|
|
3196
|
+
row.appendChild(btn);
|
|
3197
|
+
continue;
|
|
3198
|
+
}
|
|
3199
|
+
const item = doc.createElement("div");
|
|
3200
|
+
item.className = `veo-scale-item${n === min ? " veo-scale-item--first" : n === max ? " veo-scale-item--last" : ""}`;
|
|
3201
|
+
const text = doc.createElement("span");
|
|
3202
|
+
text.className = "veo-scale-label";
|
|
3203
|
+
text.textContent = lbl;
|
|
3204
|
+
item.append(btn, text);
|
|
3205
|
+
row.appendChild(item);
|
|
2563
3206
|
}
|
|
2564
3207
|
wrapper.appendChild(row);
|
|
2565
3208
|
return { wrapper, read: () => selected };
|
|
@@ -2569,6 +3212,126 @@ var init_form_content = __esm({
|
|
|
2569
3212
|
init_block_builder();
|
|
2570
3213
|
}
|
|
2571
3214
|
});
|
|
3215
|
+
|
|
3216
|
+
// src/plugins/guides/layout.ts
|
|
3217
|
+
function groupLayout(blocks) {
|
|
3218
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3219
|
+
const order = [];
|
|
3220
|
+
let lastRow = -1;
|
|
3221
|
+
blocks.forEach((block, index) => {
|
|
3222
|
+
if (!block) return;
|
|
3223
|
+
let row;
|
|
3224
|
+
if (typeof block.row === "number" && Number.isFinite(block.row)) {
|
|
3225
|
+
row = block.row;
|
|
3226
|
+
lastRow = row;
|
|
3227
|
+
} else {
|
|
3228
|
+
row = lastRow + 0.5;
|
|
3229
|
+
lastRow = row;
|
|
3230
|
+
}
|
|
3231
|
+
const col = typeof block.col === "number" && Number.isFinite(block.col) ? block.col : 0;
|
|
3232
|
+
let r = rows.get(row);
|
|
3233
|
+
if (!r) {
|
|
3234
|
+
r = { row, columns: [] };
|
|
3235
|
+
rows.set(row, r);
|
|
3236
|
+
order.push(row);
|
|
3237
|
+
}
|
|
3238
|
+
let c = r.columns.find((x) => x.col === col);
|
|
3239
|
+
if (!c) {
|
|
3240
|
+
c = { col, blocks: [] };
|
|
3241
|
+
r.columns.push(c);
|
|
3242
|
+
}
|
|
3243
|
+
c.blocks.push({ block, index });
|
|
3244
|
+
});
|
|
3245
|
+
return order.sort((a, b) => a - b).map((k) => {
|
|
3246
|
+
const r = rows.get(k);
|
|
3247
|
+
r.columns.sort((a, b) => a.col - b.col);
|
|
3248
|
+
return r;
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
var init_layout = __esm({
|
|
3252
|
+
"src/plugins/guides/layout.ts"() {
|
|
3253
|
+
}
|
|
3254
|
+
});
|
|
3255
|
+
|
|
3256
|
+
// src/plugins/guides/scroll-lock.ts
|
|
3257
|
+
function lockScroll(doc = document) {
|
|
3258
|
+
const html = doc.documentElement;
|
|
3259
|
+
const body = doc.body;
|
|
3260
|
+
if (!html || !body) return () => {
|
|
3261
|
+
};
|
|
3262
|
+
let state = states.get(doc);
|
|
3263
|
+
if (!state) {
|
|
3264
|
+
state = {
|
|
3265
|
+
count: 0,
|
|
3266
|
+
html: { overflow: html.style.overflow, paddingRight: html.style.paddingRight },
|
|
3267
|
+
body: { overflow: body.style.overflow, paddingRight: body.style.paddingRight }
|
|
3268
|
+
};
|
|
3269
|
+
states.set(doc, state);
|
|
3270
|
+
const win = doc.defaultView;
|
|
3271
|
+
const gap = win ? Math.max(0, win.innerWidth - html.clientWidth) : 0;
|
|
3272
|
+
if (gap > 0) body.style.paddingRight = `${gap}px`;
|
|
3273
|
+
html.style.overflow = "hidden";
|
|
3274
|
+
body.style.overflow = "hidden";
|
|
3275
|
+
}
|
|
3276
|
+
state.count += 1;
|
|
3277
|
+
let released = false;
|
|
3278
|
+
return () => {
|
|
3279
|
+
if (released) return;
|
|
3280
|
+
released = true;
|
|
3281
|
+
const s = states.get(doc);
|
|
3282
|
+
if (!s) return;
|
|
3283
|
+
s.count -= 1;
|
|
3284
|
+
if (s.count > 0) return;
|
|
3285
|
+
states.delete(doc);
|
|
3286
|
+
html.style.overflow = s.html.overflow;
|
|
3287
|
+
html.style.paddingRight = s.html.paddingRight;
|
|
3288
|
+
body.style.overflow = s.body.overflow;
|
|
3289
|
+
body.style.paddingRight = s.body.paddingRight;
|
|
3290
|
+
};
|
|
3291
|
+
}
|
|
3292
|
+
var states;
|
|
3293
|
+
var init_scroll_lock = __esm({
|
|
3294
|
+
"src/plugins/guides/scroll-lock.ts"() {
|
|
3295
|
+
states = /* @__PURE__ */ new WeakMap();
|
|
3296
|
+
}
|
|
3297
|
+
});
|
|
3298
|
+
|
|
3299
|
+
// src/plugins/guides/snooze.ts
|
|
3300
|
+
function snoozeDurationMs(every, unit) {
|
|
3301
|
+
const n = Number.isFinite(every) && every > 0 ? Math.floor(every) : 1;
|
|
3302
|
+
return n * (UNIT_MS[unit] ?? UNIT_MS.day);
|
|
3303
|
+
}
|
|
3304
|
+
function snoozeGuide(guideId, every, unit, now = Date.now()) {
|
|
3305
|
+
try {
|
|
3306
|
+
localStorage.setItem(KEY(guideId), String(now + snoozeDurationMs(every, unit)));
|
|
3307
|
+
} catch {
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
function isSnoozed(guideId, now = Date.now()) {
|
|
3311
|
+
try {
|
|
3312
|
+
const raw = localStorage.getItem(KEY(guideId));
|
|
3313
|
+
if (raw === null) return false;
|
|
3314
|
+
const until = Number(raw);
|
|
3315
|
+
if (!Number.isFinite(until) || until <= now) {
|
|
3316
|
+
localStorage.removeItem(KEY(guideId));
|
|
3317
|
+
return false;
|
|
3318
|
+
}
|
|
3319
|
+
return true;
|
|
3320
|
+
} catch {
|
|
3321
|
+
return false;
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3324
|
+
var KEY, UNIT_MS;
|
|
3325
|
+
var init_snooze = __esm({
|
|
3326
|
+
"src/plugins/guides/snooze.ts"() {
|
|
3327
|
+
KEY = (guideId) => `veo:snooze:${guideId}`;
|
|
3328
|
+
UNIT_MS = {
|
|
3329
|
+
hour: 36e5,
|
|
3330
|
+
day: 864e5,
|
|
3331
|
+
week: 6048e5
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
});
|
|
2572
3335
|
function num(v, fallback) {
|
|
2573
3336
|
return typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
2574
3337
|
}
|
|
@@ -2707,13 +3470,68 @@ function markerOffset(anchor, marker, position, offset6) {
|
|
|
2707
3470
|
}
|
|
2708
3471
|
return { x: x + offset6.x, y: y + offset6.y };
|
|
2709
3472
|
}
|
|
3473
|
+
function canNest(anchor) {
|
|
3474
|
+
const win = anchor.ownerDocument.defaultView;
|
|
3475
|
+
if (!win || !(anchor instanceof win.HTMLElement)) return false;
|
|
3476
|
+
if (NO_CHILDREN.has(anchor.tagName.toLowerCase())) return false;
|
|
3477
|
+
const cs = win.getComputedStyle(anchor);
|
|
3478
|
+
if (cs.display === "contents") return false;
|
|
3479
|
+
const tokens = `${cs.overflow} ${cs.overflowX} ${cs.overflowY}`.split(/\s+/).filter(Boolean);
|
|
3480
|
+
return tokens.every((t) => t === "visible");
|
|
3481
|
+
}
|
|
2710
3482
|
function attachMarker(anchor, host, marker) {
|
|
2711
3483
|
host.style.position = "absolute";
|
|
2712
3484
|
host.style.zIndex = String(num(marker.zIndex, DEFAULT_MARKER_Z));
|
|
2713
3485
|
host.style.margin = "0";
|
|
3486
|
+
const position = marker.position ?? "top-right";
|
|
3487
|
+
const offset6 = { x: num(marker.offsetX, 0), y: num(marker.offsetY, 0) };
|
|
3488
|
+
const markerSize = () => {
|
|
3489
|
+
const m = host.getBoundingClientRect();
|
|
3490
|
+
return { width: m.width, height: m.height };
|
|
3491
|
+
};
|
|
3492
|
+
if (canNest(anchor)) {
|
|
3493
|
+
const win = anchor.ownerDocument.defaultView;
|
|
3494
|
+
const prevPosition = anchor.style.position;
|
|
3495
|
+
const computed = win.getComputedStyle(anchor).position;
|
|
3496
|
+
if (!computed || computed === "static") anchor.style.position = "relative";
|
|
3497
|
+
anchor.appendChild(host);
|
|
3498
|
+
const swallowClick = (e) => {
|
|
3499
|
+
e.preventDefault();
|
|
3500
|
+
e.stopPropagation();
|
|
3501
|
+
};
|
|
3502
|
+
const swallow = (e) => e.stopPropagation();
|
|
3503
|
+
host.addEventListener("click", swallowClick);
|
|
3504
|
+
host.addEventListener("pointerdown", swallow);
|
|
3505
|
+
host.addEventListener("mousedown", swallow);
|
|
3506
|
+
const update2 = () => {
|
|
3507
|
+
const { x, y } = markerOffset(
|
|
3508
|
+
{
|
|
3509
|
+
left: -anchor.clientLeft,
|
|
3510
|
+
top: -anchor.clientTop,
|
|
3511
|
+
width: anchor.offsetWidth,
|
|
3512
|
+
height: anchor.offsetHeight
|
|
3513
|
+
},
|
|
3514
|
+
markerSize(),
|
|
3515
|
+
position,
|
|
3516
|
+
offset6
|
|
3517
|
+
);
|
|
3518
|
+
host.style.left = `${Math.round(x)}px`;
|
|
3519
|
+
host.style.top = `${Math.round(y)}px`;
|
|
3520
|
+
};
|
|
3521
|
+
update2();
|
|
3522
|
+
const stop2 = dom.autoUpdate(anchor, host, update2, { ancestorScroll: false, layoutShift: false });
|
|
3523
|
+
return () => {
|
|
3524
|
+
stop2();
|
|
3525
|
+
host.removeEventListener("click", swallowClick);
|
|
3526
|
+
host.removeEventListener("pointerdown", swallow);
|
|
3527
|
+
host.removeEventListener("mousedown", swallow);
|
|
3528
|
+
host.remove();
|
|
3529
|
+
anchor.style.position = prevPosition;
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
anchor.ownerDocument.body.appendChild(host);
|
|
2714
3533
|
const update = () => {
|
|
2715
3534
|
const a = anchor.getBoundingClientRect();
|
|
2716
|
-
const m = host.getBoundingClientRect();
|
|
2717
3535
|
const { x, y } = markerOffset(
|
|
2718
3536
|
{
|
|
2719
3537
|
left: a.left + window.scrollX,
|
|
@@ -2721,17 +3539,21 @@ function attachMarker(anchor, host, marker) {
|
|
|
2721
3539
|
width: a.width,
|
|
2722
3540
|
height: a.height
|
|
2723
3541
|
},
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
3542
|
+
markerSize(),
|
|
3543
|
+
position,
|
|
3544
|
+
offset6
|
|
2727
3545
|
);
|
|
2728
3546
|
host.style.left = `${Math.round(x)}px`;
|
|
2729
3547
|
host.style.top = `${Math.round(y)}px`;
|
|
2730
3548
|
};
|
|
2731
3549
|
update();
|
|
2732
|
-
|
|
3550
|
+
const stop = dom.autoUpdate(anchor, host, update);
|
|
3551
|
+
return () => {
|
|
3552
|
+
stop();
|
|
3553
|
+
host.remove();
|
|
3554
|
+
};
|
|
2733
3555
|
}
|
|
2734
|
-
var INFO_GLYPH, QUESTION_GLYPH, STAR_GLYPH, CIRCLE, TARGET_GLYPH, BADGE_ICONS, DEFAULT_MARKER_SIZE, DEFAULT_MARKER_Z, SIDE_GAP;
|
|
3556
|
+
var INFO_GLYPH, QUESTION_GLYPH, STAR_GLYPH, CIRCLE, TARGET_GLYPH, BADGE_ICONS, DEFAULT_MARKER_SIZE, DEFAULT_MARKER_Z, SIDE_GAP, NO_CHILDREN;
|
|
2735
3557
|
var init_badge_marker = __esm({
|
|
2736
3558
|
"src/plugins/guides/renderers/badge-marker.ts"() {
|
|
2737
3559
|
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";
|
|
@@ -2754,28 +3576,78 @@ var init_badge_marker = __esm({
|
|
|
2754
3576
|
DEFAULT_MARKER_SIZE = 14;
|
|
2755
3577
|
DEFAULT_MARKER_Z = 19e3;
|
|
2756
3578
|
SIDE_GAP = 8;
|
|
3579
|
+
NO_CHILDREN = /* @__PURE__ */ new Set([
|
|
3580
|
+
"img",
|
|
3581
|
+
"input",
|
|
3582
|
+
"textarea",
|
|
3583
|
+
"select",
|
|
3584
|
+
"iframe",
|
|
3585
|
+
"video",
|
|
3586
|
+
"audio",
|
|
3587
|
+
"canvas",
|
|
3588
|
+
"br",
|
|
3589
|
+
"hr",
|
|
3590
|
+
"svg",
|
|
3591
|
+
"object",
|
|
3592
|
+
"embed"
|
|
3593
|
+
]);
|
|
2757
3594
|
}
|
|
2758
3595
|
});
|
|
3596
|
+
function applyRowStyle(rowEl, style) {
|
|
3597
|
+
const s = rowEl.style;
|
|
3598
|
+
if (typeof style.background === "string" && style.background) s.background = style.background;
|
|
3599
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3600
|
+
const p = style[`padding${side}`];
|
|
3601
|
+
if (typeof p === "number" && Number.isFinite(p))
|
|
3602
|
+
s.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3603
|
+
const m = style[`margin${side}`];
|
|
3604
|
+
if (typeof m === "number" && Number.isFinite(m))
|
|
3605
|
+
s.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3606
|
+
}
|
|
3607
|
+
if (typeof style.minHeight === "number" && Number.isFinite(style.minHeight) && style.minHeight > 0) {
|
|
3608
|
+
s.minHeight = `${style.minHeight}px`;
|
|
3609
|
+
}
|
|
3610
|
+
if (style.align && ROW_JUSTIFY[style.align]) {
|
|
3611
|
+
s.justifyContent = ROW_JUSTIFY[style.align];
|
|
3612
|
+
rowEl.classList.add("veo-row--aligned");
|
|
3613
|
+
}
|
|
3614
|
+
if (style.valign && ROW_VALIGN[style.valign]) s.alignItems = ROW_VALIGN[style.valign];
|
|
3615
|
+
}
|
|
2759
3616
|
function inlinePosOf(p) {
|
|
2760
3617
|
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
2761
3618
|
}
|
|
2762
3619
|
function applyViewportPosition(card, position, design) {
|
|
2763
3620
|
const s = card.style;
|
|
2764
3621
|
s.position = "fixed";
|
|
2765
|
-
const
|
|
3622
|
+
const BASE = 16;
|
|
3623
|
+
const m = (k) => typeof design[k] === "number" && Number.isFinite(design[k]) ? design[k] : 0;
|
|
3624
|
+
s.margin = "0";
|
|
2766
3625
|
const hAlign = design.hAlign === "left" ? "left" : design.hAlign === "right" ? "right" : null;
|
|
2767
3626
|
const v = position.includes("top") ? "top" : position.includes("bottom") ? "bottom" : "middle";
|
|
2768
3627
|
const h = hAlign ?? (position.includes("left") ? "left" : position.includes("right") ? "right" : "middle");
|
|
2769
|
-
if (v === "top") s.top =
|
|
2770
|
-
else if (v === "bottom") s.bottom =
|
|
3628
|
+
if (v === "top") s.top = `${BASE + m("marginTop")}px`;
|
|
3629
|
+
else if (v === "bottom") s.bottom = `${BASE + m("marginBottom")}px`;
|
|
2771
3630
|
else s.top = "50%";
|
|
2772
|
-
if (h === "left") s.left =
|
|
2773
|
-
else if (h === "right") s.right =
|
|
3631
|
+
if (h === "left") s.left = `${BASE + m("marginLeft")}px`;
|
|
3632
|
+
else if (h === "right") s.right = `${BASE + m("marginRight")}px`;
|
|
2774
3633
|
else s.left = "50%";
|
|
2775
|
-
const
|
|
2776
|
-
const
|
|
3634
|
+
const dx = m("marginLeft") - m("marginRight");
|
|
3635
|
+
const dy = m("marginTop") - m("marginBottom");
|
|
3636
|
+
const tx = h === "middle" ? `calc(-50% + ${dx}px)` : "0";
|
|
3637
|
+
const ty = v === "middle" ? `calc(-50% + ${dy}px)` : "0";
|
|
2777
3638
|
s.transform = tx === "0" && ty === "0" ? "" : `translate(${tx}, ${ty})`;
|
|
2778
3639
|
}
|
|
3640
|
+
function reserveCloseSpace(card, blocks, position, design) {
|
|
3641
|
+
if (blocks.length === 0) return;
|
|
3642
|
+
const side = position.includes("bottom") ? "Bottom" : "Top";
|
|
3643
|
+
const edgeBlock = side === "Top" ? blocks[0] : blocks[blocks.length - 1];
|
|
3644
|
+
if (edgeBlock?.type === "image") return;
|
|
3645
|
+
const margin = design[`closeMargin${side}`];
|
|
3646
|
+
const need = CLOSE_RESERVE_PX + (typeof margin === "number" ? margin : 0);
|
|
3647
|
+
const current = design[`padding${side}`];
|
|
3648
|
+
const have = typeof current === "number" ? current : CARD_DEFAULT_PAD_PX;
|
|
3649
|
+
if (have < need) card.style.setProperty(`padding-${side.toLowerCase()}`, `${need}px`);
|
|
3650
|
+
}
|
|
2779
3651
|
function applyClosePosition(btn, position) {
|
|
2780
3652
|
const s = btn.style;
|
|
2781
3653
|
s.top = "auto";
|
|
@@ -2844,6 +3716,11 @@ function applyCompositeDesign(card, design) {
|
|
|
2844
3716
|
const s = card.style;
|
|
2845
3717
|
const px = (v) => typeof v === "number" && Number.isFinite(v) ? `${v}px` : null;
|
|
2846
3718
|
const strv = (v) => typeof v === "string" && v ? v : null;
|
|
3719
|
+
if (design.colorMode === "dark") {
|
|
3720
|
+
s.background = "#1f1d1b";
|
|
3721
|
+
s.color = "#f5f0eb";
|
|
3722
|
+
card.classList.add("veo-card--dark");
|
|
3723
|
+
}
|
|
2847
3724
|
if (strv(design.background)) s.background = design.background;
|
|
2848
3725
|
const textColor = strv(design.color) ?? strv(design.textColor);
|
|
2849
3726
|
if (textColor) s.color = textColor;
|
|
@@ -2865,13 +3742,14 @@ function applyCompositeDesign(card, design) {
|
|
|
2865
3742
|
const n = (k) => typeof design[k] === "number" ? design[k] : 0;
|
|
2866
3743
|
s.boxShadow = `${n("shadowX")}px ${n("shadowY")}px ${n("shadowBlur")}px ${n("shadowSpread")}px ${strv(design.shadowColor) ?? "rgba(0,0,0,0.2)"}`;
|
|
2867
3744
|
}
|
|
3745
|
+
const width = typeof design.width === "number" && Number.isFinite(design.width) ? `${Math.max(MIN_CARD_WIDTH, design.width)}px` : null;
|
|
2868
3746
|
if (design.fullWidth === true) {
|
|
2869
3747
|
s.width = "100%";
|
|
2870
3748
|
} else if (design.widthType === "fluid") {
|
|
2871
|
-
s.width = "
|
|
2872
|
-
if (
|
|
2873
|
-
} else if (
|
|
2874
|
-
s.width =
|
|
3749
|
+
s.width = "100%";
|
|
3750
|
+
if (width) s.maxWidth = width;
|
|
3751
|
+
} else if (width) {
|
|
3752
|
+
s.width = width;
|
|
2875
3753
|
}
|
|
2876
3754
|
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
2877
3755
|
const p = px(design[`padding${side}`]);
|
|
@@ -2880,13 +3758,18 @@ function applyCompositeDesign(card, design) {
|
|
|
2880
3758
|
if (m) s.setProperty(`margin-${side.toLowerCase()}`, m);
|
|
2881
3759
|
}
|
|
2882
3760
|
}
|
|
2883
|
-
var ROW_ALIGN, CompositeRenderer, CLOSE_INSET;
|
|
3761
|
+
var ROW_ALIGN, ROW_JUSTIFY, ROW_VALIGN, CompositeRenderer, CLOSE_INSET, CLOSE_RESERVE_PX, CARD_DEFAULT_PAD_PX, MIN_CARD_WIDTH, NARROW_CARD_WIDTH;
|
|
2884
3762
|
var init_composite_renderer = __esm({
|
|
2885
3763
|
"src/plugins/guides/renderers/composite-renderer.ts"() {
|
|
2886
3764
|
init_block_builder();
|
|
3765
|
+
init_checklist_block();
|
|
2887
3766
|
init_code_block();
|
|
2888
3767
|
init_form_content();
|
|
2889
3768
|
init_inline_host();
|
|
3769
|
+
init_layout();
|
|
3770
|
+
init_rich_text();
|
|
3771
|
+
init_scroll_lock();
|
|
3772
|
+
init_snooze();
|
|
2890
3773
|
init_wait_for_element();
|
|
2891
3774
|
init_badge_marker();
|
|
2892
3775
|
init_base_renderer();
|
|
@@ -2896,6 +3779,12 @@ var init_composite_renderer = __esm({
|
|
|
2896
3779
|
center: "center",
|
|
2897
3780
|
right: "flex-end"
|
|
2898
3781
|
};
|
|
3782
|
+
ROW_JUSTIFY = { ...ROW_ALIGN, distribute: "space-between" };
|
|
3783
|
+
ROW_VALIGN = {
|
|
3784
|
+
top: "flex-start",
|
|
3785
|
+
center: "center",
|
|
3786
|
+
bottom: "flex-end"
|
|
3787
|
+
};
|
|
2899
3788
|
CompositeRenderer = class extends BaseRenderer {
|
|
2900
3789
|
async render(context) {
|
|
2901
3790
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -2914,7 +3803,8 @@ var init_composite_renderer = __esm({
|
|
|
2914
3803
|
const rawDoc = anchor.ownerDocument ?? document;
|
|
2915
3804
|
const { cleanup } = mountCodeBlock(soleCode, rawDoc, context, void 0, {
|
|
2916
3805
|
anchor,
|
|
2917
|
-
pos: inlinePosOf(step.inlinePosition)
|
|
3806
|
+
pos: inlinePosOf(step.inlinePosition),
|
|
3807
|
+
...context.isPreview && soleCode.editorId ? { tagBlockId: soleCode.editorId } : {}
|
|
2918
3808
|
});
|
|
2919
3809
|
this.registerCleanup(cleanup);
|
|
2920
3810
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action: "shown" });
|
|
@@ -2927,6 +3817,10 @@ var init_composite_renderer = __esm({
|
|
|
2927
3817
|
const card = doc.createElement("div");
|
|
2928
3818
|
card.className = "veo-card";
|
|
2929
3819
|
applyCompositeDesign(card, design);
|
|
3820
|
+
if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
|
|
3821
|
+
card.setAttribute("role", "dialog");
|
|
3822
|
+
card.setAttribute("aria-label", design.ariaLabel.trim());
|
|
3823
|
+
}
|
|
2930
3824
|
const dismiss = (action, meta) => {
|
|
2931
3825
|
context.onInteraction({
|
|
2932
3826
|
guideId: context.guide.guideId,
|
|
@@ -2937,6 +3831,28 @@ var init_composite_renderer = __esm({
|
|
|
2937
3831
|
};
|
|
2938
3832
|
const readers = [];
|
|
2939
3833
|
this.buildContent(card, step.blocks ?? [], doc, context, idx, readers, host);
|
|
3834
|
+
if (blocks.length === 0) {
|
|
3835
|
+
card.classList.add("veo-card--empty");
|
|
3836
|
+
if (context.isPreview) {
|
|
3837
|
+
const zone = doc.createElement("div");
|
|
3838
|
+
zone.className = "veo-card-empty-zone";
|
|
3839
|
+
const plus = doc.createElement("b");
|
|
3840
|
+
plus.textContent = "+";
|
|
3841
|
+
const text = doc.createElement("span");
|
|
3842
|
+
text.textContent = "Clic para agregar contenido";
|
|
3843
|
+
zone.append(plus, text);
|
|
3844
|
+
card.appendChild(zone);
|
|
3845
|
+
const applyNarrow = () => {
|
|
3846
|
+
card.classList.toggle("veo-card--narrow", card.clientWidth < NARROW_CARD_WIDTH);
|
|
3847
|
+
};
|
|
3848
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
3849
|
+
const ro = new ResizeObserver(applyNarrow);
|
|
3850
|
+
ro.observe(card);
|
|
3851
|
+
this.registerCleanup(() => ro.disconnect());
|
|
3852
|
+
}
|
|
3853
|
+
applyNarrow();
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
2940
3856
|
const lastBlock = card.lastElementChild;
|
|
2941
3857
|
if (lastBlock && !lastBlock.style.marginBottom) lastBlock.style.marginBottom = "0";
|
|
2942
3858
|
let closeTooltip = null;
|
|
@@ -2954,6 +3870,14 @@ var init_composite_renderer = __esm({
|
|
|
2954
3870
|
if (closeColor) closeBtn.style.color = closeColor;
|
|
2955
3871
|
const closePos = typeof design.closePosition === "string" ? design.closePosition : "top-right";
|
|
2956
3872
|
applyClosePosition(closeBtn, closePos);
|
|
3873
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3874
|
+
const p = design[`closePadding${side}`];
|
|
3875
|
+
if (typeof p === "number")
|
|
3876
|
+
closeBtn.style.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3877
|
+
const m = design[`closeMargin${side}`];
|
|
3878
|
+
if (typeof m === "number")
|
|
3879
|
+
closeBtn.style.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3880
|
+
}
|
|
2957
3881
|
if (hoverColor) {
|
|
2958
3882
|
closeBtn.addEventListener("mouseenter", () => {
|
|
2959
3883
|
closeBtn.style.color = hoverColor;
|
|
@@ -2963,6 +3887,7 @@ var init_composite_renderer = __esm({
|
|
|
2963
3887
|
});
|
|
2964
3888
|
}
|
|
2965
3889
|
card.appendChild(closeBtn);
|
|
3890
|
+
reserveCloseSpace(card, blocks, closePos, design);
|
|
2966
3891
|
}
|
|
2967
3892
|
if (badgeMode && anchor) {
|
|
2968
3893
|
closeTooltip = this.renderBadgeMode(
|
|
@@ -2993,6 +3918,7 @@ var init_composite_renderer = __esm({
|
|
|
2993
3918
|
const bz = typeof design.backdropZIndex === "number" ? design.backdropZIndex : 2e5;
|
|
2994
3919
|
bg.style.zIndex = String(bz);
|
|
2995
3920
|
root.appendChild(bg);
|
|
3921
|
+
this.registerCleanup(lockScroll(doc));
|
|
2996
3922
|
if (design.targetAbove === true) this.registerCleanup(liftTarget(anchor, bz + 1));
|
|
2997
3923
|
}
|
|
2998
3924
|
const wrapper = doc.createElement("div");
|
|
@@ -3019,6 +3945,7 @@ var init_composite_renderer = __esm({
|
|
|
3019
3945
|
this.closeOrSkip(context);
|
|
3020
3946
|
}
|
|
3021
3947
|
});
|
|
3948
|
+
this.registerCleanup(lockScroll(doc));
|
|
3022
3949
|
}
|
|
3023
3950
|
applyViewportPosition(card, step.position, design);
|
|
3024
3951
|
overlay.appendChild(card);
|
|
@@ -3036,83 +3963,217 @@ var init_composite_renderer = __esm({
|
|
|
3036
3963
|
...block.id ? { metadata: { buttonId: block.id } } : {}
|
|
3037
3964
|
});
|
|
3038
3965
|
};
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3966
|
+
const tag = (node, block, index) => {
|
|
3967
|
+
node.dataset.veoBlock = String(index);
|
|
3968
|
+
if (block.editorId) node.dataset.veoBlockId = block.editorId;
|
|
3969
|
+
return node;
|
|
3970
|
+
};
|
|
3971
|
+
const placeholder = (block, label) => {
|
|
3972
|
+
if (!context.isPreview || !block.editorId) return null;
|
|
3973
|
+
const el2 = doc.createElement("div");
|
|
3974
|
+
el2.className = "veo-block-placeholder";
|
|
3975
|
+
el2.textContent = label;
|
|
3976
|
+
return el2;
|
|
3977
|
+
};
|
|
3978
|
+
const buildSequence = (container, cell) => {
|
|
3979
|
+
let i = 0;
|
|
3980
|
+
while (i < cell.length) {
|
|
3981
|
+
const { block: b, index } = cell[i];
|
|
3982
|
+
if (b.type === "button") {
|
|
3983
|
+
const row = doc.createElement("div");
|
|
3984
|
+
row.className = "veo-guide-actions";
|
|
3985
|
+
let rowAlign;
|
|
3986
|
+
while (i < cell.length && cell[i]?.block.type === "button") {
|
|
3987
|
+
const { block: bb, index: bi } = cell[i];
|
|
3988
|
+
if (!rowAlign) {
|
|
3989
|
+
const a = bb.style?.align;
|
|
3990
|
+
if (a === "left" || a === "center" || a === "right") rowAlign = a;
|
|
3991
|
+
}
|
|
3992
|
+
row.appendChild(tag(this.buildButton(bb, doc, context, idx, readers, emit), bb, bi));
|
|
3993
|
+
i++;
|
|
3055
3994
|
}
|
|
3056
|
-
row.
|
|
3995
|
+
if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
|
|
3996
|
+
container.appendChild(row);
|
|
3997
|
+
continue;
|
|
3998
|
+
}
|
|
3999
|
+
if (b.type === "code") {
|
|
4000
|
+
const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
|
|
4001
|
+
const el2 = node2 ?? placeholder(b, "Bloque de c\xF3digo");
|
|
4002
|
+
if (el2) container.appendChild(tag(el2, b, index));
|
|
4003
|
+
this.registerCleanup(cleanup);
|
|
3057
4004
|
i++;
|
|
4005
|
+
continue;
|
|
3058
4006
|
}
|
|
3059
|
-
if (
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
if (b.type === "code") {
|
|
3064
|
-
const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
|
|
3065
|
-
if (node2) card.appendChild(node2);
|
|
3066
|
-
this.registerCleanup(cleanup);
|
|
3067
|
-
i++;
|
|
3068
|
-
continue;
|
|
3069
|
-
}
|
|
3070
|
-
if (b.type === "form") {
|
|
3071
|
-
const form = doc.createElement("div");
|
|
3072
|
-
form.className = "veo-form";
|
|
3073
|
-
for (const field of b.fields ?? []) {
|
|
3074
|
-
const { wrapper, read } = buildField(field, doc);
|
|
3075
|
-
readers.push({ field, read });
|
|
3076
|
-
form.appendChild(wrapper);
|
|
4007
|
+
if (b.type === "form") {
|
|
4008
|
+
container.appendChild(tag(this.buildForm(b, doc, context, idx, readers, emit), b, index));
|
|
4009
|
+
i++;
|
|
4010
|
+
continue;
|
|
3077
4011
|
}
|
|
3078
|
-
|
|
4012
|
+
if (b.type === "checklist") {
|
|
4013
|
+
const el2 = buildChecklistBlock(b, doc, context);
|
|
4014
|
+
applyBlockStyle(el2, b.style, "text");
|
|
4015
|
+
el2.style.alignSelf = "stretch";
|
|
4016
|
+
el2.style.width = "100%";
|
|
4017
|
+
container.appendChild(tag(el2, b, index));
|
|
4018
|
+
i++;
|
|
4019
|
+
continue;
|
|
4020
|
+
}
|
|
4021
|
+
const node = buildContentBlock(b, doc, { editing: context.isPreview === true }) ?? placeholder(
|
|
4022
|
+
b,
|
|
4023
|
+
b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo"
|
|
4024
|
+
);
|
|
4025
|
+
if (node) container.appendChild(tag(node, b, index));
|
|
3079
4026
|
i++;
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
const rowStyles = context.guide.guideSteps[idx]?.rows;
|
|
4030
|
+
for (const row of groupLayout(blocks)) {
|
|
4031
|
+
const rowStyle = Number.isInteger(row.row) ? rowStyles?.[String(row.row)] : void 0;
|
|
4032
|
+
if (row.columns.length === 1 && !rowStyle) {
|
|
4033
|
+
buildSequence(card, row.columns[0].blocks);
|
|
3080
4034
|
continue;
|
|
3081
4035
|
}
|
|
3082
|
-
const
|
|
3083
|
-
|
|
3084
|
-
|
|
4036
|
+
const rowEl = doc.createElement("div");
|
|
4037
|
+
rowEl.className = "veo-row";
|
|
4038
|
+
if (Number.isInteger(row.row)) rowEl.dataset.veoRow = String(row.row);
|
|
4039
|
+
for (const column of row.columns) {
|
|
4040
|
+
const colEl = doc.createElement("div");
|
|
4041
|
+
colEl.className = "veo-col";
|
|
4042
|
+
buildSequence(colEl, column.blocks);
|
|
4043
|
+
rowEl.appendChild(colEl);
|
|
4044
|
+
}
|
|
4045
|
+
if (rowStyle) applyRowStyle(rowEl, rowStyle);
|
|
4046
|
+
card.appendChild(rowEl);
|
|
3085
4047
|
}
|
|
3086
4048
|
}
|
|
3087
4049
|
/** Botón con enrutado de acción (navegación / cierre / url / guía / envío de forms). */
|
|
3088
|
-
|
|
4050
|
+
/**
|
|
4051
|
+
* Bloque `form` (encuesta): pregunta (rich text del bloque) + campos + botón
|
|
4052
|
+
* de envío propio (`submit`). Con pregunta, el label del campo se omite (sería
|
|
4053
|
+
* la misma pregunta dos veces) y pasa a `aria-label` del input. El estilo del
|
|
4054
|
+
* bloque viste la pregunta (preset/alineación/color); los márgenes van al
|
|
4055
|
+
* contenedor del bloque entero.
|
|
4056
|
+
*/
|
|
4057
|
+
buildForm(b, doc, context, idx, readers, emit) {
|
|
4058
|
+
const form = doc.createElement("div");
|
|
4059
|
+
form.className = "veo-form";
|
|
4060
|
+
const style = b.style ?? {};
|
|
4061
|
+
const { marginTop, marginBottom, ...questionStyle } = style;
|
|
4062
|
+
for (const [side, v] of [
|
|
4063
|
+
["marginTop", marginTop],
|
|
4064
|
+
["marginBottom", marginBottom]
|
|
4065
|
+
]) {
|
|
4066
|
+
if (typeof v === "number" && Number.isFinite(v)) form.style[side] = `${v}px`;
|
|
4067
|
+
}
|
|
4068
|
+
let question = null;
|
|
4069
|
+
if (typeof b.html === "string" && b.html || typeof b.text === "string" && b.text) {
|
|
4070
|
+
question = doc.createElement("div");
|
|
4071
|
+
question.className = "veo-form-question";
|
|
4072
|
+
if (typeof b.html === "string" && b.html) question.appendChild(renderRichText(b.html, doc));
|
|
4073
|
+
else question.textContent = b.text;
|
|
4074
|
+
applyBlockStyle(question, questionStyle, "text");
|
|
4075
|
+
form.appendChild(question);
|
|
4076
|
+
}
|
|
4077
|
+
const questionText = question?.textContent?.trim() ?? "";
|
|
4078
|
+
const own = [];
|
|
4079
|
+
for (const field of b.fields ?? []) {
|
|
4080
|
+
const { wrapper, read } = buildField(field, doc);
|
|
4081
|
+
const st = b.style ?? {};
|
|
4082
|
+
if (typeof st.scaleColor === "string" && st.scaleColor) {
|
|
4083
|
+
wrapper.style.setProperty("--veo-scale-color", st.scaleColor);
|
|
4084
|
+
}
|
|
4085
|
+
if (typeof st.scaleSize === "number" && st.scaleSize >= 24 && st.scaleSize <= 96) {
|
|
4086
|
+
wrapper.style.setProperty("--veo-scale-size", `${st.scaleSize}px`);
|
|
4087
|
+
}
|
|
4088
|
+
if (question) {
|
|
4089
|
+
wrapper.querySelector(".veo-form-label")?.remove();
|
|
4090
|
+
if (questionText) {
|
|
4091
|
+
for (const inp of wrapper.querySelectorAll("input, textarea, select")) {
|
|
4092
|
+
inp.setAttribute("aria-label", questionText);
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
const reader = { field, read, blockId: b.id ?? null };
|
|
4097
|
+
readers.push(reader);
|
|
4098
|
+
own.push(reader);
|
|
4099
|
+
form.appendChild(wrapper);
|
|
4100
|
+
}
|
|
4101
|
+
if (b.submit) {
|
|
4102
|
+
const actions = doc.createElement("div");
|
|
4103
|
+
actions.className = "veo-guide-actions";
|
|
4104
|
+
const submit = { ...b.submit, type: "button" };
|
|
4105
|
+
const sa = submit.style?.align;
|
|
4106
|
+
if (sa === "left" || sa === "center" || sa === "right") {
|
|
4107
|
+
actions.style.justifyContent = ROW_ALIGN[sa] ?? "flex-end";
|
|
4108
|
+
}
|
|
4109
|
+
actions.appendChild(this.buildButton(submit, doc, context, idx, readers, emit, own));
|
|
4110
|
+
form.appendChild(actions);
|
|
4111
|
+
}
|
|
4112
|
+
return form;
|
|
4113
|
+
}
|
|
4114
|
+
buildButton(block, doc, context, idx, readers, emit, submitFirst) {
|
|
3089
4115
|
const btn = doc.createElement("button");
|
|
3090
4116
|
btn.type = "button";
|
|
3091
4117
|
btn.className = "veo-guide-cta";
|
|
3092
4118
|
btn.textContent = block.text?.trim() || "OK";
|
|
4119
|
+
const st = block.style ?? {};
|
|
4120
|
+
if (st.preset === "secondary" || st.preset === "link") {
|
|
4121
|
+
btn.classList.add(`veo-guide-cta--${st.preset}`);
|
|
4122
|
+
}
|
|
4123
|
+
if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
|
|
4124
|
+
btn.setAttribute("aria-label", st.ariaLabel.trim());
|
|
4125
|
+
}
|
|
3093
4126
|
applyBlockStyle(btn, block.style, "button");
|
|
3094
4127
|
const nav = context.nav?.callbacks;
|
|
3095
|
-
|
|
3096
|
-
switch (
|
|
4128
|
+
const runSimple = (spec) => {
|
|
4129
|
+
switch (spec.action) {
|
|
3097
4130
|
case "next":
|
|
3098
|
-
emit(block);
|
|
3099
4131
|
if (nav) nav.onNext();
|
|
3100
4132
|
else context.onClose();
|
|
3101
4133
|
break;
|
|
3102
4134
|
case "prev":
|
|
3103
|
-
emit(block);
|
|
3104
4135
|
nav?.onBack();
|
|
3105
4136
|
break;
|
|
4137
|
+
case "goto":
|
|
4138
|
+
if (nav?.onGoto && typeof spec.targetStep === "number") nav.onGoto(spec.targetStep);
|
|
4139
|
+
else context.onClose();
|
|
4140
|
+
break;
|
|
4141
|
+
case "snooze":
|
|
4142
|
+
snoozeGuide(
|
|
4143
|
+
context.guide.guideId,
|
|
4144
|
+
typeof spec.snoozeEvery === "number" ? spec.snoozeEvery : 1,
|
|
4145
|
+
spec.snoozeUnit ?? "day"
|
|
4146
|
+
);
|
|
4147
|
+
context.onClose();
|
|
4148
|
+
break;
|
|
3106
4149
|
case "url":
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
window.open(
|
|
4150
|
+
if (typeof spec.url === "string" && isSafeUrl(spec.url)) {
|
|
4151
|
+
if (spec.newTab === false) window.location.assign(spec.url);
|
|
4152
|
+
else window.open(spec.url, "_blank", "noopener,noreferrer");
|
|
3110
4153
|
}
|
|
3111
4154
|
break;
|
|
3112
4155
|
case "guide":
|
|
3113
|
-
|
|
3114
|
-
|
|
4156
|
+
if (spec.targetGuideId) context.onOpenGuide?.(spec.targetGuideId);
|
|
4157
|
+
this.closeOrSkip(context);
|
|
4158
|
+
break;
|
|
4159
|
+
default:
|
|
3115
4160
|
this.closeOrSkip(context);
|
|
4161
|
+
}
|
|
4162
|
+
};
|
|
4163
|
+
const runSecond = () => {
|
|
4164
|
+
if (block.secondAction?.action) runSimple(block.secondAction);
|
|
4165
|
+
};
|
|
4166
|
+
const runActions = () => {
|
|
4167
|
+
switch (block.action) {
|
|
4168
|
+
case "next":
|
|
4169
|
+
case "prev":
|
|
4170
|
+
case "goto":
|
|
4171
|
+
case "snooze":
|
|
4172
|
+
case "url":
|
|
4173
|
+
case "guide":
|
|
4174
|
+
emit(block);
|
|
4175
|
+
runSimple(block);
|
|
4176
|
+
runSecond();
|
|
3116
4177
|
break;
|
|
3117
4178
|
case "submit-forms":
|
|
3118
4179
|
case "submit-forms-advance":
|
|
@@ -3131,7 +4192,12 @@ var init_composite_renderer = __esm({
|
|
|
3131
4192
|
default:
|
|
3132
4193
|
emit(block);
|
|
3133
4194
|
this.closeOrSkip(context);
|
|
4195
|
+
runSecond();
|
|
3134
4196
|
}
|
|
4197
|
+
};
|
|
4198
|
+
btn.addEventListener("click", () => {
|
|
4199
|
+
if (submitFirst) this.submitForms(submitFirst, context, idx, btn, runActions);
|
|
4200
|
+
else runActions();
|
|
3135
4201
|
});
|
|
3136
4202
|
return btn;
|
|
3137
4203
|
}
|
|
@@ -3150,17 +4216,27 @@ var init_composite_renderer = __esm({
|
|
|
3150
4216
|
}
|
|
3151
4217
|
answers[field.key] = value;
|
|
3152
4218
|
}
|
|
3153
|
-
if (
|
|
4219
|
+
if (Object.keys(answers).length === 0) {
|
|
4220
|
+
onDone();
|
|
4221
|
+
return;
|
|
4222
|
+
}
|
|
3154
4223
|
btn.disabled = true;
|
|
3155
4224
|
const submitFn = context.onFormSubmit ?? (() => Promise.resolve(true));
|
|
3156
|
-
|
|
4225
|
+
const blockIds = new Set(
|
|
4226
|
+
readers.filter((r) => answers[r.field.key] !== void 0).map((r) => r.blockId ?? "")
|
|
4227
|
+
);
|
|
4228
|
+
const pollId = blockIds.size === 1 ? [...blockIds][0] || void 0 : void 0;
|
|
4229
|
+
void submitFn(answers, { ...pollId ? { pollId } : {}, stepIndex: idx }).then((ok) => {
|
|
3157
4230
|
btn.disabled = false;
|
|
3158
4231
|
if (!ok) return;
|
|
3159
|
-
context.
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
4232
|
+
const lastStep = !context.nav || idx >= context.nav.totalSteps - 1;
|
|
4233
|
+
if (lastStep) {
|
|
4234
|
+
context.onInteraction({
|
|
4235
|
+
guideId: context.guide.guideId,
|
|
4236
|
+
stepIndex: idx,
|
|
4237
|
+
action: "completed"
|
|
4238
|
+
});
|
|
4239
|
+
}
|
|
3164
4240
|
onDone();
|
|
3165
4241
|
});
|
|
3166
4242
|
}
|
|
@@ -3203,9 +4279,7 @@ var init_composite_renderer = __esm({
|
|
|
3203
4279
|
root.appendChild(wrapper);
|
|
3204
4280
|
if (marker) {
|
|
3205
4281
|
host.style.display = "inline-block";
|
|
3206
|
-
(anchor.ownerDocument ?? document).body.appendChild(host);
|
|
3207
4282
|
this.registerCleanup(attachMarker(anchor, host, marker));
|
|
3208
|
-
this.registerCleanup(() => host.remove());
|
|
3209
4283
|
} else {
|
|
3210
4284
|
host.style.display = "inline-block";
|
|
3211
4285
|
const pos = inlinePosOf(step.inlinePosition);
|
|
@@ -3269,6 +4343,10 @@ var init_composite_renderer = __esm({
|
|
|
3269
4343
|
}
|
|
3270
4344
|
};
|
|
3271
4345
|
CLOSE_INSET = "10px";
|
|
4346
|
+
CLOSE_RESERVE_PX = 42;
|
|
4347
|
+
CARD_DEFAULT_PAD_PX = 20;
|
|
4348
|
+
MIN_CARD_WIDTH = 120;
|
|
4349
|
+
NARROW_CARD_WIDTH = 260;
|
|
3272
4350
|
}
|
|
3273
4351
|
});
|
|
3274
4352
|
function applyFloatingPosition(wrapper, placement) {
|
|
@@ -3364,6 +4442,7 @@ var FormRenderer;
|
|
|
3364
4442
|
var init_form_renderer = __esm({
|
|
3365
4443
|
"src/plugins/guides/renderers/form-renderer.ts"() {
|
|
3366
4444
|
init_form_content();
|
|
4445
|
+
init_scroll_lock();
|
|
3367
4446
|
init_base_renderer();
|
|
3368
4447
|
FormRenderer = class extends BaseRenderer {
|
|
3369
4448
|
render(context) {
|
|
@@ -3379,6 +4458,7 @@ var init_form_renderer = __esm({
|
|
|
3379
4458
|
mountFormContent(card, context, doc);
|
|
3380
4459
|
overlay.appendChild(card);
|
|
3381
4460
|
root.appendChild(overlay);
|
|
4461
|
+
this.registerCleanup(lockScroll(doc));
|
|
3382
4462
|
const dismiss = () => {
|
|
3383
4463
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
|
|
3384
4464
|
context.onClose();
|
|
@@ -3535,12 +4615,25 @@ var ModalRenderer;
|
|
|
3535
4615
|
var init_modal_renderer = __esm({
|
|
3536
4616
|
"src/plugins/guides/renderers/modal-renderer.ts"() {
|
|
3537
4617
|
init_block_builder();
|
|
4618
|
+
init_scroll_lock();
|
|
3538
4619
|
init_walkthrough_block_builder();
|
|
3539
4620
|
init_base_renderer();
|
|
3540
4621
|
ModalRenderer = class extends BaseRenderer {
|
|
3541
4622
|
constructor() {
|
|
3542
4623
|
super(...arguments);
|
|
3543
4624
|
this.overlay = null;
|
|
4625
|
+
/** Suelta el bloqueo de scroll del backdrop (null = sin backdrop). */
|
|
4626
|
+
this.releaseScroll = null;
|
|
4627
|
+
}
|
|
4628
|
+
/** Con backdrop la app de atrás queda congelada; sin él, scrollea normal. */
|
|
4629
|
+
syncScrollLock(step) {
|
|
4630
|
+
const wantLock = step.style?.backdrop !== false;
|
|
4631
|
+
if (wantLock && !this.releaseScroll) {
|
|
4632
|
+
this.releaseScroll = lockScroll(this.host?.ownerDocument ?? document);
|
|
4633
|
+
} else if (!wantLock && this.releaseScroll) {
|
|
4634
|
+
this.releaseScroll();
|
|
4635
|
+
this.releaseScroll = null;
|
|
4636
|
+
}
|
|
3544
4637
|
}
|
|
3545
4638
|
render(context) {
|
|
3546
4639
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -3552,6 +4645,7 @@ var init_modal_renderer = __esm({
|
|
|
3552
4645
|
const overlay = ownerDocument.createElement("div");
|
|
3553
4646
|
overlay.className = overlayClassOf(step);
|
|
3554
4647
|
this.overlay = overlay;
|
|
4648
|
+
this.syncScrollLock(step);
|
|
3555
4649
|
const card = ownerDocument.createElement("div");
|
|
3556
4650
|
card.className = "veo-modal-card";
|
|
3557
4651
|
const dismiss = (action, ctaUrl, meta) => {
|
|
@@ -3609,9 +4703,12 @@ var init_modal_renderer = __esm({
|
|
|
3609
4703
|
}
|
|
3610
4704
|
onLiveUpdate(step) {
|
|
3611
4705
|
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
4706
|
+
this.syncScrollLock(step);
|
|
3612
4707
|
}
|
|
3613
4708
|
destroy() {
|
|
3614
4709
|
this.overlay = null;
|
|
4710
|
+
this.releaseScroll?.();
|
|
4711
|
+
this.releaseScroll = null;
|
|
3615
4712
|
super.destroy();
|
|
3616
4713
|
}
|
|
3617
4714
|
};
|
|
@@ -3802,9 +4899,35 @@ function matchesUrl(matcher, pageUrl) {
|
|
|
3802
4899
|
}
|
|
3803
4900
|
}
|
|
3804
4901
|
function stepMatchesPage(loc, pageUrl) {
|
|
3805
|
-
if (!loc || loc.scope === "sitewide"
|
|
4902
|
+
if (!loc || loc.scope === "sitewide") return true;
|
|
4903
|
+
if (loc.scope === "pages") {
|
|
4904
|
+
const rules = loc.rules ?? [];
|
|
4905
|
+
return rules.some((r) => pageRuleMatches(r, pageUrl));
|
|
4906
|
+
}
|
|
4907
|
+
if (!loc.pattern) return true;
|
|
3806
4908
|
return matchesUrl({ type: loc.matchType ?? "prefix", pattern: loc.pattern }, pageUrl);
|
|
3807
4909
|
}
|
|
4910
|
+
function pageRuleMatches(rule, pageUrl) {
|
|
4911
|
+
const pattern = rule.pattern.toLowerCase();
|
|
4912
|
+
const candidates = urlCandidates(pageUrl).map((c) => c.toLowerCase());
|
|
4913
|
+
switch (rule.type) {
|
|
4914
|
+
case "exact":
|
|
4915
|
+
return candidates.some((c) => c === pattern);
|
|
4916
|
+
case "prefix":
|
|
4917
|
+
return candidates.some((c) => c.startsWith(pattern));
|
|
4918
|
+
case "contains":
|
|
4919
|
+
return candidates.some((c) => c.includes(pattern));
|
|
4920
|
+
case "regex":
|
|
4921
|
+
try {
|
|
4922
|
+
const re = new RegExp(rule.pattern, "i");
|
|
4923
|
+
return candidates.some((c) => re.test(c));
|
|
4924
|
+
} catch {
|
|
4925
|
+
return false;
|
|
4926
|
+
}
|
|
4927
|
+
default:
|
|
4928
|
+
return false;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
3808
4931
|
var init_url_match = __esm({
|
|
3809
4932
|
"src/plugins/guides/url-match.ts"() {
|
|
3810
4933
|
}
|
|
@@ -3973,6 +5096,7 @@ var init_guide_preview = __esm({
|
|
|
3973
5096
|
// Preview de forms: por defecto simula el envío SIN escribir props;
|
|
3974
5097
|
// el caller puede pasar `formSubmit` real (link de preview).
|
|
3975
5098
|
onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
|
|
5099
|
+
...this.input?.fetchProgress ? { fetchProgress: this.input.fetchProgress } : {},
|
|
3976
5100
|
isPreview: true,
|
|
3977
5101
|
...this.input?.formSubmit && this.input.formSubmitNote !== void 0 ? { previewNote: this.input.formSubmitNote } : {}
|
|
3978
5102
|
});
|
|
@@ -4365,8 +5489,14 @@ function injectBuilderChrome(opts) {
|
|
|
4365
5489
|
stepsList.append(activation);
|
|
4366
5490
|
s.steps.forEach((st, i) => {
|
|
4367
5491
|
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>';
|
|
4368
|
-
const
|
|
4369
|
-
card
|
|
5492
|
+
const isStepView = s.section === "step" || s.section === "block" || s.section === "add-block" || s.section === "row" || s.section === "structure";
|
|
5493
|
+
const card = stepCard(thumb, st.label, isStepView && s.activeStep === i, {
|
|
5494
|
+
// Se pueden borrar TODOS los pasos (el panel vuelve a Activación).
|
|
5495
|
+
canRemove: true,
|
|
5496
|
+
onEdit: () => act({ action: "select-step", index: i }),
|
|
5497
|
+
onDuplicate: () => act({ action: "duplicate-step", index: i }),
|
|
5498
|
+
onRemove: () => act({ action: "remove-step", index: i })
|
|
5499
|
+
});
|
|
4370
5500
|
stepsList.append(card);
|
|
4371
5501
|
});
|
|
4372
5502
|
const add = el("button", "veo-step-add", `${svg("plus", 18)}<span>Agregar paso</span>`);
|
|
@@ -4395,16 +5525,40 @@ function injectBuilderChrome(opts) {
|
|
|
4395
5525
|
}
|
|
4396
5526
|
};
|
|
4397
5527
|
}
|
|
4398
|
-
function stepCard(inner, label, active2) {
|
|
4399
|
-
const
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
5528
|
+
function stepCard(inner, label, active2, actions) {
|
|
5529
|
+
const cls = `veo-step${active2 ? " is-active" : ""}`;
|
|
5530
|
+
if (!actions) {
|
|
5531
|
+
const b = el(
|
|
5532
|
+
"button",
|
|
5533
|
+
cls,
|
|
5534
|
+
`<div class="veo-step-box">${inner}</div><span class="veo-step-label"></span>`
|
|
5535
|
+
);
|
|
5536
|
+
b.type = "button";
|
|
5537
|
+
b.querySelector(".veo-step-label").textContent = label;
|
|
5538
|
+
b.setAttribute("aria-pressed", active2 ? "true" : "false");
|
|
5539
|
+
return b;
|
|
5540
|
+
}
|
|
5541
|
+
const card = el(
|
|
5542
|
+
"div",
|
|
5543
|
+
cls,
|
|
5544
|
+
`<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>`
|
|
4403
5545
|
);
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
5546
|
+
card.querySelector(".veo-step-label").textContent = label;
|
|
5547
|
+
card.setAttribute("aria-current", active2 ? "step" : "false");
|
|
5548
|
+
const remove = card.querySelector(".veo-step-remove");
|
|
5549
|
+
remove.disabled = !actions.canRemove;
|
|
5550
|
+
if (!actions.canRemove) remove.title = "La gu\xEDa necesita al menos un paso";
|
|
5551
|
+
const on = (sel, fn) => {
|
|
5552
|
+
card.querySelector(sel)?.addEventListener("click", (e) => {
|
|
5553
|
+
e.stopPropagation();
|
|
5554
|
+
fn();
|
|
5555
|
+
});
|
|
5556
|
+
};
|
|
5557
|
+
on(".veo-step-edit", actions.onEdit);
|
|
5558
|
+
on(".veo-step-duplicate", actions.onDuplicate);
|
|
5559
|
+
on(".veo-step-remove", actions.onRemove);
|
|
5560
|
+
card.addEventListener("click", actions.onEdit);
|
|
5561
|
+
return card;
|
|
4408
5562
|
}
|
|
4409
5563
|
var CHROME_TOPBAR_H, CHROME_STEPS_H, ICONS, CHROME_CSS;
|
|
4410
5564
|
var init_builder_chrome = __esm({
|
|
@@ -4421,6 +5575,9 @@ var init_builder_chrome = __esm({
|
|
|
4421
5575
|
chevronDown: '<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/>',
|
|
4422
5576
|
chevronUp: '<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/>',
|
|
4423
5577
|
plus: '<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>',
|
|
5578
|
+
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"/>',
|
|
5579
|
+
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"/>',
|
|
5580
|
+
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"/>',
|
|
4424
5581
|
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"/>',
|
|
4425
5582
|
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"/>'
|
|
4426
5583
|
};
|
|
@@ -4507,10 +5664,31 @@ var init_builder_chrome = __esm({
|
|
|
4507
5664
|
.veo-steps-list { display: flex; align-items: flex-start; gap: 20px; }
|
|
4508
5665
|
.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; }
|
|
4509
5666
|
.veo-step-box {
|
|
5667
|
+
position: relative; overflow: hidden;
|
|
4510
5668
|
width: 116px; height: 64px; border-radius: 10px; background: #fff; border: 2px solid transparent;
|
|
4511
5669
|
display: flex; align-items: center; justify-content: center; color: #2b2623;
|
|
4512
5670
|
box-shadow: 0 1px 2px rgba(0,0,0,0.06);
|
|
4513
5671
|
}
|
|
5672
|
+
/* Capa de acciones del paso: aparece al pasar el mouse o al enfocar un bot\xF3n. */
|
|
5673
|
+
.veo-step-hover {
|
|
5674
|
+
position: absolute; inset: 0; background: #2b2623; border-radius: 8px;
|
|
5675
|
+
display: flex; flex-direction: column; padding: 3px 4px 6px; box-sizing: border-box;
|
|
5676
|
+
opacity: 0; transition: opacity 120ms ease-out;
|
|
5677
|
+
}
|
|
5678
|
+
.veo-step:hover .veo-step-hover, .veo-step:focus-within .veo-step-hover { opacity: 1; }
|
|
5679
|
+
.veo-step-hover-top { display: flex; justify-content: space-between; align-items: center; }
|
|
5680
|
+
.veo-step-hover-main { flex: 1; display: flex; align-items: center; justify-content: center; }
|
|
5681
|
+
.veo-step-icon {
|
|
5682
|
+
border: 0; background: transparent; color: #fff; cursor: pointer; padding: 0;
|
|
5683
|
+
width: 20px; height: 20px; border-radius: 5px; display: inline-flex; align-items: center; justify-content: center;
|
|
5684
|
+
}
|
|
5685
|
+
.veo-step-icon:hover { background: rgba(255,255,255,0.14); }
|
|
5686
|
+
.veo-step-icon:disabled { opacity: 0.35; cursor: default; background: transparent; }
|
|
5687
|
+
.veo-step-edit {
|
|
5688
|
+
width: 100%; margin: 0 6px; border: 0; background: #dbeefc; color: #1d6fb8; font: inherit; font-size: 13px; font-weight: 600;
|
|
5689
|
+
border-radius: 6px; padding: 5px 10px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 6px;
|
|
5690
|
+
}
|
|
5691
|
+
.veo-step-edit:hover { background: #cbe4f9; }
|
|
4514
5692
|
.veo-step.is-active .veo-step-box { border-color: #b74d00; }
|
|
4515
5693
|
.veo-step-label { font-size: 13px; color: #6b635d; }
|
|
4516
5694
|
.veo-step.is-active .veo-step-label { color: #2b2623; font-weight: 600; }
|
|
@@ -4548,6 +5726,8 @@ function injectBuilderPanel(opts) {
|
|
|
4548
5726
|
},
|
|
4549
5727
|
isVisible: () => false,
|
|
4550
5728
|
setTopOffset: () => {
|
|
5729
|
+
},
|
|
5730
|
+
setSize: () => {
|
|
4551
5731
|
}
|
|
4552
5732
|
};
|
|
4553
5733
|
}
|
|
@@ -4600,6 +5780,21 @@ function injectBuilderPanel(opts) {
|
|
|
4600
5780
|
card.style.display = next && !(popped && !popped.closed) ? "" : "none";
|
|
4601
5781
|
};
|
|
4602
5782
|
minimize.addEventListener("click", () => setVisible(false));
|
|
5783
|
+
let compactRestore = null;
|
|
5784
|
+
const setSize = (size) => {
|
|
5785
|
+
if (maximized || popped && !popped.closed) return;
|
|
5786
|
+
if (size) {
|
|
5787
|
+
if (!compactRestore) compactRestore = { w: card.offsetWidth, h: card.offsetHeight };
|
|
5788
|
+
card.style.minHeight = "0";
|
|
5789
|
+
card.style.width = `${clamp3(Math.round(size.width), MIN_W, window.innerWidth - MARGIN * 2)}px`;
|
|
5790
|
+
card.style.height = `${clamp3(Math.round(size.height) + HEADER_H, HEADER_H + 80, window.innerHeight - top - MARGIN)}px`;
|
|
5791
|
+
} else if (compactRestore) {
|
|
5792
|
+
card.style.minHeight = "";
|
|
5793
|
+
card.style.width = `${compactRestore.w}px`;
|
|
5794
|
+
card.style.height = `${compactRestore.h}px`;
|
|
5795
|
+
compactRestore = null;
|
|
5796
|
+
}
|
|
5797
|
+
};
|
|
4603
5798
|
maximize.addEventListener("click", () => {
|
|
4604
5799
|
maximized = !maximized;
|
|
4605
5800
|
if (maximized) {
|
|
@@ -4699,6 +5894,7 @@ function injectBuilderPanel(opts) {
|
|
|
4699
5894
|
title.textContent = t;
|
|
4700
5895
|
},
|
|
4701
5896
|
setVisible,
|
|
5897
|
+
setSize,
|
|
4702
5898
|
isVisible: () => visible,
|
|
4703
5899
|
setTopOffset: (px) => {
|
|
4704
5900
|
topOffset = px;
|
|
@@ -5284,6 +6480,9 @@ function initBuilderMode() {
|
|
|
5284
6480
|
let staticTarget = null;
|
|
5285
6481
|
let manipulator = null;
|
|
5286
6482
|
let dropTarget = null;
|
|
6483
|
+
let cardClick = null;
|
|
6484
|
+
let rawBlock = null;
|
|
6485
|
+
let selectedBlockId = null;
|
|
5287
6486
|
let dragGhost = null;
|
|
5288
6487
|
let previewStepIndex = 0;
|
|
5289
6488
|
let pendingPreview = null;
|
|
@@ -5304,13 +6503,742 @@ function initBuilderMode() {
|
|
|
5304
6503
|
};
|
|
5305
6504
|
const previewMatchesPage = (cmd) => stepMatchesPage(previewStep(cmd)?.pageLocation, window.location.href);
|
|
5306
6505
|
const postPreviewStatus = (payload) => post({ type: "preview-status", payload });
|
|
5307
|
-
const
|
|
6506
|
+
const detachPreviewTools = () => {
|
|
5308
6507
|
manipulator?.detach();
|
|
5309
6508
|
manipulator = null;
|
|
5310
6509
|
dropTarget?.detach();
|
|
5311
6510
|
dropTarget = null;
|
|
6511
|
+
cardClick?.();
|
|
6512
|
+
cardClick = null;
|
|
6513
|
+
rowHover?.();
|
|
6514
|
+
rowHover = null;
|
|
6515
|
+
hoverRowId = null;
|
|
6516
|
+
hoverCard = false;
|
|
6517
|
+
rawBlock?.detach();
|
|
6518
|
+
rawBlock = null;
|
|
6519
|
+
shownPreview = null;
|
|
6520
|
+
};
|
|
6521
|
+
const withholdPreview = () => {
|
|
6522
|
+
detachPreviewTools();
|
|
6523
|
+
shownPreview = null;
|
|
5312
6524
|
closeGuidePreview();
|
|
5313
6525
|
};
|
|
6526
|
+
const FRAME_PAD = 4;
|
|
6527
|
+
const FRAME_MIN_H = 0;
|
|
6528
|
+
const TOOLBAR_H = 30;
|
|
6529
|
+
const ICON = {
|
|
6530
|
+
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>',
|
|
6531
|
+
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>',
|
|
6532
|
+
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>'
|
|
6533
|
+
};
|
|
6534
|
+
const ICON_MOVE = {
|
|
6535
|
+
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>',
|
|
6536
|
+
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>'
|
|
6537
|
+
};
|
|
6538
|
+
const moveAbility = (blocks, blockId) => {
|
|
6539
|
+
for (const row of groupLayout(blocks)) {
|
|
6540
|
+
for (let c = 0; c < row.columns.length; c++) {
|
|
6541
|
+
const col = row.columns[c];
|
|
6542
|
+
if (!col.blocks.some((b) => b.block.editorId === blockId)) continue;
|
|
6543
|
+
return { left: c > 0, right: c < row.columns.length - 1 };
|
|
6544
|
+
}
|
|
6545
|
+
}
|
|
6546
|
+
return { left: false, right: false };
|
|
6547
|
+
};
|
|
6548
|
+
let previewBlocks = [];
|
|
6549
|
+
let hoverRowId = null;
|
|
6550
|
+
let selectedRowId = null;
|
|
6551
|
+
let rowHover = null;
|
|
6552
|
+
let hoverCard = false;
|
|
6553
|
+
let panelSection = null;
|
|
6554
|
+
let shownPreview = null;
|
|
6555
|
+
const structureOf = (step) => {
|
|
6556
|
+
if (!step) return "";
|
|
6557
|
+
const { blocks, ...rest } = step;
|
|
6558
|
+
return JSON.stringify({
|
|
6559
|
+
rest,
|
|
6560
|
+
blocks: (blocks ?? []).map((b) => {
|
|
6561
|
+
const { text: _t, html: _h, style: _s, ...keep } = b;
|
|
6562
|
+
return keep;
|
|
6563
|
+
})
|
|
6564
|
+
});
|
|
6565
|
+
};
|
|
6566
|
+
const tryPatchPreview = (cmd) => {
|
|
6567
|
+
const prev = shownPreview;
|
|
6568
|
+
if (!prev || cmd.guide.guideType !== "composite" || prev.guide.guideType !== "composite") {
|
|
6569
|
+
return false;
|
|
6570
|
+
}
|
|
6571
|
+
const idx = cmd.guide.startStepIndex ?? 0;
|
|
6572
|
+
if ((prev.guide.startStepIndex ?? 0) !== idx) return false;
|
|
6573
|
+
const guideSig = (g) => JSON.stringify({ ...g, guideSteps: void 0 });
|
|
6574
|
+
if (guideSig(prev.guide) !== guideSig(cmd.guide) || prev.editable !== cmd.editable)
|
|
6575
|
+
return false;
|
|
6576
|
+
const host = document.querySelector("[data-veo-guide]");
|
|
6577
|
+
const card = host ? previewCard(host) : null;
|
|
6578
|
+
if (!host || !card) return false;
|
|
6579
|
+
const prevStep = prev.guide.guideSteps[idx];
|
|
6580
|
+
const nextStep = cmd.guide.guideSteps[idx];
|
|
6581
|
+
if (!prevStep || !nextStep) return false;
|
|
6582
|
+
if (structureOf(prevStep) !== structureOf(nextStep)) return false;
|
|
6583
|
+
const doc = card.ownerDocument;
|
|
6584
|
+
const prevBlocks = prevStep.blocks ?? [];
|
|
6585
|
+
const nextBlocks = nextStep.blocks ?? [];
|
|
6586
|
+
for (let i = 0; i < nextBlocks.length; i++) {
|
|
6587
|
+
const a = prevBlocks[i];
|
|
6588
|
+
const b = nextBlocks[i];
|
|
6589
|
+
if (a.text === b.text && a.html === b.html && JSON.stringify(a.style) === JSON.stringify(b.style)) {
|
|
6590
|
+
continue;
|
|
6591
|
+
}
|
|
6592
|
+
if (!b.editorId) return false;
|
|
6593
|
+
const node = card.querySelector(`[data-veo-block-id="${b.editorId}"]`);
|
|
6594
|
+
if (!node) return false;
|
|
6595
|
+
if (b.type === "button") {
|
|
6596
|
+
node.textContent = b.text?.trim() || "OK";
|
|
6597
|
+
node.removeAttribute("style");
|
|
6598
|
+
node.classList.remove("veo-guide-cta--secondary", "veo-guide-cta--link");
|
|
6599
|
+
const st = b.style ?? {};
|
|
6600
|
+
if (st.preset === "secondary" || st.preset === "link") {
|
|
6601
|
+
node.classList.add(`veo-guide-cta--${st.preset}`);
|
|
6602
|
+
}
|
|
6603
|
+
if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
|
|
6604
|
+
node.setAttribute("aria-label", st.ariaLabel.trim());
|
|
6605
|
+
} else node.removeAttribute("aria-label");
|
|
6606
|
+
applyBlockStyle(node, b.style, "button");
|
|
6607
|
+
continue;
|
|
6608
|
+
}
|
|
6609
|
+
if (b.type === "code" || b.type === "form") return false;
|
|
6610
|
+
const fresh = buildContentBlock(b, doc, { editing: true }) ?? (() => {
|
|
6611
|
+
const el2 = doc.createElement("div");
|
|
6612
|
+
el2.className = "veo-block-placeholder";
|
|
6613
|
+
el2.textContent = b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo";
|
|
6614
|
+
return el2;
|
|
6615
|
+
})();
|
|
6616
|
+
fresh.dataset.veoBlock = node.dataset.veoBlock ?? String(i);
|
|
6617
|
+
fresh.dataset.veoBlockId = b.editorId;
|
|
6618
|
+
node.replaceWith(fresh);
|
|
6619
|
+
}
|
|
6620
|
+
shownPreview = cmd;
|
|
6621
|
+
previewBlocks = nextBlocks;
|
|
6622
|
+
applyBlockHighlight(host, idx);
|
|
6623
|
+
renderGapZones(host, idx, nextBlocks);
|
|
6624
|
+
return true;
|
|
6625
|
+
};
|
|
6626
|
+
const previewCard = (host) => host.shadowRoot?.querySelector(".veo-card") ?? null;
|
|
6627
|
+
const clearBlockHighlight = (host) => {
|
|
6628
|
+
const card = previewCard(host);
|
|
6629
|
+
if (!card) return;
|
|
6630
|
+
for (const el2 of card.querySelectorAll(".veo-block--selected")) {
|
|
6631
|
+
el2.classList.remove("veo-block--selected");
|
|
6632
|
+
}
|
|
6633
|
+
card.querySelector(".veo-block-toolbar")?.remove();
|
|
6634
|
+
card.querySelector(".veo-block-frame")?.remove();
|
|
6635
|
+
};
|
|
6636
|
+
const applyBlockHighlight = (host, stepIndex) => {
|
|
6637
|
+
clearBlockHighlight(host);
|
|
6638
|
+
const card = previewCard(host);
|
|
6639
|
+
if (!card) return;
|
|
6640
|
+
const el2 = selectedBlockId ? card.querySelector(`[data-veo-block-id="${selectedBlockId}"]`) : null;
|
|
6641
|
+
if (!el2 || !selectedBlockId) {
|
|
6642
|
+
renderRowChrome(host, stepIndex);
|
|
6643
|
+
return;
|
|
6644
|
+
}
|
|
6645
|
+
el2.classList.add("veo-block--selected");
|
|
6646
|
+
const doc = card.ownerDocument;
|
|
6647
|
+
const cs = getComputedStyle(card);
|
|
6648
|
+
const padL = Number.parseFloat(cs.paddingLeft) || 0;
|
|
6649
|
+
const padT = Number.parseFloat(cs.paddingTop) || 0;
|
|
6650
|
+
const padR = Number.parseFloat(cs.paddingRight) || 0;
|
|
6651
|
+
const padB = Number.parseFloat(cs.paddingBottom) || 0;
|
|
6652
|
+
const parent = el2.parentElement;
|
|
6653
|
+
const inCol = parent?.classList.contains("veo-col") ? parent : null;
|
|
6654
|
+
const boxL = inCol ? inCol.offsetLeft : padL;
|
|
6655
|
+
const boxR = inCol ? inCol.offsetLeft + inCol.offsetWidth : card.clientWidth - padR;
|
|
6656
|
+
const boxT = padT;
|
|
6657
|
+
const boxB = card.clientHeight - padB;
|
|
6658
|
+
const fx = Math.max(boxL, el2.offsetLeft - FRAME_PAD);
|
|
6659
|
+
const fr = Math.min(boxR, el2.offsetLeft + el2.offsetWidth + FRAME_PAD);
|
|
6660
|
+
const fw = Math.max(0, fr - fx);
|
|
6661
|
+
const fy = Math.max(boxT, el2.offsetTop - FRAME_PAD);
|
|
6662
|
+
const fb = Math.min(boxB, el2.offsetTop + el2.offsetHeight + FRAME_PAD);
|
|
6663
|
+
const fh = Math.max(FRAME_MIN_H, fb - fy);
|
|
6664
|
+
const frame = doc.createElement("div");
|
|
6665
|
+
frame.className = "veo-block-frame";
|
|
6666
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6667
|
+
frame.style.left = `${fx}px`;
|
|
6668
|
+
frame.style.top = `${fy}px`;
|
|
6669
|
+
frame.style.width = `${fw}px`;
|
|
6670
|
+
frame.style.height = `${fh}px`;
|
|
6671
|
+
card.appendChild(frame);
|
|
6672
|
+
const bar = doc.createElement("div");
|
|
6673
|
+
bar.className = "veo-block-toolbar";
|
|
6674
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6675
|
+
const blockId = selectedBlockId;
|
|
6676
|
+
const mk = (action, label, icon, enabled = true) => {
|
|
6677
|
+
const b = doc.createElement("button");
|
|
6678
|
+
b.type = "button";
|
|
6679
|
+
b.title = label;
|
|
6680
|
+
b.setAttribute("aria-label", label);
|
|
6681
|
+
b.className = `veo-block-toolbar-${action}`;
|
|
6682
|
+
b.innerHTML = icon;
|
|
6683
|
+
b.disabled = !enabled;
|
|
6684
|
+
b.addEventListener("click", (e) => {
|
|
6685
|
+
e.preventDefault();
|
|
6686
|
+
e.stopPropagation();
|
|
6687
|
+
post({ type: "block-action", payload: { stepIndex, blockId, action } });
|
|
6688
|
+
});
|
|
6689
|
+
bar.appendChild(b);
|
|
6690
|
+
};
|
|
6691
|
+
const can = moveAbility(previewBlocks, blockId);
|
|
6692
|
+
mk("edit", "Editar", ICON.pencil);
|
|
6693
|
+
mk("duplicate", "Duplicar", ICON.copy);
|
|
6694
|
+
mk("remove", "Eliminar", ICON.trash);
|
|
6695
|
+
mk("move-left", "Mover a la izquierda", ICON_MOVE.left, can.left);
|
|
6696
|
+
mk("move-right", "Mover a la derecha", ICON_MOVE.right, can.right);
|
|
6697
|
+
bar.style.right = `${Math.max(0, card.clientWidth - fr)}px`;
|
|
6698
|
+
card.appendChild(bar);
|
|
6699
|
+
const barH = bar.offsetHeight || TOOLBAR_H;
|
|
6700
|
+
bar.dataset.veoTopAbove = String(fy - barH - 6);
|
|
6701
|
+
bar.dataset.veoTopBelow = String(fb + 6);
|
|
6702
|
+
bar.style.top = bar.dataset.veoTopAbove;
|
|
6703
|
+
renderRowChrome(host, stepIndex);
|
|
6704
|
+
};
|
|
6705
|
+
const chromeRect = (el2) => ({
|
|
6706
|
+
left: el2.offsetLeft,
|
|
6707
|
+
top: el2.offsetTop,
|
|
6708
|
+
right: el2.offsetLeft + el2.offsetWidth,
|
|
6709
|
+
bottom: el2.offsetTop + el2.offsetHeight
|
|
6710
|
+
});
|
|
6711
|
+
const intersects = (a, b) => a.left < b.right && b.left < a.right && a.top < b.bottom && b.top < a.bottom;
|
|
6712
|
+
const resolveToolbarOverlaps = (card) => {
|
|
6713
|
+
const cardBar = card.querySelector(".veo-card-toolbar");
|
|
6714
|
+
const blockBar = card.querySelector(".veo-block-toolbar");
|
|
6715
|
+
const rowBar = card.querySelector(".veo-row-toolbar");
|
|
6716
|
+
if (blockBar?.dataset.veoTopAbove) {
|
|
6717
|
+
blockBar.style.top = `${blockBar.dataset.veoTopAbove}px`;
|
|
6718
|
+
if (cardBar && intersects(chromeRect(blockBar), chromeRect(cardBar))) {
|
|
6719
|
+
blockBar.style.top = `${blockBar.dataset.veoTopBelow}px`;
|
|
6720
|
+
}
|
|
6721
|
+
}
|
|
6722
|
+
if (rowBar?.dataset.veoRight) {
|
|
6723
|
+
rowBar.style.left = "";
|
|
6724
|
+
rowBar.style.right = `${rowBar.dataset.veoRight}px`;
|
|
6725
|
+
rowBar.style.top = `${rowBar.dataset.veoTopBelow}px`;
|
|
6726
|
+
const others = [cardBar, blockBar].filter((b) => !!b);
|
|
6727
|
+
const clashes = () => others.some((o) => intersects(chromeRect(rowBar), chromeRect(o)));
|
|
6728
|
+
if (clashes()) {
|
|
6729
|
+
rowBar.style.right = "";
|
|
6730
|
+
rowBar.style.left = `${rowBar.dataset.veoLeft}px`;
|
|
6731
|
+
}
|
|
6732
|
+
if (clashes() && blockBar) {
|
|
6733
|
+
rowBar.style.left = "";
|
|
6734
|
+
rowBar.style.right = `${rowBar.dataset.veoRight}px`;
|
|
6735
|
+
rowBar.style.top = `${chromeRect(blockBar).bottom + 6}px`;
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
};
|
|
6739
|
+
const rectOf = (el2) => ({
|
|
6740
|
+
left: el2.offsetLeft,
|
|
6741
|
+
top: el2.offsetTop,
|
|
6742
|
+
right: el2.offsetLeft + el2.offsetWidth,
|
|
6743
|
+
bottom: el2.offsetTop + el2.offsetHeight
|
|
6744
|
+
});
|
|
6745
|
+
const union = (rs) => rs.length === 0 ? null : rs.reduce((acc, r) => ({
|
|
6746
|
+
left: Math.min(acc.left, r.left),
|
|
6747
|
+
top: Math.min(acc.top, r.top),
|
|
6748
|
+
right: Math.max(acc.right, r.right),
|
|
6749
|
+
bottom: Math.max(acc.bottom, r.bottom)
|
|
6750
|
+
}));
|
|
6751
|
+
const layoutRows = (card, blocks) => {
|
|
6752
|
+
const elOf = (b) => b.editorId ? card.querySelector(`[data-veo-block-id="${b.editorId}"]`) : null;
|
|
6753
|
+
const cs = getComputedStyle(card);
|
|
6754
|
+
const padL = Number.parseFloat(cs.paddingLeft) || 0;
|
|
6755
|
+
const padR = Number.parseFloat(cs.paddingRight) || 0;
|
|
6756
|
+
const contentLeft = padL;
|
|
6757
|
+
const contentRight = card.clientWidth - padR;
|
|
6758
|
+
const rows = groupLayout(blocks).map((row) => {
|
|
6759
|
+
const columns = row.columns.map((col) => {
|
|
6760
|
+
const items = col.blocks.map(({ block }) => ({ block, el: elOf(block) })).filter(
|
|
6761
|
+
(x) => !!x.el && !!x.block.editorId
|
|
6762
|
+
);
|
|
6763
|
+
const blocksRect2 = union(items.map((x) => rectOf(x.el)));
|
|
6764
|
+
if (!blocksRect2) return null;
|
|
6765
|
+
const colEl = items[0]?.el.parentElement;
|
|
6766
|
+
const rect2 = colEl?.classList.contains("veo-col") ? rectOf(colEl) : blocksRect2;
|
|
6767
|
+
return { items, rect: rect2 };
|
|
6768
|
+
}).filter((c) => c !== null);
|
|
6769
|
+
const blocksRect = union(columns.map((c) => c.rect));
|
|
6770
|
+
if (!blocksRect) return null;
|
|
6771
|
+
const rowEl = columns[0]?.items[0]?.el.parentElement?.parentElement;
|
|
6772
|
+
const rect = rowEl?.classList.contains("veo-row") ? rectOf(rowEl) : { ...blocksRect, left: contentLeft, right: contentRight };
|
|
6773
|
+
const id = columns[0]?.items[0]?.block.editorId;
|
|
6774
|
+
return { id, row: row.row, columns, rect };
|
|
6775
|
+
}).filter((r) => r !== null);
|
|
6776
|
+
return { rows, content: { left: contentLeft, right: contentRight } };
|
|
6777
|
+
};
|
|
6778
|
+
const clearRowChrome = (card) => {
|
|
6779
|
+
card.querySelector(".veo-row-frame")?.remove();
|
|
6780
|
+
card.querySelector(".veo-row-toolbar")?.remove();
|
|
6781
|
+
};
|
|
6782
|
+
const renderRowChrome = (host, stepIndex) => {
|
|
6783
|
+
const card = previewCard(host);
|
|
6784
|
+
if (!card) return;
|
|
6785
|
+
clearRowChrome(card);
|
|
6786
|
+
resolveToolbarOverlaps(card);
|
|
6787
|
+
const { rows } = layoutRows(card, previewBlocks);
|
|
6788
|
+
const rowOfSelected = selectedBlockId ? rows.find(
|
|
6789
|
+
(r) => r.columns.some((c) => c.items.some((i) => i.block.editorId === selectedBlockId))
|
|
6790
|
+
) : void 0;
|
|
6791
|
+
const target = (selectedRowId ? rows.find((r) => r.id === selectedRowId) : void 0) ?? rowOfSelected ?? (hoverRowId ? rows.find((r) => r.id === hoverRowId) : void 0);
|
|
6792
|
+
if (!target) return;
|
|
6793
|
+
const doc = card.ownerDocument;
|
|
6794
|
+
const cs = getComputedStyle(card);
|
|
6795
|
+
const padT = Number.parseFloat(cs.paddingTop) || 0;
|
|
6796
|
+
const padB = Number.parseFloat(cs.paddingBottom) || 0;
|
|
6797
|
+
const PAD = 6;
|
|
6798
|
+
const fx = Math.max(0, target.rect.left - PAD);
|
|
6799
|
+
const fr = Math.min(card.clientWidth, target.rect.right + PAD);
|
|
6800
|
+
const fy = Math.max(Math.min(padT, target.rect.top), target.rect.top - PAD);
|
|
6801
|
+
const fb = Math.min(
|
|
6802
|
+
Math.max(card.clientHeight - padB, target.rect.bottom),
|
|
6803
|
+
target.rect.bottom + PAD
|
|
6804
|
+
);
|
|
6805
|
+
const frame = doc.createElement("div");
|
|
6806
|
+
frame.className = "veo-row-frame";
|
|
6807
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6808
|
+
frame.style.left = `${fx}px`;
|
|
6809
|
+
frame.style.top = `${fy}px`;
|
|
6810
|
+
frame.style.width = `${Math.max(0, fr - fx)}px`;
|
|
6811
|
+
frame.style.height = `${Math.max(0, fb - fy)}px`;
|
|
6812
|
+
card.appendChild(frame);
|
|
6813
|
+
const bar = doc.createElement("div");
|
|
6814
|
+
bar.className = "veo-row-toolbar";
|
|
6815
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6816
|
+
const blockId = target.id;
|
|
6817
|
+
const mk = (action, label, icon, text) => {
|
|
6818
|
+
const b = doc.createElement("button");
|
|
6819
|
+
b.type = "button";
|
|
6820
|
+
b.title = label;
|
|
6821
|
+
b.setAttribute("aria-label", label);
|
|
6822
|
+
b.className = `veo-row-toolbar-${action}`;
|
|
6823
|
+
b.innerHTML = icon;
|
|
6824
|
+
if (text) b.append(text);
|
|
6825
|
+
b.addEventListener("click", (e) => {
|
|
6826
|
+
e.preventDefault();
|
|
6827
|
+
e.stopPropagation();
|
|
6828
|
+
post({ type: "row-action", payload: { stepIndex, blockId, action } });
|
|
6829
|
+
});
|
|
6830
|
+
bar.appendChild(b);
|
|
6831
|
+
};
|
|
6832
|
+
mk("edit", "Editar fila", ICON.pencil, "Editar fila");
|
|
6833
|
+
mk("duplicate", "Duplicar fila", ICON.copy);
|
|
6834
|
+
mk("remove", "Eliminar fila", ICON.trash);
|
|
6835
|
+
card.appendChild(bar);
|
|
6836
|
+
bar.dataset.veoRight = String(Math.max(0, card.clientWidth - fr));
|
|
6837
|
+
bar.dataset.veoLeft = String(fx);
|
|
6838
|
+
bar.dataset.veoTopBelow = String(fb + 6);
|
|
6839
|
+
resolveToolbarOverlaps(card);
|
|
6840
|
+
};
|
|
6841
|
+
const renderCardChrome = (host, stepIndex) => {
|
|
6842
|
+
const card = previewCard(host);
|
|
6843
|
+
if (!card) return;
|
|
6844
|
+
card.querySelector(".veo-card-frame")?.remove();
|
|
6845
|
+
card.querySelector(".veo-card-toolbar")?.remove();
|
|
6846
|
+
if (!hoverCard) {
|
|
6847
|
+
resolveToolbarOverlaps(card);
|
|
6848
|
+
return;
|
|
6849
|
+
}
|
|
6850
|
+
const doc = card.ownerDocument;
|
|
6851
|
+
const frame = doc.createElement("div");
|
|
6852
|
+
frame.className = "veo-card-frame";
|
|
6853
|
+
frame.setAttribute("data-veo-builder-chrome", "");
|
|
6854
|
+
card.appendChild(frame);
|
|
6855
|
+
const bar = doc.createElement("div");
|
|
6856
|
+
bar.className = "veo-card-toolbar";
|
|
6857
|
+
bar.setAttribute("data-veo-builder-chrome", "");
|
|
6858
|
+
const mk = (action, label, active2) => {
|
|
6859
|
+
const b = doc.createElement("button");
|
|
6860
|
+
b.type = "button";
|
|
6861
|
+
b.className = `veo-card-toolbar-${action}${active2 ? " is-active" : ""}`;
|
|
6862
|
+
b.setAttribute("aria-pressed", active2 ? "true" : "false");
|
|
6863
|
+
b.textContent = label;
|
|
6864
|
+
b.addEventListener("click", (e) => {
|
|
6865
|
+
e.preventDefault();
|
|
6866
|
+
e.stopPropagation();
|
|
6867
|
+
post({ type: "card-action", payload: { stepIndex, action } });
|
|
6868
|
+
});
|
|
6869
|
+
bar.appendChild(b);
|
|
6870
|
+
};
|
|
6871
|
+
mk("edit-container", "Editar contenedor", panelSection === "step");
|
|
6872
|
+
mk("edit-rows", "Editar filas", panelSection === "structure");
|
|
6873
|
+
card.appendChild(bar);
|
|
6874
|
+
bar.style.top = `${-Math.round((bar.offsetHeight || 34) / 2)}px`;
|
|
6875
|
+
resolveToolbarOverlaps(card);
|
|
6876
|
+
};
|
|
6877
|
+
const attachRowHover = (host, stepIndex) => {
|
|
6878
|
+
const onMove = (e) => {
|
|
6879
|
+
const card = previewCard(host);
|
|
6880
|
+
if (!card) return;
|
|
6881
|
+
const overCard = e.composedPath().some(
|
|
6882
|
+
(n) => n instanceof Element && (n.classList.contains("veo-card") || n.classList.contains("veo-card-toolbar"))
|
|
6883
|
+
);
|
|
6884
|
+
if (overCard !== hoverCard) {
|
|
6885
|
+
hoverCard = overCard;
|
|
6886
|
+
renderCardChrome(host, stepIndex);
|
|
6887
|
+
}
|
|
6888
|
+
if (e.composedPath().some((n) => n instanceof Element && n.classList.contains("veo-row-toolbar"))) {
|
|
6889
|
+
return;
|
|
6890
|
+
}
|
|
6891
|
+
const rect = card.getBoundingClientRect();
|
|
6892
|
+
const x = e.clientX - rect.left - card.clientLeft;
|
|
6893
|
+
const y = e.clientY - rect.top - card.clientTop;
|
|
6894
|
+
const bar = card.querySelector(".veo-row-toolbar");
|
|
6895
|
+
if (bar) {
|
|
6896
|
+
const AIR = 10;
|
|
6897
|
+
if (x >= bar.offsetLeft - AIR && x <= bar.offsetLeft + bar.offsetWidth + AIR && y >= bar.offsetTop - 14 && y <= bar.offsetTop + bar.offsetHeight + AIR) {
|
|
6898
|
+
return;
|
|
6899
|
+
}
|
|
6900
|
+
}
|
|
6901
|
+
const TOL = 6;
|
|
6902
|
+
const hit = layoutRows(card, previewBlocks).rows.find(
|
|
6903
|
+
(r) => x >= r.rect.left - TOL && x <= r.rect.right + TOL && y >= r.rect.top - TOL && y <= r.rect.bottom + TOL
|
|
6904
|
+
);
|
|
6905
|
+
const next = hit?.id ?? null;
|
|
6906
|
+
if (next === hoverRowId) return;
|
|
6907
|
+
hoverRowId = next;
|
|
6908
|
+
renderRowChrome(host, stepIndex);
|
|
6909
|
+
};
|
|
6910
|
+
const onLeave = () => {
|
|
6911
|
+
if (hoverCard) {
|
|
6912
|
+
hoverCard = false;
|
|
6913
|
+
renderCardChrome(host, stepIndex);
|
|
6914
|
+
}
|
|
6915
|
+
if (hoverRowId === null) return;
|
|
6916
|
+
hoverRowId = null;
|
|
6917
|
+
renderRowChrome(host, stepIndex);
|
|
6918
|
+
};
|
|
6919
|
+
host.addEventListener("mousemove", onMove);
|
|
6920
|
+
host.addEventListener("mouseleave", onLeave);
|
|
6921
|
+
return () => {
|
|
6922
|
+
host.removeEventListener("mousemove", onMove);
|
|
6923
|
+
host.removeEventListener("mouseleave", onLeave);
|
|
6924
|
+
};
|
|
6925
|
+
};
|
|
6926
|
+
const renderGapZones = (host, stepIndex, blocks) => {
|
|
6927
|
+
const card = previewCard(host);
|
|
6928
|
+
if (!card) return;
|
|
6929
|
+
for (const z of card.querySelectorAll(".veo-gap")) z.remove();
|
|
6930
|
+
const doc = card.ownerDocument;
|
|
6931
|
+
const GAP = 18;
|
|
6932
|
+
const EDGE2 = 4;
|
|
6933
|
+
const zone = (kind, x, y, w, h, label, blockId, action) => {
|
|
6934
|
+
const z = doc.createElement("div");
|
|
6935
|
+
z.className = `veo-gap veo-gap--${kind}`;
|
|
6936
|
+
z.setAttribute("data-veo-builder-chrome", "");
|
|
6937
|
+
z.dataset.veoGapAction = action;
|
|
6938
|
+
z.style.left = `${x}px`;
|
|
6939
|
+
z.style.top = `${y}px`;
|
|
6940
|
+
z.style.width = `${Math.max(w, GAP)}px`;
|
|
6941
|
+
z.style.height = `${Math.max(h, GAP)}px`;
|
|
6942
|
+
const line = doc.createElement("i");
|
|
6943
|
+
const btn = doc.createElement("button");
|
|
6944
|
+
btn.type = "button";
|
|
6945
|
+
btn.title = label;
|
|
6946
|
+
btn.setAttribute("aria-label", label);
|
|
6947
|
+
btn.textContent = "+";
|
|
6948
|
+
btn.addEventListener("click", (e) => {
|
|
6949
|
+
e.preventDefault();
|
|
6950
|
+
e.stopPropagation();
|
|
6951
|
+
post({ type: "block-layout", payload: { stepIndex, blockId, action } });
|
|
6952
|
+
});
|
|
6953
|
+
z.append(line, btn);
|
|
6954
|
+
card.appendChild(z);
|
|
6955
|
+
};
|
|
6956
|
+
const { rows, content } = layoutRows(card, blocks);
|
|
6957
|
+
if (rows.length === 0) return;
|
|
6958
|
+
const cw = content.right - content.left;
|
|
6959
|
+
rows.forEach((row, k) => {
|
|
6960
|
+
const first = row.columns[0]?.items[0]?.block.editorId;
|
|
6961
|
+
if (k === 0) {
|
|
6962
|
+
zone(
|
|
6963
|
+
"h",
|
|
6964
|
+
content.left,
|
|
6965
|
+
row.rect.top - EDGE2 - GAP,
|
|
6966
|
+
cw,
|
|
6967
|
+
GAP,
|
|
6968
|
+
"Agregar fila",
|
|
6969
|
+
first,
|
|
6970
|
+
"row-before"
|
|
6971
|
+
);
|
|
6972
|
+
} else {
|
|
6973
|
+
const prev = rows[k - 1];
|
|
6974
|
+
const y = (prev.rect.bottom + row.rect.top) / 2 - GAP / 2;
|
|
6975
|
+
zone("h", content.left, y, cw, GAP, "Agregar fila", first, "row-before");
|
|
6976
|
+
}
|
|
6977
|
+
if (k === rows.length - 1) {
|
|
6978
|
+
zone(
|
|
6979
|
+
"h",
|
|
6980
|
+
content.left,
|
|
6981
|
+
row.rect.bottom + EDGE2,
|
|
6982
|
+
cw,
|
|
6983
|
+
GAP,
|
|
6984
|
+
"Agregar fila",
|
|
6985
|
+
first,
|
|
6986
|
+
"row-after"
|
|
6987
|
+
);
|
|
6988
|
+
}
|
|
6989
|
+
});
|
|
6990
|
+
for (const row of rows) {
|
|
6991
|
+
const rh = row.rect.bottom - row.rect.top;
|
|
6992
|
+
row.columns.forEach((col, j) => {
|
|
6993
|
+
const id = col.items[0]?.block.editorId;
|
|
6994
|
+
if (j === 0) {
|
|
6995
|
+
zone(
|
|
6996
|
+
"v",
|
|
6997
|
+
row.rect.left - EDGE2 - GAP,
|
|
6998
|
+
row.rect.top,
|
|
6999
|
+
GAP,
|
|
7000
|
+
rh,
|
|
7001
|
+
"Agregar columna",
|
|
7002
|
+
id,
|
|
7003
|
+
"col-before"
|
|
7004
|
+
);
|
|
7005
|
+
} else {
|
|
7006
|
+
const prev = row.columns[j - 1];
|
|
7007
|
+
const x = (prev.rect.right + col.rect.left) / 2 - GAP / 2;
|
|
7008
|
+
zone("v", x, row.rect.top, GAP, rh, "Agregar columna", id, "col-before");
|
|
7009
|
+
}
|
|
7010
|
+
if (j === row.columns.length - 1) {
|
|
7011
|
+
zone(
|
|
7012
|
+
"v",
|
|
7013
|
+
row.rect.right + EDGE2,
|
|
7014
|
+
row.rect.top,
|
|
7015
|
+
GAP,
|
|
7016
|
+
rh,
|
|
7017
|
+
"Agregar columna",
|
|
7018
|
+
id,
|
|
7019
|
+
"col-after"
|
|
7020
|
+
);
|
|
7021
|
+
}
|
|
7022
|
+
});
|
|
7023
|
+
}
|
|
7024
|
+
for (const row of rows) {
|
|
7025
|
+
for (const col of row.columns) {
|
|
7026
|
+
const w = col.rect.right - col.rect.left;
|
|
7027
|
+
col.items.forEach((it, i) => {
|
|
7028
|
+
const next = col.items[i + 1];
|
|
7029
|
+
if (next) {
|
|
7030
|
+
const a = rectOf(it.el);
|
|
7031
|
+
const b = rectOf(next.el);
|
|
7032
|
+
const y = (a.bottom + b.top) / 2 - GAP / 2;
|
|
7033
|
+
zone(
|
|
7034
|
+
"h",
|
|
7035
|
+
col.rect.left,
|
|
7036
|
+
y,
|
|
7037
|
+
w,
|
|
7038
|
+
GAP,
|
|
7039
|
+
"Agregar bloque",
|
|
7040
|
+
it.block.editorId,
|
|
7041
|
+
"stack-after"
|
|
7042
|
+
);
|
|
7043
|
+
} else if (row.columns.length > 1 && col.rect.bottom < row.rect.bottom - GAP) {
|
|
7044
|
+
zone(
|
|
7045
|
+
"h",
|
|
7046
|
+
col.rect.left,
|
|
7047
|
+
col.rect.bottom + EDGE2,
|
|
7048
|
+
w,
|
|
7049
|
+
GAP,
|
|
7050
|
+
"Agregar bloque",
|
|
7051
|
+
it.block.editorId,
|
|
7052
|
+
"stack-after"
|
|
7053
|
+
);
|
|
7054
|
+
}
|
|
7055
|
+
});
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
};
|
|
7059
|
+
const attachCardClick = (host, stepIndex) => {
|
|
7060
|
+
let downAt = null;
|
|
7061
|
+
const onDown = (e) => {
|
|
7062
|
+
downAt = { x: e.clientX, y: e.clientY };
|
|
7063
|
+
};
|
|
7064
|
+
const onClick = (e) => {
|
|
7065
|
+
const path = e.composedPath();
|
|
7066
|
+
if (path.some(
|
|
7067
|
+
(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"))
|
|
7068
|
+
)) {
|
|
7069
|
+
return;
|
|
7070
|
+
}
|
|
7071
|
+
const inCard = path.some((n) => n instanceof Element && n.classList.contains("veo-card"));
|
|
7072
|
+
if (!inCard) return;
|
|
7073
|
+
if (downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 4) return;
|
|
7074
|
+
const blockEl = path.find(
|
|
7075
|
+
(n) => n instanceof HTMLElement && !!n.dataset.veoBlockId
|
|
7076
|
+
);
|
|
7077
|
+
if (blockEl) {
|
|
7078
|
+
if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
|
|
7079
|
+
e.preventDefault();
|
|
7080
|
+
e.stopPropagation();
|
|
7081
|
+
}
|
|
7082
|
+
selectedBlockId = blockEl.dataset.veoBlockId ?? null;
|
|
7083
|
+
applyBlockHighlight(host, stepIndex);
|
|
7084
|
+
if (selectedBlockId) {
|
|
7085
|
+
post({ type: "block-clicked", payload: { stepIndex, blockId: selectedBlockId } });
|
|
7086
|
+
}
|
|
7087
|
+
return;
|
|
7088
|
+
}
|
|
7089
|
+
const interactive = path.some(
|
|
7090
|
+
(n) => n instanceof Element && n.matches("button, a, input, textarea, select, label, [contenteditable]")
|
|
7091
|
+
);
|
|
7092
|
+
if (interactive) return;
|
|
7093
|
+
selectedBlockId = null;
|
|
7094
|
+
clearBlockHighlight(host);
|
|
7095
|
+
renderRowChrome(host, stepIndex);
|
|
7096
|
+
post({ type: "card-clicked", payload: { stepIndex } });
|
|
7097
|
+
};
|
|
7098
|
+
host.addEventListener("mousedown", onDown, true);
|
|
7099
|
+
host.addEventListener("click", onClick, true);
|
|
7100
|
+
return () => {
|
|
7101
|
+
host.removeEventListener("mousedown", onDown, true);
|
|
7102
|
+
host.removeEventListener("click", onClick, true);
|
|
7103
|
+
};
|
|
7104
|
+
};
|
|
7105
|
+
const attachRawBlock = (stepIndex, blockId) => {
|
|
7106
|
+
const nodes = Array.from(
|
|
7107
|
+
document.querySelectorAll(`[${RAW_BLOCK_ATTR}="${blockId}"]`)
|
|
7108
|
+
);
|
|
7109
|
+
if (nodes.length === 0) return null;
|
|
7110
|
+
const doc = document;
|
|
7111
|
+
const box = doc.createElement("div");
|
|
7112
|
+
box.setAttribute("data-veo-builder-chrome", "");
|
|
7113
|
+
box.style.cssText = "position:fixed;left:0;top:0;width:0;height:0;z-index:2147483646;";
|
|
7114
|
+
box.style.display = "none";
|
|
7115
|
+
const shadow = box.attachShadow({ mode: "open" });
|
|
7116
|
+
const style = doc.createElement("style");
|
|
7117
|
+
style.textContent = GUIDE_STYLES;
|
|
7118
|
+
shadow.appendChild(style);
|
|
7119
|
+
const frame = doc.createElement("div");
|
|
7120
|
+
frame.className = "veo-block-frame";
|
|
7121
|
+
frame.style.position = "fixed";
|
|
7122
|
+
const bar = doc.createElement("div");
|
|
7123
|
+
bar.className = "veo-block-toolbar";
|
|
7124
|
+
bar.style.position = "fixed";
|
|
7125
|
+
const mk = (action, label, icon, enabled = true) => {
|
|
7126
|
+
const b = doc.createElement("button");
|
|
7127
|
+
b.type = "button";
|
|
7128
|
+
b.title = label;
|
|
7129
|
+
b.setAttribute("aria-label", label);
|
|
7130
|
+
b.className = `veo-block-toolbar-${action}`;
|
|
7131
|
+
b.innerHTML = icon;
|
|
7132
|
+
b.disabled = !enabled;
|
|
7133
|
+
b.addEventListener("click", (e) => {
|
|
7134
|
+
e.preventDefault();
|
|
7135
|
+
e.stopPropagation();
|
|
7136
|
+
post({ type: "block-action", payload: { stepIndex, blockId, action } });
|
|
7137
|
+
});
|
|
7138
|
+
bar.appendChild(b);
|
|
7139
|
+
};
|
|
7140
|
+
mk("edit", "Editar", ICON.pencil);
|
|
7141
|
+
mk("duplicate", "Duplicar", ICON.copy, false);
|
|
7142
|
+
mk("remove", "Eliminar", ICON.trash);
|
|
7143
|
+
mk("move-left", "Mover a la izquierda", ICON_MOVE.left, false);
|
|
7144
|
+
mk("move-right", "Mover a la derecha", ICON_MOVE.right, false);
|
|
7145
|
+
frame.style.display = "none";
|
|
7146
|
+
bar.style.display = "none";
|
|
7147
|
+
const GAP = 18;
|
|
7148
|
+
const EDGE2 = 4;
|
|
7149
|
+
const gap = (kind, label, action) => {
|
|
7150
|
+
const z = doc.createElement("div");
|
|
7151
|
+
z.className = `veo-gap veo-gap--${kind}`;
|
|
7152
|
+
z.style.position = "fixed";
|
|
7153
|
+
z.dataset.veoGapAction = action;
|
|
7154
|
+
const line = doc.createElement("i");
|
|
7155
|
+
const btn = doc.createElement("button");
|
|
7156
|
+
btn.type = "button";
|
|
7157
|
+
btn.title = label;
|
|
7158
|
+
btn.setAttribute("aria-label", label);
|
|
7159
|
+
btn.textContent = "+";
|
|
7160
|
+
btn.addEventListener("click", (e) => {
|
|
7161
|
+
e.preventDefault();
|
|
7162
|
+
e.stopPropagation();
|
|
7163
|
+
post({ type: "block-layout", payload: { stepIndex, blockId, action } });
|
|
7164
|
+
});
|
|
7165
|
+
z.append(line, btn);
|
|
7166
|
+
return z;
|
|
7167
|
+
};
|
|
7168
|
+
const gaps = {
|
|
7169
|
+
top: gap("h", "Agregar fila", "row-before"),
|
|
7170
|
+
bottom: gap("h", "Agregar fila", "row-after"),
|
|
7171
|
+
left: gap("v", "Agregar columna", "col-before"),
|
|
7172
|
+
right: gap("v", "Agregar columna", "col-after")
|
|
7173
|
+
};
|
|
7174
|
+
shadow.append(frame, bar, gaps.top, gaps.bottom, gaps.left, gaps.right);
|
|
7175
|
+
doc.body.appendChild(box);
|
|
7176
|
+
box.style.display = "";
|
|
7177
|
+
let visible = false;
|
|
7178
|
+
const place = (el2, x, y, w, h) => {
|
|
7179
|
+
el2.style.left = `${x}px`;
|
|
7180
|
+
el2.style.top = `${y}px`;
|
|
7181
|
+
el2.style.width = `${w}px`;
|
|
7182
|
+
el2.style.height = `${h}px`;
|
|
7183
|
+
};
|
|
7184
|
+
const reposition = () => {
|
|
7185
|
+
const live = nodes.filter((n) => n.isConnected);
|
|
7186
|
+
if (live.length === 0) return;
|
|
7187
|
+
let l = Number.POSITIVE_INFINITY;
|
|
7188
|
+
let t = Number.POSITIVE_INFINITY;
|
|
7189
|
+
let r = Number.NEGATIVE_INFINITY;
|
|
7190
|
+
let b = Number.NEGATIVE_INFINITY;
|
|
7191
|
+
for (const n of live) {
|
|
7192
|
+
const rc = n.getBoundingClientRect();
|
|
7193
|
+
if (rc.width === 0 && rc.height === 0) continue;
|
|
7194
|
+
l = Math.min(l, rc.left);
|
|
7195
|
+
t = Math.min(t, rc.top);
|
|
7196
|
+
r = Math.max(r, rc.right);
|
|
7197
|
+
b = Math.max(b, rc.bottom);
|
|
7198
|
+
}
|
|
7199
|
+
if (!Number.isFinite(l)) return;
|
|
7200
|
+
place(gaps.top, l, t - EDGE2 - GAP, r - l, GAP);
|
|
7201
|
+
place(gaps.bottom, l, b + EDGE2, r - l, GAP);
|
|
7202
|
+
place(gaps.left, l - EDGE2 - GAP, t, GAP, b - t);
|
|
7203
|
+
place(gaps.right, r + EDGE2, t, GAP, b - t);
|
|
7204
|
+
if (!visible) return;
|
|
7205
|
+
place(frame, l, t, r - l, b - t);
|
|
7206
|
+
const bh = bar.offsetHeight || TOOLBAR_H;
|
|
7207
|
+
const bw = bar.offsetWidth;
|
|
7208
|
+
bar.style.left = `${Math.max(0, r - bw)}px`;
|
|
7209
|
+
bar.style.top = `${Math.max(0, t - bh - 6)}px`;
|
|
7210
|
+
};
|
|
7211
|
+
const select = (id) => {
|
|
7212
|
+
visible = id === blockId;
|
|
7213
|
+
frame.style.display = visible ? "" : "none";
|
|
7214
|
+
bar.style.display = visible ? "" : "none";
|
|
7215
|
+
reposition();
|
|
7216
|
+
};
|
|
7217
|
+
reposition();
|
|
7218
|
+
const onClick = (e) => {
|
|
7219
|
+
const path = e.composedPath();
|
|
7220
|
+
if (!nodes.some((n) => path.includes(n))) return;
|
|
7221
|
+
if (path.some((n) => n instanceof Element && n.matches("button, a"))) {
|
|
7222
|
+
e.preventDefault();
|
|
7223
|
+
e.stopPropagation();
|
|
7224
|
+
}
|
|
7225
|
+
selectedBlockId = blockId;
|
|
7226
|
+
select(blockId);
|
|
7227
|
+
post({ type: "block-clicked", payload: { stepIndex, blockId } });
|
|
7228
|
+
};
|
|
7229
|
+
doc.addEventListener("click", onClick, true);
|
|
7230
|
+
window.addEventListener("scroll", reposition, true);
|
|
7231
|
+
window.addEventListener("resize", reposition);
|
|
7232
|
+
return {
|
|
7233
|
+
select,
|
|
7234
|
+
detach: () => {
|
|
7235
|
+
doc.removeEventListener("click", onClick, true);
|
|
7236
|
+
window.removeEventListener("scroll", reposition, true);
|
|
7237
|
+
window.removeEventListener("resize", reposition);
|
|
7238
|
+
box.remove();
|
|
7239
|
+
}
|
|
7240
|
+
};
|
|
7241
|
+
};
|
|
5314
7242
|
const applyPreviewGated = (cmd) => {
|
|
5315
7243
|
const showNow = !cmd.honorTrigger;
|
|
5316
7244
|
if (showNow) {
|
|
@@ -5330,7 +7258,7 @@ function initBuilderMode() {
|
|
|
5330
7258
|
return;
|
|
5331
7259
|
}
|
|
5332
7260
|
const devices = cmd.guide.activationRules?.devices;
|
|
5333
|
-
if (!showNow &&
|
|
7261
|
+
if (!showNow && previewMode && devices && !isDeviceAllowed(devices)) {
|
|
5334
7262
|
cancelPreviewDelay();
|
|
5335
7263
|
withholdPreview();
|
|
5336
7264
|
postPreviewStatus({ state: "device-mismatch", devices });
|
|
@@ -5382,15 +7310,16 @@ function initBuilderMode() {
|
|
|
5382
7310
|
postPreviewStatus({ state: "waiting-delay", delayMs });
|
|
5383
7311
|
};
|
|
5384
7312
|
const applyPreview = (cmd) => {
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
7313
|
+
if (cmd.editable && !previewMode && tryPatchPreview(cmd)) {
|
|
7314
|
+
previewStepIndex = cmd.guide.startStepIndex ?? 0;
|
|
7315
|
+
return;
|
|
7316
|
+
}
|
|
7317
|
+
detachPreviewTools();
|
|
5389
7318
|
previewStepIndex = cmd.guide.startStepIndex ?? 0;
|
|
5390
7319
|
const rules = cmd.guide.activationRules;
|
|
5391
7320
|
const trig = rules?.trigger;
|
|
5392
7321
|
const sel = rules?.selector?.trim();
|
|
5393
|
-
const gate = cmd.honorTrigger && !previewFired &&
|
|
7322
|
+
const gate = cmd.honorTrigger && !previewFired && previewMode;
|
|
5394
7323
|
const waitsForTarget = (trig === "on_click" || trig === "on_hover") && (!isAutomaticActivation(rules) || previewDismissed);
|
|
5395
7324
|
if (gate && waitsForTarget && sel) {
|
|
5396
7325
|
cancelPreviewDelay();
|
|
@@ -5407,13 +7336,13 @@ function initBuilderMode() {
|
|
|
5407
7336
|
}
|
|
5408
7337
|
disarmPreviewTrigger();
|
|
5409
7338
|
cancelPreviewDelay();
|
|
7339
|
+
const fetchProgress = previewProgressFetcher();
|
|
5410
7340
|
const handle = previewGuide({
|
|
5411
7341
|
...cmd.guide,
|
|
7342
|
+
...fetchProgress ? { fetchProgress } : {},
|
|
5412
7343
|
onClosed: () => {
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
dropTarget?.detach();
|
|
5416
|
-
dropTarget = null;
|
|
7344
|
+
detachPreviewTools();
|
|
7345
|
+
shownPreview = null;
|
|
5417
7346
|
previewFired = false;
|
|
5418
7347
|
previewDismissed = true;
|
|
5419
7348
|
if (previewMode) {
|
|
@@ -5421,9 +7350,7 @@ function initBuilderMode() {
|
|
|
5421
7350
|
const t = r?.trigger;
|
|
5422
7351
|
const selector = r?.selector?.trim();
|
|
5423
7352
|
if ((t === "on_click" || t === "on_hover") && selector) armPreviewTrigger(selector, t);
|
|
5424
|
-
return;
|
|
5425
7353
|
}
|
|
5426
|
-
if (lastPreview) applyPreviewGated(lastPreview);
|
|
5427
7354
|
}
|
|
5428
7355
|
});
|
|
5429
7356
|
if (cmd.honorTrigger) postPreviewStatus({ state: "shown" });
|
|
@@ -5431,7 +7358,23 @@ function initBuilderMode() {
|
|
|
5431
7358
|
void handle.ready.then((result) => {
|
|
5432
7359
|
if (!result.rendered) return;
|
|
5433
7360
|
const host = handle.host();
|
|
5434
|
-
if (!host)
|
|
7361
|
+
if (!host) {
|
|
7362
|
+
const idx = cmd.guide.startStepIndex ?? 0;
|
|
7363
|
+
const only = cmd.guide.guideSteps[idx]?.blocks?.[0];
|
|
7364
|
+
if (only?.type === "code" && only.editorId) {
|
|
7365
|
+
rawBlock = attachRawBlock(idx, only.editorId);
|
|
7366
|
+
rawBlock?.select(selectedBlockId);
|
|
7367
|
+
}
|
|
7368
|
+
return;
|
|
7369
|
+
}
|
|
7370
|
+
host.classList.add("veo-no-anim");
|
|
7371
|
+
shownPreview = cmd;
|
|
7372
|
+
cardClick = attachCardClick(host, cmd.guide.startStepIndex ?? 0);
|
|
7373
|
+
const stepBlocks = cmd.guide.guideSteps[cmd.guide.startStepIndex ?? 0]?.blocks ?? [];
|
|
7374
|
+
previewBlocks = stepBlocks;
|
|
7375
|
+
applyBlockHighlight(host, cmd.guide.startStepIndex ?? 0);
|
|
7376
|
+
renderGapZones(host, cmd.guide.startStepIndex ?? 0, stepBlocks);
|
|
7377
|
+
rowHover = attachRowHover(host, cmd.guide.startStepIndex ?? 0);
|
|
5435
7378
|
manipulator = attachDesignManipulator({
|
|
5436
7379
|
host,
|
|
5437
7380
|
guideType: cmd.guide.guideType,
|
|
@@ -5499,13 +7442,21 @@ function initBuilderMode() {
|
|
|
5499
7442
|
break;
|
|
5500
7443
|
case "editor-state":
|
|
5501
7444
|
chrome?.setState(cmd.state);
|
|
7445
|
+
if (panelSection !== cmd.state.section) {
|
|
7446
|
+
panelSection = cmd.state.section;
|
|
7447
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7448
|
+
if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
|
|
7449
|
+
}
|
|
5502
7450
|
panel?.setTitle(
|
|
5503
|
-
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"
|
|
7451
|
+
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"
|
|
5504
7452
|
);
|
|
5505
7453
|
break;
|
|
5506
7454
|
case "panel-visibility":
|
|
5507
7455
|
panel?.setVisible(cmd.visible);
|
|
5508
7456
|
break;
|
|
7457
|
+
case "panel-size":
|
|
7458
|
+
panel?.setSize(cmd.size);
|
|
7459
|
+
break;
|
|
5509
7460
|
case "start-picker": {
|
|
5510
7461
|
picker?.stop();
|
|
5511
7462
|
const panelWasShown = panel?.isVisible() ?? false;
|
|
@@ -5513,10 +7464,7 @@ function initBuilderMode() {
|
|
|
5513
7464
|
chrome?.setHidden(true);
|
|
5514
7465
|
const previewToRestore = lastPreview;
|
|
5515
7466
|
if (previewToRestore) {
|
|
5516
|
-
|
|
5517
|
-
manipulator = null;
|
|
5518
|
-
dropTarget?.detach();
|
|
5519
|
-
dropTarget = null;
|
|
7467
|
+
detachPreviewTools();
|
|
5520
7468
|
closeGuidePreview();
|
|
5521
7469
|
}
|
|
5522
7470
|
const restorePanel = () => {
|
|
@@ -5555,11 +7503,21 @@ function initBuilderMode() {
|
|
|
5555
7503
|
case "block-drag":
|
|
5556
7504
|
handleBlockDrag(cmd);
|
|
5557
7505
|
break;
|
|
7506
|
+
case "select-block": {
|
|
7507
|
+
selectedBlockId = cmd.blockId;
|
|
7508
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7509
|
+
if (h && !previewMode) applyBlockHighlight(h, previewStepIndex);
|
|
7510
|
+
if (!previewMode) rawBlock?.select(cmd.blockId);
|
|
7511
|
+
break;
|
|
7512
|
+
}
|
|
7513
|
+
case "select-row": {
|
|
7514
|
+
selectedRowId = cmd.blockId;
|
|
7515
|
+
const h = document.querySelector("[data-veo-guide]");
|
|
7516
|
+
if (h && !previewMode) renderRowChrome(h, previewStepIndex);
|
|
7517
|
+
break;
|
|
7518
|
+
}
|
|
5558
7519
|
case "close-preview":
|
|
5559
|
-
|
|
5560
|
-
manipulator = null;
|
|
5561
|
-
dropTarget?.detach();
|
|
5562
|
-
dropTarget = null;
|
|
7520
|
+
detachPreviewTools();
|
|
5563
7521
|
hideDragGhost();
|
|
5564
7522
|
pendingPreview = null;
|
|
5565
7523
|
lastPreview = null;
|
|
@@ -5612,10 +7570,7 @@ function initBuilderMode() {
|
|
|
5612
7570
|
removeNavWatcher();
|
|
5613
7571
|
picker?.stop();
|
|
5614
7572
|
picker = null;
|
|
5615
|
-
|
|
5616
|
-
manipulator = null;
|
|
5617
|
-
dropTarget?.detach();
|
|
5618
|
-
dropTarget = null;
|
|
7573
|
+
detachPreviewTools();
|
|
5619
7574
|
hideDragGhost();
|
|
5620
7575
|
pendingPreview = null;
|
|
5621
7576
|
disarmPreviewTrigger();
|
|
@@ -5643,6 +7598,8 @@ function initBuilderMode() {
|
|
|
5643
7598
|
case "preview":
|
|
5644
7599
|
if (previewMode) return;
|
|
5645
7600
|
previewMode = true;
|
|
7601
|
+
previewFired = false;
|
|
7602
|
+
previewDismissed = false;
|
|
5646
7603
|
panelWasVisible = panel?.isVisible() ?? true;
|
|
5647
7604
|
panel?.setVisible(false);
|
|
5648
7605
|
chrome?.setPreview(true);
|
|
@@ -5651,6 +7608,8 @@ function initBuilderMode() {
|
|
|
5651
7608
|
case "exit-preview":
|
|
5652
7609
|
if (!previewMode) return;
|
|
5653
7610
|
previewMode = false;
|
|
7611
|
+
disarmPreviewTrigger();
|
|
7612
|
+
cancelPreviewDelay();
|
|
5654
7613
|
chrome?.setPreview(false);
|
|
5655
7614
|
panel?.setVisible(panelWasVisible);
|
|
5656
7615
|
if (lastPreview) applyPreviewGated(lastPreview);
|
|
@@ -5732,13 +7691,36 @@ function resolveDashboardOrigin(originHint) {
|
|
|
5732
7691
|
}
|
|
5733
7692
|
return null;
|
|
5734
7693
|
}
|
|
7694
|
+
function previewProgressFetcher() {
|
|
7695
|
+
const cfg = window.__veoBuilder;
|
|
7696
|
+
if (!cfg?.apiKey || !cfg?.apiUrl) return void 0;
|
|
7697
|
+
const apiUrl = cfg.apiUrl.replace(/\/+$/, "");
|
|
7698
|
+
const apiKey = cfg.apiKey;
|
|
7699
|
+
return async (guideIds) => {
|
|
7700
|
+
const params = new URLSearchParams({
|
|
7701
|
+
endUserId: "veo_preview",
|
|
7702
|
+
guideIds: guideIds.join(","),
|
|
7703
|
+
includeUnpublished: "1"
|
|
7704
|
+
});
|
|
7705
|
+
const res = await fetch(`${apiUrl}/v1/guides/progress?${params}`, {
|
|
7706
|
+
headers: { "X-Api-Key": apiKey }
|
|
7707
|
+
});
|
|
7708
|
+
if (!res.ok) return [];
|
|
7709
|
+
const data = await res.json();
|
|
7710
|
+
return data.items ?? [];
|
|
7711
|
+
};
|
|
7712
|
+
}
|
|
5735
7713
|
var init_builder_mode = __esm({
|
|
5736
7714
|
"src/plugins/builder/builder-mode.ts"() {
|
|
5737
7715
|
init_builder_token();
|
|
5738
7716
|
init_safe_env();
|
|
5739
7717
|
init_activation_devices();
|
|
5740
7718
|
init_activation_rules();
|
|
7719
|
+
init_block_builder();
|
|
7720
|
+
init_code_block();
|
|
5741
7721
|
init_guide_preview();
|
|
7722
|
+
init_layout();
|
|
7723
|
+
init_styles();
|
|
5742
7724
|
init_url_match();
|
|
5743
7725
|
init_block_drop_target();
|
|
5744
7726
|
init_bridge_protocol();
|
|
@@ -7137,6 +9119,7 @@ init_inline_renderer();
|
|
|
7137
9119
|
init_modal_renderer();
|
|
7138
9120
|
init_tooltip_renderer();
|
|
7139
9121
|
init_walkthrough_renderer();
|
|
9122
|
+
init_snooze();
|
|
7140
9123
|
init_url_match();
|
|
7141
9124
|
|
|
7142
9125
|
// src/plugins/guides/walkthrough-state.ts
|
|
@@ -7329,6 +9312,12 @@ var GuidesController = class {
|
|
|
7329
9312
|
* en cada `checkGuides` con {@link pruneComposite}.
|
|
7330
9313
|
*/
|
|
7331
9314
|
this.activeComposite = /* @__PURE__ */ new Map();
|
|
9315
|
+
/**
|
|
9316
|
+
* Progreso de esta sesión, por guía: pasos vistos y completado, según las
|
|
9317
|
+
* interacciones que ESTE SDK emitió (aunque el lote aún no haya viajado al
|
|
9318
|
+
* backend). Se mezcla con la respuesta del backend en `fetchProgress`.
|
|
9319
|
+
*/
|
|
9320
|
+
this.sessionProgress = /* @__PURE__ */ new Map();
|
|
7332
9321
|
this.debug = config.debug === true;
|
|
7333
9322
|
this.apiUrl = config.apiUrl;
|
|
7334
9323
|
this.apiKey = config.apiKey;
|
|
@@ -7415,6 +9404,10 @@ var GuidesController = class {
|
|
|
7415
9404
|
if (this.debug) console.log(`[veo] guides: filtered locally guideId=${guide.guideId}`);
|
|
7416
9405
|
return;
|
|
7417
9406
|
}
|
|
9407
|
+
if (!opts.byUser && isSnoozed(guide.guideId)) {
|
|
9408
|
+
if (this.debug) console.log(`[veo] guides: snoozed guideId=${guide.guideId}`);
|
|
9409
|
+
return;
|
|
9410
|
+
}
|
|
7418
9411
|
if (!opts.byUser && this.isHeldByRepeat(guide)) {
|
|
7419
9412
|
if (this.debug) console.log(`[veo] guides: held by repeat guideId=${guide.guideId}`);
|
|
7420
9413
|
return;
|
|
@@ -7690,6 +9683,17 @@ ${pageUrl}`;
|
|
|
7690
9683
|
const goToStep = (target) => {
|
|
7691
9684
|
cleanup();
|
|
7692
9685
|
if (target < 0 || target >= steps.length) return;
|
|
9686
|
+
const endUserId = this.client.getEndUserId();
|
|
9687
|
+
if (endUserId) {
|
|
9688
|
+
this.enqueueInteraction(
|
|
9689
|
+
guide.guideId,
|
|
9690
|
+
endUserId,
|
|
9691
|
+
sessionId,
|
|
9692
|
+
pageUrl,
|
|
9693
|
+
target > idx ? "step_advanced" : "step_back",
|
|
9694
|
+
target
|
|
9695
|
+
);
|
|
9696
|
+
}
|
|
7693
9697
|
const step = steps[target];
|
|
7694
9698
|
if (step && stepMatchesPage(step.pageLocation, pageUrl)) {
|
|
7695
9699
|
this.renderCompositeStep(guide, target, sessionId, pageUrl, badgeOpen);
|
|
@@ -7714,10 +9718,15 @@ ${pageUrl}`;
|
|
|
7714
9718
|
onInteraction: (e) => this.handleInteraction(guide, sessionId, pageUrl, e),
|
|
7715
9719
|
onClose: cleanup,
|
|
7716
9720
|
onTrack: (name, props) => this.client.track(name, props),
|
|
7717
|
-
onFormSubmit: (answers) => this.submitFormResponse(guide.guideId, answers),
|
|
9721
|
+
onFormSubmit: (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta),
|
|
7718
9722
|
onOpenGuide: (targetGuideId) => {
|
|
7719
9723
|
void this.openGuideById(targetGuideId, sessionId, pageUrl);
|
|
7720
9724
|
},
|
|
9725
|
+
// Lista de tareas: progreso del usuario y abrir una guía en su paso.
|
|
9726
|
+
fetchProgress: (ids) => this.fetchProgress(ids),
|
|
9727
|
+
onOpenGuideAt: (targetGuideId, stepIndex) => {
|
|
9728
|
+
void this.openGuideById(targetGuideId, sessionId, pageUrl, stepIndex);
|
|
9729
|
+
},
|
|
7721
9730
|
// Badge: cuando el MOTOR muestra la guía (automático, elemento objetivo
|
|
7722
9731
|
// o encadenada), el contenido se abre solo; el marcador queda para
|
|
7723
9732
|
// volver a verla. Solo `manual` deja el tooltip cerrado.
|
|
@@ -7745,7 +9754,7 @@ ${pageUrl}`;
|
|
|
7745
9754
|
const onTrack = (eventName, properties) => {
|
|
7746
9755
|
this.client.track(eventName, properties);
|
|
7747
9756
|
};
|
|
7748
|
-
const onFormSubmit = (answers) => this.submitFormResponse(guide.guideId, answers);
|
|
9757
|
+
const onFormSubmit = (answers, meta) => this.submitFormResponse(guide.guideId, answers, meta);
|
|
7749
9758
|
const onOpenGuide = (targetGuideId) => {
|
|
7750
9759
|
void this.openGuideById(targetGuideId, sessionId, pageUrl);
|
|
7751
9760
|
};
|
|
@@ -7765,7 +9774,7 @@ ${pageUrl}`;
|
|
|
7765
9774
|
* un botón); respeta el dedupe de guías activas y el flujo normal de
|
|
7766
9775
|
* tracking. 404/red → no-op (la guía destino pudo borrarse).
|
|
7767
9776
|
*/
|
|
7768
|
-
async openGuideById(guideId, sessionId, pageUrl) {
|
|
9777
|
+
async openGuideById(guideId, sessionId, pageUrl, startStep) {
|
|
7769
9778
|
if (this.activeByGuideId.has(guideId)) return;
|
|
7770
9779
|
const guide = await this.resolver.fetchById(guideId);
|
|
7771
9780
|
if (!guide) {
|
|
@@ -7773,6 +9782,15 @@ ${pageUrl}`;
|
|
|
7773
9782
|
return;
|
|
7774
9783
|
}
|
|
7775
9784
|
const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
|
|
9785
|
+
if (guide.guideType === "composite" && typeof startStep === "number") {
|
|
9786
|
+
const total = guide.guideSteps.length;
|
|
9787
|
+
const idx = Math.max(0, Math.min(total - 1, Math.floor(startStep)));
|
|
9788
|
+
this.enqueueInteractionOnce(guide.guideId, endUserId, sessionId, pageUrl, "shown", 0);
|
|
9789
|
+
if (idx > 0)
|
|
9790
|
+
this.enqueueInteraction(guide.guideId, endUserId, sessionId, pageUrl, "step_advanced", idx);
|
|
9791
|
+
this.renderCompositeStep(guide, idx, sessionId, pageUrl);
|
|
9792
|
+
return;
|
|
9793
|
+
}
|
|
7776
9794
|
if (guide.guideType === "walkthrough" && guide.guideSteps.length > 1) {
|
|
7777
9795
|
if (!this.walkthroughState.hasActive()) {
|
|
7778
9796
|
await this.startWalkthrough(guide, endUserId, sessionId, pageUrl);
|
|
@@ -7983,6 +10001,7 @@ ${pageUrl}`;
|
|
|
7983
10001
|
console.warn("[veo] guides: interaction without endUserId \u2014 skipping enqueue");
|
|
7984
10002
|
return;
|
|
7985
10003
|
}
|
|
10004
|
+
this.noteProgress(guide.guideId, event.action, event.stepIndex);
|
|
7986
10005
|
this.trackerQueue.enqueue({
|
|
7987
10006
|
guideId: guide.guideId,
|
|
7988
10007
|
payload: {
|
|
@@ -8002,6 +10021,7 @@ ${pageUrl}`;
|
|
|
8002
10021
|
* una navegación distinta) y deduplicarlas perdería datos.
|
|
8003
10022
|
*/
|
|
8004
10023
|
enqueueInteraction(guideId, endUserId, sessionId, pageUrl, action, stepIndex) {
|
|
10024
|
+
this.noteProgress(guideId, action, stepIndex);
|
|
8005
10025
|
this.trackerQueue.enqueue({
|
|
8006
10026
|
guideId,
|
|
8007
10027
|
payload: {
|
|
@@ -8014,6 +10034,45 @@ ${pageUrl}`;
|
|
|
8014
10034
|
}
|
|
8015
10035
|
});
|
|
8016
10036
|
}
|
|
10037
|
+
noteProgress(guideId, action, stepIndex) {
|
|
10038
|
+
if (action !== "shown" && action !== "step_advanced" && action !== "step_back" && action !== "completed") {
|
|
10039
|
+
return;
|
|
10040
|
+
}
|
|
10041
|
+
const cur = this.sessionProgress.get(guideId) ?? { maxStep: -1, completed: false };
|
|
10042
|
+
this.sessionProgress.set(guideId, {
|
|
10043
|
+
maxStep: Math.max(cur.maxStep, stepIndex),
|
|
10044
|
+
completed: cur.completed || action === "completed"
|
|
10045
|
+
});
|
|
10046
|
+
}
|
|
10047
|
+
/**
|
|
10048
|
+
* Progreso del usuario en varias guías (bloque «Lista de tareas»): lo pide
|
|
10049
|
+
* al backend y lo eleva con lo visto en esta sesión (nunca lo baja).
|
|
10050
|
+
*/
|
|
10051
|
+
async fetchProgress(guideIds) {
|
|
10052
|
+
const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
|
|
10053
|
+
let rows = [];
|
|
10054
|
+
try {
|
|
10055
|
+
const params = new URLSearchParams({ endUserId, guideIds: guideIds.join(",") });
|
|
10056
|
+
const res = await fetch(`${this.apiUrl}/v1/guides/progress?${params}`, {
|
|
10057
|
+
headers: { "X-Api-Key": this.apiKey }
|
|
10058
|
+
});
|
|
10059
|
+
if (res.ok) rows = (await res.json()).items ?? [];
|
|
10060
|
+
else if (this.debug) console.warn("[veo] guides/progress respondi\xF3", res.status);
|
|
10061
|
+
} catch (err) {
|
|
10062
|
+
if (this.debug) console.warn("[veo] guides/progress fall\xF3:", err);
|
|
10063
|
+
}
|
|
10064
|
+
return rows.map((r) => {
|
|
10065
|
+
const local2 = this.sessionProgress.get(r.guideId);
|
|
10066
|
+
if (!local2) return r;
|
|
10067
|
+
const seen = Math.min(r.totalSteps, local2.maxStep + 1);
|
|
10068
|
+
const completed = r.completed || local2.completed || seen >= r.totalSteps;
|
|
10069
|
+
return {
|
|
10070
|
+
...r,
|
|
10071
|
+
currentStep: completed ? r.totalSteps : Math.max(r.currentStep, seen),
|
|
10072
|
+
completed
|
|
10073
|
+
};
|
|
10074
|
+
});
|
|
10075
|
+
}
|
|
8017
10076
|
/**
|
|
8018
10077
|
* Encola con dedup por sesión del SDK. Para `shown` del primer step:
|
|
8019
10078
|
* no queremos re-emitirlo si el usuario navega entre dos páginas que
|
|
@@ -8058,13 +10117,19 @@ ${pageUrl}`;
|
|
|
8058
10117
|
* propiedades del usuario. Usa el endUserId actual (o el anónimo si aún no
|
|
8059
10118
|
* hubo identify — el backend pre-crea la fila igual que identify).
|
|
8060
10119
|
*/
|
|
8061
|
-
async submitFormResponse(guideId, answers) {
|
|
10120
|
+
async submitFormResponse(guideId, answers, meta) {
|
|
8062
10121
|
const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
|
|
8063
10122
|
try {
|
|
8064
10123
|
const res = await fetch(`${this.apiUrl}/v1/guides/${guideId}/form-response`, {
|
|
8065
10124
|
method: "POST",
|
|
8066
10125
|
headers: { "Content-Type": "application/json", "X-Api-Key": this.apiKey },
|
|
8067
|
-
body: JSON.stringify({
|
|
10126
|
+
body: JSON.stringify({
|
|
10127
|
+
endUserId,
|
|
10128
|
+
answers,
|
|
10129
|
+
sessionId: this.client.getSessionId(),
|
|
10130
|
+
...meta ? { stepPosition: meta.stepIndex } : {},
|
|
10131
|
+
...meta?.pollId ? { pollId: meta.pollId } : {}
|
|
10132
|
+
})
|
|
8068
10133
|
});
|
|
8069
10134
|
if (!res.ok && this.debug) {
|
|
8070
10135
|
console.warn("[veo] form-response respondi\xF3", res.status);
|
|
@@ -8117,7 +10182,7 @@ async function runPreviewMode(opts) {
|
|
|
8117
10182
|
} catch (err) {
|
|
8118
10183
|
if (opts.debug) console.warn("[veo] preview-resolve fall\xF3:", err);
|
|
8119
10184
|
}
|
|
8120
|
-
const submitToBackend = async (answers) => {
|
|
10185
|
+
const submitToBackend = async (answers, meta) => {
|
|
8121
10186
|
if (!guide) return false;
|
|
8122
10187
|
const veo = window.veo;
|
|
8123
10188
|
const endUserId = veo?.getEndUserId?.() ?? veo?.getAnonymousId?.() ?? null;
|
|
@@ -8126,7 +10191,12 @@ async function runPreviewMode(opts) {
|
|
|
8126
10191
|
const res = await fetch(`${opts.apiUrl}/v1/guides/${guide.guideId}/form-response`, {
|
|
8127
10192
|
method: "POST",
|
|
8128
10193
|
headers: { "Content-Type": "application/json", "X-Api-Key": opts.apiKey },
|
|
8129
|
-
body: JSON.stringify({
|
|
10194
|
+
body: JSON.stringify({
|
|
10195
|
+
endUserId,
|
|
10196
|
+
answers,
|
|
10197
|
+
...meta ? { stepPosition: meta.stepIndex } : {},
|
|
10198
|
+
...meta?.pollId ? { pollId: meta.pollId } : {}
|
|
10199
|
+
})
|
|
8130
10200
|
});
|
|
8131
10201
|
return res.ok;
|
|
8132
10202
|
} catch {
|