veo-sdk 0.3.16 → 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};
@@ -940,6 +1132,14 @@ var BaseRenderer = class {
940
1132
  registerCleanup(fn) {
941
1133
  this.cleanups.push(fn);
942
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
+ }
943
1143
  /** Remueve el host del DOM y corre todas las funciones de cleanup. */
944
1144
  destroy() {
945
1145
  for (const fn of this.cleanups) {
@@ -957,20 +1157,372 @@ var BaseRenderer = class {
957
1157
  }
958
1158
  };
959
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
+
960
1501
  // src/plugins/guides/renderers/banner-renderer.ts
961
1502
  var BannerRenderer = class extends BaseRenderer {
962
- render(context) {
1503
+ async render(context) {
963
1504
  const idx = context.nav?.stepIndex ?? 0;
964
1505
  const step = context.guide.guideSteps[idx];
965
1506
  if (!step) return;
966
- 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();
967
1515
  this.applyDesign(step.style);
968
1516
  const ownerDocument = root.ownerDocument ?? document;
969
- const position = step.style?.position === "bottom" ? "bottom" : "top";
970
1517
  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 });
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
+ });
974
1526
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
975
1527
  context.onClose();
976
1528
  };
@@ -981,59 +1533,45 @@ var BannerRenderer = class extends BaseRenderer {
981
1533
  ownerDocument,
982
1534
  context.nav.callbacks
983
1535
  ) : buildStepContent(step, ownerDocument, {
984
- 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),
985
1537
  onDismiss: () => dismiss("dismissed")
986
1538
  });
987
1539
  banner.appendChild(content);
988
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
+ }
989
1547
  if (!context.nav) {
990
1548
  this.liveContainer = banner;
991
1549
  this.liveContent = content;
992
1550
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
993
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1551
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
994
1552
  onDismiss: () => dismiss("dismissed")
995
1553
  });
1554
+ this.liveKey = this.liveKeyOf(step);
996
1555
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
997
1556
  }
998
1557
  }
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
+ }
999
1565
  onLiveUpdate(step) {
1000
1566
  if (this.liveContainer) {
1001
1567
  const position = step.style?.position === "bottom" ? "bottom" : "top";
1002
- this.liveContainer.className = `veo-banner veo-banner-${position}`;
1568
+ const embedded = Boolean(step.selector?.trim());
1569
+ this.liveContainer.className = bannerClassOf(position, embedded);
1003
1570
  }
1004
1571
  }
1005
1572
  };
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
- });
1573
+ function bannerClassOf(position, embedded) {
1574
+ return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
1037
1575
  }
1038
1576
 
1039
1577
  // src/utils/uuid.ts
@@ -1568,36 +2106,6 @@ var FormRenderer = class extends BaseRenderer {
1568
2106
  }
1569
2107
  };
1570
2108
 
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
2109
  // src/plugins/guides/renderers/inline-custom-renderer.ts
1602
2110
  var InlineCustomRenderer = class extends BaseRenderer {
1603
2111
  async render(context) {
@@ -1665,13 +2173,18 @@ var InlineRenderer = class extends BaseRenderer {
1665
2173
  const ownerDocument = root.ownerDocument ?? document;
1666
2174
  const card = ownerDocument.createElement("div");
1667
2175
  card.className = "veo-inline";
1668
- const dismiss = (action, ctaUrl) => {
1669
- 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
+ });
1670
2183
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1671
2184
  context.onClose();
1672
2185
  };
1673
2186
  const content = buildStepContent(step, ownerDocument, {
1674
- 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),
1675
2188
  onDismiss: () => dismiss("dismissed")
1676
2189
  });
1677
2190
  card.appendChild(content);
@@ -1680,7 +2193,7 @@ var InlineRenderer = class extends BaseRenderer {
1680
2193
  this.liveContent = content;
1681
2194
  this.liveKey = `${selector}|${position}`;
1682
2195
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
1683
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2196
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1684
2197
  onDismiss: () => dismiss("dismissed")
1685
2198
  });
1686
2199
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
@@ -1691,7 +2204,14 @@ var InlineRenderer = class extends BaseRenderer {
1691
2204
  };
1692
2205
 
1693
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
+ }
1694
2210
  var ModalRenderer = class extends BaseRenderer {
2211
+ constructor() {
2212
+ super(...arguments);
2213
+ this.overlay = null;
2214
+ }
1695
2215
  render(context) {
1696
2216
  const idx = context.nav?.stepIndex ?? 0;
1697
2217
  const step = context.guide.guideSteps[idx];
@@ -1700,11 +2220,17 @@ var ModalRenderer = class extends BaseRenderer {
1700
2220
  this.applyDesign(step.style);
1701
2221
  const ownerDocument = root.ownerDocument ?? document;
1702
2222
  const overlay = ownerDocument.createElement("div");
1703
- overlay.className = "veo-modal-overlay";
2223
+ overlay.className = overlayClassOf(step);
2224
+ this.overlay = overlay;
1704
2225
  const card = ownerDocument.createElement("div");
1705
2226
  card.className = "veo-modal-card";
1706
- const dismiss = (action, ctaUrl) => {
1707
- 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
+ });
1708
2234
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1709
2235
  context.onClose();
1710
2236
  };
@@ -1719,7 +2245,7 @@ var ModalRenderer = class extends BaseRenderer {
1719
2245
  ownerDocument,
1720
2246
  context.nav.callbacks
1721
2247
  ) : buildStepContent(step, ownerDocument, {
1722
- 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),
1723
2249
  onDismiss: () => dismiss("dismissed")
1724
2250
  });
