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.
@@ -162,6 +162,112 @@ var WALKTHROUGH_STATE_KEY_PREFIX = "veo:walkthrough_state:";
162
162
  var WALKTHROUGH_ABANDONMENT_TIMEOUT_MS = 30 * 60 * 1e3;
163
163
  var WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS = 5e3;
164
164
 
165
+ // src/plugins/guides/rich-text.ts
166
+ var ALLOWED_TAGS = {
167
+ P: "p",
168
+ BR: "br",
169
+ STRONG: "strong",
170
+ B: "strong",
171
+ EM: "em",
172
+ I: "em",
173
+ U: "u",
174
+ S: "s",
175
+ UL: "ul",
176
+ OL: "ol",
177
+ LI: "li",
178
+ A: "a"
179
+ };
180
+ var DROP_TAGS = /* @__PURE__ */ new Set([
181
+ "SCRIPT",
182
+ "STYLE",
183
+ "TEMPLATE",
184
+ "IFRAME",
185
+ "OBJECT",
186
+ "EMBED",
187
+ "NOSCRIPT",
188
+ "TITLE",
189
+ "TEXTAREA",
190
+ "SELECT",
191
+ "SVG",
192
+ "MATH"
193
+ ]);
194
+ var MAX_INPUT_LEN = 16 * 1024;
195
+ var MAX_DEPTH = 20;
196
+ var MAX_NODES = 1e3;
197
+ function renderRichText(html, doc) {
198
+ const fragment = doc.createDocumentFragment();
199
+ if (typeof html !== "string" || html.length === 0) return fragment;
200
+ let parsed;
201
+ try {
202
+ parsed = new DOMParser().parseFromString(
203
+ html.length > MAX_INPUT_LEN ? html.slice(0, MAX_INPUT_LEN) : html,
204
+ "text/html"
205
+ );
206
+ } catch {
207
+ fragment.appendChild(doc.createTextNode(html));
208
+ return fragment;
209
+ }
210
+ const budget = { nodes: 0, exceeded: false };
211
+ for (const child of Array.from(parsed.body.childNodes)) {
212
+ const rebuilt = rebuildNode(child, doc, 0, budget);
213
+ if (rebuilt) fragment.appendChild(rebuilt);
214
+ }
215
+ if (budget.exceeded) {
216
+ const plain = doc.createDocumentFragment();
217
+ plain.appendChild(doc.createTextNode(parsed.body.textContent ?? ""));
218
+ return plain;
219
+ }
220
+ return fragment;
221
+ }
222
+ function rebuildNode(node, doc, depth, budget) {
223
+ if (budget.exceeded) return null;
224
+ if (++budget.nodes > MAX_NODES || depth > MAX_DEPTH) {
225
+ budget.exceeded = true;
226
+ return null;
227
+ }
228
+ if (node.nodeType === Node.TEXT_NODE) {
229
+ return doc.createTextNode(node.textContent ?? "");
230
+ }
231
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
232
+ const el = node;
233
+ if (DROP_TAGS.has(el.tagName)) return null;
234
+ const mapped = ALLOWED_TAGS[el.tagName];
235
+ if (!mapped) {
236
+ const frag = doc.createDocumentFragment();
237
+ for (const child of Array.from(el.childNodes)) {
238
+ const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
239
+ if (rebuilt2) frag.appendChild(rebuilt2);
240
+ }
241
+ return frag;
242
+ }
243
+ if (mapped === "a") {
244
+ const href = el.getAttribute("href") ?? "";
245
+ if (!isSafeUrl(href)) {
246
+ const frag = doc.createDocumentFragment();
247
+ for (const child of Array.from(el.childNodes)) {
248
+ const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
249
+ if (rebuilt2) frag.appendChild(rebuilt2);
250
+ }
251
+ return frag;
252
+ }
253
+ const a = doc.createElement("a");
254
+ a.setAttribute("href", href);
255
+ a.setAttribute("target", "_blank");
256
+ a.setAttribute("rel", "noopener noreferrer nofollow");
257
+ for (const child of Array.from(el.childNodes)) {
258
+ const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
259
+ if (rebuilt2) a.appendChild(rebuilt2);
260
+ }
261
+ return a;
262
+ }
263
+ const rebuilt = doc.createElement(mapped);
264
+ for (const child of Array.from(el.childNodes)) {
265
+ const childNode = rebuildNode(child, doc, depth + 1, budget);
266
+ if (childNode) rebuilt.appendChild(childNode);
267
+ }
268
+ return rebuilt;
269
+ }
270
+
165
271
  // src/plugins/guides/block-builder.ts
