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.
package/dist/builder.cjs CHANGED
@@ -19,6 +19,8 @@ var BUILDER_GUIDE_PARAM = "veoGuide";
19
19
 
20
20
  // src/core/builder-token.ts
21
21
  var CTX_KEY = "veo:builder_ctx";
22
+ var LS_PREFIX = "veo:builder_ctx:";
23
+ var PERSIST_TTL_MS = 12 * 60 * 60 * 1e3;
22
24
  function session() {
23
25
  try {
24
26
  return window.sessionStorage;
@@ -26,6 +28,59 @@ function session() {
26
28
  return null;
27
29
  }
28
30
  }
31
+ function local() {
32
+ try {
33
+ return window.localStorage;
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+ function lsKey(guide) {
39
+ return LS_PREFIX + (guide || "default");
40
+ }
41
+ function persist(ctx) {
42
+ try {
43
+ session()?.setItem(CTX_KEY, JSON.stringify(ctx));
44
+ } catch {
45
+ }
46
+ try {
47
+ const stored = { ...ctx, savedAt: Date.now() };
48
+ local()?.setItem(lsKey(ctx.guide), JSON.stringify(stored));
49
+ } catch {
50
+ }
51
+ }
52
+ function readFreshFromLocal() {
53
+ const store = local();
54
+ if (!store) return null;
55
+ let best = null;
56
+ try {
57
+ for (let i = 0; i < store.length; i++) {
58
+ const key = store.key(i);
59
+ if (!key?.startsWith(LS_PREFIX)) continue;
60
+ const raw = store.getItem(key);
61
+ if (!raw) continue;
62
+ let parsed = null;
63
+ try {
64
+ parsed = JSON.parse(raw);
65
+ } catch {
66
+ continue;
67
+ }
68
+ if (typeof parsed?.savedAt !== "number" || Date.now() - parsed.savedAt > PERSIST_TTL_MS) {
69
+ try {
70
+ store.removeItem(key);
71
+ } catch {
72
+ }
73
+ continue;
74
+ }
75
+ if (!best || parsed.savedAt > best.savedAt) best = parsed;
76
+ }
77
+ } catch {
78
+ return null;
79
+ }
80
+ if (!best) return null;
81
+ const { savedAt: _savedAt, ...ctx } = best;
82
+ return ctx;
83
+ }
29
84
  function urlParams() {
30
85
  try {
31
86
  return new URLSearchParams(window.location.search);
@@ -45,25 +100,39 @@ function resolveBuilderContext() {
45
100
  guide: params?.get(BUILDER_GUIDE_PARAM) ?? null,
46
101
  panelPath: params?.get(BUILDER_PANEL_PATH_PARAM) ?? null
47
102
  };
48
- try {
49
- session()?.setItem(CTX_KEY, JSON.stringify(ctx));
50
- } catch {
51
- }
103
+ persist(ctx);
52
104
  return ctx;
53
105
  }
54
106
  try {
55
107
  const raw = session()?.getItem(CTX_KEY);
56
- return raw ? JSON.parse(raw) : null;
108
+ if (raw) return JSON.parse(raw);
57
109
  } catch {
58
- return null;
59
110
  }
111
+ const fromLocal = readFreshFromLocal();
112
+ if (fromLocal) {
113
+ try {
114
+ session()?.setItem(CTX_KEY, JSON.stringify(fromLocal));
115
+ } catch {
116
+ }
117
+ }
118
+ return fromLocal;
60
119
  }
61
120
  function clearBuilderToken() {
62
121
  if (!hasWindow()) return;
122
+ let guide = null;
123
+ try {
124
+ const raw = session()?.getItem(CTX_KEY);
125
+ if (raw) guide = JSON.parse(raw).guide ?? null;
126
+ } catch {
127
+ }
63
128
  try {
64
129
  session()?.removeItem(CTX_KEY);
65
130
  } catch {
66
131
  }
132
+ try {
133
+ local()?.removeItem(lsKey(guide));
134
+ } catch {
135
+ }
67
136
  }
68
137
 
69
138
  // src/plugins/guides/constants.ts
@@ -77,6 +146,16 @@ var WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS = 5e3;
77
146
  function buildStepContent(step, doc, callbacks) {
78
147
  const container = doc.createElement("div");
79
148
  container.className = "veo-guide-content";
149
+ const blocks = normalizeBlocks(step);
150
+ if (blocks) {
151
+ renderBlocks(container, blocks, doc, callbacks);
152
+ } else {
153
+ renderLegacyStep(container, step, doc, callbacks);
154
+ }
155
+ container.appendChild(createCloseButton(doc, () => callbacks.onDismiss()));
156
+ return container;
157
+ }
158
+ function renderLegacyStep(container, step, doc, callbacks) {
80
159
  if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
81
160
  const img = doc.createElement("img");
82
161
  img.className = "veo-guide-image";
@@ -111,14 +190,119 @@ function buildStepContent(step, doc, callbacks) {
111
190
  actions.appendChild(cta);
112
191
  }
113
192
  container.appendChild(actions);
193
+ }
194
+ function normalizeBlocks(step) {
195
+ if (!Array.isArray(step.blocks) || step.blocks.length === 0) return null;
196
+ const valid = step.blocks.filter((b) => {
197
+ if (!b || typeof b !== "object") return false;
198
+ if (b.type === "image") return typeof b.url === "string" && isSafeUrl(b.url);
199
+ return b.type === "title" || b.type === "text" || b.type === "button";
200
+ });
201
+ return valid.length > 0 ? valid : null;
202
+ }
203
+ function renderBlocks(container, blocks, doc, callbacks) {
204
+ let i = 0;
205
+ while (i < blocks.length) {
206
+ const block = blocks[i];
207
+ if (block?.type === "button") {
208
+ const actions = doc.createElement("div");
209
+ actions.className = "veo-guide-actions";
210
+ while (i < blocks.length && blocks[i]?.type === "button") {
211
+ const btnBlock = blocks[i];
212
+ actions.appendChild(buildButtonBlock(btnBlock, doc, callbacks));
213
+ i++;
214
+ }
215
+ container.appendChild(actions);
216
+ continue;
217
+ }
218
+ const node = buildContentBlock(block, doc);
219
+ if (node) container.appendChild(node);
220
+ i++;
221
+ }
222
+ }
223
+ function buildContentBlock(block, doc) {
224
+ switch (block.type) {
225
+ case "title": {
226
+ const el = doc.createElement("h2");
227
+ el.className = "veo-guide-title";
228
+ el.textContent = typeof block.text === "string" ? block.text : "";
229
+ applyBlockStyle(el, block.style, "text");
230
+ return el;
231
+ }
232
+ case "text": {
233
+ const el = doc.createElement("p");
234
+ el.className = "veo-guide-text";
235
+ el.textContent = typeof block.text === "string" ? block.text : "";
236
+ applyBlockStyle(el, block.style, "text");
237
+ return el;
238
+ }
239
+ case "image": {
240
+ if (typeof block.url !== "string" || !isSafeUrl(block.url)) return null;
241
+ const el = doc.createElement("img");
242
+ el.className = "veo-guide-image";
243
+ el.src = block.url;
244
+ el.alt = "";
245
+ applyBlockStyle(el, block.style, "image");
246
+ return el;
247
+ }
248
+ default:
249
+ return null;
250
+ }
251
+ }
252
+ function buildButtonBlock(block, doc, callbacks) {
253
+ const btn = doc.createElement("button");
254
+ btn.type = "button";
255
+ btn.className = "veo-guide-cta";
256
+ btn.textContent = typeof block.text === "string" && block.text ? block.text : "OK";
257
+ applyBlockStyle(btn, block.style, "button");
258
+ btn.addEventListener("click", () => {
259
+ const action = block.action ?? "dismiss";
260
+ const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
261
+ callbacks.onCtaClick(action, url);
262
+ });
263
+ return btn;
264
+ }
265
+ var BLOCK_ALIGN_SELF = {
266
+ left: "flex-start",
267
+ center: "center",
268
+ right: "flex-end"
269
+ };
270
+ function clampNum(n, min, max) {
271
+ return Math.min(max, Math.max(min, n));
272
+ }
273
+ function applyBlockStyle(el, style, kind) {
274
+ if (!style || typeof style !== "object") return;
275
+ const s = style;
276
+ const align = typeof s.align === "string" ? s.align : null;
277
+ if (align && (align === "left" || align === "center" || align === "right")) {
278
+ if (kind === "text") el.style.textAlign = align;
279
+ else el.style.alignSelf = BLOCK_ALIGN_SELF[align];
280
+ }
281
+ if (kind !== "image" && typeof s.color === "string") el.style.color = s.color;
282
+ if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
283
+ el.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
284
+ }
285
+ if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
286
+ el.style.width = `${clampNum(s.width, 10, 100)}%`;
287
+ }
288
+ if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
289
+ el.style.borderRadius = `${clampNum(s.radius, 0, 48)}px`;
290
+ }
291
+ if (typeof s.marginTop === "number" && Number.isFinite(s.marginTop)) {
292
+ el.style.marginTop = `${clampNum(s.marginTop, 0, 64)}px`;
293
+ }
294
+ if (typeof s.marginBottom === "number" && Number.isFinite(s.marginBottom)) {
295
+ el.style.marginBottom = `${clampNum(s.marginBottom, 0, 64)}px`;
296
+ }
297
+ }
298
+ function createCloseButton(doc, onClose, className = "veo-guide-close") {
114
299
  const closeBtn = doc.createElement("button");
115
300
  closeBtn.type = "button";
116
- closeBtn.className = "veo-guide-close";
301
+ closeBtn.className = className;
117
302
  closeBtn.setAttribute("aria-label", "Cerrar");
118
303
  closeBtn.textContent = "\xD7";
119
- closeBtn.addEventListener("click", () => callbacks.onDismiss());
120
- container.appendChild(closeBtn);
121
- return container;
304
+ closeBtn.addEventListener("click", onClose);
305
+ return closeBtn;
122
306
  }
