veo-sdk 0.3.15 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder-ZDNVF6KG.mjs +5 -0
- package/dist/{builder-BKLDSVN5.mjs.map → builder-ZDNVF6KG.mjs.map} +1 -1
- package/dist/builder.cjs +1085 -204
- 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-EF4BZR5R.mjs → chunk-I55Z7UGJ.mjs} +269 -5
- package/dist/chunk-I55Z7UGJ.mjs.map +1 -0
- package/dist/{chunk-2INDL77W.mjs → chunk-ICKEQ7VC.mjs} +797 -180
- package/dist/chunk-ICKEQ7VC.mjs.map +1 -0
- package/dist/{chunk-WVLVN422.mjs → chunk-MA2BXLZ7.mjs} +67 -11
- package/dist/chunk-MA2BXLZ7.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 +1180 -211
- 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 +1 -1
- package/dist/veo.js.map +1 -1
- package/package.json +1 -1
- package/dist/builder-BKLDSVN5.mjs +0 -5
- package/dist/chunk-2INDL77W.mjs.map +0 -1
- package/dist/chunk-EF4BZR5R.mjs.map +0 -1
- package/dist/chunk-WVLVN422.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -374,6 +374,118 @@ var init_constants2 = __esm({
|
|
|
374
374
|
}
|
|
375
375
|
});
|
|
376
376
|
|
|
377
|
+
// src/plugins/guides/rich-text.ts
|
|
378
|
+
function renderRichText(html, doc) {
|
|
379
|
+
const fragment = doc.createDocumentFragment();
|
|
380
|
+
if (typeof html !== "string" || html.length === 0) return fragment;
|
|
381
|
+
let parsed;
|
|
382
|
+
try {
|
|
383
|
+
parsed = new DOMParser().parseFromString(
|
|
384
|
+
html.length > MAX_INPUT_LEN ? html.slice(0, MAX_INPUT_LEN) : html,
|
|
385
|
+
"text/html"
|
|
386
|
+
);
|
|
387
|
+
} catch {
|
|
388
|
+
fragment.appendChild(doc.createTextNode(html));
|
|
389
|
+
return fragment;
|
|
390
|
+
}
|
|
391
|
+
const budget = { nodes: 0, exceeded: false };
|
|
392
|
+
for (const child of Array.from(parsed.body.childNodes)) {
|
|
393
|
+
const rebuilt = rebuildNode(child, doc, 0, budget);
|
|
394
|
+
if (rebuilt) fragment.appendChild(rebuilt);
|
|
395
|
+
}
|
|
396
|
+
if (budget.exceeded) {
|
|
397
|
+
const plain = doc.createDocumentFragment();
|
|
398
|
+
plain.appendChild(doc.createTextNode(parsed.body.textContent ?? ""));
|
|
399
|
+
return plain;
|
|
400
|
+
}
|
|
401
|
+
return fragment;
|
|
402
|
+
}
|
|
403
|
+
function rebuildNode(node, doc, depth, budget) {
|
|
404
|
+
if (budget.exceeded) return null;
|
|
405
|
+
if (++budget.nodes > MAX_NODES || depth > MAX_DEPTH) {
|
|
406
|
+
budget.exceeded = true;
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
410
|
+
return doc.createTextNode(node.textContent ?? "");
|
|
411
|
+
}
|
|
412
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return null;
|
|
413
|
+
const el = node;
|
|
414
|
+
if (DROP_TAGS.has(el.tagName)) return null;
|
|
415
|
+
const mapped = ALLOWED_TAGS[el.tagName];
|
|
416
|
+
if (!mapped) {
|
|
417
|
+
const frag = doc.createDocumentFragment();
|
|
418
|
+
for (const child of Array.from(el.childNodes)) {
|
|
419
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
420
|
+
if (rebuilt2) frag.appendChild(rebuilt2);
|
|
421
|
+
}
|
|
422
|
+
return frag;
|
|
423
|
+
}
|
|
424
|
+
if (mapped === "a") {
|
|
425
|
+
const href = el.getAttribute("href") ?? "";
|
|
426
|
+
if (!isSafeUrl(href)) {
|
|
427
|
+
const frag = doc.createDocumentFragment();
|
|
428
|
+
for (const child of Array.from(el.childNodes)) {
|
|
429
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
430
|
+
if (rebuilt2) frag.appendChild(rebuilt2);
|
|
431
|
+
}
|
|
432
|
+
return frag;
|
|
433
|
+
}
|
|
434
|
+
const a = doc.createElement("a");
|
|
435
|
+
a.setAttribute("href", href);
|
|
436
|
+
a.setAttribute("target", "_blank");
|
|
437
|
+
a.setAttribute("rel", "noopener noreferrer nofollow");
|
|
438
|
+
for (const child of Array.from(el.childNodes)) {
|
|
439
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
440
|
+
if (rebuilt2) a.appendChild(rebuilt2);
|
|
441
|
+
}
|
|
442
|
+
return a;
|
|
443
|
+
}
|
|
444
|
+
const rebuilt = doc.createElement(mapped);
|
|
445
|
+
for (const child of Array.from(el.childNodes)) {
|
|
446
|
+
const childNode = rebuildNode(child, doc, depth + 1, budget);
|
|
447
|
+
if (childNode) rebuilt.appendChild(childNode);
|
|
448
|
+
}
|
|
449
|
+
return rebuilt;
|
|
450
|
+
}
|
|
451
|
+
var ALLOWED_TAGS, DROP_TAGS, MAX_INPUT_LEN, MAX_DEPTH, MAX_NODES;
|
|
452
|
+
var init_rich_text = __esm({
|
|
453
|
+
"src/plugins/guides/rich-text.ts"() {
|
|
454
|
+
init_block_builder();
|
|
455
|
+
ALLOWED_TAGS = {
|
|
456
|
+
P: "p",
|
|
457
|
+
BR: "br",
|
|
458
|
+
STRONG: "strong",
|
|
459
|
+
B: "strong",
|
|
460
|
+
EM: "em",
|
|
461
|
+
I: "em",
|
|
462
|
+
U: "u",
|
|
463
|
+
S: "s",
|
|
464
|
+
UL: "ul",
|
|
465
|
+
OL: "ol",
|
|
466
|
+
LI: "li",
|
|
467
|
+
A: "a"
|
|
468
|
+
};
|
|
469
|
+
DROP_TAGS = /* @__PURE__ */ new Set([
|
|
470
|
+
"SCRIPT",
|
|
471
|
+
"STYLE",
|
|
472
|
+
"TEMPLATE",
|
|
473
|
+
"IFRAME",
|
|
474
|
+
"OBJECT",
|
|
475
|
+
"EMBED",
|
|
476
|
+
"NOSCRIPT",
|
|
477
|
+
"TITLE",
|
|
478
|
+
"TEXTAREA",
|
|
479
|
+
"SELECT",
|
|
480
|
+
"SVG",
|
|
481
|
+
"MATH"
|
|
482
|
+
]);
|
|
483
|
+
MAX_INPUT_LEN = 16 * 1024;
|
|
484
|
+
MAX_DEPTH = 20;
|
|
485
|
+
MAX_NODES = 1e3;
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
|
|
377
489
|
// src/plugins/guides/block-builder.ts
|
|
378
490
|
function buildStepContent(step, doc, callbacks) {
|
|
379
491
|
const container = doc.createElement("div");
|
|
@@ -468,9 +580,13 @@ function buildContentBlock(block, doc) {
|
|
|
468
580
|
return el;
|
|
469
581
|
}
|
|
470
582
|
case "text": {
|
|
471
|
-
const el = doc.createElement("
|
|
583
|
+
const el = doc.createElement("div");
|
|
472
584
|
el.className = "veo-guide-text";
|
|
473
|
-
|
|
585
|
+
if (typeof block.html === "string" && block.html) {
|
|
586
|
+
el.appendChild(renderRichText(block.html, doc));
|
|
587
|
+
} else {
|
|
588
|
+
el.textContent = typeof block.text === "string" ? block.text : "";
|
|
589
|
+
}
|
|
474
590
|
applyBlockStyle(el, block.style, "text");
|
|
475
591
|
return el;
|
|
476
592
|
}
|
|
@@ -496,7 +612,11 @@ function buildButtonBlock(block, doc, callbacks) {
|
|
|
496
612
|
btn.addEventListener("click", () => {
|
|
497
613
|
const action = block.action ?? "dismiss";
|
|
498
614
|
const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
|
|
499
|
-
|
|
615
|
+
const meta = {
|
|
616
|
+
...typeof block.id === "string" && block.id ? { buttonId: block.id } : {},
|
|
617
|
+
...typeof block.text === "string" && block.text ? { buttonText: block.text } : {}
|
|
618
|
+
};
|
|
619
|
+
callbacks.onCtaClick(action, url, Object.keys(meta).length ? meta : void 0);
|
|
500
620
|
});
|
|
501
621
|
return btn;
|
|
502
622
|
}
|
|
@@ -506,6 +626,10 @@ function clampNum(n, min, max) {
|
|
|
506
626
|
function applyBlockStyle(el, style, kind) {
|
|
507
627
|
if (!style || typeof style !== "object") return;
|
|
508
628
|
const s = style;
|
|
629
|
+
if (kind === "image" && s.bleed === true) {
|
|
630
|
+
el.classList.add("veo-guide-image--bleed");
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
509
633
|
const align = typeof s.align === "string" ? s.align : null;
|
|
510
634
|
if (align && (align === "left" || align === "center" || align === "right")) {
|
|
511
635
|
if (kind === "text") el.style.textAlign = align;
|
|
@@ -556,6 +680,7 @@ var BLOCK_ALIGN_SELF;
|
|
|
556
680
|
var init_block_builder = __esm({
|
|
557
681
|
"src/plugins/guides/block-builder.ts"() {
|
|
558
682
|
init_constants2();
|
|
683
|
+
init_rich_text();
|
|
559
684
|
BLOCK_ALIGN_SELF = {
|
|
560
685
|
left: "flex-start",
|
|
561
686
|
center: "center",
|
|
@@ -564,83 +689,6 @@ var init_block_builder = __esm({
|
|
|
564
689
|
}
|
|
565
690
|
});
|
|
566
691
|
|
|
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
692
|
// src/plugins/guides/guide-design.ts
|
|
645
693
|
function clamp(n, min, max) {
|
|
646
694
|
return Math.min(max, Math.max(min, n));
|
|
@@ -674,6 +722,9 @@ function applyDesignVars(host, style) {
|
|
|
674
722
|
if (typeof s.width === "number" && Number.isFinite(s.width)) {
|
|
675
723
|
host.style.setProperty("--veo-width", `${clamp(s.width, 220, 720)}px`);
|
|
676
724
|
}
|
|
725
|
+
if (typeof s.height === "number" && Number.isFinite(s.height)) {
|
|
726
|
+
host.style.setProperty("--veo-min-h", `${clamp(s.height, 120, 900)}px`);
|
|
727
|
+
}
|
|
677
728
|
if (s.align === "left" || s.align === "center" || s.align === "right") {
|
|
678
729
|
host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[s.align]);
|
|
679
730
|
}
|
|
@@ -803,6 +854,77 @@ var init_guide_design = __esm({
|
|
|
803
854
|
}
|
|
804
855
|
});
|
|
805
856
|
|
|
857
|
+
// src/plugins/guides/inline-host.ts
|
|
858
|
+
function readInlinePosition(style) {
|
|
859
|
+
const p = style?.inlinePosition;
|
|
860
|
+
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
861
|
+
}
|
|
862
|
+
function insertHost(anchor, host, position) {
|
|
863
|
+
switch (position) {
|
|
864
|
+
case "before":
|
|
865
|
+
anchor.before(host);
|
|
866
|
+
break;
|
|
867
|
+
case "after":
|
|
868
|
+
anchor.after(host);
|
|
869
|
+
break;
|
|
870
|
+
case "prepend":
|
|
871
|
+
anchor.prepend(host);
|
|
872
|
+
break;
|
|
873
|
+
case "append":
|
|
874
|
+
anchor.append(host);
|
|
875
|
+
break;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
function keepHostAttached(host, selector, position) {
|
|
879
|
+
const id = window.setInterval(() => {
|
|
880
|
+
if (host.isConnected) return;
|
|
881
|
+
const anchor = document.querySelector(selector);
|
|
882
|
+
if (anchor) insertHost(anchor, host, position);
|
|
883
|
+
}, 1e3);
|
|
884
|
+
return () => window.clearInterval(id);
|
|
885
|
+
}
|
|
886
|
+
var init_inline_host = __esm({
|
|
887
|
+
"src/plugins/guides/inline-host.ts"() {
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// src/plugins/guides/wait-for-element.ts
|
|
892
|
+
function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
|
|
893
|
+
return new Promise((resolve) => {
|
|
894
|
+
const safeQuery = () => {
|
|
895
|
+
try {
|
|
896
|
+
return document.querySelector(selector);
|
|
897
|
+
} catch {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
const existing = safeQuery();
|
|
902
|
+
if (existing) {
|
|
903
|
+
resolve(existing);
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
let resolved = false;
|
|
907
|
+
const finish = (el) => {
|
|
908
|
+
if (resolved) return;
|
|
909
|
+
resolved = true;
|
|
910
|
+
observer.disconnect();
|
|
911
|
+
clearTimeout(timer);
|
|
912
|
+
resolve(el);
|
|
913
|
+
};
|
|
914
|
+
const observer = new MutationObserver(() => {
|
|
915
|
+
const el = safeQuery();
|
|
916
|
+
if (el) finish(el);
|
|
917
|
+
});
|
|
918
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
919
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
var init_wait_for_element = __esm({
|
|
923
|
+
"src/plugins/guides/wait-for-element.ts"() {
|
|
924
|
+
init_constants2();
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
|
|
806
928
|
// src/plugins/guides/styles.ts
|
|
807
929
|
var GUIDE_STYLES;
|
|
808
930
|
var init_styles = __esm({
|
|
@@ -828,6 +950,13 @@ var init_styles = __esm({
|
|
|
828
950
|
z-index: ${GUIDE_Z_INDEX};
|
|
829
951
|
animation: veo-fade-in 180ms ease-out;
|
|
830
952
|
}
|
|
953
|
+
/* Sin backdrop (style.backdrop === false): la app queda usable detr\xE1s; solo la
|
|
954
|
+
tarjeta captura el mouse. El click-en-backdrop deja de cerrar (no hay backdrop). */
|
|
955
|
+
.veo-modal-overlay--none {
|
|
956
|
+
background: transparent;
|
|
957
|
+
pointer-events: none;
|
|
958
|
+
}
|
|
959
|
+
.veo-modal-overlay--none .veo-modal-card { pointer-events: auto; }
|
|
831
960
|
/*
|
|
832
961
|
* Posici\xF3n libre/preset: --veo-pos-x/y son porcentajes (default 50% = centro).
|
|
833
962
|
* El truco translate(-pos) alinea la MISMA fracci\xF3n de la tarjeta con esa
|
|
@@ -844,6 +973,7 @@ var init_styles = __esm({
|
|
|
844
973
|
border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
|
|
845
974
|
padding: var(--veo-pad, 24px);
|
|
846
975
|
max-width: var(--veo-width); width: 90%;
|
|
976
|
+
min-height: var(--veo-min-h, auto);
|
|
847
977
|
box-shadow: var(--veo-shadow);
|
|
848
978
|
animation: veo-fade-in 180ms ease-out;
|
|
849
979
|
}
|
|
@@ -859,6 +989,14 @@ var init_styles = __esm({
|
|
|
859
989
|
}
|
|
860
990
|
.veo-banner-top { top: 0; }
|
|
861
991
|
.veo-banner-bottom { bottom: 0; }
|
|
992
|
+
/* Banner EMBEBIDO en un contenedor (step.selector): fluye dentro del contenedor
|
|
993
|
+
y empuja su contenido, en vez de flotar fijo sobre la pantalla. */
|
|
994
|
+
.veo-banner-embedded {
|
|
995
|
+
position: static;
|
|
996
|
+
left: auto; right: auto;
|
|
997
|
+
width: 100%;
|
|
998
|
+
border-radius: var(--veo-radius, 0);
|
|
999
|
+
}
|
|
862
1000
|
|
|
863
1001
|
.veo-tooltip {
|
|
864
1002
|
position: absolute;
|
|
@@ -912,6 +1050,21 @@ var init_styles = __esm({
|
|
|
912
1050
|
border-radius: var(--veo-image-radius, 8px);
|
|
913
1051
|
margin: var(--veo-image-mt, 0) 0 var(--veo-image-mb, 12px);
|
|
914
1052
|
}
|
|
1053
|
+
/* Imagen A SANGRE: rompe el padding de la tarjeta y ocupa el ancho completo
|
|
1054
|
+
(hero estilo anuncio). Como primer bloque, hereda el redondeo superior. */
|
|
1055
|
+
.veo-guide-image--bleed {
|
|
1056
|
+
width: calc(100% + var(--veo-pad, 24px) * 2);
|
|
1057
|
+
max-width: none;
|
|
1058
|
+
max-height: 280px;
|
|
1059
|
+
align-self: auto;
|
|
1060
|
+
border-radius: 0;
|
|
1061
|
+
margin: 0 calc(var(--veo-pad, 24px) * -1) 12px;
|
|
1062
|
+
}
|
|
1063
|
+
.veo-guide-content > .veo-guide-image--bleed:first-child {
|
|
1064
|
+
margin-top: calc(var(--veo-pad, 24px) * -1);
|
|
1065
|
+
border-radius: calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px))
|
|
1066
|
+
calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px)) 0 0;
|
|
1067
|
+
}
|
|
915
1068
|
.veo-guide-title {
|
|
916
1069
|
font-size: var(--veo-title-size, 18px); font-weight: 600; line-height: 1.3;
|
|
917
1070
|
text-align: var(--veo-title-align, left);
|
|
@@ -924,6 +1077,18 @@ var init_styles = __esm({
|
|
|
924
1077
|
margin: var(--veo-text-mt, 0) 0 var(--veo-text-mb, 16px);
|
|
925
1078
|
color: var(--veo-text-color, var(--veo-text-secondary));
|
|
926
1079
|
}
|
|
1080
|
+
/* Rich text dentro de un bloque de texto (p/listas/links/\xE9nfasis). */
|
|
1081
|
+
.veo-guide-text p { margin: 0 0 8px; }
|
|
1082
|
+
.veo-guide-text p:last-child { margin-bottom: 0; }
|
|
1083
|
+
.veo-guide-text ul, .veo-guide-text ol { margin: 0 0 8px; padding-left: 20px; }
|
|
1084
|
+
.veo-guide-text ul { list-style: disc; }
|
|
1085
|
+
.veo-guide-text ol { list-style: decimal; }
|
|
1086
|
+
.veo-guide-text li { margin: 2px 0; display: list-item; }
|
|
1087
|
+
.veo-guide-text a { color: var(--veo-primary); text-decoration: underline; cursor: pointer; }
|
|
1088
|
+
.veo-guide-text strong { font-weight: 600; }
|
|
1089
|
+
.veo-guide-text em { font-style: italic; }
|
|
1090
|
+
.veo-guide-text u { text-decoration: underline; }
|
|
1091
|
+
.veo-guide-text s { text-decoration: line-through; }
|
|
927
1092
|
.veo-guide-actions {
|
|
928
1093
|
display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
|
|
929
1094
|
}
|
|
@@ -1062,6 +1227,44 @@ var init_styles = __esm({
|
|
|
1062
1227
|
}
|
|
1063
1228
|
.veo-walkthrough-skip:hover { color: var(--veo-text); }
|
|
1064
1229
|
|
|
1230
|
+
/* \u2500\u2500 Badge (elemento inyectado junto al ancla que abre un tooltip) \u2500\u2500 */
|
|
1231
|
+
.veo-badge {
|
|
1232
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
1233
|
+
border: none; padding: 0; margin: 0 4px;
|
|
1234
|
+
background: none; cursor: pointer;
|
|
1235
|
+
line-height: 1; vertical-align: middle;
|
|
1236
|
+
font-family: inherit;
|
|
1237
|
+
}
|
|
1238
|
+
.veo-badge:focus-visible { outline: 2px solid var(--veo-primary); outline-offset: 2px; }
|
|
1239
|
+
.veo-badge--icon {
|
|
1240
|
+
border-radius: 50%;
|
|
1241
|
+
background: color-mix(in srgb, var(--veo-primary) 14%, transparent);
|
|
1242
|
+
color: var(--veo-primary);
|
|
1243
|
+
font-weight: 600;
|
|
1244
|
+
}
|
|
1245
|
+
.veo-badge--dot {
|
|
1246
|
+
border-radius: 50%;
|
|
1247
|
+
background: var(--veo-primary);
|
|
1248
|
+
animation: veo-badge-pulse 2s ease-out infinite;
|
|
1249
|
+
}
|
|
1250
|
+
.veo-badge--pill {
|
|
1251
|
+
border-radius: 999px;
|
|
1252
|
+
background: var(--veo-primary);
|
|
1253
|
+
color: #fff;
|
|
1254
|
+
font-weight: 600;
|
|
1255
|
+
padding: 3px 9px;
|
|
1256
|
+
white-space: nowrap;
|
|
1257
|
+
}
|
|
1258
|
+
.veo-badge--image img { display: block; border-radius: 4px; object-fit: cover; }
|
|
1259
|
+
/* El tooltip del badge usa strategy fixed (el host vive dentro del flujo del
|
|
1260
|
+
cliente; un absoluto se recortar\xEDa con overflow de ancestros). */
|
|
1261
|
+
.veo-badge-tooltip { position: fixed; }
|
|
1262
|
+
@keyframes veo-badge-pulse {
|
|
1263
|
+
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--veo-primary) 45%, transparent); }
|
|
1264
|
+
70% { box-shadow: 0 0 0 7px transparent; }
|
|
1265
|
+
100% { box-shadow: 0 0 0 0 transparent; }
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1065
1268
|
.veo-custom-floating {
|
|
1066
1269
|
position: fixed;
|
|
1067
1270
|
z-index: ${GUIDE_Z_INDEX};
|
|
@@ -1114,6 +1317,37 @@ var init_base_renderer = __esm({
|
|
|
1114
1317
|
this.host = null;
|
|
1115
1318
|
this.shadow = null;
|
|
1116
1319
|
this.cleanups = [];
|
|
1320
|
+
// ── Update in-place (preview en vivo del editor) ──
|
|
1321
|
+
// La subclase setea estas piezas en render() para habilitarlo: el contenedor
|
|
1322
|
+
// del contenido, el nodo de contenido actual y cómo reconstruirlo.
|
|
1323
|
+
this.liveContainer = null;
|
|
1324
|
+
this.liveContent = null;
|
|
1325
|
+
this.liveBuild = null;
|
|
1326
|
+
/** Clave de anclaje/estructura fijada en render(); si cambia → remontar. */
|
|
1327
|
+
this.liveKey = "";
|
|
1328
|
+
}
|
|
1329
|
+
/** Clave de anclaje derivada del step (override por subclase). */
|
|
1330
|
+
liveKeyOf(_step) {
|
|
1331
|
+
return "";
|
|
1332
|
+
}
|
|
1333
|
+
/** Hook post-swap (reposicionar tooltip, actualizar clase del banner…). */
|
|
1334
|
+
onLiveUpdate(_step) {
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Actualiza el contenido del step SIN remontar el host — así el preview del
|
|
1338
|
+
* editor no parpadea en cada tecla. Devuelve `false` cuando el renderer no
|
|
1339
|
+
* lo soporta o el cambio requiere remontar (cambió el ancla/estructura);
|
|
1340
|
+
* en ese caso el caller hace el re-render completo.
|
|
1341
|
+
*/
|
|
1342
|
+
updateStep(step) {
|
|
1343
|
+
if (!this.host || !this.liveContainer || !this.liveContent || !this.liveBuild) return false;
|
|
1344
|
+
if (this.liveKeyOf(step) !== this.liveKey) return false;
|
|
1345
|
+
const next = this.liveBuild(step);
|
|
1346
|
+
this.liveContainer.replaceChild(next, this.liveContent);
|
|
1347
|
+
this.liveContent = next;
|
|
1348
|
+
this.applyDesign(step.style);
|
|
1349
|
+
this.onLiveUpdate(step);
|
|
1350
|
+
return true;
|
|
1117
1351
|
}
|
|
1118
1352
|
/**
|
|
1119
1353
|
* Crea un `<div data-veo-guide>` adjunto a `document.body`, le adjunta
|
|
@@ -1149,6 +1383,14 @@ var init_base_renderer = __esm({
|
|
|
1149
1383
|
registerCleanup(fn) {
|
|
1150
1384
|
this.cleanups.push(fn);
|
|
1151
1385
|
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Host de la guía montada (o `null` si aún no se montó / ya se destruyó).
|
|
1388
|
+
* Lo usa el modo builder para adjuntar manipulación directa (drag/resize)
|
|
1389
|
+
* sobre el shadow root abierto sin tocar los renderers.
|
|
1390
|
+
*/
|
|
1391
|
+
hostElement() {
|
|
1392
|
+
return this.host;
|
|
1393
|
+
}
|
|
1152
1394
|
/** Remueve el host del DOM y corre todas las funciones de cleanup. */
|
|
1153
1395
|
destroy() {
|
|
1154
1396
|
for (const fn of this.cleanups) {
|
|
@@ -1168,26 +1410,402 @@ var init_base_renderer = __esm({
|
|
|
1168
1410
|
}
|
|
1169
1411
|
});
|
|
1170
1412
|
|
|
1413
|
+
// src/plugins/guides/renderers/floating-arrow.ts
|
|
1414
|
+
function positionArrow(arrowEl, placement, data) {
|
|
1415
|
+
const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
|
|
1416
|
+
for (const prop of ["top", "bottom", "left", "right"]) {
|
|
1417
|
+
arrowEl.style.setProperty(prop, "");
|
|
1418
|
+
}
|
|
1419
|
+
if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
|
|
1420
|
+
if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
|
|
1421
|
+
arrowEl.style.setProperty(side, "-6px");
|
|
1422
|
+
}
|
|
1423
|
+
var STATIC_SIDE;
|
|
1424
|
+
var init_floating_arrow = __esm({
|
|
1425
|
+
"src/plugins/guides/renderers/floating-arrow.ts"() {
|
|
1426
|
+
STATIC_SIDE = {
|
|
1427
|
+
top: "bottom",
|
|
1428
|
+
bottom: "top",
|
|
1429
|
+
left: "right",
|
|
1430
|
+
right: "left"
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
function readBadgeConfig(step) {
|
|
1435
|
+
const raw = step.style?.badge;
|
|
1436
|
+
if (!raw || typeof raw !== "object") return { kind: "icon" };
|
|
1437
|
+
const b = raw;
|
|
1438
|
+
const kind = b.kind === "dot" || b.kind === "pill" || b.kind === "image" || b.kind === "icon" ? b.kind : "icon";
|
|
1439
|
+
return {
|
|
1440
|
+
kind,
|
|
1441
|
+
icon: typeof b.icon === "string" ? b.icon : null,
|
|
1442
|
+
text: typeof b.text === "string" ? b.text : null,
|
|
1443
|
+
imageUrl: typeof b.imageUrl === "string" ? b.imageUrl : null,
|
|
1444
|
+
color: typeof b.color === "string" && COLOR_RE.test(b.color.trim()) ? b.color.trim() : null,
|
|
1445
|
+
size: typeof b.size === "number" && Number.isFinite(b.size) ? b.size : null
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
function clamp2(n, min, max) {
|
|
1449
|
+
return Math.min(max, Math.max(min, n));
|
|
1450
|
+
}
|
|
1451
|
+
function buildBadgeElement(config, doc) {
|
|
1452
|
+
const btn = doc.createElement("button");
|
|
1453
|
+
btn.type = "button";
|
|
1454
|
+
btn.className = `veo-badge veo-badge--${config.kind}`;
|
|
1455
|
+
btn.setAttribute("aria-label", "M\xE1s informaci\xF3n");
|
|
1456
|
+
const size = clamp2(config.size ?? 18, 8, 48);
|
|
1457
|
+
switch (config.kind) {
|
|
1458
|
+
case "dot":
|
|
1459
|
+
btn.style.width = `${size}px`;
|
|
1460
|
+
btn.style.height = `${size}px`;
|
|
1461
|
+
if (config.color) btn.style.background = config.color;
|
|
1462
|
+
break;
|
|
1463
|
+
case "pill": {
|
|
1464
|
+
btn.textContent = config.text?.slice(0, 24) || "Nuevo";
|
|
1465
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 24)}px`;
|
|
1466
|
+
if (config.color) btn.style.background = config.color;
|
|
1467
|
+
break;
|
|
1468
|
+
}
|
|
1469
|
+
case "image": {
|
|
1470
|
+
if (config.imageUrl && isSafeUrl(config.imageUrl)) {
|
|
1471
|
+
const img = doc.createElement("img");
|
|
1472
|
+
img.src = config.imageUrl;
|
|
1473
|
+
img.alt = "";
|
|
1474
|
+
img.style.width = `${size}px`;
|
|
1475
|
+
img.style.height = `${size}px`;
|
|
1476
|
+
btn.appendChild(img);
|
|
1477
|
+
} else {
|
|
1478
|
+
btn.className = "veo-badge veo-badge--icon";
|
|
1479
|
+
btn.textContent = "?";
|
|
1480
|
+
btn.style.width = `${size}px`;
|
|
1481
|
+
btn.style.height = `${size}px`;
|
|
1482
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1483
|
+
}
|
|
1484
|
+
break;
|
|
1485
|
+
}
|
|
1486
|
+
default: {
|
|
1487
|
+
btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
|
|
1488
|
+
btn.style.width = `${size}px`;
|
|
1489
|
+
btn.style.height = `${size}px`;
|
|
1490
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1491
|
+
if (config.color) btn.style.color = config.color;
|
|
1492
|
+
break;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
return btn;
|
|
1496
|
+
}
|
|
1497
|
+
var HOVER_CLOSE_DELAY_MS, BadgeRenderer;
|
|
1498
|
+
var init_badge_renderer = __esm({
|
|
1499
|
+
"src/plugins/guides/renderers/badge-renderer.ts"() {
|
|
1500
|
+
init_block_builder();
|
|
1501
|
+
init_guide_design();
|
|
1502
|
+
init_inline_host();
|
|
1503
|
+
init_wait_for_element();
|
|
1504
|
+
init_base_renderer();
|
|
1505
|
+
init_floating_arrow();
|
|
1506
|
+
HOVER_CLOSE_DELAY_MS = 150;
|
|
1507
|
+
BadgeRenderer = class extends BaseRenderer {
|
|
1508
|
+
constructor() {
|
|
1509
|
+
super(...arguments);
|
|
1510
|
+
this.tooltip = null;
|
|
1511
|
+
this.arrowEl = null;
|
|
1512
|
+
this.badgeBtn = null;
|
|
1513
|
+
this.open = false;
|
|
1514
|
+
this.shownEmitted = false;
|
|
1515
|
+
this.trigger = "hover";
|
|
1516
|
+
this.side = "top";
|
|
1517
|
+
this.closeTimer = null;
|
|
1518
|
+
this.stopFloat = null;
|
|
1519
|
+
}
|
|
1520
|
+
async render(context) {
|
|
1521
|
+
const step = context.guide.guideSteps[0];
|
|
1522
|
+
if (!step) return;
|
|
1523
|
+
const selector = step.selector ?? context.guide.activationRules.selector;
|
|
1524
|
+
if (typeof selector !== "string" || selector.length === 0) return;
|
|
1525
|
+
const anchor = await waitForElement(selector);
|
|
1526
|
+
if (!anchor) return;
|
|
1527
|
+
const { host, root } = this.createHost();
|
|
1528
|
+
const ownerDocument = root.ownerDocument ?? document;
|
|
1529
|
+
this.applyDesign(step.style);
|
|
1530
|
+
this.trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
|
|
1531
|
+
const rawSide = step.style?.tooltipPlacement;
|
|
1532
|
+
this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
|
|
1533
|
+
const position = readInlinePosition(step.style);
|
|
1534
|
+
host.style.display = "inline-flex";
|
|
1535
|
+
host.style.verticalAlign = "middle";
|
|
1536
|
+
const badge = buildBadgeElement(readBadgeConfig(step), ownerDocument);
|
|
1537
|
+
root.appendChild(badge);
|
|
1538
|
+
this.badgeBtn = badge;
|
|
1539
|
+
insertHost(anchor, host, position);
|
|
1540
|
+
this.registerCleanup(keepHostAttached(host, selector, position));
|
|
1541
|
+
const tooltip = ownerDocument.createElement("div");
|
|
1542
|
+
tooltip.className = "veo-tooltip veo-badge-tooltip";
|
|
1543
|
+
tooltip.style.display = "none";
|
|
1544
|
+
const emit = (action, meta) => {
|
|
1545
|
+
context.onInteraction({
|
|
1546
|
+
guideId: context.guide.guideId,
|
|
1547
|
+
stepIndex: 0,
|
|
1548
|
+
action,
|
|
1549
|
+
...meta ? { metadata: meta } : {}
|
|
1550
|
+
});
|
|
1551
|
+
};
|
|
1552
|
+
const buildTooltipContent = (s) => buildStepContent(s, ownerDocument, {
|
|
1553
|
+
onCtaClick: (action, url, meta) => {
|
|
1554
|
+
emit("cta_clicked", meta);
|
|
1555
|
+
if (action === "url" && url) window.open(url, "_blank", "noopener,noreferrer");
|
|
1556
|
+
if (action === "dismiss") {
|
|
1557
|
+
emit("dismissed");
|
|
1558
|
+
context.onClose();
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
this.closeTooltip();
|
|
1562
|
+
},
|
|
1563
|
+
onDismiss: () => {
|
|
1564
|
+
emit("dismissed");
|
|
1565
|
+
context.onClose();
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
const content = buildTooltipContent(step);
|
|
1569
|
+
tooltip.appendChild(content);
|
|
1570
|
+
const arrowEl = ownerDocument.createElement("div");
|
|
1571
|
+
arrowEl.className = "veo-tooltip-arrow";
|
|
1572
|
+
tooltip.appendChild(arrowEl);
|
|
1573
|
+
root.appendChild(tooltip);
|
|
1574
|
+
this.tooltip = tooltip;
|
|
1575
|
+
this.arrowEl = arrowEl;
|
|
1576
|
+
this.liveContainer = tooltip;
|
|
1577
|
+
this.liveContent = content;
|
|
1578
|
+
this.liveKey = this.liveKeyOf(step);
|
|
1579
|
+
this.liveBuild = buildTooltipContent;
|
|
1580
|
+
const openNow = () => {
|
|
1581
|
+
this.cancelClose();
|
|
1582
|
+
if (!this.open) {
|
|
1583
|
+
this.openTooltip(badge);
|
|
1584
|
+
if (!this.shownEmitted) {
|
|
1585
|
+
this.shownEmitted = true;
|
|
1586
|
+
emit("shown");
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
};
|
|
1590
|
+
if (this.trigger === "hover") {
|
|
1591
|
+
const scheduleClose = () => {
|
|
1592
|
+
this.cancelClose();
|
|
1593
|
+
this.closeTimer = window.setTimeout(() => this.closeTooltip(), HOVER_CLOSE_DELAY_MS);
|
|
1594
|
+
};
|
|
1595
|
+
badge.addEventListener("mouseenter", openNow);
|
|
1596
|
+
badge.addEventListener("focus", openNow);
|
|
1597
|
+
badge.addEventListener("mouseleave", scheduleClose);
|
|
1598
|
+
badge.addEventListener("blur", scheduleClose);
|
|
1599
|
+
tooltip.addEventListener("mouseenter", () => this.cancelClose());
|
|
1600
|
+
tooltip.addEventListener("mouseleave", scheduleClose);
|
|
1601
|
+
} else {
|
|
1602
|
+
badge.addEventListener("click", () => {
|
|
1603
|
+
if (this.open) this.closeTooltip();
|
|
1604
|
+
else openNow();
|
|
1605
|
+
});
|
|
1606
|
+
const onDocClick = (e) => {
|
|
1607
|
+
if (!this.open) return;
|
|
1608
|
+
if (e.composedPath().includes(host)) return;
|
|
1609
|
+
this.closeTooltip();
|
|
1610
|
+
};
|
|
1611
|
+
document.addEventListener("click", onDocClick, true);
|
|
1612
|
+
this.registerCleanup(() => document.removeEventListener("click", onDocClick, true));
|
|
1613
|
+
}
|
|
1614
|
+
const onKey = (e) => {
|
|
1615
|
+
if (e.key === "Escape" && this.open) this.closeTooltip();
|
|
1616
|
+
};
|
|
1617
|
+
document.addEventListener("keydown", onKey);
|
|
1618
|
+
this.registerCleanup(() => document.removeEventListener("keydown", onKey));
|
|
1619
|
+
if (context.isPreview) openNow();
|
|
1620
|
+
}
|
|
1621
|
+
/** Cambiar de ancla/posición/trigger requiere re-montar y re-cablear. */
|
|
1622
|
+
liveKeyOf(step) {
|
|
1623
|
+
const selector = step.selector ?? "";
|
|
1624
|
+
const position = readInlinePosition(step.style);
|
|
1625
|
+
const trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
|
|
1626
|
+
return `${selector}|${position}|${trigger}`;
|
|
1627
|
+
}
|
|
1628
|
+
onLiveUpdate(step) {
|
|
1629
|
+
if (this.badgeBtn) {
|
|
1630
|
+
const doc = this.badgeBtn.ownerDocument;
|
|
1631
|
+
const next = buildBadgeElement(readBadgeConfig(step), doc);
|
|
1632
|
+
this.badgeBtn.className = next.className;
|
|
1633
|
+
this.badgeBtn.setAttribute("style", next.getAttribute("style") ?? "");
|
|
1634
|
+
this.badgeBtn.replaceChildren(...Array.from(next.childNodes));
|
|
1635
|
+
}
|
|
1636
|
+
const rawSide = step.style?.tooltipPlacement;
|
|
1637
|
+
this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
|
|
1638
|
+
if (this.open && this.badgeBtn) this.position(this.badgeBtn);
|
|
1639
|
+
}
|
|
1640
|
+
destroy() {
|
|
1641
|
+
this.cancelClose();
|
|
1642
|
+
this.stopFloat?.();
|
|
1643
|
+
this.stopFloat = null;
|
|
1644
|
+
this.tooltip = null;
|
|
1645
|
+
this.arrowEl = null;
|
|
1646
|
+
this.badgeBtn = null;
|
|
1647
|
+
this.open = false;
|
|
1648
|
+
super.destroy();
|
|
1649
|
+
}
|
|
1650
|
+
openTooltip(badge) {
|
|
1651
|
+
if (!this.tooltip) return;
|
|
1652
|
+
this.tooltip.style.display = "block";
|
|
1653
|
+
this.open = true;
|
|
1654
|
+
void this.position(badge);
|
|
1655
|
+
const reposition = () => {
|
|
1656
|
+
void this.position(badge);
|
|
1657
|
+
};
|
|
1658
|
+
window.addEventListener("scroll", reposition, true);
|
|
1659
|
+
window.addEventListener("resize", reposition);
|
|
1660
|
+
this.stopFloat = () => {
|
|
1661
|
+
window.removeEventListener("scroll", reposition, true);
|
|
1662
|
+
window.removeEventListener("resize", reposition);
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
closeTooltip() {
|
|
1666
|
+
this.cancelClose();
|
|
1667
|
+
if (!this.tooltip || !this.open) return;
|
|
1668
|
+
this.tooltip.style.display = "none";
|
|
1669
|
+
this.open = false;
|
|
1670
|
+
this.stopFloat?.();
|
|
1671
|
+
this.stopFloat = null;
|
|
1672
|
+
}
|
|
1673
|
+
cancelClose() {
|
|
1674
|
+
if (this.closeTimer !== null) {
|
|
1675
|
+
window.clearTimeout(this.closeTimer);
|
|
1676
|
+
this.closeTimer = null;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
async position(badge) {
|
|
1680
|
+
const tooltip = this.tooltip;
|
|
1681
|
+
const arrowEl = this.arrowEl;
|
|
1682
|
+
if (!tooltip || !arrowEl) return;
|
|
1683
|
+
const { x, y, placement, middlewareData } = await dom.computePosition(badge, tooltip, {
|
|
1684
|
+
strategy: "fixed",
|
|
1685
|
+
placement: this.side,
|
|
1686
|
+
middleware: [dom.offset(8), dom.flip(), dom.shift({ padding: 8 }), dom.arrow({ element: arrowEl })]
|
|
1687
|
+
});
|
|
1688
|
+
tooltip.style.left = `${x}px`;
|
|
1689
|
+
tooltip.style.top = `${y}px`;
|
|
1690
|
+
positionArrow(arrowEl, placement, middlewareData.arrow);
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
// src/plugins/guides/walkthrough-block-builder.ts
|
|
1697
|
+
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
1698
|
+
const container = doc.createElement("div");
|
|
1699
|
+
container.className = "veo-guide-content";
|
|
1700
|
+
const counter = doc.createElement("div");
|
|
1701
|
+
counter.className = "veo-walkthrough-counter";
|
|
1702
|
+
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
1703
|
+
container.appendChild(counter);
|
|
1704
|
+
const progress = doc.createElement("div");
|
|
1705
|
+
progress.className = "veo-walkthrough-progress";
|
|
1706
|
+
for (let i = 0; i < totalSteps; i++) {
|
|
1707
|
+
const dot = doc.createElement("span");
|
|
1708
|
+
dot.className = "veo-walkthrough-progress-dot";
|
|
1709
|
+
if (i < stepIndex) dot.classList.add("completed");
|
|
1710
|
+
if (i === stepIndex) dot.classList.add("active");
|
|
1711
|
+
progress.appendChild(dot);
|
|
1712
|
+
}
|
|
1713
|
+
container.appendChild(progress);
|
|
1714
|
+
if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
|
|
1715
|
+
const img = doc.createElement("img");
|
|
1716
|
+
img.className = "veo-guide-image";
|
|
1717
|
+
img.src = step.imageUrl;
|
|
1718
|
+
img.alt = typeof step.title === "string" ? step.title : "";
|
|
1719
|
+
container.appendChild(img);
|
|
1720
|
+
}
|
|
1721
|
+
if (typeof step.title === "string" && step.title) {
|
|
1722
|
+
const heading = doc.createElement("h2");
|
|
1723
|
+
heading.className = "veo-guide-title";
|
|
1724
|
+
heading.textContent = step.title;
|
|
1725
|
+
container.appendChild(heading);
|
|
1726
|
+
}
|
|
1727
|
+
if (typeof step.content === "string" && step.content) {
|
|
1728
|
+
const paragraph = doc.createElement("p");
|
|
1729
|
+
paragraph.className = "veo-guide-text";
|
|
1730
|
+
paragraph.textContent = step.content;
|
|
1731
|
+
container.appendChild(paragraph);
|
|
1732
|
+
}
|
|
1733
|
+
const actions = doc.createElement("div");
|
|
1734
|
+
actions.className = "veo-walkthrough-actions";
|
|
1735
|
+
const skipBtn = doc.createElement("button");
|
|
1736
|
+
skipBtn.type = "button";
|
|
1737
|
+
skipBtn.className = "veo-walkthrough-skip";
|
|
1738
|
+
skipBtn.textContent = "Omitir";
|
|
1739
|
+
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
1740
|
+
actions.appendChild(skipBtn);
|
|
1741
|
+
const rightGroup = doc.createElement("div");
|
|
1742
|
+
rightGroup.className = "veo-walkthrough-actions-right";
|
|
1743
|
+
if (stepIndex > 0) {
|
|
1744
|
+
const backBtn = doc.createElement("button");
|
|
1745
|
+
backBtn.type = "button";
|
|
1746
|
+
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
1747
|
+
backBtn.textContent = "Atr\xE1s";
|
|
1748
|
+
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
1749
|
+
rightGroup.appendChild(backBtn);
|
|
1750
|
+
}
|
|
1751
|
+
const isLastStep = stepIndex === totalSteps - 1;
|
|
1752
|
+
const primaryBtn = doc.createElement("button");
|
|
1753
|
+
primaryBtn.type = "button";
|
|
1754
|
+
primaryBtn.className = "veo-guide-cta";
|
|
1755
|
+
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
1756
|
+
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
1757
|
+
primaryBtn.addEventListener("click", () => {
|
|
1758
|
+
if (isLastStep) callbacks.onComplete();
|
|
1759
|
+
else callbacks.onNext();
|
|
1760
|
+
});
|
|
1761
|
+
rightGroup.appendChild(primaryBtn);
|
|
1762
|
+
actions.appendChild(rightGroup);
|
|
1763
|
+
container.appendChild(actions);
|
|
1764
|
+
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
1765
|
+
return container;
|
|
1766
|
+
}
|
|
1767
|
+
var init_walkthrough_block_builder = __esm({
|
|
1768
|
+
"src/plugins/guides/walkthrough-block-builder.ts"() {
|
|
1769
|
+
init_block_builder();
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
|
|
1171
1773
|
// src/plugins/guides/renderers/banner-renderer.ts
|
|
1774
|
+
function bannerClassOf(position, embedded) {
|
|
1775
|
+
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
1776
|
+
}
|
|
1172
1777
|
var BannerRenderer;
|
|
1173
1778
|
var init_banner_renderer = __esm({
|
|
1174
1779
|
"src/plugins/guides/renderers/banner-renderer.ts"() {
|
|
1175
1780
|
init_block_builder();
|
|
1781
|
+
init_inline_host();
|
|
1782
|
+
init_wait_for_element();
|
|
1176
1783
|
init_walkthrough_block_builder();
|
|
1177
1784
|
init_base_renderer();
|
|
1178
1785
|
BannerRenderer = class extends BaseRenderer {
|
|
1179
|
-
render(context) {
|
|
1786
|
+
async render(context) {
|
|
1180
1787
|
const idx = context.nav?.stepIndex ?? 0;
|
|
1181
1788
|
const step = context.guide.guideSteps[idx];
|
|
1182
1789
|
if (!step) return;
|
|
1183
|
-
const
|
|
1790
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1791
|
+
const selector = context.nav ? null : step.selector?.trim() ?? null;
|
|
1792
|
+
let anchor = null;
|
|
1793
|
+
if (selector) {
|
|
1794
|
+
anchor = await waitForElement(selector);
|
|
1795
|
+
if (!anchor) return;
|
|
1796
|
+
}
|
|
1797
|
+
const { host, root } = this.createHost();
|
|
1184
1798
|
this.applyDesign(step.style);
|
|
1185
1799
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1186
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1187
1800
|
const banner = ownerDocument.createElement("div");
|
|
1188
|
-
banner.className =
|
|
1189
|
-
const dismiss = (action, ctaUrl) => {
|
|
1190
|
-
context.onInteraction({
|
|
1801
|
+
banner.className = bannerClassOf(position, Boolean(anchor));
|
|
1802
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
1803
|
+
context.onInteraction({
|
|
1804
|
+
guideId: context.guide.guideId,
|
|
1805
|
+
stepIndex: idx,
|
|
1806
|
+
action,
|
|
1807
|
+
...meta ? { metadata: meta } : {}
|
|
1808
|
+
});
|
|
1191
1809
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1192
1810
|
context.onClose();
|
|
1193
1811
|
};
|
|
@@ -1198,53 +1816,43 @@ var init_banner_renderer = __esm({
|
|
|
1198
1816
|
ownerDocument,
|
|
1199
1817
|
context.nav.callbacks
|
|
1200
1818
|
) : buildStepContent(step, ownerDocument, {
|
|
1201
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
1819
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1202
1820
|
onDismiss: () => dismiss("dismissed")
|
|
1203
1821
|
});
|
|
1204
1822
|
banner.appendChild(content);
|
|
1205
1823
|
root.appendChild(banner);
|
|
1824
|
+
if (selector && anchor) {
|
|
1825
|
+
host.style.display = "block";
|
|
1826
|
+
const insertAt = position === "top" ? "prepend" : "append";
|
|
1827
|
+
insertHost(anchor, host, insertAt);
|
|
1828
|
+
this.registerCleanup(keepHostAttached(host, selector, insertAt));
|
|
1829
|
+
}
|
|
1206
1830
|
if (!context.nav) {
|
|
1831
|
+
this.liveContainer = banner;
|
|
1832
|
+
this.liveContent = content;
|
|
1833
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1834
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1835
|
+
onDismiss: () => dismiss("dismissed")
|
|
1836
|
+
});
|
|
1837
|
+
this.liveKey = this.liveKeyOf(step);
|
|
1207
1838
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1208
1839
|
}
|
|
1209
1840
|
}
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1841
|
+
/** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
|
|
1842
|
+
liveKeyOf(step) {
|
|
1843
|
+
const selector = step.selector?.trim() ?? "";
|
|
1844
|
+
if (!selector) return "";
|
|
1845
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1846
|
+
return `${selector}|${position}`;
|
|
1847
|
+
}
|
|
1848
|
+
onLiveUpdate(step) {
|
|
1849
|
+
if (this.liveContainer) {
|
|
1850
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1851
|
+
const embedded = Boolean(step.selector?.trim());
|
|
1852
|
+
this.liveContainer.className = bannerClassOf(position, embedded);
|
|
1853
|
+
}
|
|
1222
1854
|
}
|
|
1223
1855
|
};
|
|
1224
|
-
const existing = safeQuery();
|
|
1225
|
-
if (existing) {
|
|
1226
|
-
resolve(existing);
|
|
1227
|
-
return;
|
|
1228
|
-
}
|
|
1229
|
-
let resolved = false;
|
|
1230
|
-
const finish = (el) => {
|
|
1231
|
-
if (resolved) return;
|
|
1232
|
-
resolved = true;
|
|
1233
|
-
observer.disconnect();
|
|
1234
|
-
clearTimeout(timer);
|
|
1235
|
-
resolve(el);
|
|
1236
|
-
};
|
|
1237
|
-
const observer = new MutationObserver(() => {
|
|
1238
|
-
const el = safeQuery();
|
|
1239
|
-
if (el) finish(el);
|
|
1240
|
-
});
|
|
1241
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
1242
|
-
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
var init_wait_for_element = __esm({
|
|
1246
|
-
"src/plugins/guides/wait-for-element.ts"() {
|
|
1247
|
-
init_constants2();
|
|
1248
1856
|
}
|
|
1249
1857
|
});
|
|
1250
1858
|
|
|
@@ -1781,40 +2389,6 @@ var init_form_renderer = __esm({
|
|
|
1781
2389
|
}
|
|
1782
2390
|
});
|
|
1783
2391
|
|
|
1784
|
-
// src/plugins/guides/inline-host.ts
|
|
1785
|
-
function readInlinePosition(style) {
|
|
1786
|
-
const p = style?.inlinePosition;
|
|
1787
|
-
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
1788
|
-
}
|
|
1789
|
-
function insertHost(anchor, host, position) {
|
|
1790
|
-
switch (position) {
|
|
1791
|
-
case "before":
|
|
1792
|
-
anchor.before(host);
|
|
1793
|
-
break;
|
|
1794
|
-
case "after":
|
|
1795
|
-
anchor.after(host);
|
|
1796
|
-
break;
|
|
1797
|
-
case "prepend":
|
|
1798
|
-
anchor.prepend(host);
|
|
1799
|
-
break;
|
|
1800
|
-
case "append":
|
|
1801
|
-
anchor.append(host);
|
|
1802
|
-
break;
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1805
|
-
function keepHostAttached(host, selector, position) {
|
|
1806
|
-
const id = window.setInterval(() => {
|
|
1807
|
-
if (host.isConnected) return;
|
|
1808
|
-
const anchor = document.querySelector(selector);
|
|
1809
|
-
if (anchor) insertHost(anchor, host, position);
|
|
1810
|
-
}, 1e3);
|
|
1811
|
-
return () => window.clearInterval(id);
|
|
1812
|
-
}
|
|
1813
|
-
var init_inline_host = __esm({
|
|
1814
|
-
"src/plugins/guides/inline-host.ts"() {
|
|
1815
|
-
}
|
|
1816
|
-
});
|
|
1817
|
-
|
|
1818
2392
|
// src/plugins/guides/renderers/inline-custom-renderer.ts
|
|
1819
2393
|
var InlineCustomRenderer;
|
|
1820
2394
|
var init_inline_custom_renderer = __esm({
|
|
@@ -1907,24 +2481,42 @@ var init_inline_renderer = __esm({
|
|
|
1907
2481
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1908
2482
|
const card = ownerDocument.createElement("div");
|
|
1909
2483
|
card.className = "veo-inline";
|
|
1910
|
-
const dismiss = (action, ctaUrl) => {
|
|
1911
|
-
context.onInteraction({
|
|
2484
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2485
|
+
context.onInteraction({
|
|
2486
|
+
guideId: context.guide.guideId,
|
|
2487
|
+
stepIndex: 0,
|
|
2488
|
+
action,
|
|
2489
|
+
...meta ? { metadata: meta } : {}
|
|
2490
|
+
});
|
|
1912
2491
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1913
2492
|
context.onClose();
|
|
1914
2493
|
};
|
|
1915
2494
|
const content = buildStepContent(step, ownerDocument, {
|
|
1916
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2495
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1917
2496
|
onDismiss: () => dismiss("dismissed")
|
|
1918
2497
|
});
|
|
1919
2498
|
card.appendChild(content);
|
|
1920
2499
|
root.appendChild(card);
|
|
2500
|
+
this.liveContainer = card;
|
|
2501
|
+
this.liveContent = content;
|
|
2502
|
+
this.liveKey = `${selector}|${position}`;
|
|
2503
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2504
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2505
|
+
onDismiss: () => dismiss("dismissed")
|
|
2506
|
+
});
|
|
1921
2507
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1922
2508
|
}
|
|
2509
|
+
liveKeyOf(step) {
|
|
2510
|
+
return `${step.selector ?? ""}|${readInlinePosition(step.style)}`;
|
|
2511
|
+
}
|
|
1923
2512
|
};
|
|
1924
2513
|
}
|
|
1925
2514
|
});
|
|
1926
2515
|
|
|
1927
2516
|
// src/plugins/guides/renderers/modal-renderer.ts
|
|
2517
|
+
function overlayClassOf(step) {
|
|
2518
|
+
return step.style?.backdrop === false ? "veo-modal-overlay veo-modal-overlay--none" : "veo-modal-overlay";
|
|
2519
|
+
}
|
|
1928
2520
|
var ModalRenderer;
|
|
1929
2521
|
var init_modal_renderer = __esm({
|
|
1930
2522
|
"src/plugins/guides/renderers/modal-renderer.ts"() {
|
|
@@ -1932,6 +2524,10 @@ var init_modal_renderer = __esm({
|
|
|
1932
2524
|
init_walkthrough_block_builder();
|
|
1933
2525
|
init_base_renderer();
|
|
1934
2526
|
ModalRenderer = class extends BaseRenderer {
|
|
2527
|
+
constructor() {
|
|
2528
|
+
super(...arguments);
|
|
2529
|
+
this.overlay = null;
|
|
2530
|
+
}
|
|
1935
2531
|
render(context) {
|
|
1936
2532
|
const idx = context.nav?.stepIndex ?? 0;
|
|
1937
2533
|
const step = context.guide.guideSteps[idx];
|
|
@@ -1940,11 +2536,17 @@ var init_modal_renderer = __esm({
|
|
|
1940
2536
|
this.applyDesign(step.style);
|
|
1941
2537
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1942
2538
|
const overlay = ownerDocument.createElement("div");
|
|
1943
|
-
overlay.className =
|
|
2539
|
+
overlay.className = overlayClassOf(step);
|
|
2540
|
+
this.overlay = overlay;
|
|
1944
2541
|
const card = ownerDocument.createElement("div");
|
|
1945
2542
|
card.className = "veo-modal-card";
|
|
1946
|
-
const dismiss = (action, ctaUrl) => {
|
|
1947
|
-
context.onInteraction({
|
|
2543
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2544
|
+
context.onInteraction({
|
|
2545
|
+
guideId: context.guide.guideId,
|
|
2546
|
+
stepIndex: idx,
|
|
2547
|
+
action,
|
|
2548
|
+
...meta ? { metadata: meta } : {}
|
|
2549
|
+
});
|
|
1948
2550
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1949
2551
|
context.onClose();
|
|
1950
2552
|
};
|
|
@@ -1959,12 +2561,20 @@ var init_modal_renderer = __esm({
|
|
|
1959
2561
|
ownerDocument,
|
|
1960
2562
|
context.nav.callbacks
|
|
1961
2563
|
) : buildStepContent(step, ownerDocument, {
|
|
1962
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2564
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1963
2565
|
onDismiss: () => dismiss("dismissed")
|
|
1964
2566
|
});
|
|
1965
2567
|
card.appendChild(content);
|
|
1966
2568
|
overlay.appendChild(card);
|
|
1967
2569
|
root.appendChild(overlay);
|
|
2570
|
+
if (!context.nav) {
|
|
2571
|
+
this.liveContainer = card;
|
|
2572
|
+
this.liveContent = content;
|
|
2573
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2574
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2575
|
+
onDismiss: () => dismiss("dismissed")
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
1968
2578
|
overlay.addEventListener("click", (e) => {
|
|
1969
2579
|
if (e.target === overlay) onBackdropOrEsc();
|
|
1970
2580
|
});
|
|
@@ -1977,28 +2587,13 @@ var init_modal_renderer = __esm({
|
|
|
1977
2587
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1978
2588
|
}
|
|
1979
2589
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
for (const prop of ["top", "bottom", "left", "right"]) {
|
|
1988
|
-
arrowEl.style.setProperty(prop, "");
|
|
1989
|
-
}
|
|
1990
|
-
if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
|
|
1991
|
-
if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
|
|
1992
|
-
arrowEl.style.setProperty(side, "-6px");
|
|
1993
|
-
}
|
|
1994
|
-
var STATIC_SIDE;
|
|
1995
|
-
var init_floating_arrow = __esm({
|
|
1996
|
-
"src/plugins/guides/renderers/floating-arrow.ts"() {
|
|
1997
|
-
STATIC_SIDE = {
|
|
1998
|
-
top: "bottom",
|
|
1999
|
-
bottom: "top",
|
|
2000
|
-
left: "right",
|
|
2001
|
-
right: "left"
|
|
2590
|
+
onLiveUpdate(step) {
|
|
2591
|
+
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
2592
|
+
}
|
|
2593
|
+
destroy() {
|
|
2594
|
+
this.overlay = null;
|
|
2595
|
+
super.destroy();
|
|
2596
|
+
}
|
|
2002
2597
|
};
|
|
2003
2598
|
}
|
|
2004
2599
|
});
|
|
@@ -2010,6 +2605,12 @@ var init_tooltip_renderer = __esm({
|
|
|
2010
2605
|
init_base_renderer();
|
|
2011
2606
|
init_floating_arrow();
|
|
2012
2607
|
TooltipRenderer = class extends BaseRenderer {
|
|
2608
|
+
constructor() {
|
|
2609
|
+
super(...arguments);
|
|
2610
|
+
/** Lado preferido (mutable: el update in-place puede cambiarlo). */
|
|
2611
|
+
this.side = "bottom";
|
|
2612
|
+
this.reposition = null;
|
|
2613
|
+
}
|
|
2013
2614
|
async render(context) {
|
|
2014
2615
|
const step = context.guide.guideSteps[0];
|
|
2015
2616
|
if (!step) return;
|
|
@@ -2018,24 +2619,25 @@ var init_tooltip_renderer = __esm({
|
|
|
2018
2619
|
const anchor = await waitForElement(selector);
|
|
2019
2620
|
if (!anchor) return;
|
|
2020
2621
|
const rawSide = step.style?.tooltipPlacement;
|
|
2021
|
-
|
|
2622
|
+
this.side = rawSide === "top" || rawSide === "left" || rawSide === "right" ? rawSide : "bottom";
|
|
2022
2623
|
const { root } = this.createHost();
|
|
2023
2624
|
this.applyDesign(step.style);
|
|
2024
2625
|
const ownerDocument = root.ownerDocument ?? document;
|
|
2025
2626
|
const tooltip = ownerDocument.createElement("div");
|
|
2026
2627
|
tooltip.className = "veo-tooltip";
|
|
2027
|
-
const dismiss = (action, ctaUrl) => {
|
|
2628
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2028
2629
|
context.onInteraction({
|
|
2029
2630
|
guideId: context.guide.guideId,
|
|
2030
2631
|
stepIndex: 0,
|
|
2031
|
-
action
|
|
2632
|
+
action,
|
|
2633
|
+
...meta ? { metadata: meta } : {}
|
|
2032
2634
|
});
|
|
2033
2635
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
2034
2636
|
context.onClose();
|
|
2035
2637
|
};
|
|
2036
2638
|
const content = buildStepContent(step, ownerDocument, {
|
|
2037
|
-
onCtaClick: (action, url) => {
|
|
2038
|
-
dismiss("cta_clicked", action === "url" ? url : void 0);
|
|
2639
|
+
onCtaClick: (action, url, meta) => {
|
|
2640
|
+
dismiss("cta_clicked", action === "url" ? url : void 0, meta);
|
|
2039
2641
|
},
|
|
2040
2642
|
onDismiss: () => dismiss("dismissed")
|
|
2041
2643
|
});
|
|
@@ -2046,7 +2648,7 @@ var init_tooltip_renderer = __esm({
|
|
|
2046
2648
|
root.appendChild(tooltip);
|
|
2047
2649
|
const updatePosition = async () => {
|
|
2048
2650
|
const { x, y, placement, middlewareData } = await dom.computePosition(anchor, tooltip, {
|
|
2049
|
-
placement: side,
|
|
2651
|
+
placement: this.side,
|
|
2050
2652
|
middleware: [dom.offset(10), dom.flip(), dom.shift({ padding: 8 }), dom.arrow({ element: arrowEl })]
|
|
2051
2653
|
});
|
|
2052
2654
|
tooltip.style.left = `${x}px`;
|
|
@@ -2057,18 +2659,34 @@ var init_tooltip_renderer = __esm({
|
|
|
2057
2659
|
const reposition = () => {
|
|
2058
2660
|
void updatePosition();
|
|
2059
2661
|
};
|
|
2662
|
+
this.reposition = reposition;
|
|
2060
2663
|
window.addEventListener("scroll", reposition, true);
|
|
2061
2664
|
window.addEventListener("resize", reposition);
|
|
2062
2665
|
this.registerCleanup(() => {
|
|
2063
2666
|
window.removeEventListener("scroll", reposition, true);
|
|
2064
2667
|
window.removeEventListener("resize", reposition);
|
|
2065
2668
|
});
|
|
2669
|
+
this.liveContainer = tooltip;
|
|
2670
|
+
this.liveContent = content;
|
|
2671
|
+
this.liveKey = selector;
|
|
2672
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2673
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2674
|
+
onDismiss: () => dismiss("dismissed")
|
|
2675
|
+
});
|
|
2066
2676
|
context.onInteraction({
|
|
2067
2677
|
guideId: context.guide.guideId,
|
|
2068
2678
|
stepIndex: 0,
|
|
2069
2679
|
action: "shown"
|
|
2070
2680
|
});
|
|
2071
2681
|
}
|
|
2682
|
+
liveKeyOf(step) {
|
|
2683
|
+
return step.selector ?? "";
|
|
2684
|
+
}
|
|
2685
|
+
onLiveUpdate(step) {
|
|
2686
|
+
const raw = step.style?.tooltipPlacement;
|
|
2687
|
+
this.side = raw === "top" || raw === "left" || raw === "right" ? raw : "bottom";
|
|
2688
|
+
this.reposition?.();
|
|
2689
|
+
}
|
|
2072
2690
|
};
|
|
2073
2691
|
}
|
|
2074
2692
|
});
|
|
@@ -2136,7 +2754,7 @@ var init_walkthrough_renderer = __esm({
|
|
|
2136
2754
|
function previewGuide(input) {
|
|
2137
2755
|
if (!hasDocument()) {
|
|
2138
2756
|
return { close: () => {
|
|
2139
|
-
}, ready: Promise.resolve({ rendered: false }) };
|
|
2757
|
+
}, ready: Promise.resolve({ rendered: false }), host: () => null };
|
|
2140
2758
|
}
|
|
2141
2759
|
if (!activeController) activeController = new GuidePreviewController();
|
|
2142
2760
|
return activeController.preview(input);
|
|
@@ -2179,6 +2797,8 @@ function createSingleStepRenderer(type) {
|
|
|
2179
2797
|
return new FormRenderer();
|
|
2180
2798
|
case "inline-form":
|
|
2181
2799
|
return new InlineFormRenderer();
|
|
2800
|
+
case "badge":
|
|
2801
|
+
return new BadgeRenderer();
|
|
2182
2802
|
case "walkthrough":
|
|
2183
2803
|
return null;
|
|
2184
2804
|
}
|
|
@@ -2193,7 +2813,9 @@ var PREVIEW_GUIDE_ID, GuidePreviewController, activeController;
|
|
|
2193
2813
|
var init_guide_preview = __esm({
|
|
2194
2814
|
"src/plugins/guides/guide-preview.ts"() {
|
|
2195
2815
|
init_safe_env();
|
|
2816
|
+
init_badge_renderer();
|
|
2196
2817
|
init_banner_renderer();
|
|
2818
|
+
init_base_renderer();
|
|
2197
2819
|
init_custom_renderer();
|
|
2198
2820
|
init_form_renderer();
|
|
2199
2821
|
init_inline_custom_renderer();
|
|
@@ -2214,12 +2836,29 @@ var init_guide_preview = __esm({
|
|
|
2214
2836
|
this.input = null;
|
|
2215
2837
|
}
|
|
2216
2838
|
preview(input) {
|
|
2839
|
+
const nextStep = input.guideSteps[0];
|
|
2840
|
+
if (this.singleStep && this.input && input.guideType === this.input.guideType && input.guideType !== "walkthrough" && nextStep && this.singleStep.updateStep(nextStep)) {
|
|
2841
|
+
this.input = input;
|
|
2842
|
+
return {
|
|
2843
|
+
close: () => this.close(),
|
|
2844
|
+
ready: Promise.resolve({ rendered: true }),
|
|
2845
|
+
host: () => this.activeHost()
|
|
2846
|
+
};
|
|
2847
|
+
}
|
|
2217
2848
|
this.close();
|
|
2218
2849
|
this.input = input;
|
|
2219
2850
|
const guide = normalize(input);
|
|
2220
2851
|
const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
|
|
2221
2852
|
const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
|
|
2222
|
-
return { close: () => this.close(), ready };
|
|
2853
|
+
return { close: () => this.close(), ready, host: () => this.activeHost() };
|
|
2854
|
+
}
|
|
2855
|
+
/** Host de la guía montada (single-step o paso de walkthrough activo). */
|
|
2856
|
+
activeHost() {
|
|
2857
|
+
if (this.singleStep) return this.singleStep.hostElement();
|
|
2858
|
+
if (this.walkthrough instanceof BaseRenderer) {
|
|
2859
|
+
return this.walkthrough.hostElement();
|
|
2860
|
+
}
|
|
2861
|
+
return null;
|
|
2223
2862
|
}
|
|
2224
2863
|
close() {
|
|
2225
2864
|
if (this.singleStep) {
|
|
@@ -2434,8 +3073,8 @@ function injectBuilderPanel(opts) {
|
|
|
2434
3073
|
const ot = top;
|
|
2435
3074
|
iframe.style.pointerEvents = "none";
|
|
2436
3075
|
const move = (ev) => {
|
|
2437
|
-
left =
|
|
2438
|
-
top =
|
|
3076
|
+
left = clamp3(ol + (ev.clientX - sx), 0, Math.max(0, window.innerWidth - card.offsetWidth));
|
|
3077
|
+
top = clamp3(ot + (ev.clientY - sy), 0, Math.max(0, window.innerHeight - 40));
|
|
2439
3078
|
card.style.left = `${left}px`;
|
|
2440
3079
|
card.style.top = `${top}px`;
|
|
2441
3080
|
};
|
|
@@ -2456,8 +3095,8 @@ function injectBuilderPanel(opts) {
|
|
|
2456
3095
|
const sh = card.offsetHeight;
|
|
2457
3096
|
iframe.style.pointerEvents = "none";
|
|
2458
3097
|
const move = (ev) => {
|
|
2459
|
-
card.style.width = `${
|
|
2460
|
-
card.style.height = `${
|
|
3098
|
+
card.style.width = `${clamp3(sw + (ev.clientX - sx), MIN_W, window.innerWidth)}px`;
|
|
3099
|
+
card.style.height = `${clamp3(sh + (ev.clientY - sy), MIN_H, window.innerHeight)}px`;
|
|
2461
3100
|
};
|
|
2462
3101
|
const up = () => {
|
|
2463
3102
|
window.removeEventListener("mousemove", move);
|
|
@@ -2476,7 +3115,7 @@ function injectBuilderPanel(opts) {
|
|
|
2476
3115
|
}
|
|
2477
3116
|
};
|
|
2478
3117
|
}
|
|
2479
|
-
function
|
|
3118
|
+
function clamp3(v, min, max) {
|
|
2480
3119
|
return Math.min(Math.max(v, min), max);
|
|
2481
3120
|
}
|
|
2482
3121
|
function buildPanelUrl(opts) {
|
|
@@ -2540,6 +3179,242 @@ var init_builder_panel = __esm({
|
|
|
2540
3179
|
}
|
|
2541
3180
|
});
|
|
2542
3181
|
|
|
3182
|
+
// src/plugins/builder/design-manipulator.ts
|
|
3183
|
+
function clamp4(v, min, max) {
|
|
3184
|
+
return Math.min(Math.max(v, min), max);
|
|
3185
|
+
}
|
|
3186
|
+
function round3(v) {
|
|
3187
|
+
return Math.round(v * 1e3) / 1e3;
|
|
3188
|
+
}
|
|
3189
|
+
function isInteractive(target) {
|
|
3190
|
+
return Boolean(
|
|
3191
|
+
target instanceof Element && target.closest("button, a, input, textarea, select, label")
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
function attachDesignManipulator(opts) {
|
|
3195
|
+
const shadow = opts.host.shadowRoot;
|
|
3196
|
+
if (!shadow) return null;
|
|
3197
|
+
const card = shadow.querySelector(".veo-modal-card");
|
|
3198
|
+
if (card) return attachModal(opts, shadow, card);
|
|
3199
|
+
const banner = shadow.querySelector(".veo-banner");
|
|
3200
|
+
if (banner && !banner.classList.contains("veo-banner-embedded")) {
|
|
3201
|
+
return attachBanner(opts, shadow, banner);
|
|
3202
|
+
}
|
|
3203
|
+
return null;
|
|
3204
|
+
}
|
|
3205
|
+
function attachModal(opts, shadow, card) {
|
|
3206
|
+
if (card.dataset.veoManipulated === "1") return null;
|
|
3207
|
+
card.dataset.veoManipulated = "1";
|
|
3208
|
+
card.classList.add("veo-mnp-card");
|
|
3209
|
+
const doc = card.ownerDocument;
|
|
3210
|
+
const style = doc.createElement("style");
|
|
3211
|
+
style.textContent = MANIPULATOR_CSS;
|
|
3212
|
+
shadow.appendChild(style);
|
|
3213
|
+
const handles = [];
|
|
3214
|
+
for (const dir of RESIZE_DIRS) {
|
|
3215
|
+
const h = doc.createElement("div");
|
|
3216
|
+
h.className = `veo-mnp-handle veo-mnp-${dir}`;
|
|
3217
|
+
h.dataset.veoDir = dir;
|
|
3218
|
+
card.appendChild(h);
|
|
3219
|
+
handles.push(h);
|
|
3220
|
+
}
|
|
3221
|
+
let dragging = false;
|
|
3222
|
+
let detached = false;
|
|
3223
|
+
let patch = {};
|
|
3224
|
+
const setVars = (v) => {
|
|
3225
|
+
const host = opts.host;
|
|
3226
|
+
if (v.px !== void 0) host.style.setProperty("--veo-pos-x", `${v.px * 100}%`);
|
|
3227
|
+
if (v.py !== void 0) host.style.setProperty("--veo-pos-y", `${v.py * 100}%`);
|
|
3228
|
+
if (v.w !== void 0) host.style.setProperty("--veo-width", `${v.w}px`);
|
|
3229
|
+
if (v.h !== void 0) host.style.setProperty("--veo-min-h", `${v.h}px`);
|
|
3230
|
+
};
|
|
3231
|
+
const startGesture = (e, apply) => {
|
|
3232
|
+
e.preventDefault();
|
|
3233
|
+
e.stopPropagation();
|
|
3234
|
+
dragging = true;
|
|
3235
|
+
patch = {};
|
|
3236
|
+
const start = card.getBoundingClientRect();
|
|
3237
|
+
const sx = e.clientX;
|
|
3238
|
+
const sy = e.clientY;
|
|
3239
|
+
let raf = 0;
|
|
3240
|
+
let lastEv = null;
|
|
3241
|
+
const flush = () => {
|
|
3242
|
+
raf = 0;
|
|
3243
|
+
if (!lastEv) return;
|
|
3244
|
+
const next = apply(lastEv.clientX - sx, lastEv.clientY - sy, start);
|
|
3245
|
+
patch = { ...patch, ...next };
|
|
3246
|
+
setVars({
|
|
3247
|
+
...next.posX !== void 0 ? { px: next.posX } : {},
|
|
3248
|
+
...next.posY !== void 0 ? { py: next.posY } : {},
|
|
3249
|
+
...next.width !== void 0 ? { w: next.width } : {},
|
|
3250
|
+
...next.height !== void 0 ? { h: next.height } : {}
|
|
3251
|
+
});
|
|
3252
|
+
};
|
|
3253
|
+
const move = (ev) => {
|
|
3254
|
+
lastEv = ev;
|
|
3255
|
+
if (typeof window.requestAnimationFrame !== "function") flush();
|
|
3256
|
+
else if (!raf) raf = window.requestAnimationFrame(flush);
|
|
3257
|
+
};
|
|
3258
|
+
const up = () => {
|
|
3259
|
+
window.removeEventListener("mousemove", move);
|
|
3260
|
+
window.removeEventListener("mouseup", up);
|
|
3261
|
+
if (raf) window.cancelAnimationFrame(raf);
|
|
3262
|
+
flush();
|
|
3263
|
+
dragging = false;
|
|
3264
|
+
if (Object.keys(patch).length > 0 && !detached) {
|
|
3265
|
+
opts.onCommit({ stepIndex: opts.stepIndex, style: patch });
|
|
3266
|
+
}
|
|
3267
|
+
opts.onGestureEnd?.();
|
|
3268
|
+
};
|
|
3269
|
+
window.addEventListener("mousemove", move);
|
|
3270
|
+
window.addEventListener("mouseup", up);
|
|
3271
|
+
};
|
|
3272
|
+
const posXFor = (left, w) => {
|
|
3273
|
+
const span = window.innerWidth - w;
|
|
3274
|
+
return span <= 0 ? 0.5 : round3(clamp4(left / span, 0, 1));
|
|
3275
|
+
};
|
|
3276
|
+
const posYFor = (top, h) => {
|
|
3277
|
+
const span = window.innerHeight - h;
|
|
3278
|
+
return span <= 0 ? 0.5 : round3(clamp4(top / span, 0, 1));
|
|
3279
|
+
};
|
|
3280
|
+
const onCardDown = (e) => {
|
|
3281
|
+
if (isInteractive(e.target)) return;
|
|
3282
|
+
if (e.target instanceof Element && e.target.closest(".veo-mnp-handle")) return;
|
|
3283
|
+
startGesture(e, (dx, dy, start) => ({
|
|
3284
|
+
posX: posXFor(start.left + dx, start.width),
|
|
3285
|
+
posY: posYFor(start.top + dy, start.height)
|
|
3286
|
+
}));
|
|
3287
|
+
};
|
|
3288
|
+
const onHandleDown = (e) => {
|
|
3289
|
+
const dir = e.currentTarget.dataset.veoDir;
|
|
3290
|
+
startGesture(e, (dx, dy, start) => {
|
|
3291
|
+
const out = {};
|
|
3292
|
+
if (dir.includes("e")) out.width = Math.round(clamp4(start.width + dx, MIN_W2, MAX_W));
|
|
3293
|
+
if (dir.includes("w")) {
|
|
3294
|
+
out.width = Math.round(clamp4(start.width - dx, MIN_W2, MAX_W));
|
|
3295
|
+
out.posX = posXFor(start.right - out.width, out.width);
|
|
3296
|
+
}
|
|
3297
|
+
if (dir.includes("s")) out.height = Math.round(clamp4(start.height + dy, MIN_H2, MAX_H));
|
|
3298
|
+
if (dir.includes("n")) {
|
|
3299
|
+
out.height = Math.round(clamp4(start.height - dy, MIN_H2, MAX_H));
|
|
3300
|
+
out.posY = posYFor(start.bottom - out.height, out.height);
|
|
3301
|
+
}
|
|
3302
|
+
return out;
|
|
3303
|
+
});
|
|
3304
|
+
};
|
|
3305
|
+
card.addEventListener("mousedown", onCardDown);
|
|
3306
|
+
for (const h of handles) h.addEventListener("mousedown", onHandleDown);
|
|
3307
|
+
return {
|
|
3308
|
+
isDragging: () => dragging,
|
|
3309
|
+
detach: () => {
|
|
3310
|
+
if (detached) return;
|
|
3311
|
+
detached = true;
|
|
3312
|
+
card.removeEventListener("mousedown", onCardDown);
|
|
3313
|
+
for (const h of handles) h.remove();
|
|
3314
|
+
style.remove();
|
|
3315
|
+
card.classList.remove("veo-mnp-card");
|
|
3316
|
+
delete card.dataset.veoManipulated;
|
|
3317
|
+
}
|
|
3318
|
+
};
|
|
3319
|
+
}
|
|
3320
|
+
function attachBanner(opts, shadow, banner) {
|
|
3321
|
+
if (banner.dataset.veoManipulated === "1") return null;
|
|
3322
|
+
banner.dataset.veoManipulated = "1";
|
|
3323
|
+
banner.classList.add("veo-mnp-banner");
|
|
3324
|
+
const doc = banner.ownerDocument;
|
|
3325
|
+
const style = doc.createElement("style");
|
|
3326
|
+
style.textContent = MANIPULATOR_CSS;
|
|
3327
|
+
shadow.appendChild(style);
|
|
3328
|
+
let dragging = false;
|
|
3329
|
+
let detached = false;
|
|
3330
|
+
const positionOf = () => banner.classList.contains("veo-banner-bottom") ? "bottom" : "top";
|
|
3331
|
+
const onDown = (e) => {
|
|
3332
|
+
if (isInteractive(e.target)) return;
|
|
3333
|
+
e.preventDefault();
|
|
3334
|
+
dragging = true;
|
|
3335
|
+
const startPos = positionOf();
|
|
3336
|
+
let current = startPos;
|
|
3337
|
+
const move = (ev) => {
|
|
3338
|
+
const next = ev.clientY < window.innerHeight / 2 ? "top" : "bottom";
|
|
3339
|
+
if (next !== current) {
|
|
3340
|
+
current = next;
|
|
3341
|
+
banner.classList.remove("veo-banner-top", "veo-banner-bottom");
|
|
3342
|
+
banner.classList.add(`veo-banner-${next}`);
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
3345
|
+
const up = () => {
|
|
3346
|
+
window.removeEventListener("mousemove", move);
|
|
3347
|
+
window.removeEventListener("mouseup", up);
|
|
3348
|
+
dragging = false;
|
|
3349
|
+
if (current !== startPos && !detached) {
|
|
3350
|
+
opts.onCommit({ stepIndex: opts.stepIndex, style: { position: current } });
|
|
3351
|
+
}
|
|
3352
|
+
opts.onGestureEnd?.();
|
|
3353
|
+
};
|
|
3354
|
+
window.addEventListener("mousemove", move);
|
|
3355
|
+
window.addEventListener("mouseup", up);
|
|
3356
|
+
};
|
|
3357
|
+
banner.addEventListener("mousedown", onDown);
|
|
3358
|
+
return {
|
|
3359
|
+
isDragging: () => dragging,
|
|
3360
|
+
detach: () => {
|
|
3361
|
+
if (detached) return;
|
|
3362
|
+
detached = true;
|
|
3363
|
+
banner.removeEventListener("mousedown", onDown);
|
|
3364
|
+
style.remove();
|
|
3365
|
+
banner.classList.remove("veo-mnp-banner");
|
|
3366
|
+
delete banner.dataset.veoManipulated;
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
var MIN_W2, MAX_W, MIN_H2, MAX_H, EDGE, RESIZE_DIRS, MANIPULATOR_CSS;
|
|
3371
|
+
var init_design_manipulator = __esm({
|
|
3372
|
+
"src/plugins/builder/design-manipulator.ts"() {
|
|
3373
|
+
MIN_W2 = 220;
|
|
3374
|
+
MAX_W = 720;
|
|
3375
|
+
MIN_H2 = 120;
|
|
3376
|
+
MAX_H = 900;
|
|
3377
|
+
EDGE = 8;
|
|
3378
|
+
RESIZE_DIRS = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
3379
|
+
MANIPULATOR_CSS = `
|
|
3380
|
+
.veo-mnp-card {
|
|
3381
|
+
outline: 1.5px dashed rgba(255, 91, 53, 0.75);
|
|
3382
|
+
outline-offset: 2px;
|
|
3383
|
+
}
|
|
3384
|
+
.veo-mnp-card:hover { cursor: move; }
|
|
3385
|
+
.veo-mnp-handle {
|
|
3386
|
+
position: absolute;
|
|
3387
|
+
z-index: 10;
|
|
3388
|
+
background: transparent;
|
|
3389
|
+
}
|
|
3390
|
+
.veo-mnp-handle::after {
|
|
3391
|
+
content: '';
|
|
3392
|
+
position: absolute;
|
|
3393
|
+
width: 8px; height: 8px;
|
|
3394
|
+
background: #fff;
|
|
3395
|
+
border: 1.5px solid rgba(255, 91, 53, 0.9);
|
|
3396
|
+
border-radius: 2px;
|
|
3397
|
+
top: 50%; left: 50%;
|
|
3398
|
+
transform: translate(-50%, -50%);
|
|
3399
|
+
opacity: 0;
|
|
3400
|
+
transition: opacity 100ms ease;
|
|
3401
|
+
}
|
|
3402
|
+
.veo-mnp-card:hover .veo-mnp-handle::after,
|
|
3403
|
+
.veo-mnp-handle:hover::after { opacity: 1; }
|
|
3404
|
+
.veo-mnp-n { top: -${EDGE / 2}px; left: ${EDGE}px; right: ${EDGE}px; height: ${EDGE}px; cursor: ns-resize; }
|
|
3405
|
+
.veo-mnp-s { bottom: -${EDGE / 2}px; left: ${EDGE}px; right: ${EDGE}px; height: ${EDGE}px; cursor: ns-resize; }
|
|
3406
|
+
.veo-mnp-e { right: -${EDGE / 2}px; top: ${EDGE}px; bottom: ${EDGE}px; width: ${EDGE}px; cursor: ew-resize; }
|
|
3407
|
+
.veo-mnp-w { left: -${EDGE / 2}px; top: ${EDGE}px; bottom: ${EDGE}px; width: ${EDGE}px; cursor: ew-resize; }
|
|
3408
|
+
.veo-mnp-ne { top: -${EDGE / 2}px; right: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nesw-resize; }
|
|
3409
|
+
.veo-mnp-nw { top: -${EDGE / 2}px; left: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nwse-resize; }
|
|
3410
|
+
.veo-mnp-se { bottom: -${EDGE / 2}px; right: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nwse-resize; }
|
|
3411
|
+
.veo-mnp-sw { bottom: -${EDGE / 2}px; left: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nesw-resize; }
|
|
3412
|
+
.veo-mnp-banner { outline: 1.5px dashed rgba(255, 91, 53, 0.75); outline-offset: -2px; cursor: grab; }
|
|
3413
|
+
.veo-mnp-banner:active { cursor: grabbing; }
|
|
3414
|
+
`;
|
|
3415
|
+
}
|
|
3416
|
+
});
|
|
3417
|
+
|
|
2543
3418
|
// src/plugins/builder/element-picker.ts
|
|
2544
3419
|
function startElementPicker(options) {
|
|
2545
3420
|
if (!hasDocument()) {
|
|
@@ -2753,10 +3628,35 @@ function initBuilderMode() {
|
|
|
2753
3628
|
let picker = null;
|
|
2754
3629
|
let panel = null;
|
|
2755
3630
|
let staticTarget = null;
|
|
3631
|
+
let manipulator = null;
|
|
3632
|
+
let pendingPreview = null;
|
|
2756
3633
|
const getTarget = () => panel ? panel.target() : staticTarget;
|
|
2757
3634
|
const post = (event) => {
|
|
2758
3635
|
getTarget()?.postMessage({ source: VEO_BUILDER_SOURCE, token, ...event }, dashboardOrigin);
|
|
2759
3636
|
};
|
|
3637
|
+
const applyPreview = (cmd) => {
|
|
3638
|
+
manipulator?.detach();
|
|
3639
|
+
manipulator = null;
|
|
3640
|
+
const handle = previewGuide(cmd.guide);
|
|
3641
|
+
if (!cmd.editable) return;
|
|
3642
|
+
void handle.ready.then((result) => {
|
|
3643
|
+
if (!result.rendered) return;
|
|
3644
|
+
const host = handle.host();
|
|
3645
|
+
if (!host) return;
|
|
3646
|
+
manipulator = attachDesignManipulator({
|
|
3647
|
+
host,
|
|
3648
|
+
guideType: cmd.guide.guideType,
|
|
3649
|
+
stepIndex: cmd.guide.startStepIndex ?? 0,
|
|
3650
|
+
onCommit: (payload) => post({ type: "design-updated", payload }),
|
|
3651
|
+
onGestureEnd: () => {
|
|
3652
|
+
if (!pendingPreview) return;
|
|
3653
|
+
const queued = pendingPreview;
|
|
3654
|
+
pendingPreview = null;
|
|
3655
|
+
applyPreview(queued);
|
|
3656
|
+
}
|
|
3657
|
+
});
|
|
3658
|
+
});
|
|
3659
|
+
};
|
|
2760
3660
|
const handleCommand = (cmd) => {
|
|
2761
3661
|
switch (cmd.type) {
|
|
2762
3662
|
case "panel-ready":
|
|
@@ -2782,9 +3682,14 @@ function initBuilderMode() {
|
|
|
2782
3682
|
picker = null;
|
|
2783
3683
|
break;
|
|
2784
3684
|
case "preview":
|
|
2785
|
-
if (cmd.guide)
|
|
3685
|
+
if (!cmd.guide) break;
|
|
3686
|
+
if (manipulator?.isDragging()) pendingPreview = cmd;
|
|
3687
|
+
else applyPreview(cmd);
|
|
2786
3688
|
break;
|
|
2787
3689
|
case "close-preview":
|
|
3690
|
+
manipulator?.detach();
|
|
3691
|
+
manipulator = null;
|
|
3692
|
+
pendingPreview = null;
|
|
2788
3693
|
closeGuidePreview();
|
|
2789
3694
|
break;
|
|
2790
3695
|
case "teardown":
|
|
@@ -2804,6 +3709,9 @@ function initBuilderMode() {
|
|
|
2804
3709
|
window.removeEventListener("pagehide", onPageHide);
|
|
2805
3710
|
picker?.stop();
|
|
2806
3711
|
picker = null;
|
|
3712
|
+
manipulator?.detach();
|
|
3713
|
+
manipulator = null;
|
|
3714
|
+
pendingPreview = null;
|
|
2807
3715
|
closeGuidePreview();
|
|
2808
3716
|
post({ type: "closed" });
|
|
2809
3717
|
panel?.teardown();
|
|
@@ -2883,6 +3791,7 @@ var init_builder_mode = __esm({
|
|
|
2883
3791
|
init_guide_preview();
|
|
2884
3792
|
init_bridge_protocol();
|
|
2885
3793
|
init_builder_panel();
|
|
3794
|
+
init_design_manipulator();
|
|
2886
3795
|
init_element_picker();
|
|
2887
3796
|
}
|
|
2888
3797
|
});
|
|
@@ -3016,6 +3925,7 @@ var init_builder_session = __esm({
|
|
|
3016
3925
|
// src/plugins/builder/index.ts
|
|
3017
3926
|
var builder_exports = {};
|
|
3018
3927
|
__export(builder_exports, {
|
|
3928
|
+
attachDesignManipulator: () => attachDesignManipulator,
|
|
3019
3929
|
createBuilderSession: () => createBuilderSession,
|
|
3020
3930
|
initBuilderMode: () => initBuilderMode,
|
|
3021
3931
|
startElementPicker: () => startElementPicker,
|
|
@@ -3025,6 +3935,7 @@ var init_builder = __esm({
|
|
|
3025
3935
|
"src/plugins/builder/index.ts"() {
|
|
3026
3936
|
init_builder_mode();
|
|
3027
3937
|
init_builder_session();
|
|
3938
|
+
init_design_manipulator();
|
|
3028
3939
|
init_element_picker();
|
|
3029
3940
|
}
|
|
3030
3941
|
});
|
|
@@ -3291,8 +4202,8 @@ var DomListener = class {
|
|
|
3291
4202
|
let depth = 0;
|
|
3292
4203
|
while (current && current !== document.documentElement && depth < 5) {
|
|
3293
4204
|
const tag = current.tagName.toLowerCase();
|
|
3294
|
-
const
|
|
3295
|
-
if (
|
|
4205
|
+
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";
|
|
4206
|
+
if (isInteractive2) return current;
|
|
3296
4207
|
current = current.parentElement;
|
|
3297
4208
|
depth++;
|
|
3298
4209
|
}
|
|
@@ -3414,7 +4325,8 @@ var Queue = class {
|
|
|
3414
4325
|
return;
|
|
3415
4326
|
}
|
|
3416
4327
|
this.retryCount++;
|
|
3417
|
-
const
|
|
4328
|
+
const base = Math.min(1e3 * 2 ** (this.retryCount - 1), RETRY_MAX_DELAY_MS);
|
|
4329
|
+
const delayMs = base / 2 + Math.random() * (base / 2);
|
|
3418
4330
|
if (this.retryTimer) clearTimeout(this.retryTimer);
|
|
3419
4331
|
this.retryTimer = setTimeout(() => {
|
|
3420
4332
|
void this.flush();
|
|
@@ -4093,6 +5005,7 @@ function toWire(payload) {
|
|
|
4093
5005
|
if (typeof payload.stepIndex === "number") wire.stepPosition = payload.stepIndex;
|
|
4094
5006
|
if (payload.pageUrl) wire.pageUrl = payload.pageUrl;
|
|
4095
5007
|
if (payload.pagePath) wire.pagePath = payload.pagePath;
|
|
5008
|
+
if (payload.metadata) wire.metadata = payload.metadata;
|
|
4096
5009
|
return wire;
|
|
4097
5010
|
}
|
|
4098
5011
|
var GuideTrackerClient = class {
|
|
@@ -4140,6 +5053,8 @@ var GuideTrackerClient = class {
|
|
|
4140
5053
|
};
|
|
4141
5054
|
|
|
4142
5055
|
// src/plugins/guides/guide-tracker-queue.ts
|
|
5056
|
+
var RETRY_BASE_MS = 3e3;
|
|
5057
|
+
var RETRY_MAX_DELAY_MS2 = 3e4;
|
|
4143
5058
|
var GuideTrackerQueue = class {
|
|
4144
5059
|
constructor(config) {
|
|
4145
5060
|
this.config = config;
|
|
@@ -4176,20 +5091,23 @@ var GuideTrackerQueue = class {
|
|
|
4176
5091
|
*/
|
|
4177
5092
|
async flush() {
|
|
4178
5093
|
if (this.flushing || this.buffer.length === 0) return;
|
|
5094
|
+
const now = Date.now();
|
|
5095
|
+
const due = this.buffer.filter((item) => (item.nextRetryAt ?? 0) <= now);
|
|
5096
|
+
if (due.length === 0) return;
|
|
4179
5097
|
this.flushing = true;
|
|
4180
|
-
const
|
|
5098
|
+
const dueSet = new Set(due);
|
|
5099
|
+
this.buffer = this.buffer.filter((item) => !dueSet.has(item));
|
|
4181
5100
|
const results = await Promise.allSettled(
|
|
4182
|
-
|
|
4183
|
-
(item) => this.config.client.send({ guideId: item.guideId, payload: item.payload })
|
|
4184
|
-
)
|
|
5101
|
+
due.map((item) => this.config.client.send({ guideId: item.guideId, payload: item.payload }))
|
|
4185
5102
|
);
|
|
4186
5103
|
for (let i = 0; i < results.length; i++) {
|
|
4187
5104
|
const result = results[i];
|
|
4188
|
-
const item =
|
|
5105
|
+
const item = due[i];
|
|
4189
5106
|
if (!result || !item) continue;
|
|
4190
5107
|
if (result.status === "fulfilled") continue;
|
|
4191
5108
|
item.attempts += 1;
|
|
4192
5109
|
if (item.attempts < this.config.maxRetries) {
|
|
5110
|
+
item.nextRetryAt = Date.now() + this.retryDelayMs(item.attempts);
|
|
4193
5111
|
this.buffer.push(item);
|
|
4194
5112
|
} else {
|
|
4195
5113
|
this.safeOnError(result.reason, item);
|
|
@@ -4197,6 +5115,15 @@ var GuideTrackerQueue = class {
|
|
|
4197
5115
|
}
|
|
4198
5116
|
this.flushing = false;
|
|
4199
5117
|
}
|
|
5118
|
+
/**
|
|
5119
|
+
* Backoff exponencial (base = intervalo de flush) con equal-jitter: mitad
|
|
5120
|
+
* fija + mitad aleatoria. Desincroniza los reintentos entre clientes ante
|
|
5121
|
+
* una caída del backend.
|
|
5122
|
+
*/
|
|
5123
|
+
retryDelayMs(attempts) {
|
|
5124
|
+
const base = Math.min(RETRY_BASE_MS * 2 ** (attempts - 1), RETRY_MAX_DELAY_MS2);
|
|
5125
|
+
return base / 2 + Math.random() * (base / 2);
|
|
5126
|
+
}
|
|
4200
5127
|
/**
|
|
4201
5128
|
* Drena el buffer vía `sendBeacon`. Síncrono, sin retry. Para pagehide.
|
|
4202
5129
|
*/
|
|
@@ -4243,6 +5170,7 @@ var GuideTrackerQueue = class {
|
|
|
4243
5170
|
};
|
|
4244
5171
|
|
|
4245
5172
|
// src/plugins/guides/guides-controller.ts
|
|
5173
|
+
init_badge_renderer();
|
|
4246
5174
|
init_banner_renderer();
|
|
4247
5175
|
init_custom_renderer();
|
|
4248
5176
|
init_form_renderer();
|
|
@@ -4370,6 +5298,7 @@ var WalkthroughStateManager = class {
|
|
|
4370
5298
|
};
|
|
4371
5299
|
|
|
4372
5300
|
// src/plugins/guides/guides-controller.ts
|
|
5301
|
+
var RESOLVE_CACHE_TTL_MS = 3e4;
|
|
4373
5302
|
var STATUS_BY_ACTION = {
|
|
4374
5303
|
shown: "shown",
|
|
4375
5304
|
dismissed: "dismissed",
|
|
@@ -4396,6 +5325,10 @@ var GuidesController = class {
|
|
|
4396
5325
|
*/
|
|
4397
5326
|
this.activeWalkthroughRenderer = null;
|
|
4398
5327
|
this.activeWalkthroughGuide = null;
|
|
5328
|
+
/** Última resolución cacheada (mismo `(endUserId, url)` → sin red). */
|
|
5329
|
+
this.resolveCache = null;
|
|
5330
|
+
/** Resolución idéntica en vuelo, para coalescer llamadas concurrentes. */
|
|
5331
|
+
this.inflightResolve = null;
|
|
4399
5332
|
this.debug = config.debug === true;
|
|
4400
5333
|
this.apiUrl = config.apiUrl;
|
|
4401
5334
|
this.apiKey = config.apiKey;
|
|
@@ -4447,7 +5380,7 @@ var GuidesController = class {
|
|
|
4447
5380
|
}
|
|
4448
5381
|
let guides;
|
|
4449
5382
|
try {
|
|
4450
|
-
guides = await this.
|
|
5383
|
+
guides = await this.resolveGuides(endUserId, pageUrl);
|
|
4451
5384
|
} catch (err) {
|
|
4452
5385
|
if (this.debug) console.error("[veo] guides resolver threw:", err);
|
|
4453
5386
|
return;
|
|
@@ -4474,6 +5407,36 @@ var GuidesController = class {
|
|
|
4474
5407
|
this.runSingleStepRender(guide, sessionId, pageUrl);
|
|
4475
5408
|
}
|
|
4476
5409
|
}
|
|
5410
|
+
/**
|
|
5411
|
+
* `resolve` con cache de corta duración + coalescing:
|
|
5412
|
+
* - Mismo `(endUserId, url)` resuelto hace < {@link RESOLVE_CACHE_TTL_MS} →
|
|
5413
|
+
* se reusa sin red. Colapsa el doble disparo identify+pageview y los
|
|
5414
|
+
* pageviews repetidos de una SPA hacia la misma URL.
|
|
5415
|
+
* - Si ya hay un resolve idéntico en vuelo, se espera ESE (no se abre otro).
|
|
5416
|
+
* El re-render sigue siendo idempotente (dedupe por `activeByGuideId` +
|
|
5417
|
+
* `frequencyCache`), así que reusar el resultado no duplica guías.
|
|
5418
|
+
*/
|
|
5419
|
+
async resolveGuides(endUserId, pageUrl) {
|
|
5420
|
+
const key = `${endUserId}
|
|
5421
|
+
${pageUrl}`;
|
|
5422
|
+
const now = Date.now();
|
|
5423
|
+
if (this.resolveCache?.key === key && now - this.resolveCache.at < RESOLVE_CACHE_TTL_MS) {
|
|
5424
|
+
if (this.debug) console.log("[veo] guides: resolve cache hit");
|
|
5425
|
+
return this.resolveCache.guides;
|
|
5426
|
+
}
|
|
5427
|
+
if (this.inflightResolve?.key === key) {
|
|
5428
|
+
return this.inflightResolve.promise;
|
|
5429
|
+
}
|
|
5430
|
+
const promise = this.resolver.resolve(endUserId, pageUrl);
|
|
5431
|
+
this.inflightResolve = { key, promise };
|
|
5432
|
+
try {
|
|
5433
|
+
const guides = await promise;
|
|
5434
|
+
this.resolveCache = { key, at: Date.now(), guides };
|
|
5435
|
+
return guides;
|
|
5436
|
+
} finally {
|
|
5437
|
+
if (this.inflightResolve?.key === key) this.inflightResolve = null;
|
|
5438
|
+
}
|
|
5439
|
+
}
|
|
4477
5440
|
/** Cierra todas las guías activas, limpia cache de dispatch y para timers. */
|
|
4478
5441
|
destroy() {
|
|
4479
5442
|
for (const renderer of this.activeRenderers) {
|
|
@@ -4494,6 +5457,8 @@ var GuidesController = class {
|
|
|
4494
5457
|
this.activeWalkthroughGuide = null;
|
|
4495
5458
|
}
|
|
4496
5459
|
this.dispatched.clear();
|
|
5460
|
+
this.resolveCache = null;
|
|
5461
|
+
this.inflightResolve = null;
|
|
4497
5462
|
this.trackerQueue.destroy();
|
|
4498
5463
|
}
|
|
4499
5464
|
runSingleStepRender(guide, sessionId, pageUrl) {
|
|
@@ -4718,7 +5683,8 @@ var GuidesController = class {
|
|
|
4718
5683
|
* por si el usuario se identificó después de que la guía apareció.
|
|
4719
5684
|
*/
|
|
4720
5685
|
handleInteraction(guide, sessionId, pageUrl, event) {
|
|
4721
|
-
const
|
|
5686
|
+
const buttonKey = event.metadata?.buttonId ? `:${event.metadata.buttonId}` : "";
|
|
5687
|
+
const dedupKey = `${guide.guideId}:${event.action}${buttonKey}`;
|
|
4722
5688
|
if (this.dispatched.has(dedupKey)) {
|
|
4723
5689
|
if (this.debug) console.log(`[veo] guides: dedup hit ${dedupKey}`);
|
|
4724
5690
|
return;
|
|
@@ -4740,7 +5706,8 @@ var GuidesController = class {
|
|
|
4740
5706
|
action: event.action,
|
|
4741
5707
|
stepIndex: event.stepIndex,
|
|
4742
5708
|
pageUrl,
|
|
4743
|
-
pagePath: extractPath(pageUrl)
|
|
5709
|
+
pagePath: extractPath(pageUrl),
|
|
5710
|
+
...event.metadata ? { metadata: event.metadata } : {}
|
|
4744
5711
|
}
|
|
4745
5712
|
});
|
|
4746
5713
|
}
|
|
@@ -4791,6 +5758,8 @@ var GuidesController = class {
|
|
|
4791
5758
|
return new FormRenderer();
|
|
4792
5759
|
case "inline-form":
|
|
4793
5760
|
return new InlineFormRenderer();
|
|
5761
|
+
case "badge":
|
|
5762
|
+
return new BadgeRenderer();
|
|
4794
5763
|
case "walkthrough":
|
|
4795
5764
|
return null;
|
|
4796
5765
|
}
|