veo-sdk 0.3.16 → 0.4.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-ZDNVF6KG.mjs +5 -0
- package/dist/{builder-EQYOMQFY.mjs.map → builder-ZDNVF6KG.mjs.map} +1 -1
- package/dist/builder.cjs +1008 -215
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.d.cts +38 -2
- package/dist/builder.d.ts +38 -2
- package/dist/builder.mjs +4 -4
- package/dist/{chunk-QCIT7CGE.mjs → chunk-I55Z7UGJ.mjs} +269 -5
- package/dist/chunk-I55Z7UGJ.mjs.map +1 -0
- package/dist/{chunk-YHUPUTWH.mjs → chunk-ICKEQ7VC.mjs} +713 -184
- package/dist/chunk-ICKEQ7VC.mjs.map +1 -0
- package/dist/{chunk-IEHGG5NL.mjs → chunk-MA2BXLZ7.mjs} +67 -11
- package/dist/chunk-MA2BXLZ7.mjs.map +1 -0
- package/dist/{guide-preview-DewCTa-B.d.cts → guide-preview-D_xhRVLe.d.cts} +20 -2
- package/dist/{guide-preview-DewCTa-B.d.ts → guide-preview-D_xhRVLe.d.ts} +20 -2
- package/dist/index.cjs +1097 -216
- 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 +7 -6
- package/dist/index.mjs.map +1 -1
- package/dist/veo-builder.js +125 -9
- package/dist/veo-builder.js.map +1 -1
- package/dist/veo-guides.js +86 -4
- package/dist/veo-guides.js.map +1 -1
- package/dist/veo.js +1 -1
- package/dist/veo.js.map +1 -1
- package/package.json +1 -1
- package/dist/builder-EQYOMQFY.mjs +0 -5
- package/dist/chunk-IEHGG5NL.mjs.map +0 -1
- package/dist/chunk-QCIT7CGE.mjs.map +0 -1
- package/dist/chunk-YHUPUTWH.mjs.map +0 -1
package/dist/builder.cjs
CHANGED
|
@@ -142,6 +142,112 @@ var SAFE_URL_PROTOCOLS = ["http:", "https:"];
|
|
|
142
142
|
var GUIDE_HOST_ATTR = "data-veo-guide";
|
|
143
143
|
var WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS = 5e3;
|
|
144
144
|
|
|
145
|
+
// src/plugins/guides/rich-text.ts
|
|
146
|
+
var ALLOWED_TAGS = {
|
|
147
|
+
P: "p",
|
|
148
|
+
BR: "br",
|
|
149
|
+
STRONG: "strong",
|
|
150
|
+
B: "strong",
|
|
151
|
+
EM: "em",
|
|
152
|
+
I: "em",
|
|
153
|
+
U: "u",
|
|
154
|
+
S: "s",
|
|
155
|
+
UL: "ul",
|
|
156
|
+
OL: "ol",
|
|
157
|
+
LI: "li",
|
|
158
|
+
A: "a"
|
|
159
|
+
};
|
|
160
|
+
var DROP_TAGS = /* @__PURE__ */ new Set([
|
|
161
|
+
"SCRIPT",
|
|
162
|
+
"STYLE",
|
|
163
|
+
"TEMPLATE",
|
|
164
|
+
"IFRAME",
|
|
165
|
+
"OBJECT",
|
|
166
|
+
"EMBED",
|
|
167
|
+
"NOSCRIPT",
|
|
168
|
+
"TITLE",
|
|
169
|
+
"TEXTAREA",
|
|
170
|
+
"SELECT",
|
|
171
|
+
"SVG",
|
|
172
|
+
"MATH"
|
|
173
|
+
]);
|
|
174
|
+
var MAX_INPUT_LEN = 16 * 1024;
|
|
175
|
+
var MAX_DEPTH = 20;
|
|
176
|
+
var MAX_NODES = 1e3;
|
|
177
|
+
function renderRichText(html, doc) {
|
|
178
|
+
const fragment = doc.createDocumentFragment();
|
|
179
|
+
if (typeof html !== "string" || html.length === 0) return fragment;
|
|
180
|
+
let parsed;
|
|
181
|
+
try {
|
|
182
|
+
parsed = new DOMParser().parseFromString(
|
|
183
|
+
html.length > MAX_INPUT_LEN ? html.slice(0, MAX_INPUT_LEN) : html,
|
|
184
|
+
"text/html"
|
|
185
|
+
);
|
|
186
|
+
} catch {
|
|
187
|
+
fragment.appendChild(doc.createTextNode(html));
|
|
188
|
+
return fragment;
|
|
189
|
+
}
|
|
190
|
+
const budget = { nodes: 0, exceeded: false };
|
|
191
|
+
for (const child of Array.from(parsed.body.childNodes)) {
|
|
192
|
+
const rebuilt = rebuildNode(child, doc, 0, budget);
|
|
193
|
+
if (rebuilt) fragment.appendChild(rebuilt);
|
|
194
|
+
}
|
|
195
|
+
if (budget.exceeded) {
|
|
196
|
+
const plain = doc.createDocumentFragment();
|
|
197
|
+
plain.appendChild(doc.createTextNode(parsed.body.textContent ?? ""));
|
|
198
|
+
return plain;
|
|
199
|
+
}
|
|
200
|
+
return fragment;
|
|
201
|
+
}
|
|
202
|
+
function rebuildNode(node, doc, depth, budget) {
|
|
203
|
+
if (budget.exceeded) return null;
|
|
204
|
+
if (++budget.nodes > MAX_NODES || depth > MAX_DEPTH) {
|
|
205
|
+
budget.exceeded = true;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
209
|
+
return doc.createTextNode(node.textContent ?? "");
|
|
210
|
+
}
|
|
211
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return null;
|
|
212
|
+
const el = node;
|
|
213
|
+
if (DROP_TAGS.has(el.tagName)) return null;
|
|
214
|
+
const mapped = ALLOWED_TAGS[el.tagName];
|
|
215
|
+
if (!mapped) {
|
|
216
|
+
const frag = doc.createDocumentFragment();
|
|
217
|
+
for (const child of Array.from(el.childNodes)) {
|
|
218
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
219
|
+
if (rebuilt2) frag.appendChild(rebuilt2);
|
|
220
|
+
}
|
|
221
|
+
return frag;
|
|
222
|
+
}
|
|
223
|
+
if (mapped === "a") {
|
|
224
|
+
const href = el.getAttribute("href") ?? "";
|
|
225
|
+
if (!isSafeUrl(href)) {
|
|
226
|
+
const frag = doc.createDocumentFragment();
|
|
227
|
+
for (const child of Array.from(el.childNodes)) {
|
|
228
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
229
|
+
if (rebuilt2) frag.appendChild(rebuilt2);
|
|
230
|
+
}
|
|
231
|
+
return frag;
|
|
232
|
+
}
|
|
233
|
+
const a = doc.createElement("a");
|
|
234
|
+
a.setAttribute("href", href);
|
|
235
|
+
a.setAttribute("target", "_blank");
|
|
236
|
+
a.setAttribute("rel", "noopener noreferrer nofollow");
|
|
237
|
+
for (const child of Array.from(el.childNodes)) {
|
|
238
|
+
const rebuilt2 = rebuildNode(child, doc, depth + 1, budget);
|
|
239
|
+
if (rebuilt2) a.appendChild(rebuilt2);
|
|
240
|
+
}
|
|
241
|
+
return a;
|
|
242
|
+
}
|
|
243
|
+
const rebuilt = doc.createElement(mapped);
|
|
244
|
+
for (const child of Array.from(el.childNodes)) {
|
|
245
|
+
const childNode = rebuildNode(child, doc, depth + 1, budget);
|
|
246
|
+
if (childNode) rebuilt.appendChild(childNode);
|
|
247
|
+
}
|
|
248
|
+
return rebuilt;
|
|
249
|
+
}
|
|
250
|
+
|
|
145
251
|
// src/plugins/guides/block-builder.ts
|
|
146
252
|
function buildStepContent(step, doc, callbacks) {
|
|
147
253
|
const container = doc.createElement("div");
|
|
@@ -236,9 +342,13 @@ function buildContentBlock(block, doc) {
|
|
|
236
342
|
return el;
|
|
237
343
|
}
|
|
238
344
|
case "text": {
|
|
239
|
-
const el = doc.createElement("
|
|
345
|
+
const el = doc.createElement("div");
|
|
240
346
|
el.className = "veo-guide-text";
|
|
241
|
-
|
|
347
|
+
if (typeof block.html === "string" && block.html) {
|
|
348
|
+
el.appendChild(renderRichText(block.html, doc));
|
|
349
|
+
} else {
|
|
350
|
+
el.textContent = typeof block.text === "string" ? block.text : "";
|
|
351
|
+
}
|
|
242
352
|
applyBlockStyle(el, block.style, "text");
|
|
243
353
|
return el;
|
|
244
354
|
}
|
|
@@ -264,7 +374,11 @@ function buildButtonBlock(block, doc, callbacks) {
|
|
|
264
374
|
btn.addEventListener("click", () => {
|
|
265
375
|
const action = block.action ?? "dismiss";
|
|
266
376
|
const url = action === "url" && typeof block.url === "string" && isSafeUrl(block.url) ? block.url : void 0;
|
|
267
|
-
|
|
377
|
+
const meta = {
|
|
378
|
+
...typeof block.id === "string" && block.id ? { buttonId: block.id } : {},
|
|
379
|
+
...typeof block.text === "string" && block.text ? { buttonText: block.text } : {}
|
|
380
|
+
};
|
|
381
|
+
callbacks.onCtaClick(action, url, Object.keys(meta).length ? meta : void 0);
|
|
268
382
|
});
|
|
269
383
|
return btn;
|
|
270
384
|
}
|
|
@@ -279,6 +393,10 @@ function clampNum(n, min, max) {
|
|
|
279
393
|
function applyBlockStyle(el, style, kind) {
|
|
280
394
|
if (!style || typeof style !== "object") return;
|
|
281
395
|
const s = style;
|
|
396
|
+
if (kind === "image" && s.bleed === true) {
|
|
397
|
+
el.classList.add("veo-guide-image--bleed");
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
282
400
|
const align = typeof s.align === "string" ? s.align : null;
|
|
283
401
|
if (align && (align === "left" || align === "center" || align === "right")) {
|
|
284
402
|
if (kind === "text") el.style.textAlign = align;
|
|
@@ -326,78 +444,6 @@ function readCtaUrl(step) {
|
|
|
326
444
|
return isSafeUrl(candidate) ? candidate : void 0;
|
|
327
445
|
}
|
|
328
446
|
|
|
329
|
-
// src/plugins/guides/walkthrough-block-builder.ts
|
|
330
|
-
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
331
|
-
const container = doc.createElement("div");
|
|
332
|
-
container.className = "veo-guide-content";
|
|
333
|
-
const counter = doc.createElement("div");
|
|
334
|
-
counter.className = "veo-walkthrough-counter";
|
|
335
|
-
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
336
|
-
container.appendChild(counter);
|
|
337
|
-
const progress = doc.createElement("div");
|
|
338
|
-
progress.className = "veo-walkthrough-progress";
|
|
339
|
-
for (let i = 0; i < totalSteps; i++) {
|
|
340
|
-
const dot = doc.createElement("span");
|
|
341
|
-
dot.className = "veo-walkthrough-progress-dot";
|
|
342
|
-
if (i < stepIndex) dot.classList.add("completed");
|
|
343
|
-
if (i === stepIndex) dot.classList.add("active");
|
|
344
|
-
progress.appendChild(dot);
|
|
345
|
-
}
|
|
346
|
-
container.appendChild(progress);
|
|
347
|
-
if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
|
|
348
|
-
const img = doc.createElement("img");
|
|
349
|
-
img.className = "veo-guide-image";
|
|
350
|
-
img.src = step.imageUrl;
|
|
351
|
-
img.alt = typeof step.title === "string" ? step.title : "";
|
|
352
|
-
container.appendChild(img);
|
|
353
|
-
}
|
|
354
|
-
if (typeof step.title === "string" && step.title) {
|
|
355
|
-
const heading = doc.createElement("h2");
|
|
356
|
-
heading.className = "veo-guide-title";
|
|
357
|
-
heading.textContent = step.title;
|
|
358
|
-
container.appendChild(heading);
|
|
359
|
-
}
|
|
360
|
-
if (typeof step.content === "string" && step.content) {
|
|
361
|
-
const paragraph = doc.createElement("p");
|
|
362
|
-
paragraph.className = "veo-guide-text";
|
|
363
|
-
paragraph.textContent = step.content;
|
|
364
|
-
container.appendChild(paragraph);
|
|
365
|
-
}
|
|
366
|
-
const actions = doc.createElement("div");
|
|
367
|
-
actions.className = "veo-walkthrough-actions";
|
|
368
|
-
const skipBtn = doc.createElement("button");
|
|
369
|
-
skipBtn.type = "button";
|
|
370
|
-
skipBtn.className = "veo-walkthrough-skip";
|
|
371
|
-
skipBtn.textContent = "Omitir";
|
|
372
|
-
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
373
|
-
actions.appendChild(skipBtn);
|
|
374
|
-
const rightGroup = doc.createElement("div");
|
|
375
|
-
rightGroup.className = "veo-walkthrough-actions-right";
|
|
376
|
-
if (stepIndex > 0) {
|
|
377
|
-
const backBtn = doc.createElement("button");
|
|
378
|
-
backBtn.type = "button";
|
|
379
|
-
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
380
|
-
backBtn.textContent = "Atr\xE1s";
|
|
381
|
-
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
382
|
-
rightGroup.appendChild(backBtn);
|
|
383
|
-
}
|
|
384
|
-
const isLastStep = stepIndex === totalSteps - 1;
|
|
385
|
-
const primaryBtn = doc.createElement("button");
|
|
386
|
-
primaryBtn.type = "button";
|
|
387
|
-
primaryBtn.className = "veo-guide-cta";
|
|
388
|
-
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
389
|
-
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
390
|
-
primaryBtn.addEventListener("click", () => {
|
|
391
|
-
if (isLastStep) callbacks.onComplete();
|
|
392
|
-
else callbacks.onNext();
|
|
393
|
-
});
|
|
394
|
-
rightGroup.appendChild(primaryBtn);
|
|
395
|
-
actions.appendChild(rightGroup);
|
|
396
|
-
container.appendChild(actions);
|
|
397
|
-
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
398
|
-
return container;
|
|
399
|
-
}
|
|
400
|
-
|
|
401
447
|
// src/plugins/guides/guide-design.ts
|
|
402
448
|
var DARK_THEME = {
|
|
403
449
|
"--veo-bg": "#1f2937",
|
|
@@ -470,6 +516,9 @@ function applyDesignVars(host, style) {
|
|
|
470
516
|
if (typeof s.width === "number" && Number.isFinite(s.width)) {
|
|
471
517
|
host.style.setProperty("--veo-width", `${clamp(s.width, 220, 720)}px`);
|
|
472
518
|
}
|
|
519
|
+
if (typeof s.height === "number" && Number.isFinite(s.height)) {
|
|
520
|
+
host.style.setProperty("--veo-min-h", `${clamp(s.height, 120, 900)}px`);
|
|
521
|
+
}
|
|
473
522
|
if (s.align === "left" || s.align === "center" || s.align === "right") {
|
|
474
523
|
host.style.setProperty("--veo-actions-justify", ALIGN_JUSTIFY[s.align]);
|
|
475
524
|
}
|
|
@@ -555,6 +604,68 @@ function applyElementVars(host, prefix, raw) {
|
|
|
555
604
|
}
|
|
556
605
|
}
|
|
557
606
|
|
|
607
|
+
// src/plugins/guides/inline-host.ts
|
|
608
|
+
function readInlinePosition(style) {
|
|
609
|
+
const p = style?.inlinePosition;
|
|
610
|
+
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
611
|
+
}
|
|
612
|
+
function insertHost(anchor, host, position) {
|
|
613
|
+
switch (position) {
|
|
614
|
+
case "before":
|
|
615
|
+
anchor.before(host);
|
|
616
|
+
break;
|
|
617
|
+
case "after":
|
|
618
|
+
anchor.after(host);
|
|
619
|
+
break;
|
|
620
|
+
case "prepend":
|
|
621
|
+
anchor.prepend(host);
|
|
622
|
+
break;
|
|
623
|
+
case "append":
|
|
624
|
+
anchor.append(host);
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function keepHostAttached(host, selector, position) {
|
|
629
|
+
const id = window.setInterval(() => {
|
|
630
|
+
if (host.isConnected) return;
|
|
631
|
+
const anchor = document.querySelector(selector);
|
|
632
|
+
if (anchor) insertHost(anchor, host, position);
|
|
633
|
+
}, 1e3);
|
|
634
|
+
return () => window.clearInterval(id);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// src/plugins/guides/wait-for-element.ts
|
|
638
|
+
function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
|
|
639
|
+
return new Promise((resolve) => {
|
|
640
|
+
const safeQuery = () => {
|
|
641
|
+
try {
|
|
642
|
+
return document.querySelector(selector);
|
|
643
|
+
} catch {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
const existing = safeQuery();
|
|
648
|
+
if (existing) {
|
|
649
|
+
resolve(existing);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
let resolved = false;
|
|
653
|
+
const finish = (el) => {
|
|
654
|
+
if (resolved) return;
|
|
655
|
+
resolved = true;
|
|
656
|
+
observer.disconnect();
|
|
657
|
+
clearTimeout(timer);
|
|
658
|
+
resolve(el);
|
|
659
|
+
};
|
|
660
|
+
const observer = new MutationObserver(() => {
|
|
661
|
+
const el = safeQuery();
|
|
662
|
+
if (el) finish(el);
|
|
663
|
+
});
|
|
664
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
665
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
558
669
|
// src/plugins/guides/styles.ts
|
|
559
670
|
var GUIDE_STYLES = `
|
|
560
671
|
:host {
|
|
@@ -576,6 +687,13 @@ var GUIDE_STYLES = `
|
|
|
576
687
|
z-index: ${GUIDE_Z_INDEX};
|
|
577
688
|
animation: veo-fade-in 180ms ease-out;
|
|
578
689
|
}
|
|
690
|
+
/* Sin backdrop (style.backdrop === false): la app queda usable detr\xE1s; solo la
|
|
691
|
+
tarjeta captura el mouse. El click-en-backdrop deja de cerrar (no hay backdrop). */
|
|
692
|
+
.veo-modal-overlay--none {
|
|
693
|
+
background: transparent;
|
|
694
|
+
pointer-events: none;
|
|
695
|
+
}
|
|
696
|
+
.veo-modal-overlay--none .veo-modal-card { pointer-events: auto; }
|
|
579
697
|
/*
|
|
580
698
|
* Posici\xF3n libre/preset: --veo-pos-x/y son porcentajes (default 50% = centro).
|
|
581
699
|
* El truco translate(-pos) alinea la MISMA fracci\xF3n de la tarjeta con esa
|
|
@@ -592,6 +710,7 @@ var GUIDE_STYLES = `
|
|
|
592
710
|
border: var(--veo-border-width, 0) solid var(--veo-border-color, transparent);
|
|
593
711
|
padding: var(--veo-pad, 24px);
|
|
594
712
|
max-width: var(--veo-width); width: 90%;
|
|
713
|
+
min-height: var(--veo-min-h, auto);
|
|
595
714
|
box-shadow: var(--veo-shadow);
|
|
596
715
|
animation: veo-fade-in 180ms ease-out;
|
|
597
716
|
}
|
|
@@ -607,6 +726,14 @@ var GUIDE_STYLES = `
|
|
|
607
726
|
}
|
|
608
727
|
.veo-banner-top { top: 0; }
|
|
609
728
|
.veo-banner-bottom { bottom: 0; }
|
|
729
|
+
/* Banner EMBEBIDO en un contenedor (step.selector): fluye dentro del contenedor
|
|
730
|
+
y empuja su contenido, en vez de flotar fijo sobre la pantalla. */
|
|
731
|
+
.veo-banner-embedded {
|
|
732
|
+
position: static;
|
|
733
|
+
left: auto; right: auto;
|
|
734
|
+
width: 100%;
|
|
735
|
+
border-radius: var(--veo-radius, 0);
|
|
736
|
+
}
|
|
610
737
|
|
|
611
738
|
.veo-tooltip {
|
|
612
739
|
position: absolute;
|
|
@@ -660,6 +787,21 @@ var GUIDE_STYLES = `
|
|
|
660
787
|
border-radius: var(--veo-image-radius, 8px);
|
|
661
788
|
margin: var(--veo-image-mt, 0) 0 var(--veo-image-mb, 12px);
|
|
662
789
|
}
|
|
790
|
+
/* Imagen A SANGRE: rompe el padding de la tarjeta y ocupa el ancho completo
|
|
791
|
+
(hero estilo anuncio). Como primer bloque, hereda el redondeo superior. */
|
|
792
|
+
.veo-guide-image--bleed {
|
|
793
|
+
width: calc(100% + var(--veo-pad, 24px) * 2);
|
|
794
|
+
max-width: none;
|
|
795
|
+
max-height: 280px;
|
|
796
|
+
align-self: auto;
|
|
797
|
+
border-radius: 0;
|
|
798
|
+
margin: 0 calc(var(--veo-pad, 24px) * -1) 12px;
|
|
799
|
+
}
|
|
800
|
+
.veo-guide-content > .veo-guide-image--bleed:first-child {
|
|
801
|
+
margin-top: calc(var(--veo-pad, 24px) * -1);
|
|
802
|
+
border-radius: calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px))
|
|
803
|
+
calc(var(--veo-radius, 12px) - var(--veo-border-width, 0px)) 0 0;
|
|
804
|
+
}
|
|
663
805
|
.veo-guide-title {
|
|
664
806
|
font-size: var(--veo-title-size, 18px); font-weight: 600; line-height: 1.3;
|
|
665
807
|
text-align: var(--veo-title-align, left);
|
|
@@ -672,6 +814,18 @@ var GUIDE_STYLES = `
|
|
|
672
814
|
margin: var(--veo-text-mt, 0) 0 var(--veo-text-mb, 16px);
|
|
673
815
|
color: var(--veo-text-color, var(--veo-text-secondary));
|
|
674
816
|
}
|
|
817
|
+
/* Rich text dentro de un bloque de texto (p/listas/links/\xE9nfasis). */
|
|
818
|
+
.veo-guide-text p { margin: 0 0 8px; }
|
|
819
|
+
.veo-guide-text p:last-child { margin-bottom: 0; }
|
|
820
|
+
.veo-guide-text ul, .veo-guide-text ol { margin: 0 0 8px; padding-left: 20px; }
|
|
821
|
+
.veo-guide-text ul { list-style: disc; }
|
|
822
|
+
.veo-guide-text ol { list-style: decimal; }
|
|
823
|
+
.veo-guide-text li { margin: 2px 0; display: list-item; }
|
|
824
|
+
.veo-guide-text a { color: var(--veo-primary); text-decoration: underline; cursor: pointer; }
|
|
825
|
+
.veo-guide-text strong { font-weight: 600; }
|
|
826
|
+
.veo-guide-text em { font-style: italic; }
|
|
827
|
+
.veo-guide-text u { text-decoration: underline; }
|
|
828
|
+
.veo-guide-text s { text-decoration: line-through; }
|
|
675
829
|
.veo-guide-actions {
|
|
676
830
|
display: flex; gap: 8px; justify-content: var(--veo-actions-justify);
|
|
677
831
|
}
|
|
@@ -810,6 +964,44 @@ var GUIDE_STYLES = `
|
|
|
810
964
|
}
|
|
811
965
|
.veo-walkthrough-skip:hover { color: var(--veo-text); }
|
|
812
966
|
|
|
967
|
+
/* \u2500\u2500 Badge (elemento inyectado junto al ancla que abre un tooltip) \u2500\u2500 */
|
|
968
|
+
.veo-badge {
|
|
969
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
970
|
+
border: none; padding: 0; margin: 0 4px;
|
|
971
|
+
background: none; cursor: pointer;
|
|
972
|
+
line-height: 1; vertical-align: middle;
|
|
973
|
+
font-family: inherit;
|
|
974
|
+
}
|
|
975
|
+
.veo-badge:focus-visible { outline: 2px solid var(--veo-primary); outline-offset: 2px; }
|
|
976
|
+
.veo-badge--icon {
|
|
977
|
+
border-radius: 50%;
|
|
978
|
+
background: color-mix(in srgb, var(--veo-primary) 14%, transparent);
|
|
979
|
+
color: var(--veo-primary);
|
|
980
|
+
font-weight: 600;
|
|
981
|
+
}
|
|
982
|
+
.veo-badge--dot {
|
|
983
|
+
border-radius: 50%;
|
|
984
|
+
background: var(--veo-primary);
|
|
985
|
+
animation: veo-badge-pulse 2s ease-out infinite;
|
|
986
|
+
}
|
|
987
|
+
.veo-badge--pill {
|
|
988
|
+
border-radius: 999px;
|
|
989
|
+
background: var(--veo-primary);
|
|
990
|
+
color: #fff;
|
|
991
|
+
font-weight: 600;
|
|
992
|
+
padding: 3px 9px;
|
|
993
|
+
white-space: nowrap;
|
|
994
|
+
}
|
|
995
|
+
.veo-badge--image img { display: block; border-radius: 4px; object-fit: cover; }
|
|
996
|
+
/* El tooltip del badge usa strategy fixed (el host vive dentro del flujo del
|
|
997
|
+
cliente; un absoluto se recortar\xEDa con overflow de ancestros). */
|
|
998
|
+
.veo-badge-tooltip { position: fixed; }
|
|
999
|
+
@keyframes veo-badge-pulse {
|
|
1000
|
+
0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--veo-primary) 45%, transparent); }
|
|
1001
|
+
70% { box-shadow: 0 0 0 7px transparent; }
|
|
1002
|
+
100% { box-shadow: 0 0 0 0 transparent; }
|
|
1003
|
+
}
|
|
1004
|
+
|
|
813
1005
|
.veo-custom-floating {
|
|
814
1006
|
position: fixed;
|
|
815
1007
|
z-index: ${GUIDE_Z_INDEX};
|
|
@@ -905,52 +1097,412 @@ var BaseRenderer = class {
|
|
|
905
1097
|
this.shadow = shadow;
|
|
906
1098
|
return { host, shadow, root };
|
|
907
1099
|
}
|
|
908
|
-
/**
|
|
909
|
-
* Aplica la tematización por datos del step (`step.style`) como variables
|
|
910
|
-
* CSS sobre el host. Llamar tras `createHost()`. No-op si no hay host o
|
|
911
|
-
* style. Ver `guide-design.ts`.
|
|
912
|
-
*/
|
|
913
|
-
applyDesign(style) {
|
|
914
|
-
if (this.host) applyDesignVars(this.host, style);
|
|
1100
|
+
/**
|
|
1101
|
+
* Aplica la tematización por datos del step (`step.style`) como variables
|
|
1102
|
+
* CSS sobre el host. Llamar tras `createHost()`. No-op si no hay host o
|
|
1103
|
+
* style. Ver `guide-design.ts`.
|
|
1104
|
+
*/
|
|
1105
|
+
applyDesign(style) {
|
|
1106
|
+
if (this.host) applyDesignVars(this.host, style);
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Registra una función a ejecutar en `destroy()`. Útil para limpiar
|
|
1110
|
+
* listeners globales (scroll, resize) o intervalos.
|
|
1111
|
+
*/
|
|
1112
|
+
registerCleanup(fn) {
|
|
1113
|
+
this.cleanups.push(fn);
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Host de la guía montada (o `null` si aún no se montó / ya se destruyó).
|
|
1117
|
+
* Lo usa el modo builder para adjuntar manipulación directa (drag/resize)
|
|
1118
|
+
* sobre el shadow root abierto sin tocar los renderers.
|
|
1119
|
+
*/
|
|
1120
|
+
hostElement() {
|
|
1121
|
+
return this.host;
|
|
1122
|
+
}
|
|
1123
|
+
/** Remueve el host del DOM y corre todas las funciones de cleanup. */
|
|
1124
|
+
destroy() {
|
|
1125
|
+
for (const fn of this.cleanups) {
|
|
1126
|
+
try {
|
|
1127
|
+
fn();
|
|
1128
|
+
} catch {
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
this.cleanups = [];
|
|
1132
|
+
if (this.host?.parentNode) {
|
|
1133
|
+
this.host.parentNode.removeChild(this.host);
|
|
1134
|
+
}
|
|
1135
|
+
this.host = null;
|
|
1136
|
+
this.shadow = null;
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
// src/plugins/guides/renderers/floating-arrow.ts
|
|
1141
|
+
var STATIC_SIDE = {
|
|
1142
|
+
top: "bottom",
|
|
1143
|
+
bottom: "top",
|
|
1144
|
+
left: "right",
|
|
1145
|
+
right: "left"
|
|
1146
|
+
};
|
|
1147
|
+
function positionArrow(arrowEl, placement, data) {
|
|
1148
|
+
const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
|
|
1149
|
+
for (const prop of ["top", "bottom", "left", "right"]) {
|
|
1150
|
+
arrowEl.style.setProperty(prop, "");
|
|
1151
|
+
}
|
|
1152
|
+
if (data?.x != null) arrowEl.style.setProperty("left", `${data.x}px`);
|
|
1153
|
+
if (data?.y != null) arrowEl.style.setProperty("top", `${data.y}px`);
|
|
1154
|
+
arrowEl.style.setProperty(side, "-6px");
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// src/plugins/guides/renderers/badge-renderer.ts
|
|
1158
|
+
var HOVER_CLOSE_DELAY_MS = 150;
|
|
1159
|
+
var BadgeRenderer = class extends BaseRenderer {
|
|
1160
|
+
constructor() {
|
|
1161
|
+
super(...arguments);
|
|
1162
|
+
this.tooltip = null;
|
|
1163
|
+
this.arrowEl = null;
|
|
1164
|
+
this.badgeBtn = null;
|
|
1165
|
+
this.open = false;
|
|
1166
|
+
this.shownEmitted = false;
|
|
1167
|
+
this.trigger = "hover";
|
|
1168
|
+
this.side = "top";
|
|
1169
|
+
this.closeTimer = null;
|
|
1170
|
+
this.stopFloat = null;
|
|
1171
|
+
}
|
|
1172
|
+
async render(context) {
|
|
1173
|
+
const step = context.guide.guideSteps[0];
|
|
1174
|
+
if (!step) return;
|
|
1175
|
+
const selector = step.selector ?? context.guide.activationRules.selector;
|
|
1176
|
+
if (typeof selector !== "string" || selector.length === 0) return;
|
|
1177
|
+
const anchor = await waitForElement(selector);
|
|
1178
|
+
if (!anchor) return;
|
|
1179
|
+
const { host, root } = this.createHost();
|
|
1180
|
+
const ownerDocument = root.ownerDocument ?? document;
|
|
1181
|
+
this.applyDesign(step.style);
|
|
1182
|
+
this.trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
|
|
1183
|
+
const rawSide = step.style?.tooltipPlacement;
|
|
1184
|
+
this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
|
|
1185
|
+
const position = readInlinePosition(step.style);
|
|
1186
|
+
host.style.display = "inline-flex";
|
|
1187
|
+
host.style.verticalAlign = "middle";
|
|
1188
|
+
const badge = buildBadgeElement(readBadgeConfig(step), ownerDocument);
|
|
1189
|
+
root.appendChild(badge);
|
|
1190
|
+
this.badgeBtn = badge;
|
|
1191
|
+
insertHost(anchor, host, position);
|
|
1192
|
+
this.registerCleanup(keepHostAttached(host, selector, position));
|
|
1193
|
+
const tooltip = ownerDocument.createElement("div");
|
|
1194
|
+
tooltip.className = "veo-tooltip veo-badge-tooltip";
|
|
1195
|
+
tooltip.style.display = "none";
|
|
1196
|
+
const emit = (action, meta) => {
|
|
1197
|
+
context.onInteraction({
|
|
1198
|
+
guideId: context.guide.guideId,
|
|
1199
|
+
stepIndex: 0,
|
|
1200
|
+
action,
|
|
1201
|
+
...meta ? { metadata: meta } : {}
|
|
1202
|
+
});
|
|
1203
|
+
};
|
|
1204
|
+
const buildTooltipContent = (s) => buildStepContent(s, ownerDocument, {
|
|
1205
|
+
onCtaClick: (action, url, meta) => {
|
|
1206
|
+
emit("cta_clicked", meta);
|
|
1207
|
+
if (action === "url" && url) window.open(url, "_blank", "noopener,noreferrer");
|
|
1208
|
+
if (action === "dismiss") {
|
|
1209
|
+
emit("dismissed");
|
|
1210
|
+
context.onClose();
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
this.closeTooltip();
|
|
1214
|
+
},
|
|
1215
|
+
onDismiss: () => {
|
|
1216
|
+
emit("dismissed");
|
|
1217
|
+
context.onClose();
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
const content = buildTooltipContent(step);
|
|
1221
|
+
tooltip.appendChild(content);
|
|
1222
|
+
const arrowEl = ownerDocument.createElement("div");
|
|
1223
|
+
arrowEl.className = "veo-tooltip-arrow";
|
|
1224
|
+
tooltip.appendChild(arrowEl);
|
|
1225
|
+
root.appendChild(tooltip);
|
|
1226
|
+
this.tooltip = tooltip;
|
|
1227
|
+
this.arrowEl = arrowEl;
|
|
1228
|
+
this.liveContainer = tooltip;
|
|
1229
|
+
this.liveContent = content;
|
|
1230
|
+
this.liveKey = this.liveKeyOf(step);
|
|
1231
|
+
this.liveBuild = buildTooltipContent;
|
|
1232
|
+
const openNow = () => {
|
|
1233
|
+
this.cancelClose();
|
|
1234
|
+
if (!this.open) {
|
|
1235
|
+
this.openTooltip(badge);
|
|
1236
|
+
if (!this.shownEmitted) {
|
|
1237
|
+
this.shownEmitted = true;
|
|
1238
|
+
emit("shown");
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
if (this.trigger === "hover") {
|
|
1243
|
+
const scheduleClose = () => {
|
|
1244
|
+
this.cancelClose();
|
|
1245
|
+
this.closeTimer = window.setTimeout(() => this.closeTooltip(), HOVER_CLOSE_DELAY_MS);
|
|
1246
|
+
};
|
|
1247
|
+
badge.addEventListener("mouseenter", openNow);
|
|
1248
|
+
badge.addEventListener("focus", openNow);
|
|
1249
|
+
badge.addEventListener("mouseleave", scheduleClose);
|
|
1250
|
+
badge.addEventListener("blur", scheduleClose);
|
|
1251
|
+
tooltip.addEventListener("mouseenter", () => this.cancelClose());
|
|
1252
|
+
tooltip.addEventListener("mouseleave", scheduleClose);
|
|
1253
|
+
} else {
|
|
1254
|
+
badge.addEventListener("click", () => {
|
|
1255
|
+
if (this.open) this.closeTooltip();
|
|
1256
|
+
else openNow();
|
|
1257
|
+
});
|
|
1258
|
+
const onDocClick = (e) => {
|
|
1259
|
+
if (!this.open) return;
|
|
1260
|
+
if (e.composedPath().includes(host)) return;
|
|
1261
|
+
this.closeTooltip();
|
|
1262
|
+
};
|
|
1263
|
+
document.addEventListener("click", onDocClick, true);
|
|
1264
|
+
this.registerCleanup(() => document.removeEventListener("click", onDocClick, true));
|
|
1265
|
+
}
|
|
1266
|
+
const onKey = (e) => {
|
|
1267
|
+
if (e.key === "Escape" && this.open) this.closeTooltip();
|
|
1268
|
+
};
|
|
1269
|
+
document.addEventListener("keydown", onKey);
|
|
1270
|
+
this.registerCleanup(() => document.removeEventListener("keydown", onKey));
|
|
1271
|
+
if (context.isPreview) openNow();
|
|
1272
|
+
}
|
|
1273
|
+
/** Cambiar de ancla/posición/trigger requiere re-montar y re-cablear. */
|
|
1274
|
+
liveKeyOf(step) {
|
|
1275
|
+
const selector = step.selector ?? "";
|
|
1276
|
+
const position = readInlinePosition(step.style);
|
|
1277
|
+
const trigger = step.style?.badgeTrigger === "click" ? "click" : "hover";
|
|
1278
|
+
return `${selector}|${position}|${trigger}`;
|
|
1279
|
+
}
|
|
1280
|
+
onLiveUpdate(step) {
|
|
1281
|
+
if (this.badgeBtn) {
|
|
1282
|
+
const doc = this.badgeBtn.ownerDocument;
|
|
1283
|
+
const next = buildBadgeElement(readBadgeConfig(step), doc);
|
|
1284
|
+
this.badgeBtn.className = next.className;
|
|
1285
|
+
this.badgeBtn.setAttribute("style", next.getAttribute("style") ?? "");
|
|
1286
|
+
this.badgeBtn.replaceChildren(...Array.from(next.childNodes));
|
|
1287
|
+
}
|
|
1288
|
+
const rawSide = step.style?.tooltipPlacement;
|
|
1289
|
+
this.side = rawSide === "bottom" || rawSide === "left" || rawSide === "right" ? rawSide : "top";
|
|
1290
|
+
if (this.open && this.badgeBtn) this.position(this.badgeBtn);
|
|
1291
|
+
}
|
|
1292
|
+
destroy() {
|
|
1293
|
+
this.cancelClose();
|
|
1294
|
+
this.stopFloat?.();
|
|
1295
|
+
this.stopFloat = null;
|
|
1296
|
+
this.tooltip = null;
|
|
1297
|
+
this.arrowEl = null;
|
|
1298
|
+
this.badgeBtn = null;
|
|
1299
|
+
this.open = false;
|
|
1300
|
+
super.destroy();
|
|
1301
|
+
}
|
|
1302
|
+
openTooltip(badge) {
|
|
1303
|
+
if (!this.tooltip) return;
|
|
1304
|
+
this.tooltip.style.display = "block";
|
|
1305
|
+
this.open = true;
|
|
1306
|
+
void this.position(badge);
|
|
1307
|
+
const reposition = () => {
|
|
1308
|
+
void this.position(badge);
|
|
1309
|
+
};
|
|
1310
|
+
window.addEventListener("scroll", reposition, true);
|
|
1311
|
+
window.addEventListener("resize", reposition);
|
|
1312
|
+
this.stopFloat = () => {
|
|
1313
|
+
window.removeEventListener("scroll", reposition, true);
|
|
1314
|
+
window.removeEventListener("resize", reposition);
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
closeTooltip() {
|
|
1318
|
+
this.cancelClose();
|
|
1319
|
+
if (!this.tooltip || !this.open) return;
|
|
1320
|
+
this.tooltip.style.display = "none";
|
|
1321
|
+
this.open = false;
|
|
1322
|
+
this.stopFloat?.();
|
|
1323
|
+
this.stopFloat = null;
|
|
1324
|
+
}
|
|
1325
|
+
cancelClose() {
|
|
1326
|
+
if (this.closeTimer !== null) {
|
|
1327
|
+
window.clearTimeout(this.closeTimer);
|
|
1328
|
+
this.closeTimer = null;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
async position(badge) {
|
|
1332
|
+
const tooltip = this.tooltip;
|
|
1333
|
+
const arrowEl = this.arrowEl;
|
|
1334
|
+
if (!tooltip || !arrowEl) return;
|
|
1335
|
+
const { x, y, placement, middlewareData } = await dom.computePosition(badge, tooltip, {
|
|
1336
|
+
strategy: "fixed",
|
|
1337
|
+
placement: this.side,
|
|
1338
|
+
middleware: [dom.offset(8), dom.flip(), dom.shift({ padding: 8 }), dom.arrow({ element: arrowEl })]
|
|
1339
|
+
});
|
|
1340
|
+
tooltip.style.left = `${x}px`;
|
|
1341
|
+
tooltip.style.top = `${y}px`;
|
|
1342
|
+
positionArrow(arrowEl, placement, middlewareData.arrow);
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
function readBadgeConfig(step) {
|
|
1346
|
+
const raw = step.style?.badge;
|
|
1347
|
+
if (!raw || typeof raw !== "object") return { kind: "icon" };
|
|
1348
|
+
const b = raw;
|
|
1349
|
+
const kind = b.kind === "dot" || b.kind === "pill" || b.kind === "image" || b.kind === "icon" ? b.kind : "icon";
|
|
1350
|
+
return {
|
|
1351
|
+
kind,
|
|
1352
|
+
icon: typeof b.icon === "string" ? b.icon : null,
|
|
1353
|
+
text: typeof b.text === "string" ? b.text : null,
|
|
1354
|
+
imageUrl: typeof b.imageUrl === "string" ? b.imageUrl : null,
|
|
1355
|
+
color: typeof b.color === "string" && COLOR_RE.test(b.color.trim()) ? b.color.trim() : null,
|
|
1356
|
+
size: typeof b.size === "number" && Number.isFinite(b.size) ? b.size : null
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
function clamp2(n, min, max) {
|
|
1360
|
+
return Math.min(max, Math.max(min, n));
|
|
1361
|
+
}
|
|
1362
|
+
function buildBadgeElement(config, doc) {
|
|
1363
|
+
const btn = doc.createElement("button");
|
|
1364
|
+
btn.type = "button";
|
|
1365
|
+
btn.className = `veo-badge veo-badge--${config.kind}`;
|
|
1366
|
+
btn.setAttribute("aria-label", "M\xE1s informaci\xF3n");
|
|
1367
|
+
const size = clamp2(config.size ?? 18, 8, 48);
|
|
1368
|
+
switch (config.kind) {
|
|
1369
|
+
case "dot":
|
|
1370
|
+
btn.style.width = `${size}px`;
|
|
1371
|
+
btn.style.height = `${size}px`;
|
|
1372
|
+
if (config.color) btn.style.background = config.color;
|
|
1373
|
+
break;
|
|
1374
|
+
case "pill": {
|
|
1375
|
+
btn.textContent = config.text?.slice(0, 24) || "Nuevo";
|
|
1376
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 24)}px`;
|
|
1377
|
+
if (config.color) btn.style.background = config.color;
|
|
1378
|
+
break;
|
|
1379
|
+
}
|
|
1380
|
+
case "image": {
|
|
1381
|
+
if (config.imageUrl && isSafeUrl(config.imageUrl)) {
|
|
1382
|
+
const img = doc.createElement("img");
|
|
1383
|
+
img.src = config.imageUrl;
|
|
1384
|
+
img.alt = "";
|
|
1385
|
+
img.style.width = `${size}px`;
|
|
1386
|
+
img.style.height = `${size}px`;
|
|
1387
|
+
btn.appendChild(img);
|
|
1388
|
+
} else {
|
|
1389
|
+
btn.className = "veo-badge veo-badge--icon";
|
|
1390
|
+
btn.textContent = "?";
|
|
1391
|
+
btn.style.width = `${size}px`;
|
|
1392
|
+
btn.style.height = `${size}px`;
|
|
1393
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1394
|
+
}
|
|
1395
|
+
break;
|
|
1396
|
+
}
|
|
1397
|
+
default: {
|
|
1398
|
+
btn.textContent = (config.icon || "\u2139\uFE0F").slice(0, 4);
|
|
1399
|
+
btn.style.width = `${size}px`;
|
|
1400
|
+
btn.style.height = `${size}px`;
|
|
1401
|
+
btn.style.fontSize = `${clamp2(Math.round(size * 0.62), 8, 32)}px`;
|
|
1402
|
+
if (config.color) btn.style.color = config.color;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
return btn;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// src/plugins/guides/walkthrough-block-builder.ts
|
|
1410
|
+
function buildWalkthroughStepContent(step, stepIndex, totalSteps, doc, callbacks) {
|
|
1411
|
+
const container = doc.createElement("div");
|
|
1412
|
+
container.className = "veo-guide-content";
|
|
1413
|
+
const counter = doc.createElement("div");
|
|
1414
|
+
counter.className = "veo-walkthrough-counter";
|
|
1415
|
+
counter.textContent = `Paso ${stepIndex + 1} de ${totalSteps}`;
|
|
1416
|
+
container.appendChild(counter);
|
|
1417
|
+
const progress = doc.createElement("div");
|
|
1418
|
+
progress.className = "veo-walkthrough-progress";
|
|
1419
|
+
for (let i = 0; i < totalSteps; i++) {
|
|
1420
|
+
const dot = doc.createElement("span");
|
|
1421
|
+
dot.className = "veo-walkthrough-progress-dot";
|
|
1422
|
+
if (i < stepIndex) dot.classList.add("completed");
|
|
1423
|
+
if (i === stepIndex) dot.classList.add("active");
|
|
1424
|
+
progress.appendChild(dot);
|
|
1425
|
+
}
|
|
1426
|
+
container.appendChild(progress);
|
|
1427
|
+
if (typeof step.imageUrl === "string" && step.imageUrl && isSafeUrl(step.imageUrl)) {
|
|
1428
|
+
const img = doc.createElement("img");
|
|
1429
|
+
img.className = "veo-guide-image";
|
|
1430
|
+
img.src = step.imageUrl;
|
|
1431
|
+
img.alt = typeof step.title === "string" ? step.title : "";
|
|
1432
|
+
container.appendChild(img);
|
|
1433
|
+
}
|
|
1434
|
+
if (typeof step.title === "string" && step.title) {
|
|
1435
|
+
const heading = doc.createElement("h2");
|
|
1436
|
+
heading.className = "veo-guide-title";
|
|
1437
|
+
heading.textContent = step.title;
|
|
1438
|
+
container.appendChild(heading);
|
|
915
1439
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
this.cleanups.push(fn);
|
|
1440
|
+
if (typeof step.content === "string" && step.content) {
|
|
1441
|
+
const paragraph = doc.createElement("p");
|
|
1442
|
+
paragraph.className = "veo-guide-text";
|
|
1443
|
+
paragraph.textContent = step.content;
|
|
1444
|
+
container.appendChild(paragraph);
|
|
922
1445
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1446
|
+
const actions = doc.createElement("div");
|
|
1447
|
+
actions.className = "veo-walkthrough-actions";
|
|
1448
|
+
const skipBtn = doc.createElement("button");
|
|
1449
|
+
skipBtn.type = "button";
|
|
1450
|
+
skipBtn.className = "veo-walkthrough-skip";
|
|
1451
|
+
skipBtn.textContent = "Omitir";
|
|
1452
|
+
skipBtn.addEventListener("click", () => callbacks.onSkip());
|
|
1453
|
+
actions.appendChild(skipBtn);
|
|
1454
|
+
const rightGroup = doc.createElement("div");
|
|
1455
|
+
rightGroup.className = "veo-walkthrough-actions-right";
|
|
1456
|
+
if (stepIndex > 0) {
|
|
1457
|
+
const backBtn = doc.createElement("button");
|
|
1458
|
+
backBtn.type = "button";
|
|
1459
|
+
backBtn.className = "veo-walkthrough-btn-secondary";
|
|
1460
|
+
backBtn.textContent = "Atr\xE1s";
|
|
1461
|
+
backBtn.addEventListener("click", () => callbacks.onBack());
|
|
1462
|
+
rightGroup.appendChild(backBtn);
|
|
937
1463
|
}
|
|
938
|
-
|
|
1464
|
+
const isLastStep = stepIndex === totalSteps - 1;
|
|
1465
|
+
const primaryBtn = doc.createElement("button");
|
|
1466
|
+
primaryBtn.type = "button";
|
|
1467
|
+
primaryBtn.className = "veo-guide-cta";
|
|
1468
|
+
const defaultLabel = isLastStep ? "Finalizar" : "Siguiente";
|
|
1469
|
+
primaryBtn.textContent = typeof step.ctaText === "string" && step.ctaText ? step.ctaText : defaultLabel;
|
|
1470
|
+
primaryBtn.addEventListener("click", () => {
|
|
1471
|
+
if (isLastStep) callbacks.onComplete();
|
|
1472
|
+
else callbacks.onNext();
|
|
1473
|
+
});
|
|
1474
|
+
rightGroup.appendChild(primaryBtn);
|
|
1475
|
+
actions.appendChild(rightGroup);
|
|
1476
|
+
container.appendChild(actions);
|
|
1477
|
+
container.appendChild(createCloseButton(doc, () => callbacks.onSkip()));
|
|
1478
|
+
return container;
|
|
1479
|
+
}
|
|
939
1480
|
|
|
940
1481
|
// src/plugins/guides/renderers/banner-renderer.ts
|
|
941
1482
|
var BannerRenderer = class extends BaseRenderer {
|
|
942
|
-
render(context) {
|
|
1483
|
+
async render(context) {
|
|
943
1484
|
const idx = context.nav?.stepIndex ?? 0;
|
|
944
1485
|
const step = context.guide.guideSteps[idx];
|
|
945
1486
|
if (!step) return;
|
|
946
|
-
const
|
|
1487
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1488
|
+
const selector = context.nav ? null : step.selector?.trim() ?? null;
|
|
1489
|
+
let anchor = null;
|
|
1490
|
+
if (selector) {
|
|
1491
|
+
anchor = await waitForElement(selector);
|
|
1492
|
+
if (!anchor) return;
|
|
1493
|
+
}
|
|
1494
|
+
const { host, root } = this.createHost();
|
|
947
1495
|
this.applyDesign(step.style);
|
|
948
1496
|
const ownerDocument = root.ownerDocument ?? document;
|
|
949
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
950
1497
|
const banner = ownerDocument.createElement("div");
|
|
951
|
-
banner.className =
|
|
952
|
-
const dismiss = (action, ctaUrl) => {
|
|
953
|
-
context.onInteraction({
|
|
1498
|
+
banner.className = bannerClassOf(position, Boolean(anchor));
|
|
1499
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
1500
|
+
context.onInteraction({
|
|
1501
|
+
guideId: context.guide.guideId,
|
|
1502
|
+
stepIndex: idx,
|
|
1503
|
+
action,
|
|
1504
|
+
...meta ? { metadata: meta } : {}
|
|
1505
|
+
});
|
|
954
1506
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
955
1507
|
context.onClose();
|
|
956
1508
|
};
|
|
@@ -961,59 +1513,45 @@ var BannerRenderer = class extends BaseRenderer {
|
|
|
961
1513
|
ownerDocument,
|
|
962
1514
|
context.nav.callbacks
|
|
963
1515
|
) : buildStepContent(step, ownerDocument, {
|
|
964
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
1516
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
965
1517
|
onDismiss: () => dismiss("dismissed")
|
|
966
1518
|
});
|
|
967
1519
|
banner.appendChild(content);
|
|
968
1520
|
root.appendChild(banner);
|
|
1521
|
+
if (selector && anchor) {
|
|
1522
|
+
host.style.display = "block";
|
|
1523
|
+
const insertAt = position === "top" ? "prepend" : "append";
|
|
1524
|
+
insertHost(anchor, host, insertAt);
|
|
1525
|
+
this.registerCleanup(keepHostAttached(host, selector, insertAt));
|
|
1526
|
+
}
|
|
969
1527
|
if (!context.nav) {
|
|
970
1528
|
this.liveContainer = banner;
|
|
971
1529
|
this.liveContent = content;
|
|
972
1530
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
973
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
1531
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
974
1532
|
onDismiss: () => dismiss("dismissed")
|
|
975
1533
|
});
|
|
1534
|
+
this.liveKey = this.liveKeyOf(step);
|
|
976
1535
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
977
1536
|
}
|
|
978
1537
|
}
|
|
1538
|
+
/** Cambiar de contenedor o de top/bottom embebido requiere re-insertar → remontar. */
|
|
1539
|
+
liveKeyOf(step) {
|
|
1540
|
+
const selector = step.selector?.trim() ?? "";
|
|
1541
|
+
if (!selector) return "";
|
|
1542
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1543
|
+
return `${selector}|${position}`;
|
|
1544
|
+
}
|
|
979
1545
|
onLiveUpdate(step) {
|
|
980
1546
|
if (this.liveContainer) {
|
|
981
1547
|
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
982
|
-
|
|
1548
|
+
const embedded = Boolean(step.selector?.trim());
|
|
1549
|
+
this.liveContainer.className = bannerClassOf(position, embedded);
|
|
983
1550
|
}
|
|
984
1551
|
}
|
|
985
1552
|
};
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
function waitForElement(selector, timeoutMs = DEFAULT_ANCHOR_WAIT_MS) {
|
|
989
|
-
return new Promise((resolve) => {
|
|
990
|
-
const safeQuery = () => {
|
|
991
|
-
try {
|
|
992
|
-
return document.querySelector(selector);
|
|
993
|
-
} catch {
|
|
994
|
-
return null;
|
|
995
|
-
}
|
|
996
|
-
};
|
|
997
|
-
const existing = safeQuery();
|
|
998
|
-
if (existing) {
|
|
999
|
-
resolve(existing);
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
1002
|
-
let resolved = false;
|
|
1003
|
-
const finish = (el) => {
|
|
1004
|
-
if (resolved) return;
|
|
1005
|
-
resolved = true;
|
|
1006
|
-
observer.disconnect();
|
|
1007
|
-
clearTimeout(timer);
|
|
1008
|
-
resolve(el);
|
|
1009
|
-
};
|
|
1010
|
-
const observer = new MutationObserver(() => {
|
|
1011
|
-
const el = safeQuery();
|
|
1012
|
-
if (el) finish(el);
|
|
1013
|
-
});
|
|
1014
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
1015
|
-
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
1016
|
-
});
|
|
1553
|
+
function bannerClassOf(position, embedded) {
|
|
1554
|
+
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
1017
1555
|
}
|
|
1018
1556
|
|
|
1019
1557
|
// src/utils/uuid.ts
|
|
@@ -1548,36 +2086,6 @@ var FormRenderer = class extends BaseRenderer {
|
|
|
1548
2086
|
}
|
|
1549
2087
|
};
|
|
1550
2088
|
|
|
1551
|
-
// src/plugins/guides/inline-host.ts
|
|
1552
|
-
function readInlinePosition(style) {
|
|
1553
|
-
const p = style?.inlinePosition;
|
|
1554
|
-
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
1555
|
-
}
|
|
1556
|
-
function insertHost(anchor, host, position) {
|
|
1557
|
-
switch (position) {
|
|
1558
|
-
case "before":
|
|
1559
|
-
anchor.before(host);
|
|
1560
|
-
break;
|
|
1561
|
-
case "after":
|
|
1562
|
-
anchor.after(host);
|
|
1563
|
-
break;
|
|
1564
|
-
case "prepend":
|
|
1565
|
-
anchor.prepend(host);
|
|
1566
|
-
break;
|
|
1567
|
-
case "append":
|
|
1568
|
-
anchor.append(host);
|
|
1569
|
-
break;
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
function keepHostAttached(host, selector, position) {
|
|
1573
|
-
const id = window.setInterval(() => {
|
|
1574
|
-
if (host.isConnected) return;
|
|
1575
|
-
const anchor = document.querySelector(selector);
|
|
1576
|
-
if (anchor) insertHost(anchor, host, position);
|
|
1577
|
-
}, 1e3);
|
|
1578
|
-
return () => window.clearInterval(id);
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
2089
|
// src/plugins/guides/renderers/inline-custom-renderer.ts
|
|
1582
2090
|
var InlineCustomRenderer = class extends BaseRenderer {
|
|
1583
2091
|
async render(context) {
|
|
@@ -1645,13 +2153,18 @@ var InlineRenderer = class extends BaseRenderer {
|
|
|
1645
2153
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1646
2154
|
const card = ownerDocument.createElement("div");
|
|
1647
2155
|
card.className = "veo-inline";
|
|
1648
|
-
const dismiss = (action, ctaUrl) => {
|
|
1649
|
-
context.onInteraction({
|
|
2156
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2157
|
+
context.onInteraction({
|
|
2158
|
+
guideId: context.guide.guideId,
|
|
2159
|
+
stepIndex: 0,
|
|
2160
|
+
action,
|
|
2161
|
+
...meta ? { metadata: meta } : {}
|
|
2162
|
+
});
|
|
1650
2163
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1651
2164
|
context.onClose();
|
|
1652
2165
|
};
|
|
1653
2166
|
const content = buildStepContent(step, ownerDocument, {
|
|
1654
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2167
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1655
2168
|
onDismiss: () => dismiss("dismissed")
|
|
1656
2169
|
});
|
|
1657
2170
|
card.appendChild(content);
|
|
@@ -1660,7 +2173,7 @@ var InlineRenderer = class extends BaseRenderer {
|
|
|
1660
2173
|
this.liveContent = content;
|
|
1661
2174
|
this.liveKey = `${selector}|${position}`;
|
|
1662
2175
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1663
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2176
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1664
2177
|
onDismiss: () => dismiss("dismissed")
|
|
1665
2178
|
});
|
|
1666
2179
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
@@ -1671,7 +2184,14 @@ var InlineRenderer = class extends BaseRenderer {
|
|
|
1671
2184
|
};
|
|
1672
2185
|
|
|
1673
2186
|
// src/plugins/guides/renderers/modal-renderer.ts
|
|
2187
|
+
function overlayClassOf(step) {
|
|
2188
|
+
return step.style?.backdrop === false ? "veo-modal-overlay veo-modal-overlay--none" : "veo-modal-overlay";
|
|
2189
|
+
}
|
|
1674
2190
|
var ModalRenderer = class extends BaseRenderer {
|
|
2191
|
+
constructor() {
|
|
2192
|
+
super(...arguments);
|
|
2193
|
+
this.overlay = null;
|
|
2194
|
+
}
|
|
1675
2195
|
render(context) {
|
|
1676
2196
|
const idx = context.nav?.stepIndex ?? 0;
|
|
1677
2197
|
const step = context.guide.guideSteps[idx];
|
|
@@ -1680,11 +2200,17 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
1680
2200
|
this.applyDesign(step.style);
|
|
1681
2201
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1682
2202
|
const overlay = ownerDocument.createElement("div");
|
|
1683
|
-
overlay.className =
|
|
2203
|
+
overlay.className = overlayClassOf(step);
|
|
2204
|
+
this.overlay = overlay;
|
|
1684
2205
|
const card = ownerDocument.createElement("div");
|
|
1685
2206
|
card.className = "veo-modal-card";
|
|
1686
|
-
const dismiss = (action, ctaUrl) => {
|
|
1687
|
-
context.onInteraction({
|
|
2207
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
2208
|
+
context.onInteraction({
|
|
2209
|
+
guideId: context.guide.guideId,
|
|
2210
|
+
stepIndex: idx,
|
|
2211
|
+
action,
|
|
2212
|
+
...meta ? { metadata: meta } : {}
|
|
2213
|
+
});
|
|
1688
2214
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1689
2215
|
context.onClose();
|
|
1690
2216
|
};
|
|
@@ -1699,7 +2225,7 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
1699
2225
|
ownerDocument,
|
|
1700
2226
|
context.nav.callbacks
|
|
1701
2227
|
) : buildStepContent(step, ownerDocument, {
|
|
1702
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2228
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1703
2229
|
onDismiss: () => dismiss("dismissed")
|
|
1704
2230
|
});
|
|
1705
2231
|
card.appendChild(content);
|
|
@@ -1709,7 +2235,7 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
1709
2235
|
this.liveContainer = card;
|
|
1710
2236
|
this.liveContent = content;
|
|
1711
2237
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1712
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2238
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1713
2239
|
onDismiss: () => dismiss("dismissed")
|
|
1714
2240
|
});
|
|
1715
2241
|
}
|
|
@@ -1725,26 +2251,14 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
1725
2251
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1726
2252
|
}
|
|
1727
2253
|
}
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
// src/plugins/guides/renderers/floating-arrow.ts
|
|
1731
|
-
var STATIC_SIDE = {
|
|
1732
|
-
top: "bottom",
|
|
1733
|
-
bottom: "top",
|
|
1734
|
-
left: "right",
|
|
1735
|
-
right: "left"
|
|
1736
|
-
};
|
|
1737
|
-
function positionArrow(arrowEl, placement, data) {
|
|
1738
|
-
const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
|
|
1739
|
-
for (const prop of ["top", "bottom", "left", "right"]) {
|
|
1740
|
-
arrowEl.style.setProperty(prop, "");
|
|
2254
|
+
onLiveUpdate(step) {
|
|
2255
|
+
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
1741
2256
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
}
|
|
1746
|
-
|
|
1747
|
-
// src/plugins/guides/renderers/tooltip-renderer.ts
|
|
2257
|
+
destroy() {
|
|
2258
|
+
this.overlay = null;
|
|
2259
|
+
super.destroy();
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
1748
2262
|
var TooltipRenderer = class extends BaseRenderer {
|
|
1749
2263
|
constructor() {
|
|
1750
2264
|
super(...arguments);
|
|
@@ -1766,18 +2280,19 @@ var TooltipRenderer = class extends BaseRenderer {
|
|
|
1766
2280
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1767
2281
|
const tooltip = ownerDocument.createElement("div");
|
|
1768
2282
|
tooltip.className = "veo-tooltip";
|
|
1769
|
-
const dismiss = (action, ctaUrl) => {
|
|
2283
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
1770
2284
|
context.onInteraction({
|
|
1771
2285
|
guideId: context.guide.guideId,
|
|
1772
2286
|
stepIndex: 0,
|
|
1773
|
-
action
|
|
2287
|
+
action,
|
|
2288
|
+
...meta ? { metadata: meta } : {}
|
|
1774
2289
|
});
|
|
1775
2290
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1776
2291
|
context.onClose();
|
|
1777
2292
|
};
|
|
1778
2293
|
const content = buildStepContent(step, ownerDocument, {
|
|
1779
|
-
onCtaClick: (action, url) => {
|
|
1780
|
-
dismiss("cta_clicked", action === "url" ? url : void 0);
|
|
2294
|
+
onCtaClick: (action, url, meta) => {
|
|
2295
|
+
dismiss("cta_clicked", action === "url" ? url : void 0, meta);
|
|
1781
2296
|
},
|
|
1782
2297
|
onDismiss: () => dismiss("dismissed")
|
|
1783
2298
|
});
|
|
@@ -1810,7 +2325,7 @@ var TooltipRenderer = class extends BaseRenderer {
|
|
|
1810
2325
|
this.liveContent = content;
|
|
1811
2326
|
this.liveKey = selector;
|
|
1812
2327
|
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1813
|
-
onCtaClick: (action, url) => dismiss("cta_clicked", action === "url" ? url : void 0),
|
|
2328
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1814
2329
|
onDismiss: () => dismiss("dismissed")
|
|
1815
2330
|
});
|
|
1816
2331
|
context.onInteraction({
|
|
@@ -1894,14 +2409,26 @@ var GuidePreviewController = class {
|
|
|
1894
2409
|
const nextStep = input.guideSteps[0];
|
|
1895
2410
|
if (this.singleStep && this.input && input.guideType === this.input.guideType && input.guideType !== "walkthrough" && nextStep && this.singleStep.updateStep(nextStep)) {
|
|
1896
2411
|
this.input = input;
|
|
1897
|
-
return {
|
|
2412
|
+
return {
|
|
2413
|
+
close: () => this.close(),
|
|
2414
|
+
ready: Promise.resolve({ rendered: true }),
|
|
2415
|
+
host: () => this.activeHost()
|
|
2416
|
+
};
|
|
1898
2417
|
}
|
|
1899
2418
|
this.close();
|
|
1900
2419
|
this.input = input;
|
|
1901
2420
|
const guide = normalize(input);
|
|
1902
2421
|
const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
|
|
1903
2422
|
const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
|
|
1904
|
-
return { close: () => this.close(), ready };
|
|
2423
|
+
return { close: () => this.close(), ready, host: () => this.activeHost() };
|
|
2424
|
+
}
|
|
2425
|
+
/** Host de la guía montada (single-step o paso de walkthrough activo). */
|
|
2426
|
+
activeHost() {
|
|
2427
|
+
if (this.singleStep) return this.singleStep.hostElement();
|
|
2428
|
+
if (this.walkthrough instanceof BaseRenderer) {
|
|
2429
|
+
return this.walkthrough.hostElement();
|
|
2430
|
+
}
|
|
2431
|
+
return null;
|
|
1905
2432
|
}
|
|
1906
2433
|
close() {
|
|
1907
2434
|
if (this.singleStep) {
|
|
@@ -2022,7 +2549,7 @@ var activeController = null;
|
|
|
2022
2549
|
function previewGuide(input) {
|
|
2023
2550
|
if (!hasDocument()) {
|
|
2024
2551
|
return { close: () => {
|
|
2025
|
-
}, ready: Promise.resolve({ rendered: false }) };
|
|
2552
|
+
}, ready: Promise.resolve({ rendered: false }), host: () => null };
|
|
2026
2553
|
}
|
|
2027
2554
|
if (!activeController) activeController = new GuidePreviewController();
|
|
2028
2555
|
return activeController.preview(input);
|
|
@@ -2065,6 +2592,8 @@ function createSingleStepRenderer(type) {
|
|
|
2065
2592
|
return new FormRenderer();
|
|
2066
2593
|
case "inline-form":
|
|
2067
2594
|
return new InlineFormRenderer();
|
|
2595
|
+
case "badge":
|
|
2596
|
+
return new BadgeRenderer();
|
|
2068
2597
|
case "walkthrough":
|
|
2069
2598
|
return null;
|
|
2070
2599
|
}
|
|
@@ -2170,8 +2699,8 @@ function injectBuilderPanel(opts) {
|
|
|
2170
2699
|
const ot = top;
|
|
2171
2700
|
iframe.style.pointerEvents = "none";
|
|
2172
2701
|
const move = (ev) => {
|
|
2173
|
-
left =
|
|
2174
|
-
top =
|
|
2702
|
+
left = clamp3(ol + (ev.clientX - sx), 0, Math.max(0, window.innerWidth - card.offsetWidth));
|
|
2703
|
+
top = clamp3(ot + (ev.clientY - sy), 0, Math.max(0, window.innerHeight - 40));
|
|
2175
2704
|
card.style.left = `${left}px`;
|
|
2176
2705
|
card.style.top = `${top}px`;
|
|
2177
2706
|
};
|
|
@@ -2192,8 +2721,8 @@ function injectBuilderPanel(opts) {
|
|
|
2192
2721
|
const sh = card.offsetHeight;
|
|
2193
2722
|
iframe.style.pointerEvents = "none";
|
|
2194
2723
|
const move = (ev) => {
|
|
2195
|
-
card.style.width = `${
|
|
2196
|
-
card.style.height = `${
|
|
2724
|
+
card.style.width = `${clamp3(sw + (ev.clientX - sx), MIN_W, window.innerWidth)}px`;
|
|
2725
|
+
card.style.height = `${clamp3(sh + (ev.clientY - sy), MIN_H, window.innerHeight)}px`;
|
|
2197
2726
|
};
|
|
2198
2727
|
const up = () => {
|
|
2199
2728
|
window.removeEventListener("mousemove", move);
|
|
@@ -2212,7 +2741,7 @@ function injectBuilderPanel(opts) {
|
|
|
2212
2741
|
}
|
|
2213
2742
|
};
|
|
2214
2743
|
}
|
|
2215
|
-
function
|
|
2744
|
+
function clamp3(v, min, max) {
|
|
2216
2745
|
return Math.min(Math.max(v, min), max);
|
|
2217
2746
|
}
|
|
2218
2747
|
function buildPanelUrl(opts) {
|
|
@@ -2264,6 +2793,237 @@ var PANEL_CSS = `
|
|
|
2264
2793
|
}
|
|
2265
2794
|
`;
|
|
2266
2795
|
|
|
2796
|
+
// src/plugins/builder/design-manipulator.ts
|
|
2797
|
+
var MIN_W2 = 220;
|
|
2798
|
+
var MAX_W = 720;
|
|
2799
|
+
var MIN_H2 = 120;
|
|
2800
|
+
var MAX_H = 900;
|
|
2801
|
+
var EDGE = 8;
|
|
2802
|
+
var RESIZE_DIRS = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
2803
|
+
var MANIPULATOR_CSS = `
|
|
2804
|
+
.veo-mnp-card {
|
|
2805
|
+
outline: 1.5px dashed rgba(255, 91, 53, 0.75);
|
|
2806
|
+
outline-offset: 2px;
|
|
2807
|
+
}
|
|
2808
|
+
.veo-mnp-card:hover { cursor: move; }
|
|
2809
|
+
.veo-mnp-handle {
|
|
2810
|
+
position: absolute;
|
|
2811
|
+
z-index: 10;
|
|
2812
|
+
background: transparent;
|
|
2813
|
+
}
|
|
2814
|
+
.veo-mnp-handle::after {
|
|
2815
|
+
content: '';
|
|
2816
|
+
position: absolute;
|
|
2817
|
+
width: 8px; height: 8px;
|
|
2818
|
+
background: #fff;
|
|
2819
|
+
border: 1.5px solid rgba(255, 91, 53, 0.9);
|
|
2820
|
+
border-radius: 2px;
|
|
2821
|
+
top: 50%; left: 50%;
|
|
2822
|
+
transform: translate(-50%, -50%);
|
|
2823
|
+
opacity: 0;
|
|
2824
|
+
transition: opacity 100ms ease;
|
|
2825
|
+
}
|
|
2826
|
+
.veo-mnp-card:hover .veo-mnp-handle::after,
|
|
2827
|
+
.veo-mnp-handle:hover::after { opacity: 1; }
|
|
2828
|
+
.veo-mnp-n { top: -${EDGE / 2}px; left: ${EDGE}px; right: ${EDGE}px; height: ${EDGE}px; cursor: ns-resize; }
|
|
2829
|
+
.veo-mnp-s { bottom: -${EDGE / 2}px; left: ${EDGE}px; right: ${EDGE}px; height: ${EDGE}px; cursor: ns-resize; }
|
|
2830
|
+
.veo-mnp-e { right: -${EDGE / 2}px; top: ${EDGE}px; bottom: ${EDGE}px; width: ${EDGE}px; cursor: ew-resize; }
|
|
2831
|
+
.veo-mnp-w { left: -${EDGE / 2}px; top: ${EDGE}px; bottom: ${EDGE}px; width: ${EDGE}px; cursor: ew-resize; }
|
|
2832
|
+
.veo-mnp-ne { top: -${EDGE / 2}px; right: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nesw-resize; }
|
|
2833
|
+
.veo-mnp-nw { top: -${EDGE / 2}px; left: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nwse-resize; }
|
|
2834
|
+
.veo-mnp-se { bottom: -${EDGE / 2}px; right: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nwse-resize; }
|
|
2835
|
+
.veo-mnp-sw { bottom: -${EDGE / 2}px; left: -${EDGE / 2}px; width: ${EDGE * 1.5}px; height: ${EDGE * 1.5}px; cursor: nesw-resize; }
|
|
2836
|
+
.veo-mnp-banner { outline: 1.5px dashed rgba(255, 91, 53, 0.75); outline-offset: -2px; cursor: grab; }
|
|
2837
|
+
.veo-mnp-banner:active { cursor: grabbing; }
|
|
2838
|
+
`;
|
|
2839
|
+
function clamp4(v, min, max) {
|
|
2840
|
+
return Math.min(Math.max(v, min), max);
|
|
2841
|
+
}
|
|
2842
|
+
function round3(v) {
|
|
2843
|
+
return Math.round(v * 1e3) / 1e3;
|
|
2844
|
+
}
|
|
2845
|
+
function isInteractive(target) {
|
|
2846
|
+
return Boolean(
|
|
2847
|
+
target instanceof Element && target.closest("button, a, input, textarea, select, label")
|
|
2848
|
+
);
|
|
2849
|
+
}
|
|
2850
|
+
function attachDesignManipulator(opts) {
|
|
2851
|
+
const shadow = opts.host.shadowRoot;
|
|
2852
|
+
if (!shadow) return null;
|
|
2853
|
+
const card = shadow.querySelector(".veo-modal-card");
|
|
2854
|
+
if (card) return attachModal(opts, shadow, card);
|
|
2855
|
+
const banner = shadow.querySelector(".veo-banner");
|
|
2856
|
+
if (banner && !banner.classList.contains("veo-banner-embedded")) {
|
|
2857
|
+
return attachBanner(opts, shadow, banner);
|
|
2858
|
+
}
|
|
2859
|
+
return null;
|
|
2860
|
+
}
|
|
2861
|
+
function attachModal(opts, shadow, card) {
|
|
2862
|
+
if (card.dataset.veoManipulated === "1") return null;
|
|
2863
|
+
card.dataset.veoManipulated = "1";
|
|
2864
|
+
card.classList.add("veo-mnp-card");
|
|
2865
|
+
const doc = card.ownerDocument;
|
|
2866
|
+
const style = doc.createElement("style");
|
|
2867
|
+
style.textContent = MANIPULATOR_CSS;
|
|
2868
|
+
shadow.appendChild(style);
|
|
2869
|
+
const handles = [];
|
|
2870
|
+
for (const dir of RESIZE_DIRS) {
|
|
2871
|
+
const h = doc.createElement("div");
|
|
2872
|
+
h.className = `veo-mnp-handle veo-mnp-${dir}`;
|
|
2873
|
+
h.dataset.veoDir = dir;
|
|
2874
|
+
card.appendChild(h);
|
|
2875
|
+
handles.push(h);
|
|
2876
|
+
}
|
|
2877
|
+
let dragging = false;
|
|
2878
|
+
let detached = false;
|
|
2879
|
+
let patch = {};
|
|
2880
|
+
const setVars = (v) => {
|
|
2881
|
+
const host = opts.host;
|
|
2882
|
+
if (v.px !== void 0) host.style.setProperty("--veo-pos-x", `${v.px * 100}%`);
|
|
2883
|
+
if (v.py !== void 0) host.style.setProperty("--veo-pos-y", `${v.py * 100}%`);
|
|
2884
|
+
if (v.w !== void 0) host.style.setProperty("--veo-width", `${v.w}px`);
|
|
2885
|
+
if (v.h !== void 0) host.style.setProperty("--veo-min-h", `${v.h}px`);
|
|
2886
|
+
};
|
|
2887
|
+
const startGesture = (e, apply) => {
|
|
2888
|
+
e.preventDefault();
|
|
2889
|
+
e.stopPropagation();
|
|
2890
|
+
dragging = true;
|
|
2891
|
+
patch = {};
|
|
2892
|
+
const start = card.getBoundingClientRect();
|
|
2893
|
+
const sx = e.clientX;
|
|
2894
|
+
const sy = e.clientY;
|
|
2895
|
+
let raf = 0;
|
|
2896
|
+
let lastEv = null;
|
|
2897
|
+
const flush = () => {
|
|
2898
|
+
raf = 0;
|
|
2899
|
+
if (!lastEv) return;
|
|
2900
|
+
const next = apply(lastEv.clientX - sx, lastEv.clientY - sy, start);
|
|
2901
|
+
patch = { ...patch, ...next };
|
|
2902
|
+
setVars({
|
|
2903
|
+
...next.posX !== void 0 ? { px: next.posX } : {},
|
|
2904
|
+
...next.posY !== void 0 ? { py: next.posY } : {},
|
|
2905
|
+
...next.width !== void 0 ? { w: next.width } : {},
|
|
2906
|
+
...next.height !== void 0 ? { h: next.height } : {}
|
|
2907
|
+
});
|
|
2908
|
+
};
|
|
2909
|
+
const move = (ev) => {
|
|
2910
|
+
lastEv = ev;
|
|
2911
|
+
if (typeof window.requestAnimationFrame !== "function") flush();
|
|
2912
|
+
else if (!raf) raf = window.requestAnimationFrame(flush);
|
|
2913
|
+
};
|
|
2914
|
+
const up = () => {
|
|
2915
|
+
window.removeEventListener("mousemove", move);
|
|
2916
|
+
window.removeEventListener("mouseup", up);
|
|
2917
|
+
if (raf) window.cancelAnimationFrame(raf);
|
|
2918
|
+
flush();
|
|
2919
|
+
dragging = false;
|
|
2920
|
+
if (Object.keys(patch).length > 0 && !detached) {
|
|
2921
|
+
opts.onCommit({ stepIndex: opts.stepIndex, style: patch });
|
|
2922
|
+
}
|
|
2923
|
+
opts.onGestureEnd?.();
|
|
2924
|
+
};
|
|
2925
|
+
window.addEventListener("mousemove", move);
|
|
2926
|
+
window.addEventListener("mouseup", up);
|
|
2927
|
+
};
|
|
2928
|
+
const posXFor = (left, w) => {
|
|
2929
|
+
const span = window.innerWidth - w;
|
|
2930
|
+
return span <= 0 ? 0.5 : round3(clamp4(left / span, 0, 1));
|
|
2931
|
+
};
|
|
2932
|
+
const posYFor = (top, h) => {
|
|
2933
|
+
const span = window.innerHeight - h;
|
|
2934
|
+
return span <= 0 ? 0.5 : round3(clamp4(top / span, 0, 1));
|
|
2935
|
+
};
|
|
2936
|
+
const onCardDown = (e) => {
|
|
2937
|
+
if (isInteractive(e.target)) return;
|
|
2938
|
+
if (e.target instanceof Element && e.target.closest(".veo-mnp-handle")) return;
|
|
2939
|
+
startGesture(e, (dx, dy, start) => ({
|
|
2940
|
+
posX: posXFor(start.left + dx, start.width),
|
|
2941
|
+
posY: posYFor(start.top + dy, start.height)
|
|
2942
|
+
}));
|
|
2943
|
+
};
|
|
2944
|
+
const onHandleDown = (e) => {
|
|
2945
|
+
const dir = e.currentTarget.dataset.veoDir;
|
|
2946
|
+
startGesture(e, (dx, dy, start) => {
|
|
2947
|
+
const out = {};
|
|
2948
|
+
if (dir.includes("e")) out.width = Math.round(clamp4(start.width + dx, MIN_W2, MAX_W));
|
|
2949
|
+
if (dir.includes("w")) {
|
|
2950
|
+
out.width = Math.round(clamp4(start.width - dx, MIN_W2, MAX_W));
|
|
2951
|
+
out.posX = posXFor(start.right - out.width, out.width);
|
|
2952
|
+
}
|
|
2953
|
+
if (dir.includes("s")) out.height = Math.round(clamp4(start.height + dy, MIN_H2, MAX_H));
|
|
2954
|
+
if (dir.includes("n")) {
|
|
2955
|
+
out.height = Math.round(clamp4(start.height - dy, MIN_H2, MAX_H));
|
|
2956
|
+
out.posY = posYFor(start.bottom - out.height, out.height);
|
|
2957
|
+
}
|
|
2958
|
+
return out;
|
|
2959
|
+
});
|
|
2960
|
+
};
|
|
2961
|
+
card.addEventListener("mousedown", onCardDown);
|
|
2962
|
+
for (const h of handles) h.addEventListener("mousedown", onHandleDown);
|
|
2963
|
+
return {
|
|
2964
|
+
isDragging: () => dragging,
|
|
2965
|
+
detach: () => {
|
|
2966
|
+
if (detached) return;
|
|
2967
|
+
detached = true;
|
|
2968
|
+
card.removeEventListener("mousedown", onCardDown);
|
|
2969
|
+
for (const h of handles) h.remove();
|
|
2970
|
+
style.remove();
|
|
2971
|
+
card.classList.remove("veo-mnp-card");
|
|
2972
|
+
delete card.dataset.veoManipulated;
|
|
2973
|
+
}
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
function attachBanner(opts, shadow, banner) {
|
|
2977
|
+
if (banner.dataset.veoManipulated === "1") return null;
|
|
2978
|
+
banner.dataset.veoManipulated = "1";
|
|
2979
|
+
banner.classList.add("veo-mnp-banner");
|
|
2980
|
+
const doc = banner.ownerDocument;
|
|
2981
|
+
const style = doc.createElement("style");
|
|
2982
|
+
style.textContent = MANIPULATOR_CSS;
|
|
2983
|
+
shadow.appendChild(style);
|
|
2984
|
+
let dragging = false;
|
|
2985
|
+
let detached = false;
|
|
2986
|
+
const positionOf = () => banner.classList.contains("veo-banner-bottom") ? "bottom" : "top";
|
|
2987
|
+
const onDown = (e) => {
|
|
2988
|
+
if (isInteractive(e.target)) return;
|
|
2989
|
+
e.preventDefault();
|
|
2990
|
+
dragging = true;
|
|
2991
|
+
const startPos = positionOf();
|
|
2992
|
+
let current = startPos;
|
|
2993
|
+
const move = (ev) => {
|
|
2994
|
+
const next = ev.clientY < window.innerHeight / 2 ? "top" : "bottom";
|
|
2995
|
+
if (next !== current) {
|
|
2996
|
+
current = next;
|
|
2997
|
+
banner.classList.remove("veo-banner-top", "veo-banner-bottom");
|
|
2998
|
+
banner.classList.add(`veo-banner-${next}`);
|
|
2999
|
+
}
|
|
3000
|
+
};
|
|
3001
|
+
const up = () => {
|
|
3002
|
+
window.removeEventListener("mousemove", move);
|
|
3003
|
+
window.removeEventListener("mouseup", up);
|
|
3004
|
+
dragging = false;
|
|
3005
|
+
if (current !== startPos && !detached) {
|
|
3006
|
+
opts.onCommit({ stepIndex: opts.stepIndex, style: { position: current } });
|
|
3007
|
+
}
|
|
3008
|
+
opts.onGestureEnd?.();
|
|
3009
|
+
};
|
|
3010
|
+
window.addEventListener("mousemove", move);
|
|
3011
|
+
window.addEventListener("mouseup", up);
|
|
3012
|
+
};
|
|
3013
|
+
banner.addEventListener("mousedown", onDown);
|
|
3014
|
+
return {
|
|
3015
|
+
isDragging: () => dragging,
|
|
3016
|
+
detach: () => {
|
|
3017
|
+
if (detached) return;
|
|
3018
|
+
detached = true;
|
|
3019
|
+
banner.removeEventListener("mousedown", onDown);
|
|
3020
|
+
style.remove();
|
|
3021
|
+
banner.classList.remove("veo-mnp-banner");
|
|
3022
|
+
delete banner.dataset.veoManipulated;
|
|
3023
|
+
}
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
|
|
2267
3027
|
// src/plugins/autocapture/constants.ts
|
|
2268
3028
|
var HASHED_CLASS_PATTERNS = [
|
|
2269
3029
|
/^css-[a-z0-9]{4,}$/i,
|
|
@@ -2556,10 +3316,35 @@ function initBuilderMode() {
|
|
|
2556
3316
|
let picker = null;
|
|
2557
3317
|
let panel = null;
|
|
2558
3318
|
let staticTarget = null;
|
|
3319
|
+
let manipulator = null;
|
|
3320
|
+
let pendingPreview = null;
|
|
2559
3321
|
const getTarget = () => panel ? panel.target() : staticTarget;
|
|
2560
3322
|
const post = (event) => {
|
|
2561
3323
|
getTarget()?.postMessage({ source: VEO_BUILDER_SOURCE, token, ...event }, dashboardOrigin);
|
|
2562
3324
|
};
|
|
3325
|
+
const applyPreview = (cmd) => {
|
|
3326
|
+
manipulator?.detach();
|
|
3327
|
+
manipulator = null;
|
|
3328
|
+
const handle = previewGuide(cmd.guide);
|
|
3329
|
+
if (!cmd.editable) return;
|
|
3330
|
+
void handle.ready.then((result) => {
|
|
3331
|
+
if (!result.rendered) return;
|
|
3332
|
+
const host = handle.host();
|
|
3333
|
+
if (!host) return;
|
|
3334
|
+
manipulator = attachDesignManipulator({
|
|
3335
|
+
host,
|
|
3336
|
+
guideType: cmd.guide.guideType,
|
|
3337
|
+
stepIndex: cmd.guide.startStepIndex ?? 0,
|
|
3338
|
+
onCommit: (payload) => post({ type: "design-updated", payload }),
|
|
3339
|
+
onGestureEnd: () => {
|
|
3340
|
+
if (!pendingPreview) return;
|
|
3341
|
+
const queued = pendingPreview;
|
|
3342
|
+
pendingPreview = null;
|
|
3343
|
+
applyPreview(queued);
|
|
3344
|
+
}
|
|
3345
|
+
});
|
|
3346
|
+
});
|
|
3347
|
+
};
|
|
2563
3348
|
const handleCommand = (cmd) => {
|
|
2564
3349
|
switch (cmd.type) {
|
|
2565
3350
|
case "panel-ready":
|
|
@@ -2585,9 +3370,14 @@ function initBuilderMode() {
|
|
|
2585
3370
|
picker = null;
|
|
2586
3371
|
break;
|
|
2587
3372
|
case "preview":
|
|
2588
|
-
if (cmd.guide)
|
|
3373
|
+
if (!cmd.guide) break;
|
|
3374
|
+
if (manipulator?.isDragging()) pendingPreview = cmd;
|
|
3375
|
+
else applyPreview(cmd);
|
|
2589
3376
|
break;
|
|
2590
3377
|
case "close-preview":
|
|
3378
|
+
manipulator?.detach();
|
|
3379
|
+
manipulator = null;
|
|
3380
|
+
pendingPreview = null;
|
|
2591
3381
|
closeGuidePreview();
|
|
2592
3382
|
break;
|
|
2593
3383
|
case "teardown":
|
|
@@ -2607,6 +3397,9 @@ function initBuilderMode() {
|
|
|
2607
3397
|
window.removeEventListener("pagehide", onPageHide);
|
|
2608
3398
|
picker?.stop();
|
|
2609
3399
|
picker = null;
|
|
3400
|
+
manipulator?.detach();
|
|
3401
|
+
manipulator = null;
|
|
3402
|
+
pendingPreview = null;
|
|
2610
3403
|
closeGuidePreview();
|
|
2611
3404
|
post({ type: "closed" });
|
|
2612
3405
|
panel?.teardown();
|