veo-sdk 0.3.5 → 0.3.7

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.
@@ -25,6 +25,8 @@ function hasNavigatorSendBeacon() {
25
25
 
26
26
  // src/core/builder-token.ts
27
27
  var CTX_KEY = "veo:builder_ctx";
28
+ var LS_PREFIX = "veo:builder_ctx:";
29
+ var PERSIST_TTL_MS = 12 * 60 * 60 * 1e3;
28
30
  function session() {
29
31
  try {
30
32
  return window.sessionStorage;
@@ -32,6 +34,59 @@ function session() {
32
34
  return null;
33
35
  }
34
36
  }
37
+ function local() {
38
+ try {
39
+ return window.localStorage;
40
+ } catch {
41
+ return null;
42
+ }
43
+ }
44
+ function lsKey(guide) {
45
+ return LS_PREFIX + (guide || "default");
46
+ }
47
+ function persist(ctx) {
48
+ try {
49
+ session()?.setItem(CTX_KEY, JSON.stringify(ctx));
50
+ } catch {
51
+ }
52
+ try {
53
+ const stored = { ...ctx, savedAt: Date.now() };
54
+ local()?.setItem(lsKey(ctx.guide), JSON.stringify(stored));
55
+ } catch {
56
+ }
57
+ }
58
+ function readFreshFromLocal() {
59
+ const store = local();
60
+ if (!store) return null;
61
+ let best = null;
62
+ try {
63
+ for (let i = 0; i < store.length; i++) {
64
+ const key = store.key(i);
65
+ if (!key?.startsWith(LS_PREFIX)) continue;
66
+ const raw = store.getItem(key);
67
+ if (!raw) continue;
68
+ let parsed = null;
69
+ try {
70
+ parsed = JSON.parse(raw);
71
+ } catch {
72
+ continue;
73
+ }
74
+ if (typeof parsed?.savedAt !== "number" || Date.now() - parsed.savedAt > PERSIST_TTL_MS) {
75
+ try {
76
+ store.removeItem(key);
77
+ } catch {
78
+ }
79
+ continue;
80
+ }
81
+ if (!best || parsed.savedAt > best.savedAt) best = parsed;
82
+ }
83
+ } catch {
84
+ return null;
85
+ }
86
+ if (!best) return null;
87
+ const { savedAt: _savedAt, ...ctx } = best;
88
+ return ctx;
89
+ }
35
90
  function urlParams() {
36
91
  try {
37
92
  return new URLSearchParams(window.location.search);
@@ -51,18 +106,22 @@ function resolveBuilderContext() {
51
106
  guide: params?.get(BUILDER_GUIDE_PARAM) ?? null,
52
107
  panelPath: params?.get(BUILDER_PANEL_PATH_PARAM) ?? null
53
108
  };
54
- try {
55
- session()?.setItem(CTX_KEY, JSON.stringify(ctx));
56
- } catch {
57
- }
109
+ persist(ctx);
58
110
  return ctx;
59
111
  }
60
112
  try {
61
113
  const raw = session()?.getItem(CTX_KEY);
62
- return raw ? JSON.parse(raw) : null;
114
+ if (raw) return JSON.parse(raw);
63
115
  } catch {
64
- return null;
65
116
  }
117
+ const fromLocal = readFreshFromLocal();
118
+ if (fromLocal) {
119
+ try {
120
+ session()?.setItem(CTX_KEY, JSON.stringify(fromLocal));
121
+ } catch {
122
+ }
123
+ }
124
+ return fromLocal;
66
125
  }
67
126
  function resolveBuilderToken() {
68
127
  return resolveBuilderContext()?.token ?? null;
@@ -72,10 +131,20 @@ function isBuilderMode() {
72
131
  }
73
132
  function clearBuilderToken() {
74
133
  if (!hasWindow()) return;
134
+ let guide = null;
135
+ try {
136
+ const raw = session()?.getItem(CTX_KEY);
137
+ if (raw) guide = JSON.parse(raw).guide ?? null;
138
+ } catch {
139
+ }
75
140
  try {
76
141
  session()?.removeItem(CTX_KEY);
77
142
  } catch {
78
143
  }
144
+ try {
145
+ local()?.removeItem(lsKey(guide));
146
+ } catch {
147
+ }
79
148
  }
80
149
 
81
150
  // src/plugins/guides/constants.ts
@@ -97,6 +166,16 @@ var WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS = 5e3;
97
166
  function buildStepContent(step, doc, callbacks) {
98
167
  const container = doc.createElement("div");
99
168
  container.className = "veo-guide-content";
169
+ const blocks = normalizeBlocks(step);
170
+ if (blocks) {
171
+ renderBlocks(container, blocks, doc, callbacks);
172
+ } else {
173
+ renderLegacyStep(container, step, doc, callbacks);
174
+ }
175
+ container.appendChild(createCloseButton(doc, () => callbacks.onDismiss()));
176
+ return container;
177
+ }
178
+ function renderLegacyStep(container, step, doc, callbacks) {
100
179
  if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
101
180
  const img = doc.createElement("img");
102
181
  img.className = "veo-guide-image";
@@ -131,14 +210,119 @@ function buildStepContent(step, doc, callbacks) {
131
210
  actions.appendChild(cta);
132
211
  }
133
212
  container.appendChild(actions);
213
+ }
214
+ function normalizeBlocks(step) {
215
+ if (!Array.isArray(step.blocks) || step.blocks.length === 0) return null;
216
+ const valid = step.blocks.filter((b) => {
217
+ if (!b || typeof b !== "object") return false;
218
+ if (b.type === "image") return typeof b.url === "string" && isSafeUrl(b.url);
219
+ return b.type === "title" || b.type === "text" || b.type === "button";
220
+ });
221
+ return valid.length > 0 ? valid : null;
222
+ }
223
+ function renderBlocks(container, blocks, doc, callbacks) {
224
+ let i = 0;
225
+ while (i < blocks.length) {
226
+ const block = blocks[i];
227
+ if (block?.type === "button") {
228
+ const actions = doc.createElement("div");
229
+ actions.className = "veo-guide-actions";
230
+ while (i < blocks.length && blocks[i]?.type === "button") {
231
+ const btnBlock = blocks[i];
232
+ actions.appendChild(buildButtonBlock(btnBlock, doc, callbacks));
233
+ i++;
234
+ }
235
+ container.appendChild(actions);
236
+ continue;
237
+ }
238
+ const node = buildContentBlock(block, doc);
239
+ if (node) container.appendChild(node);
240
+ i++;
241
+ }
242
+ }
243
+ function buildContentBlock(block, doc) {
244
+ switch (block.type) {
245
+ case "title": {
246
+ const el = doc.createElement("h2");
247
+ el.className = "veo-guide-title";
248
+ el.textContent = typeof block.text === "string" ? block.text : "";
249
+ applyBlockStyle(el, block.style, "text");
250
+ return el;
251
+ }
252
+ case "text": {
253
+ const el = doc.createElement("p");
254
+ el.className = "veo-guide-text";
255
+ el.textContent = typeof block.text === "string" ? block.text : "";
256
+ applyBlockStyle(el, block.style, "text");
257
+ return el;
258
+ }
259
+ case "image": {
260
+ if (typeof block.url !== "string" || !isSafeUrl(block.url)) return null;
261
+ const el = doc.createElement("img");
262
+ el.className = "veo-guide-image";
263
+ el.src = block.url;
264
+ el.alt = "";
265
+ applyBlockStyle(el, block.style, "image");
266
+ return el;
267
+ }
268
+ default:
269
+ return null;
270
+ }
271
+ }
272
+ function buildButtonBlock(block, doc, callbacks) {
273
+ const btn = doc.createElement("button");
274
+ btn.type = "button";
275
+ btn.className = "veo-guide-cta";
276
+ btn.textContent = typeof block.text === "string" && block.text ? block.text : "OK";
277
+ applyBlockStyle(btn, block.style, "button");
278
+ btn.addEventListener("click", () => {
279
+ const action = block.action ?? "dismiss";
280
+ const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
281
+ callbacks.onCtaClick(action, url);
282
+ });
283
+ return btn;
284
+ }
285
+ var BLOCK_ALIGN_SELF = {
286
+ left: "flex-start",
287
+ center: "center",
288
+ right: "flex-end"
289
+ };
290
+ function clampNum(n, min, max) {
291
+ return Math.min(max, Math.max(min, n));
292
+ }
293
+ function applyBlockStyle(el, style, kind) {
294
+ if (!style || typeof style !== "object") return;
295
+ const s = style;
296
+ const align = typeof s.align === "string" ? s.align : null;
297
+ if (align && (align === "left" || align === "center" || align === "right")) {
298
+ if (kind === "text") el.style.textAlign = align;
299
+ else el.style.alignSelf = BLOCK_ALIGN_SELF[align];
300
+ }
301
+ if (kind !== "image" && typeof s.color === "string") el.style.color = s.color;
302
+ if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
303
+ el.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
304
+ }
305
+ if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
306
+ el.style.width = `${clampNum(s.width, 10, 100)}%`;
307
+ }
308
+ if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
309
+ el.style.borderRadius = `${clampNum(s.radius, 0, 48)}px`;
310
+ }
311
+ if (typeof s.marginTop === "number" && Number.isFinite(s.marginTop)) {
312
+ el.style.marginTop = `${clampNum(s.marginTop, 0, 64)}px`;
313
+ }
314
+ if (typeof s.marginBottom === "number" && Number.isFinite(s.marginBottom)) {
315
+ el.style.marginBottom = `${clampNum(s.marginBottom, 0, 64)}px`;
316
+ }
317
+ }
318
+ function createCloseButton(doc, onClose, className = "veo-guide-close") {
134
319
  const closeBtn = doc.createElement("button");
135
320
  closeBtn.type = "button";
136
- closeBtn.className = "veo-guide-close";
321
+ closeBtn.className = className;
137
322
  closeBtn.setAttribute("aria-label", "Cerrar");
138
323
  closeBtn.textContent = "\xD7";
139
- closeBtn.addEventListener("click", () => callbacks.onDismiss());
140
- container.appendChild(closeBtn);
141
- return container;
324
+ closeBtn.addEventListener("click", onClose);
325
+ return closeBtn;
142
326
  }
