veo-sdk 0.3.16 → 0.4.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-S6ZODU2M.mjs +5 -0
- package/dist/{builder-EQYOMQFY.mjs.map → builder-S6ZODU2M.mjs.map} +1 -1
- package/dist/builder.cjs +1014 -217
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.d.cts +38 -2
- package/dist/builder.d.ts +38 -2
- package/dist/builder.mjs +4 -4
- package/dist/{chunk-YHUPUTWH.mjs → chunk-F2ZWZUTR.mjs} +719 -186
- package/dist/chunk-F2ZWZUTR.mjs.map +1 -0
- package/dist/{chunk-IEHGG5NL.mjs → chunk-KLN46YQI.mjs} +67 -11
- package/dist/chunk-KLN46YQI.mjs.map +1 -0
- package/dist/{chunk-QCIT7CGE.mjs → chunk-ULH64GMX.mjs} +269 -5
- package/dist/chunk-ULH64GMX.mjs.map +1 -0
- package/dist/{guide-preview-DewCTa-B.d.cts → guide-preview-D_xhRVLe.d.cts} +20 -2
- package/dist/{guide-preview-DewCTa-B.d.ts → guide-preview-D_xhRVLe.d.ts} +20 -2
- package/dist/index.cjs +1103 -218
- 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 +7 -6
- package/dist/index.mjs.map +1 -1
- package/dist/veo-builder.js +125 -9
- package/dist/veo-builder.js.map +1 -1
- package/dist/veo-guides.js +86 -4
- package/dist/veo-guides.js.map +1 -1
- package/dist/veo.js +2 -2
- package/dist/veo.js.map +1 -1
- package/package.json +1 -1
- package/dist/builder-EQYOMQFY.mjs +0 -5
- package/dist/chunk-IEHGG5NL.mjs.map +0 -1
- package/dist/chunk-QCIT7CGE.mjs.map +0 -1
- package/dist/chunk-YHUPUTWH.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -113,7 +113,11 @@ function readFreshFromLocal() {
|
|
|
113
113
|
}
|
|
114
114
|
function urlParams() {
|
|
115
115
|
try {
|
|
116
|
-
|
|
116
|
+
const search = new URLSearchParams(window.location.search);
|
|
117
|
+
if (search.get(BUILDER_TOKEN_PARAM)) return search;
|
|
118
|
+
const hash = window.location.hash.replace(/^#/, "");
|
|
119
|
+
if (hash.includes(`${BUILDER_TOKEN_PARAM}=`)) return new URLSearchParams(hash);
|
|
120
|
+
return search;
|
|
117
121
|
} catch {
|
|
118
122
|
return null;
|
|
119
123
|
}
|
|
@@ -178,7 +182,7 @@ var init_builder_token = __esm({
|
|
|
178
182
|
init_builder_constants();
|
|
179
183
|
CTX_KEY = "veo:builder_ctx";
|
|
180
184
|
LS_PREFIX = "veo:builder_ctx:";
|
|
181
|
-
PERSIST_TTL_MS =
|
|
185
|
+
PERSIST_TTL_MS = 30 * 60 * 1e3;
|
|
182
186
|
}
|
|
183
187
|
});
|
|
184
188
|
|
|
@@ -374,6 +378,118 @@ var init_constants2 = __esm({
|
|
|
374
378
|
}
|
|
375
379
|
});
|
|
376
380
|
|
|
381
|
+
// src/plugins/guides/rich-text.ts
|
|
382
|
+
function renderRichText(html, doc) {
|
|
383
|
+
const fragment = doc.createDocumentFragment();
|
|
384
|
+
if (typeof html !== "string" || html.length === 0) return fragment;
|
|
385
|
+
let parsed;
|
|
386
|
+
try {
|
|
387
|
+
parsed = new DOMParser().parseFromString(
|
|
388
|
+
html.length > MAX_INPUT_LEN ? html.slice(0, MAX_INPUT_LEN) : html,
|
|
389
|
+
"text/html"
|
|
390
|
+
);
|
|
391
|
+
} catch {
|
|
392
|
+
fragment.appendChild(doc.createTextNode(html));
|
|
393
|
+
return fragment;
|
|
394
|
+
}
|
|
395
|
+
const budget = { nodes: 0, exceeded: false };
|
|
396
|
+
for (const child of Array.from(parsed.body.childNodes)) {
|
|
397
|
+
const rebuilt = rebuildNode(child, doc, 0, budget);
|
|
398
|
+
if (rebuilt) fragment.appendChild(rebuilt);
|
|
399
|
+
}
|
|
400
|
+
if (budget.exceeded) {
|
|
401
|
+
const plain = doc.createDocumentFragment();
|
|
402
|
+
plain.appendChild(doc.createTextNode(parsed.body.textContent ?? ""));
|
|
403
|
+
return plain;
|
|
404
|
+
}
|
|
405
|
+
return fragment;
|
|
406
|
+
}
|
|
407
|
+
function rebuildNode(node, doc, depth, budget) {
|
|
408
|
+
if (budget.exceeded) return null;
|
|
409
|
+
if (++budget.nodes > MAX_NODES || depth > MAX_DEPTH) {
|
|
410
|
+
budget.exceeded = true;
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
414
|
+
return doc.createTextNode(node.textContent ?? "");
|
|
415
|
+
}
|
|
416
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return null;
|
|
417
|
+
const el = node;
|
|
418
|
+
if (DROP_TAGS.has(el.tagName)) return null;
|
|
419
|
+
const mapped = ALLOWED_TAGS[el.tagName];
|
|
420
|
+
if (!mapped) {
|
|
421
|
+
const frag = doc.createDocumentFragment();
|
|
422
|
+
for (const child of Array.from(el.childNodes)) {
|
|
423
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
424
|
+
if (rebuilt2) frag.appendChild(rebuilt2);
|
|
425
|
+
}
|
|
426
|
+
return frag;
|
|
427
|
+
}
|
|
428
|
+
if (mapped === "a") {
|
|
429
|
+
const href = el.getAttribute("href") ?? "";
|
|
430
|
+
if (!isSafeUrl(href)) {
|
|
431
|
+
const frag = doc.createDocumentFragment();
|
|
432
|
+
for (const child of Array.from(el.childNodes)) {
|
|
433
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
434
|
+
if (rebuilt2) frag.appendChild(rebuilt2);
|
|
435
|
+
}
|
|
436
|
+
return frag;
|
|
437
|
+
}
|
|
438
|
+
const a = doc.createElement("a");
|
|
439
|
+
a.setAttribute("href", href);
|
|
440
|
+
a.setAttribute("target", "_blank");
|
|
441
|
+
a.setAttribute("rel", "noopener noreferrer nofollow");
|
|
442
|
+
for (const child of Array.from(el.childNodes)) {
|
|
443
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
444
|
+
if (rebuilt2) a.appendChild(rebuilt2);
|
|
445
|
+
}
|
|
446
|
+
return a;
|
|
447
|
+
}
|
|
448
|
+
const rebuilt = doc.createElement(mapped);
|
|
449
|
+
for (const child of Array.from(el.childNodes)) {
|
|
450
|
+
const childNode = rebuildNode(child, doc, depth + 1, budget);
|
|
451
|
+
if (childNode) rebuilt.appendChild(childNode);
|
|
452
|
+
}
|
|
453
|
+
return rebuilt;
|
|
454
|
+
}
|
|
455
|
+
var ALLOWED_TAGS, DROP_TAGS, MAX_INPUT_LEN, MAX_DEPTH, MAX_NODES;
|
|
456
|
+
var init_rich_text = __esm({
|
|
457
|
+
"src/plugins/guides/rich-text.ts"() {
|
|
458
|
+
init_block_builder();
|
|
459
|
+
ALLOWED_TAGS = {
|
|
460
|
+
P: "p",
|
|
461
|
+
BR: "br",
|
|
462
|
+
STRONG: "strong",
|
|
463
|
+
B: "strong",
|
|
464
|
+
EM: "em",
|
|
465
|
+
I: "em",
|
|
466
|
+
U: "u",
|
|
467
|
+
S: "s",
|
|
468
|
+
UL: "ul",
|
|
469
|
+
OL: "ol",
|
|
470
|
+
LI: "li",
|
|
471
|
+
A: "a"
|
|
472
|
+
};
|
|
473
|
+
DROP_TAGS = /* @__PURE__ */ new Set([
|
|
474
|
+
"SCRIPT",
|
|
475
|
+
"STYLE",
|
|
476
|
+
"TEMPLATE",
|
|
477
|
+
"IFRAME",
|
|
478
|
+
"OBJECT",
|
|
479
|
+
"EMBED",
|
|
480
|
+
"NOSCRIPT",
|
|
481
|
+
"TITLE",
|
|
482
|
+
"TEXTAREA",
|
|
483
|
+
"SELECT",
|
|
484
|
+
"SVG",
|
|
485
|
+
"MATH"
|
|
486
|
+
]);
|
|
487
|
+
MAX_INPUT_LEN = 16 * 1024;
|
|
488
|
+
MAX_DEPTH = 20;
|
|
489
|
+
MAX_NODES = 1e3;
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
377
493
|
// src/plugins/guides/block-builder.ts
|
|
378
494
|
function buildStepContent(step, doc, callbacks) {
|
|
379
495
|
const container = doc.createElement("div");
|
|
@@ -468,9 +584,13 @@ function buildContentBlock(block, doc) {
|
|
|
468
584
|
return el;
|
|
469
585
|
}
|
|
470
586
|
case "text": {
|
|
471
|
-
const el = doc.createElement("
|
|
587
|
+
const el = doc.createElement("div");
|
|
472
588
|
el.className = "veo-guide-text";
|
|
473
|
-
|
|
589
|
+
if (typeof block.html === "string" && block.html) {
|
|
590
|
+
el.appendChild(renderRichText(block.html, doc));
|
|
591
|
+
} else {
|
|
592
|
+
el.textContent = typeof block.text === "string" ? block.text : "";
|
|
593
|
+
}
|
|
474
594
|
applyBlockStyle(el, block.style, "text");
|
|
475
595
|
return el;
|
|
476
596
|
}
|
|
@@ -496,7 +616,11 @@ function buildButtonBlock(block, doc, callbacks) {
|
|
|
496
616
|
btn.addEventListener("click", () => {
|
|
497
617
|
const action = block.action ?? "dismiss";
|
|
498
618
|
const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
|
|
499
|
-
|
|
619
|
+
const meta = {
|
|
620
|
+
...typeof block.id === "string" && block.id ? { buttonId: block.id } : {},
|
|
621
|
+
...typeof block.text === "string" && block.text ? { buttonText: block.text } : {}
|
|
622
|
+
};
|
|
623
|
+
callbacks.onCtaClick(action, url, Object.keys(meta).length ? meta : void 0);
|
|
500
624
|
});
|
|
501
625
|
return btn;
|
|
502
626
|
}
|
|
@@ -506,6 +630,10 @@ function clampNum(n, min, max) {
|
|
|
506
630
|
function applyBlockStyle(el, style, kind) {
|
|
507
631
|
if (!style || typeof style !== "object") return;
|
|
508
632
|
const s = style;
|
|
633
|
+
if (kind === "image" && s.bleed === true) {
|
|
634
|
+
el.classList.add("veo-guide-image--bleed");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
509
637
|
const align = typeof s.align === "string" ? s.align : null;
|
|
510
638
|
if (align && (align === "left" || align === "center" || align === "right")) {
|
|
511
639
|
if (kind === "text") el.style.textAlign = align;
|
|
@@ -556,6 +684,7 @@ var BLOCK_ALIGN_SELF;
|
|
|
556
684
|
var init_block_builder = __esm({
|
|
557
685
|
"src/plugins/guides/block-builder.ts"() {
|
|
558
686
|
init_constants2();
|
|
687
|
+
init_rich_text();
|
|
559
688
|
BLOCK_ALIGN_SELF = {
|
|
560
689
|
left: "flex-start",
|
|
561
690
|
center: "center",
|
|
@@ -564,83 +693,6 @@ var init_block_builder = __esm({
|
|
|
564
693
|
}
|
|
565
694
|
});
|
|
566
695
|
|
|
567
|
-
// src/plugins/guides/walkthrough-block-builder.ts
|
|
568
|
-
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
569
|
-
const container = doc.createElement("div");
|
|
570
|
-
container.className = "veo-guide-content";
|
|
571
|
-
const counter = doc.createElement("div");
|
|
572
|
-
counter.className = "veo-walkthrough-counter";
|
|
573
|
-
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
574
|
-
container.appendChild(counter);
|
|
575
|
-
const progress = doc.createElement("div");
|
|
576
|
-
progress.className = "veo-walkthrough-progress";
|
|
577
|
-
for (let i = 0; i < totalSteps; i++) {
|
|
578
|
-
const dot = doc.createElement("span");
|
|
579
|
-
dot.className = "veo-walkthrough-progress-dot";
|
|
580
|
-
if (i < stepIndex) dot.classList.add("completed");
|
|
581
|
-
if (i === stepIndex) dot.classList.add("active");
|
|
582
|
-
progress.appendChild(dot);
|
|
583
|
-
}
|
|
584
|
-
container.appendChild(progress);
|
|
585
|
-
if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
|
|
586
|
-
const img = doc.createElement("img");
|
|
587
|
-
img.className = "veo-guide-image";
|
|
588
|
-
img.src = step.imageUrl;
|
|
589
|
-
img.alt = typeof step.title === "string" ? step.title : "";
|
|
590
|
-
container.appendChild(img);
|
|
591
|
-
}
|
|
592
|
-
if (typeof step.title === "string" && step.title) {
|
|
593
|
-
const heading = doc.createElement("h2");
|
|
594
|
-
heading.className = "veo-guide-title";
|
|
595
|
-
heading.textContent = step.title;
|
|
596
|
-
container.appendChild(heading);
|
|
597
|
-
}
|
|
598
|
-
if (typeof step.content === "string" && step.content) {
|
|
599
|
-
const paragraph = doc.createElement("p");
|
|
600
|
-
paragraph.className = "veo-guide-text";
|
|
601
|
-
paragraph.textContent = step.content;
|
|
602
|
-
container.appendChild(paragraph);
|
|
603
|
-
}
|
|
604
|
-
const actions = doc.createElement("div");
|
|
605
|
-
actions.className = "veo-walkthrough-actions";
|
|
606
|
-
const skipBtn = doc.createElement("button");
|
|
607
|
-
skipBtn.type = "button";
|
|
608
|
-
skipBtn.className = "veo-walkthrough-skip";
|
|
609
|
-
skipBtn.textContent = "Omitir";
|
|
610
|
-
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
611
|
-
actions.appendChild(skipBtn);
|
|
612
|
-
const rightGroup = doc.createElement("div");
|
|
613
|
-
rightGroup.className = "veo-walkthrough-actions-right";
|
|
614
|
-
if (stepIndex > 0) {
|
|
615
|
-
const backBtn = doc.createElement("button");
|
|
616
|
-
backBtn.type = "button";
|
|
617
|
-
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
618
|
-
backBtn.textContent = "Atr\xE1s";
|
|
619
|
-
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
620
|
-
rightGroup.appendChild(backBtn);
|
|
621
|
-
}
|
|
622
|
-
const isLastStep = stepIndex === totalSteps - 1;
|
|
623
|
-
const primaryBtn = doc.createElement("button");
|
|
624
|
-
primaryBtn.type = "button";
|
|
625
|
-
primaryBtn.className = "veo-guide-cta";
|
|
626
|
-
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
627
|
-
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
628
|
-
primaryBtn.addEventListener("click", () => {
|
|
629
|
-
if (isLastStep) callbacks.onComplete();
|
|
630
|
-
else callbacks.onNext();
|
|
631
|
-
});
|
|
632
|
-
rightGroup.appendChild(primaryBtn);
|
|
633
|
-
actions.appendChild(rightGroup);
|
|
634
|
-
container.appendChild(actions);
|
|
635
|
-
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
636
|
-
return container;
|
|
637
|
-
}
|
|
638
|
-
var init_walkthrough_block_builder = __esm({
|
|
639
|
-
"src/plugins/guides/walkthrough-block-builder.ts"() {
|
|
640
|
-
init_block_builder();
|
|
641
|
-
}
|
|
642
|
-
});
|
|
643
|
-
|
|
644
696
|
// src/plugins/guides/guide-design.ts
|
|
645
697
|
function clamp(n, min, max) {
|
|
646
698
|
return Math.min(max, Math.max(min, n));
|
|
@@ -674,6 +726,9 @@ function applyDesignVars(host, style) {
|
|
|
674
726
|
if (typeof s.width === "number" && Number.isFinite(s.width)) {
|
|
675
727
|
host.style.setProperty("--veo-width", `${clamp(s.width, 220, 720)}px`);
|
|
676
728
|
}
|
|
729
|
+
if (typeof s.height === "number" && Number.isFinite(s.height)) {
|
|
730
|
+
host.style.setProperty("--veo-min-h", `${clamp(s.height, 120, 900)}px`);
|
|
731
|
+
}
|
|
677
732
|
if (s.align === "left" || s.align === "center" || s.align === "right") {
|
|
678
733
|
host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[s.align]);
|
|
679
734
|
}
|
|
@@ -803,6 +858,77 @@ var init_guide_design = __esm({
|
|
|
803
858
|
}
|
|
804
859
|
});
|
|
805
860
|
|
|
861
|
+
// src/plugins/guides/inline-host.ts
|
|
862
|
+
function readInlinePosition(style) {
|
|
863
|
+
const p = style?.inlinePosition;
|
|
864
|
+
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
865
|
+
}
|
|
866
|
+
function insertHost(anchor, host, position) {
|
|
867
|
+
switch (position) {
|
|
868
|
+
case "before":
|
|
869
|
+
anchor.before(host);
|
|
870
|
+
break;
|
|
871
|
+
case "after":
|
|
872
|
+
anchor.after(host);
|
|
873
|
+
break;
|
|
874
|
+
case "prepend":
|
|
875
|
+
anchor.prepend(host);
|
|
876
|
+
break;
|
|
877
|
+
case "append":
|
|
878
|
+
anchor.append(host);
|
|
879
|
+
break;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function keepHostAttached(host, selector, position) {
|
|
883
|
+
const id = window.setInterval(() => {
|
|
884
|
+
if (host.isConnected) return;
|
|
885
|
+
const anchor = document.querySelector(selector);
|
|
886
|
+
if (anchor) insertHost(anchor, host, position);
|
|
887
|
+
}, 1e3);
|
|
888
|
+
return () => window.clearInterval(id);
|
|
889
|
+
}
|
|
890
|
+
var init_inline_host = __esm({
|
|
891
|
+
"src/plugins/guides/inline-host.ts"() {
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
// src/plugins/guides/wait-for-element.ts
|
|
896
|
+
function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
|
|
897
|
+
return new Promise((resolve) => {
|
|
898
|
+
const safeQuery = () => {
|
|
899
|
+
try {
|
|
900
|
+
return document.querySelector(selector);
|
|
901
|
+
} catch {
|
|
902
|
+
return null;
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
const existing = safeQuery();
|
|
906
|
+
if (existing) {
|
|
907
|
+
resolve(existing);
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
let resolved = false;
|
|
911
|
+
const finish = (el) => {
|
|
912
|
+
if (resolved) return;
|
|
913
|
+
resolved = true;
|
|
914
|
+
observer.disconnect();
|
|
915
|
+
clearTimeout(timer);
|
|
916
|
+
resolve(el);
|
|
917
|
+
};
|
|
918
|
+
const observer = new MutationObserver(() => {
|
|
919
|
+
const el = safeQuery();
|
|
920
|
+
if (el) finish(el);
|
|
921
|
+
});
|
|
922
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
923
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
var init_wait_for_element = __esm({
|
|
927
|
+
"src/plugins/guides/wait-for-element.ts"() {
|
|
928
|
+
init_constants2();
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
|
|
806
932
|
// src/plugins/guides/styles.ts
|
|
807
933
|
var GUIDE_STYLES;
|
|
808
934
|
var init_styles = __esm({
|
|
@@ -828,6 +954,13 @@ var init_styles = __esm({
|
|
|
828
954
|
z-index: ${GUIDE_Z_INDEX};
|
|
829
955
|
animation: veo-fade-in 180ms ease-out;
|
|
830
956
|
}
|
|
957
|
+
/* Sin backdrop (style.backdrop === false): la app queda usable detr\xE1s; solo la
|
|
958
|
+
tarjeta captura el mouse. El click-en-backdrop deja de cerrar (no hay backdrop). */
|
|
959
|
+
.veo-modal-overlay--none {
|
|
960
|
+
background: transparent;
|
|
961
|
+
pointer-events: none;
|
|
962
|
+
}
|
|
963
|
+
.veo-modal-overlay--none .veo-modal-card { pointer-events: auto; }
|
|
831
964
|
/*
|
|
832
965
|
* Posici\xF3n libre/preset: --veo-pos-x/y son porcentajes (default 50% = centro).
|
|
833
966
|
* El truco translate(-pos) alinea la MISMA fracci\xF3n de la tarjeta con esa
|
|
@@ -844,6 +977,7 @@ var init_styles = __esm({
|
|
|
844
977
|
border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
|
|
845
978
|
padding: var(--veo-pad, 24px);
|
|
846
979
|
max-width: var(--veo-width); width: 90%;
|
|
980
|
+
min-height: var(--veo-min-h, auto);
|
|
847
981
|
box-shadow: var(--veo-shadow);
|
|
848
982
|
animation: veo-fade-in 180ms ease-out;
|
|
849
983
|
}
|
|
@@ -859,6 +993,14 @@ var init_styles = __esm({
|
|
|
859
993
|
}
|
|
860
994
|
.veo-banner-top { top: 0; }
|
|
861
995
|
.veo-banner-bottom { bottom: 0; }
|
|
996
|
+
/* Banner EMBEBIDO en un contenedor (step.selector): fluye dentro del contenedor
|
|
997
|
+
y empuja su contenido, en vez de flotar fijo sobre la pantalla. */
|
|
998
|
+
.veo-banner-embedded {
|
|
999
|
+
position: static;
|
|
1000
|
+
left: auto; right: auto;
|
|
1001
|
+
width: 100%;
|
|
1002
|
+
border-radius: var(--veo-radius, 0);
|
|
1003
|
+
}
|
|
862
1004
|
|
|
863
1005
|
.veo-tooltip {
|
|
864
1006
|
position: absolute;
|
|
@@ -912,6 +1054,21 @@ var init_styles = __esm({
|
|
|
912
1054
|
border-radius: var(--veo-image-radius, 8px);
|
|
913
1055
|
margin: var(--veo-image-mt, 0) 0 var(--veo-image-mb, 12px);
|
|
914
1056
|
}
|
|
1057
|
+
/* Imagen A SANGRE: rompe el padding de la tarjeta y ocupa el ancho completo
|
|
1058
|
+
(hero estilo anuncio). Como primer bloque, hereda el redondeo superior. */
|
|
1059
|
+
.veo-guide-image--bleed {
|
|
1060
|
+
width: calc(100% + var(--veo-pad, 24px) * 2);
|
|
1061
|
+
max-width: none;
|
|
1062
|
+
max-height: 280px;
|
|
1063
|
+
align-self: auto;
|
|
1064
|
+
border-radius: 0;
|
|
1065
|
+
margin: 0 calc(var(--veo-pad, 24px) * -1) 12px;
|
|
1066
|
+
}
|
|
1067
|
+
.veo-guide-content > .veo-guide-image--bleed:first-child {
|
|
1068
|
+
margin-top: calc(var(--veo-pad, 24px) * -1);
|
|
1069
|
+
border-radius: calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px))
|
|
1070
|
+
calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px)) 0 0;
|
|
1071
|
+
}
|
|
915
1072
|
.veo-guide-title {
|
|
916
1073
|
font-size: var(--veo-title-size, 18px); font-weight: 600; line-height: 1.3;
|
|
917
1074
|
text-align: var(--veo-title-align, left);
|
|
@@ -924,6 +1081,18 @@ var init_styles = __esm({
|
|
|
924
1081
|
margin: var(--veo-text-mt, 0) 0 var(--veo-text-mb, 16px);
|
|
925
1082
|
color: var(--veo-text-color, var(--veo-text-secondary));
|
|
926
1083
|
}
|
|
1084
|
+
/* Rich text dentro de un bloque de texto (p/listas/links/\xE9nfasis). */
|
|
1085
|
+
.veo-guide-text p { margin: 0 0 8px; }
|
|
1086
|
+
.veo-guide-text p:last-child { margin-bottom: 0; }
|
|
1087
|
+
.veo-guide-text ul, .veo-guide-text ol { margin: 0 0 8px; padding-left: 20px; }
|
|
1088
|
+
.veo-guide-text ul { list-style: disc; }
|
|
1089
|
+
.veo-guide-text ol { list-style: decimal; }
|
|
1090
|
+
.veo-guide-text li { margin: 2px 0; display: list-item; }
|
|
1091
|
+
.veo-guide-text a { color: var(--veo-primary); text-decoration: underline; cursor: pointer; }
|
|
1092
|
+
.veo-guide-text strong { font-weight: 600; }
|
|
1093
|
+
.veo-guide-text em { font-style: italic; }
|
|
1094
|
+
.veo-guide-text u { text-decoration: underline; }
|
|
1095
|
+
.veo-guide-text s { text-decoration: line-through; }
|
|
927
1096
|
.veo-guide-actions {
|
|
928
1097
|
display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
|
|
929
1098
|
}
|
|
@@ -1062,6 +1231,44 @@ var init_styles = __esm({
|
|
|
1062
1231
|
}
|
|
1063
1232
|
.veo-walkthrough-skip:hover { color: var(--veo-text); }
|
|
1064
1233
|
|
|
1234
|
+
/* \u2500\u2500 Badge (elemento inyectado junto al ancla que abre un tooltip) \u2500\u2500 */
|
|
1235
|
+
.veo-badge {
|
|
1236
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1237
|
+
border: none; padding: 0; margin: 0 4px;
|
|
1238
|
+
background: none; cursor: pointer;
|
|
1239
|
+
line-height: 1; vertical-align: middle;
|
|
1240
|
+
font-family: inherit;
|
|
1241
|
+
}
|
|
1242
|
+
.veo-badge:focus-visible { outline: 2px solid var(--veo-primary); outline-offset: 2px; }
|
|
1243
|
+
.veo-badge--icon {
|
|
1244
|
+
border-radius: 50%;
|
|
1245
|
+
background: color-mix(in srgb, var(--veo-primary) 14%, transparent);
|
|
1246
|
+
color: var(--veo-primary);
|
|
1247
|
+
font-weight: 600;
|
|
1248
|
+
}
|
|
1249
|
+
.veo-badge--dot {
|
|
1250
|
+
border-radius: 50%;
|
|
1251
|
+
background: var(--veo-primary);
|
|
1252
|
+
animation: veo-badge-pulse 2s ease-out infinite;
|
|
1253
|
+
}
|
|
1254
|
+
.veo-badge--pill {
|
|
1255
|
+
border-radius: 999px;
|
|
1256
|
+
background: var(--veo-primary);
|
|
1257
|
+
color: #fff;
|
|
1258
|
+
font-weight: 600;
|
|
1259
|
+
padding: 3px 9px;
|
|
1260
|
+
white-space: nowrap;
|
|
1261
|
+
}
|
|
1262
|
+
.veo-badge--image img { display: block; border-radius: 4px; object-fit: cover; }
|
|
1263
|
+
/* El tooltip del badge usa strategy fixed (el host vive dentro del flujo del
|
|
1264
|
+
cliente; un absoluto se recortar\xEDa con overflow de ancestros). */
|
|
1265
|
+
.veo-badge-tooltip { position: fixed; }
|
|
1266
|
+
@keyframes veo-badge-pulse {
|
|
1267
|
+
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--veo-primary) 45%, transparent); }
|
|
1268
|
+
70% { box-shadow: 0 0 0 7px transparent; }
|
|
1269
|
+
100% { box-shadow: 0 0 0 0 transparent; }
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1065
1272
|
.veo-custom-floating {
|
|
1066
1273
|
position: fixed;
|
|
1067
1274
|
z-index: ${GUIDE_Z_INDEX};
|
|
@@ -1180,6 +1387,14 @@ var init_base_renderer = __esm({
|
|
|
1180
1387
|
registerCleanup(fn) {
|
|
1181
1388
|
this.cleanups.push(fn);
|
|
1182
1389
|
}
|
|
1390
|
+
/**
|
|
1391
|
+
* Host de la guía montada (o `null` si aún no se montó / ya se destruyó).
|
|
1392
|
+
* Lo usa el modo builder para adjuntar manipulación directa (drag/resize)
|
|
1393
|
+
* sobre el shadow root abierto sin tocar los renderers.
|
|
1394
|
+
*/
|
|
1395
|
+
hostElement() {
|
|
1396
|
+
return this.host;
|
|
1397
|
+
}
|
|
1183
1398
|
/** Remueve el host del DOM y corre todas las funciones de cleanup. */
|
|
1184
1399
|
destroy() {
|
|
1185
1400
|
for (const fn of this.cleanups) {
|
|
@@ -1199,26 +1414,402 @@ var init_base_renderer = __esm({
|
|
|
1199
1414
|
}
|
|
1200
1415
|
});
|
|
1201
1416
|
|
|
1417
|
+
// src/plugins/guides/renderers/floating-arrow.ts
|
|
1418
|
+
function positionArrow(arrowEl, placement, data) {
|
|
1419
|
+
const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
|
|
1420
|
+
for (const prop of ["top", "bottom", "left", "right"]) {
|
|
1421
|
+
arrowEl.style.setProperty(prop, "");
|
|
1422
|
+
}
|
|
1423
|
+
if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
|
|
1424
|
+
if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
|
|
1425
|
+
arrowEl.style.setProperty(side, "-6px");
|
|
1426
|
+
}
|
|
1427
|
+
var STATIC_SIDE;
|
|
1428
|
+
var init_floating_arrow = __esm({
|
|
1429
|
+
"src/plugins/guides/renderers/floating-arrow.ts"() {
|
|
1430
|
+
STATIC_SIDE = {
|
|
1431
|
+
top: "bottom",
|
|
1432
|
+
bottom: "top",
|
|
1433
|
+
left: "right",
|
|
1434
|
+
right: "left"
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
function readBadgeConfig(step) {
|
|
1439
|
+
const raw = step.style?.badge;
|
|
1440
|
+
if (!raw || typeof raw !== "object") return { kind: "icon" };
|
|
1441
|
+
const b = raw;
|
|
1442
|
+
const kind = b.kind === "dot" || b.kind === "pill" || b.kind === "image" || b.kind === "icon" ? b.kind : "icon";
|
|
1443
|
+
return {
|
|
1444
|
+
kind,
|
|
1445
|
+
icon: typeof b.icon === "string" ? b.icon : null,
|
|
1446
|
+
text: typeof b.text === "string" ? b.text : null,
|
|
1447
|
+
imageUrl: typeof b.imageUrl === "string" ? b.imageUrl : null,
|
|
1448
|
+
color: typeof b.color === "string" && COLOR_RE.test(b.color.trim()) ? b.color.trim() : null,
|
|
1449
|
+
size: typeof b.size === "number" && Number.isFinite(b.size) ? b.size : null
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
function clamp2(n, min, max) {
|
|
1453
|
+
return Math.min(max, Math.max(min, n));
|
|
1454
|
+
}
|
|
1455
|
+
function buildBadgeElement(config, doc) {
|
|
1456
|
+
const btn = doc.createElement("button");
|
|
1457
|
+
btn.type = "button";
|
|
1458
|
+
btn.className = `veo-badge veo-badge--${config.kind}`;
|
|
1459
|
+
btn.setAttribute("aria-label", "M\xE1s informaci\xF3n");
|
|
1460
|
+
const size = clamp2(config.size ?? 18, 8, 48);
|
|
1461
|
+
switch (config.kind) {
|
|
1462
|
+
case "dot":
|
|
1463
|
+
btn.style.width = `${size}px`;
|
|
1464
|
+
btn.style.height = `${size}px`;
|
|
1465
|
+
if (config.color) btn.style.background = config.color;
|
|
1466
|
+
break;
|
|
1467
|
+
case "pill": {
|
|
1468
|
+
btn.textContent = config.text?.slice(0, 24) || "Nuevo";
|
|
1469
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 24)}px`;
|
|
1470
|
+
if (config.color) btn.style.background = config.color;
|
|
1471
|
+
break;
|
|
1472
|
+
}
|
|
1473
|
+
case "image": {
|
|
1474
|
+
if (config.imageUrl && isSafeUrl(config.imageUrl)) {
|
|
1475
|
+
const img = doc.createElement("img");
|
|
1476
|
+
img.src = config.imageUrl;
|
|
1477
|
+
img.alt = "";
|
|
1478
|
+
img.style.width = `${size}px`;
|
|
1479
|
+
img.style.height = `${size}px`;
|
|
1480
|
+
btn.appendChild(img);
|
|
1481
|
+
} else {
|
|
1482
|
+
btn.className = "veo-badge veo-badge--icon";
|
|
1483
|
+
btn.textContent = "?";
|
|
1484
|
+
btn.style.width = `${size}px`;
|
|
1485
|
+
btn.style.height = `${size}px`;
|
|
1486
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1487
|
+
}
|
|
1488
|
+
break;
|
|
1489
|
+
}
|
|
1490
|
+
default: {
|
|
1491
|
+
btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
|
|
1492
|
+
btn.style.width = `${size}px`;
|
|
1493
|
+
btn.style.height = `${size}px`;
|
|
1494
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1495
|
+
if (config.color) btn.style.color = config.color;
|
|
1496
|
+
break;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return btn;
|
|
1500
|
+
}
|
|
1501
|
+
var HOVER_CLOSE_DELAY_MS, BadgeRenderer;
|
|
1502
|
+
var init_badge_renderer = __esm({
|
|
1503
|
+
"src/plugins/guides/renderers/badge-renderer.ts"() {
|
|
1504
|
+
init_block_builder();
|
|
1505
|
+
init_guide_design();
|
|
1506
|
+
init_inline_host();
|
|
1507
|
+
init_wait_for_element();
|
|
1508
|
+
init_base_renderer();
|
|
1509
|
+
init_floating_arrow();
|
|
1510
|
+
HOVER_CLOSE_DELAY_MS = 150;
|
|
1511
|
+
BadgeRenderer = class extends BaseRenderer {
|
|
1512
|
+
constructor() {
|
|
1513
|
+
super(...arguments);
|
|
1514
|
+
this.tooltip = null;
|
|
1515
|
+
this.arrowEl = null;
|
|
1516
|
+
this.badgeBtn = null;
|
|
1517
|
+
this.open = false;
|
|
1518
|
+
this.shownEmitted = false;
|
|
1519
|
+
this.trigger = "hover";
|
|
1520
|
+
this.side = "top";
|
|
1521
|
+
this.closeTimer = null;
|
|
1522
|
+
this.stopFloat = null;
|
|
1523
|
+
}
|
|
1524
|
+
async render(context) {
|
|
1525
|
+
const step = context.guide.guideSteps[0];
|
|
1526
|
+
if (!step) return;
|
|
1527
|
+
const selector = step.selector ?? context.guide.activationRules.selector;
|
|
1528
|
+
if (typeof selector !== "string" || selector.length === 0) return;
|
|
1529
|
+
const anchor = await waitForElement(selector);
|
|
1530
|
+
if (!anchor) return;
|
|
1531
|
+
const { host, root } = this.createHost();
|
|
1532
|
+
const ownerDocument = root.ownerDocument ?? document;
|
|
1533
|
+
this.applyDesign(step.style);
|
|
1534
|
+
this.trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
|
|
1535
|
+
const rawSide = step.style?.tooltipPlacement;
|
|
1536
|
+
this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
|
|
1537
|
+
const position = readInlinePosition(step.style);
|
|
1538
|
+
host.style.display = "inline-flex";
|
|
1539
|
+
host.style.verticalAlign = "middle";
|
|
1540
|
+
const badge = buildBadgeElement(readBadgeConfig(step), ownerDocument);
|
|
1541
|
+
root.appendChild(badge);
|
|
1542
|
+
this.badgeBtn = badge;
|
|
1543
|
+
insertHost(anchor, host, position);
|
|
1544
|
+
this.registerCleanup(keepHostAttached(host, selector, position));
|
|
1545
|
+
const tooltip = ownerDocument.createElement("div");
|
|
1546
|
+
tooltip.className = "veo-tooltip veo-badge-tooltip";
|
|
1547
|
+
tooltip.style.display = "none";
|
|
1548
|
+
const emit = (action, meta) => {
|
|
1549
|
+
context.onInteraction({
|
|
1550
|
+
guideId: context.guide.guideId,
|
|
1551
|
+
stepIndex: 0,
|
|
1552
|
+
action,
|
|
1553
|
+
...meta ? { metadata: meta } : {}
|
|
1554
|
+
});
|
|
1555
|
+
};
|
|
1556
|
+
const buildTooltipContent = (s) => buildStepContent(s, ownerDocument, {
|
|
1557
|
+
onCtaClick: (action, url, meta) => {
|
|
1558
|
+
emit("cta_clicked", meta);
|
|
1559
|
+
if (action === "url" && url) window.open(url, "_blank", "noopener,noreferrer");
|
|
1560
|
+
if (action === "dismiss") {
|
|
1561
|
+
emit("dismissed");
|
|
1562
|
+
context.onClose();
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
this.closeTooltip();
|
|
1566
|
+
},
|
|
1567
|
+
onDismiss: () => {
|
|
1568
|
+
emit("dismissed");
|
|
1569
|
+
context.onClose();
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
const content = buildTooltipContent(step);
|
|
1573
|
+
tooltip.appendChild(content);
|
|
1574
|
+
const arrowEl = ownerDocument.createElement("div");
|
|
1575
|
+
arrowEl.className = "veo-tooltip-arrow";
|
|
1576
|
+
tooltip.appendChild(arrowEl);
|
|
1577
|
+
root.appendChild(tooltip);
|
|
1578
|
+
this.tooltip = tooltip;
|
|
1579
|
+
this.arrowEl = arrowEl;
|
|
1580
|
+
this.liveContainer = tooltip;
|
|
1581
|
+
this.liveContent = content;
|
|
1582
|
+
this.liveKey = this.liveKeyOf(step);
|
|
1583
|
+
this.liveBuild = buildTooltipContent;
|
|
1584
|
+
const openNow = () => {
|
|
1585
|
+
this.cancelClose();
|
|
1586
|
+
if (!this.open) {
|
|
1587
|
+
this.openTooltip(badge);
|
|
1588
|
+
if (!this.shownEmitted) {
|
|
1589
|
+
this.shownEmitted = true;
|
|
1590
|
+
emit("shown");
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
if (this.trigger === "hover") {
|
|
1595
|
+
const scheduleClose = () => {
|
|
1596
|
+
this.cancelClose();
|
|
1597
|
+
this.closeTimer = window.setTimeout(() => this.closeTooltip(), HOVER_CLOSE_DELAY_MS);
|
|
1598
|
+
};
|
|
1599
|
+
badge.addEventListener("mouseenter", openNow);
|
|
1600
|
+
badge.addEventListener("focus", openNow);
|
|
1601
|
+
badge.addEventListener("mouseleave", scheduleClose);
|
|
1602
|
+
badge.addEventListener("blur", scheduleClose);
|
|
1603
|
+
tooltip.addEventListener("mouseenter", () => this.cancelClose());
|
|
1604
|
+
tooltip.addEventListener("mouseleave", scheduleClose);
|
|
1605
|
+
} else {
|
|
1606
|
+
badge.addEventListener("click", () => {
|
|
1607
|
+
if (this.open) this.closeTooltip();
|
|
1608
|
+
else openNow();
|
|
1609
|
+
});
|
|
1610
|
+
const onDocClick = (e) => {
|
|
1611
|
+
if (!this.open) return;
|
|
1612
|
+
if (e.composedPath().includes(host)) return;
|
|
1613
|
+
this.closeTooltip();
|
|
1614
|
+
};
|
|
1615
|
+
document.addEventListener("click", onDocClick, true);
|
|
1616
|
+
this.registerCleanup(() => document.removeEventListener("click", onDocClick, true));
|
|
1617
|
+
}
|
|
1618
|
+
const onKey = (e) => {
|
|
1619
|
+
if (e.key === "Escape" && this.open) this.closeTooltip();
|
|
1620
|
+
};
|
|
1621
|
+
document.addEventListener("keydown", onKey);
|
|
1622
|
+
this.registerCleanup(() => document.removeEventListener("keydown", onKey));
|
|
1623
|
+
if (context.isPreview) openNow();
|
|
1624
|
+
}
|
|
1625
|
+
/** Cambiar de ancla/posición/trigger requiere re-montar y re-cablear. */
|
|
1626
|
+
liveKeyOf(step) {
|
|
1627
|
+
const selector = step.selector ?? "";
|
|
1628
|
+
const position = readInlinePosition(step.style);
|
|
1629
|
+
const trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
|
|
1630
|
+
return `${selector}|${position}|${trigger}`;
|
|
1631
|
+
}
|
|
1632
|
+
onLiveUpdate(step) {
|
|
1633
|
+
if (this.badgeBtn) {
|
|
1634
|
+
const doc = this.badgeBtn.ownerDocument;
|
|
1635
|
+
const next = buildBadgeElement(readBadgeConfig(step), doc);
|
|
1636
|
+
this.badgeBtn.className = next.className;
|
|
1637
|
+
this.badgeBtn.setAttribute("style", next.getAttribute("style") ?? "");
|
|
1638
|
+
this.badgeBtn.replaceChildren(...Array.from(next.childNodes));
|
|
1639
|
+
}
|
|
1640
|
+
const rawSide = step.style?.tooltipPlacement;
|
|
1641
|
+
this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
|
|
1642
|
+
if (this.open && this.badgeBtn) this.position(this.badgeBtn);
|
|
1643
|
+
}
|
|
1644
|
+
destroy() {
|
|
1645
|
+
this.cancelClose();
|
|
1646
|
+
this.stopFloat?.();
|
|
1647
|
+
this.stopFloat = null;
|
|
1648
|
+
this.tooltip = null;
|
|
1649
|
+
this.arrowEl = null;
|
|
1650
|
+
this.badgeBtn = null;
|
|
1651
|
+
this.open = false;
|
|
1652
|
+
super.destroy();
|
|
1653
|
+
}
|
|
1654
|
+
openTooltip(badge) {
|
|
1655
|
+
if (!this.tooltip) return;
|
|
1656
|
+
this.tooltip.style.display = "block";
|
|
1657
|
+
this.open = true;
|
|
1658
|
+
void this.position(badge);
|
|
1659
|
+
const reposition = () => {
|
|
1660
|
+
void this.position(badge);
|
|
1661
|
+
};
|
|
1662
|
+
window.addEventListener("scroll", reposition, true);
|
|
1663
|
+
window.addEventListener("resize", reposition);
|
|
1664
|
+
this.stopFloat = () => {
|
|
1665
|
+
window.removeEventListener("scroll", reposition, true);
|
|
1666
|
+
window.removeEventListener("resize", reposition);
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
closeTooltip() {
|
|
1670
|
+
this.cancelClose();
|
|
1671
|
+
if (!this.tooltip || !this.open) return;
|
|
1672
|
+
this.tooltip.style.display = "none";
|
|
1673
|
+
this.open = false;
|
|
1674
|
+
this.stopFloat?.();
|
|
1675
|
+
this.stopFloat = null;
|
|
1676
|
+
}
|
|
1677
|
+
cancelClose() {
|
|
1678
|
+
if (this.closeTimer !== null) {
|
|
1679
|
+
window.clearTimeout(this.closeTimer);
|
|
1680
|
+
this.closeTimer = null;
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
async position(badge) {
|
|
1684
|
+
const tooltip = this.tooltip;
|
|
1685
|
+
const arrowEl = this.arrowEl;
|
|
1686
|
+
if (!tooltip || !arrowEl) return;
|
|
1687
|
+
const { x, y, placement, middlewareData } = await dom.computePosition(badge, tooltip, {
|
|
1688
|
+
strategy: "fixed",
|
|
1689
|
+
placement: this.side,
|
|
1690
|
+
middleware: [dom.offset(8), dom.flip(), dom.shift({ padding: 8 }), dom.arrow({ element: arrowEl })]
|
|
1691
|
+
});
|
|
1692
|
+
tooltip.style.left = `${x}px`;
|
|
1693
|
+
tooltip.style.top = `${y}px`;
|
|
1694
|
+
positionArrow(arrowEl, placement, middlewareData.arrow);
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
});
|
|
1699
|
+
|
|
1700
|
+
// src/plugins/guides/walkthrough-block-builder.ts
|
|
1701
|
+
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
1702
|
+
const container = doc.createElement("div");
|
|
1703
|
+
container.className = "veo-guide-content";
|
|
1704
|
+
const counter = doc.createElement("div");
|
|
1705
|
+
counter.className = "veo-walkthrough-counter";
|
|
1706
|
+
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
1707
|
+
container.appendChild(counter);
|
|
1708
|
+
const progress = doc.createElement("div");
|
|
1709
|
+
progress.className = "veo-walkthrough-progress";
|
|
1710
|
+
for (let i = 0; i < totalSteps; i++) {
|
|
1711
|
+
const dot = doc.createElement("span");
|
|
1712
|
+
dot.className = "veo-walkthrough-progress-dot";
|
|
1713
|
+
if (i < stepIndex) dot.classList.add("completed");
|
|
1714
|
+
if (i === stepIndex) dot.classList.add("active");
|
|
1715
|
+
progress.appendChild(dot);
|
|
1716
|
+
}
|
|
1717
|
+
container.appendChild(progress);
|
|
1718
|
+
if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
|
|
1719
|
+
const img = doc.createElement("img");
|
|
1720
|
+
img.className = "veo-guide-image";
|
|
1721
|
+
img.src = step.imageUrl;
|
|
1722
|
+
img.alt = typeof step.title === "string" ? step.title : "";
|
|
1723
|
+
container.appendChild(img);
|
|
1724
|
+
}
|
|
1725
|
+
if (typeof step.title === "string" && step.title) {
|
|
1726
|
+
const heading = doc.createElement("h2");
|
|
1727
|
+
heading.className = "veo-guide-title";
|
|
1728
|
+
heading.textContent = step.title;
|
|
1729
|
+
container.appendChild(heading);
|
|
1730
|
+
}
|
|
1731
|
+
if (typeof step.content === "string" && step.content) {
|
|
1732
|
+
const paragraph = doc.createElement("p");
|
|
1733
|
+
paragraph.className = "veo-guide-text";
|
|
1734
|
+
paragraph.textContent = step.content;
|
|
1735
|
+
container.appendChild(paragraph);
|
|
1736
|
+
}
|
|
1737
|
+
const actions = doc.createElement("div");
|
|
1738
|
+
actions.className = "veo-walkthrough-actions";
|
|
1739
|
+
const skipBtn = doc.createElement("button");
|
|
1740
|
+
skipBtn.type = "button";
|
|
1741
|
+
skipBtn.className = "veo-walkthrough-skip";
|
|
1742
|
+
skipBtn.textContent = "Omitir";
|
|
1743
|
+
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
1744
|
+
actions.appendChild(skipBtn);
|
|
1745
|
+
const rightGroup = doc.createElement("div");
|
|
1746
|
+
rightGroup.className = "veo-walkthrough-actions-right";
|
|
1747
|
+
if (stepIndex > 0) {
|
|
1748
|
+
const backBtn = doc.createElement("button");
|
|
1749
|
+
backBtn.type = "button";
|
|
1750
|
+
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
1751
|
+
backBtn.textContent = "Atr\xE1s";
|
|
1752
|
+
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
1753
|
+
rightGroup.appendChild(backBtn);
|
|
1754
|
+
}
|
|
1755
|
+
const isLastStep = stepIndex === totalSteps - 1;
|
|
1756
|
+
const primaryBtn = doc.createElement("button");
|
|
1757
|
+
primaryBtn.type = "button";
|
|
1758
|
+
primaryBtn.className = "veo-guide-cta";
|
|
1759
|
+
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
1760
|
+
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
1761
|
+
primaryBtn.addEventListener("click", () => {
|
|
1762
|
+
if (isLastStep) callbacks.onComplete();
|
|
1763
|
+
else callbacks.onNext();
|
|
1764
|
+
});
|
|
1765
|
+
rightGroup.appendChild(primaryBtn);
|
|
1766
|
+
actions.appendChild(rightGroup);
|
|
1767
|
+
container.appendChild(actions);
|
|
1768
|
+
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
1769
|
+
return container;
|
|
1770
|
+
}
|
|
1771
|
+
var init_walkthrough_block_builder = __esm({
|
|
1772
|
+
"src/plugins/guides/walkthrough-block-builder.ts"() {
|
|
1773
|
+
init_block_builder();
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1202
1777
|
// src/plugins/guides/renderers/banner-renderer.ts
|
|
1778
|
+
function bannerClassOf(position, embedded) {
|
|
1779
|
+
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
1780
|
+
}
|
|
1203
1781
|
var BannerRenderer;
|
|
1204
1782
|
var init_banner_renderer = __esm({
|
|
1205
1783
|
"src/plugins/guides/renderers/banner-renderer.ts"() {
|
|
1206
1784
|
init_block_builder();
|
|
1785
|
+
init_inline_host();
|
|
1786
|
+
init_wait_for_element();
|
|
1207
1787
|
init_walkthrough_block_builder();
|
|
1208
1788
|
init_base_renderer();
|
|
1209
1789
|
BannerRenderer = class extends BaseRenderer {
|
|
1210
|
-
render(context) {
|
|
1790
|
+
async render(context) {
|
|
1211
1791
|
const idx = context.nav?.stepIndex ?? 0;
|
|
1212
1792
|
const step = context.guide.guideSteps[idx];
|
|
1213
1793
|
if (!step) return;
|
|
1214
|
-
const
|
|
1794
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1795
|
+
const selector = context.nav ? null : step.selector?.trim() ?? null;
|
|
1796
|
+
let anchor = null;
|
|
1797
|
+
if (selector) {
|
|
1798
|
+
anchor = await waitForElement(selector);
|
|
1799
|
+
if (!anchor) return;
|
|
1800
|
+
}
|
|
1801
|
+
const { host, root } = this.createHost();
|
|
1215
1802
|
this.applyDesign(step.style);
|
|
1216
1803
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1217
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1218
1804
|
const banner = ownerDocument.createElement("div");
|
|
1219
|
-
banner.className =
|
|
1220
|
-
const dismiss = (action, ctaUrl) => {
|
|
1221
|
-
context.onInteraction({
|
|
1805
|
+
banner.className = bannerClassOf(position, Boolean(anchor));
|
|
1806
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
1807
|
+
context.onInteraction({
|
|
1808
|
+
guideId: context.guide.guideId,
|
|
1809
|
+
stepIndex: idx,
|
|
1810
|
+
action,
|
|
1811
|
+
...meta ? { metadata: meta } : {}
|
|
1812
|
+
});
|
|
1222
1813
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1223
1814
|
context.onClose();
|
|
1224
1815
|
};
|
|
@@ -1229,68 +1820,46 @@ var init_banner_renderer = __esm({
|
|
|
1229
1820
|
ownerDocument,
|
|
1230
1821
|
context.nav.callbacks
|
|
1231
1822
|
) : buildStepContent(step, ownerDocument, {
|
|
1232
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
1823
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1233
1824
|
onDismiss: () => dismiss("dismissed")
|
|
1234
1825
|
});
|
|
1235
1826
|
banner.appendChild(content);
|
|
1236
1827
|
root.appendChild(banner);
|
|
1828
|
+
if (selector && anchor) {
|
|
1829
|
+
host.style.display = "block";
|
|
1830
|
+
const insertAt = position === "top" ? "prepend" : "append";
|
|
1831
|
+
insertHost(anchor, host, insertAt);
|
|
1832
|
+
this.registerCleanup(keepHostAttached(host, selector, insertAt));
|
|
1833
|
+
}
|
|
1237
1834
|
if (!context.nav) {
|
|
1238
1835
|
this.liveContainer = banner;
|
|
1239
1836
|
this.liveContent = content;
|
|
1240
1837
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1241
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
1838
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1242
1839
|
onDismiss: () => dismiss("dismissed")
|
|
1243
1840
|
});
|
|
1841
|
+
this.liveKey = this.liveKeyOf(step);
|
|
1244
1842
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1245
1843
|
}
|
|
1246
1844
|
}
|
|
1845
|
+
/** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
|
|
1846
|
+
liveKeyOf(step) {
|
|
1847
|
+
const selector = step.selector?.trim() ?? "";
|
|
1848
|
+
if (!selector) return "";
|
|
1849
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1850
|
+
return `${selector}|${position}`;
|
|
1851
|
+
}
|
|
1247
1852
|
onLiveUpdate(step) {
|
|
1248
1853
|
if (this.liveContainer) {
|
|
1249
1854
|
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1250
|
-
|
|
1855
|
+
const embedded = Boolean(step.selector?.trim());
|
|
1856
|
+
this.liveContainer.className = bannerClassOf(position, embedded);
|
|
1251
1857
|
}
|
|
1252
1858
|
}
|
|
1253
1859
|
};
|
|
1254
1860
|
}
|
|
1255
1861
|
});
|
|
1256
1862
|
|
|
1257
|
-
// src/plugins/guides/wait-for-element.ts
|
|
1258
|
-
function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
|
|
1259
|
-
return new Promise((resolve) => {
|
|
1260
|
-
const safeQuery = () => {
|
|
1261
|
-
try {
|
|
1262
|
-
return document.querySelector(selector);
|
|
1263
|
-
} catch {
|
|
1264
|
-
return null;
|
|
1265
|
-
}
|
|
1266
|
-
};
|
|
1267
|
-
const existing = safeQuery();
|
|
1268
|
-
if (existing) {
|
|
1269
|
-
resolve(existing);
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
let resolved = false;
|
|
1273
|
-
const finish = (el) => {
|
|
1274
|
-
if (resolved) return;
|
|
1275
|
-
resolved = true;
|
|
1276
|
-
observer.disconnect();
|
|
1277
|
-
clearTimeout(timer);
|
|
1278
|
-
resolve(el);
|
|
1279
|
-
};
|
|
1280
|
-
const observer = new MutationObserver(() => {
|
|
1281
|
-
const el = safeQuery();
|
|
1282
|
-
if (el) finish(el);
|
|
1283
|
-
});
|
|
1284
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
1285
|
-
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1288
|
-
var init_wait_for_element = __esm({
|
|
1289
|
-
"src/plugins/guides/wait-for-element.ts"() {
|
|
1290
|
-
init_constants2();
|
|
1291
|
-
}
|
|
1292
|
-
});
|
|
1293
|
-
|
|
1294
1863
|
// src/plugins/guides/renderers/custom-frame.ts
|
|
1295
1864
|
function mountCustomFrame(doc, step, context, opts) {
|
|
1296
1865
|
const iframe = doc.createElement("iframe");
|
|
@@ -1824,40 +2393,6 @@ var init_form_renderer = __esm({
|
|
|
1824
2393
|
}
|
|
1825
2394
|
});
|
|
1826
2395
|
|
|
1827
|
-
// src/plugins/guides/inline-host.ts
|
|
1828
|
-
function readInlinePosition(style) {
|
|
1829
|
-
const p = style?.inlinePosition;
|
|
1830
|
-
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
1831
|
-
}
|
|
1832
|
-
function insertHost(anchor, host, position) {
|
|
1833
|
-
switch (position) {
|
|
1834
|
-
case "before":
|
|
1835
|
-
anchor.before(host);
|
|
1836
|
-
break;
|
|
1837
|
-
case "after":
|
|
1838
|
-
anchor.after(host);
|
|
1839
|
-
break;
|
|
1840
|
-
case "prepend":
|
|
1841
|
-
anchor.prepend(host);
|
|
1842
|
-
break;
|
|
1843
|
-
case "append":
|
|
1844
|
-
anchor.append(host);
|
|
1845
|
-
break;
|
|
1846
|
-
}
|
|
1847
|
-
}
|
|
1848
|
-
function keepHostAttached(host, selector, position) {
|
|
1849
|
-
const id = window.setInterval(() => {
|
|
1850
|
-
if (host.isConnected) return;
|
|
1851
|
-
const anchor = document.querySelector(selector);
|
|
1852
|
-
if (anchor) insertHost(anchor, host, position);
|
|
1853
|
-
}, 1e3);
|
|
1854
|
-
return () => window.clearInterval(id);
|
|
1855
|
-
}
|
|
1856
|
-
var init_inline_host = __esm({
|
|
1857
|
-
"src/plugins/guides/inline-host.ts"() {
|
|
1858
|
-
}
|
|
1859
|
-
});
|
|
1860
|
-
|
|
1861
2396
|
// src/plugins/guides/renderers/inline-custom-renderer.ts
|
|
1862
2397
|
var InlineCustomRenderer;
|
|
1863
2398
|
var init_inline_custom_renderer = __esm({
|
|
@@ -1950,13 +2485,18 @@ var init_inline_renderer = __esm({
|
|
|
1950
2485
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1951
2486
|
const card = ownerDocument.createElement("div");
|
|
1952
2487
|
card.className = "veo-inline";
|
|
1953
|
-
const dismiss = (action, ctaUrl) => {
|
|
1954
|
-
context.onInteraction({
|
|
2488
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2489
|
+
context.onInteraction({
|
|
2490
|
+
guideId: context.guide.guideId,
|
|
2491
|
+
stepIndex: 0,
|
|
2492
|
+
action,
|
|
2493
|
+
...meta ? { metadata: meta } : {}
|
|
2494
|
+
});
|
|
1955
2495
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1956
2496
|
context.onClose();
|
|
1957
2497
|
};
|
|
1958
2498
|
const content = buildStepContent(step, ownerDocument, {
|
|
1959
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2499
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1960
2500
|
onDismiss: () => dismiss("dismissed")
|
|
1961
2501
|
});
|
|
1962
2502
|
card.appendChild(content);
|
|
@@ -1965,7 +2505,7 @@ var init_inline_renderer = __esm({
|
|
|
1965
2505
|
this.liveContent = content;
|
|
1966
2506
|
this.liveKey = `${selector}|${position}`;
|
|
1967
2507
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1968
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2508
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1969
2509
|
onDismiss: () => dismiss("dismissed")
|
|
1970
2510
|
});
|
|
1971
2511
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
@@ -1978,6 +2518,9 @@ var init_inline_renderer = __esm({
|
|
|
1978
2518
|
});
|
|
1979
2519
|
|
|
1980
2520
|
// src/plugins/guides/renderers/modal-renderer.ts
|
|
2521
|
+
function overlayClassOf(step) {
|
|
2522
|
+
return step.style?.backdrop === false ? "veo-modal-overlay veo-modal-overlay--none" : "veo-modal-overlay";
|
|
2523
|
+
}
|
|
1981
2524
|
var ModalRenderer;
|
|
1982
2525
|
var init_modal_renderer = __esm({
|
|
1983
2526
|
"src/plugins/guides/renderers/modal-renderer.ts"() {
|
|
@@ -1985,6 +2528,10 @@ var init_modal_renderer = __esm({
|
|
|
1985
2528
|
init_walkthrough_block_builder();
|
|
1986
2529
|
init_base_renderer();
|
|
1987
2530
|
ModalRenderer = class extends BaseRenderer {
|
|
2531
|
+
constructor() {
|
|
2532
|
+
super(...arguments);
|
|
2533
|
+
this.overlay = null;
|
|
2534
|
+
}
|
|
1988
2535
|
render(context) {
|
|
1989
2536
|
const idx = context.nav?.stepIndex ?? 0;
|
|
1990
2537
|
const step = context.guide.guideSteps[idx];
|
|
@@ -1993,11 +2540,17 @@ var init_modal_renderer = __esm({
|
|
|
1993
2540
|
this.applyDesign(step.style);
|
|
1994
2541
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1995
2542
|
const overlay = ownerDocument.createElement("div");
|
|
1996
|
-
overlay.className =
|
|
2543
|
+
overlay.className = overlayClassOf(step);
|
|
2544
|
+
this.overlay = overlay;
|
|
1997
2545
|
const card = ownerDocument.createElement("div");
|
|
1998
2546
|
card.className = "veo-modal-card";
|
|
1999
|
-
const dismiss = (action, ctaUrl) => {
|
|
2000
|
-
context.onInteraction({
|
|
2547
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2548
|
+
context.onInteraction({
|
|
2549
|
+
guideId: context.guide.guideId,
|
|
2550
|
+
stepIndex: idx,
|
|
2551
|
+
action,
|
|
2552
|
+
...meta ? { metadata: meta } : {}
|
|
2553
|
+
});
|
|
2001
2554
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
2002
2555
|
context.onClose();
|
|
2003
2556
|
};
|
|
@@ -2012,7 +2565,7 @@ var init_modal_renderer = __esm({
|
|
|
2012
2565
|
ownerDocument,
|
|
2013
2566
|
context.nav.callbacks
|
|
2014
2567
|
) : buildStepContent(step, ownerDocument, {
|
|
2015
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2568
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2016
2569
|
onDismiss: () => dismiss("dismissed")
|
|
2017
2570
|
});
|
|
2018
2571
|
card.appendChild(content);
|
|
@@ -2022,7 +2575,7 @@ var init_modal_renderer = __esm({
|
|
|
2022
2575
|
this.liveContainer = card;
|
|
2023
2576
|
this.liveContent = content;
|
|
2024
2577
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2025
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2578
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2026
2579
|
onDismiss: () => dismiss("dismissed")
|
|
2027
2580
|
});
|
|
2028
2581
|
}
|
|
@@ -2038,28 +2591,13 @@ var init_modal_renderer = __esm({
|
|
|
2038
2591
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
2039
2592
|
}
|
|
2040
2593
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
for (const prop of ["top", "bottom", "left", "right"]) {
|
|
2049
|
-
arrowEl.style.setProperty(prop, "");
|
|
2050
|
-
}
|
|
2051
|
-
if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
|
|
2052
|
-
if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
|
|
2053
|
-
arrowEl.style.setProperty(side, "-6px");
|
|
2054
|
-
}
|
|
2055
|
-
var STATIC_SIDE;
|
|
2056
|
-
var init_floating_arrow = __esm({
|
|
2057
|
-
"src/plugins/guides/renderers/floating-arrow.ts"() {
|
|
2058
|
-
STATIC_SIDE = {
|
|
2059
|
-
top: "bottom",
|
|
2060
|
-
bottom: "top",
|
|
2061
|
-
left: "right",
|
|
2062
|
-
right: "left"
|
|
2594
|
+
onLiveUpdate(step) {
|
|
2595
|
+
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
2596
|
+
}
|
|
2597
|
+
destroy() {
|
|
2598
|
+
this.overlay = null;
|
|
2599
|
+
super.destroy();
|
|
2600
|
+
}
|
|
2063
2601
|
};
|
|
2064
2602
|
}
|
|
2065
2603
|
});
|
|
@@ -2091,18 +2629,19 @@ var init_tooltip_renderer = __esm({
|
|
|
2091
2629
|
const ownerDocument = root.ownerDocument ?? document;
|
|
2092
2630
|
const tooltip = ownerDocument.createElement("div");
|
|
2093
2631
|
tooltip.className = "veo-tooltip";
|
|
2094
|
-
const dismiss = (action, ctaUrl) => {
|
|
2632
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2095
2633
|
context.onInteraction({
|
|
2096
2634
|
guideId: context.guide.guideId,
|
|
2097
2635
|
stepIndex: 0,
|
|
2098
|
-
action
|
|
2636
|
+
action,
|
|
2637
|
+
...meta ? { metadata: meta } : {}
|
|
2099
2638
|
});
|
|
2100
2639
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
2101
2640
|
context.onClose();
|
|
2102
2641
|
};
|
|
2103
2642
|
const content = buildStepContent(step, ownerDocument, {
|
|
2104
|
-
onCtaClick: (action, url) => {
|
|
2105
|
-
dismiss("cta_clicked", action === "url" ? url : void 0);
|
|
2643
|
+
onCtaClick: (action, url, meta) => {
|
|
2644
|
+
dismiss("cta_clicked", action === "url" ? url : void 0, meta);
|
|
2106
2645
|
},
|
|
2107
2646
|
onDismiss: () => dismiss("dismissed")
|
|
2108
2647
|
});
|
|
@@ -2135,7 +2674,7 @@ var init_tooltip_renderer = __esm({
|
|
|
2135
2674
|
this.liveContent = content;
|
|
2136
2675
|
this.liveKey = selector;
|
|
2137
2676
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2138
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2677
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2139
2678
|
onDismiss: () => dismiss("dismissed")
|
|
2140
2679
|
});
|
|
2141
2680
|
context.onInteraction({
|
|
@@ -2219,7 +2758,7 @@ var init_walkthrough_renderer = __esm({
|
|
|
2219
2758
|
function previewGuide(input) {
|
|
2220
2759
|
if (!hasDocument()) {
|
|
2221
2760
|
return { close: () => {
|
|
2222
|
-
}, ready: Promise.resolve({ rendered: false }) };
|
|
2761
|
+
}, ready: Promise.resolve({ rendered: false }), host: () => null };
|
|
2223
2762
|
}
|
|
2224
2763
|
if (!activeController) activeController = new GuidePreviewController();
|
|
2225
2764
|
return activeController.preview(input);
|
|
@@ -2262,6 +2801,8 @@ function createSingleStepRenderer(type) {
|
|
|
2262
2801
|
return new FormRenderer();
|
|
2263
2802
|
case "inline-form":
|
|
2264
2803
|
return new InlineFormRenderer();
|
|
2804
|
+
case "badge":
|
|
2805
|
+
return new BadgeRenderer();
|
|
2265
2806
|
case "walkthrough":
|
|
2266
2807
|
return null;
|
|
2267
2808
|
}
|
|
@@ -2276,7 +2817,9 @@ var PREVIEW_GUIDE_ID, GuidePreviewController, activeController;
|
|
|
2276
2817
|
var init_guide_preview = __esm({
|
|
2277
2818
|
"src/plugins/guides/guide-preview.ts"() {
|
|
2278
2819
|
init_safe_env();
|
|
2820
|
+
init_badge_renderer();
|
|
2279
2821
|
init_banner_renderer();
|
|
2822
|
+
init_base_renderer();
|
|
2280
2823
|
init_custom_renderer();
|
|
2281
2824
|
init_form_renderer();
|
|
2282
2825
|
init_inline_custom_renderer();
|
|
@@ -2300,14 +2843,26 @@ var init_guide_preview = __esm({
|
|
|
2300
2843
|
const nextStep = input.guideSteps[0];
|
|
2301
2844
|
if (this.singleStep && this.input && input.guideType === this.input.guideType && input.guideType !== "walkthrough" && nextStep && this.singleStep.updateStep(nextStep)) {
|
|
2302
2845
|
this.input = input;
|
|
2303
|
-
return {
|
|
2846
|
+
return {
|
|
2847
|
+
close: () => this.close(),
|
|
2848
|
+
ready: Promise.resolve({ rendered: true }),
|
|
2849
|
+
host: () => this.activeHost()
|
|
2850
|
+
};
|
|
2304
2851
|
}
|
|
2305
2852
|
this.close();
|
|
2306
2853
|
this.input = input;
|
|
2307
2854
|
const guide = normalize(input);
|
|
2308
2855
|
const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
|
|
2309
2856
|
const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
|
|
2310
|
-
return { close: () => this.close(), ready };
|
|
2857
|
+
return { close: () => this.close(), ready, host: () => this.activeHost() };
|
|
2858
|
+
}
|
|
2859
|
+
/** Host de la guía montada (single-step o paso de walkthrough activo). */
|
|
2860
|
+
activeHost() {
|
|
2861
|
+
if (this.singleStep) return this.singleStep.hostElement();
|
|
2862
|
+
if (this.walkthrough instanceof BaseRenderer) {
|
|
2863
|
+
return this.walkthrough.hostElement();
|
|
2864
|
+
}
|
|
2865
|
+
return null;
|
|
2311
2866
|
}
|
|
2312
2867
|
close() {
|
|
2313
2868
|
if (this.singleStep) {
|
|
@@ -2522,8 +3077,8 @@ function injectBuilderPanel(opts) {
|
|
|
2522
3077
|
const ot = top;
|
|
2523
3078
|
iframe.style.pointerEvents = "none";
|
|
2524
3079
|
const move = (ev) => {
|
|
2525
|
-
left =
|
|
2526
|
-
top =
|
|
3080
|
+
left = clamp3(ol + (ev.clientX - sx), 0, Math.max(0, window.innerWidth - card.offsetWidth));
|
|
3081
|
+
top = clamp3(ot + (ev.clientY - sy), 0, Math.max(0, window.innerHeight - 40));
|
|
2527
3082
|
card.style.left = `${left}px`;
|
|
2528
3083
|
card.style.top = `${top}px`;
|
|
2529
3084
|
};
|
|
@@ -2544,8 +3099,8 @@ function injectBuilderPanel(opts) {
|
|
|
2544
3099
|
const sh = card.offsetHeight;
|
|
2545
3100
|
iframe.style.pointerEvents = "none";
|
|
2546
3101
|
const move = (ev) => {
|
|
2547
|
-
card.style.width = `${
|
|
2548
|
-
card.style.height = `${
|
|
3102
|
+
card.style.width = `${clamp3(sw + (ev.clientX - sx), MIN_W, window.innerWidth)}px`;
|
|
3103
|
+
card.style.height = `${clamp3(sh + (ev.clientY - sy), MIN_H, window.innerHeight)}px`;
|
|
2549
3104
|
};
|
|
2550
3105
|
const up = () => {
|
|
2551
3106
|
window.removeEventListener("mousemove", move);
|
|
@@ -2564,7 +3119,7 @@ function injectBuilderPanel(opts) {
|
|
|
2564
3119
|
}
|
|
2565
3120
|
};
|
|
2566
3121
|
}
|
|
2567
|
-
function
|
|
3122
|
+
function clamp3(v, min, max) {
|
|
2568
3123
|
return Math.min(Math.max(v, min), max);
|
|
2569
3124
|
}
|
|
2570
3125
|
function buildPanelUrl(opts) {
|
|
@@ -2628,6 +3183,242 @@ var init_builder_panel = __esm({
|
|
|
2628
3183
|
}
|
|
2629
3184
|
});
|
|
2630
3185
|
|
|
3186
|
+
// src/plugins/builder/design-manipulator.ts
|
|
3187
|
+
function clamp4(v, min, max) {
|
|
3188
|
+
return Math.min(Math.max(v, min), max);
|
|
3189
|
+
}
|
|
3190
|
+
function round3(v) {
|
|
3191
|
+
return Math.round(v * 1e3) / 1e3;
|
|
3192
|
+
}
|
|
3193
|
+
function isInteractive(target) {
|
|
3194
|
+
return Boolean(
|
|
3195
|
+
target instanceof Element && target.closest("button, a, input, textarea, select, label")
|
|
3196
|
+
);
|
|
3197
|
+
}
|
|
3198
|
+
function attachDesignManipulator(opts) {
|
|
3199
|
+
const shadow = opts.host.shadowRoot;
|
|
3200
|
+
if (!shadow) return null;
|
|
3201
|
+
const card = shadow.querySelector(".veo-modal-card");
|
|
3202
|
+
if (card) return attachModal(opts, shadow, card);
|
|
3203
|
+
const banner = shadow.querySelector(".veo-banner");
|
|
3204
|
+
if (banner && !banner.classList.contains("veo-banner-embedded")) {
|
|
3205
|
+
return attachBanner(opts, shadow, banner);
|
|
3206
|
+
}
|
|
3207
|
+
return null;
|
|
3208
|
+
}
|
|
3209
|
+
function attachModal(opts, shadow, card) {
|
|
3210
|
+
if (card.dataset.veoManipulated === "1") return null;
|
|
3211
|
+
card.dataset.veoManipulated = "1";
|
|
3212
|
+
card.classList.add("veo-mnp-card");
|
|
3213
|
+
const doc = card.ownerDocument;
|
|
3214
|
+
const style = doc.createElement("style");
|
|
3215
|
+
style.textContent = MANIPULATOR_CSS;
|
|
3216
|
+
shadow.appendChild(style);
|
|
3217
|
+
const handles = [];
|
|
3218
|
+
for (const dir of RESIZE_DIRS) {
|
|
3219
|
+
const h = doc.createElement("div");
|
|
3220
|
+
h.className = `veo-mnp-handle veo-mnp-${dir}`;
|
|
3221
|
+
h.dataset.veoDir = dir;
|
|
3222
|
+
card.appendChild(h);
|
|
3223
|
+
handles.push(h);
|
|
3224
|
+
}
|
|
3225
|
+
let dragging = false;
|
|
3226
|
+
let detached = false;
|
|
3227
|
+
let patch = {};
|
|
3228
|
+
const setVars = (v) => {
|
|
3229
|
+
const host = opts.host;
|
|
3230
|
+
if (v.px !== void 0) host.style.setProperty("--veo-pos-x", `${v.px * 100}%`);
|
|
3231
|
+
if (v.py !== void 0) host.style.setProperty("--veo-pos-y", `${v.py * 100}%`);
|
|
3232
|
+
if (v.w !== void 0) host.style.setProperty("--veo-width", `${v.w}px`);
|
|
3233
|
+
if (v.h !== void 0) host.style.setProperty("--veo-min-h", `${v.h}px`);
|
|
3234
|
+
};
|
|
3235
|
+
const startGesture = (e, apply) => {
|
|
3236
|
+
e.preventDefault();
|
|
3237
|
+
e.stopPropagation();
|
|
3238
|
+
dragging = true;
|
|
3239
|
+
patch = {};
|
|
3240
|
+
const start = card.getBoundingClientRect();
|
|
3241
|
+
const sx = e.clientX;
|
|
3242
|
+
const sy = e.clientY;
|
|
3243
|
+
let raf = 0;
|
|
3244
|
+
let lastEv = null;
|
|
3245
|
+
const flush = () => {
|
|
3246
|
+
raf = 0;
|
|
3247
|
+
if (!lastEv) return;
|
|
3248
|
+
const next = apply(lastEv.clientX - sx, lastEv.clientY - sy, start);
|
|
3249
|
+
patch = { ...patch, ...next };
|
|
3250
|
+
setVars({
|
|
3251
|
+
...next.posX !== void 0 ? { px: next.posX } : {},
|
|
3252
|
+
...next.posY !== void 0 ? { py: next.posY } : {},
|
|
3253
|
+
...next.width !== void 0 ? { w: next.width } : {},
|
|
3254
|
+
...next.height !== void 0 ? { h: next.height } : {}
|
|
3255
|
+
});
|
|
3256
|
+
};
|
|
3257
|
+
const move = (ev) => {
|
|
3258
|
+
lastEv = ev;
|
|
3259
|
+
if (typeof window.requestAnimationFrame !== "function") flush();
|
|
3260
|
+
else if (!raf) raf = window.requestAnimationFrame(flush);
|
|
3261
|
+
};
|
|
3262
|
+
const up = () => {
|
|
3263
|
+
window.removeEventListener("mousemove", move);
|
|
3264
|
+
window.removeEventListener("mouseup", up);
|
|
3265
|
+
if (raf) window.cancelAnimationFrame(raf);
|
|
3266
|
+
flush();
|
|
3267
|
+
dragging = false;
|
|
3268
|
+
if (Object.keys(patch).length > 0 && !detached) {
|
|
3269
|
+
opts.onCommit({ stepIndex: opts.stepIndex, style: patch });
|
|
3270
|
+
}
|
|
3271
|
+
opts.onGestureEnd?.();
|
|
3272
|
+
};
|
|
3273
|
+
window.addEventListener("mousemove", move);
|
|
3274
|
+
window.addEventListener("mouseup", up);
|
|
3275
|
+
};
|
|
3276
|
+
const posXFor = (left, w) => {
|
|
3277
|
+
const span = window.innerWidth - w;
|
|
3278
|
+
return span <= 0 ? 0.5 : round3(clamp4(left / span, 0, 1));
|
|
3279
|
+
};
|
|
3280
|
+
const posYFor = (top, h) => {
|
|
3281
|
+
const span = window.innerHeight - h;
|
|
3282
|
+
return span <= 0 ? 0.5 : round3(clamp4(top / span, 0, 1));
|
|
3283
|
+
};
|
|
3284
|
+
const onCardDown = (e) => {
|
|
3285
|
+
if (isInteractive(e.target)) return;
|
|
3286
|
+
if (e.target instanceof Element && e.target.closest(".veo-mnp-handle")) return;
|
|
3287
|
+
startGesture(e, (dx, dy, start) => ({
|
|
3288
|
+
posX: posXFor(start.left + dx, start.width),
|
|
3289
|
+
posY: posYFor(start.top + dy, start.height)
|
|
3290
|
+
}));
|
|
3291
|
+
};
|
|
3292
|
+
const onHandleDown = (e) => {
|
|
3293
|
+
const dir = e.currentTarget.dataset.veoDir;
|
|
3294
|
+
startGesture(e, (dx, dy, start) => {
|
|
3295
|
+
const out = {};
|
|
3296
|
+
if (dir.includes("e")) out.width = Math.round(clamp4(start.width + dx, MIN_W2, MAX_W));
|
|
3297
|
+
if (dir.includes("w")) {
|
|
3298
|
+
out.width = Math.round(clamp4(start.width - dx, MIN_W2, MAX_W));
|
|
3299
|
+
out.posX = posXFor(start.right - out.width, out.width);
|
|
3300
|
+
}
|
|
3301
|
+
if (dir.includes("s")) out.height = Math.round(clamp4(start.height + dy, MIN_H2, MAX_H));
|
|
3302
|
+
if (dir.includes("n")) {
|
|
3303
|
+
out.height = Math.round(clamp4(start.height - dy, MIN_H2, MAX_H));
|
|
3304
|
+
out.posY = posYFor(start.bottom - out.height, out.height);
|
|
3305
|
+
}
|
|
3306
|
+
return out;
|
|
3307
|
+
});
|
|
3308
|
+
};
|
|
3309
|
+
card.addEventListener("mousedown", onCardDown);
|
|
3310
|
+
for (const h of handles) h.addEventListener("mousedown", onHandleDown);
|
|
3311
|
+
return {
|
|
3312
|
+
isDragging: () => dragging,
|
|
3313
|
+
detach: () => {
|
|
3314
|
+
if (detached) return;
|
|
3315
|
+
detached = true;
|
|
3316
|
+
card.removeEventListener("mousedown", onCardDown);
|
|
3317
|
+
for (const h of handles) h.remove();
|
|
3318
|
+
style.remove();
|
|
3319
|
+
card.classList.remove("veo-mnp-card");
|
|
3320
|
+
delete card.dataset.veoManipulated;
|
|
3321
|
+
}
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
function attachBanner(opts, shadow, banner) {
|
|
3325
|
+
if (banner.dataset.veoManipulated === "1") return null;
|
|
3326
|
+
banner.dataset.veoManipulated = "1";
|
|
3327
|
+
banner.classList.add("veo-mnp-banner");
|
|
3328
|
+
const doc = banner.ownerDocument;
|
|
3329
|
+
const style = doc.createElement("style");
|
|
3330
|
+
style.textContent = MANIPULATOR_CSS;
|
|
3331
|
+
shadow.appendChild(style);
|
|
3332
|
+
let dragging = false;
|
|
3333
|
+
let detached = false;
|
|
3334
|
+
const positionOf = () => banner.classList.contains("veo-banner-bottom") ? "bottom" : "top";
|
|
3335
|
+
const onDown = (e) => {
|
|
3336
|
+
if (isInteractive(e.target)) return;
|
|
3337
|
+
e.preventDefault();
|
|
3338
|
+
dragging = true;
|
|
3339
|
+
const startPos = positionOf();
|
|
3340
|
+
let current = startPos;
|
|
3341
|
+
const move = (ev) => {
|
|
3342
|
+
const next = ev.clientY < window.innerHeight / 2 ? "top" : "bottom";
|
|
3343
|
+
if (next !== current) {
|
|
3344
|
+
current = next;
|
|
3345
|
+
banner.classList.remove("veo-banner-top", "veo-banner-bottom");
|
|
3346
|
+
banner.classList.add(`veo-banner-${next}`);
|
|
3347
|
+
}
|
|
3348
|
+
};
|
|
3349
|
+
const up = () => {
|
|
3350
|
+
window.removeEventListener("mousemove", move);
|
|
3351
|
+
window.removeEventListener("mouseup", up);
|
|
3352
|
+
dragging = false;
|
|
3353
|
+
if (current !== startPos && !detached) {
|
|
3354
|
+
opts.onCommit({ stepIndex: opts.stepIndex, style: { position: current } });
|
|
3355
|
+
}
|
|
3356
|
+
opts.onGestureEnd?.();
|
|
3357
|
+
};
|
|
3358
|
+
window.addEventListener("mousemove", move);
|
|
3359
|
+
window.addEventListener("mouseup", up);
|
|
3360
|
+
};
|
|
3361
|
+
banner.addEventListener("mousedown", onDown);
|
|
3362
|
+
return {
|
|
3363
|
+
isDragging: () => dragging,
|
|
3364
|
+
detach: () => {
|
|
3365
|
+
if (detached) return;
|
|
3366
|
+
detached = true;
|
|
3367
|
+
banner.removeEventListener("mousedown", onDown);
|
|
3368
|
+
style.remove();
|
|
3369
|
+
banner.classList.remove("veo-mnp-banner");
|
|
3370
|
+
delete banner.dataset.veoManipulated;
|
|
3371
|
+
}
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
var MIN_W2, MAX_W, MIN_H2, MAX_H, EDGE, RESIZE_DIRS, MANIPULATOR_CSS;
|
|
3375
|
+
var init_design_manipulator = __esm({
|
|
3376
|
+
"src/plugins/builder/design-manipulator.ts"() {
|
|
3377
|
+
MIN_W2 = 220;
|
|
3378
|
+
MAX_W = 720;
|
|
3379
|
+
MIN_H2 = 120;
|
|
3380
|
+
MAX_H = 900;
|
|
3381
|
+
EDGE = 8;
|
|
3382
|
+
RESIZE_DIRS = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
3383
|
+
MANIPULATOR_CSS = `
|
|
3384
|
+
.veo-mnp-card {
|
|
3385
|
+
outline: 1.5px dashed rgba(255, 91, 53, 0.75);
|
|
3386
|
+
outline-offset: 2px;
|
|
3387
|
+
}
|
|
3388
|
+
.veo-mnp-card:hover { cursor: move; }
|
|
3389
|
+
.veo-mnp-handle {
|
|
3390
|
+
position: absolute;
|
|
3391
|
+
z-index: 10;
|
|
3392
|
+
background: transparent;
|
|
3393
|
+
}
|
|
3394
|
+
.veo-mnp-handle::after {
|
|
3395
|
+
content: '';
|
|
3396
|
+
position: absolute;
|
|
3397
|
+
width: 8px; height: 8px;
|
|
3398
|
+
background: #fff;
|
|
3399
|
+
border: 1.5px solid rgba(255, 91, 53, 0.9);
|
|
3400
|
+
border-radius: 2px;
|
|
3401
|
+
top: 50%; left: 50%;
|
|
3402
|
+
transform: translate(-50%, -50%);
|
|
3403
|
+
opacity: 0;
|
|
3404
|
+
transition: opacity 100ms ease;
|
|
3405
|
+
}
|
|
3406
|
+
.veo-mnp-card:hover .veo-mnp-handle::after,
|
|
3407
|
+
.veo-mnp-handle:hover::after { opacity: 1; }
|
|
3408
|
+
.veo-mnp-n { top: -${EDGE / 2}px; left: ${EDGE}px; right: ${EDGE}px; height: ${EDGE}px; cursor: ns-resize; }
|
|
3409
|
+
.veo-mnp-s { bottom: -${EDGE / 2}px; left: ${EDGE}px; right: ${EDGE}px; height: ${EDGE}px; cursor: ns-resize; }
|
|
3410
|
+
.veo-mnp-e { right: -${EDGE / 2}px; top: ${EDGE}px; bottom: ${EDGE}px; width: ${EDGE}px; cursor: ew-resize; }
|
|
3411
|
+
.veo-mnp-w { left: -${EDGE / 2}px; top: ${EDGE}px; bottom: ${EDGE}px; width: ${EDGE}px; cursor: ew-resize; }
|
|
3412
|
+
.veo-mnp-ne { top: -${EDGE / 2}px; right: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nesw-resize; }
|
|
3413
|
+
.veo-mnp-nw { top: -${EDGE / 2}px; left: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nwse-resize; }
|
|
3414
|
+
.veo-mnp-se { bottom: -${EDGE / 2}px; right: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nwse-resize; }
|
|
3415
|
+
.veo-mnp-sw { bottom: -${EDGE / 2}px; left: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nesw-resize; }
|
|
3416
|
+
.veo-mnp-banner { outline: 1.5px dashed rgba(255, 91, 53, 0.75); outline-offset: -2px; cursor: grab; }
|
|
3417
|
+
.veo-mnp-banner:active { cursor: grabbing; }
|
|
3418
|
+
`;
|
|
3419
|
+
}
|
|
3420
|
+
});
|
|
3421
|
+
|
|
2631
3422
|
// src/plugins/builder/element-picker.ts
|
|
2632
3423
|
function startElementPicker(options) {
|
|
2633
3424
|
if (!hasDocument()) {
|
|
@@ -2841,10 +3632,35 @@ function initBuilderMode() {
|
|
|
2841
3632
|
let picker = null;
|
|
2842
3633
|
let panel = null;
|
|
2843
3634
|
let staticTarget = null;
|
|
3635
|
+
let manipulator = null;
|
|
3636
|
+
let pendingPreview = null;
|
|
2844
3637
|
const getTarget = () => panel ? panel.target() : staticTarget;
|
|
2845
3638
|
const post = (event) => {
|
|
2846
3639
|
getTarget()?.postMessage({ source: VEO_BUILDER_SOURCE, token, ...event }, dashboardOrigin);
|
|
2847
3640
|
};
|
|
3641
|
+
const applyPreview = (cmd) => {
|
|
3642
|
+
manipulator?.detach();
|
|
3643
|
+
manipulator = null;
|
|
3644
|
+
const handle = previewGuide(cmd.guide);
|
|
3645
|
+
if (!cmd.editable) return;
|
|
3646
|
+
void handle.ready.then((result) => {
|
|
3647
|
+
if (!result.rendered) return;
|
|
3648
|
+
const host = handle.host();
|
|
3649
|
+
if (!host) return;
|
|
3650
|
+
manipulator = attachDesignManipulator({
|
|
3651
|
+
host,
|
|
3652
|
+
guideType: cmd.guide.guideType,
|
|
3653
|
+
stepIndex: cmd.guide.startStepIndex ?? 0,
|
|
3654
|
+
onCommit: (payload) => post({ type: "design-updated", payload }),
|
|
3655
|
+
onGestureEnd: () => {
|
|
3656
|
+
if (!pendingPreview) return;
|
|
3657
|
+
const queued = pendingPreview;
|
|
3658
|
+
pendingPreview = null;
|
|
3659
|
+
applyPreview(queued);
|
|
3660
|
+
}
|
|
3661
|
+
});
|
|
3662
|
+
});
|
|
3663
|
+
};
|
|
2848
3664
|
const handleCommand = (cmd) => {
|
|
2849
3665
|
switch (cmd.type) {
|
|
2850
3666
|
case "panel-ready":
|
|
@@ -2870,9 +3686,14 @@ function initBuilderMode() {
|
|
|
2870
3686
|
picker = null;
|
|
2871
3687
|
break;
|
|
2872
3688
|
case "preview":
|
|
2873
|
-
if (cmd.guide)
|
|
3689
|
+
if (!cmd.guide) break;
|
|
3690
|
+
if (manipulator?.isDragging()) pendingPreview = cmd;
|
|
3691
|
+
else applyPreview(cmd);
|
|
2874
3692
|
break;
|
|
2875
3693
|
case "close-preview":
|
|
3694
|
+
manipulator?.detach();
|
|
3695
|
+
manipulator = null;
|
|
3696
|
+
pendingPreview = null;
|
|
2876
3697
|
closeGuidePreview();
|
|
2877
3698
|
break;
|
|
2878
3699
|
case "teardown":
|
|
@@ -2892,6 +3713,9 @@ function initBuilderMode() {
|
|
|
2892
3713
|
window.removeEventListener("pagehide", onPageHide);
|
|
2893
3714
|
picker?.stop();
|
|
2894
3715
|
picker = null;
|
|
3716
|
+
manipulator?.detach();
|
|
3717
|
+
manipulator = null;
|
|
3718
|
+
pendingPreview = null;
|
|
2895
3719
|
closeGuidePreview();
|
|
2896
3720
|
post({ type: "closed" });
|
|
2897
3721
|
panel?.teardown();
|
|
@@ -2971,6 +3795,7 @@ var init_builder_mode = __esm({
|
|
|
2971
3795
|
init_guide_preview();
|
|
2972
3796
|
init_bridge_protocol();
|
|
2973
3797
|
init_builder_panel();
|
|
3798
|
+
init_design_manipulator();
|
|
2974
3799
|
init_element_picker();
|
|
2975
3800
|
}
|
|
2976
3801
|
});
|
|
@@ -3104,6 +3929,7 @@ var init_builder_session = __esm({
|
|
|
3104
3929
|
// src/plugins/builder/index.ts
|
|
3105
3930
|
var builder_exports = {};
|
|
3106
3931
|
__export(builder_exports, {
|
|
3932
|
+
attachDesignManipulator: () => attachDesignManipulator,
|
|
3107
3933
|
createBuilderSession: () => createBuilderSession,
|
|
3108
3934
|
initBuilderMode: () => initBuilderMode,
|
|
3109
3935
|
startElementPicker: () => startElementPicker,
|
|
@@ -3113,6 +3939,7 @@ var init_builder = __esm({
|
|
|
3113
3939
|
"src/plugins/builder/index.ts"() {
|
|
3114
3940
|
init_builder_mode();
|
|
3115
3941
|
init_builder_session();
|
|
3942
|
+
init_design_manipulator();
|
|
3116
3943
|
init_element_picker();
|
|
3117
3944
|
}
|
|
3118
3945
|
});
|
|
@@ -3379,8 +4206,8 @@ var DomListener = class {
|
|
|
3379
4206
|
let depth = 0;
|
|
3380
4207
|
while (current && current !== document.documentElement && depth < 5) {
|
|
3381
4208
|
const tag = current.tagName.toLowerCase();
|
|
3382
|
-
const
|
|
3383
|
-
if (
|
|
4209
|
+
const isInteractive2 = tag === "button" || tag === "a" || tag === "input" || tag === "select" || tag === "textarea" || current.hasAttribute("data-veo-tag") || current.hasAttribute("role") || current.hasAttribute("onclick") || current instanceof HTMLElement && current.style.cursor === "pointer";
|
|
4210
|
+
if (isInteractive2) return current;
|
|
3384
4211
|
current = current.parentElement;
|
|
3385
4212
|
depth++;
|
|
3386
4213
|
}
|
|
@@ -3502,7 +4329,8 @@ var Queue = class {
|
|
|
3502
4329
|
return;
|
|
3503
4330
|
}
|
|
3504
4331
|
this.retryCount++;
|
|
3505
|
-
const
|
|
4332
|
+
const base = Math.min(1e3 * 2 ** (this.retryCount - 1), RETRY_MAX_DELAY_MS);
|
|
4333
|
+
const delayMs = base / 2 + Math.random() * (base / 2);
|
|
3506
4334
|
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
3507
4335
|
this.retryTimer = setTimeout(() => {
|
|
3508
4336
|
void this.flush();
|
|
@@ -4181,6 +5009,7 @@ function toWire(payload) {
|
|
|
4181
5009
|
if (typeof payload.stepIndex === "number") wire.stepPosition = payload.stepIndex;
|
|
4182
5010
|
if (payload.pageUrl) wire.pageUrl = payload.pageUrl;
|
|
4183
5011
|
if (payload.pagePath) wire.pagePath = payload.pagePath;
|
|
5012
|
+
if (payload.metadata) wire.metadata = payload.metadata;
|
|
4184
5013
|
return wire;
|
|
4185
5014
|
}
|
|
4186
5015
|
var GuideTrackerClient = class {
|
|
@@ -4228,6 +5057,8 @@ var GuideTrackerClient = class {
|
|
|
4228
5057
|
};
|
|
4229
5058
|
|
|
4230
5059
|
// src/plugins/guides/guide-tracker-queue.ts
|
|
5060
|
+
var RETRY_BASE_MS = 3e3;
|
|
5061
|
+
var RETRY_MAX_DELAY_MS2 = 3e4;
|
|
4231
5062
|
var GuideTrackerQueue = class {
|
|
4232
5063
|
constructor(config) {
|
|
4233
5064
|
this.config = config;
|
|
@@ -4264,20 +5095,23 @@ var GuideTrackerQueue = class {
|
|
|
4264
5095
|
*/
|
|
4265
5096
|
async flush() {
|
|
4266
5097
|
if (this.flushing || this.buffer.length === 0) return;
|
|
5098
|
+
const now = Date.now();
|
|
5099
|
+
const due = this.buffer.filter((item) => (item.nextRetryAt ?? 0) <= now);
|
|
5100
|
+
if (due.length === 0) return;
|
|
4267
5101
|
this.flushing = true;
|
|
4268
|
-
const
|
|
5102
|
+
const dueSet = new Set(due);
|
|
5103
|
+
this.buffer = this.buffer.filter((item) => !dueSet.has(item));
|
|
4269
5104
|
const results = await Promise.allSettled(
|
|
4270
|
-
|
|
4271
|
-
(item) => this.config.client.send({ guideId: item.guideId, payload: item.payload })
|
|
4272
|
-
)
|
|
5105
|
+
due.map((item) => this.config.client.send({ guideId: item.guideId, payload: item.payload }))
|
|
4273
5106
|
);
|
|
4274
5107
|
for (let i = 0; i < results.length; i++) {
|
|
4275
5108
|
const result = results[i];
|
|
4276
|
-
const item =
|
|
5109
|
+
const item = due[i];
|
|
4277
5110
|
if (!result || !item) continue;
|
|
4278
5111
|
if (result.status === "fulfilled") continue;
|
|
4279
5112
|
item.attempts += 1;
|
|
4280
5113
|
if (item.attempts < this.config.maxRetries) {
|
|
5114
|
+
item.nextRetryAt = Date.now() + this.retryDelayMs(item.attempts);
|
|
4281
5115
|
this.buffer.push(item);
|
|
4282
5116
|
} else {
|
|
4283
5117
|
this.safeOnError(result.reason, item);
|
|
@@ -4285,6 +5119,15 @@ var GuideTrackerQueue = class {
|
|
|
4285
5119
|
}
|
|
4286
5120
|
this.flushing = false;
|
|
4287
5121
|
}
|
|
5122
|
+
/**
|
|
5123
|
+
* Backoff exponencial (base = intervalo de flush) con equal-jitter: mitad
|
|
5124
|
+
* fija + mitad aleatoria. Desincroniza los reintentos entre clientes ante
|
|
5125
|
+
* una caída del backend.
|
|
5126
|
+
*/
|
|
5127
|
+
retryDelayMs(attempts) {
|
|
5128
|
+
const base = Math.min(RETRY_BASE_MS * 2 ** (attempts - 1), RETRY_MAX_DELAY_MS2);
|
|
5129
|
+
return base / 2 + Math.random() * (base / 2);
|
|
5130
|
+
}
|
|
4288
5131
|
/**
|
|
4289
5132
|
* Drena el buffer vía `sendBeacon`. Síncrono, sin retry. Para pagehide.
|
|
4290
5133
|
*/
|
|
@@ -4331,6 +5174,7 @@ var GuideTrackerQueue = class {
|
|
|
4331
5174
|
};
|
|
4332
5175
|
|
|
4333
5176
|
// src/plugins/guides/guides-controller.ts
|
|
5177
|
+
init_badge_renderer();
|
|
4334
5178
|
init_banner_renderer();
|
|
4335
5179
|
init_custom_renderer();
|
|
4336
5180
|
init_form_renderer();
|
|
@@ -4458,6 +5302,7 @@ var WalkthroughStateManager = class {
|
|
|
4458
5302
|
};
|
|
4459
5303
|
|
|
4460
5304
|
// src/plugins/guides/guides-controller.ts
|
|
5305
|
+
var RESOLVE_CACHE_TTL_MS = 3e4;
|
|
4461
5306
|
var STATUS_BY_ACTION = {
|
|
4462
5307
|
shown: "shown",
|
|
4463
5308
|
dismissed: "dismissed",
|
|
@@ -4484,6 +5329,10 @@ var GuidesController = class {
|
|
|
4484
5329
|
*/
|
|
4485
5330
|
this.activeWalkthroughRenderer = null;
|
|
4486
5331
|
this.activeWalkthroughGuide = null;
|
|
5332
|
+
/** Última resolución cacheada (mismo `(endUserId, url)` → sin red). */
|
|
5333
|
+
this.resolveCache = null;
|
|
5334
|
+
/** Resolución idéntica en vuelo, para coalescer llamadas concurrentes. */
|
|
5335
|
+
this.inflightResolve = null;
|
|
4487
5336
|
this.debug = config.debug === true;
|
|
4488
5337
|
this.apiUrl = config.apiUrl;
|
|
4489
5338
|
this.apiKey = config.apiKey;
|
|
@@ -4535,7 +5384,7 @@ var GuidesController = class {
|
|
|
4535
5384
|
}
|
|
4536
5385
|
let guides;
|
|
4537
5386
|
try {
|
|
4538
|
-
guides = await this.
|
|
5387
|
+
guides = await this.resolveGuides(endUserId, pageUrl);
|
|
4539
5388
|
} catch (err) {
|
|
4540
5389
|
if (this.debug) console.error("[veo] guides resolver threw:", err);
|
|
4541
5390
|
return;
|
|
@@ -4562,6 +5411,36 @@ var GuidesController = class {
|
|
|
4562
5411
|
this.runSingleStepRender(guide, sessionId, pageUrl);
|
|
4563
5412
|
}
|
|
4564
5413
|
}
|
|
5414
|
+
/**
|
|
5415
|
+
* `resolve` con cache de corta duración + coalescing:
|
|
5416
|
+
* - Mismo `(endUserId, url)` resuelto hace < {@link RESOLVE_CACHE_TTL_MS} →
|
|
5417
|
+
* se reusa sin red. Colapsa el doble disparo identify+pageview y los
|
|
5418
|
+
* pageviews repetidos de una SPA hacia la misma URL.
|
|
5419
|
+
* - Si ya hay un resolve idéntico en vuelo, se espera ESE (no se abre otro).
|
|
5420
|
+
* El re-render sigue siendo idempotente (dedupe por `activeByGuideId` +
|
|
5421
|
+
* `frequencyCache`), así que reusar el resultado no duplica guías.
|
|
5422
|
+
*/
|
|
5423
|
+
async resolveGuides(endUserId, pageUrl) {
|
|
5424
|
+
const key = `${endUserId}
|
|
5425
|
+
${pageUrl}`;
|
|
5426
|
+
const now = Date.now();
|
|
5427
|
+
if (this.resolveCache?.key === key && now - this.resolveCache.at < RESOLVE_CACHE_TTL_MS) {
|
|
5428
|
+
if (this.debug) console.log("[veo] guides: resolve cache hit");
|
|
5429
|
+
return this.resolveCache.guides;
|
|
5430
|
+
}
|
|
5431
|
+
if (this.inflightResolve?.key === key) {
|
|
5432
|
+
return this.inflightResolve.promise;
|
|
5433
|
+
}
|
|
5434
|
+
const promise = this.resolver.resolve(endUserId, pageUrl);
|
|
5435
|
+
this.inflightResolve = { key, promise };
|
|
5436
|
+
try {
|
|
5437
|
+
const guides = await promise;
|
|
5438
|
+
this.resolveCache = { key, at: Date.now(), guides };
|
|
5439
|
+
return guides;
|
|
5440
|
+
} finally {
|
|
5441
|
+
if (this.inflightResolve?.key === key) this.inflightResolve = null;
|
|
5442
|
+
}
|
|
5443
|
+
}
|
|
4565
5444
|
/** Cierra todas las guías activas, limpia cache de dispatch y para timers. */
|
|
4566
5445
|
destroy() {
|
|
4567
5446
|
for (const renderer of this.activeRenderers) {
|
|
@@ -4582,6 +5461,8 @@ var GuidesController = class {
|
|
|
4582
5461
|
this.activeWalkthroughGuide = null;
|
|
4583
5462
|
}
|
|
4584
5463
|
this.dispatched.clear();
|
|
5464
|
+
this.resolveCache = null;
|
|
5465
|
+
this.inflightResolve = null;
|
|
4585
5466
|
this.trackerQueue.destroy();
|
|
4586
5467
|
}
|
|
4587
5468
|
runSingleStepRender(guide, sessionId, pageUrl) {
|
|
@@ -4806,7 +5687,8 @@ var GuidesController = class {
|
|
|
4806
5687
|
* por si el usuario se identificó después de que la guía apareció.
|
|
4807
5688
|
*/
|
|
4808
5689
|
handleInteraction(guide, sessionId, pageUrl, event) {
|
|
4809
|
-
const
|
|
5690
|
+
const buttonKey = event.metadata?.buttonId ? `:${event.metadata.buttonId}` : "";
|
|
5691
|
+
const dedupKey = `${guide.guideId}:${event.action}${buttonKey}`;
|
|
4810
5692
|
if (this.dispatched.has(dedupKey)) {
|
|
4811
5693
|
if (this.debug) console.log(`[veo] guides: dedup hit ${dedupKey}`);
|
|
4812
5694
|
return;
|
|
@@ -4828,7 +5710,8 @@ var GuidesController = class {
|
|
|
4828
5710
|
action: event.action,
|
|
4829
5711
|
stepIndex: event.stepIndex,
|
|
4830
5712
|
pageUrl,
|
|
4831
|
-
pagePath: extractPath(pageUrl)
|
|
5713
|
+
pagePath: extractPath(pageUrl),
|
|
5714
|
+
...event.metadata ? { metadata: event.metadata } : {}
|
|
4832
5715
|
}
|
|
4833
5716
|
});
|
|
4834
5717
|
}
|
|
@@ -4879,6 +5762,8 @@ var GuidesController = class {
|
|
|
4879
5762
|
return new FormRenderer();
|
|
4880
5763
|
case "inline-form":
|
|
4881
5764
|
return new InlineFormRenderer();
|
|
5765
|
+
case "badge":
|
|
5766
|
+
return new BadgeRenderer();
|
|
4882
5767
|
case "walkthrough":
|
|
4883
5768
|
return null;
|
|
4884
5769
|
}
|