Simplify the hover replacement function (#1535)

Simplify the hover replacement function, which has been borrowed from postcss-pseudo-classes

Note: 'parses nested commas in selectors correctly' was failing after this PR, however I don't think that the previous behaviour was desirable, so have added a new test to formalize this expectation
This commit is contained in:
Eoghan Murray
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 67657a8710
commit 2dbfe3b18a
3 changed files with 18 additions and 53 deletions

View File

@@ -0,0 +1,6 @@
---
"rrweb-snapshot": patch
"rrweb": patch
---
Slight simplification to how we replace :hover after #1458

View File

@@ -17,7 +17,7 @@ const mediaSelectorPlugin: AcceptedPlugin = {
},
};
// Adapted from https://github.com/giuseppeg/postcss-pseudo-classes/blob/master/index.js
// Simplified from https://github.com/giuseppeg/postcss-pseudo-classes/blob/master/index.js
const pseudoClassPlugin: AcceptedPlugin = {
postcssPlugin: 'postcss-hover-classes',
prepare: function () {
@@ -28,58 +28,9 @@ const pseudoClassPlugin: AcceptedPlugin = {
return;
}
fixed.push(rule);
rule.selectors.forEach(function (selector) {
if (!selector.includes(':')) {
return;
}
const selectorParts = selector.replace(/\n/g, ' ').split(' ');
const pseudoedSelectorParts: string[] = [];
selectorParts.forEach(function (selectorPart) {
const pseudos = selectorPart.match(/::?([^:]+)/g);
if (!pseudos) {
pseudoedSelectorParts.push(selectorPart);
return;
}
const baseSelector = selectorPart.substr(
0,
selectorPart.length - pseudos.join('').length,
);
const classPseudos = pseudos.map(function (pseudo) {
const pseudoToCheck = pseudo.replace(/\(.*/g, '');
if (pseudoToCheck !== ':hover') {
return pseudo;
}
// Ignore pseudo-elements!
if (pseudo.match(/^::/)) {
return pseudo;
}
// Kill the colon
pseudo = pseudo.substr(1);
// Replace left and right parens
pseudo = pseudo.replace(/\(/g, '\\(');
pseudo = pseudo.replace(/\)/g, '\\)');
return '.' + '\\:' + pseudo;
});
pseudoedSelectorParts.push(baseSelector + classPseudos.join(''));
});
addSelector(pseudoedSelectorParts.join(' '));
function addSelector(newSelector: string) {
if (newSelector && newSelector !== selector) {
rule.selector += ',\n' + newSelector;
}
if (selector.includes(':hover')) {
rule.selector += ',\n' + selector.replace(/:hover/g, '.\\:hover');
}
});
},

View File

@@ -50,10 +50,18 @@ describe('css parser', () => {
describe('pseudoClassPlugin', () => {
it('parses nested commas in selectors correctly', () => {
const cssText =
'body > ul :is(li:not(:first-of-type) a:hover, li:not(:first-of-type).active a) {background: red;}';
'body > ul :is(li:not(:first-of-type) a.current, li:not(:first-of-type).active a) {background: red;}';
expect(parse(pseudoClassPlugin, cssText)).toEqual(cssText);
});
it("doesn't ignore :hover within :is brackets", () => {
const cssText =
'body > ul :is(li:not(:first-of-type) a:hover, li:not(:first-of-type).active a) {background: red;}';
expect(parse(pseudoClassPlugin, cssText))
.toEqual(`body > ul :is(li:not(:first-of-type) a:hover, li:not(:first-of-type).active a),
body > ul :is(li:not(:first-of-type) a.\\:hover, li:not(:first-of-type).active a) {background: red;}`);
});
it('should parse selector with comma nested inside ()', () => {
const cssText =
'[_nghost-ng-c4172599085]:not(.fit-content).aim-select:hover:not(:disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--invalid, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--active) { border-color: rgb(84, 84, 84); }';