@bitovi/vybit 0.4.9 → 0.5.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,7 @@ ${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);
1493
1516
  }
1494
1517
  }
1495
1518
  function revertPreview() {
@@ -1842,6 +1865,845 @@ ${pad}</${tag}>`;
1842
1865
  }
1843
1866
  };
1844
1867
 
1868
+ // node_modules/html-to-image/es/util.js
1869
+ function resolveUrl(url, baseUrl) {
1870
+ if (url.match(/^[a-z]+:\/\//i)) {
1871
+ return url;
1872
+ }
1873
+ if (url.match(/^\/\//)) {
1874
+ return window.location.protocol + url;
1875
+ }
1876
+ if (url.match(/^[a-z]+:/i)) {
1877
+ return url;
1878
+ }
1879
+ const doc = document.implementation.createHTMLDocument();
1880
+ const base = doc.createElement("base");
1881
+ const a = doc.createElement("a");
1882
+ doc.head.appendChild(base);
1883
+ doc.body.appendChild(a);
1884
+ if (baseUrl) {
1885
+ base.href = baseUrl;
1886
+ }
1887
+ a.href = url;
1888
+ return a.href;
1889
+ }
1890
+ var uuid = /* @__PURE__ */ (() => {
1891
+ let counter = 0;
1892
+ const random = () => (
1893
+ // eslint-disable-next-line no-bitwise
1894
+ `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4)
1895
+ );
1896
+ return () => {
1897
+ counter += 1;
1898
+ return `u${random()}${counter}`;
1899
+ };
1900
+ })();
1901
+ function toArray(arrayLike) {
1902
+ const arr = [];
1903
+ for (let i = 0, l = arrayLike.length; i < l; i++) {
1904
+ arr.push(arrayLike[i]);
1905
+ }
1906
+ return arr;
1907
+ }
1908
+ var styleProps = null;
1909
+ function getStyleProperties(options = {}) {
1910
+ if (styleProps) {
1911
+ return styleProps;
1912
+ }
1913
+ if (options.includeStyleProperties) {
1914
+ styleProps = options.includeStyleProperties;
1915
+ return styleProps;
1916
+ }
1917
+ styleProps = toArray(window.getComputedStyle(document.documentElement));
1918
+ return styleProps;
1919
+ }
1920
+ function px(node, styleProperty) {
1921
+ const win = node.ownerDocument.defaultView || window;
1922
+ const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
1923
+ return val ? parseFloat(val.replace("px", "")) : 0;
1924
+ }
1925
+ function getNodeWidth(node) {
1926
+ const leftBorder = px(node, "border-left-width");
1927
+ const rightBorder = px(node, "border-right-width");
1928
+ return node.clientWidth + leftBorder + rightBorder;
1929
+ }
1930
+ function getNodeHeight(node) {
1931
+ const topBorder = px(node, "border-top-width");
1932
+ const bottomBorder = px(node, "border-bottom-width");
1933
+ return node.clientHeight + topBorder + bottomBorder;
1934
+ }
1935
+ function getImageSize(targetNode, options = {}) {
1936
+ const width = options.width || getNodeWidth(targetNode);
1937
+ const height = options.height || getNodeHeight(targetNode);
1938
+ return { width, height };
1939
+ }
1940
+ function getPixelRatio() {
1941
+ let ratio;
1942
+ let FINAL_PROCESS;
1943
+ try {
1944
+ FINAL_PROCESS = process;
1945
+ } catch (e) {
1946
+ }
1947
+ const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
1948
+ if (val) {
1949
+ ratio = parseInt(val, 10);
1950
+ if (Number.isNaN(ratio)) {
1951
+ ratio = 1;
1952
+ }
1953
+ }
1954
+ return ratio || window.devicePixelRatio || 1;
1955
+ }
1956
+ var canvasDimensionLimit = 16384;
1957
+ function checkCanvasDimensions(canvas) {
1958
+ if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
1959
+ if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
1960
+ if (canvas.width > canvas.height) {
1961
+ canvas.height *= canvasDimensionLimit / canvas.width;
1962
+ canvas.width = canvasDimensionLimit;
1963
+ } else {
1964
+ canvas.width *= canvasDimensionLimit / canvas.height;
1965
+ canvas.height = canvasDimensionLimit;
1966
+ }
1967
+ } else if (canvas.width > canvasDimensionLimit) {
1968
+ canvas.height *= canvasDimensionLimit / canvas.width;
1969
+ canvas.width = canvasDimensionLimit;
1970
+ } else {
1971
+ canvas.width *= canvasDimensionLimit / canvas.height;
1972
+ canvas.height = canvasDimensionLimit;
1973
+ }
1974
+ }
1975
+ }
1976
+ function createImage(url) {
1977
+ return new Promise((resolve, reject) => {
1978
+ const img = new Image();
1979
+ img.onload = () => {
1980
+ img.decode().then(() => {
1981
+ requestAnimationFrame(() => resolve(img));
1982
+ });
1983
+ };
1984
+ img.onerror = reject;
1985
+ img.crossOrigin = "anonymous";
1986
+ img.decoding = "async";
1987
+ img.src = url;
1988
+ });
1989
+ }
1990
+ async function svgToDataURL(svg) {
1991
+ return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`);
1992
+ }
1993
+ async function nodeToDataURL(node, width, height) {
1994
+ const xmlns = "http://www.w3.org/2000/svg";
1995
+ const svg = document.createElementNS(xmlns, "svg");
1996
+ const foreignObject = document.createElementNS(xmlns, "foreignObject");
1997
+ svg.setAttribute("width", `${width}`);
1998
+ svg.setAttribute("height", `${height}`);
1999
+ svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
2000
+ foreignObject.setAttribute("width", "100%");
2001
+ foreignObject.setAttribute("height", "100%");
2002
+ foreignObject.setAttribute("x", "0");
2003
+ foreignObject.setAttribute("y", "0");
2004
+ foreignObject.setAttribute("externalResourcesRequired", "true");
2005
+ svg.appendChild(foreignObject);
2006
+ foreignObject.appendChild(node);
2007
+ return svgToDataURL(svg);
2008
+ }
2009
+ var isInstanceOfElement = (node, instance) => {
2010
+ if (node instanceof instance)
2011
+ return true;
2012
+ const nodePrototype = Object.getPrototypeOf(node);
2013
+ if (nodePrototype === null)
2014
+ return false;
2015
+ return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
2016
+ };
2017
+
2018
+ // node_modules/html-to-image/es/clone-pseudos.js
2019
+ function formatCSSText(style) {
2020
+ const content = style.getPropertyValue("content");
2021
+ return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
2022
+ }
2023
+ function formatCSSProperties(style, options) {
2024
+ return getStyleProperties(options).map((name) => {
2025
+ const value = style.getPropertyValue(name);
2026
+ const priority = style.getPropertyPriority(name);
2027
+ return `${name}: ${value}${priority ? " !important" : ""};`;
2028
+ }).join(" ");
2029
+ }
2030
+ function getPseudoElementStyle(className, pseudo, style, options) {
2031
+ const selector = `.${className}:${pseudo}`;
2032
+ const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
2033
+ return document.createTextNode(`${selector}{${cssText}}`);
2034
+ }
2035
+ function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
2036
+ const style = window.getComputedStyle(nativeNode, pseudo);
2037
+ const content = style.getPropertyValue("content");
2038
+ if (content === "" || content === "none") {
2039
+ return;
2040
+ }
2041
+ const className = uuid();
2042
+ try {
2043
+ clonedNode.className = `${clonedNode.className} ${className}`;
2044
+ } catch (err) {
2045
+ return;
2046
+ }
2047
+ const styleElement = document.createElement("style");
2048
+ styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
2049
+ clonedNode.appendChild(styleElement);
2050
+ }
2051
+ function clonePseudoElements(nativeNode, clonedNode, options) {
2052
+ clonePseudoElement(nativeNode, clonedNode, ":before", options);
2053
+ clonePseudoElement(nativeNode, clonedNode, ":after", options);
2054
+ }
2055
+
2056
+ // node_modules/html-to-image/es/mimes.js
2057
+ var WOFF = "application/font-woff";
2058
+ var JPEG = "image/jpeg";
2059
+ var mimes = {
2060
+ woff: WOFF,
2061
+ woff2: WOFF,
2062
+ ttf: "application/font-truetype",
2063
+ eot: "application/vnd.ms-fontobject",
2064
+ png: "image/png",
2065
+ jpg: JPEG,
2066
+ jpeg: JPEG,
2067
+ gif: "image/gif",
2068
+ tiff: "image/tiff",
2069
+ svg: "image/svg+xml",
2070
+ webp: "image/webp"
2071
+ };
2072
+ function getExtension(url) {
2073
+ const match = /\.([^./]*?)$/g.exec(url);
2074
+ return match ? match[1] : "";
2075
+ }
2076
+ function getMimeType(url) {
2077
+ const extension = getExtension(url).toLowerCase();
2078
+ return mimes[extension] || "";
2079
+ }
2080
+
2081
+ // node_modules/html-to-image/es/dataurl.js
2082
+ function getContentFromDataUrl(dataURL) {
2083
+ return dataURL.split(/,/)[1];
2084
+ }
2085
+ function isDataUrl(url) {
2086
+ return url.search(/^(data:)/) !== -1;
2087
+ }
2088
+ function makeDataUrl(content, mimeType) {
2089
+ return `data:${mimeType};base64,${content}`;
2090
+ }
2091
+ async function fetchAsDataURL(url, init2, process2) {
2092
+ const res = await fetch(url, init2);
2093
+ if (res.status === 404) {
2094
+ throw new Error(`Resource "${res.url}" not found`);
2095
+ }
2096
+ const blob = await res.blob();
2097
+ return new Promise((resolve, reject) => {
2098
+ const reader = new FileReader();
2099
+ reader.onerror = reject;
2100
+ reader.onloadend = () => {
2101
+ try {
2102
+ resolve(process2({ res, result: reader.result }));
2103
+ } catch (error) {
2104
+ reject(error);
2105
+ }
2106
+ };
2107
+ reader.readAsDataURL(blob);
2108
+ });
2109
+ }
2110
+ var cache = {};
2111
+ function getCacheKey(url, contentType, includeQueryParams) {
2112
+ let key = url.replace(/\?.*/, "");
2113
+ if (includeQueryParams) {
2114
+ key = url;
2115
+ }
2116
+ if (/ttf|otf|eot|woff2?/i.test(key)) {
2117
+ key = key.replace(/.*\//, "");
2118
+ }
2119
+ return contentType ? `[${contentType}]${key}` : key;
2120
+ }
2121
+ async function resourceToDataURL(resourceUrl, contentType, options) {
2122
+ const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
2123
+ if (cache[cacheKey] != null) {
2124
+ return cache[cacheKey];
2125
+ }
2126
+ if (options.cacheBust) {
2127
+ resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
2128
+ }
2129
+ let dataURL;
2130
+ try {
2131
+ const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => {
2132
+ if (!contentType) {
2133
+ contentType = res.headers.get("Content-Type") || "";
2134
+ }
2135
+ return getContentFromDataUrl(result);
2136
+ });
2137
+ dataURL = makeDataUrl(content, contentType);
2138
+ } catch (error) {
2139
+ dataURL = options.imagePlaceholder || "";
2140
+ let msg = `Failed to fetch resource: ${resourceUrl}`;
2141
+ if (error) {
2142
+ msg = typeof error === "string" ? error : error.message;
2143
+ }
2144
+ if (msg) {
2145
+ console.warn(msg);
2146
+ }
2147
+ }
2148
+ cache[cacheKey] = dataURL;
2149
+ return dataURL;
2150
+ }
2151
+
2152
+ // node_modules/html-to-image/es/clone-node.js
2153
+ async function cloneCanvasElement(canvas) {
2154
+ const dataURL = canvas.toDataURL();
2155
+ if (dataURL === "data:,") {
2156
+ return canvas.cloneNode(false);
2157
+ }
2158
+ return createImage(dataURL);
2159
+ }
2160
+ async function cloneVideoElement(video, options) {
2161
+ if (video.currentSrc) {
2162
+ const canvas = document.createElement("canvas");
2163
+ const ctx = canvas.getContext("2d");
2164
+ canvas.width = video.clientWidth;
2165
+ canvas.height = video.clientHeight;
2166
+ ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
2167
+ const dataURL2 = canvas.toDataURL();
2168
+ return createImage(dataURL2);
2169
+ }
2170
+ const poster = video.poster;
2171
+ const contentType = getMimeType(poster);
2172
+ const dataURL = await resourceToDataURL(poster, contentType, options);
2173
+ return createImage(dataURL);
2174
+ }
2175
+ async function cloneIFrameElement(iframe, options) {
2176
+ var _a;
2177
+ try {
2178
+ if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
2179
+ return await cloneNode(iframe.contentDocument.body, options, true);
2180
+ }
2181
+ } catch (_b) {
2182
+ }
2183
+ return iframe.cloneNode(false);
2184
+ }
2185
+ async function cloneSingleNode(node, options) {
2186
+ if (isInstanceOfElement(node, HTMLCanvasElement)) {
2187
+ return cloneCanvasElement(node);
2188
+ }
2189
+ if (isInstanceOfElement(node, HTMLVideoElement)) {
2190
+ return cloneVideoElement(node, options);
2191
+ }
2192
+ if (isInstanceOfElement(node, HTMLIFrameElement)) {
2193
+ return cloneIFrameElement(node, options);
2194
+ }
2195
+ return node.cloneNode(isSVGElement(node));
2196
+ }
2197
+ var isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
2198
+ var isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
2199
+ async function cloneChildren(nativeNode, clonedNode, options) {
2200
+ var _a, _b;
2201
+ if (isSVGElement(clonedNode)) {
2202
+ return clonedNode;
2203
+ }
2204
+ let children = [];
2205
+ if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
2206
+ children = toArray(nativeNode.assignedNodes());
2207
+ } else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
2208
+ children = toArray(nativeNode.contentDocument.body.childNodes);
2209
+ } else {
2210
+ children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
2211
+ }
2212
+ if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
2213
+ return clonedNode;
2214
+ }
2215
+ await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => {
2216
+ if (clonedChild) {
2217
+ clonedNode.appendChild(clonedChild);
2218
+ }
2219
+ }), Promise.resolve());
2220
+ return clonedNode;
2221
+ }
2222
+ function cloneCSSStyle(nativeNode, clonedNode, options) {
2223
+ const targetStyle = clonedNode.style;
2224
+ if (!targetStyle) {
2225
+ return;
2226
+ }
2227
+ const sourceStyle = window.getComputedStyle(nativeNode);
2228
+ if (sourceStyle.cssText) {
2229
+ targetStyle.cssText = sourceStyle.cssText;
2230
+ targetStyle.transformOrigin = sourceStyle.transformOrigin;
2231
+ } else {
2232
+ getStyleProperties(options).forEach((name) => {
2233
+ let value = sourceStyle.getPropertyValue(name);
2234
+ if (name === "font-size" && value.endsWith("px")) {
2235
+ const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
2236
+ value = `${reducedFont}px`;
2237
+ }
2238
+ if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") {
2239
+ value = "block";
2240
+ }
2241
+ if (name === "d" && clonedNode.getAttribute("d")) {
2242
+ value = `path(${clonedNode.getAttribute("d")})`;
2243
+ }
2244
+ targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
2245
+ });
2246
+ }
2247
+ }
2248
+ function cloneInputValue(nativeNode, clonedNode) {
2249
+ if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
2250
+ clonedNode.innerHTML = nativeNode.value;
2251
+ }
2252
+ if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
2253
+ clonedNode.setAttribute("value", nativeNode.value);
2254
+ }
2255
+ }
2256
+ function cloneSelectValue(nativeNode, clonedNode) {
2257
+ if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
2258
+ const clonedSelect = clonedNode;
2259
+ const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value"));
2260
+ if (selectedOption) {
2261
+ selectedOption.setAttribute("selected", "");
2262
+ }
2263
+ }
2264
+ }
2265
+ function decorate(nativeNode, clonedNode, options) {
2266
+ if (isInstanceOfElement(clonedNode, Element)) {
2267
+ cloneCSSStyle(nativeNode, clonedNode, options);
2268
+ clonePseudoElements(nativeNode, clonedNode, options);
2269
+ cloneInputValue(nativeNode, clonedNode);
2270
+ cloneSelectValue(nativeNode, clonedNode);
2271
+ }
2272
+ return clonedNode;
2273
+ }
2274
+ async function ensureSVGSymbols(clone, options) {
2275
+ const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : [];
2276
+ if (uses.length === 0) {
2277
+ return clone;
2278
+ }
2279
+ const processedDefs = {};
2280
+ for (let i = 0; i < uses.length; i++) {
2281
+ const use = uses[i];
2282
+ const id = use.getAttribute("xlink:href");
2283
+ if (id) {
2284
+ const exist = clone.querySelector(id);
2285
+ const definition = document.querySelector(id);
2286
+ if (!exist && definition && !processedDefs[id]) {
2287
+ processedDefs[id] = await cloneNode(definition, options, true);
2288
+ }
2289
+ }
2290
+ }
2291
+ const nodes = Object.values(processedDefs);
2292
+ if (nodes.length) {
2293
+ const ns = "http://www.w3.org/1999/xhtml";
2294
+ const svg = document.createElementNS(ns, "svg");
2295
+ svg.setAttribute("xmlns", ns);
2296
+ svg.style.position = "absolute";
2297
+ svg.style.width = "0";
2298
+ svg.style.height = "0";
2299
+ svg.style.overflow = "hidden";
2300
+ svg.style.display = "none";
2301
+ const defs = document.createElementNS(ns, "defs");
2302
+ svg.appendChild(defs);
2303
+ for (let i = 0; i < nodes.length; i++) {
2304
+ defs.appendChild(nodes[i]);
2305
+ }
2306
+ clone.appendChild(svg);
2307
+ }
2308
+ return clone;
2309
+ }
2310
+ async function cloneNode(node, options, isRoot) {
2311
+ if (!isRoot && options.filter && !options.filter(node)) {
2312
+ return null;
2313
+ }
2314
+ 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));
2315
+ }
2316
+
2317
+ // node_modules/html-to-image/es/embed-resources.js
2318
+ var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
2319
+ var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
2320
+ var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
2321
+ function toRegex(url) {
2322
+ const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
2323
+ return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
2324
+ }
2325
+ function parseURLs(cssText) {
2326
+ const urls = [];
2327
+ cssText.replace(URL_REGEX, (raw, quotation, url) => {
2328
+ urls.push(url);
2329
+ return raw;
2330
+ });
2331
+ return urls.filter((url) => !isDataUrl(url));
2332
+ }
2333
+ async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
2334
+ try {
2335
+ const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
2336
+ const contentType = getMimeType(resourceURL);
2337
+ let dataURL;
2338
+ if (getContentFromUrl) {
2339
+ const content = await getContentFromUrl(resolvedURL);
2340
+ dataURL = makeDataUrl(content, contentType);
2341
+ } else {
2342
+ dataURL = await resourceToDataURL(resolvedURL, contentType, options);
2343
+ }
2344
+ return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
2345
+ } catch (error) {
2346
+ }
2347
+ return cssText;
2348
+ }
2349
+ function filterPreferredFontFormat(str, { preferredFontFormat }) {
2350
+ return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => {
2351
+ while (true) {
2352
+ const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
2353
+ if (!format) {
2354
+ return "";
2355
+ }
2356
+ if (format === preferredFontFormat) {
2357
+ return `src: ${src};`;
2358
+ }
2359
+ }
2360
+ });
2361
+ }
2362
+ function shouldEmbed(url) {
2363
+ return url.search(URL_REGEX) !== -1;
2364
+ }
2365
+ async function embedResources(cssText, baseUrl, options) {
2366
+ if (!shouldEmbed(cssText)) {
2367
+ return cssText;
2368
+ }
2369
+ const filteredCSSText = filterPreferredFontFormat(cssText, options);
2370
+ const urls = parseURLs(filteredCSSText);
2371
+ return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
2372
+ }
2373
+
2374
+ // node_modules/html-to-image/es/embed-images.js
2375
+ async function embedProp(propName, node, options) {
2376
+ var _a;
2377
+ const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
2378
+ if (propValue) {
2379
+ const cssString = await embedResources(propValue, null, options);
2380
+ node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
2381
+ return true;
2382
+ }
2383
+ return false;
2384
+ }
2385
+ async function embedBackground(clonedNode, options) {
2386
+ ;
2387
+ await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
2388
+ await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
2389
+ }
2390
+ async function embedImageNode(clonedNode, options) {
2391
+ const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
2392
+ if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
2393
+ return;
2394
+ }
2395
+ const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
2396
+ const dataURL = await resourceToDataURL(url, getMimeType(url), options);
2397
+ await new Promise((resolve, reject) => {
2398
+ clonedNode.onload = resolve;
2399
+ clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
2400
+ try {
2401
+ resolve(options.onImageErrorHandler(...attributes));
2402
+ } catch (error) {
2403
+ reject(error);
2404
+ }
2405
+ } : reject;
2406
+ const image = clonedNode;
2407
+ if (image.decode) {
2408
+ image.decode = resolve;
2409
+ }
2410
+ if (image.loading === "lazy") {
2411
+ image.loading = "eager";
2412
+ }
2413
+ if (isImageElement) {
2414
+ clonedNode.srcset = "";
2415
+ clonedNode.src = dataURL;
2416
+ } else {
2417
+ clonedNode.href.baseVal = dataURL;
2418
+ }
2419
+ });
2420
+ }
2421
+ async function embedChildren(clonedNode, options) {
2422
+ const children = toArray(clonedNode.childNodes);
2423
+ const deferreds = children.map((child) => embedImages(child, options));
2424
+ await Promise.all(deferreds).then(() => clonedNode);
2425
+ }
2426
+ async function embedImages(clonedNode, options) {
2427
+ if (isInstanceOfElement(clonedNode, Element)) {
2428
+ await embedBackground(clonedNode, options);
2429
+ await embedImageNode(clonedNode, options);
2430
+ await embedChildren(clonedNode, options);
2431
+ }
2432
+ }
2433
+
2434
+ // node_modules/html-to-image/es/apply-style.js
2435
+ function applyStyle(node, options) {
2436
+ const { style } = node;
2437
+ if (options.backgroundColor) {
2438
+ style.backgroundColor = options.backgroundColor;
2439
+ }
2440
+ if (options.width) {
2441
+ style.width = `${options.width}px`;
2442
+ }
2443
+ if (options.height) {
2444
+ style.height = `${options.height}px`;
2445
+ }
2446
+ const manual = options.style;
2447
+ if (manual != null) {
2448
+ Object.keys(manual).forEach((key) => {
2449
+ style[key] = manual[key];
2450
+ });
2451
+ }
2452
+ return node;
2453
+ }
2454
+
2455
+ // node_modules/html-to-image/es/embed-webfonts.js
2456
+ var cssFetchCache = {};
2457
+ async function fetchCSS(url) {
2458
+ let cache2 = cssFetchCache[url];
2459
+ if (cache2 != null) {
2460
+ return cache2;
2461
+ }
2462
+ const res = await fetch(url);
2463
+ const cssText = await res.text();
2464
+ cache2 = { url, cssText };
2465
+ cssFetchCache[url] = cache2;
2466
+ return cache2;
2467
+ }
2468
+ async function embedFonts(data, options) {
2469
+ let cssText = data.cssText;
2470
+ const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
2471
+ const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
2472
+ const loadFonts = fontLocs.map(async (loc) => {
2473
+ let url = loc.replace(regexUrl, "$1");
2474
+ if (!url.startsWith("https://")) {
2475
+ url = new URL(url, data.url).href;
2476
+ }
2477
+ return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
2478
+ cssText = cssText.replace(loc, `url(${result})`);
2479
+ return [loc, result];
2480
+ });
2481
+ });
2482
+ return Promise.all(loadFonts).then(() => cssText);
2483
+ }
2484
+ function parseCSS(source) {
2485
+ if (source == null) {
2486
+ return [];
2487
+ }
2488
+ const result = [];
2489
+ const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
2490
+ let cssText = source.replace(commentsRegex, "");
2491
+ const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi");
2492
+ while (true) {
2493
+ const matches = keyframesRegex.exec(cssText);
2494
+ if (matches === null) {
2495
+ break;
2496
+ }
2497
+ result.push(matches[0]);
2498
+ }
2499
+ cssText = cssText.replace(keyframesRegex, "");
2500
+ const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
2501
+ const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})";
2502
+ const unifiedRegex = new RegExp(combinedCSSRegex, "gi");
2503
+ while (true) {
2504
+ let matches = importRegex.exec(cssText);
2505
+ if (matches === null) {
2506
+ matches = unifiedRegex.exec(cssText);
2507
+ if (matches === null) {
2508
+ break;
2509
+ } else {
2510
+ importRegex.lastIndex = unifiedRegex.lastIndex;
2511
+ }
2512
+ } else {
2513
+ unifiedRegex.lastIndex = importRegex.lastIndex;
2514
+ }
2515
+ result.push(matches[0]);
2516
+ }
2517
+ return result;
2518
+ }
2519
+ async function getCSSRules(styleSheets, options) {
2520
+ const ret = [];
2521
+ const deferreds = [];
2522
+ styleSheets.forEach((sheet) => {
2523
+ if ("cssRules" in sheet) {
2524
+ try {
2525
+ toArray(sheet.cssRules || []).forEach((item, index) => {
2526
+ if (item.type === CSSRule.IMPORT_RULE) {
2527
+ let importIndex = index + 1;
2528
+ const url = item.href;
2529
+ const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
2530
+ try {
2531
+ sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length);
2532
+ } catch (error) {
2533
+ console.error("Error inserting rule from remote css", {
2534
+ rule,
2535
+ error
2536
+ });
2537
+ }
2538
+ })).catch((e) => {
2539
+ console.error("Error loading remote css", e.toString());
2540
+ });
2541
+ deferreds.push(deferred);
2542
+ }
2543
+ });
2544
+ } catch (e) {
2545
+ const inline2 = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
2546
+ if (sheet.href != null) {
2547
+ deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
2548
+ inline2.insertRule(rule, inline2.cssRules.length);
2549
+ })).catch((err) => {
2550
+ console.error("Error loading remote stylesheet", err);
2551
+ }));
2552
+ }
2553
+ console.error("Error inlining remote css file", e);
2554
+ }
2555
+ }
2556
+ });
2557
+ return Promise.all(deferreds).then(() => {
2558
+ styleSheets.forEach((sheet) => {
2559
+ if ("cssRules" in sheet) {
2560
+ try {
2561
+ toArray(sheet.cssRules || []).forEach((item) => {
2562
+ ret.push(item);
2563
+ });
2564
+ } catch (e) {
2565
+ console.error(`Error while reading CSS rules from ${sheet.href}`, e);
2566
+ }
2567
+ }
2568
+ });
2569
+ return ret;
2570
+ });
2571
+ }
2572
+ function getWebFontRules(cssRules) {
2573
+ return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src")));
2574
+ }
2575
+ async function parseWebFontRules(node, options) {
2576
+ if (node.ownerDocument == null) {
2577
+ throw new Error("Provided element is not within a Document");
2578
+ }
2579
+ const styleSheets = toArray(node.ownerDocument.styleSheets);
2580
+ const cssRules = await getCSSRules(styleSheets, options);
2581
+ return getWebFontRules(cssRules);
2582
+ }
2583
+ function normalizeFontFamily(font) {
2584
+ return font.trim().replace(/["']/g, "");
2585
+ }
2586
+ function getUsedFonts(node) {
2587
+ const fonts = /* @__PURE__ */ new Set();
2588
+ function traverse(node2) {
2589
+ const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
2590
+ fontFamily.split(",").forEach((font) => {
2591
+ fonts.add(normalizeFontFamily(font));
2592
+ });
2593
+ Array.from(node2.children).forEach((child) => {
2594
+ if (child instanceof HTMLElement) {
2595
+ traverse(child);
2596
+ }
2597
+ });
2598
+ }
2599
+ traverse(node);
2600
+ return fonts;
2601
+ }
2602
+ async function getWebFontCSS(node, options) {
2603
+ const rules = await parseWebFontRules(node, options);
2604
+ const usedFonts = getUsedFonts(node);
2605
+ const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
2606
+ const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
2607
+ return embedResources(rule.cssText, baseUrl, options);
2608
+ }));
2609
+ return cssTexts.join("\n");
2610
+ }
2611
+ async function embedWebFonts(clonedNode, options) {
2612
+ const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
2613
+ if (cssText) {
2614
+ const styleNode = document.createElement("style");
2615
+ const sytleContent = document.createTextNode(cssText);
2616
+ styleNode.appendChild(sytleContent);
2617
+ if (clonedNode.firstChild) {
2618
+ clonedNode.insertBefore(styleNode, clonedNode.firstChild);
2619
+ } else {
2620
+ clonedNode.appendChild(styleNode);
2621
+ }
2622
+ }
2623
+ }
2624
+
2625
+ // node_modules/html-to-image/es/index.js
2626
+ async function toSvg(node, options = {}) {
2627
+ const { width, height } = getImageSize(node, options);
2628
+ const clonedNode = await cloneNode(node, options, true);
2629
+ await embedWebFonts(clonedNode, options);
2630
+ await embedImages(clonedNode, options);
2631
+ applyStyle(clonedNode, options);
2632
+ const datauri = await nodeToDataURL(clonedNode, width, height);
2633
+ return datauri;
2634
+ }
2635
+ async function toCanvas(node, options = {}) {
2636
+ const { width, height } = getImageSize(node, options);
2637
+ const svg = await toSvg(node, options);
2638
+ const img = await createImage(svg);
2639
+ const canvas = document.createElement("canvas");
2640
+ const context = canvas.getContext("2d");
2641
+ const ratio = options.pixelRatio || getPixelRatio();
2642
+ const canvasWidth = options.canvasWidth || width;
2643
+ const canvasHeight = options.canvasHeight || height;
2644
+ canvas.width = canvasWidth * ratio;
2645
+ canvas.height = canvasHeight * ratio;
2646
+ if (!options.skipAutoScale) {
2647
+ checkCanvasDimensions(canvas);
2648
+ }
2649
+ canvas.style.width = `${canvasWidth}`;
2650
+ canvas.style.height = `${canvasHeight}`;
2651
+ if (options.backgroundColor) {
2652
+ context.fillStyle = options.backgroundColor;
2653
+ context.fillRect(0, 0, canvas.width, canvas.height);
2654
+ }
2655
+ context.drawImage(img, 0, 0, canvas.width, canvas.height);
2656
+ return canvas;
2657
+ }
2658
+ async function toPng(node, options = {}) {
2659
+ const canvas = await toCanvas(node, options);
2660
+ return canvas.toDataURL();
2661
+ }
2662
+
2663
+ // overlay/src/screenshot.ts
2664
+ function areSiblings(nodes) {
2665
+ if (nodes.length === 0) return false;
2666
+ const parent = nodes[0].parentElement;
2667
+ return nodes.every((n) => n.parentElement === parent);
2668
+ }
2669
+ async function captureRegion(nodes) {
2670
+ if (nodes.length === 1) {
2671
+ const rect = nodes[0].getBoundingClientRect();
2672
+ const width2 = Math.round(rect.width);
2673
+ const height2 = Math.round(rect.height);
2674
+ const dataUrl = await toPng(nodes[0], { skipFonts: true, width: width2, height: height2 });
2675
+ return { dataUrl, width: width2, height: height2 };
2676
+ }
2677
+ const parent = nodes[0].parentElement;
2678
+ const rects = nodes.map((n) => n.getBoundingClientRect());
2679
+ const top = Math.min(...rects.map((r) => r.top));
2680
+ const left = Math.min(...rects.map((r) => r.left));
2681
+ const right = Math.max(...rects.map((r) => r.right));
2682
+ const bottom = Math.max(...rects.map((r) => r.bottom));
2683
+ const width = Math.round(right - left);
2684
+ const height = Math.round(bottom - top);
2685
+ const ghost = parent.cloneNode(false);
2686
+ ghost.style.padding = "0";
2687
+ ghost.style.border = "none";
2688
+ ghost.style.margin = "0";
2689
+ ghost.style.width = `${width}px`;
2690
+ for (const node of nodes) {
2691
+ ghost.appendChild(node.cloneNode(true));
2692
+ }
2693
+ ghost.style.position = "fixed";
2694
+ ghost.style.left = "0";
2695
+ ghost.style.top = "0";
2696
+ ghost.style.zIndex = "999999";
2697
+ ghost.style.pointerEvents = "none";
2698
+ document.body.appendChild(ghost);
2699
+ try {
2700
+ const dataUrl = await toPng(ghost, { skipFonts: true, width, height });
2701
+ return { dataUrl, width, height };
2702
+ } finally {
2703
+ ghost.remove();
2704
+ }
2705
+ }
2706
+
1845
2707
  // overlay/src/index.ts
1846
2708
  var shadowRoot;
1847
2709
  var shadowHost;
@@ -1852,7 +2714,7 @@ ${pad}</${tag}>`;
1852
2714
  var currentTargetEl = null;
1853
2715
  var currentBoundary = null;
1854
2716
  var currentInstances = [];
1855
- var addingMode = false;
2717
+ var cachedNearGroups = null;
1856
2718
  var hoverOutlineEl = null;
1857
2719
  var hoverTooltipEl = null;
1858
2720
  var lastHoveredEl = null;
@@ -1985,7 +2847,7 @@ ${pad}</${tag}>`;
1985
2847
  align-self: stretch;
1986
2848
  }
1987
2849
  /* Base style for all buttons inside the toolbar */
1988
- .draw-btn, .el-pick-btn, .el-add-btn {
2850
+ .draw-btn, .el-reselect-btn, .el-pick-btn, .el-add-btn {
1989
2851
  background: transparent;
1990
2852
  border: none;
1991
2853
  border-radius: 0;
@@ -2009,10 +2871,59 @@ ${pad}</${tag}>`;
2009
2871
  .el-pick-btn { gap: 3px; padding: 0 8px; font-size: 12px; font-weight: 600; letter-spacing: 0.01em; }
2010
2872
  .el-pick-btn svg { opacity: 0.7; flex-shrink: 0; }
2011
2873
  .el-add-btn { padding: 0 10px; font-size: 15px; font-weight: 400; }
2012
- .draw-btn:hover, .el-pick-btn:hover, .el-add-btn:hover,
2874
+ .el-reselect-btn { padding: 0 9px; }
2875
+ .draw-btn:hover, .el-reselect-btn:hover, .el-pick-btn:hover, .el-add-btn:hover,
2013
2876
  .el-pick-btn.open {
2014
2877
  background: rgba(255,255,255,0.12);
2015
2878
  }
2879
+ /* \u2500\u2500 Hover preview highlight (dashed, for group hover) \u2500\u2500 */
2880
+ .highlight-preview {
2881
+ position: fixed;
2882
+ pointer-events: none;
2883
+ border: 2px dashed #00848B;
2884
+ border-radius: 2px;
2885
+ box-sizing: border-box;
2886
+ z-index: 999998;
2887
+ }
2888
+ /* \u2500\u2500 Group picker popover (replaces instance picker) \u2500\u2500 */
2889
+ .el-group-empty {
2890
+ padding: 12px 14px;
2891
+ font-size: 11px;
2892
+ color: #687879;
2893
+ text-align: center;
2894
+ }
2895
+ .el-group-row {
2896
+ display: flex;
2897
+ align-items: center;
2898
+ gap: 8px;
2899
+ padding: 5px 12px;
2900
+ cursor: pointer;
2901
+ transition: background 0.1s;
2902
+ }
2903
+ .el-group-row:hover { background: rgba(0,132,139,0.05); }
2904
+ .el-group-row input[type=checkbox] {
2905
+ accent-color: #00848B;
2906
+ width: 13px;
2907
+ height: 13px;
2908
+ flex-shrink: 0;
2909
+ cursor: pointer;
2910
+ }
2911
+ .el-group-count {
2912
+ font-size: 11px;
2913
+ font-weight: 600;
2914
+ color: #334041;
2915
+ min-width: 20px;
2916
+ }
2917
+ .el-group-diff {
2918
+ flex: 1;
2919
+ font-size: 10px;
2920
+ font-family: 'SF Mono', 'JetBrains Mono', 'Fira Code', monospace;
2921
+ overflow: hidden;
2922
+ text-overflow: ellipsis;
2923
+ white-space: nowrap;
2924
+ }
2925
+ .el-group-diff .diff-add { color: #16a34a; }
2926
+ .el-group-diff .diff-rem { color: #dc2626; }
2016
2927
  /* \u2500\u2500 Instance picker popover \u2500\u2500 */
2017
2928
  .el-picker {
2018
2929
  position: fixed;
@@ -2211,7 +3122,6 @@ ${pad}</${tag}>`;
2211
3122
  var drawPopoverEl = null;
2212
3123
  var pickerEl = null;
2213
3124
  var pickerCloseHandler = null;
2214
- var selectedInstanceIndices = /* @__PURE__ */ new Set();
2215
3125
  function removeDrawButton() {
2216
3126
  toolbarEl?.remove();
2217
3127
  toolbarEl = null;
@@ -2281,7 +3191,7 @@ ${pad}</${tag}>`;
2281
3191
  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
2282
3192
  <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
2283
3193
  </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>`;
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>`;
2285
3195
  async function positionWithFlip(anchor, floating, placement = "top-start") {
2286
3196
  const { x, y } = await computePosition2(anchor, floating, {
2287
3197
  placement,
@@ -2292,14 +3202,23 @@ ${pad}</${tag}>`;
2292
3202
  }
2293
3203
  function showDrawButton(targetEl) {
2294
3204
  removeDrawButton();
2295
- const allEquivalentNodes = [...currentEquivalentNodes];
2296
- const instanceCount = allEquivalentNodes.length;
3205
+ const instanceCount = currentEquivalentNodes.length;
2297
3206
  const toolbar = document.createElement("div");
2298
3207
  toolbar.className = "el-toolbar";
2299
3208
  toolbar.style.left = "0px";
2300
3209
  toolbar.style.top = "0px";
2301
3210
  shadowRoot.appendChild(toolbar);
2302
3211
  toolbarEl = toolbar;
3212
+ const reselectBtn = document.createElement("button");
3213
+ reselectBtn.className = "el-reselect-btn";
3214
+ reselectBtn.innerHTML = RESELECT_SVG;
3215
+ reselectBtn.title = "Re-select element";
3216
+ toolbar.appendChild(reselectBtn);
3217
+ reselectBtn.addEventListener("click", (e) => {
3218
+ e.stopPropagation();
3219
+ clearHighlights();
3220
+ setSelectMode(true);
3221
+ });
2303
3222
  const drawBtn = document.createElement("button");
2304
3223
  drawBtn.className = "draw-btn";
2305
3224
  drawBtn.innerHTML = PENCIL_SVG;
@@ -2316,69 +3235,47 @@ ${pad}</${tag}>`;
2316
3235
  showDrawPopover(drawBtn);
2317
3236
  }
2318
3237
  });
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();
3238
+ const sep = document.createElement("div");
3239
+ sep.className = "el-toolbar-sep";
3240
+ toolbar.appendChild(sep);
3241
+ const addGroupBtn = document.createElement("button");
3242
+ addGroupBtn.className = "el-add-btn";
3243
+ addGroupBtn.textContent = `${instanceCount} +`;
3244
+ addGroupBtn.title = `${instanceCount} matching element${instanceCount !== 1 ? "s" : ""} selected \u2014 click to add similar`;
3245
+ toolbar.appendChild(addGroupBtn);
3246
+ addGroupBtn.addEventListener("click", (e) => {
3247
+ e.stopPropagation();
3248
+ drawPopoverEl?.remove();
3249
+ drawPopoverEl = null;
3250
+ if (pickerEl) {
3251
+ pickerEl.remove();
2361
3252
  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
- }
3253
+ addGroupBtn.classList.remove("open");
3254
+ } else {
3255
+ addGroupBtn.classList.add("open");
3256
+ showGroupPicker(
3257
+ addGroupBtn,
3258
+ () => addGroupBtn.classList.remove("open"),
3259
+ (totalCount) => {
3260
+ addGroupBtn.textContent = `${totalCount} +`;
3261
+ addGroupBtn.title = `${totalCount} matching element${totalCount !== 1 ? "s" : ""} selected \u2014 click to add similar`;
3262
+ }
3263
+ );
3264
+ }
3265
+ });
2369
3266
  positionWithFlip(targetEl, toolbar);
2370
3267
  }
2371
- function showInstancePicker(anchorBtn, onClose, onApply) {
3268
+ function showGroupPicker(anchorBtn, onClose, onCountChange) {
2372
3269
  if (pickerCloseHandler) {
2373
3270
  document.removeEventListener("click", pickerCloseHandler, { capture: true });
2374
3271
  pickerCloseHandler = null;
2375
3272
  }
2376
3273
  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
- );
3274
+ if (!cachedNearGroups && currentTargetEl) {
3275
+ const exactSet = new Set(currentEquivalentNodes);
3276
+ cachedNearGroups = computeNearGroups(currentTargetEl, exactSet, shadowHost);
3277
+ }
3278
+ const groups = cachedNearGroups ?? [];
2382
3279
  const picker = document.createElement("div");
2383
3280
  picker.className = "el-picker";
2384
3281
  picker.style.left = "0px";
@@ -2389,72 +3286,89 @@ ${pad}</${tag}>`;
2389
3286
  header.className = "el-picker-header";
2390
3287
  const title = document.createElement("span");
2391
3288
  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);
3289
+ title.textContent = "Similar elements";
2401
3290
  header.appendChild(title);
2402
- header.appendChild(actions);
2403
3291
  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) => {
3292
+ if (groups.length === 0) {
3293
+ const empty = document.createElement("div");
3294
+ empty.className = "el-group-empty";
3295
+ empty.textContent = "No similar elements found";
3296
+ picker.appendChild(empty);
3297
+ } else {
3298
+ let clearPreviewHighlights2 = function() {
3299
+ shadowRoot.querySelectorAll(".highlight-preview").forEach((el) => el.remove());
3300
+ }, updateSelection2 = function() {
3301
+ const allNodes = [...baseNodes];
3302
+ for (const idx of checkedGroups) {
3303
+ for (const el of groups[idx].elements) {
3304
+ if (!allNodes.includes(el)) allNodes.push(el);
3305
+ }
3306
+ }
3307
+ currentEquivalentNodes = allNodes;
3308
+ shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
3309
+ currentEquivalentNodes.forEach((n) => highlightElement(n));
3310
+ onCountChange(currentEquivalentNodes.length);
3311
+ if (currentTargetEl && currentBoundary) {
3312
+ sendTo("panel", {
3313
+ type: "ELEMENT_SELECTED",
3314
+ componentName: currentBoundary.componentName,
3315
+ instanceCount: currentEquivalentNodes.length,
3316
+ classes: typeof currentTargetEl.className === "string" ? currentTargetEl.className : "",
3317
+ tailwindConfig: tailwindConfigCache
3318
+ });
3319
+ }
3320
+ };
3321
+ var clearPreviewHighlights = clearPreviewHighlights2, updateSelection = updateSelection2;
3322
+ const list = document.createElement("div");
3323
+ list.className = "el-picker-list";
3324
+ picker.appendChild(list);
3325
+ const checkedGroups = /* @__PURE__ */ new Set();
3326
+ const baseNodes = [...currentEquivalentNodes];
3327
+ groups.forEach((group, idx) => {
2412
3328
  const row = document.createElement("label");
2413
- row.className = "el-picker-row";
3329
+ row.className = "el-group-row";
2414
3330
  const cb = document.createElement("input");
2415
3331
  cb.type = "checkbox";
2416
- cb.checked = selected.has(i);
3332
+ cb.checked = false;
2417
3333
  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();
3334
+ if (cb.checked) checkedGroups.add(idx);
3335
+ else checkedGroups.delete(idx);
3336
+ updateSelection2();
2422
3337
  });
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>`;
3338
+ const count = document.createElement("span");
3339
+ count.className = "el-group-count";
3340
+ count.textContent = `(${group.elements.length})`;
3341
+ const diff = document.createElement("span");
3342
+ diff.className = "el-group-diff";
3343
+ 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>`);
3346
+ diff.innerHTML = parts.join(" ");
2430
3347
  row.appendChild(cb);
2431
- row.appendChild(badge);
2432
- row.appendChild(label);
3348
+ row.appendChild(count);
3349
+ row.appendChild(diff);
2433
3350
  list.appendChild(row);
3351
+ row.addEventListener("mouseenter", () => {
3352
+ clearPreviewHighlights2();
3353
+ for (const el of group.elements) {
3354
+ const rect = el.getBoundingClientRect();
3355
+ const preview = document.createElement("div");
3356
+ preview.className = "highlight-preview";
3357
+ preview.style.top = `${rect.top - 3}px`;
3358
+ preview.style.left = `${rect.left - 3}px`;
3359
+ preview.style.width = `${rect.width + 6}px`;
3360
+ preview.style.height = `${rect.height + 6}px`;
3361
+ shadowRoot.appendChild(preview);
3362
+ }
3363
+ });
3364
+ row.addEventListener("mouseleave", () => {
3365
+ clearPreviewHighlights2();
3366
+ });
2434
3367
  });
2435
3368
  }
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();
3369
+ positionWithFlip(anchorBtn, picker);
2457
3370
  const removePicker = () => {
3371
+ shadowRoot.querySelectorAll(".highlight-preview").forEach((el) => el.remove());
2458
3372
  if (pickerCloseHandler) {
2459
3373
  document.removeEventListener("click", pickerCloseHandler, { capture: true });
2460
3374
  pickerCloseHandler = null;
@@ -2462,16 +3376,6 @@ ${pad}</${tag}>`;
2462
3376
  pickerEl?.remove();
2463
3377
  pickerEl = null;
2464
3378
  };
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
3379
  setTimeout(() => {
2476
3380
  pickerCloseHandler = (e) => {
2477
3381
  const path = e.composedPath();
@@ -2515,6 +3419,26 @@ ${pad}</${tag}>`;
2515
3419
  });
2516
3420
  popover.appendChild(row);
2517
3421
  }
3422
+ const sep = document.createElement("div");
3423
+ sep.style.cssText = "height:1px;background:#DFE2E2;margin:4px 0;";
3424
+ popover.appendChild(sep);
3425
+ const screenshotHeader = document.createElement("div");
3426
+ screenshotHeader.className = "draw-popover-header";
3427
+ screenshotHeader.textContent = "Screenshot & Annotate";
3428
+ popover.appendChild(screenshotHeader);
3429
+ const screenshotRow = document.createElement("button");
3430
+ screenshotRow.className = "draw-popover-item";
3431
+ screenshotRow.innerHTML = `
3432
+ <span class="draw-popover-icon">\u{1F4F7}</span>
3433
+ <span class="draw-popover-label">Screenshot & Annotate</span>
3434
+ `;
3435
+ screenshotRow.addEventListener("click", (e) => {
3436
+ e.stopPropagation();
3437
+ drawPopoverEl?.remove();
3438
+ drawPopoverEl = null;
3439
+ handleCaptureScreenshot();
3440
+ });
3441
+ popover.appendChild(screenshotRow);
2518
3442
  drawPopoverEl = popover;
2519
3443
  shadowRoot.appendChild(popover);
2520
3444
  positionWithFlip(anchorBtn, popover, "top-start");
@@ -2563,86 +3487,25 @@ ${pad}</${tag}>`;
2563
3487
  e.preventDefault();
2564
3488
  e.stopPropagation();
2565
3489
  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
- }
3490
+ const targetEl = target;
3491
+ const classString = typeof targetEl.className === "string" ? targetEl.className : "";
3492
+ const result = findExactMatches(targetEl, shadowHost);
3493
+ const componentName = result.componentName ?? targetEl.tagName.toLowerCase();
2603
3494
  clearHighlights();
2604
- const equivalentNodes = [];
2605
- for (const node of newNodes) {
2606
- equivalentNodes.push(node);
3495
+ for (const node of result.exactMatch) {
2607
3496
  highlightElement(node);
2608
3497
  }
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`);
3498
+ console.log(`[overlay] ${componentName} \u2014 ${result.exactMatch.length} exact matches highlighted`);
2621
3499
  const config = await fetchTailwindConfig();
2622
- const targetEl = target;
2623
- const classString = targetEl.className;
2624
- if (typeof classString !== "string") return;
2625
- currentEquivalentNodes = equivalentNodes;
3500
+ currentEquivalentNodes = result.exactMatch;
2626
3501
  currentTargetEl = targetEl;
2627
3502
  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
- }
3503
+ cachedNearGroups = null;
3504
+ currentInstances = result.exactMatch.map((node, i) => ({
3505
+ index: i,
3506
+ label: (node.innerText || "").trim().slice(0, 40) || `#${i + 1}`,
3507
+ parent: node.parentElement?.tagName.toLowerCase() ?? ""
3508
+ }));
2646
3509
  clearHoverPreview();
2647
3510
  setSelectMode(false);
2648
3511
  showDrawButton(targetEl);
@@ -2653,7 +3516,7 @@ ${pad}</${tag}>`;
2653
3516
  sendTo("panel", {
2654
3517
  type: "ELEMENT_SELECTED",
2655
3518
  componentName,
2656
- instanceCount: equivalentNodes.length,
3519
+ instanceCount: result.exactMatch.length,
2657
3520
  classes: classString,
2658
3521
  tailwindConfig: config
2659
3522
  });
@@ -2664,7 +3527,6 @@ ${pad}</${tag}>`;
2664
3527
  document.addEventListener("click", clickHandler, { capture: true });
2665
3528
  document.addEventListener("mousemove", mouseMoveHandler, { passive: true });
2666
3529
  } else {
2667
- addingMode = false;
2668
3530
  document.documentElement.style.cursor = "";
2669
3531
  document.removeEventListener("click", clickHandler, { capture: true });
2670
3532
  document.removeEventListener("mousemove", mouseMoveHandler);
@@ -2672,16 +3534,19 @@ ${pad}</${tag}>`;
2672
3534
  }
2673
3535
  sendTo("panel", { type: "SELECT_MODE_CHANGED", active: on });
2674
3536
  }
3537
+ var PANEL_OPEN_KEY = "tw-inspector-panel-open";
2675
3538
  function toggleInspect(btn) {
2676
3539
  active = !active;
2677
3540
  if (active) {
2678
3541
  btn.classList.add("active");
3542
+ sessionStorage.setItem(PANEL_OPEN_KEY, "1");
2679
3543
  const panelUrl = `${SERVER_ORIGIN}/panel`;
2680
3544
  if (!activeContainer.isOpen()) {
2681
3545
  activeContainer.open(panelUrl);
2682
3546
  }
2683
3547
  } else {
2684
3548
  btn.classList.remove("active");
3549
+ sessionStorage.removeItem(PANEL_OPEN_KEY);
2685
3550
  setSelectMode(false);
2686
3551
  activeContainer.close();
2687
3552
  revertPreview();
@@ -2710,7 +3575,8 @@ ${pad}</${tag}>`;
2710
3575
  const wrapper = document.createElement("div");
2711
3576
  wrapper.setAttribute("data-tw-design-canvas", "true");
2712
3577
  wrapper.style.cssText = `
2713
- border: 2px dashed #00848B;
3578
+ outline: 2px dashed #00848B;
3579
+ outline-offset: 2px;
2714
3580
  border-radius: 6px;
2715
3581
  background: #FAFBFB;
2716
3582
  position: relative;
@@ -2836,7 +3702,7 @@ ${pad}</${tag}>`;
2836
3702
  default:
2837
3703
  targetEl.insertAdjacentElement("beforebegin", wrapper);
2838
3704
  }
2839
- designCanvasWrappers.push(wrapper);
3705
+ designCanvasWrappers.push({ wrapper, replacedNodes: null, parent: null, anchor: null });
2840
3706
  iframe.addEventListener("load", () => {
2841
3707
  const contextMsg = {
2842
3708
  type: "ELEMENT_CONTEXT",
@@ -2859,6 +3725,99 @@ ${pad}</${tag}>`;
2859
3725
  setTimeout(trySend, 200);
2860
3726
  });
2861
3727
  }
3728
+ async function handleCaptureScreenshot() {
3729
+ if (!currentTargetEl || !currentBoundary) {
3730
+ showToast("Select an element first");
3731
+ return;
3732
+ }
3733
+ if (!areSiblings(currentEquivalentNodes)) {
3734
+ showToast("Screenshot & Annotate requires all selected elements to be siblings in the DOM.");
3735
+ return;
3736
+ }
3737
+ let screenshot;
3738
+ let screenshotWidth;
3739
+ let screenshotHeight;
3740
+ try {
3741
+ ({ dataUrl: screenshot, width: screenshotWidth, height: screenshotHeight } = await captureRegion(currentEquivalentNodes));
3742
+ } catch (err) {
3743
+ showToast("Screenshot capture failed");
3744
+ console.error("[overlay] captureRegion error:", err);
3745
+ return;
3746
+ }
3747
+ const parent = currentEquivalentNodes[0].parentElement;
3748
+ const anchor = currentEquivalentNodes[0].nextSibling;
3749
+ const firstStyle = getComputedStyle(currentEquivalentNodes[0]);
3750
+ const lastStyle = getComputedStyle(currentEquivalentNodes[currentEquivalentNodes.length - 1]);
3751
+ const marginTop = firstStyle.marginTop;
3752
+ const marginBottom = lastStyle.marginBottom;
3753
+ const marginLeft = firstStyle.marginLeft;
3754
+ const marginRight = firstStyle.marginRight;
3755
+ const replacedNodes = [...currentEquivalentNodes];
3756
+ const targetEl = currentTargetEl;
3757
+ const boundary = currentBoundary;
3758
+ const instanceCount = currentEquivalentNodes.length;
3759
+ clearHighlights();
3760
+ for (const node of currentEquivalentNodes) {
3761
+ node.remove();
3762
+ }
3763
+ const PANEL_CHROME_HEIGHT = 40;
3764
+ const wrapper = document.createElement("div");
3765
+ wrapper.setAttribute("data-tw-design-canvas", "true");
3766
+ wrapper.style.cssText = `
3767
+ outline: 2px dashed #00848B;
3768
+ outline-offset: 2px;
3769
+ border-radius: 6px;
3770
+ background: #FAFBFB;
3771
+ position: relative;
3772
+ overflow: hidden;
3773
+ width: ${screenshotWidth}px;
3774
+ height: ${screenshotHeight + PANEL_CHROME_HEIGHT}px;
3775
+ min-width: 300px;
3776
+ margin-top: ${marginTop};
3777
+ margin-bottom: ${marginBottom};
3778
+ margin-left: ${marginLeft};
3779
+ margin-right: ${marginRight};
3780
+ box-shadow: 0 4px 24px rgba(0,0,0,0.15);
3781
+ box-sizing: border-box;
3782
+ `;
3783
+ const iframe = document.createElement("iframe");
3784
+ iframe.src = `${SERVER_ORIGIN}/panel/?mode=design`;
3785
+ iframe.style.cssText = `
3786
+ width: 100%;
3787
+ height: 100%;
3788
+ border: none;
3789
+ display: block;
3790
+ `;
3791
+ 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 });
3798
+ iframe.addEventListener("load", () => {
3799
+ const contextMsg = {
3800
+ type: "ELEMENT_CONTEXT",
3801
+ componentName: boundary.componentName,
3802
+ instanceCount,
3803
+ target: {
3804
+ tag: targetEl.tagName.toLowerCase(),
3805
+ classes: typeof targetEl.className === "string" ? targetEl.className : "",
3806
+ innerText: (targetEl.innerText || "").trim().slice(0, 60)
3807
+ },
3808
+ context: buildContext(targetEl, "", "", /* @__PURE__ */ new Map()),
3809
+ insertMode: "replace",
3810
+ screenshot
3811
+ };
3812
+ let attempts = 0;
3813
+ const trySend = () => {
3814
+ sendTo("design", contextMsg);
3815
+ attempts++;
3816
+ if (attempts < 5) setTimeout(trySend, 300);
3817
+ };
3818
+ setTimeout(trySend, 200);
3819
+ });
3820
+ }
2862
3821
  function getDefaultContainer() {
2863
3822
  try {
2864
3823
  const stored = localStorage.getItem("tw-panel-container");
@@ -2958,8 +3917,11 @@ ${pad}</${tag}>`;
2958
3917
  }
2959
3918
  } else if (msg.type === "INSERT_DESIGN_CANVAS") {
2960
3919
  injectDesignCanvas(msg.insertMode);
3920
+ } else if (msg.type === "CAPTURE_SCREENSHOT") {
3921
+ handleCaptureScreenshot();
2961
3922
  } else if (msg.type === "DESIGN_SUBMITTED") {
2962
- const last = designCanvasWrappers[designCanvasWrappers.length - 1];
3923
+ const lastEntry = designCanvasWrappers[designCanvasWrappers.length - 1];
3924
+ const last = lastEntry?.wrapper;
2963
3925
  if (last) {
2964
3926
  const iframe = last.querySelector("iframe");
2965
3927
  if (iframe && msg.image) {
@@ -2980,7 +3942,18 @@ ${pad}</${tag}>`;
2980
3942
  }
2981
3943
  } else if (msg.type === "DESIGN_CLOSE") {
2982
3944
  const last = designCanvasWrappers.pop();
2983
- if (last) last.remove();
3945
+ if (last) {
3946
+ if (last.replacedNodes && last.parent) {
3947
+ for (const node of last.replacedNodes) {
3948
+ if (last.anchor) {
3949
+ last.parent.insertBefore(node, last.anchor);
3950
+ } else {
3951
+ last.parent.appendChild(node);
3952
+ }
3953
+ }
3954
+ }
3955
+ last.wrapper.remove();
3956
+ }
2984
3957
  }
2985
3958
  });
2986
3959
  window.addEventListener("resize", () => {
@@ -2988,13 +3961,24 @@ ${pad}</${tag}>`;
2988
3961
  shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
2989
3962
  currentEquivalentNodes.forEach((n) => highlightElement(n));
2990
3963
  }
3964
+ if (toolbarEl && currentTargetEl) {
3965
+ positionWithFlip(currentTargetEl, toolbarEl);
3966
+ }
2991
3967
  });
2992
3968
  window.addEventListener("scroll", () => {
2993
3969
  if (currentEquivalentNodes.length > 0) {
2994
3970
  shadowRoot.querySelectorAll(".highlight-overlay").forEach((el) => el.remove());
2995
3971
  currentEquivalentNodes.forEach((n) => highlightElement(n));
2996
3972
  }
3973
+ if (toolbarEl && currentTargetEl) {
3974
+ positionWithFlip(currentTargetEl, toolbarEl);
3975
+ }
2997
3976
  }, { capture: true, passive: true });
3977
+ if (sessionStorage.getItem(PANEL_OPEN_KEY) === "1") {
3978
+ active = true;
3979
+ btn.classList.add("active");
3980
+ activeContainer.open(`${SERVER_ORIGIN}/panel`);
3981
+ }
2998
3982
  window.addEventListener("overlay-ws-connected", () => {
2999
3983
  if (wasConnected) {
3000
3984
  showToast("Reconnected");