mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Merge remote-tracking branch 'upstream/main' into prototype/signal-forms
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ git_override(
|
||||
bazel_dep(name = "devinfra")
|
||||
git_override(
|
||||
module_name = "devinfra",
|
||||
commit = "80db036355181684ed07021a175e9f039190d3d6",
|
||||
commit = "00a4cf14cdc374867673a52ecb556f356da8bec0",
|
||||
remote = "https://github.com/angular/dev-infra.git",
|
||||
)
|
||||
|
||||
|
||||
@@ -27,9 +27,12 @@ ts_project(
|
||||
),
|
||||
deps = [
|
||||
":entities",
|
||||
"//adev:node_modules/marked",
|
||||
"//adev:node_modules/preact",
|
||||
"//adev:node_modules/preact-render-to-string",
|
||||
"//adev:node_modules/prettier",
|
||||
"//adev:node_modules/shiki",
|
||||
"//adev/shared-docs/pipeline/shared:linking",
|
||||
"//adev/shared-docs/pipeline/shared:shiki",
|
||||
"//adev/shared-docs/pipeline/shared/marked",
|
||||
"//adev/shared-docs/pipeline/shared/regions",
|
||||
|
||||
@@ -21,6 +21,7 @@ ts_project(
|
||||
"//adev:node_modules/@types/jsdom",
|
||||
"//adev:node_modules/jsdom",
|
||||
"//adev/shared-docs/pipeline/api-gen/rendering:render_api_to_html_lib",
|
||||
"//adev/shared-docs/pipeline/shared:shiki",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -76,5 +76,7 @@ ts_project(
|
||||
deps = [
|
||||
":guides_lib",
|
||||
"//adev:node_modules/@types/node",
|
||||
"//adev/shared-docs/pipeline/shared:shiki",
|
||||
"//adev/shared-docs/pipeline/shared/marked",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ ts_project(
|
||||
"//adev:node_modules/marked",
|
||||
"//adev:node_modules/mermaid",
|
||||
"//adev:node_modules/playwright-core",
|
||||
"//adev:node_modules/shiki",
|
||||
"//adev/shared-docs/pipeline/shared:linking",
|
||||
"//adev/shared-docs/pipeline/shared:shiki",
|
||||
"//adev/shared-docs/pipeline/shared/regions",
|
||||
|
||||
@@ -4,5 +4,8 @@ ts_project(
|
||||
name = "renderer_context",
|
||||
srcs = ["renderer-context.mts"],
|
||||
visibility = ["//adev/shared-docs/pipeline/shared/marked/test:__subpackages__"],
|
||||
deps = ["//adev/shared-docs/pipeline/shared/marked"],
|
||||
deps = [
|
||||
"//adev/shared-docs/pipeline/shared:shiki",
|
||||
"//adev/shared-docs/pipeline/shared/marked",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -36,7 +36,7 @@ export function provideSettings(): (Provider | EnvironmentProviders)[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrrates the provided data to the latest data format, if needed.
|
||||
* Migrates the provided data to the latest data format, if needed.
|
||||
* Returns a new object with the migrated data.
|
||||
*
|
||||
* @param data Non-migrated data
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
[showCommentNodes]="showCommentNodes()"
|
||||
[isHydrationEnabled]="isHydrationEnabled()"
|
||||
(toggleInspector)="toggleInspector()"
|
||||
[signalGraphEnabled]="signalGraphEnabled()"
|
||||
/>
|
||||
|
||||
@let profilerVisible = activeTab() === 'Profiler';
|
||||
|
||||
-1
@@ -43,7 +43,6 @@
|
||||
@if (currentSelectedElement(); as currentSelectedElement) {
|
||||
<ng-property-tab
|
||||
[currentSelectedElement]="currentSelectedElement"
|
||||
[signalGraphEnabled]="signalGraphEnabled()"
|
||||
(showSignalGraph)="showSignalGraph($event)"
|
||||
(inspect)="inspect($event)"
|
||||
(viewSource)="viewSource($event)"
|
||||
|
||||
-2
@@ -118,8 +118,6 @@ export class DirectiveExplorerComponent {
|
||||
read: ElementRef,
|
||||
});
|
||||
|
||||
readonly signalGraphEnabled = input.required<boolean>();
|
||||
|
||||
readonly currentSelectedElement = signal<IndexedNode | null>(null);
|
||||
readonly forest = signal<DevToolsNode[]>([]);
|
||||
readonly splitDirection = signal<'horizontal' | 'vertical'>('horizontal');
|
||||
|
||||
+1
@@ -41,6 +41,7 @@ ng_project(
|
||||
"//:node_modules/@angular/core",
|
||||
"//:node_modules/@angular/material",
|
||||
"//:node_modules/rxjs",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services:settings",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/defer-view",
|
||||
|
||||
+6
-2
@@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectionStrategy, Component, input, output, signal} from '@angular/core';
|
||||
import {ChangeDetectionStrategy, Component, input, output, signal, inject} from '@angular/core';
|
||||
import {MatExpansionModule} from '@angular/material/expansion';
|
||||
import {MatIcon} from '@angular/material/icon';
|
||||
|
||||
import {IndexedNode} from '../directive-forest/index-forest';
|
||||
import {ComponentMetadataComponent} from './component-metadata.component';
|
||||
import {ButtonComponent} from '../../../shared/button/button.component';
|
||||
import {Settings} from '../../../application-services/settings';
|
||||
|
||||
@Component({
|
||||
templateUrl: './property-tab-header.component.html',
|
||||
@@ -22,9 +23,12 @@ import {ButtonComponent} from '../../../shared/button/button.component';
|
||||
imports: [MatExpansionModule, MatIcon, ComponentMetadataComponent, ButtonComponent],
|
||||
})
|
||||
export class PropertyTabHeaderComponent {
|
||||
private readonly settings = inject(Settings);
|
||||
|
||||
protected readonly currentSelectedElement = input.required<IndexedNode>();
|
||||
protected readonly signalGraphEnabled = input.required<boolean>();
|
||||
protected readonly showSignalGraph = output<void>();
|
||||
|
||||
protected readonly expanded = signal(false);
|
||||
|
||||
protected readonly signalGraphEnabled = this.settings.signalGraphEnabled;
|
||||
}
|
||||
|
||||
-2
@@ -3,7 +3,6 @@
|
||||
@if (currentSelectedElement) {
|
||||
<ng-property-tab-header
|
||||
[currentSelectedElement]="currentSelectedElement"
|
||||
[signalGraphEnabled]="signalGraphEnabled()"
|
||||
(showSignalGraph)="showSignalGraph.emit(null)"
|
||||
/>
|
||||
<ng-property-tab-body
|
||||
@@ -11,7 +10,6 @@
|
||||
(viewSource)="viewSource.emit($event)"
|
||||
(showSignalGraph)="showSignalGraph.emit($event)"
|
||||
[currentSelectedElement]="currentSelectedElement"
|
||||
[signalGraphEnabled]="signalGraphEnabled()"
|
||||
/>
|
||||
|
||||
@let hydration = currentSelectedElement.hydration;
|
||||
|
||||
-1
@@ -24,7 +24,6 @@ import {DeferViewComponent} from './defer-view/defer-view.component';
|
||||
})
|
||||
export class PropertyTabComponent {
|
||||
readonly currentSelectedElement = input.required<IndexedNode | null>();
|
||||
readonly signalGraphEnabled = input.required<boolean>();
|
||||
|
||||
readonly viewSource = output<string>();
|
||||
readonly inspect = output<{node: FlatNode; directivePosition: DirectivePosition}>();
|
||||
|
||||
+1
@@ -61,6 +61,7 @@ ng_project(
|
||||
"//devtools/projects/ng-devtools/src/lib/application-environment",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-providers:supported_apis",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services:frame_manager",
|
||||
"//devtools/projects/ng-devtools/src/lib/application-services:settings",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/directive-forest/index-forest",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-resolver",
|
||||
"//devtools/projects/ng-devtools/src/lib/devtools-tabs/directive-explorer/property-tab/property-view/resolution-path",
|
||||
|
||||
-1
@@ -5,7 +5,6 @@
|
||||
(viewSource)="viewSource.emit(directive.name)"
|
||||
(showSignalGraph)="showSignalGraph.emit($event)"
|
||||
[directive]="directive"
|
||||
[signalGraphEnabled]="signalGraphEnabled()"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ import {PropertyViewComponent} from './property-view.component';
|
||||
})
|
||||
export class PropertyTabBodyComponent {
|
||||
readonly currentSelectedElement = input.required<IndexedNode>();
|
||||
readonly signalGraphEnabled = input.required<boolean>();
|
||||
|
||||
readonly inspect = output<{node: FlatNode; directivePosition: DirectivePosition}>();
|
||||
readonly viewSource = output<string>();
|
||||
|
||||
-1
@@ -24,7 +24,6 @@
|
||||
<ng-property-view-tree
|
||||
[dataSource]="panel.controls().dataSource"
|
||||
[treeControl]="panel.controls().treeControl"
|
||||
[signalGraphEnabled]="signalGraphEnabled()"
|
||||
(updateValue)="updateValue($event)"
|
||||
(inspect)="handleInspect($event)"
|
||||
(showSignalGraph)="showSignalGraph.emit($event)"
|
||||
|
||||
-1
@@ -53,7 +53,6 @@ export class PropertyViewBodyComponent {
|
||||
readonly directivePropControls = input.required<DirectiveTreeData>();
|
||||
readonly directiveOutputControls = input.required<DirectiveTreeData>();
|
||||
readonly directiveStateControls = input.required<DirectiveTreeData>();
|
||||
readonly signalGraphEnabled = input.required<boolean>();
|
||||
|
||||
readonly inspect = output<{node: FlatNode; directivePosition: DirectivePosition}>();
|
||||
readonly showSignalGraph = output<DebugSignalGraphNode>();
|
||||
|
||||
+4
-1
@@ -20,6 +20,7 @@ import {MatTree, MatTreeNode, MatTreeNodeDef, MatTreeNodePadding} from '@angular
|
||||
import {SUPPORTED_APIS} from '../../../../application-providers/supported_apis';
|
||||
import {SignalGraphManager} from '../../signal-graph/signal-graph-manager';
|
||||
import {DebugSignalGraphNode} from '../../../../../../../protocol';
|
||||
import {Settings} from '../../../../application-services/settings';
|
||||
|
||||
@Component({
|
||||
selector: 'ng-property-view-tree',
|
||||
@@ -41,14 +42,16 @@ import {DebugSignalGraphNode} from '../../../../../../../protocol';
|
||||
export class PropertyViewTreeComponent {
|
||||
protected readonly supportedApis = inject(SUPPORTED_APIS);
|
||||
private readonly signalGraph = inject(SignalGraphManager);
|
||||
private readonly settings = inject(Settings);
|
||||
|
||||
readonly dataSource = input.required<PropertyDataSource>();
|
||||
readonly treeControl = input.required<FlatTreeControl<FlatNode>>();
|
||||
readonly signalGraphEnabled = input.required<boolean>();
|
||||
readonly updateValue = output<any>();
|
||||
readonly inspect = output<any>();
|
||||
readonly showSignalGraph = output<DebugSignalGraphNode>();
|
||||
|
||||
protected readonly signalGraphEnabled = this.settings.signalGraphEnabled;
|
||||
|
||||
hasChild = (_: number, node: FlatNode): boolean => node.expandable;
|
||||
|
||||
toggle(node: FlatNode): void {
|
||||
|
||||
-1
@@ -8,7 +8,6 @@
|
||||
[directivePropControls]="directivePropControls()!"
|
||||
[directiveOutputControls]="directiveOutputControls()!"
|
||||
[directiveStateControls]="directiveStateControls()!"
|
||||
[signalGraphEnabled]="signalGraphEnabled()"
|
||||
(inspect)="inspect.emit($event)"
|
||||
(showSignalGraph)="showSignalGraph.emit($event)"
|
||||
></ng-property-view-body>
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ import {PropertyViewHeaderComponent} from './property-view-header.component';
|
||||
})
|
||||
export class PropertyViewComponent {
|
||||
readonly directive = input.required<{name: string}>();
|
||||
readonly signalGraphEnabled = input.required<boolean>();
|
||||
|
||||
readonly inspect = output<{node: FlatNode; directivePosition: DirectivePosition}>();
|
||||
readonly viewSource = output<void>();
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
[class.drag-scrolling]="dragScrolling()"
|
||||
>
|
||||
<div
|
||||
*cdkVirtualFor="let d of graphData(); let i = index"
|
||||
*cdkVirtualFor="let d of graphData(); let i = index; trackBy: trackByIndex"
|
||||
[ngStyle]="d.style"
|
||||
class="frame-bar"
|
||||
[class.selected]="selectedFrameIndexes().has(i)"
|
||||
|
||||
+4
@@ -250,6 +250,10 @@ export class FrameSelectorComponent {
|
||||
this._viewportScrollState.scrollLeft - dx * DRAG_SCROLL_SPEED;
|
||||
}
|
||||
|
||||
trackByIndex(index: number): number {
|
||||
return index;
|
||||
}
|
||||
|
||||
private getBarStyles(frame: ProfilerFrame, multiplicationFactor: number): GraphNode {
|
||||
const height = frame.duration * multiplicationFactor;
|
||||
const colorPercentage = Math.max(10, Math.round((height / MAX_HEIGHT) * 100));
|
||||
|
||||
@@ -4,6 +4,23 @@
|
||||
|
||||
```ts
|
||||
|
||||
// @public (undocumented)
|
||||
export const BASE_EFFECT_NODE: Omit<BaseEffectNode, 'fn' | 'destroy' | 'cleanup' | 'run'>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BaseEffectNode extends ReactiveNode {
|
||||
// (undocumented)
|
||||
cleanup(): void;
|
||||
// (undocumented)
|
||||
destroy(): void;
|
||||
// (undocumented)
|
||||
fn: () => void;
|
||||
// (undocumented)
|
||||
hasRun: boolean;
|
||||
// (undocumented)
|
||||
run(): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ComputationFn<S, D> = (source: S, previous?: {
|
||||
source: S;
|
||||
@@ -134,6 +151,9 @@ export interface ReactiveNode {
|
||||
version: Version;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export function runEffect(node: BaseEffectNode): void;
|
||||
|
||||
// @public (undocumented)
|
||||
export function runPostProducerCreatedFn(node: ReactiveNode): void;
|
||||
|
||||
|
||||
+51
@@ -32,6 +32,57 @@ export declare class MyComponent {
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: animate_enter_with_structural_directive.js
|
||||
****************************************************************************************************/
|
||||
import { Component, Directive } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class AnyStructuralDirective {
|
||||
}
|
||||
AnyStructuralDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: AnyStructuralDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
AnyStructuralDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: AnyStructuralDirective, isStandalone: true, selector: "[any-structural-directive]", ngImport: i0 });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: AnyStructuralDirective, decorators: [{
|
||||
type: Directive,
|
||||
args: [{
|
||||
selector: '[any-structural-directive]',
|
||||
standalone: true,
|
||||
}]
|
||||
}] });
|
||||
export class MyComponent {
|
||||
}
|
||||
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, isStandalone: true, selector: "my-component", ngImport: i0, template: `
|
||||
<div>
|
||||
<p *any-structural-directive animate.enter="slide">Sliding Content</p>
|
||||
</div>
|
||||
`, isInline: true, dependencies: [{ kind: "directive", type: AnyStructuralDirective, selector: "[any-structural-directive]" }] });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-component',
|
||||
imports: [AnyStructuralDirective],
|
||||
standalone: true,
|
||||
template: `
|
||||
<div>
|
||||
<p *any-structural-directive animate.enter="slide">Sliding Content</p>
|
||||
</div>
|
||||
`,
|
||||
}]
|
||||
}] });
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: animate_enter_with_structural_directive.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class AnyStructuralDirective {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<AnyStructuralDirective, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<AnyStructuralDirective, "[any-structural-directive]", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
export declare class MyComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never, true, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: animate_enter_with_string_host_bindings.js
|
||||
****************************************************************************************************/
|
||||
|
||||
+17
@@ -18,6 +18,23 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate animate enter instructions on element with a structural directive",
|
||||
"inputFiles": [
|
||||
"animate_enter_with_structural_directive.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"expected": "animate_enter_with_structural_directive_template.js",
|
||||
"generated": "animate_enter_with_structural_directive.js"
|
||||
}
|
||||
],
|
||||
"failureMessage": "Incorrect ɵɵanimateEnter() call"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate animate enter instructions with host binding and simple string",
|
||||
"inputFiles": [
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import {Component, Directive} from '@angular/core';
|
||||
|
||||
@Directive({
|
||||
selector: '[any-structural-directive]',
|
||||
standalone: true,
|
||||
})
|
||||
export class AnyStructuralDirective {}
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
imports: [AnyStructuralDirective],
|
||||
standalone: true,
|
||||
template: `
|
||||
<div>
|
||||
<p *any-structural-directive animate.enter="slide">Sliding Content</p>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import * as i0 from "@angular/core";
|
||||
function MyComponent_p_1_Template(rf, ctx) { if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "p");
|
||||
i0.ɵɵanimateEnter("slide");
|
||||
i0.ɵɵtext(1, "Sliding Content");
|
||||
i0.ɵɵelementEnd();
|
||||
} }
|
||||
…
|
||||
MyComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MyComponent, selectors: [["my-component"]], decls: 2, vars: 0, consts: [[4, "any-structural-directive"]], template: function MyComponent_Template(rf, ctx) { if (rf & 1) {
|
||||
i0.ɵɵelementStart(0, "div");
|
||||
i0.ɵɵtemplate(1, MyComponent_p_1_Template, 2, 0, "p", 0);
|
||||
i0.ɵɵelementEnd();
|
||||
} }, dependencies: [AnyStructuralDirective], encapsulation: 2 });
|
||||
@@ -961,6 +961,14 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
return directives;
|
||||
}
|
||||
|
||||
private filterAnimationAttributes(attributes: t.TextAttribute[]): t.TextAttribute[] {
|
||||
return attributes.filter((a) => !a.name.startsWith('animate.'));
|
||||
}
|
||||
|
||||
private filterAnimationInputs(attributes: t.BoundAttribute[]): t.BoundAttribute[] {
|
||||
return attributes.filter((a) => a.type !== BindingType.Animation);
|
||||
}
|
||||
|
||||
private wrapInTemplate(
|
||||
node: t.Element | t.Component | t.Content | t.Template,
|
||||
templateProperties: ParsedProperty[],
|
||||
@@ -986,8 +994,8 @@ class HtmlAstToIvyAst implements html.Visitor {
|
||||
};
|
||||
|
||||
if (node instanceof t.Element || node instanceof t.Component) {
|
||||
hoistedAttrs.attributes.push(...node.attributes);
|
||||
hoistedAttrs.inputs.push(...node.inputs);
|
||||
hoistedAttrs.attributes.push(...this.filterAnimationAttributes(node.attributes));
|
||||
hoistedAttrs.inputs.push(...this.filterAnimationInputs(node.inputs));
|
||||
hoistedAttrs.outputs.push(...node.outputs);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,3 +55,4 @@ export {
|
||||
export {Watch, WatchCleanupFn, WatchCleanupRegisterFn, createWatch} from './src/watch';
|
||||
export {setAlternateWeakRefImpl} from './src/weak_ref';
|
||||
export {untracked} from './src/untracked';
|
||||
export {runEffect, BASE_EFFECT_NODE, BaseEffectNode} from './src/effect';
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {
|
||||
consumerAfterComputation,
|
||||
consumerBeforeComputation,
|
||||
consumerPollProducersForChange,
|
||||
REACTIVE_NODE,
|
||||
ReactiveNode,
|
||||
} from './graph';
|
||||
|
||||
/**
|
||||
* An effect can, optionally, register a cleanup function. If registered, the cleanup is executed
|
||||
* before the next effect run. The cleanup function makes it possible to "cancel" any work that the
|
||||
* previous effect run might have started.
|
||||
*/
|
||||
export type EffectCleanupFn = () => void;
|
||||
|
||||
/**
|
||||
* A callback passed to the effect function that makes it possible to register cleanup logic.
|
||||
*/
|
||||
export type EffectCleanupRegisterFn = (cleanupFn: EffectCleanupFn) => void;
|
||||
|
||||
export interface BaseEffectNode extends ReactiveNode {
|
||||
hasRun: boolean;
|
||||
fn: () => void;
|
||||
destroy(): void;
|
||||
cleanup(): void;
|
||||
run(): void;
|
||||
}
|
||||
|
||||
export const BASE_EFFECT_NODE: Omit<BaseEffectNode, 'fn' | 'destroy' | 'cleanup' | 'run'> =
|
||||
/* @__PURE__ */ (() => ({
|
||||
...REACTIVE_NODE,
|
||||
consumerIsAlwaysLive: true,
|
||||
consumerAllowSignalWrites: true,
|
||||
dirty: true,
|
||||
hasRun: false,
|
||||
kind: 'effect',
|
||||
}))();
|
||||
|
||||
export function runEffect(node: BaseEffectNode) {
|
||||
node.dirty = false;
|
||||
if (node.hasRun && !consumerPollProducersForChange(node)) {
|
||||
return;
|
||||
}
|
||||
node.hasRun = true;
|
||||
const prevNode = consumerBeforeComputation(node);
|
||||
try {
|
||||
node.cleanup();
|
||||
node.fn();
|
||||
} finally {
|
||||
consumerAfterComputation(node, prevNode);
|
||||
}
|
||||
}
|
||||
@@ -166,6 +166,9 @@ export function ɵɵanimateEnter(value: string | Function): typeof ɵɵanimateEn
|
||||
|
||||
const tNode = getCurrentTNode()!;
|
||||
const nativeElement = getNativeByTNode(tNode, lView) as HTMLElement;
|
||||
|
||||
ngDevMode && assertElementNodes(nativeElement, 'animate.enter');
|
||||
|
||||
const renderer = lView[RENDERER];
|
||||
const ngZone = lView[INJECTOR]!.get(NgZone);
|
||||
|
||||
@@ -270,6 +273,8 @@ export function ɵɵanimateEnterListener(value: AnimationFunction): typeof ɵɵa
|
||||
const tNode = getCurrentTNode()!;
|
||||
const nativeElement = getNativeByTNode(tNode, lView) as HTMLElement;
|
||||
|
||||
ngDevMode && assertElementNodes(nativeElement, 'animate.enter');
|
||||
|
||||
cancelLeavingNodes(tNode, lView);
|
||||
|
||||
value.call(lView[CONTEXT], {target: nativeElement, animationComplete: noOpAnimationComplete});
|
||||
@@ -306,6 +311,8 @@ export function ɵɵanimateLeave(value: string | Function): typeof ɵɵanimateLe
|
||||
const tNode = getCurrentTNode()!;
|
||||
const nativeElement = getNativeByTNode(tNode, lView) as Element;
|
||||
|
||||
ngDevMode && assertElementNodes(nativeElement, 'animate.leave');
|
||||
|
||||
// This instruction is called in the update pass.
|
||||
const renderer = lView[RENDERER];
|
||||
const elementRegistry = getAnimationElementRemovalRegistry();
|
||||
@@ -374,9 +381,7 @@ export function ɵɵanimateLeaveListener(value: AnimationFunction): typeof ɵɵa
|
||||
const tView = getTView();
|
||||
const nativeElement = getNativeByTNode(tNode, lView) as Element;
|
||||
|
||||
if ((nativeElement as Node).nodeType !== Node.ELEMENT_NODE) {
|
||||
return ɵɵanimateLeaveListener;
|
||||
}
|
||||
ngDevMode && assertElementNodes(nativeElement, 'animate.leave');
|
||||
|
||||
const elementRegistry = getAnimationElementRemovalRegistry();
|
||||
ngDevMode &&
|
||||
@@ -524,6 +529,15 @@ function assertAnimationTypes(value: string | Function, instruction: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function assertElementNodes(nativeElement: Element, instruction: string) {
|
||||
if ((nativeElement as Node).nodeType !== Node.ELEMENT_NODE) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.ANIMATE_INVALID_VALUE,
|
||||
`'${instruction}' can only be used on an element node, got ${stringify((nativeElement as Node).nodeType)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function actually adds the classes that animate element that's leaving the DOM.
|
||||
* Once it finishes, it calls the remove function that was provided by the DOM renderer.
|
||||
|
||||
@@ -16,10 +16,8 @@ import {
|
||||
SIGNAL_NODE,
|
||||
type SignalNode,
|
||||
} from '../../../primitives/signals';
|
||||
|
||||
import {type Signal} from '../reactivity/api';
|
||||
import {type EffectCleanupFn, type EffectCleanupRegisterFn} from './effect';
|
||||
|
||||
import {type Signal} from '../reactivity/api';
|
||||
import {TracingService, TracingSnapshot} from '../../application/tracing';
|
||||
import {
|
||||
ChangeDetectionScheduler,
|
||||
|
||||
@@ -7,15 +7,13 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
REACTIVE_NODE,
|
||||
ReactiveNode,
|
||||
SIGNAL,
|
||||
consumerAfterComputation,
|
||||
consumerBeforeComputation,
|
||||
consumerDestroy,
|
||||
consumerPollProducersForChange,
|
||||
isInNotificationPhase,
|
||||
setActiveConsumer,
|
||||
BaseEffectNode,
|
||||
BASE_EFFECT_NODE,
|
||||
runEffect,
|
||||
} from '../../../primitives/signals';
|
||||
import {FLAGS, LViewFlags, LView, EFFECTS} from '../interfaces/view';
|
||||
import {markAncestorsForTraversal} from '../util/view_utils';
|
||||
@@ -191,17 +189,12 @@ export function effect(
|
||||
return effectRef;
|
||||
}
|
||||
|
||||
export interface EffectNode extends ReactiveNode, SchedulableEffect {
|
||||
hasRun: boolean;
|
||||
export interface EffectNode extends BaseEffectNode, SchedulableEffect {
|
||||
cleanupFns: EffectCleanupFn[] | undefined;
|
||||
injector: Injector;
|
||||
notifier: ChangeDetectionScheduler;
|
||||
|
||||
onDestroyFn: () => void;
|
||||
fn: (cleanupFn: EffectCleanupRegisterFn) => void;
|
||||
run(): void;
|
||||
destroy(): void;
|
||||
maybeCleanup(): void;
|
||||
}
|
||||
|
||||
export interface ViewEffectNode extends EffectNode {
|
||||
@@ -212,47 +205,27 @@ export interface RootEffectNode extends EffectNode {
|
||||
scheduler: EffectScheduler;
|
||||
}
|
||||
|
||||
export const BASE_EFFECT_NODE: Omit<EffectNode, 'fn' | 'destroy' | 'injector' | 'notifier'> =
|
||||
export const EFFECT_NODE: Omit<EffectNode, 'fn' | 'destroy' | 'injector' | 'notifier'> =
|
||||
/* @__PURE__ */ (() => ({
|
||||
...REACTIVE_NODE,
|
||||
consumerIsAlwaysLive: true,
|
||||
consumerAllowSignalWrites: true,
|
||||
dirty: true,
|
||||
hasRun: false,
|
||||
...BASE_EFFECT_NODE,
|
||||
cleanupFns: undefined,
|
||||
zone: null,
|
||||
kind: 'effect',
|
||||
onDestroyFn: noop,
|
||||
run(this: EffectNode): void {
|
||||
this.dirty = false;
|
||||
|
||||
if (ngDevMode && isInNotificationPhase()) {
|
||||
throw new Error(`Schedulers cannot synchronously execute watches while scheduling.`);
|
||||
}
|
||||
|
||||
if (this.hasRun && !consumerPollProducersForChange(this)) {
|
||||
return;
|
||||
}
|
||||
this.hasRun = true;
|
||||
|
||||
const registerCleanupFn: EffectCleanupRegisterFn = (cleanupFn) =>
|
||||
(this.cleanupFns ??= []).push(cleanupFn);
|
||||
|
||||
const prevNode = consumerBeforeComputation(this);
|
||||
|
||||
// We clear `setIsRefreshingViews` so that `markForCheck()` within the body of an effect will
|
||||
// cause CD to reach the component in question.
|
||||
const prevRefreshingViews = setIsRefreshingViews(false);
|
||||
try {
|
||||
this.maybeCleanup();
|
||||
this.fn(registerCleanupFn);
|
||||
runEffect(this);
|
||||
} finally {
|
||||
setIsRefreshingViews(prevRefreshingViews);
|
||||
consumerAfterComputation(this, prevNode);
|
||||
}
|
||||
},
|
||||
|
||||
maybeCleanup(this: EffectNode): void {
|
||||
cleanup(this: EffectNode): void {
|
||||
if (!this.cleanupFns?.length) {
|
||||
return;
|
||||
}
|
||||
@@ -273,7 +246,7 @@ export const BASE_EFFECT_NODE: Omit<EffectNode, 'fn' | 'destroy' | 'injector' |
|
||||
|
||||
export const ROOT_EFFECT_NODE: Omit<RootEffectNode, 'fn' | 'scheduler' | 'notifier' | 'injector'> =
|
||||
/* @__PURE__ */ (() => ({
|
||||
...BASE_EFFECT_NODE,
|
||||
...EFFECT_NODE,
|
||||
consumerMarkedDirty(this: RootEffectNode) {
|
||||
this.scheduler.schedule(this);
|
||||
this.notifier.notify(NotificationSource.RootEffect);
|
||||
@@ -281,14 +254,14 @@ export const ROOT_EFFECT_NODE: Omit<RootEffectNode, 'fn' | 'scheduler' | 'notifi
|
||||
destroy(this: RootEffectNode) {
|
||||
consumerDestroy(this);
|
||||
this.onDestroyFn();
|
||||
this.maybeCleanup();
|
||||
this.cleanup();
|
||||
this.scheduler.remove(this);
|
||||
},
|
||||
}))();
|
||||
|
||||
export const VIEW_EFFECT_NODE: Omit<ViewEffectNode, 'fn' | 'view' | 'injector' | 'notifier'> =
|
||||
/* @__PURE__ */ (() => ({
|
||||
...BASE_EFFECT_NODE,
|
||||
...EFFECT_NODE,
|
||||
consumerMarkedDirty(this: ViewEffectNode): void {
|
||||
this.view[FLAGS] |= LViewFlags.HasChildViewsToRefresh;
|
||||
markAncestorsForTraversal(this.view);
|
||||
@@ -297,7 +270,7 @@ export const VIEW_EFFECT_NODE: Omit<ViewEffectNode, 'fn' | 'view' | 'injector' |
|
||||
destroy(this: ViewEffectNode): void {
|
||||
consumerDestroy(this);
|
||||
this.onDestroyFn();
|
||||
this.maybeCleanup();
|
||||
this.cleanup();
|
||||
this.view[EFFECTS]?.delete(this);
|
||||
},
|
||||
}))();
|
||||
@@ -311,7 +284,7 @@ export function createViewEffect(
|
||||
node.view = view;
|
||||
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
||||
node.notifier = notifier;
|
||||
node.fn = fn;
|
||||
node.fn = createEffectFn(node, fn);
|
||||
|
||||
view[EFFECTS] ??= new Set();
|
||||
view[EFFECTS].add(node);
|
||||
@@ -326,7 +299,7 @@ export function createRootEffect(
|
||||
notifier: ChangeDetectionScheduler,
|
||||
): RootEffectNode {
|
||||
const node = Object.create(ROOT_EFFECT_NODE) as RootEffectNode;
|
||||
node.fn = fn;
|
||||
node.fn = createEffectFn(node, fn);
|
||||
node.scheduler = scheduler;
|
||||
node.notifier = notifier;
|
||||
node.zone = typeof Zone !== 'undefined' ? Zone.current : null;
|
||||
@@ -334,3 +307,9 @@ export function createRootEffect(
|
||||
node.notifier.notify(NotificationSource.RootEffect);
|
||||
return node;
|
||||
}
|
||||
|
||||
function createEffectFn(node: EffectNode, fn: (onCleanup: EffectCleanupRegisterFn) => void) {
|
||||
return () => {
|
||||
fn((cleanupFn) => (node.cleanupFns ??= []).push(cleanupFn));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,11 +4,10 @@ load("@rules_angular//src/ts_project:index.bzl", _ts_project = "ts_project")
|
||||
def ts_project(
|
||||
name,
|
||||
deps = [],
|
||||
srcs = [],
|
||||
tsconfig = None,
|
||||
testonly = False,
|
||||
visibility = None,
|
||||
# TODO: Enable this for all `ts_project` targets at end of migration.
|
||||
ignore_strict_deps = True,
|
||||
rule_impl = _ts_project,
|
||||
**kwargs):
|
||||
rule_impl(
|
||||
@@ -17,13 +16,14 @@ def ts_project(
|
||||
declaration = True,
|
||||
tsconfig = tsconfig,
|
||||
visibility = visibility,
|
||||
srcs = srcs,
|
||||
deps = deps,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if not ignore_strict_deps:
|
||||
strict_deps_test(
|
||||
name = "%s_strict_deps_test" % name,
|
||||
srcs = kwargs.get("srcs", []),
|
||||
deps = deps,
|
||||
)
|
||||
strict_deps_test(
|
||||
name = "%s_strict_deps_test" % name,
|
||||
srcs = srcs,
|
||||
tsconfig = tsconfig,
|
||||
deps = deps,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user