Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Css fixes main #58681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 72 additions & 33 deletions packages/compiler/src/shadow_css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -734,16 +734,18 @@ export class ShadowCss {
_polyfillHostRe.lastIndex = 0;
if (_polyfillHostRe.test(selector)) {
const replaceBy = `[${hostSelector}]`;
return selector
.replace(_polyfillHostNoCombinatorReGlobal, (_hnc, selector) => {
let result = selector;
while (result.match(_polyfillHostNoCombinatorRe)) {
result = result.replace(_polyfillHostNoCombinatorRe, (_hnc, selector) => {
return selector.replace(
/([^:\)]*)(:*)(.*)/,
(_: string, before: string, colon: string, after: string) => {
return before + replaceBy + colon + after;
},
);
})
.replace(_polyfillHostRe, replaceBy + ' ');
});
}
return result.replace(_polyfillHostRe, replaceBy);
}

return scopeSelector + ' ' + selector;
Expand All @@ -765,7 +767,7 @@ export class ShadowCss {
const isRe = /\[is=([^\]]*)\]/g;
scopeSelector = scopeSelector.replace(isRe, (_: string, ...parts: string[]) => parts[0]);

const attrName = '[' + scopeSelector + ']';
const attrName = `[${scopeSelector}]`;

const _scopeSelectorPart = (p: string) => {
let scopedP = p.trim();
Expand All @@ -776,15 +778,15 @@ export class ShadowCss {

if (p.includes(_polyfillHostNoCombinator)) {
scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector);
if (_polyfillHostNoCombinatorWithinPseudoFunction.test(p)) {
const [_, before, colon, after] = scopedP.match(/([^:]*)(:*)(.*)/)!;
if (!p.match(_polyfillHostNoCombinatorOutsidePseudoFunction)) {
const [_, before, colon, after] = scopedP.match(/([^:]*)(:*)([\s\S]*)/)!;
scopedP = before + attrName + colon + after;
}
} else {
// remove :host since it should be unnecessary
const t = p.replace(_polyfillHostRe, '');
if (t.length > 0) {
const matches = t.match(/([^:]*)(:*)(.*)/);
const matches = t.match(/([^:]*)(:*)([\s\S]*)/);
if (matches) {
scopedP = matches[1] + attrName + matches[2] + matches[3];
}
Expand All @@ -800,30 +802,67 @@ export class ShadowCss {
const _pseudoFunctionAwareScopeSelectorPart = (selectorPart: string) => {
let scopedPart = '';

const cssPrefixWithPseudoSelectorFunctionMatch = selectorPart.match(
_cssPrefixWithPseudoSelectorFunction,
);
if (cssPrefixWithPseudoSelectorFunctionMatch) {
const [cssPseudoSelectorFunction] = cssPrefixWithPseudoSelectorFunctionMatch;
// Collect all outer `:where()` and `:is()` selectors,
// counting parenthesis to keep nested selectors intact.
const pseudoSelectorParts = [];
let pseudoSelectorMatch: RegExpExecArray | null;
while (
(pseudoSelectorMatch = _cssPrefixWithPseudoSelectorFunction.exec(selectorPart)) !== null
) {
let openedBrackets = 1;
let index = _cssPrefixWithPseudoSelectorFunction.lastIndex;

while (index < selectorPart.length) {
const currentSymbol = selectorPart[index];
index++;
if (currentSymbol === '(') {
openedBrackets++;
continue;
}
if (currentSymbol === ')') {
openedBrackets--;
if (openedBrackets === 0) {
break;
}
continue;
}
}

// Unwrap the pseudo selector to scope its contents.
// For example,
// - `:where(selectorToScope)` -> `selectorToScope`;
// - `:is(.foo, .bar)` -> `.foo, .bar`.
const selectorToScope = selectorPart.slice(cssPseudoSelectorFunction.length, -1);
pseudoSelectorParts.push(
`${pseudoSelectorMatch[0]}${selectorPart.slice(_cssPrefixWithPseudoSelectorFunction.lastIndex, index)}`,
);
_cssPrefixWithPseudoSelectorFunction.lastIndex = index;
}

if (selectorToScope.includes(_polyfillHostNoCombinator)) {
this._shouldScopeIndicator = true;
}
// If selector consists of only `:where()` and `:is()` on the outer level
// scope those pseudo-selectors individually, otherwise scope the whole
// selector.
if (pseudoSelectorParts.join('') === selectorPart) {
scopedPart = pseudoSelectorParts
.map((selectorPart) => {
const [cssPseudoSelectorFunction] =
selectorPart.match(_cssPrefixWithPseudoSelectorFunction) ?? [];

// Unwrap the pseudo selector to scope its contents.
// For example,
// - `:where(selectorToScope)` -> `selectorToScope`;
// - `:is(.foo, .bar)` -> `.foo, .bar`.
const selectorToScope = selectorPart.slice(cssPseudoSelectorFunction?.length, -1);

if (selectorToScope.includes(_polyfillHostNoCombinator)) {
this._shouldScopeIndicator = true;
}

const scopedInnerPart = this._scopeSelector({
selector: selectorToScope,
scopeSelector,
hostSelector,
});
const scopedInnerPart = this._scopeSelector({
selector: selectorToScope,
scopeSelector,
hostSelector,
});

// Put the result back into the pseudo selector function.
scopedPart = `${cssPseudoSelectorFunction}${scopedInnerPart})`;
// Put the result back into the pseudo selector function.
return `${cssPseudoSelectorFunction}${scopedInnerPart})`;
})
.join('');
} else {
this._shouldScopeIndicator =
this._shouldScopeIndicator || selectorPart.includes(_polyfillHostNoCombinator);
Expand Down Expand Up @@ -962,7 +1001,7 @@ class SafeSelector {
}

const _cssScopedPseudoFunctionPrefix = '(:(where|is)\\()?';
const _cssPrefixWithPseudoSelectorFunction = /^:(where|is)\(/i;
const _cssPrefixWithPseudoSelectorFunction = /:(where|is)\(/gi;
const _cssContentNextSelectorRe =
/polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim;
const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim;
Expand All @@ -979,11 +1018,11 @@ const _cssColonHostContextReGlobal = new RegExp(
);
const _cssColonHostContextRe = new RegExp(_polyfillHostContext + _parenSuffix, 'im');
const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator';
const _polyfillHostNoCombinatorWithinPseudoFunction = new RegExp(
`:.*\\(.*${_polyfillHostNoCombinator}.*\\)`,
const _polyfillHostNoCombinatorOutsidePseudoFunction = new RegExp(
`${_polyfillHostNoCombinator}(?![^(]*\\))`,
'g',
);
const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/;
const _polyfillHostNoCombinatorReGlobal = new RegExp(_polyfillHostNoCombinatorRe, 'g');
const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s,]*)/;
const _shadowDOMSelectorsRe = [
/::shadow/g,
/::content/g,
Expand Down
38 changes: 38 additions & 0 deletions packages/compiler/test/shadow_css/host_and_host_context_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,21 @@ describe('ShadowCss, :host and :host-context', () => {
expect(shim(':host(:not(p)):before {}', 'contenta', 'a-host')).toEqualCss(
'[a-host]:not(p):before {}',
);
expect(shim(':host:not(:host.foo) {}', 'contenta', 'a-host')).toEqualCss(
'[a-host]:not([a-host].foo) {}',
);
expect(shim(':host:not(.foo:host) {}', 'contenta', 'a-host')).toEqualCss(
'[a-host]:not(.foo[a-host]) {}',
);
expect(shim(':host:not(:host.foo, :host.bar) {}', 'contenta', 'a-host')).toEqualCss(
'[a-host]:not([a-host].foo, .bar[a-host]) {}',
);
expect(shim(':host:not(:host.foo, .bar :host) {}', 'contenta', 'a-host')).toEqualCss(
'[a-host]:not([a-host].foo, .bar [a-host]) {}',
);
expect(shim(':host:not(.foo, .bar) {}', 'contenta', 'a-host')).toEqualCss(
'[a-host]:not(.foo, .bar) {}',
);
});

// see b/63672152
Expand All @@ -104,6 +119,17 @@ describe('ShadowCss, :host and :host-context', () => {
'cmp [a-host] child {}',
);
});

it('should support newlines in the same selector and content ', () => {
const selector = `.foo:not(
:host) {
background-color:
green;
}`;
expect(shim(selector, 'contenta', 'a-host')).toEqualCss(
'.foo[contenta]:not( [a-host]) { background-color:green;}',
);
});
});

describe(':host-context', () => {
Expand Down Expand Up @@ -240,6 +266,18 @@ describe('ShadowCss, :host and :host-context', () => {
);
});

it('should handle no selector :host', () => {
// The second selector below should have a `[a-host]` attribute selector
// attached to `.one`, current `:host-context` unwrapping logic doesn't
// work correctly on prefixed selectors, see #58345.
expect(shim(':host:host-context(.one) {}', 'contenta', 'a-host')).toEqualCss(
'.one[a-host][a-host], .one [a-host] {}',
);
expect(shim(':host-context(.one) :host {}', 'contenta', 'a-host')).toEqualCss(
'.one [a-host] {}',
);
});

it('should handle selectors on different elements', () => {
expect(shim(':host-context(div) :host(.x) > .y {}', 'contenta', 'a-host')).toEqualCss(
'div .x[a-host] > .y[contenta] {}',
Expand Down
65 changes: 54 additions & 11 deletions packages/compiler/test/shadow_css/shadow_css_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ describe('ShadowCss', () => {
expect(shim(css, 'contenta')).toEqualCss(expected);
});

it('should support newlines in the same selector and content ', () => {
const selector = `.foo:not(
.bar) {
background-color:
green;
}`;
expect(shim(selector, 'contenta', 'a-host')).toEqualCss(
'.foo[contenta]:not( .bar) { background-color:green;}',
);
});

it('should handle complicated selectors', () => {
expect(shim('one::before {}', 'contenta')).toEqualCss('one[contenta]::before {}');
expect(shim('one two {}', 'contenta')).toEqualCss('one[contenta] two[contenta] {}');
Expand Down Expand Up @@ -93,25 +104,15 @@ describe('ShadowCss', () => {
'div[contenta]:where(.one) {}',
);
expect(shim('div:where() {}', 'contenta', 'hosta')).toEqualCss('div[contenta]:where() {}');
// See `xit('should parse concatenated pseudo selectors'`
expect(shim(':where(a):where(b) {}', 'contenta', 'hosta')).toEqualCss(
':where(a)[contenta]:where(b) {}',
':where(a[contenta]):where(b[contenta]) {}',
);
expect(shim('*:where(.one) {}', 'contenta', 'hosta')).toEqualCss('*[contenta]:where(.one) {}');
expect(shim('*:where(.one) ::ng-deep .foo {}', 'contenta', 'hosta')).toEqualCss(
'*[contenta]:where(.one) .foo {}',
);
});

xit('should parse concatenated pseudo selectors', () => {
// Current logic leads to a result with an outer scope
// It could be changed, to not increase specificity
// Requires a more complex parsing
expect(shim(':where(a):where(b) {}', 'contenta', 'hosta')).toEqualCss(
':where(a[contenta]):where(b[contenta]) {}',
);
});

it('should handle pseudo functions correctly', () => {
// :where()
expect(shim(':where(.one) {}', 'contenta', 'hosta')).toEqualCss(':where(.one[contenta]) {}');
Expand Down Expand Up @@ -200,6 +201,18 @@ describe('ShadowCss', () => {
).toEqualCss(
':where(:where(a[contenta]:has(.foo), b[contenta]) :is(.one[contenta], .two[contenta]:where(.foo > .bar))) {}',
);
expect(shim(':where(.two):first-child {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:where(.two):first-child {}',
);
expect(shim(':first-child:where(.two) {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:first-child:where(.two) {}',
);
expect(shim(':where(.two):nth-child(3) {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:where(.two):nth-child(3) {}',
);
expect(shim('table :where(td, th):hover { color: lime; }', 'contenta', 'hosta')).toEqualCss(
'table[contenta] [contenta]:where(td, th):hover { color:lime;}',
);

// complex selectors
expect(shim(':host:is([foo],[foo-2])>div.example-2 {}', 'contenta', 'a-host')).toEqualCss(
Expand Down Expand Up @@ -229,6 +242,24 @@ describe('ShadowCss', () => {
expect(shim(':has(a) :has(b) {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:has(a) [contenta]:has(b) {}',
);
expect(shim(':has(a, b) {}', 'contenta', 'hosta')).toEqualCss('[contenta]:has(a, b) {}');
expect(shim(':has(a, b:where(.foo), :is(.bar)) {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:has(a, b:where(.foo), :is(.bar)) {}',
);
expect(
shim(':has(a, b:where(.foo), :is(.bar):first-child):first-letter {}', 'contenta', 'hosta'),
).toEqualCss('[contenta]:has(a, b:where(.foo), :is(.bar):first-child):first-letter {}');
expect(
shim(':where(a, b:where(.foo), :has(.bar):first-child) {}', 'contenta', 'hosta'),
).toEqualCss(
':where(a[contenta], b[contenta]:where(.foo), [contenta]:has(.bar):first-child) {}',
);
expect(shim(':has(.one :host, .two) {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:has(.one [hosta], .two) {}',
);
expect(shim(':has(.one, :host) {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:has(.one, [hosta]) {}',
);
});

it('should handle :host inclusions inside pseudo-selectors selectors', () => {
Expand All @@ -254,6 +285,18 @@ describe('ShadowCss', () => {
expect(shim('.one :where(:host, .two) {}', 'contenta', 'hosta')).toEqualCss(
'.one :where([hosta], .two[contenta]) {}',
);
expect(shim(':is(.foo):is(:host):is(.two) {}', 'contenta', 'hosta')).toEqualCss(
':is(.foo):is([hosta]):is(.two[contenta]) {}',
);
expect(shim(':where(.one, :host .two):first-letter {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:where(.one, [hosta] .two):first-letter {}',
);
expect(shim(':first-child:where(.one, :host .two) {}', 'contenta', 'hosta')).toEqualCss(
'[contenta]:first-child:where(.one, [hosta] .two) {}',
);
expect(
shim(':where(.one, :host .two):nth-child(3):is(.foo, a:where(.bar)) {}', 'contenta', 'hosta'),
).toEqualCss('[contenta]:where(.one, [hosta] .two):nth-child(3):is(.foo, a:where(.bar)) {}');
});

it('should handle escaped selector with space (if followed by a hex char)', () => {
Expand Down