veo-sdk 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ export { INTERACTIVE_SELECTOR, VEO_BLOCK_MIME, attachBlockDropTarget, attachDesignManipulator, closestInteractive, createBuilderSession, elementLabelText, initBuilderMode, isInteractiveElement, startElementPicker, stopElementPicker } from './chunk-DU6I4ZU2.mjs';
2
+ import './chunk-SZCIXA5X.mjs';
3
+ import './chunk-PTG3BTJE.mjs';
4
+ //# sourceMappingURL=builder-PPLIAUCM.mjs.map
5
+ //# sourceMappingURL=builder-PPLIAUCM.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"builder-ENKEIVF2.mjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"builder-PPLIAUCM.mjs"}
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) {
@@ -5217,14 +5303,19 @@ function injectBuilderChrome(opts) {
5217
5303
  let previewOn = false;
5218
5304
  let hiddenOn = false;
5219
5305
  let heightCb = null;
5220
- const height = () => CHROME_TOPBAR_H + (collapsed ? 0 : CHROME_STEPS_H);
5306
+ let tagging = false;
5307
+ const height = () => CHROME_TOPBAR_H + (collapsed || tagging ? 0 : CHROME_STEPS_H);
5221
5308
  const applyHeight = () => {
5222
5309
  const h = previewOn || hiddenOn ? 0 : height();
5223
5310
  html.style.marginTop = dock === "top" ? `${h}px` : prevMarginTop;
5224
5311
  html.style.marginBottom = dock === "bottom" ? `${h}px` : prevMarginBottom;
5225
5312
  root.style.setProperty("--veo-chrome-h", `${height()}px`);
5226
5313
  root.classList.toggle("is-bottom", dock === "bottom");
5227
- stepsBar.hidden = collapsed;
5314
+ stepsBar.hidden = collapsed || tagging;
5315
+ togglePill.hidden = tagging;
5316
+ preview.hidden = tagging;
5317
+ saveGroup.hidden = tagging;
5318
+ showPanel.title = tagging ? "Mostrar el panel" : "Mostrar el editor";
5228
5319
  const open = dock === "top" ? "chevronUp" : "chevronDown";
5229
5320
  const closedIcon = dock === "top" ? "chevronDown" : "chevronUp";
5230
5321
  togglePill.innerHTML = `<span>${collapsed ? "Mostrar pasos" : "Ocultar pasos"}</span>${svg(
@@ -5277,8 +5368,13 @@ function injectBuilderChrome(opts) {
5277
5368
  const render = () => {
5278
5369
  const s = current;
5279
5370
  if (!s) return;
5371
+ const nextTagging = s.variant === "tagging";
5372
+ if (nextTagging !== tagging) {
5373
+ tagging = nextTagging;
5374
+ applyHeight();
5375
+ }
5280
5376
  const date = shortDate(s.createdAt);
5281
- nameEl.textContent = `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5377
+ nameEl.textContent = tagging ? s.guideName.trim() || "Modo tagging" : `${s.guideName.trim() || "Sin nombre"}${date ? ` \xB7 ${date}` : ""}`;
5282
5378
  if (s.status) {
5283
5379
  statusEl.hidden = false;
5284
5380
  statusEl.textContent = s.status.label;
@@ -5377,6 +5473,8 @@ var CHROME_CSS = `
5377
5473
  font-size: 14px; color: #1c1917;
5378
5474
  }
5379
5475
  .veo-chrome[hidden] { display: none; }
5476
+ /* Modo tagging oculta controles por atributo: gana a los display de sus clases. */
5477
+ .veo-chrome [hidden], .veo-steps-toggle[hidden] { display: none !important; }
5380
5478
  .veo-chrome.is-bottom { top: auto; bottom: 0; flex-direction: column-reverse; }
5381
5479
  .veo-chrome.is-bottom .veo-tb-menu { top: auto; bottom: calc(100% + 8px); }
5382
5480
  .veo-chrome.is-bottom .veo-steps { border-bottom: 0; border-top: 1px solid #d6cfc8; }
@@ -5996,90 +6094,51 @@ function attachBanner(opts, shadow, banner) {
5996
6094
  };
5997
6095
  }
5998
6096
 
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
6097
+ // src/plugins/builder/interactive-elements.ts
6098
+ var INTERACTIVE_ROLES = [
6099
+ "button",
6100
+ "link",
6101
+ "tab",
6102
+ "menuitem",
6103
+ "menuitemcheckbox",
6104
+ "menuitemradio",
6105
+ "checkbox",
6106
+ "radio",
6107
+ "switch",
6108
+ "option",
6109
+ "combobox",
6110
+ "textbox",
6111
+ "searchbox",
6112
+ "slider",
6113
+ "spinbutton"
6011
6114
  ];
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"
6036
- ];
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
- }
6115
+ var INTERACTIVE_SELECTOR = [
6116
+ "button",
6117
+ "a[href]",
6118
+ 'input:not([type="hidden"])',
6119
+ "select",
6120
+ "textarea",
6121
+ "form",
6122
+ "summary",
6123
+ '[contenteditable="true"]',
6124
+ "[onclick]",
6125
+ "[data-veo-tag]",
6126
+ ...INTERACTIVE_ROLES.map((r) => `[role="${r}"]`)
6127
+ ].join(", ");
6128
+ function closestInteractive(element) {
6129
+ try {
6130
+ return element.closest(INTERACTIVE_SELECTOR);
6131
+ } catch {
6132
+ return null;
6053
6133
  }
6054
- return null;
6055
6134
  }
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}`;
6066
- }
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
- }
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(" > ");
6135
+ var EMOJI_RE = /\p{Extended_Pictographic}|\p{Emoji_Modifier}|\u{FE0F}|\u{200D}|\u{20E3}/gu;
6136
+ function elementLabelText(element, max = 120) {
6137
+ const clean = (raw) => (raw ?? "").replace(EMOJI_RE, "").replace(/\s+/g, " ").trim();
6138
+ const text = clean(element.textContent);
6139
+ if (text) return text.slice(0, max);
6140
+ const fallback = clean(element.getAttribute("aria-label")) || clean(element.getAttribute("title"));
6141
+ return fallback.slice(0, max);
6083
6142
  }
6084
6143
 
6085
6144
  // src/plugins/builder/element-picker.ts
@@ -6152,21 +6211,24 @@ var ElementPicker = class {
6152
6211
  this.label = null;
6153
6212
  this.current = null;
6154
6213
  this.maxAncestors = DEFAULT_MAX_ANCESTORS;
6214
+ this.only = null;
6155
6215
  this.onPick = null;
6156
6216
  this.onCancel = null;
6157
6217
  this.onMove = (event) => {
6158
- const target = event.target;
6159
- if (!(target instanceof Element) || this.isOwn(target)) return;
6218
+ if (!(event.target instanceof Element) || this.isOwn(event.target)) return;
6219
+ const target = this.resolveTarget(event.target);
6160
6220
  this.current = target;
6161
- this.draw(target);
6221
+ if (target) this.draw(target);
6222
+ else this.hideBox();
6162
6223
  };
6163
6224
  this.onClick = (event) => {
6164
6225
  if (event.altKey) return;
6165
- const target = this.current ?? (event.target instanceof Element ? event.target : null);
6166
- if (!target || this.isOwn(target)) return;
6226
+ if (this.isOwn(event.target)) return;
6167
6227
  event.preventDefault();
6168
6228
  event.stopPropagation();
6169
6229
  event.stopImmediatePropagation();
6230
+ const target = this.current ?? this.resolveTarget(event.target);
6231
+ if (!target) return;
6170
6232
  const picked = this.describe(target);
6171
6233
  const cb = this.onPick;
6172
6234
  this.stop();
@@ -6193,6 +6255,7 @@ var ElementPicker = class {
6193
6255
  start(options) {
6194
6256
  this.stop();
6195
6257
  this.maxAncestors = options.maxAncestors ?? DEFAULT_MAX_ANCESTORS;
6258
+ this.only = options.only ?? null;
6196
6259
  this.onPick = options.onPick;
6197
6260
  this.onCancel = options.onCancel ?? null;
6198
6261
  const host = document.createElement("div");
@@ -6255,9 +6318,19 @@ var ElementPicker = class {
6255
6318
  this.box = null;
6256
6319
  this.label = null;
6257
6320
  this.current = null;
6321
+ this.only = null;
6258
6322
  this.onPick = null;
6259
6323
  this.onCancel = null;
6260
6324
  }
6325
+ /** Elemento que se elegiría para el nodo bajo el cursor (o null si no hay). */
6326
+ resolveTarget(target) {
6327
+ if (!(target instanceof Element) || this.isOwn(target)) return null;
6328
+ return this.only === "interactive" ? closestInteractive(target) : target;
6329
+ }
6330
+ hideBox() {
6331
+ if (this.box) this.box.style.display = "none";
6332
+ if (this.label) this.label.style.display = "none";
6333
+ }
6261
6334
  /** ¿Es un nodo de nuestro propio overlay? (no debe ser seleccionable). */
6262
6335
  isOwn(target) {
6263
6336
  return target instanceof Node && this.host !== null && this.host.contains(target);
@@ -6294,7 +6367,7 @@ var ElementPicker = class {
6294
6367
  tag: element.tagName.toLowerCase(),
6295
6368
  id: element.id || null,
6296
6369
  classes: filterHumanClasses(element),
6297
- text: (element.textContent ?? "").trim().slice(0, 120),
6370
+ text: elementLabelText(element),
6298
6371
  rect: {
6299
6372
  x: Math.round(rect.left),
6300
6373
  y: Math.round(rect.top),
@@ -6319,6 +6392,41 @@ function stopElementPicker() {
6319
6392
  }
6320
6393
 
6321
6394
  // src/plugins/builder/builder-mode.ts
6395
+ var CONFIRMATION_STYLES = `
6396
+ :host { all: initial; }
6397
+ .veo-confirm-backdrop {
6398
+ position: fixed; inset: 0; z-index: 2147483645;
6399
+ background: rgba(20, 16, 14, 0.45);
6400
+ }
6401
+ .veo-confirm {
6402
+ position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%);
6403
+ z-index: 2147483646; box-sizing: border-box;
6404
+ width: min(432px, calc(100vw - 32px)); padding: 28px 24px 24px;
6405
+ border-radius: 16px; background: #fff; color: #1d1917;
6406
+ box-shadow: 0 24px 64px rgba(0,0,0,0.28);
6407
+ font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
6408
+ text-align: center;
6409
+ }
6410
+ .veo-confirm-check {
6411
+ width: 64px; height: 64px; margin: 0 auto; border-radius: 50%;
6412
+ background: #e6f6df; color: #2f9a4e; display: flex; align-items: center; justify-content: center;
6413
+ }
6414
+ .veo-confirm-check svg { width: 30px; height: 30px; }
6415
+ .veo-confirm-title { margin: 18px 0 0; font-size: 17px; font-weight: 600; }
6416
+ .veo-confirm-msg { margin: 8px 0 0; font-size: 13px; color: #6b6461; }
6417
+ .veo-confirm-rows {
6418
+ margin: 18px 0 0; padding: 12px 14px; border-radius: 12px; background: #f6f2ef; text-align: left;
6419
+ }
6420
+ .veo-confirm-row { display: flex; justify-content: space-between; gap: 16px; font-size: 13px; padding: 4px 0; }
6421
+ .veo-confirm-row dt { color: #6b6461; margin: 0; }
6422
+ .veo-confirm-row dd { margin: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
6423
+ .veo-confirm-btn {
6424
+ display: block; width: 100%; margin-top: 18px; height: 44px; border: 0; border-radius: 10px;
6425
+ background: #b74d00; color: #fff; font: inherit; font-size: 15px; font-weight: 600; cursor: pointer;
6426
+ }
6427
+ .veo-confirm-btn:hover { background: #953c00; }
6428
+ .veo-confirm-btn:focus-visible { outline: 3px solid rgba(183,77,0,0.35); outline-offset: 2px; }
6429
+ `;
6322
6430
  function initBuilderMode() {
6323
6431
  if (!hasWindow() || !hasDocument()) return null;
6324
6432
  const ctx = resolveBuilderContext();
@@ -7300,7 +7408,7 @@ function initBuilderMode() {
7300
7408
  if (h && hoverCard && !previewMode) renderCardChrome(h, previewStepIndex);
7301
7409
  }
7302
7410
  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"
7411
+ 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
7412
  );
7305
7413
  break;
7306
7414
  case "panel-visibility":
@@ -7338,7 +7446,8 @@ function initBuilderMode() {
7338
7446
  post({ type: "pick-cancelled" });
7339
7447
  },
7340
7448
  ...cmd.maxAncestors !== void 0 ? { maxAncestors: cmd.maxAncestors } : {},
7341
- ...cmd.hint !== void 0 ? { hint: cmd.hint } : {}
7449
+ ...cmd.hint !== void 0 ? { hint: cmd.hint } : {},
7450
+ ...cmd.only !== void 0 ? { only: cmd.only } : {}
7342
7451
  });
7343
7452
  break;
7344
7453
  }
@@ -7347,6 +7456,15 @@ function initBuilderMode() {
7347
7456
  picker = null;
7348
7457
  chrome?.setHidden(false);
7349
7458
  break;
7459
+ case "scan-page":
7460
+ post({ type: "page-scan", payload: scanPage(cmd.selectors) });
7461
+ break;
7462
+ case "highlight":
7463
+ setHighlight(cmd.selector);
7464
+ break;
7465
+ case "show-confirmation":
7466
+ showConfirmation(cmd.payload);
7467
+ break;
7350
7468
  case "preview":
7351
7469
  if (!cmd.guide) break;
7352
7470
  if (manipulator?.isDragging()) pendingPreview = cmd;
@@ -7431,10 +7549,154 @@ function initBuilderMode() {
7431
7549
  post({ type: "closed" });
7432
7550
  panel?.teardown();
7433
7551
  panel = null;
7552
+ highlightBox?.remove();
7553
+ highlightBox = null;
7554
+ confirmation?.remove();
7555
+ confirmation = null;
7556
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
7557
+ confirmationKey = null;
7434
7558
  chrome?.teardown();
7435
7559
  chrome = null;
7436
7560
  clearBuilderToken();
7437
7561
  };
7562
+ const scanPage = (selectors) => {
7563
+ const present = selectors.filter((sel) => {
7564
+ try {
7565
+ return document.querySelector(sel) !== null;
7566
+ } catch {
7567
+ return false;
7568
+ }
7569
+ });
7570
+ const taggedEls = /* @__PURE__ */ new Set();
7571
+ for (const sel of present) {
7572
+ try {
7573
+ for (const e of Array.from(document.querySelectorAll(sel))) taggedEls.add(e);
7574
+ } catch {
7575
+ }
7576
+ }
7577
+ const seen = /* @__PURE__ */ new Set();
7578
+ const candidates = [];
7579
+ const nodes = document.querySelectorAll(INTERACTIVE_SELECTOR);
7580
+ for (const element of Array.from(nodes)) {
7581
+ if (candidates.length >= 200) break;
7582
+ if (element.closest("[data-veo-builder-chrome],[data-veo-builder-panel],[data-veo-picker]")) {
7583
+ continue;
7584
+ }
7585
+ if (element.getClientRects().length === 0) continue;
7586
+ let tagged = false;
7587
+ for (let n = element; n; n = n.parentElement) {
7588
+ if (taggedEls.has(n)) {
7589
+ tagged = true;
7590
+ break;
7591
+ }
7592
+ }
7593
+ if (tagged) continue;
7594
+ const selector = buildSelectorPath(element, 5);
7595
+ if (!selector || seen.has(selector)) continue;
7596
+ seen.add(selector);
7597
+ candidates.push({
7598
+ selector,
7599
+ shortSelector: buildSelectorPath(element, 0),
7600
+ tag: element.tagName.toLowerCase(),
7601
+ id: element.id || null,
7602
+ classes: filterHumanClasses(element),
7603
+ text: elementLabelText(element)
7604
+ });
7605
+ }
7606
+ const { pathname, hash } = window.location;
7607
+ const routePath = /^#\/./.test(hash) ? `${pathname}${hash}` : pathname;
7608
+ return { pagePath: pathname, routePath, candidates, present };
7609
+ };
7610
+ let confirmation = null;
7611
+ let confirmationKey = null;
7612
+ const closeConfirmation = () => {
7613
+ if (!confirmation) return;
7614
+ confirmation.remove();
7615
+ confirmation = null;
7616
+ if (confirmationKey) document.removeEventListener("keydown", confirmationKey, true);
7617
+ confirmationKey = null;
7618
+ panel?.setVisible(true);
7619
+ post({ type: "confirmation-closed" });
7620
+ };
7621
+ const showConfirmation = (card) => {
7622
+ if (confirmation) {
7623
+ confirmation.remove();
7624
+ confirmation = null;
7625
+ }
7626
+ panel?.setVisible(false);
7627
+ const host = document.createElement("div");
7628
+ host.setAttribute("data-veo-builder-chrome", "");
7629
+ host.setAttribute("data-veo-confirmation", "");
7630
+ const shadow = host.attachShadow({ mode: "open" });
7631
+ const style = document.createElement("style");
7632
+ style.textContent = CONFIRMATION_STYLES;
7633
+ const backdrop = document.createElement("div");
7634
+ backdrop.className = "veo-confirm-backdrop";
7635
+ const dialog = document.createElement("div");
7636
+ dialog.className = "veo-confirm";
7637
+ dialog.setAttribute("role", "dialog");
7638
+ dialog.setAttribute("aria-modal", "true");
7639
+ dialog.setAttribute("aria-label", card.title);
7640
+ const badge = document.createElement("div");
7641
+ badge.className = "veo-confirm-check";
7642
+ 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>';
7643
+ const title = document.createElement("h2");
7644
+ title.className = "veo-confirm-title";
7645
+ title.textContent = card.title;
7646
+ const message = document.createElement("p");
7647
+ message.className = "veo-confirm-msg";
7648
+ message.textContent = card.message;
7649
+ const rows = document.createElement("dl");
7650
+ rows.className = "veo-confirm-rows";
7651
+ for (const r of card.rows) {
7652
+ const row = document.createElement("div");
7653
+ row.className = "veo-confirm-row";
7654
+ const dt = document.createElement("dt");
7655
+ dt.textContent = r.label;
7656
+ const dd = document.createElement("dd");
7657
+ dd.textContent = r.value;
7658
+ row.append(dt, dd);
7659
+ rows.appendChild(row);
7660
+ }
7661
+ const button = document.createElement("button");
7662
+ button.type = "button";
7663
+ button.className = "veo-confirm-btn";
7664
+ button.textContent = card.buttonLabel ?? "Listo";
7665
+ button.addEventListener("click", closeConfirmation);
7666
+ dialog.append(badge, title, message);
7667
+ if (card.rows.length > 0) dialog.appendChild(rows);
7668
+ dialog.appendChild(button);
7669
+ shadow.append(style, backdrop, dialog);
7670
+ document.body.appendChild(host);
7671
+ confirmation = host;
7672
+ confirmationKey = (e) => {
7673
+ if (e.key !== "Escape") return;
7674
+ e.preventDefault();
7675
+ e.stopPropagation();
7676
+ closeConfirmation();
7677
+ };
7678
+ document.addEventListener("keydown", confirmationKey, true);
7679
+ button.focus();
7680
+ };
7681
+ let highlightBox = null;
7682
+ const setHighlight = (selector) => {
7683
+ highlightBox?.remove();
7684
+ highlightBox = null;
7685
+ if (!selector) return;
7686
+ let target = null;
7687
+ try {
7688
+ target = document.querySelector(selector);
7689
+ } catch {
7690
+ return;
7691
+ }
7692
+ if (!target) return;
7693
+ const r = target.getBoundingClientRect();
7694
+ const box = document.createElement("div");
7695
+ box.setAttribute("data-veo-builder-chrome", "");
7696
+ 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;`;
7697
+ document.body.appendChild(box);
7698
+ highlightBox = box;
7699
+ };
7438
7700
  if (ctx.panel) {
7439
7701
  chrome = injectBuilderChrome({
7440
7702
  appLabel: window.location.hostname,