2019-04-01 15:36:43 -07:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-04-01 15:36:43 -07:00
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
2019-08-15 13:42:17 -07:00
|
|
|
import {newArray} from '../../util/array_utils';
|
2019-04-08 22:47:23 +02:00
|
|
|
import {TAttributes, TElementNode, TNode, TNodeType} from '../interfaces/node';
|
2019-05-21 22:00:47 +02:00
|
|
|
import {ProjectionSlots} from '../interfaces/projection';
|
2020-01-30 14:57:44 -08:00
|
|
|
import {DECLARATION_COMPONENT_VIEW, T_HOST} from '../interfaces/view';
|
2019-06-07 20:46:11 -07:00
|
|
|
import {applyProjection} from '../node_manipulation';
|
2019-05-21 22:00:47 +02:00
|
|
|
import {getProjectAsAttrValue, isNodeMatchingSelectorList, isSelectorInSelectorList} from '../node_selector_matcher';
|
2020-09-14 13:43:44 -07:00
|
|
|
import {getLView, getTView, setCurrentTNodeAsNotParent} from '../state';
|
2019-05-15 22:51:40 -07:00
|
|
|
import {getOrCreateTNode} from './shared';
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
2019-06-07 20:46:11 -07:00
|
|
|
|
2019-05-21 22:00:47 +02:00
|
|
|
/**
|
|
|
|
* Checks a given node against matching projection slots and returns the
|
|
|
|
* determined slot index. Returns "null" if no slot matched the given node.
|
|
|
|
*
|
|
|
|
* This function takes into account the parsed ngProjectAs selector from the
|
|
|
|
* node's attributes. If present, it will check whether the ngProjectAs selector
|
|
|
|
* matches any of the projection slot selectors.
|
|
|
|
*/
|
|
|
|
export function matchingProjectionSlotIndex(tNode: TNode, projectionSlots: ProjectionSlots): number|
|
|
|
|
null {
|
|
|
|
let wildcardNgContentIndex = null;
|
|
|
|
const ngProjectAsAttrVal = getProjectAsAttrValue(tNode);
|
|
|
|
for (let i = 0; i < projectionSlots.length; i++) {
|
|
|
|
const slotValue = projectionSlots[i];
|
|
|
|
// The last wildcard projection slot should match all nodes which aren't matching
|
|
|
|
// any selector. This is necessary to be backwards compatible with view engine.
|
|
|
|
if (slotValue === '*') {
|
|
|
|
wildcardNgContentIndex = i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// If we ran into an `ngProjectAs` attribute, we should match its parsed selector
|
|
|
|
// to the list of selectors, otherwise we fall back to matching against the node.
|
|
|
|
if (ngProjectAsAttrVal === null ?
|
|
|
|
isNodeMatchingSelectorList(tNode, slotValue, /* isProjectionMode */ true) :
|
|
|
|
isSelectorInSelectorList(ngProjectAsAttrVal, slotValue)) {
|
|
|
|
return i; // first matching selector "captures" a given node
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return wildcardNgContentIndex;
|
|
|
|
}
|
2019-04-08 22:47:23 +02:00
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
/**
|
|
|
|
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
|
|
|
|
* It takes all the selectors from the entire component's template and decides where
|
|
|
|
* each projected node belongs (it re-distributes nodes among "buckets" where each "bucket" is
|
|
|
|
* backed by a selector).
|
|
|
|
*
|
|
|
|
* This function requires CSS selectors to be provided in 2 forms: parsed (by a compiler) and text,
|
|
|
|
* un-parsed form.
|
|
|
|
*
|
|
|
|
* The parsed form is needed for efficient matching of a node against a given CSS selector.
|
|
|
|
* The un-parsed, textual form is needed for support of the ngProjectAs attribute.
|
|
|
|
*
|
|
|
|
* Having a CSS selector in 2 different formats is not ideal, but alternatives have even more
|
|
|
|
* drawbacks:
|
|
|
|
* - having only a textual form would require runtime parsing of CSS selectors;
|
|
|
|
* - we can't have only a parsed as we can't re-construct textual form from it (as entered by a
|
|
|
|
* template author).
|
|
|
|
*
|
2019-05-21 22:00:47 +02:00
|
|
|
* @param projectionSlots? A collection of projection slots. A projection slot can be based
|
|
|
|
* on a parsed CSS selectors or set to the wildcard selector ("*") in order to match
|
|
|
|
* all nodes which do not match any selector. If not specified, a single wildcard
|
|
|
|
* selector projection slot will be defined.
|
2019-04-04 11:41:52 -07:00
|
|
|
*
|
2019-04-10 13:45:26 -07:00
|
|
|
* @codeGenApi
|
2019-04-01 15:36:43 -07:00
|
|
|
*/
|
2019-05-21 22:00:47 +02:00
|
|
|
export function ɵɵprojectionDef(projectionSlots?: ProjectionSlots): void {
|
2019-11-13 16:22:55 -08:00
|
|
|
const componentNode = getLView()[DECLARATION_COMPONENT_VIEW][T_HOST] as TElementNode;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
if (!componentNode.projection) {
|
2019-05-21 22:00:47 +02:00
|
|
|
// If no explicit projection slots are defined, fall back to a single
|
|
|
|
// projection slot with the wildcard selector.
|
|
|
|
const numProjectionSlots = projectionSlots ? projectionSlots.length : 1;
|
2020-04-13 16:40:21 -07:00
|
|
|
const projectionHeads: (TNode|null)[] = componentNode.projection =
|
|
|
|
newArray(numProjectionSlots, null! as TNode);
|
|
|
|
const tails: (TNode|null)[] = projectionHeads.slice();
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
let componentChild: TNode|null = componentNode.child;
|
|
|
|
|
|
|
|
while (componentChild !== null) {
|
2019-05-21 22:00:47 +02:00
|
|
|
const slotIndex =
|
|
|
|
projectionSlots ? matchingProjectionSlotIndex(componentChild, projectionSlots) : 0;
|
2019-04-01 15:36:43 -07:00
|
|
|
|
2019-05-21 22:00:47 +02:00
|
|
|
if (slotIndex !== null) {
|
|
|
|
if (tails[slotIndex]) {
|
2020-04-13 16:40:21 -07:00
|
|
|
tails[slotIndex]!.projectionNext = componentChild;
|
2019-05-21 22:00:47 +02:00
|
|
|
} else {
|
|
|
|
projectionHeads[slotIndex] = componentChild;
|
|
|
|
}
|
|
|
|
tails[slotIndex] = componentChild;
|
2019-04-01 15:36:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
componentChild = componentChild.next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-31 17:11:57 +02:00
|
|
|
let delayProjection = false;
|
|
|
|
export function setDelayProjection(value: boolean) {
|
|
|
|
delayProjection = value;
|
|
|
|
}
|
|
|
|
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
|
|
|
|
* to the projectionDef instruction.
|
|
|
|
*
|
|
|
|
* @param nodeIndex
|
|
|
|
* @param selectorIndex:
|
|
|
|
* - 0 when the selector is `*` (or unspecified as this is the default value),
|
|
|
|
* - 1 based index of the selector from the {@link projectionDef}
|
2019-04-11 11:01:01 -07:00
|
|
|
*
|
2019-04-10 13:45:26 -07:00
|
|
|
* @codeGenApi
|
2020-04-13 16:40:21 -07:00
|
|
|
*/
|
2019-05-17 18:49:21 -07:00
|
|
|
export function ɵɵprojection(
|
2019-04-08 22:47:23 +02:00
|
|
|
nodeIndex: number, selectorIndex: number = 0, attrs?: TAttributes): void {
|
2019-04-01 15:36:43 -07:00
|
|
|
const lView = getLView();
|
2020-01-30 14:57:44 -08:00
|
|
|
const tView = getTView();
|
|
|
|
const tProjectionNode =
|
2020-09-04 13:03:46 -07:00
|
|
|
getOrCreateTNode(tView, nodeIndex, TNodeType.Projection, null, attrs || null);
|
2019-04-01 15:36:43 -07:00
|
|
|
|
|
|
|
// We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views.
|
|
|
|
if (tProjectionNode.projection === null) tProjectionNode.projection = selectorIndex;
|
|
|
|
|
|
|
|
// `<ng-content>` has no content
|
2020-09-14 13:43:44 -07:00
|
|
|
setCurrentTNodeAsNotParent();
|
2019-04-01 15:36:43 -07:00
|
|
|
|
2019-05-31 17:11:57 +02:00
|
|
|
// We might need to delay the projection of nodes if they are in the middle of an i18n block
|
|
|
|
if (!delayProjection) {
|
|
|
|
// re-distribution of projectable nodes is stored on a component's view level
|
2020-01-30 14:57:44 -08:00
|
|
|
applyProjection(tView, lView, tProjectionNode);
|
2019-05-31 17:11:57 +02:00
|
|
|
}
|
2019-04-01 15:36:43 -07:00
|
|
|
}
|