@bitovi/vybit 0.5.0 → 0.7.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.
@@ -1121,607 +1121,205 @@
1121
1121
  });
1122
1122
  };
1123
1123
 
1124
- // overlay/src/ws.ts
1125
- var socket = null;
1126
- var connected = false;
1127
- var handlers = [];
1128
- function connect(url = "ws://localhost:3333") {
1129
- socket = new WebSocket(url);
1130
- socket.addEventListener("open", () => {
1131
- connected = true;
1132
- send({ type: "REGISTER", role: "overlay" });
1133
- window.dispatchEvent(new CustomEvent("overlay-ws-connected"));
1134
- });
1135
- socket.addEventListener("close", () => {
1136
- connected = false;
1137
- socket = null;
1138
- window.dispatchEvent(new CustomEvent("overlay-ws-disconnected"));
1139
- setTimeout(() => connect(url), 3e3);
1140
- });
1141
- socket.addEventListener("message", (event) => {
1142
- try {
1143
- const data = JSON.parse(event.data);
1144
- for (const handler of handlers) {
1145
- handler(data);
1146
- }
1147
- } catch (err) {
1148
- console.error("[tw-overlay] Failed to parse message:", err);
1149
- }
1150
- });
1151
- socket.addEventListener("error", (err) => {
1152
- console.error("[tw-overlay] WebSocket error:", err);
1153
- });
1154
- }
1155
- function send(data) {
1156
- if (connected && socket) {
1157
- socket.send(JSON.stringify(data));
1158
- } else {
1159
- console.warn("[tw-overlay] Cannot send \u2014 not connected");
1124
+ // overlay/src/containers/ModalContainer.ts
1125
+ var STORAGE_KEY = "tw-modal-bounds";
1126
+ function loadBounds() {
1127
+ try {
1128
+ const stored = localStorage.getItem(STORAGE_KEY);
1129
+ if (stored) return JSON.parse(stored);
1130
+ } catch {
1160
1131
  }
1132
+ return { top: 80, left: Math.max(0, window.innerWidth - 440), width: 400, height: 600 };
1161
1133
  }
1162
- function onMessage(handler) {
1163
- handlers.push(handler);
1164
- }
1165
- function sendTo(role, data) {
1166
- send({ ...data, to: role });
1167
- }
1168
-
1169
- // overlay/src/fiber.ts
1170
- function getFiber(domNode) {
1171
- const key = Object.keys(domNode).find((k) => k.startsWith("__reactFiber$"));
1172
- return key ? domNode[key] : null;
1173
- }
1174
- function findComponentBoundary(fiber) {
1175
- let current = fiber.return;
1176
- while (current) {
1177
- if (typeof current.type === "function") {
1178
- return {
1179
- componentType: current.type,
1180
- componentName: current.type.displayName || current.type.name || "Unknown",
1181
- componentFiber: current
1182
- };
1183
- }
1184
- current = current.return;
1134
+ function saveBounds(bounds) {
1135
+ try {
1136
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(bounds));
1137
+ } catch {
1185
1138
  }
1186
- return null;
1187
1139
  }
1188
- function getRootFiber() {
1189
- const candidateIds = ["root", "app", "__next"];
1190
- for (const id of candidateIds) {
1191
- const el = document.getElementById(id);
1192
- if (!el) continue;
1193
- const key = Object.keys(el).find((k) => k.startsWith("__reactContainer$"));
1194
- if (key) {
1195
- const container = el[key];
1196
- if (container?.stateNode?.current) {
1197
- return container.stateNode.current;
1198
- }
1199
- return container;
1200
- }
1140
+ var ModalContainer = class {
1141
+ constructor(shadowRoot2) {
1142
+ this.shadowRoot = shadowRoot2;
1201
1143
  }
1202
- const reactRoot = document.querySelector("[data-reactroot]");
1203
- if (reactRoot) {
1204
- return getFiber(reactRoot);
1144
+ name = "modal";
1145
+ host = null;
1146
+ bounds = loadBounds();
1147
+ open(panelUrl) {
1148
+ if (this.host) return;
1149
+ this.bounds = loadBounds();
1150
+ const host = document.createElement("div");
1151
+ host.className = "container-modal";
1152
+ this.applyBounds(host);
1153
+ host.style.position = "fixed";
1154
+ host.style.zIndex = "999999";
1155
+ host.style.background = "#1e1e2e";
1156
+ host.style.borderRadius = "8px";
1157
+ host.style.boxShadow = "0 8px 32px rgba(0,0,0,0.4)";
1158
+ host.style.display = "flex";
1159
+ host.style.flexDirection = "column";
1160
+ host.style.overflow = "hidden";
1161
+ host.style.pointerEvents = "auto";
1162
+ const handle = document.createElement("div");
1163
+ handle.style.cssText = `
1164
+ height: 28px;
1165
+ background: #181825;
1166
+ cursor: move;
1167
+ display: flex;
1168
+ align-items: center;
1169
+ justify-content: center;
1170
+ flex-shrink: 0;
1171
+ user-select: none;
1172
+ `;
1173
+ handle.innerHTML = `<svg width="32" height="6" viewBox="0 0 32 6" fill="none"><rect x="0" y="0" width="32" height="2" rx="1" fill="#585b70"/><rect x="0" y="4" width="32" height="2" rx="1" fill="#585b70"/></svg>`;
1174
+ this.setupDrag(handle, host);
1175
+ host.appendChild(handle);
1176
+ const iframe = document.createElement("iframe");
1177
+ iframe.src = panelUrl;
1178
+ iframe.allow = "microphone";
1179
+ iframe.style.cssText = "flex:1; border:none; width:100%;";
1180
+ host.appendChild(iframe);
1181
+ const gripper = document.createElement("div");
1182
+ gripper.style.cssText = `
1183
+ position: absolute;
1184
+ bottom: 0;
1185
+ right: 0;
1186
+ width: 16px;
1187
+ height: 16px;
1188
+ cursor: nwse-resize;
1189
+ `;
1190
+ gripper.innerHTML = '<span style="position:absolute;bottom:2px;right:4px;color:#585b70;font-size:10px;">\u25E2</span>';
1191
+ this.setupResize(gripper, host, iframe);
1192
+ host.appendChild(gripper);
1193
+ this.shadowRoot.appendChild(host);
1194
+ this.host = host;
1205
1195
  }
1206
- return null;
1207
- }
1208
- function findAllInstances(rootFiber, componentType) {
1209
- const results = [];
1210
- function walk(fiber) {
1211
- if (!fiber) return;
1212
- if (fiber.type === componentType) {
1213
- results.push(fiber);
1196
+ close() {
1197
+ if (this.host) {
1198
+ this.host.remove();
1199
+ this.host = null;
1214
1200
  }
1215
- walk(fiber.child);
1216
- walk(fiber.sibling);
1217
- }
1218
- walk(rootFiber);
1219
- return results;
1220
- }
1221
-
1222
- // overlay/src/grouping.ts
1223
- function cssEscape(cls) {
1224
- if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(cls);
1225
- return cls.replace(/([^\w-])/g, "\\$1");
1226
- }
1227
- function buildSelector(tag, classes) {
1228
- return tag.toLowerCase() + classes.map((c) => `.${cssEscape(c)}`).join("");
1229
- }
1230
- function parseClassList(className) {
1231
- if (typeof className !== "string") return [];
1232
- return className.trim().split(/\s+/).filter(Boolean).sort();
1233
- }
1234
- function classDiff(refClasses, candidateClasses) {
1235
- const added = [];
1236
- const removed = [];
1237
- for (const c of candidateClasses) {
1238
- if (!refClasses.has(c)) added.push(c);
1239
1201
  }
1240
- for (const c of refClasses) {
1241
- if (!candidateClasses.has(c)) removed.push(c);
1202
+ isOpen() {
1203
+ return this.host !== null;
1242
1204
  }
1243
- added.sort();
1244
- removed.sort();
1245
- return { added, removed };
1246
- }
1247
- function diffLabel(added, removed) {
1248
- const parts = [];
1249
- for (const a of added) parts.push(`+${a}`);
1250
- for (const r of removed) parts.push(`-${r}`);
1251
- return parts.join(" ");
1252
- }
1253
- var MAX_DIFF = 10;
1254
- var MAX_CANDIDATES = 200;
1255
- function findExactMatches(clickedEl, shadowHost2) {
1256
- const classes = parseClassList(typeof clickedEl.className === "string" ? clickedEl.className : "");
1257
- const tag = clickedEl.tagName;
1258
- const fiber = getFiber(clickedEl);
1259
- const boundary = fiber ? findComponentBoundary(fiber) : null;
1260
- const componentName = boundary?.componentName ?? null;
1261
- let exactMatches;
1262
- if (boundary) {
1263
- const rootFiber = getRootFiber();
1264
- const allNodes = rootFiber ? collectComponentDOMNodes(rootFiber, boundary.componentType, tag) : [];
1265
- exactMatches = allNodes.filter(
1266
- (n) => n.tagName === tag && n.className === clickedEl.className
1267
- );
1268
- } else {
1269
- if (classes.length === 0) {
1270
- exactMatches = Array.from(
1271
- document.querySelectorAll(tag.toLowerCase())
1272
- ).filter(
1273
- (n) => (typeof n.className === "string" ? n.className.trim() : "") === "" && !isInShadowHost(n, shadowHost2)
1274
- );
1275
- } else {
1276
- const selector = buildSelector(tag, classes);
1277
- exactMatches = Array.from(
1278
- document.querySelectorAll(selector)
1279
- ).filter(
1280
- (n) => n.className === clickedEl.className && !isInShadowHost(n, shadowHost2)
1281
- );
1282
- }
1205
+ applyBounds(el) {
1206
+ el.style.top = `${this.bounds.top}px`;
1207
+ el.style.left = `${this.bounds.left}px`;
1208
+ el.style.width = `${this.bounds.width}px`;
1209
+ el.style.height = `${this.bounds.height}px`;
1283
1210
  }
1284
- if (!exactMatches.includes(clickedEl)) {
1285
- exactMatches.unshift(clickedEl);
1211
+ setupDrag(handle, host) {
1212
+ let startX = 0, startY = 0, startLeft = 0, startTop = 0;
1213
+ const onMove = (e) => {
1214
+ this.bounds.left = startLeft + (e.clientX - startX);
1215
+ this.bounds.top = startTop + (e.clientY - startY);
1216
+ host.style.left = `${this.bounds.left}px`;
1217
+ host.style.top = `${this.bounds.top}px`;
1218
+ };
1219
+ const onUp = () => {
1220
+ document.removeEventListener("mousemove", onMove);
1221
+ document.removeEventListener("mouseup", onUp);
1222
+ saveBounds(this.bounds);
1223
+ };
1224
+ handle.addEventListener("mousedown", (e) => {
1225
+ e.preventDefault();
1226
+ startX = e.clientX;
1227
+ startY = e.clientY;
1228
+ startLeft = this.bounds.left;
1229
+ startTop = this.bounds.top;
1230
+ document.addEventListener("mousemove", onMove);
1231
+ document.addEventListener("mouseup", onUp);
1232
+ });
1286
1233
  }
1287
- return {
1288
- exactMatch: exactMatches,
1289
- nearGroups: [],
1290
- // Not computed yet lazy
1291
- componentName
1292
- };
1293
- }
1294
- function computeNearGroups(clickedEl, exactMatchSet, shadowHost2) {
1295
- const rawClassName = typeof clickedEl.className === "string" ? clickedEl.className : "";
1296
- const classes = parseClassList(rawClassName);
1297
- const tag = clickedEl.tagName;
1298
- const refSet = new Set(classes);
1299
- if (classes.length === 0) return [];
1300
- const fiber = getFiber(clickedEl);
1301
- const boundary = fiber ? findComponentBoundary(fiber) : null;
1302
- let candidates;
1303
- if (boundary) {
1304
- const rootFiber = getRootFiber();
1305
- candidates = rootFiber ? collectComponentDOMNodes(rootFiber, boundary.componentType, tag) : [];
1306
- candidates = candidates.filter((n) => !exactMatchSet.has(n));
1307
- } else {
1308
- const seen = new Set(exactMatchSet);
1309
- candidates = [];
1310
- for (const cls of classes) {
1311
- const sel = `${tag.toLowerCase()}.${cssEscape(cls)}`;
1312
- for (const n of document.querySelectorAll(sel)) {
1313
- if (!seen.has(n) && !isInShadowHost(n, shadowHost2)) {
1314
- seen.add(n);
1315
- candidates.push(n);
1316
- if (candidates.length >= MAX_CANDIDATES) break;
1317
- }
1318
- }
1319
- if (candidates.length >= MAX_CANDIDATES) break;
1320
- }
1321
- }
1322
- const groupMap = /* @__PURE__ */ new Map();
1323
- for (const el of candidates) {
1324
- const candidateClasses = new Set(parseClassList(typeof el.className === "string" ? el.className : ""));
1325
- const { added, removed } = classDiff(refSet, candidateClasses);
1326
- const totalDiff = added.length + removed.length;
1327
- if (totalDiff === 0 || totalDiff > MAX_DIFF) continue;
1328
- const key = `+${added.join(",")}|-${removed.join(",")}`;
1329
- const existing = groupMap.get(key);
1330
- if (existing) {
1331
- existing.elements.push(el);
1332
- } else {
1333
- groupMap.set(key, { added, removed, elements: [el] });
1334
- }
1335
- }
1336
- const groups = [];
1337
- for (const [, g] of groupMap) {
1338
- groups.push({
1339
- label: diffLabel(g.added, g.removed),
1340
- added: g.added,
1341
- removed: g.removed,
1342
- elements: g.elements
1234
+ setupResize(gripper, host, iframe) {
1235
+ let startX = 0, startY = 0, startW = 0, startH = 0;
1236
+ const onMove = (e) => {
1237
+ this.bounds.width = Math.max(300, startW + (e.clientX - startX));
1238
+ this.bounds.height = Math.max(200, startH + (e.clientY - startY));
1239
+ host.style.width = `${this.bounds.width}px`;
1240
+ host.style.height = `${this.bounds.height}px`;
1241
+ };
1242
+ const onUp = () => {
1243
+ iframe.style.pointerEvents = "";
1244
+ document.removeEventListener("mousemove", onMove);
1245
+ document.removeEventListener("mouseup", onUp);
1246
+ saveBounds(this.bounds);
1247
+ };
1248
+ gripper.addEventListener("mousedown", (e) => {
1249
+ e.preventDefault();
1250
+ iframe.style.pointerEvents = "none";
1251
+ startX = e.clientX;
1252
+ startY = e.clientY;
1253
+ startW = this.bounds.width;
1254
+ startH = this.bounds.height;
1255
+ document.addEventListener("mousemove", onMove);
1256
+ document.addEventListener("mouseup", onUp);
1343
1257
  });
1344
1258
  }
1345
- groups.sort((a, b) => {
1346
- const diffA = a.added.length + a.removed.length;
1347
- const diffB = b.added.length + b.removed.length;
1348
- if (diffA !== diffB) return diffA - diffB;
1349
- return b.elements.length - a.elements.length;
1350
- });
1351
- return groups;
1352
- }
1353
- function collectComponentDOMNodes(rootFiber, componentType, tagName) {
1354
- const instances = findAllInstances(rootFiber, componentType);
1355
- const results = [];
1356
- for (const inst of instances) {
1357
- collectHostNodes(inst.child, tagName, results);
1358
- }
1359
- return results;
1360
- }
1361
- function collectHostNodes(fiber, tagName, out) {
1362
- if (!fiber) return;
1363
- if (fiber.tag === 5 && fiber.stateNode instanceof HTMLElement) {
1364
- if (fiber.stateNode.tagName === tagName) {
1365
- out.push(fiber.stateNode);
1366
- }
1367
- }
1368
- collectHostNodes(fiber.child, tagName, out);
1369
- collectHostNodes(fiber.sibling, tagName, out);
1370
- }
1371
- function isInShadowHost(el, shadowHost2) {
1372
- if (!shadowHost2) return false;
1373
- return shadowHost2.contains(el);
1374
- }
1259
+ };
1375
1260
 
1376
- // overlay/src/context.ts
1377
- function buildContext(target, oldClass, newClass, originalClassMap) {
1378
- const ancestors = [];
1379
- let current = target;
1380
- while (current && current !== document.documentElement) {
1381
- ancestors.push(current);
1382
- current = current.parentElement;
1383
- }
1384
- ancestors.reverse();
1385
- return buildLevel(ancestors, 0, target, oldClass, newClass, originalClassMap, 0);
1386
- }
1387
- function buildLevel(ancestors, ancestorIndex, target, oldClass, newClass, originalClassMap, indent) {
1388
- const el = ancestors[ancestorIndex];
1389
- const pad = " ".repeat(indent);
1390
- const tag = el.tagName.toLowerCase();
1391
- let attrs = "";
1392
- if (el.id) attrs += ` id="${el.id}"`;
1393
- const originalClass = originalClassMap.get(el);
1394
- const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
1395
- if (classStr) {
1396
- attrs += ` class="${classStr}"`;
1397
- }
1398
- const isTarget = el === target;
1399
- if (isTarget) {
1400
- const text = getInnerText(el);
1401
- const textNode = text ? `
1402
- ${pad} ${text}` : "";
1403
- return `${pad}<${tag}${attrs}> <!-- TARGET: change ${oldClass} \u2192 ${newClass} -->${textNode}
1404
- ${pad}</${tag}>`;
1261
+ // overlay/src/containers/PopoverContainer.ts
1262
+ var PopoverContainer = class {
1263
+ constructor(shadowRoot2) {
1264
+ this.shadowRoot = shadowRoot2;
1405
1265
  }
1406
- if (ancestorIndex >= ancestors.length - 1) {
1407
- return `${pad}<${tag}${attrs} />`;
1266
+ name = "popover";
1267
+ host = null;
1268
+ open(panelUrl) {
1269
+ if (this.host) return;
1270
+ const host = document.createElement("div");
1271
+ host.className = "container-popover";
1272
+ host.style.cssText = `
1273
+ position: fixed;
1274
+ top: 0;
1275
+ right: 0;
1276
+ width: 400px;
1277
+ height: 100vh;
1278
+ z-index: 999999;
1279
+ background: #1e1e2e;
1280
+ box-shadow: -4px 0 24px rgba(0,0,0,0.3);
1281
+ pointer-events: auto;
1282
+ `;
1283
+ const iframe = document.createElement("iframe");
1284
+ iframe.src = panelUrl;
1285
+ iframe.allow = "microphone";
1286
+ iframe.style.cssText = "width:100%; height:100%; border:none;";
1287
+ host.appendChild(iframe);
1288
+ this.shadowRoot.appendChild(host);
1289
+ this.host = host;
1408
1290
  }
1409
- const nextAncestor = ancestors[ancestorIndex + 1];
1410
- const children = Array.from(el.children);
1411
- const relevantIndex = children.indexOf(nextAncestor);
1412
- let inner = "";
1413
- if (relevantIndex === -1) {
1414
- inner = buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1);
1415
- } else {
1416
- const start = Math.max(0, relevantIndex - 3);
1417
- const end = Math.min(children.length - 1, relevantIndex + 3);
1418
- if (start > 0) {
1419
- inner += `${pad} ...
1420
- `;
1421
- }
1422
- for (let i = start; i <= end; i++) {
1423
- if (i === relevantIndex) {
1424
- inner += buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1) + "\n";
1425
- } else {
1426
- inner += renderSiblingNode(children[i], indent + 1, originalClassMap) + "\n";
1427
- }
1428
- }
1429
- if (end < children.length - 1) {
1430
- inner += `${pad} ...
1431
- `;
1291
+ close() {
1292
+ if (this.host) {
1293
+ this.host.remove();
1294
+ this.host = null;
1432
1295
  }
1433
1296
  }
1434
- return `${pad}<${tag}${attrs}>
1435
- ${inner}${pad}</${tag}>`;
1436
- }
1437
- function renderSiblingNode(el, indent, originalClassMap) {
1438
- const pad = " ".repeat(indent);
1439
- const tag = el.tagName.toLowerCase();
1440
- let attrs = "";
1441
- if (el.id) attrs += ` id="${el.id}"`;
1442
- const originalClass = originalClassMap.get(el);
1443
- const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
1444
- if (classStr) {
1445
- attrs += ` class="${classStr}"`;
1446
- }
1447
- const text = getInnerText(el);
1448
- if (!el.id && (!el.className || !el.className.trim()) && !text) {
1449
- return `${pad}<${tag}>...</${tag}>`;
1450
- }
1451
- if (text) {
1452
- return `${pad}<${tag}${attrs}>
1453
- ${pad} ${text}
1454
- ${pad}</${tag}>`;
1297
+ isOpen() {
1298
+ return this.host !== null;
1455
1299
  }
1456
- if (el.children.length > 0) {
1457
- return `${pad}<${tag}${attrs}>
1458
- ${pad} ...
1459
- ${pad}</${tag}>`;
1300
+ };
1301
+
1302
+ // overlay/src/containers/PopupContainer.ts
1303
+ var PopupContainer = class {
1304
+ name = "popup";
1305
+ popup = null;
1306
+ open(panelUrl) {
1307
+ if (this.popup && !this.popup.closed) {
1308
+ this.popup.focus();
1309
+ return;
1310
+ }
1311
+ this.popup = window.open(panelUrl, "tw-panel", "popup,width=420,height=700");
1460
1312
  }
1461
- return `${pad}<${tag}${attrs} />`;
1462
- }
1463
- function getInnerText(el) {
1464
- let text = "";
1465
- for (const node of Array.from(el.childNodes)) {
1466
- if (node.nodeType === Node.TEXT_NODE) {
1467
- text += node.textContent || "";
1313
+ close() {
1314
+ if (this.popup && !this.popup.closed) {
1315
+ this.popup.close();
1468
1316
  }
1317
+ this.popup = null;
1469
1318
  }
1470
- text = text.trim();
1471
- if (text.length > 60) text = text.slice(0, 57) + "...";
1472
- return text;
1473
- }
1474
-
1475
- // overlay/src/patcher.ts
1476
- var previewState = null;
1477
- var previewStyleEl = null;
1478
- var committedStyleEl = null;
1479
- var previewGeneration = 0;
1480
- async function applyPreview(elements, oldClass, newClass, serverOrigin) {
1481
- const gen = ++previewGeneration;
1482
- if (!previewState) {
1483
- previewState = {
1484
- elements,
1485
- originalClasses: elements.map((n) => n.className)
1486
- };
1487
- }
1488
- if (newClass) {
1489
- try {
1490
- const res = await fetch(`${serverOrigin}/css`, {
1491
- method: "POST",
1492
- headers: { "Content-Type": "application/json" },
1493
- body: JSON.stringify({ classes: [newClass] })
1494
- });
1495
- if (gen !== previewGeneration) return;
1496
- const { css } = await res.json();
1497
- if (gen !== previewGeneration) return;
1498
- if (!previewStyleEl) {
1499
- previewStyleEl = document.createElement("style");
1500
- previewStyleEl.setAttribute("data-tw-preview", "");
1501
- document.head.appendChild(previewStyleEl);
1502
- }
1503
- previewStyleEl.textContent = css;
1504
- } catch {
1505
- }
1506
- }
1507
- if (gen !== previewGeneration) return;
1508
- if (previewState) {
1509
- for (let i = 0; i < previewState.elements.length; i++) {
1510
- previewState.elements[i].className = previewState.originalClasses[i];
1511
- }
1512
- }
1513
- for (const node of elements) {
1514
- if (oldClass) node.classList.remove(oldClass);
1515
- if (newClass) node.classList.add(newClass);
1516
- }
1517
- }
1518
- function revertPreview() {
1519
- previewGeneration++;
1520
- if (previewState) {
1521
- for (let i = 0; i < previewState.elements.length; i++) {
1522
- previewState.elements[i].className = previewState.originalClasses[i];
1523
- }
1524
- previewState = null;
1525
- }
1526
- previewStyleEl?.remove();
1527
- previewStyleEl = null;
1528
- }
1529
- function getPreviewState() {
1530
- return previewState;
1531
- }
1532
- function commitPreview() {
1533
- previewGeneration++;
1534
- previewState = null;
1535
- if (previewStyleEl) {
1536
- const css = previewStyleEl.textContent || "";
1537
- if (css) {
1538
- if (!committedStyleEl) {
1539
- committedStyleEl = document.createElement("style");
1540
- committedStyleEl.setAttribute("data-tw-committed", "");
1541
- document.head.appendChild(committedStyleEl);
1542
- }
1543
- committedStyleEl.textContent += css;
1544
- }
1545
- previewStyleEl.remove();
1546
- previewStyleEl = null;
1547
- }
1548
- }
1549
-
1550
- // overlay/src/containers/PopoverContainer.ts
1551
- var PopoverContainer = class {
1552
- constructor(shadowRoot2) {
1553
- this.shadowRoot = shadowRoot2;
1554
- }
1555
- name = "popover";
1556
- host = null;
1557
- open(panelUrl) {
1558
- if (this.host) return;
1559
- const host = document.createElement("div");
1560
- host.className = "container-popover";
1561
- host.style.cssText = `
1562
- position: fixed;
1563
- top: 0;
1564
- right: 0;
1565
- width: 400px;
1566
- height: 100vh;
1567
- z-index: 999999;
1568
- background: #1e1e2e;
1569
- box-shadow: -4px 0 24px rgba(0,0,0,0.3);
1570
- pointer-events: auto;
1571
- `;
1572
- const iframe = document.createElement("iframe");
1573
- iframe.src = panelUrl;
1574
- iframe.style.cssText = "width:100%; height:100%; border:none;";
1575
- host.appendChild(iframe);
1576
- this.shadowRoot.appendChild(host);
1577
- this.host = host;
1578
- }
1579
- close() {
1580
- if (this.host) {
1581
- this.host.remove();
1582
- this.host = null;
1583
- }
1584
- }
1585
- isOpen() {
1586
- return this.host !== null;
1587
- }
1588
- };
1589
-
1590
- // overlay/src/containers/ModalContainer.ts
1591
- var STORAGE_KEY = "tw-modal-bounds";
1592
- function loadBounds() {
1593
- try {
1594
- const stored = localStorage.getItem(STORAGE_KEY);
1595
- if (stored) return JSON.parse(stored);
1596
- } catch {
1597
- }
1598
- return { top: 80, left: Math.max(0, window.innerWidth - 440), width: 400, height: 600 };
1599
- }
1600
- function saveBounds(bounds) {
1601
- try {
1602
- localStorage.setItem(STORAGE_KEY, JSON.stringify(bounds));
1603
- } catch {
1604
- }
1605
- }
1606
- var ModalContainer = class {
1607
- constructor(shadowRoot2) {
1608
- this.shadowRoot = shadowRoot2;
1609
- }
1610
- name = "modal";
1611
- host = null;
1612
- bounds = loadBounds();
1613
- open(panelUrl) {
1614
- if (this.host) return;
1615
- this.bounds = loadBounds();
1616
- const host = document.createElement("div");
1617
- host.className = "container-modal";
1618
- this.applyBounds(host);
1619
- host.style.position = "fixed";
1620
- host.style.zIndex = "999999";
1621
- host.style.background = "#1e1e2e";
1622
- host.style.borderRadius = "8px";
1623
- host.style.boxShadow = "0 8px 32px rgba(0,0,0,0.4)";
1624
- host.style.display = "flex";
1625
- host.style.flexDirection = "column";
1626
- host.style.overflow = "hidden";
1627
- host.style.pointerEvents = "auto";
1628
- const handle = document.createElement("div");
1629
- handle.style.cssText = `
1630
- height: 28px;
1631
- background: #181825;
1632
- cursor: move;
1633
- display: flex;
1634
- align-items: center;
1635
- justify-content: center;
1636
- flex-shrink: 0;
1637
- user-select: none;
1638
- `;
1639
- handle.innerHTML = '<span style="color:#585b70;font-size:11px;letter-spacing:2px;">\u22EF\u22EF\u22EF</span>';
1640
- this.setupDrag(handle, host);
1641
- host.appendChild(handle);
1642
- const iframe = document.createElement("iframe");
1643
- iframe.src = panelUrl;
1644
- iframe.style.cssText = "flex:1; border:none; width:100%;";
1645
- host.appendChild(iframe);
1646
- const gripper = document.createElement("div");
1647
- gripper.style.cssText = `
1648
- position: absolute;
1649
- bottom: 0;
1650
- right: 0;
1651
- width: 16px;
1652
- height: 16px;
1653
- cursor: nwse-resize;
1654
- `;
1655
- gripper.innerHTML = '<span style="position:absolute;bottom:2px;right:4px;color:#585b70;font-size:10px;">\u25E2</span>';
1656
- this.setupResize(gripper, host, iframe);
1657
- host.appendChild(gripper);
1658
- this.shadowRoot.appendChild(host);
1659
- this.host = host;
1660
- }
1661
- close() {
1662
- if (this.host) {
1663
- this.host.remove();
1664
- this.host = null;
1665
- }
1666
- }
1667
- isOpen() {
1668
- return this.host !== null;
1669
- }
1670
- applyBounds(el) {
1671
- el.style.top = `${this.bounds.top}px`;
1672
- el.style.left = `${this.bounds.left}px`;
1673
- el.style.width = `${this.bounds.width}px`;
1674
- el.style.height = `${this.bounds.height}px`;
1675
- }
1676
- setupDrag(handle, host) {
1677
- let startX = 0, startY = 0, startLeft = 0, startTop = 0;
1678
- const onMove = (e) => {
1679
- this.bounds.left = startLeft + (e.clientX - startX);
1680
- this.bounds.top = startTop + (e.clientY - startY);
1681
- host.style.left = `${this.bounds.left}px`;
1682
- host.style.top = `${this.bounds.top}px`;
1683
- };
1684
- const onUp = () => {
1685
- document.removeEventListener("mousemove", onMove);
1686
- document.removeEventListener("mouseup", onUp);
1687
- saveBounds(this.bounds);
1688
- };
1689
- handle.addEventListener("mousedown", (e) => {
1690
- e.preventDefault();
1691
- startX = e.clientX;
1692
- startY = e.clientY;
1693
- startLeft = this.bounds.left;
1694
- startTop = this.bounds.top;
1695
- document.addEventListener("mousemove", onMove);
1696
- document.addEventListener("mouseup", onUp);
1697
- });
1698
- }
1699
- setupResize(gripper, host, iframe) {
1700
- let startX = 0, startY = 0, startW = 0, startH = 0;
1701
- const onMove = (e) => {
1702
- this.bounds.width = Math.max(300, startW + (e.clientX - startX));
1703
- this.bounds.height = Math.max(200, startH + (e.clientY - startY));
1704
- host.style.width = `${this.bounds.width}px`;
1705
- host.style.height = `${this.bounds.height}px`;
1706
- };
1707
- const onUp = () => {
1708
- iframe.style.pointerEvents = "";
1709
- document.removeEventListener("mousemove", onMove);
1710
- document.removeEventListener("mouseup", onUp);
1711
- saveBounds(this.bounds);
1712
- };
1713
- gripper.addEventListener("mousedown", (e) => {
1714
- e.preventDefault();
1715
- iframe.style.pointerEvents = "none";
1716
- startX = e.clientX;
1717
- startY = e.clientY;
1718
- startW = this.bounds.width;
1719
- startH = this.bounds.height;
1720
- document.addEventListener("mousemove", onMove);
1721
- document.addEventListener("mouseup", onUp);
1722
- });
1723
- }
1724
- };
1319
+ isOpen() {
1320
+ return this.popup !== null && !this.popup.closed;
1321
+ }
1322
+ };
1725
1323
 
1726
1324
  // overlay/src/containers/SidebarContainer.ts
1727
1325
  var SidebarContainer = class {
@@ -1794,76 +1392,477 @@ ${pad}</${tag}>`;
1794
1392
  host.appendChild(resizeHandle);
1795
1393
  const iframe = document.createElement("iframe");
1796
1394
  iframe.src = panelUrl;
1395
+ iframe.allow = "microphone";
1797
1396
  iframe.style.cssText = "flex:1; border:none; height:100%;";
1798
1397
  host.appendChild(iframe);
1799
1398
  this.shadowRoot.appendChild(host);
1800
1399
  this.host = host;
1801
1400
  }
1802
- close() {
1803
- if (this.host) {
1804
- this.host.remove();
1805
- this.host = null;
1806
- if (this.pageWrapper) {
1807
- const children = Array.from(this.pageWrapper.childNodes);
1808
- const shadowHost2 = document.getElementById("tw-visual-editor-host");
1809
- for (const node of children) {
1810
- if (shadowHost2 && shadowHost2.parentNode) {
1811
- shadowHost2.parentNode.insertBefore(node, shadowHost2);
1812
- } else {
1813
- document.body.appendChild(node);
1814
- }
1815
- }
1816
- this.pageWrapper.remove();
1817
- this.pageWrapper = null;
1401
+ close() {
1402
+ if (this.host) {
1403
+ this.host.remove();
1404
+ this.host = null;
1405
+ if (this.pageWrapper) {
1406
+ const children = Array.from(this.pageWrapper.childNodes);
1407
+ const shadowHost2 = document.getElementById("tw-visual-editor-host");
1408
+ for (const node of children) {
1409
+ if (shadowHost2 && shadowHost2.parentNode) {
1410
+ shadowHost2.parentNode.insertBefore(node, shadowHost2);
1411
+ } else {
1412
+ document.body.appendChild(node);
1413
+ }
1414
+ }
1415
+ this.pageWrapper.remove();
1416
+ this.pageWrapper = null;
1417
+ }
1418
+ document.body.style.overflow = this.originalBodyOverflow || "";
1419
+ }
1420
+ }
1421
+ isOpen() {
1422
+ return this.host !== null;
1423
+ }
1424
+ setupResize(handle, host) {
1425
+ let startX = 0, startW = 0;
1426
+ const onMove = (e) => {
1427
+ this.width = Math.max(280, startW - (e.clientX - startX));
1428
+ host.style.width = `${this.width}px`;
1429
+ document.documentElement.style.paddingRight = `${this.width}px`;
1430
+ };
1431
+ const onUp = () => {
1432
+ document.removeEventListener("mousemove", onMove);
1433
+ document.removeEventListener("mouseup", onUp);
1434
+ };
1435
+ handle.addEventListener("mousedown", (e) => {
1436
+ e.preventDefault();
1437
+ startX = e.clientX;
1438
+ startW = this.width;
1439
+ document.addEventListener("mousemove", onMove);
1440
+ document.addEventListener("mouseup", onUp);
1441
+ });
1442
+ }
1443
+ };
1444
+
1445
+ // overlay/src/context.ts
1446
+ function buildContext(target, oldClass, newClass, originalClassMap) {
1447
+ const ancestors = [];
1448
+ let current = target;
1449
+ while (current && current !== document.documentElement) {
1450
+ ancestors.push(current);
1451
+ current = current.parentElement;
1452
+ }
1453
+ ancestors.reverse();
1454
+ return buildLevel(ancestors, 0, target, oldClass, newClass, originalClassMap, 0);
1455
+ }
1456
+ function buildLevel(ancestors, ancestorIndex, target, oldClass, newClass, originalClassMap, indent) {
1457
+ const el = ancestors[ancestorIndex];
1458
+ const pad = " ".repeat(indent);
1459
+ const tag = el.tagName.toLowerCase();
1460
+ let attrs = "";
1461
+ if (el.id) attrs += ` id="${el.id}"`;
1462
+ const originalClass = originalClassMap.get(el);
1463
+ const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
1464
+ if (classStr) {
1465
+ attrs += ` class="${classStr}"`;
1466
+ }
1467
+ const isTarget = el === target;
1468
+ if (isTarget) {
1469
+ const text = getInnerText(el);
1470
+ const textNode = text ? `
1471
+ ${pad} ${text}` : "";
1472
+ return `${pad}<${tag}${attrs}> <!-- TARGET: change ${oldClass} \u2192 ${newClass} -->${textNode}
1473
+ ${pad}</${tag}>`;
1474
+ }
1475
+ if (ancestorIndex >= ancestors.length - 1) {
1476
+ return `${pad}<${tag}${attrs} />`;
1477
+ }
1478
+ const nextAncestor = ancestors[ancestorIndex + 1];
1479
+ const children = Array.from(el.children);
1480
+ const relevantIndex = children.indexOf(nextAncestor);
1481
+ let inner = "";
1482
+ if (relevantIndex === -1) {
1483
+ inner = buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1);
1484
+ } else {
1485
+ const start = Math.max(0, relevantIndex - 3);
1486
+ const end = Math.min(children.length - 1, relevantIndex + 3);
1487
+ if (start > 0) {
1488
+ inner += `${pad} ...
1489
+ `;
1490
+ }
1491
+ for (let i = start; i <= end; i++) {
1492
+ if (i === relevantIndex) {
1493
+ inner += buildLevel(ancestors, ancestorIndex + 1, target, oldClass, newClass, originalClassMap, indent + 1) + "\n";
1494
+ } else {
1495
+ inner += renderSiblingNode(children[i], indent + 1, originalClassMap) + "\n";
1496
+ }
1497
+ }
1498
+ if (end < children.length - 1) {
1499
+ inner += `${pad} ...
1500
+ `;
1501
+ }
1502
+ }
1503
+ return `${pad}<${tag}${attrs}>
1504
+ ${inner}${pad}</${tag}>`;
1505
+ }
1506
+ function renderSiblingNode(el, indent, originalClassMap) {
1507
+ const pad = " ".repeat(indent);
1508
+ const tag = el.tagName.toLowerCase();
1509
+ let attrs = "";
1510
+ if (el.id) attrs += ` id="${el.id}"`;
1511
+ const originalClass = originalClassMap.get(el);
1512
+ const classStr = originalClass != null ? originalClass.trim() : typeof el.className === "string" ? el.className.trim() : "";
1513
+ if (classStr) {
1514
+ attrs += ` class="${classStr}"`;
1515
+ }
1516
+ const text = getInnerText(el);
1517
+ if (!el.id && (!el.className || !el.className.trim()) && !text) {
1518
+ return `${pad}<${tag}>...</${tag}>`;
1519
+ }
1520
+ if (text) {
1521
+ return `${pad}<${tag}${attrs}>
1522
+ ${pad} ${text}
1523
+ ${pad}</${tag}>`;
1524
+ }
1525
+ if (el.children.length > 0) {
1526
+ return `${pad}<${tag}${attrs}>
1527
+ ${pad} ...
1528
+ ${pad}</${tag}>`;
1529
+ }
1530
+ return `${pad}<${tag}${attrs} />`;
1531
+ }
1532
+ function getInnerText(el) {
1533
+ let text = "";
1534
+ for (const node of Array.from(el.childNodes)) {
1535
+ if (node.nodeType === Node.TEXT_NODE) {
1536
+ text += node.textContent || "";
1537
+ }
1538
+ }
1539
+ text = text.trim();
1540
+ if (text.length > 60) text = text.slice(0, 57) + "...";
1541
+ return text;
1542
+ }
1543
+
1544
+ // overlay/src/fiber.ts
1545
+ function getFiber(domNode) {
1546
+ const key = Object.keys(domNode).find((k) => k.startsWith("__reactFiber$"));
1547
+ return key ? domNode[key] : null;
1548
+ }
1549
+ function findComponentBoundary(fiber) {
1550
+ let current = fiber.return;
1551
+ while (current) {
1552
+ if (typeof current.type === "function") {
1553
+ return {
1554
+ componentType: current.type,
1555
+ componentName: current.type.displayName || current.type.name || "Unknown",
1556
+ componentFiber: current
1557
+ };
1558
+ }
1559
+ current = current.return;
1560
+ }
1561
+ return null;
1562
+ }
1563
+ function getRootFiber() {
1564
+ const candidateIds = ["root", "app", "__next"];
1565
+ for (const id of candidateIds) {
1566
+ const el = document.getElementById(id);
1567
+ if (!el) continue;
1568
+ const key = Object.keys(el).find((k) => k.startsWith("__reactContainer$"));
1569
+ if (key) {
1570
+ const container = el[key];
1571
+ if (container?.stateNode?.current) {
1572
+ return container.stateNode.current;
1573
+ }
1574
+ return container;
1575
+ }
1576
+ }
1577
+ const reactRoot = document.querySelector("[data-reactroot]");
1578
+ if (reactRoot) {
1579
+ return getFiber(reactRoot);
1580
+ }
1581
+ return null;
1582
+ }
1583
+ function findAllInstances(rootFiber, componentType) {
1584
+ const results = [];
1585
+ function walk(fiber) {
1586
+ if (!fiber) return;
1587
+ if (fiber.type === componentType) {
1588
+ results.push(fiber);
1589
+ }
1590
+ walk(fiber.child);
1591
+ walk(fiber.sibling);
1592
+ }
1593
+ walk(rootFiber);
1594
+ return results;
1595
+ }
1596
+
1597
+ // overlay/src/grouping.ts
1598
+ function cssEscape(cls) {
1599
+ if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(cls);
1600
+ return cls.replace(/([^\w-])/g, "\\$1");
1601
+ }
1602
+ function buildSelector(tag, classes) {
1603
+ return tag.toLowerCase() + classes.map((c) => `.${cssEscape(c)}`).join("");
1604
+ }
1605
+ function parseClassList(className) {
1606
+ if (typeof className !== "string") return [];
1607
+ return className.trim().split(/\s+/).filter(Boolean).sort();
1608
+ }
1609
+ function classDiff(refClasses, candidateClasses) {
1610
+ const added = [];
1611
+ const removed = [];
1612
+ for (const c of candidateClasses) {
1613
+ if (!refClasses.has(c)) added.push(c);
1614
+ }
1615
+ for (const c of refClasses) {
1616
+ if (!candidateClasses.has(c)) removed.push(c);
1617
+ }
1618
+ added.sort();
1619
+ removed.sort();
1620
+ return { added, removed };
1621
+ }
1622
+ function diffLabel(added, removed) {
1623
+ const parts = [];
1624
+ for (const a of added) parts.push(`+${a}`);
1625
+ for (const r of removed) parts.push(`-${r}`);
1626
+ return parts.join(" ");
1627
+ }
1628
+ var MAX_DIFF = 10;
1629
+ var MAX_CANDIDATES = 200;
1630
+ function findExactMatches(clickedEl, shadowHost2) {
1631
+ const classes = parseClassList(typeof clickedEl.className === "string" ? clickedEl.className : "");
1632
+ const tag = clickedEl.tagName;
1633
+ const fiber = getFiber(clickedEl);
1634
+ const boundary = fiber ? findComponentBoundary(fiber) : null;
1635
+ const componentName = boundary?.componentName ?? null;
1636
+ let exactMatches;
1637
+ if (boundary) {
1638
+ const rootFiber = getRootFiber();
1639
+ const allNodes = rootFiber ? collectComponentDOMNodes(rootFiber, boundary.componentType, tag) : [];
1640
+ exactMatches = allNodes.filter(
1641
+ (n) => n.tagName === tag && n.className === clickedEl.className
1642
+ );
1643
+ } else {
1644
+ if (classes.length === 0) {
1645
+ exactMatches = Array.from(
1646
+ document.querySelectorAll(tag.toLowerCase())
1647
+ ).filter(
1648
+ (n) => (typeof n.className === "string" ? n.className.trim() : "") === "" && !isInShadowHost(n, shadowHost2)
1649
+ );
1650
+ } else {
1651
+ const selector = buildSelector(tag, classes);
1652
+ exactMatches = Array.from(
1653
+ document.querySelectorAll(selector)
1654
+ ).filter(
1655
+ (n) => n.className === clickedEl.className && !isInShadowHost(n, shadowHost2)
1656
+ );
1657
+ }
1658
+ }
1659
+ if (!exactMatches.includes(clickedEl)) {
1660
+ exactMatches.unshift(clickedEl);
1661
+ }
1662
+ return {
1663
+ exactMatch: exactMatches,
1664
+ nearGroups: [],
1665
+ // Not computed yet — lazy
1666
+ componentName
1667
+ };
1668
+ }
1669
+ function computeNearGroups(clickedEl, exactMatchSet, shadowHost2) {
1670
+ const rawClassName = typeof clickedEl.className === "string" ? clickedEl.className : "";
1671
+ const classes = parseClassList(rawClassName);
1672
+ const tag = clickedEl.tagName;
1673
+ const refSet = new Set(classes);
1674
+ if (classes.length === 0) return [];
1675
+ const fiber = getFiber(clickedEl);
1676
+ const boundary = fiber ? findComponentBoundary(fiber) : null;
1677
+ let candidates;
1678
+ if (boundary) {
1679
+ const rootFiber = getRootFiber();
1680
+ candidates = rootFiber ? collectComponentDOMNodes(rootFiber, boundary.componentType, tag) : [];
1681
+ candidates = candidates.filter((n) => !exactMatchSet.has(n));
1682
+ } else {
1683
+ const seen = new Set(exactMatchSet);
1684
+ candidates = [];
1685
+ for (const cls of classes) {
1686
+ const sel = `${tag.toLowerCase()}.${cssEscape(cls)}`;
1687
+ for (const n of document.querySelectorAll(sel)) {
1688
+ if (!seen.has(n) && !isInShadowHost(n, shadowHost2)) {
1689
+ seen.add(n);
1690
+ candidates.push(n);
1691
+ if (candidates.length >= MAX_CANDIDATES) break;
1692
+ }
1693
+ }
1694
+ if (candidates.length >= MAX_CANDIDATES) break;
1695
+ }
1696
+ }
1697
+ const groupMap = /* @__PURE__ */ new Map();
1698
+ for (const el of candidates) {
1699
+ const candidateClasses = new Set(parseClassList(typeof el.className === "string" ? el.className : ""));
1700
+ const { added, removed } = classDiff(refSet, candidateClasses);
1701
+ const totalDiff = added.length + removed.length;
1702
+ if (totalDiff === 0 || totalDiff > MAX_DIFF) continue;
1703
+ const key = `+${added.join(",")}|-${removed.join(",")}`;
1704
+ const existing = groupMap.get(key);
1705
+ if (existing) {
1706
+ existing.elements.push(el);
1707
+ } else {
1708
+ groupMap.set(key, { added, removed, elements: [el] });
1709
+ }
1710
+ }
1711
+ const groups = [];
1712
+ for (const [, g] of groupMap) {
1713
+ groups.push({
1714
+ label: diffLabel(g.added, g.removed),
1715
+ added: g.added,
1716
+ removed: g.removed,
1717
+ elements: g.elements
1718
+ });
1719
+ }
1720
+ groups.sort((a, b) => {
1721
+ const diffA = a.added.length + a.removed.length;
1722
+ const diffB = b.added.length + b.removed.length;
1723
+ if (diffA !== diffB) return diffA - diffB;
1724
+ return b.elements.length - a.elements.length;
1725
+ });
1726
+ return groups;
1727
+ }
1728
+ function collectComponentDOMNodes(rootFiber, componentType, tagName) {
1729
+ const instances = findAllInstances(rootFiber, componentType);
1730
+ const results = [];
1731
+ for (const inst of instances) {
1732
+ collectHostNodes(inst.child, tagName, results);
1733
+ }
1734
+ return results;
1735
+ }
1736
+ function collectHostNodes(fiber, tagName, out) {
1737
+ if (!fiber) return;
1738
+ if (fiber.tag === 5 && fiber.stateNode instanceof HTMLElement) {
1739
+ if (fiber.stateNode.tagName === tagName) {
1740
+ out.push(fiber.stateNode);
1741
+ }
1742
+ }
1743
+ collectHostNodes(fiber.child, tagName, out);
1744
+ collectHostNodes(fiber.sibling, tagName, out);
1745
+ }
1746
+ function isInShadowHost(el, shadowHost2) {
1747
+ if (!shadowHost2) return false;
1748
+ return shadowHost2.contains(el);
1749
+ }
1750
+
1751
+ // overlay/src/patcher.ts
1752
+ var previewState = null;
1753
+ var previewStyleEl = null;
1754
+ var committedStyleEl = null;
1755
+ var previewGeneration = 0;
1756
+ async function applyPreview(elements, oldClass, newClass, serverOrigin) {
1757
+ const gen = ++previewGeneration;
1758
+ if (!previewState) {
1759
+ previewState = {
1760
+ elements,
1761
+ originalClasses: elements.map((n) => n.className)
1762
+ };
1763
+ }
1764
+ if (newClass) {
1765
+ try {
1766
+ const res = await fetch(`${serverOrigin}/css`, {
1767
+ method: "POST",
1768
+ headers: { "Content-Type": "application/json" },
1769
+ body: JSON.stringify({ classes: [newClass] })
1770
+ });
1771
+ if (gen !== previewGeneration) return;
1772
+ const { css } = await res.json();
1773
+ if (gen !== previewGeneration) return;
1774
+ if (!previewStyleEl) {
1775
+ previewStyleEl = document.createElement("style");
1776
+ previewStyleEl.setAttribute("data-tw-preview", "");
1777
+ document.head.appendChild(previewStyleEl);
1818
1778
  }
1819
- document.body.style.overflow = this.originalBodyOverflow || "";
1779
+ previewStyleEl.textContent = css;
1780
+ } catch {
1820
1781
  }
1821
1782
  }
1822
- isOpen() {
1823
- return this.host !== null;
1783
+ if (gen !== previewGeneration) return;
1784
+ if (previewState) {
1785
+ for (let i = 0; i < previewState.elements.length; i++) {
1786
+ previewState.elements[i].className = previewState.originalClasses[i];
1787
+ }
1824
1788
  }
1825
- setupResize(handle, host) {
1826
- let startX = 0, startW = 0;
1827
- const onMove = (e) => {
1828
- this.width = Math.max(280, startW - (e.clientX - startX));
1829
- host.style.width = `${this.width}px`;
1830
- document.documentElement.style.paddingRight = `${this.width}px`;
1831
- };
1832
- const onUp = () => {
1833
- document.removeEventListener("mousemove", onMove);
1834
- document.removeEventListener("mouseup", onUp);
1789
+ for (const node of elements) {
1790
+ if (oldClass) node.classList.remove(oldClass);
1791
+ if (newClass) node.classList.add(newClass);
1792
+ }
1793
+ }
1794
+ async function applyPreviewBatch(elements, pairs, serverOrigin) {
1795
+ const gen = ++previewGeneration;
1796
+ if (!previewState) {
1797
+ previewState = {
1798
+ elements,
1799
+ originalClasses: elements.map((n) => n.className)
1835
1800
  };
1836
- handle.addEventListener("mousedown", (e) => {
1837
- e.preventDefault();
1838
- startX = e.clientX;
1839
- startW = this.width;
1840
- document.addEventListener("mousemove", onMove);
1841
- document.addEventListener("mouseup", onUp);
1842
- });
1843
1801
  }
1844
- };
1845
-
1846
- // overlay/src/containers/PopupContainer.ts
1847
- var PopupContainer = class {
1848
- name = "popup";
1849
- popup = null;
1850
- open(panelUrl) {
1851
- if (this.popup && !this.popup.closed) {
1852
- this.popup.focus();
1853
- return;
1802
+ const newClasses = pairs.map((p) => p.newClass).filter(Boolean);
1803
+ if (newClasses.length > 0) {
1804
+ try {
1805
+ const res = await fetch(`${serverOrigin}/css`, {
1806
+ method: "POST",
1807
+ headers: { "Content-Type": "application/json" },
1808
+ body: JSON.stringify({ classes: newClasses })
1809
+ });
1810
+ if (gen !== previewGeneration) return;
1811
+ const { css } = await res.json();
1812
+ if (gen !== previewGeneration) return;
1813
+ if (!previewStyleEl) {
1814
+ previewStyleEl = document.createElement("style");
1815
+ previewStyleEl.setAttribute("data-tw-preview", "");
1816
+ document.head.appendChild(previewStyleEl);
1817
+ }
1818
+ previewStyleEl.textContent = css;
1819
+ } catch {
1854
1820
  }
1855
- this.popup = window.open(panelUrl, "tw-panel", "popup,width=420,height=700");
1856
1821
  }
1857
- close() {
1858
- if (this.popup && !this.popup.closed) {
1859
- this.popup.close();
1822
+ if (gen !== previewGeneration) return;
1823
+ if (previewState) {
1824
+ for (let i = 0; i < previewState.elements.length; i++) {
1825
+ previewState.elements[i].className = previewState.originalClasses[i];
1860
1826
  }
1861
- this.popup = null;
1862
1827
  }
1863
- isOpen() {
1864
- return this.popup !== null && !this.popup.closed;
1828
+ for (const node of elements) {
1829
+ for (const { oldClass, newClass } of pairs) {
1830
+ if (oldClass) node.classList.remove(oldClass);
1831
+ if (newClass) node.classList.add(newClass);
1832
+ }
1865
1833
  }
1866
- };
1834
+ }
1835
+ function revertPreview() {
1836
+ previewGeneration++;
1837
+ if (previewState) {
1838
+ for (let i = 0; i < previewState.elements.length; i++) {
1839
+ previewState.elements[i].className = previewState.originalClasses[i];
1840
+ }
1841
+ previewState = null;
1842
+ }
1843
+ previewStyleEl?.remove();
1844
+ previewStyleEl = null;
1845
+ }
1846
+ function getPreviewState() {
1847
+ return previewState;
1848
+ }
1849
+ function commitPreview() {
1850
+ previewGeneration++;
1851
+ previewState = null;
1852
+ if (previewStyleEl) {
1853
+ const css = previewStyleEl.textContent || "";
1854
+ if (css) {
1855
+ if (!committedStyleEl) {
1856
+ committedStyleEl = document.createElement("style");
1857
+ committedStyleEl.setAttribute("data-tw-committed", "");
1858
+ document.head.appendChild(committedStyleEl);
1859
+ }
1860
+ committedStyleEl.textContent += css;
1861
+ }
1862
+ previewStyleEl.remove();
1863
+ previewStyleEl = null;
1864
+ }
1865
+ }
1867
1866
 
1868
1867
  // node_modules/html-to-image/es/util.js
1869
1868
  function resolveUrl(url, baseUrl) {
@@ -2704,6 +2703,51 @@ ${pad}</${tag}>`;
2704
2703
  }
2705
2704
  }
2706
2705
 
2706
+ // overlay/src/ws.ts
2707
+ var socket = null;
2708
+ var connected = false;
2709
+ var handlers = [];
2710
+ function connect(url = "ws://localhost:3333") {
2711
+ socket = new WebSocket(url);
2712
+ socket.addEventListener("open", () => {
2713
+ connected = true;
2714
+ send({ type: "REGISTER", role: "overlay" });
2715
+ window.dispatchEvent(new CustomEvent("overlay-ws-connected"));
2716
+ });
2717
+ socket.addEventListener("close", () => {
2718
+ connected = false;
2719
+ socket = null;
2720
+ window.dispatchEvent(new CustomEvent("overlay-ws-disconnected"));
2721
+ setTimeout(() => connect(url), 3e3);
2722
+ });
2723
+ socket.addEventListener("message", (event) => {
2724
+ try {
2725
+ const data = JSON.parse(event.data);
2726
+ for (const handler of handlers) {
2727
+ handler(data);
2728
+ }
2729
+ } catch (err) {
2730
+ console.error("[tw-overlay] Failed to parse message:", err);
2731
+ }
2732
+ });
2733
+ socket.addEventListener("error", (err) => {
2734
+ console.error("[tw-overlay] WebSocket error:", err);
2735
+ });
2736
+ }
2737
+ function send(data) {
2738
+ if (connected && socket) {
2739
+ socket.send(JSON.stringify(data));
2740
+ } else {
2741
+ console.warn("[tw-overlay] Cannot send \u2014 not connected");
2742
+ }
2743
+ }
2744
+ function onMessage(handler) {
2745
+ handlers.push(handler);
2746
+ }
2747
+ function sendTo(role, data) {
2748
+ send({ ...data, to: role });
2749
+ }
2750
+
2707
2751
  // overlay/src/index.ts
2708
2752
  var shadowRoot;
2709
2753
  var shadowHost;
@@ -2886,11 +2930,40 @@ ${pad}</${tag}>`;
2886
2930
  z-index: 999998;
2887
2931
  }
2888
2932
  /* \u2500\u2500 Group picker popover (replaces instance picker) \u2500\u2500 */
2933
+ .el-group-exact {
2934
+ display: flex;
2935
+ align-items: center;
2936
+ gap: 8px;
2937
+ padding: 8px 12px;
2938
+ font-size: 11px;
2939
+ color: #A0ABAB;
2940
+ }
2941
+ .el-group-exact .el-count-chip {
2942
+ display: inline-flex;
2943
+ align-items: center;
2944
+ justify-content: center;
2945
+ min-width: 20px;
2946
+ padding: 1px 6px;
2947
+ font-size: 10px;
2948
+ font-weight: 600;
2949
+ color: #fff;
2950
+ background: #00848B;
2951
+ border-radius: 9999px;
2952
+ }
2953
+ .el-group-divider {
2954
+ padding: 6px 12px 4px;
2955
+ font-size: 10px;
2956
+ font-weight: 700;
2957
+ text-transform: uppercase;
2958
+ letter-spacing: 0.05em;
2959
+ color: #687879;
2960
+ border-top: 1px solid #DFE2E2;
2961
+ }
2889
2962
  .el-group-empty {
2890
2963
  padding: 12px 14px;
2891
2964
  font-size: 11px;
2892
2965
  color: #687879;
2893
- text-align: center;
2966
+ text-align: left;
2894
2967
  }
2895
2968
  .el-group-row {
2896
2969
  display: flex;
@@ -3187,11 +3260,8 @@ ${pad}</${tag}>`;
3187
3260
  const label = boundary?.componentName ?? target.tagName.toLowerCase();
3188
3261
  showHoverPreview(target, label);
3189
3262
  }
3190
- var PENCIL_SVG = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3191
- <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
3192
- <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
3193
- </svg>`;
3194
- var RESELECT_SVG = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M8 12l2.5 6 1.5-3 3-1.5z"/></svg>`;
3263
+ var PENCIL_SVG = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M15,0H1C0.448,0,0,0.448,0,1v9c0,0.552,0.448,1,1,1h2.882l-1.776,3.553c-0.247,0.494-0.047,1.095,0.447,1.342C2.696,15.966,2.849,16,2.999,16c0.367,0,0.72-0.202,0.896-0.553L4.618,14h6.764l0.724,1.447C12.281,15.798,12.634,16,13.001,16c0.15,0,0.303-0.034,0.446-0.105c0.494-0.247,0.694-0.848,0.447-1.342L12.118,11H15c0.552,0,1-0.448,1-1V1C16,0.448,15.552,0,15,0z M5.618,12l0.5-1h3.764l0.5,1H5.618z M14,9H2V2h12V9z"/></svg>`;
3264
+ var RESELECT_SVG = `<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><path d="M14,0H2C.895,0,0,.895,0,2V14c0,1.105,.895,2,2,2H6c.552,0,1-.448,1-1h0c0-.552-.448-1-1-1H2V2H14V6c0,.552,.448,1,1,1h0c.552,0,1-.448,1-1V2c0-1.105-.895-2-2-2Z"/><path d="M12.043,10.629l2.578-.644c.268-.068,.43-.339,.362-.607-.043-.172-.175-.308-.345-.358l-7-2c-.175-.051-.363-.002-.492,.126-.128,.129-.177,.317-.126,.492l2,7c.061,.214,.257,.362,.48,.362h.009c.226-.004,.421-.16,.476-.379l.644-2.578,3.664,3.664c.397,.384,1.03,.373,1.414-.025,.374-.388,.374-1.002,0-1.389l-3.664-3.664Z"/></svg>`;
3195
3265
  async function positionWithFlip(anchor, floating, placement = "top-start") {
3196
3266
  const { x, y } = await computePosition2(anchor, floating, {
3197
3267
  placement,
@@ -3267,7 +3337,9 @@ ${pad}</${tag}>`;
3267
3337
  }
3268
3338
  function showGroupPicker(anchorBtn, onClose, onCountChange) {
3269
3339
  if (pickerCloseHandler) {
3270
- document.removeEventListener("click", pickerCloseHandler, { capture: true });
3340
+ document.removeEventListener("click", pickerCloseHandler, {
3341
+ capture: true
3342
+ });
3271
3343
  pickerCloseHandler = null;
3272
3344
  }
3273
3345
  pickerEl?.remove();
@@ -3286,13 +3358,28 @@ ${pad}</${tag}>`;
3286
3358
  header.className = "el-picker-header";
3287
3359
  const title = document.createElement("span");
3288
3360
  title.className = "el-picker-title";
3289
- title.textContent = "Similar elements";
3361
+ title.textContent = "Selection";
3290
3362
  header.appendChild(title);
3291
3363
  picker.appendChild(header);
3364
+ const exactCount = currentEquivalentNodes.length;
3365
+ const exactRow = document.createElement("div");
3366
+ exactRow.className = "el-group-exact";
3367
+ const chip = document.createElement("span");
3368
+ chip.className = "el-count-chip";
3369
+ chip.textContent = String(exactCount);
3370
+ exactRow.appendChild(chip);
3371
+ const exactLabel = document.createElement("span");
3372
+ exactLabel.textContent = `exact match${exactCount !== 1 ? "es" : ""} selected`;
3373
+ exactRow.appendChild(exactLabel);
3374
+ picker.appendChild(exactRow);
3375
+ const divider = document.createElement("div");
3376
+ divider.className = "el-group-divider";
3377
+ divider.textContent = "Similar";
3378
+ picker.appendChild(divider);
3292
3379
  if (groups.length === 0) {
3293
3380
  const empty = document.createElement("div");
3294
3381
  empty.className = "el-group-empty";
3295
- empty.textContent = "No similar elements found";
3382
+ empty.textContent = "No additional similar elements found";
3296
3383
  picker.appendChild(empty);
3297
3384
  } else {
3298
3385
  let clearPreviewHighlights2 = function() {
@@ -3341,8 +3428,10 @@ ${pad}</${tag}>`;
3341
3428
  const diff = document.createElement("span");
3342
3429
  diff.className = "el-group-diff";
3343
3430
  const parts = [];
3344
- for (const a of group.added) parts.push(`<span class="diff-add">+${a}</span>`);
3345
- for (const r of group.removed) parts.push(`<span class="diff-rem">-${r}</span>`);
3431
+ for (const a of group.added)
3432
+ parts.push(`<span class="diff-add">+${a}</span>`);
3433
+ for (const r of group.removed)
3434
+ parts.push(`<span class="diff-rem">-${r}</span>`);
3346
3435
  diff.innerHTML = parts.join(" ");
3347
3436
  row.appendChild(cb);
3348
3437
  row.appendChild(count);
@@ -3370,7 +3459,9 @@ ${pad}</${tag}>`;
3370
3459
  const removePicker = () => {
3371
3460
  shadowRoot.querySelectorAll(".highlight-preview").forEach((el) => el.remove());
3372
3461
  if (pickerCloseHandler) {
3373
- document.removeEventListener("click", pickerCloseHandler, { capture: true });
3462
+ document.removeEventListener("click", pickerCloseHandler, {
3463
+ capture: true
3464
+ });
3374
3465
  pickerCloseHandler = null;
3375
3466
  }
3376
3467
  pickerEl?.remove();
@@ -3429,7 +3520,7 @@ ${pad}</${tag}>`;
3429
3520
  const screenshotRow = document.createElement("button");
3430
3521
  screenshotRow.className = "draw-popover-item";
3431
3522
  screenshotRow.innerHTML = `
3432
- <span class="draw-popover-icon">\u{1F4F7}</span>
3523
+ <span class="draw-popover-icon"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor"><rect x="4" y="4" width="8" height="8" rx="1" ry="1"/><path d="M2,2H6V0H2C.895,0,0,.895,0,2V6H2V2Z"/><path d="M14,0h-4V2h4V6h2V2c0-1.105-.895-2-2-2Z"/><path d="M14,14h-4v2h4c1.105,0,2-.895,2-2v-4h-2v4Z"/><path d="M2,10H0v4c0,1.105,.895,2,2,2H6v-2H2v-4Z"/></svg></span>
3433
3524
  <span class="draw-popover-label">Screenshot & Annotate</span>
3434
3525
  `;
3435
3526
  screenshotRow.addEventListener("click", (e) => {
@@ -3483,7 +3574,10 @@ ${pad}</${tag}>`;
3483
3574
  async function clickHandler(e) {
3484
3575
  const composed = e.composedPath();
3485
3576
  if (composed.some((el) => el === shadowHost)) return;
3486
- if (composed.some((el) => el instanceof HTMLElement && el.hasAttribute("data-tw-design-canvas"))) return;
3577
+ if (composed.some(
3578
+ (el) => el instanceof HTMLElement && el.hasAttribute("data-tw-design-canvas")
3579
+ ))
3580
+ return;
3487
3581
  e.preventDefault();
3488
3582
  e.stopPropagation();
3489
3583
  const target = e.target;
@@ -3495,7 +3589,6 @@ ${pad}</${tag}>`;
3495
3589
  for (const node of result.exactMatch) {
3496
3590
  highlightElement(node);
3497
3591
  }
3498
- console.log(`[overlay] ${componentName} \u2014 ${result.exactMatch.length} exact matches highlighted`);
3499
3592
  const config = await fetchTailwindConfig();
3500
3593
  currentEquivalentNodes = result.exactMatch;
3501
3594
  currentTargetEl = targetEl;
@@ -3590,6 +3683,7 @@ ${pad}</${tag}>`;
3590
3683
  `;
3591
3684
  const iframe = document.createElement("iframe");
3592
3685
  iframe.src = `${SERVER_ORIGIN}/panel/?mode=design`;
3686
+ iframe.allow = "microphone";
3593
3687
  iframe.style.cssText = `
3594
3688
  width: 100%;
3595
3689
  height: 100%;
@@ -3702,7 +3796,12 @@ ${pad}</${tag}>`;
3702
3796
  default:
3703
3797
  targetEl.insertAdjacentElement("beforebegin", wrapper);
3704
3798
  }
3705
- designCanvasWrappers.push({ wrapper, replacedNodes: null, parent: null, anchor: null });
3799
+ designCanvasWrappers.push({
3800
+ wrapper,
3801
+ replacedNodes: null,
3802
+ parent: null,
3803
+ anchor: null
3804
+ });
3706
3805
  iframe.addEventListener("load", () => {
3707
3806
  const contextMsg = {
3708
3807
  type: "ELEMENT_CONTEXT",
@@ -3731,23 +3830,36 @@ ${pad}</${tag}>`;
3731
3830
  return;
3732
3831
  }
3733
3832
  if (!areSiblings(currentEquivalentNodes)) {
3734
- showToast("Screenshot & Annotate requires all selected elements to be siblings in the DOM.");
3833
+ showToast(
3834
+ "Screenshot & Annotate requires all selected elements to be siblings in the DOM."
3835
+ );
3735
3836
  return;
3736
3837
  }
3737
3838
  let screenshot;
3738
3839
  let screenshotWidth;
3739
3840
  let screenshotHeight;
3740
3841
  try {
3741
- ({ dataUrl: screenshot, width: screenshotWidth, height: screenshotHeight } = await captureRegion(currentEquivalentNodes));
3842
+ ({
3843
+ dataUrl: screenshot,
3844
+ width: screenshotWidth,
3845
+ height: screenshotHeight
3846
+ } = await captureRegion(currentEquivalentNodes));
3742
3847
  } catch (err) {
3743
3848
  showToast("Screenshot capture failed");
3744
3849
  console.error("[overlay] captureRegion error:", err);
3745
3850
  return;
3746
3851
  }
3747
3852
  const parent = currentEquivalentNodes[0].parentElement;
3748
- const anchor = currentEquivalentNodes[0].nextSibling;
3853
+ if (!parent) {
3854
+ showToast("Cannot find parent element");
3855
+ return;
3856
+ }
3857
+ const marker = document.createComment("tw-placeholder");
3858
+ parent.insertBefore(marker, currentEquivalentNodes[0]);
3749
3859
  const firstStyle = getComputedStyle(currentEquivalentNodes[0]);
3750
- const lastStyle = getComputedStyle(currentEquivalentNodes[currentEquivalentNodes.length - 1]);
3860
+ const lastStyle = getComputedStyle(
3861
+ currentEquivalentNodes[currentEquivalentNodes.length - 1]
3862
+ );
3751
3863
  const marginTop = firstStyle.marginTop;
3752
3864
  const marginBottom = lastStyle.marginBottom;
3753
3865
  const marginLeft = firstStyle.marginLeft;
@@ -3789,12 +3901,9 @@ ${pad}</${tag}>`;
3789
3901
  display: block;
3790
3902
  `;
3791
3903
  wrapper.appendChild(iframe);
3792
- if (anchor) {
3793
- parent.insertBefore(wrapper, anchor);
3794
- } else {
3795
- parent.appendChild(wrapper);
3796
- }
3797
- designCanvasWrappers.push({ wrapper, replacedNodes, parent, anchor });
3904
+ parent.insertBefore(wrapper, marker);
3905
+ marker.remove();
3906
+ designCanvasWrappers.push({ wrapper, replacedNodes, parent, anchor: wrapper.nextSibling });
3798
3907
  iframe.addEventListener("load", () => {
3799
3908
  const contextMsg = {
3800
3909
  type: "ELEMENT_CONTEXT",
@@ -3867,9 +3976,18 @@ ${pad}</${tag}>`;
3867
3976
  setSelectMode(false);
3868
3977
  }
3869
3978
  } else if (msg.type === "PATCH_PREVIEW" && currentEquivalentNodes.length > 0) {
3870
- applyPreview(currentEquivalentNodes, msg.oldClass, msg.newClass, SERVER_ORIGIN);
3979
+ applyPreview(
3980
+ currentEquivalentNodes,
3981
+ msg.oldClass,
3982
+ msg.newClass,
3983
+ SERVER_ORIGIN
3984
+ );
3985
+ } else if (msg.type === "PATCH_PREVIEW_BATCH" && currentEquivalentNodes.length > 0) {
3986
+ applyPreviewBatch(currentEquivalentNodes, msg.pairs, SERVER_ORIGIN);
3871
3987
  } else if (msg.type === "PATCH_REVERT") {
3872
3988
  revertPreview();
3989
+ } else if (msg.type === "PATCH_REVERT_STAGED" && currentEquivalentNodes.length > 0) {
3990
+ applyPreview(currentEquivalentNodes, msg.oldClass, msg.newClass, SERVER_ORIGIN).then(() => commitPreview());
3873
3991
  } else if (msg.type === "PATCH_STAGE" && currentTargetEl && currentBoundary) {
3874
3992
  const state = getPreviewState();
3875
3993
  const originalClassMap = /* @__PURE__ */ new Map();
@@ -3880,7 +3998,12 @@ ${pad}</${tag}>`;
3880
3998
  }
3881
3999
  const targetElIndex = currentEquivalentNodes.indexOf(currentTargetEl);
3882
4000
  const originalClassString = state && targetElIndex !== -1 ? state.originalClasses[targetElIndex] : currentTargetEl.className;
3883
- const context = buildContext(currentTargetEl, msg.oldClass, msg.newClass, originalClassMap);
4001
+ const context = buildContext(
4002
+ currentTargetEl,
4003
+ msg.oldClass,
4004
+ msg.newClass,
4005
+ originalClassMap
4006
+ );
3884
4007
  send({
3885
4008
  type: "PATCH_STAGED",
3886
4009
  patch: {
@@ -3940,6 +4063,8 @@ ${pad}</${tag}>`;
3940
4063
  last.appendChild(img);
3941
4064
  }
3942
4065
  }
4066
+ } else if (msg.type === "CLOSE_PANEL") {
4067
+ if (active) toggleInspect(btn);
3943
4068
  } else if (msg.type === "DESIGN_CLOSE") {
3944
4069
  const last = designCanvasWrappers.pop();
3945
4070
  if (last) {
@@ -3953,6 +4078,12 @@ ${pad}</${tag}>`;
3953
4078
  }
3954
4079
  }
3955
4080
  last.wrapper.remove();
4081
+ if (currentTargetEl && currentEquivalentNodes.length > 0) {
4082
+ for (const n of currentEquivalentNodes) {
4083
+ highlightElement(n);
4084
+ }
4085
+ showDrawButton(currentTargetEl);
4086
+ }
3956
4087
  }
3957
4088
  }
3958
4089
  });
@@ -3965,15 +4096,19 @@ ${pad}</${tag}>`;
3965
4096
  positionWithFlip(currentTargetEl, toolbarEl);
3966
4097
  }
3967
4098
  });
3968
- window.addEventListener("scroll", () => {
3969
- if (currentEquivalentNodes.length > 0) {
3970
- shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
3971
- currentEquivalentNodes.forEach((n) => highlightElement(n));
3972
- }
3973
- if (toolbarEl && currentTargetEl) {
3974
- positionWithFlip(currentTargetEl, toolbarEl);
3975
- }
3976
- }, { capture: true, passive: true });
4099
+ window.addEventListener(
4100
+ "scroll",
4101
+ () => {
4102
+ if (currentEquivalentNodes.length > 0) {
4103
+ shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
4104
+ currentEquivalentNodes.forEach((n) => highlightElement(n));
4105
+ }
4106
+ if (toolbarEl && currentTargetEl) {
4107
+ positionWithFlip(currentTargetEl, toolbarEl);
4108
+ }
4109
+ },
4110
+ { capture: true, passive: true }
4111
+ );
3977
4112
  if (sessionStorage.getItem(PANEL_OPEN_KEY) === "1") {
3978
4113
  active = true;
3979
4114
  btn.classList.add("active");