veo-sdk 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder-AW37YKWO.mjs +5 -0
- package/dist/{builder-S6ZODU2M.mjs.map → builder-AW37YKWO.mjs.map} +1 -1
- package/dist/builder.cjs +40 -13
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.d.cts +2 -2
- package/dist/builder.d.ts +2 -2
- package/dist/builder.mjs +4 -4
- package/dist/{chunk-KLN46YQI.mjs → chunk-3W47W3PI.mjs} +105 -19
- package/dist/chunk-3W47W3PI.mjs.map +1 -0
- package/dist/{chunk-ULH64GMX.mjs → chunk-H464CQY3.mjs} +3 -3
- package/dist/{chunk-ULH64GMX.mjs.map → chunk-H464CQY3.mjs.map} +1 -1
- package/dist/{chunk-F2ZWZUTR.mjs → chunk-UJTSYDQ6.mjs} +42 -15
- package/dist/chunk-UJTSYDQ6.mjs.map +1 -0
- package/dist/{guide-preview-D_xhRVLe.d.cts → guide-preview-B912Y6UY.d.cts} +4 -2
- package/dist/{guide-preview-D_xhRVLe.d.ts → guide-preview-B912Y6UY.d.ts} +4 -2
- package/dist/index.cjs +142 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +5 -5
- package/dist/veo-builder.js +5 -5
- package/dist/veo-builder.js.map +1 -1
- package/dist/veo-guides.js +5 -5
- package/dist/veo-guides.js.map +1 -1
- package/package.json +1 -1
- package/dist/builder-S6ZODU2M.mjs +0 -5
- package/dist/chunk-F2ZWZUTR.mjs.map +0 -1
- package/dist/chunk-KLN46YQI.mjs.map +0 -1
|
@@ -34,8 +34,8 @@ interface CustomPlacement {
|
|
|
34
34
|
height?: number | string;
|
|
35
35
|
}
|
|
36
36
|
type UrlMatchType = 'exact' | 'prefix' | 'regex';
|
|
37
|
-
type ActivationTrigger = 'immediate' | 'on_element' | 'on_event' | 'delayed';
|
|
38
|
-
type CtaAction = 'dismiss' | 'next' | 'url';
|
|
37
|
+
type ActivationTrigger = 'immediate' | 'on_element' | 'on_event' | 'delayed' | 'on_click' | 'on_hover';
|
|
38
|
+
type CtaAction = 'dismiss' | 'next' | 'url' | 'guide';
|
|
39
39
|
interface UrlMatcher {
|
|
40
40
|
type: UrlMatchType;
|
|
41
41
|
pattern: string;
|
|
@@ -76,6 +76,8 @@ interface StepBlock {
|
|
|
76
76
|
url?: string | null;
|
|
77
77
|
/** button: qué hace al hacer click. */
|
|
78
78
|
action?: CtaAction | null;
|
|
79
|
+
/** button con acción `guide`: id de la guía que abre (encadenado). */
|
|
80
|
+
targetGuideId?: string | null;
|
|
79
81
|
/** Estilo por-bloque (align/color/fontSize/margin…). Se aplica inline. */
|
|
80
82
|
style?: Record<string, unknown> | null;
|
|
81
83
|
}
|
|
@@ -34,8 +34,8 @@ interface CustomPlacement {
|
|
|
34
34
|
height?: number | string;
|
|
35
35
|
}
|
|
36
36
|
type UrlMatchType = 'exact' | 'prefix' | 'regex';
|
|
37
|
-
type ActivationTrigger = 'immediate' | 'on_element' | 'on_event' | 'delayed';
|
|
38
|
-
type CtaAction = 'dismiss' | 'next' | 'url';
|
|
37
|
+
type ActivationTrigger = 'immediate' | 'on_element' | 'on_event' | 'delayed' | 'on_click' | 'on_hover';
|
|
38
|
+
type CtaAction = 'dismiss' | 'next' | 'url' | 'guide';
|
|
39
39
|
interface UrlMatcher {
|
|
40
40
|
type: UrlMatchType;
|
|
41
41
|
pattern: string;
|
|
@@ -76,6 +76,8 @@ interface StepBlock {
|
|
|
76
76
|
url?: string | null;
|
|
77
77
|
/** button: qué hace al hacer click. */
|
|
78
78
|
action?: CtaAction | null;
|
|
79
|
+
/** button con acción `guide`: id de la guía que abre (encadenado). */
|
|
80
|
+
targetGuideId?: string | null;
|
|
79
81
|
/** Estilo por-bloque (align/color/fontSize/margin…). Se aplica inline. */
|
|
80
82
|
style?: Record<string, unknown> | null;
|
|
81
83
|
}
|
package/dist/index.cjs
CHANGED
|
@@ -615,12 +615,12 @@ function buildButtonBlock(block, doc, callbacks) {
|
|
|
615
615
|
applyBlockStyle(btn, block.style, "button");
|
|
616
616
|
btn.addEventListener("click", () => {
|
|
617
617
|
const action = block.action ?? "dismiss";
|
|
618
|
-
const
|
|
618
|
+
const target = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : action === "guide" && typeof block.targetGuideId === "string" && block.targetGuideId ? block.targetGuideId : void 0;
|
|
619
619
|
const meta = {
|
|
620
620
|
...typeof block.id === "string" && block.id ? { buttonId: block.id } : {},
|
|
621
621
|
...typeof block.text === "string" && block.text ? { buttonText: block.text } : {}
|
|
622
622
|
};
|
|
623
|
-
callbacks.onCtaClick(action,
|
|
623
|
+
callbacks.onCtaClick(action, target, Object.keys(meta).length ? meta : void 0);
|
|
624
624
|
});
|
|
625
625
|
return btn;
|
|
626
626
|
}
|
|
@@ -1554,15 +1554,16 @@ var init_badge_renderer = __esm({
|
|
|
1554
1554
|
});
|
|
1555
1555
|
};
|
|
1556
1556
|
const buildTooltipContent = (s) => buildStepContent(s, ownerDocument, {
|
|
1557
|
-
onCtaClick: (action,
|
|
1557
|
+
onCtaClick: (action, target, meta) => {
|
|
1558
1558
|
emit("cta_clicked", meta);
|
|
1559
|
-
if (action === "url" &&
|
|
1559
|
+
if (action === "url" && target) window.open(target, "_blank", "noopener,noreferrer");
|
|
1560
1560
|
if (action === "dismiss") {
|
|
1561
1561
|
emit("dismissed");
|
|
1562
1562
|
context.onClose();
|
|
1563
1563
|
return;
|
|
1564
1564
|
}
|
|
1565
1565
|
this.closeTooltip();
|
|
1566
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
1566
1567
|
},
|
|
1567
1568
|
onDismiss: () => {
|
|
1568
1569
|
emit("dismissed");
|
|
@@ -1820,7 +1821,10 @@ var init_banner_renderer = __esm({
|
|
|
1820
1821
|
ownerDocument,
|
|
1821
1822
|
context.nav.callbacks
|
|
1822
1823
|
) : buildStepContent(step, ownerDocument, {
|
|
1823
|
-
onCtaClick: (action,
|
|
1824
|
+
onCtaClick: (action, target, meta) => {
|
|
1825
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
1826
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
1827
|
+
},
|
|
1824
1828
|
onDismiss: () => dismiss("dismissed")
|
|
1825
1829
|
});
|
|
1826
1830
|
banner.appendChild(content);
|
|
@@ -1835,7 +1839,10 @@ var init_banner_renderer = __esm({
|
|
|
1835
1839
|
this.liveContainer = banner;
|
|
1836
1840
|
this.liveContent = content;
|
|
1837
1841
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1838
|
-
onCtaClick: (action,
|
|
1842
|
+
onCtaClick: (action, target, meta) => {
|
|
1843
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
1844
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
1845
|
+
},
|
|
1839
1846
|
onDismiss: () => dismiss("dismissed")
|
|
1840
1847
|
});
|
|
1841
1848
|
this.liveKey = this.liveKeyOf(step);
|
|
@@ -2496,7 +2503,10 @@ var init_inline_renderer = __esm({
|
|
|
2496
2503
|
context.onClose();
|
|
2497
2504
|
};
|
|
2498
2505
|
const content = buildStepContent(step, ownerDocument, {
|
|
2499
|
-
onCtaClick: (action,
|
|
2506
|
+
onCtaClick: (action, target, meta) => {
|
|
2507
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2508
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2509
|
+
},
|
|
2500
2510
|
onDismiss: () => dismiss("dismissed")
|
|
2501
2511
|
});
|
|
2502
2512
|
card.appendChild(content);
|
|
@@ -2505,7 +2515,10 @@ var init_inline_renderer = __esm({
|
|
|
2505
2515
|
this.liveContent = content;
|
|
2506
2516
|
this.liveKey = `${selector}|${position}`;
|
|
2507
2517
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2508
|
-
onCtaClick: (action,
|
|
2518
|
+
onCtaClick: (action, target, meta) => {
|
|
2519
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2520
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2521
|
+
},
|
|
2509
2522
|
onDismiss: () => dismiss("dismissed")
|
|
2510
2523
|
});
|
|
2511
2524
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
@@ -2565,7 +2578,10 @@ var init_modal_renderer = __esm({
|
|
|
2565
2578
|
ownerDocument,
|
|
2566
2579
|
context.nav.callbacks
|
|
2567
2580
|
) : buildStepContent(step, ownerDocument, {
|
|
2568
|
-
onCtaClick: (action,
|
|
2581
|
+
onCtaClick: (action, target, meta) => {
|
|
2582
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2583
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2584
|
+
},
|
|
2569
2585
|
onDismiss: () => dismiss("dismissed")
|
|
2570
2586
|
});
|
|
2571
2587
|
card.appendChild(content);
|
|
@@ -2575,7 +2591,10 @@ var init_modal_renderer = __esm({
|
|
|
2575
2591
|
this.liveContainer = card;
|
|
2576
2592
|
this.liveContent = content;
|
|
2577
2593
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2578
|
-
onCtaClick: (action,
|
|
2594
|
+
onCtaClick: (action, target, meta) => {
|
|
2595
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2596
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2597
|
+
},
|
|
2579
2598
|
onDismiss: () => dismiss("dismissed")
|
|
2580
2599
|
});
|
|
2581
2600
|
}
|
|
@@ -2640,8 +2659,9 @@ var init_tooltip_renderer = __esm({
|
|
|
2640
2659
|
context.onClose();
|
|
2641
2660
|
};
|
|
2642
2661
|
const content = buildStepContent(step, ownerDocument, {
|
|
2643
|
-
onCtaClick: (action,
|
|
2644
|
-
dismiss("cta_clicked", action === "url" ?
|
|
2662
|
+
onCtaClick: (action, target, meta) => {
|
|
2663
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2664
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2645
2665
|
},
|
|
2646
2666
|
onDismiss: () => dismiss("dismissed")
|
|
2647
2667
|
});
|
|
@@ -2674,7 +2694,10 @@ var init_tooltip_renderer = __esm({
|
|
|
2674
2694
|
this.liveContent = content;
|
|
2675
2695
|
this.liveKey = selector;
|
|
2676
2696
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2677
|
-
onCtaClick: (action,
|
|
2697
|
+
onCtaClick: (action, target, meta) => {
|
|
2698
|
+
dismiss("cta_clicked", action === "url" ? target : void 0, meta);
|
|
2699
|
+
if (action === "guide" && target) context.onOpenGuide?.(target);
|
|
2700
|
+
},
|
|
2678
2701
|
onDismiss: () => dismiss("dismissed")
|
|
2679
2702
|
});
|
|
2680
2703
|
context.onInteraction({
|
|
@@ -2891,6 +2914,10 @@ var init_guide_preview = __esm({
|
|
|
2891
2914
|
onClose: () => this.close(),
|
|
2892
2915
|
onTrack: () => {
|
|
2893
2916
|
},
|
|
2917
|
+
// Encadenado en preview: no-op — el editor no puede montar otra guía
|
|
2918
|
+
// guardada; el click igual cierra la preview actual (vía onCtaClick).
|
|
2919
|
+
onOpenGuide: () => {
|
|
2920
|
+
},
|
|
2894
2921
|
// Preview de forms: por defecto simula el envío SIN escribir props;
|
|
2895
2922
|
// el caller puede pasar `formSubmit` real (link de preview).
|
|
2896
2923
|
onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
|
|
@@ -5333,6 +5360,12 @@ var GuidesController = class {
|
|
|
5333
5360
|
this.resolveCache = null;
|
|
5334
5361
|
/** Resolución idéntica en vuelo, para coalescer llamadas concurrentes. */
|
|
5335
5362
|
this.inflightResolve = null;
|
|
5363
|
+
/**
|
|
5364
|
+
* Disparadores por interacción armados (`on_click`/`on_hover`): guideId →
|
|
5365
|
+
* desarme del listener. Se re-arman desde cero en cada `checkGuides` (el
|
|
5366
|
+
* resolve fresco manda) y se limpian en `destroy()`.
|
|
5367
|
+
*/
|
|
5368
|
+
this.armedTriggers = /* @__PURE__ */ new Map();
|
|
5336
5369
|
this.debug = config.debug === true;
|
|
5337
5370
|
this.apiUrl = config.apiUrl;
|
|
5338
5371
|
this.apiKey = config.apiKey;
|
|
@@ -5390,26 +5423,80 @@ var GuidesController = class {
|
|
|
5390
5423
|
return;
|
|
5391
5424
|
}
|
|
5392
5425
|
if (this.debug) console.log(`[veo] guides: ${guides.length} eligible at ${pageUrl}`);
|
|
5426
|
+
this.disarmTriggers();
|
|
5393
5427
|
for (const guide of guides) {
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
}
|
|
5398
|
-
if (this.activeByGuideId.has(guide.guideId)) {
|
|
5399
|
-
if (this.debug) console.log(`[veo] guides: already shown guideId=${guide.guideId}`);
|
|
5428
|
+
const trigger = guide.activationRules.trigger;
|
|
5429
|
+
if ((trigger === "on_click" || trigger === "on_hover") && guide.activationRules.selector) {
|
|
5430
|
+
this.armInteractionTrigger(guide, endUserId, sessionId, pageUrl);
|
|
5400
5431
|
continue;
|
|
5401
5432
|
}
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5433
|
+
await this.showGuide(guide, endUserId, sessionId, pageUrl);
|
|
5434
|
+
}
|
|
5435
|
+
}
|
|
5436
|
+
/**
|
|
5437
|
+
* Muestra una guía elegible (filtro de frecuencia + dedupe + dispatch por
|
|
5438
|
+
* tipo). Cuerpo original del loop de `checkGuides`, extraído para que los
|
|
5439
|
+
* disparadores por interacción pasen por LOS MISMOS chequeos al disparar.
|
|
5440
|
+
*/
|
|
5441
|
+
async showGuide(guide, endUserId, sessionId, pageUrl) {
|
|
5442
|
+
if (this.frequencyCache.shouldFilter(guide.guideId, guide.displayFrequency)) {
|
|
5443
|
+
if (this.debug) console.log(`[veo] guides: filtered locally guideId=${guide.guideId}`);
|
|
5444
|
+
return;
|
|
5445
|
+
}
|
|
5446
|
+
if (this.activeByGuideId.has(guide.guideId)) {
|
|
5447
|
+
if (this.debug) console.log(`[veo] guides: already shown guideId=${guide.guideId}`);
|
|
5448
|
+
return;
|
|
5449
|
+
}
|
|
5450
|
+
if (guide.guideType === "walkthrough" && guide.guideSteps.length > 1) {
|
|
5451
|
+
if (this.walkthroughState.hasActive()) {
|
|
5452
|
+
if (this.debug)
|
|
5453
|
+
console.log(`[veo] guides: walkthrough ${guide.guideId} skipped (another active)`);
|
|
5454
|
+
return;
|
|
5410
5455
|
}
|
|
5411
|
-
this.
|
|
5456
|
+
await this.startWalkthrough(guide, endUserId, sessionId, pageUrl);
|
|
5457
|
+
return;
|
|
5412
5458
|
}
|
|
5459
|
+
this.runSingleStepRender(guide, sessionId, pageUrl);
|
|
5460
|
+
}
|
|
5461
|
+
/**
|
|
5462
|
+
* Arma un disparador por interacción: la guía NO se muestra al cargar, sino
|
|
5463
|
+
* cuando el usuario hace click / pasa el mouse sobre `activationRules.selector`.
|
|
5464
|
+
* Listener DELEGADO en `document` (fase captura, sin preventDefault — el click
|
|
5465
|
+
* original de la app sigue funcionando). Al disparar se desarma y la guía pasa
|
|
5466
|
+
* por `showGuide` (mismos chequeos de frecuencia/dedupe). Tras cerrarse, el
|
|
5467
|
+
* próximo pageview/identify la re-arma — hover/click vuelve a mostrarla
|
|
5468
|
+
* mientras la frecuencia lo permita.
|
|
5469
|
+
*/
|
|
5470
|
+
armInteractionTrigger(guide, endUserId, sessionId, pageUrl) {
|
|
5471
|
+
if (this.frequencyCache.shouldFilter(guide.guideId, guide.displayFrequency)) return;
|
|
5472
|
+
if (this.activeByGuideId.has(guide.guideId)) return;
|
|
5473
|
+
if (this.armedTriggers.has(guide.guideId)) return;
|
|
5474
|
+
const selector = guide.activationRules.selector;
|
|
5475
|
+
const eventName = guide.activationRules.trigger === "on_hover" ? "mouseover" : "click";
|
|
5476
|
+
const onFire = (e) => {
|
|
5477
|
+
const target = e.target;
|
|
5478
|
+
if (!(target instanceof Element)) return;
|
|
5479
|
+
try {
|
|
5480
|
+
if (!target.closest(selector)) return;
|
|
5481
|
+
} catch {
|
|
5482
|
+
return;
|
|
5483
|
+
}
|
|
5484
|
+
disarm();
|
|
5485
|
+
void this.showGuide(guide, endUserId, sessionId, pageUrl);
|
|
5486
|
+
};
|
|
5487
|
+
const disarm = () => {
|
|
5488
|
+
document.removeEventListener(eventName, onFire, true);
|
|
5489
|
+
this.armedTriggers.delete(guide.guideId);
|
|
5490
|
+
};
|
|
5491
|
+
document.addEventListener(eventName, onFire, true);
|
|
5492
|
+
this.armedTriggers.set(guide.guideId, disarm);
|
|
5493
|
+
if (this.debug)
|
|
5494
|
+
console.log(`[veo] guides: armed ${eventName} trigger for ${guide.guideId} on ${selector}`);
|
|
5495
|
+
}
|
|
5496
|
+
/** Desarma todos los disparadores por interacción pendientes. */
|
|
5497
|
+
disarmTriggers() {
|
|
5498
|
+
for (const disarm of this.armedTriggers.values()) disarm();
|
|
5499
|
+
this.armedTriggers.clear();
|
|
5413
5500
|
}
|
|
5414
5501
|
/**
|
|
5415
5502
|
* `resolve` con cache de corta duración + coalescing:
|
|
@@ -5460,6 +5547,7 @@ ${pageUrl}`;
|
|
|
5460
5547
|
this.activeWalkthroughRenderer = null;
|
|
5461
5548
|
this.activeWalkthroughGuide = null;
|
|
5462
5549
|
}
|
|
5550
|
+
this.disarmTriggers();
|
|
5463
5551
|
this.dispatched.clear();
|
|
5464
5552
|
this.resolveCache = null;
|
|
5465
5553
|
this.inflightResolve = null;
|
|
@@ -5492,8 +5580,11 @@ ${pageUrl}`;
|
|
|
5492
5580
|
this.client.track(eventName, properties);
|
|
5493
5581
|
};
|
|
5494
5582
|
const onFormSubmit = (answers) => this.submitFormResponse(guide.guideId, answers);
|
|
5583
|
+
const onOpenGuide = (targetGuideId) => {
|
|
5584
|
+
void this.openGuideById(targetGuideId, sessionId, pageUrl);
|
|
5585
|
+
};
|
|
5495
5586
|
try {
|
|
5496
|
-
void renderer.render({ guide, onInteraction, onClose, onTrack, onFormSubmit });
|
|
5587
|
+
void renderer.render({ guide, onInteraction, onClose, onTrack, onFormSubmit, onOpenGuide });
|
|
5497
5588
|
} catch (err) {
|
|
5498
5589
|
if (this.debug) console.error("[veo] renderer.render threw:", err);
|
|
5499
5590
|
this.activeRenderers.delete(renderer);
|
|
@@ -5502,6 +5593,28 @@ ${pageUrl}`;
|
|
|
5502
5593
|
}
|
|
5503
5594
|
}
|
|
5504
5595
|
}
|
|
5596
|
+
/**
|
|
5597
|
+
* Abre una guía POR ID, disparada desde el botón de otra guía (encadenado).
|
|
5598
|
+
* Ignora las URL rules y el trigger (el autor lo pidió explícitamente desde
|
|
5599
|
+
* un botón); respeta el dedupe de guías activas y el flujo normal de
|
|
5600
|
+
* tracking. 404/red → no-op (la guía destino pudo borrarse).
|
|
5601
|
+
*/
|
|
5602
|
+
async openGuideById(guideId, sessionId, pageUrl) {
|
|
5603
|
+
if (this.activeByGuideId.has(guideId)) return;
|
|
5604
|
+
const guide = await this.resolver.fetchById(guideId);
|
|
5605
|
+
if (!guide) {
|
|
5606
|
+
if (this.debug) console.warn(`[veo] guides: chained guide ${guideId} not found`);
|
|
5607
|
+
return;
|
|
5608
|
+
}
|
|
5609
|
+
const endUserId = this.client.getEndUserId() ?? this.client.getAnonymousId();
|
|
5610
|
+
if (guide.guideType === "walkthrough" && guide.guideSteps.length > 1) {
|
|
5611
|
+
if (!this.walkthroughState.hasActive()) {
|
|
5612
|
+
await this.startWalkthrough(guide, endUserId, sessionId, pageUrl);
|
|
5613
|
+
}
|
|
5614
|
+
return;
|
|
5615
|
+
}
|
|
5616
|
+
this.runSingleStepRender(guide, sessionId, pageUrl);
|
|
5617
|
+
}
|
|
5505
5618
|
/**
|
|
5506
5619
|
* Si hay un walkthrough persistido, intenta pintar el step actual en
|
|
5507
5620
|
* la página actual. Retorna `true` si lo intentó (sea éxito o cancel),
|