166
272
  function buildStepContent(step, doc, callbacks) {
167
273
  const container = doc.createElement("div");
@@ -256,9 +362,13 @@ function buildContentBlock(block, doc) {
256
362
  return el;
257
363
  }
258
364
  case "text": {
259
- const el = doc.createElement("p");
365
+ const el = doc.createElement("div");
260
366
  el.className = "veo-guide-text";
261
- el.textContent = typeof block.text === "string" ? block.text : "";
367
+ if (typeof block.html === "string" && block.html) {
368
+ el.appendChild(renderRichText(block.html, doc));
369
+ } else {
370
+ el.textContent = typeof block.text === "string" ? block.text : "";
371
+ }
262
372
  applyBlockStyle(el, block.style, "text");
263
373
  return el;
264
374
  }
@@ -284,7 +394,11 @@ function buildButtonBlock(block, doc, callbacks) {
284
394
  btn.addEventListener("click", () => {
285
395
  const action = block.action ?? "dismiss";
286
396
  const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
287
- callbacks.onCtaClick(action, url);
397
+ const meta = {
398
+ ...typeof block.id === "string" && block.id ? { buttonId: block.id } : {},
399
+ ...typeof block.text === "string" && block.text ? { buttonText: block.text } : {}
400
+ };
401
+ callbacks.onCtaClick(action, url, Object.keys(meta).length ? meta : void 0);
288
402
  });
289
403
  return btn;
290
404
  }
@@ -299,6 +413,10 @@ function clampNum(n, min, max) {
299
413
  function applyBlockStyle(el, style, kind) {
300
414
  if (!style || typeof style !== "object") return;
301
415
  const s = style;
416
+ if (kind === "image" && s.bleed === true) {
417
+ el.classList.add("veo-guide-image--bleed");
418
+ return;
419
+ }
302
420
  const align = typeof s.align === "string" ? s.align : null;
303
421
  if (align && (align === "left" || align === "center" || align === "right")) {
304
422
  if (kind === "text") el.style.textAlign = align;
@@ -346,78 +464,6 @@ function readCtaUrl(step) {
346
464
  return isSafeUrl(candidate) ? candidate : void 0;
347
465
  }
348
466
 
349
- // src/plugins/guides/walkthrough-block-builder.ts
350
- function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
351
- const container = doc.createElement("div");
352
- container.className = "veo-guide-content";
353
- const counter = doc.createElement("div");
354
- counter.className = "veo-walkthrough-counter";
355
- counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
356
- container.appendChild(counter);
357
- const progress = doc.createElement("div");
358
- progress.className = "veo-walkthrough-progress";
359
- for (let i = 0; i < totalSteps; i++) {
360
- const dot = doc.createElement("span");
361
- dot.className = "veo-walkthrough-progress-dot";
362
- if (i < stepIndex) dot.classList.add("completed");
363
- if (i === stepIndex) dot.classList.add("active");
364
- progress.appendChild(dot);
365
- }
366
- container.appendChild(progress);
367
- if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
368
- const img = doc.createElement("img");
369
- img.className = "veo-guide-image";
370
- img.src = step.imageUrl;
371
- img.alt = typeof step.title === "string" ? step.title : "";
372
- container.appendChild(img);
373
- }
374
- if (typeof step.title === "string" && step.title) {
375
- const heading = doc.createElement("h2");
376
- heading.className = "veo-guide-title";
377
- heading.textContent = step.title;
378
- container.appendChild(heading);
379
- }
380
- if (typeof step.content === "string" && step.content) {
381
- const paragraph = doc.createElement("p");
382
- paragraph.className = "veo-guide-text";
383
- paragraph.textContent = step.content;
384
- container.appendChild(paragraph);
385
- }
386
- const actions = doc.createElement("div");
387
- actions.className = "veo-walkthrough-actions";
388
- const skipBtn = doc.createElement("button");
389
- skipBtn.type = "button";
390
- skipBtn.className = "veo-walkthrough-skip";
391
- skipBtn.textContent = "Omitir";
392
- skipBtn.addEventListener("click", () => callbacks.onSkip());
393
- actions.appendChild(skipBtn);
394
- const rightGroup = doc.createElement("div");
395
- rightGroup.className = "veo-walkthrough-actions-right";
396
- if (stepIndex > 0) {
397
- const backBtn = doc.createElement("button");
398
- backBtn.type = "button";
399
- backBtn.className = "veo-walkthrough-btn-secondary";
400
- backBtn.textContent = "Atr\xE1s";
401
- backBtn.addEventListener("click", () => callbacks.onBack());
402
- rightGroup.appendChild(backBtn);
403
- }
404
- const isLastStep = stepIndex === totalSteps - 1;
405
- const primaryBtn = doc.createElement("button");
406
- primaryBtn.type = "button";
407
- primaryBtn.className = "veo-guide-cta";
408
- const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
409
- primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
410
- primaryBtn.addEventListener("click", () => {
411
- if (isLastStep) callbacks.onComplete();
412
- else callbacks.onNext();
413
- });
414
- rightGroup.appendChild(primaryBtn);
415
- actions.appendChild(rightGroup);
416
- container.appendChild(actions);
417
- container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
418
- return container;
419
- }
420
-
421
467
  // src/plugins/guides/guide-design.ts
422
468
  var DARK_THEME = {
423
469
  "--veo-bg": "#1f2937",
@@ -490,6 +536,9 @@ function applyDesignVars(host, style) {
490
536
  if (typeof s.width === "number" && Number.isFinite(s.width)) {
491
537
  host.style.setProperty("--veo-width", `${clamp(s.width, 220, 720)}px`);
492
538
  }
539
+ if (typeof s.height === "number" && Number.isFinite(s.height)) {
540
+ host.style.setProperty("--veo-min-h", `${clamp(s.height, 120, 900)}px`);
541
+ }
493
542
  if (s.align === "left" || s.align === "center" || s.align === "right") {
494
543
  host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[s.align]);
495
544
  }
@@ -575,6 +624,68 @@ function applyElementVars(host, prefix, raw) {
575
624
  }
576
625
  }
577
626
 
627
+ // src/plugins/guides/inline-host.ts
628
+ function readInlinePosition(style) {
629
+ const p = style?.inlinePosition;
630
+ return p === "before" || p === "prepend" || p === "append" ? p : "after";
631
+ }
632
+ function insertHost(anchor, host, position) {
633
+ switch (position) {
634
+ case "before":
635
+ anchor.before(host);
636
+ break;
637
+ case "after":
638
+ anchor.after(host);
639
+ break;
640
+ case "prepend":
641
+ anchor.prepend(host);
642
+ break;
643
+ case "append":
644
+ anchor.append(host);
645
+ break;
646
+ }
647
+ }
648
+ function keepHostAttached(host, selector, position) {
649
+ const id = window.setInterval(() => {
650
+ if (host.isConnected) return;
651
+ const anchor = document.querySelector(selector);
652
+ if (anchor) insertHost(anchor, host, position);
653
+ }, 1e3);
654
+ return () => window.clearInterval(id);
655
+ }
656
+
657
+ // src/plugins/guides/wait-for-element.ts
658
+ function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
659
+ return new Promise((resolve) => {
660
+ const safeQuery = () => {
661
+ try {
662
+ return document.querySelector(selector);
663
+ } catch {
664
+ return null;
665
+ }
666
+ };
667
+ const existing = safeQuery();
668
+ if (existing) {
669
+ resolve(existing);
670
+ return;
671
+ }
672
+ let resolved = false;
673
+ const finish = (el) => {
674
+ if (resolved) return;
675
+ resolved = true;
676
+ observer.disconnect();
677
+ clearTimeout(timer);
678
+ resolve(el);
679
+ };
680
+ const observer = new MutationObserver(() => {
681
+ const el = safeQuery();
682
+ if (el) finish(el);
683
+ });
684
+ observer.observe(document.body, { childList: true, subtree: true });
685
+ const timer = setTimeout(() => finish(null), timeoutMs);
686
+ });
687
+ }
688
+
578
689
  // src/plugins/guides/styles.ts
579
690
  var GUIDE_STYLES = `
580
691
  :host {
@@ -596,6 +707,13 @@ var GUIDE_STYLES = `
596
707
  z-index: ${GUIDE_Z_INDEX};
597
708
  animation: veo-fade-in 180ms ease-out;
598
709
  }
710
+ /* Sin backdrop (style.backdrop === false): la app queda usable detr\xE1s; solo la
711
+ tarjeta captura el mouse. El click-en-backdrop deja de cerrar (no hay backdrop). */
712
+ .veo-modal-overlay--none {
713
+ background: transparent;
714
+ pointer-events: none;
715
+ }
716
+ .veo-modal-overlay--none .veo-modal-card { pointer-events: auto; }
599
717
  /*
600
718
  * Posici\xF3n libre/preset: --veo-pos-x/y son porcentajes (default 50% = centro).
601
719
  * El truco translate(-pos) alinea la MISMA fracci\xF3n de la tarjeta con esa
@@ -612,6 +730,7 @@ var GUIDE_STYLES = `
612
730
  border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
613
731
  padding: var(--veo-pad, 24px);
614
732
  max-width: var(--veo-width); width: 90%;
733
+ min-height: var(--veo-min-h, auto);
615
734
  box-shadow: var(--veo-shadow);
616
735
  animation: veo-fade-in 180ms ease-out;
617
736
  }
@@ -627,6 +746,14 @@ var GUIDE_STYLES = `
627
746
  }
628
747
  .veo-banner-top { top: 0; }
629
748
  .veo-banner-bottom { bottom: 0; }
749
+ /* Banner EMBEBIDO en un contenedor (step.selector): fluye dentro del contenedor
750
+ y empuja su contenido, en vez de flotar fijo sobre la pantalla. */
751
+ .veo-banner-embedded {
752
+ position: static;
753
+ left: auto; right: auto;
754
+ width: 100%;
755
+ border-radius: var(--veo-radius, 0);
756
+ }
630
757
 
