From e638b292f244bbd75df06a18a8322eb02fb452c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=A9stor=20Sabater?= Date: Thu, 3 Oct 2024 22:41:37 +0200 Subject: [PATCH 1/9] docs(router): document segment consumption during route evaluation (#58069) PR Close #58069 --- packages/router/src/models.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/router/src/models.ts b/packages/router/src/models.ts index e116bbdcdcde..ba11476559d8 100644 --- a/packages/router/src/models.ts +++ b/packages/router/src/models.ts @@ -1115,6 +1115,9 @@ export interface CanMatch { * * {@example router/route_functional_guards.ts region="CanMatchFn"} * + * @param route The route configuration. + * @param segments The URL segments that have not been consumed by previous parent route evaluations. + * * @publicApi * @see {@link Route} */ From 3b989ac5bd951a3d28bcd0ada150fc81503a016a Mon Sep 17 00:00:00 2001 From: Daniel Payet Date: Mon, 21 Oct 2024 19:38:59 +0200 Subject: [PATCH 2/9] fix(localize): Adding arb format to the list of valid formats in the localization extractor cli (#58287) Although the ARB format is supported, it's missing from the command's list of options. Fix #58286 PR Close #58287 --- packages/localize/tools/src/extract/cli.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/localize/tools/src/extract/cli.ts b/packages/localize/tools/src/extract/cli.ts index 391b30831dbc..7ec28c74a8da 100644 --- a/packages/localize/tools/src/extract/cli.ts +++ b/packages/localize/tools/src/extract/cli.ts @@ -48,7 +48,18 @@ const options = yargs(args) .option('f', { alias: 'format', required: true, - choices: ['xmb', 'xlf', 'xlif', 'xliff', 'xlf2', 'xlif2', 'xliff2', 'json', 'legacy-migrate'], + choices: [ + 'xmb', + 'xlf', + 'xlif', + 'xliff', + 'xlf2', + 'xlif2', + 'xliff2', + 'json', + 'legacy-migrate', + 'arb', + ], describe: 'The format of the translation file.', type: 'string', }) From ddda3558ca9b854442676cbcb7f9730ec76ca884 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Sat, 26 Oct 2024 09:07:04 +0200 Subject: [PATCH 3/9] refactor(core): remove globalApi tag (#58375) `@globalApi` was an AIO implementation detail that isn't relevant anymore. PR Close #58375 --- packages/core/src/render3/util/change_detection_utils.ts | 1 - packages/core/src/render3/util/discovery_utils.ts | 9 --------- packages/localize/src/localize/src/localize.ts | 1 - 3 files changed, 11 deletions(-) diff --git a/packages/core/src/render3/util/change_detection_utils.ts b/packages/core/src/render3/util/change_detection_utils.ts index 407c4666ba04..8a92a746be7c 100644 --- a/packages/core/src/render3/util/change_detection_utils.ts +++ b/packages/core/src/render3/util/change_detection_utils.ts @@ -22,7 +22,6 @@ import {getRootComponents} from './discovery_utils'; * @param component Component to {@link ChangeDetectorRef#markForCheck mark for check}. * * @publicApi - * @globalApi ng */ export function applyChanges(component: {}): void { ngDevMode && assertDefined(component, 'component'); diff --git a/packages/core/src/render3/util/discovery_utils.ts b/packages/core/src/render3/util/discovery_utils.ts index f0bb7eb1bf98..ee9bd487da85 100644 --- a/packages/core/src/render3/util/discovery_utils.ts +++ b/packages/core/src/render3/util/discovery_utils.ts @@ -51,7 +51,6 @@ import {getLViewParent, unwrapRNode} from './view_utils'; * is no component associated with it. * * @publicApi - * @globalApi ng */ export function getComponent(element: Element): T | null { ngDevMode && assertDomElement(element); @@ -79,7 +78,6 @@ export function getComponent(element: Element): T | null { * inside any component. * * @publicApi - * @globalApi ng */ export function getContext(element: Element): T | null { assertDomElement(element); @@ -101,7 +99,6 @@ export function getContext(element: Element): T | null { * part of a component view. * * @publicApi - * @globalApi ng */ export function getOwningComponent(elementOrDir: Element | {}): T | null { const context = getLContext(elementOrDir)!; @@ -124,7 +121,6 @@ export function getOwningComponent(elementOrDir: Element | {}): T | null { * @returns Root components associated with the target object. * * @publicApi - * @globalApi ng */ export function getRootComponents(elementOrDir: Element | {}): {}[] { const lView = readPatchedLView<{}>(elementOrDir); @@ -139,7 +135,6 @@ export function getRootComponents(elementOrDir: Element | {}): {}[] { * @returns Injector associated with the element, component or directive instance. * * @publicApi - * @globalApi ng */ export function getInjector(elementOrDir: Element | {}): Injector { const context = getLContext(elementOrDir)!; @@ -201,7 +196,6 @@ export function getInjectionTokens(element: Element): any[] { * @returns Array of directives associated with the node. * * @publicApi - * @globalApi ng */ export function getDirectives(node: Node): {}[] { // Skip text nodes because we can't have directives associated with them. @@ -266,7 +260,6 @@ export interface ComponentDebugMetadata extends DirectiveDebugMetadata { * @returns metadata of the passed directive or component * * @publicApi - * @globalApi ng */ export function getDirectiveMetadata( directiveOrComponentInstance: any, @@ -329,7 +322,6 @@ export function getLocalRefs(target: {}): {[key: string]: any} { * @returns Host element of the target. * * @publicApi - * @globalApi ng */ export function getHostElement(componentOrDirective: {}): Element { return getLContext(componentOrDirective)!.native as unknown as Element; @@ -398,7 +390,6 @@ export interface Listener { * @returns Array of event listeners on the DOM element. * * @publicApi - * @globalApi ng */ export function getListeners(element: Element): Listener[] { ngDevMode && assertDomElement(element); diff --git a/packages/localize/src/localize/src/localize.ts b/packages/localize/src/localize/src/localize.ts index 8c478435df76..e862d91a7f90 100644 --- a/packages/localize/src/localize/src/localize.ts +++ b/packages/localize/src/localize/src/localize.ts @@ -141,7 +141,6 @@ export interface TranslateFn { * @param expressions a collection of the values of each placeholder in the template string. * @returns the translated string, with the `messageParts` and `expressions` interleaved together. * - * @globalApi * @publicApi */ export const $localize: LocalizeFn = function ( From 1feaa9fd25b837c506612862050bbf2a42643a53 Mon Sep 17 00:00:00 2001 From: Jeevan Mahesha Date: Sat, 26 Oct 2024 20:57:00 +0530 Subject: [PATCH 4/9] docs: update ProfileEditorComponent to use inject() for FormBuilder (#58378) PR Close #58378 --- .../app/profile-editor/profile-editor.component.2.ts | 10 ++++------ adev/src/content/guide/forms/reactive-forms.md | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/adev/src/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts b/adev/src/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts index a3eb7ca0958d..4de7ca7fdd9d 100644 --- a/adev/src/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts +++ b/adev/src/content/examples/reactive-forms/src/app/profile-editor/profile-editor.component.2.ts @@ -1,6 +1,6 @@ // #docplaster // #docregion form-builder -import {Component} from '@angular/core'; +import {Component, inject} from '@angular/core'; // #docregion form-builder-imports import {FormBuilder} from '@angular/forms'; // #enddocregion form-builder-imports, form-builder @@ -15,6 +15,9 @@ import {FormArray} from '@angular/forms'; styleUrls: ['./profile-editor.component.css'], }) export class ProfileEditorComponent { + // #docregion inject-form-builder + private formBuilder = inject(FormBuilder); + // #enddocregion inject-form-builder // #docregion formgroup-compare profileForm = this.formBuilder.group({ firstName: [''], @@ -34,11 +37,6 @@ export class ProfileEditorComponent { return this.profileForm.get('aliases') as FormArray; } - // #docregion inject-form-builder, form-builder - - constructor(private formBuilder: FormBuilder) {} - // #enddocregion inject-form-builder, form-builder - updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', diff --git a/adev/src/content/guide/forms/reactive-forms.md b/adev/src/content/guide/forms/reactive-forms.md index dd830ffe3bd4..6452e3235d7d 100644 --- a/adev/src/content/guide/forms/reactive-forms.md +++ b/adev/src/content/guide/forms/reactive-forms.md @@ -259,7 +259,7 @@ Import the `FormBuilder` class from the `@angular/forms` package. -The `FormBuilder` service is an injectable provider that is provided with the reactive forms module. Inject this dependency by adding it to the component constructor. +The `FormBuilder` service is an injectable provider from the reactive forms module. Use the `inject()` function to inject this dependency in your component. From 7d67bd30672f765cbfcba988f89196bc4af401d2 Mon Sep 17 00:00:00 2001 From: george looshch Date: Sun, 27 Oct 2024 18:49:04 +0000 Subject: [PATCH 5/9] docs: fix backtick escaping in a Markdown file (#58387) Fix backtick escaping for the template string example in the documentation on expression syntax. Closes #58382 PR Close #58387 PR Close #58387 --- adev/src/content/guide/templates/expression-syntax.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adev/src/content/guide/templates/expression-syntax.md b/adev/src/content/guide/templates/expression-syntax.md index 74e75c96716f..0091a4ca735a 100644 --- a/adev/src/content/guide/templates/expression-syntax.md +++ b/adev/src/content/guide/templates/expression-syntax.md @@ -19,10 +19,10 @@ Angular supports a subset of [literal values](https://developer.mozilla.org/en-U ### Unsupported literals -| Literal type | Example value | -| --------------- | ------------------- | -| Template string | `\`Hello ${name}\`` | -| RegExp | `/\d+/` | +| Literal type | Example value | +| --------------- | --------------------- | +| Template string | `` `Hello ${name}` `` | +| RegExp | `/\d+/` | ## Globals From 29660f2df9b105bd6ed004f3d54faf9d31cbf993 Mon Sep 17 00:00:00 2001 From: george looshch Date: Sun, 27 Oct 2024 17:40:53 +0000 Subject: [PATCH 6/9] docs: remove an unnecessary whitespace (#58388) Remove an unnecessary whitespace between an opening parenthesis and a word in the documentation on lifecycle. Closes #58380 PR Close #58388 PR Close #58388 --- adev/src/content/guide/components/lifecycle.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adev/src/content/guide/components/lifecycle.md b/adev/src/content/guide/components/lifecycle.md index 1caac7bc72b4..e95fc7b9106d 100644 --- a/adev/src/content/guide/components/lifecycle.md +++ b/adev/src/content/guide/components/lifecycle.md @@ -175,8 +175,8 @@ During initialization, the first `ngDoCheck` runs after `ngOnInit`. ### ngAfterContentInit -The `ngAfterContentInit` method runs once after all the children nested inside the component ( -its _content_) have been initialized. +The `ngAfterContentInit` method runs once after all the children nested inside the component (its +_content_) have been initialized. You can use this lifecycle hook to read the results of [content queries](guide/components/queries#content-queries). While you can access the initialized From 1f13a5eb3ba7a894f0dc006afb4f24ae87bde788 Mon Sep 17 00:00:00 2001 From: Angular Robot Date: Tue, 29 Oct 2024 05:13:28 +0000 Subject: [PATCH 7/9] build: update scorecard action dependencies (#58400) See associated pull request for more information. PR Close #58400 --- .github/workflows/scorecard.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 1df80f32de60..50749762093f 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -25,7 +25,7 @@ jobs: steps: - name: 'Checkout code' - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false @@ -47,6 +47,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 + uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 with: sarif_file: results.sarif From 69dce38e778cb4c15aa06347031765a84e3ac6a5 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Tue, 29 Oct 2024 14:47:17 -0700 Subject: [PATCH 8/9] Revert fix(compiler): transform pseudo selectors correctly for the encapsulated view. (#58417) This commit reverts #57796 for v18 PR Close #58417 --- packages/compiler/src/shadow_css.ts | 170 +++-------------- .../shadow_css/host_and_host_context_spec.ts | 36 ---- .../test/shadow_css/shadow_css_spec.ts | 177 ------------------ 3 files changed, 30 insertions(+), 353 deletions(-) diff --git a/packages/compiler/src/shadow_css.ts b/packages/compiler/src/shadow_css.ts index bbaad8b43cf4..f21dc0027022 100644 --- a/packages/compiler/src/shadow_css.ts +++ b/packages/compiler/src/shadow_css.ts @@ -338,7 +338,7 @@ export class ShadowCss { * captures how many (if any) leading whitespaces are present or a comma * - (?:(?:(['"])((?:\\\\|\\\2|(?!\2).)+)\2)|(-?[A-Za-z][\w\-]*)) * captures two different possible keyframes, ones which are quoted or ones which are valid css - * indents (custom properties excluded) + * idents (custom properties excluded) * - (?=[,\s;]|$) * simply matches the end of the possible keyframe, valid endings are: a comma, a space, a * semicolon or the end of the string @@ -459,7 +459,7 @@ export class ShadowCss { */ private _scopeCssText(cssText: string, scopeSelector: string, hostSelector: string): string { const unscopedRules = this._extractUnscopedRulesFromCssText(cssText); - // replace :host and :host-context with -shadowcsshost and -shadowcsshostcontext respectively + // replace :host and :host-context -shadowcsshost and -shadowcsshost respectively cssText = this._insertPolyfillHostInCssText(cssText); cssText = this._convertColonHost(cssText); cssText = this._convertColonHostContext(cssText); @@ -539,7 +539,7 @@ export class ShadowCss { * .foo .bar { ... } */ private _convertColonHostContext(cssText: string): string { - return cssText.replace(_cssColonHostContextReGlobal, (selectorText, pseudoPrefix) => { + return cssText.replace(_cssColonHostContextReGlobal, (selectorText) => { // We have captured a selector that contains a `:host-context` rule. // For backward compatibility `:host-context` may contain a comma separated list of selectors. @@ -594,12 +594,10 @@ export class ShadowCss { } // The context selectors now must be combined with each other to capture all the possible - // selectors that `:host-context` can match. See `_combineHostContextSelectors()` for more + // selectors that `:host-context` can match. See `combineHostContextSelectors()` for more // info about how this is done. return contextSelectorGroups - .map((contextSelectors) => - _combineHostContextSelectors(contextSelectors, selectorText, pseudoPrefix), - ) + .map((contextSelectors) => combineHostContextSelectors(contextSelectors, selectorText)) .join(', '); }); } @@ -618,12 +616,7 @@ export class ShadowCss { let selector = rule.selector; let content = rule.content; if (rule.selector[0] !== '@') { - selector = this._scopeSelector({ - selector, - scopeSelector, - hostSelector, - isParentSelector: true, - }); + selector = this._scopeSelector(rule.selector, scopeSelector, hostSelector); } else if (scopedAtRuleIdentifiers.some((atRule) => rule.selector.startsWith(atRule))) { content = this._scopeSelectors(rule.content, scopeSelector, hostSelector); } else if (rule.selector.startsWith('@font-face') || rule.selector.startsWith('@page')) { @@ -663,44 +656,15 @@ export class ShadowCss { }); } - private _safeSelector: SafeSelector | undefined; - private _shouldScopeIndicator: boolean | undefined; - - // `isParentSelector` is used to distinguish the selectors which are coming from - // the initial selector string and any nested selectors, parsed recursively, - // for example `selector = 'a:where(.one)'` could be the parent, while recursive call - // would have `selector = '.one'`. - private _scopeSelector({ - selector, - scopeSelector, - hostSelector, - isParentSelector = false, - }: { - selector: string; - scopeSelector: string; - hostSelector: string; - isParentSelector?: boolean; - }): string { - // Split the selector into independent parts by `,` (comma) unless - // comma is within parenthesis, for example `:is(.one, two)`. - // Negative lookup after comma allows not splitting inside nested parenthesis, - // up to three levels (((,))). - const selectorSplitRe = - / ?,(?!(?:[^)(]*(?:\([^)(]*(?:\([^)(]*(?:\([^)(]*\)[^)(]*)*\)[^)(]*)*\)[^)(]*)*\))) ?/; - + private _scopeSelector(selector: string, scopeSelector: string, hostSelector: string): string { return selector - .split(selectorSplitRe) + .split(/ ?, ?/) .map((part) => part.split(_shadowDeepSelectors)) .map((deepParts) => { const [shallowPart, ...otherParts] = deepParts; const applyScope = (shallowPart: string) => { if (this._selectorNeedsScoping(shallowPart, scopeSelector)) { - return this._applySelectorScope({ - selector: shallowPart, - scopeSelector, - hostSelector, - isParentSelector, - }); + return this._applySelectorScope(shallowPart, scopeSelector, hostSelector); } else { return shallowPart; } @@ -733,9 +697,9 @@ export class ShadowCss { if (_polyfillHostRe.test(selector)) { const replaceBy = `[${hostSelector}]`; return selector - .replace(_polyfillHostNoCombinatorReGlobal, (_hnc, selector) => { + .replace(_polyfillHostNoCombinatorRe, (hnc, selector) => { return selector.replace( - /([^:\)]*)(:*)(.*)/, + /([^:]*)(:*)(.*)/, (_: string, before: string, colon: string, after: string) => { return before + replaceBy + colon + after; }, @@ -749,17 +713,11 @@ export class ShadowCss { // return a selector with [name] suffix on each simple selector // e.g. .foo.bar > .zot becomes .foo[name].bar[name] > .zot[name] /** @internal */ - private _applySelectorScope({ - selector, - scopeSelector, - hostSelector, - isParentSelector, - }: { - selector: string; - scopeSelector: string; - hostSelector: string; - isParentSelector?: boolean; - }): string { + private _applySelectorScope( + selector: string, + scopeSelector: string, + hostSelector: string, + ): string { const isRe = /\[is=([^\]]*)\]/g; scopeSelector = scopeSelector.replace(isRe, (_: string, ...parts: string[]) => parts[0]); @@ -774,10 +732,6 @@ export class ShadowCss { if (p.includes(_polyfillHostNoCombinator)) { scopedP = this._applySimpleSelectorScope(p, scopeSelector, hostSelector); - if (_polyfillHostNoCombinatorWithinPseudoFunction.test(p)) { - const [_, before, colon, after] = scopedP.match(/([^:]*)(:*)(.*)/)!; - scopedP = before + attrName + colon + after; - } } else { // remove :host since it should be unnecessary const t = p.replace(_polyfillHostRe, ''); @@ -792,60 +746,13 @@ export class ShadowCss { return scopedP; }; - // Wraps `_scopeSelectorPart()` to not use it directly on selectors with - // pseudo selector functions like `:where()`. Selectors within pseudo selector - // functions are recursively sent to `_scopeSelector()`. - const _pseudoFunctionAwareScopeSelectorPart = (selectorPart: string) => { - let scopedPart = ''; - - const cssPrefixWithPseudoSelectorFunctionMatch = selectorPart.match( - _cssPrefixWithPseudoSelectorFunction, - ); - if (cssPrefixWithPseudoSelectorFunctionMatch) { - const [cssPseudoSelectorFunction] = cssPrefixWithPseudoSelectorFunctionMatch; - - // 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, - }); - - // Put the result back into the pseudo selector function. - scopedPart = `${cssPseudoSelectorFunction}${scopedInnerPart})`; - } else { - this._shouldScopeIndicator = - this._shouldScopeIndicator || selectorPart.includes(_polyfillHostNoCombinator); - scopedPart = this._shouldScopeIndicator ? _scopeSelectorPart(selectorPart) : selectorPart; - } - - return scopedPart; - }; - - if (isParentSelector) { - this._safeSelector = new SafeSelector(selector); - selector = this._safeSelector.content(); - } + const safeContent = new SafeSelector(selector); + selector = safeContent.content(); let scopedSelector = ''; let startIndex = 0; let res: RegExpExecArray | null; - // Combinators aren't used as a delimiter if they are within parenthesis, - // for example `:where(.one .two)` stays intact. - // Similarly to selector separation by comma initially, negative lookahead - // is used here to not break selectors within nested parenthesis up to three - // nested layers. - const sep = - /( |>|\+|~(?!=))(?!([^)(]*(?:\([^)(]*(?:\([^)(]*(?:\([^)(]*\)[^)(]*)*\)[^)(]*)*\)[^)(]*)*\)))\s*/g; + const sep = /( |>|\+|~(?!=))\s*/g; // If a selector appears before :host it should not be shimmed as it // matches on ancestor elements and not on elements in the host's shadow @@ -859,13 +766,8 @@ export class ShadowCss { // - `tag :host` -> `tag [h]` (`tag` is not scoped because it's considered part of a // `:host-context(tag)`) const hasHost = selector.includes(_polyfillHostNoCombinator); - // Only scope parts after or on the same level as the first `-shadowcsshost-no-combinator` - // when it is present. The selector has the same level when it is a part of a pseudo - // selector, like `:where()`, for example `:where(:host, .foo)` would result in `.foo` - // being scoped. - if (isParentSelector || this._shouldScopeIndicator) { - this._shouldScopeIndicator = !hasHost; - } + // Only scope parts after the first `-shadowcsshost-no-combinator` when it is present + let shouldScope = !hasHost; while ((res = sep.exec(selector)) !== null) { const separator = res[1]; @@ -884,17 +786,18 @@ export class ShadowCss { continue; } - const scopedPart = _pseudoFunctionAwareScopeSelectorPart(part); + shouldScope = shouldScope || part.includes(_polyfillHostNoCombinator); + const scopedPart = shouldScope ? _scopeSelectorPart(part) : part; scopedSelector += `${scopedPart} ${separator} `; startIndex = sep.lastIndex; } const part = selector.substring(startIndex); - scopedSelector += _pseudoFunctionAwareScopeSelectorPart(part); + shouldScope = shouldScope || part.includes(_polyfillHostNoCombinator); + scopedSelector += shouldScope ? _scopeSelectorPart(part) : part; // replace the placeholders with their original values - // using values stored inside the `safeSelector` instance. - return this._safeSelector!.restore(scopedSelector); + return safeContent.restore(scopedSelector); } private _insertPolyfillHostInCssText(selector: string): string { @@ -959,8 +862,6 @@ class SafeSelector { } } -const _cssScopedPseudoFunctionPrefix = '(:(where|is)\\()?'; -const _cssPrefixWithPseudoSelectorFunction = /^:(where|is)\(/i; const _cssContentNextSelectorRe = /polyfill-next-selector[^}]*content:[\s]*?(['"])(.*?)\1[;\s]*}([^{]*?){/gim; const _cssContentRuleRe = /(polyfill-rule)[^}]*(content:[\s]*(['"])(.*?)\3)[;\s]*[^}]*}/gim; @@ -971,17 +872,10 @@ const _polyfillHost = '-shadowcsshost'; const _polyfillHostContext = '-shadowcsscontext'; const _parenSuffix = '(?:\\((' + '(?:\\([^)(]*\\)|[^)(]*)+?' + ')\\))?([^,{]*)'; const _cssColonHostRe = new RegExp(_polyfillHost + _parenSuffix, 'gim'); -const _cssColonHostContextReGlobal = new RegExp( - _cssScopedPseudoFunctionPrefix + '(' + _polyfillHostContext + _parenSuffix + ')', - 'gim', -); +const _cssColonHostContextReGlobal = new RegExp(_polyfillHostContext + _parenSuffix, 'gim'); const _cssColonHostContextRe = new RegExp(_polyfillHostContext + _parenSuffix, 'im'); const _polyfillHostNoCombinator = _polyfillHost + '-no-combinator'; -const _polyfillHostNoCombinatorWithinPseudoFunction = new RegExp( - `:.*\\(.*${_polyfillHostNoCombinator}.*\\)`, -); const _polyfillHostNoCombinatorRe = /-shadowcsshost-no-combinator([^\s]*)/; -const _polyfillHostNoCombinatorReGlobal = new RegExp(_polyfillHostNoCombinatorRe, 'g'); const _shadowDOMSelectorsRe = [ /::shadow/g, /::content/g, @@ -1232,11 +1126,7 @@ function unescapeQuotes(str: string, isQuoted: boolean): string { * @param contextSelectors an array of context selectors that will be combined. * @param otherSelectors the rest of the selectors that are not context selectors. */ -function _combineHostContextSelectors( - contextSelectors: string[], - otherSelectors: string, - pseudoPrefix = '', -): string { +function combineHostContextSelectors(contextSelectors: string[], otherSelectors: string): string { const hostMarker = _polyfillHostNoCombinator; _polyfillHostRe.lastIndex = 0; // reset the regex to ensure we get an accurate test const otherSelectorsHasHost = _polyfillHostRe.test(otherSelectors); @@ -1265,8 +1155,8 @@ function _combineHostContextSelectors( return combined .map((s) => otherSelectorsHasHost - ? `${pseudoPrefix}${s}${otherSelectors}` - : `${pseudoPrefix}${s}${hostMarker}${otherSelectors}, ${pseudoPrefix}${s} ${hostMarker}${otherSelectors}`, + ? `${s}${otherSelectors}` + : `${s}${hostMarker}${otherSelectors}, ${s} ${hostMarker}${otherSelectors}`, ) .join(','); } diff --git a/packages/compiler/test/shadow_css/host_and_host_context_spec.ts b/packages/compiler/test/shadow_css/host_and_host_context_spec.ts index 5b5689feb2be..c4dc29c372b4 100644 --- a/packages/compiler/test/shadow_css/host_and_host_context_spec.ts +++ b/packages/compiler/test/shadow_css/host_and_host_context_spec.ts @@ -107,42 +107,6 @@ describe('ShadowCss, :host and :host-context', () => { }); describe(':host-context', () => { - it('should transform :host-context with pseudo selectors', () => { - expect( - shim(':host-context(backdrop:not(.borderless)) .backdrop {}', 'contenta', 'hosta'), - ).toEqualCss( - 'backdrop:not(.borderless)[hosta] .backdrop[contenta], backdrop:not(.borderless) [hosta] .backdrop[contenta] {}', - ); - expect(shim(':where(:host-context(backdrop)) {}', 'contenta', 'hosta')).toEqualCss( - ':where(backdrop[hosta]), :where(backdrop [hosta]) {}', - ); - expect(shim(':where(:host-context(outer1)) :host(bar) {}', 'contenta', 'hosta')).toEqualCss( - ':where(outer1) bar[hosta] {}', - ); - expect( - shim(':where(:host-context(.one)) :where(:host-context(.two)) {}', 'contenta', 'a-host'), - ).toEqualCss( - ':where(.one.two[a-host]), ' + // `one` and `two` both on the host - ':where(.one.two [a-host]), ' + // `one` and `two` are both on the same ancestor - ':where(.one .two[a-host]), ' + // `one` is an ancestor and `two` is on the host - ':where(.one .two [a-host]), ' + // `one` and `two` are both ancestors (in that order) - ':where(.two .one[a-host]), ' + // `two` is an ancestor and `one` is on the host - ':where(.two .one [a-host])' + // `two` and `one` are both ancestors (in that order) - ' {}', - ); - expect( - shim(':where(:host-context(backdrop)) .foo ~ .bar {}', 'contenta', 'hosta'), - ).toEqualCss( - ':where(backdrop[hosta]) .foo[contenta] ~ .bar[contenta], :where(backdrop [hosta]) .foo[contenta] ~ .bar[contenta] {}', - ); - expect(shim(':where(:host-context(backdrop)) :host {}', 'contenta', 'hosta')).toEqualCss( - ':where(backdrop) [hosta] {}', - ); - expect(shim('div:where(:host-context(backdrop)) :host {}', 'contenta', 'hosta')).toEqualCss( - 'div:where(backdrop) [hosta] {}', - ); - }); - it('should handle tag selector', () => { expect(shim(':host-context(div) {}', 'contenta', 'a-host')).toEqualCss( 'div[a-host], div [a-host] {}', diff --git a/packages/compiler/test/shadow_css/shadow_css_spec.ts b/packages/compiler/test/shadow_css/shadow_css_spec.ts index 77a0a361a319..e7e038d1b52e 100644 --- a/packages/compiler/test/shadow_css/shadow_css_spec.ts +++ b/packages/compiler/test/shadow_css/shadow_css_spec.ts @@ -66,18 +66,6 @@ describe('ShadowCss', () => { expect(shim('one[attr="va lue"] {}', 'contenta')).toEqualCss('one[attr="va lue"][contenta] {}'); expect(shim('one[attr] {}', 'contenta')).toEqualCss('one[attr][contenta] {}'); expect(shim('[is="one"] {}', 'contenta')).toEqualCss('[is="one"][contenta] {}'); - expect(shim('[attr] {}', 'contenta')).toEqualCss('[attr][contenta] {}'); - }); - - it('should transform :host with attributes', () => { - expect(shim(':host [attr] {}', 'contenta', 'hosta')).toEqualCss('[hosta] [attr][contenta] {}'); - expect(shim(':host(create-first-project) {}', 'contenta', 'hosta')).toEqualCss( - 'create-first-project[hosta] {}', - ); - expect(shim(':host[attr] {}', 'contenta', 'hosta')).toEqualCss('[attr][hosta] {}'); - expect(shim(':host[attr]:where(:not(.cm-button)) {}', 'contenta', 'hosta')).toEqualCss( - '[attr][hosta]:where(:not(.cm-button)) {}', - ); }); it('should handle escaped sequences in selectors', () => { @@ -89,171 +77,6 @@ describe('ShadowCss', () => { expect(shim('.one\\:two .three\\:four {}', 'contenta')).toEqualCss( '.one\\:two[contenta] .three\\:four[contenta] {}', ); - expect(shim('div:where(.one) {}', 'contenta', 'hosta')).toEqualCss( - '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) {}', - ); - 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]) {}'); - expect(shim(':where(div.one span.two) {}', 'contenta', 'hosta')).toEqualCss( - ':where(div.one[contenta] span.two[contenta]) {}', - ); - expect(shim(':where(.one) .two {}', 'contenta', 'hosta')).toEqualCss( - ':where(.one[contenta]) .two[contenta] {}', - ); - expect(shim(':where(:host) {}', 'contenta', 'hosta')).toEqualCss(':where([hosta]) {}'); - expect(shim(':where(:host) .one {}', 'contenta', 'hosta')).toEqualCss( - ':where([hosta]) .one[contenta] {}', - ); - expect(shim(':where(.one) :where(:host) {}', 'contenta', 'hosta')).toEqualCss( - ':where(.one) :where([hosta]) {}', - ); - expect(shim(':where(.one :host) {}', 'contenta', 'hosta')).toEqualCss( - ':where(.one [hosta]) {}', - ); - expect(shim('div :where(.one) {}', 'contenta', 'hosta')).toEqualCss( - 'div[contenta] :where(.one[contenta]) {}', - ); - expect(shim(':host :where(.one .two) {}', 'contenta', 'hosta')).toEqualCss( - '[hosta] :where(.one[contenta] .two[contenta]) {}', - ); - expect(shim(':where(.one, .two) {}', 'contenta', 'hosta')).toEqualCss( - ':where(.one[contenta], .two[contenta]) {}', - ); - expect(shim(':where(.one > .two) {}', 'contenta', 'hosta')).toEqualCss( - ':where(.one[contenta] > .two[contenta]) {}', - ); - expect(shim(':where(> .one) {}', 'contenta', 'hosta')).toEqualCss( - ':where( > .one[contenta]) {}', - ); - expect(shim(':where(:not(.one) ~ .two) {}', 'contenta', 'hosta')).toEqualCss( - ':where([contenta]:not(.one) ~ .two[contenta]) {}', - ); - expect(shim(':where([foo]) {}', 'contenta', 'hosta')).toEqualCss(':where([foo][contenta]) {}'); - - // :is() - expect(shim('div:is(.foo) {}', 'contenta', 'a-host')).toEqualCss('div[contenta]:is(.foo) {}'); - expect(shim(':is(.dark :host) {}', 'contenta', 'a-host')).toEqualCss(':is(.dark [a-host]) {}'); - expect(shim(':is(.dark) :is(:host) {}', 'contenta', 'a-host')).toEqualCss( - ':is(.dark) :is([a-host]) {}', - ); - expect(shim(':host:is(.foo) {}', 'contenta', 'a-host')).toEqualCss('[a-host]:is(.foo) {}'); - expect(shim(':is(.foo) {}', 'contenta', 'a-host')).toEqualCss(':is(.foo[contenta]) {}'); - expect(shim(':is(.foo, .bar, .baz) {}', 'contenta', 'a-host')).toEqualCss( - ':is(.foo[contenta], .bar[contenta], .baz[contenta]) {}', - ); - expect(shim(':is(.foo, .bar) :host {}', 'contenta', 'a-host')).toEqualCss( - ':is(.foo, .bar) [a-host] {}', - ); - - // :is() and :where() - expect( - shim( - ':is(.foo, .bar) :is(.baz) :where(.one, .two) :host :where(.three:first-child) {}', - 'contenta', - 'a-host', - ), - ).toEqualCss( - ':is(.foo, .bar) :is(.baz) :where(.one, .two) [a-host] :where(.three[contenta]:first-child) {}', - ); - expect(shim(':where(:is(a)) {}', 'contenta', 'hosta')).toEqualCss( - ':where(:is(a[contenta])) {}', - ); - expect(shim(':where(:is(a, b)) {}', 'contenta', 'hosta')).toEqualCss( - ':where(:is(a[contenta], b[contenta])) {}', - ); - expect(shim(':where(:host:is(.one, .two)) {}', 'contenta', 'hosta')).toEqualCss( - ':where([hosta]:is(.one, .two)) {}', - ); - expect(shim(':where(:host :is(.one, .two)) {}', 'contenta', 'hosta')).toEqualCss( - ':where([hosta] :is(.one[contenta], .two[contenta])) {}', - ); - expect(shim(':where(:is(a, b) :is(.one, .two)) {}', 'contenta', 'hosta')).toEqualCss( - ':where(:is(a[contenta], b[contenta]) :is(.one[contenta], .two[contenta])) {}', - ); - expect( - shim( - ':where(:where(a:has(.foo), b) :is(.one, .two:where(.foo > .bar))) {}', - 'contenta', - 'hosta', - ), - ).toEqualCss( - ':where(:where(a[contenta]:has(.foo), b[contenta]) :is(.one[contenta], .two[contenta]:where(.foo > .bar))) {}', - ); - - // complex selectors - expect(shim(':host:is([foo],[foo-2])>div.example-2 {}', 'contenta', 'a-host')).toEqualCss( - '[a-host]:is([foo],[foo-2]) > div.example-2[contenta] {}', - ); - expect(shim(':host:is([foo], [foo-2]) > div.example-2 {}', 'contenta', 'a-host')).toEqualCss( - '[a-host]:is([foo], [foo-2]) > div.example-2[contenta] {}', - ); - expect(shim(':host:has([foo],[foo-2])>div.example-2 {}', 'contenta', 'a-host')).toEqualCss( - '[a-host]:has([foo],[foo-2]) > div.example-2[contenta] {}', - ); - - // :has() - expect(shim('div:has(a) {}', 'contenta', 'hosta')).toEqualCss('div[contenta]:has(a) {}'); - expect(shim('div:has(a) :host {}', 'contenta', 'hosta')).toEqualCss('div:has(a) [hosta] {}'); - expect(shim(':has(a) :host :has(b) {}', 'contenta', 'hosta')).toEqualCss( - ':has(a) [hosta] [contenta]:has(b) {}', - ); - expect(shim('div:has(~ .one) {}', 'contenta', 'hosta')).toEqualCss( - 'div[contenta]:has(~ .one) {}', - ); - // Unlike `:is()` or `:where()` the attribute selector isn't placed inside - // of `:has()`. That is deliberate, `[contenta]:has(a)` would select all - // `[contenta]` with `a` inside, while `:has(a[contenta])` would select - // everything that contains `a[contenta]`, targeting elements outside of - // encapsulated scope. - expect(shim(':has(a) :has(b) {}', 'contenta', 'hosta')).toEqualCss( - '[contenta]:has(a) [contenta]:has(b) {}', - ); - }); - - it('should handle :host inclusions inside pseudo-selectors selectors', () => { - expect(shim('.header:not(.admin) {}', 'contenta', 'hosta')).toEqualCss( - '.header[contenta]:not(.admin) {}', - ); - expect(shim('.header:is(:host > .toolbar, :host ~ .panel) {}', 'contenta', 'hosta')).toEqualCss( - '.header[contenta]:is([hosta] > .toolbar, [hosta] ~ .panel) {}', - ); - expect( - shim('.header:where(:host > .toolbar, :host ~ .panel) {}', 'contenta', 'hosta'), - ).toEqualCss('.header[contenta]:where([hosta] > .toolbar, [hosta] ~ .panel) {}'); - expect(shim('.header:not(.admin, :host.super .header) {}', 'contenta', 'hosta')).toEqualCss( - '.header[contenta]:not(.admin, .super[hosta] .header) {}', - ); - expect( - shim('.header:not(.admin, :host.super .header, :host.mega .header) {}', 'contenta', 'hosta'), - ).toEqualCss('.header[contenta]:not(.admin, .super[hosta] .header, .mega[hosta] .header) {}'); - - expect(shim('.one :where(.two, :host) {}', 'contenta', 'hosta')).toEqualCss( - '.one :where(.two[contenta], [hosta]) {}', - ); - expect(shim('.one :where(:host, .two) {}', 'contenta', 'hosta')).toEqualCss( - '.one :where([hosta], .two[contenta]) {}', - ); }); it('should handle escaped selector with space (if followed by a hex char)', () => { From 48eac2320d66bca2b07d29f65b15636f4abadb50 Mon Sep 17 00:00:00 2001 From: Alex Rickabaugh Date: Wed, 30 Oct 2024 12:03:32 -0700 Subject: [PATCH 9/9] release: cut the v18.2.10 release --- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6c7de5d0d7..c0505f8e12dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ + +# 18.2.10 (2024-10-30) +### compiler +| Commit | Type | Description | +| -- | -- | -- | +| [69dce38e778](https://github.com/angular/angular/commit/69dce38e778cb4c15aa06347031765a84e3ac6a5) | fix | Revert: transform pseudo selectors correctly for the encapsulated view. ([#58417](https://github.com/angular/angular/pull/58417)) | +### localize +| Commit | Type | Description | +| -- | -- | -- | +| [3b989ac5bd9](https://github.com/angular/angular/commit/3b989ac5bd951a3d28bcd0ada150fc81503a016a) | fix | Adding arb format to the list of valid formats in the localization extractor cli ([#58287](https://github.com/angular/angular/pull/58287)) | + + + # 18.2.9 (2024-10-23) ### compiler-cli diff --git a/package.json b/package.json index 35388d3313cf..773374cd35ce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "18.2.9", + "version": "18.2.10", "private": true, "description": "Angular - a web framework for modern web apps", "homepage": "https://github.com/angular/angular",