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
|
@@ -329,6 +329,125 @@ function rebuildNode(node, doc, depth, budget) {
|
|
|
329
329
|
return rebuilt;
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
// src/plugins/guides/video-block.ts
|
|
333
|
+
function videoEmbedUrl(provider, url) {
|
|
334
|
+
let u;
|
|
335
|
+
try {
|
|
336
|
+
u = new URL(url);
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
341
|
+
const host = u.hostname.replace(/^www\./, "").replace(/^m\./, "");
|
|
342
|
+
const id = (re, from) => {
|
|
343
|
+
const m = from.match(re);
|
|
344
|
+
return m?.[1] ?? null;
|
|
345
|
+
};
|
|
346
|
+
switch (provider) {
|
|
347
|
+
case "youtube": {
|
|
348
|
+
let v = null;
|
|
349
|
+
if (host === "youtube.com" || host === "youtube-nocookie.com") {
|
|
350
|
+
v = u.searchParams.get("v") ?? id(/^\/(?:embed|shorts|live)\/([A-Za-z0-9_-]{6,})/, u.pathname);
|
|
351
|
+
} else if (host === "youtu.be") {
|
|
352
|
+
v = id(/^\/([A-Za-z0-9_-]{6,})/, u.pathname);
|
|
353
|
+
}
|
|
354
|
+
if (!v || !/^[A-Za-z0-9_-]{6,}$/.test(v)) return null;
|
|
355
|
+
return `https://www.youtube-nocookie.com/embed/${v}`;
|
|
356
|
+
}
|
|
357
|
+
case "vimeo": {
|
|
358
|
+
if (host !== "vimeo.com" && host !== "player.vimeo.com") return null;
|
|
359
|
+
const v = id(/(\d{6,})/, u.pathname);
|
|
360
|
+
return v ? `https://player.vimeo.com/video/${v}` : null;
|
|
361
|
+
}
|
|
362
|
+
case "loom": {
|
|
363
|
+
if (host !== "loom.com") return null;
|
|
364
|
+
const v = id(/^\/(?:share|embed)\/([A-Za-z0-9]{16,})/, u.pathname);
|
|
365
|
+
return v ? `https://www.loom.com/embed/${v}` : null;
|
|
366
|
+
}
|
|
367
|
+
case "wistia": {
|
|
368
|
+
if (!under(host, "wistia.com") && !under(host, "wistia.net")) return null;
|
|
369
|
+
const v = id(/\/(?:medias|embed\/iframe)\/([A-Za-z0-9]{6,})/, u.pathname);
|
|
370
|
+
return v ? `https://fast.wistia.net/embed/iframe/${v}` : null;
|
|
371
|
+
}
|
|
372
|
+
case "msstream": {
|
|
373
|
+
if (!under(host, "microsoftstream.com")) return null;
|
|
374
|
+
const v = id(/\/(?:embed\/)?video\/([0-9a-f-]{36})/i, u.pathname);
|
|
375
|
+
return v ? `https://web.microsoftstream.com/embed/video/${v}?autoplay=false&showinfo=true` : null;
|
|
376
|
+
}
|
|
377
|
+
case "vidyard": {
|
|
378
|
+
if (!under(host, "vidyard.com")) return null;
|
|
379
|
+
const v = id(/\/watch\/([A-Za-z0-9_-]{8,})/, u.pathname) ?? (host === "play.vidyard.com" ? id(/^\/([A-Za-z0-9_-]{8,})/, u.pathname) : null);
|
|
380
|
+
return v ? `https://play.vidyard.com/${v}` : null;
|
|
381
|
+
}
|
|
382
|
+
case "sharepoint":
|
|
383
|
+
return u.protocol === "https:" && under(host, "sharepoint.com") ? u.href : null;
|
|
384
|
+
case "brightcove":
|
|
385
|
+
return u.protocol === "https:" && host === "players.brightcove.net" && u.searchParams.get("videoId") ? u.href : null;
|
|
386
|
+
case "kaltura":
|
|
387
|
+
return u.protocol === "https:" && under(host, "kaltura.com") && u.pathname.startsWith("/p/") ? u.href : null;
|
|
388
|
+
default:
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function under(host, domain) {
|
|
393
|
+
return host === domain || host.endsWith(`.${domain}`);
|
|
394
|
+
}
|
|
395
|
+
function fileVideoUrl(url) {
|
|
396
|
+
try {
|
|
397
|
+
const u = new URL(url);
|
|
398
|
+
if (u.protocol !== "https:" && u.protocol !== "http:") return null;
|
|
399
|
+
return /\.(mp4|webm|mov)$/i.test(u.pathname) ? u.href : null;
|
|
400
|
+
} catch {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function buildVideoBlock(block, doc, opts = {}) {
|
|
405
|
+
const isFile = block.provider === "file";
|
|
406
|
+
const src = typeof block.url === "string" ? isFile ? fileVideoUrl(block.url) : videoEmbedUrl(block.provider, block.url) : null;
|
|
407
|
+
if (!src) return null;
|
|
408
|
+
const s = block.style ?? {};
|
|
409
|
+
const wrap = doc.createElement("div");
|
|
410
|
+
wrap.className = "veo-guide-video";
|
|
411
|
+
const ratio = s.aspect === "4:3" ? "4 / 3" : "16 / 9";
|
|
412
|
+
wrap.style.aspectRatio = ratio;
|
|
413
|
+
if (s.widthMode === "fill") {
|
|
414
|
+
wrap.style.width = "100%";
|
|
415
|
+
} else {
|
|
416
|
+
const w = typeof s.width === "number" && Number.isFinite(s.width) ? s.width : 400;
|
|
417
|
+
wrap.style.width = `${Math.max(120, Math.min(1600, w))}px`;
|
|
418
|
+
wrap.style.maxWidth = "100%";
|
|
419
|
+
}
|
|
420
|
+
const align = s.align === "center" ? "center" : s.align === "right" ? "flex-end" : "flex-start";
|
|
421
|
+
wrap.style.alignSelf = align;
|
|
422
|
+
if (isFile) {
|
|
423
|
+
const video = doc.createElement("video");
|
|
424
|
+
video.src = src;
|
|
425
|
+
video.controls = true;
|
|
426
|
+
video.setAttribute("playsinline", "");
|
|
427
|
+
video.preload = "metadata";
|
|
428
|
+
video.setAttribute(
|
|
429
|
+
"aria-label",
|
|
430
|
+
typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video"
|
|
431
|
+
);
|
|
432
|
+
if (opts.editing) video.style.pointerEvents = "none";
|
|
433
|
+
wrap.appendChild(video);
|
|
434
|
+
return wrap;
|
|
435
|
+
}
|
|
436
|
+
const frame = doc.createElement("iframe");
|
|
437
|
+
frame.src = src;
|
|
438
|
+
frame.title = typeof block.alt === "string" && block.alt.trim() ? block.alt.trim() : "Video";
|
|
439
|
+
frame.setAttribute("loading", "lazy");
|
|
440
|
+
frame.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
|
|
441
|
+
frame.setAttribute(
|
|
442
|
+
"allow",
|
|
443
|
+
`accelerometer; autoplay; clipboard-write; encrypted-media; picture-in-picture${opts.editing ? "" : "; fullscreen"}`
|
|
444
|
+
);
|
|
445
|
+
if (!opts.editing) frame.setAttribute("allowfullscreen", "");
|
|
446
|
+
if (opts.editing) frame.style.pointerEvents = "none";
|
|
447
|
+
wrap.appendChild(frame);
|
|
448
|
+
return wrap;
|
|
449
|
+
}
|
|
450
|
+
|
|
332
451
|
// src/plugins/guides/block-builder.ts
|
|
333
452
|
function buildStepContent(step, doc, callbacks) {
|
|
334
453
|
const container = doc.createElement("div");
|
|
@@ -453,8 +572,12 @@ function renderBlocks(container, blocks, doc, callbacks) {
|
|
|
453
572
|
i++;
|
|
454
573
|
}
|
|
455
574
|
}
|
|
456
|
-
function buildContentBlock(block, doc) {
|
|
575
|
+
function buildContentBlock(block, doc, opts = {}) {
|
|
457
576
|
switch (block.type) {
|
|
577
|
+
case "video":
|
|
578
|
+
return buildVideoBlock(block, doc, opts);
|
|
579
|
+
case "divider":
|
|
580
|
+
return buildDividerBlock(block, doc);
|
|
458
581
|
case "title": {
|
|
459
582
|
const el = doc.createElement("h2");
|
|
460
583
|
el.className = "veo-guide-title";
|
|
@@ -478,7 +601,7 @@ function buildContentBlock(block, doc) {
|
|
|
478
601
|
const el = doc.createElement("img");
|
|
479
602
|
el.className = "veo-guide-image";
|
|
480
603
|
el.src = block.url;
|
|
481
|
-
el.alt = "";
|
|
604
|
+
el.alt = typeof block.alt === "string" ? block.alt : "";
|
|
482
605
|
applyBlockStyle(el, block.style, "image");
|
|
483
606
|
return el;
|
|
484
607
|
}
|
|
@@ -486,6 +609,32 @@ function buildContentBlock(block, doc) {
|
|
|
486
609
|
return null;
|
|
487
610
|
}
|
|
488
611
|
}
|
|
612
|
+
var DIVIDER_LINE_STYLES = /* @__PURE__ */ new Set(["solid", "dotted", "dashed"]);
|
|
613
|
+
function buildDividerBlock(block, doc) {
|
|
614
|
+
const s = block.style ?? {};
|
|
615
|
+
const wrap = doc.createElement("div");
|
|
616
|
+
wrap.className = "veo-guide-divider";
|
|
617
|
+
wrap.setAttribute("role", "separator");
|
|
618
|
+
const hr = doc.createElement("hr");
|
|
619
|
+
const custom = s.preset === "custom";
|
|
620
|
+
const color = custom && typeof s.color === "string" && s.color ? s.color : s.colorMode === "dark" ? "rgba(255, 255, 255, 0.35)" : "#e5e7eb";
|
|
621
|
+
const thickness = custom && typeof s.thickness === "number" && Number.isFinite(s.thickness) ? clampNum(Math.round(s.thickness), 1, 12) : 1;
|
|
622
|
+
const lineStyle = typeof s.lineStyle === "string" && DIVIDER_LINE_STYLES.has(s.lineStyle) ? s.lineStyle : "solid";
|
|
623
|
+
hr.style.borderTop = `${thickness}px ${lineStyle} ${color}`;
|
|
624
|
+
wrap.appendChild(hr);
|
|
625
|
+
for (const [k, prop] of [
|
|
626
|
+
["marginTop", "marginTop"],
|
|
627
|
+
["marginRight", "marginRight"],
|
|
628
|
+
["marginBottom", "marginBottom"],
|
|
629
|
+
["marginLeft", "marginLeft"]
|
|
630
|
+
]) {
|
|
631
|
+
const v = s[k];
|
|
632
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
633
|
+
wrap.style[prop] = `${clampNum(v, 0, 64)}px`;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return wrap;
|
|
637
|
+
}
|
|
489
638
|
function buildButtonBlock(block, doc, callbacks) {
|
|
490
639
|
const btn = doc.createElement("button");
|
|
491
640
|
btn.type = "button";
|
|
@@ -512,6 +661,12 @@ var BLOCK_ALIGN_SELF = {
|
|
|
512
661
|
function clampNum(n, min, max) {
|
|
513
662
|
return Math.min(max, Math.max(min, n));
|
|
514
663
|
}
|
|
664
|
+
var TEXT_PRESETS = {
|
|
665
|
+
title: { size: 18, weight: 600, lineHeight: 1.3 },
|
|
666
|
+
subtitle: { size: 16, weight: 600, lineHeight: 1.35 },
|
|
667
|
+
paragraph: { size: 14, weight: 400, lineHeight: 1.5 },
|
|
668
|
+
small: { size: 12, weight: 400, lineHeight: 1.5 }
|
|
669
|
+
};
|
|
515
670
|
function applyBlockStyle(el, style, kind) {
|
|
516
671
|
if (!style || typeof style !== "object") return;
|
|
517
672
|
const s = style;
|
|
@@ -521,14 +676,34 @@ function applyBlockStyle(el, style, kind) {
|
|
|
521
676
|
}
|
|
522
677
|
const align = typeof s.align === "string" ? s.align : null;
|
|
523
678
|
if (align && (align === "left" || align === "center" || align === "right")) {
|
|
524
|
-
if (kind === "text")
|
|
525
|
-
|
|
679
|
+
if (kind === "text") {
|
|
680
|
+
el.style.textAlign = align;
|
|
681
|
+
el.style.alignSelf = BLOCK_ALIGN_SELF[align];
|
|
682
|
+
} else if (kind === "image") el.style.alignSelf = BLOCK_ALIGN_SELF[align];
|
|
683
|
+
}
|
|
684
|
+
if (kind === "text" && typeof s.preset === "string" && s.preset in TEXT_PRESETS) {
|
|
685
|
+
const preset = TEXT_PRESETS[s.preset];
|
|
686
|
+
if (preset) {
|
|
687
|
+
el.style.fontSize = `${preset.size}px`;
|
|
688
|
+
el.style.fontWeight = String(preset.weight);
|
|
689
|
+
el.style.lineHeight = String(preset.lineHeight);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (kind !== "image" && s.colorMode === "dark") {
|
|
693
|
+
el.style.background = "#1f1d1b";
|
|
694
|
+
el.style.color = "#f5f0eb";
|
|
695
|
+
el.style.borderRadius = el.style.borderRadius || "8px";
|
|
526
696
|
}
|
|
527
697
|
if (kind !== "image" && typeof s.color === "string") el.style.color = s.color;
|
|
528
698
|
if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
|
|
529
699
|
el.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
|
|
530
700
|
}
|
|
531
|
-
if (kind === "image" &&
|
|
701
|
+
if (kind === "image" && s.naturalSize === true) {
|
|
702
|
+
el.style.width = "auto";
|
|
703
|
+
el.style.maxWidth = "100%";
|
|
704
|
+
el.style.maxHeight = "none";
|
|
705
|
+
el.style.objectFit = "contain";
|
|
706
|
+
} else if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
|
|
532
707
|
el.style.width = `${clampNum(s.width, 10, 100)}%`;
|
|
533
708
|
}
|
|
534
709
|
if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
|
|
@@ -949,7 +1124,161 @@ var GUIDE_STYLES = `
|
|
|
949
1124
|
}
|
|
950
1125
|
/* Card gen\xE9rica del modelo unificado. El dise\xF1o (bg/borde/padding/\u2026) lo aplica
|
|
951
1126
|
el renderer como estilos inline desde step.design; ac\xE1 van los defaults. */
|
|
1127
|
+
/* Edici\xF3n (builder): sin animaci\xF3n de entrada al repintar la preview. */
|
|
1128
|
+
:host(.veo-no-anim) * { animation: none !important; }
|
|
1129
|
+
|
|
1130
|
+
/* Bloque video: caja con relaci\xF3n de aspecto; el iframe la llena. */
|
|
1131
|
+
.veo-guide-video { position: relative; overflow: hidden; border-radius: 8px; background: #000; }
|
|
1132
|
+
.veo-guide-video iframe, .veo-guide-video video {
|
|
1133
|
+
position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/* \u2500\u2500 Layout por filas/columnas (composite) \u2500\u2500
|
|
1137
|
+
El espacio tiene UNA fuente por capa: el aire con el borde lo da el padding
|
|
1138
|
+
de la card; la separaci\xF3n entre filas/bloques la da el margen inferior
|
|
1139
|
+
(12px, ah\xED vive la l\xEDnea \xAB+\xBB del editor); los bloques NO llevan padding
|
|
1140
|
+
propio (solo el que el autor les ponga inline). El \xFAltimo elemento no deja
|
|
1141
|
+
margen colgando. */
|
|
1142
|
+
.veo-card > [data-veo-block], .veo-card > .veo-row, .veo-card > .veo-guide-actions,
|
|
1143
|
+
.veo-col > [data-veo-block], .veo-col > .veo-guide-actions { margin-bottom: 12px; }
|
|
1144
|
+
/* La card y cada columna son columnas flex: cada bloque mide SU contenido (como
|
|
1145
|
+
el bot\xF3n) y se ubica con su alineaci\xF3n (align-self); filas, columnas, formularios
|
|
1146
|
+
y filas de botones ocupan todo el ancho. */
|
|
1147
|
+
.veo-card:not(.veo-card--empty), .veo-col {
|
|
1148
|
+
display: flex; flex-direction: column; align-items: flex-start;
|
|
1149
|
+
}
|
|
1150
|
+
.veo-card > [data-veo-block], .veo-col > [data-veo-block] { max-width: 100%; }
|
|
1151
|
+
/* L\xEDnea horizontal: ocupa todo el ancho; el padding del bloque es solo vertical
|
|
1152
|
+
(zona clickeable en el editor) y la l\xEDnea es el borde superior del hr. */
|
|
1153
|
+
.veo-card > .veo-guide-divider, .veo-col > .veo-guide-divider { padding: 6px 0; align-self: stretch; }
|
|
1154
|
+
.veo-guide-divider { box-sizing: border-box; }
|
|
1155
|
+
/* Bloque de c\xF3digo (modo host): ocupa todo el ancho, como un formulario. */
|
|
1156
|
+
.veo-code-host { display: block; min-width: 0; }
|
|
1157
|
+
.veo-card > .veo-code-host, .veo-col > .veo-code-host { align-self: stretch; }
|
|
1158
|
+
.veo-guide-divider hr { margin: 0; border: 0; height: 0; }
|
|
1159
|
+
.veo-card > .veo-row, .veo-card > .veo-guide-actions, .veo-card > .veo-form,
|
|
1160
|
+
.veo-col > .veo-guide-actions, .veo-col > .veo-form { align-self: stretch; }
|
|
1161
|
+
.veo-row { display: flex; gap: 12px; align-items: flex-start; box-sizing: border-box; }
|
|
1162
|
+
.veo-col { flex: 1 1 0; min-width: 0; }
|
|
1163
|
+
.veo-col > :last-child { margin-bottom: 0; }
|
|
1164
|
+
/* Fila con alineaci\xF3n horizontal (editor de fila): las columnas miden su contenido. */
|
|
1165
|
+
.veo-row--aligned > .veo-col { flex: 0 1 auto; }
|
|
1166
|
+
|
|
1167
|
+
/* \u2500\u2500 Editor in-app: bloque seleccionado + barra de acciones + placeholders \u2500\u2500 */
|
|
1168
|
+
.veo-block-placeholder {
|
|
1169
|
+
border: 1px dashed #b0a9a5; border-radius: 6px; padding: 12px; margin-bottom: 12px;
|
|
1170
|
+
color: #8a8582; font-size: 13px; text-align: center;
|
|
1171
|
+
}
|
|
1172
|
+
/* El bloque seleccionado no lleva contorno propio: lo rodea .veo-block-frame
|
|
1173
|
+
(marco aparte, con aire y alto m\xEDnimo, que no altera el layout). */
|
|
1174
|
+
.veo-block--selected { position: relative; z-index: 1; }
|
|
1175
|
+
.veo-block-frame {
|
|
1176
|
+
position: absolute; z-index: 4; pointer-events: none; box-sizing: border-box;
|
|
1177
|
+
border: 2px solid #3b82f6; border-radius: 10px; background: rgba(59, 130, 246, 0.04);
|
|
1178
|
+
}
|
|
1179
|
+
/* Barra del bloque (mock): p\xEDldora blanca con sombra, solo \xEDconos; la papelera
|
|
1180
|
+
en rojo, separada por una l\xEDnea. */
|
|
1181
|
+
.veo-block-toolbar {
|
|
1182
|
+
position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
|
|
1183
|
+
background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
|
|
1184
|
+
box-shadow: 0 6px 20px rgba(0,0,0,0.16);
|
|
1185
|
+
}
|
|
1186
|
+
.veo-block-toolbar button {
|
|
1187
|
+
border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px; border-radius: 8px;
|
|
1188
|
+
display: inline-flex; align-items: center; justify-content: center; line-height: 1;
|
|
1189
|
+
}
|
|
1190
|
+
.veo-block-toolbar button:hover { background: #f5efeb; }
|
|
1191
|
+
.veo-block-toolbar button svg { width: 20px; height: 20px; }
|
|
1192
|
+
.veo-block-toolbar .veo-block-toolbar-remove { color: #e5484d; }
|
|
1193
|
+
/* Separadores: antes de la papelera y antes de las flechas de mover. */
|
|
1194
|
+
.veo-block-toolbar .veo-block-toolbar-remove, .veo-block-toolbar .veo-block-toolbar-move-left {
|
|
1195
|
+
margin-left: 6px; position: relative;
|
|
1196
|
+
}
|
|
1197
|
+
.veo-block-toolbar .veo-block-toolbar-remove::before, .veo-block-toolbar .veo-block-toolbar-move-left::before {
|
|
1198
|
+
content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
|
|
1199
|
+
}
|
|
1200
|
+
.veo-block-toolbar button:disabled { opacity: 0.3; cursor: default; background: transparent; }
|
|
1201
|
+
/* Contenedor (hover): contorno azul de la tarjeta y dos pesta\xF1as centradas
|
|
1202
|
+
sobre su borde superior: \xABEditar contenedor\xBB (outline) y \xABEditar filas\xBB. */
|
|
1203
|
+
.veo-card-frame {
|
|
1204
|
+
position: absolute; z-index: 3; inset: 0; pointer-events: none; box-sizing: border-box;
|
|
1205
|
+
border: 2px solid #3b82f6; border-radius: inherit;
|
|
1206
|
+
}
|
|
1207
|
+
.veo-card-toolbar {
|
|
1208
|
+
position: absolute; z-index: 6; left: 50%; transform: translateX(-50%);
|
|
1209
|
+
display: inline-flex; align-items: flex-end; gap: 8px; white-space: nowrap;
|
|
1210
|
+
}
|
|
1211
|
+
.veo-card-toolbar button {
|
|
1212
|
+
cursor: pointer; padding: 8px 14px; border-radius: 8px; border: 2px solid #3b82f6;
|
|
1213
|
+
font: 600 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
1214
|
+
background: #fff; color: #3b82f6;
|
|
1215
|
+
}
|
|
1216
|
+
.veo-card-toolbar button:hover { background: #eff6ff; }
|
|
1217
|
+
/* Con fondo = la vista que el panel muestra ahora (contenedor o estructura). */
|
|
1218
|
+
.veo-card-toolbar button.is-active { background: #3b82f6; color: #fff; }
|
|
1219
|
+
.veo-card-toolbar button.is-active:hover { background: #2563eb; border-color: #2563eb; }
|
|
1220
|
+
/* Editor de fila: marco al pasar el mouse por una fila (o la fila del bloque
|
|
1221
|
+
seleccionado) y barra \xABEditar fila \xB7 duplicar\xBB bajo su esquina derecha. */
|
|
1222
|
+
.veo-row-frame {
|
|
1223
|
+
position: absolute; z-index: 3; pointer-events: none; box-sizing: border-box;
|
|
1224
|
+
border: 2px solid #3b82f6; border-radius: 10px;
|
|
1225
|
+
}
|
|
1226
|
+
.veo-row-toolbar {
|
|
1227
|
+
position: absolute; z-index: 5; display: inline-flex; align-items: center; gap: 2px; padding: 4px 6px;
|
|
1228
|
+
background: #fff; border: 1px solid rgba(0,0,0,0.06); border-radius: 12px;
|
|
1229
|
+
box-shadow: 0 6px 20px rgba(0,0,0,0.16); white-space: nowrap;
|
|
1230
|
+
}
|
|
1231
|
+
.veo-row-toolbar button {
|
|
1232
|
+
border: 0; background: transparent; color: #4b4642; cursor: pointer; padding: 6px 8px; border-radius: 8px;
|
|
1233
|
+
display: inline-flex; align-items: center; gap: 8px; line-height: 1;
|
|
1234
|
+
font: 500 14px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
1235
|
+
}
|
|
1236
|
+
.veo-row-toolbar button:hover { background: #f5efeb; }
|
|
1237
|
+
.veo-row-toolbar button svg { width: 20px; height: 20px; }
|
|
1238
|
+
.veo-row-toolbar .veo-row-toolbar-duplicate { margin-left: 6px; position: relative; }
|
|
1239
|
+
.veo-row-toolbar .veo-row-toolbar-duplicate::before {
|
|
1240
|
+
content: ""; position: absolute; left: -4px; top: 6px; bottom: 6px; width: 1px; background: #e7e2de;
|
|
1241
|
+
}
|
|
1242
|
+
.veo-row-toolbar .veo-row-toolbar-remove { color: #e5484d; }
|
|
1243
|
+
/* Huecos \xAB+\xBB (mock): zona invisible entre elementos; al pasar el mouse aparece
|
|
1244
|
+
la l\xEDnea azul (horizontal = fila / bloque apilado, vertical = columna) con
|
|
1245
|
+
un \xAB+\xBB circular al centro. */
|
|
1246
|
+
.veo-gap { position: absolute; z-index: 4; }
|
|
1247
|
+
.veo-gap i { position: absolute; background: #3b82f6; opacity: 0; transition: opacity 100ms; pointer-events: none; }
|
|
1248
|
+
.veo-gap--h i { left: 0; right: 0; top: 50%; height: 2px; margin-top: -1px; }
|
|
1249
|
+
.veo-gap--v i { top: 0; bottom: 0; left: 50%; width: 2px; margin-left: -1px; }
|
|
1250
|
+
.veo-gap button {
|
|
1251
|
+
position: absolute; left: 50%; top: 50%; margin: -11px 0 0 -11px; box-sizing: border-box;
|
|
1252
|
+
opacity: 0; transition: opacity 100ms; width: 22px; height: 22px; border-radius: 50%; padding: 0;
|
|
1253
|
+
border: 2px solid #fff; background: #3b82f6; color: #fff; cursor: pointer;
|
|
1254
|
+
font: 600 15px/18px system-ui, -apple-system, "Segoe UI", sans-serif; text-align: center;
|
|
1255
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.25);
|
|
1256
|
+
}
|
|
1257
|
+
.veo-gap:hover i, .veo-gap:hover button, .veo-gap:focus-within button { opacity: 1; }
|
|
1258
|
+
.veo-gap button:hover { background: #2563eb; }
|
|
1259
|
+
|
|
1260
|
+
/* Tarjeta sin bloques (paso reci\xE9n creado). La tarjeta NO impone tama\xF1o ni
|
|
1261
|
+
padding propios (as\xED el ancho/padding del dise\xF1o se ven tal cual); el tama\xF1o
|
|
1262
|
+
m\xEDnimo lo pone la zona punteada azul de la preview. */
|
|
1263
|
+
.veo-card--empty { display: flex; align-items: stretch; }
|
|
1264
|
+
.veo-card-empty-zone {
|
|
1265
|
+
flex: 1; min-width: 0; min-height: 160px; margin-top: 14px;
|
|
1266
|
+
display: flex; align-items: center; justify-content: center; gap: 6px; text-align: center;
|
|
1267
|
+
border: 2px dashed #3b82f6; border-radius: 8px; background: #eff6ff;
|
|
1268
|
+
color: #2563eb; font-size: 16px; font-weight: 500; user-select: none; pointer-events: none;
|
|
1269
|
+
padding: 0 12px; box-sizing: border-box; overflow: hidden;
|
|
1270
|
+
}
|
|
1271
|
+
.veo-card-empty-zone b { font-weight: 600; font-size: 22px; line-height: 1; }
|
|
1272
|
+
/* Sin ancho configurado, la zona da un tama\xF1o c\xF3modo por defecto. */
|
|
1273
|
+
.veo-card--empty:not([style*="width"]) .veo-card-empty-zone { min-width: 360px; }
|
|
1274
|
+
/* Tarjeta angosta (la mide el renderer): solo el \xAB+\xBB, sin texto, y m\xE1s baja. */
|
|
1275
|
+
.veo-card--narrow .veo-card-empty-zone span { display: none; }
|
|
1276
|
+
.veo-card--narrow .veo-card-empty-zone { min-height: 120px; }
|
|
952
1277
|
.veo-card {
|
|
1278
|
+
/* Contenedor posicionado SIEMPRE: el \u2715 (absolute) se ubica respecto a la
|
|
1279
|
+
tarjeta tambi\xE9n en pasos embebidos (inline), no respecto a la app. Los
|
|
1280
|
+
flotantes lo pisan con position:fixed inline. */
|
|
1281
|
+
position: relative;
|
|
953
1282
|
pointer-events: auto;
|
|
954
1283
|
background: var(--veo-bg, #ffffff);
|
|
955
1284
|
color: var(--veo-text, #1f2937);
|
|
@@ -1042,6 +1371,13 @@ var GUIDE_STYLES = `
|
|
|
1042
1371
|
transition: opacity 120ms ease;
|
|
1043
1372
|
}
|
|
1044
1373
|
.veo-guide-cta:hover { opacity: 0.9; }
|
|
1374
|
+
/* Presets del boton (editor): secundario = contorno; enlace = solo texto. */
|
|
1375
|
+
.veo-guide-cta--secondary {
|
|
1376
|
+
background: transparent; color: var(--veo-primary);
|
|
1377
|
+
border: 1px solid var(--veo-primary);
|
|
1378
|
+
}
|
|
1379
|
+
.veo-guide-cta--link { background: transparent; color: var(--veo-primary); padding: 0; }
|
|
1380
|
+
.veo-guide-cta--link:hover { opacity: 1; text-decoration: underline; }
|
|
1045
1381
|
.veo-guide-cta:active { transform: translateY(1px); }
|
|
1046
1382
|
.veo-guide-close {
|
|
1047
1383
|
position: absolute; top: -6px; right: -6px;
|
|
@@ -1072,6 +1408,10 @@ var GUIDE_STYLES = `
|
|
|
1072
1408
|
|
|
1073
1409
|
.veo-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
|
|
1074
1410
|
.veo-form-field { display: flex; flex-direction: column; gap: 6px; }
|
|
1411
|
+
/* Pregunta de la encuesta (rich text del bloque form). */
|
|
1412
|
+
.veo-form-question { font-size: 16px; font-weight: 600; line-height: 1.35; color: var(--veo-text); }
|
|
1413
|
+
.veo-form-question p { margin: 0; }
|
|
1414
|
+
.veo-form > .veo-guide-actions { margin: 0; }
|
|
1075
1415
|
.veo-form-label { font-size: 13px; font-weight: 500; color: var(--veo-text); }
|
|
1076
1416
|
.veo-form-input {
|
|
1077
1417
|
font-size: 14px; color: var(--veo-text);
|
|
@@ -1080,26 +1420,71 @@ var GUIDE_STYLES = `
|
|
|
1080
1420
|
padding: 8px 10px; width: 100%;
|
|
1081
1421
|
}
|
|
1082
1422
|
.veo-form-input:focus { outline: 2px solid var(--veo-primary); outline-offset: -1px; border-color: transparent; }
|
|
1083
|
-
|
|
1423
|
+
/* Lista de tareas (mock): \xEDtems con anillo de progreso, texto + subt\xEDtulo,
|
|
1424
|
+
y al pie \xABX de N completadas\xBB + porcentaje + barra. */
|
|
1425
|
+
.veo-checklist {
|
|
1426
|
+
display: flex; flex-direction: column; gap: 22px; align-self: stretch; width: 100%;
|
|
1427
|
+
min-width: 360px; box-sizing: border-box;
|
|
1428
|
+
}
|
|
1429
|
+
.veo-checklist-title { font-size: 24px; font-weight: 700; line-height: 1.25; color: var(--veo-cl-title); }
|
|
1430
|
+
.veo-checklist-title p { margin: 0; }
|
|
1431
|
+
.veo-checklist-items { display: flex; flex-direction: column; gap: 22px; }
|
|
1432
|
+
.veo-checklist-item {
|
|
1433
|
+
display: flex; align-items: center; gap: 18px; width: 100%; text-align: left;
|
|
1434
|
+
background: none; border: 0; padding: 0; cursor: pointer; font: inherit; color: inherit;
|
|
1435
|
+
}
|
|
1436
|
+
.veo-checklist-item:hover .veo-checklist-label { text-decoration: underline; }
|
|
1437
|
+
.veo-checklist-ring { width: 44px; height: 44px; flex: 0 0 auto; }
|
|
1438
|
+
.veo-checklist-texts { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
1439
|
+
.veo-checklist-label {
|
|
1440
|
+
font-size: calc(var(--veo-cl-font-size, 14px) + 4px); font-weight: var(--veo-cl-font-weight, 600);
|
|
1441
|
+
line-height: 1.3; color: var(--veo-cl-title);
|
|
1442
|
+
}
|
|
1443
|
+
.veo-checklist-sub { font-size: calc(var(--veo-cl-font-size, 14px) + 1px); color: var(--veo-cl-subtitle); }
|
|
1444
|
+
.veo-checklist-footer { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; }
|
|
1445
|
+
.veo-checklist-summary {
|
|
1446
|
+
display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
|
|
1447
|
+
font-size: calc(var(--veo-cl-font-size, 14px) + 1px);
|
|
1448
|
+
}
|
|
1449
|
+
.veo-checklist-count { color: var(--veo-cl-subtitle); }
|
|
1450
|
+
.veo-checklist-percent { font-weight: 700; font-size: calc(var(--veo-cl-font-size, 14px) + 3px); color: var(--veo-cl-bar); }
|
|
1451
|
+
.veo-checklist-bar { height: var(--veo-cl-bar-h, 8px); background: #e7e2de; border-radius: 999px; overflow: hidden; }
|
|
1452
|
+
.veo-checklist--dark .veo-checklist-bar { background: #3c3734; }
|
|
1453
|
+
.veo-checklist-bar-fill { height: 100%; background: var(--veo-cl-bar); border-radius: 999px; transition: width 300ms ease; }
|
|
1454
|
+
.veo-checklist-empty { margin: 0; font-size: 13px; color: var(--veo-cl-subtitle); }
|
|
1455
|
+
/* Opci\xF3n m\xFAltiple (mock): c\xEDrculos/casillas grandes con su texto al lado. */
|
|
1456
|
+
.veo-form-options { display: flex; flex-direction: column; gap: 14px; }
|
|
1084
1457
|
.veo-form-option {
|
|
1085
|
-
display: flex; align-items: center; gap:
|
|
1086
|
-
font-size:
|
|
1087
|
-
}
|
|
1088
|
-
.veo-form-option input {
|
|
1089
|
-
|
|
1458
|
+
display: flex; align-items: center; gap: 14px;
|
|
1459
|
+
font-size: 16px; color: var(--veo-text); cursor: pointer;
|
|
1460
|
+
}
|
|
1461
|
+
.veo-form-option input {
|
|
1462
|
+
accent-color: var(--veo-primary); margin: 0; cursor: pointer;
|
|
1463
|
+
width: 26px; height: 26px; flex: 0 0 auto;
|
|
1464
|
+
}
|
|
1465
|
+
/* Escala num\xE9rica (mock): casillas cuadradas; con etiquetas, cada casilla
|
|
1466
|
+
lleva la suya debajo (la primera cuelga a la izquierda, la \xFAltima a la derecha). */
|
|
1467
|
+
.veo-form-scale { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-start; }
|
|
1468
|
+
.veo-scale-item { display: inline-flex; flex-direction: column; align-items: center; gap: 8px; }
|
|
1469
|
+
.veo-scale-item--first { align-items: flex-start; }
|
|
1470
|
+
.veo-scale-item--last { align-items: flex-end; }
|
|
1471
|
+
.veo-scale-label { font-size: 12px; line-height: 1.2; color: #8a8582; white-space: nowrap; }
|
|
1090
1472
|
.veo-nps-btn {
|
|
1091
|
-
min-width:
|
|
1092
|
-
font-size:
|
|
1093
|
-
background: var(--veo-bg); border: 1px solid #
|
|
1473
|
+
min-width: var(--veo-scale-size, 44px); height: var(--veo-scale-size, 44px); padding: 0 6px;
|
|
1474
|
+
font-size: 16px; font-weight: 600; color: var(--veo-text);
|
|
1475
|
+
background: var(--veo-bg); border: 1px solid #d5cec9; border-radius: 8px;
|
|
1094
1476
|
cursor: pointer; transition: all 100ms ease;
|
|
1095
1477
|
}
|
|
1096
|
-
.veo-nps-btn:hover { border-color: var(--veo-primary); }
|
|
1478
|
+
.veo-nps-btn:hover { border-color: var(--veo-scale-color, var(--veo-primary)); }
|
|
1097
1479
|
.veo-rating-btn {
|
|
1098
1480
|
background: none; border: none; padding: 0 2px;
|
|
1099
1481
|
font-size: 26px; line-height: 1; color: #d1d5db;
|
|
1100
1482
|
cursor: pointer; transition: color 100ms ease;
|
|
1101
1483
|
}
|
|
1102
|
-
.veo-scale-active.veo-nps-btn {
|
|
1484
|
+
.veo-scale-active.veo-nps-btn {
|
|
1485
|
+
background: var(--veo-scale-color, var(--veo-primary)); color: #fff;
|
|
1486
|
+
border-color: var(--veo-scale-color, var(--veo-primary));
|
|
1487
|
+
}
|
|
1103
1488
|
.veo-scale-active.veo-rating-btn { color: #f59e0b; }
|
|
1104
1489
|
.veo-form-error { font-size: 13px; color: #dc2626; margin: 0; }
|
|
1105
1490
|
.veo-form-thanks {
|
|
@@ -1768,6 +2153,210 @@ function bannerClassOf(position, embedded) {
|
|
|
1768
2153
|
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
1769
2154
|
}
|
|
1770
2155
|
|
|
2156
|
+
// src/plugins/guides/checklist-block.ts
|
|
2157
|
+
var CHECKLIST_DEFAULTS = {
|
|
2158
|
+
iconEmptyColor: "#e7e2de",
|
|
2159
|
+
iconFillColor: "#406a31",
|
|
2160
|
+
barColor: "#406a31",
|
|
2161
|
+
barHeight: 8,
|
|
2162
|
+
fontSize: 14,
|
|
2163
|
+
fontWeight: 600,
|
|
2164
|
+
titleColor: "#2c2825",
|
|
2165
|
+
subtitleColor: "#8a8582",
|
|
2166
|
+
subtitleTemplate: "Completado <%= currentStep %> de <%= totalSteps %> pasos"
|
|
2167
|
+
};
|
|
2168
|
+
var WEIGHTS = { regular: 400, medium: 500, semibold: 600, bold: 700 };
|
|
2169
|
+
function checklistStyle(style) {
|
|
2170
|
+
const s = style ?? {};
|
|
2171
|
+
const str = (k, fb) => typeof s[k] === "string" && s[k].trim() ? s[k] : fb;
|
|
2172
|
+
const num2 = (k, fb, min, max) => typeof s[k] === "number" && Number.isFinite(s[k]) ? Math.min(max, Math.max(min, s[k])) : fb;
|
|
2173
|
+
const weightRaw = s.fontWeight;
|
|
2174
|
+
const fontWeight = typeof weightRaw === "number" ? Math.min(900, Math.max(100, weightRaw)) : typeof weightRaw === "string" && WEIGHTS[weightRaw.toLowerCase()] ? WEIGHTS[weightRaw.toLowerCase()] : CHECKLIST_DEFAULTS.fontWeight;
|
|
2175
|
+
const dark = s.colorMode === "dark";
|
|
2176
|
+
return {
|
|
2177
|
+
colorMode: dark ? "dark" : "light",
|
|
2178
|
+
iconEmptyColor: str("iconEmptyColor", dark ? "#3c3734" : CHECKLIST_DEFAULTS.iconEmptyColor),
|
|
2179
|
+
iconFillColor: str("iconFillColor", CHECKLIST_DEFAULTS.iconFillColor),
|
|
2180
|
+
barColor: str("barColor", CHECKLIST_DEFAULTS.barColor),
|
|
2181
|
+
barHeight: num2("barHeight", CHECKLIST_DEFAULTS.barHeight, 2, 40),
|
|
2182
|
+
fontSize: num2("fontSize", CHECKLIST_DEFAULTS.fontSize, 10, 40),
|
|
2183
|
+
fontWeight,
|
|
2184
|
+
titleColor: str("titleColor", dark ? "#f5efeb" : CHECKLIST_DEFAULTS.titleColor),
|
|
2185
|
+
subtitleColor: str("subtitleColor", dark ? "#b0a9a5" : CHECKLIST_DEFAULTS.subtitleColor),
|
|
2186
|
+
subtitleTemplate: str("subtitleTemplate", CHECKLIST_DEFAULTS.subtitleTemplate)
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
function renderSubtitle(template, p) {
|
|
2190
|
+
return template.replace(/<%=\s*currentStep\s*%>/g, String(p.currentStep)).replace(/<%=\s*totalSteps\s*%>/g, String(p.totalSteps));
|
|
2191
|
+
}
|
|
2192
|
+
var RING_R = 10;
|
|
2193
|
+
var RING_C = 2 * Math.PI * RING_R;
|
|
2194
|
+
function buildRing(doc, fraction, completed, st) {
|
|
2195
|
+
const ns = "http://www.w3.org/2000/svg";
|
|
2196
|
+
const svg = doc.createElementNS(ns, "svg");
|
|
2197
|
+
svg.setAttribute("viewBox", "0 0 28 28");
|
|
2198
|
+
svg.setAttribute("class", "veo-checklist-ring");
|
|
2199
|
+
svg.setAttribute("aria-hidden", "true");
|
|
2200
|
+
const track = doc.createElementNS(ns, "circle");
|
|
2201
|
+
track.setAttribute("cx", "14");
|
|
2202
|
+
track.setAttribute("cy", "14");
|
|
2203
|
+
track.setAttribute("r", String(RING_R));
|
|
2204
|
+
track.setAttribute("fill", completed ? st.iconFillColor : "none");
|
|
2205
|
+
track.setAttribute("stroke", st.iconEmptyColor);
|
|
2206
|
+
track.setAttribute("stroke-width", "4.5");
|
|
2207
|
+
svg.appendChild(track);
|
|
2208
|
+
if (completed) {
|
|
2209
|
+
const check = doc.createElementNS(ns, "path");
|
|
2210
|
+
check.setAttribute("d", "M9 14.5l3.2 3.2L19 11");
|
|
2211
|
+
check.setAttribute("fill", "none");
|
|
2212
|
+
check.setAttribute("stroke", "#fff");
|
|
2213
|
+
check.setAttribute("stroke-width", "2.4");
|
|
2214
|
+
check.setAttribute("stroke-linecap", "round");
|
|
2215
|
+
check.setAttribute("stroke-linejoin", "round");
|
|
2216
|
+
svg.appendChild(check);
|
|
2217
|
+
return svg;
|
|
2218
|
+
}
|
|
2219
|
+
const f = Math.max(0, Math.min(1, fraction));
|
|
2220
|
+
if (f > 0) {
|
|
2221
|
+
const arc = doc.createElementNS(ns, "circle");
|
|
2222
|
+
arc.setAttribute("cx", "14");
|
|
2223
|
+
arc.setAttribute("cy", "14");
|
|
2224
|
+
arc.setAttribute("r", String(RING_R));
|
|
2225
|
+
arc.setAttribute("fill", "none");
|
|
2226
|
+
arc.setAttribute("stroke", st.iconFillColor);
|
|
2227
|
+
arc.setAttribute("stroke-width", "4.5");
|
|
2228
|
+
arc.setAttribute("stroke-linecap", "butt");
|
|
2229
|
+
arc.setAttribute("stroke-dasharray", `${(RING_C * f).toFixed(2)} ${RING_C.toFixed(2)}`);
|
|
2230
|
+
arc.setAttribute("transform", "rotate(-90 14 14)");
|
|
2231
|
+
svg.appendChild(arc);
|
|
2232
|
+
}
|
|
2233
|
+
return svg;
|
|
2234
|
+
}
|
|
2235
|
+
function buildChecklistBlock(block, doc, context) {
|
|
2236
|
+
const st = checklistStyle(block.style);
|
|
2237
|
+
const items = (block.items ?? []).filter((i) => i && typeof i.guideId === "string" && i.guideId);
|
|
2238
|
+
const root = doc.createElement("div");
|
|
2239
|
+
root.className = `veo-checklist${st.colorMode === "dark" ? " veo-checklist--dark" : ""}`;
|
|
2240
|
+
root.style.setProperty("--veo-cl-font-size", `${st.fontSize}px`);
|
|
2241
|
+
root.style.setProperty("--veo-cl-font-weight", String(st.fontWeight));
|
|
2242
|
+
root.style.setProperty("--veo-cl-title", st.titleColor);
|
|
2243
|
+
root.style.setProperty("--veo-cl-subtitle", st.subtitleColor);
|
|
2244
|
+
root.style.setProperty("--veo-cl-bar", st.barColor);
|
|
2245
|
+
root.style.setProperty("--veo-cl-bar-h", `${st.barHeight}px`);
|
|
2246
|
+
if (typeof block.html === "string" && block.html || typeof block.text === "string" && block.text) {
|
|
2247
|
+
const title = doc.createElement("div");
|
|
2248
|
+
title.className = "veo-checklist-title";
|
|
2249
|
+
if (typeof block.html === "string" && block.html)
|
|
2250
|
+
title.appendChild(renderRichText(block.html, doc));
|
|
2251
|
+
else title.textContent = block.text;
|
|
2252
|
+
root.appendChild(title);
|
|
2253
|
+
}
|
|
2254
|
+
const list = doc.createElement("div");
|
|
2255
|
+
list.className = "veo-checklist-items";
|
|
2256
|
+
list.setAttribute("role", "list");
|
|
2257
|
+
root.appendChild(list);
|
|
2258
|
+
const footer = doc.createElement("div");
|
|
2259
|
+
footer.className = "veo-checklist-footer";
|
|
2260
|
+
const summary = doc.createElement("div");
|
|
2261
|
+
summary.className = "veo-checklist-summary";
|
|
2262
|
+
const count = doc.createElement("span");
|
|
2263
|
+
count.className = "veo-checklist-count";
|
|
2264
|
+
const percent = doc.createElement("span");
|
|
2265
|
+
percent.className = "veo-checklist-percent";
|
|
2266
|
+
summary.append(count, percent);
|
|
2267
|
+
const bar = doc.createElement("div");
|
|
2268
|
+
bar.className = "veo-checklist-bar";
|
|
2269
|
+
const fill = doc.createElement("div");
|
|
2270
|
+
fill.className = "veo-checklist-bar-fill";
|
|
2271
|
+
bar.appendChild(fill);
|
|
2272
|
+
footer.append(summary, bar);
|
|
2273
|
+
root.appendChild(footer);
|
|
2274
|
+
const paint = (progress) => {
|
|
2275
|
+
list.textContent = "";
|
|
2276
|
+
const visible = progress ? items.filter((i) => progress.has(i.guideId)) : items;
|
|
2277
|
+
let done = 0;
|
|
2278
|
+
for (const item of visible) {
|
|
2279
|
+
const p = progress?.get(item.guideId) ?? null;
|
|
2280
|
+
if (p?.completed) done++;
|
|
2281
|
+
const row = doc.createElement("button");
|
|
2282
|
+
row.type = "button";
|
|
2283
|
+
row.className = `veo-checklist-item${p?.completed ? " veo-checklist-item--done" : ""}`;
|
|
2284
|
+
row.setAttribute("role", "listitem");
|
|
2285
|
+
const fraction = p && p.totalSteps > 0 ? p.currentStep / p.totalSteps : 0;
|
|
2286
|
+
row.appendChild(buildRing(doc, fraction, p?.completed === true, st));
|
|
2287
|
+
const texts = doc.createElement("span");
|
|
2288
|
+
texts.className = "veo-checklist-texts";
|
|
2289
|
+
const label = doc.createElement("span");
|
|
2290
|
+
label.className = "veo-checklist-label";
|
|
2291
|
+
label.textContent = (item.text ?? "").trim() || p?.guideName || "Gu\xEDa";
|
|
2292
|
+
if (p && p.published === false) row.title = "Gu\xEDa no publicada: los usuarios no la ver\xE1n";
|
|
2293
|
+
texts.appendChild(label);
|
|
2294
|
+
if (p) {
|
|
2295
|
+
const sub = doc.createElement("span");
|
|
2296
|
+
sub.className = "veo-checklist-sub";
|
|
2297
|
+
sub.textContent = renderSubtitle(st.subtitleTemplate, p);
|
|
2298
|
+
texts.appendChild(sub);
|
|
2299
|
+
}
|
|
2300
|
+
row.appendChild(texts);
|
|
2301
|
+
row.addEventListener("click", (e) => {
|
|
2302
|
+
e.preventDefault();
|
|
2303
|
+
e.stopPropagation();
|
|
2304
|
+
if (context.isPreview) return;
|
|
2305
|
+
const start = p && !p.completed ? Math.min(p.currentStep, p.totalSteps - 1) : 0;
|
|
2306
|
+
if (context.onOpenGuideAt) context.onOpenGuideAt(item.guideId, start);
|
|
2307
|
+
else context.onOpenGuide?.(item.guideId);
|
|
2308
|
+
});
|
|
2309
|
+
list.appendChild(row);
|
|
2310
|
+
}
|
|
2311
|
+
const total = visible.length;
|
|
2312
|
+
const pct = total > 0 ? Math.round(done / total * 100) : 0;
|
|
2313
|
+
count.textContent = `${done} de ${total} ${total === 1 ? "completada" : "completadas"}`;
|
|
2314
|
+
percent.textContent = `${pct}%`;
|
|
2315
|
+
fill.style.width = `${pct}%`;
|
|
2316
|
+
footer.hidden = total === 0;
|
|
2317
|
+
};
|
|
2318
|
+
if (context.isPreview && items.length === 0) {
|
|
2319
|
+
const sample = /* @__PURE__ */ new Map([
|
|
2320
|
+
[
|
|
2321
|
+
"__sample_1",
|
|
2322
|
+
{
|
|
2323
|
+
guideId: "__sample_1",
|
|
2324
|
+
guideName: "Configura tu primer env\xEDo",
|
|
2325
|
+
totalSteps: 2,
|
|
2326
|
+
currentStep: 1,
|
|
2327
|
+
completed: false
|
|
2328
|
+
}
|
|
2329
|
+
],
|
|
2330
|
+
[
|
|
2331
|
+
"__sample_2",
|
|
2332
|
+
{
|
|
2333
|
+
guideId: "__sample_2",
|
|
2334
|
+
guideName: "Conecta tu tienda",
|
|
2335
|
+
totalSteps: 2,
|
|
2336
|
+
currentStep: 2,
|
|
2337
|
+
completed: true
|
|
2338
|
+
}
|
|
2339
|
+
]
|
|
2340
|
+
]);
|
|
2341
|
+
items.push(
|
|
2342
|
+
{ guideId: "__sample_1", text: "Configura tu primer env\xEDo" },
|
|
2343
|
+
{ guideId: "__sample_2", text: "Conecta tu tienda" }
|
|
2344
|
+
);
|
|
2345
|
+
paint(sample);
|
|
2346
|
+
const hint = doc.createElement("p");
|
|
2347
|
+
hint.className = "veo-checklist-empty";
|
|
2348
|
+
hint.textContent = "Ejemplo: agrega gu\xEDas a la lista desde Opciones.";
|
|
2349
|
+
root.appendChild(hint);
|
|
2350
|
+
return root;
|
|
2351
|
+
}
|
|
2352
|
+
paint(null);
|
|
2353
|
+
if (context.fetchProgress && items.length > 0) {
|
|
2354
|
+
void context.fetchProgress(items.map((i) => i.guideId)).then((rows) => paint(new Map(rows.map((r) => [r.guideId, r])))).catch(() => {
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
return root;
|
|
2358
|
+
}
|
|
2359
|
+
|
|
1771
2360
|
// src/utils/uuid.ts
|
|
1772
2361
|
function uuidv7() {
|
|
1773
2362
|
const timestamp = Date.now();
|
|
@@ -1924,6 +2513,7 @@ function escapeClosing(code, tag) {
|
|
|
1924
2513
|
}
|
|
1925
2514
|
|
|
1926
2515
|
// src/plugins/guides/code-block.ts
|
|
2516
|
+
var RAW_BLOCK_ATTR = "data-veo-raw-block";
|
|
1927
2517
|
function insertRawNodes(anchor, nodes, pos) {
|
|
1928
2518
|
const parent = anchor.parentNode;
|
|
1929
2519
|
if (pos === "prepend") {
|
|
@@ -1970,6 +2560,11 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
|
|
|
1970
2560
|
}
|
|
1971
2561
|
if (rawInline) {
|
|
1972
2562
|
const rawNodes = Array.from(holder.childNodes);
|
|
2563
|
+
if (rawInline.tagBlockId) {
|
|
2564
|
+
for (const n of rawNodes) {
|
|
2565
|
+
if (n instanceof Element) n.setAttribute(RAW_BLOCK_ATTR, rawInline.tagBlockId);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
1973
2568
|
insertRawNodes(rawInline.anchor, rawNodes, rawInline.pos);
|
|
1974
2569
|
injected.push(...rawNodes);
|
|
1975
2570
|
} else if (host) {
|
|
@@ -1979,24 +2574,40 @@ function mountCodeBlock(block, doc, context, host, rawInline) {
|
|
|
1979
2574
|
injected.push(holder);
|
|
1980
2575
|
const slot = doc.createElement("slot");
|
|
1981
2576
|
slot.setAttribute("name", slotName);
|
|
1982
|
-
|
|
2577
|
+
const wrapper = doc.createElement("div");
|
|
2578
|
+
wrapper.className = "veo-code-host";
|
|
2579
|
+
wrapper.appendChild(slot);
|
|
2580
|
+
node = wrapper;
|
|
1983
2581
|
} else {
|
|
1984
2582
|
node = holder;
|
|
1985
2583
|
}
|
|
1986
2584
|
}
|
|
2585
|
+
let cancelled = false;
|
|
1987
2586
|
if (typeof block.js === "string" && block.js.trim()) {
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2587
|
+
const js = block.js;
|
|
2588
|
+
const run = () => {
|
|
2589
|
+
if (cancelled) return;
|
|
2590
|
+
const s = hostDoc.createElement("script");
|
|
2591
|
+
s.textContent = wrapUserScript(js);
|
|
2592
|
+
hostDoc.body.appendChild(s);
|
|
2593
|
+
injected.push(s);
|
|
2594
|
+
};
|
|
2595
|
+
if (host && !host.isConnected) queueMicrotask(run);
|
|
2596
|
+
else run();
|
|
1992
2597
|
}
|
|
1993
2598
|
return {
|
|
1994
2599
|
node,
|
|
1995
2600
|
cleanup: () => {
|
|
2601
|
+
cancelled = true;
|
|
1996
2602
|
for (const n of injected) n.parentNode?.removeChild(n);
|
|
1997
2603
|
}
|
|
1998
2604
|
};
|
|
1999
2605
|
}
|
|
2606
|
+
function wrapUserScript(js) {
|
|
2607
|
+
return `(function(){try{
|
|
2608
|
+
${js}
|
|
2609
|
+
}catch(e){console.error("[OnUser] Error en el bloque de c\xF3digo:",e)}})();`;
|
|
2610
|
+
}
|
|
2000
2611
|
function runScript(old, hostDoc) {
|
|
2001
2612
|
const s = hostDoc.createElement("script");
|
|
2002
2613
|
for (const attr of Array.from(old.attributes)) s.setAttribute(attr.name, attr.value);
|
|
@@ -2231,8 +2842,14 @@ function buildField(field, doc) {
|
|
|
2231
2842
|
wrapper.appendChild(group);
|
|
2232
2843
|
return { wrapper, read: () => inputs.find((i) => i.input.checked)?.value };
|
|
2233
2844
|
}
|
|
2234
|
-
case "nps":
|
|
2235
|
-
|
|
2845
|
+
case "nps": {
|
|
2846
|
+
const min = typeof field.min === "number" ? field.min : 0;
|
|
2847
|
+
const max = typeof field.max === "number" && field.max > min ? field.max : 10;
|
|
2848
|
+
return buildScale(wrapper, doc, min, max, "veo-nps-btn", void 0, {
|
|
2849
|
+
labels: field.scaleLabels ?? [],
|
|
2850
|
+
showLabels: field.showLabels !== false
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2236
2853
|
case "rating":
|
|
2237
2854
|
return buildScale(wrapper, doc, 1, 5, "veo-rating-btn", "\u2605");
|
|
2238
2855
|
default: {
|
|
@@ -2245,17 +2862,21 @@ function buildField(field, doc) {
|
|
|
2245
2862
|
}
|
|
2246
2863
|
}
|
|
2247
2864
|
}
|
|
2248
|
-
function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
2865
|
+
function buildScale(wrapper, doc, min, max, btnClass, symbol, opts = { labels: [], showLabels: false }) {
|
|
2249
2866
|
const row = doc.createElement("div");
|
|
2250
2867
|
row.className = "veo-form-scale";
|
|
2251
2868
|
let selected;
|
|
2252
2869
|
const buttons = [];
|
|
2870
|
+
const labelOf = (n) => opts.labels[n - min]?.trim() ?? "";
|
|
2871
|
+
const anyLabel = opts.showLabels && Array.from({ length: max - min + 1 }, (_, i) => min + i).some((n) => labelOf(n));
|
|
2253
2872
|
for (let n = min; n <= max; n++) {
|
|
2254
2873
|
const btn = doc.createElement("button");
|
|
2255
2874
|
btn.type = "button";
|
|
2256
2875
|
btn.className = btnClass;
|
|
2257
2876
|
btn.textContent = symbol ?? String(n);
|
|
2258
|
-
|
|
2877
|
+
const lbl = labelOf(n);
|
|
2878
|
+
btn.setAttribute("aria-label", lbl ? `${n} \xB7 ${lbl}` : String(n));
|
|
2879
|
+
if (lbl) btn.title = lbl;
|
|
2259
2880
|
btn.addEventListener("click", () => {
|
|
2260
2881
|
selected = n;
|
|
2261
2882
|
buttons.forEach((b, i) => {
|
|
@@ -2264,11 +2885,127 @@ function buildScale(wrapper, doc, min, max, btnClass, symbol) {
|
|
|
2264
2885
|
});
|
|
2265
2886
|
});
|
|
2266
2887
|
buttons.push(btn);
|
|
2267
|
-
|
|
2888
|
+
if (!anyLabel) {
|
|
2889
|
+
row.appendChild(btn);
|
|
2890
|
+
continue;
|
|
2891
|
+
}
|
|
2892
|
+
const item = doc.createElement("div");
|
|
2893
|
+
item.className = `veo-scale-item${n === min ? " veo-scale-item--first" : n === max ? " veo-scale-item--last" : ""}`;
|
|
2894
|
+
const text = doc.createElement("span");
|
|
2895
|
+
text.className = "veo-scale-label";
|
|
2896
|
+
text.textContent = lbl;
|
|
2897
|
+
item.append(btn, text);
|
|
2898
|
+
row.appendChild(item);
|
|
2268
2899
|
}
|
|
2269
2900
|
wrapper.appendChild(row);
|
|
2270
2901
|
return { wrapper, read: () => selected };
|
|
2271
2902
|
}
|
|
2903
|
+
|
|
2904
|
+
// src/plugins/guides/layout.ts
|
|
2905
|
+
function groupLayout(blocks) {
|
|
2906
|
+
const rows = /* @__PURE__ */ new Map();
|
|
2907
|
+
const order = [];
|
|
2908
|
+
let lastRow = -1;
|
|
2909
|
+
blocks.forEach((block, index) => {
|
|
2910
|
+
if (!block) return;
|
|
2911
|
+
let row;
|
|
2912
|
+
if (typeof block.row === "number" && Number.isFinite(block.row)) {
|
|
2913
|
+
row = block.row;
|
|
2914
|
+
lastRow = row;
|
|
2915
|
+
} else {
|
|
2916
|
+
row = lastRow + 0.5;
|
|
2917
|
+
lastRow = row;
|
|
2918
|
+
}
|
|
2919
|
+
const col = typeof block.col === "number" && Number.isFinite(block.col) ? block.col : 0;
|
|
2920
|
+
let r = rows.get(row);
|
|
2921
|
+
if (!r) {
|
|
2922
|
+
r = { row, columns: [] };
|
|
2923
|
+
rows.set(row, r);
|
|
2924
|
+
order.push(row);
|
|
2925
|
+
}
|
|
2926
|
+
let c = r.columns.find((x) => x.col === col);
|
|
2927
|
+
if (!c) {
|
|
2928
|
+
c = { col, blocks: [] };
|
|
2929
|
+
r.columns.push(c);
|
|
2930
|
+
}
|
|
2931
|
+
c.blocks.push({ block, index });
|
|
2932
|
+
});
|
|
2933
|
+
return order.sort((a, b) => a - b).map((k) => {
|
|
2934
|
+
const r = rows.get(k);
|
|
2935
|
+
r.columns.sort((a, b) => a.col - b.col);
|
|
2936
|
+
return r;
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
// src/plugins/guides/scroll-lock.ts
|
|
2941
|
+
var states = /* @__PURE__ */ new WeakMap();
|
|
2942
|
+
function lockScroll(doc = document) {
|
|
2943
|
+
const html = doc.documentElement;
|
|
2944
|
+
const body = doc.body;
|
|
2945
|
+
if (!html || !body) return () => {
|
|
2946
|
+
};
|
|
2947
|
+
let state = states.get(doc);
|
|
2948
|
+
if (!state) {
|
|
2949
|
+
state = {
|
|
2950
|
+
count: 0,
|
|
2951
|
+
html: { overflow: html.style.overflow, paddingRight: html.style.paddingRight },
|
|
2952
|
+
body: { overflow: body.style.overflow, paddingRight: body.style.paddingRight }
|
|
2953
|
+
};
|
|
2954
|
+
states.set(doc, state);
|
|
2955
|
+
const win = doc.defaultView;
|
|
2956
|
+
const gap = win ? Math.max(0, win.innerWidth - html.clientWidth) : 0;
|
|
2957
|
+
if (gap > 0) body.style.paddingRight = `${gap}px`;
|
|
2958
|
+
html.style.overflow = "hidden";
|
|
2959
|
+
body.style.overflow = "hidden";
|
|
2960
|
+
}
|
|
2961
|
+
state.count += 1;
|
|
2962
|
+
let released = false;
|
|
2963
|
+
return () => {
|
|
2964
|
+
if (released) return;
|
|
2965
|
+
released = true;
|
|
2966
|
+
const s = states.get(doc);
|
|
2967
|
+
if (!s) return;
|
|
2968
|
+
s.count -= 1;
|
|
2969
|
+
if (s.count > 0) return;
|
|
2970
|
+
states.delete(doc);
|
|
2971
|
+
html.style.overflow = s.html.overflow;
|
|
2972
|
+
html.style.paddingRight = s.html.paddingRight;
|
|
2973
|
+
body.style.overflow = s.body.overflow;
|
|
2974
|
+
body.style.paddingRight = s.body.paddingRight;
|
|
2975
|
+
};
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
// src/plugins/guides/snooze.ts
|
|
2979
|
+
var KEY = (guideId) => `veo:snooze:${guideId}`;
|
|
2980
|
+
var UNIT_MS = {
|
|
2981
|
+
hour: 36e5,
|
|
2982
|
+
day: 864e5,
|
|
2983
|
+
week: 6048e5
|
|
2984
|
+
};
|
|
2985
|
+
function snoozeDurationMs(every, unit) {
|
|
2986
|
+
const n = Number.isFinite(every) && every > 0 ? Math.floor(every) : 1;
|
|
2987
|
+
return n * (UNIT_MS[unit] ?? UNIT_MS.day);
|
|
2988
|
+
}
|
|
2989
|
+
function snoozeGuide(guideId, every, unit, now = Date.now()) {
|
|
2990
|
+
try {
|
|
2991
|
+
localStorage.setItem(KEY(guideId), String(now + snoozeDurationMs(every, unit)));
|
|
2992
|
+
} catch {
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
function isSnoozed(guideId, now = Date.now()) {
|
|
2996
|
+
try {
|
|
2997
|
+
const raw = localStorage.getItem(KEY(guideId));
|
|
2998
|
+
if (raw === null) return false;
|
|
2999
|
+
const until = Number(raw);
|
|
3000
|
+
if (!Number.isFinite(until) || until <= now) {
|
|
3001
|
+
localStorage.removeItem(KEY(guideId));
|
|
3002
|
+
return false;
|
|
3003
|
+
}
|
|
3004
|
+
return true;
|
|
3005
|
+
} catch {
|
|
3006
|
+
return false;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
2272
3009
|
var INFO_GLYPH = "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021";
|
|
2273
3010
|
var QUESTION_GLYPH = "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75";
|
|
2274
3011
|
var STAR_GLYPH = "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z";
|
|
@@ -2427,13 +3164,83 @@ function markerOffset(anchor, marker, position, offset6) {
|
|
|
2427
3164
|
}
|
|
2428
3165
|
return { x: x + offset6.x, y: y + offset6.y };
|
|
2429
3166
|
}
|
|
3167
|
+
var NO_CHILDREN = /* @__PURE__ */ new Set([
|
|
3168
|
+
"img",
|
|
3169
|
+
"input",
|
|
3170
|
+
"textarea",
|
|
3171
|
+
"select",
|
|
3172
|
+
"iframe",
|
|
3173
|
+
"video",
|
|
3174
|
+
"audio",
|
|
3175
|
+
"canvas",
|
|
3176
|
+
"br",
|
|
3177
|
+
"hr",
|
|
3178
|
+
"svg",
|
|
3179
|
+
"object",
|
|
3180
|
+
"embed"
|
|
3181
|
+
]);
|
|
3182
|
+
function canNest(anchor) {
|
|
3183
|
+
const win = anchor.ownerDocument.defaultView;
|
|
3184
|
+
if (!win || !(anchor instanceof win.HTMLElement)) return false;
|
|
3185
|
+
if (NO_CHILDREN.has(anchor.tagName.toLowerCase())) return false;
|
|
3186
|
+
const cs = win.getComputedStyle(anchor);
|
|
3187
|
+
if (cs.display === "contents") return false;
|
|
3188
|
+
const tokens = `${cs.overflow} ${cs.overflowX} ${cs.overflowY}`.split(/\s+/).filter(Boolean);
|
|
3189
|
+
return tokens.every((t) => t === "visible");
|
|
3190
|
+
}
|
|
2430
3191
|
function attachMarker(anchor, host, marker) {
|
|
2431
3192
|
host.style.position = "absolute";
|
|
2432
3193
|
host.style.zIndex = String(num(marker.zIndex, DEFAULT_MARKER_Z));
|
|
2433
3194
|
host.style.margin = "0";
|
|
3195
|
+
const position = marker.position ?? "top-right";
|
|
3196
|
+
const offset6 = { x: num(marker.offsetX, 0), y: num(marker.offsetY, 0) };
|
|
3197
|
+
const markerSize = () => {
|
|
3198
|
+
const m = host.getBoundingClientRect();
|
|
3199
|
+
return { width: m.width, height: m.height };
|
|
3200
|
+
};
|
|
3201
|
+
if (canNest(anchor)) {
|
|
3202
|
+
const win = anchor.ownerDocument.defaultView;
|
|
3203
|
+
const prevPosition = anchor.style.position;
|
|
3204
|
+
const computed = win.getComputedStyle(anchor).position;
|
|
3205
|
+
if (!computed || computed === "static") anchor.style.position = "relative";
|
|
3206
|
+
anchor.appendChild(host);
|
|
3207
|
+
const swallowClick = (e) => {
|
|
3208
|
+
e.preventDefault();
|
|
3209
|
+
e.stopPropagation();
|
|
3210
|
+
};
|
|
3211
|
+
const swallow = (e) => e.stopPropagation();
|
|
3212
|
+
host.addEventListener("click", swallowClick);
|
|
3213
|
+
host.addEventListener("pointerdown", swallow);
|
|
3214
|
+
host.addEventListener("mousedown", swallow);
|
|
3215
|
+
const update2 = () => {
|
|
3216
|
+
const { x, y } = markerOffset(
|
|
3217
|
+
{
|
|
3218
|
+
left: -anchor.clientLeft,
|
|
3219
|
+
top: -anchor.clientTop,
|
|
3220
|
+
width: anchor.offsetWidth,
|
|
3221
|
+
height: anchor.offsetHeight
|
|
3222
|
+
},
|
|
3223
|
+
markerSize(),
|
|
3224
|
+
position,
|
|
3225
|
+
offset6
|
|
3226
|
+
);
|
|
3227
|
+
host.style.left = `${Math.round(x)}px`;
|
|
3228
|
+
host.style.top = `${Math.round(y)}px`;
|
|
3229
|
+
};
|
|
3230
|
+
update2();
|
|
3231
|
+
const stop2 = autoUpdate(anchor, host, update2, { ancestorScroll: false, layoutShift: false });
|
|
3232
|
+
return () => {
|
|
3233
|
+
stop2();
|
|
3234
|
+
host.removeEventListener("click", swallowClick);
|
|
3235
|
+
host.removeEventListener("pointerdown", swallow);
|
|
3236
|
+
host.removeEventListener("mousedown", swallow);
|
|
3237
|
+
host.remove();
|
|
3238
|
+
anchor.style.position = prevPosition;
|
|
3239
|
+
};
|
|
3240
|
+
}
|
|
3241
|
+
anchor.ownerDocument.body.appendChild(host);
|
|
2434
3242
|
const update = () => {
|
|
2435
3243
|
const a = anchor.getBoundingClientRect();
|
|
2436
|
-
const m = host.getBoundingClientRect();
|
|
2437
3244
|
const { x, y } = markerOffset(
|
|
2438
3245
|
{
|
|
2439
3246
|
left: a.left + window.scrollX,
|
|
@@ -2441,15 +3248,19 @@ function attachMarker(anchor, host, marker) {
|
|
|
2441
3248
|
width: a.width,
|
|
2442
3249
|
height: a.height
|
|
2443
3250
|
},
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
3251
|
+
markerSize(),
|
|
3252
|
+
position,
|
|
3253
|
+
offset6
|
|
2447
3254
|
);
|
|
2448
3255
|
host.style.left = `${Math.round(x)}px`;
|
|
2449
3256
|
host.style.top = `${Math.round(y)}px`;
|
|
2450
3257
|
};
|
|
2451
3258
|
update();
|
|
2452
|
-
|
|
3259
|
+
const stop = autoUpdate(anchor, host, update);
|
|
3260
|
+
return () => {
|
|
3261
|
+
stop();
|
|
3262
|
+
host.remove();
|
|
3263
|
+
};
|
|
2453
3264
|
}
|
|
2454
3265
|
|
|
2455
3266
|
// src/plugins/guides/renderers/composite-renderer.ts
|
|
@@ -2458,6 +3269,32 @@ var ROW_ALIGN = {
|
|
|
2458
3269
|
center: "center",
|
|
2459
3270
|
right: "flex-end"
|
|
2460
3271
|
};
|
|
3272
|
+
var ROW_JUSTIFY = { ...ROW_ALIGN, distribute: "space-between" };
|
|
3273
|
+
var ROW_VALIGN = {
|
|
3274
|
+
top: "flex-start",
|
|
3275
|
+
center: "center",
|
|
3276
|
+
bottom: "flex-end"
|
|
3277
|
+
};
|
|
3278
|
+
function applyRowStyle(rowEl, style) {
|
|
3279
|
+
const s = rowEl.style;
|
|
3280
|
+
if (typeof style.background === "string" && style.background) s.background = style.background;
|
|
3281
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3282
|
+
const p = style[`padding${side}`];
|
|
3283
|
+
if (typeof p === "number" && Number.isFinite(p))
|
|
3284
|
+
s.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3285
|
+
const m = style[`margin${side}`];
|
|
3286
|
+
if (typeof m === "number" && Number.isFinite(m))
|
|
3287
|
+
s.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3288
|
+
}
|
|
3289
|
+
if (typeof style.minHeight === "number" && Number.isFinite(style.minHeight) && style.minHeight > 0) {
|
|
3290
|
+
s.minHeight = `${style.minHeight}px`;
|
|
3291
|
+
}
|
|
3292
|
+
if (style.align && ROW_JUSTIFY[style.align]) {
|
|
3293
|
+
s.justifyContent = ROW_JUSTIFY[style.align];
|
|
3294
|
+
rowEl.classList.add("veo-row--aligned");
|
|
3295
|
+
}
|
|
3296
|
+
if (style.valign && ROW_VALIGN[style.valign]) s.alignItems = ROW_VALIGN[style.valign];
|
|
3297
|
+
}
|
|
2461
3298
|
var CompositeRenderer = class extends BaseRenderer {
|
|
2462
3299
|
async render(context) {
|
|
2463
3300
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -2476,7 +3313,8 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2476
3313
|
const rawDoc = anchor.ownerDocument ?? document;
|
|
2477
3314
|
const { cleanup } = mountCodeBlock(soleCode, rawDoc, context, void 0, {
|
|
2478
3315
|
anchor,
|
|
2479
|
-
pos: inlinePosOf(step.inlinePosition)
|
|
3316
|
+
pos: inlinePosOf(step.inlinePosition),
|
|
3317
|
+
...context.isPreview && soleCode.editorId ? { tagBlockId: soleCode.editorId } : {}
|
|
2480
3318
|
});
|
|
2481
3319
|
this.registerCleanup(cleanup);
|
|
2482
3320
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action: "shown" });
|
|
@@ -2489,6 +3327,10 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2489
3327
|
const card = doc.createElement("div");
|
|
2490
3328
|
card.className = "veo-card";
|
|
2491
3329
|
applyCompositeDesign(card, design);
|
|
3330
|
+
if (typeof design.ariaLabel === "string" && design.ariaLabel.trim()) {
|
|
3331
|
+
card.setAttribute("role", "dialog");
|
|
3332
|
+
card.setAttribute("aria-label", design.ariaLabel.trim());
|
|
3333
|
+
}
|
|
2492
3334
|
const dismiss = (action, meta) => {
|
|
2493
3335
|
context.onInteraction({
|
|
2494
3336
|
guideId: context.guide.guideId,
|
|
@@ -2499,6 +3341,28 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2499
3341
|
};
|
|
2500
3342
|
const readers = [];
|
|
2501
3343
|
this.buildContent(card, step.blocks ?? [], doc, context, idx, readers, host);
|
|
3344
|
+
if (blocks.length === 0) {
|
|
3345
|
+
card.classList.add("veo-card--empty");
|
|
3346
|
+
if (context.isPreview) {
|
|
3347
|
+
const zone = doc.createElement("div");
|
|
3348
|
+
zone.className = "veo-card-empty-zone";
|
|
3349
|
+
const plus = doc.createElement("b");
|
|
3350
|
+
plus.textContent = "+";
|
|
3351
|
+
const text = doc.createElement("span");
|
|
3352
|
+
text.textContent = "Clic para agregar contenido";
|
|
3353
|
+
zone.append(plus, text);
|
|
3354
|
+
card.appendChild(zone);
|
|
3355
|
+
const applyNarrow = () => {
|
|
3356
|
+
card.classList.toggle("veo-card--narrow", card.clientWidth < NARROW_CARD_WIDTH);
|
|
3357
|
+
};
|
|
3358
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
3359
|
+
const ro = new ResizeObserver(applyNarrow);
|
|
3360
|
+
ro.observe(card);
|
|
3361
|
+
this.registerCleanup(() => ro.disconnect());
|
|
3362
|
+
}
|
|
3363
|
+
applyNarrow();
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
2502
3366
|
const lastBlock = card.lastElementChild;
|
|
2503
3367
|
if (lastBlock && !lastBlock.style.marginBottom) lastBlock.style.marginBottom = "0";
|
|
2504
3368
|
let closeTooltip = null;
|
|
@@ -2516,6 +3380,14 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2516
3380
|
if (closeColor) closeBtn.style.color = closeColor;
|
|
2517
3381
|
const closePos = typeof design.closePosition === "string" ? design.closePosition : "top-right";
|
|
2518
3382
|
applyClosePosition(closeBtn, closePos);
|
|
3383
|
+
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
3384
|
+
const p = design[`closePadding${side}`];
|
|
3385
|
+
if (typeof p === "number")
|
|
3386
|
+
closeBtn.style.setProperty(`padding-${side.toLowerCase()}`, `${p}px`);
|
|
3387
|
+
const m = design[`closeMargin${side}`];
|
|
3388
|
+
if (typeof m === "number")
|
|
3389
|
+
closeBtn.style.setProperty(`margin-${side.toLowerCase()}`, `${m}px`);
|
|
3390
|
+
}
|
|
2519
3391
|
if (hoverColor) {
|
|
2520
3392
|
closeBtn.addEventListener("mouseenter", () => {
|
|
2521
3393
|
closeBtn.style.color = hoverColor;
|
|
@@ -2525,6 +3397,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2525
3397
|
});
|
|
2526
3398
|
}
|
|
2527
3399
|
card.appendChild(closeBtn);
|
|
3400
|
+
reserveCloseSpace(card, blocks, closePos, design);
|
|
2528
3401
|
}
|
|
2529
3402
|
if (badgeMode && anchor) {
|
|
2530
3403
|
closeTooltip = this.renderBadgeMode(
|
|
@@ -2555,6 +3428,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2555
3428
|
const bz = typeof design.backdropZIndex === "number" ? design.backdropZIndex : 2e5;
|
|
2556
3429
|
bg.style.zIndex = String(bz);
|
|
2557
3430
|
root.appendChild(bg);
|
|
3431
|
+
this.registerCleanup(lockScroll(doc));
|
|
2558
3432
|
if (design.targetAbove === true) this.registerCleanup(liftTarget(anchor, bz + 1));
|
|
2559
3433
|
}
|
|
2560
3434
|
const wrapper = doc.createElement("div");
|
|
@@ -2581,6 +3455,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2581
3455
|
this.closeOrSkip(context);
|
|
2582
3456
|
}
|
|
2583
3457
|
});
|
|
3458
|
+
this.registerCleanup(lockScroll(doc));
|
|
2584
3459
|
}
|
|
2585
3460
|
applyViewportPosition(card, step.position, design);
|
|
2586
3461
|
overlay.appendChild(card);
|
|
@@ -2598,84 +3473,218 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2598
3473
|
...block.id ? { metadata: { buttonId: block.id } } : {}
|
|
2599
3474
|
});
|
|
2600
3475
|
};
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
3476
|
+
const tag = (node, block, index) => {
|
|
3477
|
+
node.dataset.veoBlock = String(index);
|
|
3478
|
+
if (block.editorId) node.dataset.veoBlockId = block.editorId;
|
|
3479
|
+
return node;
|
|
3480
|
+
};
|
|
3481
|
+
const placeholder = (block, label) => {
|
|
3482
|
+
if (!context.isPreview || !block.editorId) return null;
|
|
3483
|
+
const el = doc.createElement("div");
|
|
3484
|
+
el.className = "veo-block-placeholder";
|
|
3485
|
+
el.textContent = label;
|
|
3486
|
+
return el;
|
|
3487
|
+
};
|
|
3488
|
+
const buildSequence = (container, cell) => {
|
|
3489
|
+
let i = 0;
|
|
3490
|
+
while (i < cell.length) {
|
|
3491
|
+
const { block: b, index } = cell[i];
|
|
3492
|
+
if (b.type === "button") {
|
|
3493
|
+
const row = doc.createElement("div");
|
|
3494
|
+
row.className = "veo-guide-actions";
|
|
3495
|
+
let rowAlign;
|
|
3496
|
+
while (i < cell.length && cell[i]?.block.type === "button") {
|
|
3497
|
+
const { block: bb, index: bi } = cell[i];
|
|
3498
|
+
if (!rowAlign) {
|
|
3499
|
+
const a = bb.style?.align;
|
|
3500
|
+
if (a === "left" || a === "center" || a === "right") rowAlign = a;
|
|
3501
|
+
}
|
|
3502
|
+
row.appendChild(tag(this.buildButton(bb, doc, context, idx, readers, emit), bb, bi));
|
|
3503
|
+
i++;
|
|
2617
3504
|
}
|
|
2618
|
-
row.
|
|
3505
|
+
if (rowAlign) row.style.justifyContent = ROW_ALIGN[rowAlign] ?? "flex-end";
|
|
3506
|
+
container.appendChild(row);
|
|
3507
|
+
continue;
|
|
3508
|
+
}
|
|
3509
|
+
if (b.type === "code") {
|
|
3510
|
+
const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
|
|
3511
|
+
const el = node2 ?? placeholder(b, "Bloque de c\xF3digo");
|
|
3512
|
+
if (el) container.appendChild(tag(el, b, index));
|
|
3513
|
+
this.registerCleanup(cleanup);
|
|
2619
3514
|
i++;
|
|
3515
|
+
continue;
|
|
2620
3516
|
}
|
|
2621
|
-
if (
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
if (b.type === "code") {
|
|
2626
|
-
const { node: node2, cleanup } = mountCodeBlock(b, doc, context, host);
|
|
2627
|
-
if (node2) card.appendChild(node2);
|
|
2628
|
-
this.registerCleanup(cleanup);
|
|
2629
|
-
i++;
|
|
2630
|
-
continue;
|
|
2631
|
-
}
|
|
2632
|
-
if (b.type === "form") {
|
|
2633
|
-
const form = doc.createElement("div");
|
|
2634
|
-
form.className = "veo-form";
|
|
2635
|
-
for (const field of b.fields ?? []) {
|
|
2636
|
-
const { wrapper, read } = buildField(field, doc);
|
|
2637
|
-
readers.push({ field, read });
|
|
2638
|
-
form.appendChild(wrapper);
|
|
3517
|
+
if (b.type === "form") {
|
|
3518
|
+
container.appendChild(tag(this.buildForm(b, doc, context, idx, readers, emit), b, index));
|
|
3519
|
+
i++;
|
|
3520
|
+
continue;
|
|
2639
3521
|
}
|
|
2640
|
-
|
|
3522
|
+
if (b.type === "checklist") {
|
|
3523
|
+
const el = buildChecklistBlock(b, doc, context);
|
|
3524
|
+
applyBlockStyle(el, b.style, "text");
|
|
3525
|
+
el.style.alignSelf = "stretch";
|
|
3526
|
+
el.style.width = "100%";
|
|
3527
|
+
container.appendChild(tag(el, b, index));
|
|
3528
|
+
i++;
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
const node = buildContentBlock(b, doc, { editing: context.isPreview === true }) ?? placeholder(
|
|
3532
|
+
b,
|
|
3533
|
+
b.type === "image" ? "Imagen (sin URL)" : b.type === "video" ? "Video (elige proveedor y pega la URL)" : "Bloque vac\xEDo"
|
|
3534
|
+
);
|
|
3535
|
+
if (node) container.appendChild(tag(node, b, index));
|
|
2641
3536
|
i++;
|
|
3537
|
+
}
|
|
3538
|
+
};
|
|
3539
|
+
const rowStyles = context.guide.guideSteps[idx]?.rows;
|
|
3540
|
+
for (const row of groupLayout(blocks)) {
|
|
3541
|
+
const rowStyle = Number.isInteger(row.row) ? rowStyles?.[String(row.row)] : void 0;
|
|
3542
|
+
if (row.columns.length === 1 && !rowStyle) {
|
|
3543
|
+
buildSequence(card, row.columns[0].blocks);
|
|
2642
3544
|
continue;
|
|
2643
3545
|
}
|
|
2644
|
-
const
|
|
2645
|
-
|
|
2646
|
-
|
|
3546
|
+
const rowEl = doc.createElement("div");
|
|
3547
|
+
rowEl.className = "veo-row";
|
|
3548
|
+
if (Number.isInteger(row.row)) rowEl.dataset.veoRow = String(row.row);
|
|
3549
|
+
for (const column of row.columns) {
|
|
3550
|
+
const colEl = doc.createElement("div");
|
|
3551
|
+
colEl.className = "veo-col";
|
|
3552
|
+
buildSequence(colEl, column.blocks);
|
|
3553
|
+
rowEl.appendChild(colEl);
|
|
3554
|
+
}
|
|
3555
|
+
if (rowStyle) applyRowStyle(rowEl, rowStyle);
|
|
3556
|
+
card.appendChild(rowEl);
|
|
2647
3557
|
}
|
|
2648
3558
|
}
|
|
2649
3559
|
/** Botón con enrutado de acción (navegación / cierre / url / guía / envío de forms). */
|
|
2650
|
-
|
|
3560
|
+
/**
|
|
3561
|
+
* Bloque `form` (encuesta): pregunta (rich text del bloque) + campos + botón
|
|
3562
|
+
* de envío propio (`submit`). Con pregunta, el label del campo se omite (sería
|
|
3563
|
+
* la misma pregunta dos veces) y pasa a `aria-label` del input. El estilo del
|
|
3564
|
+
* bloque viste la pregunta (preset/alineación/color); los márgenes van al
|
|
3565
|
+
* contenedor del bloque entero.
|
|
3566
|
+
*/
|
|
3567
|
+
buildForm(b, doc, context, idx, readers, emit) {
|
|
3568
|
+
const form = doc.createElement("div");
|
|
3569
|
+
form.className = "veo-form";
|
|
3570
|
+
const style = b.style ?? {};
|
|
3571
|
+
const { marginTop, marginBottom, ...questionStyle } = style;
|
|
3572
|
+
for (const [side, v] of [
|
|
3573
|
+
["marginTop", marginTop],
|
|
3574
|
+
["marginBottom", marginBottom]
|
|
3575
|
+
]) {
|
|
3576
|
+
if (typeof v === "number" && Number.isFinite(v)) form.style[side] = `${v}px`;
|
|
3577
|
+
}
|
|
3578
|
+
let question = null;
|
|
3579
|
+
if (typeof b.html === "string" && b.html || typeof b.text === "string" && b.text) {
|
|
3580
|
+
question = doc.createElement("div");
|
|
3581
|
+
question.className = "veo-form-question";
|
|
3582
|
+
if (typeof b.html === "string" && b.html) question.appendChild(renderRichText(b.html, doc));
|
|
3583
|
+
else question.textContent = b.text;
|
|
3584
|
+
applyBlockStyle(question, questionStyle, "text");
|
|
3585
|
+
form.appendChild(question);
|
|
3586
|
+
}
|
|
3587
|
+
const questionText = question?.textContent?.trim() ?? "";
|
|
3588
|
+
const own = [];
|
|
3589
|
+
for (const field of b.fields ?? []) {
|
|
3590
|
+
const { wrapper, read } = buildField(field, doc);
|
|
3591
|
+
const st = b.style ?? {};
|
|
3592
|
+
if (typeof st.scaleColor === "string" && st.scaleColor) {
|
|
3593
|
+
wrapper.style.setProperty("--veo-scale-color", st.scaleColor);
|
|
3594
|
+
}
|
|
3595
|
+
if (typeof st.scaleSize === "number" && st.scaleSize >= 24 && st.scaleSize <= 96) {
|
|
3596
|
+
wrapper.style.setProperty("--veo-scale-size", `${st.scaleSize}px`);
|
|
3597
|
+
}
|
|
3598
|
+
if (question) {
|
|
3599
|
+
wrapper.querySelector(".veo-form-label")?.remove();
|
|
3600
|
+
if (questionText) {
|
|
3601
|
+
for (const inp of wrapper.querySelectorAll("input, textarea, select")) {
|
|
3602
|
+
inp.setAttribute("aria-label", questionText);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
const reader = { field, read, blockId: b.id ?? null };
|
|
3607
|
+
readers.push(reader);
|
|
3608
|
+
own.push(reader);
|
|
3609
|
+
form.appendChild(wrapper);
|
|
3610
|
+
}
|
|
3611
|
+
if (b.submit) {
|
|
3612
|
+
const actions = doc.createElement("div");
|
|
3613
|
+
actions.className = "veo-guide-actions";
|
|
3614
|
+
const submit = { ...b.submit, type: "button" };
|
|
3615
|
+
const sa = submit.style?.align;
|
|
3616
|
+
if (sa === "left" || sa === "center" || sa === "right") {
|
|
3617
|
+
actions.style.justifyContent = ROW_ALIGN[sa] ?? "flex-end";
|
|
3618
|
+
}
|
|
3619
|
+
actions.appendChild(this.buildButton(submit, doc, context, idx, readers, emit, own));
|
|
3620
|
+
form.appendChild(actions);
|
|
3621
|
+
}
|
|
3622
|
+
return form;
|
|
3623
|
+
}
|
|
3624
|
+
buildButton(block, doc, context, idx, readers, emit, submitFirst) {
|
|
2651
3625
|
const btn = doc.createElement("button");
|
|
2652
3626
|
btn.type = "button";
|
|
2653
3627
|
btn.className = "veo-guide-cta";
|
|
2654
3628
|
btn.textContent = block.text?.trim() || "OK";
|
|
3629
|
+
const st = block.style ?? {};
|
|
3630
|
+
if (st.preset === "secondary" || st.preset === "link") {
|
|
3631
|
+
btn.classList.add(`veo-guide-cta--${st.preset}`);
|
|
3632
|
+
}
|
|
3633
|
+
if (typeof st.ariaLabel === "string" && st.ariaLabel.trim()) {
|
|
3634
|
+
btn.setAttribute("aria-label", st.ariaLabel.trim());
|
|
3635
|
+
}
|
|
2655
3636
|
applyBlockStyle(btn, block.style, "button");
|
|
2656
3637
|
const nav = context.nav?.callbacks;
|
|
2657
|
-
|
|
2658
|
-
switch (
|
|
3638
|
+
const runSimple = (spec) => {
|
|
3639
|
+
switch (spec.action) {
|
|
2659
3640
|
case "next":
|
|
2660
|
-
emit(block);
|
|
2661
3641
|
if (nav) nav.onNext();
|
|
2662
3642
|
else context.onClose();
|
|
2663
3643
|
break;
|
|
2664
3644
|
case "prev":
|
|
2665
|
-
emit(block);
|
|
2666
3645
|
nav?.onBack();
|
|
2667
3646
|
break;
|
|
3647
|
+
case "goto":
|
|
3648
|
+
if (nav?.onGoto && typeof spec.targetStep === "number") nav.onGoto(spec.targetStep);
|
|
3649
|
+
else context.onClose();
|
|
3650
|
+
break;
|
|
3651
|
+
case "snooze":
|
|
3652
|
+
snoozeGuide(
|
|
3653
|
+
context.guide.guideId,
|
|
3654
|
+
typeof spec.snoozeEvery === "number" ? spec.snoozeEvery : 1,
|
|
3655
|
+
spec.snoozeUnit ?? "day"
|
|
3656
|
+
);
|
|
3657
|
+
context.onClose();
|
|
3658
|
+
break;
|
|
2668
3659
|
case "url":
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
window.open(
|
|
3660
|
+
if (typeof spec.url === "string" && isSafeUrl(spec.url)) {
|
|
3661
|
+
if (spec.newTab === false) window.location.assign(spec.url);
|
|
3662
|
+
else window.open(spec.url, "_blank", "noopener,noreferrer");
|
|
2672
3663
|
}
|
|
2673
3664
|
break;
|
|
2674
3665
|
case "guide":
|
|
2675
|
-
|
|
2676
|
-
if (block.targetGuideId) context.onOpenGuide?.(block.targetGuideId);
|
|
3666
|
+
if (spec.targetGuideId) context.onOpenGuide?.(spec.targetGuideId);
|
|
2677
3667
|
this.closeOrSkip(context);
|
|
2678
3668
|
break;
|
|
3669
|
+
default:
|
|
3670
|
+
this.closeOrSkip(context);
|
|
3671
|
+
}
|
|
3672
|
+
};
|
|
3673
|
+
const runSecond = () => {
|
|
3674
|
+
if (block.secondAction?.action) runSimple(block.secondAction);
|
|
3675
|
+
};
|
|
3676
|
+
const runActions = () => {
|
|
3677
|
+
switch (block.action) {
|
|
3678
|
+
case "next":
|
|
3679
|
+
case "prev":
|
|
3680
|
+
case "goto":
|
|
3681
|
+
case "snooze":
|
|
3682
|
+
case "url":
|
|
3683
|
+
case "guide":
|
|
3684
|
+
emit(block);
|
|
3685
|
+
runSimple(block);
|
|
3686
|
+
runSecond();
|
|
3687
|
+
break;
|
|
2679
3688
|
case "submit-forms":
|
|
2680
3689
|
case "submit-forms-advance":
|
|
2681
3690
|
case "submit-forms-goto":
|
|
@@ -2693,7 +3702,12 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2693
3702
|
default:
|
|
2694
3703
|
emit(block);
|
|
2695
3704
|
this.closeOrSkip(context);
|
|
3705
|
+
runSecond();
|
|
2696
3706
|
}
|
|
3707
|
+
};
|
|
3708
|
+
btn.addEventListener("click", () => {
|
|
3709
|
+
if (submitFirst) this.submitForms(submitFirst, context, idx, btn, runActions);
|
|
3710
|
+
else runActions();
|
|
2697
3711
|
});
|
|
2698
3712
|
return btn;
|
|
2699
3713
|
}
|
|
@@ -2712,17 +3726,27 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2712
3726
|
}
|
|
2713
3727
|
answers[field.key] = value;
|
|
2714
3728
|
}
|
|
2715
|
-
if (
|
|
3729
|
+
if (Object.keys(answers).length === 0) {
|
|
3730
|
+
onDone();
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
2716
3733
|
btn.disabled = true;
|
|
2717
3734
|
const submitFn = context.onFormSubmit ?? (() => Promise.resolve(true));
|
|
2718
|
-
|
|
3735
|
+
const blockIds = new Set(
|
|
3736
|
+
readers.filter((r) => answers[r.field.key] !== void 0).map((r) => r.blockId ?? "")
|
|
3737
|
+
);
|
|
3738
|
+
const pollId = blockIds.size === 1 ? [...blockIds][0] || void 0 : void 0;
|
|
3739
|
+
void submitFn(answers, { ...pollId ? { pollId } : {}, stepIndex: idx }).then((ok) => {
|
|
2719
3740
|
btn.disabled = false;
|
|
2720
3741
|
if (!ok) return;
|
|
2721
|
-
context.
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
3742
|
+
const lastStep = !context.nav || idx >= context.nav.totalSteps - 1;
|
|
3743
|
+
if (lastStep) {
|
|
3744
|
+
context.onInteraction({
|
|
3745
|
+
guideId: context.guide.guideId,
|
|
3746
|
+
stepIndex: idx,
|
|
3747
|
+
action: "completed"
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
2726
3750
|
onDone();
|
|
2727
3751
|
});
|
|
2728
3752
|
}
|
|
@@ -2765,9 +3789,7 @@ var CompositeRenderer = class extends BaseRenderer {
|
|
|
2765
3789
|
root.appendChild(wrapper);
|
|
2766
3790
|
if (marker) {
|
|
2767
3791
|
host.style.display = "inline-block";
|
|
2768
|
-
(anchor.ownerDocument ?? document).body.appendChild(host);
|
|
2769
3792
|
this.registerCleanup(attachMarker(anchor, host, marker));
|
|
2770
|
-
this.registerCleanup(() => host.remove());
|
|
2771
3793
|
} else {
|
|
2772
3794
|
host.style.display = "inline-block";
|
|
2773
3795
|
const pos = inlinePosOf(step.inlinePosition);
|
|
@@ -2836,21 +3858,38 @@ function inlinePosOf(p) {
|
|
|
2836
3858
|
function applyViewportPosition(card, position, design) {
|
|
2837
3859
|
const s = card.style;
|
|
2838
3860
|
s.position = "fixed";
|
|
2839
|
-
const
|
|
3861
|
+
const BASE = 16;
|
|
3862
|
+
const m = (k) => typeof design[k] === "number" && Number.isFinite(design[k]) ? design[k] : 0;
|
|
3863
|
+
s.margin = "0";
|
|
2840
3864
|
const hAlign = design.hAlign === "left" ? "left" : design.hAlign === "right" ? "right" : null;
|
|
2841
3865
|
const v = position.includes("top") ? "top" : position.includes("bottom") ? "bottom" : "middle";
|
|
2842
3866
|
const h = hAlign ?? (position.includes("left") ? "left" : position.includes("right") ? "right" : "middle");
|
|
2843
|
-
if (v === "top") s.top =
|
|
2844
|
-
else if (v === "bottom") s.bottom =
|
|
3867
|
+
if (v === "top") s.top = `${BASE + m("marginTop")}px`;
|
|
3868
|
+
else if (v === "bottom") s.bottom = `${BASE + m("marginBottom")}px`;
|
|
2845
3869
|
else s.top = "50%";
|
|
2846
|
-
if (h === "left") s.left =
|
|
2847
|
-
else if (h === "right") s.right =
|
|
3870
|
+
if (h === "left") s.left = `${BASE + m("marginLeft")}px`;
|
|
3871
|
+
else if (h === "right") s.right = `${BASE + m("marginRight")}px`;
|
|
2848
3872
|
else s.left = "50%";
|
|
2849
|
-
const
|
|
2850
|
-
const
|
|
3873
|
+
const dx = m("marginLeft") - m("marginRight");
|
|
3874
|
+
const dy = m("marginTop") - m("marginBottom");
|
|
3875
|
+
const tx = h === "middle" ? `calc(-50% + ${dx}px)` : "0";
|
|
3876
|
+
const ty = v === "middle" ? `calc(-50% + ${dy}px)` : "0";
|
|
2851
3877
|
s.transform = tx === "0" && ty === "0" ? "" : `translate(${tx}, ${ty})`;
|
|
2852
3878
|
}
|
|
2853
3879
|
var CLOSE_INSET = "10px";
|
|
3880
|
+
var CLOSE_RESERVE_PX = 42;
|
|
3881
|
+
var CARD_DEFAULT_PAD_PX = 20;
|
|
3882
|
+
function reserveCloseSpace(card, blocks, position, design) {
|
|
3883
|
+
if (blocks.length === 0) return;
|
|
3884
|
+
const side = position.includes("bottom") ? "Bottom" : "Top";
|
|
3885
|
+
const edgeBlock = side === "Top" ? blocks[0] : blocks[blocks.length - 1];
|
|
3886
|
+
if (edgeBlock?.type === "image") return;
|
|
3887
|
+
const margin = design[`closeMargin${side}`];
|
|
3888
|
+
const need = CLOSE_RESERVE_PX + (typeof margin === "number" ? margin : 0);
|
|
3889
|
+
const current = design[`padding${side}`];
|
|
3890
|
+
const have = typeof current === "number" ? current : CARD_DEFAULT_PAD_PX;
|
|
3891
|
+
if (have < need) card.style.setProperty(`padding-${side.toLowerCase()}`, `${need}px`);
|
|
3892
|
+
}
|
|
2854
3893
|
function applyClosePosition(btn, position) {
|
|
2855
3894
|
const s = btn.style;
|
|
2856
3895
|
s.top = "auto";
|
|
@@ -2915,10 +3954,17 @@ async function anchorPosition(anchor, wrapper, caret, preferred = "bottom") {
|
|
|
2915
3954
|
window.removeEventListener("resize", reposition);
|
|
2916
3955
|
};
|
|
2917
3956
|
}
|
|
3957
|
+
var MIN_CARD_WIDTH = 120;
|
|
3958
|
+
var NARROW_CARD_WIDTH = 260;
|
|
2918
3959
|
function applyCompositeDesign(card, design) {
|
|
2919
3960
|
const s = card.style;
|
|
2920
3961
|
const px = (v) => typeof v === "number" && Number.isFinite(v) ? `${v}px` : null;
|
|
2921
3962
|
const strv = (v) => typeof v === "string" && v ? v : null;
|
|
3963
|
+
if (design.colorMode === "dark") {
|
|
3964
|
+
s.background = "#1f1d1b";
|
|
3965
|
+
s.color = "#f5f0eb";
|
|
3966
|
+
card.classList.add("veo-card--dark");
|
|
3967
|
+
}
|
|
2922
3968
|
if (strv(design.background)) s.background = design.background;
|
|
2923
3969
|
const textColor = strv(design.color) ?? strv(design.textColor);
|
|
2924
3970
|
if (textColor) s.color = textColor;
|
|
@@ -2940,13 +3986,14 @@ function applyCompositeDesign(card, design) {
|
|
|
2940
3986
|
const n = (k) => typeof design[k] === "number" ? design[k] : 0;
|
|
2941
3987
|
s.boxShadow = `${n("shadowX")}px ${n("shadowY")}px ${n("shadowBlur")}px ${n("shadowSpread")}px ${strv(design.shadowColor) ?? "rgba(0,0,0,0.2)"}`;
|
|
2942
3988
|
}
|
|
3989
|
+
const width = typeof design.width === "number" && Number.isFinite(design.width) ? `${Math.max(MIN_CARD_WIDTH, design.width)}px` : null;
|
|
2943
3990
|
if (design.fullWidth === true) {
|
|
2944
3991
|
s.width = "100%";
|
|
2945
3992
|
} else if (design.widthType === "fluid") {
|
|
2946
|
-
s.width = "
|
|
2947
|
-
if (
|
|
2948
|
-
} else if (
|
|
2949
|
-
s.width =
|
|
3993
|
+
s.width = "100%";
|
|
3994
|
+
if (width) s.maxWidth = width;
|
|
3995
|
+
} else if (width) {
|
|
3996
|
+
s.width = width;
|
|
2950
3997
|
}
|
|
2951
3998
|
for (const side of ["Top", "Right", "Bottom", "Left"]) {
|
|
2952
3999
|
const p = px(design[`padding${side}`]);
|
|
@@ -3050,6 +4097,7 @@ var FormRenderer = class extends BaseRenderer {
|
|
|
3050
4097
|
mountFormContent(card, context, doc);
|
|
3051
4098
|
overlay.appendChild(card);
|
|
3052
4099
|
root.appendChild(overlay);
|
|
4100
|
+
this.registerCleanup(lockScroll(doc));
|
|
3053
4101
|
const dismiss = () => {
|
|
3054
4102
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
|
|
3055
4103
|
context.onClose();
|
|
@@ -3177,6 +4225,18 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3177
4225
|
constructor() {
|
|
3178
4226
|
super(...arguments);
|
|
3179
4227
|
this.overlay = null;
|
|
4228
|
+
/** Suelta el bloqueo de scroll del backdrop (null = sin backdrop). */
|
|
4229
|
+
this.releaseScroll = null;
|
|
4230
|
+
}
|
|
4231
|
+
/** Con backdrop la app de atrás queda congelada; sin él, scrollea normal. */
|
|
4232
|
+
syncScrollLock(step) {
|
|
4233
|
+
const wantLock = step.style?.backdrop !== false;
|
|
4234
|
+
if (wantLock && !this.releaseScroll) {
|
|
4235
|
+
this.releaseScroll = lockScroll(this.host?.ownerDocument ?? document);
|
|
4236
|
+
} else if (!wantLock && this.releaseScroll) {
|
|
4237
|
+
this.releaseScroll();
|
|
4238
|
+
this.releaseScroll = null;
|
|
4239
|
+
}
|
|
3180
4240
|
}
|
|
3181
4241
|
render(context) {
|
|
3182
4242
|
const idx = context.nav?.stepIndex ?? 0;
|
|
@@ -3188,6 +4248,7 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3188
4248
|
const overlay = ownerDocument.createElement("div");
|
|
3189
4249
|
overlay.className = overlayClassOf(step);
|
|
3190
4250
|
this.overlay = overlay;
|
|
4251
|
+
this.syncScrollLock(step);
|
|
3191
4252
|
const card = ownerDocument.createElement("div");
|
|
3192
4253
|
card.className = "veo-modal-card";
|
|
3193
4254
|
const dismiss = (action, ctaUrl, meta) => {
|
|
@@ -3245,9 +4306,12 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
3245
4306
|
}
|
|
3246
4307
|
onLiveUpdate(step) {
|
|
3247
4308
|
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
4309
|
+
this.syncScrollLock(step);
|
|
3248
4310
|
}
|
|
3249
4311
|
destroy() {
|
|
3250
4312
|
this.overlay = null;
|
|
4313
|
+
this.releaseScroll?.();
|
|
4314
|
+
this.releaseScroll = null;
|
|
3251
4315
|
super.destroy();
|
|
3252
4316
|
}
|
|
3253
4317
|
};
|
|
@@ -3473,6 +4537,7 @@ var GuidePreviewController = class {
|
|
|
3473
4537
|
// Preview de forms: por defecto simula el envío SIN escribir props;
|
|
3474
4538
|
// el caller puede pasar `formSubmit` real (link de preview).
|
|
3475
4539
|
onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
|
|
4540
|
+
...this.input?.fetchProgress ? { fetchProgress: this.input.fetchProgress } : {},
|
|
3476
4541
|
isPreview: true,
|
|
3477
4542
|
...this.input?.formSubmit && this.input.formSubmitNote !== void 0 ? { previewNote: this.input.formSubmitNote } : {}
|
|
3478
4543
|
});
|
|
@@ -3658,9 +4723,35 @@ function matchesUrl(matcher, pageUrl) {
|
|
|
3658
4723
|
}
|
|
3659
4724
|
}
|
|
3660
4725
|
function stepMatchesPage(loc, pageUrl) {
|
|
3661
|
-
if (!loc || loc.scope === "sitewide"
|
|
4726
|
+
if (!loc || loc.scope === "sitewide") return true;
|
|
4727
|
+
if (loc.scope === "pages") {
|
|
4728
|
+
const rules = loc.rules ?? [];
|
|
4729
|
+
return rules.some((r) => pageRuleMatches(r, pageUrl));
|
|
4730
|
+
}
|
|
4731
|
+
if (!loc.pattern) return true;
|
|
3662
4732
|
return matchesUrl({ type: loc.matchType ?? "prefix", pattern: loc.pattern }, pageUrl);
|
|
3663
4733
|
}
|
|
4734
|
+
function pageRuleMatches(rule, pageUrl) {
|
|
4735
|
+
const pattern = rule.pattern.toLowerCase();
|
|
4736
|
+
const candidates = urlCandidates(pageUrl).map((c) => c.toLowerCase());
|
|
4737
|
+
switch (rule.type) {
|
|
4738
|
+
case "exact":
|
|
4739
|
+
return candidates.some((c) => c === pattern);
|
|
4740
|
+
case "prefix":
|
|
4741
|
+
return candidates.some((c) => c.startsWith(pattern));
|
|
4742
|
+
case "contains":
|
|
4743
|
+
return candidates.some((c) => c.includes(pattern));
|
|
4744
|
+
case "regex":
|
|
4745
|
+
try {
|
|
4746
|
+
const re = new RegExp(rule.pattern, "i");
|
|
4747
|
+
return candidates.some((c) => re.test(c));
|
|
4748
|
+
} catch {
|
|
4749
|
+
return false;
|
|
4750
|
+
}
|
|
4751
|
+
default:
|
|
4752
|
+
return false;
|
|
4753
|
+
}
|
|
4754
|
+
}
|
|
3664
4755
|
|
|
3665
4756
|
// src/plugins/autocapture/constants.ts
|
|
3666
4757
|
var DEFAULT_AUTOCAPTURE_CONFIG = {
|
|
@@ -3795,6 +4886,6 @@ function buildSelectorPath(element, maxAncestors = 5) {
|
|
|
3795
4886
|
return parts.join(" > ");
|
|
3796
4887
|
}
|
|
3797
4888
|
|
|
3798
|
-
export { ALWAYS_BLOCK_SELECTORS, BadgeRenderer, BannerRenderer, CompositeRenderer, CustomRenderer, DEFAULT_AUTOCAPTURE_CONFIG, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, DEFAULT_TRACKER_MAX_RETRIES, FREQUENCY_CACHE_KEY_PREFIX, FREQUENCY_CACHE_MAX_ENTRIES, FREQUENCY_CACHE_TTL_MS, FormRenderer, InlineCustomRenderer, InlineFormRenderer, InlineRenderer, MAX_ANCESTORS, ModalRenderer, PRIORITY_ATTRIBUTES, SENSITIVE_ATTRIBUTES, TooltipRenderer, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS, WALKTHROUGH_STATE_KEY_PREFIX, WalkthroughRenderer, activationDelayMs, buildSelectorPath, clearBuilderToken, clearPreviewToken, closeGuidePreview, filterHumanClasses, getPreviewToken, hasDocument, hasLocalStorage, hasNavigatorSendBeacon, hasWindow, isAutomaticActivation, isBuilderMode, isDeviceAllowed, previewGuide, resolveBuilderContext, resolveBuilderToken, stepMatchesPage, uuidv7 };
|
|
3799
|
-
//# sourceMappingURL=chunk-
|
|
3800
|
-
//# sourceMappingURL=chunk-
|
|
4889
|
+
export { ALWAYS_BLOCK_SELECTORS, BadgeRenderer, BannerRenderer, CompositeRenderer, CustomRenderer, DEFAULT_AUTOCAPTURE_CONFIG, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, DEFAULT_TRACKER_MAX_RETRIES, FREQUENCY_CACHE_KEY_PREFIX, FREQUENCY_CACHE_MAX_ENTRIES, FREQUENCY_CACHE_TTL_MS, FormRenderer, GUIDE_STYLES, InlineCustomRenderer, InlineFormRenderer, InlineRenderer, MAX_ANCESTORS, ModalRenderer, PRIORITY_ATTRIBUTES, RAW_BLOCK_ATTR, SENSITIVE_ATTRIBUTES, TooltipRenderer, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS, WALKTHROUGH_STATE_KEY_PREFIX, WalkthroughRenderer, activationDelayMs, applyBlockStyle, buildContentBlock, buildSelectorPath, clearBuilderToken, clearPreviewToken, closeGuidePreview, filterHumanClasses, getPreviewToken, groupLayout, hasDocument, hasLocalStorage, hasNavigatorSendBeacon, hasWindow, isAutomaticActivation, isBuilderMode, isDeviceAllowed, isSnoozed, previewGuide, resolveBuilderContext, resolveBuilderToken, stepMatchesPage, uuidv7 };
|
|
4890
|
+
//# sourceMappingURL=chunk-NGP7IJVE.mjs.map
|
|
4891
|
+
//# sourceMappingURL=chunk-NGP7IJVE.mjs.map
|