arcy.js 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{arcy.chat.f698fce59921.js → arcy.chat.27fabef680f1.js} +1 -1
- package/dist/arcy.flow.46ba8a96f99e.js +3 -0
- package/dist/{arcy.legacy.061d3b544569.js → arcy.legacy.98a48482777b.js} +2 -2
- package/dist/arcy.loader.js +1 -1
- package/dist/{arcy.modern.d30bb13c138a.js → arcy.modern.2c3fe0b458df.js} +2 -2
- package/dist/arcy.picker.777b2d4d1127.js +95 -0
- package/dist/{chat-VOYR3S25.js → chat-NCTVDXR5.js} +0 -3
- package/dist/{chunk-GDGE3HLE.js → chunk-7S5DA67F.js} +2 -2
- package/dist/{chunk-NY3NXM2V.js → chunk-AW7DHYSA.js} +1 -1
- package/dist/{chunk-NIHUDSWK.js → chunk-QDCO2MRA.js} +1 -1
- package/dist/design-mode.cjs +1 -1
- package/dist/design-mode.d.ts +9 -1
- package/dist/design-mode.js +2 -2
- package/dist/{flow-M7W3H3C5.js → flow-FGTIZOXU.js} +62 -17
- package/dist/index.cjs +490 -95
- package/dist/index.d.ts +21 -6
- package/dist/index.js +16 -12
- package/dist/{picker-GPZ2GHB4.js → picker-6REZLJW3.js} +417 -69
- package/package.json +1 -1
- package/dist/arcy.flow.fef131718e42.js +0 -3
- package/dist/arcy.picker.b7e531503cf2.js +0 -59
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PICKER_CONTRACT, PICKER_GLOBAL } from './chunk-DKYMIAYF.js';
|
|
2
|
-
import { SCREENSHOT_RENDER_BUDGET_MS } from './chunk-
|
|
2
|
+
import { SCREENSHOT_RENDER_BUDGET_MS } from './chunk-7S5DA67F.js';
|
|
3
3
|
import { createShell, injectStyles, warn, HOST_ATTRIBUTE } from './chunk-LGWYJSYX.js';
|
|
4
|
-
import { matchTarget } from './chunk-
|
|
5
|
-
import { resolveInteractiveTarget, createTargetFingerprint, captureEventFingerprint, closestAcrossShadow, isStableId, containsPii, SELECTOR_CANDIDATES_MAX, collectElementsDeep, querySelectorAllDeep } from './chunk-
|
|
4
|
+
import { matchTarget } from './chunk-QDCO2MRA.js';
|
|
5
|
+
import { resolveInteractiveTarget, createTargetFingerprint, captureEventFingerprint, closestAcrossShadow, isStableId, containsPii, SELECTOR_CANDIDATES_MAX, parentOrHost, collectElementsDeep, querySelectorAllDeep } from './chunk-AW7DHYSA.js';
|
|
6
6
|
|
|
7
7
|
/* arcy.js — https://arcyai.com */
|
|
8
8
|
|
|
@@ -1667,6 +1667,7 @@ var OUTPUT_MAX_DPR = 2;
|
|
|
1667
1667
|
var OUTPUT_MAX_PIXELS = 26e5;
|
|
1668
1668
|
var DATA_URL_SOFT_MAX = 36e5;
|
|
1669
1669
|
var RENDER_TIMEOUT_MS = SCREENSHOT_RENDER_BUDGET_MS;
|
|
1670
|
+
var RESOURCE_TIMEOUT_MS = 1500;
|
|
1670
1671
|
var PII_PATTERNS = [
|
|
1671
1672
|
/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
1672
1673
|
/\b\d{3}-\d{2}-\d{4}\b/g,
|
|
@@ -1701,6 +1702,213 @@ function computeCropRect(target, viewportWidth, viewportHeight) {
|
|
|
1701
1702
|
if (bottom - top > height) top += (bottom - top - height) / 2;
|
|
1702
1703
|
return { left, top, width: Math.max(0, width), height: Math.max(0, height) };
|
|
1703
1704
|
}
|
|
1705
|
+
var SETTLE_MAX_MS = 900;
|
|
1706
|
+
var SETTLE_STABLE_FRAMES = 2;
|
|
1707
|
+
function effectiveOpacity(el, view) {
|
|
1708
|
+
let opacity = 1;
|
|
1709
|
+
let current = el;
|
|
1710
|
+
let depth = 0;
|
|
1711
|
+
while (current !== null && depth < 64) {
|
|
1712
|
+
try {
|
|
1713
|
+
const value = Number.parseFloat(view.getComputedStyle(current).opacity);
|
|
1714
|
+
if (!Number.isNaN(value)) opacity *= value;
|
|
1715
|
+
} catch {
|
|
1716
|
+
break;
|
|
1717
|
+
}
|
|
1718
|
+
if (opacity <= 1e-3) return 0;
|
|
1719
|
+
current = parentOrHost(current);
|
|
1720
|
+
depth++;
|
|
1721
|
+
}
|
|
1722
|
+
return opacity;
|
|
1723
|
+
}
|
|
1724
|
+
function waitUntilSettled(el, view) {
|
|
1725
|
+
return new Promise((resolve) => {
|
|
1726
|
+
const raf = view.requestAnimationFrame;
|
|
1727
|
+
if (typeof raf !== "function") {
|
|
1728
|
+
resolve();
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
const start = Date.now();
|
|
1732
|
+
let previous = null;
|
|
1733
|
+
let stable = 0;
|
|
1734
|
+
const check = () => {
|
|
1735
|
+
try {
|
|
1736
|
+
if (Date.now() - start >= SETTLE_MAX_MS) {
|
|
1737
|
+
resolve();
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
const rect = el.getBoundingClientRect();
|
|
1741
|
+
const now = `${Math.round(rect.left)},${Math.round(rect.top)},${Math.round(rect.width)},${Math.round(rect.height)}`;
|
|
1742
|
+
const opaque = effectiveOpacity(el, view) >= 0.99;
|
|
1743
|
+
stable = opaque && now === previous ? stable + 1 : 0;
|
|
1744
|
+
previous = now;
|
|
1745
|
+
if (stable >= SETTLE_STABLE_FRAMES) {
|
|
1746
|
+
resolve();
|
|
1747
|
+
return;
|
|
1748
|
+
}
|
|
1749
|
+
raf.call(view, check);
|
|
1750
|
+
} catch {
|
|
1751
|
+
resolve();
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
raf.call(view, check);
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
function findRenderRoot(el, crop, root, view) {
|
|
1758
|
+
let covering = null;
|
|
1759
|
+
let cheapest = null;
|
|
1760
|
+
let current = el;
|
|
1761
|
+
let depth = 0;
|
|
1762
|
+
while (current !== null && current !== root && depth < 64) {
|
|
1763
|
+
if (isTransformed(current, view)) {
|
|
1764
|
+
covering = null;
|
|
1765
|
+
cheapest = null;
|
|
1766
|
+
} else {
|
|
1767
|
+
const rect = current.getBoundingClientRect();
|
|
1768
|
+
if (covering === null && covers(rect, crop)) covering = current;
|
|
1769
|
+
if (cheapest === null && keptRatio(rect, crop) >= MIN_CROP_KEPT) {
|
|
1770
|
+
cheapest = current;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
current = parentOrHost(current);
|
|
1774
|
+
depth++;
|
|
1775
|
+
}
|
|
1776
|
+
if (covering !== null && isAffordable(covering, view)) return covering;
|
|
1777
|
+
return cheapest ?? covering ?? root;
|
|
1778
|
+
}
|
|
1779
|
+
function isAffordable(el, view) {
|
|
1780
|
+
if (countElements(el) > NODE_BUDGET) return false;
|
|
1781
|
+
const rect = el.getBoundingClientRect();
|
|
1782
|
+
const viewport = Math.max(1, view.innerWidth * view.innerHeight);
|
|
1783
|
+
return rect.width * rect.height / viewport <= AREA_BUDGET_SCREENS;
|
|
1784
|
+
}
|
|
1785
|
+
var NODE_BUDGET = 800;
|
|
1786
|
+
var AREA_BUDGET_SCREENS = 3;
|
|
1787
|
+
var MIN_CROP_KEPT = 0.4;
|
|
1788
|
+
function keptRatio(bounds, crop) {
|
|
1789
|
+
const whole = crop.width * crop.height;
|
|
1790
|
+
if (whole <= 0) return 0;
|
|
1791
|
+
const kept = clipCrop(crop, {
|
|
1792
|
+
left: bounds.left,
|
|
1793
|
+
top: bounds.top,
|
|
1794
|
+
width: bounds.width,
|
|
1795
|
+
height: bounds.height
|
|
1796
|
+
});
|
|
1797
|
+
return kept.width * kept.height / whole;
|
|
1798
|
+
}
|
|
1799
|
+
function countElements(el) {
|
|
1800
|
+
try {
|
|
1801
|
+
return el.getElementsByTagName("*").length;
|
|
1802
|
+
} catch {
|
|
1803
|
+
return Number.MAX_SAFE_INTEGER;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
function clipCrop(crop, bounds) {
|
|
1807
|
+
const left = Math.max(crop.left, bounds.left);
|
|
1808
|
+
const top = Math.max(crop.top, bounds.top);
|
|
1809
|
+
const right = Math.min(crop.left + crop.width, bounds.left + bounds.width);
|
|
1810
|
+
const bottom = Math.min(crop.top + crop.height, bounds.top + bounds.height);
|
|
1811
|
+
return {
|
|
1812
|
+
left,
|
|
1813
|
+
top,
|
|
1814
|
+
width: Math.max(0, right - left),
|
|
1815
|
+
height: Math.max(0, bottom - top)
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
function isTransformed(el, view) {
|
|
1819
|
+
try {
|
|
1820
|
+
const style = view.getComputedStyle(el);
|
|
1821
|
+
return style.transform !== "" && style.transform !== "none" || style.perspective !== "" && style.perspective !== "none" || style.translate !== "" && style.translate !== "none" || style.rotate !== "" && style.rotate !== "none" || style.scale !== "" && style.scale !== "none";
|
|
1822
|
+
} catch {
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
}
|
|
1826
|
+
function covers(rect, crop) {
|
|
1827
|
+
return rect.left <= crop.left + 0.5 && rect.top <= crop.top + 0.5 && rect.right >= crop.left + crop.width - 0.5 && rect.bottom >= crop.top + crop.height - 0.5;
|
|
1828
|
+
}
|
|
1829
|
+
function backgroundUnder(root, crop, doc, view) {
|
|
1830
|
+
const points = [
|
|
1831
|
+
[crop.left + crop.width / 2, crop.top + crop.height / 2],
|
|
1832
|
+
[crop.left + 1, crop.top + 1],
|
|
1833
|
+
[crop.left + crop.width - 1, crop.top + crop.height - 1]
|
|
1834
|
+
];
|
|
1835
|
+
for (const [x, y] of points) {
|
|
1836
|
+
let stack = [];
|
|
1837
|
+
try {
|
|
1838
|
+
stack = doc.elementsFromPoint(x, y) ?? [];
|
|
1839
|
+
} catch {
|
|
1840
|
+
break;
|
|
1841
|
+
}
|
|
1842
|
+
for (const candidate of stack) {
|
|
1843
|
+
if (candidate === root || root.contains(candidate)) continue;
|
|
1844
|
+
if (candidate.hasAttribute(HOST_ATTRIBUTE)) continue;
|
|
1845
|
+
const color = opaqueBackground(candidate, view);
|
|
1846
|
+
if (color) return color;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
let current = root;
|
|
1850
|
+
let depth = 0;
|
|
1851
|
+
while (current !== null && depth < 64) {
|
|
1852
|
+
const color = opaqueBackground(current, view);
|
|
1853
|
+
if (color) return color;
|
|
1854
|
+
current = parentOrHost(current);
|
|
1855
|
+
depth++;
|
|
1856
|
+
}
|
|
1857
|
+
return "#ffffff";
|
|
1858
|
+
}
|
|
1859
|
+
function opaqueBackground(el, view) {
|
|
1860
|
+
try {
|
|
1861
|
+
const color = view.getComputedStyle(el).backgroundColor;
|
|
1862
|
+
if (!color || color === "transparent") return null;
|
|
1863
|
+
if (color.replace(/\s/g, "").startsWith("rgba(0,0,0,0)")) return null;
|
|
1864
|
+
return color;
|
|
1865
|
+
} catch {
|
|
1866
|
+
return null;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
function renderRootStyle(root, rootRect, doc, view, shiftX, shiftY) {
|
|
1870
|
+
const placement = {
|
|
1871
|
+
transform: `translate(${shiftX}px, ${shiftY}px)`,
|
|
1872
|
+
transformOrigin: "top left",
|
|
1873
|
+
margin: "0"
|
|
1874
|
+
};
|
|
1875
|
+
if (root === doc.documentElement) {
|
|
1876
|
+
return {
|
|
1877
|
+
...placement,
|
|
1878
|
+
width: `${view.innerWidth}px`,
|
|
1879
|
+
minHeight: `${view.innerHeight}px`,
|
|
1880
|
+
overflow: "visible"
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
return {
|
|
1884
|
+
...placement,
|
|
1885
|
+
position: "static",
|
|
1886
|
+
inset: "auto",
|
|
1887
|
+
float: "none",
|
|
1888
|
+
// The size has to be stated, and this is the line the whole subtree render
|
|
1889
|
+
// stands on. The clone is laid out inside a `foreignObject` the size of
|
|
1890
|
+
// the CROP, and a block element with no width of its own fills its
|
|
1891
|
+
// parent: a 1440px section became 163px wide and the page reflowed into a
|
|
1892
|
+
// column, which is why the hero came back as an empty panel rather than a
|
|
1893
|
+
// misplaced one. `documentElement` was pinned the same way from the start,
|
|
1894
|
+
// which is why nobody met this until the root became something else.
|
|
1895
|
+
// Border-box, because a measured rect is a border box.
|
|
1896
|
+
boxSizing: "border-box",
|
|
1897
|
+
width: `${rootRect.width}px`,
|
|
1898
|
+
minHeight: `${rootRect.height}px`
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
function withDeadline(work, ms, view) {
|
|
1902
|
+
return Promise.race([
|
|
1903
|
+
work,
|
|
1904
|
+
new Promise((resolve) => {
|
|
1905
|
+
try {
|
|
1906
|
+
view.setTimeout(() => resolve(null), ms);
|
|
1907
|
+
} catch {
|
|
1908
|
+
}
|
|
1909
|
+
})
|
|
1910
|
+
]);
|
|
1911
|
+
}
|
|
1704
1912
|
function isTextualField(el) {
|
|
1705
1913
|
if (el instanceof HTMLTextAreaElement) return true;
|
|
1706
1914
|
if (!(el instanceof HTMLInputElement)) return false;
|
|
@@ -1710,6 +1918,57 @@ function isTextualField(el) {
|
|
|
1710
1918
|
function isCapturable(node) {
|
|
1711
1919
|
return !(node instanceof Element && node.hasAttribute(HOST_ATTRIBUTE));
|
|
1712
1920
|
}
|
|
1921
|
+
function repairAutoMargins(live, clone, view) {
|
|
1922
|
+
let repaired = 0;
|
|
1923
|
+
const walk = (liveNode, cloneNode2, depth) => {
|
|
1924
|
+
if (depth > 64 || repaired > 2e3) return;
|
|
1925
|
+
const liveKids = liveNode.children;
|
|
1926
|
+
const cloneKids = cloneNode2.children;
|
|
1927
|
+
let l = 0;
|
|
1928
|
+
let c = 0;
|
|
1929
|
+
while (l < liveKids.length && c < cloneKids.length) {
|
|
1930
|
+
const liveKid = liveKids[l];
|
|
1931
|
+
const cloneKid = cloneKids[c];
|
|
1932
|
+
if (liveKid.tagName !== cloneKid.tagName) {
|
|
1933
|
+
l++;
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
if (applyUsedMargins(liveNode, liveKid, cloneKid, view)) repaired++;
|
|
1937
|
+
walk(liveKid, cloneKid, depth + 1);
|
|
1938
|
+
l++;
|
|
1939
|
+
c++;
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
try {
|
|
1943
|
+
walk(live, clone, 0);
|
|
1944
|
+
} catch {
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
var BLOCK_DISPLAYS = /* @__PURE__ */ new Set(["block", "flow-root", "list-item", "table-cell"]);
|
|
1948
|
+
function applyUsedMargins(liveParent, live, clone, view) {
|
|
1949
|
+
try {
|
|
1950
|
+
const style = view.getComputedStyle(live);
|
|
1951
|
+
if (style.marginLeft !== "0px" || style.marginRight !== "0px") return false;
|
|
1952
|
+
if (style.position !== "static" && style.position !== "relative") return false;
|
|
1953
|
+
if (style.float !== "none") return false;
|
|
1954
|
+
const parentStyle = view.getComputedStyle(liveParent);
|
|
1955
|
+
if (!BLOCK_DISPLAYS.has(parentStyle.display)) return false;
|
|
1956
|
+
const rect = live.getBoundingClientRect();
|
|
1957
|
+
const parentRect = liveParent.getBoundingClientRect();
|
|
1958
|
+
const contentLeft = parentRect.left + parseFloat(parentStyle.borderLeftWidth || "0") + parseFloat(parentStyle.paddingLeft || "0");
|
|
1959
|
+
const contentRight = parentRect.right - parseFloat(parentStyle.borderRightWidth || "0") - parseFloat(parentStyle.paddingRight || "0");
|
|
1960
|
+
const left = rect.left - contentLeft;
|
|
1961
|
+
const right = contentRight - rect.right;
|
|
1962
|
+
if (left < 0.5 || right < 0.5 || Math.abs(left - right) > 1) return false;
|
|
1963
|
+
const target = clone;
|
|
1964
|
+
if (!target.style) return false;
|
|
1965
|
+
target.style.setProperty("margin-left", `${left}px`);
|
|
1966
|
+
target.style.setProperty("margin-right", `${right}px`);
|
|
1967
|
+
return true;
|
|
1968
|
+
} catch {
|
|
1969
|
+
return false;
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1713
1972
|
function maskClone(root) {
|
|
1714
1973
|
const doc = root.ownerDocument ?? root;
|
|
1715
1974
|
const view = doc.defaultView;
|
|
@@ -1742,7 +2001,8 @@ function maskClone(root) {
|
|
|
1742
2001
|
el.value = masked;
|
|
1743
2002
|
}
|
|
1744
2003
|
const placeholder = el.getAttribute("placeholder");
|
|
1745
|
-
if (placeholder)
|
|
2004
|
+
if (placeholder)
|
|
2005
|
+
el.setAttribute("placeholder", maskPiiText(placeholder));
|
|
1746
2006
|
}
|
|
1747
2007
|
}
|
|
1748
2008
|
node = walker.nextNode();
|
|
@@ -1782,6 +2042,8 @@ async function captureElementScreenshot(el) {
|
|
|
1782
2042
|
const doc = el.ownerDocument;
|
|
1783
2043
|
const view = doc.defaultView;
|
|
1784
2044
|
if (!view || !doc.documentElement || !el.isConnected) return null;
|
|
2045
|
+
await waitUntilSettled(el, view);
|
|
2046
|
+
if (!el.isConnected) return null;
|
|
1785
2047
|
const targetRectRaw = el.getBoundingClientRect();
|
|
1786
2048
|
const targetRect = {
|
|
1787
2049
|
left: targetRectRaw.left,
|
|
@@ -1790,31 +2052,54 @@ async function captureElementScreenshot(el) {
|
|
|
1790
2052
|
height: targetRectRaw.height
|
|
1791
2053
|
};
|
|
1792
2054
|
if (targetRect.width <= 0 || targetRect.height <= 0) return null;
|
|
1793
|
-
const
|
|
2055
|
+
const idealCrop = computeCropRect(
|
|
2056
|
+
targetRect,
|
|
2057
|
+
view.innerWidth,
|
|
2058
|
+
view.innerHeight
|
|
2059
|
+
);
|
|
2060
|
+
if (idealCrop.width <= 0 || idealCrop.height <= 0) return null;
|
|
2061
|
+
const renderRoot = findRenderRoot(el, idealCrop, doc.documentElement, view);
|
|
2062
|
+
const rootRect = renderRoot.getBoundingClientRect();
|
|
2063
|
+
const rootBox = {
|
|
2064
|
+
left: rootRect.left,
|
|
2065
|
+
top: rootRect.top,
|
|
2066
|
+
width: rootRect.width,
|
|
2067
|
+
height: rootRect.height
|
|
2068
|
+
};
|
|
2069
|
+
const cropRect = clipCrop(idealCrop, rootBox);
|
|
1794
2070
|
if (cropRect.width <= 0 || cropRect.height <= 0) return null;
|
|
1795
2071
|
const scale = outputScale(view, cropRect);
|
|
1796
|
-
const shiftX = -(cropRect.left
|
|
1797
|
-
const shiftY = -(cropRect.top
|
|
1798
|
-
const canvas = await
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
2072
|
+
const shiftX = -(cropRect.left - rootRect.left);
|
|
2073
|
+
const shiftY = -(cropRect.top - rootRect.top);
|
|
2074
|
+
const canvas = await withDeadline(
|
|
2075
|
+
domToCanvas(renderRoot, {
|
|
2076
|
+
width: cropRect.width,
|
|
2077
|
+
height: cropRect.height,
|
|
2078
|
+
scale,
|
|
2079
|
+
backgroundColor: backgroundUnder(renderRoot, cropRect, doc, view),
|
|
2080
|
+
// Per resource, not per capture. `RENDER_TIMEOUT_MS` is the total,
|
|
2081
|
+
// and it is the race around this call that enforces it.
|
|
2082
|
+
timeout: RESOURCE_TIMEOUT_MS,
|
|
2083
|
+
filter: isCapturable,
|
|
2084
|
+
onCloneNode: (cloned) => {
|
|
2085
|
+
if (cloned instanceof Element) {
|
|
2086
|
+
repairAutoMargins(renderRoot, cloned, view);
|
|
2087
|
+
}
|
|
2088
|
+
maskClone(cloned);
|
|
2089
|
+
},
|
|
2090
|
+
// No `preferredFormat`. Asking for woff2 sounds like asking for the
|
|
2091
|
+
// smaller file and is really a filter: a face whose chosen source is
|
|
2092
|
+
// not woff2 is dropped rather than downgraded, and the text renders in
|
|
2093
|
+
// the browser's serif fallback. On our own hero that turned the button
|
|
2094
|
+
// label into Times (D1258). Let the library take whatever each face
|
|
2095
|
+
// actually offers.
|
|
2096
|
+
font: {},
|
|
2097
|
+
style: renderRootStyle(renderRoot, rootBox, doc, view, shiftX, shiftY)
|
|
2098
|
+
}),
|
|
2099
|
+
RENDER_TIMEOUT_MS,
|
|
2100
|
+
view
|
|
2101
|
+
);
|
|
2102
|
+
if (!canvas) return null;
|
|
1818
2103
|
const ctx = canvas.getContext("2d");
|
|
1819
2104
|
if (!ctx) return null;
|
|
1820
2105
|
drawOutline(ctx, targetRect, cropRect, scale);
|
|
@@ -1834,48 +2119,91 @@ async function captureElementScreenshot(el) {
|
|
|
1834
2119
|
var PICKER_HOST_ID = "arcy-picker";
|
|
1835
2120
|
var BANNER_CLASS = "arcy-picker-banner";
|
|
1836
2121
|
var HIGHLIGHT_CLASS = "arcy-picker-highlight";
|
|
2122
|
+
var INK = "#17131A";
|
|
2123
|
+
var SURFACE = "#201B23";
|
|
2124
|
+
var HAIRLINE = "#332B33";
|
|
2125
|
+
var TEXT = "#F3EEE8";
|
|
2126
|
+
var MUTED = "#9E959A";
|
|
2127
|
+
var ACCENT = "#FF5A3C";
|
|
2128
|
+
var ON_ACCENT = "#17131A";
|
|
1837
2129
|
var PICKER_CSS = `
|
|
1838
2130
|
.${BANNER_CLASS} {
|
|
1839
2131
|
display: flex;
|
|
1840
2132
|
align-items: center;
|
|
1841
|
-
gap:
|
|
1842
|
-
padding:
|
|
2133
|
+
gap: 12px;
|
|
2134
|
+
padding: 7px 8px 7px 16px;
|
|
1843
2135
|
border-radius: 999px;
|
|
1844
|
-
background:
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
2136
|
+
background: ${INK};
|
|
2137
|
+
border: 1px solid ${HAIRLINE};
|
|
2138
|
+
color: ${TEXT};
|
|
2139
|
+
font: 400 14px/1.2 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
2140
|
+
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.38);
|
|
1848
2141
|
white-space: nowrap;
|
|
1849
2142
|
}
|
|
1850
|
-
.${BANNER_CLASS}
|
|
2143
|
+
.${BANNER_CLASS}__mark {
|
|
2144
|
+
display: flex;
|
|
2145
|
+
align-items: baseline;
|
|
2146
|
+
gap: 7px;
|
|
2147
|
+
}
|
|
2148
|
+
.${BANNER_CLASS}__brand {
|
|
1851
2149
|
font-weight: 600;
|
|
1852
|
-
|
|
2150
|
+
letter-spacing: 0.02em;
|
|
1853
2151
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2152
|
+
/* The label role from DESIGN.md: 600, 12px, uppercase, tracked wide. */
|
|
2153
|
+
.${BANNER_CLASS}__label {
|
|
2154
|
+
font-size: 12px;
|
|
2155
|
+
font-weight: 600;
|
|
2156
|
+
letter-spacing: 0.14em;
|
|
2157
|
+
text-transform: uppercase;
|
|
2158
|
+
color: ${MUTED};
|
|
2159
|
+
}
|
|
2160
|
+
/* Pick and Browse are one control with two states, not two buttons that
|
|
2161
|
+
* happen to disagree. The old bar drew both as outlines and coloured whichever
|
|
2162
|
+
* was on, which reads as two things you can press rather than as a switch. */
|
|
2163
|
+
.${BANNER_CLASS}__modes {
|
|
2164
|
+
display: flex;
|
|
2165
|
+
align-items: center;
|
|
2166
|
+
gap: 2px;
|
|
2167
|
+
padding: 3px;
|
|
2168
|
+
border-radius: 999px;
|
|
2169
|
+
background: ${SURFACE};
|
|
1859
2170
|
}
|
|
1860
2171
|
.${BANNER_CLASS} button {
|
|
1861
2172
|
appearance: none;
|
|
1862
|
-
border: 1px solid
|
|
2173
|
+
border: 1px solid transparent;
|
|
1863
2174
|
border-radius: 999px;
|
|
1864
2175
|
background: transparent;
|
|
1865
|
-
color:
|
|
2176
|
+
color: ${MUTED};
|
|
1866
2177
|
font: inherit;
|
|
1867
|
-
|
|
2178
|
+
font-weight: 500;
|
|
2179
|
+
padding: 5px 14px;
|
|
1868
2180
|
cursor: pointer;
|
|
2181
|
+
transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
1869
2182
|
}
|
|
1870
2183
|
.${BANNER_CLASS} button:hover {
|
|
1871
|
-
|
|
2184
|
+
color: ${TEXT};
|
|
2185
|
+
}
|
|
2186
|
+
.${BANNER_CLASS} button:focus-visible {
|
|
2187
|
+
outline: 2px solid ${ACCENT};
|
|
2188
|
+
outline-offset: 2px;
|
|
1872
2189
|
}
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
2190
|
+
/* The one filled control on the bar (DESIGN.md, one accent fill per surface). */
|
|
2191
|
+
.${BANNER_CLASS}__modes button.is-active {
|
|
2192
|
+
background: ${ACCENT};
|
|
2193
|
+
color: ${ON_ACCENT};
|
|
2194
|
+
font-weight: 600;
|
|
2195
|
+
}
|
|
2196
|
+
.${BANNER_CLASS}__modes button.is-active:hover {
|
|
2197
|
+
color: ${ON_ACCENT};
|
|
2198
|
+
}
|
|
2199
|
+
.${BANNER_CLASS}__exit {
|
|
2200
|
+
border-color: ${HAIRLINE} !important;
|
|
2201
|
+
}
|
|
2202
|
+
.${BANNER_CLASS}__exit:hover {
|
|
2203
|
+
background: ${SURFACE};
|
|
1876
2204
|
}
|
|
1877
2205
|
.${HIGHLIGHT_CLASS} {
|
|
1878
|
-
border-radius:
|
|
2206
|
+
border-radius: 4px;
|
|
1879
2207
|
}
|
|
1880
2208
|
`;
|
|
1881
2209
|
function applySurvivalStyle(el, pointerEvents, offsets = []) {
|
|
@@ -1887,8 +2215,13 @@ function applySurvivalStyle(el, pointerEvents, offsets = []) {
|
|
|
1887
2215
|
}
|
|
1888
2216
|
var HIGHLIGHT_STYLE = [
|
|
1889
2217
|
["box-sizing", "border-box"],
|
|
1890
|
-
[
|
|
1891
|
-
|
|
2218
|
+
[
|
|
2219
|
+
"border",
|
|
2220
|
+
// ADR 0171's accent border rule: an edge, not a fill. The tint behind it
|
|
2221
|
+
// is what keeps a thin outline readable on a busy page.
|
|
2222
|
+
`2px solid ${ACCENT}`
|
|
2223
|
+
],
|
|
2224
|
+
["background", "rgba(255, 90, 60, 0.1)"]
|
|
1892
2225
|
];
|
|
1893
2226
|
var BANNER_OFFSET = [
|
|
1894
2227
|
["left", "50%"],
|
|
@@ -1897,10 +2230,6 @@ var BANNER_OFFSET = [
|
|
|
1897
2230
|
var BANNER_STYLE = [
|
|
1898
2231
|
["transform", "translateX(-50%)"]
|
|
1899
2232
|
];
|
|
1900
|
-
function statusLabel(target) {
|
|
1901
|
-
const { text, tag } = target.core;
|
|
1902
|
-
return text ? `Sent "${text}" to ARCY` : `Sent <${tag}> to ARCY`;
|
|
1903
|
-
}
|
|
1904
2233
|
function createPickerOverlay(context) {
|
|
1905
2234
|
let shell = createShell({ id: PICKER_HOST_ID });
|
|
1906
2235
|
if (!shell) return null;
|
|
@@ -1923,28 +2252,36 @@ function createPickerOverlay(context) {
|
|
|
1923
2252
|
for (const [property, value] of BANNER_STYLE) {
|
|
1924
2253
|
banner.style.setProperty(property, value);
|
|
1925
2254
|
}
|
|
1926
|
-
const
|
|
1927
|
-
|
|
1928
|
-
|
|
2255
|
+
const mark = doc.createElement("span");
|
|
2256
|
+
mark.className = `${BANNER_CLASS}__mark`;
|
|
2257
|
+
const brand = doc.createElement("span");
|
|
2258
|
+
brand.className = `${BANNER_CLASS}__brand`;
|
|
2259
|
+
brand.textContent = "ARCY";
|
|
2260
|
+
const label = doc.createElement("span");
|
|
2261
|
+
label.className = `${BANNER_CLASS}__label`;
|
|
2262
|
+
label.textContent = "Element picker";
|
|
2263
|
+
mark.append(brand, label);
|
|
2264
|
+
const modes = doc.createElement("span");
|
|
2265
|
+
modes.className = `${BANNER_CLASS}__modes`;
|
|
2266
|
+
applySurvivalStyle(modes, "auto");
|
|
2267
|
+
modes.style.setProperty("position", "relative", "important");
|
|
1929
2268
|
const pickButton = doc.createElement("button");
|
|
1930
2269
|
pickButton.type = "button";
|
|
1931
2270
|
pickButton.textContent = "Pick";
|
|
1932
2271
|
const browseButton = doc.createElement("button");
|
|
1933
2272
|
browseButton.type = "button";
|
|
1934
2273
|
browseButton.textContent = "Browse";
|
|
1935
|
-
|
|
1936
|
-
status.className = `${BANNER_CLASS}__status`;
|
|
1937
|
-
status.textContent = "Click the element you want this step to target.";
|
|
2274
|
+
modes.append(pickButton, browseButton);
|
|
1938
2275
|
const exitButton = doc.createElement("button");
|
|
1939
2276
|
exitButton.type = "button";
|
|
2277
|
+
exitButton.className = `${BANNER_CLASS}__exit`;
|
|
1940
2278
|
exitButton.textContent = "Exit";
|
|
1941
|
-
banner.append(
|
|
2279
|
+
banner.append(mark, modes, exitButton);
|
|
1942
2280
|
shell.root.append(highlight, banner);
|
|
1943
2281
|
function setMode(next) {
|
|
1944
2282
|
mode = next;
|
|
1945
2283
|
pickButton.classList.toggle("is-active", next === "pick");
|
|
1946
2284
|
browseButton.classList.toggle("is-active", next === "browse");
|
|
1947
|
-
status.textContent = next === "pick" ? "Click the element you want this step to target." : "Browsing. Clicks work normally; switch back to pick.";
|
|
1948
2285
|
if (next === "browse") hideHighlight();
|
|
1949
2286
|
}
|
|
1950
2287
|
function hideHighlight() {
|
|
@@ -2027,7 +2364,6 @@ function createPickerOverlay(context) {
|
|
|
2027
2364
|
const ranked = rankSelectors(el);
|
|
2028
2365
|
fingerprint.selector = ranked.autoSelector;
|
|
2029
2366
|
fingerprint.selectorCandidates = ranked.candidates;
|
|
2030
|
-
status.textContent = statusLabel(fingerprint);
|
|
2031
2367
|
hovered = el;
|
|
2032
2368
|
moveHighlight(el);
|
|
2033
2369
|
let matchCount = 0;
|
|
@@ -2038,17 +2374,29 @@ function createPickerOverlay(context) {
|
|
|
2038
2374
|
context.onPick(fingerprint, matchCount);
|
|
2039
2375
|
if (context.screenshotsEnabled === false) return;
|
|
2040
2376
|
const token = ++screenshotToken;
|
|
2041
|
-
|
|
2042
|
-
if (
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2377
|
+
afterNextPaint(() => {
|
|
2378
|
+
if (destroyed || token !== screenshotToken) return;
|
|
2379
|
+
void captureElementScreenshot(el).then((screenshot) => {
|
|
2380
|
+
if (!screenshot || destroyed || token !== screenshotToken) return;
|
|
2381
|
+
try {
|
|
2382
|
+
context.onScreenshot?.(screenshot);
|
|
2383
|
+
} catch {
|
|
2384
|
+
}
|
|
2385
|
+
});
|
|
2047
2386
|
});
|
|
2048
2387
|
} catch (error) {
|
|
2049
2388
|
warn(`The element picker could not read that element. ${String(error)}`);
|
|
2050
2389
|
}
|
|
2051
2390
|
};
|
|
2391
|
+
function afterNextPaint(fn) {
|
|
2392
|
+
const view = doc.defaultView;
|
|
2393
|
+
const raf = view?.requestAnimationFrame;
|
|
2394
|
+
if (typeof raf !== "function") {
|
|
2395
|
+
setTimeout(fn, 0);
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
raf.call(view, () => raf.call(view, fn));
|
|
2399
|
+
}
|
|
2052
2400
|
function isEditable(target) {
|
|
2053
2401
|
if (!(target instanceof Element)) return false;
|
|
2054
2402
|
const tag = target.tagName;
|
package/package.json
CHANGED