143
327
  function isSafeUrl(url) {
144
328
  if (typeof url !== "string" || url.length === 0) return false;
@@ -156,6 +340,78 @@ function readCtaUrl(step) {
156
340
  return isSafeUrl(candidate) ? candidate : void 0;
157
341
  }
158
342
 
343
+ // src/plugins/guides/walkthrough-block-builder.ts
344
+ function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
345
+ const container = doc.createElement("div");
346
+ container.className = "veo-guide-content";
347
+ const counter = doc.createElement("div");
348
+ counter.className = "veo-walkthrough-counter";
349
+ counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
350
+ container.appendChild(counter);
351
+ const progress = doc.createElement("div");
352
+ progress.className = "veo-walkthrough-progress";
353
+ for (let i = 0; i < totalSteps; i++) {
354
+ const dot = doc.createElement("span");
355
+ dot.className = "veo-walkthrough-progress-dot";
356
+ if (i < stepIndex) dot.classList.add("completed");
357
+ if (i === stepIndex) dot.classList.add("active");
358
+ progress.appendChild(dot);
359
+ }
360
+ container.appendChild(progress);
361
+ if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
362
+ const img = doc.createElement("img");
363
+ img.className = "veo-guide-image";
364
+ img.src = step.imageUrl;
365
+ img.alt = typeof step.title === "string" ? step.title : "";
366
+ container.appendChild(img);
367
+ }
368
+ if (typeof step.title === "string" && step.title) {
369
+ const heading = doc.createElement("h2");
370
+ heading.className = "veo-guide-title";
371
+ heading.textContent = step.title;
372
+ container.appendChild(heading);
373
+ }
374
+ if (typeof step.content === "string" && step.content) {
375
+ const paragraph = doc.createElement("p");
376
+ paragraph.className = "veo-guide-text";
377
+ paragraph.textContent = step.content;
378
+ container.appendChild(paragraph);
379
+ }
380
+ const actions = doc.createElement("div");
381
+ actions.className = "veo-walkthrough-actions";
382
+ const skipBtn = doc.createElement("button");
383
+ skipBtn.type = "button";
384
+ skipBtn.className = "veo-walkthrough-skip";
385
+ skipBtn.textContent = "Omitir";
386
+ skipBtn.addEventListener("click", () => callbacks.onSkip());
387
+ actions.appendChild(skipBtn);
388
+ const rightGroup = doc.createElement("div");
389
+ rightGroup.className = "veo-walkthrough-actions-right";
390
+ if (stepIndex > 0) {
391
+ const backBtn = doc.createElement("button");
392
+ backBtn.type = "button";
393
+ backBtn.className = "veo-walkthrough-btn-secondary";
394
+ backBtn.textContent = "Atr\xE1s";
395
+ backBtn.addEventListener("click", () => callbacks.onBack());
396
+ rightGroup.appendChild(backBtn);
397
+ }
398
+ const isLastStep = stepIndex === totalSteps - 1;
399
+ const primaryBtn = doc.createElement("button");
400
+ primaryBtn.type = "button";
401
+ primaryBtn.className = "veo-guide-cta";
402
+ const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
403
+ primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
404
+ primaryBtn.addEventListener("click", () => {
405
+ if (isLastStep) callbacks.onComplete();
406
+ else callbacks.onNext();
407
+ });
408
+ rightGroup.appendChild(primaryBtn);
409
+ actions.appendChild(rightGroup);
410
+ container.appendChild(actions);
411
+ container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
412
+ return container;
413
+ }
414
+
159
415
  // src/plugins/guides/guide-design.ts
