veo-sdk 0.3.15 → 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-BKLDSVN5.mjs.map → builder-ZDNVF6KG.mjs.map} +1 -1
- package/dist/builder.cjs +1085 -204
- 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-EF4BZR5R.mjs → chunk-I55Z7UGJ.mjs} +269 -5
- package/dist/chunk-I55Z7UGJ.mjs.map +1 -0
- package/dist/{chunk-2INDL77W.mjs → chunk-ICKEQ7VC.mjs} +797 -180
- package/dist/chunk-ICKEQ7VC.mjs.map +1 -0
- package/dist/{chunk-WVLVN422.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 +1180 -211
- 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-BKLDSVN5.mjs +0 -5
- package/dist/chunk-2INDL77W.mjs.map +0 -1
- package/dist/chunk-EF4BZR5R.mjs.map +0 -1
- package/dist/chunk-WVLVN422.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};
|
|
@@ -854,6 +1046,37 @@ var BaseRenderer = class {
|
|
|
854
1046
|
this.host = null;
|
|
855
1047
|
this.shadow = null;
|
|
856
1048
|
this.cleanups = [];
|
|
1049
|
+
// ── Update in-place (preview en vivo del editor) ──
|
|
1050
|
+
// La subclase setea estas piezas en render() para habilitarlo: el contenedor
|
|
1051
|
+
// del contenido, el nodo de contenido actual y cómo reconstruirlo.
|
|
1052
|
+
this.liveContainer = null;
|
|
1053
|
+
this.liveContent = null;
|
|
1054
|
+
this.liveBuild = null;
|
|
1055
|
+
/** Clave de anclaje/estructura fijada en render(); si cambia → remontar. */
|
|
1056
|
+
this.liveKey = "";
|
|
1057
|
+
}
|
|
1058
|
+
/** Clave de anclaje derivada del step (override por subclase). */
|
|
1059
|
+
liveKeyOf(_step) {
|
|
1060
|
+
return "";
|
|
1061
|
+
}
|
|
1062
|
+
/** Hook post-swap (reposicionar tooltip, actualizar clase del banner…). */
|
|
1063
|
+
onLiveUpdate(_step) {
|
|
1064
|
+
}
|
|
1065
|
+
/**
|
|
1066
|
+
* Actualiza el contenido del step SIN remontar el host — así el preview del
|
|
1067
|
+
* editor no parpadea en cada tecla. Devuelve `false` cuando el renderer no
|
|
1068
|
+
* lo soporta o el cambio requiere remontar (cambió el ancla/estructura);
|
|
1069
|
+
* en ese caso el caller hace el re-render completo.
|
|
1070
|
+
*/
|
|
1071
|
+
updateStep(step) {
|
|
1072
|
+
if (!this.host || !this.liveContainer || !this.liveContent || !this.liveBuild) return false;
|
|
1073
|
+
if (this.liveKeyOf(step) !== this.liveKey) return false;
|
|
1074
|
+
const next = this.liveBuild(step);
|
|
1075
|
+
this.liveContainer.replaceChild(next, this.liveContent);
|
|
1076
|
+
this.liveContent = next;
|
|
1077
|
+
this.applyDesign(step.style);
|
|
1078
|
+
this.onLiveUpdate(step);
|
|
1079
|
+
return true;
|
|
857
1080
|
}
|
|
858
1081
|
/**
|
|
859
1082
|
* Crea un `<div data-veo-guide>` adjunto a `document.body`, le adjunta
|
|
@@ -882,44 +1105,404 @@ var BaseRenderer = class {
|
|
|
882
1105
|
applyDesign(style) {
|
|
883
1106
|
if (this.host) applyDesignVars(this.host, style);
|
|
884
1107
|
}
|
|
885
|
-
/**
|
|
886
|
-
* Registra una función a ejecutar en `destroy()`. Útil para limpiar
|
|
887
|
-
* listeners globales (scroll, resize) o intervalos.
|
|
888
|
-
*/
|
|
889
|
-
registerCleanup(fn) {
|
|
890
|
-
this.cleanups.push(fn);
|
|
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);
|
|
1439
|
+
}
|
|
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);
|
|
891
1445
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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);
|
|
906
1463
|
}
|
|
907
|
-
|
|
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
|
+
}
|
|
908
1480
|
|
|
909
1481
|
// src/plugins/guides/renderers/banner-renderer.ts
|
|
910
1482
|
var BannerRenderer = class extends BaseRenderer {
|
|
911
|
-
render(context) {
|
|
1483
|
+
async render(context) {
|
|
912
1484
|
const idx = context.nav?.stepIndex ?? 0;
|
|
913
1485
|
const step = context.guide.guideSteps[idx];
|
|
914
1486
|
if (!step) return;
|
|
915
|
-
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();
|
|
916
1495
|
this.applyDesign(step.style);
|
|
917
1496
|
const ownerDocument = root.ownerDocument ?? document;
|
|
918
|
-
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
919
1497
|
const banner = ownerDocument.createElement("div");
|
|
920
|
-
banner.className =
|
|
921
|
-
const dismiss = (action, ctaUrl) => {
|
|
922
|
-
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
|
+
});
|
|
923
1506
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
924
1507
|
context.onClose();
|
|
925
1508
|
};
|
|
@@ -930,47 +1513,45 @@ var BannerRenderer = class extends BaseRenderer {
|
|
|
930
1513
|
ownerDocument,
|
|
931
1514
|
context.nav.callbacks
|
|
932
1515
|
) : buildStepContent(step, ownerDocument, {
|
|
933
|
-
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),
|
|
934
1517
|
onDismiss: () => dismiss("dismissed")
|
|
935
1518
|
});
|
|
936
1519
|
banner.appendChild(content);
|
|
937
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
|
+
}
|
|
938
1527
|
if (!context.nav) {
|
|
1528
|
+
this.liveContainer = banner;
|
|
1529
|
+
this.liveContent = content;
|
|
1530
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
1531
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
1532
|
+
onDismiss: () => dismiss("dismissed")
|
|
1533
|
+
});
|
|
1534
|
+
this.liveKey = this.liveKeyOf(step);
|
|
939
1535
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
940
1536
|
}
|
|
941
1537
|
}
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const existing = safeQuery();
|
|
955
|
-
if (existing) {
|
|
956
|
-
resolve(existing);
|
|
957
|
-
return;
|
|
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
|
+
}
|
|
1545
|
+
onLiveUpdate(step) {
|
|
1546
|
+
if (this.liveContainer) {
|
|
1547
|
+
const position = step.style?.position === "bottom" ? "bottom" : "top";
|
|
1548
|
+
const embedded = Boolean(step.selector?.trim());
|
|
1549
|
+
this.liveContainer.className = bannerClassOf(position, embedded);
|
|
958
1550
|
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
observer.disconnect();
|
|
964
|
-
clearTimeout(timer);
|
|
965
|
-
resolve(el);
|
|
966
|
-
};
|
|
967
|
-
const observer = new MutationObserver(() => {
|
|
968
|
-
const el = safeQuery();
|
|
969
|
-
if (el) finish(el);
|
|
970
|
-
});
|
|
971
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
972
|
-
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
973
|
-
});
|
|
1551
|
+
}
|
|
1552
|
+
};
|
|
1553
|
+
function bannerClassOf(position, embedded) {
|
|
1554
|
+
return embedded ? "veo-banner veo-banner-embedded" : `veo-banner veo-banner-${position}`;
|
|
974
1555
|
}
|
|
975
1556
|
|
|
976
1557
|
// src/utils/uuid.ts
|
|
@@ -1505,36 +2086,6 @@ var FormRenderer = class extends BaseRenderer {
|
|
|
1505
2086
|
}
|
|
1506
2087
|
};
|
|
1507
2088
|
|
|
1508
|
-
// src/plugins/guides/inline-host.ts
|
|
1509
|
-
function readInlinePosition(style) {
|
|
1510
|
-
const p = style?.inlinePosition;
|
|
1511
|
-
return p === "before" || p === "prepend" || p === "append" ? p : "after";
|
|
1512
|
-
}
|
|
1513
|
-
function insertHost(anchor, host, position) {
|
|
1514
|
-
switch (position) {
|
|
1515
|
-
case "before":
|
|
1516
|
-
anchor.before(host);
|
|
1517
|
-
break;
|
|
1518
|
-
case "after":
|
|
1519
|
-
anchor.after(host);
|
|
1520
|
-
break;
|
|
1521
|
-
case "prepend":
|
|
1522
|
-
anchor.prepend(host);
|
|
1523
|
-
break;
|
|
1524
|
-
case "append":
|
|
1525
|
-
anchor.append(host);
|
|
1526
|
-
break;
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
function keepHostAttached(host, selector, position) {
|
|
1530
|
-
const id = window.setInterval(() => {
|
|
1531
|
-
if (host.isConnected) return;
|
|
1532
|
-
const anchor = document.querySelector(selector);
|
|
1533
|
-
if (anchor) insertHost(anchor, host, position);
|
|
1534
|
-
}, 1e3);
|
|
1535
|
-
return () => window.clearInterval(id);
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
2089
|
// src/plugins/guides/renderers/inline-custom-renderer.ts
|
|
1539
2090
|
var InlineCustomRenderer = class extends BaseRenderer {
|
|
1540
2091
|
async render(context) {
|
|
@@ -1602,23 +2153,45 @@ var InlineRenderer = class extends BaseRenderer {
|
|
|
1602
2153
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1603
2154
|
const card = ownerDocument.createElement("div");
|
|
1604
2155
|
card.className = "veo-inline";
|
|
1605
|
-
const dismiss = (action, ctaUrl) => {
|
|
1606
|
-
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
|
+
});
|
|
1607
2163
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1608
2164
|
context.onClose();
|
|
1609
2165
|
};
|
|
1610
2166
|
const content = buildStepContent(step, ownerDocument, {
|
|
1611
|
-
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),
|
|
1612
2168
|
onDismiss: () => dismiss("dismissed")
|
|
1613
2169
|
});
|
|
1614
2170
|
card.appendChild(content);
|
|
1615
2171
|
root.appendChild(card);
|
|
2172
|
+
this.liveContainer = card;
|
|
2173
|
+
this.liveContent = content;
|
|
2174
|
+
this.liveKey = `${selector}|${position}`;
|
|
2175
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2176
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2177
|
+
onDismiss: () => dismiss("dismissed")
|
|
2178
|
+
});
|
|
1616
2179
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1617
2180
|
}
|
|
2181
|
+
liveKeyOf(step) {
|
|
2182
|
+
return `${step.selector ?? ""}|${readInlinePosition(step.style)}`;
|
|
2183
|
+
}
|
|
1618
2184
|
};
|
|
1619
2185
|
|
|
1620
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
|
+
}
|
|
1621
2190
|
var ModalRenderer = class extends BaseRenderer {
|
|
2191
|
+
constructor() {
|
|
2192
|
+
super(...arguments);
|
|
2193
|
+
this.overlay = null;
|
|
2194
|
+
}
|
|
1622
2195
|
render(context) {
|
|
1623
2196
|
const idx = context.nav?.stepIndex ?? 0;
|
|
1624
2197
|
const step = context.guide.guideSteps[idx];
|
|
@@ -1627,11 +2200,17 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
1627
2200
|
this.applyDesign(step.style);
|
|
1628
2201
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1629
2202
|
const overlay = ownerDocument.createElement("div");
|
|
1630
|
-
overlay.className =
|
|
2203
|
+
overlay.className = overlayClassOf(step);
|
|
2204
|
+
this.overlay = overlay;
|
|
1631
2205
|
const card = ownerDocument.createElement("div");
|
|
1632
2206
|
card.className = "veo-modal-card";
|
|
1633
|
-
const dismiss = (action, ctaUrl) => {
|
|
1634
|
-
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
|
+
});
|
|
1635
2214
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1636
2215
|
context.onClose();
|
|
1637
2216
|
};
|
|
@@ -1646,12 +2225,20 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
1646
2225
|
ownerDocument,
|
|
1647
2226
|
context.nav.callbacks
|
|
1648
2227
|
) : buildStepContent(step, ownerDocument, {
|
|
1649
|
-
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),
|
|
1650
2229
|
onDismiss: () => dismiss("dismissed")
|
|
1651
2230
|
});
|
|
1652
2231
|
card.appendChild(content);
|
|
1653
2232
|
overlay.appendChild(card);
|
|
1654
2233
|
root.appendChild(overlay);
|
|
2234
|
+
if (!context.nav) {
|
|
2235
|
+
this.liveContainer = card;
|
|
2236
|
+
this.liveContent = content;
|
|
2237
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2238
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2239
|
+
onDismiss: () => dismiss("dismissed")
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
1655
2242
|
overlay.addEventListener("click", (e) => {
|
|
1656
2243
|
if (e.target === overlay) onBackdropOrEsc();
|
|
1657
2244
|
});
|
|
@@ -1664,27 +2251,21 @@ var ModalRenderer = class extends BaseRenderer {
|
|
|
1664
2251
|
context.onInteraction({ guideId: context.guide.guideId, stepIndex: 0, action: "shown" });
|
|
1665
2252
|
}
|
|
1666
2253
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
// src/plugins/guides/renderers/floating-arrow.ts
|
|
1670
|
-
var STATIC_SIDE = {
|
|
1671
|
-
top: "bottom",
|
|
1672
|
-
bottom: "top",
|
|
1673
|
-
left: "right",
|
|
1674
|
-
right: "left"
|
|
1675
|
-
};
|
|
1676
|
-
function positionArrow(arrowEl, placement, data) {
|
|
1677
|
-
const side = STATIC_SIDE[placement.split("-")[0] ?? "bottom"] ?? "top";
|
|
1678
|
-
for (const prop of ["top", "bottom", "left", "right"]) {
|
|
1679
|
-
arrowEl.style.setProperty(prop, "");
|
|
2254
|
+
onLiveUpdate(step) {
|
|
2255
|
+
if (this.overlay) this.overlay.className = overlayClassOf(step);
|
|
1680
2256
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
// src/plugins/guides/renderers/tooltip-renderer.ts
|
|
2257
|
+
destroy() {
|
|
2258
|
+
this.overlay = null;
|
|
2259
|
+
super.destroy();
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
1687
2262
|
var TooltipRenderer = class extends BaseRenderer {
|
|
2263
|
+
constructor() {
|
|
2264
|
+
super(...arguments);
|
|
2265
|
+
/** Lado preferido (mutable: el update in-place puede cambiarlo). */
|
|
2266
|
+
this.side = "bottom";
|
|
2267
|
+
this.reposition = null;
|
|
2268
|
+
}
|
|
1688
2269
|
async render(context) {
|
|
1689
2270
|
const step = context.guide.guideSteps[0];
|
|
1690
2271
|
if (!step) return;
|
|
@@ -1693,24 +2274,25 @@ var TooltipRenderer = class extends BaseRenderer {
|
|
|
1693
2274
|
const anchor = await waitForElement(selector);
|
|
1694
2275
|
if (!anchor) return;
|
|
1695
2276
|
const rawSide = step.style?.tooltipPlacement;
|
|
1696
|
-
|
|
2277
|
+
this.side = rawSide === "top" || rawSide === "left" || rawSide === "right" ? rawSide : "bottom";
|
|
1697
2278
|
const { root } = this.createHost();
|
|
1698
2279
|
this.applyDesign(step.style);
|
|
1699
2280
|
const ownerDocument = root.ownerDocument ?? document;
|
|
1700
2281
|
const tooltip = ownerDocument.createElement("div");
|
|
1701
2282
|
tooltip.className = "veo-tooltip";
|
|
1702
|
-
const dismiss = (action, ctaUrl) => {
|
|
2283
|
+
const dismiss = (action, ctaUrl, meta) => {
|
|
1703
2284
|
context.onInteraction({
|
|
1704
2285
|
guideId: context.guide.guideId,
|
|
1705
2286
|
stepIndex: 0,
|
|
1706
|
-
action
|
|
2287
|
+
action,
|
|
2288
|
+
...meta ? { metadata: meta } : {}
|
|
1707
2289
|
});
|
|
1708
2290
|
if (ctaUrl) window.open(ctaUrl, "_blank", "noopener,noreferrer");
|
|
1709
2291
|
context.onClose();
|
|
1710
2292
|
};
|
|
1711
2293
|
const content = buildStepContent(step, ownerDocument, {
|
|
1712
|
-
onCtaClick: (action, url) => {
|
|
1713
|
-
dismiss("cta_clicked", action === "url" ? url : void 0);
|
|
2294
|
+
onCtaClick: (action, url, meta) => {
|
|
2295
|
+
dismiss("cta_clicked", action === "url" ? url : void 0, meta);
|
|
1714
2296
|
},
|
|
1715
2297
|
onDismiss: () => dismiss("dismissed")
|
|
1716
2298
|
});
|
|
@@ -1721,7 +2303,7 @@ var TooltipRenderer = class extends BaseRenderer {
|
|
|
1721
2303
|
root.appendChild(tooltip);
|
|
1722
2304
|
const updatePosition = async () => {
|
|
1723
2305
|
const { x, y, placement, middlewareData } = await dom.computePosition(anchor, tooltip, {
|
|
1724
|
-
placement: side,
|
|
2306
|
+
placement: this.side,
|
|
1725
2307
|
middleware: [dom.offset(10), dom.flip(), dom.shift({ padding: 8 }), dom.arrow({ element: arrowEl })]
|
|
1726
2308
|
});
|
|
1727
2309
|
tooltip.style.left = `${x}px`;
|
|
@@ -1732,18 +2314,34 @@ var TooltipRenderer = class extends BaseRenderer {
|
|
|
1732
2314
|
const reposition = () => {
|
|
1733
2315
|
void updatePosition();
|
|
1734
2316
|
};
|
|
2317
|
+
this.reposition = reposition;
|
|
1735
2318
|
window.addEventListener("scroll", reposition, true);
|
|
1736
2319
|
window.addEventListener("resize", reposition);
|
|
1737
2320
|
this.registerCleanup(() => {
|
|
1738
2321
|
window.removeEventListener("scroll", reposition, true);
|
|
1739
2322
|
window.removeEventListener("resize", reposition);
|
|
1740
2323
|
});
|
|
2324
|
+
this.liveContainer = tooltip;
|
|
2325
|
+
this.liveContent = content;
|
|
2326
|
+
this.liveKey = selector;
|
|
2327
|
+
this.liveBuild = (s) => buildStepContent(s, ownerDocument, {
|
|
2328
|
+
onCtaClick: (action, url, meta) => dismiss("cta_clicked", action === "url" ? url : void 0, meta),
|
|
2329
|
+
onDismiss: () => dismiss("dismissed")
|
|
2330
|
+
});
|
|
1741
2331
|
context.onInteraction({
|
|
1742
2332
|
guideId: context.guide.guideId,
|
|
1743
2333
|
stepIndex: 0,
|
|
1744
2334
|
action: "shown"
|
|
1745
2335
|
});
|
|
1746
2336
|
}
|
|
2337
|
+
liveKeyOf(step) {
|
|
2338
|
+
return step.selector ?? "";
|
|
2339
|
+
}
|
|
2340
|
+
onLiveUpdate(step) {
|
|
2341
|
+
const raw = step.style?.tooltipPlacement;
|
|
2342
|
+
this.side = raw === "top" || raw === "left" || raw === "right" ? raw : "bottom";
|
|
2343
|
+
this.reposition?.();
|
|
2344
|
+
}
|
|
1747
2345
|
};
|
|
1748
2346
|
var WalkthroughRenderer = class extends BaseRenderer {
|
|
1749
2347
|
async render(context) {
|
|
@@ -1808,12 +2406,29 @@ var GuidePreviewController = class {
|
|
|
1808
2406
|
this.input = null;
|
|
1809
2407
|
}
|
|
1810
2408
|
preview(input) {
|
|
2409
|
+
const nextStep = input.guideSteps[0];
|
|
2410
|
+
if (this.singleStep && this.input && input.guideType === this.input.guideType && input.guideType !== "walkthrough" && nextStep && this.singleStep.updateStep(nextStep)) {
|
|
2411
|
+
this.input = input;
|
|
2412
|
+
return {
|
|
2413
|
+
close: () => this.close(),
|
|
2414
|
+
ready: Promise.resolve({ rendered: true }),
|
|
2415
|
+
host: () => this.activeHost()
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
1811
2418
|
this.close();
|
|
1812
2419
|
this.input = input;
|
|
1813
2420
|
const guide = normalize(input);
|
|
1814
2421
|
const start = typeof input.startStepIndex === "number" ? input.startStepIndex : 0;
|
|
1815
2422
|
const ready = guide.guideType === "walkthrough" ? this.startWalkthrough(guide, start) : this.renderSingleStep(guide);
|
|
1816
|
-
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;
|
|
1817
2432
|
}
|
|
1818
2433
|
close() {
|
|
1819
2434
|
if (this.singleStep) {
|
|
@@ -1934,7 +2549,7 @@ var activeController = null;
|
|
|
1934
2549
|
function previewGuide(input) {
|
|
1935
2550
|
if (!hasDocument()) {
|
|
1936
2551
|
return { close: () => {
|
|
1937
|
-
}, ready: Promise.resolve({ rendered: false }) };
|
|
2552
|
+
}, ready: Promise.resolve({ rendered: false }), host: () => null };
|
|
1938
2553
|
}
|
|
1939
2554
|
if (!activeController) activeController = new GuidePreviewController();
|
|
1940
2555
|
return activeController.preview(input);
|
|
@@ -1977,6 +2592,8 @@ function createSingleStepRenderer(type) {
|
|
|
1977
2592
|
return new FormRenderer();
|
|
1978
2593
|
case "inline-form":
|
|
1979
2594
|
return new InlineFormRenderer();
|
|
2595
|
+
case "badge":
|
|
2596
|
+
return new BadgeRenderer();
|
|
1980
2597
|
case "walkthrough":
|
|
1981
2598
|
return null;
|
|
1982
2599
|
}
|
|
@@ -2082,8 +2699,8 @@ function injectBuilderPanel(opts) {
|
|
|
2082
2699
|
const ot = top;
|
|
2083
2700
|
iframe.style.pointerEvents = "none";
|
|
2084
2701
|
const move = (ev) => {
|
|
2085
|
-
left =
|
|
2086
|
-
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));
|
|
2087
2704
|
card.style.left = `${left}px`;
|
|
2088
2705
|
card.style.top = `${top}px`;
|
|
2089
2706
|
};
|
|
@@ -2104,8 +2721,8 @@ function injectBuilderPanel(opts) {
|
|
|
2104
2721
|
const sh = card.offsetHeight;
|
|
2105
2722
|
iframe.style.pointerEvents = "none";
|
|
2106
2723
|
const move = (ev) => {
|
|
2107
|
-
card.style.width = `${
|
|
2108
|
-
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`;
|
|
2109
2726
|
};
|
|
2110
2727
|
const up = () => {
|
|
2111
2728
|
window.removeEventListener("mousemove", move);
|
|
@@ -2124,7 +2741,7 @@ function injectBuilderPanel(opts) {
|
|
|
2124
2741
|
}
|
|
2125
2742
|
};
|
|
2126
2743
|
}
|
|
2127
|
-
function
|
|
2744
|
+
function clamp3(v, min, max) {
|
|
2128
2745
|
return Math.min(Math.max(v, min), max);
|
|
2129
2746
|
}
|
|
2130
2747
|
function buildPanelUrl(opts) {
|
|
@@ -2176,6 +2793,237 @@ var PANEL_CSS = `
|
|
|
2176
2793
|
}
|
|
2177
2794
|
`;
|
|
2178
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
|
+
|
|
2179
3027
|
// src/plugins/autocapture/constants.ts
|
|
2180
3028
|
var HASHED_CLASS_PATTERNS = [
|
|
2181
3029
|
/^css-[a-z0-9]{4,}$/i,
|
|
@@ -2468,10 +3316,35 @@ function initBuilderMode() {
|
|
|
2468
3316
|
let picker = null;
|
|
2469
3317
|
let panel = null;
|
|
2470
3318
|
let staticTarget = null;
|
|
3319
|
+
let manipulator = null;
|
|
3320
|
+
let pendingPreview = null;
|
|
2471
3321
|
const getTarget = () => panel ? panel.target() : staticTarget;
|
|
2472
3322
|
const post = (event) => {
|
|
2473
3323
|
getTarget()?.postMessage({ source: VEO_BUILDER_SOURCE, token, ...event }, dashboardOrigin);
|
|
2474
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
|
+
};
|
|
2475
3348
|
const handleCommand = (cmd) => {
|
|
2476
3349
|
switch (cmd.type) {
|
|
2477
3350
|
case "panel-ready":
|
|
@@ -2497,9 +3370,14 @@ function initBuilderMode() {
|
|
|
2497
3370
|
picker = null;
|
|
2498
3371
|
break;
|
|
2499
3372
|
case "preview":
|
|
2500
|
-
if (cmd.guide)
|
|
3373
|
+
if (!cmd.guide) break;
|
|
3374
|
+
if (manipulator?.isDragging()) pendingPreview = cmd;
|
|
3375
|
+
else applyPreview(cmd);
|
|
2501
3376
|
break;
|
|
2502
3377
|
case "close-preview":
|
|
3378
|
+
manipulator?.detach();
|
|
3379
|
+
manipulator = null;
|
|
3380
|
+
pendingPreview = null;
|
|
2503
3381
|
closeGuidePreview();
|
|
2504
3382
|
break;
|
|
2505
3383
|
case "teardown":
|
|
@@ -2519,6 +3397,9 @@ function initBuilderMode() {
|
|
|
2519
3397
|
window.removeEventListener("pagehide", onPageHide);
|
|
2520
3398
|
picker?.stop();
|
|
2521
3399
|
picker = null;
|
|
3400
|
+
manipulator?.detach();
|
|
3401
|
+
manipulator = null;
|
|
3402
|
+
pendingPreview = null;
|
|
2522
3403
|
closeGuidePreview();
|
|
2523
3404
|
post({ type: "closed" });
|
|
2524
3405
|
panel?.teardown();
|