/
githubmirror
/
angular
Обзор
Документация
Войти
/
githubmirror
/
angular
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
packages/core/src/render3/interfaces/node.ts
1 013 строк
35 KB
SkyZeroZx
fix(core): sanitize host bindings on concrete hosts (#69558)
29 июл 2026, 18:36
29 июл 2026, 18:36
d06e374
Код
Авторство
О чём код?
/** * @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 {AbstractType, Type} from '../../interface/type'; import {KeyValueArray} from '../../util/array_utils'; import {TStylingRange} from '../interfaces/styling'; import {AttributeMarker} from './attribute_marker'; import {ForeignComponent} from '../../interface/foreign_component'; import {TIcu} from './i18n'; import {CssSelector} from './projection'; import {RNode} from './renderer_dom'; import type {LView, TView} from './view'; /** * Internal tag name used for a root host `TNode` when Angular creates a component against an * existing host element. The concrete DOM tag is resolved from the native element at runtime. */ export const enum TNodeName { DynamicHost = '#host', } /** * TNodeType corresponds to the {@link TNode} `type` property. * * NOTE: type IDs are such that we use each bit to denote a type. This is done so that we can easily * check if the `TNode` is of more than one type. * * `if (tNode.type === TNodeType.Text || tNode.type === TNode.Element)` * can be written as: * `if (tNode.type & (TNodeType.Text | TNodeType.Element))` * * However any given `TNode` can only be of one type. */ export const enum TNodeType { /** * The TNode contains information about a DOM element aka {@link RText}. */ Text = 0b1, /** * The TNode contains information about a DOM element aka {@link RElement}. */ Element = 0b10, /** * The TNode contains information about an {@link LContainer} for embedded views. */ Container = 0b100, /** * The TNode contains information about an `<ng-container>` element {@link RNode}. */ ElementContainer = 0b1000, /** * The TNode contains information about an `<ng-content>` projection */ Projection = 0b10000, /** * The TNode contains information about an ICU comment used in `i18n`. */ Icu = 0b100000, /** * Special node type representing a placeholder for future `TNode` at this location. * * I18n translation blocks are created before the element nodes which they contain. (I18n blocks * can span over many elements.) Because i18n `TNode`s (representing text) are created first they * often may need to point to element `TNode`s which are not yet created. In such a case we create * a `Placeholder` `TNode`. This allows the i18n to structurally link the `TNode`s together * without knowing any information about the future nodes which will be at that location. * * On `firstCreatePass` When element instruction executes it will try to create a `TNode` at that * location. Seeing a `Placeholder` `TNode` already there tells the system that it should reuse * existing `TNode` (rather than create a new one) and just update the missing information. */ Placeholder = 0b1000000, /** * The TNode contains information about a `@let` declaration. */ LetDeclaration = 0b10000000, /** * The TNode contains a control directive's update function. */ ControlDirective = 0b100000000, // Combined Types These should never be used for `TNode.type` only as a useful way to check // if `TNode.type` is one of several choices. // See: https://github.com/microsoft/TypeScript/issues/35875 why we can't refer to existing enum. AnyRNode = 0b11, // Text | Element AnyContainer = 0b1100, // Container | ElementContainer } /** * Converts `TNodeType` into human readable text. * Make sure this matches with `TNodeType` */ export function toTNodeTypeAsString(tNodeType: TNodeType): string { let text = ''; tNodeType & TNodeType.Text && (text += '|Text'); tNodeType & TNodeType.Element && (text += '|Element'); tNodeType & TNodeType.Container && (text += '|Container'); tNodeType & TNodeType.ElementContainer && (text += '|ElementContainer'); tNodeType & TNodeType.Projection && (text += '|Projection'); tNodeType & TNodeType.Icu && (text += '|IcuContainer'); tNodeType & TNodeType.Placeholder && (text += '|Placeholder'); tNodeType & TNodeType.LetDeclaration && (text += '|LetDeclaration'); return text.length > 0 ? text.substring(1) : text; } /** * Helper function to detect if a given value matches a `TNode` shape. * * The logic uses the `insertBeforeIndex` and its possible values as * a way to differentiate a TNode shape from other types of objects * within the `TView.data`. This is not a perfect check, but it can * be a reasonable differentiator, since we control the shapes of objects * within `TView.data`. */ export function isTNodeShape(value: unknown): value is TNode { return ( value != null && typeof value === 'object' && ((value as TNode).insertBeforeIndex === null || typeof (value as TNode).insertBeforeIndex === 'number' || Array.isArray((value as TNode).insertBeforeIndex)) ); } export function isLetDeclaration(tNode: TNode): boolean { return !!(tNode.type & TNodeType.LetDeclaration); } /** * Corresponds to the TNode.flags property. */ export const enum TNodeFlags { /** Bit #1 - This bit is set if the node is a host for any directive (including a component) */ isDirectiveHost = 1 << 0, /** Bit #2 - This bit is set if the node has been projected */ isProjected = 1 << 1, /** Bit #3 - This bit is set if any directive on this node has content queries */ hasContentQuery = 1 << 2, /** Bit #4 - This bit is set if the node has any "class" inputs */ hasClassInput = 1 << 3, /** Bit #5 - This bit is set if the node has any "style" inputs */ hasStyleInput = 1 << 4, /** Bit #6 - This bit is set if the node has been detached by i18n */ isDetached = 1 << 5, /** * Bit #7 - This bit is set if the node has directives with host bindings. * * This flags allows us to guard host-binding logic and invoke it only on nodes * that actually have directives with host bindings. */ hasHostBindings = 1 << 6, /** * Bit #8 - This bit is set if the node is a located inside skip hydration block. */ inSkipHydrationBlock = 1 << 7, /** * Bit #9 - This bit is set if the node is a start of a set of control flow blocks. */ isControlFlowStart = 1 << 8, /** * Bit #10 - This bit is set if the node is within a set of control flow blocks. */ isInControlFlow = 1 << 9, /** * Bit #11 - This bit is set if the node hosts a custom control component. * * A custom control component's model property is named `value`. */ isFormValueControl = 1 << 10, /** * Bit #12 - This bit is set if the node hosts a custom checkbox component. * * A custom checkbox component's model property is named `checked`. */ isFormCheckboxControl = 1 << 11, /** * Bit #13 - This bit is set if the node hosts an interoperable control implementation. * * This is used to bind to a `ControlValueAccessor` from `@angular/forms`. */ isPassThroughControl = 1 << 12, } /** * Corresponds to the TNode.providerIndexes property. */ export const enum TNodeProviderIndexes { /** The index of the first provider on this node is encoded on the least significant bits. */ ProvidersStartIndexMask = 0b00000000000011111111111111111111, /** * The count of view providers from the component on this node is * encoded on the 20 most significant bits. */ CptViewProvidersCountShift = 20, CptViewProvidersCountShifter = 0b00000000000100000000000000000000, } /** * A combination of: * - Attribute names and values. * - Special markers acting as flags to alter attributes processing. * - Parsed ngProjectAs selectors. */ export type TAttributes = (string | AttributeMarker | CssSelector)[]; /** * Constants that are associated with a view. Includes: * - Attribute arrays. * - Local definition arrays. * - Translated messages (i18n). * - Foreign components. */ export type TConstants = (TAttributes | string | ForeignComponent<any, any>)[]; /** * Factory function that returns an array of consts. Consts can be represented as a function in * case any additional statements are required to define consts in the list. An example is i18n * where additional i18n calls are generated, which should be executed when consts are requested * for the first time. */ export type TConstantsFactory = () => TConstants; /** * TConstants type that describes how the `consts` field is generated on ComponentDef: it can be * either an array or a factory function that returns that array. */ export type TConstantsOrFactory = TConstants | TConstantsFactory; /** * Binding data (flyweight) for a particular node that is shared between all templates * of a specific type. * * If a property is: * - PropertyAliases: that property's data was generated and this is it * - Null: that property's data was already generated and nothing was found. * - Undefined: that property's data has not yet been generated * * see: https://en.wikipedia.org/wiki/Flyweight_pattern for more on the Flyweight pattern */ export interface TNode { /** The type of the TNode. See TNodeType. */ type: TNodeType; /** * Index of the TNode in TView.data and corresponding native element in LView. * * This is necessary to get from any TNode to its corresponding native element when * traversing the node tree. * * If index is -1, this is a dynamically created container node or embedded view node. */ index: number; /** * Insert before existing DOM node index. * * When DOM nodes are being inserted, normally they are being appended as they are created. * Under i18n case, the translated text nodes are created ahead of time as part of the * `ɵɵi18nStart` instruction which means that this `TNode` can't just be appended and instead * needs to be inserted using `insertBeforeIndex` semantics. * * Additionally sometimes it is necessary to insert new text nodes as a child of this `TNode`. In * such a case the value stores an array of text nodes to insert. * * Example: * ```html * <div i18n> * Hello <span>World</span>! * </div> * ``` * In the above example the `ɵɵi18nStart` instruction can create `Hello `, `World` and `!` text * nodes. It can also insert `Hello ` and `!` text node as a child of `<div>`, but it can't * insert `World` because the `<span>` node has not yet been created. In such a case the * `<span>` `TNode` will have an array which will direct the `<span>` to not only insert * itself in front of `!` but also to insert the `World` (created by `ɵɵi18nStart`) into * `<span>` itself. * * Pseudo code: * ```ts * if (insertBeforeIndex === null) { * // append as normal * } else if (Array.isArray(insertBeforeIndex)) { * // First insert current `TNode` at correct location * const currentNode = lView[this.index]; * parentNode.insertBefore(currentNode, lView[this.insertBeforeIndex[0]]); * // Now append all of the children * for(let i=1; i<this.insertBeforeIndex; i++) { * currentNode.appendChild(lView[this.insertBeforeIndex[i]]); * } * } else { * parentNode.insertBefore(lView[this.index], lView[this.insertBeforeIndex]) * } * ``` * - null: Append as normal using `parentNode.appendChild` * - `number`: Append using * `parentNode.insertBefore(lView[this.index], lView[this.insertBeforeIndex])` * * *Initialization* * * Because `ɵɵi18nStart` executes before nodes are created, on `TView.firstCreatePass` it is not * possible for `ɵɵi18nStart` to set the `insertBeforeIndex` value as the corresponding `TNode` * has not yet been created. For this reason the `ɵɵi18nStart` creates a `TNodeType.Placeholder` * `TNode` at that location. See `TNodeType.Placeholder` for more information. */ insertBeforeIndex: InsertBeforeIndex; /** * The index of the closest injector in this node's LView. * * If the index === -1, there is no injector on this node or any ancestor node in this view. * * If the index !== -1, it is the index of this node's injector OR the index of a parent * injector in the same view. We pass the parent injector index down the node tree of a view so * it's possible to find the parent injector without walking a potentially deep node tree. * Injector indices are not set across view boundaries because there could be multiple component * hosts. * * If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent * injector. */ injectorIndex: number; /** Stores starting index of the directives. */ directiveStart: number; /** * Stores final exclusive index of the directives. * * The area right behind the `directiveStart-directiveEnd` range is used to allocate the * `HostBindingFunction` `vars` (or null if no bindings.) Therefore `directiveEnd` is used to set * `LFrame.bindingRootIndex` before `HostBindingFunction` is executed. */ directiveEnd: number; /** * Offset from the `directiveStart` at which the component (one at most) of the node is stored. * Set to -1 if no components have been applied to the node. Component index can be found using * `directiveStart + componentOffset`. */ componentOffset: number; /** * Index at which the signal forms field directive is stored. * Value is set to -1 if there are no field directives. */ controlDirectiveIndex: number; /** * Index at which the custom control directive is stored. * Value is set to -1 if there is no custom control directive. */ customControlIndex: number; /** * Stores the last directive which had a styling instruction. * * Initial value of this is `-1` which means that no `hostBindings` styling instruction has * executed. As `hostBindings` instructions execute they set the value to the index of the * `DirectiveDef` which contained the last `hostBindings` styling instruction. * * Valid values are: * - `-1` No `hostBindings` instruction has executed. * - `directiveStart <= directiveStylingLast < directiveEnd`: Points to the `DirectiveDef` of * the last styling instruction which executed in the `hostBindings`. * * This data is needed so that styling instructions know which static styling data needs to be * collected from the `DirectiveDef.hostAttrs`. A styling instruction needs to collect all data * since last styling instruction. */ directiveStylingLast: number; /** * Stores indexes of property bindings. This field is only set in the ngDevMode and holds * indexes of property bindings so TestBed can get bound property metadata for a given node. */ propertyBindings: number[] | null; /** * Stores if Node isComponent, isProjected, hasContentQuery, hasClassInput and hasStyleInput * etc. */ flags: TNodeFlags; /** * This number stores two values using its bits: * * - the index of the first provider on that node (first 16 bits) * - the count of view providers from the component on this node (last 16 bits) */ // TODO(misko): break this into actual vars. providerIndexes: TNodeProviderIndexes; /** * The value name associated with this node. * if type: * `TNodeType.Text`: text value * `TNodeType.Element`: tag name * `TNodeType.ICUContainer`: `TIcu` */ value: any; /** * The namespace associated with this node. */ namespace: string | null; /** * Attributes associated with an element. We need to store attributes to support various * use-cases (attribute injection, content projection with selectors, directives matching). * Attributes are stored statically because reading them from the DOM would be way too slow for * content projection and queries. * * Since attrs will always be calculated first, they will never need to be marked undefined by * other instructions. * * For regular attributes a name of an attribute and its value alternate in the array. * e.g. ['role', 'checkbox'] * This array can contain flags that will indicate "special attributes" (attributes with * namespaces, attributes extracted from bindings and outputs). */ attrs: TAttributes | null; /** * Same as `TNode.attrs` but contains merged data across all directive host bindings. * * We need to keep `attrs` as unmerged so that it can be used for attribute selectors. * We merge attrs here so that it can be used in a performant way for initial rendering. * * The `attrs` are merged in first pass in following order: * - Component's `hostAttrs` * - Directives' `hostAttrs` * - Template `TNode.attrs` associated with the current `TNode`. */ mergedAttrs: TAttributes | null; /** * A set of local names under which a given element is exported in a template and * visible to queries. An entry in this array can be created for different reasons: * - an element itself is referenced, ex.: `<div #foo>` * - a component is referenced, ex.: `<my-cmpt #foo>` * - a directive is referenced, ex.: `<my-cmpt #foo="directiveExportAs">`. * * A given element might have different local names and those names can be associated * with a directive. We store local names at even indexes while odd indexes are reserved * for directive index in a view (or `-1` if there is no associated directive). * * Some examples: * - `<div #foo>` => `["foo", -1]` * - `<my-cmpt #foo>` => `["foo", myCmptIdx]` * - `<my-cmpt #foo #bar="directiveExportAs">` => `["foo", myCmptIdx, "bar", directiveIdx]` * - `<div #foo #bar="directiveExportAs">` => `["foo", -1, "bar", directiveIdx]` */ localNames: (string | number)[] | null; /** Information about input properties that need to be set once from attribute data. */ initialInputs: InitialInputData | null; /** * Input data for all directives on this node. `null` means that there are no directives with * inputs on this node. */ inputs: NodeInputBindings | null; /** * Input data for host directives applied to the node. */ hostDirectiveInputs: HostDirectiveInputs | null; /** * Output data for all directives on this node. `null` means that there are no directives with * outputs on this node. */ outputs: NodeOutputBindings | null; /** * Input data for host directives applied to the node. */ hostDirectiveOutputs: HostDirectiveOutputs | null; /** * Mapping between directive classes applied to the node and their indexes. */ directiveToIndex: DirectiveIndexMap | null; /** * The TView attached to this node. * * If this TNode corresponds to an LContainer with a template (e.g. structural * directive), the template's TView will be stored here. * * If this TNode corresponds to an element, tView will be `null`. */ tView: TView | null; /** * The next sibling node. Necessary so we can propagate through the root nodes of a view * to insert them or remove them from the DOM. */ next: TNode | null; /** * The previous sibling node. * This simplifies operations when we need a pointer to the previous node. */ prev: TNode | null; /** * The next projected sibling. Since in Angular content projection works on the node-by-node * basis the act of projecting nodes might change nodes relationship at the insertion point * (target view). At the same time we need to keep initial relationship between nodes as * expressed in content view. */ projectionNext: TNode | null; /** * First child of the current node. * * For component nodes, the child will always be a ContentChild (in same view). * For embedded view nodes, the child will be in their child view. */ child: TNode | null; /** * Parent node (in the same view only). * * We need a reference to a node's parent so we can append the node to its parent's native * element at the appropriate time. * * If the parent would be in a different view (e.g. component host), this property will be null. * It's important that we don't try to cross component boundaries when retrieving the parent * because the parent will change (e.g. index, attrs) depending on where the component was * used (and thus shouldn't be stored on TNode). In these cases, we retrieve the parent through * LView.node instead (which will be instance-specific). * * If this is an inline view node (V), the parent will be its container. */ parent: TElementNode | TContainerNode | null; /** * List of projected TNodes for a given component host element OR index into the said nodes. * * For easier discussion assume this example: * `<parent>`'s view definition: * ```html * <child id="c1">content1</child> * <child id="c2"><span>content2</span></child> * ``` * `<child>`'s view definition: * ```html * <ng-content id="cont1"></ng-content> * ``` * * If `Array.isArray(projection)` then `TNode` is a host element: * - `projection` stores the content nodes which are to be projected. * - The nodes represent categories defined by the selector: For example: * `<ng-content/><ng-content select="abc"/>` would represent the heads for `<ng-content/>` * and `<ng-content select="abc"/>` respectively. * - The nodes we store in `projection` are heads only, we used `.next` to get their * siblings. * - The nodes `.next` is sorted/rewritten as part of the projection setup. * - `projection` size is equal to the number of projections `<ng-content>`. The size of * `c1` will be `1` because `<child>` has only one `<ng-content>`. * - we store `projection` with the host (`c1`, `c2`) rather than the `<ng-content>` (`cont1`) * because the same component (`<child>`) can be used in multiple locations (`c1`, `c2`) and * as a result have different set of nodes to project. * - without `projection` it would be difficult to efficiently traverse nodes to be projected. * * If `typeof projection == 'number'` then `TNode` is a `<ng-content>` element: * - `projection` is an index of the host's `projection`Nodes. * - This would return the first head node to project: * `getHost(currentTNode).projection[currentTNode.projection]`. * - When projecting nodes the parent node retrieved may be a `<ng-content>` node, in which case * the process is recursive in nature. * * If `projection` is of type `RNode[][]` than we have a collection of native nodes passed as * projectable nodes during dynamic component creation. */ projection: (TNode | RNode[])[] | number | null; /** * A collection of all `style` static values for an element (including from host). * * This field will be populated if and when: * * - There are one or more initial `style`s on an element (e.g. `<div style="width:200px;">`) * - There are one or more initial `style`s on a directive/component host * (e.g. `@Directive({host: {style: "width:200px;" } }`) */ styles: string | null; /** * A collection of all `style` static values for an element excluding host sources. * * Populated when there are one or more initial `style`s on an element * (e.g. `<div style="width:200px;">`) * Must be stored separately from `tNode.styles` to facilitate setting directive * inputs that shadow the `style` property. If we used `tNode.styles` as is for shadowed inputs, * we would feed host styles back into directives as "inputs". If we used `tNode.attrs`, we * would have to concatenate the attributes on every template pass. Instead, we process once on * first create pass and store here. */ stylesWithoutHost: string | null; /** * A `KeyValueArray` version of residual `styles`. * * When there are styling instructions than each instruction stores the static styling * which is of lower priority than itself. This means that there may be a higher priority * styling than the instruction. * * Imagine: * ```angular-ts * <div style="color: highest;" my-dir> * * @Directive({ * host: { * style: 'color: lowest; ', * '[styles.color]': 'exp' // ɵɵstyleProp('color', ctx.exp); * } * }) * ``` * * In the above case: * - `color: lowest` is stored with `ɵɵstyleProp('color', ctx.exp);` instruction * - `color: highest` is the residual and is stored here. * * - `undefined': not initialized. * - `null`: initialized but `styles` is `null` * - `KeyValueArray`: parsed version of `styles`. */ residualStyles: KeyValueArray<any> | undefined | null; /** * A collection of all class static values for an element (including from host). * * This field will be populated if and when: * * - There are one or more initial classes on an element (e.g. `<div class="one two three">`) * - There are one or more initial classes on an directive/component host * (e.g. `@Directive({host: {class: "SOME_CLASS" } }`) */ classes: string | null; /** * A collection of all class static values for an element excluding host sources. * * Populated when there are one or more initial classes on an element * (e.g. `<div class="SOME_CLASS">`) * Must be stored separately from `tNode.classes` to facilitate setting directive * inputs that shadow the `class` property. If we used `tNode.classes` as is for shadowed * inputs, we would feed host classes back into directives as "inputs". If we used * `tNode.attrs`, we would have to concatenate the attributes on every template pass. Instead, * we process once on first create pass and store here. */ classesWithoutHost: string | null; /** * A `KeyValueArray` version of residual `classes`. * * Same as `TNode.residualStyles` but for classes. * * - `undefined': not initialized. * - `null`: initialized but `classes` is `null` * - `KeyValueArray`: parsed version of `classes`. */ residualClasses: KeyValueArray<any> | undefined | null; /** * Stores the head/tail index of the class bindings. * * - If no bindings, the head and tail will both be 0. * - If there are template bindings, stores the head/tail of the class bindings in the template. * - If no template bindings but there are host bindings, the head value will point to the last * host binding for "class" (not the head of the linked list), tail will be 0. * * See: `style_binding_list.ts` for details. * * This is used by `insertTStylingBinding` to know where the next styling binding should be * inserted so that they can be sorted in priority order. */ classBindings: TStylingRange; /** * Stores the head/tail index of the class bindings. * * - If no bindings, the head and tail will both be 0. * - If there are template bindings, stores the head/tail of the style bindings in the template. * - If no template bindings but there are host bindings, the head value will point to the last * host binding for "style" (not the head of the linked list), tail will be 0. * * See: `style_binding_list.ts` for details. * * This is used by `insertTStylingBinding` to know where the next styling binding should be * inserted so that they can be sorted in priority order. */ styleBindings: TStylingRange; } /** * See `TNode.insertBeforeIndex` */ export type InsertBeforeIndex = null | number | number[]; /** Static data for an element */ export interface TElementNode extends TNode { /** Index in the data[] array */ index: number; child: TElementNode | TTextNode | TElementContainerNode | TContainerNode | TProjectionNode | null; /** * Element nodes will have parents unless they are the first node of a component or * embedded view (which means their parent is in a different view and must be * retrieved using viewData[HOST_NODE]). */ parent: TElementNode | TElementContainerNode | null; tView: null; /** * If this is a component TNode with projection, this will be an array of projected * TNodes or native nodes (see TNode.projection for more info). If it's a regular element node * or a component without projection, it will be null. */ projection: (TNode | RNode[])[] | null; /** * Stores TagName */ value: string; } /** Static data for a text node */ export interface TTextNode extends TNode { /** Index in the data[] array */ index: number; child: null; /** * Text nodes will have parents unless they are the first node of a component or * embedded view (which means their parent is in a different view and must be * retrieved using LView.node). */ parent: TElementNode | TElementContainerNode | null; tView: null; projection: null; } /** Static data for an LContainer */ export interface TContainerNode extends TNode { /** * Index in the data[] array. * * If it's -1, this is a dynamically created container node that isn't stored in * data[] (e.g. when you inject ViewContainerRef) . */ index: number; child: null; /** * Container nodes will have parents unless: * * - They are the first node of a component or embedded view * - They are dynamically created */ parent: TElementNode | TElementContainerNode | null; tView: TView | null; projection: null; value: null; } /** Static data for an <ng-container> */ export interface TElementContainerNode extends TNode { /** Index in the LView[] array. */ index: number; child: TElementNode | TTextNode | TContainerNode | TElementContainerNode | TProjectionNode | null; parent: TElementNode | TElementContainerNode | null; tView: null; projection: null; } /** Static data for an ICU expression */ export interface TIcuContainerNode extends TNode { /** Index in the LView[] array. */ index: number; child: null; parent: TElementNode | TElementContainerNode | null; tView: null; projection: null; value: TIcu; } /** Static data for an LProjectionNode */ export interface TProjectionNode extends TNode { /** Index in the data[] array */ child: null; /** * Projection nodes will have parents unless they are the first node of a component * or embedded view (which means their parent is in a different view and must be * retrieved using LView.node). */ parent: TElementNode | TElementContainerNode | null; tView: null; /** Index of the projection node. (See TNode.projection for more info.) */ projection: number; value: null; } /** * Static data for a `@let` declaration. This node is necessary, because the expression of a * `@let` declaration can contain code that uses the node injector (e.g. pipes). In order for * the node injector to work, it needs this `TNode`. */ export interface TLetDeclarationNode extends TNode { index: number; child: null; parent: TElementNode | TElementContainerNode | null; tView: null; projection: null; value: null; // TODO(crisbeto): capture the name here? Might come in handy for the dev tools. } /** * A union type representing all TNode types that can host a directive. */ export type TDirectiveHostNode = TElementNode | TContainerNode | TElementContainerNode; /** * Maps the public names of outputs available on a specific node to the index * of the directive instance that defines the output, for example: * * ``` * { * "publicName": [0, 5] * } * ``` */ export type NodeOutputBindings = Record<string, number[]>; /** * Maps the public names of inputs applied to a specific node to the index of the * directive instance to which the input value should be written, for example: * * ``` * { * "publicName": [0, 5] * } * ``` */ export type NodeInputBindings = Record<string, number[]>; /** * This array contains information about input properties that * need to be set once from attribute data. It's ordered by * directive index (relative to element) so it's simple to * look up a specific directive's initial input data. * * Within each sub-array: * * i+0: public name * i+1: initial value * * If a directive on a node does not have any input properties * that should be set from attributes, its index is set to null * to avoid a sparse array. * * e.g. [null, ['role-min', 'minified-input', 'button']] */ export type InitialInputData = (InitialInputs | null)[]; /** * Used by InitialInputData to store input properties * that should be set once from attributes. * * i+0: attribute name * i+1: minified/internal input name * i+2: input flags * i+3: initial value * * e.g. ['role-min', 'minified-input', 'button'] */ export type InitialInputs = string[]; /** * Represents inputs coming from a host directive and exposed on a TNode. * * - The key is the public name of an input as it is exposed on the specific node. * - The value is an array where: * - i+0: Index of the host directive that should be written to. * - i+1: Public name of the input as it was defined on the host directive before aliasing. */ export type HostDirectiveInputs = Record<string, (number | string)[]>; /** * Represents outputs coming from a host directive and exposed on a TNode. * * - The key is the public name of an output as it is exposed on the specific node. * - The value is an array where: * - i+0: Index of the host directive on which the output is defined.. * - i+1: Public name of the output as it was defined on the host directive before aliasing. */ export type HostDirectiveOutputs = Record<string, (number | string)[]>; /** * Represents a map between a class reference and the index at which its directive is available on * a specific TNode. The value can be either: * 1. A number means that there's only one selector-matched directive on the node and it * doesn't have any host directives. * 2. An array means that there's a selector-matched directive and it has host directives. * The array is structured as follows: * - 0: Index of the selector-matched directive. * - 1: Start index of the range within which the host directives are defined. * - 2: End of the host directive range. * * Example: * ``` * Map { * [NoHostDirectives]: 5, * [HasHostDirectives]: [10, 6, 8], * } * ``` */ export type DirectiveIndexMap = Map< Type<unknown> | AbstractType<unknown>, number | [directiveIndex: number, hostDirectivesStart: number, hostDirectivesEnd: number] >; /** * Type representing a set of TNodes that can have local refs (`#foo`) placed on them. */ export type TNodeWithLocalRefs = TContainerNode | TElementNode | TElementContainerNode; /** * Type for a function that extracts a value for a local refs. * Example: * - `<div #nativeDivEl>` - `nativeDivEl` should point to the native `<div>` element; * - `<ng-template #tplRef>` - `tplRef` should point to the `TemplateRef` instance; */ export type LocalRefExtractor = (tNode: TNodeWithLocalRefs, currentView: LView) => any; /** * Returns `true` if the `TNode` has a directive which has `@Input()` for `class` binding. * * ```html * <div my-dir [class]="exp"></div> * ``` * and * ```ts * @Directive({ * }) * class MyDirective { * @Input() * class: string; * } * ``` * * In the above case it is necessary to write the reconciled styling information into the * directive's input. * * @param tNode */ export function hasClassInput(tNode: TNode) { return (tNode.flags & TNodeFlags.hasClassInput) !== 0; } /** * Returns `true` if the `TNode` has a directive which has `@Input()` for `style` binding. * * ```html * <div my-dir [style]="exp"></div> * ``` * and * ```ts * @Directive({ * }) * class MyDirective { * @Input() * class: string; * } * ``` * * In the above case it is necessary to write the reconciled styling information into the * directive's input. * * @param tNode */ export function hasStyleInput(tNode: TNode) { return (tNode.flags & TNodeFlags.hasStyleInput) !== 0; }