160
416
  var DARK_THEME = {
161
417
  "--veo-bg": "#1f2937",
@@ -168,6 +424,16 @@ var ALIGN_JUSTIFY = {
168
424
  center: "center",
169
425
  right: "flex-end"
170
426
  };
427
+ var ALIGN_SELF = {
428
+ left: "flex-start",
429
+ center: "center",
430
+ right: "flex-end"
431
+ };
432
+ var ELEMENT_ALIGN_MODE = {
433
+ title: "text",
434
+ text: "text",
435
+ image: "self"
436
+ };
171
437
  var SHADOW = {
172
438
  none: "none",
173
439
  soft: "0 4px 14px rgba(0, 0, 0, 0.10)",
@@ -195,6 +461,9 @@ function applyDesignVars(host, style) {
195
461
  if (typeof s.accentColor === "string" && COLOR_RE.test(s.accentColor.trim())) {
196
462
  host.style.setProperty("--veo-primary", s.accentColor.trim());
197
463
  }
464
+ if (typeof s.fontFamily === "string" && s.fontFamily.length <= 200 && !/[<>{}]/.test(s.fontFamily)) {
465
+ host.style.setProperty("--veo-font", s.fontFamily.trim());
466
+ }
198
467
  if (s.theme === "dark") {
199
468
  for (const [key, value] of Object.entries(DARK_THEME)) {
200
469
  host.style.setProperty(key, value);
@@ -236,6 +505,43 @@ function applyDesignVars(host, style) {
236
505
  host.style.setProperty("--veo-pos-x", `${px * 100}%`);
237
506
  host.style.setProperty("--veo-pos-y", `${py * 100}%`);
238
507
  }
508
+ if (s.elements && typeof s.elements === "object") {
509
+ const el = s.elements;
510
+ applyElementVars(host, "title", el.title);
511
+ applyElementVars(host, "text", el.text);
512
+ applyElementVars(host, "image", el.image);
513
+ const cta = el.cta;
514
+ if (cta && typeof cta.align === "string" && ALIGN_JUSTIFY[cta.align]) {
515
+ host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[cta.align]);
516
+ }
517
+ }
518
+ }
519
+ function applyElementVars(host, prefix, raw) {
520
+ if (!raw || typeof raw !== "object") return;
521
+ const el = raw;
522
+ if (typeof el.align === "string" && (el.align === "left" || el.align === "center" || el.align === "right")) {
523
+ const mode = ELEMENT_ALIGN_MODE[prefix] ?? "text";
524
+ const value = mode === "self" ? ALIGN_SELF[el.align] : el.align;
525
+ host.style.setProperty(`--veo-${prefix}-align`, value);
526
+ }
527
+ if (typeof el.color === "string" && COLOR_RE.test(el.color.trim())) {
528
+ host.style.setProperty(`--veo-${prefix}-color`, el.color.trim());
529
+ }
530
+ if (typeof el.fontSize === "number" && Number.isFinite(el.fontSize)) {
531
+ host.style.setProperty(`--veo-${prefix}-size`, `${clamp(el.fontSize, 8, 72)}px`);
532
+ }
533
+ if (typeof el.width === "number" && Number.isFinite(el.width)) {
534
+ host.style.setProperty(`--veo-${prefix}-width`, `${clamp(el.width, 10, 100)}%`);
535
+ }
536
+ if (typeof el.radius === "number" && Number.isFinite(el.radius)) {
537
+ host.style.setProperty(`--veo-${prefix}-radius`, `${clamp(el.radius, 0, 48)}px`);
538
+ }
539
+ if (typeof el.marginTop === "number" && Number.isFinite(el.marginTop)) {
540
+ host.style.setProperty(`--veo-${prefix}-mt`, `${clamp(el.marginTop, 0, 64)}px`);
541
+ }
542
+ if (typeof el.marginBottom === "number" && Number.isFinite(el.marginBottom)) {
543
+ host.style.setProperty(`--veo-${prefix}-mb`, `${clamp(el.marginBottom, 0, 64)}px`);
544
+ }
239
545
  }
240
546
 
241
547
  // src/plugins/guides/styles.ts
@@ -251,7 +557,7 @@ var GUIDE_STYLES = `
251
557
  --veo-actions-justify: flex-end;
252
558
  all: initial;
253
559
  }
254
- * { box-sizing: border-box; font-family: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif; }
560
+ * { box-sizing: border-box; font-family: var(--veo-font, -apple-system, system-ui, "Segoe UI", Roboto, sans-serif); }
255
561
 
256
562
  .veo-modal-overlay {
257
563
  position: fixed; inset: 0;
@@ -302,6 +608,15 @@ var GUIDE_STYLES = `
302
608
  box-shadow: var(--veo-shadow, 0 8px 30px rgba(0, 0, 0, 0.18));
303
609
  animation: veo-fade-in 150ms ease-out;
304
610
  }
611
+ .veo-tooltip-arrow {
612
+ position: absolute;
613
+ width: 12px;
614
+ height: 12px;
615
+ background: var(--veo-bg);
616
+ border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
617
+ transform: rotate(45deg);
618
+ pointer-events: none;
619
+ }
305
620
 
306
621
  .veo-inline {
307
622
  background: var(--veo-bg);
@@ -320,17 +635,25 @@ var GUIDE_STYLES = `
320
635
  display: flex; flex-direction: column;
321
636
  }
322
637
  .veo-guide-image {
323
- display: block; width: 100%; max-height: 180px;
324
- object-fit: cover; border-radius: 8px;
325
- margin-bottom: 12px;
638
+ display: block;
639
+ width: var(--veo-image-width, 100%);
640
+ max-height: 180px;
641
+ object-fit: cover;
642
+ align-self: var(--veo-image-align, stretch);
643
+ border-radius: var(--veo-image-radius, 8px);
644
+ margin: var(--veo-image-mt, 0) 0 var(--veo-image-mb, 12px);
326
645
  }
327
646
  .veo-guide-title {
328
- font-size: 18px; font-weight: 600; line-height: 1.3;
329
- margin: 0 0 6px; color: var(--veo-text);
647
+ font-size: var(--veo-title-size, 18px); font-weight: 600; line-height: 1.3;
648
+ text-align: var(--veo-title-align, left);
649
+ margin: var(--veo-title-mt, 0) 0 var(--veo-title-mb, 6px);
650
+ color: var(--veo-title-color, var(--veo-text));
330
651
  }
331
652
  .veo-guide-text {
332
- font-size: 14px; line-height: 1.5;
333
- margin: 0 0 16px; color: var(--veo-text-secondary);
653
+ font-size: var(--veo-text-size, 14px); line-height: 1.5;
654
+ text-align: var(--veo-text-align, left);
655
+ margin: var(--veo-text-mt, 0) 0 var(--veo-text-mb, 16px);
656
+ color: var(--veo-text-color, var(--veo-text-secondary));
334
657
  }
335
658
  .veo-guide-actions {
336
659
  display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
@@ -467,6 +790,7 @@ var GUIDE_STYLES = `
467
790
  }
468
791
  .veo-custom-inline {
469
792
  display: block;
793
+ position: relative;
470
794
  margin: 12px 0;
471
795
  }
472
796
  .veo-custom-frame {
@@ -476,6 +800,21 @@ var GUIDE_STYLES = `
476
800
  background: transparent;
477
801
  color-scheme: normal;
478
802
  }
803
+ /*
804
+ * X de cerrar de las gu\xEDas custom: el HTML del cliente vive en un iframe
805
+ * sandbox, as\xED que el bot\xF3n lo pone el host POR ENCIMA del iframe. Con fondo
806
+ * propio para ser visible sobre cualquier contenido.
807
+ */
808
+ .veo-custom-close {
809
+ position: absolute; top: 6px; right: 6px; z-index: 1;
810
+ width: 22px; height: 22px; padding: 0;
811
+ display: flex; align-items: center; justify-content: center;
812
+ font-size: 16px; line-height: 1; color: #6b7280;
813
+ background: rgba(255, 255, 255, 0.92);
814
+ border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 50%;
815
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14); cursor: pointer;
816
+ }
817
+ .veo-custom-close:hover { color: #111827; }
479
818
 
480
819
  @keyframes veo-fade-in { from { opacity: 0; } to { opacity: 1; } }
481
820
  @keyframes veo-slide-up {
@@ -549,7 +888,8 @@ var BaseRenderer = class {
549
888
  // src/plugins/guides/renderers/banner-renderer.ts
550
889
  var BannerRenderer = class extends BaseRenderer {
551
890
  render(context) {
552
- const step = context.guide.guideSteps[0];
891
+ const idx = context.nav?.stepIndex ?? 0;
892
+ const step = context.guide.guideSteps[idx];
553
893
  if (!step) return;
554
894
  const { root } = this.createHost();
555
895
  this.applyDesign(step.style);
@@ -558,27 +898,25 @@ var BannerRenderer = class extends BaseRenderer {
558
898
  const banner = ownerDocument.createElement("div");
559
899
  banner.className = `veo-banner veo-banner-${position}`;
560
900
  const dismiss = (action, ctaUrl) => {
561
- context.onInteraction({
562
- guideId: context.guide.guideId,
563
- stepIndex: 0,
564
- action
565
- });
901
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
566
902
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
567
903
  context.onClose();
568
904
  };
569
- const content = buildStepContent(step, ownerDocument, {
570
- onCtaClick: (action, url) => {
571
- dismiss("cta_clicked", action === "url" ? url : void 0);
572
- },
905
+ const content = context.nav ? buildWalkthroughStepContent(
906
+ step,
907
+ context.nav.stepIndex,
908
+ context.nav.totalSteps,
909
+ ownerDocument,
910
+ context.nav.callbacks
911
+ ) : buildStepContent(step, ownerDocument, {
912
+ onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
573
913
  onDismiss: () => dismiss("dismissed")
574
914
  });
575
915
  banner.appendChild(content);
576
916
  root.appendChild(banner);
577
- context.onInteraction({
578
- guideId: context.guide.guideId,
579
- stepIndex: 0,
580
- action: "shown"
581
- });
917
+ if (!context.nav) {
918
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
919
+ }
582
920
  }
583
921
  };
584
922
 
@@ -648,7 +986,11 @@ function mountCustomFrame(doc, step, context, opts) {
648
986
  const hasFixedHeight = opts.fixedHeight !== void 0 && opts.fixedHeight !== null;
649
987
  if (hasFixedHeight) iframe.style.height = toCssSize(opts.fixedHeight);
650
988
  const token = uuidv7();
651
- iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token);
989
+ const wantsTailwind = step.style?.tailwind === true;
990
+ iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token, {
991
+ tokens: collectHostTokens(),
992
+ tailwind: wantsTailwind
993
+ });
652
994
  const close = () => context.onClose();
653
995
  const onMessage = (event) => {
654
996
  if (event.source !== iframe.contentWindow) return;
@@ -697,12 +1039,37 @@ function mountCustomFrame(doc, step, context, opts) {
697
1039
  function toCssSize(value) {
698
1040
  return typeof value === "number" ? `${value}px` : value;
699
1041
  }
1042
+ function collectHostTokens() {
1043
+ if (typeof window === "undefined" || typeof document === "undefined") return "";
1044
+ try {
1045
+ const rootStyle = getComputedStyle(document.documentElement);
1046
+ const decls = [];
1047
+ const seen = /* @__PURE__ */ new Set();
1048
+ for (let i = 0; i < rootStyle.length && decls.length < 400; i++) {
1049
+ const name = rootStyle.item(i);
1050
+ if (!name.startsWith("--") || seen.has(name)) continue;
1051
+ seen.add(name);
1052
+ const value = rootStyle.getPropertyValue(name).trim();
1053
+ if (!value || value.length > 200 || /[<>{}]/.test(value)) continue;
1054
+ decls.push(`${name}:${value}`);
1055
+ }
1056
+ const bodyFont = getComputedStyle(document.body).fontFamily;
1057
+ if (bodyFont && bodyFont.length <= 200 && !/[<>{}]/.test(bodyFont)) {
1058
+ decls.push(`--veo-host-font:${bodyFont}`);
1059
+ }
1060
+ return decls.length ? `:root{${decls.join(";")}}` : "";
1061
+ } catch {
1062
+ return "";
1063
+ }
1064
+ }
700
1065
  function isRecord(value) {
701
1066
  return typeof value === "object" && value !== null && !Array.isArray(value);
702
1067
  }
703
- function buildSrcdoc(html, css, js, token) {
1068
+ function buildSrcdoc(html, css, js, token, opts) {
704
1069
  const bridge = `(function(){var T=${JSON.stringify(token)};function P(m){m.__veo=T;parent.postMessage(m,'*');}window.veo={dismiss:function(){P({type:'dismiss'});},cta:function(u){P({type:'cta',url:u});},track:function(n,p){P({type:'track',name:n,props:p||{}});},navigate:function(u){P({type:'navigate',url:u});}};document.addEventListener('click',function(e){var a=e.target&&e.target.closest?e.target.closest('a[href]'):null;if(!a)return;var h=a.getAttribute('href');if(!h||h.charAt(0)==='#'||/^(javascript|mailto|tel):/i.test(h))return;e.preventDefault();if(a.target==='_blank'){P({type:'cta',url:a.href,close:false});}else{P({type:'navigate',url:h});}},true);function H(){P({type:'resize',height:Math.ceil(document.documentElement.scrollHeight)});}window.addEventListener('load',H);try{if(typeof ResizeObserver!=='undefined'){new ResizeObserver(H).observe(document.documentElement);}}catch(e){}})();`;
705
- return `<!DOCTYPE html><html><head><meta charset="utf-8"><style>html,body{margin:0;padding:0;background:transparent;}</style><style>${escapeClosing(css, "style")}</style></head><body>${html}<script>${bridge}</script>` + (js ? `<script>${escapeClosing(js, "script")}</script>` : "") + "</body></html>";
1070
+ const tokensStyle = opts.tokens ? `<style>${escapeClosing(opts.tokens, "style")}</style>` : "";
1071
+ const tailwind = opts.tailwind ? '<script src="https://cdn.tailwindcss.com"></script>' : "";
1072
+ return '<!DOCTYPE html><html><head><meta charset="utf-8"><style>html,body{margin:0;padding:0;background:transparent;font-family:var(--veo-host-font,system-ui,sans-serif);}</style>' + tokensStyle + tailwind + `<style>${escapeClosing(css, "style")}</style></head><body>${html}<script>${bridge}</script>` + (js ? `<script>${escapeClosing(js, "script")}</script>` : "") + "</body></html>";
706
1073
  }
707
1074
  function escapeClosing(code, tag) {
708
1075
  const rx = tag === "script" ? /<\/script/gi : /<\/style/gi;
@@ -735,6 +1102,20 @@ var CustomRenderer = class extends BaseRenderer {
735
1102
  });
736
1103
  this.registerCleanup(cleanup);
737
1104
  wrapper.appendChild(iframe);
1105
+ wrapper.appendChild(
1106
+ createCloseButton(
1107
+ doc,
1108
+ () => {
1109
+ context.onInteraction({
1110
+ guideId: context.guide.guideId,
1111
+ stepIndex: 0,
1112
+ action: "dismissed"
1113
+ });
1114
+ context.onClose();
1115
+ },
1116
+ "veo-custom-close"
1117
+ )
1118
+ );
738
1119
  root.appendChild(wrapper);
739
1120
  if (placement.mode === "floating") {
740
1121
  applyFloatingPosition(wrapper, placement);
@@ -872,13 +1253,7 @@ function mountFormContent(card, context, doc) {
872
1253
  });
873
1254
  });
874
1255
  content.appendChild(form);
875
- const closeBtn = doc.createElement("button");
876
- closeBtn.type = "button";
877
- closeBtn.className = "veo-guide-close";
878
- closeBtn.setAttribute("aria-label", "Cerrar");
879
- closeBtn.textContent = "\xD7";
880
- closeBtn.addEventListener("click", dismiss);
881
- content.appendChild(closeBtn);
1256
+ content.appendChild(createCloseButton(doc, dismiss));
882
1257
  card.appendChild(content);
883
1258
  }
884
1259
  function showThanks(card, doc, note) {
@@ -1142,6 +1517,20 @@ var InlineCustomRenderer = class extends BaseRenderer {
1142
1517
  const { iframe, cleanup } = mountCustomFrame(doc, step, context, { width: "100%" });
1143
1518
  this.registerCleanup(cleanup);
1144
1519
  wrapper.appendChild(iframe);
1520
+ wrapper.appendChild(
1521
+ createCloseButton(
1522
+ doc,
1523
+ () => {
1524
+ context.onInteraction({
1525
+ guideId: context.guide.guideId,
1526
+ stepIndex: 0,
1527
+ action: "dismissed"
1528
+ });
1529
+ context.onClose();
1530
+ },
1531
+ "veo-custom-close"
1532
+ )
1533
+ );
1145
1534
  root.appendChild(wrapper);
1146
1535
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1147
1536
  }
@@ -1207,7 +1596,8 @@ var InlineRenderer = class extends BaseRenderer {
1207
1596
  // src/plugins/guides/renderers/modal-renderer.ts
1208
1597
  var ModalRenderer = class extends BaseRenderer {
1209
1598
  render(context) {
1210
- const step = context.guide.guideSteps[0];
1599
+ const idx = context.nav?.stepIndex ?? 0;
1600
+ const step = context.guide.guideSteps[idx];
1211
1601
  if (!step) return;
1212
1602
  const { root } = this.createHost();
1213
1603
  this.applyDesign(step.style);
@@ -1217,38 +1607,59 @@ var ModalRenderer = class extends BaseRenderer {
1217
1607
  const card = ownerDocument.createElement("div");
1218
1608
  card.className = "veo-modal-card";
1219
1609
  const dismiss = (action, ctaUrl) => {
1220
- context.onInteraction({
1221
- guideId: context.guide.guideId,
1222
- stepIndex: 0,
1223
- action
1224
- });
1610
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
1225
1611
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1226
1612
  context.onClose();
1227
1613
  };
1228
- const content = buildStepContent(step, ownerDocument, {
1229
- onCtaClick: (action, url) => {
1230
- dismiss("cta_clicked", action === "url" ? url : void 0);
1231
- },
1614
+ const onBackdropOrEsc = () => {
1615
+ if (context.nav) context.nav.callbacks.onSkip();
1616
+ else dismiss("dismissed");
1617
+ };
1618
+ const content = context.nav ? buildWalkthroughStepContent(
1619
+ step,
1620
+ context.nav.stepIndex,
1621
+ context.nav.totalSteps,
1622
+ ownerDocument,
1623
+ context.nav.callbacks
1624
+ ) : buildStepContent(step, ownerDocument, {
1625
+ onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1232
1626
  onDismiss: () => dismiss("dismissed")
1233
1627
  });
1234
1628
  card.appendChild(content);
1235
1629
  overlay.appendChild(card);
1236
1630
  root.appendChild(overlay);
1237
1631
  overlay.addEventListener("click", (e) => {
1238
- if (e.target === overlay) dismiss("dismissed");
1632
+ if (e.target === overlay) onBackdropOrEsc();
1239
1633
  });
1240
1634
  const onKey = (e) => {
1241
- if (e.key === "Escape") dismiss("dismissed");
1635
+ if (e.key === "Escape") onBackdropOrEsc();
1242
1636
  };
1243
1637
  document.addEventListener("keydown", onKey);
1244
1638
  this.registerCleanup(() => document.removeEventListener("keydown", onKey));
1245
- context.onInteraction({
1246
- guideId: context.guide.guideId,
1247
- stepIndex: 0,
1248
- action: "shown"
1249
- });
1639
+ if (!context.nav) {
1640
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1641
+ }
1250
1642
  }
1251
1643
  };
1644
+
1645
+ // src/plugins/guides/renderers/floating-arrow.ts
1646
+ var STATIC_SIDE = {
1647
+ top: "bottom",
1648
+ bottom: "top",
1649
+ left: "right",
1650
+ right: "left"
1651
+ };
1652
+ function positionArrow(arrowEl, placement, data) {
1653
+ const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1654
+ for (const prop of ["top", "bottom", "left", "right"]) {
1655
+ arrowEl.style.setProperty(prop, "");
1656
+ }
1657
+ if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1658
+ if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1659
+ arrowEl.style.setProperty(side, "-6px");
1660
+ }
1661
+
1662
+ // src/plugins/guides/renderers/tooltip-renderer.ts
1252
1663
  var TooltipRenderer = class extends BaseRenderer {
1253
1664
  async render(context) {
1254
1665
  const step = context.guide.guideSteps[0];
@@ -1278,14 +1689,18 @@ var TooltipRenderer = class extends BaseRenderer {
1278
1689
  onDismiss: () => dismiss("dismissed")
1279
1690
  });
1280
1691
  tooltip.appendChild(content);
1692
+ const arrowEl = ownerDocument.createElement("div");
1693
+ arrowEl.className = "veo-tooltip-arrow";
1694
+ tooltip.appendChild(arrowEl);
1281
1695
  root.appendChild(tooltip);
1282
1696
  const updatePosition = async () => {
1283
- const { x, y } = await computePosition(anchor, tooltip, {
1697
+ const { x, y, placement, middlewareData } = await computePosition(anchor, tooltip, {
1284
1698
  placement: "bottom",
1285
- middleware: [offset(8), flip(), shift({ padding: 8 })]
1699
+ middleware: [offset(10), flip(), shift({ padding: 8 }), arrow({ element: arrowEl })]
1286
1700
  });
1287
1701
  tooltip.style.left = `${x}px`;
1288
1702
  tooltip.style.top = `${y}px`;
1703
+ positionArrow(arrowEl, placement, middlewareData.arrow);
1289
1704
  };
1290
1705
  await updatePosition();
1291
1706
  const reposition = () => {
@@ -1304,79 +1719,6 @@ var TooltipRenderer = class extends BaseRenderer {
1304
1719
  });
1305
1720
  }
1306
1721
  };
1307
-
1308
- // src/plugins/guides/walkthrough-block-builder.ts
1309
- function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
1310
- const container = doc.createElement("div");
1311
- container.className = "veo-guide-content";
1312
- const counter = doc.createElement("div");
1313
- counter.className = "veo-walkthrough-counter";
1314
- counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
1315
- container.appendChild(counter);
1316
- const progress = doc.createElement("div");
1317
- progress.className = "veo-walkthrough-progress";
1318
- for (let i = 0; i < totalSteps; i++) {
1319
- const dot = doc.createElement("span");
1320
- dot.className = "veo-walkthrough-progress-dot";
1321
- if (i < stepIndex) dot.classList.add("completed");
1322
- if (i === stepIndex) dot.classList.add("active");
1323
- progress.appendChild(dot);
1324
- }
1325
- container.appendChild(progress);
1326
- if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
1327
- const img = doc.createElement("img");
1328
- img.className = "veo-guide-image";
1329
- img.src = step.imageUrl;
1330
- img.alt = typeof step.title === "string" ? step.title : "";
1331
- container.appendChild(img);
1332
- }
1333
- if (typeof step.title === "string" && step.title) {
1334
- const heading = doc.createElement("h2");
1335
- heading.className = "veo-guide-title";
1336
- heading.textContent = step.title;
1337
- container.appendChild(heading);
1338
- }
1339
- if (typeof step.content === "string" && step.content) {
1340
- const paragraph = doc.createElement("p");
1341
- paragraph.className = "veo-guide-text";
1342
- paragraph.textContent = step.content;
1343
- container.appendChild(paragraph);
1344
- }
1345
- const actions = doc.createElement("div");
1346
- actions.className = "veo-walkthrough-actions";
1347
- const skipBtn = doc.createElement("button");
1348
- skipBtn.type = "button";
1349
- skipBtn.className = "veo-walkthrough-skip";
1350
- skipBtn.textContent = "Omitir";
1351
- skipBtn.addEventListener("click", () => callbacks.onSkip());
1352
- actions.appendChild(skipBtn);
1353
- const rightGroup = doc.createElement("div");
1354
- rightGroup.className = "veo-walkthrough-actions-right";
1355
- if (stepIndex > 0) {
1356
- const backBtn = doc.createElement("button");
1357
- backBtn.type = "button";
1358
- backBtn.className = "veo-walkthrough-btn-secondary";
1359
- backBtn.textContent = "Atr\xE1s";
1360
- backBtn.addEventListener("click", () => callbacks.onBack());
1361
- rightGroup.appendChild(backBtn);
1362
- }
1363
- const isLastStep = stepIndex === totalSteps - 1;
1364
- const primaryBtn = doc.createElement("button");
1365
- primaryBtn.type = "button";
1366
- primaryBtn.className = "veo-guide-cta";
1367
- const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
1368
- primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
1369
- primaryBtn.addEventListener("click", () => {
1370
- if (isLastStep) callbacks.onComplete();
1371
- else callbacks.onNext();
1372
- });
1373
- rightGroup.appendChild(primaryBtn);
1374
- actions.appendChild(rightGroup);
1375
- container.appendChild(actions);
1376
- return container;
1377
- }
1378
-
1379
- // src/plugins/guides/renderers/walkthrough-renderer.ts
1380
1722
  var WalkthroughRenderer = class extends BaseRenderer {
1381
1723
  async render(context) {
1382
1724
  const step = context.guide.guideSteps[context.currentStepIndex];
@@ -1424,27 +1766,13 @@ var WalkthroughRenderer = class extends BaseRenderer {
1424
1766
  return true;
1425
1767
  }
1426
1768
  };
1427
- var STATIC_SIDE = {
1428
- top: "bottom",
1429
- bottom: "top",
1430
- left: "right",
1431
- right: "left"
1432
- };
1433
- function positionArrow(arrowEl, placement, data) {
1434
- const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1435
- for (const prop of ["top", "bottom", "left", "right"]) {
1436
- arrowEl.style.setProperty(prop, "");
1437
- }
1438
- if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1439
- if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1440
- arrowEl.style.setProperty(side, "-6px");
1441
- }
1442
1769
 
1443
1770
  // src/plugins/guides/guide-preview.ts
1444
1771
  var PREVIEW_GUIDE_ID = "__veo_preview__";
1445
1772
  var GuidePreviewController = class {
1446
1773
  constructor() {
1447
1774
  this.singleStep = null;
1775
+ /** Renderer del paso de walkthrough activo (walkthrough tooltip o modal/banner). */
1448
1776
  this.walkthrough = null;
1449
1777
  this.walkthroughGuide = null;
1450
1778
  this.walkthroughIndex = 0;
@@ -1517,7 +1845,6 @@ var GuidePreviewController = class {
1517
1845
  safeDestroy(this.walkthrough);
1518
1846
  this.walkthrough = null;
1519
1847
  }
1520
- const renderer = new WalkthroughRenderer();
1521
1848
  const callbacks = {
1522
1849
  onNext: () => {
1523
1850
  const next = this.walkthroughIndex + 1;
@@ -1537,6 +1864,30 @@ var GuidePreviewController = class {
1537
1864
  onSkip: () => this.close(),
1538
1865
  onComplete: () => this.close()
1539
1866
  };
1867
+ const step = guide.guideSteps[index];
1868
+ const render = step?.style?.render;
1869
+ if (render === "modal" || render === "banner") {
1870
+ const renderer2 = render === "modal" ? new ModalRenderer() : new BannerRenderer();
1871
+ try {
1872
+ renderer2.render({
1873
+ guide,
1874
+ onInteraction: () => {
1875
+ },
1876
+ onClose: () => this.close(),
1877
+ nav: { stepIndex: index, totalSteps: guide.guideSteps.length, callbacks }
1878
+ });
1879
+ } catch {
1880
+ safeDestroy(renderer2);
1881
+ return false;
1882
+ }
1883
+ if (this.walkthroughGuide !== guide) {
1884
+ safeDestroy(renderer2);
1885
+ return false;
1886
+ }
1887
+ this.walkthrough = renderer2;
1888
+ return true;
1889
+ }
1890
+ const renderer = new WalkthroughRenderer();
1540
1891
  let success = false;
1541
1892
  try {
1542
1893
  success = await renderer.render({ guide, currentStepIndex: index, callbacks });
@@ -1743,5 +2094,5 @@ function buildSelectorPath(element, maxAncestors = 5) {
1743
2094
  }
1744
2095
 
1745
2096
  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 };
1746
- //# sourceMappingURL=chunk-GHP7ZJCW.mjs.map
1747
- //# sourceMappingURL=chunk-GHP7ZJCW.mjs.map
2097
+ //# sourceMappingURL=chunk-4VJIILXF.mjs.map
2098
+ //# sourceMappingURL=chunk-4VJIILXF.mjs.map