veo-sdk 0.1.0 → 0.2.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.
@@ -0,0 +1,2072 @@
1
+ 'use strict';
2
+
3
+ var dom = require('@floating-ui/dom');
4
+
5
+ // src/utils/safe-env.ts
6
+ function hasWindow() {
7
+ return typeof window !== "undefined";
8
+ }
9
+ function hasDocument() {
10
+ return typeof document !== "undefined";
11
+ }
12
+
13
+ // src/plugins/guides/constants.ts
14
+ var GUIDE_Z_INDEX = 2147483640;
15
+ var DEFAULT_ANCHOR_WAIT_MS = 5e3;
16
+ var SAFE_URL_PROTOCOLS = ["http:", "https:"];
17
+ var GUIDE_HOST_ATTR = "data-veo-guide";
18
+ var WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS = 5e3;
19
+
20
+ // src/plugins/guides/block-builder.ts
21
+ function buildStepContent(step, doc, callbacks) {
22
+ const container = doc.createElement("div");
23
+ container.className = "veo-guide-content";
24
+ if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
25
+ const img = doc.createElement("img");
26
+ img.className = "veo-guide-image";
27
+ img.src = step.imageUrl;
28
+ img.alt = typeof step.title === "string" ? step.title : "";
29
+ container.appendChild(img);
30
+ }
31
+ if (typeof step.title === "string" && step.title) {
32
+ const heading = doc.createElement("h2");
33
+ heading.className = "veo-guide-title";
34
+ heading.textContent = step.title;
35
+ container.appendChild(heading);
36
+ }
37
+ if (typeof step.content === "string" && step.content) {
38
+ const paragraph = doc.createElement("p");
39
+ paragraph.className = "veo-guide-text";
40
+ paragraph.textContent = step.content;
41
+ container.appendChild(paragraph);
42
+ }
43
+ const actions = doc.createElement("div");
44
+ actions.className = "veo-guide-actions";
45
+ if (typeof step.ctaText === "string" && step.ctaText) {
46
+ const cta = doc.createElement("button");
47
+ cta.type = "button";
48
+ cta.className = "veo-guide-cta";
49
+ cta.textContent = step.ctaText;
50
+ cta.addEventListener("click", () => {
51
+ const action = step.ctaAction ?? "dismiss";
52
+ const ctaUrl = readCtaUrl(step);
53
+ callbacks.onCtaClick(action, ctaUrl);
54
+ });
55
+ actions.appendChild(cta);
56
+ }
57
+ container.appendChild(actions);
58
+ const closeBtn = doc.createElement("button");
59
+ closeBtn.type = "button";
60
+ closeBtn.className = "veo-guide-close";
61
+ closeBtn.setAttribute("aria-label", "Cerrar");
62
+ closeBtn.textContent = "\xD7";
63
+ closeBtn.addEventListener("click", () => callbacks.onDismiss());
64
+ container.appendChild(closeBtn);
65
+ return container;
66
+ }
67
+ function isSafeUrl(url) {
68
+ if (typeof url !== "string" || url.length === 0) return false;
69
+ try {
70
+ const base = typeof window !== "undefined" ? window.location.href : "http://localhost/";
71
+ const parsed = new URL(url, base);
72
+ return SAFE_URL_PROTOCOLS.includes(parsed.protocol);
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+ function readCtaUrl(step) {
78
+ const candidate = step.style?.ctaUrl;
79
+ if (typeof candidate !== "string") return void 0;
80
+ return isSafeUrl(candidate) ? candidate : void 0;
81
+ }
82
+
83
+ // src/plugins/guides/guide-design.ts
84
+ var DARK_THEME = {
85
+ "--veo-bg": "#1f2937",
86
+ "--veo-text": "#f9fafb",
87
+ "--veo-text-secondary": "#d1d5db",
88
+ "--veo-shadow": "0 20px 60px rgba(0, 0, 0, 0.55)"
89
+ };
90
+ var ALIGN_JUSTIFY = {
91
+ left: "flex-start",
92
+ center: "center",
93
+ right: "flex-end"
94
+ };
95
+ var SHADOW = {
96
+ none: "none",
97
+ soft: "0 4px 14px rgba(0, 0, 0, 0.10)",
98
+ medium: "0 8px 30px rgba(0, 0, 0, 0.18)",
99
+ strong: "0 24px 60px rgba(0, 0, 0, 0.32)"
100
+ };
101
+ var PRESET_POS = {
102
+ center: [0.5, 0.5],
103
+ top: [0.5, 0],
104
+ bottom: [0.5, 1],
105
+ left: [0, 0.5],
106
+ right: [1, 0.5],
107
+ "top-left": [0, 0],
108
+ "top-right": [1, 0],
109
+ "bottom-left": [0, 1],
110
+ "bottom-right": [1, 1]
111
+ };
112
+ var COLOR_RE = /^#[0-9a-f]{3,8}$|^(rgb|rgba|hsl|hsla)\([\d.,%/\sdeg]+\)$|^[a-z]{3,20}$/i;
113
+ function clamp(n, min, max) {
114
+ return Math.min(max, Math.max(min, n));
115
+ }
116
+ function applyDesignVars(host, style) {
117
+ if (!style || typeof style !== "object") return;
118
+ const s = style;
119
+ if (typeof s.accentColor === "string" && COLOR_RE.test(s.accentColor.trim())) {
120
+ host.style.setProperty("--veo-primary", s.accentColor.trim());
121
+ }
122
+ if (s.theme === "dark") {
123
+ for (const [key, value] of Object.entries(DARK_THEME)) {
124
+ host.style.setProperty(key, value);
125
+ }
126
+ }
127
+ if (typeof s.radius === "number" && Number.isFinite(s.radius)) {
128
+ host.style.setProperty("--veo-radius", `${clamp(s.radius, 0, 48)}px`);
129
+ }
130
+ if (typeof s.width === "number" && Number.isFinite(s.width)) {
131
+ host.style.setProperty("--veo-width", `${clamp(s.width, 220, 720)}px`);
132
+ }
133
+ if (s.align === "left" || s.align === "center" || s.align === "right") {
134
+ host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[s.align]);
135
+ }
136
+ if (typeof s.padding === "number" && Number.isFinite(s.padding)) {
137
+ host.style.setProperty("--veo-pad", `${clamp(s.padding, 0, 64)}px`);
138
+ }
139
+ if (typeof s.margin === "number" && Number.isFinite(s.margin)) {
140
+ host.style.setProperty("--veo-margin", `${clamp(s.margin, 0, 64)}px`);
141
+ }
142
+ if (typeof s.borderWidth === "number" && Number.isFinite(s.borderWidth)) {
143
+ host.style.setProperty("--veo-border-width", `${clamp(s.borderWidth, 0, 16)}px`);
144
+ }
145
+ if (typeof s.borderColor === "string" && COLOR_RE.test(s.borderColor.trim())) {
146
+ host.style.setProperty("--veo-border-color", s.borderColor.trim());
147
+ }
148
+ if (typeof s.shadow === "string" && SHADOW[s.shadow]) {
149
+ host.style.setProperty("--veo-shadow", SHADOW[s.shadow]);
150
+ }
151
+ let px;
152
+ let py;
153
+ if (typeof s.posX === "number" && typeof s.posY === "number") {
154
+ px = clamp(s.posX, 0, 1);
155
+ py = clamp(s.posY, 0, 1);
156
+ } else if (typeof s.overlayPosition === "string" && PRESET_POS[s.overlayPosition]) {
157
+ [px, py] = PRESET_POS[s.overlayPosition];
158
+ }
159
+ if (px !== void 0 && py !== void 0) {
160
+ host.style.setProperty("--veo-pos-x", `${px * 100}%`);
161
+ host.style.setProperty("--veo-pos-y", `${py * 100}%`);
162
+ }
163
+ }
164
+
165
+ // src/plugins/guides/styles.ts
166
+ var GUIDE_STYLES = `
167
+ :host {
168
+ --veo-primary: #4f46e5;
169
+ --veo-text: #1f2937;
170
+ --veo-text-secondary: #4b5563;
171
+ --veo-bg: #ffffff;
172
+ --veo-radius: 12px;
173
+ --veo-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
174
+ --veo-width: 420px;
175
+ --veo-actions-justify: flex-end;
176
+ all: initial;
177
+ }
178
+ * { box-sizing: border-box; font-family: -apple-system, system-ui, "Segoe UI", Roboto, sans-serif; }
179
+
180
+ .veo-modal-overlay {
181
+ position: fixed; inset: 0;
182
+ background: rgba(15, 15, 30, 0.45);
183
+ z-index: ${GUIDE_Z_INDEX};
184
+ animation: veo-fade-in 180ms ease-out;
185
+ }
186
+ /*
187
+ * Posici\xF3n libre/preset: --veo-pos-x/y son porcentajes (default 50% = centro).
188
+ * El truco translate(-pos) alinea la MISMA fracci\xF3n de la tarjeta con esa
189
+ * fracci\xF3n del overlay, as\xED la tarjeta nunca se sale (0% = pegada izquierda,
190
+ * 100% = pegada derecha, 50% = centrada) sea cual sea su ancho.
191
+ */
192
+ .veo-modal-card {
193
+ position: absolute;
194
+ left: var(--veo-pos-x, 50%);
195
+ top: var(--veo-pos-y, 50%);
196
+ transform: translate(calc(var(--veo-pos-x, 50%) * -1), calc(var(--veo-pos-y, 50%) * -1));
197
+ background: var(--veo-bg);
198
+ border-radius: var(--veo-radius);
199
+ border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
200
+ padding: var(--veo-pad, 24px);
201
+ max-width: var(--veo-width); width: 90%;
202
+ box-shadow: var(--veo-shadow);
203
+ animation: veo-fade-in 180ms ease-out;
204
+ }
205
+
206
+ .veo-banner {
207
+ position: fixed; left: 0; right: 0;
208
+ background: var(--veo-bg);
209
+ border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
210
+ padding: var(--veo-pad, 16px);
211
+ z-index: ${GUIDE_Z_INDEX};
212
+ box-shadow: var(--veo-shadow, 0 2px 12px rgba(0, 0, 0, 0.12));
213
+ animation: veo-slide-down 200ms ease-out;
214
+ }
215
+ .veo-banner-top { top: 0; }
216
+ .veo-banner-bottom { bottom: 0; }
217
+
218
+ .veo-tooltip {
219
+ position: absolute;
220
+ background: var(--veo-bg);
221
+ border-radius: var(--veo-radius);
222
+ border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
223
+ padding: var(--veo-pad, 15px);
224
+ max-width: 300px;
225
+ z-index: ${GUIDE_Z_INDEX};
226
+ box-shadow: var(--veo-shadow, 0 8px 30px rgba(0, 0, 0, 0.18));
227
+ animation: veo-fade-in 150ms ease-out;
228
+ }
229
+
230
+ .veo-inline {
231
+ background: var(--veo-bg);
232
+ border-radius: var(--veo-radius);
233
+ border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
234
+ padding: var(--veo-pad, 20px);
235
+ max-width: var(--veo-width);
236
+ margin: var(--veo-margin, 12px) 0;
237
+ position: relative;
238
+ box-shadow: var(--veo-shadow);
239
+ animation: veo-fade-in 160ms ease-out;
240
+ }
241
+
242
+ .veo-guide-content {
243
+ position: relative;
244
+ display: flex; flex-direction: column;
245
+ }
246
+ .veo-guide-image {
247
+ display: block; width: 100%; max-height: 180px;
248
+ object-fit: cover; border-radius: 8px;
249
+ margin-bottom: 12px;
250
+ }
251
+ .veo-guide-title {
252
+ font-size: 18px; font-weight: 600; line-height: 1.3;
253
+ margin: 0 0 6px; color: var(--veo-text);
254
+ }
255
+ .veo-guide-text {
256
+ font-size: 14px; line-height: 1.5;
257
+ margin: 0 0 16px; color: var(--veo-text-secondary);
258
+ }
259
+ .veo-guide-actions {
260
+ display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
261
+ }
262
+ .veo-guide-cta {
263
+ background: var(--veo-primary); color: #fff; border: none;
264
+ padding: 9px 18px; border-radius: 8px;
265
+ font-size: 14px; font-weight: 500; cursor: pointer;
266
+ transition: opacity 120ms ease;
267
+ }
268
+ .veo-guide-cta:hover { opacity: 0.9; }
269
+ .veo-guide-cta:active { transform: translateY(1px); }
270
+ .veo-guide-close {
271
+ position: absolute; top: -6px; right: -6px;
272
+ width: 24px; height: 24px;
273
+ background: none; border: none;
274
+ font-size: 22px; line-height: 1;
275
+ cursor: pointer; color: #9ca3af; padding: 0;
276
+ }
277
+ .veo-guide-close:hover { color: var(--veo-text); }
278
+
279
+ .veo-form { display: flex; flex-direction: column; gap: 14px; margin-bottom: 16px; }
280
+ .veo-form-field { display: flex; flex-direction: column; gap: 6px; }
281
+ .veo-form-label { font-size: 13px; font-weight: 500; color: var(--veo-text); }
282
+ .veo-form-input {
283
+ font-size: 14px; color: var(--veo-text);
284
+ background: var(--veo-bg);
285
+ border: 1px solid #d1d5db; border-radius: 8px;
286
+ padding: 8px 10px; width: 100%;
287
+ }
288
+ .veo-form-input:focus { outline: 2px solid var(--veo-primary); outline-offset: -1px; border-color: transparent; }
289
+ .veo-form-options { display: flex; flex-direction: column; gap: 6px; }
290
+ .veo-form-option {
291
+ display: flex; align-items: center; gap: 8px;
292
+ font-size: 14px; color: var(--veo-text); cursor: pointer;
293
+ }
294
+ .veo-form-option input { accent-color: var(--veo-primary); margin: 0; cursor: pointer; }
295
+ .veo-form-scale { display: flex; gap: 4px; flex-wrap: wrap; }
296
+ .veo-nps-btn {
297
+ min-width: 30px; height: 30px; padding: 0 4px;
298
+ font-size: 13px; font-weight: 500; color: var(--veo-text);
299
+ background: var(--veo-bg); border: 1px solid #d1d5db; border-radius: 6px;
300
+ cursor: pointer; transition: all 100ms ease;
301
+ }
302
+ .veo-nps-btn:hover { border-color: var(--veo-primary); }
303
+ .veo-rating-btn {
304
+ background: none; border: none; padding: 0 2px;
305
+ font-size: 26px; line-height: 1; color: #d1d5db;
306
+ cursor: pointer; transition: color 100ms ease;
307
+ }
308
+ .veo-scale-active.veo-nps-btn { background: var(--veo-primary); color: #fff; border-color: var(--veo-primary); }
309
+ .veo-scale-active.veo-rating-btn { color: #f59e0b; }
310
+ .veo-form-error { font-size: 13px; color: #dc2626; margin: 0; }
311
+ .veo-form-thanks {
312
+ text-align: center; padding: 24px 8px;
313
+ font-size: 16px; font-weight: 600; color: var(--veo-text);
314
+ }
315
+ .veo-form-preview-note {
316
+ text-align: center; margin: 0 0 12px;
317
+ font-size: 12px; color: #b45309;
318
+ }
319
+
320
+ .veo-walkthrough {
321
+ position: absolute;
322
+ background: var(--veo-bg);
323
+ border-radius: var(--veo-radius);
324
+ border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
325
+ padding: var(--veo-pad, 16px);
326
+ max-width: 340px;
327
+ z-index: ${GUIDE_Z_INDEX};
328
+ box-shadow: var(--veo-shadow, 0 8px 30px rgba(0, 0, 0, 0.2));
329
+ animation: veo-fade-in 150ms ease-out;
330
+ }
331
+ .veo-walkthrough-arrow {
332
+ position: absolute;
333
+ width: 12px;
334
+ height: 12px;
335
+ background: var(--veo-bg);
336
+ transform: rotate(45deg);
337
+ pointer-events: none;
338
+ }
339
+ .veo-walkthrough-counter {
340
+ font-size: 11px; color: #9ca3af;
341
+ margin-bottom: 8px; letter-spacing: 0.02em;
342
+ }
343
+ .veo-walkthrough-progress {
344
+ display: flex; gap: 4px;
345
+ margin-bottom: 12px;
346
+ }
347
+ .veo-walkthrough-progress-dot {
348
+ width: 8px; height: 8px;
349
+ border-radius: 50%;
350
+ background: #e5e7eb;
351
+ transition: background 200ms ease;
352
+ }
353
+ .veo-walkthrough-progress-dot.active { background: var(--veo-primary); }
354
+ .veo-walkthrough-progress-dot.completed { background: var(--veo-primary); opacity: 0.5; }
355
+ .veo-walkthrough-actions {
356
+ display: flex; align-items: center;
357
+ margin-top: 16px; gap: 8px;
358
+ }
359
+ .veo-walkthrough-actions-right {
360
+ display: flex; gap: 8px;
361
+ margin-left: auto;
362
+ }
363
+ .veo-walkthrough-btn-secondary {
364
+ background: transparent; color: #6b7280;
365
+ border: 1px solid #e5e7eb;
366
+ padding: 8px 16px; border-radius: 8px;
367
+ font-size: 13px; cursor: pointer;
368
+ transition: background 120ms ease;
369
+ }
370
+ .veo-walkthrough-btn-secondary:hover { background: #f9fafb; }
371
+ .veo-walkthrough-skip {
372
+ background: transparent; color: #9ca3af;
373
+ border: none; padding: 4px 8px;
374
+ font-size: 12px; cursor: pointer;
375
+ }
376
+ .veo-walkthrough-skip:hover { color: var(--veo-text); }
377
+
378
+ .veo-custom-floating {
379
+ position: fixed;
380
+ z-index: ${GUIDE_Z_INDEX};
381
+ max-width: calc(100vw - 16px);
382
+ max-height: calc(100vh - 16px);
383
+ animation: veo-fade-in 160ms ease;
384
+ }
385
+ .veo-custom-anchored {
386
+ position: fixed;
387
+ top: 0; left: 0;
388
+ z-index: ${GUIDE_Z_INDEX};
389
+ max-width: calc(100vw - 16px);
390
+ animation: veo-fade-in 160ms ease;
391
+ }
392
+ .veo-custom-inline {
393
+ display: block;
394
+ margin: 12px 0;
395
+ }
396
+ .veo-custom-frame {
397
+ display: block;
398
+ border: 0;
399
+ width: 360px;
400
+ background: transparent;
401
+ color-scheme: normal;
402
+ }
403
+
404
+ @keyframes veo-fade-in { from { opacity: 0; } to { opacity: 1; } }
405
+ @keyframes veo-slide-up {
406
+ from { transform: translateY(10px); opacity: 0; }
407
+ to { transform: translateY(0); opacity: 1; }
408
+ }
409
+ @keyframes veo-slide-down {
410
+ from { transform: translateY(-10px); opacity: 0; }
411
+ to { transform: translateY(0); opacity: 1; }
412
+ }
413
+ `;
414
+
415
+ // src/plugins/guides/renderers/base-renderer.ts
416
+ var BaseRenderer = class {
417
+ constructor() {
418
+ this.host = null;
419
+ this.shadow = null;
420
+ this.cleanups = [];
421
+ }
422
+ /**
423
+ * Crea un `<div data-veo-guide>` adjunto a `document.body`, le adjunta
424
+ * un ShadowRoot abierto e inyecta los estilos. Retorna el nodo raíz
425
+ * donde la subclase puede insertar el contenido específico.
426
+ */
427
+ createHost() {
428
+ const host = document.createElement("div");
429
+ host.setAttribute(GUIDE_HOST_ATTR, "");
430
+ const shadow = host.attachShadow({ mode: "open" });
431
+ const style = document.createElement("style");
432
+ style.textContent = GUIDE_STYLES;
433
+ shadow.appendChild(style);
434
+ const root = document.createElement("div");
435
+ shadow.appendChild(root);
436
+ document.body.appendChild(host);
437
+ this.host = host;
438
+ this.shadow = shadow;
439
+ return { host, shadow, root };
440
+ }
441
+ /**
442
+ * Aplica la tematización por datos del step (`step.style`) como variables
443
+ * CSS sobre el host. Llamar tras `createHost()`. No-op si no hay host o
444
+ * style. Ver `guide-design.ts`.
445
+ */
446
+ applyDesign(style) {
447
+ if (this.host) applyDesignVars(this.host, style);
448
+ }
449
+ /**
450
+ * Registra una función a ejecutar en `destroy()`. Útil para limpiar
451
+ * listeners globales (scroll, resize) o intervalos.
452
+ */
453
+ registerCleanup(fn) {
454
+ this.cleanups.push(fn);
455
+ }
456
+ /** Remueve el host del DOM y corre todas las funciones de cleanup. */
457
+ destroy() {
458
+ for (const fn of this.cleanups) {
459
+ try {
460
+ fn();
461
+ } catch {
462
+ }
463
+ }
464
+ this.cleanups = [];
465
+ if (this.host?.parentNode) {
466
+ this.host.parentNode.removeChild(this.host);
467
+ }
468
+ this.host = null;
469
+ this.shadow = null;
470
+ }
471
+ };
472
+
473
+ // src/plugins/guides/renderers/banner-renderer.ts
474
+ var BannerRenderer = class extends BaseRenderer {
475
+ render(context) {
476
+ const step = context.guide.guideSteps[0];
477
+ if (!step) return;
478
+ const { root } = this.createHost();
479
+ this.applyDesign(step.style);
480
+ const ownerDocument = root.ownerDocument ?? document;
481
+ const position = step.style?.position === "bottom" ? "bottom" : "top";
482
+ const banner = ownerDocument.createElement("div");
483
+ banner.className = `veo-banner veo-banner-${position}`;
484
+ const dismiss = (action, ctaUrl) => {
485
+ context.onInteraction({
486
+ guideId: context.guide.guideId,
487
+ stepIndex: 0,
488
+ action
489
+ });
490
+ if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
491
+ context.onClose();
492
+ };
493
+ const content = buildStepContent(step, ownerDocument, {
494
+ onCtaClick: (action, url) => {
495
+ dismiss("cta_clicked", action === "url" ? url : void 0);
496
+ },
497
+ onDismiss: () => dismiss("dismissed")
498
+ });
499
+ banner.appendChild(content);
500
+ root.appendChild(banner);
501
+ context.onInteraction({
502
+ guideId: context.guide.guideId,
503
+ stepIndex: 0,
504
+ action: "shown"
505
+ });
506
+ }
507
+ };
508
+
509
+ // src/plugins/guides/wait-for-element.ts
510
+ function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
511
+ return new Promise((resolve) => {
512
+ const safeQuery = () => {
513
+ try {
514
+ return document.querySelector(selector);
515
+ } catch {
516
+ return null;
517
+ }
518
+ };
519
+ const existing = safeQuery();
520
+ if (existing) {
521
+ resolve(existing);
522
+ return;
523
+ }
524
+ let resolved = false;
525
+ const finish = (el) => {
526
+ if (resolved) return;
527
+ resolved = true;
528
+ observer.disconnect();
529
+ clearTimeout(timer);
530
+ resolve(el);
531
+ };
532
+ const observer = new MutationObserver(() => {
533
+ const el = safeQuery();
534
+ if (el) finish(el);
535
+ });
536
+ observer.observe(document.body, { childList: true, subtree: true });
537
+ const timer = setTimeout(() => finish(null), timeoutMs);
538
+ });
539
+ }
540
+
541
+ // src/utils/uuid.ts
542
+ function uuidv7() {
543
+ const timestamp = Date.now();
544
+ const timeHex = timestamp.toString(16).padStart(12, "0");
545
+ const random = new Uint8Array(10);
546
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
547
+ crypto.getRandomValues(random);
548
+ } else {
549
+ for (let i = 0; i < random.length; i++) {
550
+ random[i] = Math.floor(Math.random() * 256);
551
+ }
552
+ }
553
+ random[0] = (random[0] ?? 0) & 15 | 112;
554
+ random[2] = (random[2] ?? 0) & 63 | 128;
555
+ const hex = Array.from(random, (b) => b.toString(16).padStart(2, "0")).join("");
556
+ return [
557
+ timeHex.slice(0, 8),
558
+ timeHex.slice(8, 12),
559
+ hex.slice(0, 4),
560
+ hex.slice(4, 8),
561
+ hex.slice(8, 20)
562
+ ].join("-");
563
+ }
564
+
565
+ // src/plugins/guides/renderers/custom-frame.ts
566
+ function mountCustomFrame(doc, step, context, opts) {
567
+ const iframe = doc.createElement("iframe");
568
+ iframe.className = "veo-custom-frame";
569
+ iframe.setAttribute("sandbox", "allow-scripts");
570
+ iframe.setAttribute("title", context.guide.guideName || "Veo");
571
+ iframe.style.width = opts.width;
572
+ const hasFixedHeight = opts.fixedHeight !== void 0 && opts.fixedHeight !== null;
573
+ if (hasFixedHeight) iframe.style.height = toCssSize(opts.fixedHeight);
574
+ const token = uuidv7();
575
+ iframe.srcdoc = buildSrcdoc(step.html ?? "", step.css ?? "", step.js ?? "", token);
576
+ const close = () => context.onClose();
577
+ const onMessage = (event) => {
578
+ if (event.source !== iframe.contentWindow) return;
579
+ const data = event.data;
580
+ if (!data || typeof data !== "object" || data.__veo !== token) return;
581
+ switch (data.type) {
582
+ case "dismiss":
583
+ context.onInteraction({
584
+ guideId: context.guide.guideId,
585
+ stepIndex: 0,
586
+ action: "dismissed"
587
+ });
588
+ close();
589
+ break;
590
+ case "cta":
591
+ context.onInteraction({
592
+ guideId: context.guide.guideId,
593
+ stepIndex: 0,
594
+ action: "cta_clicked"
595
+ });
596
+ if (typeof data.url === "string" && isSafeUrl(data.url)) {
597
+ window.open(data.url, "_blank", "noopener,noreferrer");
598
+ }
599
+ if (data.close !== false) close();
600
+ break;
601
+ case "navigate":
602
+ if (typeof data.url === "string" && isSafeUrl(data.url)) {
603
+ window.location.assign(data.url);
604
+ }
605
+ break;
606
+ case "track":
607
+ if (typeof data.name === "string" && context.onTrack) {
608
+ context.onTrack(data.name, isRecord(data.props) ? data.props : void 0);
609
+ }
610
+ break;
611
+ case "resize":
612
+ if (!hasFixedHeight && typeof data.height === "number" && data.height > 0) {
613
+ iframe.style.height = `${Math.ceil(data.height)}px`;
614
+ }
615
+ break;
616
+ }
617
+ };
618
+ window.addEventListener("message", onMessage);
619
+ return { iframe, cleanup: () => window.removeEventListener("message", onMessage) };
620
+ }
621
+ function toCssSize(value) {
622
+ return typeof value === "number" ? `${value}px` : value;
623
+ }
624
+ function isRecord(value) {
625
+ return typeof value === "object" && value !== null && !Array.isArray(value);
626
+ }
627
+ function buildSrcdoc(html, css, js, token) {
628
+ 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){}})();`;
629
+ 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>";
630
+ }
631
+ function escapeClosing(code, tag) {
632
+ const rx = tag === "script" ? /<\/script/gi : /<\/style/gi;
633
+ return code.replace(rx, `<\\/${tag}`);
634
+ }
635
+
636
+ // src/plugins/guides/renderers/custom-renderer.ts
637
+ var DEFAULT_PLACEMENT = { mode: "floating", position: "bottom-right" };
638
+ var DEFAULT_OFFSET = 16;
639
+ var DEFAULT_WIDTH = 360;
640
+ var CustomRenderer = class extends BaseRenderer {
641
+ async render(context) {
642
+ const step = context.guide.guideSteps[0];
643
+ if (!step || typeof step.html !== "string" || step.html.length === 0) return;
644
+ const placement = step.placement ?? DEFAULT_PLACEMENT;
645
+ let anchor = null;
646
+ if (placement.mode === "anchored") {
647
+ const selector = placement.selector ?? step.selector ?? context.guide.activationRules.selector;
648
+ if (typeof selector !== "string" || selector.length === 0) return;
649
+ anchor = await waitForElement(selector);
650
+ if (!anchor) return;
651
+ }
652
+ const { root } = this.createHost();
653
+ const doc = root.ownerDocument ?? document;
654
+ const wrapper = doc.createElement("div");
655
+ wrapper.className = placement.mode === "anchored" ? "veo-custom-anchored" : "veo-custom-floating";
656
+ const { iframe, cleanup } = mountCustomFrame(doc, step, context, {
657
+ width: toCssSize(placement.width ?? DEFAULT_WIDTH),
658
+ fixedHeight: placement.height ?? null
659
+ });
660
+ this.registerCleanup(cleanup);
661
+ wrapper.appendChild(iframe);
662
+ root.appendChild(wrapper);
663
+ if (placement.mode === "floating") {
664
+ applyFloatingPosition(wrapper, placement);
665
+ } else if (anchor) {
666
+ this.registerCleanup(await this.setupAnchoredPosition(anchor, wrapper, placement));
667
+ }
668
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
669
+ }
670
+ async setupAnchoredPosition(anchor, wrapper, placement) {
671
+ const side = placement.side ?? "bottom";
672
+ const update = async () => {
673
+ const { x, y } = await dom.computePosition(anchor, wrapper, {
674
+ placement: side,
675
+ strategy: "fixed",
676
+ middleware: [dom.offset(placement.offsetY ?? 8), dom.flip(), dom.shift({ padding: 8 })]
677
+ });
678
+ wrapper.style.left = `${x}px`;
679
+ wrapper.style.top = `${y}px`;
680
+ };
681
+ await update();
682
+ const reposition = () => {
683
+ void update();
684
+ };
685
+ window.addEventListener("scroll", reposition, true);
686
+ window.addEventListener("resize", reposition);
687
+ return () => {
688
+ window.removeEventListener("scroll", reposition, true);
689
+ window.removeEventListener("resize", reposition);
690
+ };
691
+ }
692
+ };
693
+ function applyFloatingPosition(wrapper, placement) {
694
+ const position = placement.position ?? "bottom-right";
695
+ const ox = `${placement.offsetX ?? DEFAULT_OFFSET}px`;
696
+ const oy = `${placement.offsetY ?? DEFAULT_OFFSET}px`;
697
+ const s = wrapper.style;
698
+ if (position === "center") {
699
+ s.top = "50%";
700
+ s.left = "50%";
701
+ s.transform = "translate(-50%, -50%)";
702
+ return;
703
+ }
704
+ const [vertical, horizontal] = position.split("-");
705
+ if (vertical === "top") s.top = oy;
706
+ else s.bottom = oy;
707
+ if (horizontal === "left") {
708
+ s.left = ox;
709
+ } else if (horizontal === "right") {
710
+ s.right = ox;
711
+ } else {
712
+ s.left = "50%";
713
+ s.transform = "translateX(-50%)";
714
+ }
715
+ }
716
+
717
+ // src/plugins/guides/form-content.ts
718
+ function mountFormContent(card, context, doc) {
719
+ const step = context.guide.guideSteps[0];
720
+ if (!step) return;
721
+ const fields = step.fields ?? [];
722
+ const dismiss = () => {
723
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
724
+ context.onClose();
725
+ };
726
+ const content = doc.createElement("div");
727
+ content.className = "veo-guide-content";
728
+ if (step.title) {
729
+ const heading = doc.createElement("h2");
730
+ heading.className = "veo-guide-title";
731
+ heading.textContent = step.title;
732
+ content.appendChild(heading);
733
+ }
734
+ if (step.content) {
735
+ const paragraph = doc.createElement("p");
736
+ paragraph.className = "veo-guide-text";
737
+ paragraph.textContent = step.content;
738
+ content.appendChild(paragraph);
739
+ }
740
+ const readers = [];
741
+ const form = doc.createElement("form");
742
+ form.className = "veo-form";
743
+ form.noValidate = true;
744
+ for (const field of fields) {
745
+ const { wrapper, read } = buildField(field, doc);
746
+ readers.push({ field, read });
747
+ form.appendChild(wrapper);
748
+ }
749
+ const error = doc.createElement("p");
750
+ error.className = "veo-form-error";
751
+ error.style.display = "none";
752
+ form.appendChild(error);
753
+ const actions = doc.createElement("div");
754
+ actions.className = "veo-guide-actions";
755
+ const submit = doc.createElement("button");
756
+ submit.type = "submit";
757
+ submit.className = "veo-guide-cta";
758
+ submit.textContent = step.ctaText || "Enviar";
759
+ actions.appendChild(submit);
760
+ form.appendChild(actions);
761
+ form.addEventListener("submit", (e) => {
762
+ e.preventDefault();
763
+ error.style.display = "none";
764
+ const answers = {};
765
+ for (const { field, read } of readers) {
766
+ const value = read();
767
+ const empty = value === void 0 || value === null || value === "";
768
+ if (empty) {
769
+ if (field.required) {
770
+ error.textContent = `Complet\xE1 "${field.label}"`;
771
+ error.style.display = "block";
772
+ return;
773
+ }
774
+ continue;
775
+ }
776
+ answers[field.key] = value;
777
+ }
778
+ if (Object.keys(answers).length === 0) {
779
+ error.textContent = "Respond\xE9 al menos un campo";
780
+ error.style.display = "block";
781
+ return;
782
+ }
783
+ submit.disabled = true;
784
+ const submitFn = context.onFormSubmit ?? (() => Promise.resolve(true));
785
+ void submitFn(answers).then((ok) => {
786
+ if (!ok) {
787
+ submit.disabled = false;
788
+ error.textContent = "No se pudo enviar. Prob\xE1 de nuevo.";
789
+ error.style.display = "block";
790
+ return;
791
+ }
792
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "completed" });
793
+ const note = context.isPreview ? context.previewNote ?? "Vista previa: la respuesta NO se guard\xF3." : null;
794
+ showThanks(card, doc, note);
795
+ window.setTimeout(() => context.onClose(), note ? 2200 : 1400);
796
+ });
797
+ });
798
+ content.appendChild(form);
799
+ const closeBtn = doc.createElement("button");
800
+ closeBtn.type = "button";
801
+ closeBtn.className = "veo-guide-close";
802
+ closeBtn.setAttribute("aria-label", "Cerrar");
803
+ closeBtn.textContent = "\xD7";
804
+ closeBtn.addEventListener("click", dismiss);
805
+ content.appendChild(closeBtn);
806
+ card.appendChild(content);
807
+ }
808
+ function showThanks(card, doc, note) {
809
+ while (card.firstChild) card.removeChild(card.firstChild);
810
+ const thanks = doc.createElement("div");
811
+ thanks.className = "veo-form-thanks";
812
+ thanks.textContent = "\u2713 \xA1Gracias por tu respuesta!";
813
+ card.appendChild(thanks);
814
+ if (note) {
815
+ const noteEl = doc.createElement("p");
816
+ noteEl.className = "veo-form-preview-note";
817
+ noteEl.textContent = note;
818
+ card.appendChild(noteEl);
819
+ }
820
+ }
821
+ function buildField(field, doc) {
822
+ const wrapper = doc.createElement("div");
823
+ wrapper.className = "veo-form-field";
824
+ const label = doc.createElement("label");
825
+ label.className = "veo-form-label";
826
+ label.textContent = field.required ? `${field.label} *` : field.label;
827
+ wrapper.appendChild(label);
828
+ switch (field.type) {
829
+ case "textarea": {
830
+ const input = doc.createElement("textarea");
831
+ input.className = "veo-form-input";
832
+ input.rows = 3;
833
+ if (field.placeholder) input.placeholder = field.placeholder;
834
+ wrapper.appendChild(input);
835
+ return { wrapper, read: () => input.value.trim() };
836
+ }
837
+ case "number": {
838
+ const input = doc.createElement("input");
839
+ input.type = "number";
840
+ input.className = "veo-form-input";
841
+ if (field.placeholder) input.placeholder = field.placeholder;
842
+ wrapper.appendChild(input);
843
+ return {
844
+ wrapper,
845
+ read: () => input.value.trim() === "" ? void 0 : Number(input.value)
846
+ };
847
+ }
848
+ case "select": {
849
+ const select = doc.createElement("select");
850
+ select.className = "veo-form-input";
851
+ const blank = doc.createElement("option");
852
+ blank.value = "";
853
+ blank.textContent = field.placeholder || "Eleg\xED una opci\xF3n\u2026";
854
+ select.appendChild(blank);
855
+ for (const opt of field.options ?? []) {
856
+ const option = doc.createElement("option");
857
+ option.value = opt;
858
+ option.textContent = opt;
859
+ select.appendChild(option);
860
+ }
861
+ wrapper.appendChild(select);
862
+ return { wrapper, read: () => select.value || void 0 };
863
+ }
864
+ case "radio": {
865
+ const group = doc.createElement("div");
866
+ group.className = "veo-form-options";
867
+ const name = `veo-${field.key}`;
868
+ const inputs = [];
869
+ for (const opt of field.options ?? []) {
870
+ const optionLabel = doc.createElement("label");
871
+ optionLabel.className = "veo-form-option";
872
+ const radio = doc.createElement("input");
873
+ radio.type = "radio";
874
+ radio.name = name;
875
+ radio.value = opt;
876
+ inputs.push(radio);
877
+ const text = doc.createElement("span");
878
+ text.textContent = opt;
879
+ optionLabel.appendChild(radio);
880
+ optionLabel.appendChild(text);
881
+ group.appendChild(optionLabel);
882
+ }
883
+ wrapper.appendChild(group);
884
+ return { wrapper, read: () => inputs.find((i) => i.checked)?.value };
885
+ }
886
+ case "multiselect": {
887
+ const group = doc.createElement("div");
888
+ group.className = "veo-form-options";
889
+ const inputs = [];
890
+ for (const opt of field.options ?? []) {
891
+ const optionLabel = doc.createElement("label");
892
+ optionLabel.className = "veo-form-option";
893
+ const checkbox = doc.createElement("input");
894
+ checkbox.type = "checkbox";
895
+ checkbox.value = opt;
896
+ inputs.push(checkbox);
897
+ const text = doc.createElement("span");
898
+ text.textContent = opt;
899
+ optionLabel.appendChild(checkbox);
900
+ optionLabel.appendChild(text);
901
+ group.appendChild(optionLabel);
902
+ }
903
+ wrapper.appendChild(group);
904
+ return {
905
+ wrapper,
906
+ read: () => {
907
+ const values = inputs.filter((i) => i.checked).map((i) => i.value);
908
+ return values.length > 0 ? values : void 0;
909
+ }
910
+ };
911
+ }
912
+ case "checkbox": {
913
+ const optionLabel = doc.createElement("label");
914
+ optionLabel.className = "veo-form-option";
915
+ const checkbox = doc.createElement("input");
916
+ checkbox.type = "checkbox";
917
+ const text = doc.createElement("span");
918
+ text.textContent = field.placeholder || "S\xED";
919
+ optionLabel.appendChild(checkbox);
920
+ optionLabel.appendChild(text);
921
+ wrapper.appendChild(optionLabel);
922
+ return { wrapper, read: () => checkbox.checked ? true : field.required ? "" : false };
923
+ }
924
+ case "yesno": {
925
+ const group = doc.createElement("div");
926
+ group.className = "veo-form-options";
927
+ const name = `veo-${field.key}`;
928
+ const [yesLabel, noLabel] = field.options?.length === 2 ? field.options : ["S\xED", "No"];
929
+ const inputs = [];
930
+ for (const { label: optText, value } of [
931
+ { label: yesLabel, value: true },
932
+ { label: noLabel, value: false }
933
+ ]) {
934
+ const optionLabel = doc.createElement("label");
935
+ optionLabel.className = "veo-form-option";
936
+ const radio = doc.createElement("input");
937
+ radio.type = "radio";
938
+ radio.name = name;
939
+ inputs.push({ input: radio, value });
940
+ const text = doc.createElement("span");
941
+ text.textContent = optText;
942
+ optionLabel.appendChild(radio);
943
+ optionLabel.appendChild(text);
944
+ group.appendChild(optionLabel);
945
+ }
946
+ wrapper.appendChild(group);
947
+ return { wrapper, read: () => inputs.find((i) => i.input.checked)?.value };
948
+ }
949
+ case "nps":
950
+ return buildScale(wrapper, doc, 0, 10, "veo-nps-btn");
951
+ case "rating":
952
+ return buildScale(wrapper, doc, 1, 5, "veo-rating-btn", "\u2605");
953
+ default: {
954
+ const input = doc.createElement("input");
955
+ input.type = "text";
956
+ input.className = "veo-form-input";
957
+ if (field.placeholder) input.placeholder = field.placeholder;
958
+ wrapper.appendChild(input);
959
+ return { wrapper, read: () => input.value.trim() };
960
+ }
961
+ }
962
+ }
963
+ function buildScale(wrapper, doc, min, max, btnClass, symbol) {
964
+ const row = doc.createElement("div");
965
+ row.className = "veo-form-scale";
966
+ let selected;
967
+ const buttons = [];
968
+ for (let n = min; n <= max; n++) {
969
+ const btn = doc.createElement("button");
970
+ btn.type = "button";
971
+ btn.className = btnClass;
972
+ btn.textContent = symbol ?? String(n);
973
+ btn.setAttribute("aria-label", String(n));
974
+ btn.addEventListener("click", () => {
975
+ selected = n;
976
+ buttons.forEach((b, i) => {
977
+ const active = symbol ? i + min <= n : i + min === n;
978
+ b.classList.toggle("veo-scale-active", active);
979
+ });
980
+ });
981
+ buttons.push(btn);
982
+ row.appendChild(btn);
983
+ }
984
+ wrapper.appendChild(row);
985
+ return { wrapper, read: () => selected };
986
+ }
987
+
988
+ // src/plugins/guides/renderers/form-renderer.ts
989
+ var FormRenderer = class extends BaseRenderer {
990
+ render(context) {
991
+ const step = context.guide.guideSteps[0];
992
+ if (!step || (step.fields ?? []).length === 0) return;
993
+ const { root } = this.createHost();
994
+ this.applyDesign(step.style);
995
+ const doc = root.ownerDocument ?? document;
996
+ const overlay = doc.createElement("div");
997
+ overlay.className = "veo-modal-overlay";
998
+ const card = doc.createElement("div");
999
+ card.className = "veo-modal-card";
1000
+ mountFormContent(card, context, doc);
1001
+ overlay.appendChild(card);
1002
+ root.appendChild(overlay);
1003
+ const dismiss = () => {
1004
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "dismissed" });
1005
+ context.onClose();
1006
+ };
1007
+ overlay.addEventListener("click", (e) => {
1008
+ if (e.target === overlay) dismiss();
1009
+ });
1010
+ const onKey = (e) => {
1011
+ if (e.key === "Escape") dismiss();
1012
+ };
1013
+ document.addEventListener("keydown", onKey);
1014
+ this.registerCleanup(() => document.removeEventListener("keydown", onKey));
1015
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1016
+ }
1017
+ };
1018
+
1019
+ // src/plugins/guides/inline-host.ts
1020
+ function readInlinePosition(style) {
1021
+ const p = style?.inlinePosition;
1022
+ return p === "before" || p === "prepend" || p === "append" ? p : "after";
1023
+ }
1024
+ function insertHost(anchor, host, position) {
1025
+ switch (position) {
1026
+ case "before":
1027
+ anchor.before(host);
1028
+ break;
1029
+ case "after":
1030
+ anchor.after(host);
1031
+ break;
1032
+ case "prepend":
1033
+ anchor.prepend(host);
1034
+ break;
1035
+ case "append":
1036
+ anchor.append(host);
1037
+ break;
1038
+ }
1039
+ }
1040
+ function keepHostAttached(host, selector, position) {
1041
+ const id = window.setInterval(() => {
1042
+ if (host.isConnected) return;
1043
+ const anchor = document.querySelector(selector);
1044
+ if (anchor) insertHost(anchor, host, position);
1045
+ }, 1e3);
1046
+ return () => window.clearInterval(id);
1047
+ }
1048
+
1049
+ // src/plugins/guides/renderers/inline-custom-renderer.ts
1050
+ var InlineCustomRenderer = class extends BaseRenderer {
1051
+ async render(context) {
1052
+ const step = context.guide.guideSteps[0];
1053
+ if (!step || typeof step.html !== "string" || step.html.length === 0) return;
1054
+ const selector = step.selector ?? context.guide.activationRules.selector;
1055
+ if (typeof selector !== "string" || selector.length === 0) return;
1056
+ const anchor = await waitForElement(selector);
1057
+ if (!anchor) return;
1058
+ const { host, root } = this.createHost();
1059
+ host.style.display = "block";
1060
+ const position = readInlinePosition(step.style);
1061
+ insertHost(anchor, host, position);
1062
+ this.registerCleanup(keepHostAttached(host, selector, position));
1063
+ const doc = root.ownerDocument ?? document;
1064
+ const wrapper = doc.createElement("div");
1065
+ wrapper.className = "veo-custom-inline";
1066
+ const { iframe, cleanup } = mountCustomFrame(doc, step, context, { width: "100%" });
1067
+ this.registerCleanup(cleanup);
1068
+ wrapper.appendChild(iframe);
1069
+ root.appendChild(wrapper);
1070
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1071
+ }
1072
+ };
1073
+
1074
+ // src/plugins/guides/renderers/inline-form-renderer.ts
1075
+ var InlineFormRenderer = class extends BaseRenderer {
1076
+ async render(context) {
1077
+ const step = context.guide.guideSteps[0];
1078
+ if (!step || (step.fields ?? []).length === 0) return;
1079
+ const selector = step.selector ?? context.guide.activationRules.selector;
1080
+ if (typeof selector !== "string" || selector.length === 0) return;
1081
+ const anchor = await waitForElement(selector);
1082
+ if (!anchor) return;
1083
+ const { host, root } = this.createHost();
1084
+ host.style.display = "block";
1085
+ const position = readInlinePosition(step.style);
1086
+ insertHost(anchor, host, position);
1087
+ this.registerCleanup(keepHostAttached(host, selector, position));
1088
+ this.applyDesign(step.style);
1089
+ const doc = root.ownerDocument ?? document;
1090
+ const card = doc.createElement("div");
1091
+ card.className = "veo-inline";
1092
+ mountFormContent(card, context, doc);
1093
+ root.appendChild(card);
1094
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1095
+ }
1096
+ };
1097
+
1098
+ // src/plugins/guides/renderers/inline-renderer.ts
1099
+ var InlineRenderer = class extends BaseRenderer {
1100
+ async render(context) {
1101
+ const step = context.guide.guideSteps[0];
1102
+ if (!step) return;
1103
+ const selector = step.selector ?? context.guide.activationRules.selector;
1104
+ if (typeof selector !== "string" || selector.length === 0) return;
1105
+ const anchor = await waitForElement(selector);
1106
+ if (!anchor) return;
1107
+ const { host, root } = this.createHost();
1108
+ host.style.display = "block";
1109
+ const position = readInlinePosition(step.style);
1110
+ insertHost(anchor, host, position);
1111
+ this.registerCleanup(keepHostAttached(host, selector, position));
1112
+ this.applyDesign(step.style);
1113
+ const ownerDocument = root.ownerDocument ?? document;
1114
+ const card = ownerDocument.createElement("div");
1115
+ card.className = "veo-inline";
1116
+ const dismiss = (action, ctaUrl) => {
1117
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action });
1118
+ if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1119
+ context.onClose();
1120
+ };
1121
+ const content = buildStepContent(step, ownerDocument, {
1122
+ onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
1123
+ onDismiss: () => dismiss("dismissed")
1124
+ });
1125
+ card.appendChild(content);
1126
+ root.appendChild(card);
1127
+ context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
1128
+ }
1129
+ };
1130
+
1131
+ // src/plugins/guides/renderers/modal-renderer.ts
1132
+ var ModalRenderer = class extends BaseRenderer {
1133
+ render(context) {
1134
+ const step = context.guide.guideSteps[0];
1135
+ if (!step) return;
1136
+ const { root } = this.createHost();
1137
+ this.applyDesign(step.style);
1138
+ const ownerDocument = root.ownerDocument ?? document;
1139
+ const overlay = ownerDocument.createElement("div");
1140
+ overlay.className = "veo-modal-overlay";
1141
+ const card = ownerDocument.createElement("div");
1142
+ card.className = "veo-modal-card";
1143
+ const dismiss = (action, ctaUrl) => {
1144
+ context.onInteraction({
1145
+ guideId: context.guide.guideId,
1146
+ stepIndex: 0,
1147
+ action
1148
+ });
1149
+ if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1150
+ context.onClose();
1151
+ };
1152
+ const content = buildStepContent(step, ownerDocument, {
1153
+ onCtaClick: (action, url) => {
1154
+ dismiss("cta_clicked", action === "url" ? url : void 0);
1155
+ },
1156
+ onDismiss: () => dismiss("dismissed")
1157
+ });
1158
+ card.appendChild(content);
1159
+ overlay.appendChild(card);
1160
+ root.appendChild(overlay);
1161
+ overlay.addEventListener("click", (e) => {
1162
+ if (e.target === overlay) dismiss("dismissed");
1163
+ });
1164
+ const onKey = (e) => {
1165
+ if (e.key === "Escape") dismiss("dismissed");
1166
+ };
1167
+ document.addEventListener("keydown", onKey);
1168
+ this.registerCleanup(() => document.removeEventListener("keydown", onKey));
1169
+ context.onInteraction({
1170
+ guideId: context.guide.guideId,
1171
+ stepIndex: 0,
1172
+ action: "shown"
1173
+ });
1174
+ }
1175
+ };
1176
+ var TooltipRenderer = class extends BaseRenderer {
1177
+ async render(context) {
1178
+ const step = context.guide.guideSteps[0];
1179
+ if (!step) return;
1180
+ const selector = step.selector ?? context.guide.activationRules.selector;
1181
+ if (typeof selector !== "string" || selector.length === 0) return;
1182
+ const anchor = await waitForElement(selector);
1183
+ if (!anchor) return;
1184
+ const { root } = this.createHost();
1185
+ this.applyDesign(step.style);
1186
+ const ownerDocument = root.ownerDocument ?? document;
1187
+ const tooltip = ownerDocument.createElement("div");
1188
+ tooltip.className = "veo-tooltip";
1189
+ const dismiss = (action, ctaUrl) => {
1190
+ context.onInteraction({
1191
+ guideId: context.guide.guideId,
1192
+ stepIndex: 0,
1193
+ action
1194
+ });
1195
+ if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
1196
+ context.onClose();
1197
+ };
1198
+ const content = buildStepContent(step, ownerDocument, {
1199
+ onCtaClick: (action, url) => {
1200
+ dismiss("cta_clicked", action === "url" ? url : void 0);
1201
+ },
1202
+ onDismiss: () => dismiss("dismissed")
1203
+ });
1204
+ tooltip.appendChild(content);
1205
+ root.appendChild(tooltip);
1206
+ const updatePosition = async () => {
1207
+ const { x, y } = await dom.computePosition(anchor, tooltip, {
1208
+ placement: "bottom",
1209
+ middleware: [dom.offset(8), dom.flip(), dom.shift({ padding: 8 })]
1210
+ });
1211
+ tooltip.style.left = `${x}px`;
1212
+ tooltip.style.top = `${y}px`;
1213
+ };
1214
+ await updatePosition();
1215
+ const reposition = () => {
1216
+ void updatePosition();
1217
+ };
1218
+ window.addEventListener("scroll", reposition, true);
1219
+ window.addEventListener("resize", reposition);
1220
+ this.registerCleanup(() => {
1221
+ window.removeEventListener("scroll", reposition, true);
1222
+ window.removeEventListener("resize", reposition);
1223
+ });
1224
+ context.onInteraction({
1225
+ guideId: context.guide.guideId,
1226
+ stepIndex: 0,
1227
+ action: "shown"
1228
+ });
1229
+ }
1230
+ };
1231
+
1232
+ // src/plugins/guides/walkthrough-block-builder.ts
1233
+ function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
1234
+ const container = doc.createElement("div");
1235
+ container.className = "veo-guide-content";
1236
+ const counter = doc.createElement("div");
1237
+ counter.className = "veo-walkthrough-counter";
1238
+ counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
1239
+ container.appendChild(counter);
1240
+ const progress = doc.createElement("div");
1241
+ progress.className = "veo-walkthrough-progress";
1242
+ for (let i = 0; i < totalSteps; i++) {
1243
+ const dot = doc.createElement("span");
1244
+ dot.className = "veo-walkthrough-progress-dot";
1245
+ if (i < stepIndex) dot.classList.add("completed");
1246
+ if (i === stepIndex) dot.classList.add("active");
1247
+ progress.appendChild(dot);
1248
+ }
1249
+ container.appendChild(progress);
1250
+ if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
1251
+ const img = doc.createElement("img");
1252
+ img.className = "veo-guide-image";
1253
+ img.src = step.imageUrl;
1254
+ img.alt = typeof step.title === "string" ? step.title : "";
1255
+ container.appendChild(img);
1256
+ }
1257
+ if (typeof step.title === "string" && step.title) {
1258
+ const heading = doc.createElement("h2");
1259
+ heading.className = "veo-guide-title";
1260
+ heading.textContent = step.title;
1261
+ container.appendChild(heading);
1262
+ }
1263
+ if (typeof step.content === "string" && step.content) {
1264
+ const paragraph = doc.createElement("p");
1265
+ paragraph.className = "veo-guide-text";
1266
+ paragraph.textContent = step.content;
1267
+ container.appendChild(paragraph);
1268
+ }
1269
+ const actions = doc.createElement("div");
1270
+ actions.className = "veo-walkthrough-actions";
1271
+ const skipBtn = doc.createElement("button");
1272
+ skipBtn.type = "button";
1273
+ skipBtn.className = "veo-walkthrough-skip";
1274
+ skipBtn.textContent = "Omitir";
1275
+ skipBtn.addEventListener("click", () => callbacks.onSkip());
1276
+ actions.appendChild(skipBtn);
1277
+ const rightGroup = doc.createElement("div");
1278
+ rightGroup.className = "veo-walkthrough-actions-right";
1279
+ if (stepIndex > 0) {
1280
+ const backBtn = doc.createElement("button");
1281
+ backBtn.type = "button";
1282
+ backBtn.className = "veo-walkthrough-btn-secondary";
1283
+ backBtn.textContent = "Atr\xE1s";
1284
+ backBtn.addEventListener("click", () => callbacks.onBack());
1285
+ rightGroup.appendChild(backBtn);
1286
+ }
1287
+ const isLastStep = stepIndex === totalSteps - 1;
1288
+ const primaryBtn = doc.createElement("button");
1289
+ primaryBtn.type = "button";
1290
+ primaryBtn.className = "veo-guide-cta";
1291
+ const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
1292
+ primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
1293
+ primaryBtn.addEventListener("click", () => {
1294
+ if (isLastStep) callbacks.onComplete();
1295
+ else callbacks.onNext();
1296
+ });
1297
+ rightGroup.appendChild(primaryBtn);
1298
+ actions.appendChild(rightGroup);
1299
+ container.appendChild(actions);
1300
+ return container;
1301
+ }
1302
+
1303
+ // src/plugins/guides/renderers/walkthrough-renderer.ts
1304
+ var WalkthroughRenderer = class extends BaseRenderer {
1305
+ async render(context) {
1306
+ const step = context.guide.guideSteps[context.currentStepIndex];
1307
+ if (!step) return false;
1308
+ const selector = step.selector ?? context.guide.activationRules.selector;
1309
+ if (typeof selector !== "string" || selector.length === 0) return false;
1310
+ const anchor = await waitForElement(selector, WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS);
1311
+ if (!anchor) return false;
1312
+ const { root } = this.createHost();
1313
+ this.applyDesign(step.style);
1314
+ const ownerDocument = root.ownerDocument ?? document;
1315
+ const tooltip = ownerDocument.createElement("div");
1316
+ tooltip.className = "veo-walkthrough";
1317
+ const content = buildWalkthroughStepContent(
1318
+ step,
1319
+ context.currentStepIndex,
1320
+ context.guide.guideSteps.length,
1321
+ ownerDocument,
1322
+ context.callbacks
1323
+ );
1324
+ tooltip.appendChild(content);
1325
+ const arrowEl = ownerDocument.createElement("div");
1326
+ arrowEl.className = "veo-walkthrough-arrow";
1327
+ tooltip.appendChild(arrowEl);
1328
+ root.appendChild(tooltip);
1329
+ const updatePosition = async () => {
1330
+ const { x, y, placement, middlewareData } = await dom.computePosition(anchor, tooltip, {
1331
+ placement: "bottom",
1332
+ middleware: [dom.offset(10), dom.flip(), dom.shift({ padding: 8 }), dom.arrow({ element: arrowEl })]
1333
+ });
1334
+ tooltip.style.left = `${x}px`;
1335
+ tooltip.style.top = `${y}px`;
1336
+ positionArrow(arrowEl, placement, middlewareData.arrow);
1337
+ };
1338
+ await updatePosition();
1339
+ const reposition = () => {
1340
+ void updatePosition();
1341
+ };
1342
+ window.addEventListener("scroll", reposition, true);
1343
+ window.addEventListener("resize", reposition);
1344
+ this.registerCleanup(() => {
1345
+ window.removeEventListener("scroll", reposition, true);
1346
+ window.removeEventListener("resize", reposition);
1347
+ });
1348
+ return true;
1349
+ }
1350
+ };
1351
+ var STATIC_SIDE = {
1352
+ top: "bottom",
1353
+ bottom: "top",
1354
+ left: "right",
1355
+ right: "left"
1356
+ };
1357
+ function positionArrow(arrowEl, placement, data) {
1358
+ const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
1359
+ for (const prop of ["top", "bottom", "left", "right"]) {
1360
+ arrowEl.style.setProperty(prop, "");
1361
+ }
1362
+ if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
1363
+ if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
1364
+ arrowEl.style.setProperty(side, "-6px");
1365
+ }
1366
+
1367
+ // src/plugins/guides/guide-preview.ts
1368
+ var PREVIEW_GUIDE_ID = "__veo_preview__";
1369
+ var GuidePreviewController = class {
1370
+ constructor() {
1371
+ this.singleStep = null;
1372
+ this.walkthrough = null;
1373
+ this.walkthroughGuide = null;
1374
+ this.walkthroughIndex = 0;
1375
+ /** Input de la preview ACTIVA (para formSubmit/nota de forms). */
1376
+ this.input = null;
1377
+ }
1378
+ preview(input) {
1379
+ this.close();
1380
+ this.input = input;
1381
+ const guide = normalize(input);
1382
+ const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
1383
+ const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
1384
+ return { close: () => this.close(), ready };
1385
+ }
1386
+ close() {
1387
+ if (this.singleStep) {
1388
+ safeDestroy(this.singleStep);
1389
+ this.singleStep = null;
1390
+ }
1391
+ if (this.walkthrough) {
1392
+ safeDestroy(this.walkthrough);
1393
+ this.walkthrough = null;
1394
+ }
1395
+ this.walkthroughGuide = null;
1396
+ this.walkthroughIndex = 0;
1397
+ this.input = null;
1398
+ }
1399
+ async renderSingleStep(guide) {
1400
+ const renderer = createSingleStepRenderer(guide.guideType);
1401
+ if (!renderer) return { rendered: false };
1402
+ this.singleStep = renderer;
1403
+ let shown = false;
1404
+ try {
1405
+ await renderer.render({
1406
+ guide,
1407
+ onInteraction: (event) => {
1408
+ if (event.action === "shown") shown = true;
1409
+ },
1410
+ onClose: () => this.close(),
1411
+ onTrack: () => {
1412
+ },
1413
+ // Preview de forms: por defecto simula el envío SIN escribir props;
1414
+ // el caller puede pasar `formSubmit` real (link de preview).
1415
+ onFormSubmit: this.input?.formSubmit ?? (() => Promise.resolve(true)),
1416
+ isPreview: true,
1417
+ ...this.input?.formSubmit && this.input.formSubmitNote !== void 0 ? { previewNote: this.input.formSubmitNote } : {}
1418
+ });
1419
+ } catch {
1420
+ this.close();
1421
+ return { rendered: false };
1422
+ }
1423
+ if (this.singleStep !== renderer) {
1424
+ safeDestroy(renderer);
1425
+ return { rendered: false };
1426
+ }
1427
+ return { rendered: shown };
1428
+ }
1429
+ async startWalkthrough(guide, startIndex = 0) {
1430
+ if (guide.guideSteps.length === 0) return { rendered: false };
1431
+ this.walkthroughGuide = guide;
1432
+ const idx = Math.min(Math.max(0, Math.floor(startIndex)), guide.guideSteps.length - 1);
1433
+ this.walkthroughIndex = idx;
1434
+ const rendered = await this.renderWalkthroughStep(idx);
1435
+ return { rendered };
1436
+ }
1437
+ async renderWalkthroughStep(index) {
1438
+ const guide = this.walkthroughGuide;
1439
+ if (!guide) return false;
1440
+ if (this.walkthrough) {
1441
+ safeDestroy(this.walkthrough);
1442
+ this.walkthrough = null;
1443
+ }
1444
+ const renderer = new WalkthroughRenderer();
1445
+ const callbacks = {
1446
+ onNext: () => {
1447
+ const next = this.walkthroughIndex + 1;
1448
+ if (next >= guide.guideSteps.length) {
1449
+ this.close();
1450
+ return;
1451
+ }
1452
+ this.walkthroughIndex = next;
1453
+ void this.renderWalkthroughStep(next);
1454
+ },
1455
+ onBack: () => {
1456
+ const prev = this.walkthroughIndex - 1;
1457
+ if (prev < 0) return;
1458
+ this.walkthroughIndex = prev;
1459
+ void this.renderWalkthroughStep(prev);
1460
+ },
1461
+ onSkip: () => this.close(),
1462
+ onComplete: () => this.close()
1463
+ };
1464
+ let success = false;
1465
+ try {
1466
+ success = await renderer.render({ guide, currentStepIndex: index, callbacks });
1467
+ } catch {
1468
+ success = false;
1469
+ }
1470
+ if (!success || this.walkthroughGuide !== guide) {
1471
+ safeDestroy(renderer);
1472
+ return false;
1473
+ }
1474
+ this.walkthrough = renderer;
1475
+ return true;
1476
+ }
1477
+ };
1478
+ var activeController = null;
1479
+ function previewGuide(input) {
1480
+ if (!hasDocument()) {
1481
+ return { close: () => {
1482
+ }, ready: Promise.resolve({ rendered: false }) };
1483
+ }
1484
+ if (!activeController) activeController = new GuidePreviewController();
1485
+ return activeController.preview(input);
1486
+ }
1487
+ function closeGuidePreview() {
1488
+ activeController?.close();
1489
+ }
1490
+ function normalize(input) {
1491
+ const selector = input.activationRules?.selector;
1492
+ const activationRules = {
1493
+ url: input.activationRules?.url ?? { type: "prefix", pattern: "/" },
1494
+ trigger: "immediate",
1495
+ ...selector !== void 0 ? { selector } : {}
1496
+ };
1497
+ return {
1498
+ guideId: PREVIEW_GUIDE_ID,
1499
+ guideName: input.guideName ?? "Preview",
1500
+ guideType: input.guideType,
1501
+ guideSteps: input.guideSteps,
1502
+ activationRules,
1503
+ displayFrequency: "always",
1504
+ displayPriority: 0
1505
+ };
1506
+ }
1507
+ function createSingleStepRenderer(type) {
1508
+ switch (type) {
1509
+ case "modal":
1510
+ return new ModalRenderer();
1511
+ case "banner":
1512
+ return new BannerRenderer();
1513
+ case "tooltip":
1514
+ return new TooltipRenderer();
1515
+ case "custom":
1516
+ return new CustomRenderer();
1517
+ case "inline":
1518
+ return new InlineRenderer();
1519
+ case "inline-custom":
1520
+ return new InlineCustomRenderer();
1521
+ case "form":
1522
+ return new FormRenderer();
1523
+ case "inline-form":
1524
+ return new InlineFormRenderer();
1525
+ case "walkthrough":
1526
+ return null;
1527
+ }
1528
+ }
1529
+ function safeDestroy(renderer) {
1530
+ try {
1531
+ renderer.destroy();
1532
+ } catch {
1533
+ }
1534
+ }
1535
+
1536
+ // src/plugins/builder/bridge-protocol.ts
1537
+ var VEO_BUILDER_SOURCE = "veo-builder";
1538
+ var BUILDER_TOKEN_PARAM = "veoBuilder";
1539
+ var BUILDER_ORIGIN_PARAM = "veoBuilderOrigin";
1540
+
1541
+ // src/plugins/autocapture/constants.ts
1542
+ var HASHED_CLASS_PATTERNS = [
1543
+ /^css-[a-z0-9]{4,}$/i,
1544
+ // emotion, styled-components nuevas
1545
+ /^_[a-z0-9]+_[a-z0-9]{4,}_\d+$/,
1546
+ // CSS Modules con hash
1547
+ /^[a-z]{2,4}-[a-z0-9]{6,}$/i,
1548
+ // Tailwind JIT (a veces)
1549
+ /^[a-f0-9]{6,}$/i,
1550
+ // hashes hex puros
1551
+ /^sc-[a-z0-9]+$/i
1552
+ // styled-components legacy
1553
+ ];
1554
+
1555
+ // src/plugins/autocapture/selector-builder.ts
1556
+ function isHashedClass(className) {
1557
+ return HASHED_CLASS_PATTERNS.some((pattern) => pattern.test(className));
1558
+ }
1559
+ function filterHumanClasses(element) {
1560
+ return Array.from(element.classList).filter((c) => !isHashedClass(c) && c.length < 64);
1561
+ }
1562
+ var STABLE_ATTRIBUTES = [
1563
+ "data-testid",
1564
+ "data-test",
1565
+ "data-cy",
1566
+ "name",
1567
+ "aria-label",
1568
+ "placeholder",
1569
+ "alt",
1570
+ "title",
1571
+ // `href` identifica muy bien a los links (`<a>`), un target frecuente de
1572
+ // autocapture; va después de los atributos semánticos pero antes de los
1573
+ // genéricos. Valores largos (URLs absolutas con query) se descartan por
1574
+ // MAX_ATTR_VALUE_LENGTH.
1575
+ "href",
1576
+ "role",
1577
+ "type"
1578
+ ];
1579
+ var MAX_ATTR_VALUE_LENGTH = 100;
1580
+ function cssEscape(value) {
1581
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
1582
+ return CSS.escape(value);
1583
+ }
1584
+ return value.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
1585
+ }
1586
+ function escapeAttrValue(value) {
1587
+ return value.replace(/[\\"]/g, "\\$&");
1588
+ }
1589
+ function stableAttributeSelector(element, tag) {
1590
+ for (const attr of STABLE_ATTRIBUTES) {
1591
+ const value = element.getAttribute(attr);
1592
+ if (value && value.length > 0 && value.length <= MAX_ATTR_VALUE_LENGTH) {
1593
+ return `${tag}[${attr}="${escapeAttrValue(value)}"]`;
1594
+ }
1595
+ }
1596
+ return null;
1597
+ }
1598
+ function buildElementSelector(element) {
1599
+ const tag = element.tagName.toLowerCase();
1600
+ const veoTag = element.getAttribute("data-veo-tag");
1601
+ if (veoTag) {
1602
+ return `${tag}[data-veo-tag="${escapeAttrValue(veoTag)}"]`;
1603
+ }
1604
+ if (element.id) {
1605
+ const classes2 = filterHumanClasses(element);
1606
+ const classPart2 = classes2.length > 0 ? `.${classes2.map(cssEscape).join(".")}` : "";
1607
+ return `${tag}#${cssEscape(element.id)}${classPart2}`;
1608
+ }
1609
+ const attrSelector = stableAttributeSelector(element, tag);
1610
+ if (attrSelector) return attrSelector;
1611
+ const classes = filterHumanClasses(element);
1612
+ const classPart = classes.length > 0 ? `.${classes.map(cssEscape).join(".")}` : "";
1613
+ return `${tag}${classPart}`;
1614
+ }
1615
+ function buildSelectorPath(element, maxAncestors = 5) {
1616
+ const parts = [];
1617
+ let current = element;
1618
+ let depth = 0;
1619
+ while (current && current !== document.documentElement && depth <= maxAncestors) {
1620
+ parts.unshift(buildElementSelector(current));
1621
+ current = current.parentElement;
1622
+ depth++;
1623
+ }
1624
+ return parts.join(" > ");
1625
+ }
1626
+
1627
+ // src/plugins/builder/element-picker.ts
1628
+ var PICKER_Z = 2147483646;
1629
+ var PICKER_HOST_ATTR = "data-veo-picker";
1630
+ var DEFAULT_MAX_ANCESTORS = 5;
1631
+ var PICKER_STYLES = `
1632
+ :host { all: initial; }
1633
+ .veo-pick-box {
1634
+ position: fixed;
1635
+ z-index: ${PICKER_Z};
1636
+ pointer-events: none;
1637
+ box-sizing: border-box;
1638
+ background: rgba(99, 102, 241, 0.22);
1639
+ border: 1px dashed rgba(99, 102, 241, 0.95);
1640
+ border-radius: 2px;
1641
+ transition: top 60ms ease-out, left 60ms ease-out, width 60ms ease-out, height 60ms ease-out;
1642
+ }
1643
+ .veo-pick-label {
1644
+ position: fixed;
1645
+ z-index: ${PICKER_Z};
1646
+ pointer-events: none;
1647
+ max-width: 360px;
1648
+ background: #1e1b4b;
1649
+ color: #e0e7ff;
1650
+ font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
1651
+ padding: 6px 9px;
1652
+ border-radius: 6px;
1653
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
1654
+ }
1655
+ .veo-pick-label .veo-pick-sel { color: #c7d2fe; word-break: break-all; }
1656
+ .veo-pick-label .veo-pick-dim { color: #a5b4fc; margin-top: 3px; }
1657
+ .veo-pick-hint {
1658
+ position: fixed;
1659
+ top: 12px;
1660
+ left: 50%;
1661
+ transform: translateX(-50%);
1662
+ z-index: ${PICKER_Z};
1663
+ pointer-events: none;
1664
+ background: #4338ca;
1665
+ color: #fff;
1666
+ font: 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1667
+ padding: 8px 14px;
1668
+ border-radius: 999px;
1669
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
1670
+ }
1671
+ `;
1672
+ var ElementPicker = class {
1673
+ constructor() {
1674
+ this.host = null;
1675
+ this.box = null;
1676
+ this.label = null;
1677
+ this.current = null;
1678
+ this.maxAncestors = DEFAULT_MAX_ANCESTORS;
1679
+ this.onPick = null;
1680
+ this.onCancel = null;
1681
+ this.onMove = (event) => {
1682
+ const target = event.target;
1683
+ if (!(target instanceof Element) || this.isOwn(target)) return;
1684
+ this.current = target;
1685
+ this.draw(target);
1686
+ };
1687
+ this.onClick = (event) => {
1688
+ const target = this.current ?? (event.target instanceof Element ? event.target : null);
1689
+ if (!target || this.isOwn(target)) return;
1690
+ event.preventDefault();
1691
+ event.stopPropagation();
1692
+ event.stopImmediatePropagation();
1693
+ const picked = this.describe(target);
1694
+ const cb = this.onPick;
1695
+ this.stop();
1696
+ cb?.(picked);
1697
+ };
1698
+ this.onKey = (event) => {
1699
+ if (event.key !== "Escape") return;
1700
+ event.preventDefault();
1701
+ event.stopPropagation();
1702
+ const cb = this.onCancel;
1703
+ this.stop();
1704
+ cb?.();
1705
+ };
1706
+ /** Traga mousedown/mouseup durante el picking (evita focus/active de la app). */
1707
+ this.swallow = (event) => {
1708
+ if (this.isOwn(event.target)) return;
1709
+ event.preventDefault();
1710
+ event.stopPropagation();
1711
+ };
1712
+ this.reposition = () => {
1713
+ if (this.current) this.draw(this.current);
1714
+ };
1715
+ }
1716
+ start(options) {
1717
+ this.stop();
1718
+ this.maxAncestors = options.maxAncestors ?? DEFAULT_MAX_ANCESTORS;
1719
+ this.onPick = options.onPick;
1720
+ this.onCancel = options.onCancel ?? null;
1721
+ const host = document.createElement("div");
1722
+ host.setAttribute(PICKER_HOST_ATTR, "");
1723
+ const shadow = host.attachShadow({ mode: "open" });
1724
+ const style = document.createElement("style");
1725
+ style.textContent = PICKER_STYLES;
1726
+ this.box = document.createElement("div");
1727
+ this.box.className = "veo-pick-box";
1728
+ this.box.style.display = "none";
1729
+ this.label = document.createElement("div");
1730
+ this.label.className = "veo-pick-label";
1731
+ this.label.style.display = "none";
1732
+ const hint = document.createElement("div");
1733
+ hint.className = "veo-pick-hint";
1734
+ hint.textContent = options.hint ?? "Seleccion\xE1 un elemento \xB7 Esc para cancelar";
1735
+ shadow.append(style, this.box, this.label, hint);
1736
+ document.body.appendChild(host);
1737
+ this.host = host;
1738
+ document.addEventListener("mousemove", this.onMove, true);
1739
+ document.addEventListener("click", this.onClick, true);
1740
+ document.addEventListener("mousedown", this.swallow, true);
1741
+ document.addEventListener("mouseup", this.swallow, true);
1742
+ document.addEventListener("keydown", this.onKey, true);
1743
+ window.addEventListener("scroll", this.reposition, true);
1744
+ window.addEventListener("resize", this.reposition);
1745
+ }
1746
+ stop() {
1747
+ document.removeEventListener("mousemove", this.onMove, true);
1748
+ document.removeEventListener("click", this.onClick, true);
1749
+ document.removeEventListener("mousedown", this.swallow, true);
1750
+ document.removeEventListener("mouseup", this.swallow, true);
1751
+ document.removeEventListener("keydown", this.onKey, true);
1752
+ window.removeEventListener("scroll", this.reposition, true);
1753
+ window.removeEventListener("resize", this.reposition);
1754
+ if (this.host?.parentNode) this.host.parentNode.removeChild(this.host);
1755
+ this.host = null;
1756
+ this.box = null;
1757
+ this.label = null;
1758
+ this.current = null;
1759
+ this.onPick = null;
1760
+ this.onCancel = null;
1761
+ }
1762
+ /** ¿Es un nodo de nuestro propio overlay? (no debe ser seleccionable). */
1763
+ isOwn(target) {
1764
+ return target instanceof Node && this.host !== null && this.host.contains(target);
1765
+ }
1766
+ /** Dibuja el recuadro de resalte y la etiqueta sobre el elemento dado. */
1767
+ draw(element) {
1768
+ if (!this.box || !this.label) return;
1769
+ const rect = element.getBoundingClientRect();
1770
+ this.box.style.display = "block";
1771
+ this.box.style.top = `${rect.top}px`;
1772
+ this.box.style.left = `${rect.left}px`;
1773
+ this.box.style.width = `${rect.width}px`;
1774
+ this.box.style.height = `${rect.height}px`;
1775
+ const shortSel = buildSelectorPath(element, 0);
1776
+ this.label.textContent = "";
1777
+ const sel = document.createElement("div");
1778
+ sel.className = "veo-pick-sel";
1779
+ sel.textContent = shortSel;
1780
+ const dim = document.createElement("div");
1781
+ dim.className = "veo-pick-dim";
1782
+ dim.textContent = `${Math.round(rect.width)} \xD7 ${Math.round(rect.height)}`;
1783
+ this.label.append(sel, dim);
1784
+ this.label.style.display = "block";
1785
+ const LABEL_H = 48;
1786
+ const placeAbove = rect.bottom + 8 + LABEL_H > window.innerHeight;
1787
+ this.label.style.top = placeAbove ? `${Math.max(8, rect.top - LABEL_H - 8)}px` : `${rect.bottom + 8}px`;
1788
+ this.label.style.left = `${Math.max(8, rect.left)}px`;
1789
+ }
1790
+ describe(element) {
1791
+ const rect = element.getBoundingClientRect();
1792
+ return {
1793
+ selector: buildSelectorPath(element, this.maxAncestors),
1794
+ shortSelector: buildSelectorPath(element, 0),
1795
+ tag: element.tagName.toLowerCase(),
1796
+ id: element.id || null,
1797
+ classes: filterHumanClasses(element),
1798
+ text: (element.textContent ?? "").trim().slice(0, 120),
1799
+ rect: {
1800
+ x: Math.round(rect.left),
1801
+ y: Math.round(rect.top),
1802
+ width: Math.round(rect.width),
1803
+ height: Math.round(rect.height)
1804
+ }
1805
+ };
1806
+ }
1807
+ };
1808
+ var activePicker = null;
1809
+ function startElementPicker(options) {
1810
+ if (!hasDocument()) {
1811
+ return { stop: () => {
1812
+ } };
1813
+ }
1814
+ if (!activePicker) activePicker = new ElementPicker();
1815
+ activePicker.start(options);
1816
+ return { stop: () => activePicker?.stop() };
1817
+ }
1818
+ function stopElementPicker() {
1819
+ activePicker?.stop();
1820
+ }
1821
+
1822
+ // src/plugins/builder/builder-mode.ts
1823
+ function initBuilderMode() {
1824
+ if (!hasWindow() || !hasDocument()) return null;
1825
+ let params;
1826
+ try {
1827
+ params = new URLSearchParams(window.location.search);
1828
+ } catch {
1829
+ return null;
1830
+ }
1831
+ const token = params.get(BUILDER_TOKEN_PARAM);
1832
+ if (!token) return null;
1833
+ const dashboardOrigin = resolveDashboardOrigin(params);
1834
+ if (!dashboardOrigin) return null;
1835
+ const target = resolveTarget();
1836
+ if (!target) return null;
1837
+ let picker = null;
1838
+ const post = (event) => {
1839
+ target.postMessage({ source: VEO_BUILDER_SOURCE, token, ...event }, dashboardOrigin);
1840
+ };
1841
+ const handleCommand = (cmd) => {
1842
+ switch (cmd.type) {
1843
+ case "start-picker":
1844
+ picker?.stop();
1845
+ picker = startElementPicker({
1846
+ onPick: (payload) => {
1847
+ picker = null;
1848
+ post({ type: "picked", payload });
1849
+ },
1850
+ onCancel: () => {
1851
+ picker = null;
1852
+ post({ type: "pick-cancelled" });
1853
+ },
1854
+ ...cmd.maxAncestors !== void 0 ? { maxAncestors: cmd.maxAncestors } : {},
1855
+ ...cmd.hint !== void 0 ? { hint: cmd.hint } : {}
1856
+ });
1857
+ break;
1858
+ case "stop-picker":
1859
+ picker?.stop();
1860
+ picker = null;
1861
+ break;
1862
+ case "preview":
1863
+ if (cmd.guide) void previewGuide(cmd.guide).ready;
1864
+ break;
1865
+ case "close-preview":
1866
+ closeGuidePreview();
1867
+ break;
1868
+ case "teardown":
1869
+ teardown();
1870
+ break;
1871
+ }
1872
+ };
1873
+ const onMessage = (event) => {
1874
+ if (event.origin !== dashboardOrigin) return;
1875
+ const data = event.data;
1876
+ if (!data || data.source !== VEO_BUILDER_SOURCE || data.token !== token) return;
1877
+ handleCommand(data);
1878
+ };
1879
+ const onPageHide = () => post({ type: "closed" });
1880
+ const teardown = () => {
1881
+ window.removeEventListener("message", onMessage);
1882
+ window.removeEventListener("pagehide", onPageHide);
1883
+ picker?.stop();
1884
+ picker = null;
1885
+ closeGuidePreview();
1886
+ post({ type: "closed" });
1887
+ };
1888
+ const activate = () => {
1889
+ window.addEventListener("message", onMessage);
1890
+ window.addEventListener("pagehide", onPageHide);
1891
+ post({ type: "ready" });
1892
+ };
1893
+ const cfg = window.__veoBuilder;
1894
+ if (cfg?.apiKey && cfg?.apiUrl) {
1895
+ void verifyBuilderToken(cfg.apiUrl, cfg.apiKey, token).then((ok) => {
1896
+ if (ok) activate();
1897
+ else if (window.console) console.warn("[veo] token de builder inv\xE1lido \u2014 modo no activado");
1898
+ });
1899
+ } else {
1900
+ activate();
1901
+ }
1902
+ return { teardown };
1903
+ }
1904
+ async function verifyBuilderToken(apiUrl, apiKey, token) {
1905
+ try {
1906
+ const url = `${apiUrl.replace(/\/+$/, "")}/v1/builder/verify`;
1907
+ const res = await fetch(url, {
1908
+ method: "POST",
1909
+ headers: { "Content-Type": "application/json", "X-Api-Key": apiKey },
1910
+ body: JSON.stringify({ token })
1911
+ });
1912
+ if (!res.ok) return true;
1913
+ const data = await res.json().catch(() => null);
1914
+ return data?.valid !== false;
1915
+ } catch {
1916
+ return true;
1917
+ }
1918
+ }
1919
+ function resolveTarget() {
1920
+ if (window.opener && window.opener !== window) return window.opener;
1921
+ if (window.parent && window.parent !== window) return window.parent;
1922
+ return null;
1923
+ }
1924
+ function resolveDashboardOrigin(params) {
1925
+ const explicit = params.get(BUILDER_ORIGIN_PARAM);
1926
+ if (explicit) {
1927
+ try {
1928
+ return new URL(explicit).origin;
1929
+ } catch {
1930
+ return null;
1931
+ }
1932
+ }
1933
+ if (document.referrer) {
1934
+ try {
1935
+ return new URL(document.referrer).origin;
1936
+ } catch {
1937
+ return null;
1938
+ }
1939
+ }
1940
+ return null;
1941
+ }
1942
+
1943
+ // src/plugins/builder/builder-session.ts
1944
+ var DEFAULT_FEATURES = "popup,width=1280,height=860";
1945
+ var POPUP_POLL_MS = 600;
1946
+ function createBuilderSession(options) {
1947
+ if (!hasWindow()) return noopSession();
1948
+ const dashboardOrigin = options.dashboardOrigin ?? window.location.origin;
1949
+ const appOrigin = safeOrigin(options.appUrl);
1950
+ const url = buildAppUrl(options.appUrl, options.token, dashboardOrigin);
1951
+ const frame = options.frame ?? null;
1952
+ const useFrame = frame !== null;
1953
+ let popup = null;
1954
+ if (useFrame) {
1955
+ frame.src = url;
1956
+ } else {
1957
+ popup = window.open(
1958
+ url,
1959
+ options.windowName ?? "veo-builder",
1960
+ options.windowFeatures ?? DEFAULT_FEATURES
1961
+ );
1962
+ }
1963
+ const targetWindow = () => useFrame ? frame?.contentWindow ?? null : popup;
1964
+ const post = (command) => {
1965
+ const w = targetWindow();
1966
+ if (!w) return;
1967
+ if (!useFrame && popup?.closed) return;
1968
+ w.postMessage(
1969
+ { source: VEO_BUILDER_SOURCE, token: options.token, ...command },
1970
+ appOrigin ?? "*"
1971
+ );
1972
+ };
1973
+ let pollTimer;
1974
+ const cleanup = () => {
1975
+ window.removeEventListener("message", onMessage);
1976
+ if (pollTimer !== void 0) {
1977
+ clearInterval(pollTimer);
1978
+ pollTimer = void 0;
1979
+ }
1980
+ };
1981
+ const onMessage = (event) => {
1982
+ if (appOrigin && event.origin !== appOrigin) return;
1983
+ const data = event.data;
1984
+ if (!data || data.source !== VEO_BUILDER_SOURCE || data.token !== options.token) return;
1985
+ switch (data.type) {
1986
+ case "ready":
1987
+ options.onReady?.();
1988
+ break;
1989
+ case "picked":
1990
+ if (data.payload) options.onPicked?.(data.payload);
1991
+ break;
1992
+ case "pick-cancelled":
1993
+ options.onCancelled?.();
1994
+ break;
1995
+ case "closed":
1996
+ cleanup();
1997
+ options.onClosed?.();
1998
+ break;
1999
+ }
2000
+ };
2001
+ window.addEventListener("message", onMessage);
2002
+ if (!useFrame) {
2003
+ pollTimer = setInterval(() => {
2004
+ if (popup?.closed) {
2005
+ cleanup();
2006
+ options.onClosed?.();
2007
+ }
2008
+ }, POPUP_POLL_MS);
2009
+ }
2010
+ return {
2011
+ startPicker: (o) => post({
2012
+ type: "start-picker",
2013
+ ...o?.maxAncestors !== void 0 ? { maxAncestors: o.maxAncestors } : {},
2014
+ ...o?.hint !== void 0 ? { hint: o.hint } : {}
2015
+ }),
2016
+ stopPicker: () => post({ type: "stop-picker" }),
2017
+ preview: (guide) => post({ type: "preview", guide }),
2018
+ closePreview: () => post({ type: "close-preview" }),
2019
+ close: () => {
2020
+ post({ type: "teardown" });
2021
+ if (useFrame) {
2022
+ if (frame) frame.src = "about:blank";
2023
+ } else if (popup && !popup.closed) {
2024
+ popup.close();
2025
+ }
2026
+ cleanup();
2027
+ }
2028
+ };
2029
+ }
2030
+ function buildAppUrl(appUrl, token, dashboardOrigin) {
2031
+ try {
2032
+ const u = new URL(appUrl, window.location.href);
2033
+ u.searchParams.set(BUILDER_TOKEN_PARAM, token);
2034
+ u.searchParams.set(BUILDER_ORIGIN_PARAM, dashboardOrigin);
2035
+ return u.toString();
2036
+ } catch {
2037
+ return appUrl;
2038
+ }
2039
+ }
2040
+ function safeOrigin(url) {
2041
+ try {
2042
+ return new URL(url, window.location.href).origin;
2043
+ } catch {
2044
+ return null;
2045
+ }
2046
+ }
2047
+ function noopSession() {
2048
+ return {
2049
+ startPicker: () => {
2050
+ },
2051
+ stopPicker: () => {
2052
+ },
2053
+ preview: () => {
2054
+ },
2055
+ closePreview: () => {
2056
+ },
2057
+ close: () => {
2058
+ }
2059
+ };
2060
+ }
2061
+
2062
+ // src/builder.ts
2063
+ initBuilderMode();
2064
+
2065
+ exports.closeGuidePreview = closeGuidePreview;
2066
+ exports.createBuilderSession = createBuilderSession;
2067
+ exports.initBuilderMode = initBuilderMode;
2068
+ exports.previewGuide = previewGuide;
2069
+ exports.startElementPicker = startElementPicker;
2070
+ exports.stopElementPicker = stopElementPicker;
2071
+ //# sourceMappingURL=builder.cjs.map
2072
+ //# sourceMappingURL=builder.cjs.map