@percy/dom 1.32.3-beta.0 → 1.32.3-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.js +75 -3
- package/package.json +2 -2
package/dist/bundle.js
CHANGED
|
@@ -855,6 +855,49 @@
|
|
|
855
855
|
} catch (e) {/* selector unsupported in this scope */}
|
|
856
856
|
});
|
|
857
857
|
}
|
|
858
|
+
|
|
859
|
+
// Walk the LIVE document and every shadow root (open, or closed via the CDP
|
|
860
|
+
// WeakMap) WITHOUT relying on data-percy-shadow-host markers. walkShadowDOM
|
|
861
|
+
// descends through those markers, but they are only stamped later during
|
|
862
|
+
// cloning — so during this pre-clone marking pass it cannot reach shadow
|
|
863
|
+
// content. We descend via the live shadowRoot directly instead.
|
|
864
|
+
function eachScopeIncludingShadow(root, visit) {
|
|
865
|
+
if (!root || typeof root.querySelectorAll !== 'function') return;
|
|
866
|
+
visit(root);
|
|
867
|
+
for (const el of root.querySelectorAll('*')) {
|
|
868
|
+
const shadow = getShadowRoot(el);
|
|
869
|
+
if (shadow) eachScopeIncludingShadow(shadow, visit);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Auto-detect open native popovers page-wide, INCLUDING inside shadow roots.
|
|
874
|
+
// `:popover-open` is an unambiguous serialize-time state (like :checked /
|
|
875
|
+
// :disabled), so it is stamped automatically rather than only on
|
|
876
|
+
// pseudoClassEnabledElements. The renderer's popover-element-helper already
|
|
877
|
+
// re-opens any [popover][data-percy-popover-open] across shadow boundaries;
|
|
878
|
+
// without this stamp a popover open at snapshot time renders hidden via the
|
|
879
|
+
// UA `[popover]:not(:popover-open){display:none}` rule. If `:popover-open`
|
|
880
|
+
// is unsupported the selector throws — we stop querying and warn once.
|
|
881
|
+
function markOpenPopovers(ctx) {
|
|
882
|
+
let supported = true;
|
|
883
|
+
eachScopeIncludingShadow(ctx.dom, scope => {
|
|
884
|
+
if (!supported) return;
|
|
885
|
+
// Only the `:popover-open` SELECTOR can legitimately throw a SyntaxError
|
|
886
|
+
// (engines without popover support). Scope the try to the query and
|
|
887
|
+
// materialize the matches; stamp OUTSIDE the try. Otherwise a throw from
|
|
888
|
+
// stampOnce mid-iteration would be misreported as "unsupported" AND would
|
|
889
|
+
// silently skip every remaining scope.
|
|
890
|
+
let matches;
|
|
891
|
+
try {
|
|
892
|
+
matches = scope.querySelectorAll('[popover]:popover-open');
|
|
893
|
+
} catch (e) {
|
|
894
|
+
supported = false;
|
|
895
|
+
ctx.warnings.add('Browser does not support :popover-open pseudo-class.');
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
for (const el of matches) stampOnce(ctx, el, POPOVER_OPEN_ATTR, 'true');
|
|
899
|
+
});
|
|
900
|
+
}
|
|
858
901
|
function isPopoverOpen(ctx, element) {
|
|
859
902
|
try {
|
|
860
903
|
return element.matches(':popover-open');
|
|
@@ -959,6 +1002,7 @@
|
|
|
959
1002
|
function markPseudoClassElements(ctx, config) {
|
|
960
1003
|
ctx._liveMutations = [];
|
|
961
1004
|
markInteractiveStates(ctx);
|
|
1005
|
+
markOpenPopovers(ctx);
|
|
962
1006
|
if (config) getElementsToProcess(ctx, config, true);
|
|
963
1007
|
}
|
|
964
1008
|
|
|
@@ -986,10 +1030,26 @@
|
|
|
986
1030
|
return decls.join(' ');
|
|
987
1031
|
}
|
|
988
1032
|
|
|
1033
|
+
// Resolve a nested rule's selector against its parent per CSS Nesting.
|
|
1034
|
+
// The CSSOM serializes nested selectors with the implicit nesting selector
|
|
1035
|
+
// made explicit, so every nested selector — including each item of a
|
|
1036
|
+
// selector list — carries its own `&`. Replacing every `&` with :is(parent)
|
|
1037
|
+
// therefore fully scopes the rule. Without this, a bare top-level `&`
|
|
1038
|
+
// resolves to :root on engines that support nesting, so component-scoped
|
|
1039
|
+
// interactive-state rules leak page-wide (PER-9775). :is(...) preserves the
|
|
1040
|
+
// parent's grouping/specificity.
|
|
1041
|
+
function resolveNestedSelector(selectorText, parentSelector) {
|
|
1042
|
+
if (!parentSelector) return selectorText;
|
|
1043
|
+
return selectorText.replace(/&/g, `:is(${parentSelector})`);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
989
1046
|
// Walk a CSSRule list yielding every reachable style rule. Nested rules
|
|
990
1047
|
// inside @media/@layer/@supports are emitted with the at-rule prelude
|
|
991
1048
|
// preserved as a wrapper string; flat-emitting would drop the guard.
|
|
992
|
-
|
|
1049
|
+
// `parentSelector` carries the enclosing style rule's (already-resolved)
|
|
1050
|
+
// selector down through native CSS nesting so `&`-relative children are
|
|
1051
|
+
// resolved against it rather than leaking as a bare top-level `&`.
|
|
1052
|
+
function walkCSSRules(ruleList, parentSelector = null) {
|
|
993
1053
|
const result = [];
|
|
994
1054
|
for (let i = 0; i < ruleList.length; i++) {
|
|
995
1055
|
const rule = ruleList[i];
|
|
@@ -998,7 +1058,19 @@
|
|
|
998
1058
|
var _rule$media;
|
|
999
1059
|
const conditionText = rule.conditionText || ((_rule$media = rule.media) === null || _rule$media === void 0 ? void 0 : _rule$media.mediaText);
|
|
1000
1060
|
const atRulePrelude = conditionText && rule.cssText ? rule.cssText.split('{')[0].trim() : null;
|
|
1001
|
-
|
|
1061
|
+
// An at-rule (@media/@layer/@supports) keeps the current parent scope
|
|
1062
|
+
// and is re-applied as a wrapper. A style rule with nested children
|
|
1063
|
+
// (native CSS nesting) becomes the scope for its children — resolve
|
|
1064
|
+
// its own selector against any outer parent first so deeper nesting
|
|
1065
|
+
// composes correctly.
|
|
1066
|
+
// Default to the current scope (also covers at-rules and nested-decl
|
|
1067
|
+
// rules that have no selector of their own). A style rule with nested
|
|
1068
|
+
// children resolves its own selector first and becomes the new scope.
|
|
1069
|
+
let childParent = parentSelector;
|
|
1070
|
+
if (!atRulePrelude && rule.selectorText) {
|
|
1071
|
+
childParent = resolveNestedSelector(rule.selectorText, parentSelector);
|
|
1072
|
+
}
|
|
1073
|
+
for (const inner of walkCSSRules(rule.cssRules, childParent)) {
|
|
1002
1074
|
if (atRulePrelude && inner.selectorText) {
|
|
1003
1075
|
result.push({
|
|
1004
1076
|
selectorText: inner.selectorText,
|
|
@@ -1014,7 +1086,7 @@
|
|
|
1014
1086
|
// @charset, @counter-style, etc.) are skipped — they can't contain
|
|
1015
1087
|
// interactive pseudos.
|
|
1016
1088
|
result.push({
|
|
1017
|
-
selectorText: rule.selectorText,
|
|
1089
|
+
selectorText: resolveNestedSelector(rule.selectorText, parentSelector),
|
|
1018
1090
|
style: rule.style,
|
|
1019
1091
|
wrapper: null
|
|
1020
1092
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@percy/dom",
|
|
3
|
-
"version": "1.32.3-beta.
|
|
3
|
+
"version": "1.32.3-beta.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"interactor.js": "^2.0.0-beta.10"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "f2029c138431bb176dd41ea0d1df1f3a3120cc8d"
|
|
39
39
|
}
|