
committed by
Alex Rickabaugh

parent
6cb1adf105
commit
6e5fb99304
@ -22,7 +22,7 @@ export {
|
||||
InjectFlags as ɵInjectFlags,
|
||||
PublicFeature as ɵPublicFeature,
|
||||
NgOnChangesFeature as ɵNgOnChangesFeature,
|
||||
CssSelector as ɵCssSelector,
|
||||
CssSelectorList as ɵCssSelectorList,
|
||||
NC as ɵNC,
|
||||
C as ɵC,
|
||||
E as ɵE,
|
||||
|
@ -123,8 +123,8 @@ export function renderComponent<T>(
|
||||
const componentDef = (componentType as ComponentType<T>).ngComponentDef as ComponentDef<T>;
|
||||
if (componentDef.type != componentType) componentDef.type = componentType;
|
||||
let component: T;
|
||||
// TODO: Replace when flattening CssSelector type
|
||||
const componentTag = componentDef.selector ![0] ![0] ![0];
|
||||
// The first index of the first selector is the tag name.
|
||||
const componentTag = componentDef.selectors ![0] ![0] as string;
|
||||
const hostNode = locateHostElement(rendererFactory, opts.host || componentTag);
|
||||
const rootContext: RootContext = {
|
||||
// Incomplete initialization due to circular reference.
|
||||
|
@ -17,7 +17,7 @@ import {resolveRendererType2} from '../view/util';
|
||||
|
||||
import {diPublic} from './di';
|
||||
import {ComponentDef, ComponentDefFeature, ComponentTemplate, DirectiveDef, DirectiveDefFeature, DirectiveDefListOrFactory, PipeDef, PipeDefListOrFactory} from './interfaces/definition';
|
||||
import {CssSelector} from './interfaces/projection';
|
||||
import {CssSelectorList, SelectorFlags} from './interfaces/projection';
|
||||
|
||||
|
||||
|
||||
@ -42,8 +42,8 @@ export function defineComponent<T>(componentDefinition: {
|
||||
*/
|
||||
type: Type<T>;
|
||||
|
||||
/** The selector that will be used to match nodes to this component. */
|
||||
selector: CssSelector;
|
||||
/** The selectors that will be used to match nodes to this component. */
|
||||
selectors: CssSelectorList;
|
||||
|
||||
/**
|
||||
* Factory method used to create an instance of directive.
|
||||
@ -185,7 +185,7 @@ export function defineComponent<T>(componentDefinition: {
|
||||
onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
|
||||
directiveDefs: componentDefinition.directiveDefs || null,
|
||||
pipeDefs: componentDefinition.pipeDefs || null,
|
||||
selector: componentDefinition.selector
|
||||
selectors: componentDefinition.selectors
|
||||
};
|
||||
const feature = componentDefinition.features;
|
||||
feature && feature.forEach((fn) => fn(def));
|
||||
@ -317,8 +317,8 @@ export const defineDirective = defineComponent as any as<T>(directiveDefinition:
|
||||
*/
|
||||
type: Type<T>;
|
||||
|
||||
/** The selector that will be used to match nodes to this directive. */
|
||||
selector: CssSelector;
|
||||
/** The selectors that will be used to match nodes to this directive. */
|
||||
selectors: CssSelectorList;
|
||||
|
||||
/**
|
||||
* Factory method used to create an instance of directive.
|
||||
|
@ -12,7 +12,7 @@ import {InjectFlags} from './di';
|
||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveType} from './interfaces/definition';
|
||||
|
||||
export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, injectAttribute, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di';
|
||||
export {CssSelector} from './interfaces/projection';
|
||||
export {CssSelectorList} from './interfaces/projection';
|
||||
|
||||
|
||||
|
||||
|
@ -11,14 +11,14 @@ import './ng_dev_mode';
|
||||
import {assertEqual, assertLessThan, assertNotEqual, assertNotNull, assertNull, assertSame} from './assert';
|
||||
import {LContainer, TContainer} from './interfaces/container';
|
||||
import {LInjector} from './interfaces/injector';
|
||||
import {CssSelector, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||
import {CssSelectorList, LProjection, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
|
||||
import {LQueries} from './interfaces/query';
|
||||
import {LView, LViewFlags, LifecycleStage, RootContext, TData, TView} from './interfaces/view';
|
||||
|
||||
import {LContainerNode, LElementNode, LNode, LNodeType, TNodeFlags, LProjectionNode, LTextNode, LViewNode, TNode, TContainerNode, InitialInputData, InitialInputs, PropertyAliases, PropertyAliasValue,} from './interfaces/node';
|
||||
import {assertNodeType} from './node_assert';
|
||||
import {appendChild, insertChild, insertView, appendProjectedNode, removeView, canInsertNativeNode} from './node_manipulation';
|
||||
import {isNodeMatchingSelector, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher';
|
||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefList, DirectiveDefListOrFactory, DirectiveType, PipeDef, PipeDefListOrFactory} from './interfaces/definition';
|
||||
import {RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
|
||||
import {isDifferent, stringify} from './util';
|
||||
@ -532,7 +532,7 @@ function cacheMatchingDirectivesForNode(tNode: TNode): void {
|
||||
|
||||
for (let i = 0; i < registry.length; i++) {
|
||||
const def = registry[i];
|
||||
if (isNodeMatchingSelector(tNode, def.selector !)) {
|
||||
if (isNodeMatchingSelectorList(tNode, def.selectors !)) {
|
||||
if ((def as ComponentDef<any>).template) {
|
||||
if (componentFlag) throwMultipleComponentError(tNode);
|
||||
componentFlag |= TNodeFlags.Component;
|
||||
@ -1567,7 +1567,7 @@ function viewAttached(view: LView): boolean {
|
||||
* @param rawSelectors A collection of CSS selectors in the raw, un-parsed form
|
||||
*/
|
||||
export function projectionDef(
|
||||
index: number, selectors?: CssSelector[], textSelectors?: string[]): void {
|
||||
index: number, selectors?: CssSelectorList[], textSelectors?: string[]): void {
|
||||
const noOfNodeBuckets = selectors ? selectors.length + 1 : 1;
|
||||
const distributedNodes = new Array<LNode[]>(noOfNodeBuckets);
|
||||
for (let i = 0; i < noOfNodeBuckets; i++) {
|
||||
|
@ -12,7 +12,7 @@ import {Provider} from '../../core';
|
||||
import {RendererType2} from '../../render/api';
|
||||
import {Type} from '../../type';
|
||||
import {resolveRendererType2} from '../../view/util';
|
||||
import {CssSelector} from './projection';
|
||||
import {CssSelectorList} from './projection';
|
||||
|
||||
|
||||
/**
|
||||
@ -61,8 +61,8 @@ export interface DirectiveDef<T> {
|
||||
/** Function that makes a directive public to the DI system. */
|
||||
diPublic: ((def: DirectiveDef<any>) => void)|null;
|
||||
|
||||
/** The selector that will be used to match nodes to this directive. */
|
||||
selector: CssSelector;
|
||||
/** The selectors that will be used to match nodes to this directive. */
|
||||
selectors: CssSelectorList;
|
||||
|
||||
/**
|
||||
* A dictionary mapping the inputs' minified property names to their public API names, which
|
||||
|
@ -17,33 +17,61 @@ export interface LProjection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parsed selector in the following format:
|
||||
* [tagName, attr1Name, attr1Val, ..., attrnName, attrnValue, 'class', className1, className2, ...,
|
||||
* classNameN]
|
||||
* Expresses a single CSS Selector.
|
||||
*
|
||||
* * For example, given the following selector:
|
||||
* `div.foo.bar[attr1=val1][attr2]` a parsed format would be:
|
||||
* `['div', 'attr1', 'val1', 'attr2', '', 'class', 'foo', 'bar']`.
|
||||
* Beginning of array
|
||||
* - First index: element name
|
||||
* - Subsequent odd indices: attr keys
|
||||
* - Subsequent even indices: attr values
|
||||
*
|
||||
* Things to notice:
|
||||
* - tag name is always at the position 0
|
||||
* - the `class` attribute is always the last attribute in a pre-parsed array
|
||||
* - class names in a selector are at the end of an array (after the attribute with the name
|
||||
* 'class').
|
||||
* After SelectorFlags.CLASS flag
|
||||
* - Class name values
|
||||
*
|
||||
* SelectorFlags.NOT flag
|
||||
* - Changes the mode to NOT
|
||||
* - Can be combined with other flags to set the element / attr / class mode
|
||||
*
|
||||
* e.g. SelectorFlags.NOT | SelectorFlags.ELEMENT
|
||||
*
|
||||
* Example:
|
||||
* Original: `div.foo.bar[attr1=val1][attr2]`
|
||||
* Parsed: ['div', 'attr1', 'val1', 'attr2', '', SelectorFlags.CLASS, 'foo', 'bar']
|
||||
*
|
||||
* Original: 'div[attr1]:not(.foo[attr2])
|
||||
* Parsed: [
|
||||
* 'div', 'attr1', '',
|
||||
* SelectorFlags.NOT | SelectorFlags.ATTRIBUTE 'attr2', '', SelectorFlags.CLASS, 'foo'
|
||||
* ]
|
||||
*
|
||||
* See more examples in node_selector_matcher_spec.ts
|
||||
*/
|
||||
export type SimpleCssSelector = string[];
|
||||
export type CssSelector = (string | SelectorFlags)[];
|
||||
|
||||
/**
|
||||
* A complex selector expressed as an Array where:
|
||||
* - element at index 0 is a selector (SimpleCSSSelector) to match
|
||||
* - elements at index 1..n is a selector (SimpleCSSSelector) that should NOT match
|
||||
* A list of CssSelectors.
|
||||
*
|
||||
* A directive or component can have multiple selectors. This type is used for
|
||||
* directive defs so any of the selectors in the list will match that directive.
|
||||
*
|
||||
* Original: 'form, [ngForm]'
|
||||
* Parsed: [['form'], ['', 'ngForm', '']]
|
||||
*/
|
||||
export type CssSelectorWithNegations = [SimpleCssSelector | null, SimpleCssSelector[] | null];
|
||||
export type CssSelectorList = CssSelector[];
|
||||
|
||||
/**
|
||||
* A collection of complex selectors (CSSSelectorWithNegations) in a parsed form
|
||||
*/
|
||||
export type CssSelector = CssSelectorWithNegations[];
|
||||
/** Flags used to build up CssSelectors */
|
||||
export const enum SelectorFlags {
|
||||
/** Indicates this is the beginning of a new negative selector */
|
||||
NOT = 0b0001,
|
||||
|
||||
/** Mode for matching attributes */
|
||||
ATTRIBUTE = 0b0010,
|
||||
|
||||
/** Mode for matching tag names */
|
||||
ELEMENT = 0b0100,
|
||||
|
||||
/** Mode for matching class names */
|
||||
CLASS = 0b1000,
|
||||
}
|
||||
|
||||
export const NG_PROJECT_AS_ATTR_NAME = 'ngProjectAs';
|
||||
|
||||
|
@ -10,7 +10,7 @@ import './ng_dev_mode';
|
||||
|
||||
import {assertNotNull} from './assert';
|
||||
import {TNode, unusedValueExportToPlacateAjd as unused1} from './interfaces/node';
|
||||
import {CssSelector, CssSelectorWithNegations, NG_PROJECT_AS_ATTR_NAME, SimpleCssSelector, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
|
||||
import {CssSelector, CssSelectorList, NG_PROJECT_AS_ATTR_NAME, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2;
|
||||
|
||||
@ -35,79 +35,80 @@ function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string):
|
||||
* @param selector
|
||||
* @returns true if node matches the selector.
|
||||
*/
|
||||
export function isNodeMatchingSimpleSelector(tNode: TNode, selector: SimpleCssSelector): boolean {
|
||||
const noOfSelectorParts = selector.length;
|
||||
ngDevMode && assertNotNull(selector[0], 'the selector should have a tag name');
|
||||
const tagNameInSelector = selector[0];
|
||||
export function isNodeMatchingSelector(tNode: TNode, selector: CssSelector): boolean {
|
||||
ngDevMode && assertNotNull(selector[0], 'Selector should have a tag name');
|
||||
|
||||
// check tag tame
|
||||
if (tagNameInSelector !== '' && tagNameInSelector !== tNode.tagName) {
|
||||
return false;
|
||||
}
|
||||
let mode: SelectorFlags = SelectorFlags.ELEMENT;
|
||||
const nodeAttrs = tNode.attrs !;
|
||||
|
||||
// short-circuit case where we are only matching on element's tag name
|
||||
if (noOfSelectorParts === 1) {
|
||||
return true;
|
||||
}
|
||||
// When processing ":not" selectors, we skip to the next ":not" if the
|
||||
// current one doesn't match
|
||||
let skipToNextSelector = false;
|
||||
|
||||
// short-circuit case where an element has no attrs but a selector tries to match some
|
||||
if (noOfSelectorParts > 1 && !tNode.attrs) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < selector.length; i++) {
|
||||
const current = selector[i];
|
||||
if (typeof current === 'number') {
|
||||
// If we finish processing a :not selector and it hasn't failed, return false
|
||||
if (!skipToNextSelector && !isPositive(mode) && !isPositive(current as number)) {
|
||||
return false;
|
||||
}
|
||||
// If we are skipping to the next :not() and this mode flag is positive,
|
||||
// it's a part of the current :not() selector, and we should keep skipping
|
||||
if (skipToNextSelector && isPositive(current)) continue;
|
||||
skipToNextSelector = false;
|
||||
mode = (current as number) | (mode & SelectorFlags.NOT);
|
||||
continue;
|
||||
}
|
||||
|
||||
const attrsInNode = tNode.attrs !;
|
||||
if (skipToNextSelector) continue;
|
||||
|
||||
for (let i = 1; i < noOfSelectorParts; i += 2) {
|
||||
const attrNameInSelector = selector[i];
|
||||
const attrIdxInNode = attrsInNode.indexOf(attrNameInSelector);
|
||||
if (attrIdxInNode % 2 !== 0) { // attribute names are stored at even indexes
|
||||
return false;
|
||||
if (mode & SelectorFlags.ELEMENT) {
|
||||
mode = SelectorFlags.ATTRIBUTE | mode & SelectorFlags.NOT;
|
||||
if (current !== '' && current !== tNode.tagName) {
|
||||
if (isPositive(mode)) return false;
|
||||
skipToNextSelector = true;
|
||||
}
|
||||
} else {
|
||||
const attrValInSelector = selector[i + 1];
|
||||
if (attrValInSelector !== '') {
|
||||
// selector should also match on an attribute value
|
||||
const attrValInNode = attrsInNode[attrIdxInNode + 1];
|
||||
if (attrNameInSelector === 'class') {
|
||||
// iterate over all the remaining items in the selector selector array = class names
|
||||
for (i++; i < noOfSelectorParts; i++) {
|
||||
if (!isCssClassMatching(attrValInNode, selector[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (attrValInSelector !== attrValInNode) {
|
||||
return false;
|
||||
const attrName = mode & SelectorFlags.CLASS ? 'class' : current;
|
||||
const attrIndexInNode = findAttrIndexInNode(attrName, nodeAttrs);
|
||||
|
||||
if (attrIndexInNode === -1) {
|
||||
if (isPositive(mode)) return false;
|
||||
skipToNextSelector = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
const selectorAttrValue = mode & SelectorFlags.CLASS ? current : selector[++i];
|
||||
if (selectorAttrValue !== '') {
|
||||
const nodeAttrValue = nodeAttrs[attrIndexInNode + 1];
|
||||
if (mode & SelectorFlags.CLASS &&
|
||||
!isCssClassMatching(nodeAttrValue, selectorAttrValue as string) ||
|
||||
mode & SelectorFlags.ATTRIBUTE && selectorAttrValue !== nodeAttrValue) {
|
||||
if (isPositive(mode)) return false;
|
||||
skipToNextSelector = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return isPositive(mode) || skipToNextSelector;
|
||||
}
|
||||
|
||||
export function isNodeMatchingSelectorWithNegations(
|
||||
tNode: TNode, selector: CssSelectorWithNegations): boolean {
|
||||
const positiveSelector = selector[0];
|
||||
if (positiveSelector != null && !isNodeMatchingSimpleSelector(tNode, positiveSelector)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// do we have any negation parts in this selector?
|
||||
const negativeSelectors = selector[1];
|
||||
if (negativeSelectors) {
|
||||
for (let i = 0; i < negativeSelectors.length; i++) {
|
||||
// if one of negative selectors matched than the whole selector doesn't match
|
||||
if (isNodeMatchingSimpleSelector(tNode, negativeSelectors[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
function isPositive(mode: SelectorFlags): boolean {
|
||||
return (mode & SelectorFlags.NOT) === 0;
|
||||
}
|
||||
|
||||
export function isNodeMatchingSelector(tNode: TNode, selector: CssSelector): boolean {
|
||||
function findAttrIndexInNode(name: string, attrs: string[] | null): number {
|
||||
if (attrs === null) return -1;
|
||||
for (let i = 0; i < attrs.length; i += 2) {
|
||||
if (attrs[i] === name) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function isNodeMatchingSelectorList(tNode: TNode, selector: CssSelectorList): boolean {
|
||||
for (let i = 0; i < selector.length; i++) {
|
||||
if (isNodeMatchingSelectorWithNegations(tNode, selector[i])) {
|
||||
if (isNodeMatchingSelector(tNode, selector[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -136,13 +137,13 @@ export function getProjectAsAttrValue(tNode: TNode): string|null {
|
||||
* to the raw (un-parsed) CSS selector instead of using standard selector matching logic.
|
||||
*/
|
||||
export function matchingSelectorIndex(
|
||||
tNode: TNode, selectors: CssSelector[], textSelectors: string[]): number {
|
||||
tNode: TNode, selectors: CssSelectorList[], textSelectors: string[]): number {
|
||||
const ngProjectAsAttrVal = getProjectAsAttrValue(tNode);
|
||||
for (let i = 0; i < selectors.length; i++) {
|
||||
// if a node has the ngProjectAs attribute match it against unparsed selector
|
||||
// match a node against a parsed selector only if ngProjectAs attribute is not present
|
||||
if (ngProjectAsAttrVal === textSelectors[i] ||
|
||||
ngProjectAsAttrVal === null && isNodeMatchingSelector(tNode, selectors[i])) {
|
||||
ngProjectAsAttrVal === null && isNodeMatchingSelectorList(tNode, selectors[i])) {
|
||||
return i + 1; // first matching selector "captures" a given node
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user