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