1725
2251
  card.appendChild(content);
@@ -1729,7 +2255,7 @@ var ModalRenderer = class extends BaseRenderer {
1729
2255
  this.liveContainer = card;
1730
2256
  this.liveContent = content;
1731
2257
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
1732
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2258
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1733
2259
  onDismiss: () => dismiss("dismissed")
1734
2260
  });
1735
2261
  }
@@ -1745,26 +2271,14 @@ var ModalRenderer = class extends BaseRenderer {
1745
2271
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1746
2272
  }
1747
2273
  }
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, "");
2274
+ onLiveUpdate(step) {
2275
+ if (this.overlay) this.overlay.className = overlayClassOf(step);
1761
2276
  }
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
2277
+ destroy() {
2278
+ this.overlay = null;
2279
+ super.destroy();
2280
+ }
2281
+ };
1768
2282
  var TooltipRenderer = class extends BaseRenderer {
1769
2283
  constructor() {
1770
2284
  super(...arguments);
@@ -1786,18 +2300,19 @@ var TooltipRenderer = class extends BaseRenderer {
1786
2300
  const ownerDocument = root.ownerDocument ?? document;
1787
2301
  const tooltip = ownerDocument.createElement("div");
1788
2302
  tooltip.className = "veo-tooltip";
1789
- const dismiss = (action, ctaUrl) => {
2303
+ const dismiss = (action, ctaUrl, meta) => {
1790
2304
  context.onInteraction({
1791
2305
  guideId: context.guide.guideId,
1792
2306
  stepIndex: 0,
1793
- action
2307
+ action,
2308
+ ...meta ? { metadata: meta } : {}
1794
2309
  });
1795
2310
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1796
2311
  context.onClose();
1797
2312
  };
1798
2313
  const content = buildStepContent(step, ownerDocument, {
1799
- onCtaClick: (action, url) => {
1800
- dismiss("cta_clicked", action === "url" ? url : void 0);
2314
+ onCtaClick: (action, url, meta) => {
2315
+ dismiss("cta_clicked", action === "url" ? url : void 0, meta);
1801
2316
  },
1802
2317
  onDismiss: () => dismiss("dismissed")
1803
2318
  });
@@ -1830,7 +2345,7 @@ var TooltipRenderer = class extends BaseRenderer {
1830
2345
  this.liveContent = content;
1831
2346
  this.liveKey = selector;
1832
2347
  this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
1833
- onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
2348
+ onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
1834
2349
  onDismiss: () => dismiss("dismissed")
1835
2350
  });
1836
2351
  context.onInteraction({
@@ -1914,14 +2429,26 @@ var GuidePreviewController = class {
1914
2429
  const nextStep = input.guideSteps[0];
1915
2430
  if (this.singleStep && this.input && input.guideType === this.input.guideType && input.guideType !== "walkthrough" && nextStep && this.singleStep.updateStep(nextStep)) {
1916
2431
  this.input = input;
1917
- return { close: () => this.close(), ready: Promise.resolve({ rendered: true }) };
2432
+ return {
2433
+ close: () => this.close(),
2434
+ ready: Promise.resolve({ rendered: true }),
2435
+ host: () => this.activeHost()
2436
+ };
1918
2437
  }
1919
2438
  this.close();
1920
2439
  this.input = input;
1921
2440
  const guide = normalize(input);
1922
2441
  const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
1923
2442
  const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
1924
- 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;
1925
2452
  }
1926
2453
  close() {
1927
2454
  if (this.singleStep) {
@@ -2042,7 +2569,7 @@ var activeController = null;
2042
2569
  function previewGuide(input) {
2043
2570
  if (!hasDocument()) {
2044
2571
  return { close: () => {
2045
- }, ready: Promise.resolve({ rendered: false }) };
2572
+ }, ready: Promise.resolve({ rendered: false }), host: () => null };
2046
2573
  }
2047
2574
  if (!activeController) activeController = new GuidePreviewController();
2048
2575
  return activeController.preview(input);
@@ -2085,6 +2612,8 @@ function createSingleStepRenderer(type) {
2085
2612
  return new FormRenderer();
2086
2613
  case "inline-form":
2087
2614
  return new InlineFormRenderer();
2615
+ case "badge":
2616
+ return new BadgeRenderer();
2088
2617
  case "walkthrough":
2089
2618
  return null;
2090
2619
  }
@@ -2229,6 +2758,6 @@ function buildSelectorPath(element, maxAncestors = 5) {
2229
2758
  return parts.join(" > ");
2230
2759
  }
2231
2760
 
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
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