@bitovi/vybit 0.4.10 → 0.6.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.
@@ -932,8 +932,8 @@
932
932
  }
933
933
  return getComputedStyle2(parentNode).position === "fixed" || hasFixedPositionAncestor(parentNode, stopNode);
934
934
  }
935
- function getClippingElementAncestors(element, cache) {
936
- const cachedResult = cache.get(element);
935
+ function getClippingElementAncestors(element, cache2) {
936
+ const cachedResult = cache2.get(element);
937
937
  if (cachedResult) {
938
938
  return cachedResult;
939
939
  }
@@ -955,7 +955,7 @@
955
955
  }
956
956
  currentNode = getParentNode(currentNode);
957
957
  }
958
- cache.set(element, result);
958
+ cache2.set(element, result);
959
959
  return result;
960
960
  }
961
961
  function getClippingRect(_ref) {
@@ -1106,14 +1106,14 @@
1106
1106
  var offset2 = offset;
1107
1107
  var flip2 = flip;
1108
1108
  var computePosition2 = (reference, floating, options) => {
1109
- const cache = /* @__PURE__ */ new Map();
1109
+ const cache2 = /* @__PURE__ */ new Map();
1110
1110
  const mergedOptions = {
1111
1111
  platform,
1112
1112
  ...options
1113
1113
  };
1114
1114
  const platformWithCache = {
1115
1115
  ...mergedOptions.platform,
1116
- _c: cache
1116
+ _c: cache2
1117
1117
  };
1118
1118
  return computePosition(reference, floating, {
1119
1119
  ...mergedOptions,
@@ -1218,138 +1218,159 @@
1218
1218
  walk(rootFiber);
1219
1219
  return results;
1220
1220
  }
1221
- function getChildPath(componentFiber, targetFiber) {
1222
- const path = [];
1223
- let current = targetFiber;
1224
- while (current && current !== componentFiber) {
1225
- const parent = current.return;
1226
- if (!parent) break;
1227
- let index = 0;
1228
- let sibling = parent.child;
1229
- while (sibling && sibling !== current) {
1230
- sibling = sibling.sibling;
1231
- index++;
1232
- }
1233
- path.push(index);
1234
- current = parent;
1235
- }
1236
- path.reverse();
1237
- return path;
1238
- }
1239
- function resolvePathToDOM(instanceFiber, path) {
1240
- let current = instanceFiber;
1241
- for (const index of path) {
1242
- if (!current) return null;
1243
- current = current.child;
1244
- if (!current) return null;
1245
- for (let i = 0; i < index; i++) {
1246
- if (!current) return null;
1247
- current = current.sibling;
1248
- }
1249
- }
1250
- if (!current) return null;
1251
- return getDOMNode(current);
1252
- }
1253
- function getDOMNode(fiber) {
1254
- if (fiber.stateNode instanceof HTMLElement) {
1255
- return fiber.stateNode;
1256
- }
1257
- let child = fiber.child;
1258
- while (child) {
1259
- if (child.tag === 5 && child.stateNode instanceof HTMLElement) {
1260
- return child.stateNode;
1261
- }
1262
- const result = getDOMNode(child);
1263
- if (result) return result;
1264
- child = child.sibling;
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
+ }
1240
+ for (const c of refClasses) {
1241
+ if (!candidateClasses.has(c)) removed.push(c);
1242
+ }
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
+ }
1265
1283
  }
1266
- return null;
1267
- }
1268
- function findEquivalentDescendant(container, clicked, containerNode) {
1269
- const chain = [];
1270
- let el = clicked;
1271
- while (el && el !== containerNode) {
1272
- chain.unshift(el);
1273
- el = el.parentElement;
1274
- }
1275
- if (chain.length === 0) return container;
1276
- let node = container;
1277
- for (const ancestor of chain) {
1278
- const match = Array.from(node.children).find(
1279
- (c) => c instanceof HTMLElement && c.tagName === ancestor.tagName && c.className === ancestor.className
1280
- ) ?? null;
1281
- if (!match) return null;
1282
- node = match;
1284
+ if (!exactMatches.includes(clickedEl)) {
1285
+ exactMatches.unshift(clickedEl);
1283
1286
  }
1284
- return node;
1287
+ return {
1288
+ exactMatch: exactMatches,
1289
+ nearGroups: [],
1290
+ // Not computed yet — lazy
1291
+ componentName
1292
+ };
1285
1293
  }
1286
- function findInlineRepeatedNodes(targetFiber, boundaryFiber, minSiblings = 3) {
1287
- const clickedNode = getDOMNode(targetFiber);
1288
- if (!clickedNode) return [];
1289
- let current = targetFiber;
1290
- let bestResult = [];
1291
- while (current && current !== boundaryFiber) {
1292
- const parent = current.return;
1293
- if (!parent) break;
1294
- const sameType = [];
1295
- let child = parent.child;
1296
- while (child) {
1297
- if (child.type === current.type) {
1298
- sameType.push(child);
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
+ }
1299
1318
  }
1300
- child = child.sibling;
1319
+ if (candidates.length >= MAX_CANDIDATES) break;
1301
1320
  }
1302
- if (sameType.length >= minSiblings && sameType.length > bestResult.length) {
1303
- const containerNode = getDOMNode(current);
1304
- if (!containerNode) {
1305
- current = parent;
1306
- continue;
1307
- }
1308
- const containerNodes = [];
1309
- for (const sib of sameType) {
1310
- const node = getDOMNode(sib);
1311
- if (node) containerNodes.push(node);
1312
- }
1313
- const majorityClass = containerNodes.map((n) => n.className).sort(
1314
- (a, b) => containerNodes.filter((n) => n.className === b).length - containerNodes.filter((n) => n.className === a).length
1315
- )[0];
1316
- const outliers = containerNodes.filter((n) => n.className !== majorityClass);
1317
- if (outliers.length > 1) {
1318
- current = parent;
1319
- continue;
1320
- }
1321
- const results = [];
1322
- for (const sibContainer of containerNodes) {
1323
- const equiv = findEquivalentDescendant(sibContainer, clickedNode, containerNode);
1324
- if (equiv) results.push(equiv);
1325
- }
1326
- if (results.length > bestResult.length) {
1327
- bestResult = results;
1328
- }
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] });
1329
1334
  }
1330
- current = parent;
1331
- }
1332
- return bestResult;
1333
- }
1334
- function findDOMEquivalents(el) {
1335
- const classes = typeof el.className === "string" ? el.className.trim() : "";
1336
- if (!classes) return [el];
1337
- const exactMatches = Array.from(
1338
- document.querySelectorAll(el.tagName.toLowerCase())
1339
- ).filter((n) => n.className === el.className);
1340
- if (exactMatches.length >= 2) return exactMatches;
1341
- const parent = el.parentElement;
1342
- if (!parent) return [el];
1343
- const siblings = Array.from(parent.children).filter(
1344
- (c) => c instanceof HTMLElement && c.tagName === el.tagName
1345
- );
1346
- if (siblings.length < 2) return [el];
1347
- const majorityClass = siblings.map((n) => n.className).sort(
1348
- (a, b) => siblings.filter((n) => n.className === b).length - siblings.filter((n) => n.className === a).length
1349
- )[0];
1350
- const outliers = siblings.filter((n) => n.className !== majorityClass);
1351
- if (outliers.length > 1) return [el];
1352
- return siblings.filter((n) => n.className === majorityClass);
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
1343
+ });
1344
+ }
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);
1353
1374
  }
1354
1375
 
1355
1376
  // overlay/src/context.ts
@@ -1464,22 +1485,24 @@ ${pad}</${tag}>`;
1464
1485
  originalClasses: elements.map((n) => n.className)
1465
1486
  };
1466
1487
  }
1467
- try {
1468
- const res = await fetch(`${serverOrigin}/css`, {
1469
- method: "POST",
1470
- headers: { "Content-Type": "application/json" },
1471
- body: JSON.stringify({ classes: [newClass] })
1472
- });
1473
- if (gen !== previewGeneration) return;
1474
- const { css } = await res.json();
1475
- if (gen !== previewGeneration) return;
1476
- if (!previewStyleEl) {
1477
- previewStyleEl = document.createElement("style");
1478
- previewStyleEl.setAttribute("data-tw-preview", "");
1479
- document.head.appendChild(previewStyleEl);
1480
- }
1481
- previewStyleEl.textContent = css;
1482
- } catch {
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
+ }
1483
1506
  }
1484
1507
  if (gen !== previewGeneration) return;
1485
1508
  if (previewState) {
@@ -1489,7 +1512,48 @@ ${pad}</${tag}>`;
1489
1512
  }
1490
1513
  for (const node of elements) {
1491
1514
  if (oldClass) node.classList.remove(oldClass);
1492
- node.classList.add(newClass);
1515
+ if (newClass) node.classList.add(newClass);
1516
+ }
1517
+ }
1518
+ async function applyPreviewBatch(elements, pairs, serverOrigin) {
1519
+ const gen = ++previewGeneration;
1520
+ if (!previewState) {
1521
+ previewState = {
1522
+ elements,
1523
+ originalClasses: elements.map((n) => n.className)
1524
+ };
1525
+ }
1526
+ const newClasses = pairs.map((p) => p.newClass).filter(Boolean);
1527
+ if (newClasses.length > 0) {
1528
+ try {
1529
+ const res = await fetch(`${serverOrigin}/css`, {
1530
+ method: "POST",
1531
+ headers: { "Content-Type": "application/json" },
1532
+ body: JSON.stringify({ classes: newClasses })
1533
+ });
1534
+ if (gen !== previewGeneration) return;
1535
+ const { css } = await res.json();
1536
+ if (gen !== previewGeneration) return;
1537
+ if (!previewStyleEl) {
1538
+ previewStyleEl = document.createElement("style");
1539
+ previewStyleEl.setAttribute("data-tw-preview", "");
1540
+ document.head.appendChild(previewStyleEl);
1541
+ }
1542
+ previewStyleEl.textContent = css;
1543
+ } catch {
1544
+ }
1545
+ }
1546
+ if (gen !== previewGeneration) return;
1547
+ if (previewState) {
1548
+ for (let i = 0; i < previewState.elements.length; i++) {
1549
+ previewState.elements[i].className = previewState.originalClasses[i];
1550
+ }
1551
+ }
1552
+ for (const node of elements) {
1553
+ for (const { oldClass, newClass } of pairs) {
1554
+ if (oldClass) node.classList.remove(oldClass);
1555
+ if (newClass) node.classList.add(newClass);
1556
+ }
1493
1557
  }
1494
1558
  }
