veo-sdk 0.3.16 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,7 +26,7 @@ function hasNavigatorSendBeacon() {
26
26
  // src/core/builder-token.ts
27
27
  var CTX_KEY = "veo:builder_ctx";
28
28
  var LS_PREFIX = "veo:builder_ctx:";
29
- var PERSIST_TTL_MS = 12 * 60 * 60 * 1e3;
29
+ var PERSIST_TTL_MS = 30 * 60 * 1e3;
30
30
  function session() {
31
31
  try {
32
32
  return window.sessionStorage;
@@ -89,7 +89,11 @@ function readFreshFromLocal() {
89
89
  }
90
90
  function urlParams() {
91
91
  try {
92
- return new URLSearchParams(window.location.search);
92
+ const search = new URLSearchParams(window.location.search);
93
+ if (search.get(BUILDER_TOKEN_PARAM)) return search;
94
+ const hash = window.location.hash.replace(/^#/, "");
95
+ if (hash.includes(`${BUILDER_TOKEN_PARAM}=`)) return new URLSearchParams(hash);
96
+ return search;
93
97
  } catch {
94
98
  return null;
95
99
  }
@@ -162,6 +166,112 @@ var WALKTHROUGH_STATE_KEY_PREFIX = "veo:walkthrough_state:";
162
166
  var WALKTHROUGH_ABANDONMENT_TIMEOUT_MS = 30 * 60 * 1e3;
163
167
  var WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS = 5e3;
164
168
 
169
+ // src/plugins/guides/rich-text.ts
170
+ var ALLOWED_TAGS = {
171
+ P: "p",
172
+ BR: "br",
173
+ STRONG: "strong",
174
+ B: "strong",
175
+ EM: "em",
176
+ I: "em",
177
+ U: "u",
178
+ S: "s",
179
+ UL: "ul",
180
+ OL: "ol",
181
+ LI: "li",
182
+ A: "a"
183
+ };
184
+ var DROP_TAGS = /* @__PURE__ */ new Set([
185
+ "SCRIPT",
186
+ "STYLE",
187
+ "TEMPLATE",
188
+ "IFRAME",
189
+ "OBJECT",
190
+ "EMBED",
191
+ "NOSCRIPT",
192
+ "TITLE",
193
+ "TEXTAREA",
194
+ "SELECT",
195
+ "SVG",
196
+ "MATH"
197
+ ]);
198
+ var MAX_INPUT_LEN = 16 * 1024;
199
+ var MAX_DEPTH = 20;
200
+ var MAX_NODES = 1e3;
201
+ function renderRichText(html, doc) {
202
+ const fragment = doc.createDocumentFragment();
203
+ if (typeof html !== "string" || html.length === 0) return fragment;
204
+ let parsed;
205
+ try {
206
+ parsed = new DOMParser().parseFromString(
207
+ html.length > MAX_INPUT_LEN ? html.slice(0, MAX_INPUT_LEN) : html,
208
+ "text/html"
209
+ );
210
+ } catch {
211
+ fragment.appendChild(doc.createTextNode(html));
212
+ return fragment;
213
+ }
214
+ const budget = { nodes: 0, exceeded: false };
215
+ for (const child of Array.from(parsed.body.childNodes)) {
216
+ const rebuilt = rebuildNode(child, doc, 0, budget);
217
+ if (rebuilt) fragment.appendChild(rebuilt);
218
+ }
219
+ if (budget.exceeded) {
220
+ const plain = doc.createDocumentFragment();
221
+ plain.appendChild(doc.createTextNode(parsed.body.textContent ?? ""));
222
+ return plain;
223
+ }
224
+ return fragment;
225
+ }
226
+ function rebuildNode(node, doc, depth, budget) {
227
+ if (budget.exceeded) return null;
228
+ if (++budget.nodes > MAX_NODES || depth > MAX_DEPTH) {
229
+ budget.exceeded = true;
230
+ return null;
231
+ }
232
+ if (node.nodeType === Node.TEXT_NODE) {
233
+ return doc.createTextNode(node.textContent ?? "");
234
+ }
235
+ if (node.nodeType !== Node.ELEMENT_NODE) return null;
236
+ const el = node;
237
+ if (DROP_TAGS.has(el.tagName)) return null;
238
+ const mapped = ALLOWED_TAGS[el.tagName];
239
+ if (!mapped) {
240
+ const frag = doc.createDocumentFragment();
241
+ for (const child of Array.from(el.childNodes)) {
242
+ const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
243
+ if (rebuilt2) frag.appendChild(rebuilt2);
244
+ }
245
+ return frag;
246
+ }
247
+ if (mapped === "a") {
248
+ const href = el.getAttribute("href") ?? "";
249
+ if (!isSafeUrl(href)) {
250
+ const frag = doc.createDocumentFragment();
251
+ for (const child of Array.from(el.childNodes)) {
252
+ const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
253
+ if (rebuilt2) frag.appendChild(rebuilt2);
254
+ }
255
+ return frag;
256
+ }
257
+ const a = doc.createElement("a");
258
+ a.setAttribute("href", href);
259
+ a.setAttribute("target", "_blank");
260
+ a.setAttribute("rel", "noopener noreferrer nofollow");
261
+ for (const child of Array.from(el.childNodes)) {
262
+ const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
263
+ if (rebuilt2) a.appendChild(rebuilt2);
264
+ }
265
+ return a;
266
+ }
267
+ const rebuilt = doc.createElement(mapped);
268
+ for (const child of Array.from(el.childNodes)) {
269
+ const childNode = rebuildNode(child, doc, depth + 1, budget);
270
+ if (childNode) rebuilt.appendChild(childNode);
271
+ }
272
+ return rebuilt;
273
+ }
274
+
165
275
  // src/plugins/guides/block-builder.ts
166
276
  function buildStepContent(step, doc, callbacks) {
167
277
  const container = doc.createElement("div");
@@ -256,9 +366,13 @@ function buildContentBlock(block, doc) {
256
366
  return el;
257
367
  }
258
368
  case "text": {
259
- const el = doc.createElement("p");
369
+ const el = doc.createElement("div");
260
370
  el.className = "veo-guide-text";
261
- el.textContent = typeof block.text === "string" ? block.text : "";
371
+ if (typeof block.html === "string" && block.html) {
372
+ el.appendChild(renderRichText(block.html, doc));
373
+ } else {
374
+ el.textContent = typeof block.text === "string" ? block.text : "";
375
+ }
262
376
  applyBlockStyle(el, block.style, "text");
263
377
  return el;
264
378
  }
@@ -284,7 +398,11 @@ function buildButtonBlock(block, doc, callbacks) {
284
398
  btn.addEventListener("click", () => {
285
399
  const action = block.action ?? "dismiss";
286
400
  const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
287
- callbacks.onCtaClick(action, url);
401
+ const meta = {
402
+ ...typeof block.id === "string" && block.id ? { buttonId: block.id } : {},
403
+ ...typeof block.text === "string" && block.text ? { buttonText: block.text } : {}
404
+ };
405
+ callbacks.onCtaClick(action, url, Object.keys(meta).length ? meta : void 0);
288
406
  });
289
407
  return btn;
290
408
  }
@@ -299,6 +417,10 @@ function clampNum(n, min, max) {
299
417
  function applyBlockStyle(el, style, kind) {
300
418
  if (!style || typeof style !== "object") return;
301
419
  const s = style;
420
+ if (kind === "image" && s.bleed === true) {
421
+ el.classList.add("veo-guide-image--bleed");
422
+ return;
423
+ }
302
424
  const align = typeof s.align === "string" ? s.align : null;
303
425
  if (align && (align === "left" || align === "center" || align === "right")) {
304
426
  if (kind === "text") el.style.textAlign = align;
@@ -346,78 +468,6 @@ function readCtaUrl(step) {
346
468
  return isSafeUrl(candidate) ? candidate : void 0;
347
469
  }
348
470
 
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
471
  // src/plugins/guides/guide-design.ts
422
472
  var DARK_THEME = {
423
473
  "--veo-bg": "#1f2937",
@@ -490,6 +540,9 @@ function applyDesignVars(host, style) {
490
540
  if (typeof s.width === "number" && Number.isFinite(s.width)) {
491
541
  host.style.setProperty("--veo-width", `${clamp(s.width, 220, 720)}px`);
492
542
  }
543
+ if (typeof s.height === "number" && Number.isFinite(s.height)) {
544
+ host.style.setProperty("--veo-min-h", `${clamp(s.height, 120, 900)}px`);
545
+ }
493
546
  if (s.align === "left" || s.align === "center" || s.align === "right") {
494
547
  host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[s.align]);
495
548
  }
@@ -575,6 +628,68 @@ function applyElementVars(host, prefix, raw) {
575
628
  }
576
629
  }
577
630
 
631
+ // src/plugins/guides/inline-host.ts
632
+ function readInlinePosition(style) {
633
+ const p = style?.inlinePosition;
634
+ return p === "before" || p === "prepend" || p === "append" ? p : "after";
635
+ }
636
+ function insertHost(anchor, host, position) {
637
+ switch (position) {
638
+ case "before":
639
+ anchor.before(host);
640
+ break;
641
+ case "after":
642
+ anchor.after(host);
643
+ break;
644
+ case "prepend":
645
+ anchor.prepend(host);
646
+ break;
647
+ case "append":
648
+ anchor.append(host);
649
+ break;
650
+ }
651
+ }
652
+ function keepHostAttached(host, selector, position) {
653
+ const id = window.setInterval(() => {
654
+ if (host.isConnected) return;
655
+ const anchor = document.querySelector(selector);
656
+ if (anchor) insertHost(anchor, host, position);
657
+ }, 1e3);
658
+ return () => window.clearInterval(id);
659
+ }
660
+
661
+ // src/plugins/guides/wait-for-element.ts
662
+ function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
663
+ return new Promise((resolve) => {
664
+ const safeQuery = () => {
665
+ try {
666
+ return document.querySelector(selector);
667
+ } catch {
668
+ return null;
669
+ }
670
+ };
671
+ const existing = safeQuery();
672
+ if (existing) {
673
+ resolve(existing);
674
+ return;
675
+ }
676
+ let resolved = false;
677
+ const finish = (el) => {
678
+ if (resolved) return;
679
+ resolved = true;
680
+ observer.disconnect();
681
+ clearTimeout(timer);
682
+ resolve(el);
683
+ };
684
+ const observer = new MutationObserver(() => {
685
+ const el = safeQuery();
686
+ if (el) finish(el);
687
+ });
688
+ observer.observe(document.body, { childList: true, subtree: true });
689
+ const timer = setTimeout(() => finish(null), timeoutMs);
690
+ });
691
+ }
692
+
578
693
  // src/plugins/guides/styles.ts
579
694
  var GUIDE_STYLES = `
580
695
  :host {
@@ -596,6 +711,13 @@ var GUIDE_STYLES = `
596
711
  z-index: ${GUIDE_Z_INDEX};
597
712
  animation: veo-fade-in 180ms ease-out;
598
713
  }
714
+ /* Sin backdrop (style.backdrop === false): la app queda usable detr\xE1s; solo la
715
+ tarjeta captura el mouse. El click-en-backdrop deja de cerrar (no hay backdrop). */
716
+ .veo-modal-overlay--none {
717
+ background: transparent;
718
+ pointer-events: none;
719
+ }
720
+ .veo-modal-overlay--none .veo-modal-card { pointer-events: auto; }
599
721
  /*
600
722
  * Posici\xF3n libre/preset: --veo-pos-x/y son porcentajes (default 50% = centro).
601
723
  * El truco translate(-pos) alinea la MISMA fracci\xF3n de la tarjeta con esa
@@ -612,6 +734,7 @@ var GUIDE_STYLES = `
612
734
  border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
613
735
  padding: var(--veo-pad, 24px);
614
736
  max-width: var(--veo-width); width: 90%;
737
+ min-height: var(--veo-min-h, auto);
615
738
  box-shadow: var(--veo-shadow);
616
739
  animation: veo-fade-in 180ms ease-out;
617
740
  }
@@ -627,6 +750,14 @@ var GUIDE_STYLES = `
627
750
  }
628
751
  .veo-banner-top { top: 0; }
629
752
  .veo-banner-bottom { bottom: 0; }
753
+ /* Banner EMBEBIDO en un contenedor (step.selector): fluye dentro del contenedor
754
+ y empuja su contenido, en vez de flotar fijo sobre la pantalla. */
755
+ .veo-banner-embedded {
756
+ position: static;
757
+ left: auto; right: auto;
758
+ width: 100%;
759
+ border-radius: var(--veo-radius, 0);
760
+ }
630
761
 
631
762
  .veo-tooltip {
632
763
  position: absolute;
@@ -680,6 +811,21 @@ var GUIDE_STYLES = `
680
811
  border-radius: var(--veo-image-radius, 8px);
681
812
  margin: var(--veo-image-mt, 0) 0 var(--veo-image-mb, 12px);
682
813
  }
814
+ /* Imagen A SANGRE: rompe el padding de la tarjeta y ocupa el ancho completo
815
+ (hero estilo anuncio). Como primer bloque, hereda el redondeo superior. */
816
+ .veo-guide-image--bleed {
817
+ width: calc(100% + var(--veo-pad, 24px) * 2);
818
+ max-width: none;
819
+ max-height: 280px;
820
+ align-self: auto;
821
+ border-radius: 0;
822
+ margin: 0 calc(var(--veo-pad, 24px) * -1) 12px;
823
+ }
824
+ .veo-guide-content > .veo-guide-image--bleed:first-child {
825
+ margin-top: calc(var(--veo-pad, 24px) * -1);
826
+ border-radius: calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px))
827
+ calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px)) 0 0;
828
+ }
683
829
  .veo-guide-title {
684
830
  font-size: var(--veo-title-size, 18px); font-weight: 600; line-height: 1.3;
685
831
  text-align: var(--veo-title-align, left);
@@ -692,6 +838,18 @@ var GUIDE_STYLES = `
692
838
  margin: var(--veo-text-mt, 0) 0 var(--veo-text-mb, 16px);
693
839
  color: var(--veo-text-color, var(--veo-text-secondary));
694
840
  }
841
+ /* Rich text dentro de un bloque de texto (p/listas/links/\xE9nfasis). */
842
+ .veo-guide-text p { margin: 0 0 8px; }
843
+ .veo-guide-text p:last-child { margin-bottom: 0; }
844
+ .veo-guide-text ul, .veo-guide-text ol { margin: 0 0 8px; padding-left: 20px; }
845
+ .veo-guide-text ul { list-style: disc; }
846
+ .veo-guide-text ol { list-style: decimal; }
847
+ .veo-guide-text li { margin: 2px 0; display: list-item; }
848
+ .veo-guide-text a { color: var(--veo-primary); text-decoration: underline; cursor: pointer; }
849
+ .veo-guide-text strong { font-weight: 600; }
850
+ .veo-guide-text em { font-style: italic; }
851
+ .veo-guide-text u { text-decoration: underline; }
852
+ .veo-guide-text s { text-decoration: line-through; }
695
853
  .veo-guide-actions {
696
854
  display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
697
855
  }
@@ -830,6 +988,44 @@ var GUIDE_STYLES = `
830
988
  }
831
989
  .veo-walkthrough-skip:hover { color: var(--veo-text); }
832
990
 
991
+ /* \u2500\u2500 Badge (elemento inyectado junto al ancla que abre un tooltip) \u2500\u2500 */
992
+ .veo-badge {
993
+ display: inline-flex; align-items: center; justify-content: center;
994
+ border: none; padding: 0; margin: 0 4px;
995
+ background: none; cursor: pointer;
996
+ line-height: 1; vertical-align: middle;
997
+ font-family: inherit;
998
+ }
999
+ .veo-badge:focus-visible { outline: 2px solid var(--veo-primary); outline-offset: 2px; }
1000
+ .veo-badge--icon {
1001
+ border-radius: 50%;
1002
+ background: color-mix(in srgb, var(--veo-primary) 14%, transparent);
1003
+ color: var(--veo-primary);
1004
+ font-weight: 600;
1005
+ }
1006
+ .veo-badge--dot {
1007
+ border-radius: 50%;
1008
+ background: var(--veo-primary);
1009
+ animation: veo-badge-pulse 2s ease-out infinite;
1010
+ }
1011
+ .veo-badge--pill {
1012
+ border-radius: 999px;
1013
+ background: var(--veo-primary);
1014
+ color: #fff;
1015
+ font-weight: 600;
1016
+ padding: 3px 9px;
1017
+ white-space: nowrap;
1018
+ }
1019
+ .veo-badge--image img { display: block; border-radius: 4px; object-fit: cover; }
1020
+ /* El tooltip del badge usa strategy fixed (el host vive dentro del flujo del
1021
+ cliente; un absoluto se recortar\xEDa con overflow de ancestros). */
1022
+ .veo-badge-tooltip { position: fixed; }
1023
+ @keyframes veo-badge-pulse {
1024
+ 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--veo-primary) 45%, transparent); }
1025
+ 70% { box-shadow: 0 0 0 7px transparent; }
1026
+ 100% { box-shadow: 0 0 0 0 transparent; }
1027
+ }
1028
+
833
1029
  .veo-custom-floating {
834
1030
  position: fixed;
835
1031
  z-index: ${GUIDE_Z_INDEX};
@@ -940,6 +1136,14 @@ var BaseRenderer = class {
940
1136
  registerCleanup(fn) {
941
1137
  this.cleanups.push(fn);
942
1138
  }
1139
+ /**
1140
+ * Host de la guía montada (o `null` si aún no se montó / ya se destruyó).
1141
+ * Lo usa el modo builder para adjuntar manipulación directa (drag/resize)
1142
+ * sobre el shadow root abierto sin tocar los renderers.
1143
+ */
1144
+ hostElement() {
1145
+ return this.host;
1146
+ }
943
1147
  /** Remueve el host del DOM y corre todas las funciones de cleanup. */
944
1148
  destroy() {
945
1149
  for (const fn of this.cleanups) {
@@ -957,20 +1161,372 @@ var BaseRenderer = class {
957
1161
  }
958
1162
  };
959
1163
 
1164
+ // src/plugins/guides/renderers/floating-arrow.ts
1165
+ var STATIC_SIDE = {
1166
+ top: "bottom",
1167
+ bottom: "top",
1168
+ left: "right",
1169
+ right: "left"
1170
+ };
1171
+ function positionArrow(arrowEl, placement, data) {
1172
+ const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1173
+ for (const prop of ["top", "bottom", "left", "right"]) {
1174
+ arrowEl.style.setProperty(prop, "");
1175
+ }
1176
+ if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1177
+ if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1178
+ arrowEl.style.setProperty(side, "-6px");
1179
+ }
1180
+
1181
+ // src/plugins/guides/renderers/badge-renderer.ts
1182
+ var HOVER_CLOSE_DELAY_MS = 150;
1183
+ var BadgeRenderer = class extends BaseRenderer {
1184
+ constructor() {
1185
+ super(...arguments);
1186
+ this.tooltip = null;
1187
+ this.arrowEl = null;
1188
+ this.badgeBtn = null;
1189
+ this.open = false;
1190
+ this.shownEmitted = false;
1191
+ this.trigger = "hover";
1192
+ this.side = "top";
1193
+ this.closeTimer = null;
1194
+ this.stopFloat = null;
1195
+ }
1196
+ async render(context) {
1197
+ const step = context.guide.guideSteps[0];
1198
+ if (!step) return;
1199
+ const selector = step.selector ?? context.guide.activationRules.selector;
1200
+ if (typeof selector !== "string" || selector.length === 0) return;
1201
+ const anchor = await waitForElement(selector);
1202
+ if (!anchor) return;
1203
+ const { host, root } = this.createHost();
1204
+ const ownerDocument = root.ownerDocument ?? document;
1205
+ this.applyDesign(step.style);
1206
+ this.trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
1207
+ const rawSide = step.style?.tooltipPlacement;
1208
+ this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
1209
+ const position = readInlinePosition(step.style);
1210
+ host.style.display = "inline-flex";
1211
+ host.style.verticalAlign = "middle";
1212
+ const badge = buildBadgeElement(readBadgeConfig(step), ownerDocument);
1213
+ root.appendChild(badge);
1214
+ this.badgeBtn = badge;
1215
+ insertHost(anchor, host, position);
1216
+ this.registerCleanup(keepHostAttached(host, selector, position));
1217
+ const tooltip = ownerDocument.createElement("div");
1218
+ tooltip.className = "veo-tooltip veo-badge-tooltip";
1219
+ tooltip.style.display = "none";
1220
+ const emit = (action, meta) => {
1221
+ context.onInteraction({
1222
+ guideId: context.guide.guideId,
1223
+ stepIndex: 0,
1224
+ action,
1225
+ ...meta ? { metadata: meta } : {}
1226
+ });
1227
+ };
1228
+ const buildTooltipContent = (s) => buildStepContent(s, ownerDocument, {
1229
+ onCtaClick: (action, url, meta) => {
1230
+ emit("cta_clicked", meta);
1231
+ if (action === "url" && url) window.open(url, "_blank", "noopener,noreferrer");
1232
+ if (action === "dismiss") {
1233
+ emit("dismissed");
1234
+ context.onClose();
1235
+ return;
1236
+ }
1237
+ this.closeTooltip();
1238
+ },
1239
+ onDismiss: () => {
1240
+ emit("dismissed");
1241
+ context.onClose();
1242
+ }
1243
+ });
1244
+ const content = buildTooltipContent(step);
1245
+ tooltip.appendChild(content);
1246
+ const arrowEl = ownerDocument.createElement("div");
1247
+ arrowEl.className = "veo-tooltip-arrow";
1248
+ tooltip.appendChild(arrowEl);
1249
+ root.appendChild(tooltip);
1250
+ this.tooltip = tooltip;
1251
+ this.arrowEl = arrowEl;
1252
+ this.liveContainer = tooltip;
1253
+ this.liveContent = content;
1254
+ this.liveKey = this.liveKeyOf(step);
1255
+ this.liveBuild = buildTooltipContent;
1256
+ const openNow = () => {
1257
+ this.cancelClose();
1258
+ if (!this.open) {
1259
+ this.openTooltip(badge);
1260
+ if (!this.shownEmitted) {
1261
+ this.shownEmitted = true;
1262
+ emit("shown");
1263
+ }
1264
+ }
1265
+ };
1266
+ if (this.trigger === "hover") {
1267
+ const scheduleClose = () => {
1268
+ this.cancelClose();
1269
+ this.closeTimer = window.setTimeout(() => this.closeTooltip(), HOVER_CLOSE_DELAY_MS);
1270
+ };
1271
+ badge.addEventListener("mouseenter", openNow);
1272
+ badge.addEventListener("focus", openNow);
1273
+ badge.addEventListener("mouseleave", scheduleClose);
1274
+ badge.addEventListener("blur", scheduleClose);
1275
+ tooltip.addEventListener("mouseenter", () => this.cancelClose());
1276
+ tooltip.addEventListener("mouseleave", scheduleClose);
1277
+ } else {
1278
+ badge.addEventListener("click", () => {
1279
+ if (this.open) this.closeTooltip();
1280
+ else openNow();
1281
+ });
1282
+ const onDocClick = (e) => {
1283
+ if (!this.open) return;
1284
+ if (e.composedPath().includes(host)) return;
1285
+ this.closeTooltip();
1286
+ };
1287
+ document.addEventListener("click", onDocClick, true);
1288
+ this.registerCleanup(() => document.removeEventListener("click", onDocClick, true));
1289
+ }
1290
+ const onKey = (e) => {
1291
+ if (e.key === "Escape" && this.open) this.closeTooltip();
1292
+ };
1293
+ document.addEventListener("keydown", onKey);
1294
+ this.registerCleanup(() => document.removeEventListener("keydown", onKey));
1295
+ if (context.isPreview) openNow();
1296
+ }
1297
+ /** Cambiar de ancla/posición/trigger requiere re-montar y re-cablear. */
1298
+ liveKeyOf(step) {
1299
+ const selector = step.selector ?? "";
1300
+ const position = readInlinePosition(step.style);
1301
+ const trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
1302
+ return `${selector}|${position}|${trigger}`;
1303
+ }
1304
+ onLiveUpdate(step) {
1305
+ if (this.badgeBtn) {
1306
+ const doc = this.badgeBtn.ownerDocument;
1307
+ const next = buildBadgeElement(readBadgeConfig(step), doc);
1308
+ this.badgeBtn.className = next.className;
1309
+ this.badgeBtn.setAttribute("style", next.getAttribute("style") ?? "");
1310
+ this.badgeBtn.replaceChildren(...Array.from(next.childNodes));
1311
+ }
1312
+ const rawSide = step.style?.tooltipPlacement;
1313
+ this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
1314
+ if (this.open && this.badgeBtn) this.position(this.badgeBtn);
1315
+ }
1316
+ destroy() {
1317
+ this.cancelClose();
1318
+ this.stopFloat?.();
1319
+ this.stopFloat = null;
1320
+ this.tooltip = null;
1321
+ this.arrowEl = null;
1322
+ this.badgeBtn = null;
1323
+ this.open = false;
1324
+ super.destroy();
1325
+ }
1326
+ openTooltip(badge) {
1327
+ if (!this.tooltip) return;
1328
+ this.tooltip.style.display = "block";
1329
+ this.open = true;
1330
+ void this.position(badge);
1331
+ const reposition = () => {
1332
+ void this.position(badge);
1333
+ };
1334
+ window.addEventListener("scroll", reposition, true);
1335
+ window.addEventListener("resize", reposition);
1336
+ this.stopFloat = () => {
1337
+ window.removeEventListener("scroll", reposition, true);
1338
+ window.removeEventListener("resize", reposition);
1339
+ };
1340
+ }
1341
+ closeTooltip() {
1342
+ this.cancelClose();
1343
+ if (!this.tooltip || !this.open) return;
1344
+ this.tooltip.style.display = "none";
1345
+ this.open = false;
1346
+ this.stopFloat?.();
1347
+ this.stopFloat = null;
1348
+ }
1349
+ cancelClose() {
1350
+ if (this.closeTimer !== null) {
1351
+ window.clearTimeout(this.closeTimer);
1352
+ this.closeTimer = null;
1353
+ }
1354
+ }
1355
+ async position(badge) {
1356
+ const tooltip = this.tooltip;
1357
+ const arrowEl = this.arrowEl;
1358
+ if (!tooltip || !arrowEl) return;
1359
+ const { x, y, placement, middlewareData } = await computePosition(badge, tooltip, {
1360
+ strategy: "fixed",
1361
+ placement: this.side,
1362
+ middleware: [offset(8), flip(), shift({ padding: 8 }), arrow({ element: arrowEl })]
1363
+ });
1364
+ tooltip.style.left = `${x}px`;
1365
+ tooltip.style.top = `${y}px`;
1366
+ positionArrow(arrowEl, placement, middlewareData.arrow);
1367
+ }
1368
+ };
1369
+ function readBadgeConfig(step) {
1370
+ const raw = step.style?.badge;
1371
+ if (!raw || typeof raw !== "object") return { kind: "icon" };
1372
+ const b = raw;
1373
+ const kind = b.kind === "dot" || b.kind === "pill" || b.kind === "image" || b.kind === "icon" ? b.kind : "icon";
1374
+ return {
1375
+ kind,
1376
+ icon: typeof b.icon === "string" ? b.icon : null,
1377
+ text: typeof b.text === "string" ? b.text : null,
1378
+ imageUrl: typeof b.imageUrl === "string" ? b.imageUrl : null,
1379
+ color: typeof b.color === "string" && COLOR_RE.test(b.color.trim()) ? b.color.trim() : null,
1380
+ size: typeof b.size === "number" && Number.isFinite(b.size) ? b.size : null
1381
+ };
1382
+ }
1383
+ function clamp2(n, min, max) {
1384
+ return Math.min(max, Math.max(min, n));
1385
+ }
1386
+ function buildBadgeElement(config, doc) {
1387
+ const btn = doc.createElement("button");
1388
+ btn.type = "button";
1389
+ btn.className = `veo-badge veo-badge--${config.kind}`;
1390
+ btn.setAttribute("aria-label", "M\xE1s informaci\xF3n");
1391
+ const size = clamp2(config.size ?? 18, 8, 48);
1392
+ switch (config.kind) {
1393
+ case "dot":
1394
+ btn.style.width = `${size}px`;
1395
+ btn.style.height = `${size}px`;
1396
+ if (config.color) btn.style.background = config.color;
1397
+ break;
1398
+ case "pill": {
1399
+ btn.textContent = config.text?.slice(0, 24) || "Nuevo";
1400
+ btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 24)}px`;
1401
+ if (config.color) btn.style.background = config.color;
1402
+ break;
1403
+ }
1404
+ case "image": {
1405
+ if (config.imageUrl && isSafeUrl(config.imageUrl)) {
1406
+ const img = doc.createElement("img");
1407
+ img.src = config.imageUrl;
1408
+ img.alt = "";
1409
+ img.style.width = `${size}px`;
1410
+ img.style.height = `${size}px`;
1411
+ btn.appendChild(img);
1412
+ } else {
1413
+ btn.className = "veo-badge veo-badge--icon";
1414
+ btn.textContent = "?";
1415
+ btn.style.width = `${size}px`;
1416
+ btn.style.height = `${size}px`;
1417
+ btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
1418
+ }
1419
+ break;
1420
+ }
1421
+ default: {
1422
+ btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
1423
+ btn.style.width = `${size}px`;
1424
+ btn.style.height = `${size}px`;
1425
+ btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
1426
+ if (config.color) btn.style.color = config.color;
1427
+ break;
1428
+ }
1429
+ }
1430
+ return btn;
1431
+ }
1432
+
1433
+ // src/plugins/guides/walkthrough-block-builder.ts
1434
+ function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
1435
+ const container = doc.createElement("div");
1436
+ container.className = "veo-guide-content";
1437
+ const counter = doc.createElement("div");
1438
+ counter.className = "veo-walkthrough-counter";
1439
+ counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
1440
+ container.appendChild(counter);
1441
+ const progress = doc.createElement("div");
1442
+ progress.className = "veo-walkthrough-progress";
1443
+ for (let i = 0; i < totalSteps; i++) {
1444
+ const dot = doc.createElement("span");
1445
+ dot.className = "veo-walkthrough-progress-dot";
1446
+ if (i < stepIndex) dot.classList.add("completed");
1447
+ if (i === stepIndex) dot.classList.add("active");
1448
+ progress.appendChild(dot);
1449
+ }
1450
+ container.appendChild(progress);
1451
+ if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
1452
+ const img = doc.createElement("img");
1453
+ img.className = "veo-guide-image";
1454
+ img.src = step.imageUrl;
1455
+ img.alt = typeof step.title === "string" ? step.title : "";
1456
+ container.appendChild(img);
1457
+ }
1458
+ if (typeof step.title === "string" && step.title) {
1459
+ const heading = doc.createElement("h2");
1460
+ heading.className = "veo-guide-title";
1461
+ heading.textContent = step.title;
1462
+ container.appendChild(heading);
1463
+ }
1464
+ if (typeof step.content === "string" && step.content) {
1465
+ const paragraph = doc.createElement("p");
1466
+ paragraph.className = "veo-guide-text";
1467
+ paragraph.textContent = step.content;
1468
+ container.appendChild(paragraph);
1469
+ }
1470
+ const actions = doc.createElement("div");
1471
+ actions.className = "veo-walkthrough-actions";
1472
+ const skipBtn = doc.createElement("button");
1473
+ skipBtn.type = "button";
1474
+ skipBtn.className = "veo-walkthrough-skip";
1475
+ skipBtn.textContent = "Omitir";
1476
+ skipBtn.addEventListener("click", () => callbacks.onSkip());
1477
+ actions.appendChild(skipBtn);
1478
+ const rightGroup = doc.createElement("div");
1479
+ rightGroup.className = "veo-walkthrough-actions-right";
1480
+ if (stepIndex > 0) {
1481
+ const backBtn = doc.createElement("button");
1482
+ backBtn.type = "button";
1483
+ backBtn.className = "veo-walkthrough-btn-secondary";
1484
+ backBtn.textContent = "Atr\xE1s";
1485
+ backBtn.addEventListener("click", () => callbacks.onBack());
1486
+ rightGroup.appendChild(backBtn);
1487
+ }
1488
+ const isLastStep = stepIndex === totalSteps - 1;
1489
+ const primaryBtn = doc.createElement("button");
1490
+ primaryBtn.type = "button";
1491
+ primaryBtn.className = "veo-guide-cta";
1492
+ const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
1493
+ primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
1494
+ primaryBtn.addEventListener("click", () => {
1495
+ if (isLastStep) callbacks.onComplete();
1496
+ else callbacks.onNext();
1497
+ });
1498
+ rightGroup.appendChild(primaryBtn);
1499
+ actions.appendChild(rightGroup);
1500
+ container.appendChild(actions);
1501
+ container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
1502
+ return container;
1503
+ }
1504
+
960
1505
  // src/plugins/guides/renderers/banner-renderer.ts
961
1506
  var BannerRenderer = class extends BaseRenderer {
962
- render(context) {
1507
+ async render(context) {
963
1508
  const idx = context.nav?.stepIndex ?? 0;
964
1509
  const step = context.guide.guideSteps[idx];
965
1510
  if (!step) return;
966
- const { root } = this.createHost();
1511
+ const position = step.style?.position === "bottom" ? "bottom" : "top";
1512
+ const selector = context.nav ? null : step.selector?.trim() ?? null;
1513
+ let anchor = null;
1514
+ if (selector) {
1515
+ anchor = await waitForElement(selector);
1516
+ if (!anchor) return;
1517
+ }
1518
+ const { host, root } = this.createHost();
967
1519
  this.applyDesign(step.style);
968
1520
  const ownerDocument = root.ownerDocument ?? document;
969
- const position = step.style?.position === "bottom" ? "bottom" : "top";
970
1521
  const banner = ownerDocument.createElement("div");
971
- banner.className = `veo-banner veo-banner-${position}`;
972
- const dismiss = (action, ctaUrl) => {
973
- context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
1522
+ banner.className = bannerClassOf(position, Boolean(anchor));
1523
+ const dismiss = (action, ctaUrl, meta) => {
1524
+ context.onInteraction({
1525
+ guideId: context.guide.guideId,
1526
+ stepIndex: idx,
1527
+ action,
1528
+ ...meta ? { metadata: meta } : {}
1529
+ });
974
1530
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
975
1531
  context.onClose();
976
1532
  };
@@ -981,59 +1537,45 @@ var BannerRenderer = class extends BaseRenderer {
981
1537
  ownerDocument,
982
1538
  context.nav.callbacks
983
1539
  ) : buildStepContent(step, ownerDocument, {
984
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1540
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
985
1541
  onDismiss: () => dismiss("dismissed")
986
1542
  });
987
1543
  banner.appendChild(content);
988
1544
  root.appendChild(banner);
1545
+ if (selector && anchor) {
1546
+ host.style.display = "block";
1547
+ const insertAt = position === "top" ? "prepend" : "append";
1548
+ insertHost(anchor, host, insertAt);
1549
+ this.registerCleanup(keepHostAttached(host, selector, insertAt));
1550
+ }
989
1551
  if (!context.nav) {
990
1552
  this.liveContainer = banner;
991
1553
  this.liveContent = content;
992
1554
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
993
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1555
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
994
1556
  onDismiss: () => dismiss("dismissed")
995
1557
  });
1558
+ this.liveKey = this.liveKeyOf(step);
996
1559
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
997
1560
  }
998
1561
  }
1562
+ /** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
1563
+ liveKeyOf(step) {
1564
+ const selector = step.selector?.trim() ?? "";
1565
+ if (!selector) return "";
1566
+ const position = step.style?.position === "bottom" ? "bottom" : "top";
1567
+ return `${selector}|${position}`;
1568
+ }
999
1569
  onLiveUpdate(step) {
1000
1570
  if (this.liveContainer) {
1001
1571
  const position = step.style?.position === "bottom" ? "bottom" : "top";
1002
- this.liveContainer.className = `veo-banner veo-banner-${position}`;
1572
+ const embedded = Boolean(step.selector?.trim());
1573
+ this.liveContainer.className = bannerClassOf(position, embedded);
1003
1574
  }
1004
1575
  }
1005
1576
  };
1006
-
1007
- // src/plugins/guides/wait-for-element.ts
1008
- function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
1009
- return new Promise((resolve) => {
1010
- const safeQuery = () => {
1011
- try {
1012
- return document.querySelector(selector);
1013
- } catch {
1014
- return null;
1015
- }
1016
- };
1017
- const existing = safeQuery();
1018
- if (existing) {
1019
- resolve(existing);
1020
- return;
1021
- }
1022
- let resolved = false;
1023
- const finish = (el) => {
1024
- if (resolved) return;
1025
- resolved = true;
1026
- observer.disconnect();
1027
- clearTimeout(timer);
1028
- resolve(el);
1029
- };
1030
- const observer = new MutationObserver(() => {
1031
- const el = safeQuery();
1032
- if (el) finish(el);
1033
- });
1034
- observer.observe(document.body, { childList: true, subtree: true });
1035
- const timer = setTimeout(() => finish(null), timeoutMs);
1036
- });
1577
+ function bannerClassOf(position, embedded) {
1578
+ return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
1037
1579
  }
1038
1580
 
1039
1581
  // src/utils/uuid.ts
@@ -1568,36 +2110,6 @@ var FormRenderer = class extends BaseRenderer {
1568
2110
  }
1569
2111
  };
1570
2112
 
1571
- // src/plugins/guides/inline-host.ts
1572
- function readInlinePosition(style) {
1573
- const p = style?.inlinePosition;
1574
- return p === "before" || p === "prepend" || p === "append" ? p : "after";
1575
- }
1576
- function insertHost(anchor, host, position) {
1577
- switch (position) {
1578
- case "before":
1579
- anchor.before(host);
1580
- break;
1581
- case "after":
1582
- anchor.after(host);
1583
- break;
1584
- case "prepend":
1585
- anchor.prepend(host);
1586
- break;
1587
- case "append":
1588
- anchor.append(host);
1589
- break;
1590
- }
1591
- }
1592
- function keepHostAttached(host, selector, position) {
1593
- const id = window.setInterval(() => {
1594
- if (host.isConnected) return;
1595
- const anchor = document.querySelector(selector);
1596
- if (anchor) insertHost(anchor, host, position);
1597
- }, 1e3);
1598
- return () => window.clearInterval(id);
1599
- }
1600
-
1601
2113
  // src/plugins/guides/renderers/inline-custom-renderer.ts
1602
2114
  var InlineCustomRenderer = class extends BaseRenderer {
1603
2115
  async render(context) {
@@ -1665,13 +2177,18 @@ var InlineRenderer = class extends BaseRenderer {
1665
2177
  const ownerDocument = root.ownerDocument ?? document;
1666
2178
  const card = ownerDocument.createElement("div");
1667
2179
  card.className = "veo-inline";
1668
- const dismiss = (action, ctaUrl) => {
1669
- context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action });
2180
+ const dismiss = (action, ctaUrl, meta) => {
2181
+ context.onInteraction({
2182
+ guideId: context.guide.guideId,
2183
+ stepIndex: 0,
2184
+ action,
2185
+ ...meta ? { metadata: meta } : {}
2186
+ });
1670
2187
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1671
2188
  context.onClose();
1672
2189
  };
1673
2190
  const content = buildStepContent(step, ownerDocument, {
1674
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2191
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1675
2192
  onDismiss: () => dismiss("dismissed")
1676
2193
  });
1677
2194
  card.appendChild(content);
@@ -1680,7 +2197,7 @@ var InlineRenderer = class extends BaseRenderer {
1680
2197
  this.liveContent = content;
1681
2198
  this.liveKey = `${selector}|${position}`;
1682
2199
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
1683
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2200
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1684
2201
  onDismiss: () => dismiss("dismissed")
1685
2202
  });
1686
2203
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
@@ -1691,7 +2208,14 @@ var InlineRenderer = class extends BaseRenderer {
1691
2208
  };
1692
2209
 
1693
2210
  // src/plugins/guides/renderers/modal-renderer.ts
2211
+ function overlayClassOf(step) {
2212
+ return step.style?.backdrop === false ? "veo-modal-overlay veo-modal-overlay--none" : "veo-modal-overlay";
2213
+ }
1694
2214
  var ModalRenderer = class extends BaseRenderer {
2215
+ constructor() {
2216
+ super(...arguments);
2217
+ this.overlay = null;
2218
+ }
1695
2219
  render(context) {
1696
2220
  const idx = context.nav?.stepIndex ?? 0;
1697
2221
  const step = context.guide.guideSteps[idx];
@@ -1700,11 +2224,17 @@ var ModalRenderer = class extends BaseRenderer {
1700
2224
  this.applyDesign(step.style);
1701
2225
  const ownerDocument = root.ownerDocument ?? document;
1702
2226
  const overlay = ownerDocument.createElement("div");
1703
- overlay.className = "veo-modal-overlay";
2227
+ overlay.className = overlayClassOf(step);
2228
+ this.overlay = overlay;
1704
2229
  const card = ownerDocument.createElement("div");
1705
2230
  card.className = "veo-modal-card";
1706
- const dismiss = (action, ctaUrl) => {
1707
- context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
2231
+ const dismiss = (action, ctaUrl, meta) => {
2232
+ context.onInteraction({
2233
+ guideId: context.guide.guideId,
2234
+ stepIndex: idx,
2235
+ action,
2236
+ ...meta ? { metadata: meta } : {}
2237
+ });
1708
2238
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1709
2239
  context.onClose();
1710
2240
  };
@@ -1719,7 +2249,7 @@ var ModalRenderer = class extends BaseRenderer {
1719
2249
  ownerDocument,
1720
2250
  context.nav.callbacks
1721
2251
  ) : buildStepContent(step, ownerDocument, {
1722
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2252
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1723
2253
  onDismiss: () => dismiss("dismissed")
1724
2254
  });
1725
2255
  card.appendChild(content);
@@ -1729,7 +2259,7 @@ var ModalRenderer = class extends BaseRenderer {
1729
2259
  this.liveContainer = card;
1730
2260
  this.liveContent = content;
1731
2261
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
1732
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2262
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1733
2263
  onDismiss: () => dismiss("dismissed")
1734
2264
  });
1735
2265
  }
@@ -1745,26 +2275,14 @@ var ModalRenderer = class extends BaseRenderer {
1745
2275
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1746
2276
  }
1747
2277
  }
1748
- };
1749
-
1750
- // src/plugins/guides/renderers/floating-arrow.ts
1751
- var STATIC_SIDE = {
1752
- top: "bottom",
1753
- bottom: "top",
1754
- left: "right",
1755
- right: "left"
1756
- };
1757
- function positionArrow(arrowEl, placement, data) {
1758
- const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1759
- for (const prop of ["top", "bottom", "left", "right"]) {
1760
- arrowEl.style.setProperty(prop, "");
2278
+ onLiveUpdate(step) {
2279
+ if (this.overlay) this.overlay.className = overlayClassOf(step);
1761
2280
  }
1762
- if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1763
- if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1764
- arrowEl.style.setProperty(side, "-6px");
1765
- }
1766
-
1767
- // src/plugins/guides/renderers/tooltip-renderer.ts
2281
+ destroy() {
2282
+ this.overlay = null;
2283
+ super.destroy();
2284
+ }
2285
+ };
1768
2286
  var TooltipRenderer = class extends BaseRenderer {
1769
2287
  constructor() {
1770
2288
  super(...arguments);
@@ -1786,18 +2304,19 @@ var TooltipRenderer = class extends BaseRenderer {
1786
2304
  const ownerDocument = root.ownerDocument ?? document;
1787
2305
  const tooltip = ownerDocument.createElement("div");
1788
2306
  tooltip.className = "veo-tooltip";
1789
- const dismiss = (action, ctaUrl) => {
2307
+ const dismiss = (action, ctaUrl, meta) => {
1790
2308
  context.onInteraction({
1791
2309
  guideId: context.guide.guideId,
1792
2310
  stepIndex: 0,
1793
- action
2311
+ action,
2312
+ ...meta ? { metadata: meta } : {}
1794
2313
  });
1795
2314
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1796
2315
  context.onClose();
1797
2316
  };
1798
2317
  const content = buildStepContent(step, ownerDocument, {
1799
- onCtaClick: (action, url) => {
1800
- dismiss("cta_clicked", action === "url" ? url : void 0);
2318
+ onCtaClick: (action, url, meta) => {
2319
+ dismiss("cta_clicked", action === "url" ? url : void 0, meta);
1801
2320
  },
1802
2321
  onDismiss: () => dismiss("dismissed")
1803
2322
  });
@@ -1830,7 +2349,7 @@ var TooltipRenderer = class extends BaseRenderer {
1830
2349
  this.liveContent = content;
1831
2350
  this.liveKey = selector;
1832
2351
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
1833
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2352
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1834
2353
  onDismiss: () => dismiss("dismissed")
1835
2354
  });
1836
2355
  context.onInteraction({
@@ -1914,14 +2433,26 @@ var GuidePreviewController = class {
1914
2433
  const nextStep = input.guideSteps[0];
1915
2434
  if (this.singleStep && this.input && input.guideType === this.input.guideType && input.guideType !== "walkthrough" && nextStep && this.singleStep.updateStep(nextStep)) {
1916
2435
  this.input = input;
1917
- return { close: () => this.close(), ready: Promise.resolve({ rendered: true }) };
2436
+ return {
2437
+ close: () => this.close(),
2438
+ ready: Promise.resolve({ rendered: true }),
2439
+ host: () => this.activeHost()
2440
+ };
1918
2441
  }
1919
2442
  this.close();
1920
2443
  this.input = input;
1921
2444
  const guide = normalize(input);
1922
2445
  const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
1923
2446
  const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
1924
- return { close: () => this.close(), ready };
2447
+ return { close: () => this.close(), ready, host: () => this.activeHost() };
2448
+ }
2449
+ /** Host de la guía montada (single-step o paso de walkthrough activo). */
2450
+ activeHost() {
2451
+ if (this.singleStep) return this.singleStep.hostElement();
2452
+ if (this.walkthrough instanceof BaseRenderer) {
2453
+ return this.walkthrough.hostElement();
2454
+ }
2455
+ return null;
1925
2456
  }
1926
2457
  close() {
1927
2458
  if (this.singleStep) {
@@ -2042,7 +2573,7 @@ var activeController = null;
2042
2573
  function previewGuide(input) {
2043
2574
  if (!hasDocument()) {
2044
2575
  return { close: () => {
2045
- }, ready: Promise.resolve({ rendered: false }) };
2576
+ }, ready: Promise.resolve({ rendered: false }), host: () => null };
2046
2577
  }
2047
2578
  if (!activeController) activeController = new GuidePreviewController();
2048
2579
  return activeController.preview(input);
@@ -2085,6 +2616,8 @@ function createSingleStepRenderer(type) {
2085
2616
  return new FormRenderer();
2086
2617
  case "inline-form":
2087
2618
  return new InlineFormRenderer();
2619
+ case "badge":
2620
+ return new BadgeRenderer();
2088
2621
  case "walkthrough":
2089
2622
  return null;
2090
2623
  }
@@ -2229,6 +2762,6 @@ function buildSelectorPath(element, maxAncestors = 5) {
2229
2762
  return parts.join(" > ");
2230
2763
  }
2231
2764
 
2232
- 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 };
2233
- //# sourceMappingURL=chunk-YHUPUTWH.mjs.map
2234
- //# sourceMappingURL=chunk-YHUPUTWH.mjs.map
2765
+ 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 };
2766
+ //# sourceMappingURL=chunk-F2ZWZUTR.mjs.map
2767
+ //# sourceMappingURL=chunk-F2ZWZUTR.mjs.map