123
307
  function isSafeUrl(url) {
124
308
  if (typeof url !== "string" || url.length === 0) return false;
@@ -136,6 +320,78 @@ function readCtaUrl(step) {
136
320
  return isSafeUrl(candidate) ? candidate : void 0;
137
321
  }
138
322
 
323
+ // src/plugins/guides/walkthrough-block-builder.ts
324
+ function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
325
+ const container = doc.createElement("div");
326
+ container.className = "veo-guide-content";
327
+ const counter = doc.createElement("div");
328
+ counter.className = "veo-walkthrough-counter";
329
+ counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
330
+ container.appendChild(counter);
331
+ const progress = doc.createElement("div");
332
+ progress.className = "veo-walkthrough-progress";
333
+ for (let i = 0; i < totalSteps; i++) {
334
+ const dot = doc.createElement("span");
335
+ dot.className = "veo-walkthrough-progress-dot";
336
+ if (i < stepIndex) dot.classList.add("completed");
337
+ if (i === stepIndex) dot.classList.add("active");
338
+ progress.appendChild(dot);
339
+ }
340
+ container.appendChild(progress);
341
+ if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
342
+ const img = doc.createElement("img");
343
+ img.className = "veo-guide-image";
344
+ img.src = step.imageUrl;
345
+ img.alt = typeof step.title === "string" ? step.title : "";
346
+ container.appendChild(img);
347
+ }
348
+ if (typeof step.title === "string" && step.title) {
349
+ const heading = doc.createElement("h2");
350
+ heading.className = "veo-guide-title";
351
+ heading.textContent = step.title;
352
+ container.appendChild(heading);
353
+ }
354
+ if (typeof step.content === "string" && step.content) {
355
+ const paragraph = doc.createElement("p");
356
+ paragraph.className = "veo-guide-text";
357
+ paragraph.textContent = step.content;
358
+ container.appendChild(paragraph);
359
+ }
360
+ const actions = doc.createElement("div");
361
+ actions.className = "veo-walkthrough-actions";
362
+ const skipBtn = doc.createElement("button");
363
+ skipBtn.type = "button";
364
+ skipBtn.className = "veo-walkthrough-skip";
365
+ skipBtn.textContent = "Omitir";
366
+ skipBtn.addEventListener("click", () => callbacks.onSkip());
367
+ actions.appendChild(skipBtn);
368
+ const rightGroup = doc.createElement("div");
369
+ rightGroup.className = "veo-walkthrough-actions-right";
370
+ if (stepIndex > 0) {
371
+ const backBtn = doc.createElement("button");
372
+ backBtn.type = "button";
373
+ backBtn.className = "veo-walkthrough-btn-secondary";
374
+ backBtn.textContent = "Atr\xE1s";
375
+ backBtn.addEventListener("click", () => callbacks.onBack());
376
+ rightGroup.appendChild(backBtn);
377
+ }
378
+ const isLastStep = stepIndex === totalSteps - 1;
379
+ const primaryBtn = doc.createElement("button");
380
+ primaryBtn.type = "button";
381
+ primaryBtn.className = "veo-guide-cta";
382
+ const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
383
+ primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
384
+ primaryBtn.addEventListener("click", () => {
385
+ if (isLastStep) callbacks.onComplete();
386
+ else callbacks.onNext();
387
+ });
388
+ rightGroup.appendChild(primaryBtn);
389
+ actions.appendChild(rightGroup);
390
+ container.appendChild(actions);
391
+ container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
392
+ return container;
393
+ }
394
+
139
395
  // src/plugins/guides/guide-design.ts