631
758
  .veo-tooltip {
632
759
  position: absolute;
@@ -680,6 +807,21 @@ var GUIDE_STYLES = `
680
807
  border-radius: var(--veo-image-radius, 8px);
681
808
  margin: var(--veo-image-mt, 0) 0 var(--veo-image-mb, 12px);
682
809
  }
810
+ /* Imagen A SANGRE: rompe el padding de la tarjeta y ocupa el ancho completo
811
+ (hero estilo anuncio). Como primer bloque, hereda el redondeo superior. */
812
+ .veo-guide-image--bleed {
813
+ width: calc(100% + var(--veo-pad, 24px) * 2);
814
+ max-width: none;
815
+ max-height: 280px;
816
+ align-self: auto;
817
+ border-radius: 0;
818
+ margin: 0 calc(var(--veo-pad, 24px) * -1) 12px;
819
+ }
820
+ .veo-guide-content > .veo-guide-image--bleed:first-child {
821
+ margin-top: calc(var(--veo-pad, 24px) * -1);
822
+ border-radius: calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px))
823
+ calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px)) 0 0;
824
+ }
683
825
  .veo-guide-title {
684
826
  font-size: var(--veo-title-size, 18px); font-weight: 600; line-height: 1.3;
685
827
  text-align: var(--veo-title-align, left);
@@ -692,6 +834,18 @@ var GUIDE_STYLES = `
692
834
  margin: var(--veo-text-mt, 0) 0 var(--veo-text-mb, 16px);
693
835
  color: var(--veo-text-color, var(--veo-text-secondary));
694
836
  }
837
+ /* Rich text dentro de un bloque de texto (p/listas/links/\xE9nfasis). */
838
+ .veo-guide-text p { margin: 0 0 8px; }
839
+ .veo-guide-text p:last-child { margin-bottom: 0; }
840
+ .veo-guide-text ul, .veo-guide-text ol { margin: 0 0 8px; padding-left: 20px; }
841
+ .veo-guide-text ul { list-style: disc; }
842
+ .veo-guide-text ol { list-style: decimal; }
843
+ .veo-guide-text li { margin: 2px 0; display: list-item; }
844
+ .veo-guide-text a { color: var(--veo-primary); text-decoration: underline; cursor: pointer; }
845
+ .veo-guide-text strong { font-weight: 600; }
846
+ .veo-guide-text em { font-style: italic; }
847
+ .veo-guide-text u { text-decoration: underline; }
848
+ .veo-guide-text s { text-decoration: line-through; }
695
849
  .veo-guide-actions {
696
850
  display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
697
851
  }