1495
1559
  function revertPreview() {
@@ -1548,6 +1612,7 @@ ${pad}</${tag}>`;
1548
1612
  `;
1549
1613
  const iframe = document.createElement("iframe");
1550
1614
  iframe.src = panelUrl;
1615
+ iframe.allow = "microphone";
1551
1616
  iframe.style.cssText = "width:100%; height:100%; border:none;";
1552
1617
  host.appendChild(iframe);
1553
1618
  this.shadowRoot.appendChild(host);
@@ -1618,6 +1683,7 @@ ${pad}</${tag}>`;
1618
1683
  host.appendChild(handle);
1619
1684
  const iframe = document.createElement("iframe");
1620
1685
  iframe.src = panelUrl;
1686
+ iframe.allow = "microphone";
1621
1687
  iframe.style.cssText = "flex:1; border:none; width:100%;";
1622
1688
  host.appendChild(iframe);
1623
1689
  const gripper = document.createElement("div");
@@ -1771,6 +1837,7 @@ ${pad}</${tag}>`;
1771
1837
  host.appendChild(resizeHandle);
1772
1838
  const iframe = document.createElement("iframe");
1773
1839
  iframe.src = panelUrl;
1840
+ iframe.allow = "microphone";
1774
1841
  iframe.style.cssText = "flex:1; border:none; height:100%;";
1775
1842
  host.appendChild(iframe);
1776
1843
  this.shadowRoot.appendChild(host);
@@ -1842,6 +1909,845 @@ ${pad}</${tag}>`;
1842
1909
  }
1843
1910
  };
1844
1911
 
1912
+ // node_modules/html-to-image/es/util.js
1913
+ function resolveUrl(url, baseUrl) {
1914
+ if (url.match(/^[a-z]+:\/\//i)) {
1915
+ return url;
1916
+ }
1917
+ if (url.match(/^\/\//)) {
1918
+ return window.location.protocol + url;
1919
+ }
1920
+ if (url.match(/^[a-z]+:/i)) {
1921
+ return url;
1922
+ }
1923
+ const doc = document.implementation.createHTMLDocument();
1924
+ const base = doc.createElement("base");
1925
+ const a = doc.createElement("a");
1926
+ doc.head.appendChild(base);
1927
+ doc.body.appendChild(a);
1928
+ if (baseUrl) {
1929
+ base.href = baseUrl;
1930
+ }
1931
+ a.href = url;
1932
+ return a.href;
1933
+ }
1934
+ var uuid = /* @__PURE__ */ (() => {
1935
+ let counter = 0;
1936
+ const random = () => (
1937
+ // eslint-disable-next-line no-bitwise
1938
+ `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4)
1939
+ );
1940
+ return () => {
1941
+ counter += 1;
1942
+ return `u${random()}${counter}`;
1943
+ };
1944
+ })();
1945
+ function toArray(arrayLike) {
1946
+ const arr = [];
1947
+ for (let i = 0, l = arrayLike.length; i < l; i++) {
1948
+ arr.push(arrayLike[i]);
1949
+ }
1950
+ return arr;
1951
+ }
1952
+ var styleProps = null;
1953
+ function getStyleProperties(options = {}) {
1954
+ if (styleProps) {
1955
+ return styleProps;
1956
+ }
1957
+ if (options.includeStyleProperties) {
1958
+ styleProps = options.includeStyleProperties;
1959
+ return styleProps;
1960
+ }
1961
+ styleProps = toArray(window.getComputedStyle(document.documentElement));
1962
+ return styleProps;
1963
+ }
1964
+ function px(node, styleProperty) {
1965
+ const win = node.ownerDocument.defaultView || window;
1966
+ const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
1967
+ return val ? parseFloat(val.replace("px", "")) : 0;
1968
+ }
1969
+ function getNodeWidth(node) {
1970
+ const leftBorder = px(node, "border-left-width");
1971
+ const rightBorder = px(node, "border-right-width");
1972
+ return node.clientWidth + leftBorder + rightBorder;
1973
+ }
1974
+ function getNodeHeight(node) {
1975
+ const topBorder = px(node, "border-top-width");
1976
+ const bottomBorder = px(node, "border-bottom-width");
1977
+ return node.clientHeight + topBorder + bottomBorder;
1978
+ }
1979
+ function getImageSize(targetNode, options = {}) {
1980
+ const width = options.width || getNodeWidth(targetNode);
1981
+ const height = options.height || getNodeHeight(targetNode);
1982
+ return { width, height };
1983
+ }
1984
+ function getPixelRatio() {
1985
+ let ratio;
1986
+ let FINAL_PROCESS;
1987
+ try {
1988
+ FINAL_PROCESS = process;
1989
+ } catch (e) {
1990
+ }
1991
+ const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
1992
+ if (val) {
1993
+ ratio = parseInt(val, 10);
1994
+ if (Number.isNaN(ratio)) {
1995
+ ratio = 1;
1996
+ }
1997
+ }
1998
+ return ratio || window.devicePixelRatio || 1;
1999
+ }
2000
+ var canvasDimensionLimit = 16384;
2001
+ function checkCanvasDimensions(canvas) {
2002
+ if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
2003
+ if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
2004
+ if (canvas.width > canvas.height) {
2005
+ canvas.height *= canvasDimensionLimit / canvas.width;
2006
+ canvas.width = canvasDimensionLimit;
2007
+ } else {
2008
+ canvas.width *= canvasDimensionLimit / canvas.height;
2009
+ canvas.height = canvasDimensionLimit;
2010
+ }
2011
+ } else if (canvas.width > canvasDimensionLimit) {
2012
+ canvas.height *= canvasDimensionLimit / canvas.width;
2013
+ canvas.width = canvasDimensionLimit;
2014
+ } else {
2015
+ canvas.width *= canvasDimensionLimit / canvas.height;
2016
+ canvas.height = canvasDimensionLimit;
2017
+ }
2018
+ }
2019
+ }
2020
+ function createImage(url) {
2021
+ return new Promise((resolve, reject) => {
2022
+ const img = new Image();
2023
+ img.onload = () => {
2024
+ img.decode().then(() => {
2025
+ requestAnimationFrame(() => resolve(img));
2026
+ });
2027
+ };
2028
+ img.onerror = reject;
2029
+ img.crossOrigin = "anonymous";
2030
+ img.decoding = "async";
2031
+ img.src = url;
2032
+ });
2033
+ }
2034
+ async function svgToDataURL(svg) {
2035
+ return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`);
2036
+ }
2037
+ async function nodeToDataURL(node, width, height) {
2038
+ const xmlns = "http://www.w3.org/2000/svg";
2039
+ const svg = document.createElementNS(xmlns, "svg");
2040
+ const foreignObject = document.createElementNS(xmlns, "foreignObject");
2041
+ svg.setAttribute("width", `${width}`);
2042
+ svg.setAttribute("height", `${height}`);
2043
+ svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
2044
+ foreignObject.setAttribute("width", "100%");
2045
+ foreignObject.setAttribute("height", "100%");
2046
+ foreignObject.setAttribute("x", "0");
2047
+ foreignObject.setAttribute("y", "0");
2048
+ foreignObject.setAttribute("externalResourcesRequired", "true");
2049
+ svg.appendChild(foreignObject);
2050
+ foreignObject.appendChild(node);
2051
+ return svgToDataURL(svg);
2052
+ }
2053
+ var isInstanceOfElement = (node, instance) => {
2054
+ if (node instanceof instance)
2055
+ return true;
2056
+ const nodePrototype = Object.getPrototypeOf(node);
2057
+ if (nodePrototype === null)
2058
+ return false;
2059
+ return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
2060
+ };
2061
+
2062
+ // node_modules/html-to-image/es/clone-pseudos.js
2063
+ function formatCSSText(style) {
2064
+ const content = style.getPropertyValue("content");
2065
+ return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
2066
+ }
2067
+ function formatCSSProperties(style, options) {
2068
+ return getStyleProperties(options).map((name) => {
2069
+ const value = style.getPropertyValue(name);
2070
+ const priority = style.getPropertyPriority(name);
2071
+ return `${name}: ${value}${priority ? " !important" : ""};`;
2072
+ }).join(" ");
2073
+ }
2074
+ function getPseudoElementStyle(className, pseudo, style, options) {
2075
+ const selector = `.${className}:${pseudo}`;
2076
+ const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
2077
+ return document.createTextNode(`${selector}{${cssText}}`);
2078
+ }
2079
+ function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
2080
+ const style = window.getComputedStyle(nativeNode, pseudo);
2081
+ const content = style.getPropertyValue("content");
2082
+ if (content === "" || content === "none") {
2083
+ return;
2084
+ }
2085
+ const className = uuid();
2086
+ try {
2087
+ clonedNode.className = `${clonedNode.className} ${className}`;
2088
+ } catch (err) {
2089
+ return;
2090
+ }
2091
+ const styleElement = document.createElement("style");
2092
+ styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
2093
+ clonedNode.appendChild(styleElement);
2094
+ }
2095
+ function clonePseudoElements(nativeNode, clonedNode, options) {
2096
+ clonePseudoElement(nativeNode, clonedNode, ":before", options);
2097
+ clonePseudoElement(nativeNode, clonedNode, ":after", options);
2098
+ }
2099
+
2100
+ // node_modules/html-to-image/es/mimes.js
2101
+ var WOFF = "application/font-woff";
2102
+ var JPEG = "image/jpeg";
2103
+ var mimes = {
2104
+ woff: WOFF,
2105
+ woff2: WOFF,
2106
+ ttf: "application/font-truetype",
2107
+ eot: "application/vnd.ms-fontobject",
2108
+ png: "image/png",
2109
+ jpg: JPEG,
2110
+ jpeg: JPEG,
2111
+ gif: "image/gif",
2112
+ tiff: "image/tiff",
2113
+ svg: "image/svg+xml",
2114
+ webp: "image/webp"
2115
+ };
2116
+ function getExtension(url) {
2117
+ const match = /\.([^./]*?)$/g.exec(url);
2118
+ return match ? match[1] : "";
2119
+ }
2120
+ function getMimeType(url) {
2121
+ const extension = getExtension(url).toLowerCase();
2122
+ return mimes[extension] || "";
2123
+ }
2124
+
2125
+ // node_modules/html-to-image/es/dataurl.js
2126
+ function getContentFromDataUrl(dataURL) {
2127
+ return dataURL.split(/,/)[1];
2128
+ }
2129
+ function isDataUrl(url) {
2130
+ return url.search(/^(data:)/) !== -1;
2131
+ }
2132
+ function makeDataUrl(content, mimeType) {
2133
+ return `data:${mimeType};base64,${content}`;
2134
+ }
2135
+ async function fetchAsDataURL(url, init2, process2) {
2136
+ const res = await fetch(url, init2);
2137
+ if (res.status === 404) {
2138
+ throw new Error(`Resource "${res.url}" not found`);
2139
+ }
2140
+ const blob = await res.blob();
2141
+ return new Promise((resolve, reject) => {
2142
+ const reader = new FileReader();
2143
+ reader.onerror = reject;
2144
+ reader.onloadend = () => {
2145
+ try {
2146
+ resolve(process2({ res, result: reader.result }));
2147
+ } catch (error) {
2148
+ reject(error);
2149
+ }
2150
+ };
2151
+ reader.readAsDataURL(blob);
2152
+ });
2153
+ }
2154
+ var cache = {};
2155
+ function getCacheKey(url, contentType, includeQueryParams) {
2156
+ let key = url.replace(/\?.*/, "");
2157
+ if (includeQueryParams) {
2158
+ key = url;
2159
+ }
2160
+ if (/ttf|otf|eot|woff2?/i.test(key)) {
2161
+ key = key.replace(/.*\//, "");
2162
+ }
2163
+ return contentType ? `[${contentType}]${key}` : key;
2164
+ }
2165
+ async function resourceToDataURL(resourceUrl, contentType, options) {
2166
+ const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
2167
+ if (cache[cacheKey] != null) {
2168
+ return cache[cacheKey];
2169
+ }
2170
+ if (options.cacheBust) {
2171
+ resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
2172
+ }
2173
+ let dataURL;
2174
+ try {
2175
+ const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => {
2176
+ if (!contentType) {
2177
+ contentType = res.headers.get("Content-Type") || "";
2178
+ }
2179
+ return getContentFromDataUrl(result);
2180
+ });
2181
+ dataURL = makeDataUrl(content, contentType);
2182
+ } catch (error) {
2183
+ dataURL = options.imagePlaceholder || "";
2184
+ let msg = `Failed to fetch resource: ${resourceUrl}`;
2185
+ if (error) {
2186
+ msg = typeof error === "string" ? error : error.message;
2187
+ }
2188
+ if (msg) {
2189
+ console.warn(msg);
2190
+ }
2191
+ }
2192
+ cache[cacheKey] = dataURL;
2193
+ return dataURL;
2194
+ }
2195
+
2196
+ // node_modules/html-to-image/es/clone-node.js
2197
+ async function cloneCanvasElement(canvas) {
2198
+ const dataURL = canvas.toDataURL();
2199
+ if (dataURL === "data:,") {
2200
+ return canvas.cloneNode(false);
2201
+ }
2202
+ return createImage(dataURL);
2203
+ }
2204
+ async function cloneVideoElement(video, options) {
2205
+ if (video.currentSrc) {
2206
+ const canvas = document.createElement("canvas");
2207
+ const ctx = canvas.getContext("2d");
2208
+ canvas.width = video.clientWidth;
2209
+ canvas.height = video.clientHeight;
2210
+ ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
2211
+ const dataURL2 = canvas.toDataURL();
2212
+ return createImage(dataURL2);
2213
+ }
2214
+ const poster = video.poster;
2215
+ const contentType = getMimeType(poster);
2216
+ const dataURL = await resourceToDataURL(poster, contentType, options);
2217
+ return createImage(dataURL);
2218
+ }
2219
+ async function cloneIFrameElement(iframe, options) {
2220
+ var _a;
2221
+ try {
2222
+ if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
2223
+ return await cloneNode(iframe.contentDocument.body, options, true);
2224
+ }
2225
+ } catch (_b) {
2226
+ }
2227
+ return iframe.cloneNode(false);
2228
+ }
2229
+ async function cloneSingleNode(node, options) {
2230
+ if (isInstanceOfElement(node, HTMLCanvasElement)) {
2231
+ return cloneCanvasElement(node);
2232
+ }
2233
+ if (isInstanceOfElement(node, HTMLVideoElement)) {
2234
+ return cloneVideoElement(node, options);
2235
+ }
2236
+ if (isInstanceOfElement(node, HTMLIFrameElement)) {
2237
+ return cloneIFrameElement(node, options);
2238
+ }
2239
+ return node.cloneNode(isSVGElement(node));
2240
+ }
2241
+ var isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
2242
+ var isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
2243
+ async function cloneChildren(nativeNode, clonedNode, options) {
2244
+ var _a, _b;
2245
+ if (isSVGElement(clonedNode)) {
2246
+ return clonedNode;
2247
+ }
2248
+ let children = [];
2249
+ if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
2250
+ children = toArray(nativeNode.assignedNodes());
2251
+ } else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
2252
+ children = toArray(nativeNode.contentDocument.body.childNodes);
2253
+ } else {
2254
+ children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
2255
+ }
2256
+ if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
2257
+ return clonedNode;
2258
+ }
2259
+ await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => {
2260
+ if (clonedChild) {
2261
+ clonedNode.appendChild(clonedChild);
2262
+ }
2263
+ }), Promise.resolve());
2264
+ return clonedNode;
2265
+ }
2266
+ function cloneCSSStyle(nativeNode, clonedNode, options) {
2267
+ const targetStyle = clonedNode.style;
2268
+ if (!targetStyle) {
2269
+ return;
2270
+ }
2271
+ const sourceStyle = window.getComputedStyle(nativeNode);
2272
+ if (sourceStyle.cssText) {
2273
+ targetStyle.cssText = sourceStyle.cssText;
2274
+ targetStyle.transformOrigin = sourceStyle.transformOrigin;
2275
+ } else {
2276
+ getStyleProperties(options).forEach((name) => {
2277
+ let value = sourceStyle.getPropertyValue(name);
2278
+ if (name === "font-size" && value.endsWith("px")) {
2279
+ const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
2280
+ value = `${reducedFont}px`;
2281
+ }
2282
+ if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") {
2283
+ value = "block";
2284
+ }
2285
+ if (name === "d" && clonedNode.getAttribute("d")) {
2286
+ value = `path(${clonedNode.getAttribute("d")})`;
2287
+ }
2288
+ targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
2289
+ });
2290
+ }
2291
+ }
2292
+ function cloneInputValue(nativeNode, clonedNode) {
2293
+ if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
2294
+ clonedNode.innerHTML = nativeNode.value;
2295
+ }
2296
+ if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
2297
+ clonedNode.setAttribute("value", nativeNode.value);
2298
+ }
2299
+ }
2300
+ function cloneSelectValue(nativeNode, clonedNode) {
2301
+ if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
2302
+ const clonedSelect = clonedNode;
2303
+ const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value"));
2304
+ if (selectedOption) {
2305
+ selectedOption.setAttribute("selected", "");
2306
+ }
2307
+ }
2308
+ }
2309
+ function decorate(nativeNode, clonedNode, options) {
2310
+ if (isInstanceOfElement(clonedNode, Element)) {
2311
+ cloneCSSStyle(nativeNode, clonedNode, options);
2312
+ clonePseudoElements(nativeNode, clonedNode, options);
2313
+ cloneInputValue(nativeNode, clonedNode);
2314
+ cloneSelectValue(nativeNode, clonedNode);
2315
+ }
2316
+ return clonedNode;
2317
+ }
2318
+ async function ensureSVGSymbols(clone, options) {
2319
+ const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : [];
2320
+ if (uses.length === 0) {
2321
+ return clone;
2322
+ }
2323
+ const processedDefs = {};
2324
+ for (let i = 0; i < uses.length; i++) {
2325
+ const use = uses[i];
2326
+ const id = use.getAttribute("xlink:href");
2327
+ if (id) {
2328
+ const exist = clone.querySelector(id);
2329
+ const definition = document.querySelector(id);
2330
+ if (!exist && definition && !processedDefs[id]) {
2331
+ processedDefs[id] = await cloneNode(definition, options, true);
2332
+ }
2333
+ }
2334
+ }
2335
+ const nodes = Object.values(processedDefs);
2336
+ if (nodes.length) {
2337
+ const ns = "http://www.w3.org/1999/xhtml";
2338
+ const svg = document.createElementNS(ns, "svg");
2339
+ svg.setAttribute("xmlns", ns);
2340
+ svg.style.position = "absolute";
2341
+ svg.style.width = "0";
2342
+ svg.style.height = "0";
2343
+ svg.style.overflow = "hidden";
2344
+ svg.style.display = "none";
2345
+ const defs = document.createElementNS(ns, "defs");
2346
+ svg.appendChild(defs);
2347
+ for (let i = 0; i < nodes.length; i++) {
2348
+ defs.appendChild(nodes[i]);
2349
+ }
2350
+ clone.appendChild(svg);
2351
+ }
2352
+ return clone;
2353
+ }
2354
+ async function cloneNode(node, options, isRoot) {
2355
+ if (!isRoot && options.filter && !options.filter(node)) {
2356
+ return null;
2357
+ }
2358
+ return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode, options)).then((clonedNode) => ensureSVGSymbols(clonedNode, options));
2359
+ }
2360
+
2361
+ // node_modules/html-to-image/es/embed-resources.js
2362
+ var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
2363
+ var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
2364
+ var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
2365
+ function toRegex(url) {
2366
+ const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
2367
+ return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
2368
+ }
2369
+ function parseURLs(cssText) {
2370
+ const urls = [];
2371
+ cssText.replace(URL_REGEX, (raw, quotation, url) => {
2372
+ urls.push(url);
2373
+ return raw;
2374
+ });
2375
+ return urls.filter((url) => !isDataUrl(url));
2376
+ }
2377
+ async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
2378
+ try {
2379
+ const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
2380
+ const contentType = getMimeType(resourceURL);
2381
+ let dataURL;
2382
+ if (getContentFromUrl) {
2383
+ const content = await getContentFromUrl(resolvedURL);
2384
+ dataURL = makeDataUrl(content, contentType);
2385
+ } else {
2386
+ dataURL = await resourceToDataURL(resolvedURL, contentType, options);
2387
+ }
2388
+ return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
2389
+ } catch (error) {
2390
+ }
2391
+ return cssText;
2392
+ }
2393
+ function filterPreferredFontFormat(str, { preferredFontFormat }) {
2394
+ return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => {
2395
+ while (true) {
2396
+ const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
2397
+ if (!format) {
2398
+ return "";
2399
+ }
2400
+ if (format === preferredFontFormat) {
2401
+ return `src: ${src};`;
2402
+ }
2403
+ }
2404
+ });
2405
+ }
2406
+ function shouldEmbed(url) {
2407
+ return url.search(URL_REGEX) !== -1;
2408
+ }
2409
+ async function embedResources(cssText, baseUrl, options) {
2410
+ if (!shouldEmbed(cssText)) {
2411
+ return cssText;
2412
+ }
2413
+ const filteredCSSText = filterPreferredFontFormat(cssText, options);
2414
+ const urls = parseURLs(filteredCSSText);
2415
+ return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
2416
+ }
2417
+
2418
+ // node_modules/html-to-image/es/embed-images.js
2419
+ async function embedProp(propName, node, options) {
2420
+ var _a;
2421
+ const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
2422
+ if (propValue) {
2423
+ const cssString = await embedResources(propValue, null, options);
2424
+ node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
2425
+ return true;
2426
+ }
2427
+ return false;
2428
+ }
2429
+ async function embedBackground(clonedNode, options) {
2430
+ ;
2431
+ await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
2432
+ await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
2433
+ }
2434
+ async function embedImageNode(clonedNode, options) {
2435
+ const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
2436
+ if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
2437
+ return;
2438
+ }
2439
+ const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
2440
+ const dataURL = await resourceToDataURL(url, getMimeType(url), options);
2441
+ await new Promise((resolve, reject) => {
2442
+ clonedNode.onload = resolve;
2443
+ clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
2444
+ try {
2445
+ resolve(options.onImageErrorHandler(...attributes));
2446
+ } catch (error) {
2447
+ reject(error);
2448
+ }
2449
+ } : reject;
2450
+ const image = clonedNode;
2451
+ if (image.decode) {
2452
+ image.decode = resolve;
2453
+ }
2454
+ if (image.loading === "lazy") {
2455
+ image.loading = "eager";
2456
+ }
2457
+ if (isImageElement) {
2458
+ clonedNode.srcset = "";
2459
+ clonedNode.src = dataURL;
2460
+ } else {
2461
+ clonedNode.href.baseVal = dataURL;
2462
+ }
2463
+ });
2464
+ }
2465
+ async function embedChildren(clonedNode, options) {
2466
+ const children = toArray(clonedNode.childNodes);
2467
+ const deferreds = children.map((child) => embedImages(child, options));
2468
+ await Promise.all(deferreds).then(() => clonedNode);
2469
+ }
2470
+ async function embedImages(clonedNode, options) {
2471
+ if (isInstanceOfElement(clonedNode, Element)) {
2472
+ await embedBackground(clonedNode, options);
2473
+ await embedImageNode(clonedNode, options);
2474
+ await embedChildren(clonedNode, options);
2475
+ }
2476
+ }
2477
+
2478
+ // node_modules/html-to-image/es/apply-style.js
2479
+ function applyStyle(node, options) {
2480
+ const { style } = node;
2481
+ if (options.backgroundColor) {
2482
+ style.backgroundColor = options.backgroundColor;
2483
+ }
2484
+ if (options.width) {
2485
+ style.width = `${options.width}px`;
2486
+ }
2487
+ if (options.height) {
2488
+ style.height = `${options.height}px`;
2489
+ }
2490
+ const manual = options.style;
2491
+ if (manual != null) {
2492
+ Object.keys(manual).forEach((key) => {
2493
+ style[key] = manual[key];
2494
+ });
2495
+ }
2496
+ return node;
2497
+ }
2498
+
2499
+ // node_modules/html-to-image/es/embed-webfonts.js
2500
+ var cssFetchCache = {};
2501
+ async function fetchCSS(url) {
2502
+ let cache2 = cssFetchCache[url];
2503
+ if (cache2 != null) {
2504
+ return cache2;
2505
+ }
2506
+ const res = await fetch(url);
2507
+ const cssText = await res.text();
2508
+ cache2 = { url, cssText };
2509
+ cssFetchCache[url] = cache2;
2510
+ return cache2;
2511
+ }
2512
+ async function embedFonts(data, options) {
2513
+ let cssText = data.cssText;
2514
+ const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
2515
+ const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
2516
+ const loadFonts = fontLocs.map(async (loc) => {
2517
+ let url = loc.replace(regexUrl, "$1");
2518
+ if (!url.startsWith("https://")) {
2519
+ url = new URL(url, data.url).href;
2520
+ }
2521
+ return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
2522
+ cssText = cssText.replace(loc, `url(${result})`);
2523
+ return [loc, result];
2524
+ });
2525
+ });
2526
+ return Promise.all(loadFonts).then(() => cssText);
2527
+ }
2528
+ function parseCSS(source) {
2529
+ if (source == null) {
2530
+ return [];
2531
+ }
2532
+ const result = [];
2533
+ const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
2534
+ let cssText = source.replace(commentsRegex, "");
2535
+ const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi");
2536
+ while (true) {
2537
+ const matches = keyframesRegex.exec(cssText);
2538
+ if (matches === null) {
2539
+ break;
2540
+ }
2541
+ result.push(matches[0]);
2542
+ }
2543
+ cssText = cssText.replace(keyframesRegex, "");
2544
+ const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
2545
+ const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})";
2546
+ const unifiedRegex = new RegExp(combinedCSSRegex, "gi");
2547
+ while (true) {
2548
+ let matches = importRegex.exec(cssText);
2549
+ if (matches === null) {
2550
+ matches = unifiedRegex.exec(cssText);
2551
+ if (matches === null) {
2552
+ break;
2553
+ } else {
2554
+ importRegex.lastIndex = unifiedRegex.lastIndex;
2555
+ }
2556
+ } else {
2557
+ unifiedRegex.lastIndex = importRegex.lastIndex;
2558
+ }
2559
+ result.push(matches[0]);
2560
+ }
2561
+ return result;
2562
+ }
2563
+ async function getCSSRules(styleSheets, options) {
2564
+ const ret = [];
2565
+ const deferreds = [];
2566
+ styleSheets.forEach((sheet) => {
2567
+ if ("cssRules" in sheet) {
2568
+ try {
2569
+ toArray(sheet.cssRules || []).forEach((item, index) => {
2570
+ if (item.type === CSSRule.IMPORT_RULE) {
2571
+ let importIndex = index + 1;
2572
+ const url = item.href;
2573
+ const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
2574
+ try {
2575
+ sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length);
2576
+ } catch (error) {
2577
+ console.error("Error inserting rule from remote css", {
2578
+ rule,
2579
+ error
2580
+ });
2581
+ }
2582
+ })).catch((e) => {
2583
+ console.error("Error loading remote css", e.toString());
2584
+ });
2585
+ deferreds.push(deferred);
2586
+ }
2587
+ });
2588
+ } catch (e) {
2589
+ const inline2 = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
2590
+ if (sheet.href != null) {
2591
+ deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
2592
+ inline2.insertRule(rule, inline2.cssRules.length);
2593
+ })).catch((err) => {
2594
+ console.error("Error loading remote stylesheet", err);
2595
+ }));
2596
+ }
2597
+ console.error("Error inlining remote css file", e);
2598
+ }
2599
+ }
2600
+ });
2601
+ return Promise.all(deferreds).then(() => {
2602
+ styleSheets.forEach((sheet) => {
2603
+ if ("cssRules" in sheet) {
2604
+ try {
2605
+ toArray(sheet.cssRules || []).forEach((item) => {
2606
+ ret.push(item);
2607
+ });
2608
+ } catch (e) {
2609
+ console.error(`Error while reading CSS rules from ${sheet.href}`, e);
2610
+ }
2611
+ }
2612
+ });
2613
+ return ret;
2614
+ });
2615
+ }
2616
+ function getWebFontRules(cssRules) {
2617
+ return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src")));
2618
+ }
2619
+ async function parseWebFontRules(node, options) {
2620
+ if (node.ownerDocument == null) {
2621
+ throw new Error("Provided element is not within a Document");
2622
+ }
2623
+ const styleSheets = toArray(node.ownerDocument.styleSheets);
2624
+ const cssRules = await getCSSRules(styleSheets, options);
2625
+ return getWebFontRules(cssRules);
2626
+ }
2627
+ function normalizeFontFamily(font) {
2628
+ return font.trim().replace(/["']/g, "");
2629
+ }
2630
+ function getUsedFonts(node) {
2631
+ const fonts = /* @__PURE__ */ new Set();
2632
+ function traverse(node2) {
2633
+ const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
2634
+ fontFamily.split(",").forEach((font) => {
2635
+ fonts.add(normalizeFontFamily(font));
2636
+ });
2637
+ Array.from(node2.children).forEach((child) => {
2638
+ if (child instanceof HTMLElement) {
2639
+ traverse(child);
2640
+ }
2641
+ });
2642
+ }
2643
+ traverse(node);
2644
+ return fonts;
2645
+ }
2646
+ async function getWebFontCSS(node, options) {
2647
+ const rules = await parseWebFontRules(node, options);
2648
+ const usedFonts = getUsedFonts(node);
2649
+ const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
2650
+ const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
2651
+ return embedResources(rule.cssText, baseUrl, options);
2652
+ }));
2653
+ return cssTexts.join("\n");
2654
+ }
2655
+ async function embedWebFonts(clonedNode, options) {
2656
+ const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
2657
+ if (cssText) {
2658
+ const styleNode = document.createElement("style");
2659
+ const sytleContent = document.createTextNode(cssText);
2660
+ styleNode.appendChild(sytleContent);
2661
+ if (clonedNode.firstChild) {
2662
+ clonedNode.insertBefore(styleNode, clonedNode.firstChild);
2663
+ } else {
2664
+ clonedNode.appendChild(styleNode);
2665
+ }
2666
+ }
2667
+ }
2668
+
2669
+ // node_modules/html-to-image/es/index.js
2670
+ async function toSvg(node, options = {}) {
2671
+ const { width, height } = getImageSize(node, options);
2672
+ const clonedNode = await cloneNode(node, options, true);
2673
+ await embedWebFonts(clonedNode, options);
2674
+ await embedImages(clonedNode, options);
2675
+ applyStyle(clonedNode, options);
2676
+ const datauri = await nodeToDataURL(clonedNode, width, height);
2677
+ return datauri;
2678
+ }
2679
+ async function toCanvas(node, options = {}) {
2680
+ const { width, height } = getImageSize(node, options);
2681
+ const svg = await toSvg(node, options);
2682
+ const img = await createImage(svg);
2683
+ const canvas = document.createElement("canvas");
2684
+ const context = canvas.getContext("2d");
2685
+ const ratio = options.pixelRatio || getPixelRatio();
2686
+ const canvasWidth = options.canvasWidth || width;
2687
+ const canvasHeight = options.canvasHeight || height;
2688
+ canvas.width = canvasWidth * ratio;
2689
+ canvas.height = canvasHeight * ratio;
2690
+ if (!options.skipAutoScale) {
2691
+ checkCanvasDimensions(canvas);
2692
+ }
2693
+ canvas.style.width = `${canvasWidth}`;
2694
+ canvas.style.height = `${canvasHeight}`;
2695
+ if (options.backgroundColor) {
2696
+ context.fillStyle = options.backgroundColor;
2697
+ context.fillRect(0, 0, canvas.width, canvas.height);
2698
+ }
2699
+ context.drawImage(img, 0, 0, canvas.width, canvas.height);
2700
+ return canvas;
2701
+ }
2702
+ async function toPng(node, options = {}) {
2703
+ const canvas = await toCanvas(node, options);
2704
+ return canvas.toDataURL();
2705
+ }
2706
+
2707
+ // overlay/src/screenshot.ts
2708
+ function areSiblings(nodes) {
2709
+ if (nodes.length === 0) return false;
2710
+ const parent = nodes[0].parentElement;
2711
+ return nodes.every((n) => n.parentElement === parent);
2712
+ }
2713
+ async function captureRegion(nodes) {
2714
+ if (nodes.length === 1) {
2715
+ const rect = nodes[0].getBoundingClientRect();
2716
+ const width2 = Math.round(rect.width);
2717
+ const height2 = Math.round(rect.height);
2718
+ const dataUrl = await toPng(nodes[0], { skipFonts: true, width: width2, height: height2 });
2719
+ return { dataUrl, width: width2, height: height2 };
2720
+ }
2721
+ const parent = nodes[0].parentElement;
2722
+ const rects = nodes.map((n) => n.getBoundingClientRect());
2723
+ const top = Math.min(...rects.map((r) => r.top));
2724
+ const left = Math.min(...rects.map((r) => r.left));
2725
+ const right = Math.max(...rects.map((r) => r.right));
2726
+ const bottom = Math.max(...rects.map((r) => r.bottom));
2727
+ const width = Math.round(right - left);
2728
+ const height = Math.round(bottom - top);
2729
+ const ghost = parent.cloneNode(false);
2730
+ ghost.style.padding = "0";
2731
+ ghost.style.border = "none";
2732
+ ghost.style.margin = "0";
2733
+ ghost.style.width = `${width}px`;
2734
+ for (const node of nodes) {
2735
+ ghost.appendChild(node.cloneNode(true));
2736
+ }
2737
+ ghost.style.position = "fixed";
2738
+ ghost.style.left = "0";
2739
+ ghost.style.top = "0";
2740
+ ghost.style.zIndex = "999999";
2741
+ ghost.style.pointerEvents = "none";
2742
+ document.body.appendChild(ghost);
2743
+ try {
2744
+ const dataUrl = await toPng(ghost, { skipFonts: true, width, height });
2745
+ return { dataUrl, width, height };
2746
+ } finally {
2747
+ ghost.remove();
2748
+ }
2749
+ }
2750
+
1845
2751
  // overlay/src/index.ts
1846
2752
  var shadowRoot;
1847
2753
  var shadowHost;
@@ -1852,7 +2758,7 @@ ${pad}</${tag}>`;
1852
2758
  var currentTargetEl = null;
1853
2759
  var currentBoundary = null;
1854
2760
  var currentInstances = [];
1855
- var addingMode = false;
2761
+ var cachedNearGroups = null;
1856
2762
  var hoverOutlineEl = null;
1857
2763
  var hoverTooltipEl = null;
1858
2764
  var lastHoveredEl = null;
@@ -1985,7 +2891,7 @@ ${pad}</${tag}>`;
1985
2891
  align-self: stretch;
1986
2892
  }
1987
2893
  /* Base style for all buttons inside the toolbar */
1988
- .draw-btn, .el-pick-btn, .el-add-btn {
2894
+ .draw-btn, .el-reselect-btn, .el-pick-btn, .el-add-btn {
1989
2895
  background: transparent;
1990
2896
  border: none;
1991
2897
  border-radius: 0;
@@ -2009,10 +2915,59 @@ ${pad}</${tag}>`;
2009
2915
  .el-pick-btn { gap: 3px; padding: 0 8px; font-size: 12px; font-weight: 600; letter-spacing: 0.01em; }
2010
2916
  .el-pick-btn svg { opacity: 0.7; flex-shrink: 0; }
2011
2917
  .el-add-btn { padding: 0 10px; font-size: 15px; font-weight: 400; }
2012
- .draw-btn:hover, .el-pick-btn:hover, .el-add-btn:hover,
2918
+ .el-reselect-btn { padding: 0 9px; }
2919
+ .draw-btn:hover, .el-reselect-btn:hover, .el-pick-btn:hover, .el-add-btn:hover,
2013
2920
  .el-pick-btn.open {
2014
2921
  background: rgba(255,255,255,0.12);
2015
2922
  }
2923
+ /* \u2500\u2500 Hover preview highlight (dashed, for group hover) \u2500\u2500 */
2924
+ .highlight-preview {
2925
+ position: fixed;
2926
+ pointer-events: none;
2927
+ border: 2px dashed #00848B;
2928
+ border-radius: 2px;
2929
+ box-sizing: border-box;
2930
+ z-index: 999998;
2931
+ }
2932
+ /* \u2500\u2500 Group picker popover (replaces instance picker) \u2500\u2500 */
2933
+ .el-group-empty {
2934
+ padding: 12px 14px;
2935
+ font-size: 11px;
2936
+ color: #687879;
2937
+ text-align: center;
2938
+ }
2939
+ .el-group-row {
2940
+ display: flex;
2941
+ align-items: center;
2942
+ gap: 8px;
2943
+ padding: 5px 12px;
2944
+ cursor: pointer;
2945
+ transition: background 0.1s;
2946
+ }
2947
+ .el-group-row:hover { background: rgba(0,132,139,0.05); }
2948
+ .el-group-row input[type=checkbox] {
2949
+ accent-color: #00848B;
2950
+ width: 13px;
2951
+ height: 13px;
2952
+ flex-shrink: 0;
2953
+ cursor: pointer;
2954
+ }
2955
+ .el-group-count {
2956
+ font-size: 11px;
2957
+ font-weight: 600;
2958
+ color: #334041;
2959
+ min-width: 20px;
2960
+ }
2961
+ .el-group-diff {
2962
+ flex: 1;
2963
+ font-size: 10px;
2964
+ font-family: 'SF Mono', 'JetBrains Mono', 'Fira Code', monospace;
2965
+ overflow: hidden;
2966
+ text-overflow: ellipsis;
2967
+ white-space: nowrap;
2968
+ }
2969
+ .el-group-diff .diff-add { color: #16a34a; }
2970
+ .el-group-diff .diff-rem { color: #dc2626; }
2016
2971
  /* \u2500\u2500 Instance picker popover \u2500\u2500 */
2017
2972
  .el-picker {
2018
2973
  position: fixed;
@@ -2211,7 +3166,6 @@ ${pad}</${tag}>`;
2211
3166
  var drawPopoverEl = null;
2212
3167
  var pickerEl = null;
2213
3168
  var pickerCloseHandler = null;
2214
- var selectedInstanceIndices = /* @__PURE__ */ new Set();
2215
3169
  function removeDrawButton() {
2216
3170
  toolbarEl?.remove();
2217
3171
  toolbarEl = null;
@@ -2281,7 +3235,7 @@ ${pad}</${tag}>`;
2281
3235
  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
2282
3236
  <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
2283
3237
  </svg>`;
2284
- var CHEVRON_SVG = `<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>`;
3238
+ 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>`;
2285
3239
  async function positionWithFlip(anchor, floating, placement = "top-start") {
2286
3240
  const { x, y } = await computePosition2(anchor, floating, {
2287
3241
  placement,
@@ -2292,14 +3246,23 @@ ${pad}</${tag}>`;
2292
3246
  }
2293
3247
  function showDrawButton(targetEl) {
2294
3248
  removeDrawButton();
2295
- const allEquivalentNodes = [...currentEquivalentNodes];
2296
- const instanceCount = allEquivalentNodes.length;
3249
+ const instanceCount = currentEquivalentNodes.length;
2297
3250
  const toolbar = document.createElement("div");
2298
3251
  toolbar.className = "el-toolbar";
2299
3252
  toolbar.style.left = "0px";
2300
3253
  toolbar.style.top = "0px";
2301
3254
  shadowRoot.appendChild(toolbar);
2302
3255
  toolbarEl = toolbar;
3256
+ const reselectBtn = document.createElement("button");
3257
+ reselectBtn.className = "el-reselect-btn";
3258
+ reselectBtn.innerHTML = RESELECT_SVG;
3259
+ reselectBtn.title = "Re-select element";
3260
+ toolbar.appendChild(reselectBtn);
3261
+ reselectBtn.addEventListener("click", (e) => {
3262
+ e.stopPropagation();
3263
+ clearHighlights();
3264
+ setSelectMode(true);
3265
+ });
2303
3266
  const drawBtn = document.createElement("button");
2304
3267
  drawBtn.className = "draw-btn";
2305
3268
  drawBtn.innerHTML = PENCIL_SVG;
@@ -2316,69 +3279,47 @@ ${pad}</${tag}>`;
2316
3279
  showDrawPopover(drawBtn);
2317
3280
  }
2318
3281
  });
2319
- if (instanceCount > 1) {
2320
- const sep = document.createElement("div");
2321
- sep.className = "el-toolbar-sep";
2322
- toolbar.appendChild(sep);
2323
- const countBtn = document.createElement("button");
2324
- countBtn.className = "el-pick-btn";
2325
- const updateCountBtn = (n) => {
2326
- countBtn.innerHTML = `${n} ${CHEVRON_SVG}`;
2327
- };
2328
- updateCountBtn(instanceCount);
2329
- countBtn.title = "Select which instances to edit";
2330
- toolbar.appendChild(countBtn);
2331
- countBtn.addEventListener("click", (e) => {
2332
- e.stopPropagation();
2333
- drawPopoverEl?.remove();
2334
- drawPopoverEl = null;
2335
- if (pickerEl) {
2336
- pickerEl.remove();
2337
- pickerEl = null;
2338
- countBtn.classList.remove("open");
2339
- } else {
2340
- countBtn.classList.add("open");
2341
- showInstancePicker(
2342
- countBtn,
2343
- () => countBtn.classList.remove("open"),
2344
- (indices) => {
2345
- currentEquivalentNodes = indices.map((i) => allEquivalentNodes[i]).filter(Boolean);
2346
- shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
2347
- currentEquivalentNodes.forEach((n) => highlightElement(n));
2348
- updateCountBtn(currentEquivalentNodes.length);
2349
- }
2350
- );
2351
- }
2352
- });
2353
- const addBtn = document.createElement("button");
2354
- addBtn.className = "el-add-btn";
2355
- addBtn.textContent = "+";
2356
- addBtn.title = "Add a different element to selection";
2357
- toolbar.appendChild(addBtn);
2358
- addBtn.addEventListener("click", (e) => {
2359
- e.stopPropagation();
2360
- pickerEl?.remove();
3282
+ const sep = document.createElement("div");
3283
+ sep.className = "el-toolbar-sep";
3284
+ toolbar.appendChild(sep);
3285
+ const addGroupBtn = document.createElement("button");
3286
+ addGroupBtn.className = "el-add-btn";
3287
+ addGroupBtn.textContent = `${instanceCount} +`;
3288
+ addGroupBtn.title = `${instanceCount} matching element${instanceCount !== 1 ? "s" : ""} selected \u2014 click to add similar`;
3289
+ toolbar.appendChild(addGroupBtn);
3290
+ addGroupBtn.addEventListener("click", (e) => {
3291
+ e.stopPropagation();
3292
+ drawPopoverEl?.remove();
3293
+ drawPopoverEl = null;
3294
+ if (pickerEl) {
3295
+ pickerEl.remove();
2361
3296
  pickerEl = null;
2362
- drawPopoverEl?.remove();
2363
- drawPopoverEl = null;
2364
- addingMode = true;
2365
- setSelectMode(true);
2366
- showToast("Click another element to add it to the selection", 2500);
2367
- });
2368
- }
3297
+ addGroupBtn.classList.remove("open");
3298
+ } else {
3299
+ addGroupBtn.classList.add("open");
3300
+ showGroupPicker(
3301
+ addGroupBtn,
3302
+ () => addGroupBtn.classList.remove("open"),
3303
+ (totalCount) => {
3304
+ addGroupBtn.textContent = `${totalCount} +`;
3305
+ addGroupBtn.title = `${totalCount} matching element${totalCount !== 1 ? "s" : ""} selected \u2014 click to add similar`;
3306
+ }
3307
+ );
3308
+ }
3309
+ });
2369
3310
  positionWithFlip(targetEl, toolbar);
2370
3311
  }
2371
- function showInstancePicker(anchorBtn, onClose, onApply) {
3312
+ function showGroupPicker(anchorBtn, onClose, onCountChange) {
2372
3313
  if (pickerCloseHandler) {
2373
3314
  document.removeEventListener("click", pickerCloseHandler, { capture: true });
2374
3315
  pickerCloseHandler = null;
2375
3316
  }
2376
3317
  pickerEl?.remove();
2377
- const instances = currentInstances;
2378
- const allIndices = instances.map((_, i) => i);
2379
- const selected = new Set(
2380
- selectedInstanceIndices.size > 0 && selectedInstanceIndices.size <= instances.length ? [...selectedInstanceIndices].filter((i) => i < instances.length) : allIndices
2381
- );
3318
+ if (!cachedNearGroups && currentTargetEl) {
3319
+ const exactSet = new Set(currentEquivalentNodes);
3320
+ cachedNearGroups = computeNearGroups(currentTargetEl, exactSet, shadowHost);
3321
+ }
3322
+ const groups = cachedNearGroups ?? [];
2382
3323
  const picker = document.createElement("div");
2383
3324
  picker.className = "el-picker";
2384
3325
  picker.style.left = "0px";
@@ -2389,72 +3330,89 @@ ${pad}</${tag}>`;
2389
3330
  header.className = "el-picker-header";
2390
3331
  const title = document.createElement("span");
2391
3332
  title.className = "el-picker-title";
2392
- title.textContent = currentBoundary?.componentName ?? "Instances";
2393
- const actions = document.createElement("div");
2394
- actions.className = "el-picker-actions";
2395
- const allLink = document.createElement("a");
2396
- allLink.textContent = "All";
2397
- const noneLink = document.createElement("a");
2398
- noneLink.textContent = "None";
2399
- actions.appendChild(allLink);
2400
- actions.appendChild(noneLink);
3333
+ title.textContent = "Similar elements";
2401
3334
  header.appendChild(title);
2402
- header.appendChild(actions);
2403
3335
  picker.appendChild(header);
2404
- const list = document.createElement("div");
2405
- list.className = "el-picker-list";
2406
- picker.appendChild(list);
2407
- const checkboxes = [];
2408
- function renderRows() {
2409
- list.innerHTML = "";
2410
- checkboxes.length = 0;
2411
- instances.forEach((inst, i) => {
3336
+ if (groups.length === 0) {
3337
+ const empty = document.createElement("div");
3338
+ empty.className = "el-group-empty";
3339
+ empty.textContent = "No similar elements found";
3340
+ picker.appendChild(empty);
3341
+ } else {
3342
+ let clearPreviewHighlights2 = function() {
3343
+ shadowRoot.querySelectorAll(".highlight-preview").forEach((el) => el.remove());
3344
+ }, updateSelection2 = function() {
3345
+ const allNodes = [...baseNodes];
3346
+ for (const idx of checkedGroups) {
3347
+ for (const el of groups[idx].elements) {
3348
+ if (!allNodes.includes(el)) allNodes.push(el);
3349
+ }
3350
+ }
3351
+ currentEquivalentNodes = allNodes;
3352
+ shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
3353
+ currentEquivalentNodes.forEach((n) => highlightElement(n));
3354
+ onCountChange(currentEquivalentNodes.length);
3355
+ if (currentTargetEl && currentBoundary) {
3356
+ sendTo("panel", {
3357
+ type: "ELEMENT_SELECTED",
3358
+ componentName: currentBoundary.componentName,
3359
+ instanceCount: currentEquivalentNodes.length,
3360
+ classes: typeof currentTargetEl.className === "string" ? currentTargetEl.className : "",
3361
+ tailwindConfig: tailwindConfigCache
3362
+ });
3363
+ }
3364
+ };
3365
+ var clearPreviewHighlights = clearPreviewHighlights2, updateSelection = updateSelection2;
3366
+ const list = document.createElement("div");
3367
+ list.className = "el-picker-list";
3368
+ picker.appendChild(list);
3369
+ const checkedGroups = /* @__PURE__ */ new Set();
3370
+ const baseNodes = [...currentEquivalentNodes];
3371
+ groups.forEach((group, idx) => {
2412
3372
  const row = document.createElement("label");
2413
- row.className = "el-picker-row";
3373
+ row.className = "el-group-row";
2414
3374
  const cb = document.createElement("input");
2415
3375
  cb.type = "checkbox";
2416
- cb.checked = selected.has(i);
3376
+ cb.checked = false;
2417
3377
  cb.addEventListener("change", () => {
2418
- if (cb.checked) selected.add(i);
2419
- else selected.delete(i);
2420
- badge.className = "el-picker-badge" + (cb.checked ? " checked" : "");
2421
- updateApply();
3378
+ if (cb.checked) checkedGroups.add(idx);
3379
+ else checkedGroups.delete(idx);
3380
+ updateSelection2();
2422
3381
  });
2423
- checkboxes.push(cb);
2424
- const badge = document.createElement("span");
2425
- badge.className = "el-picker-badge" + (cb.checked ? " checked" : "");
2426
- badge.textContent = String(i + 1);
2427
- const label = document.createElement("span");
2428
- label.className = "el-picker-label";
2429
- label.innerHTML = `${inst.label} <span class="el-picker-tag">${inst.parent}</span>`;
3382
+ const count = document.createElement("span");
3383
+ count.className = "el-group-count";
3384
+ count.textContent = `(${group.elements.length})`;
3385
+ const diff = document.createElement("span");
3386
+ diff.className = "el-group-diff";
3387
+ const parts = [];
3388
+ for (const a of group.added) parts.push(`<span class="diff-add">+${a}</span>`);
3389
+ for (const r of group.removed) parts.push(`<span class="diff-rem">-${r}</span>`);
3390
+ diff.innerHTML = parts.join(" ");
2430
3391
  row.appendChild(cb);
2431
- row.appendChild(badge);
2432
- row.appendChild(label);
3392
+ row.appendChild(count);
3393
+ row.appendChild(diff);
2433
3394
  list.appendChild(row);
3395
+ row.addEventListener("mouseenter", () => {
3396
+ clearPreviewHighlights2();
3397
+ for (const el of group.elements) {
3398
+ const rect = el.getBoundingClientRect();
3399
+ const preview = document.createElement("div");
3400
+ preview.className = "highlight-preview";
3401
+ preview.style.top = `${rect.top - 3}px`;
3402
+ preview.style.left = `${rect.left - 3}px`;
3403
+ preview.style.width = `${rect.width + 6}px`;
3404
+ preview.style.height = `${rect.height + 6}px`;
3405
+ shadowRoot.appendChild(preview);
3406
+ }
3407
+ });
3408
+ row.addEventListener("mouseleave", () => {
3409
+ clearPreviewHighlights2();
3410
+ });
2434
3411
  });
2435
3412
  }
2436
- renderRows();
2437
- allLink.addEventListener("click", () => {
2438
- instances.forEach((_, i) => selected.add(i));
2439
- renderRows();
2440
- updateApply();
2441
- });
2442
- noneLink.addEventListener("click", () => {
2443
- selected.clear();
2444
- renderRows();
2445
- updateApply();
2446
- });
2447
- const footer = document.createElement("div");
2448
- footer.className = "el-picker-footer";
2449
- const applyBtn = document.createElement("button");
2450
- applyBtn.className = "el-picker-apply";
2451
- footer.appendChild(applyBtn);
2452
- picker.appendChild(footer);
2453
- function updateApply() {
2454
- applyBtn.textContent = `Apply (${selected.size} selected)`;
2455
- }
2456
- updateApply();
3413
+ positionWithFlip(anchorBtn, picker);
2457
3414
  const removePicker = () => {
3415
+ shadowRoot.querySelectorAll(".highlight-preview").forEach((el) => el.remove());
2458
3416
  if (pickerCloseHandler) {
2459
3417
  document.removeEventListener("click", pickerCloseHandler, { capture: true });
2460
3418
  pickerCloseHandler = null;
@@ -2462,16 +3420,6 @@ ${pad}</${tag}>`;
2462
3420
  pickerEl?.remove();
2463
3421
  pickerEl = null;
2464
3422
  };
2465
- applyBtn.addEventListener("click", (e) => {
2466
- e.stopPropagation();
2467
- const selectedIndices = [...selected];
2468
- selectedInstanceIndices = new Set(selectedIndices);
2469
- sendTo("panel", { type: "SELECT_MATCHING", indices: selectedIndices });
2470
- onApply?.(selectedIndices);
2471
- removePicker();
2472
- onClose();
2473
- });
2474
- positionWithFlip(anchorBtn, picker);
2475
3423
  setTimeout(() => {
2476
3424
  pickerCloseHandler = (e) => {
2477
3425
  const path = e.composedPath();
@@ -2515,6 +3463,26 @@ ${pad}</${tag}>`;
2515
3463
  });
2516
3464
  popover.appendChild(row);
2517
3465
  }
3466
+ const sep = document.createElement("div");
3467
+ sep.style.cssText = "height:1px;background:#DFE2E2;margin:4px 0;";
3468
+ popover.appendChild(sep);
3469
+ const screenshotHeader = document.createElement("div");
3470
+ screenshotHeader.className = "draw-popover-header";
3471
+ screenshotHeader.textContent = "Screenshot & Annotate";
3472
+ popover.appendChild(screenshotHeader);
3473
+ const screenshotRow = document.createElement("button");
3474
+ screenshotRow.className = "draw-popover-item";
3475
+ screenshotRow.innerHTML = `
3476
+ <span class="draw-popover-icon">\u{1F4F7}</span>
3477
+ <span class="draw-popover-label">Screenshot & Annotate</span>
3478
+ `;
3479
+ screenshotRow.addEventListener("click", (e) => {
3480
+ e.stopPropagation();
3481
+ drawPopoverEl?.remove();
3482
+ drawPopoverEl = null;
3483
+ handleCaptureScreenshot();
3484
+ });
3485
+ popover.appendChild(screenshotRow);
2518
3486
  drawPopoverEl = popover;
2519
3487
  shadowRoot.appendChild(popover);
2520
3488
  positionWithFlip(anchorBtn, popover, "top-start");
@@ -2563,86 +3531,25 @@ ${pad}</${tag}>`;
2563
3531
  e.preventDefault();
2564
3532
  e.stopPropagation();
2565
3533
  const target = e.target;
2566
- const fiber = getFiber(target);
2567
- const boundary = fiber ? findComponentBoundary(fiber) : null;
2568
- const hasFiber = fiber !== null && boundary !== null;
2569
- const newNodes = [];
2570
- let componentName;
2571
- if (hasFiber) {
2572
- const rootFiber = getRootFiber();
2573
- if (!rootFiber) {
2574
- showToast("Could not find React root.");
2575
- return;
2576
- }
2577
- const instances = findAllInstances(rootFiber, boundary.componentType);
2578
- const path = getChildPath(boundary.componentFiber, fiber);
2579
- for (const inst of instances) {
2580
- const node = resolvePathToDOM(inst, path);
2581
- if (node) newNodes.push(node);
2582
- }
2583
- componentName = boundary.componentName;
2584
- } else {
2585
- const targetEl2 = target;
2586
- newNodes.push(...findDOMEquivalents(targetEl2));
2587
- componentName = targetEl2.tagName.toLowerCase();
2588
- }
2589
- if (addingMode && currentEquivalentNodes.length > 0) {
2590
- addingMode = false;
2591
- const merged = [...currentEquivalentNodes];
2592
- for (const n of newNodes) {
2593
- if (!merged.includes(n)) merged.push(n);
2594
- }
2595
- clearHighlights();
2596
- merged.forEach((n) => highlightElement(n));
2597
- currentEquivalentNodes = merged;
2598
- selectedInstanceIndices = /* @__PURE__ */ new Set();
2599
- if (currentTargetEl) showDrawButton(currentTargetEl);
2600
- sendTo("panel", { type: "ELEMENT_SELECTED", componentName: currentBoundary?.componentName ?? componentName, instanceCount: merged.length, classes: currentTargetEl?.className ?? "", tailwindConfig: await fetchTailwindConfig() });
2601
- return;
2602
- }
3534
+ const targetEl = target;
3535
+ const classString = typeof targetEl.className === "string" ? targetEl.className : "";
3536
+ const result = findExactMatches(targetEl, shadowHost);
3537
+ const componentName = result.componentName ?? targetEl.tagName.toLowerCase();
2603
3538
  clearHighlights();
2604
- const equivalentNodes = [];
2605
- for (const node of newNodes) {
2606
- equivalentNodes.push(node);
3539
+ for (const node of result.exactMatch) {
2607
3540
  highlightElement(node);
2608
3541
  }
2609
- if (hasFiber && equivalentNodes.length <= 1) {
2610
- const repeated = findInlineRepeatedNodes(fiber, boundary.componentFiber);
2611
- if (repeated.length > 0) {
2612
- clearHighlights();
2613
- equivalentNodes.length = 0;
2614
- for (const node of repeated) {
2615
- equivalentNodes.push(node);
2616
- highlightElement(node);
2617
- }
2618
- }
2619
- }
2620
- console.log(`[overlay] ${componentName} \u2014 ${equivalentNodes.length} highlighted`);
3542
+ console.log(`[overlay] ${componentName} \u2014 ${result.exactMatch.length} exact matches highlighted`);
2621
3543
  const config = await fetchTailwindConfig();
2622
- const targetEl = target;
2623
- const classString = targetEl.className;
2624
- if (typeof classString !== "string") return;
2625
- currentEquivalentNodes = equivalentNodes;
3544
+ currentEquivalentNodes = result.exactMatch;
2626
3545
  currentTargetEl = targetEl;
2627
3546
  currentBoundary = { componentName };
2628
- selectedInstanceIndices = /* @__PURE__ */ new Set();
2629
- if (hasFiber) {
2630
- const rootFiber = getRootFiber();
2631
- const instances = rootFiber ? findAllInstances(rootFiber, boundary.componentType) : [];
2632
- currentInstances = instances.map((inst, i) => {
2633
- const domNode = inst.stateNode instanceof HTMLElement ? inst.stateNode : null;
2634
- const label = domNode ? (domNode.innerText || "").trim().slice(0, 40) || `#${i + 1}` : `#${i + 1}`;
2635
- const parentFiber = inst.return;
2636
- const parent = parentFiber?.type?.name ?? "";
2637
- return { index: i, label, parent };
2638
- });
2639
- } else {
2640
- currentInstances = equivalentNodes.map((node, i) => ({
2641
- index: i,
2642
- label: (node.innerText || "").trim().slice(0, 40) || `#${i + 1}`,
2643
- parent: node.parentElement?.tagName.toLowerCase() ?? ""
2644
- }));
2645
- }
3547
+ cachedNearGroups = null;
3548
+ currentInstances = result.exactMatch.map((node, i) => ({
3549
+ index: i,
3550
+ label: (node.innerText || "").trim().slice(0, 40) || `#${i + 1}`,
3551
+ parent: node.parentElement?.tagName.toLowerCase() ?? ""
3552
+ }));
2646
3553
  clearHoverPreview();
2647
3554
  setSelectMode(false);
2648
3555
  showDrawButton(targetEl);
@@ -2653,7 +3560,7 @@ ${pad}</${tag}>`;
2653
3560
  sendTo("panel", {
2654
3561
  type: "ELEMENT_SELECTED",
2655
3562
  componentName,
2656
- instanceCount: equivalentNodes.length,
3563
+ instanceCount: result.exactMatch.length,
2657
3564
  classes: classString,
2658
3565
  tailwindConfig: config
2659
3566
  });
@@ -2664,7 +3571,6 @@ ${pad}</${tag}>`;
2664
3571
  document.addEventListener("click", clickHandler, { capture: true });
2665
3572
  document.addEventListener("mousemove", mouseMoveHandler, { passive: true });
2666
3573
  } else {
2667
- addingMode = false;
2668
3574
  document.documentElement.style.cursor = "";
2669
3575
  document.removeEventListener("click", clickHandler, { capture: true });
2670
3576
  document.removeEventListener("mousemove", mouseMoveHandler);
@@ -2672,16 +3578,19 @@ ${pad}</${tag}>`;
2672
3578
  }
2673
3579
  sendTo("panel", { type: "SELECT_MODE_CHANGED", active: on });
2674
3580
  }
3581
+ var PANEL_OPEN_KEY = "tw-inspector-panel-open";
2675
3582
  function toggleInspect(btn) {
2676
3583
  active = !active;
2677
3584
  if (active) {
2678
3585
  btn.classList.add("active");
3586
+ sessionStorage.setItem(PANEL_OPEN_KEY, "1");
2679
3587
  const panelUrl = `${SERVER_ORIGIN}/panel`;
2680
3588
  if (!activeContainer.isOpen()) {
2681
3589
  activeContainer.open(panelUrl);
2682
3590
  }
2683
3591
  } else {
2684
3592
  btn.classList.remove("active");
3593
+ sessionStorage.removeItem(PANEL_OPEN_KEY);
2685
3594
  setSelectMode(false);
2686
3595
  activeContainer.close();
2687
3596
  revertPreview();
@@ -2710,7 +3619,8 @@ ${pad}</${tag}>`;
2710
3619
  const wrapper = document.createElement("div");
2711
3620
  wrapper.setAttribute("data-tw-design-canvas", "true");
2712
3621
  wrapper.style.cssText = `
2713
- border: 2px dashed #00848B;
3622
+ outline: 2px dashed #00848B;
3623
+ outline-offset: 2px;
2714
3624
  border-radius: 6px;
2715
3625
  background: #FAFBFB;
2716
3626
  position: relative;
@@ -2724,6 +3634,7 @@ ${pad}</${tag}>`;
2724
3634
  `;
2725
3635
  const iframe = document.createElement("iframe");
2726
3636
  iframe.src = `${SERVER_ORIGIN}/panel/?mode=design`;
3637
+ iframe.allow = "microphone";
2727
3638
  iframe.style.cssText = `
2728
3639
  width: 100%;
2729
3640
  height: 100%;
@@ -2836,7 +3747,7 @@ ${pad}</${tag}>`;
2836
3747
  default:
2837
3748
  targetEl.insertAdjacentElement("beforebegin", wrapper);
2838
3749
  }
2839
- designCanvasWrappers.push(wrapper);
3750
+ designCanvasWrappers.push({ wrapper, replacedNodes: null, parent: null, anchor: null });
2840
3751
  iframe.addEventListener("load", () => {
2841
3752
  const contextMsg = {
2842
3753
  type: "ELEMENT_CONTEXT",
@@ -2859,6 +3770,99 @@ ${pad}</${tag}>`;
2859
3770
  setTimeout(trySend, 200);
2860
3771
  });
2861
3772
  }
3773
+ async function handleCaptureScreenshot() {
3774
+ if (!currentTargetEl || !currentBoundary) {
3775
+ showToast("Select an element first");
3776
+ return;
3777
+ }
3778
+ if (!areSiblings(currentEquivalentNodes)) {
3779
+ showToast("Screenshot & Annotate requires all selected elements to be siblings in the DOM.");
3780
+ return;
3781
+ }
3782
+ let screenshot;
3783
+ let screenshotWidth;
3784
+ let screenshotHeight;
3785
+ try {
3786
+ ({ dataUrl: screenshot, width: screenshotWidth, height: screenshotHeight } = await captureRegion(currentEquivalentNodes));
3787
+ } catch (err) {
3788
+ showToast("Screenshot capture failed");
3789
+ console.error("[overlay] captureRegion error:", err);
3790
+ return;
3791
+ }
3792
+ const parent = currentEquivalentNodes[0].parentElement;
3793
+ const anchor = currentEquivalentNodes[0].nextSibling;
3794
+ const firstStyle = getComputedStyle(currentEquivalentNodes[0]);
3795
+ const lastStyle = getComputedStyle(currentEquivalentNodes[currentEquivalentNodes.length - 1]);
3796
+ const marginTop = firstStyle.marginTop;
3797
+ const marginBottom = lastStyle.marginBottom;
3798
+ const marginLeft = firstStyle.marginLeft;
3799
+ const marginRight = firstStyle.marginRight;
3800
+ const replacedNodes = [...currentEquivalentNodes];
3801
+ const targetEl = currentTargetEl;
3802
+ const boundary = currentBoundary;
3803
+ const instanceCount = currentEquivalentNodes.length;
3804
+ clearHighlights();
3805
+ for (const node of currentEquivalentNodes) {
3806
+ node.remove();
3807
+ }
3808
+ const PANEL_CHROME_HEIGHT = 40;
3809
+ const wrapper = document.createElement("div");
3810
+ wrapper.setAttribute("data-tw-design-canvas", "true");
3811
+ wrapper.style.cssText = `
3812
+ outline: 2px dashed #00848B;
3813
+ outline-offset: 2px;
3814
+ border-radius: 6px;
3815
+ background: #FAFBFB;
3816
+ position: relative;
3817
+ overflow: hidden;
3818
+ width: ${screenshotWidth}px;
3819
+ height: ${screenshotHeight + PANEL_CHROME_HEIGHT}px;
3820
+ min-width: 300px;
3821
+ margin-top: ${marginTop};
3822
+ margin-bottom: ${marginBottom};
3823
+ margin-left: ${marginLeft};
3824
+ margin-right: ${marginRight};
3825
+ box-shadow: 0 4px 24px rgba(0,0,0,0.15);
3826
+ box-sizing: border-box;
3827
+ `;
3828
+ const iframe = document.createElement("iframe");
3829
+ iframe.src = `${SERVER_ORIGIN}/panel/?mode=design`;
3830
+ iframe.style.cssText = `
3831
+ width: 100%;
3832
+ height: 100%;
3833
+ border: none;
3834
+ display: block;
3835
+ `;
3836
+ wrapper.appendChild(iframe);
3837
+ if (anchor) {
3838
+ parent.insertBefore(wrapper, anchor);
3839
+ } else {
3840
+ parent.appendChild(wrapper);
3841
+ }
3842
+ designCanvasWrappers.push({ wrapper, replacedNodes, parent, anchor });
3843
+ iframe.addEventListener("load", () => {
3844
+ const contextMsg = {
3845
+ type: "ELEMENT_CONTEXT",
3846
+ componentName: boundary.componentName,
3847
+ instanceCount,
3848
+ target: {
3849
+ tag: targetEl.tagName.toLowerCase(),
3850
+ classes: typeof targetEl.className === "string" ? targetEl.className : "",
3851
+ innerText: (targetEl.innerText || "").trim().slice(0, 60)
3852
+ },
3853
+ context: buildContext(targetEl, "", "", /* @__PURE__ */ new Map()),
3854
+ insertMode: "replace",
3855
+ screenshot
3856
+ };
3857
+ let attempts = 0;
3858
+ const trySend = () => {
3859
+ sendTo("design", contextMsg);
3860
+ attempts++;
3861
+ if (attempts < 5) setTimeout(trySend, 300);
3862
+ };
3863
+ setTimeout(trySend, 200);
3864
+ });
3865
+ }
2862
3866
  function getDefaultContainer() {
2863
3867
  try {
2864
3868
  const stored = localStorage.getItem("tw-panel-container");
@@ -2909,6 +3913,8 @@ ${pad}</${tag}>`;
2909
3913
  }
2910
3914
  } else if (msg.type === "PATCH_PREVIEW" && currentEquivalentNodes.length > 0) {
2911
3915
  applyPreview(currentEquivalentNodes, msg.oldClass, msg.newClass, SERVER_ORIGIN);
3916
+ } else if (msg.type === "PATCH_PREVIEW_BATCH" && currentEquivalentNodes.length > 0) {
3917
+ applyPreviewBatch(currentEquivalentNodes, msg.pairs, SERVER_ORIGIN);
2912
3918
  } else if (msg.type === "PATCH_REVERT") {
2913
3919
  revertPreview();
2914
3920
  } else if (msg.type === "PATCH_STAGE" && currentTargetEl && currentBoundary) {
@@ -2958,8 +3964,11 @@ ${pad}</${tag}>`;
2958
3964
  }
2959
3965
  } else if (msg.type === "INSERT_DESIGN_CANVAS") {
2960
3966
  injectDesignCanvas(msg.insertMode);
3967
+ } else if (msg.type === "CAPTURE_SCREENSHOT") {
3968
+ handleCaptureScreenshot();
2961
3969
  } else if (msg.type === "DESIGN_SUBMITTED") {
2962
- const last = designCanvasWrappers[designCanvasWrappers.length - 1];
3970
+ const lastEntry = designCanvasWrappers[designCanvasWrappers.length - 1];
3971
+ const last = lastEntry?.wrapper;
2963
3972
  if (last) {
2964
3973
  const iframe = last.querySelector("iframe");
2965
3974
  if (iframe && msg.image) {
@@ -2978,9 +3987,22 @@ ${pad}</${tag}>`;
2978
3987
  last.appendChild(img);
2979
3988
  }
2980
3989
  }
3990
+ } else if (msg.type === "CLOSE_PANEL") {
3991
+ if (active) toggleInspect(btn);
2981
3992
  } else if (msg.type === "DESIGN_CLOSE") {
2982
3993
  const last = designCanvasWrappers.pop();
2983
- if (last) last.remove();
3994
+ if (last) {
3995
+ if (last.replacedNodes && last.parent) {
3996
+ for (const node of last.replacedNodes) {
3997
+ if (last.anchor) {
3998
+ last.parent.insertBefore(node, last.anchor);
3999
+ } else {
4000
+ last.parent.appendChild(node);
4001
+ }
4002
+ }
4003
+ }
4004
+ last.wrapper.remove();
4005
+ }
2984
4006
  }
2985
4007
  });
2986
4008
  window.addEventListener("resize", () => {
@@ -2988,13 +4010,24 @@ ${pad}</${tag}>`;
2988
4010
  shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
2989
4011
  currentEquivalentNodes.forEach((n) => highlightElement(n));
2990
4012
  }
4013
+ if (toolbarEl && currentTargetEl) {
4014
+ positionWithFlip(currentTargetEl, toolbarEl);
4015
+ }
2991
4016
  });
2992
4017
  window.addEventListener("scroll", () => {
2993
4018
  if (currentEquivalentNodes.length > 0) {
2994
4019
  shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
2995
4020
  currentEquivalentNodes.forEach((n) => highlightElement(n));
2996
4021
  }
4022
+ if (toolbarEl && currentTargetEl) {
4023
+ positionWithFlip(currentTargetEl, toolbarEl);
4024
+ }
2997
4025
  }, { capture: true, passive: true });
4026
+ if (sessionStorage.getItem(PANEL_OPEN_KEY) === "1") {
4027
+ active = true;
4028
+ btn.classList.add("active");
4029
+ activeContainer.open(`${SERVER_ORIGIN}/panel`);
4030
+ }
2998
4031
  window.addEventListener("overlay-ws-connected", () => {
2999
4032
  if (wasConnected) {
3000
4033
  showToast("Reconnected");