140
396
  var DARK_THEME = {
141
397
  "--veo-bg": "#1f2937",
@@ -148,6 +404,16 @@ var ALIGN_JUSTIFY = {
148
404
  center: "center",
149
405
  right: "flex-end"
150
406
  };
407
+ var ALIGN_SELF = {
408
+ left: "flex-start",
409
+ center: "center",
410
+ right: "flex-end"
411
+ };
412
+ var ELEMENT_ALIGN_MODE = {
413
+ title: "text",
414
+ text: "text",
415
+ image: "self"
416
+ };
151
417
  var SHADOW = {
152
418
  none: "none",
153
419
  soft: "0 4px 14px rgba(0, 0, 0, 0.10)",
@@ -175,6 +441,9 @@ function applyDesignVars(host, style) {
175
441
  if (typeof s.accentColor === "string" && COLOR_RE.test(s.accentColor.trim())) {
176
442
  host.style.setProperty("--veo-primary", s.accentColor.trim());
177
443
  }
444
+ if (typeof s.fontFamily === "string" && s.fontFamily.length <= 200 && !/[<>{}]/.test(s.fontFamily)) {
445
+ host.style.setProperty("--veo-font", s.fontFamily.trim());
446
+ }
178
447
  if (s.theme === "dark") {
179
448
  for (const [key, value] of Object.entries(DARK_THEME)) {
180
449
  host.style.setProperty(key, value);
@@ -216,6 +485,43 @@ function applyDesignVars(host, style) {
216
485
  host.style.setProperty("--veo-pos-x", `${px * 100}%`);
217
486
  host.style.setProperty("--veo-pos-y", `${py * 100}%`);
218
487
  }
488
+ if (s.elements && typeof s.elements === "object") {
489
+ const el = s.elements;
490
+ applyElementVars(host, "title", el.title);
491
+ applyElementVars(host, "text", el.text);
492
+ applyElementVars(host, "image", el.image);
493
+ const cta = el.cta;
494
+ if (cta && typeof cta.align === "string" && ALIGN_JUSTIFY[cta.align]) {
495
+ host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[cta.align]);
496
+ }
497
+ }
498
+ }
499
+ function applyElementVars(host, prefix, raw) {
500
+ if (!raw || typeof raw !== "object") return;
501
+ const el = raw;
502
+ if (typeof el.align === "string" && (el.align === "left" || el.align === "center" || el.align === "right")) {
503
+ const mode = ELEMENT_ALIGN_MODE[prefix] ?? "text";
504
+ const value = mode === "self" ? ALIGN_SELF[el.align] : el.align;
505
+ host.style.setProperty(`--veo-${prefix}-align`, value);
506
+ }
507
+ if (typeof el.color === "string" && COLOR_RE.test(el.color.trim())) {
508
+ host.style.setProperty(`--veo-${prefix}-color`, el.color.trim());
509
+ }
510
+ if (typeof el.fontSize === "number" && Number.isFinite(el.fontSize)) {
511
+ host.style.setProperty(`--veo-${prefix}-size`, `${clamp(el.fontSize, 8, 72)}px`);
512
+ }
513
+ if (typeof el.width === "number" && Number.isFinite(el.width)) {
514
+ host.style.setProperty(`--veo-${prefix}-width`, `${clamp(el.width, 10, 100)}%`);
515
+ }
516
+ if (typeof el.radius === "number" && Number.isFinite(el.radius)) {
517
+ host.style.setProperty(`--veo-${prefix}-radius`, `${clamp(el.radius, 0, 48)}px`);
518
+ }
519
+ if (typeof el.marginTop === "number" && Number.isFinite(el.marginTop)) {
520
+ host.style.setProperty(`--veo-${prefix}-mt`, `${clamp(el.marginTop, 0, 64)}px`);
521
+ }
522
+ if (typeof el.marginBottom === "number" && Number.isFinite(el.marginBottom)) {
523
+ host.style.setProperty(`--veo-${prefix}-mb`, `${clamp(el.marginBottom, 0, 64)}px`);
524
+ }
219
525
  }
220
526
 
221
527
  // src/plugins/guides/styles.ts
@@ -231,7 +537,7 @@ var GUIDE_STYLES = `
231
537
  --veo-actions-justify: flex-end;
232
538
  all: initial;
233
539
  }
234
- * { box-sizing: border-box; font-family: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif; }
540
+ * { box-sizing: border-box; font-family: var(--veo-font, -apple-system, system-ui, "Segoe UI", Roboto, sans-serif); }
235
541
 
236
542
  .veo-modal-overlay {
237
543
  position: fixed; inset: 0;
@@ -282,6 +588,15 @@ var GUIDE_STYLES = `
282
588
  box-shadow: var(--veo-shadow, 0 8px 30px rgba(0, 0, 0, 0.18));
283
589
  animation: veo-fade-in 150ms ease-out;
284
590
  }
591
+ .veo-tooltip-arrow {
592
+ position: absolute;
593
+ width: 12px;
594
+ height: 12px;
595
+ background: var(--veo-bg);
596
+ border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
597
+ transform: rotate(45deg);
598
+ pointer-events: none;
599
+ }
285
600
 
286
601
  .veo-inline {
287
602
  background: var(--veo-bg);
@@ -300,17 +615,25 @@ var GUIDE_STYLES = `
300
615
  display: flex; flex-direction: column;
301
616
  }
302
617
  .veo-guide-image {
303
- display: block; width: 100%; max-height: 180px;
304
- object-fit: cover; border-radius: 8px;
305
- margin-bottom: 12px;
618
+ display: block;
619
+ width: var(--veo-image-width, 100%);
620
+ max-height: 180px;
621
+ object-fit: cover;
622
+ align-self: var(--veo-image-align, stretch);
623
+ border-radius: var(--veo-image-radius, 8px);
624
+ margin: var(--veo-image-mt, 0) 0 var(--veo-image-mb, 12px);
306
625
  }
307
626
  .veo-guide-title {
308
- font-size: 18px; font-weight: 600; line-height: 1.3;
309
- margin: 0 0 6px; color: var(--veo-text);
627
+ font-size: var(--veo-title-size, 18px); font-weight: 600; line-height: 1.3;
628
+ text-align: var(--veo-title-align, left);
629
+ margin: var(--veo-title-mt, 0) 0 var(--veo-title-mb, 6px);
630
+ color: var(--veo-title-color, var(--veo-text));
310
631
  }
311
632
  .veo-guide-text {
312
- font-size: 14px; line-height: 1.5;
313
- margin: 0 0 16px; color: var(--veo-text-secondary);
633
+ font-size: var(--veo-text-size, 14px); line-height: 1.5;
634
+ text-align: var(--veo-text-align, left);
635
+ margin: var(--veo-text-mt, 0) 0 var(--veo-text-mb, 16px);
636
+ color: var(--veo-text-color, var(--veo-text-secondary));
314
637
  }
315
638
  .veo-guide-actions {
316
639
  display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
@@ -447,6 +770,7 @@ var GUIDE_STYLES = `
447
770
  }
448
771
  .veo-custom-inline {
449
772
  display: block;
773
+ position: relative;
450
774
  margin: 12px 0;
451
775
  }
452
776
  .veo-custom-frame {
@@ -456,6 +780,21 @@ var GUIDE_STYLES = `
456
780
  background: transparent;
457
781
  color-scheme: normal;
458
782
  }
783
+ /*
784
+ * X de cerrar de las gu\xEDas custom: el HTML del cliente vive en un iframe
785
+ * sandbox, as\xED que el bot\xF3n lo pone el host POR ENCIMA del iframe. Con fondo
786
+ * propio para ser visible sobre cualquier contenido.
787
+ */
788
+ .veo-custom-close {
789
+ position: absolute; top: 6px; right: 6px; z-index: 1;
790
+ width: 22px; height: 22px; padding: 0;
791
+ display: flex; align-items: center; justify-content: center;
792
+ font-size: 16px; line-height: 1; color: #6b7280;
793
+ background: rgba(255, 255, 255, 0.92);
794
+ border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 50%;
795
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14); cursor: pointer;
796
+ }
797
+ .veo-custom-close:hover { color: #111827; }
459
798
 
460
799
  @keyframes veo-fade-in { from { opacity: 0; } to { opacity: 1; } }
461
800
  @keyframes veo-slide-up {
@@ -529,7 +868,8 @@ var BaseRenderer = class {
529
868
  // src/plugins/guides/renderers/banner-renderer.ts
530
869
  var BannerRenderer = class extends BaseRenderer {
531
870
  render(context) {
532
- const step = context.guide.guideSteps[0];
871
+ const idx = context.nav?.stepIndex ?? 0;
872
+ const step = context.guide.guideSteps[idx];
533
873
  if (!step) return;
534
874
  const { root } = this.createHost();
535
875
  this.applyDesign(step.style);
@@ -538,27 +878,25 @@ var BannerRenderer = class extends BaseRenderer {
538
878
  const banner = ownerDocument.createElement("div");
539
879
  banner.className = `veo-banner veo-banner-${position}`;
540
880
  const dismiss = (action, ctaUrl) => {
541
- context.onInteraction({
542
- guideId: context.guide.guideId,
543
- stepIndex: 0,
544
- action
545
- });
881
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
546
882
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
547
883
  context.onClose();
548
884
  };
549
- const content = buildStepContent(step, ownerDocument, {
550
- onCtaClick: (action, url) => {
551
- dismiss("cta_clicked", action === "url" ? url : void 0);
552
- },
885
+ const content = context.nav ? buildWalkthroughStepContent(
886
+ step,
887
+ context.nav.stepIndex,
888
+ context.nav.totalSteps,
889
+ ownerDocument,
890
+ context.nav.callbacks
891
+ ) : buildStepContent(step, ownerDocument, {
892
+ onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
553
893
  onDismiss: () => dismiss("dismissed")
554
894
  });
555
895
  banner.appendChild(content);
556
896
  root.appendChild(banner);
557
- context.onInteraction({
558
- guideId: context.guide.guideId,
559
- stepIndex: 0,
560
- action: "shown"
561
- });
897
+ if (!context.nav) {
898
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
899
+ }
562
900
  }
563
901
  };
564
902
 
@@ -628,7 +966,11 @@ function mountCustomFrame(doc, step, context, opts) {
628
966
  const hasFixedHeight = opts.fixedHeight !== void 0 && opts.fixedHeight !== null;
629
967
  if (hasFixedHeight) iframe.style.height = toCssSize(opts.fixedHeight);
630
968
  const token = uuidv7();
631
- iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token);
969
+ const wantsTailwind = step.style?.tailwind === true;
970
+ iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token, {
971
+ tokens: collectHostTokens(),
972
+ tailwind: wantsTailwind
973
+ });
632
974
  const close = () => context.onClose();
633
975
  const onMessage = (event) => {
634
976
  if (event.source !== iframe.contentWindow) return;
@@ -677,12 +1019,37 @@ function mountCustomFrame(doc, step, context, opts) {
677
1019
  function toCssSize(value) {
678
1020
  return typeof value === "number" ? `${value}px` : value;
679
1021
  }
1022
+ function collectHostTokens() {
1023
+ if (typeof window === "undefined" || typeof document === "undefined") return "";
1024
+ try {
1025
+ const rootStyle = getComputedStyle(document.documentElement);
1026
+ const decls = [];
1027
+ const seen = /* @__PURE__ */ new Set();
1028
+ for (let i = 0; i < rootStyle.length && decls.length < 400; i++) {
1029
+ const name = rootStyle.item(i);
1030
+ if (!name.startsWith("--") || seen.has(name)) continue;
1031
+ seen.add(name);
1032
+ const value = rootStyle.getPropertyValue(name).trim();
1033
+ if (!value || value.length > 200 || /[<>{}]/.test(value)) continue;
1034
+ decls.push(`${name}:${value}`);
1035
+ }
1036
+ const bodyFont = getComputedStyle(document.body).fontFamily;
1037
+ if (bodyFont && bodyFont.length <= 200 && !/[<>{}]/.test(bodyFont)) {
1038
+ decls.push(`--veo-host-font:${bodyFont}`);
1039
+ }
1040
+ return decls.length ? `:root{${decls.join(";")}}` : "";
1041
+ } catch {
1042
+ return "";
1043
+ }
1044
+ }
680
1045
  function isRecord(value) {
681
1046
  return typeof value === "object" && value !== null && !Array.isArray(value);
682
1047
  }
683
- function buildSrcdoc(html, css, js, token) {
1048
+ function buildSrcdoc(html, css, js, token, opts) {
684
1049
  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){}})();`;
685
- 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>";
1050
+ const tokensStyle = opts.tokens ? `<style>${escapeClosing(opts.tokens, "style")}</style>` : "";
1051
+ const tailwind = opts.tailwind ? '<script src="https://cdn.tailwindcss.com"></script>' : "";
1052
+ 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>";
686
1053
  }
687
1054
  function escapeClosing(code, tag) {
688
1055
  const rx = tag === "script" ? /<\/script/gi : /<\/style/gi;
@@ -715,6 +1082,20 @@ var CustomRenderer = class extends BaseRenderer {
715
1082
  });
716
1083
  this.registerCleanup(cleanup);
717
1084
  wrapper.appendChild(iframe);
1085
+ wrapper.appendChild(
1086
+ createCloseButton(
1087
+ doc,
1088
+ () => {
1089
+ context.onInteraction({
1090
+ guideId: context.guide.guideId,
1091
+ stepIndex: 0,
1092
+ action: "dismissed"
1093
+ });
1094
+ context.onClose();
1095
+ },
1096
+ "veo-custom-close"
1097
+ )
1098
+ );
718
1099
  root.appendChild(wrapper);
719
1100
  if (placement.mode === "floating") {
720
1101
  applyFloatingPosition(wrapper, placement);
@@ -852,13 +1233,7 @@ function mountFormContent(card, context, doc) {
852
1233
  });
853
1234
  });