@@ -830,6 +984,44 @@ var GUIDE_STYLES = `
830
984
  }
831
985
  .veo-walkthrough-skip:hover { color: var(--veo-text); }
832
986
 
987
+ /* \u2500\u2500 Badge (elemento inyectado junto al ancla que abre un tooltip) \u2500\u2500 */
988
+ .veo-badge {
989
+ display: inline-flex; align-items: center; justify-content: center;
990
+ border: none; padding: 0; margin: 0 4px;
991
+ background: none; cursor: pointer;
992
+ line-height: 1; vertical-align: middle;
993
+ font-family: inherit;
994
+ }
995
+ .veo-badge:focus-visible { outline: 2px solid var(--veo-primary); outline-offset: 2px; }
996
+ .veo-badge--icon {
997
+ border-radius: 50%;
998
+ background: color-mix(in srgb, var(--veo-primary) 14%, transparent);
999
+ color: var(--veo-primary);
1000
+ font-weight: 600;
1001
+ }
1002
+ .veo-badge--dot {
1003
+ border-radius: 50%;
1004
+ background: var(--veo-primary);
1005
+ animation: veo-badge-pulse 2s ease-out infinite;
1006
+ }
1007
+ .veo-badge--pill {
1008
+ border-radius: 999px;
1009
+ background: var(--veo-primary);
1010
+ color: #fff;
1011
+ font-weight: 600;
1012
+ padding: 3px 9px;
1013
+ white-space: nowrap;
1014
+ }
1015
+ .veo-badge--image img { display: block; border-radius: 4px; object-fit: cover; }
1016
+ /* El tooltip del badge usa strategy fixed (el host vive dentro del flujo del
1017
+ cliente; un absoluto se recortar\xEDa con overflow de ancestros). */
1018
+ .veo-badge-tooltip { position: fixed; }
1019
+ @keyframes veo-badge-pulse {
1020
+ 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--veo-primary) 45%, transparent); }
1021
+ 70% { box-shadow: 0 0 0 7px transparent; }
1022
+ 100% { box-shadow: 0 0 0 0 transparent; }
1023
+ }
1024
+
833
1025
  .veo-custom-floating {
834
1026
  position: fixed;
835
1027
  z-index: ${GUIDE_Z_INDEX};
@@ -874,6 +1066,37 @@ var BaseRenderer = class {
874
1066
  this.host = null;
875
1067
  this.shadow = null;
876
1068
  this.cleanups = [];
1069
+ // ── Update in-place (preview en vivo del editor) ──
1070
+ // La subclase setea estas piezas en render() para habilitarlo: el contenedor
1071
+ // del contenido, el nodo de contenido actual y cómo reconstruirlo.
1072
+ this.liveContainer = null;
1073
+ this.liveContent = null;
1074
+ this.liveBuild = null;
1075
+ /** Clave de anclaje/estructura fijada en render(); si cambia → remontar. */
1076
+ this.liveKey = "";
1077
+ }
1078
+ /** Clave de anclaje derivada del step (override por subclase). */
1079
+ liveKeyOf(_step) {
1080
+ return "";
1081
+ }
1082
+ /** Hook post-swap (reposicionar tooltip, actualizar clase del banner…). */
1083
+ onLiveUpdate(_step) {
1084
+ }
1085
+ /**
1086
+ * Actualiza el contenido del step SIN remontar el host — así el preview del
1087
+ * editor no parpadea en cada tecla. Devuelve `false` cuando el renderer no
1088
+ * lo soporta o el cambio requiere remontar (cambió el ancla/estructura);
1089
+ * en ese caso el caller hace el re-render completo.
1090
+ */
1091
+ updateStep(step) {
1092
+ if (!this.host || !this.liveContainer || !this.liveContent || !this.liveBuild) return false;
1093
+ if (this.liveKeyOf(step) !== this.liveKey) return false;
1094
+ const next = this.liveBuild(step);
1095
+ this.liveContainer.replaceChild(next, this.liveContent);
1096
+ this.liveContent = next;
1097
+ this.applyDesign(step.style);
1098
+ this.onLiveUpdate(step);
1099
+ return true;
877
1100
  }
878
1101
  /**
879
1102
  * Crea un `<div data-veo-guide>` adjunto a `document.body`, le adjunta
@@ -909,6 +1132,14 @@ var BaseRenderer = class {
909
1132
  registerCleanup(fn) {
910
1133
  this.cleanups.push(fn);
911
1134
  }
1135
+ /**
1136
+ * Host de la guía montada (o `null` si aún no se montó / ya se destruyó).
1137
+ * Lo usa el modo builder para adjuntar manipulación directa (drag/resize)
1138
+ * sobre el shadow root abierto sin tocar los renderers.
1139
+ */
1140
+ hostElement() {
1141
+ return this.host;
1142
+ }
912
1143
  /** Remueve el host del DOM y corre todas las funciones de cleanup. */
913
1144
  destroy() {
914
1145
  for (const fn of this.cleanups) {
@@ -926,20 +1157,372 @@ var BaseRenderer = class {
926
1157
  }
927
1158
  };
928
1159
 
1160
+ // src/plugins/guides/renderers/floating-arrow.ts
1161
+ var STATIC_SIDE = {
1162
+ top: "bottom",
1163
+ bottom: "top",
1164
+ left: "right",
1165
+ right: "left"
1166
+ };
1167
+ function positionArrow(arrowEl, placement, data) {
1168
+ const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1169
+ for (const prop of ["top", "bottom", "left", "right"]) {
1170
+ arrowEl.style.setProperty(prop, "");
1171
+ }
1172
+ if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1173
+ if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1174
+ arrowEl.style.setProperty(side, "-6px");
1175
+ }
1176
+
1177
+ // src/plugins/guides/renderers/badge-renderer.ts
1178
+ var HOVER_CLOSE_DELAY_MS = 150;
1179
+ var BadgeRenderer = class extends BaseRenderer {
1180
+ constructor() {
1181
+ super(...arguments);
1182
+ this.tooltip = null;
1183
+ this.arrowEl = null;
1184
+ this.badgeBtn = null;
1185
+ this.open = false;
1186
+ this.shownEmitted = false;
1187
+ this.trigger = "hover";
1188
+ this.side = "top";
1189
+ this.closeTimer = null;
1190
+ this.stopFloat = null;
1191
+ }
1192
+ async render(context) {
1193
+ const step = context.guide.guideSteps[0];
1194
+ if (!step) return;
1195
+ const selector = step.selector ?? context.guide.activationRules.selector;
1196
+ if (typeof selector !== "string" || selector.length === 0) return;
1197
+ const anchor = await waitForElement(selector);
1198
+ if (!anchor) return;
1199
+ const { host, root } = this.createHost();
1200
+ const ownerDocument = root.ownerDocument ?? document;
1201
+ this.applyDesign(step.style);
1202
+ this.trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
1203
+ const rawSide = step.style?.tooltipPlacement;
1204
+ this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
1205
+ const position = readInlinePosition(step.style);
1206
+ host.style.display = "inline-flex";
1207
+ host.style.verticalAlign = "middle";
1208
+ const badge = buildBadgeElement(readBadgeConfig(step), ownerDocument);
1209
+ root.appendChild(badge);
1210
+ this.badgeBtn = badge;
1211
+ insertHost(anchor, host, position);
1212
+ this.registerCleanup(keepHostAttached(host, selector, position));
1213
+ const tooltip = ownerDocument.createElement("div");
1214
+ tooltip.className = "veo-tooltip veo-badge-tooltip";
1215
+ tooltip.style.display = "none";
1216
+ const emit = (action, meta) => {
1217
+ context.onInteraction({
1218
+ guideId: context.guide.guideId,
1219
+ stepIndex: 0,
1220
+ action,
1221
+ ...meta ? { metadata: meta } : {}
1222
+ });
1223
+ };
1224
+ const buildTooltipContent = (s) => buildStepContent(s, ownerDocument, {
1225
+ onCtaClick: (action, url, meta) => {
1226
+ emit("cta_clicked", meta);
1227
+ if (action === "url" && url) window.open(url, "_blank", "noopener,noreferrer");
1228
+ if (action === "dismiss") {
1229
+ emit("dismissed");
1230
+ context.onClose();
1231
+ return;
1232
+ }
1233
+ this.closeTooltip();
1234
+ },
1235
+ onDismiss: () => {
1236
+ emit("dismissed");
1237
+ context.onClose();
1238
+ }
1239
+ });
1240
+ const content = buildTooltipContent(step);
1241
+ tooltip.appendChild(content);
1242
+ const arrowEl = ownerDocument.createElement("div");
1243
+ arrowEl.className = "veo-tooltip-arrow";
1244
+ tooltip.appendChild(arrowEl);
1245
+ root.appendChild(tooltip);
1246
+ this.tooltip = tooltip;
1247
+ this.arrowEl = arrowEl;
1248
+ this.liveContainer = tooltip;
1249
+ this.liveContent = content;
1250
+ this.liveKey = this.liveKeyOf(step);
1251
+ this.liveBuild = buildTooltipContent;
1252
+ const openNow = () => {
1253
+ this.cancelClose();
1254
+ if (!this.open) {
1255
+ this.openTooltip(badge);
1256
+ if (!this.shownEmitted) {
1257
+ this.shownEmitted = true;
1258
+ emit("shown");
1259
+ }
1260
+ }
1261
+ };
1262
+ if (this.trigger === "hover") {
1263
+ const scheduleClose = () => {
1264
+ this.cancelClose();
1265
+ this.closeTimer = window.setTimeout(() => this.closeTooltip(), HOVER_CLOSE_DELAY_MS);
1266
+ };
1267
+ badge.addEventListener("mouseenter", openNow);
1268
+ badge.addEventListener("focus", openNow);
1269
+ badge.addEventListener("mouseleave", scheduleClose);
1270
+ badge.addEventListener("blur", scheduleClose);
1271
+ tooltip.addEventListener("mouseenter", () => this.cancelClose());
1272
+ tooltip.addEventListener("mouseleave", scheduleClose);
1273
+ } else {
1274
+ badge.addEventListener("click", () => {
1275
+ if (this.open) this.closeTooltip();
1276
+ else openNow();
1277
+ });
1278
+ const onDocClick = (e) => {
1279
+ if (!this.open) return;
1280
+ if (e.composedPath().includes(host)) return;
1281
+ this.closeTooltip();
1282
+ };
1283
+ document.addEventListener("click", onDocClick, true);
1284
+ this.registerCleanup(() => document.removeEventListener("click", onDocClick, true));
1285
+ }
1286
+ const onKey = (e) => {
1287
+ if (e.key === "Escape" && this.open) this.closeTooltip();
1288
+ };
1289
+ document.addEventListener("keydown", onKey);
1290
+ this.registerCleanup(() => document.removeEventListener("keydown", onKey));
1291
+ if (context.isPreview) openNow();
1292
+ }
1293
+ /** Cambiar de ancla/posición/trigger requiere re-montar y re-cablear. */
1294
+ liveKeyOf(step) {
1295
+ const selector = step.selector ?? "";
1296
+ const position = readInlinePosition(step.style);
1297
+ const trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
1298
+ return `${selector}|${position}|${trigger}`;
1299
+ }
1300
+ onLiveUpdate(step) {
1301
+ if (this.badgeBtn) {
1302
+ const doc = this.badgeBtn.ownerDocument;
1303
+ const next = buildBadgeElement(readBadgeConfig(step), doc);
1304
+ this.badgeBtn.className = next.className;
1305
+ this.badgeBtn.setAttribute("style", next.getAttribute("style") ?? "");
1306
+ this.badgeBtn.replaceChildren(...Array.from(next.childNodes));
1307
+ }
1308
+ const rawSide = step.style?.tooltipPlacement;
1309
+ this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
1310
+ if (this.open && this.badgeBtn) this.position(this.badgeBtn);
1311
+ }
1312
+ destroy() {
1313
+ this.cancelClose();
1314
+ this.stopFloat?.();
1315
+ this.stopFloat = null;
1316
+ this.tooltip = null;
1317
+ this.arrowEl = null;
1318
+ this.badgeBtn = null;
1319
+ this.open = false;
1320
+ super.destroy();
1321
+ }
1322
+ openTooltip(badge) {
1323
+ if (!this.tooltip) return;
1324
+ this.tooltip.style.display = "block";
1325
+ this.open = true;
1326
+ void this.position(badge);
1327
+ const reposition = () => {
1328
+ void this.position(badge);
1329
+ };
1330
+ window.addEventListener("scroll", reposition, true);
1331
+ window.addEventListener("resize", reposition);
1332
+ this.stopFloat = () => {
1333
+ window.removeEventListener("scroll", reposition, true);
1334
+ window.removeEventListener("resize", reposition);
1335
+ };
1336
+ }
1337
+ closeTooltip() {
1338
+ this.cancelClose();
1339
+ if (!this.tooltip || !this.open) return;
1340
+ this.tooltip.style.display = "none";
1341
+ this.open = false;
1342
+ this.stopFloat?.();
1343
+ this.stopFloat = null;
1344
+ }
1345
+ cancelClose() {
1346
+ if (this.closeTimer !== null) {
1347
+ window.clearTimeout(this.closeTimer);
1348
+ this.closeTimer = null;
1349
+ }
1350
+ }
1351
+ async position(badge) {
1352
+ const tooltip = this.tooltip;
1353
+ const arrowEl = this.arrowEl;
1354
+ if (!tooltip || !arrowEl) return;
1355
+ const { x, y, placement, middlewareData } = await computePosition(badge, tooltip, {
1356
+ strategy: "fixed",
1357
+ placement: this.side,
1358
+ middleware: [offset(8), flip(), shift({ padding: 8 }), arrow({ element: arrowEl })]
1359
+ });
1360
+ tooltip.style.left = `${x}px`;
1361
+ tooltip.style.top = `${y}px`;
1362
+ positionArrow(arrowEl, placement, middlewareData.arrow);
1363
+ }
1364
+ };
1365
+ function readBadgeConfig(step) {
1366
+ const raw = step.style?.badge;
1367
+ if (!raw || typeof raw !== "object") return { kind: "icon" };
1368
+ const b = raw;
1369
+ const kind = b.kind === "dot" || b.kind === "pill" || b.kind === "image" || b.kind === "icon" ? b.kind : "icon";
1370
+ return {
1371
+ kind,
1372
+ icon: typeof b.icon === "string" ? b.icon : null,
1373
+ text: typeof b.text === "string" ? b.text : null,
1374
+ imageUrl: typeof b.imageUrl === "string" ? b.imageUrl : null,
1375
+ color: typeof b.color === "string" && COLOR_RE.test(b.color.trim()) ? b.color.trim() : null,
1376
+ size: typeof b.size === "number" && Number.isFinite(b.size) ? b.size : null
1377
+ };
1378
+ }
1379
+ function clamp2(n, min, max) {
1380
+ return Math.min(max, Math.max(min, n));
1381
+ }
1382
+ function buildBadgeElement(config, doc) {
1383
+ const btn = doc.createElement("button");
1384
+ btn.type = "button";
1385
+ btn.className = `veo-badge veo-badge--${config.kind}`;
1386
+ btn.setAttribute("aria-label", "M\xE1s informaci\xF3n");
1387
+ const size = clamp2(config.size ?? 18, 8, 48);
1388
+ switch (config.kind) {
1389
+ case "dot":
1390
+ btn.style.width = `${size}px`;
1391
+ btn.style.height = `${size}px`;
1392
+ if (config.color) btn.style.background = config.color;
1393
+ break;
1394
+ case "pill": {
1395
+ btn.textContent = config.text?.slice(0, 24) || "Nuevo";
1396
+ btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 24)}px`;
1397
+ if (config.color) btn.style.background = config.color;
1398
+ break;
1399
+ }
1400
+ case "image": {
1401
+ if (config.imageUrl && isSafeUrl(config.imageUrl)) {
1402
+ const img = doc.createElement("img");
1403
+ img.src = config.imageUrl;
1404
+ img.alt = "";
1405
+ img.style.width = `${size}px`;
1406
+ img.style.height = `${size}px`;
1407
+ btn.appendChild(img);
1408
+ } else {
1409
+ btn.className = "veo-badge veo-badge--icon";
1410
+ btn.textContent = "?";
1411
+ btn.style.width = `${size}px`;
1412
+ btn.style.height = `${size}px`;
1413
+ btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
1414
+ }
1415
+ break;
1416
+ }
1417
+ default: {
1418
+ btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
1419
+ btn.style.width = `${size}px`;
1420
+ btn.style.height = `${size}px`;
1421
+ btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
1422
+ if (config.color) btn.style.color = config.color;
1423
+ break;
1424
+ }
1425
+ }
1426
+ return btn;
1427
+ }
1428
+
1429
+ // src/plugins/guides/walkthrough-block-builder.ts
1430
+ function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
1431
+ const container = doc.createElement("div");
1432
+ container.className = "veo-guide-content";
1433
+ const counter = doc.createElement("div");
1434
+ counter.className = "veo-walkthrough-counter";
1435
+ counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
1436
+ container.appendChild(counter);
1437
+ const progress = doc.createElement("div");
1438
+ progress.className = "veo-walkthrough-progress";
1439
+ for (let i = 0; i < totalSteps; i++) {
1440
+ const dot = doc.createElement("span");
1441
+ dot.className = "veo-walkthrough-progress-dot";
1442
+ if (i < stepIndex) dot.classList.add("completed");
1443
+ if (i === stepIndex) dot.classList.add("active");
1444
+ progress.appendChild(dot);
1445
+ }
1446
+ container.appendChild(progress);
1447
+ if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
1448
+ const img = doc.createElement("img");
1449
+ img.className = "veo-guide-image";
1450
+ img.src = step.imageUrl;
1451
+ img.alt = typeof step.title === "string" ? step.title : "";
1452
+ container.appendChild(img);
1453
+ }
1454
+ if (typeof step.title === "string" && step.title) {
1455
+ const heading = doc.createElement("h2");
1456
+ heading.className = "veo-guide-title";
1457
+ heading.textContent = step.title;
1458
+ container.appendChild(heading);
1459
+ }
1460
+ if (typeof step.content === "string" && step.content) {
1461
+ const paragraph = doc.createElement("p");
1462
+ paragraph.className = "veo-guide-text";
1463
+ paragraph.textContent = step.content;
1464
+ container.appendChild(paragraph);
1465
+ }
1466
+ const actions = doc.createElement("div");
1467
+ actions.className = "veo-walkthrough-actions";
1468
+ const skipBtn = doc.createElement("button");
1469
+ skipBtn.type = "button";
1470
+ skipBtn.className = "veo-walkthrough-skip";
1471
+ skipBtn.textContent = "Omitir";
1472
+ skipBtn.addEventListener("click", () => callbacks.onSkip());
1473
+ actions.appendChild(skipBtn);
1474
+ const rightGroup = doc.createElement("div");
1475
+ rightGroup.className = "veo-walkthrough-actions-right";
1476
+ if (stepIndex > 0) {
1477
+ const backBtn = doc.createElement("button");
1478
+ backBtn.type = "button";
1479
+ backBtn.className = "veo-walkthrough-btn-secondary";
1480
+ backBtn.textContent = "Atr\xE1s";
1481
+ backBtn.addEventListener("click", () => callbacks.onBack());
1482
+ rightGroup.appendChild(backBtn);
1483
+ }
1484
+ const isLastStep = stepIndex === totalSteps - 1;
1485
+ const primaryBtn = doc.createElement("button");
1486
+ primaryBtn.type = "button";
1487
+ primaryBtn.className = "veo-guide-cta";
1488
+ const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
1489
+ primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
1490
+ primaryBtn.addEventListener("click", () => {
1491
+ if (isLastStep) callbacks.onComplete();
1492
+ else callbacks.onNext();
1493
+ });
1494
+ rightGroup.appendChild(primaryBtn);
1495
+ actions.appendChild(rightGroup);
1496
+ container.appendChild(actions);
1497
+ container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
1498
+ return container;
1499
+ }
1500
+
929
1501
  // src/plugins/guides/renderers/banner-renderer.ts
