veo-sdk 0.3.6 → 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.
@@ -47,6 +47,25 @@ interface ActivationRules {
47
47
  eventName?: string;
48
48
  delayMs?: number;
49
49
  }
50
+ /** Tipo de un bloque de contenido (para pasos con varios títulos/textos/botones). */
51
+ type StepBlockType = 'title' | 'text' | 'button' | 'image';
52
+ /**
53
+ * Un bloque de contenido dentro de un paso. Permite componer un paso con VARIOS
54
+ * títulos/textos/botones/imágenes en orden (no solo un título + un texto + un
55
+ * CTA). Si `step.blocks` está presente, el renderer lo usa; si no, cae a los
56
+ * campos escalares (`title`/`content`/`ctaText`/`imageUrl`) — retrocompatible.
57
+ */
58
+ interface StepBlock {
59
+ type: StepBlockType;
60
+ /** title/text/button: el texto. */
61
+ text?: string | null;
62
+ /** image: la URL; button con acción `url`: el destino. */
63
+ url?: string | null;
64
+ /** button: qué hace al hacer click. */
65
+ action?: CtaAction | null;
66
+ /** Estilo por-bloque (align/color/fontSize/margin…). Se aplica inline. */
67
+ style?: Record<string, unknown> | null;
68
+ }
50
69
  interface GuideStep {
51
70
  title: string;
52
71
  content: string;
@@ -57,9 +76,15 @@ interface GuideStep {
57
76
  /**
58
77
  * Free-form JSONB. Convención: si `ctaAction === 'url'`, la URL destino
59
78
  * se lee de `style.ctaUrl`. Otras keys que puede traer: `position`
60
- * (`top` | `bottom` para banners), `theme`.
79
+ * (`top` | `bottom` para banners), `theme`, y `render` (tipo de presentación
80
+ * del paso dentro de un walkthrough heterogéneo: modal/banner/tooltip).
61
81
  */
62
82
  style?: Record<string, unknown> | null;
83
+ /**
84
+ * Bloques de contenido ordenados (varios títulos/textos/botones/imágenes).
85
+ * Si está presente y no vacío, tiene prioridad sobre los campos escalares.
86
+ */
87
+ blocks?: StepBlock[] | null;
63
88
  /** Solo guías `custom`: código a renderizar en un iframe sandbox + ubicación. */
64
89
  html?: string | null;
65
90
  css?: string | null;
@@ -47,6 +47,25 @@ interface ActivationRules {
47
47
  eventName?: string;
48
48
  delayMs?: number;
49
49
  }
50
+ /** Tipo de un bloque de contenido (para pasos con varios títulos/textos/botones). */
51
+ type StepBlockType = 'title' | 'text' | 'button' | 'image';
52
+ /**
53
+ * Un bloque de contenido dentro de un paso. Permite componer un paso con VARIOS
54
+ * títulos/textos/botones/imágenes en orden (no solo un título + un texto + un
55
+ * CTA). Si `step.blocks` está presente, el renderer lo usa; si no, cae a los
56
+ * campos escalares (`title`/`content`/`ctaText`/`imageUrl`) — retrocompatible.
57
+ */
58
+ interface StepBlock {
59
+ type: StepBlockType;
60
+ /** title/text/button: el texto. */
61
+ text?: string | null;
62
+ /** image: la URL; button con acción `url`: el destino. */
63
+ url?: string | null;
64
+ /** button: qué hace al hacer click. */
65
+ action?: CtaAction | null;
66
+ /** Estilo por-bloque (align/color/fontSize/margin…). Se aplica inline. */
67
+ style?: Record<string, unknown> | null;
68
+ }
50
69
  interface GuideStep {
51
70
  title: string;
52
71
  content: string;
@@ -57,9 +76,15 @@ interface GuideStep {
57
76
  /**
58
77
  * Free-form JSONB. Convención: si `ctaAction === 'url'`, la URL destino
59
78
  * se lee de `style.ctaUrl`. Otras keys que puede traer: `position`
60
- * (`top` | `bottom` para banners), `theme`.
79
+ * (`top` | `bottom` para banners), `theme`, y `render` (tipo de presentación
80
+ * del paso dentro de un walkthrough heterogéneo: modal/banner/tooltip).
61
81
  */
62
82
  style?: Record<string, unknown> | null;
83
+ /**
84
+ * Bloques de contenido ordenados (varios títulos/textos/botones/imágenes).
85
+ * Si está presente y no vacío, tiene prioridad sobre los campos escalares.
86
+ */
87
+ blocks?: StepBlock[] | null;
63
88
  /** Solo guías `custom`: código a renderizar en un iframe sandbox + ubicación. */
64
89
  html?: string | null;
65
90
  css?: string | null;
package/dist/index.cjs CHANGED
@@ -378,6 +378,16 @@ var init_constants2 = __esm({
378
378
  function buildStepContent(step, doc, callbacks) {
379
379
  const container = doc.createElement("div");
380
380
  container.className = "veo-guide-content";
381
+ const blocks = normalizeBlocks(step);
382
+ if (blocks) {
383
+ renderBlocks(container, blocks, doc, callbacks);
384
+ } else {
385
+ renderLegacyStep(container, step, doc, callbacks);
386
+ }
387
+ container.appendChild(createCloseButton(doc, () => callbacks.onDismiss()));
388
+ return container;
389
+ }
390
+ function renderLegacyStep(container, step, doc, callbacks) {
381
391
  if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
382
392
  const img = doc.createElement("img");
383
393
  img.className = "veo-guide-image";
@@ -412,8 +422,105 @@ function buildStepContent(step, doc, callbacks) {
412
422
  actions.appendChild(cta);
413
423
  }
414
424
  container.appendChild(actions);
415
- container.appendChild(createCloseButton(doc, () => callbacks.onDismiss()));
416
- return container;
425
+ }
426
+ function normalizeBlocks(step) {
427
+ if (!Array.isArray(step.blocks) || step.blocks.length === 0) return null;
428
+ const valid = step.blocks.filter((b) => {
429
+ if (!b || typeof b !== "object") return false;
430
+ if (b.type === "image") return typeof b.url === "string" && isSafeUrl(b.url);
431
+ return b.type === "title" || b.type === "text" || b.type === "button";
432
+ });
433
+ return valid.length > 0 ? valid : null;
434
+ }
435
+ function renderBlocks(container, blocks, doc, callbacks) {
436
+ let i = 0;
437
+ while (i < blocks.length) {
438
+ const block = blocks[i];
439
+ if (block?.type === "button") {
440
+ const actions = doc.createElement("div");
441
+ actions.className = "veo-guide-actions";
442
+ while (i < blocks.length && blocks[i]?.type === "button") {
443
+ const btnBlock = blocks[i];
444
+ actions.appendChild(buildButtonBlock(btnBlock, doc, callbacks));
445
+ i++;
446
+ }
447
+ container.appendChild(actions);
448
+ continue;
449
+ }
450
+ const node = buildContentBlock(block, doc);
451
+ if (node) container.appendChild(node);
452
+ i++;
453
+ }
454
+ }
455
+ function buildContentBlock(block, doc) {
456
+ switch (block.type) {
457
+ case "title": {
458
+ const el = doc.createElement("h2");
459
+ el.className = "veo-guide-title";
460
+ el.textContent = typeof block.text === "string" ? block.text : "";
461
+ applyBlockStyle(el, block.style, "text");
462
+ return el;
463
+ }
464
+ case "text": {
465
+ const el = doc.createElement("p");
466
+ el.className = "veo-guide-text";
467
+ el.textContent = typeof block.text === "string" ? block.text : "";
468
+ applyBlockStyle(el, block.style, "text");
469
+ return el;
470
+ }
471
+ case "image": {
472
+ if (typeof block.url !== "string" || !isSafeUrl(block.url)) return null;
473
+ const el = doc.createElement("img");
474
+ el.className = "veo-guide-image";
475
+ el.src = block.url;
476
+ el.alt = "";
477
+ applyBlockStyle(el, block.style, "image");
478
+ return el;
479
+ }
480
+ default:
481
+ return null;
482
+ }
483
+ }
484
+ function buildButtonBlock(block, doc, callbacks) {
485
+ const btn = doc.createElement("button");
486
+ btn.type = "button";
487
+ btn.className = "veo-guide-cta";
488
+ btn.textContent = typeof block.text === "string" && block.text ? block.text : "OK";
489
+ applyBlockStyle(btn, block.style, "button");
490
+ btn.addEventListener("click", () => {
491
+ const action = block.action ?? "dismiss";
492
+ const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
493
+ callbacks.onCtaClick(action, url);
494
+ });
495
+ return btn;
496
+ }
497
+ function clampNum(n, min, max) {
498
+ return Math.min(max, Math.max(min, n));
499
+ }
500
+ function applyBlockStyle(el, style, kind) {
501
+ if (!style || typeof style !== "object") return;
502
+ const s = style;
503
+ const align = typeof s.align === "string" ? s.align : null;
504
+ if (align && (align === "left" || align === "center" || align === "right")) {
505
+ if (kind === "text") el.style.textAlign = align;
506
+ else el.style.alignSelf = BLOCK_ALIGN_SELF[align];
507
+ }
508
+ if (kind !== "image" && typeof s.color === "string") el.style.color = s.color;
509
+ if (kind === "text" && typeof s.fontSize === "number" && Number.isFinite(s.fontSize)) {
510
+ el.style.fontSize = `${clampNum(s.fontSize, 8, 72)}px`;
511
+ }
512
+ if (kind === "image" && typeof s.width === "number" && Number.isFinite(s.width)) {
513
+ el.style.width = `${clampNum(s.width, 10, 100)}%`;
514
+ }
515
+ if (kind === "image" && typeof s.radius === "number" && Number.isFinite(s.radius)) {
516
+ el.style.borderRadius = `${clampNum(s.radius, 0, 48)}px`;
517
+ }
518
+ if (typeof s.marginTop === "number" && Number.isFinite(s.marginTop)) {
519
+ el.style.marginTop = `${clampNum(s.marginTop, 0, 64)}px`;
520
+ }
521
+ if (typeof s.marginBottom === "number" && Number.isFinite(s.marginBottom)) {
522
+ el.style.marginBottom = `${clampNum(s.marginBottom, 0, 64)}px`;
523
+ }
417
524
  }
418
525
  function createCloseButton(doc, onClose, className = "veo-guide-close") {
419
526
  const closeBtn = doc.createElement("button");
@@ -439,9 +546,92 @@ function readCtaUrl(step) {
439
546
  if (typeof candidate !== "string") return void 0;
440
547
  return isSafeUrl(candidate) ? candidate : void 0;
441
548
  }
549
+ var BLOCK_ALIGN_SELF;
442
550
  var init_block_builder = __esm({
443
551
  "src/plugins/guides/block-builder.ts"() {
444
552
  init_constants2();
553
+ BLOCK_ALIGN_SELF = {
554
+ left: "flex-start",
555
+ center: "center",
556
+ right: "flex-end"
557
+ };
558
+ }
559
+ });
560
+
561
+ // src/plugins/guides/walkthrough-block-builder.ts
562
+ function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
563
+ const container = doc.createElement("div");
564
+ container.className = "veo-guide-content";
565
+ const counter = doc.createElement("div");
566
+ counter.className = "veo-walkthrough-counter";
567
+ counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
568
+ container.appendChild(counter);
569
+ const progress = doc.createElement("div");
570
+ progress.className = "veo-walkthrough-progress";
571
+ for (let i = 0; i < totalSteps; i++) {
572
+ const dot = doc.createElement("span");
573
+ dot.className = "veo-walkthrough-progress-dot";
574
+ if (i < stepIndex) dot.classList.add("completed");
575
+ if (i === stepIndex) dot.classList.add("active");
576
+ progress.appendChild(dot);
577
+ }
578
+ container.appendChild(progress);
579
+ if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
580
+ const img = doc.createElement("img");
581
+ img.className = "veo-guide-image";
582
+ img.src = step.imageUrl;
583
+ img.alt = typeof step.title === "string" ? step.title : "";
584
+ container.appendChild(img);
585
+ }
586
+ if (typeof step.title === "string" && step.title) {
587
+ const heading = doc.createElement("h2");
588
+ heading.className = "veo-guide-title";
589
+ heading.textContent = step.title;
590
+ container.appendChild(heading);
591
+ }
592
+ if (typeof step.content === "string" && step.content) {
593
+ const paragraph = doc.createElement("p");
594
+ paragraph.className = "veo-guide-text";
595
+ paragraph.textContent = step.content;
596
+ container.appendChild(paragraph);
597
+ }
598
+ const actions = doc.createElement("div");
599
+ actions.className = "veo-walkthrough-actions";
600
+ const skipBtn = doc.createElement("button");
601
+ skipBtn.type = "button";
602
+ skipBtn.className = "veo-walkthrough-skip";
603
+ skipBtn.textContent = "Omitir";
604
+ skipBtn.addEventListener("click", () => callbacks.onSkip());
605
+ actions.appendChild(skipBtn);
606
+ const rightGroup = doc.createElement("div");
607
+ rightGroup.className = "veo-walkthrough-actions-right";
608
+ if (stepIndex > 0) {
609
+ const backBtn = doc.createElement("button");
610
+ backBtn.type = "button";
611
+ backBtn.className = "veo-walkthrough-btn-secondary";
612
+ backBtn.textContent = "Atr\xE1s";
613
+ backBtn.addEventListener("click", () => callbacks.onBack());
614
+ rightGroup.appendChild(backBtn);
615
+ }
616
+ const isLastStep = stepIndex === totalSteps - 1;
617
+ const primaryBtn = doc.createElement("button");
618
+ primaryBtn.type = "button";
619
+ primaryBtn.className = "veo-guide-cta";
620
+ const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
621
+ primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
622
+ primaryBtn.addEventListener("click", () => {
623
+ if (isLastStep) callbacks.onComplete();
624
+ else callbacks.onNext();
625
+ });
626
+ rightGroup.appendChild(primaryBtn);
627
+ actions.appendChild(rightGroup);
628
+ container.appendChild(actions);
629
+ container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
630
+ return container;
631
+ }
632
+ var init_walkthrough_block_builder = __esm({
633
+ "src/plugins/guides/walkthrough-block-builder.ts"() {
634
+ init_block_builder();
445
635
  }
446
636
  });
447
637
 
@@ -455,6 +645,9 @@ function applyDesignVars(host, style) {
455
645
  if (typeof s.accentColor === "string" && COLOR_RE.test(s.accentColor.trim())) {
456
646
  host.style.setProperty("--veo-primary", s.accentColor.trim());
457
647
  }
648
+ if (typeof s.fontFamily === "string" && s.fontFamily.length <= 200 && !/[<>{}]/.test(s.fontFamily)) {
649
+ host.style.setProperty("--veo-font", s.fontFamily.trim());
650
+ }
458
651
  if (s.theme === "dark") {
459
652
  for (const [key, value] of Object.entries(DARK_THEME)) {
460
653
  host.style.setProperty(key, value);
@@ -596,7 +789,7 @@ var init_styles = __esm({
596
789
  --veo-actions-justify: flex-end;
597
790
  all: initial;
598
791
  }
599
- * { box-sizing: border-box; font-family: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif; }
792
+ * { box-sizing: border-box; font-family: var(--veo-font, -apple-system, system-ui, "Segoe UI", Roboto, sans-serif); }
600
793
 
601
794
  .veo-modal-overlay {
602
795
  position: fixed; inset: 0;
@@ -939,10 +1132,12 @@ var BannerRenderer;
939
1132
  var init_banner_renderer = __esm({
940
1133
  "src/plugins/guides/renderers/banner-renderer.ts"() {
941
1134
  init_block_builder();
1135
+ init_walkthrough_block_builder();
942
1136
  init_base_renderer();
943
1137
  BannerRenderer = class extends BaseRenderer {
944
1138
  render(context) {
945
- const step = context.guide.guideSteps[0];
1139
+ const idx = context.nav?.stepIndex ?? 0;
1140
+ const step = context.guide.guideSteps[idx];
946
1141
  if (!step) return;
947
1142
  const { root } = this.createHost();
948
1143
  this.applyDesign(step.style);
@@ -951,27 +1146,25 @@ var init_banner_renderer = __esm({
951
1146
  const banner = ownerDocument.createElement("div");
952
1147
  banner.className = `veo-banner veo-banner-${position}`;
953
1148
  const dismiss = (action, ctaUrl) => {
954
- context.onInteraction({
955
- guideId: context.guide.guideId,
956
- stepIndex: 0,
957
- action
958
- });
1149
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
959
1150
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
960
1151
  context.onClose();
961
1152
  };
962
- const content = buildStepContent(step, ownerDocument, {
963
- onCtaClick: (action, url) => {
964
- dismiss("cta_clicked", action === "url" ? url : void 0);
965
- },
1153
+ const content = context.nav ? buildWalkthroughStepContent(
1154
+ step,
1155
+ context.nav.stepIndex,
1156
+ context.nav.totalSteps,
1157
+ ownerDocument,
1158
+ context.nav.callbacks
1159
+ ) : buildStepContent(step, ownerDocument, {
1160
+ onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
966
1161
  onDismiss: () => dismiss("dismissed")
967
1162
  });
968
1163
  banner.appendChild(content);
969
1164
  root.appendChild(banner);
970
- context.onInteraction({
971
- guideId: context.guide.guideId,
972
- stepIndex: 0,
973
- action: "shown"
974
- });
1165
+ if (!context.nav) {
1166
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1167
+ }
975
1168
  }
976
1169
  };
977
1170
  }
@@ -1693,10 +1886,12 @@ var ModalRenderer;
1693
1886
  var init_modal_renderer = __esm({
1694
1887
  "src/plugins/guides/renderers/modal-renderer.ts"() {
1695
1888
  init_block_builder();
1889
+ init_walkthrough_block_builder();
1696
1890
  init_base_renderer();
1697
1891
  ModalRenderer = class extends BaseRenderer {
1698
1892
  render(context) {
1699
- const step = context.guide.guideSteps[0];
1893
+ const idx = context.nav?.stepIndex ?? 0;
1894
+ const step = context.guide.guideSteps[idx];
1700
1895
  if (!step) return;
1701
1896
  const { root } = this.createHost();
1702
1897
  this.applyDesign(step.style);
@@ -1706,36 +1901,38 @@ var init_modal_renderer = __esm({
1706
1901
  const card = ownerDocument.createElement("div");
1707
1902
  card.className = "veo-modal-card";
1708
1903
  const dismiss = (action, ctaUrl) => {
1709
- context.onInteraction({
1710
- guideId: context.guide.guideId,
1711
- stepIndex: 0,
1712
- action
1713
- });
1904
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: idx, action });
1714
1905
  if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1715
1906
  context.onClose();
1716
1907
  };
1717
- const content = buildStepContent(step, ownerDocument, {
1718
- onCtaClick: (action, url) => {
1719
- dismiss("cta_clicked", action === "url" ? url : void 0);
1720
- },
1908
+ const onBackdropOrEsc = () => {
1909
+ if (context.nav) context.nav.callbacks.onSkip();
1910
+ else dismiss("dismissed");
1911
+ };
1912
+ const content = context.nav ? buildWalkthroughStepContent(
1913
+ step,
1914
+ context.nav.stepIndex,
1915
+ context.nav.totalSteps,
1916
+ ownerDocument,
1917
+ context.nav.callbacks
1918
+ ) : buildStepContent(step, ownerDocument, {
1919
+ onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1721
1920
  onDismiss: () => dismiss("dismissed")
1722
1921
  });
1723
1922
  card.appendChild(content);
1724
1923
  overlay.appendChild(card);
1725
1924
  root.appendChild(overlay);
1726
1925
  overlay.addEventListener("click", (e) => {
1727
- if (e.target === overlay) dismiss("dismissed");
1926
+ if (e.target === overlay) onBackdropOrEsc();
1728
1927
  });
1729
1928
  const onKey = (e) => {
1730
- if (e.key === "Escape") dismiss("dismissed");
1929
+ if (e.key === "Escape") onBackdropOrEsc();
1731
1930
  };
1732
1931
  document.addEventListener("keydown", onKey);
1733
1932
  this.registerCleanup(() => document.removeEventListener("keydown", onKey));
1734
- context.onInteraction({
1735
- guideId: context.guide.guideId,
1736
- stepIndex: 0,
1737
- action: "shown"
1738
- });
1933
+ if (!context.nav) {
1934
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1935
+ }
1739
1936
  }
1740
1937
  };
1741
1938
  }
@@ -1830,83 +2027,6 @@ var init_tooltip_renderer = __esm({
1830
2027
  };
1831
2028
  }
1832
2029
  });
1833
-
1834
- // src/plugins/guides/walkthrough-block-builder.ts
1835
- function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
1836
- const container = doc.createElement("div");
1837
- container.className = "veo-guide-content";
1838
- const counter = doc.createElement("div");
1839
- counter.className = "veo-walkthrough-counter";
1840
- counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
1841
- container.appendChild(counter);
1842
- const progress = doc.createElement("div");
1843
- progress.className = "veo-walkthrough-progress";
1844
- for (let i = 0; i < totalSteps; i++) {
1845
- const dot = doc.createElement("span");
1846
- dot.className = "veo-walkthrough-progress-dot";
1847
- if (i < stepIndex) dot.classList.add("completed");
1848
- if (i === stepIndex) dot.classList.add("active");
1849
- progress.appendChild(dot);
1850
- }
1851
- container.appendChild(progress);
1852
- if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
1853
- const img = doc.createElement("img");
1854
- img.className = "veo-guide-image";
1855
- img.src = step.imageUrl;
1856
- img.alt = typeof step.title === "string" ? step.title : "";
1857
- container.appendChild(img);
1858
- }
1859
- if (typeof step.title === "string" && step.title) {
1860
- const heading = doc.createElement("h2");
1861
- heading.className = "veo-guide-title";
1862
- heading.textContent = step.title;
1863
- container.appendChild(heading);
1864
- }
1865
- if (typeof step.content === "string" && step.content) {
1866
- const paragraph = doc.createElement("p");
1867
- paragraph.className = "veo-guide-text";
1868
- paragraph.textContent = step.content;
1869
- container.appendChild(paragraph);
1870
- }
1871
- const actions = doc.createElement("div");
1872
- actions.className = "veo-walkthrough-actions";
1873
- const skipBtn = doc.createElement("button");
1874
- skipBtn.type = "button";
1875
- skipBtn.className = "veo-walkthrough-skip";
1876
- skipBtn.textContent = "Omitir";
1877
- skipBtn.addEventListener("click", () => callbacks.onSkip());
1878
- actions.appendChild(skipBtn);
1879
- const rightGroup = doc.createElement("div");
1880
- rightGroup.className = "veo-walkthrough-actions-right";
1881
- if (stepIndex > 0) {
1882
- const backBtn = doc.createElement("button");
1883
- backBtn.type = "button";
1884
- backBtn.className = "veo-walkthrough-btn-secondary";
1885
- backBtn.textContent = "Atr\xE1s";
1886
- backBtn.addEventListener("click", () => callbacks.onBack());
1887
- rightGroup.appendChild(backBtn);
1888
- }
1889
- const isLastStep = stepIndex === totalSteps - 1;
1890
- const primaryBtn = doc.createElement("button");
1891
- primaryBtn.type = "button";
1892
- primaryBtn.className = "veo-guide-cta";
1893
- const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
1894
- primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
1895
- primaryBtn.addEventListener("click", () => {
1896
- if (isLastStep) callbacks.onComplete();
1897
- else callbacks.onNext();
1898
- });
1899
- rightGroup.appendChild(primaryBtn);
1900
- actions.appendChild(rightGroup);
1901
- container.appendChild(actions);
1902
- container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
1903
- return container;
1904
- }
1905
- var init_walkthrough_block_builder = __esm({
1906
- "src/plugins/guides/walkthrough-block-builder.ts"() {
1907
- init_block_builder();
1908
- }
1909
- });
1910
2030
  var WalkthroughRenderer;
1911
2031
  var init_walkthrough_renderer = __esm({
1912
2032
  "src/plugins/guides/renderers/walkthrough-renderer.ts"() {
@@ -2039,6 +2159,7 @@ var init_guide_preview = __esm({
2039
2159
  GuidePreviewController = class {
2040
2160
  constructor() {
2041
2161
  this.singleStep = null;
2162
+ /** Renderer del paso de walkthrough activo (walkthrough tooltip o modal/banner). */
2042
2163
  this.walkthrough = null;
2043
2164
  this.walkthroughGuide = null;
2044
2165
  this.walkthroughIndex = 0;
@@ -2111,7 +2232,6 @@ var init_guide_preview = __esm({
2111
2232
  safeDestroy(this.walkthrough);
2112
2233
  this.walkthrough = null;
2113
2234
  }
2114
- const renderer = new WalkthroughRenderer();
2115
2235
  const callbacks = {
2116
2236
  onNext: () => {
2117
2237
  const next = this.walkthroughIndex + 1;
@@ -2131,6 +2251,30 @@ var init_guide_preview = __esm({
2131
2251
  onSkip: () => this.close(),
2132
2252
  onComplete: () => this.close()
2133
2253
  };
2254
+ const step = guide.guideSteps[index];
2255
+ const render = step?.style?.render;
2256
+ if (render === "modal" || render === "banner") {
2257
+ const renderer2 = render === "modal" ? new ModalRenderer() : new BannerRenderer();
2258
+ try {
2259
+ renderer2.render({
2260
+ guide,
2261
+ onInteraction: () => {
2262
+ },
2263
+ onClose: () => this.close(),
2264
+ nav: { stepIndex: index, totalSteps: guide.guideSteps.length, callbacks }
2265
+ });
2266
+ } catch {
2267
+ safeDestroy(renderer2);
2268
+ return false;
2269
+ }
2270
+ if (this.walkthroughGuide !== guide) {
2271
+ safeDestroy(renderer2);
2272
+ return false;
2273
+ }
2274
+ this.walkthrough = renderer2;
2275
+ return true;
2276
+ }
2277
+ const renderer = new WalkthroughRenderer();
2134
2278
  let success = false;
2135
2279
  try {
2136
2280
  success = await renderer.render({ guide, currentStepIndex: index, callbacks });
@@ -4198,6 +4342,11 @@ var GuidesController = class {
4198
4342
  */
4199
4343
  this.activeByGuideId = /* @__PURE__ */ new Map();
4200
4344
  this.dispatched = /* @__PURE__ */ new Set();
4345
+ /**
4346
+ * Renderer del paso de walkthrough activo. Puede ser el `WalkthroughRenderer`
4347
+ * (tooltip anclado) o un Modal/Banner con navegación (walkthrough heterogéneo);
4348
+ * tipamos por `destroy()`, lo único que el controller necesita.
4349
+ */
4201
4350
  this.activeWalkthroughRenderer = null;
4202
4351
  this.activeWalkthroughGuide = null;
4203
4352
  this.debug = config.debug === true;
@@ -4378,7 +4527,6 @@ var GuidesController = class {
4378
4527
  this.activeWalkthroughRenderer.destroy();
4379
4528
  this.activeWalkthroughRenderer = null;
4380
4529
  }
4381
- const renderer = new WalkthroughRenderer();
4382
4530
  this.activeWalkthroughGuide = guide;
4383
4531
  const callbacks = {
4384
4532
  onNext: () => this.handleWalkthroughNext(endUserId, sessionId, pageUrl),
@@ -4386,6 +4534,28 @@ var GuidesController = class {
4386
4534
  onSkip: () => this.handleWalkthroughSkip(endUserId, sessionId, pageUrl),
4387
4535
  onComplete: () => this.handleWalkthroughComplete(endUserId, sessionId, pageUrl)
4388
4536
  };
4537
+ const step = guide.guideSteps[stepIndex];
4538
+ const render = readStepRender(step);
4539
+ if (render === "modal" || render === "banner") {
4540
+ const renderer2 = render === "modal" ? new ModalRenderer() : new BannerRenderer();
4541
+ try {
4542
+ renderer2.render({
4543
+ guide,
4544
+ onInteraction: () => {
4545
+ },
4546
+ onClose: () => {
4547
+ },
4548
+ nav: { stepIndex, totalSteps: guide.guideSteps.length, callbacks }
4549
+ });
4550
+ } catch (err) {
4551
+ if (this.debug) console.error("[veo] sequence renderer threw:", err);
4552
+ this.tearDownWalkthrough();
4553
+ return false;
4554
+ }
4555
+ this.activeWalkthroughRenderer = renderer2;
4556
+ return true;
4557
+ }
4558
+ const renderer = new WalkthroughRenderer();
4389
4559
  let success = false;
4390
4560
  try {
4391
4561
  success = await renderer.render({ guide, currentStepIndex: stepIndex, callbacks });
@@ -4608,6 +4778,10 @@ function extractPath(url) {
4608
4778
  return "/";
4609
4779
  }
4610
4780
  }
4781
+ function readStepRender(step) {
4782
+ const r = step?.style?.render;
4783
+ return typeof r === "string" ? r : "walkthrough";
4784
+ }
4611
4785
 
4612
4786
  // src/plugins/guides/index.ts
4613
4787
  init_guide_preview();