854
1235
  content.appendChild(form);
855
- const closeBtn = doc.createElement("button");
856
- closeBtn.type = "button";
857
- closeBtn.className = "veo-guide-close";
858
- closeBtn.setAttribute("aria-label", "Cerrar");
859
- closeBtn.textContent = "\xD7";
860
- closeBtn.addEventListener("click", dismiss);
861
- content.appendChild(closeBtn);
1236
+ content.appendChild(createCloseButton(doc, dismiss));
862
1237
  card.appendChild(content);
863
1238
  }
864
1239
  function showThanks(card, doc, note) {
@@ -1122,6 +1497,20 @@ var InlineCustomRenderer = class extends BaseRenderer {
1122
1497
  const { iframe, cleanup } = mountCustomFrame(doc, step, context, { width: "100%" });
1123
1498
  this.registerCleanup(cleanup);
1124
1499
  wrapper.appendChild(iframe);
1500
+ wrapper.appendChild(
1501
+ createCloseButton(
1502
+ doc,
1503
+ () => {
1504
+ context.onInteraction({
1505
+ guideId: context.guide.guideId,
1506
+ stepIndex: 0,
1507
+ action: "dismissed"
1508
+ });
1509
+ context.onClose();
1510
+ },
1511
+ "veo-custom-close"
1512
+ )
1513
+ );
1125
1514
  root.appendChild(wrapper);
1126
1515
  context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1127
1516
  }
@@ -1187,7 +1576,8 @@ var InlineRenderer = class extends BaseRenderer {
1187
1576
  // src/plugins/guides/renderers/modal-renderer.ts
1188
1577
  var ModalRenderer = class extends BaseRenderer {
1189
1578
  render(context) {
1190
- const step = context.guide.guideSteps[0];
1579
+ const idx = context.nav?.stepIndex ?? 0;
1580
+ const step = context.guide.guideSteps[idx];
1191
1581
  if (!step) return;
1192
1582
  const { root } = this.createHost();
1193
1583
  this.applyDesign(step.style);
@@ -1197,38 +1587,59 @@ var ModalRenderer = class extends BaseRenderer {
1197
1587
  const card = ownerDocument.createElement("div");
1198
1588
  card.className = "veo-modal-card";
1199
1589
  const dismiss = (action, ctaUrl) => {
1200
- context.onInteraction({
1201
- guideId: context.guide.guideId,
1202
- stepIndex: 0,
1203
- action
1204
- });
1590
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
1205
1591
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1206
1592
  context.onClose();
1207
1593
  };
1208
- const content = buildStepContent(step, ownerDocument, {
1209
- onCtaClick: (action, url) => {
1210
- dismiss("cta_clicked", action === "url" ? url : void 0);
1211
- },
1594
+ const onBackdropOrEsc = () => {
1595
+ if (context.nav) context.nav.callbacks.onSkip();
1596
+ else dismiss("dismissed");
1597
+ };
1598
+ const content = context.nav ? buildWalkthroughStepContent(
1599
+ step,
1600
+ context.nav.stepIndex,
1601
+ context.nav.totalSteps,
1602
+ ownerDocument,
1603
+ context.nav.callbacks
1604
+ ) : buildStepContent(step, ownerDocument, {
1605
+ onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1212
1606
  onDismiss: () => dismiss("dismissed")
1213
1607
  });
1214
1608
  card.appendChild(content);
1215
1609
  overlay.appendChild(card);
1216
1610
  root.appendChild(overlay);
1217
1611
  overlay.addEventListener("click", (e) => {
1218
- if (e.target === overlay) dismiss("dismissed");
1612
+ if (e.target === overlay) onBackdropOrEsc();
1219
1613
  });
1220
1614
  const onKey = (e) => {
1221
- if (e.key === "Escape") dismiss("dismissed");
1615
+ if (e.key === "Escape") onBackdropOrEsc();
1222
1616
  };
1223
1617
  document.addEventListener("keydown", onKey);
1224
1618
  this.registerCleanup(() => document.removeEventListener("keydown", onKey));
1225
- context.onInteraction({
1226
- guideId: context.guide.guideId,
1227
- stepIndex: 0,
1228
- action: "shown"
1229
- });
1619
+ if (!context.nav) {
1620
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1621
+ }
1230
1622
  }
1231
1623
  };
1624
+
1625
+ // src/plugins/guides/renderers/floating-arrow.ts
1626
+ var STATIC_SIDE = {
1627
+ top: "bottom",
1628
+ bottom: "top",
1629
+ left: "right",
1630
+ right: "left"
1631
+ };
1632
+ function positionArrow(arrowEl, placement, data) {
1633
+ const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1634
+ for (const prop of ["top", "bottom", "left", "right"]) {
1635
+ arrowEl.style.setProperty(prop, "");
1636
+ }
1637
+ if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1638
+ if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1639
+ arrowEl.style.setProperty(side, "-6px");
1640
+ }
1641
+
1642
+ // src/plugins/guides/renderers/tooltip-renderer.ts
1232
1643
  var TooltipRenderer = class extends BaseRenderer {
1233
1644
  async render(context) {
1234
1645
  const step = context.guide.guideSteps[0];
@@ -1258,14 +1669,18 @@ var TooltipRenderer = class extends BaseRenderer {
1258
1669
  onDismiss: () => dismiss("dismissed")
1259
1670
  });
1260
1671
  tooltip.appendChild(content);
1672
+ const arrowEl = ownerDocument.createElement("div");
1673
+ arrowEl.className = "veo-tooltip-arrow";
1674
+ tooltip.appendChild(arrowEl);
1261
1675
  root.appendChild(tooltip);
1262
1676
  const updatePosition = async () => {
1263
- const { x, y } = await dom.computePosition(anchor, tooltip, {
1677
+ const { x, y, placement, middlewareData } = await dom.computePosition(anchor, tooltip, {
1264
1678
  placement: "bottom",
1265
- middleware: [dom.offset(8), dom.flip(), dom.shift({ padding: 8 })]
1679
+ middleware: [dom.offset(10), dom.flip(), dom.shift({ padding: 8 }), dom.arrow({ element: arrowEl })]
1266
1680
  });
1267
1681
  tooltip.style.left = `${x}px`;
1268
1682
  tooltip.style.top = `${y}px`;
1683
+ positionArrow(arrowEl, placement, middlewareData.arrow);
1269
1684
  };
1270
1685
  await updatePosition();
1271
1686
  const reposition = () => {
@@ -1284,79 +1699,6 @@ var TooltipRenderer = class extends BaseRenderer {
1284
1699
  });
1285
1700
  }
1286
1701
  };
1287
-
1288
- // src/plugins/guides/walkthrough-block-builder.ts
1289
- function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
1290
- const container = doc.createElement("div");
1291
- container.className = "veo-guide-content";
1292
- const counter = doc.createElement("div");
1293
- counter.className = "veo-walkthrough-counter";
1294
- counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
1295
- container.appendChild(counter);
1296
- const progress = doc.createElement("div");
1297
- progress.className = "veo-walkthrough-progress";
1298
- for (let i = 0; i < totalSteps; i++) {
1299
- const dot = doc.createElement("span");
1300
- dot.className = "veo-walkthrough-progress-dot";
1301
- if (i < stepIndex) dot.classList.add("completed");
1302
- if (i === stepIndex) dot.classList.add("active");
1303
- progress.appendChild(dot);
1304
- }
1305
- container.appendChild(progress);
1306
- if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
1307
- const img = doc.createElement("img");
1308
- img.className = "veo-guide-image";
1309
- img.src = step.imageUrl;
1310
- img.alt = typeof step.title === "string" ? step.title : "";
1311
- container.appendChild(img);
1312
- }
1313
- if (typeof step.title === "string" && step.title) {
1314
- const heading = doc.createElement("h2");
1315
- heading.className = "veo-guide-title";
1316
- heading.textContent = step.title;
1317
- container.appendChild(heading);
1318
- }
1319
- if (typeof step.content === "string" && step.content) {
1320
- const paragraph = doc.createElement("p");
1321
- paragraph.className = "veo-guide-text";
1322
- paragraph.textContent = step.content;
1323
- container.appendChild(paragraph);
1324
- }
1325
- const actions = doc.createElement("div");
1326
- actions.className = "veo-walkthrough-actions";
1327
- const skipBtn = doc.createElement("button");
1328
- skipBtn.type = "button";
1329
- skipBtn.className = "veo-walkthrough-skip";
1330
- skipBtn.textContent = "Omitir";
1331
- skipBtn.addEventListener("click", () => callbacks.onSkip());
1332
- actions.appendChild(skipBtn);
1333
- const rightGroup = doc.createElement("div");
1334
- rightGroup.className = "veo-walkthrough-actions-right";
1335
- if (stepIndex > 0) {
1336
- const backBtn = doc.createElement("button");
1337
- backBtn.type = "button";
1338
- backBtn.className = "veo-walkthrough-btn-secondary";
1339
- backBtn.textContent = "Atr\xE1s";
1340
- backBtn.addEventListener("click", () => callbacks.onBack());
1341
- rightGroup.appendChild(backBtn);
1342
- }
1343
- const isLastStep = stepIndex === totalSteps - 1;
1344
- const primaryBtn = doc.createElement("button");
1345
- primaryBtn.type = "button";
1346
- primaryBtn.className = "veo-guide-cta";
1347
- const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
1348
- primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
1349
- primaryBtn.addEventListener("click", () => {
1350
- if (isLastStep) callbacks.onComplete();
1351
- else callbacks.onNext();
1352
- });
1353
- rightGroup.appendChild(primaryBtn);
1354
- actions.appendChild(rightGroup);
1355
- container.appendChild(actions);
1356
- return container;
1357
- }
1358
-
1359
- // src/plugins/guides/renderers/walkthrough-renderer.ts
1360
1702
  var WalkthroughRenderer = class extends BaseRenderer {
1361
1703
  async render(context) {
1362
1704
  const step = context.guide.guideSteps[context.currentStepIndex];
@@ -1404,27 +1746,13 @@ var WalkthroughRenderer = class extends BaseRenderer {
1404
1746
  return true;
1405
1747
  }
1406
1748
  };
1407
- var STATIC_SIDE = {
1408
- top: "bottom",
1409
- bottom: "top",
1410
- left: "right",
1411
- right: "left"
1412
- };
1413
- function positionArrow(arrowEl, placement, data) {
1414
- const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1415
- for (const prop of ["top", "bottom", "left", "right"]) {
1416
- arrowEl.style.setProperty(prop, "");
1417
- }
1418
- if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1419
- if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1420
- arrowEl.style.setProperty(side, "-6px");
1421
- }
1422
1749
 
1423
1750
  // src/plugins/guides/guide-preview.ts
1424
1751
  var PREVIEW_GUIDE_ID = "__veo_preview__";
1425
1752
  var GuidePreviewController = class {
1426
1753
  constructor() {
1427
1754
  this.singleStep = null;
1755
+ /** Renderer del paso de walkthrough activo (walkthrough tooltip o modal/banner). */
1428
1756
  this.walkthrough = null;
1429
1757
  this.walkthroughGuide = null;
1430
1758
  this.walkthroughIndex = 0;
@@ -1497,7 +1825,6 @@ var GuidePreviewController = class {
1497
1825
  safeDestroy(this.walkthrough);
1498
1826
  this.walkthrough = null;
1499
1827
  }
1500
- const renderer = new WalkthroughRenderer();
1501
1828
  const callbacks = {
1502
1829
  onNext: () => {
1503
1830
  const next = this.walkthroughIndex + 1;
@@ -1517,6 +1844,30 @@ var GuidePreviewController = class {
1517
1844
  onSkip: () => this.close(),
1518
1845
  onComplete: () => this.close()
1519
1846
  };
1847
+ const step = guide.guideSteps[index];
1848
+ const render = step?.style?.render;
1849
+ if (render === "modal" || render === "banner") {
1850
+ const renderer2 = render === "modal" ? new ModalRenderer() : new BannerRenderer();
1851
+ try {
1852
+ renderer2.render({
1853
+ guide,
1854
+ onInteraction: () => {
1855
+ },
1856
+ onClose: () => this.close(),
1857
+ nav: { stepIndex: index, totalSteps: guide.guideSteps.length, callbacks }
1858
+ });
1859
+ } catch {
1860
+ safeDestroy(renderer2);
1861
+ return false;
1862
+ }
1863
+ if (this.walkthroughGuide !== guide) {
1864
+ safeDestroy(renderer2);
1865
+ return false;
1866
+ }
1867
+ this.walkthrough = renderer2;
1868
+ return true;
1869
+ }
1870
+ const renderer = new WalkthroughRenderer();
1520
1871
  let success = false;
1521
1872
  try {
1522
1873
  success = await renderer.render({ guide, currentStepIndex: index, callbacks });