930
1502
  var BannerRenderer = class extends BaseRenderer {
931
- render(context) {
1503
+ async render(context) {
932
1504
  const idx = context.nav?.stepIndex ?? 0;
933
1505
  const step = context.guide.guideSteps[idx];
934
1506
  if (!step) return;
935
- const { root } = this.createHost();
1507
+ const position = step.style?.position === "bottom" ? "bottom" : "top";
1508
+ const selector = context.nav ? null : step.selector?.trim() ?? null;
1509
+ let anchor = null;
1510
+ if (selector) {
1511
+ anchor = await waitForElement(selector);
1512
+ if (!anchor) return;
1513
+ }
1514
+ const { host, root } = this.createHost();
936
1515
  this.applyDesign(step.style);
937
1516
  const ownerDocument = root.ownerDocument ?? document;
938
- const position = step.style?.position === "bottom" ? "bottom" : "top";
939
1517
  const banner = ownerDocument.createElement("div");
940
- banner.className = `veo-banner veo-banner-${position}`;
941
- const dismiss = (action, ctaUrl) => {
942
- context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
1518
+ banner.className = bannerClassOf(position, Boolean(anchor));
1519
+ const dismiss = (action, ctaUrl, meta) => {
1520
+ context.onInteraction({
1521
+ guideId: context.guide.guideId,
1522
+ stepIndex: idx,
1523
+ action,
1524
+ ...meta ? { metadata: meta } : {}
1525
+ });
943
1526
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
944
1527
  context.onClose();
945
1528
  };
@@ -950,47 +1533,45 @@ var BannerRenderer = class extends BaseRenderer {
950
1533
  ownerDocument,
951
1534
  context.nav.callbacks
952
1535
  ) : buildStepContent(step, ownerDocument, {
953
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1536
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
954
1537
  onDismiss: () => dismiss("dismissed")
955
1538
  });
956
1539
  banner.appendChild(content);
957
1540
  root.appendChild(banner);
1541
+ if (selector && anchor) {
1542
+ host.style.display = "block";
1543
+ const insertAt = position === "top" ? "prepend" : "append";
1544
+ insertHost(anchor, host, insertAt);
1545
+ this.registerCleanup(keepHostAttached(host, selector, insertAt));
1546
+ }
958
1547
  if (!context.nav) {
1548
+ this.liveContainer = banner;
1549
+ this.liveContent = content;
1550
+ this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
1551
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1552
+ onDismiss: () => dismiss("dismissed")
1553
+ });
1554
+ this.liveKey = this.liveKeyOf(step);
959
1555
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
960
1556
  }
961
1557
  }
962
- };
963
-
964
- // src/plugins/guides/wait-for-element.ts
965
- function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
966
- return new Promise((resolve) => {
967
- const safeQuery = () => {
968
- try {
969
- return document.querySelector(selector);
970
- } catch {
971
- return null;
972
- }
973
- };
974
- const existing = safeQuery();
975
- if (existing) {
976
- resolve(existing);
977
- return;
1558
+ /** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
1559
+ liveKeyOf(step) {
1560
+ const selector = step.selector?.trim() ?? "";
1561
+ if (!selector) return "";
1562
+ const position = step.style?.position === "bottom" ? "bottom" : "top";
1563
+ return `${selector}|${position}`;
1564
+ }
1565
+ onLiveUpdate(step) {
1566
+ if (this.liveContainer) {
1567
+ const position = step.style?.position === "bottom" ? "bottom" : "top";
1568
+ const embedded = Boolean(step.selector?.trim());
1569
+ this.liveContainer.className = bannerClassOf(position, embedded);
978
1570
  }
979
- let resolved = false;
980
- const finish = (el) => {
981
- if (resolved) return;
982
- resolved = true;
983
- observer.disconnect();
984
- clearTimeout(timer);
985
- resolve(el);
986
- };
987
- const observer = new MutationObserver(() => {
988
- const el = safeQuery();
989
- if (el) finish(el);
990
- });
991
- observer.observe(document.body, { childList: true, subtree: true });
992
- const timer = setTimeout(() => finish(null), timeoutMs);
993
- });
1571
+ }
1572
+ };
1573
+ function bannerClassOf(position, embedded) {
1574
+ return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
994
1575
  }
995
1576
 
996
1577
  // src/utils/uuid.ts
@@ -1525,36 +2106,6 @@ var FormRenderer = class extends BaseRenderer {
1525
2106
  }
1526
2107
  };
1527
2108
 
1528
- // src/plugins/guides/inline-host.ts
1529
- function readInlinePosition(style) {
1530
- const p = style?.inlinePosition;
1531
- return p === "before" || p === "prepend" || p === "append" ? p : "after";
1532
- }
1533
- function insertHost(anchor, host, position) {
1534
- switch (position) {
1535
- case "before":
1536
- anchor.before(host);
1537
- break;
1538
- case "after":
1539
- anchor.after(host);
1540
- break;
1541
- case "prepend":
1542
- anchor.prepend(host);
1543
- break;
1544
- case "append":
1545
- anchor.append(host);
1546
- break;
1547
- }
1548
- }
1549
- function keepHostAttached(host, selector, position) {
1550
- const id = window.setInterval(() => {
1551
- if (host.isConnected) return;
1552
- const anchor = document.querySelector(selector);
1553
- if (anchor) insertHost(anchor, host, position);
1554
- }, 1e3);
1555
- return () => window.clearInterval(id);
1556
- }
1557
-
1558
2109
  // src/plugins/guides/renderers/inline-custom-renderer.ts
1559
2110
  var InlineCustomRenderer = class extends BaseRenderer {
1560
2111
  async render(context) {
@@ -1622,23 +2173,45 @@ var InlineRenderer = class extends BaseRenderer {
1622
2173
  const ownerDocument = root.ownerDocument ?? document;
1623
2174
  const card = ownerDocument.createElement("div");
1624
2175
  card.className = "veo-inline";
1625
- const dismiss = (action, ctaUrl) => {
1626
- context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action });
2176
+ const dismiss = (action, ctaUrl, meta) => {
2177
+ context.onInteraction({
2178
+ guideId: context.guide.guideId,
2179
+ stepIndex: 0,
2180
+ action,
2181
+ ...meta ? { metadata: meta } : {}
2182
+ });
1627
2183
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1628
2184
  context.onClose();
1629
2185
  };
1630
2186
  const content = buildStepContent(step, ownerDocument, {
1631
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2187
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1632
2188
  onDismiss: () => dismiss("dismissed")
1633
2189
  });
1634
2190
  card.appendChild(content);
1635
2191
  root.appendChild(card);
2192
+ this.liveContainer = card;
2193
+ this.liveContent = content;
2194
+ this.liveKey = `${selector}|${position}`;
2195
+ this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
2196
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
2197
+ onDismiss: () => dismiss("dismissed")
2198
+ });
1636
2199
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1637
2200
  }
2201
+ liveKeyOf(step) {
2202
+ return `${step.selector ?? ""}|${readInlinePosition(step.style)}`;
2203
+ }
1638
2204
  };
1639
2205
 
1640
2206
  // src/plugins/guides/renderers/modal-renderer.ts
2207
+ function overlayClassOf(step) {
2208
+ return step.style?.backdrop === false ? "veo-modal-overlay veo-modal-overlay--none" : "veo-modal-overlay";
2209
+ }
1641
2210
  var ModalRenderer = class extends BaseRenderer {
2211
+ constructor() {
2212
+ super(...arguments);
2213
+ this.overlay = null;
2214
+ }
1642
2215
  render(context) {
1643
2216
  const idx = context.nav?.stepIndex ?? 0;
1644
2217
  const step = context.guide.guideSteps[idx];
@@ -1647,11 +2220,17 @@ var ModalRenderer = class extends BaseRenderer {
1647
2220
  this.applyDesign(step.style);
1648
2221
  const ownerDocument = root.ownerDocument ?? document;
1649
2222
  const overlay = ownerDocument.createElement("div");
1650
- overlay.className = "veo-modal-overlay";
2223
+ overlay.className = overlayClassOf(step);
2224
+ this.overlay = overlay;
1651
2225
  const card = ownerDocument.createElement("div");
1652
2226
  card.className = "veo-modal-card";
1653
- const dismiss = (action, ctaUrl) => {
1654
- context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
2227
+ const dismiss = (action, ctaUrl, meta) => {
2228
+ context.onInteraction({
2229
+ guideId: context.guide.guideId,
2230
+ stepIndex: idx,
2231
+ action,
2232
+ ...meta ? { metadata: meta } : {}
2233
+ });
1655
2234
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1656
2235
  context.onClose();
1657
2236
  };
@@ -1666,12 +2245,20 @@ var ModalRenderer = class extends BaseRenderer {
1666
2245
  ownerDocument,
1667
2246
  context.nav.callbacks
1668
2247
  ) : buildStepContent(step, ownerDocument, {
1669
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2248
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1670
2249
  onDismiss: () => dismiss("dismissed")
1671
2250
  });
1672
2251
  card.appendChild(content);
1673
2252
  overlay.appendChild(card);
1674
2253
  root.appendChild(overlay);
2254
+ if (!context.nav) {
2255
+ this.liveContainer = card;
2256
+ this.liveContent = content;
2257
+ this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
2258
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
2259
+ onDismiss: () => dismiss("dismissed")
2260
+ });
2261
+ }
1675
2262
  overlay.addEventListener("click", (e) => {
1676
2263
  if (e.target === overlay) onBackdropOrEsc();
1677
2264
  });
@@ -1684,27 +2271,21 @@ var ModalRenderer = class extends BaseRenderer {
1684
2271
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1685
2272
  }
1686
2273
  }
1687
- };
1688
-
1689
- // src/plugins/guides/renderers/floating-arrow.ts
1690
- var STATIC_SIDE = {
1691
- top: "bottom",
1692
- bottom: "top",
1693
- left: "right",
1694
- right: "left"
1695
- };
1696
- function positionArrow(arrowEl, placement, data) {
1697
- const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1698
- for (const prop of ["top", "bottom", "left", "right"]) {
1699
- arrowEl.style.setProperty(prop, "");
2274
+ onLiveUpdate(step) {
2275
+ if (this.overlay) this.overlay.className = overlayClassOf(step);
1700
2276
  }
1701
- if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1702
- if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1703
- arrowEl.style.setProperty(side, "-6px");
1704
- }
1705
-
1706
- // src/plugins/guides/renderers/tooltip-renderer.ts
2277
+ destroy() {
2278
+ this.overlay = null;
2279
+ super.destroy();
2280
+ }
2281
+ };
1707
2282
  var TooltipRenderer = class extends BaseRenderer {
2283
+ constructor() {
2284
+ super(...arguments);
2285
+ /** Lado preferido (mutable: el update in-place puede cambiarlo). */
2286
+ this.side = "bottom";
2287
+ this.reposition = null;
2288
+ }
1708
2289
  async render(context) {
1709
2290
  const step = context.guide.guideSteps[0];
1710
2291
  if (!step) return;
@@ -1713,24 +2294,25 @@ var TooltipRenderer = class extends BaseRenderer {
1713
2294
  const anchor = await waitForElement(selector);
1714
2295
  if (!anchor) return;
1715
2296
  const rawSide = step.style?.tooltipPlacement;
1716
- const side = rawSide === "top" || rawSide === "left" || rawSide === "right" ? rawSide : "bottom";
2297
+ this.side = rawSide === "top" || rawSide === "left" || rawSide === "right" ? rawSide : "bottom";
1717
2298
  const { root } = this.createHost();
1718
2299
  this.applyDesign(step.style);
1719
2300
  const ownerDocument = root.ownerDocument ?? document;
1720
2301
  const tooltip = ownerDocument.createElement("div");
1721
2302
  tooltip.className = "veo-tooltip";
1722
- const dismiss = (action, ctaUrl) => {
2303
+ const dismiss = (action, ctaUrl, meta) => {
1723
2304
  context.onInteraction({
1724
2305
  guideId: context.guide.guideId,
1725
2306
  stepIndex: 0,
1726
- action
2307
+ action,
2308
+ ...meta ? { metadata: meta } : {}
1727
2309
  });
1728
2310
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1729
2311
  context.onClose();
1730
2312
  };
1731
2313
  const content = buildStepContent(step, ownerDocument, {
1732
- onCtaClick: (action, url) => {
1733
- dismiss("cta_clicked", action === "url" ? url : void 0);
2314
+ onCtaClick: (action, url, meta) => {
2315
+ dismiss("cta_clicked", action === "url" ? url : void 0, meta);
1734
2316
  },
1735
2317
  onDismiss: () => dismiss("dismissed")
1736
2318
  });
@@ -1741,7 +2323,7 @@ var TooltipRenderer = class extends BaseRenderer {
1741
2323
  root.appendChild(tooltip);
1742
2324
  const updatePosition = async () => {
1743
2325
  const { x, y, placement, middlewareData } = await computePosition(anchor, tooltip, {
1744
- placement: side,
2326
+ placement: this.side,
1745
2327
  middleware: [offset(10), flip(), shift({ padding: 8 }), arrow({ element: arrowEl })]
1746
2328
  });
1747
2329
  tooltip.style.left = `${x}px`;
@@ -1752,18 +2334,34 @@ var TooltipRenderer = class extends BaseRenderer {
1752
2334
  const reposition = () => {
1753
2335
  void updatePosition();
1754
2336
  };
2337
+ this.reposition = reposition;
1755
2338
  window.addEventListener("scroll", reposition, true);
1756
2339
  window.addEventListener("resize", reposition);
1757
2340
  this.registerCleanup(() => {
1758
2341
  window.removeEventListener("scroll", reposition, true);
1759
2342
  window.removeEventListener("resize", reposition);
1760
2343
  });
2344
+ this.liveContainer = tooltip;
2345
+ this.liveContent = content;
2346
+ this.liveKey = selector;
2347
+ this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
2348
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
2349
+ onDismiss: () => dismiss("dismissed")
2350
+ });
1761
2351
  context.onInteraction({
1762
2352
  guideId: context.guide.guideId,
1763
2353
  stepIndex: 0,
1764
2354
  action: "shown"
1765
2355
  });
1766
2356
  }
2357
+ liveKeyOf(step) {
2358
+ return step.selector ?? "";
2359
+ }
2360
+ onLiveUpdate(step) {
2361
+ const raw = step.style?.tooltipPlacement;
2362
+ this.side = raw === "top" || raw === "left" || raw === "right" ? raw : "bottom";
2363
+ this.reposition?.();
2364
+ }
1767
2365
  };
1768
2366
  var WalkthroughRenderer = class extends BaseRenderer {
1769
2367
  async render(context) {
@@ -1828,12 +2426,29 @@ var GuidePreviewController = class {
1828
2426
  this.input = null;
1829
2427
  }
1830
2428
  preview(input) {
2429
+ const nextStep = input.guideSteps[0];
2430
+ if (this.singleStep && this.input && input.guideType === this.input.guideType && input.guideType !== "walkthrough" && nextStep && this.singleStep.updateStep(nextStep)) {
2431
+ this.input = input;
2432
+ return {
2433
+ close: () => this.close(),
2434
+ ready: Promise.resolve({ rendered: true }),
2435
+ host: () => this.activeHost()
2436
+ };
2437
+ }
1831
2438
  this.close();
1832
2439
  this.input = input;
1833
2440
  const guide = normalize(input);
1834
2441
  const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
1835
2442
  const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
1836
- return { close: () => this.close(), ready };
2443
+ return { close: () => this.close(), ready, host: () => this.activeHost() };
2444
+ }
2445
+ /** Host de la guía montada (single-step o paso de walkthrough activo). */
2446
+ activeHost() {
2447
+ if (this.singleStep) return this.singleStep.hostElement();
2448
+ if (this.walkthrough instanceof BaseRenderer) {
2449
+ return this.walkthrough.hostElement();
2450
+ }
2451
+ return null;
1837
2452
  }
1838
2453
  close() {
1839
2454
  if (this.singleStep) {
@@ -1954,7 +2569,7 @@ var activeController = null;
1954
2569
  function previewGuide(input) {
1955
2570
  if (!hasDocument()) {
1956
2571
  return { close: () => {
1957
- }, ready: Promise.resolve({ rendered: false }) };
2572
+ }, ready: Promise.resolve({ rendered: false }), host: () => null };
1958
2573
  }
1959
2574
  if (!activeController) activeController = new GuidePreviewController();
1960
2575
  return activeController.preview(input);
@@ -1997,6 +2612,8 @@ function createSingleStepRenderer(type) {
1997
2612
  return new FormRenderer();
1998
2613
  case "inline-form":
1999
2614
  return new InlineFormRenderer();
2615
+ case "badge":
2616
+ return new BadgeRenderer();
2000
2617
  case "walkthrough":
2001
2618
  return null;
2002
2619
  }
@@ -2141,6 +2758,6 @@ function buildSelectorPath(element, maxAncestors = 5) {
2141
2758
  return parts.join(" > ");
2142
2759
  }
2143
2760
 
2144
- export { ALWAYS_BLOCK_SELECTORS, BannerRenderer, CustomRenderer, DEFAULT_AUTOCAPTURE_CONFIG, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, DEFAULT_TRACKER_MAX_RETRIES, FREQUENCY_CACHE_KEY_PREFIX, FREQUENCY_CACHE_MAX_ENTRIES, FREQUENCY_CACHE_TTL_MS, FormRenderer, InlineCustomRenderer, InlineFormRenderer, InlineRenderer, MAX_ANCESTORS, ModalRenderer, PRIORITY_ATTRIBUTES, SENSITIVE_ATTRIBUTES, TooltipRenderer, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS, WALKTHROUGH_STATE_KEY_PREFIX, WalkthroughRenderer, buildSelectorPath, clearBuilderToken, closeGuidePreview, filterHumanClasses, hasDocument, hasLocalStorage, hasNavigatorSendBeacon, hasWindow, isBuilderMode, previewGuide, resolveBuilderContext, resolveBuilderToken, uuidv7 };
2145
- //# sourceMappingURL=chunk-2INDL77W.mjs.map
2146
- //# sourceMappingURL=chunk-2INDL77W.mjs.map
2761
+ export { ALWAYS_BLOCK_SELECTORS, BadgeRenderer, BannerRenderer, CustomRenderer, DEFAULT_AUTOCAPTURE_CONFIG, DEFAULT_TRACKER_BATCH_SIZE, DEFAULT_TRACKER_FLUSH_INTERVAL_MS, DEFAULT_TRACKER_MAX_RETRIES, FREQUENCY_CACHE_KEY_PREFIX, FREQUENCY_CACHE_MAX_ENTRIES, FREQUENCY_CACHE_TTL_MS, FormRenderer, InlineCustomRenderer, InlineFormRenderer, InlineRenderer, MAX_ANCESTORS, ModalRenderer, PRIORITY_ATTRIBUTES, SENSITIVE_ATTRIBUTES, TooltipRenderer, WALKTHROUGH_ABANDONMENT_TIMEOUT_MS, WALKTHROUGH_STATE_KEY_PREFIX, WalkthroughRenderer, buildSelectorPath, clearBuilderToken, closeGuidePreview, filterHumanClasses, hasDocument, hasLocalStorage, hasNavigatorSendBeacon, hasWindow, isBuilderMode, previewGuide, resolveBuilderContext, resolveBuilderToken, uuidv7 };
2762
+ //# sourceMappingURL=chunk-ICKEQ7VC.mjs.map
2763
+ //# sourceMappingURL=chunk-ICKEQ7VC.mjs.map