veo-sdk 0.6.2 → 0.6.4

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.cjs CHANGED
@@ -283,6 +283,92 @@ function clearBuilderToken() {
283
283
  }
284
284
  }
285
285
 
286
+ // src/plugins/autocapture/constants.ts
287
+ var HASHED_CLASS_PATTERNS = [
288
+ /^css-[a-z0-9]{4,}$/i,
289
+ // emotion, styled-components nuevas
290
+ /^_[a-z0-9]+_[a-z0-9]{4,}_\d+$/,
291
+ // CSS Modules con hash
292
+ /^[a-z]{2,4}-[a-z0-9]{6,}$/i,
293
+ // Tailwind JIT (a veces)
294
+ /^[a-f0-9]{6,}$/i,
295
+ // hashes hex puros
296
+ /^sc-[a-z0-9]+$/i
297
+ // styled-components legacy
298
+ ];
299
+
300
+ // src/plugins/autocapture/selector-builder.ts
301
+ function isHashedClass(className) {
302
+ return HASHED_CLASS_PATTERNS.some((pattern) => pattern.test(className));
303
+ }
304
+ function filterHumanClasses(element) {
305
+ return Array.from(element.classList).filter((c) => !isHashedClass(c) && c.length < 64);
306
+ }
307
+ var STABLE_ATTRIBUTES = [
308
+ "data-testid",
309
+ "data-test",
310
+ "data-cy",
311
+ "name",
312
+ "aria-label",
313
+ "placeholder",
314
+ "alt",
315
+ "title",
316
+ // `href` identifica muy bien a los links (`<a>`), un target frecuente de
317
+ // autocapture; va después de los atributos semánticos pero antes de los
318
+ // genéricos. Valores largos (URLs absolutas con query) se descartan por
319
+ // MAX_ATTR_VALUE_LENGTH.
320
+ "href",
321
+ "role",
322
+ "type"
323
+ ];
324
+ var MAX_ATTR_VALUE_LENGTH = 100;
325
+ function cssEscape(value) {
326
+ if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
327
+ return CSS.escape(value);
328
+ }
329
+ return value.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
330
+ }
331
+ function escapeAttrValue(value) {
332
+ return value.replace(/[\\"]/g, "\\$&");
333
+ }
334
+ function stableAttributeSelector(element, tag) {
335
+ for (const attr of STABLE_ATTRIBUTES) {
336
+ const value = element.getAttribute(attr);
337
+ if (value && value.length > 0 && value.length <= MAX_ATTR_VALUE_LENGTH) {
338
+ return `${tag}[${attr}="${escapeAttrValue(value)}"]`;
339
+ }
340
+ }
341
+ return null;
342
+ }
343
+ function buildElementSelector(element) {
344
+ const tag = element.tagName.toLowerCase();
345
+ const veoTag = element.getAttribute("data-veo-tag");
346
+ if (veoTag) {
347
+ return `${tag}[data-veo-tag="${escapeAttrValue(veoTag)}"]`;
348
+ }
349
+ if (element.id) {
350
+ const classes2 = filterHumanClasses(element);
351
+ const classPart2 = classes2.length > 0 ? `.${classes2.map(cssEscape).join(".")}` : "";
352
+ return `${tag}#${cssEscape(element.id)}${classPart2}`;
353
+ }
354
+ const attrSelector = stableAttributeSelector(element, tag);
355
+ if (attrSelector) return attrSelector;
356
+ const classes = filterHumanClasses(element);
357
+ const classPart = classes.length > 0 ? `.${classes.map(cssEscape).join(".")}` : "";
358
+ return `${tag}${classPart}`;
359
+ }
360
+ function buildSelectorPath(element, maxAncestors = 5) {
361
+ const parts = [];
362
+ let current = element;
363
+ let depth = 0;
364
+ while (current && current !== document.documentElement && depth <= maxAncestors) {
365
+ parts.unshift(buildElementSelector(current));
366
+ current = current.parentElement;
367
+ depth++;
368
+ }
369
+ return parts.join(" > ");
370
+ }
371
+
286
372
  // src/plugins/guides/activation-devices.ts
287
373
  var MOBILE_MAX_WIDTH = 767;
288
374
  function isDeviceAllowed(devices, viewportWidth = typeof window === "undefined" ? 1024 : window.innerWidth) {
@@ -309,6 +395,7 @@ var GUIDE_Z_INDEX = 2147483640;
309
395
  var DEFAULT_ANCHOR_WAIT_MS = 5e3;
310
396
  var SAFE_URL_PROTOCOLS = ["http:", "https:"];
311
397
  var GUIDE_HOST_ATTR = "data-veo-guide";
398
+ var BADGE_MARKER_HOST_ATTR = "data-veo-badge";
312
399
  var WALKTHROUGH_STEP_SELECTOR_TIMEOUT_MS = 5e3;
313
400
 
314
401
  // src/plugins/guides/rich-text.ts
@@ -2123,6 +2210,25 @@ var BaseRenderer = class {
2123
2210
  this.shadow = shadow;
2124
2211
  return { host, shadow, root };
2125
2212
  }
2213
+ /**
2214
+ * Host SUELTO con los mismos estilos (shadow DOM), sin agregarlo al DOM ni
2215
+ * registrarlo como `this.host`. Para piezas que deben vivir dentro de la app
2216
+ * (el marcador de un badge, junto a su ancla) mientras la tarjeta se queda
2217
+ * en `body`: si la tarjeta viajara dentro del ancla, un ancestro con
2218
+ * `transform`/`overflow` la recortaría y su z-index quedaría atrapado.
2219
+ */
2220
+ createDetachedHost(attr = BADGE_MARKER_HOST_ATTR) {
2221
+ const host = document.createElement("div");
2222
+ host.setAttribute(attr, "");
2223
+ const shadow = host.attachShadow({ mode: "open" });
2224
+ const style = document.createElement("style");
2225
+ style.textContent = GUIDE_STYLES;
2226
+ shadow.appendChild(style);
2227
+ const root = document.createElement("div");
2228
+ shadow.appendChild(root);
2229
+ this.registerCleanup(() => host.remove());
2230
+ return { host, root };
2231
+ }
2126
2232
  /**
2127
2233
  * Aplica la tematización por datos del step (`step.style`) como variables
2128
2234
  * CSS sobre el host. Llamar tras `createHost()`. No-op si no hay host o
@@ -3335,6 +3441,13 @@ function snoozeGuide(guideId, every, unit, now = Date.now()) {
3335
3441
  } catch {
3336
3442
  }
3337
3443
  }
3444
+
3445
+ // src/plugins/guides/z-index.ts
3446
+ var MAX_Z_INDEX = 2147483647;
3447
+ function clampZIndex(value, fallback) {
3448
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
3449
+ return Math.max(-MAX_Z_INDEX, Math.min(MAX_Z_INDEX, Math.round(value)));
3450
+ }
3338
3451
  var INFO_GLYPH = "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021";
3339
3452
  var QUESTION_GLYPH = "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75";
3340
3453
  var STAR_GLYPH = "M11.48 3.499a.562.562 0 011.04 0l2.125 5.111a.563.563 0 00.475.345l5.518.442c.499.04.701.663.321.988l-4.204 3.602a.563.563 0 00-.182.557l1.285 5.385a.562.562 0 01-.84.61l-4.725-2.885a.563.563 0 00-.586 0L6.982 20.54a.562.562 0 01-.84-.61l1.285-5.386a.562.562 0 00-.182-.557l-4.204-3.602a.563.563 0 01.321-.988l5.518-.442a.563.563 0 00.475-.345L11.48 3.5z";
@@ -3519,7 +3632,7 @@ function canNest(anchor) {
3519
3632
  }
3520
3633
  function attachMarker(anchor, host, marker) {
3521
3634
  host.style.position = "absolute";
3522
- host.style.zIndex = String(num(marker.zIndex, DEFAULT_MARKER_Z));
3635
+ host.style.zIndex = String(clampZIndex(marker.zIndex, DEFAULT_MARKER_Z));
3523
3636
  host.style.margin = "0";
3524
3637
  const position = marker.position ?? "top-right";
3525
3638
  const offset6 = { x: num(marker.offsetX, 0), y: num(marker.offsetY, 0) };
@@ -3657,7 +3770,9 @@ var CompositeRenderer = class extends BaseRenderer {
3657
3770
  const { host, root } = this.createHost();
3658
3771
  const doc = root.ownerDocument ?? document;
3659
3772
  const design = step.design ?? {};
3660
- if (typeof design.zIndex === "number") host.style.zIndex = String(design.zIndex);
3773
+ if (typeof design.zIndex === "number") {
3774
+ host.style.zIndex = String(clampZIndex(design.zIndex, 0));
3775
+ }
3661
3776
  const card = doc.createElement("div");
3662
3777
  card.className = "veo-card";
3663
3778
  const themeMode = themeModeFor(context.guide.theme, design);
@@ -3766,7 +3881,7 @@ var CompositeRenderer = class extends BaseRenderer {
3766
3881
  const bg = doc.createElement("div");
3767
3882
  bg.className = "veo-composite-overlay veo-composite-overlay--dim";
3768
3883
  if (typeof design.backdropColor === "string") bg.style.background = design.backdropColor;
3769
- const bz = typeof design.backdropZIndex === "number" ? design.backdropZIndex : 2e5;
3884
+ const bz = clampZIndex(design.backdropZIndex, 2e5);
3770
3885
  bg.style.zIndex = String(bz);
3771
3886
  root.appendChild(bg);
3772
3887
  this.registerCleanup(lockScroll(doc));
@@ -3788,7 +3903,7 @@ var CompositeRenderer = class extends BaseRenderer {
3788
3903
  overlay.style.background = design.backdropColor;
3789
3904
  }
3790
3905
  if (typeof design.backdropZIndex === "number") {
3791
- overlay.style.zIndex = String(design.backdropZIndex);
3906
+ overlay.style.zIndex = String(clampZIndex(design.backdropZIndex, 0));
3792
3907
  }
3793
3908
  overlay.addEventListener("click", (e) => {
3794
3909
  if (e.target === overlay) {
@@ -4109,6 +4224,7 @@ var CompositeRenderer = class extends BaseRenderer {
4109
4224
  renderBadgeMode(step, card, anchor, anchorSelector, host, root, doc, context, idx, design) {
4110
4225
  const trigger = step.badge?.trigger === "click" ? "click" : "hover";
4111
4226
  const placement = step.badge?.placement === "top" || step.badge?.placement === "left" || step.badge?.placement === "right" ? step.badge.placement : "bottom";
4227
+ const { host: markerHost, root: markerRoot } = this.createDetachedHost();
4112
4228
  const badge = doc.createElement("div");
4113
4229
  badge.className = "veo-composite-badge";
4114
4230
  badge.setAttribute("role", "button");
@@ -4119,7 +4235,8 @@ var CompositeRenderer = class extends BaseRenderer {
4119
4235
  const marker = step.badge?.marker ?? null;
4120
4236
  if (marker) badge.appendChild(buildMarkerElement(doc, marker));
4121
4237
  else this.buildContent(badge, step.badge?.blocks ?? [], doc, context, idx, [], host);
4122
- root.appendChild(badge);
4238
+ markerRoot.appendChild(badge);
4239
+ host.style.display = "block";
4123
4240
  if (design.fullWidth === true) {
4124
4241
  card.style.width = "auto";
4125
4242
  card.style.maxWidth = "320px";
@@ -4134,13 +4251,13 @@ var CompositeRenderer = class extends BaseRenderer {
4134
4251
  wrapper.appendChild(card);
4135
4252
  root.appendChild(wrapper);
4136
4253
  if (marker) {
4137
- host.style.display = "inline-block";
4138
- this.registerCleanup(attachMarker(anchor, host, marker));
4254
+ markerHost.style.display = "inline-block";
4255
+ this.registerCleanup(attachMarker(anchor, markerHost, marker));
4139
4256
  } else {
4140
- host.style.display = "inline-block";
4257
+ markerHost.style.display = "inline-block";
4141
4258
  const pos = inlinePosOf(step.inlinePosition);
4142
- insertHost(anchor, host, pos);
4143
- this.registerCleanup(keepHostAttached(host, anchorSelector, pos));
4259
+ insertHost(anchor, markerHost, pos);
4260
+ this.registerCleanup(keepHostAttached(markerHost, anchorSelector, pos));
4144
4261
  }
4145
4262
  let open = false;
4146
4263
  let stopFloat = null;
@@ -4270,7 +4387,7 @@ function liftTarget(anchor, zIndex) {
4270
4387
  const prevPosition = el2.style.position;
4271
4388
  const prevZ = el2.style.zIndex;
4272
4389
  if (getComputedStyle(el2).position === "static") el2.style.position = "relative";
4273
- el2.style.zIndex = String(zIndex);
4390
+ el2.style.zIndex = String(clampZIndex(zIndex, 0));
4274
4391
  return () => {
4275
4392
  el2.style.position = prevPosition;
4276
4393
  el2.style.zIndex = prevZ;
@@ -5217,14 +5334,19 @@ function injectBuilderChrome(opts) {
5217
5334
  let previewOn = false;
5218
5335
  let hiddenOn = false;
5219
5336
  let heightCb = null;
5220
- const height = () => CHROME_TOPBAR_H + (collapsed ? 0 : CHROME_STEPS_H);
5337
+ let tagging = false;
5338
+ const height = () => CHROME_TOPBAR_H + (collapsed || tagging ? 0 : CHROME_STEPS_H);
5221
5339
  const applyHeight = () => {
5222
5340
  const h = previewOn || hiddenOn ? 0 : height();
5223
5341
  html.style.marginTop = dock === "top" ? `${h}px` : prevMarginTop;
5224
5342
  html.style.marginBottom = dock === "bottom" ? `${h}px` : prevMarginBottom;
5225
5343
  root.style.setProperty("--veo-chrome-h", `${height()}px`);
5226
5344
  root.classList.toggle("is-bottom", dock === "bottom");
5227
- stepsBar.hidden = collapsed;
5345
+ stepsBar.hidden = collapsed || tagging;
5346
+ togglePill.hidden = tagging;
5347
+ preview.hidden = tagging;
5348
+ saveGroup.hidden = tagging;
5349
+ showPanel.title = tagging ? "Mostrar el panel" : "Mostrar el editor";
5228
5350
  const open = dock === "top" ? "chevronUp" : "chevronDown";
5229
5351
  const closedIcon = dock === "top" ? "chevronDown" : "chevronUp";
5230
5352
  togglePill.innerHTML = `<span>${collapsed ? "Mostrar pasos" : "Ocultar pasos"}</span>${svg(
@@ -5277,8 +5399,13 @@ function injectBuilderChrome(opts) {
5277
5399
  const render = () => {
5278
5400
  const s = current;
5279
5401
  if (!s) return;
5402
+ const nextTagging = s.variant === "tagging";
5403
+ if (nextTagging !== tagging) {
5404
+ tagging = nextTagging;
5405
+ applyHeight();
5406
+ }
5280
5407
  const date = shortDate(s.createdAt);
5281
- nameEl.textContent = `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5408
+ nameEl.textContent = tagging ? s.guideName.trim() || "Modo tagging" : `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5282
5409
  if (s.status) {
5283
5410
  statusEl.hidden = false;
5284
5411
  statusEl.textContent = s.status.label;
@@ -5377,6 +5504,8 @@ var CHROME_CSS = `
5377
5504
  font-size: 14px; color: #1c1917;
5378
5505
  }
5379
5506
  .veo-chrome[hidden] { display: none; }
5507
+ /* Modo tagging oculta controles por atributo: gana a los display de sus clases. */
5508
+ .veo-chrome [hidden], .veo-steps-toggle[hidden] { display: none !important; }
5380
5509
  .veo-chrome.is-bottom { top: auto; bottom: 0; flex-direction: column-reverse; }
5381
5510
  .veo-chrome.is-bottom .veo-tb-menu { top: auto; bottom: calc(100% + 8px); }
5382
5511
  .veo-chrome.is-bottom .veo-steps { border-bottom: 0; border-top: 1px solid #d6cfc8; }
@@ -5996,90 +6125,51 @@ function attachBanner(opts, shadow, banner) {
5996
6125
  };
5997
6126
  }
5998
6127
 
5999
- // src/plugins/autocapture/constants.ts
6000
- var HASHED_CLASS_PATTERNS = [
6001
- /^css-[a-z0-9]{4,}$/i,
6002
- // emotion, styled-components nuevas
6003
- /^_[a-z0-9]+_[a-z0-9]{4,}_\d+$/,
6004
- // CSS Modules con hash
6005
- /^[a-z]{2,4}-[a-z0-9]{6,}$/i,
6006
- // Tailwind JIT (a veces)
6007
- /^[a-f0-9]{6,}$/i,
6008
- // hashes hex puros
6009
- /^sc-[a-z0-9]+$/i
6010
- // styled-components legacy
6011
- ];
6012
-
6013
- // src/plugins/autocapture/selector-builder.ts
6014
- function isHashedClass(className) {
6015
- return HASHED_CLASS_PATTERNS.some((pattern) => pattern.test(className));
6016
- }
6017
- function filterHumanClasses(element) {
6018
- return Array.from(element.classList).filter((c) => !isHashedClass(c) && c.length < 64);
6019
- }
6020
- var STABLE_ATTRIBUTES = [
6021
- "data-testid",
6022
- "data-test",
6023
- "data-cy",
6024
- "name",
6025
- "aria-label",
6026
- "placeholder",
6027
- "alt",
6028
- "title",
6029
- // `href` identifica muy bien a los links (`<a>`), un target frecuente de
6030
- // autocapture; va después de los atributos semánticos pero antes de los
6031
- // genéricos. Valores largos (URLs absolutas con query) se descartan por
6032
- // MAX_ATTR_VALUE_LENGTH.
6033
- "href",
6034
- "role",
6035
- "type"
6128
+ // src/plugins/builder/interactive-elements.ts
6129
+ var INTERACTIVE_ROLES = [
6130
+ "button",
6131
+ "link",
6132
+ "tab",
6133
+ "menuitem",
6134
+ "menuitemcheckbox",
6135
+ "menuitemradio",
6136
+ "checkbox",
6137
+ "radio",
6138
+ "switch",
6139
+ "option",
6140
+ "combobox",
6141
+ "textbox",
6142
+ "searchbox",
6143
+ "slider",
6144
+ "spinbutton"
6036
6145
  ];
6037
- var MAX_ATTR_VALUE_LENGTH = 100;
6038
- function cssEscape(value) {
6039
- if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
6040
- return CSS.escape(value);
6041
- }
6042
- return value.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6043
- }
6044
- function escapeAttrValue(value) {
6045
- return value.replace(/[\\"]/g, "\\$&");
6046
- }
6047
- function stableAttributeSelector(element, tag) {
6048
- for (const attr of STABLE_ATTRIBUTES) {
6049
- const value = element.getAttribute(attr);
6050
- if (value && value.length > 0 && value.length <= MAX_ATTR_VALUE_LENGTH) {
6051
- return `${tag}[${attr}="${escapeAttrValue(value)}"]`;
6052
- }
6053
- }
6054
- return null;
6055
- }
6056
- function buildElementSelector(element) {
6057
- const tag = element.tagName.toLowerCase();
6058
- const veoTag = element.getAttribute("data-veo-tag");
6059
- if (veoTag) {
6060
- return `${tag}[data-veo-tag="${escapeAttrValue(veoTag)}"]`;
6061
- }
6062
- if (element.id) {
6063
- const classes2 = filterHumanClasses(element);
6064
- const classPart2 = classes2.length > 0 ? `.${classes2.map(cssEscape).join(".")}` : "";
6065
- return `${tag}#${cssEscape(element.id)}${classPart2}`;
6146
+ var INTERACTIVE_SELECTOR = [
6147
+ "button",
6148
+ "a[href]",
6149
+ 'input:not([type="hidden"])',
6150
+ "select",
6151
+ "textarea",
6152
+ "form",
6153
+ "summary",
6154
+ '[contenteditable="true"]',
6155
+ "[onclick]",
6156
+ "[data-veo-tag]",
6157
+ ...INTERACTIVE_ROLES.map((r) => `[role="${r}"]`)
6158
+ ].join(", ");
6159
+ function closestInteractive(element) {
6160
+ try {
6161
+ return element.closest(INTERACTIVE_SELECTOR);
6162
+ } catch {
6163
+ return null;
6066
6164
  }
6067
- const attrSelector = stableAttributeSelector(element, tag);
6068
- if (attrSelector) return attrSelector;
6069
- const classes = filterHumanClasses(element);
6070
- const classPart = classes.length > 0 ? `.${classes.map(cssEscape).join(".")}` : "";
6071
- return `${tag}${classPart}`;
6072
6165
  }
6073
- function buildSelectorPath(element, maxAncestors = 5) {
6074
- const parts = [];
6075
- let current = element;
6076
- let depth = 0;
6077
- while (current && current !== document.documentElement && depth <= maxAncestors) {
6078
- parts.unshift(buildElementSelector(current));
6079
- current = current.parentElement;
6080
- depth++;
6081
- }
6082
- return parts.join(" > ");
6166
+ var EMOJI_RE = /\p{Extended_Pictographic}|\p{Emoji_Modifier}|\u{FE0F}|\u{200D}|\u{20E3}/gu;
6167
+ function elementLabelText(element, max = 120) {
6168
+ const clean = (raw) => (raw ?? "").replace(EMOJI_RE, "").replace(/\s+/g, " ").trim();
6169
+ const text = clean(element.textContent);
6170
+ if (text) return text.slice(0, max);
6171
+ const fallback = clean(element.getAttribute("aria-label")) || clean(element.getAttribute("title"));
6172
+ return fallback.slice(0, max);
6083
6173
  }
6084
6174
 
6085
6175
  // src/plugins/builder/element-picker.ts
@@ -6152,21 +6242,24 @@ var ElementPicker = class {
6152
6242
  this.label = null;
6153
6243
  this.current = null;
6154
6244
  this.maxAncestors = DEFAULT_MAX_ANCESTORS;
6245
+ this.only = null;
6155
6246
  this.onPick = null;
6156
6247
  this.onCancel = null;
6157
6248
  this.onMove = (event) => {
6158
- const target = event.target;
6159
- if (!(target instanceof Element) || this.isOwn(target)) return;
6249
+ if (!(event.target instanceof Element) || this.isOwn(event.target)) return;
6250
+ const target = this.resolveTarget(event.target);
6160
6251
  this.current = target;
6161
- this.draw(target);
6252
+ if (target) this.draw(target);
6253
+ else this.hideBox();
6162
6254
  };
6163
6255
  this.onClick = (event) => {
6164
6256
  if (event.altKey) return;
6165
- const target = this.current ?? (event.target instanceof Element ? event.target : null);
6166
- if (!target || this.isOwn(target)) return;
6257
+ if (this.isOwn(event.target)) return;
6167
6258
  event.preventDefault();
6168
6259
  event.stopPropagation();
6169
6260
  event.stopImmediatePropagation();
6261
+ const target = this.current ?? this.resolveTarget(event.target);
6262
+ if (!target) return;
6170
6263
  const picked = this.describe(target);
6171
6264
  const cb = this.onPick;
6172
6265
  this.stop();
@@ -6193,6 +6286,7 @@ var ElementPicker = class {
6193
6286
  start(options) {
6194
6287
  this.stop();
6195
6288
  this.maxAncestors = options.maxAncestors ?? DEFAULT_MAX_ANCESTORS;
6289
+ this.only = options.only ?? null;
6196
6290
  this.onPick = options.onPick;
6197
6291
  this.onCancel = options.onCancel ?? null;
6198
6292
  const host = document.createElement("div");
@@ -6255,9 +6349,19 @@ var ElementPicker = class {
6255
6349
  this.box = null;
6256
6350
  this.label = null;
6257
6351
  this.current = null;
6352
+ this.only = null;
6258
6353
  this.onPick = null;
6259
6354
  this.onCancel = null;
6260
6355
  }
6356
+ /** Elemento que se elegiría para el nodo bajo el cursor (o null si no hay). */
6357
+ resolveTarget(target) {
6358
+ if (!(target instanceof Element) || this.isOwn(target)) return null;
6359
+ return this.only === "interactive" ? closestInteractive(target) : target;
6360
+ }
6361
+ hideBox() {
6362
+ if (this.box) this.box.style.display = "none";
6363
+ if (this.label) this.label.style.display = "none";
6364
+ }
6261
6365
  /** ¿Es un nodo de nuestro propio overlay? (no debe ser seleccionable). */
6262
6366
  isOwn(target) {
6263
6367
  return target instanceof Node && this.host !== null && this.host.contains(target);
@@ -6294,7 +6398,7 @@ var ElementPicker = class {
6294
6398
  tag: element.tagName.toLowerCase(),
6295
6399
  id: element.id || null,
6296
6400
  classes: filterHumanClasses(element),
6297
- text: (element.textContent ?? "").trim().slice(0, 120),
6401
+ text: elementLabelText(element),
6298
6402
  rect: {
6299
6403
  x: Math.round(rect.left),
6300
6404
  y: Math.round(rect.top),
@@ -6319,6 +6423,41 @@ function stopElementPicker() {
6319
6423
  }
6320
6424
 
6321
6425
  // src/plugins/builder/builder-mode.ts
6426
+ var CONFIRMATION_STYLES = `
6427
+ :host { all: initial; }
6428
+ .veo-confirm-backdrop {
6429
+ position: fixed; inset: 0; z-index: 2147483645;
6430
+ background: rgba(20, 16, 14, 0.45);
6431
+ }
6432
+ .veo-confirm {
6433
+ position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
6434
+ z-index: 2147483646; box-sizing: border-box;
6435
+ width: min(432px, calc(100vw - 32px)); padding: 28px 24px 24px;
6436
+ border-radius: 16px; background: #fff; color: #1d1917;
6437
+ box-shadow: 0 24px 64px rgba(0,0,0,0.28);
6438
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
6439
+ text-align: center;
6440
+ }
6441
+ .veo-confirm-check {
6442
+ width: 64px; height: 64px; margin: 0 auto; border-radius: 50%;
6443
+ background: #e6f6df; color: #2f9a4e; display: flex; align-items: center; justify-content: center;
6444
+ }
6445
+ .veo-confirm-check svg { width: 30px; height: 30px; }
6446
+ .veo-confirm-title { margin: 18px 0 0; font-size: 17px; font-weight: 600; }
6447
+ .veo-confirm-msg { margin: 8px 0 0; font-size: 13px; color: #6b6461; }
6448
+ .veo-confirm-rows {
6449
+ margin: 18px 0 0; padding: 12px 14px; border-radius: 12px; background: #f6f2ef; text-align: left;
6450
+ }
6451
+ .veo-confirm-row { display: flex; justify-content: space-between; gap: 16px; font-size: 13px; padding: 4px 0; }
6452
+ .veo-confirm-row dt { color: #6b6461; margin: 0; }
6453
+ .veo-confirm-row dd { margin: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6454
+ .veo-confirm-btn {
6455
+ display: block; width: 100%; margin-top: 18px; height: 44px; border: 0; border-radius: 10px;
6456
+ background: #b74d00; color: #fff; font: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
6457
+ }
6458
+ .veo-confirm-btn:hover { background: #953c00; }
6459
+ .veo-confirm-btn:focus-visible { outline: 3px solid rgba(183,77,0,0.35); outline-offset: 2px; }
6460
+ `;
6322
6461
  function initBuilderMode() {
6323
6462
  if (!hasWindow() || !hasDocument()) return null;
6324
6463
  const ctx = resolveBuilderContext();
@@ -7300,7 +7439,7 @@ function initBuilderMode() {
7300
7439
  if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
7301
7440
  }
7302
7441
  panel?.setTitle(
7303
- cmd.state.section === "activation" ? "Ajustes de activaci\xF3n" : cmd.state.section === "badge" ? "Ajustes de badge" : cmd.state.section === "target" ? "Ajustes del elemento objetivo" : cmd.state.section === "row" ? "Editar fila" : cmd.state.blockLabel ? `Editor \xB7 ${cmd.state.blockLabel}` : cmd.state.steps[cmd.state.activeStep]?.label ?? "Paso"
7442
+ cmd.state.panelTitle ?? (cmd.state.section === "activation" ? "Ajustes de activaci\xF3n" : cmd.state.section === "badge" ? "Ajustes de badge" : cmd.state.section === "target" ? "Ajustes del elemento objetivo" : cmd.state.section === "row" ? "Editar fila" : cmd.state.blockLabel ? `Editor \xB7 ${cmd.state.blockLabel}` : cmd.state.steps[cmd.state.activeStep]?.label ?? "Paso")
7304
7443
  );
7305
7444
  break;
7306
7445
  case "panel-visibility":
@@ -7338,7 +7477,8 @@ function initBuilderMode() {
7338
7477
  post({ type: "pick-cancelled" });
7339
7478
  },
7340
7479
  ...cmd.maxAncestors !== void 0 ? { maxAncestors: cmd.maxAncestors } : {},
7341
- ...cmd.hint !== void 0 ? { hint: cmd.hint } : {}
7480
+ ...cmd.hint !== void 0 ? { hint: cmd.hint } : {},
7481
+ ...cmd.only !== void 0 ? { only: cmd.only } : {}
7342
7482
  });
7343
7483
  break;
7344
7484
  }
@@ -7347,6 +7487,15 @@ function initBuilderMode() {
7347
7487
  picker = null;
7348
7488
  chrome?.setHidden(false);
7349
7489
  break;
7490
+ case "scan-page":
7491
+ post({ type: "page-scan", payload: scanPage(cmd.selectors) });
7492
+ break;
7493
+ case "highlight":
7494
+ setHighlight(cmd.selector);
7495
+ break;
7496
+ case "show-confirmation":
7497
+ showConfirmation(cmd.payload);
7498
+ break;
7350
7499
  case "preview":
7351
7500
  if (!cmd.guide) break;
7352
7501
  if (manipulator?.isDragging()) pendingPreview = cmd;
@@ -7431,10 +7580,154 @@ function initBuilderMode() {
7431
7580
  post({ type: "closed" });
7432
7581
  panel?.teardown();
7433
7582
  panel = null;
7583
+ highlightBox?.remove();
7584
+ highlightBox = null;
7585
+ confirmation?.remove();
7586
+ confirmation = null;
7587
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
7588
+ confirmationKey = null;
7434
7589
  chrome?.teardown();
7435
7590
  chrome = null;
7436
7591
  clearBuilderToken();
7437
7592
  };
7593
+ const scanPage = (selectors) => {
7594
+ const present = selectors.filter((sel) => {
7595
+ try {
7596
+ return document.querySelector(sel) !== null;
7597
+ } catch {
7598
+ return false;
7599
+ }
7600
+ });
7601
+ const taggedEls = /* @__PURE__ */ new Set();
7602
+ for (const sel of present) {
7603
+ try {
7604
+ for (const e of Array.from(document.querySelectorAll(sel))) taggedEls.add(e);
7605
+ } catch {
7606
+ }
7607
+ }
7608
+ const seen = /* @__PURE__ */ new Set();
7609
+ const candidates = [];
7610
+ const nodes = document.querySelectorAll(INTERACTIVE_SELECTOR);
7611
+ for (const element of Array.from(nodes)) {
7612
+ if (candidates.length >= 200) break;
7613
+ if (element.closest("[data-veo-builder-chrome],[data-veo-builder-panel],[data-veo-picker]")) {
7614
+ continue;
7615
+ }
7616
+ if (element.getClientRects().length === 0) continue;
7617
+ let tagged = false;
7618
+ for (let n = element; n; n = n.parentElement) {
7619
+ if (taggedEls.has(n)) {
7620
+ tagged = true;
7621
+ break;
7622
+ }
7623
+ }
7624
+ if (tagged) continue;
7625
+ const selector = buildSelectorPath(element, 5);
7626
+ if (!selector || seen.has(selector)) continue;
7627
+ seen.add(selector);
7628
+ candidates.push({
7629
+ selector,
7630
+ shortSelector: buildSelectorPath(element, 0),
7631
+ tag: element.tagName.toLowerCase(),
7632
+ id: element.id || null,
7633
+ classes: filterHumanClasses(element),
7634
+ text: elementLabelText(element)
7635
+ });
7636
+ }
7637
+ const { pathname, hash } = window.location;
7638
+ const routePath = /^#\/./.test(hash) ? `${pathname}${hash}` : pathname;
7639
+ return { pagePath: pathname, routePath, candidates, present };
7640
+ };
7641
+ let confirmation = null;
7642
+ let confirmationKey = null;
7643
+ const closeConfirmation = () => {
7644
+ if (!confirmation) return;
7645
+ confirmation.remove();
7646
+ confirmation = null;
7647
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
7648
+ confirmationKey = null;
7649
+ panel?.setVisible(true);
7650
+ post({ type: "confirmation-closed" });
7651
+ };
7652
+ const showConfirmation = (card) => {
7653
+ if (confirmation) {
7654
+ confirmation.remove();
7655
+ confirmation = null;
7656
+ }
7657
+ panel?.setVisible(false);
7658
+ const host = document.createElement("div");
7659
+ host.setAttribute("data-veo-builder-chrome", "");
7660
+ host.setAttribute("data-veo-confirmation", "");
7661
+ const shadow = host.attachShadow({ mode: "open" });
7662
+ const style = document.createElement("style");
7663
+ style.textContent = CONFIRMATION_STYLES;
7664
+ const backdrop = document.createElement("div");
7665
+ backdrop.className = "veo-confirm-backdrop";
7666
+ const dialog = document.createElement("div");
7667
+ dialog.className = "veo-confirm";
7668
+ dialog.setAttribute("role", "dialog");
7669
+ dialog.setAttribute("aria-modal", "true");
7670
+ dialog.setAttribute("aria-label", card.title);
7671
+ const badge = document.createElement("div");
7672
+ badge.className = "veo-confirm-check";
7673
+ badge.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
7674
+ const title = document.createElement("h2");
7675
+ title.className = "veo-confirm-title";
7676
+ title.textContent = card.title;
7677
+ const message = document.createElement("p");
7678
+ message.className = "veo-confirm-msg";
7679
+ message.textContent = card.message;
7680
+ const rows = document.createElement("dl");
7681
+ rows.className = "veo-confirm-rows";
7682
+ for (const r of card.rows) {
7683
+ const row = document.createElement("div");
7684
+ row.className = "veo-confirm-row";
7685
+ const dt = document.createElement("dt");
7686
+ dt.textContent = r.label;
7687
+ const dd = document.createElement("dd");
7688
+ dd.textContent = r.value;
7689
+ row.append(dt, dd);
7690
+ rows.appendChild(row);
7691
+ }
7692
+ const button = document.createElement("button");
7693
+ button.type = "button";
7694
+ button.className = "veo-confirm-btn";
7695
+ button.textContent = card.buttonLabel ?? "Listo";
7696
+ button.addEventListener("click", closeConfirmation);
7697
+ dialog.append(badge, title, message);
7698
+ if (card.rows.length > 0) dialog.appendChild(rows);
7699
+ dialog.appendChild(button);
7700
+ shadow.append(style, backdrop, dialog);
7701
+ document.body.appendChild(host);
7702
+ confirmation = host;
7703
+ confirmationKey = (e) => {
7704
+ if (e.key !== "Escape") return;
7705
+ e.preventDefault();
7706
+ e.stopPropagation();
7707
+ closeConfirmation();
7708
+ };
7709
+ document.addEventListener("keydown", confirmationKey, true);
7710
+ button.focus();
7711
+ };
7712
+ let highlightBox = null;
7713
+ const setHighlight = (selector) => {
7714
+ highlightBox?.remove();
7715
+ highlightBox = null;
7716
+ if (!selector) return;
7717
+ let target = null;
7718
+ try {
7719
+ target = document.querySelector(selector);
7720
+ } catch {
7721
+ return;
7722
+ }
7723
+ if (!target) return;
7724
+ const r = target.getBoundingClientRect();
7725
+ const box = document.createElement("div");
7726
+ box.setAttribute("data-veo-builder-chrome", "");
7727
+ box.style.cssText = `position:fixed;left:${r.left - 3}px;top:${r.top - 3}px;width:${r.width + 6}px;height:${r.height + 6}px;border:2px solid #FF5B35;border-radius:6px;background:rgba(255,91,53,0.10);pointer-events:none;z-index:2147483646;`;
7728
+ document.body.appendChild(box);
7729
+ highlightBox = box;
7730
+ };
7438
7731
  if (ctx.panel) {
7439
7732
  chrome = injectBuilderChrome({
7440
7733
  appLabel: window.location.hostname,